git: cd7b65080f2f - main - fwisound: add Apple FireWire audio driver

From: Adrian Chadd <adrian_at_FreeBSD.org>
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 ***