diff options
Diffstat (limited to 'drivers/hid/intel-ish-hid/ishtp')
-rw-r--r-- | drivers/hid/intel-ish-hid/ishtp/bus.c | 4 | ||||
-rw-r--r-- | drivers/hid/intel-ish-hid/ishtp/bus.h | 1 | ||||
-rw-r--r-- | drivers/hid/intel-ish-hid/ishtp/client-buffers.c | 21 | ||||
-rw-r--r-- | drivers/hid/intel-ish-hid/ishtp/client.c | 21 | ||||
-rw-r--r-- | drivers/hid/intel-ish-hid/ishtp/client.h | 3 | ||||
-rw-r--r-- | drivers/hid/intel-ish-hid/ishtp/hbm.c | 21 | ||||
-rw-r--r-- | drivers/hid/intel-ish-hid/ishtp/init.c | 38 | ||||
-rw-r--r-- | drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h | 45 | ||||
-rw-r--r-- | drivers/hid/intel-ish-hid/ishtp/loader.c | 432 | ||||
-rw-r--r-- | drivers/hid/intel-ish-hid/ishtp/loader.h | 265 |
10 files changed, 774 insertions, 77 deletions
diff --git a/drivers/hid/intel-ish-hid/ishtp/bus.c b/drivers/hid/intel-ish-hid/ishtp/bus.c index 03d5601ce807..5ac7d70a7c84 100644 --- a/drivers/hid/intel-ish-hid/ishtp/bus.c +++ b/drivers/hid/intel-ish-hid/ishtp/bus.c @@ -236,7 +236,7 @@ static int ishtp_cl_device_probe(struct device *dev) * * Return: 1 if dev & drv matches, 0 otherwise. */ -static int ishtp_cl_bus_match(struct device *dev, struct device_driver *drv) +static int ishtp_cl_bus_match(struct device *dev, const struct device_driver *drv) { struct ishtp_cl_device *device = to_ishtp_cl_device(dev); struct ishtp_cl_driver *driver = to_ishtp_cl_driver(drv); @@ -844,6 +844,7 @@ EXPORT_SYMBOL(ishtp_device); /** * ishtp_wait_resume() - Wait for IPC resume + * @dev: ishtp device * * Wait for IPC resume * @@ -931,4 +932,5 @@ static void __exit ishtp_bus_unregister(void) module_init(ishtp_bus_register); module_exit(ishtp_bus_unregister); +MODULE_DESCRIPTION("ISHTP bus driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/hid/intel-ish-hid/ishtp/bus.h b/drivers/hid/intel-ish-hid/ishtp/bus.h index 5bb85c932e4c..53645ac89ee8 100644 --- a/drivers/hid/intel-ish-hid/ishtp/bus.h +++ b/drivers/hid/intel-ish-hid/ishtp/bus.h @@ -46,7 +46,6 @@ struct ishtp_cl_device { }; int ishtp_bus_new_client(struct ishtp_device *dev); -void ishtp_remove_all_clients(struct ishtp_device *dev); int ishtp_cl_device_bind(struct ishtp_cl *cl); void ishtp_cl_bus_rx_event(struct ishtp_cl_device *device); diff --git a/drivers/hid/intel-ish-hid/ishtp/client-buffers.c b/drivers/hid/intel-ish-hid/ishtp/client-buffers.c index 513d7a4a1b8a..97f4026b1627 100644 --- a/drivers/hid/intel-ish-hid/ishtp/client-buffers.c +++ b/drivers/hid/intel-ish-hid/ishtp/client-buffers.c @@ -252,27 +252,6 @@ int ishtp_cl_io_rb_recycle(struct ishtp_cl_rb *rb) EXPORT_SYMBOL(ishtp_cl_io_rb_recycle); /** - * ishtp_cl_tx_empty() -test whether client device tx buffer is empty - * @cl: Pointer to client device instance - * - * Look client device tx buffer list, and check whether this list is empty - * - * Return: true if client tx buffer list is empty else false - */ -bool ishtp_cl_tx_empty(struct ishtp_cl *cl) -{ - int tx_list_empty; - unsigned long tx_flags; - - spin_lock_irqsave(&cl->tx_list_spinlock, tx_flags); - tx_list_empty = list_empty(&cl->tx_list.list); - spin_unlock_irqrestore(&cl->tx_list_spinlock, tx_flags); - - return !!tx_list_empty; -} -EXPORT_SYMBOL(ishtp_cl_tx_empty); - -/** * ishtp_cl_rx_get_rb() -Get a rb from client device rx buffer list * @cl: Pointer to client device instance * diff --git a/drivers/hid/intel-ish-hid/ishtp/client.c b/drivers/hid/intel-ish-hid/ishtp/client.c index 8a7f2f6a4f86..21a2c0773cc2 100644 --- a/drivers/hid/intel-ish-hid/ishtp/client.c +++ b/drivers/hid/intel-ish-hid/ishtp/client.c @@ -14,25 +14,6 @@ #include "hbm.h" #include "client.h" -int ishtp_cl_get_tx_free_buffer_size(struct ishtp_cl *cl) -{ - unsigned long tx_free_flags; - int size; - - spin_lock_irqsave(&cl->tx_free_list_spinlock, tx_free_flags); - size = cl->tx_ring_free_size * cl->device->fw_client->props.max_msg_length; - spin_unlock_irqrestore(&cl->tx_free_list_spinlock, tx_free_flags); - - return size; -} -EXPORT_SYMBOL(ishtp_cl_get_tx_free_buffer_size); - -int ishtp_cl_get_tx_free_rings(struct ishtp_cl *cl) -{ - return cl->tx_ring_free_size; -} -EXPORT_SYMBOL(ishtp_cl_get_tx_free_rings); - /** * ishtp_read_list_flush() - Flush read queue * @cl: ishtp client instance @@ -863,7 +844,7 @@ static void ipc_tx_send(void *prm) /* Send ipc fragment */ ishtp_hdr.length = dev->mtu; ishtp_hdr.msg_complete = 0; - /* All fregments submitted to IPC queue with no callback */ + /* All fragments submitted to IPC queue with no callback */ ishtp_write_message(dev, &ishtp_hdr, pmsg); cl->tx_offs += dev->mtu; rem = cl_msg->send_buf.size - cl->tx_offs; diff --git a/drivers/hid/intel-ish-hid/ishtp/client.h b/drivers/hid/intel-ish-hid/ishtp/client.h index fc62dd1495da..0efd49dd2530 100644 --- a/drivers/hid/intel-ish-hid/ishtp/client.h +++ b/drivers/hid/intel-ish-hid/ishtp/client.h @@ -109,7 +109,6 @@ struct ishtp_cl { }; /* Client connection managenment internal functions */ -int ishtp_can_client_connect(struct ishtp_device *ishtp_dev, guid_t *uuid); int ishtp_fw_cl_by_id(struct ishtp_device *dev, uint8_t client_id); void ishtp_cl_send_msg(struct ishtp_device *dev, struct ishtp_cl *cl); void recv_ishtp_cl_msg(struct ishtp_device *dev, @@ -121,8 +120,6 @@ int ishtp_cl_alloc_rx_ring(struct ishtp_cl *cl); int ishtp_cl_alloc_tx_ring(struct ishtp_cl *cl); void ishtp_cl_free_rx_ring(struct ishtp_cl *cl); void ishtp_cl_free_tx_ring(struct ishtp_cl *cl); -int ishtp_cl_get_tx_free_buffer_size(struct ishtp_cl *cl); -int ishtp_cl_get_tx_free_rings(struct ishtp_cl *cl); /* DMA I/F functions */ void recv_ishtp_cl_msg_dma(struct ishtp_device *dev, void *msg, diff --git a/drivers/hid/intel-ish-hid/ishtp/hbm.c b/drivers/hid/intel-ish-hid/ishtp/hbm.c index 9c031a06e4c4..8ee5467127d8 100644 --- a/drivers/hid/intel-ish-hid/ishtp/hbm.c +++ b/drivers/hid/intel-ish-hid/ishtp/hbm.c @@ -13,6 +13,7 @@ #include "ishtp-dev.h" #include "hbm.h" #include "client.h" +#include "loader.h" /** * ishtp_hbm_fw_cl_allocate() - Allocate FW clients @@ -570,6 +571,10 @@ void ishtp_hbm_dispatch(struct ishtp_device *dev, return; } + /* Start firmware loading process if it has loader capability */ + if (version_res->host_version_supported & ISHTP_SUPPORT_CAP_LOADER) + schedule_work(&dev->work_fw_loader); + dev->version.major_version = HBM_MAJOR_VERSION; dev->version.minor_version = HBM_MINOR_VERSION; if (dev->dev_state == ISHTP_DEV_INIT_CLIENTS && @@ -865,6 +870,20 @@ eoi: } /** + * ishtp_loader_recv_msg() - Receive a message from the ISHTP device + * @dev: The ISHTP device + * @buf: The buffer containing the message + */ +static void ishtp_loader_recv_msg(struct ishtp_device *dev, void *buf) +{ + if (dev->fw_loader_rx_buf) + memcpy(dev->fw_loader_rx_buf, buf, dev->fw_loader_rx_size); + + dev->fw_loader_received = true; + wake_up_interruptible(&dev->wait_loader_recvd_msg); +} + +/** * recv_fixed_cl_msg() - Receive fixed client message * @dev: ISHTP device instance * @ishtp_hdr: received bus message @@ -890,6 +909,8 @@ void recv_fixed_cl_msg(struct ishtp_device *dev, else dev_err(dev->devc, "unknown fixed client msg [%02X]\n", msg_hdr->cmd); + } else if (ishtp_hdr->fw_addr == ISHTP_LOADER_CLIENT_ADDR) { + ishtp_loader_recv_msg(dev, rd_msg_buf); } } diff --git a/drivers/hid/intel-ish-hid/ishtp/init.c b/drivers/hid/intel-ish-hid/ishtp/init.c index 02a00cc2dd11..26bf9045a8de 100644 --- a/drivers/hid/intel-ish-hid/ishtp/init.c +++ b/drivers/hid/intel-ish-hid/ishtp/init.c @@ -5,42 +5,14 @@ * Copyright (c) 2003-2016, Intel Corporation. */ +#include <linux/devm-helpers.h> #include <linux/export.h> #include <linux/slab.h> #include <linux/sched.h> #include "ishtp-dev.h" #include "hbm.h" #include "client.h" - -/** - * ishtp_dev_state_str() -Convert to string format - * @state: state to convert - * - * Convert state to string for prints - * - * Return: character pointer to converted string - */ -const char *ishtp_dev_state_str(int state) -{ - switch (state) { - case ISHTP_DEV_INITIALIZING: - return "INITIALIZING"; - case ISHTP_DEV_INIT_CLIENTS: - return "INIT_CLIENTS"; - case ISHTP_DEV_ENABLED: - return "ENABLED"; - case ISHTP_DEV_RESETTING: - return "RESETTING"; - case ISHTP_DEV_DISABLED: - return "DISABLED"; - case ISHTP_DEV_POWER_DOWN: - return "POWER_DOWN"; - case ISHTP_DEV_POWER_UP: - return "POWER_UP"; - default: - return "unknown"; - } -} +#include "loader.h" /** * ishtp_device_init() - ishtp device init @@ -51,6 +23,8 @@ const char *ishtp_dev_state_str(int state) */ void ishtp_device_init(struct ishtp_device *dev) { + int ret; + dev->dev_state = ISHTP_DEV_INITIALIZING; INIT_LIST_HEAD(&dev->cl_list); INIT_LIST_HEAD(&dev->device_list); @@ -59,6 +33,7 @@ void ishtp_device_init(struct ishtp_device *dev) spin_lock_init(&dev->rd_msg_spinlock); init_waitqueue_head(&dev->wait_hbm_recvd_msg); + init_waitqueue_head(&dev->wait_loader_recvd_msg); spin_lock_init(&dev->read_list_spinlock); spin_lock_init(&dev->device_lock); spin_lock_init(&dev->device_list_lock); @@ -76,6 +51,9 @@ void ishtp_device_init(struct ishtp_device *dev) INIT_LIST_HEAD(&dev->read_list.list); + ret = devm_work_autocancel(dev->devc, &dev->work_fw_loader, ishtp_loader_work); + if (ret) + dev_err_probe(dev->devc, ret, "Failed to initialise FW loader work\n"); } EXPORT_SYMBOL(ishtp_device_init); diff --git a/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h b/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h index 32142c7d9a04..ec9f6e87aaf2 100644 --- a/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h +++ b/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h @@ -57,7 +57,6 @@ enum ishtp_dev_state { ISHTP_DEV_POWER_DOWN, ISHTP_DEV_POWER_UP }; -const char *ishtp_dev_state_str(int state); struct ishtp_cl; @@ -123,11 +122,37 @@ struct ishtp_hw_ops { }; /** + * struct ishtp_driver_data - Driver-specific data for ISHTP devices + * + * This structure holds driver-specific data that can be associated with each + * ISHTP device instance. It allows for the storage of data that is unique to + * a particular driver or hardware variant. + * + * @fw_generation: The generation name associated with a specific hardware + * variant of the Intel Integrated Sensor Hub (ISH). This allows + * the driver to load the correct firmware based on the device's + * hardware variant. For example, "lnlm" for the Lunar Lake-M + * platform. The generation name must not exceed 8 characters + * in length. + */ +struct ishtp_driver_data { + char *fw_generation; +}; + +struct ish_version { + u16 major; + u16 minor; + u16 hotfix; + u16 build; +}; + +/** * struct ishtp_device - ISHTP private device struct */ struct ishtp_device { struct device *devc; /* pointer to lowest device */ struct pci_dev *pdev; /* PCI device to get device ids */ + struct ishtp_driver_data *driver_data; /* pointer to driver-specific data */ /* waitq for waiting for suspend response */ wait_queue_head_t suspend_wait; @@ -147,6 +172,17 @@ struct ishtp_device { struct hbm_version version; int transfer_path; /* Choice of transfer path: IPC or DMA */ + /* work structure for scheduling firmware loading tasks */ + struct work_struct work_fw_loader; + /* waitq for waiting for command response from the firmware loader */ + wait_queue_head_t wait_loader_recvd_msg; + /* indicating whether a message from the firmware loader has been received */ + bool fw_loader_received; + /* pointer to a buffer for receiving messages from the firmware loader */ + void *fw_loader_rx_buf; + /* size of the buffer pointed to by fw_loader_rx_buf */ + int fw_loader_rx_size; + /* ishtp device states */ enum ishtp_dev_state dev_state; enum ishtp_hbm_state hbm_state; @@ -206,12 +242,19 @@ struct ishtp_device { /* Dump to trace buffers if enabled*/ ishtp_print_log print_log; + /* Base version of Intel's released firmware */ + struct ish_version base_ver; + /* Vendor-customized project version */ + struct ish_version prj_ver; + /* Debug stats */ unsigned int ipc_rx_cnt; unsigned long long ipc_rx_bytes_cnt; unsigned int ipc_tx_cnt; unsigned long long ipc_tx_bytes_cnt; + /* Time of the last clock sync */ + unsigned long prev_sync; const struct ishtp_hw_ops *ops; size_t mtu; uint32_t ishtp_msg_hdr; diff --git a/drivers/hid/intel-ish-hid/ishtp/loader.c b/drivers/hid/intel-ish-hid/ishtp/loader.c new file mode 100644 index 000000000000..f34086b29cf0 --- /dev/null +++ b/drivers/hid/intel-ish-hid/ishtp/loader.c @@ -0,0 +1,432 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ISHTP firmware loader function + * + * Copyright (c) 2024, Intel Corporation. + * + * This module implements the functionality to load the main ISH firmware from the host, starting + * with the Lunar Lake generation. It leverages a new method that enhances space optimization and + * flexibility by dividing the ISH firmware into a bootloader and main firmware. + * + * Please refer to the [Documentation](Documentation/hid/intel-ish-hid.rst) for the details on + * flows. + * + * Additionally, address potential error scenarios to ensure graceful failure handling. + * - Firmware Image Not Found: + * Occurs when `request_firmware()` cannot locate the firmware image. The ISH firmware will + * remain in a state awaiting firmware loading from the host, with no further action from + * the ISHTP driver. + * Recovery: Re-insmod the ISH drivers allows for a retry of the firmware loading from the host. + * + * - DMA Buffer Allocation Failure: + * This happens if allocating a DMA buffer during `prepare_dma_bufs()` fails. The ISH firmware + * will stay in a waiting state, and the ISHTP driver will release any allocated DMA buffers and + * firmware without further actions. + * Recovery: Re-insmod the ISH drivers allows for a retry of the firmware loading from the host. + * + * - Incorrect Firmware Image: + * Using an incorrect firmware image will initiate the firmware loading process but will + * eventually be refused by the ISH firmware after three unsuccessful attempts, indicated by + * returning an error code. The ISHTP driver will stop attempting after three tries. + * Recovery: A platform reset is required to retry firmware loading from the host. + */ + +#define dev_fmt(fmt) "ISH loader: " fmt + +#include <linux/cacheflush.h> +#include <linux/container_of.h> +#include <linux/crc32.h> +#include <linux/dev_printk.h> +#include <linux/dma-mapping.h> +#include <linux/dmi.h> +#include <linux/errno.h> +#include <linux/firmware.h> +#include <linux/gfp_types.h> +#include <linux/math.h> +#include <linux/module.h> +#include <linux/pfn.h> +#include <linux/sprintf.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/wait.h> + +#include "hbm.h" +#include "loader.h" + +/** + * loader_write_message() - Write a message to the ISHTP device + * @dev: The ISHTP device + * @buf: The buffer containing the message + * @len: The length of the message + * + * Return: 0 on success, negative error code on failure + */ +static int loader_write_message(struct ishtp_device *dev, void *buf, int len) +{ + struct ishtp_msg_hdr ishtp_hdr = { + .fw_addr = ISHTP_LOADER_CLIENT_ADDR, + .length = len, + .msg_complete = 1, + }; + + dev->fw_loader_received = false; + + return ishtp_write_message(dev, &ishtp_hdr, buf); +} + +/** + * loader_xfer_cmd() - Transfer a command to the ISHTP device + * @dev: The ISHTP device + * @req: The request buffer + * @req_len: The length of the request + * @resp: The response buffer + * @resp_len: The length of the response + * + * Return: 0 on success, negative error code on failure + */ +static int loader_xfer_cmd(struct ishtp_device *dev, void *req, int req_len, + void *resp, int resp_len) +{ + union loader_msg_header req_hdr; + union loader_msg_header resp_hdr; + struct device *devc = dev->devc; + int rv; + + dev->fw_loader_rx_buf = resp; + dev->fw_loader_rx_size = resp_len; + + rv = loader_write_message(dev, req, req_len); + req_hdr.val32 = le32_to_cpup(req); + + if (rv < 0) { + dev_err(devc, "write cmd %u failed:%d\n", req_hdr.command, rv); + return rv; + } + + /* Wait the ACK */ + wait_event_interruptible_timeout(dev->wait_loader_recvd_msg, dev->fw_loader_received, + ISHTP_LOADER_TIMEOUT); + resp_hdr.val32 = le32_to_cpup(resp); + dev->fw_loader_rx_size = 0; + dev->fw_loader_rx_buf = NULL; + if (!dev->fw_loader_received) { + dev_err(devc, "wait response of cmd %u timeout\n", req_hdr.command); + return -ETIMEDOUT; + } + + if (!resp_hdr.is_response) { + dev_err(devc, "not a response for %u\n", req_hdr.command); + return -EBADMSG; + } + + if (req_hdr.command != resp_hdr.command) { + dev_err(devc, "unexpected cmd response %u:%u\n", req_hdr.command, + resp_hdr.command); + return -EBADMSG; + } + + if (resp_hdr.status) { + dev_err(devc, "cmd %u failed %u\n", req_hdr.command, resp_hdr.status); + return -EIO; + } + + return 0; +} + +/** + * release_dma_bufs() - Release the DMA buffer for transferring firmware fragments + * @dev: The ISHTP device + * @fragment: The ISHTP firmware fragment descriptor + * @dma_bufs: The array of DMA fragment buffers + * @fragment_size: The size of a single DMA fragment + */ +static void release_dma_bufs(struct ishtp_device *dev, + struct loader_xfer_dma_fragment *fragment, + void **dma_bufs, u32 fragment_size) +{ + dma_addr_t dma_addr; + int i; + + for (i = 0; i < FRAGMENT_MAX_NUM; i++) { + if (dma_bufs[i]) { + dma_addr = le64_to_cpu(fragment->fragment_tbl[i].ddr_adrs); + dma_free_coherent(dev->devc, fragment_size, dma_bufs[i], dma_addr); + dma_bufs[i] = NULL; + } + } +} + +/** + * prepare_dma_bufs() - Prepare the DMA buffer for transferring firmware fragments + * @dev: The ISHTP device + * @ish_fw: The ISH firmware + * @fragment: The ISHTP firmware fragment descriptor + * @dma_bufs: The array of DMA fragment buffers + * @fragment_size: The size of a single DMA fragment + * @fragment_count: Number of fragments + * + * Return: 0 on success, negative error code on failure + */ +static int prepare_dma_bufs(struct ishtp_device *dev, + const struct firmware *ish_fw, + struct loader_xfer_dma_fragment *fragment, + void **dma_bufs, u32 fragment_size, u32 fragment_count) +{ + dma_addr_t dma_addr; + u32 offset = 0; + u32 length; + int i; + + for (i = 0; i < fragment_count && offset < ish_fw->size; i++) { + dma_bufs[i] = dma_alloc_coherent(dev->devc, fragment_size, &dma_addr, GFP_KERNEL); + if (!dma_bufs[i]) + return -ENOMEM; + + fragment->fragment_tbl[i].ddr_adrs = cpu_to_le64(dma_addr); + length = clamp(ish_fw->size - offset, 0, fragment_size); + fragment->fragment_tbl[i].length = cpu_to_le32(length); + fragment->fragment_tbl[i].fw_off = cpu_to_le32(offset); + memcpy(dma_bufs[i], ish_fw->data + offset, length); + clflush_cache_range(dma_bufs[i], fragment_size); + + offset += length; + } + + return 0; +} + +#define ISH_FW_FILE_VENDOR_NAME_SKU_FMT "intel/ish/ish_%s_%08x_%08x_%08x.bin" +#define ISH_FW_FILE_VENDOR_SKU_FMT "intel/ish/ish_%s_%08x_%08x.bin" +#define ISH_FW_FILE_VENDOR_NAME_FMT "intel/ish/ish_%s_%08x_%08x.bin" +#define ISH_FW_FILE_VENDOR_FMT "intel/ish/ish_%s_%08x.bin" +#define ISH_FW_FILE_DEFAULT_FMT "intel/ish/ish_%s.bin" + +#define ISH_FW_FILENAME_LEN_MAX 56 + +#define ISH_CRC_INIT (~0u) +#define ISH_CRC_XOROUT (~0u) + +static int _request_ish_firmware(const struct firmware **firmware_p, + const char *name, struct device *dev) +{ + int ret; + + dev_dbg(dev, "Try to load firmware: %s\n", name); + ret = firmware_request_nowarn(firmware_p, name, dev); + if (!ret) + dev_info(dev, "load firmware: %s\n", name); + + return ret; +} + +/** + * request_ish_firmware() - Request and load the ISH firmware. + * @firmware_p: Pointer to the firmware image. + * @dev: Device for which firmware is being requested. + * + * This function attempts to load the Integrated Sensor Hub (ISH) firmware + * for the given device in the following order, prioritizing custom firmware + * with more precise matching patterns: + * + * ish_${fw_generation}_${SYS_VENDOR_CRC32}_$(PRODUCT_NAME_CRC32)_${PRODUCT_SKU_CRC32}.bin + * ish_${fw_generation}_${SYS_VENDOR_CRC32}_${PRODUCT_SKU_CRC32}.bin + * ish_${fw_generation}_${SYS_VENDOR_CRC32}_$(PRODUCT_NAME_CRC32).bin + * ish_${fw_generation}_${SYS_VENDOR_CRC32}.bin + * ish_${fw_generation}.bin + * + * The driver will load the first matching firmware and skip the rest. If no + * matching firmware is found, it will proceed to the next pattern in the + * specified order. If all searches fail, the default Intel firmware, listed + * last in the order above, will be loaded. + * + * The firmware file name is constructed using CRC32 checksums of strings. + * This is done to create a valid file name that does not contain spaces + * or special characters which may be present in the original strings. + * + * The CRC-32 algorithm uses the following parameters: + * Poly: 0x04C11DB7 + * Init: 0xFFFFFFFF + * RefIn: true + * RefOut: true + * XorOut: 0xFFFFFFFF + * + * Return: 0 on success, negative error code on failure. + */ +static int request_ish_firmware(const struct firmware **firmware_p, + struct device *dev) +{ + const char *gen, *sys_vendor, *product_name, *product_sku; + struct ishtp_device *ishtp = dev_get_drvdata(dev); + u32 vendor_crc, name_crc, sku_crc; + char filename[ISH_FW_FILENAME_LEN_MAX]; + int ret; + + gen = ishtp->driver_data->fw_generation; + sys_vendor = dmi_get_system_info(DMI_SYS_VENDOR); + product_name = dmi_get_system_info(DMI_PRODUCT_NAME); + product_sku = dmi_get_system_info(DMI_PRODUCT_SKU); + + if (sys_vendor) + vendor_crc = crc32(ISH_CRC_INIT, sys_vendor, strlen(sys_vendor)) ^ ISH_CRC_XOROUT; + if (product_name) + name_crc = crc32(ISH_CRC_INIT, product_name, strlen(product_name)) ^ ISH_CRC_XOROUT; + if (product_sku) + sku_crc = crc32(ISH_CRC_INIT, product_sku, strlen(product_sku)) ^ ISH_CRC_XOROUT; + + if (sys_vendor && product_name && product_sku) { + snprintf(filename, sizeof(filename), ISH_FW_FILE_VENDOR_NAME_SKU_FMT, gen, + vendor_crc, name_crc, sku_crc); + ret = _request_ish_firmware(firmware_p, filename, dev); + if (!ret) + return 0; + } + + if (sys_vendor && product_sku) { + snprintf(filename, sizeof(filename), ISH_FW_FILE_VENDOR_SKU_FMT, gen, vendor_crc, + sku_crc); + ret = _request_ish_firmware(firmware_p, filename, dev); + if (!ret) + return 0; + } + + if (sys_vendor && product_name) { + snprintf(filename, sizeof(filename), ISH_FW_FILE_VENDOR_NAME_FMT, gen, vendor_crc, + name_crc); + ret = _request_ish_firmware(firmware_p, filename, dev); + if (!ret) + return 0; + } + + if (sys_vendor) { + snprintf(filename, sizeof(filename), ISH_FW_FILE_VENDOR_FMT, gen, vendor_crc); + ret = _request_ish_firmware(firmware_p, filename, dev); + if (!ret) + return 0; + } + + snprintf(filename, sizeof(filename), ISH_FW_FILE_DEFAULT_FMT, gen); + return _request_ish_firmware(firmware_p, filename, dev); +} + +static int copy_manifest(const struct firmware *fw, struct ish_global_manifest *manifest) +{ + u32 offset; + + for (offset = 0; offset + sizeof(*manifest) < fw->size; offset += ISH_MANIFEST_ALIGNMENT) { + memcpy(manifest, fw->data + offset, sizeof(*manifest)); + + if (le32_to_cpu(manifest->sig_fourcc) == ISH_GLOBAL_SIG) + return 0; + } + + return -1; +} + +static void copy_ish_version(struct version_in_manifest *src, struct ish_version *dst) +{ + dst->major = le16_to_cpu(src->major); + dst->minor = le16_to_cpu(src->minor); + dst->hotfix = le16_to_cpu(src->hotfix); + dst->build = le16_to_cpu(src->build); +} + +/** + * ishtp_loader_work() - Load the ISHTP firmware + * @work: The work structure + * + * The ISH Loader attempts to load firmware by sending a series of commands + * to the ISH device. If a command fails to be acknowledged by the ISH device, + * the loader will retry sending the command, up to a maximum of + * ISHTP_LOADER_RETRY_TIMES. + * + * After the maximum number of retries has been reached without success, the + * ISH bootloader will return an error status code and will no longer respond + * to the driver's commands. This behavior indicates that the ISH Loader has + * encountered a critical error during the firmware loading process. + * + * In such a case, where the ISH bootloader is unresponsive after all retries + * have been exhausted, a platform reset is required to restore communication + * with the ISH device and to recover from this error state. + */ +void ishtp_loader_work(struct work_struct *work) +{ + DEFINE_RAW_FLEX(struct loader_xfer_dma_fragment, fragment, fragment_tbl, FRAGMENT_MAX_NUM); + struct ishtp_device *dev = container_of(work, struct ishtp_device, work_fw_loader); + union loader_msg_header query_hdr = { .command = LOADER_CMD_XFER_QUERY, }; + union loader_msg_header start_hdr = { .command = LOADER_CMD_START, }; + union loader_msg_header fragment_hdr = { .command = LOADER_CMD_XFER_FRAGMENT, }; + struct loader_xfer_query query = { .header = cpu_to_le32(query_hdr.val32), }; + struct loader_start start = { .header = cpu_to_le32(start_hdr.val32), }; + union loader_recv_message recv_msg; + struct ish_global_manifest manifest; + const struct firmware *ish_fw; + void *dma_bufs[FRAGMENT_MAX_NUM] = {}; + u32 fragment_size; + u32 fragment_count; + int retry = ISHTP_LOADER_RETRY_TIMES; + int rv; + + rv = request_ish_firmware(&ish_fw, dev->devc); + if (rv < 0) { + dev_err(dev->devc, "request ISH firmware failed:%d\n", rv); + return; + } + + fragment->fragment.header = cpu_to_le32(fragment_hdr.val32); + fragment->fragment.xfer_mode = cpu_to_le32(LOADER_XFER_MODE_DMA); + fragment->fragment.is_last = cpu_to_le32(1); + fragment->fragment.size = cpu_to_le32(ish_fw->size); + /* Calculate the size of a single DMA fragment */ + fragment_size = PFN_ALIGN(DIV_ROUND_UP(ish_fw->size, FRAGMENT_MAX_NUM)); + /* Calculate the count of DMA fragments */ + fragment_count = DIV_ROUND_UP(ish_fw->size, fragment_size); + fragment->fragment_cnt = cpu_to_le32(fragment_count); + + rv = prepare_dma_bufs(dev, ish_fw, fragment, dma_bufs, fragment_size, fragment_count); + if (rv) { + dev_err(dev->devc, "prepare DMA buffer failed.\n"); + goto out; + } + + do { + query.image_size = cpu_to_le32(ish_fw->size); + rv = loader_xfer_cmd(dev, &query, sizeof(query), recv_msg.raw_data, + sizeof(struct loader_xfer_query_ack)); + if (rv) + continue; /* try again if failed */ + + dev_dbg(dev->devc, "ISH Bootloader Version %u.%u.%u.%u\n", + recv_msg.query_ack.version_major, + recv_msg.query_ack.version_minor, + recv_msg.query_ack.version_hotfix, + recv_msg.query_ack.version_build); + + rv = loader_xfer_cmd(dev, fragment, + struct_size(fragment, fragment_tbl, fragment_count), + recv_msg.raw_data, sizeof(struct loader_xfer_fragment_ack)); + if (rv) + continue; /* try again if failed */ + + rv = loader_xfer_cmd(dev, &start, sizeof(start), recv_msg.raw_data, + sizeof(struct loader_start_ack)); + if (rv) + continue; /* try again if failed */ + + dev_info(dev->devc, "firmware loaded. size:%zu\n", ish_fw->size); + if (!copy_manifest(ish_fw, &manifest)) { + copy_ish_version(&manifest.base_ver, &dev->base_ver); + copy_ish_version(&manifest.prj_ver, &dev->prj_ver); + dev_info(dev->devc, "FW base version: %u.%u.%u.%u\n", + dev->base_ver.major, dev->base_ver.minor, + dev->base_ver.hotfix, dev->base_ver.build); + dev_info(dev->devc, "FW project version: %u.%u.%u.%u\n", + dev->prj_ver.major, dev->prj_ver.minor, + dev->prj_ver.hotfix, dev->prj_ver.build); + } + break; + } while (--retry); + +out: + release_dma_bufs(dev, fragment, dma_bufs, fragment_size); + release_firmware(ish_fw); +} diff --git a/drivers/hid/intel-ish-hid/ishtp/loader.h b/drivers/hid/intel-ish-hid/ishtp/loader.h new file mode 100644 index 000000000000..4dda038b4947 --- /dev/null +++ b/drivers/hid/intel-ish-hid/ishtp/loader.h @@ -0,0 +1,265 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * ISHTP firmware loader header + * + * Copyright (c) 2024, Intel Corporation. + */ + +#ifndef _ISHTP_LOADER_H_ +#define _ISHTP_LOADER_H_ + +#include <linux/bits.h> +#include <linux/jiffies.h> +#include <linux/sizes.h> +#include <linux/types.h> + +#include "ishtp-dev.h" + +struct work_struct; + +#define LOADER_MSG_SIZE \ + (IPC_PAYLOAD_SIZE - sizeof(struct ishtp_msg_hdr)) + +/* + * ISHTP firmware loader protocol definition + */ +#define LOADER_CMD_XFER_QUERY 0 /* SW -> FW */ +#define LOADER_CMD_XFER_FRAGMENT 1 /* SW -> FW */ +#define LOADER_CMD_START 2 /* SW -> FW */ + +/* Only support DMA mode */ +#define LOADER_XFER_MODE_DMA BIT(0) + +/** + * union loader_msg_header - ISHTP firmware loader message header + * @command: Command type + * @is_response: Indicates if the message is a response + * @has_next: Indicates if there is a next message + * @reserved: Reserved for future use + * @status: Status of the message + * @val32: entire header as a 32-bit value + */ +union loader_msg_header { + struct { + __u32 command:7; + __u32 is_response:1; + __u32 has_next:1; + __u32 reserved:15; + __u32 status:8; + }; + __u32 val32; +}; + +/** + * struct loader_xfer_query - ISHTP firmware loader transfer query packet + * @header: Header of the message + * @image_size: Size of the image + */ +struct loader_xfer_query { + __le32 header; + __le32 image_size; +}; + +/** + * struct loader_version - ISHTP firmware loader version + * @value: Value of the version + * @major: Major version + * @minor: Minor version + * @hotfix: Hotfix version + * @build: Build version + */ +struct loader_version { + union { + __le32 value; + struct { + __u8 major; + __u8 minor; + __u8 hotfix; + __u8 build; + }; + }; +}; + +/** + * struct loader_capability - ISHTP firmware loader capability + * @max_fw_image_size: Maximum firmware image size + * @support_mode: Support mode + * @reserved: Reserved for future use + * @platform: Platform + * @max_dma_buf_size: Maximum DMA buffer size, multiples of 4096 + */ +struct loader_capability { + __le32 max_fw_image_size; + __le16 support_mode; + __u8 reserved; + __u8 platform; + __le32 max_dma_buf_size; +}; + +/** + * struct loader_xfer_query_ack - ISHTP firmware loader transfer query acknowledgment + * @header: Header of the message + * @version_major: ISH Major version + * @version_minor: ISH Minor version + * @version_hotfix: ISH Hotfix version + * @version_build: ISH Build version + * @protocol_version: Protocol version + * @loader_version: Loader version + * @capability: Loader capability + */ +struct loader_xfer_query_ack { + __le32 header; + __le16 version_major; + __le16 version_minor; + __le16 version_hotfix; + __le16 version_build; + __le32 protocol_version; + struct loader_version loader_version; + struct loader_capability capability; +}; + +/** + * struct loader_xfer_fragment - ISHTP firmware loader transfer fragment + * @header: Header of the message + * @xfer_mode: Transfer mode + * @offset: Offset + * @size: Size + * @is_last: Is last + */ +struct loader_xfer_fragment { + __le32 header; + __le32 xfer_mode; + __le32 offset; + __le32 size; + __le32 is_last; +}; + +/** + * struct loader_xfer_fragment_ack - ISHTP firmware loader transfer fragment acknowledgment + * @header: Header of the message + */ +struct loader_xfer_fragment_ack { + __le32 header; +}; + +/** + * struct fragment_dscrpt - ISHTP firmware loader fragment descriptor + * @ddr_adrs: The address in host DDR + * @fw_off: The offset of the fragment in the fw image + * @length: The length of the fragment + */ +struct fragment_dscrpt { + __le64 ddr_adrs; + __le32 fw_off; + __le32 length; +}; + +#define FRAGMENT_MAX_NUM \ + ((LOADER_MSG_SIZE - sizeof(struct loader_xfer_dma_fragment)) / \ + sizeof(struct fragment_dscrpt)) + +/** + * struct loader_xfer_dma_fragment - ISHTP firmware loader transfer DMA fragment + * @fragment: Fragment + * @fragment_cnt: How many descriptors in the fragment_tbl + * @fragment_tbl: Fragment table + */ +struct loader_xfer_dma_fragment { + struct loader_xfer_fragment fragment; + __le32 fragment_cnt; + struct fragment_dscrpt fragment_tbl[] __counted_by(fragment_cnt); +}; + +/** + * struct loader_start - ISHTP firmware loader start + * @header: Header of the message + */ +struct loader_start { + __le32 header; +}; + +/** + * struct loader_start_ack - ISHTP firmware loader start acknowledgment + * @header: Header of the message + */ +struct loader_start_ack { + __le32 header; +}; + +union loader_recv_message { + __le32 header; + struct loader_xfer_query_ack query_ack; + struct loader_xfer_fragment_ack fragment_ack; + struct loader_start_ack start_ack; + __u8 raw_data[LOADER_MSG_SIZE]; +}; + +/* + * ISHTP firmware loader internal use + */ +/* ISHTP firmware loader command timeout */ +#define ISHTP_LOADER_TIMEOUT msecs_to_jiffies(100) + +/* ISHTP firmware loader retry times */ +#define ISHTP_LOADER_RETRY_TIMES 3 + +/** + * struct ish_firmware_variant - ISH firmware variant + * @device: PCI Device ID + * @filename: The firmware file name + */ +struct ish_firmware_variant { + unsigned short device; + const char *filename; +}; + +/* + * ISHTP firmware loader API for ISHTP hbm + */ + +/* ISHTP capability bit for firmware loader */ +#define ISHTP_SUPPORT_CAP_LOADER BIT(4) + +/* Firmware loader address */ +#define ISHTP_LOADER_CLIENT_ADDR 16 + +/** + * ishtp_loader_work - The work function to start the firmware loading process + * @work: The work structure + */ +void ishtp_loader_work(struct work_struct *work); + +/* ISH Manifest alignment in binary is 4KB aligned */ +#define ISH_MANIFEST_ALIGNMENT SZ_4K + +/* Signature for ISH global manifest */ +#define ISH_GLOBAL_SIG 0x47485349 /* FourCC 'I', 'S', 'H', 'G' */ + +struct version_in_manifest { + __le16 major; + __le16 minor; + __le16 hotfix; + __le16 build; +}; + +/** + * struct ish_global_manifest - global manifest for ISH + * @sig_fourcc: Signature FourCC, should be 'I', 'S', 'H', 'G'. + * @len: Length of the manifest. + * @header_version: Version of the manifest header. + * @flags: Flags for additional information. + * @base_ver: Base version of Intel's released firmware. + * @reserved: Reserved space for future use. + * @prj_ver: Vendor-customized project version. + */ +struct ish_global_manifest { + __le32 sig_fourcc; + __le32 len; + __le32 header_version; + __le32 flags; + struct version_in_manifest base_ver; + __le32 reserved[13]; + struct version_in_manifest prj_ver; +}; + +#endif /* _ISHTP_LOADER_H_ */ |