nuttx/drivers/rpmsg/rpmsg_virtio.c
Bowen Wang ab47465dd5 drivers/rpmsg: add get_local_cpuname to rpmsg ops
Add get_local_cpuname to the rpmsg framework ops to support communicate
with the same remote core with multi rpmsg transport.

Some rpmsg services will send local cpu name to remote core and then let
remote core to connect local core by using this cpu name, when there are
multi rpmsg channels with same remote core, the remote core may connect
to incorrect core, so use the error rpmsg channel.

For example, there are two rpmsg channels between ap and audio:

ap core                     audio core
 [ap1] <-- rpmsg virtio1 --> [audio1]
 [ap2] <-- rpmsg virtio2 --> [audio2]

When we want to use the rpmsg virtio1 to communicate, ap core may send
local cpuname "ap2" to audio, so the audio core use remote cpu "ap2" to
connect with ap, and resulting in the use of incorrect rpmsg channel.

Signed-off-by: Bowen Wang <wangbowen6@xiaomi.com>
2024-09-17 01:55:51 +08:00

648 lines
18 KiB
C

/****************************************************************************
* 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 <nuttx/config.h>
#include <debug.h>
#include <stdio.h>
#include <sys/param.h>
#include <nuttx/kmalloc.h>
#include <nuttx/kthread.h>
#include <nuttx/semaphore.h>
#include <nuttx/rpmsg/rpmsg_virtio.h>
#include <rpmsg/rpmsg_internal.h>
/****************************************************************************
* 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;
}