aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAbdelkader Boudih <freebsd@seuros.com>2026-07-12 15:25:52 +0000
committerAdrian Chadd <adrian@FreeBSD.org>2026-07-12 15:25:57 +0000
commit23a6997e3071cea8d8fc5238792fdd0ad5b5dc9a (patch)
tree269b5ab2305a0fe67fb744505ffb3c5ddb0e2d2a
parentd387a43ec8e2663b2d8bc0c8cb02d3a2ff14b6e6 (diff)
fwdv: add AV/C DV capture driver for FireWire camcorders
Add fwdv(4) driver for DV video capture from FireWire camcorders using AV/C protocol and isochronous streaming. Supports AV/C tape transport commands (play, stop, ff, rewind, pause, record, eject) with NTSC/PAL auto-detection and read(2) interface for frame capture. Reviewed by: adrian Differential Revision: https://reviews.freebsd.org/D58122
-rw-r--r--share/man/man4/Makefile1
-rw-r--r--share/man/man4/fwdv.480
-rw-r--r--sys/conf/NOTES1
-rw-r--r--sys/conf/files1
-rw-r--r--sys/dev/firewire/fwdv.c1101
-rw-r--r--sys/dev/firewire/fwdv.h195
-rw-r--r--sys/modules/firewire/Makefile1
-rw-r--r--sys/modules/firewire/fwdv/Makefile11
8 files changed, 1391 insertions, 0 deletions
diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile
index ab937d5cacee..f987dae1b62e 100644
--- a/share/man/man4/Makefile
+++ b/share/man/man4/Makefile
@@ -179,6 +179,7 @@ MAN= aac.4 \
filemon.4 \
firewire.4 \
fwcam.4 \
+ fwdv.4 \
fwisound.4 \
${_ftgpio.4} \
${_ftwd.4} \
diff --git a/share/man/man4/fwdv.4 b/share/man/man4/fwdv.4
new file mode 100644
index 000000000000..c21a95019c70
--- /dev/null
+++ b/share/man/man4/fwdv.4
@@ -0,0 +1,80 @@
+.\"
+.\" Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com>
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd June 19, 2026
+.Dt FWDV 4
+.Os
+.Sh NAME
+.Nm fwdv
+.Nd AV/C DV capture driver for FireWire camcorders
+.Sh SYNOPSIS
+.Cd "device firewire"
+.Cd "device fwdv"
+.Sh DESCRIPTION
+The
+.Nm
+driver provides DV frame capture from AV/C tape devices attached via
+IEEE 1394 (FireWire), including DV camcorders, VCRs, and DV converters.
+.Pp
+The driver attaches to the
+.Xr firewire 4
+bus and creates
+.Pa /dev/fwdvX
+at load time.
+When an AV/C device is connected, the driver automatically discovers it
+and begins receiving IEC 61883 isochronous packets on first open,
+assembling complete DV frames.
+Applications read raw DV frames from the character device
+and control tape transport through
+.Xr ioctl 2 .
+.Pp
+Both NTSC and PAL SD-DV streams are detected from the incoming CIP header.
+DVCPRO HD frame sizing is also recognized.
+.Sh IOCTLS
+.Bl -tag -width ".Dv FWDV_GINFO"
+.It Dv FWDV_GINFO Pq Vt "struct fwdv_info"
+Return driver state, active ISO channel, detected DV system, frame size,
+frame counters, dropped frame count, device EUI-64, and vendor/model strings
+from the device configuration ROM.
+.It Dv FWDV_PLAY
+Send the AV/C play command to tape subunit 0.
+.It Dv FWDV_STOP
+Send the AV/C stop command to tape subunit 0.
+.It Dv FWDV_FFWD
+Send the AV/C fast-forward command to tape subunit 0.
+.It Dv FWDV_REW
+Send the AV/C rewind command to tape subunit 0.
+.El
+.Sh SYSCTLS
+.Bl -tag -width ".Va hw.firewire.fwdv.iso_channel"
+.It Va hw.firewire.fwdv.iso_channel
+Fallback isochronous receive channel.
+The default is 63.
+If the device output plug control register reports an active channel, that
+device-reported channel is used instead.
+This value can be set as a tunable in
+.Xr loader.conf 5 .
+.El
+.Sh FILES
+.Bl -tag -width ".Pa /dev/fwdvX" -compact
+.It Pa /dev/fwdvX
+DV capture character device.
+Read to obtain raw DV frames.
+.El
+.Sh SEE ALSO
+.Xr firewire 4 ,
+.Xr fwohci 4
+.Pp
+AV/C Digital Interface Command Set General Specification 4.1,
+1394 Trade Association Document 2001012.
+.Pp
+IEC 61883-1 and IEC 61883-2.
+.Sh HISTORY
+The
+.Nm
+driver first appeared in
+.Fx 16.0 .
+.Sh AUTHORS
+.An Abdelkader Boudih Aq Mt freebsd@seuros.com
diff --git a/sys/conf/NOTES b/sys/conf/NOTES
index 7b253927e9a8..560b3554fe06 100644
--- a/sys/conf/NOTES
+++ b/sys/conf/NOTES
@@ -2615,6 +2615,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 fwdv # AV/C DV capture over FireWire
device fwisound # Apple FireWire audio
#####################################################################
diff --git a/sys/conf/files b/sys/conf/files
index 86dbd83e21d9..392a6dd3a10f 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -1733,6 +1733,7 @@ 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/fwdv.c optional fwdv
dev/firewire/fwisound.c optional fwisound sound
dev/firewire/fwisound_pcm.c optional fwisound sound
dev/firewire/fwmem.c optional firewire
diff --git a/sys/dev/firewire/fwdv.c b/sys/dev/firewire/fwdv.c
new file mode 100644
index 000000000000..b1bb6bb997cc
--- /dev/null
+++ b/sys/dev/firewire/fwdv.c
@@ -0,0 +1,1101 @@
+/*
+ * Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/*
+ * fwdv(4) - AV/C DV capture driver for FireWire camcorders
+ *
+ * References:
+ * AV/C General Specification 4.1 (TA Document 2001012)
+ * IEC 61883-1, IEC 61883-2
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/module.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/conf.h>
+#include <sys/malloc.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/sysctl.h>
+#include <sys/taskqueue.h>
+#include <sys/fcntl.h>
+#include <sys/poll.h>
+#include <sys/selinfo.h>
+#include <sys/uio.h>
+#include <sys/mbuf.h>
+
+#include <dev/firewire/firewire.h>
+#include <dev/firewire/firewirereg.h>
+#include <dev/firewire/iec13213.h>
+#include <dev/firewire/iec68113.h>
+#include <dev/firewire/fwdv.h>
+#include <dev/firewire/fw_helpers.h>
+
+static MALLOC_DEFINE(M_FWDV, "fwdv", "AV/C DV over FireWire");
+
+static int debug = 0;
+static int iso_channel = FWDV_DEFAULT_ISO_CH;
+SYSCTL_DECL(_hw_firewire);
+static SYSCTL_NODE(_hw_firewire, OID_AUTO, fwdv, CTLFLAG_RD | CTLFLAG_MPSAFE,
+ 0, "AV/C DV");
+SYSCTL_INT(_hw_firewire_fwdv, OID_AUTO, debug, CTLFLAG_RWTUN, &debug, 0,
+ "fwdv debug level");
+SYSCTL_INT(_hw_firewire_fwdv, OID_AUTO, iso_channel, CTLFLAG_RWTUN,
+ &iso_channel, 0, "ISO channel for DV receive");
+
+#define FWDV_DEBUG(lev, fmt, ...) \
+ do { \
+ if (debug >= (lev)) \
+ printf("fwdv: " fmt, ## __VA_ARGS__); \
+ } while (0)
+
+static void fwdv_identify(driver_t *, device_t);
+static int fwdv_probe(device_t);
+static int fwdv_attach(device_t);
+static int fwdv_detach(device_t);
+static void fwdv_post_busreset(void *);
+static void fwdv_post_explore(void *);
+static void fwdv_probe_task(void *, int);
+
+static int fwdv_avc_command(struct fwdv_softc *, const uint8_t *, int,
+ uint8_t *, int *);
+static int fwdv_avc_unit_info(struct fwdv_softc *);
+static int fwdv_avc_play(struct fwdv_softc *);
+static int fwdv_avc_stop(struct fwdv_softc *);
+
+static int fwdv_read_opcr(struct fwdv_softc *, int, uint32_t *);
+
+static int fwdv_iso_start(struct fwdv_softc *);
+static void fwdv_iso_stop(struct fwdv_softc *);
+static void fwdv_iso_input(struct fw_xferq *);
+
+static void fwdv_fcp_handler(struct fw_xfer *);
+
+static d_open_t fwdv_cdev_open;
+static d_close_t fwdv_cdev_close;
+static d_read_t fwdv_cdev_read;
+static d_poll_t fwdv_cdev_poll;
+static d_ioctl_t fwdv_cdev_ioctl;
+
+static struct cdevsw fwdv_cdevsw = {
+ .d_version = D_VERSION,
+ .d_flags = D_TRACKCLOSE,
+ .d_open = fwdv_cdev_open,
+ .d_close = fwdv_cdev_close,
+ .d_read = fwdv_cdev_read,
+ .d_poll = fwdv_cdev_poll,
+ .d_ioctl = fwdv_cdev_ioctl,
+ .d_name = "fwdv",
+};
+
+static int
+fwdv_read_quadlet(struct fwdv_softc *sc, uint16_t addr_hi, uint32_t addr_lo,
+ uint32_t *val)
+{
+ uint16_t dst;
+ uint8_t spd;
+
+ FWDV_LOCK(sc);
+ if (sc->fwdev == NULL) {
+ FWDV_UNLOCK(sc);
+ return (ENXIO);
+ }
+ dst = FWLOCALBUS | sc->fwdev->dst;
+ spd = min(sc->fwdev->speed, FWSPD_S400);
+ FWDV_UNLOCK(sc);
+
+ return (fw_read_quadlet(sc->fd.fc, M_FWDV, dst, spd,
+ addr_hi, addr_lo, val));
+}
+
+static void
+fwdv_fcp_handler(struct fw_xfer *xfer)
+{
+ struct fwdv_softc *sc = (struct fwdv_softc *)xfer->sc;
+ struct fw_pkt *fp;
+ uint16_t src_id;
+ int len;
+
+ fp = &xfer->recv.hdr;
+
+ if (xfer->resp != 0) {
+ FWDV_DEBUG(1, "FCP response error: %d\n", xfer->resp);
+ goto done;
+ }
+
+ len = fp->mode.wreqb.len;
+ if (len < 4 || len > FCP_MAX_FRAME_LEN) {
+ FWDV_DEBUG(1, "FCP response bad len: %d\n", len);
+ goto done;
+ }
+
+ if (xfer->recv.pay_len < 4) {
+ FWDV_DEBUG(1, "FCP response short payload: %d\n",
+ xfer->recv.pay_len);
+ goto done;
+ }
+ if (xfer->recv.pay_len < len)
+ len = xfer->recv.pay_len;
+
+ src_id = fp->mode.wreqb.src;
+ FWDV_LOCK(sc);
+ if (sc->fwdev == NULL ||
+ (FWLOCALBUS | sc->fwdev->dst) != src_id) {
+ FWDV_UNLOCK(sc);
+ FWDV_DEBUG(2, "FCP response from unknown node 0x%04x\n",
+ src_id);
+ goto done;
+ }
+
+ if (xfer->recv.payload != NULL) {
+ memcpy(sc->fcp_resp, xfer->recv.payload,
+ min(len, FCP_MAX_FRAME_LEN));
+ sc->fcp_resp_len = len;
+ sc->fcp_resp_ready = 1;
+ wakeup(&sc->fcp_resp_ready);
+ }
+ FWDV_UNLOCK(sc);
+
+done:
+ STAILQ_INSERT_TAIL(&sc->fcp_bind.xferlist, xfer, link);
+}
+
+static int
+fwdv_avc_command(struct fwdv_softc *sc, const uint8_t *cmd, int cmd_len,
+ uint8_t *resp, int *resp_len)
+{
+ struct fw_xfer *xfer;
+ struct fw_pkt *fp;
+ uint16_t dst;
+ uint8_t spd;
+ int err, padded_len;
+
+ FWDV_LOCK(sc);
+ while (sc->avc_busy) {
+ if (sc->state == FWDV_STATE_DETACHING) {
+ FWDV_UNLOCK(sc);
+ return (ENXIO);
+ }
+ msleep(&sc->avc_busy, &sc->mtx, PWAIT, "fwdvavc", hz);
+ }
+ sc->avc_busy = 1;
+
+ if (sc->fwdev == NULL) {
+ sc->avc_busy = 0;
+ wakeup(&sc->avc_busy);
+ FWDV_UNLOCK(sc);
+ return (ENXIO);
+ }
+ dst = FWLOCALBUS | sc->fwdev->dst;
+ spd = min(sc->fwdev->speed, FWSPD_S400);
+ FWDV_UNLOCK(sc);
+
+ padded_len = roundup2(cmd_len, 4);
+
+ xfer = fw_xfer_alloc_buf(M_FWDV, padded_len, 0);
+ if (xfer == NULL) {
+ err = ENOMEM;
+ goto done;
+ }
+
+ xfer->fc = sc->fd.fc;
+ xfer->hand = fw_xferwake;
+
+ fp = &xfer->send.hdr;
+ fp->mode.wreqb.tcode = FWTCODE_WREQB;
+ fp->mode.wreqb.dst = dst;
+ fp->mode.wreqb.dest_hi = FCP_COMMAND_ADDR >> 32;
+ fp->mode.wreqb.dest_lo = FCP_COMMAND_ADDR & 0xffffffff;
+ fp->mode.wreqb.len = padded_len;
+ fp->mode.wreqb.extcode = 0;
+ xfer->send.spd = spd;
+ xfer->send.pay_len = padded_len;
+
+ memcpy(xfer->send.payload, cmd, cmd_len);
+ if (padded_len > cmd_len)
+ memset((uint8_t *)xfer->send.payload + cmd_len, 0,
+ padded_len - cmd_len);
+
+ FWDV_LOCK(sc);
+ sc->fcp_resp_ready = 0;
+ sc->fcp_resp_len = 0;
+ FWDV_UNLOCK(sc);
+
+ FWDV_DEBUG(2, "AVC cmd: %02x %02x %02x %02x (len=%d)\n",
+ cmd[0], cmd[1], cmd_len > 2 ? cmd[2] : 0,
+ cmd_len > 3 ? cmd[3] : 0, cmd_len);
+
+ err = fw_xfer_request_wait(xfer->fc, xfer, 2 * hz);
+ if (err != 0) {
+ fw_xfer_free_buf(xfer);
+ goto done;
+ }
+
+ if (xfer->resp != 0 ||
+ xfer->recv.hdr.mode.wres.rtcode != FWRCODE_COMPLETE) {
+ FWDV_DEBUG(1, "FCP write failed: resp=%d rtcode=%d\n",
+ xfer->resp, xfer->recv.hdr.mode.wres.rtcode);
+ err = EIO;
+ }
+ fw_xfer_free_buf(xfer);
+ if (err != 0)
+ goto done;
+
+ FWDV_LOCK(sc);
+ for (int interim_retry = 0; interim_retry < 3; interim_retry++) {
+ while (!sc->fcp_resp_ready) {
+ if (sc->state == FWDV_STATE_DETACHING ||
+ sc->fwdev == NULL) {
+ err = ENXIO;
+ goto done_locked;
+ }
+ err = msleep(&sc->fcp_resp_ready, &sc->mtx, PCATCH,
+ "fwdvfcp", 3 * hz);
+ if (err) {
+ err = (err == EWOULDBLOCK) ? ETIMEDOUT : err;
+ goto done_locked;
+ }
+ }
+
+ if (sc->fcp_resp_len >= 1 &&
+ (sc->fcp_resp[0] >> 4) == AVC_RESP_INTERIM) {
+ FWDV_DEBUG(2, "AVC INTERIM, waiting for final\n");
+ sc->fcp_resp_ready = 0;
+ if (interim_retry == 2) {
+ FWDV_DEBUG(1, "AVC INTERIM timeout\n");
+ err = ETIMEDOUT;
+ goto done_locked;
+ }
+ continue;
+ }
+ break;
+ }
+
+ if (resp != NULL && resp_len != NULL) {
+ int copy_len = min(sc->fcp_resp_len, *resp_len);
+ memcpy(resp, sc->fcp_resp, copy_len);
+ *resp_len = copy_len;
+ }
+ FWDV_UNLOCK(sc);
+
+ FWDV_DEBUG(2, "AVC resp: %02x %02x %02x %02x (len=%d)\n",
+ sc->fcp_resp[0], sc->fcp_resp[1],
+ sc->fcp_resp_len > 2 ? sc->fcp_resp[2] : 0,
+ sc->fcp_resp_len > 3 ? sc->fcp_resp[3] : 0,
+ sc->fcp_resp_len);
+
+ FWDV_LOCK(sc);
+ sc->avc_busy = 0;
+ wakeup(&sc->avc_busy);
+ FWDV_UNLOCK(sc);
+
+ return (0);
+
+done_locked:
+ sc->avc_busy = 0;
+ wakeup(&sc->avc_busy);
+ FWDV_UNLOCK(sc);
+ return (err);
+
+done:
+ FWDV_LOCK(sc);
+ sc->avc_busy = 0;
+ wakeup(&sc->avc_busy);
+ FWDV_UNLOCK(sc);
+ return (err);
+}
+
+static int
+fwdv_avc_unit_info(struct fwdv_softc *sc)
+{
+ uint8_t cmd[8], resp[8];
+ int resp_len, err;
+
+ cmd[0] = (AVC_CTYPE_STATUS << 4) | 0;
+ cmd[1] = AVC_ADDR_UNIT;
+ cmd[2] = AVC_OP_UNIT_INFO;
+ cmd[3] = 0xff;
+ cmd[4] = 0xff;
+ cmd[5] = 0xff;
+ cmd[6] = 0xff;
+ cmd[7] = 0xff;
+
+ resp_len = sizeof(resp);
+ err = fwdv_avc_command(sc, cmd, 8, resp, &resp_len);
+ if (err)
+ return (err);
+
+ if (resp_len < 8) {
+ device_printf(sc->fd.dev,
+ "UNIT INFO: short response (%d bytes)\n", resp_len);
+ return (EIO);
+ }
+ if ((resp[0] >> 4) != AVC_RESP_STABLE) {
+ device_printf(sc->fd.dev,
+ "UNIT INFO: unexpected response 0x%02x\n", resp[0]);
+ return (EIO);
+ }
+
+ FWDV_DEBUG(1, "UNIT INFO: unit_type=0x%02x, company_id=%02x%02x%02x\n",
+ resp[4] >> 3, resp[5], resp[6], resp[7]);
+
+ return (0);
+}
+
+static int
+fwdv_avc_tape_control(struct fwdv_softc *sc, uint8_t opcode, uint8_t operand,
+ const char *name)
+{
+ uint8_t cmd[4], resp[4];
+ int resp_len, err;
+
+ cmd[0] = (AVC_CTYPE_CONTROL << 4) | 0;
+ cmd[1] = AVC_ADDR_TAPE0;
+ cmd[2] = opcode;
+ cmd[3] = operand;
+
+ resp_len = sizeof(resp);
+ err = fwdv_avc_command(sc, cmd, 4, resp, &resp_len);
+ if (err)
+ return (err);
+ if (resp_len < 1)
+ return (EIO);
+
+ if ((resp[0] >> 4) != AVC_RESP_ACCEPTED) {
+ device_printf(sc->fd.dev, "%s rejected: 0x%02x\n",
+ name, resp[0]);
+ return (EIO);
+ }
+
+ FWDV_DEBUG(1, "%s accepted\n", name);
+ return (0);
+}
+
+static int
+fwdv_avc_play(struct fwdv_softc *sc)
+{
+
+ return (fwdv_avc_tape_control(sc, AVC_OP_PLAY, AVC_PLAY_FWD, "PLAY"));
+}
+
+static int
+fwdv_avc_stop(struct fwdv_softc *sc)
+{
+
+ return (fwdv_avc_tape_control(sc, AVC_OP_WIND, AVC_WIND_STOP, "STOP"));
+}
+
+static int
+fwdv_read_opcr(struct fwdv_softc *sc, int plug, uint32_t *val)
+{
+
+ return (fwdv_read_quadlet(sc, CSR_BASE_HI,
+ CSR_BASE_LO | PCR_oPCR(plug), val));
+}
+
+static int
+fwdv_iso_start(struct fwdv_softc *sc)
+{
+ struct firewire_comm *fc = sc->fd.fc;
+ struct fw_xferq *xferq;
+ uint32_t opcr_val;
+ int dma_ch, err;
+ uint8_t ch;
+
+ mtx_assert(&sc->mtx, MA_NOTOWNED);
+
+ FWDV_LOCK(sc);
+ if (sc->dma_ch >= 0 || sc->state == FWDV_STATE_STREAMING) {
+ FWDV_UNLOCK(sc);
+ return (0);
+ }
+ if (sc->state == FWDV_STATE_DETACHING) {
+ FWDV_UNLOCK(sc);
+ return (ENXIO);
+ }
+ FWDV_UNLOCK(sc);
+
+ ch = (uint8_t)(iso_channel & ISO_CHANNEL_MASK);
+ err = fwdv_read_opcr(sc, 0, &opcr_val);
+ if (err == 0 && (opcr_val & OPCR_ONLINE)) {
+ uint8_t dev_ch = (opcr_val & OPCR_CHANNEL_MASK) >>
+ OPCR_CHANNEL_SHIFT;
+ if (dev_ch < ISO_CHANNEL_MAX) {
+ ch = dev_ch;
+ FWDV_DEBUG(1, "oPCR[0] channel=%d\n", ch);
+ }
+ }
+
+ dma_ch = fw_open_isodma(fc, 0);
+ if (dma_ch < 0) {
+ device_printf(sc->fd.dev, "no IR DMA channel available\n");
+ return (EBUSY);
+ }
+
+ FWDV_LOCK(sc);
+ if (sc->dma_ch >= 0) {
+ FWDV_UNLOCK(sc);
+ fc->ir[dma_ch]->flag &= ~FWXFERQ_OPEN;
+ return (0);
+ }
+ FWDV_UNLOCK(sc);
+
+ xferq = fc->ir[dma_ch];
+ xferq->flag |= FWXFERQ_EXTBUF | FWXFERQ_HANDLER | FWXFERQ_STREAM;
+ sc->iso_channel = ch;
+ xferq->flag &= ~FWXFERQ_CHTAGMASK;
+ xferq->flag |= (ch & ISO_CHANNEL_MASK) | (1 << 6);
+
+ xferq->sc = (caddr_t)sc;
+ xferq->hand = fwdv_iso_input;
+ xferq->bnchunk = FWDV_ISO_NCHUNK;
+ xferq->bnpacket = 1;
+ xferq->psize = FWDV_ISO_PKTSIZE;
+ xferq->queued = 0;
+ xferq->buf = NULL;
+
+ xferq->bulkxfer = malloc(sizeof(struct fw_bulkxfer) * xferq->bnchunk,
+ M_FWDV, M_WAITOK | M_ZERO);
+
+ fw_iso_init_chunks(xferq);
+
+ sc->frame_size = FWDV_MAX_FRAME_SIZE;
+ sc->frame_buf = malloc(sc->frame_size, M_FWDV, M_WAITOK | M_ZERO);
+ sc->read_buf = malloc(sc->frame_size, M_FWDV, M_WAITOK | M_ZERO);
+ sc->frame_offset = 0;
+ sc->frame_ready = 0;
+ sc->frame_dropped = 0;
+ sc->frame_count = 0;
+ sc->dv_detected = 0;
+
+ err = fc->irx_enable(fc, dma_ch);
+ if (err) {
+ device_printf(sc->fd.dev, "failed to start IR DMA: %d\n", err);
+ goto fail;
+ }
+
+ FWDV_LOCK(sc);
+ if (sc->state == FWDV_STATE_DETACHING || sc->fwdev == NULL) {
+ FWDV_UNLOCK(sc);
+ fc->irx_disable(fc, dma_ch);
+ err = ENXIO;
+ goto fail;
+ }
+ sc->dma_ch = dma_ch;
+ sc->state = FWDV_STATE_STREAMING;
+ wakeup(sc);
+ FWDV_UNLOCK(sc);
+
+ return (0);
+
+fail:
+ fw_iso_free_chunks(xferq, M_FWDV);
+ xferq->flag &= ~(FWXFERQ_MODEMASK | FWXFERQ_OPEN | FWXFERQ_STREAM |
+ FWXFERQ_EXTBUF | FWXFERQ_HANDLER | FWXFERQ_CHTAGMASK);
+ xferq->hand = NULL;
+
+ free(sc->frame_buf, M_FWDV);
+ free(sc->read_buf, M_FWDV);
+ sc->frame_buf = NULL;
+ sc->read_buf = NULL;
+ sc->dma_ch = -1;
+
+ return (err);
+}
+
+static void
+fwdv_iso_stop(struct fwdv_softc *sc)
+{
+ struct firewire_comm *fc = sc->fd.fc;
+ struct fw_xferq *xferq;
+ int dma_ch;
+
+ FWDV_LOCK(sc);
+ dma_ch = sc->dma_ch;
+ if (dma_ch < 0) {
+ FWDV_UNLOCK(sc);
+ return;
+ }
+ sc->dma_ch = -1;
+ FWDV_UNLOCK(sc);
+
+ xferq = fc->ir[dma_ch];
+
+ if (xferq->flag & FWXFERQ_RUNNING)
+ fc->irx_disable(fc, dma_ch);
+
+ FWDV_LOCK(sc);
+ fw_iso_wait_inactive_locked(&sc->mtx, &sc->iso_active, "fwdvis");
+ sc->frame_ready = 0;
+ while (sc->read_in_progress)
+ msleep(&sc->read_in_progress, &sc->mtx, PWAIT, "fwdvst", hz);
+ FWDV_UNLOCK(sc);
+
+ xferq->flag &= ~(FWXFERQ_MODEMASK | FWXFERQ_OPEN | FWXFERQ_STREAM |
+ FWXFERQ_EXTBUF | FWXFERQ_HANDLER | FWXFERQ_CHTAGMASK);
+ xferq->hand = NULL;
+
+ fw_iso_free_chunks(xferq, M_FWDV);
+
+ free(sc->frame_buf, M_FWDV);
+ free(sc->read_buf, M_FWDV);
+ sc->frame_buf = NULL;
+ sc->read_buf = NULL;
+}
+
+static void
+fwdv_iso_input(struct fw_xferq *xferq)
+{
+ struct fwdv_softc *sc = (struct fwdv_softc *)xferq->sc;
+ struct fw_bulkxfer *sxfer;
+ struct fw_pkt *fp;
+ struct ciphdr *ciph;
+ struct dvdbc *dv;
+ struct mbuf *m;
+ uint8_t *ptr, *end;
+ uint32_t plen;
+ uint8_t *tmp;
+ int dma_ch;
+
+ FWDV_LOCK(sc);
+ dma_ch = sc->dma_ch;
+ if (dma_ch < 0 || sc->frame_buf == NULL) {
+ FWDV_UNLOCK(sc);
+ return;
+ }
+ sc->iso_active = 1;
+ FWDV_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 <= sizeof(struct ciphdr)) {
+ m_freem(m);
+ continue;
+ }
+
+ if (m->m_len < (int)(4 + sizeof(struct ciphdr))) {
+ m_freem(m);
+ continue;
+ }
+
+ ciph = (struct ciphdr *)(mtod(m, uint8_t *) + 4);
+
+ if (ciph->fmt != CIP_FMT_DVCR) {
+ m_freem(m);
+ continue;
+ }
+
+ if (!sc->dv_detected) {
+ sc->dv_system = ciph->fdf.dv.fs;
+ sc->dv_detected = 1;
+ if (ciph->fdf.dv.stype == CIP_STYPE_HD) {
+ sc->frame_size = DV_DVCPRO_HD_FRAME;
+ } else {
+ sc->frame_size = sc->dv_system ?
+ DV_SD_PAL_FRAME : DV_SD_NTSC_FRAME;
+ }
+ printf("fwdv: DV stype=%d %s detected (frame=%u)\n",
+ ciph->fdf.dv.stype,
+ sc->dv_system ? "PAL" : "NTSC",
+ sc->frame_size);
+ }
+
+ ptr = (uint8_t *)(ciph + 1);
+ end = mtod(m, uint8_t *) + 4 + plen;
+ if (end > mtod(m, uint8_t *) + m->m_len)
+ end = mtod(m, uint8_t *) + m->m_len;
+
+ while (ptr + DV_DIF_BLOCK_SIZE <= end) {
+ dv = (struct dvdbc *)ptr;
+
+ if (dv->sct == DV_SCT_HEADER && dv->dseq == 0) {
+ if (sc->frame_offset > 0) {
+ if (sc->frame_offset >= sc->frame_size) {
+ FWDV_LOCK(sc);
+ if (sc->read_in_progress) {
+ sc->frame_dropped++;
+ } else {
+ if (sc->frame_ready)
+ sc->frame_dropped++;
+ tmp = sc->read_buf;
+ sc->read_buf =
+ sc->frame_buf;
+ sc->frame_buf = tmp;
+ sc->frame_ready = 1;
+ sc->frame_count++;
+ wakeup(sc);
+ selwakeup(&sc->rsel);
+ }
+ FWDV_UNLOCK(sc);
+ } else {
+ sc->frame_dropped++;
+ }
+ }
+ sc->frame_offset = 0;
+
+ if (sc->dv_system == 1 &&
+ (dv->payload[0] & DV_DSF_12) == 0)
+ dv->payload[0] |= DV_DSF_12;
+ }
+
+ if (sc->frame_offset + DV_DIF_BLOCK_SIZE <=
+ sc->frame_size) {
+ memcpy(sc->frame_buf + sc->frame_offset,
+ ptr, DV_DIF_BLOCK_SIZE);
+ sc->frame_offset += DV_DIF_BLOCK_SIZE;
+ } else {
+ sc->frame_dropped++;
+ sc->frame_offset = 0;
+ }
+
+ ptr += DV_DIF_BLOCK_SIZE;
+ }
+
+ m_freem(m);
+ }
+
+ fw_iso_rearm_done(xferq, sc->fd.fc, &sc->mtx, &sc->iso_active,
+ &sc->dma_ch, dma_ch);
+}
+
+static int
+fwdv_cdev_open(struct cdev *dev, int oflags, int devtype, struct thread *td)
+{
+ struct fwdv_softc *sc = dev->si_drv1;
+ int err = 0;
+
+ FWDV_LOCK(sc);
+ if (sc->state == FWDV_STATE_DETACHING) {
+ FWDV_UNLOCK(sc);
+ return (ENXIO);
+ }
+ while (sc->state == FWDV_STATE_STARTING) {
+ err = msleep(sc, &sc->mtx, PCATCH, "fwdvst", 5 * hz);
+ if (err) {
+ FWDV_UNLOCK(sc);
+ return (err == EWOULDBLOCK ? EAGAIN : err);
+ }
+ }
+ if (sc->state == FWDV_STATE_DETACHING) {
+ FWDV_UNLOCK(sc);
+ return (ENXIO);
+ }
+ if (sc->state != FWDV_STATE_PROBED &&
+ sc->state != FWDV_STATE_STREAMING) {
+ FWDV_UNLOCK(sc);
+ return (EAGAIN);
+ }
+
+ sc->open_count++;
+ if (sc->open_count == 1 && sc->state == FWDV_STATE_PROBED) {
+ sc->state = FWDV_STATE_STARTING;
+ FWDV_UNLOCK(sc);
+ err = fwdv_iso_start(sc);
+ if (err) {
+ FWDV_LOCK(sc);
+ sc->open_count--;
+ if (sc->state == FWDV_STATE_STARTING) {
+ sc->state = FWDV_STATE_PROBED;
+ wakeup(sc);
+ }
+ FWDV_UNLOCK(sc);
+ }
+ } else {
+ FWDV_UNLOCK(sc);
+ }
+ return (err);
+}
+
+static int
+fwdv_cdev_close(struct cdev *dev, int fflag, int devtype, struct thread *td)
+{
+ struct fwdv_softc *sc = dev->si_drv1;
+
+ FWDV_LOCK(sc);
+ sc->open_count--;
+ if (sc->open_count <= 0) {
+ sc->open_count = 0;
+ if (sc->state == FWDV_STATE_STREAMING) {
+ FWDV_UNLOCK(sc);
+ fwdv_iso_stop(sc);
+ FWDV_LOCK(sc);
+ if (sc->state != FWDV_STATE_DETACHING)
+ sc->state = FWDV_STATE_PROBED;
+ }
+ }
+ FWDV_UNLOCK(sc);
+ return (0);
+}
+
+static int
+fwdv_cdev_read(struct cdev *dev, struct uio *uio, int ioflag)
+{
+ struct fwdv_softc *sc = dev->si_drv1;
+ int err;
+
+ FWDV_LOCK(sc);
+ while (!sc->frame_ready) {
+ if (sc->state != FWDV_STATE_STREAMING) {
+ FWDV_UNLOCK(sc);
+ return (ENXIO);
+ }
+ if (ioflag & FNONBLOCK) {
+ FWDV_UNLOCK(sc);
+ return (EAGAIN);
+ }
+ err = msleep(sc, &sc->mtx, PCATCH, "fwdvrd", 0);
+ if (err) {
+ FWDV_UNLOCK(sc);
+ return (err);
+ }
+ }
+
+ sc->read_in_progress = 1;
+ sc->frame_ready = 0;
+ FWDV_UNLOCK(sc);
+
+ err = uiomove(sc->read_buf, min(uio->uio_resid, sc->frame_size),
+ uio);
+
+ FWDV_LOCK(sc);
+ sc->read_in_progress = 0;
+ wakeup(&sc->read_in_progress);
+ FWDV_UNLOCK(sc);
+
+ return (err);
+}
+
+static int
+fwdv_cdev_poll(struct cdev *dev, int events, struct thread *td)
+{
+ struct fwdv_softc *sc = dev->si_drv1;
+ int revents = 0;
+
+ FWDV_LOCK(sc);
+ if (sc->state != FWDV_STATE_STREAMING) {
+ revents = POLLHUP;
+ } else if (events & (POLLIN | POLLRDNORM)) {
+ if (sc->frame_ready)
+ revents |= events & (POLLIN | POLLRDNORM);
+ else
+ selrecord(td, &sc->rsel);
+ }
+ FWDV_UNLOCK(sc);
+
+ return (revents);
+}
+
+static int
+fwdv_cdev_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag,
+ struct thread *td)
+{
+ struct fwdv_softc *sc = dev->si_drv1;
+ struct fwdv_info *info;
+
+ switch (cmd) {
+ case FWDV_GINFO:
+ info = (struct fwdv_info *)data;
+ memset(info, 0, sizeof(*info));
+ FWDV_LOCK(sc);
+ info->state = sc->state;
+ info->iso_channel = sc->iso_channel;
+ info->dv_system = sc->dv_detected ? sc->dv_system : 0xff;
+ info->frame_size = sc->frame_size;
+ info->frame_count = sc->frame_count;
+ info->frame_dropped = sc->frame_dropped;
+ info->eui_hi = sc->eui_hi;
+ info->eui_lo = sc->eui_lo;
+ strlcpy(info->vendor, sc->vendor, sizeof(info->vendor));
+ strlcpy(info->model, sc->model, sizeof(info->model));
+ FWDV_UNLOCK(sc);
+ return (0);
+
+ case FWDV_PLAY:
+ return (fwdv_avc_play(sc));
+
+ case FWDV_STOP:
+ return (fwdv_avc_stop(sc));
+
+ case FWDV_FFWD:
+ return (fwdv_avc_tape_control(sc, AVC_OP_WIND,
+ AVC_WIND_FFWD, "FFWD"));
+
+ case FWDV_REW:
+ return (fwdv_avc_tape_control(sc, AVC_OP_WIND,
+ AVC_WIND_REW, "REW"));
+
+ default:
+ return (ENOTTY);
+ }
+}
+
+static void
+fwdv_parse_identity(struct fwdv_softc *sc)
+{
+ struct fw_device *fwdev = sc->fwdev;
+ struct crom_context cc;
+
+ sc->vendor[0] = '\0';
+ sc->model[0] = '\0';
+
+ if (fwdev == NULL)
+ return;
+
+ crom_init_context(&cc, fwdev->csrrom);
+
+ crom_search_key(&cc, CSRKEY_VENDOR);
+ crom_next(&cc);
+ crom_parse_text(&cc, sc->vendor, sizeof(sc->vendor));
+
+ crom_search_key(&cc, CSRKEY_MODEL);
+ crom_next(&cc);
+ crom_parse_text(&cc, sc->model, sizeof(sc->model));
+
+}
+
+static void
+fwdv_post_explore(void *arg)
+{
+ struct fwdv_softc *sc = (struct fwdv_softc *)arg;
+ struct fw_device *fwdev;
+ int was_streaming;
+
+ FWDV_LOCK(sc);
+
+ if (sc->state == FWDV_STATE_DETACHING) {
+ FWDV_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) {
+ was_streaming = (sc->state == FWDV_STATE_STREAMING);
+ device_printf(sc->fd.dev, "AV/C device disconnected%s\n",
+ was_streaming ? " (was streaming)" : "");
+ sc->fwdev = NULL;
+ sc->state = FWDV_STATE_IDLE;
+ wakeup(sc);
+ selwakeup(&sc->rsel);
+ FWDV_UNLOCK(sc);
+ if (was_streaming)
+ fwdv_iso_stop(sc);
+ FWDV_LOCK(sc);
+ }
+ }
+
+ if (sc->fwdev == NULL) {
+ STAILQ_FOREACH(fwdev, &sc->fd.fc->devices, link) {
+ if (fwdev->status != FWDEVATTACHED)
+ continue;
+
+ if (!crom_has_specver(fwdev->csrrom,
+ CSRVAL_1394TA, CSR_PROTAVC))
+ continue;
+
+ sc->fwdev = fwdev;
+ sc->eui_hi = fwdev->eui.hi;
+ sc->eui_lo = fwdev->eui.lo;
+
+ FWDV_UNLOCK(sc);
+
+ if (taskqueue_enqueue(taskqueue_thread,
+ &sc->probe_task) != 0) {
+ device_printf(sc->fd.dev,
+ "probe task enqueue failed\n");
+ FWDV_LOCK(sc);
+ sc->fwdev = NULL;
+ FWDV_UNLOCK(sc);
+ }
+ return;
+ }
+ }
+
+ FWDV_UNLOCK(sc);
+}
+
+static void
+fwdv_probe_task(void *arg, int pending __unused)
+{
+ struct fwdv_softc *sc = (struct fwdv_softc *)arg;
+ int err, restart;
+
+ FWDV_LOCK(sc);
+ if (sc->state == FWDV_STATE_DETACHING || sc->fwdev == NULL) {
+ FWDV_UNLOCK(sc);
+ return;
+ }
+ FWDV_UNLOCK(sc);
+
+ fwdv_parse_identity(sc);
+ err = fwdv_avc_unit_info(sc);
+ if (err) {
+ device_printf(sc->fd.dev,
+ "UNIT INFO failed (%d), device may not be ready\n", err);
+ }
+
+ FWDV_LOCK(sc);
+ if (sc->state == FWDV_STATE_DETACHING || sc->fwdev == NULL) {
+ FWDV_UNLOCK(sc);
+ return;
+ }
+ sc->state = FWDV_STATE_PROBED;
+ restart = (sc->open_count > 0);
+ FWDV_UNLOCK(sc);
+
+ if (restart)
+ fwdv_iso_start(sc);
+}
+
+static void
+fwdv_post_busreset(void *arg __unused)
+{
+
+}
+
+static void
+fwdv_identify(driver_t *driver, device_t parent)
+{
+
+ if (device_find_child(parent, "fwdv", DEVICE_UNIT_ANY) == NULL)
+ BUS_ADD_CHILD(parent, 0, "fwdv", DEVICE_UNIT_ANY);
+}
+
+static int
+fwdv_probe(device_t dev)
+{
+
+ device_set_desc(dev, "AV/C DV Capture over FireWire");
+ return (0);
+}
+
+static int
+fwdv_attach(device_t dev)
+{
+ struct fwdv_softc *sc;
+ struct firewire_comm *fc;
+ int err;
+
+ sc = device_get_softc(dev);
+ sc->fd.dev = dev;
+ sc->fd.fc = device_get_ivars(dev);
+ fc = sc->fd.fc;
+ mtx_init(&sc->mtx, "fwdv", NULL, MTX_DEF);
+
+ sc->fwdev = NULL;
+ sc->state = FWDV_STATE_IDLE;
+ sc->dma_ch = -1;
+ sc->open_count = 0;
+ sc->dv_system = 0;
+ sc->dv_detected = 0;
+ sc->avc_busy = 0;
+ sc->iso_active = 0;
+ knlist_init_mtx(&sc->rsel.si_note, &sc->mtx);
+ TASK_INIT(&sc->probe_task, 0, fwdv_probe_task, sc);
+
+ sc->cdev = make_dev(&fwdv_cdevsw, device_get_unit(dev),
+ UID_ROOT, GID_OPERATOR, 0660, "fwdv%d", device_get_unit(dev));
+ if (sc->cdev == NULL) {
+ device_printf(dev, "make_dev failed\n");
+ knlist_destroy(&sc->rsel.si_note);
+ mtx_destroy(&sc->mtx);
+ return (ENXIO);
+ }
+ sc->cdev->si_drv1 = sc;
+
+ STAILQ_INIT(&sc->fcp_bind.xferlist);
+ sc->fcp_bind.start = FCP_RESPONSE_ADDR;
+ sc->fcp_bind.end = FCP_RESPONSE_ADDR + FCP_MAX_FRAME_LEN - 1;
+ sc->fcp_bind.sc = sc;
+ if (fw_xferlist_add(&sc->fcp_bind.xferlist, M_FWDV,
+ 0, FCP_MAX_FRAME_LEN, FWDV_FCP_XFERS, fc, sc,
+ fwdv_fcp_handler) < FWDV_FCP_XFERS) {
+ device_printf(dev, "fw_xferlist_add: allocation failed\n");
+ fw_xferlist_remove(&sc->fcp_bind.xferlist);
+ destroy_dev(sc->cdev);
+ knlist_destroy(&sc->rsel.si_note);
+ mtx_destroy(&sc->mtx);
+ return (ENOMEM);
+ }
+
+ err = fw_bindadd(fc, &sc->fcp_bind);
+ if (err) {
+ device_printf(dev, "fw_bindadd failed: %d\n", err);
+ fw_xferlist_remove(&sc->fcp_bind.xferlist);
+ destroy_dev(sc->cdev);
+ knlist_destroy(&sc->rsel.si_note);
+ mtx_destroy(&sc->mtx);
+ return (err);
+ }
+
+ sc->fd.post_busreset = fwdv_post_busreset;
+ sc->fd.post_explore = fwdv_post_explore;
+
+ fwdv_post_explore(sc);
+
+ return (0);
+}
+
+static int
+fwdv_detach(device_t dev)
+{
+ struct fwdv_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ FWDV_LOCK(sc);
+ sc->state = FWDV_STATE_DETACHING;
+ wakeup(sc);
+ wakeup(&sc->fcp_resp_ready);
+ wakeup(&sc->avc_busy);
+ FWDV_UNLOCK(sc);
+
+ taskqueue_drain(taskqueue_thread, &sc->probe_task);
+ fwdv_iso_stop(sc);
+
+ fw_bindremove(sc->fd.fc, &sc->fcp_bind);
+ fw_xferlist_remove(&sc->fcp_bind.xferlist);
+
+ if (sc->cdev != NULL)
+ destroy_dev(sc->cdev);
+
+ seldrain(&sc->rsel);
+ knlist_destroy(&sc->rsel.si_note);
+ mtx_destroy(&sc->mtx);
+
+ return (0);
+}
+
+static device_method_t fwdv_methods[] = {
+ DEVMETHOD(device_identify, fwdv_identify),
+ DEVMETHOD(device_probe, fwdv_probe),
+ DEVMETHOD(device_attach, fwdv_attach),
+ DEVMETHOD(device_detach, fwdv_detach),
+ DEVMETHOD_END
+};
+
+static driver_t fwdv_driver = {
+ "fwdv",
+ fwdv_methods,
+ sizeof(struct fwdv_softc)
+};
+
+DRIVER_MODULE(fwdv, firewire, fwdv_driver, NULL, NULL);
+MODULE_VERSION(fwdv, 1);
+MODULE_DEPEND(fwdv, firewire, 1, 1, 1);
diff --git a/sys/dev/firewire/fwdv.h b/sys/dev/firewire/fwdv.h
new file mode 100644
index 000000000000..f4c2c3dfc561
--- /dev/null
+++ b/sys/dev/firewire/fwdv.h
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#ifndef _DEV_FIREWIRE_FWDV_H_
+#define _DEV_FIREWIRE_FWDV_H_
+
+/* FCP (Function Control Protocol) register addresses (IEC 61883-1 9.2) */
+#define FCP_COMMAND_ADDR 0xfffff0000b00ULL
+#define FCP_RESPONSE_ADDR 0xfffff0000d00ULL
+#define FCP_MAX_FRAME_LEN 512
+
+/* AV/C command/response type codes (ctype / response, 4-bit) */
+#define AVC_CTYPE_CONTROL 0x0
+#define AVC_CTYPE_STATUS 0x1
+#define AVC_CTYPE_NOTIFY 0x3
+
+#define AVC_RESP_NOT_IMPL 0x8
+#define AVC_RESP_ACCEPTED 0x9
+#define AVC_RESP_REJECTED 0xa
+#define AVC_RESP_IN_TRANSITION 0xb
+#define AVC_RESP_STABLE 0xc
+#define AVC_RESP_INTERIM 0xf
+
+/* AV/C subunit types (5-bit) */
+#define AVC_SUBUNIT_TAPE 0x04
+#define AVC_SUBUNIT_UNIT 0x1f
+
+/* AV/C subunit ID (3-bit) */
+#define AVC_SUBUNIT_ID_0 0x00
+#define AVC_SUBUNIT_ID_IGNORE 0x07
+
+/* AV/C address byte: subunit_type(5) | subunit_id(3) */
+#define AVC_ADDR(type, id) (((type) << 3) | ((id) & 0x07))
+#define AVC_ADDR_UNIT AVC_ADDR(AVC_SUBUNIT_UNIT, AVC_SUBUNIT_ID_IGNORE)
+#define AVC_ADDR_TAPE0 AVC_ADDR(AVC_SUBUNIT_TAPE, AVC_SUBUNIT_ID_0)
+
+/* AV/C unit commands (opcode) */
+#define AVC_OP_UNIT_INFO 0x30
+#define AVC_OP_SUBUNIT_INFO 0x31
+#define AVC_OP_VENDOR_DEP 0x00
+#define AVC_OP_POWER 0xb2
+
+/* AV/C tape subunit opcodes */
+#define AVC_OP_PLAY 0xc3
+#define AVC_OP_RECORD 0xc2
+#define AVC_OP_WIND 0xc4
+#define AVC_OP_TRANSPORT_STATE 0xd0
+
+/* PLAY operand[0] values */
+#define AVC_PLAY_FWD 0x75
+#define AVC_PLAY_STOP 0x60
+
+/* WIND operand[0] values */
+#define AVC_WIND_FFWD 0x75
+#define AVC_WIND_REW 0x65
+#define AVC_WIND_STOP 0x60
+
+/* TRANSPORT_STATE operand[0] values */
+#define AVC_XPORT_PLAY 0x01
+#define AVC_XPORT_RECORD 0x02
+#define AVC_XPORT_WIND_FF 0x03
+#define AVC_XPORT_WIND_REW 0x04
+#define AVC_XPORT_NO_INFO 0x7f
+
+/* POWER operand[0] */
+#define AVC_POWER_ON 0x70
+#define AVC_POWER_OFF 0x60
+
+/* IEEE 1394 CSR register space */
+#define CSR_BASE_HI 0xffff
+#define CSR_BASE_LO 0xf0000000
+
+/* ISO channel limits */
+#define ISO_CHANNEL_MASK 0x3f
+#define ISO_CHANNEL_MAX 64
+
+/* IEC 61883 plug control registers (CSR offsets) */
+#define PCR_oMPR 0x900
+#define PCR_oPCR(n) (0x904 + (n) * 4)
+#define PCR_iMPR 0x980
+#define PCR_iPCR(n) (0x984 + (n) * 4)
+
+/*
+ * oPCR register fields (IEC 61883-1 7.5)
+ *
+ * 31 30 29:24 23:16 15:14 13:10 9:0
+ * online bcast n_p2p_conn channel speed overhead_id payload
+ */
+#define OPCR_ONLINE (1U << 31)
+#define OPCR_BCAST (1U << 30)
+#define OPCR_N_P2P_MASK 0x3f000000
+#define OPCR_N_P2P_SHIFT 24
+#define OPCR_CHANNEL_MASK 0x003f0000
+#define OPCR_CHANNEL_SHIFT 16
+#define OPCR_SPEED_MASK 0x0000c000
+#define OPCR_SPEED_SHIFT 14
+#define OPCR_OVERHEAD_MASK 0x00003c00
+#define OPCR_OVERHEAD_SHIFT 10
+#define OPCR_PAYLOAD_MASK 0x000003ff
+
+/* DV frame sizes */
+#define DV_SD_NTSC_FRAME (10 * 150 * 80) /* 120,000 bytes */
+#define DV_SD_PAL_FRAME (12 * 150 * 80) /* 144,000 bytes */
+#define DV_DVCPRO_HD_FRAME (10 * 250 * 80) /* 200,000 bytes */
+
+/*
+ * ioctl interface
+ */
+struct fwdv_info {
+ uint8_t state;
+ uint8_t iso_channel;
+ uint8_t dv_system; /* 0=NTSC, 1=PAL, 0xff=unknown */
+ uint8_t _pad;
+ uint32_t frame_size;
+ uint32_t frame_count;
+ uint32_t frame_dropped;
+ uint32_t eui_hi; /* device EUI-64 high */
+ uint32_t eui_lo; /* device EUI-64 low */
+ char vendor[32]; /* from config ROM */
+ char model[32]; /* from config ROM */
+};
+
+#define FWDV_GINFO _IOR('D', 1, struct fwdv_info)
+#define FWDV_PLAY _IO('D', 2)
+#define FWDV_STOP _IO('D', 3)
+#define FWDV_FFWD _IO('D', 4)
+#define FWDV_REW _IO('D', 5)
+
+/* fwdv state values */
+#define FWDV_STATE_IDLE 0 /* no AV/C device found */
+#define FWDV_STATE_PROBED 1 /* device found, not streaming */
+#define FWDV_STATE_STARTING 2 /* iso_start in progress */
+#define FWDV_STATE_STREAMING 3 /* iso receive active */
+#define FWDV_STATE_DETACHING 4
+
+#define DV_DIF_BLOCK_SIZE 80
+#define FWDV_MAX_FRAME_SIZE DV_DVCPRO_HD_FRAME
+#define FWDV_ISO_NCHUNK 256
+#define FWDV_ISO_PKTSIZE 2048
+#define FWDV_DEFAULT_ISO_CH 63
+#define FWDV_FCP_XFERS 4
+
+#ifdef _KERNEL
+
+struct fwdv_softc {
+ struct firewire_dev_comm fd;
+ struct mtx mtx;
+ struct cdev *cdev;
+
+ struct fw_device *fwdev;
+ uint32_t eui_hi;
+ uint32_t eui_lo;
+
+ struct fw_bind fcp_bind;
+ uint8_t fcp_resp[FCP_MAX_FRAME_LEN];
+ int fcp_resp_len;
+ int fcp_resp_ready;
+ int avc_busy;
+
+ int iso_active;
+
+ char vendor[32];
+ char model[32];
+
+ struct task probe_task;
+
+ int dma_ch;
+ uint8_t iso_channel;
+
+ uint8_t *frame_buf;
+ uint8_t *read_buf;
+ uint32_t frame_size;
+ uint32_t frame_offset;
+ int frame_ready;
+ int read_in_progress;
+ uint32_t frame_count;
+ uint32_t frame_dropped;
+ int open_count;
+ struct selinfo rsel;
+
+ uint8_t dv_system;
+ int dv_detected;
+
+ int state;
+};
+
+#define FWDV_LOCK(sc) mtx_lock(&(sc)->mtx)
+#define FWDV_UNLOCK(sc) mtx_unlock(&(sc)->mtx)
+
+#endif /* _KERNEL */
+
+#endif /* _DEV_FIREWIRE_FWDV_H_ */
diff --git a/sys/modules/firewire/Makefile b/sys/modules/firewire/Makefile
index 70768b3bb77e..bd8c187f0626 100644
--- a/sys/modules/firewire/Makefile
+++ b/sys/modules/firewire/Makefile
@@ -1,5 +1,6 @@
SUBDIR= firewire \
fwcam \
+ fwdv \
fwe \
fwip \
fwisound \
diff --git a/sys/modules/firewire/fwdv/Makefile b/sys/modules/firewire/fwdv/Makefile
new file mode 100644
index 000000000000..943f47d84e31
--- /dev/null
+++ b/sys/modules/firewire/fwdv/Makefile
@@ -0,0 +1,11 @@
+# Makefile for the AV/C DV capture driver
+
+.PATH: ${SRCTOP}/sys/dev/firewire
+
+KMOD = fwdv
+SRCS = bus_if.h device_if.h \
+ fwdv.c fwdv.h fw_helpers.h \
+ firewire.h firewirereg.h \
+ iec13213.h iec68113.h
+
+.include <bsd.kmod.mk>