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 <[email protected]> wrote: > > The branch main has been updated by adrian: > > URL: > https://cgit.FreeBSD.org/src/commit/?id=40e05479718460820cd24c22e59519a4f13eba3d > > commit 40e05479718460820cd24c22e59519a4f13eba3d > Author: Abdelkader Boudih <[email protected]> > AuthorDate: 2026-06-25 02:14:22 +0000 > Commit: Adrian Chadd <[email protected]> > 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 <[email protected]> > +.\" > +.\" 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 [email protected] > 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 <[email protected]> > + * > + * 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 *** >
