/* * Copyright (c) 2026 Abdelkader Boudih * * SPDX-License-Identifier: BSD-2-Clause */ #ifndef _DEV_FIREWIRE_FW_HELPERS_H_ #define _DEV_FIREWIRE_FW_HELPERS_H_ static __inline int fw_xfer_timeout_cancel(struct fw_xfer *xfer) { struct firewire_comm *fc = xfer->fc; struct fw_xfer *txfer; int can_cancel_sent, cancelled, remove_tlabel; can_cancel_sent = 0; cancelled = 0; remove_tlabel = 0; /* * FWXF_INQ xfers are still on the software AT queue and can be * removed safely. FWXF_SENT xfers have left the controller and are * waiting only for a split response, so removing their tlabel is enough * to make a late response miss this xfer. FWXF_START xfers are still * owned by the controller descriptor ring and must not be cancelled. */ FW_GLOCK(fc); if ((xfer->flag & FWXF_INQ) != 0) { STAILQ_REMOVE(&xfer->q->q, xfer, fw_xfer, link); xfer->flag &= ~FWXF_INQ; cancelled = 1; remove_tlabel = 1; } else if ((xfer->flag & FWXF_SENT) != 0) { can_cancel_sent = 1; } FW_GUNLOCK(fc); if (remove_tlabel || can_cancel_sent) { mtx_lock(&fc->tlabel_lock); if (xfer->tl >= 0) { STAILQ_FOREACH(txfer, &fc->tlabels[xfer->tl], tlabel) { if (txfer == xfer) break; } if (txfer == xfer) { STAILQ_REMOVE(&fc->tlabels[xfer->tl], xfer, fw_xfer, tlabel); cancelled = 1; } xfer->tl = -1; } mtx_unlock(&fc->tlabel_lock); } if (cancelled) { mtx_lock(&fc->wait_lock); xfer->resp = ETIMEDOUT; xfer->flag |= FWXF_WAKE; mtx_unlock(&fc->wait_lock); wakeup(xfer); } return (cancelled); } /* * Wait for an async transfer to complete, with timeout. * * If the timeout fires while the controller still owns the transfer, keep the * xfer alive and continue waiting for the core completion path. Returning * early in that state lets callers free an xfer still referenced by the * controller descriptor ring. */ static __inline int fw_xferwait_timo(struct fw_xfer *xfer, int timo) { struct firewire_comm *fc = xfer->fc; struct mtx *lock = &fc->wait_lock; int err = 0; int timedout = 0; mtx_lock(lock); while ((xfer->flag & FWXF_WAKE) == 0) { err = msleep(xfer, lock, PWAIT, "fwxfer", timo); if (err == EWOULDBLOCK) { mtx_unlock(lock); if (fw_xfer_timeout_cancel(xfer)) return (ETIMEDOUT); timedout = 1; mtx_lock(lock); continue; } if (err) { mtx_unlock(lock); return (err); } } mtx_unlock(lock); if (timedout && xfer->resp == ETIMEDOUT) return (ETIMEDOUT); return (0); } /* * Submit an async request and wait for completion with timeout. */ static __inline int fw_xfer_request_wait(struct firewire_comm *fc, struct fw_xfer *xfer, int timo) { int err; err = fw_asyreq(fc, -1, xfer); if (err != 0) return (err); return (fw_xferwait_timo(xfer, timo)); } static __inline int fw_read_quadlet(struct firewire_comm *fc, struct malloc_type *mtype, uint16_t dst, uint8_t spd, uint16_t addr_hi, uint32_t addr_lo, uint32_t *val) { struct fw_xfer *xfer; struct fw_pkt *fp; int err; xfer = fw_xfer_alloc_buf(mtype, 0, 4); if (xfer == NULL) return (ENOMEM); xfer->send.spd = spd; xfer->fc = fc; xfer->hand = fw_xferwake; fp = &xfer->send.hdr; fp->mode.rreqq.tcode = FWTCODE_RREQQ; fp->mode.rreqq.dst = dst; fp->mode.rreqq.dest_hi = addr_hi; fp->mode.rreqq.dest_lo = addr_lo; err = fw_xfer_request_wait(fc, xfer, 2 * hz); if (err != 0) goto out; if (xfer->resp == 0 && xfer->recv.hdr.mode.rresq.rtcode == FWRCODE_COMPLETE) *val = ntohl(xfer->recv.hdr.mode.rresq.data); else err = EIO; out: fw_xfer_free_buf(xfer); return (err); } static __inline int fw_write_quadlet(struct firewire_comm *fc, struct malloc_type *mtype, uint16_t dst, uint8_t spd, uint16_t addr_hi, uint32_t addr_lo, uint32_t val) { struct fw_xfer *xfer; struct fw_pkt *fp; int err; xfer = fw_xfer_alloc_buf(mtype, 0, 0); if (xfer == NULL) return (ENOMEM); xfer->send.spd = spd; xfer->fc = fc; xfer->hand = fw_xferwake; fp = &xfer->send.hdr; fp->mode.wreqq.tcode = FWTCODE_WREQQ; fp->mode.wreqq.dst = dst; fp->mode.wreqq.dest_hi = addr_hi; fp->mode.wreqq.dest_lo = addr_lo; fp->mode.wreqq.data = htonl(val); err = fw_xfer_request_wait(fc, xfer, 2 * hz); if (err != 0) goto out; if (xfer->resp != 0 || xfer->recv.hdr.mode.wres.rtcode != FWRCODE_COMPLETE) err = EIO; out: fw_xfer_free_buf(xfer); return (err); } static __inline void fw_iso_init_chunks(struct fw_xferq *xferq) { struct mbuf *m; int i; STAILQ_INIT(&xferq->stvalid); STAILQ_INIT(&xferq->stfree); STAILQ_INIT(&xferq->stdma); xferq->stproc = NULL; for (i = 0; i < xferq->bnchunk; i++) { m = m_getcl(M_WAITOK, MT_DATA, M_PKTHDR); xferq->bulkxfer[i].mbuf = m; m->m_len = m->m_pkthdr.len = m->m_ext.ext_size; STAILQ_INSERT_TAIL(&xferq->stfree, &xferq->bulkxfer[i], link); } } static __inline void fw_iso_free_chunks(struct fw_xferq *xferq, struct malloc_type *mtype) { int i; for (i = 0; i < xferq->bnchunk; i++) { if (xferq->bulkxfer[i].mbuf != NULL) m_freem(xferq->bulkxfer[i].mbuf); } free(xferq->bulkxfer, mtype); xferq->bulkxfer = NULL; } /* * Dequeue an ISO receive mbuf, replace with a fresh one. * Returns the consumed mbuf, or NULL on error/allocation failure. */ static __inline struct mbuf * fw_iso_dequeue(struct fw_xferq *xferq, struct fw_bulkxfer *sxfer, struct firewire_comm *fc) { struct fw_pkt *fp; struct mbuf *m, *m0; fp = mtod(sxfer->mbuf, struct fw_pkt *); if (fc->irx_post != NULL) fc->irx_post(fc, fp->mode.ld); m = sxfer->mbuf; m0 = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); if (m0 == NULL) { /* Allocation failed; recycle the original mbuf. */ STAILQ_INSERT_TAIL(&xferq->stfree, sxfer, link); return (NULL); } m0->m_len = m0->m_pkthdr.len = m0->m_ext.ext_size; sxfer->mbuf = m0; STAILQ_INSERT_TAIL(&xferq->stfree, sxfer, link); if (sxfer->resp != 0) { m_freem(m); return (NULL); } return (m); } static __inline void fw_iso_rearm(struct fw_xferq *xferq, struct firewire_comm *fc, int dma_ch) { if (STAILQ_FIRST(&xferq->stfree) != NULL && dma_ch >= 0) fc->irx_enable(fc, dma_ch); } static __inline void fw_iso_wait_inactive_locked(struct mtx *mtx, int *active, const char *wmesg) { mtx_assert(mtx, MA_OWNED); while (*active) msleep(active, mtx, PWAIT, wmesg, hz); } static __inline void fw_iso_rearm_done(struct fw_xferq *xferq, struct firewire_comm *fc, struct mtx *mtx, int *active, int *cur_dma_ch, int dma_ch) { mtx_lock(mtx); if (*cur_dma_ch == dma_ch) fw_iso_rearm(xferq, fc, dma_ch); *active = 0; wakeup(active); mtx_unlock(mtx); } #endif /* _DEV_FIREWIRE_FW_HELPERS_H_ */