diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/remoteproc/remoteproc_core.c | 306 |
1 files changed, 209 insertions, 97 deletions
diff --git a/drivers/remoteproc/remoteproc_core.c b/drivers/remoteproc/remoteproc_core.c index 8990c51c16f0..10348451c6c9 100644 --- a/drivers/remoteproc/remoteproc_core.c +++ b/drivers/remoteproc/remoteproc_core.c @@ -63,9 +63,8 @@ static void klist_rproc_put(struct klist_node *n); static DEFINE_KLIST(rprocs, klist_rproc_get, klist_rproc_put); typedef int (*rproc_handle_resources_t)(struct rproc *rproc, - struct fw_resource *rsc, int len); -typedef int (*rproc_handle_resource_t)(struct rproc *rproc, - struct fw_resource *rsc); + struct resource_table *table, int len); +typedef int (*rproc_handle_resource_t)(struct rproc *rproc, void *, int avail); /* * This is the IOMMU fault handler we register with the IOMMU API @@ -281,9 +280,10 @@ rproc_load_segments(struct rproc *rproc, const u8 *elf_data, size_t len) } /** - * rproc_handle_virtio_hdr() - handle a virtio header resource + * rproc_handle_early_vdev() - early handle a virtio header resource * @rproc: the remote processor * @rsc: the resource descriptor + * @avail: size of available data (for sanity checking the image) * * The existence of this virtio hdr resource entry means that the firmware * of this @rproc supports this virtio device. @@ -291,37 +291,32 @@ rproc_load_segments(struct rproc *rproc, const u8 *elf_data, size_t len) * Currently we support only a single virtio device of type VIRTIO_ID_RPMSG, * but the plan is to remove this limitation and support any number * of virtio devices (and of any type). We'll also add support for dynamically - * adding (and removing) virtio devices over the rpmsg bus, but small + * adding (and removing) virtio devices over the rpmsg bus, but simple * firmwares that doesn't want to get involved with rpmsg will be able - * to simple use the resource table for this. - * - * At this point this virtio header entry is rather simple: it just - * announces the virtio device id and the supported virtio device features. - * The plan though is to extend this to include the vring information and - * the virtio config space, too (but first, some resource table overhaul - * is needed: move from fixed-sized to variable-length TLV entries). - * - * For now, the 'flags' member of the resource entry contains the virtio - * device id, the 'da' member contains the device features, and 'pa' is - * where we need to store the guest features once negotiation completes. - * As usual, the 'id' member of this resource contains the index of this - * resource type (i.e. is this the first virtio hdr entry, the 2nd, ...). + * to simply use the resource table for this. * * Returns 0 on success, or an appropriate error code otherwise */ -static int rproc_handle_virtio_hdr(struct rproc *rproc, struct fw_resource *rsc) +static int rproc_handle_early_vdev(struct rproc *rproc, struct fw_rsc_vdev *rsc, + int avail) { struct rproc_vdev *rvdev; + /* make sure resource isn't truncated */ + if (sizeof(*rsc) > avail) { + dev_err(rproc->dev, "vdev rsc is truncated\n"); + return -EINVAL; + } + /* we only support VIRTIO_ID_RPMSG devices for now */ - if (rsc->flags != VIRTIO_ID_RPMSG) { - dev_warn(rproc->dev, "unsupported vdev: %d\n", rsc->flags); + if (rsc->id != VIRTIO_ID_RPMSG) { + dev_warn(rproc->dev, "unsupported vdev: %d\n", rsc->id); return -EINVAL; } /* we only support a single vdev per rproc for now */ - if (rsc->id || rproc->rvdev) { - dev_warn(rproc->dev, "redundant vdev entry: %s\n", rsc->name); + if (rproc->rvdev) { + dev_warn(rproc->dev, "redundant vdev entry\n"); return -EINVAL; } @@ -330,7 +325,7 @@ static int rproc_handle_virtio_hdr(struct rproc *rproc, struct fw_resource *rsc) return -ENOMEM; /* remember the device features */ - rvdev->dfeatures = rsc->da; + rvdev->dfeatures = rsc->dfeatures; rproc->rvdev = rvdev; rvdev->rproc = rproc; @@ -339,9 +334,10 @@ static int rproc_handle_virtio_hdr(struct rproc *rproc, struct fw_resource *rsc) } /** - * rproc_handle_vring() - handle a vring fw resource + * rproc_handle_vdev() - handle a vdev fw resource * @rproc: the remote processor * @rsc: the vring resource descriptor + * @avail: size of available data (for sanity checking the image) * * This resource entry requires allocation of non-cacheable memory * for a virtio vring. Currently we only support two vrings per remote @@ -360,57 +356,82 @@ static int rproc_handle_virtio_hdr(struct rproc *rproc, struct fw_resource *rsc) * * Returns 0 on success, or an appropriate error code otherwise */ -static int rproc_handle_vring(struct rproc *rproc, struct fw_resource *rsc) +static int rproc_handle_vdev(struct rproc *rproc, struct fw_rsc_vdev *rsc, + int avail) { struct device *dev = rproc->dev; struct rproc_vdev *rvdev = rproc->rvdev; - dma_addr_t dma; - int size, id = rsc->id; - void *va; + int i; - /* no vdev is in place ? */ - if (!rvdev) { - dev_err(dev, "vring requested without a virtio dev entry\n"); + /* make sure resource isn't truncated */ + if (sizeof(*rsc) + rsc->num_of_vrings * sizeof(struct fw_rsc_vdev_vring) + + rsc->config_len > avail) { + dev_err(rproc->dev, "vdev rsc is truncated\n"); return -EINVAL; } - /* the firmware must provide the expected queue size */ - if (!rsc->len) { - dev_err(dev, "missing expected queue size\n"); + /* make sure reserved bytes are zeroes */ + if (rsc->reserved[0] || rsc->reserved[1]) { + dev_err(dev, "vdev rsc has non zero reserved bytes\n"); return -EINVAL; } - /* we currently support two vrings per rproc (for rx and tx) */ - if (id >= ARRAY_SIZE(rvdev->vring)) { - dev_err(dev, "%s: invalid vring id %d\n", rsc->name, id); + dev_dbg(dev, "vdev rsc: id %d, dfeatures %x, cfg len %d, %d vrings\n", + rsc->id, rsc->dfeatures, rsc->config_len, rsc->num_of_vrings); + + /* no vdev is in place ? */ + if (!rvdev) { + dev_err(dev, "vring requested without a virtio dev entry\n"); return -EINVAL; } - /* have we already allocated this vring id ? */ - if (rvdev->vring[id].len) { - dev_err(dev, "%s: duplicated id %d\n", rsc->name, id); + /* we currently support two vrings per rproc (for rx and tx) */ + if (rsc->num_of_vrings != ARRAY_SIZE(rvdev->vring)) { + dev_err(dev, "too many vrings: %d\n", rsc->num_of_vrings); return -EINVAL; } - /* actual size of vring (in bytes) */ - size = PAGE_ALIGN(vring_size(rsc->len, AMP_VRING_ALIGN)); + /* initialize the vrings */ + for (i = 0; i < rsc->num_of_vrings; i++) { + struct fw_rsc_vdev_vring *vring = &rsc->vring[i]; + dma_addr_t dma; + int size; + void *va; + + /* make sure reserved bytes are zeroes */ + if (vring->reserved) { + dev_err(dev, "vring rsc has non zero reserved bytes\n"); + return -EINVAL; + } - /* - * Allocate non-cacheable memory for the vring. In the future - * this call will also configure the IOMMU for us - */ - va = dma_alloc_coherent(dev, size, &dma, GFP_KERNEL); - if (!va) { - dev_err(dev, "dma_alloc_coherent failed\n"); - return -ENOMEM; - } + /* the firmware must provide the expected queue size */ + if (!vring->num) { + dev_err(dev, "missing expected queue size\n"); + /* potential cleanups are taken care of later on */ + return -EINVAL; + } - dev_dbg(dev, "vring%d: va %p dma %x qsz %d ring size %x\n", id, va, - dma, rsc->len, size); + /* actual size of vring (in bytes) */ + size = PAGE_ALIGN(vring_size(vring->num, AMP_VRING_ALIGN)); - rvdev->vring[id].len = rsc->len; - rvdev->vring[id].va = va; - rvdev->vring[id].dma = dma; + /* + * Allocate non-cacheable memory for the vring. In the future + * this call will also configure the IOMMU for us + */ + va = dma_alloc_coherent(dev, size, &dma, GFP_KERNEL); + if (!va) { + dev_err(dev, "dma_alloc_coherent failed\n"); + /* potential cleanups are taken care of later on */ + return -EINVAL; + } + + dev_dbg(dev, "vring%d: va %p dma %x qsz %d ring size %x\n", i, + va, dma, vring->num, size); + + rvdev->vring[i].len = vring->num; + rvdev->vring[i].va = va; + rvdev->vring[i].dma = dma; + } return 0; } @@ -419,6 +440,7 @@ static int rproc_handle_vring(struct rproc *rproc, struct fw_resource *rsc) * rproc_handle_trace() - handle a shared trace buffer resource * @rproc: the remote processor * @rsc: the trace resource descriptor + * @avail: size of available data (for sanity checking the image) * * In case the remote processor dumps trace logs into memory, * export it via debugfs. @@ -430,13 +452,25 @@ static int rproc_handle_vring(struct rproc *rproc, struct fw_resource *rsc) * * Returns 0 on success, or an appropriate error code otherwise */ -static int rproc_handle_trace(struct rproc *rproc, struct fw_resource *rsc) +static int rproc_handle_trace(struct rproc *rproc, struct fw_rsc_trace *rsc, + int avail) { struct rproc_mem_entry *trace; struct device *dev = rproc->dev; void *ptr; char name[15]; + if (sizeof(*rsc) > avail) { + dev_err(rproc->dev, "trace rsc is truncated\n"); + return -EINVAL; + } + + /* make sure reserved bytes are zeroes */ + if (rsc->reserved) { + dev_err(dev, "trace rsc has non zero reserved bytes\n"); + return -EINVAL; + } + /* what's the kernel address of this resource ? */ ptr = rproc_da_to_va(rproc, rsc->da, rsc->len); if (!ptr) { @@ -469,7 +503,7 @@ static int rproc_handle_trace(struct rproc *rproc, struct fw_resource *rsc) rproc->num_traces++; - dev_dbg(dev, "%s added: va %p, da 0x%llx, len 0x%x\n", name, ptr, + dev_dbg(dev, "%s added: va %p, da 0x%x, len 0x%x\n", name, ptr, rsc->da, rsc->len); return 0; @@ -479,6 +513,7 @@ static int rproc_handle_trace(struct rproc *rproc, struct fw_resource *rsc) * rproc_handle_devmem() - handle devmem resource entry * @rproc: remote processor handle * @rsc: the devmem resource entry + * @avail: size of available data (for sanity checking the image) * * Remote processors commonly need to access certain on-chip peripherals. * @@ -499,7 +534,8 @@ static int rproc_handle_trace(struct rproc *rproc, struct fw_resource *rsc) * and not allow firmwares to request access to physical addresses that * are outside those ranges. */ -static int rproc_handle_devmem(struct rproc *rproc, struct fw_resource *rsc) +static int rproc_handle_devmem(struct rproc *rproc, struct fw_rsc_devmem *rsc, + int avail) { struct rproc_mem_entry *mapping; int ret; @@ -508,6 +544,17 @@ static int rproc_handle_devmem(struct rproc *rproc, struct fw_resource *rsc) if (!rproc->domain) return -EINVAL; + if (sizeof(*rsc) > avail) { + dev_err(rproc->dev, "devmem rsc is truncated\n"); + return -EINVAL; + } + + /* make sure reserved bytes are zeroes */ + if (rsc->reserved) { + dev_err(rproc->dev, "devmem rsc has non zero reserved bytes\n"); + return -EINVAL; + } + mapping = kzalloc(sizeof(*mapping), GFP_KERNEL); if (!mapping) { dev_err(rproc->dev, "kzalloc mapping failed\n"); @@ -531,7 +578,7 @@ static int rproc_handle_devmem(struct rproc *rproc, struct fw_resource *rsc) mapping->len = rsc->len; list_add_tail(&mapping->node, &rproc->mappings); - dev_dbg(rproc->dev, "mapped devmem pa 0x%llx, da 0x%llx, len 0x%x\n", + dev_dbg(rproc->dev, "mapped devmem pa 0x%x, da 0x%x, len 0x%x\n", rsc->pa, rsc->da, rsc->len); return 0; @@ -545,6 +592,7 @@ out: * rproc_handle_carveout() - handle phys contig memory allocation requests * @rproc: rproc handle * @rsc: the resource entry + * @avail: size of available data (for image validation) * * This function will handle firmware requests for allocation of physically * contiguous memory regions. @@ -558,7 +606,8 @@ out: * needed to map it (in case @rproc is using an IOMMU). Reducing the TLB * pressure is important; it may have a substantial impact on performance. */ -static int rproc_handle_carveout(struct rproc *rproc, struct fw_resource *rsc) +static int rproc_handle_carveout(struct rproc *rproc, + struct fw_rsc_carveout *rsc, int avail) { struct rproc_mem_entry *carveout, *mapping; struct device *dev = rproc->dev; @@ -566,6 +615,20 @@ static int rproc_handle_carveout(struct rproc *rproc, struct fw_resource *rsc) void *va; int ret; + if (sizeof(*rsc) > avail) { + dev_err(rproc->dev, "carveout rsc is truncated\n"); + return -EINVAL; + } + + /* make sure reserved bytes are zeroes */ + if (rsc->reserved) { + dev_err(dev, "carveout rsc has non zero reserved bytes\n"); + return -EINVAL; + } + + 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"); @@ -624,7 +687,7 @@ static int rproc_handle_carveout(struct rproc *rproc, struct fw_resource *rsc) mapping->len = rsc->len; list_add_tail(&mapping->node, &rproc->mappings); - dev_dbg(dev, "carveout mapped 0x%llx to 0x%x\n", rsc->da, dma); + dev_dbg(dev, "carveout mapped 0x%x to 0x%x\n", rsc->da, dma); /* * Some remote processors might need to know the pa @@ -665,36 +728,44 @@ free_mapping: * enum fw_resource_type. */ static rproc_handle_resource_t rproc_handle_rsc[] = { - [RSC_CARVEOUT] = rproc_handle_carveout, - [RSC_DEVMEM] = rproc_handle_devmem, - [RSC_TRACE] = rproc_handle_trace, - [RSC_VRING] = rproc_handle_vring, - [RSC_VIRTIO_DEV] = NULL, /* handled early upon registration */ + [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_handle_vdev, }; /* handle firmware resource entries before booting the remote processor */ static int -rproc_handle_boot_rsc(struct rproc *rproc, struct fw_resource *rsc, int len) +rproc_handle_boot_rsc(struct rproc *rproc, struct resource_table *table, int len) { struct device *dev = rproc->dev; rproc_handle_resource_t handler; - int ret = 0; + int ret = 0, i; + + for (i = 0; i < table->num; i++) { + int offset = table->offset[i]; + struct fw_rsc_hdr *hdr = (void *)table + offset; + int avail = len - offset - sizeof(*hdr); + void *rsc = (void *)hdr + sizeof(*hdr); + + /* make sure table isn't truncated */ + if (avail < 0) { + dev_err(dev, "rsc table is truncated\n"); + return -EINVAL; + } - for (; len >= sizeof(*rsc); rsc++, len -= sizeof(*rsc)) { - dev_dbg(dev, "rsc: type %d, da 0x%llx, pa 0x%llx, len 0x%x, " - "id %d, name %s, flags %x\n", rsc->type, rsc->da, - rsc->pa, rsc->len, rsc->id, rsc->name, rsc->flags); + dev_dbg(dev, "rsc: type %d\n", hdr->type); - if (rsc->type >= RSC_LAST) { - dev_warn(dev, "unsupported resource %d\n", rsc->type); + if (hdr->type >= RSC_LAST) { + dev_warn(dev, "unsupported resource %d\n", hdr->type); continue; } - handler = rproc_handle_rsc[rsc->type]; + handler = rproc_handle_rsc[hdr->type]; if (!handler) continue; - ret = handler(rproc, rsc); + ret = handler(rproc, rsc, avail); if (ret) break; } @@ -704,18 +775,31 @@ rproc_handle_boot_rsc(struct rproc *rproc, struct fw_resource *rsc, int len) /* handle firmware resource entries while registering the remote processor */ static int -rproc_handle_virtio_rsc(struct rproc *rproc, struct fw_resource *rsc, int len) +rproc_handle_virtio_rsc(struct rproc *rproc, struct resource_table *table, int len) { struct device *dev = rproc->dev; - int ret = -ENODEV; + int ret = 0, i; + + for (i = 0; i < table->num; i++) { + int offset = table->offset[i]; + struct fw_rsc_hdr *hdr = (void *)table + offset; + int avail = len - offset - sizeof(*hdr); - for (; len >= sizeof(*rsc); rsc++, len -= sizeof(*rsc)) - if (rsc->type == RSC_VIRTIO_DEV) { - dev_dbg(dev, "found vdev %d/%s features %llx\n", - rsc->flags, rsc->name, rsc->da); - ret = rproc_handle_virtio_hdr(rproc, rsc); + /* make sure table isn't truncated */ + if (avail < 0) { + dev_err(dev, "rsc table is truncated\n"); + return -EINVAL; + } + + dev_dbg(dev, "%s: rsc type %d\n", __func__, hdr->type); + + if (hdr->type == RSC_VDEV) { + struct fw_rsc_vdev *vrsc = + (struct fw_rsc_vdev *)hdr->data; + ret = rproc_handle_early_vdev(rproc, vrsc, avail); break; } + } return ret; } @@ -744,7 +828,9 @@ static int rproc_handle_resources(struct rproc *rproc, const u8 *elf_data, struct elf32_hdr *ehdr; struct elf32_shdr *shdr; const char *name_table; + struct device *dev = rproc->dev; int i, ret = -EINVAL; + struct resource_table *table; ehdr = (struct elf32_hdr *)elf_data; shdr = (struct elf32_shdr *)(elf_data + ehdr->e_shoff); @@ -752,21 +838,47 @@ static int rproc_handle_resources(struct rproc *rproc, const u8 *elf_data, /* look for the resource table and handle it */ for (i = 0; i < ehdr->e_shnum; i++, shdr++) { - if (!strcmp(name_table + shdr->sh_name, ".resource_table")) { - struct fw_resource *table = (struct fw_resource *) - (elf_data + shdr->sh_offset); + int size = shdr->sh_size; + int offset = shdr->sh_offset; - if (shdr->sh_offset + shdr->sh_size > len) { - dev_err(rproc->dev, - "truncated fw: need 0x%x avail 0x%x\n", - shdr->sh_offset + shdr->sh_size, len); - ret = -EINVAL; - } + if (strcmp(name_table + shdr->sh_name, ".resource_table")) + continue; - ret = handler(rproc, table, shdr->sh_size); + table = (struct resource_table *)(elf_data + offset); - break; + /* make sure we have the entire table */ + if (offset + size > len) { + dev_err(dev, "resource table truncated\n"); + return -EINVAL; + } + + /* make sure table has at least the header */ + if (sizeof(struct resource_table) > size) { + dev_err(dev, "header-less resource table\n"); + return -EINVAL; } + + /* we don't support any version beyond the first */ + if (table->ver != 1) { + dev_err(dev, "unsupported fw ver: %d\n", table->ver); + return -EINVAL; + } + + /* make sure reserved bytes are zeroes */ + if (table->reserved[0] || table->reserved[1]) { + dev_err(dev, "non zero reserved bytes\n"); + return -EINVAL; + } + + /* make sure the offsets array isn't truncated */ + if (table->num * sizeof(table->offset[0]) + + sizeof(struct resource_table) > size) { + dev_err(dev, "resource table incomplete\n"); + return -EINVAL; + } + + ret = handler(rproc, table, shdr->sh_size); + break; } return ret; @@ -980,7 +1092,7 @@ static void rproc_fw_config_virtio(const struct firmware *fw, void *context) if (rproc_fw_sanity_check(rproc, fw) < 0) goto out; - /* does the fw supports any virtio devices ? */ + /* does the fw support any virtio devices ? */ ret = rproc_handle_resources(rproc, fw->data, fw->size, rproc_handle_virtio_rsc); if (ret) { |