diff options
Diffstat (limited to 'drivers/remoteproc')
-rw-r--r-- | drivers/remoteproc/Kconfig | 16 | ||||
-rw-r--r-- | drivers/remoteproc/Makefile | 7 | ||||
-rw-r--r-- | drivers/remoteproc/qcom_adsp_pil.c | 399 | ||||
-rw-r--r-- | drivers/remoteproc/qcom_q6v5_pil.c | 1 | ||||
-rw-r--r-- | drivers/remoteproc/qcom_wcnss.c | 53 | ||||
-rw-r--r-- | drivers/remoteproc/qcom_wcnss.h | 2 | ||||
-rw-r--r-- | drivers/remoteproc/qcom_wcnss_iris.c | 9 | ||||
-rw-r--r-- | drivers/remoteproc/remoteproc_core.c | 222 | ||||
-rw-r--r-- | drivers/remoteproc/remoteproc_debugfs.c | 71 | ||||
-rw-r--r-- | drivers/remoteproc/remoteproc_internal.h | 6 | ||||
-rw-r--r-- | drivers/remoteproc/remoteproc_sysfs.c | 151 | ||||
-rw-r--r-- | drivers/remoteproc/remoteproc_virtio.c | 17 | ||||
-rw-r--r-- | drivers/remoteproc/st_remoteproc.c | 4 |
13 files changed, 796 insertions, 162 deletions
diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig index 14d5d2d43a38..9c41128750ca 100644 --- a/drivers/remoteproc/Kconfig +++ b/drivers/remoteproc/Kconfig @@ -78,6 +78,17 @@ config DA8XX_REMOTEPROC It's safe to say n here if you're not interested in multimedia offloading. +config QCOM_ADSP_PIL + tristate "Qualcomm ADSP Peripheral Image Loader" + depends on OF && ARCH_QCOM + depends on QCOM_SMEM + select MFD_SYSCON + select QCOM_MDT_LOADER + select REMOTEPROC + help + Say y here to support the TrustZone based Peripherial Image Loader + for the Qualcomm ADSP remote processors. + config QCOM_MDT_LOADER tristate @@ -92,10 +103,6 @@ config QCOM_Q6V5_PIL Say y here to support the Qualcomm Peripherial Image Loader for the Hexagon V5 based remote processors. -config QCOM_WCNSS_IRIS - tristate - depends on OF && ARCH_QCOM - config QCOM_WCNSS_PIL tristate "Qualcomm WCNSS Peripheral Image Loader" depends on OF && ARCH_QCOM @@ -103,7 +110,6 @@ config QCOM_WCNSS_PIL depends on REMOTEPROC select QCOM_MDT_LOADER select QCOM_SCM - select QCOM_WCNSS_IRIS help Say y here to support the Peripheral Image Loader for the Qualcomm Wireless Connectivity Subsystem. diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile index 924f0cb25470..6290c3f72e83 100644 --- a/drivers/remoteproc/Makefile +++ b/drivers/remoteproc/Makefile @@ -5,15 +5,18 @@ obj-$(CONFIG_REMOTEPROC) += remoteproc.o remoteproc-y := remoteproc_core.o remoteproc-y += remoteproc_debugfs.o +remoteproc-y += remoteproc_sysfs.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 obj-$(CONFIG_WKUP_M3_RPROC) += wkup_m3_rproc.o obj-$(CONFIG_DA8XX_REMOTEPROC) += da8xx_remoteproc.o +obj-$(CONFIG_QCOM_ADSP_PIL) += qcom_adsp_pil.o obj-$(CONFIG_QCOM_MDT_LOADER) += qcom_mdt_loader.o obj-$(CONFIG_QCOM_Q6V5_PIL) += qcom_q6v5_pil.o -obj-$(CONFIG_QCOM_WCNSS_IRIS) += qcom_wcnss_iris.o -obj-$(CONFIG_QCOM_WCNSS_PIL) += qcom_wcnss.o +obj-$(CONFIG_QCOM_WCNSS_PIL) += qcom_wcnss_pil.o +qcom_wcnss_pil-y += qcom_wcnss.o +qcom_wcnss_pil-y += qcom_wcnss_iris.o obj-$(CONFIG_ST_REMOTEPROC) += st_remoteproc.o obj-$(CONFIG_ST_SLIM_REMOTEPROC) += st_slim_rproc.o diff --git a/drivers/remoteproc/qcom_adsp_pil.c b/drivers/remoteproc/qcom_adsp_pil.c new file mode 100644 index 000000000000..914163315091 --- /dev/null +++ b/drivers/remoteproc/qcom_adsp_pil.c @@ -0,0 +1,399 @@ +/* + * Qualcomm ADSP Peripheral Image Loader for MSM8974 and MSM8996 + * + * Copyright (C) 2016 Linaro Ltd + * Copyright (C) 2014 Sony Mobile Communications AB + * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/firmware.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/qcom_scm.h> +#include <linux/regulator/consumer.h> +#include <linux/remoteproc.h> +#include <linux/soc/qcom/smem.h> +#include <linux/soc/qcom/smem_state.h> + +#include "qcom_mdt_loader.h" +#include "remoteproc_internal.h" + +#define ADSP_CRASH_REASON_SMEM 423 +#define ADSP_FIRMWARE_NAME "adsp.mdt" +#define ADSP_PAS_ID 1 + +struct qcom_adsp { + struct device *dev; + struct rproc *rproc; + + int wdog_irq; + int fatal_irq; + int ready_irq; + int handover_irq; + int stop_ack_irq; + + struct qcom_smem_state *state; + unsigned stop_bit; + + struct regulator *cx_supply; + + struct completion start_done; + struct completion stop_done; + + phys_addr_t mem_phys; + phys_addr_t mem_reloc; + void *mem_region; + size_t mem_size; +}; + +static int adsp_load(struct rproc *rproc, const struct firmware *fw) +{ + struct qcom_adsp *adsp = (struct qcom_adsp *)rproc->priv; + phys_addr_t fw_addr; + size_t fw_size; + bool relocate; + int ret; + + ret = qcom_scm_pas_init_image(ADSP_PAS_ID, fw->data, fw->size); + if (ret) { + dev_err(&rproc->dev, "invalid firmware metadata\n"); + return ret; + } + + ret = qcom_mdt_parse(fw, &fw_addr, &fw_size, &relocate); + if (ret) { + dev_err(&rproc->dev, "failed to parse mdt header\n"); + return ret; + } + + if (relocate) { + adsp->mem_reloc = fw_addr; + + ret = qcom_scm_pas_mem_setup(ADSP_PAS_ID, adsp->mem_phys, fw_size); + if (ret) { + dev_err(&rproc->dev, "unable to setup memory for image\n"); + return ret; + } + } + + return qcom_mdt_load(rproc, fw, rproc->firmware); +} + +static const struct rproc_fw_ops adsp_fw_ops = { + .find_rsc_table = qcom_mdt_find_rsc_table, + .load = adsp_load, +}; + +static int adsp_start(struct rproc *rproc) +{ + struct qcom_adsp *adsp = (struct qcom_adsp *)rproc->priv; + int ret; + + ret = regulator_enable(adsp->cx_supply); + if (ret) + return ret; + + ret = qcom_scm_pas_auth_and_reset(ADSP_PAS_ID); + if (ret) { + dev_err(adsp->dev, + "failed to authenticate image and release reset\n"); + goto disable_regulators; + } + + ret = wait_for_completion_timeout(&adsp->start_done, + msecs_to_jiffies(5000)); + if (!ret) { + dev_err(adsp->dev, "start timed out\n"); + qcom_scm_pas_shutdown(ADSP_PAS_ID); + ret = -ETIMEDOUT; + goto disable_regulators; + } + + ret = 0; + +disable_regulators: + regulator_disable(adsp->cx_supply); + + return ret; +} + +static int adsp_stop(struct rproc *rproc) +{ + struct qcom_adsp *adsp = (struct qcom_adsp *)rproc->priv; + int ret; + + qcom_smem_state_update_bits(adsp->state, + BIT(adsp->stop_bit), + BIT(adsp->stop_bit)); + + ret = wait_for_completion_timeout(&adsp->stop_done, + msecs_to_jiffies(5000)); + if (ret == 0) + dev_err(adsp->dev, "timed out on wait\n"); + + qcom_smem_state_update_bits(adsp->state, + BIT(adsp->stop_bit), + 0); + + ret = qcom_scm_pas_shutdown(ADSP_PAS_ID); + if (ret) + dev_err(adsp->dev, "failed to shutdown: %d\n", ret); + + return ret; +} + +static void *adsp_da_to_va(struct rproc *rproc, u64 da, int len) +{ + struct qcom_adsp *adsp = (struct qcom_adsp *)rproc->priv; + int offset; + + offset = da - adsp->mem_reloc; + if (offset < 0 || offset + len > adsp->mem_size) + return NULL; + + return adsp->mem_region + offset; +} + +static const struct rproc_ops adsp_ops = { + .start = adsp_start, + .stop = adsp_stop, + .da_to_va = adsp_da_to_va, +}; + +static irqreturn_t adsp_wdog_interrupt(int irq, void *dev) +{ + struct qcom_adsp *adsp = dev; + + rproc_report_crash(adsp->rproc, RPROC_WATCHDOG); + + return IRQ_HANDLED; +} + +static irqreturn_t adsp_fatal_interrupt(int irq, void *dev) +{ + struct qcom_adsp *adsp = dev; + size_t len; + char *msg; + + msg = qcom_smem_get(QCOM_SMEM_HOST_ANY, ADSP_CRASH_REASON_SMEM, &len); + if (!IS_ERR(msg) && len > 0 && msg[0]) + dev_err(adsp->dev, "fatal error received: %s\n", msg); + + rproc_report_crash(adsp->rproc, RPROC_FATAL_ERROR); + + if (!IS_ERR(msg)) + msg[0] = '\0'; + + return IRQ_HANDLED; +} + +static irqreturn_t adsp_ready_interrupt(int irq, void *dev) +{ + return IRQ_HANDLED; +} + +static irqreturn_t adsp_handover_interrupt(int irq, void *dev) +{ + struct qcom_adsp *adsp = dev; + + complete(&adsp->start_done); + + return IRQ_HANDLED; +} + +static irqreturn_t adsp_stop_ack_interrupt(int irq, void *dev) +{ + struct qcom_adsp *adsp = dev; + + complete(&adsp->stop_done); + + return IRQ_HANDLED; +} + +static int adsp_init_regulator(struct qcom_adsp *adsp) +{ + adsp->cx_supply = devm_regulator_get(adsp->dev, "cx"); + if (IS_ERR(adsp->cx_supply)) + return PTR_ERR(adsp->cx_supply); + + regulator_set_load(adsp->cx_supply, 100000); + + return 0; +} + +static int adsp_request_irq(struct qcom_adsp *adsp, + struct platform_device *pdev, + const char *name, + irq_handler_t thread_fn) +{ + int ret; + + ret = platform_get_irq_byname(pdev, name); + if (ret < 0) { + dev_err(&pdev->dev, "no %s IRQ defined\n", name); + return ret; + } + + ret = devm_request_threaded_irq(&pdev->dev, ret, + NULL, thread_fn, + IRQF_ONESHOT, + "adsp", adsp); + if (ret) + dev_err(&pdev->dev, "request %s IRQ failed\n", name); + + return ret; +} + +static int adsp_alloc_memory_region(struct qcom_adsp *adsp) +{ + struct device_node *node; + struct resource r; + int ret; + + node = of_parse_phandle(adsp->dev->of_node, "memory-region", 0); + if (!node) { + dev_err(adsp->dev, "no memory-region specified\n"); + return -EINVAL; + } + + ret = of_address_to_resource(node, 0, &r); + if (ret) + return ret; + + adsp->mem_phys = adsp->mem_reloc = r.start; + adsp->mem_size = resource_size(&r); + adsp->mem_region = devm_ioremap_wc(adsp->dev, adsp->mem_phys, adsp->mem_size); + if (!adsp->mem_region) { + dev_err(adsp->dev, "unable to map memory region: %pa+%zx\n", + &r.start, adsp->mem_size); + return -EBUSY; + } + + return 0; +} + +static int adsp_probe(struct platform_device *pdev) +{ + struct qcom_adsp *adsp; + struct rproc *rproc; + int ret; + + if (!qcom_scm_is_available()) + return -EPROBE_DEFER; + + if (!qcom_scm_pas_supported(ADSP_PAS_ID)) { + dev_err(&pdev->dev, "PAS is not available for ADSP\n"); + return -ENXIO; + } + + rproc = rproc_alloc(&pdev->dev, pdev->name, &adsp_ops, + ADSP_FIRMWARE_NAME, sizeof(*adsp)); + if (!rproc) { + dev_err(&pdev->dev, "unable to allocate remoteproc\n"); + return -ENOMEM; + } + + rproc->fw_ops = &adsp_fw_ops; + + adsp = (struct qcom_adsp *)rproc->priv; + adsp->dev = &pdev->dev; + adsp->rproc = rproc; + platform_set_drvdata(pdev, adsp); + + init_completion(&adsp->start_done); + init_completion(&adsp->stop_done); + + ret = adsp_alloc_memory_region(adsp); + if (ret) + goto free_rproc; + + ret = adsp_init_regulator(adsp); + if (ret) + goto free_rproc; + + ret = adsp_request_irq(adsp, pdev, "wdog", adsp_wdog_interrupt); + if (ret < 0) + goto free_rproc; + adsp->wdog_irq = ret; + + ret = adsp_request_irq(adsp, pdev, "fatal", adsp_fatal_interrupt); + if (ret < 0) + goto free_rproc; + adsp->fatal_irq = ret; + + ret = adsp_request_irq(adsp, pdev, "ready", adsp_ready_interrupt); + if (ret < 0) + goto free_rproc; + adsp->ready_irq = ret; + + ret = adsp_request_irq(adsp, pdev, "handover", adsp_handover_interrupt); + if (ret < 0) + goto free_rproc; + adsp->handover_irq = ret; + + ret = adsp_request_irq(adsp, pdev, "stop-ack", adsp_stop_ack_interrupt); + if (ret < 0) + goto free_rproc; + adsp->stop_ack_irq = ret; + + adsp->state = qcom_smem_state_get(&pdev->dev, "stop", + &adsp->stop_bit); + if (IS_ERR(adsp->state)) { + ret = PTR_ERR(adsp->state); + goto free_rproc; + } + + ret = rproc_add(rproc); + if (ret) + goto free_rproc; + + return 0; + +free_rproc: + rproc_put(rproc); + + return ret; +} + +static int adsp_remove(struct platform_device *pdev) +{ + struct qcom_adsp *adsp = platform_get_drvdata(pdev); + + qcom_smem_state_put(adsp->state); + rproc_del(adsp->rproc); + rproc_put(adsp->rproc); + + return 0; +} + +static const struct of_device_id adsp_of_match[] = { + { .compatible = "qcom,msm8974-adsp-pil" }, + { .compatible = "qcom,msm8996-adsp-pil" }, + { }, +}; + +static struct platform_driver adsp_driver = { + .probe = adsp_probe, + .remove = adsp_remove, + .driver = { + .name = "qcom_adsp_pil", + .of_match_table = adsp_of_match, + }, +}; + +module_platform_driver(adsp_driver); +MODULE_DESCRIPTION("Qualcomm MSM8974/MSM8996 ADSP Peripherial Image Loader"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/remoteproc/qcom_q6v5_pil.c b/drivers/remoteproc/qcom_q6v5_pil.c index 2e0caaaa766a..b08989b48df7 100644 --- a/drivers/remoteproc/qcom_q6v5_pil.c +++ b/drivers/remoteproc/qcom_q6v5_pil.c @@ -894,6 +894,7 @@ static const struct of_device_id q6v5_of_match[] = { { .compatible = "qcom,q6v5-pil", }, { }, }; +MODULE_DEVICE_TABLE(of, q6v5_of_match); static struct platform_driver q6v5_driver = { .probe = q6v5_probe, diff --git a/drivers/remoteproc/qcom_wcnss.c b/drivers/remoteproc/qcom_wcnss.c index f5cedeaafba1..ebd61f5d18bb 100644 --- a/drivers/remoteproc/qcom_wcnss.c +++ b/drivers/remoteproc/qcom_wcnss.c @@ -30,6 +30,7 @@ #include <linux/remoteproc.h> #include <linux/soc/qcom/smem.h> #include <linux/soc/qcom/smem_state.h> +#include <linux/rpmsg/qcom_smd.h> #include "qcom_mdt_loader.h" #include "remoteproc_internal.h" @@ -94,6 +95,10 @@ struct qcom_wcnss { phys_addr_t mem_reloc; void *mem_region; size_t mem_size; + + struct device_node *smd_node; + struct qcom_smd_edge *smd_edge; + struct rproc_subdev smd_subdev; }; static const struct wcnss_data riva_data = { @@ -143,7 +148,6 @@ void qcom_wcnss_assign_iris(struct qcom_wcnss *wcnss, mutex_unlock(&wcnss->iris_lock); } -EXPORT_SYMBOL_GPL(qcom_wcnss_assign_iris); static int wcnss_load(struct rproc *rproc, const struct firmware *fw) { @@ -396,6 +400,23 @@ static irqreturn_t wcnss_stop_ack_interrupt(int irq, void *dev) return IRQ_HANDLED; } +static int wcnss_smd_probe(struct rproc_subdev *subdev) +{ + struct qcom_wcnss *wcnss = container_of(subdev, struct qcom_wcnss, smd_subdev); + + wcnss->smd_edge = qcom_smd_register_edge(wcnss->dev, wcnss->smd_node); + + return IS_ERR(wcnss->smd_edge) ? PTR_ERR(wcnss->smd_edge) : 0; +} + +static void wcnss_smd_remove(struct rproc_subdev *subdev) +{ + struct qcom_wcnss *wcnss = container_of(subdev, struct qcom_wcnss, smd_subdev); + + qcom_smd_unregister_edge(wcnss->smd_edge); + wcnss->smd_edge = NULL; +} + static int wcnss_init_regulators(struct qcom_wcnss *wcnss, const struct wcnss_vreg_info *info, int num_vregs) @@ -578,6 +599,10 @@ static int wcnss_probe(struct platform_device *pdev) } } + wcnss->smd_node = of_get_child_by_name(pdev->dev.of_node, "smd-edge"); + if (wcnss->smd_node) + rproc_add_subdev(rproc, &wcnss->smd_subdev, wcnss_smd_probe, wcnss_smd_remove); + ret = rproc_add(rproc); if (ret) goto free_rproc; @@ -596,6 +621,7 @@ static int wcnss_remove(struct platform_device *pdev) of_platform_depopulate(&pdev->dev); + of_node_put(wcnss->smd_node); qcom_smem_state_put(wcnss->state); rproc_del(wcnss->rproc); rproc_free(wcnss->rproc); @@ -609,6 +635,7 @@ static const struct of_device_id wcnss_of_match[] = { { .compatible = "qcom,pronto-v2-pil", &pronto_v2_data }, { }, }; +MODULE_DEVICE_TABLE(of, wcnss_of_match); static struct platform_driver wcnss_driver = { .probe = wcnss_probe, @@ -619,6 +646,28 @@ static struct platform_driver wcnss_driver = { }, }; -module_platform_driver(wcnss_driver); +static int __init wcnss_init(void) +{ + int ret; + + ret = platform_driver_register(&wcnss_driver); + if (ret) + return ret; + + ret = platform_driver_register(&qcom_iris_driver); + if (ret) + platform_driver_unregister(&wcnss_driver); + + return ret; +} +module_init(wcnss_init); + +static void __exit wcnss_exit(void) +{ + platform_driver_unregister(&qcom_iris_driver); + platform_driver_unregister(&wcnss_driver); +} +module_exit(wcnss_exit); + MODULE_DESCRIPTION("Qualcomm Peripherial Image Loader for Wireless Subsystem"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/remoteproc/qcom_wcnss.h b/drivers/remoteproc/qcom_wcnss.h index 9dc4a9fe41e1..25fb7f62a457 100644 --- a/drivers/remoteproc/qcom_wcnss.h +++ b/drivers/remoteproc/qcom_wcnss.h @@ -4,6 +4,8 @@ struct qcom_iris; struct qcom_wcnss; +extern struct platform_driver qcom_iris_driver; + struct wcnss_vreg_info { const char * const name; int min_voltage; diff --git a/drivers/remoteproc/qcom_wcnss_iris.c b/drivers/remoteproc/qcom_wcnss_iris.c index f0ca24a8dd0b..e842be58e8c7 100644 --- a/drivers/remoteproc/qcom_wcnss_iris.c +++ b/drivers/remoteproc/qcom_wcnss_iris.c @@ -94,14 +94,12 @@ disable_regulators: return ret; } -EXPORT_SYMBOL_GPL(qcom_iris_enable); void qcom_iris_disable(struct qcom_iris *iris) { clk_disable_unprepare(iris->xo_clk); regulator_bulk_disable(iris->num_vregs, iris->vregs); } -EXPORT_SYMBOL_GPL(qcom_iris_disable); static int qcom_iris_probe(struct platform_device *pdev) { @@ -173,8 +171,9 @@ static const struct of_device_id iris_of_match[] = { { .compatible = "qcom,wcn3680", .data = &wcn3680_data }, {} }; +MODULE_DEVICE_TABLE(of, iris_of_match); -static struct platform_driver wcnss_driver = { +struct platform_driver qcom_iris_driver = { .probe = qcom_iris_probe, .remove = qcom_iris_remove, .driver = { @@ -182,7 +181,3 @@ static struct platform_driver wcnss_driver = { .of_match_table = iris_of_match, }, }; - -module_platform_driver(wcnss_driver); -MODULE_DESCRIPTION("Qualcomm Wireless Subsystem Iris driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/remoteproc/remoteproc_core.c b/drivers/remoteproc/remoteproc_core.c index c6bfb3496684..f0f6ec1ab12b 100644 --- a/drivers/remoteproc/remoteproc_core.c +++ b/drivers/remoteproc/remoteproc_core.c @@ -236,6 +236,10 @@ int rproc_alloc_vring(struct rproc_vdev *rvdev, int i) } notifyid = ret; + /* Potentially bump max_notifyid */ + if (notifyid > rproc->max_notifyid) + rproc->max_notifyid = notifyid; + dev_dbg(dev, "vring%d: va %p dma %pad size 0x%x idr %d\n", i, va, &dma, size, notifyid); @@ -296,6 +300,20 @@ void rproc_free_vring(struct rproc_vring *rvring) rsc->vring[idx].notifyid = -1; } +static int rproc_vdev_do_probe(struct rproc_subdev *subdev) +{ + struct rproc_vdev *rvdev = container_of(subdev, struct rproc_vdev, subdev); + + return rproc_add_virtio_dev(rvdev, rvdev->id); +} + +static void rproc_vdev_do_remove(struct rproc_subdev *subdev) +{ + struct rproc_vdev *rvdev = container_of(subdev, struct rproc_vdev, subdev); + + rproc_remove_virtio_dev(rvdev); +} + /** * rproc_handle_vdev() - handle a vdev fw resource * @rproc: the remote processor @@ -356,6 +374,9 @@ static int rproc_handle_vdev(struct rproc *rproc, struct fw_rsc_vdev *rsc, if (!rvdev) return -ENOMEM; + kref_init(&rvdev->refcount); + + rvdev->id = rsc->id; rvdev->rproc = rproc; /* parse the vrings */ @@ -368,22 +389,51 @@ static int rproc_handle_vdev(struct rproc *rproc, struct fw_rsc_vdev *rsc, /* remember the resource offset*/ rvdev->rsc_offset = offset; + /* allocate the vring resources */ + for (i = 0; i < rsc->num_of_vrings; i++) { + ret = rproc_alloc_vring(rvdev, i); + if (ret) + goto unwind_vring_allocations; + } + + /* track the rvdevs list reference */ + kref_get(&rvdev->refcount); + list_add_tail(&rvdev->node, &rproc->rvdevs); - /* it is now safe to add the virtio device */ - ret = rproc_add_virtio_dev(rvdev, rsc->id); - if (ret) - goto remove_rvdev; + rproc_add_subdev(rproc, &rvdev->subdev, + rproc_vdev_do_probe, rproc_vdev_do_remove); return 0; -remove_rvdev: - list_del(&rvdev->node); +unwind_vring_allocations: + for (i--; i >= 0; i--) + rproc_free_vring(&rvdev->vring[i]); free_rvdev: kfree(rvdev); return ret; } +void rproc_vdev_release(struct kref *ref) +{ + struct rproc_vdev *rvdev = container_of(ref, struct rproc_vdev, refcount); + struct rproc_vring *rvring; + struct rproc *rproc = rvdev->rproc; + int id; + + for (id = 0; id < ARRAY_SIZE(rvdev->vring); id++) { + rvring = &rvdev->vring[id]; + if (!rvring->va) + continue; + + rproc_free_vring(rvring); + } + + rproc_remove_subdev(rproc, &rvdev->subdev); + list_del(&rvdev->node); + kfree(rvdev); +} + /** * rproc_handle_trace() - handle a shared trace buffer resource * @rproc: the remote processor @@ -673,15 +723,6 @@ free_carv: return ret; } -static int rproc_count_vrings(struct rproc *rproc, struct fw_rsc_vdev *rsc, - int offset, int avail) -{ - /* Summarize the number of notification IDs */ - rproc->max_notifyid += rsc->num_of_vrings; - - return 0; -} - /* * A lookup table for resource handlers. The indices are defined in * enum fw_resource_type. @@ -690,10 +731,6 @@ static rproc_handle_resource_t rproc_loading_handlers[RSC_LAST] = { [RSC_CARVEOUT] = (rproc_handle_resource_t)rproc_handle_carveout, [RSC_DEVMEM] = (rproc_handle_resource_t)rproc_handle_devmem, [RSC_TRACE] = (rproc_handle_resource_t)rproc_handle_trace, - [RSC_VDEV] = (rproc_handle_resource_t)rproc_count_vrings, -}; - -static rproc_handle_resource_t rproc_vdev_handler[RSC_LAST] = { [RSC_VDEV] = (rproc_handle_resource_t)rproc_handle_vdev, }; @@ -736,6 +773,34 @@ static int rproc_handle_resources(struct rproc *rproc, int len, return ret; } +static int rproc_probe_subdevices(struct rproc *rproc) +{ + struct rproc_subdev *subdev; + int ret; + + list_for_each_entry(subdev, &rproc->subdevs, node) { + ret = subdev->probe(subdev); + if (ret) + goto unroll_registration; + } + + return 0; + +unroll_registration: + list_for_each_entry_continue_reverse(subdev, &rproc->subdevs, node) + subdev->remove(subdev); + + return ret; +} + +static void rproc_remove_subdevices(struct rproc *rproc) +{ + struct rproc_subdev *subdev; + + list_for_each_entry(subdev, &rproc->subdevs, node) + subdev->remove(subdev); +} + /** * rproc_resource_cleanup() - clean up and free all acquired resources * @rproc: rproc handle @@ -782,7 +847,7 @@ static void rproc_resource_cleanup(struct rproc *rproc) /* clean up remote vdev entries */ list_for_each_entry_safe(rvdev, rvtmp, &rproc->rvdevs, node) - rproc_remove_virtio_dev(rvdev); + kref_put(&rvdev->refcount, rproc_vdev_release); } /* @@ -824,25 +889,16 @@ static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw) /* * Create a copy of the resource table. When a virtio device starts * and calls vring_new_virtqueue() the address of the allocated vring - * will be stored in the cached_table. Before the device is started, - * cached_table will be copied into device memory. + * will be stored in the table_ptr. Before the device is started, + * table_ptr will be copied into device memory. */ - rproc->cached_table = kmemdup(table, tablesz, GFP_KERNEL); - if (!rproc->cached_table) + rproc->table_ptr = kmemdup(table, tablesz, GFP_KERNEL); + if (!rproc->table_ptr) goto clean_up; - rproc->table_ptr = rproc->cached_table; - /* reset max_notifyid */ rproc->max_notifyid = -1; - /* look for virtio devices and register them */ - ret = rproc_handle_resources(rproc, tablesz, rproc_vdev_handler); - if (ret) { - dev_err(dev, "Failed to handle vdev resources: %d\n", ret); - goto clean_up; - } - /* handle fw resources which are required to boot rproc */ ret = rproc_handle_resources(rproc, tablesz, rproc_loading_handlers); if (ret) { @@ -858,18 +914,16 @@ static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw) } /* - * The starting device has been given the rproc->cached_table as the + * The starting device has been given the rproc->table_ptr as the * resource table. The address of the vring along with the other - * allocated resources (carveouts etc) is stored in cached_table. + * allocated resources (carveouts etc) is stored in table_ptr. * In order to pass this information to the remote device we must copy * this information to device memory. We also update the table_ptr so * that any subsequent changes will be applied to the loaded version. */ loaded_table = rproc_find_loaded_rsc_table(rproc, fw); - if (loaded_table) { - memcpy(loaded_table, rproc->cached_table, tablesz); - rproc->table_ptr = loaded_table; - } + if (loaded_table) + memcpy(loaded_table, rproc->table_ptr, tablesz); /* power up the remote processor */ ret = rproc->ops->start(rproc); @@ -878,17 +932,26 @@ static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw) goto clean_up_resources; } + /* probe any subdevices for the remote processor */ + ret = rproc_probe_subdevices(rproc); + if (ret) { + dev_err(dev, "failed to probe subdevices for %s: %d\n", + rproc->name, ret); + goto stop_rproc; + } + rproc->state = RPROC_RUNNING; dev_info(dev, "remote processor %s is now up\n", rproc->name); return 0; +stop_rproc: + rproc->ops->stop(rproc); clean_up_resources: rproc_resource_cleanup(rproc); clean_up: - kfree(rproc->cached_table); - rproc->cached_table = NULL; + kfree(rproc->table_ptr); rproc->table_ptr = NULL; rproc_disable_iommu(rproc); @@ -1121,6 +1184,9 @@ void rproc_shutdown(struct rproc *rproc) if (!atomic_dec_and_test(&rproc->power)) goto out; + /* remove any subdevices for the remote processor */ + rproc_remove_subdevices(rproc); + /* power off the remote processor */ ret = rproc->ops->stop(rproc); if (ret) { @@ -1135,8 +1201,7 @@ void rproc_shutdown(struct rproc *rproc) rproc_disable_iommu(rproc); /* Free the copy of the resource table */ - kfree(rproc->cached_table); - rproc->cached_table = NULL; + kfree(rproc->table_ptr); rproc->table_ptr = NULL; /* if in crash state, unlock crash handler */ @@ -1273,6 +1338,7 @@ static void rproc_type_release(struct device *dev) if (rproc->index >= 0) ida_simple_remove(&rproc_dev_index, rproc->index); + kfree(rproc->firmware); kfree(rproc); } @@ -1310,31 +1376,31 @@ struct rproc *rproc_alloc(struct device *dev, const char *name, { struct rproc *rproc; char *p, *template = "rproc-%s-fw"; - int name_len = 0; + int name_len; if (!dev || !name || !ops) return NULL; - if (!firmware) + if (!firmware) { /* - * Make room for default firmware name (minus %s plus '\0'). * If the caller didn't pass in a firmware name then - * construct a default name. We're already glomming 'len' - * bytes onto the end of the struct rproc allocation, so do - * a few more for the default firmware name (but only if - * the caller doesn't pass one). + * construct a default name. */ name_len = strlen(name) + strlen(template) - 2 + 1; - - rproc = kzalloc(sizeof(*rproc) + len + name_len, GFP_KERNEL); - if (!rproc) - return NULL; - - if (!firmware) { - p = (char *)rproc + sizeof(struct rproc) + len; + p = kmalloc(name_len, GFP_KERNEL); + if (!p) + return NULL; snprintf(p, name_len, template, name); } else { - p = (char *)firmware; + p = kstrdup(firmware, GFP_KERNEL); + if (!p) + return NULL; + } + + rproc = kzalloc(sizeof(struct rproc) + len, GFP_KERNEL); + if (!rproc) { + kfree(p); + return NULL; } rproc->firmware = p; @@ -1346,6 +1412,7 @@ struct rproc *rproc_alloc(struct device *dev, const char *name, device_initialize(&rproc->dev); rproc->dev.parent = dev; rproc->dev.type = &rproc_type; + rproc->dev.class = &rproc_class; /* Assign a unique device index and name */ rproc->index = ida_simple_get(&rproc_dev_index, 0, 0, GFP_KERNEL); @@ -1370,6 +1437,7 @@ struct rproc *rproc_alloc(struct device *dev, const char *name, INIT_LIST_HEAD(&rproc->mappings); INIT_LIST_HEAD(&rproc->traces); INIT_LIST_HEAD(&rproc->rvdevs); + INIT_LIST_HEAD(&rproc->subdevs); INIT_WORK(&rproc->crash_handler, rproc_crash_handler_work); init_completion(&rproc->crash_comp); @@ -1428,8 +1496,6 @@ EXPORT_SYMBOL(rproc_put); */ int rproc_del(struct rproc *rproc) { - struct rproc_vdev *rvdev, *tmp; - if (!rproc) return -EINVAL; @@ -1441,10 +1507,6 @@ int rproc_del(struct rproc *rproc) if (rproc->auto_boot) rproc_shutdown(rproc); - /* clean up remote vdev entries */ - list_for_each_entry_safe(rvdev, tmp, &rproc->rvdevs, node) - rproc_remove_virtio_dev(rvdev); - /* the rproc is downref'ed as soon as it's removed from the klist */ mutex_lock(&rproc_list_mutex); list_del(&rproc->node); @@ -1457,6 +1519,36 @@ int rproc_del(struct rproc *rproc) EXPORT_SYMBOL(rproc_del); /** + * rproc_add_subdev() - add a subdevice to a remoteproc + * @rproc: rproc handle to add the subdevice to + * @subdev: subdev handle to register + * @probe: function to call when the rproc boots + * @remove: function to call when the rproc shuts down + */ +void rproc_add_subdev(struct rproc *rproc, + struct rproc_subdev *subdev, + int (*probe)(struct rproc_subdev *subdev), + void (*remove)(struct rproc_subdev *subdev)) +{ + subdev->probe = probe; + subdev->remove = remove; + + list_add_tail(&subdev->node, &rproc->subdevs); +} +EXPORT_SYMBOL(rproc_add_subdev); + +/** + * rproc_remove_subdev() - remove a subdevice from a remoteproc + * @rproc: rproc handle to remove the subdevice from + * @subdev: subdev handle, previously registered with rproc_add_subdev() + */ +void rproc_remove_subdev(struct rproc *rproc, struct rproc_subdev *subdev) +{ + list_del(&subdev->node); +} +EXPORT_SYMBOL(rproc_remove_subdev); + +/** * rproc_report_crash() - rproc crash reporter function * @rproc: remote processor * @type: crash type @@ -1484,6 +1576,7 @@ EXPORT_SYMBOL(rproc_report_crash); static int __init remoteproc_init(void) { + rproc_init_sysfs(); rproc_init_debugfs(); return 0; @@ -1495,6 +1588,7 @@ static void __exit remoteproc_exit(void) ida_destroy(&rproc_dev_index); rproc_exit_debugfs(); + rproc_exit_sysfs(); } module_exit(remoteproc_exit); diff --git a/drivers/remoteproc/remoteproc_debugfs.c b/drivers/remoteproc/remoteproc_debugfs.c index 374797206c79..1c122e230cec 100644 --- a/drivers/remoteproc/remoteproc_debugfs.c +++ b/drivers/remoteproc/remoteproc_debugfs.c @@ -59,75 +59,6 @@ static const struct file_operations trace_rproc_ops = { .llseek = generic_file_llseek, }; -/* - * A state-to-string lookup table, for exposing a human readable state - * via debugfs. Always keep in sync with enum rproc_state - */ -static const char * const rproc_state_string[] = { - "offline", - "suspended", - "running", - "crashed", - "invalid", -}; - -/* expose the state of the remote processor via debugfs */ -static ssize_t rproc_state_read(struct file *filp, char __user *userbuf, - size_t count, loff_t *ppos) -{ - struct rproc *rproc = filp->private_data; - unsigned int state; - char buf[30]; - int i; - - state = rproc->state > RPROC_LAST ? RPROC_LAST : rproc->state; - - i = scnprintf(buf, 30, "%.28s (%d)\n", rproc_state_string[state], - rproc->state); - - return simple_read_from_buffer(userbuf, count, ppos, buf, i); -} - -static ssize_t rproc_state_write(struct file *filp, const char __user *userbuf, - size_t count, loff_t *ppos) -{ - struct rproc *rproc = filp->private_data; - char buf[10]; - int ret; - - if (count > sizeof(buf) || count <= 0) - return -EINVAL; - - ret = copy_from_user(buf, userbuf, count); - if (ret) - return -EFAULT; - - if (buf[count - 1] == '\n') - buf[count - 1] = '\0'; - - if (!strncmp(buf, "start", count)) { - ret = rproc_boot(rproc); - if (ret) { - dev_err(&rproc->dev, "Boot failed: %d\n", ret); - return ret; - } - } else if (!strncmp(buf, "stop", count)) { - rproc_shutdown(rproc); - } else { - dev_err(&rproc->dev, "Unrecognised option: %s\n", buf); - return -EINVAL; - } - - return count; -} - -static const struct file_operations rproc_state_ops = { - .read = rproc_state_read, - .write = rproc_state_write, - .open = simple_open, - .llseek = generic_file_llseek, -}; - /* expose the name of the remote processor via debugfs */ static ssize_t rproc_name_read(struct file *filp, char __user *userbuf, size_t count, loff_t *ppos) @@ -265,8 +196,6 @@ void rproc_create_debug_dir(struct rproc *rproc) debugfs_create_file("name", 0400, rproc->dbg_dir, 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); } diff --git a/drivers/remoteproc/remoteproc_internal.h b/drivers/remoteproc/remoteproc_internal.h index 4cf93ca2816e..1e9e5b3f021c 100644 --- a/drivers/remoteproc/remoteproc_internal.h +++ b/drivers/remoteproc/remoteproc_internal.h @@ -49,6 +49,7 @@ struct rproc_fw_ops { void rproc_release(struct kref *kref); irqreturn_t rproc_vq_interrupt(struct rproc *rproc, int vq_id); int rproc_boot_nowait(struct rproc *rproc); +void rproc_vdev_release(struct kref *ref); /* from remoteproc_virtio.c */ int rproc_add_virtio_dev(struct rproc_vdev *rvdev, int id); @@ -63,6 +64,11 @@ void rproc_create_debug_dir(struct rproc *rproc); void rproc_init_debugfs(void); void rproc_exit_debugfs(void); +/* from remoteproc_sysfs.c */ +extern struct class rproc_class; +int rproc_init_sysfs(void); +void rproc_exit_sysfs(void); + void rproc_free_vring(struct rproc_vring *rvring); int rproc_alloc_vring(struct rproc_vdev *rvdev, int i); diff --git a/drivers/remoteproc/remoteproc_sysfs.c b/drivers/remoteproc/remoteproc_sysfs.c new file mode 100644 index 000000000000..bc5b0e00efb1 --- /dev/null +++ b/drivers/remoteproc/remoteproc_sysfs.c @@ -0,0 +1,151 @@ +/* + * Remote Processor Framework + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/remoteproc.h> + +#include "remoteproc_internal.h" + +#define to_rproc(d) container_of(d, struct rproc, dev) + +/* Expose the loaded / running firmware name via sysfs */ +static ssize_t firmware_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct rproc *rproc = to_rproc(dev); + + return sprintf(buf, "%s\n", rproc->firmware); +} + +/* Change firmware name via sysfs */ +static ssize_t firmware_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct rproc *rproc = to_rproc(dev); + char *p; + int err, len = count; + + err = mutex_lock_interruptible(&rproc->lock); + if (err) { + dev_err(dev, "can't lock rproc %s: %d\n", rproc->name, err); + return -EINVAL; + } + + if (rproc->state != RPROC_OFFLINE) { + dev_err(dev, "can't change firmware while running\n"); + err = -EBUSY; + goto out; + } + + len = strcspn(buf, "\n"); + + p = kstrndup(buf, len, GFP_KERNEL); + if (!p) { + err = -ENOMEM; + goto out; + } + + kfree(rproc->firmware); + rproc->firmware = p; +out: + mutex_unlock(&rproc->lock); + + return err ? err : count; +} +static DEVICE_ATTR_RW(firmware); + +/* + * A state-to-string lookup table, for exposing a human readable state + * via sysfs. Always keep in sync with enum rproc_state + */ +static const char * const rproc_state_string[] = { + [RPROC_OFFLINE] = "offline", + [RPROC_SUSPENDED] = "suspended", + [RPROC_RUNNING] = "running", + [RPROC_CRASHED] = "crashed", + [RPROC_LAST] = "invalid", +}; + +/* Expose the state of the remote processor via sysfs */ +static ssize_t state_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct rproc *rproc = to_rproc(dev); + unsigned int state; + + state = rproc->state > RPROC_LAST ? RPROC_LAST : rproc->state; + return sprintf(buf, "%s\n", rproc_state_string[state]); +} + +/* Change remote processor state via sysfs */ +static ssize_t state_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct rproc *rproc = to_rproc(dev); + int ret = 0; + + if (sysfs_streq(buf, "start")) { + if (rproc->state == RPROC_RUNNING) + return -EBUSY; + + ret = rproc_boot(rproc); + if (ret) + dev_err(&rproc->dev, "Boot failed: %d\n", ret); + } else if (sysfs_streq(buf, "stop")) { + if (rproc->state != RPROC_RUNNING) + return -EINVAL; + + rproc_shutdown(rproc); + } else { + dev_err(&rproc->dev, "Unrecognised option: %s\n", buf); + ret = -EINVAL; + } + return ret ? ret : count; +} +static DEVICE_ATTR_RW(state); + +static struct attribute *rproc_attrs[] = { + &dev_attr_firmware.attr, + &dev_attr_state.attr, + NULL +}; + +static const struct attribute_group rproc_devgroup = { + .attrs = rproc_attrs +}; + +static const struct attribute_group *rproc_devgroups[] = { + &rproc_devgroup, + NULL +}; + +struct class rproc_class = { + .name = "remoteproc", + .dev_groups = rproc_devgroups, +}; + +int __init rproc_init_sysfs(void) +{ + /* create remoteproc device class for sysfs */ + int err = class_register(&rproc_class); + + if (err) + pr_err("remoteproc: unable to register class\n"); + return err; +} + +void __exit rproc_exit_sysfs(void) +{ + class_unregister(&rproc_class); +} diff --git a/drivers/remoteproc/remoteproc_virtio.c b/drivers/remoteproc/remoteproc_virtio.c index 01870a16d6d2..364411fb7734 100644 --- a/drivers/remoteproc/remoteproc_virtio.c +++ b/drivers/remoteproc/remoteproc_virtio.c @@ -79,7 +79,7 @@ static struct virtqueue *rp_find_vq(struct virtio_device *vdev, struct rproc_vring *rvring; struct virtqueue *vq; void *addr; - int len, size, ret; + int len, size; /* we're temporarily limited to two virtqueues per rvdev */ if (id >= ARRAY_SIZE(rvdev->vring)) @@ -88,10 +88,6 @@ static struct virtqueue *rp_find_vq(struct virtio_device *vdev, if (!name) return NULL; - ret = rproc_alloc_vring(rvdev, id); - if (ret) - return ERR_PTR(ret); - rvring = &rvdev->vring[id]; addr = rvring->va; len = rvring->len; @@ -130,7 +126,6 @@ static void __rproc_virtio_del_vqs(struct virtio_device *vdev) rvring = vq->priv; rvring->vq = NULL; vring_del_virtqueue(vq); - rproc_free_vring(rvring); } } @@ -282,14 +277,13 @@ static const struct virtio_config_ops rproc_virtio_config_ops = { * Never call this function directly; it will be called by the driver * core when needed. */ -static void rproc_vdev_release(struct device *dev) +static void rproc_virtio_dev_release(struct device *dev) { struct virtio_device *vdev = dev_to_virtio(dev); struct rproc_vdev *rvdev = vdev_to_rvdev(vdev); struct rproc *rproc = vdev_to_rproc(vdev); - list_del(&rvdev->node); - kfree(rvdev); + kref_put(&rvdev->refcount, rproc_vdev_release); put_device(&rproc->dev); } @@ -313,7 +307,7 @@ int rproc_add_virtio_dev(struct rproc_vdev *rvdev, int id) vdev->id.device = id, vdev->config = &rproc_virtio_config_ops, vdev->dev.parent = dev; - vdev->dev.release = rproc_vdev_release; + vdev->dev.release = rproc_virtio_dev_release; /* * We're indirectly making a non-temporary copy of the rproc pointer @@ -325,6 +319,9 @@ int rproc_add_virtio_dev(struct rproc_vdev *rvdev, int id) */ get_device(&rproc->dev); + /* Reference the vdev and vring allocations */ + kref_get(&rvdev->refcount); + ret = register_virtio_device(vdev); if (ret) { put_device(&rproc->dev); diff --git a/drivers/remoteproc/st_remoteproc.c b/drivers/remoteproc/st_remoteproc.c index ae8963fcc8c8..da4e152e9733 100644 --- a/drivers/remoteproc/st_remoteproc.c +++ b/drivers/remoteproc/st_remoteproc.c @@ -245,8 +245,10 @@ static int st_rproc_probe(struct platform_device *pdev) goto free_rproc; enabled = st_rproc_state(pdev); - if (enabled < 0) + if (enabled < 0) { + ret = enabled; goto free_rproc; + } if (enabled) { atomic_inc(&rproc->power); |