xref: /freebsd/sys/dev/firewire/fw_helpers.h (revision af20f017ec0b75456d9031059bc79be01f141c3b)
1 /*
2  * Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com>
3  *
4  * SPDX-License-Identifier: BSD-2-Clause
5  */
6 
7 #ifndef _DEV_FIREWIRE_FW_HELPERS_H_
8 #define _DEV_FIREWIRE_FW_HELPERS_H_
9 
10 static __inline int
fw_xfer_timeout_cancel(struct fw_xfer * xfer)11 fw_xfer_timeout_cancel(struct fw_xfer *xfer)
12 {
13 	struct firewire_comm *fc = xfer->fc;
14 	struct fw_xfer *txfer;
15 	int can_cancel_sent, cancelled, remove_tlabel;
16 
17 	can_cancel_sent = 0;
18 	cancelled = 0;
19 	remove_tlabel = 0;
20 
21 	/*
22 	 * FWXF_INQ xfers are still on the software AT queue and can be
23 	 * removed safely.  FWXF_SENT xfers have left the controller and are
24 	 * waiting only for a split response, so removing their tlabel is enough
25 	 * to make a late response miss this xfer.  FWXF_START xfers are still
26 	 * owned by the controller descriptor ring and must not be cancelled.
27 	 */
28 	FW_GLOCK(fc);
29 	if ((xfer->flag & FWXF_INQ) != 0) {
30 		STAILQ_REMOVE(&xfer->q->q, xfer, fw_xfer, link);
31 		xfer->flag &= ~FWXF_INQ;
32 		cancelled = 1;
33 		remove_tlabel = 1;
34 	} else if ((xfer->flag & FWXF_SENT) != 0) {
35 		can_cancel_sent = 1;
36 	}
37 	FW_GUNLOCK(fc);
38 
39 	if (remove_tlabel || can_cancel_sent) {
40 		mtx_lock(&fc->tlabel_lock);
41 		if (xfer->tl >= 0) {
42 			STAILQ_FOREACH(txfer, &fc->tlabels[xfer->tl], tlabel) {
43 				if (txfer == xfer)
44 					break;
45 			}
46 			if (txfer == xfer) {
47 				STAILQ_REMOVE(&fc->tlabels[xfer->tl], xfer,
48 				    fw_xfer, tlabel);
49 				cancelled = 1;
50 			}
51 			xfer->tl = -1;
52 		}
53 		mtx_unlock(&fc->tlabel_lock);
54 	}
55 
56 	if (cancelled) {
57 		mtx_lock(&fc->wait_lock);
58 		xfer->resp = ETIMEDOUT;
59 		xfer->flag |= FWXF_WAKE;
60 		mtx_unlock(&fc->wait_lock);
61 		wakeup(xfer);
62 	}
63 
64 	return (cancelled);
65 }
66 
67 /*
68  * Wait for an async transfer to complete, with timeout.
69  *
70  * If the timeout fires while the controller still owns the transfer, keep the
71  * xfer alive and continue waiting for the core completion path.  Returning
72  * early in that state lets callers free an xfer still referenced by the
73  * controller descriptor ring.
74  */
75 static __inline int
fw_xferwait_timo(struct fw_xfer * xfer,int timo)76 fw_xferwait_timo(struct fw_xfer *xfer, int timo)
77 {
78 	struct firewire_comm *fc = xfer->fc;
79 	struct mtx *lock = &fc->wait_lock;
80 	int err = 0;
81 	int timedout = 0;
82 
83 	mtx_lock(lock);
84 	while ((xfer->flag & FWXF_WAKE) == 0) {
85 		err = msleep(xfer, lock, PWAIT, "fwxfer", timo);
86 		if (err == EWOULDBLOCK) {
87 			mtx_unlock(lock);
88 			if (fw_xfer_timeout_cancel(xfer))
89 				return (ETIMEDOUT);
90 			timedout = 1;
91 			mtx_lock(lock);
92 			continue;
93 		}
94 		if (err) {
95 			mtx_unlock(lock);
96 			return (err);
97 		}
98 	}
99 	mtx_unlock(lock);
100 	if (timedout && xfer->resp == ETIMEDOUT)
101 		return (ETIMEDOUT);
102 	return (0);
103 }
104 
105 /*
106  * Submit an async request and wait for completion with timeout.
107  */
108 static __inline int
fw_xfer_request_wait(struct firewire_comm * fc,struct fw_xfer * xfer,int timo)109 fw_xfer_request_wait(struct firewire_comm *fc, struct fw_xfer *xfer, int timo)
110 {
111 	int err;
112 
113 	err = fw_asyreq(fc, -1, xfer);
114 	if (err != 0)
115 		return (err);
116 	return (fw_xferwait_timo(xfer, timo));
117 }
118 
119 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)120 fw_read_quadlet(struct firewire_comm *fc, struct malloc_type *mtype,
121     uint16_t dst, uint8_t spd, uint16_t addr_hi, uint32_t addr_lo,
122     uint32_t *val)
123 {
124 	struct fw_xfer *xfer;
125 	struct fw_pkt *fp;
126 	int err;
127 
128 	xfer = fw_xfer_alloc_buf(mtype, 0, 4);
129 	if (xfer == NULL)
130 		return (ENOMEM);
131 
132 	xfer->send.spd = spd;
133 	xfer->fc = fc;
134 	xfer->hand = fw_xferwake;
135 
136 	fp = &xfer->send.hdr;
137 	fp->mode.rreqq.tcode = FWTCODE_RREQQ;
138 	fp->mode.rreqq.dst = dst;
139 	fp->mode.rreqq.dest_hi = addr_hi;
140 	fp->mode.rreqq.dest_lo = addr_lo;
141 
142 	err = fw_xfer_request_wait(fc, xfer, 2 * hz);
143 	if (err != 0)
144 		goto out;
145 
146 	if (xfer->resp == 0 &&
147 	    xfer->recv.hdr.mode.rresq.rtcode == FWRCODE_COMPLETE)
148 		*val = ntohl(xfer->recv.hdr.mode.rresq.data);
149 	else
150 		err = EIO;
151 out:
152 	fw_xfer_free_buf(xfer);
153 	return (err);
154 }
155 
156 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)157 fw_write_quadlet(struct firewire_comm *fc, struct malloc_type *mtype,
158     uint16_t dst, uint8_t spd, uint16_t addr_hi, uint32_t addr_lo,
159     uint32_t val)
160 {
161 	struct fw_xfer *xfer;
162 	struct fw_pkt *fp;
163 	int err;
164 
165 	xfer = fw_xfer_alloc_buf(mtype, 0, 0);
166 	if (xfer == NULL)
167 		return (ENOMEM);
168 
169 	xfer->send.spd = spd;
170 	xfer->fc = fc;
171 	xfer->hand = fw_xferwake;
172 
173 	fp = &xfer->send.hdr;
174 	fp->mode.wreqq.tcode = FWTCODE_WREQQ;
175 	fp->mode.wreqq.dst = dst;
176 	fp->mode.wreqq.dest_hi = addr_hi;
177 	fp->mode.wreqq.dest_lo = addr_lo;
178 	fp->mode.wreqq.data = htonl(val);
179 
180 	err = fw_xfer_request_wait(fc, xfer, 2 * hz);
181 	if (err != 0)
182 		goto out;
183 
184 	if (xfer->resp != 0 ||
185 	    xfer->recv.hdr.mode.wres.rtcode != FWRCODE_COMPLETE)
186 		err = EIO;
187 out:
188 	fw_xfer_free_buf(xfer);
189 	return (err);
190 }
191 
192 static __inline void
fw_iso_init_chunks(struct fw_xferq * xferq)193 fw_iso_init_chunks(struct fw_xferq *xferq)
194 {
195 	struct mbuf *m;
196 	int i;
197 
198 	STAILQ_INIT(&xferq->stvalid);
199 	STAILQ_INIT(&xferq->stfree);
200 	STAILQ_INIT(&xferq->stdma);
201 	xferq->stproc = NULL;
202 
203 	for (i = 0; i < xferq->bnchunk; i++) {
204 		m = m_getcl(M_WAITOK, MT_DATA, M_PKTHDR);
205 		xferq->bulkxfer[i].mbuf = m;
206 		m->m_len = m->m_pkthdr.len = m->m_ext.ext_size;
207 		STAILQ_INSERT_TAIL(&xferq->stfree, &xferq->bulkxfer[i], link);
208 	}
209 }
210 
211 static __inline void
fw_iso_free_chunks(struct fw_xferq * xferq,struct malloc_type * mtype)212 fw_iso_free_chunks(struct fw_xferq *xferq, struct malloc_type *mtype)
213 {
214 	int i;
215 
216 	for (i = 0; i < xferq->bnchunk; i++) {
217 		if (xferq->bulkxfer[i].mbuf != NULL)
218 			m_freem(xferq->bulkxfer[i].mbuf);
219 	}
220 	free(xferq->bulkxfer, mtype);
221 	xferq->bulkxfer = NULL;
222 }
223 
224 /*
225  * Dequeue an ISO receive mbuf, replace with a fresh one.
226  * Returns the consumed mbuf, or NULL on error/allocation failure.
227  */
228 static __inline struct mbuf *
fw_iso_dequeue(struct fw_xferq * xferq,struct fw_bulkxfer * sxfer,struct firewire_comm * fc)229 fw_iso_dequeue(struct fw_xferq *xferq, struct fw_bulkxfer *sxfer,
230     struct firewire_comm *fc)
231 {
232 	struct fw_pkt *fp;
233 	struct mbuf *m, *m0;
234 
235 	fp = mtod(sxfer->mbuf, struct fw_pkt *);
236 	if (fc->irx_post != NULL)
237 		fc->irx_post(fc, fp->mode.ld);
238 
239 	m = sxfer->mbuf;
240 
241 	m0 = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR);
242 	if (m0 == NULL) {
243 		/* Allocation failed; recycle the original mbuf. */
244 		STAILQ_INSERT_TAIL(&xferq->stfree, sxfer, link);
245 		return (NULL);
246 	}
247 
248 	m0->m_len = m0->m_pkthdr.len = m0->m_ext.ext_size;
249 	sxfer->mbuf = m0;
250 	STAILQ_INSERT_TAIL(&xferq->stfree, sxfer, link);
251 
252 	if (sxfer->resp != 0) {
253 		m_freem(m);
254 		return (NULL);
255 	}
256 
257 	return (m);
258 }
259 
260 static __inline void
fw_iso_rearm(struct fw_xferq * xferq,struct firewire_comm * fc,int dma_ch)261 fw_iso_rearm(struct fw_xferq *xferq, struct firewire_comm *fc, int dma_ch)
262 {
263 
264 	if (STAILQ_FIRST(&xferq->stfree) != NULL && dma_ch >= 0)
265 		fc->irx_enable(fc, dma_ch);
266 }
267 
268 static __inline void
fw_iso_wait_inactive_locked(struct mtx * mtx,int * active,const char * wmesg)269 fw_iso_wait_inactive_locked(struct mtx *mtx, int *active, const char *wmesg)
270 {
271 
272 	mtx_assert(mtx, MA_OWNED);
273 	while (*active)
274 		msleep(active, mtx, PWAIT, wmesg, hz);
275 }
276 
277 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)278 fw_iso_rearm_done(struct fw_xferq *xferq, struct firewire_comm *fc,
279     struct mtx *mtx, int *active, int *cur_dma_ch, int dma_ch)
280 {
281 
282 	mtx_lock(mtx);
283 	if (*cur_dma_ch == dma_ch)
284 		fw_iso_rearm(xferq, fc, dma_ch);
285 	*active = 0;
286 	wakeup(active);
287 	mtx_unlock(mtx);
288 }
289 
290 #endif /* _DEV_FIREWIRE_FW_HELPERS_H_ */
291