diff --git a/hw/bsp/nordic_pca10095/boot-nrf5340.ld b/hw/bsp/nordic_pca10095/boot-nrf5340.ld index 1d60514633..839b1ef26b 100644 --- a/hw/bsp/nordic_pca10095/boot-nrf5340.ld +++ b/hw/bsp/nordic_pca10095/boot-nrf5340.ld @@ -20,6 +20,8 @@ MEMORY { FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 0x8000 RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x80000 + sram_ipc0_tx (rw) : ORIGIN = 0x20070000, LENGTH = 0x4000 + sram_ipc0_rx (rw) : ORIGIN = 0x20074000, LENGTH = 0x4000 } /* The bootloader does not contain an image header */ diff --git a/hw/bsp/nordic_pca10095/nrf5340.ld b/hw/bsp/nordic_pca10095/nrf5340.ld index 6ee5122a54..82e41d1cad 100644 --- a/hw/bsp/nordic_pca10095/nrf5340.ld +++ b/hw/bsp/nordic_pca10095/nrf5340.ld @@ -20,6 +20,8 @@ MEMORY { FLASH (rx) : ORIGIN = 0x0000c000, LENGTH = 0x76000 RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x80000 + sram_ipc0_tx (rw) : ORIGIN = 0x20070000, LENGTH = 0x4000 + sram_ipc0_rx (rw) : ORIGIN = 0x20074000, LENGTH = 0x4000 } /* This linker script is used for images and thus contains an image header */ diff --git a/hw/bsp/nordic_pca10095/syscfg.yml b/hw/bsp/nordic_pca10095/syscfg.yml index 33292f7e8f..50dfefb58f 100644 --- a/hw/bsp/nordic_pca10095/syscfg.yml +++ b/hw/bsp/nordic_pca10095/syscfg.yml @@ -108,15 +108,30 @@ syscfg.vals.BLE_TRANSPORT: BLE_TRANSPORT_LL: nrf5340 syscfg.vals.BSP_NRF5340_NET_ENABLE: - BSP_NRF5340_NET_FLASH_ENABLE: 1 BLE_HCI_VS: 1 +syscfg.vals.'BSP_NRF5340_NET_ENABLE && BLE_TRANSPORT_LL != "ipc"': + BSP_NRF5340_NET_FLASH_ENABLE: 1 + syscfg.vals.BSP_NRF5340_NET_FLASH_ENABLE: IPC_NRF5340_CHANNELS: 4 syscfg.vals.IPC_NRF5340_FLASH_CLIENT: HAL_FLASH_MAX_DEVICE_COUNT: 3 +syscfg.vals.'BLE_TRANSPORT_LL=="ipc" || BLE_TRANSPORT_IPC_BACKEND=="icbmsg"': + # The APP core have the same values but switched directions + BLE_TRANSPORT_IPC_TX_CHANNEL: 0 + BLE_TRANSPORT_IPC_RX_CHANNEL: 1 + IPC_SYNC_TX_CHANNEL: 0 + IPC_SYNC_RX_CHANNEL: 1 + IPC_ICBMSG_TX_REGION_NAME: "\".ipc0_tx\"" + IPC_ICBMSG_RX_REGION_NAME: "\".ipc0_rx\"" + IPC_ICBMSG_NUM_TX_BLOCKS: 16 + IPC_ICBMSG_NUM_RX_BLOCKS: 24 + IPC_ICBMSG_TX_REGION_SIZE: 0x4000 + IPC_ICBMSG_RX_REGION_SIZE: 0x4000 + syscfg.restrictions.BSP_NRF5340_NET_FLASH_ENABLE: - 'IPC_NRF5340_CHANNELS >= 4' diff --git a/hw/bsp/nordic_pca10095_net/boot-nrf5340_net.ld b/hw/bsp/nordic_pca10095_net/boot-nrf5340_net.ld index 8a2a152fca..629cbb360a 100644 --- a/hw/bsp/nordic_pca10095_net/boot-nrf5340_net.ld +++ b/hw/bsp/nordic_pca10095_net/boot-nrf5340_net.ld @@ -21,6 +21,8 @@ MEMORY FLASH (rx) : ORIGIN = 0x01000000, LENGTH = 0x4000 RAM (rwx) : ORIGIN = 0x21000000, LENGTH = 0x10000 IPC (rw) : ORIGIN = 0x20000400, LENGTH = 0x400 + sram_ipc0_tx (rw) : ORIGIN = 0x20070000, LENGTH = 0x4000 + sram_ipc0_rx (rw) : ORIGIN = 0x20074000, LENGTH = 0x4000 } /* The bootloader does not contain an image header */ diff --git a/hw/bsp/nordic_pca10095_net/nrf5340_net.ld b/hw/bsp/nordic_pca10095_net/nrf5340_net.ld index 91bfe1c497..b28ab837ff 100644 --- a/hw/bsp/nordic_pca10095_net/nrf5340_net.ld +++ b/hw/bsp/nordic_pca10095_net/nrf5340_net.ld @@ -21,6 +21,8 @@ MEMORY FLASH (rx) : ORIGIN = 0x01008000, LENGTH = 0x30000 RAM (rwx) : ORIGIN = 0x21000000, LENGTH = 0x10000 IPC (rw) : ORIGIN = 0x20000400, LENGTH = 0x400 + sram_ipc0_tx (rw) : ORIGIN = 0x20070000, LENGTH = 0x4000 + sram_ipc0_rx (rw) : ORIGIN = 0x20074000, LENGTH = 0x4000 } /* This linker script is used for images and thus contains an image header */ diff --git a/hw/bsp/nordic_pca10095_net/pkg.yml b/hw/bsp/nordic_pca10095_net/pkg.yml index 8e6ce7dc6f..e6ccea70ea 100644 --- a/hw/bsp/nordic_pca10095_net/pkg.yml +++ b/hw/bsp/nordic_pca10095_net/pkg.yml @@ -34,5 +34,7 @@ pkg.deps: - "@apache-mynewt-core/hw/scripts" - "@apache-mynewt-core/hw/mcu/nordic/nrf5340_net" - "@apache-mynewt-core/libc" + +pkg.deps.'BLE_TRANSPORT == 1 && BLE_TRANSPORT_HS != "ipc"': - "@apache-mynewt-core/sys/flash_map" - "@apache-mynewt-core/hw/drivers/flash/ipc_nrf5340_flash" diff --git a/hw/bsp/nordic_pca10095_net/syscfg.yml b/hw/bsp/nordic_pca10095_net/syscfg.yml index 915b502cfc..5f61e9d6f0 100644 --- a/hw/bsp/nordic_pca10095_net/syscfg.yml +++ b/hw/bsp/nordic_pca10095_net/syscfg.yml @@ -79,3 +79,16 @@ syscfg.vals.BLE_CONTROLLER: syscfg.vals.BLE_TRANSPORT: BLE_TRANSPORT_HS: nrf5340 + +syscfg.vals.'BLE_TRANSPORT_HS=="ipc" || BLE_TRANSPORT_IPC_BACKEND=="icbmsg"': + # The APP core have the same values but switched directions + BLE_TRANSPORT_IPC_TX_CHANNEL: 1 + BLE_TRANSPORT_IPC_RX_CHANNEL: 0 + IPC_SYNC_TX_CHANNEL: 1 + IPC_SYNC_RX_CHANNEL: 0 + IPC_ICBMSG_TX_REGION_NAME: "\".ipc0_rx\"" + IPC_ICBMSG_RX_REGION_NAME: "\".ipc0_tx\"" + IPC_ICBMSG_NUM_TX_BLOCKS: 24 + IPC_ICBMSG_NUM_RX_BLOCKS: 16 + IPC_ICBMSG_TX_REGION_SIZE: 0x4000 + IPC_ICBMSG_RX_REGION_SIZE: 0x4000 diff --git a/hw/drivers/ipc/icbmsg/include/icbmsg/icbmsg.h b/hw/drivers/ipc/icbmsg/include/icbmsg/icbmsg.h new file mode 100644 index 0000000000..a8fa2fbd7c --- /dev/null +++ b/hw/drivers/ipc/icbmsg/include/icbmsg/icbmsg.h @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef _HW_DRIVERS_IPC_ICBMSG_H +#define _HW_DRIVERS_IPC_ICBMSG_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct ipc_service_cb { + void (*received)(const void *data, size_t len, void *user_data); +}; + +struct ipc_ept_cfg { + const char *name; + struct ipc_service_cb cb; + void *user_data; + uint8_t tx_channel; + uint8_t rx_channel; +}; + +struct ipc_icmsg_buf { + size_t block_id; + uint8_t *data; + uint16_t len; +}; + +typedef void (*ipc_icbmsg_recv_cb)(uint8_t ipc_id, void *user_data); + +uint8_t ipc_icmsg_register_ept(uint8_t ipc_id, struct ipc_ept_cfg *cfg); + +int ipc_icbmsg_send(uint8_t ipc_id, uint8_t ept_addr, const void *data, uint16_t len); + +int ipc_icbmsg_send_buf(uint8_t ipc_id, uint8_t ept_addr, struct ipc_icmsg_buf *buf); + +int ipc_icbmsg_alloc_tx_buf(uint8_t ipc_id, struct ipc_icmsg_buf *buf, uint32_t size); + +uint8_t ipc_icsmsg_ept_ready(uint8_t ipc_id, uint8_t ept_addr); + +#ifdef __cplusplus +} +#endif + +#endif /* _HW_DRIVERS_IPC_ICBMSG_H */ diff --git a/hw/drivers/ipc/icbmsg/pkg.yml b/hw/drivers/ipc/icbmsg/pkg.yml new file mode 100644 index 0000000000..41f0b0a832 --- /dev/null +++ b/hw/drivers/ipc/icbmsg/pkg.yml @@ -0,0 +1,29 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +pkg.name: hw/drivers/ipc/icbmsg +pkg.description: IPC driver with icbmsg backend +pkg.author: "Apache Mynewt " +pkg.homepage: "http://mynewt.apache.org/" +pkg.keywords: + - ipc + +pkg.deps: + - "@apache-mynewt-core/hw/mcu/nordic" + - "@apache-mynewt-core/kernel/os" diff --git a/hw/drivers/ipc/icbmsg/src/icbmsg.c b/hw/drivers/ipc/icbmsg/src/icbmsg.c new file mode 100644 index 0000000000..28e05ad139 --- /dev/null +++ b/hw/drivers/ipc/icbmsg/src/icbmsg.c @@ -0,0 +1,974 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * ICBMsg backend. + * + * This is an IPC service backend that dynamically allocates buffers for data storage + * and uses ICMsg to send references to them. + * + * Shared memory organization + * -------------------------- + * + * Single channel (RX or TX) of the shared memory is divided into two areas: ICMsg area + * followed by Blocks area. ICMsg is used to send and receive short 3-byte messages. + * The ICMsg messages are queued inside the ICSM area using PBUF format. + * Blocks area is evenly divided into aligned blocks. Blocks are used to allocate + * buffers containing actual data. Data buffers can span multiple blocks. The first block + * starts with the size of the following data. + * + * +------------+-------------+ + * | ICMsg area | Blocks area | + * +------------+-------------+ + * _______/ \_________________________________________ + * / \ + * +-----------+-----------+-----------+-----------+- -+-----------+ + * | Block 0 | Block 1 | Block 2 | Block 3 | ... | Block N-1 | + * +-----------+-----------+-----------+-----------+- -+-----------+ + * _____/ \_____ + * / \ + * +------+--------------------------------+---------+ + * | size | data_buffer[size] ... | padding | + * +------+--------------------------------+---------+ + * + * The sender holds information about reserved blocks using bitarray and it is responsible + * for allocating and releasing the blocks. The receiver just tells the sender that it + * does not need a specific buffer anymore. + * + * Control messages + * ---------------- + * + * ICMsg is used to send and receive small 3-byte control messages. + * + * - Send data + * | MSG_DATA | endpoint address | block index | + * This message is used to send data buffer to specific endpoint. + * + * - Release data + * | MSG_RELEASE_DATA | 0 | block index | + * This message is a response to the "Send data" message and it is used to inform that + * specific buffer is not used anymore and can be released. Endpoint addresses does + * not matter here, so it is zero. + * + * - Bound endpoint + * | MSG_BOUND | endpoint address | block index | + * This message starts the bounding of the endpoint. The buffer contains a + * null-terminated endpoint name. + * + * - Release bound endpoint + * | MSG_RELEASE_BOUND | endpoint address | block index | + * This message is a response to the "Bound endpoint" message and it is used to inform + * that a specific buffer (starting at "block index") is not used anymore and + * a the endpoint is bounded and can now receive a data. + * + * Bounding endpoints + * ------------------ + * + * When ICMsg is bounded and user registers an endpoint on initiator side, the backend + * sends "Bound endpoint". Endpoint address is assigned by the initiator. When follower + * gets the message and user on follower side also registered the same endpoint, + * the backend calls "bound" callback and sends back "Release bound endpoint". + * The follower saves the endpoint address. The follower's endpoint is ready to send + * and receive data. When the initiator gets "Release bound endpoint" message or any + * data messages, it calls bound endpoint and it is ready to send data. + */ + + +#include +#include +#include +#include +#include +#include "utils.h" +#include "pbuf.h" + +#define TX_REGION MYNEWT_VAL(IPC_ICBMSG_TX_REGION_NAME) +#define RX_REGION MYNEWT_VAL(IPC_ICBMSG_RX_REGION_NAME) +#define TX_REGION_SIZE MYNEWT_VAL(IPC_ICBMSG_TX_REGION_SIZE) +#define RX_REGION_SIZE MYNEWT_VAL(IPC_ICBMSG_RX_REGION_SIZE) +__attribute__((section(TX_REGION))) static uint8_t ipc0_tx_start[TX_REGION_SIZE]; +__attribute__((section(RX_REGION))) static uint8_t ipc0_rx_start[RX_REGION_SIZE]; +#define ipc0_tx_end (ipc0_tx_start + sizeof(ipc0_tx_start)) +#define ipc0_rx_end (ipc0_rx_start + sizeof(ipc0_rx_start)) + +#define TX_BLOCKS_NUM MYNEWT_VAL(IPC_ICBMSG_NUM_TX_BLOCKS) +#define RX_BLOCKS_NUM MYNEWT_VAL(IPC_ICBMSG_NUM_RX_BLOCKS) +#define tx_BLOCKS_NUM TX_BLOCKS_NUM +#define rx_BLOCKS_NUM RX_BLOCKS_NUM + +/* A string used to synchronize cores */ +static const uint8_t magic[] = {0x45, 0x6d, 0x31, 0x6c, 0x31, 0x4b, + 0x30, 0x72, 0x6e, 0x33, 0x6c, 0x69, 0x34}; + + +#define PBUF_RX_READ_BUF_SIZE 128 +static uint8_t icmsg_rx_buffer[PBUF_RX_READ_BUF_SIZE] __attribute__((aligned(4))); + +/** Allowed number of endpoints within an IPC instance. */ +#define NUM_EPT MYNEWT_VAL(IPC_ICBMSG_NUM_EP) + +/** Special endpoint address indicating invalid (or empty) entry. */ +#define EPT_ADDR_INVALID 0xFF + +/** Special value for empty entry in bound message waiting table. */ +#define WAITING_BOUND_MSG_EMPTY 0xFFFF + +/** Size of the header (size field) of the block. */ +#define BLOCK_HEADER_SIZE (sizeof(struct block_header)) + +/** Flag indicating that ICMsg was bounded for this instance. */ +#define CONTROL_BOUNDED (1 << 31) + +/** Registered endpoints count mask in flags. */ +#define FLAG_EPT_COUNT_MASK 0xFFFF + +enum icmsg_state { + ICMSG_STATE_OFF, + ICMSG_STATE_BUSY, + ICMSG_STATE_READY, +}; + +enum msg_type { + /* Data message. */ + MSG_DATA = 0, + /* Release data buffer message. */ + MSG_RELEASE_DATA, + /* Endpoint bounding message. */ + MSG_BOUND, + /* Release endpoint bound message. + * This message is also indicator for the receiving side + * that the endpoint bounding was fully processed on + * the sender side. + */ + MSG_RELEASE_BOUND, +}; + +enum ept_bounding_state { + /* Endpoint in not configured (initial state). */ + EPT_UNCONFIGURED = 0, + /* Endpoint is configured, waiting for work queue to + * start bounding process. + */ + EPT_CONFIGURED, + /* Only on initiator. Bound message was send, + * but bound callback was not called yet, because + * we are waiting for any incoming messages. + */ + EPT_BOUNDING, + /* Bounding is done. Bound callback was called. */ + EPT_READY, +}; + +struct channel_config { + /* Address where the blocks start. */ + uint8_t *blocks_ptr; + /* Size of one block. */ + size_t block_size; + /* Number of blocks. */ + size_t block_count; +}; + +struct ipc_instance; + +struct ept_data { + struct ipc_instance *ipc; + struct ipc_ept_cfg *cfg; + /* Bounding state. */ + uint32_t state; + /* Endpoint address. */ + uint8_t addr; +}; + +struct ipc_instance { + /* Bit is set when TX block is in use */ + uint8_t tx_usage_bitmap[DIV_ROUND_UP(TX_BLOCKS_NUM, 8)]; + /* Bit is set, if the buffer starting at + * this block should be kept after exit + * from receive handler. + */ + uint8_t rx_usage_bitmap[DIV_ROUND_UP(RX_BLOCKS_NUM, 8)]; + /* TX ICSMsg Area packet buffer */ + struct pbuf tx_pb; + /* RX ICSMsg Area packet buffer */ + struct pbuf rx_pb; + /* TX channel config. */ + struct channel_config tx; + /* RX channel config. */ + struct channel_config rx; + /* Array of registered endpoints. */ + struct ept_data ept[NUM_EPT]; + uint16_t waiting_bound[NUM_EPT]; + /* Flags on higher bits, number of registered + * endpoints on lower. + */ + uint32_t flags; + /* This side has an initiator role. */ + bool is_initiator; + uint8_t state; + uint8_t ipc_id; +}; +struct block_header { + /* Size of the data field. It must be volatile, because + * when this value is read and validated for security + * reasons, compiler cannot generate code that reads + * it again after validation. + */ + volatile size_t size; +}; + +struct block_content { + struct block_header header; + /* Buffer data. */ + uint8_t data[]; +}; + +struct control_message { + /* Message type. */ + uint8_t msg_type; + /* Endpoint address or zero for MSG_RELEASE_DATA. */ + uint8_t ept_addr; + /* Block index to send or release. */ + uint8_t block_index; +}; + +static struct ipc_instance ipc_instances[1]; + +/** + * Calculate pointer to block from its index and channel configuration (RX or TX). + * No validation is performed. + */ +static struct block_content * +block_from_index(const struct channel_config *ch_conf, size_t block_index) +{ + return (struct block_content *)(ch_conf->blocks_ptr + block_index * ch_conf->block_size); +} + +/** + * Calculate pointer to data buffer from block index and channel configuration (RX or TX). + * Also validate the index and optionally the buffer size allocated on the this block. + */ +static uint8_t * +buffer_from_index_validate(const struct channel_config *ch_conf, + size_t block_index, size_t *size) +{ + size_t allocable_size; + size_t buffer_size; + uint8_t *end_ptr; + struct block_content *block; + + if (block_index >= ch_conf->block_count) { + /* Block index invalid */ + return NULL; + } + + block = block_from_index(ch_conf, block_index); + + if (size != NULL) { + allocable_size = ch_conf->block_count * ch_conf->block_size; + end_ptr = ch_conf->blocks_ptr + allocable_size; + buffer_size = block->header.size; + + if ((buffer_size > allocable_size - BLOCK_HEADER_SIZE) || + (&block->data[buffer_size] > end_ptr)) { + /* Block corrupted */ + return NULL; + } + + *size = buffer_size; + } + + return block->data; +} + +static int +find_zero_bits(uint8_t bitmap[], size_t total_bits, + size_t n, size_t *start_index) +{ + size_t zero_count = 0; + size_t first_zero_bit_pos; + size_t bit_id; + size_t byte_id; + uint8_t bit_pos; + + /* Find the first sequence of n consecutive 0 bits */ + for (bit_id = 0; bit_id < total_bits; ++bit_id) { + byte_id = bit_id / 8; + bit_pos = bit_id % 8; + + if ((bitmap[byte_id] & (1 << bit_pos)) == 0) { + if (zero_count == 0) { + first_zero_bit_pos = bit_id; + } + zero_count++; + + if (zero_count == n) { + *start_index = first_zero_bit_pos; + return 0; + } + } else { + zero_count = 0; + } + } + + return -1; +} + +static void +alloc_bitmap_bits(uint8_t bitmap[], size_t n, size_t start_index) +{ + for (size_t i = 0; i < n; ++i) { + size_t bit_index = start_index + i; + size_t byte_index = bit_index / 8; + size_t bit_pos = bit_index % 8; + + bitmap[byte_index] |= (1 << bit_pos); + } +} + +static void +free_bitmap_bits(uint8_t bitmap[], size_t n, size_t start_index) +{ + for (size_t i = 0; i < n; ++i) { + size_t bit_index = start_index + i; + size_t byte_index = bit_index / 8; + size_t bit_pos = bit_index % 8; + + bitmap[byte_index] &= ~(1 << bit_pos); + } +} + +static int +alloc_tx_buffer(struct ipc_instance *ipc, uint32_t size, uint8_t **buffer, + size_t *tx_block_index) +{ + int rc; + struct block_content *block; + size_t total_bits = sizeof(ipc->tx_usage_bitmap) * 8; + size_t total_size = size + BLOCK_HEADER_SIZE; + size_t num_blocks = DIV_ROUND_UP(total_size, ipc->tx.block_size); + + rc = find_zero_bits(ipc->tx_usage_bitmap, total_bits, + num_blocks, tx_block_index); + if (rc) { + return rc; + } + + alloc_bitmap_bits(ipc->tx_usage_bitmap, num_blocks, *tx_block_index); + + /* Get block pointer and adjust size to actually allocated space. */ + size = ipc->tx.block_size * num_blocks - BLOCK_HEADER_SIZE; + block = block_from_index(&ipc->tx, *tx_block_index); + block->header.size = size; + *buffer = block->data; + + return 0; +} + +/** + * Allocate buffer for transmission. + */ +int +ipc_icbmsg_alloc_tx_buf(uint8_t ipc_id, struct ipc_icmsg_buf *buf, uint32_t size) +{ + struct ipc_instance *ipc = &ipc_instances[ipc_id]; + + return alloc_tx_buffer(ipc, size, &buf->data, &buf->block_id); +} + +/** + * Release all or part of the blocks occupied by the buffer. + */ +static void +release_tx_blocks(struct ipc_instance *ipc, size_t release_index, size_t size) +{ + size_t num_blocks; + size_t total_size; + + /* Calculate number of blocks. */ + total_size = size + BLOCK_HEADER_SIZE; + num_blocks = DIV_ROUND_UP(total_size, ipc->tx.block_size); + + if (num_blocks > 0) { + free_bitmap_bits(ipc->tx_usage_bitmap, num_blocks, release_index); + } +} + +/** + * Send control message over ICMsg with mutex locked. Mutex must be locked because + * ICMsg may return error on concurrent invocations even when there is enough space + * in queue. + */ +static int +send_control_message(struct ept_data *ept, enum msg_type msg_type, uint8_t block_index) +{ + int ret; + struct ipc_instance *ipc = ept->ipc; + + const struct control_message message = { + .msg_type = (uint8_t)msg_type, + .ept_addr = ept->addr, + .block_index = block_index, + }; + + if (ipc->state != ICMSG_STATE_READY) { + return -EBUSY; + } + + ret = pbuf_write(&ipc->tx_pb, (const char *)&message, sizeof(message)); + + if (ret < 0) { + return ret; + } else if (ret < sizeof(message)) { + return -EBADMSG; + } + + ipc_signal(ept->cfg->tx_channel); + + return 0; +} + +/** + * Send data contained in specified block. + */ +static int +send_block(struct ept_data *ept, enum msg_type msg_type, size_t tx_block_index, size_t size) +{ + int r; + struct ipc_instance *ipc = ept->ipc; + struct block_content *block; + + block = block_from_index(&ipc->tx, tx_block_index); + block->header.size = size; + + r = send_control_message(ept, msg_type, tx_block_index); + if (r < 0) { + release_tx_blocks(ipc, tx_block_index, size); + } + + return r; +} + +/** + * Find endpoint that was registered with name that matches name + * contained in the endpoint bound message received from remote. + */ +static int +find_ept_by_name(struct ipc_instance *ipc, const char *name) +{ + const struct channel_config *rx_conf = &ipc->rx; + const char *buffer_end = (const char *)rx_conf->blocks_ptr + + rx_conf->block_count * rx_conf->block_size; + struct ept_data *ept; + size_t name_size; + size_t i; + + /* Requested name is in shared memory, so we have to assume that it + * can be corrupted. Extra care must be taken to avoid out of + * bounds reads. + */ + name_size = strnlen(name, buffer_end - name - 1) + 1; + + for (i = 0; i < NUM_EPT; i++) { + ept = &ipc->ept[i]; + if (ept->state == EPT_CONFIGURED && + strncmp(ept->cfg->name, name, name_size) == 0) { + return i; + } + } + + return -ENOENT; +} + +/** + * Send bound message on specified endpoint. + */ +static int +send_bound_message(struct ept_data *ept) +{ + int rc; + size_t msg_len; + uint8_t *buffer; + size_t tx_block_index; + + msg_len = strlen(ept->cfg->name) + 1; + rc = alloc_tx_buffer(ept->ipc, msg_len, &buffer, &tx_block_index); + if (rc >= 0) { + strcpy((char *)buffer, ept->cfg->name); + rc = send_block(ept, MSG_BOUND, tx_block_index, msg_len); + } + + return rc; +} + +/** + * Get endpoint from endpoint address. Also validates if the address is correct and + * endpoint is in correct state for receiving. + */ +static struct ept_data * +get_ept_and_rx_validate(struct ipc_instance *ipc, uint8_t ept_addr) +{ + struct ept_data *ept; + + if (ept_addr >= NUM_EPT) { + return NULL; + } + + ept = &ipc->ept[ept_addr]; + + if (ept->state == EPT_READY) { + /* Valid state - nothing to do. */ + } else if (ept->state == EPT_BOUNDING) { + /* The remote endpoint is ready */ + ept->state = EPT_READY; + } else { + return NULL; + } + + return ept; +} + +/** + * Data message received. + */ +static int +received_data(struct ipc_instance *ipc, size_t rx_block_index, uint8_t ept_addr) +{ + uint8_t *buffer; + struct ept_data *ept; + size_t size; + + /* Validate. */ + buffer = buffer_from_index_validate(&ipc->rx, rx_block_index, &size); + ept = get_ept_and_rx_validate(ipc, ept_addr); + if (buffer == NULL || ept == NULL) { + return -EINVAL; + } + + /* Call the endpoint callback. */ + ept->cfg->cb.received(buffer, size, ept->cfg->user_data); + + /* Release the buffer */ + send_control_message(ept, MSG_RELEASE_DATA, rx_block_index); + + return 0; +} + +/** + * Release data message received. + */ +static int +received_release_data(struct ipc_instance *ipc, size_t tx_block_index) +{ + size_t size; + uint8_t *buffer; + + /* Validate. */ + buffer = buffer_from_index_validate(&ipc->tx, tx_block_index, &size); + if (buffer == NULL) { + return -EINVAL; + } + + /* Release. */ + release_tx_blocks(ipc, tx_block_index, size); + + return 0; +} + +/** + * Bound endpoint message received. + */ +static int +received_bound(struct ipc_instance *ipc, size_t rx_block_index, uint8_t rem_ept_addr) +{ + int rc; + struct ept_data *ept; + uint8_t *buffer; + size_t size; + uint8_t ept_addr; + + /* Validate */ + buffer = buffer_from_index_validate(&ipc->rx, rx_block_index, &size); + if (buffer == NULL) { + /* Received invalid block index */ + return -1; + } + + rc = find_ept_by_name(ipc, (const char *)buffer); + if (rc < 0) { + assert(rem_ept_addr < NUM_EPT); + /* Put message to waiting array. */ + ipc->waiting_bound[rem_ept_addr] = rx_block_index; + + return 0; + } + + /* Set the remote endpoint address */ + ept_addr = rc; + assert(ept_addr < NUM_EPT); + ept = &ipc->ept[ept_addr]; + ept->addr = rem_ept_addr; + + if (ept->state != EPT_CONFIGURED) { + /* Unexpected bounding from remote on endpoint */ + return -EINVAL; + } + + ept->state = EPT_READY; + + send_control_message(ept, MSG_RELEASE_BOUND, rx_block_index); + + return 0; +} + +/** + * Handles ICMsg control messages received from the remote. + */ +static void +control_received(struct ipc_instance *ipc, const struct control_message *message) +{ + uint8_t ept_addr; + + ept_addr = message->ept_addr; + if (ept_addr >= NUM_EPT) { + return; + } + + switch (message->msg_type) { + case MSG_RELEASE_DATA: + received_release_data(ipc, message->block_index); + break; + case MSG_RELEASE_BOUND: + assert(received_release_data(ipc, message->block_index) == 0); + assert(get_ept_and_rx_validate(ipc, ept_addr) != NULL); + break; + case MSG_BOUND: + received_bound(ipc, message->block_index, ept_addr); + break; + case MSG_DATA: + received_data(ipc, message->block_index, ept_addr); + break; + default: + /* Silently ignore other messages types. They can be used in future + * protocol version. + */ + break; + } +} + +static void +process_ipc_data(uint8_t ipc_id) +{ + struct ipc_instance *ipc = &ipc_instances[ipc_id]; + int icmsg_len = pbuf_read(&ipc->rx_pb, (char *)icmsg_rx_buffer, sizeof(icmsg_rx_buffer)); + + if (ipc->state == ICMSG_STATE_READY) { + if (icmsg_len < sizeof(struct control_message)) { + return; + } + + control_received(ipc, (const struct control_message *)icmsg_rx_buffer); + } else { + /* After core restart, the first message in ICMsg area should be + * the magic string. + */ + assert(ipc->state == ICMSG_STATE_BUSY); + + /* Allow magic number longer than sizeof(magic) for future protocol version. */ + bool endpoint_invalid = (icmsg_len < sizeof(magic) || + memcmp(magic, icmsg_rx_buffer, sizeof(magic))); + + assert(!endpoint_invalid); + + /* Set flag that ICMsg is bounded and now, endpoint bounding may start. */ + ipc->flags |= CONTROL_BOUNDED; + ipc->state = ICMSG_STATE_READY; + } +} + +void +ipc_process_signal(uint8_t ipc_id) +{ + int icmsg_len; + struct ipc_instance *ipc = &ipc_instances[ipc_id]; + + do { + icmsg_len = pbuf_read(&ipc->rx_pb, NULL, 0); + if (icmsg_len <= 0) { + /* Unlikely, no data in buffer. */ + return; + } + + if (sizeof(icmsg_rx_buffer) < icmsg_len) { + return; + } + + process_ipc_data(ipc_id); + } while(true); +} + +/** + * Send to endpoint (without copy). + */ +int +ipc_icbmsg_send_buf(uint8_t ipc_id, uint8_t ept_addr, struct ipc_icmsg_buf *buf) +{ + struct ipc_instance *ipc = &ipc_instances[ipc_id]; + struct ept_data *ept = &ipc->ept[ept_addr]; + + /* Send data message. */ + return send_block(ept, MSG_DATA, buf->block_id, buf->len); +} + +/** + * Send to endpoint (with copy). + */ +int +ipc_icbmsg_send(uint8_t ipc_id, uint8_t ept_addr, const void *data, uint16_t len) +{ + int rc; + uint8_t *buffer; + struct ipc_instance *ipc = &ipc_instances[ipc_id]; + struct ept_data *ept = &ipc->ept[ept_addr]; + size_t tx_block_index; + + /* Allocate the buffer. */ + rc = alloc_tx_buffer(ipc, len, &buffer, &tx_block_index); + if (rc < 0) { + return rc; + } + + /* Copy data to allocated buffer. */ + memcpy(buffer, data, len); + + /* Send data message. */ + rc = send_block(ept, MSG_DATA, tx_block_index, len); + if (rc < 0) { + return rc; + } + + return 0; +} + +/** + * Register new endpoint + */ +uint8_t +ipc_icmsg_register_ept(uint8_t ipc_id, struct ipc_ept_cfg *cfg) +{ + int rc; + size_t i; + struct ipc_instance *ipc; + struct ept_data *ept = NULL; + uint8_t ept_addr; + + assert(ipc_id < sizeof(ipc_instances)); + ipc = &ipc_instances[ipc_id]; + + /* Reserve new endpoint index. */ + ept_addr = (ipc->flags++) & FLAG_EPT_COUNT_MASK; + assert(ept_addr < NUM_EPT); + + /* Add new endpoint. */ + ept = &ipc->ept[ept_addr]; + ept->ipc = ipc; + ept->state = EPT_CONFIGURED; + ept->cfg = cfg; + + if (ipc->is_initiator) { + ept->addr = ept_addr; + ept->state = EPT_BOUNDING; + + rc = send_bound_message(ept); + assert(rc == 0); + } else { + ept->addr = EPT_ADDR_INVALID; + + /* Respond to pending bounds */ + for (i = 0; i < NUM_EPT; ++i) { + if (ipc->waiting_bound[i] != WAITING_BOUND_MSG_EMPTY) { + received_bound(ipc, ipc->waiting_bound[i], i); + ipc->waiting_bound[i] = WAITING_BOUND_MSG_EMPTY; + } + } + } + + return ept_addr; +} + +/** + * Number of bytes per each ICMsg message. It is used to calculate size of ICMsg area. + */ +#define BYTES_PER_ICMSG_MESSAGE (ROUND_UP(sizeof(struct control_message), \ + sizeof(void *)) + PBUF_PACKET_LEN_SZ) + +/** + * Maximum ICMsg overhead. It is used to calculate size of ICMsg area. + */ +#define ICMSG_BUFFER_OVERHEAD(i) \ + (PBUF_HEADER_OVERHEAD(GET_CACHE_ALIGNMENT(i)) + 2 * BYTES_PER_ICMSG_MESSAGE) + +/** + * Returns required block alignment for instance "i". + */ +#define GET_CACHE_ALIGNMENT(i) (sizeof(uint32_t)) + +/** + * Calculates minimum size required for ICMsg region for specific number of local + * and remote blocks. The minimum size ensures that ICMsg queue is will never overflow + * because it can hold data message for each local block and release message + * for each remote block. + */ +#define GET_ICMSG_MIN_SIZE(i, local_blocks, remote_blocks) \ + (ICMSG_BUFFER_OVERHEAD(i) + BYTES_PER_ICMSG_MESSAGE * \ + (local_blocks + remote_blocks)) + +/** + * Calculate aligned block size by evenly dividing remaining space after removing + * the space for ICMsg. + */ +#define GET_BLOCK_SIZE(i, total_size, local_blocks, remote_blocks) ROUND_DOWN( \ + ((total_size) - GET_ICMSG_MIN_SIZE(i, (local_blocks), (remote_blocks))) / \ + (local_blocks), GET_CACHE_ALIGNMENT(i)) + +/** + * Calculate offset where area for blocks starts which is just after the ICMsg. + */ +#define GET_BLOCKS_OFFSET(i, total_size, local_blocks, remote_blocks) \ + ((total_size) - GET_BLOCK_SIZE(i, (total_size), (local_blocks), \ + (remote_blocks)) * (local_blocks)) + +/** + * Return shared memory start address aligned to block alignment and cache line. + */ +#define GET_MEM_ADDR_INST(i, direction) \ + ROUND_UP(ipc ## i ## _ ## direction ## _start, GET_CACHE_ALIGNMENT(i)) + +/** + * Return shared memory end address aligned to block alignment and cache line. + */ +#define GET_MEM_END_INST(i, direction) \ + ROUND_DOWN(ipc ## i ## _ ## direction ## _end, GET_CACHE_ALIGNMENT(i)) + +/** + * Return shared memory size aligned to block alignment and cache line. + */ +#define GET_MEM_SIZE_INST(i, direction) \ + (GET_MEM_END_INST(i, direction) - GET_MEM_ADDR_INST(i, direction)) + +/** + * Returns GET_ICMSG_SIZE, but for specific instance and direction. + * 'loc' and 'rem' parameters tells the direction. They can be either "tx, rx" + * or "rx, tx". + */ +#define GET_ICMSG_SIZE_INST(i, loc, rem) \ + GET_BLOCKS_OFFSET(i, \ + GET_MEM_SIZE_INST(i, loc), \ + loc ## _BLOCKS_NUM, \ + rem ## _BLOCKS_NUM) + +/** + * Returns address where area for blocks starts for specific instance and direction. + * 'loc' and 'rem' parameters tells the direction. They can be either "tx, rx" + * or "rx, tx". + */ +#define GET_BLOCKS_ADDR_INST(i, loc, rem) \ + GET_MEM_ADDR_INST(i, loc) + \ + GET_ICMSG_SIZE_INST(i, loc, rem) + +/** + * Returns block size for specific instance and direction. + * 'loc' and 'rem' parameters tells the direction. They can be either "tx, rx" + * or "rx, tx". + */ +#define GET_BLOCK_SIZE_INST(i, loc, rem) \ + GET_BLOCK_SIZE(i, \ + GET_MEM_SIZE_INST(i, loc), \ + loc ## _BLOCKS_NUM, \ + rem ## _BLOCKS_NUM) + +#define IPC_INSTANCE_INIT(i) (struct ipc_instance) { \ + .tx_pb = { \ + .cfg = PBUF_CFG_INIT(GET_MEM_ADDR_INST(i, tx), \ + GET_ICMSG_SIZE_INST(i, tx, rx), \ + GET_CACHE_ALIGNMENT(i)), \ + }, \ + .rx_pb = { \ + .cfg = PBUF_CFG_INIT(GET_MEM_ADDR_INST(i, rx), \ + GET_ICMSG_SIZE_INST(i, rx, tx), \ + GET_CACHE_ALIGNMENT(i)), \ + }, \ + .tx = { \ + .blocks_ptr = (uint8_t *)GET_BLOCKS_ADDR_INST(i, tx, rx), \ + .block_count = TX_BLOCKS_NUM, \ + .block_size = GET_BLOCK_SIZE_INST(i, tx, rx), \ + }, \ + .rx = { \ + .blocks_ptr = (uint8_t *)GET_BLOCKS_ADDR_INST(i, rx, tx), \ + .block_count = RX_BLOCKS_NUM, \ + .block_size = GET_BLOCK_SIZE_INST(i, rx, tx), \ + }, \ +} + +/** + * Backend initialization. + */ +int +ipc_open(uint8_t ipc_id) +{ + int rc; + struct ipc_instance *ipc; + + assert(ipc_id < sizeof(ipc_instances)); + + ipc = &ipc_instances[ipc_id]; + memset(ipc, 0, sizeof(*ipc)); + *ipc = IPC_INSTANCE_INIT(0); + + assert(ipc->state == ICMSG_STATE_OFF); + ipc->state = ICMSG_STATE_BUSY; + + memset(ipc->tx_usage_bitmap, 0, DIV_ROUND_UP(TX_BLOCKS_NUM, 8)); + memset(ipc->rx_usage_bitmap, 0, DIV_ROUND_UP(RX_BLOCKS_NUM, 8)); + + ipc->is_initiator = (ipc->rx.blocks_ptr < ipc->tx.blocks_ptr); + memset(&ipc->waiting_bound, 0xFF, sizeof(ipc->waiting_bound)); + + rc = pbuf_init(&ipc->tx_pb); + assert(rc == 0); + + /* Initialize local copies of rx_pb. */ + ipc->rx_pb.data.wr_idx = 0; + ipc->rx_pb.data.rd_idx = 0; + + rc = pbuf_write(&ipc->tx_pb, (char *)magic, sizeof(magic)); + assert(rc == sizeof(magic)); + + return 0; +} + +uint8_t +ipc_ready(uint8_t ipc_id) +{ + struct ipc_instance *ipc = &ipc_instances[ipc_id]; + + if (ipc->state == ICMSG_STATE_READY) { + return 1; + } + + return 0; +} + +uint8_t +ipc_icsmsg_ept_ready(uint8_t ipc_id, uint8_t ept_addr) +{ + struct ipc_instance *ipc = &ipc_instances[ipc_id]; + + if (ipc->ept[ept_addr].state == EPT_READY) { + return 1; + } + + return 0; +} diff --git a/hw/drivers/ipc/icbmsg/src/pbuf.c b/hw/drivers/ipc/icbmsg/src/pbuf.c new file mode 100644 index 0000000000..b3dc52dae7 --- /dev/null +++ b/hw/drivers/ipc/icbmsg/src/pbuf.c @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include "pbuf.h" +#include "utils.h" + +/* Helper funciton for getting numer of bytes being written to the bufer. */ +static uint32_t +idx_occupied(uint32_t len, uint32_t wr_idx, uint32_t rd_idx) +{ + /* It is implicitly assumed wr_idx and rd_idx cannot differ by more then len. */ + return (rd_idx > wr_idx) ? (len - (rd_idx - wr_idx)) : (wr_idx - rd_idx); +} + +/* Helper function for wrapping the index from the begging if above buffer len. */ +static uint32_t +idx_wrap(uint32_t len, uint32_t idx) +{ + return (idx >= len) ? (idx % len) : (idx); +} + +static int +validate_cfg(const struct pbuf_cfg *cfg) +{ + /* Validate pointers. */ + if (!cfg || !cfg->rd_idx_loc || !cfg->wr_idx_loc || !cfg->data_loc) { + return -EINVAL; + } + + /* Validate pointer alignment. */ + if (!IS_PTR_ALIGNED_BYTES(cfg->rd_idx_loc, MAX(cfg->dcache_alignment, _PBUF_IDX_SIZE)) || + !IS_PTR_ALIGNED_BYTES(cfg->wr_idx_loc, MAX(cfg->dcache_alignment, _PBUF_IDX_SIZE)) || + !IS_PTR_ALIGNED_BYTES(cfg->data_loc, _PBUF_IDX_SIZE)) { + return -EINVAL; + } + + /* Validate len. */ + if (cfg->len < _PBUF_MIN_DATA_LEN || !IS_PTR_ALIGNED_BYTES(cfg->len, _PBUF_IDX_SIZE)) { + return -EINVAL; + } + + /* Validate pointer values. */ + if (!(cfg->rd_idx_loc < cfg->wr_idx_loc) || + !((uint8_t *)cfg->wr_idx_loc < cfg->data_loc) || + !(((uint8_t *)cfg->rd_idx_loc + MAX(_PBUF_IDX_SIZE, cfg->dcache_alignment)) == + (uint8_t *)cfg->wr_idx_loc)) { + return -EINVAL; + } + + return 0; +} + +int +pbuf_init(struct pbuf *pb) +{ + if (validate_cfg(&pb->cfg) != 0) { + return -EINVAL; + } + + /* Initialize local copy of indexes. */ + pb->data.wr_idx = 0; + pb->data.rd_idx = 0; + + /* Clear shared memory. */ + *(pb->cfg.wr_idx_loc) = pb->data.wr_idx; + *(pb->cfg.rd_idx_loc) = pb->data.rd_idx; + + return 0; +} + +int +pbuf_write(struct pbuf *pb, const char *data, uint16_t len) +{ + if (pb == NULL || len == 0 || data == NULL) { + /* Incorrect call. */ + return -EINVAL; + } + + uint8_t *const data_loc = pb->cfg.data_loc; + const uint32_t blen = pb->cfg.len; + uint32_t rd_idx = *(pb->cfg.rd_idx_loc); + uint32_t wr_idx = pb->data.wr_idx; + + /* wr_idx must always be aligned. */ + assert(IS_PTR_ALIGNED_BYTES(wr_idx, _PBUF_IDX_SIZE)); + /* rd_idx shall always be aligned, but its value is received from the reader. + * Can not assert. + */ + if (!IS_PTR_ALIGNED_BYTES(rd_idx, _PBUF_IDX_SIZE)) { + return -EINVAL; + } + + assert(wr_idx < blen); + uint32_t free_space = blen - idx_occupied(blen, wr_idx, rd_idx) - _PBUF_IDX_SIZE; + + /* Packet length, data + packet length size. */ + uint32_t plen = len + PBUF_PACKET_LEN_SZ; + + /* Check if packet will fit into the buffer. */ + if (free_space < plen) { + return -ENOMEM; + } + + /* Clear packet len with zeros and update. Clearing is done for possible versioning in the + * future. Writing is allowed now, because shared wr_idx value is updated at the very end. + */ + *((uint32_t *)(&data_loc[wr_idx])) = 0; + put_be16(&data_loc[wr_idx], len); + + wr_idx = idx_wrap(blen, wr_idx + PBUF_PACKET_LEN_SZ); + + /* Write until end of the buffer, if data will be wrapped. */ + uint32_t tail = MIN(len, blen - wr_idx); + + memcpy(&data_loc[wr_idx], data, tail); + + if (len > tail) { + /* Copy remaining data to buffer front. */ + memcpy(&data_loc[0], data + tail, len - tail); + } + + wr_idx = idx_wrap(blen, ROUND_UP(wr_idx + len, _PBUF_IDX_SIZE)); + /* Update wr_idx. */ + pb->data.wr_idx = wr_idx; + *(pb->cfg.wr_idx_loc) = wr_idx; + + return len; +} + +int +pbuf_read(struct pbuf *pb, char *buf, uint16_t len) +{ + if (pb == NULL) { + /* Incorrect call. */ + return -EINVAL; + } + + uint8_t *const data_loc = pb->cfg.data_loc; + const uint32_t blen = pb->cfg.len; + uint32_t wr_idx = *(pb->cfg.wr_idx_loc); + uint32_t rd_idx = pb->data.rd_idx; + + /* rd_idx must always be aligned. */ + assert(IS_PTR_ALIGNED_BYTES(rd_idx, _PBUF_IDX_SIZE)); + /* wr_idx shall always be aligned, but its value is received from the + * writer. Can not assert. + */ + if (!IS_PTR_ALIGNED_BYTES(wr_idx, _PBUF_IDX_SIZE)) { + return -EINVAL; + } + + if (rd_idx == wr_idx) { + /* Buffer is empty. */ + return 0; + } + + /* Get packet len.*/ + uint16_t plen = get_be16(&data_loc[rd_idx]); + + if (!buf) { + return (int)plen; + } + + if (plen > len) { + return -ENOMEM; + } + + uint32_t occupied_space = idx_occupied(blen, wr_idx, rd_idx); + + if (occupied_space < plen + PBUF_PACKET_LEN_SZ) { + /* This should never happen. */ + return -EAGAIN; + } + + rd_idx = idx_wrap(blen, rd_idx + PBUF_PACKET_LEN_SZ); + + /* Packet will fit into provided buffer, truncate len if provided len + * is bigger than necessary. + */ + len = MIN(plen, len); + + /* Read until end of the buffer, if data are wrapped. */ + uint32_t tail = MIN(blen - rd_idx, len); + + memcpy(buf, &data_loc[rd_idx], tail); + + if (len > tail) { + memcpy(&buf[tail], &pb->cfg.data_loc[0], len - tail); + } + + /* Update rd_idx. */ + rd_idx = idx_wrap(blen, ROUND_UP(rd_idx + len, _PBUF_IDX_SIZE)); + + pb->data.rd_idx = rd_idx; + *(pb->cfg.rd_idx_loc) = rd_idx; + + return len; +} diff --git a/hw/drivers/ipc/icbmsg/src/pbuf.h b/hw/drivers/ipc/icbmsg/src/pbuf.h new file mode 100644 index 0000000000..5dd26108bf --- /dev/null +++ b/hw/drivers/ipc/icbmsg/src/pbuf.h @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_IPC_PBUF_H_ +#define ZEPHYR_INCLUDE_IPC_PBUF_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Packed buffer API + * @defgroup pbuf Packed Buffer API + * @ingroup ipc + * @{ + */ + +/** @brief Size of packet length field. */ +#define PBUF_PACKET_LEN_SZ sizeof(uint32_t) + +/* Amount of data that is left unused to distinguish between empty and full. */ +#define _PBUF_IDX_SIZE sizeof(uint32_t) + +/* Minimal length of the data field in the buffer to store the smalest packet + * possible. + * (+1) for at least one byte of data. + * (+_PBUF_IDX_SIZE) to distinguish buffer full and buffer empty. + * Rounded up to keep wr/rd indexes pointing to aligned address. + */ +#define _PBUF_MIN_DATA_LEN ROUND_UP(PBUF_PACKET_LEN_SZ + 1 + _PBUF_IDX_SIZE, _PBUF_IDX_SIZE) + +/** @brief Control block of packet buffer. + * + * The structure contains configuration data. + */ +struct pbuf_cfg { + /* Address of the variable holding index value of + * the first valid byte in data[]. + */ + volatile uint32_t *rd_idx_loc; + /* Address of the variable holding index value of + * the first free byte in data[]. + */ + volatile uint32_t *wr_idx_loc; + /* CPU data cache line size in bytes. Used for validation + * TODO: To be replaced by flags. + */ + uint32_t dcache_alignment; + /* Length of data[] in bytes. */ + uint32_t len; + /* Location of the data[]. */ + uint8_t *data_loc; +}; + +/** + * @brief Data block of the packed buffer. + * + * The structure contains local copies of wr and rd indexes used by writer and + * reader respectively. + */ +struct pbuf_data { + /* Index of the first holding first free byte in data[]. + * Used for writing. + */ + volatile uint32_t wr_idx; + /* Index of the first holding first valid byte in data[]. + * Used for reading. + */ + volatile uint32_t rd_idx; +}; + +/** + * @brief Scure packed buffer. + * + * The packet buffer implements lightweight unidirectional packet + * buffer with read/write semantics on top of a memory region shared + * by the reader and writer. It embeds cache and memory barrier management to + * ensure correct data access. + * + * This structure supports single writer and reader. Data stored in the buffer + * is encapsulated to a message (with length header). The read/write API is + * written in a way to protect the data from being corrupted. + */ +struct pbuf { + /* Configuration of the buffer. */ + struct pbuf_cfg cfg; + /* Data used to read and write to the buffer */ + struct pbuf_data data; +}; + +/** + * @brief Macro for configuration initialization. + * + * It is recommended to use this macro to initialize packed buffer + * configuration. + * + * @param mem_addr Memory address for pbuf. + * @param size Size of the memory. + * @param dcache_align Data cache alignment. + */ +#define PBUF_CFG_INIT(mem_addr, size, dcache_align) \ + { \ + .rd_idx_loc = (uint32_t *)(mem_addr), \ + .wr_idx_loc = (uint32_t *)((uint8_t *)(mem_addr) + \ + MAX(dcache_align, _PBUF_IDX_SIZE)), \ + .data_loc = (uint8_t *)((uint8_t *)(mem_addr) + \ + MAX(dcache_align, _PBUF_IDX_SIZE) + _PBUF_IDX_SIZE), \ + .len = (uint32_t)((uint32_t)(size) - MAX(dcache_align, _PBUF_IDX_SIZE) - \ + _PBUF_IDX_SIZE), \ + .dcache_alignment = (dcache_align), \ + } + +/** + * @brief Macro calculates memory overhead taken by the header in shared memory. + * + * It contains the read index, write index and padding. + * + * @param dcache_align Data cache alignment. + */ +#define PBUF_HEADER_OVERHEAD(dcache_align) \ + (MAX(dcache_align, _PBUF_IDX_SIZE) + _PBUF_IDX_SIZE) + +/** + * @brief Initialize the packet buffer. + * + * This function initializes the packet buffer based on provided configuration. + * If the configuration is incorrect, the function will return error. + * + * @param pb Pointer to the packed buffer containing + * configuration and data. Configuration has to be + * fixed before the initialization. + * @retval 0 on success. + * @retval -EINVAL when the input parameter is incorrect. + */ +int pbuf_init(struct pbuf *pb); + +/** + * @brief Write specified amount of data to the packet buffer. + * + * This function call writes specified amount of data to the packet buffer if + * the buffer will fit the data. + * + * @param pb A buffer to which to write. + * @param buf Pointer to the data to be written to the buffer. + * @param len Number of bytes to be written to the buffer. Must be positive. + * @retval int Number of bytes written, negative error code on fail. + * -EINVAL, if any of input parameter is incorrect. + * -ENOMEM, if len is bigger than the buffer can fit. + */ + +int pbuf_write(struct pbuf *pb, const char *buf, uint16_t len); + +/** + * @brief Read specified amount of data from the packet buffer. + * + * Single read allows to read the message send by the single write. + * The provided %p buf must be big enough to store the whole message. + * + * @param pb A buffer from which data will be read. + * @param buf Data pointer to which read data will be written. + * If NULL, len of stored message is returned. + * @param len Number of bytes to be read from the buffer. + * @retval int Bytes read, negative error code on fail. + * Bytes to be read, if buf == NULL. + * -EINVAL, if any of input parameter is incorrect. + * -ENOMEM, if message can not fit in provided buf. + * -EAGAIN, if not whole message is ready yet. + */ +int pbuf_read(struct pbuf *pb, char *buf, uint16_t len); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_IPC_PBUF_H_ */ diff --git a/hw/drivers/ipc/icbmsg/src/utils.h b/hw/drivers/ipc/icbmsg/src/utils.h new file mode 100644 index 0000000000..a5d6a49541 --- /dev/null +++ b/hw/drivers/ipc/icbmsg/src/utils.h @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef _HW_DRIVERS_IPC_ICBMSG_UTILS_H +#define _HW_DRIVERS_IPC_ICBMSG_UTILS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) + +/* Value of x rounded up to the next multiple of align. */ +#define ROUND_UP(x, align) \ + ((((unsigned long)(x) + ((unsigned long)(align) - 1)) / \ + (unsigned long)(align)) * (unsigned long)(align)) + +#define ROUND_DOWN(x, align) \ + (((unsigned long)(x) / (unsigned long)(align)) * (unsigned long)(align)) + +/* Check if a pointer is aligned for against a specific byte boundary */ +#define IS_PTR_ALIGNED_BYTES(ptr, bytes) ((((uintptr_t)ptr) % bytes) == 0) + +#ifndef MAX +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif + +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _HW_DRIVERS_IPC_ICBMSG_UTILS_H */ diff --git a/hw/drivers/ipc/icbmsg/syscfg.yml b/hw/drivers/ipc/icbmsg/syscfg.yml new file mode 100644 index 0000000000..67547e13b6 --- /dev/null +++ b/hw/drivers/ipc/icbmsg/syscfg.yml @@ -0,0 +1,47 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +syscfg.defs: + IPC_ICBMSG_NUM_EP: + description: > + Allowed number of endpoints within an IPC instance. + value: 2 + IPC_ICBMSG_TX_REGION_SIZE: + description: > + Size of IPC TX region. + value: 0x800 + IPC_ICBMSG_RX_REGION_SIZE: + description: > + Size of IPC RX region. + value: 0x800 + IPC_ICBMSG_TX_REGION_NAME: + description: > + Name of IPC TX region. + value: "\".ipc0_tx\"" + IPC_ICBMSG_RX_REGION_NAME: + description: > + Size of IPC RX region. + value: "\".ipc0_rx\"" + IPC_ICBMSG_NUM_TX_BLOCKS: + description: > + Number of blocks within IPC TX region. + value: 24 + IPC_ICBMSG_NUM_RX_BLOCKS: + description: > + Number of blocks within IPC RX region. + value: 24 diff --git a/hw/drivers/ipc/include/ipc/ipc.h b/hw/drivers/ipc/include/ipc/ipc.h new file mode 100644 index 0000000000..93c5910034 --- /dev/null +++ b/hw/drivers/ipc/include/ipc/ipc.h @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef _HW_DRIVERS_IPC_H +#define _HW_DRIVERS_IPC_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void ipc_init(void); +int ipc_open(uint8_t ipc_id); +int ipc_signal(uint8_t ipc_id); +void ipc_process_signal(uint8_t ipc_id); +uint8_t ipc_ready(uint8_t ipc_id); + +#ifdef __cplusplus +} +#endif + +#endif /* _HW_DRIVERS_IPC_H */ diff --git a/hw/drivers/ipc/pkg.yml b/hw/drivers/ipc/pkg.yml new file mode 100644 index 0000000000..c79155ee65 --- /dev/null +++ b/hw/drivers/ipc/pkg.yml @@ -0,0 +1,32 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +pkg.name: hw/drivers/ipc +pkg.description: IPC driver +pkg.author: "Apache Mynewt " +pkg.homepage: "http://mynewt.apache.org/" +pkg.keywords: + - ipc + +pkg.deps: + - "@apache-mynewt-core/hw/mcu/nordic" + - "@apache-mynewt-core/kernel/os" + +pkg.init: + ipc_init: 10 diff --git a/hw/drivers/ipc/src/ipc.c b/hw/drivers/ipc/src/ipc.c new file mode 100644 index 0000000000..76565506a3 --- /dev/null +++ b/hw/drivers/ipc/src/ipc.c @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include +#include +#include + +#define IPC_MAX_CHANS MYNEWT_VAL(IPC_CHANNELS) +#define IPC_SYNC_ID 0 + +/* IPC channels used for startup sync */ +#define IPC_SYNC_TX_CHANNEL MYNEWT_VAL(IPC_SYNC_TX_CHANNEL) +#define IPC_SYNC_RX_CHANNEL MYNEWT_VAL(IPC_SYNC_RX_CHANNEL) + +static void +ipc_cb(uint8_t channel) +{ + assert(channel == IPC_SYNC_RX_CHANNEL); + + os_trace_isr_enter(); + + ipc_process_signal(IPC_SYNC_ID); + + os_trace_isr_exit(); +} + +int +ipc_signal(uint8_t channel) +{ + return hal_ipc_signal(channel); +} + +void +ipc_init(void) +{ + uint32_t start; + + hal_ipc_init(); + + hal_ipc_register_callback(IPC_SYNC_RX_CHANNEL, ipc_cb); + + ipc_open(IPC_SYNC_ID); + + hal_ipc_enable_irq(IPC_SYNC_RX_CHANNEL, 1); + + hal_ipc_start(); + + ipc_signal(IPC_SYNC_TX_CHANNEL); + start = os_cputime_ticks_to_usecs(os_cputime_get32()); + + while (!ipc_ready(IPC_SYNC_ID)) { + ipc_cb(IPC_SYNC_RX_CHANNEL); + + if ((os_cputime_ticks_to_usecs(os_cputime_get32()) - start) > 1000) { + ipc_signal(IPC_SYNC_TX_CHANNEL); + start = os_cputime_ticks_to_usecs(os_cputime_get32()); + } + } +} diff --git a/hw/drivers/ipc/syscfg.yml b/hw/drivers/ipc/syscfg.yml new file mode 100644 index 0000000000..ae87b8b823 --- /dev/null +++ b/hw/drivers/ipc/syscfg.yml @@ -0,0 +1,39 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +syscfg.defs: + IPC_CHANNELS: + description: > + Number of enabled IPC channels + value: 5 + range: 1..16 + + IPC_SYNC_TX_CHANNEL: + description: > + Select IPC TX channel to be used for sync. + range: 0..31 + value: 1 + + IPC_SYNC_RX_CHANNEL: + description: > + Select IPC RX channel to be used for sync. + range: 0..31 + value: 0 + +syscfg.vals.'BSP_NRF5340_NET || BSP_NRF54_RAD': + IPC_CHANNELS: 16 diff --git a/hw/hal/include/hal/hal_ipc.h b/hw/hal/include/hal/hal_ipc.h new file mode 100644 index 0000000000..88add13e16 --- /dev/null +++ b/hw/hal/include/hal/hal_ipc.h @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * @addtogroup HAL + * @{ + * @defgroup HALIPC HAL IPC + * @{ + */ + +#ifndef H_HAL_IPC_ +#define H_HAL_IPC_ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*hal_ipc_cb)(uint8_t channel); + +void hal_ipc_init(void); +void hal_ipc_start(void); +void hal_ipc_register_callback(uint8_t channel, hal_ipc_cb cb); +void hal_ipc_enable_irq(uint8_t channel, bool enable); +int hal_ipc_signal(uint8_t channel); + +#ifdef __cplusplus +} +#endif + +#endif /* H_HAL_IPC_ */ + +/** + * @} HALIPC + * @} HAL + */ diff --git a/hw/mcu/nordic/nrf5340/nrf5340.ld b/hw/mcu/nordic/nrf5340/nrf5340.ld index 339ac60abe..6a304d48b0 100644 --- a/hw/mcu/nordic/nrf5340/nrf5340.ld +++ b/hw/mcu/nordic/nrf5340/nrf5340.ld @@ -147,6 +147,22 @@ INCLUDE "link_tables.ld.h" . = ALIGN(4); } > RAM + /* Section for IPC RX */ + .ipc0_rx (NOLOAD): + { + . = ALIGN(4); + *(.ipc0_rx) + . = ALIGN(4); + } > sram_ipc0_rx + + /* Section for IPC TX */ + .ipc0_tx (NOLOAD): + { + . = ALIGN(4); + *(.ipc0_tx) + . = ALIGN(4); + } > sram_ipc0_tx + /* This section will be zeroed by RTT package init */ .rtt (NOLOAD): { diff --git a/hw/mcu/nordic/nrf5340/pkg.yml b/hw/mcu/nordic/nrf5340/pkg.yml index ddc93e25a7..c936d5b20a 100644 --- a/hw/mcu/nordic/nrf5340/pkg.yml +++ b/hw/mcu/nordic/nrf5340/pkg.yml @@ -30,6 +30,12 @@ pkg.deps: - "@apache-mynewt-core/hw/mcu/nordic/nrf_common" - "@apache-mynewt-core/hw/mcu/nordic/nrf5340/tfm" +pkg.deps.'BLE_TRANSPORT_LL == "nrf5340"': + - "@apache-mynewt-core/hw/drivers/ipc_nrf5340" + +pkg.deps.'BLE_TRANSPORT_LL == "ipc"': + - "@apache-mynewt-core/hw/drivers/ipc" + pkg.deps.BUS_DRIVER_PRESENT: - "@apache-mynewt-core/hw/bus/drivers/spi_hal" - "@apache-mynewt-core/hw/bus/drivers/i2c_common" diff --git a/hw/mcu/nordic/nrf5340/src/hal_ipc.c b/hw/mcu/nordic/nrf5340/src/hal_ipc.c new file mode 100644 index 0000000000..57eb0947ef --- /dev/null +++ b/hw/mcu/nordic/nrf5340/src/hal_ipc.c @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include +#include +#include +#include +#include + +/* Always use unsecure peripheral for IPC, unless pre-TrusZone bootloader is present in netcore */ +#undef NRF_IPC +#if MYNEWT_VAL(IPC_NRF5340_PRE_TRUSTZONE_NETCORE_BOOT) +#define NRF_IPC NRF_IPC_S +#else +#define NRF_IPC NRF_IPC_NS +#endif + +#ifndef BIT +#define BIT(n) (1UL << (n)) +#endif + +#define IPC_MAX_CHANS 4 + +static hal_ipc_cb cbs[IPC_MAX_CHANS]; + +int +hal_ipc_signal(uint8_t channel) +{ + assert(channel < IPC_MAX_CHANS); + + nrf_ipc_task_trigger(NRF_IPC, nrf_ipc_send_task_get(channel)); + + return 0; +} + +void +hal_ipc_register_callback(uint8_t channel, hal_ipc_cb cb) +{ + assert(channel < IPC_MAX_CHANS); + + cbs[channel] = cb; +} + +void +hal_ipc_enable_irq(uint8_t channel, bool enable) +{ + assert(channel < IPC_MAX_CHANS); + + if (enable) { + NRF_IPC->RECEIVE_CNF[channel] = BIT(channel); + NRF_IPC->INTENSET = BIT(channel); + } else { + NRF_IPC->INTENCLR = BIT(channel); + NRF_IPC->RECEIVE_CNF[channel] = 0; + } +} + +static void +ipc_isr(void) +{ + uint32_t irq_pend; + uint8_t channel; + + os_trace_isr_enter(); + + /* Handle only interrupts that were enabled */ + irq_pend = NRF_IPC->INTPEND & NRF_IPC->INTEN; + + for (channel = 0; channel < IPC_MAX_CHANS; ++channel) { + if (irq_pend & BIT(channel)) { + NRF_IPC->EVENTS_RECEIVE[channel] = 0; + + if (cbs[channel] != NULL) { + cbs[channel](channel); + } + } + } + + os_trace_isr_exit(); +} + +void +hal_ipc_init(void) +{ + uint8_t i; + + /* Make sure network core if off when we set up IPC */ + nrf_reset_network_force_off(NRF_RESET, true); + + if (MYNEWT_VAL(MCU_APP_SECURE) && !MYNEWT_VAL(IPC_NRF5340_PRE_TRUSTZONE_NETCORE_BOOT)) { + /* + * When bootloader is secure and application is not all peripherals are + * in unsecure mode. This is done by bootloader. + * If application runs in secure mode IPC manually chooses to use unsecure version + * so net core can always use same peripheral. + */ + NRF_SPU->PERIPHID[42].PERM &= ~SPU_PERIPHID_PERM_SECATTR_Msk; + } + + /* Enable IPC channels */ + for (i = 0; i < IPC_MAX_CHANS; ++i) { + NRF_IPC->SEND_CNF[i] = BIT(i); + NRF_IPC->RECEIVE_CNF[i] = 0; + } + + NRF_IPC->INTENCLR = 0xFFFF; + NVIC_ClearPendingIRQ(IPC_IRQn); + NVIC_SetVector(IPC_IRQn, (uint32_t)ipc_isr); + NVIC_EnableIRQ(IPC_IRQn); +} + +void +hal_ipc_start(void) +{ + if (MYNEWT_VAL(MCU_APP_SECURE)) { + /* this allows netcore to access appcore RAM */ + NRF_SPU_S->EXTDOMAIN[0].PERM = SPU_EXTDOMAIN_PERM_SECATTR_Secure << SPU_EXTDOMAIN_PERM_SECATTR_Pos; + } + + /* Start Network Core */ + nrf_reset_network_force_off(NRF_RESET, false); + + /* + * Wait for NET core to start and init it's side of IPC. + * It may take several seconds if there is net core + * embedded image in the application flash. + */ +} diff --git a/hw/mcu/nordic/nrf5340/syscfg.yml b/hw/mcu/nordic/nrf5340/syscfg.yml index cfa8e4374a..d127648c69 100644 --- a/hw/mcu/nordic/nrf5340/syscfg.yml +++ b/hw/mcu/nordic/nrf5340/syscfg.yml @@ -579,6 +579,17 @@ syscfg.defs: Adds default MPU configuration package value: 0 + IPC_NRF5340_PRE_TRUSTZONE_NETCORE_BOOT: + description: > + Set this to one only in case of the bootloader predates TrustZone + changes. With ARM TrusZone support added NRF_IPC_NS block is used + for both secure and non-secure application. Pre-TrustZone code + was always running in secure mode and network core code always + accessed NRF_IP_S (including code that is running in bootloader). + NRF_IP_S->GPMEM[] was used to pass address of net core application + image to net bootloader. + value: 0 + syscfg.vals: OS_TICKS_PER_SEC: 128 diff --git a/hw/mcu/nordic/nrf5340_net/nrf5340_net.ld b/hw/mcu/nordic/nrf5340_net/nrf5340_net.ld index 74a20ff494..8544f58425 100644 --- a/hw/mcu/nordic/nrf5340_net/nrf5340_net.ld +++ b/hw/mcu/nordic/nrf5340_net/nrf5340_net.ld @@ -138,6 +138,22 @@ INCLUDE "link_tables.ld.h" *(.ipc) } > IPC + /* Section for IPC RX */ + .ipc0_rx (NOLOAD): + { + . = ALIGN(4); + *(.ipc0_rx) + . = ALIGN(4); + } > sram_ipc0_rx + + /* Section for IPC TX */ + .ipc0_tx (NOLOAD): + { + . = ALIGN(4); + *(.ipc0_tx) + . = ALIGN(4); + } > sram_ipc0_tx + /* This section will be zeroed by RTT package init */ .rtt (NOLOAD): { diff --git a/hw/mcu/nordic/nrf5340_net/pkg.yml b/hw/mcu/nordic/nrf5340_net/pkg.yml index 83146c6566..55fe9de50a 100644 --- a/hw/mcu/nordic/nrf5340_net/pkg.yml +++ b/hw/mcu/nordic/nrf5340_net/pkg.yml @@ -28,8 +28,13 @@ pkg.keywords: pkg.deps: - "@apache-mynewt-core/hw/mcu/nordic" - "@apache-mynewt-core/hw/mcu/nordic/nrf_common" + +pkg.deps.'BLE_TRANSPORT_HS == "nrf5340"': - "@apache-mynewt-core/hw/drivers/ipc_nrf5340" +pkg.deps.'BLE_TRANSPORT_HS == "ipc"': + - "@apache-mynewt-core/hw/drivers/ipc" + pkg.cflags.NFC_PINS_AS_GPIO: - '-DCONFIG_NFCT_PINS_AS_GPIOS=1' diff --git a/hw/mcu/nordic/nrf5340_net/src/hal_ipc.c b/hw/mcu/nordic/nrf5340_net/src/hal_ipc.c new file mode 100644 index 0000000000..98d669cbd6 --- /dev/null +++ b/hw/mcu/nordic/nrf5340_net/src/hal_ipc.c @@ -0,0 +1,112 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include +#include +#include +#include +#include + +#ifndef BIT +#define BIT(n) (1UL << (n)) +#endif + +#define IPC_MAX_CHANS 4 + +static hal_ipc_cb cbs[IPC_MAX_CHANS]; + +int +hal_ipc_signal(uint8_t channel) +{ + assert(channel < IPC_MAX_CHANS); + + nrf_ipc_task_trigger(NRF_IPC, nrf_ipc_send_task_get(channel)); + + return 0; +} + +void +hal_ipc_register_callback(uint8_t channel, hal_ipc_cb cb) +{ + assert(channel < IPC_MAX_CHANS); + + cbs[channel] = cb; +} + +void +hal_ipc_enable_irq(uint8_t channel, bool enable) +{ + assert(channel < IPC_MAX_CHANS); + + if (enable) { + NRF_IPC->RECEIVE_CNF[channel] = BIT(channel); + NRF_IPC->INTENSET = BIT(channel); + } else { + NRF_IPC->INTENCLR = BIT(channel); + NRF_IPC->RECEIVE_CNF[channel] = 0; + } +} + +static void +ipc_isr(void) +{ + uint32_t irq_pend; + uint8_t channel; + + os_trace_isr_enter(); + + /* Handle only interrupts that were enabled */ + irq_pend = NRF_IPC->INTPEND & NRF_IPC->INTEN; + + for (channel = 0; channel < IPC_MAX_CHANS; ++channel) { + if (irq_pend & BIT(channel)) { + NRF_IPC->EVENTS_RECEIVE[channel] = 0; + + if (cbs[channel] != NULL) { + cbs[channel](channel); + } + } + } + + os_trace_isr_exit(); +} + +void +hal_ipc_init(void) +{ + uint8_t i; + + /* Enable IPC channels */ + for (i = 0; i < IPC_MAX_CHANS; ++i) { + NRF_IPC->SEND_CNF[i] = BIT(i); + NRF_IPC->RECEIVE_CNF[i] = 0; + } + + NRF_IPC->INTENCLR = 0xFFFF; + NVIC_ClearPendingIRQ(IPC_IRQn); + NVIC_SetVector(IPC_IRQn, (uint32_t)ipc_isr); + NVIC_EnableIRQ(IPC_IRQn); +} + +void +hal_ipc_start(void) +{ +} diff --git a/hw/mcu/nordic/nrf5340_net/src/hal_vflash.c b/hw/mcu/nordic/nrf5340_net/src/hal_vflash.c index 8024ce4517..dd06a729c7 100644 --- a/hw/mcu/nordic/nrf5340_net/src/hal_vflash.c +++ b/hw/mcu/nordic/nrf5340_net/src/hal_vflash.c @@ -27,7 +27,9 @@ #include #include #include +#if MYNEWT_PKG_apache_mynewt_core__hw_drivers_ipc_nrf5340 #include +#endif #define NRF5340_NET_VFLASH_SECTOR_SZ 2048 @@ -218,7 +220,12 @@ nrf5340_net_vflash_init(const struct hal_flash *dev) const void *img_addr; uint32_t image_size; +#if MYNEWT_PKG_apache_mynewt_core__hw_drivers_ipc_nrf5340 img_addr = ipc_nrf5340_net_image_get(&image_size); +#else + img_addr = 0; + image_size = 0; +#endif /* * Application side IPC will set ipc_share data