Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,33 @@
# SPDX-License-Identifier: GPL-2.0-only
menu "Intel ISH HID support"
depends on (X86_64 || COMPILE_TEST) && PCI
config INTEL_ISH_HID
tristate "Intel Integrated Sensor Hub"
default n
depends on X86
help
The Integrated Sensor Hub (ISH) enables the ability to offload
sensor polling and algorithm processing to a dedicated low power
processor in the chipset. This allows the core processor to go into
low power modes more often, resulting in the increased battery life.
The current processors that support ISH are: Cherrytrail, Skylake,
Broxton and Kaby Lake.
Say Y here if you want to support Intel ISH. If unsure, say N.
config INTEL_ISH_FIRMWARE_DOWNLOADER
tristate "Host Firmware Load feature for Intel ISH"
depends on INTEL_ISH_HID
depends on X86
help
The Integrated Sensor Hub (ISH) enables the kernel to offload
sensor polling and algorithm processing to a dedicated low power
processor in the chipset.
The Host Firmware Load feature adds support to load the ISH
firmware from host file system at boot.
Say M here if you want to support Host Firmware Loading feature
for Intel ISH. If unsure, say N.
endmenu
@@ -0,0 +1,27 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile - Intel ISH HID drivers
# Copyright (c) 2014-2016, Intel Corporation.
#
#
obj-$(CONFIG_INTEL_ISH_HID) += intel-ishtp.o
intel-ishtp-objs := ishtp/init.o
intel-ishtp-objs += ishtp/hbm.o
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
intel-ish-ipc-objs += ipc/pci-ish.o
obj-$(CONFIG_INTEL_ISH_HID) += intel-ishtp-hid.o
intel-ishtp-hid-objs := ishtp-hid.o
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 $(src)/ishtp
@@ -0,0 +1,220 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* ISH registers definitions
*
* Copyright (c) 2012-2016, Intel Corporation.
*/
#ifndef _ISHTP_ISH_REGS_H_
#define _ISHTP_ISH_REGS_H_
/*** IPC PCI Offsets and sizes ***/
/* ISH IPC Base Address */
#define IPC_REG_BASE 0x0000
/* Peripheral Interrupt Status Register */
#define IPC_REG_PISR_CHV_AB (IPC_REG_BASE + 0x00)
/* Peripheral Interrupt Mask Register */
#define IPC_REG_PIMR_CHV_AB (IPC_REG_BASE + 0x04)
/*BXT, CHV_K0*/
/*Peripheral Interrupt Status Register */
#define IPC_REG_PISR_BXT (IPC_REG_BASE + 0x0C)
/*Peripheral Interrupt Mask Register */
#define IPC_REG_PIMR_BXT (IPC_REG_BASE + 0x08)
/***********************************/
/* ISH Host Firmware status Register */
#define IPC_REG_ISH_HOST_FWSTS (IPC_REG_BASE + 0x34)
/* Host Communication Register */
#define IPC_REG_HOST_COMM (IPC_REG_BASE + 0x38)
/* Reset register */
#define IPC_REG_ISH_RST (IPC_REG_BASE + 0x44)
/* Inbound doorbell register Host to ISH */
#define IPC_REG_HOST2ISH_DRBL (IPC_REG_BASE + 0x48)
/* Outbound doorbell register ISH to Host */
#define IPC_REG_ISH2HOST_DRBL (IPC_REG_BASE + 0x54)
/* ISH to HOST message registers */
#define IPC_REG_ISH2HOST_MSG (IPC_REG_BASE + 0x60)
/* HOST to ISH message registers */
#define IPC_REG_HOST2ISH_MSG (IPC_REG_BASE + 0xE0)
/* REMAP2 to enable DMA (D3 RCR) */
#define IPC_REG_ISH_RMP2 (IPC_REG_BASE + 0x368)
#define IPC_REG_MAX (IPC_REG_BASE + 0x400)
/*** register bits - HISR ***/
/* bit corresponds HOST2ISH interrupt in PISR and PIMR registers */
#define IPC_INT_HOST2ISH_BIT (1<<0)
/***********************************/
/*CHV_A0, CHV_B0*/
/* bit corresponds ISH2HOST interrupt in PISR and PIMR registers */
#define IPC_INT_ISH2HOST_BIT_CHV_AB (1<<3)
/*BXT, CHV_K0*/
/* bit corresponds ISH2HOST interrupt in PISR and PIMR registers */
#define IPC_INT_ISH2HOST_BIT_BXT (1<<0)
/***********************************/
/* bit corresponds ISH2HOST busy clear interrupt in PIMR register */
#define IPC_INT_ISH2HOST_CLR_MASK_BIT (1<<11)
/* offset of ISH2HOST busy clear interrupt in IPC_BUSY_CLR register */
#define IPC_INT_ISH2HOST_CLR_OFFS (0)
/* bit corresponds ISH2HOST busy clear interrupt in IPC_BUSY_CLR register */
#define IPC_INT_ISH2HOST_CLR_BIT (1<<IPC_INT_ISH2HOST_CLR_OFFS)
/* bit corresponds busy bit in doorbell registers */
#define IPC_DRBL_BUSY_OFFS (31)
#define IPC_DRBL_BUSY_BIT (1<<IPC_DRBL_BUSY_OFFS)
#define IPC_HOST_OWNS_MSG_OFFS (30)
/*
* A0: bit means that host owns MSGnn registers and is reading them.
* ISH FW may not write to them
*/
#define IPC_HOST_OWNS_MSG_BIT (1<<IPC_HOST_OWNS_MSG_OFFS)
/*
* Host status bits (HOSTCOMM)
*/
/* bit corresponds host ready bit in Host Status Register (HOST_COMM) */
#define IPC_HOSTCOMM_READY_OFFS (7)
#define IPC_HOSTCOMM_READY_BIT (1<<IPC_HOSTCOMM_READY_OFFS)
/***********************************/
/*CHV_A0, CHV_B0*/
#define IPC_HOSTCOMM_INT_EN_OFFS_CHV_AB (31)
#define IPC_HOSTCOMM_INT_EN_BIT_CHV_AB \
(1<<IPC_HOSTCOMM_INT_EN_OFFS_CHV_AB)
/*BXT, CHV_K0*/
#define IPC_PIMR_INT_EN_OFFS_BXT (0)
#define IPC_PIMR_INT_EN_BIT_BXT (1<<IPC_PIMR_INT_EN_OFFS_BXT)
#define IPC_HOST2ISH_BUSYCLEAR_MASK_OFFS_BXT (8)
#define IPC_HOST2ISH_BUSYCLEAR_MASK_BIT \
(1<<IPC_HOST2ISH_BUSYCLEAR_MASK_OFFS_BXT)
/***********************************/
/*
* both Host and ISH have ILUP at bit 0
* bit corresponds host ready bit in both status registers
*/
#define IPC_ILUP_OFFS (0)
#define IPC_ILUP_BIT (1<<IPC_ILUP_OFFS)
/*
* ISH FW status bits in ISH FW Status Register
*/
#define IPC_ISH_FWSTS_SHIFT 12
#define IPC_ISH_FWSTS_MASK GENMASK(15, 12)
#define IPC_GET_ISH_FWSTS(status) \
(((status) & IPC_ISH_FWSTS_MASK) >> IPC_ISH_FWSTS_SHIFT)
/*
* FW status bits (relevant)
*/
#define IPC_FWSTS_ILUP 0x1
#define IPC_FWSTS_ISHTP_UP (1<<1)
#define IPC_FWSTS_DMA0 (1<<16)
#define IPC_FWSTS_DMA1 (1<<17)
#define IPC_FWSTS_DMA2 (1<<18)
#define IPC_FWSTS_DMA3 (1<<19)
#define IPC_ISH_IN_DMA \
(IPC_FWSTS_DMA0 | IPC_FWSTS_DMA1 | IPC_FWSTS_DMA2 | IPC_FWSTS_DMA3)
/* bit corresponds host ready bit in ISH FW Status Register */
#define IPC_ISH_ISHTP_READY_OFFS (1)
#define IPC_ISH_ISHTP_READY_BIT (1<<IPC_ISH_ISHTP_READY_OFFS)
#define IPC_RMP2_DMA_ENABLED 0x1 /* Value to enable DMA, per D3 RCR */
#define IPC_MSG_MAX_SIZE 0x80
#define IPC_HEADER_LENGTH_MASK 0x03FF
#define IPC_HEADER_PROTOCOL_MASK 0x0F
#define IPC_HEADER_MNG_CMD_MASK 0x0F
#define IPC_HEADER_LENGTH_OFFSET 0
#define IPC_HEADER_PROTOCOL_OFFSET 10
#define IPC_HEADER_MNG_CMD_OFFSET 16
#define IPC_HEADER_GET_LENGTH(drbl_reg) \
(((drbl_reg) >> IPC_HEADER_LENGTH_OFFSET)&IPC_HEADER_LENGTH_MASK)
#define IPC_HEADER_GET_PROTOCOL(drbl_reg) \
(((drbl_reg) >> IPC_HEADER_PROTOCOL_OFFSET)&IPC_HEADER_PROTOCOL_MASK)
#define IPC_HEADER_GET_MNG_CMD(drbl_reg) \
(((drbl_reg) >> IPC_HEADER_MNG_CMD_OFFSET)&IPC_HEADER_MNG_CMD_MASK)
#define IPC_IS_BUSY(drbl_reg) \
(((drbl_reg)&IPC_DRBL_BUSY_BIT) == ((uint32_t)IPC_DRBL_BUSY_BIT))
/***********************************/
/*CHV_A0, CHV_B0*/
#define IPC_INT_FROM_ISH_TO_HOST_CHV_AB(drbl_reg) \
(((drbl_reg)&IPC_INT_ISH2HOST_BIT_CHV_AB) == \
((u32)IPC_INT_ISH2HOST_BIT_CHV_AB))
/*BXT, CHV_K0*/
#define IPC_INT_FROM_ISH_TO_HOST_BXT(drbl_reg) \
(((drbl_reg)&IPC_INT_ISH2HOST_BIT_BXT) == \
((u32)IPC_INT_ISH2HOST_BIT_BXT))
/***********************************/
#define IPC_BUILD_HEADER(length, protocol, busy) \
(((busy)<<IPC_DRBL_BUSY_OFFS) | \
((protocol) << IPC_HEADER_PROTOCOL_OFFSET) | \
((length)<<IPC_HEADER_LENGTH_OFFSET))
#define IPC_BUILD_MNG_MSG(cmd, length) \
(((1)<<IPC_DRBL_BUSY_OFFS)| \
((IPC_PROTOCOL_MNG)<<IPC_HEADER_PROTOCOL_OFFSET)| \
((cmd)<<IPC_HEADER_MNG_CMD_OFFSET)| \
((length)<<IPC_HEADER_LENGTH_OFFSET))
#define IPC_SET_HOST_READY(host_status) \
((host_status) |= (IPC_HOSTCOMM_READY_BIT))
#define IPC_SET_HOST_ILUP(host_status) \
((host_status) |= (IPC_ILUP_BIT))
#define IPC_CLEAR_HOST_READY(host_status) \
((host_status) ^= (IPC_HOSTCOMM_READY_BIT))
#define IPC_CLEAR_HOST_ILUP(host_status) \
((host_status) ^= (IPC_ILUP_BIT))
/* todo - temp until PIMR HW ready */
#define IPC_HOST_BUSY_READING_OFFS 6
/* bit corresponds host ready bit in Host Status Register (HOST_COMM) */
#define IPC_HOST_BUSY_READING_BIT (1<<IPC_HOST_BUSY_READING_OFFS)
#define IPC_SET_HOST_BUSY_READING(host_status) \
((host_status) |= (IPC_HOST_BUSY_READING_BIT))
#define IPC_CLEAR_HOST_BUSY_READING(host_status)\
((host_status) ^= (IPC_HOST_BUSY_READING_BIT))
#define IPC_IS_ISH_ISHTP_READY(ish_status) \
(((ish_status) & IPC_ISH_ISHTP_READY_BIT) == \
((uint32_t)IPC_ISH_ISHTP_READY_BIT))
#define IPC_IS_ISH_ILUP(ish_status) \
(((ish_status) & IPC_ILUP_BIT) == ((uint32_t)IPC_ILUP_BIT))
#define IPC_PROTOCOL_ISHTP 1
#define IPC_PROTOCOL_MNG 3
#define MNG_RX_CMPL_ENABLE 0
#define MNG_RX_CMPL_DISABLE 1
#define MNG_RX_CMPL_INDICATION 2
#define MNG_RESET_NOTIFY 3
#define MNG_RESET_NOTIFY_ACK 4
#define MNG_SYNC_FW_CLOCK 5
#define MNG_ILLEGAL_CMD 0xFF
#endif /* _ISHTP_ISH_REGS_H_ */
@@ -0,0 +1,100 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* H/W layer of ISHTP provider device (ISH)
*
* Copyright (c) 2014-2016, Intel Corporation.
*/
#ifndef _ISHTP_HW_ISH_H_
#define _ISHTP_HW_ISH_H_
#include <linux/pci.h>
#include <linux/interrupt.h>
#include "hw-ish-regs.h"
#include "ishtp-dev.h"
#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 PCI_DEVICE_ID_INTEL_ISH_WCL 0x4D45
#define PCI_DEVICE_ID_INTEL_ISH_NVL_H 0xD354
#define PCI_DEVICE_ID_INTEL_ISH_NVL_S 0x6E78
#define REVISION_ID_CHT_A0 0x6
#define REVISION_ID_CHT_Ax_SI 0x0
#define REVISION_ID_CHT_Bx_SI 0x10
#define REVISION_ID_CHT_Kx_SI 0x20
#define REVISION_ID_CHT_Dx_SI 0x30
#define REVISION_ID_CHT_B0 0xB0
#define REVISION_ID_SI_MASK 0x70
struct ipc_rst_payload_type {
uint16_t reset_id;
uint16_t reserved;
};
struct time_sync_format {
uint8_t ts1_source;
uint8_t ts2_source;
uint16_t reserved;
} __packed;
struct ipc_time_update_msg {
uint64_t primary_host_time;
struct time_sync_format sync_info;
uint64_t secondary_host_time;
} __packed;
enum {
HOST_UTC_TIME_USEC = 0,
HOST_SYSTEM_TIME_USEC = 1
};
struct ish_hw {
void __iomem *mem_addr;
};
/*
* ISH FW status type
*/
enum {
FWSTS_AFTER_RESET = 0,
FWSTS_WAIT_FOR_HOST = 4,
FWSTS_START_KERNEL_DMA = 5,
FWSTS_FW_IS_RUNNING = 7,
FWSTS_SENSOR_APP_LOADED = 8,
FWSTS_SENSOR_APP_RUNNING = 15
};
#define to_ish_hw(dev) (struct ish_hw *)((dev)->hw)
irqreturn_t ish_irq_handler(int irq, void *dev_id);
struct ishtp_device *ish_dev_init(struct pci_dev *pdev);
int ish_hw_start(struct ishtp_device *dev);
void ish_device_disable(struct ishtp_device *dev);
int ish_disable_dma(struct ishtp_device *dev);
void ish_set_host_ready(struct ishtp_device *dev);
#endif /* _ISHTP_HW_ISH_H_ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,492 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* PCI glue for ISHTP provider device (ISH) driver
*
* Copyright (c) 2014-2016, Intel Corporation.
*/
#include <linux/acpi.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/pci.h>
#include <linux/sched.h>
#include <linux/suspend.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#define CREATE_TRACE_POINTS
#include <trace/events/intel_ish.h>
#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,
ISHTP_DRIVER_DATA_WCL,
ISHTP_DRIVER_DATA_NVL_H,
ISHTP_DRIVER_DATA_NVL_S,
};
#define ISH_FW_GEN_LNL_M "lnlm"
#define ISH_FW_GEN_PTL "ptl"
#define ISH_FW_GEN_WCL "wcl"
#define ISH_FW_GEN_NVL_H "nvlh"
#define ISH_FW_GEN_NVL_S "nvls"
#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,
},
[ISHTP_DRIVER_DATA_WCL] = {
.fw_generation = ISH_FW_GEN_WCL,
},
[ISHTP_DRIVER_DATA_NVL_H] = {
.fw_generation = ISH_FW_GEN_NVL_H,
},
[ISHTP_DRIVER_DATA_NVL_S] = {
.fw_generation = ISH_FW_GEN_NVL_S,
},
};
static const struct pci_device_id ish_pci_tbl[] = {
{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_DEVICE_DATA(INTEL, ISH_LNL_M, ISHTP_DRIVER_DATA_LNL_M)},
{PCI_DEVICE_DATA(INTEL, ISH_PTL_H, ISHTP_DRIVER_DATA_PTL)},
{PCI_DEVICE_DATA(INTEL, ISH_PTL_P, ISHTP_DRIVER_DATA_PTL)},
{PCI_DEVICE_DATA(INTEL, ISH_WCL, ISHTP_DRIVER_DATA_WCL)},
{PCI_DEVICE_DATA(INTEL, ISH_NVL_H, ISHTP_DRIVER_DATA_NVL_H)},
{PCI_DEVICE_DATA(INTEL, ISH_NVL_S, ISHTP_DRIVER_DATA_NVL_S)},
{}
};
MODULE_DEVICE_TABLE(pci, ish_pci_tbl);
/**
* ish_event_tracer() - Callback function to dump trace messages
* @dev: ishtp device
* @format: printf style format
*
* Callback to direct log messages to Linux trace buffers
*/
static __printf(2, 3)
void ish_event_tracer(struct ishtp_device *dev, const char *format, ...)
{
if (trace_ishtp_dump_enabled()) {
va_list args;
char tmp_buf[100];
va_start(args, format);
vsnprintf(tmp_buf, sizeof(tmp_buf), format, args);
va_end(args);
trace_call__ishtp_dump(tmp_buf);
}
}
/**
* ish_init() - Init function
* @dev: ishtp device
*
* This function initialize wait queues for suspend/resume and call
* calls hadware initialization function. This will initiate
* startup sequence
*
* Return: 0 for success or error code for failure
*/
static int ish_init(struct ishtp_device *dev)
{
int ret;
/* Set the state of ISH HW to start */
ret = ish_hw_start(dev);
if (ret) {
dev_err(dev->devc, "ISH: hw start failed.\n");
return ret;
}
/* Start the inter process communication to ISH processor */
ret = ishtp_start(dev);
if (ret) {
dev_err(dev->devc, "ISHTP: Protocol init failed.\n");
return ret;
}
return 0;
}
static const struct pci_device_id ish_invalid_pci_ids[] = {
/* Mehlow platform special pci ids */
{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 == PCI_DEVICE_ID_INTEL_ISH_CHV;
}
static inline bool ish_should_leave_d0i3(struct pci_dev *pdev)
{
struct ishtp_device *dev = pci_get_drvdata(pdev);
u32 fwsts = dev->ops->get_fw_status(dev);
if (dev->suspend_flag || !IPC_IS_ISH_ILUP(fwsts))
return false;
return !pm_resume_via_firmware() || pdev->device == PCI_DEVICE_ID_INTEL_ISH_CHV;
}
/**
* ish_probe() - PCI driver probe callback
* @pdev: pci device
* @ent: pci device id
*
* Initialize PCI function, setup interrupt and call for ISH initialization
*
* Return: 0 for success or error code for failure
*/
static int ish_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
int ret;
struct ish_hw *hw;
unsigned long irq_flag = 0;
struct ishtp_device *ishtp;
struct device *dev = &pdev->dev;
/* Check for invalid platforms for ISH support */
if (pci_dev_present(ish_invalid_pci_ids))
return -ENODEV;
/* enable pci dev */
ret = pcim_enable_device(pdev);
if (ret) {
dev_err(dev, "ISH: Failed to enable PCI device\n");
return ret;
}
/* set PCI host mastering */
pci_set_master(pdev);
/* pci request regions for ISH driver */
ret = pcim_iomap_regions(pdev, 1 << 0, KBUILD_MODNAME);
if (ret) {
dev_err(dev, "ISH: Failed to get PCI regions\n");
return ret;
}
/* allocates and initializes the ISH dev structure */
ishtp = ish_dev_init(pdev);
if (!ishtp) {
ret = -ENOMEM;
return ret;
}
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];
ishtp->pdev = pdev;
/* 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;
ret = devm_request_irq(dev, pdev->irq, ish_irq_handler,
irq_flag, KBUILD_MODNAME, ishtp);
if (ret) {
dev_err(dev, "ISH: request IRQ %d failed\n", pdev->irq);
return ret;
}
dev_set_drvdata(ishtp->devc, ishtp);
init_waitqueue_head(&ishtp->suspend_wait);
init_waitqueue_head(&ishtp->resume_wait);
/* Enable PME for EHL */
if (pdev->device == PCI_DEVICE_ID_INTEL_ISH_EHL_Ax)
device_init_wakeup(dev, true);
ret = ish_init(ishtp);
if (ret)
return ret;
return 0;
}
/**
* ish_remove() - PCI driver remove callback
* @pdev: pci device
*
* This function does cleanup of ISH on pci remove callback
*/
static void ish_remove(struct pci_dev *pdev)
{
struct ishtp_device *ishtp_dev = pci_get_drvdata(pdev);
ishtp_bus_remove_all_clients(ishtp_dev, false);
ish_device_disable(ishtp_dev);
}
/**
* ish_shutdown() - PCI driver shutdown callback
* @pdev: pci device
*
* This function sets up wakeup for S5
*/
static void ish_shutdown(struct pci_dev *pdev)
{
if (pdev->device == PCI_DEVICE_ID_INTEL_ISH_EHL_Ax)
pci_prepare_to_sleep(pdev);
}
static struct device __maybe_unused *ish_resume_device;
/**
* ish_resume_handler() - Work function to complete resume
* @work: work struct
*
* The resume work function to complete resume function asynchronously.
* There are two resume paths, one where ISH is not powered off,
* in that case a simple resume message is enough, others we need
* a reset sequence.
*/
static void __maybe_unused ish_resume_handler(struct work_struct *work)
{
struct pci_dev *pdev = to_pci_dev(ish_resume_device);
struct ishtp_device *dev = pci_get_drvdata(pdev);
if (ish_should_leave_d0i3(pdev)) {
if (device_may_wakeup(&pdev->dev))
disable_irq_wake(pdev->irq);
ish_set_host_ready(dev);
ishtp_send_resume(dev);
/* Waiting to get resume response */
if (dev->resume_flag)
wait_event_interruptible_timeout(dev->resume_wait,
!dev->resume_flag,
msecs_to_jiffies(WAIT_FOR_RESUME_ACK_MS));
/*
* If the flag is not cleared, something is wrong with ISH FW.
* So on resume, need to go through init sequence again.
*/
if (dev->resume_flag)
ish_init(dev);
} else {
/*
* Resume from the D3, full reboot of ISH processor will happen,
* so need to go through init sequence again.
*/
ish_init(dev);
}
}
/**
* ish_suspend() - ISH suspend callback
* @device: device pointer
*
* ISH suspend callback
*
* Return: 0 to the pm core
*/
static int __maybe_unused ish_suspend(struct device *device)
{
struct pci_dev *pdev = to_pci_dev(device);
struct ishtp_device *dev = pci_get_drvdata(pdev);
if (ish_should_enter_d0i3(pdev)) {
/*
* If previous suspend hasn't been asnwered then ISH is likely
* dead, don't attempt nested notification
*/
if (dev->suspend_flag)
return 0;
dev->resume_flag = 0;
dev->suspend_flag = 1;
ishtp_send_suspend(dev);
/* 25 ms should be enough for live ISH to flush all IPC buf */
if (dev->suspend_flag)
wait_event_interruptible_timeout(dev->suspend_wait,
!dev->suspend_flag,
msecs_to_jiffies(25));
if (dev->suspend_flag) {
/*
* It looks like FW halt, clear the DMA bit, and put
* ISH into D3, and FW would reset on resume.
*/
ish_disable_dma(dev);
} else {
/*
* Save state so PCI core will keep the device at D0,
* the ISH would enter D0i3
*/
pci_save_state(pdev);
if (device_may_wakeup(&pdev->dev))
enable_irq_wake(pdev->irq);
}
} else {
/*
* Clear the DMA bit before putting ISH into D3,
* or ISH FW would reset automatically.
*/
ish_disable_dma(dev);
}
return 0;
}
static __maybe_unused DECLARE_WORK(resume_work, ish_resume_handler);
/**
* ish_resume() - ISH resume callback
* @device: device pointer
*
* ISH resume callback
*
* Return: 0 to the pm core
*/
static int __maybe_unused ish_resume(struct device *device)
{
struct pci_dev *pdev = to_pci_dev(device);
struct ishtp_device *dev = pci_get_drvdata(pdev);
ish_resume_device = device;
dev->resume_flag = 1;
/* If ISH resume from D3, reset ishtp clients before return */
if (!ish_should_leave_d0i3(pdev))
ishtp_reset_handler(dev);
queue_work(dev->unbound_wq, &resume_work);
return 0;
}
static int __maybe_unused ish_freeze(struct device *device)
{
struct pci_dev *pdev = to_pci_dev(device);
return pci_save_state(pdev);
}
static const struct dev_pm_ops __maybe_unused ish_pm_ops = {
.suspend = pm_sleep_ptr(ish_suspend),
.resume = pm_sleep_ptr(ish_resume),
.freeze = pm_sleep_ptr(ish_freeze),
.restore = pm_sleep_ptr(ish_resume),
.poweroff = pm_sleep_ptr(ish_suspend),
};
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,
.probe = ish_probe,
.remove = ish_remove,
.shutdown = ish_shutdown,
.driver.pm = &ish_pm_ops,
.dev_groups = ish_firmware_groups,
};
module_pci_driver(ish_driver);
/* Original author */
MODULE_AUTHOR("Daniel Drubin <daniel.drubin@intel.com>");
/* Adoption to upstream Linux kernel */
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);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,959 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* ISHTP client driver for HID (ISH)
*
* Copyright (c) 2014-2016, Intel Corporation.
*/
#include <linux/module.h>
#include <linux/hid.h>
#include <linux/intel-ish-client-if.h>
#include <linux/sched.h>
#include "ishtp-hid.h"
/* ISH Transport protocol (ISHTP in short) GUID */
static const struct ishtp_device_id hid_ishtp_id_table[] = {
{ .guid = GUID_INIT(0x33AECD58, 0xB679, 0x4E54,
0x9B, 0xD9, 0xA0, 0x4D, 0x34, 0xF0, 0xC2, 0x26), },
{ }
};
MODULE_DEVICE_TABLE(ishtp, hid_ishtp_id_table);
/* Rx ring buffer pool size */
#define HID_CL_RX_RING_SIZE 32
#define HID_CL_TX_RING_SIZE 16
#define cl_data_to_dev(client_data) ishtp_device(client_data->cl_device)
/**
* report_bad_packet() - Report bad packets
* @hid_ishtp_cl: Client instance to get stats
* @recv_buf: Raw received host interface message
* @cur_pos: Current position index in payload
* @payload_len: Length of payload expected
*
* Dumps error in case bad packet is received
*/
static void report_bad_packet(struct ishtp_cl *hid_ishtp_cl, void *recv_buf,
size_t cur_pos, size_t payload_len)
{
struct hostif_msg *recv_msg = recv_buf;
struct ishtp_cl_data *client_data = ishtp_get_client_data(hid_ishtp_cl);
dev_err(cl_data_to_dev(client_data), "[hid-ish]: BAD packet %02X\n"
"total_bad=%u cur_pos=%u\n"
"[%02X %02X %02X %02X]\n"
"payload_len=%u\n"
"multi_packet_cnt=%u\n"
"is_response=%02X\n",
recv_msg->hdr.command, client_data->bad_recv_cnt,
(unsigned int)cur_pos,
((unsigned char *)recv_msg)[0], ((unsigned char *)recv_msg)[1],
((unsigned char *)recv_msg)[2], ((unsigned char *)recv_msg)[3],
(unsigned int)payload_len, client_data->multi_packet_cnt,
recv_msg->hdr.command & ~CMD_MASK);
}
/**
* process_recv() - Received and parse incoming packet
* @hid_ishtp_cl: Client instance to get stats
* @recv_buf: Raw received host interface message
* @data_len: length of the message
*
* Parse the incoming packet. If it is a response packet then it will update
* per instance flags and wake up the caller waiting to for the response.
*/
static void process_recv(struct ishtp_cl *hid_ishtp_cl, void *recv_buf,
size_t data_len)
{
struct hostif_msg *recv_msg;
unsigned char *payload;
struct device_info *dev_info;
int i, j;
size_t payload_len, total_len, cur_pos, raw_len, msg_len;
int report_type;
struct report_list *reports_list;
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;
struct ishtp_hid_data *hid_data = NULL;
struct hid_device *hid = NULL;
payload = recv_buf + sizeof(struct hostif_msg_hdr);
total_len = data_len;
cur_pos = 0;
do {
if (cur_pos + sizeof(struct hostif_msg) > total_len) {
dev_err(cl_data_to_dev(client_data),
"[hid-ish]: error, received %u which is less than data header %u\n",
(unsigned int)data_len,
(unsigned int)sizeof(struct hostif_msg_hdr));
++client_data->bad_recv_cnt;
ish_hw_reset(ishtp_get_ishtp_device(hid_ishtp_cl));
break;
}
recv_msg = (struct hostif_msg *)(recv_buf + cur_pos);
payload_len = recv_msg->hdr.size;
/* Sanity checks */
if (cur_pos + payload_len + sizeof(struct hostif_msg) >
total_len) {
++client_data->bad_recv_cnt;
report_bad_packet(hid_ishtp_cl, recv_msg, cur_pos,
payload_len);
ish_hw_reset(ishtp_get_ishtp_device(hid_ishtp_cl));
break;
}
hid_ishtp_trace(client_data, "%s %d\n",
__func__, recv_msg->hdr.command & CMD_MASK);
switch (recv_msg->hdr.command & CMD_MASK) {
case HOSTIF_DM_ENUM_DEVICES:
if ((!(recv_msg->hdr.command & ~CMD_MASK) ||
client_data->init_done)) {
++client_data->bad_recv_cnt;
report_bad_packet(hid_ishtp_cl, recv_msg,
cur_pos,
payload_len);
ish_hw_reset(ishtp_get_ishtp_device(hid_ishtp_cl));
break;
}
client_data->hid_dev_count = (unsigned int)*payload;
if (!client_data->hid_devices)
client_data->hid_devices = devm_kcalloc(
cl_data_to_dev(client_data),
client_data->hid_dev_count,
sizeof(struct device_info),
GFP_KERNEL);
if (!client_data->hid_devices) {
dev_err(cl_data_to_dev(client_data),
"Mem alloc failed for hid device info\n");
wake_up_interruptible(&client_data->init_wait);
break;
}
for (i = 0; i < client_data->hid_dev_count; ++i) {
if (1 + sizeof(struct device_info) * i >=
payload_len) {
dev_err(cl_data_to_dev(client_data),
"[hid-ish]: [ENUM_DEVICES]: content size %zu is bigger than payload_len %zu\n",
1 + sizeof(struct device_info)
* i, payload_len);
}
if (1 + sizeof(struct device_info) * i >=
data_len)
break;
dev_info = (struct device_info *)(payload + 1 +
sizeof(struct device_info) * i);
if (client_data->hid_devices)
memcpy(client_data->hid_devices + i,
dev_info,
sizeof(struct device_info));
}
client_data->enum_devices_done = true;
wake_up_interruptible(&client_data->init_wait);
break;
case HOSTIF_GET_HID_DESCRIPTOR:
if ((!(recv_msg->hdr.command & ~CMD_MASK) ||
client_data->init_done)) {
++client_data->bad_recv_cnt;
report_bad_packet(hid_ishtp_cl, recv_msg,
cur_pos,
payload_len);
ish_hw_reset(ishtp_get_ishtp_device(hid_ishtp_cl));
break;
}
if (!client_data->hid_descr[curr_hid_dev])
client_data->hid_descr[curr_hid_dev] =
devm_kmalloc(cl_data_to_dev(client_data),
payload_len, GFP_KERNEL);
if (client_data->hid_descr[curr_hid_dev]) {
memcpy(client_data->hid_descr[curr_hid_dev],
payload, payload_len);
client_data->hid_descr_size[curr_hid_dev] =
payload_len;
client_data->hid_descr_done = true;
}
wake_up_interruptible(&client_data->init_wait);
break;
case HOSTIF_GET_REPORT_DESCRIPTOR:
if ((!(recv_msg->hdr.command & ~CMD_MASK) ||
client_data->init_done)) {
++client_data->bad_recv_cnt;
report_bad_packet(hid_ishtp_cl, recv_msg,
cur_pos,
payload_len);
ish_hw_reset(ishtp_get_ishtp_device(hid_ishtp_cl));
break;
}
if (!client_data->report_descr[curr_hid_dev])
client_data->report_descr[curr_hid_dev] =
devm_kmalloc(cl_data_to_dev(client_data),
payload_len, GFP_KERNEL);
if (client_data->report_descr[curr_hid_dev]) {
memcpy(client_data->report_descr[curr_hid_dev],
payload,
payload_len);
client_data->report_descr_size[curr_hid_dev] =
payload_len;
client_data->report_descr_done = true;
}
wake_up_interruptible(&client_data->init_wait);
break;
case HOSTIF_GET_FEATURE_REPORT:
report_type = HID_FEATURE_REPORT;
goto do_get_report;
case HOSTIF_GET_INPUT_REPORT:
report_type = HID_INPUT_REPORT;
do_get_report:
/* Get index of device that matches this id */
for (i = 0; i < client_data->num_hid_devices; ++i) {
if (recv_msg->hdr.device_id ==
client_data->hid_devices[i].dev_id) {
hid = client_data->hid_sensor_hubs[i];
if (!hid)
break;
hid_data = hid->driver_data;
if (hid_data->raw_get_req) {
raw_len =
(hid_data->raw_buf_size <
payload_len) ?
hid_data->raw_buf_size :
payload_len;
memcpy(hid_data->raw_buf,
payload, raw_len);
} else {
hid_input_report
(hid, report_type,
payload, payload_len,
0);
}
ishtp_hid_wakeup(hid);
break;
}
}
break;
case HOSTIF_SET_FEATURE_REPORT:
/* Get index of device that matches this id */
for (i = 0; i < client_data->num_hid_devices; ++i) {
if (recv_msg->hdr.device_id ==
client_data->hid_devices[i].dev_id)
if (client_data->hid_sensor_hubs[i]) {
ishtp_hid_wakeup(
client_data->hid_sensor_hubs[
i]);
break;
}
}
break;
case HOSTIF_PUBLISH_INPUT_REPORT:
report_type = HID_INPUT_REPORT;
for (i = 0; i < client_data->num_hid_devices; ++i)
if (recv_msg->hdr.device_id ==
client_data->hid_devices[i].dev_id)
if (client_data->hid_sensor_hubs[i])
hid_input_report(
client_data->hid_sensor_hubs[
i],
report_type, payload,
payload_len, 0);
break;
case HOSTIF_PUBLISH_INPUT_REPORT_LIST:
report_type = HID_INPUT_REPORT;
reports_list = (struct report_list *)payload;
report = reports_list->reports;
for (j = 0; j < reports_list->num_of_reports; j++) {
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);
for (i = 0; i < client_data->num_hid_devices;
++i)
if (recv_msg->hdr.device_id ==
client_data->hid_devices[i].dev_id &&
client_data->hid_sensor_hubs[i]) {
hid_input_report(
client_data->hid_sensor_hubs[
i],
report_type,
payload, payload_len,
0);
}
report += sizeof(*report) + payload_len;
}
break;
default:
++client_data->bad_recv_cnt;
report_bad_packet(hid_ishtp_cl, recv_msg, cur_pos,
payload_len);
ish_hw_reset(ishtp_get_ishtp_device(hid_ishtp_cl));
break;
}
msg_len = payload_len + sizeof(struct hostif_msg);
if (!cur_pos && cur_pos + msg_len < total_len)
++client_data->multi_packet_cnt;
cur_pos += msg_len;
payload += msg_len;
} while (cur_pos < total_len);
}
/**
* ish_cl_event_cb() - bus driver callback for incoming message/packet
* @device: Pointer to the ishtp client device for which this message
* is targeted
*
* Remove the packet from the list and process the message by calling
* process_recv
*/
static void ish_cl_event_cb(struct ishtp_cl_device *device)
{
struct ishtp_cl *hid_ishtp_cl = ishtp_get_drvdata(device);
struct ishtp_cl_rb *rb_in_proc;
size_t r_length;
if (!hid_ishtp_cl)
return;
while ((rb_in_proc = ishtp_cl_rx_get_rb(hid_ishtp_cl)) != NULL) {
if (!rb_in_proc->buffer.data)
return;
r_length = rb_in_proc->buf_idx;
/* decide what to do with received data */
process_recv(hid_ishtp_cl, rb_in_proc->buffer.data, r_length);
ishtp_cl_io_rb_recycle(rb_in_proc);
}
}
/**
* hid_ishtp_set_feature() - send request to ISH FW to set a feature request
* @hid: hid device instance for this request
* @buf: feature buffer
* @len: Length of feature buffer
* @report_id: Report id for the feature set request
*
* This is called from hid core .request() callback. This function doesn't wait
* for response.
*/
void hid_ishtp_set_feature(struct hid_device *hid, char *buf, unsigned int len,
int report_id)
{
struct ishtp_hid_data *hid_data = hid->driver_data;
struct ishtp_cl_data *client_data = hid_data->client_data;
struct hostif_msg *msg = (struct hostif_msg *)buf;
int rv;
int i;
hid_ishtp_trace(client_data, "%s hid %p\n", __func__, hid);
rv = ishtp_hid_link_ready_wait(client_data);
if (rv) {
hid_ishtp_trace(client_data, "%s hid %p link not ready\n",
__func__, hid);
return;
}
memset(msg, 0, sizeof(struct hostif_msg));
msg->hdr.command = HOSTIF_SET_FEATURE_REPORT;
for (i = 0; i < client_data->num_hid_devices; ++i) {
if (hid == client_data->hid_sensor_hubs[i]) {
msg->hdr.device_id =
client_data->hid_devices[i].dev_id;
break;
}
}
if (i == client_data->num_hid_devices)
return;
rv = ishtp_cl_send(client_data->hid_ishtp_cl, buf, len);
if (rv)
hid_ishtp_trace(client_data, "%s hid %p send failed\n",
__func__, hid);
}
/**
* hid_ishtp_get_report() - request to get feature/input report
* @hid: hid device instance for this request
* @report_id: Report id for the get request
* @report_type: Report type for the this request
*
* This is called from hid core .request() callback. This function will send
* request to FW and return without waiting for response.
*/
void hid_ishtp_get_report(struct hid_device *hid, int report_id,
int report_type)
{
struct ishtp_hid_data *hid_data = hid->driver_data;
struct ishtp_cl_data *client_data = hid_data->client_data;
struct hostif_msg_to_sensor msg = {};
int rv;
int i;
hid_ishtp_trace(client_data, "%s hid %p\n", __func__, hid);
rv = ishtp_hid_link_ready_wait(client_data);
if (rv) {
hid_ishtp_trace(client_data, "%s hid %p link not ready\n",
__func__, hid);
return;
}
msg.hdr.command = (report_type == HID_FEATURE_REPORT) ?
HOSTIF_GET_FEATURE_REPORT : HOSTIF_GET_INPUT_REPORT;
for (i = 0; i < client_data->num_hid_devices; ++i) {
if (hid == client_data->hid_sensor_hubs[i]) {
msg.hdr.device_id =
client_data->hid_devices[i].dev_id;
break;
}
}
if (i == client_data->num_hid_devices)
return;
msg.report_id = report_id;
rv = ishtp_cl_send(client_data->hid_ishtp_cl, (uint8_t *)&msg,
sizeof(msg));
if (rv)
hid_ishtp_trace(client_data, "%s hid %p send failed\n",
__func__, hid);
}
/**
* ishtp_hid_link_ready_wait() - Wait for link ready
* @client_data: client data instance
*
* If the transport link started suspend process, then wait, till either
* resumed or timeout
*
* Return: 0 on success, non zero on error
*/
int ishtp_hid_link_ready_wait(struct ishtp_cl_data *client_data)
{
int rc;
if (client_data->suspended) {
hid_ishtp_trace(client_data, "wait for link ready\n");
rc = wait_event_interruptible_timeout(
client_data->ishtp_resume_wait,
!client_data->suspended,
5 * HZ);
if (rc == 0) {
hid_ishtp_trace(client_data, "link not ready\n");
return -EIO;
}
hid_ishtp_trace(client_data, "link ready\n");
}
return 0;
}
/**
* ishtp_enum_enum_devices() - Enumerate hid devices
* @hid_ishtp_cl: client instance
*
* Helper function to send request to firmware to enumerate HID devices
*
* Return: 0 on success, non zero on error
*/
static int ishtp_enum_enum_devices(struct ishtp_cl *hid_ishtp_cl)
{
struct hostif_msg msg;
struct ishtp_cl_data *client_data = ishtp_get_client_data(hid_ishtp_cl);
int retry_count;
int rv;
/* Send HOSTIF_DM_ENUM_DEVICES */
client_data->enum_devices_done = false;
memset(&msg, 0, sizeof(struct hostif_msg));
msg.hdr.command = HOSTIF_DM_ENUM_DEVICES;
rv = ishtp_cl_send(hid_ishtp_cl, (unsigned char *)&msg,
sizeof(struct hostif_msg));
if (rv)
return rv;
retry_count = 0;
while (!client_data->enum_devices_done &&
retry_count < 10) {
wait_event_interruptible_timeout(client_data->init_wait,
client_data->enum_devices_done,
3 * HZ);
++retry_count;
if (!client_data->enum_devices_done)
/* Send HOSTIF_DM_ENUM_DEVICES */
rv = ishtp_cl_send(hid_ishtp_cl,
(unsigned char *) &msg,
sizeof(struct hostif_msg));
}
if (!client_data->enum_devices_done) {
dev_err(cl_data_to_dev(client_data),
"[hid-ish]: timed out waiting for enum_devices\n");
return -ETIMEDOUT;
}
if (!client_data->hid_devices) {
dev_err(cl_data_to_dev(client_data),
"[hid-ish]: failed to allocate HID dev structures\n");
return -ENOMEM;
}
client_data->num_hid_devices = client_data->hid_dev_count;
dev_info(ishtp_device(client_data->cl_device),
"[hid-ish]: enum_devices_done OK, num_hid_devices=%d\n",
client_data->num_hid_devices);
return 0;
}
/**
* ishtp_get_hid_descriptor() - Get hid descriptor
* @hid_ishtp_cl: client instance
* @index: Index into the hid_descr array
*
* Helper function to send request to firmware get HID descriptor of a device
*
* Return: 0 on success, non zero on error
*/
static int ishtp_get_hid_descriptor(struct ishtp_cl *hid_ishtp_cl, int index)
{
struct hostif_msg msg;
struct ishtp_cl_data *client_data = ishtp_get_client_data(hid_ishtp_cl);
int rv;
/* Get HID descriptor */
client_data->hid_descr_done = false;
memset(&msg, 0, sizeof(struct hostif_msg));
msg.hdr.command = HOSTIF_GET_HID_DESCRIPTOR;
msg.hdr.device_id = client_data->hid_devices[index].dev_id;
rv = ishtp_cl_send(hid_ishtp_cl, (unsigned char *) &msg,
sizeof(struct hostif_msg));
if (rv)
return rv;
if (!client_data->hid_descr_done) {
wait_event_interruptible_timeout(client_data->init_wait,
client_data->hid_descr_done,
3 * HZ);
if (!client_data->hid_descr_done) {
dev_err(cl_data_to_dev(client_data),
"[hid-ish]: timed out for hid_descr_done\n");
return -EIO;
}
if (!client_data->hid_descr[index]) {
dev_err(cl_data_to_dev(client_data),
"[hid-ish]: allocation HID desc fail\n");
return -ENOMEM;
}
}
return 0;
}
/**
* ishtp_get_report_descriptor() - Get report descriptor
* @hid_ishtp_cl: client instance
* @index: Index into the hid_descr array
*
* Helper function to send request to firmware get HID report descriptor of
* a device
*
* Return: 0 on success, non zero on error
*/
static int ishtp_get_report_descriptor(struct ishtp_cl *hid_ishtp_cl,
int index)
{
struct hostif_msg msg;
struct ishtp_cl_data *client_data = ishtp_get_client_data(hid_ishtp_cl);
int rv;
/* Get report descriptor */
client_data->report_descr_done = false;
memset(&msg, 0, sizeof(struct hostif_msg));
msg.hdr.command = HOSTIF_GET_REPORT_DESCRIPTOR;
msg.hdr.device_id = client_data->hid_devices[index].dev_id;
rv = ishtp_cl_send(hid_ishtp_cl, (unsigned char *) &msg,
sizeof(struct hostif_msg));
if (rv)
return rv;
if (!client_data->report_descr_done)
wait_event_interruptible_timeout(client_data->init_wait,
client_data->report_descr_done,
3 * HZ);
if (!client_data->report_descr_done) {
dev_err(cl_data_to_dev(client_data),
"[hid-ish]: timed out for report descr\n");
return -EIO;
}
if (!client_data->report_descr[index]) {
dev_err(cl_data_to_dev(client_data),
"[hid-ish]: failed to alloc report descr\n");
return -ENOMEM;
}
return 0;
}
/**
* hid_ishtp_cl_init() - Init function for ISHTP client
* @hid_ishtp_cl: ISHTP client instance
* @reset: true if called for init after reset
*
* This function complete the initializtion of the client. The summary of
* processing:
* - Send request to enumerate the hid clients
* Get the HID descriptor for each enumearated device
* Get report description of each device
* Register each device wik hid core by calling ishtp_hid_probe
*
* Return: 0 on success, non zero on error
*/
static int hid_ishtp_cl_init(struct ishtp_cl *hid_ishtp_cl, bool reset)
{
struct ishtp_cl_data *client_data = ishtp_get_client_data(hid_ishtp_cl);
int i;
int rv;
dev_dbg(cl_data_to_dev(client_data), "%s\n", __func__);
hid_ishtp_trace(client_data, "%s reset flag: %d\n", __func__, reset);
client_data->init_done = 0;
rv = ishtp_cl_establish_connection(hid_ishtp_cl,
&hid_ishtp_id_table[0].guid,
HID_CL_TX_RING_SIZE,
HID_CL_RX_RING_SIZE,
reset);
if (rv) {
dev_err(cl_data_to_dev(client_data),
"client connect fail\n");
goto err_cl_disconnect;
}
hid_ishtp_trace(client_data, "%s client connected\n", __func__);
/* Register read callback */
ishtp_register_event_cb(client_data->cl_device, ish_cl_event_cb);
rv = ishtp_enum_enum_devices(hid_ishtp_cl);
if (rv)
goto err_cl_disconnect;
hid_ishtp_trace(client_data, "%s enumerated device count %d\n",
__func__, client_data->num_hid_devices);
for (i = 0; i < client_data->num_hid_devices; ++i) {
client_data->cur_hid_dev = i;
rv = ishtp_get_hid_descriptor(hid_ishtp_cl, i);
if (rv)
goto err_cl_disconnect;
rv = ishtp_get_report_descriptor(hid_ishtp_cl, i);
if (rv)
goto err_cl_disconnect;
if (!reset) {
rv = ishtp_hid_probe(i, client_data);
if (rv) {
dev_err(cl_data_to_dev(client_data),
"[hid-ish]: HID probe for #%u failed: %d\n",
i, rv);
goto err_cl_disconnect;
}
}
} /* for() on all hid devices */
client_data->init_done = 1;
client_data->suspended = false;
wake_up_interruptible(&client_data->ishtp_resume_wait);
hid_ishtp_trace(client_data, "%s successful init\n", __func__);
return 0;
err_cl_disconnect:
ishtp_cl_destroy_connection(hid_ishtp_cl, reset);
return rv;
}
/**
* hid_ishtp_cl_deinit() - Deinit function for ISHTP client
* @hid_ishtp_cl: ISHTP client instance
*
* Unlink and free hid client
*/
static void hid_ishtp_cl_deinit(struct ishtp_cl *hid_ishtp_cl)
{
ishtp_cl_destroy_connection(hid_ishtp_cl, false);
/* disband and free all Tx and Rx client-level rings */
ishtp_cl_free(hid_ishtp_cl);
}
static void hid_ishtp_cl_reset_handler(struct work_struct *work)
{
struct ishtp_cl_data *client_data;
struct ishtp_cl *hid_ishtp_cl;
int retry;
int rv;
client_data = container_of(work, struct ishtp_cl_data, work);
hid_ishtp_cl = client_data->hid_ishtp_cl;
hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__,
hid_ishtp_cl);
dev_dbg(ishtp_device(client_data->cl_device), "%s\n", __func__);
ishtp_cl_destroy_connection(hid_ishtp_cl, true);
client_data->num_hid_devices = 0;
for (retry = 0; retry < 3; ++retry) {
rv = hid_ishtp_cl_init(hid_ishtp_cl, true);
if (!rv)
break;
dev_err(cl_data_to_dev(client_data), "Retry reset init\n");
}
if (rv) {
dev_err(cl_data_to_dev(client_data), "Reset Failed\n");
hid_ishtp_trace(client_data, "%s Failed hid_ishtp_cl %p\n",
__func__, hid_ishtp_cl);
}
}
static void hid_ishtp_cl_resume_handler(struct work_struct *work)
{
struct ishtp_cl_data *client_data = container_of(work, struct ishtp_cl_data, resume_work);
struct ishtp_cl *hid_ishtp_cl = client_data->hid_ishtp_cl;
if (ishtp_wait_resume(ishtp_get_ishtp_device(hid_ishtp_cl))) {
/*
* Clear the suspended flag only when the connection is established.
* If the connection is not established, the suspended flag will be cleared after
* the connection is made.
*/
if (ishtp_get_connection_state(hid_ishtp_cl) == ISHTP_CL_CONNECTED) {
client_data->suspended = false;
wake_up_interruptible(&client_data->ishtp_resume_wait);
}
} else {
hid_ishtp_trace(client_data, "hid client: wait for resume timed out");
dev_err(cl_data_to_dev(client_data), "wait for resume timed out");
}
}
ishtp_print_log ishtp_hid_print_trace;
/**
* hid_ishtp_cl_probe() - ISHTP client driver probe
* @cl_device: ISHTP client device instance
*
* This function gets called on device create on ISHTP bus
*
* Return: 0 on success, non zero on error
*/
static int hid_ishtp_cl_probe(struct ishtp_cl_device *cl_device)
{
struct ishtp_cl *hid_ishtp_cl;
struct ishtp_cl_data *client_data;
int rv;
if (!cl_device)
return -ENODEV;
client_data = devm_kzalloc(ishtp_device(cl_device),
sizeof(*client_data),
GFP_KERNEL);
if (!client_data)
return -ENOMEM;
hid_ishtp_cl = ishtp_cl_allocate(cl_device);
if (!hid_ishtp_cl)
return -ENOMEM;
ishtp_set_drvdata(cl_device, hid_ishtp_cl);
ishtp_set_client_data(hid_ishtp_cl, client_data);
client_data->hid_ishtp_cl = hid_ishtp_cl;
client_data->cl_device = cl_device;
init_waitqueue_head(&client_data->init_wait);
init_waitqueue_head(&client_data->ishtp_resume_wait);
INIT_WORK(&client_data->work, hid_ishtp_cl_reset_handler);
INIT_WORK(&client_data->resume_work, hid_ishtp_cl_resume_handler);
ishtp_hid_print_trace = ishtp_trace_callback(cl_device);
rv = hid_ishtp_cl_init(hid_ishtp_cl, false);
if (rv) {
ishtp_cl_free(hid_ishtp_cl);
return rv;
}
ishtp_get_device(cl_device);
return 0;
}
/**
* hid_ishtp_cl_remove() - ISHTP client driver remove
* @cl_device: ISHTP client device instance
*
* This function gets called on device remove on ISHTP bus
*
* Return: 0
*/
static void hid_ishtp_cl_remove(struct ishtp_cl_device *cl_device)
{
struct ishtp_cl *hid_ishtp_cl = ishtp_get_drvdata(cl_device);
struct ishtp_cl_data *client_data = ishtp_get_client_data(hid_ishtp_cl);
hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__,
hid_ishtp_cl);
dev_dbg(ishtp_device(cl_device), "%s\n", __func__);
ishtp_put_device(cl_device);
ishtp_hid_remove(client_data);
hid_ishtp_cl_deinit(hid_ishtp_cl);
hid_ishtp_cl = NULL;
client_data->num_hid_devices = 0;
}
/**
* hid_ishtp_cl_reset() - ISHTP client driver reset
* @cl_device: ISHTP client device instance
*
* This function gets called on device reset on ISHTP bus
*
* Return: 0
*/
static int hid_ishtp_cl_reset(struct ishtp_cl_device *cl_device)
{
struct ishtp_cl *hid_ishtp_cl = ishtp_get_drvdata(cl_device);
struct ishtp_cl_data *client_data = ishtp_get_client_data(hid_ishtp_cl);
hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__,
hid_ishtp_cl);
queue_work(ishtp_get_workqueue(cl_device), &client_data->work);
return 0;
}
/**
* hid_ishtp_cl_suspend() - ISHTP client driver suspend
* @device: device instance
*
* This function gets called on system suspend
*
* Return: 0
*/
static int hid_ishtp_cl_suspend(struct device *device)
{
struct ishtp_cl_device *cl_device = ishtp_dev_to_cl_device(device);
struct ishtp_cl *hid_ishtp_cl = ishtp_get_drvdata(cl_device);
struct ishtp_cl_data *client_data = ishtp_get_client_data(hid_ishtp_cl);
hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__,
hid_ishtp_cl);
client_data->suspended = true;
return 0;
}
/**
* hid_ishtp_cl_resume() - ISHTP client driver resume
* @device: device instance
*
* This function gets called on system resume
*
* Return: 0
*/
static int hid_ishtp_cl_resume(struct device *device)
{
struct ishtp_cl_device *cl_device = ishtp_dev_to_cl_device(device);
struct ishtp_cl *hid_ishtp_cl = ishtp_get_drvdata(cl_device);
struct ishtp_cl_data *client_data = ishtp_get_client_data(hid_ishtp_cl);
hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__,
hid_ishtp_cl);
queue_work(ishtp_get_workqueue(cl_device), &client_data->resume_work);
return 0;
}
static const struct dev_pm_ops hid_ishtp_pm_ops = {
.suspend = hid_ishtp_cl_suspend,
.resume = hid_ishtp_cl_resume,
};
static struct ishtp_cl_driver hid_ishtp_cl_driver = {
.name = "ish-hid",
.id = hid_ishtp_id_table,
.probe = hid_ishtp_cl_probe,
.remove = hid_ishtp_cl_remove,
.reset = hid_ishtp_cl_reset,
.driver.pm = &hid_ishtp_pm_ops,
};
static int __init ish_hid_init(void)
{
int rv;
/* Register ISHTP client device driver with ISHTP Bus */
rv = ishtp_cl_driver_register(&hid_ishtp_cl_driver, THIS_MODULE);
return rv;
}
static void __exit ish_hid_exit(void)
{
ishtp_cl_driver_unregister(&hid_ishtp_cl_driver);
}
late_initcall(ish_hid_init);
module_exit(ish_hid_exit);
MODULE_DESCRIPTION("ISH ISHTP HID client driver");
/* Primary author */
MODULE_AUTHOR("Daniel Drubin <daniel.drubin@intel.com>");
/*
* Several modification for multi instance support
* suspend/resume and clean up
*/
MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
MODULE_LICENSE("GPL");
@@ -0,0 +1,275 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* ISHTP-HID glue driver.
*
* Copyright (c) 2012-2016, Intel Corporation.
*/
#include <linux/hid.h>
#include <linux/intel-ish-client-if.h>
#include <uapi/linux/input.h>
#include "ishtp-hid.h"
/**
* ishtp_hid_parse() - hid-core .parse() callback
* @hid: hid device instance
*
* This function gets called during call to hid_add_device
*
* Return: 0 on success and non zero on error
*/
static int ishtp_hid_parse(struct hid_device *hid)
{
struct ishtp_hid_data *hid_data = hid->driver_data;
struct ishtp_cl_data *client_data = hid_data->client_data;
int rv;
rv = hid_parse_report(hid, client_data->report_descr[hid_data->index],
client_data->report_descr_size[hid_data->index]);
if (rv)
return rv;
return 0;
}
/* Empty callbacks with success return code */
static int ishtp_hid_start(struct hid_device *hid)
{
return 0;
}
static void ishtp_hid_stop(struct hid_device *hid)
{
}
static int ishtp_hid_open(struct hid_device *hid)
{
return 0;
}
static void ishtp_hid_close(struct hid_device *hid)
{
}
static int ishtp_raw_request(struct hid_device *hid, unsigned char reportnum,
__u8 *buf, size_t len, unsigned char rtype,
int reqtype)
{
struct ishtp_hid_data *hid_data = hid->driver_data;
char *ishtp_buf = NULL;
size_t ishtp_buf_len;
unsigned int header_size = sizeof(struct hostif_msg);
if (rtype == HID_OUTPUT_REPORT)
return -EINVAL;
hid_data->request_done = false;
switch (reqtype) {
case HID_REQ_GET_REPORT:
hid_data->raw_buf = buf;
hid_data->raw_buf_size = len;
hid_data->raw_get_req = true;
hid_ishtp_get_report(hid, reportnum, rtype);
break;
case HID_REQ_SET_REPORT:
/*
* Spare 7 bytes for 64b accesses through
* get/put_unaligned_le64()
*/
ishtp_buf_len = len + header_size;
ishtp_buf = kzalloc(ishtp_buf_len + 7, GFP_KERNEL);
if (!ishtp_buf)
return -ENOMEM;
memcpy(ishtp_buf + header_size, buf, len);
hid_ishtp_set_feature(hid, ishtp_buf, ishtp_buf_len, reportnum);
kfree(ishtp_buf);
break;
}
hid_hw_wait(hid);
return len;
}
/**
* ishtp_hid_request() - hid-core .request() callback
* @hid: hid device instance
* @rep: pointer to hid_report
* @reqtype: type of req. [GET|SET]_REPORT
*
* This function is used to set/get feaure/input report.
*/
static void ishtp_hid_request(struct hid_device *hid, struct hid_report *rep,
int reqtype)
{
struct ishtp_hid_data *hid_data = hid->driver_data;
/* the specific report length, just HID part of it */
unsigned int len = ((rep->size - 1) >> 3) + 1 + (rep->id > 0);
char *buf;
unsigned int header_size = sizeof(struct hostif_msg);
len += header_size;
hid_data->request_done = false;
switch (reqtype) {
case HID_REQ_GET_REPORT:
hid_data->raw_get_req = false;
hid_ishtp_get_report(hid, rep->id, rep->type);
break;
case HID_REQ_SET_REPORT:
/*
* Spare 7 bytes for 64b accesses through
* get/put_unaligned_le64()
*/
buf = kzalloc(len + 7, GFP_KERNEL);
if (!buf)
return;
hid_output_report(rep, buf + header_size);
hid_ishtp_set_feature(hid, buf, len, rep->id);
kfree(buf);
break;
}
}
/**
* ishtp_wait_for_response() - hid-core .wait() callback
* @hid: hid device instance
*
* This function is used to wait after get feaure/input report.
*
* Return: 0 on success and non zero on error
*/
static int ishtp_wait_for_response(struct hid_device *hid)
{
struct ishtp_hid_data *hid_data = hid->driver_data;
int rv;
hid_ishtp_trace(client_data, "%s hid %p\n", __func__, hid);
rv = ishtp_hid_link_ready_wait(hid_data->client_data);
if (rv)
return rv;
if (!hid_data->request_done)
wait_event_interruptible_timeout(hid_data->hid_wait,
hid_data->request_done, 3 * HZ);
if (!hid_data->request_done) {
hid_err(hid,
"timeout waiting for response from ISHTP device\n");
return -ETIMEDOUT;
}
hid_ishtp_trace(client_data, "%s hid %p done\n", __func__, hid);
hid_data->request_done = false;
return 0;
}
/**
* ishtp_hid_wakeup() - Wakeup caller
* @hid: hid device instance
*
* This function will wakeup caller waiting for Get/Set feature report
*/
void ishtp_hid_wakeup(struct hid_device *hid)
{
struct ishtp_hid_data *hid_data = hid->driver_data;
hid_data->request_done = true;
wake_up_interruptible(&hid_data->hid_wait);
}
static const struct hid_ll_driver ishtp_hid_ll_driver = {
.parse = ishtp_hid_parse,
.start = ishtp_hid_start,
.stop = ishtp_hid_stop,
.open = ishtp_hid_open,
.close = ishtp_hid_close,
.request = ishtp_hid_request,
.wait = ishtp_wait_for_response,
.raw_request = ishtp_raw_request
};
/**
* ishtp_hid_probe() - hid register ll driver
* @cur_hid_dev: Index of hid device calling to register
* @client_data: Client data pointer
*
* This function is used to allocate and add HID device.
*
* Return: 0 on success, non zero on error
*/
int ishtp_hid_probe(unsigned int cur_hid_dev,
struct ishtp_cl_data *client_data)
{
int rv;
struct hid_device *hid;
struct ishtp_hid_data *hid_data;
hid = hid_allocate_device();
if (IS_ERR(hid))
return PTR_ERR(hid);
hid_data = kzalloc_obj(*hid_data);
if (!hid_data) {
rv = -ENOMEM;
goto err_hid_data;
}
hid_data->index = cur_hid_dev;
hid_data->client_data = client_data;
init_waitqueue_head(&hid_data->hid_wait);
hid->driver_data = hid_data;
client_data->hid_sensor_hubs[cur_hid_dev] = hid;
hid->ll_driver = &ishtp_hid_ll_driver;
hid->bus = BUS_INTEL_ISHTP;
hid->dev.parent = ishtp_device(client_data->cl_device);
hid->version = le16_to_cpu(ISH_HID_VERSION);
hid->vendor = le16_to_cpu(client_data->hid_devices[cur_hid_dev].vid);
hid->product = le16_to_cpu(client_data->hid_devices[cur_hid_dev].pid);
snprintf(hid->name, sizeof(hid->name), "%s %04X:%04X", "hid-ishtp",
hid->vendor, hid->product);
rv = hid_add_device(hid);
if (rv)
goto err_hid_device;
hid_ishtp_trace(client_data, "%s allocated hid %p\n", __func__, hid);
return 0;
err_hid_device:
kfree(hid_data);
err_hid_data:
hid_destroy_device(hid);
return rv;
}
/**
* ishtp_hid_remove() - Remove registered hid device
* @client_data: client data pointer
*
* This function is used to destroy allocatd HID device.
*/
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]) {
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;
}
}
}
@@ -0,0 +1,181 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* ISHTP-HID glue driver's definitions.
*
* Copyright (c) 2014-2016, Intel Corporation.
*/
#ifndef ISHTP_HID__H
#define ISHTP_HID__H
/* The fixed ISH product and vendor id */
#define ISH_HID_VENDOR 0x8086
#define ISH_HID_PRODUCT 0x22D8
#define ISH_HID_VERSION 0x0200
#define CMD_MASK 0x7F
#define IS_RESPONSE 0x80
/* Used to dump to Linux trace buffer, if enabled */
extern ishtp_print_log ishtp_hid_print_trace;
#define hid_ishtp_trace(client, ...) \
(ishtp_hid_print_trace)(NULL, __VA_ARGS__)
/* ISH HID message structure */
struct hostif_msg_hdr {
uint8_t command; /* Bit 7: is_response */
uint8_t device_id;
uint8_t status;
uint8_t flags;
uint16_t size;
} __packed;
struct hostif_msg {
struct hostif_msg_hdr hdr;
uint8_t payload[];
} __packed;
struct hostif_msg_to_sensor {
struct hostif_msg_hdr hdr;
uint8_t report_id;
} __packed;
struct device_info {
uint32_t dev_id;
uint8_t dev_class;
uint16_t pid;
uint16_t vid;
} __packed;
struct ishtp_version {
uint8_t major;
uint8_t minor;
uint8_t hotfix;
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 report reports[];
} __packed;
/* HOSTIF commands */
#define HOSTIF_HID_COMMAND_BASE 0
#define HOSTIF_GET_HID_DESCRIPTOR 0
#define HOSTIF_GET_REPORT_DESCRIPTOR 1
#define HOSTIF_GET_FEATURE_REPORT 2
#define HOSTIF_SET_FEATURE_REPORT 3
#define HOSTIF_GET_INPUT_REPORT 4
#define HOSTIF_PUBLISH_INPUT_REPORT 5
#define HOSTIF_PUBLISH_INPUT_REPORT_LIST 6
#define HOSTIF_DM_COMMAND_BASE 32
#define HOSTIF_DM_ENUM_DEVICES 33
#define HOSTIF_DM_ADD_DEVICE 34
#define MAX_HID_DEVICES 32
/**
* struct ishtp_cl_data - Encapsulate per ISH TP HID Client
* @enum_device_done: Enum devices response complete flag
* @hid_descr_done: HID descriptor complete flag
* @report_descr_done: Get report descriptor complete flag
* @init_done: Init process completed successfully
* @suspended: System is under suspend state or in progress
* @num_hid_devices: Number of HID devices enumerated in this client
* @cur_hid_dev: This keeps track of the device index for which
* initialization and registration with HID core
* in progress.
* @hid_devices: Store vid/pid/devid for each enumerated HID device
* @report_descr: Stores the raw report descriptors for each HID device
* @report_descr_size: Report description of size of above repo_descr[]
* @hid_sensor_hubs: Pointer to hid_device for all HID device, so that
* when clients are removed, they can be freed
* @hid_descr: Pointer to hid descriptor for each enumerated hid
* device
* @hid_descr_size: Size of each above report descriptor
* @init_wait: Wait queue to wait during initialization, where the
* client send message to ISH FW and wait for response
* @ishtp_hid_wait: The wait for get report during wait callback from hid
* core
* @bad_recv_cnt: Running count of packets received with error
* @multi_packet_cnt: Count of fragmented packet count
*
* This structure is used to store completion flags and per client data like
* report description, number of HID devices etc.
*/
struct ishtp_cl_data {
/* completion flags */
bool enum_devices_done;
bool hid_descr_done;
bool report_descr_done;
bool init_done;
bool suspended;
unsigned int num_hid_devices;
unsigned int cur_hid_dev;
unsigned int hid_dev_count;
struct device_info *hid_devices;
unsigned char *report_descr[MAX_HID_DEVICES];
int report_descr_size[MAX_HID_DEVICES];
struct hid_device *hid_sensor_hubs[MAX_HID_DEVICES];
unsigned char *hid_descr[MAX_HID_DEVICES];
int hid_descr_size[MAX_HID_DEVICES];
wait_queue_head_t init_wait;
wait_queue_head_t ishtp_resume_wait;
struct ishtp_cl *hid_ishtp_cl;
/* Statistics */
unsigned int bad_recv_cnt;
int multi_packet_cnt;
struct work_struct work;
struct work_struct resume_work;
struct ishtp_cl_device *cl_device;
};
/**
* struct ishtp_hid_data - Per instance HID data
* @index: Device index in the order of enumeration
* @request_done: Get Feature/Input report complete flag
* used during get/set request from hid core
* @client_data: Link to the client instance
* @hid_wait: Completion waitq
*
* @raw_get_req: Flag indicating raw get request ongoing
* @raw_buf: raw request buffer filled on receiving get report
* @raw_buf_size: raw request buffer size
* Used to tie hid hid->driver data to driver client instance
*/
struct ishtp_hid_data {
int index;
bool request_done;
struct ishtp_cl_data *client_data;
wait_queue_head_t hid_wait;
/* raw request */
bool raw_get_req;
u8 *raw_buf;
size_t raw_buf_size;
};
/* Interface functions between HID LL driver and ISH TP client */
void hid_ishtp_set_feature(struct hid_device *hid, char *buf, unsigned int len,
int report_id);
void hid_ishtp_get_report(struct hid_device *hid, int report_id,
int report_type);
int ishtp_hid_probe(unsigned int cur_hid_dev,
struct ishtp_cl_data *client_data);
void ishtp_hid_remove(struct ishtp_cl_data *client_data);
int ishtp_hid_link_ready_wait(struct ishtp_cl_data *client_data);
void ishtp_hid_wakeup(struct hid_device *hid);
#endif /* ISHTP_HID__H */
@@ -0,0 +1,957 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* ISHTP bus driver
*
* Copyright (c) 2012-2016, Intel Corporation.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include "bus.h"
#include "ishtp-dev.h"
#include "client.h"
#include "hbm.h"
static int ishtp_use_dma;
module_param_named(ishtp_use_dma, ishtp_use_dma, int, 0600);
MODULE_PARM_DESC(ishtp_use_dma, "Use DMA to send messages");
#define to_ishtp_cl_driver(d) container_of(d, struct ishtp_cl_driver, driver)
#define to_ishtp_cl_device(d) container_of(d, struct ishtp_cl_device, dev)
static bool ishtp_device_ready;
/**
* ishtp_recv() - process ishtp message
* @dev: ishtp device
*
* If a message with valid header and size is received, then
* this function calls appropriate handler. The host or firmware
* address is zero, then they are host bus management message,
* otherwise they are message fo clients.
*/
void ishtp_recv(struct ishtp_device *dev)
{
uint32_t msg_hdr;
struct ishtp_msg_hdr *ishtp_hdr;
/* Read ISHTP header dword */
msg_hdr = dev->ops->ishtp_read_hdr(dev);
if (!msg_hdr)
return;
dev->ops->sync_fw_clock(dev);
ishtp_hdr = (struct ishtp_msg_hdr *)&msg_hdr;
dev->ishtp_msg_hdr = msg_hdr;
/* Sanity check: ISHTP frag. length in header */
if (ishtp_hdr->length > dev->mtu) {
dev_err(dev->devc,
"ISHTP hdr - bad length: %u; dropped [%08X]\n",
(unsigned int)ishtp_hdr->length, msg_hdr);
return;
}
/* ISHTP bus message */
if (!ishtp_hdr->host_addr && !ishtp_hdr->fw_addr)
recv_hbm(dev, ishtp_hdr);
/* ISHTP fixed-client message */
else if (!ishtp_hdr->host_addr)
recv_fixed_cl_msg(dev, ishtp_hdr);
else
/* ISHTP client message */
recv_ishtp_cl_msg(dev, ishtp_hdr);
}
EXPORT_SYMBOL(ishtp_recv);
/**
* ishtp_send_msg() - Send ishtp message
* @dev: ishtp device
* @hdr: Message header
* @msg: Message contents
* @ipc_send_compl: completion callback
* @ipc_send_compl_prm: completion callback parameter
*
* Send a multi fragment message via IPC. After sending the first fragment
* the completion callback is called to schedule transmit of next fragment.
*
* Return: This returns IPC send message status.
*/
int ishtp_send_msg(struct ishtp_device *dev, struct ishtp_msg_hdr *hdr,
void *msg, void(*ipc_send_compl)(void *),
void *ipc_send_compl_prm)
{
unsigned char ipc_msg[IPC_FULL_MSG_SIZE];
uint32_t drbl_val;
drbl_val = dev->ops->ipc_get_header(dev, hdr->length +
sizeof(struct ishtp_msg_hdr),
1);
memcpy(ipc_msg, &drbl_val, sizeof(uint32_t));
memcpy(ipc_msg + sizeof(uint32_t), hdr, sizeof(uint32_t));
memcpy(ipc_msg + 2 * sizeof(uint32_t), msg, hdr->length);
return dev->ops->write(dev, ipc_send_compl, ipc_send_compl_prm,
ipc_msg, 2 * sizeof(uint32_t) + hdr->length);
}
/**
* ishtp_write_message() - Send ishtp single fragment message
* @dev: ishtp device
* @hdr: Message header
* @buf: message data
*
* Send a single fragment message via IPC. This returns IPC send message
* status.
*
* Return: This returns IPC send message status.
*/
int ishtp_write_message(struct ishtp_device *dev, struct ishtp_msg_hdr *hdr,
void *buf)
{
return ishtp_send_msg(dev, hdr, buf, NULL, NULL);
}
/**
* ishtp_fw_cl_by_uuid() - locate index of fw client
* @dev: ishtp device
* @uuid: uuid of the client to search
*
* Search firmware client using UUID.
*
* Return: fw client index or -ENOENT if not found
*/
int ishtp_fw_cl_by_uuid(struct ishtp_device *dev, const guid_t *uuid)
{
unsigned int i;
for (i = 0; i < dev->fw_clients_num; ++i) {
if (guid_equal(uuid, &dev->fw_clients[i].props.protocol_name))
return i;
}
return -ENOENT;
}
EXPORT_SYMBOL(ishtp_fw_cl_by_uuid);
/**
* ishtp_fw_cl_get_client() - return client information to client
* @dev: the ishtp device structure
* @uuid: uuid of the client to search
*
* Search firmware client using UUID and reture related client information.
*
* Return: pointer of client information on success, NULL on failure.
*/
struct ishtp_fw_client *ishtp_fw_cl_get_client(struct ishtp_device *dev,
const guid_t *uuid)
{
int i;
unsigned long flags;
spin_lock_irqsave(&dev->fw_clients_lock, flags);
i = ishtp_fw_cl_by_uuid(dev, uuid);
spin_unlock_irqrestore(&dev->fw_clients_lock, flags);
if (i < 0 || dev->fw_clients[i].props.fixed_address)
return NULL;
return &dev->fw_clients[i];
}
EXPORT_SYMBOL(ishtp_fw_cl_get_client);
/**
* ishtp_get_fw_client_id() - Get fw client id
* @fw_client: firmware client used to fetch the ID
*
* This interface is used to reset HW get FW client id.
*
* Return: firmware client id.
*/
int ishtp_get_fw_client_id(struct ishtp_fw_client *fw_client)
{
return fw_client->client_id;
}
EXPORT_SYMBOL(ishtp_get_fw_client_id);
/**
* ishtp_fw_cl_by_id() - return index to fw_clients for client_id
* @dev: the ishtp device structure
* @client_id: fw client id to search
*
* Search firmware client using client id.
*
* Return: index on success, -ENOENT on failure.
*/
int ishtp_fw_cl_by_id(struct ishtp_device *dev, uint8_t client_id)
{
int i, res = -ENOENT;
unsigned long flags;
spin_lock_irqsave(&dev->fw_clients_lock, flags);
for (i = 0; i < dev->fw_clients_num; i++) {
if (dev->fw_clients[i].client_id == client_id) {
res = i;
break;
}
}
spin_unlock_irqrestore(&dev->fw_clients_lock, flags);
return res;
}
/**
* ishtp_cl_device_probe() - Bus probe() callback
* @dev: the device structure
*
* This is a bus probe callback and calls the drive probe function.
*
* Return: Return value from driver probe() call.
*/
static int ishtp_cl_device_probe(struct device *dev)
{
struct ishtp_cl_device *device = to_ishtp_cl_device(dev);
struct ishtp_cl_driver *driver;
if (!device)
return 0;
driver = to_ishtp_cl_driver(dev->driver);
if (!driver || !driver->probe)
return -ENODEV;
return driver->probe(device);
}
/**
* ishtp_cl_bus_match() - Bus match() callback
* @dev: the device structure
* @drv: the driver structure
*
* This is a bus match callback, called when a new ishtp_cl_device is
* registered during ishtp bus client enumeration. Use the guid_t in
* drv and dev to decide whether they match or not.
*
* Return: 1 if dev & drv matches, 0 otherwise.
*/
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);
struct ishtp_fw_client *client = device->fw_client;
const struct ishtp_device_id *id;
if (client) {
for (id = driver->id; !guid_is_null(&id->guid); id++) {
if (guid_equal(&id->guid, &client->props.protocol_name))
return 1;
}
}
return 0;
}
/**
* ishtp_cl_device_remove() - Bus remove() callback
* @dev: the device structure
*
* This is a bus remove callback and calls the drive remove function.
* Since the ISH driver model supports only built in, this is
* primarily can be called during pci driver init failure.
*
* Return: Return value from driver remove() call.
*/
static void ishtp_cl_device_remove(struct device *dev)
{
struct ishtp_cl_device *device = to_ishtp_cl_device(dev);
struct ishtp_cl_driver *driver = to_ishtp_cl_driver(dev->driver);
if (device->event_cb) {
device->event_cb = NULL;
cancel_work_sync(&device->event_work);
}
if (driver->remove)
driver->remove(device);
}
/**
* ishtp_cl_device_suspend() - Bus suspend callback
* @dev: device
*
* Called during device suspend process.
*
* Return: Return value from driver suspend() call.
*/
static int ishtp_cl_device_suspend(struct device *dev)
{
struct ishtp_cl_device *device = to_ishtp_cl_device(dev);
struct ishtp_cl_driver *driver;
int ret = 0;
if (!device)
return 0;
driver = to_ishtp_cl_driver(dev->driver);
if (driver && driver->driver.pm) {
if (driver->driver.pm->suspend)
ret = driver->driver.pm->suspend(dev);
}
return ret;
}
/**
* ishtp_cl_device_resume() - Bus resume callback
* @dev: device
*
* Called during device resume process.
*
* Return: Return value from driver resume() call.
*/
static int ishtp_cl_device_resume(struct device *dev)
{
struct ishtp_cl_device *device = to_ishtp_cl_device(dev);
struct ishtp_cl_driver *driver;
int ret = 0;
if (!device)
return 0;
driver = to_ishtp_cl_driver(dev->driver);
if (driver && driver->driver.pm) {
if (driver->driver.pm->resume)
ret = driver->driver.pm->resume(dev);
}
return ret;
}
/**
* ishtp_cl_device_reset() - Reset callback
* @device: ishtp client device instance
*
* This is a callback when HW reset is done and the device need
* reinit.
*
* Return: Return value from driver reset() call.
*/
static int ishtp_cl_device_reset(struct ishtp_cl_device *device)
{
struct ishtp_cl_driver *driver;
int ret = 0;
device->event_cb = NULL;
cancel_work_sync(&device->event_work);
driver = to_ishtp_cl_driver(device->dev.driver);
if (driver && driver->reset)
ret = driver->reset(device);
return ret;
}
static ssize_t modalias_show(struct device *dev, struct device_attribute *a,
char *buf)
{
int len;
len = snprintf(buf, PAGE_SIZE, ISHTP_MODULE_PREFIX "%s\n", dev_name(dev));
return (len >= PAGE_SIZE) ? (PAGE_SIZE - 1) : len;
}
static DEVICE_ATTR_RO(modalias);
static struct attribute *ishtp_cl_dev_attrs[] = {
&dev_attr_modalias.attr,
NULL,
};
ATTRIBUTE_GROUPS(ishtp_cl_dev);
static int ishtp_cl_uevent(const struct device *dev, struct kobj_uevent_env *env)
{
if (add_uevent_var(env, "MODALIAS=" ISHTP_MODULE_PREFIX "%s", dev_name(dev)))
return -ENOMEM;
return 0;
}
static const struct dev_pm_ops ishtp_cl_bus_dev_pm_ops = {
/* Suspend callbacks */
.suspend = ishtp_cl_device_suspend,
.resume = ishtp_cl_device_resume,
/* Hibernate callbacks */
.freeze = ishtp_cl_device_suspend,
.thaw = ishtp_cl_device_resume,
.restore = ishtp_cl_device_resume,
};
static const struct bus_type ishtp_cl_bus_type = {
.name = "ishtp",
.dev_groups = ishtp_cl_dev_groups,
.probe = ishtp_cl_device_probe,
.match = ishtp_cl_bus_match,
.remove = ishtp_cl_device_remove,
.pm = &ishtp_cl_bus_dev_pm_ops,
.uevent = ishtp_cl_uevent,
};
static void ishtp_cl_dev_release(struct device *dev)
{
kfree(to_ishtp_cl_device(dev));
}
static const struct device_type ishtp_cl_device_type = {
.release = ishtp_cl_dev_release,
};
/**
* ishtp_bus_add_device() - Function to create device on bus
* @dev: ishtp device
* @uuid: uuid of the client
* @name: Name of the client
*
* Allocate ISHTP bus client device, attach it to uuid
* and register with ISHTP bus.
*
* Return: ishtp_cl_device pointer or NULL on failure
*/
static struct ishtp_cl_device *ishtp_bus_add_device(struct ishtp_device *dev,
guid_t uuid, char *name)
{
struct ishtp_cl_device *device;
int status;
unsigned long flags;
spin_lock_irqsave(&dev->device_list_lock, flags);
list_for_each_entry(device, &dev->device_list, device_link) {
if (!strcmp(name, dev_name(&device->dev))) {
device->fw_client = &dev->fw_clients[
dev->fw_client_presentation_num - 1];
spin_unlock_irqrestore(&dev->device_list_lock, flags);
ishtp_cl_device_reset(device);
return device;
}
}
spin_unlock_irqrestore(&dev->device_list_lock, flags);
device = kzalloc_obj(struct ishtp_cl_device);
if (!device)
return NULL;
device->dev.parent = dev->devc;
device->dev.bus = &ishtp_cl_bus_type;
device->dev.type = &ishtp_cl_device_type;
device->ishtp_dev = dev;
device->fw_client =
&dev->fw_clients[dev->fw_client_presentation_num - 1];
dev_set_name(&device->dev, "%s", name);
spin_lock_irqsave(&dev->device_list_lock, flags);
list_add_tail(&device->device_link, &dev->device_list);
spin_unlock_irqrestore(&dev->device_list_lock, flags);
status = device_register(&device->dev);
if (status) {
spin_lock_irqsave(&dev->device_list_lock, flags);
list_del(&device->device_link);
spin_unlock_irqrestore(&dev->device_list_lock, flags);
dev_err(dev->devc, "Failed to register ISHTP client device\n");
put_device(&device->dev);
return NULL;
}
ishtp_device_ready = true;
return device;
}
/**
* ishtp_bus_remove_device() - Function to relase device on bus
* @device: client device instance
*
* This is a counterpart of ishtp_bus_add_device.
* Device is unregistered.
* the device structure is freed in 'ishtp_cl_dev_release' function
* Called only during error in pci driver init path.
*/
static void ishtp_bus_remove_device(struct ishtp_cl_device *device)
{
device_unregister(&device->dev);
}
/**
* ishtp_cl_driver_register() - Client driver register
* @driver: the client driver instance
* @owner: Owner of this driver module
*
* Once a client driver is probed, it created a client
* instance and registers with the bus.
*
* Return: Return value of driver_register or -ENODEV if not ready
*/
int ishtp_cl_driver_register(struct ishtp_cl_driver *driver,
struct module *owner)
{
if (!ishtp_device_ready)
return -ENODEV;
driver->driver.name = driver->name;
driver->driver.owner = owner;
driver->driver.bus = &ishtp_cl_bus_type;
return driver_register(&driver->driver);
}
EXPORT_SYMBOL(ishtp_cl_driver_register);
/**
* ishtp_cl_driver_unregister() - Client driver unregister
* @driver: the client driver instance
*
* Unregister client during device removal process.
*/
void ishtp_cl_driver_unregister(struct ishtp_cl_driver *driver)
{
driver_unregister(&driver->driver);
}
EXPORT_SYMBOL(ishtp_cl_driver_unregister);
/**
* ishtp_bus_event_work() - event work function
* @work: work struct pointer
*
* Once an event is received for a client this work
* function is called. If the device has registered a
* callback then the callback is called.
*/
static void ishtp_bus_event_work(struct work_struct *work)
{
struct ishtp_cl_device *device;
device = container_of(work, struct ishtp_cl_device, event_work);
if (device->event_cb)
device->event_cb(device);
}
/**
* ishtp_cl_bus_rx_event() - schedule event work
* @device: client device instance
*
* Once an event is received for a client this schedules
* a work function to process.
*/
void ishtp_cl_bus_rx_event(struct ishtp_cl_device *device)
{
if (!device || !device->event_cb)
return;
if (device->event_cb)
queue_work(device->ishtp_dev->unbound_wq, &device->event_work);
}
/**
* ishtp_register_event_cb() - Register callback
* @device: client device instance
* @event_cb: Event processor for an client
*
* Register a callback for events, called from client driver
*
* Return: Return 0 or -EALREADY if already registered
*/
int ishtp_register_event_cb(struct ishtp_cl_device *device,
void (*event_cb)(struct ishtp_cl_device *))
{
if (device->event_cb)
return -EALREADY;
device->event_cb = event_cb;
INIT_WORK(&device->event_work, ishtp_bus_event_work);
return 0;
}
EXPORT_SYMBOL(ishtp_register_event_cb);
/**
* ishtp_get_device() - update usage count for the device
* @cl_device: client device instance
*
* Increment the usage count. The device can't be deleted
*/
void ishtp_get_device(struct ishtp_cl_device *cl_device)
{
cl_device->reference_count++;
}
EXPORT_SYMBOL(ishtp_get_device);
/**
* ishtp_put_device() - decrement usage count for the device
* @cl_device: client device instance
*
* Decrement the usage count. The device can be deleted is count = 0
*/
void ishtp_put_device(struct ishtp_cl_device *cl_device)
{
cl_device->reference_count--;
}
EXPORT_SYMBOL(ishtp_put_device);
/**
* ishtp_set_drvdata() - set client driver data
* @cl_device: client device instance
* @data: driver data need to be set
*
* Set client driver data to cl_device->driver_data.
*/
void ishtp_set_drvdata(struct ishtp_cl_device *cl_device, void *data)
{
cl_device->driver_data = data;
}
EXPORT_SYMBOL(ishtp_set_drvdata);
/**
* ishtp_get_drvdata() - get client driver data
* @cl_device: client device instance
*
* Get client driver data from cl_device->driver_data.
*
* Return: pointer of driver data
*/
void *ishtp_get_drvdata(struct ishtp_cl_device *cl_device)
{
return cl_device->driver_data;
}
EXPORT_SYMBOL(ishtp_get_drvdata);
/**
* ishtp_dev_to_cl_device() - get ishtp_cl_device instance from device instance
* @device: device instance
*
* Get ish_cl_device instance which embeds device instance in it.
*
* Return: pointer to ishtp_cl_device instance
*/
struct ishtp_cl_device *ishtp_dev_to_cl_device(struct device *device)
{
return to_ishtp_cl_device(device);
}
EXPORT_SYMBOL(ishtp_dev_to_cl_device);
/**
* ishtp_bus_new_client() - Create a new client
* @dev: ISHTP device instance
*
* Once bus protocol enumerates a client, this is called
* to add a device for the client.
*
* Return: 0 on success or error code on failure
*/
int ishtp_bus_new_client(struct ishtp_device *dev)
{
int i;
char *dev_name;
struct ishtp_cl_device *cl_device;
guid_t device_uuid;
/*
* For all reported clients, create an unconnected client and add its
* device to ISHTP bus.
* If appropriate driver has loaded, this will trigger its probe().
* Otherwise, probe() will be called when driver is loaded
*/
i = dev->fw_client_presentation_num - 1;
device_uuid = dev->fw_clients[i].props.protocol_name;
dev_name = kasprintf(GFP_KERNEL, "{%pUL}", &device_uuid);
if (!dev_name)
return -ENOMEM;
cl_device = ishtp_bus_add_device(dev, device_uuid, dev_name);
if (!cl_device) {
kfree(dev_name);
return -ENOENT;
}
kfree(dev_name);
return 0;
}
/**
* ishtp_cl_device_bind() - bind a device
* @cl: ishtp client device
*
* Binds connected ishtp_cl to ISHTP bus device
*
* Return: 0 on success or fault code
*/
int ishtp_cl_device_bind(struct ishtp_cl *cl)
{
struct ishtp_cl_device *cl_device;
unsigned long flags;
int rv;
if (!cl->fw_client_id || cl->state != ISHTP_CL_CONNECTED)
return -EFAULT;
rv = -ENOENT;
spin_lock_irqsave(&cl->dev->device_list_lock, flags);
list_for_each_entry(cl_device, &cl->dev->device_list,
device_link) {
if (cl_device->fw_client &&
cl_device->fw_client->client_id == cl->fw_client_id) {
cl->device = cl_device;
rv = 0;
break;
}
}
spin_unlock_irqrestore(&cl->dev->device_list_lock, flags);
return rv;
}
/**
* ishtp_bus_remove_all_clients() - Remove all clients
* @ishtp_dev: ishtp device
* @warm_reset: Reset due to FW reset dure to errors or S3 suspend
*
* This is part of reset/remove flow. This function the main processing
* only targets error processing, if the FW has forced reset or
* error to remove connected clients. When warm reset the client devices are
* not removed.
*/
void ishtp_bus_remove_all_clients(struct ishtp_device *ishtp_dev,
bool warm_reset)
{
struct ishtp_cl_device *cl_device, *n;
struct ishtp_cl *cl;
unsigned long flags;
spin_lock_irqsave(&ishtp_dev->cl_list_lock, flags);
list_for_each_entry(cl, &ishtp_dev->cl_list, link) {
cl->state = ISHTP_CL_DISCONNECTED;
if (warm_reset && cl->device && cl->device->reference_count)
continue;
/*
* Wake any pending process. The waiter would check dev->state
* and determine that it's not enabled already,
* and will return error to its caller
*/
wake_up_interruptible(&cl->wait_ctrl_res);
/* Disband any pending read/write requests and free rb */
ishtp_cl_flush_queues(cl);
/* Remove all free and in_process rings, both Rx and Tx */
ishtp_cl_free_rx_ring(cl);
ishtp_cl_free_tx_ring(cl);
/*
* Free client and ISHTP bus client device structures
* don't free host client because it is part of the OS fd
* structure
*/
}
spin_unlock_irqrestore(&ishtp_dev->cl_list_lock, flags);
/* Release DMA buffers for client messages */
ishtp_cl_free_dma_buf(ishtp_dev);
/* remove bus clients */
spin_lock_irqsave(&ishtp_dev->device_list_lock, flags);
list_for_each_entry_safe(cl_device, n, &ishtp_dev->device_list,
device_link) {
cl_device->fw_client = NULL;
if (warm_reset && cl_device->reference_count)
continue;
list_del(&cl_device->device_link);
spin_unlock_irqrestore(&ishtp_dev->device_list_lock, flags);
ishtp_bus_remove_device(cl_device);
spin_lock_irqsave(&ishtp_dev->device_list_lock, flags);
}
spin_unlock_irqrestore(&ishtp_dev->device_list_lock, flags);
/* Free all client structures */
spin_lock_irqsave(&ishtp_dev->fw_clients_lock, flags);
kfree(ishtp_dev->fw_clients);
ishtp_dev->fw_clients = NULL;
ishtp_dev->fw_clients_num = 0;
ishtp_dev->fw_client_presentation_num = 0;
ishtp_dev->fw_client_index = 0;
bitmap_zero(ishtp_dev->fw_clients_map, ISHTP_CLIENTS_MAX);
spin_unlock_irqrestore(&ishtp_dev->fw_clients_lock, flags);
}
EXPORT_SYMBOL(ishtp_bus_remove_all_clients);
/**
* ishtp_reset_handler() - IPC reset handler
* @dev: ishtp device
*
* ISHTP Handler for IPC_RESET notification
*/
void ishtp_reset_handler(struct ishtp_device *dev)
{
unsigned long flags;
/* Handle FW-initiated reset */
dev->dev_state = ISHTP_DEV_RESETTING;
/* Clear BH processing queue - no further HBMs */
spin_lock_irqsave(&dev->rd_msg_spinlock, flags);
dev->rd_msg_fifo_head = dev->rd_msg_fifo_tail = 0;
spin_unlock_irqrestore(&dev->rd_msg_spinlock, flags);
/* Handle ISH FW reset against upper layers */
ishtp_bus_remove_all_clients(dev, true);
}
EXPORT_SYMBOL(ishtp_reset_handler);
/**
* ishtp_reset_compl_handler() - Reset completion handler
* @dev: ishtp device
*
* ISHTP handler for IPC_RESET sequence completion to start
* host message bus start protocol sequence.
*/
void ishtp_reset_compl_handler(struct ishtp_device *dev)
{
dev->dev_state = ISHTP_DEV_INIT_CLIENTS;
dev->hbm_state = ISHTP_HBM_START;
ishtp_hbm_start_req(dev);
}
EXPORT_SYMBOL(ishtp_reset_compl_handler);
/**
* ishtp_use_dma_transfer() - Function to use DMA
*
* This interface is used to enable usage of DMA
*
* Return non zero if DMA can be enabled
*/
int ishtp_use_dma_transfer(void)
{
return ishtp_use_dma;
}
/**
* ishtp_device() - Return device pointer
* @device: ISH-TP client device instance
*
* This interface is used to return device pointer from ishtp_cl_device
* instance.
*
* Return: device *.
*/
struct device *ishtp_device(struct ishtp_cl_device *device)
{
return &device->dev;
}
EXPORT_SYMBOL(ishtp_device);
/**
* ishtp_wait_resume() - Wait for IPC resume
* @dev: ishtp device
*
* Wait for IPC resume
*
* Return: resume complete or not
*/
bool ishtp_wait_resume(struct ishtp_device *dev)
{
/* Waiting to get resume response */
if (dev->resume_flag)
wait_event_interruptible_timeout(dev->resume_wait,
!dev->resume_flag,
msecs_to_jiffies(WAIT_FOR_RESUME_ACK_MS));
return (!dev->resume_flag);
}
EXPORT_SYMBOL_GPL(ishtp_wait_resume);
/**
* ishtp_get_pci_device() - Return PCI device dev pointer
* This interface is used to return PCI device pointer
* from ishtp_cl_device instance.
* @device: ISH-TP client device instance
*
* Return: device *.
*/
struct device *ishtp_get_pci_device(struct ishtp_cl_device *device)
{
return device->ishtp_dev->devc;
}
EXPORT_SYMBOL(ishtp_get_pci_device);
/**
* ishtp_get_workqueue - Retrieve the workqueue associated with an ISHTP device
* @cl_device: Pointer to the ISHTP client device structure
*
* Returns the workqueue_struct pointer (unbound_wq) associated with the given
* ISHTP client device. This workqueue is typically used for scheduling work
* related to the device.
*
* Return: Pointer to struct workqueue_struct.
*/
struct workqueue_struct *ishtp_get_workqueue(struct ishtp_cl_device *cl_device)
{
return cl_device->ishtp_dev->unbound_wq;
}
EXPORT_SYMBOL(ishtp_get_workqueue);
/**
* ishtp_trace_callback() - Return trace callback
* @cl_device: ISH-TP client device instance
*
* This interface is used to return trace callback function pointer.
*
* Return: *ishtp_print_log()
*/
ishtp_print_log ishtp_trace_callback(struct ishtp_cl_device *cl_device)
{
return cl_device->ishtp_dev->print_log;
}
EXPORT_SYMBOL(ishtp_trace_callback);
/**
* ish_hw_reset() - Call HW reset IPC callback
* @dev: ISHTP device instance
*
* This interface is used to reset HW in case of error.
*
* Return: value from IPC hw_reset callback
*/
int ish_hw_reset(struct ishtp_device *dev)
{
return dev->ops->hw_reset(dev);
}
EXPORT_SYMBOL(ish_hw_reset);
/**
* ishtp_bus_register() - Function to register bus
*
* This register ishtp bus
*
* Return: Return output of bus_register
*/
static int __init ishtp_bus_register(void)
{
return bus_register(&ishtp_cl_bus_type);
}
/**
* ishtp_bus_unregister() - Function to unregister bus
*
* This unregister ishtp bus
*/
static void __exit ishtp_bus_unregister(void)
{
bus_unregister(&ishtp_cl_bus_type);
}
module_init(ishtp_bus_register);
module_exit(ishtp_bus_unregister);
MODULE_DESCRIPTION("ISHTP bus driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,75 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* ISHTP bus definitions
*
* Copyright (c) 2014-2016, Intel Corporation.
*/
#ifndef _LINUX_ISHTP_CL_BUS_H
#define _LINUX_ISHTP_CL_BUS_H
#include <linux/device.h>
#include <linux/device-id/ishtp.h>
#include <linux/intel-ish-client-if.h>
struct ishtp_cl;
struct ishtp_cl_device;
struct ishtp_device;
struct ishtp_msg_hdr;
/**
* struct ishtp_cl_device - ISHTP device handle
* @dev: device pointer
* @ishtp_dev: pointer to ishtp device structure to primarily to access
* hw device operation callbacks and properties
* @fw_client: fw_client pointer to get fw information like protocol name
* max message length etc.
* @device_link: Link to next client in the list on a bus
* @event_work: Used to schedule rx event for client
* @driver_data: Storage driver private data
* @reference_count: Used for get/put device
* @event_cb: Callback to driver to send events
*
* An ishtp_cl_device pointer is returned from ishtp_add_device()
* and links ISHTP bus clients to their actual host client pointer.
* Drivers for ISHTP devices will get an ishtp_cl_device pointer
* when being probed and shall use it for doing bus I/O.
*/
struct ishtp_cl_device {
struct device dev;
struct ishtp_device *ishtp_dev;
struct ishtp_fw_client *fw_client;
struct list_head device_link;
struct work_struct event_work;
void *driver_data;
int reference_count;
void (*event_cb)(struct ishtp_cl_device *device);
};
int ishtp_bus_new_client(struct ishtp_device *dev);
int ishtp_cl_device_bind(struct ishtp_cl *cl);
void ishtp_cl_bus_rx_event(struct ishtp_cl_device *device);
/* Write a multi-fragment message */
int ishtp_send_msg(struct ishtp_device *dev,
struct ishtp_msg_hdr *hdr, void *msg,
void (*ipc_send_compl)(void *),
void *ipc_send_compl_prm);
/* Write a single-fragment message */
int ishtp_write_message(struct ishtp_device *dev,
struct ishtp_msg_hdr *hdr,
void *buf);
/* Use DMA to send/receive messages */
int ishtp_use_dma_transfer(void);
/* Exported functions */
void ishtp_bus_remove_all_clients(struct ishtp_device *ishtp_dev,
bool warm_reset);
void ishtp_recv(struct ishtp_device *dev);
void ishtp_reset_handler(struct ishtp_device *dev);
void ishtp_reset_compl_handler(struct ishtp_device *dev);
int ishtp_fw_cl_by_uuid(struct ishtp_device *dev, const guid_t *cuuid);
#endif /* _LINUX_ISHTP_CL_BUS_H */
@@ -0,0 +1,276 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* ISHTP Ring Buffers
*
* Copyright (c) 2003-2016, Intel Corporation.
*/
#include <linux/slab.h>
#include "client.h"
/**
* ishtp_cl_alloc_rx_ring() - Allocate RX ring buffers
* @cl: client device instance
*
* Allocate and initialize RX ring buffers
*
* Return: 0 on success else -ENOMEM
*/
int ishtp_cl_alloc_rx_ring(struct ishtp_cl *cl)
{
size_t len = cl->device->fw_client->props.max_msg_length;
int j;
struct ishtp_cl_rb *rb;
int ret = 0;
unsigned long flags;
for (j = 0; j < cl->rx_ring_size; ++j) {
rb = ishtp_io_rb_init(cl);
if (!rb) {
ret = -ENOMEM;
goto out;
}
ret = ishtp_io_rb_alloc_buf(rb, len);
if (ret)
goto out;
spin_lock_irqsave(&cl->free_list_spinlock, flags);
list_add_tail(&rb->list, &cl->free_rb_list.list);
spin_unlock_irqrestore(&cl->free_list_spinlock, flags);
}
return 0;
out:
dev_err(&cl->device->dev, "error in allocating Rx buffers\n");
ishtp_cl_free_rx_ring(cl);
return ret;
}
/**
* ishtp_cl_alloc_tx_ring() - Allocate TX ring buffers
* @cl: client device instance
*
* Allocate and initialize TX ring buffers
*
* Return: 0 on success else -ENOMEM
*/
int ishtp_cl_alloc_tx_ring(struct ishtp_cl *cl)
{
size_t len = cl->device->fw_client->props.max_msg_length;
int j;
unsigned long flags;
cl->tx_ring_free_size = 0;
/* Allocate pool to free Tx bufs */
for (j = 0; j < cl->tx_ring_size; ++j) {
struct ishtp_cl_tx_ring *tx_buf;
tx_buf = kzalloc_obj(struct ishtp_cl_tx_ring);
if (!tx_buf)
goto out;
tx_buf->send_buf.data = kmalloc(len, GFP_KERNEL);
if (!tx_buf->send_buf.data) {
kfree(tx_buf);
goto out;
}
spin_lock_irqsave(&cl->tx_free_list_spinlock, flags);
list_add_tail(&tx_buf->list, &cl->tx_free_list.list);
++cl->tx_ring_free_size;
spin_unlock_irqrestore(&cl->tx_free_list_spinlock, flags);
}
return 0;
out:
dev_err(&cl->device->dev, "error in allocating Tx pool\n");
ishtp_cl_free_tx_ring(cl);
return -ENOMEM;
}
/**
* ishtp_cl_free_rx_ring() - Free RX ring buffers
* @cl: client device instance
*
* Free RX ring buffers
*/
void ishtp_cl_free_rx_ring(struct ishtp_cl *cl)
{
struct ishtp_cl_rb *rb;
unsigned long flags;
/* release allocated memory - pass over free_rb_list */
spin_lock_irqsave(&cl->free_list_spinlock, flags);
while (!list_empty(&cl->free_rb_list.list)) {
rb = list_entry(cl->free_rb_list.list.next, struct ishtp_cl_rb,
list);
list_del(&rb->list);
kfree(rb->buffer.data);
kfree(rb);
}
spin_unlock_irqrestore(&cl->free_list_spinlock, flags);
/* release allocated memory - pass over in_process_list */
spin_lock_irqsave(&cl->in_process_spinlock, flags);
while (!list_empty(&cl->in_process_list.list)) {
rb = list_entry(cl->in_process_list.list.next,
struct ishtp_cl_rb, list);
list_del(&rb->list);
kfree(rb->buffer.data);
kfree(rb);
}
spin_unlock_irqrestore(&cl->in_process_spinlock, flags);
}
/**
* ishtp_cl_free_tx_ring() - Free TX ring buffers
* @cl: client device instance
*
* Free TX ring buffers
*/
void ishtp_cl_free_tx_ring(struct ishtp_cl *cl)
{
struct ishtp_cl_tx_ring *tx_buf;
unsigned long flags;
spin_lock_irqsave(&cl->tx_free_list_spinlock, flags);
/* release allocated memory - pass over tx_free_list */
while (!list_empty(&cl->tx_free_list.list)) {
tx_buf = list_entry(cl->tx_free_list.list.next,
struct ishtp_cl_tx_ring, list);
list_del(&tx_buf->list);
--cl->tx_ring_free_size;
kfree(tx_buf->send_buf.data);
kfree(tx_buf);
}
spin_unlock_irqrestore(&cl->tx_free_list_spinlock, flags);
spin_lock_irqsave(&cl->tx_list_spinlock, flags);
/* release allocated memory - pass over tx_list */
while (!list_empty(&cl->tx_list.list)) {
tx_buf = list_entry(cl->tx_list.list.next,
struct ishtp_cl_tx_ring, list);
list_del(&tx_buf->list);
kfree(tx_buf->send_buf.data);
kfree(tx_buf);
}
spin_unlock_irqrestore(&cl->tx_list_spinlock, flags);
}
/**
* ishtp_io_rb_free() - Free IO request block
* @rb: IO request block
*
* Free io request block memory
*/
void ishtp_io_rb_free(struct ishtp_cl_rb *rb)
{
if (rb == NULL)
return;
kfree(rb->buffer.data);
kfree(rb);
}
/**
* ishtp_io_rb_init() - Allocate and init IO request block
* @cl: client device instance
*
* Allocate and initialize request block
*
* Return: Allocted IO request block pointer
*/
struct ishtp_cl_rb *ishtp_io_rb_init(struct ishtp_cl *cl)
{
struct ishtp_cl_rb *rb;
rb = kzalloc_obj(struct ishtp_cl_rb);
if (!rb)
return NULL;
INIT_LIST_HEAD(&rb->list);
rb->cl = cl;
rb->buf_idx = 0;
return rb;
}
/**
* ishtp_io_rb_alloc_buf() - Allocate and init response buffer
* @rb: IO request block
* @length: length of response buffer
*
* Allocate respose buffer
*
* Return: 0 on success else -ENOMEM
*/
int ishtp_io_rb_alloc_buf(struct ishtp_cl_rb *rb, size_t length)
{
if (!rb)
return -EINVAL;
if (length == 0)
return 0;
rb->buffer.data = kmalloc(length, GFP_KERNEL);
if (!rb->buffer.data)
return -ENOMEM;
rb->buffer.size = length;
return 0;
}
/**
* ishtp_cl_io_rb_recycle() - Recycle IO request blocks
* @rb: IO request block
*
* Re-append rb to its client's free list and send flow control if needed
*
* Return: 0 on success else -EFAULT
*/
int ishtp_cl_io_rb_recycle(struct ishtp_cl_rb *rb)
{
struct ishtp_cl *cl;
int rets = 0;
unsigned long flags;
if (!rb || !rb->cl)
return -EFAULT;
cl = rb->cl;
spin_lock_irqsave(&cl->free_list_spinlock, flags);
list_add_tail(&rb->list, &cl->free_rb_list.list);
spin_unlock_irqrestore(&cl->free_list_spinlock, flags);
/*
* If we returned the first buffer to empty 'free' list,
* send flow control
*/
if (!cl->out_flow_ctrl_creds)
rets = ishtp_cl_read_start(cl);
return rets;
}
EXPORT_SYMBOL(ishtp_cl_io_rb_recycle);
/**
* ishtp_cl_rx_get_rb() -Get a rb from client device rx buffer list
* @cl: Pointer to client device instance
*
* Check client device in-processing buffer list and get a rb from it.
*
* Return: rb pointer if buffer list isn't empty else NULL
*/
struct ishtp_cl_rb *ishtp_cl_rx_get_rb(struct ishtp_cl *cl)
{
unsigned long rx_flags;
struct ishtp_cl_rb *rb;
spin_lock_irqsave(&cl->in_process_spinlock, rx_flags);
rb = list_first_entry_or_null(&cl->in_process_list.list,
struct ishtp_cl_rb, list);
if (rb)
list_del_init(&rb->list);
spin_unlock_irqrestore(&cl->in_process_spinlock, rx_flags);
return rb;
}
EXPORT_SYMBOL(ishtp_cl_rx_get_rb);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,152 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* ISHTP client logic
*
* Copyright (c) 2003-2016, Intel Corporation.
*/
#ifndef _ISHTP_CLIENT_H_
#define _ISHTP_CLIENT_H_
#include <linux/types.h>
#include "ishtp-dev.h"
/* Tx and Rx ring size */
#define CL_DEF_RX_RING_SIZE 2
#define CL_DEF_TX_RING_SIZE 2
#define CL_MAX_RX_RING_SIZE 32
#define CL_MAX_TX_RING_SIZE 32
#define DMA_SLOT_SIZE 4096
/* Number of IPC fragments after which it's worth sending via DMA */
#define DMA_WORTH_THRESHOLD 3
/* DMA/IPC Tx paths. Other the default means enforcement */
#define CL_TX_PATH_DEFAULT 0
#define CL_TX_PATH_IPC 1
#define CL_TX_PATH_DMA 2
/* Client Tx buffer list entry */
struct ishtp_cl_tx_ring {
struct list_head list;
struct ishtp_msg_data send_buf;
};
/* ISHTP client instance */
struct ishtp_cl {
struct list_head link;
struct ishtp_device *dev;
enum cl_state state;
int status;
/* Link to ISHTP bus device */
struct ishtp_cl_device *device;
/* ID of client connected */
uint8_t host_client_id;
uint8_t fw_client_id;
uint8_t ishtp_flow_ctrl_creds;
uint8_t out_flow_ctrl_creds;
/* dma */
int last_tx_path;
/* 0: ack wasn't received,1:ack was received */
int last_dma_acked;
unsigned char *last_dma_addr;
/* 0: ack wasn't received,1:ack was received */
int last_ipc_acked;
/* Rx ring buffer pool */
unsigned int rx_ring_size;
struct ishtp_cl_rb free_rb_list;
spinlock_t free_list_spinlock;
/* Rx in-process list */
struct ishtp_cl_rb in_process_list;
spinlock_t in_process_spinlock;
/* Client Tx buffers list */
unsigned int tx_ring_size;
struct ishtp_cl_tx_ring tx_list, tx_free_list;
int tx_ring_free_size;
spinlock_t tx_list_spinlock;
spinlock_t tx_free_list_spinlock;
size_t tx_offs; /* Offset in buffer at head of 'tx_list' */
/**
* if we get a FC, and the list is not empty, we must know whether we
* are at the middle of sending.
* if so -need to increase FC counter, otherwise, need to start sending
* the first msg in list
* (!)This is for counting-FC implementation only. Within single-FC the
* other party may NOT send FC until it receives complete message
*/
int sending;
/* Send FC spinlock */
spinlock_t fc_spinlock;
/* wait queue for connect and disconnect response from FW */
wait_queue_head_t wait_ctrl_res;
/* Error stats */
unsigned int err_send_msg;
unsigned int err_send_fc;
/* Send/recv stats */
unsigned int send_msg_cnt_ipc;
unsigned int send_msg_cnt_dma;
unsigned int recv_msg_cnt_ipc;
unsigned int recv_msg_cnt_dma;
unsigned int recv_msg_num_frags;
unsigned int ishtp_flow_ctrl_cnt;
unsigned int out_flow_ctrl_cnt;
/* Rx msg ... out FC timing */
ktime_t ts_rx;
ktime_t ts_out_fc;
ktime_t ts_max_fc_delay;
void *client_data;
};
/* Client connection managenment internal functions */
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,
struct ishtp_msg_hdr *ishtp_hdr);
int ishtp_cl_read_start(struct ishtp_cl *cl);
/* Ring Buffer I/F */
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);
/* DMA I/F functions */
void recv_ishtp_cl_msg_dma(struct ishtp_device *dev, void *msg,
struct dma_xfer_hbm *hbm);
void ishtp_cl_alloc_dma_buf(struct ishtp_device *dev);
void ishtp_cl_free_dma_buf(struct ishtp_device *dev);
void *ishtp_cl_get_dma_send_buf(struct ishtp_device *dev,
uint32_t size);
void ishtp_cl_release_dma_acked_mem(struct ishtp_device *dev,
void *msg_addr,
uint8_t size);
/* Request blocks alloc/free I/F */
struct ishtp_cl_rb *ishtp_io_rb_init(struct ishtp_cl *cl);
void ishtp_io_rb_free(struct ishtp_cl_rb *priv_rb);
int ishtp_io_rb_alloc_buf(struct ishtp_cl_rb *rb, size_t length);
/**
* ishtp_cl_cmp_id - tells if file private data have same id
* returns true - if ids are the same and not NULL
*/
static inline bool ishtp_cl_cmp_id(const struct ishtp_cl *cl1,
const struct ishtp_cl *cl2)
{
return cl1 && cl2 &&
(cl1->host_client_id == cl2->host_client_id) &&
(cl1->fw_client_id == cl2->fw_client_id);
}
#endif /* _ISHTP_CLIENT_H_ */
@@ -0,0 +1,176 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* ISHTP DMA I/F functions
*
* Copyright (c) 2003-2016, Intel Corporation.
*/
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include "ishtp-dev.h"
#include "client.h"
/**
* ishtp_cl_alloc_dma_buf() - Allocate DMA RX and TX buffer
* @dev: ishtp device
*
* Allocate RX and TX DMA buffer once during bus setup.
* It allocates 1MB, RX and TX DMA buffer, which are divided
* into slots.
*/
void ishtp_cl_alloc_dma_buf(struct ishtp_device *dev)
{
dma_addr_t h;
if (dev->ishtp_host_dma_tx_buf)
return;
dev->ishtp_host_dma_tx_buf_size = 1024*1024;
dev->ishtp_host_dma_rx_buf_size = 1024*1024;
/* Allocate Tx buffer and init usage bitmap */
dev->ishtp_host_dma_tx_buf = dma_alloc_coherent(dev->devc,
dev->ishtp_host_dma_tx_buf_size,
&h, GFP_KERNEL);
if (dev->ishtp_host_dma_tx_buf)
dev->ishtp_host_dma_tx_buf_phys = h;
dev->ishtp_dma_num_slots = dev->ishtp_host_dma_tx_buf_size /
DMA_SLOT_SIZE;
dev->ishtp_dma_tx_map = kcalloc(dev->ishtp_dma_num_slots,
sizeof(uint8_t),
GFP_KERNEL);
spin_lock_init(&dev->ishtp_dma_tx_lock);
/* Allocate Rx buffer */
dev->ishtp_host_dma_rx_buf = dma_alloc_coherent(dev->devc,
dev->ishtp_host_dma_rx_buf_size,
&h, GFP_KERNEL);
if (dev->ishtp_host_dma_rx_buf)
dev->ishtp_host_dma_rx_buf_phys = h;
}
/**
* ishtp_cl_free_dma_buf() - Free DMA RX and TX buffer
* @dev: ishtp device
*
* Free DMA buffer when all clients are released. This is
* only happens during error path in ISH built in driver
* model
*/
void ishtp_cl_free_dma_buf(struct ishtp_device *dev)
{
dma_addr_t h;
if (dev->ishtp_host_dma_tx_buf) {
h = dev->ishtp_host_dma_tx_buf_phys;
dma_free_coherent(dev->devc, dev->ishtp_host_dma_tx_buf_size,
dev->ishtp_host_dma_tx_buf, h);
}
if (dev->ishtp_host_dma_rx_buf) {
h = dev->ishtp_host_dma_rx_buf_phys;
dma_free_coherent(dev->devc, dev->ishtp_host_dma_rx_buf_size,
dev->ishtp_host_dma_rx_buf, h);
}
kfree(dev->ishtp_dma_tx_map);
dev->ishtp_host_dma_tx_buf = NULL;
dev->ishtp_host_dma_rx_buf = NULL;
dev->ishtp_dma_tx_map = NULL;
}
/*
* ishtp_cl_get_dma_send_buf() - Get a DMA memory slot
* @dev: ishtp device
* @size: Size of memory to get
*
* Find and return free address of "size" bytes in dma tx buffer.
* the function will mark this address as "in-used" memory.
*
* Return: NULL when no free buffer else a buffer to copy
*/
void *ishtp_cl_get_dma_send_buf(struct ishtp_device *dev,
uint32_t size)
{
unsigned long flags;
int i, j, free;
/* additional slot is needed if there is rem */
int required_slots = (size / DMA_SLOT_SIZE)
+ 1 * (size % DMA_SLOT_SIZE != 0);
if (!dev->ishtp_dma_tx_map) {
dev_err(dev->devc, "Fail to allocate Tx map\n");
return NULL;
}
spin_lock_irqsave(&dev->ishtp_dma_tx_lock, flags);
for (i = 0; i <= (dev->ishtp_dma_num_slots - required_slots); i++) {
free = 1;
for (j = 0; j < required_slots; j++)
if (dev->ishtp_dma_tx_map[i+j]) {
free = 0;
i += j;
break;
}
if (free) {
/* mark memory as "caught" */
for (j = 0; j < required_slots; j++)
dev->ishtp_dma_tx_map[i+j] = 1;
spin_unlock_irqrestore(&dev->ishtp_dma_tx_lock, flags);
return (i * DMA_SLOT_SIZE) +
(unsigned char *)dev->ishtp_host_dma_tx_buf;
}
}
spin_unlock_irqrestore(&dev->ishtp_dma_tx_lock, flags);
dev_err(dev->devc, "No free DMA buffer to send msg\n");
return NULL;
}
/*
* ishtp_cl_release_dma_acked_mem() - Release DMA memory slot
* @dev: ishtp device
* @msg_addr: message address of slot
* @size: Size of memory to get
*
* Release_dma_acked_mem - returnes the acked memory to free list.
* (from msg_addr, size bytes long)
*/
void ishtp_cl_release_dma_acked_mem(struct ishtp_device *dev,
void *msg_addr,
uint8_t size)
{
unsigned long flags;
int acked_slots = (size / DMA_SLOT_SIZE)
+ 1 * (size % DMA_SLOT_SIZE != 0);
int i, j;
if ((msg_addr - dev->ishtp_host_dma_tx_buf) % DMA_SLOT_SIZE) {
dev_err(dev->devc, "Bad DMA Tx ack address\n");
return;
}
if (!dev->ishtp_dma_tx_map) {
dev_err(dev->devc, "Fail to allocate Tx map\n");
return;
}
i = (msg_addr - dev->ishtp_host_dma_tx_buf) / DMA_SLOT_SIZE;
spin_lock_irqsave(&dev->ishtp_dma_tx_lock, flags);
for (j = 0; j < acked_slots; j++) {
if ((i + j) >= dev->ishtp_dma_num_slots ||
!dev->ishtp_dma_tx_map[i+j]) {
/* no such slot, or memory is already free */
spin_unlock_irqrestore(&dev->ishtp_dma_tx_lock, flags);
dev_err(dev->devc, "Bad DMA Tx ack address\n");
return;
}
dev->ishtp_dma_tx_map[i+j] = 0;
}
spin_unlock_irqrestore(&dev->ishtp_dma_tx_lock, flags);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,314 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* ISHTP bus layer messages handling
*
* Copyright (c) 2003-2016, Intel Corporation.
*/
#ifndef _ISHTP_HBM_H_
#define _ISHTP_HBM_H_
#include <linux/uuid.h>
struct ishtp_device;
struct ishtp_msg_hdr;
struct ishtp_cl;
/*
* Timeouts in Seconds
*/
#define ISHTP_INTEROP_TIMEOUT 7 /* Timeout on ready message */
#define ISHTP_CL_CONNECT_TIMEOUT 15 /* HPS: Client Connect Timeout */
/*
* ISHTP Version
*/
#define HBM_MINOR_VERSION 0
#define HBM_MAJOR_VERSION 1
/* Host bus message command opcode */
#define ISHTP_HBM_CMD_OP_MSK 0x7f
/* Host bus message command RESPONSE */
#define ISHTP_HBM_CMD_RES_MSK 0x80
/*
* ISHTP Bus Message Command IDs
*/
#define HOST_START_REQ_CMD 0x01
#define HOST_START_RES_CMD 0x81
#define HOST_STOP_REQ_CMD 0x02
#define HOST_STOP_RES_CMD 0x82
#define FW_STOP_REQ_CMD 0x03
#define HOST_ENUM_REQ_CMD 0x04
#define HOST_ENUM_RES_CMD 0x84
#define HOST_CLIENT_PROPERTIES_REQ_CMD 0x05
#define HOST_CLIENT_PROPERTIES_RES_CMD 0x85
#define CLIENT_CONNECT_REQ_CMD 0x06
#define CLIENT_CONNECT_RES_CMD 0x86
#define CLIENT_DISCONNECT_REQ_CMD 0x07
#define CLIENT_DISCONNECT_RES_CMD 0x87
#define ISHTP_FLOW_CONTROL_CMD 0x08
#define DMA_BUFFER_ALLOC_NOTIFY 0x11
#define DMA_BUFFER_ALLOC_RESPONSE 0x91
#define DMA_XFER 0x12
#define DMA_XFER_ACK 0x92
/*
* ISHTP Stop Reason
* used by hbm_host_stop_request.reason
*/
#define DRIVER_STOP_REQUEST 0x00
/*
* ISHTP BUS Interface Section
*/
struct ishtp_msg_hdr {
uint32_t fw_addr:8;
uint32_t host_addr:8;
uint32_t length:9;
uint32_t reserved:6;
uint32_t msg_complete:1;
} __packed;
struct ishtp_bus_message {
uint8_t hbm_cmd;
uint8_t data[];
} __packed;
/**
* struct hbm_cl_cmd - client specific host bus command
* CONNECT, DISCONNECT, and FlOW CONTROL
*
* @hbm_cmd - bus message command header
* @fw_addr - address of the fw client
* @host_addr - address of the client in the driver
* @data
*/
struct ishtp_hbm_cl_cmd {
uint8_t hbm_cmd;
uint8_t fw_addr;
uint8_t host_addr;
uint8_t data;
};
struct hbm_version {
uint8_t minor_version;
uint8_t major_version;
} __packed;
struct hbm_host_version_request {
uint8_t hbm_cmd;
uint8_t reserved;
struct hbm_version host_version;
} __packed;
struct hbm_host_version_response {
uint8_t hbm_cmd;
uint8_t host_version_supported;
struct hbm_version fw_max_version;
} __packed;
struct hbm_host_stop_request {
uint8_t hbm_cmd;
uint8_t reason;
uint8_t reserved[2];
} __packed;
struct hbm_host_stop_response {
uint8_t hbm_cmd;
uint8_t reserved[3];
} __packed;
struct hbm_host_enum_request {
uint8_t hbm_cmd;
uint8_t reserved[3];
} __packed;
struct hbm_host_enum_response {
uint8_t hbm_cmd;
uint8_t reserved[3];
uint8_t valid_addresses[32];
} __packed;
struct ishtp_client_properties {
guid_t protocol_name;
uint8_t protocol_version;
uint8_t max_number_of_connections;
uint8_t fixed_address;
uint8_t single_recv_buf;
uint32_t max_msg_length;
uint8_t dma_hdr_len;
#define ISHTP_CLIENT_DMA_ENABLED 0x80
uint8_t reserved4;
uint8_t reserved5;
uint8_t reserved6;
} __packed;
struct hbm_props_request {
uint8_t hbm_cmd;
uint8_t address;
uint8_t reserved[2];
} __packed;
struct hbm_props_response {
uint8_t hbm_cmd;
uint8_t address;
uint8_t status;
uint8_t reserved[1];
struct ishtp_client_properties client_properties;
} __packed;
/**
* struct hbm_client_connect_request - connect/disconnect request
*
* @hbm_cmd - bus message command header
* @fw_addr - address of the fw client
* @host_addr - address of the client in the driver
* @reserved
*/
struct hbm_client_connect_request {
uint8_t hbm_cmd;
uint8_t fw_addr;
uint8_t host_addr;
uint8_t reserved;
} __packed;
/**
* struct hbm_client_connect_response - connect/disconnect response
*
* @hbm_cmd - bus message command header
* @fw_addr - address of the fw client
* @host_addr - address of the client in the driver
* @status - status of the request
*/
struct hbm_client_connect_response {
uint8_t hbm_cmd;
uint8_t fw_addr;
uint8_t host_addr;
uint8_t status;
} __packed;
#define ISHTP_FC_MESSAGE_RESERVED_LENGTH 5
struct hbm_flow_control {
uint8_t hbm_cmd;
uint8_t fw_addr;
uint8_t host_addr;
uint8_t reserved[ISHTP_FC_MESSAGE_RESERVED_LENGTH];
} __packed;
struct dma_alloc_notify {
uint8_t hbm;
uint8_t status;
uint8_t reserved[2];
uint32_t buf_size;
uint64_t buf_address;
/* [...] May come more size/address pairs */
} __packed;
struct dma_xfer_hbm {
uint8_t hbm;
uint8_t fw_client_id;
uint8_t host_client_id;
uint8_t reserved;
uint64_t msg_addr;
uint32_t msg_length;
uint32_t reserved2;
} __packed;
/* System state */
#define ISHTP_SYSTEM_STATE_CLIENT_ADDR 13
#define SYSTEM_STATE_SUBSCRIBE 0x1
#define SYSTEM_STATE_STATUS 0x2
#define SYSTEM_STATE_QUERY_SUBSCRIBERS 0x3
#define SYSTEM_STATE_STATE_CHANGE_REQ 0x4
/*indicates suspend and resume states*/
#define CONNECTED_STANDBY_STATE_BIT (1<<0)
#define SUSPEND_STATE_BIT (1<<1)
struct ish_system_states_header {
uint32_t cmd;
uint32_t cmd_status; /*responses will have this set*/
} __packed;
struct ish_system_states_subscribe {
struct ish_system_states_header hdr;
uint32_t states;
} __packed;
struct ish_system_states_status {
struct ish_system_states_header hdr;
uint32_t supported_states;
uint32_t states_status;
} __packed;
struct ish_system_states_query_subscribers {
struct ish_system_states_header hdr;
} __packed;
struct ish_system_states_state_change_req {
struct ish_system_states_header hdr;
uint32_t requested_states;
uint32_t states_status;
} __packed;
/**
* enum ishtp_hbm_state - host bus message protocol state
*
* @ISHTP_HBM_IDLE : protocol not started
* @ISHTP_HBM_START : start request message was sent
* @ISHTP_HBM_ENUM_CLIENTS : enumeration request was sent
* @ISHTP_HBM_CLIENT_PROPERTIES : acquiring clients properties
*/
enum ishtp_hbm_state {
ISHTP_HBM_IDLE = 0,
ISHTP_HBM_START,
ISHTP_HBM_STARTED,
ISHTP_HBM_ENUM_CLIENTS,
ISHTP_HBM_CLIENT_PROPERTIES,
ISHTP_HBM_WORKING,
ISHTP_HBM_STOPPED,
};
static inline void ishtp_hbm_hdr(struct ishtp_msg_hdr *hdr, size_t length)
{
hdr->host_addr = 0;
hdr->fw_addr = 0;
hdr->length = length;
hdr->msg_complete = 1;
hdr->reserved = 0;
}
int ishtp_hbm_start_req(struct ishtp_device *dev);
int ishtp_hbm_start_wait(struct ishtp_device *dev);
int ishtp_hbm_cl_flow_control_req(struct ishtp_device *dev,
struct ishtp_cl *cl);
int ishtp_hbm_cl_disconnect_req(struct ishtp_device *dev, struct ishtp_cl *cl);
int ishtp_hbm_cl_connect_req(struct ishtp_device *dev, struct ishtp_cl *cl);
void ishtp_hbm_enum_clients_req(struct ishtp_device *dev);
void bh_hbm_work_fn(struct work_struct *work);
void recv_hbm(struct ishtp_device *dev, struct ishtp_msg_hdr *ishtp_hdr);
void recv_fixed_cl_msg(struct ishtp_device *dev,
struct ishtp_msg_hdr *ishtp_hdr);
void ishtp_hbm_dispatch(struct ishtp_device *dev,
struct ishtp_bus_message *hdr);
void ishtp_query_subscribers(struct ishtp_device *dev);
/* Exported I/F */
void ishtp_send_suspend(struct ishtp_device *dev);
void ishtp_send_resume(struct ishtp_device *dev);
#endif /* _ISHTP_HBM_H_ */
@@ -0,0 +1,84 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Initialization protocol for ISHTP driver
*
* 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"
#include "loader.h"
/**
* ishtp_device_init() - ishtp device init
* @dev: ISHTP device instance
*
* After ISHTP device is alloacted, this function is used to initialize
* each field which includes spin lock, work struct and lists
*/
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);
dev->rd_msg_fifo_head = 0;
dev->rd_msg_fifo_tail = 0;
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);
spin_lock_init(&dev->cl_list_lock);
spin_lock_init(&dev->fw_clients_lock);
INIT_WORK(&dev->bh_hbm_work, bh_hbm_work_fn);
bitmap_zero(dev->host_clients_map, ISHTP_CLIENTS_MAX);
dev->open_handle_count = 0;
/*
* Reserving client ID 0 for ISHTP Bus Message communications
*/
bitmap_set(dev->host_clients_map, 0, 1);
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);
/**
* ishtp_start() - Start ISH processing
* @dev: ISHTP device instance
*
* Start ISHTP processing by sending query subscriber message
*
* Return: 0 on success else -ENODEV
*/
int ishtp_start(struct ishtp_device *dev)
{
if (ishtp_hbm_start_wait(dev)) {
dev_err(dev->devc, "HBM haven't started");
goto err;
}
/* suspend & resume notification - send QUERY_SUBSCRIBERS msg */
ishtp_query_subscribers(dev);
return 0;
err:
dev_err(dev->devc, "link layer initialization failed.\n");
dev->dev_state = ISHTP_DEV_DISABLED;
return -ENODEV;
}
EXPORT_SYMBOL(ishtp_start);
@@ -0,0 +1,287 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Most ISHTP provider device and ISHTP logic declarations
*
* Copyright (c) 2003-2016, Intel Corporation.
*/
#ifndef _ISHTP_DEV_H_
#define _ISHTP_DEV_H_
#include <linux/types.h>
#include <linux/spinlock.h>
#include <linux/intel-ish-client-if.h>
#include "bus.h"
#include "hbm.h"
#define IPC_PAYLOAD_SIZE 128
#define ISHTP_RD_MSG_BUF_SIZE IPC_PAYLOAD_SIZE
#define IPC_FULL_MSG_SIZE 132
/* Number of messages to be held in ISR->BH FIFO */
#define RD_INT_FIFO_SIZE 64
/*
* Number of IPC messages to be held in Tx FIFO, to be sent by ISR -
* Tx complete interrupt or RX_COMPLETE handler
*/
#define IPC_TX_FIFO_SIZE 512
/*
* Number of Maximum ISHTP Clients
*/
#define ISHTP_CLIENTS_MAX 256
/*
* Number of File descriptors/handles
* that can be opened to the driver.
*
* Limit to 255: 256 Total Clients
* minus internal client for ISHTP Bus Messages
*/
#define ISHTP_MAX_OPEN_HANDLE_COUNT (ISHTP_CLIENTS_MAX - 1)
/* Internal Clients Number */
#define ISHTP_HOST_CLIENT_ID_ANY (-1)
#define ISHTP_HBM_HOST_CLIENT_ID 0
#define MAX_DMA_DELAY 20
/* 300ms to get resume response */
#define WAIT_FOR_RESUME_ACK_MS 300
/* ISHTP device states */
enum ishtp_dev_state {
ISHTP_DEV_INITIALIZING = 0,
ISHTP_DEV_INIT_CLIENTS,
ISHTP_DEV_ENABLED,
ISHTP_DEV_RESETTING,
ISHTP_DEV_DISABLED,
ISHTP_DEV_POWER_DOWN,
ISHTP_DEV_POWER_UP
};
struct ishtp_cl;
/**
* struct ishtp_fw_client - representation of fw client
*
* @props - client properties
* @client_id - fw client id
*/
struct ishtp_fw_client {
struct ishtp_client_properties props;
uint8_t client_id;
};
/*
* Control info for IPC messages ISHTP/IPC sending FIFO -
* list with inline data buffer
* This structure will be filled with parameters submitted
* by the caller glue layer
* 'buf' may be pointing to the external buffer or to 'inline_data'
* 'offset' will be initialized to 0 by submitting
*
* 'ipc_send_compl' is intended for use by clients that send fragmented
* messages. When a fragment is sent down to IPC msg regs,
* it will be called.
* If it has more fragments to send, it will do it. With last fragment
* it will send appropriate ISHTP "message-complete" flag.
* It will remove the outstanding message
* (mark outstanding buffer as available).
* If counting flow control is in work and there are more flow control
* credits, it can put the next client message queued in cl.
* structure for IPC processing.
*
*/
struct wr_msg_ctl_info {
/* Will be called with 'ipc_send_compl_prm' as parameter */
void (*ipc_send_compl)(void *);
void *ipc_send_compl_prm;
size_t length;
struct list_head link;
unsigned char inline_data[IPC_FULL_MSG_SIZE];
};
/*
* The ISHTP layer talks to hardware IPC message using the following
* callbacks
*/
struct ishtp_hw_ops {
int (*hw_reset)(struct ishtp_device *dev);
int (*ipc_reset)(struct ishtp_device *dev);
uint32_t (*ipc_get_header)(struct ishtp_device *dev, int length,
int busy);
int (*write)(struct ishtp_device *dev,
void (*ipc_send_compl)(void *), void *ipc_send_compl_prm,
unsigned char *msg, int length);
uint32_t (*ishtp_read_hdr)(const struct ishtp_device *dev);
int (*ishtp_read)(struct ishtp_device *dev, unsigned char *buffer,
unsigned long buffer_length);
uint32_t (*get_fw_status)(struct ishtp_device *dev);
void (*sync_fw_clock)(struct ishtp_device *dev);
bool (*dma_no_cache_snooping)(struct ishtp_device *dev);
};
/**
* 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;
bool suspend_flag; /* Suspend is active */
/* waitq for waiting for resume response */
wait_queue_head_t resume_wait;
bool resume_flag; /*Resume is active */
/*
* lock for the device, for everything that doesn't have
* a dedicated spinlock
*/
spinlock_t device_lock;
bool recvd_hw_ready;
struct hbm_version version;
int transfer_path; /* Choice of transfer path: IPC or DMA */
/* Alloc a dedicated unbound workqueue for ishtp device */
struct workqueue_struct *unbound_wq;
/* 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;
/* driver read queue */
struct ishtp_cl_rb read_list;
spinlock_t read_list_spinlock;
/* list of ishtp_cl's */
struct list_head cl_list;
spinlock_t cl_list_lock;
long open_handle_count;
/* List of bus devices */
struct list_head device_list;
spinlock_t device_list_lock;
/* waiting queues for receive message from FW */
wait_queue_head_t wait_hw_ready;
wait_queue_head_t wait_hbm_recvd_msg;
/* FIFO for input messages for BH processing */
unsigned char rd_msg_fifo[RD_INT_FIFO_SIZE * IPC_PAYLOAD_SIZE];
unsigned int rd_msg_fifo_head, rd_msg_fifo_tail;
spinlock_t rd_msg_spinlock;
struct work_struct bh_hbm_work;
/* IPC write queue */
struct list_head wr_processing_list, wr_free_list;
/* For both processing list and free list */
spinlock_t wr_processing_spinlock;
struct ishtp_fw_client *fw_clients; /*Note:memory has to be allocated*/
DECLARE_BITMAP(fw_clients_map, ISHTP_CLIENTS_MAX);
DECLARE_BITMAP(host_clients_map, ISHTP_CLIENTS_MAX);
uint8_t fw_clients_num;
uint8_t fw_client_presentation_num;
uint8_t fw_client_index;
spinlock_t fw_clients_lock;
/* TX DMA buffers and slots */
int ishtp_host_dma_enabled;
void *ishtp_host_dma_tx_buf;
unsigned int ishtp_host_dma_tx_buf_size;
uint64_t ishtp_host_dma_tx_buf_phys;
int ishtp_dma_num_slots;
/* map of 4k blocks in Tx dma buf: 0-free, 1-used */
uint8_t *ishtp_dma_tx_map;
spinlock_t ishtp_dma_tx_lock;
/* RX DMA buffers and slots */
void *ishtp_host_dma_rx_buf;
unsigned int ishtp_host_dma_rx_buf_size;
uint64_t ishtp_host_dma_rx_buf_phys;
/* 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;
char hw[] __aligned(sizeof(void *));
};
static inline unsigned long ishtp_secs_to_jiffies(unsigned long sec)
{
return msecs_to_jiffies(sec * MSEC_PER_SEC);
}
/*
* Register Access Function
*/
static inline int ish_ipc_reset(struct ishtp_device *dev)
{
return dev->ops->ipc_reset(dev);
}
/* Exported function */
void ishtp_device_init(struct ishtp_device *dev);
int ishtp_start(struct ishtp_device *dev);
#endif /*_ISHTP_DEV_H_*/
@@ -0,0 +1,486 @@
// 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;
}
/* Patterns with PRODUCT_FAMILY */
#define ISH_FW_FILE_VENDOR_FAMILY_NAME_SKU_FMT "intel/ish/ish_%s_%08x_%08x_%08x_%08x.bin"
#define ISH_FW_FILE_VENDOR_FAMILY_SKU_FMT "intel/ish/ish_%s_%08x_%08x_%08x.bin"
#define ISH_FW_FILE_VENDOR_FAMILY_NAME_FMT "intel/ish/ish_%s_%08x_%08x_%08x.bin"
#define ISH_FW_FILE_VENDOR_FAMILY_FMT "intel/ish/ish_%s_%08x_%08x.bin"
#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 72
#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_FAMILY_CRC32)
* _$(PRODUCT_NAME_CRC32)_${PRODUCT_SKU_CRC32}.bin
*
* ish_${fw_generation}_${SYS_VENDOR_CRC32}_$(PRODUCT_FAMILY_CRC32)_${PRODUCT_SKU_CRC32}.bin
* ish_${fw_generation}_${SYS_VENDOR_CRC32}_$(PRODUCT_FAMILY_CRC32)_$(PRODUCT_NAME_CRC32).bin
* ish_${fw_generation}_${SYS_VENDOR_CRC32}_$(PRODUCT_FAMILY_CRC32).bin
* 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;
const char *product_family;
struct ishtp_device *ishtp = dev_get_drvdata(dev);
u32 vendor_crc, name_crc, sku_crc, family_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);
product_family = dmi_get_system_info(DMI_PRODUCT_FAMILY);
if (sys_vendor)
vendor_crc = crc32(ISH_CRC_INIT, sys_vendor, strlen(sys_vendor)) ^ ISH_CRC_XOROUT;
if (product_family)
family_crc = crc32(ISH_CRC_INIT, product_family,
strlen(product_family)) ^ 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;
/* PRODUCT_FAMILY-extended matching */
if (sys_vendor && product_family && product_name && product_sku) {
snprintf(filename, sizeof(filename),
ISH_FW_FILE_VENDOR_FAMILY_NAME_SKU_FMT,
gen, vendor_crc, family_crc, name_crc, sku_crc);
ret = _request_ish_firmware(firmware_p, filename, dev);
if (!ret)
return 0;
}
if (sys_vendor && product_family && product_sku) {
snprintf(filename, sizeof(filename),
ISH_FW_FILE_VENDOR_FAMILY_SKU_FMT,
gen, vendor_crc, family_crc, sku_crc);
ret = _request_ish_firmware(firmware_p, filename, dev);
if (!ret)
return 0;
}
if (sys_vendor && product_family && product_name) {
snprintf(filename, sizeof(filename),
ISH_FW_FILE_VENDOR_FAMILY_NAME_FMT,
gen, vendor_crc, family_crc, name_crc);
ret = _request_ish_firmware(firmware_p, filename, dev);
if (!ret)
return 0;
}
if (sys_vendor && product_family) {
snprintf(filename, sizeof(filename),
ISH_FW_FILE_VENDOR_FAMILY_FMT,
gen, vendor_crc, family_crc);
ret = _request_ish_firmware(firmware_p, filename, dev);
if (!ret)
return 0;
}
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);
}
@@ -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_ */