zephyr/subsys/ipc/rpmsg_service/rpmsg_backend.c
Celina Sophie Kalus 19c8fa2441 ipc: rpmsg_service: Support IPM without data transfer
RPMsg currently sends four dummy bytes over the IPM when sending a
notification/interrupt to another CPU. However, most IPM drivers
support sending messages with no data transfer, and for some IPM
drivers, like the STM32 HSEM IPM driver, data transfer is not
possible at all, thus breaking compatibility. To fix this, send an
empty message with size 0 instead of a dummy message if the STM32
HSEM IPM driver is active.

Signed-off-by: Celina Sophie Kalus <hello@celinakalus.de>
2024-02-26 11:39:29 +00:00

304 lines
7.5 KiB
C

/*
* Copyright (c) 2021, Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "rpmsg_backend.h"
#include <zephyr/kernel.h>
#include <zephyr/drivers/ipm.h>
#include <zephyr/device.h>
#include <zephyr/init.h>
#include <zephyr/logging/log.h>
#include <openamp/open_amp.h>
#include <metal/device.h>
#define LOG_MODULE_NAME rpmsg_backend
LOG_MODULE_REGISTER(LOG_MODULE_NAME, CONFIG_RPMSG_SERVICE_LOG_LEVEL);
/* 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_HOST
#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
#define IPM_WORK_QUEUE_PRIORITY K_HIGHEST_APPLICATION_THREAD_PRIO
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 *const ipm_tx_handle =
DEVICE_DT_GET(DT_CHOSEN(zephyr_ipc_tx));
static const struct device *const ipm_rx_handle =
DEVICE_DT_GET(DT_CHOSEN(zephyr_ipc_rx));
#elif defined(CONFIG_RPMSG_SERVICE_SINGLE_IPM_SUPPORT)
static const struct device *const ipm_handle =
DEVICE_DT_GET(DT_CHOSEN(zephyr_ipc));
#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 *vqueue[2];
static struct k_work ipm_work;
static unsigned char ipc_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 ipc_virtio_set_status(struct virtio_device *vdev, unsigned char status)
{
sys_write8(status, VDEV_STATUS_ADDR);
}
static uint32_t ipc_virtio_get_features(struct virtio_device *vdev)
{
return BIT(VIRTIO_RPMSG_F_NS);
}
static void ipc_virtio_set_features(struct virtio_device *vdev, uint32_t features)
{
}
static void ipc_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_B1)
uint32_t current_core = sse_200_platform_get_cpu_id();
status = ipm_send(ipm_handle, 0, current_core ? 0 : 1, 0, 1);
#elif defined(CONFIG_IPM_STM32_HSEM)
/* No data transfer, only doorbell. */
status = ipm_send(ipm_handle, 0, 0, NULL, 0);
#else
/* The IPM interface is unclear on whether or not ipm_send
* can be called with NULL as data, thus, drivers might cause
* problems if you do. To avoid problems, we always send some
* dummy data, unless the IPM driver cannot transfer data.
* Ref: #68741
*/
uint32_t dummy_data = 0x55005500;
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 = ipc_virtio_get_status,
.set_status = ipc_virtio_set_status,
.get_features = ipc_virtio_get_features,
.set_features = ipc_virtio_set_features,
.notify = ipc_virtio_notify,
};
static void ipm_callback_process(struct k_work *work)
{
virtqueue_notification(vqueue[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_queue_start(&ipm_work_q, ipm_stack_area,
K_THREAD_STACK_SIZEOF(ipm_stack_area),
IPM_WORK_QUEUE_PRIORITY, NULL);
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)
if (!device_is_ready(ipm_tx_handle)) {
LOG_ERR("IPM TX device is not ready");
return -ENODEV;
}
if (!device_is_ready(ipm_rx_handle)) {
LOG_ERR("IPM RX device is not ready");
return -ENODEV;
}
ipm_register_callback(ipm_rx_handle, ipm_callback, NULL);
err = ipm_set_enabled(ipm_rx_handle, 1);
if (err != 0) {
LOG_ERR("Could not enable IPM interrupts and callbacks for RX");
return err;
}
#elif defined(CONFIG_RPMSG_SERVICE_SINGLE_IPM_SUPPORT)
if (!device_is_ready(ipm_handle)) {
LOG_ERR("IPM device is not ready");
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 */
vqueue[0] = virtqueue_allocate(VRING_SIZE);
if (!vqueue[0]) {
LOG_ERR("virtqueue_allocate failed to alloc vqueue[0]");
return -ENOMEM;
}
vqueue[1] = virtqueue_allocate(VRING_SIZE);
if (!vqueue[1]) {
LOG_ERR("virtqueue_allocate failed to alloc vqueue[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 = vqueue[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 = vqueue[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(void)
{
ipc_virtio_set_status(NULL, 0);
return 0;
}
SYS_INIT(init_status_flag, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
#endif /* MASTER */