diff mbox series

[3/3] hid: intel-ish-hid: Add support for vendor customized firmware loading

Message ID 20240813034736.812475-4-lixu.zhang@intel.com
State Superseded
Headers show
Series hid: intel-ish-hid: Add support for vendor customized firmware loading | expand

Commit Message

Zhang Lixu Aug. 13, 2024, 3:47 a.m. UTC
Enhance the firmware loader to support the loading of vendor-specific
customized firmware for the Intel Integrated Sensor Hub (ISH). The
loader now constructs firmware file names based on the DMI_SYS_VENDOR,
DMI_PRODUCT_NAME, and DMI_PRODUCT_SKU information in Desktop Management
Interface (DMI). The loader will attempt to load the firmware files
following a specific naming convention in sequence. If successful, it
will skip the remaining files.

For more details, please refer to Documentation/hid/intel-ish-hid.rst.

Signed-off-by: Zhang Lixu <lixu.zhang@intel.com>
Acked-by: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
---
 drivers/hid/intel-ish-hid/ipc/pci-ish.c  |   2 +
 drivers/hid/intel-ish-hid/ishtp/loader.c | 104 ++++++++++++++++++++++-
 2 files changed, 103 insertions(+), 3 deletions(-)
diff mbox series

Patch

diff --git a/drivers/hid/intel-ish-hid/ipc/pci-ish.c b/drivers/hid/intel-ish-hid/ipc/pci-ish.c
index f20463082dc4..aae0d965b47b 100644
--- a/drivers/hid/intel-ish-hid/ipc/pci-ish.c
+++ b/drivers/hid/intel-ish-hid/ipc/pci-ish.c
@@ -31,6 +31,7 @@  enum ishtp_driver_data_index {
 #define ISH_FW_GEN_LNL_M "lnlm"
 
 #define ISH_FIRMWARE_PATH(gen) "intel/ish/ish_" gen ".bin"
+#define ISH_FIRMWARE_PATH_ALL "intel/ish/ish_*.bin"
 
 static struct ishtp_driver_data ishtp_driver_data[] = {
 	[ISHTP_DRIVER_DATA_LNL_M] = {
@@ -400,3 +401,4 @@  MODULE_DESCRIPTION("Intel(R) Integrated Sensor Hub PCI Device Driver");
 MODULE_LICENSE("GPL");
 
 MODULE_FIRMWARE(ISH_FIRMWARE_PATH(ISH_FW_GEN_LNL_M));
+MODULE_FIRMWARE(ISH_FIRMWARE_PATH_ALL);
diff --git a/drivers/hid/intel-ish-hid/ishtp/loader.c b/drivers/hid/intel-ish-hid/ishtp/loader.c
index b4b15279c9c4..11f1a08e17f4 100644
--- a/drivers/hid/intel-ish-hid/ishtp/loader.c
+++ b/drivers/hid/intel-ish-hid/ishtp/loader.c
@@ -35,8 +35,10 @@ 
 
 #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>
@@ -193,21 +195,117 @@  static int prepare_dma_bufs(struct ishtp_device *dev,
 	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_DEFALUT_FMT "intel/ish/ish_%s.bin"
 
 #define ISH_FW_FILENAME_LEN_MAX 56
 
+#define ISH_CRC_INIT (~0u)
+#define ISH_CRC_XOROUT (~0u)
+
+static inline 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);
-	const char *gen;
+	u32 vendor_crc, name_crc, sku_crc;
 	char filename[ISH_FW_FILENAME_LEN_MAX];
+	int ret;
 
 	gen = ishtp->driver_data->fw_generation;
-	snprintf(filename, sizeof(filename), ISH_FW_FILE_DEFALUT_FMT, gen);
+	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;
+	}
 
-	return request_firmware(firmware_p, filename, dev);
+	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_DEFALUT_FMT, gen);
+	return _request_ish_firmware(firmware_p, filename, dev);
 }
 
 /**