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