| /* |
| * Copyright 2019 Red Hat Inc. |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a |
| * copy of this software and associated documentation files (the "Software"), |
| * to deal in the Software without restriction, including without limitation |
| * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
| * and/or sell copies of the Software, and to permit persons to whom the |
| * Software is furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be included in |
| * all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR |
| * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, |
| * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
| * OTHER DEALINGS IN THE SOFTWARE. |
| */ |
| #include "priv.h" |
| #include <core/falcon.h> |
| #include <core/firmware.h> |
| #include <nvfw/fw.h> |
| #include <nvfw/ls.h> |
| |
| void |
| nvkm_acr_lsfw_del(struct nvkm_acr_lsfw *lsfw) |
| { |
| nvkm_blob_dtor(&lsfw->img); |
| nvkm_firmware_put(lsfw->sig); |
| list_del(&lsfw->head); |
| kfree(lsfw); |
| } |
| |
| void |
| nvkm_acr_lsfw_del_all(struct nvkm_acr *acr) |
| { |
| struct nvkm_acr_lsfw *lsfw, *lsft; |
| list_for_each_entry_safe(lsfw, lsft, &acr->lsfw, head) { |
| nvkm_acr_lsfw_del(lsfw); |
| } |
| } |
| |
| static struct nvkm_acr_lsfw * |
| nvkm_acr_lsfw_get(struct nvkm_acr *acr, enum nvkm_acr_lsf_id id) |
| { |
| struct nvkm_acr_lsfw *lsfw; |
| list_for_each_entry(lsfw, &acr->lsfw, head) { |
| if (lsfw->id == id) |
| return lsfw; |
| } |
| return NULL; |
| } |
| |
| struct nvkm_acr_lsfw * |
| nvkm_acr_lsfw_add(const struct nvkm_acr_lsf_func *func, struct nvkm_acr *acr, |
| struct nvkm_falcon *falcon, enum nvkm_acr_lsf_id id) |
| { |
| struct nvkm_acr_lsfw *lsfw; |
| |
| if (!acr || list_empty(&acr->hsfw)) |
| return ERR_PTR(-ENOSYS); |
| |
| lsfw = nvkm_acr_lsfw_get(acr, id); |
| if (lsfw && lsfw->func) { |
| nvkm_error(&acr->subdev, "LSFW %d redefined\n", id); |
| return ERR_PTR(-EEXIST); |
| } |
| |
| if (!lsfw) { |
| if (!(lsfw = kzalloc(sizeof(*lsfw), GFP_KERNEL))) |
| return ERR_PTR(-ENOMEM); |
| |
| lsfw->id = id; |
| list_add_tail(&lsfw->head, &acr->lsfw); |
| } |
| |
| lsfw->func = func; |
| lsfw->falcon = falcon; |
| return lsfw; |
| } |
| |
| static struct nvkm_acr_lsfw * |
| nvkm_acr_lsfw_load_sig_image_desc_(struct nvkm_subdev *subdev, |
| struct nvkm_falcon *falcon, |
| enum nvkm_acr_lsf_id id, |
| const char *path, int ver, |
| const struct nvkm_acr_lsf_func *func, |
| const struct firmware **pdesc) |
| { |
| struct nvkm_acr *acr = subdev->device->acr; |
| struct nvkm_acr_lsfw *lsfw; |
| int ret; |
| |
| if (IS_ERR((lsfw = nvkm_acr_lsfw_add(func, acr, falcon, id)))) |
| return lsfw; |
| |
| ret = nvkm_firmware_load_name(subdev, path, "sig", ver, &lsfw->sig); |
| if (ret) |
| goto done; |
| |
| ret = nvkm_firmware_load_blob(subdev, path, "image", ver, &lsfw->img); |
| if (ret) |
| goto done; |
| |
| ret = nvkm_firmware_load_name(subdev, path, "desc", ver, pdesc); |
| done: |
| if (ret) { |
| nvkm_acr_lsfw_del(lsfw); |
| return ERR_PTR(ret); |
| } |
| |
| return lsfw; |
| } |
| |
| static void |
| nvkm_acr_lsfw_from_desc(const struct nvfw_ls_desc_head *desc, |
| struct nvkm_acr_lsfw *lsfw) |
| { |
| lsfw->bootloader_size = ALIGN(desc->bootloader_size, 256); |
| lsfw->bootloader_imem_offset = desc->bootloader_imem_offset; |
| |
| lsfw->app_size = ALIGN(desc->app_size, 256); |
| lsfw->app_start_offset = desc->app_start_offset; |
| lsfw->app_imem_entry = desc->app_imem_entry; |
| lsfw->app_resident_code_offset = desc->app_resident_code_offset; |
| lsfw->app_resident_code_size = desc->app_resident_code_size; |
| lsfw->app_resident_data_offset = desc->app_resident_data_offset; |
| lsfw->app_resident_data_size = desc->app_resident_data_size; |
| |
| lsfw->ucode_size = ALIGN(lsfw->app_resident_data_offset, 256) + |
| lsfw->bootloader_size; |
| lsfw->data_size = lsfw->app_size + lsfw->bootloader_size - |
| lsfw->ucode_size; |
| } |
| |
| int |
| nvkm_acr_lsfw_load_sig_image_desc(struct nvkm_subdev *subdev, |
| struct nvkm_falcon *falcon, |
| enum nvkm_acr_lsf_id id, |
| const char *path, int ver, |
| const struct nvkm_acr_lsf_func *func) |
| { |
| const struct firmware *fw; |
| struct nvkm_acr_lsfw *lsfw; |
| |
| lsfw = nvkm_acr_lsfw_load_sig_image_desc_(subdev, falcon, id, path, ver, |
| func, &fw); |
| if (IS_ERR(lsfw)) |
| return PTR_ERR(lsfw); |
| |
| nvkm_acr_lsfw_from_desc(&nvfw_ls_desc(subdev, fw->data)->head, lsfw); |
| nvkm_firmware_put(fw); |
| return 0; |
| } |
| |
| int |
| nvkm_acr_lsfw_load_sig_image_desc_v1(struct nvkm_subdev *subdev, |
| struct nvkm_falcon *falcon, |
| enum nvkm_acr_lsf_id id, |
| const char *path, int ver, |
| const struct nvkm_acr_lsf_func *func) |
| { |
| const struct firmware *fw; |
| struct nvkm_acr_lsfw *lsfw; |
| |
| lsfw = nvkm_acr_lsfw_load_sig_image_desc_(subdev, falcon, id, path, ver, |
| func, &fw); |
| if (IS_ERR(lsfw)) |
| return PTR_ERR(lsfw); |
| |
| nvkm_acr_lsfw_from_desc(&nvfw_ls_desc_v1(subdev, fw->data)->head, lsfw); |
| nvkm_firmware_put(fw); |
| return 0; |
| } |
| |
| int |
| nvkm_acr_lsfw_load_bl_inst_data_sig(struct nvkm_subdev *subdev, |
| struct nvkm_falcon *falcon, |
| enum nvkm_acr_lsf_id id, |
| const char *path, int ver, |
| const struct nvkm_acr_lsf_func *func) |
| { |
| struct nvkm_acr *acr = subdev->device->acr; |
| struct nvkm_acr_lsfw *lsfw; |
| const struct firmware *bl = NULL, *inst = NULL, *data = NULL; |
| const struct nvfw_bin_hdr *hdr; |
| const struct nvfw_bl_desc *desc; |
| u32 *bldata; |
| int ret; |
| |
| if (IS_ERR((lsfw = nvkm_acr_lsfw_add(func, acr, falcon, id)))) |
| return PTR_ERR(lsfw); |
| |
| ret = nvkm_firmware_load_name(subdev, path, "bl", ver, &bl); |
| if (ret) |
| goto done; |
| |
| hdr = nvfw_bin_hdr(subdev, bl->data); |
| desc = nvfw_bl_desc(subdev, bl->data + hdr->header_offset); |
| bldata = (void *)(bl->data + hdr->data_offset); |
| |
| ret = nvkm_firmware_load_name(subdev, path, "inst", ver, &inst); |
| if (ret) |
| goto done; |
| |
| ret = nvkm_firmware_load_name(subdev, path, "data", ver, &data); |
| if (ret) |
| goto done; |
| |
| ret = nvkm_firmware_load_name(subdev, path, "sig", ver, &lsfw->sig); |
| if (ret) |
| goto done; |
| |
| lsfw->bootloader_size = ALIGN(desc->code_size, 256); |
| lsfw->bootloader_imem_offset = desc->start_tag << 8; |
| |
| lsfw->app_start_offset = lsfw->bootloader_size; |
| lsfw->app_imem_entry = 0; |
| lsfw->app_resident_code_offset = 0; |
| lsfw->app_resident_code_size = ALIGN(inst->size, 256); |
| lsfw->app_resident_data_offset = lsfw->app_resident_code_size; |
| lsfw->app_resident_data_size = ALIGN(data->size, 256); |
| lsfw->app_size = lsfw->app_resident_code_size + |
| lsfw->app_resident_data_size; |
| |
| lsfw->img.size = lsfw->bootloader_size + lsfw->app_size; |
| if (!(lsfw->img.data = kzalloc(lsfw->img.size, GFP_KERNEL))) { |
| ret = -ENOMEM; |
| goto done; |
| } |
| |
| memcpy(lsfw->img.data, bldata, lsfw->bootloader_size); |
| memcpy(lsfw->img.data + lsfw->app_start_offset + |
| lsfw->app_resident_code_offset, inst->data, inst->size); |
| memcpy(lsfw->img.data + lsfw->app_start_offset + |
| lsfw->app_resident_data_offset, data->data, data->size); |
| |
| lsfw->ucode_size = ALIGN(lsfw->app_resident_data_offset, 256) + |
| lsfw->bootloader_size; |
| lsfw->data_size = lsfw->app_size + lsfw->bootloader_size - |
| lsfw->ucode_size; |
| |
| done: |
| if (ret) |
| nvkm_acr_lsfw_del(lsfw); |
| nvkm_firmware_put(data); |
| nvkm_firmware_put(inst); |
| nvkm_firmware_put(bl); |
| return ret; |
| } |