git: 23a6997e3071 - main - fwdv: add AV/C DV capture driver for FireWire camcorders
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Sun, 12 Jul 2026 15:35:35 UTC
The branch main has been updated by adrian:
URL: https://cgit.FreeBSD.org/src/commit/?id=23a6997e3071cea8d8fc5238792fdd0ad5b5dc9a
commit 23a6997e3071cea8d8fc5238792fdd0ad5b5dc9a
Author: Abdelkader Boudih <freebsd@seuros.com>
AuthorDate: 2026-07-12 15:25:52 +0000
Commit: Adrian Chadd <adrian@FreeBSD.org>
CommitDate: 2026-07-12 15:25:57 +0000
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
---
share/man/man4/Makefile | 1 +
share/man/man4/fwdv.4 | 80 +++
sys/conf/NOTES | 1 +
sys/conf/files | 1 +
sys/dev/firewire/fwdv.c | 1101 ++++++++++++++++++++++++++++++++++++
sys/dev/firewire/fwdv.h | 195 +++++++
sys/modules/firewire/Makefile | 1 +
sys/modules/firewire/fwdv/Makefile | 11 +
8 files changed, 1391 insertions(+)
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));
*** 504 LINES SKIPPED ***