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 ***

Reply via email to