diff options
| author | Abdelkader Boudih <freebsd@seuros.com> | 2026-07-12 15:25:52 +0000 |
|---|---|---|
| committer | Adrian Chadd <adrian@FreeBSD.org> | 2026-07-12 15:25:57 +0000 |
| commit | 23a6997e3071cea8d8fc5238792fdd0ad5b5dc9a (patch) | |
| tree | 269b5ab2305a0fe67fb744505ffb3c5ddb0e2d2a | |
| parent | d387a43ec8e2663b2d8bc0c8cb02d3a2ff14b6e6 (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/Makefile | 1 | ||||
| -rw-r--r-- | share/man/man4/fwdv.4 | 80 | ||||
| -rw-r--r-- | sys/conf/NOTES | 1 | ||||
| -rw-r--r-- | sys/conf/files | 1 | ||||
| -rw-r--r-- | sys/dev/firewire/fwdv.c | 1101 | ||||
| -rw-r--r-- | sys/dev/firewire/fwdv.h | 195 | ||||
| -rw-r--r-- | sys/modules/firewire/Makefile | 1 | ||||
| -rw-r--r-- | sys/modules/firewire/fwdv/Makefile | 11 |
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> |
