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 <mixer_if.h> 44 45 #define DUMMY_NPCHAN 1 46 #define DUMMY_NRCHAN 1 47 #define DUMMY_NCHAN (DUMMY_NPCHAN + DUMMY_NRCHAN) 48 49 struct dummy_chan { 50 struct dummy_softc *sc; 51 struct pcm_channel *chan; 52 struct snd_dbuf *buf; 53 struct pcmchan_caps *caps; 54 uint32_t ptr; 55 int dir; 56 int run; 57 }; 58 59 struct dummy_softc { 60 struct snddev_info info; 61 device_t dev; 62 uint32_t cap_fmts[4]; 63 struct pcmchan_caps caps; 64 int chnum; 65 struct dummy_chan chans[DUMMY_NCHAN]; 66 struct callout callout; 67 struct mtx *lock; 68 bool stopped; 69 }; 70 71 static bool 72 dummy_active(struct dummy_softc *sc) 73 { 74 struct dummy_chan *ch; 75 int i; 76 77 snd_mtxassert(sc->lock); 78 79 for (i = 0; i < sc->chnum; i++) { 80 ch = &sc->chans[i]; 81 if (ch->run) 82 return (true); 83 } 84 85 /* No channel is running at the moment. */ 86 return (false); 87 } 88 89 static void 90 dummy_chan_io(void *arg) 91 { 92 struct dummy_softc *sc = arg; 93 struct dummy_chan *ch; 94 int i = 0; 95 96 if (sc->stopped) 97 return; 98 99 /* Do not reschedule if no channel is running. */ 100 if (!dummy_active(sc)) 101 return; 102 103 for (i = 0; i < sc->chnum; i++) { 104 ch = &sc->chans[i]; 105 if (!ch->run) 106 continue; 107 if (ch->dir == PCMDIR_PLAY) { 108 ch->ptr += sndbuf_getblksz(ch->buf); 109 ch->ptr %= sndbuf_getsize(ch->buf); 110 } else 111 sndbuf_fillsilence(ch->buf); 112 snd_mtxunlock(sc->lock); 113 chn_intr(ch->chan); 114 snd_mtxlock(sc->lock); 115 } 116 if (!sc->stopped) 117 callout_schedule(&sc->callout, 1); 118 } 119 120 static int 121 dummy_chan_free(kobj_t obj, void *data) 122 { 123 struct dummy_chan *ch =data; 124 uint8_t *buf; 125 126 buf = sndbuf_getbuf(ch->buf); 127 if (buf != NULL) 128 free(buf, M_DEVBUF); 129 130 return (0); 131 } 132 133 static void * 134 dummy_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, 135 struct pcm_channel *c, int dir) 136 { 137 struct dummy_softc *sc; 138 struct dummy_chan *ch; 139 uint8_t *buf; 140 size_t bufsz; 141 142 sc = devinfo; 143 144 snd_mtxlock(sc->lock); 145 146 ch = &sc->chans[sc->chnum++]; 147 ch->sc = sc; 148 ch->dir = dir; 149 ch->chan = c; 150 ch->buf = b; 151 ch->caps = &sc->caps; 152 153 snd_mtxunlock(sc->lock); 154 155 bufsz = pcm_getbuffersize(sc->dev, 2048, 2048, 65536); 156 buf = malloc(bufsz, M_DEVBUF, M_WAITOK | M_ZERO); 157 if (sndbuf_setup(ch->buf, buf, bufsz) != 0) { 158 dummy_chan_free(obj, ch); 159 return (NULL); 160 } 161 162 return (ch); 163 } 164 165 static int 166 dummy_chan_setformat(kobj_t obj, void *data, uint32_t format) 167 { 168 struct dummy_chan *ch = data; 169 int i; 170 171 for (i = 0; ch->caps->fmtlist[i]; i++) 172 if (format == ch->caps->fmtlist[i]) 173 return (0); 174 175 return (EINVAL); 176 } 177 178 static uint32_t 179 dummy_chan_setspeed(kobj_t obj, void *data, uint32_t speed) 180 { 181 struct dummy_chan *ch = data; 182 183 RANGE(speed, ch->caps->minspeed, ch->caps->maxspeed); 184 185 return (speed); 186 } 187 188 static uint32_t 189 dummy_chan_setblocksize(kobj_t obj, void *data, uint32_t blocksize) 190 { 191 struct dummy_chan *ch = data; 192 193 return (sndbuf_getblksz(ch->buf)); 194 } 195 196 static int 197 dummy_chan_trigger(kobj_t obj, void *data, int go) 198 { 199 struct dummy_chan *ch = data; 200 struct dummy_softc *sc = ch->sc; 201 202 snd_mtxlock(sc->lock); 203 204 if (sc->stopped) { 205 snd_mtxunlock(sc->lock); 206 return (0); 207 } 208 209 switch (go) { 210 case PCMTRIG_START: 211 ch->ptr = 0; 212 ch->run = 1; 213 callout_reset(&sc->callout, 1, dummy_chan_io, sc); 214 break; 215 case PCMTRIG_STOP: 216 case PCMTRIG_ABORT: 217 ch->run = 0; 218 /* If all channels are stopped, stop the callout as well. */ 219 if (!dummy_active(sc)) 220 callout_stop(&sc->callout); 221 default: 222 break; 223 } 224 225 snd_mtxunlock(sc->lock); 226 227 return (0); 228 } 229 230 static uint32_t 231 dummy_chan_getptr(kobj_t obj, void *data) 232 { 233 struct dummy_chan *ch = data; 234 235 return (ch->run ? ch->ptr : 0); 236 } 237 238 static struct pcmchan_caps * 239 dummy_chan_getcaps(kobj_t obj, void *data) 240 { 241 struct dummy_chan *ch = data; 242 243 return (ch->caps); 244 } 245 246 static kobj_method_t dummy_chan_methods[] = { 247 KOBJMETHOD(channel_init, dummy_chan_init), 248 KOBJMETHOD(channel_free, dummy_chan_free), 249 KOBJMETHOD(channel_setformat, dummy_chan_setformat), 250 KOBJMETHOD(channel_setspeed, dummy_chan_setspeed), 251 KOBJMETHOD(channel_setblocksize,dummy_chan_setblocksize), 252 KOBJMETHOD(channel_trigger, dummy_chan_trigger), 253 KOBJMETHOD(channel_getptr, dummy_chan_getptr), 254 KOBJMETHOD(channel_getcaps, dummy_chan_getcaps), 255 KOBJMETHOD_END 256 }; 257 258 CHANNEL_DECLARE(dummy_chan); 259 260 static int 261 dummy_mixer_init(struct snd_mixer *m) 262 { 263 struct dummy_softc *sc; 264 265 sc = mix_getdevinfo(m); 266 if (sc == NULL) 267 return (-1); 268 269 pcm_setflags(sc->dev, pcm_getflags(sc->dev) | SD_F_SOFTPCMVOL); 270 mix_setdevs(m, SOUND_MASK_PCM | SOUND_MASK_VOLUME | SOUND_MASK_RECLEV); 271 mix_setrecdevs(m, SOUND_MASK_RECLEV); 272 273 return (0); 274 } 275 276 static int 277 dummy_mixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) 278 { 279 return (0); 280 } 281 282 static uint32_t 283 dummy_mixer_setrecsrc(struct snd_mixer *m, uint32_t src) 284 { 285 return (src == SOUND_MASK_RECLEV ? src : 0); 286 } 287 288 static kobj_method_t dummy_mixer_methods[] = { 289 KOBJMETHOD(mixer_init, dummy_mixer_init), 290 KOBJMETHOD(mixer_set, dummy_mixer_set), 291 KOBJMETHOD(mixer_setrecsrc, dummy_mixer_setrecsrc), 292 KOBJMETHOD_END 293 }; 294 295 MIXER_DECLARE(dummy_mixer); 296 297 static void 298 dummy_identify(driver_t *driver, device_t parent) 299 { 300 if (device_find_child(parent, driver->name, DEVICE_UNIT_ANY) != NULL) 301 return; 302 if (BUS_ADD_CHILD(parent, 0, driver->name, DEVICE_UNIT_ANY) == NULL) 303 device_printf(parent, "add child failed\n"); 304 } 305 306 static int 307 dummy_probe(device_t dev) 308 { 309 device_set_desc(dev, "Dummy Audio Device"); 310 311 return (0); 312 } 313 314 static int 315 dummy_attach(device_t dev) 316 { 317 struct dummy_softc *sc; 318 char status[SND_STATUSLEN]; 319 int i = 0; 320 321 sc = device_get_softc(dev); 322 sc->dev = dev; 323 sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_dummy softc"); 324 callout_init_mtx(&sc->callout, sc->lock, 0); 325 326 sc->cap_fmts[0] = SND_FORMAT(AFMT_S32_LE, 2, 0); 327 sc->cap_fmts[1] = SND_FORMAT(AFMT_S24_LE, 2, 0); 328 sc->cap_fmts[2] = SND_FORMAT(AFMT_S16_LE, 2, 0); 329 sc->cap_fmts[3] = 0; 330 sc->caps = (struct pcmchan_caps){ 331 8000, /* minspeed */ 332 96000, /* maxspeed */ 333 sc->cap_fmts, /* fmtlist */ 334 0, /* caps */ 335 }; 336 337 pcm_setflags(dev, pcm_getflags(dev) | SD_F_MPSAFE); 338 pcm_init(dev, sc); 339 for (i = 0; i < DUMMY_NPCHAN; i++) 340 pcm_addchan(dev, PCMDIR_PLAY, &dummy_chan_class, sc); 341 for (i = 0; i < DUMMY_NRCHAN; i++) 342 pcm_addchan(dev, PCMDIR_REC, &dummy_chan_class, sc); 343 344 snprintf(status, SND_STATUSLEN, "on %s", 345 device_get_nameunit(device_get_parent(dev))); 346 if (pcm_register(dev, status)) 347 return (ENXIO); 348 mixer_init(dev, &dummy_mixer_class, sc); 349 350 /* 351 * Create an alias so that tests do not need to guess which one is the 352 * dummy device if there are more devices present in the system. 353 */ 354 make_dev_alias(sc->info.dsp_dev, "dsp.dummy"); 355 356 return (0); 357 } 358 359 static int 360 dummy_detach(device_t dev) 361 { 362 struct dummy_softc *sc = device_get_softc(dev); 363 int err; 364 365 snd_mtxlock(sc->lock); 366 sc->stopped = true; 367 snd_mtxunlock(sc->lock); 368 callout_drain(&sc->callout); 369 err = pcm_unregister(dev); 370 snd_mtxfree(sc->lock); 371 372 return (err); 373 } 374 375 static device_method_t dummy_methods[] = { 376 /* Device interface */ 377 DEVMETHOD(device_identify, dummy_identify), 378 DEVMETHOD(device_probe, dummy_probe), 379 DEVMETHOD(device_attach, dummy_attach), 380 DEVMETHOD(device_detach, dummy_detach), 381 DEVMETHOD_END 382 }; 383 384 static driver_t dummy_driver = { 385 "pcm", 386 dummy_methods, 387 sizeof(struct dummy_softc), 388 }; 389 390 DRIVER_MODULE(snd_dummy, nexus, dummy_driver, 0, 0); 391 MODULE_DEPEND(snd_dummy, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); 392 MODULE_VERSION(snd_dummy, 1); 393