xref: /freebsd/sys/dev/sound/dummy.c (revision b1bebaaba9b9c0ddfe503c43ca8e9e3917ee2c57)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2024-2025 The FreeBSD Foundation
5  *
6  * This software was developed by Christos Margiolis <christos@FreeBSD.org>
7  * under sponsorship from the FreeBSD Foundation.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28  * SUCH DAMAGE.
29  */
30 
31 #include <sys/cdefs.h>
32 
33 #include <sys/param.h>
34 #include <sys/systm.h>
35 #include <sys/kernel.h>
36 #include <sys/bus.h>
37 
38 #ifdef HAVE_KERNEL_OPTION_HEADERS
39 #include "opt_snd.h"
40 #endif
41 
42 #include <dev/sound/pcm/sound.h>
43 #include <dev/sound/midi/mpu401.h>
44 
45 #include <mixer_if.h>
46 #include <mpufoi_if.h>
47 
48 #define DUMMY_NPCHAN	1
49 #define DUMMY_NRCHAN	1
50 #define DUMMY_NCHAN	(DUMMY_NPCHAN + DUMMY_NRCHAN)
51 
52 struct dummy_chan {
53 	struct dummy_softc *sc;
54 	struct pcm_channel *chan;
55 	struct snd_dbuf *buf;
56 	struct pcmchan_caps *caps;
57 	uint32_t ptr;
58 	int dir;
59 	int run;
60 };
61 
62 struct dummy_softc {
63 	struct snddev_info info;
64 	device_t dev;
65 	uint32_t cap_fmts[4];
66 	struct pcmchan_caps caps;
67 	int chnum;
68 	struct dummy_chan chans[DUMMY_NCHAN];
69 	struct callout callout;
70 	struct mtx lock;
71 	bool stopped;
72 	struct mpu401 *mpu;
73 	mpu401_intr_t *mpu_intr;
74 };
75 
76 static bool
77 dummy_active(struct dummy_softc *sc)
78 {
79 	struct dummy_chan *ch;
80 	int i;
81 
82 	mtx_assert(&sc->lock, MA_OWNED);
83 
84 	for (i = 0; i < sc->chnum; i++) {
85 		ch = &sc->chans[i];
86 		if (ch->run)
87 			return (true);
88 	}
89 
90 	/* No channel is running at the moment. */
91 	return (false);
92 }
93 
94 static void
95 dummy_chan_io(void *arg)
96 {
97 	struct dummy_softc *sc = arg;
98 	struct dummy_chan *ch;
99 	int i = 0;
100 
101 	if (sc->mpu_intr)
102 		(sc->mpu_intr)(sc->mpu);
103 
104 	if (sc->stopped)
105 		return;
106 
107 	/* Do not reschedule if no channel is running. */
108 	if (!dummy_active(sc))
109 		return;
110 
111 	for (i = 0; i < sc->chnum; i++) {
112 		ch = &sc->chans[i];
113 		if (!ch->run)
114 			continue;
115 		if (ch->dir == PCMDIR_PLAY) {
116 			ch->ptr += ch->buf->blksz;
117 			ch->ptr %= ch->buf->bufsize;
118 		} else
119 			sndbuf_fillsilence(ch->buf);
120 		mtx_unlock(&sc->lock);
121 		chn_intr(ch->chan);
122 		mtx_lock(&sc->lock);
123 	}
124 	if (!sc->stopped)
125 		callout_schedule(&sc->callout, 1);
126 }
127 
128 static int
129 dummy_chan_free(kobj_t obj, void *data)
130 {
131 	struct dummy_chan *ch =data;
132 	uint8_t *buf;
133 
134 	buf = ch->buf->buf;
135 	free(buf, M_DEVBUF);
136 
137 	return (0);
138 }
139 
140 static void *
141 dummy_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b,
142     struct pcm_channel *c, int dir)
143 {
144 	struct dummy_softc *sc;
145 	struct dummy_chan *ch;
146 	uint8_t *buf;
147 	size_t bufsz;
148 
149 	sc = devinfo;
150 
151 	mtx_lock(&sc->lock);
152 
153 	ch = &sc->chans[sc->chnum++];
154 	ch->sc = sc;
155 	ch->dir = dir;
156 	ch->chan = c;
157 	ch->buf = b;
158 	ch->caps = &sc->caps;
159 
160 	mtx_unlock(&sc->lock);
161 
162 	bufsz = pcm_getbuffersize(sc->dev, 2048, 2048, 65536);
163 	buf = malloc(bufsz, M_DEVBUF, M_WAITOK | M_ZERO);
164 	if (sndbuf_setup(ch->buf, buf, bufsz) != 0) {
165 		dummy_chan_free(obj, ch);
166 		return (NULL);
167 	}
168 
169 	return (ch);
170 }
171 
172 static int
173 dummy_chan_setformat(kobj_t obj, void *data, uint32_t format)
174 {
175 	struct dummy_chan *ch = data;
176 	int i;
177 
178 	for (i = 0; ch->caps->fmtlist[i]; i++)
179 		if (format == ch->caps->fmtlist[i])
180 			return (0);
181 
182 	return (EINVAL);
183 }
184 
185 static uint32_t
186 dummy_chan_setspeed(kobj_t obj, void *data, uint32_t speed)
187 {
188 	struct dummy_chan *ch = data;
189 
190 	RANGE(speed, ch->caps->minspeed, ch->caps->maxspeed);
191 
192 	return (speed);
193 }
194 
195 static uint32_t
196 dummy_chan_setblocksize(kobj_t obj, void *data, uint32_t blocksize)
197 {
198 	struct dummy_chan *ch = data;
199 
200 	return (ch->buf->blksz);
201 }
202 
203 static int
204 dummy_chan_trigger(kobj_t obj, void *data, int go)
205 {
206 	struct dummy_chan *ch = data;
207 	struct dummy_softc *sc = ch->sc;
208 
209 	mtx_lock(&sc->lock);
210 
211 	if (sc->stopped) {
212 		mtx_unlock(&sc->lock);
213 		return (0);
214 	}
215 
216 	switch (go) {
217 	case PCMTRIG_START:
218 		ch->ptr = 0;
219 		ch->run = 1;
220 		callout_reset(&sc->callout, 1, dummy_chan_io, sc);
221 		break;
222 	case PCMTRIG_STOP:
223 	case PCMTRIG_ABORT:
224 		ch->run = 0;
225 		/* If all channels are stopped, stop the callout as well. */
226 		if (!dummy_active(sc))
227 			callout_stop(&sc->callout);
228 	default:
229 		break;
230 	}
231 
232 	mtx_unlock(&sc->lock);
233 
234 	return (0);
235 }
236 
237 static uint32_t
238 dummy_chan_getptr(kobj_t obj, void *data)
239 {
240 	struct dummy_chan *ch = data;
241 
242 	return (ch->run ? ch->ptr : 0);
243 }
244 
245 static struct pcmchan_caps *
246 dummy_chan_getcaps(kobj_t obj, void *data)
247 {
248 	struct dummy_chan *ch = data;
249 
250 	return (ch->caps);
251 }
252 
253 static kobj_method_t dummy_chan_methods[] = {
254 	KOBJMETHOD(channel_init,	dummy_chan_init),
255 	KOBJMETHOD(channel_free,	dummy_chan_free),
256 	KOBJMETHOD(channel_setformat,	dummy_chan_setformat),
257 	KOBJMETHOD(channel_setspeed,	dummy_chan_setspeed),
258 	KOBJMETHOD(channel_setblocksize,dummy_chan_setblocksize),
259 	KOBJMETHOD(channel_trigger,	dummy_chan_trigger),
260 	KOBJMETHOD(channel_getptr,	dummy_chan_getptr),
261 	KOBJMETHOD(channel_getcaps,	dummy_chan_getcaps),
262 	KOBJMETHOD_END
263 };
264 
265 CHANNEL_DECLARE(dummy_chan);
266 
267 static int
268 dummy_mixer_init(struct snd_mixer *m)
269 {
270 	struct dummy_softc *sc;
271 
272 	sc = mix_getdevinfo(m);
273 	if (sc == NULL)
274 		return (-1);
275 
276 	pcm_setflags(sc->dev, pcm_getflags(sc->dev) | SD_F_SOFTPCMVOL);
277 	mix_setdevs(m, SOUND_MASK_PCM | SOUND_MASK_VOLUME | SOUND_MASK_RECLEV);
278 	mix_setrecdevs(m, SOUND_MASK_RECLEV);
279 
280 	return (0);
281 }
282 
283 static int
284 dummy_mixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right)
285 {
286 	return (0);
287 }
288 
289 static uint32_t
290 dummy_mixer_setrecsrc(struct snd_mixer *m, uint32_t src)
291 {
292 	return (src == SOUND_MASK_RECLEV ? src : 0);
293 }
294 
295 static kobj_method_t dummy_mixer_methods[] = {
296 	KOBJMETHOD(mixer_init,		dummy_mixer_init),
297 	KOBJMETHOD(mixer_set,		dummy_mixer_set),
298 	KOBJMETHOD(mixer_setrecsrc,	dummy_mixer_setrecsrc),
299 	KOBJMETHOD_END
300 };
301 
302 MIXER_DECLARE(dummy_mixer);
303 
304 static uint8_t
305 dummy_mpu_read(struct mpu401 *arg, void *sc, int reg)
306 {
307 	return (0);
308 }
309 
310 static void
311 dummy_mpu_write(struct mpu401 *arg, void *sc, int reg, unsigned char b)
312 {
313 }
314 
315 static int
316 dummy_mpu_uninit(struct mpu401 *arg, void *cookie)
317 {
318 	struct dummy_softc *sc = cookie;
319 
320 	mtx_lock(&sc->lock);
321 	sc->mpu_intr = NULL;
322 	sc->mpu = NULL;
323 	mtx_unlock(&sc->lock);
324 
325 	return (0);
326 }
327 
328 static kobj_method_t dummy_mpu_methods[] = {
329 	KOBJMETHOD(mpufoi_read,		dummy_mpu_read),
330 	KOBJMETHOD(mpufoi_write,	dummy_mpu_write),
331 	KOBJMETHOD(mpufoi_uninit,	dummy_mpu_uninit),
332 	KOBJMETHOD_END
333 };
334 
335 static DEFINE_CLASS(dummy_mpu, dummy_mpu_methods, 0);
336 
337 static void
338 dummy_identify(driver_t *driver, device_t parent)
339 {
340 	if (device_find_child(parent, driver->name, DEVICE_UNIT_ANY) != NULL)
341 		return;
342 	if (BUS_ADD_CHILD(parent, 0, driver->name, DEVICE_UNIT_ANY) == NULL)
343 		device_printf(parent, "add child failed\n");
344 }
345 
346 static int
347 dummy_probe(device_t dev)
348 {
349 	device_set_desc(dev, "Dummy Audio Device");
350 
351 	return (0);
352 }
353 
354 static int
355 dummy_attach(device_t dev)
356 {
357 	struct dummy_softc *sc;
358 	char status[SND_STATUSLEN];
359 	int i = 0;
360 
361 	sc = device_get_softc(dev);
362 	sc->dev = dev;
363 	mtx_init(&sc->lock, device_get_nameunit(dev), "snd_dummy softc",
364 	    MTX_DEF);
365 	callout_init_mtx(&sc->callout, &sc->lock, 0);
366 
367 	sc->cap_fmts[0] = SND_FORMAT(AFMT_S32_LE, 2, 0);
368 	sc->cap_fmts[1] = SND_FORMAT(AFMT_S24_LE, 2, 0);
369 	sc->cap_fmts[2] = SND_FORMAT(AFMT_S16_LE, 2, 0);
370 	sc->cap_fmts[3] = 0;
371 	sc->caps = (struct pcmchan_caps){
372 		8000,		/* minspeed */
373 		96000,		/* maxspeed */
374 		sc->cap_fmts,	/* fmtlist */
375 		0,		/* caps */
376 	};
377 
378 	pcm_setflags(dev, pcm_getflags(dev) | SD_F_MPSAFE);
379 	pcm_init(dev, sc);
380 	for (i = 0; i < DUMMY_NPCHAN; i++)
381 		pcm_addchan(dev, PCMDIR_PLAY, &dummy_chan_class, sc);
382 	for (i = 0; i < DUMMY_NRCHAN; i++)
383 		pcm_addchan(dev, PCMDIR_REC, &dummy_chan_class, sc);
384 
385 	snprintf(status, SND_STATUSLEN, "on %s",
386 	    device_get_nameunit(device_get_parent(dev)));
387 	if (pcm_register(dev, status))
388 		return (ENXIO);
389 	mixer_init(dev, &dummy_mixer_class, sc);
390 
391 	/*
392 	 * Create an alias so that tests do not need to guess which one is the
393 	 * dummy device if there are more devices present in the system.
394 	 */
395 	make_dev_alias(sc->info.dsp_dev, "dsp.dummy");
396 
397 	sc->mpu = mpu401_init(&dummy_mpu_class, sc, dummy_chan_io,
398 	    &sc->mpu_intr);
399 	if (sc->mpu == NULL)
400 		return (ENXIO);
401 
402 	return (0);
403 }
404 
405 static int
406 dummy_detach(device_t dev)
407 {
408 	struct dummy_softc *sc = device_get_softc(dev);
409 	int err;
410 
411 	mtx_lock(&sc->lock);
412 	sc->stopped = true;
413 	mtx_unlock(&sc->lock);
414 	callout_drain(&sc->callout);
415 	err = pcm_unregister(dev);
416 	mpu401_uninit(sc->mpu);
417 	mtx_destroy(&sc->lock);
418 
419 	return (err);
420 }
421 
422 static device_method_t dummy_methods[] = {
423 	/* Device interface */
424 	DEVMETHOD(device_identify,	dummy_identify),
425 	DEVMETHOD(device_probe,		dummy_probe),
426 	DEVMETHOD(device_attach,	dummy_attach),
427 	DEVMETHOD(device_detach,	dummy_detach),
428 	DEVMETHOD_END
429 };
430 
431 static driver_t dummy_driver = {
432 	"pcm",
433 	dummy_methods,
434 	sizeof(struct dummy_softc),
435 };
436 
437 DRIVER_MODULE(snd_dummy, nexus, dummy_driver, 0, 0);
438 MODULE_DEPEND(snd_dummy, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER);
439 MODULE_VERSION(snd_dummy, 1);
440