1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * virtio-snd: Virtio sound device |
4 | * Copyright (C) 2021 OpenSynergy GmbH |
5 | */ |
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 | |
14 | u32 virtsnd_msg_timeout_ms = MSEC_PER_SEC; |
15 | module_param_named(msg_timeout_ms, virtsnd_msg_timeout_ms, uint, 0644); |
16 | MODULE_PARM_DESC(msg_timeout_ms, "Message completion timeout in milliseconds" ); |
17 | |
18 | static 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 | */ |
29 | static 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(vq: vqueue, sgs: psgs, out_sgs: 0, in_sgs: 1, data: event, gfp) || !notify) |
42 | return; |
43 | |
44 | if (virtqueue_kick_prepare(vq: vqueue)) |
45 | virtqueue_notify(vq: 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 | */ |
55 | static 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 the |
78 | * device. |
79 | * |
80 | * Context: Interrupt context. |
81 | */ |
82 | static 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(vq: vqueue); |
93 | while ((event = virtqueue_get_buf(vq: vqueue, len: &length))) { |
94 | virtsnd_event_dispatch(snd, event); |
95 | virtsnd_event_send(vqueue, event, notify: true, GFP_ATOMIC); |
96 | } |
97 | } while (!virtqueue_enable_cb(vq: vqueue)); |
98 | spin_unlock_irqrestore(lock: &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 | */ |
110 | static int virtsnd_find_vqs(struct virtio_snd *snd) |
111 | { |
112 | struct virtio_device *vdev = snd->vdev; |
113 | static vq_callback_t *callbacks[VIRTIO_SND_VQ_MAX] = { |
114 | [VIRTIO_SND_VQ_CONTROL] = virtsnd_ctl_notify_cb, |
115 | [VIRTIO_SND_VQ_EVENT] = virtsnd_event_notify_cb, |
116 | [VIRTIO_SND_VQ_TX] = virtsnd_pcm_tx_notify_cb, |
117 | [VIRTIO_SND_VQ_RX] = virtsnd_pcm_rx_notify_cb |
118 | }; |
119 | static const char *names[VIRTIO_SND_VQ_MAX] = { |
120 | [VIRTIO_SND_VQ_CONTROL] = "virtsnd-ctl" , |
121 | [VIRTIO_SND_VQ_EVENT] = "virtsnd-event" , |
122 | [VIRTIO_SND_VQ_TX] = "virtsnd-tx" , |
123 | [VIRTIO_SND_VQ_RX] = "virtsnd-rx" |
124 | }; |
125 | struct virtqueue *vqs[VIRTIO_SND_VQ_MAX] = { 0 }; |
126 | unsigned int i; |
127 | unsigned int n; |
128 | int rc; |
129 | |
130 | rc = virtio_find_vqs(vdev, nvqs: VIRTIO_SND_VQ_MAX, vqs, callbacks, names, |
131 | NULL); |
132 | if (rc) { |
133 | dev_err(&vdev->dev, "failed to initialize virtqueues\n" ); |
134 | return rc; |
135 | } |
136 | |
137 | for (i = 0; i < VIRTIO_SND_VQ_MAX; ++i) |
138 | snd->queues[i].vqueue = vqs[i]; |
139 | |
140 | /* Allocate events and populate the event queue */ |
141 | virtqueue_disable_cb(vq: vqs[VIRTIO_SND_VQ_EVENT]); |
142 | |
143 | n = virtqueue_get_vring_size(vq: vqs[VIRTIO_SND_VQ_EVENT]); |
144 | |
145 | snd->event_msgs = kmalloc_array(n, size: sizeof(*snd->event_msgs), |
146 | GFP_KERNEL); |
147 | if (!snd->event_msgs) |
148 | return -ENOMEM; |
149 | |
150 | for (i = 0; i < n; ++i) |
151 | virtsnd_event_send(vqueue: vqs[VIRTIO_SND_VQ_EVENT], |
152 | event: &snd->event_msgs[i], notify: false, GFP_KERNEL); |
153 | |
154 | return 0; |
155 | } |
156 | |
157 | /** |
158 | * virtsnd_enable_event_vq() - Enable the event virtqueue. |
159 | * @snd: VirtIO sound device. |
160 | * |
161 | * Context: Any context. |
162 | */ |
163 | static void virtsnd_enable_event_vq(struct virtio_snd *snd) |
164 | { |
165 | struct virtio_snd_queue *queue = virtsnd_event_queue(snd); |
166 | |
167 | if (!virtqueue_enable_cb(vq: queue->vqueue)) |
168 | virtsnd_event_notify_cb(vqueue: queue->vqueue); |
169 | } |
170 | |
171 | /** |
172 | * virtsnd_disable_event_vq() - Disable the event virtqueue. |
173 | * @snd: VirtIO sound device. |
174 | * |
175 | * Context: Any context. |
176 | */ |
177 | static void virtsnd_disable_event_vq(struct virtio_snd *snd) |
178 | { |
179 | struct virtio_snd_queue *queue = virtsnd_event_queue(snd); |
180 | struct virtio_snd_event *event; |
181 | u32 length; |
182 | unsigned long flags; |
183 | |
184 | if (queue->vqueue) { |
185 | spin_lock_irqsave(&queue->lock, flags); |
186 | virtqueue_disable_cb(vq: queue->vqueue); |
187 | while ((event = virtqueue_get_buf(vq: queue->vqueue, len: &length))) |
188 | virtsnd_event_dispatch(snd, event); |
189 | spin_unlock_irqrestore(lock: &queue->lock, flags); |
190 | } |
191 | } |
192 | |
193 | /** |
194 | * virtsnd_build_devs() - Read configuration and build ALSA devices. |
195 | * @snd: VirtIO sound device. |
196 | * |
197 | * Context: Any context that permits to sleep. |
198 | * Return: 0 on success, -errno on failure. |
199 | */ |
200 | static int virtsnd_build_devs(struct virtio_snd *snd) |
201 | { |
202 | struct virtio_device *vdev = snd->vdev; |
203 | struct device *dev = &vdev->dev; |
204 | int rc; |
205 | |
206 | rc = snd_card_new(parent: dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, |
207 | THIS_MODULE, extra_size: 0, card_ret: &snd->card); |
208 | if (rc < 0) |
209 | return rc; |
210 | |
211 | snd->card->private_data = snd; |
212 | |
213 | strscpy(snd->card->driver, VIRTIO_SND_CARD_DRIVER, |
214 | sizeof(snd->card->driver)); |
215 | strscpy(snd->card->shortname, VIRTIO_SND_CARD_NAME, |
216 | sizeof(snd->card->shortname)); |
217 | if (dev->parent->bus) |
218 | snprintf(buf: snd->card->longname, size: sizeof(snd->card->longname), |
219 | VIRTIO_SND_CARD_NAME " at %s/%s/%s" , |
220 | dev->parent->bus->name, dev_name(dev: dev->parent), |
221 | dev_name(dev)); |
222 | else |
223 | snprintf(buf: snd->card->longname, size: sizeof(snd->card->longname), |
224 | VIRTIO_SND_CARD_NAME " at %s/%s" , |
225 | dev_name(dev: dev->parent), dev_name(dev)); |
226 | |
227 | rc = virtsnd_jack_parse_cfg(snd); |
228 | if (rc) |
229 | return rc; |
230 | |
231 | rc = virtsnd_pcm_parse_cfg(snd); |
232 | if (rc) |
233 | return rc; |
234 | |
235 | rc = virtsnd_chmap_parse_cfg(snd); |
236 | if (rc) |
237 | return rc; |
238 | |
239 | if (virtio_has_feature(vdev, fbit: VIRTIO_SND_F_CTLS)) { |
240 | rc = virtsnd_kctl_parse_cfg(snd); |
241 | if (rc) |
242 | return rc; |
243 | } |
244 | |
245 | if (snd->njacks) { |
246 | rc = virtsnd_jack_build_devs(snd); |
247 | if (rc) |
248 | return rc; |
249 | } |
250 | |
251 | if (snd->nsubstreams) { |
252 | rc = virtsnd_pcm_build_devs(snd); |
253 | if (rc) |
254 | return rc; |
255 | } |
256 | |
257 | if (snd->nchmaps) { |
258 | rc = virtsnd_chmap_build_devs(snd); |
259 | if (rc) |
260 | return rc; |
261 | } |
262 | |
263 | if (snd->nkctls) { |
264 | rc = virtsnd_kctl_build_devs(snd); |
265 | if (rc) |
266 | return rc; |
267 | } |
268 | |
269 | return snd_card_register(card: snd->card); |
270 | } |
271 | |
272 | /** |
273 | * virtsnd_validate() - Validate if the device can be started. |
274 | * @vdev: VirtIO parent device. |
275 | * |
276 | * Context: Any context. |
277 | * Return: 0 on success, -EINVAL on failure. |
278 | */ |
279 | static int virtsnd_validate(struct virtio_device *vdev) |
280 | { |
281 | if (!vdev->config->get) { |
282 | dev_err(&vdev->dev, "configuration access disabled\n" ); |
283 | return -EINVAL; |
284 | } |
285 | |
286 | if (!virtio_has_feature(vdev, VIRTIO_F_VERSION_1)) { |
287 | dev_err(&vdev->dev, |
288 | "device does not comply with spec version 1.x\n" ); |
289 | return -EINVAL; |
290 | } |
291 | |
292 | if (!virtsnd_msg_timeout_ms) { |
293 | dev_err(&vdev->dev, "msg_timeout_ms value cannot be zero\n" ); |
294 | return -EINVAL; |
295 | } |
296 | |
297 | if (virtsnd_pcm_validate(vdev)) |
298 | return -EINVAL; |
299 | |
300 | return 0; |
301 | } |
302 | |
303 | /** |
304 | * virtsnd_probe() - Create and initialize the device. |
305 | * @vdev: VirtIO parent device. |
306 | * |
307 | * Context: Any context that permits to sleep. |
308 | * Return: 0 on success, -errno on failure. |
309 | */ |
310 | static int virtsnd_probe(struct virtio_device *vdev) |
311 | { |
312 | struct virtio_snd *snd; |
313 | unsigned int i; |
314 | int rc; |
315 | |
316 | snd = devm_kzalloc(dev: &vdev->dev, size: sizeof(*snd), GFP_KERNEL); |
317 | if (!snd) |
318 | return -ENOMEM; |
319 | |
320 | snd->vdev = vdev; |
321 | INIT_LIST_HEAD(list: &snd->ctl_msgs); |
322 | INIT_LIST_HEAD(list: &snd->pcm_list); |
323 | |
324 | vdev->priv = snd; |
325 | |
326 | for (i = 0; i < VIRTIO_SND_VQ_MAX; ++i) |
327 | spin_lock_init(&snd->queues[i].lock); |
328 | |
329 | rc = virtsnd_find_vqs(snd); |
330 | if (rc) |
331 | goto on_exit; |
332 | |
333 | virtio_device_ready(dev: vdev); |
334 | |
335 | rc = virtsnd_build_devs(snd); |
336 | if (rc) |
337 | goto on_exit; |
338 | |
339 | virtsnd_enable_event_vq(snd); |
340 | |
341 | on_exit: |
342 | if (rc) |
343 | virtsnd_remove(vdev); |
344 | |
345 | return rc; |
346 | } |
347 | |
348 | /** |
349 | * virtsnd_remove() - Remove VirtIO and ALSA devices. |
350 | * @vdev: VirtIO parent device. |
351 | * |
352 | * Context: Any context that permits to sleep. |
353 | */ |
354 | static void virtsnd_remove(struct virtio_device *vdev) |
355 | { |
356 | struct virtio_snd *snd = vdev->priv; |
357 | unsigned int i; |
358 | |
359 | virtsnd_disable_event_vq(snd); |
360 | virtsnd_ctl_msg_cancel_all(snd); |
361 | |
362 | if (snd->card) |
363 | snd_card_free(card: snd->card); |
364 | |
365 | vdev->config->del_vqs(vdev); |
366 | virtio_reset_device(dev: vdev); |
367 | |
368 | for (i = 0; snd->substreams && i < snd->nsubstreams; ++i) { |
369 | struct virtio_pcm_substream *vss = &snd->substreams[i]; |
370 | |
371 | cancel_work_sync(work: &vss->elapsed_period); |
372 | virtsnd_pcm_msg_free(vss); |
373 | } |
374 | |
375 | kfree(objp: snd->event_msgs); |
376 | } |
377 | |
378 | #ifdef CONFIG_PM_SLEEP |
379 | /** |
380 | * virtsnd_freeze() - Suspend device. |
381 | * @vdev: VirtIO parent device. |
382 | * |
383 | * Context: Any context. |
384 | * Return: 0 on success, -errno on failure. |
385 | */ |
386 | static int virtsnd_freeze(struct virtio_device *vdev) |
387 | { |
388 | struct virtio_snd *snd = vdev->priv; |
389 | unsigned int i; |
390 | |
391 | virtsnd_disable_event_vq(snd); |
392 | virtsnd_ctl_msg_cancel_all(snd); |
393 | |
394 | vdev->config->del_vqs(vdev); |
395 | virtio_reset_device(dev: vdev); |
396 | |
397 | for (i = 0; i < snd->nsubstreams; ++i) |
398 | cancel_work_sync(work: &snd->substreams[i].elapsed_period); |
399 | |
400 | kfree(objp: snd->event_msgs); |
401 | snd->event_msgs = NULL; |
402 | |
403 | return 0; |
404 | } |
405 | |
406 | /** |
407 | * virtsnd_restore() - Resume device. |
408 | * @vdev: VirtIO parent device. |
409 | * |
410 | * Context: Any context. |
411 | * Return: 0 on success, -errno on failure. |
412 | */ |
413 | static int virtsnd_restore(struct virtio_device *vdev) |
414 | { |
415 | struct virtio_snd *snd = vdev->priv; |
416 | int rc; |
417 | |
418 | rc = virtsnd_find_vqs(snd); |
419 | if (rc) |
420 | return rc; |
421 | |
422 | virtio_device_ready(dev: vdev); |
423 | |
424 | virtsnd_enable_event_vq(snd); |
425 | |
426 | return 0; |
427 | } |
428 | #endif /* CONFIG_PM_SLEEP */ |
429 | |
430 | static const struct virtio_device_id id_table[] = { |
431 | { VIRTIO_ID_SOUND, VIRTIO_DEV_ANY_ID }, |
432 | { 0 }, |
433 | }; |
434 | |
435 | static unsigned int features[] = { |
436 | VIRTIO_SND_F_CTLS |
437 | }; |
438 | |
439 | static struct virtio_driver virtsnd_driver = { |
440 | .driver.name = KBUILD_MODNAME, |
441 | .driver.owner = THIS_MODULE, |
442 | .id_table = id_table, |
443 | .feature_table = features, |
444 | .feature_table_size = ARRAY_SIZE(features), |
445 | .validate = virtsnd_validate, |
446 | .probe = virtsnd_probe, |
447 | .remove = virtsnd_remove, |
448 | #ifdef CONFIG_PM_SLEEP |
449 | .freeze = virtsnd_freeze, |
450 | .restore = virtsnd_restore, |
451 | #endif |
452 | }; |
453 | |
454 | module_virtio_driver(virtsnd_driver); |
455 | |
456 | MODULE_DEVICE_TABLE(virtio, id_table); |
457 | MODULE_DESCRIPTION("Virtio sound card driver" ); |
458 | MODULE_LICENSE("GPL" ); |
459 | |