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