1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2003 Mathew Kanner
5 * Copyright (c) 1998 The NetBSD Foundation, Inc.
6 * All rights reserved.
7 * Copyright (c) 2025 The FreeBSD Foundation
8 *
9 * Portions of this software were developed by Christos Margiolis
10 * <christos@FreeBSD.org> under sponsorship from the FreeBSD Foundation.
11 *
12 * This code is derived from software contributed to The NetBSD Foundation
13 * by Lennart Augustsson (augustss@netbsd.org).
14 *
15 * Redistribution and use in source and binary forms, with or without
16 * modification, are permitted provided that the following conditions
17 * are met:
18 * 1. Redistributions of source code must retain the above copyright
19 * notice, this list of conditions and the following disclaimer.
20 * 2. Redistributions in binary form must reproduce the above copyright
21 * notice, this list of conditions and the following disclaimer in the
22 * documentation and/or other materials provided with the distribution.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
25 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
26 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
27 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
28 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
32 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34 * POSSIBILITY OF SUCH DAMAGE.
35 */
36
37 #include <sys/param.h>
38 #include <sys/systm.h>
39 #include <sys/conf.h>
40 #include <sys/fcntl.h>
41 #include <sys/kernel.h>
42 #include <sys/kobj.h>
43 #include <sys/limits.h>
44 #include <sys/lock.h>
45 #include <sys/mutex.h>
46 #include <sys/poll.h>
47 #include <sys/selinfo.h>
48 #include <sys/uio.h>
49
50 #ifdef HAVE_KERNEL_OPTION_HEADERS
51 #include "opt_snd.h"
52 #endif
53
54 #include <dev/sound/midi/midi.h>
55 #include <dev/sound/midi/midiq.h>
56
57 #include "mpu_if.h"
58
59 MALLOC_DEFINE(M_MIDI, "midi buffers", "Midi data allocation area");
60
61 #define MIDI_NAMELEN 16
62 struct snd_midi {
63 KOBJ_FIELDS;
64 struct mtx lock;
65 void *cookie;
66
67 int unit;
68 int channel;
69
70 int flags; /* File flags */
71 MIDIQ_HEAD(, char) inq, outq;
72 int rchan, wchan;
73 struct selinfo rsel, wsel;
74 int hiwat; /* QLEN(outq)>High-water -> disable
75 * writes from userland */
76 struct cdev *dev;
77 };
78
79 static d_open_t midi_open;
80 static d_close_t midi_close;
81 static d_ioctl_t midi_ioctl;
82 static d_read_t midi_read;
83 static d_write_t midi_write;
84 static d_poll_t midi_poll;
85
86 static struct cdevsw midi_cdevsw = {
87 .d_version = D_VERSION,
88 .d_open = midi_open,
89 .d_close = midi_close,
90 .d_read = midi_read,
91 .d_write = midi_write,
92 .d_ioctl = midi_ioctl,
93 .d_poll = midi_poll,
94 .d_name = "midi",
95 };
96
97 struct unrhdr *dev_unr = NULL;
98 struct unrhdr *chn_unr = NULL;
99
100 /*
101 * Register a new midi device.
102 *
103 * "cookie" is passed to the MPU calls, and is normally set to the driver's
104 * softc.
105 */
106 struct snd_midi *
midi_init(kobj_class_t cls,void * cookie)107 midi_init(kobj_class_t cls, void *cookie)
108 {
109 struct snd_midi *m;
110 int inqsize, outqsize;
111 uint8_t *ibuf = NULL;
112 uint8_t *obuf = NULL;
113
114 m = malloc(sizeof(*m), M_MIDI, M_WAITOK | M_ZERO);
115 kobj_init((kobj_t)m, cls);
116 inqsize = MPU_INQSIZE(m, cookie);
117 outqsize = MPU_OUTQSIZE(m, cookie);
118
119 if (!inqsize && !outqsize)
120 goto err1;
121
122 mtx_init(&m->lock, "raw midi", NULL, 0);
123
124 if (inqsize)
125 ibuf = malloc(inqsize, M_MIDI, M_WAITOK);
126 if (outqsize)
127 obuf = malloc(outqsize, M_MIDI, M_WAITOK);
128
129 mtx_lock(&m->lock);
130
131 m->hiwat = outqsize / 2;
132
133 MIDIQ_INIT(m->inq, ibuf, inqsize);
134 MIDIQ_INIT(m->outq, obuf, outqsize);
135
136 m->flags = 0;
137 m->unit = alloc_unr(dev_unr);
138 m->channel = alloc_unr(chn_unr);
139 m->cookie = cookie;
140
141 if (MPU_INIT(m, cookie))
142 goto err2;
143
144 mtx_unlock(&m->lock);
145
146 m->dev = make_dev(&midi_cdevsw, m->unit, UID_ROOT, GID_WHEEL, 0666,
147 "midi%d.%d", m->unit, m->channel);
148 m->dev->si_drv1 = m;
149
150 return m;
151
152 err2:
153 mtx_destroy(&m->lock);
154
155 free(MIDIQ_BUF(m->inq), M_MIDI);
156 free(MIDIQ_BUF(m->outq), M_MIDI);
157 err1:
158 free(m, M_MIDI);
159 return NULL;
160 }
161
162 int
midi_uninit(struct snd_midi * m)163 midi_uninit(struct snd_midi *m)
164 {
165 mtx_lock(&m->lock);
166 if (m->rchan) {
167 wakeup(&m->rchan);
168 m->rchan = 0;
169 }
170 if (m->wchan) {
171 wakeup(&m->wchan);
172 m->wchan = 0;
173 }
174 mtx_unlock(&m->lock);
175 MPU_UNINIT(m, m->cookie);
176 destroy_dev(m->dev);
177 free_unr(dev_unr, m->unit);
178 free_unr(chn_unr, m->channel);
179 free(MIDIQ_BUF(m->inq), M_MIDI);
180 free(MIDIQ_BUF(m->outq), M_MIDI);
181 mtx_destroy(&m->lock);
182 free(m, M_MIDI);
183
184 return (0);
185 }
186
187 /*
188 * midi_in: process all data until the queue is full, then discards the rest.
189 * Since midi_in is a state machine, data discards can cause it to get out of
190 * whack. Process as much as possible. It calls, wakeup, selnotify and
191 * psignal at most once.
192 */
193 int
midi_in(struct snd_midi * m,uint8_t * buf,int size)194 midi_in(struct snd_midi *m, uint8_t *buf, int size)
195 {
196 int used;
197
198 mtx_lock(&m->lock);
199
200 if (!(m->flags & M_RX)) {
201 /* We should return 0 but this may stop receiving/sending. */
202 mtx_unlock(&m->lock);
203 return (size);
204 }
205
206 used = 0;
207
208 if (MIDIQ_AVAIL(m->inq) > size) {
209 used = size;
210 MIDIQ_ENQ(m->inq, buf, size);
211 } else {
212 mtx_unlock(&m->lock);
213 return 0;
214 }
215 if (m->rchan) {
216 wakeup(&m->rchan);
217 m->rchan = 0;
218 }
219 selwakeup(&m->rsel);
220 mtx_unlock(&m->lock);
221 return used;
222 }
223
224 /*
225 * midi_out: The only clearer of the M_TXEN flag.
226 */
227 int
midi_out(struct snd_midi * m,uint8_t * buf,int size)228 midi_out(struct snd_midi *m, uint8_t *buf, int size)
229 {
230 int used;
231
232 mtx_lock(&m->lock);
233
234 if (!(m->flags & M_TXEN)) {
235 mtx_unlock(&m->lock);
236 return (0);
237 }
238
239 used = min(size, MIDIQ_LEN(m->outq));
240 if (used)
241 MIDIQ_DEQ(m->outq, buf, used);
242 if (MIDIQ_EMPTY(m->outq)) {
243 m->flags &= ~M_TXEN;
244 MPU_CALLBACK(m, m->cookie, m->flags);
245 }
246 if (used && MIDIQ_AVAIL(m->outq) > m->hiwat) {
247 if (m->wchan) {
248 wakeup(&m->wchan);
249 m->wchan = 0;
250 }
251 selwakeup(&m->wsel);
252 }
253 mtx_unlock(&m->lock);
254 return used;
255 }
256
257 int
midi_open(struct cdev * i_dev,int flags,int mode,struct thread * td)258 midi_open(struct cdev *i_dev, int flags, int mode, struct thread *td)
259 {
260 struct snd_midi *m = i_dev->si_drv1;
261 int retval;
262
263 if (m == NULL)
264 return ENXIO;
265
266 mtx_lock(&m->lock);
267
268 retval = 0;
269
270 if (flags & FREAD) {
271 if (MIDIQ_SIZE(m->inq) == 0)
272 retval = ENXIO;
273 else if (m->flags & M_RX)
274 retval = EBUSY;
275 if (retval)
276 goto err;
277 }
278 if (flags & FWRITE) {
279 if (MIDIQ_SIZE(m->outq) == 0)
280 retval = ENXIO;
281 else if (m->flags & M_TX)
282 retval = EBUSY;
283 if (retval)
284 goto err;
285 }
286
287 m->rchan = 0;
288 m->wchan = 0;
289
290 if (flags & FREAD) {
291 m->flags |= M_RX | M_RXEN;
292 /*
293 * Only clear the inq, the outq might still have data to drain
294 * from a previous session
295 */
296 MIDIQ_CLEAR(m->inq);
297 }
298
299 if (flags & FWRITE)
300 m->flags |= M_TX;
301
302 MPU_CALLBACK(m, m->cookie, m->flags);
303
304 err:
305 mtx_unlock(&m->lock);
306 return retval;
307 }
308
309 int
midi_close(struct cdev * i_dev,int flags,int mode,struct thread * td)310 midi_close(struct cdev *i_dev, int flags, int mode, struct thread *td)
311 {
312 struct snd_midi *m = i_dev->si_drv1;
313 int retval;
314 int oldflags;
315
316 if (m == NULL)
317 return ENXIO;
318
319 mtx_lock(&m->lock);
320
321 if ((flags & FREAD && !(m->flags & M_RX)) ||
322 (flags & FWRITE && !(m->flags & M_TX))) {
323 retval = ENXIO;
324 goto err;
325 }
326
327 oldflags = m->flags;
328
329 if (flags & FREAD)
330 m->flags &= ~(M_RX | M_RXEN);
331 if (flags & FWRITE)
332 m->flags &= ~M_TX;
333
334 if ((m->flags & (M_TXEN | M_RXEN)) != (oldflags & (M_RXEN | M_TXEN)))
335 MPU_CALLBACK(m, m->cookie, m->flags);
336
337 mtx_unlock(&m->lock);
338 retval = 0;
339 err: return retval;
340 }
341
342 /*
343 * TODO: midi_read, per oss programmer's guide pg. 42 should return as soon
344 * as data is available.
345 */
346 int
midi_read(struct cdev * i_dev,struct uio * uio,int ioflag)347 midi_read(struct cdev *i_dev, struct uio *uio, int ioflag)
348 {
349 #define MIDI_RSIZE 32
350 struct snd_midi *m = i_dev->si_drv1;
351 int retval;
352 int used;
353 char buf[MIDI_RSIZE];
354
355 retval = EIO;
356
357 if (m == NULL)
358 goto err0;
359
360 mtx_lock(&m->lock);
361
362 if (!(m->flags & M_RX))
363 goto err1;
364
365 while (uio->uio_resid > 0) {
366 while (MIDIQ_EMPTY(m->inq)) {
367 retval = EWOULDBLOCK;
368 if (ioflag & O_NONBLOCK)
369 goto err1;
370 m->rchan = 1;
371 retval = msleep(&m->rchan, &m->lock,
372 PCATCH | PDROP, "midi RX", 0);
373 /*
374 * We slept, maybe things have changed since last
375 * dying check
376 */
377 if (retval == EINTR)
378 goto err0;
379 if (m != i_dev->si_drv1)
380 retval = ENXIO;
381 if (retval)
382 goto err0;
383 mtx_lock(&m->lock);
384 m->rchan = 0;
385 }
386 /*
387 * At this point, it is certain that m->inq has data
388 */
389
390 used = min(MIDIQ_LEN(m->inq), uio->uio_resid);
391 used = min(used, MIDI_RSIZE);
392
393 MIDIQ_DEQ(m->inq, buf, used);
394 mtx_unlock(&m->lock);
395 retval = uiomove(buf, used, uio);
396 if (retval)
397 goto err0;
398 mtx_lock(&m->lock);
399 }
400
401 /*
402 * If we Made it here then transfer is good
403 */
404 retval = 0;
405 err1:
406 mtx_unlock(&m->lock);
407 err0:
408 return retval;
409 }
410
411 /*
412 * midi_write: The only setter of M_TXEN
413 */
414
415 int
midi_write(struct cdev * i_dev,struct uio * uio,int ioflag)416 midi_write(struct cdev *i_dev, struct uio *uio, int ioflag)
417 {
418 #define MIDI_WSIZE 32
419 struct snd_midi *m = i_dev->si_drv1;
420 int retval;
421 int used;
422 char buf[MIDI_WSIZE];
423
424 retval = 0;
425 if (m == NULL)
426 goto err0;
427
428 mtx_lock(&m->lock);
429
430 if (!(m->flags & M_TX))
431 goto err1;
432
433 while (uio->uio_resid > 0) {
434 while (MIDIQ_AVAIL(m->outq) == 0) {
435 retval = EWOULDBLOCK;
436 if (ioflag & O_NONBLOCK)
437 goto err1;
438 m->wchan = 1;
439 retval = msleep(&m->wchan, &m->lock,
440 PCATCH | PDROP, "midi TX", 0);
441 /*
442 * We slept, maybe things have changed since last
443 * dying check
444 */
445 if (retval == EINTR)
446 goto err0;
447 if (m != i_dev->si_drv1)
448 retval = ENXIO;
449 if (retval)
450 goto err0;
451 mtx_lock(&m->lock);
452 m->wchan = 0;
453 }
454
455 /*
456 * We are certain than data can be placed on the queue
457 */
458
459 used = min(MIDIQ_AVAIL(m->outq), uio->uio_resid);
460 used = min(used, MIDI_WSIZE);
461
462 mtx_unlock(&m->lock);
463 retval = uiomove(buf, used, uio);
464 if (retval)
465 goto err0;
466 mtx_lock(&m->lock);
467 MIDIQ_ENQ(m->outq, buf, used);
468 /*
469 * Inform the bottom half that data can be written
470 */
471 if (!(m->flags & M_TXEN)) {
472 m->flags |= M_TXEN;
473 MPU_CALLBACK(m, m->cookie, m->flags);
474 }
475 }
476 /*
477 * If we Made it here then transfer is good
478 */
479 retval = 0;
480 err1:
481 mtx_unlock(&m->lock);
482 err0: return retval;
483 }
484
485 int
midi_ioctl(struct cdev * i_dev,u_long cmd,caddr_t arg,int mode,struct thread * td)486 midi_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode,
487 struct thread *td)
488 {
489 return ENXIO;
490 }
491
492 int
midi_poll(struct cdev * i_dev,int events,struct thread * td)493 midi_poll(struct cdev *i_dev, int events, struct thread *td)
494 {
495 struct snd_midi *m = i_dev->si_drv1;
496 int revents;
497
498 if (m == NULL)
499 return 0;
500
501 revents = 0;
502
503 mtx_lock(&m->lock);
504
505 if (events & (POLLIN | POLLRDNORM)) {
506 if (!MIDIQ_EMPTY(m->inq))
507 revents |= events & (POLLIN | POLLRDNORM);
508 else
509 selrecord(td, &m->rsel);
510 }
511 if (events & (POLLOUT | POLLWRNORM)) {
512 if (MIDIQ_AVAIL(m->outq) < m->hiwat)
513 revents |= events & (POLLOUT | POLLWRNORM);
514 else
515 selrecord(td, &m->wsel);
516 }
517
518 mtx_unlock(&m->lock);
519
520 return (revents);
521 }
522
523 static void
midi_sysinit(void * data __unused)524 midi_sysinit(void *data __unused)
525 {
526 dev_unr = new_unrhdr(0, INT_MAX, NULL);
527 chn_unr = new_unrhdr(0, INT_MAX, NULL);
528 }
529 SYSINIT(midi_sysinit, SI_SUB_DRIVERS, SI_ORDER_FIRST, midi_sysinit, NULL);
530
531 static void
midi_sysuninit(void * data __unused)532 midi_sysuninit(void *data __unused)
533 {
534 if (dev_unr != NULL)
535 delete_unrhdr(dev_unr);
536 if (chn_unr != NULL)
537 delete_unrhdr(chn_unr);
538 }
539 SYSUNINIT(midi_sysuninit, SI_SUB_DRIVERS, SI_ORDER_ANY, midi_sysuninit, NULL);
540