mirror of
git://nv-tegra.nvidia.com/linux-nv-oot.git
synced 2025-12-22 17:25:35 +03:00
Firmware requests triggered by userspace action can be executed in interruptible context, which can cause signals to interrupt firmware loading. Ordinarily, one would propagate the error code to userspace and let it retry. However, request_firmware does not have proper handling of retryable errors, as sysfs fallbacks will clobber the original error code. Furthermore, pm_runtime, through which we would propagate the error code, doesn't support transient resume failures either. As such, as a quick fix, change to request_firmware_direct and retry it directly in situ 10 times. Bug 4753613 Co-developed-by: Zuyi Hu <zuyih@nvidia.com> Signed-off-by: Mikko Perttunen <mperttunen@nvidia.com> Change-Id: Idb9a04d4fa80aeffbfd0ad0176cce55c4dedf488 Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3202848 (cherry picked from commit a17e81cad18bbdfef0194471524b675d74501054) Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3219109 (cherry picked from commit 30c08f369a8f3acd642fcec70814086bd472e734) Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3253514 GVS: buildbot_gerritrpt <buildbot_gerritrpt@nvidia.com> Reviewed-by: Santosh BS <santoshb@nvidia.com> Tested-by: Zuyi Hu <zuyih@nvidia.com>
214 lines
5.3 KiB
C
214 lines
5.3 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2015-2023, NVIDIA Corporation. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/platform_device.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/firmware.h>
|
|
#include <linux/pci_ids.h>
|
|
#include <linux/iopoll.h>
|
|
|
|
#include "falcon.h"
|
|
|
|
enum falcon_memory {
|
|
FALCON_MEMORY_IMEM,
|
|
FALCON_MEMORY_DATA,
|
|
};
|
|
|
|
static void falcon_writel(struct falcon *falcon, u32 value, u32 offset)
|
|
{
|
|
writel(value, falcon->regs + offset);
|
|
}
|
|
|
|
int falcon_wait_idle(struct falcon *falcon)
|
|
{
|
|
u32 value;
|
|
|
|
return readl_poll_timeout(falcon->regs + FALCON_IDLESTATE, value,
|
|
(value == 0), 10, 100000);
|
|
}
|
|
|
|
static int falcon_dma_wait_idle(struct falcon *falcon)
|
|
{
|
|
u32 value;
|
|
|
|
return readl_poll_timeout(falcon->regs + FALCON_DMATRFCMD, value,
|
|
(value & FALCON_DMATRFCMD_IDLE), 10, 100000);
|
|
}
|
|
|
|
static int falcon_copy_chunk(struct falcon *falcon,
|
|
phys_addr_t base,
|
|
unsigned long offset,
|
|
enum falcon_memory target)
|
|
{
|
|
u32 cmd = FALCON_DMATRFCMD_SIZE_256B;
|
|
|
|
if (target == FALCON_MEMORY_IMEM)
|
|
cmd |= FALCON_DMATRFCMD_IMEM;
|
|
|
|
falcon_writel(falcon, offset, FALCON_DMATRFMOFFS);
|
|
falcon_writel(falcon, base, FALCON_DMATRFFBOFFS);
|
|
falcon_writel(falcon, cmd, FALCON_DMATRFCMD);
|
|
|
|
return falcon_dma_wait_idle(falcon);
|
|
}
|
|
|
|
static void falcon_copy_firmware_image(struct falcon *falcon,
|
|
const struct firmware *firmware)
|
|
{
|
|
u32 *virt = falcon->firmware.virt;
|
|
size_t i;
|
|
|
|
/* copy the whole thing taking into account endianness */
|
|
for (i = 0; i < firmware->size / sizeof(u32); i++)
|
|
virt[i] = le32_to_cpu(((__le32 *)firmware->data)[i]);
|
|
}
|
|
|
|
static int falcon_parse_firmware_image(struct falcon *falcon)
|
|
{
|
|
struct falcon_fw_bin_header_v1 *bin = (void *)falcon->firmware.virt;
|
|
struct falcon_fw_os_header_v1 *os;
|
|
|
|
/* endian problems would show up right here */
|
|
if (bin->magic != PCI_VENDOR_ID_NVIDIA && bin->magic != 0x10fe) {
|
|
dev_err(falcon->dev, "incorrect firmware magic\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* currently only version 1 is supported */
|
|
if (bin->version != 1) {
|
|
dev_err(falcon->dev, "unsupported firmware version\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* check that the firmware size is consistent */
|
|
if (bin->size > falcon->firmware.size) {
|
|
dev_err(falcon->dev, "firmware image size inconsistency\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
os = falcon->firmware.virt + bin->os_header_offset;
|
|
|
|
falcon->firmware.bin_data.size = bin->os_size;
|
|
falcon->firmware.bin_data.offset = bin->os_data_offset;
|
|
falcon->firmware.code.offset = os->code_offset;
|
|
falcon->firmware.code.size = os->code_size;
|
|
falcon->firmware.data.offset = os->data_offset;
|
|
falcon->firmware.data.size = os->data_size;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int falcon_read_firmware(struct falcon *falcon, const char *name)
|
|
{
|
|
int err, retry_count=10;
|
|
|
|
retry_request:
|
|
/* request_firmware prints error if it fails */
|
|
err = request_firmware_direct(&falcon->firmware.firmware, name, falcon->dev);
|
|
if (err == -EINTR && retry_count) {
|
|
retry_count--;
|
|
goto retry_request;
|
|
}
|
|
|
|
if (err < 0)
|
|
return err;
|
|
|
|
falcon->firmware.size = falcon->firmware.firmware->size;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int falcon_load_firmware(struct falcon *falcon)
|
|
{
|
|
const struct firmware *firmware = falcon->firmware.firmware;
|
|
int err;
|
|
|
|
/* copy firmware image into local area. this also ensures endianness */
|
|
falcon_copy_firmware_image(falcon, firmware);
|
|
|
|
/* parse the image data */
|
|
err = falcon_parse_firmware_image(falcon);
|
|
if (err < 0) {
|
|
dev_err(falcon->dev, "failed to parse firmware image\n");
|
|
return err;
|
|
}
|
|
|
|
release_firmware(firmware);
|
|
falcon->firmware.firmware = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int falcon_init(struct falcon *falcon)
|
|
{
|
|
falcon->firmware.virt = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void falcon_exit(struct falcon *falcon)
|
|
{
|
|
if (falcon->firmware.firmware)
|
|
release_firmware(falcon->firmware.firmware);
|
|
}
|
|
|
|
int falcon_boot(struct falcon *falcon)
|
|
{
|
|
unsigned long offset;
|
|
u32 value;
|
|
int err;
|
|
|
|
if (!falcon->firmware.virt)
|
|
return -EINVAL;
|
|
|
|
err = readl_poll_timeout(falcon->regs + FALCON_DMACTL, value,
|
|
(value & (FALCON_DMACTL_IMEM_SCRUBBING |
|
|
FALCON_DMACTL_DMEM_SCRUBBING)) == 0,
|
|
10, 10000);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
falcon_writel(falcon, 0, FALCON_DMACTL);
|
|
|
|
/* setup the address of the binary data so Falcon can access it later */
|
|
falcon_writel(falcon, (falcon->firmware.iova +
|
|
falcon->firmware.bin_data.offset) >> 8,
|
|
FALCON_DMATRFBASE);
|
|
|
|
/* copy the data segment into Falcon internal memory */
|
|
for (offset = 0; offset < falcon->firmware.data.size; offset += 256)
|
|
falcon_copy_chunk(falcon,
|
|
falcon->firmware.data.offset + offset,
|
|
offset, FALCON_MEMORY_DATA);
|
|
|
|
/* copy the code segment into Falcon internal memory */
|
|
for (offset = 0; offset < falcon->firmware.code.size; offset += 256)
|
|
falcon_copy_chunk(falcon, falcon->firmware.code.offset + offset,
|
|
offset, FALCON_MEMORY_IMEM);
|
|
|
|
/* enable interface */
|
|
falcon_writel(falcon, FALCON_ITFEN_MTHDEN |
|
|
FALCON_ITFEN_CTXEN,
|
|
FALCON_ITFEN);
|
|
|
|
/* boot falcon */
|
|
falcon_writel(falcon, 0x00000000, FALCON_BOOTVEC);
|
|
falcon_writel(falcon, FALCON_CPUCTL_STARTCPU, FALCON_CPUCTL);
|
|
|
|
err = falcon_wait_idle(falcon);
|
|
if (err < 0) {
|
|
dev_err(falcon->dev, "Falcon boot failed due to timeout\n");
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void falcon_execute_method(struct falcon *falcon, u32 method, u32 data)
|
|
{
|
|
falcon_writel(falcon, method >> 2, FALCON_UCLASS_METHOD_OFFSET);
|
|
falcon_writel(falcon, data, FALCON_UCLASS_METHOD_DATA);
|
|
}
|