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