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