| // 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" |
| |
| /* Page size in bytes in use by MCU. */ |
| #define FW_PAGE_SIZE 4096 |
| |
| /* |
| * 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_cursignal; /* 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_data - Context for seq_file operations used on 'fw_core_dump' |
| * debugfs file. |
| * @kbdev: Instance of a GPU platform device that implements a CSF interface. |
| */ |
| struct fw_core_dump_data { |
| struct kbase_device *kbdev; |
| }; |
| |
| /* |
| * 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 |
| */ |
| static 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 }; |
| |
| /* 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, ®s)) |
| 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, ®s); |
| 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; |
| } |
| |
| /** |
| * 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. |
| */ |
| static 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_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; |
| } |