source: mainline/uspace/lib/usbhost/src/ddf_helpers.c@ 0918382f

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 0918382f was 0918382f, checked in by Jan Vesely <jano.vesely@…>, 12 years ago

usb: Switch to using port number as device identifier.

Port number info will be needed for HS SPLIT transactions. Moreover,
it can be used to produce predictable, persistent device names.

Switch port_number from size_t to unsigned. There are max 256 ports so
even short would be enough.

  • Property mode set to 100644
File size: 17.2 KB
Line 
1/*
2 * Copyright (c) 2013 Jan Vesely
3 * All rights reserved.
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
29/** @addtogroup libusbhost
30 * @{
31 */
32/** @file
33 *
34 */
35
36#include <usb_iface.h>
37#include <usb/classes/classes.h>
38#include <usb/debug.h>
39#include <usb/descriptor.h>
40#include <usb/request.h>
41#include <errno.h>
42#include <str_error.h>
43
44#include "ddf_helpers.h"
45
46#define CTRL_PIPE_MIN_PACKET_SIZE 8
47
48typedef struct hc_dev {
49 ddf_fun_t *ctl_fun;
50 list_t devices;
51 fibril_mutex_t guard;
52 hcd_t hcd;
53} hc_dev_t;
54
55static hc_dev_t *dev_to_hc_dev(ddf_dev_t *dev)
56{
57 return ddf_dev_data_get(dev);
58}
59
60hcd_t *dev_to_hcd(ddf_dev_t *dev)
61{
62 hc_dev_t *hc_dev = dev_to_hc_dev(dev);
63 if (!hc_dev) {
64 usb_log_error("Invalid HCD device.\n");
65 return NULL;
66 }
67 return &hc_dev->hcd;
68}
69
70typedef struct usb_dev {
71 link_t link;
72 list_t devices;
73 ddf_fun_t *fun;
74 usb_address_t address;
75 usb_speed_t speed;
76 unsigned port;
77} usb_dev_t;
78
79static int hcd_ddf_new_device(ddf_dev_t *device, usb_dev_t *hub, unsigned port);
80static int hcd_ddf_remove_device(ddf_dev_t *device, usb_dev_t *hub, unsigned port);
81
82
83/* DDF INTERFACE */
84
85/** Register endpoint interface function.
86 * @param fun DDF function.
87 * @param address USB address of the device.
88 * @param endpoint USB endpoint number to be registered.
89 * @param transfer_type Endpoint's transfer type.
90 * @param direction USB communication direction the endpoint is capable of.
91 * @param max_packet_size Maximu size of packets the endpoint accepts.
92 * @param interval Preferred timeout between communication.
93 * @return Error code.
94 */
95static int register_endpoint(
96 ddf_fun_t *fun, usb_endpoint_t endpoint,
97 usb_transfer_type_t transfer_type, usb_direction_t direction,
98 size_t max_packet_size, unsigned interval)
99{
100 assert(fun);
101 hcd_t *hcd = dev_to_hcd(ddf_fun_get_dev(fun));
102 usb_dev_t *dev = ddf_fun_data_get(fun);
103 assert(hcd);
104 assert(dev);
105 const size_t size = max_packet_size;
106 const usb_target_t target =
107 {{.address = dev->address, .endpoint = endpoint}};
108
109 usb_log_debug("Register endpoint %d:%d %s-%s %zuB %ums.\n",
110 dev->address, endpoint, usb_str_transfer_type(transfer_type),
111 usb_str_direction(direction), max_packet_size, interval);
112
113 return hcd_add_ep(hcd, target, direction, transfer_type,
114 max_packet_size, size);
115}
116
117/** Unregister endpoint interface function.
118 * @param fun DDF function.
119 * @param address USB address of the endpoint.
120 * @param endpoint USB endpoint number.
121 * @param direction Communication direction of the enpdoint to unregister.
122 * @return Error code.
123 */
124static int unregister_endpoint(
125 ddf_fun_t *fun, usb_endpoint_t endpoint, usb_direction_t direction)
126{
127 assert(fun);
128 hcd_t *hcd = dev_to_hcd(ddf_fun_get_dev(fun));
129 usb_dev_t *dev = ddf_fun_data_get(fun);
130 assert(hcd);
131 assert(dev);
132 const usb_target_t target =
133 {{.address = dev->address, .endpoint = endpoint}};
134 usb_log_debug("Unregister endpoint %d:%d %s.\n",
135 dev->address, endpoint, usb_str_direction(direction));
136 return hcd_remove_ep(hcd, target, direction);
137}
138
139static int reserve_default_address(ddf_fun_t *fun, usb_speed_t speed)
140{
141 assert(fun);
142 hcd_t *hcd = dev_to_hcd(ddf_fun_get_dev(fun));
143 usb_dev_t *dev = ddf_fun_data_get(fun);
144 assert(hcd);
145 assert(dev);
146
147 usb_log_debug("Device %d requested default address at %s speed\n",
148 dev->address, usb_str_speed(speed));
149 return hcd_reserve_default_address(hcd, speed);
150}
151
152static int release_default_address(ddf_fun_t *fun)
153{
154 assert(fun);
155 hcd_t *hcd = dev_to_hcd(ddf_fun_get_dev(fun));
156 usb_dev_t *dev = ddf_fun_data_get(fun);
157 assert(hcd);
158 assert(dev);
159
160 usb_log_debug("Device %d released default address\n", dev->address);
161 return hcd_release_default_address(hcd);
162}
163
164static int device_enumerate(ddf_fun_t *fun, unsigned port)
165{
166 assert(fun);
167 ddf_dev_t *ddf_dev = ddf_fun_get_dev(fun);
168 usb_dev_t *dev = ddf_fun_data_get(fun);
169 assert(ddf_dev);
170 assert(dev);
171 usb_log_debug("Hub %d reported a new USB device on port: %u\n",
172 dev->address, port);
173 return hcd_ddf_new_device(ddf_dev, dev, port);
174}
175
176static int device_remove(ddf_fun_t *fun, unsigned port)
177{
178 assert(fun);
179 ddf_dev_t *ddf_dev = ddf_fun_get_dev(fun);
180 usb_dev_t *dev = ddf_fun_data_get(fun);
181 assert(ddf_dev);
182 assert(dev);
183 usb_log_debug("Hub `%s' reported removal of device on port %u\n",
184 ddf_fun_get_name(fun), port);
185 return hcd_ddf_remove_device(ddf_dev, dev, port);
186}
187
188/** Gets handle of the respective device.
189 *
190 * @param[in] fun Device function.
191 * @param[out] handle Place to write the handle.
192 * @return Error code.
193 */
194static int get_my_device_handle(ddf_fun_t *fun, devman_handle_t *handle)
195{
196 assert(fun);
197 if (handle)
198 *handle = ddf_fun_get_handle(fun);
199 return EOK;
200}
201
202/** Inbound communication interface function.
203 * @param fun DDF function.
204 * @param target Communication target.
205 * @param setup_data Data to use in setup stage (control transfers).
206 * @param data Pointer to data buffer.
207 * @param size Size of the data buffer.
208 * @param callback Function to call on communication end.
209 * @param arg Argument passed to the callback function.
210 * @return Error code.
211 */
212static int dev_read(ddf_fun_t *fun, usb_endpoint_t endpoint,
213 uint64_t setup_data, uint8_t *data, size_t size,
214 usbhc_iface_transfer_in_callback_t callback, void *arg)
215{
216 assert(fun);
217 usb_dev_t *usb_dev = ddf_fun_data_get(fun);
218 assert(usb_dev);
219 const usb_target_t target = {{
220 .address = usb_dev->address,
221 .endpoint = endpoint,
222 }};
223 return hcd_send_batch(dev_to_hcd(ddf_fun_get_dev(fun)), target,
224 USB_DIRECTION_IN, data, size, setup_data, callback, NULL, arg,
225 "READ");
226}
227
228/** Outbound communication interface function.
229 * @param fun DDF function.
230 * @param target Communication target.
231 * @param setup_data Data to use in setup stage (control transfers).
232 * @param data Pointer to data buffer.
233 * @param size Size of the data buffer.
234 * @param callback Function to call on communication end.
235 * @param arg Argument passed to the callback function.
236 * @return Error code.
237 */
238static int dev_write(ddf_fun_t *fun, usb_endpoint_t endpoint,
239 uint64_t setup_data, const uint8_t *data, size_t size,
240 usbhc_iface_transfer_out_callback_t callback, void *arg)
241{
242 assert(fun);
243 usb_dev_t *usb_dev = ddf_fun_data_get(fun);
244 assert(usb_dev);
245 const usb_target_t target = {{
246 .address = usb_dev->address,
247 .endpoint = endpoint,
248 }};
249 return hcd_send_batch(dev_to_hcd(ddf_fun_get_dev(fun)),
250 target, USB_DIRECTION_OUT, (uint8_t*)data, size, setup_data, NULL,
251 callback, arg, "WRITE");
252}
253
254/** USB device interface */
255static usb_iface_t usb_iface = {
256 .get_my_device_handle = get_my_device_handle,
257
258 .reserve_default_address = reserve_default_address,
259 .release_default_address = release_default_address,
260
261 .device_enumerate = device_enumerate,
262 .device_remove = device_remove,
263
264 .register_endpoint = register_endpoint,
265 .unregister_endpoint = unregister_endpoint,
266
267 .read = dev_read,
268 .write = dev_write,
269};
270
271/** Standard USB device interface) */
272static ddf_dev_ops_t usb_ops = {
273 .interfaces[USB_DEV_IFACE] = &usb_iface,
274};
275
276
277/* DDF HELPERS */
278
279#define GET_DEVICE_DESC(size) \
280{ \
281 .request_type = SETUP_REQUEST_TYPE_DEVICE_TO_HOST \
282 | (USB_REQUEST_TYPE_STANDARD << 5) \
283 | USB_REQUEST_RECIPIENT_DEVICE, \
284 .request = USB_DEVREQ_GET_DESCRIPTOR, \
285 .value = uint16_host2usb(USB_DESCTYPE_DEVICE << 8), \
286 .index = uint16_host2usb(0), \
287 .length = uint16_host2usb(size), \
288};
289
290#define SET_ADDRESS(address) \
291{ \
292 .request_type = SETUP_REQUEST_TYPE_HOST_TO_DEVICE \
293 | (USB_REQUEST_TYPE_STANDARD << 5) \
294 | USB_REQUEST_RECIPIENT_DEVICE, \
295 .request = USB_DEVREQ_SET_ADDRESS, \
296 .value = uint16_host2usb(address), \
297 .index = uint16_host2usb(0), \
298 .length = uint16_host2usb(0), \
299};
300
301static int hcd_ddf_add_device(ddf_dev_t *parent, usb_dev_t *hub_dev,
302 unsigned port, usb_address_t address, usb_speed_t speed, const char *name,
303 const match_id_list_t *mids)
304{
305 assert(parent);
306
307 char default_name[10] = { 0 }; /* usbxyz-ss */
308 if (!name) {
309 snprintf(default_name, sizeof(default_name) - 1,
310 "usb%u-%cs", address, usb_str_speed(speed)[0]);
311 name = default_name;
312 }
313
314 //TODO more checks
315 ddf_fun_t *fun = ddf_fun_create(parent, fun_inner, name);
316 if (!fun)
317 return ENOMEM;
318 usb_dev_t *info = ddf_fun_data_alloc(fun, sizeof(usb_dev_t));
319 if (!info) {
320 ddf_fun_destroy(fun);
321 return ENOMEM;
322 }
323 info->address = address;
324 info->speed = speed;
325 info->fun = fun;
326 info->port = port;
327 link_initialize(&info->link);
328 list_initialize(&info->devices);
329
330 ddf_fun_set_ops(fun, &usb_ops);
331 list_foreach(mids->ids, iter) {
332 match_id_t *mid = list_get_instance(iter, match_id_t, link);
333 ddf_fun_add_match_id(fun, mid->id, mid->score);
334 }
335
336 int ret = ddf_fun_bind(fun);
337 if (ret != EOK) {
338 ddf_fun_destroy(fun);
339 return ret;
340 }
341
342 if (hub_dev) {
343 list_append(&info->link, &hub_dev->devices);
344 } else {
345 hc_dev_t *hc_dev = dev_to_hc_dev(parent);
346 list_append(&info->link, &hc_dev->devices);
347 }
348 return EOK;
349}
350
351#define ADD_MATCHID_OR_RETURN(list, sc, str, ...) \
352do { \
353 match_id_t *mid = malloc(sizeof(match_id_t)); \
354 if (!mid) { \
355 clean_match_ids(list); \
356 return ENOMEM; \
357 } \
358 char *id = NULL; \
359 int ret = asprintf(&id, str, ##__VA_ARGS__); \
360 if (ret < 0) { \
361 clean_match_ids(list); \
362 free(mid); \
363 return ENOMEM; \
364 } \
365 mid->score = sc; \
366 mid->id = id; \
367 add_match_id(list, mid); \
368} while (0)
369
370/* This is a copy of lib/usbdev/src/recognise.c */
371static int create_match_ids(match_id_list_t *l,
372 usb_standard_device_descriptor_t *d)
373{
374 assert(l);
375 assert(d);
376
377 if (d->vendor_id != 0) {
378 /* First, with release number. */
379 ADD_MATCHID_OR_RETURN(l, 100,
380 "usb&vendor=%#04x&product=%#04x&release=%x.%x",
381 d->vendor_id, d->product_id, (d->device_version >> 8),
382 (d->device_version & 0xff));
383
384 /* Next, without release number. */
385 ADD_MATCHID_OR_RETURN(l, 90, "usb&vendor=%#04x&product=%#04x",
386 d->vendor_id, d->product_id);
387 }
388
389 /* Class match id */
390 ADD_MATCHID_OR_RETURN(l, 50, "usb&class=%s",
391 usb_str_class(d->device_class));
392
393 /* As a last resort, try fallback driver. */
394 ADD_MATCHID_OR_RETURN(l, 10, "usb&fallback");
395
396 return EOK;
397
398}
399
400static int hcd_ddf_remove_device(ddf_dev_t *device, usb_dev_t *hub,
401 unsigned port)
402{
403 assert(device);
404
405 hcd_t *hcd = dev_to_hcd(device);
406 assert(hcd);
407
408 hc_dev_t *hc_dev = dev_to_hc_dev(device);
409 assert(hc_dev);
410
411 fibril_mutex_lock(&hc_dev->guard);
412
413 usb_dev_t *victim = NULL;
414
415 list_foreach(hub->devices, it) {
416 victim = list_get_instance(it, usb_dev_t, link);
417 if (victim->port == port)
418 break;
419 }
420 if (victim && victim->port == port) {
421 list_remove(&victim->link);
422 fibril_mutex_unlock(&hc_dev->guard);
423 const int ret = ddf_fun_unbind(victim->fun);
424 if (ret == EOK) {
425 ddf_fun_destroy(victim->fun);
426 hcd_release_address(hcd, victim->address);
427 } else {
428 usb_log_warning("Failed to unbind device `%s': %s\n",
429 ddf_fun_get_name(victim->fun), str_error(ret));
430 }
431 return EOK;
432 }
433 return ENOENT;
434}
435
436static int hcd_ddf_new_device(ddf_dev_t *device, usb_dev_t *hub, unsigned port)
437{
438 assert(device);
439
440 hcd_t *hcd = dev_to_hcd(device);
441 assert(hcd);
442
443 usb_speed_t speed = USB_SPEED_MAX;
444
445 /* This checks whether the default address is reserved and gets speed */
446 int ret = usb_endpoint_manager_get_info_by_address(&hcd->ep_manager,
447 USB_ADDRESS_DEFAULT, &speed);
448 if (ret != EOK) {
449 return ret;
450 }
451
452 static const usb_target_t default_target = {{
453 .address = USB_ADDRESS_DEFAULT,
454 .endpoint = 0,
455 }};
456
457 const usb_address_t address = hcd_request_address(hcd, speed);
458 if (address < 0)
459 return address;
460
461 const usb_target_t target = {{
462 .address = address,
463 .endpoint = 0,
464 }};
465
466 /* Add default pipe on default address */
467 ret = hcd_add_ep(hcd,
468 default_target, USB_DIRECTION_BOTH, USB_TRANSFER_CONTROL,
469 CTRL_PIPE_MIN_PACKET_SIZE, CTRL_PIPE_MIN_PACKET_SIZE);
470
471 if (ret != EOK) {
472 hcd_release_address(hcd, address);
473 return ret;
474 }
475
476 /* Get max packet size for default pipe */
477 usb_standard_device_descriptor_t desc = { 0 };
478 static const usb_device_request_setup_packet_t get_device_desc_8 =
479 GET_DEVICE_DESC(CTRL_PIPE_MIN_PACKET_SIZE);
480
481 // TODO CALLBACKS
482 ssize_t got = hcd_send_batch_sync(hcd, default_target, USB_DIRECTION_IN,
483 &desc, CTRL_PIPE_MIN_PACKET_SIZE, *(uint64_t *)&get_device_desc_8,
484 "read first 8 bytes of dev descriptor");
485
486 if (got != CTRL_PIPE_MIN_PACKET_SIZE) {
487 hcd_remove_ep(hcd, default_target, USB_DIRECTION_BOTH);
488 hcd_release_address(hcd, address);
489 return got < 0 ? got : EOVERFLOW;
490 }
491
492 /* Register EP on the new address */
493 ret = hcd_add_ep(hcd, target, USB_DIRECTION_BOTH, USB_TRANSFER_CONTROL,
494 desc.max_packet_size, desc.max_packet_size);
495 if (ret != EOK) {
496 hcd_remove_ep(hcd, default_target, USB_DIRECTION_BOTH);
497 hcd_remove_ep(hcd, target, USB_DIRECTION_BOTH);
498 hcd_release_address(hcd, address);
499 return ret;
500 }
501
502 /* Set new address */
503 const usb_device_request_setup_packet_t set_address =
504 SET_ADDRESS(target.address);
505
506 got = hcd_send_batch_sync(hcd, default_target, USB_DIRECTION_OUT,
507 NULL, 0, *(uint64_t *)&set_address, "set address");
508
509 hcd_remove_ep(hcd, default_target, USB_DIRECTION_BOTH);
510
511 if (got != 0) {
512 hcd_remove_ep(hcd, target, USB_DIRECTION_BOTH);
513 hcd_release_address(hcd, address);
514 return got;
515 }
516
517 /* Get std device descriptor */
518 static const usb_device_request_setup_packet_t get_device_desc =
519 GET_DEVICE_DESC(sizeof(desc));
520
521 got = hcd_send_batch_sync(hcd, target, USB_DIRECTION_IN,
522 &desc, sizeof(desc), *(uint64_t *)&get_device_desc,
523 "read device descriptor");
524 if (ret != EOK) {
525 hcd_remove_ep(hcd, target, USB_DIRECTION_BOTH);
526 hcd_release_address(hcd, target.address);
527 return got < 0 ? got : EOVERFLOW;
528 }
529
530 /* Create match ids from the device descriptor */
531 match_id_list_t mids;
532 init_match_ids(&mids);
533
534 ret = create_match_ids(&mids, &desc);
535 if (ret != EOK) {
536 hcd_remove_ep(hcd, target, USB_DIRECTION_BOTH);
537 hcd_release_address(hcd, target.address);
538 return ret;
539 }
540
541 /* Register device */
542 ret = hcd_ddf_add_device(device, hub, port, address, speed, NULL, &mids);
543 clean_match_ids(&mids);
544 if (ret != EOK) {
545 hcd_remove_ep(hcd, target, USB_DIRECTION_BOTH);
546 hcd_release_address(hcd, target.address);
547 }
548
549 return ret;
550}
551
552/** Announce root hub to the DDF
553 *
554 * @param[in] device Host controller ddf device
555 * @return Error code
556 */
557int hcd_ddf_setup_root_hub(ddf_dev_t *device)
558{
559 assert(device);
560 hcd_t *hcd = dev_to_hcd(device);
561 assert(hcd);
562
563 const usb_speed_t speed = hcd->ep_manager.max_speed;
564
565 hcd_reserve_default_address(hcd, speed);
566 const int ret = hcd_ddf_new_device(device, NULL, 0);
567 hcd_release_default_address(hcd);
568 return ret;
569}
570
571/** Initialize hc structures.
572 *
573 * @param[in] device DDF instance of the device to use.
574 *
575 * This function does all the ddf work for hc driver.
576 */
577int hcd_ddf_setup_hc(ddf_dev_t *device, usb_speed_t max_speed,
578 size_t bw, bw_count_func_t bw_count)
579{
580 assert(device);
581
582 hc_dev_t *instance = ddf_dev_data_alloc(device, sizeof(hc_dev_t));
583 if (instance == NULL) {
584 usb_log_error("Failed to allocate HCD ddf structure.\n");
585 return ENOMEM;
586 }
587 list_initialize(&instance->devices);
588 fibril_mutex_initialize(&instance->guard);
589 hcd_init(&instance->hcd, max_speed, bw, bw_count);
590
591 int ret = ENOMEM;
592 instance->ctl_fun = ddf_fun_create(device, fun_exposed, "ctl");
593 if (!instance->ctl_fun) {
594 usb_log_error("Failed to create HCD ddf fun.\n");
595 goto err_destroy_fun;
596 }
597
598 ret = ddf_fun_bind(instance->ctl_fun);
599 if (ret != EOK) {
600 usb_log_error("Failed to bind ctl_fun: %s.\n", str_error(ret));
601 goto err_destroy_fun;
602 }
603
604 ret = ddf_fun_add_to_category(instance->ctl_fun, USB_HC_CATEGORY);
605 if (ret != EOK) {
606 usb_log_error("Failed to add fun to category: %s.\n",
607 str_error(ret));
608 ddf_fun_unbind(instance->ctl_fun);
609 goto err_destroy_fun;
610 }
611
612 /* HC should be ok at this point (except it can't do anything) */
613 return EOK;
614
615err_destroy_fun:
616 ddf_fun_destroy(instance->ctl_fun);
617 instance->ctl_fun = NULL;
618 return ret;
619}
620
621void hcd_ddf_clean_hc(ddf_dev_t *device)
622{
623 assert(device);
624 hc_dev_t *hc = dev_to_hc_dev(device);
625 assert(hc);
626 const int ret = ddf_fun_unbind(hc->ctl_fun);
627 if (ret == EOK)
628 ddf_fun_destroy(hc->ctl_fun);
629}
630/**
631 * @}
632 */
Note: See TracBrowser for help on using the repository browser.