xref: /freebsd/sys/dev/sound/midi/midi.c (revision eccd366b0a8ba7d902fcf0b1bec447926a75c36c)
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