A virtual metal_device is created, next the needed IO regions are created and added to this device. Immediately we extract these regions back out and make use of them. There is no reason to create the metal_device and add the IO regions to it, instead simply use the IO regions directly. This is similar to what was already done to the openamp_rsc_table sample. Signed-off-by: Andrew Davis <afd@ti.com>
270 lines
6.8 KiB
C
270 lines
6.8 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>
|
|
|
|
#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_io_region shm_io;
|
|
|
|
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;
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* declare shared memory region */
|
|
metal_io_init(&shm_io, (void *)SHM_START_ADDR, shm_physmap, SHM_SIZE, -1, 0, NULL);
|
|
*io = &shm_io;
|
|
|
|
/* 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 */
|