source: mainline/uspace/lib/usbhost/src/ddf_helpers.c@ 9efad54

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 9efad54 was 9efad54, checked in by Ondřej Hlavatý <aearsis@…>, 7 years ago

usb: move endpoint descriptor parsing to HC

This better separates responsibilities. Now the device driver does not
care about the contents of an endpoint descriptor, and HC can parse the
values according to device's actual speed.

Currently, it is device driver's responsibility to fetch endpoint
descriptors, map them and register pipes - sending the endpoint
descriptor back to HC. HC then parses it, and fills the pipe
description, which then sends back to device driver. We shall probably
fetch the endpoint descriptor from inside the HC (also fixing the USB
spec violation of communication with EP0).

  • Property mode set to 100644
File size: 15.3 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 <adt/list.h>
37#include <assert.h>
38#include <async.h>
39#include <ddf/driver.h>
40#include <ddf/interrupt.h>
41#include <device/hw_res_parsed.h>
42#include <errno.h>
43#include <str_error.h>
44#include <usb/classes/classes.h>
45#include <usb/debug.h>
46#include <usb/descriptor.h>
47#include <usb/usb.h>
48#include <usb_iface.h>
49#include <usbhc_iface.h>
50
51#include "bus.h"
52#include "endpoint.h"
53
54#include "ddf_helpers.h"
55
56
57static int hcd_ddf_new_device(hc_device_t *hcd, ddf_dev_t *hc, device_t *hub_dev, unsigned port);
58static int hcd_ddf_remove_device(ddf_dev_t *device, device_t *hub, unsigned port);
59
60
61/* DDF INTERFACE */
62
63/** Register endpoint interface function.
64 * @param fun DDF function.
65 * @param endpoint_desc Endpoint description.
66 * @return Error code.
67 */
68static int register_endpoint(ddf_fun_t *fun, usb_pipe_desc_t *pipe_desc,
69 const usb_endpoint_descriptors_t *ep_desc)
70{
71 assert(fun);
72 hc_device_t *hcd = dev_to_hcd(ddf_fun_get_dev(fun));
73 device_t *dev = ddf_fun_data_get(fun);
74 assert(hcd);
75 assert(hcd->bus);
76 assert(dev);
77
78 endpoint_t *ep;
79 const int err = bus_endpoint_add(dev, ep_desc, &ep);
80 if (err)
81 return err;
82
83 if (pipe_desc) {
84 pipe_desc->endpoint_no = ep->endpoint;
85 pipe_desc->direction = ep->direction;
86 pipe_desc->transfer_type = ep->transfer_type;
87 pipe_desc->max_transfer_size = ep->max_transfer_size;
88 }
89 endpoint_del_ref(ep);
90
91 return EOK;
92}
93
94 /** Unregister endpoint interface function.
95 * @param fun DDF function.
96 * @param endpoint_desc Endpoint description.
97 * @return Error code.
98 */
99static int unregister_endpoint(ddf_fun_t *fun, const usb_pipe_desc_t *endpoint_desc)
100{
101 assert(fun);
102 hc_device_t *hcd = dev_to_hcd(ddf_fun_get_dev(fun));
103 device_t *dev = ddf_fun_data_get(fun);
104 assert(hcd);
105 assert(hcd->bus);
106 assert(dev);
107
108 const usb_target_t target = {{
109 .address = dev->address,
110 .endpoint = endpoint_desc->endpoint_no
111 }};
112
113 endpoint_t *ep = bus_find_endpoint(dev, target, endpoint_desc->direction);
114 if (!ep)
115 return ENOENT;
116
117 return bus_endpoint_remove(ep);
118}
119
120static int reserve_default_address(ddf_fun_t *fun, usb_speed_t speed)
121{
122 assert(fun);
123 hc_device_t *hcd = dev_to_hcd(ddf_fun_get_dev(fun));
124 device_t *dev = ddf_fun_data_get(fun);
125 assert(hcd);
126 assert(hcd->bus);
127 assert(dev);
128
129 usb_log_debug("Device %d requested default address at %s speed\n",
130 dev->address, usb_str_speed(speed));
131 return bus_reserve_default_address(hcd->bus, speed);
132}
133
134static int release_default_address(ddf_fun_t *fun)
135{
136 assert(fun);
137 hc_device_t *hcd = dev_to_hcd(ddf_fun_get_dev(fun));
138 device_t *dev = ddf_fun_data_get(fun);
139 assert(hcd);
140 assert(hcd->bus);
141 assert(dev);
142
143 usb_log_debug("Device %d released default address\n", dev->address);
144 return bus_release_default_address(hcd->bus);
145}
146
147static int device_enumerate(ddf_fun_t *fun, unsigned port)
148{
149 assert(fun);
150 ddf_dev_t *hc = ddf_fun_get_dev(fun);
151 assert(hc);
152 hc_device_t *hcd = dev_to_hcd(hc);
153 assert(hcd);
154 device_t *hub = ddf_fun_data_get(fun);
155 assert(hub);
156
157 usb_log_debug("Hub %d reported a new USB device on port: %u\n",
158 hub->address, port);
159 return hcd_ddf_new_device(hcd, hc, hub, port);
160}
161
162static int device_remove(ddf_fun_t *fun, unsigned port)
163{
164 assert(fun);
165 ddf_dev_t *ddf_dev = ddf_fun_get_dev(fun);
166 device_t *dev = ddf_fun_data_get(fun);
167 assert(ddf_dev);
168 assert(dev);
169 usb_log_debug("Hub `%s' reported removal of device on port %u\n",
170 ddf_fun_get_name(fun), port);
171 return hcd_ddf_remove_device(ddf_dev, dev, port);
172}
173
174/** Gets handle of the respective device.
175 *
176 * @param[in] fun Device function.
177 * @param[out] handle Place to write the handle.
178 * @return Error code.
179 */
180static int get_my_device_handle(ddf_fun_t *fun, devman_handle_t *handle)
181{
182 assert(fun);
183 if (handle)
184 *handle = ddf_fun_get_handle(fun);
185 return EOK;
186}
187
188/** Inbound communication interface function.
189 * @param fun DDF function.
190 * @param target Communication target.
191 * @param setup_data Data to use in setup stage (control transfers).
192 * @param data Pointer to data buffer.
193 * @param size Size of the data buffer.
194 * @param callback Function to call on communication end.
195 * @param arg Argument passed to the callback function.
196 * @return Error code.
197 */
198static int dev_read(ddf_fun_t *fun, usb_target_t target,
199 uint64_t setup_data, char *data, size_t size,
200 usbhc_iface_transfer_callback_t callback, void *arg)
201{
202 assert(fun);
203 device_t *dev = ddf_fun_data_get(fun);
204 assert(dev);
205
206 target.address = dev->address;
207
208 return bus_device_send_batch(dev, target, USB_DIRECTION_IN,
209 data, size, setup_data,
210 callback, arg, "READ");
211}
212
213/** Outbound communication interface function.
214 * @param fun DDF function.
215 * @param target Communication target.
216 * @param setup_data Data to use in setup stage (control transfers).
217 * @param data Pointer to data buffer.
218 * @param size Size of the data buffer.
219 * @param callback Function to call on communication end.
220 * @param arg Argument passed to the callback function.
221 * @return Error code.
222 */
223static int dev_write(ddf_fun_t *fun, usb_target_t target,
224 uint64_t setup_data, const char *data, size_t size,
225 usbhc_iface_transfer_callback_t callback, void *arg)
226{
227 assert(fun);
228 device_t *dev = ddf_fun_data_get(fun);
229 assert(dev);
230
231 target.address = dev->address;
232
233 return bus_device_send_batch(dev, target, USB_DIRECTION_OUT,
234 (char *) data, size, setup_data,
235 callback, arg, "WRITE");
236}
237
238/** USB device interface */
239static usb_iface_t usb_iface = {
240 .get_my_device_handle = get_my_device_handle,
241};
242
243/** USB host controller interface */
244static usbhc_iface_t usbhc_iface = {
245 .reserve_default_address = reserve_default_address,
246 .release_default_address = release_default_address,
247
248 .device_enumerate = device_enumerate,
249 .device_remove = device_remove,
250
251 .register_endpoint = register_endpoint,
252 .unregister_endpoint = unregister_endpoint,
253
254 .read = dev_read,
255 .write = dev_write,
256};
257
258/** Standard USB device interface) */
259static ddf_dev_ops_t usb_ops = {
260 .interfaces[USB_DEV_IFACE] = &usb_iface,
261 .interfaces[USBHC_DEV_IFACE] = &usbhc_iface,
262};
263
264
265/* DDF HELPERS */
266
267#define ADD_MATCHID_OR_RETURN(list, sc, str, ...) \
268do { \
269 match_id_t *mid = malloc(sizeof(match_id_t)); \
270 if (!mid) { \
271 clean_match_ids(list); \
272 return ENOMEM; \
273 } \
274 char *id = NULL; \
275 int ret = asprintf(&id, str, ##__VA_ARGS__); \
276 if (ret < 0) { \
277 clean_match_ids(list); \
278 free(mid); \
279 return ENOMEM; \
280 } \
281 mid->score = sc; \
282 mid->id = id; \
283 add_match_id(list, mid); \
284} while (0)
285
286/* This is a copy of lib/usbdev/src/recognise.c */
287static int create_match_ids(match_id_list_t *l,
288 usb_standard_device_descriptor_t *d)
289{
290 assert(l);
291 assert(d);
292
293 if (d->vendor_id != 0) {
294 /* First, with release number. */
295 ADD_MATCHID_OR_RETURN(l, 100,
296 "usb&vendor=%#04x&product=%#04x&release=%x.%x",
297 d->vendor_id, d->product_id, (d->device_version >> 8),
298 (d->device_version & 0xff));
299
300 /* Next, without release number. */
301 ADD_MATCHID_OR_RETURN(l, 90, "usb&vendor=%#04x&product=%#04x",
302 d->vendor_id, d->product_id);
303 }
304
305 /* Class match id */
306 ADD_MATCHID_OR_RETURN(l, 50, "usb&class=%s",
307 usb_str_class(d->device_class));
308
309 /* As a last resort, try fallback driver. */
310 ADD_MATCHID_OR_RETURN(l, 10, "usb&fallback");
311
312 return EOK;
313}
314
315static int hcd_ddf_remove_device(ddf_dev_t *device, device_t *hub,
316 unsigned port)
317{
318 assert(device);
319
320 hc_device_t *hcd = dev_to_hcd(device);
321 assert(hcd);
322 assert(hcd->bus);
323
324 fibril_mutex_lock(&hub->guard);
325
326 device_t *victim = NULL;
327
328 list_foreach(hub->devices, link, device_t, it) {
329 if (it->port == port) {
330 victim = it;
331 break;
332 }
333 }
334 if (victim) {
335 assert(victim->fun);
336 assert(victim->port == port);
337 assert(victim->hub == hub);
338 list_remove(&victim->link);
339 fibril_mutex_unlock(&hub->guard);
340 const int ret = ddf_fun_unbind(victim->fun);
341 if (ret == EOK) {
342 bus_device_remove(victim);
343 ddf_fun_destroy(victim->fun);
344 } else {
345 usb_log_warning("Failed to unbind device `%s': %s\n",
346 ddf_fun_get_name(victim->fun), str_error(ret));
347 }
348 return EOK;
349 }
350 fibril_mutex_unlock(&hub->guard);
351 return ENOENT;
352}
353
354device_t *hcd_ddf_fun_create(hc_device_t *hc)
355{
356 /* Create DDF function for the new device */
357 ddf_fun_t *fun = ddf_fun_create(hc->ddf_dev, fun_inner, NULL);
358 if (!fun)
359 return NULL;
360
361 ddf_fun_set_ops(fun, &usb_ops);
362
363 /* Create USB device node for the new device */
364 device_t *dev = ddf_fun_data_alloc(fun, hc->bus->device_size);
365 if (!dev) {
366 ddf_fun_destroy(fun);
367 return NULL;
368 }
369
370 bus_device_init(dev, hc->bus);
371 dev->fun = fun;
372 return dev;
373}
374
375void hcd_ddf_fun_destroy(device_t *dev)
376{
377 assert(dev);
378 assert(dev->fun);
379 ddf_fun_destroy(dev->fun);
380}
381
382int hcd_device_explore(device_t *device)
383{
384 int err;
385 match_id_list_t mids;
386 usb_standard_device_descriptor_t desc = { 0 };
387
388 init_match_ids(&mids);
389
390 const usb_target_t control_ep = {{
391 .address = device->address,
392 .endpoint = 0,
393 }};
394
395 /* Get std device descriptor */
396 const usb_device_request_setup_packet_t get_device_desc =
397 GET_DEVICE_DESC(sizeof(desc));
398
399 usb_log_debug("Device(%d): Requesting full device descriptor.",
400 device->address);
401 ssize_t got = bus_device_send_batch_sync(device, control_ep, USB_DIRECTION_IN,
402 (char *) &desc, sizeof(desc), *(uint64_t *)&get_device_desc,
403 "read device descriptor");
404 if (got < 0) {
405 err = got < 0 ? got : EOVERFLOW;
406 usb_log_error("Device(%d): Failed to set get dev descriptor: %s",
407 device->address, str_error(err));
408 goto out;
409 }
410
411 /* Create match ids from the device descriptor */
412 usb_log_debug("Device(%d): Creating match IDs.", device->address);
413 if ((err = create_match_ids(&mids, &desc))) {
414 usb_log_error("Device(%d): Failed to create match ids: %s", device->address, str_error(err));
415 goto out;
416 }
417
418 list_foreach(mids.ids, link, const match_id_t, mid) {
419 ddf_fun_add_match_id(device->fun, mid->id, mid->score);
420 }
421
422out:
423 clean_match_ids(&mids);
424 return err;
425}
426
427static int hcd_ddf_new_device(hc_device_t *hcd, ddf_dev_t *hc, device_t *hub, unsigned port)
428{
429 int err;
430 assert(hcd);
431 assert(hcd->bus);
432 assert(hub);
433 assert(hc);
434
435 device_t *dev = hcd_ddf_fun_create(hcd);
436 if (!dev) {
437 usb_log_error("Failed to create USB device function.");
438 return ENOMEM;
439 }
440
441 dev->hub = hub;
442 dev->port = port;
443
444 if ((err = bus_device_enumerate(dev))) {
445 usb_log_error("Failed to initialize USB dev memory structures.");
446 return err;
447 }
448
449 /* If the driver didn't name the dev when enumerating,
450 * do it in some generic way.
451 */
452 if (!ddf_fun_get_name(dev->fun)) {
453 bus_device_set_default_name(dev);
454 }
455
456 if ((err = ddf_fun_bind(dev->fun))) {
457 usb_log_error("Device(%d): Failed to register: %s.", dev->address, str_error(err));
458 goto err_usb_dev;
459 }
460
461 fibril_mutex_lock(&hub->guard);
462 list_append(&dev->link, &hub->devices);
463 fibril_mutex_unlock(&hub->guard);
464
465 return EOK;
466
467err_usb_dev:
468 hcd_ddf_fun_destroy(dev);
469 return err;
470}
471
472/** Announce root hub to the DDF
473 *
474 * @param[in] device Host controller ddf device
475 * @return Error code
476 */
477int hcd_setup_virtual_root_hub(hc_device_t *hcd)
478{
479 int err;
480
481 assert(hcd);
482
483 if ((err = bus_reserve_default_address(hcd->bus, USB_SPEED_MAX))) {
484 usb_log_error("Failed to reserve default address for roothub setup: %s", str_error(err));
485 return err;
486 }
487
488 device_t *dev = hcd_ddf_fun_create(hcd);
489 if (!dev) {
490 usb_log_error("Failed to create function for the root hub.");
491 goto err_default_address;
492 }
493
494 ddf_fun_set_name(dev->fun, "roothub");
495
496 /* Assign an address to the device */
497 if ((err = bus_device_enumerate(dev))) {
498 usb_log_error("Failed to enumerate roothub device: %s", str_error(err));
499 goto err_usb_dev;
500 }
501
502 if ((err = ddf_fun_bind(dev->fun))) {
503 usb_log_error("Failed to register roothub: %s.", str_error(err));
504 goto err_usb_dev;
505 }
506
507 bus_release_default_address(hcd->bus);
508 return EOK;
509
510err_usb_dev:
511 hcd_ddf_fun_destroy(dev);
512err_default_address:
513 bus_release_default_address(hcd->bus);
514 return err;
515}
516
517/** Initialize hc structures.
518 *
519 * @param[in] device DDF instance of the device to use.
520 * @param[in] max_speed Maximum supported USB speed.
521 * @param[in] bw available bandwidth.
522 * @param[in] bw_count Function to compute required ep bandwidth.
523 *
524 * @return Error code.
525 * This function does all the ddf work for hc driver.
526 */
527int hcd_ddf_setup_hc(ddf_dev_t *device, size_t size)
528{
529 assert(device);
530
531 hc_device_t *instance = ddf_dev_data_alloc(device, size);
532 if (instance == NULL) {
533 usb_log_error("Failed to allocate HCD ddf structure.\n");
534 return ENOMEM;
535 }
536 instance->ddf_dev = device;
537
538 int ret = ENOMEM;
539 instance->ctl_fun = ddf_fun_create(device, fun_exposed, "ctl");
540 if (!instance->ctl_fun) {
541 usb_log_error("Failed to create HCD ddf fun.\n");
542 goto err_destroy_fun;
543 }
544
545 ret = ddf_fun_bind(instance->ctl_fun);
546 if (ret != EOK) {
547 usb_log_error("Failed to bind ctl_fun: %s.\n", str_error(ret));
548 goto err_destroy_fun;
549 }
550
551 ret = ddf_fun_add_to_category(instance->ctl_fun, USB_HC_CATEGORY);
552 if (ret != EOK) {
553 usb_log_error("Failed to add fun to category: %s.\n",
554 str_error(ret));
555 ddf_fun_unbind(instance->ctl_fun);
556 goto err_destroy_fun;
557 }
558
559 /* HC should be ok at this point (except it can't do anything) */
560 return EOK;
561
562err_destroy_fun:
563 ddf_fun_destroy(instance->ctl_fun);
564 instance->ctl_fun = NULL;
565 return ret;
566}
567
568void hcd_ddf_clean_hc(hc_device_t *hcd)
569{
570 if (ddf_fun_unbind(hcd->ctl_fun) == EOK)
571 ddf_fun_destroy(hcd->ctl_fun);
572}
573
574/** Call the parent driver with a request to enable interrupt
575 *
576 * @param[in] device Device asking for interrupts
577 * @param[in] inum Interrupt number
578 * @return Error code.
579 */
580int hcd_ddf_enable_interrupt(hc_device_t *hcd, int inum)
581{
582 async_sess_t *parent_sess = ddf_dev_parent_sess_get(hcd->ddf_dev);
583 if (parent_sess == NULL)
584 return EIO;
585
586 return hw_res_enable_interrupt(parent_sess, inum);
587}
588
589int hcd_ddf_get_registers(hc_device_t *hcd, hw_res_list_parsed_t *hw_res)
590{
591 async_sess_t *parent_sess = ddf_dev_parent_sess_get(hcd->ddf_dev);
592 if (parent_sess == NULL)
593 return EIO;
594
595 hw_res_list_parsed_init(hw_res);
596 const int ret = hw_res_get_list_parsed(parent_sess, hw_res, 0);
597 if (ret != EOK)
598 hw_res_list_parsed_clean(hw_res);
599 return ret;
600}
601
602/**
603 * @}
604 */
Note: See TracBrowser for help on using the repository browser.