nrf_wifi: Add nRF71 support

nRF7120 PDK support that uses IPC as comms b/w APP and Wi-Fi domains.

Signed-off-by: Chaitanya Tata <Chaitanya.Tata@nordicsemi.no>
This commit is contained in:
Chaitanya Tata 2024-12-22 00:43:04 +05:30 committed by Benjamin Cabé
commit 3953bb9ce3
16 changed files with 979 additions and 17 deletions

View file

@ -27,9 +27,11 @@ zephyr_library_sources_ifndef(CONFIG_NRF70_OFFLOADED_RAW_TX
src/fmac_main.c
)
zephyr_library_sources_ifdef(CONFIG_NRF_WIFI_PATCHES_BUILTIN
src/fw_load.c
)
if(NOT CONFIG_NRF71_ON_IPC)
zephyr_library_sources_ifdef(CONFIG_NRF_WIFI_PATCHES_BUILTIN
src/fw_load.c
)
endif()
if(NOT CONFIG_NRF70_RADIO_TEST AND NOT CONFIG_NRF70_OFFLOADED_RAW_TX)
zephyr_library_sources(

View file

@ -5,6 +5,9 @@
# SPDX-License-Identifier: Apache-2.0
#
# TODO: Use DTS generated Kconfig once the board support is added
DT_COMPAT_NORDIC_WIFI71 := nordic,wifi71
menuconfig WIFI_NRF70
bool "nRF70 driver"
select NET_L2_WIFI_MGMT if NETWORKING
@ -16,7 +19,8 @@ menuconfig WIFI_NRF70
depends on \
DT_HAS_NORDIC_NRF7002_SPI_ENABLED || DT_HAS_NORDIC_NRF7002_QSPI_ENABLED || \
DT_HAS_NORDIC_NRF7001_SPI_ENABLED || DT_HAS_NORDIC_NRF7001_QSPI_ENABLED || \
DT_HAS_NORDIC_NRF7000_SPI_ENABLED || DT_HAS_NORDIC_NRF7000_QSPI_ENABLED
DT_HAS_NORDIC_NRF7000_SPI_ENABLED || DT_HAS_NORDIC_NRF7000_QSPI_ENABLED || \
$(dt_compat_enabled,$(DT_COMPAT_NORDIC_WIFI71))
help
Nordic Wi-Fi Driver
@ -24,7 +28,7 @@ if WIFI_NRF70
# Hidden symbols for internal use
config WIFI_NRF7002
bool
default y if DT_HAS_NORDIC_NRF7002_SPI_ENABLED || DT_HAS_NORDIC_NRF7002_QSPI_ENABLED
default y if DT_HAS_NORDIC_NRF7002_SPI_ENABLED || DT_HAS_NORDIC_NRF7002_QSPI_ENABLED || $(dt_compat_enabled,$(DT_COMPAT_NORDIC_WIFI71))
config WIFI_NRF7001
bool
@ -151,6 +155,7 @@ endchoice
config NRF_WIFI_LOW_POWER
bool "Low power mode in nRF Wi-Fi chipsets"
depends on !NRF70_RADIO_TEST && !NRF70_AP_MODE
depends on !NRF71_ON_IPC
default y
config NRF70_TCP_IP_CHECKSUM_OFFLOAD
@ -202,6 +207,7 @@ config NRF70_SR_COEX_RF_SWITCH
config NRF70_SR_COEX_SLEEP_CTRL_GPIO_CTRL
bool "Configuration of GPIO control for coexistence"
depends on !NRF71_ON_IPC
default y
config NRF70_SR_COEX_SWCTRL1_OUTPUT

View file

@ -485,8 +485,12 @@ void reg_change_callbk_fn(void *vif_ctx,
}
#endif /* !CONFIG_NRF70_RADIO_TEST */
#ifdef CONFIG_NRF71_ON_IPC
#define MAX_TX_PWR(label) DT_PROP(DT_NODELABEL(wifi), label) * 4
#else
/* DTS uses 1dBm as the unit for TX power, while the RPU uses 0.25dBm */
#define MAX_TX_PWR(label) DT_PROP(DT_NODELABEL(nrf70), label) * 4
#endif /* CONFIG_NRF71_ON_IPC */
void configure_tx_pwr_settings(struct nrf_wifi_tx_pwr_ctrl_params *tx_pwr_ctrl_params,
struct nrf_wifi_tx_pwr_ceil_params *tx_pwr_ceil_params)

View file

@ -31,12 +31,13 @@ enum nrf_wifi_status nrf_wifi_fw_load(void *rpu_ctx)
LOG_ERR("%s: nrf_wifi_fmac_fw_parse failed", __func__);
return status;
}
#ifndef CONFIG_NRF71_ON_IPC
/* Load the FW patches to the RPU */
status = nrf_wifi_fmac_fw_load(rpu_ctx, &fw_info);
if (status != NRF_WIFI_STATUS_SUCCESS) {
LOG_ERR("%s: nrf_wifi_fmac_fw_load failed", __func__);
}
#endif /* !CONFIG_NRF71_ON_IPC */
return status;
}

View file

@ -575,6 +575,7 @@ enum nrf_wifi_status nrf_wifi_get_mac_addr(struct nrf_wifi_vif_ctx_zep *vif_ctx_
random_mac_addr,
WIFI_MAC_ADDR_LEN);
#elif CONFIG_WIFI_OTP_MAC_ADDRESS
#ifndef CONFIG_NRF71_ON_IPC
status = nrf_wifi_fmac_otp_mac_addr_get(fmac_dev_ctx,
vif_ctx_zep->vif_idx,
vif_ctx_zep->mac_addr.addr);
@ -583,6 +584,15 @@ enum nrf_wifi_status nrf_wifi_get_mac_addr(struct nrf_wifi_vif_ctx_zep *vif_ctx_
__func__);
goto unlock;
}
#else
/* Set dummy MAC address */
vif_ctx_zep->mac_addr.addr[0] = 0x00;
vif_ctx_zep->mac_addr.addr[1] = 0x00;
vif_ctx_zep->mac_addr.addr[2] = 0x5E;
vif_ctx_zep->mac_addr.addr[3] = 0x00;
vif_ctx_zep->mac_addr.addr[4] = 0x10;
vif_ctx_zep->mac_addr.addr[5] = 0x00;
#endif /* !CONFIG_NRF71_ON_IPC */
#endif
if (!nrf_wifi_utils_is_mac_addr_valid(vif_ctx_zep->mac_addr.addr)) {

View file

@ -26,8 +26,18 @@ if (CONFIG_NRF70_BUSLIB)
inc
${NRF_WIFI_DIR}/os_if/inc
)
zephyr_library_include_directories_ifdef(CONFIG_NRF71_ON_IPC
${NRF_WIFI_DIR}/bus_if/bal/inc
# QSPI is common to (Q)SPI and IPC
${NRF_WIFI_DIR}/bus_if/bus/qspi/inc
${NRF_WIFI_DIR}/fw_if/umac_if/inc/fw
${NRF_WIFI_DIR}/hw_if/hal/inc
)
zephyr_library_compile_definitions_ifdef(CONFIG_NRF71_ON_IPC
NRF71_ON_IPC
)
zephyr_library_sources(
rpu_hw_if.c
device.c
)
if(NOT CONFIG_WIFI_NRF70)
@ -36,9 +46,16 @@ if (CONFIG_NRF70_BUSLIB)
)
endif()
zephyr_library_sources_ifdef(CONFIG_NRF70_ON_QSPI
rpu_hw_if.c
qspi_if.c
)
zephyr_library_sources_ifdef(CONFIG_NRF70_ON_SPI
rpu_hw_if.c
spi_if.c
)
zephyr_library_sources_ifdef(CONFIG_NRF71_ON_IPC
ipc_if.c
ipc_service.c
spsc_qm.c
)
endif()

View file

@ -10,6 +10,7 @@ DT_COMPAT_NORDIC_NRF7001_QSPI := nordic,nrf7001-qspi
DT_COMPAT_NORDIC_NRF7001_SPI := nordic,nrf7001-spi
DT_COMPAT_NORDIC_NRF7000_QSPI := nordic,nrf7000-qspi
DT_COMPAT_NORDIC_NRF7000_SPI := nordic,nrf7000-spi
DT_COMPAT_NORDIC_WIFI71 := nordic,wifi71
menuconfig NRF70_BUSLIB
bool "NRF70 Bus Library"
@ -30,6 +31,15 @@ config NRF70_ON_SPI
$(dt_compat_enabled,$(DT_COMPAT_NORDIC_NRF7000_SPI))
select SPI
config NRF71_ON_IPC
def_bool $(dt_compat_enabled,$(DT_COMPAT_NORDIC_WIFI71))
select MBOX
select IPC_SERVICE
select SPSC_PBUF
help
nRF71 is a Wi-Fi and BLE combo SoC and uses IPC as a communication
between APP and Wi-Fi cores.
module = WIFI_NRF70_BUSLIB
module-dep = LOG
module-str = Log level for Wi-Fi nRF70 bus library

View file

@ -12,15 +12,26 @@
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/wifi/nrf_wifi/bus/qspi_if.h>
#include <stdio.h>
#include <string.h>
#if defined(CONFIG_NRF71_ON_IPC)
#include "ipc_if.h"
#else
#include <zephyr/drivers/wifi/nrf_wifi/bus/qspi_if.h>
#include "spi_if.h"
static struct qspi_config config;
#endif
#if defined(CONFIG_NRF70_ON_QSPI)
#if defined(CONFIG_NRF71_ON_IPC)
static struct rpu_dev ipc = {
.init = ipc_init,
.deinit = ipc_deinit,
.send = ipc_send,
.recv = ipc_recv,
.register_rx_cb = ipc_register_rx_cb,
};
#elif defined(CONFIG_NRF70_ON_QSPI)
static struct qspi_dev qspi = {.init = qspi_init,
.deinit = qspi_deinit,
.read = qspi_read,
@ -34,6 +45,7 @@ static struct qspi_dev spim = {.init = spim_init,
.hl_read = spim_hl_read};
#endif
#ifndef CONFIG_NRF71_ON_IPC
struct qspi_config *qspi_defconfig(void)
{
memset(&config, 0, sizeof(struct qspi_config));
@ -71,12 +83,20 @@ struct qspi_config *qspi_get_config(void)
{
return &config;
}
#endif
#ifndef CONFIG_NRF71_ON_IPC
struct qspi_dev *qspi_dev(void)
{
#if CONFIG_NRF70_ON_QSPI
#if defined(CONFIG_NRF70_ON_QSPI)
return &qspi;
#else
return &spim;
#endif
}
#else
struct rpu_dev *rpu_dev(void)
{
return &ipc;
}
#endif /*! CONFIG_NRF71_ON_IPC */

View file

@ -0,0 +1,135 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @brief File containing API definitions for the
* IPC bus layer of the nRF71 Wi-Fi driver.
*/
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(wifi_nrf_bus, CONFIG_WIFI_NRF70_BUSLIB_LOG_LEVEL);
#include "ipc_if.h"
#include "bal_structs.h"
#include "qspi.h"
#include "common/hal_structs_common.h"
/* Define addresses to use for the free queues */
#define EVENT_FREEQ_ADDR 0x200C2000
#define CMD_FREEQ_ADDR 0x200C3000
#define NUM_INSTANCES 3
#define NUM_ENDPOINTS 1
struct device *ipc_instances[NUM_INSTANCES];
struct ipc_ept ept[NUM_ENDPOINTS];
struct ipc_ept_cfg ept_cfg[NUM_ENDPOINTS];
static wifi_ipc_t wifi_event;
static wifi_ipc_t wifi_cmd;
static wifi_ipc_t wifi_tx;
static int (*callback_func)(void *data);
static void event_recv(void *data, void *priv)
{
struct nrf_wifi_bus_qspi_dev_ctx *dev_ctx = NULL;
struct nrf_wifi_bal_dev_ctx *bal_dev_ctx = NULL;
struct nrf_wifi_hal_dev_ctx *hal_dev_ctx = NULL;
dev_ctx = (struct nrf_wifi_bus_qspi_dev_ctx *)priv;
bal_dev_ctx = (struct nrf_wifi_bal_dev_ctx *)dev_ctx->bal_dev_ctx;
hal_dev_ctx = (struct nrf_wifi_hal_dev_ctx *)bal_dev_ctx->hal_dev_ctx;
LOG_DBG("Event IPC received");
hal_dev_ctx->ipc_msg = data;
callback_func(priv);
LOG_DBG("Event IPC callback completed");
}
int ipc_init(void)
{
wifi_ipc_host_event_init(&wifi_event, EVENT_FREEQ_ADDR);
LOG_DBG("Event IPC initialized");
wifi_ipc_host_cmd_init(&wifi_cmd, CMD_FREEQ_ADDR);
LOG_DBG("Command IPC initialized");
return 0;
}
int ipc_deinit(void)
{
return 0;
}
int ipc_recv(ipc_ctx_t ctx, void *data, int len)
{
return 0;
}
int ipc_send(ipc_ctx_t ctx, const void *data, int len)
{
int ret = 0;
switch (ctx.inst) {
case IPC_INSTANCE_CMD_CTRL:
/* IPC service on RPU may not have been established. Keep trying. */
do {
ret = wifi_ipc_host_cmd_send_memcpy(&wifi_cmd, data, len);
} while (ret == WIFI_IPC_STATUS_BUSYQ_NOTREADY);
/* Critical error during IPC service transfer. Should never happen. */
if (ret == WIFI_IPC_STATUS_BUSYQ_CRITICAL_ERR) {
LOG_ERR("Critical error during IPC CMD busyq transfer");
return -1;
}
break;
case IPC_INSTANCE_CMD_TX:
/* IPC service on RPU may not have been established. Keep trying. */
do {
ret = wifi_ipc_host_tx_send(&wifi_tx, data);
} while (ret == WIFI_IPC_STATUS_BUSYQ_NOTREADY);
/* Critical error during IPC service transfer. Should never happen. */
if (ret == WIFI_IPC_STATUS_BUSYQ_CRITICAL_ERR) {
LOG_ERR("Critical error during IPC TX busyq transfer");
return -1;
}
break;
case IPC_INSTANCE_RX:
break;
default:
break;
}
LOG_DBG("IPC send completed: %d", ret);
return ret;
}
int ipc_register_rx_cb(int (*rx_handler)(void *priv), void *data)
{
int ret;
callback_func = rx_handler;
ret = wifi_ipc_bind_ipc_service_tx_rx(&wifi_cmd, &wifi_event,
DEVICE_DT_GET(DT_NODELABEL(ipc0)), event_recv, data);
if (ret != WIFI_IPC_STATUS_OK) {
LOG_ERR("Failed to bind IPC service: %d", ret);
return -1;
}
ret = wifi_ipc_bind_ipc_service(&wifi_tx, DEVICE_DT_GET(DT_NODELABEL(ipc1)), event_recv,
data);
if (ret != WIFI_IPC_STATUS_OK) {
LOG_ERR("Failed to bind IPC service: %d", ret);
return -1;
}
return 0;
}

View file

@ -0,0 +1,48 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef __IPC_IF_H__
#define __IPC_IF_H__
#include <zephyr/kernel.h>
#include "ipc_service.h"
typedef enum {
IPC_INSTANCE_CMD_CTRL = 0,
IPC_INSTANCE_CMD_TX,
IPC_INSTANCE_EVT,
IPC_INSTANCE_RX
} ipc_instances_nrf71_t;
typedef enum {
IPC_EPT_UMAC = 0,
IPC_EPT_LMAC
} ipc_epts_nrf71_t;
typedef struct ipc_ctx {
ipc_instances_nrf71_t inst;
ipc_epts_nrf71_t ept;
} ipc_ctx_t;
struct rpu_dev {
int (*init)();
int (*deinit)(void);
int (*send)(ipc_ctx_t ctx, const void *data, int len);
int (*recv)(ipc_ctx_t ctx, void *data, int len);
int (*register_rx_cb)(int (*rx_handler)(void *priv), void *data);
};
struct rpu_dev *rpu_dev(void);
int ipc_init(void);
int ipc_deinit(void);
int ipc_send(ipc_ctx_t ctx, const void *data, int len);
/* Blocking Receive */
int ipc_recv(ipc_ctx_t ctx, void *data, int len);
/* Non-blocking Receive (global, not per instance) */
int ipc_register_rx_cb(int (*rx_handler)(void *priv), void *data);
#endif /* __IPC_IF_H__ */

View file

@ -0,0 +1,206 @@
/*
* Copyright (c) 2025 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @brief File containing API definitions for the
* IPC service layer of the nrf71 Wi-Fi driver.
*/
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(wifi_nrf_bus, CONFIG_WIFI_NRF70_BUSLIB_LOG_LEVEL);
#include "ipc_service.h"
static void wifi_ipc_ep_bound(void *priv)
{
wifi_ipc_t *context = (wifi_ipc_t *)priv;
context->busy_q.ipc_ready = true;
}
static void wifi_ipc_recv_callback(const void *data, size_t len, void *priv)
{
(void)len;
uint32_t global_addr = *((uint32_t *)data);
wifi_ipc_t *context = (wifi_ipc_t *)priv;
context->busy_q.recv_cb((void *)global_addr, context->busy_q.priv);
if (context->free_q != NULL) {
while (!spsc32_push(context->free_q, global_addr)) {
};
}
}
static void wifi_ipc_busyq_init(wifi_ipc_busyq_t *busyq, const ipc_device_wrapper_t *ipc_inst,
void *rx_cb, void *priv)
{
busyq->ipc_inst = ipc_inst;
busyq->ipc_ep_cfg.cb.bound = wifi_ipc_ep_bound;
busyq->ipc_ep_cfg.cb.received = wifi_ipc_recv_callback;
busyq->recv_cb = rx_cb;
busyq->ipc_ready = false;
busyq->priv = priv;
}
/**
* Register the IPC service on the busy_queue
*/
static wifi_ipc_status_t wifi_ipc_busyq_register(wifi_ipc_t *context)
{
int ret;
const struct device *ipc_instance = GET_IPC_INSTANCE(context->busy_q.ipc_inst);
ret = ipc_service_open_instance(ipc_instance);
if (ret < 0) {
return WIFI_IPC_STATUS_INIT_ERR;
}
context->busy_q.ipc_ep_cfg.name = "ep";
context->busy_q.ipc_ep_cfg.priv = context;
ret = ipc_service_register_endpoint(ipc_instance, &context->busy_q.ipc_ep,
&context->busy_q.ipc_ep_cfg);
if (ret < 0 && ret != -EALREADY) {
return WIFI_IPC_STATUS_INIT_ERR;
}
LOG_INF("IPC busy queue registered");
return WIFI_IPC_STATUS_OK;
}
wifi_ipc_status_t wifi_ipc_bind_ipc_service(wifi_ipc_t *context,
const ipc_device_wrapper_t *ipc_inst,
void (*rx_cb)(void *data, void *priv), void *priv)
{
wifi_ipc_busyq_init(&context->busy_q, ipc_inst, rx_cb, priv);
return wifi_ipc_busyq_register(context);
}
wifi_ipc_status_t wifi_ipc_bind_ipc_service_tx_rx(wifi_ipc_t *tx, wifi_ipc_t *rx,
const ipc_device_wrapper_t *ipc_inst,
void (*rx_cb)(void *data, void *priv), void *priv)
{
wifi_ipc_busyq_init(&rx->busy_q, ipc_inst, rx_cb, priv);
/**
* When initialising an IPC service, both TX and RX mailboxes need to be
* registered at the same time using a single function call. Both tx and
* rx need to refer to the same IPC instance.
*/
tx->linked_ipc = &rx->busy_q;
return wifi_ipc_busyq_register(rx);
}
wifi_ipc_status_t wifi_ipc_freeq_get(wifi_ipc_t *context, uint32_t *data)
{
if (context->free_q == NULL) {
LOG_ERR("Free queue is not initialised");
return WIFI_IPC_STATUS_FREEQ_UNINIT_ERR;
}
if (spsc32_is_empty(context->free_q)) {
LOG_DBG("Free queue is empty");
return WIFI_IPC_STATUS_FREEQ_EMPTY;
}
if (!spsc32_read_head(context->free_q, data)) {
LOG_DBG("Free queue is empty");
return WIFI_IPC_STATUS_FREEQ_EMPTY;
}
return WIFI_IPC_STATUS_OK;
}
wifi_ipc_status_t wifi_ipc_freeq_send(wifi_ipc_t *context, uint32_t data)
{
return (spsc32_push(context->free_q, data) == true ? WIFI_IPC_STATUS_OK
: WIFI_IPC_STATUS_FREEQ_FULL);
}
wifi_ipc_status_t wifi_ipc_busyq_send(wifi_ipc_t *context, uint32_t *data)
{
/* Get correct linked endpoint */
wifi_ipc_busyq_t *busyq =
context->linked_ipc ? context->linked_ipc : &context->busy_q;
if (!busyq->ipc_ready) {
LOG_ERR("IPC service is not ready");
return WIFI_IPC_STATUS_BUSYQ_NOTREADY;
}
int ret = ipc_service_send(&busyq->ipc_ep, data, sizeof(*data));
if (ret == -ENOMEM) {
LOG_ERR("No space in the buffer");
/* No space in the buffer */
return WIFI_IPC_STATUS_BUSYQ_FULL;
} else if (ret < 0) {
LOG_ERR("Critical IPC failure: %d", ret);
/* Critical IPC failure */
return WIFI_IPC_STATUS_BUSYQ_CRITICAL_ERR;
}
if (context->free_q != NULL) {
/* Free up global address pointer from the free queue */
uint32_t data_out;
return (spsc32_pop(context->free_q, &data_out) == true
? (*data == data_out ? WIFI_IPC_STATUS_OK
: WIFI_IPC_STATUS_FREEQ_INVALID)
: WIFI_IPC_STATUS_FREEQ_EMPTY);
}
return WIFI_IPC_STATUS_OK;
}
wifi_ipc_status_t wifi_ipc_host_cmd_init(wifi_ipc_t *context, uint32_t addr_freeq)
{
context->free_q = (void *)addr_freeq;
return WIFI_IPC_STATUS_OK;
}
wifi_ipc_status_t wifi_ipc_host_event_init(wifi_ipc_t *context, uint32_t addr_freeq)
{
context->free_q = (void *)addr_freeq;
return WIFI_IPC_STATUS_OK;
}
wifi_ipc_status_t wifi_ipc_host_cmd_get(wifi_ipc_t *context, uint32_t *data)
{
return wifi_ipc_freeq_get(context, data);
}
wifi_ipc_status_t wifi_ipc_host_cmd_send(wifi_ipc_t *context, uint32_t *data)
{
return wifi_ipc_busyq_send(context, data);
}
wifi_ipc_status_t wifi_ipc_host_cmd_send_memcpy(wifi_ipc_t *context, const void *msg,
size_t len)
{
int ret;
uint32_t gdram_addr;
ret = wifi_ipc_host_cmd_get(context, &gdram_addr);
if (ret != WIFI_IPC_STATUS_OK) {
LOG_ERR("Failed to get command location from free queue: %d", ret);
return ret;
}
memcpy((void *)gdram_addr, msg, len);
return wifi_ipc_host_cmd_send(context, &gdram_addr);
}
wifi_ipc_status_t wifi_ipc_host_tx_send(wifi_ipc_t *context, const void *msg)
{
return wifi_ipc_host_cmd_send(context, (uint32_t *)&msg);
}

View file

@ -0,0 +1,260 @@
/*
* Copyright (c) 2025 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef IPC_SERVICE_H
#define IPC_SERVICE_H
#include <zephyr/device.h>
#include <zephyr/ipc/ipc_service.h>
#include "spsc_qm.h"
#define GET_IPC_INSTANCE(dev) (dev)
typedef struct device ipc_device_wrapper_t;
#include <stdint.h>
#include <stdbool.h>
/*
* Must be large enough to contain the internal struct (spsc_pbuf struct) and
* at least two bytes of data (one is reserved for written message length)
*/
#define _MIN_SPSC_SIZE (sizeof(spsc_queue_t) + sizeof(uint32_t))
/*
* TODO: Unsure why some additional bytes are needed for overhead.
*/
#define WIFI_IPC_GET_SPSC_SIZE(x) (_MIN_SPSC_SIZE + 12 + (x))
/* 4 x cmd location 32-bit pointers of 400 bytes each */
#define WIFI_IPC_CMD_SIZE 400
#define WIFI_IPC_CMD_NUM 4
#define WIFI_IPC_CMD_SPSC_SIZE WIFI_IPC_GET_SPSC_SIZE(WIFI_IPC_CMD_NUM * sizeof(uint32_t))
/* 7 x event location 32-bit pointers of 1000 bytes each */
#define WIFI_IPC_EVENT_SIZE 1000
#define WIFI_IPC_EVENT_NUM 7
#define WIFI_IPC_EVENT_SPSC_SIZE WIFI_IPC_GET_SPSC_SIZE(WIFI_IPC_EVENT_NUM * sizeof(uint32_t))
/**
* @enum wifi_ipc_status_t
* @brief Status codes for the Wi-Fi IPC service.
*
* This enumeration defines various status codes that represent
* the state or result of operations in the Wi-Fi IPC service.
*/
typedef enum {
/** Status indicating the operation completed successfully. */
WIFI_IPC_STATUS_OK = 0,
/** Error indicating failure to register IPC service for the Busy queue. */
WIFI_IPC_STATUS_INIT_ERR,
/** Error indicating that the Free queue has not been initialized. */
WIFI_IPC_STATUS_FREEQ_UNINIT_ERR,
/** Status indicating that the Free queue is empty. */
WIFI_IPC_STATUS_FREEQ_EMPTY,
/** Error indicating that the value passed to wifi_ipc_busyq_send()
* does not match the value from the Free queue.
*/
WIFI_IPC_STATUS_FREEQ_INVALID,
/** Status indicating that the Free queue is full. */
WIFI_IPC_STATUS_FREEQ_FULL,
/** Error indicating that the IPC service for the Busy queue connection
* has not been established.
*/
WIFI_IPC_STATUS_BUSYQ_NOTREADY,
/** Status indicating that the Busy queue is full. */
WIFI_IPC_STATUS_BUSYQ_FULL,
/** Critical error indicating an IPC transfer failure. This should never happen. */
WIFI_IPC_STATUS_BUSYQ_CRITICAL_ERR,
} wifi_ipc_status_t;
/**
* Structure to hold context information for busy queue.
*/
typedef struct {
const ipc_device_wrapper_t *ipc_inst;
struct ipc_ept ipc_ep;
struct ipc_ept_cfg ipc_ep_cfg;
void (*recv_cb)(const void *data, const void *priv);
const void *priv;
volatile bool ipc_ready;
} wifi_ipc_busyq_t;
/**
* Top-level structure to hold context information for sending data between RPU
* and the Host.
*/
typedef struct {
spsc_queue_t *free_q;
wifi_ipc_busyq_t busy_q;
wifi_ipc_busyq_t *linked_ipc;
} wifi_ipc_t;
/**
* Performs memory-to-memory copy via MVDMA.
*
* Enters low power state by issuing wait-for-interrupt (WFI) while waiting for
* MVDMA event to complete.
*
* @param[in] p_dest : Pointer to destination memory to be copied to.
* @param[in] p_src : Pointer to source memory to be copied from.
* @param[in] len : Number of bytes to be copied.
*/
void wifi_ipc_mvdma_copy(void *p_dest, const void *p_src, size_t len);
/**
* Bind either TX or RX context to one IPC service. This utilises the half-duplex
* capability of the IPC service.
*
* @param[in] p_context : Pointer to wifi_ipc_t struct.
* @param[in] ipc_inst : Pointer to the IPC instance.
* @param[in] rx_cb : If binding RX context, this is the callback function.
* Leave NULL if binding to a TX.
* @param[in] priv : If binding RX context, this is the private data to be passed
* along with the callback function.
* Leave NULL if binding to a TX.
* @return : wifi_ipc_status_ok if successful, otherwise wifi_ipc_status_init_err.
*/
wifi_ipc_status_t wifi_ipc_bind_ipc_service(wifi_ipc_t *p_context,
const ipc_device_wrapper_t *ipc_inst,
void (*rx_cb)(void *data, void *priv), void *priv);
/**
* Bind both TX and RX contexts to a single IPC service. This utilises the
* full-duplex capability of the IPC service.
*
* @param[in] p_tx : Pointer to wifi_ipc_t struct to bind IPC TX mailbox to.
* @param[in] p_rx : Pointer to wifi_ipc_t struct to bind IPC RX mailbox to.
* @param[in] ipc_inst : Pointer to the IPC instance.
* @param[in] rx_cb : Callback function to bind to data in received from RX mailbox.
* @param[in] priv : Private data to the callback function.
* @return : wifi_ipc_status_ok if successful, otherwise wifi_ipc_status_init_err.
*/
wifi_ipc_status_t wifi_ipc_bind_ipc_service_tx_rx(wifi_ipc_t *p_tx, wifi_ipc_t *p_rx,
const ipc_device_wrapper_t *ipc_inst,
void (*rx_cb)(void *data, void *priv),
void *priv);
/**
* Get data from the free queue.
*
* @param[in] p_context : Pointer to wifi_ipc_t struct.
* @param[out] data : Pointer to the data to read to.
* @return : wifi_ipc_status_ok if successful, otherwise wifi_ipc_status_freeq_empty.
*/
wifi_ipc_status_t wifi_ipc_freeq_get(wifi_ipc_t *p_context, uint32_t *data);
/**
* Send data to the free queue.
*
* @param[in] p_context : Pointer to wifi_ipc_t struct.
* @param[in] data : 32-bit data to send.
* @return : wifi_ipc_status_ok if successful, otherwise wifi_ipc_status_freeq_full.
*/
wifi_ipc_status_t wifi_ipc_freeq_send(wifi_ipc_t *p_context, uint32_t data);
/**
* Send data to the busy queue over IPC service, and pop the same data from the
* free queue.
*
* @param[in] p_context : Pointer to wifi_ipc_t struct.
* @param[in] data : Pointer to the data to send to.
* @return : wifi_ipc_status_ok if successful, otherwise one of the following:
* - wifi_ipc_status_busyq_notready
* - wifi_ipc_status_busyq_full
* - wifi_ipc_status_busyq_critical_err
* - wifi_ipc_status_freeq_invalid
* - wifi_ipc_status_freeq_empty
*/
wifi_ipc_status_t wifi_ipc_busyq_send(wifi_ipc_t *p_context, uint32_t *data);
/**
* Prepares and initialises the Host for sending a command to RPU.
*
* The free queue points to the already allocated free queue from the RPU.
*
* The busy queue using IPC service must be initialised using @see wifi_ipc_bind_ipc_service()
* or @see wifi_ipc_bind_ipc_service_tx_rx().
*
* @param[in] p_context : Pointer to wifi_ipc_t struct.
* @param[in] addr_freeq : Address of the allocated free queue.
* @return : wifi_ipc_status_ok if successful.
*/
wifi_ipc_status_t wifi_ipc_host_cmd_init(wifi_ipc_t *p_context, uint32_t addr_freeq);
/**
* Prepares and initialises the Host for receiving an event from RPU.
*
* The free queue points to the already allocated free queue from the RPU.
*
* The busy queue using IPC service must be initialised using @see wifi_ipc_bind_ipc_service()
* or @see wifi_ipc_bind_ipc_service_tx_rx().
*
* @param[in] p_context : Pointer to wifi_ipc_t struct.
* @param[in] addr_freeq : Address of the allocated SPSC free queue.
* @return : wifi_ipc_status_ok if successful.
*/
wifi_ipc_status_t wifi_ipc_host_event_init(wifi_ipc_t *p_context, uint32_t addr_freeq);
/**
* Get a command location from the free queue.
*
* @param[in] p_context : Pointer to wifi_ipc_t struct.
* @param[out] p_data : Pointer to data to write event location to.
* @return : wifi_ipc_status_ok if successful, otherwise wifi_ipc_status_freeq_empty.
*/
wifi_ipc_status_t wifi_ipc_host_cmd_get(wifi_ipc_t *p_context, uint32_t *p_data);
/**
* Send an event location pointer to the Host and frees up the event location
* pointer from the free queue.
*
* @param[in] p_context : Pointer to wifi_ipc_t struct.
* @param[in] p_data : Pointer to command location to be sent.
* @return : wifi_ipc_status_ok if successful, otherwise one of the following
* - wifi_ipc_status_busyq_notready
* - wifi_ipc_status_busyq_full
* - wifi_ipc_status_busyq_critical_err
* - wifi_ipc_status_freeq_invalid
* - wifi_ipc_status_freeq_empty
*/
wifi_ipc_status_t wifi_ipc_host_cmd_send(wifi_ipc_t *p_context, uint32_t *p_data);
/**
* Send a command from the Host to RPU using standard memcpy.
*
* 1. Retrieves an address pointer of Packet RAM from the free queue.
* 2. Copies local message to the retrieved address pointer via memcpy.
* 3. Sends the address pointer to the busy queue via IPC service.
* 4. Upon successful transmission, removes the address pointer the free queue.
*
* @param[in] p_context : Pointer to wifi_ipc_t struct.
* @param[in] p_msg : Pointer the local message to be copied to Packet RAM via memcpy.
* @param[in] len : Length of the local message in bytes.
* @return : wifi_ipc_status_ok if send is successful, otherwise one of
* status code from wifi_ipc_status_t will be returned.
*/
wifi_ipc_status_t wifi_ipc_host_cmd_send_memcpy(wifi_ipc_t *p_context, const void *p_msg,
size_t len);
/**
* Send a tx data pointer from the Host to RPU and raises RPU interrupt.
*
* @param[in] p_context : Pointer to wifi_ipc_t struct.
* @param[in] p_msg : Pointer to message.
* @return : wifi_ipc_status_ok if send is successful, otherwise one of
* status code from wifi_ipc_status_t will be returned.
*/
wifi_ipc_status_t wifi_ipc_host_tx_send(wifi_ipc_t *p_context, const void *p_msg);
#endif /* IPC_SERVICE_H */

View file

@ -0,0 +1,84 @@
/*
* Copyright (c) 2025 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @brief File containing API definitions for the
* SPSC queue management layer of the nRF71 Wi-Fi driver.
*/
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(wifi_nrf_bus, CONFIG_WIFI_NRF70_BUSLIB_LOG_LEVEL);
#include "spsc_qm.h"
spsc_queue_t *spsc32_init(uint32_t address, size_t size)
{
return spsc_pbuf_init((void *)address, size, 0);
}
bool spsc32_push(spsc_queue_t *queue, uint32_t value)
{
char *pbuf;
uint8_t len = sizeof(uint32_t);
if (spsc_pbuf_alloc(queue, len, &pbuf) != len) {
LOG_ERR("%s: Failed to allocate buffer", __func__);
return false;
}
memcpy(pbuf, &value, len);
spsc_pbuf_commit(queue, len);
return true;
}
bool spsc32_pop(spsc_queue_t *queue, uint32_t *out_value)
{
char *buf;
uint16_t plen = spsc_pbuf_claim(queue, &buf);
if (plen == 0) {
LOG_ERR("%s: Failed to claim buffer", __func__);
return false;
}
spsc_pbuf_free(queue, plen);
*out_value = *((uint32_t *)buf);
return true;
}
bool spsc32_read_head(spsc_queue_t *queue, uint32_t *out_value)
{
char *buf;
uint16_t plen = spsc_pbuf_claim(queue, &buf);
if (plen == 0) {
LOG_ERR("%s: Failed to claim buffer", __func__);
return false;
}
*out_value = *((uint32_t *)buf);
return true;
}
bool spsc32_is_empty(spsc_queue_t *queue)
{
char *buf;
return spsc_pbuf_claim(queue, &buf) == 0;
}
bool spsc32_is_full(spsc_queue_t *queue)
{
char *pbuf;
uint8_t len = sizeof(uint32_t);
return spsc_pbuf_alloc(queue, len, &pbuf) != len;
}

View file

@ -0,0 +1,79 @@
/*
* Copyright (c) 2025 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @brief File containing API definitions for the
* SPSC queue management layer of the nRF71 Wi-Fi driver.
* SPSC Queue Manager API for handling 32-bit values.
*
* The Queue Manager API for Single-Producer, Single-Consumer (SPSC) queue. This
* API allows queues to be allocated, pushed and popped.
*/
#ifndef SPSC_QM_H
#define SPSC_QM_H
#include <zephyr/sys/spsc_pbuf.h>
#include <stdint.h>
#include <stdbool.h>
typedef struct spsc_pbuf spsc_queue_t;
/**
* Initialise and allocate SPSC queue.
*
* @param[in] address : Address to allocate.
* @param[in] size : Size in bytes to allocate.
* @return : SPSC packet queue.
*/
spsc_queue_t *spsc32_init(uint32_t address, size_t size);
/**
* Push a value onto the tail of a queue.
*
* @param[in] pb : Pointer to SPSC packet queue.
* @param[in] value : The value to push to the queue.
* @return : true if push is successful, false otherwise.
*/
bool spsc32_push(spsc_queue_t *pb, uint32_t value);
/**
* Pop a value from the head of a queue and return it.
*
* @param[in] pb : Pointer to SPSC packet queue.
* @param[out] out_value : Pointer to the value to pop to.
* @return : true if pop is successful, false otherwise.
*/
bool spsc32_pop(spsc_queue_t *pb, uint32_t *out_value);
/**
* Return a value at the head of a queue without popping it.
*
* @param[in] pb : Pointer to SPSC packet queue.
* @param[out] out_value : Pointer to value to read from the head of the queue.
* @return : true if read is successful, false otherwise.
*/
bool spsc32_read_head(spsc_queue_t *pb, uint32_t *out_value);
/**
* Test whether a queue is empty.
*
* @param[in] pb : Pointer to SPSC packet queue.
* @return : true if the queue is empty, false otherwise.
*/
bool spsc32_is_empty(spsc_queue_t *pb);
/**
* Test whether a queue is full.
*
* @param[in] pb : Pointer to SPSC packet queue.
* @return : true if the queue is full, false otherwise.
*/
bool spsc32_is_full(spsc_queue_t *pb);
#endif /* SPSC_QM_H */

View file

@ -15,5 +15,13 @@ zephyr_library_sources(
timer.c
work.c
)
zephyr_include_directories_ifdef(CONFIG_NRF71_ON_IPC
${CMAKE_CURRENT_LIST_DIR}/../bus/
${CMAKE_CURRENT_LIST_DIR}/../hw_if/hal/inc
)
zephyr_library_compile_definitions_ifdef(CONFIG_NRF71_ON_IPC
NRF71_ON_IPC
)
zephyr_library_link_libraries(nrf-wifi-osal)

View file

@ -18,14 +18,19 @@
#include <zephyr/drivers/gpio.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/__assert.h>
#ifdef CONFIG_NRF71_ON_IPC
#include "ipc_if.h"
#else
#include <zephyr/drivers/wifi/nrf_wifi/bus/rpu_hw_if.h>
#include <zephyr/drivers/wifi/nrf_wifi/bus/qspi_if.h>
#endif /* CONFIG_NRF71_ON_IPC */
#include <zephyr/sys/math_extras.h>
#include "shim.h"
#include "work.h"
#include "timer.h"
#include "osal_ops.h"
#include "common/hal_structs_common.h"
LOG_MODULE_REGISTER(wifi_nrf, CONFIG_WIFI_NRF70_LOG_LEVEL);
#if defined(CONFIG_NOCACHE_MEMORY)
@ -122,6 +127,7 @@ static int zep_shim_mem_cmp(const void *addr1,
return memcmp(addr1, addr2, size);
}
#ifndef CONFIG_NRF71_ON_IPC
static unsigned int zep_shim_qspi_read_reg32(void *priv, unsigned long addr)
{
unsigned int val;
@ -174,6 +180,7 @@ static void zep_shim_qspi_cpy_to(void *priv, unsigned long addr, const void *src
dev->write(addr, src, count_aligned);
}
#endif /* !CONFIG_NRF71_ON_IPC */
static void *zep_shim_spinlock_alloc(void)
{
@ -807,14 +814,56 @@ static enum nrf_wifi_status zep_shim_bus_qspi_dev_init(void *os_qspi_dev_ctx)
static void zep_shim_bus_qspi_dev_deinit(void *priv)
{
struct zep_shim_bus_qspi_priv *qspi_priv = priv;
#ifndef CONFIG_NRF71_ON_IPC
volatile struct qspi_dev *dev = qspi_priv->qspi_dev;
#else
volatile struct rpu_dev *dev = qspi_priv->qspi_dev;
#endif /* !CONFIG_NRF71_ON_IPC */
dev->deinit();
}
#ifdef CONFIG_NRF71_ON_IPC
static int ipc_send_msg(unsigned int msg_type, void *msg, unsigned int len)
{
enum nrf_wifi_status status = NRF_WIFI_STATUS_FAIL;
struct rpu_dev *dev = rpu_dev();
int ret;
ipc_ctx_t ctx;
switch (msg_type) {
case NRF_WIFI_HAL_MSG_TYPE_CMD_CTRL:
ctx.inst = IPC_INSTANCE_CMD_CTRL;
ctx.ept = IPC_EPT_UMAC;
break;
case NRF_WIFI_HAL_MSG_TYPE_CMD_DATA_TX:
ctx.inst = IPC_INSTANCE_CMD_TX;
ctx.ept = IPC_EPT_UMAC;
break;
case NRF_WIFI_HAL_MSG_TYPE_CMD_DATA_RX:
ctx.inst = IPC_INSTANCE_RX;
ctx.ept = IPC_EPT_LMAC;
break;
default:
nrf_wifi_osal_log_err("%s: Invalid msg_type (%d)", __func__, msg_type);
goto out;
};
ret = dev->send(ctx, msg, len);
if (ret < 0) {
nrf_wifi_osal_log_err("%s: Sending message to RPU failed\n", __func__);
goto out;
}
status = NRF_WIFI_STATUS_SUCCESS;
out:
return status;
}
#endif /* CONFIG_NRF71_ON_IPC */
static void *zep_shim_bus_qspi_dev_add(void *os_qspi_priv, void *osal_qspi_dev_ctx)
{
struct zep_shim_bus_qspi_priv *zep_qspi_priv = os_qspi_priv;
#ifndef CONFIG_NRF71_ON_IPC
struct qspi_dev *dev = qspi_dev();
int ret;
enum nrf_wifi_status status;
@ -836,6 +885,11 @@ static void *zep_shim_bus_qspi_dev_add(void *os_qspi_priv, void *osal_qspi_dev_c
LOG_ERR("%s: RPU enable failed with error %d", __func__, ret);
return NULL;
}
#else
struct rpu_dev *dev = rpu_dev();
dev->init();
#endif /* !CONFIG_NRF71_ON_IPC */
zep_qspi_priv->qspi_dev = dev;
zep_qspi_priv->dev_added = true;
@ -849,8 +903,10 @@ static void zep_shim_bus_qspi_dev_rem(void *priv)
ARG_UNUSED(dev);
#ifndef CONFIG_NRF71_ON_IPC
/* TODO: Make qspi_dev a dynamic instance and remove it here */
rpu_disable();
#endif /* !CONFIG_NRF71_ON_IPC */
}
static void *zep_shim_bus_qspi_init(void)
@ -908,6 +964,7 @@ static void zep_shim_bus_qspi_dev_host_map_get(void *os_qspi_dev_ctx,
host_map->addr = 0;
}
#ifndef CONFIG_NRF71_ON_IPC
static void irq_work_handler(struct k_work *work)
{
int ret = 0;
@ -940,6 +997,8 @@ static void zep_shim_irq_handler(const struct device *dev, struct gpio_callback
k_work_schedule_for_queue(&zep_wifi_intr_q, &intr_priv->work, K_NO_WAIT);
}
#endif /* !CONFIG_NRF71_ON_IPC */
static enum nrf_wifi_status zep_shim_bus_qspi_intr_reg(void *os_dev_ctx, void *callbk_data,
int (*callbk_fn)(void *callbk_data))
{
@ -948,6 +1007,14 @@ static enum nrf_wifi_status zep_shim_bus_qspi_intr_reg(void *os_dev_ctx, void *c
ARG_UNUSED(os_dev_ctx);
#ifdef CONFIG_NRF71_ON_IPC
ret = ipc_register_rx_cb(callbk_fn, callbk_data);
if (ret) {
LOG_ERR("%s: ipc_register_rx_cb failed\n", __func__);
goto out;
}
status = NRF_WIFI_STATUS_SUCCESS;
#else
intr_priv = zep_shim_mem_zalloc(sizeof(*intr_priv));
if (!intr_priv) {
@ -970,18 +1037,20 @@ static enum nrf_wifi_status zep_shim_bus_qspi_intr_reg(void *os_dev_ctx, void *c
}
status = NRF_WIFI_STATUS_SUCCESS;
#endif /* CONFIG_NRF71_ON_IPC */
out:
return status;
}
static void zep_shim_bus_qspi_intr_unreg(void *os_qspi_dev_ctx)
{
#ifndef CONFIG_NRF71_ON_IPC
struct k_work_sync sync;
int ret;
#endif /* !CONFIG_NRF71_ON_IPC */
ARG_UNUSED(os_qspi_dev_ctx);
#ifndef CONFIG_NRF71_ON_IPC
ret = rpu_irq_remove(&intr_priv->gpio_cb_data);
if (ret) {
LOG_ERR("%s: rpu_irq_remove failed", __func__);
@ -992,6 +1061,7 @@ static void zep_shim_bus_qspi_intr_unreg(void *os_qspi_dev_ctx)
zep_shim_mem_free(intr_priv);
intr_priv = NULL;
#endif /*! CONFIG_NRF71_ON_IPC */
}
#ifdef CONFIG_NRF_WIFI_LOW_POWER
@ -1072,12 +1142,12 @@ const struct nrf_wifi_osal_ops nrf_wifi_os_zep_ops = {
.mem_cpy = zep_shim_mem_cpy,
.mem_set = zep_shim_mem_set,
.mem_cmp = zep_shim_mem_cmp,
#ifndef CONFIG_NRF71_ON_IPC
.qspi_read_reg32 = zep_shim_qspi_read_reg32,
.qspi_write_reg32 = zep_shim_qspi_write_reg32,
.qspi_cpy_from = zep_shim_qspi_cpy_from,
.qspi_cpy_to = zep_shim_qspi_cpy_to,
#endif /* CONFIG_NRF71_ON_IPC */
.spinlock_alloc = zep_shim_spinlock_alloc,
.spinlock_free = zep_shim_spinlock_free,
.spinlock_init = zep_shim_spinlock_init,
@ -1157,7 +1227,9 @@ const struct nrf_wifi_osal_ops nrf_wifi_os_zep_ops = {
.bus_qspi_ps_wake = zep_shim_bus_qspi_ps_wake,
.bus_qspi_ps_status = zep_shim_bus_qspi_ps_status,
#endif /* CONFIG_NRF_WIFI_LOW_POWER */
.assert = zep_shim_assert,
.strlen = zep_shim_strlen,
#ifdef CONFIG_NRF71_ON_IPC
.ipc_send_msg = ipc_send_msg,
#endif /* CONFIG_NRF71_ON_IPC */
};