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_REC) 116 sndbuf_fillsilence(ch->buf); 117 ch->ptr += ch->buf->blksz; 118 ch->ptr %= ch->buf->bufsize; 119 mtx_unlock(&sc->lock); 120 chn_intr(ch->chan); 121 mtx_lock(&sc->lock); 122 } 123 if (!sc->stopped) 124 callout_schedule(&sc->callout, 1); 125 } 126 127 static int 128 dummy_chan_free(kobj_t obj, void *data) 129 { 130 struct dummy_chan *ch =data; 131 uint8_t *buf; 132 133 buf = ch->buf->buf; 134 free(buf, M_DEVBUF); 135 136 return (0); 137 } 138 139 static void * 140 dummy_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, 141 struct pcm_channel *c, int dir) 142 { 143 struct dummy_softc *sc; 144 struct dummy_chan *ch; 145 uint8_t *buf; 146 size_t bufsz; 147 148 sc = devinfo; 149 150 mtx_lock(&sc->lock); 151 152 ch = &sc->chans[sc->chnum++]; 153 ch->sc = sc; 154 ch->dir = dir; 155 ch->chan = c; 156 ch->buf = b; 157 ch->caps = &sc->caps; 158 159 mtx_unlock(&sc->lock); 160 161 bufsz = pcm_getbuffersize(sc->dev, 2048, 2048, 65536); 162 buf = malloc(bufsz, M_DEVBUF, M_WAITOK | M_ZERO); 163 if (sndbuf_setup(ch->buf, buf, bufsz) != 0) { 164 dummy_chan_free(obj, ch); 165 return (NULL); 166 } 167 168 return (ch); 169 } 170 171 static int 172 dummy_chan_setformat(kobj_t obj, void *data, uint32_t format) 173 { 174 struct dummy_chan *ch = data; 175 int i; 176 177 for (i = 0; ch->caps->fmtlist[i]; i++) 178 if (format == ch->caps->fmtlist[i]) 179 return (0); 180 181 return (EINVAL); 182 } 183 184 static uint32_t 185 dummy_chan_setspeed(kobj_t obj, void *data, uint32_t speed) 186 { 187 struct dummy_chan *ch = data; 188 189 RANGE(speed, ch->caps->minspeed, ch->caps->maxspeed); 190 191 return (speed); 192 } 193 194 static uint32_t 195 dummy_chan_setblocksize(kobj_t obj, void *data, uint32_t blocksize) 196 { 197 struct dummy_chan *ch = data; 198 199 return (ch->buf->blksz); 200 } 201 202 static int 203 dummy_chan_trigger(kobj_t obj, void *data, int go) 204 { 205 struct dummy_chan *ch = data; 206 struct dummy_softc *sc = ch->sc; 207 208 mtx_lock(&sc->lock); 209 210 if (sc->stopped) { 211 mtx_unlock(&sc->lock); 212 return (0); 213 } 214 215 switch (go) { 216 case PCMTRIG_START: 217 ch->ptr = 0; 218 ch->run = 1; 219 callout_reset(&sc->callout, 1, dummy_chan_io, sc); 220 break; 221 case PCMTRIG_STOP: 222 case PCMTRIG_ABORT: 223 ch->run = 0; 224 /* If all channels are stopped, stop the callout as well. */ 225 if (!dummy_active(sc)) 226 callout_stop(&sc->callout); 227 default: 228 break; 229 } 230 231 mtx_unlock(&sc->lock); 232 233 return (0); 234 } 235 236 static uint32_t 237 dummy_chan_getptr(kobj_t obj, void *data) 238 { 239 struct dummy_chan *ch = data; 240 241 return (ch->run ? ch->ptr : 0); 242 } 243 244 static struct pcmchan_caps * 245 dummy_chan_getcaps(kobj_t obj, void *data) 246 { 247 struct dummy_chan *ch = data; 248 249 return (ch->caps); 250 } 251 252 static kobj_method_t dummy_chan_methods[] = { 253 KOBJMETHOD(channel_init, dummy_chan_init), 254 KOBJMETHOD(channel_free, dummy_chan_free), 255 KOBJMETHOD(channel_setformat, dummy_chan_setformat), 256 KOBJMETHOD(channel_setspeed, dummy_chan_setspeed), 257 KOBJMETHOD(channel_setblocksize,dummy_chan_setblocksize), 258 KOBJMETHOD(channel_trigger, dummy_chan_trigger), 259 KOBJMETHOD(channel_getptr, dummy_chan_getptr), 260 KOBJMETHOD(channel_getcaps, dummy_chan_getcaps), 261 KOBJMETHOD_END 262 }; 263 264 CHANNEL_DECLARE(dummy_chan); 265 266 static int 267 dummy_mixer_init(struct snd_mixer *m) 268 { 269 struct dummy_softc *sc; 270 271 sc = mix_getdevinfo(m); 272 if (sc == NULL) 273 return (-1); 274 275 pcm_setflags(sc->dev, pcm_getflags(sc->dev) | SD_F_SOFTPCMVOL); 276 mix_setdevs(m, SOUND_MASK_PCM | SOUND_MASK_VOLUME | SOUND_MASK_RECLEV); 277 mix_setrecdevs(m, SOUND_MASK_RECLEV); 278 279 return (0); 280 } 281 282 static int 283 dummy_mixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) 284 { 285 return (0); 286 } 287 288 static uint32_t 289 dummy_mixer_setrecsrc(struct snd_mixer *m, uint32_t src) 290 { 291 return (src == SOUND_MASK_RECLEV ? src : 0); 292 } 293 294 static kobj_method_t dummy_mixer_methods[] = { 295 KOBJMETHOD(mixer_init, dummy_mixer_init), 296 KOBJMETHOD(mixer_set, dummy_mixer_set), 297 KOBJMETHOD(mixer_setrecsrc, dummy_mixer_setrecsrc), 298 KOBJMETHOD_END 299 }; 300 301 MIXER_DECLARE(dummy_mixer); 302 303 static uint8_t 304 dummy_mpu_read(struct mpu401 *arg, void *sc, int reg) 305 { 306 return (0); 307 } 308 309 static void 310 dummy_mpu_write(struct mpu401 *arg, void *sc, int reg, unsigned char b) 311 { 312 } 313 314 static int 315 dummy_mpu_uninit(struct mpu401 *arg, void *cookie) 316 { 317 struct dummy_softc *sc = cookie; 318 319 mtx_lock(&sc->lock); 320 sc->mpu_intr = NULL; 321 sc->mpu = NULL; 322 mtx_unlock(&sc->lock); 323 324 return (0); 325 } 326 327 static kobj_method_t dummy_mpu_methods[] = { 328 KOBJMETHOD(mpufoi_read, dummy_mpu_read), 329 KOBJMETHOD(mpufoi_write, dummy_mpu_write), 330 KOBJMETHOD(mpufoi_uninit, dummy_mpu_uninit), 331 KOBJMETHOD_END 332 }; 333 334 static DEFINE_CLASS(dummy_mpu, dummy_mpu_methods, 0); 335 336 static void 337 dummy_identify(driver_t *driver, device_t parent) 338 { 339 if (device_find_child(parent, driver->name, DEVICE_UNIT_ANY) != NULL) 340 return; 341 if (BUS_ADD_CHILD(parent, 0, driver->name, DEVICE_UNIT_ANY) == NULL) 342 device_printf(parent, "add child failed\n"); 343 } 344 345 static int 346 dummy_probe(device_t dev) 347 { 348 device_set_desc(dev, "Dummy Audio Device"); 349 350 return (0); 351 } 352 353 static int 354 dummy_attach(device_t dev) 355 { 356 struct dummy_softc *sc; 357 char status[SND_STATUSLEN]; 358 int i = 0; 359 360 sc = device_get_softc(dev); 361 sc->dev = dev; 362 mtx_init(&sc->lock, device_get_nameunit(dev), "snd_dummy softc", 363 MTX_DEF); 364 callout_init_mtx(&sc->callout, &sc->lock, 0); 365 366 sc->cap_fmts[0] = SND_FORMAT(AFMT_S32_LE, 2, 0); 367 sc->cap_fmts[1] = SND_FORMAT(AFMT_S24_LE, 2, 0); 368 sc->cap_fmts[2] = SND_FORMAT(AFMT_S16_LE, 2, 0); 369 sc->cap_fmts[3] = 0; 370 sc->caps = (struct pcmchan_caps){ 371 8000, /* minspeed */ 372 96000, /* maxspeed */ 373 sc->cap_fmts, /* fmtlist */ 374 0, /* caps */ 375 }; 376 377 pcm_setflags(dev, pcm_getflags(dev) | SD_F_MPSAFE); 378 pcm_init(dev, sc); 379 for (i = 0; i < DUMMY_NPCHAN; i++) 380 pcm_addchan(dev, PCMDIR_PLAY, &dummy_chan_class, sc); 381 for (i = 0; i < DUMMY_NRCHAN; i++) 382 pcm_addchan(dev, PCMDIR_REC, &dummy_chan_class, sc); 383 384 snprintf(status, SND_STATUSLEN, "on %s", 385 device_get_nameunit(device_get_parent(dev))); 386 if (pcm_register(dev, status)) 387 return (ENXIO); 388 mixer_init(dev, &dummy_mixer_class, sc); 389 390 /* 391 * Create an alias so that tests do not need to guess which one is the 392 * dummy device if there are more devices present in the system. 393 */ 394 make_dev_alias(sc->info.dsp_dev, "dsp.dummy"); 395 396 sc->mpu = mpu401_init(&dummy_mpu_class, sc, dummy_chan_io, 397 &sc->mpu_intr); 398 if (sc->mpu == NULL) 399 return (ENXIO); 400 401 return (0); 402 } 403 404 static int 405 dummy_detach(device_t dev) 406 { 407 struct dummy_softc *sc = device_get_softc(dev); 408 int err; 409 410 mtx_lock(&sc->lock); 411 sc->stopped = true; 412 mtx_unlock(&sc->lock); 413 callout_drain(&sc->callout); 414 err = pcm_unregister(dev); 415 mpu401_uninit(sc->mpu); 416 mtx_destroy(&sc->lock); 417 418 return (err); 419 } 420 421 static device_method_t dummy_methods[] = { 422 /* Device interface */ 423 DEVMETHOD(device_identify, dummy_identify), 424 DEVMETHOD(device_probe, dummy_probe), 425 DEVMETHOD(device_attach, dummy_attach), 426 DEVMETHOD(device_detach, dummy_detach), 427 DEVMETHOD_END 428 }; 429 430 static driver_t dummy_driver = { 431 "pcm", 432 dummy_methods, 433 sizeof(struct dummy_softc), 434 }; 435 436 DRIVER_MODULE(snd_dummy, nexus, dummy_driver, 0, 0); 437 MODULE_DEPEND(snd_dummy, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); 438 MODULE_VERSION(snd_dummy, 1); 439