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