git: cd7b65080f2f - main - fwisound: add Apple FireWire audio driver
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Thu, 09 Jul 2026 02:54:13 UTC
The branch main has been updated by adrian:
URL: https://cgit.FreeBSD.org/src/commit/?id=cd7b65080f2f2e038ddba42048f24f8e98b011f2
commit cd7b65080f2f2e038ddba42048f24f8e98b011f2
Author: Abdelkader Boudih <freebsd@seuros.com>
AuthorDate: 2026-07-09 02:52:43 +0000
Commit: Adrian Chadd <adrian@FreeBSD.org>
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 <freebsd@seuros.com>
+.\"
+.\" 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 freebsd@seuros.com
+.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 <freebsd@seuros.com>
+ *
+ * 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 <freebsd@seuros.com>
+ *
+ * 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 <freebsd@seuros.com>
+ *
+ * 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 ***