summaryrefslogtreecommitdiff
path: root/mali_kbase/csf/mali_kbase_csf_firmware_core_dump.c
diff options
context:
space:
mode:
Diffstat (limited to 'mali_kbase/csf/mali_kbase_csf_firmware_core_dump.c')
-rw-r--r--mali_kbase/csf/mali_kbase_csf_firmware_core_dump.c833
1 files changed, 833 insertions, 0 deletions
diff --git a/mali_kbase/csf/mali_kbase_csf_firmware_core_dump.c b/mali_kbase/csf/mali_kbase_csf_firmware_core_dump.c
new file mode 100644
index 0000000..e371db2
--- /dev/null
+++ b/mali_kbase/csf/mali_kbase_csf_firmware_core_dump.c
@@ -0,0 +1,833 @@
+// 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)
+{
+ 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)
+{
+ int 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(ppos);
+
+ err = fw_core_dump_create(kbdev);
+
+ return err ? err : 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;
+}