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