diff options
Diffstat (limited to 'drivers/hid/intel-ish-hid')
-rw-r--r-- | drivers/hid/intel-ish-hid/Kconfig | 1 | ||||
-rw-r--r-- | drivers/hid/intel-ish-hid/Makefile | 3 | ||||
-rw-r--r-- | drivers/hid/intel-ish-hid/ipc/hw-ish.h | 47 | ||||
-rw-r--r-- | drivers/hid/intel-ish-hid/ipc/ipc.c | 36 | ||||
-rw-r--r-- | drivers/hid/intel-ish-hid/ipc/pci-ish.c | 136 | ||||
-rw-r--r-- | drivers/hid/intel-ish-hid/ishtp-fw-loader.c | 4 | ||||
-rw-r--r-- | drivers/hid/intel-ish-hid/ishtp-hid-client.c | 27 | ||||
-rw-r--r-- | drivers/hid/intel-ish-hid/ishtp-hid.c | 4 | ||||
-rw-r--r-- | drivers/hid/intel-ish-hid/ishtp-hid.h | 11 | ||||
-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 |
19 files changed, 953 insertions, 167 deletions
diff --git a/drivers/hid/intel-ish-hid/Kconfig b/drivers/hid/intel-ish-hid/Kconfig index 253dc10d35ef..568c8688784e 100644 --- a/drivers/hid/intel-ish-hid/Kconfig +++ b/drivers/hid/intel-ish-hid/Kconfig @@ -6,7 +6,6 @@ config INTEL_ISH_HID tristate "Intel Integrated Sensor Hub" default n depends on X86 - depends on HID help The Integrated Sensor Hub (ISH) enables the ability to offload sensor polling and algorithm processing to a dedicated low power diff --git a/drivers/hid/intel-ish-hid/Makefile b/drivers/hid/intel-ish-hid/Makefile index f0a82b1c7cb9..e1e062e4b542 100644 --- a/drivers/hid/intel-ish-hid/Makefile +++ b/drivers/hid/intel-ish-hid/Makefile @@ -11,6 +11,7 @@ intel-ishtp-objs += ishtp/client.o intel-ishtp-objs += ishtp/bus.o intel-ishtp-objs += ishtp/dma-if.o intel-ishtp-objs += ishtp/client-buffers.o +intel-ishtp-objs += ishtp/loader.o obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-ipc.o intel-ish-ipc-objs := ipc/ipc.o @@ -23,4 +24,4 @@ intel-ishtp-hid-objs += ishtp-hid-client.o obj-$(CONFIG_INTEL_ISH_FIRMWARE_DOWNLOADER) += intel-ishtp-loader.o intel-ishtp-loader-objs += ishtp-fw-loader.o -ccflags-y += -I $(srctree)/$(src)/ishtp +ccflags-y += -I $(src)/ishtp diff --git a/drivers/hid/intel-ish-hid/ipc/hw-ish.h b/drivers/hid/intel-ish-hid/ipc/hw-ish.h index f89b300417d7..07e90d51f073 100644 --- a/drivers/hid/intel-ish-hid/ipc/hw-ish.h +++ b/drivers/hid/intel-ish-hid/ipc/hw-ish.h @@ -13,28 +13,31 @@ #include "hw-ish-regs.h" #include "ishtp-dev.h" -#define CHV_DEVICE_ID 0x22D8 -#define BXT_Ax_DEVICE_ID 0x0AA2 -#define BXT_Bx_DEVICE_ID 0x1AA2 -#define APL_Ax_DEVICE_ID 0x5AA2 -#define SPT_Ax_DEVICE_ID 0x9D35 -#define CNL_Ax_DEVICE_ID 0x9DFC -#define GLK_Ax_DEVICE_ID 0x31A2 -#define CNL_H_DEVICE_ID 0xA37C -#define ICL_MOBILE_DEVICE_ID 0x34FC -#define SPT_H_DEVICE_ID 0xA135 -#define CML_LP_DEVICE_ID 0x02FC -#define CMP_H_DEVICE_ID 0x06FC -#define EHL_Ax_DEVICE_ID 0x4BB3 -#define TGL_LP_DEVICE_ID 0xA0FC -#define TGL_H_DEVICE_ID 0x43FC -#define ADL_S_DEVICE_ID 0x7AF8 -#define ADL_P_DEVICE_ID 0x51FC -#define ADL_N_DEVICE_ID 0x54FC -#define RPL_S_DEVICE_ID 0x7A78 -#define MTL_P_DEVICE_ID 0x7E45 -#define ARL_H_DEVICE_ID 0x7745 -#define ARL_S_DEVICE_ID 0x7F78 +#define PCI_DEVICE_ID_INTEL_ISH_CHV 0x22D8 +#define PCI_DEVICE_ID_INTEL_ISH_BXT_Ax 0x0AA2 +#define PCI_DEVICE_ID_INTEL_ISH_BXT_Bx 0x1AA2 +#define PCI_DEVICE_ID_INTEL_ISH_APL_Ax 0x5AA2 +#define PCI_DEVICE_ID_INTEL_ISH_SPT_Ax 0x9D35 +#define PCI_DEVICE_ID_INTEL_ISH_CNL_Ax 0x9DFC +#define PCI_DEVICE_ID_INTEL_ISH_GLK_Ax 0x31A2 +#define PCI_DEVICE_ID_INTEL_ISH_CNL_H 0xA37C +#define PCI_DEVICE_ID_INTEL_ISH_ICL_MOBILE 0x34FC +#define PCI_DEVICE_ID_INTEL_ISH_SPT_H 0xA135 +#define PCI_DEVICE_ID_INTEL_ISH_CML_LP 0x02FC +#define PCI_DEVICE_ID_INTEL_ISH_CMP_H 0x06FC +#define PCI_DEVICE_ID_INTEL_ISH_EHL_Ax 0x4BB3 +#define PCI_DEVICE_ID_INTEL_ISH_TGL_LP 0xA0FC +#define PCI_DEVICE_ID_INTEL_ISH_TGL_H 0x43FC +#define PCI_DEVICE_ID_INTEL_ISH_ADL_S 0x7AF8 +#define PCI_DEVICE_ID_INTEL_ISH_ADL_P 0x51FC +#define PCI_DEVICE_ID_INTEL_ISH_ADL_N 0x54FC +#define PCI_DEVICE_ID_INTEL_ISH_RPL_S 0x7A78 +#define PCI_DEVICE_ID_INTEL_ISH_MTL_P 0x7E45 +#define PCI_DEVICE_ID_INTEL_ISH_ARL_H 0x7745 +#define PCI_DEVICE_ID_INTEL_ISH_ARL_S 0x7F78 +#define PCI_DEVICE_ID_INTEL_ISH_LNL_M 0xA845 +#define PCI_DEVICE_ID_INTEL_ISH_PTL_H 0xE345 +#define PCI_DEVICE_ID_INTEL_ISH_PTL_P 0xE445 #define REVISION_ID_CHT_A0 0x6 #define REVISION_ID_CHT_Ax_SI 0x0 diff --git a/drivers/hid/intel-ish-hid/ipc/ipc.c b/drivers/hid/intel-ish-hid/ipc/ipc.c index dd5fc60874ba..4c861119e97a 100644 --- a/drivers/hid/intel-ish-hid/ipc/ipc.c +++ b/drivers/hid/intel-ish-hid/ipc/ipc.c @@ -78,7 +78,7 @@ static bool check_generated_interrupt(struct ishtp_device *dev) bool interrupt_generated = true; uint32_t pisr_val = 0; - if (dev->pdev->device == CHV_DEVICE_ID) { + if (dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_CHV) { pisr_val = ish_reg_read(dev, IPC_REG_PISR_CHV_AB); interrupt_generated = IPC_INT_FROM_ISH_TO_HOST_CHV_AB(pisr_val); @@ -117,7 +117,7 @@ static bool ish_is_input_ready(struct ishtp_device *dev) */ static void set_host_ready(struct ishtp_device *dev) { - if (dev->pdev->device == CHV_DEVICE_ID) { + if (dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_CHV) { if (dev->pdev->revision == REVISION_ID_CHT_A0 || (dev->pdev->revision & REVISION_ID_SI_MASK) == REVISION_ID_CHT_Ax_SI) @@ -517,6 +517,10 @@ static int ish_fw_reset_handler(struct ishtp_device *dev) /* ISH FW is dead */ if (!ish_is_input_ready(dev)) return -EPIPE; + + /* Send clock sync at once after reset */ + ishtp_dev->prev_sync = 0; + /* * Set HOST2ISH.ILUP. Apparently we need this BEFORE sending * RESET_NOTIFY_ACK - FW will be checking for it @@ -546,11 +550,11 @@ static int ish_fw_reset_handler(struct ishtp_device *dev) /** * fw_reset_work_fn() - FW reset worker function - * @unused: not used + * @work: Work item * * Call ish_fw_reset_handler to complete FW reset */ -static void fw_reset_work_fn(struct work_struct *unused) +static void fw_reset_work_fn(struct work_struct *work) { int rv; @@ -562,7 +566,8 @@ static void fw_reset_work_fn(struct work_struct *unused) wake_up_interruptible(&ishtp_dev->wait_hw_ready); /* ISHTP notification in IPC_RESET sequence completion */ - ishtp_reset_compl_handler(ishtp_dev); + if (!work_pending(work)) + ishtp_reset_compl_handler(ishtp_dev); } else dev_err(ishtp_dev->devc, "[ishtp-ish]: FW reset failed (%d)\n", rv); @@ -576,15 +581,14 @@ static void fw_reset_work_fn(struct work_struct *unused) */ static void _ish_sync_fw_clock(struct ishtp_device *dev) { - static unsigned long prev_sync; - uint64_t usec; + struct ipc_time_update_msg time = {}; - if (prev_sync && time_before(jiffies, prev_sync + 20 * HZ)) + if (dev->prev_sync && time_before(jiffies, dev->prev_sync + 20 * HZ)) return; - prev_sync = jiffies; - usec = ktime_to_us(ktime_get_boottime()); - ipc_send_mng_msg(dev, MNG_SYNC_FW_CLOCK, &usec, sizeof(uint64_t)); + dev->prev_sync = jiffies; + /* The fields of time would be updated while sending message */ + ipc_send_mng_msg(dev, MNG_SYNC_FW_CLOCK, &time, sizeof(time)); } /** @@ -909,11 +913,11 @@ static uint32_t ish_ipc_get_header(struct ishtp_device *dev, int length, */ static bool _dma_no_cache_snooping(struct ishtp_device *dev) { - return (dev->pdev->device == EHL_Ax_DEVICE_ID || - dev->pdev->device == TGL_LP_DEVICE_ID || - dev->pdev->device == TGL_H_DEVICE_ID || - dev->pdev->device == ADL_S_DEVICE_ID || - dev->pdev->device == ADL_P_DEVICE_ID); + return (dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_EHL_Ax || + dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_TGL_LP || + dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_TGL_H || + dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_ADL_S || + dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_ADL_P); } static const struct ishtp_hw_ops ish_hw_ops = { diff --git a/drivers/hid/intel-ish-hid/ipc/pci-ish.c b/drivers/hid/intel-ish-hid/ipc/pci-ish.c index 56bd4f02f319..ff0fc8010072 100644 --- a/drivers/hid/intel-ish-hid/ipc/pci-ish.c +++ b/drivers/hid/intel-ish-hid/ipc/pci-ish.c @@ -23,30 +23,54 @@ #include "ishtp-dev.h" #include "hw-ish.h" +enum ishtp_driver_data_index { + ISHTP_DRIVER_DATA_NONE, + ISHTP_DRIVER_DATA_LNL_M, + ISHTP_DRIVER_DATA_PTL, +}; + +#define ISH_FW_GEN_LNL_M "lnlm" +#define ISH_FW_GEN_PTL "ptl" + +#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] = { + .fw_generation = ISH_FW_GEN_LNL_M, + }, + [ISHTP_DRIVER_DATA_PTL] = { + .fw_generation = ISH_FW_GEN_PTL, + }, +}; + static const struct pci_device_id ish_pci_tbl[] = { - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, CHV_DEVICE_ID)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, BXT_Ax_DEVICE_ID)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, BXT_Bx_DEVICE_ID)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, APL_Ax_DEVICE_ID)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, SPT_Ax_DEVICE_ID)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, CNL_Ax_DEVICE_ID)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, GLK_Ax_DEVICE_ID)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, CNL_H_DEVICE_ID)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, ICL_MOBILE_DEVICE_ID)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, SPT_H_DEVICE_ID)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, CML_LP_DEVICE_ID)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, CMP_H_DEVICE_ID)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, EHL_Ax_DEVICE_ID)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, TGL_LP_DEVICE_ID)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, TGL_H_DEVICE_ID)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, ADL_S_DEVICE_ID)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, ADL_P_DEVICE_ID)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, ADL_N_DEVICE_ID)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, RPL_S_DEVICE_ID)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MTL_P_DEVICE_ID)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, ARL_H_DEVICE_ID)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, ARL_S_DEVICE_ID)}, - {0, } + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_CHV)}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_BXT_Ax)}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_BXT_Bx)}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_APL_Ax)}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_SPT_Ax)}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_CNL_Ax)}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_GLK_Ax)}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_CNL_H)}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_ICL_MOBILE)}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_SPT_H)}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_CML_LP)}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_CMP_H)}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_EHL_Ax)}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_TGL_LP)}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_TGL_H)}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_ADL_S)}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_ADL_P)}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_ADL_N)}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_RPL_S)}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_MTL_P)}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_ARL_H)}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_ARL_S)}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_LNL_M), .driver_data = ISHTP_DRIVER_DATA_LNL_M}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_PTL_H), .driver_data = ISHTP_DRIVER_DATA_PTL}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_PTL_P), .driver_data = ISHTP_DRIVER_DATA_PTL}, + {} }; MODULE_DEVICE_TABLE(pci, ish_pci_tbl); @@ -105,19 +129,19 @@ static int ish_init(struct ishtp_device *dev) static const struct pci_device_id ish_invalid_pci_ids[] = { /* Mehlow platform special pci ids */ - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xA309)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xA30A)}, + {PCI_VDEVICE(INTEL, 0xA309)}, + {PCI_VDEVICE(INTEL, 0xA30A)}, {} }; static inline bool ish_should_enter_d0i3(struct pci_dev *pdev) { - return !pm_suspend_via_firmware() || pdev->device == CHV_DEVICE_ID; + return !pm_suspend_via_firmware() || pdev->device == PCI_DEVICE_ID_INTEL_ISH_CHV; } static inline bool ish_should_leave_d0i3(struct pci_dev *pdev) { - return !pm_resume_via_firmware() || pdev->device == CHV_DEVICE_ID; + return !pm_resume_via_firmware() || pdev->device == PCI_DEVICE_ID_INTEL_ISH_CHV; } /** @@ -166,6 +190,7 @@ static int ish_probe(struct pci_dev *pdev, const struct pci_device_id *ent) } hw = to_ish_hw(ishtp); ishtp->print_log = ish_event_tracer; + ishtp->driver_data = &ishtp_driver_data[ent->driver_data]; /* mapping IO device memory */ hw->mem_addr = pcim_iomap_table(pdev)[0]; @@ -173,6 +198,11 @@ static int ish_probe(struct pci_dev *pdev, const struct pci_device_id *ent) /* request and enable interrupt */ ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_ALL_TYPES); + if (ret < 0) { + dev_err(dev, "ISH: Failed to allocate IRQ vectors\n"); + return ret; + } + if (!pdev->msi_enabled && !pdev->msix_enabled) irq_flag = IRQF_SHARED; @@ -189,7 +219,7 @@ static int ish_probe(struct pci_dev *pdev, const struct pci_device_id *ent) init_waitqueue_head(&ishtp->resume_wait); /* Enable PME for EHL */ - if (pdev->device == EHL_Ax_DEVICE_ID) + if (pdev->device == PCI_DEVICE_ID_INTEL_ISH_EHL_Ax) device_init_wakeup(dev, true); ret = ish_init(ishtp); @@ -222,7 +252,7 @@ static void ish_remove(struct pci_dev *pdev) */ static void ish_shutdown(struct pci_dev *pdev) { - if (pdev->device == EHL_Ax_DEVICE_ID) + if (pdev->device == PCI_DEVICE_ID_INTEL_ISH_EHL_Ax) pci_prepare_to_sleep(pdev); } @@ -358,6 +388,50 @@ static int __maybe_unused ish_resume(struct device *device) static SIMPLE_DEV_PM_OPS(ish_pm_ops, ish_suspend, ish_resume); +static ssize_t base_version_show(struct device *cdev, + struct device_attribute *attr, char *buf) +{ + struct ishtp_device *dev = dev_get_drvdata(cdev); + + return sysfs_emit(buf, "%u.%u.%u.%u\n", dev->base_ver.major, + dev->base_ver.minor, dev->base_ver.hotfix, + dev->base_ver.build); +} +static DEVICE_ATTR_RO(base_version); + +static ssize_t project_version_show(struct device *cdev, + struct device_attribute *attr, char *buf) +{ + struct ishtp_device *dev = dev_get_drvdata(cdev); + + return sysfs_emit(buf, "%u.%u.%u.%u\n", dev->prj_ver.major, + dev->prj_ver.minor, dev->prj_ver.hotfix, + dev->prj_ver.build); +} +static DEVICE_ATTR_RO(project_version); + +static struct attribute *ish_firmware_attrs[] = { + &dev_attr_base_version.attr, + &dev_attr_project_version.attr, + NULL +}; + +static umode_t firmware_is_visible(struct kobject *kobj, struct attribute *attr, + int i) +{ + struct ishtp_device *dev = dev_get_drvdata(kobj_to_dev(kobj)); + + return dev->driver_data->fw_generation ? attr->mode : 0; +} + +static const struct attribute_group ish_firmware_group = { + .name = "firmware", + .attrs = ish_firmware_attrs, + .is_visible = firmware_is_visible, +}; + +__ATTRIBUTE_GROUPS(ish_firmware); + static struct pci_driver ish_driver = { .name = KBUILD_MODNAME, .id_table = ish_pci_tbl, @@ -365,6 +439,7 @@ static struct pci_driver ish_driver = { .remove = ish_remove, .shutdown = ish_shutdown, .driver.pm = &ish_pm_ops, + .dev_groups = ish_firmware_groups, }; module_pci_driver(ish_driver); @@ -376,3 +451,6 @@ MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>"); 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-fw-loader.c b/drivers/hid/intel-ish-hid/ishtp-fw-loader.c index e157863a8b25..f4a671d6386c 100644 --- a/drivers/hid/intel-ish-hid/ishtp-fw-loader.c +++ b/drivers/hid/intel-ish-hid/ishtp-fw-loader.c @@ -635,7 +635,7 @@ static int ish_fw_xfer_direct_dma(struct ishtp_cl_data *client_data, const struct firmware *fw, const struct shim_fw_info fw_info) { - int rv; + int rv = 0; void *dma_buf; dma_addr_t dma_buf_phy; u32 fragment_offset, fragment_size, payload_max_size; @@ -793,7 +793,7 @@ static int load_fw_from_host(struct ishtp_cl_data *client_data) if (rv < 0) goto end_err_fw_release; - /* Step 3: Start ISH main firmware exeuction */ + /* Step 3: Start ISH main firmware execution */ rv = ish_fw_start(client_data); if (rv < 0) diff --git a/drivers/hid/intel-ish-hid/ishtp-hid-client.c b/drivers/hid/intel-ish-hid/ishtp-hid-client.c index fbd4f8ea1951..6550ad5bfbb5 100644 --- a/drivers/hid/intel-ish-hid/ishtp-hid-client.c +++ b/drivers/hid/intel-ish-hid/ishtp-hid-client.c @@ -70,10 +70,10 @@ static void process_recv(struct ishtp_cl *hid_ishtp_cl, void *recv_buf, unsigned char *payload; struct device_info *dev_info; int i, j; - size_t payload_len, total_len, cur_pos, raw_len; + size_t payload_len, total_len, cur_pos, raw_len, msg_len; int report_type; struct report_list *reports_list; - char *reports; + struct report *report; size_t report_len; struct ishtp_cl_data *client_data = ishtp_get_client_data(hid_ishtp_cl); int curr_hid_dev = client_data->cur_hid_dev; @@ -280,14 +280,13 @@ do_get_report: case HOSTIF_PUBLISH_INPUT_REPORT_LIST: report_type = HID_INPUT_REPORT; reports_list = (struct report_list *)payload; - reports = (char *)reports_list->reports; + report = reports_list->reports; for (j = 0; j < reports_list->num_of_reports; j++) { - recv_msg = (struct hostif_msg *)(reports + - sizeof(uint16_t)); - report_len = *(uint16_t *)reports; - payload = reports + sizeof(uint16_t) + - sizeof(struct hostif_msg_hdr); + recv_msg = container_of(&report->msg, + struct hostif_msg, hdr); + report_len = report->size; + payload = recv_msg->payload; payload_len = report_len - sizeof(struct hostif_msg_hdr); @@ -304,7 +303,7 @@ do_get_report: 0); } - reports += sizeof(uint16_t) + report_len; + report += sizeof(*report) + payload_len; } break; default: @@ -316,12 +315,12 @@ do_get_report: } - if (!cur_pos && cur_pos + payload_len + - sizeof(struct hostif_msg) < total_len) + msg_len = payload_len + sizeof(struct hostif_msg); + if (!cur_pos && cur_pos + msg_len < total_len) ++client_data->multi_packet_cnt; - cur_pos += payload_len + sizeof(struct hostif_msg); - payload += payload_len + sizeof(struct hostif_msg); + cur_pos += msg_len; + payload += msg_len; } while (cur_pos < total_len); } @@ -833,9 +832,9 @@ static void hid_ishtp_cl_remove(struct ishtp_cl_device *cl_device) hid_ishtp_cl); dev_dbg(ishtp_device(cl_device), "%s\n", __func__); - hid_ishtp_cl_deinit(hid_ishtp_cl); ishtp_put_device(cl_device); ishtp_hid_remove(client_data); + hid_ishtp_cl_deinit(hid_ishtp_cl); hid_ishtp_cl = NULL; diff --git a/drivers/hid/intel-ish-hid/ishtp-hid.c b/drivers/hid/intel-ish-hid/ishtp-hid.c index 00c6f0ebf356..be2c62fc8251 100644 --- a/drivers/hid/intel-ish-hid/ishtp-hid.c +++ b/drivers/hid/intel-ish-hid/ishtp-hid.c @@ -261,12 +261,14 @@ err_hid_data: */ void ishtp_hid_remove(struct ishtp_cl_data *client_data) { + void *data; int i; for (i = 0; i < client_data->num_hid_devices; ++i) { if (client_data->hid_sensor_hubs[i]) { - kfree(client_data->hid_sensor_hubs[i]->driver_data); + data = client_data->hid_sensor_hubs[i]->driver_data; hid_destroy_device(client_data->hid_sensor_hubs[i]); + kfree(data); client_data->hid_sensor_hubs[i] = NULL; } } diff --git a/drivers/hid/intel-ish-hid/ishtp-hid.h b/drivers/hid/intel-ish-hid/ishtp-hid.h index 35dddc5015b3..2bc19e8ba13e 100644 --- a/drivers/hid/intel-ish-hid/ishtp-hid.h +++ b/drivers/hid/intel-ish-hid/ishtp-hid.h @@ -31,6 +31,7 @@ struct hostif_msg_hdr { struct hostif_msg { struct hostif_msg_hdr hdr; + uint8_t payload[]; } __packed; struct hostif_msg_to_sensor { @@ -52,15 +53,17 @@ struct ishtp_version { uint16_t build; } __packed; +struct report { + uint16_t size; + struct hostif_msg_hdr msg; +} __packed; + /* struct for ISHTP aggregated input data */ struct report_list { uint16_t total_size; uint8_t num_of_reports; uint8_t flags; - struct { - uint16_t size_of_report; - uint8_t report[1]; - } __packed reports[1]; + struct report reports[]; } __packed; /* HOSTIF commands */ 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_ */ |