// SPDX-License-Identifier: GPL-2.0 // ISHTP interface for ChromeOS Embedded Controller // // Copyright (c) 2019, Intel Corporation. // // ISHTP client driver for talking to the Chrome OS EC firmware running // on Intel Integrated Sensor Hub (ISH) using the ISH Transport protocol // (ISH-TP).
/* * ISH TX/RX ring buffer pool size * * The AP->ISH messages and corresponding ISH->AP responses are * serialized. We need 1 TX and 1 RX buffer for these. * * The MKBP ISH->AP events are serialized. We need one additional RX * buffer for them.
*/ #define CROS_ISH_CL_TX_RING_SIZE 8 #define CROS_ISH_CL_RX_RING_SIZE 8
/* * ISH firmware timeout for 1 message send failure is 1Hz, and the * firmware will retry 2 times, so 3Hz is used for timeout.
*/ #define ISHTP_SEND_TIMEOUT (3 * HZ)
/* * The Read-Write Semaphore is used to prevent message TX or RX while * the ishtp client is being initialized or undergoing reset. * * The readers are the kernel function calls responsible for IA->ISH * and ISH->AP messaging. * * The writers are .reset() and .probe() function.
*/ static DECLARE_RWSEM(init_lock);
/** * struct response_info - Encapsulate firmware response related * information for passing between function ish_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: 0 for success, negative error code for a failure in process_recv(). * @token: Expected token for response that we are waiting on. * @received: Set to true on receiving a valid firmware response to host command * @wait_queue: Wait queue for host to wait for firmware response.
*/ struct response_info { void *data;
size_t max_size;
size_t size; int error;
u8 token; bool received;
wait_queue_head_t wait_queue;
};
/** * struct ishtp_cl_data - Encapsulate per ISH TP Client. * * @cros_ish_cl: ISHTP firmware client instance. * @cl_device: ISHTP client device instance. * @response: Response info passing between ish_send() and process_recv(). * @work_ishtp_reset: Work queue reset handling. * @work_ec_evt: Work queue for EC events. * @ec_dev: CrOS EC MFD device. * * This structure is used to store per client data.
*/ struct ishtp_cl_data { struct ishtp_cl *cros_ish_cl; struct ishtp_cl_device *cl_device;
/* * Used for passing firmware response information between * ish_send() and process_recv() callback.
*/ struct response_info response;
/** * ish_evt_handler - ISH to AP event handler * @work: Work struct
*/ staticvoid ish_evt_handler(struct work_struct *work)
{ struct ishtp_cl_data *client_data =
container_of(work, struct ishtp_cl_data, work_ec_evt);
cros_ec_irq_thread(0, client_data->ec_dev);
}
/** * ish_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 is 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 ish_send(struct ishtp_cl_data *client_data,
u8 *out_msg, size_t out_size,
u8 *in_msg, size_t in_size)
{ static u8 next_token; int rv; struct header *out_hdr = (struct header *)out_msg; struct ishtp_cl *cros_ish_cl = client_data->cros_ish_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 host message\n"); return -ETIMEDOUT;
}
if (client_data->response.error < 0) return client_data->response.error;
return client_data->response.size;
}
/** * process_recv() - Received and parse incoming packet * @cros_ish_cl: Client instance to get stats * @rb_in_proc: Host interface message buffer * @timestamp: Timestamp of when parent callback started * * 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. If it is an event packet then it will schedule event work.
*/ staticvoid process_recv(struct ishtp_cl *cros_ish_cl, struct ishtp_cl_rb *rb_in_proc, ktime_t timestamp)
{
size_t data_len = rb_in_proc->buf_idx; struct ishtp_cl_data *client_data =
ishtp_get_client_data(cros_ish_cl); struct device *dev = cl_data_to_dev(client_data); struct cros_ish_in_msg *in_msg =
(struct cros_ish_in_msg *)rb_in_proc->buffer.data;
/* Proceed only if reset or init is not in progress */ if (!down_read_trylock(&init_lock)) { /* Free the buffer */
ishtp_cl_io_rb_recycle(rb_in_proc);
dev_warn(dev, "Host is not ready to receive incoming messages\n"); return;
}
/* * All firmware messages contain a header. Check the buffer size * before accessing elements inside.
*/ if (!rb_in_proc->buffer.data) {
dev_warn(dev, "rb_in_proc->buffer.data returned null");
client_data->response.error = -EBADMSG; goto end_error;
}
if (data_len < sizeof(struct header)) {
dev_err(dev, "data size %zu is less than header %zu\n",
data_len, sizeof(struct header));
client_data->response.error = -EMSGSIZE; goto end_error;
}
switch (in_msg->hdr.channel) { case CROS_EC_COMMAND: if (client_data->response.received) {
dev_err(dev, "Previous firmware message not yet processed\n"); goto end_error;
}
if (client_data->response.token != in_msg->hdr.token) {
dev_err_ratelimited(dev, "Dropping old response token %d\n",
in_msg->hdr.token); goto end_error;
}
/* Sanity check */ if (!client_data->response.data) {
dev_err(dev, "Receiving buffer is null. Should be allocated by calling function\n");
client_data->response.error = -EINVAL; goto error_wake_up;
}
if (data_len > client_data->response.max_size) {
dev_err(dev, "Received buffer size %zu is larger than allocated buffer %zu\n",
data_len, client_data->response.max_size);
client_data->response.error = -EMSGSIZE; goto error_wake_up;
}
if (in_msg->hdr.status) {
dev_err(dev, "firmware returned status %d\n",
in_msg->hdr.status);
client_data->response.error = -EIO; goto error_wake_up;
}
/* 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);
error_wake_up: /* Free the buffer since we copied data or didn't need it */
ishtp_cl_io_rb_recycle(rb_in_proc);
rb_in_proc = NULL;
/* Set flag before waking up the caller */
client_data->response.received = true;
/* Wake the calling thread */
wake_up_interruptible(&client_data->response.wait_queue);
break;
case CROS_MKBP_EVENT: /* Free the buffer. This is just an event without data */
ishtp_cl_io_rb_recycle(rb_in_proc);
rb_in_proc = NULL; /* * Set timestamp from beginning of function since we actually * got an incoming MKBP event
*/
client_data->ec_dev->last_event_time = timestamp;
schedule_work(&client_data->work_ec_evt);
end_error: /* Free the buffer if we already haven't */ if (rb_in_proc)
ishtp_cl_io_rb_recycle(rb_in_proc);
up_read(&init_lock);
}
/** * ish_event_cb() - bus driver callback for incoming message * @cl_device: ISHTP client device for which this message is targeted. * * Remove the packet from the list and process the message by calling * process_recv.
*/ staticvoid ish_event_cb(struct ishtp_cl_device *cl_device)
{ struct ishtp_cl_rb *rb_in_proc; struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device);
ktime_t timestamp;
/* * Take timestamp as close to hardware interrupt as possible for sensor * timestamps.
*/
timestamp = cros_ec_get_time_ns();
while ((rb_in_proc = ishtp_cl_rx_get_rb(cros_ish_cl)) != NULL) { /* Decide what to do with received data */
process_recv(cros_ish_cl, rb_in_proc, timestamp);
}
}
/** * cros_ish_init() - Init function for ISHTP client * @cros_ish_cl: ISHTP client instance * @reset: true if called from reset handler * * This function complete the initializtion of the client. * * Return: 0 for success, negative error code for failure.
*/ staticint cros_ish_init(struct ishtp_cl *cros_ish_cl, bool reset)
{ int rv; struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
if (in_msg->ec_response.data_len > msg->insize) {
dev_err(ec_dev->dev, "Packet too long (%d bytes, expected %d)",
in_msg->ec_response.data_len, msg->insize); return -ENOSPC;
}
/* Copy response packet payload and compute checksum */ for (i = 0; i < sizeof(struct ec_host_response); i++)
sum += ((u8 *)in_msg)[IN_MSG_EC_RESPONSE_PREAMBLE + i];
offset = sizeof(struct cros_ish_in_msg); for (i = 0; i < in_msg->ec_response.data_len; i++)
sum += msg->data[i] = ((u8 *)in_msg)[offset + i];
if (sum) {
dev_dbg(ec_dev->dev, "Bad received packet checksum %d\n", sum); return -EBADMSG;
}
/* Sanity checks */ if (in_size > ec_dev->din_size) {
dev_err(dev, "Incoming payload size %zu is too large for ec_dev->din_size %d\n",
in_size, ec_dev->din_size); return -EMSGSIZE;
}
if (out_size > ec_dev->dout_size) {
dev_err(dev, "Outgoing payload size %zu is too large for ec_dev->dout_size %d\n",
out_size, ec_dev->dout_size); return -EMSGSIZE;
}
/* Proceed only if reset-init is not in progress */ if (!down_read_trylock(&init_lock)) {
dev_warn(dev, "Host is not ready to send messages to ISH. Try again\n"); return -EAGAIN;
}
/* Prepare the package to be sent over ISH TP */
out_msg->hdr.channel = CROS_EC_COMMAND;
out_msg->hdr.status = 0;
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.