The branch main has been updated by adrian: URL: https://cgit.FreeBSD.org/src/commit/?id=cd7b65080f2f2e038ddba42048f24f8e98b011f2
commit cd7b65080f2f2e038ddba42048f24f8e98b011f2 Author: Abdelkader Boudih <[email protected]> AuthorDate: 2026-07-09 02:52:43 +0000 Commit: Adrian Chadd <[email protected]> CommitDate: 2026-07-09 02:52:46 +0000 fwisound: add Apple FireWire audio driver Expose audio capture from Apple FireWire devices as a standard pcm(4)/dsp(4) device via the newpcm framework. (adrian: I've tested this on an isight camera and looped it back to USB speakers via "sox -t oss /dev/dsp3 -t oss /dev/dsp4") Reviewed by: adrian Differential Revision: https://reviews.freebsd.org/D58109 --- share/man/man4/Makefile | 1 + share/man/man4/fwisound.4 | 96 ++++++ sys/conf/NOTES | 1 + sys/conf/files | 2 + sys/dev/firewire/fwisound.c | 609 +++++++++++++++++++++++++++++++++ sys/dev/firewire/fwisound.h | 112 ++++++ sys/dev/firewire/fwisound_pcm.c | 233 +++++++++++++ sys/modules/firewire/Makefile | 1 + sys/modules/firewire/fwisound/Makefile | 12 + 9 files changed, 1067 insertions(+) diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile index 775a3e84f933..56b50d39d6f4 100644 --- a/share/man/man4/Makefile +++ b/share/man/man4/Makefile @@ -179,6 +179,7 @@ MAN= aac.4 \ filemon.4 \ firewire.4 \ fwcam.4 \ + fwisound.4 \ ${_ftgpio.4} \ ${_ftwd.4} \ full.4 \ diff --git a/share/man/man4/fwisound.4 b/share/man/man4/fwisound.4 new file mode 100644 index 000000000000..0da89fbe2bdc --- /dev/null +++ b/share/man/man4/fwisound.4 @@ -0,0 +1,96 @@ +.\" +.\" Copyright (c) 2026 Abdelkader Boudih <[email protected]> +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd June 19, 2026 +.Dt FWISOUND 4 +.Os +.Sh NAME +.Nm fwisound +.Nd Apple FireWire audio driver +.Sh SYNOPSIS +.Cd "device firewire" +.Cd "device fwisound" +.Cd "device sound" +.Sh DESCRIPTION +The +.Nm +driver provides audio capture from Apple FireWire devices that use a +proprietary isochronous audio protocol +.Pq not IEC 61883/AMDTP . +.Pp +The driver matches FireWire units with Apple OUI +.Pq 0x000a27 +and decodes the +.Qq sght +packet signature to deliver stereo PCM audio to the +.Xr sound 4 +framework. +.Pp +The device attaches as a standard +.Xr sound 4 +capture device accessible at +.Pa /dev/dspX . +The fixed audio format is 48000\~Hz, 2-channel, signed 16-bit big-endian +.Pq S16BE . +.Sh HARDWARE +The +.Nm +driver supports Apple FireWire devices with the following unit directory: +.Pp +.Bl -tag -width "unit_sw_version" -compact +.It Va unit_spec_ID +0x000a27 +.Pq Apple OUI +.It Va unit_sw_version +0x000010 +.El +.Pp +Known compatible hardware: +.Pp +.Bl -bullet -compact +.It +Apple iSight (FireWire cylinder) +.El +.Sh SYSCTLS +.Bl -tag -width ".Va hw.firewire.fwisound.iso_channel" +.It Va hw.firewire.fwisound.iso_channel +Isochronous receive channel (default 1). +Can be set as a tunable in +.Xr loader.conf 5 . +Must not conflict with +.Va hw.firewire.fwcam.iso_channel +.Pq default 0 . +.El +.Sh FILES +.Bl -tag -width ".Pa /dev/dspX" -compact +.It Pa /dev/dspX +PCM capture device created by +.Xr sound 4 . +.El +.Sh EXAMPLES +Record five seconds of audio: +.Bd -literal -offset indent +rec -r 48000 -c 2 -e signed-integer -b 16 -B /tmp/out.wav trim 5 +.Ed +.Pp +If multiple sound devices are present, address the device explicitly: +.Bd -literal -offset indent +sox -t ossdsp /dev/dsp4 -r 48000 -c 2 -e signed-integer -b 16 \e + /tmp/out.wav trim 5 +.Ed +.Sh SEE ALSO +.Xr firewire 4 , +.Xr fwohci 4 , +.Xr sound 4 , +.Xr sysctl 8 +.Sh HISTORY +The +.Nm +driver first appeared in +.Fx 16.0 . +.Sh AUTHORS +.An Abdelkader Boudih Aq Mt [email protected] +.Pp +Protocol reverse-engineered by Clemens Ladisch. diff --git a/sys/conf/NOTES b/sys/conf/NOTES index 41f2514788df..c7931db374c7 100644 --- a/sys/conf/NOTES +++ b/sys/conf/NOTES @@ -2614,6 +2614,7 @@ device sbp_targ # SBP-2 Target mode (Requires scbus and targ) device fwe # Ethernet over FireWire (non-standard!) device fwip # IP over FireWire (RFC2734 and RFC3146) device fwcam # IIDC digital cameras over FireWire +device fwisound # Apple FireWire audio ##################################################################### # dcons support (Dumb Console Device) diff --git a/sys/conf/files b/sys/conf/files index 3635e6474708..09d4ff70e322 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -1733,6 +1733,8 @@ dev/firewire/fwcrom.c optional firewire dev/firewire/fwdev.c optional firewire dev/firewire/fwcam.c optional fwcam dev/firewire/fwdma.c optional firewire +dev/firewire/fwisound.c optional fwisound sound +dev/firewire/fwisound_pcm.c optional fwisound sound dev/firewire/fwmem.c optional firewire dev/firewire/fwohci.c optional firewire dev/firewire/fwohci_pci.c optional firewire pci diff --git a/sys/dev/firewire/fwisound.c b/sys/dev/firewire/fwisound.c new file mode 100644 index 000000000000..9766f88060f7 --- /dev/null +++ b/sys/dev/firewire/fwisound.c @@ -0,0 +1,609 @@ +/* + * Copyright (c) 2026 Abdelkader Boudih <[email protected]> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* + * fwisound(4) - Apple FireWire audio driver + * + * Protocol reverse-engineered by Clemens Ladisch (Linux isight_audio). + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/sysctl.h> +#include <sys/taskqueue.h> +#include <sys/mbuf.h> + +#include <dev/firewire/firewire.h> +#include <dev/firewire/firewirereg.h> +#include <dev/firewire/iec13213.h> +#include <dev/firewire/fwisound.h> +#include <dev/firewire/fw_helpers.h> + +static MALLOC_DEFINE(M_FWISOUND, "fwisound", "Apple FireWire Audio"); + +static int debug = 0; +static int iso_channel = FWISOUND_ISO_CHANNEL; +SYSCTL_DECL(_hw_firewire); +static SYSCTL_NODE(_hw_firewire, OID_AUTO, fwisound, + CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "Apple FireWire Audio"); +SYSCTL_INT(_hw_firewire_fwisound, OID_AUTO, debug, CTLFLAG_RWTUN, &debug, 0, + "fwisound debug level"); +SYSCTL_INT(_hw_firewire_fwisound, OID_AUTO, iso_channel, CTLFLAG_RWTUN, + &iso_channel, 0, "ISO channel for isochronous receive (default 1)"); + +#define FWISOUND_DEBUG(lev, fmt, ...) \ + do { \ + if (debug >= (lev)) \ + printf("fwisound: " fmt, ## __VA_ARGS__); \ + } while (0) + +static void fwisound_identify(driver_t *, device_t); +static int fwisound_probe(device_t); +static int fwisound_attach(device_t); +static int fwisound_detach(device_t); +static void fwisound_post_busreset(void *); +static void fwisound_post_explore(void *); +static void fwisound_probe_task(void *, int); +static void fwisound_start_task(void *, int); +static void fwisound_stop_task(void *, int); +static void fwisound_iso_input(struct fw_xferq *); + +static int +fwisound_read_quadlet(struct fwisound_softc *sc, uint32_t offset, uint32_t *val) +{ + uint16_t dst; + uint8_t spd; + int err; + + FWISOUND_LOCK(sc); + if (sc->fwdev == NULL) { + FWISOUND_UNLOCK(sc); + return (ENXIO); + } + dst = FWLOCALBUS | sc->fwdev->dst; + spd = min(sc->fwdev->speed, FWSPD_S400); + FWISOUND_UNLOCK(sc); + + err = fw_read_quadlet(sc->fd.fc, M_FWISOUND, dst, spd, + sc->cmd_hi, sc->cmd_lo + offset, val); + if (err) + FWISOUND_DEBUG(1, "read_quadlet: offset=0x%x err=%d\n", + offset, err); + return (err); +} + +static int +fwisound_write_quadlet(struct fwisound_softc *sc, uint32_t offset, uint32_t val) +{ + uint16_t dst; + uint8_t spd; + int err; + + FWISOUND_LOCK(sc); + if (sc->fwdev == NULL) { + FWISOUND_UNLOCK(sc); + return (ENXIO); + } + dst = FWLOCALBUS | sc->fwdev->dst; + spd = min(sc->fwdev->speed, FWSPD_S400); + FWISOUND_UNLOCK(sc); + + err = fw_write_quadlet(sc->fd.fc, M_FWISOUND, dst, spd, + sc->cmd_hi, sc->cmd_lo + offset, val); + if (err) + FWISOUND_DEBUG(1, "write_quadlet: offset=0x%x err=%d\n", + offset, err); + return (err); +} + +static uint32_t +fwisound_find_audio_unit(struct fw_device *fwdev) +{ + struct csrhdr *hdr; + struct csrdirectory *root, *udir; + struct csrreg *reg, *u; + int i, j, spec_ok, ver_ok; + uint32_t audio_base; + + hdr = (struct csrhdr *)fwdev->csrrom; + if (hdr->info_len <= 1) + return (0); + + root = (struct csrdirectory *)(fwdev->csrrom + 1 + hdr->info_len); + + for (i = 0; i < root->crc_len; i++) { + reg = &root->entry[i]; + + if (reg->key != CROM_UDIR) + continue; + + udir = (struct csrdirectory *)(reg + reg->val); + + spec_ok = 0; + ver_ok = 0; + audio_base = 0; + + for (j = 0; j < udir->crc_len; j++) { + u = &udir->entry[j]; + + if (u->key == CSRKEY_SPEC && u->val == FWISOUND_SPEC_ID) + spec_ok = 1; + else if (u->key == CSRKEY_VER && + u->val == FWISOUND_VERSION) + ver_ok = 1; + else if (u->key == IIDC_CROM_CMD_BASE) + audio_base = u->val; + } + + if (spec_ok && ver_ok && audio_base != 0) + return (audio_base); + } + + return (0); +} + +static void +fwisound_probe_task(void *arg, int pending __unused) +{ + struct fwisound_softc *sc = (struct fwisound_softc *)arg; + uint32_t val; + int err; + + if (sc->state == FWISOUND_STATE_DETACHING || sc->fwdev == NULL) + return; + + err = fwisound_write_quadlet(sc, FWISOUND_REG_SAMPLE_RATE, + FWISOUND_RATE_48000); + if (err) { + device_printf(sc->dev, + "failed to set sample rate: %d\n", err); + return; + } + + if (fwisound_read_quadlet(sc, FWISOUND_REG_GAIN_RAW_START, &val) == 0) + sc->gain_raw_min = (int32_t)val; + if (fwisound_read_quadlet(sc, FWISOUND_REG_GAIN_RAW_END, &val) == 0) + sc->gain_raw_max = (int32_t)val; + + FWISOUND_LOCK(sc); + if (sc->state == FWISOUND_STATE_DETACHING) { + FWISOUND_UNLOCK(sc); + return; + } + sc->state = FWISOUND_STATE_PROBED; + FWISOUND_UNLOCK(sc); +} + +static void +fwisound_start_task(void *arg, int pending __unused) +{ + struct fwisound_softc *sc = (struct fwisound_softc *)arg; + + if (sc->state == FWISOUND_STATE_DETACHING) + return; + + fwisound_iso_start(sc); +} + +static void +fwisound_stop_task(void *arg, int pending __unused) +{ + struct fwisound_softc *sc = (struct fwisound_softc *)arg; + + fwisound_iso_stop(sc); + + FWISOUND_LOCK(sc); + if (sc->state != FWISOUND_STATE_DETACHING) + sc->state = FWISOUND_STATE_PROBED; + FWISOUND_UNLOCK(sc); +} + +int +fwisound_iso_start(struct fwisound_softc *sc) +{ + struct firewire_comm *fc = sc->fd.fc; + struct fw_xferq *xferq; + uint32_t val; + uint8_t speed; + int dma_ch, err; + + mtx_assert(&sc->mtx, MA_NOTOWNED); + + FWISOUND_LOCK(sc); + if (sc->dma_ch >= 0 || sc->state == FWISOUND_STATE_STREAMING) { + FWISOUND_UNLOCK(sc); + return (0); + } + if (sc->state == FWISOUND_STATE_DETACHING || sc->fwdev == NULL) { + FWISOUND_UNLOCK(sc); + return (ENXIO); + } + speed = min(sc->fwdev->speed, FWSPD_S400); + FWISOUND_UNLOCK(sc); + + dma_ch = fw_open_isodma(fc, 0); + if (dma_ch < 0) { + device_printf(sc->dev, "no IR DMA channel available\n"); + return (EBUSY); + } + + FWISOUND_LOCK(sc); + if (sc->dma_ch >= 0) { + FWISOUND_UNLOCK(sc); + fc->ir[dma_ch]->flag &= ~FWXFERQ_OPEN; + return (0); + } + FWISOUND_UNLOCK(sc); + + xferq = fc->ir[dma_ch]; + xferq->flag |= FWXFERQ_EXTBUF | FWXFERQ_HANDLER | FWXFERQ_STREAM; + + xferq->flag &= ~FWXFERQ_CHTAGMASK; + xferq->flag |= sc->iso_channel & FWXFERQ_CHTAGMASK; + + xferq->sc = (caddr_t)sc; + xferq->hand = fwisound_iso_input; + xferq->bnchunk = FWISOUND_ISO_NCHUNK; + xferq->bnpacket = 1; + xferq->psize = FWISOUND_ISO_PKTSIZE; + xferq->queued = 0; + xferq->buf = NULL; + + xferq->bulkxfer = malloc( + sizeof(struct fw_bulkxfer) * xferq->bnchunk, + M_FWISOUND, M_WAITOK | M_ZERO); + + fw_iso_init_chunks(xferq); + + sc->sample_total = 0; + + val = sc->iso_channel | ((uint32_t)speed << 16); + err = fwisound_write_quadlet(sc, FWISOUND_REG_ISO_TX_CONFIG, val); + if (err) { + device_printf(sc->dev, + "failed to set ISO_TX_CONFIG: %d\n", err); + goto fail; + } + + err = fwisound_write_quadlet(sc, FWISOUND_REG_AUDIO_ENABLE, + FWISOUND_AUDIO_ENABLE); + if (err) { + device_printf(sc->dev, + "failed to enable audio: %d\n", err); + goto fail; + } + + err = fc->irx_enable(fc, dma_ch); + if (err) { + device_printf(sc->dev, "failed to start IR DMA: %d\n", err); + fwisound_write_quadlet(sc, FWISOUND_REG_AUDIO_ENABLE, 0); + goto fail; + } + + FWISOUND_LOCK(sc); + if (sc->state == FWISOUND_STATE_DETACHING) { + FWISOUND_UNLOCK(sc); + fc->irx_disable(fc, dma_ch); + fwisound_write_quadlet(sc, FWISOUND_REG_AUDIO_ENABLE, 0); + err = ENXIO; + goto fail; + } + sc->dma_ch = dma_ch; + sc->state = FWISOUND_STATE_STREAMING; + FWISOUND_UNLOCK(sc); + + return (0); + +fail: + fw_iso_free_chunks(xferq, M_FWISOUND); + xferq->flag &= ~(FWXFERQ_MODEMASK | FWXFERQ_OPEN | FWXFERQ_STREAM | + FWXFERQ_EXTBUF | FWXFERQ_HANDLER | FWXFERQ_CHTAGMASK); + xferq->hand = NULL; + sc->dma_ch = -1; + return (err); +} + +void +fwisound_iso_stop(struct fwisound_softc *sc) +{ + struct firewire_comm *fc = sc->fd.fc; + struct fw_xferq *xferq; + int dma_ch; + + FWISOUND_LOCK(sc); + dma_ch = sc->dma_ch; + if (dma_ch < 0) { + FWISOUND_UNLOCK(sc); + return; + } + sc->dma_ch = -1; + FWISOUND_UNLOCK(sc); + + xferq = fc->ir[dma_ch]; + + xferq->flag &= ~FWXFERQ_OPEN; + xferq->hand = NULL; + + if (xferq->flag & FWXFERQ_RUNNING) + fc->irx_disable(fc, dma_ch); + + if (sc->fwdev != NULL) + fwisound_write_quadlet(sc, FWISOUND_REG_AUDIO_ENABLE, 0); + + FWISOUND_LOCK(sc); + fw_iso_wait_inactive_locked(&sc->mtx, &sc->iso_active, "fwisis"); + FWISOUND_UNLOCK(sc); + + xferq->flag &= ~(FWXFERQ_MODEMASK | FWXFERQ_STREAM | + FWXFERQ_EXTBUF | FWXFERQ_HANDLER | FWXFERQ_CHTAGMASK); + + fw_iso_free_chunks(xferq, M_FWISOUND); +} + +static void +fwisound_iso_input(struct fw_xferq *xferq) +{ + struct fwisound_softc *sc = (struct fwisound_softc *)xferq->sc; + struct fwisound_chan *chan; + struct fw_bulkxfer *sxfer; + struct fw_pkt *fp; + struct fwisound_payload *pay; + struct mbuf *m; + uint8_t *ring; + uint32_t plen, nbytes, sample_count, ring_size, pos, chunk, old_ptr; + int dma_ch, need_intr; + + FWISOUND_LOCK(sc); + dma_ch = sc->dma_ch; + if (dma_ch < 0) { + FWISOUND_UNLOCK(sc); + return; + } + sc->iso_active = 1; + chan = sc->chan; + FWISOUND_UNLOCK(sc); + + while ((sxfer = STAILQ_FIRST(&xferq->stvalid)) != NULL) { + STAILQ_REMOVE_HEAD(&xferq->stvalid, link); + + m = fw_iso_dequeue(xferq, sxfer, sc->fd.fc); + if (m == NULL) + continue; + + fp = mtod(m, struct fw_pkt *); + plen = fp->mode.stream.len; + if (plen < 20 || (uint32_t)m->m_len < 4 + plen) { + m_freem(m); + continue; + } + + pay = (struct fwisound_payload *)(mtod(m, uint8_t *) + 4); + if (ntohl(pay->signature) != FWISOUND_SIGNATURE) { + m_freem(m); + continue; + } + + sample_count = ntohl(pay->sample_count); + if (sample_count == 0 || sample_count > 475) { + m_freem(m); + continue; + } + + nbytes = sample_count * 4; + if (plen < 16 + nbytes) { + m_freem(m); + continue; + } + + if (sc->sample_total != 0 && + ntohl(pay->sample_total) != sc->sample_total) + sc->dropped++; + sc->sample_total = ntohl(pay->sample_total) + sample_count; + + need_intr = 0; + if (chan != NULL && chan->running && + chan->buf != NULL && chan->pcm_chan != NULL) { + ring = sndbuf_getbufofs(chan->buf, 0); + ring_size = chan->buf->bufsize; + old_ptr = chan->hw_ptr; + + uint8_t *src = (uint8_t *)pay->samples; + uint32_t rem = nbytes; + + while (rem > 0) { + pos = chan->hw_ptr % ring_size; + chunk = MIN(rem, ring_size - pos); + memcpy(ring + pos, src, chunk); + chan->hw_ptr += chunk; + src += chunk; + rem -= chunk; + } + + if (chan->buf->blksz == 0 || + (old_ptr / chan->buf->blksz) != + (chan->hw_ptr / chan->buf->blksz)) + need_intr = 1; + } + + m_freem(m); + + if (need_intr) { + chn_intr(chan->pcm_chan); + } + } + + fw_iso_rearm_done(xferq, sc->fd.fc, &sc->mtx, &sc->iso_active, + &sc->dma_ch, dma_ch); +} + +static void +fwisound_post_explore(void *arg) +{ + struct fwisound_softc *sc = (struct fwisound_softc *)arg; + struct fw_device *fwdev; + uint32_t audio_base; + int err; + + FWISOUND_LOCK(sc); + if (sc->state == FWISOUND_STATE_DETACHING) { + FWISOUND_UNLOCK(sc); + return; + } + + if (sc->fwdev != NULL) { + STAILQ_FOREACH(fwdev, &sc->fd.fc->devices, link) { + if (fwdev == sc->fwdev && + fwdev->status == FWDEVATTACHED) + break; + } + if (fwdev == NULL) { + device_printf(sc->dev, "device disconnected\n"); + sc->fwdev = NULL; + sc->state = FWISOUND_STATE_IDLE; + FWISOUND_UNLOCK(sc); + fwisound_iso_stop(sc); + FWISOUND_LOCK(sc); + } + } + + if (sc->fwdev == NULL) { + STAILQ_FOREACH(fwdev, &sc->fd.fc->devices, link) { + if (fwdev->status != FWDEVATTACHED) + continue; + + audio_base = fwisound_find_audio_unit(fwdev); + if (audio_base == 0) + continue; + + sc->fwdev = fwdev; + sc->cmd_hi = 0xffff; + sc->cmd_lo = 0xf0000000 | (audio_base << 2); + + FWISOUND_UNLOCK(sc); + err = taskqueue_enqueue(taskqueue_thread, + &sc->probe_task); + if (err != 0) + device_printf(sc->dev, + "taskqueue_enqueue failed: %d\n", err); + return; + } + } + + FWISOUND_UNLOCK(sc); +} + +static void +fwisound_post_busreset(void *arg __unused) +{ + +} + +static void +fwisound_identify(driver_t *driver, device_t parent) +{ + + if (device_find_child(parent, "fwisound", DEVICE_UNIT_ANY) == NULL) + BUS_ADD_CHILD(parent, 0, "fwisound", DEVICE_UNIT_ANY); +} + +static int +fwisound_probe(device_t dev) +{ + + device_set_desc(dev, "Apple FireWire Audio"); + return (0); +} + +static int +fwisound_attach(device_t dev) +{ + struct fwisound_softc *sc; + + sc = device_get_softc(dev); + sc->fd.dev = dev; + sc->fd.fc = device_get_ivars(dev); + sc->dev = dev; + mtx_init(&sc->mtx, "fwisound", NULL, MTX_DEF); + + sc->fwdev = NULL; + sc->state = FWISOUND_STATE_IDLE; + sc->dma_ch = -1; + sc->iso_active = 0; + sc->iso_channel = (uint8_t)(iso_channel & FWXFERQ_CHTAGMASK); + sc->chan = NULL; + TASK_INIT(&sc->probe_task, 0, fwisound_probe_task, sc); + TASK_INIT(&sc->start_task, 0, fwisound_start_task, sc); + TASK_INIT(&sc->stop_task, 0, fwisound_stop_task, sc); + + sc->fd.post_busreset = fwisound_post_busreset; + sc->fd.post_explore = fwisound_post_explore; + + sc->pcm_dev = device_add_child(dev, "pcm", DEVICE_UNIT_ANY); + if (sc->pcm_dev == NULL) { + device_printf(dev, "failed to add pcm child\n"); + mtx_destroy(&sc->mtx); + return (ENXIO); + } + + bus_attach_children(dev); + + fwisound_post_explore(sc); + + return (0); +} + +static int +fwisound_detach(device_t dev) +{ + struct fwisound_softc *sc; + + sc = device_get_softc(dev); + + FWISOUND_LOCK(sc); + sc->state = FWISOUND_STATE_DETACHING; + FWISOUND_UNLOCK(sc); + + taskqueue_drain(taskqueue_thread, &sc->probe_task); + taskqueue_drain(taskqueue_thread, &sc->start_task); + taskqueue_drain(taskqueue_thread, &sc->stop_task); + fwisound_iso_stop(sc); + + if (sc->pcm_dev != NULL) + device_delete_children(dev); + + FWISOUND_LOCK(sc); + sc->fwdev = NULL; + FWISOUND_UNLOCK(sc); + + mtx_destroy(&sc->mtx); + return (0); +} + +static device_method_t fwisound_methods[] = { + DEVMETHOD(device_identify, fwisound_identify), + DEVMETHOD(device_probe, fwisound_probe), + DEVMETHOD(device_attach, fwisound_attach), + DEVMETHOD(device_detach, fwisound_detach), + + DEVMETHOD_END +}; + +static driver_t fwisound_driver = { + "fwisound", + fwisound_methods, + sizeof(struct fwisound_softc), +}; + +DRIVER_MODULE(fwisound, firewire, fwisound_driver, 0, 0); +MODULE_VERSION(fwisound, 1); +MODULE_DEPEND(fwisound, firewire, 1, 1, 1); +MODULE_DEPEND(fwisound, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); diff --git a/sys/dev/firewire/fwisound.h b/sys/dev/firewire/fwisound.h new file mode 100644 index 000000000000..47a9790e074c --- /dev/null +++ b/sys/dev/firewire/fwisound.h @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2026 Abdelkader Boudih <[email protected]> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifndef _DEV_FIREWIRE_FWISOUND_H_ +#define _DEV_FIREWIRE_FWISOUND_H_ + +#include <dev/firewire/fwcam.h> + +/* Protocol reverse-engineered by Clemens Ladisch (Linux isight-firmware). */ +#define FWISOUND_SPEC_ID APPLE_FW_OUI +#define FWISOUND_VERSION 0x000010 /* SW_ISIGHT_AUDIO */ + +#define APPLE_FW_OUI 0x000a27 + +/* Audio register offsets from audio_base */ +#define FWISOUND_REG_AUDIO_ENABLE 0x000 /* bit31 = enable streaming */ +#define FWISOUND_REG_DEF_AUDIO_GAIN 0x204 +#define FWISOUND_REG_GAIN_RAW_START 0x210 +#define FWISOUND_REG_GAIN_RAW_END 0x214 +#define FWISOUND_REG_GAIN_DB_START 0x218 +#define FWISOUND_REG_GAIN_DB_END 0x21c +#define FWISOUND_REG_SAMPLE_RATE_INQ 0x280 +#define FWISOUND_REG_ISO_TX_CONFIG 0x300 /* bits[19:16]=speed, [3:0]=ch*/ +#define FWISOUND_REG_SAMPLE_RATE 0x400 /* bit31 = 1 → 48000 Hz */ +#define FWISOUND_REG_GAIN 0x500 +#define FWISOUND_REG_MUTE 0x504 + +/* REG_AUDIO_ENABLE / REG_SAMPLE_RATE bit */ +#define FWISOUND_AUDIO_ENABLE (1u << 31) +#define FWISOUND_RATE_48000 (1u << 31) + +/* Apple FireWire audio ISO payload. */ +struct fwisound_payload { + uint32_t sample_count; /* samples in this packet */ + uint32_t signature; /* 0x73676874 = "sght" */ + uint32_t sample_total; /* running total (drop detect)*/ + uint32_t reserved; + int16_t samples[2 * 475]; /* stereo S16BE; *2 valid */ +}; + +#define FWISOUND_SIGNATURE 0x73676874u /* "sght" */ + +/* ISO DMA parameters */ +#define FWISOUND_ISO_CHANNEL 1 +#define FWISOUND_ISO_NCHUNK 64 +#define FWISOUND_ISO_PKTSIZE 2048 /* MCLBYTES */ + +/* Driver states */ +#define FWISOUND_STATE_IDLE 0 +#define FWISOUND_STATE_PROBED 1 +#define FWISOUND_STATE_STREAMING 2 +#define FWISOUND_STATE_DETACHING 3 + +#ifdef _KERNEL + +#include <dev/sound/pcm/sound.h> + +struct fwisound_chan { + struct fwisound_softc *sc; + struct pcm_channel *pcm_chan; + struct snd_dbuf *buf; + struct pcmchan_caps caps; + uint32_t fmts[2]; + uint32_t hw_ptr; /* ring write offset (bytes) */ + int running; +}; + +struct fwisound_softc { + struct firewire_dev_comm fd; /* MUST BE FIRST */ + struct mtx mtx; + device_t dev; + device_t pcm_dev; + + struct fw_device *fwdev; + uint16_t cmd_hi; /* always 0xffff */ + uint32_t cmd_lo; /* 0xf0000000|(audio_base<<2) */ + + struct task probe_task; + struct task start_task; /* deferred iso_start */ + struct task stop_task; /* deferred iso_stop */ + + /* ISO streaming */ + int dma_ch; /* -1 = not open */ + int iso_active; /* iso_input running */ + uint8_t iso_channel; + + /* PCM channel back-reference */ + struct fwisound_chan *chan; + + /* Sample drop detection */ + uint32_t sample_total; + + /* Gain range (read from camera during probe) */ + int32_t gain_raw_min; + int32_t gain_raw_max; + + int state; + int dropped; +}; + +#define FWISOUND_LOCK(sc) mtx_lock(&(sc)->mtx) +#define FWISOUND_UNLOCK(sc) mtx_unlock(&(sc)->mtx) + +int fwisound_iso_start(struct fwisound_softc *); +void fwisound_iso_stop(struct fwisound_softc *); + +#endif /* _KERNEL */ + +#endif /* _DEV_FIREWIRE_FWISOUND_H_ */ diff --git a/sys/dev/firewire/fwisound_pcm.c b/sys/dev/firewire/fwisound_pcm.c new file mode 100644 index 000000000000..a7dd6f1c1a01 --- /dev/null +++ b/sys/dev/firewire/fwisound_pcm.c @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2026 Abdelkader Boudih <[email protected]> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* newpcm channel integration for fwisound(4). */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/malloc.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/taskqueue.h> + +#include <dev/firewire/firewire.h> +#include <dev/firewire/firewirereg.h> +#include <dev/firewire/fwisound.h> + +#include <dev/sound/pcm/sound.h> + +#define FWISOUND_BUFSZ 65536 + + +static void * +fwisound_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, + struct pcm_channel *c, int dir) +{ + struct fwisound_softc *sc = devinfo; + struct fwisound_chan *chan; + void *buf; + unsigned int bufsz; + + if (dir != PCMDIR_REC) + return (NULL); + + chan = malloc(sizeof(*chan), M_DEVBUF, M_WAITOK | M_ZERO); + chan->sc = sc; + chan->pcm_chan = c; + chan->buf = b; + + chan->fmts[0] = SND_FORMAT(AFMT_S16_BE, 2, 0); + chan->fmts[1] = 0; + + chan->caps.minspeed = 48000; + chan->caps.maxspeed = 48000; + chan->caps.fmtlist = chan->fmts; + chan->caps.caps = 0; + + bufsz = pcm_getbuffersize(sc->pcm_dev, 4096, FWISOUND_BUFSZ, 131072); + buf = malloc(bufsz, M_DEVBUF, M_WAITOK | M_ZERO); + if (sndbuf_setup(b, buf, bufsz) != 0) { + free(buf, M_DEVBUF); + free(chan, M_DEVBUF); + return (NULL); + } + + FWISOUND_LOCK(sc); + sc->chan = chan; + FWISOUND_UNLOCK(sc); + + return (chan); +} + +static int +fwisound_chan_free(kobj_t obj, void *data) +{ + struct fwisound_chan *chan = data; + struct fwisound_softc *sc = chan->sc; + void *buf; + + if (chan->running) { + taskqueue_enqueue(taskqueue_thread, &sc->stop_task); + taskqueue_drain(taskqueue_thread, &sc->stop_task); *** 187 LINES SKIPPED ***
