Skip to content

Commit

Permalink
remoteproc: Add remote processor coredump support
Browse files Browse the repository at this point in the history
As the remoteproc framework restarts the remote processor after a fatal
event, it's useful to be able to acquire a coredump of the remote
processor's state, for post mortem debugging.

This patch introduces a mechanism for extracting the memory contents
after the remote has stopped and before the restart sequence has begun
in the recovery path. The remoteproc framework builds the core dump in
memory and use devcoredump to expose this to user space.

Signed-off-by: Sarangdhar Joshi <spjoshi@codeaurora.org>
[bjorn: Use vmalloc instead of composing the ELF on the fly]
Signed-off-by: Bjorn Andersson <bjorn.andersson@linaro.org>
  • Loading branch information
Sarangdhar Joshi authored and Bjorn Andersson committed Feb 12, 2018
1 parent 2d02d15 commit 2666ca9
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 0 deletions.
1 change: 1 addition & 0 deletions drivers/remoteproc/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ config REMOTEPROC
select CRC32
select FW_LOADER
select VIRTIO
select WANT_DEV_COREDUMP
help
Support for remote processors (such as DSP coprocessors). These
are mainly used on embedded systems.
Expand Down
128 changes: 128 additions & 0 deletions drivers/remoteproc/remoteproc_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include <linux/firmware.h>
#include <linux/string.h>
#include <linux/debugfs.h>
#include <linux/devcoredump.h>
#include <linux/remoteproc.h>
#include <linux/iommu.h>
#include <linux/idr.h>
Expand Down Expand Up @@ -801,6 +802,20 @@ static void rproc_remove_subdevices(struct rproc *rproc)
subdev->remove(subdev);
}

/**
* rproc_coredump_cleanup() - clean up dump_segments list
* @rproc: the remote processor handle
*/
static void rproc_coredump_cleanup(struct rproc *rproc)
{
struct rproc_dump_segment *entry, *tmp;

list_for_each_entry_safe(entry, tmp, &rproc->dump_segments, node) {
list_del(&entry->node);
kfree(entry);
}
}

/**
* rproc_resource_cleanup() - clean up and free all acquired resources
* @rproc: rproc handle
Expand Down Expand Up @@ -848,6 +863,8 @@ static void rproc_resource_cleanup(struct rproc *rproc)
/* clean up remote vdev entries */
list_for_each_entry_safe(rvdev, rvtmp, &rproc->rvdevs, node)
kref_put(&rvdev->refcount, rproc_vdev_release);

rproc_coredump_cleanup(rproc);
}

static int rproc_start(struct rproc *rproc, const struct firmware *fw)
Expand Down Expand Up @@ -1017,6 +1034,113 @@ static int rproc_stop(struct rproc *rproc)
return 0;
}

/**
* rproc_coredump_add_segment() - add segment of device memory to coredump
* @rproc: handle of a remote processor
* @da: device address
* @size: size of segment
*
* Add device memory to the list of segments to be included in a coredump for
* the remoteproc.
*
* Return: 0 on success, negative errno on error.
*/
int rproc_coredump_add_segment(struct rproc *rproc, dma_addr_t da, size_t size)
{
struct rproc_dump_segment *segment;

segment = kzalloc(sizeof(*segment), GFP_KERNEL);
if (!segment)
return -ENOMEM;

segment->da = da;
segment->size = size;

list_add_tail(&segment->node, &rproc->dump_segments);

return 0;
}
EXPORT_SYMBOL(rproc_coredump_add_segment);

/**
* rproc_coredump() - perform coredump
* @rproc: rproc handle
*
* This function will generate an ELF header for the registered segments
* and create a devcoredump device associated with rproc.
*/
static void rproc_coredump(struct rproc *rproc)
{
struct rproc_dump_segment *segment;
struct elf32_phdr *phdr;
struct elf32_hdr *ehdr;
size_t data_size;
size_t offset;
void *data;
void *ptr;
int phnum = 0;

if (list_empty(&rproc->dump_segments))
return;

data_size = sizeof(*ehdr);
list_for_each_entry(segment, &rproc->dump_segments, node) {
data_size += sizeof(*phdr) + segment->size;

phnum++;
}

data = vmalloc(data_size);
if (!data)
return;

ehdr = data;

memset(ehdr, 0, sizeof(*ehdr));
memcpy(ehdr->e_ident, ELFMAG, SELFMAG);
ehdr->e_ident[EI_CLASS] = ELFCLASS32;
ehdr->e_ident[EI_DATA] = ELFDATA2LSB;
ehdr->e_ident[EI_VERSION] = EV_CURRENT;
ehdr->e_ident[EI_OSABI] = ELFOSABI_NONE;
ehdr->e_type = ET_CORE;
ehdr->e_machine = EM_NONE;
ehdr->e_version = EV_CURRENT;
ehdr->e_entry = rproc->bootaddr;
ehdr->e_phoff = sizeof(*ehdr);
ehdr->e_ehsize = sizeof(*ehdr);
ehdr->e_phentsize = sizeof(*phdr);
ehdr->e_phnum = phnum;

phdr = data + ehdr->e_phoff;
offset = ehdr->e_phoff + sizeof(*phdr) * ehdr->e_phnum;
list_for_each_entry(segment, &rproc->dump_segments, node) {
memset(phdr, 0, sizeof(*phdr));
phdr->p_type = PT_LOAD;
phdr->p_offset = offset;
phdr->p_vaddr = segment->da;
phdr->p_paddr = segment->da;
phdr->p_filesz = segment->size;
phdr->p_memsz = segment->size;
phdr->p_flags = PF_R | PF_W | PF_X;
phdr->p_align = 0;

ptr = rproc_da_to_va(rproc, segment->da, segment->size);
if (!ptr) {
dev_err(&rproc->dev,
"invalid coredump segment (%pad, %zu)\n",
&segment->da, segment->size);
memset(data + offset, 0xff, segment->size);
} else {
memcpy(data + offset, ptr, segment->size);
}

offset += phdr->p_filesz;
phdr++;
}

dev_coredumpv(&rproc->dev, data, data_size, GFP_KERNEL);
}

/**
* rproc_trigger_recovery() - recover a remoteproc
* @rproc: the remote processor
Expand All @@ -1043,6 +1167,9 @@ int rproc_trigger_recovery(struct rproc *rproc)
if (ret)
goto unlock_mutex;

/* generate coredump */
rproc_coredump(rproc);

/* load firmware */
ret = request_firmware(&firmware_p, rproc->firmware, dev);
if (ret < 0) {
Expand Down Expand Up @@ -1443,6 +1570,7 @@ struct rproc *rproc_alloc(struct device *dev, const char *name,
INIT_LIST_HEAD(&rproc->traces);
INIT_LIST_HEAD(&rproc->rvdevs);
INIT_LIST_HEAD(&rproc->subdevs);
INIT_LIST_HEAD(&rproc->dump_segments);

INIT_WORK(&rproc->crash_handler, rproc_crash_handler_work);

Expand Down
18 changes: 18 additions & 0 deletions include/linux/remoteproc.h
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,21 @@ enum rproc_crash_type {
RPROC_FATAL_ERROR,
};

/**
* struct rproc_dump_segment - segment info from ELF header
* @node: list node related to the rproc segment list
* @da: device address of the segment
* @size: size of the segment
*/
struct rproc_dump_segment {
struct list_head node;

dma_addr_t da;
size_t size;

loff_t offset;
};

/**
* struct rproc - represents a physical remote processor device
* @node: list node of this rproc object
Expand Down Expand Up @@ -424,6 +439,7 @@ enum rproc_crash_type {
* @cached_table: copy of the resource table
* @table_sz: size of @cached_table
* @has_iommu: flag to indicate if remote processor is behind an MMU
* @dump_segments: list of segments in the firmware
*/
struct rproc {
struct list_head node;
Expand Down Expand Up @@ -455,6 +471,7 @@ struct rproc {
size_t table_sz;
bool has_iommu;
bool auto_boot;
struct list_head dump_segments;
};

/**
Expand Down Expand Up @@ -534,6 +551,7 @@ void rproc_free(struct rproc *rproc);
int rproc_boot(struct rproc *rproc);
void rproc_shutdown(struct rproc *rproc);
void rproc_report_crash(struct rproc *rproc, enum rproc_crash_type type);
int rproc_coredump_add_segment(struct rproc *rproc, dma_addr_t da, size_t size);

static inline struct rproc_vdev *vdev_to_rvdev(struct virtio_device *vdev)
{
Expand Down

0 comments on commit 2666ca9

Please sign in to comment.