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
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);