// 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 #include #include #include #include #include #include #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, ®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; } #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; }