drivers: firmware: Add support for IRONside calls
IRONside calls are remote procedure calls which comprise the runtime interface of Nordic IRONside SE. They are realized using a simple IPC mechanism. A local domain (client) issues requests to the server by exchanging data in shared memory, which is divided into evenly sized buffers. The client selects a buffer, writes a request into it, and sends it to the server. The server processes that request and writes a response into the same buffer before returning it to the client. This patch adds the initial client-side implementation on top of MBOX. It features cache management and a blocking alloc/dispatch/release API for synchronous, zero-copy transfers. A new devicetree binding is added to support this implementation. It is patterned after the `zephyr,ipc-*` bindings, where each node associates a pair of mailboxes and a shared memory region. Signed-off-by: Grzegorz Swiderski <grzegorz.swiderski@nordicsemi.no>
This commit is contained in:
parent
829c796e04
commit
47df9ec981
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
6
drivers/firmware/nrf_ironside/CMakeLists.txt
Normal file
6
drivers/firmware/nrf_ironside/CMakeLists.txt
Normal file
@ -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)
|
||||
22
drivers/firmware/nrf_ironside/Kconfig
Normal file
22
drivers/firmware/nrf_ironside/Kconfig
Normal file
@ -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
|
||||
139
drivers/firmware/nrf_ironside/call.c
Normal file
139
drivers/firmware/nrf_ironside/call.c
Normal file
@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Nordic Semiconductor ASA
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/cache.h>
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/drivers/firmware/nrf_ironside/call.h>
|
||||
#include <zephyr/drivers/mbox.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/sys/barrier.h>
|
||||
#include <zephyr/sys/math_extras.h>
|
||||
#include <zephyr/sys/util.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
22
dts/bindings/firmware/nordic,ironside-call.yaml
Normal file
22
dts/bindings/firmware/nordic,ironside-call.yaml
Normal file
@ -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")
|
||||
82
include/zephyr/drivers/firmware/nrf_ironside/call.h
Normal file
82
include/zephyr/drivers/firmware/nrf_ironside/call.h
Normal file
@ -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 <stdint.h>
|
||||
|
||||
/** @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_ */
|
||||
Loading…
Reference in New Issue
Block a user