source: mainline/uspace/lib/usbhost/src/usb_endpoint_manager.c@ 8a637a4

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 8a637a4 was 8a637a4, checked in by Martin Decky <martin@…>, 10 years ago

remove EEXISTS in favor of EEXIST

  • Property mode set to 100644
File size: 13.6 KB
Line 
1/*
2 * Copyright (c) 2011 Jan Vesely
3 * All rights eps.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28/** @addtogroup libusbhost
29 * @{
30 */
31/** @file
32 * HC Endpoint management.
33 */
34
35#include <stdbool.h>
36#include <assert.h>
37#include <errno.h>
38
39#include <usb/debug.h>
40#include <usb/host/usb_endpoint_manager.h>
41
42/** Endpoint compare helper function.
43 *
44 * USB_DIRECTION_BOTH matches both IN and OUT.
45 * @param ep Endpoint to compare, non-null.
46 * @param address Tested address.
47 * @param endpoint Tested endpoint number.
48 * @param direction Tested direction.
49 * @return True if ep can be used to communicate with given device,
50 * false otherwise.
51 */
52static inline bool ep_match(const endpoint_t *ep,
53 usb_address_t address, usb_endpoint_t endpoint, usb_direction_t direction)
54{
55 assert(ep);
56 return
57 ((direction == ep->direction)
58 || (ep->direction == USB_DIRECTION_BOTH)
59 || (direction == USB_DIRECTION_BOTH))
60 && (endpoint == ep->endpoint)
61 && (address == ep->address);
62}
63
64/** Get list that holds endpoints for given address.
65 * @param instance usb_endpoint_manager structure, non-null.
66 * @param addr USB address, must be >= 0.
67 * @return Pointer to the appropriate list.
68 */
69static list_t * get_list(usb_endpoint_manager_t *instance, usb_address_t addr)
70{
71 assert(instance);
72 assert(addr >= 0);
73 return &instance->endpoint_lists[addr % ENDPOINT_LIST_COUNT];
74}
75
76/** Internal search function, works on locked structure.
77 * @param instance usb_endpoint_manager structure, non-null.
78 * @param address USB address, must be valid.
79 * @param endpoint USB endpoint number.
80 * @param direction Communication direction.
81 * @return Pointer to endpoint_t structure representing given communication
82 * target, NULL if there is no such endpoint registered.
83 * @note Assumes that the internal mutex is locked.
84 */
85static endpoint_t * find_locked(usb_endpoint_manager_t *instance,
86 usb_address_t address, usb_endpoint_t endpoint, usb_direction_t direction)
87{
88 assert(instance);
89 assert(fibril_mutex_is_locked(&instance->guard));
90 if (address < 0)
91 return NULL;
92 list_foreach(*get_list(instance, address), link, endpoint_t, ep) {
93 if (ep_match(ep, address, endpoint, direction))
94 return ep;
95 }
96 return NULL;
97}
98
99/** Calculate bandwidth that needs to be reserved for communication with EP.
100 * Calculation follows USB 1.1 specification.
101 * @param speed Device's speed.
102 * @param type Type of the transfer.
103 * @param size Number of byte to transfer.
104 * @param max_packet_size Maximum bytes in one packet.
105 */
106size_t bandwidth_count_usb11(usb_speed_t speed, usb_transfer_type_t type,
107 size_t size, size_t max_packet_size)
108{
109 /* We care about bandwidth only for interrupt and isochronous. */
110 if ((type != USB_TRANSFER_INTERRUPT)
111 && (type != USB_TRANSFER_ISOCHRONOUS)) {
112 return 0;
113 }
114
115 const unsigned packet_count =
116 (size + max_packet_size - 1) / max_packet_size;
117 /* TODO: It may be that ISO and INT transfers use only one packet per
118 * transaction, but I did not find text in USB spec to confirm this */
119 /* NOTE: All data packets will be considered to be max_packet_size */
120 switch (speed)
121 {
122 case USB_SPEED_LOW:
123 assert(type == USB_TRANSFER_INTERRUPT);
124 /* Protocol overhead 13B
125 * (3 SYNC bytes, 3 PID bytes, 2 Endpoint + CRC bytes, 2
126 * CRC bytes, and a 3-byte interpacket delay)
127 * see USB spec page 45-46. */
128 /* Speed penalty 8: low speed is 8-times slower*/
129 return packet_count * (13 + max_packet_size) * 8;
130 case USB_SPEED_FULL:
131 /* Interrupt transfer overhead see above
132 * or page 45 of USB spec */
133 if (type == USB_TRANSFER_INTERRUPT)
134 return packet_count * (13 + max_packet_size);
135
136 assert(type == USB_TRANSFER_ISOCHRONOUS);
137 /* Protocol overhead 9B
138 * (2 SYNC bytes, 2 PID bytes, 2 Endpoint + CRC bytes, 2 CRC
139 * bytes, and a 1-byte interpacket delay)
140 * see USB spec page 42 */
141 return packet_count * (9 + max_packet_size);
142 default:
143 return 0;
144 }
145}
146
147/** Initialize to default state.
148 * You need to provide valid bw_count function if you plan to use
149 * add_endpoint/remove_endpoint pair.
150 *
151 * @param instance usb_endpoint_manager structure, non-null.
152 * @param available_bandwidth Size of the bandwidth pool.
153 * @param bw_count function to use to calculate endpoint bw requirements.
154 * @return Error code.
155 */
156int usb_endpoint_manager_init(usb_endpoint_manager_t *instance,
157 size_t available_bandwidth,
158 size_t (*bw_count)(usb_speed_t, usb_transfer_type_t, size_t, size_t))
159{
160 assert(instance);
161 fibril_mutex_initialize(&instance->guard);
162 instance->free_bw = available_bandwidth;
163 instance->bw_count = bw_count;
164 for (unsigned i = 0; i < ENDPOINT_LIST_COUNT; ++i) {
165 list_initialize(&instance->endpoint_lists[i]);
166 }
167 return EOK;
168}
169
170/** Check setup packet data for signs of toggle reset.
171 *
172 * @param[in] instance usb_endpoint_manager structure, non-null.
173 * @param[in] target Device to receive setup packet.
174 * @param[in] data Setup packet data.
175 *
176 * Really ugly one. Resets toggle bit on all endpoints that need it.
177 * @TODO Use tools from libusbdev requests.h
178 */
179void usb_endpoint_manager_reset_eps_if_need(usb_endpoint_manager_t *instance,
180 usb_target_t target, const uint8_t data[8])
181{
182 assert(instance);
183 if (!usb_target_is_valid(target)) {
184 usb_log_error("Invalid data when checking for toggle reset.\n");
185 return;
186 }
187
188 assert(data);
189 switch (data[1])
190 {
191 case 0x01: /* Clear Feature -- resets only cleared ep */
192 /* Recipient is endpoint, value is zero (ENDPOINT_STALL) */
193 // TODO Use macros in libusbdev requests.h
194 if (((data[0] & 0xf) == 1) && ((data[2] | data[3]) == 0)) {
195 fibril_mutex_lock(&instance->guard);
196 /* endpoint number is < 16, thus first byte is enough */
197 list_foreach(*get_list(instance, target.address),
198 link, endpoint_t, ep) {
199 if ((ep->address == target.address)
200 && (ep->endpoint = data[4])) {
201 endpoint_toggle_set(ep,0);
202 }
203 }
204 fibril_mutex_unlock(&instance->guard);
205 }
206 break;
207
208 case 0x9: /* Set Configuration */
209 case 0x11: /* Set Interface */
210 /* Recipient must be device, this resets all endpoints,
211 * In fact there should be no endpoints but EP 0 registered
212 * as different interfaces use different endpoints,
213 * unless you're changing configuration or alternative
214 * interface of an already setup device. */
215 if ((data[0] & 0xf) == 0) {
216 fibril_mutex_lock(&instance->guard);
217 list_foreach(*get_list(instance, target.address),
218 link, endpoint_t, ep) {
219 if (ep->address == target.address) {
220 endpoint_toggle_set(ep,0);
221 }
222 }
223 fibril_mutex_unlock(&instance->guard);
224 }
225 break;
226 }
227}
228
229/** Register endpoint structure.
230 * Checks for duplicates.
231 * @param instance usb_endpoint_manager, non-null.
232 * @param ep endpoint_t to register.
233 * @param data_size Size of data to transfer.
234 * @return Error code.
235 */
236int usb_endpoint_manager_register_ep(usb_endpoint_manager_t *instance,
237 endpoint_t *ep, size_t data_size)
238{
239 assert(instance);
240 if (ep == NULL || ep->address < 0)
241 return EINVAL;
242
243 fibril_mutex_lock(&instance->guard);
244 /* Check for available bandwidth */
245 if (ep->bandwidth > instance->free_bw) {
246 fibril_mutex_unlock(&instance->guard);
247 return ENOSPC;
248 }
249
250 /* Check for existence */
251 const endpoint_t *endpoint =
252 find_locked(instance, ep->address, ep->endpoint, ep->direction);
253 if (endpoint != NULL) {
254 fibril_mutex_unlock(&instance->guard);
255 return EEXIST;
256 }
257 list_append(&ep->link, get_list(instance, ep->address));
258
259 instance->free_bw -= ep->bandwidth;
260 fibril_mutex_unlock(&instance->guard);
261 return EOK;
262}
263
264/** Unregister endpoint structure.
265 * Checks for duplicates.
266 * @param instance usb_endpoint_manager, non-null.
267 * @param ep endpoint_t to unregister.
268 * @return Error code.
269 */
270int usb_endpoint_manager_unregister_ep(
271 usb_endpoint_manager_t *instance, endpoint_t *ep)
272{
273 assert(instance);
274 if (ep == NULL || ep->address < 0)
275 return EINVAL;
276
277 fibril_mutex_lock(&instance->guard);
278 if (!list_member(&ep->link, get_list(instance, ep->address))) {
279 fibril_mutex_unlock(&instance->guard);
280 return ENOENT;
281 }
282 list_remove(&ep->link);
283 instance->free_bw += ep->bandwidth;
284 fibril_mutex_unlock(&instance->guard);
285 return EOK;
286}
287
288/** Find endpoint_t representing the given communication route.
289 * @param instance usb_endpoint_manager, non-null.
290 * @param address
291 */
292endpoint_t * usb_endpoint_manager_find_ep(usb_endpoint_manager_t *instance,
293 usb_address_t address, usb_endpoint_t endpoint, usb_direction_t direction)
294{
295 assert(instance);
296
297 fibril_mutex_lock(&instance->guard);
298 endpoint_t *ep = find_locked(instance, address, endpoint, direction);
299 fibril_mutex_unlock(&instance->guard);
300 return ep;
301}
302
303/** Create and register new endpoint_t structure.
304 * @param instance usb_endpoint_manager structure, non-null.
305 * @param address USB address.
306 * @param endpoint USB endpoint number.
307 * @param direction Communication direction.
308 * @param type USB transfer type.
309 * @param speed USB Communication speed.
310 * @param max_packet_size Maximum size of data packets.
311 * @param data_size Expected communication size.
312 * @param callback function to call just after registering.
313 * @param arg Argument to pass to the callback function.
314 * @return Error code.
315 */
316int usb_endpoint_manager_add_ep(usb_endpoint_manager_t *instance,
317 usb_address_t address, usb_endpoint_t endpoint, usb_direction_t direction,
318 usb_transfer_type_t type, usb_speed_t speed, size_t max_packet_size,
319 size_t data_size, int (*callback)(endpoint_t *, void *), void *arg)
320{
321 assert(instance);
322 if (instance->bw_count == NULL)
323 return ENOTSUP;
324 if (address < 0)
325 return EINVAL;
326
327 const size_t bw =
328 instance->bw_count(speed, type, data_size, max_packet_size);
329
330 fibril_mutex_lock(&instance->guard);
331 /* Check for available bandwidth */
332 if (bw > instance->free_bw) {
333 fibril_mutex_unlock(&instance->guard);
334 return ENOSPC;
335 }
336
337 /* Check for existence */
338 endpoint_t *ep = find_locked(instance, address, endpoint, direction);
339 if (ep != NULL) {
340 fibril_mutex_unlock(&instance->guard);
341 return EEXIST;
342 }
343
344 ep = endpoint_create(
345 address, endpoint, direction, type, speed, max_packet_size, bw);
346 if (!ep) {
347 fibril_mutex_unlock(&instance->guard);
348 return ENOMEM;
349 }
350
351 if (callback) {
352 const int ret = callback(ep, arg);
353 if (ret != EOK) {
354 fibril_mutex_unlock(&instance->guard);
355 endpoint_destroy(ep);
356 return ret;
357 }
358 }
359 list_append(&ep->link, get_list(instance, ep->address));
360
361 instance->free_bw -= ep->bandwidth;
362 fibril_mutex_unlock(&instance->guard);
363 return EOK;
364}
365
366/** Unregister and destroy endpoint_t structure representing given route.
367 * @param instance usb_endpoint_manager structure, non-null.
368 * @param address USB address.
369 * @param endpoint USB endpoint number.
370 * @param direction Communication direction.
371 * @param callback Function to call after unregister, before destruction.
372 * @arg Argument to pass to the callback function.
373 * @return Error code.
374 */
375int usb_endpoint_manager_remove_ep(usb_endpoint_manager_t *instance,
376 usb_address_t address, usb_endpoint_t endpoint, usb_direction_t direction,
377 void (*callback)(endpoint_t *, void *), void *arg)
378{
379 assert(instance);
380 fibril_mutex_lock(&instance->guard);
381 endpoint_t *ep = find_locked(instance, address, endpoint, direction);
382 if (ep != NULL) {
383 list_remove(&ep->link);
384 instance->free_bw += ep->bandwidth;
385 }
386 fibril_mutex_unlock(&instance->guard);
387 if (ep == NULL)
388 return ENOENT;
389
390 if (callback) {
391 callback(ep, arg);
392 }
393 endpoint_destroy(ep);
394 return EOK;
395}
396
397/** Unregister and destroy all endpoints using given address.
398 * @param instance usb_endpoint_manager structure, non-null.
399 * @param address USB address.
400 * @param endpoint USB endpoint number.
401 * @param direction Communication direction.
402 * @param callback Function to call after unregister, before destruction.
403 * @arg Argument to pass to the callback function.
404 * @return Error code.
405 */
406void usb_endpoint_manager_remove_address(usb_endpoint_manager_t *instance,
407 usb_address_t address, void (*callback)(endpoint_t *, void *), void *arg)
408{
409 list_t *list;
410 link_t *link;
411 link_t *next;
412
413 assert(address >= 0);
414 assert(instance);
415 fibril_mutex_lock(&instance->guard);
416
417 list = get_list(instance, address);
418 link = list_first(list);
419 while (link != NULL) {
420 endpoint_t *ep = list_get_instance(link, endpoint_t, link);
421 next = list_next(link, list);
422
423 if (ep->address == address) {
424 list_remove(&ep->link);
425 if (callback)
426 callback(ep, arg);
427 endpoint_destroy(ep);
428 }
429 link = next;
430 }
431 fibril_mutex_unlock(&instance->guard);
432}
433/**
434 * @}
435 */
Note: See TracBrowser for help on using the repository browser.