Skip to content

Commit 579a267

Browse files
lixuzhaJiri Kosina
authored andcommitted
HID: intel-ish-hid: Implement loading firmware from host feature
Starting from the Lunar Lake generation, the ISH firmware has been divided into two components for better space optimization and increased flexibility. These components include a bootloader that is integrated into the BIOS, and a main firmware that is stored within the operating system's file system. Introduce support for loading ISH main firmware from host. This feature is applicable for Lunar Lake and later generation. Current intel-ishtp-loader, is designed for Chrome OS based systems which uses core boot and has different firmware loading method. For non chrome systems the ISH firmware loading uses different method. Key differences include: 1. The new method utilizes ISHTP capability/fixed client to enumerate the firmware loader function. It does not require a connection or flow control, unlike the method used in Chrome OS, which is enumerated as an ISHTP dynamic client driver, necessitating connect/disconnect operations and flow control. 2. The new method employs a table to describe firmware fragments, which are sent to ISH in a single operation. Conversely, the Chrome OS method sends firmware fragments in multiple operations within a loop, sending only one fragment at a time. Additionally, address potential error scenarios to ensure graceful failure handling. - Firmware Not Found: Triggers if request_firmware() fails, leaving ISH in a waiting state. Recovery: Re-insmod the ISH drivers to retry. - DMA Buffer Allocation Failure: Occurs during prepare_dma_bufs(), leading to ISH waiting state. Allocated resources are released. Recovery: Re-insmod the ISH drivers to retry. - Incorrect Firmware Image: Causes ISH to refuse loading after three failed attempts. Recovery: A platform reset is required. Please refer to the [Documentation](Documentation/hid/intel-ish-hid.rst) for the details on flows. Signed-off-by: Zhang Lixu <[email protected]> Acked-by: Srinivas Pandruvada <[email protected]> Signed-off-by: Jiri Kosina <[email protected]>
1 parent 6b2a374 commit 579a267

File tree

6 files changed

+542
-0
lines changed

6 files changed

+542
-0
lines changed

drivers/hid/intel-ish-hid/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ intel-ishtp-objs += ishtp/client.o
1111
intel-ishtp-objs += ishtp/bus.o
1212
intel-ishtp-objs += ishtp/dma-if.o
1313
intel-ishtp-objs += ishtp/client-buffers.o
14+
intel-ishtp-objs += ishtp/loader.o
1415

1516
obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-ipc.o
1617
intel-ish-ipc-objs := ipc/ipc.o

drivers/hid/intel-ish-hid/ishtp/hbm.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "ishtp-dev.h"
1414
#include "hbm.h"
1515
#include "client.h"
16+
#include "loader.h"
1617

1718
/**
1819
* ishtp_hbm_fw_cl_allocate() - Allocate FW clients
@@ -570,6 +571,10 @@ void ishtp_hbm_dispatch(struct ishtp_device *dev,
570571
return;
571572
}
572573

574+
/* Start firmware loading process if it has loader capability */
575+
if (version_res->host_version_supported & ISHTP_SUPPORT_CAP_LOADER)
576+
schedule_work(&dev->work_fw_loader);
577+
573578
dev->version.major_version = HBM_MAJOR_VERSION;
574579
dev->version.minor_version = HBM_MINOR_VERSION;
575580
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)
864869
return;
865870
}
866871

872+
/**
873+
* ishtp_loader_recv_msg() - Receive a message from the ISHTP device
874+
* @dev: The ISHTP device
875+
* @buf: The buffer containing the message
876+
*/
877+
static void ishtp_loader_recv_msg(struct ishtp_device *dev, void *buf)
878+
{
879+
if (dev->fw_loader_rx_buf)
880+
memcpy(dev->fw_loader_rx_buf, buf, dev->fw_loader_rx_size);
881+
882+
dev->fw_loader_received = true;
883+
wake_up_interruptible(&dev->wait_loader_recvd_msg);
884+
}
885+
867886
/**
868887
* recv_fixed_cl_msg() - Receive fixed client message
869888
* @dev: ISHTP device instance
@@ -890,6 +909,8 @@ void recv_fixed_cl_msg(struct ishtp_device *dev,
890909
else
891910
dev_err(dev->devc, "unknown fixed client msg [%02X]\n",
892911
msg_hdr->cmd);
912+
} else if (ishtp_hdr->fw_addr == ISHTP_LOADER_CLIENT_ADDR) {
913+
ishtp_loader_recv_msg(dev, rd_msg_buf);
893914
}
894915
}
895916

drivers/hid/intel-ish-hid/ishtp/init.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
* Copyright (c) 2003-2016, Intel Corporation.
66
*/
77

8+
#include <linux/devm-helpers.h>
89
#include <linux/export.h>
910
#include <linux/slab.h>
1011
#include <linux/sched.h>
1112
#include "ishtp-dev.h"
1213
#include "hbm.h"
1314
#include "client.h"
15+
#include "loader.h"
1416

1517
/**
1618
* ishtp_dev_state_str() -Convert to string format
@@ -51,6 +53,8 @@ const char *ishtp_dev_state_str(int state)
5153
*/
5254
void ishtp_device_init(struct ishtp_device *dev)
5355
{
56+
int ret;
57+
5458
dev->dev_state = ISHTP_DEV_INITIALIZING;
5559
INIT_LIST_HEAD(&dev->cl_list);
5660
INIT_LIST_HEAD(&dev->device_list);
@@ -59,6 +63,7 @@ void ishtp_device_init(struct ishtp_device *dev)
5963
spin_lock_init(&dev->rd_msg_spinlock);
6064

6165
init_waitqueue_head(&dev->wait_hbm_recvd_msg);
66+
init_waitqueue_head(&dev->wait_loader_recvd_msg);
6267
spin_lock_init(&dev->read_list_spinlock);
6368
spin_lock_init(&dev->device_lock);
6469
spin_lock_init(&dev->device_list_lock);
@@ -76,6 +81,9 @@ void ishtp_device_init(struct ishtp_device *dev)
7681

7782
INIT_LIST_HEAD(&dev->read_list.list);
7883

84+
ret = devm_work_autocancel(dev->devc, &dev->work_fw_loader, ishtp_loader_work);
85+
if (ret)
86+
dev_err_probe(dev->devc, ret, "Failed to initialise FW loader work\n");
7987
}
8088
EXPORT_SYMBOL(ishtp_device_init);
8189

drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,17 @@ struct ishtp_device {
164164
struct hbm_version version;
165165
int transfer_path; /* Choice of transfer path: IPC or DMA */
166166

167+
/* work structure for scheduling firmware loading tasks */
168+
struct work_struct work_fw_loader;
169+
/* waitq for waiting for command response from the firmware loader */
170+
wait_queue_head_t wait_loader_recvd_msg;
171+
/* indicating whether a message from the firmware loader has been received */
172+
bool fw_loader_received;
173+
/* pointer to a buffer for receiving messages from the firmware loader */
174+
void *fw_loader_rx_buf;
175+
/* size of the buffer pointed to by fw_loader_rx_buf */
176+
int fw_loader_rx_size;
177+
167178
/* ishtp device states */
168179
enum ishtp_dev_state dev_state;
169180
enum ishtp_hbm_state hbm_state;
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
/*
3+
* ISHTP firmware loader function
4+
*
5+
* Copyright (c) 2024, Intel Corporation.
6+
*
7+
* This module implements the functionality to load the main ISH firmware from the host, starting
8+
* with the Lunar Lake generation. It leverages a new method that enhances space optimization and
9+
* flexibility by dividing the ISH firmware into a bootloader and main firmware.
10+
*
11+
* Please refer to the [Documentation](Documentation/hid/intel-ish-hid.rst) for the details on
12+
* flows.
13+
*
14+
* Additionally, address potential error scenarios to ensure graceful failure handling.
15+
* - Firmware Image Not Found:
16+
* Occurs when `request_firmware()` cannot locate the firmware image. The ISH firmware will
17+
* remain in a state awaiting firmware loading from the host, with no further action from
18+
* the ISHTP driver.
19+
* Recovery: Re-insmod the ISH drivers allows for a retry of the firmware loading from the host.
20+
*
21+
* - DMA Buffer Allocation Failure:
22+
* This happens if allocating a DMA buffer during `prepare_dma_bufs()` fails. The ISH firmware
23+
* will stay in a waiting state, and the ISHTP driver will release any allocated DMA buffers and
24+
* firmware without further actions.
25+
* Recovery: Re-insmod the ISH drivers allows for a retry of the firmware loading from the host.
26+
*
27+
* - Incorrect Firmware Image:
28+
* Using an incorrect firmware image will initiate the firmware loading process but will
29+
* eventually be refused by the ISH firmware after three unsuccessful attempts, indicated by
30+
* returning an error code. The ISHTP driver will stop attempting after three tries.
31+
* Recovery: A platform reset is required to retry firmware loading from the host.
32+
*/
33+
34+
#define dev_fmt(fmt) "ISH loader: " fmt
35+
36+
#include <linux/cacheflush.h>
37+
#include <linux/container_of.h>
38+
#include <linux/dev_printk.h>
39+
#include <linux/dma-mapping.h>
40+
#include <linux/errno.h>
41+
#include <linux/firmware.h>
42+
#include <linux/gfp_types.h>
43+
#include <linux/math.h>
44+
#include <linux/module.h>
45+
#include <linux/pfn.h>
46+
#include <linux/string.h>
47+
#include <linux/types.h>
48+
#include <linux/wait.h>
49+
50+
#include "hbm.h"
51+
#include "loader.h"
52+
53+
/**
54+
* loader_write_message() - Write a message to the ISHTP device
55+
* @dev: The ISHTP device
56+
* @buf: The buffer containing the message
57+
* @len: The length of the message
58+
*
59+
* Return: 0 on success, negative error code on failure
60+
*/
61+
static int loader_write_message(struct ishtp_device *dev, void *buf, int len)
62+
{
63+
struct ishtp_msg_hdr ishtp_hdr = {
64+
.fw_addr = ISHTP_LOADER_CLIENT_ADDR,
65+
.length = len,
66+
.msg_complete = 1,
67+
};
68+
69+
dev->fw_loader_received = false;
70+
71+
return ishtp_write_message(dev, &ishtp_hdr, buf);
72+
}
73+
74+
/**
75+
* loader_xfer_cmd() - Transfer a command to the ISHTP device
76+
* @dev: The ISHTP device
77+
* @req: The request buffer
78+
* @req_len: The length of the request
79+
* @resp: The response buffer
80+
* @resp_len: The length of the response
81+
*
82+
* Return: 0 on success, negative error code on failure
83+
*/
84+
static int loader_xfer_cmd(struct ishtp_device *dev, void *req, int req_len,
85+
void *resp, int resp_len)
86+
{
87+
struct loader_msg_header *req_hdr = req;
88+
struct loader_msg_header *resp_hdr = resp;
89+
struct device *devc = dev->devc;
90+
int rv;
91+
92+
dev->fw_loader_rx_buf = resp;
93+
dev->fw_loader_rx_size = resp_len;
94+
95+
rv = loader_write_message(dev, req, req_len);
96+
if (rv < 0) {
97+
dev_err(devc, "write cmd %u failed:%d\n", req_hdr->command, rv);
98+
return rv;
99+
}
100+
101+
/* Wait the ACK */
102+
wait_event_interruptible_timeout(dev->wait_loader_recvd_msg, dev->fw_loader_received,
103+
ISHTP_LOADER_TIMEOUT);
104+
dev->fw_loader_rx_size = 0;
105+
dev->fw_loader_rx_buf = NULL;
106+
if (!dev->fw_loader_received) {
107+
dev_err(devc, "wait response of cmd %u timeout\n", req_hdr->command);
108+
return -ETIMEDOUT;
109+
}
110+
111+
if (!resp_hdr->is_response) {
112+
dev_err(devc, "not a response for %u\n", req_hdr->command);
113+
return -EBADMSG;
114+
}
115+
116+
if (req_hdr->command != resp_hdr->command) {
117+
dev_err(devc, "unexpected cmd response %u:%u\n", req_hdr->command,
118+
resp_hdr->command);
119+
return -EBADMSG;
120+
}
121+
122+
if (resp_hdr->status) {
123+
dev_err(devc, "cmd %u failed %u\n", req_hdr->command, resp_hdr->status);
124+
return -EIO;
125+
}
126+
127+
return 0;
128+
}
129+
130+
/**
131+
* release_dma_bufs() - Release the DMA buffer for transferring firmware fragments
132+
* @dev: The ISHTP device
133+
* @fragment: The ISHTP firmware fragment descriptor
134+
* @dma_bufs: The array of DMA fragment buffers
135+
* @fragment_size: The size of a single DMA fragment
136+
*/
137+
static void release_dma_bufs(struct ishtp_device *dev,
138+
struct loader_xfer_dma_fragment *fragment,
139+
void **dma_bufs, u32 fragment_size)
140+
{
141+
int i;
142+
143+
for (i = 0; i < FRAGMENT_MAX_NUM; i++) {
144+
if (dma_bufs[i]) {
145+
dma_free_coherent(dev->devc, fragment_size, dma_bufs[i],
146+
fragment->fragment_tbl[i].ddr_adrs);
147+
dma_bufs[i] = NULL;
148+
}
149+
}
150+
}
151+
152+
/**
153+
* prepare_dma_bufs() - Prepare the DMA buffer for transferring firmware fragments
154+
* @dev: The ISHTP device
155+
* @ish_fw: The ISH firmware
156+
* @fragment: The ISHTP firmware fragment descriptor
157+
* @dma_bufs: The array of DMA fragment buffers
158+
* @fragment_size: The size of a single DMA fragment
159+
*
160+
* Return: 0 on success, negative error code on failure
161+
*/
162+
static int prepare_dma_bufs(struct ishtp_device *dev,
163+
const struct firmware *ish_fw,
164+
struct loader_xfer_dma_fragment *fragment,
165+
void **dma_bufs, u32 fragment_size)
166+
{
167+
u32 offset = 0;
168+
int i;
169+
170+
for (i = 0; i < fragment->fragment_cnt && offset < ish_fw->size; i++) {
171+
dma_bufs[i] = dma_alloc_coherent(dev->devc, fragment_size,
172+
&fragment->fragment_tbl[i].ddr_adrs, GFP_KERNEL);
173+
if (!dma_bufs[i])
174+
return -ENOMEM;
175+
176+
fragment->fragment_tbl[i].length = clamp(ish_fw->size - offset, 0, fragment_size);
177+
fragment->fragment_tbl[i].fw_off = offset;
178+
memcpy(dma_bufs[i], ish_fw->data + offset, fragment->fragment_tbl[i].length);
179+
clflush_cache_range(dma_bufs[i], fragment_size);
180+
181+
offset += fragment->fragment_tbl[i].length;
182+
}
183+
184+
return 0;
185+
}
186+
187+
/**
188+
* ishtp_loader_work() - Load the ISHTP firmware
189+
* @work: The work structure
190+
*
191+
* The ISH Loader attempts to load firmware by sending a series of commands
192+
* to the ISH device. If a command fails to be acknowledged by the ISH device,
193+
* the loader will retry sending the command, up to a maximum of
194+
* ISHTP_LOADER_RETRY_TIMES.
195+
*
196+
* After the maximum number of retries has been reached without success, the
197+
* ISH bootloader will return an error status code and will no longer respond
198+
* to the driver's commands. This behavior indicates that the ISH Loader has
199+
* encountered a critical error during the firmware loading process.
200+
*
201+
* In such a case, where the ISH bootloader is unresponsive after all retries
202+
* have been exhausted, a platform reset is required to restore communication
203+
* with the ISH device and to recover from this error state.
204+
*/
205+
void ishtp_loader_work(struct work_struct *work)
206+
{
207+
DEFINE_RAW_FLEX(struct loader_xfer_dma_fragment, fragment, fragment_tbl, FRAGMENT_MAX_NUM);
208+
struct ishtp_device *dev = container_of(work, struct ishtp_device, work_fw_loader);
209+
struct loader_xfer_query query = {
210+
.header.command = LOADER_CMD_XFER_QUERY,
211+
};
212+
struct loader_start start = {
213+
.header.command = LOADER_CMD_START,
214+
};
215+
union loader_recv_message recv_msg;
216+
char *filename = dev->driver_data->fw_filename;
217+
const struct firmware *ish_fw;
218+
void *dma_bufs[FRAGMENT_MAX_NUM] = {};
219+
u32 fragment_size;
220+
int retry = ISHTP_LOADER_RETRY_TIMES;
221+
int rv;
222+
223+
rv = request_firmware(&ish_fw, filename, dev->devc);
224+
if (rv < 0) {
225+
dev_err(dev->devc, "request firmware %s failed:%d\n", filename, rv);
226+
return;
227+
}
228+
229+
fragment->fragment.header.command = LOADER_CMD_XFER_FRAGMENT;
230+
fragment->fragment.xfer_mode = LOADER_XFER_MODE_DMA;
231+
fragment->fragment.is_last = 1;
232+
fragment->fragment.size = ish_fw->size;
233+
/* Calculate the size of a single DMA fragment */
234+
fragment_size = PFN_ALIGN(DIV_ROUND_UP(ish_fw->size, FRAGMENT_MAX_NUM));
235+
/* Calculate the count of DMA fragments */
236+
fragment->fragment_cnt = DIV_ROUND_UP(ish_fw->size, fragment_size);
237+
238+
rv = prepare_dma_bufs(dev, ish_fw, fragment, dma_bufs, fragment_size);
239+
if (rv) {
240+
dev_err(dev->devc, "prepare DMA buffer failed.\n");
241+
goto out;
242+
}
243+
244+
do {
245+
query.image_size = ish_fw->size;
246+
rv = loader_xfer_cmd(dev, &query, sizeof(query), recv_msg.raw_data,
247+
sizeof(struct loader_xfer_query_ack));
248+
if (rv)
249+
continue; /* try again if failed */
250+
251+
dev_dbg(dev->devc, "ISH Version %u.%u.%u.%u\n",
252+
recv_msg.query_ack.version_major,
253+
recv_msg.query_ack.version_minor,
254+
recv_msg.query_ack.version_hotfix,
255+
recv_msg.query_ack.version_build);
256+
257+
rv = loader_xfer_cmd(dev, fragment,
258+
struct_size(fragment, fragment_tbl, fragment->fragment_cnt),
259+
recv_msg.raw_data, sizeof(struct loader_xfer_fragment_ack));
260+
if (rv)
261+
continue; /* try again if failed */
262+
263+
rv = loader_xfer_cmd(dev, &start, sizeof(start), recv_msg.raw_data,
264+
sizeof(struct loader_start_ack));
265+
if (rv)
266+
continue; /* try again if failed */
267+
268+
dev_info(dev->devc, "firmware loaded. size:%zu\n", ish_fw->size);
269+
break;
270+
} while (--retry);
271+
272+
out:
273+
release_dma_bufs(dev, fragment, dma_bufs, fragment_size);
274+
release_firmware(ish_fw);
275+
}

0 commit comments

Comments
 (0)