Re: git: 40e054797184 - main - fwcam: add IIDC 1394 FireWire camera driver

From: Adrian Chadd <adrian_at_freebsd.org>
Date: Thu, 25 Jun 2026 03:40:23 UTC
thanks btw to ziaee@ for working through the man page stuff with
seuros, that was super helpful!


-a

On Wed, 24 Jun 2026 at 19:22, Adrian Chadd <adrian@freebsd.org> wrote:
>
> The branch main has been updated by adrian:
>
> URL: https://cgit.FreeBSD.org/src/commit/?id=40e05479718460820cd24c22e59519a4f13eba3d
>
> commit 40e05479718460820cd24c22e59519a4f13eba3d
> Author:     Abdelkader Boudih <freebsd@seuros.com>
> AuthorDate: 2026-06-25 02:14:22 +0000
> Commit:     Adrian Chadd <adrian@FreeBSD.org>
> CommitDate: 2026-06-25 02:20:55 +0000
>
>     fwcam: add IIDC 1394 FireWire camera driver
>
>     Add fwcam(4), a driver for IIDC v1.30 (TA Document 1999023) digital
>     cameras over IEEE 1394.
>
>     Supports Format_0 (VGA) video modes with isochronous receive DMA,
>     feature control (brightness, exposure, gain, shutter, white balance,
>     focus, etc.), poll/kqueue, and hot-plug via bus reset handling.
>
>     Reviewed by:    adrian
>     Differential Revision:  https://reviews.freebsd.org/D57685
> ---
>  share/man/man4/Makefile             |    1 +
>  share/man/man4/fwcam.4              |  116 ++++
>  sys/conf/NOTES                      |    1 +
>  sys/conf/files                      |    1 +
>  sys/dev/firewire/fwcam.c            | 1051 +++++++++++++++++++++++++++++++++++
>  sys/dev/firewire/fwcam.h            |  263 +++++++++
>  sys/modules/firewire/Makefile       |    1 +
>  sys/modules/firewire/fwcam/Makefile |   11 +
>  8 files changed, 1445 insertions(+)
>
> diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile
> index ca42627265d2..b029d8a64933 100644
> --- a/share/man/man4/Makefile
> +++ b/share/man/man4/Makefile
> @@ -177,6 +177,7 @@ MAN=        aac.4 \
>         ffs.4 \
>         filemon.4 \
>         firewire.4 \
> +       fwcam.4 \
>         ${_ftgpio.4} \
>         ${_ftwd.4} \
>         full.4 \
> diff --git a/share/man/man4/fwcam.4 b/share/man/man4/fwcam.4
> new file mode 100644
> index 000000000000..c375d1eab9b1
> --- /dev/null
> +++ b/share/man/man4/fwcam.4
> @@ -0,0 +1,116 @@
> +.\"
> +.\" Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com>
> +.\"
> +.\" SPDX-License-Identifier: BSD-2-Clause
> +.\"
> +.Dd June 19, 2026
> +.Dt FWCAM 4
> +.Os
> +.Sh NAME
> +.Nm fwcam
> +.Nd IIDC FireWire digital camera driver
> +.Sh SYNOPSIS
> +.Cd "device firewire"
> +.Cd "device fwcam"
> +.Sh DESCRIPTION
> +The
> +.Nm
> +driver provides support for IIDC (Instrumentation and Industrial Digital
> +Camera) v1.30 cameras attached via IEEE 1394 (FireWire).
> +.Pp
> +Video frames are received over an isochronous DMA channel and exposed
> +through a character device.
> +Applications read raw frames from
> +.Pa /dev/fwcamX
> +and configure the camera via
> +.Xr ioctl 2 .
> +.Pp
> +The driver supports Format_0 (VGA non-compressed) video with the
> +following modes:
> +.Bl -tag -width "Mode 3" -compact
> +.It Mode 0
> +160x120 YUV444 (57,600 bytes per frame)
> +.It Mode 1
> +320x240 YUV422 (153,600 bytes per frame)
> +.It Mode 2
> +640x480 YUV411 (460,800 bytes per frame)
> +.It Mode 3
> +640x480 YUV422 (614,400 bytes per frame)
> +.It Mode 4
> +640x480 RGB8 (921,600 bytes per frame)
> +.It Mode 5
> +640x480 Mono8 (307,200 bytes per frame)
> +.El
> +.Pp
> +Camera features
> +.Pq brightness, gain, shutter, focus, etc.\&
> +are queried and set via
> +.Dv FWCAM_GFEAT
> +and
> +.Dv FWCAM_SFEAT
> +ioctls.
> +.Sh HARDWARE
> +The
> +.Nm
> +driver supports any camera compliant with the IIDC 1394-based Digital Camera
> +Specification v1.30.
> +Tested hardware:
> +.Bl -bullet -compact
> +.It
> +Apple iSight (FireWire cylinder),
> +EUI-64 000a270004135e5d, Format_0 modes 1\(en3
> +.El
> +.Sh IOCTLS
> +.Bl -tag -width "FWCAM_GINFO"
> +.It Dv FWCAM_GMODE Pq Vt "struct fwcam_mode"
> +Get the current video format, mode, and frame rate.
> +The
> +.Va frame_size
> +field is filled with the computed frame size in bytes.
> +.It Dv FWCAM_SMODE Pq Vt "struct fwcam_mode"
> +Set the video format, mode, and frame rate.
> +Streaming is restarted if active.
> +.It Dv FWCAM_GFEAT Pq Vt "struct fwcam_feature"
> +Get feature capabilities and current value.
> +The caller sets
> +.Va id
> +to one of the
> +.Dv FWCAM_FEAT_*
> +constants; the driver fills
> +.Va flags ,
> +.Va min ,
> +.Va max ,
> +and
> +.Va value .
> +.It Dv FWCAM_SFEAT Pq Vt "struct fwcam_feature"
> +Set a feature value.
> +.It Dv FWCAM_GINFO Pq Vt "struct fwcam_info"
> +Return camera capabilities, current settings, and stream statistics
> +including dropped frame count and active ISO channel.
> +.El
> +.Sh SYSCTLS
> +.Bl -tag -width "hw.firewire.fwcam.iso_channel"
> +.It Va hw.firewire.fwcam.iso_channel
> +Isochronous receive channel (default 0).
> +Can be set as a tunable in
> +.Xr loader.conf 5 .
> +.El
> +.Sh FILES
> +.Bl -tag -width "/dev/fwcamX" -compact
> +.It Pa /dev/fwcamX
> +Camera character device.
> +Read to obtain raw video frames.
> +.El
> +.Sh SEE ALSO
> +.Xr firewire 4 ,
> +.Xr fwohci 4
> +.Pp
> +IIDC 1394-based Digital Camera Specification v1.30,
> +1394 Trade Association Document 1999023.
> +.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 d982164ae3a1..41f2514788df 100644
> --- a/sys/conf/NOTES
> +++ b/sys/conf/NOTES
> @@ -2613,6 +2613,7 @@ device            sbp             # SCSI over Firewire (Requires scbus and da)
>  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
>
>  #####################################################################
>  # dcons support (Dumb Console Device)
> diff --git a/sys/conf/files b/sys/conf/files
> index 126a08d697b4..cc18ecbb9301 100644
> --- a/sys/conf/files
> +++ b/sys/conf/files
> @@ -1729,6 +1729,7 @@ dev/filemon/filemon.c             optional filemon
>  dev/firewire/firewire.c                optional firewire
>  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/fwmem.c           optional firewire
>  dev/firewire/fwohci.c          optional firewire
> diff --git a/sys/dev/firewire/fwcam.c b/sys/dev/firewire/fwcam.c
> new file mode 100644
> index 000000000000..38a99c359c18
> --- /dev/null
> +++ b/sys/dev/firewire/fwcam.c
> @@ -0,0 +1,1051 @@
> +/*
> + * Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com>
> + *
> + * SPDX-License-Identifier: BSD-2-Clause
> + */
> +
> +/*
> + * fwcam(4) - IIDC 1394-based Digital Camera driver
> + *
> + * Implements the IIDC v1.30 specification (TA Document 1999023) for
> + * FireWire digital cameras.
> + */
> +
> +#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/fwcam.h>
> +#include <dev/firewire/fw_helpers.h>
> +
> +static MALLOC_DEFINE(M_FWCAM, "fwcam", "IIDC FireWire Camera");
> +
> +static int debug = 0;
> +static int iso_channel = 0;
> +SYSCTL_DECL(_hw_firewire);
> +static SYSCTL_NODE(_hw_firewire, OID_AUTO, fwcam, CTLFLAG_RD | CTLFLAG_MPSAFE,
> +    0, "IIDC Camera");
> +SYSCTL_INT(_hw_firewire_fwcam, OID_AUTO, debug, CTLFLAG_RWTUN, &debug, 0,
> +    "fwcam debug level");
> +SYSCTL_INT(_hw_firewire_fwcam, OID_AUTO, iso_channel, CTLFLAG_RWTUN,
> +    &iso_channel, 0, "ISO channel for isochronous receive (default 0)");
> +
> +#define FWCAM_DEBUG(lev, fmt, ...)                                     \
> +       do {                                                            \
> +               if (debug >= (lev))                                     \
> +                       printf("fwcam: " fmt, ## __VA_ARGS__);          \
> +       } while (0)
> +
> +static void    fwcam_identify(driver_t *, device_t);
> +static int     fwcam_probe(device_t);
> +static int     fwcam_attach(device_t);
> +static int     fwcam_detach(device_t);
> +static void    fwcam_post_busreset(void *);
> +static void    fwcam_probe_task(void *, int);
> +static void    fwcam_post_explore(void *);
> +static int     fwcam_iso_start(struct fwcam_softc *);
> +static void    fwcam_iso_stop(struct fwcam_softc *);
> +static void    fwcam_iso_input(struct fw_xferq *);
> +
> +static d_open_t                fwcam_cdev_open;
> +static d_close_t       fwcam_cdev_close;
> +static d_read_t                fwcam_cdev_read;
> +static d_poll_t                fwcam_cdev_poll;
> +static d_ioctl_t       fwcam_cdev_ioctl;
> +
> +static struct cdevsw fwcam_cdevsw = {
> +       .d_version =    D_VERSION,
> +       .d_flags =      D_TRACKCLOSE,
> +       .d_open =       fwcam_cdev_open,
> +       .d_close =      fwcam_cdev_close,
> +       .d_read =       fwcam_cdev_read,
> +       .d_poll =       fwcam_cdev_poll,
> +       .d_ioctl =      fwcam_cdev_ioctl,
> +       .d_name =       "fwcam",
> +};
> +
> +static uint32_t
> +fwcam_find_iidc(struct fw_device *fwdev)
> +{
> +       struct crom_context cc;
> +       struct csrreg *reg;
> +       uint32_t cmd_base;
> +
> +       if (!crom_has_specver(fwdev->csrrom, CSRVAL_1394TA, CSR_PROTCAM130) &&
> +           !crom_has_specver(fwdev->csrrom, CSRVAL_1394TA, CSR_PROTCAM120) &&
> +           !crom_has_specver(fwdev->csrrom, CSRVAL_1394TA, CSR_PROTCAM104))
> +               return (0);
> +
> +       cmd_base = 0;
> +       crom_init_context(&cc, fwdev->csrrom);
> +       reg = crom_search_key(&cc, IIDC_CROM_CMD_BASE);
> +       if (reg != NULL && reg->val != 0)
> +               cmd_base = reg->val;
> +
> +       return (cmd_base);
> +}
> +
> +static int
> +fwcam_read_quadlet(struct fwcam_softc *sc, uint32_t offset, uint32_t *val)
> +{
> +       uint16_t dst;
> +       uint8_t spd;
> +       int err;
> +
> +       FWCAM_LOCK(sc);
> +       if (sc->fwdev == NULL) {
> +               FWCAM_UNLOCK(sc);
> +               return (ENXIO);
> +       }
> +       dst = FWLOCALBUS | sc->fwdev->dst;
> +       spd = min(sc->fwdev->speed, FWSPD_S400);
> +       FWCAM_UNLOCK(sc);
> +
> +       err = fw_read_quadlet(sc->fd.fc, M_FWCAM, dst, spd,
> +           sc->cmd_hi, sc->cmd_lo + offset, val);
> +       if (err)
> +               FWCAM_DEBUG(1, "read_quadlet: offset=0x%x err=%d\n",
> +                   offset, err);
> +       return (err);
> +}
> +
> +static int
> +fwcam_write_quadlet(struct fwcam_softc *sc, uint32_t offset, uint32_t val)
> +{
> +       uint16_t dst;
> +       uint8_t spd;
> +       int err;
> +
> +       FWCAM_LOCK(sc);
> +       if (sc->fwdev == NULL) {
> +               FWCAM_UNLOCK(sc);
> +               return (ENXIO);
> +       }
> +       dst = FWLOCALBUS | sc->fwdev->dst;
> +       spd = min(sc->fwdev->speed, FWSPD_S400);
> +       FWCAM_UNLOCK(sc);
> +
> +       err = fw_write_quadlet(sc->fd.fc, M_FWCAM, dst, spd,
> +           sc->cmd_hi, sc->cmd_lo + offset, val);
> +       if (err)
> +               FWCAM_DEBUG(1, "write_quadlet: offset=0x%x err=%d\n",
> +                   offset, err);
> +       return (err);
> +}
> +
> +#if 0
> +static const char *
> +fwcam_format_name(int format)
> +{
> +       static const char *names[] = {
> +               "VGA (Format_0)",
> +               "Super VGA 1 (Format_1)",
> +               "Super VGA 2 (Format_2)",
> +               "Reserved",
> +               "Reserved",
> +               "Reserved",
> +               "Still Image (Format_6)",
> +               "Partial Image (Format_7)",
> +       };
> +
> +       if (format >= 0 && format <= 7)
> +               return (names[format]);
> +       return ("Unknown");
> +}
> +#endif
> +
> +static int
> +fwcam_read_capabilities(struct fwcam_softc *sc)
> +{
> +       int err;
> +
> +       err = fwcam_read_quadlet(sc, IIDC_V_FORMAT_INQ, &sc->formats);
> +       if (err) {
> +               device_printf(sc->fd.dev,
> +                   "failed to read V_FORMAT_INQ: %d\n", err);
> +               return (err);
> +       }
> +
> +       err = fwcam_read_quadlet(sc, IIDC_BASIC_FUNC_INQ, &sc->basic_func);
> +       if (err) {
> +               device_printf(sc->fd.dev,
> +                   "failed to read BASIC_FUNC_INQ: %d\n", err);
> +               return (err);
> +       }
> +
> +       err = fwcam_read_quadlet(sc, IIDC_FEATURE_HI_INQ, &sc->features_hi);
> +       if (err)
> +               sc->features_hi = 0;
> +
> +       err = fwcam_read_quadlet(sc, IIDC_FEATURE_LO_INQ, &sc->features_lo);
> +       if (err)
> +               sc->features_lo = 0;
> +
> +       return (0);
> +}
> +
> +static int
> +fwcam_power_on(struct fwcam_softc *sc)
> +{
> +       uint32_t val;
> +       int err, retries;
> +
> +       err = fwcam_read_quadlet(sc, IIDC_BASIC_FUNC_INQ, &val);
> +       if (err) {
> +               device_printf(sc->fd.dev,
> +                   "cannot read BASIC_FUNC_INQ: %d\n", err);
> +               return (err);
> +       }
> +
> +       if ((val & IIDC_CAM_POWER_CTRL) == 0) {
> +               FWCAM_DEBUG(1, "no power control, assuming powered\n");
> +               return (0);
> +       }
> +
> +       err = fwcam_read_quadlet(sc, IIDC_CAMERA_POWER, &val);
> +       if (err == 0 && (val & IIDC_POWER_ON)) {
> +               FWCAM_DEBUG(1, "camera already powered on\n");
> +               return (0);
> +       }
> +
> +       err = fwcam_write_quadlet(sc, IIDC_CAMERA_POWER, IIDC_POWER_ON);
> +       if (err) {
> +               device_printf(sc->fd.dev,
> +                   "failed to write CAMERA_POWER: %d\n", err);
> +               return (err);
> +       }
> +
> +       for (retries = 0; retries < 50; retries++) {
> +               pause("fwcampw", hz / 10);
> +
> +               if (sc->state == FWCAM_STATE_DETACHING)
> +                       return (ENXIO);
> +
> +               err = fwcam_read_quadlet(sc, IIDC_CAMERA_POWER, &val);
> +               if (err)
> +                       continue;       /* read may fail while powering up */
> +
> +               if (val & IIDC_POWER_ON)
> +                       return (0);
> +       }
> +
> +       device_printf(sc->fd.dev, "camera power-on timeout (5s)\n");
> +       return (ETIMEDOUT);
> +}
> +
> +static void
> +fwcam_probe_task(void *arg, int pending __unused)
> +{
> +       struct fwcam_softc *sc = (struct fwcam_softc *)arg;
> +       int err;
> +
> +       if (sc->state == FWCAM_STATE_DETACHING || sc->fwdev == NULL)
> +               return;
> +
> +       err = fwcam_power_on(sc);
> +       if (err) {
> +               device_printf(sc->fd.dev,
> +                   "power-on failed (%d), trying to read anyway\n", err);
> +       }
> +
> +       if (sc->state == FWCAM_STATE_DETACHING)
> +               return;
> +
> +       if (fwcam_read_capabilities(sc) == 0) {
> +               uint32_t val;
> +
> +               if (fwcam_read_quadlet(sc, IIDC_CUR_V_FORMAT, &val) == 0)
> +                       sc->cur_format = (val >> 29) & 0x7;
> +               if (fwcam_read_quadlet(sc, IIDC_CUR_V_MODE, &val) == 0)
> +                       sc->cur_mode = (val >> 29) & 0x7;
> +               if (fwcam_read_quadlet(sc, IIDC_CUR_V_FRM_RATE, &val) == 0)
> +                       sc->cur_framerate = (val >> 29) & 0x7;
> +
> +               FWCAM_LOCK(sc);
> +               if (sc->state == FWCAM_STATE_DETACHING) {
> +                       FWCAM_UNLOCK(sc);
> +                       return;
> +               }
> +               sc->state = FWCAM_STATE_PROBED;
> +               FWCAM_UNLOCK(sc);
> +
> +               if (sc->open_count > 0 &&
> +                   sc->state != FWCAM_STATE_DETACHING)
> +                       fwcam_iso_start(sc);
> +       }
> +}
> +
> +/*
> + * Compute expected frame size for current format/mode.
> + * Format_0 (VGA) modes:
> + *   Mode 0: 160x120 YUV444  = 160*120*3 = 57600
> + *   Mode 1: 320x240 YUV422  = 320*240*2 = 153600
> + *   Mode 2: 640x480 YUV411  = 640*480*3/2 = 460800
> + *   Mode 3: 640x480 YUV422  = 640*480*2 = 614400
> + *   Mode 4: 640x480 RGB8    = 640*480*3 = 921600
> + *   Mode 5: 640x480 Mono8   = 640*480 = 307200
> + */
> +static uint32_t
> +fwcam_frame_size(struct fwcam_softc *sc)
> +{
> +       static const uint32_t fmt0_sizes[] = {
> +               160 * 120 * 3,          /* mode 0: YUV444 */
> +               320 * 240 * 2,          /* mode 1: YUV422 */
> +               640 * 480 * 3 / 2,      /* mode 2: YUV411 */
> +               640 * 480 * 2,          /* mode 3: YUV422 */
> +               640 * 480 * 3,          /* mode 4: RGB8 */
> +               640 * 480,              /* mode 5: Mono8 */
> +       };
> +
> +       if (sc->cur_format == IIDC_FMT_VGA && sc->cur_mode < nitems(fmt0_sizes))
> +               return (fmt0_sizes[sc->cur_mode]);
> +
> +       /* Default to largest VGA mode */
> +       return (FWCAM_MAX_FRAME_SIZE);
> +}
> +
> +static int
> +fwcam_iso_start(struct fwcam_softc *sc)
> +{
> +       struct firewire_comm *fc = sc->fd.fc;
> +       struct fw_xferq *xferq;
> +       uint32_t val;
> +       int dma_ch, err;
> +
> +       mtx_assert(&sc->mtx, MA_NOTOWNED);
> +
> +       FWCAM_LOCK(sc);
> +       if (sc->dma_ch >= 0 || sc->state == FWCAM_STATE_STREAMING) {
> +               FWCAM_UNLOCK(sc);
> +               return (0);     /* already running or starting */
> +       }
> +       if (sc->state == FWCAM_STATE_DETACHING) {
> +               FWCAM_UNLOCK(sc);
> +               return (ENXIO);
> +       }
> +       FWCAM_UNLOCK(sc);
> +
> +       dma_ch = fw_open_isodma(fc, 0);
> +       if (dma_ch < 0) {
> +               device_printf(sc->fd.dev, "no IR DMA channel available\n");
> +               return (EBUSY);
> +       }
> +
> +       FWCAM_LOCK(sc);
> +       if (sc->dma_ch >= 0) {
> +               FWCAM_UNLOCK(sc);
> +               fc->ir[dma_ch]->flag &= ~FWXFERQ_OPEN;
> +               return (0);
> +       }
> +       FWCAM_UNLOCK(sc);
> +
> +       xferq = fc->ir[dma_ch];
> +       xferq->flag |= FWXFERQ_EXTBUF | FWXFERQ_HANDLER | FWXFERQ_STREAM;
> +
> +       sc->iso_channel = (uint8_t)(iso_channel & FWXFERQ_CHTAGMASK);
> +       xferq->flag &= ~FWXFERQ_CHTAGMASK;
> +       xferq->flag |= sc->iso_channel & FWXFERQ_CHTAGMASK;
> +
> +       xferq->sc = (caddr_t)sc;
> +       xferq->hand = fwcam_iso_input;
> +       xferq->bnchunk = FWCAM_ISO_NCHUNK;
> +       xferq->bnpacket = 1;
> +       xferq->psize = FWCAM_ISO_PKTSIZE;
> +       xferq->queued = 0;
> +       xferq->buf = NULL;
> +
> +       xferq->bulkxfer = malloc(sizeof(struct fw_bulkxfer) * xferq->bnchunk,
> +           M_FWCAM, M_WAITOK | M_ZERO);
> +
> +       fw_iso_init_chunks(xferq);
> +
> +       sc->frame_size = fwcam_frame_size(sc);
> +       sc->frame_buf = malloc(sc->frame_size, M_FWCAM, M_WAITOK | M_ZERO);
> +       sc->read_buf = malloc(sc->frame_size, M_FWCAM, M_WAITOK | M_ZERO);
> +       sc->frame_offset = 0;
> +       sc->frame_ready = 0;
> +       sc->frame_dropped = 0;
> +
> +       val = ((uint32_t)sc->iso_channel << IIDC_ISO_CH_SHIFT) |
> +           ((uint32_t)sc->iso_speed << IIDC_ISO_SPEED_SHIFT);
> +       err = fwcam_write_quadlet(sc, IIDC_ISO_CHANNEL, val);
> +       if (err) {
> +               device_printf(sc->fd.dev,
> +                   "failed to set ISO_CHANNEL: %d\n", err);
> +               goto fail;
> +       }
> +
> +       err = fwcam_write_quadlet(sc, IIDC_ISO_EN, IIDC_ISO_EN_ON);
> +       if (err) {
> +               device_printf(sc->fd.dev,
> +                   "failed to enable ISO: %d\n", err);
> +               goto fail;
> +       }
> +
> +       err = fc->irx_enable(fc, dma_ch);
> +       if (err) {
> +               device_printf(sc->fd.dev,
> +                   "failed to start IR DMA: %d\n", err);
> +               fwcam_write_quadlet(sc, IIDC_ISO_EN, 0);
> +               goto fail;
> +       }
> +
> +       FWCAM_LOCK(sc);
> +       if (sc->state == FWCAM_STATE_DETACHING) {
> +               FWCAM_UNLOCK(sc);
> +               fc->irx_disable(fc, dma_ch);
> +               fwcam_write_quadlet(sc, IIDC_ISO_EN, 0);
> +               err = ENXIO;
> +               goto fail;
> +       }
> +       sc->dma_ch = dma_ch;
> +       sc->state = FWCAM_STATE_STREAMING;
> +       FWCAM_UNLOCK(sc);
> +
> +       return (0);
> +
> +fail:
> +       fw_iso_free_chunks(xferq, M_FWCAM);
> +       xferq->flag &= ~(FWXFERQ_MODEMASK | FWXFERQ_OPEN | FWXFERQ_STREAM |
> +           FWXFERQ_EXTBUF | FWXFERQ_HANDLER | FWXFERQ_CHTAGMASK);
> +       xferq->hand = NULL;
> +
> +       free(sc->frame_buf, M_FWCAM);
> +       free(sc->read_buf, M_FWCAM);
> +       sc->frame_buf = NULL;
> +       sc->read_buf = NULL;
> +       sc->dma_ch = -1;
> +
> +       return (err);
> +}
> +
> +static void
> +fwcam_iso_stop(struct fwcam_softc *sc)
> +{
> +       struct firewire_comm *fc = sc->fd.fc;
> +       struct fw_xferq *xferq;
> +       int dma_ch;
> +
> +       FWCAM_LOCK(sc);
> +       dma_ch = sc->dma_ch;
> +       if (dma_ch < 0) {
> +               FWCAM_UNLOCK(sc);
> +               return;
> +       }
> +       sc->dma_ch = -1;        /* claim ownership of teardown */
> +       FWCAM_UNLOCK(sc);
> +
> +       xferq = fc->ir[dma_ch];
> +
> +       if (xferq->flag & FWXFERQ_RUNNING)
> +               fc->irx_disable(fc, dma_ch);
> +
> +       if (sc->fwdev != NULL)
> +               fwcam_write_quadlet(sc, IIDC_ISO_EN, 0);
> +
> +       FWCAM_LOCK(sc);
> +       fw_iso_wait_inactive_locked(&sc->mtx, &sc->iso_active, "fwcamis");
> +       sc->frame_ready = 0;
> +       while (sc->read_in_progress)
> +               msleep(&sc->read_in_progress, &sc->mtx, PWAIT, "fwcamst", hz);
> +       FWCAM_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_FWCAM);
> +
> +       free(sc->frame_buf, M_FWCAM);
> +       free(sc->read_buf, M_FWCAM);
> +       sc->frame_buf = NULL;
> +       sc->read_buf = NULL;
> +}
> +
> +static void
> +fwcam_iso_input(struct fw_xferq *xferq)
> +{
> +       struct fwcam_softc *sc = (struct fwcam_softc *)xferq->sc;
> +       struct fw_bulkxfer *sxfer;
> +       struct fw_pkt *fp;
> +       struct mbuf *m;
> +       uint8_t *payload;
> +       uint32_t plen;
> +       uint8_t *tmp;
> +       int dma_ch;
> +
> +       FWCAM_LOCK(sc);
> +       dma_ch = sc->dma_ch;
> +       if (dma_ch < 0 || sc->frame_buf == NULL) {
> +               FWCAM_UNLOCK(sc);
> +               return;
> +       }
> +       sc->iso_active = 1;
> +       FWCAM_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 == 0) {
> +                       m_freem(m);
> +                       continue;       /* empty packet (padding) */
> +               }
> +
> +               if (fp->mode.stream.sy == 1) {
> +                       if (sc->frame_offset > 0) {
> +                               if (sc->frame_offset == sc->frame_size) {
> +                                       FWCAM_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;
> +                                               wakeup(sc);
> +                                               selwakeup(&sc->rsel);
> +                                       }
> +                                       FWCAM_UNLOCK(sc);
> +                               } else {
> +                                       sc->frame_dropped++;
> +                               }
> +                       }
> +                       sc->frame_offset = 0;
> +               }
> +
> +               if (m->m_len < 4) {
> +                       m_freem(m);
> +                       continue;
> +               }
> +               payload = mtod(m, uint8_t *) + 4;
> +               if (plen > (uint32_t)(m->m_len - 4))
> +                       plen = m->m_len - 4;
> +
> +               if (sc->frame_offset + plen <= sc->frame_size) {
> +                       memcpy(sc->frame_buf + sc->frame_offset, payload, plen);
> +                       sc->frame_offset += plen;
> +               } else {
> +                       sc->frame_dropped++;
> +                       sc->frame_offset = 0;
> +               }
> +
> +               m_freem(m);
> +       }
> +
> +       fw_iso_rearm_done(xferq, sc->fd.fc, &sc->mtx, &sc->iso_active,
> +           &sc->dma_ch, dma_ch);
> +}
> +
> +static int
> +fwcam_cdev_open(struct cdev *dev, int oflags, int devtype, struct thread *td)
> +{
> +       struct fwcam_softc *sc = dev->si_drv1;
> +       int err = 0;
> +
> +       FWCAM_LOCK(sc);
> +       if (sc->state == FWCAM_STATE_DETACHING) {
> +               FWCAM_UNLOCK(sc);
> +               return (ENXIO);
> +       }
> +       if (sc->state != FWCAM_STATE_PROBED &&
> +           sc->state != FWCAM_STATE_STREAMING) {
> +               FWCAM_UNLOCK(sc);
> +               return (EAGAIN);        /* not yet probed */
> +       }
> +
> +       sc->open_count++;
> +       if (sc->open_count == 1 && sc->state == FWCAM_STATE_PROBED) {
> +               FWCAM_UNLOCK(sc);
> +               err = fwcam_iso_start(sc);
> +               if (err) {
> +                       FWCAM_LOCK(sc);
> +                       sc->open_count--;
> +                       FWCAM_UNLOCK(sc);
> +               }
> +       } else {
> +               FWCAM_UNLOCK(sc);
> +       }
> +       return (err);
> +}
> +
> +static int
> +fwcam_cdev_close(struct cdev *dev, int fflag, int devtype, struct thread *td)
> +{
> +       struct fwcam_softc *sc = dev->si_drv1;
> +
> +       FWCAM_LOCK(sc);
> +       sc->open_count--;
> +       if (sc->open_count <= 0) {
> +               sc->open_count = 0;
> +               if (sc->state == FWCAM_STATE_STREAMING) {
> +                       FWCAM_UNLOCK(sc);
> +                       fwcam_iso_stop(sc);
> +                       FWCAM_LOCK(sc);
> +                       if (sc->state != FWCAM_STATE_DETACHING)
> +                               sc->state = FWCAM_STATE_PROBED;
> +               }
> +       }
> +       FWCAM_UNLOCK(sc);
> +       return (0);
> +}
> +
> +static int
> +fwcam_cdev_read(struct cdev *dev, struct uio *uio, int ioflag)
> +{
> +       struct fwcam_softc *sc = dev->si_drv1;
> +       int err;
> +
> +       FWCAM_LOCK(sc);
> +       while (!sc->frame_ready) {
> +               if (sc->state != FWCAM_STATE_STREAMING) {
> +                       FWCAM_UNLOCK(sc);
> +                       return (ENXIO);
> +               }
> +               if (ioflag & FNONBLOCK) {
> +                       FWCAM_UNLOCK(sc);
> +                       return (EAGAIN);
> +               }
> +               err = msleep(sc, &sc->mtx, PCATCH, "fwcamrd", 5 * hz);
> +               if (err) {
> +                       FWCAM_UNLOCK(sc);
> +                       return (err);
> +               }
> +       }
> +
> +       sc->frame_ready = 0;
> +       if (sc->read_buf == NULL) {
> +               FWCAM_UNLOCK(sc);
> +               return (ENXIO);
> +       }
> +       sc->read_in_progress = 1;
> +       FWCAM_UNLOCK(sc);
> +
> +       err = uiomove(sc->read_buf,
> +           MIN(uio->uio_resid, sc->frame_size), uio);
> +
> +       FWCAM_LOCK(sc);
> +       sc->read_in_progress = 0;
> +       wakeup(&sc->read_in_progress);
> +       FWCAM_UNLOCK(sc);
> +
> +       return (err);
> +}
> +
> +static int
> +fwcam_cdev_poll(struct cdev *dev, int events, struct thread *td)
> +{
> +       struct fwcam_softc *sc = dev->si_drv1;
> +       int revents = 0;
> +
> +       FWCAM_LOCK(sc);
> +       if (events & (POLLIN | POLLRDNORM)) {
> +               if (sc->frame_ready)
> +                       revents |= events & (POLLIN | POLLRDNORM);
> +               else
> +                       selrecord(td, &sc->rsel);
> +       }
> +       FWCAM_UNLOCK(sc);
> +       return (revents);
> +}
> +
> +static const uint32_t fwcam_feat_inq[] = {
> +       [FWCAM_FEAT_BRIGHTNESS]   = IIDC_BRIGHTNESS_INQ,
> +       [FWCAM_FEAT_AUTO_EXPOSURE] = IIDC_AUTO_EXPOSURE_INQ,
> +       [FWCAM_FEAT_SHARPNESS]    = IIDC_SHARPNESS_INQ,
> +       [FWCAM_FEAT_WHITE_BALANCE] = IIDC_WHITE_BAL_INQ,
> +       [FWCAM_FEAT_HUE]         = IIDC_HUE_INQ,
> +       [FWCAM_FEAT_SATURATION]  = IIDC_SATURATION_INQ,
> +       [FWCAM_FEAT_GAMMA]       = IIDC_GAMMA_INQ,
> +       [FWCAM_FEAT_SHUTTER]     = IIDC_SHUTTER_INQ,
> +       [FWCAM_FEAT_GAIN]        = IIDC_GAIN_INQ,
> +       [FWCAM_FEAT_IRIS]        = IIDC_IRIS_INQ,
> +       [FWCAM_FEAT_FOCUS]       = IIDC_FOCUS_INQ,
> +       [FWCAM_FEAT_TEMPERATURE] = IIDC_TEMPERATURE_INQ,
> +       [FWCAM_FEAT_TRIGGER]     = IIDC_TRIGGER_INQ,
> +};
> +
> +static const uint32_t fwcam_feat_ctrl[] = {
> +       [FWCAM_FEAT_BRIGHTNESS]   = IIDC_BRIGHTNESS,
> +       [FWCAM_FEAT_AUTO_EXPOSURE] = IIDC_AUTO_EXPOSURE,
> +       [FWCAM_FEAT_SHARPNESS]    = IIDC_SHARPNESS,
> +       [FWCAM_FEAT_WHITE_BALANCE] = IIDC_WHITE_BALANCE,
> +       [FWCAM_FEAT_HUE]         = IIDC_HUE,
> +       [FWCAM_FEAT_SATURATION]  = IIDC_SATURATION,
> +       [FWCAM_FEAT_GAMMA]       = IIDC_GAMMA,
> +       [FWCAM_FEAT_SHUTTER]     = IIDC_SHUTTER,
> +       [FWCAM_FEAT_GAIN]        = IIDC_GAIN,
> +       [FWCAM_FEAT_IRIS]        = IIDC_IRIS,
> +       [FWCAM_FEAT_FOCUS]       = IIDC_FOCUS,
> +       [FWCAM_FEAT_TEMPERATURE] = IIDC_TEMPERATURE,
> +       [FWCAM_FEAT_TRIGGER]     = IIDC_TRIGGER_MODE,
> +       [FWCAM_FEAT_ZOOM]        = IIDC_ZOOM,
> +       [FWCAM_FEAT_PAN]         = IIDC_PAN,
> +       [FWCAM_FEAT_TILT]        = IIDC_TILT,
> +};
> +
> +static int
> +fwcam_get_feature(struct fwcam_softc *sc, struct fwcam_feature *feat)
> +{
> +       uint32_t inq, ctrl;
> +       int err;
> +
> +       if (feat->id >= FWCAM_FEAT_MAX)
> +               return (EINVAL);
> +
> +       feat->flags = 0;
> +       feat->min = 0;
> +       feat->max = 0;
> +       if (feat->id < nitems(fwcam_feat_inq) &&
> +           fwcam_feat_inq[feat->id] != 0) {
> +               err = fwcam_read_quadlet(sc, fwcam_feat_inq[feat->id], &inq);
> +               if (err)
> +                       return (err);
> +
> +               if (inq & (1 << 31))
> +                       feat->flags |= FWCAM_FEATF_PRESENT;
> +               if (inq & (1 << 28))
> +                       feat->flags |= FWCAM_FEATF_ONOFF;
> +               if (inq & (1 << 27))
> +                       feat->flags |= FWCAM_FEATF_AUTO;
> +               if (inq & (1 << 26))
> +                       feat->flags |= FWCAM_FEATF_MANUAL;
> +               feat->min = (inq >> 12) & 0xfff;
> +               feat->max = inq & 0xfff;
> +       }
> +
> +       if (feat->id >= nitems(fwcam_feat_ctrl) ||
> +           fwcam_feat_ctrl[feat->id] == 0)
> +               return (EINVAL);
> +
> +       err = fwcam_read_quadlet(sc, fwcam_feat_ctrl[feat->id], &ctrl);
> +       if (err)
> +               return (err);
> +
> +       feat->value = ctrl & 0xfff;
> +       /* White balance has U/B in bits [20:31] and V/R in bits [8:19] */
> +       if (feat->id == FWCAM_FEAT_WHITE_BALANCE)
> +               feat->value2 = (ctrl >> 12) & 0xfff;
> +       else
> +               feat->value2 = 0;
> +
> +       return (0);
> +}
> +
> +static int
> +fwcam_set_feature(struct fwcam_softc *sc, struct fwcam_feature *feat)
> +{
> +       uint32_t ctrl, val;
> +       int err;
> +
> +       if (feat->id >= FWCAM_FEAT_MAX)
> +               return (EINVAL);
> +       if (feat->id >= nitems(fwcam_feat_ctrl) ||
> +           fwcam_feat_ctrl[feat->id] == 0)
> +               return (EINVAL);
> +
> +       err = fwcam_read_quadlet(sc, fwcam_feat_ctrl[feat->id], &ctrl);
> +       if (err)
> +               return (err);
> +
> +       if (feat->id == FWCAM_FEAT_WHITE_BALANCE) {
> +               /* White balance: value=V/R (low 12), value2=U/B (high 12) */
> +               val = (ctrl & 0xff000000) |
> +                   ((feat->value2 & 0xfff) << 12) |
> +                   (feat->value & 0xfff);
> +       } else {
> +               /* Preserve upper bits, set new value in lower 12 */
> +               val = (ctrl & 0xfffff000) | (feat->value & 0xfff);
> +       }
> +
> +       return (fwcam_write_quadlet(sc, fwcam_feat_ctrl[feat->id], val));
> +}
> +
> +static int
> +fwcam_cdev_ioctl(struct cdev *dev, u_long cmd, caddr_t data,
> +    int fflag, struct thread *td)
> +{
> *** 557 LINES SKIPPED ***
>