Commit 611d9ca7 authored by Jiri Kosina's avatar Jiri Kosina

Merge branch 'for-6.10/intel-ish' into for-linus

- Implement loading firmware from host in intel-ish driver, needed
  to support Lunar Lake and later (Zhang Lixu)
parents c216843c 25247cf6
This diff is collapsed.
......@@ -11,6 +11,7 @@ intel-ishtp-objs += ishtp/client.o
intel-ishtp-objs += ishtp/bus.o
intel-ishtp-objs += ishtp/dma-if.o
intel-ishtp-objs += ishtp/client-buffers.o
intel-ishtp-objs += ishtp/loader.o
obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-ipc.o
intel-ish-ipc-objs := ipc/ipc.o
......
......@@ -13,28 +13,29 @@
#include "hw-ish-regs.h"
#include "ishtp-dev.h"
#define CHV_DEVICE_ID 0x22D8
#define BXT_Ax_DEVICE_ID 0x0AA2
#define BXT_Bx_DEVICE_ID 0x1AA2
#define APL_Ax_DEVICE_ID 0x5AA2
#define SPT_Ax_DEVICE_ID 0x9D35
#define CNL_Ax_DEVICE_ID 0x9DFC
#define GLK_Ax_DEVICE_ID 0x31A2
#define CNL_H_DEVICE_ID 0xA37C
#define ICL_MOBILE_DEVICE_ID 0x34FC
#define SPT_H_DEVICE_ID 0xA135
#define CML_LP_DEVICE_ID 0x02FC
#define CMP_H_DEVICE_ID 0x06FC
#define EHL_Ax_DEVICE_ID 0x4BB3
#define TGL_LP_DEVICE_ID 0xA0FC
#define TGL_H_DEVICE_ID 0x43FC
#define ADL_S_DEVICE_ID 0x7AF8
#define ADL_P_DEVICE_ID 0x51FC
#define ADL_N_DEVICE_ID 0x54FC
#define RPL_S_DEVICE_ID 0x7A78
#define MTL_P_DEVICE_ID 0x7E45
#define ARL_H_DEVICE_ID 0x7745
#define ARL_S_DEVICE_ID 0x7F78
#define PCI_DEVICE_ID_INTEL_ISH_CHV 0x22D8
#define PCI_DEVICE_ID_INTEL_ISH_BXT_Ax 0x0AA2
#define PCI_DEVICE_ID_INTEL_ISH_BXT_Bx 0x1AA2
#define PCI_DEVICE_ID_INTEL_ISH_APL_Ax 0x5AA2
#define PCI_DEVICE_ID_INTEL_ISH_SPT_Ax 0x9D35
#define PCI_DEVICE_ID_INTEL_ISH_CNL_Ax 0x9DFC
#define PCI_DEVICE_ID_INTEL_ISH_GLK_Ax 0x31A2
#define PCI_DEVICE_ID_INTEL_ISH_CNL_H 0xA37C
#define PCI_DEVICE_ID_INTEL_ISH_ICL_MOBILE 0x34FC
#define PCI_DEVICE_ID_INTEL_ISH_SPT_H 0xA135
#define PCI_DEVICE_ID_INTEL_ISH_CML_LP 0x02FC
#define PCI_DEVICE_ID_INTEL_ISH_CMP_H 0x06FC
#define PCI_DEVICE_ID_INTEL_ISH_EHL_Ax 0x4BB3
#define PCI_DEVICE_ID_INTEL_ISH_TGL_LP 0xA0FC
#define PCI_DEVICE_ID_INTEL_ISH_TGL_H 0x43FC
#define PCI_DEVICE_ID_INTEL_ISH_ADL_S 0x7AF8
#define PCI_DEVICE_ID_INTEL_ISH_ADL_P 0x51FC
#define PCI_DEVICE_ID_INTEL_ISH_ADL_N 0x54FC
#define PCI_DEVICE_ID_INTEL_ISH_RPL_S 0x7A78
#define PCI_DEVICE_ID_INTEL_ISH_MTL_P 0x7E45
#define PCI_DEVICE_ID_INTEL_ISH_ARL_H 0x7745
#define PCI_DEVICE_ID_INTEL_ISH_ARL_S 0x7F78
#define PCI_DEVICE_ID_INTEL_ISH_LNL_M 0xA845
#define REVISION_ID_CHT_A0 0x6
#define REVISION_ID_CHT_Ax_SI 0x0
......
......@@ -78,7 +78,7 @@ static bool check_generated_interrupt(struct ishtp_device *dev)
bool interrupt_generated = true;
uint32_t pisr_val = 0;
if (dev->pdev->device == CHV_DEVICE_ID) {
if (dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_CHV) {
pisr_val = ish_reg_read(dev, IPC_REG_PISR_CHV_AB);
interrupt_generated =
IPC_INT_FROM_ISH_TO_HOST_CHV_AB(pisr_val);
......@@ -117,7 +117,7 @@ static bool ish_is_input_ready(struct ishtp_device *dev)
*/
static void set_host_ready(struct ishtp_device *dev)
{
if (dev->pdev->device == CHV_DEVICE_ID) {
if (dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_CHV) {
if (dev->pdev->revision == REVISION_ID_CHT_A0 ||
(dev->pdev->revision & REVISION_ID_SI_MASK) ==
REVISION_ID_CHT_Ax_SI)
......@@ -546,11 +546,11 @@ static int ish_fw_reset_handler(struct ishtp_device *dev)
/**
* fw_reset_work_fn() - FW reset worker function
* @unused: not used
* @work: Work item
*
* Call ish_fw_reset_handler to complete FW reset
*/
static void fw_reset_work_fn(struct work_struct *unused)
static void fw_reset_work_fn(struct work_struct *work)
{
int rv;
......@@ -562,7 +562,8 @@ static void fw_reset_work_fn(struct work_struct *unused)
wake_up_interruptible(&ishtp_dev->wait_hw_ready);
/* ISHTP notification in IPC_RESET sequence completion */
ishtp_reset_compl_handler(ishtp_dev);
if (!work_pending(work))
ishtp_reset_compl_handler(ishtp_dev);
} else
dev_err(ishtp_dev->devc, "[ishtp-ish]: FW reset failed (%d)\n",
rv);
......@@ -909,11 +910,11 @@ static uint32_t ish_ipc_get_header(struct ishtp_device *dev, int length,
*/
static bool _dma_no_cache_snooping(struct ishtp_device *dev)
{
return (dev->pdev->device == EHL_Ax_DEVICE_ID ||
dev->pdev->device == TGL_LP_DEVICE_ID ||
dev->pdev->device == TGL_H_DEVICE_ID ||
dev->pdev->device == ADL_S_DEVICE_ID ||
dev->pdev->device == ADL_P_DEVICE_ID);
return (dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_EHL_Ax ||
dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_TGL_LP ||
dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_TGL_H ||
dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_ADL_S ||
dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_ADL_P);
}
static const struct ishtp_hw_ops ish_hw_ops = {
......
......@@ -23,30 +23,44 @@
#include "ishtp-dev.h"
#include "hw-ish.h"
enum ishtp_driver_data_index {
ISHTP_DRIVER_DATA_NONE,
ISHTP_DRIVER_DATA_LNL_M,
};
#define ISH_FW_FILENAME_LNL_M "intel/ish/ish_lnlm.bin"
static struct ishtp_driver_data ishtp_driver_data[] = {
[ISHTP_DRIVER_DATA_LNL_M] = {
.fw_filename = ISH_FW_FILENAME_LNL_M,
},
};
static const struct pci_device_id ish_pci_tbl[] = {
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, CHV_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, BXT_Ax_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, BXT_Bx_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, APL_Ax_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, SPT_Ax_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, CNL_Ax_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, GLK_Ax_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, CNL_H_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, ICL_MOBILE_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, SPT_H_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, CML_LP_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, CMP_H_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, EHL_Ax_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, TGL_LP_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, TGL_H_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, ADL_S_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, ADL_P_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, ADL_N_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, RPL_S_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MTL_P_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, ARL_H_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, ARL_S_DEVICE_ID)},
{0, }
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_CHV)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_BXT_Ax)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_BXT_Bx)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_APL_Ax)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_SPT_Ax)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_CNL_Ax)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_GLK_Ax)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_CNL_H)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_ICL_MOBILE)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_SPT_H)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_CML_LP)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_CMP_H)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_EHL_Ax)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_TGL_LP)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_TGL_H)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_ADL_S)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_ADL_P)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_ADL_N)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_RPL_S)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_MTL_P)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_ARL_H)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_ARL_S)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_LNL_M), .driver_data = ISHTP_DRIVER_DATA_LNL_M},
{}
};
MODULE_DEVICE_TABLE(pci, ish_pci_tbl);
......@@ -105,19 +119,19 @@ static int ish_init(struct ishtp_device *dev)
static const struct pci_device_id ish_invalid_pci_ids[] = {
/* Mehlow platform special pci ids */
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xA309)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xA30A)},
{PCI_VDEVICE(INTEL, 0xA309)},
{PCI_VDEVICE(INTEL, 0xA30A)},
{}
};
static inline bool ish_should_enter_d0i3(struct pci_dev *pdev)
{
return !pm_suspend_via_firmware() || pdev->device == CHV_DEVICE_ID;
return !pm_suspend_via_firmware() || pdev->device == PCI_DEVICE_ID_INTEL_ISH_CHV;
}
static inline bool ish_should_leave_d0i3(struct pci_dev *pdev)
{
return !pm_resume_via_firmware() || pdev->device == CHV_DEVICE_ID;
return !pm_resume_via_firmware() || pdev->device == PCI_DEVICE_ID_INTEL_ISH_CHV;
}
/**
......@@ -166,6 +180,7 @@ static int ish_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
}
hw = to_ish_hw(ishtp);
ishtp->print_log = ish_event_tracer;
ishtp->driver_data = &ishtp_driver_data[ent->driver_data];
/* mapping IO device memory */
hw->mem_addr = pcim_iomap_table(pdev)[0];
......@@ -194,7 +209,7 @@ static int ish_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
init_waitqueue_head(&ishtp->resume_wait);
/* Enable PME for EHL */
if (pdev->device == EHL_Ax_DEVICE_ID)
if (pdev->device == PCI_DEVICE_ID_INTEL_ISH_EHL_Ax)
device_init_wakeup(dev, true);
ret = ish_init(ishtp);
......@@ -227,7 +242,7 @@ static void ish_remove(struct pci_dev *pdev)
*/
static void ish_shutdown(struct pci_dev *pdev)
{
if (pdev->device == EHL_Ax_DEVICE_ID)
if (pdev->device == PCI_DEVICE_ID_INTEL_ISH_EHL_Ax)
pci_prepare_to_sleep(pdev);
}
......@@ -381,3 +396,5 @@ 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_FW_FILENAME_LNL_M);
......@@ -13,6 +13,7 @@
#include "ishtp-dev.h"
#include "hbm.h"
#include "client.h"
#include "loader.h"
/**
* ishtp_hbm_fw_cl_allocate() - Allocate FW clients
......@@ -570,6 +571,10 @@ void ishtp_hbm_dispatch(struct ishtp_device *dev,
return;
}
/* Start firmware loading process if it has loader capability */
if (version_res->host_version_supported & ISHTP_SUPPORT_CAP_LOADER)
schedule_work(&dev->work_fw_loader);
dev->version.major_version = HBM_MAJOR_VERSION;
dev->version.minor_version = HBM_MINOR_VERSION;
if (dev->dev_state == ISHTP_DEV_INIT_CLIENTS &&
......@@ -864,6 +869,20 @@ void recv_hbm(struct ishtp_device *dev, struct ishtp_msg_hdr *ishtp_hdr)
return;
}
/**
* ishtp_loader_recv_msg() - Receive a message from the ISHTP device
* @dev: The ISHTP device
* @buf: The buffer containing the message
*/
static void ishtp_loader_recv_msg(struct ishtp_device *dev, void *buf)
{
if (dev->fw_loader_rx_buf)
memcpy(dev->fw_loader_rx_buf, buf, dev->fw_loader_rx_size);
dev->fw_loader_received = true;
wake_up_interruptible(&dev->wait_loader_recvd_msg);
}
/**
* recv_fixed_cl_msg() - Receive fixed client message
* @dev: ISHTP device instance
......@@ -890,6 +909,8 @@ void recv_fixed_cl_msg(struct ishtp_device *dev,
else
dev_err(dev->devc, "unknown fixed client msg [%02X]\n",
msg_hdr->cmd);
} else if (ishtp_hdr->fw_addr == ISHTP_LOADER_CLIENT_ADDR) {
ishtp_loader_recv_msg(dev, rd_msg_buf);
}
}
......
......@@ -5,12 +5,14 @@
* Copyright (c) 2003-2016, Intel Corporation.
*/
#include <linux/devm-helpers.h>
#include <linux/export.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include "ishtp-dev.h"
#include "hbm.h"
#include "client.h"
#include "loader.h"
/**
* ishtp_dev_state_str() -Convert to string format
......@@ -51,6 +53,8 @@ const char *ishtp_dev_state_str(int state)
*/
void ishtp_device_init(struct ishtp_device *dev)
{
int ret;
dev->dev_state = ISHTP_DEV_INITIALIZING;
INIT_LIST_HEAD(&dev->cl_list);
INIT_LIST_HEAD(&dev->device_list);
......@@ -59,6 +63,7 @@ void ishtp_device_init(struct ishtp_device *dev)
spin_lock_init(&dev->rd_msg_spinlock);
init_waitqueue_head(&dev->wait_hbm_recvd_msg);
init_waitqueue_head(&dev->wait_loader_recvd_msg);
spin_lock_init(&dev->read_list_spinlock);
spin_lock_init(&dev->device_lock);
spin_lock_init(&dev->device_list_lock);
......@@ -76,6 +81,9 @@ void ishtp_device_init(struct ishtp_device *dev)
INIT_LIST_HEAD(&dev->read_list.list);
ret = devm_work_autocancel(dev->devc, &dev->work_fw_loader, ishtp_loader_work);
if (ret)
dev_err_probe(dev->devc, ret, "Failed to initialise FW loader work\n");
}
EXPORT_SYMBOL(ishtp_device_init);
......
......@@ -122,12 +122,29 @@ struct ishtp_hw_ops {
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_filename: The firmware filename 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.
*/
struct ishtp_driver_data {
char *fw_filename;
};
/**
* struct ishtp_device - ISHTP private device struct
*/
struct ishtp_device {
struct device *devc; /* pointer to lowest device */
struct pci_dev *pdev; /* PCI device to get device ids */
struct ishtp_driver_data *driver_data; /* pointer to driver-specific data */
/* waitq for waiting for suspend response */
wait_queue_head_t suspend_wait;
......@@ -147,6 +164,17 @@ struct ishtp_device {
struct hbm_version version;
int transfer_path; /* Choice of transfer path: IPC or DMA */
/* work structure for scheduling firmware loading tasks */
struct work_struct work_fw_loader;
/* waitq for waiting for command response from the firmware loader */
wait_queue_head_t wait_loader_recvd_msg;
/* indicating whether a message from the firmware loader has been received */
bool fw_loader_received;
/* pointer to a buffer for receiving messages from the firmware loader */
void *fw_loader_rx_buf;
/* size of the buffer pointed to by fw_loader_rx_buf */
int fw_loader_rx_size;
/* ishtp device states */
enum ishtp_dev_state dev_state;
enum ishtp_hbm_state hbm_state;
......
// 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/dev_printk.h>
#include <linux/dma-mapping.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/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)
{
struct loader_msg_header *req_hdr = req;
struct loader_msg_header *resp_hdr = resp;
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);
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);
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)
{
int i;
for (i = 0; i < FRAGMENT_MAX_NUM; i++) {
if (dma_bufs[i]) {
dma_free_coherent(dev->devc, fragment_size, dma_bufs[i],
fragment->fragment_tbl[i].ddr_adrs);
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
*
* 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 offset = 0;
int i;
for (i = 0; i < fragment->fragment_cnt && offset < ish_fw->size; i++) {
dma_bufs[i] = dma_alloc_coherent(dev->devc, fragment_size,
&fragment->fragment_tbl[i].ddr_adrs, GFP_KERNEL);
if (!dma_bufs[i])
return -ENOMEM;
fragment->fragment_tbl[i].length = clamp(ish_fw->size - offset, 0, fragment_size);
fragment->fragment_tbl[i].fw_off = offset;
memcpy(dma_bufs[i], ish_fw->data + offset, fragment->fragment_tbl[i].length);
clflush_cache_range(dma_bufs[i], fragment_size);
offset += fragment->fragment_tbl[i].length;
}
return 0;
}
/**
* 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);
struct loader_xfer_query query = {
.header.command = LOADER_CMD_XFER_QUERY,
};
struct loader_start start = {
.header.command = LOADER_CMD_START,
};
union loader_recv_message recv_msg;
char *filename = dev->driver_data->fw_filename;
const struct firmware *ish_fw;
void *dma_bufs[FRAGMENT_MAX_NUM] = {};
u32 fragment_size;
int retry = ISHTP_LOADER_RETRY_TIMES;
int rv;
rv = request_firmware(&ish_fw, filename, dev->devc);
if (rv < 0) {
dev_err(dev->devc, "request firmware %s failed:%d\n", filename, rv);
return;
}
fragment->fragment.header.command = LOADER_CMD_XFER_FRAGMENT;
fragment->fragment.xfer_mode = LOADER_XFER_MODE_DMA;
fragment->fragment.is_last = 1;
fragment->fragment.size = 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->fragment_cnt = DIV_ROUND_UP(ish_fw->size, fragment_size);
rv = prepare_dma_bufs(dev, ish_fw, fragment, dma_bufs, fragment_size);
if (rv) {
dev_err(dev->devc, "prepare DMA buffer failed.\n");
goto out;
}
do {
query.image_size = 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 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->fragment_cnt),
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);
break;
} while (--retry);
out:
release_dma_bufs(dev, fragment, dma_bufs, fragment_size);
release_firmware(ish_fw);
}
/* 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/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)
/**
* struct 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
*/
struct loader_msg_header {
__le32 command:7;
__le32 is_response:1;
__le32 has_next:1;
__le32 reserved:15;
__le32 status:8;
};
/**
* 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 {
struct loader_msg_header 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 {
struct loader_msg_header 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 {
struct loader_msg_header 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 {
struct loader_msg_header 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 {
struct loader_msg_header header;
};
/**
* struct loader_start_ack - ISHTP firmware loader start acknowledgment
* @header: Header of the message
*/
struct loader_start_ack {
struct loader_msg_header header;
};
union loader_recv_message {
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);
#endif /* _ISHTP_LOADER_H_ */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment