Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Anton Yakovlev | 1678 | 98.65% | 9 | 69.23% |
Jiri Pirko | 20 | 1.18% | 2 | 15.38% |
Michael S. Tsirkin | 2 | 0.12% | 1 | 7.69% |
Chen Huang | 1 | 0.06% | 1 | 7.69% |
Total | 1701 | 13 |
// SPDX-License-Identifier: GPL-2.0+ /* * virtio-snd: Virtio sound device * Copyright (C) 2021 OpenSynergy GmbH */ #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/virtio_config.h> #include <sound/initval.h> #include <uapi/linux/virtio_ids.h> #include "virtio_card.h" u32 virtsnd_msg_timeout_ms = MSEC_PER_SEC; module_param_named(msg_timeout_ms, virtsnd_msg_timeout_ms, uint, 0644); MODULE_PARM_DESC(msg_timeout_ms, "Message completion timeout in milliseconds"); static void virtsnd_remove(struct virtio_device *vdev); /** * virtsnd_event_send() - Add an event to the event queue. * @vqueue: Underlying event virtqueue. * @event: Event. * @notify: Indicates whether or not to send a notification to the device. * @gfp: Kernel flags for memory allocation. * * Context: Any context. */ static void virtsnd_event_send(struct virtqueue *vqueue, struct virtio_snd_event *event, bool notify, gfp_t gfp) { struct scatterlist sg; struct scatterlist *psgs[1] = { &sg }; /* reset event content */ memset(event, 0, sizeof(*event)); sg_init_one(&sg, event, sizeof(*event)); if (virtqueue_add_sgs(vqueue, psgs, 0, 1, event, gfp) || !notify) return; if (virtqueue_kick_prepare(vqueue)) virtqueue_notify(vqueue); } /** * virtsnd_event_dispatch() - Dispatch an event from the device side. * @snd: VirtIO sound device. * @event: VirtIO sound event. * * Context: Any context. */ static void virtsnd_event_dispatch(struct virtio_snd *snd, struct virtio_snd_event *event) { switch (le32_to_cpu(event->hdr.code)) { case VIRTIO_SND_EVT_JACK_CONNECTED: case VIRTIO_SND_EVT_JACK_DISCONNECTED: virtsnd_jack_event(snd, event); break; case VIRTIO_SND_EVT_PCM_PERIOD_ELAPSED: case VIRTIO_SND_EVT_PCM_XRUN: virtsnd_pcm_event(snd, event); break; case VIRTIO_SND_EVT_CTL_NOTIFY: virtsnd_kctl_event(snd, event); break; } } /** * virtsnd_event_notify_cb() - Dispatch all reported events from the event queue. * @vqueue: Underlying event virtqueue. * * This callback function is called upon a vring interrupt request from the * device. * * Context: Interrupt context. */ static void virtsnd_event_notify_cb(struct virtqueue *vqueue) { struct virtio_snd *snd = vqueue->vdev->priv; struct virtio_snd_queue *queue = virtsnd_event_queue(snd); struct virtio_snd_event *event; u32 length; unsigned long flags; spin_lock_irqsave(&queue->lock, flags); do { virtqueue_disable_cb(vqueue); while ((event = virtqueue_get_buf(vqueue, &length))) { virtsnd_event_dispatch(snd, event); virtsnd_event_send(vqueue, event, true, GFP_ATOMIC); } } while (!virtqueue_enable_cb(vqueue)); spin_unlock_irqrestore(&queue->lock, flags); } /** * virtsnd_find_vqs() - Enumerate and initialize all virtqueues. * @snd: VirtIO sound device. * * After calling this function, the event queue is disabled. * * Context: Any context. * Return: 0 on success, -errno on failure. */ static int virtsnd_find_vqs(struct virtio_snd *snd) { struct virtio_device *vdev = snd->vdev; struct virtqueue_info vqs_info[VIRTIO_SND_VQ_MAX] = { [VIRTIO_SND_VQ_CONTROL] = { "virtsnd-ctl", virtsnd_ctl_notify_cb }, [VIRTIO_SND_VQ_EVENT] = { "virtsnd-event", virtsnd_event_notify_cb }, [VIRTIO_SND_VQ_TX] = { "virtsnd-tx", virtsnd_pcm_tx_notify_cb }, [VIRTIO_SND_VQ_RX] = { "virtsnd-rx", virtsnd_pcm_rx_notify_cb }, }; struct virtqueue *vqs[VIRTIO_SND_VQ_MAX] = { 0 }; unsigned int i; unsigned int n; int rc; rc = virtio_find_vqs(vdev, VIRTIO_SND_VQ_MAX, vqs, vqs_info, NULL); if (rc) { dev_err(&vdev->dev, "failed to initialize virtqueues\n"); return rc; } for (i = 0; i < VIRTIO_SND_VQ_MAX; ++i) snd->queues[i].vqueue = vqs[i]; /* Allocate events and populate the event queue */ virtqueue_disable_cb(vqs[VIRTIO_SND_VQ_EVENT]); n = virtqueue_get_vring_size(vqs[VIRTIO_SND_VQ_EVENT]); snd->event_msgs = kmalloc_array(n, sizeof(*snd->event_msgs), GFP_KERNEL); if (!snd->event_msgs) return -ENOMEM; for (i = 0; i < n; ++i) virtsnd_event_send(vqs[VIRTIO_SND_VQ_EVENT], &snd->event_msgs[i], false, GFP_KERNEL); return 0; } /** * virtsnd_enable_event_vq() - Enable the event virtqueue. * @snd: VirtIO sound device. * * Context: Any context. */ static void virtsnd_enable_event_vq(struct virtio_snd *snd) { struct virtio_snd_queue *queue = virtsnd_event_queue(snd); if (!virtqueue_enable_cb(queue->vqueue)) virtsnd_event_notify_cb(queue->vqueue); } /** * virtsnd_disable_event_vq() - Disable the event virtqueue. * @snd: VirtIO sound device. * * Context: Any context. */ static void virtsnd_disable_event_vq(struct virtio_snd *snd) { struct virtio_snd_queue *queue = virtsnd_event_queue(snd); struct virtio_snd_event *event; u32 length; unsigned long flags; if (queue->vqueue) { spin_lock_irqsave(&queue->lock, flags); virtqueue_disable_cb(queue->vqueue); while ((event = virtqueue_get_buf(queue->vqueue, &length))) virtsnd_event_dispatch(snd, event); spin_unlock_irqrestore(&queue->lock, flags); } } /** * virtsnd_build_devs() - Read configuration and build ALSA devices. * @snd: VirtIO sound device. * * Context: Any context that permits to sleep. * Return: 0 on success, -errno on failure. */ static int virtsnd_build_devs(struct virtio_snd *snd) { struct virtio_device *vdev = snd->vdev; struct device *dev = &vdev->dev; int rc; rc = snd_card_new(dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, THIS_MODULE, 0, &snd->card); if (rc < 0) return rc; snd->card->private_data = snd; strscpy(snd->card->driver, VIRTIO_SND_CARD_DRIVER, sizeof(snd->card->driver)); strscpy(snd->card->shortname, VIRTIO_SND_CARD_NAME, sizeof(snd->card->shortname)); if (dev->parent->bus) snprintf(snd->card->longname, sizeof(snd->card->longname), VIRTIO_SND_CARD_NAME " at %s/%s/%s", dev->parent->bus->name, dev_name(dev->parent), dev_name(dev)); else snprintf(snd->card->longname, sizeof(snd->card->longname), VIRTIO_SND_CARD_NAME " at %s/%s", dev_name(dev->parent), dev_name(dev)); rc = virtsnd_jack_parse_cfg(snd); if (rc) return rc; rc = virtsnd_pcm_parse_cfg(snd); if (rc) return rc; rc = virtsnd_chmap_parse_cfg(snd); if (rc) return rc; if (virtio_has_feature(vdev, VIRTIO_SND_F_CTLS)) { rc = virtsnd_kctl_parse_cfg(snd); if (rc) return rc; } if (snd->njacks) { rc = virtsnd_jack_build_devs(snd); if (rc) return rc; } if (snd->nsubstreams) { rc = virtsnd_pcm_build_devs(snd); if (rc) return rc; } if (snd->nchmaps) { rc = virtsnd_chmap_build_devs(snd); if (rc) return rc; } if (snd->nkctls) { rc = virtsnd_kctl_build_devs(snd); if (rc) return rc; } return snd_card_register(snd->card); } /** * virtsnd_validate() - Validate if the device can be started. * @vdev: VirtIO parent device. * * Context: Any context. * Return: 0 on success, -EINVAL on failure. */ static int virtsnd_validate(struct virtio_device *vdev) { if (!vdev->config->get) { dev_err(&vdev->dev, "configuration access disabled\n"); return -EINVAL; } if (!virtio_has_feature(vdev, VIRTIO_F_VERSION_1)) { dev_err(&vdev->dev, "device does not comply with spec version 1.x\n"); return -EINVAL; } if (!virtsnd_msg_timeout_ms) { dev_err(&vdev->dev, "msg_timeout_ms value cannot be zero\n"); return -EINVAL; } if (virtsnd_pcm_validate(vdev)) return -EINVAL; return 0; } /** * virtsnd_probe() - Create and initialize the device. * @vdev: VirtIO parent device. * * Context: Any context that permits to sleep. * Return: 0 on success, -errno on failure. */ static int virtsnd_probe(struct virtio_device *vdev) { struct virtio_snd *snd; unsigned int i; int rc; snd = devm_kzalloc(&vdev->dev, sizeof(*snd), GFP_KERNEL); if (!snd) return -ENOMEM; snd->vdev = vdev; INIT_LIST_HEAD(&snd->ctl_msgs); INIT_LIST_HEAD(&snd->pcm_list); vdev->priv = snd; for (i = 0; i < VIRTIO_SND_VQ_MAX; ++i) spin_lock_init(&snd->queues[i].lock); rc = virtsnd_find_vqs(snd); if (rc) goto on_exit; virtio_device_ready(vdev); rc = virtsnd_build_devs(snd); if (rc) goto on_exit; virtsnd_enable_event_vq(snd); on_exit: if (rc) virtsnd_remove(vdev); return rc; } /** * virtsnd_remove() - Remove VirtIO and ALSA devices. * @vdev: VirtIO parent device. * * Context: Any context that permits to sleep. */ static void virtsnd_remove(struct virtio_device *vdev) { struct virtio_snd *snd = vdev->priv; unsigned int i; virtsnd_disable_event_vq(snd); virtsnd_ctl_msg_cancel_all(snd); if (snd->card) snd_card_free(snd->card); vdev->config->del_vqs(vdev); virtio_reset_device(vdev); for (i = 0; snd->substreams && i < snd->nsubstreams; ++i) { struct virtio_pcm_substream *vss = &snd->substreams[i]; cancel_work_sync(&vss->elapsed_period); virtsnd_pcm_msg_free(vss); } kfree(snd->event_msgs); } #ifdef CONFIG_PM_SLEEP /** * virtsnd_freeze() - Suspend device. * @vdev: VirtIO parent device. * * Context: Any context. * Return: 0 on success, -errno on failure. */ static int virtsnd_freeze(struct virtio_device *vdev) { struct virtio_snd *snd = vdev->priv; unsigned int i; virtsnd_disable_event_vq(snd); virtsnd_ctl_msg_cancel_all(snd); vdev->config->del_vqs(vdev); virtio_reset_device(vdev); for (i = 0; i < snd->nsubstreams; ++i) cancel_work_sync(&snd->substreams[i].elapsed_period); kfree(snd->event_msgs); snd->event_msgs = NULL; return 0; } /** * virtsnd_restore() - Resume device. * @vdev: VirtIO parent device. * * Context: Any context. * Return: 0 on success, -errno on failure. */ static int virtsnd_restore(struct virtio_device *vdev) { struct virtio_snd *snd = vdev->priv; int rc; rc = virtsnd_find_vqs(snd); if (rc) return rc; virtio_device_ready(vdev); virtsnd_enable_event_vq(snd); return 0; } #endif /* CONFIG_PM_SLEEP */ static const struct virtio_device_id id_table[] = { { VIRTIO_ID_SOUND, VIRTIO_DEV_ANY_ID }, { 0 }, }; static unsigned int features[] = { VIRTIO_SND_F_CTLS }; static struct virtio_driver virtsnd_driver = { .driver.name = KBUILD_MODNAME, .id_table = id_table, .feature_table = features, .feature_table_size = ARRAY_SIZE(features), .validate = virtsnd_validate, .probe = virtsnd_probe, .remove = virtsnd_remove, #ifdef CONFIG_PM_SLEEP .freeze = virtsnd_freeze, .restore = virtsnd_restore, #endif }; module_virtio_driver(virtsnd_driver); MODULE_DEVICE_TABLE(virtio, id_table); MODULE_DESCRIPTION("Virtio sound card driver"); MODULE_LICENSE("GPL");
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1