source: mainline/uspace/lib/usbhost/src/bus.c@ 874381a

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 874381a was a35b458, checked in by Jiří Zárevúcky <zarevucky.jiri@…>, 8 years ago

style: Remove trailing whitespace on _all_ lines, including empty ones, for particular file types.

Command used: tools/srepl '\s\+$' '' -- *.c *.h *.py *.sh *.s *.S *.ag

Currently, whitespace on empty lines is very inconsistent.
There are two basic choices: Either remove the whitespace, or keep empty lines
indented to the level of surrounding code. The former is AFAICT more common,
and also much easier to do automatically.

Alternatively, we could write script for automatic indentation, and use that
instead. However, if such a script exists, it's possible to use the indented
style locally, by having the editor apply relevant conversions on load/save,
without affecting remote repository. IMO, it makes more sense to adopt
the simpler rule.

  • Property mode set to 100644
File size: 17.3 KB
Line 
1/*
2 * Copyright (c) 2018 Ondrej Hlavaty, Petr Manek
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 * The Bus is a structure that serves as an interface of the HC driver
35 * implementation for the usbhost library. Every HC driver that uses libusbhost
36 * must use a bus_t (or its child), fill it with bus_ops and present it to the
37 * library. The library then handles the DDF interface and translates it to the
38 * bus callbacks.
39 */
40
41#include <ddf/driver.h>
42#include <errno.h>
43#include <mem.h>
44#include <macros.h>
45#include <stdio.h>
46#include <str_error.h>
47#include <usb/debug.h>
48#include <usb/dma_buffer.h>
49
50#include "endpoint.h"
51#include "bus.h"
52
53/**
54 * Initializes the base bus structure.
55 */
56void bus_init(bus_t *bus, size_t device_size)
57{
58 assert(bus);
59 assert(device_size >= sizeof(device_t));
60 memset(bus, 0, sizeof(bus_t));
61
62 fibril_mutex_initialize(&bus->guard);
63 bus->device_size = device_size;
64}
65
66/**
67 * Initialize the device_t structure belonging to a bus.
68 */
69int bus_device_init(device_t *dev, bus_t *bus)
70{
71 assert(bus);
72
73 memset(dev, 0, sizeof(*dev));
74
75 dev->bus = bus;
76
77 link_initialize(&dev->link);
78 list_initialize(&dev->devices);
79 fibril_mutex_initialize(&dev->guard);
80
81 return EOK;
82}
83
84/**
85 * Create a name of the ddf function node.
86 */
87int bus_device_set_default_name(device_t *dev)
88{
89 assert(dev);
90 assert(dev->fun);
91
92 char buf[10] = { 0 }; /* usbxyz-ss */
93 snprintf(buf, sizeof(buf), "usb%u-%cs",
94 dev->address, usb_str_speed(dev->speed)[0]);
95
96 return ddf_fun_set_name(dev->fun, buf);
97}
98
99/**
100 * Setup devices Transaction Translation.
101 *
102 * This applies for Low/Full speed devices under High speed hub only. Other
103 * devices just inherit TT from the hub.
104 *
105 * Roothub must be handled specially.
106 */
107static void device_setup_tt(device_t *dev)
108{
109 if (!dev->hub)
110 return;
111
112 if (dev->hub->speed == USB_SPEED_HIGH && usb_speed_is_11(dev->speed)) {
113 /* For LS devices under HS hub */
114 dev->tt.dev = dev->hub;
115 dev->tt.port = dev->port;
116 }
117 else {
118 /* Inherit hub's TT */
119 dev->tt = dev->hub->tt;
120 }
121}
122
123/**
124 * Invoke the device_enumerate bus operation.
125 *
126 * There's no need to synchronize here, because no one knows the device yet.
127 */
128int bus_device_enumerate(device_t *dev)
129{
130 assert(dev);
131
132 if (!dev->bus->ops->device_enumerate)
133 return ENOTSUP;
134
135 if (dev->online)
136 return EINVAL;
137
138 device_setup_tt(dev);
139
140 const int r = dev->bus->ops->device_enumerate(dev);
141 if (r)
142 return r;
143
144 dev->online = true;
145
146 if (dev->hub) {
147 fibril_mutex_lock(&dev->hub->guard);
148 list_append(&dev->link, &dev->hub->devices);
149 fibril_mutex_unlock(&dev->hub->guard);
150 }
151
152 return EOK;
153}
154
155/**
156 * Clean endpoints and children that could have been left behind after
157 * asking the driver of device to offline/remove a device.
158 *
159 * Note that EP0's lifetime is shared with the device, and as such is not
160 * touched.
161 */
162static void device_clean_ep_children(device_t *dev, const char *op)
163{
164 assert(fibril_mutex_is_locked(&dev->guard));
165
166 /* Unregister endpoints left behind. */
167 for (usb_endpoint_t i = 1; i < USB_ENDPOINT_MAX; ++i) {
168 if (!dev->endpoints[i])
169 continue;
170
171 usb_log_warning("USB device '%s' driver left endpoint %u registered after %s.",
172 ddf_fun_get_name(dev->fun), i, op);
173
174 endpoint_t * const ep = dev->endpoints[i];
175 endpoint_add_ref(ep);
176
177 fibril_mutex_unlock(&dev->guard);
178 const int err = bus_endpoint_remove(ep);
179 if (err)
180 usb_log_warning("Endpoint %u cannot be removed. "
181 "Some deffered cleanup was faster?", ep->endpoint);
182
183 endpoint_del_ref(ep);
184 fibril_mutex_lock(&dev->guard);
185 }
186
187 for (usb_endpoint_t i = 1; i < USB_ENDPOINT_MAX; ++i)
188 assert(dev->endpoints[i] == NULL);
189
190 /* Remove also orphaned children. */
191 while (!list_empty(&dev->devices)) {
192 device_t * const child = list_get_instance(list_first(&dev->devices), device_t, link);
193
194 /*
195 * This is not an error condition, as devices cannot remove
196 * their children devices while they are removed, because for
197 * DDF, they are siblings.
198 */
199 usb_log_debug("USB device '%s' driver left device '%s' behind after %s.",
200 ddf_fun_get_name(dev->fun), ddf_fun_get_name(child->fun), op);
201
202 /*
203 * The child node won't disappear, because its parent's driver
204 * is already dead. And the child will need the guard to remove
205 * itself from the list.
206 */
207 fibril_mutex_unlock(&dev->guard);
208 bus_device_gone(child);
209 fibril_mutex_lock(&dev->guard);
210 }
211 assert(list_empty(&dev->devices));
212}
213
214/**
215 * Resolve a USB device that is gone.
216 */
217void bus_device_gone(device_t *dev)
218{
219 assert(dev);
220 assert(dev->fun != NULL);
221
222 const bus_ops_t *ops = dev->bus->ops;
223
224 /* First, block new transfers and operations. */
225 fibril_mutex_lock(&dev->guard);
226 dev->online = false;
227
228 /* Unbinding will need guard unlocked. */
229 fibril_mutex_unlock(&dev->guard);
230
231 /* Remove our device from our hub's children. */
232 if (dev->hub) {
233 fibril_mutex_lock(&dev->hub->guard);
234 list_remove(&dev->link);
235 fibril_mutex_unlock(&dev->hub->guard);
236 }
237
238 /*
239 * Unbind the DDF function. That will result in dev_gone called in
240 * driver, which shall destroy its pipes and remove its children.
241 */
242 const int err = ddf_fun_unbind(dev->fun);
243 if (err) {
244 usb_log_error("Failed to unbind USB device '%s': %s",
245 ddf_fun_get_name(dev->fun), str_error(err));
246 return;
247 }
248
249 /* Remove what driver left behind */
250 fibril_mutex_lock(&dev->guard);
251 device_clean_ep_children(dev, "removing");
252
253 /* Tell the HC to release its resources. */
254 if (ops->device_gone)
255 ops->device_gone(dev);
256
257 /* Check whether the driver didn't forgot EP0 */
258 if (dev->endpoints[0]) {
259 if (ops->endpoint_unregister)
260 ops->endpoint_unregister(dev->endpoints[0]);
261 /* Release the EP0 bus reference */
262 endpoint_del_ref(dev->endpoints[0]);
263 }
264
265 /* Destroy the function, freeing also the device, unlocking mutex. */
266 ddf_fun_destroy(dev->fun);
267}
268
269/**
270 * The user wants this device back online.
271 */
272int bus_device_online(device_t *dev)
273{
274 int rc;
275 assert(dev);
276
277 fibril_mutex_lock(&dev->guard);
278 if (dev->online) {
279 rc = EINVAL;
280 goto err_lock;
281 }
282
283 /* First, tell the HC driver. */
284 const bus_ops_t *ops = dev->bus->ops;
285 if (ops->device_online && (rc = ops->device_online(dev))) {
286 usb_log_warning("Host controller failed to make device '%s' online: %s",
287 ddf_fun_get_name(dev->fun), str_error(rc));
288 goto err_lock;
289 }
290
291 /* Allow creation of new endpoints and communication with the device. */
292 dev->online = true;
293
294 /* Onlining will need the guard */
295 fibril_mutex_unlock(&dev->guard);
296
297 if ((rc = ddf_fun_online(dev->fun))) {
298 usb_log_warning("Failed to take device '%s' online: %s",
299 ddf_fun_get_name(dev->fun), str_error(rc));
300 goto err;
301 }
302
303 usb_log_info("USB Device '%s' is now online.", ddf_fun_get_name(dev->fun));
304 return EOK;
305
306err_lock:
307 fibril_mutex_unlock(&dev->guard);
308err:
309 return rc;
310}
311
312/**
313 * The user requested to take this device offline.
314 */
315int bus_device_offline(device_t *dev)
316{
317 int rc;
318 assert(dev);
319
320 /* Make sure we're the one who offlines this device */
321 if (!dev->online) {
322 rc = ENOENT;
323 goto err;
324 }
325
326 /*
327 * XXX: If the device is removed/offlined just now, this can fail on
328 * assertion. We most probably need some kind of enum status field to
329 * make the synchronization work.
330 */
331
332 /* Tear down all drivers working with the device. */
333 if ((rc = ddf_fun_offline(dev->fun))) {
334 goto err;
335 }
336
337 fibril_mutex_lock(&dev->guard);
338 dev->online = false;
339 device_clean_ep_children(dev, "offlining");
340
341 /* Tell also the HC driver. */
342 const bus_ops_t *ops = dev->bus->ops;
343 if (ops->device_offline)
344 ops->device_offline(dev);
345
346 fibril_mutex_unlock(&dev->guard);
347 usb_log_info("USB Device '%s' is now offline.", ddf_fun_get_name(dev->fun));
348 return EOK;
349
350err:
351 return rc;
352}
353
354/**
355 * Calculate an index to the endpoint array.
356 * For the default control endpoint 0, it must return 0.
357 * For different arguments, the result is stable but not defined.
358 */
359static size_t bus_endpoint_index(usb_endpoint_t ep, usb_direction_t dir)
360{
361 return 2 * ep + (dir == USB_DIRECTION_OUT);
362}
363
364/**
365 * Create and register new endpoint to the bus.
366 *
367 * @param[in] device The device of which the endpoint shall be created
368 * @param[in] desc Endpoint descriptors as reported by the device
369 * @param[out] out_ep The resulting new endpoint reference, if any. Can be NULL.
370 */
371int bus_endpoint_add(device_t *device, const usb_endpoint_descriptors_t *desc, endpoint_t **out_ep)
372{
373 int err;
374 assert(device);
375
376 bus_t *bus = device->bus;
377
378 if (!bus->ops->endpoint_register)
379 return ENOTSUP;
380
381 endpoint_t *ep;
382 if (bus->ops->endpoint_create) {
383 ep = bus->ops->endpoint_create(device, desc);
384 if (!ep)
385 return ENOMEM;
386 } else {
387 ep = calloc(1, sizeof(endpoint_t));
388 if (!ep)
389 return ENOMEM;
390 endpoint_init(ep, device, desc);
391 }
392
393 assert((ep->required_transfer_buffer_policy & ~ep->transfer_buffer_policy) == 0);
394
395 /* Bus reference */
396 endpoint_add_ref(ep);
397
398 const size_t idx = bus_endpoint_index(ep->endpoint, ep->direction);
399 if (idx >= ARRAY_SIZE(device->endpoints)) {
400 usb_log_warning("Invalid endpoint description (ep no %u out of "
401 "bounds)", ep->endpoint);
402 goto drop;
403 }
404
405 if (ep->max_transfer_size == 0) {
406 usb_log_warning("Invalid endpoint description (mps %zu, "
407 "%u packets)", ep->max_packet_size, ep->packets_per_uframe);
408 goto drop;
409 }
410
411 usb_log_debug("Register endpoint %d:%d %s-%s %zuB.",
412 device->address, ep->endpoint,
413 usb_str_transfer_type(ep->transfer_type),
414 usb_str_direction(ep->direction),
415 ep->max_transfer_size);
416
417 fibril_mutex_lock(&device->guard);
418 if (!device->online && ep->endpoint != 0) {
419 err = EAGAIN;
420 } else if (device->endpoints[idx] != NULL) {
421 err = EEXIST;
422 } else {
423 err = bus->ops->endpoint_register(ep);
424 if (!err)
425 device->endpoints[idx] = ep;
426 }
427 fibril_mutex_unlock(&device->guard);
428 if (err) {
429 endpoint_del_ref(ep);
430 return err;
431 }
432
433 if (out_ep) {
434 /* Exporting reference */
435 endpoint_add_ref(ep);
436 *out_ep = ep;
437 }
438
439 return EOK;
440drop:
441 /* Bus reference */
442 endpoint_del_ref(ep);
443 return EINVAL;
444}
445
446/**
447 * Search for an endpoint. Returns a reference.
448 */
449endpoint_t *bus_find_endpoint(device_t *device, usb_endpoint_t endpoint,
450 usb_direction_t dir)
451{
452 assert(device);
453
454 const size_t idx = bus_endpoint_index(endpoint, dir);
455 const size_t ctrl_idx = bus_endpoint_index(endpoint, USB_DIRECTION_BOTH);
456
457 endpoint_t *ep = NULL;
458
459 fibril_mutex_lock(&device->guard);
460 if (idx < ARRAY_SIZE(device->endpoints))
461 ep = device->endpoints[idx];
462 /*
463 * If the endpoint was not found, it's still possible it is a control
464 * endpoint having direction BOTH.
465 */
466 if (!ep && ctrl_idx < ARRAY_SIZE(device->endpoints)) {
467 ep = device->endpoints[ctrl_idx];
468 if (ep && ep->transfer_type != USB_TRANSFER_CONTROL)
469 ep = NULL;
470 }
471 if (ep) {
472 /* Exporting reference */
473 endpoint_add_ref(ep);
474 }
475 fibril_mutex_unlock(&device->guard);
476 return ep;
477}
478
479/**
480 * Remove an endpoint from the device.
481 */
482int bus_endpoint_remove(endpoint_t *ep)
483{
484 assert(ep);
485 assert(ep->device);
486
487 device_t *device = ep->device;
488 if (!device)
489 return ENOENT;
490
491 bus_t *bus = device->bus;
492
493 if (!bus->ops->endpoint_unregister)
494 return ENOTSUP;
495
496 usb_log_debug("Unregister endpoint %d:%d %s-%s %zuB.",
497 device->address, ep->endpoint,
498 usb_str_transfer_type(ep->transfer_type),
499 usb_str_direction(ep->direction),
500 ep->max_transfer_size);
501
502 const size_t idx = bus_endpoint_index(ep->endpoint, ep->direction);
503
504 if (idx >= ARRAY_SIZE(device->endpoints))
505 return EINVAL;
506
507 fibril_mutex_lock(&device->guard);
508
509 /* Check whether the endpoint is registered */
510 if (device->endpoints[idx] != ep) {
511 fibril_mutex_unlock(&device->guard);
512 return EINVAL;
513 }
514
515 bus->ops->endpoint_unregister(ep);
516 device->endpoints[idx] = NULL;
517 fibril_mutex_unlock(&device->guard);
518
519 /* Bus reference */
520 endpoint_del_ref(ep);
521
522 return EOK;
523}
524
525/**
526 * Reserve the default address on the bus for the specified device (hub).
527 */
528int bus_reserve_default_address(bus_t *bus, device_t *dev)
529{
530 assert(bus);
531
532 int err;
533 fibril_mutex_lock(&bus->guard);
534 if (bus->default_address_owner != NULL) {
535 err = (bus->default_address_owner == dev) ? EINVAL : EAGAIN;
536 } else {
537 bus->default_address_owner = dev;
538 err = EOK;
539 }
540 fibril_mutex_unlock(&bus->guard);
541 return err;
542}
543
544/**
545 * Release the default address.
546 */
547void bus_release_default_address(bus_t *bus, device_t *dev)
548{
549 assert(bus);
550
551 fibril_mutex_lock(&bus->guard);
552 if (bus->default_address_owner != dev) {
553 usb_log_error("Device %d tried to release default address, "
554 "which is not reserved for it.", dev->address);
555 } else {
556 bus->default_address_owner = NULL;
557 }
558 fibril_mutex_unlock(&bus->guard);
559}
560
561/**
562 * Assert some conditions on transfer request. As the request is an entity of
563 * HC driver only, we can force these conditions harder. Invalid values from
564 * devices shall be caught on DDF interface already.
565 */
566static void check_request(const transfer_request_t *request)
567{
568 assert(usb_target_is_valid(&request->target));
569 assert(request->dir != USB_DIRECTION_BOTH);
570 /* Non-zero offset => size is non-zero */
571 assert(request->offset == 0 || request->size != 0);
572 /* Non-zero size => buffer is set */
573 assert(request->size == 0 || dma_buffer_is_set(&request->buffer));
574 /* Non-null arg => callback is set */
575 assert(request->arg == NULL || request->on_complete != NULL);
576 assert(request->name);
577}
578
579/**
580 * Initiate a transfer with given device.
581 *
582 * @return Error code.
583 */
584int bus_issue_transfer(device_t *device, const transfer_request_t *request)
585{
586 assert(device);
587 assert(request);
588
589 check_request(request);
590 assert(device->address == request->target.address);
591
592 /* Temporary reference */
593 endpoint_t *ep = bus_find_endpoint(device, request->target.endpoint, request->dir);
594 if (ep == NULL) {
595 usb_log_error("Endpoint(%d:%d) not registered for %s.",
596 device->address, request->target.endpoint, request->name);
597 return ENOENT;
598 }
599
600 assert(ep->device == device);
601
602 const int err = endpoint_send_batch(ep, request);
603
604 /* Temporary reference */
605 endpoint_del_ref(ep);
606
607 return err;
608}
609
610/**
611 * A structure to pass data from the completion callback to the caller.
612 */
613typedef struct {
614 fibril_mutex_t done_mtx;
615 fibril_condvar_t done_cv;
616 bool done;
617
618 size_t transferred_size;
619 int error;
620} sync_data_t;
621
622/**
623 * Callback for finishing the transfer. Wake the issuing thread.
624 */
625static int sync_transfer_complete(void *arg, int error, size_t transferred_size)
626{
627 sync_data_t *d = arg;
628 assert(d);
629 d->transferred_size = transferred_size;
630 d->error = error;
631 fibril_mutex_lock(&d->done_mtx);
632 d->done = true;
633 fibril_condvar_broadcast(&d->done_cv);
634 fibril_mutex_unlock(&d->done_mtx);
635 return EOK;
636}
637
638/**
639 * Issue a transfer on the bus, wait for the result.
640 *
641 * @param device Device for which to send the batch
642 * @param target The target of the transfer.
643 * @param direction A direction of the transfer.
644 * @param data A pointer to the data buffer.
645 * @param size Size of the data buffer.
646 * @param setup_data Data to use in the setup stage (Control communication type)
647 * @param name Communication identifier (for nicer output).
648 */
649errno_t bus_device_send_batch_sync(device_t *device, usb_target_t target,
650 usb_direction_t direction, char *data, size_t size, uint64_t setup_data,
651 const char *name, size_t *transferred_size)
652{
653 int err;
654 sync_data_t sd = { .done = false };
655 fibril_mutex_initialize(&sd.done_mtx);
656 fibril_condvar_initialize(&sd.done_cv);
657
658 transfer_request_t request = {
659 .target = target,
660 .dir = direction,
661 .offset = ((uintptr_t) data) % PAGE_SIZE,
662 .size = size,
663 .setup = setup_data,
664 .on_complete = sync_transfer_complete,
665 .arg = &sd,
666 .name = name,
667 };
668
669 if (data &&
670 (err = dma_buffer_lock(&request.buffer, data - request.offset, size)))
671 return err;
672
673 if ((err = bus_issue_transfer(device, &request))) {
674 dma_buffer_unlock(&request.buffer, size);
675 return err;
676 }
677
678 /*
679 * Note: There are requests that are completed synchronously. It is not
680 * therefore possible to just lock the mutex before and wait.
681 */
682 fibril_mutex_lock(&sd.done_mtx);
683 while (!sd.done)
684 fibril_condvar_wait(&sd.done_cv, &sd.done_mtx);
685 fibril_mutex_unlock(&sd.done_mtx);
686
687 dma_buffer_unlock(&request.buffer, size);
688
689 if (transferred_size)
690 *transferred_size = sd.transferred_size;
691
692 return sd.error;
693}
694
695/**
696 * @}
697 */
Note: See TracBrowser for help on using the repository browser.