diff --git a/drivers/i3c/CMakeLists.txt b/drivers/i3c/CMakeLists.txt index 8a941aef829..889e9539bc9 100644 --- a/drivers/i3c/CMakeLists.txt +++ b/drivers/i3c/CMakeLists.txt @@ -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 +) diff --git a/drivers/i3c/Kconfig b/drivers/i3c/Kconfig index 4d055e7a542..60714f03812 100644 --- a/drivers/i3c/Kconfig +++ b/drivers/i3c/Kconfig @@ -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" diff --git a/drivers/i3c/i3c_ibi_workq.c b/drivers/i3c/i3c_ibi_workq.c new file mode 100644 index 00000000000..4df003b3e7b --- /dev/null +++ b/drivers/i3c/i3c_ibi_workq.c @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2016 Wind River Systems, Inc. + * Copyright (c) 2016,2022 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +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); diff --git a/include/zephyr/drivers/i3c/ibi.h b/include/zephyr/drivers/i3c/ibi.h index cd3c6db56de..8156db1c22e 100644 --- a/include/zephyr/drivers/i3c/ibi.h +++ b/include/zephyr/drivers/i3c/ibi.h @@ -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