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