The branch main has been updated by adrian:

URL: 
https://cgit.FreeBSD.org/src/commit/?id=23a6997e3071cea8d8fc5238792fdd0ad5b5dc9a

commit 23a6997e3071cea8d8fc5238792fdd0ad5b5dc9a
Author:     Abdelkader Boudih <[email protected]>
AuthorDate: 2026-07-12 15:25:52 +0000
Commit:     Adrian Chadd <[email protected]>
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 <[email protected]>
+.\"
+.\" 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 [email protected]
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 <[email protected]>
+ *
+ * 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 ***

Reply via email to