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 ***
