i3c: add a global workqueue for IBI

Adds support for a global workqueue so drivers can defer
IBI callbacks instead of doing it in interrupt context.

Signed-off-by: Daniel Leung <daniel.leung@intel.com>
This commit is contained in:
Daniel Leung 2022-07-12 11:11:04 -07:00 committed by Anas Nashif
parent 1493285912
commit 66a9a15104
4 changed files with 400 additions and 0 deletions

View file

@ -13,3 +13,8 @@ zephyr_library_sources_ifdef(
CONFIG_USERSPACE
i3c_handlers.c
)
zephyr_library_sources_ifdef(
CONFIG_I3C_IBI_WORKQUEUE
i3c_ibi_workq.c
)

View file

@ -40,6 +40,43 @@ config I3C_IBI_MAX_PAYLOAD_SIZE
help
Maxmium IBI payload size.
menuconfig I3C_IBI_WORKQUEUE
bool "Use IBI Workqueue"
help
Use global workqueue for processing IBI.
This is enabled by driver if needed.
if I3C_IBI_WORKQUEUE
config I3C_IBI_WORKQUEUE_STACK_SIZE
int "IBI workqueue stack size"
default 1024
help
Stack size for the IBI global workqueue.
config I3C_IBI_WORKQUEUE_PRIORITY
int "IBI workqueue thread priority"
default -1
help
Thread priority for the IBI global workqueue.
config I3C_IBI_WORKQUEUE_LENGTH
int "IBI workqueue queue length"
default 8
help
Define the maximum number of IBIs that can be
queued in the workqueue.
config I3C_IBI_WORKQUEUE_VERBOSE_DEBUG
bool "Verbose debug messages for IBI workqueue"
help
This turns on verbose debug for the IBI workqueue
when logging level is set to DEBUG, and prints
the IBI payload.
endif # I3C_IBI_WORKQUEUE
endif # I3C_USE_IBI
comment "Initialization Priority"

236
drivers/i3c/i3c_ibi_workq.c Normal file
View file

@ -0,0 +1,236 @@
/*
* Copyright (c) 2016 Wind River Systems, Inc.
* Copyright (c) 2016,2022 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/init.h>
#include <zephyr/drivers/i3c.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(i3c, CONFIG_I3C_LOG_LEVEL);
/* Statically allocated array of IBI work item nodes */
static struct i3c_ibi_work i3c_ibi_work_nodes[CONFIG_I3C_IBI_WORKQUEUE_LENGTH];
static K_KERNEL_STACK_DEFINE(i3c_ibi_work_q_stack,
CONFIG_I3C_IBI_WORKQUEUE_STACK_SIZE);
/* IBI workqueue */
static struct k_work_q i3c_ibi_work_q;
static sys_slist_t i3c_ibi_work_nodes_free;
static inline int ibi_work_submit(struct i3c_ibi_work *ibi_node)
{
return k_work_submit_to_queue(&i3c_ibi_work_q, &ibi_node->work);
}
int i3c_ibi_work_enqueue(struct i3c_ibi_work *ibi_work)
{
sys_snode_t *node;
struct i3c_ibi_work *ibi_node;
int ret;
node = sys_slist_get(&i3c_ibi_work_nodes_free);
if (node == NULL) {
ret = -ENOMEM;
goto out;
}
ibi_node = (struct i3c_ibi_work *)node;
(void)memcpy(ibi_node, ibi_work, sizeof(*ibi_node));
ret = ibi_work_submit(ibi_node);
if (ret >= 0) {
ret = 0;
}
out:
return ret;
}
int i3c_ibi_work_enqueue_target_irq(struct i3c_device_desc *target,
uint8_t *payload, size_t payload_len)
{
sys_snode_t *node;
struct i3c_ibi_work *ibi_node;
int ret;
node = sys_slist_get(&i3c_ibi_work_nodes_free);
if (node == NULL) {
ret = -ENOMEM;
goto out;
}
ibi_node = (struct i3c_ibi_work *)node;
ibi_node->type = I3C_IBI_TARGET_INTR;
ibi_node->target = target;
ibi_node->payload.payload_len = payload_len;
if ((payload != NULL) && (payload_len > 0U)) {
(void)memcpy(&ibi_node->payload.payload[0],
payload, payload_len);
}
ret = ibi_work_submit(ibi_node);
if (ret >= 0) {
ret = 0;
}
out:
return ret;
}
int i3c_ibi_work_enqueue_hotjoin(const struct device *dev)
{
sys_snode_t *node;
struct i3c_ibi_work *ibi_node;
int ret;
node = sys_slist_get(&i3c_ibi_work_nodes_free);
if (node == NULL) {
ret = -ENOMEM;
goto out;
}
ibi_node = (struct i3c_ibi_work *)node;
ibi_node->type = I3C_IBI_HOTJOIN;
ibi_node->controller = dev;
ibi_node->payload.payload_len = 0;
ret = ibi_work_submit(ibi_node);
if (ret >= 0) {
ret = 0;
}
out:
return ret;
}
int i3c_ibi_work_enqueue_cb(const struct device *dev,
k_work_handler_t work_cb)
{
sys_snode_t *node;
struct i3c_ibi_work *ibi_node;
int ret;
node = sys_slist_get(&i3c_ibi_work_nodes_free);
if (node == NULL) {
ret = -ENOMEM;
goto out;
}
ibi_node = (struct i3c_ibi_work *)node;
ibi_node->type = I3C_IBI_WORKQUEUE_CB;
ibi_node->controller = dev;
ibi_node->work_cb = work_cb;
ret = ibi_work_submit(ibi_node);
if (ret >= 0) {
ret = 0;
}
out:
return ret;
}
static void i3c_ibi_work_handler(struct k_work *work)
{
struct i3c_ibi_work *ibi_node = CONTAINER_OF(work, struct i3c_ibi_work, work);
struct i3c_ibi_payload *payload;
int ret = 0;
if (IS_ENABLED(CONFIG_I3C_IBI_WORKQUEUE_VERBOSE_DEBUG) &&
((uint32_t)ibi_node->type <= I3C_IBI_TYPE_MAX)) {
LOG_DBG("Processing IBI work %p (type %d, len %u)",
ibi_node, (int)ibi_node->type,
ibi_node->payload.payload_len);
if (ibi_node->payload.payload_len > 0U) {
LOG_HEXDUMP_DBG(&ibi_node->payload.payload[0],
ibi_node->payload.payload_len, "IBI Payload");
}
}
switch (ibi_node->type) {
case I3C_IBI_TARGET_INTR:
if (ibi_node->payload.payload_len != 0U) {
payload = &ibi_node->payload;
} else {
payload = NULL;
}
ret = ibi_node->target->ibi_cb(ibi_node->target, payload);
if ((ret != 0) && (ret != -EBUSY)) {
LOG_ERR("IBI work %p cb returns %d", ibi_node, ret);
}
break;
case I3C_IBI_HOTJOIN:
ret = i3c_do_daa(ibi_node->controller);
if ((ret != 0) && (ret != -EBUSY)) {
LOG_ERR("i3c_do_daa returns %d", ret);
}
break;
case I3C_IBI_WORKQUEUE_CB:
if (ibi_node->work_cb != NULL) {
ibi_node->work_cb(work);
}
break;
case I3C_IBI_CONTROLLER_ROLE_REQUEST:
/* TODO: Add support for controller role request */
__fallthrough;
default:
/* Unknown IBI type: do nothing */
LOG_DBG("Cannot process IBI type %d", (int)ibi_node->type);
break;
}
if (ret == -EBUSY) {
/* Retry if bus is busy. */
if (ibi_work_submit(ibi_node) < 0) {
LOG_ERR("Error re-adding IBI work %p", ibi_node);
}
} else {
/* Add the now processed node back to the free list */
sys_slist_append(&i3c_ibi_work_nodes_free, (sys_snode_t *)ibi_node);
}
}
static int i3c_ibi_work_q_init(const struct device *dev)
{
ARG_UNUSED(dev);
struct k_work_queue_config cfg = {
.name = "i3c_ibi_workq",
.no_yield = true,
};
/* Init the linked list of work item nodes */
sys_slist_init(&i3c_ibi_work_nodes_free);
for (int i = 0; i < ARRAY_SIZE(i3c_ibi_work_nodes); i++) {
i3c_ibi_work_nodes[i].work.handler = i3c_ibi_work_handler;
sys_slist_append(&i3c_ibi_work_nodes_free,
(sys_snode_t *)&i3c_ibi_work_nodes[i]);
}
/* Start the workqueue */
k_work_queue_start(&i3c_ibi_work_q, i3c_ibi_work_q_stack,
K_KERNEL_STACK_SIZEOF(i3c_ibi_work_q_stack),
CONFIG_I3C_IBI_WORKQUEUE_PRIORITY, &cfg);
return 0;
}
SYS_INIT(i3c_ibi_work_q_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);

View file

@ -43,6 +43,12 @@ enum i3c_ibi_type {
I3C_IBI_HOTJOIN,
I3C_IBI_TYPE_MAX = I3C_IBI_HOTJOIN,
/*
* Not an actual IBI type, but simply used by
* the IBI workq for generic callbacks.
*/
I3C_IBI_WORKQUEUE_CB,
};
/**
@ -76,6 +82,52 @@ struct i3c_ibi_payload {
uint8_t payload[CONFIG_I3C_IBI_MAX_PAYLOAD_SIZE];
};
/**
* @brief Node about a queued IBI.
*/
struct i3c_ibi_work {
/**
* Private, do not modify.
*/
sys_snode_t node;
/**
* k_work struct.
*/
struct k_work work;
/**
* IBI type.
*/
enum i3c_ibi_type type;
union {
/**
* Use for @see I3C_IBI_HOTJOIN.
*/
const struct device *controller;
/**
* Use for @see I3C_IBI_TARGET_INTR,
* and @see I3C_IBI_CONTROLLER_ROLE_REQUEST.
*/
struct i3c_device_desc *target;
};
union {
/**
* IBI payload.
*/
struct i3c_ibi_payload payload;
/**
* Generic workqueue callback when
* type is I3C_IBI_WORKQUEUE_CB.
*/
k_work_handler_t work_cb;
};
};
/**
* @brief Function called when In-Band Interrupt received from target device.
*
@ -96,6 +148,76 @@ struct i3c_ibi_payload {
typedef int (*i3c_target_ibi_cb_t)(struct i3c_device_desc *target,
struct i3c_ibi_payload *payload);
/**
* @brief Queue an IBI work item for future processing.
*
* This queues up an IBI work item in the IBI workqueue
* for future processing.
*
* Note that this will copy the @p ibi_work struct into
* internal structure. If there is not enough space to
* copy the @p ibi_work struct, this returns -ENOMEM.
*
* @param ibi_work Pointer to the IBI work item struct.
*
* @retval 0 If work item is successfully queued.
* @retval -ENOMEM If no more free internal node to
* store IBI work item.
* @retval Others @see k_work_submit_to_queue
*/
int i3c_ibi_work_enqueue(struct i3c_ibi_work *ibi_work);
/**
* @brief Queue a target interrupt IBI for future processing.
*
* This queues up a target interrupt IBI in the IBI workqueue
* for future processing.
*
* @param target Pointer to target device descriptor.
* @param payload Pointer to IBI payload byte array.
* @param payload_len Length of payload byte array.
*
* @retval 0 If work item is successfully queued.
* @retval -ENOMEM If no more free internal node to
* store IBI work item.
* @retval Others @see k_work_submit_to_queue
*/
int i3c_ibi_work_enqueue_target_irq(struct i3c_device_desc *target,
uint8_t *payload, size_t payload_len);
/**
* @brief Queue a hot join IBI for future processing.
*
* This queues up a hot join IBI in the IBI workqueue
* for future processing.
*
* @param dev Pointer to controller device driver instance.
*
* @retval 0 If work item is successfully queued.
* @retval -ENOMEM If no more free internal node to
* store IBI work item.
* @retval Others @see k_work_submit_to_queue
*/
int i3c_ibi_work_enqueue_hotjoin(const struct device *dev);
/**
* @brief Queue a generic callback for future processing.
*
* This queues up a generic callback in the IBI workqueue
* for future processing.
*
* @param dev Pointer to controller device driver instance.
* @param work_cb Callback function.
*
* @retval 0 If work item is successfully queued.
* @retval -ENOMEM If no more free internal node to
* store IBI work item.
* @retval Others @see k_work_submit_to_queue
*/
int i3c_ibi_work_enqueue_cb(const struct device *dev,
k_work_handler_t work_cb);
#ifdef __cplusplus
}
#endif