| /* |
| * Copyright (c) 2014, Mentor Graphics Corporation |
| * All rights reserved. |
| * Copyright (c) 2015 Xilinx, Inc. All rights reserved. |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| */ |
| |
| #include <metal/alloc.h> |
| #include <metal/log.h> |
| #include <metal/utilities.h> |
| #include <openamp/elf_loader.h> |
| #include <openamp/remoteproc.h> |
| #include <openamp/remoteproc_loader.h> |
| #include <openamp/remoteproc_virtio.h> |
| #include <openamp/rsc_table_parser.h> |
| |
| /****************************************************************************** |
| * static functions |
| *****************************************************************************/ |
| static struct loader_ops * |
| remoteproc_check_fw_format(const void *img_data, size_t img_len) |
| { |
| if (img_len <= 0) |
| return NULL; |
| else if (elf_identify(img_data, img_len) == 0) |
| return &elf_ops; |
| else |
| return NULL; |
| } |
| |
| static struct remoteproc_mem * |
| remoteproc_get_mem(struct remoteproc *rproc, const char *name, |
| metal_phys_addr_t pa, metal_phys_addr_t da, |
| void *va, size_t size) |
| { |
| struct metal_list *node; |
| struct remoteproc_mem *mem; |
| |
| metal_list_for_each(&rproc->mems, node) { |
| mem = metal_container_of(node, struct remoteproc_mem, node); |
| if (name) { |
| if (!strncmp(name, mem->name, sizeof(mem->name))) |
| return mem; |
| } else if (pa != METAL_BAD_PHYS) { |
| metal_phys_addr_t pa_start, pa_end; |
| |
| pa_start = mem->pa; |
| pa_end = pa_start + mem->size; |
| if (pa >= pa_start && (pa + size) <= pa_end) |
| return mem; |
| } else if (da != METAL_BAD_PHYS) { |
| metal_phys_addr_t da_start, da_end; |
| |
| da_start = mem->da; |
| da_end = da_start + mem->size; |
| if (da >= da_start && (da + size) <= da_end) |
| return mem; |
| } else if (va) { |
| if (metal_io_virt_to_offset(mem->io, va) != |
| METAL_BAD_OFFSET) |
| return mem; |
| |
| } else { |
| return NULL; |
| } |
| } |
| return NULL; |
| } |
| |
| static metal_phys_addr_t |
| remoteproc_datopa(struct remoteproc_mem *mem, metal_phys_addr_t da) |
| { |
| metal_phys_addr_t pa; |
| |
| pa = mem->pa + da - mem->da; |
| return pa; |
| } |
| |
| static metal_phys_addr_t |
| remoteproc_patoda(struct remoteproc_mem *mem, metal_phys_addr_t pa) |
| { |
| metal_phys_addr_t da; |
| |
| da = mem->da + pa - mem->pa; |
| return da; |
| } |
| |
| static void *remoteproc_get_rsc_table(struct remoteproc *rproc, |
| void *store, |
| struct image_store_ops *store_ops, |
| size_t offset, |
| size_t len) |
| { |
| int ret; |
| void *rsc_table = NULL; |
| const void *img_data; |
| |
| /* Copy the resource table to local memory, |
| * the caller should be responsible to release the memory |
| */ |
| rsc_table = metal_allocate_memory(len); |
| if (!rsc_table) { |
| return RPROC_ERR_PTR(-RPROC_ENOMEM); |
| } |
| ret = store_ops->load(store, offset, len, &img_data, RPROC_LOAD_ANYADDR, |
| NULL, 1); |
| if (ret < 0 || ret < (int)len || img_data == NULL) { |
| metal_log(METAL_LOG_ERROR, |
| "get rsc failed: 0x%llx, 0x%llx\r\n", offset, len); |
| rsc_table = RPROC_ERR_PTR(-RPROC_EINVAL); |
| goto error; |
| } |
| memcpy(rsc_table, img_data, len); |
| |
| ret = handle_rsc_table(rproc, rsc_table, len, NULL); |
| if (ret < 0) { |
| rsc_table = RPROC_ERR_PTR(ret); |
| goto error; |
| } |
| return rsc_table; |
| |
| error: |
| metal_free_memory(rsc_table); |
| return rsc_table; |
| } |
| |
| int remoteproc_parse_rsc_table(struct remoteproc *rproc, |
| struct resource_table *rsc_table, |
| size_t rsc_size) |
| { |
| struct metal_io_region *io; |
| |
| io = remoteproc_get_io_with_va(rproc, (void *)rsc_table); |
| return handle_rsc_table(rproc, rsc_table, rsc_size, io); |
| } |
| |
| int remoteproc_set_rsc_table(struct remoteproc *rproc, |
| struct resource_table *rsc_table, |
| size_t rsc_size) |
| { |
| int ret; |
| struct metal_io_region *io; |
| |
| io = remoteproc_get_io_with_va(rproc, (void *)rsc_table); |
| if (!io) |
| return -EINVAL; |
| ret = remoteproc_parse_rsc_table(rproc, rsc_table, rsc_size); |
| if (!ret) { |
| rproc->rsc_table = rsc_table; |
| rproc->rsc_len = rsc_size; |
| rproc->rsc_io = io; |
| } |
| return ret; |
| |
| } |
| |
| struct remoteproc *remoteproc_init(struct remoteproc *rproc, |
| struct remoteproc_ops *ops, void *priv) |
| { |
| if (rproc) { |
| memset(rproc, 0, sizeof (*rproc)); |
| rproc->state = RPROC_OFFLINE; |
| metal_mutex_init(&rproc->lock); |
| metal_list_init(&rproc->mems); |
| metal_list_init(&rproc->vdevs); |
| } |
| rproc = ops->init(rproc, ops, priv); |
| return rproc; |
| } |
| |
| int remoteproc_remove(struct remoteproc *rproc) |
| { |
| int ret; |
| |
| if (rproc) { |
| metal_mutex_acquire(&rproc->lock); |
| if (rproc->state == RPROC_OFFLINE) |
| rproc->ops->remove(rproc); |
| else |
| ret = -EBUSY; |
| metal_mutex_release(&rproc->lock); |
| } else { |
| ret = -EINVAL; |
| } |
| return ret; |
| } |
| |
| int remoteproc_config(struct remoteproc *rproc, void *data) |
| { |
| int ret = -RPROC_ENODEV; |
| |
| if (rproc) { |
| metal_mutex_acquire(&rproc->lock); |
| if (rproc->state == RPROC_OFFLINE) { |
| /* configure operation is allowed if the state is |
| * offline or ready. This function can be called |
| * mulitple times before start the remote. |
| */ |
| if (rproc->ops->config) |
| ret = rproc->ops->config(rproc, data); |
| rproc->state = RPROC_READY; |
| } else { |
| ret = -RPROC_EINVAL; |
| } |
| metal_mutex_release(&rproc->lock); |
| } |
| return ret; |
| } |
| |
| int remoteproc_start(struct remoteproc *rproc) |
| { |
| int ret = -RPROC_ENODEV; |
| |
| if (rproc) { |
| metal_mutex_acquire(&rproc->lock); |
| if (rproc->state == RPROC_READY) { |
| ret = rproc->ops->start(rproc); |
| rproc->state = RPROC_RUNNING; |
| } else { |
| ret = -RPROC_EINVAL; |
| } |
| metal_mutex_release(&rproc->lock); |
| } |
| return ret; |
| } |
| |
| int remoteproc_stop(struct remoteproc *rproc) |
| { |
| int ret = -RPROC_ENODEV; |
| |
| if (rproc) { |
| metal_mutex_acquire(&rproc->lock); |
| if (rproc->state != RPROC_STOPPED && |
| rproc->state != RPROC_OFFLINE) { |
| if (rproc->ops->stop) |
| ret = rproc->ops->stop(rproc); |
| rproc->state = RPROC_STOPPED; |
| } else { |
| ret = 0; |
| } |
| metal_mutex_release(&rproc->lock); |
| } |
| return ret; |
| } |
| |
| int remoteproc_shutdown(struct remoteproc *rproc) |
| { |
| int ret = -RPROC_ENODEV; |
| |
| if (rproc) { |
| ret = 0; |
| metal_mutex_acquire(&rproc->lock); |
| if (rproc->state != RPROC_OFFLINE) { |
| if (rproc->state != RPROC_STOPPED) { |
| if (rproc->ops->stop) |
| ret = rproc->ops->stop(rproc); |
| } |
| if (!ret) { |
| if (rproc->ops->shutdown) |
| ret = rproc->ops->shutdown(rproc); |
| if (!ret) { |
| rproc->state = RPROC_OFFLINE; |
| } |
| } |
| } |
| metal_mutex_release(&rproc->lock); |
| } |
| return ret; |
| } |
| |
| struct metal_io_region * |
| remoteproc_get_io_with_name(struct remoteproc *rproc, |
| const char *name) |
| { |
| struct remoteproc_mem *mem; |
| |
| mem = remoteproc_get_mem(rproc, name, |
| METAL_BAD_PHYS, METAL_BAD_PHYS, NULL, 0); |
| if (mem) |
| return mem->io; |
| else |
| return NULL; |
| } |
| |
| struct metal_io_region * |
| remoteproc_get_io_with_pa(struct remoteproc *rproc, |
| metal_phys_addr_t pa) |
| { |
| struct remoteproc_mem *mem; |
| |
| mem = remoteproc_get_mem(rproc, NULL, pa, METAL_BAD_PHYS, NULL, 0); |
| if (mem) |
| return mem->io; |
| else |
| return NULL; |
| } |
| |
| struct metal_io_region * |
| remoteproc_get_io_with_da(struct remoteproc *rproc, |
| metal_phys_addr_t da, |
| unsigned long *offset) |
| { |
| struct remoteproc_mem *mem; |
| |
| mem = remoteproc_get_mem(rproc, NULL, METAL_BAD_PHYS, da, NULL, 0); |
| if (mem) { |
| struct metal_io_region *io; |
| metal_phys_addr_t pa; |
| |
| io = mem->io; |
| pa = remoteproc_datopa(mem, da); |
| *offset = metal_io_phys_to_offset(io, pa); |
| return io; |
| } else { |
| return NULL; |
| } |
| } |
| |
| struct metal_io_region * |
| remoteproc_get_io_with_va(struct remoteproc *rproc, void *va) |
| { |
| struct remoteproc_mem *mem; |
| |
| mem = remoteproc_get_mem(rproc, NULL, METAL_BAD_PHYS, METAL_BAD_PHYS, |
| va, 0); |
| if (mem) |
| return mem->io; |
| else |
| return NULL; |
| } |
| |
| void *remoteproc_mmap(struct remoteproc *rproc, |
| metal_phys_addr_t *pa, metal_phys_addr_t *da, |
| size_t size, unsigned int attribute, |
| struct metal_io_region **io) |
| { |
| void *va = NULL; |
| metal_phys_addr_t lpa, lda; |
| struct remoteproc_mem *mem; |
| |
| if (!rproc) |
| return NULL; |
| else if (!pa && !da) |
| return NULL; |
| if (pa) |
| lpa = *pa; |
| else |
| lpa = METAL_BAD_PHYS; |
| if (da) |
| lda = *da; |
| else |
| lda = METAL_BAD_PHYS; |
| mem = remoteproc_get_mem(rproc, NULL, lpa, lda, NULL, size); |
| if (mem) { |
| if (lpa != METAL_BAD_PHYS) |
| lda = remoteproc_patoda(mem, lpa); |
| else if (lda != METAL_BAD_PHYS) |
| lpa = remoteproc_datopa(mem, lda); |
| if (io) |
| *io = mem->io; |
| va = metal_io_phys_to_virt(mem->io, lpa); |
| } else if (rproc->ops->mmap) { |
| va = rproc->ops->mmap(rproc, &lpa, &lda, size, attribute, io); |
| } |
| |
| if (pa) |
| *pa = lpa; |
| if (da) |
| *da = lda; |
| return va; |
| } |
| |
| int remoteproc_load(struct remoteproc *rproc, const char *path, |
| void *store, struct image_store_ops *store_ops, |
| void **img_info) |
| { |
| int ret; |
| struct loader_ops *loader; |
| const void *img_data; |
| void *limg_info = NULL; |
| size_t offset, noffset; |
| size_t len, nlen; |
| int last_load_state; |
| metal_phys_addr_t da, rsc_da; |
| int rsc_len; |
| size_t rsc_size; |
| void *rsc_table = NULL; |
| struct metal_io_region *io = NULL; |
| |
| if (!rproc) |
| return -RPROC_ENODEV; |
| |
| metal_mutex_acquire(&rproc->lock); |
| metal_log(METAL_LOG_DEBUG, "%s: check remoteproc status\r\n", __func__); |
| /* If remoteproc is not in ready state, cannot load executable */ |
| if (rproc->state != RPROC_READY && rproc->state != RPROC_CONFIGURED) { |
| metal_log(METAL_LOG_ERROR, |
| "load failure: invalid rproc state %d.\r\n", |
| rproc->state); |
| metal_mutex_release(&rproc->lock); |
| return -RPROC_EINVAL; |
| } |
| |
| if (!store_ops) { |
| metal_log(METAL_LOG_ERROR, |
| "load failure: loader ops is not set.\r\n"); |
| metal_mutex_release(&rproc->lock); |
| return -RPROC_EINVAL; |
| } |
| |
| /* Open exectuable to get ready to parse */ |
| metal_log(METAL_LOG_DEBUG, "%s: open exectuable image\r\n", __func__); |
| ret = store_ops->open(store, path, &img_data); |
| if (ret <= 0) { |
| metal_log(METAL_LOG_ERROR, |
| "load failure: failed to open firmware %d.\n", |
| ret); |
| metal_mutex_release(&rproc->lock); |
| return -RPROC_EINVAL; |
| } |
| len = ret; |
| metal_assert(img_data != NULL); |
| |
| /* Check executable format to select a parser */ |
| loader = rproc->loader; |
| if (!loader) { |
| metal_log(METAL_LOG_DEBUG, "%s: check loader\r\n", __func__); |
| loader = remoteproc_check_fw_format(img_data, len); |
| if (!loader) { |
| metal_log(METAL_LOG_ERROR, |
| "load failure: failed to get store ops.\n"); |
| ret = -RPROC_EINVAL; |
| goto error1; |
| } |
| rproc->loader = loader; |
| } |
| |
| /* Load exectuable headers */ |
| metal_log(METAL_LOG_DEBUG, "%s: loading headers\r\n", __func__); |
| offset = 0; |
| last_load_state = RPROC_LOADER_NOT_READY; |
| while(1) { |
| ret = loader->load_header(img_data, offset, len, |
| &limg_info, last_load_state, |
| &noffset, &nlen); |
| last_load_state = (unsigned int)ret; |
| metal_log(METAL_LOG_DEBUG, |
| "%s, load header 0x%lx, 0x%x, next 0x%lx, 0x%x\r\n", |
| __func__, offset, len, noffset, nlen); |
| if (ret < 0) { |
| metal_log(METAL_LOG_ERROR, |
| "load header failed 0x%lx,%d.\r\n", |
| offset, len); |
| |
| goto error2; |
| } else if ((ret & RPROC_LOADER_READY_TO_LOAD) != 0) { |
| if (nlen == 0) |
| break; |
| else if ((noffset > (offset + len)) && |
| (store_ops->features & SUPPORT_SEEK) == 0) { |
| /* Required data is not continued, however |
| * seek is not supported, stop to load |
| * headers such as ELF section headers which |
| * is usually located to the end of image. |
| * Continue to load binary data to target |
| * memory. |
| */ |
| break; |
| } |
| } |
| /* Continue to load headers image data */ |
| img_data = NULL; |
| ret = store_ops->load(store, noffset, nlen, |
| &img_data, |
| RPROC_LOAD_ANYADDR, |
| NULL, 1); |
| if (ret < (int)nlen) { |
| metal_log(METAL_LOG_ERROR, |
| "load image data failed 0x%x,%d\r\n", |
| noffset, nlen); |
| goto error2; |
| } |
| offset = noffset; |
| len = nlen; |
| } |
| ret = elf_locate_rsc_table(limg_info, &rsc_da, &offset, &rsc_size); |
| if (ret == 0 && rsc_size > 0) { |
| /* parse resource table */ |
| rsc_len = (int)rsc_size; |
| rsc_table = remoteproc_get_rsc_table(rproc, store, store_ops, |
| offset, rsc_len); |
| } else { |
| rsc_len = ret; |
| } |
| |
| /* load executable data */ |
| metal_log(METAL_LOG_DEBUG, "%s: load executable data\r\n", __func__); |
| offset = 0; |
| len = 0; |
| ret = -EINVAL; |
| while(1) { |
| unsigned char padding; |
| size_t nmemsize; |
| metal_phys_addr_t pa; |
| |
| da = RPROC_LOAD_ANYADDR; |
| nlen = 0; |
| nmemsize = 0; |
| noffset = 0; |
| ret = loader->load_data(rproc, img_data, offset, len, |
| &limg_info, last_load_state, &da, |
| &noffset, &nlen, &padding, &nmemsize); |
| if (ret < 0) { |
| metal_log(METAL_LOG_ERROR, |
| "load data failed,0x%lx,%d\r\n", |
| noffset, nlen); |
| goto error3; |
| } |
| metal_log(METAL_LOG_DEBUG, |
| "load data: da 0x%lx, offset 0x%lx, len = 0x%lx, memsize = 0x%lx, state 0x%x\r\n", |
| da, noffset, nlen, nmemsize, ret); |
| last_load_state = ret; |
| if (da != RPROC_LOAD_ANYADDR) { |
| /* Data is supposed to be loaded to target memory */ |
| img_data = NULL; |
| /* get the I/O region from remoteproc */ |
| pa = METAL_BAD_PHYS; |
| (void)remoteproc_mmap(rproc, &pa, &da, nmemsize, 0, &io); |
| if (pa == METAL_BAD_PHYS || io == NULL) { |
| metal_log(METAL_LOG_ERROR, |
| "load failed, no mapping for 0x%llx.\r\n", |
| da); |
| ret = -RPROC_EINVAL; |
| goto error3; |
| } |
| if (nlen > 0) { |
| ret = store_ops->load(store, noffset, nlen, |
| &img_data, pa, io, 1); |
| if (ret != (int)nlen) { |
| metal_log(METAL_LOG_ERROR, |
| "load data failed 0x%lx, 0x%lx, 0x%x\r\n", |
| pa, noffset, nlen); |
| ret = -RPROC_EINVAL; |
| goto error3; |
| } |
| } |
| if (nmemsize > nlen) { |
| size_t tmpoffset; |
| |
| tmpoffset = metal_io_phys_to_offset(io, |
| pa + nlen); |
| metal_io_block_set(io, tmpoffset, |
| padding, (nmemsize - nlen)); |
| } |
| } else if (nlen != 0) { |
| ret = store_ops->load(store, noffset, nlen, |
| &img_data, |
| RPROC_LOAD_ANYADDR, |
| NULL, 1); |
| if (ret < (int)nlen) { |
| if ((last_load_state & |
| RPROC_LOADER_POST_DATA_LOAD) != 0) { |
| metal_log(METAL_LOG_WARNING, |
| "not all the headers are loaded\r\n"); |
| break; |
| } |
| metal_log(METAL_LOG_ERROR, |
| "post-load image data failed 0x%x,%d\r\n", |
| noffset, nlen); |
| goto error3; |
| } |
| offset = noffset; |
| len = nlen; |
| } else { |
| /* (last_load_state & RPROC_LOADER_LOAD_COMPLETE) != 0 */ |
| break; |
| } |
| } |
| |
| if (rsc_len < 0) { |
| ret = elf_locate_rsc_table(limg_info, &rsc_da, |
| &offset, &rsc_size); |
| if (ret == 0 && rsc_size > 0) { |
| /* parse resource table */ |
| rsc_len = (int)rsc_size; |
| rsc_table = remoteproc_get_rsc_table(rproc, store, |
| store_ops, |
| offset, |
| rsc_len); |
| } |
| } |
| |
| /* Update resource table */ |
| if (rsc_len && rsc_da != METAL_BAD_PHYS) { |
| void *rsc_table_cp = rsc_table; |
| |
| metal_log(METAL_LOG_DEBUG, |
| "%s, update resource table\r\n", __func__); |
| rsc_table = remoteproc_mmap(rproc, NULL, &rsc_da, |
| rsc_len, 0, &io); |
| if (rsc_table) { |
| size_t rsc_io_offset; |
| |
| /* Update resource table */ |
| rsc_io_offset = metal_io_virt_to_offset(io, rsc_table); |
| ret = metal_io_block_write(io, rsc_io_offset, |
| rsc_table_cp, rsc_len); |
| if (ret != rsc_len) { |
| metal_log(METAL_LOG_WARNING, |
| "load: failed to update rsc\r\n"); |
| } |
| rproc->rsc_table = rsc_table; |
| rproc->rsc_len = rsc_len; |
| } else { |
| metal_log(METAL_LOG_WARNING, |
| "load: not able to update rsc table.\n"); |
| } |
| metal_free_memory(rsc_table_cp); |
| /* So that the rsc_table will not get released */ |
| rsc_table = NULL; |
| } |
| |
| metal_log(METAL_LOG_DEBUG, "%s: successfully load firmware\r\n", |
| __func__); |
| /* get entry point from the firmware */ |
| rproc->bootaddr = loader->get_entry(limg_info); |
| rproc->state = RPROC_READY; |
| |
| metal_mutex_release(&rproc->lock); |
| if (img_info) |
| *img_info = limg_info; |
| else |
| loader->release(limg_info); |
| store_ops->close(store); |
| return 0; |
| |
| error3: |
| if (rsc_table) |
| metal_free_memory(rsc_table); |
| error2: |
| loader->release(limg_info); |
| error1: |
| store_ops->close(store); |
| metal_mutex_release(&rproc->lock); |
| return ret; |
| } |
| |
| int remoteproc_load_noblock(struct remoteproc *rproc, |
| const void *img_data, size_t offset, size_t len, |
| void **img_info, |
| metal_phys_addr_t *pa, struct metal_io_region **io, |
| size_t *noffset, size_t *nlen, |
| size_t *nmlen, unsigned char *padding) |
| { |
| int ret; |
| struct loader_ops *loader; |
| void *limg_info = NULL; |
| int last_load_state; |
| metal_phys_addr_t da, rsc_da; |
| size_t rsc_size; |
| void *rsc_table = NULL, *lrsc_table = NULL; |
| |
| if (!rproc) |
| return -RPROC_ENODEV; |
| |
| metal_assert(pa != NULL); |
| metal_assert(io != NULL); |
| metal_assert(noffset != NULL); |
| metal_assert(nlen != NULL); |
| metal_assert(nmlen != NULL); |
| metal_assert(padding != NULL); |
| |
| metal_mutex_acquire(&rproc->lock); |
| metal_log(METAL_LOG_DEBUG, "%s: check remoteproc status\r\n", __func__); |
| /* If remoteproc is not in ready state, cannot load executable */ |
| if (rproc->state != RPROC_READY) { |
| metal_log(METAL_LOG_ERROR, |
| "load failure: invalid rproc state %d.\r\n", |
| rproc->state); |
| metal_mutex_release(&rproc->lock); |
| return -RPROC_EINVAL; |
| } |
| |
| /* Check executable format to select a parser */ |
| loader = rproc->loader; |
| if (!loader) { |
| metal_log(METAL_LOG_DEBUG, "%s: check loader\r\n", __func__); |
| if (img_data == NULL || offset != 0 || len == 0) { |
| metal_log(METAL_LOG_ERROR, |
| "load failure, invalid inputs, not able to identify image.\r\n"); |
| metal_mutex_release(&rproc->lock); |
| return -RPROC_EINVAL; |
| } |
| loader = remoteproc_check_fw_format(img_data, len); |
| if (!loader) { |
| metal_log(METAL_LOG_ERROR, |
| "load failure: failed to identify image.\n"); |
| ret = -RPROC_EINVAL; |
| metal_mutex_release(&rproc->lock); |
| return -RPROC_EINVAL; |
| } |
| rproc->loader = loader; |
| } |
| if (img_info == NULL || *img_info == NULL ) { |
| last_load_state = 0; |
| } else { |
| limg_info = *img_info; |
| last_load_state = loader->get_load_state(limg_info); |
| if (last_load_state < 0) { |
| metal_log(METAL_LOG_ERROR, |
| "load failure, not able get load state.\r\n"); |
| metal_mutex_release(&rproc->lock); |
| return -RPROC_EINVAL; |
| } |
| } |
| da = RPROC_LOAD_ANYADDR; |
| *nlen = 0; |
| if ((last_load_state & RPROC_LOADER_READY_TO_LOAD) == 0 && |
| (last_load_state & RPROC_LOADER_LOAD_COMPLETE) == 0) { |
| /* Get the mandatory executable headers */ |
| ret = loader->load_header(img_data, offset, len, |
| &limg_info, last_load_state, |
| noffset, nlen); |
| last_load_state = (unsigned int)ret; |
| metal_log(METAL_LOG_DEBUG, |
| "%s, load header 0x%lx, 0x%x, next 0x%lx, 0x%x\r\n", |
| __func__, offset, len, *noffset, *nlen); |
| if (ret < 0) { |
| metal_log(METAL_LOG_ERROR, |
| "load header failed 0x%lx,%d.\r\n", |
| offset, len); |
| |
| goto error1; |
| } |
| last_load_state = loader->get_load_state(limg_info); |
| if (*nlen != 0 && |
| (last_load_state & RPROC_LOADER_READY_TO_LOAD) == 0) |
| goto out; |
| } |
| if ((last_load_state & RPROC_LOADER_READY_TO_LOAD) != 0 || |
| (last_load_state & RPROC_LOADER_POST_DATA_LOAD) != 0) { |
| /* Enough information to know which target memory for |
| * which data. |
| */ |
| ret = loader->load_data(rproc, img_data, offset, len, |
| &limg_info, last_load_state, &da, |
| noffset, nlen, padding, nmlen); |
| metal_log(METAL_LOG_DEBUG, |
| "%s, load data 0x%lx, 0x%x, next 0x%lx, 0x%x\r\n", |
| __func__, offset, len, *noffset, *nlen); |
| if (ret < 0) { |
| metal_log(METAL_LOG_ERROR, |
| "load data failed,0x%lx,%d\r\n", |
| offset, len); |
| goto error1; |
| } |
| if (da != RPROC_LOAD_ANYADDR) { |
| /* get the I/O region from remoteproc */ |
| *pa = METAL_BAD_PHYS; |
| (void)remoteproc_mmap(rproc, pa, &da, *nmlen, 0, io); |
| if (*pa == METAL_BAD_PHYS || io == NULL) { |
| metal_log(METAL_LOG_ERROR, |
| "load failed, no mapping for 0x%llx.\r\n", |
| da); |
| ret = -RPROC_EINVAL; |
| goto error1; |
| } |
| } |
| if (*nlen != 0) |
| goto out; |
| else |
| last_load_state = loader->get_load_state(limg_info); |
| } |
| if ((last_load_state & RPROC_LOADER_LOAD_COMPLETE) != 0) { |
| /* Get resource table */ |
| size_t rsc_offset; |
| size_t rsc_io_offset; |
| |
| ret = elf_locate_rsc_table(limg_info, &rsc_da, |
| &rsc_offset, &rsc_size); |
| if (ret == 0 && rsc_size > 0) { |
| lrsc_table = metal_allocate_memory(rsc_size); |
| if (lrsc_table == NULL) { |
| ret = -RPROC_ENOMEM; |
| goto error1; |
| } |
| rsc_table = remoteproc_mmap(rproc, NULL, &rsc_da, |
| rsc_size, 0, io); |
| if (*io == NULL) { |
| metal_log(METAL_LOG_ERROR, |
| "load failed: failed to mmap rsc\r\n"); |
| metal_free_memory(lrsc_table); |
| goto error1; |
| } |
| rsc_io_offset = metal_io_virt_to_offset(*io, rsc_table); |
| ret = metal_io_block_read(*io, rsc_io_offset, |
| lrsc_table, (int)rsc_size); |
| if (ret != (int)rsc_size) { |
| metal_log(METAL_LOG_ERROR, |
| "load failed: failed to get rsc\r\n"); |
| metal_free_memory(lrsc_table); |
| goto error1; |
| } |
| /* parse resource table */ |
| ret = remoteproc_parse_rsc_table(rproc, lrsc_table, |
| rsc_size); |
| if (ret == (int)rsc_size) { |
| metal_log(METAL_LOG_ERROR, |
| "load failed: failed to parse rsc\r\n"); |
| metal_free_memory(lrsc_table); |
| goto error1; |
| } |
| /* Update resource table */ |
| ret = metal_io_block_write(*io, rsc_io_offset, |
| lrsc_table, (int)rsc_size); |
| if (ret != (int)rsc_size) { |
| metal_log(METAL_LOG_WARNING, |
| "load exectuable, failed to update rsc\r\n"); |
| } |
| rproc->rsc_table = rsc_table; |
| rproc->rsc_len = (int)rsc_size; |
| metal_free_memory(lrsc_table); |
| } |
| } |
| out: |
| if (img_info != NULL) |
| *img_info = limg_info; |
| else |
| loader->release(limg_info); |
| metal_mutex_release(&rproc->lock); |
| return 0; |
| |
| error1: |
| loader->release(limg_info); |
| metal_mutex_release(&rproc->lock); |
| return ret; |
| } |
| |
| unsigned int remoteproc_allocate_id(struct remoteproc *rproc, |
| unsigned int start, |
| unsigned int end) |
| { |
| unsigned int notifyid; |
| |
| if (start == RSC_NOTIFY_ID_ANY) |
| start = 0; |
| if (end == RSC_NOTIFY_ID_ANY) |
| end = METAL_BITS_PER_ULONG; |
| notifyid = metal_bitmap_next_set_bit(&rproc->bitmap, |
| start, end); |
| if (notifyid != end) |
| metal_bitmap_set_bit(&rproc->bitmap, notifyid); |
| return notifyid; |
| } |
| |
| static int remoteproc_virtio_notify(void *priv, uint32_t id) |
| { |
| struct remoteproc *rproc = priv; |
| |
| return rproc->ops->notify(rproc, id); |
| } |
| |
| struct virtio_device * |
| remoteproc_create_virtio(struct remoteproc *rproc, |
| int vdev_id, unsigned int role, |
| void (*rst_cb)(struct virtio_device *vdev)) |
| { |
| char *rsc_table; |
| struct fw_rsc_vdev *vdev_rsc; |
| struct metal_io_region *vdev_rsc_io; |
| struct virtio_device *vdev; |
| struct remoteproc_virtio *rpvdev; |
| size_t vdev_rsc_offset; |
| unsigned int notifyid; |
| unsigned int num_vrings, i; |
| struct metal_list *node; |
| |
| metal_assert(rproc); |
| metal_mutex_acquire(&rproc->lock); |
| rsc_table = rproc->rsc_table; |
| vdev_rsc_io = rproc->rsc_io; |
| vdev_rsc_offset = find_rsc(rsc_table, RSC_VDEV, vdev_id); |
| if (!vdev_rsc_offset) { |
| metal_mutex_release(&rproc->lock); |
| return NULL; |
| } |
| vdev_rsc = (struct fw_rsc_vdev *)(rsc_table + vdev_rsc_offset); |
| notifyid = vdev_rsc->notifyid; |
| /* Check if the virtio device is already created */ |
| metal_list_for_each(&rproc->vdevs, node) { |
| rpvdev = metal_container_of(node, struct remoteproc_virtio, |
| node); |
| if (rpvdev->vdev.index == notifyid) |
| return &rpvdev->vdev; |
| } |
| vdev = rproc_virtio_create_vdev(role, notifyid, |
| vdev_rsc, vdev_rsc_io, rproc, |
| remoteproc_virtio_notify, |
| rst_cb); |
| rpvdev = metal_container_of(vdev, struct remoteproc_virtio, vdev); |
| metal_list_add_tail(&rproc->vdevs, &rpvdev->node); |
| num_vrings = vdev_rsc->num_of_vrings; |
| /* set the notification id for vrings */ |
| for (i = 0; i < num_vrings; i++) { |
| struct fw_rsc_vdev_vring *vring_rsc; |
| metal_phys_addr_t da; |
| unsigned int num_descs, align; |
| struct metal_io_region *io; |
| void *va; |
| size_t size; |
| int ret; |
| |
| vring_rsc = &vdev_rsc->vring[i]; |
| notifyid = vring_rsc->notifyid; |
| da = vring_rsc->da; |
| num_descs = vring_rsc->num; |
| align = vring_rsc->align; |
| size = vring_size(num_descs, align); |
| va = remoteproc_mmap(rproc, NULL, &da, size, 0, &io); |
| if (!va) |
| goto err1; |
| ret = rproc_virtio_init_vring(vdev, i, notifyid, |
| va, io, num_descs, align); |
| if (ret) |
| goto err1; |
| } |
| metal_mutex_release(&rproc->lock); |
| return vdev; |
| |
| err1: |
| remoteproc_remove_virtio(rproc, vdev); |
| metal_mutex_release(&rproc->lock); |
| return NULL; |
| } |
| |
| void remoteproc_remove_virtio(struct remoteproc *rproc, |
| struct virtio_device *vdev) |
| { |
| struct remoteproc_virtio *rpvdev; |
| |
| (void)rproc; |
| metal_assert(vdev); |
| rpvdev = metal_container_of(vdev, struct remoteproc_virtio, vdev); |
| metal_list_del(&rpvdev->node); |
| rproc_virtio_remove_vdev(&rpvdev->vdev); |
| } |
| |
| int remoteproc_get_notification(struct remoteproc *rproc, uint32_t notifyid) |
| { |
| struct remoteproc_virtio *rpvdev; |
| struct metal_list *node; |
| int ret; |
| |
| metal_list_for_each(&rproc->vdevs, node) { |
| rpvdev = metal_container_of(node, struct remoteproc_virtio, |
| node); |
| ret = rproc_virtio_notified(&rpvdev->vdev, notifyid); |
| if (ret) |
| return ret; |
| } |
| return 0; |
| } |