summaryrefslogtreecommitdiff
path: root/mali_kbase/csf/mali_kbase_csf_firmware_core_dump.c
blob: 1ac2483a17db38e0dd8ba8d647662d8b82de8f86 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
 *
 * (C) COPYRIGHT 2021-2023 ARM Limited. All rights reserved.
 *
 * This program is free software and is provided to you under the terms of the
 * GNU General Public License version 2 as published by the Free Software
 * Foundation, and any use by you of this program is subject to the terms
 * of such GNU license.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, you can access it online at
 * http://www.gnu.org/licenses/gpl-2.0.html.
 *
 */

#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/list.h>
#include <linux/file.h>
#include <linux/elf.h>
#include <linux/elfcore.h>
#include <linux/version_compat_defs.h>

#include "mali_kbase.h"
#include "mali_kbase_csf_firmware_core_dump.h"
#include "backend/gpu/mali_kbase_pm_internal.h"

/*
 * FW image header core dump data format supported.
 * Currently only version 0.1 is supported.
 */
#define FW_CORE_DUMP_DATA_VERSION_MAJOR 0
#define FW_CORE_DUMP_DATA_VERSION_MINOR 1

/* Full version of the image header core dump data format */
#define FW_CORE_DUMP_DATA_VERSION \
	((FW_CORE_DUMP_DATA_VERSION_MAJOR << 8) | FW_CORE_DUMP_DATA_VERSION_MINOR)

/* Validity flag to indicate if the MCU registers in the buffer are valid */
#define FW_MCU_STATUS_MASK 0x1
#define FW_MCU_STATUS_VALID (1 << 0)

/* Core dump entry fields */
#define FW_CORE_DUMP_VERSION_INDEX 0
#define FW_CORE_DUMP_START_ADDR_INDEX 1

/* MCU registers stored by a firmware core dump */
struct fw_core_dump_mcu {
	u32 r0;
	u32 r1;
	u32 r2;
	u32 r3;
	u32 r4;
	u32 r5;
	u32 r6;
	u32 r7;
	u32 r8;
	u32 r9;
	u32 r10;
	u32 r11;
	u32 r12;
	u32 sp;
	u32 lr;
	u32 pc;
};

/* Any ELF definitions used in this file are from elf.h/elfcore.h except
 * when specific 32-bit versions are required (mainly for the
 * ELF_PRSTATUS32 note that is used to contain the MCU registers).
 */

/* - 32-bit version of timeval structures used in ELF32 PRSTATUS note. */
struct prstatus32_timeval {
	int tv_sec;
	int tv_usec;
};

/* - Structure defining ELF32 PRSTATUS note contents, as defined by the
 *   GNU binutils BFD library used by GDB, in bfd/hosts/x86-64linux.h.
 *   Note: GDB checks for the size of this structure to be 0x94.
 *   Modified pr_reg (array containing the Arm 32-bit MCU registers) to
 *   use u32[18] instead of elf_gregset32_t to prevent introducing new typedefs.
 */
struct elf_prstatus32 {
	struct elf_siginfo pr_info; /* Info associated with signal. */
	short int pr_cursig; /* Current signal. */
	unsigned int pr_sigpend; /* Set of pending signals. */
	unsigned int pr_sighold; /* Set of held signals. */
	pid_t pr_pid;
	pid_t pr_ppid;
	pid_t pr_pgrp;
	pid_t pr_sid;
	struct prstatus32_timeval pr_utime; /* User time. */
	struct prstatus32_timeval pr_stime; /* System time. */
	struct prstatus32_timeval pr_cutime; /* Cumulative user time. */
	struct prstatus32_timeval pr_cstime; /* Cumulative system time. */
	u32 pr_reg[18]; /* GP registers. */
	int pr_fpvalid; /* True if math copro being used. */
};

/*
 * struct fw_core_dump_seq_off - Iterator for seq_file operations used on 'fw_core_dump'
 * debugfs file.
 * @interface: current firmware memory interface
 * @page_num: current page number (0..) within @interface
 */
struct fw_core_dump_seq_off {
	struct kbase_csf_firmware_interface *interface;
	u32 page_num;
};

/**
 * fw_get_core_dump_mcu - Get the MCU registers saved by a firmware core dump
 *
 * @kbdev: Instance of a GPU platform device that implements a CSF interface.
 * @regs:  Pointer to a core dump mcu struct where the MCU registers are copied
 *         to. Should be allocated by the called.
 *
 * Return: 0 if successfully copied the MCU registers, negative error code otherwise.
 */
static int fw_get_core_dump_mcu(struct kbase_device *kbdev, struct fw_core_dump_mcu *regs)
{
	unsigned int i;
	u32 status = 0;
	u32 data_addr = kbdev->csf.fw_core_dump.mcu_regs_addr;
	u32 *data = (u32 *)regs;

	/* Check if the core dump entry exposed the buffer */
	if (!regs || !kbdev->csf.fw_core_dump.available)
		return -EPERM;

	/* Check if the data in the buffer is valid, if not, return error */
	kbase_csf_read_firmware_memory(kbdev, data_addr, &status);
	if ((status & FW_MCU_STATUS_MASK) != FW_MCU_STATUS_VALID)
		return -EPERM;

	/* According to image header documentation, the MCU registers core dump
	 * buffer is 32-bit aligned.
	 */
	for (i = 1; i <= sizeof(struct fw_core_dump_mcu) / sizeof(u32); ++i)
		kbase_csf_read_firmware_memory(kbdev, data_addr + i * sizeof(u32), &data[i - 1]);

	return 0;
}

/**
 * fw_core_dump_fill_elf_header - Initializes an ELF32 header
 * @hdr:	ELF32 header to initialize
 * @sections:	Number of entries in the ELF program header table
 *
 * Initializes an ELF32 header for an ARM 32-bit little-endian
 * 'Core file' object file.
 */
static void fw_core_dump_fill_elf_header(struct elf32_hdr *hdr, unsigned int sections)
{
	/* Reset all members in header. */
	memset(hdr, 0, sizeof(*hdr));

	/* Magic number identifying file as an ELF object. */
	memcpy(hdr->e_ident, ELFMAG, SELFMAG);

	/* Identify file as 32-bit, little-endian, using current
	 * ELF header version, with no OS or ABI specific ELF
	 * extensions used.
	 */
	hdr->e_ident[EI_CLASS] = ELFCLASS32;
	hdr->e_ident[EI_DATA] = ELFDATA2LSB;
	hdr->e_ident[EI_VERSION] = EV_CURRENT;
	hdr->e_ident[EI_OSABI] = ELFOSABI_NONE;

	/* 'Core file' type of object file. */
	hdr->e_type = ET_CORE;

	/* ARM 32-bit architecture (AARCH32) */
	hdr->e_machine = EM_ARM;

	/* Object file version: the original format. */
	hdr->e_version = EV_CURRENT;

	/* Offset of program header table in file. */
	hdr->e_phoff = sizeof(struct elf32_hdr);

	/* No processor specific flags. */
	hdr->e_flags = 0;

	/* Size of the ELF header in bytes. */
	hdr->e_ehsize = sizeof(struct elf32_hdr);

	/* Size of the ELF program header entry in bytes. */
	hdr->e_phentsize = sizeof(struct elf32_phdr);

	/* Number of entries in the program header table. */
	hdr->e_phnum = sections;
}

/**
 * fw_core_dump_fill_elf_program_header_note - Initializes an ELF32 program header
 * for holding auxiliary information
 * @phdr:		ELF32 program header
 * @file_offset:	Location of the note in the file in bytes
 * @size:		Size of the note in bytes.
 *
 * Initializes an ELF32 program header describing auxiliary information (containing
 * one or more notes) of @size bytes alltogether located in the file at offset
 * @file_offset.
 */
static void fw_core_dump_fill_elf_program_header_note(struct elf32_phdr *phdr, u32 file_offset,
						      u32 size)
{
	/* Auxiliary information (note) in program header. */
	phdr->p_type = PT_NOTE;

	/* Location of first note in file in bytes. */
	phdr->p_offset = file_offset;

	/* Size of all notes combined in bytes. */
	phdr->p_filesz = size;

	/* Other members not relevant for a note. */
	phdr->p_vaddr = 0;
	phdr->p_paddr = 0;
	phdr->p_memsz = 0;
	phdr->p_align = 0;
	phdr->p_flags = 0;
}

/**
 * fw_core_dump_fill_elf_program_header - Initializes an ELF32 program header for a loadable segment
 * @phdr:		ELF32 program header to initialize.
 * @file_offset:	Location of loadable segment in file in bytes
 *                      (aligned to FW_PAGE_SIZE bytes)
 * @vaddr:		32-bit virtual address where to write the segment
 *                      (aligned to FW_PAGE_SIZE bytes)
 * @size:		Size of the segment in bytes.
 * @flags:		CSF_FIRMWARE_ENTRY_* flags describing access permissions.
 *
 * Initializes an ELF32 program header describing a loadable segment of
 * @size bytes located in the file at offset @file_offset to be loaded
 * at virtual address @vaddr with access permissions as described by
 * CSF_FIRMWARE_ENTRY_* flags in @flags.
 */
static void fw_core_dump_fill_elf_program_header(struct elf32_phdr *phdr, u32 file_offset,
						 u32 vaddr, u32 size, u32 flags)
{
	/* Loadable segment in program header. */
	phdr->p_type = PT_LOAD;

	/* Location of segment in file in bytes. Aligned to p_align bytes. */
	phdr->p_offset = file_offset;

	/* Virtual address of segment. Aligned to p_align bytes. */
	phdr->p_vaddr = vaddr;

	/* Physical address of segment. Not relevant. */
	phdr->p_paddr = 0;

	/* Size of segment in file and memory. */
	phdr->p_filesz = size;
	phdr->p_memsz = size;

	/* Alignment of segment in the file and memory in bytes (integral power of 2). */
	phdr->p_align = FW_PAGE_SIZE;

	/* Set segment access permissions. */
	phdr->p_flags = 0;
	if (flags & CSF_FIRMWARE_ENTRY_READ)
		phdr->p_flags |= PF_R;
	if (flags & CSF_FIRMWARE_ENTRY_WRITE)
		phdr->p_flags |= PF_W;
	if (flags & CSF_FIRMWARE_ENTRY_EXECUTE)
		phdr->p_flags |= PF_X;
}

/**
 * fw_core_dump_get_prstatus_note_size - Calculates size of a ELF32 PRSTATUS note
 * @name:	Name given to the PRSTATUS note.
 *
 * Calculates the size of a 32-bit PRSTATUS note (which contains information
 * about a process like the current MCU registers) taking into account
 * @name must be padded to a 4-byte multiple.
 *
 * Return: size of 32-bit PRSTATUS note in bytes.
 */
static unsigned int fw_core_dump_get_prstatus_note_size(char *name)
{
	return sizeof(struct elf32_note) + roundup(strlen(name) + 1, 4) +
	       sizeof(struct elf_prstatus32);
}

/**
 * fw_core_dump_fill_elf_prstatus - Initializes an ELF32 PRSTATUS structure
 * @prs:	ELF32 PRSTATUS note to initialize
 * @regs:	MCU registers to copy into the PRSTATUS note
 *
 * Initializes an ELF32 PRSTATUS structure with MCU registers @regs.
 * Other process information is N/A for CSF Firmware.
 */
static void fw_core_dump_fill_elf_prstatus(struct elf_prstatus32 *prs,
					   struct fw_core_dump_mcu *regs)
{
	/* Only fill in registers (32-bit) of PRSTATUS note. */
	memset(prs, 0, sizeof(*prs));
	prs->pr_reg[0] = regs->r0;
	prs->pr_reg[1] = regs->r1;
	prs->pr_reg[2] = regs->r2;
	prs->pr_reg[3] = regs->r3;
	prs->pr_reg[4] = regs->r4;
	prs->pr_reg[5] = regs->r5;
	prs->pr_reg[6] = regs->r0;
	prs->pr_reg[7] = regs->r7;
	prs->pr_reg[8] = regs->r8;
	prs->pr_reg[9] = regs->r9;
	prs->pr_reg[10] = regs->r10;
	prs->pr_reg[11] = regs->r11;
	prs->pr_reg[12] = regs->r12;
	prs->pr_reg[13] = regs->sp;
	prs->pr_reg[14] = regs->lr;
	prs->pr_reg[15] = regs->pc;
}

/**
 * fw_core_dump_create_prstatus_note - Creates an ELF32 PRSTATUS note
 * @name:	Name for the PRSTATUS note
 * @prs:	ELF32 PRSTATUS structure to put in the PRSTATUS note
 * @created_prstatus_note:
 *		Pointer to the allocated ELF32 PRSTATUS note
 *
 * Creates an ELF32 note with one PRSTATUS entry containing the
 * ELF32 PRSTATUS structure @prs. Caller needs to free the created note in
 * @created_prstatus_note.
 *
 * Return: 0 on failure, otherwise size of ELF32 PRSTATUS note in bytes.
 */
static unsigned int fw_core_dump_create_prstatus_note(char *name, struct elf_prstatus32 *prs,
						      struct elf32_note **created_prstatus_note)
{
	struct elf32_note *note;
	unsigned int note_name_sz;
	unsigned int note_sz;

	/* Allocate memory for ELF32 note containing a PRSTATUS note. */
	note_name_sz = strlen(name) + 1;
	note_sz = sizeof(struct elf32_note) + roundup(note_name_sz, 4) +
		  sizeof(struct elf_prstatus32);
	note = kmalloc(note_sz, GFP_KERNEL);
	if (!note)
		return 0;

	/* Fill in ELF32 note with one entry for a PRSTATUS note. */
	note->n_namesz = note_name_sz;
	note->n_descsz = sizeof(struct elf_prstatus32);
	note->n_type = NT_PRSTATUS;
	memcpy(note + 1, name, note_name_sz);
	memcpy((char *)(note + 1) + roundup(note_name_sz, 4), prs, sizeof(*prs));

	/* Return pointer and size of the created ELF32 note. */
	*created_prstatus_note = note;
	return note_sz;
}

/**
 * fw_core_dump_write_elf_header - Writes ELF header for the FW core dump
 * @m: the seq_file handle
 *
 * Writes the ELF header of the core dump including program headers for
 * memory sections and a note containing the current MCU register
 * values.
 *
 * Excludes memory sections without read access permissions or
 * are for protected memory.
 *
 * The data written is as follows:
 * - ELF header
 * - ELF PHDRs for memory sections
 * - ELF PHDR for program header NOTE
 * - ELF PRSTATUS note
 * - 0-bytes padding to multiple of ELF_EXEC_PAGESIZE
 *
 * The actual memory section dumps should follow this (not written
 * by this function).
 *
 * Retrieves the necessary information via the struct
 * fw_core_dump_data stored in the private member of the seq_file
 * handle.
 *
 * Return:
 * * 0		- success
 * * -ENOMEM	- not enough memory for allocating ELF32 note
 */
int fw_core_dump_write_elf_header(struct seq_file *m)
{
	struct elf32_hdr hdr;
	struct elf32_phdr phdr;
	struct fw_core_dump_data *dump_data = m->private;
	struct kbase_device *const kbdev = dump_data->kbdev;
	struct kbase_csf_firmware_interface *interface;
	struct elf_prstatus32 elf_prs;
	struct elf32_note *elf_prstatus_note;
	unsigned int sections = 0;
	unsigned int elf_prstatus_note_size;
	u32 elf_prstatus_offset;
	u32 elf_phdr_note_offset;
	u32 elf_memory_sections_data_offset;
	u32 total_pages = 0;
	u32 padding_size, *padding;
	struct fw_core_dump_mcu regs = { 0 };

	CSTD_UNUSED(total_pages);

	/* Count number of memory sections. */
	list_for_each_entry(interface, &kbdev->csf.firmware_interfaces, node) {
		/* Skip memory sections that cannot be read or are protected. */
		if ((interface->flags & CSF_FIRMWARE_ENTRY_PROTECTED) ||
		    (interface->flags & CSF_FIRMWARE_ENTRY_READ) == 0)
			continue;
		sections++;
	}

	/* Prepare ELF header. */
	fw_core_dump_fill_elf_header(&hdr, sections + 1);
	seq_write(m, &hdr, sizeof(struct elf32_hdr));

	elf_prstatus_note_size = fw_core_dump_get_prstatus_note_size("CORE");
	/* PHDRs of PT_LOAD type. */
	elf_phdr_note_offset = sizeof(struct elf32_hdr) + sections * sizeof(struct elf32_phdr);
	/* PHDR of PT_NOTE type. */
	elf_prstatus_offset = elf_phdr_note_offset + sizeof(struct elf32_phdr);
	elf_memory_sections_data_offset = elf_prstatus_offset + elf_prstatus_note_size;

	/* Calculate padding size to page offset. */
	padding_size = roundup(elf_memory_sections_data_offset, ELF_EXEC_PAGESIZE) -
		       elf_memory_sections_data_offset;
	elf_memory_sections_data_offset += padding_size;

	/* Prepare ELF program header table. */
	list_for_each_entry(interface, &kbdev->csf.firmware_interfaces, node) {
		/* Skip memory sections that cannot be read or are protected. */
		if ((interface->flags & CSF_FIRMWARE_ENTRY_PROTECTED) ||
		    (interface->flags & CSF_FIRMWARE_ENTRY_READ) == 0)
			continue;

		fw_core_dump_fill_elf_program_header(&phdr, elf_memory_sections_data_offset,
						     interface->virtual,
						     interface->num_pages * FW_PAGE_SIZE,
						     interface->flags);

		seq_write(m, &phdr, sizeof(struct elf32_phdr));

		elf_memory_sections_data_offset += interface->num_pages * FW_PAGE_SIZE;
		total_pages += interface->num_pages;
	}

	/* Prepare PHDR of PT_NOTE type. */
	fw_core_dump_fill_elf_program_header_note(&phdr, elf_prstatus_offset,
						  elf_prstatus_note_size);
	seq_write(m, &phdr, sizeof(struct elf32_phdr));

	/* Prepare ELF note of PRSTATUS type. */
	if (fw_get_core_dump_mcu(kbdev, &regs))
		dev_dbg(kbdev->dev, "MCU Registers not available, all registers set to zero");
	/* Even if MCU Registers are not available the ELF prstatus is still
	 * filled with the registers equal to zero.
	 */
	fw_core_dump_fill_elf_prstatus(&elf_prs, &regs);
	elf_prstatus_note_size =
		fw_core_dump_create_prstatus_note("CORE", &elf_prs, &elf_prstatus_note);
	if (elf_prstatus_note_size == 0)
		return -ENOMEM;

	seq_write(m, elf_prstatus_note, elf_prstatus_note_size);
	kfree(elf_prstatus_note);

	/* Pad file to page size. */
	padding = kzalloc(padding_size, GFP_KERNEL);
	seq_write(m, padding, padding_size);
	kfree(padding);

	return 0;
}

#define MAX_FW_CORE_DUMP_HEADER_SIZE (1 << 14)

/**
 * get_fw_core_dump_size - Get firmware core dump size
 * @kbdev: Instance of a GPU platform device that implements a CSF interface.
 *
 * Return: size on success, -1 otherwise.
 */
size_t get_fw_core_dump_size(struct kbase_device *kbdev)
{
	static char buffer[MAX_FW_CORE_DUMP_HEADER_SIZE];
	size_t size;
	struct fw_core_dump_data private = {.kbdev = kbdev};
	struct seq_file m = {.private = &private, .buf = buffer, .size = MAX_FW_CORE_DUMP_HEADER_SIZE};
	struct kbase_csf_firmware_interface *interface;

	fw_core_dump_write_elf_header(&m);
	if (unlikely(m.count >= m.size)) {
		dev_warn(kbdev->dev, "firmware core dump header may be larger than buffer size");
		return -1;
	}
	size = m.count;

	list_for_each_entry(interface, &kbdev->csf.firmware_interfaces, node) {
		/* Skip memory sections that cannot be read or are protected. */
		if ((interface->flags & CSF_FIRMWARE_ENTRY_PROTECTED) ||
		    (interface->flags & CSF_FIRMWARE_ENTRY_READ) == 0)
			continue;

		size += interface->num_pages * FW_PAGE_SIZE;
	}

	return size;
}

/**
 * fw_core_dump_create - Requests firmware to save state for a firmware core dump
 * @kbdev: Instance of a GPU platform device that implements a CSF interface.
 *
 * Return: 0 on success, error code otherwise.
 */
int fw_core_dump_create(struct kbase_device *kbdev)
{
	int err;

	/* Ensure MCU is active before requesting the core dump. */
	kbase_csf_scheduler_pm_active(kbdev);
	err = kbase_csf_scheduler_killable_wait_mcu_active(kbdev);
	if (!err)
		err = kbase_csf_firmware_req_core_dump(kbdev);

	kbase_csf_scheduler_pm_idle(kbdev);

	return err;
}

/**
 * fw_core_dump_seq_start - seq_file start operation for firmware core dump file
 * @m: the seq_file handle
 * @_pos: holds the current position in pages
 *        (0 or most recent position used in previous session)
 *
 * Starts a seq_file session, positioning the iterator for the session to page @_pos - 1
 * within the firmware interface memory sections. @_pos value 0 is used to indicate the
 * position of the ELF header at the start of the file.
 *
 * Retrieves the necessary information via the struct fw_core_dump_data stored in
 * the private member of the seq_file handle.
 *
 * Return:
 * * iterator pointer	- pointer to iterator struct fw_core_dump_seq_off
 * * SEQ_START_TOKEN	- special iterator pointer indicating its is the start of the file
 * * NULL		- iterator could not be allocated
 */
static void *fw_core_dump_seq_start(struct seq_file *m, loff_t *_pos)
{
	struct fw_core_dump_data *dump_data = m->private;
	struct fw_core_dump_seq_off *data;
	struct kbase_csf_firmware_interface *interface;
	loff_t pos = *_pos;

	if (pos == 0)
		return SEQ_START_TOKEN;

	/* Move iterator in the right position based on page number within
	 * available pages of firmware interface memory sections.
	 */
	pos--; /* ignore start token */
	list_for_each_entry(interface, &dump_data->kbdev->csf.firmware_interfaces, node) {
		/* Skip memory sections that cannot be read or are protected. */
		if ((interface->flags & CSF_FIRMWARE_ENTRY_PROTECTED) ||
		    (interface->flags & CSF_FIRMWARE_ENTRY_READ) == 0)
			continue;

		if (pos >= interface->num_pages) {
			pos -= interface->num_pages;
		} else {
			data = kmalloc(sizeof(*data), GFP_KERNEL);
			if (!data)
				return NULL;
			data->interface = interface;
			data->page_num = pos;
			return data;
		}
	}

	return NULL;
}

/**
 * fw_core_dump_seq_stop - seq_file stop operation for firmware core dump file
 * @m: the seq_file handle
 * @v: the current iterator (pointer to struct fw_core_dump_seq_off)
 *
 * Closes the current session and frees any memory related.
 */
static void fw_core_dump_seq_stop(struct seq_file *m, void *v)
{
	CSTD_UNUSED(m);
	kfree(v);
}

/**
 * fw_core_dump_seq_next - seq_file next operation for firmware core dump file
 * @m: the seq_file handle
 * @v: the current iterator (pointer to struct fw_core_dump_seq_off)
 * @pos: holds the current position in pages
 *        (0 or most recent position used in previous session)
 *
 * Moves the iterator @v forward to the next page within the firmware interface
 * memory sections and returns the updated position in @pos.
 * @v value SEQ_START_TOKEN indicates the ELF header position.
 *
 * Return:
 * * iterator pointer	- pointer to iterator struct fw_core_dump_seq_off
 * * NULL		- iterator could not be allocated
 */
static void *fw_core_dump_seq_next(struct seq_file *m, void *v, loff_t *pos)
{
	struct fw_core_dump_data *dump_data = m->private;
	struct fw_core_dump_seq_off *data = v;
	struct kbase_csf_firmware_interface *interface;
	struct list_head *interfaces = &dump_data->kbdev->csf.firmware_interfaces;

	/* Is current position at the ELF header ? */
	if (v == SEQ_START_TOKEN) {
		if (list_empty(interfaces))
			return NULL;

		/* Prepare iterator for starting at first page in firmware interface
		 * memory sections.
		 */
		data = kmalloc(sizeof(*data), GFP_KERNEL);
		if (!data)
			return NULL;
		data->interface =
			list_first_entry(interfaces, struct kbase_csf_firmware_interface, node);
		data->page_num = 0;
		++*pos;
		return data;
	}

	/* First attempt to satisfy from current firmware interface memory section. */
	interface = data->interface;
	if (data->page_num + 1 < interface->num_pages) {
		data->page_num++;
		++*pos;
		return data;
	}

	/* Need next firmware interface memory section. This could be the last one. */
	if (list_is_last(&interface->node, interfaces)) {
		kfree(data);
		return NULL;
	}

	/* Move to first page in next firmware interface memory section. */
	data->interface = list_next_entry(interface, node);
	data->page_num = 0;
	++*pos;

	return data;
}

/**
 * fw_core_dump_seq_show - seq_file show operation for firmware core dump file
 * @m: the seq_file handle
 * @v: the current iterator (pointer to struct fw_core_dump_seq_off)
 *
 * Writes the current page in a firmware interface memory section indicated
 * by the iterator @v to the file. If @v is SEQ_START_TOKEN the ELF
 * header is written.
 *
 * Return: 0 on success, error code otherwise.
 */
static int fw_core_dump_seq_show(struct seq_file *m, void *v)
{
	struct fw_core_dump_seq_off *data = v;
	struct page *page;
	u32 *p;

	/* Either write the ELF header or current page. */
	if (v == SEQ_START_TOKEN)
		return fw_core_dump_write_elf_header(m);

	/* Write the current page. */
	page = as_page(data->interface->phys[data->page_num]);
	p = kbase_kmap_atomic(page);
	seq_write(m, p, FW_PAGE_SIZE);
	kbase_kunmap_atomic(p);

	return 0;
}

/* Sequence file operations for firmware core dump file. */
static const struct seq_operations fw_core_dump_seq_ops = {
	.start = fw_core_dump_seq_start,
	.next = fw_core_dump_seq_next,
	.stop = fw_core_dump_seq_stop,
	.show = fw_core_dump_seq_show,
};

/**
 * fw_core_dump_debugfs_open - callback for opening the 'fw_core_dump' debugfs file
 * @inode: inode of the file
 * @file:  file pointer
 *
 * Prepares for servicing a write request to request a core dump from firmware and
 * a read request to retrieve the core dump.
 *
 * Returns an error if the firmware is not initialized yet.
 *
 * Return: 0 on success, error code otherwise.
 */
static int fw_core_dump_debugfs_open(struct inode *inode, struct file *file)
{
	struct kbase_device *const kbdev = inode->i_private;
	struct fw_core_dump_data *dump_data;
	int ret;

	/* Fail if firmware is not initialized yet. */
	if (!kbdev->csf.firmware_inited) {
		ret = -ENODEV;
		goto open_fail;
	}

	/* Open a sequence file for iterating through the pages in the
	 * firmware interface memory pages. seq_open stores a
	 * struct seq_file * in the private_data field of @file.
	 */
	ret = seq_open(file, &fw_core_dump_seq_ops);
	if (ret)
		goto open_fail;

	/* Allocate a context for sequence file operations. */
	dump_data = kmalloc(sizeof(*dump_data), GFP_KERNEL);
	if (!dump_data) {
		ret = -ENOMEM;
		goto out;
	}

	/* Kbase device will be shared with sequence file operations. */
	dump_data->kbdev = kbdev;

	/* Link our sequence file context. */
	((struct seq_file *)file->private_data)->private = dump_data;

	return 0;
out:
	seq_release(inode, file);
open_fail:
	return ret;
}

/**
 * fw_core_dump_debugfs_write - callback for a write to the 'fw_core_dump' debugfs file
 * @file:  file pointer
 * @ubuf:  user buffer containing data to store
 * @count: number of bytes in user buffer
 * @ppos:  file position
 *
 * Any data written to the file triggers a firmware core dump request which
 * subsequently can be retrieved by reading from the file.
 *
 * Return: @count if the function succeeded. An error code on failure.
 */
static ssize_t fw_core_dump_debugfs_write(struct file *file, const char __user *ubuf, size_t count,
					  loff_t *ppos)
{
	ssize_t err;
	struct fw_core_dump_data *dump_data = ((struct seq_file *)file->private_data)->private;
	struct kbase_device *const kbdev = dump_data->kbdev;

	CSTD_UNUSED(ubuf);
	CSTD_UNUSED(ppos);

	err = fw_core_dump_create(kbdev);

	return err ? err : (ssize_t)count;
}

/**
 * fw_core_dump_debugfs_release - callback for releasing the 'fw_core_dump' debugfs file
 * @inode: inode of the file
 * @file:  file pointer
 *
 * Return: 0 on success, error code otherwise.
 */
static int fw_core_dump_debugfs_release(struct inode *inode, struct file *file)
{
	struct fw_core_dump_data *dump_data = ((struct seq_file *)file->private_data)->private;

	seq_release(inode, file);

	kfree(dump_data);

	return 0;
}
/* Debugfs file operations for firmware core dump file. */
static const struct file_operations kbase_csf_fw_core_dump_fops = {
	.owner = THIS_MODULE,
	.open = fw_core_dump_debugfs_open,
	.read = seq_read,
	.write = fw_core_dump_debugfs_write,
	.llseek = seq_lseek,
	.release = fw_core_dump_debugfs_release,
};

void kbase_csf_firmware_core_dump_init(struct kbase_device *const kbdev)
{
#if IS_ENABLED(CONFIG_DEBUG_FS)
	debugfs_create_file("fw_core_dump", 0600, kbdev->mali_debugfs_directory, kbdev,
			    &kbase_csf_fw_core_dump_fops);
#endif /* CONFIG_DEBUG_FS */
}

int kbase_csf_firmware_core_dump_entry_parse(struct kbase_device *kbdev, const u32 *entry)
{
	/* Casting to u16 as version is defined by bits 15:0 */
	kbdev->csf.fw_core_dump.version = (u16)entry[FW_CORE_DUMP_VERSION_INDEX];

	if (kbdev->csf.fw_core_dump.version != FW_CORE_DUMP_DATA_VERSION)
		return -EPERM;

	kbdev->csf.fw_core_dump.mcu_regs_addr = entry[FW_CORE_DUMP_START_ADDR_INDEX];
	kbdev->csf.fw_core_dump.available = true;

	return 0;
}