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 LOG_DBG("Transfer ZLP");
91 usb_write(ep, NULL, 0, NULL);
92 }
93 trans->status = 0;
94 goto done;
95 }
96
97 ret = usb_write(ep, trans->buffer, trans->bsize, &bytes);
98 if (ret) {
99 LOG_ERR("Transfer error %d, ep 0x%02x", ret, ep);
100 /* transfer error */
101 trans->status = -EINVAL;
102 goto done;
103 }
104
105 trans->buffer += bytes;
106 trans->bsize -= bytes;
107 trans->tsize += bytes;
108 } else {
109 ret = usb_dc_ep_read_wait(ep, trans->buffer, trans->bsize,
110 &bytes);
111 if (ret) {
112 LOG_ERR("Transfer error %d, ep 0x%02x", ret, ep);
113 /* transfer error */
114 trans->status = -EINVAL;
115 goto done;
116 }
117
118 trans->buffer += bytes;
119 trans->bsize -= bytes;
120 trans->tsize += bytes;
121
122 /* ZLP, short-pkt or buffer full */
123 if (!bytes || (bytes % usb_dc_ep_mps(ep)) || !trans->bsize) {
124 /* transfer complete */
125 trans->status = 0;
126 goto done;
127 }
128
129 /* we expect mote data, clear NAK */
130 usb_dc_ep_read_continue(ep);
131 }
132
133 done:
134 if (trans->status != -EBUSY && trans->cb) { /* Transfer complete */
135 usb_transfer_callback cb = trans->cb;
136 int tsize = trans->tsize;
137 void *priv = trans->priv;
138
139 if (k_is_in_isr()) {
140 /* reschedule completion in thread context */
141 k_work_submit_to_queue(&USB_WORK_Q, &trans->work);
142 return;
143 }
144
145 LOG_DBG("Transfer done, ep 0x%02x, status %d, size %zu",
146 trans->ep, trans->status, trans->tsize);
147
148 trans->cb = NULL;
149 k_sem_give(&trans->sem);
150
151 /* Transfer completion callback */
152 cb(ep, tsize, priv);
153 }
154 }
155
usb_transfer_ep_callback(uint8_t ep,enum usb_dc_ep_cb_status_code status)156 void usb_transfer_ep_callback(uint8_t ep, enum usb_dc_ep_cb_status_code status)
157 {
158 struct usb_transfer_data *trans = usb_ep_get_transfer(ep);
159
160 if (status != USB_DC_EP_DATA_IN && status != USB_DC_EP_DATA_OUT) {
161 return;
162 }
163
164 if (!trans) {
165 if (status == USB_DC_EP_DATA_OUT) {
166 uint32_t bytes;
167 /* In the unlikely case we receive data while no
168 * transfer is ongoing, we have to consume the data
169 * anyway. This is to prevent stucking reception on
170 * other endpoints (e.g dw driver has only one rx-fifo,
171 * so drain it).
172 */
173 do {
174 uint8_t data;
175
176 usb_dc_ep_read_wait(ep, &data, 1, &bytes);
177 } while (bytes);
178
179 LOG_ERR("RX data lost, no transfer");
180 }
181 return;
182 }
183
184 if (!k_is_in_isr() || (status == USB_DC_EP_DATA_OUT)) {
185 /* If we are not in IRQ context, no need to defer work */
186 /* Read (out) needs to be done from ep_callback */
187 usb_transfer_work(&trans->work);
188 } else {
189 k_work_submit_to_queue(&USB_WORK_Q, &trans->work);
190 }
191 }
192
usb_transfer(uint8_t ep,uint8_t * data,size_t dlen,unsigned int flags,usb_transfer_callback cb,void * cb_data)193 int usb_transfer(uint8_t ep, uint8_t *data, size_t dlen, unsigned int flags,
194 usb_transfer_callback cb, void *cb_data)
195 {
196 struct usb_transfer_data *trans = NULL;
197 int key, ret = 0;
198
199 /* Parallel transfer to same endpoint is not supported. */
200 if (usb_transfer_is_busy(ep)) {
201 return -EBUSY;
202 }
203
204 LOG_DBG("Transfer start, ep 0x%02x, data %p, dlen %zd",
205 ep, data, dlen);
206
207 key = irq_lock();
208
209 for (size_t i = 0; i < ARRAY_SIZE(ut_data); i++) {
210 if (!k_sem_take(&ut_data[i].sem, K_NO_WAIT)) {
211 trans = &ut_data[i];
212 break;
213 }
214 }
215
216 if (!trans) {
217 LOG_ERR("No transfer slot available");
218 ret = -ENOMEM;
219 goto done;
220 }
221
222 if (trans->status == -EBUSY) {
223 /* A transfer is already ongoing and not completed */
224 LOG_ERR("A transfer is already ongoing, ep 0x%02x", ep);
225 k_sem_give(&trans->sem);
226 ret = -EBUSY;
227 goto done;
228 }
229
230 /* Configure new transfer */
231 trans->ep = ep;
232 trans->buffer = data;
233 trans->bsize = dlen;
234 trans->tsize = 0;
235 trans->cb = cb;
236 trans->flags = flags;
237 trans->priv = cb_data;
238 trans->status = -EBUSY;
239
240 if (usb_dc_ep_mps(ep) && (dlen % usb_dc_ep_mps(ep))) {
241 /* no need to send ZLP since last packet will be a short one */
242 trans->flags |= USB_TRANS_NO_ZLP;
243 }
244
245 if (flags & USB_TRANS_WRITE) {
246 /* start writing first chunk */
247 k_work_submit_to_queue(&USB_WORK_Q, &trans->work);
248 } else {
249 /* ready to read, clear NAK */
250 ret = usb_dc_ep_read_continue(ep);
251 }
252
253 done:
254 irq_unlock(key);
255 return ret;
256 }
257
usb_cancel_transfer(uint8_t ep)258 void usb_cancel_transfer(uint8_t ep)
259 {
260 struct usb_transfer_data *trans;
261 unsigned int key;
262
263 key = irq_lock();
264
265 trans = usb_ep_get_transfer(ep);
266 if (!trans) {
267 goto done;
268 }
269
270 if (trans->status != -EBUSY) {
271 goto done;
272 }
273
274 trans->status = -ECANCELED;
275 k_work_submit_to_queue(&USB_WORK_Q, &trans->work);
276
277 done:
278 irq_unlock(key);
279 }
280
usb_cancel_transfers(void)281 void usb_cancel_transfers(void)
282 {
283 for (size_t i = 0; i < ARRAY_SIZE(ut_data); i++) {
284 struct usb_transfer_data *trans = &ut_data[i];
285 unsigned int key;
286
287 key = irq_lock();
288
289 if (trans->status == -EBUSY) {
290 trans->status = -ECANCELED;
291 k_work_submit_to_queue(&USB_WORK_Q, &trans->work);
292 LOG_DBG("Cancel transfer for ep: 0x%02x", trans->ep);
293 }
294
295 irq_unlock(key);
296 }
297 }
298
usb_transfer_sync_cb(uint8_t ep,int size,void * priv)299 static void usb_transfer_sync_cb(uint8_t ep, int size, void *priv)
300 {
301 struct usb_transfer_sync_priv *pdata = priv;
302
303 pdata->tsize = size;
304 k_sem_give(&pdata->sem);
305 }
306
usb_transfer_sync(uint8_t ep,uint8_t * data,size_t dlen,unsigned int flags)307 int usb_transfer_sync(uint8_t ep, uint8_t *data, size_t dlen, unsigned int flags)
308 {
309 struct usb_transfer_sync_priv pdata;
310 int ret;
311
312 k_sem_init(&pdata.sem, 0, 1);
313
314 ret = usb_transfer(ep, data, dlen, flags, usb_transfer_sync_cb, &pdata);
315 if (ret) {
316 return ret;
317 }
318
319 /* Semaphore will be released by the transfer completion callback */
320 k_sem_take(&pdata.sem, K_FOREVER);
321
322 return pdata.tsize;
323 }
324
325 /* Init transfer slots */
usb_transfer_init(void)326 int usb_transfer_init(void)
327 {
328 for (size_t i = 0; i < ARRAY_SIZE(ut_data); i++) {
329 k_work_init(&ut_data[i].work, usb_transfer_work);
330 k_sem_init(&ut_data[i].sem, 1, 1);
331 }
332
333 return 0;
334 }
335