forked from luck/tmp_suning_uos_patched
- Remoteproc Recovery - by Fernando Guzman Lugo - when a remote processor
crash is detected, this mechanism will remove all virtio children devices, wait until their drivers let go, hard reset the remote processor and reload the firmware (resulting in the relevant virtio children devices re-added). Essentially the entire software stack is reset, together with the relevant hardware, so users don't have to reset the entire phone. - STE Modem driver is added - by Sjur Brændeland - OMAP DSP boot address support is added - by Juan Gutierrez - A handful of fixes/cleanups - Sjur Brændeland, Dan Carpenter, Emil Goode -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.11 (GNU/Linux) iQIcBAABAgAGBQJQbU3JAAoJELLolMlTRIoMJn4QANjLHhYw/BdfMF9E2DRQe0ew HFD/siXpQKXMwJ+xDCP9RfGm88tHdn8l/q6NFCOL/hr6TywnY3RrYfijL7O8qQOQ coIMaigOwWr9b55YBGD17ahNDsPGFdfAblWVyJBPfFf/kgVYb2NBNgTCMbGisqrK g83t85ULZeGXeWHZxCOxGEQ1cai4HXpsPOGRxDQFeZKU7qM2fVbY+3zeQIymdZ7v dByifhkwexjqD3n4n2TYRKQo1nC6dSBIaoF5rhRfdKk7L4rmf5J5oII66iRYIuDD vCPblfnrLjd6nwOp/fKqEQlno8uDV8Ryjx90YyWp+IpLJO9RbQko3TDwojgjN6e/ Edg+08nmck1mfE3qKNROCmwK3Dr3j/MOkqwKfS3l7U6VMsBebwdk5me4RBTexSiH QZzFK3Q2q5K9U+GPOgb3uBI8dHfW4/Q2rkIcM9fGEuSRUzRBPO7OwCU85KTEQxxy PxhAYvXWzJM4lk8dUqCq0+z0Wj35RolEK/TsrwSKL/D8NxgFEgRSkAVR0TlFihEy VOqtuGQ30OqAHByggfbtJyYVC67BIeFJPMACxKL5682cJRiZ4wCRXwMmtged/K0p 2BI3Gmz7j22mrTV2ziixNIX7fT6FoTO5KSmbJGJwbHWgctDHHFcWqSmcia6MxWzY GxJkH+jEd3qos08kcWaZ =cpBc -----END PGP SIGNATURE----- Merge tag 'remoteproc-for-3.7' of git://git.kernel.org/pub/scm/linux/kernel/git/ohad/remoteproc Pull remoteproc update from Ohad Ben-Cohen: - Remoteproc Recovery - by Fernando Guzman Lugo When a remote processor crash is detected, this mechanism will remove all virtio children devices, wait until their drivers let go, hard reset the remote processor and reload the firmware (resulting in the relevant virtio children devices re-added). Essentially the entire software stack is reset, together with the relevant hardware, so users don't have to reset the entire phone. - STE Modem driver is added - by Sjur Brændeland - OMAP DSP boot address support is added - by Juan Gutierrez - A handful of fixes/cleanups - Sjur Brændeland, Dan Carpenter, Emil Goode * tag 'remoteproc-for-3.7' of git://git.kernel.org/pub/scm/linux/kernel/git/ohad/remoteproc: remoteproc: Fix use of format specifyer remoteproc: fix a potential NULL-dereference on cleanup remoteproc: select VIRTIO to avoid build breakage remoteproc: return -EFAULT on copy_from_user failure remoteproc: snprintf() can return more than was printed remoteproc: Add STE modem driver remtoteproc: maintain max notifyid remoteproc: create a 'recovery' debugfs entry remoteproc: add actual recovery implementation remoteproc: add rproc_report_crash function to notify rproc crashes remoteproc: Add dependency to HAS_DMA remoteproc/omap: set bootaddr support
This commit is contained in:
commit
4d6d367232
Documentation
drivers/remoteproc
KconfigMakefileomap_remoteproc.cremoteproc_core.cremoteproc_debugfs.cremoteproc_internal.hste_modem_rproc.c
include/linux
|
@ -129,6 +129,13 @@ int dummy_rproc_example(struct rproc *my_rproc)
|
|||
|
||||
Returns 0 on success and -EINVAL if @rproc isn't valid.
|
||||
|
||||
void rproc_report_crash(struct rproc *rproc, enum rproc_crash_type type)
|
||||
- Report a crash in a remoteproc
|
||||
This function must be called every time a crash is detected by the
|
||||
platform specific rproc implementation. This should not be called from a
|
||||
non-remoteproc driver. This function can be called from atomic/interrupt
|
||||
context.
|
||||
|
||||
5. Implementation callbacks
|
||||
|
||||
These callbacks should be provided by platform-specific remoteproc
|
||||
|
|
|
@ -4,11 +4,14 @@ menu "Remoteproc drivers (EXPERIMENTAL)"
|
|||
config REMOTEPROC
|
||||
tristate
|
||||
depends on EXPERIMENTAL
|
||||
depends on HAS_DMA
|
||||
select FW_CONFIG
|
||||
select VIRTIO
|
||||
|
||||
config OMAP_REMOTEPROC
|
||||
tristate "OMAP remoteproc support"
|
||||
depends on EXPERIMENTAL
|
||||
depends on HAS_DMA
|
||||
depends on ARCH_OMAP4
|
||||
depends on OMAP_IOMMU
|
||||
select REMOTEPROC
|
||||
|
@ -27,4 +30,15 @@ config OMAP_REMOTEPROC
|
|||
It's safe to say n here if you're not interested in multimedia
|
||||
offloading or just want a bare minimum kernel.
|
||||
|
||||
config STE_MODEM_RPROC
|
||||
tristate "STE-Modem remoteproc support"
|
||||
depends on EXPERIMENTAL
|
||||
depends on HAS_DMA
|
||||
select REMOTEPROC
|
||||
default n
|
||||
help
|
||||
Say y or m here to support STE-Modem shared memory driver.
|
||||
This can be either built-in or a loadable module.
|
||||
If unsure say N.
|
||||
|
||||
endmenu
|
||||
|
|
|
@ -8,3 +8,4 @@ remoteproc-y += remoteproc_debugfs.o
|
|||
remoteproc-y += remoteproc_virtio.o
|
||||
remoteproc-y += remoteproc_elf_loader.o
|
||||
obj-$(CONFIG_OMAP_REMOTEPROC) += omap_remoteproc.o
|
||||
obj-$(CONFIG_STE_MODEM_RPROC) += ste_modem_rproc.o
|
||||
|
|
|
@ -116,6 +116,9 @@ static int omap_rproc_start(struct rproc *rproc)
|
|||
struct omap_rproc_pdata *pdata = pdev->dev.platform_data;
|
||||
int ret;
|
||||
|
||||
if (pdata->set_bootaddr)
|
||||
pdata->set_bootaddr(rproc->bootaddr);
|
||||
|
||||
oproc->nb.notifier_call = omap_rproc_mbox_callback;
|
||||
|
||||
/* every omap rproc is assigned a mailbox instance for messaging */
|
||||
|
|
|
@ -50,6 +50,18 @@ typedef int (*rproc_handle_resource_t)(struct rproc *rproc, void *, int avail);
|
|||
/* Unique indices for remoteproc devices */
|
||||
static DEFINE_IDA(rproc_dev_index);
|
||||
|
||||
static const char * const rproc_crash_names[] = {
|
||||
[RPROC_MMUFAULT] = "mmufault",
|
||||
};
|
||||
|
||||
/* translate rproc_crash_type to string */
|
||||
static const char *rproc_crash_to_string(enum rproc_crash_type type)
|
||||
{
|
||||
if (type < ARRAY_SIZE(rproc_crash_names))
|
||||
return rproc_crash_names[type];
|
||||
return "unkown";
|
||||
}
|
||||
|
||||
/*
|
||||
* This is the IOMMU fault handler we register with the IOMMU API
|
||||
* (when relevant; not all remote processors access memory through
|
||||
|
@ -57,18 +69,19 @@ static DEFINE_IDA(rproc_dev_index);
|
|||
*
|
||||
* IOMMU core will invoke this handler whenever the remote processor
|
||||
* will try to access an unmapped device address.
|
||||
*
|
||||
* Currently this is mostly a stub, but it will be later used to trigger
|
||||
* the recovery of the remote processor.
|
||||
*/
|
||||
static int rproc_iommu_fault(struct iommu_domain *domain, struct device *dev,
|
||||
unsigned long iova, int flags, void *token)
|
||||
{
|
||||
struct rproc *rproc = token;
|
||||
|
||||
dev_err(dev, "iommu fault: da 0x%lx flags 0x%x\n", iova, flags);
|
||||
|
||||
rproc_report_crash(rproc, RPROC_MMUFAULT);
|
||||
|
||||
/*
|
||||
* Let the iommu core know we're not really handling this fault;
|
||||
* we just plan to use this as a recovery trigger.
|
||||
* we just used it as a recovery trigger.
|
||||
*/
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
@ -215,8 +228,11 @@ int rproc_alloc_vring(struct rproc_vdev *rvdev, int i)
|
|||
return ret;
|
||||
}
|
||||
|
||||
dev_dbg(dev, "vring%d: va %p dma %x size %x idr %d\n", i, va,
|
||||
dma, size, notifyid);
|
||||
/* Store largest notifyid */
|
||||
rproc->max_notifyid = max(rproc->max_notifyid, notifyid);
|
||||
|
||||
dev_dbg(dev, "vring%d: va %p dma %llx size %x idr %d\n", i, va,
|
||||
(unsigned long long)dma, size, notifyid);
|
||||
|
||||
rvring->va = va;
|
||||
rvring->dma = dma;
|
||||
|
@ -256,13 +272,25 @@ rproc_parse_vring(struct rproc_vdev *rvdev, struct fw_rsc_vdev *rsc, int i)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int rproc_max_notifyid(int id, void *p, void *data)
|
||||
{
|
||||
int *maxid = data;
|
||||
*maxid = max(*maxid, id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void rproc_free_vring(struct rproc_vring *rvring)
|
||||
{
|
||||
int size = PAGE_ALIGN(vring_size(rvring->len, rvring->align));
|
||||
struct rproc *rproc = rvring->rvdev->rproc;
|
||||
int maxid = 0;
|
||||
|
||||
dma_free_coherent(rproc->dev.parent, size, rvring->va, rvring->dma);
|
||||
idr_remove(&rproc->notifyids, rvring->notifyid);
|
||||
|
||||
/* Find the largest remaining notifyid */
|
||||
idr_for_each(&rproc->notifyids, rproc_max_notifyid, &maxid);
|
||||
rproc->max_notifyid = maxid;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -545,17 +573,10 @@ static int rproc_handle_carveout(struct rproc *rproc,
|
|||
dev_dbg(dev, "carveout rsc: da %x, pa %x, len %x, flags %x\n",
|
||||
rsc->da, rsc->pa, rsc->len, rsc->flags);
|
||||
|
||||
mapping = kzalloc(sizeof(*mapping), GFP_KERNEL);
|
||||
if (!mapping) {
|
||||
dev_err(dev, "kzalloc mapping failed\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
carveout = kzalloc(sizeof(*carveout), GFP_KERNEL);
|
||||
if (!carveout) {
|
||||
dev_err(dev, "kzalloc carveout failed\n");
|
||||
ret = -ENOMEM;
|
||||
goto free_mapping;
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
va = dma_alloc_coherent(dev->parent, rsc->len, &dma, GFP_KERNEL);
|
||||
|
@ -565,7 +586,8 @@ static int rproc_handle_carveout(struct rproc *rproc,
|
|||
goto free_carv;
|
||||
}
|
||||
|
||||
dev_dbg(dev, "carveout va %p, dma %x, len 0x%x\n", va, dma, rsc->len);
|
||||
dev_dbg(dev, "carveout va %p, dma %llx, len 0x%x\n", va,
|
||||
(unsigned long long)dma, rsc->len);
|
||||
|
||||
/*
|
||||
* Ok, this is non-standard.
|
||||
|
@ -585,11 +607,18 @@ static int rproc_handle_carveout(struct rproc *rproc,
|
|||
* physical address in this case.
|
||||
*/
|
||||
if (rproc->domain) {
|
||||
mapping = kzalloc(sizeof(*mapping), GFP_KERNEL);
|
||||
if (!mapping) {
|
||||
dev_err(dev, "kzalloc mapping failed\n");
|
||||
ret = -ENOMEM;
|
||||
goto dma_free;
|
||||
}
|
||||
|
||||
ret = iommu_map(rproc->domain, rsc->da, dma, rsc->len,
|
||||
rsc->flags);
|
||||
if (ret) {
|
||||
dev_err(dev, "iommu_map failed: %d\n", ret);
|
||||
goto dma_free;
|
||||
goto free_mapping;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -603,7 +632,8 @@ static int rproc_handle_carveout(struct rproc *rproc,
|
|||
mapping->len = rsc->len;
|
||||
list_add_tail(&mapping->node, &rproc->mappings);
|
||||
|
||||
dev_dbg(dev, "carveout mapped 0x%x to 0x%x\n", rsc->da, dma);
|
||||
dev_dbg(dev, "carveout mapped 0x%x to 0x%llx\n",
|
||||
rsc->da, (unsigned long long)dma);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -634,12 +664,12 @@ static int rproc_handle_carveout(struct rproc *rproc,
|
|||
|
||||
return 0;
|
||||
|
||||
free_mapping:
|
||||
kfree(mapping);
|
||||
dma_free:
|
||||
dma_free_coherent(dev->parent, rsc->len, va, dma);
|
||||
free_carv:
|
||||
kfree(carveout);
|
||||
free_mapping:
|
||||
kfree(mapping);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -871,6 +901,91 @@ static void rproc_fw_config_virtio(const struct firmware *fw, void *context)
|
|||
complete_all(&rproc->firmware_loading_complete);
|
||||
}
|
||||
|
||||
static int rproc_add_virtio_devices(struct rproc *rproc)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* rproc_del() calls must wait until async loader completes */
|
||||
init_completion(&rproc->firmware_loading_complete);
|
||||
|
||||
/*
|
||||
* We must retrieve early virtio configuration info from
|
||||
* the firmware (e.g. whether to register a virtio device,
|
||||
* what virtio features does it support, ...).
|
||||
*
|
||||
* We're initiating an asynchronous firmware loading, so we can
|
||||
* be built-in kernel code, without hanging the boot process.
|
||||
*/
|
||||
ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG,
|
||||
rproc->firmware, &rproc->dev, GFP_KERNEL,
|
||||
rproc, rproc_fw_config_virtio);
|
||||
if (ret < 0) {
|
||||
dev_err(&rproc->dev, "request_firmware_nowait err: %d\n", ret);
|
||||
complete_all(&rproc->firmware_loading_complete);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* rproc_trigger_recovery() - recover a remoteproc
|
||||
* @rproc: the remote processor
|
||||
*
|
||||
* The recovery is done by reseting all the virtio devices, that way all the
|
||||
* rpmsg drivers will be reseted along with the remote processor making the
|
||||
* remoteproc functional again.
|
||||
*
|
||||
* This function can sleep, so it cannot be called from atomic context.
|
||||
*/
|
||||
int rproc_trigger_recovery(struct rproc *rproc)
|
||||
{
|
||||
struct rproc_vdev *rvdev, *rvtmp;
|
||||
|
||||
dev_err(&rproc->dev, "recovering %s\n", rproc->name);
|
||||
|
||||
init_completion(&rproc->crash_comp);
|
||||
|
||||
/* clean up remote vdev entries */
|
||||
list_for_each_entry_safe(rvdev, rvtmp, &rproc->rvdevs, node)
|
||||
rproc_remove_virtio_dev(rvdev);
|
||||
|
||||
/* wait until there is no more rproc users */
|
||||
wait_for_completion(&rproc->crash_comp);
|
||||
|
||||
return rproc_add_virtio_devices(rproc);
|
||||
}
|
||||
|
||||
/**
|
||||
* rproc_crash_handler_work() - handle a crash
|
||||
*
|
||||
* This function needs to handle everything related to a crash, like cpu
|
||||
* registers and stack dump, information to help to debug the fatal error, etc.
|
||||
*/
|
||||
static void rproc_crash_handler_work(struct work_struct *work)
|
||||
{
|
||||
struct rproc *rproc = container_of(work, struct rproc, crash_handler);
|
||||
struct device *dev = &rproc->dev;
|
||||
|
||||
dev_dbg(dev, "enter %s\n", __func__);
|
||||
|
||||
mutex_lock(&rproc->lock);
|
||||
|
||||
if (rproc->state == RPROC_CRASHED || rproc->state == RPROC_OFFLINE) {
|
||||
/* handle only the first crash detected */
|
||||
mutex_unlock(&rproc->lock);
|
||||
return;
|
||||
}
|
||||
|
||||
rproc->state = RPROC_CRASHED;
|
||||
dev_err(dev, "handling crash #%u in %s\n", ++rproc->crash_cnt,
|
||||
rproc->name);
|
||||
|
||||
mutex_unlock(&rproc->lock);
|
||||
|
||||
if (!rproc->recovery_disabled)
|
||||
rproc_trigger_recovery(rproc);
|
||||
}
|
||||
|
||||
/**
|
||||
* rproc_boot() - boot a remote processor
|
||||
* @rproc: handle of a remote processor
|
||||
|
@ -992,6 +1107,10 @@ void rproc_shutdown(struct rproc *rproc)
|
|||
|
||||
rproc_disable_iommu(rproc);
|
||||
|
||||
/* if in crash state, unlock crash handler */
|
||||
if (rproc->state == RPROC_CRASHED)
|
||||
complete_all(&rproc->crash_comp);
|
||||
|
||||
rproc->state = RPROC_OFFLINE;
|
||||
|
||||
dev_info(dev, "stopped remote processor %s\n", rproc->name);
|
||||
|
@ -1026,7 +1145,7 @@ EXPORT_SYMBOL(rproc_shutdown);
|
|||
int rproc_add(struct rproc *rproc)
|
||||
{
|
||||
struct device *dev = &rproc->dev;
|
||||
int ret = 0;
|
||||
int ret;
|
||||
|
||||
ret = device_add(dev);
|
||||
if (ret < 0)
|
||||
|
@ -1040,26 +1159,7 @@ int rproc_add(struct rproc *rproc)
|
|||
/* create debugfs entries */
|
||||
rproc_create_debug_dir(rproc);
|
||||
|
||||
/* rproc_del() calls must wait until async loader completes */
|
||||
init_completion(&rproc->firmware_loading_complete);
|
||||
|
||||
/*
|
||||
* We must retrieve early virtio configuration info from
|
||||
* the firmware (e.g. whether to register a virtio device,
|
||||
* what virtio features does it support, ...).
|
||||
*
|
||||
* We're initiating an asynchronous firmware loading, so we can
|
||||
* be built-in kernel code, without hanging the boot process.
|
||||
*/
|
||||
ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG,
|
||||
rproc->firmware, dev, GFP_KERNEL,
|
||||
rproc, rproc_fw_config_virtio);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "request_firmware_nowait failed: %d\n", ret);
|
||||
complete_all(&rproc->firmware_loading_complete);
|
||||
}
|
||||
|
||||
return ret;
|
||||
return rproc_add_virtio_devices(rproc);
|
||||
}
|
||||
EXPORT_SYMBOL(rproc_add);
|
||||
|
||||
|
@ -1165,6 +1265,9 @@ struct rproc *rproc_alloc(struct device *dev, const char *name,
|
|||
INIT_LIST_HEAD(&rproc->traces);
|
||||
INIT_LIST_HEAD(&rproc->rvdevs);
|
||||
|
||||
INIT_WORK(&rproc->crash_handler, rproc_crash_handler_work);
|
||||
init_completion(&rproc->crash_comp);
|
||||
|
||||
rproc->state = RPROC_OFFLINE;
|
||||
|
||||
return rproc;
|
||||
|
@ -1221,6 +1324,32 @@ int rproc_del(struct rproc *rproc)
|
|||
}
|
||||
EXPORT_SYMBOL(rproc_del);
|
||||
|
||||
/**
|
||||
* rproc_report_crash() - rproc crash reporter function
|
||||
* @rproc: remote processor
|
||||
* @type: crash type
|
||||
*
|
||||
* This function must be called every time a crash is detected by the low-level
|
||||
* drivers implementing a specific remoteproc. This should not be called from a
|
||||
* non-remoteproc driver.
|
||||
*
|
||||
* This function can be called from atomic/interrupt context.
|
||||
*/
|
||||
void rproc_report_crash(struct rproc *rproc, enum rproc_crash_type type)
|
||||
{
|
||||
if (!rproc) {
|
||||
pr_err("NULL rproc pointer\n");
|
||||
return;
|
||||
}
|
||||
|
||||
dev_err(&rproc->dev, "crash detected in %s: type %s\n",
|
||||
rproc->name, rproc_crash_to_string(type));
|
||||
|
||||
/* create a new task to handle the error */
|
||||
schedule_work(&rproc->crash_handler);
|
||||
}
|
||||
EXPORT_SYMBOL(rproc_report_crash);
|
||||
|
||||
static int __init remoteproc_init(void)
|
||||
{
|
||||
rproc_init_debugfs();
|
||||
|
|
|
@ -28,6 +28,9 @@
|
|||
#include <linux/debugfs.h>
|
||||
#include <linux/remoteproc.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
#include "remoteproc_internal.h"
|
||||
|
||||
/* remoteproc debugfs parent dir */
|
||||
static struct dentry *rproc_dbg;
|
||||
|
@ -79,7 +82,7 @@ static ssize_t rproc_state_read(struct file *filp, char __user *userbuf,
|
|||
|
||||
state = rproc->state > RPROC_LAST ? RPROC_LAST : rproc->state;
|
||||
|
||||
i = snprintf(buf, 30, "%.28s (%d)\n", rproc_state_string[state],
|
||||
i = scnprintf(buf, 30, "%.28s (%d)\n", rproc_state_string[state],
|
||||
rproc->state);
|
||||
|
||||
return simple_read_from_buffer(userbuf, count, ppos, buf, i);
|
||||
|
@ -100,7 +103,7 @@ static ssize_t rproc_name_read(struct file *filp, char __user *userbuf,
|
|||
char buf[100];
|
||||
int i;
|
||||
|
||||
i = snprintf(buf, sizeof(buf), "%.98s\n", rproc->name);
|
||||
i = scnprintf(buf, sizeof(buf), "%.98s\n", rproc->name);
|
||||
|
||||
return simple_read_from_buffer(userbuf, count, ppos, buf, i);
|
||||
}
|
||||
|
@ -111,6 +114,82 @@ static const struct file_operations rproc_name_ops = {
|
|||
.llseek = generic_file_llseek,
|
||||
};
|
||||
|
||||
/* expose recovery flag via debugfs */
|
||||
static ssize_t rproc_recovery_read(struct file *filp, char __user *userbuf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct rproc *rproc = filp->private_data;
|
||||
char *buf = rproc->recovery_disabled ? "disabled\n" : "enabled\n";
|
||||
|
||||
return simple_read_from_buffer(userbuf, count, ppos, buf, strlen(buf));
|
||||
}
|
||||
|
||||
/*
|
||||
* By writing to the 'recovery' debugfs entry, we control the behavior of the
|
||||
* recovery mechanism dynamically. The default value of this entry is "enabled".
|
||||
*
|
||||
* The 'recovery' debugfs entry supports these commands:
|
||||
*
|
||||
* enabled: When enabled, the remote processor will be automatically
|
||||
* recovered whenever it crashes. Moreover, if the remote
|
||||
* processor crashes while recovery is disabled, it will
|
||||
* be automatically recovered too as soon as recovery is enabled.
|
||||
*
|
||||
* disabled: When disabled, a remote processor will remain in a crashed
|
||||
* state if it crashes. This is useful for debugging purposes;
|
||||
* without it, debugging a crash is substantially harder.
|
||||
*
|
||||
* recover: This function will trigger an immediate recovery if the
|
||||
* remote processor is in a crashed state, without changing
|
||||
* or checking the recovery state (enabled/disabled).
|
||||
* This is useful during debugging sessions, when one expects
|
||||
* additional crashes to happen after enabling recovery. In this
|
||||
* case, enabling recovery will make it hard to debug subsequent
|
||||
* crashes, so it's recommended to keep recovery disabled, and
|
||||
* instead use the "recover" command as needed.
|
||||
*/
|
||||
static ssize_t
|
||||
rproc_recovery_write(struct file *filp, const char __user *user_buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct rproc *rproc = filp->private_data;
|
||||
char buf[10];
|
||||
int ret;
|
||||
|
||||
if (count > sizeof(buf))
|
||||
return count;
|
||||
|
||||
ret = copy_from_user(buf, user_buf, count);
|
||||
if (ret)
|
||||
return -EFAULT;
|
||||
|
||||
/* remove end of line */
|
||||
if (buf[count - 1] == '\n')
|
||||
buf[count - 1] = '\0';
|
||||
|
||||
if (!strncmp(buf, "enabled", count)) {
|
||||
rproc->recovery_disabled = false;
|
||||
/* if rproc has crashed, trigger recovery */
|
||||
if (rproc->state == RPROC_CRASHED)
|
||||
rproc_trigger_recovery(rproc);
|
||||
} else if (!strncmp(buf, "disabled", count)) {
|
||||
rproc->recovery_disabled = true;
|
||||
} else if (!strncmp(buf, "recover", count)) {
|
||||
/* if rproc has crashed, trigger recovery */
|
||||
if (rproc->state == RPROC_CRASHED)
|
||||
rproc_trigger_recovery(rproc);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static const struct file_operations rproc_recovery_ops = {
|
||||
.read = rproc_recovery_read,
|
||||
.write = rproc_recovery_write,
|
||||
.open = simple_open,
|
||||
.llseek = generic_file_llseek,
|
||||
};
|
||||
|
||||
void rproc_remove_trace_file(struct dentry *tfile)
|
||||
{
|
||||
debugfs_remove(tfile);
|
||||
|
@ -154,6 +233,8 @@ void rproc_create_debug_dir(struct rproc *rproc)
|
|||
rproc, &rproc_name_ops);
|
||||
debugfs_create_file("state", 0400, rproc->dbg_dir,
|
||||
rproc, &rproc_state_ops);
|
||||
debugfs_create_file("recovery", 0400, rproc->dbg_dir,
|
||||
rproc, &rproc_recovery_ops);
|
||||
}
|
||||
|
||||
void __init rproc_init_debugfs(void)
|
||||
|
|
|
@ -63,6 +63,7 @@ void rproc_free_vring(struct rproc_vring *rvring);
|
|||
int rproc_alloc_vring(struct rproc_vdev *rvdev, int i);
|
||||
|
||||
void *rproc_da_to_va(struct rproc *rproc, u64 da, int len);
|
||||
int rproc_trigger_recovery(struct rproc *rproc);
|
||||
|
||||
static inline
|
||||
int rproc_fw_sanity_check(struct rproc *rproc, const struct firmware *fw)
|
||||
|
|
322
drivers/remoteproc/ste_modem_rproc.c
Normal file
322
drivers/remoteproc/ste_modem_rproc.c
Normal file
|
@ -0,0 +1,322 @@
|
|||
/*
|
||||
* Copyright (C) ST-Ericsson AB 2012
|
||||
* Author: Sjur Brændeland <sjur.brandeland@stericsson.com>
|
||||
* License terms: GNU General Public License (GPL), version 2
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/remoteproc.h>
|
||||
#include <linux/ste_modem_shm.h>
|
||||
#include "remoteproc_internal.h"
|
||||
|
||||
#define SPROC_FW_SIZE (50 * 4096)
|
||||
#define SPROC_MAX_TOC_ENTRIES 32
|
||||
#define SPROC_MAX_NOTIFY_ID 14
|
||||
#define SPROC_RESOURCE_NAME "rsc-table"
|
||||
#define SPROC_MODEM_NAME "ste-modem"
|
||||
#define SPROC_MODEM_FIRMWARE SPROC_MODEM_NAME "-fw.bin"
|
||||
|
||||
#define sproc_dbg(sproc, fmt, ...) \
|
||||
dev_dbg(&sproc->mdev->pdev.dev, fmt, ##__VA_ARGS__)
|
||||
#define sproc_err(sproc, fmt, ...) \
|
||||
dev_err(&sproc->mdev->pdev.dev, fmt, ##__VA_ARGS__)
|
||||
|
||||
/* STE-modem control structure */
|
||||
struct sproc {
|
||||
struct rproc *rproc;
|
||||
struct ste_modem_device *mdev;
|
||||
int error;
|
||||
void *fw_addr;
|
||||
size_t fw_size;
|
||||
dma_addr_t fw_dma_addr;
|
||||
};
|
||||
|
||||
/* STE-Modem firmware entry */
|
||||
struct ste_toc_entry {
|
||||
__le32 start;
|
||||
__le32 size;
|
||||
__le32 flags;
|
||||
__le32 entry_point;
|
||||
__le32 load_addr;
|
||||
char name[12];
|
||||
};
|
||||
|
||||
/*
|
||||
* The Table Of Content is located at the start of the firmware image and
|
||||
* at offset zero in the shared memory region. The resource table typically
|
||||
* contains the initial boot image (boot strap) and other information elements
|
||||
* such as remoteproc resource table. Each entry is identified by a unique
|
||||
* name.
|
||||
*/
|
||||
struct ste_toc {
|
||||
struct ste_toc_entry table[SPROC_MAX_TOC_ENTRIES];
|
||||
};
|
||||
|
||||
/* Loads the firmware to shared memory. */
|
||||
static int sproc_load_segments(struct rproc *rproc, const struct firmware *fw)
|
||||
{
|
||||
struct sproc *sproc = rproc->priv;
|
||||
|
||||
memcpy(sproc->fw_addr, fw->data, fw->size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Find the entry for resource table in the Table of Content */
|
||||
static struct ste_toc_entry *sproc_find_rsc_entry(const struct firmware *fw)
|
||||
{
|
||||
int i;
|
||||
struct ste_toc *toc;
|
||||
|
||||
if (!fw)
|
||||
return NULL;
|
||||
|
||||
toc = (void *)fw->data;
|
||||
|
||||
/* Search the table for the resource table */
|
||||
for (i = 0; i < SPROC_MAX_TOC_ENTRIES &&
|
||||
toc->table[i].start != 0xffffffff; i++) {
|
||||
|
||||
if (!strncmp(toc->table[i].name, SPROC_RESOURCE_NAME,
|
||||
sizeof(toc->table[i].name))) {
|
||||
if (toc->table[i].start > fw->size)
|
||||
return NULL;
|
||||
return &toc->table[i];
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Find the resource table inside the remote processor's firmware. */
|
||||
static struct resource_table *
|
||||
sproc_find_rsc_table(struct rproc *rproc, const struct firmware *fw,
|
||||
int *tablesz)
|
||||
{
|
||||
struct sproc *sproc = rproc->priv;
|
||||
struct resource_table *table;
|
||||
struct ste_toc_entry *entry;
|
||||
|
||||
entry = sproc_find_rsc_entry(fw);
|
||||
if (!entry) {
|
||||
sproc_err(sproc, "resource table not found in fw\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
table = (void *)(fw->data + entry->start);
|
||||
|
||||
/* sanity check size and offset of resource table */
|
||||
if (entry->start > SPROC_FW_SIZE ||
|
||||
entry->size > SPROC_FW_SIZE ||
|
||||
fw->size > SPROC_FW_SIZE ||
|
||||
entry->start + entry->size > fw->size ||
|
||||
sizeof(struct resource_table) > entry->size) {
|
||||
sproc_err(sproc, "bad size of fw or resource table\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* we don't support any version beyond the first */
|
||||
if (table->ver != 1) {
|
||||
sproc_err(sproc, "unsupported fw ver: %d\n", table->ver);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* make sure reserved bytes are zeroes */
|
||||
if (table->reserved[0] || table->reserved[1]) {
|
||||
sproc_err(sproc, "non zero reserved bytes\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* make sure the offsets array isn't truncated */
|
||||
if (table->num > SPROC_MAX_TOC_ENTRIES ||
|
||||
table->num * sizeof(table->offset[0]) +
|
||||
sizeof(struct resource_table) > entry->size) {
|
||||
sproc_err(sproc, "resource table incomplete\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* If the fw size has grown, release the previous fw allocation */
|
||||
if (SPROC_FW_SIZE < fw->size) {
|
||||
sproc_err(sproc, "Insufficient space for fw (%d < %zd)\n",
|
||||
SPROC_FW_SIZE, fw->size);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
sproc->fw_size = fw->size;
|
||||
*tablesz = entry->size;
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
/* STE modem firmware handler operations */
|
||||
const struct rproc_fw_ops sproc_fw_ops = {
|
||||
.load = sproc_load_segments,
|
||||
.find_rsc_table = sproc_find_rsc_table,
|
||||
};
|
||||
|
||||
/* Kick the modem with specified notification id */
|
||||
static void sproc_kick(struct rproc *rproc, int vqid)
|
||||
{
|
||||
struct sproc *sproc = rproc->priv;
|
||||
|
||||
sproc_dbg(sproc, "kick vqid:%d\n", vqid);
|
||||
|
||||
/*
|
||||
* We need different notification IDs for RX and TX so add
|
||||
* an offset on TX notification IDs.
|
||||
*/
|
||||
sproc->mdev->ops.kick(sproc->mdev, vqid + SPROC_MAX_NOTIFY_ID);
|
||||
}
|
||||
|
||||
/* Received a kick from a modem, kick the virtqueue */
|
||||
static void sproc_kick_callback(struct ste_modem_device *mdev, int vqid)
|
||||
{
|
||||
struct sproc *sproc = mdev->drv_data;
|
||||
|
||||
if (rproc_vq_interrupt(sproc->rproc, vqid) == IRQ_NONE)
|
||||
sproc_dbg(sproc, "no message was found in vqid %d\n", vqid);
|
||||
}
|
||||
|
||||
struct ste_modem_dev_cb sproc_dev_cb = {
|
||||
.kick = sproc_kick_callback,
|
||||
};
|
||||
|
||||
/* Start the STE modem */
|
||||
static int sproc_start(struct rproc *rproc)
|
||||
{
|
||||
struct sproc *sproc = rproc->priv;
|
||||
int i, err;
|
||||
|
||||
sproc_dbg(sproc, "start ste-modem\n");
|
||||
|
||||
/* Sanity test the max_notifyid */
|
||||
if (rproc->max_notifyid > SPROC_MAX_NOTIFY_ID) {
|
||||
sproc_err(sproc, "Notification IDs too high:%d\n",
|
||||
rproc->max_notifyid);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Subscribe to notifications */
|
||||
for (i = 0; i < rproc->max_notifyid; i++) {
|
||||
err = sproc->mdev->ops.kick_subscribe(sproc->mdev, i);
|
||||
if (err) {
|
||||
sproc_err(sproc,
|
||||
"subscription of kicks failed:%d\n", err);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
/* Request modem start-up*/
|
||||
return sproc->mdev->ops.power(sproc->mdev, true);
|
||||
}
|
||||
|
||||
/* Stop the STE modem */
|
||||
static int sproc_stop(struct rproc *rproc)
|
||||
{
|
||||
struct sproc *sproc = rproc->priv;
|
||||
sproc_dbg(sproc, "stop ste-modem\n");
|
||||
|
||||
return sproc->mdev->ops.power(sproc->mdev, false);
|
||||
}
|
||||
|
||||
static struct rproc_ops sproc_ops = {
|
||||
.start = sproc_start,
|
||||
.stop = sproc_stop,
|
||||
.kick = sproc_kick,
|
||||
};
|
||||
|
||||
/* STE modem device is unregistered */
|
||||
static int sproc_drv_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct ste_modem_device *mdev =
|
||||
container_of(pdev, struct ste_modem_device, pdev);
|
||||
struct sproc *sproc = mdev->drv_data;
|
||||
|
||||
sproc_dbg(sproc, "remove ste-modem\n");
|
||||
|
||||
/* Reset device callback functions */
|
||||
sproc->mdev->ops.setup(sproc->mdev, NULL);
|
||||
|
||||
/* Unregister as remoteproc device */
|
||||
rproc_del(sproc->rproc);
|
||||
rproc_put(sproc->rproc);
|
||||
|
||||
mdev->drv_data = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Handle probe of a modem device */
|
||||
static int sproc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct ste_modem_device *mdev =
|
||||
container_of(pdev, struct ste_modem_device, pdev);
|
||||
struct sproc *sproc;
|
||||
struct rproc *rproc;
|
||||
int err;
|
||||
|
||||
dev_dbg(&mdev->pdev.dev, "probe ste-modem\n");
|
||||
|
||||
if (!mdev->ops.setup || !mdev->ops.kick || !mdev->ops.kick_subscribe ||
|
||||
!mdev->ops.power) {
|
||||
dev_err(&mdev->pdev.dev, "invalid mdev ops\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
rproc = rproc_alloc(&mdev->pdev.dev, mdev->pdev.name, &sproc_ops,
|
||||
SPROC_MODEM_FIRMWARE, sizeof(*sproc));
|
||||
if (!rproc)
|
||||
return -ENOMEM;
|
||||
|
||||
sproc = rproc->priv;
|
||||
sproc->mdev = mdev;
|
||||
sproc->rproc = rproc;
|
||||
mdev->drv_data = sproc;
|
||||
|
||||
/* Provide callback functions to modem device */
|
||||
sproc->mdev->ops.setup(sproc->mdev, &sproc_dev_cb);
|
||||
|
||||
/* Set the STE-modem specific firmware handler */
|
||||
rproc->fw_ops = &sproc_fw_ops;
|
||||
|
||||
/*
|
||||
* STE-modem requires the firmware to be located
|
||||
* at the start of the shared memory region. So we need to
|
||||
* reserve space for firmware at the start.
|
||||
*/
|
||||
sproc->fw_addr = dma_alloc_coherent(rproc->dev.parent, SPROC_FW_SIZE,
|
||||
&sproc->fw_dma_addr,
|
||||
GFP_KERNEL);
|
||||
if (!sproc->fw_addr) {
|
||||
sproc_err(sproc, "Cannot allocate memory for fw\n");
|
||||
err = -ENOMEM;
|
||||
goto free_rproc;
|
||||
}
|
||||
|
||||
/* Register as a remoteproc device */
|
||||
err = rproc_add(rproc);
|
||||
if (err)
|
||||
goto free_rproc;
|
||||
|
||||
return 0;
|
||||
|
||||
free_rproc:
|
||||
/* Reset device data upon error */
|
||||
mdev->drv_data = NULL;
|
||||
rproc_put(rproc);
|
||||
return err;
|
||||
}
|
||||
|
||||
static struct platform_driver sproc_driver = {
|
||||
.driver = {
|
||||
.name = SPROC_MODEM_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = sproc_probe,
|
||||
.remove = sproc_drv_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(sproc_driver);
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("STE Modem driver using the Remote Processor Framework");
|
|
@ -30,6 +30,7 @@ struct platform_device;
|
|||
* @ops: start/stop rproc handlers
|
||||
* @device_enable: omap-specific handler for enabling a device
|
||||
* @device_shutdown: omap-specific handler for shutting down a device
|
||||
* @set_bootaddr: omap-specific handler for setting the rproc boot address
|
||||
*/
|
||||
struct omap_rproc_pdata {
|
||||
const char *name;
|
||||
|
@ -40,6 +41,7 @@ struct omap_rproc_pdata {
|
|||
const struct rproc_ops *ops;
|
||||
int (*device_enable) (struct platform_device *pdev);
|
||||
int (*device_shutdown) (struct platform_device *pdev);
|
||||
void(*set_bootaddr)(u32);
|
||||
};
|
||||
|
||||
#if defined(CONFIG_OMAP_REMOTEPROC) || defined(CONFIG_OMAP_REMOTEPROC_MODULE)
|
||||
|
|
|
@ -360,6 +360,19 @@ enum rproc_state {
|
|||
RPROC_LAST = 4,
|
||||
};
|
||||
|
||||
/**
|
||||
* enum rproc_crash_type - remote processor crash types
|
||||
* @RPROC_MMUFAULT: iommu fault
|
||||
*
|
||||
* Each element of the enum is used as an array index. So that, the value of
|
||||
* the elements should be always something sane.
|
||||
*
|
||||
* Feel free to add more types when needed.
|
||||
*/
|
||||
enum rproc_crash_type {
|
||||
RPROC_MMUFAULT,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct rproc - represents a physical remote processor device
|
||||
* @node: klist node of this rproc object
|
||||
|
@ -383,6 +396,11 @@ enum rproc_state {
|
|||
* @rvdevs: list of remote virtio devices
|
||||
* @notifyids: idr for dynamically assigning rproc-wide unique notify ids
|
||||
* @index: index of this rproc device
|
||||
* @crash_handler: workqueue for handling a crash
|
||||
* @crash_cnt: crash counter
|
||||
* @crash_comp: completion used to sync crash handler and the rproc reload
|
||||
* @recovery_disabled: flag that state if recovery was disabled
|
||||
* @max_notifyid: largest allocated notify id.
|
||||
*/
|
||||
struct rproc {
|
||||
struct klist_node node;
|
||||
|
@ -406,6 +424,11 @@ struct rproc {
|
|||
struct list_head rvdevs;
|
||||
struct idr notifyids;
|
||||
int index;
|
||||
struct work_struct crash_handler;
|
||||
unsigned crash_cnt;
|
||||
struct completion crash_comp;
|
||||
bool recovery_disabled;
|
||||
int max_notifyid;
|
||||
};
|
||||
|
||||
/* we currently support only two vrings per rvdev */
|
||||
|
@ -460,6 +483,7 @@ int rproc_del(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);
|
||||
|
||||
static inline struct rproc_vdev *vdev_to_rvdev(struct virtio_device *vdev)
|
||||
{
|
||||
|
|
56
include/linux/ste_modem_shm.h
Normal file
56
include/linux/ste_modem_shm.h
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright (C) ST-Ericsson AB 2012
|
||||
* Author: Sjur Brendeland / sjur.brandeland@stericsson.com
|
||||
*
|
||||
* License terms: GNU General Public License (GPL) version 2
|
||||
*/
|
||||
|
||||
#ifndef __INC_MODEM_DEV_H
|
||||
#define __INC_MODEM_DEV_H
|
||||
#include <linux/types.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
struct ste_modem_device;
|
||||
|
||||
/**
|
||||
* struct ste_modem_dev_cb - Callbacks for modem initiated events.
|
||||
* @kick: Called when the modem kicks the host.
|
||||
*
|
||||
* This structure contains callbacks for actions triggered by the modem.
|
||||
*/
|
||||
struct ste_modem_dev_cb {
|
||||
void (*kick)(struct ste_modem_device *mdev, int notify_id);
|
||||
};
|
||||
|
||||
/**
|
||||
* struct ste_modem_dev_ops - Functions to control modem and modem interface.
|
||||
*
|
||||
* @power: Main power switch, used for cold-start or complete power off.
|
||||
* @kick: Kick the modem.
|
||||
* @kick_subscribe: Subscribe for notifications from the modem.
|
||||
* @setup: Provide callback functions to modem device.
|
||||
*
|
||||
* This structure contains functions used by the ste remoteproc driver
|
||||
* to manage the modem.
|
||||
*/
|
||||
struct ste_modem_dev_ops {
|
||||
int (*power)(struct ste_modem_device *mdev, bool on);
|
||||
int (*kick)(struct ste_modem_device *mdev, int notify_id);
|
||||
int (*kick_subscribe)(struct ste_modem_device *mdev, int notify_id);
|
||||
int (*setup)(struct ste_modem_device *mdev,
|
||||
struct ste_modem_dev_cb *cfg);
|
||||
};
|
||||
|
||||
/**
|
||||
* struct ste_modem_device - represent the STE modem device
|
||||
* @pdev: Reference to platform device
|
||||
* @ops: Operations used to manage the modem.
|
||||
* @drv_data: Driver private data.
|
||||
*/
|
||||
struct ste_modem_device {
|
||||
struct platform_device pdev;
|
||||
struct ste_modem_dev_ops ops;
|
||||
void *drv_data;
|
||||
};
|
||||
|
||||
#endif /*INC_MODEM_DEV_H*/
|
Loading…
Reference in New Issue
Block a user