| /* |
| * Copyright 2022 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/memory.h> |
| #include <subdev/mmu.h> |
| |
| #include <nvfw/fw.h> |
| #include <nvfw/hs.h> |
| |
| int |
| nvkm_falcon_fw_patch(struct nvkm_falcon_fw *fw) |
| { |
| struct nvkm_falcon *falcon = fw->falcon; |
| u32 sig_base_src = fw->sig_base_prd; |
| u32 src, dst, len, i; |
| int idx = 0; |
| |
| FLCNFW_DBG(fw, "patching sigs:%d size:%d", fw->sig_nr, fw->sig_size); |
| if (fw->func->signature) { |
| idx = fw->func->signature(fw, &sig_base_src); |
| if (idx < 0) |
| return idx; |
| } |
| |
| src = idx * fw->sig_size; |
| dst = fw->sig_base_img; |
| len = fw->sig_size / 4; |
| FLCNFW_DBG(fw, "patch idx:%d src:%08x dst:%08x", idx, sig_base_src + src, dst); |
| for (i = 0; i < len; i++) { |
| u32 sig = *(u32 *)(fw->sigs + src); |
| |
| if (nvkm_printk_ok(falcon->owner, falcon->user, NV_DBG_TRACE)) { |
| if (i % 8 == 0) |
| printk(KERN_INFO "sig -> %08x:", dst); |
| printk(KERN_CONT " %08x", sig); |
| } |
| |
| *(u32 *)(fw->fw.img + dst) = sig; |
| src += 4; |
| dst += 4; |
| } |
| |
| return 0; |
| } |
| |
| static void |
| nvkm_falcon_fw_dtor_sigs(struct nvkm_falcon_fw *fw) |
| { |
| kfree(fw->sigs); |
| fw->sigs = NULL; |
| } |
| |
| int |
| nvkm_falcon_fw_boot(struct nvkm_falcon_fw *fw, struct nvkm_subdev *user, |
| bool release, u32 *pmbox0, u32 *pmbox1, u32 mbox0_ok, u32 irqsclr) |
| { |
| struct nvkm_falcon *falcon = fw->falcon; |
| int ret; |
| |
| ret = nvkm_falcon_get(falcon, user); |
| if (ret) |
| return ret; |
| |
| if (fw->sigs) { |
| ret = nvkm_falcon_fw_patch(fw); |
| if (ret) |
| goto done; |
| |
| nvkm_falcon_fw_dtor_sigs(fw); |
| } |
| |
| /* after last write to the img, sync dma mappings */ |
| dma_sync_single_for_device(fw->fw.device->dev, |
| fw->fw.phys, |
| sg_dma_len(&fw->fw.mem.sgl), |
| DMA_TO_DEVICE); |
| |
| FLCNFW_DBG(fw, "resetting"); |
| fw->func->reset(fw); |
| |
| FLCNFW_DBG(fw, "loading"); |
| if (fw->func->setup) { |
| ret = fw->func->setup(fw); |
| if (ret) |
| goto done; |
| } |
| |
| ret = fw->func->load(fw); |
| if (ret) |
| goto done; |
| |
| FLCNFW_DBG(fw, "booting"); |
| ret = fw->func->boot(fw, pmbox0, pmbox1, mbox0_ok, irqsclr); |
| if (ret) |
| FLCNFW_ERR(fw, "boot failed: %d", ret); |
| else |
| FLCNFW_DBG(fw, "booted"); |
| |
| done: |
| if (ret || release) |
| nvkm_falcon_put(falcon, user); |
| return ret; |
| } |
| |
| int |
| nvkm_falcon_fw_oneinit(struct nvkm_falcon_fw *fw, struct nvkm_falcon *falcon, |
| struct nvkm_vmm *vmm, struct nvkm_memory *inst) |
| { |
| int ret; |
| |
| fw->falcon = falcon; |
| fw->vmm = nvkm_vmm_ref(vmm); |
| fw->inst = nvkm_memory_ref(inst); |
| |
| if (fw->boot) { |
| FLCN_DBG(falcon, "mapping %s fw", fw->fw.name); |
| ret = nvkm_vmm_get(fw->vmm, 12, nvkm_memory_size(&fw->fw.mem.memory), &fw->vma); |
| if (ret) { |
| FLCN_ERR(falcon, "get %d", ret); |
| return ret; |
| } |
| |
| ret = nvkm_memory_map(&fw->fw.mem.memory, 0, fw->vmm, fw->vma, NULL, 0); |
| if (ret) { |
| FLCN_ERR(falcon, "map %d", ret); |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| void |
| nvkm_falcon_fw_dtor(struct nvkm_falcon_fw *fw) |
| { |
| nvkm_vmm_put(fw->vmm, &fw->vma); |
| nvkm_vmm_unref(&fw->vmm); |
| nvkm_memory_unref(&fw->inst); |
| nvkm_falcon_fw_dtor_sigs(fw); |
| nvkm_firmware_dtor(&fw->fw); |
| } |
| |
| static const struct nvkm_firmware_func |
| nvkm_falcon_fw_dma = { |
| .type = NVKM_FIRMWARE_IMG_DMA, |
| }; |
| |
| static const struct nvkm_firmware_func |
| nvkm_falcon_fw = { |
| .type = NVKM_FIRMWARE_IMG_RAM, |
| }; |
| |
| int |
| nvkm_falcon_fw_sign(struct nvkm_falcon_fw *fw, u32 sig_base_img, u32 sig_size, const u8 *sigs, |
| int sig_nr_prd, u32 sig_base_prd, int sig_nr_dbg, u32 sig_base_dbg) |
| { |
| fw->sig_base_prd = sig_base_prd; |
| fw->sig_base_dbg = sig_base_dbg; |
| fw->sig_base_img = sig_base_img; |
| fw->sig_size = sig_size; |
| fw->sig_nr = sig_nr_prd + sig_nr_dbg; |
| |
| fw->sigs = kmalloc_array(fw->sig_nr, fw->sig_size, GFP_KERNEL); |
| if (!fw->sigs) |
| return -ENOMEM; |
| |
| memcpy(fw->sigs, sigs + sig_base_prd, sig_nr_prd * fw->sig_size); |
| if (sig_nr_dbg) |
| memcpy(fw->sigs + sig_size, sigs + sig_base_dbg, sig_nr_dbg * fw->sig_size); |
| |
| return 0; |
| } |
| |
| int |
| nvkm_falcon_fw_ctor(const struct nvkm_falcon_fw_func *func, const char *name, |
| struct nvkm_device *device, bool dma, const void *src, u32 len, |
| struct nvkm_falcon *falcon, struct nvkm_falcon_fw *fw) |
| { |
| const struct nvkm_firmware_func *type = dma ? &nvkm_falcon_fw_dma : &nvkm_falcon_fw; |
| int ret; |
| |
| fw->func = func; |
| |
| ret = nvkm_firmware_ctor(type, name, device, src, len, &fw->fw); |
| if (ret) |
| return ret; |
| |
| return falcon ? nvkm_falcon_fw_oneinit(fw, falcon, NULL, NULL) : 0; |
| } |
| |
| int |
| nvkm_falcon_fw_ctor_hs(const struct nvkm_falcon_fw_func *func, const char *name, |
| struct nvkm_subdev *subdev, const char *bl, const char *img, int ver, |
| struct nvkm_falcon *falcon, struct nvkm_falcon_fw *fw) |
| { |
| const struct firmware *blob; |
| const struct nvfw_bin_hdr *hdr; |
| const struct nvfw_hs_header *hshdr; |
| const struct nvfw_hs_load_header *lhdr; |
| const struct nvfw_bl_desc *desc; |
| u32 loc, sig; |
| int ret; |
| |
| ret = nvkm_firmware_load_name(subdev, img, "", ver, &blob); |
| if (ret) |
| return ret; |
| |
| hdr = nvfw_bin_hdr(subdev, blob->data); |
| hshdr = nvfw_hs_header(subdev, blob->data + hdr->header_offset); |
| |
| ret = nvkm_falcon_fw_ctor(func, name, subdev->device, bl != NULL, |
| blob->data + hdr->data_offset, hdr->data_size, falcon, fw); |
| if (ret) |
| goto done; |
| |
| /* Earlier FW releases by NVIDIA for Nouveau's use aren't in NVIDIA's |
| * standard format, and don't have the indirection seen in the 0x10de |
| * case. |
| */ |
| switch (hdr->bin_magic) { |
| case 0x000010de: |
| loc = *(u32 *)(blob->data + hshdr->patch_loc); |
| sig = *(u32 *)(blob->data + hshdr->patch_sig); |
| break; |
| case 0x3b1d14f0: |
| loc = hshdr->patch_loc; |
| sig = hshdr->patch_sig; |
| break; |
| default: |
| WARN_ON(1); |
| ret = -EINVAL; |
| goto done; |
| } |
| |
| ret = nvkm_falcon_fw_sign(fw, loc, hshdr->sig_prod_size, blob->data, |
| 1, hshdr->sig_prod_offset + sig, |
| 1, hshdr->sig_dbg_offset + sig); |
| if (ret) |
| goto done; |
| |
| lhdr = nvfw_hs_load_header(subdev, blob->data + hshdr->hdr_offset); |
| |
| fw->nmem_base_img = 0; |
| fw->nmem_base = lhdr->non_sec_code_off; |
| fw->nmem_size = lhdr->non_sec_code_size; |
| |
| fw->imem_base_img = lhdr->apps[0]; |
| fw->imem_base = ALIGN(lhdr->apps[0], 0x100); |
| fw->imem_size = lhdr->apps[lhdr->num_apps + 0]; |
| |
| fw->dmem_base_img = lhdr->data_dma_base; |
| fw->dmem_base = 0; |
| fw->dmem_size = lhdr->data_size; |
| fw->dmem_sign = loc - lhdr->data_dma_base; |
| |
| if (bl) { |
| nvkm_firmware_put(blob); |
| |
| ret = nvkm_firmware_load_name(subdev, bl, "", ver, &blob); |
| if (ret) |
| return ret; |
| |
| hdr = nvfw_bin_hdr(subdev, blob->data); |
| desc = nvfw_bl_desc(subdev, blob->data + hdr->header_offset); |
| |
| fw->boot_addr = desc->start_tag << 8; |
| fw->boot_size = desc->code_size; |
| fw->boot = kmemdup(blob->data + hdr->data_offset + desc->code_off, |
| fw->boot_size, GFP_KERNEL); |
| if (!fw->boot) |
| ret = -ENOMEM; |
| } else { |
| fw->boot_addr = fw->nmem_base; |
| } |
| |
| done: |
| if (ret) |
| nvkm_falcon_fw_dtor(fw); |
| |
| nvkm_firmware_put(blob); |
| return ret; |
| } |
| |
| int |
| nvkm_falcon_fw_ctor_hs_v2(const struct nvkm_falcon_fw_func *func, const char *name, |
| struct nvkm_subdev *subdev, const char *img, int ver, |
| struct nvkm_falcon *falcon, struct nvkm_falcon_fw *fw) |
| { |
| const struct nvfw_bin_hdr *hdr; |
| const struct nvfw_hs_header_v2 *hshdr; |
| const struct nvfw_hs_load_header_v2 *lhdr; |
| const struct firmware *blob; |
| u32 loc, sig, cnt, *meta; |
| int ret; |
| |
| ret = nvkm_firmware_load_name(subdev, img, "", ver, &blob); |
| if (ret) |
| return ret; |
| |
| hdr = nvfw_bin_hdr(subdev, blob->data); |
| hshdr = nvfw_hs_header_v2(subdev, blob->data + hdr->header_offset); |
| meta = (u32 *)(blob->data + hshdr->meta_data_offset); |
| loc = *(u32 *)(blob->data + hshdr->patch_loc); |
| sig = *(u32 *)(blob->data + hshdr->patch_sig); |
| cnt = *(u32 *)(blob->data + hshdr->num_sig); |
| |
| ret = nvkm_falcon_fw_ctor(func, name, subdev->device, true, |
| blob->data + hdr->data_offset, hdr->data_size, falcon, fw); |
| if (ret) |
| goto done; |
| |
| ret = nvkm_falcon_fw_sign(fw, loc, hshdr->sig_prod_size / cnt, blob->data, |
| cnt, hshdr->sig_prod_offset + sig, 0, 0); |
| if (ret) |
| goto done; |
| |
| lhdr = nvfw_hs_load_header_v2(subdev, blob->data + hshdr->header_offset); |
| |
| fw->imem_base_img = lhdr->app[0].offset; |
| fw->imem_base = 0; |
| fw->imem_size = lhdr->app[0].size; |
| |
| fw->dmem_base_img = lhdr->os_data_offset; |
| fw->dmem_base = 0; |
| fw->dmem_size = lhdr->os_data_size; |
| fw->dmem_sign = loc - lhdr->os_data_offset; |
| |
| fw->boot_addr = lhdr->app[0].offset; |
| |
| fw->fuse_ver = meta[0]; |
| fw->engine_id = meta[1]; |
| fw->ucode_id = meta[2]; |
| |
| done: |
| if (ret) |
| nvkm_falcon_fw_dtor(fw); |
| |
| nvkm_firmware_put(blob); |
| return ret; |
| } |