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
dummy_active(struct dummy_softc * sc)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
dummy_chan_io(void * arg)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
dummy_chan_free(kobj_t obj,void * data)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 *
dummy_chan_init(kobj_t obj,void * devinfo,struct snd_dbuf * b,struct pcm_channel * c,int dir)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
dummy_chan_setformat(kobj_t obj,void * data,uint32_t format)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
dummy_chan_setspeed(kobj_t obj,void * data,uint32_t speed)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
dummy_chan_setblocksize(kobj_t obj,void * data,uint32_t blocksize)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
dummy_chan_trigger(kobj_t obj,void * data,int go)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
dummy_chan_getptr(kobj_t obj,void * data)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 *
dummy_chan_getcaps(kobj_t obj,void * data)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
dummy_mixer_init(struct snd_mixer * m)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
dummy_mixer_set(struct snd_mixer * m,unsigned dev,unsigned left,unsigned right)284 dummy_mixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right)
285 {
286 return (0);
287 }
288
289 static uint32_t
dummy_mixer_setrecsrc(struct snd_mixer * m,uint32_t src)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
dummy_mpu_read(struct mpu401 * arg,void * sc,int reg)305 dummy_mpu_read(struct mpu401 *arg, void *sc, int reg)
306 {
307 return (0);
308 }
309
310 static void
dummy_mpu_write(struct mpu401 * arg,void * sc,int reg,unsigned char b)311 dummy_mpu_write(struct mpu401 *arg, void *sc, int reg, unsigned char b)
312 {
313 }
314
315 static int
dummy_mpu_uninit(struct mpu401 * arg,void * cookie)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
dummy_identify(driver_t * driver,device_t parent)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
dummy_probe(device_t dev)347 dummy_probe(device_t dev)
348 {
349 device_set_desc(dev, "Dummy Audio Device");
350
351 return (0);
352 }
353
354 static int
dummy_attach(device_t dev)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
dummy_detach(device_t dev)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