xref: /freebsd/sys/dev/firewire/fwisound.c (revision dd56711e83533ffe40f3f54b01565537f85381cc)
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