diff options
Diffstat (limited to 'lib/chibios-contrib/os/hal/src/usbh')
-rw-r--r-- | lib/chibios-contrib/os/hal/src/usbh/TODO.txt | 21 | ||||
-rw-r--r-- | lib/chibios-contrib/os/hal/src/usbh/hal_usbh_aoa.c | 666 | ||||
-rw-r--r-- | lib/chibios-contrib/os/hal/src/usbh/hal_usbh_debug.c | 219 | ||||
-rw-r--r-- | lib/chibios-contrib/os/hal/src/usbh/hal_usbh_desciter.c | 161 | ||||
-rw-r--r-- | lib/chibios-contrib/os/hal/src/usbh/hal_usbh_ftdi.c | 710 | ||||
-rw-r--r-- | lib/chibios-contrib/os/hal/src/usbh/hal_usbh_hid.c | 311 | ||||
-rw-r--r-- | lib/chibios-contrib/os/hal/src/usbh/hal_usbh_hub.c | 279 | ||||
-rw-r--r-- | lib/chibios-contrib/os/hal/src/usbh/hal_usbh_msd.c | 952 | ||||
-rw-r--r-- | lib/chibios-contrib/os/hal/src/usbh/hal_usbh_uvc.c | 718 |
9 files changed, 4037 insertions, 0 deletions
diff --git a/lib/chibios-contrib/os/hal/src/usbh/TODO.txt b/lib/chibios-contrib/os/hal/src/usbh/TODO.txt new file mode 100644 index 000000000..87269bee1 --- /dev/null +++ b/lib/chibios-contrib/os/hal/src/usbh/TODO.txt | |||
@@ -0,0 +1,21 @@ | |||
1 | In decreasing order of priority: | ||
2 | |||
3 | Bugs: | ||
4 | - Synchronization on driver unload between usbhMainLoop and driver APIs | ||
5 | - MSD: ok | ||
6 | - AOA: not done | ||
7 | - HUB: ok | ||
8 | - FTDI: not done | ||
9 | - HID: ok | ||
10 | - UVC: not done | ||
11 | |||
12 | |||
13 | Enhancements: | ||
14 | - Way to return error from the load() functions in order to stop the enumeration process | ||
15 | - Event sources from the low-level driver, in order to know when to call usbhMainLoop (from the low-level driver and from the HUB driver status callback) | ||
16 | - Possibility of internal main loop | ||
17 | - Linked list for drivers for dynamic registration | ||
18 | - A way to automate matching (similar to linux) | ||
19 | - Hooks to override driver loading and to inform the user of problems | ||
20 | - for STM32 LLD: think of a way to prevent Bulk IN NAK interrupt flood. | ||
21 | - Integrate VBUS power switching functionality to the API. | ||
diff --git a/lib/chibios-contrib/os/hal/src/usbh/hal_usbh_aoa.c b/lib/chibios-contrib/os/hal/src/usbh/hal_usbh_aoa.c new file mode 100644 index 000000000..77ac4e733 --- /dev/null +++ b/lib/chibios-contrib/os/hal/src/usbh/hal_usbh_aoa.c | |||
@@ -0,0 +1,666 @@ | |||
1 | /* | ||
2 | ChibiOS - Copyright (C) 2006..2017 Giovanni Di Sirio | ||
3 | Copyright (C) 2015..2019 Diego Ismirlian, (dismirlian(at)google's mail) | ||
4 | |||
5 | Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | you may not use this file except in compliance with the License. | ||
7 | You may obtain a copy of the License at | ||
8 | |||
9 | http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | |||
11 | Unless required by applicable law or agreed to in writing, software | ||
12 | distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | See the License for the specific language governing permissions and | ||
15 | limitations under the License. | ||
16 | */ | ||
17 | |||
18 | #include "hal.h" | ||
19 | |||
20 | #if HAL_USBH_USE_AOA | ||
21 | |||
22 | #if !HAL_USE_USBH | ||
23 | #error "USBHAOA needs USBH" | ||
24 | #endif | ||
25 | |||
26 | #include <string.h> | ||
27 | #include "usbh/dev/aoa.h" | ||
28 | #include "usbh/internal.h" | ||
29 | |||
30 | //#pragma GCC optimize("Og") | ||
31 | |||
32 | #define _USBH_DEBUG_HELPER_CLASS_DRIVER container_of(aoacp, USBHAOADriver, channel) | ||
33 | #define _USBH_DEBUG_HELPER_ENABLE_TRACE USBHAOA_DEBUG_ENABLE_TRACE | ||
34 | #define _USBH_DEBUG_HELPER_ENABLE_INFO USBHAOA_DEBUG_ENABLE_INFO | ||
35 | #define _USBH_DEBUG_HELPER_ENABLE_WARNINGS USBHAOA_DEBUG_ENABLE_WARNINGS | ||
36 | #define _USBH_DEBUG_HELPER_ENABLE_ERRORS USBHAOA_DEBUG_ENABLE_ERRORS | ||
37 | #include "usbh/debug_helpers.h" | ||
38 | |||
39 | /*===========================================================================*/ | ||
40 | /* Constants */ | ||
41 | /*===========================================================================*/ | ||
42 | |||
43 | #if !defined(HAL_USBHAOA_DEFAULT_MANUFACTURER) | ||
44 | #define HAL_USBHAOA_DEFAULT_MANUFACTURER "ChibiOS" | ||
45 | #endif | ||
46 | |||
47 | #if !defined(HAL_USBHAOA_DEFAULT_MODEL) | ||
48 | #define HAL_USBHAOA_DEFAULT_MODEL "USBH AOA Driver" | ||
49 | #endif | ||
50 | |||
51 | #if !defined(HAL_USBHAOA_DEFAULT_DESCRIPTION) | ||
52 | #define HAL_USBHAOA_DEFAULT_DESCRIPTION "ChibiOS USBH AOA Driver" | ||
53 | #endif | ||
54 | |||
55 | #if !defined(HAL_USBHAOA_DEFAULT_VERSION) | ||
56 | #define HAL_USBHAOA_DEFAULT_VERSION CH_KERNEL_VERSION | ||
57 | #endif | ||
58 | |||
59 | #if !defined(HAL_USBHAOA_DEFAULT_URI) | ||
60 | #define HAL_USBHAOA_DEFAULT_URI NULL | ||
61 | #endif | ||
62 | |||
63 | #if !defined(HAL_USBHAOA_DEFAULT_SERIAL) | ||
64 | #define HAL_USBHAOA_DEFAULT_SERIAL NULL | ||
65 | #endif | ||
66 | |||
67 | #if !defined(HAL_USBHAOA_DEFAULT_AUDIO_MODE) | ||
68 | #define HAL_USBHAOA_DEFAULT_AUDIO_MODE USBHAOA_AUDIO_MODE_DISABLED | ||
69 | #endif | ||
70 | |||
71 | #define AOA_GOOGLE_VID 0x18D1 | ||
72 | #define AOA_GOOGLE_PID_ACCESSORY 0x2D00 | ||
73 | #define AOA_GOOGLE_PID_ACCESSORY_ABD 0x2D01 | ||
74 | #define AOA_GOOGLE_PID_AUDIO 0x2D02 | ||
75 | #define AOA_GOOGLE_PID_AUDIO_ABD 0x2D03 | ||
76 | #define AOA_GOOGLE_PID_ACCESSORY_AUDIO 0x2D04 | ||
77 | #define AOA_GOOGLE_PID_ACCESSORY_AUDIO_ABD 0x2D05 | ||
78 | |||
79 | #define AOA_ACCESSORY_GET_PROTOCOL 51 | ||
80 | #define AOA_ACCESSORY_SEND_STRING 52 | ||
81 | #define AOA_ACCESSORY_START 53 | ||
82 | |||
83 | #define AOA_SET_AUDIO_MODE 58 | ||
84 | |||
85 | static bool _get_protocol(usbh_device_t *dev, uint16_t *protocol); | ||
86 | static bool _accessory_start(usbh_device_t *dev); | ||
87 | static bool _set_audio_mode(usbh_device_t *dev, uint16_t mode); | ||
88 | static bool _send_string(usbh_device_t *dev, uint8_t index, const char *string); | ||
89 | |||
90 | |||
91 | static void _stop_channelS(USBHAOAChannel *aoacp); | ||
92 | |||
93 | /*===========================================================================*/ | ||
94 | /* USB Class driver loader for AOA */ | ||
95 | /*===========================================================================*/ | ||
96 | USBHAOADriver USBHAOAD[HAL_USBHAOA_MAX_INSTANCES]; | ||
97 | |||
98 | static usbh_baseclassdriver_t *_aoa_load(usbh_device_t *dev, const uint8_t *descriptor, uint16_t rem); | ||
99 | static void _aoa_unload(usbh_baseclassdriver_t *drv); | ||
100 | static void _aoa_init(void); | ||
101 | |||
102 | static const usbh_classdriver_vmt_t class_driver_vmt = { | ||
103 | _aoa_init, | ||
104 | _aoa_load, | ||
105 | _aoa_unload | ||
106 | }; | ||
107 | |||
108 | const usbh_classdriverinfo_t usbhaoaClassDriverInfo = { | ||
109 | "AOA", &class_driver_vmt | ||
110 | }; | ||
111 | |||
112 | #if defined(HAL_USBHAOA_FILTER_CALLBACK) | ||
113 | extern usbhaoa_filter_callback_t HAL_USBHAOA_FILTER_CALLBACK; | ||
114 | #endif | ||
115 | |||
116 | static usbh_baseclassdriver_t *_aoa_load(usbh_device_t *dev, const uint8_t *descriptor, uint16_t rem) { | ||
117 | int i; | ||
118 | USBHAOADriver *aoap; | ||
119 | |||
120 | if (dev->devDesc.idVendor != AOA_GOOGLE_VID) { | ||
121 | uint16_t protocol; | ||
122 | static USBHAOAConfig config = { | ||
123 | { | ||
124 | HAL_USBHAOA_DEFAULT_MANUFACTURER, | ||
125 | HAL_USBHAOA_DEFAULT_MODEL, | ||
126 | HAL_USBHAOA_DEFAULT_DESCRIPTION, | ||
127 | HAL_USBHAOA_DEFAULT_VERSION, | ||
128 | HAL_USBHAOA_DEFAULT_URI, | ||
129 | HAL_USBHAOA_DEFAULT_SERIAL | ||
130 | }, | ||
131 | |||
132 | { | ||
133 | HAL_USBHAOA_DEFAULT_AUDIO_MODE, | ||
134 | } | ||
135 | }; | ||
136 | |||
137 | if (descriptor[1] != USBH_DT_DEVICE) { | ||
138 | udevinfo("AOA: Won't try to detect Android device at interface level"); | ||
139 | return NULL; | ||
140 | } | ||
141 | |||
142 | udevinfo("AOA: Unrecognized VID"); | ||
143 | |||
144 | #if defined(HAL_USBHAOA_FILTER_CALLBACK) | ||
145 | if (!HAL_USBHAOA_FILTER_CALLBACK(dev, descriptor, rem, &config)) { | ||
146 | return NULL; | ||
147 | } | ||
148 | #endif | ||
149 | |||
150 | udevinfo("AOA: Try if it's an Android device"); | ||
151 | if (_get_protocol(dev, &protocol) != HAL_SUCCESS) { | ||
152 | udevinfo("AOA: not an Android device"); | ||
153 | return NULL; | ||
154 | } | ||
155 | udevinfof("AOA: Possible Android device found (protocol=%d)", protocol); | ||
156 | |||
157 | if (config.channel.manufacturer != NULL) { | ||
158 | if ((_send_string(dev, USBHAOA_ACCESSORY_STRING_MANUFACTURER, config.channel.manufacturer) != HAL_SUCCESS) | ||
159 | || (_send_string(dev, USBHAOA_ACCESSORY_STRING_MODEL, config.channel.model) != HAL_SUCCESS) | ||
160 | || (_send_string(dev, USBHAOA_ACCESSORY_STRING_DESCRIPTION, config.channel.description) != HAL_SUCCESS) | ||
161 | || (_send_string(dev, USBHAOA_ACCESSORY_STRING_VERSION, config.channel.version) != HAL_SUCCESS) | ||
162 | || (_send_string(dev, USBHAOA_ACCESSORY_STRING_URI, config.channel.uri) != HAL_SUCCESS) | ||
163 | || (_send_string(dev, USBHAOA_ACCESSORY_STRING_SERIAL, config.channel.serial) != HAL_SUCCESS)) { | ||
164 | udeverr("AOA: Can't send string; abort start"); | ||
165 | return NULL; | ||
166 | } | ||
167 | } | ||
168 | |||
169 | if (protocol > 1) { | ||
170 | if (_set_audio_mode(dev, (uint16_t)(config.audio.mode)) != HAL_SUCCESS) { | ||
171 | udeverr("AOA: Can't set audio mode; abort channel start"); | ||
172 | return NULL; | ||
173 | } | ||
174 | } | ||
175 | |||
176 | if (_accessory_start(dev) != HAL_SUCCESS) { | ||
177 | udeverr("AOA: Can't start accessory; abort channel start"); | ||
178 | } else { | ||
179 | udevinfo("AOA: Accessory started"); | ||
180 | } | ||
181 | |||
182 | return NULL; | ||
183 | } | ||
184 | |||
185 | /* AOAv2: | ||
186 | 0x2D00 accessory Provides two bulk endpoints for communicating with an Android application. | ||
187 | 0x2D01 accessory + adb For debugging purposes during accessory development. Available only if the user has enabled USB Debugging in the Android device settings. | ||
188 | 0x2D02 audio For streaming audio from an Android device to an accessory. | ||
189 | 0x2D03 audio + adb | ||
190 | 0x2D04 accessory + audio | ||
191 | 0x2D05 accessory + audio + adb | ||
192 | */ | ||
193 | |||
194 | switch (dev->devDesc.idProduct) { | ||
195 | case AOA_GOOGLE_PID_ACCESSORY: | ||
196 | case AOA_GOOGLE_PID_ACCESSORY_ABD: | ||
197 | // case AOA_GOOGLE_PID_AUDIO: | ||
198 | // case AOA_GOOGLE_PID_AUDIO_ABD: | ||
199 | case AOA_GOOGLE_PID_ACCESSORY_AUDIO: | ||
200 | case AOA_GOOGLE_PID_ACCESSORY_AUDIO_ABD: | ||
201 | break; | ||
202 | default: | ||
203 | udeverr("AOA: Unrecognized PID"); | ||
204 | return NULL; | ||
205 | } | ||
206 | |||
207 | const usbh_interface_descriptor_t * const ifdesc = (const usbh_interface_descriptor_t *)descriptor; | ||
208 | if ((_usbh_match_descriptor(descriptor, rem, USBH_DT_INTERFACE, 0xFF, 0xFF, 0x00) != HAL_SUCCESS) | ||
209 | || (ifdesc->bNumEndpoints < 2)) { | ||
210 | udeverr("AOA: This IF is not the Accessory IF"); | ||
211 | return NULL; | ||
212 | } | ||
213 | |||
214 | udevinfof("AOA: Found Accessory Interface #%d", ifdesc->bInterfaceNumber); | ||
215 | |||
216 | for (i = 0; i < HAL_USBHAOA_MAX_INSTANCES; i++) { | ||
217 | if (USBHAOAD[i].dev == NULL) { | ||
218 | aoap = &USBHAOAD[i]; | ||
219 | goto alloc_ok; | ||
220 | } | ||
221 | } | ||
222 | |||
223 | udevwarn("AOA: Can't alloc driver"); | ||
224 | |||
225 | /* can't alloc */ | ||
226 | return NULL; | ||
227 | |||
228 | alloc_ok: | ||
229 | /* initialize the driver's variables */ | ||
230 | usbhEPSetName(&dev->ctrl, "AOA[CTRL]"); | ||
231 | aoap->state = USBHAOA_STATE_ACTIVE; | ||
232 | |||
233 | generic_iterator_t iep; | ||
234 | if_iterator_t iif; | ||
235 | iif.iad = 0; | ||
236 | iif.curr = descriptor; | ||
237 | iif.rem = rem; | ||
238 | |||
239 | aoap->channel.epin.status = USBH_EPSTATUS_UNINITIALIZED; | ||
240 | aoap->channel.epout.status = USBH_EPSTATUS_UNINITIALIZED; | ||
241 | |||
242 | for (ep_iter_init(&iep, &iif); iep.valid; ep_iter_next(&iep)) { | ||
243 | const usbh_endpoint_descriptor_t *const epdesc = ep_get(&iep); | ||
244 | if ((epdesc->bEndpointAddress & 0x80) && (epdesc->bmAttributes == USBH_EPTYPE_BULK)) { | ||
245 | udevinfof("AOA: BULK IN endpoint found: bEndpointAddress=%02x", epdesc->bEndpointAddress); | ||
246 | usbhEPObjectInit(&aoap->channel.epin, dev, epdesc); | ||
247 | usbhEPSetName(&aoap->channel.epin, "AOA[BIN ]"); | ||
248 | } else if (((epdesc->bEndpointAddress & 0x80) == 0) | ||
249 | && (epdesc->bmAttributes == USBH_EPTYPE_BULK)) { | ||
250 | udevinfof("AOA: BULK OUT endpoint found: bEndpointAddress=%02x", epdesc->bEndpointAddress); | ||
251 | usbhEPObjectInit(&aoap->channel.epout, dev, epdesc); | ||
252 | usbhEPSetName(&aoap->channel.epout, "AOA[BOUT]"); | ||
253 | } else { | ||
254 | udevinfof("AOA: unsupported endpoint found: bEndpointAddress=%02x, bmAttributes=%02x", | ||
255 | epdesc->bEndpointAddress, epdesc->bmAttributes); | ||
256 | } | ||
257 | } | ||
258 | |||
259 | if ((aoap->channel.epin.status != USBH_EPSTATUS_CLOSED) | ||
260 | || (aoap->channel.epout.status != USBH_EPSTATUS_CLOSED)) { | ||
261 | udevwarn("AOA: Couldn't find endpoints"); | ||
262 | aoap->state = USBHAOA_STATE_STOP; | ||
263 | return NULL; | ||
264 | } | ||
265 | |||
266 | aoap->state = USBHAOA_STATE_READY; | ||
267 | aoap->channel.state = USBHAOA_CHANNEL_STATE_ACTIVE; | ||
268 | udevwarn("AOA: Ready"); | ||
269 | return (usbh_baseclassdriver_t *)aoap; | ||
270 | } | ||
271 | |||
272 | static void _aoa_unload(usbh_baseclassdriver_t *drv) { | ||
273 | osalDbgCheck(drv != NULL); | ||
274 | USBHAOADriver *const aoap = (USBHAOADriver *)drv; | ||
275 | osalSysLock(); | ||
276 | _stop_channelS(&aoap->channel); | ||
277 | aoap->channel.state = USBHAOA_CHANNEL_STATE_STOP; | ||
278 | aoap->state = USBHAOA_STATE_STOP; | ||
279 | osalSysUnlock(); | ||
280 | } | ||
281 | |||
282 | /* ------------------------------------ */ | ||
283 | /* Accessory data channel */ | ||
284 | /* ------------------------------------ */ | ||
285 | |||
286 | static void _submitOutI(USBHAOAChannel *aoacp, uint32_t len) { | ||
287 | uclassdrvdbgf("AOA: Submit OUT %d", len); | ||
288 | aoacp->oq_urb.requestedLength = len; | ||
289 | usbhURBObjectResetI(&aoacp->oq_urb); | ||
290 | usbhURBSubmitI(&aoacp->oq_urb); | ||
291 | } | ||
292 | |||
293 | static void _out_cb(usbh_urb_t *urb) { | ||
294 | USBHAOAChannel *const aoacp = (USBHAOAChannel *)urb->userData; | ||
295 | switch (urb->status) { | ||
296 | case USBH_URBSTATUS_OK: | ||
297 | aoacp->oq_ptr = aoacp->oq_buff; | ||
298 | aoacp->oq_counter = 64; | ||
299 | chThdDequeueNextI(&aoacp->oq_waiting, Q_OK); | ||
300 | chnAddFlagsI(aoacp, CHN_OUTPUT_EMPTY | CHN_TRANSMISSION_END); | ||
301 | return; | ||
302 | case USBH_URBSTATUS_DISCONNECTED: | ||
303 | uclassdrvwarn("AOA: URB OUT disconnected"); | ||
304 | chThdDequeueAllI(&aoacp->oq_waiting, Q_RESET); | ||
305 | chnAddFlagsI(aoacp, CHN_OUTPUT_EMPTY); | ||
306 | return; | ||
307 | default: | ||
308 | uclassdrverrf("AOA: URB OUT status unexpected = %d", urb->status); | ||
309 | break; | ||
310 | } | ||
311 | usbhURBObjectResetI(&aoacp->oq_urb); | ||
312 | usbhURBSubmitI(&aoacp->oq_urb); | ||
313 | } | ||
314 | |||
315 | static size_t _write_timeout(USBHAOAChannel *aoacp, const uint8_t *bp, | ||
316 | size_t n, systime_t timeout) { | ||
317 | chDbgCheck(n > 0U); | ||
318 | |||
319 | size_t w = 0; | ||
320 | osalSysLock(); | ||
321 | while (true) { | ||
322 | if (aoacp->state != USBHAOA_CHANNEL_STATE_READY) { | ||
323 | osalSysUnlock(); | ||
324 | return w; | ||
325 | } | ||
326 | while (usbhURBIsBusy(&aoacp->oq_urb)) { | ||
327 | if (chThdEnqueueTimeoutS(&aoacp->oq_waiting, timeout) != Q_OK) { | ||
328 | osalSysUnlock(); | ||
329 | return w; | ||
330 | } | ||
331 | } | ||
332 | |||
333 | *aoacp->oq_ptr++ = *bp++; | ||
334 | if (--aoacp->oq_counter == 0) { | ||
335 | _submitOutI(aoacp, 64); | ||
336 | osalOsRescheduleS(); | ||
337 | } | ||
338 | osalSysUnlock(); /* Gives a preemption chance in a controlled point.*/ | ||
339 | |||
340 | w++; | ||
341 | if (--n == 0U) | ||
342 | return w; | ||
343 | |||
344 | osalSysLock(); | ||
345 | } | ||
346 | } | ||
347 | |||
348 | static msg_t _put_timeout(USBHAOAChannel *aoacp, uint8_t b, systime_t timeout) { | ||
349 | |||
350 | osalSysLock(); | ||
351 | if (aoacp->state != USBHAOA_CHANNEL_STATE_READY) { | ||
352 | osalSysUnlock(); | ||
353 | return Q_RESET; | ||
354 | } | ||
355 | |||
356 | while (usbhURBIsBusy(&aoacp->oq_urb)) { | ||
357 | msg_t msg = chThdEnqueueTimeoutS(&aoacp->oq_waiting, timeout); | ||
358 | if (msg < Q_OK) { | ||
359 | osalSysUnlock(); | ||
360 | return msg; | ||
361 | } | ||
362 | } | ||
363 | |||
364 | *aoacp->oq_ptr++ = b; | ||
365 | if (--aoacp->oq_counter == 0) { | ||
366 | _submitOutI(aoacp, 64); | ||
367 | osalOsRescheduleS(); | ||
368 | } | ||
369 | osalSysUnlock(); | ||
370 | return Q_OK; | ||
371 | } | ||
372 | |||
373 | static size_t _write(USBHAOAChannel *aoacp, const uint8_t *bp, size_t n) { | ||
374 | return _write_timeout(aoacp, bp, n, TIME_INFINITE); | ||
375 | } | ||
376 | |||
377 | static msg_t _put(USBHAOAChannel *aoacp, uint8_t b) { | ||
378 | return _put_timeout(aoacp, b, TIME_INFINITE); | ||
379 | } | ||
380 | |||
381 | static void _submitInI(USBHAOAChannel *aoacp) { | ||
382 | uclassdrvdbg("AOA: Submit IN"); | ||
383 | usbhURBObjectResetI(&aoacp->iq_urb); | ||
384 | usbhURBSubmitI(&aoacp->iq_urb); | ||
385 | } | ||
386 | |||
387 | static void _in_cb(usbh_urb_t *urb) { | ||
388 | USBHAOAChannel *const aoacp = (USBHAOAChannel *)urb->userData; | ||
389 | switch (urb->status) { | ||
390 | case USBH_URBSTATUS_OK: | ||
391 | if (urb->actualLength == 0) { | ||
392 | uurbdbgf("AOA: URB IN no data"); | ||
393 | } else { | ||
394 | uurbdbgf("AOA: URB IN data len=%d", urb->actualLength); | ||
395 | aoacp->iq_ptr = aoacp->iq_buff; | ||
396 | aoacp->iq_counter = urb->actualLength; | ||
397 | chThdDequeueNextI(&aoacp->iq_waiting, Q_OK); | ||
398 | chnAddFlagsI(aoacp, CHN_INPUT_AVAILABLE); | ||
399 | } | ||
400 | break; | ||
401 | case USBH_URBSTATUS_DISCONNECTED: | ||
402 | uurbwarn("AOA: URB IN disconnected"); | ||
403 | chThdDequeueAllI(&aoacp->iq_waiting, Q_RESET); | ||
404 | chnAddFlagsI(aoacp, CHN_DISCONNECTED); | ||
405 | aoacp->state = USBHAOA_CHANNEL_STATE_ACTIVE; | ||
406 | container_of(aoacp, USBHAOADriver, channel)->state = USBHAOA_STATE_ACTIVE; | ||
407 | break; | ||
408 | default: | ||
409 | uurberrf("AOA: URB IN status unexpected = %d", urb->status); | ||
410 | _submitInI(aoacp); | ||
411 | break; | ||
412 | } | ||
413 | } | ||
414 | |||
415 | static size_t _read_timeout(USBHAOAChannel *aoacp, uint8_t *bp, | ||
416 | size_t n, systime_t timeout) { | ||
417 | size_t r = 0; | ||
418 | |||
419 | chDbgCheck(n > 0U); | ||
420 | |||
421 | osalSysLock(); | ||
422 | while (true) { | ||
423 | if (aoacp->state != USBHAOA_CHANNEL_STATE_READY) { | ||
424 | osalSysUnlock(); | ||
425 | return r; | ||
426 | } | ||
427 | while (aoacp->iq_counter == 0) { | ||
428 | if (!usbhURBIsBusy(&aoacp->iq_urb)) | ||
429 | _submitInI(aoacp); | ||
430 | if (chThdEnqueueTimeoutS(&aoacp->iq_waiting, timeout) != Q_OK) { | ||
431 | osalSysUnlock(); | ||
432 | return r; | ||
433 | } | ||
434 | } | ||
435 | *bp++ = *aoacp->iq_ptr++; | ||
436 | if (--aoacp->iq_counter == 0) { | ||
437 | _submitInI(aoacp); | ||
438 | osalOsRescheduleS(); | ||
439 | } | ||
440 | osalSysUnlock(); | ||
441 | |||
442 | r++; | ||
443 | if (--n == 0U) | ||
444 | return r; | ||
445 | |||
446 | osalSysLock(); | ||
447 | } | ||
448 | } | ||
449 | |||
450 | static msg_t _get_timeout(USBHAOAChannel *aoacp, systime_t timeout) { | ||
451 | uint8_t b; | ||
452 | |||
453 | osalSysLock(); | ||
454 | if (aoacp->state != USBHAOA_CHANNEL_STATE_READY) { | ||
455 | osalSysUnlock(); | ||
456 | return Q_RESET; | ||
457 | } | ||
458 | while (aoacp->iq_counter == 0) { | ||
459 | if (!usbhURBIsBusy(&aoacp->iq_urb)) | ||
460 | _submitInI(aoacp); | ||
461 | msg_t msg = chThdEnqueueTimeoutS(&aoacp->iq_waiting, timeout); | ||
462 | if (msg < Q_OK) { | ||
463 | osalSysUnlock(); | ||
464 | return msg; | ||
465 | } | ||
466 | } | ||
467 | b = *aoacp->iq_ptr++; | ||
468 | if (--aoacp->iq_counter == 0) { | ||
469 | _submitInI(aoacp); | ||
470 | osalOsRescheduleS(); | ||
471 | } | ||
472 | osalSysUnlock(); | ||
473 | |||
474 | return (msg_t)b; | ||
475 | } | ||
476 | |||
477 | static msg_t _get(USBHAOAChannel *aoacp) { | ||
478 | return _get_timeout(aoacp, TIME_INFINITE); | ||
479 | } | ||
480 | |||
481 | static size_t _read(USBHAOAChannel *aoacp, uint8_t *bp, size_t n) { | ||
482 | return _read_timeout(aoacp, bp, n, TIME_INFINITE); | ||
483 | } | ||
484 | |||
485 | static msg_t _ctl(USBHAOAChannel *ftdipp, unsigned int operation, void *arg) { | ||
486 | (void)ftdipp; | ||
487 | (void)operation; | ||
488 | (void)arg; | ||
489 | return MSG_OK; | ||
490 | } | ||
491 | |||
492 | static const struct AOADriverVMT async_channel_vmt = { | ||
493 | (size_t)0, | ||
494 | (size_t (*)(void *, const uint8_t *, size_t))_write, | ||
495 | (size_t (*)(void *, uint8_t *, size_t))_read, | ||
496 | (msg_t (*)(void *, uint8_t))_put, | ||
497 | (msg_t (*)(void *))_get, | ||
498 | (msg_t (*)(void *, uint8_t, systime_t))_put_timeout, | ||
499 | (msg_t (*)(void *, systime_t))_get_timeout, | ||
500 | (size_t (*)(void *, const uint8_t *, size_t, systime_t))_write_timeout, | ||
501 | (size_t (*)(void *, uint8_t *, size_t, systime_t))_read_timeout, | ||
502 | (msg_t (*)(void *, unsigned int, void *))_ctl | ||
503 | }; | ||
504 | |||
505 | static void _stop_channelS(USBHAOAChannel *aoacp) { | ||
506 | if (aoacp->state != USBHAOA_CHANNEL_STATE_READY) | ||
507 | return; | ||
508 | uclassdrvwarn("AOA: Stop channel"); | ||
509 | chVTResetI(&aoacp->vt); | ||
510 | usbhEPCloseS(&aoacp->epin); | ||
511 | usbhEPCloseS(&aoacp->epout); | ||
512 | chThdDequeueAllI(&aoacp->iq_waiting, Q_RESET); | ||
513 | chThdDequeueAllI(&aoacp->oq_waiting, Q_RESET); | ||
514 | chnAddFlagsI(aoacp, CHN_DISCONNECTED); | ||
515 | aoacp->state = USBHAOA_CHANNEL_STATE_ACTIVE; | ||
516 | osalOsRescheduleS(); | ||
517 | } | ||
518 | |||
519 | static void _vt(void *p) { | ||
520 | USBHAOAChannel *const aoacp = (USBHAOAChannel *)p; | ||
521 | osalSysLockFromISR(); | ||
522 | if (aoacp->state == USBHAOA_CHANNEL_STATE_READY) { | ||
523 | uint32_t len = aoacp->oq_ptr - aoacp->oq_buff; | ||
524 | if (len && !usbhURBIsBusy(&aoacp->oq_urb)) { | ||
525 | _submitOutI(aoacp, len); | ||
526 | } | ||
527 | if ((aoacp->iq_counter == 0) && !usbhURBIsBusy(&aoacp->iq_urb)) { | ||
528 | _submitInI(aoacp); | ||
529 | } | ||
530 | chVTSetI(&aoacp->vt, OSAL_MS2I(16), _vt, aoacp); | ||
531 | } | ||
532 | osalSysUnlockFromISR(); | ||
533 | } | ||
534 | |||
535 | void usbhaoaChannelStart(USBHAOADriver *aoap) { | ||
536 | |||
537 | osalDbgCheck(aoap); | ||
538 | |||
539 | USBHAOAChannel *const aoacp = (USBHAOAChannel *)&aoap->channel; | ||
540 | |||
541 | osalDbgCheck(aoap->state == USBHAOA_STATE_READY); | ||
542 | |||
543 | osalDbgCheck((aoacp->state == USBHAOA_CHANNEL_STATE_ACTIVE) | ||
544 | || (aoacp->state == USBHAOA_CHANNEL_STATE_READY)); | ||
545 | |||
546 | if (aoacp->state == USBHAOA_CHANNEL_STATE_READY) | ||
547 | return; | ||
548 | |||
549 | usbhURBObjectInit(&aoacp->oq_urb, &aoacp->epout, _out_cb, aoacp, aoacp->oq_buff, 0); | ||
550 | chThdQueueObjectInit(&aoacp->oq_waiting); | ||
551 | aoacp->oq_counter = 64; | ||
552 | aoacp->oq_ptr = aoacp->oq_buff; | ||
553 | usbhEPOpen(&aoacp->epout); | ||
554 | |||
555 | usbhURBObjectInit(&aoacp->iq_urb, &aoacp->epin, _in_cb, aoacp, aoacp->iq_buff, 64); | ||
556 | chThdQueueObjectInit(&aoacp->iq_waiting); | ||
557 | aoacp->iq_counter = 0; | ||
558 | aoacp->iq_ptr = aoacp->iq_buff; | ||
559 | usbhEPOpen(&aoacp->epin); | ||
560 | usbhURBSubmit(&aoacp->iq_urb); | ||
561 | |||
562 | chVTObjectInit(&aoacp->vt); | ||
563 | chVTSet(&aoacp->vt, OSAL_MS2I(16), _vt, aoacp); | ||
564 | |||
565 | aoacp->state = USBHAOA_CHANNEL_STATE_READY; | ||
566 | |||
567 | osalEventBroadcastFlags(&aoacp->event, CHN_CONNECTED | CHN_OUTPUT_EMPTY); | ||
568 | } | ||
569 | |||
570 | void usbhaoaChannelStop(USBHAOADriver *aoap) { | ||
571 | osalDbgCheck((aoap->channel.state == USBHAOA_CHANNEL_STATE_ACTIVE) | ||
572 | || (aoap->channel.state == USBHAOA_CHANNEL_STATE_READY)); | ||
573 | osalSysLock(); | ||
574 | _stop_channelS(&aoap->channel); | ||
575 | osalSysUnlock(); | ||
576 | } | ||
577 | |||
578 | /* ------------------------------------ */ | ||
579 | /* General AOA functions */ | ||
580 | /* ------------------------------------ */ | ||
581 | static bool _get_protocol(usbh_device_t *dev, uint16_t *protocol) { | ||
582 | USBH_DEFINE_BUFFER(uint16_t proto); | ||
583 | |||
584 | usbh_urbstatus_t ret = usbhControlRequest(dev, | ||
585 | USBH_REQTYPE_DIR_IN | USBH_REQTYPE_TYPE_VENDOR | USBH_REQTYPE_RECIP_DEVICE, | ||
586 | AOA_ACCESSORY_GET_PROTOCOL, | ||
587 | 0, | ||
588 | 0, | ||
589 | 2, | ||
590 | (uint8_t *)&proto); | ||
591 | |||
592 | if ((ret != USBH_URBSTATUS_OK) || (proto > 2)) | ||
593 | return HAL_FAILED; | ||
594 | |||
595 | *protocol = proto; | ||
596 | return HAL_SUCCESS; | ||
597 | } | ||
598 | |||
599 | static bool _accessory_start(usbh_device_t *dev) { | ||
600 | usbh_urbstatus_t ret = usbhControlRequest(dev, | ||
601 | USBH_REQTYPE_DIR_OUT | USBH_REQTYPE_TYPE_VENDOR | USBH_REQTYPE_RECIP_DEVICE, | ||
602 | AOA_ACCESSORY_START, | ||
603 | 0, | ||
604 | 0, | ||
605 | 0, | ||
606 | NULL); | ||
607 | |||
608 | if (ret != USBH_URBSTATUS_OK) | ||
609 | return HAL_FAILED; | ||
610 | |||
611 | return HAL_SUCCESS; | ||
612 | } | ||
613 | |||
614 | static bool _set_audio_mode(usbh_device_t *dev, uint16_t mode) { | ||
615 | usbh_urbstatus_t ret = usbhControlRequest(dev, | ||
616 | USBH_REQTYPE_DIR_OUT | USBH_REQTYPE_TYPE_VENDOR | USBH_REQTYPE_RECIP_DEVICE, | ||
617 | AOA_SET_AUDIO_MODE, | ||
618 | mode, | ||
619 | 0, | ||
620 | 0, | ||
621 | NULL); | ||
622 | |||
623 | if (ret != USBH_URBSTATUS_OK) | ||
624 | return HAL_FAILED; | ||
625 | |||
626 | return HAL_SUCCESS; | ||
627 | } | ||
628 | |||
629 | static bool _send_string(usbh_device_t *dev, uint8_t index, const char *string) | ||
630 | { | ||
631 | USBH_DEFINE_BUFFER(const char nullstr[1]) = {0}; | ||
632 | if (string == NULL) | ||
633 | string = nullstr; | ||
634 | |||
635 | usbh_urbstatus_t ret = usbhControlRequest(dev, | ||
636 | USBH_REQTYPE_DIR_OUT | USBH_REQTYPE_TYPE_VENDOR | USBH_REQTYPE_RECIP_DEVICE, | ||
637 | AOA_ACCESSORY_SEND_STRING, | ||
638 | 0, | ||
639 | index, | ||
640 | strlen(string) + 1, | ||
641 | (uint8_t *)string); | ||
642 | |||
643 | if (ret != USBH_URBSTATUS_OK) | ||
644 | return HAL_FAILED; | ||
645 | |||
646 | return HAL_SUCCESS; | ||
647 | } | ||
648 | |||
649 | static void _object_init(USBHAOADriver *aoap) { | ||
650 | osalDbgCheck(aoap != NULL); | ||
651 | memset(aoap, 0, sizeof(*aoap)); | ||
652 | aoap->info = &usbhaoaClassDriverInfo; | ||
653 | aoap->state = USBHAOA_STATE_STOP; | ||
654 | aoap->channel.vmt = &async_channel_vmt; | ||
655 | osalEventObjectInit(&aoap->channel.event); | ||
656 | aoap->channel.state = USBHAOA_CHANNEL_STATE_STOP; | ||
657 | } | ||
658 | |||
659 | static void _aoa_init(void) { | ||
660 | uint8_t i; | ||
661 | for (i = 0; i < HAL_USBHAOA_MAX_INSTANCES; i++) { | ||
662 | _object_init(&USBHAOAD[i]); | ||
663 | } | ||
664 | } | ||
665 | |||
666 | #endif | ||
diff --git a/lib/chibios-contrib/os/hal/src/usbh/hal_usbh_debug.c b/lib/chibios-contrib/os/hal/src/usbh/hal_usbh_debug.c new file mode 100644 index 000000000..225489df8 --- /dev/null +++ b/lib/chibios-contrib/os/hal/src/usbh/hal_usbh_debug.c | |||
@@ -0,0 +1,219 @@ | |||
1 | /* | ||
2 | ChibiOS - Copyright (C) 2006..2017 Giovanni Di Sirio | ||
3 | Copyright (C) 2015..2019 Diego Ismirlian, (dismirlian(at)google's mail) | ||
4 | |||
5 | Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | you may not use this file except in compliance with the License. | ||
7 | You may obtain a copy of the License at | ||
8 | |||
9 | http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | |||
11 | Unless required by applicable law or agreed to in writing, software | ||
12 | distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | See the License for the specific language governing permissions and | ||
15 | limitations under the License. | ||
16 | */ | ||
17 | |||
18 | #include "hal.h" | ||
19 | |||
20 | #if HAL_USE_USBH && USBH_DEBUG_ENABLE | ||
21 | |||
22 | #include "ch.h" | ||
23 | #include "usbh/debug.h" | ||
24 | #include "chprintf.h" | ||
25 | #include <stdarg.h> | ||
26 | |||
27 | #define TEMP_BUFF_LEN 255 | ||
28 | |||
29 | /* ************************ */ | ||
30 | /* Circular queue structure */ | ||
31 | /* ************************ */ | ||
32 | static int dq_append_string(usbh_dq_t *q, const uint8_t *s, int len) { | ||
33 | if (len <= 0) return 0; | ||
34 | if (len > TEMP_BUFF_LEN) len = TEMP_BUFF_LEN; | ||
35 | if (q->rem < len + 1) return -1; | ||
36 | q->rem -= len + 1; | ||
37 | |||
38 | uint8_t *d = q->next; | ||
39 | *d++ = len; | ||
40 | if (d == q->end) d = q->start; | ||
41 | while (len--) { | ||
42 | *d++ = *s++; | ||
43 | if (d == q->end) d = q->start; | ||
44 | } | ||
45 | q->next = d; | ||
46 | return 0; | ||
47 | } | ||
48 | |||
49 | static void dq_remove_oldest_string(usbh_dq_t *q) { | ||
50 | int len = *q->first; | ||
51 | if (len) { | ||
52 | ++len; | ||
53 | q->rem += len; | ||
54 | q->first += len; | ||
55 | if (q->first >= q->end) { | ||
56 | q->first -= q->sz; | ||
57 | } | ||
58 | if (q->rem == q->sz) { | ||
59 | *q->first = 0; | ||
60 | } | ||
61 | } | ||
62 | } | ||
63 | |||
64 | static int dq_read_oldest_string(usbh_dq_t *q, uint8_t *d) { | ||
65 | uint8_t *s = q->first; | ||
66 | int len; | ||
67 | int sz; | ||
68 | len = sz = *s++; | ||
69 | while (len--) { | ||
70 | *d++ = *s++; | ||
71 | if (d == q->end) d = q->start; | ||
72 | } | ||
73 | *d = 0; | ||
74 | return sz; | ||
75 | } | ||
76 | |||
77 | static void dq_init(usbh_dq_t *q, uint8_t *buff, int len) { | ||
78 | q->start = q->first = q->next = buff; | ||
79 | q->end = q->start + len; | ||
80 | q->sz = q->rem = len; | ||
81 | *buff = 0; | ||
82 | } | ||
83 | |||
84 | |||
85 | static uint8_t buff[TEMP_BUFF_LEN + 1]; | ||
86 | |||
87 | static inline syssts_t _dbg_prologue(struct usbh_debug_helper *debug, | ||
88 | uint32_t hfnum, uint16_t hfir, const char *s, int *len) { | ||
89 | syssts_t sts = chSysGetStatusAndLockX(); | ||
90 | |||
91 | debug->last = osalOsGetSystemTimeX(); | ||
92 | if (debug->ena) { | ||
93 | debug->first = debug->last; | ||
94 | } | ||
95 | |||
96 | if (((hfnum & 0x3fff) == 0x3fff) && (hfir == (hfnum >> 16))) { | ||
97 | *len = chsnprintf((char *)buff, sizeof(buff), "+%08d ", debug->last - debug->first); | ||
98 | debug->ena = FALSE; | ||
99 | } else { | ||
100 | uint32_t f = hfnum & 0xffff; | ||
101 | uint32_t p = 1000 - ((hfnum >> 16) / (hfir / 1000)); | ||
102 | *len = chsnprintf((char *)buff, sizeof(buff), "%05d.%03d %s", f, p, s); | ||
103 | debug->ena = TRUE; | ||
104 | } | ||
105 | |||
106 | return sts; | ||
107 | } | ||
108 | |||
109 | static inline void dbg_epilogue(struct usbh_debug_helper *debug, | ||
110 | syssts_t sts, int len) { | ||
111 | |||
112 | while (dq_append_string(&debug->dq, buff, len) < 0) { | ||
113 | dq_remove_oldest_string(&debug->dq); | ||
114 | } | ||
115 | |||
116 | if (debug->on) { | ||
117 | chThdResumeI(&debug->tr, MSG_OK); | ||
118 | } | ||
119 | |||
120 | chSysRestoreStatusX(sts); | ||
121 | } | ||
122 | |||
123 | #if USBH_DEBUG_MULTI_HOST | ||
124 | void usbDbgPrintf(USBHDriver *host, const char *fmt, ...) { | ||
125 | if (!host) return; | ||
126 | struct usbh_debug_helper *const debug = &host->debug; | ||
127 | uint32_t hfnum = host->otg->HFNUM; | ||
128 | uint16_t hfir = host->otg->HFIR; | ||
129 | #else | ||
130 | void usbDbgPrintf(const char *fmt, ...) { | ||
131 | struct usbh_debug_helper *const debug = &usbh_debug; | ||
132 | uint32_t hfnum = USBH_DEBUG_SINGLE_HOST_SELECTION.otg->HFNUM; | ||
133 | uint16_t hfir = USBH_DEBUG_SINGLE_HOST_SELECTION.otg->HFIR; | ||
134 | #endif | ||
135 | int len; | ||
136 | |||
137 | syssts_t sts = _dbg_prologue(debug, hfnum, hfir, "", &len); | ||
138 | |||
139 | va_list ap; | ||
140 | va_start(ap, fmt); | ||
141 | len += chvsnprintf((char *)buff + len, sizeof(buff) - len, fmt, ap); | ||
142 | va_end(ap); | ||
143 | |||
144 | dbg_epilogue(debug, sts, len); | ||
145 | } | ||
146 | |||
147 | #if USBH_DEBUG_MULTI_HOST | ||
148 | void usbDbgPuts(USBHDriver *host, const char *s) { | ||
149 | if (!host) return; | ||
150 | struct usbh_debug_helper *const debug = &host->debug; | ||
151 | uint32_t hfnum = host->otg->HFNUM; | ||
152 | uint16_t hfir = host->otg->HFIR; | ||
153 | #else | ||
154 | void usbDbgPuts(const char *s) { | ||
155 | struct usbh_debug_helper *const debug = &usbh_debug; | ||
156 | uint32_t hfnum = USBH_DEBUG_SINGLE_HOST_SELECTION.otg->HFNUM; | ||
157 | uint16_t hfir = USBH_DEBUG_SINGLE_HOST_SELECTION.otg->HFIR; | ||
158 | #endif | ||
159 | int len; | ||
160 | syssts_t sts = _dbg_prologue(debug, hfnum, hfir, s, &len); | ||
161 | dbg_epilogue(debug, sts, len); | ||
162 | } | ||
163 | |||
164 | #if USBH_DEBUG_MULTI_HOST | ||
165 | void usbDbgEnable(USBHDriver *host, bool enable) { | ||
166 | struct usbh_debug_helper *const debug = &host->debug; | ||
167 | #else | ||
168 | void usbDbgEnable(bool enable) { | ||
169 | struct usbh_debug_helper *const debug = &usbh_debug; | ||
170 | #endif | ||
171 | debug->on = enable; | ||
172 | } | ||
173 | |||
174 | static void usb_debug_thread(void *arg) { | ||
175 | #if USBH_DEBUG_MULTI_HOST | ||
176 | USBHDriver *const host = (USBHDriver *)arg; | ||
177 | struct usbh_debug_helper *const debug = &host->debug; | ||
178 | #else | ||
179 | (void)arg; | ||
180 | struct usbh_debug_helper *const debug = &usbh_debug; | ||
181 | #endif | ||
182 | |||
183 | uint8_t rdbuff[TEMP_BUFF_LEN + 1]; | ||
184 | |||
185 | chRegSetThreadName("USBH_DBG"); | ||
186 | while (true) { | ||
187 | chSysLock(); | ||
188 | int len = dq_read_oldest_string(&debug->dq, rdbuff); | ||
189 | if (!len) { | ||
190 | chThdSuspendS(&debug->tr); | ||
191 | chSysUnlock(); | ||
192 | } else { | ||
193 | dq_remove_oldest_string(&debug->dq); | ||
194 | chSysUnlock(); | ||
195 | #if USBH_DEBUG_MULTI_HOST | ||
196 | USBH_DEBUG_OUTPUT_CALLBACK(host, rdbuff, len); | ||
197 | #else | ||
198 | USBH_DEBUG_OUTPUT_CALLBACK(rdbuff, len); | ||
199 | #endif | ||
200 | } | ||
201 | } | ||
202 | } | ||
203 | |||
204 | #if USBH_DEBUG_MULTI_HOST | ||
205 | void usbDbgInit(USBHDriver *host) { | ||
206 | struct usbh_debug_helper *const debug = &host->debug; | ||
207 | void *param = host; | ||
208 | #else | ||
209 | void usbDbgInit(void) { | ||
210 | struct usbh_debug_helper *const debug = &usbh_debug; | ||
211 | void *param = NULL; | ||
212 | #endif | ||
213 | dq_init(&debug->dq, debug->buff, sizeof(debug->buff)); | ||
214 | debug->on = true; | ||
215 | chThdCreateStatic(debug->thd_wa, sizeof(debug->thd_wa), | ||
216 | NORMALPRIO, usb_debug_thread, param); | ||
217 | } | ||
218 | |||
219 | #endif | ||
diff --git a/lib/chibios-contrib/os/hal/src/usbh/hal_usbh_desciter.c b/lib/chibios-contrib/os/hal/src/usbh/hal_usbh_desciter.c new file mode 100644 index 000000000..0ccf4e46b --- /dev/null +++ b/lib/chibios-contrib/os/hal/src/usbh/hal_usbh_desciter.c | |||
@@ -0,0 +1,161 @@ | |||
1 | /* | ||
2 | ChibiOS - Copyright (C) 2006..2017 Giovanni Di Sirio | ||
3 | Copyright (C) 2015..2019 Diego Ismirlian, (dismirlian(at)google's mail) | ||
4 | |||
5 | Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | you may not use this file except in compliance with the License. | ||
7 | You may obtain a copy of the License at | ||
8 | |||
9 | http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | |||
11 | Unless required by applicable law or agreed to in writing, software | ||
12 | distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | See the License for the specific language governing permissions and | ||
15 | limitations under the License. | ||
16 | */ | ||
17 | |||
18 | #include "hal.h" | ||
19 | |||
20 | #if HAL_USE_USBH | ||
21 | |||
22 | #include "usbh/defs.h" | ||
23 | #include "usbh/desciter.h" | ||
24 | |||
25 | void cfg_iter_init(generic_iterator_t *icfg, const uint8_t *buff, uint16_t rem) { | ||
26 | icfg->valid = 0; | ||
27 | |||
28 | if ((rem < 2) || (buff[0] < 2) || (rem < buff[0]) | ||
29 | || (buff[0] < USBH_DT_CONFIG_SIZE) | ||
30 | || (buff[1] != USBH_DT_CONFIG)) | ||
31 | return; | ||
32 | |||
33 | if (rem > ((usbh_config_descriptor_t *)buff)->wTotalLength) { | ||
34 | rem = ((usbh_config_descriptor_t *)buff)->wTotalLength; | ||
35 | } | ||
36 | |||
37 | icfg->valid = 1; | ||
38 | icfg->rem = rem; | ||
39 | icfg->curr = buff; | ||
40 | } | ||
41 | |||
42 | void if_iter_next(if_iterator_t *iif) { | ||
43 | const uint8_t *curr = iif->curr; | ||
44 | uint16_t rem = iif->rem; | ||
45 | |||
46 | iif->valid = 0; | ||
47 | |||
48 | if ((rem < 2) || (curr[0] < 2) || (rem < curr[0])) | ||
49 | return; | ||
50 | |||
51 | for (;;) { | ||
52 | rem -= curr[0]; | ||
53 | curr += curr[0]; | ||
54 | |||
55 | if ((rem < 2) || (curr[0] < 2) || (rem < curr[0])) | ||
56 | return; | ||
57 | |||
58 | if (curr[1] == USBH_DT_INTERFACE_ASSOCIATION) { | ||
59 | if (curr[0] < USBH_DT_INTERFACE_ASSOCIATION_SIZE) | ||
60 | return; | ||
61 | |||
62 | iif->iad = (usbh_ia_descriptor_t *)curr; | ||
63 | |||
64 | } else if (curr[1] == USBH_DT_INTERFACE) { | ||
65 | if (curr[0] < USBH_DT_INTERFACE_SIZE) | ||
66 | return; | ||
67 | |||
68 | if (iif->iad) { | ||
69 | if ((curr[2] < iif->iad->bFirstInterface) | ||
70 | || (curr[2] >= (iif->iad->bFirstInterface + iif->iad->bInterfaceCount))) | ||
71 | iif->iad = 0; | ||
72 | } | ||
73 | break; | ||
74 | } | ||
75 | } | ||
76 | |||
77 | iif->valid = 1; | ||
78 | iif->rem = rem; | ||
79 | iif->curr = curr; | ||
80 | } | ||
81 | |||
82 | void if_iter_init(if_iterator_t *iif, const generic_iterator_t *icfg) { | ||
83 | iif->iad = 0; | ||
84 | iif->curr = icfg->curr; | ||
85 | iif->rem = icfg->rem; | ||
86 | if_iter_next(iif); | ||
87 | } | ||
88 | |||
89 | void ep_iter_next(generic_iterator_t *iep) { | ||
90 | const uint8_t *curr = iep->curr; | ||
91 | uint16_t rem = iep->rem; | ||
92 | |||
93 | iep->valid = 0; | ||
94 | |||
95 | if ((rem < 2) || (curr[0] < 2) || (rem < curr[0])) | ||
96 | return; | ||
97 | |||
98 | for (;;) { | ||
99 | rem -= curr[0]; | ||
100 | curr += curr[0]; | ||
101 | |||
102 | if ((rem < 2) || (curr[0] < 2) || (rem < curr[0])) | ||
103 | return; | ||
104 | |||
105 | if ((curr[1] == USBH_DT_INTERFACE_ASSOCIATION) | ||
106 | || (curr[1] == USBH_DT_INTERFACE) | ||
107 | || (curr[1] == USBH_DT_CONFIG)) { | ||
108 | return; | ||
109 | } else if (curr[1] == USBH_DT_ENDPOINT) { | ||
110 | if (curr[0] < USBH_DT_ENDPOINT_SIZE) | ||
111 | return; | ||
112 | |||
113 | break; | ||
114 | } | ||
115 | } | ||
116 | |||
117 | iep->valid = 1; | ||
118 | iep->rem = rem; | ||
119 | iep->curr = curr; | ||
120 | } | ||
121 | |||
122 | void ep_iter_init(generic_iterator_t *iep, const if_iterator_t *iif) { | ||
123 | iep->curr = iif->curr; | ||
124 | iep->rem = iif->rem; | ||
125 | ep_iter_next(iep); | ||
126 | } | ||
127 | |||
128 | void cs_iter_next(generic_iterator_t *ics) { | ||
129 | const uint8_t *curr = ics->curr; | ||
130 | uint16_t rem = ics->rem; | ||
131 | |||
132 | ics->valid = 0; | ||
133 | |||
134 | if ((rem < 2) || (curr[0] < 2) || (rem < curr[0])) | ||
135 | return; | ||
136 | |||
137 | rem -= curr[0]; | ||
138 | curr += curr[0]; | ||
139 | |||
140 | if ((rem < 2) || (curr[0] < 2) || (rem < curr[0])) | ||
141 | return; | ||
142 | |||
143 | if ((curr[1] == USBH_DT_INTERFACE_ASSOCIATION) | ||
144 | || (curr[1] == USBH_DT_INTERFACE) | ||
145 | || (curr[1] == USBH_DT_CONFIG) | ||
146 | || (curr[1] == USBH_DT_ENDPOINT)) { | ||
147 | return; | ||
148 | } | ||
149 | |||
150 | ics->valid = 1; | ||
151 | ics->rem = rem; | ||
152 | ics->curr = curr; | ||
153 | } | ||
154 | |||
155 | void cs_iter_init(generic_iterator_t *ics, const generic_iterator_t *iter) { | ||
156 | ics->curr = iter->curr; | ||
157 | ics->rem = iter->rem; | ||
158 | cs_iter_next(ics); | ||
159 | } | ||
160 | |||
161 | #endif | ||
diff --git a/lib/chibios-contrib/os/hal/src/usbh/hal_usbh_ftdi.c b/lib/chibios-contrib/os/hal/src/usbh/hal_usbh_ftdi.c new file mode 100644 index 000000000..1b06405be --- /dev/null +++ b/lib/chibios-contrib/os/hal/src/usbh/hal_usbh_ftdi.c | |||
@@ -0,0 +1,710 @@ | |||
1 | /* | ||
2 | ChibiOS - Copyright (C) 2006..2017 Giovanni Di Sirio | ||
3 | Copyright (C) 2015..2019 Diego Ismirlian, (dismirlian(at)google's mail) | ||
4 | |||
5 | Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | you may not use this file except in compliance with the License. | ||
7 | You may obtain a copy of the License at | ||
8 | |||
9 | http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | |||
11 | Unless required by applicable law or agreed to in writing, software | ||
12 | distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | See the License for the specific language governing permissions and | ||
15 | limitations under the License. | ||
16 | */ | ||
17 | |||
18 | #include "hal.h" | ||
19 | |||
20 | #if HAL_USBH_USE_FTDI | ||
21 | |||
22 | #if !HAL_USE_USBH | ||
23 | #error "USBHFTDI needs USBH" | ||
24 | #endif | ||
25 | |||
26 | #include <string.h> | ||
27 | #include "usbh/dev/ftdi.h" | ||
28 | #include "usbh/internal.h" | ||
29 | |||
30 | #define _USBH_DEBUG_HELPER_CLASS_DRIVER ftdipp->ftdip | ||
31 | #define _USBH_DEBUG_HELPER_ENABLE_TRACE USBHFTDI_DEBUG_ENABLE_TRACE | ||
32 | #define _USBH_DEBUG_HELPER_ENABLE_INFO USBHFTDI_DEBUG_ENABLE_INFO | ||
33 | #define _USBH_DEBUG_HELPER_ENABLE_WARNINGS USBHFTDI_DEBUG_ENABLE_WARNINGS | ||
34 | #define _USBH_DEBUG_HELPER_ENABLE_ERRORS USBHFTDI_DEBUG_ENABLE_ERRORS | ||
35 | #include "usbh/debug_helpers.h" | ||
36 | |||
37 | |||
38 | static void _ftdip_object_init(USBHFTDIPortDriver *ftdipp); | ||
39 | |||
40 | /*===========================================================================*/ | ||
41 | /* USB Class driver loader for FTDI */ | ||
42 | /*===========================================================================*/ | ||
43 | USBHFTDIDriver USBHFTDID[HAL_USBHFTDI_MAX_INSTANCES]; | ||
44 | |||
45 | static void _ftdi_init(void); | ||
46 | static usbh_baseclassdriver_t *_ftdi_load(usbh_device_t *dev, const uint8_t *descriptor, uint16_t rem); | ||
47 | static void _ftdi_unload(usbh_baseclassdriver_t *drv); | ||
48 | |||
49 | static const usbh_classdriver_vmt_t class_driver_vmt = { | ||
50 | _ftdi_init, | ||
51 | _ftdi_load, | ||
52 | _ftdi_unload | ||
53 | }; | ||
54 | |||
55 | const usbh_classdriverinfo_t usbhftdiClassDriverInfo = { | ||
56 | "FTDI", &class_driver_vmt | ||
57 | }; | ||
58 | |||
59 | static USBHFTDIPortDriver *_find_port(void) { | ||
60 | uint8_t i; | ||
61 | for (i = 0; i < HAL_USBHFTDI_MAX_PORTS; i++) { | ||
62 | if (FTDIPD[i].ftdip == NULL) | ||
63 | return &FTDIPD[i]; | ||
64 | } | ||
65 | return NULL; | ||
66 | } | ||
67 | |||
68 | static usbh_baseclassdriver_t *_ftdi_load(usbh_device_t *dev, const uint8_t *descriptor, uint16_t rem) { | ||
69 | int i; | ||
70 | USBHFTDIDriver *ftdip; | ||
71 | |||
72 | if (_usbh_match_vid_pid(dev, 0x0403, -1) != HAL_SUCCESS) | ||
73 | return NULL; | ||
74 | |||
75 | switch (dev->devDesc.idProduct) { | ||
76 | case 0x6001: | ||
77 | case 0x6010: | ||
78 | case 0x6011: | ||
79 | case 0x6014: | ||
80 | case 0x6015: | ||
81 | case 0xE2E6: | ||
82 | break; | ||
83 | default: | ||
84 | udeverr("FTDI: Unrecognized PID"); | ||
85 | return NULL; | ||
86 | } | ||
87 | |||
88 | if (_usbh_match_descriptor(descriptor, rem, USBH_DT_INTERFACE, | ||
89 | 0xff, 0xff, 0xff) != HAL_SUCCESS) | ||
90 | return NULL; | ||
91 | |||
92 | if (((const usbh_interface_descriptor_t *)descriptor)->bInterfaceNumber != 0) { | ||
93 | udevwarn("FTDI: Will allocate driver along with IF #0"); | ||
94 | } | ||
95 | |||
96 | /* alloc driver */ | ||
97 | for (i = 0; i < HAL_USBHFTDI_MAX_INSTANCES; i++) { | ||
98 | if (USBHFTDID[i].dev == NULL) { | ||
99 | ftdip = &USBHFTDID[i]; | ||
100 | goto alloc_ok; | ||
101 | } | ||
102 | } | ||
103 | |||
104 | udevwarn("FTDI: Can't alloc driver"); | ||
105 | |||
106 | /* can't alloc */ | ||
107 | return NULL; | ||
108 | |||
109 | alloc_ok: | ||
110 | /* initialize the driver's variables */ | ||
111 | ftdip->ports = 0; | ||
112 | switch (dev->devDesc.bcdDevice) { | ||
113 | case 0x200: //AM | ||
114 | udevinfo("FTDI: Type A chip"); | ||
115 | ftdip->type = USBHFTDI_TYPE_A; | ||
116 | break; | ||
117 | case 0x400: //BM | ||
118 | case 0x500: //2232C | ||
119 | case 0x600: //R | ||
120 | case 0x1000: //230X | ||
121 | udevinfo("FTDI: Type B chip"); | ||
122 | ftdip->type = USBHFTDI_TYPE_B; | ||
123 | break; | ||
124 | case 0x700: //2232H; | ||
125 | case 0x800: //4232H; | ||
126 | case 0x900: //232H; | ||
127 | udevinfo("FTDI: Type H chip"); | ||
128 | ftdip->type = USBHFTDI_TYPE_H; | ||
129 | break; | ||
130 | default: | ||
131 | udeverr("FTDI: Unrecognized chip type"); | ||
132 | return NULL; | ||
133 | } | ||
134 | usbhEPSetName(&dev->ctrl, "FTD[CTRL]"); | ||
135 | |||
136 | /* parse the configuration descriptor */ | ||
137 | generic_iterator_t iep, icfg; | ||
138 | if_iterator_t iif; | ||
139 | cfg_iter_init(&icfg, dev->fullConfigurationDescriptor, dev->basicConfigDesc.wTotalLength); | ||
140 | for (if_iter_init(&iif, &icfg); iif.valid; if_iter_next(&iif)) { | ||
141 | const usbh_interface_descriptor_t *const ifdesc = if_get(&iif); | ||
142 | udevinfof("FTDI: Interface #%d", ifdesc->bInterfaceNumber); | ||
143 | |||
144 | USBHFTDIPortDriver *const prt = _find_port(); | ||
145 | if (prt == NULL) { | ||
146 | udevwarn("\tCan't alloc port for this interface"); | ||
147 | break; | ||
148 | } | ||
149 | |||
150 | prt->ifnum = ifdesc->bInterfaceNumber; | ||
151 | prt->epin.status = USBH_EPSTATUS_UNINITIALIZED; | ||
152 | prt->epout.status = USBH_EPSTATUS_UNINITIALIZED; | ||
153 | |||
154 | for (ep_iter_init(&iep, &iif); iep.valid; ep_iter_next(&iep)) { | ||
155 | const usbh_endpoint_descriptor_t *const epdesc = ep_get(&iep); | ||
156 | if ((epdesc->bEndpointAddress & 0x80) && (epdesc->bmAttributes == USBH_EPTYPE_BULK)) { | ||
157 | udevinfof("BULK IN endpoint found: bEndpointAddress=%02x", epdesc->bEndpointAddress); | ||
158 | usbhEPObjectInit(&prt->epin, dev, epdesc); | ||
159 | usbhEPSetName(&prt->epin, "FTD[BIN ]"); | ||
160 | } else if (((epdesc->bEndpointAddress & 0x80) == 0) | ||
161 | && (epdesc->bmAttributes == USBH_EPTYPE_BULK)) { | ||
162 | udevinfof("BULK OUT endpoint found: bEndpointAddress=%02x", epdesc->bEndpointAddress); | ||
163 | usbhEPObjectInit(&prt->epout, dev, epdesc); | ||
164 | usbhEPSetName(&prt->epout, "FTD[BOUT]"); | ||
165 | } else { | ||
166 | udevinfof("unsupported endpoint found: bEndpointAddress=%02x, bmAttributes=%02x", | ||
167 | epdesc->bEndpointAddress, epdesc->bmAttributes); | ||
168 | } | ||
169 | } | ||
170 | |||
171 | if ((prt->epin.status != USBH_EPSTATUS_CLOSED) | ||
172 | || (prt->epout.status != USBH_EPSTATUS_CLOSED)) { | ||
173 | udevwarn("\tCouldn't find endpoints; can't alloc port for this interface"); | ||
174 | continue; | ||
175 | } | ||
176 | |||
177 | /* link the new block driver to the list */ | ||
178 | prt->next = ftdip->ports; | ||
179 | ftdip->ports = prt; | ||
180 | prt->ftdip = ftdip; | ||
181 | |||
182 | prt->state = USBHFTDIP_STATE_ACTIVE; | ||
183 | } | ||
184 | |||
185 | return (usbh_baseclassdriver_t *)ftdip; | ||
186 | |||
187 | } | ||
188 | |||
189 | static void _stopS(USBHFTDIPortDriver *ftdipp); | ||
190 | static void _ftdi_unload(usbh_baseclassdriver_t *drv) { | ||
191 | osalDbgCheck(drv != NULL); | ||
192 | USBHFTDIDriver *const ftdip = (USBHFTDIDriver *)drv; | ||
193 | USBHFTDIPortDriver *ftdipp = ftdip->ports; | ||
194 | |||
195 | osalMutexLock(&ftdip->mtx); | ||
196 | while (ftdipp) { | ||
197 | osalSysLock(); | ||
198 | _stopS(ftdipp); | ||
199 | osalSysUnlock(); | ||
200 | ftdipp = ftdipp->next; | ||
201 | } | ||
202 | |||
203 | ftdipp = ftdip->ports; | ||
204 | osalSysLock(); | ||
205 | while (ftdipp) { | ||
206 | USBHFTDIPortDriver *next = ftdipp->next; | ||
207 | _ftdip_object_init(ftdipp); | ||
208 | ftdipp = next; | ||
209 | } | ||
210 | osalSysUnlock(); | ||
211 | osalMutexUnlock(&ftdip->mtx); | ||
212 | } | ||
213 | |||
214 | |||
215 | USBHFTDIPortDriver FTDIPD[HAL_USBHFTDI_MAX_PORTS]; | ||
216 | |||
217 | |||
218 | #define FTDI_COMMAND_RESET 0 | ||
219 | #define FTDI_RESET_ALL 0 | ||
220 | #define FTDI_RESET_PURGE_RX 1 | ||
221 | #define FTDI_RESET_PURGE_TX 2 | ||
222 | |||
223 | #define FTDI_COMMAND_SETFLOW 2 | ||
224 | |||
225 | #define FTDI_COMMAND_SETBAUD 3 | ||
226 | |||
227 | #define FTDI_COMMAND_SETDATA 4 | ||
228 | #define FTDI_SETDATA_BREAK (0x1 << 14) | ||
229 | |||
230 | #if 0 | ||
231 | #define FTDI_COMMAND_MODEMCTRL 1 | ||
232 | #define FTDI_COMMAND_GETMODEMSTATUS 5 /* Retrieve current value of modem status register */ | ||
233 | #define FTDI_COMMAND_SETEVENTCHAR 6 /* Set the event character */ | ||
234 | #define FTDI_COMMAND_SETERRORCHAR 7 /* Set the error character */ | ||
235 | #define FTDI_COMMAND_SETLATENCYTIMER 9 /* Set the latency timer */ | ||
236 | #define FTDI_COMMAND_GETLATENCYTIMER 10 /* Get the latency timer */ | ||
237 | #endif | ||
238 | |||
239 | /* | ||
240 | * DATA FORMAT | ||
241 | * | ||
242 | * IN Endpoint | ||
243 | * | ||
244 | * The device reserves the first two bytes of data on this endpoint to contain | ||
245 | * the current values of the modem and line status registers. In the absence of | ||
246 | * data, the device generates a message consisting of these two status bytes | ||
247 | * every 40 ms | ||
248 | * | ||
249 | * Byte 0: Modem Status | ||
250 | * | ||
251 | * Offset Description | ||
252 | * B0 Reserved - must be 1 | ||
253 | * B1 Reserved - must be 0 | ||
254 | * B2 Reserved - must be 0 | ||
255 | * B3 Reserved - must be 0 | ||
256 | * B4 Clear to Send (CTS) | ||
257 | * B5 Data Set Ready (DSR) | ||
258 | * B6 Ring Indicator (RI) | ||
259 | * B7 Receive Line Signal Detect (RLSD) | ||
260 | * | ||
261 | * Byte 1: Line Status | ||
262 | * | ||
263 | * Offset Description | ||
264 | * B0 Data Ready (DR) | ||
265 | * B1 Overrun Error (OE) | ||
266 | * B2 Parity Error (PE) | ||
267 | * B3 Framing Error (FE) | ||
268 | * B4 Break Interrupt (BI) | ||
269 | * B5 Transmitter Holding Register (THRE) | ||
270 | * B6 Transmitter Empty (TEMT) | ||
271 | * B7 Error in RCVR FIFO | ||
272 | * | ||
273 | */ | ||
274 | #define FTDI_RS0_CTS (1 << 4) | ||
275 | #define FTDI_RS0_DSR (1 << 5) | ||
276 | #define FTDI_RS0_RI (1 << 6) | ||
277 | #define FTDI_RS0_RLSD (1 << 7) | ||
278 | |||
279 | #define FTDI_RS_DR 1 | ||
280 | #define FTDI_RS_OE (1<<1) | ||
281 | #define FTDI_RS_PE (1<<2) | ||
282 | #define FTDI_RS_FE (1<<3) | ||
283 | #define FTDI_RS_BI (1<<4) | ||
284 | #define FTDI_RS_THRE (1<<5) | ||
285 | #define FTDI_RS_TEMT (1<<6) | ||
286 | #define FTDI_RS_FIFO (1<<7) | ||
287 | |||
288 | |||
289 | static usbh_urbstatus_t _ftdi_port_control(USBHFTDIPortDriver *ftdipp, | ||
290 | uint8_t bRequest, uint8_t wValue, uint8_t bHIndex, uint16_t wLength, | ||
291 | uint8_t *buff) { | ||
292 | |||
293 | static const uint8_t bmRequestType[] = { | ||
294 | USBH_REQTYPE_TYPE_VENDOR | USBH_REQTYPE_DIR_OUT | USBH_REQTYPE_RECIP_DEVICE, //0 FTDI_COMMAND_RESET | ||
295 | USBH_REQTYPE_TYPE_VENDOR | USBH_REQTYPE_DIR_OUT | USBH_REQTYPE_RECIP_DEVICE, //1 FTDI_COMMAND_MODEMCTRL | ||
296 | USBH_REQTYPE_TYPE_VENDOR | USBH_REQTYPE_DIR_OUT | USBH_REQTYPE_RECIP_DEVICE, //2 FTDI_COMMAND_SETFLOW | ||
297 | USBH_REQTYPE_TYPE_VENDOR | USBH_REQTYPE_DIR_OUT | USBH_REQTYPE_RECIP_DEVICE, //3 FTDI_COMMAND_SETBAUD | ||
298 | USBH_REQTYPE_TYPE_VENDOR | USBH_REQTYPE_DIR_OUT | USBH_REQTYPE_RECIP_DEVICE, //4 FTDI_COMMAND_SETDATA | ||
299 | }; | ||
300 | |||
301 | osalDbgCheck(bRequest < sizeof_array(bmRequestType)); | ||
302 | osalDbgCheck(bRequest != 1); | ||
303 | |||
304 | USBH_DEFINE_BUFFER(const usbh_control_request_t req) = { | ||
305 | bmRequestType[bRequest], | ||
306 | bRequest, | ||
307 | wValue, | ||
308 | (bHIndex << 8) | (ftdipp->ifnum + 1), | ||
309 | wLength | ||
310 | }; | ||
311 | |||
312 | return usbhControlRequestExtended(ftdipp->ftdip->dev, &req, buff, NULL, OSAL_MS2I(1000)); | ||
313 | } | ||
314 | |||
315 | static uint32_t _get_divisor(const USBHFTDIPortDriver *ftdipp, uint32_t baud) { | ||
316 | usbhftdi_type_t type = ftdipp->ftdip->type; | ||
317 | static const uint8_t divfrac[8] = {0, 3, 2, 4, 1, 5, 6, 7}; | ||
318 | uint32_t divisor; | ||
319 | |||
320 | if (type == USBHFTDI_TYPE_A) { | ||
321 | uint32_t divisor3 = ((48000000UL / 2) + baud / 2) / baud; | ||
322 | uclassdrvinfof("FTDI: desired=%dbps, real=%dbps", baud, (48000000UL / 2) / divisor3); | ||
323 | if ((divisor3 & 0x7) == 7) | ||
324 | divisor3++; /* round x.7/8 up to x+1 */ | ||
325 | |||
326 | divisor = divisor3 >> 3; | ||
327 | divisor3 &= 0x7; | ||
328 | if (divisor3 == 1) | ||
329 | divisor |= 0xc000; | ||
330 | else if (divisor3 >= 4) | ||
331 | divisor |= 0x4000; | ||
332 | else if (divisor3 != 0) | ||
333 | divisor |= 0x8000; | ||
334 | else if (divisor == 1) | ||
335 | divisor = 0; /* special case for maximum baud rate */ | ||
336 | } else { | ||
337 | if (type == USBHFTDI_TYPE_B) { | ||
338 | divisor = ((48000000UL / 2) + baud / 2) / baud; | ||
339 | uclassdrvinfof("FTDI: desired=%dbps, real=%dbps", baud, (48000000UL / 2) / divisor); | ||
340 | } else { | ||
341 | /* hi-speed baud rate is 10-bit sampling instead of 16-bit */ | ||
342 | if (baud < 1200) | ||
343 | baud = 1200; | ||
344 | divisor = (120000000UL * 8 + baud * 5) / (baud * 10); | ||
345 | uclassdrvinfof("FTDI: desired=%dbps, real=%dbps", baud, (120000000UL * 8) / divisor / 10); | ||
346 | } | ||
347 | divisor = (divisor >> 3) | (divfrac[divisor & 0x7] << 14); | ||
348 | |||
349 | /* Deal with special cases for highest baud rates. */ | ||
350 | if (divisor == 1) | ||
351 | divisor = 0; | ||
352 | else if (divisor == 0x4001) | ||
353 | divisor = 1; | ||
354 | |||
355 | if (type == USBHFTDI_TYPE_H) | ||
356 | divisor |= 0x00020000; | ||
357 | } | ||
358 | return divisor; | ||
359 | } | ||
360 | |||
361 | static usbh_urbstatus_t _set_baudrate(USBHFTDIPortDriver *ftdipp, uint32_t baudrate) { | ||
362 | uint32_t divisor = _get_divisor(ftdipp, baudrate); | ||
363 | uint16_t wValue = (uint16_t)divisor; | ||
364 | uint16_t wIndex = (uint16_t)(divisor >> 16); | ||
365 | if (ftdipp->ftdip->dev->basicConfigDesc.bNumInterfaces > 1) | ||
366 | wIndex = (wIndex << 8) | (ftdipp->ifnum + 1); | ||
367 | |||
368 | USBH_DEFINE_BUFFER(const usbh_control_request_t req) = { | ||
369 | USBH_REQTYPE_TYPE_VENDOR | USBH_REQTYPE_DIR_OUT | USBH_REQTYPE_RECIP_DEVICE, | ||
370 | FTDI_COMMAND_SETBAUD, | ||
371 | wValue, | ||
372 | wIndex, | ||
373 | 0 | ||
374 | }; | ||
375 | return usbhControlRequestExtended(ftdipp->ftdip->dev, &req, NULL, NULL, OSAL_MS2I(1000)); | ||
376 | } | ||
377 | |||
378 | |||
379 | static void _submitOutI(USBHFTDIPortDriver *ftdipp, uint32_t len) { | ||
380 | uclassdrvdbgf("FTDI: Submit OUT %d", len); | ||
381 | ftdipp->oq_urb.requestedLength = len; | ||
382 | usbhURBObjectResetI(&ftdipp->oq_urb); | ||
383 | usbhURBSubmitI(&ftdipp->oq_urb); | ||
384 | } | ||
385 | |||
386 | static void _out_cb(usbh_urb_t *urb) { | ||
387 | USBHFTDIPortDriver *const ftdipp = (USBHFTDIPortDriver *)urb->userData; | ||
388 | switch (urb->status) { | ||
389 | case USBH_URBSTATUS_OK: | ||
390 | ftdipp->oq_ptr = ftdipp->oq_buff; | ||
391 | ftdipp->oq_counter = 64; | ||
392 | chThdDequeueNextI(&ftdipp->oq_waiting, Q_OK); | ||
393 | return; | ||
394 | case USBH_URBSTATUS_DISCONNECTED: | ||
395 | uurbwarn("FTDI: URB OUT disconnected"); | ||
396 | chThdDequeueAllI(&ftdipp->oq_waiting, Q_RESET); | ||
397 | return; | ||
398 | default: | ||
399 | uurberrf("FTDI: URB OUT status unexpected = %d", urb->status); | ||
400 | break; | ||
401 | } | ||
402 | usbhURBObjectResetI(&ftdipp->oq_urb); | ||
403 | usbhURBSubmitI(&ftdipp->oq_urb); | ||
404 | } | ||
405 | |||
406 | static size_t _write_timeout(USBHFTDIPortDriver *ftdipp, const uint8_t *bp, | ||
407 | size_t n, systime_t timeout) { | ||
408 | chDbgCheck(n > 0U); | ||
409 | |||
410 | size_t w = 0; | ||
411 | osalSysLock(); | ||
412 | while (true) { | ||
413 | if (ftdipp->state != USBHFTDIP_STATE_READY) { | ||
414 | osalSysUnlock(); | ||
415 | return w; | ||
416 | } | ||
417 | while (usbhURBIsBusy(&ftdipp->oq_urb)) { | ||
418 | if (chThdEnqueueTimeoutS(&ftdipp->oq_waiting, timeout) != Q_OK) { | ||
419 | osalSysUnlock(); | ||
420 | return w; | ||
421 | } | ||
422 | } | ||
423 | |||
424 | *ftdipp->oq_ptr++ = *bp++; | ||
425 | if (--ftdipp->oq_counter == 0) { | ||
426 | _submitOutI(ftdipp, 64); | ||
427 | osalOsRescheduleS(); | ||
428 | } | ||
429 | osalSysUnlock(); /* Gives a preemption chance in a controlled point.*/ | ||
430 | |||
431 | w++; | ||
432 | if (--n == 0U) | ||
433 | return w; | ||
434 | |||
435 | osalSysLock(); | ||
436 | } | ||
437 | } | ||
438 | |||
439 | static msg_t _put_timeout(USBHFTDIPortDriver *ftdipp, uint8_t b, systime_t timeout) { | ||
440 | |||
441 | osalSysLock(); | ||
442 | if (ftdipp->state != USBHFTDIP_STATE_READY) { | ||
443 | osalSysUnlock(); | ||
444 | return Q_RESET; | ||
445 | } | ||
446 | |||
447 | while (usbhURBIsBusy(&ftdipp->oq_urb)) { | ||
448 | msg_t msg = chThdEnqueueTimeoutS(&ftdipp->oq_waiting, timeout); | ||
449 | if (msg < Q_OK) { | ||
450 | osalSysUnlock(); | ||
451 | return msg; | ||
452 | } | ||
453 | } | ||
454 | |||
455 | *ftdipp->oq_ptr++ = b; | ||
456 | if (--ftdipp->oq_counter == 0) { | ||
457 | _submitOutI(ftdipp, 64); | ||
458 | osalOsRescheduleS(); | ||
459 | } | ||
460 | osalSysUnlock(); | ||
461 | return Q_OK; | ||
462 | } | ||
463 | |||
464 | static size_t _write(USBHFTDIPortDriver *ftdipp, const uint8_t *bp, size_t n) { | ||
465 | return _write_timeout(ftdipp, bp, n, TIME_INFINITE); | ||
466 | } | ||
467 | |||
468 | static msg_t _put(USBHFTDIPortDriver *ftdipp, uint8_t b) { | ||
469 | return _put_timeout(ftdipp, b, TIME_INFINITE); | ||
470 | } | ||
471 | |||
472 | static void _submitInI(USBHFTDIPortDriver *ftdipp) { | ||
473 | uclassdrvdbg("FTDI: Submit IN"); | ||
474 | usbhURBObjectResetI(&ftdipp->iq_urb); | ||
475 | usbhURBSubmitI(&ftdipp->iq_urb); | ||
476 | } | ||
477 | |||
478 | static void _in_cb(usbh_urb_t *urb) { | ||
479 | USBHFTDIPortDriver *const ftdipp = (USBHFTDIPortDriver *)urb->userData; | ||
480 | switch (urb->status) { | ||
481 | case USBH_URBSTATUS_OK: | ||
482 | if (urb->actualLength < 2) { | ||
483 | uurbwarnf("FTDI: URB IN actualLength = %d, < 2", urb->actualLength); | ||
484 | } else if (urb->actualLength > 2) { | ||
485 | uurbdbgf("FTDI: URB IN data len=%d, status=%02x %02x", | ||
486 | urb->actualLength - 2, | ||
487 | ((uint8_t *)urb->buff)[0], | ||
488 | ((uint8_t *)urb->buff)[1]); | ||
489 | ftdipp->iq_ptr = ftdipp->iq_buff + 2; | ||
490 | ftdipp->iq_counter = urb->actualLength - 2; | ||
491 | chThdDequeueNextI(&ftdipp->iq_waiting, Q_OK); | ||
492 | return; | ||
493 | } else { | ||
494 | uurbdbgf("FTDI: URB IN no data, status=%02x %02x", | ||
495 | ((uint8_t *)urb->buff)[0], | ||
496 | ((uint8_t *)urb->buff)[1]); | ||
497 | // return; | ||
498 | } | ||
499 | break; | ||
500 | case USBH_URBSTATUS_DISCONNECTED: | ||
501 | uurbwarn("FTDI: URB IN disconnected"); | ||
502 | chThdDequeueAllI(&ftdipp->iq_waiting, Q_RESET); | ||
503 | return; | ||
504 | default: | ||
505 | uurberrf("FTDI: URB IN status unexpected = %d", urb->status); | ||
506 | break; | ||
507 | } | ||
508 | _submitInI(ftdipp); | ||
509 | } | ||
510 | |||
511 | static size_t _read_timeout(USBHFTDIPortDriver *ftdipp, uint8_t *bp, | ||
512 | size_t n, systime_t timeout) { | ||
513 | size_t r = 0; | ||
514 | |||
515 | chDbgCheck(n > 0U); | ||
516 | |||
517 | osalSysLock(); | ||
518 | while (true) { | ||
519 | if (ftdipp->state != USBHFTDIP_STATE_READY) { | ||
520 | osalSysUnlock(); | ||
521 | return r; | ||
522 | } | ||
523 | while (ftdipp->iq_counter == 0) { | ||
524 | if (!usbhURBIsBusy(&ftdipp->iq_urb)) | ||
525 | _submitInI(ftdipp); | ||
526 | if (chThdEnqueueTimeoutS(&ftdipp->iq_waiting, timeout) != Q_OK) { | ||
527 | osalSysUnlock(); | ||
528 | return r; | ||
529 | } | ||
530 | } | ||
531 | *bp++ = *ftdipp->iq_ptr++; | ||
532 | if (--ftdipp->iq_counter == 0) { | ||
533 | _submitInI(ftdipp); | ||
534 | osalOsRescheduleS(); | ||
535 | } | ||
536 | osalSysUnlock(); | ||
537 | |||
538 | r++; | ||
539 | if (--n == 0U) | ||
540 | return r; | ||
541 | |||
542 | osalSysLock(); | ||
543 | } | ||
544 | } | ||
545 | |||
546 | static msg_t _get_timeout(USBHFTDIPortDriver *ftdipp, systime_t timeout) { | ||
547 | uint8_t b; | ||
548 | |||
549 | osalSysLock(); | ||
550 | if (ftdipp->state != USBHFTDIP_STATE_READY) { | ||
551 | osalSysUnlock(); | ||
552 | return Q_RESET; | ||
553 | } | ||
554 | while (ftdipp->iq_counter == 0) { | ||
555 | if (!usbhURBIsBusy(&ftdipp->iq_urb)) | ||
556 | _submitInI(ftdipp); | ||
557 | msg_t msg = chThdEnqueueTimeoutS(&ftdipp->iq_waiting, timeout); | ||
558 | if (msg < Q_OK) { | ||
559 | osalSysUnlock(); | ||
560 | return msg; | ||
561 | } | ||
562 | } | ||
563 | b = *ftdipp->iq_ptr++; | ||
564 | if (--ftdipp->iq_counter == 0) { | ||
565 | _submitInI(ftdipp); | ||
566 | osalOsRescheduleS(); | ||
567 | } | ||
568 | osalSysUnlock(); | ||
569 | |||
570 | return (msg_t)b; | ||
571 | } | ||
572 | |||
573 | static msg_t _get(USBHFTDIPortDriver *ftdipp) { | ||
574 | return _get_timeout(ftdipp, TIME_INFINITE); | ||
575 | } | ||
576 | |||
577 | static size_t _read(USBHFTDIPortDriver *ftdipp, uint8_t *bp, size_t n) { | ||
578 | return _read_timeout(ftdipp, bp, n, TIME_INFINITE); | ||
579 | } | ||
580 | |||
581 | static msg_t _ctl(USBHFTDIPortDriver *ftdipp, unsigned int operation, void *arg) { | ||
582 | (void)ftdipp; | ||
583 | (void)operation; | ||
584 | (void)arg; | ||
585 | return MSG_OK; | ||
586 | } | ||
587 | |||
588 | static void _vt(void *p) { | ||
589 | USBHFTDIPortDriver *const ftdipp = (USBHFTDIPortDriver *)p; | ||
590 | osalSysLockFromISR(); | ||
591 | uint32_t len = ftdipp->oq_ptr - ftdipp->oq_buff; | ||
592 | if (len && !usbhURBIsBusy(&ftdipp->oq_urb)) { | ||
593 | _submitOutI(ftdipp, len); | ||
594 | } | ||
595 | if ((ftdipp->iq_counter == 0) && !usbhURBIsBusy(&ftdipp->iq_urb)) { | ||
596 | _submitInI(ftdipp); | ||
597 | } | ||
598 | chVTSetI(&ftdipp->vt, OSAL_MS2I(16), _vt, ftdipp); | ||
599 | osalSysUnlockFromISR(); | ||
600 | } | ||
601 | |||
602 | static const struct FTDIPortDriverVMT async_channel_vmt = { | ||
603 | (size_t)0, | ||
604 | (size_t (*)(void *, const uint8_t *, size_t))_write, | ||
605 | (size_t (*)(void *, uint8_t *, size_t))_read, | ||
606 | (msg_t (*)(void *, uint8_t))_put, | ||
607 | (msg_t (*)(void *))_get, | ||
608 | (msg_t (*)(void *, uint8_t, systime_t))_put_timeout, | ||
609 | (msg_t (*)(void *, systime_t))_get_timeout, | ||
610 | (size_t (*)(void *, const uint8_t *, size_t, systime_t))_write_timeout, | ||
611 | (size_t (*)(void *, uint8_t *, size_t, systime_t))_read_timeout, | ||
612 | (msg_t (*)(void *, unsigned int, void *))_ctl | ||
613 | }; | ||
614 | |||
615 | |||
616 | static void _stopS(USBHFTDIPortDriver *ftdipp) { | ||
617 | if (ftdipp->state != USBHFTDIP_STATE_READY) | ||
618 | return; | ||
619 | chVTResetI(&ftdipp->vt); | ||
620 | usbhEPCloseS(&ftdipp->epin); | ||
621 | usbhEPCloseS(&ftdipp->epout); | ||
622 | chThdDequeueAllI(&ftdipp->iq_waiting, Q_RESET); | ||
623 | chThdDequeueAllI(&ftdipp->oq_waiting, Q_RESET); | ||
624 | ftdipp->state = USBHFTDIP_STATE_ACTIVE; | ||
625 | osalOsRescheduleS(); | ||
626 | } | ||
627 | |||
628 | void usbhftdipStop(USBHFTDIPortDriver *ftdipp) { | ||
629 | osalDbgCheck((ftdipp->state == USBHFTDIP_STATE_ACTIVE) | ||
630 | || (ftdipp->state == USBHFTDIP_STATE_READY)); | ||
631 | |||
632 | osalSysLock(); | ||
633 | chMtxLockS(&ftdipp->ftdip->mtx); | ||
634 | _stopS(ftdipp); | ||
635 | chMtxUnlockS(&ftdipp->ftdip->mtx); | ||
636 | osalSysUnlock(); | ||
637 | } | ||
638 | |||
639 | void usbhftdipStart(USBHFTDIPortDriver *ftdipp, const USBHFTDIPortConfig *config) { | ||
640 | static const USBHFTDIPortConfig default_config = { | ||
641 | HAL_USBHFTDI_DEFAULT_SPEED, | ||
642 | HAL_USBHFTDI_DEFAULT_FRAMING, | ||
643 | HAL_USBHFTDI_DEFAULT_HANDSHAKE, | ||
644 | HAL_USBHFTDI_DEFAULT_XON, | ||
645 | HAL_USBHFTDI_DEFAULT_XOFF | ||
646 | }; | ||
647 | |||
648 | osalDbgCheck((ftdipp->state == USBHFTDIP_STATE_ACTIVE) | ||
649 | || (ftdipp->state == USBHFTDIP_STATE_READY)); | ||
650 | |||
651 | if (ftdipp->state == USBHFTDIP_STATE_READY) | ||
652 | return; | ||
653 | |||
654 | osalMutexLock(&ftdipp->ftdip->mtx); | ||
655 | if (config == NULL) | ||
656 | config = &default_config; | ||
657 | |||
658 | uint16_t wValue = 0; | ||
659 | _ftdi_port_control(ftdipp, FTDI_COMMAND_RESET, FTDI_RESET_ALL, 0, 0, NULL); | ||
660 | _set_baudrate(ftdipp, config->speed); | ||
661 | _ftdi_port_control(ftdipp, FTDI_COMMAND_SETDATA, config->framing, 0, 0, NULL); | ||
662 | if (config->handshake & USBHFTDI_HANDSHAKE_XON_XOFF) | ||
663 | wValue = (config->xoff_character << 8) | config->xon_character; | ||
664 | _ftdi_port_control(ftdipp, FTDI_COMMAND_SETFLOW, wValue, config->handshake, 0, NULL); | ||
665 | |||
666 | usbhURBObjectInit(&ftdipp->oq_urb, &ftdipp->epout, _out_cb, ftdipp, ftdipp->oq_buff, 0); | ||
667 | chThdQueueObjectInit(&ftdipp->oq_waiting); | ||
668 | ftdipp->oq_counter = 64; | ||
669 | ftdipp->oq_ptr = ftdipp->oq_buff; | ||
670 | usbhEPOpen(&ftdipp->epout); | ||
671 | |||
672 | usbhURBObjectInit(&ftdipp->iq_urb, &ftdipp->epin, _in_cb, ftdipp, ftdipp->iq_buff, 64); | ||
673 | chThdQueueObjectInit(&ftdipp->iq_waiting); | ||
674 | ftdipp->iq_counter = 0; | ||
675 | ftdipp->iq_ptr = ftdipp->iq_buff; | ||
676 | usbhEPOpen(&ftdipp->epin); | ||
677 | usbhURBSubmit(&ftdipp->iq_urb); | ||
678 | |||
679 | chVTObjectInit(&ftdipp->vt); | ||
680 | chVTSet(&ftdipp->vt, OSAL_MS2I(16), _vt, ftdipp); | ||
681 | |||
682 | ftdipp->state = USBHFTDIP_STATE_READY; | ||
683 | osalMutexUnlock(&ftdipp->ftdip->mtx); | ||
684 | } | ||
685 | |||
686 | static void _ftdi_object_init(USBHFTDIDriver *ftdip) { | ||
687 | osalDbgCheck(ftdip != NULL); | ||
688 | memset(ftdip, 0, sizeof(*ftdip)); | ||
689 | ftdip->info = &usbhftdiClassDriverInfo; | ||
690 | osalMutexObjectInit(&ftdip->mtx); | ||
691 | } | ||
692 | |||
693 | static void _ftdip_object_init(USBHFTDIPortDriver *ftdipp) { | ||
694 | osalDbgCheck(ftdipp != NULL); | ||
695 | memset(ftdipp, 0, sizeof(*ftdipp)); | ||
696 | ftdipp->vmt = &async_channel_vmt; | ||
697 | ftdipp->state = USBHFTDIP_STATE_STOP; | ||
698 | } | ||
699 | |||
700 | static void _ftdi_init(void) { | ||
701 | uint8_t i; | ||
702 | for (i = 0; i < HAL_USBHFTDI_MAX_INSTANCES; i++) { | ||
703 | _ftdi_object_init(&USBHFTDID[i]); | ||
704 | } | ||
705 | for (i = 0; i < HAL_USBHFTDI_MAX_PORTS; i++) { | ||
706 | _ftdip_object_init(&FTDIPD[i]); | ||
707 | } | ||
708 | } | ||
709 | |||
710 | #endif | ||
diff --git a/lib/chibios-contrib/os/hal/src/usbh/hal_usbh_hid.c b/lib/chibios-contrib/os/hal/src/usbh/hal_usbh_hid.c new file mode 100644 index 000000000..f381b90ec --- /dev/null +++ b/lib/chibios-contrib/os/hal/src/usbh/hal_usbh_hid.c | |||
@@ -0,0 +1,311 @@ | |||
1 | /* | ||
2 | ChibiOS - Copyright (C) 2006..2017 Giovanni Di Sirio | ||
3 | Copyright (C) 2015..2019 Diego Ismirlian, (dismirlian(at)google's mail) | ||
4 | |||
5 | Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | you may not use this file except in compliance with the License. | ||
7 | You may obtain a copy of the License at | ||
8 | |||
9 | http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | |||
11 | Unless required by applicable law or agreed to in writing, software | ||
12 | distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | See the License for the specific language governing permissions and | ||
15 | limitations under the License. | ||
16 | */ | ||
17 | |||
18 | #include "hal.h" | ||
19 | |||
20 | #if HAL_USBH_USE_HID | ||
21 | |||
22 | #if !HAL_USE_USBH | ||
23 | #error "USBHHID needs USBH" | ||
24 | #endif | ||
25 | |||
26 | #include <string.h> | ||
27 | #include "usbh/dev/hid.h" | ||
28 | #include "usbh/internal.h" | ||
29 | |||
30 | #define _USBH_DEBUG_HELPER_CLASS_DRIVER hidp | ||
31 | #define _USBH_DEBUG_HELPER_ENABLE_TRACE USBHHID_DEBUG_ENABLE_TRACE | ||
32 | #define _USBH_DEBUG_HELPER_ENABLE_INFO USBHHID_DEBUG_ENABLE_INFO | ||
33 | #define _USBH_DEBUG_HELPER_ENABLE_WARNINGS USBHHID_DEBUG_ENABLE_WARNINGS | ||
34 | #define _USBH_DEBUG_HELPER_ENABLE_ERRORS USBHHID_DEBUG_ENABLE_ERRORS | ||
35 | #include "usbh/debug_helpers.h" | ||
36 | |||
37 | #define USBH_HID_REQ_GET_REPORT 0x01 | ||
38 | #define USBH_HID_REQ_GET_IDLE 0x02 | ||
39 | #define USBH_HID_REQ_GET_PROTOCOL 0x03 | ||
40 | #define USBH_HID_REQ_SET_REPORT 0x09 | ||
41 | #define USBH_HID_REQ_SET_IDLE 0x0A | ||
42 | #define USBH_HID_REQ_SET_PROTOCOL 0x0B | ||
43 | |||
44 | /*===========================================================================*/ | ||
45 | /* USB Class driver loader for HID */ | ||
46 | /*===========================================================================*/ | ||
47 | |||
48 | USBHHIDDriver USBHHIDD[HAL_USBHHID_MAX_INSTANCES]; | ||
49 | |||
50 | static void _hid_init(void); | ||
51 | static usbh_baseclassdriver_t *_hid_load(usbh_device_t *dev, const uint8_t *descriptor, uint16_t rem); | ||
52 | static void _hid_unload(usbh_baseclassdriver_t *drv); | ||
53 | static void _stop_locked(USBHHIDDriver *hidp); | ||
54 | |||
55 | static const usbh_classdriver_vmt_t class_driver_vmt = { | ||
56 | _hid_init, | ||
57 | _hid_load, | ||
58 | _hid_unload | ||
59 | }; | ||
60 | |||
61 | const usbh_classdriverinfo_t usbhhidClassDriverInfo = { | ||
62 | "HID", &class_driver_vmt | ||
63 | }; | ||
64 | |||
65 | static usbh_baseclassdriver_t *_hid_load(usbh_device_t *dev, const uint8_t *descriptor, uint16_t rem) { | ||
66 | int i; | ||
67 | USBHHIDDriver *hidp; | ||
68 | |||
69 | if (_usbh_match_descriptor(descriptor, rem, USBH_DT_INTERFACE, | ||
70 | 0x03, -1, -1) != HAL_SUCCESS) | ||
71 | return NULL; | ||
72 | |||
73 | const usbh_interface_descriptor_t * const ifdesc = (const usbh_interface_descriptor_t *)descriptor; | ||
74 | |||
75 | if ((ifdesc->bAlternateSetting != 0) | ||
76 | || (ifdesc->bNumEndpoints < 1)) { | ||
77 | return NULL; | ||
78 | } | ||
79 | |||
80 | |||
81 | /* alloc driver */ | ||
82 | for (i = 0; i < HAL_USBHHID_MAX_INSTANCES; i++) { | ||
83 | if (USBHHIDD[i].dev == NULL) { | ||
84 | hidp = &USBHHIDD[i]; | ||
85 | goto alloc_ok; | ||
86 | } | ||
87 | } | ||
88 | |||
89 | udevwarn("Can't alloc HID driver"); | ||
90 | |||
91 | /* can't alloc */ | ||
92 | return NULL; | ||
93 | |||
94 | alloc_ok: | ||
95 | /* initialize the driver's variables */ | ||
96 | hidp->epin.status = USBH_EPSTATUS_UNINITIALIZED; | ||
97 | #if HAL_USBHHID_USE_INTERRUPT_OUT | ||
98 | hidp->epout.status = USBH_EPSTATUS_UNINITIALIZED; | ||
99 | #endif | ||
100 | hidp->ifnum = ifdesc->bInterfaceNumber; | ||
101 | usbhEPSetName(&dev->ctrl, "HID[CTRL]"); | ||
102 | |||
103 | /* parse the configuration descriptor */ | ||
104 | if_iterator_t iif; | ||
105 | generic_iterator_t iep; | ||
106 | iif.iad = 0; | ||
107 | iif.curr = descriptor; | ||
108 | iif.rem = rem; | ||
109 | for (ep_iter_init(&iep, &iif); iep.valid; ep_iter_next(&iep)) { | ||
110 | const usbh_endpoint_descriptor_t *const epdesc = ep_get(&iep); | ||
111 | if ((epdesc->bEndpointAddress & 0x80) && (epdesc->bmAttributes == USBH_EPTYPE_INT)) { | ||
112 | udevinfof("INT IN endpoint found: bEndpointAddress=%02x", epdesc->bEndpointAddress); | ||
113 | usbhEPObjectInit(&hidp->epin, dev, epdesc); | ||
114 | usbhEPSetName(&hidp->epin, "HID[IIN ]"); | ||
115 | #if HAL_USBHHID_USE_INTERRUPT_OUT | ||
116 | } else if (((epdesc->bEndpointAddress & 0x80) == 0) | ||
117 | && (epdesc->bmAttributes == USBH_EPTYPE_INT)) { | ||
118 | uinfof("INT OUT endpoint found: bEndpointAddress=%02x", epdesc->bEndpointAddress); | ||
119 | usbhEPObjectInit(&hidp->epout, dev, epdesc); | ||
120 | usbhEPSetName(&hidp->epout, "HID[IOUT]"); | ||
121 | #endif | ||
122 | } else { | ||
123 | udevinfof("unsupported endpoint found: bEndpointAddress=%02x, bmAttributes=%02x", | ||
124 | epdesc->bEndpointAddress, epdesc->bmAttributes); | ||
125 | } | ||
126 | } | ||
127 | if (hidp->epin.status != USBH_EPSTATUS_CLOSED) { | ||
128 | goto deinit; | ||
129 | } | ||
130 | |||
131 | if (ifdesc->bInterfaceSubClass != 0x01) { | ||
132 | hidp->type = USBHHID_DEVTYPE_GENERIC; | ||
133 | udevinfof("HID: bInterfaceSubClass=%02x, generic HID", ifdesc->bInterfaceSubClass); | ||
134 | if (ifdesc->bInterfaceSubClass != 0x00) { | ||
135 | udevinfof("HID: bInterfaceSubClass=%02x is an invalid bInterfaceSubClass value", | ||
136 | ifdesc->bInterfaceSubClass); | ||
137 | } | ||
138 | } else if (ifdesc->bInterfaceProtocol == 0x01) { | ||
139 | hidp->type = USBHHID_DEVTYPE_BOOT_KEYBOARD; | ||
140 | udevinfo("HID: BOOT protocol keyboard found"); | ||
141 | } else if (ifdesc->bInterfaceProtocol == 0x02) { | ||
142 | hidp->type = USBHHID_DEVTYPE_BOOT_MOUSE; | ||
143 | udevinfo("HID: BOOT protocol mouse found"); | ||
144 | } else { | ||
145 | udeverrf("HID: bInterfaceProtocol=%02x is an invalid boot protocol, abort", | ||
146 | ifdesc->bInterfaceProtocol); | ||
147 | goto deinit; | ||
148 | } | ||
149 | |||
150 | hidp->state = USBHHID_STATE_ACTIVE; | ||
151 | |||
152 | return (usbh_baseclassdriver_t *)hidp; | ||
153 | |||
154 | deinit: | ||
155 | /* Here, the enpoints are closed, and the driver is unlinked */ | ||
156 | return NULL; | ||
157 | } | ||
158 | |||
159 | static void _hid_unload(usbh_baseclassdriver_t *drv) { | ||
160 | USBHHIDDriver *const hidp = (USBHHIDDriver *)drv; | ||
161 | chSemWait(&hidp->sem); | ||
162 | _stop_locked(hidp); | ||
163 | hidp->state = USBHHID_STATE_STOP; | ||
164 | chSemSignal(&hidp->sem); | ||
165 | } | ||
166 | |||
167 | static void _in_cb(usbh_urb_t *urb) { | ||
168 | USBHHIDDriver *const hidp = (USBHHIDDriver *)urb->userData; | ||
169 | switch (urb->status) { | ||
170 | case USBH_URBSTATUS_OK: | ||
171 | if (hidp->config->cb_report) { | ||
172 | hidp->config->cb_report(hidp, urb->actualLength); | ||
173 | } | ||
174 | break; | ||
175 | case USBH_URBSTATUS_DISCONNECTED: | ||
176 | uurbwarn("HID: URB IN disconnected"); | ||
177 | |||
178 | return; | ||
179 | case USBH_URBSTATUS_TIMEOUT: | ||
180 | //no data | ||
181 | break; | ||
182 | default: | ||
183 | uurberrf("HID: URB IN status unexpected = %d", urb->status); | ||
184 | break; | ||
185 | } | ||
186 | usbhURBObjectResetI(&hidp->in_urb); | ||
187 | usbhURBSubmitI(&hidp->in_urb); | ||
188 | } | ||
189 | |||
190 | void usbhhidStart(USBHHIDDriver *hidp, const USBHHIDConfig *cfg) { | ||
191 | osalDbgCheck(hidp && cfg); | ||
192 | osalDbgCheck(cfg->report_buffer && (cfg->protocol <= USBHHID_PROTOCOL_REPORT)); | ||
193 | |||
194 | chSemWait(&hidp->sem); | ||
195 | if (hidp->state == USBHHID_STATE_READY) { | ||
196 | chSemSignal(&hidp->sem); | ||
197 | return; | ||
198 | } | ||
199 | osalDbgCheck(hidp->state == USBHHID_STATE_ACTIVE); | ||
200 | |||
201 | hidp->config = cfg; | ||
202 | |||
203 | /* init the URBs */ | ||
204 | uint32_t report_len = hidp->epin.wMaxPacketSize; | ||
205 | if (report_len > cfg->report_len) | ||
206 | report_len = cfg->report_len; | ||
207 | usbhURBObjectInit(&hidp->in_urb, &hidp->epin, _in_cb, hidp, | ||
208 | cfg->report_buffer, report_len); | ||
209 | |||
210 | /* open the int IN/OUT endpoints */ | ||
211 | usbhEPOpen(&hidp->epin); | ||
212 | #if HAL_USBHHID_USE_INTERRUPT_OUT | ||
213 | if (hidp->epout.status == USBH_EPSTATUS_CLOSED) { | ||
214 | usbhEPOpen(&hidp->epout); | ||
215 | } | ||
216 | #endif | ||
217 | |||
218 | usbhhidSetProtocol(hidp, cfg->protocol); | ||
219 | |||
220 | usbhURBSubmit(&hidp->in_urb); | ||
221 | |||
222 | hidp->state = USBHHID_STATE_READY; | ||
223 | chSemSignal(&hidp->sem); | ||
224 | } | ||
225 | |||
226 | static void _stop_locked(USBHHIDDriver *hidp) { | ||
227 | if (hidp->state == USBHHID_STATE_ACTIVE) | ||
228 | return; | ||
229 | |||
230 | osalDbgCheck(hidp->state == USBHHID_STATE_READY); | ||
231 | |||
232 | usbhEPClose(&hidp->epin); | ||
233 | #if HAL_USBHHID_USE_INTERRUPT_OUT | ||
234 | if (hidp->epout.status != USBH_EPSTATUS_UNINITIALIZED) { | ||
235 | usbhEPClose(&hidp->epout); | ||
236 | } | ||
237 | #endif | ||
238 | hidp->state = USBHHID_STATE_ACTIVE; | ||
239 | } | ||
240 | |||
241 | void usbhhidStop(USBHHIDDriver *hidp) { | ||
242 | chSemWait(&hidp->sem); | ||
243 | _stop_locked(hidp); | ||
244 | chSemSignal(&hidp->sem); | ||
245 | } | ||
246 | |||
247 | usbh_urbstatus_t usbhhidGetReport(USBHHIDDriver *hidp, | ||
248 | uint8_t report_id, usbhhid_reporttype_t report_type, | ||
249 | void *data, uint16_t len) { | ||
250 | osalDbgCheck(hidp); | ||
251 | osalDbgAssert((uint8_t)report_type <= USBHHID_REPORTTYPE_FEATURE, "wrong report type"); | ||
252 | return usbhControlRequest(hidp->dev, | ||
253 | USBH_REQTYPE_CLASSIN(USBH_REQTYPE_RECIP_INTERFACE), USBH_HID_REQ_GET_REPORT, | ||
254 | ((uint8_t)report_type << 8) | report_id, hidp->ifnum, len, data); | ||
255 | } | ||
256 | |||
257 | usbh_urbstatus_t usbhhidSetReport(USBHHIDDriver *hidp, | ||
258 | uint8_t report_id, usbhhid_reporttype_t report_type, | ||
259 | const void *data, uint16_t len) { | ||
260 | osalDbgCheck(hidp); | ||
261 | osalDbgAssert((uint8_t)report_type <= USBHHID_REPORTTYPE_FEATURE, "wrong report type"); | ||
262 | return usbhControlRequest(hidp->dev, | ||
263 | USBH_REQTYPE_CLASSOUT(USBH_REQTYPE_RECIP_INTERFACE), USBH_HID_REQ_SET_REPORT, | ||
264 | ((uint8_t)report_type << 8) | report_id, hidp->ifnum, len, (void *)data); | ||
265 | } | ||
266 | |||
267 | usbh_urbstatus_t usbhhidGetIdle(USBHHIDDriver *hidp, uint8_t report_id, uint8_t *duration) { | ||
268 | osalDbgCheck(hidp); | ||
269 | return usbhControlRequest(hidp->dev, | ||
270 | USBH_REQTYPE_CLASSIN(USBH_REQTYPE_RECIP_INTERFACE), USBH_HID_REQ_GET_IDLE, | ||
271 | report_id, hidp->ifnum, 1, duration); | ||
272 | } | ||
273 | |||
274 | usbh_urbstatus_t usbhhidSetIdle(USBHHIDDriver *hidp, uint8_t report_id, uint8_t duration) { | ||
275 | osalDbgCheck(hidp); | ||
276 | return usbhControlRequest(hidp->dev, | ||
277 | USBH_REQTYPE_CLASSOUT(USBH_REQTYPE_RECIP_INTERFACE), USBH_HID_REQ_SET_IDLE, | ||
278 | (duration << 8) | report_id, hidp->ifnum, 0, NULL); | ||
279 | } | ||
280 | |||
281 | usbh_urbstatus_t usbhhidGetProtocol(USBHHIDDriver *hidp, uint8_t *protocol) { | ||
282 | osalDbgCheck(hidp); | ||
283 | return usbhControlRequest(hidp->dev, | ||
284 | USBH_REQTYPE_CLASSIN(USBH_REQTYPE_RECIP_INTERFACE), USBH_HID_REQ_GET_PROTOCOL, | ||
285 | 0, hidp->ifnum, 1, protocol); | ||
286 | } | ||
287 | |||
288 | usbh_urbstatus_t usbhhidSetProtocol(USBHHIDDriver *hidp, uint8_t protocol) { | ||
289 | osalDbgCheck(hidp); | ||
290 | osalDbgAssert(protocol <= 1, "invalid protocol"); | ||
291 | return usbhControlRequest(hidp->dev, | ||
292 | USBH_REQTYPE_CLASSOUT(USBH_REQTYPE_RECIP_INTERFACE), USBH_HID_REQ_SET_PROTOCOL, | ||
293 | protocol, hidp->ifnum, 0, NULL); | ||
294 | } | ||
295 | |||
296 | static void _hid_object_init(USBHHIDDriver *hidp) { | ||
297 | osalDbgCheck(hidp != NULL); | ||
298 | memset(hidp, 0, sizeof(*hidp)); | ||
299 | hidp->info = &usbhhidClassDriverInfo; | ||
300 | hidp->state = USBHHID_STATE_STOP; | ||
301 | chSemObjectInit(&hidp->sem, 1); | ||
302 | } | ||
303 | |||
304 | static void _hid_init(void) { | ||
305 | uint8_t i; | ||
306 | for (i = 0; i < HAL_USBHHID_MAX_INSTANCES; i++) { | ||
307 | _hid_object_init(&USBHHIDD[i]); | ||
308 | } | ||
309 | } | ||
310 | |||
311 | #endif | ||
diff --git a/lib/chibios-contrib/os/hal/src/usbh/hal_usbh_hub.c b/lib/chibios-contrib/os/hal/src/usbh/hal_usbh_hub.c new file mode 100644 index 000000000..b8937fad7 --- /dev/null +++ b/lib/chibios-contrib/os/hal/src/usbh/hal_usbh_hub.c | |||
@@ -0,0 +1,279 @@ | |||
1 | /* | ||
2 | ChibiOS - Copyright (C) 2006..2017 Giovanni Di Sirio | ||
3 | Copyright (C) 2015..2019 Diego Ismirlian, (dismirlian(at)google's mail) | ||
4 | |||
5 | Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | you may not use this file except in compliance with the License. | ||
7 | You may obtain a copy of the License at | ||
8 | |||
9 | http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | |||
11 | Unless required by applicable law or agreed to in writing, software | ||
12 | distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | See the License for the specific language governing permissions and | ||
15 | limitations under the License. | ||
16 | */ | ||
17 | |||
18 | #include "hal.h" | ||
19 | |||
20 | #if HAL_USBH_USE_HUB | ||
21 | |||
22 | #if !HAL_USE_USBH | ||
23 | #error "USBHHUB needs HAL_USE_USBH" | ||
24 | #endif | ||
25 | |||
26 | #include <string.h> | ||
27 | #include "usbh/dev/hub.h" | ||
28 | #include "usbh/internal.h" | ||
29 | |||
30 | #define _USBH_DEBUG_HELPER_CLASS_DRIVER hubp | ||
31 | #define _USBH_DEBUG_HELPER_ENABLE_TRACE USBHHUB_DEBUG_ENABLE_TRACE | ||
32 | #define _USBH_DEBUG_HELPER_ENABLE_INFO USBHHUB_DEBUG_ENABLE_INFO | ||
33 | #define _USBH_DEBUG_HELPER_ENABLE_WARNINGS USBHHUB_DEBUG_ENABLE_WARNINGS | ||
34 | #define _USBH_DEBUG_HELPER_ENABLE_ERRORS USBHHUB_DEBUG_ENABLE_ERRORS | ||
35 | #include "usbh/debug_helpers.h" | ||
36 | |||
37 | USBHHubDriver USBHHUBD[HAL_USBHHUB_MAX_INSTANCES]; | ||
38 | static usbh_port_t USBHPorts[HAL_USBHHUB_MAX_PORTS]; | ||
39 | |||
40 | static void _hub_init(void); | ||
41 | static usbh_baseclassdriver_t *_hub_load(usbh_device_t *dev, const uint8_t *descriptor, uint16_t rem); | ||
42 | static void _hub_unload(usbh_baseclassdriver_t *drv); | ||
43 | static const usbh_classdriver_vmt_t usbhhubClassDriverVMT = { | ||
44 | _hub_init, | ||
45 | _hub_load, | ||
46 | _hub_unload | ||
47 | }; | ||
48 | |||
49 | const usbh_classdriverinfo_t usbhhubClassDriverInfo = { | ||
50 | "HUB", &usbhhubClassDriverVMT | ||
51 | }; | ||
52 | |||
53 | |||
54 | void _usbhub_port_object_init(usbh_port_t *port, USBHDriver *usbh, | ||
55 | USBHHubDriver *hub, uint8_t number) { | ||
56 | memset(port, 0, sizeof(*port)); | ||
57 | port->number = number; | ||
58 | port->device.host = usbh; | ||
59 | port->hub = hub; | ||
60 | } | ||
61 | |||
62 | usbh_urbstatus_t usbhhubControlRequest(USBHDriver *host, USBHHubDriver *hub, | ||
63 | uint8_t bmRequestType, | ||
64 | uint8_t bRequest, | ||
65 | uint16_t wValue, | ||
66 | uint16_t wIndex, | ||
67 | uint16_t wLength, | ||
68 | uint8_t *buf) { | ||
69 | if (hub == NULL) | ||
70 | return usbh_lld_root_hub_request(host, bmRequestType, bRequest, wValue, wIndex, wLength, buf); | ||
71 | |||
72 | return usbhControlRequest(hub->dev, | ||
73 | bmRequestType, bRequest, wValue, wIndex, wLength, buf); | ||
74 | } | ||
75 | |||
76 | |||
77 | static void _urb_complete(usbh_urb_t *urb) { | ||
78 | |||
79 | USBHHubDriver *const hubdp = (USBHHubDriver *)urb->userData; | ||
80 | switch (urb->status) { | ||
81 | case USBH_URBSTATUS_TIMEOUT: | ||
82 | /* the device NAKed */ | ||
83 | uurbdbg("HUB: no info"); | ||
84 | //hubdp->statuschange = 0; | ||
85 | break; | ||
86 | case USBH_URBSTATUS_OK: { | ||
87 | uint8_t len = hubdp->hubDesc.bNbrPorts / 8 + 1; | ||
88 | if (urb->actualLength != len) { | ||
89 | uurbwarnf("Expected %d status change bytes but got %d", len, urb->actualLength); | ||
90 | } | ||
91 | |||
92 | if (urb->actualLength < len) | ||
93 | len = urb->actualLength; | ||
94 | |||
95 | if (len > 4) | ||
96 | len = 4; | ||
97 | |||
98 | uint8_t *sc = (uint8_t *)&hubdp->statuschange; | ||
99 | uint8_t *r = hubdp->scbuff; | ||
100 | while (len--) | ||
101 | *sc++ |= *r++; | ||
102 | |||
103 | uurbinfof("HUB: change, %08x", hubdp->statuschange); | ||
104 | } break; | ||
105 | case USBH_URBSTATUS_DISCONNECTED: | ||
106 | uurbwarn("HUB: URB disconnected, aborting poll"); | ||
107 | return; | ||
108 | default: | ||
109 | uurberrf("HUB: URB status unexpected = %d", urb->status); | ||
110 | break; | ||
111 | } | ||
112 | |||
113 | usbhURBObjectResetI(urb); | ||
114 | usbhURBSubmitI(urb); | ||
115 | } | ||
116 | |||
117 | static usbh_baseclassdriver_t *_hub_load(usbh_device_t *dev, | ||
118 | const uint8_t *descriptor, uint16_t rem) { | ||
119 | int i; | ||
120 | |||
121 | USBHHubDriver *hubdp; | ||
122 | |||
123 | if (_usbh_match_descriptor(descriptor, rem, USBH_DT_DEVICE, | ||
124 | 0x09, 0x00, 0x00) != HAL_SUCCESS) | ||
125 | return NULL; | ||
126 | |||
127 | generic_iterator_t iep, icfg; | ||
128 | if_iterator_t iif; | ||
129 | |||
130 | cfg_iter_init(&icfg, dev->fullConfigurationDescriptor, | ||
131 | dev->basicConfigDesc.wTotalLength); | ||
132 | |||
133 | if_iter_init(&iif, &icfg); | ||
134 | if (!iif.valid) | ||
135 | return NULL; | ||
136 | |||
137 | if (_usbh_match_descriptor(iif.curr, iif.rem, USBH_DT_INTERFACE, | ||
138 | 0x09, 0x00, 0x00) != HAL_SUCCESS) | ||
139 | return NULL; | ||
140 | |||
141 | ep_iter_init(&iep, &iif); | ||
142 | if (!iep.valid) | ||
143 | return NULL; | ||
144 | const usbh_endpoint_descriptor_t *const epdesc = ep_get(&iep); | ||
145 | if ((epdesc->bmAttributes & 0x03) != USBH_EPTYPE_INT) { | ||
146 | return NULL; | ||
147 | } | ||
148 | |||
149 | |||
150 | /* alloc driver */ | ||
151 | for (i = 0; i < HAL_USBHHUB_MAX_INSTANCES; i++) { | ||
152 | if (USBHHUBD[i].dev == NULL) { | ||
153 | hubdp = &USBHHUBD[i]; | ||
154 | goto alloc_ok; | ||
155 | } | ||
156 | } | ||
157 | |||
158 | udevwarn("Can't alloc HUB driver"); | ||
159 | |||
160 | /* can't alloc */ | ||
161 | return NULL; | ||
162 | |||
163 | alloc_ok: | ||
164 | /* initialize the driver's variables */ | ||
165 | hubdp->epint.status = USBH_EPSTATUS_UNINITIALIZED; | ||
166 | hubdp->dev = dev; | ||
167 | hubdp->ports = 0; | ||
168 | |||
169 | usbhEPSetName(&dev->ctrl, "HUB[CTRL]"); | ||
170 | |||
171 | /* read Hub descriptor */ | ||
172 | udevinfo("Read Hub descriptor"); | ||
173 | if (usbhhubControlRequest(dev->host, hubdp, | ||
174 | USBH_REQTYPE_DIR_IN | USBH_REQTYPE_TYPE_CLASS | USBH_REQTYPE_RECIP_DEVICE, | ||
175 | USBH_REQ_GET_DESCRIPTOR, | ||
176 | (USBH_DT_HUB << 8), 0, sizeof(hubdp->hubDesc), | ||
177 | (uint8_t *)&hubdp->hubDesc) != USBH_URBSTATUS_OK) { | ||
178 | hubdp->dev = NULL; | ||
179 | return NULL; | ||
180 | } | ||
181 | |||
182 | const usbh_hub_descriptor_t *const hubdesc = &hubdp->hubDesc; | ||
183 | |||
184 | udevinfof("Hub descriptor loaded; %d ports, wHubCharacteristics=%04x, bPwrOn2PwrGood=%d, bHubContrCurrent=%d", | ||
185 | hubdesc->bNbrPorts, | ||
186 | hubdesc->wHubCharacteristics, | ||
187 | hubdesc->bPwrOn2PwrGood, | ||
188 | hubdesc->bHubContrCurrent); | ||
189 | |||
190 | /* Alloc ports */ | ||
191 | uint8_t ports = hubdesc->bNbrPorts; | ||
192 | for (i = 0; (ports > 0) && (i < HAL_USBHHUB_MAX_PORTS); i++) { | ||
193 | if (USBHPorts[i].hub == NULL) { | ||
194 | udevinfof("Alloc port %d", ports); | ||
195 | _usbhub_port_object_init(&USBHPorts[i], dev->host, hubdp, ports); | ||
196 | USBHPorts[i].next = hubdp->ports; | ||
197 | hubdp->ports = &USBHPorts[i]; | ||
198 | --ports; | ||
199 | } | ||
200 | } | ||
201 | |||
202 | if (ports) { | ||
203 | udevwarn("Could not alloc all ports"); | ||
204 | } | ||
205 | |||
206 | /* link hub to the host's list */ | ||
207 | list_add_tail(&hubdp->node, &dev->host->hubs); | ||
208 | |||
209 | /* enable power to ports */ | ||
210 | usbh_port_t *port = hubdp->ports; | ||
211 | while (port) { | ||
212 | udevinfof("Enable power for port %d", port->number); | ||
213 | usbhhubSetFeaturePort(port, USBH_PORT_FEAT_POWER); | ||
214 | port = port->next; | ||
215 | } | ||
216 | |||
217 | if (hubdesc->bPwrOn2PwrGood) | ||
218 | osalThreadSleepMilliseconds(2 * hubdesc->bPwrOn2PwrGood); | ||
219 | |||
220 | /* initialize the status change endpoint and trigger the first transfer */ | ||
221 | usbhEPObjectInit(&hubdp->epint, dev, epdesc); | ||
222 | usbhEPSetName(&hubdp->epint, "HUB[INT ]"); | ||
223 | usbhEPOpen(&hubdp->epint); | ||
224 | |||
225 | usbhURBObjectInit(&hubdp->urb, &hubdp->epint, | ||
226 | _urb_complete, hubdp, hubdp->scbuff, | ||
227 | (hubdesc->bNbrPorts + 8) / 8); | ||
228 | |||
229 | usbhURBSubmit(&hubdp->urb); | ||
230 | |||
231 | hubdp->dev = NULL; | ||
232 | return (usbh_baseclassdriver_t *)hubdp; | ||
233 | } | ||
234 | |||
235 | static void _hub_unload(usbh_baseclassdriver_t *drv) { | ||
236 | osalDbgCheck(drv != NULL); | ||
237 | USBHHubDriver *const hubdp = (USBHHubDriver *)drv; | ||
238 | |||
239 | /* close the status change endpoint (this cancels ongoing URBs) */ | ||
240 | usbhEPClose(&hubdp->epint); | ||
241 | |||
242 | /* de-alloc ports and unload drivers */ | ||
243 | usbh_port_t *port = hubdp->ports; | ||
244 | while (port) { | ||
245 | _usbh_port_disconnected(port); | ||
246 | port->hub = NULL; | ||
247 | port = port->next; | ||
248 | } | ||
249 | |||
250 | /* unlink the hub from the host's list */ | ||
251 | list_del(&hubdp->node); | ||
252 | |||
253 | } | ||
254 | |||
255 | static void _object_init(USBHHubDriver *hubdp) { | ||
256 | osalDbgCheck(hubdp != NULL); | ||
257 | memset(hubdp, 0, sizeof(*hubdp)); | ||
258 | hubdp->info = &usbhhubClassDriverInfo; | ||
259 | } | ||
260 | |||
261 | static void _hub_init(void) { | ||
262 | uint8_t i; | ||
263 | for (i = 0; i < HAL_USBHHUB_MAX_INSTANCES; i++) { | ||
264 | _object_init(&USBHHUBD[i]); | ||
265 | } | ||
266 | } | ||
267 | |||
268 | #else | ||
269 | |||
270 | #if HAL_USE_USBH | ||
271 | #include <string.h> | ||
272 | void _usbhub_port_object_init(usbh_port_t *port, USBHDriver *usbh, uint8_t number) { | ||
273 | memset(port, 0, sizeof(*port)); | ||
274 | port->number = number; | ||
275 | port->device.host = usbh; | ||
276 | } | ||
277 | #endif | ||
278 | |||
279 | #endif | ||
diff --git a/lib/chibios-contrib/os/hal/src/usbh/hal_usbh_msd.c b/lib/chibios-contrib/os/hal/src/usbh/hal_usbh_msd.c new file mode 100644 index 000000000..081cccb8a --- /dev/null +++ b/lib/chibios-contrib/os/hal/src/usbh/hal_usbh_msd.c | |||
@@ -0,0 +1,952 @@ | |||
1 | /* | ||
2 | ChibiOS - Copyright (C) 2006..2017 Giovanni Di Sirio | ||
3 | Copyright (C) 2015..2019 Diego Ismirlian, (dismirlian(at)google's mail) | ||
4 | |||
5 | Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | you may not use this file except in compliance with the License. | ||
7 | You may obtain a copy of the License at | ||
8 | |||
9 | http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | |||
11 | Unless required by applicable law or agreed to in writing, software | ||
12 | distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | See the License for the specific language governing permissions and | ||
15 | limitations under the License. | ||
16 | */ | ||
17 | |||
18 | #include "hal.h" | ||
19 | |||
20 | #if HAL_USBH_USE_MSD | ||
21 | |||
22 | #if !HAL_USE_USBH | ||
23 | #error "USBHMSD needs USBH" | ||
24 | #endif | ||
25 | |||
26 | #include <string.h> | ||
27 | #include "usbh/dev/msd.h" | ||
28 | #include "usbh/internal.h" | ||
29 | |||
30 | #define _USBH_DEBUG_HELPER_CLASS_DRIVER msdp | ||
31 | #define _USBH_DEBUG_HELPER_ENABLE_TRACE USBHMSD_DEBUG_ENABLE_TRACE | ||
32 | #define _USBH_DEBUG_HELPER_ENABLE_INFO USBHMSD_DEBUG_ENABLE_INFO | ||
33 | #define _USBH_DEBUG_HELPER_ENABLE_WARNINGS USBHMSD_DEBUG_ENABLE_WARNINGS | ||
34 | #define _USBH_DEBUG_HELPER_ENABLE_ERRORS USBHMSD_DEBUG_ENABLE_ERRORS | ||
35 | #include "usbh/debug_helpers.h" | ||
36 | |||
37 | static void _lun_object_deinit(USBHMassStorageLUNDriver *lunp); | ||
38 | |||
39 | /*===========================================================================*/ | ||
40 | /* USB Class driver loader for MSD */ | ||
41 | /*===========================================================================*/ | ||
42 | |||
43 | struct USBHMassStorageDriver { | ||
44 | /* inherited from abstract class driver */ | ||
45 | _usbh_base_classdriver_data | ||
46 | |||
47 | usbh_ep_t epin; | ||
48 | usbh_ep_t epout; | ||
49 | uint8_t ifnum; | ||
50 | uint8_t max_lun; | ||
51 | uint32_t tag; | ||
52 | |||
53 | USBHMassStorageLUNDriver *luns; | ||
54 | }; | ||
55 | |||
56 | static USBHMassStorageDriver USBHMSD[HAL_USBHMSD_MAX_INSTANCES]; | ||
57 | |||
58 | static void _msd_init(void); | ||
59 | static usbh_baseclassdriver_t *_msd_load(usbh_device_t *dev, const uint8_t *descriptor, uint16_t rem); | ||
60 | static void _msd_unload(usbh_baseclassdriver_t *drv); | ||
61 | |||
62 | static const usbh_classdriver_vmt_t class_driver_vmt = { | ||
63 | _msd_init, | ||
64 | _msd_load, | ||
65 | _msd_unload | ||
66 | }; | ||
67 | |||
68 | const usbh_classdriverinfo_t usbhmsdClassDriverInfo = { | ||
69 | "MSD", &class_driver_vmt | ||
70 | }; | ||
71 | |||
72 | #define MSD_REQ_RESET 0xFF | ||
73 | #define MSD_GET_MAX_LUN 0xFE | ||
74 | |||
75 | static usbh_baseclassdriver_t *_msd_load(usbh_device_t *dev, const uint8_t *descriptor, uint16_t rem) { | ||
76 | int i; | ||
77 | USBHMassStorageDriver *msdp; | ||
78 | uint8_t luns; | ||
79 | usbh_urbstatus_t stat; | ||
80 | |||
81 | if (_usbh_match_descriptor(descriptor, rem, USBH_DT_INTERFACE, | ||
82 | 0x08, 0x06, 0x50) != HAL_SUCCESS) | ||
83 | return NULL; | ||
84 | |||
85 | const usbh_interface_descriptor_t * const ifdesc = (const usbh_interface_descriptor_t *)descriptor; | ||
86 | |||
87 | if ((ifdesc->bAlternateSetting != 0) | ||
88 | || (ifdesc->bNumEndpoints < 2)) { | ||
89 | return NULL; | ||
90 | } | ||
91 | |||
92 | /* alloc driver */ | ||
93 | for (i = 0; i < HAL_USBHMSD_MAX_INSTANCES; i++) { | ||
94 | if (USBHMSD[i].dev == NULL) { | ||
95 | msdp = &USBHMSD[i]; | ||
96 | goto alloc_ok; | ||
97 | } | ||
98 | } | ||
99 | |||
100 | udevwarn("Can't alloc MSD driver"); | ||
101 | |||
102 | /* can't alloc */ | ||
103 | return NULL; | ||
104 | |||
105 | alloc_ok: | ||
106 | /* initialize the driver's variables */ | ||
107 | msdp->epin.status = USBH_EPSTATUS_UNINITIALIZED; | ||
108 | msdp->epout.status = USBH_EPSTATUS_UNINITIALIZED; | ||
109 | msdp->max_lun = 0; | ||
110 | msdp->tag = 0; | ||
111 | msdp->luns = 0; | ||
112 | msdp->ifnum = ifdesc->bInterfaceNumber; | ||
113 | usbhEPSetName(&dev->ctrl, "MSD[CTRL]"); | ||
114 | |||
115 | /* parse the configuration descriptor */ | ||
116 | if_iterator_t iif; | ||
117 | generic_iterator_t iep; | ||
118 | iif.iad = 0; | ||
119 | iif.curr = descriptor; | ||
120 | iif.rem = rem; | ||
121 | for (ep_iter_init(&iep, &iif); iep.valid; ep_iter_next(&iep)) { | ||
122 | const usbh_endpoint_descriptor_t *const epdesc = ep_get(&iep); | ||
123 | if ((epdesc->bEndpointAddress & 0x80) && (epdesc->bmAttributes == USBH_EPTYPE_BULK)) { | ||
124 | udevinfof("BULK IN endpoint found: bEndpointAddress=%02x", epdesc->bEndpointAddress); | ||
125 | usbhEPObjectInit(&msdp->epin, dev, epdesc); | ||
126 | usbhEPSetName(&msdp->epin, "MSD[BIN ]"); | ||
127 | } else if (((epdesc->bEndpointAddress & 0x80) == 0) | ||
128 | && (epdesc->bmAttributes == USBH_EPTYPE_BULK)) { | ||
129 | udevinfof("BULK OUT endpoint found: bEndpointAddress=%02x", epdesc->bEndpointAddress); | ||
130 | usbhEPObjectInit(&msdp->epout, dev, epdesc); | ||
131 | usbhEPSetName(&msdp->epout, "MSD[BOUT]"); | ||
132 | } else { | ||
133 | udevinfof("unsupported endpoint found: bEndpointAddress=%02x, bmAttributes=%02x", | ||
134 | epdesc->bEndpointAddress, epdesc->bmAttributes); | ||
135 | } | ||
136 | } | ||
137 | if ((msdp->epin.status != USBH_EPSTATUS_CLOSED) || (msdp->epout.status != USBH_EPSTATUS_CLOSED)) { | ||
138 | goto deinit; | ||
139 | } | ||
140 | |||
141 | /* read the number of LUNs */ | ||
142 | udevinfo("Reading Max LUN:"); | ||
143 | USBH_DEFINE_BUFFER(uint8_t buff[4]); | ||
144 | stat = usbhControlRequest(dev, | ||
145 | USBH_REQTYPE_CLASSIN(USBH_REQTYPE_RECIP_INTERFACE), | ||
146 | MSD_GET_MAX_LUN, 0, msdp->ifnum, 1, buff); | ||
147 | if (stat == USBH_URBSTATUS_OK) { | ||
148 | msdp->max_lun = buff[0] + 1; | ||
149 | udevinfof("\tmax_lun = %d", msdp->max_lun); | ||
150 | if (msdp->max_lun > HAL_USBHMSD_MAX_LUNS) { | ||
151 | msdp->max_lun = HAL_USBHMSD_MAX_LUNS; | ||
152 | udevwarnf("\tUsing max_lun = %d", msdp->max_lun); | ||
153 | } | ||
154 | } else if (stat == USBH_URBSTATUS_STALL) { | ||
155 | udevwarn("\tStall, max_lun = 1"); | ||
156 | msdp->max_lun = 1; | ||
157 | } else { | ||
158 | udeverr("\tError"); | ||
159 | goto deinit; | ||
160 | } | ||
161 | |||
162 | /* open the bulk IN/OUT endpoints */ | ||
163 | usbhEPOpen(&msdp->epin); | ||
164 | usbhEPOpen(&msdp->epout); | ||
165 | |||
166 | /* Alloc one block device per logical unit found */ | ||
167 | luns = msdp->max_lun; | ||
168 | for (i = 0; (luns > 0) && (i < HAL_USBHMSD_MAX_LUNS); i++) { | ||
169 | if (MSBLKD[i].msdp == NULL) { | ||
170 | /* link the new block driver to the list */ | ||
171 | MSBLKD[i].next = msdp->luns; | ||
172 | msdp->luns = &MSBLKD[i]; | ||
173 | MSBLKD[i].msdp = msdp; | ||
174 | MSBLKD[i].state = BLK_ACTIVE; | ||
175 | luns--; | ||
176 | } | ||
177 | } | ||
178 | |||
179 | return (usbh_baseclassdriver_t *)msdp; | ||
180 | |||
181 | deinit: | ||
182 | /* Here, the enpoints are closed, and the driver is unlinked */ | ||
183 | return NULL; | ||
184 | } | ||
185 | |||
186 | static void _msd_unload(usbh_baseclassdriver_t *drv) { | ||
187 | osalDbgCheck(drv != NULL); | ||
188 | USBHMassStorageDriver *const msdp = (USBHMassStorageDriver *)drv; | ||
189 | USBHMassStorageLUNDriver *lunp = msdp->luns; | ||
190 | |||
191 | /* disconnect all LUNs */ | ||
192 | while (lunp) { | ||
193 | usbhmsdLUNDisconnect(lunp); | ||
194 | _lun_object_deinit(lunp); | ||
195 | lunp = lunp->next; | ||
196 | } | ||
197 | |||
198 | usbhEPClose(&msdp->epin); | ||
199 | usbhEPClose(&msdp->epout); | ||
200 | } | ||
201 | |||
202 | |||
203 | /*===========================================================================*/ | ||
204 | /* MSD Class driver operations (Bulk-Only transport) */ | ||
205 | /*===========================================================================*/ | ||
206 | |||
207 | /* USB Bulk Only Transport SCSI Command block wrapper */ | ||
208 | typedef PACKED_STRUCT { | ||
209 | uint32_t dCBWSignature; | ||
210 | uint32_t dCBWTag; | ||
211 | uint32_t dCBWDataTransferLength; | ||
212 | uint8_t bmCBWFlags; | ||
213 | uint8_t bCBWLUN; | ||
214 | uint8_t bCBWCBLength; | ||
215 | uint8_t CBWCB[16]; | ||
216 | } msd_cbw_t; | ||
217 | #define MSD_CBW_SIGNATURE 0x43425355 | ||
218 | #define MSD_CBWFLAGS_D2H 0x80 | ||
219 | #define MSD_CBWFLAGS_H2D 0x00 | ||
220 | |||
221 | /* USB Bulk Only Transport SCSI Command status wrapper */ | ||
222 | typedef PACKED_STRUCT { | ||
223 | uint32_t dCSWSignature; | ||
224 | uint32_t dCSWTag; | ||
225 | uint32_t dCSWDataResidue; | ||
226 | uint8_t bCSWStatus; | ||
227 | } msd_csw_t; | ||
228 | #define MSD_CSW_SIGNATURE 0x53425355 | ||
229 | |||
230 | typedef struct { | ||
231 | msd_cbw_t *cbw; | ||
232 | uint8_t csw_status; | ||
233 | uint32_t data_processed; | ||
234 | } msd_transaction_t; | ||
235 | |||
236 | typedef enum { | ||
237 | MSD_BOTRESULT_OK, | ||
238 | MSD_BOTRESULT_DISCONNECTED, | ||
239 | MSD_BOTRESULT_ERROR | ||
240 | } msd_bot_result_t; | ||
241 | |||
242 | typedef enum { | ||
243 | MSD_RESULT_OK = MSD_BOTRESULT_OK, | ||
244 | MSD_RESULT_DISCONNECTED = MSD_BOTRESULT_DISCONNECTED, | ||
245 | MSD_RESULT_TRANSPORT_ERROR = MSD_BOTRESULT_ERROR, | ||
246 | MSD_RESULT_FAILED | ||
247 | } msd_result_t; | ||
248 | |||
249 | |||
250 | #define CSW_STATUS_PASSED 0 | ||
251 | #define CSW_STATUS_FAILED 1 | ||
252 | #define CSW_STATUS_PHASE_ERROR 2 | ||
253 | |||
254 | static bool _msd_bot_reset(USBHMassStorageDriver *msdp) { | ||
255 | |||
256 | usbh_urbstatus_t res; | ||
257 | res = usbhControlRequest(msdp->dev, | ||
258 | USBH_REQTYPE_CLASSOUT(USBH_REQTYPE_RECIP_INTERFACE), | ||
259 | 0xFF, 0, msdp->ifnum, 0, NULL); | ||
260 | if (res != USBH_URBSTATUS_OK) { | ||
261 | return FALSE; | ||
262 | } | ||
263 | |||
264 | osalThreadSleepMilliseconds(100); | ||
265 | |||
266 | return usbhEPReset(&msdp->epin) && usbhEPReset(&msdp->epout); | ||
267 | } | ||
268 | |||
269 | static msd_bot_result_t _msd_bot_transaction(msd_transaction_t *tran, USBHMassStorageLUNDriver *lunp, void *data) { | ||
270 | |||
271 | USBHMassStorageDriver *const msdp = lunp->msdp; | ||
272 | |||
273 | uint32_t data_actual_len, actual_len; | ||
274 | usbh_urbstatus_t status; | ||
275 | USBH_DEFINE_BUFFER(msd_csw_t csw); | ||
276 | |||
277 | tran->cbw->bCBWLUN = (uint8_t)(lunp - &msdp->luns[0]); | ||
278 | tran->cbw->dCBWSignature = MSD_CBW_SIGNATURE; | ||
279 | tran->cbw->dCBWTag = ++msdp->tag; | ||
280 | tran->data_processed = 0; | ||
281 | |||
282 | /* control phase */ | ||
283 | status = usbhBulkTransfer(&msdp->epout, tran->cbw, | ||
284 | sizeof(*tran->cbw), &actual_len, OSAL_MS2I(1000)); | ||
285 | |||
286 | if (status == USBH_URBSTATUS_CANCELLED) { | ||
287 | uclassdrverr("\tMSD: Control phase: USBH_URBSTATUS_CANCELLED"); | ||
288 | return MSD_BOTRESULT_DISCONNECTED; | ||
289 | } | ||
290 | |||
291 | if ((status != USBH_URBSTATUS_OK) || (actual_len != sizeof(*tran->cbw))) { | ||
292 | uclassdrverrf("\tMSD: Control phase: status = %d (!= OK), actual_len = %d (expected to send %d)", | ||
293 | status, actual_len, sizeof(*tran->cbw)); | ||
294 | _msd_bot_reset(msdp); | ||
295 | return MSD_BOTRESULT_ERROR; | ||
296 | } | ||
297 | |||
298 | |||
299 | /* data phase */ | ||
300 | data_actual_len = 0; | ||
301 | if (tran->cbw->dCBWDataTransferLength) { | ||
302 | usbh_ep_t *const ep = tran->cbw->bmCBWFlags & MSD_CBWFLAGS_D2H ? &msdp->epin : &msdp->epout; | ||
303 | status = usbhBulkTransfer( | ||
304 | ep, | ||
305 | data, | ||
306 | tran->cbw->dCBWDataTransferLength, | ||
307 | &data_actual_len, OSAL_MS2I(20000)); | ||
308 | |||
309 | if (status == USBH_URBSTATUS_CANCELLED) { | ||
310 | uclassdrverr("\tMSD: Data phase: USBH_URBSTATUS_CANCELLED"); | ||
311 | return MSD_BOTRESULT_DISCONNECTED; | ||
312 | } | ||
313 | |||
314 | if (status == USBH_URBSTATUS_STALL) { | ||
315 | uclassdrverrf("\tMSD: Data phase: USBH_URBSTATUS_STALL, clear halt"); | ||
316 | status = (usbhEPReset(ep) == HAL_SUCCESS) ? USBH_URBSTATUS_OK : USBH_URBSTATUS_ERROR; | ||
317 | } | ||
318 | |||
319 | if (status != USBH_URBSTATUS_OK) { | ||
320 | uclassdrverrf("\tMSD: Data phase: status = %d (!= OK), resetting", status); | ||
321 | _msd_bot_reset(msdp); | ||
322 | return MSD_BOTRESULT_ERROR; | ||
323 | } | ||
324 | } | ||
325 | |||
326 | |||
327 | /* status phase */ | ||
328 | status = usbhBulkTransfer(&msdp->epin, &csw, | ||
329 | sizeof(csw), &actual_len, OSAL_MS2I(1000)); | ||
330 | |||
331 | if (status == USBH_URBSTATUS_STALL) { | ||
332 | uclassdrvwarn("\tMSD: Status phase: USBH_URBSTATUS_STALL, clear halt and retry"); | ||
333 | |||
334 | status = (usbhEPReset(&msdp->epin) == HAL_SUCCESS) ? USBH_URBSTATUS_OK : USBH_URBSTATUS_ERROR; | ||
335 | |||
336 | if (status == USBH_URBSTATUS_OK) { | ||
337 | status = usbhBulkTransfer(&msdp->epin, &csw, | ||
338 | sizeof(csw), &actual_len, OSAL_MS2I(1000)); | ||
339 | } | ||
340 | } | ||
341 | |||
342 | if (status == USBH_URBSTATUS_CANCELLED) { | ||
343 | uclassdrverr("\tMSD: Status phase: USBH_URBSTATUS_CANCELLED"); | ||
344 | return MSD_BOTRESULT_DISCONNECTED; | ||
345 | } | ||
346 | |||
347 | if (status != USBH_URBSTATUS_OK) { | ||
348 | uclassdrverrf("\tMSD: Status phase: status = %d (!= OK), resetting", status); | ||
349 | _msd_bot_reset(msdp); | ||
350 | return MSD_BOTRESULT_ERROR; | ||
351 | } | ||
352 | |||
353 | /* validate CSW */ | ||
354 | if ((actual_len != sizeof(csw)) | ||
355 | || (csw.dCSWSignature != MSD_CSW_SIGNATURE) | ||
356 | || (csw.dCSWTag != msdp->tag) | ||
357 | || (csw.bCSWStatus >= CSW_STATUS_PHASE_ERROR)) { | ||
358 | /* CSW is not valid */ | ||
359 | uclassdrverrf("\tMSD: Status phase: Invalid CSW: len=%d, dCSWSignature=%x, dCSWTag=%x (expected %x), bCSWStatus=%d, resetting", | ||
360 | actual_len, | ||
361 | csw.dCSWSignature, | ||
362 | csw.dCSWTag, | ||
363 | msdp->tag, | ||
364 | csw.bCSWStatus); | ||
365 | _msd_bot_reset(msdp); | ||
366 | return MSD_BOTRESULT_ERROR; | ||
367 | } | ||
368 | |||
369 | /* check if CSW is meaningful */ | ||
370 | if ((csw.bCSWStatus != CSW_STATUS_PHASE_ERROR) | ||
371 | && (csw.dCSWDataResidue > tran->cbw->dCBWDataTransferLength)) { | ||
372 | /* CSW is not meaningful */ | ||
373 | uclassdrverrf("\tMSD: Status phase: CSW not meaningful: bCSWStatus=%d, dCSWDataResidue=%u, dCBWDataTransferLength=%u, resetting", | ||
374 | csw.bCSWStatus, | ||
375 | csw.dCSWDataResidue, | ||
376 | tran->cbw->dCBWDataTransferLength); | ||
377 | _msd_bot_reset(msdp); | ||
378 | return MSD_BOTRESULT_ERROR; | ||
379 | } | ||
380 | |||
381 | if (csw.bCSWStatus == CSW_STATUS_PHASE_ERROR) { | ||
382 | uclassdrverr("\tMSD: Status phase: Phase error, resetting"); | ||
383 | _msd_bot_reset(msdp); | ||
384 | return MSD_BOTRESULT_ERROR; | ||
385 | } | ||
386 | |||
387 | tran->data_processed = tran->cbw->dCBWDataTransferLength - csw.dCSWDataResidue; | ||
388 | if (data_actual_len < tran->data_processed) { | ||
389 | tran->data_processed = data_actual_len; | ||
390 | } | ||
391 | |||
392 | tran->csw_status = csw.bCSWStatus; | ||
393 | |||
394 | return MSD_BOTRESULT_OK; | ||
395 | } | ||
396 | |||
397 | |||
398 | /* ----------------------------------------------------- */ | ||
399 | /* SCSI Commands */ | ||
400 | /* ----------------------------------------------------- */ | ||
401 | |||
402 | /* Read 10 and Write 10 */ | ||
403 | #define SCSI_CMD_READ_10 0x28 | ||
404 | #define SCSI_CMD_WRITE_10 0x2A | ||
405 | |||
406 | /* Request sense */ | ||
407 | #define SCSI_CMD_REQUEST_SENSE 0x03 | ||
408 | typedef PACKED_STRUCT { | ||
409 | uint8_t byte[18]; | ||
410 | } scsi_sense_response_t; | ||
411 | |||
412 | #define SCSI_SENSE_KEY_GOOD 0x00 | ||
413 | #define SCSI_SENSE_KEY_RECOVERED_ERROR 0x01 | ||
414 | #define SCSI_SENSE_KEY_NOT_READY 0x02 | ||
415 | #define SCSI_SENSE_KEY_MEDIUM_ERROR 0x03 | ||
416 | #define SCSI_SENSE_KEY_HARDWARE_ERROR 0x04 | ||
417 | #define SCSI_SENSE_KEY_ILLEGAL_REQUEST 0x05 | ||
418 | #define SCSI_SENSE_KEY_UNIT_ATTENTION 0x06 | ||
419 | #define SCSI_SENSE_KEY_DATA_PROTECT 0x07 | ||
420 | #define SCSI_SENSE_KEY_BLANK_CHECK 0x08 | ||
421 | #define SCSI_SENSE_KEY_VENDOR_SPECIFIC 0x09 | ||
422 | #define SCSI_SENSE_KEY_COPY_ABORTED 0x0A | ||
423 | #define SCSI_SENSE_KEY_ABORTED_COMMAND 0x0B | ||
424 | #define SCSI_SENSE_KEY_VOLUME_OVERFLOW 0x0D | ||
425 | #define SCSI_SENSE_KEY_MISCOMPARE 0x0E | ||
426 | #define SCSI_ASENSE_NO_ADDITIONAL_INFORMATION 0x00 | ||
427 | #define SCSI_ASENSE_LOGICAL_UNIT_NOT_READY 0x04 | ||
428 | #define SCSI_ASENSE_INVALID_FIELD_IN_CDB 0x24 | ||
429 | #define SCSI_ASENSE_NOT_READY_TO_READY_CHANGE 0x28 | ||
430 | #define SCSI_ASENSE_WRITE_PROTECTED 0x27 | ||
431 | #define SCSI_ASENSE_FORMAT_ERROR 0x31 | ||
432 | #define SCSI_ASENSE_INVALID_COMMAND 0x20 | ||
433 | #define SCSI_ASENSE_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE 0x21 | ||
434 | #define SCSI_ASENSE_MEDIUM_NOT_PRESENT 0x3A | ||
435 | #define SCSI_ASENSEQ_NO_QUALIFIER 0x00 | ||
436 | #define SCSI_ASENSEQ_FORMAT_COMMAND_FAILED 0x01 | ||
437 | #define SCSI_ASENSEQ_INITIALIZING_COMMAND_REQUIRED 0x02 | ||
438 | #define SCSI_ASENSEQ_OPERATION_IN_PROGRESS 0x07 | ||
439 | |||
440 | /* Inquiry */ | ||
441 | #define SCSI_CMD_INQUIRY 0x12 | ||
442 | typedef PACKED_STRUCT { | ||
443 | uint8_t peripheral; | ||
444 | uint8_t removable; | ||
445 | uint8_t version; | ||
446 | uint8_t response_data_format; | ||
447 | uint8_t additional_length; | ||
448 | uint8_t sccstp; | ||
449 | uint8_t bqueetc; | ||
450 | uint8_t cmdque; | ||
451 | uint8_t vendorID[8]; | ||
452 | uint8_t productID[16]; | ||
453 | uint8_t productRev[4]; | ||
454 | } scsi_inquiry_response_t; | ||
455 | |||
456 | /* Read Capacity 10 */ | ||
457 | #define SCSI_CMD_READ_CAPACITY_10 0x25 | ||
458 | typedef PACKED_STRUCT { | ||
459 | uint32_t last_block_addr; | ||
460 | uint32_t block_size; | ||
461 | } scsi_readcapacity10_response_t; | ||
462 | |||
463 | /* Start/Stop Unit */ | ||
464 | #define SCSI_CMD_START_STOP_UNIT 0x1B | ||
465 | typedef PACKED_STRUCT { | ||
466 | uint8_t op_code; | ||
467 | uint8_t lun_immed; | ||
468 | uint8_t res1; | ||
469 | uint8_t res2; | ||
470 | uint8_t loej_start; | ||
471 | uint8_t control; | ||
472 | } scsi_startstopunit_request_t; | ||
473 | |||
474 | /* test unit ready */ | ||
475 | #define SCSI_CMD_TEST_UNIT_READY 0x00 | ||
476 | |||
477 | static msd_result_t scsi_requestsense(USBHMassStorageLUNDriver *lunp, scsi_sense_response_t *resp); | ||
478 | |||
479 | static msd_result_t _scsi_perform_transaction(USBHMassStorageLUNDriver *lunp, | ||
480 | msd_transaction_t *transaction, void *data) { | ||
481 | |||
482 | USBHMassStorageDriver *const msdp = lunp->msdp; | ||
483 | (void)msdp; | ||
484 | |||
485 | msd_bot_result_t res; | ||
486 | res = _msd_bot_transaction(transaction, lunp, data); | ||
487 | if (res != MSD_BOTRESULT_OK) { | ||
488 | return (msd_result_t)res; | ||
489 | } | ||
490 | |||
491 | if (transaction->csw_status == CSW_STATUS_FAILED) { | ||
492 | if (transaction->cbw->CBWCB[0] != SCSI_CMD_REQUEST_SENSE) { | ||
493 | /* do auto-sense (except for SCSI_CMD_REQUEST_SENSE!) */ | ||
494 | uclassdrvwarn("\tMSD: Command failed, auto-sense"); | ||
495 | USBH_DEFINE_BUFFER(scsi_sense_response_t sense); | ||
496 | if (scsi_requestsense(lunp, &sense) == MSD_RESULT_OK) { | ||
497 | uclassdrvwarnf("\tMSD: REQUEST SENSE: Sense key=%x, ASC=%02x, ASCQ=%02x", | ||
498 | sense.byte[2] & 0xf, sense.byte[12], sense.byte[13]); | ||
499 | } | ||
500 | } | ||
501 | return MSD_RESULT_FAILED; | ||
502 | } | ||
503 | |||
504 | return MSD_RESULT_OK; | ||
505 | } | ||
506 | |||
507 | static msd_result_t scsi_inquiry(USBHMassStorageLUNDriver *lunp, scsi_inquiry_response_t *resp) { | ||
508 | USBH_DEFINE_BUFFER(msd_cbw_t cbw); | ||
509 | msd_transaction_t transaction; | ||
510 | msd_result_t res; | ||
511 | |||
512 | memset(cbw.CBWCB, 0, sizeof(cbw.CBWCB)); | ||
513 | cbw.dCBWDataTransferLength = sizeof(scsi_inquiry_response_t); | ||
514 | cbw.bmCBWFlags = MSD_CBWFLAGS_D2H; | ||
515 | cbw.bCBWCBLength = 6; | ||
516 | cbw.CBWCB[0] = SCSI_CMD_INQUIRY; | ||
517 | cbw.CBWCB[4] = sizeof(scsi_inquiry_response_t); | ||
518 | transaction.cbw = &cbw; | ||
519 | |||
520 | res = _scsi_perform_transaction(lunp, &transaction, resp); | ||
521 | if (res == MSD_RESULT_OK) { | ||
522 | //transaction is OK; check length | ||
523 | if (transaction.data_processed < cbw.dCBWDataTransferLength) { | ||
524 | res = MSD_RESULT_TRANSPORT_ERROR; | ||
525 | } | ||
526 | } | ||
527 | |||
528 | return res; | ||
529 | } | ||
530 | |||
531 | static msd_result_t scsi_requestsense(USBHMassStorageLUNDriver *lunp, scsi_sense_response_t *resp) { | ||
532 | USBH_DEFINE_BUFFER(msd_cbw_t cbw); | ||
533 | msd_transaction_t transaction; | ||
534 | msd_result_t res; | ||
535 | |||
536 | memset(cbw.CBWCB, 0, sizeof(cbw.CBWCB)); | ||
537 | cbw.dCBWDataTransferLength = sizeof(scsi_sense_response_t); | ||
538 | cbw.bmCBWFlags = MSD_CBWFLAGS_D2H; | ||
539 | cbw.bCBWCBLength = 12; | ||
540 | cbw.CBWCB[0] = SCSI_CMD_REQUEST_SENSE; | ||
541 | cbw.CBWCB[4] = sizeof(scsi_sense_response_t); | ||
542 | transaction.cbw = &cbw; | ||
543 | |||
544 | res = _scsi_perform_transaction(lunp, &transaction, resp); | ||
545 | if (res == MSD_RESULT_OK) { | ||
546 | //transaction is OK; check length | ||
547 | if (transaction.data_processed < cbw.dCBWDataTransferLength) { | ||
548 | res = MSD_RESULT_TRANSPORT_ERROR; | ||
549 | } | ||
550 | } | ||
551 | |||
552 | return res; | ||
553 | } | ||
554 | |||
555 | static msd_result_t scsi_testunitready(USBHMassStorageLUNDriver *lunp) { | ||
556 | USBH_DEFINE_BUFFER(msd_cbw_t cbw); | ||
557 | msd_transaction_t transaction; | ||
558 | |||
559 | memset(cbw.CBWCB, 0, sizeof(cbw.CBWCB)); | ||
560 | cbw.dCBWDataTransferLength = 0; | ||
561 | cbw.bmCBWFlags = MSD_CBWFLAGS_D2H; | ||
562 | cbw.bCBWCBLength = 6; | ||
563 | cbw.CBWCB[0] = SCSI_CMD_TEST_UNIT_READY; | ||
564 | transaction.cbw = &cbw; | ||
565 | |||
566 | return _scsi_perform_transaction(lunp, &transaction, NULL); | ||
567 | } | ||
568 | |||
569 | static msd_result_t scsi_readcapacity10(USBHMassStorageLUNDriver *lunp, scsi_readcapacity10_response_t *resp) { | ||
570 | USBH_DEFINE_BUFFER(msd_cbw_t cbw); | ||
571 | msd_transaction_t transaction; | ||
572 | msd_result_t res; | ||
573 | |||
574 | memset(cbw.CBWCB, 0, sizeof(cbw.CBWCB)); | ||
575 | cbw.dCBWDataTransferLength = sizeof(scsi_readcapacity10_response_t); | ||
576 | cbw.bmCBWFlags = MSD_CBWFLAGS_D2H; | ||
577 | cbw.bCBWCBLength = 12; | ||
578 | cbw.CBWCB[0] = SCSI_CMD_READ_CAPACITY_10; | ||
579 | transaction.cbw = &cbw; | ||
580 | |||
581 | res = _scsi_perform_transaction(lunp, &transaction, resp); | ||
582 | if (res == MSD_RESULT_OK) { | ||
583 | //transaction is OK; check length | ||
584 | if (transaction.data_processed < cbw.dCBWDataTransferLength) { | ||
585 | res = MSD_RESULT_TRANSPORT_ERROR; | ||
586 | } | ||
587 | } | ||
588 | |||
589 | return res; | ||
590 | } | ||
591 | |||
592 | |||
593 | static msd_result_t scsi_read10(USBHMassStorageLUNDriver *lunp, uint32_t lba, uint16_t n, uint8_t *data, uint32_t *actual_len) { | ||
594 | USBH_DEFINE_BUFFER(msd_cbw_t cbw); | ||
595 | msd_transaction_t transaction; | ||
596 | msd_result_t res; | ||
597 | |||
598 | memset(cbw.CBWCB, 0, sizeof(cbw.CBWCB)); | ||
599 | cbw.dCBWDataTransferLength = n * lunp->info.blk_size; | ||
600 | cbw.bmCBWFlags = MSD_CBWFLAGS_D2H; | ||
601 | cbw.bCBWCBLength = 10; | ||
602 | cbw.CBWCB[0] = SCSI_CMD_READ_10; | ||
603 | cbw.CBWCB[2] = (uint8_t)(lba >> 24); | ||
604 | cbw.CBWCB[3] = (uint8_t)(lba >> 16); | ||
605 | cbw.CBWCB[4] = (uint8_t)(lba >> 8); | ||
606 | cbw.CBWCB[5] = (uint8_t)(lba); | ||
607 | cbw.CBWCB[7] = (uint8_t)(n >> 8); | ||
608 | cbw.CBWCB[8] = (uint8_t)(n); | ||
609 | transaction.cbw = &cbw; | ||
610 | |||
611 | res = _scsi_perform_transaction(lunp, &transaction, data); | ||
612 | if (actual_len) { | ||
613 | *actual_len = transaction.data_processed; | ||
614 | } | ||
615 | if (res == MSD_RESULT_OK) { | ||
616 | //transaction is OK; check length | ||
617 | if (transaction.data_processed < cbw.dCBWDataTransferLength) { | ||
618 | res = MSD_RESULT_TRANSPORT_ERROR; | ||
619 | } | ||
620 | } | ||
621 | |||
622 | return res; | ||
623 | } | ||
624 | |||
625 | static msd_result_t scsi_write10(USBHMassStorageLUNDriver *lunp, uint32_t lba, uint16_t n, const uint8_t *data, uint32_t *actual_len) { | ||
626 | USBH_DEFINE_BUFFER(msd_cbw_t cbw); | ||
627 | msd_transaction_t transaction; | ||
628 | msd_result_t res; | ||
629 | |||
630 | memset(cbw.CBWCB, 0, sizeof(cbw.CBWCB)); | ||
631 | cbw.dCBWDataTransferLength = n * lunp->info.blk_size; | ||
632 | cbw.bmCBWFlags = MSD_CBWFLAGS_H2D; | ||
633 | cbw.bCBWCBLength = 10; | ||
634 | cbw.CBWCB[0] = SCSI_CMD_WRITE_10; | ||
635 | cbw.CBWCB[2] = (uint8_t)(lba >> 24); | ||
636 | cbw.CBWCB[3] = (uint8_t)(lba >> 16); | ||
637 | cbw.CBWCB[4] = (uint8_t)(lba >> 8); | ||
638 | cbw.CBWCB[5] = (uint8_t)(lba); | ||
639 | cbw.CBWCB[7] = (uint8_t)(n >> 8); | ||
640 | cbw.CBWCB[8] = (uint8_t)(n); | ||
641 | transaction.cbw = &cbw; | ||
642 | |||
643 | res = _scsi_perform_transaction(lunp, &transaction, (void *)data); | ||
644 | if (actual_len) { | ||
645 | *actual_len = transaction.data_processed; | ||
646 | } | ||
647 | if (res == MSD_RESULT_OK) { | ||
648 | //transaction is OK; check length | ||
649 | if (transaction.data_processed < cbw.dCBWDataTransferLength) { | ||
650 | res = MSD_RESULT_TRANSPORT_ERROR; | ||
651 | } | ||
652 | } | ||
653 | |||
654 | return res; | ||
655 | } | ||
656 | |||
657 | |||
658 | |||
659 | /*===========================================================================*/ | ||
660 | /* Block driver data/functions */ | ||
661 | /*===========================================================================*/ | ||
662 | |||
663 | USBHMassStorageLUNDriver MSBLKD[HAL_USBHMSD_MAX_LUNS]; | ||
664 | |||
665 | static const struct USBHMassStorageDriverVMT blk_vmt = { | ||
666 | (size_t)0, | ||
667 | (bool (*)(void *))usbhmsdLUNIsInserted, | ||
668 | (bool (*)(void *))usbhmsdLUNIsProtected, | ||
669 | (bool (*)(void *))usbhmsdLUNConnect, | ||
670 | (bool (*)(void *))usbhmsdLUNDisconnect, | ||
671 | (bool (*)(void *, uint32_t, uint8_t *, uint32_t))usbhmsdLUNRead, | ||
672 | (bool (*)(void *, uint32_t, const uint8_t *, uint32_t))usbhmsdLUNWrite, | ||
673 | (bool (*)(void *))usbhmsdLUNSync, | ||
674 | (bool (*)(void *, BlockDeviceInfo *))usbhmsdLUNGetInfo | ||
675 | }; | ||
676 | |||
677 | static void _lun_object_deinit(USBHMassStorageLUNDriver *lunp) { | ||
678 | osalDbgCheck(lunp != NULL); | ||
679 | chSemWait(&lunp->sem); | ||
680 | lunp->msdp = NULL; | ||
681 | lunp->next = NULL; | ||
682 | memset(&lunp->info, 0, sizeof(lunp->info)); | ||
683 | lunp->state = BLK_STOP; | ||
684 | chSemSignal(&lunp->sem); | ||
685 | } | ||
686 | |||
687 | static void _lun_object_init(USBHMassStorageLUNDriver *lunp) { | ||
688 | osalDbgCheck(lunp != NULL); | ||
689 | memset(lunp, 0, sizeof(*lunp)); | ||
690 | lunp->vmt = &blk_vmt; | ||
691 | lunp->state = BLK_STOP; | ||
692 | chSemObjectInit(&lunp->sem, 1); | ||
693 | /* Unnecessary because of the memset: | ||
694 | lunp->msdp = NULL; | ||
695 | lunp->next = NULL; | ||
696 | lunp->info.* = 0; | ||
697 | */ | ||
698 | } | ||
699 | |||
700 | bool usbhmsdLUNConnect(USBHMassStorageLUNDriver *lunp) { | ||
701 | osalDbgCheck(lunp != NULL); | ||
702 | osalDbgCheck(lunp->msdp != NULL); | ||
703 | msd_result_t res; | ||
704 | |||
705 | USBHMassStorageDriver *const msdp = lunp->msdp; | ||
706 | (void)msdp; | ||
707 | |||
708 | chSemWait(&lunp->sem); | ||
709 | osalDbgAssert((lunp->state == BLK_READY) || (lunp->state == BLK_ACTIVE), "invalid state"); | ||
710 | if (lunp->state == BLK_READY) { | ||
711 | chSemSignal(&lunp->sem); | ||
712 | return HAL_SUCCESS; | ||
713 | } | ||
714 | lunp->state = BLK_CONNECTING; | ||
715 | |||
716 | { | ||
717 | USBH_DEFINE_BUFFER(scsi_inquiry_response_t inq); | ||
718 | uclassdrvinfo("INQUIRY..."); | ||
719 | res = scsi_inquiry(lunp, &inq); | ||
720 | if (res == MSD_RESULT_DISCONNECTED) { | ||
721 | goto failed; | ||
722 | } else if (res == MSD_RESULT_TRANSPORT_ERROR) { | ||
723 | //retry? | ||
724 | goto failed; | ||
725 | } else if (res == MSD_RESULT_FAILED) { | ||
726 | //retry? | ||
727 | goto failed; | ||
728 | } | ||
729 | |||
730 | uclassdrvinfof("\tPDT=%02x", inq.peripheral & 0x1f); | ||
731 | if (inq.peripheral != 0) { | ||
732 | uclassdrverr("\tUnsupported PDT"); | ||
733 | goto failed; | ||
734 | } | ||
735 | } | ||
736 | |||
737 | // Test if unit ready | ||
738 | uint8_t i; | ||
739 | for (i = 0; i < 10; i++) { | ||
740 | uclassdrvinfo("TEST UNIT READY..."); | ||
741 | res = scsi_testunitready(lunp); | ||
742 | if (res == MSD_RESULT_DISCONNECTED) { | ||
743 | goto failed; | ||
744 | } else if (res == MSD_RESULT_TRANSPORT_ERROR) { | ||
745 | //retry? | ||
746 | goto failed; | ||
747 | } else if (res == MSD_RESULT_FAILED) { | ||
748 | uclassdrvinfo("\tTEST UNIT READY: Command Failed, retry"); | ||
749 | osalThreadSleepMilliseconds(200); | ||
750 | continue; | ||
751 | } | ||
752 | uclassdrvinfo("\tReady."); | ||
753 | break; | ||
754 | } | ||
755 | if (i == 10) goto failed; | ||
756 | |||
757 | { | ||
758 | USBH_DEFINE_BUFFER(scsi_readcapacity10_response_t cap); | ||
759 | // Read capacity | ||
760 | uclassdrvinfo("READ CAPACITY(10)..."); | ||
761 | res = scsi_readcapacity10(lunp, &cap); | ||
762 | if (res == MSD_RESULT_DISCONNECTED) { | ||
763 | goto failed; | ||
764 | } else if (res == MSD_RESULT_TRANSPORT_ERROR) { | ||
765 | //retry? | ||
766 | goto failed; | ||
767 | } else if (res == MSD_RESULT_FAILED) { | ||
768 | //retry? | ||
769 | goto failed; | ||
770 | } | ||
771 | |||
772 | lunp->info.blk_size = __REV(cap.block_size); | ||
773 | lunp->info.blk_num = __REV(cap.last_block_addr) + 1; | ||
774 | } | ||
775 | |||
776 | uclassdrvinfof("\tBlock size=%dbytes, blocks=%u (~%u MB)", lunp->info.blk_size, lunp->info.blk_num, | ||
777 | (uint32_t)(((uint64_t)lunp->info.blk_size * lunp->info.blk_num) / (1024UL * 1024UL))); | ||
778 | |||
779 | uclassdrvinfo("MSD Connected."); | ||
780 | lunp->state = BLK_READY; | ||
781 | chSemSignal(&lunp->sem); | ||
782 | return HAL_SUCCESS; | ||
783 | |||
784 | /* Connection failed, state reset to BLK_ACTIVE.*/ | ||
785 | failed: | ||
786 | uclassdrvinfo("MSD Connect failed."); | ||
787 | lunp->state = BLK_ACTIVE; | ||
788 | chSemSignal(&lunp->sem); | ||
789 | return HAL_FAILED; | ||
790 | } | ||
791 | |||
792 | bool usbhmsdLUNDisconnect(USBHMassStorageLUNDriver *lunp) { | ||
793 | osalDbgCheck(lunp != NULL); | ||
794 | |||
795 | chSemWait(&lunp->sem); | ||
796 | osalDbgAssert((lunp->state == BLK_READY) || (lunp->state == BLK_ACTIVE), "invalid state"); | ||
797 | if (lunp->state == BLK_ACTIVE) { | ||
798 | chSemSignal(&lunp->sem); | ||
799 | return HAL_SUCCESS; | ||
800 | } | ||
801 | lunp->state = BLK_DISCONNECTING; | ||
802 | |||
803 | //TODO: complete: sync, etc. | ||
804 | |||
805 | lunp->state = BLK_ACTIVE; | ||
806 | chSemSignal(&lunp->sem); | ||
807 | |||
808 | return HAL_SUCCESS; | ||
809 | } | ||
810 | |||
811 | bool usbhmsdLUNRead(USBHMassStorageLUNDriver *lunp, uint32_t startblk, | ||
812 | uint8_t *buffer, uint32_t n) { | ||
813 | |||
814 | osalDbgCheck(lunp != NULL); | ||
815 | bool ret = HAL_FAILED; | ||
816 | uint16_t blocks; | ||
817 | msd_result_t res; | ||
818 | uint32_t actual_len; | ||
819 | |||
820 | chSemWait(&lunp->sem); | ||
821 | if (lunp->state != BLK_READY) { | ||
822 | chSemSignal(&lunp->sem); | ||
823 | return ret; | ||
824 | } | ||
825 | lunp->state = BLK_READING; | ||
826 | |||
827 | while (n) { | ||
828 | if (n > 0xffff) { | ||
829 | blocks = 0xffff; | ||
830 | } else { | ||
831 | blocks = (uint16_t)n; | ||
832 | } | ||
833 | res = scsi_read10(lunp, startblk, blocks, buffer, &actual_len); | ||
834 | if (res == MSD_RESULT_DISCONNECTED) { | ||
835 | goto exit; | ||
836 | } else if (res == MSD_RESULT_TRANSPORT_ERROR) { | ||
837 | //retry? | ||
838 | goto exit; | ||
839 | } else if (res == MSD_RESULT_FAILED) { | ||
840 | //retry? | ||
841 | goto exit; | ||
842 | } | ||
843 | n -= blocks; | ||
844 | startblk += blocks; | ||
845 | buffer += blocks * lunp->info.blk_size; | ||
846 | } | ||
847 | |||
848 | ret = HAL_SUCCESS; | ||
849 | |||
850 | exit: | ||
851 | lunp->state = BLK_READY; | ||
852 | chSemSignal(&lunp->sem); | ||
853 | return ret; | ||
854 | } | ||
855 | |||
856 | bool usbhmsdLUNWrite(USBHMassStorageLUNDriver *lunp, uint32_t startblk, | ||
857 | const uint8_t *buffer, uint32_t n) { | ||
858 | |||
859 | osalDbgCheck(lunp != NULL); | ||
860 | bool ret = HAL_FAILED; | ||
861 | uint16_t blocks; | ||
862 | msd_result_t res; | ||
863 | uint32_t actual_len; | ||
864 | |||
865 | chSemWait(&lunp->sem); | ||
866 | if (lunp->state != BLK_READY) { | ||
867 | chSemSignal(&lunp->sem); | ||
868 | return ret; | ||
869 | } | ||
870 | lunp->state = BLK_WRITING; | ||
871 | |||
872 | while (n) { | ||
873 | if (n > 0xffff) { | ||
874 | blocks = 0xffff; | ||
875 | } else { | ||
876 | blocks = (uint16_t)n; | ||
877 | } | ||
878 | res = scsi_write10(lunp, startblk, blocks, buffer, &actual_len); | ||
879 | if (res == MSD_RESULT_DISCONNECTED) { | ||
880 | goto exit; | ||
881 | } else if (res == MSD_RESULT_TRANSPORT_ERROR) { | ||
882 | //retry? | ||
883 | goto exit; | ||
884 | } else if (res == MSD_RESULT_FAILED) { | ||
885 | //retry? | ||
886 | goto exit; | ||
887 | } | ||
888 | n -= blocks; | ||
889 | startblk += blocks; | ||
890 | buffer += blocks * lunp->info.blk_size; | ||
891 | } | ||
892 | |||
893 | ret = HAL_SUCCESS; | ||
894 | |||
895 | exit: | ||
896 | lunp->state = BLK_READY; | ||
897 | chSemSignal(&lunp->sem); | ||
898 | return ret; | ||
899 | } | ||
900 | |||
901 | bool usbhmsdLUNSync(USBHMassStorageLUNDriver *lunp) { | ||
902 | osalDbgCheck(lunp != NULL); | ||
903 | (void)lunp; | ||
904 | //TODO: Do SCSI Sync | ||
905 | return HAL_SUCCESS; | ||
906 | } | ||
907 | |||
908 | bool usbhmsdLUNGetInfo(USBHMassStorageLUNDriver *lunp, BlockDeviceInfo *bdip) { | ||
909 | osalDbgCheck(lunp != NULL); | ||
910 | osalDbgCheck(bdip != NULL); | ||
911 | |||
912 | osalSysLock(); | ||
913 | if (lunp->state >= BLK_READY) { | ||
914 | *bdip = lunp->info; | ||
915 | osalSysUnlock(); | ||
916 | return HAL_SUCCESS; | ||
917 | } | ||
918 | osalSysUnlock(); | ||
919 | return HAL_FAILED; | ||
920 | } | ||
921 | |||
922 | bool usbhmsdLUNIsInserted(USBHMassStorageLUNDriver *lunp) { | ||
923 | osalDbgCheck(lunp != NULL); | ||
924 | return (lunp->state >= BLK_ACTIVE); | ||
925 | } | ||
926 | |||
927 | bool usbhmsdLUNIsProtected(USBHMassStorageLUNDriver *lunp) { | ||
928 | osalDbgCheck(lunp != NULL); | ||
929 | //TODO: Implement | ||
930 | return FALSE; | ||
931 | } | ||
932 | |||
933 | USBHDriver *usbhmsdLUNGetHost(const USBHMassStorageLUNDriver *lunp) { | ||
934 | return lunp->msdp->dev->host; | ||
935 | } | ||
936 | |||
937 | static void _msd_object_init(USBHMassStorageDriver *msdp) { | ||
938 | osalDbgCheck(msdp != NULL); | ||
939 | memset(msdp, 0, sizeof(*msdp)); | ||
940 | msdp->info = &usbhmsdClassDriverInfo; | ||
941 | } | ||
942 | |||
943 | static void _msd_init(void) { | ||
944 | uint8_t i; | ||
945 | for (i = 0; i < HAL_USBHMSD_MAX_INSTANCES; i++) { | ||
946 | _msd_object_init(&USBHMSD[i]); | ||
947 | } | ||
948 | for (i = 0; i < HAL_USBHMSD_MAX_LUNS; i++) { | ||
949 | _lun_object_init(&MSBLKD[i]); | ||
950 | } | ||
951 | } | ||
952 | #endif | ||
diff --git a/lib/chibios-contrib/os/hal/src/usbh/hal_usbh_uvc.c b/lib/chibios-contrib/os/hal/src/usbh/hal_usbh_uvc.c new file mode 100644 index 000000000..3a8b488b6 --- /dev/null +++ b/lib/chibios-contrib/os/hal/src/usbh/hal_usbh_uvc.c | |||
@@ -0,0 +1,718 @@ | |||
1 | /* | ||
2 | ChibiOS - Copyright (C) 2006..2017 Giovanni Di Sirio | ||
3 | Copyright (C) 2015..2019 Diego Ismirlian, (dismirlian(at)google's mail) | ||
4 | |||
5 | Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | you may not use this file except in compliance with the License. | ||
7 | You may obtain a copy of the License at | ||
8 | |||
9 | http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | |||
11 | Unless required by applicable law or agreed to in writing, software | ||
12 | distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | See the License for the specific language governing permissions and | ||
15 | limitations under the License. | ||
16 | */ | ||
17 | |||
18 | #include "hal.h" | ||
19 | |||
20 | #if HAL_USBH_USE_UVC | ||
21 | |||
22 | #if !HAL_USE_USBH | ||
23 | #error "USBHUVC needs HAL_USE_USBH" | ||
24 | #endif | ||
25 | |||
26 | #if !HAL_USBH_USE_IAD | ||
27 | #error "USBHUVC needs HAL_USBH_USE_IAD" | ||
28 | #endif | ||
29 | |||
30 | #include <string.h> | ||
31 | #include "usbh/dev/uvc.h" | ||
32 | #include "usbh/internal.h" | ||
33 | |||
34 | #define _USBH_DEBUG_HELPER_CLASS_DRIVER uvcdp | ||
35 | #define _USBH_DEBUG_HELPER_ENABLE_TRACE USBHUVC_DEBUG_ENABLE_TRACE | ||
36 | #define _USBH_DEBUG_HELPER_ENABLE_INFO USBHUVC_DEBUG_ENABLE_INFO | ||
37 | #define _USBH_DEBUG_HELPER_ENABLE_WARNINGS USBHUVC_DEBUG_ENABLE_WARNINGS | ||
38 | #define _USBH_DEBUG_HELPER_ENABLE_ERRORS USBHUVC_DEBUG_ENABLE_ERRORS | ||
39 | #include "usbh/debug_helpers.h" | ||
40 | |||
41 | USBHUVCDriver USBHUVCD[HAL_USBHUVC_MAX_INSTANCES]; | ||
42 | |||
43 | static void _uvc_init(void); | ||
44 | static usbh_baseclassdriver_t *_uvc_load(usbh_device_t *dev, | ||
45 | const uint8_t *descriptor, uint16_t rem); | ||
46 | static void _uvc_unload(usbh_baseclassdriver_t *drv); | ||
47 | |||
48 | static const usbh_classdriver_vmt_t class_driver_vmt = { | ||
49 | _uvc_init, | ||
50 | _uvc_load, | ||
51 | _uvc_unload | ||
52 | }; | ||
53 | const usbh_classdriverinfo_t usbhuvcClassDriverInfo = { | ||
54 | "UVC", &class_driver_vmt | ||
55 | }; | ||
56 | |||
57 | static bool _request(USBHUVCDriver *uvcdp, | ||
58 | uint8_t bRequest, uint8_t entity, uint8_t control, | ||
59 | uint16_t wLength, uint8_t *data, uint8_t interf) { | ||
60 | |||
61 | usbh_urbstatus_t res; | ||
62 | |||
63 | if (bRequest & 0x80) { | ||
64 | res = usbhControlRequest(uvcdp->dev, | ||
65 | USBH_REQTYPE_CLASSIN(USBH_REQTYPE_RECIP_INTERFACE), | ||
66 | bRequest, | ||
67 | ((control) << 8), | ||
68 | (interf) | ((entity) << 8), | ||
69 | wLength, data); | ||
70 | } else { | ||
71 | res = usbhControlRequest(uvcdp->dev, | ||
72 | USBH_REQTYPE_CLASSOUT(USBH_REQTYPE_RECIP_INTERFACE), | ||
73 | bRequest, | ||
74 | ((control) << 8), | ||
75 | (interf) | ((entity) << 8), | ||
76 | wLength, data); | ||
77 | } | ||
78 | |||
79 | if (res != USBH_URBSTATUS_OK) | ||
80 | return HAL_FAILED; | ||
81 | |||
82 | return HAL_SUCCESS; | ||
83 | } | ||
84 | |||
85 | bool usbhuvcVCRequest(USBHUVCDriver *uvcdp, | ||
86 | uint8_t bRequest, uint8_t entity, uint8_t control, | ||
87 | uint16_t wLength, uint8_t *data) { | ||
88 | return _request(uvcdp, bRequest, entity, control, wLength, data, if_get(&uvcdp->ivc)->bInterfaceNumber); | ||
89 | } | ||
90 | |||
91 | bool usbhuvcVSRequest(USBHUVCDriver *uvcdp, | ||
92 | uint8_t bRequest, uint8_t control, | ||
93 | uint16_t wLength, uint8_t *data) { | ||
94 | |||
95 | return _request(uvcdp, bRequest, 0, control, wLength, data, if_get(&uvcdp->ivs)->bInterfaceNumber); | ||
96 | } | ||
97 | |||
98 | static bool _set_vs_alternate(USBHUVCDriver *uvcdp, uint16_t min_ep_size) { | ||
99 | |||
100 | if (min_ep_size == 0) { | ||
101 | uclassdrvinfo("Selecting Alternate setting 0"); | ||
102 | return usbhStdReqSetInterface(uvcdp->dev, if_get(&uvcdp->ivs)->bInterfaceNumber, 0); | ||
103 | } | ||
104 | |||
105 | if_iterator_t iif = uvcdp->ivs; | ||
106 | generic_iterator_t iep; | ||
107 | const usbh_endpoint_descriptor_t *ep = NULL; | ||
108 | uint8_t alt = 0; | ||
109 | uint16_t sz = 0xffff; | ||
110 | |||
111 | uclassdrvinfof("Searching alternate setting with min_ep_size=%d", min_ep_size); | ||
112 | |||
113 | for (; iif.valid; if_iter_next(&iif)) { | ||
114 | const usbh_interface_descriptor_t *const ifdesc = if_get(&iif); | ||
115 | |||
116 | if ((ifdesc->bInterfaceClass != UVC_CC_VIDEO) | ||
117 | || (ifdesc->bInterfaceSubClass != UVC_SC_VIDEOSTREAMING)) | ||
118 | continue; | ||
119 | |||
120 | uclassdrvinfof("\tScanning alternate setting=%d", ifdesc->bAlternateSetting); | ||
121 | |||
122 | if (ifdesc->bNumEndpoints == 0) | ||
123 | continue; | ||
124 | |||
125 | for (ep_iter_init(&iep, &iif); iep.valid; ep_iter_next(&iep)) { | ||
126 | const usbh_endpoint_descriptor_t *const epdesc = ep_get(&iep); | ||
127 | if (((epdesc->bmAttributes & 0x03) == USBH_EPTYPE_ISO) | ||
128 | && ((epdesc->bEndpointAddress & 0x80) == USBH_EPDIR_IN)) { | ||
129 | |||
130 | uclassdrvinfof("\t Endpoint wMaxPacketSize = %d", epdesc->wMaxPacketSize); | ||
131 | |||
132 | if (epdesc->wMaxPacketSize >= min_ep_size) { | ||
133 | if (epdesc->wMaxPacketSize < sz) { | ||
134 | uclassdrvinfo("\t Found new optimal alternate setting"); | ||
135 | sz = epdesc->wMaxPacketSize; | ||
136 | alt = ifdesc->bAlternateSetting; | ||
137 | ep = epdesc; | ||
138 | } | ||
139 | } | ||
140 | } | ||
141 | } | ||
142 | } | ||
143 | |||
144 | if (ep && alt) { | ||
145 | uclassdrvinfof("\tSelecting Alternate setting %d", alt); | ||
146 | if (usbhStdReqSetInterface(uvcdp->dev, if_get(&uvcdp->ivs)->bInterfaceNumber, alt) == HAL_SUCCESS) { | ||
147 | usbhEPObjectInit(&uvcdp->ep_iso, uvcdp->dev, ep); | ||
148 | usbhEPSetName(&uvcdp->ep_iso, "UVC[ISO ]"); | ||
149 | return HAL_SUCCESS; | ||
150 | } | ||
151 | } | ||
152 | |||
153 | return HAL_FAILED; | ||
154 | } | ||
155 | |||
156 | #if USBH_DEBUG_ENABLE && USBHUVC_DEBUG_ENABLE_INFO | ||
157 | void usbhuvcPrintProbeCommit(USBHUVCDriver *uvcdp, | ||
158 | const usbh_uvc_ctrl_vs_probecommit_data_t *pc) { | ||
159 | (void)uvcdp; | ||
160 | |||
161 | //uinfof("UVC: probe/commit data:"); | ||
162 | uclassdrvinfof("\tbmHint=%04x", pc->bmHint); | ||
163 | uclassdrvinfof("\tbFormatIndex=%d, bFrameIndex=%d, dwFrameInterval=%u", | ||
164 | pc->bFormatIndex, pc->bFrameIndex, pc->dwFrameInterval); | ||
165 | uclassdrvinfof("\twKeyFrameRate=%d, wPFrameRate=%d, wCompQuality=%u, wCompWindowSize=%u", | ||
166 | pc->wKeyFrameRate, pc->wPFrameRate, pc->wCompQuality, pc->wCompWindowSize); | ||
167 | uclassdrvinfof("\twDelay=%d", pc->wDelay); | ||
168 | uclassdrvinfof("\tdwMaxVideoFrameSize=%u", pc->dwMaxVideoFrameSize); | ||
169 | uclassdrvinfof("\tdwMaxPayloadTransferSize=%u", pc->dwMaxPayloadTransferSize); | ||
170 | /* uclassdrvinfof("\tdwClockFrequency=%u", pc->dwClockFrequency); | ||
171 | uclassdrvinfof("\tbmFramingInfo=%02x", pc->bmFramingInfo); | ||
172 | uclassdrvinfof("\tbPreferedVersion=%d, bMinVersion=%d, bMaxVersion=%d", | ||
173 | pc->bPreferedVersion, pc->bMinVersion, pc->bMaxVersion); */ | ||
174 | } | ||
175 | #endif | ||
176 | |||
177 | static void _post(USBHUVCDriver *uvcdp, usbh_urb_t *urb, memory_pool_t *mp, uint16_t type) { | ||
178 | usbhuvc_message_base_t *const msg = (usbhuvc_message_base_t *)((uint8_t *)urb->buff - offsetof(usbhuvc_message_data_t, data)); | ||
179 | msg->timestamp = osalOsGetSystemTimeX(); | ||
180 | |||
181 | usbhuvc_message_base_t *const new_msg = (usbhuvc_message_base_t *)chPoolAllocI(mp); | ||
182 | if (new_msg != NULL) { | ||
183 | /* allocated the new buffer, now try to post the message to the mailbox */ | ||
184 | msg_t r = chMBPostI(&uvcdp->mb, (msg_t)msg); | ||
185 | if (r == MSG_OK) { | ||
186 | /* everything OK, complete the missing fields */ | ||
187 | msg->type = type; | ||
188 | msg->length = urb->actualLength; | ||
189 | |||
190 | /* change the URB's buffer to the newly allocated one */ | ||
191 | urb->buff = ((usbhuvc_message_data_t *)new_msg)->data; | ||
192 | } else { | ||
193 | if (r == MSG_RESET) { | ||
194 | uurbwarn("UVC: error, mailbox reset"); | ||
195 | } else { | ||
196 | uurberr("UVC: error, mailbox overrun"); | ||
197 | } | ||
198 | /* couldn't post the message, free the newly allocated buffer */ | ||
199 | chPoolFreeI(&uvcdp->mp_status, new_msg); | ||
200 | } | ||
201 | } else { | ||
202 | uurberrf("UVC: error, %s pool overrun", mp == &uvcdp->mp_data ? "data" : "status"); | ||
203 | } | ||
204 | } | ||
205 | |||
206 | static void _cb_int(usbh_urb_t *urb) { | ||
207 | USBHUVCDriver *uvcdp = (USBHUVCDriver *)urb->userData; | ||
208 | |||
209 | switch (urb->status) { | ||
210 | case USBH_URBSTATUS_OK: | ||
211 | if (urb->actualLength >= 2) { | ||
212 | _post(uvcdp, urb, &uvcdp->mp_status, USBHUVC_MESSAGETYPE_STATUS); | ||
213 | } else { | ||
214 | uurberrf("UVC: INT IN, actualLength=%d", urb->actualLength); | ||
215 | } | ||
216 | break; | ||
217 | case USBH_URBSTATUS_TIMEOUT: /* the device NAKed */ | ||
218 | uurbdbg("UVC: INT IN no info"); | ||
219 | break; | ||
220 | case USBH_URBSTATUS_DISCONNECTED: | ||
221 | case USBH_URBSTATUS_CANCELLED: | ||
222 | uurbwarn("UVC: INT IN status = DISCONNECTED/CANCELLED, aborting"); | ||
223 | return; | ||
224 | default: | ||
225 | uurberrf("UVC: INT IN error, unexpected status = %d", urb->status); | ||
226 | break; | ||
227 | } | ||
228 | |||
229 | usbhURBObjectResetI(urb); | ||
230 | usbhURBSubmitI(urb); | ||
231 | } | ||
232 | |||
233 | static void _cb_iso(usbh_urb_t *urb) { | ||
234 | USBHUVCDriver *uvcdp = (USBHUVCDriver *)urb->userData; | ||
235 | |||
236 | if ((urb->status == USBH_URBSTATUS_DISCONNECTED) | ||
237 | || (urb->status == USBH_URBSTATUS_CANCELLED)) { | ||
238 | uurbwarn("UVC: ISO IN status = DISCONNECTED/CANCELLED, aborting"); | ||
239 | return; | ||
240 | } | ||
241 | |||
242 | if (urb->status != USBH_URBSTATUS_OK) { | ||
243 | uurberrf("UVC: ISO IN error, unexpected status = %d", urb->status); | ||
244 | } else if (urb->actualLength >= 2) { | ||
245 | const uint8_t *const buff = (const uint8_t *)urb->buff; | ||
246 | if (buff[0] < 2) { | ||
247 | uurberrf("UVC: ISO IN, bHeaderLength=%d", buff[0]); | ||
248 | } else if (buff[0] > urb->actualLength) { | ||
249 | uurberrf("UVC: ISO IN, bHeaderLength=%d > actualLength=%d", buff[0], urb->actualLength); | ||
250 | } else { | ||
251 | uurbdbgf("UVC: ISO IN len=%d, hdr=%d, FID=%d, EOF=%d, ERR=%d, EOH=%d", | ||
252 | urb->actualLength, | ||
253 | buff[0], | ||
254 | buff[1] & UVC_HDR_FID, | ||
255 | buff[1] & UVC_HDR_EOF, | ||
256 | buff[1] & UVC_HDR_ERR, | ||
257 | buff[1] & UVC_HDR_EOH); | ||
258 | |||
259 | if ((urb->actualLength > buff[0]) | ||
260 | || (buff[1] & (UVC_HDR_EOF | UVC_HDR_ERR))) { | ||
261 | _post(uvcdp, urb, &uvcdp->mp_data, USBHUVC_MESSAGETYPE_DATA); | ||
262 | } else { | ||
263 | uurbdbgf("UVC: ISO IN skip: len=%d, hdr=%d, FID=%d, EOF=%d, ERR=%d, EOH=%d", | ||
264 | urb->actualLength, | ||
265 | buff[0], | ||
266 | buff[1] & UVC_HDR_FID, | ||
267 | buff[1] & UVC_HDR_EOF, | ||
268 | buff[1] & UVC_HDR_ERR, | ||
269 | buff[1] & UVC_HDR_EOH); | ||
270 | } | ||
271 | } | ||
272 | } else if (urb->actualLength > 0) { | ||
273 | uurberrf("UVC: ISO IN, actualLength=%d", urb->actualLength); | ||
274 | } | ||
275 | |||
276 | usbhURBObjectResetI(urb); | ||
277 | usbhURBSubmitI(urb); | ||
278 | } | ||
279 | |||
280 | |||
281 | bool usbhuvcStreamStart(USBHUVCDriver *uvcdp, uint16_t min_ep_sz) { | ||
282 | bool ret = HAL_FAILED; | ||
283 | |||
284 | osalSysLock(); | ||
285 | osalDbgCheck(uvcdp && (uvcdp->state != USBHUVC_STATE_UNINITIALIZED) && | ||
286 | (uvcdp->state != USBHUVC_STATE_BUSY)); | ||
287 | if (uvcdp->state == USBHUVC_STATE_STREAMING) { | ||
288 | osalSysUnlock(); | ||
289 | return HAL_SUCCESS; | ||
290 | } | ||
291 | if (uvcdp->state != USBHUVC_STATE_READY) { | ||
292 | osalSysUnlock(); | ||
293 | return HAL_FAILED; | ||
294 | } | ||
295 | uvcdp->state = USBHUVC_STATE_BUSY; | ||
296 | osalSysUnlock(); | ||
297 | |||
298 | uint32_t workramsz; | ||
299 | const uint8_t *elem; | ||
300 | uint32_t datapackets; | ||
301 | uint32_t data_sz; | ||
302 | |||
303 | //set the alternate setting | ||
304 | if (_set_vs_alternate(uvcdp, min_ep_sz) != HAL_SUCCESS) | ||
305 | goto exit; | ||
306 | |||
307 | //reserve working RAM | ||
308 | data_sz = (uvcdp->ep_iso.wMaxPacketSize + sizeof(usbhuvc_message_data_t) + 3) & ~3; | ||
309 | datapackets = HAL_USBHUVC_WORK_RAM_SIZE / data_sz; | ||
310 | if (datapackets == 0) { | ||
311 | uclassdrverr("Not enough work RAM"); | ||
312 | goto failed; | ||
313 | } | ||
314 | |||
315 | workramsz = datapackets * data_sz; | ||
316 | uclassdrvinfof("Reserving %u bytes of RAM (%d data packets of %d bytes)", workramsz, datapackets, data_sz); | ||
317 | if (datapackets > (HAL_USBHUVC_MAX_MAILBOX_SZ - HAL_USBHUVC_STATUS_PACKETS_COUNT)) { | ||
318 | uclassdrvwarn("Mailbox may overflow, use a larger HAL_USBHUVC_MAX_MAILBOX_SZ. UVC will under-utilize the assigned work RAM."); | ||
319 | } | ||
320 | chMBResumeX(&uvcdp->mb); | ||
321 | |||
322 | uvcdp->mp_data_buffer = chHeapAlloc(NULL, workramsz); | ||
323 | if (uvcdp->mp_data_buffer == NULL) { | ||
324 | uclassdrverr("Couldn't reserve RAM"); | ||
325 | goto failed; | ||
326 | } | ||
327 | |||
328 | //initialize the mempool | ||
329 | chPoolObjectInit(&uvcdp->mp_data, data_sz, NULL); | ||
330 | elem = (const uint8_t *)uvcdp->mp_data_buffer; | ||
331 | while (datapackets--) { | ||
332 | chPoolFree(&uvcdp->mp_data, (void *)elem); | ||
333 | elem += data_sz; | ||
334 | } | ||
335 | |||
336 | //open the endpoint | ||
337 | usbhEPOpen(&uvcdp->ep_iso); | ||
338 | |||
339 | //allocate 1 buffer and submit the first transfer | ||
340 | { | ||
341 | usbhuvc_message_data_t *const msg = (usbhuvc_message_data_t *)chPoolAlloc(&uvcdp->mp_data); | ||
342 | osalDbgCheck(msg); | ||
343 | usbhURBObjectInit(&uvcdp->urb_iso, &uvcdp->ep_iso, _cb_iso, uvcdp, msg->data, uvcdp->ep_iso.wMaxPacketSize); | ||
344 | } | ||
345 | |||
346 | usbhURBSubmit(&uvcdp->urb_iso); | ||
347 | |||
348 | ret = HAL_SUCCESS; | ||
349 | goto exit; | ||
350 | |||
351 | failed: | ||
352 | _set_vs_alternate(uvcdp, 0); | ||
353 | if (uvcdp->mp_data_buffer) | ||
354 | chHeapFree(uvcdp->mp_data_buffer); | ||
355 | |||
356 | exit: | ||
357 | osalSysLock(); | ||
358 | if (ret == HAL_SUCCESS) | ||
359 | uvcdp->state = USBHUVC_STATE_STREAMING; | ||
360 | else | ||
361 | uvcdp->state = USBHUVC_STATE_READY; | ||
362 | osalSysUnlock(); | ||
363 | return ret; | ||
364 | } | ||
365 | |||
366 | bool usbhuvcStreamStop(USBHUVCDriver *uvcdp) { | ||
367 | osalSysLock(); | ||
368 | osalDbgCheck(uvcdp && (uvcdp->state != USBHUVC_STATE_UNINITIALIZED) && | ||
369 | (uvcdp->state != USBHUVC_STATE_BUSY)); | ||
370 | if (uvcdp->state != USBHUVC_STATE_STREAMING) { | ||
371 | osalSysUnlock(); | ||
372 | return HAL_SUCCESS; | ||
373 | } | ||
374 | uvcdp->state = USBHUVC_STATE_BUSY; | ||
375 | |||
376 | //close the ISO endpoint | ||
377 | usbhEPCloseS(&uvcdp->ep_iso); | ||
378 | |||
379 | //purge the mailbox | ||
380 | chMBResetI(&uvcdp->mb); //TODO: the status messages are lost!! | ||
381 | chMtxLockS(&uvcdp->mtx); | ||
382 | osalSysUnlock(); | ||
383 | |||
384 | //free the working memory | ||
385 | chHeapFree(uvcdp->mp_data_buffer); | ||
386 | uvcdp->mp_data_buffer = 0; | ||
387 | |||
388 | //set alternate setting to 0 | ||
389 | _set_vs_alternate(uvcdp, 0); | ||
390 | |||
391 | osalSysLock(); | ||
392 | uvcdp->state = USBHUVC_STATE_READY; | ||
393 | chMtxUnlockS(&uvcdp->mtx); | ||
394 | osalSysUnlock(); | ||
395 | return HAL_SUCCESS; | ||
396 | } | ||
397 | |||
398 | bool usbhuvcFindVSDescriptor(USBHUVCDriver *uvcdp, | ||
399 | generic_iterator_t *ics, | ||
400 | uint8_t bDescriptorSubtype, | ||
401 | bool start) { | ||
402 | |||
403 | if (start) | ||
404 | cs_iter_init(ics, (generic_iterator_t *)&uvcdp->ivs); | ||
405 | else | ||
406 | cs_iter_next(ics); | ||
407 | |||
408 | for (; ics->valid; cs_iter_next(ics)) { | ||
409 | if (ics->curr[1] != UVC_CS_INTERFACE) | ||
410 | break; | ||
411 | if (ics->curr[2] == bDescriptorSubtype) | ||
412 | return HAL_SUCCESS; | ||
413 | if (!start) | ||
414 | break; | ||
415 | } | ||
416 | return HAL_FAILED; | ||
417 | } | ||
418 | |||
419 | void usbhuvcResetPC(USBHUVCDriver *uvcdp) { | ||
420 | memset(&uvcdp->pc, 0, sizeof(uvcdp->pc)); | ||
421 | } | ||
422 | |||
423 | bool usbhuvcProbe(USBHUVCDriver *uvcdp) { | ||
424 | // memset(&uvcdp->pc_min, 0, sizeof(uvcdp->pc_min)); | ||
425 | // memset(&uvcdp->pc_max, 0, sizeof(uvcdp->pc_max)); | ||
426 | |||
427 | if (usbhuvcVSRequest(uvcdp, UVC_SET_CUR, UVC_CTRL_VS_PROBE_CONTROL, sizeof(uvcdp->pc), (uint8_t *)&uvcdp->pc) != HAL_SUCCESS) | ||
428 | return HAL_FAILED; | ||
429 | if (usbhuvcVSRequest(uvcdp, UVC_GET_CUR, UVC_CTRL_VS_PROBE_CONTROL, sizeof(uvcdp->pc), (uint8_t *)&uvcdp->pc) != HAL_SUCCESS) | ||
430 | return HAL_FAILED; | ||
431 | if (usbhuvcVSRequest(uvcdp, UVC_GET_MAX, UVC_CTRL_VS_PROBE_CONTROL, sizeof(uvcdp->pc_max), (uint8_t *)&uvcdp->pc_max) != HAL_SUCCESS) | ||
432 | return HAL_FAILED; | ||
433 | if (usbhuvcVSRequest(uvcdp, UVC_GET_MIN, UVC_CTRL_VS_PROBE_CONTROL, sizeof(uvcdp->pc_min), (uint8_t *)&uvcdp->pc_min) != HAL_SUCCESS) | ||
434 | return HAL_FAILED; | ||
435 | return HAL_SUCCESS; | ||
436 | } | ||
437 | |||
438 | bool usbhuvcCommit(USBHUVCDriver *uvcdp) { | ||
439 | if (usbhuvcVSRequest(uvcdp, UVC_SET_CUR, UVC_CTRL_VS_COMMIT_CONTROL, sizeof(uvcdp->pc), (uint8_t *)&uvcdp->pc) != HAL_SUCCESS) | ||
440 | return HAL_FAILED; | ||
441 | |||
442 | osalSysLock(); | ||
443 | if (uvcdp->state == USBHUVC_STATE_ACTIVE) | ||
444 | uvcdp->state = USBHUVC_STATE_READY; | ||
445 | osalSysUnlock(); | ||
446 | return HAL_SUCCESS; | ||
447 | } | ||
448 | |||
449 | uint32_t usbhuvcEstimateRequiredEPSize(USBHUVCDriver *uvcdp, const uint8_t *formatdesc, | ||
450 | const uint8_t *framedesc, uint32_t dwFrameInterval) { | ||
451 | |||
452 | osalDbgCheck(framedesc); | ||
453 | osalDbgCheck(framedesc[0] > 3); | ||
454 | osalDbgCheck(framedesc[1] == UVC_CS_INTERFACE); | ||
455 | osalDbgCheck(formatdesc); | ||
456 | osalDbgCheck(formatdesc[0] > 3); | ||
457 | osalDbgCheck(formatdesc[1] == UVC_CS_INTERFACE); | ||
458 | |||
459 | uint16_t w, h, div, mul; | ||
460 | uint8_t bpp; | ||
461 | |||
462 | switch (framedesc[2]) { | ||
463 | case UVC_VS_FRAME_MJPEG: { | ||
464 | const usbh_uvc_frame_mjpeg_t *frame = (const usbh_uvc_frame_mjpeg_t *)framedesc; | ||
465 | //const usbh_uvc_format_mjpeg_t *fmt = (const usbh_uvc_format_mjpeg_t *)formatdesc; | ||
466 | w = frame->wWidth; | ||
467 | h = frame->wHeight; | ||
468 | bpp = 16; //TODO: check this!! | ||
469 | mul = 1; | ||
470 | div = 5; //TODO: check this estimate | ||
471 | } break; | ||
472 | case UVC_VS_FRAME_UNCOMPRESSED: { | ||
473 | const usbh_uvc_frame_uncompressed_t *frame = (const usbh_uvc_frame_uncompressed_t *)framedesc; | ||
474 | const usbh_uvc_format_uncompressed *fmt = (const usbh_uvc_format_uncompressed *)formatdesc; | ||
475 | w = frame->wWidth; | ||
476 | h = frame->wHeight; | ||
477 | bpp = fmt->bBitsPerPixel; | ||
478 | mul = div = 1; | ||
479 | } break; | ||
480 | default: | ||
481 | uclassdrvwarn("Unsupported format"); | ||
482 | return 0xffffffff; | ||
483 | } | ||
484 | |||
485 | uint32_t sz = w * h / 8 * bpp; | ||
486 | sz *= 10000000UL / dwFrameInterval; | ||
487 | sz /= 1000; | ||
488 | |||
489 | if (uvcdp->dev->speed == USBH_DEVSPEED_HIGH) | ||
490 | div *= 8; | ||
491 | |||
492 | return (sz * mul) / div + 12; | ||
493 | } | ||
494 | |||
495 | static usbh_baseclassdriver_t *_uvc_load(usbh_device_t *dev, const uint8_t *descriptor, uint16_t rem) { | ||
496 | |||
497 | USBHUVCDriver *uvcdp; | ||
498 | uint8_t i; | ||
499 | |||
500 | if (_usbh_match_descriptor(descriptor, rem, USBH_DT_INTERFACE_ASSOCIATION, | ||
501 | 0x0e, 0x03, 0x00) != HAL_SUCCESS) | ||
502 | return NULL; | ||
503 | |||
504 | /* alloc driver */ | ||
505 | for (i = 0; i < HAL_USBHUVC_MAX_INSTANCES; i++) { | ||
506 | if (USBHUVCD[i].dev == NULL) { | ||
507 | uvcdp = &USBHUVCD[i]; | ||
508 | goto alloc_ok; | ||
509 | } | ||
510 | } | ||
511 | |||
512 | udevwarn("Can't alloc UVC driver"); | ||
513 | |||
514 | /* can't alloc */ | ||
515 | return NULL; | ||
516 | |||
517 | alloc_ok: | ||
518 | /* initialize the driver's variables */ | ||
519 | uvcdp->ivc.curr = uvcdp->ivs.curr = NULL; | ||
520 | |||
521 | usbhEPSetName(&dev->ctrl, "UVC[CTRL]"); | ||
522 | |||
523 | const usbh_ia_descriptor_t *iad = (const usbh_ia_descriptor_t *)descriptor; | ||
524 | if_iterator_t iif; | ||
525 | generic_iterator_t ics; | ||
526 | generic_iterator_t iep; | ||
527 | |||
528 | iif.iad = iad; | ||
529 | iif.curr = descriptor; | ||
530 | iif.rem = rem; | ||
531 | |||
532 | for (if_iter_next(&iif); iif.valid; if_iter_next(&iif)) { | ||
533 | if (iif.iad != iad) break; | ||
534 | |||
535 | const usbh_interface_descriptor_t *const ifdesc = if_get(&iif); | ||
536 | if (ifdesc->bInterfaceClass != UVC_CC_VIDEO) { | ||
537 | udevwarnf("Skipping Interface %d (class != UVC_CC_VIDEO)", | ||
538 | ifdesc->bInterfaceNumber); | ||
539 | continue; | ||
540 | } | ||
541 | |||
542 | udevinfof("Interface %d, Alt=%d, Class=UVC_CC_VIDEO, Subclass=%02x", | ||
543 | ifdesc->bInterfaceNumber, | ||
544 | ifdesc->bAlternateSetting, | ||
545 | ifdesc->bInterfaceSubClass); | ||
546 | |||
547 | switch (ifdesc->bInterfaceSubClass) { | ||
548 | case UVC_SC_VIDEOCONTROL: | ||
549 | if (uvcdp->ivc.curr == NULL) { | ||
550 | uvcdp->ivc = iif; | ||
551 | } | ||
552 | for (cs_iter_init(&ics, (generic_iterator_t *)&iif); ics.valid; cs_iter_next(&ics)) { | ||
553 | if (ics.curr[1] != UVC_CS_INTERFACE) { | ||
554 | udevwarnf("Unknown descriptor=%02X", ics.curr[1]); | ||
555 | continue; | ||
556 | } | ||
557 | switch (ics.curr[2]) { | ||
558 | case UVC_VC_HEADER: | ||
559 | udevinfo(" VC_HEADER"); break; | ||
560 | case UVC_VC_INPUT_TERMINAL: | ||
561 | udevinfof(" VC_INPUT_TERMINAL, ID=%d", ics.curr[3]); break; | ||
562 | case UVC_VC_OUTPUT_TERMINAL: | ||
563 | udevinfof(" VC_OUTPUT_TERMINAL, ID=%d", ics.curr[3]); break; | ||
564 | case UVC_VC_SELECTOR_UNIT: | ||
565 | udevinfof(" VC_SELECTOR_UNIT, ID=%d", ics.curr[3]); break; | ||
566 | case UVC_VC_PROCESSING_UNIT: | ||
567 | udevinfof(" VC_PROCESSING_UNIT, ID=%d", ics.curr[3]); break; | ||
568 | case UVC_VC_EXTENSION_UNIT: | ||
569 | udevinfof(" VC_EXTENSION_UNIT, ID=%d", ics.curr[3]); break; | ||
570 | default: | ||
571 | udevwarnf("Unknown video bDescriptorSubtype=%02x", ics.curr[2]); | ||
572 | break; | ||
573 | } | ||
574 | } | ||
575 | break; | ||
576 | case UVC_SC_VIDEOSTREAMING: | ||
577 | if (uvcdp->ivs.curr == NULL) { | ||
578 | uvcdp->ivs = iif; | ||
579 | } | ||
580 | for (cs_iter_init(&ics, (generic_iterator_t *)&iif); ics.valid; cs_iter_next(&ics)) { | ||
581 | if (ics.curr[1] != UVC_CS_INTERFACE) { | ||
582 | udevwarnf("Unknown descriptor=%02X", ics.curr[1]); | ||
583 | continue; | ||
584 | } | ||
585 | switch (ics.curr[2]) { | ||
586 | case UVC_VS_INPUT_HEADER: | ||
587 | udevinfo(" VS_INPUT_HEADER"); break; | ||
588 | case UVC_VS_OUTPUT_HEADER: | ||
589 | udevinfo(" VS_OUTPUT_HEADER"); break; | ||
590 | case UVC_VS_STILL_IMAGE_FRAME: | ||
591 | udevinfo(" VS_STILL_IMAGE_FRAME"); break; | ||
592 | |||
593 | case UVC_VS_FORMAT_UNCOMPRESSED: | ||
594 | udevinfof(" VS_FORMAT_UNCOMPRESSED, bFormatIndex=%d", ics.curr[3]); break; | ||
595 | case UVC_VS_FORMAT_MPEG2TS: | ||
596 | udevinfof(" VS_FORMAT_MPEG2TS, bFormatIndex=%d", ics.curr[3]); break; | ||
597 | case UVC_VS_FORMAT_DV: | ||
598 | udevinfof(" VS_FORMAT_DV, bFormatIndex=%d", ics.curr[3]); break; | ||
599 | case UVC_VS_FORMAT_MJPEG: | ||
600 | udevinfof(" VS_FORMAT_MJPEG, bFormatIndex=%d", ics.curr[3]); break; | ||
601 | case UVC_VS_FORMAT_FRAME_BASED: | ||
602 | udevinfof(" VS_FORMAT_FRAME_BASED, bFormatIndex=%d", ics.curr[3]); break; | ||
603 | case UVC_VS_FORMAT_STREAM_BASED: | ||
604 | udevinfof(" VS_FORMAT_STREAM_BASED, bFormatIndex=%d", ics.curr[3]); break; | ||
605 | |||
606 | case UVC_VS_FRAME_UNCOMPRESSED: | ||
607 | udevinfof(" VS_FRAME_UNCOMPRESSED, bFrameIndex=%d", ics.curr[3]); break; | ||
608 | case UVC_VS_FRAME_MJPEG: | ||
609 | udevinfof(" VS_FRAME_MJPEG, bFrameIndex=%d", ics.curr[3]); break; | ||
610 | case UVC_VS_FRAME_FRAME_BASED: | ||
611 | udevinfof(" VS_FRAME_FRAME_BASED, bFrameIndex=%d", ics.curr[3]); break; | ||
612 | |||
613 | case UVC_VS_COLOR_FORMAT: | ||
614 | udevinfo(" VS_COLOR_FORMAT"); break; | ||
615 | default: | ||
616 | udevwarnf("Unknown video bDescriptorSubtype=%02x", ics.curr[2]); | ||
617 | break; | ||
618 | } | ||
619 | } | ||
620 | break; | ||
621 | default: | ||
622 | udevwarnf("Unknown video bInterfaceSubClass=%02x", ifdesc->bInterfaceSubClass); | ||
623 | break; | ||
624 | } | ||
625 | |||
626 | for (ep_iter_init(&iep, &iif); iep.valid; ep_iter_next(&iep)) { | ||
627 | const usbh_endpoint_descriptor_t *const epdesc = ep_get(&iep); | ||
628 | |||
629 | if ((ifdesc->bInterfaceSubClass == UVC_SC_VIDEOCONTROL) | ||
630 | && ((epdesc->bmAttributes & 0x03) == USBH_EPTYPE_INT) | ||
631 | && ((epdesc->bEndpointAddress & 0x80) == USBH_EPDIR_IN)) { | ||
632 | /* found VC interrupt endpoint */ | ||
633 | udevinfof(" VC Interrupt endpoint; %02x, bInterval=%d", | ||
634 | epdesc->bEndpointAddress, epdesc->bInterval); | ||
635 | usbhEPObjectInit(&uvcdp->ep_int, dev, epdesc); | ||
636 | usbhEPSetName(&uvcdp->ep_int, "UVC[INT ]"); | ||
637 | } else if ((ifdesc->bInterfaceSubClass == UVC_SC_VIDEOSTREAMING) | ||
638 | && ((epdesc->bmAttributes & 0x03) == USBH_EPTYPE_ISO) | ||
639 | && ((epdesc->bEndpointAddress & 0x80) == USBH_EPDIR_IN)) { | ||
640 | /* found VS isochronous endpoint */ | ||
641 | udevinfof(" VS Isochronous endpoint; %02x, bInterval=%d, bmAttributes=%02x", | ||
642 | epdesc->bEndpointAddress, epdesc->bInterval, epdesc->bmAttributes); | ||
643 | } else { | ||
644 | /* unknown EP */ | ||
645 | udevwarnf(" <unknown endpoint>, bEndpointAddress=%02x, bmAttributes=%02x", | ||
646 | epdesc->bEndpointAddress, epdesc->bmAttributes); | ||
647 | } | ||
648 | |||
649 | for (cs_iter_init(&ics, &iep); ics.valid; cs_iter_next(&ics)) { | ||
650 | udevinfof(" CS_ENDPOINT bLength=%d, bDescriptorType=%02X", | ||
651 | ics.curr[0], ics.curr[1]); | ||
652 | } | ||
653 | } | ||
654 | } | ||
655 | |||
656 | if ((uvcdp->ivc.curr == NULL) || (uvcdp->ivs.curr == NULL)) { | ||
657 | return NULL; | ||
658 | } | ||
659 | |||
660 | // uvcdp->dev = dev; | ||
661 | |||
662 | _set_vs_alternate(uvcdp, 0); | ||
663 | |||
664 | /* initialize the INT endpoint */ | ||
665 | chPoolObjectInit(&uvcdp->mp_status, sizeof(usbhuvc_message_status_t), NULL); | ||
666 | for(i = 0; i < HAL_USBHUVC_STATUS_PACKETS_COUNT; i++) | ||
667 | chPoolFree(&uvcdp->mp_status, &uvcdp->mp_status_buffer[i]); | ||
668 | |||
669 | usbhEPOpen(&uvcdp->ep_int); | ||
670 | |||
671 | usbhuvc_message_status_t *const msg = (usbhuvc_message_status_t *)chPoolAlloc(&uvcdp->mp_status); | ||
672 | osalDbgCheck(msg); | ||
673 | usbhURBObjectInit(&uvcdp->urb_int, &uvcdp->ep_int, _cb_int, uvcdp, msg->data, USBHUVC_MAX_STATUS_PACKET_SZ); | ||
674 | osalSysLock(); | ||
675 | usbhURBSubmitI(&uvcdp->urb_int); | ||
676 | uvcdp->state = USBHUVC_STATE_ACTIVE; | ||
677 | osalOsRescheduleS(); /* because of usbhURBSubmitI */ | ||
678 | osalSysUnlock(); | ||
679 | |||
680 | dev->keepFullCfgDesc++; | ||
681 | return (usbh_baseclassdriver_t *)uvcdp; | ||
682 | } | ||
683 | |||
684 | static void _uvc_unload(usbh_baseclassdriver_t *drv) { | ||
685 | USBHUVCDriver *const uvcdp = (USBHUVCDriver *)drv; | ||
686 | |||
687 | usbhuvcStreamStop(uvcdp); | ||
688 | |||
689 | usbhEPClose(&uvcdp->ep_int); | ||
690 | |||
691 | //TODO: free | ||
692 | |||
693 | if (drv->dev->keepFullCfgDesc) | ||
694 | drv->dev->keepFullCfgDesc--; | ||
695 | |||
696 | osalSysLock(); | ||
697 | uvcdp->state = USBHUVC_STATE_STOP; | ||
698 | osalSysUnlock(); | ||
699 | } | ||
700 | |||
701 | static void _object_init(USBHUVCDriver *uvcdp) { | ||
702 | osalDbgCheck(uvcdp != NULL); | ||
703 | memset(uvcdp, 0, sizeof(*uvcdp)); | ||
704 | uvcdp->info = &usbhuvcClassDriverInfo; | ||
705 | chMBObjectInit(&uvcdp->mb, uvcdp->mb_buff, HAL_USBHUVC_MAX_MAILBOX_SZ); | ||
706 | chMtxObjectInit(&uvcdp->mtx); | ||
707 | uvcdp->state = USBHUVC_STATE_STOP; | ||
708 | } | ||
709 | |||
710 | static void _uvc_init(void) { | ||
711 | uint8_t i; | ||
712 | for (i = 0; i < HAL_USBHUVC_MAX_INSTANCES; i++) { | ||
713 | _object_init(&USBHUVCD[i]); | ||
714 | } | ||
715 | } | ||
716 | |||
717 | #endif | ||
718 | |||