diff --git a/doc/services/ipc/index.rst b/doc/services/ipc/index.rst index 1b4cbc6b5e52b..7893e6867327d 100644 --- a/doc/services/ipc/index.rst +++ b/doc/services/ipc/index.rst @@ -7,3 +7,4 @@ Interprocessor Communication (IPC) :maxdepth: 1 ipc_service/ipc_service.rst + ipc_msg_service/ipc_msg_service.rst diff --git a/doc/services/ipc/ipc_msg_service/ipc_msg_service.rst b/doc/services/ipc/ipc_msg_service/ipc_msg_service.rst new file mode 100644 index 0000000000000..445c8397db0d9 --- /dev/null +++ b/doc/services/ipc/ipc_msg_service/ipc_msg_service.rst @@ -0,0 +1,257 @@ +.. _ipc_msg_service: + +IPC Message Service +################### + +.. contents:: + :local: + :depth: 2 + +The IPC message service API provides an interface to facilitate the following +functions between two domains or CPUs: + +* Exchanging messages, where these messages are strongly typed, +* Query status for associated domains or CPUs, and +* Receiving events pertaining to associated domains or CPUs. + +Overview +======== + +A communication channel consists of one instance and one or several endpoints +associated with the instance. + +An instance is the external representation of a physical communication channel +between two domains or CPUs. The actual implementation and internal +representation of the instance is peculiar to each backend. + +An individual instance is not used to send messages between domains/CPUs. +To send and receive messages, the user must create (register) an endpoint in +the instance. This allows for the connection of the two domains of interest. + +It is possible to have zero or multiple endpoints for one single instance, +possibly with different priorities, and to use each to exchange messages. +Endpoint prioritization and multi-instance ability highly depend on the backend +used and its implementation. + +The endpoint is an entity the user must use to send and receive messages between +two domains (connected by the instance). An endpoint is always associated to an +instance. + +The creation of the instances is left to the backend, usually at init time. +The registration of the endpoints is left to the user, usually at run time. + +The API does not mandate a way for the backend to create instances but it is +strongly recommended to use the devicetree to retrieve the configuration +parameters for an instance. Currently, each backend defines its own +DT-compatible configuration that is used to configure the interface at boot +time. + +Exchanging messages +=================== + +To send messages between domains or CPUs, an endpoint must be registered onto +an instance. + +See the following example: + +.. note:: + + Before registering an endpoint, the instance must be opened using the + :c:func:`ipc_msg_service_open_instance` function. + + +.. code-block:: c + + #include + + static void bound_cb(void *priv) + { + /* Endpoint bounded */ + } + + static int recv_cb(uint16_t msg_type, const void *msg_data, void *priv) + { + /* Data received */ + + return 0; + } + + static struct ipc_msg_ept_cfg ept0_cfg = { + .name = "ept0", + .cb = { + .bound = bound_cb, + .received = recv_cb, + }, + }; + + int main(void) + { + const struct device *inst0; + struct ipc_ept ept0; + struct ipc_msg_type_cmd message; + int ret; + + inst0 = DEVICE_DT_GET(DT_NODELABEL(ipc0)); + ret = ipc_msg_service_open_instance(inst0); + ret = ipc_msg_service_register_endpoint(inst0, &ept0, &ept0_cfg); + + /* Wait for endpoint bound (bound_cb called) */ + + message.cmd = 0x01; + ret = ipc_msg_service_send(&ept0, IPC_MSG_TYPE_CMD, &message); + } + +Querying status +=============== + +The API provides a way to perform query on the backend regarding instance +and endpoint. + +See the following example for querying if the endpoint is ready for +exchanging messages: + +.. code-block:: c + + #include + + static void bound_cb(void *priv) + { + /* Endpoint bounded */ + } + + static int recv_cb(uint16_t msg_type, const void *msg_data, void *priv) + { + /* Data received */ + + return 0; + } + + static struct ipc_msg_ept_cfg ept0_cfg = { + .name = "ept0", + .cb = { + .bound = bound_cb, + .received = recv_cb, + }, + }; + + int main(void) + { + const struct device *inst0; + struct ipc_ept ept0; + struct ipc_msg_type_cmd message; + int ret; + + inst0 = DEVICE_DT_GET(DT_NODELABEL(ipc0)); + ret = ipc_msg_service_open_instance(inst0); + ret = ipc_msg_service_register_endpoint(inst0, &ept0, &ept0_cfg); + + /* Wait for endpoint bound (bound_cb called) */ + + /* Check if endpoint is ready. */ + ret = ipc_msg_service_query(&ept0, IPC_MSG_QUERY_IS_READY, NULL, NULL); + if (ret != 0) { + /* Endpoint is not ready */ + } + + message.cmd = 0x01; + ret = ipc_msg_service_send(&ept0, IPC_MSG_TYPE_CMD, &message); + } + +Events +====== + +The backend can also do a callback when certain events come in through +the instance or endpoint. + +See the following example on adding an event callback: + +.. code-block:: c + + #include + + static void bound_cb(void *priv) + { + /* Endpoint bounded */ + } + + static int recv_cb(uint16_t msg_type, const void *msg_data, void *priv) + { + /* Data received */ + + return 0; + } + + static int evt_cb(uint16_t evt_type, const void *evt_data, void *priv) + { + /* Event received */ + + return 0; + } + + static struct ipc_msg_ept_cfg ept0_cfg = { + .name = "ept0", + .cb = { + .bound = bound_cb, + .event = evt_cb, + .received = recv_cb, + }, + }; + + int main(void) + { + const struct device *inst0; + struct ipc_ept ept0; + struct ipc_msg_type_cmd message; + int ret; + + inst0 = DEVICE_DT_GET(DT_NODELABEL(ipc0)); + ret = ipc_msg_service_open_instance(inst0); + ret = ipc_msg_service_register_endpoint(inst0, &ept0, &ept0_cfg); + + /* Wait for endpoint bound (bound_cb called) */ + + /* Check if endpoint is ready. */ + ret = ipc_msg_service_query(&ept0, IPC_MSG_QUERY_IS_READY, NULL, NULL); + if (ret != 0) { + /* Endpoint is not ready */ + } + + message.cmd = 0x01; + ret = ipc_msg_service_send(&ept0, IPC_MSG_TYPE_CMD, &message); + } + +Backends +======== + +The requirements needed for implementing backends give flexibility to the IPC +message service. These allow for the addition of dedicated backends having only +a subsets of features for specific use cases. + +The backend must support at least the following: + +* The init-time creation of instances. +* The run-time registration of an endpoint in an instance. + +Additionally, the backend can also support the following: + +* The run-time deregistration of an endpoint from the instance. +* The run-time closing of an instance. +* The run-time querying of an endpoint or instance status. + +Each backend can have its own limitations and features that make the backend +unique and dedicated to a specific use case. The IPC message service API can be +used with multiple backends simultaneously, combining the pros and cons of each +backend. + +API Reference +============= + +IPC Message Service API +*********************** + +.. doxygengroup:: ipc_msg_service_api + +IPC Message Service Backend API +******************************* + +.. doxygengroup:: ipc_msg_service_backend diff --git a/drivers/ipm/CMakeLists.txt b/drivers/ipm/CMakeLists.txt index ca2aa03591c3e..c911433e589d2 100644 --- a/drivers/ipm/CMakeLists.txt +++ b/drivers/ipm/CMakeLists.txt @@ -10,7 +10,6 @@ zephyr_library_sources_ifdef(CONFIG_IPM_MHU ipm_mhu.c) zephyr_library_sources_ifdef(CONFIG_IPM_STM32_IPCC ipm_stm32_ipcc.c) zephyr_library_sources_ifdef(CONFIG_IPM_NRFX ipm_nrfx_ipc.c) zephyr_library_sources_ifdef(CONFIG_IPM_STM32_HSEM ipm_stm32_hsem.c) -zephyr_library_sources_ifdef(CONFIG_IPM_CAVS_HOST ipm_cavs_host.c) zephyr_library_sources_ifdef(CONFIG_IPM_SEDI ipm_sedi.c) zephyr_library_sources_ifdef(CONFIG_IPM_IVSHMEM ipm_ivshmem.c) zephyr_library_sources_ifdef(CONFIG_ESP32_SOFT_IPM ipm_esp32.c) diff --git a/drivers/ipm/Kconfig b/drivers/ipm/Kconfig index ba4571ed7581a..3bd88c44b238b 100644 --- a/drivers/ipm/Kconfig +++ b/drivers/ipm/Kconfig @@ -74,7 +74,6 @@ config IPM_MBOX source "drivers/ipm/Kconfig.nrfx" source "drivers/ipm/Kconfig.imx" source "drivers/ipm/Kconfig.stm32" -source "drivers/ipm/Kconfig.intel_adsp" source "drivers/ipm/Kconfig.ivshmem" source "drivers/ipm/Kconfig.sedi" diff --git a/drivers/ipm/Kconfig.intel_adsp b/drivers/ipm/Kconfig.intel_adsp deleted file mode 100644 index dbf6cdf91f28a..0000000000000 --- a/drivers/ipm/Kconfig.intel_adsp +++ /dev/null @@ -1,51 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# Copyright (c) 2022, Intel Corporation - -config IPM_CALLBACK_ASYNC - bool "Deliver callbacks asynchronously" - default y if IPM_CAVS_HOST - help - When selected, the driver supports "asynchronous" command - delivery. Commands will stay active after the ISR returns, - until the application expressly "completes" the command - later. - -config IPM_CAVS_HOST - bool "cAVS DSP/host communication" - select INTEL_ADSP_IPC - help - Driver for host/DSP communication on intel_adsp devices - -if IPM_CAVS_HOST - -config IPM_CAVS_HOST_INBOX_OFFSET - hex "Byte offset of cAVS inbox window" - depends on INTEL_ADSP_IPC - default 0x6000 - help - Location of the host-writable inbox window within the - HP_SRAM_RESERVE region. This location must be synchronized - with host driver and SOF source code (must match - SRAM_INBOX_BASE). Be careful. - -config IPM_CAVS_HOST_OUTBOX_OFFSET - hex "Byte offset of cAVS outbox memory" - depends on INTEL_ADSP_IPC - default 0x1000 - help - Location of the "outbox" region for SOF IPC3/4 message - within the pre-existing window 0 (this is not the same as - the HP_SRAM_RESERVE region used for INBOX_OFFSET). This - location must be synchronized with host driver and SOF - source code (where it must equal SRAM_SW_REG_SIZE). Be - careful. - -config IPM_CAVS_HOST_REGWORD - bool "Store first 4 bytes in IPC register" - depends on INTEL_ADSP_IPC - help - Protocol variant. When true, the first four bytes of a - message are passed in the cAVS IDR/TDR register pair instead - of in the SRAM window. Only available on cAVS 1.8+. - -endif # IPM_CAVS_HOST diff --git a/drivers/ipm/Kconfig.sedi b/drivers/ipm/Kconfig.sedi index bbb347dd5f467..c80676d153824 100644 --- a/drivers/ipm/Kconfig.sedi +++ b/drivers/ipm/Kconfig.sedi @@ -9,3 +9,11 @@ config IPM_SEDI This option enables the Intel SEDI IPM(IPC) driver. This driver is simply a shim driver built upon the SEDI bare metal IPC driver in the hal-intel module + +config IPM_CALLBACK_ASYNC + bool "Deliver callbacks asynchronously" + help + When selected, the driver supports "asynchronous" command + delivery. Commands will stay active after the ISR returns, + until the application expressly "completes" the command + later. diff --git a/drivers/ipm/ipm_cavs_host.c b/drivers/ipm/ipm_cavs_host.c deleted file mode 100644 index a616d0b41bc97..0000000000000 --- a/drivers/ipm/ipm_cavs_host.c +++ /dev/null @@ -1,212 +0,0 @@ -/* Copyright (c) 2022, Intel Corporation - * SPDX-License-Identifier: Apache-2.0 - */ -#include -#include -#include -#include -#include -#include -#include - -/* Matches SOF_IPC_MSG_MAX_SIZE, though in practice nothing anywhere - * near that big is ever sent. Should maybe consider making this a - * kconfig to avoid waste. - */ -#define MAX_MSG 384 - -/* Note: these addresses aren't flexible! We require that they match - * current SOF ipc3/4 layout, which means that: - * - * + Buffer addresses are 4k-aligned (this is a hardware requirement) - * + Inbuf must be 4k after outbuf, with no use of the intervening memory - * + Outbuf must be 4k after the start of win0 (this is where the host driver looks) - * - * One side effect is that the word "before" MSG_INBUF is owned by our - * code too, and can be used for a nice trick below. - */ - -/* host windows */ -#define DMWBA(win_base) (win_base + 0x0) -#define DMWLO(win_base) (win_base + 0x4) - - -struct ipm_cavs_host_data { - ipm_callback_t callback; - void *user_data; - bool enabled; -}; - -/* Note: this call is unsynchronized. The IPM docs are silent as to - * whether this is required, and the SOF code that will be using this - * is externally synchronized already. - */ -static int send(const struct device *dev, int wait, uint32_t id, - const void *data, int size) -{ - const struct device *mw0 = DEVICE_DT_GET(DT_NODELABEL(mem_window0)); - - if (!device_is_ready(mw0)) { - return -ENODEV; - } - const struct mem_win_config *mw0_config = mw0->config; - uint32_t *buf = (uint32_t *)sys_cache_uncached_ptr_get( - (void *)((uint32_t)mw0_config->mem_base - + CONFIG_IPM_CAVS_HOST_OUTBOX_OFFSET)); - - if (!intel_adsp_ipc_is_complete(INTEL_ADSP_IPC_HOST_DEV)) { - return -EBUSY; - } - - if ((size < 0) || (size > MAX_MSG)) { - return -EMSGSIZE; - } - - if ((id & 0xc0000000) != 0) { - /* cAVS IDR register has only 30 usable bits */ - return -EINVAL; - } - - uint32_t ext_data = 0; - - /* Protocol variant (used by SOF "ipc4"): store the first word - * of the message in the IPC scratch registers - */ - if (IS_ENABLED(CONFIG_IPM_CAVS_HOST_REGWORD) && size >= 4) { - ext_data = ((uint32_t *)data)[0]; - data = &((const uint32_t *)data)[1]; - size -= 4; - } - - memcpy(buf, data, size); - - int ret = intel_adsp_ipc_send_message(INTEL_ADSP_IPC_HOST_DEV, id, ext_data); - - /* The IPM docs call for "busy waiting" here, but in fact - * there's a blocking synchronous call available that might be - * better. But then we'd have to check whether we're in - * interrupt context, and it's not clear to me that SOF would - * benefit anyway as all its usage is async. This is OK for - * now. - */ - if (ret == -EBUSY && wait) { - while (!intel_adsp_ipc_is_complete(INTEL_ADSP_IPC_HOST_DEV)) { - k_busy_wait(1); - } - } - - return ret; -} - -static bool ipc_handler(const struct device *dev, void *arg, - uint32_t data, uint32_t ext_data) -{ - ARG_UNUSED(arg); - struct device *ipmdev = arg; - struct ipm_cavs_host_data *devdata = ipmdev->data; - const struct device *mw1 = DEVICE_DT_GET(DT_NODELABEL(mem_window1)); - - if (!device_is_ready(mw1)) { - return -ENODEV; - } - const struct mem_win_config *mw1_config = mw1->config; - uint32_t *msg = sys_cache_uncached_ptr_get((void *)mw1_config->mem_base); - - /* We play tricks to leave one word available before the - * beginning of the SRAM window, this way the host can see the - * same offsets it does with the original ipc4 protocol - * implementation, but here in the firmware we see a single - * contiguous buffer. See above. - */ - if (IS_ENABLED(CONFIG_IPM_CAVS_HOST_REGWORD)) { - msg = &msg[-1]; - msg[0] = ext_data; - } - - if (devdata->enabled && (devdata->callback != NULL)) { - devdata->callback(ipmdev, devdata->user_data, - data & 0x3fffffff, msg); - } - - /* Return false for async handling */ - return !IS_ENABLED(IPM_CALLBACK_ASYNC); -} - -static int max_data_size_get(const struct device *ipmdev) -{ - return MAX_MSG; -} - -static uint32_t max_id_val_get(const struct device *ipmdev) -{ - /* 30 user-writable bits in cAVS IDR register */ - return 0x3fffffff; -} - -static void register_callback(const struct device *port, - ipm_callback_t cb, - void *user_data) -{ - struct ipm_cavs_host_data *data = port->data; - - data->callback = cb; - data->user_data = user_data; -} - -static int set_enabled(const struct device *ipmdev, int enable) -{ - /* This protocol doesn't support any kind of queuing, and in - * fact will stall if a message goes unacknowledged. Support - * it as best we can by gating the callbacks only. That will - * allow the DONE notifications to proceed as normal, at the - * cost of dropping any messages received while not "enabled" - * of course. - */ - struct ipm_cavs_host_data *data = ipmdev->data; - - data->enabled = enable; - return 0; -} - -static void complete(const struct device *ipmdev) -{ - intel_adsp_ipc_complete(INTEL_ADSP_IPC_HOST_DEV); -} - -static int init(const struct device *dev) -{ - struct ipm_cavs_host_data *data = dev->data; - - const struct device *mw1 = DEVICE_DT_GET(DT_NODELABEL(mem_window1)); - - if (!device_is_ready(mw1)) { - return -ENODEV; - } - const struct mem_win_config *mw1_config = mw1->config; - /* Initialize hardware SRAM window. SOF will give the host 8k - * here, let's limit it to just the memory we're using for - * futureproofing. - */ - - sys_write32(ROUND_UP(MAX_MSG, 8) | 0x7, DMWLO(mw1_config->base_addr)); - sys_write32((mw1_config->mem_base | ADSP_DMWBA_ENABLE), DMWBA(mw1_config->base_addr)); - - intel_adsp_ipc_set_message_handler(INTEL_ADSP_IPC_HOST_DEV, ipc_handler, (void *)dev); - - data->enabled = true; - return 0; -} - -static DEVICE_API(ipm, api) = { - .send = send, - .max_data_size_get = max_data_size_get, - .max_id_val_get = max_id_val_get, - .register_callback = register_callback, - .set_enabled = set_enabled, - .complete = complete, -}; - -static struct ipm_cavs_host_data data; - -DEVICE_DEFINE(ipm_cavs_host, "ipm_cavs_host", init, NULL, &data, NULL, - PRE_KERNEL_2, 1, &api); diff --git a/include/zephyr/ipc/backends/ipc_msg_intel_adsp_ipc.h b/include/zephyr/ipc/backends/ipc_msg_intel_adsp_ipc.h new file mode 100644 index 0000000000000..61aeee4c6d8a5 --- /dev/null +++ b/include/zephyr/ipc/backends/ipc_msg_intel_adsp_ipc.h @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2022, 2025 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef ZEPHYR_INCLUDE_IPC_MSG_BACKEND_INTEL_ADSP_HOST_IPC_H +#define ZEPHYR_INCLUDE_IPC_MSG_BACKEND_INTEL_ADSP_HOST_IPC_H + +#include +#include +#include +#include + +#include + +/* Enum for custom IPC message types. */ +enum intel_adsp_ipc_msg_types { + /** + * IPC message type. + * + * Use with struct intel_adsp_ipc_msg. + */ + INTEL_ADSP_IPC_MSG = IPC_MSG_TYPE_CUSTOM_START, + + /** + * IPC message type for synchronous send. + * + * Use with struct intel_adsp_ipc_msg_sync. + */ + INTEL_ADSP_IPC_MSG_SYNC, + + /** + * IPC message type for emergency send. + * + * Use with struct intel_adsp_ipc_msg_emergency. + */ + INTEL_ADSP_IPC_MSG_EMERGENCY, + + /** + * DONE message to acknowlege message from host has been processed. + * + * No data is associated with this message type. + */ + INTEL_ADSP_IPC_MSG_DONE, +}; + +/* Enum for custom IPC event types. */ +enum intel_adsp_ipc_evt_types { + /** IPC message event type for IPC done. */ + INTEL_ADSP_IPC_EVT_DONE = IPC_MSG_EVT_CUSTOM_START, +}; + +/* Enum for return values for event callback. */ +enum intel_adsp_ipc_evt_returns { + /** Return by event callback if external completion is performed. */ + INTEL_ADSP_IPC_EVT_RET_EXT_COMPLETE = 1, +}; + +/* Enum for custom IPC query types. */ +enum intel_adsp_ipc_query_types { + /** + * Event type to query if host has completed processing the message. + * + * Returns 0 if completed, -EAGAIN if not. + */ + INTEL_ADSP_IPC_QUERY_IS_COMPLETE = IPC_MSG_QUERY_CUSTOM_START, +}; + +/** Struct for message type INTEL_ADSP_IPC_MSG. */ +struct intel_adsp_ipc_msg { + /** Data */ + uint32_t data; + + /** Extended data*/ + uint32_t extdata; +}; + +/** Struct for message type INTEL_ADSP_IPC_MSG_SYNC. */ +struct intel_adsp_ipc_msg_sync { + /** Data */ + uint32_t data; + + /** Extended data */ + uint32_t extdata; + + /** Timeout to wait for host to finish processing the message. */ + k_timeout_t timeout; +}; + +/** Struct for message type INTEL_ADSP_IPC_MSG_EMERGENCY. */ +struct intel_adsp_ipc_msg_emergency { + /** Data */ + uint32_t data; + + /** Extended data */ + uint32_t extdata; +}; + +#ifdef CONFIG_INTEL_ADSP_IPC_OLD_INTERFACE + +/** + * @brief Intel ADSP IPC Message Handler Callback. + * + * This function, once registered via intel_adsp_ipc_set_message_handler(), + * is invoked in interrupt context to service messages sent from the + * foreign/connected IPC context. The message contents of the TDR and + * TDD registers are provided in the data/ext_data argument. + * + * The function should return true if processing of the message is + * complete and return notification to the other side (via the TDA + * register) is desired immediately. Returning false means that no + * return "DONE" interrupt will occur until intel_adsp_ipc_complete() is + * called on this device at some point in the future. + * + * @note Further messages on the link will not be transmitted or + * received while an in-progress message remains incomplete! + * + * @param dev IPC device. + * @param arg Registered argument from intel_adsp_ipc_set_message_handler(). + * @param data Message data from other side (low bits of TDR register). + * @param ext_dat Extended message data (TDD register). + * @return true if the message is completely handled. + */ +typedef bool (*intel_adsp_ipc_handler_t)(const struct device *dev, void *arg, uint32_t data, + uint32_t ext_data); + +/** + * @brief Intel ADSP IPC Message Complete Callback. + * + * This function, once registered via intel_adsp_ipc_set_done_handler(), is + * invoked in interrupt context when a "DONE" return interrupt is + * received from the other side of the connection (indicating that a + * previously sent message is finished processing). + * + * @note On Intel ADSP hardware the DONE interrupt is transmitted + * synchronously with the interrupt being cleared on the remote + * device. It is not possible to delay processing. This callback + * will still occur, but protocols which rely on notification of + * asynchronous command processing will need modification. + * + * @param dev IPC device. + * @param arg Registered argument from intel_adsp_ipc_set_done_handler(). + * @return True if IPC completion will be done externally, otherwise false. + * @note Returning True will cause this API to skip writing IPC registers + * signalling IPC message completion and those actions should be done by + * external code manually. Returning false from the handler will perform + * writing to IPC registers signalling message completion normally by this API. + */ +typedef bool (*intel_adsp_ipc_done_t)(const struct device *dev, void *arg); + +#endif /* CONFIG_INTEL_ADSP_IPC_OLD_INTERFACE */ + +#ifdef CONFIG_PM_DEVICE +typedef int (*intel_adsp_ipc_resume_handler_t)(const struct device *dev, void *arg); + +typedef int (*intel_adsp_ipc_suspend_handler_t)(const struct device *dev, void *arg); +#endif /* CONFIG_PM_DEVICE */ + +/** + * Intel Audio DSP IPC message backend config struct + */ +struct intel_adsp_ipc_config { + /** Pointer to hardware register block. */ + volatile struct intel_adsp_ipc *regs; +}; + +/** + * Intel Audio DSP IPC message backend data struct + */ +struct intel_adsp_ipc_data { + /** Semaphore used to wait for remote acknowledgment of sent message. */ + struct k_sem sem; + + /** General driver lock. */ + struct k_spinlock lock; + + /** Pending TX acknowlegement. */ + bool tx_ack_pending; + + /** Pointer to endpoint configuration. */ + const struct ipc_msg_ept_cfg *ept_cfg; + +#ifdef CONFIG_INTEL_ADSP_IPC_OLD_INTERFACE + /** Callback for message handler. */ + intel_adsp_ipc_handler_t handle_message; + + /** Argument for message handler callback. */ + void *handler_arg; + + /** Callback for done notification. */ + intel_adsp_ipc_done_t done_notify; + + /** Argument for done notification callback. */ + void *done_arg; +#endif /* CONFIG_INTEL_ADSP_IPC_OLD_INTERFACE */ + +#ifdef CONFIG_PM_DEVICE + /** Pointer to resume handler. */ + intel_adsp_ipc_resume_handler_t resume_fn; + + /** Argument for resume handler. */ + void *resume_fn_args; + + /** Pointer to suspend handler. */ + intel_adsp_ipc_suspend_handler_t suspend_fn; + + /** Argument for suspend handler. */ + void *suspend_fn_args; +#endif /* CONFIG_PM_DEVICE */ +}; + +#ifdef CONFIG_PM_DEVICE + +/** + * @brief Registers resume callback handler used to resume Device from suspended state. + * + * @param dev IPC device. + * @param fn Callback function. + * @param arg Value to pass as the "arg" parameter to the function. + */ +void intel_adsp_ipc_set_resume_handler(const struct device *dev, intel_adsp_ipc_resume_handler_t fn, + void *arg); + +/** + * @brief Registers suspend callback handler used to suspend active Device. + * + * @param dev IPC device. + * @param fn Callback function. + * @param arg Value to pass as the "arg" parameter to the function. + */ +void intel_adsp_ipc_set_suspend_handler(const struct device *dev, + intel_adsp_ipc_suspend_handler_t fn, void *arg); + +#endif /* CONFIG_PM_DEVICE */ + +#endif /* ZEPHYR_INCLUDE_IPC_MSG_BACKEND_INTEL_ADSP_HOST_IPC_H */ diff --git a/include/zephyr/ipc/ipc_msg_evts.h b/include/zephyr/ipc/ipc_msg_evts.h new file mode 100644 index 0000000000000..ab32515eb6c32 --- /dev/null +++ b/include/zephyr/ipc/ipc_msg_evts.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2025 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_IPC_IPC_MSG_EVTS_H_ +#define ZEPHYR_INCLUDE_IPC_IPC_MSG_EVTS_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief IPC Message Service Event Types + * @defgroup ipc_msg_service_evt_types IPC Message Service Event Types + * @ingroup ipc_msg_service_api + * @{ + */ + +/** + * Enum for IPC message service event types. + */ +enum ipc_msg_evts { + /** + * Start event type number for common event types. + * + * @internal + * Starting at 1 to avoid having a type with value 0. + * @endinternal + */ + IPC_MSG_EVT_COMMON_START = 1, + + /** Remote has acknowleged the message. */ + IPC_MSG_EVT_REMOTE_ACK = IPC_MSG_EVT_COMMON_START, + + /** Remote has done processing the message. */ + IPC_MSG_EVT_REMOTE_DONE, + + /** + * @cond INTERNAL_HIDDEN + * + * Module specific event types are defined below. + * + * - When adding types for a new module, leave some spaces + * for the previous module to grow. A good practice would be + * to round up the last entry of previous module to the next + * hundreds and add another hundred. + * + * For example: + * IPC_MSG_EVT_MODULE_A_TYPE_LATEST = 5038, + * <... rounding up to next hundred and add 100 ...> + * IPC_MSG_EVT_MODULE_B_TYPE_NEW = 5200, + * + * - Removing old types should follow the deprecation process + * to avoid breaking any users of those types. + * + * @endcond + */ + + /** Starting number to define custom event types. */ + IPC_MSG_EVT_CUSTOM_START = 50000, + + /** Maximum number of event types. */ + IPC_MSG_EVT_NUM_MAX = 65536 +}; + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_IPC_IPC_MSG_EVTS_H_ */ diff --git a/include/zephyr/ipc/ipc_msg_queries.h b/include/zephyr/ipc/ipc_msg_queries.h new file mode 100644 index 0000000000000..f30723fa218e6 --- /dev/null +++ b/include/zephyr/ipc/ipc_msg_queries.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2025 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_IPC_IPC_MSG_QUERIES_H_ +#define ZEPHYR_INCLUDE_IPC_IPC_MSG_QUERIES_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief IPC Message Service Query Types + * @defgroup ipc_msg_service_query_types IPC Message Service Query Types + * @ingroup ipc_msg_service_api + * @{ + */ + +/** + * Enum for IPC message service query types. + */ +enum ipc_msg_queries { + /** + * Start event type number for common query types. + * + * @internal + * Starting at 1 to avoid having a type with value 0. + * @endinternal + */ + IPC_MSG_QUERY_COMMON_START = 1, + + /** + * Ask if the backend is ready. + * + * Returns 0 if backend is ready. Negative value if not. + */ + IPC_MSG_QUERY_IS_READY = IPC_MSG_QUERY_COMMON_START, + + /** + * @cond INTERNAL_HIDDEN + * + * Module specific query types are defined below. + * + * - When adding types for a new module, leave some spaces + * for the previous module to grow. A good practice would be + * to round up the last entry of previous module to the next + * hundreds and add another hundred. + * + * For example: + * IPC_MSG_QUERY_MODULE_A_TYPE_LATEST = 5038, + * <... rounding up to next hundred and add 100 ...> + * IPC_MSG_QUERY_MODULE_B_TYPE_NEW = 5200, + * + * - Removing old types should follow the deprecation process + * to avoid breaking any users of those types. + * + * @endcond + */ + + /** Starting number to define custom query types. */ + IPC_MSG_QUERY_CUSTOM_START = 50000, + + /** Maximum number of query types. */ + IPC_MSG_QUERY_NUM_MAX = 65536 +}; + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_IPC_IPC_MSG_QUERIES_H_ */ diff --git a/include/zephyr/ipc/ipc_msg_service.h b/include/zephyr/ipc/ipc_msg_service.h new file mode 100644 index 0000000000000..5dfdfaede4792 --- /dev/null +++ b/include/zephyr/ipc/ipc_msg_service.h @@ -0,0 +1,344 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * Copyright (c) 2025 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_IPC_IPC_MSG_SERVICE_H_ +#define ZEPHYR_INCLUDE_IPC_IPC_MSG_SERVICE_H_ + +#include + +#include +#include +#include + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief IPC Message Service API + * @defgroup ipc_msg_service_api IPC Message Service APIs + * @ingroup ipc + * @{ + */ + +/** + * @cond INTERNAL_HIDDEN + * + * These are for internal use only, so skip these in public documentation. + */ + +/** + * Some terminology: + * + * - INSTANCE: an instance is the external representation of a physical + * communication channel between two domains / CPUs. + * + * The actual implementation and internal representation of the + * instance is peculiar to each backend. For example for + * OpenAMP-based backends, an instance is usually represented by a + * shared memory region and a couple of IPM devices for RX/TX + * signalling. + * + * It's important to note that an instance per se is not used to + * send data between domains / CPUs. To send and receive data the + * user have to create (register) an endpoint in the instance + * connecting the two domains of interest. + * + * It's possible to have zero or multiple endpoints in one single + * instance, each one used to exchange data, possibly with different + * priorities. + * + * The creation of the instances is left to the backend (usually at + * init time), while the registration of the endpoints is left to + * the user (usually at run time). + * + * - ENDPOINT: an endpoint is the entity the user must use to send / receive + * data between two domains (connected by the instance). An + * endpoint is always associated to an instance. + * + * - BACKEND: the backend must take care of at least two different things: + * + * 1) creating the instances at init time + * 2) creating / registering the endpoints onto an instance at run + * time when requested by the user + * + * The API doesn't mandate a way for the backend to create the + * instances but itis strongly recommended to use the DT to retrieve + * the configuration parameters for the instance. + * + * Common API usage from the application prospective: + * + * HOST REMOTE + * ----------------------------------------------------------------------------- + * # Open the (same) instance on host and remote + * ipc_msg_service_open() ipc_msg_service_open() + * + * # Register the endpoints + * ipc_msg_service_register_endpoint() ipc_msg_service_register_endpoint() + * .bound() .bound() + * + * # After the .bound() callbacks are received the communication channel + * # is ready to be used + * + * # Start sending and receiving data + * ipc_msg_service_send() + * .receive() + * ipc_msg_service_send() + * .receive() + * + */ + +/** + * @endcond + */ + +/** + * @brief Event callback structure. + * + * It is registered during endpoint registration. + * This structure is part of the endpoint configuration. + */ +struct ipc_msg_service_cb { + /** + * @brief Bind was successful. + * + * This callback is called when the endpoint binding is successful. + * + * @param[in] priv Private data. + */ + void (*bound)(void *priv); + + /** + * @brief The endpoint unbound by the remote. + * + * This callback is called when the endpoint binding is removed. It may happen on + * different reasons, e.g. when the remote deregistered the endpoint, connection was + * lost, or remote CPU got reset. + * + * You may want to do some cleanup, resetting, e.t.c. and after that if you want to bound + * again, you can register the endpoint. When the remote becomes available again and it + * also registers the endpoint, the binding will be reestablished and the `bound()` + * callback will be called. + * + * @param[in] priv Private data. + */ + void (*unbound)(void *priv); + + /** + * @brief New packet arrived. + * + * This callback is called when new data is received. + * + * @note When this function returns, the message data (@a msg_data) is to be considered + * released and is no longer valid. + * + * @note This callback can execute in interrupt context. Use only interrupt-safe APIs. + * + * @param[in] msg_type Message type of the received message. + * @param[in] msg_data Pointer to message. + * @param[in] priv Private data. + * + * @retval 0 if received message is processed successfully. + * @retval other errno code if error. Positive values are backend specific. + */ + int (*received)(uint16_t msg_type, const void *msg_data, void *priv); + + /** + * @brief Event triggered. + * + * This callback is called when certain events are triggered. + * + * @note When this function returns, the event data (@a evt_data) is to be considered + * released and is no longer valid. + * + * @note This callback can execute in interrupt context. Use only interrupt-safe APIs. + * + * @param[in] evt_type Event type. + * @param[in] evt_data Pointer to event related data. + * @param[in] priv Private data. + * + * @retval 0 if event is acknowledged or processed successfully. + * @retval other errno code if error. Positive values are backend specific. + */ + int (*event)(uint16_t evt_type, const void *evt_data, void *priv); + + /** + * @brief An error occurred. + * + * @param[in] message Error message. + * @param[in] priv Private data. + */ + void (*error)(const char *message, void *priv); +}; + +/** + * @brief Endpoint instance. + */ +struct ipc_msg_ept { + + /** Instance this endpoint belongs to. */ + const struct device *instance; + + /** + * Backend-specific token used to identify an endpoint in an instance. + * + * @note Only used by specific backend and should not be used by user of the API. + */ + void *token; +}; + +/** + * @brief Endpoint configuration structure. + */ +struct ipc_msg_ept_cfg { + + /** Name of the endpoint. */ + const char *name; + + /** Endpoint priority. If the backend supports priorities. */ + int prio; + + /** Event callback structure. */ + struct ipc_msg_service_cb cb; + + /** Private data. */ + void *priv; +}; + +/** + * @brief Open an instance + * + * Function to be used to open an instance before being able to register a new + * endpoint on it. + * + * @param[in] instance Instance to open. + * + * @retval -EINVAL when instance configuration is invalid. + * @retval -EIO when no backend is registered. + * @retval -EALREADY when the instance is already opened (or being opened). + * + * @retval 0 on success or when not implemented on the backend (not needed). + * @retval other errno codes depending on the implementation of the backend. + */ +int ipc_msg_service_open_instance(const struct device *instance); + +/** + * @brief Close an instance + * + * Function to be used to close an instance. All bounded endpoints must be + * deregistered using ipc_service_deregister_endpoint before this + * is called. + * + * @param[in] instance Instance to close. + * + * @retval -EINVAL when instance configuration is invalid. + * @retval -EIO when no backend is registered. + * @retval -EALREADY when the instance is not already opened. + * @retval -EBUSY when an endpoint exists that hasn't been + * deregistered + * + * @retval 0 on success or when not implemented on the backend (not needed). + * @retval other errno codes depending on the implementation of the backend. + */ +int ipc_msg_service_close_instance(const struct device *instance); + +/** + * @brief Register IPC endpoint onto an instance. + * + * Registers IPC endpoint onto an instance to enable communication with a + * remote device. + * + * The same function registers endpoints for both host and remote devices. + * + * @param[in] instance Instance to register the endpoint onto. + * @param[in] ept Endpoint object. + * @param[in] cfg Endpoint configuration. + * + * @note Keep the variable pointed by @p cfg alive when endpoint is in use. + * + * @retval -EIO when no backend is registered. + * @retval -EINVAL when instance, endpoint or configuration is invalid. + * @retval -EBUSY when the instance is busy. + * + * @retval 0 on success. + * @retval other errno codes depending on the implementation of the backend. + */ +int ipc_msg_service_register_endpoint(const struct device *instance, struct ipc_msg_ept *ept, + const struct ipc_msg_ept_cfg *cfg); + +/** + * @brief Deregister an IPC endpoint from its instance. + * + * Deregisters an IPC endpoint from its instance. + * + * The same function deregisters endpoints for both host and remote devices. + * + * @param[in] ept Endpoint object. + * + * @retval -EIO when no backend is registered. + * @retval -EINVAL when instance, endpoint or configuration is invalid. + * @retval -ENOENT when the endpoint is not registered with the instance. + * @retval -EBUSY when the instance is busy. + * + * @retval 0 on success. + * @retval other errno codes depending on the implementation of the backend. + */ +int ipc_msg_service_deregister_endpoint(struct ipc_msg_ept *ept); + +/** + * @brief Send data using given IPC endpoint. + * + * @param[in] ept Registered endpoint by @ref ipc_service_register_endpoint. + * @param[in] msg_type Message type. + * @param[in] msg_data Pointer to the message to be sent. + * + * @retval -EIO when no backend is registered or send hook is missing from + * backend. + * @retval -EINVAL when instance or endpoint is invalid. + * @retval -ENOENT when the endpoint is not registered with the instance. + * @retval -EBADMSG when the message is invalid. + * @retval -EBUSY when the instance is busy. + * @retval -ENOTSUP when message type is not supported. + * + * @retval 0 if message is sent. + * @retval other errno codes depending on the implementation of the backend. + */ +int ipc_msg_service_send(struct ipc_msg_ept *ept, uint16_t msg_type, const void *msg_data); + +/** + * @brief Query given IPC endpoint. + * + * @param[in] ept Registered endpoint by @ref ipc_service_register_endpoint. + * @param[in] query_type Query type. + * @param[in] query_data Pointer to the query data. + * @param[out] query_response Pointer to the query response. + * + * @retval -EIO when no backend is registered or query hook is missing from + * backend. + * @retval -EINVAL when instance or endpoint is invalid. + * @retval -ENOENT when the endpoint is not registered with the instance. + * @retval -ENOTSUP when query type is not supported. + * + * @retval other Depends on query type. Negative values are errors. + * Positive values are query specific. + */ +int ipc_msg_service_query(struct ipc_msg_ept *ept, uint16_t query_type, const void *query_data, + void *query_response); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_IPC_IPC_MSG_SERVICE_H_ */ diff --git a/include/zephyr/ipc/ipc_msg_service_backend.h b/include/zephyr/ipc/ipc_msg_service_backend.h new file mode 100644 index 0000000000000..4d8f0ef35b5f1 --- /dev/null +++ b/include/zephyr/ipc/ipc_msg_service_backend.h @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * Copyright (c) 2025 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_IPC_MSG_SERVICE_IPC_MSG_SERVICE_BACKEND_H_ +#define ZEPHYR_INCLUDE_IPC_MSG_SERVICE_IPC_MSG_SERVICE_BACKEND_H_ + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief IPC Message Service Backend + * @defgroup ipc_msg_service_backend IPC Message Service Backend + * @ingroup ipc_msg_service_api + * @{ + */ + +/** + * @brief IPC message service backend configuration structure. + * + * This structure is used for configuration backend during registration. + */ +struct ipc_msg_service_backend { + /** + * @brief Pointer to the function that will be used to open an instance + * + * @param[in] instance Instance pointer. + * + * @retval -EALREADY when the instance is already opened. + * + * @retval 0 on success + * @retval other errno codes depending on the implementation of the backend. + */ + int (*open_instance)(const struct device *instance); + + /** + * @brief Pointer to the function that will be used to close an instance + * + * @param[in] instance Instance pointer. + * + * @retval -EALREADY when the instance is not already inited. + * + * @retval 0 on success + * @retval other errno codes depending on the implementation of the backend. + */ + int (*close_instance)(const struct device *instance); + + /** + * @brief Pointer to the function that will be used to query backend status. + * + * @param[in] instance Instance pointer. + * @param[in] token Backend-specific token. + * @param[in] query_type Query type. + * @param[in] query_data Pointer to the query data. + * @param[out] query_response Pointer to the query response. + * + * @retval int Depends on query type. Negative values are errors. + * Positive values are query specific. + */ + int (*query)(const struct device *instance, void *token, uint16_t query_type, + const void *query_data, void *query_response); + + /** + * @brief Pointer to the function that will be used to send data to the endpoint. + * + * @param[in] instance Instance pointer. + * @param[in] token Backend-specific token. + * @param[in] msg_type Message type. + * @param[in] msg_data Pointer to the message to be sent. + * + * @retval -EINVAL when instance is invalid. + * @retval -ENOENT when the endpoint is not registered with the instance. + * @retval -EBADMSG when the message is invalid. + * @retval -EBUSY when the instance is busy or not ready. + * + * @retval 0 if message is sent. + * @retval other errno codes depending on the implementation of the backend. + */ + int (*send)(const struct device *instance, void *token, uint16_t msg_type, + const void *msg_data); + + /** + * @brief Pointer to the function that will be used to register endpoints. + * + * @param[in] instance Instance to register the endpoint onto. + * @param[out] token Backend-specific token. + * @param[in] cfg Endpoint configuration. + * + * @retval -EINVAL when the endpoint configuration or instance is invalid. + * @retval -EBUSY when the instance is busy or not ready. + * + * @retval 0 on success + * @retval other errno codes depending on the implementation of the backend. + */ + int (*register_endpoint)(const struct device *instance, void **token, + const struct ipc_msg_ept_cfg *cfg); + + /** + * @brief Pointer to the function that will be used to deregister endpoints. + * + * @param[in] instance Instance from which to deregister the endpoint. + * @param[in] token Backend-specific token. + * + * @retval -EINVAL when the endpoint configuration or instance is invalid. + * @retval -ENOENT when the endpoint is not registered with the instance. + * @retval -EBUSY when the instance is busy or not ready. + * + * @retval 0 on success + * @retval other errno codes depending on the implementation of the backend. + */ + int (*deregister_endpoint)(const struct device *instance, void *token); +}; + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_IPC_MSG_SERVICE_IPC_MSG_SERVICE_BACKEND_H_ */ diff --git a/include/zephyr/ipc/ipc_msg_types.h b/include/zephyr/ipc/ipc_msg_types.h new file mode 100644 index 0000000000000..660d284baceab --- /dev/null +++ b/include/zephyr/ipc/ipc_msg_types.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2025 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_IPC_IPC_MSG_TYPES_H_ +#define ZEPHYR_INCLUDE_IPC_IPC_MSG_TYPES_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief IPC Message Service Message Types + * @defgroup ipc_msg_service_msg_types IPC Message Service Message Types + * @ingroup ipc_msg_service_api + * @{ + */ + +/** + * Enum for IPC message types. + */ +enum ipc_msg_types { + /** + * Start message type number for common message types. + * + * @internal + * Starting at 1 to avoid having a type with value 0. + * @endinternal + */ + IPC_MSG_TYPE_COMMON_START = 1, + + /** IPC message type with only a uint32_t command. */ + IPC_MSG_TYPE_CMD = IPC_MSG_TYPE_COMMON_START, + + /** Starting number to module specific IPC message types. */ + IPC_MSG_TYPE_MODULE_START = 1000, + + /** + * @cond INTERNAL_HIDDEN + * + * Module specific IPC message types are defined below. + * + * - When adding types for a new module, leave some spaces + * for the previous module to grow. A good practice would be + * to round up the last entry of previous module to the next + * hundreds and add another hundred. + * + * For example: + * IPC_MSG_TYPE_MODULE_A_TYPE_LATEST = 5038, + * <... rounding up to next hundred and add 100 ...> + * IPC_MSG_TYPE_MODULE_B_TYPE_NEW = 5200, + * + * - Removing old types should follow the deprecation process + * to avoid breaking any users of those types. + * + * @endcond + */ + + /** Starting number to define custom IPC message types. */ + IPC_MSG_TYPE_CUSTOM_START = 50000, + + /** Maximum number of IPC message types. */ + IPC_MSG_TYPE_NUM_MAX = 65536 +}; + +/** + * IPC_MSG_TYPE_CMD struct + */ +struct ipc_msg_type_cmd { + /** IPC command. */ + uint32_t cmd; +}; + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_IPC_IPC_MSG_TYPES_H_ */ diff --git a/soc/intel/intel_adsp/Kconfig b/soc/intel/intel_adsp/Kconfig index 27a8e838b67be..a65236283520d 100644 --- a/soc/intel/intel_adsp/Kconfig +++ b/soc/intel/intel_adsp/Kconfig @@ -11,6 +11,7 @@ config SOC_FAMILY_INTEL_ADSP select ARCH_HAS_USERSPACE if XTENSA_MMU imply XTENSA_MMU_DOUBLE_MAP select CPU_CACHE_INCOHERENT + select IPC_MSG_SERVICE if DT_HAS_INTEL_ADSP_HOST_IPC_ENABLED if SOC_FAMILY_INTEL_ADSP @@ -32,16 +33,20 @@ config INTEL_ADSP_SIM_NO_SECONDARY_CORE_FLOW endif # INTEL_ADSP_SIM -DT_COMPAT_INTEL_ADSP_HOST_IPC := intel,adsp-host-ipc -DT_COMPAT_INTEL_ADSP_IDC := intel,adsp-idc - config INTEL_ADSP_IPC - bool "Driver for the host IPC interrupt delivery" - default $(dt_compat_enabled,$(DT_COMPAT_INTEL_ADSP_HOST_IPC)) if !SOF - default $(dt_compat_enabled,$(DT_COMPAT_INTEL_ADSP_IDC)) if !SOF + bool "Driver for the host IPC interrupt delivery (Deprecated)" + select DEPRECATED + help + Deprecated config for IPC. Will be removed in the future. + +config INTEL_ADSP_IPC_OLD_INTERFACE + bool "Expose old interface for the IPC" + depends on IPC_MSG_BACKEND_INTEL_ADSP_IPC + default y + select INTEL_ADSP_IPC help - Driver for the host IPC interrupt delivery mechanism. - Currently SOF has its own driver for this hardware. + Expose the old IPC interface (intel_adsp_ipc_* functions) to + maintain backward compatibility. config MEMORY_WIN_0_SIZE int "Size of memory window 0" diff --git a/soc/intel/intel_adsp/common/CMakeLists.txt b/soc/intel/intel_adsp/common/CMakeLists.txt index a39e9ea98e774..9e49bd26eabec 100644 --- a/soc/intel/intel_adsp/common/CMakeLists.txt +++ b/soc/intel/intel_adsp/common/CMakeLists.txt @@ -9,7 +9,7 @@ zephyr_library_named(intel_adsp_common) zephyr_include_directories(include) zephyr_library_include_directories(${ZEPHYR_BASE}/drivers) -zephyr_library_sources_ifdef(CONFIG_INTEL_ADSP_IPC ipc.c) +zephyr_library_sources_ifdef(CONFIG_INTEL_ADSP_IPC_OLD_INTERFACE ipc.c) zephyr_library_sources_ifdef(CONFIG_GDBSTUB gdbstub_backend_sram.c diff --git a/soc/intel/intel_adsp/common/include/intel_adsp_ipc.h b/soc/intel/intel_adsp/common/include/intel_adsp_ipc.h index 1f283466a7350..dc6365202f5e2 100644 --- a/soc/intel/intel_adsp/common/include/intel_adsp_ipc.h +++ b/soc/intel/intel_adsp/common/include/intel_adsp_ipc.h @@ -9,69 +9,9 @@ #include #include -struct intel_adsp_ipc_config { - volatile struct intel_adsp_ipc *regs; -}; +#include -/** - * @brief Intel ADSP IPC Message Handler Callback. - * - * This function, once registered via intel_adsp_ipc_set_message_handler(), - * is invoked in interrupt context to service messages sent from the - * foreign/connected IPC context. The message contents of the TDR and - * TDD registers are provided in the data/ext_data argument. - * - * The function should return true if processing of the message is - * complete and return notification to the other side (via the TDA - * register) is desired immediately. Returning false means that no - * return "DONE" interrupt will occur until intel_adsp_ipc_complete() is - * called on this device at some point in the future. - * - * @note Further messages on the link will not be transmitted or - * received while an in-progress message remains incomplete! - * - * @param dev IPC device. - * @param arg Registered argument from intel_adsp_ipc_set_message_handler(). - * @param data Message data from other side (low bits of TDR register). - * @param ext_dat Extended message data (TDD register). - * @return true if the message is completely handled. - */ -typedef bool (*intel_adsp_ipc_handler_t)(const struct device *dev, void *arg, - uint32_t data, uint32_t ext_data); - -/** - * @brief Intel ADSP IPC Message Complete Callback. - * - * This function, once registered via intel_adsp_ipc_set_done_handler(), is - * invoked in interrupt context when a "DONE" return interrupt is - * received from the other side of the connection (indicating that a - * previously sent message is finished processing). - * - * @note On Intel ADSP hardware the DONE interrupt is transmitted - * synchronously with the interrupt being cleared on the remote - * device. It is not possible to delay processing. This callback - * will still occur, but protocols which rely on notification of - * asynchronous command processing will need modification. - * - * @param dev IPC device. - * @param arg Registered argument from intel_adsp_ipc_set_done_handler(). - * @return True if IPC completion will be done externally, otherwise false. - * @note Returning True will cause this API to skip writing IPC registers - * signalling IPC message completion and those actions should be done by - * external code manually. Returning false from the handler will perform - * writing to IPC registers signalling message completion normally by this API. - */ -typedef bool (*intel_adsp_ipc_done_t)(const struct device *dev, void *arg); - -struct intel_adsp_ipc_data { - struct k_sem sem; - struct k_spinlock lock; - intel_adsp_ipc_handler_t handle_message; - void *handler_arg; - intel_adsp_ipc_done_t done_notify; - void *done_arg; - bool tx_ack_pending; -}; +#if defined(CONFIG_INTEL_ADSP_IPC_OLD_INTERFACE) || defined(__DOXYGEN__) /** * @brief Register message callback handler. @@ -98,34 +38,27 @@ void intel_adsp_ipc_set_message_handler(const struct device *dev, void intel_adsp_ipc_set_done_handler(const struct device *dev, intel_adsp_ipc_done_t fn, void *arg); -/** @brief Initialize Intel ADSP IPC device. - * - * Initialize the device. Must be called before any API calls or - * interrupts can be serviced. - * - * @param dev IPC device. - * @return Zero on success, negative codes for error. - */ -int intel_adsp_ipc_init(const struct device *dev); - -/** @brief Complete an in-progress message. +/** + * @brief Complete an in-progress message. * * Notify the other side that the current in-progress message is * complete. This is a noop if no message is in progress. * * @note Further messages on the link will not be transmitted or - * received while an in-progress message remains incomplete! + * received while an in-progress message remains incomplete! * * @param dev IPC device. */ void intel_adsp_ipc_complete(const struct device *dev); -/** @brief Message-in-progress predicate. +/** + * @brief Message-in-progress predicate. * * Returns false if a message has been received but not yet completed * via intel_adsp_ipc_complete(), true otherwise. * * @param dev IPC device. + * * @return True if no message is in progress. */ bool intel_adsp_ipc_is_complete(const struct device *dev); @@ -175,38 +108,6 @@ int intel_adsp_ipc_send_message_sync(const struct device *dev, void intel_adsp_ipc_send_message_emergency(const struct device *dev, uint32_t data, uint32_t ext_data); -#ifdef CONFIG_PM_DEVICE - -typedef int (*intel_adsp_ipc_resume_handler_t)(const struct device *dev, void *arg); - -typedef int (*intel_adsp_ipc_suspend_handler_t)(const struct device *dev, void *arg); - -/** - * @brief Registers resume callback handler used to resume Device from suspended state. - * - * @param dev IPC device. - * @param fn Callback function. - * @param arg Value to pass as the "arg" parameter to the function. - */ -void intel_adsp_ipc_set_resume_handler(const struct device *dev, - intel_adsp_ipc_resume_handler_t fn, void *arg); - -/** - * @brief Registers suspend callback handler used to suspend active Device. - * - * @param dev IPC device. - * @param fn Callback function. - * @param arg Value to pass as the "arg" parameter to the function. - */ -void intel_adsp_ipc_set_suspend_handler(const struct device *dev, - intel_adsp_ipc_suspend_handler_t fn, void *arg); - -struct ipc_control_driver_api { - intel_adsp_ipc_resume_handler_t resume_fn; - void *resume_fn_args; - intel_adsp_ipc_suspend_handler_t suspend_fn; - void *suspend_fn_args; -}; +#endif /* CONFIG_INTEL_ADSP_IPC_OLD_INTERFACE */ -#endif /* CONFIG_PM_DEVICE */ #endif /* ZEPHYR_INCLUDE_INTEL_ADSP_IPC_H */ diff --git a/soc/intel/intel_adsp/common/ipc.c b/soc/intel/intel_adsp/common/ipc.c index bceee1b7c2a29..af2986a337d5f 100644 --- a/soc/intel/intel_adsp/common/ipc.c +++ b/soc/intel/intel_adsp/common/ipc.c @@ -1,20 +1,22 @@ -/* Copyright (c) 2022 Intel Corporation +/* + * Copyright (c) 2022, 2025 Intel Corporation + * * SPDX-License-Identifier: Apache-2.0 */ - #include -#include -#include -#include -#include -#include -#include -#include -#include #include -void intel_adsp_ipc_set_message_handler(const struct device *dev, - intel_adsp_ipc_handler_t fn, void *arg) +#include +#include + +#include +#include + +static struct ipc_msg_ept intel_adsp_ipc_ept; +static struct ipc_msg_ept_cfg intel_adsp_ipc_ept_cfg; + +void intel_adsp_ipc_set_message_handler(const struct device *dev, intel_adsp_ipc_handler_t fn, + void *arg) { struct intel_adsp_ipc_data *devdata = dev->data; k_spinlock_key_t key = k_spin_lock(&devdata->lock); @@ -24,8 +26,7 @@ void intel_adsp_ipc_set_message_handler(const struct device *dev, k_spin_unlock(&devdata->lock, key); } -void intel_adsp_ipc_set_done_handler(const struct device *dev, - intel_adsp_ipc_done_t fn, void *arg) +void intel_adsp_ipc_set_done_handler(const struct device *dev, intel_adsp_ipc_done_t fn, void *arg) { struct intel_adsp_ipc_data *devdata = dev->data; k_spinlock_key_t key = k_spin_lock(&devdata->lock); @@ -35,314 +36,121 @@ void intel_adsp_ipc_set_done_handler(const struct device *dev, k_spin_unlock(&devdata->lock, key); } -static void intel_adsp_ipc_isr(const void *devarg) +static int intel_adsp_ipc_receive_cb(uint16_t msg_type, const void *msg_data, void *priv) { - const struct device *dev = devarg; - const struct intel_adsp_ipc_config *config = dev->config; + const struct device *dev = INTEL_ADSP_IPC_HOST_DEV; struct intel_adsp_ipc_data *devdata = dev->data; + const struct intel_adsp_ipc_msg *msg = (const struct intel_adsp_ipc_msg *)msg_data; + bool done = true; - volatile struct intel_adsp_ipc *regs = config->regs; - k_spinlock_key_t key = k_spin_lock(&devdata->lock); - - if (regs->tdr & INTEL_ADSP_IPC_BUSY) { - bool done = true; - - if (devdata->handle_message != NULL) { - uint32_t msg = regs->tdr & ~INTEL_ADSP_IPC_BUSY; - uint32_t ext = regs->tdd; - - done = devdata->handle_message(dev, devdata->handler_arg, msg, ext); - } - - regs->tdr = INTEL_ADSP_IPC_BUSY; - if (done) { -#ifdef CONFIG_SOC_SERIES_INTEL_ADSP_ACE - regs->tda = INTEL_ADSP_IPC_ACE1X_TDA_DONE; -#else - regs->tda = INTEL_ADSP_IPC_DONE; -#endif - } + if (msg_type != INTEL_ADSP_IPC_MSG) { + return -EBADMSG; } - /* Same signal, but on different bits in 1.5 */ - bool done = (regs->ida & INTEL_ADSP_IPC_DONE); + if (devdata->handle_message != NULL) { + done = devdata->handle_message(dev, devdata->handler_arg, msg->data, msg->extdata); + } if (done) { - bool external_completion = false; - - if (devdata->done_notify != NULL) { - external_completion = devdata->done_notify(dev, devdata->done_arg); - } - devdata->tx_ack_pending = false; - /* Allow the system to enter the runtime idle state after the IPC acknowledgment - * is received. - */ - pm_policy_state_lock_put(PM_STATE_RUNTIME_IDLE, PM_ALL_SUBSTATES); - k_sem_give(&devdata->sem); - - /* IPC completion registers will be set externally */ - if (external_completion) { - k_spin_unlock(&devdata->lock, key); - return; - } - - regs->ida = INTEL_ADSP_IPC_DONE; + return 0; + } else { + return -EBADMSG; } - - k_spin_unlock(&devdata->lock, key); } -int intel_adsp_ipc_init(const struct device *dev) +static int intel_adsp_ipc_event_cb(uint16_t evt_type, const void *evt_data, void *priv) { - pm_device_busy_set(dev); + const struct device *dev = INTEL_ADSP_IPC_HOST_DEV; struct intel_adsp_ipc_data *devdata = dev->data; - const struct intel_adsp_ipc_config *config = dev->config; - - memset(devdata, 0, sizeof(*devdata)); - - k_sem_init(&devdata->sem, 0, 1); - - /* ACK any latched interrupts (including TDA to clear IDA on - * the other side!), then enable. - */ - config->regs->tdr = INTEL_ADSP_IPC_BUSY; - config->regs->ida = INTEL_ADSP_IPC_DONE; -#ifdef CONFIG_SOC_SERIES_INTEL_ADSP_ACE - config->regs->tda = INTEL_ADSP_IPC_ACE1X_TDA_DONE; -#else - config->regs->tda = INTEL_ADSP_IPC_DONE; -#endif - config->regs->ctl |= (INTEL_ADSP_IPC_CTL_IDIE | INTEL_ADSP_IPC_CTL_TBIE); - pm_device_busy_clear(dev); - - return 0; -} + bool external_completion = false; -void intel_adsp_ipc_complete(const struct device *dev) -{ - const struct intel_adsp_ipc_config *config = dev->config; - -#ifdef CONFIG_SOC_SERIES_INTEL_ADSP_ACE - config->regs->tda = INTEL_ADSP_IPC_ACE1X_TDA_DONE; -#else - config->regs->tda = INTEL_ADSP_IPC_DONE; -#endif -} + ARG_UNUSED(evt_data); -bool intel_adsp_ipc_is_complete(const struct device *dev) -{ - const struct intel_adsp_ipc_config *config = dev->config; - const struct intel_adsp_ipc_data *devdata = dev->data; - bool not_busy = (config->regs->idr & INTEL_ADSP_IPC_BUSY) == 0; - - return not_busy && !devdata->tx_ack_pending; -} - -int intel_adsp_ipc_send_message(const struct device *dev, - uint32_t data, uint32_t ext_data) -{ -#ifdef CONFIG_PM_DEVICE - enum pm_device_state current_state; - - if (pm_device_state_get(INTEL_ADSP_IPC_HOST_DEV, ¤t_state) != 0 || - current_state != PM_DEVICE_STATE_ACTIVE) { - return -ESHUTDOWN; + if (evt_type != INTEL_ADSP_IPC_EVT_DONE) { + return -EINVAL; } -#endif - - pm_device_busy_set(dev); - const struct intel_adsp_ipc_config *config = dev->config; - struct intel_adsp_ipc_data *devdata = dev->data; - k_spinlock_key_t key = k_spin_lock(&devdata->lock); - if ((config->regs->idr & INTEL_ADSP_IPC_BUSY) != 0 || devdata->tx_ack_pending) { - k_spin_unlock(&devdata->lock, key); - return -EBUSY; + if (devdata->done_notify != NULL) { + external_completion = devdata->done_notify(dev, devdata->done_arg); } - k_sem_reset(&devdata->sem); - /* Prevent entering runtime idle state until IPC acknowledgment is received. */ - pm_policy_state_lock_get(PM_STATE_RUNTIME_IDLE, PM_ALL_SUBSTATES); - devdata->tx_ack_pending = true; - config->regs->idd = ext_data; - config->regs->idr = data | INTEL_ADSP_IPC_BUSY; - k_spin_unlock(&devdata->lock, key); - pm_device_busy_clear(dev); - return 0; -} - -int intel_adsp_ipc_send_message_sync(const struct device *dev, - uint32_t data, uint32_t ext_data, - k_timeout_t timeout) -{ - struct intel_adsp_ipc_data *devdata = dev->data; - - int ret = intel_adsp_ipc_send_message(dev, data, ext_data); - - if (!ret) { - k_sem_take(&devdata->sem, timeout); + if (external_completion) { + return INTEL_ADSP_IPC_EVT_RET_EXT_COMPLETE; + } else { + return 0; } - return ret; } -void intel_adsp_ipc_send_message_emergency(const struct device *dev, uint32_t data, - uint32_t ext_data) +void intel_adsp_ipc_complete(const struct device *dev) { - const struct intel_adsp_ipc_config * const config = dev->config; - - volatile struct intel_adsp_ipc * const regs = config->regs; - bool done; - - /* check if host is processing message. */ - while (regs->idr & INTEL_ADSP_IPC_BUSY) { - k_busy_wait(1); - } + int ret; - /* check if host has pending acknowledge msg - * Same signal, but on different bits in 1.5 - */ - done = regs->ida & INTEL_ADSP_IPC_DONE; - if (done) { - /* IPC completion */ - regs->ida = INTEL_ADSP_IPC_DONE; - } + ret = ipc_msg_service_send(&intel_adsp_ipc_ept, INTEL_ADSP_IPC_MSG_DONE, NULL); - regs->idd = ext_data; - regs->idr = data | INTEL_ADSP_IPC_BUSY; + ARG_UNUSED(ret); } -#if DT_NODE_EXISTS(INTEL_ADSP_IPC_HOST_DTNODE) - -#if defined(CONFIG_SOC_SERIES_INTEL_ADSP_ACE) -static inline void ace_ipc_intc_unmask(void) -{ - ACE_DINT[0].ie[ACE_INTL_HIPC] = BIT(0); -} -#else -static inline void ace_ipc_intc_unmask(void) {} -#endif - -static int dt_init(const struct device *dev) +bool intel_adsp_ipc_is_complete(const struct device *dev) { - IRQ_CONNECT(DT_IRQN(INTEL_ADSP_IPC_HOST_DTNODE), 0, intel_adsp_ipc_isr, - INTEL_ADSP_IPC_HOST_DEV, 0); - irq_enable(DT_IRQN(INTEL_ADSP_IPC_HOST_DTNODE)); + int ret; - ace_ipc_intc_unmask(); + ret = ipc_msg_service_query(&intel_adsp_ipc_ept, INTEL_ADSP_IPC_QUERY_IS_COMPLETE, NULL, + NULL); - return intel_adsp_ipc_init(dev); + return ret == 0; } -#ifdef CONFIG_PM_DEVICE - -void intel_adsp_ipc_set_resume_handler(const struct device *dev, - intel_adsp_ipc_resume_handler_t fn, void *arg) +int intel_adsp_ipc_send_message(const struct device *dev, uint32_t data, uint32_t ext_data) { - struct ipc_control_driver_api *api = - (struct ipc_control_driver_api *)dev->api; - struct intel_adsp_ipc_data *devdata = dev->data; - k_spinlock_key_t key = k_spin_lock(&devdata->lock); + struct intel_adsp_ipc_msg msg = {.data = data, .extdata = ext_data}; + int ret; - api->resume_fn = fn; - api->resume_fn_args = arg; + ret = ipc_msg_service_send(&intel_adsp_ipc_ept, INTEL_ADSP_IPC_MSG, &msg); - k_spin_unlock(&devdata->lock, key); + return ret; } -void intel_adsp_ipc_set_suspend_handler(const struct device *dev, - intel_adsp_ipc_suspend_handler_t fn, void *arg) +int intel_adsp_ipc_send_message_sync(const struct device *dev, uint32_t data, uint32_t ext_data, + k_timeout_t timeout) { - struct ipc_control_driver_api *api = - (struct ipc_control_driver_api *)dev->api; - struct intel_adsp_ipc_data *devdata = dev->data; - k_spinlock_key_t key = k_spin_lock(&devdata->lock); + struct intel_adsp_ipc_msg_sync msg = { + .data = data, .extdata = ext_data, .timeout = timeout}; + int ret; - api->suspend_fn = fn; - api->suspend_fn_args = arg; + ret = ipc_msg_service_send(&intel_adsp_ipc_ept, INTEL_ADSP_IPC_MSG_SYNC, &msg); - k_spin_unlock(&devdata->lock, key); + return ret; } -/** - * @brief Manages IPC driver power state change. - * - * @param dev IPC device. - * @param action Power state to be changed to. - * @return int Returns 0 on success or optionaly error code from the - * registered ipc_power_control_api callbacks. - * - * @note PM lock is taken at the start of each power transition to prevent concurrent calls - * to @ref pm_device_action_run function. - * If IPC Device performs hardware operation and device is busy (what should not happen) - * function returns failure. It is API user responsibility to make sure we are not entering - * device power transition while device is busy. - */ -static int ipc_pm_action(const struct device *dev, enum pm_device_action action) +void intel_adsp_ipc_send_message_emergency(const struct device *dev, uint32_t data, + uint32_t ext_data) { - if (pm_device_is_busy(INTEL_ADSP_IPC_HOST_DEV)) { - return -EBUSY; - } + struct intel_adsp_ipc_msg_emergency msg = {.data = data, .extdata = ext_data}; + int ret; - const struct ipc_control_driver_api *api = - (const struct ipc_control_driver_api *)dev->api; - - int ret = 0; - - switch (action) { - case PM_DEVICE_ACTION_SUSPEND: - if (api->suspend_fn) { - ret = api->suspend_fn(dev, api->suspend_fn_args); - if (!ret) { - irq_disable(DT_IRQN(INTEL_ADSP_IPC_HOST_DTNODE)); - } - } - break; - case PM_DEVICE_ACTION_RESUME: - irq_enable(DT_IRQN(INTEL_ADSP_IPC_HOST_DTNODE)); - if (!irq_is_enabled(DT_IRQN(INTEL_ADSP_IPC_HOST_DTNODE))) { - ret = -EINTR; - break; - } - ace_ipc_intc_unmask(); - ret = intel_adsp_ipc_init(dev); - if (ret) { - break; - } - if (api->resume_fn) { - ret = api->resume_fn(dev, api->resume_fn_args); - } - break; - default: - /* Return as default value when given PM action is not supported */ - return -ENOTSUP; - } + ret = ipc_msg_service_send(&intel_adsp_ipc_ept, INTEL_ADSP_IPC_MSG_EMERGENCY, &msg); - return ret; + ARG_UNUSED(ret); } -/** - * @brief Callback functions to be executed by Zephyr application - * during IPC device suspend and resume. - */ -static struct ipc_control_driver_api ipc_power_control_api = { - .resume_fn = NULL, - .resume_fn_args = NULL, - .suspend_fn = NULL, - .suspend_fn_args = NULL +static struct ipc_msg_ept_cfg intel_adsp_ipc_ept_cfg = { + .name = "intel_adsp_ipc_ept", + .cb = { + .received = intel_adsp_ipc_receive_cb, + .event = intel_adsp_ipc_event_cb, + }, }; -PM_DEVICE_DT_DEFINE(INTEL_ADSP_IPC_HOST_DTNODE, ipc_pm_action); - -#endif /* CONFIG_PM_DEVICE */ - -static const struct intel_adsp_ipc_config ipc_host_config = { - .regs = (void *)INTEL_ADSP_IPC_REG_ADDRESS, -}; +static int intel_adsp_ipc_old_init(void) +{ + int ret; + const struct device *ipc_dev = INTEL_ADSP_IPC_HOST_DEV; -static struct intel_adsp_ipc_data ipc_host_data; + ret = ipc_msg_service_register_endpoint(ipc_dev, &intel_adsp_ipc_ept, + &intel_adsp_ipc_ept_cfg); -DEVICE_DT_DEFINE(INTEL_ADSP_IPC_HOST_DTNODE, dt_init, PM_DEVICE_DT_GET(INTEL_ADSP_IPC_HOST_DTNODE), - &ipc_host_data, &ipc_host_config, PRE_KERNEL_2, 0, COND_CODE_1(CONFIG_PM_DEVICE, - (&ipc_power_control_api), (NULL))); + return ret; +} -#endif /* DT_NODE_EXISTS(INTEL_ADSP_IPC_HOST_DTNODE) */ +/* Backend is at PRE_KERNEL_2:0, so we need to init after that. */ +SYS_INIT(intel_adsp_ipc_old_init, PRE_KERNEL_2, 1); diff --git a/subsys/ipc/CMakeLists.txt b/subsys/ipc/CMakeLists.txt index d7df0d0b2dd62..8b9f37c9e0a5c 100644 --- a/subsys/ipc/CMakeLists.txt +++ b/subsys/ipc/CMakeLists.txt @@ -2,3 +2,4 @@ add_subdirectory_ifdef(CONFIG_RPMSG_SERVICE rpmsg_service) add_subdirectory_ifdef(CONFIG_IPC_SERVICE ipc_service) +add_subdirectory_ifdef(CONFIG_IPC_MSG_SERVICE ipc_msg_service) diff --git a/subsys/ipc/Kconfig b/subsys/ipc/Kconfig index f8e2445d4cf39..9afa41f010d0a 100644 --- a/subsys/ipc/Kconfig +++ b/subsys/ipc/Kconfig @@ -7,5 +7,6 @@ menu "Inter Processor Communication" source "subsys/ipc/rpmsg_service/Kconfig" source "subsys/ipc/ipc_service/Kconfig" +source "subsys/ipc/ipc_msg_service/Kconfig" endmenu diff --git a/subsys/ipc/ipc_msg_service/CMakeLists.txt b/subsys/ipc/ipc_msg_service/CMakeLists.txt new file mode 100644 index 0000000000000..bde951ac611da --- /dev/null +++ b/subsys/ipc/ipc_msg_service/CMakeLists.txt @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +zephyr_library_sources(ipc_msg_service.c) + +add_subdirectory(backends) diff --git a/subsys/ipc/ipc_msg_service/Kconfig b/subsys/ipc/ipc_msg_service/Kconfig new file mode 100644 index 0000000000000..c8296842f475b --- /dev/null +++ b/subsys/ipc/ipc_msg_service/Kconfig @@ -0,0 +1,25 @@ +# +# Copyright (c) 2021 Nordic Semiconductor (ASA) +# Copyright (c) 2025 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 +# + +menuconfig IPC_MSG_SERVICE + bool "IPC message service support multiple backends [EXPERIMENTAL]" + select EXPERIMENTAL + help + Enables support for a service that can be shared by multiple + users. Ability to work in different backends. The backend + should be registered before application starts using + the IPC Message Service. + +if IPC_MSG_SERVICE + +rsource "backends/Kconfig" + +module = IPC_MSG_SERVICE +module-str = IPC message service and backend +source "subsys/logging/Kconfig.template.log_config" + +endif # IPC_MSG_SERVICE diff --git a/subsys/ipc/ipc_msg_service/backends/CMakeLists.txt b/subsys/ipc/ipc_msg_service/backends/CMakeLists.txt new file mode 100644 index 0000000000000..a1f01c13fc270 --- /dev/null +++ b/subsys/ipc/ipc_msg_service/backends/CMakeLists.txt @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library_sources_ifdef(CONFIG_IPC_MSG_BACKEND_INTEL_ADSP_IPC ipc_msg_intel_adsp_ipc.c) diff --git a/subsys/ipc/ipc_msg_service/backends/Kconfig b/subsys/ipc/ipc_msg_service/backends/Kconfig new file mode 100644 index 0000000000000..e9dcd15d742f6 --- /dev/null +++ b/subsys/ipc/ipc_msg_service/backends/Kconfig @@ -0,0 +1,14 @@ +# +# Copyright (c) 2021 Nordic Semiconductor (ASA) +# Copyright (c) 2025 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 +# + +config IPC_MSG_SERVICE_REG_BACKEND_PRIORITY + int "Initialization priority of modules registering IPC message service backend" + default 46 + help + The backend must be registered before the endpoint register. + +rsource "Kconfig.intel_adsp" diff --git a/subsys/ipc/ipc_msg_service/backends/Kconfig.intel_adsp b/subsys/ipc/ipc_msg_service/backends/Kconfig.intel_adsp new file mode 100644 index 0000000000000..91895b988ceec --- /dev/null +++ b/subsys/ipc/ipc_msg_service/backends/Kconfig.intel_adsp @@ -0,0 +1,13 @@ +# +# Copyright (c) 2025 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 +# + +config IPC_MSG_BACKEND_INTEL_ADSP_IPC + bool "Backend for the Intel Audio DSP IPC" + default DT_HAS_INTEL_ADSP_HOST_IPC_ENABLED + help + Backend for Intel Audio DSP IPC. + + Note that this is currently not used SOF as its own driver for this hardware. diff --git a/subsys/ipc/ipc_msg_service/backends/ipc_msg_intel_adsp_ipc.c b/subsys/ipc/ipc_msg_service/backends/ipc_msg_intel_adsp_ipc.c new file mode 100644 index 0000000000000..6b3e68d45ef05 --- /dev/null +++ b/subsys/ipc/ipc_msg_service/backends/ipc_msg_intel_adsp_ipc.c @@ -0,0 +1,442 @@ +/* + * Copyright (c) 2022, 2025 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT intel_adsp_host_ipc + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +static inline void ace_ipc_intc_mask(void) +{ +#if defined(CONFIG_SOC_SERIES_INTEL_ADSP_ACE) + ACE_DINT[0].ie[ACE_INTL_HIPC] = ACE_DINT[0].ie[ACE_INTL_HIPC] & ~BIT(0); +#endif +} + +static inline void ace_ipc_intc_unmask(void) +{ +#if defined(CONFIG_SOC_SERIES_INTEL_ADSP_ACE) + ACE_DINT[0].ie[ACE_INTL_HIPC] = BIT(0); +#endif +} + +static void intel_adsp_ipc_isr(const void *devarg) +{ + const struct device *dev = devarg; + const struct intel_adsp_ipc_config *config = dev->config; + struct intel_adsp_ipc_data *devdata = dev->data; + const struct ipc_msg_ept_cfg *ept_cfg = devdata->ept_cfg; + + volatile struct intel_adsp_ipc *regs = config->regs; + k_spinlock_key_t key = k_spin_lock(&devdata->lock); + + if (regs->tdr & INTEL_ADSP_IPC_BUSY) { + bool done = true; + + if (ept_cfg->cb.received != NULL) { + struct intel_adsp_ipc_msg cb_msg = { + .data = regs->tdr & ~INTEL_ADSP_IPC_BUSY, + .extdata = regs->tdd, + }; + + int ret = ept_cfg->cb.received(INTEL_ADSP_IPC_MSG, &cb_msg, ept_cfg->priv); + + done = (ret == 0); + } + + regs->tdr = INTEL_ADSP_IPC_BUSY; + if (done) { +#ifdef CONFIG_SOC_SERIES_INTEL_ADSP_ACE + regs->tda = INTEL_ADSP_IPC_ACE1X_TDA_DONE; +#else + regs->tda = INTEL_ADSP_IPC_DONE; +#endif + } + } + + /* Same signal, but on different bits in 1.5 */ + bool done = (regs->ida & INTEL_ADSP_IPC_DONE); + + if (done) { + bool external_completion = false; + + if (ept_cfg->cb.event != NULL) { + int ret = ept_cfg->cb.event(INTEL_ADSP_IPC_EVT_DONE, NULL, ept_cfg->priv); + + if (ret == INTEL_ADSP_IPC_EVT_RET_EXT_COMPLETE) { + external_completion = true; + } + } + + devdata->tx_ack_pending = false; + /* Allow the system to enter the runtime idle state after the IPC acknowledgment + * is received. + */ + pm_policy_state_lock_put(PM_STATE_RUNTIME_IDLE, PM_ALL_SUBSTATES); + k_sem_give(&devdata->sem); + + /* IPC completion registers will be set externally */ + if (external_completion) { + k_spin_unlock(&devdata->lock, key); + return; + } + + regs->ida = INTEL_ADSP_IPC_DONE; + } + + k_spin_unlock(&devdata->lock, key); +} + +int intel_adsp_ipc_init(const struct device *dev) +{ + pm_device_busy_set(dev); + struct intel_adsp_ipc_data *devdata = dev->data; + const struct intel_adsp_ipc_config *config = dev->config; + + k_sem_init(&devdata->sem, 0, 1); + + /* ACK any latched interrupts (including TDA to clear IDA on + * the other side!), then enable. + */ + config->regs->tdr = INTEL_ADSP_IPC_BUSY; + config->regs->ida = INTEL_ADSP_IPC_DONE; +#ifdef CONFIG_SOC_SERIES_INTEL_ADSP_ACE + config->regs->tda = INTEL_ADSP_IPC_ACE1X_TDA_DONE; +#else + config->regs->tda = INTEL_ADSP_IPC_DONE; +#endif + config->regs->ctl |= (INTEL_ADSP_IPC_CTL_IDIE | INTEL_ADSP_IPC_CTL_TBIE); + pm_device_busy_clear(dev); + + return 0; +} + +static int intel_adsp_ipc_register_ept(const struct device *instance, void **token, + const struct ipc_msg_ept_cfg *cfg) +{ + struct intel_adsp_ipc_data *data = instance->data; + + data->ept_cfg = cfg; + + irq_enable(DT_IRQN(INTEL_ADSP_IPC_HOST_DTNODE)); + ace_ipc_intc_unmask(); + + return 0; +} + +static int intel_adsp_ipc_deregister_ept(const struct device *instance, void *token) +{ + struct intel_adsp_ipc_data *data = instance->data; + + data->ept_cfg = NULL; + + ace_ipc_intc_mask(); + irq_disable(DT_IRQN(INTEL_ADSP_IPC_HOST_DTNODE)); + + return 0; +} + +static void ipc_complete(const struct device *dev) +{ + const struct intel_adsp_ipc_config *config = dev->config; + +#ifdef CONFIG_SOC_SERIES_INTEL_ADSP_ACE + config->regs->tda = INTEL_ADSP_IPC_ACE1X_TDA_DONE; +#else + config->regs->tda = INTEL_ADSP_IPC_DONE; +#endif +} + +static bool ipc_is_complete(const struct device *dev) +{ + const struct intel_adsp_ipc_config *config = dev->config; + const struct intel_adsp_ipc_data *devdata = dev->data; + bool not_busy = (config->regs->idr & INTEL_ADSP_IPC_BUSY) == 0; + + return not_busy && !devdata->tx_ack_pending; +} + +static int ipc_send_message(const struct device *dev, uint32_t data, uint32_t ext_data) +{ +#ifdef CONFIG_PM_DEVICE + enum pm_device_state current_state; + + if (pm_device_state_get(INTEL_ADSP_IPC_HOST_DEV, ¤t_state) != 0 || + current_state != PM_DEVICE_STATE_ACTIVE) { + return -ESHUTDOWN; + } +#endif + + pm_device_busy_set(dev); + const struct intel_adsp_ipc_config *config = dev->config; + struct intel_adsp_ipc_data *devdata = dev->data; + k_spinlock_key_t key = k_spin_lock(&devdata->lock); + + if ((config->regs->idr & INTEL_ADSP_IPC_BUSY) != 0 || devdata->tx_ack_pending) { + k_spin_unlock(&devdata->lock, key); + return -EBUSY; + } + + k_sem_reset(&devdata->sem); + + /* Prevent entering runtime idle state until IPC acknowledgment is received. */ + pm_policy_state_lock_get(PM_STATE_RUNTIME_IDLE, PM_ALL_SUBSTATES); + + devdata->tx_ack_pending = true; + + config->regs->idd = ext_data; + config->regs->idr = data | INTEL_ADSP_IPC_BUSY; + + k_spin_unlock(&devdata->lock, key); + + pm_device_busy_clear(dev); + + return 0; +} + +static int ipc_send_message_sync(const struct device *dev, uint32_t data, uint32_t ext_data, + k_timeout_t timeout) +{ + struct intel_adsp_ipc_data *devdata = dev->data; + + int ret = ipc_send_message(dev, data, ext_data); + + if (!ret) { + k_sem_take(&devdata->sem, timeout); + } + + return ret; +} + +static int ipc_send_message_emergency(const struct device *dev, uint32_t data, uint32_t ext_data) +{ + const struct intel_adsp_ipc_config *const config = dev->config; + + volatile struct intel_adsp_ipc *const regs = config->regs; + bool done; + + /* check if host is processing message. */ + while (regs->idr & INTEL_ADSP_IPC_BUSY) { + k_busy_wait(1); + } + + /* check if host has pending acknowledge msg + * Same signal, but on different bits in 1.5 + */ + done = regs->ida & INTEL_ADSP_IPC_DONE; + if (done) { + /* IPC completion */ + regs->ida = INTEL_ADSP_IPC_DONE; + } + + regs->idd = ext_data; + regs->idr = data | INTEL_ADSP_IPC_BUSY; + + return 0; +} + +static int intel_adsp_ipc_send(const struct device *dev, void *token, uint16_t msg_type, + const void *data) +{ + int ret; + + switch (msg_type) { + case INTEL_ADSP_IPC_MSG: { + const struct intel_adsp_ipc_msg *msg = (const struct intel_adsp_ipc_msg *)data; + + ret = ipc_send_message(dev, msg->data, msg->extdata); + + break; + } + case INTEL_ADSP_IPC_MSG_SYNC: { + const struct intel_adsp_ipc_msg_sync *msg = + (const struct intel_adsp_ipc_msg_sync *)data; + + ret = ipc_send_message_sync(dev, msg->data, msg->extdata, msg->timeout); + + break; + } + case INTEL_ADSP_IPC_MSG_EMERGENCY: { + const struct intel_adsp_ipc_msg_emergency *msg = + (const struct intel_adsp_ipc_msg_emergency *)data; + + ret = ipc_send_message_emergency(dev, msg->data, msg->extdata); + + break; + } + case INTEL_ADSP_IPC_MSG_DONE: + ipc_complete(dev); + + ret = 0; + + break; + default: + ret = -EBADMSG; + + break; + } + + return ret; +} + +static int intel_adsp_ipc_query(const struct device *dev, void *token, uint16_t query_type, + const void *query_data, void *query_response) +{ + int ret; + + ARG_UNUSED(query_data); + ARG_UNUSED(query_response); + + switch (query_type) { + case INTEL_ADSP_IPC_QUERY_IS_COMPLETE: { + bool completed = ipc_is_complete(dev); + + if (completed) { + ret = 0; + } else { + ret = -EAGAIN; + } + + break; + } + default: + ret = -ENOTSUP; + + break; + } + + return ret; +} + +static int intel_adsp_ipc_dt_init(const struct device *dev) +{ + struct intel_adsp_ipc_data *devdata = dev->data; + + memset(devdata, 0, sizeof(*devdata)); + + IRQ_CONNECT(DT_IRQN(INTEL_ADSP_IPC_HOST_DTNODE), 0, intel_adsp_ipc_isr, + INTEL_ADSP_IPC_HOST_DEV, 0); + + return intel_adsp_ipc_init(dev); +} + +#ifdef CONFIG_PM_DEVICE + +void intel_adsp_ipc_set_resume_handler(const struct device *dev, intel_adsp_ipc_resume_handler_t fn, + void *arg) +{ + struct intel_adsp_ipc_data *devdata = dev->data; + k_spinlock_key_t key = k_spin_lock(&devdata->lock); + + devdata->resume_fn = fn; + devdata->resume_fn_args = arg; + + k_spin_unlock(&devdata->lock, key); +} + +void intel_adsp_ipc_set_suspend_handler(const struct device *dev, + intel_adsp_ipc_suspend_handler_t fn, void *arg) +{ + struct intel_adsp_ipc_data *devdata = dev->data; + k_spinlock_key_t key = k_spin_lock(&devdata->lock); + + devdata->suspend_fn = fn; + devdata->suspend_fn_args = arg; + + k_spin_unlock(&devdata->lock, key); +} + +/** + * @brief Manages IPC driver power state change. + * + * @param dev IPC device. + * @param action Power state to be changed to. + * + * @return int Returns 0 on success or optionaly error code from the + * registered ipc_power_control_api callbacks. + * + * @note PM lock is taken at the start of each power transition to prevent concurrent calls + * to @ref pm_device_action_run function. + * If IPC Device performs hardware operation and device is busy (what should not happen) + * function returns failure. It is API user responsibility to make sure we are not entering + * device power transition while device is busy. + */ +static int ipc_pm_action(const struct device *dev, enum pm_device_action action) +{ + if (pm_device_is_busy(INTEL_ADSP_IPC_HOST_DEV)) { + return -EBUSY; + } + + struct intel_adsp_ipc_data *devdata = dev->data; + + int ret = 0; + + switch (action) { + case PM_DEVICE_ACTION_SUSPEND: + if (devdata->suspend_fn) { + ret = devdata->suspend_fn(dev, devdata->suspend_fn_args); + if (!ret) { + irq_disable(DT_IRQN(INTEL_ADSP_IPC_HOST_DTNODE)); + } + } + break; + case PM_DEVICE_ACTION_RESUME: + irq_enable(DT_IRQN(INTEL_ADSP_IPC_HOST_DTNODE)); + if (!irq_is_enabled(DT_IRQN(INTEL_ADSP_IPC_HOST_DTNODE))) { + ret = -EINTR; + break; + } + ace_ipc_intc_unmask(); + ret = intel_adsp_ipc_init(dev); + if (ret) { + break; + } + if (devdata->resume_fn) { + ret = devdata->resume_fn(dev, devdata->resume_fn_args); + } + break; + default: + /* Return as default value when given PM action is not supported */ + return -ENOTSUP; + } + + return ret; +} + +PM_DEVICE_DT_DEFINE(INTEL_ADSP_IPC_HOST_DTNODE, ipc_pm_action); + +#endif /* CONFIG_PM_DEVICE */ + +static const struct intel_adsp_ipc_config ipc_host_config = { + .regs = (void *)INTEL_ADSP_IPC_REG_ADDRESS, +}; + +static struct intel_adsp_ipc_data ipc_host_data; + +const static struct ipc_msg_service_backend intel_adsp_ipc_backend_api = { + .query = intel_adsp_ipc_query, + .send = intel_adsp_ipc_send, + .register_endpoint = intel_adsp_ipc_register_ept, + .deregister_endpoint = intel_adsp_ipc_deregister_ept, +}; + +DEVICE_DT_DEFINE(INTEL_ADSP_IPC_HOST_DTNODE, intel_adsp_ipc_dt_init, + PM_DEVICE_DT_GET(INTEL_ADSP_IPC_HOST_DTNODE), &ipc_host_data, &ipc_host_config, + PRE_KERNEL_2, 0, &intel_adsp_ipc_backend_api); diff --git a/subsys/ipc/ipc_msg_service/ipc_msg_service.c b/subsys/ipc/ipc_msg_service/ipc_msg_service.c new file mode 100644 index 0000000000000..8722a914d5dc7 --- /dev/null +++ b/subsys/ipc/ipc_msg_service/ipc_msg_service.c @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * Copyright (c) 2021 Carlo Caione + * Copyright (c) 2025 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +#include +#include + +LOG_MODULE_REGISTER(ipc_msg_service, CONFIG_IPC_MSG_SERVICE_LOG_LEVEL); + +int ipc_msg_service_open_instance(const struct device *instance) +{ + const struct ipc_msg_service_backend *backend; + + if (!instance) { + LOG_ERR("Invalid instance"); + return -EINVAL; + } + + backend = (const struct ipc_msg_service_backend *)instance->api; + + if (!backend) { + LOG_ERR("Invalid backend configuration"); + return -EIO; + } + + if (!backend->open_instance) { + /* maybe not needed on backend */ + return 0; + } + + return backend->open_instance(instance); +} + +int ipc_msg_service_close_instance(const struct device *instance) +{ + const struct ipc_msg_service_backend *backend; + + if (!instance) { + LOG_ERR("Invalid instance"); + return -EINVAL; + } + + backend = (const struct ipc_msg_service_backend *)instance->api; + + if (!backend) { + LOG_ERR("Invalid backend configuration"); + return -EIO; + } + + if (!backend->close_instance) { + /* maybe not needed on backend */ + return 0; + } + + return backend->close_instance(instance); +} + +int ipc_msg_service_register_endpoint(const struct device *instance, struct ipc_msg_ept *ept, + const struct ipc_msg_ept_cfg *cfg) +{ + const struct ipc_msg_service_backend *backend; + + if (!instance || !ept || !cfg) { + LOG_ERR("Invalid instance, endpoint or configuration"); + return -EINVAL; + } + + backend = (const struct ipc_msg_service_backend *)instance->api; + + if (!backend || !backend->register_endpoint) { + LOG_ERR("Invalid backend configuration"); + return -EIO; + } + + LOG_DBG("Register endpoint %s", cfg->name ? cfg->name : ""); + + ept->instance = instance; + + return backend->register_endpoint(instance, &ept->token, cfg); +} + +int ipc_msg_service_deregister_endpoint(struct ipc_msg_ept *ept) +{ + const struct ipc_msg_service_backend *backend; + int err; + + if (!ept) { + LOG_ERR("Invalid endpoint"); + return -EINVAL; + } + + if (!ept->instance) { + LOG_ERR("Endpoint not registered\n"); + return -ENOENT; + } + + backend = ept->instance->api; + + if (!backend || !backend->deregister_endpoint) { + LOG_ERR("Invalid backend configuration"); + return -EIO; + } + + err = backend->deregister_endpoint(ept->instance, ept->token); + if (err != 0) { + return err; + } + + ept->instance = 0; + + return 0; +} + +int ipc_msg_service_send(struct ipc_msg_ept *ept, uint16_t msg_type, const void *msg_data) +{ + const struct ipc_msg_service_backend *backend; + + if (!ept) { + LOG_ERR("Invalid endpoint"); + return -EINVAL; + } + + if (!ept->instance) { + LOG_ERR("Endpoint not registered\n"); + return -ENOENT; + } + + backend = ept->instance->api; + + if (!backend || !backend->send) { + LOG_ERR("Invalid backend configuration"); + return -EIO; + } + + return backend->send(ept->instance, ept->token, msg_type, msg_data); +} + +int ipc_msg_service_query(struct ipc_msg_ept *ept, uint16_t query_type, const void *query_data, + void *query_response) +{ + const struct ipc_msg_service_backend *backend; + + if (!ept) { + return -EINVAL; + } + + if (!ept->instance) { + return -ENOENT; + } + + backend = ept->instance->api; + + if (!backend || !backend->query) { + return -EIO; + } + + return backend->query(ept->instance, ept->token, query_type, query_data, query_response); +} diff --git a/tests/boards/intel_adsp/smoke/CMakeLists.txt b/tests/boards/intel_adsp/smoke/CMakeLists.txt index 2d994416b5925..5fb768241239d 100644 --- a/tests/boards/intel_adsp/smoke/CMakeLists.txt +++ b/tests/boards/intel_adsp/smoke/CMakeLists.txt @@ -4,4 +4,4 @@ cmake_minimum_required(VERSION 3.20.0) find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) project(intel_adsp) -target_sources(app PRIVATE src/main.c src/smpboot.c src/hostipc.c src/cpus.c src/ipm.c) +target_sources(app PRIVATE src/main.c src/smpboot.c src/hostipc.c src/cpus.c src/clock.c) diff --git a/tests/boards/intel_adsp/smoke/prj.conf b/tests/boards/intel_adsp/smoke/prj.conf index a30e22b8c55d4..30b23341a23d5 100644 --- a/tests/boards/intel_adsp/smoke/prj.conf +++ b/tests/boards/intel_adsp/smoke/prj.conf @@ -2,5 +2,3 @@ CONFIG_ZTEST=y CONFIG_SMP=y CONFIG_SMP_BOOT_DELAY=y CONFIG_SCHED_CPU_MASK=y -CONFIG_IPM=y -CONFIG_IPM_CAVS_HOST=y diff --git a/tests/boards/intel_adsp/smoke/src/clock.c b/tests/boards/intel_adsp/smoke/src/clock.c new file mode 100644 index 0000000000000..c16f2fcc8ecb5 --- /dev/null +++ b/tests/boards/intel_adsp/smoke/src/clock.c @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2022, 2025 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "tests.h" + +static volatile uint32_t host_dt; + +#ifdef CONFIG_INTEL_ADSP_IPC_OLD_INTERFACE + +static bool clock_msg(const struct device *dev, void *arg, uint32_t data, uint32_t ext_data) +{ + *(uint32_t *)arg = data; + return true; +} + +#else /* CONFIG_INTEL_ADSP_IPC_OLD_INTERFACE */ + +extern struct ipc_msg_ept ipc_ept; + +int clock_ipc_receive_cb(uint16_t msg_type, const void *msg_data, void *priv) +{ + zassert_true(msg_type == INTEL_ADSP_IPC_MSG, "unexpected msg type"); + + const struct intel_adsp_ipc_msg *msg = (const struct intel_adsp_ipc_msg *)msg_data; + + *(uint32_t *)priv = msg->data; + + return 0; +} + +struct ipc_msg_ept_cfg clock_ipc_ept_cfg = { + .name = "host_ipc_ept", + .cb = { + .received = clock_ipc_receive_cb, + }, + .priv = (void *)&host_dt, +}; + +extern int intel_adsp_ipc_send_message(const struct device *dev, uint32_t data, uint32_t ext_data); + +#endif /* CONFIG_INTEL_ADSP_IPC_OLD_INTERFACE */ + +ZTEST(intel_adsp, test_clock_calibrate) +{ + uint32_t cyc0, cyc1, hz, diff; + +#ifndef CONFIG_INTEL_ADSP_IPC_OLD_INTERFACE + int ret; + + ret = ipc_msg_service_register_endpoint(INTEL_ADSP_IPC_HOST_DEV, &ipc_ept, + &clock_ipc_ept_cfg); + zassert_equal(ret, 0, "cannot register IPC endpoint"); +#endif /* CONFIG_INTEL_ADSP_IPC_OLD_INTERFACE */ + + /* Prime the host script's timestamp */ + cyc0 = k_cycle_get_32(); + intel_adsp_ipc_send_message(INTEL_ADSP_IPC_HOST_DEV, IPCCMD_TIMESTAMP, 0); + + k_msleep(1000); + host_dt = 0; +#ifdef CONFIG_INTEL_ADSP_IPC_OLD_INTERFACE + intel_adsp_ipc_set_message_handler(INTEL_ADSP_IPC_HOST_DEV, clock_msg, (void *)&host_dt); +#endif /* CONFIG_INTEL_ADSP_IPC_OLD_INTERFACE */ + + /* Now do it again, but with a handler to catch the result */ + cyc1 = k_cycle_get_32(); + intel_adsp_ipc_send_message(INTEL_ADSP_IPC_HOST_DEV, IPCCMD_TIMESTAMP, 0); + AWAIT(host_dt != 0); +#ifdef CONFIG_INTEL_ADSP_IPC_OLD_INTERFACE + intel_adsp_ipc_set_message_handler(INTEL_ADSP_IPC_HOST_DEV, NULL, NULL); +#else + ret = ipc_msg_service_deregister_endpoint(&ipc_ept); + zassert_equal(ret, 0, "cannot de-register IPC endpoint"); +#endif + + hz = 1000000ULL * (cyc1 - cyc0) / host_dt; + printk("CLOCK: %lld Hz\n", (1000000ULL * (cyc1 - cyc0)) / host_dt); + + /* Make sure we're within 1% of spec */ + diff = abs((int32_t)(hz - CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC)); + zassert_true((hz / MIN(1, diff)) > 100, "clock rate wrong"); +} diff --git a/tests/boards/intel_adsp/smoke/src/cpus.c b/tests/boards/intel_adsp/smoke/src/cpus.c index afdc460faf185..5d006150b77da 100644 --- a/tests/boards/intel_adsp/smoke/src/cpus.c +++ b/tests/boards/intel_adsp/smoke/src/cpus.c @@ -1,6 +1,9 @@ -/* Copyright (c) 2022 Intel Corporation +/* + * Copyright (c) 2022, 2025 Intel Corporation + * * SPDX-License-Identifier: Apache-2.0 */ + #include #include #include @@ -120,7 +123,7 @@ static void core_smoke(void *arg) clk_ratios[cpu] / 1000, clk_ratios[cpu] % 1000); for (int i = 0; i < cpu; i++) { - int32_t diff = MAX(1, abs(clk_ratios[i] - clk_ratios[cpu])); + int32_t diff = MAX(1, abs((int32_t)(clk_ratios[i] - clk_ratios[cpu]))); zassert_true((clk_ratios[cpu] / diff) > 100, "clocks off by more than 1%%"); @@ -135,7 +138,7 @@ static void core_smoke(void *arg) cyc1 = ccount(); dt = cyc1 - cyc0; insns = count0 * 2; - zassert_true((dt / insns) < 3, + zassert_true((dt / insns) < 3.5, "instruction rate too slow, icache disabled?"); printk(" CPI = %d.%2.2d\n", dt / insns, ((1000 * dt) / insns) % 1000); } @@ -159,17 +162,8 @@ static void halt_and_restart(int cpu) { printk("halt/restart core %d...\n", cpu); static bool alive_flag; - uint32_t all_cpus = BIT(arch_num_cpus()) - 1; int ret; - /* On older hardware we need to get the host to turn the core - * off. Construct an ADSPCS with only this core disabled - */ - if (!IS_ENABLED(CONFIG_SOC_INTEL_CAVS_V25)) { - intel_adsp_ipc_send_message(INTEL_ADSP_IPC_HOST_DEV, IPCCMD_ADSPCS, - (all_cpus & ~BIT(cpu)) << 16); - } - ret = soc_adsp_halt_cpu(cpu); zassert_ok(ret, "Couldn't halt CPU"); @@ -178,17 +172,6 @@ static void halt_and_restart(int cpu) k_msleep(100); zassert_false(alive_flag, "cpu didn't halt"); - if (!IS_ENABLED(CONFIG_SOC_INTEL_CAVS_V25)) { - /* Likewise need to ask the host to turn it back on, - * and give it some time to spin up before we hit it. - * We don't have a return message wired to be notified - * of completion. - */ - intel_adsp_ipc_send_message(INTEL_ADSP_IPC_HOST_DEV, IPCCMD_ADSPCS, - all_cpus << 16); - k_msleep(50); - } - k_smp_cpu_start(cpu, NULL, NULL); /* Startup can be slow */ @@ -212,6 +195,10 @@ ZTEST(intel_adsp_boot, test_2nd_cpu_halt) { int ret; + if (IS_ENABLED(CONFIG_SOC_SERIES_INTEL_ADSP_ACE)) { + ztest_test_skip(); + } + /* Obviously this only works on CPU0. So, we create a thread pinned * to CPU0 to effectively run the test. */ diff --git a/tests/boards/intel_adsp/smoke/src/hostipc.c b/tests/boards/intel_adsp/smoke/src/hostipc.c index cb71af352cfd1..7304c411582ce 100644 --- a/tests/boards/intel_adsp/smoke/src/hostipc.c +++ b/tests/boards/intel_adsp/smoke/src/hostipc.c @@ -1,6 +1,9 @@ -/* Copyright (c) 2022 Intel Corporation +/* + * Copyright (c) 2022, 2025 Intel Corporation + * * SPDX-License-Identifier: Apache-2.0 */ + #include #include #include @@ -11,6 +14,7 @@ static volatile bool done_flag, msg_flag; #define RETURN_MSG_SYNC_VAL 0x12345 #define RETURN_MSG_ASYNC_VAL 0x54321 +#ifdef CONFIG_INTEL_ADSP_IPC_OLD_INTERFACE static bool ipc_message(const struct device *dev, void *arg, uint32_t data, uint32_t ext_data) { @@ -29,13 +33,99 @@ static bool ipc_done(const struct device *dev, void *arg) done_flag = true; return false; } +#else /* CONFIG_INTEL_ADSP_IPC_OLD_INTERFACE */ + +#include +#include + +struct ipc_msg_ept ipc_ept; + +int ipc_receive_cb(uint16_t msg_type, const void *msg_data, void *priv) +{ + zassert_true(msg_type == INTEL_ADSP_IPC_MSG || + msg_type == INTEL_ADSP_IPC_MSG_SYNC, "unexpected msg type"); + + const struct intel_adsp_ipc_msg *msg = (const struct intel_adsp_ipc_msg *)msg_data; + + zassert_equal(msg->data, msg->extdata, "unequal message data/ext_data"); + zassert_true(msg->data == RETURN_MSG_SYNC_VAL || + msg->data == RETURN_MSG_ASYNC_VAL, "unexpected msg data"); + + msg_flag = true; + + return 0; +} + +int ipc_event_cb(uint16_t evt_type, const void *evt_data, void *priv) +{ + zassert_false(done_flag, "done called unexpectedly"); + + done_flag = true; + + return 0; +} + +struct ipc_msg_ept_cfg host_ipc_ept_cfg = { + .name = "host_ipc_ept", + .cb = { + .received = ipc_receive_cb, + .event = ipc_event_cb, + }, +}; + +static void intel_adsp_ipc_complete(const struct device *dev) +{ + int ret; + + ret = ipc_msg_service_send(&ipc_ept, INTEL_ADSP_IPC_MSG_DONE, NULL); + + ARG_UNUSED(ret); +} + +static bool intel_adsp_ipc_is_complete(const struct device *dev) +{ + int ret; + + ret = ipc_msg_service_query(&ipc_ept, INTEL_ADSP_IPC_QUERY_IS_COMPLETE, NULL, NULL); + + return ret == 0; +} + +int intel_adsp_ipc_send_message(const struct device *dev, uint32_t data, uint32_t ext_data) +{ + struct intel_adsp_ipc_msg msg = {.data = data, .extdata = ext_data}; + int ret; + + ret = ipc_msg_service_send(&ipc_ept, INTEL_ADSP_IPC_MSG, &msg); + + return ret; +} + +static int intel_adsp_ipc_send_message_sync(const struct device *dev, uint32_t data, + uint32_t ext_data, k_timeout_t timeout) +{ + struct intel_adsp_ipc_msg_sync msg = { + .data = data, .extdata = ext_data, .timeout = timeout}; + int ret; + + ret = ipc_msg_service_send(&ipc_ept, INTEL_ADSP_IPC_MSG_SYNC, &msg); + + return ret; +} +#endif /* CONFIG_INTEL_ADSP_IPC_OLD_INTERFACE */ ZTEST(intel_adsp, test_host_ipc) { int ret; +#ifdef CONFIG_INTEL_ADSP_IPC_OLD_INTERFACE intel_adsp_ipc_set_message_handler(INTEL_ADSP_IPC_HOST_DEV, ipc_message, NULL); intel_adsp_ipc_set_done_handler(INTEL_ADSP_IPC_HOST_DEV, ipc_done, NULL); +#else /* CONFIG_INTEL_ADSP_IPC_OLD_INTERFACE */ + ret = ipc_msg_service_register_endpoint(INTEL_ADSP_IPC_HOST_DEV, &ipc_ept, + &host_ipc_ept_cfg); + zassert_equal(ret, 0, "cannot register IPC endpoint"); +#endif /* CONFIG_INTEL_ADSP_IPC_OLD_INTERFACE */ /* Just send a message and wait for it to complete */ printk("Simple message send...\n"); @@ -97,7 +187,12 @@ ZTEST(intel_adsp, test_host_ipc) zassert_true(intel_adsp_ipc_is_complete(INTEL_ADSP_IPC_HOST_DEV), "sync message incomplete"); +#ifdef CONFIG_INTEL_ADSP_IPC_OLD_INTERFACE /* Clean up. Further tests might want to use IPC */ intel_adsp_ipc_set_message_handler(INTEL_ADSP_IPC_HOST_DEV, NULL, NULL); intel_adsp_ipc_set_done_handler(INTEL_ADSP_IPC_HOST_DEV, NULL, NULL); +#else /* CONFIG_INTEL_ADSP_IPC_OLD_INTERFACE */ + ret = ipc_msg_service_deregister_endpoint(&ipc_ept); + zassert_equal(ret, 0, "cannot de-register IPC endpoint"); +#endif /* CONFIG_INTEL_ADSP_IPC_OLD_INTERFACE */ } diff --git a/tests/boards/intel_adsp/smoke/src/ipm.c b/tests/boards/intel_adsp/smoke/src/ipm.c deleted file mode 100644 index f16cab35fedbc..0000000000000 --- a/tests/boards/intel_adsp/smoke/src/ipm.c +++ /dev/null @@ -1,88 +0,0 @@ -/* Copyright (c) 2022 Intel Corporation - * SPDX-License-Identifier: Apache-2.0 - */ -#include -#include -#include "tests.h" - -#define IPM_DEV device_get_binding("ipm_cavs_host") - -/* Two values impossible to transmit in a cAVS ID */ -#define ID_INBOUND 0xfffffff0 -#define ID_INVALID 0xffffffff - -static K_SEM_DEFINE(ipm_sem, 0, 1); - -static const uint32_t msg[] = { 29, 15, 58, 71, 99 }; - -static uint32_t received_id = ID_INVALID; -static volatile uint32_t *received_data; - -static void ipm_msg(const struct device *ipmdev, void *user_data, - uint32_t id, volatile void *data) -{ - zassert_equal(ipmdev, IPM_DEV, "wrong device"); - zassert_equal(user_data, NULL, "wrong user_data pointer"); - zassert_equal(received_id, ID_INBOUND, "unexpected message"); - - received_id = id; - received_data = data; - - k_sem_give(&ipm_sem); -} - -static void msg_transact(bool do_wait) -{ - /* Send an IPCCMD_RETURN_MSG, this will send us a return - * message with msg[0] as the ID (on cAVS 1.8+, otherwise - * zero). - */ - received_id = ID_INBOUND; - ipm_send(IPM_DEV, do_wait, IPCCMD_RETURN_MSG, msg, sizeof(msg)); - - /* Wait for the return message */ - k_sem_take(&ipm_sem, K_FOREVER); - - zassert_equal(received_id, - IS_ENABLED(IPM_CAVS_HOST_REGWORD) ? msg[0] : 0, - "wrong return message ID"); - - received_id = ID_INVALID; - - /* Now whitebox the message protocol: copy the message buffer - * (on the host side!) from the outbox to the inbox. That - * will write into our "already received" inbox buffer memory. - * We do this using the underlying intel_adsp_ipc API, which works - * only because we know it works. Note that on cAVS 1.8+, the - * actual in-use amount of the message will be one word - * shorter (because the first word is sent as IPC ext_data), - * but it won't be inspected below. - */ - for (int i = 0; i < ARRAY_SIZE(msg); i++) { - intel_adsp_ipc_send_message_sync(INTEL_ADSP_IPC_HOST_DEV, IPCCMD_WINCOPY, - (i << 16) | i, K_FOREVER); - } - - /* Validate data */ - for (int i = 0; i < ARRAY_SIZE(msg); i++) { - zassert_equal(msg[i], received_data[i], "wrong message data"); - } -} - -/* This is a little whiteboxey. It relies on the knowledge that an - * IPM message is nothing but a IPC message with the "id" parameter - * passed as data (and, on cAVS 1.8+ only, the first word of the - * message buffer passed as ext_data). - */ -ZTEST(intel_adsp, test_ipm_cavs_host) -{ - /* Restore IPM driver state (we've been mucking with intel_adsp_ipc tests) */ - intel_adsp_ipc_set_message_handler(INTEL_ADSP_IPC_HOST_DEV, ipm_handler, (void *)IPM_DEV); - intel_adsp_ipc_set_done_handler(INTEL_ADSP_IPC_HOST_DEV, NULL, NULL); - - ipm_register_callback(IPM_DEV, ipm_msg, NULL); - - /* Do it twice just for coverage on the wait parameter */ - msg_transact(true); - msg_transact(false); -} diff --git a/tests/boards/intel_adsp/smoke/src/main.c b/tests/boards/intel_adsp/smoke/src/main.c index c212b1e6acb88..ee8e38fa9b79c 100644 --- a/tests/boards/intel_adsp/smoke/src/main.c +++ b/tests/boards/intel_adsp/smoke/src/main.c @@ -1,46 +1,12 @@ -/* Copyright (c) 2022 Intel Corporation +/* + * Copyright (c) 2022, 2025 Intel Corporation + * * SPDX-License-Identifier: Apache-2.0 */ + #include #include #include -#include "tests.h" - -intel_adsp_ipc_handler_t ipm_handler; - -static bool clock_msg(const struct device *dev, void *arg, - uint32_t data, uint32_t ext_data) -{ - *(uint32_t *)arg = data; - return true; -} - -ZTEST(intel_adsp, test_clock_calibrate) -{ - static volatile uint32_t host_dt; - uint32_t cyc0, cyc1, hz, diff; - - /* Prime the host script's timestamp */ - cyc0 = k_cycle_get_32(); - intel_adsp_ipc_send_message(INTEL_ADSP_IPC_HOST_DEV, IPCCMD_TIMESTAMP, 0); - - k_msleep(1000); - host_dt = 0; - intel_adsp_ipc_set_message_handler(INTEL_ADSP_IPC_HOST_DEV, clock_msg, (void *)&host_dt); - - /* Now do it again, but with a handler to catch the result */ - cyc1 = k_cycle_get_32(); - intel_adsp_ipc_send_message(INTEL_ADSP_IPC_HOST_DEV, IPCCMD_TIMESTAMP, 0); - AWAIT(host_dt != 0); - intel_adsp_ipc_set_message_handler(INTEL_ADSP_IPC_HOST_DEV, NULL, NULL); - - hz = 1000000ULL * (cyc1 - cyc0) / host_dt; - printk("CLOCK: %lld Hz\n", (1000000ULL * (cyc1 - cyc0)) / host_dt); - - /* Make sure we're within 1% of spec */ - diff = abs(hz - CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC); - zassert_true((hz / MIN(1, diff)) > 100, "clock rate wrong"); -} #if XCHAL_HAVE_VECBASE ZTEST(intel_adsp, test_vecbase_lock) @@ -66,15 +32,6 @@ ZTEST(intel_adsp, test_vecbase_lock) } #endif -static void *intel_adsp_setup(void) -{ - struct intel_adsp_ipc_data *devdata = INTEL_ADSP_IPC_HOST_DEV->data; - - ipm_handler = devdata->handle_message; - - return NULL; -} - static void intel_adsp_teardown(void *data) { /* Wait a bit so the python script on host is ready to receive @@ -85,7 +42,6 @@ static void intel_adsp_teardown(void *data) k_msleep(1000); } -ZTEST_SUITE(intel_adsp, NULL, intel_adsp_setup, NULL, NULL, - intel_adsp_teardown); +ZTEST_SUITE(intel_adsp, NULL, NULL, NULL, NULL, intel_adsp_teardown); -ZTEST_SUITE(intel_adsp_boot, NULL, intel_adsp_setup, NULL, NULL, NULL); +ZTEST_SUITE(intel_adsp_boot, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/boards/intel_adsp/smoke/src/tests.h b/tests/boards/intel_adsp/smoke/src/tests.h index 0bb9b1f2de298..61bfddaa4e9e6 100644 --- a/tests/boards/intel_adsp/smoke/src/tests.h +++ b/tests/boards/intel_adsp/smoke/src/tests.h @@ -20,10 +20,4 @@ zassert_true(i < 10000, "timeout waiting for %s", #expr); \ } while (0) -/* Cached copy of the ipm_cavs_host driver's handler. We save it at - * the start of the test because we want to do unit testing on the - * underlying adsp_ipc device, then recover it later. - */ -extern intel_adsp_ipc_handler_t ipm_handler; - #endif /* ZEPHYR_TESTS_INTEL_ADSP_TESTS_H */ diff --git a/tests/boards/intel_adsp/smoke/testcase.yaml b/tests/boards/intel_adsp/smoke/testcase.yaml index 9561d1c2d5774..71e677f13f36f 100644 --- a/tests/boards/intel_adsp/smoke/testcase.yaml +++ b/tests/boards/intel_adsp/smoke/testcase.yaml @@ -1,5 +1,23 @@ tests: boards.intel_adsp.smoke: - platform_allow: intel_adsp/cavs25 + platform_allow: + - intel_adsp/cavs25 + - intel_adsp/ace15_mtpm + - intel_adsp/ace20_lnl + - intel_adsp/ace30/ptl + integration_platforms: + - intel_adsp/cavs25 + - intel_adsp/ace15_mtpm + - intel_adsp/ace30/ptl + boards.intel_adsp.smoke.ipc_msg: + extra_configs: + - CONFIG_INTEL_ADSP_IPC_OLD_INTERFACE=n + platform_allow: + - intel_adsp/cavs25 + - intel_adsp/ace15_mtpm + - intel_adsp/ace20_lnl + - intel_adsp/ace30/ptl integration_platforms: - intel_adsp/cavs25 + - intel_adsp/ace15_mtpm + - intel_adsp/ace30/ptl diff --git a/tests/subsys/ipc/ipc_msg_service/CMakeLists.txt b/tests/subsys/ipc/ipc_msg_service/CMakeLists.txt new file mode 100644 index 0000000000000..35a86c0bc20f6 --- /dev/null +++ b/tests/subsys/ipc/ipc_msg_service/CMakeLists.txt @@ -0,0 +1,14 @@ +# +# Copyright 2021 Google LLC +# Copyright 2025 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 +# + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(ipc_msg_service) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/subsys/ipc/ipc_msg_service/boards/qemu_cortex_a53.overlay b/tests/subsys/ipc/ipc_msg_service/boards/qemu_cortex_a53.overlay new file mode 100644 index 0000000000000..511668e9374ac --- /dev/null +++ b/tests/subsys/ipc/ipc_msg_service/boards/qemu_cortex_a53.overlay @@ -0,0 +1,20 @@ +/* + * Copyright 2021 Carlo Caione + * Copyright 2025 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + ipc10: ipc10 { + compatible = "ipc-msg-service-backend"; + offset = <10>; + status = "okay"; + }; + + ipc20: ipc20 { + compatible = "ipc-msg-service-backend"; + offset = <20>; + status = "okay"; + }; +}; diff --git a/tests/subsys/ipc/ipc_msg_service/dts/bindings/ipc-service-backend.yaml b/tests/subsys/ipc/ipc_msg_service/dts/bindings/ipc-service-backend.yaml new file mode 100644 index 0000000000000..c79c23908a6ed --- /dev/null +++ b/tests/subsys/ipc/ipc_msg_service/dts/bindings/ipc-service-backend.yaml @@ -0,0 +1,16 @@ +# +# Copyright (c) 2021 Carlo Caione +# Copyright (c) 2025 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 +# + +description: Backend binding to test the IPC message service + +compatible: "ipc-msg-service-backend" + +properties: + offset: + type: int + required: true + description: offset to add diff --git a/tests/subsys/ipc/ipc_msg_service/prj.conf b/tests/subsys/ipc/ipc_msg_service/prj.conf new file mode 100644 index 0000000000000..ec1a2f0aff06f --- /dev/null +++ b/tests/subsys/ipc/ipc_msg_service/prj.conf @@ -0,0 +1,9 @@ +# +# Copyright 2021 Carlo Caione +# Copyright 2025 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 +# + +CONFIG_ZTEST=y +CONFIG_IPC_MSG_SERVICE=y diff --git a/tests/subsys/ipc/ipc_msg_service/src/backend.c b/tests/subsys/ipc/ipc_msg_service/src/backend.c new file mode 100644 index 0000000000000..8f612a804e55b --- /dev/null +++ b/tests/subsys/ipc/ipc_msg_service/src/backend.c @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2021, Carlo Caione + * Copyright (c) 2025 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + * + * Simple backend that adds an offset (defined into the DT) to whatever it is + * passed in input as IPC message. + */ + +#include + +#include +#include +#include +#include + +#define DT_DRV_COMPAT ipc_msg_service_backend + +struct backend_data_t { + bool endpoint_registered; + const struct ipc_msg_ept_cfg *cfg; +}; + +struct backend_config_t { + unsigned int offset; +}; + +static int send(const struct device *instance, void *token, uint16_t msg_type, const void *msg_data) +{ + const struct backend_config_t *dev_config; + struct backend_data_t *dev_data; + const struct ipc_msg_type_cmd *msg; + struct ipc_msg_type_cmd cb_msg; + int ret; + + if (msg_type != IPC_MSG_TYPE_CMD) { + return -ENOTSUP; + } + + dev_config = instance->config; + dev_data = instance->data; + msg = (const struct ipc_msg_type_cmd *)msg_data; + + cb_msg.cmd = msg->cmd + dev_config->offset; + + ret = dev_data->cfg->cb.event(IPC_MSG_EVT_REMOTE_DONE, NULL, dev_data->cfg->priv); + ARG_UNUSED(ret); + + ret = dev_data->cfg->cb.received(msg_type, &cb_msg, dev_data->cfg->priv); + ARG_UNUSED(ret); + + return 0; +} + +static int query(const struct device *instance, void *token, uint16_t query_type, + const void *query_data, void *query_response) +{ + struct backend_data_t *dev_data = instance->data; + int ret; + + ARG_UNUSED(query_data); + ARG_UNUSED(query_response); + + if (query_type == IPC_MSG_QUERY_IS_READY) { + ret = dev_data->endpoint_registered ? 0 : -ENOENT; + } else { + ret = -ENOTSUP; + } + + return ret; +} + +static int register_ept(const struct device *instance, void **token, + const struct ipc_msg_ept_cfg *cfg) +{ + struct backend_data_t *data = instance->data; + + data->cfg = cfg; + data->endpoint_registered = true; + + return 0; +} + +static int deregister_ept(const struct device *instance, void *token) +{ + struct backend_data_t *data = instance->data; + + data->cfg = NULL; + data->endpoint_registered = false; + + return 0; +} + +const static struct ipc_msg_service_backend backend_ops = { + .query = query, + .send = send, + .register_endpoint = register_ept, + .deregister_endpoint = deregister_ept, +}; + +#define DEFINE_BACKEND_DEVICE(i) \ + static struct backend_config_t backend_config_##i = { \ + .offset = DT_INST_PROP(i, offset), \ + }; \ + \ + static struct backend_data_t backend_data_##i; \ + \ + DEVICE_DT_INST_DEFINE(i, NULL, NULL, &backend_data_##i, &backend_config_##i, POST_KERNEL, \ + CONFIG_IPC_MSG_SERVICE_REG_BACKEND_PRIORITY, &backend_ops); + +DT_INST_FOREACH_STATUS_OKAY(DEFINE_BACKEND_DEVICE) diff --git a/tests/subsys/ipc/ipc_msg_service/src/main.c b/tests/subsys/ipc/ipc_msg_service/src/main.c new file mode 100644 index 0000000000000..5c9d0d62c2036 --- /dev/null +++ b/tests/subsys/ipc/ipc_msg_service/src/main.c @@ -0,0 +1,173 @@ +/* + * Copyright 2021 Carlo Caione, + * Copyright 2025 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +static K_SEM_DEFINE(evt_done_sem, 0, 1); + +static int received_cb(uint16_t msg_type, const void *msg_data, void *priv) +{ + uintptr_t expected = (uintptr_t)priv; + const struct ipc_msg_type_cmd *msg = (const struct ipc_msg_type_cmd *)msg_data; + + zassert_equal(msg_type, IPC_MSG_TYPE_CMD, "received incorrect type of message"); + + printk("<<< Received cmd: %d, expected: %ld\n", msg->cmd, expected); + + zassert_equal(msg->cmd, expected, "msg doesn't match the expected value"); + + return 0; +} + +static int event_cb(uint16_t evt_type, const void *evt_data, void *priv) +{ + ARG_UNUSED(evt_data); + + zassert_equal(evt_type, IPC_MSG_EVT_REMOTE_DONE, "received incorrect event"); + + k_sem_give(&evt_done_sem); + + return 0; +} + +static struct ipc_msg_ept_cfg ept_cfg = { + .name = "test_ept", + .cb = { + .received = received_cb, + .event = event_cb, + }, +}; + +ZTEST(ipc_msg_service, test_ipc_msg_service_send) +{ + const struct device *dev_10; + const struct device *dev_20; + struct ipc_msg_ept ept_10; + struct ipc_msg_ept ept_20; + struct ipc_msg_type_cmd msg; + int ret; + + dev_10 = DEVICE_DT_GET(DT_NODELABEL(ipc10)); + dev_20 = DEVICE_DT_GET(DT_NODELABEL(ipc20)); + + /* + * We send 10 through the ipc10 instance so we expect 20 in the + * receiving callback (10 + 10 == 20) + */ + msg.cmd = 10; + printk(">>> Sending cmd %d\n", msg.cmd); + + /* Save the expected result in priv */ + ept_cfg.priv = (void *)20; + + ret = ipc_msg_service_register_endpoint(dev_10, &ept_10, &ept_cfg); + zassert_ok(ret, "ipc_msg_service_register_endpoint() failed"); + + ret = ipc_msg_service_send(&ept_10, IPC_MSG_TYPE_CMD, (const void *)&msg); + zassert_ok(ret, "ipc_msg_service_send() failed"); + + zassert_ok(k_sem_take(&evt_done_sem, K_MSEC(100)), "done event not received"); + + /* + * We send 10 again this time through the ipc20 instance so we expect + * 20 in the receiving callback (10 + 20 == 30) + */ + msg.cmd = 10; + printk(">>> Sending cmd %d\n", msg.cmd); + + /* Save the expected result in priv */ + ept_cfg.priv = (void *)30; + + ret = ipc_msg_service_register_endpoint(dev_20, &ept_20, &ept_cfg); + zassert_ok(ret, "ipc_msg_service_register_endpoint() failed"); + + ret = ipc_msg_service_send(&ept_20, IPC_MSG_TYPE_CMD, (const void *)&msg); + zassert_ok(ret, "ipc_msg_service_send() failed"); + + zassert_ok(k_sem_take(&evt_done_sem, K_MSEC(100)), "done event not received"); + + /* Deregister the endpoint and ensure that we fail correctly. */ + ret = ipc_msg_service_deregister_endpoint(&ept_10); + zassert_ok(ret, "ipc_msg_service_deregister_endpoint() failed"); + + ret = ipc_msg_service_deregister_endpoint(&ept_20); + zassert_ok(ret, "ipc_msg_service_deregister_endpoint() failed"); +} + +ZTEST(ipc_msg_service, test_ipc_msg_endpoint_not_registered) +{ + const struct device *dev_10; + struct ipc_msg_ept ept_10; + int ret; + + dev_10 = DEVICE_DT_GET(DT_NODELABEL(ipc10)); + + /* Register then de-register endpoint. */ + ret = ipc_msg_service_register_endpoint(dev_10, &ept_10, &ept_cfg); + zassert_ok(ret, "ipc_msg_service_register_endpoint() failed"); + + ret = ipc_msg_service_deregister_endpoint(&ept_10); + zassert_ok(ret, "ipc_msg_service_deregister_endpoint() failed"); + + /* Should fail as endpoint has already been de-registered. */ + ret = ipc_msg_service_send(&ept_10, IPC_MSG_TYPE_CMD, NULL); + zassert_equal(ret, -ENOENT, "ipc_msg_service_send() should return -ENOENT"); +} + +ZTEST(ipc_msg_service, test_ipc_msg_wrong_message_type) +{ + const struct device *dev_10; + struct ipc_msg_ept ept_10; + struct ipc_msg_type_cmd msg = {.cmd = 10}; + int ret; + + dev_10 = DEVICE_DT_GET(DT_NODELABEL(ipc10)); + + ret = ipc_msg_service_register_endpoint(dev_10, &ept_10, &ept_cfg); + zassert_ok(ret, "ipc_msg_service_register_endpoint() failed"); + + /* IPC_MSG_TYPE_CUSTOM_START is not a valid type in this test. */ + ret = ipc_msg_service_send(&ept_10, IPC_MSG_TYPE_CUSTOM_START, (const void *)&msg); + zassert_equal(ret, -ENOTSUP, "ipc_msg_service_send() should return -ENOTSUP"); + + zassert_equal(k_sem_take(&evt_done_sem, K_MSEC(100)), -EAGAIN, + "done event received but should not"); + + ret = ipc_msg_service_deregister_endpoint(&ept_10); + zassert_ok(ret, "ipc_msg_service_deregister_endpoint() failed"); +} + +ZTEST(ipc_msg_service, test_ipc_msg_endpoint_query) +{ + const struct device *dev_10; + struct ipc_msg_ept ept_10; + int ret; + + dev_10 = DEVICE_DT_GET(DT_NODELABEL(ipc10)); + + /* Since endpointe has never registered, API pointer is not valid, hence -EIO. */ + ret = ipc_msg_service_query(&ept_10, IPC_MSG_QUERY_IS_READY, NULL, NULL); + zassert_equal(ret, -EIO, "ipc_msg_service_query() should return -EIO"); + + ret = ipc_msg_service_register_endpoint(dev_10, &ept_10, &ept_cfg); + zassert_ok(ret, "ipc_msg_service_register_endpoint() failed"); + + ret = ipc_msg_service_query(&ept_10, IPC_MSG_QUERY_IS_READY, NULL, NULL); + zassert_equal(ret, 0, "ipc_msg_service_query() should return 0"); + + ret = ipc_msg_service_deregister_endpoint(&ept_10); + zassert_ok(ret, "ipc_msg_service_deregister_endpoint() failed"); + + /* Now this will -ENOENT as API pointer has been set above. */ + ret = ipc_msg_service_query(&ept_10, IPC_MSG_QUERY_IS_READY, NULL, NULL); + zassert_equal(ret, -ENOENT, "ipc_msg_service_query() should return -ENOENT"); +} + +ZTEST_SUITE(ipc_msg_service, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/subsys/ipc/ipc_msg_service/testcase.yaml b/tests/subsys/ipc/ipc_msg_service/testcase.yaml new file mode 100644 index 0000000000000..a55f9d498b8ba --- /dev/null +++ b/tests/subsys/ipc/ipc_msg_service/testcase.yaml @@ -0,0 +1,12 @@ +# +# Copyright 2021 Carlo Caione 2021 +# Copyright 2025 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 +# + +tests: + ipc.ipc_msg_service: + tags: ipc_msg_service + harness: ztest + platform_allow: qemu_cortex_a53