Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F162329897
D58109.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
27 KB
Referenced Files
None
Subscribers
None
D58109.diff
View Options
diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile
--- a/share/man/man4/Makefile
+++ b/share/man/man4/Makefile
@@ -179,6 +179,7 @@
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
--- /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
--- a/sys/conf/NOTES
+++ b/sys/conf/NOTES
@@ -2614,6 +2614,7 @@
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
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -1733,6 +1733,8 @@
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.h b/sys/dev/firewire/fwisound.h
new file mode 100644
--- /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.c b/sys/dev/firewire/fwisound.c
new file mode 100644
--- /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_pcm.c b/sys/dev/firewire/fwisound_pcm.c
new file mode 100644
--- /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);
+ }
+
+ FWISOUND_LOCK(sc);
+ sc->chan = NULL;
+ FWISOUND_UNLOCK(sc);
+
+ buf = sndbuf_getbufofs(chan->buf, 0);
+ if (buf != NULL)
+ free(buf, M_DEVBUF);
+
+ free(chan, M_DEVBUF);
+ return (0);
+}
+
+static int
+fwisound_chan_setformat(kobj_t obj, void *data, uint32_t fmt)
+{
+
+ if (fmt != SND_FORMAT(AFMT_S16_BE, 2, 0))
+ return (EINVAL);
+ return (0);
+}
+
+static uint32_t
+fwisound_chan_setspeed(kobj_t obj, void *data, uint32_t speed)
+{
+
+ return (48000);
+}
+
+static uint32_t
+fwisound_chan_setblocksize(kobj_t obj, void *data, uint32_t blksz)
+{
+ struct fwisound_chan *chan = data;
+
+ blksz = rounddown(blksz, 4);
+ if (blksz < 4)
+ blksz = 4;
+
+ sndbuf_resize(chan->buf, chan->buf->bufsize / blksz, blksz);
+
+ return (chan->buf->blksz);
+}
+
+static int
+fwisound_chan_trigger(kobj_t obj, void *data, int go)
+{
+ struct fwisound_chan *chan = data;
+ struct fwisound_softc *sc = chan->sc;
+
+ switch (go) {
+ case PCMTRIG_START:
+ chan->hw_ptr = 0;
+ sc->dropped = 0;
+ sc->sample_total = 0;
+ chan->running = 1;
+ taskqueue_enqueue(taskqueue_thread, &sc->start_task);
+ return (0);
+
+ case PCMTRIG_STOP:
+ case PCMTRIG_ABORT:
+ chan->running = 0;
+ taskqueue_drain(taskqueue_thread, &sc->start_task);
+ taskqueue_enqueue(taskqueue_thread, &sc->stop_task);
+ return (0);
+
+ default:
+ return (0);
+ }
+}
+
+static uint32_t
+fwisound_chan_getptr(kobj_t obj, void *data)
+{
+ struct fwisound_chan *chan = data;
+
+ return (chan->hw_ptr % chan->buf->bufsize);
+}
+
+static struct pcmchan_caps *
+fwisound_chan_getcaps(kobj_t obj, void *data)
+{
+ struct fwisound_chan *chan = data;
+
+ return (&chan->caps);
+}
+
+static kobj_method_t fwisound_chan_methods[] = {
+ KOBJMETHOD(channel_init, fwisound_chan_init),
+ KOBJMETHOD(channel_free, fwisound_chan_free),
+ KOBJMETHOD(channel_setformat, fwisound_chan_setformat),
+ KOBJMETHOD(channel_setspeed, fwisound_chan_setspeed),
+ KOBJMETHOD(channel_setblocksize, fwisound_chan_setblocksize),
+ KOBJMETHOD(channel_trigger, fwisound_chan_trigger),
+ KOBJMETHOD(channel_getptr, fwisound_chan_getptr),
+ KOBJMETHOD(channel_getcaps, fwisound_chan_getcaps),
+ KOBJMETHOD_END
+};
+
+CHANNEL_DECLARE(fwisound_chan);
+
+static int
+fwisound_pcm_probe(device_t dev)
+{
+
+ device_set_desc(dev, "Apple FireWire audio");
+ return (0);
+}
+
+static int
+fwisound_pcm_attach(device_t dev)
+{
+ struct fwisound_softc *sc;
+ char status[SND_STATUSLEN];
+ int err;
+
+ sc = device_get_softc(device_get_parent(dev));
+ sc->pcm_dev = dev;
+
+ pcm_init(dev, sc);
+ pcm_setflags(dev, pcm_getflags(dev) | SD_F_MPSAFE);
+
+ err = pcm_addchan(dev, PCMDIR_REC, &fwisound_chan_class, sc);
+ if (err != 0) {
+ device_printf(dev, "pcm_addchan failed: %d\n", err);
+ return (err);
+ }
+
+ snprintf(status, sizeof(status), "Apple FireWire audio at %s",
+ device_get_nameunit(device_get_parent(dev)));
+ return (pcm_register(dev, status));
+}
+
+static int
+fwisound_pcm_detach(device_t dev)
+{
+
+ return (pcm_unregister(dev));
+}
+
+static device_method_t fwisound_pcm_methods[] = {
+ DEVMETHOD(device_probe, fwisound_pcm_probe),
+ DEVMETHOD(device_attach, fwisound_pcm_attach),
+ DEVMETHOD(device_detach, fwisound_pcm_detach),
+
+ DEVMETHOD_END
+};
+
+static driver_t fwisound_pcm_driver = {
+ "pcm",
+ fwisound_pcm_methods,
+ PCM_SOFTC_SIZE,
+};
+
+DRIVER_MODULE(fwisound_pcm, fwisound, fwisound_pcm_driver, 0, 0);
+MODULE_DEPEND(fwisound_pcm, fwisound, 1, 1, 1);
+MODULE_DEPEND(fwisound_pcm, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER);
diff --git a/sys/modules/firewire/Makefile b/sys/modules/firewire/Makefile
--- a/sys/modules/firewire/Makefile
+++ b/sys/modules/firewire/Makefile
@@ -2,6 +2,7 @@
fwcam \
fwe \
fwip \
+ fwisound \
sbp \
sbp_targ
diff --git a/sys/modules/firewire/fwisound/Makefile b/sys/modules/firewire/fwisound/Makefile
new file mode 100644
--- /dev/null
+++ b/sys/modules/firewire/fwisound/Makefile
@@ -0,0 +1,12 @@
+# Makefile for the Apple FireWire audio driver
+
+.PATH: ${SRCTOP}/sys/dev/firewire
+
+KMOD = fwisound
+SRCS = bus_if.h device_if.h \
+ channel_if.h mixer_if.h feeder_if.h \
+ fwisound.c fwisound_pcm.c fwisound.h fw_helpers.h \
+ firewire.h firewirereg.h \
+ iec13213.h
+
+.include <bsd.kmod.mk>
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, Jul 13, 3:04 AM (19 h, 19 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
35001140
Default Alt Text
D58109.diff (27 KB)
Attached To
Mode
D58109: fwisound: add Apple FireWire audio driver
Attached
Detach File
Event Timeline
Log In to Comment