+config VHOST_SOF
+ tristate "Vhost SOF driver"
+ depends on SND_SOC_SOF
you probably want to make sure VHOST_SOF and SND_SOC_SOF are both
built-in or module. Without constraints you are likely to trigger errors
with randconfig. It's a classic.
+ select VHOST
+ select VHOST_RPMSG
+ default n
not needed, default is always no.
+ ---help---
+ SOF vhost VirtIO driver. It exports the same IPC interface, as the
+ one, used for Audio DSP communication, to Linux VirtIO guests.
[...]
+struct vhost_dsp {
+ struct vhost_rpmsg vrdev;
+
+ struct sof_vhost_client *snd;
+
+ bool active;
I am struggling with this definition, it seems to be a local flag but
how is it aligned to the actual DSP status?
In other words, can you have cases where the dsp is considered 'active'
here but might be pm_runtime suspended?
I am sure you've thought of it based on previous threads, it'd be worth
adding comments.
+
+ /* RPMsg address of the position update endpoint */
+ u32 posn_addr;
+ /* position update buffer and work */
+ struct vhost_work posn_work;
+ struct sof_ipc_stream_posn posn;
+
+ /* IPC request buffer */
+ struct sof_rpmsg_ipc_req ipc_buf;
+ /* IPC response buffer */
+ u8 reply_buf[SOF_IPC_MSG_MAX_SIZE];
+ /*
+ * data response header, captured audio data is copied directly from the
+ * DMA buffer
so zero-copy is not supported for now, right?
+ */
+ struct sof_rpmsg_data_resp data_resp;
+};
+
+/* A guest is booting */
+static int vhost_dsp_activate(struct vhost_dsp *dsp)
+{
+ unsigned int i;
+ int ret = 0;
+
+ mutex_lock(&dsp->vrdev.dev.mutex);
+
+ /* Wait until all the VirtQueues have been initialised */
+ if (!dsp->active) {
+ struct vhost_virtqueue *vq;
+
+ for (i = 0, vq = dsp->vrdev.vq;
+ i < ARRAY_SIZE(dsp->vrdev.vq);
+ i++, vq++) {
+ /* .private_data is required != NULL */
+ vq->private_data = vq;
+ /* needed for re-initialisation upon guest reboot */
+ ret = vhost_vq_init_access(vq);
+ if (ret)
+ vq_err(vq,
+ "%s(): error %d initialising vq #%d\n",
+ __func__, ret, i);
+ }
+
+ /* Send an RPMsg namespace announcement */
+ if (!ret && !vhost_rpmsg_ns_announce(&dsp->vrdev, "sof_rpmsg",
+ SOF_RPMSG_ADDR_IPC))
+ dsp->active = true;
+ }
+
+ mutex_unlock(&dsp->vrdev.dev.mutex);
+
+ return ret;
+}
+
+/* A guest is powered off or reset */
+static void vhost_dsp_deactivate(struct vhost_dsp *dsp)
+{
+ unsigned int i;
+
+ mutex_lock(&dsp->vrdev.dev.mutex);
+
+ if (dsp->active) {
+ struct vhost_virtqueue *vq;
+
+ dsp->active = false;
can you explain why this is not symmetrical with _activate above: there
is no rmpsg communication here?
+
+ /* If a VM reboots sof_vhost_client_release() isn't called */
+ sof_vhost_topology_purge(dsp->snd);
+
+ /* signal, that we're inactive */
+ for (i = 0, vq = dsp->vrdev.vq;
+ i < ARRAY_SIZE(dsp->vrdev.vq);
+ i++, vq++) {
+ mutex_lock(&vq->mutex);
+ vq->private_data = NULL;
+ mutex_unlock(&vq->mutex);
+ }
+ }
+
+ mutex_unlock(&dsp->vrdev.dev.mutex);
+}
+
[...]
+/* .ioctl(): we only use VHOST_SET_RUNNING in a non-default way */
+static long vhost_dsp_ioctl(struct file *filp, unsigned int ioctl,
+ unsigned long arg)
+{
+ struct vhost_dsp *dsp = filp->private_data;
+ void __user *argp = (void __user *)arg;
+ struct vhost_adsp_topology tplg;
+ u64 __user *featurep = argp;
+ u64 features;
+ int start;
+ long ret;
+
+ switch (ioctl) {
+ case VHOST_GET_FEATURES:
+ features = VHOST_DSP_FEATURES;
+ if (copy_to_user(featurep, &features, sizeof(features)))
+ return -EFAULT;
+ return 0;
+ case VHOST_SET_FEATURES:
+ if (copy_from_user(&features, featurep, sizeof(features)))
+ return -EFAULT;
+ return vhost_dsp_set_features(dsp, features);
+ case VHOST_GET_BACKEND_FEATURES:
+ features = 0;
+ if (copy_to_user(featurep, &features, sizeof(features)))
+ return -EFAULT;
+ return 0;
+ case VHOST_SET_BACKEND_FEATURES:
+ if (copy_from_user(&features, featurep, sizeof(features)))
+ return -EFAULT;
+ if (features)
+ return -EOPNOTSUPP;
+ return 0;
+ case VHOST_RESET_OWNER:
+ mutex_lock(&dsp->vrdev.dev.mutex);
+ ret = vhost_dev_check_owner(&dsp->vrdev.dev);
+ if (!ret) {
+ struct vhost_umem *umem =
+ vhost_dev_reset_owner_prepare();
+ if (!umem) {
+ ret = -ENOMEM;
+ } else {
+ vhost_dev_stop(&dsp->vrdev.dev);
+ vhost_dev_reset_owner(&dsp->vrdev.dev, umem);
+ }
+ }
+ mutex_unlock(&dsp->vrdev.dev.mutex);
+ return ret;
+ case VHOST_SET_OWNER:
+ mutex_lock(&dsp->vrdev.dev.mutex);
+ ret = vhost_dev_set_owner(&dsp->vrdev.dev);
+ mutex_unlock(&dsp->vrdev.dev.mutex);
+ return ret;
+ case VHOST_SET_RUNNING:
+ if (copy_from_user(&start, argp, sizeof(start)))
+ return -EFAULT;
+
+ if (start)
+ return vhost_dsp_activate(dsp);
+
+ vhost_dsp_deactivate(dsp);
+ return 0;
+ case VHOST_ADSP_SET_GUEST_TPLG:
+ if (copy_from_user(&tplg, argp, sizeof(tplg)))
+ return -EFAULT;
+ return sof_vhost_set_tplg(dsp->snd, &tplg);
+ }
All cases seem to use return, so the the code below seems to be the
default: case?
+ mutex_lock(&dsp->vrdev.dev.mutex);
+ ret = vhost_dev_ioctl(&dsp->vrdev.dev, ioctl, argp);
+ if (ret == -ENOIOCTLCMD)
+ ret = vhost_vring_ioctl(&dsp->vrdev.dev, ioctl, argp);
+ mutex_unlock(&dsp->vrdev.dev.mutex);
+
+ return ret;
+}
[...]
+static ssize_t vhost_dsp_ipc_write(struct vhost_rpmsg *vr,
+ struct vhost_rpmsg_iter *iter)
+{
+ struct vhost_dsp *dsp = container_of(vr, struct vhost_dsp, vrdev);
+
+ return vhost_rpmsg_copy(vr, iter, dsp->reply_buf,
+ vhost_rpmsg_iter_len(iter)) ==
+ vhost_rpmsg_iter_len(iter) ? 0 : -EIO;
+}
This is rather convoluted code, with the same function called on both
sides of a comparison.
+
+/* Called only once to get guest's position update endpoint address */
+static ssize_t vhost_dsp_posn_read(struct vhost_rpmsg *vr,
+ struct vhost_rpmsg_iter *iter)
+{
+ struct vhost_dsp *dsp = container_of(vr, struct vhost_dsp, vrdev);
+ struct vhost_virtqueue *vq = &dsp->vrdev.vq[VIRTIO_RPMSG_REQUEST];
+ size_t len = vhost_rpmsg_iter_len(iter);
+ size_t nbytes;
+
+ if ((int)dsp->posn_addr >= 0) {
posn_addr is defined as a u32, so what are you trying to test here?
+ vq_err(vq, "%s(): position queue address %u already set\n",
+ __func__, dsp->posn_addr);
+ return -EINVAL;
+ }
+
+ if (len != sizeof(dsp->posn_addr)) {
that also seems suspicious:
+ /* RPMsg address of the position update endpoint */
+ u32 posn_addr;
why would a length which should have different values have anything to
do with a constant 4 bytes?
+ vq_err(vq, "%s(): data count %zu invalid\n",
+ __func__, len);
+ return -EINVAL;
+ }
+
+ nbytes = vhost_rpmsg_copy(vr, iter, &dsp->posn_addr,
+ sizeof(dsp->posn_addr));
+ if (nbytes != sizeof(dsp->posn_addr)) {
and again here?
+ vq_err(vq,
+ "%s(): got %zu instead of %zu bytes position update\n",
+ __func__, nbytes, sizeof(dsp->posn_addr));
+ return -EIO;
+ }
+
+ pr_debug("%s(): guest position endpoint address 0x%x\n", __func__,
+ dsp->posn_addr);
+
+ return 0;
+}
+
[...]
+static int vhost_dsp_open(struct inode *inode, struct file *filp)
+{
+ struct miscdevice *misc = filp->private_data;
+ struct snd_sof_dev *sdev = dev_get_drvdata(misc->parent);
+ struct vhost_dsp *dsp = kmalloc(sizeof(*dsp), GFP_KERNEL);
+
+ if (!dsp)
+ return -ENOMEM;
+
+ dsp->vrdev.dev.mm = NULL;
+ dsp->snd = sof_vhost_client_add(sdev, dsp);
+ if (!dsp->snd) {
+ kfree(dsp);
+ return -ENOMEM;
+ }
+
+ /*
+ * TODO: do we ever want to support multiple guest machines per DSP, if
That is a basic assumption yes.
+ * not, we might as well perform all allocations when registering the
+ * misc device.
+ */
+ dsp->active = false;
+ dsp->posn_addr = -EINVAL;
+ dsp->posn.rhdr.error = -ENODATA;
+
+ vhost_rpmsg_init(&dsp->vrdev, vhost_dsp_ept, ARRAY_SIZE(vhost_dsp_ept));
+ vhost_work_init(&dsp->posn_work, vhost_dsp_send_posn);
+
+ /* Overwrite file private data */
+ filp->private_data = dsp;
+
+ return 0;
+}
[...]
+/* Always called from an interrupt thread context */
+static int vhost_dsp_update_posn(struct vhost_dsp *dsp,
+ struct sof_ipc_stream_posn *posn)
+{
+ struct vhost_virtqueue *vq = &dsp->vrdev.vq[VIRTIO_RPMSG_RESPONSE];
+ int ret;
+
+ if (!dsp->active)
+ return 0;
is there a case where you can get a position update without the dsp
being active?
And shouldn't this be an error?
+
+ memcpy(&dsp->posn, posn, sizeof(dsp->posn));
+
+ mutex_lock(&vq->mutex);
+
+ /*
+ * VirtQueues can only be processed in the context of the VMM process or
+ * a vhost work queue
+ */
+ vhost_work_queue(&dsp->vrdev.dev, &dsp->posn_work);
+
+ mutex_unlock(&vq->mutex);
+
+ return ret;