/* Number of times we attempt to load the firmware before giving up */ #define MAX_LOAD_ATTEMPTS 3
/* ISH TX/RX ring buffer pool size */ #define LOADER_CL_RX_RING_SIZE 1 #define LOADER_CL_TX_RING_SIZE 1
/* * ISH Shim firmware loader reserves 4 Kb buffer in SRAM. The buffer is * used to temporarily hold the data transferred from host to Shim * firmware loader. Reason for the odd size of 3968 bytes? Each IPC * transfer is 128 bytes (= 4 bytes header + 124 bytes payload). So the * 4 Kb buffer can hold maximum of 32 IPC transfers, which means we can * have a max payload of 3968 bytes (= 32 x 124 payload).
*/ #define LOADER_SHIM_IPC_BUF_SIZE 3968
/** * enum ish_loader_commands - ISH loader host commands. * @LOADER_CMD_XFER_QUERY: Query the Shim firmware loader for * capabilities * @LOADER_CMD_XFER_FRAGMENT: Transfer one firmware image fragment at a * time. The command may be executed * multiple times until the entire firmware * image is downloaded to SRAM. * @LOADER_CMD_START: Start executing the main firmware.
*/ enum ish_loader_commands {
LOADER_CMD_XFER_QUERY = 0,
LOADER_CMD_XFER_FRAGMENT,
LOADER_CMD_START,
};
/* * ISH firmware max delay for one transmit failure is 1 Hz, * and firmware will retry 2 times, so 3 Hz is used for timeout.
*/ #define ISHTP_SEND_TIMEOUT (3 * HZ)
/* * Loader transfer modes: * * LOADER_XFER_MODE_ISHTP mode uses the existing ISH-TP mechanism to * transfer data. This may use IPC or DMA if supported in firmware. * The buffer size is limited to 4 Kb by the IPC/ISH-TP protocol for * both IPC & DMA (legacy). * * LOADER_XFER_MODE_DIRECT_DMA - firmware loading is a bit different * from the sensor data streaming. Here we download a large (300+ Kb) * image directly to ISH SRAM memory. There is limited benefit of * DMA'ing 300 Kb image in 4 Kb chucks limit. Hence, we introduce * this "direct dma" mode, where we do not use ISH-TP for DMA, but * instead manage the DMA directly in kernel driver and Shim firmware * loader (allocate buffer, break in chucks and transfer). This allows * to overcome 4 Kb limit, and optimize the data flow path in firmware.
*/ #define LOADER_XFER_MODE_DIRECT_DMA BIT(0) #define LOADER_XFER_MODE_ISHTP BIT(1)
/* * The firmware loading latency will be minimum if we can DMA the * entire ISH firmware image in one go. This requires that we allocate * a large DMA buffer in kernel, which could be problematic on some * platforms. So here we limit the DMA buffer size via a module_param. * We default to 4 pages, but a customer can set it to higher limit if * deemed appropriate for his platform.
*/ staticint dma_buf_size_limit = 4 * PAGE_SIZE;
/** * struct loader_msg_hdr - Header for ISH Loader commands. * @command: LOADER_CMD* commands. Bit 7 is the response. * @reserved: Reserved space * @status: Command response status. Non 0, is error * condition. * * This structure is used as header for every command/data sent/received * between Host driver and ISH Shim firmware loader.
*/ struct loader_msg_hdr {
u8 command;
u8 reserved[2];
u8 status;
} __packed;
/** * struct response_info - Encapsulate firmware response related * information for passing between function * loader_cl_send() and process_recv() callback. * @data: Copy the data received from firmware here. * @max_size: Max size allocated for the @data buffer. If the * received data exceeds this value, we log an * error. * @size: Actual size of data received from firmware. * @error: Returns 0 for success, negative error code for a * failure in function process_recv(). * @received: Set to true on receiving a valid firmware * response to host command * @wait_queue: Wait queue for Host firmware loading where the * client sends message to ISH firmware and waits * for response
*/ struct response_info { void *data;
size_t max_size;
size_t size; int error; bool received;
wait_queue_head_t wait_queue;
};
/* * struct ishtp_cl_data - Encapsulate per ISH-TP Client Data. * @work_ishtp_reset: Work queue for reset handling. * @work_fw_load: Work queue for host firmware loading. * @flag_retry: Flag for indicating host firmware loading should * be retried. * @retry_count: Count the number of retries. * * This structure is used to store data per client.
*/ struct ishtp_cl_data { struct ishtp_cl *loader_ishtp_cl; struct ishtp_cl_device *cl_device;
/* * Used for passing firmware response information between * loader_cl_send() and process_recv() callback.
*/ struct response_info response;
/* * In certain failure scenrios, it makes sense to reset the ISH * subsystem and retry Host firmware loading (e.g. bad message * packet, ENOMEM, etc.). On the other hand, failures due to * protocol mismatch, etc., are not recoverable. We do not * retry them. * * If set, the flag indicates that we should re-try the * particular failure.
*/ bool flag_retry; int retry_count;
};
/** * loader_cl_send() - Send message from host to firmware * * @client_data: Client data instance * @out_msg: Message buffer to be sent to firmware * @out_size: Size of out going message * @in_msg: Message buffer where the incoming data copied. * This buffer is allocated by calling * @in_size: Max size of incoming message * * Return: Number of bytes copied in the in_msg on success, negative * error code on failure.
*/ staticint loader_cl_send(struct ishtp_cl_data *client_data,
u8 *out_msg, size_t out_size,
u8 *in_msg, size_t in_size)
{ int rv; struct loader_msg_hdr *out_hdr = (struct loader_msg_hdr *)out_msg; struct ishtp_cl *loader_ishtp_cl = client_data->loader_ishtp_cl;
wait_event_interruptible_timeout(client_data->response.wait_queue,
client_data->response.received,
ISHTP_SEND_TIMEOUT); if (!client_data->response.received) {
dev_err(cl_data_to_dev(client_data), "Timed out for response to command=%02lx",
out_hdr->command & CMD_MASK); return -ETIMEDOUT;
}
if (client_data->response.error < 0) return client_data->response.error;
return client_data->response.size;
}
/** * process_recv() - Receive and parse incoming packet * @loader_ishtp_cl: Client instance to get stats * @rb_in_proc: ISH received message buffer * * Parse the incoming packet. If it is a response packet then it will * update received and wake up the caller waiting to for the response.
*/ staticvoid process_recv(struct ishtp_cl *loader_ishtp_cl, struct ishtp_cl_rb *rb_in_proc)
{ struct loader_msg_hdr *hdr;
size_t data_len = rb_in_proc->buf_idx; struct ishtp_cl_data *client_data =
ishtp_get_client_data(loader_ishtp_cl);
/* Sanity check */ if (!client_data->response.data) {
dev_err(cl_data_to_dev(client_data), "Receiving buffer is null. Should be allocated by calling function\n");
client_data->response.error = -EINVAL; goto end;
}
if (client_data->response.received) {
dev_err(cl_data_to_dev(client_data), "Previous firmware message not yet processed\n");
client_data->response.error = -EINVAL; goto end;
} /* * All firmware messages have a header. Check buffer size * before accessing elements inside.
*/ if (!rb_in_proc->buffer.data) {
dev_warn(cl_data_to_dev(client_data), "rb_in_proc->buffer.data returned null");
client_data->response.error = -EBADMSG; goto end;
}
if (data_len < sizeof(struct loader_msg_hdr)) {
dev_err(cl_data_to_dev(client_data), "data size %zu is less than header %zu\n",
data_len, sizeof(struct loader_msg_hdr));
client_data->response.error = -EMSGSIZE; goto end;
}
if (data_len > client_data->response.max_size) {
dev_err(cl_data_to_dev(client_data), "Received buffer size %zu is larger than allocated buffer %zu\n",
data_len, client_data->response.max_size);
client_data->response.error = -EMSGSIZE; goto end;
}
/* We expect only "response" messages from firmware */ if (!(hdr->command & IS_RESPONSE)) {
dev_err(cl_data_to_dev(client_data), "Invalid response to command\n");
client_data->response.error = -EIO; goto end;
}
if (hdr->status) {
dev_err(cl_data_to_dev(client_data), "Loader returned status %d\n",
hdr->status);
client_data->response.error = -EIO; goto end;
}
/* Update the actual received buffer size */
client_data->response.size = data_len;
/* * Copy the buffer received in firmware response for the * calling thread.
*/
memcpy(client_data->response.data,
rb_in_proc->buffer.data, data_len);
/* Set flag before waking up the caller */
client_data->response.received = true;
end: /* Free the buffer */
ishtp_cl_io_rb_recycle(rb_in_proc);
rb_in_proc = NULL;
/* Wake the calling thread */
wake_up_interruptible(&client_data->response.wait_queue);
}
/** * loader_cl_event_cb() - bus driver callback for incoming message * @cl_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
*/ staticvoid loader_cl_event_cb(struct ishtp_cl_device *cl_device)
{ struct ishtp_cl_rb *rb_in_proc; struct ishtp_cl *loader_ishtp_cl = ishtp_get_drvdata(cl_device);
while ((rb_in_proc = ishtp_cl_rx_get_rb(loader_ishtp_cl)) != NULL) { /* Process the data packet from firmware */
process_recv(loader_ishtp_cl, rb_in_proc);
}
}
/** * ish_query_loader_prop() - Query ISH Shim firmware loader * @client_data: Client data instance * @fw: Pointer to firmware data struct in host memory * @fw_info: Loader firmware properties * * This function queries the ISH Shim firmware loader for capabilities. * * Return: 0 for success, negative error code for failure.
*/ staticint ish_query_loader_prop(struct ishtp_cl_data *client_data, conststruct firmware *fw, struct shim_fw_info *fw_info)
{ int rv; struct loader_xfer_query ldr_xfer_query; struct loader_xfer_query_response ldr_xfer_query_resp;
/* On success, the return value is the received buffer size */ if (rv != sizeof(struct loader_xfer_query_response)) {
dev_err(cl_data_to_dev(client_data), "data size %d is not equal to size of loader_xfer_query_response %zu\n",
rv, sizeof(struct loader_xfer_query_response));
client_data->flag_retry = true;
*fw_info = (struct shim_fw_info){}; return -EMSGSIZE;
}
/* Save fw_info for use outside this function */
*fw_info = ldr_xfer_query_resp.fw_info;
/* Sanity checks */ if (fw_info->ldr_capability.max_fw_image_size < fw->size) {
dev_err(cl_data_to_dev(client_data), "ISH firmware size %zu is greater than Shim firmware loader max supported %d\n",
fw->size,
fw_info->ldr_capability.max_fw_image_size); return -ENOSPC;
}
/* For DMA the buffer size should be multiple of cacheline size */ if ((fw_info->ldr_capability.xfer_mode & LOADER_XFER_MODE_DIRECT_DMA) &&
(fw_info->ldr_capability.max_dma_buf_size % L1_CACHE_BYTES)) {
dev_err(cl_data_to_dev(client_data), "Shim firmware loader buffer size %d should be multiple of cacheline\n",
fw_info->ldr_capability.max_dma_buf_size); return -EINVAL;
}
return 0;
}
/** * ish_fw_xfer_ishtp() - Loads ISH firmware using ishtp interface * @client_data: Client data instance * @fw: Pointer to firmware data struct in host memory * * This function uses ISH-TP to transfer ISH firmware from host to * ISH SRAM. Lower layers may use IPC or DMA depending on firmware * support. * * Return: 0 for success, negative error code for failure.
*/ staticint ish_fw_xfer_ishtp(struct ishtp_cl_data *client_data, conststruct firmware *fw)
{ int rv;
u32 fragment_offset, fragment_size, payload_max_size; struct loader_xfer_ipc_fragment *ldr_xfer_ipc_frag; struct loader_msg_hdr ldr_xfer_ipc_ack;
end_err_resp_buf_release: /* Free ISH buffer if not done already, in error case */
kfree(ldr_xfer_ipc_frag); return rv;
}
/** * ish_fw_xfer_direct_dma() - Loads ISH firmware using direct dma * @client_data: Client data instance * @fw: Pointer to firmware data struct in host memory * @fw_info: Loader firmware properties * * Host firmware load is a unique case where we need to download * a large firmware image (200+ Kb). This function implements * direct DMA transfer in kernel and ISH firmware. This allows * us to overcome the ISH-TP 4 Kb limit, and allows us to DMA * directly to ISH UMA at location of choice. * Function depends on corresponding support in ISH firmware. * * Return: 0 for success, negative error code for failure.
*/ staticint ish_fw_xfer_direct_dma(struct ishtp_cl_data *client_data, conststruct firmware *fw, conststruct shim_fw_info fw_info)
{ int rv = 0; void *dma_buf;
dma_addr_t dma_buf_phy;
u32 fragment_offset, fragment_size, payload_max_size; struct loader_msg_hdr ldr_xfer_dma_frag_ack; struct loader_xfer_dma_fragment ldr_xfer_dma_frag; struct device *devc = ishtp_get_pci_device(client_data->cl_device);
u32 shim_fw_buf_size =
fw_info.ldr_capability.max_dma_buf_size;
/* * payload_max_size should be set to minimum of * (1) Size of firmware to be loaded, * (2) Max DMA buffer size supported by Shim firmware, * (3) DMA buffer size limit set by boot_param dma_buf_size_limit.
*/
payload_max_size = min3(fw->size,
(size_t)shim_fw_buf_size,
(size_t)dma_buf_size_limit);
/* * Buffer size should be multiple of cacheline size * if it's not, select the previous cacheline boundary.
*/
payload_max_size &= ~(L1_CACHE_BYTES - 1);
/** * ish_fw_start() - Start executing ISH main firmware * @client_data: client data instance * * This function sends message to Shim firmware loader to start * the execution of ISH main firmware. * * Return: 0 for success, negative error code for failure.
*/ staticint ish_fw_start(struct ishtp_cl_data *client_data)
{ struct loader_start ldr_start; struct loader_msg_hdr ldr_start_ack;
/** * load_fw_from_host() - Loads ISH firmware from host * @client_data: Client data instance * * This function loads the ISH firmware to ISH SRAM and starts execution * * Return: 0 for success, negative error code for failure.
*/ staticint load_fw_from_host(struct ishtp_cl_data *client_data)
{ int rv;
u32 xfer_mode; char *filename; conststruct firmware *fw; struct shim_fw_info fw_info; struct ishtp_cl *loader_ishtp_cl = client_data->loader_ishtp_cl;
/* * The sequence of the following two cancel_work_sync() is * important. The work_fw_load can in turn schedue * work_ishtp_reset, so first cancel work_fw_load then * cancel work_ishtp_reset.
*/
cancel_work_sync(&client_data->work_fw_load);
cancel_work_sync(&client_data->work_ishtp_reset);
loader_deinit(loader_ishtp_cl);
ishtp_put_device(cl_device);
}
/** * loader_ishtp_cl_reset() - ISH-TP client driver reset * @cl_device: ISH-TP client device instance * * This function gets called on device reset on ISH-TP bus * * Return: 0
*/ staticint loader_ishtp_cl_reset(struct ishtp_cl_device *cl_device)
{ struct ishtp_cl_data *client_data; struct ishtp_cl *loader_ishtp_cl = ishtp_get_drvdata(cl_device);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.