brintos

brintos / linux-shallow public Read only

0
0
Text · 10.3 KiB · 965209e Raw
455 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * virtio-snd: Virtio sound device4 * Copyright (C) 2021 OpenSynergy GmbH5 */6#include <linux/module.h>7#include <linux/moduleparam.h>8#include <linux/virtio_config.h>9#include <sound/initval.h>10#include <uapi/linux/virtio_ids.h>11 12#include "virtio_card.h"13 14u32 virtsnd_msg_timeout_ms = MSEC_PER_SEC;15module_param_named(msg_timeout_ms, virtsnd_msg_timeout_ms, uint, 0644);16MODULE_PARM_DESC(msg_timeout_ms, "Message completion timeout in milliseconds");17 18static void virtsnd_remove(struct virtio_device *vdev);19 20/**21 * virtsnd_event_send() - Add an event to the event queue.22 * @vqueue: Underlying event virtqueue.23 * @event: Event.24 * @notify: Indicates whether or not to send a notification to the device.25 * @gfp: Kernel flags for memory allocation.26 *27 * Context: Any context.28 */29static void virtsnd_event_send(struct virtqueue *vqueue,30			       struct virtio_snd_event *event, bool notify,31			       gfp_t gfp)32{33	struct scatterlist sg;34	struct scatterlist *psgs[1] = { &sg };35 36	/* reset event content */37	memset(event, 0, sizeof(*event));38 39	sg_init_one(&sg, event, sizeof(*event));40 41	if (virtqueue_add_sgs(vqueue, psgs, 0, 1, event, gfp) || !notify)42		return;43 44	if (virtqueue_kick_prepare(vqueue))45		virtqueue_notify(vqueue);46}47 48/**49 * virtsnd_event_dispatch() - Dispatch an event from the device side.50 * @snd: VirtIO sound device.51 * @event: VirtIO sound event.52 *53 * Context: Any context.54 */55static void virtsnd_event_dispatch(struct virtio_snd *snd,56				   struct virtio_snd_event *event)57{58	switch (le32_to_cpu(event->hdr.code)) {59	case VIRTIO_SND_EVT_JACK_CONNECTED:60	case VIRTIO_SND_EVT_JACK_DISCONNECTED:61		virtsnd_jack_event(snd, event);62		break;63	case VIRTIO_SND_EVT_PCM_PERIOD_ELAPSED:64	case VIRTIO_SND_EVT_PCM_XRUN:65		virtsnd_pcm_event(snd, event);66		break;67	case VIRTIO_SND_EVT_CTL_NOTIFY:68		virtsnd_kctl_event(snd, event);69		break;70	}71}72 73/**74 * virtsnd_event_notify_cb() - Dispatch all reported events from the event queue.75 * @vqueue: Underlying event virtqueue.76 *77 * This callback function is called upon a vring interrupt request from the78 * device.79 *80 * Context: Interrupt context.81 */82static void virtsnd_event_notify_cb(struct virtqueue *vqueue)83{84	struct virtio_snd *snd = vqueue->vdev->priv;85	struct virtio_snd_queue *queue = virtsnd_event_queue(snd);86	struct virtio_snd_event *event;87	u32 length;88	unsigned long flags;89 90	spin_lock_irqsave(&queue->lock, flags);91	do {92		virtqueue_disable_cb(vqueue);93		while ((event = virtqueue_get_buf(vqueue, &length))) {94			virtsnd_event_dispatch(snd, event);95			virtsnd_event_send(vqueue, event, true, GFP_ATOMIC);96		}97	} while (!virtqueue_enable_cb(vqueue));98	spin_unlock_irqrestore(&queue->lock, flags);99}100 101/**102 * virtsnd_find_vqs() - Enumerate and initialize all virtqueues.103 * @snd: VirtIO sound device.104 *105 * After calling this function, the event queue is disabled.106 *107 * Context: Any context.108 * Return: 0 on success, -errno on failure.109 */110static int virtsnd_find_vqs(struct virtio_snd *snd)111{112	struct virtio_device *vdev = snd->vdev;113	struct virtqueue_info vqs_info[VIRTIO_SND_VQ_MAX] = {114		[VIRTIO_SND_VQ_CONTROL] = { "virtsnd-ctl",115					    virtsnd_ctl_notify_cb },116		[VIRTIO_SND_VQ_EVENT] = { "virtsnd-event",117					  virtsnd_event_notify_cb },118		[VIRTIO_SND_VQ_TX] = { "virtsnd-tx",119				       virtsnd_pcm_tx_notify_cb },120		[VIRTIO_SND_VQ_RX] = { "virtsnd-rx",121				       virtsnd_pcm_rx_notify_cb },122	};123	struct virtqueue *vqs[VIRTIO_SND_VQ_MAX] = { 0 };124	unsigned int i;125	unsigned int n;126	int rc;127 128	rc = virtio_find_vqs(vdev, VIRTIO_SND_VQ_MAX, vqs, vqs_info, NULL);129	if (rc) {130		dev_err(&vdev->dev, "failed to initialize virtqueues\n");131		return rc;132	}133 134	for (i = 0; i < VIRTIO_SND_VQ_MAX; ++i)135		snd->queues[i].vqueue = vqs[i];136 137	/* Allocate events and populate the event queue */138	virtqueue_disable_cb(vqs[VIRTIO_SND_VQ_EVENT]);139 140	n = virtqueue_get_vring_size(vqs[VIRTIO_SND_VQ_EVENT]);141 142	snd->event_msgs = kmalloc_array(n, sizeof(*snd->event_msgs),143					GFP_KERNEL);144	if (!snd->event_msgs)145		return -ENOMEM;146 147	for (i = 0; i < n; ++i)148		virtsnd_event_send(vqs[VIRTIO_SND_VQ_EVENT],149				   &snd->event_msgs[i], false, GFP_KERNEL);150 151	return 0;152}153 154/**155 * virtsnd_enable_event_vq() - Enable the event virtqueue.156 * @snd: VirtIO sound device.157 *158 * Context: Any context.159 */160static void virtsnd_enable_event_vq(struct virtio_snd *snd)161{162	struct virtio_snd_queue *queue = virtsnd_event_queue(snd);163 164	if (!virtqueue_enable_cb(queue->vqueue))165		virtsnd_event_notify_cb(queue->vqueue);166}167 168/**169 * virtsnd_disable_event_vq() - Disable the event virtqueue.170 * @snd: VirtIO sound device.171 *172 * Context: Any context.173 */174static void virtsnd_disable_event_vq(struct virtio_snd *snd)175{176	struct virtio_snd_queue *queue = virtsnd_event_queue(snd);177	struct virtio_snd_event *event;178	u32 length;179	unsigned long flags;180 181	if (queue->vqueue) {182		spin_lock_irqsave(&queue->lock, flags);183		virtqueue_disable_cb(queue->vqueue);184		while ((event = virtqueue_get_buf(queue->vqueue, &length)))185			virtsnd_event_dispatch(snd, event);186		spin_unlock_irqrestore(&queue->lock, flags);187	}188}189 190/**191 * virtsnd_build_devs() - Read configuration and build ALSA devices.192 * @snd: VirtIO sound device.193 *194 * Context: Any context that permits to sleep.195 * Return: 0 on success, -errno on failure.196 */197static int virtsnd_build_devs(struct virtio_snd *snd)198{199	struct virtio_device *vdev = snd->vdev;200	struct device *dev = &vdev->dev;201	int rc;202 203	rc = snd_card_new(dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,204			  THIS_MODULE, 0, &snd->card);205	if (rc < 0)206		return rc;207 208	snd->card->private_data = snd;209 210	strscpy(snd->card->driver, VIRTIO_SND_CARD_DRIVER,211		sizeof(snd->card->driver));212	strscpy(snd->card->shortname, VIRTIO_SND_CARD_NAME,213		sizeof(snd->card->shortname));214	if (dev->parent->bus)215		snprintf(snd->card->longname, sizeof(snd->card->longname),216			 VIRTIO_SND_CARD_NAME " at %s/%s/%s",217			 dev->parent->bus->name, dev_name(dev->parent),218			 dev_name(dev));219	else220		snprintf(snd->card->longname, sizeof(snd->card->longname),221			 VIRTIO_SND_CARD_NAME " at %s/%s",222			 dev_name(dev->parent), dev_name(dev));223 224	rc = virtsnd_jack_parse_cfg(snd);225	if (rc)226		return rc;227 228	rc = virtsnd_pcm_parse_cfg(snd);229	if (rc)230		return rc;231 232	rc = virtsnd_chmap_parse_cfg(snd);233	if (rc)234		return rc;235 236	if (virtio_has_feature(vdev, VIRTIO_SND_F_CTLS)) {237		rc = virtsnd_kctl_parse_cfg(snd);238		if (rc)239			return rc;240	}241 242	if (snd->njacks) {243		rc = virtsnd_jack_build_devs(snd);244		if (rc)245			return rc;246	}247 248	if (snd->nsubstreams) {249		rc = virtsnd_pcm_build_devs(snd);250		if (rc)251			return rc;252	}253 254	if (snd->nchmaps) {255		rc = virtsnd_chmap_build_devs(snd);256		if (rc)257			return rc;258	}259 260	if (snd->nkctls) {261		rc = virtsnd_kctl_build_devs(snd);262		if (rc)263			return rc;264	}265 266	return snd_card_register(snd->card);267}268 269/**270 * virtsnd_validate() - Validate if the device can be started.271 * @vdev: VirtIO parent device.272 *273 * Context: Any context.274 * Return: 0 on success, -EINVAL on failure.275 */276static int virtsnd_validate(struct virtio_device *vdev)277{278	if (!vdev->config->get) {279		dev_err(&vdev->dev, "configuration access disabled\n");280		return -EINVAL;281	}282 283	if (!virtio_has_feature(vdev, VIRTIO_F_VERSION_1)) {284		dev_err(&vdev->dev,285			"device does not comply with spec version 1.x\n");286		return -EINVAL;287	}288 289	if (!virtsnd_msg_timeout_ms) {290		dev_err(&vdev->dev, "msg_timeout_ms value cannot be zero\n");291		return -EINVAL;292	}293 294	if (virtsnd_pcm_validate(vdev))295		return -EINVAL;296 297	return 0;298}299 300/**301 * virtsnd_probe() - Create and initialize the device.302 * @vdev: VirtIO parent device.303 *304 * Context: Any context that permits to sleep.305 * Return: 0 on success, -errno on failure.306 */307static int virtsnd_probe(struct virtio_device *vdev)308{309	struct virtio_snd *snd;310	unsigned int i;311	int rc;312 313	snd = devm_kzalloc(&vdev->dev, sizeof(*snd), GFP_KERNEL);314	if (!snd)315		return -ENOMEM;316 317	snd->vdev = vdev;318	INIT_LIST_HEAD(&snd->ctl_msgs);319	INIT_LIST_HEAD(&snd->pcm_list);320 321	vdev->priv = snd;322 323	for (i = 0; i < VIRTIO_SND_VQ_MAX; ++i)324		spin_lock_init(&snd->queues[i].lock);325 326	rc = virtsnd_find_vqs(snd);327	if (rc)328		goto on_exit;329 330	virtio_device_ready(vdev);331 332	rc = virtsnd_build_devs(snd);333	if (rc)334		goto on_exit;335 336	virtsnd_enable_event_vq(snd);337 338on_exit:339	if (rc)340		virtsnd_remove(vdev);341 342	return rc;343}344 345/**346 * virtsnd_remove() - Remove VirtIO and ALSA devices.347 * @vdev: VirtIO parent device.348 *349 * Context: Any context that permits to sleep.350 */351static void virtsnd_remove(struct virtio_device *vdev)352{353	struct virtio_snd *snd = vdev->priv;354	unsigned int i;355 356	virtsnd_disable_event_vq(snd);357	virtsnd_ctl_msg_cancel_all(snd);358 359	if (snd->card)360		snd_card_free(snd->card);361 362	vdev->config->del_vqs(vdev);363	virtio_reset_device(vdev);364 365	for (i = 0; snd->substreams && i < snd->nsubstreams; ++i) {366		struct virtio_pcm_substream *vss = &snd->substreams[i];367 368		cancel_work_sync(&vss->elapsed_period);369		virtsnd_pcm_msg_free(vss);370	}371 372	kfree(snd->event_msgs);373}374 375#ifdef CONFIG_PM_SLEEP376/**377 * virtsnd_freeze() - Suspend device.378 * @vdev: VirtIO parent device.379 *380 * Context: Any context.381 * Return: 0 on success, -errno on failure.382 */383static int virtsnd_freeze(struct virtio_device *vdev)384{385	struct virtio_snd *snd = vdev->priv;386	unsigned int i;387 388	virtsnd_disable_event_vq(snd);389	virtsnd_ctl_msg_cancel_all(snd);390 391	vdev->config->del_vqs(vdev);392	virtio_reset_device(vdev);393 394	for (i = 0; i < snd->nsubstreams; ++i)395		cancel_work_sync(&snd->substreams[i].elapsed_period);396 397	kfree(snd->event_msgs);398	snd->event_msgs = NULL;399 400	return 0;401}402 403/**404 * virtsnd_restore() - Resume device.405 * @vdev: VirtIO parent device.406 *407 * Context: Any context.408 * Return: 0 on success, -errno on failure.409 */410static int virtsnd_restore(struct virtio_device *vdev)411{412	struct virtio_snd *snd = vdev->priv;413	int rc;414 415	rc = virtsnd_find_vqs(snd);416	if (rc)417		return rc;418 419	virtio_device_ready(vdev);420 421	virtsnd_enable_event_vq(snd);422 423	return 0;424}425#endif /* CONFIG_PM_SLEEP */426 427static const struct virtio_device_id id_table[] = {428	{ VIRTIO_ID_SOUND, VIRTIO_DEV_ANY_ID },429	{ 0 },430};431 432static unsigned int features[] = {433	VIRTIO_SND_F_CTLS434};435 436static struct virtio_driver virtsnd_driver = {437	.driver.name = KBUILD_MODNAME,438	.id_table = id_table,439	.feature_table = features,440	.feature_table_size = ARRAY_SIZE(features),441	.validate = virtsnd_validate,442	.probe = virtsnd_probe,443	.remove = virtsnd_remove,444#ifdef CONFIG_PM_SLEEP445	.freeze = virtsnd_freeze,446	.restore = virtsnd_restore,447#endif448};449 450module_virtio_driver(virtsnd_driver);451 452MODULE_DEVICE_TABLE(virtio, id_table);453MODULE_DESCRIPTION("Virtio sound card driver");454MODULE_LICENSE("GPL");455