1 /*
2 * Copyright (c) 2018 Linaro
3 * Copyright (c) 2019 PHYTEC Messtechnik GmbH
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 #include <zephyr/kernel.h>
9 #include <zephyr/usb/usb_device.h>
10 #include <zephyr/logging/log.h>
11
12 #include "usb_transfer.h"
13 #include "usb_work_q.h"
14
15 LOG_MODULE_REGISTER(usb_transfer, CONFIG_USB_DEVICE_LOG_LEVEL);
16
17 struct usb_transfer_sync_priv {
18 int tsize;
19 struct k_sem sem;
20 };
21
22 struct usb_transfer_data {
23 /** endpoint associated to the transfer */
24 uint8_t ep;
25 /** Transfer status */
26 int status;
27 /** Transfer read/write buffer */
28 uint8_t *buffer;
29 /** Transfer buffer size */
30 size_t bsize;
31 /** Transferred size */
32 size_t tsize;
33 /** Transfer callback */
34 usb_transfer_callback cb;
35 /** Transfer caller private data */
36 void *priv;
37 /** Transfer synchronization semaphore */
38 struct k_sem sem;
39 /** Transfer read/write work */
40 struct k_work work;
41 /** Transfer flags */
42 unsigned int flags;
43 };
44
45 /** Max number of parallel transfers */
46 static struct usb_transfer_data ut_data[CONFIG_USB_MAX_NUM_TRANSFERS];
47
48 /* Transfer management */
usb_ep_get_transfer(uint8_t ep)49 static struct usb_transfer_data *usb_ep_get_transfer(uint8_t ep)
50 {
51 for (size_t i = 0; i < ARRAY_SIZE(ut_data); i++) {
52 if (ut_data[i].ep == ep && ut_data[i].status != 0) {
53 return &ut_data[i];
54 }
55 }
56
57 return NULL;
58 }
59
usb_transfer_is_busy(uint8_t ep)60 bool usb_transfer_is_busy(uint8_t ep)
61 {
62 struct usb_transfer_data *trans = usb_ep_get_transfer(ep);
63
64 if (trans && trans->status == -EBUSY) {
65 return true;
66 }
67
68 return false;
69 }
70
usb_transfer_work(struct k_work * item)71 static void usb_transfer_work(struct k_work *item)
72 {
73 struct usb_transfer_data *trans;
74 int ret = 0;
75 uint32_t bytes;
76 uint8_t ep;
77
78 trans = CONTAINER_OF(item, struct usb_transfer_data, work);
79 ep = trans->ep;
80
81 if (trans->status != -EBUSY) {
82 /* transfer cancelled or already completed */
83 LOG_DBG("Transfer cancelled or completed, ep 0x%02x", ep);
84 goto done;
85 }
86
87 if (trans->flags & USB_TRANS_WRITE) {
88 if (!trans->bsize) {
89 if (trans->flags & USB_TRANS_NO_ZLP) {
90 trans->status = 0;
91 goto done;
92 }
93
94 /* Host have to read the ZLP just like any other DATA
95 * packet. Set USB_TRANS_NO_ZLP flag so the transfer
96 * will end next time we get ACK from host.
97 */
98 LOG_DBG("Transfer ZLP");
99 trans->flags |= USB_TRANS_NO_ZLP;
100 }
101
102 ret = usb_write(ep, trans->buffer, trans->bsize, &bytes);
103 if (ret) {
104 LOG_ERR("Transfer error %d, ep 0x%02x", ret, ep);
105 /* transfer error */
106 trans->status = -EINVAL;
107 goto done;
108 }
109
110 trans->buffer += bytes;
111 trans->bsize -= bytes;
112 trans->tsize += bytes;
113 } else {
114 ret = usb_dc_ep_read_wait(ep, trans->buffer, trans->bsize,
115 &bytes);
116 if (ret) {
117 LOG_ERR("Transfer error %d, ep 0x%02x", ret, ep);
118 /* transfer error */
119 trans->status = -EINVAL;
120 goto done;
121 }
122
123 trans->buffer += bytes;
124 trans->bsize -= bytes;
125 trans->tsize += bytes;
126
127 /* ZLP, short-pkt or buffer full */
128 if (!bytes || (bytes % usb_dc_ep_mps(ep)) || !trans->bsize) {
129 /* transfer complete */
130 trans->status = 0;
131 goto done;
132 }
133
134 /* we expect mote data, clear NAK */
135 usb_dc_ep_read_continue(ep);
136 }
137
138 done:
139 if (trans->status != -EBUSY) { /* Transfer complete */
140 usb_transfer_callback cb = trans->cb;
141 int tsize = trans->tsize;
142 void *priv = trans->priv;
143
144 if (k_is_in_isr()) {
145 /* reschedule completion in thread context */
146 k_work_submit_to_queue(&USB_WORK_Q, &trans->work);
147 return;
148 }
149
150 LOG_DBG("Transfer done, ep 0x%02x, status %d, size %zu",
151 trans->ep, trans->status, trans->tsize);
152
153 trans->cb = NULL;
154 k_sem_give(&trans->sem);
155
156 /* Transfer completion callback */
157 if (cb) {
158 cb(ep, tsize, priv);
159 }
160 }
161 }
162
usb_transfer_ep_callback(uint8_t ep,enum usb_dc_ep_cb_status_code status)163 void usb_transfer_ep_callback(uint8_t ep, enum usb_dc_ep_cb_status_code status)
164 {
165 struct usb_transfer_data *trans = usb_ep_get_transfer(ep);
166
167 if (status != USB_DC_EP_DATA_IN && status != USB_DC_EP_DATA_OUT) {
168 return;
169 }
170
171 if (!trans) {
172 if (status == USB_DC_EP_DATA_OUT) {
173 uint32_t bytes;
174 /* In the unlikely case we receive data while no
175 * transfer is ongoing, we have to consume the data
176 * anyway. This is to prevent stucking reception on
177 * other endpoints (e.g dw driver has only one rx-fifo,
178 * so drain it).
179 */
180 do {
181 uint8_t data;
182
183 usb_dc_ep_read_wait(ep, &data, 1, &bytes);
184 } while (bytes);
185
186 LOG_ERR("RX data lost, no transfer");
187 }
188 return;
189 }
190
191 if (!k_is_in_isr() || (status == USB_DC_EP_DATA_OUT)) {
192 /* If we are not in IRQ context, no need to defer work */
193 /* Read (out) needs to be done from ep_callback */
194 usb_transfer_work(&trans->work);
195 } else {
196 k_work_submit_to_queue(&USB_WORK_Q, &trans->work);
197 }
198 }
199
usb_transfer(uint8_t ep,uint8_t * data,size_t dlen,unsigned int flags,usb_transfer_callback cb,void * cb_data)200 int usb_transfer(uint8_t ep, uint8_t *data, size_t dlen, unsigned int flags,
201 usb_transfer_callback cb, void *cb_data)
202 {
203 struct usb_transfer_data *trans = NULL;
204 int key, ret = 0;
205
206 /* Parallel transfer to same endpoint is not supported. */
207 if (usb_transfer_is_busy(ep)) {
208 return -EBUSY;
209 }
210
211 LOG_DBG("Transfer start, ep 0x%02x, data %p, dlen %zd",
212 ep, data, dlen);
213
214 key = irq_lock();
215
216 for (size_t i = 0; i < ARRAY_SIZE(ut_data); i++) {
217 if (!k_sem_take(&ut_data[i].sem, K_NO_WAIT)) {
218 trans = &ut_data[i];
219 break;
220 }
221 }
222
223 if (!trans) {
224 LOG_ERR("No transfer slot available");
225 ret = -ENOMEM;
226 goto done;
227 }
228
229 if (trans->status == -EBUSY) {
230 /* A transfer is already ongoing and not completed */
231 LOG_ERR("A transfer is already ongoing, ep 0x%02x", ep);
232 k_sem_give(&trans->sem);
233 ret = -EBUSY;
234 goto done;
235 }
236
237 /* Configure new transfer */
238 trans->ep = ep;
239 trans->buffer = data;
240 trans->bsize = dlen;
241 trans->tsize = 0;
242 trans->cb = cb;
243 trans->flags = flags;
244 trans->priv = cb_data;
245 trans->status = -EBUSY;
246
247 if (usb_dc_ep_mps(ep) && (dlen % usb_dc_ep_mps(ep))) {
248 /* no need to send ZLP since last packet will be a short one */
249 trans->flags |= USB_TRANS_NO_ZLP;
250 }
251
252 if (flags & USB_TRANS_WRITE) {
253 /* start writing first chunk */
254 k_work_submit_to_queue(&USB_WORK_Q, &trans->work);
255 } else {
256 /* ready to read, clear NAK */
257 ret = usb_dc_ep_read_continue(ep);
258 }
259
260 done:
261 irq_unlock(key);
262 return ret;
263 }
264
usb_cancel_transfer(uint8_t ep)265 void usb_cancel_transfer(uint8_t ep)
266 {
267 struct usb_transfer_data *trans;
268 unsigned int key;
269
270 key = irq_lock();
271
272 trans = usb_ep_get_transfer(ep);
273 if (!trans) {
274 goto done;
275 }
276
277 if (trans->status != -EBUSY) {
278 goto done;
279 }
280
281 trans->status = -ECANCELED;
282 k_work_submit_to_queue(&USB_WORK_Q, &trans->work);
283
284 done:
285 irq_unlock(key);
286 }
287
usb_cancel_transfers(void)288 void usb_cancel_transfers(void)
289 {
290 for (size_t i = 0; i < ARRAY_SIZE(ut_data); i++) {
291 struct usb_transfer_data *trans = &ut_data[i];
292 unsigned int key;
293
294 key = irq_lock();
295
296 if (trans->status == -EBUSY) {
297 trans->status = -ECANCELED;
298 k_work_submit_to_queue(&USB_WORK_Q, &trans->work);
299 LOG_DBG("Cancel transfer for ep: 0x%02x", trans->ep);
300 }
301
302 irq_unlock(key);
303 }
304 }
305
usb_transfer_sync_cb(uint8_t ep,int size,void * priv)306 static void usb_transfer_sync_cb(uint8_t ep, int size, void *priv)
307 {
308 struct usb_transfer_sync_priv *pdata = priv;
309
310 pdata->tsize = size;
311 k_sem_give(&pdata->sem);
312 }
313
usb_transfer_sync(uint8_t ep,uint8_t * data,size_t dlen,unsigned int flags)314 int usb_transfer_sync(uint8_t ep, uint8_t *data, size_t dlen, unsigned int flags)
315 {
316 struct usb_transfer_sync_priv pdata;
317 int ret;
318
319 k_sem_init(&pdata.sem, 0, 1);
320
321 ret = usb_transfer(ep, data, dlen, flags, usb_transfer_sync_cb, &pdata);
322 if (ret) {
323 return ret;
324 }
325
326 /* Semaphore will be released by the transfer completion callback */
327 k_sem_take(&pdata.sem, K_FOREVER);
328
329 return pdata.tsize;
330 }
331
332 /* Init transfer slots */
usb_transfer_init(void)333 int usb_transfer_init(void)
334 {
335 for (size_t i = 0; i < ARRAY_SIZE(ut_data); i++) {
336 k_work_init(&ut_data[i].work, usb_transfer_work);
337 k_sem_init(&ut_data[i].sem, 1, 1);
338 }
339
340 return 0;
341 }
342