/* * SPDX-FileCopyrightText: Copyright 2021-2023 Arm Limited and/or its affiliates * SPDX-License-Identifier: GPL-2.0-only * * This program is free software and is provided to you under the terms of the * GNU General Public License version 2 as published by the Free Software * Foundation, and any use by you of this program is subject to the terms * of such GNU licence. * * 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, you can access it online at * http://www.gnu.org/licenses/gpl-2.0.html. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /**************************************************************************** * Defines ****************************************************************************/ #define DMA_ADDR_BITS 32 /* Number of address bits */ #define ETHOSU_RPROC_DRIVER_VERSION "0.0.1" #define DEFAULT_FW_FILE "arm-ethos-u65.fw" #define DEFAULT_AUTO_BOOT false /* firmware naming module parameter */ static char fw_filename_param[256] = DEFAULT_FW_FILE; /* As the remoteproc is setup at probe, just allow the filename readonly */ module_param_string(filename, fw_filename_param, sizeof(fw_filename_param), 0444); MODULE_PARM_DESC(filename, "Filename for firmware image for Ethos-U remoteproc"); static bool auto_boot = DEFAULT_AUTO_BOOT; module_param(auto_boot, bool, 0); MODULE_PARM_DESC(auto_boot, "Set to one to auto boot at load."); #define RSC_MAPPING RSC_VENDOR_START + 1 /** * struct fw_rsc_map_range - memory map range * @da: Start device address of the memory address range * @pa: Start physical address of the memory address range * @len: length of memory address range * * Memory range to translate between physical and device addresses. */ struct fw_rsc_map_range { uint32_t da; uint32_t pa; uint32_t len; } __packed; /** * struct fw_rsc_mapping - memory map for address translation * @num_ranges: Number of ranges in the memory map * @range: Array of the ranges in the memory map * * This resource entry requests the host to provide information for how to * translate between physical and device addresses. */ struct fw_rsc_mapping { uint8_t num_ranges; struct fw_rsc_map_range range[0]; } __packed; struct ethosu_rproc { struct device *dev; struct reset_control *rstc; struct mbox_client mbox_client; struct mbox_chan *ch_rx; struct mbox_chan *ch_tx; struct workqueue_struct *wq; struct work_struct work; }; /* declaration is in remoteproc_internal.h */ extern irqreturn_t rproc_vq_interrupt(struct rproc *rproc, int vq_id); static void ethosu_mbox_bottom(struct work_struct *work) { struct ethosu_rproc *erproc = container_of( work, struct ethosu_rproc, work); struct rproc *rproc = dev_get_drvdata(erproc->dev); dev_dbg(&rproc->dev, "Handle interrupt"); rproc_vq_interrupt(rproc, 0); } static void ethosu_mbox_top(struct mbox_client *client, void *message) { struct ethosu_rproc *erproc = container_of( client, struct ethosu_rproc, mbox_client); queue_work(erproc->wq, &erproc->work); } static int ethosu_add_carveout(struct rproc *rproc, const phys_addr_t pa, const size_t size, const char *name) { struct device *dev = rproc->dev.parent; dma_addr_t da; void __iomem *va; struct rproc_mem_entry *mem; da = translate_phys_to_dma(dev, pa); dev_dbg(dev, "PA to DA. pa=0x%pa, da=0x%pad", &pa, &da); if (da == DMA_MAPPING_ERROR) { dev_err(dev, "No mapping found for PA. pa=%pa, size=%zu", &pa, size); return -ENOMEM; } va = devm_ioremap_wc(dev, pa, size); if (!va) { dev_err(dev, "Failed to remap address. pa=%pa, len=%zu", &pa, size); return -ENOMEM; } mem = rproc_mem_entry_init(dev, va, pa, size, da, NULL, NULL, name); if (!mem) return -ENOMEM; dev_info(dev, "Add carveout mapping. dma=%pad, da=%x, va=%p, len=%zu", &mem->dma, mem->da, mem->va, mem->len); rproc_add_carveout(rproc, mem); return 0; } static int ethosu_rproc_prepare(struct rproc *rproc) { struct device *dev = rproc->dev.parent; struct device_node *np = dev->of_node; struct of_phandle_iterator it; struct resource res; int i; int ret; dev_info(dev, "Preparing Ethos-U"); /* Add carveout for each 'reg' device tree entry */ for (i = 0; of_address_to_resource(np, i, &res) == 0; i++) { dev_info(dev, "Found resource. start=%llx, size=%llx", res.start, resource_size(&res)); ret = ethosu_add_carveout(rproc, res.start, resource_size(&res), res.name); if (ret) return ret; } of_phandle_iterator_init(&it, np, "memory-region", NULL, 0); while (of_phandle_iterator_next(&it) == 0) { struct reserved_mem *res_mem = of_reserved_mem_lookup(it.node); if (!res_mem) { dev_err(dev, "Failed to look up memory region. node=%p", it.node); return -EINVAL; } dev_info(dev, "Found memory region. pa=%llx, size=%llu, name=%s", res_mem->base, res_mem->size, it.node->name); ret = ethosu_add_carveout(rproc, res_mem->base, res_mem->size, it.node->name); if (ret) return ret; } return 0; } static int ethosu_rproc_start(struct rproc *rproc) { struct ethosu_rproc *erproc = (struct ethosu_rproc *)rproc->priv; struct device *dev = erproc->dev; dev_info(dev, "Starting up Ethos-U subsystem CPU"); return reset_control_deassert(erproc->rstc); } static int ethosu_rproc_stop(struct rproc *rproc) { struct ethosu_rproc *erproc = (struct ethosu_rproc *)rproc->priv; struct device *dev = erproc->dev; dev_info(dev, "Stopping Ethos-U subsystem CPU"); return reset_control_assert(erproc->rstc); } static void ethosu_rproc_kick(struct rproc *rproc, int vqid) { struct ethosu_rproc *erproc = (struct ethosu_rproc *)rproc->priv; dev_dbg(&rproc->dev, "Kicking Ethos-U remoteproc vqid: %d!", vqid); mbox_send_message(erproc->ch_tx, (void *)&vqid); } static int ethosu_rproc_handle_rsc(struct rproc *rproc, u32 rsc_type, void *rsc, int offset, int avail) { struct ethosu_rproc *erproc = (struct ethosu_rproc *)rproc->priv; struct device *dev = erproc->dev; struct fw_rsc_mapping *mapping = rsc; const struct bus_dma_region *map; size_t num_ranges = 0U; size_t i; if (rsc_type != RSC_MAPPING) return RSC_IGNORED; if (struct_size(mapping, range, mapping->num_ranges) > avail) { dev_err(dev, "mapping rsc is truncated\n"); return -EINVAL; } for (map = dev->dma_range_map; map->size; ++map) num_ranges++; if (num_ranges > mapping->num_ranges) { dev_err(dev, "Mapping rsc doesn't have enough room for DMA ranges\n"); return -EINVAL; } for (i = 0U; i < num_ranges; ++i) { struct fw_rsc_map_range *range = &mapping->range[i]; map = &dev->dma_range_map[i]; range->da = map->dma_start; range->pa = map->cpu_start; range->len = map->size; } dev_dbg(dev, "handle_rsc: Mapping rsc setup"); return RSC_HANDLED; } static const struct rproc_ops ethosu_rproc_ops = { .prepare = ðosu_rproc_prepare, .start = ðosu_rproc_start, .stop = ðosu_rproc_stop, .kick = ðosu_rproc_kick, .handle_rsc = ðosu_rproc_handle_rsc, }; static int ethosu_mailbox_init(struct ethosu_rproc *erproc) { struct device *dev = erproc->dev; struct mbox_client *cl = &erproc->mbox_client; INIT_WORK(&erproc->work, ethosu_mbox_bottom); erproc->wq = create_singlethread_workqueue("ethosu_rproc_wq"); if (!erproc->wq) { dev_err(dev, "Failed to create work queue"); return -EINVAL; } cl->dev = dev; cl->rx_callback = ethosu_mbox_top; cl->tx_prepare = NULL; cl->tx_done = NULL; cl->tx_block = true; cl->knows_txdone = false; cl->tx_tout = 500; erproc->ch_rx = mbox_request_channel_byname(cl, "rx"); if (IS_ERR(erproc->ch_rx)) { dev_err(dev, "Failed to request mbox chan rx"); return PTR_ERR(erproc->ch_rx); } erproc->ch_tx = mbox_request_channel_byname(cl, "tx"); if (IS_ERR(erproc->ch_tx)) { dev_info(dev, "Using same channel for RX and TX"); erproc->ch_tx = erproc->ch_rx; } return 0; } static const struct of_device_id ethosu_rproc_match[] = { { .compatible = "arm,ethosu-rproc" }, { /* sentinel */ }, }; static int ethosu_rproc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; struct ethosu_rproc *erproc; struct rproc *rproc; int ret; /* map the first 'memory-region' for DMA-mapping */ ret = of_reserved_mem_device_init(dev); if (ret) return ret; dma_set_mask_and_coherent(dev, DMA_BIT_MASK(DMA_ADDR_BITS)); rproc = devm_rproc_alloc(dev, np->name, ðosu_rproc_ops, fw_filename_param, sizeof(*erproc)); if (!rproc) return -ENOMEM; platform_set_drvdata(pdev, rproc); /* Configure rproc */ rproc->has_iommu = false; rproc->auto_boot = auto_boot; /* Configure Ethos-U rproc */ erproc = rproc->priv; erproc->dev = dev; /* Get the reset handler for the subsystem */ erproc->rstc = devm_reset_control_get_exclusive_by_index(dev, 0); if (IS_ERR(erproc->rstc)) { dev_err(&pdev->dev, "Failed to get reset controller."); return PTR_ERR(erproc->rstc); } /* Allocate and initialize mailbox client */ ret = ethosu_mailbox_init(erproc); if (ret) return ret; ret = rproc_add(rproc); if (ret) { dev_err(dev, "Failed to add rproc"); goto free_mbox; } return 0; free_mbox: if (erproc->wq) destroy_workqueue(erproc->wq); mbox_free_channel(erproc->ch_rx); if (erproc->ch_tx != erproc->ch_rx) mbox_free_channel(erproc->ch_tx); return ret; } static int ethosu_rproc_remove(struct platform_device *pdev) { struct rproc *rproc = platform_get_drvdata(pdev); struct ethosu_rproc *erproc = rproc->priv; if (erproc->wq) destroy_workqueue(erproc->wq); if (erproc->ch_tx != erproc->ch_rx) mbox_free_channel(erproc->ch_tx); mbox_free_channel(erproc->ch_rx); rproc_del(rproc); return 0; } static struct platform_driver ethosu_rproc_driver = { .probe = ethosu_rproc_probe, .remove = ethosu_rproc_remove, .driver = { .name = "ethosu-rproc", .of_match_table = of_match_ptr(ethosu_rproc_match), }, }; module_platform_driver(ethosu_rproc_driver); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Arm Ltd"); MODULE_DESCRIPTION("Arm Ethos-U NPU RemoteProc Driver"); MODULE_VERSION(ETHOSU_RPROC_DRIVER_VERSION);