diff --git a/drivers/firmware/CMakeLists.txt b/drivers/firmware/CMakeLists.txt index 1222e9388ea..167d21addb5 100644 --- a/drivers/firmware/CMakeLists.txt +++ b/drivers/firmware/CMakeLists.txt @@ -1,3 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 +# zephyr-keep-sorted-start +add_subdirectory(nrf_ironside) add_subdirectory_ifdef(CONFIG_ARM_SCMI scmi) +# zephyr-keep-sorted-stop diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig index 41576061237..3e3afe9ffdd 100644 --- a/drivers/firmware/Kconfig +++ b/drivers/firmware/Kconfig @@ -10,6 +10,9 @@ config ARM_SCMI Enable support for ARM's System Configuration and Management Interface (SCMI). +# zephyr-keep-sorted-start +source "drivers/firmware/nrf_ironside/Kconfig" source "drivers/firmware/scmi/Kconfig" +# zephyr-keep-sorted-stop endmenu diff --git a/drivers/firmware/nrf_ironside/CMakeLists.txt b/drivers/firmware/nrf_ironside/CMakeLists.txt new file mode 100644 index 00000000000..90320c62df5 --- /dev/null +++ b/drivers/firmware/nrf_ironside/CMakeLists.txt @@ -0,0 +1,6 @@ +# Copyright (c) 2025 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +zephyr_library_sources_ifdef(CONFIG_NRF_IRONSIDE_CALL call.c) diff --git a/drivers/firmware/nrf_ironside/Kconfig b/drivers/firmware/nrf_ironside/Kconfig new file mode 100644 index 00000000000..adfdf97f648 --- /dev/null +++ b/drivers/firmware/nrf_ironside/Kconfig @@ -0,0 +1,22 @@ +# Copyright (c) 2025 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +config NRF_IRONSIDE_CALL + bool + depends on DT_HAS_NORDIC_IRONSIDE_CALL_ENABLED + depends on SOC_NRF54H20_IRON + select EVENTS + select MBOX + help + This is selected by features that require support for IRONside calls. + +if NRF_IRONSIDE_CALL + +config NRF_IRONSIDE_CALL_INIT_PRIORITY + int "IRONside calls' initialization priority" + default 41 + help + Initialization priority of IRONside calls. It must be below MBOX_INIT_PRIORITY, + but higher than the priority of any feature that selects NRF_IRONSIDE_CALL. + +endif # NRF_IRONSIDE_CALL diff --git a/drivers/firmware/nrf_ironside/call.c b/drivers/firmware/nrf_ironside/call.c new file mode 100644 index 00000000000..8d77119828c --- /dev/null +++ b/drivers/firmware/nrf_ironside/call.c @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define DT_DRV_COMPAT nordic_ironside_call + +#define SHM_NODE DT_INST_PHANDLE(0, memory_region) +#define NUM_BUFS (DT_REG_SIZE(SHM_NODE) / sizeof(struct ironside_call_buf)) +#define ALL_BUF_BITS BIT_MASK(NUM_BUFS) + +/* Note: this area is already zero-initialized at reset time. */ +static struct ironside_call_buf *const bufs = (void *)DT_REG_ADDR(SHM_NODE); + +#if defined(CONFIG_DCACHE_LINE_SIZE) +BUILD_ASSERT((DT_REG_ADDR(SHM_NODE) % CONFIG_DCACHE_LINE_SIZE) == 0); +BUILD_ASSERT((sizeof(struct ironside_call_buf) % CONFIG_DCACHE_LINE_SIZE) == 0); +#endif + +static const struct mbox_dt_spec mbox_rx = MBOX_DT_SPEC_INST_GET(0, rx); +static const struct mbox_dt_spec mbox_tx = MBOX_DT_SPEC_INST_GET(0, tx); + +K_EVENT_DEFINE(alloc_evts); +K_EVENT_DEFINE(rsp_evts); + +static void ironside_call_rsp(const struct device *dev, mbox_channel_id_t channel_id, + void *user_data, struct mbox_msg *data) +{ + ARG_UNUSED(dev); + ARG_UNUSED(channel_id); + ARG_UNUSED(user_data); + ARG_UNUSED(data); + + struct ironside_call_buf *buf; + uint32_t rsp_buf_bits = 0; + + /* Check which buffers are not being dispatched currently. Those must + * not be cache-invalidated, in case they're used in thread context. + * + * This value will remain valid as long as ironside_call_rsp is never + * preempted by ironside_call_dispatch; the former runs in MBOX ISR, + * while the latter shall not run in ISR (because of k_event_wait). + */ + const uint32_t skip_buf_bits = k_event_test(&rsp_evts, ALL_BUF_BITS); + + for (int i = 0; i < NUM_BUFS; i++) { + if (skip_buf_bits & BIT(i)) { + continue; + } + + buf = &bufs[i]; + + sys_cache_data_invd_range(buf, sizeof(*buf)); + barrier_dmem_fence_full(); + + if (buf->status != IRONSIDE_CALL_STATUS_IDLE && + buf->status != IRONSIDE_CALL_STATUS_REQ) { + rsp_buf_bits |= BIT(i); + } + } + k_event_post(&rsp_evts, rsp_buf_bits); +} + +static int ironside_call_init(const struct device *dev) +{ + ARG_UNUSED(dev); + + int err; + + k_event_set(&alloc_evts, ALL_BUF_BITS); + k_event_set(&rsp_evts, ALL_BUF_BITS); + + err = mbox_register_callback_dt(&mbox_rx, ironside_call_rsp, NULL); + __ASSERT_NO_MSG(err == 0); + + err = mbox_set_enabled_dt(&mbox_rx, 1); + __ASSERT_NO_MSG(err == 0); + + return 0; +} + +DEVICE_DT_INST_DEFINE(0, ironside_call_init, NULL, NULL, NULL, POST_KERNEL, + CONFIG_NRF_IRONSIDE_CALL_INIT_PRIORITY, NULL); + +struct ironside_call_buf *ironside_call_alloc(void) +{ + uint32_t avail_buf_bits; + uint32_t alloc_buf_bit; + + do { + avail_buf_bits = k_event_wait(&alloc_evts, ALL_BUF_BITS, false, K_FOREVER); + + /* Try allocating the first available block. + * If it's claimed by another thread, go back and wait for another block. + */ + alloc_buf_bit = LSB_GET(avail_buf_bits); + } while (k_event_clear(&alloc_evts, alloc_buf_bit) == 0); + + return &bufs[u32_count_trailing_zeros(alloc_buf_bit)]; +} + +void ironside_call_dispatch(struct ironside_call_buf *buf) +{ + const uint32_t buf_bit = BIT(buf - bufs); + int err; + + buf->status = IRONSIDE_CALL_STATUS_REQ; + barrier_dmem_fence_full(); + + sys_cache_data_flush_range(buf, sizeof(*buf)); + + k_event_clear(&rsp_evts, buf_bit); + + err = mbox_send_dt(&mbox_tx, NULL); + __ASSERT_NO_MSG(err == 0); + + k_event_wait(&rsp_evts, buf_bit, false, K_FOREVER); +} + +void ironside_call_release(struct ironside_call_buf *buf) +{ + const uint32_t buf_bit = BIT(buf - bufs); + + buf->status = IRONSIDE_CALL_STATUS_IDLE; + barrier_dmem_fence_full(); + + sys_cache_data_flush_range(buf, sizeof(*buf)); + + k_event_post(&alloc_evts, buf_bit); +} diff --git a/dts/bindings/firmware/nordic,ironside-call.yaml b/dts/bindings/firmware/nordic,ironside-call.yaml new file mode 100644 index 00000000000..14f39cb0b74 --- /dev/null +++ b/dts/bindings/firmware/nordic,ironside-call.yaml @@ -0,0 +1,22 @@ +# Copyright (c) 2025 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +description: IPC configuration for Nordic IRONside calls + +compatible: "nordic,ironside-call" + +include: base.yaml + +properties: + memory-region: + type: phandle + required: true + description: phandle to shared memory region + + mboxes: + required: true + description: MBOX channel specifiers (TX and RX are required) + + mbox-names: + required: true + description: MBOX channel names (must be called "tx" and "rx") diff --git a/include/zephyr/drivers/firmware/nrf_ironside/call.h b/include/zephyr/drivers/firmware/nrf_ironside/call.h new file mode 100644 index 00000000000..178b9371cdb --- /dev/null +++ b/include/zephyr/drivers/firmware/nrf_ironside/call.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_ZEPHYR_DRIVERS_FIRMWARE_NRF_IRONSIDE_CALL_H_ +#define ZEPHYR_INCLUDE_ZEPHYR_DRIVERS_FIRMWARE_NRF_IRONSIDE_CALL_H_ + +#include + +/** @brief Maximum number of arguments to an IRONside call. + * + * This is chosen so that the containing message buffer size is minimal but + * cache line aligned. + */ +#define NRF_IRONSIDE_CALL_NUM_ARGS 7 + +/** @brief Message buffer. */ +struct ironside_call_buf { + /** Status code. This is set by the API. */ + uint16_t status; + /** Operation identifier. This is set by the user. */ + uint16_t id; + /** Operation arguments. These are set by the user. */ + uint32_t args[NRF_IRONSIDE_CALL_NUM_ARGS]; +}; + +/** + * @name Message buffer status codes. + * @{ + */ + +/** Buffer is idle and available for allocation. */ +#define IRONSIDE_CALL_STATUS_IDLE 0 +/** Request was processed successfully by the server. */ +#define IRONSIDE_CALL_STATUS_RSP_SUCCESS 1 +/** Request status code is unknown. */ +#define IRONSIDE_CALL_STATUS_RSP_ERR_UNKNOWN_STATUS 2 +/** Request status code is no longer supported. */ +#define IRONSIDE_CALL_STATUS_RSP_ERR_EXPIRED_STATUS 3 +/** Operation identifier is unknown. */ +#define IRONSIDE_CALL_STATUS_RSP_ERR_UNKNOWN_ID 4 +/** Operation identifier is no longer supported. */ +#define IRONSIDE_CALL_STATUS_RSP_ERR_EXPIRED_ID 5 +/** Buffer contains a request from the client. */ +#define IRONSIDE_CALL_STATUS_REQ 6 + +/** + * @} + */ + +/** + * @brief Allocate memory for an IRONside call. + * + * This function will block when no buffers are available, until one is + * released by another thread on the client side. + * + * @return Pointer to the allocated buffer. + */ +struct ironside_call_buf *ironside_call_alloc(void); + +/** + * @brief Dispatch an IRONside call. + * + * This function will block until a response is received from the server. + * + * @param buf Buffer returned by ironside_call_alloc(). It should be populated + * with request data before calling this function. Upon returning, + * this data will have been replaced by response data. + */ +void ironside_call_dispatch(struct ironside_call_buf *buf); + +/** + * @brief Release an IRONside call buffer. + * + * This function must be called after processing the response. + * + * @param buf Buffer used to perform the call. + */ +void ironside_call_release(struct ironside_call_buf *buf); + +#endif /* ZEPHYR_INCLUDE_ZEPHYR_DRIVERS_FIRMWARE_NRF_IRONSIDE_CALL_H_ */