From 8afd519c3470f685f964deebd61aa51d83cde90a Mon Sep 17 00:00:00 2001 From: Fernando Guzman Lugo Date: Thu, 30 Aug 2012 13:26:12 -0500 Subject: remoteproc: add rproc_report_crash function to notify rproc crashes Allow low-level remoteproc drivers to report rproc crashes by exporting a new rproc_report_crash() function (invoking this from non-rproc drivers is probably wrong, and should be carefully scrutinized if ever needed). rproc_report_crash() can be called from any context; it offloads the tasks of handling the crash to a separate thread. Handling the crash from a separate thread is helpful because: - Ability to call invoke rproc_report_crash() from atomic context, due to the fact that many crashes trigger an interrupt, so this function can be called directly from ISR context. - Avoiding deadlocks which could happen if rproc_report_crash() is called from a function which indirectly holds the rproc lock. Handling the crash might involve: - Remoteproc register dump - Remoteproc stack dump - Remoteproc core dump - Saving Remoteproc traces so they can be read after the crash - Reseting the remoteproc in order to make it functional again (hard recovery) Right now, we only print the crash type which was detected, and only the mmufault type is supported. Remoteproc low-level drivers can add more types when needed. Signed-off-by: Fernando Guzman Lugo [ohad: some commentary, white space and commit log changes] Signed-off-by: Ohad Ben-Cohen --- include/linux/remoteproc.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'include') diff --git a/include/linux/remoteproc.h b/include/linux/remoteproc.h index 131b53957b9f..a46ed2723803 100644 --- a/include/linux/remoteproc.h +++ b/include/linux/remoteproc.h @@ -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,8 @@ 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 */ struct rproc { struct klist_node node; @@ -406,6 +421,8 @@ struct rproc { struct list_head rvdevs; struct idr notifyids; int index; + struct work_struct crash_handler; + unsigned crash_cnt; }; /* we currently support only two vrings per rvdev */ @@ -460,6 +477,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) { -- cgit v1.2.3 From 70b85ef83ce3523f709b622d2c4cb31778686338 Mon Sep 17 00:00:00 2001 From: Fernando Guzman Lugo Date: Thu, 30 Aug 2012 13:26:13 -0500 Subject: remoteproc: add actual recovery implementation Add rproc_trigger_recovery() which takes care of the recovery itself, by removing, and re-adding, all of the remoteproc's virtio devices. This resets all virtio users of the remote processor, during which the remote processor is powered off and on again. Signed-off-by: Fernando Guzman Lugo [ohad: introduce rproc_add_virtio_devices to avoid 1.copying code 2.anomaly] [ohad: some white space, naming and commit log changes] Signed-off-by: Ohad Ben-Cohen --- drivers/remoteproc/remoteproc_core.c | 84 +++++++++++++++++++++++--------- drivers/remoteproc/remoteproc_internal.h | 1 + include/linux/remoteproc.h | 2 + 3 files changed, 65 insertions(+), 22 deletions(-) (limited to 'include') diff --git a/drivers/remoteproc/remoteproc_core.c b/drivers/remoteproc/remoteproc_core.c index 93e2b3526543..5000d7589cf5 100644 --- a/drivers/remoteproc/remoteproc_core.c +++ b/drivers/remoteproc/remoteproc_core.c @@ -884,6 +884,60 @@ out: 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 * @@ -911,7 +965,7 @@ static void rproc_crash_handler_work(struct work_struct *work) mutex_unlock(&rproc->lock); - /* TODO: handle crash */ + rproc_trigger_recovery(rproc); } /** @@ -1035,6 +1089,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); @@ -1069,7 +1127,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) @@ -1083,26 +1141,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); @@ -1209,6 +1248,7 @@ struct rproc *rproc_alloc(struct device *dev, const char *name, INIT_LIST_HEAD(&rproc->rvdevs); INIT_WORK(&rproc->crash_handler, rproc_crash_handler_work); + init_completion(&rproc->crash_comp); rproc->state = RPROC_OFFLINE; diff --git a/drivers/remoteproc/remoteproc_internal.h b/drivers/remoteproc/remoteproc_internal.h index a690ebe7aa51..7bb66482d061 100644 --- a/drivers/remoteproc/remoteproc_internal.h +++ b/drivers/remoteproc/remoteproc_internal.h @@ -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) diff --git a/include/linux/remoteproc.h b/include/linux/remoteproc.h index a46ed2723803..0c1a2f95be76 100644 --- a/include/linux/remoteproc.h +++ b/include/linux/remoteproc.h @@ -398,6 +398,7 @@ enum rproc_crash_type { * @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 */ struct rproc { struct klist_node node; @@ -423,6 +424,7 @@ struct rproc { int index; struct work_struct crash_handler; unsigned crash_cnt; + struct completion crash_comp; }; /* we currently support only two vrings per rvdev */ -- cgit v1.2.3 From 2e37abb89a2ef13c524b0728bb9893f996a10b6b Mon Sep 17 00:00:00 2001 From: Fernando Guzman Lugo Date: Tue, 18 Sep 2012 12:26:35 +0300 Subject: remoteproc: create a 'recovery' debugfs entry Add a 'recovery' debugfs entry to dynamically disable/enable recovery at runtime. This is useful when one is trying to debug an rproc crash; without it, a recovery will immediately take place, making it harder to debug the crash. Contributions from Subramaniam Chanderashekarapuram. Examples: - disabling recovery: $ echo disabled > /remoteproc/remoteproc0/recovery - in case you want to recover a crash, but keep recovery disabled (useful in debugging sessions when you expect additional crashes you want to debug): $ echo recover > /remoteproc/remoteproc0/recovery - enabling recovery: $ echo enabled > /remoteproc/remoteproc0/recovery Signed-off-by: Fernando Guzman Lugo [ohad: some white space, commentary and commit log changes] Signed-off-by: Ohad Ben-Cohen --- drivers/remoteproc/remoteproc_core.c | 3 +- drivers/remoteproc/remoteproc_debugfs.c | 81 +++++++++++++++++++++++++++++++++ include/linux/remoteproc.h | 2 + 3 files changed, 85 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/drivers/remoteproc/remoteproc_core.c b/drivers/remoteproc/remoteproc_core.c index 5000d7589cf5..29fc8236cac9 100644 --- a/drivers/remoteproc/remoteproc_core.c +++ b/drivers/remoteproc/remoteproc_core.c @@ -965,7 +965,8 @@ static void rproc_crash_handler_work(struct work_struct *work) mutex_unlock(&rproc->lock); - rproc_trigger_recovery(rproc); + if (!rproc->recovery_disabled) + rproc_trigger_recovery(rproc); } /** diff --git a/drivers/remoteproc/remoteproc_debugfs.c b/drivers/remoteproc/remoteproc_debugfs.c index 03833850f214..10a38258e31d 100644 --- a/drivers/remoteproc/remoteproc_debugfs.c +++ b/drivers/remoteproc/remoteproc_debugfs.c @@ -28,6 +28,9 @@ #include #include #include +#include + +#include "remoteproc_internal.h" /* remoteproc debugfs parent dir */ static struct dentry *rproc_dbg; @@ -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 ret; + + /* 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) diff --git a/include/linux/remoteproc.h b/include/linux/remoteproc.h index 0c1a2f95be76..2ccc3fe2046d 100644 --- a/include/linux/remoteproc.h +++ b/include/linux/remoteproc.h @@ -399,6 +399,7 @@ enum rproc_crash_type { * @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 */ struct rproc { struct klist_node node; @@ -425,6 +426,7 @@ struct rproc { struct work_struct crash_handler; unsigned crash_cnt; struct completion crash_comp; + bool recovery_disabled; }; /* we currently support only two vrings per rvdev */ -- cgit v1.2.3 From 099a3f33c82b5153a4422eb92c648098b3f7c086 Mon Sep 17 00:00:00 2001 From: Sjur Brændeland Date: Tue, 18 Sep 2012 20:32:45 +0200 Subject: remtoteproc: maintain max notifyid MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some of the rproc drivers (STE modem specifically) needs to know the range of the notification IDs used for notifying the device. Maintain a variable in struct rproc holding the largest allocated notification id, so low-level rproc drivers could access it. Signed-off-by: Sjur Brændeland [ohad: rebase, slightly edit commit log] Signed-off-by: Ohad Ben-Cohen --- drivers/remoteproc/remoteproc_core.c | 15 +++++++++++++++ include/linux/remoteproc.h | 2 ++ 2 files changed, 17 insertions(+) (limited to 'include') diff --git a/drivers/remoteproc/remoteproc_core.c b/drivers/remoteproc/remoteproc_core.c index 29fc8236cac9..b6c622982f8c 100644 --- a/drivers/remoteproc/remoteproc_core.c +++ b/drivers/remoteproc/remoteproc_core.c @@ -228,6 +228,9 @@ int rproc_alloc_vring(struct rproc_vdev *rvdev, int i) return ret; } + /* Store largest notifyid */ + rproc->max_notifyid = max(rproc->max_notifyid, notifyid); + dev_dbg(dev, "vring%d: va %p dma %x size %x idr %d\n", i, va, dma, size, notifyid); @@ -269,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; } /** diff --git a/include/linux/remoteproc.h b/include/linux/remoteproc.h index 2ccc3fe2046d..faf33324c78f 100644 --- a/include/linux/remoteproc.h +++ b/include/linux/remoteproc.h @@ -400,6 +400,7 @@ enum rproc_crash_type { * @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; @@ -427,6 +428,7 @@ struct rproc { unsigned crash_cnt; struct completion crash_comp; bool recovery_disabled; + int max_notifyid; }; /* we currently support only two vrings per rvdev */ -- cgit v1.2.3 From ec4d02d9180f407c41f8310a13b34e473c671fbb Mon Sep 17 00:00:00 2001 From: Sjur Brændeland Date: Thu, 20 Sep 2012 18:32:56 +0200 Subject: remoteproc: Add STE modem driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for the STE modem shared memory driver. This driver hooks into the remoteproc framework in order to manage configuration and the virtio devices. This driver adds custom firmware handlers, because STE modem uses a custom firmware layout. Signed-off-by: Sjur Brændeland cc: Linus Walleij cc: Alan Cox [ohad: validate mdev->ops, move setup() to probe/remove, trivial style changes] Signed-off-by: Ohad Ben-Cohen --- drivers/remoteproc/Kconfig | 11 ++ drivers/remoteproc/Makefile | 1 + drivers/remoteproc/ste_modem_rproc.c | 322 +++++++++++++++++++++++++++++++++++ include/linux/ste_modem_shm.h | 56 ++++++ 4 files changed, 390 insertions(+) create mode 100644 drivers/remoteproc/ste_modem_rproc.c create mode 100644 include/linux/ste_modem_shm.h (limited to 'include') diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig index b4d8de56b52e..a1024d9569ca 100644 --- a/drivers/remoteproc/Kconfig +++ b/drivers/remoteproc/Kconfig @@ -29,4 +29,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 diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile index 934ce6e2c66b..391b65181c05 100644 --- a/drivers/remoteproc/Makefile +++ b/drivers/remoteproc/Makefile @@ -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 diff --git a/drivers/remoteproc/ste_modem_rproc.c b/drivers/remoteproc/ste_modem_rproc.c new file mode 100644 index 000000000000..a7743c069339 --- /dev/null +++ b/drivers/remoteproc/ste_modem_rproc.c @@ -0,0 +1,322 @@ +/* + * Copyright (C) ST-Ericsson AB 2012 + * Author: Sjur Brændeland + * License terms: GNU General Public License (GPL), version 2 + */ + +#include +#include +#include +#include +#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"); diff --git a/include/linux/ste_modem_shm.h b/include/linux/ste_modem_shm.h new file mode 100644 index 000000000000..8444a4eff1bb --- /dev/null +++ b/include/linux/ste_modem_shm.h @@ -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 +#include + +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*/ -- cgit v1.2.3