/* * Copyright (c) 2026 Abdelkader Boudih * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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);