ipc: RPMsg service to register multiple endpoints

This patch implements a service that adds multiendpoint
capabilities to RPMsg. Multiple endpoints are intended to be used
when multiple modules need services from a remote processor. Each
module may register one or more RPMsg endpoints.

The implementation separates backend from the service, what
allows to extend this module to support other topologies like
Linux <-> Zephyr.

Co-authored-by: Piotr Szkotak <piotr.szkotak@nordicsemi.no>
Signed-off-by: Hubert Miś <hubert.mis@nordicsemi.no>
This commit is contained in:
Hubert Miś 2021-01-18 12:19:49 +01:00 committed by Carles Cufí
commit b0ec7a63ab
11 changed files with 726 additions and 0 deletions

View file

@ -552,6 +552,7 @@
/subsys/fs/fuse_fs_access.c @vanwinkeljan
/subsys/fs/littlefs_fs.c @pabigot
/subsys/fs/nvs/ @Laczen
/subsys/ipc/ @ioannisg
/subsys/logging/ @nordic-krch
/subsys/logging/log_backend_net.c @nordic-krch @jukkar
/subsys/lorawan/ @Mani-Sadhasivam

View file

@ -0,0 +1,78 @@
/*
* Copyright (c) 2020 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_RPMSG_SERVICE_RPMSG_SERVICE_H_
#define ZEPHYR_INCLUDE_RPMSG_SERVICE_RPMSG_SERVICE_H_
#include <openamp/open_amp.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief RPMsg service API
* @defgroup rpmsg_service_api RPMsg service APIs
* @{
*/
/**
* @brief Register IPC endpoint
*
* Registers IPC endpoint to enable communication with a remote device.
* The endpoint is created when the slave device registers it.
*
* The same function registers endpoints for both master and slave devices.
*
* @param name String containing the name of the endpoint. Must be identical
* for master and slave
* @param cb Callback executed when data are available on given endpoint
*
* @retval >=0 id of registered endpoint on success;
* @retval -EINPROGRESS when requested to register an endpoint after endpoints
* creation procedure has started;
* @retval -ENOMEM when there is not enough slots to register the endpoint;
* @retval <0 an other negative errno code, reported by rpmsg.
*/
int rpmsg_service_register_endpoint(const char *name, rpmsg_ept_cb cb);
/**
* @brief Send data using given IPC endpoint
*
* @param endpoint_id Id of registered endpoint, obtained by
* @ref rpmsg_service_register_endpoint
* @param data Pointer to the buffer to send through RPMsg service
* @param len Number of bytes to send.
*
* @retval >=0 number of sent bytes;
* @retval <0 an error code, reported by rpmsg.
*/
int rpmsg_service_send(int endpoint_id, const void *data, size_t len);
/**
* @brief Check if endpoint is bound.
*
* Checks if remote endpoint has been created
* and the master has bound its endpoint to it.
*
* @param endpoint_id Id of registered endpoint, obtained by
* @ref rpmsg_service_register_endpoint
*
* @retval true endpoint is bound
* @retval false endpoint not bound
*/
bool rpmsg_service_endpoint_is_bound(int endpoint_id);
/**
* @}
*/
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_INCLUDE_RPMSG_SERVICE_RPMSG_SERVICE_H_ */

View file

@ -10,6 +10,7 @@ add_subdirectory_ifdef(CONFIG_CPLUSPLUS cpp)
add_subdirectory_ifdef(CONFIG_DISK_ACCESS disk)
add_subdirectory_ifdef(CONFIG_EMUL emul)
add_subdirectory(fs)
add_subdirectory(ipc)
add_subdirectory(mgmt)
add_subdirectory_ifdef(CONFIG_MCUBOOT_IMG_MANAGER dfu)
add_subdirectory_ifdef(CONFIG_NET_BUF net)

View file

@ -1,6 +1,7 @@
# Subsystem configuration options
# Copyright (c) 2016-2017 Intel Corporation
# Copyright (c) 2021 Nordic Semiconductor
# SPDX-License-Identifier: Apache-2.0
menu "Sub Systems and OS Services"
@ -23,6 +24,8 @@ source "subsys/fb/Kconfig"
source "subsys/fs/Kconfig"
source "subsys/ipc/Kconfig"
source "subsys/jwt/Kconfig"
source "subsys/logging/Kconfig"

View file

@ -0,0 +1,3 @@
# SPDX-License-Identifier: Apache-2.0
add_subdirectory_ifdef(CONFIG_RPMSG_SERVICE rpmsg_service)

10
subsys/ipc/Kconfig Normal file
View file

@ -0,0 +1,10 @@
# IPC subsystem configuration options
# Copyright (c) 2021 Nordic Semiconductor
# SPDX-License-Identifier: Apache-2.0
menu "Inter Processor Communication"
source "subsys/ipc/rpmsg_service/Kconfig"
endmenu

View file

@ -0,0 +1,4 @@
# SPDX-License-Identifier: Apache-2.0
zephyr_sources(rpmsg_backend.c)
zephyr_sources(rpmsg_service.c)

View file

@ -0,0 +1,122 @@
# Copyright (c) 2020-2021 Nordic Semiconductor (ASA)
# SPDX-License-Identifier: Apache-2.0
# Workaround for not being able to have commas in macro arguments
DT_CHOSEN_Z_IPC_SHM := zephyr,ipc_shm
DT_CHOSEN_Z_IPC := zephyr,ipc
DT_CHOSEN_Z_IPC_TX := zephyr,ipc_tx
DT_CHOSEN_Z_IPC_RX := zephyr,ipc_rx
config RPMSG_SERVICE_SINGLE_IPM_SUPPORT
bool
default $(dt_chosen_enabled,$(DT_CHOSEN_Z_IPC))
help
This option must be selected when single IPM is used for
both TX and RX communication
config RPMSG_SERVICE_DUAL_IPM_SUPPORT
bool
default $(dt_chosen_enabled,$(DT_CHOSEN_Z_IPC_TX)) && \
$(dt_chosen_enabled,$(DT_CHOSEN_Z_IPC_RX))
help
This option must be selected when separate IPMs are used for
TX and RX communication
menuconfig RPMSG_SERVICE
bool "RPMsg service for multiple users"
select IPM
select OPENAMP
help
Enables support for a service that can be shared by multiple
users to establish RPMsg endpoints for given channel.
if RPMSG_SERVICE
config RPMSG_SERVICE_SHM_BASE_ADDRESS
hex
default "$(dt_chosen_reg_addr_hex,$(DT_CHOSEN_Z_IPC_SHM))"
help
This option specifies base address of the memory region to
be used for the OpenAMP IPC shared memory
config RPMSG_SERVICE_SHM_SIZE
hex
default "$(dt_chosen_reg_size_hex,$(DT_CHOSEN_Z_IPC_SHM))"
help
This option specifies size of the memory region to be used
for the OpenAMP IPC shared memory
if RPMSG_SERVICE_SINGLE_IPM_SUPPORT
config RPMSG_SERVICE_IPM_NAME
string
default "$(dt_chosen_label,$(DT_CHOSEN_Z_IPC))"
help
This option specifies the IPM device name to be used
endif # RPMSG_SERVICE_SINGLE_IPM_SUPPORT
if RPMSG_SERVICE_DUAL_IPM_SUPPORT
config RPMSG_SERVICE_IPM_TX_NAME
string
default "$(dt_chosen_label,$(DT_CHOSEN_Z_IPC_TX))"
help
This option specifies the IPM device name to be used for
TX communication
config RPMSG_SERVICE_IPM_RX_NAME
string
default "$(dt_chosen_label,$(DT_CHOSEN_Z_IPC_RX))"
help
This option specifies the IPM device name to be used for
RX communication
endif # RPMSG_SERVICE_DUAL_IPM_SUPPORT
choice RPMSG_SERVICE_MODE
prompt "RPMsg Service mode"
config RPMSG_SERVICE_MODE_MASTER
bool "RPMsg master"
select OPENAMP_MASTER
config RPMSG_SERVICE_MODE_REMOTE
bool "RPMsg remote"
select OPENAMP_SLAVE
endchoice
config RPMSG_SERVICE_NUM_ENDPOINTS
int "Max number of registered endpoints"
default 2
help
Maximal number of endpoints that can be registered for given
RPMsg service.
config RPMSG_SERVICE_WORK_QUEUE_STACK_SIZE
int "Size of RX work queue stack"
default 2048
help
Size of stack used by work queue RX thread. This work queue is
created in the RPMsg Service backend module to prevent notifying
service users about received data from the system work queue.
config RPMSG_SERVICE_INIT_PRIORITY
int "Initialization priority of RPMsg service"
default 48
help
The order of RPMsg Service initialization and endpoints registration
is important to avoid race conditions in RPMsg endpoints handshake.
If in doubt, do not modify this value.
config RPMSG_SERVICE_EP_REG_PRIORITY
int "Initialization priority of modules registering RPMsg endpoints"
default 47
help
The endpoints must be registered before RPMsg Service is initialized.
If in doubt, do not modify this value.
endif # RPMSG_SERVICE

View file

@ -0,0 +1,296 @@
/*
* Copyright (c) 2021, Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "rpmsg_backend.h"
#include <zephyr.h>
#include <drivers/ipm.h>
#include <device.h>
#include <logging/log.h>
#include <openamp/open_amp.h>
#include <metal/device.h>
#define LOG_LEVEL LOG_LEVEL_INFO
#define LOG_MODULE_NAME rpmsg_backend
LOG_MODULE_REGISTER(LOG_MODULE_NAME);
/* Configuration defines */
#if !DT_HAS_CHOSEN(zephyr_ipc_shm)
#error "Module requires definition of shared memory for rpmsg"
#endif
#define MASTER IS_ENABLED(CONFIG_RPMSG_SERVICE_MODE_MASTER)
#if MASTER
#define VIRTQUEUE_ID 0
#define RPMSG_ROLE RPMSG_MASTER
#else
#define VIRTQUEUE_ID 1
#define RPMSG_ROLE RPMSG_REMOTE
#endif
/* Configuration defines */
#define VRING_COUNT 2
#define VRING_RX_ADDRESS (VDEV_START_ADDR + SHM_SIZE - VDEV_STATUS_SIZE)
#define VRING_TX_ADDRESS (VDEV_START_ADDR + SHM_SIZE)
#define VRING_ALIGNMENT 4
#define VRING_SIZE 16
#define IPM_WORK_QUEUE_STACK_SIZE CONFIG_RPMSG_SERVICE_WORK_QUEUE_STACK_SIZE
#if IS_ENABLED(CONFIG_COOP_ENABLED)
#define IPM_WORK_QUEUE_PRIORITY -1
#else
#define IPM_WORK_QUEUE_PRIORITY 0
#endif
K_THREAD_STACK_DEFINE(ipm_stack_area, IPM_WORK_QUEUE_STACK_SIZE);
struct k_work_q ipm_work_q;
/* End of configuration defines */
#if defined(CONFIG_RPMSG_SERVICE_DUAL_IPM_SUPPORT)
static const struct device *ipm_tx_handle;
static const struct device *ipm_rx_handle;
#elif defined(CONFIG_RPMSG_SERVICE_SINGLE_IPM_SUPPORT)
static const struct device *ipm_handle;
#endif
static metal_phys_addr_t shm_physmap[] = { SHM_START_ADDR };
static struct metal_device shm_device = {
.name = SHM_DEVICE_NAME,
.bus = NULL,
.num_regions = 1,
{
{
.virt = (void *) SHM_START_ADDR,
.physmap = shm_physmap,
.size = SHM_SIZE,
.page_shift = 0xffffffff,
.page_mask = 0xffffffff,
.mem_flags = 0,
.ops = { NULL },
},
},
.node = { NULL },
.irq_num = 0,
.irq_info = NULL
};
static struct virtio_vring_info rvrings[2] = {
[0] = {
.info.align = VRING_ALIGNMENT,
},
[1] = {
.info.align = VRING_ALIGNMENT,
},
};
static struct virtqueue *vq[2];
static struct k_work ipm_work;
static unsigned char virtio_get_status(struct virtio_device *vdev)
{
#if MASTER
return VIRTIO_CONFIG_STATUS_DRIVER_OK;
#else
return sys_read8(VDEV_STATUS_ADDR);
#endif
}
static void virtio_set_status(struct virtio_device *vdev, unsigned char status)
{
sys_write8(status, VDEV_STATUS_ADDR);
}
static uint32_t virtio_get_features(struct virtio_device *vdev)
{
return BIT(VIRTIO_RPMSG_F_NS);
}
static void virtio_set_features(struct virtio_device *vdev,
uint32_t features)
{
}
static void virtio_notify(struct virtqueue *vq)
{
int status;
#if defined(CONFIG_RPMSG_SERVICE_DUAL_IPM_SUPPORT)
status = ipm_send(ipm_tx_handle, 0, 0, NULL, 0);
#elif defined(CONFIG_RPMSG_SERVICE_SINGLE_IPM_SUPPORT)
#if defined(CONFIG_SOC_MPS2_AN521) || \
defined(CONFIG_SOC_V2M_MUSCA_A) || \
defined(CONFIG_SOC_V2M_MUSCA_B1)
uint32_t current_core = sse_200_platform_get_cpu_id();
status = ipm_send(ipm_handle, 0, current_core ? 0 : 1, 0, 1);
#else
uint32_t dummy_data = 0x55005500; /* Some data must be provided */
status = ipm_send(ipm_handle, 0, 0, &dummy_data, sizeof(dummy_data));
#endif /* #if defined(CONFIG_SOC_MPS2_AN521) */
#endif
if (status != 0) {
LOG_ERR("ipm_send failed to notify: %d", status);
}
}
const struct virtio_dispatch dispatch = {
.get_status = virtio_get_status,
.set_status = virtio_set_status,
.get_features = virtio_get_features,
.set_features = virtio_set_features,
.notify = virtio_notify,
};
static void ipm_callback_process(struct k_work *work)
{
virtqueue_notification(vq[VIRTQUEUE_ID]);
}
static void ipm_callback(const struct device *dev,
void *context, uint32_t id,
volatile void *data)
{
(void)dev;
LOG_DBG("Got callback of id %u", id);
/* TODO: Separate workqueue is needed only
* for serialization master (app core)
*
* Use sysworkq to optimize memory footprint
* for serialization slave (net core)
*/
k_work_submit_to_queue(&ipm_work_q, &ipm_work);
}
int rpmsg_backend_init(struct metal_io_region **io, struct virtio_device *vdev)
{
int32_t err;
struct metal_init_params metal_params = METAL_INIT_DEFAULTS;
struct metal_device *device;
/* Start IPM workqueue */
k_work_q_start(&ipm_work_q, ipm_stack_area,
K_THREAD_STACK_SIZEOF(ipm_stack_area),
IPM_WORK_QUEUE_PRIORITY);
k_thread_name_set(&ipm_work_q.thread, "ipm_work_q");
/* Setup IPM workqueue item */
k_work_init(&ipm_work, ipm_callback_process);
/* Libmetal setup */
err = metal_init(&metal_params);
if (err) {
LOG_ERR("metal_init: failed - error code %d", err);
return err;
}
err = metal_register_generic_device(&shm_device);
if (err) {
LOG_ERR("Couldn't register shared memory device: %d", err);
return err;
}
err = metal_device_open("generic", SHM_DEVICE_NAME, &device);
if (err) {
LOG_ERR("metal_device_open failed: %d", err);
return err;
}
*io = metal_device_io_region(device, 0);
if (!*io) {
LOG_ERR("metal_device_io_region failed to get region");
return err;
}
/* IPM setup */
#if defined(CONFIG_RPMSG_SERVICE_DUAL_IPM_SUPPORT)
ipm_tx_handle = device_get_binding(CONFIG_RPMSG_SERVICE_IPM_TX_NAME);
ipm_rx_handle = device_get_binding(CONFIG_RPMSG_SERVICE_IPM_RX_NAME);
if (!ipm_tx_handle) {
LOG_ERR("Could not get TX IPM device handle");
return -ENODEV;
}
if (!ipm_rx_handle) {
LOG_ERR("Could not get RX IPM device handle");
return -ENODEV;
}
ipm_register_callback(ipm_rx_handle, ipm_callback, NULL);
#elif defined(CONFIG_RPMSG_SERVICE_SINGLE_IPM_SUPPORT)
ipm_handle = device_get_binding(CONFIG_RPMSG_SERVICE_IPM_NAME);
if (ipm_handle == NULL) {
LOG_ERR("Could not get IPM device handle");
return -ENODEV;
}
ipm_register_callback(ipm_handle, ipm_callback, NULL);
err = ipm_set_enabled(ipm_handle, 1);
if (err != 0) {
LOG_ERR("Could not enable IPM interrupts and callbacks");
return err;
}
#endif
/* Virtqueue setup */
vq[0] = virtqueue_allocate(VRING_SIZE);
if (!vq[0]) {
LOG_ERR("virtqueue_allocate failed to alloc vq[0]");
return -ENOMEM;
}
vq[1] = virtqueue_allocate(VRING_SIZE);
if (!vq[1]) {
LOG_ERR("virtqueue_allocate failed to alloc vq[1]");
return -ENOMEM;
}
rvrings[0].io = *io;
rvrings[0].info.vaddr = (void *)VRING_TX_ADDRESS;
rvrings[0].info.num_descs = VRING_SIZE;
rvrings[0].info.align = VRING_ALIGNMENT;
rvrings[0].vq = vq[0];
rvrings[1].io = *io;
rvrings[1].info.vaddr = (void *)VRING_RX_ADDRESS;
rvrings[1].info.num_descs = VRING_SIZE;
rvrings[1].info.align = VRING_ALIGNMENT;
rvrings[1].vq = vq[1];
vdev->role = RPMSG_ROLE;
vdev->vrings_num = VRING_COUNT;
vdev->func = &dispatch;
vdev->vrings_info = &rvrings[0];
return 0;
}
#if MASTER
/* Make sure we clear out the status flag very early (before we bringup the
* secondary core) so the secondary core see's the proper status
*/
int init_status_flag(const struct device *arg)
{
virtio_set_status(NULL, 0);
return 0;
}
SYS_INIT(init_status_flag, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
#endif /* MASTER */

View file

@ -0,0 +1,44 @@
/*
* Copyright (c) 2021, Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_SUBSYS_IPC_RPMSG_BACKEND_H
#define ZEPHYR_SUBSYS_IPC_RPMSG_BACKEND_H
#include <openamp/rpmsg_virtio.h>
#ifdef __cplusplus
extern "C" {
#endif
#define VDEV_START_ADDR CONFIG_RPMSG_SERVICE_SHM_BASE_ADDRESS
#define VDEV_SIZE CONFIG_RPMSG_SERVICE_SHM_SIZE
#define VDEV_STATUS_ADDR VDEV_START_ADDR
#define VDEV_STATUS_SIZE 0x400
#define SHM_START_ADDR (VDEV_START_ADDR + VDEV_STATUS_SIZE)
#define SHM_SIZE (VDEV_SIZE - VDEV_STATUS_SIZE)
#define SHM_DEVICE_NAME "sramx.shm"
/*
* @brief Initialize RPMsg backend
*
* @param io Shared memory IO region. This is an output parameter providing
* a pointer to an actual shared memory IO region structure.
* Caller of this function shall pass an address at which the
* pointer to the shared memory IO region structure is stored.
* @param vdev Pointer to the virtio device initialized by this function.
*
* @retval 0 Initialization successful
* @retval <0 Initialization error reported by OpenAMP
*/
int rpmsg_backend_init(struct metal_io_region **io, struct virtio_device *vdev);
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_SUBSYS_IPC_RPMSG_BACKEND_H */

View file

@ -0,0 +1,164 @@
/*
* Copyright (c) 2020-2021, Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <ipc/rpmsg_service.h>
#include "rpmsg_backend.h"
#include <zephyr.h>
#include <device.h>
#include <logging/log.h>
#include <openamp/open_amp.h>
#define LOG_LEVEL LOG_LEVEL_INFO
#define LOG_MODULE_NAME rpmsg_service
LOG_MODULE_REGISTER(LOG_MODULE_NAME);
#define MASTER IS_ENABLED(CONFIG_RPMSG_SERVICE_MODE_MASTER)
static struct virtio_device vdev;
static struct rpmsg_virtio_device rvdev;
static struct metal_io_region *io;
static bool ep_crt_started;
#if MASTER
static struct rpmsg_virtio_shm_pool shpool;
#endif
static struct {
const char *name;
rpmsg_ept_cb cb;
struct rpmsg_endpoint ep;
volatile bool bound;
} endpoints[CONFIG_RPMSG_SERVICE_NUM_ENDPOINTS];
static void rpmsg_service_unbind(struct rpmsg_endpoint *ep)
{
rpmsg_destroy_ept(ep);
}
#if MASTER
static void ns_bind_cb(struct rpmsg_device *rdev,
const char *name,
uint32_t dest)
{
int err;
for (int i = 0; i < CONFIG_RPMSG_SERVICE_NUM_ENDPOINTS; ++i) {
if (strcmp(name, endpoints[i].name) == 0) {
err = rpmsg_create_ept(&endpoints[i].ep,
rdev,
name,
RPMSG_ADDR_ANY,
dest,
endpoints[i].cb,
rpmsg_service_unbind);
if (err != 0) {
LOG_ERR("Creating remote endpoint %s"
" failed wirh error %d", name, err);
} else {
endpoints[i].bound = true;
}
return;
}
}
LOG_ERR("Remote endpoint %s not registered locally", name);
}
#endif
static int rpmsg_service_init(const struct device *dev)
{
int32_t err;
(void)dev;
LOG_DBG("RPMsg service initialization start");
err = rpmsg_backend_init(&io, &vdev);
if (err) {
LOG_ERR("RPMsg backend init failed with error %d", err);
return err;
}
#if MASTER
rpmsg_virtio_init_shm_pool(&shpool, (void *)SHM_START_ADDR, SHM_SIZE);
err = rpmsg_init_vdev(&rvdev, &vdev, ns_bind_cb, io, &shpool);
#else
err = rpmsg_init_vdev(&rvdev, &vdev, NULL, io, NULL);
#endif
if (err) {
LOG_ERR("rpmsg_init_vdev failed %d", err);
return err;
}
ep_crt_started = true;
#if !MASTER
struct rpmsg_device *rdev;
rdev = rpmsg_virtio_get_rpmsg_device(&rvdev);
for (int i = 0; i < CONFIG_RPMSG_SERVICE_NUM_ENDPOINTS; ++i) {
if (endpoints[i].name) {
err = rpmsg_create_ept(&endpoints[i].ep,
rdev,
endpoints[i].name,
RPMSG_ADDR_ANY,
RPMSG_ADDR_ANY,
endpoints[i].cb,
rpmsg_service_unbind);
if (err) {
LOG_ERR("rpmsg_create_ept failed %d", err);
return err;
}
}
}
#endif
LOG_DBG("RPMsg service initialized");
return 0;
}
int rpmsg_service_register_endpoint(const char *name, rpmsg_ept_cb cb)
{
if (ep_crt_started) {
return -EINPROGRESS;
}
for (int i = 0; i < CONFIG_RPMSG_SERVICE_NUM_ENDPOINTS; ++i) {
if (!endpoints[i].name) {
endpoints[i].name = name;
endpoints[i].cb = cb;
return i;
}
}
LOG_ERR("No free slots to register endpoint %s", name);
return -ENOMEM;
}
bool rpmsg_service_endpoint_is_bound(int endpoint_id)
{
return endpoints[endpoint_id].bound;
}
int rpmsg_service_send(int endpoint_id, const void *data, size_t len)
{
return rpmsg_send(&endpoints[endpoint_id].ep, data, len);
}
SYS_INIT(rpmsg_service_init, POST_KERNEL, CONFIG_RPMSG_SERVICE_INIT_PRIORITY);