You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
269 lines
6.8 KiB
269 lines
6.8 KiB
/* |
|
* 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 */
|
|
|