/**************************************************************************** * drivers/rpmsg/rpmsg_virtio.c * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #ifndef ALIGN_UP # define ALIGN_UP(s, a) (((s) + (a) - 1) & ~((a) - 1)) #endif #define RPMSG_VIRTIO_TIMEOUT_MS 20 #define RPMSG_VIRTIO_NOTIFYID 0 /**************************************************************************** * Private Types ****************************************************************************/ struct rpmsg_virtio_priv_s { struct rpmsg_s rpmsg; struct rpmsg_virtio_device rvdev; FAR struct rpmsg_virtio_s *dev; FAR struct rpmsg_virtio_rsc_s *rsc; struct virtio_device vdev; struct rpmsg_virtio_shm_pool pool[2]; struct virtio_vring_info rvrings[2]; sem_t semtx; sem_t semrx; pid_t tid; }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ static int rpmsg_virtio_wait(FAR struct rpmsg_s *rpmsg, FAR sem_t *sem); static int rpmsg_virtio_post(FAR struct rpmsg_s *rpmsg, FAR sem_t *sem); static void rpmsg_virtio_dump(FAR struct rpmsg_s *rpmsg); static FAR const char * rpmsg_virtio_get_local_cpuname(FAR struct rpmsg_s *rpmsg); static FAR const char *rpmsg_virtio_get_cpuname(FAR struct rpmsg_s *rpmsg); static int rpmsg_virtio_get_tx_buffer_size(FAR struct rpmsg_s *rpmsg); static int rpmsg_virtio_get_rx_buffer_size_(FAR struct rpmsg_s *rpmsg); static int rpmsg_virtio_create_virtqueues_(FAR struct virtio_device *vdev, unsigned int flags, unsigned int nvqs, FAR const char *names[], vq_callback callbacks[]); static uint8_t rpmsg_virtio_get_status_(FAR struct virtio_device *dev); static void rpmsg_virtio_set_status_(FAR struct virtio_device *dev, uint8_t status); static uint32_t rpmsg_virtio_get_features_(FAR struct virtio_device *dev); static void rpmsg_virtio_set_features(FAR struct virtio_device *dev, uint32_t feature); static void rpmsg_virtio_notify(FAR struct virtqueue *vq); /**************************************************************************** * Private Data ****************************************************************************/ static const struct rpmsg_ops_s g_rpmsg_virtio_ops = { .wait = rpmsg_virtio_wait, .post = rpmsg_virtio_post, .dump = rpmsg_virtio_dump, .get_local_cpuname = rpmsg_virtio_get_local_cpuname, .get_cpuname = rpmsg_virtio_get_cpuname, .get_tx_buffer_size = rpmsg_virtio_get_tx_buffer_size, .get_rx_buffer_size = rpmsg_virtio_get_rx_buffer_size_, }; static const struct virtio_dispatch g_rpmsg_virtio_dispatch = { .create_virtqueues = rpmsg_virtio_create_virtqueues_, .get_status = rpmsg_virtio_get_status_, .set_status = rpmsg_virtio_set_status_, .get_features = rpmsg_virtio_get_features_, .set_features = rpmsg_virtio_set_features, .notify = rpmsg_virtio_notify, }; /**************************************************************************** * Private Functions ****************************************************************************/ static FAR struct rpmsg_virtio_priv_s * rpmsg_virtio_get_priv(FAR struct virtio_device *vdev) { FAR struct rpmsg_virtio_device *rvdev = vdev->priv; return metal_container_of(rvdev, struct rpmsg_virtio_priv_s, rvdev); } static int rpmsg_virtio_create_virtqueues_(FAR struct virtio_device *vdev, unsigned int flags, unsigned int nvqs, FAR const char *names[], vq_callback callbacks[]) { int ret; int i; if (nvqs > vdev->vrings_num) { return ERROR_VQUEUE_INVLD_PARAM; } /* Initialize virtqueue for each vring */ for (i = 0; i < nvqs; i++) { FAR struct virtio_vring_info *vinfo = &vdev->vrings_info[i]; FAR struct vring_alloc_info *valloc = &vinfo->info; #ifndef CONFIG_OPENAMP_VIRTIO_DEVICE_ONLY if (vdev->role == VIRTIO_DEV_DRIVER) { size_t offset; offset = metal_io_virt_to_offset(vinfo->io, valloc->vaddr); metal_io_block_set(vinfo->io, offset, 0, vring_size(valloc->num_descs, valloc->align)); } #endif ret = virtqueue_create(vdev, i, names[i], valloc, callbacks[i], vdev->func->notify, vinfo->vq); if (ret < 0) { return ret; } } return 0; } static uint8_t rpmsg_virtio_get_status_(FAR struct virtio_device *vdev) { FAR struct rpmsg_virtio_priv_s *priv = rpmsg_virtio_get_priv(vdev); return priv->rsc->rpmsg_vdev.status; } static void rpmsg_virtio_set_status_(FAR struct virtio_device *vdev, uint8_t status) { FAR struct rpmsg_virtio_priv_s *priv = rpmsg_virtio_get_priv(vdev); priv->rsc->rpmsg_vdev.status = status; } static uint32_t rpmsg_virtio_get_features_(FAR struct virtio_device *vdev) { FAR struct rpmsg_virtio_priv_s *priv = rpmsg_virtio_get_priv(vdev); return priv->rsc->rpmsg_vdev.dfeatures; } static void rpmsg_virtio_set_features(FAR struct virtio_device *vdev, uint32_t features) { FAR struct rpmsg_virtio_priv_s *priv = rpmsg_virtio_get_priv(vdev); priv->rsc->rpmsg_vdev.gfeatures = features; } static void rpmsg_virtio_notify(FAR struct virtqueue *vq) { FAR struct virtio_device *vdev = vq->vq_dev; FAR struct rpmsg_virtio_priv_s *priv = rpmsg_virtio_get_priv(vdev); RPMSG_VIRTIO_NOTIFY(priv->dev, vdev->vrings_info->notifyid); } static bool rpmsg_virtio_is_recursive(FAR struct rpmsg_virtio_priv_s *priv) { return nxsched_gettid() == priv->tid; } static int rpmsg_virtio_wait(FAR struct rpmsg_s *rpmsg, FAR sem_t *sem) { FAR struct rpmsg_virtio_priv_s *priv = (FAR struct rpmsg_virtio_priv_s *)rpmsg; int ret; if (!rpmsg_virtio_is_recursive(priv)) { return nxsem_wait_uninterruptible(sem); } while (1) { ret = nxsem_trywait(sem); if (ret >= 0) { break; } nxsem_wait(&priv->semtx); virtqueue_notification(priv->rvdev.rvq); } return ret; } static void rpmsg_virtio_wakeup_tx(FAR struct rpmsg_virtio_priv_s *priv) { int semcount; nxsem_get_value(&priv->semtx, &semcount); while (semcount++ < 1) { nxsem_post(&priv->semtx); } } static int rpmsg_virtio_post(FAR struct rpmsg_s *rpmsg, FAR sem_t *sem) { FAR struct rpmsg_virtio_priv_s *priv = (FAR struct rpmsg_virtio_priv_s *)rpmsg; int semcount; int ret; nxsem_get_value(sem, &semcount); ret = nxsem_post(sem); if (priv && semcount >= 0) { rpmsg_virtio_wakeup_tx(priv); } return ret; } #ifdef CONFIG_OPENAMP_DEBUG static int rpmsg_virtio_buffer_nused(FAR struct rpmsg_virtio_device *rvdev, bool rx) { FAR struct virtqueue *vq = rx ? rvdev->rvq : rvdev->svq; uint16_t nused = vq->vq_ring.avail->idx - vq->vq_ring.used->idx; if ((rpmsg_virtio_get_role(rvdev) == RPMSG_HOST) ^ rx) { return nused; } else { return vq->vq_nentries - nused; } } static void rpmsg_virtio_dump_buffer(FAR struct rpmsg_virtio_device *rvdev, bool rx) { FAR struct virtqueue *vq = rx ? rvdev->rvq : rvdev->svq; int num; int i; num = rpmsg_virtio_buffer_nused(rvdev, rx); metal_log(METAL_LOG_EMERGENCY, " %s buffer, total %d, pending %d\n", rx ? "RX" : "TX", vq->vq_nentries, num); for (i = 0; i < num; i++) { FAR void *addr; int desc_idx; if ((rpmsg_virtio_get_role(rvdev) == RPMSG_HOST) ^ rx) { desc_idx = (vq->vq_ring.used->idx + i) & (vq->vq_nentries - 1); desc_idx = vq->vq_ring.avail->ring[desc_idx]; } else { desc_idx = (vq->vq_ring.avail->idx + i) & (vq->vq_nentries - 1); desc_idx = vq->vq_ring.used->ring[desc_idx].id; } addr = metal_io_phys_to_virt(vq->shm_io, vq->vq_ring.desc[desc_idx].addr); if (addr) { FAR struct rpmsg_hdr *hdr = addr; FAR struct rpmsg_endpoint *ept; ept = rpmsg_get_ept_from_addr(&rvdev->rdev, rx ? hdr->dst : hdr->src); if (ept) { metal_log(METAL_LOG_EMERGENCY, " %s buffer %p hold by %s\n", rx ? "RX" : "TX", hdr, ept->name); } } } } static void rpmsg_virtio_dump(FAR struct rpmsg_s *rpmsg) { FAR struct rpmsg_virtio_priv_s *priv = (FAR struct rpmsg_virtio_priv_s *)rpmsg; FAR struct rpmsg_virtio_device *rvdev = &priv->rvdev; FAR struct rpmsg_device *rdev = rpmsg->rdev; FAR struct rpmsg_endpoint *ept; FAR struct metal_list *node; bool needlock = true; if (!rvdev->vdev) { return; } if (up_interrupt_context() || sched_idletask() || nxmutex_is_hold(&rdev->lock)) { needlock = false; } if (needlock) { metal_mutex_acquire(&rdev->lock); } metal_log(METAL_LOG_EMERGENCY, "Dump rpmsg info between cpu (master: %s)%s <==> %s:\n", rpmsg_virtio_get_role(rvdev) == RPMSG_HOST ? "yes" : "no", CONFIG_RPMSG_LOCAL_CPUNAME, rpmsg_get_cpuname(rdev)); metal_log(METAL_LOG_EMERGENCY, "rpmsg vq RX:\n"); virtqueue_dump(rvdev->rvq); metal_log(METAL_LOG_EMERGENCY, "rpmsg vq TX:\n"); virtqueue_dump(rvdev->svq); metal_log(METAL_LOG_EMERGENCY, " rpmsg ept list:\n"); metal_list_for_each(&rdev->endpoints, node) { ept = metal_container_of(node, struct rpmsg_endpoint, node); metal_log(METAL_LOG_EMERGENCY, " ept %s\n", ept->name); } metal_log(METAL_LOG_EMERGENCY, " rpmsg buffer list:\n"); rpmsg_virtio_dump_buffer(rvdev, true); rpmsg_virtio_dump_buffer(rvdev, false); if (needlock) { metal_mutex_release(&rdev->lock); } } #else static void rpmsg_virtio_dump(FAR struct rpmsg_s *rpmsg) { /* Nothing */ } #endif static FAR const char * rpmsg_virtio_get_local_cpuname(FAR struct rpmsg_s *rpmsg) { FAR struct rpmsg_virtio_priv_s *priv = (FAR struct rpmsg_virtio_priv_s *)rpmsg; return RPMSG_VIRTIO_GET_LOCAL_CPUNAME(priv->dev); } static FAR const char *rpmsg_virtio_get_cpuname(FAR struct rpmsg_s *rpmsg) { FAR struct rpmsg_virtio_priv_s *priv = (FAR struct rpmsg_virtio_priv_s *)rpmsg; return RPMSG_VIRTIO_GET_CPUNAME(priv->dev); } static int rpmsg_virtio_get_tx_buffer_size(FAR struct rpmsg_s *rpmsg) { return rpmsg_virtio_get_buffer_size(rpmsg->rdev); } static int rpmsg_virtio_get_rx_buffer_size_(FAR struct rpmsg_s *rpmsg) { return rpmsg_virtio_get_rx_buffer_size(rpmsg->rdev); } static void rpmsg_virtio_wakeup_rx(FAR struct rpmsg_virtio_priv_s *priv) { int semcount; nxsem_get_value(&priv->semrx, &semcount); if (semcount < 1) { nxsem_post(&priv->semrx); } } static int rpmsg_virtio_callback(FAR void *arg, uint32_t vqid) { FAR struct rpmsg_virtio_priv_s *priv = arg; FAR struct rpmsg_virtio_device *rvdev = &priv->rvdev; FAR struct virtio_device *vdev = rvdev->vdev; FAR struct virtqueue *rvq = rvdev->rvq; if (vqid == RPMSG_VIRTIO_NOTIFY_ALL || vqid == vdev->vrings_info[rvq->vq_queue_index].notifyid) { rpmsg_virtio_wakeup_rx(priv); } return OK; } static int rpmsg_virtio_notify_wait(FAR struct rpmsg_device *rdev, uint32_t id) { FAR struct rpmsg_virtio_priv_s *priv = metal_container_of(rdev, struct rpmsg_virtio_priv_s, rvdev.rdev); if (!rpmsg_virtio_is_recursive(priv)) { return -EAGAIN; } /* Wait to wakeup */ nxsem_tickwait(&priv->semtx, MSEC2TICK(RPMSG_VIRTIO_TIMEOUT_MS)); virtqueue_notification(priv->rvdev.rvq); return 0; } static int rpmsg_virtio_start(FAR struct rpmsg_virtio_priv_s *priv) { FAR struct virtio_vring_info *rvrings = priv->rvrings; FAR struct virtio_device *vdev = &priv->vdev; FAR struct rpmsg_virtio_rsc_s *rsc; struct rpmsg_virtio_config config; FAR void *shbuf0; FAR void *shbuf1; uint32_t align0; uint32_t align1; uint32_t tbsz; uint32_t v0sz; uint32_t v1sz; uint32_t shbufsz0; uint32_t shbufsz1; int ret; rsc = RPMSG_VIRTIO_GET_RESOURCE(priv->dev); if (!rsc) { return -EINVAL; } priv->rsc = rsc; vdev->notifyid = RPMSG_VIRTIO_NOTIFYID; vdev->vrings_num = rsc->rpmsg_vdev.num_of_vrings; vdev->role = RPMSG_VIRTIO_IS_MASTER(priv->dev) ? RPMSG_HOST : RPMSG_REMOTE; vdev->func = &g_rpmsg_virtio_dispatch; align0 = rsc->rpmsg_vring0.align; align1 = rsc->rpmsg_vring1.align; tbsz = ALIGN_UP(sizeof(struct rpmsg_virtio_rsc_s), MAX(align0, align1)); v0sz = ALIGN_UP(vring_size(rsc->rpmsg_vring0.num, align0), align0); v1sz = ALIGN_UP(vring_size(rsc->rpmsg_vring1.num, align1), align1); shbuf0 = (FAR char *)rsc + tbsz + v0sz + v1sz; shbufsz0 = rsc->config.r2h_buf_size * rsc->rpmsg_vring0.num; shbuf1 = shbuf0 + shbufsz0; shbufsz1 = rsc->config.h2r_buf_size * rsc->rpmsg_vring1.num; rvrings[0].io = metal_io_get_region(); rvrings[0].info.vaddr = (FAR char *)rsc + tbsz; rvrings[0].info.num_descs = rsc->rpmsg_vring0.num; rvrings[0].info.align = rsc->rpmsg_vring0.align; rvrings[0].vq = virtqueue_allocate(rsc->rpmsg_vring0.num); if (rvrings[0].vq == NULL) { return -ENOMEM; } rvrings[1].io = metal_io_get_region(); rvrings[1].info.vaddr = (FAR char *)rsc + tbsz + v0sz; rvrings[1].info.num_descs = rsc->rpmsg_vring1.num; rvrings[1].info.align = rsc->rpmsg_vring1.align; rvrings[1].vq = virtqueue_allocate(rsc->rpmsg_vring1.num); if (rvrings[1].vq == NULL) { ret = -ENOMEM; goto err_vq0; } vdev->vrings_info = &rvrings[0]; rpmsg_virtio_init_shm_pool(&priv->pool[0], shbuf0, shbufsz0); rpmsg_virtio_init_shm_pool(&priv->pool[1], shbuf1, shbufsz1); config.h2r_buf_size = rsc->config.h2r_buf_size; config.r2h_buf_size = rsc->config.r2h_buf_size; config.split_shpool = true; ret = rpmsg_init_vdev_with_config(&priv->rvdev, vdev, rpmsg_ns_bind, metal_io_get_region(), priv->pool, &config); if (ret != 0) { rpmsgerr("rpmsg_init_vdev failed %d\n", ret); ret = -ENOMEM; goto err_vq1; } priv->rvdev.rdev.ns_unbind_cb = rpmsg_ns_unbind; priv->rvdev.rdev.notify_wait_cb = rpmsg_virtio_notify_wait; RPMSG_VIRTIO_REGISTER_CALLBACK(priv->dev, rpmsg_virtio_callback, priv); rpmsg_virtio_wakeup_rx(priv); /* Broadcast device_created to all registers */ rpmsg_device_created(&priv->rpmsg); return 0; err_vq1: virtqueue_free(rvrings[1].vq); err_vq0: virtqueue_free(rvrings[0].vq); return ret; } static int rpmsg_virtio_thread(int argc, FAR char *argv[]) { FAR struct rpmsg_virtio_priv_s *priv = (FAR struct rpmsg_virtio_priv_s *) ((uintptr_t)strtoul(argv[2], NULL, 0)); int ret; priv->tid = nxsched_gettid(); ret = rpmsg_virtio_start(priv); if (ret < 0) { rpmsgerr("rpmsg virtio thread start failed %d\n", ret); return ret; } while (1) { nxsem_wait_uninterruptible(&priv->semrx); virtqueue_notification(priv->rvdev.rvq); } return 0; } /**************************************************************************** * Public Functions ****************************************************************************/ int rpmsg_virtio_initialize(FAR struct rpmsg_virtio_s *dev) { FAR struct rpmsg_virtio_priv_s *priv; FAR char *argv[3]; char arg1[32]; char name[32]; int ret; priv = kmm_zalloc(sizeof(struct rpmsg_virtio_priv_s)); if (priv == NULL) { return -ENOMEM; } priv->dev = dev; nxsem_init(&priv->semrx, 0, 0); nxsem_init(&priv->semtx, 0, 0); snprintf(name, sizeof(name), "/dev/rpmsg/%s", RPMSG_VIRTIO_GET_CPUNAME(dev)); ret = rpmsg_register(name, &priv->rpmsg, &g_rpmsg_virtio_ops); if (ret < 0) { goto err_driver; } snprintf(arg1, sizeof(arg1), "%p", priv); argv[0] = (FAR char *)RPMSG_VIRTIO_GET_CPUNAME(dev); argv[1] = arg1; argv[2] = NULL; ret = kthread_create("rpmsg_virtio", CONFIG_RPMSG_VIRTIO_PRIORITY, CONFIG_RPMSG_VIRTIO_STACKSIZE, rpmsg_virtio_thread, argv); if (ret < 0) { goto err_thread; } return OK; err_thread: rpmsg_unregister(name, &priv->rpmsg); err_driver: nxsem_destroy(&priv->semrx); nxsem_destroy(&priv->semtx); kmm_free(priv); return ret; }