18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Coredump functionality for Remoteproc framework. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (c) 2020, The Linux Foundation. All rights reserved. 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/completion.h> 98c2ecf20Sopenharmony_ci#include <linux/devcoredump.h> 108c2ecf20Sopenharmony_ci#include <linux/device.h> 118c2ecf20Sopenharmony_ci#include <linux/kernel.h> 128c2ecf20Sopenharmony_ci#include <linux/remoteproc.h> 138c2ecf20Sopenharmony_ci#include "remoteproc_internal.h" 148c2ecf20Sopenharmony_ci#include "remoteproc_elf_helpers.h" 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_cistruct rproc_coredump_state { 178c2ecf20Sopenharmony_ci struct rproc *rproc; 188c2ecf20Sopenharmony_ci void *header; 198c2ecf20Sopenharmony_ci struct completion dump_done; 208c2ecf20Sopenharmony_ci}; 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci/** 238c2ecf20Sopenharmony_ci * rproc_coredump_cleanup() - clean up dump_segments list 248c2ecf20Sopenharmony_ci * @rproc: the remote processor handle 258c2ecf20Sopenharmony_ci */ 268c2ecf20Sopenharmony_civoid rproc_coredump_cleanup(struct rproc *rproc) 278c2ecf20Sopenharmony_ci{ 288c2ecf20Sopenharmony_ci struct rproc_dump_segment *entry, *tmp; 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci list_for_each_entry_safe(entry, tmp, &rproc->dump_segments, node) { 318c2ecf20Sopenharmony_ci list_del(&entry->node); 328c2ecf20Sopenharmony_ci kfree(entry); 338c2ecf20Sopenharmony_ci } 348c2ecf20Sopenharmony_ci} 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ci/** 378c2ecf20Sopenharmony_ci * rproc_coredump_add_segment() - add segment of device memory to coredump 388c2ecf20Sopenharmony_ci * @rproc: handle of a remote processor 398c2ecf20Sopenharmony_ci * @da: device address 408c2ecf20Sopenharmony_ci * @size: size of segment 418c2ecf20Sopenharmony_ci * 428c2ecf20Sopenharmony_ci * Add device memory to the list of segments to be included in a coredump for 438c2ecf20Sopenharmony_ci * the remoteproc. 448c2ecf20Sopenharmony_ci * 458c2ecf20Sopenharmony_ci * Return: 0 on success, negative errno on error. 468c2ecf20Sopenharmony_ci */ 478c2ecf20Sopenharmony_ciint rproc_coredump_add_segment(struct rproc *rproc, dma_addr_t da, size_t size) 488c2ecf20Sopenharmony_ci{ 498c2ecf20Sopenharmony_ci struct rproc_dump_segment *segment; 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_ci segment = kzalloc(sizeof(*segment), GFP_KERNEL); 528c2ecf20Sopenharmony_ci if (!segment) 538c2ecf20Sopenharmony_ci return -ENOMEM; 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci segment->da = da; 568c2ecf20Sopenharmony_ci segment->size = size; 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci list_add_tail(&segment->node, &rproc->dump_segments); 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ci return 0; 618c2ecf20Sopenharmony_ci} 628c2ecf20Sopenharmony_ciEXPORT_SYMBOL(rproc_coredump_add_segment); 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci/** 658c2ecf20Sopenharmony_ci * rproc_coredump_add_custom_segment() - add custom coredump segment 668c2ecf20Sopenharmony_ci * @rproc: handle of a remote processor 678c2ecf20Sopenharmony_ci * @da: device address 688c2ecf20Sopenharmony_ci * @size: size of segment 698c2ecf20Sopenharmony_ci * @dumpfn: custom dump function called for each segment during coredump 708c2ecf20Sopenharmony_ci * @priv: private data 718c2ecf20Sopenharmony_ci * 728c2ecf20Sopenharmony_ci * Add device memory to the list of segments to be included in the coredump 738c2ecf20Sopenharmony_ci * and associate the segment with the given custom dump function and private 748c2ecf20Sopenharmony_ci * data. 758c2ecf20Sopenharmony_ci * 768c2ecf20Sopenharmony_ci * Return: 0 on success, negative errno on error. 778c2ecf20Sopenharmony_ci */ 788c2ecf20Sopenharmony_ciint rproc_coredump_add_custom_segment(struct rproc *rproc, 798c2ecf20Sopenharmony_ci dma_addr_t da, size_t size, 808c2ecf20Sopenharmony_ci void (*dumpfn)(struct rproc *rproc, 818c2ecf20Sopenharmony_ci struct rproc_dump_segment *segment, 828c2ecf20Sopenharmony_ci void *dest, size_t offset, 838c2ecf20Sopenharmony_ci size_t size), 848c2ecf20Sopenharmony_ci void *priv) 858c2ecf20Sopenharmony_ci{ 868c2ecf20Sopenharmony_ci struct rproc_dump_segment *segment; 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci segment = kzalloc(sizeof(*segment), GFP_KERNEL); 898c2ecf20Sopenharmony_ci if (!segment) 908c2ecf20Sopenharmony_ci return -ENOMEM; 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci segment->da = da; 938c2ecf20Sopenharmony_ci segment->size = size; 948c2ecf20Sopenharmony_ci segment->priv = priv; 958c2ecf20Sopenharmony_ci segment->dump = dumpfn; 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci list_add_tail(&segment->node, &rproc->dump_segments); 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci return 0; 1008c2ecf20Sopenharmony_ci} 1018c2ecf20Sopenharmony_ciEXPORT_SYMBOL(rproc_coredump_add_custom_segment); 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci/** 1048c2ecf20Sopenharmony_ci * rproc_coredump_set_elf_info() - set coredump elf information 1058c2ecf20Sopenharmony_ci * @rproc: handle of a remote processor 1068c2ecf20Sopenharmony_ci * @class: elf class for coredump elf file 1078c2ecf20Sopenharmony_ci * @machine: elf machine for coredump elf file 1088c2ecf20Sopenharmony_ci * 1098c2ecf20Sopenharmony_ci * Set elf information which will be used for coredump elf file. 1108c2ecf20Sopenharmony_ci * 1118c2ecf20Sopenharmony_ci * Return: 0 on success, negative errno on error. 1128c2ecf20Sopenharmony_ci */ 1138c2ecf20Sopenharmony_ciint rproc_coredump_set_elf_info(struct rproc *rproc, u8 class, u16 machine) 1148c2ecf20Sopenharmony_ci{ 1158c2ecf20Sopenharmony_ci if (class != ELFCLASS64 && class != ELFCLASS32) 1168c2ecf20Sopenharmony_ci return -EINVAL; 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci rproc->elf_class = class; 1198c2ecf20Sopenharmony_ci rproc->elf_machine = machine; 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci return 0; 1228c2ecf20Sopenharmony_ci} 1238c2ecf20Sopenharmony_ciEXPORT_SYMBOL(rproc_coredump_set_elf_info); 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_cistatic void rproc_coredump_free(void *data) 1268c2ecf20Sopenharmony_ci{ 1278c2ecf20Sopenharmony_ci struct rproc_coredump_state *dump_state = data; 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci vfree(dump_state->header); 1308c2ecf20Sopenharmony_ci complete(&dump_state->dump_done); 1318c2ecf20Sopenharmony_ci} 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_cistatic void *rproc_coredump_find_segment(loff_t user_offset, 1348c2ecf20Sopenharmony_ci struct list_head *segments, 1358c2ecf20Sopenharmony_ci size_t *data_left) 1368c2ecf20Sopenharmony_ci{ 1378c2ecf20Sopenharmony_ci struct rproc_dump_segment *segment; 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci list_for_each_entry(segment, segments, node) { 1408c2ecf20Sopenharmony_ci if (user_offset < segment->size) { 1418c2ecf20Sopenharmony_ci *data_left = segment->size - user_offset; 1428c2ecf20Sopenharmony_ci return segment; 1438c2ecf20Sopenharmony_ci } 1448c2ecf20Sopenharmony_ci user_offset -= segment->size; 1458c2ecf20Sopenharmony_ci } 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci *data_left = 0; 1488c2ecf20Sopenharmony_ci return NULL; 1498c2ecf20Sopenharmony_ci} 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_cistatic void rproc_copy_segment(struct rproc *rproc, void *dest, 1528c2ecf20Sopenharmony_ci struct rproc_dump_segment *segment, 1538c2ecf20Sopenharmony_ci size_t offset, size_t size) 1548c2ecf20Sopenharmony_ci{ 1558c2ecf20Sopenharmony_ci void *ptr; 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci if (segment->dump) { 1588c2ecf20Sopenharmony_ci segment->dump(rproc, segment, dest, offset, size); 1598c2ecf20Sopenharmony_ci } else { 1608c2ecf20Sopenharmony_ci ptr = rproc_da_to_va(rproc, segment->da + offset, size); 1618c2ecf20Sopenharmony_ci if (!ptr) { 1628c2ecf20Sopenharmony_ci dev_err(&rproc->dev, 1638c2ecf20Sopenharmony_ci "invalid copy request for segment %pad with offset %zu and size %zu)\n", 1648c2ecf20Sopenharmony_ci &segment->da, offset, size); 1658c2ecf20Sopenharmony_ci memset(dest, 0xff, size); 1668c2ecf20Sopenharmony_ci } else { 1678c2ecf20Sopenharmony_ci memcpy(dest, ptr, size); 1688c2ecf20Sopenharmony_ci } 1698c2ecf20Sopenharmony_ci } 1708c2ecf20Sopenharmony_ci} 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_cistatic ssize_t rproc_coredump_read(char *buffer, loff_t offset, size_t count, 1738c2ecf20Sopenharmony_ci void *data, size_t header_sz) 1748c2ecf20Sopenharmony_ci{ 1758c2ecf20Sopenharmony_ci size_t seg_data, bytes_left = count; 1768c2ecf20Sopenharmony_ci ssize_t copy_sz; 1778c2ecf20Sopenharmony_ci struct rproc_dump_segment *seg; 1788c2ecf20Sopenharmony_ci struct rproc_coredump_state *dump_state = data; 1798c2ecf20Sopenharmony_ci struct rproc *rproc = dump_state->rproc; 1808c2ecf20Sopenharmony_ci void *elfcore = dump_state->header; 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci /* Copy the vmalloc'ed header first. */ 1838c2ecf20Sopenharmony_ci if (offset < header_sz) { 1848c2ecf20Sopenharmony_ci copy_sz = memory_read_from_buffer(buffer, count, &offset, 1858c2ecf20Sopenharmony_ci elfcore, header_sz); 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci return copy_sz; 1888c2ecf20Sopenharmony_ci } 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci /* 1918c2ecf20Sopenharmony_ci * Find out the segment memory chunk to be copied based on offset. 1928c2ecf20Sopenharmony_ci * Keep copying data until count bytes are read. 1938c2ecf20Sopenharmony_ci */ 1948c2ecf20Sopenharmony_ci while (bytes_left) { 1958c2ecf20Sopenharmony_ci seg = rproc_coredump_find_segment(offset - header_sz, 1968c2ecf20Sopenharmony_ci &rproc->dump_segments, 1978c2ecf20Sopenharmony_ci &seg_data); 1988c2ecf20Sopenharmony_ci /* EOF check */ 1998c2ecf20Sopenharmony_ci if (!seg) { 2008c2ecf20Sopenharmony_ci dev_info(&rproc->dev, "Ramdump done, %lld bytes read", 2018c2ecf20Sopenharmony_ci offset); 2028c2ecf20Sopenharmony_ci break; 2038c2ecf20Sopenharmony_ci } 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_ci copy_sz = min_t(size_t, bytes_left, seg_data); 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci rproc_copy_segment(rproc, buffer, seg, seg->size - seg_data, 2088c2ecf20Sopenharmony_ci copy_sz); 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci offset += copy_sz; 2118c2ecf20Sopenharmony_ci buffer += copy_sz; 2128c2ecf20Sopenharmony_ci bytes_left -= copy_sz; 2138c2ecf20Sopenharmony_ci } 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci return count - bytes_left; 2168c2ecf20Sopenharmony_ci} 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_ci/** 2198c2ecf20Sopenharmony_ci * rproc_coredump() - perform coredump 2208c2ecf20Sopenharmony_ci * @rproc: rproc handle 2218c2ecf20Sopenharmony_ci * 2228c2ecf20Sopenharmony_ci * This function will generate an ELF header for the registered segments 2238c2ecf20Sopenharmony_ci * and create a devcoredump device associated with rproc. Based on the 2248c2ecf20Sopenharmony_ci * coredump configuration this function will directly copy the segments 2258c2ecf20Sopenharmony_ci * from device memory to userspace or copy segments from device memory to 2268c2ecf20Sopenharmony_ci * a separate buffer, which can then be read by userspace. 2278c2ecf20Sopenharmony_ci * The first approach avoids using extra vmalloc memory. But it will stall 2288c2ecf20Sopenharmony_ci * recovery flow until dump is read by userspace. 2298c2ecf20Sopenharmony_ci */ 2308c2ecf20Sopenharmony_civoid rproc_coredump(struct rproc *rproc) 2318c2ecf20Sopenharmony_ci{ 2328c2ecf20Sopenharmony_ci struct rproc_dump_segment *segment; 2338c2ecf20Sopenharmony_ci void *phdr; 2348c2ecf20Sopenharmony_ci void *ehdr; 2358c2ecf20Sopenharmony_ci size_t data_size; 2368c2ecf20Sopenharmony_ci size_t offset; 2378c2ecf20Sopenharmony_ci void *data; 2388c2ecf20Sopenharmony_ci u8 class = rproc->elf_class; 2398c2ecf20Sopenharmony_ci int phnum = 0; 2408c2ecf20Sopenharmony_ci struct rproc_coredump_state dump_state; 2418c2ecf20Sopenharmony_ci enum rproc_dump_mechanism dump_conf = rproc->dump_conf; 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci if (list_empty(&rproc->dump_segments) || 2448c2ecf20Sopenharmony_ci dump_conf == RPROC_COREDUMP_DISABLED) 2458c2ecf20Sopenharmony_ci return; 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_ci if (class == ELFCLASSNONE) { 2488c2ecf20Sopenharmony_ci dev_err(&rproc->dev, "Elf class is not set\n"); 2498c2ecf20Sopenharmony_ci return; 2508c2ecf20Sopenharmony_ci } 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_ci data_size = elf_size_of_hdr(class); 2538c2ecf20Sopenharmony_ci list_for_each_entry(segment, &rproc->dump_segments, node) { 2548c2ecf20Sopenharmony_ci /* 2558c2ecf20Sopenharmony_ci * For default configuration buffer includes headers & segments. 2568c2ecf20Sopenharmony_ci * For inline dump buffer just includes headers as segments are 2578c2ecf20Sopenharmony_ci * directly read from device memory. 2588c2ecf20Sopenharmony_ci */ 2598c2ecf20Sopenharmony_ci data_size += elf_size_of_phdr(class); 2608c2ecf20Sopenharmony_ci if (dump_conf == RPROC_COREDUMP_ENABLED) 2618c2ecf20Sopenharmony_ci data_size += segment->size; 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ci phnum++; 2648c2ecf20Sopenharmony_ci } 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_ci data = vmalloc(data_size); 2678c2ecf20Sopenharmony_ci if (!data) 2688c2ecf20Sopenharmony_ci return; 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci ehdr = data; 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_ci memset(ehdr, 0, elf_size_of_hdr(class)); 2738c2ecf20Sopenharmony_ci /* e_ident field is common for both elf32 and elf64 */ 2748c2ecf20Sopenharmony_ci elf_hdr_init_ident(ehdr, class); 2758c2ecf20Sopenharmony_ci 2768c2ecf20Sopenharmony_ci elf_hdr_set_e_type(class, ehdr, ET_CORE); 2778c2ecf20Sopenharmony_ci elf_hdr_set_e_machine(class, ehdr, rproc->elf_machine); 2788c2ecf20Sopenharmony_ci elf_hdr_set_e_version(class, ehdr, EV_CURRENT); 2798c2ecf20Sopenharmony_ci elf_hdr_set_e_entry(class, ehdr, rproc->bootaddr); 2808c2ecf20Sopenharmony_ci elf_hdr_set_e_phoff(class, ehdr, elf_size_of_hdr(class)); 2818c2ecf20Sopenharmony_ci elf_hdr_set_e_ehsize(class, ehdr, elf_size_of_hdr(class)); 2828c2ecf20Sopenharmony_ci elf_hdr_set_e_phentsize(class, ehdr, elf_size_of_phdr(class)); 2838c2ecf20Sopenharmony_ci elf_hdr_set_e_phnum(class, ehdr, phnum); 2848c2ecf20Sopenharmony_ci 2858c2ecf20Sopenharmony_ci phdr = data + elf_hdr_get_e_phoff(class, ehdr); 2868c2ecf20Sopenharmony_ci offset = elf_hdr_get_e_phoff(class, ehdr); 2878c2ecf20Sopenharmony_ci offset += elf_size_of_phdr(class) * elf_hdr_get_e_phnum(class, ehdr); 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_ci list_for_each_entry(segment, &rproc->dump_segments, node) { 2908c2ecf20Sopenharmony_ci memset(phdr, 0, elf_size_of_phdr(class)); 2918c2ecf20Sopenharmony_ci elf_phdr_set_p_type(class, phdr, PT_LOAD); 2928c2ecf20Sopenharmony_ci elf_phdr_set_p_offset(class, phdr, offset); 2938c2ecf20Sopenharmony_ci elf_phdr_set_p_vaddr(class, phdr, segment->da); 2948c2ecf20Sopenharmony_ci elf_phdr_set_p_paddr(class, phdr, segment->da); 2958c2ecf20Sopenharmony_ci elf_phdr_set_p_filesz(class, phdr, segment->size); 2968c2ecf20Sopenharmony_ci elf_phdr_set_p_memsz(class, phdr, segment->size); 2978c2ecf20Sopenharmony_ci elf_phdr_set_p_flags(class, phdr, PF_R | PF_W | PF_X); 2988c2ecf20Sopenharmony_ci elf_phdr_set_p_align(class, phdr, 0); 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_ci if (dump_conf == RPROC_COREDUMP_ENABLED) 3018c2ecf20Sopenharmony_ci rproc_copy_segment(rproc, data + offset, segment, 0, 3028c2ecf20Sopenharmony_ci segment->size); 3038c2ecf20Sopenharmony_ci 3048c2ecf20Sopenharmony_ci offset += elf_phdr_get_p_filesz(class, phdr); 3058c2ecf20Sopenharmony_ci phdr += elf_size_of_phdr(class); 3068c2ecf20Sopenharmony_ci } 3078c2ecf20Sopenharmony_ci if (dump_conf == RPROC_COREDUMP_ENABLED) { 3088c2ecf20Sopenharmony_ci dev_coredumpv(&rproc->dev, data, data_size, GFP_KERNEL); 3098c2ecf20Sopenharmony_ci return; 3108c2ecf20Sopenharmony_ci } 3118c2ecf20Sopenharmony_ci 3128c2ecf20Sopenharmony_ci /* Initialize the dump state struct to be used by rproc_coredump_read */ 3138c2ecf20Sopenharmony_ci dump_state.rproc = rproc; 3148c2ecf20Sopenharmony_ci dump_state.header = data; 3158c2ecf20Sopenharmony_ci init_completion(&dump_state.dump_done); 3168c2ecf20Sopenharmony_ci 3178c2ecf20Sopenharmony_ci dev_coredumpm(&rproc->dev, NULL, &dump_state, data_size, GFP_KERNEL, 3188c2ecf20Sopenharmony_ci rproc_coredump_read, rproc_coredump_free); 3198c2ecf20Sopenharmony_ci 3208c2ecf20Sopenharmony_ci /* 3218c2ecf20Sopenharmony_ci * Wait until the dump is read and free is called. Data is freed 3228c2ecf20Sopenharmony_ci * by devcoredump framework automatically after 5 minutes. 3238c2ecf20Sopenharmony_ci */ 3248c2ecf20Sopenharmony_ci wait_for_completion(&dump_state.dump_done); 3258c2ecf20Sopenharmony_ci} 326