1 /*
2 * Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7 /*
8 * fwisound(4) - Apple FireWire audio driver
9 *
10 * Protocol reverse-engineered by Clemens Ladisch (Linux isight_audio).
11 */
12
13 #include <sys/param.h>
14 #include <sys/systm.h>
15 #include <sys/module.h>
16 #include <sys/bus.h>
17 #include <sys/kernel.h>
18 #include <sys/malloc.h>
19 #include <sys/lock.h>
20 #include <sys/mutex.h>
21 #include <sys/sysctl.h>
22 #include <sys/taskqueue.h>
23 #include <sys/mbuf.h>
24
25 #include <dev/firewire/firewire.h>
26 #include <dev/firewire/firewirereg.h>
27 #include <dev/firewire/iec13213.h>
28 #include <dev/firewire/fwisound.h>
29 #include <dev/firewire/fw_helpers.h>
30
31 static MALLOC_DEFINE(M_FWISOUND, "fwisound", "Apple FireWire Audio");
32
33 static int debug = 0;
34 static int iso_channel = FWISOUND_ISO_CHANNEL;
35 SYSCTL_DECL(_hw_firewire);
36 static SYSCTL_NODE(_hw_firewire, OID_AUTO, fwisound,
37 CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "Apple FireWire Audio");
38 SYSCTL_INT(_hw_firewire_fwisound, OID_AUTO, debug, CTLFLAG_RWTUN, &debug, 0,
39 "fwisound debug level");
40 SYSCTL_INT(_hw_firewire_fwisound, OID_AUTO, iso_channel, CTLFLAG_RWTUN,
41 &iso_channel, 0, "ISO channel for isochronous receive (default 1)");
42
43 #define FWISOUND_DEBUG(lev, fmt, ...) \
44 do { \
45 if (debug >= (lev)) \
46 printf("fwisound: " fmt, ## __VA_ARGS__); \
47 } while (0)
48
49 static int fwisound_probe(device_t);
50 static int fwisound_attach(device_t);
51 static int fwisound_detach(device_t);
52 static void fwisound_probe_task(void *, int);
53 static void fwisound_start_task(void *, int);
54 static void fwisound_stop_task(void *, int);
55 static void fwisound_iso_input(struct fw_xferq *);
56
57 static int
fwisound_read_quadlet(struct fwisound_softc * sc,uint32_t offset,uint32_t * val)58 fwisound_read_quadlet(struct fwisound_softc *sc, uint32_t offset, uint32_t *val)
59 {
60 uint16_t dst;
61 uint8_t spd;
62 int err;
63
64 FWISOUND_LOCK(sc);
65 if (sc->fwdev == NULL) {
66 FWISOUND_UNLOCK(sc);
67 return (ENXIO);
68 }
69 dst = FWLOCALBUS | sc->fwdev->dst;
70 spd = min(sc->fwdev->speed, FWSPD_S400);
71 FWISOUND_UNLOCK(sc);
72
73 err = fw_read_quadlet(sc->fd.fc, M_FWISOUND, dst, spd,
74 sc->cmd_hi, sc->cmd_lo + offset, val);
75 if (err)
76 FWISOUND_DEBUG(1, "read_quadlet: offset=0x%x err=%d\n",
77 offset, err);
78 return (err);
79 }
80
81 static int
fwisound_write_quadlet(struct fwisound_softc * sc,uint32_t offset,uint32_t val)82 fwisound_write_quadlet(struct fwisound_softc *sc, uint32_t offset, uint32_t val)
83 {
84 uint16_t dst;
85 uint8_t spd;
86 int err;
87
88 FWISOUND_LOCK(sc);
89 if (sc->fwdev == NULL) {
90 FWISOUND_UNLOCK(sc);
91 return (ENXIO);
92 }
93 dst = FWLOCALBUS | sc->fwdev->dst;
94 spd = min(sc->fwdev->speed, FWSPD_S400);
95 FWISOUND_UNLOCK(sc);
96
97 err = fw_write_quadlet(sc->fd.fc, M_FWISOUND, dst, spd,
98 sc->cmd_hi, sc->cmd_lo + offset, val);
99 if (err)
100 FWISOUND_DEBUG(1, "write_quadlet: offset=0x%x err=%d\n",
101 offset, err);
102 return (err);
103 }
104
105 /*
106 * Extract the audio command base register (key 0x40) from the unit directory.
107 */
108 static uint32_t
fwisound_find_audio_base(struct fw_unit * unit)109 fwisound_find_audio_base(struct fw_unit *unit)
110 {
111 struct fw_device *fwdev = unit->fwdev;
112 struct csrdirectory *udir;
113 struct csrreg *reg;
114 uint32_t udir_qoff, udir_len, rom_quads;
115 int i;
116
117 rom_quads = CSRROMSIZE / 4;
118 udir_qoff = unit->dir_offset / 4;
119 if (udir_qoff >= rom_quads)
120 return (0);
121
122 udir = (struct csrdirectory *)&fwdev->csrrom[udir_qoff];
123 udir_len = udir->crc_len;
124 if (udir_qoff + 1 + udir_len > rom_quads)
125 return (0);
126 if (!crom_crc_valid((uint32_t *)&udir->entry[0], udir_len, udir->crc)) {
127 if (firewire_debug)
128 printf("fwisound: bad CRC in unit directory at 0x%x\n",
129 udir_qoff);
130 }
131
132 for (i = 0; i < (int)udir_len; i++) {
133 if (udir_qoff + 1 + i >= rom_quads)
134 break;
135 reg = &udir->entry[i];
136 if (reg->key == IIDC_CROM_CMD_BASE && reg->val != 0)
137 return (reg->val);
138 }
139 return (0);
140 }
141
142 static void
fwisound_probe_task(void * arg,int pending __unused)143 fwisound_probe_task(void *arg, int pending __unused)
144 {
145 struct fwisound_softc *sc = (struct fwisound_softc *)arg;
146 uint32_t val;
147 int err;
148
149 if (sc->state == FWISOUND_STATE_DETACHING || sc->fwdev == NULL)
150 return;
151
152 err = fwisound_write_quadlet(sc, FWISOUND_REG_SAMPLE_RATE,
153 FWISOUND_RATE_48000);
154 if (err) {
155 device_printf(sc->dev,
156 "failed to set sample rate: %d\n", err);
157 return;
158 }
159
160 if (fwisound_read_quadlet(sc, FWISOUND_REG_GAIN_RAW_START, &val) == 0)
161 sc->gain_raw_min = (int32_t)val;
162 if (fwisound_read_quadlet(sc, FWISOUND_REG_GAIN_RAW_END, &val) == 0)
163 sc->gain_raw_max = (int32_t)val;
164
165 FWISOUND_LOCK(sc);
166 if (sc->state == FWISOUND_STATE_DETACHING) {
167 FWISOUND_UNLOCK(sc);
168 return;
169 }
170 sc->state = FWISOUND_STATE_PROBED;
171 FWISOUND_UNLOCK(sc);
172 }
173
174 static void
fwisound_start_task(void * arg,int pending __unused)175 fwisound_start_task(void *arg, int pending __unused)
176 {
177 struct fwisound_softc *sc = (struct fwisound_softc *)arg;
178
179 if (sc->state == FWISOUND_STATE_DETACHING)
180 return;
181
182 fwisound_iso_start(sc);
183 }
184
185 static void
fwisound_stop_task(void * arg,int pending __unused)186 fwisound_stop_task(void *arg, int pending __unused)
187 {
188 struct fwisound_softc *sc = (struct fwisound_softc *)arg;
189
190 fwisound_iso_stop(sc);
191
192 FWISOUND_LOCK(sc);
193 if (sc->state != FWISOUND_STATE_DETACHING)
194 sc->state = FWISOUND_STATE_PROBED;
195 FWISOUND_UNLOCK(sc);
196 }
197
198 int
fwisound_iso_start(struct fwisound_softc * sc)199 fwisound_iso_start(struct fwisound_softc *sc)
200 {
201 struct firewire_comm *fc = sc->fd.fc;
202 struct fw_xferq *xferq;
203 uint32_t val;
204 uint8_t speed;
205 int dma_ch, err;
206
207 mtx_assert(&sc->mtx, MA_NOTOWNED);
208
209 FWISOUND_LOCK(sc);
210 if (sc->dma_ch >= 0 || sc->state == FWISOUND_STATE_STREAMING) {
211 FWISOUND_UNLOCK(sc);
212 return (0);
213 }
214 if (sc->state == FWISOUND_STATE_DETACHING || sc->fwdev == NULL) {
215 FWISOUND_UNLOCK(sc);
216 return (ENXIO);
217 }
218 speed = min(sc->fwdev->speed, FWSPD_S400);
219 FWISOUND_UNLOCK(sc);
220
221 dma_ch = fw_open_isodma(fc, 0);
222 if (dma_ch < 0) {
223 device_printf(sc->dev, "no IR DMA channel available\n");
224 return (EBUSY);
225 }
226
227 FWISOUND_LOCK(sc);
228 if (sc->dma_ch >= 0) {
229 FWISOUND_UNLOCK(sc);
230 fc->ir[dma_ch]->flag &= ~FWXFERQ_OPEN;
231 return (0);
232 }
233 FWISOUND_UNLOCK(sc);
234
235 xferq = fc->ir[dma_ch];
236 xferq->flag |= FWXFERQ_EXTBUF | FWXFERQ_HANDLER | FWXFERQ_STREAM;
237
238 xferq->flag &= ~FWXFERQ_CHTAGMASK;
239 xferq->flag |= sc->iso_channel & FWXFERQ_CHTAGMASK;
240
241 xferq->sc = (caddr_t)sc;
242 xferq->hand = fwisound_iso_input;
243 xferq->bnchunk = FWISOUND_ISO_NCHUNK;
244 xferq->bnpacket = 1;
245 xferq->psize = FWISOUND_ISO_PKTSIZE;
246 xferq->queued = 0;
247 xferq->buf = NULL;
248
249 xferq->bulkxfer = malloc(
250 sizeof(struct fw_bulkxfer) * xferq->bnchunk,
251 M_FWISOUND, M_WAITOK | M_ZERO);
252
253 fw_iso_init_chunks(xferq);
254
255 sc->sample_total = 0;
256
257 val = sc->iso_channel | ((uint32_t)speed << 16);
258 err = fwisound_write_quadlet(sc, FWISOUND_REG_ISO_TX_CONFIG, val);
259 if (err) {
260 device_printf(sc->dev,
261 "failed to set ISO_TX_CONFIG: %d\n", err);
262 goto fail;
263 }
264
265 err = fwisound_write_quadlet(sc, FWISOUND_REG_AUDIO_ENABLE,
266 FWISOUND_AUDIO_ENABLE);
267 if (err) {
268 device_printf(sc->dev,
269 "failed to enable audio: %d\n", err);
270 goto fail;
271 }
272
273 err = fc->irx_enable(fc, dma_ch);
274 if (err) {
275 device_printf(sc->dev, "failed to start IR DMA: %d\n", err);
276 fwisound_write_quadlet(sc, FWISOUND_REG_AUDIO_ENABLE, 0);
277 goto fail;
278 }
279
280 FWISOUND_LOCK(sc);
281 if (sc->state == FWISOUND_STATE_DETACHING) {
282 FWISOUND_UNLOCK(sc);
283 fc->irx_disable(fc, dma_ch);
284 fwisound_write_quadlet(sc, FWISOUND_REG_AUDIO_ENABLE, 0);
285 err = ENXIO;
286 goto fail;
287 }
288 sc->dma_ch = dma_ch;
289 sc->state = FWISOUND_STATE_STREAMING;
290 FWISOUND_UNLOCK(sc);
291
292 return (0);
293
294 fail:
295 fw_iso_free_chunks(xferq, M_FWISOUND);
296 xferq->flag &= ~(FWXFERQ_MODEMASK | FWXFERQ_OPEN | FWXFERQ_STREAM |
297 FWXFERQ_EXTBUF | FWXFERQ_HANDLER | FWXFERQ_CHTAGMASK);
298 xferq->hand = NULL;
299 sc->dma_ch = -1;
300 return (err);
301 }
302
303 void
fwisound_iso_stop(struct fwisound_softc * sc)304 fwisound_iso_stop(struct fwisound_softc *sc)
305 {
306 struct firewire_comm *fc = sc->fd.fc;
307 struct fw_xferq *xferq;
308 int dma_ch;
309
310 FWISOUND_LOCK(sc);
311 dma_ch = sc->dma_ch;
312 if (dma_ch < 0) {
313 FWISOUND_UNLOCK(sc);
314 return;
315 }
316 sc->dma_ch = -1;
317 FWISOUND_UNLOCK(sc);
318
319 xferq = fc->ir[dma_ch];
320
321 xferq->flag &= ~FWXFERQ_OPEN;
322 xferq->hand = NULL;
323
324 if (xferq->flag & FWXFERQ_RUNNING)
325 fc->irx_disable(fc, dma_ch);
326
327 if (sc->fwdev != NULL)
328 fwisound_write_quadlet(sc, FWISOUND_REG_AUDIO_ENABLE, 0);
329
330 FWISOUND_LOCK(sc);
331 fw_iso_wait_inactive_locked(&sc->mtx, &sc->iso_active, "fwisis");
332 FWISOUND_UNLOCK(sc);
333
334 xferq->flag &= ~(FWXFERQ_MODEMASK | FWXFERQ_STREAM |
335 FWXFERQ_EXTBUF | FWXFERQ_HANDLER | FWXFERQ_CHTAGMASK);
336
337 fw_iso_free_chunks(xferq, M_FWISOUND);
338 }
339
340 static void
fwisound_iso_input(struct fw_xferq * xferq)341 fwisound_iso_input(struct fw_xferq *xferq)
342 {
343 struct fwisound_softc *sc = (struct fwisound_softc *)xferq->sc;
344 struct fwisound_chan *chan;
345 struct fw_bulkxfer *sxfer;
346 struct fw_pkt *fp;
347 struct fwisound_payload *pay;
348 struct mbuf *m;
349 uint8_t *ring;
350 uint32_t plen, nbytes, sample_count, ring_size, pos, chunk, old_ptr;
351 int dma_ch, need_intr;
352
353 FWISOUND_LOCK(sc);
354 dma_ch = sc->dma_ch;
355 if (dma_ch < 0) {
356 FWISOUND_UNLOCK(sc);
357 return;
358 }
359 sc->iso_active = 1;
360 chan = sc->chan;
361 FWISOUND_UNLOCK(sc);
362
363 while ((sxfer = STAILQ_FIRST(&xferq->stvalid)) != NULL) {
364 STAILQ_REMOVE_HEAD(&xferq->stvalid, link);
365
366 m = fw_iso_dequeue(xferq, sxfer, sc->fd.fc);
367 if (m == NULL)
368 continue;
369
370 fp = mtod(m, struct fw_pkt *);
371 plen = fp->mode.stream.len;
372 if (plen < 20 || (uint32_t)m->m_len < 4 + plen) {
373 m_freem(m);
374 continue;
375 }
376
377 pay = (struct fwisound_payload *)(mtod(m, uint8_t *) + 4);
378 if (ntohl(pay->signature) != FWISOUND_SIGNATURE) {
379 m_freem(m);
380 continue;
381 }
382
383 sample_count = ntohl(pay->sample_count);
384 if (sample_count == 0 || sample_count > FWISOUND_MAX_SAMPLES) {
385 m_freem(m);
386 continue;
387 }
388
389 nbytes = sample_count * 4;
390 if (plen < 16 + nbytes) {
391 m_freem(m);
392 continue;
393 }
394
395 if (sc->sample_total != 0 &&
396 ntohl(pay->sample_total) != sc->sample_total)
397 sc->dropped++;
398 sc->sample_total = ntohl(pay->sample_total) + sample_count;
399
400 need_intr = 0;
401 if (chan != NULL && chan->running &&
402 chan->buf != NULL && chan->pcm_chan != NULL) {
403 ring = sndbuf_getbufofs(chan->buf, 0);
404 ring_size = chan->buf->bufsize;
405 old_ptr = chan->hw_ptr;
406
407 uint8_t *src = (uint8_t *)pay->samples;
408 uint32_t rem = nbytes;
409
410 while (rem > 0) {
411 pos = chan->hw_ptr % ring_size;
412 chunk = MIN(rem, ring_size - pos);
413 memcpy(ring + pos, src, chunk);
414 chan->hw_ptr += chunk;
415 src += chunk;
416 rem -= chunk;
417 }
418
419 if (chan->buf->blksz == 0 ||
420 (old_ptr / chan->buf->blksz) !=
421 (chan->hw_ptr / chan->buf->blksz))
422 need_intr = 1;
423 }
424
425 m_freem(m);
426
427 if (need_intr) {
428 chn_intr(chan->pcm_chan);
429 }
430 }
431
432 fw_iso_rearm_done(xferq, sc->fd.fc, &sc->mtx, &sc->iso_active,
433 &sc->dma_ch, dma_ch);
434 }
435
436
437 static int
fwisound_probe(device_t dev)438 fwisound_probe(device_t dev)
439 {
440 struct fw_unit *unit;
441
442 unit = fw_get_unit(dev);
443 if (unit == NULL)
444 return (ENXIO);
445
446 if (unit->spec_id != FWISOUND_SPEC_ID ||
447 unit->sw_version != FWISOUND_VERSION)
448 return (ENXIO);
449
450 device_set_desc(dev, "Apple FireWire Audio");
451 return (BUS_PROBE_DEFAULT);
452 }
453
454 static int
fwisound_attach(device_t dev)455 fwisound_attach(device_t dev)
456 {
457 struct fwisound_softc *sc;
458 struct fw_unit *unit;
459 uint32_t audio_base;
460
461 unit = fw_get_unit(dev);
462 if (unit == NULL || unit->fwdev == NULL)
463 return (ENXIO);
464
465 audio_base = fwisound_find_audio_base(unit);
466 if (audio_base == 0) {
467 device_printf(dev, "no audio base in unit directory\n");
468 return (ENXIO);
469 }
470
471 sc = device_get_softc(dev);
472 sc->fd.dev = dev;
473 sc->fd.fc = fw_get_comm(dev);
474 sc->dev = dev;
475 mtx_init(&sc->mtx, "fwisound", NULL, MTX_DEF);
476
477 sc->fwdev = unit->fwdev;
478 sc->cmd_hi = 0xffff;
479 sc->cmd_lo = 0xf0000000 | (audio_base << 2);
480 sc->state = FWISOUND_STATE_IDLE;
481 sc->dma_ch = -1;
482 sc->iso_active = 0;
483 sc->iso_channel = (uint8_t)(iso_channel & FWXFERQ_CHTAGMASK);
484 sc->chan = NULL;
485 TASK_INIT(&sc->probe_task, 0, fwisound_probe_task, sc);
486 TASK_INIT(&sc->start_task, 0, fwisound_start_task, sc);
487 TASK_INIT(&sc->stop_task, 0, fwisound_stop_task, sc);
488
489 sc->pcm_dev = device_add_child(dev, "pcm", DEVICE_UNIT_ANY);
490 if (sc->pcm_dev == NULL) {
491 device_printf(dev, "failed to add pcm child\n");
492 mtx_destroy(&sc->mtx);
493 return (ENXIO);
494 }
495
496 bus_attach_children(dev);
497
498 if (taskqueue_enqueue(taskqueue_thread, &sc->probe_task) != 0)
499 device_printf(dev, "failed to enqueue probe task\n");
500
501 return (0);
502 }
503
504 static int
fwisound_detach(device_t dev)505 fwisound_detach(device_t dev)
506 {
507 struct fwisound_softc *sc;
508
509 sc = device_get_softc(dev);
510
511 FWISOUND_LOCK(sc);
512 sc->state = FWISOUND_STATE_DETACHING;
513 FWISOUND_UNLOCK(sc);
514
515 taskqueue_drain(taskqueue_thread, &sc->probe_task);
516 taskqueue_drain(taskqueue_thread, &sc->start_task);
517 taskqueue_drain(taskqueue_thread, &sc->stop_task);
518 fwisound_iso_stop(sc);
519
520 if (sc->pcm_dev != NULL)
521 device_delete_children(dev);
522
523 FWISOUND_LOCK(sc);
524 sc->fwdev = NULL;
525 FWISOUND_UNLOCK(sc);
526
527 mtx_destroy(&sc->mtx);
528 return (0);
529 }
530
531 static device_method_t fwisound_methods[] = {
532 DEVMETHOD(device_probe, fwisound_probe),
533 DEVMETHOD(device_attach, fwisound_attach),
534 DEVMETHOD(device_detach, fwisound_detach),
535
536 DEVMETHOD_END
537 };
538
539 static driver_t fwisound_driver = {
540 "fwisound",
541 fwisound_methods,
542 sizeof(struct fwisound_softc),
543 };
544
545 DRIVER_MODULE(fwisound, firewire, fwisound_driver, 0, 0);
546 MODULE_VERSION(fwisound, 1);
547 MODULE_DEPEND(fwisound, firewire, 1, 1, 1);
548 MODULE_DEPEND(fwisound, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER);
549