/* * Copyright (c) 2021, Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include "rpmsg_backend.h" #include #include #include #include #include #include #include #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 */