source: mainline/uspace/drv/bus/usb/usbhub/usbhub.c@ 7dddd7b

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 7dddd7b was 7dddd7b, checked in by Petr Manek <petr.manek@…>, 8 years ago

usbdev: refactor polling

Until now, device polling had to be executed by calling one of four
proxy functions, which served as a syntax sugar and had no clear
distinction between each other (not to mention misleading names and high
number of arguments).

In this commit, the four mentioned functions are discarded in favor of
one main function, which was proxied by them either way. The number of
arguments have decreased in favor of named struct fields in the auto
polling config structure.

Drivers, which make use of polling, such as usbhid and usbhub were
updated to the latest API design.

  • Property mode set to 100644
File size: 18.2 KB
Line 
1/*
2 * Copyright (c) 2010 Matus Dekanek
3 * Copyright (c) 2011 Jan Vesely
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * - Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * - Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * - The name of the author may not be used to endorse or promote products
16 * derived from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30/** @addtogroup drvusbhub
31 * @{
32 */
33/** @file
34 * @brief usb hub main functionality
35 */
36
37#include <ddf/driver.h>
38#include <stdbool.h>
39#include <errno.h>
40#include <str_error.h>
41#include <inttypes.h>
42#include <stdio.h>
43
44#include <usb/usb.h>
45#include <usb/debug.h>
46#include <usb/dev/pipes.h>
47#include <usb/classes/classes.h>
48#include <usb/descriptor.h>
49#include <usb/dev/recognise.h>
50#include <usb/dev/request.h>
51#include <usb/classes/hub.h>
52#include <usb/dev/poll.h>
53#include <usb_iface.h>
54
55#include "usbhub.h"
56#include "status.h"
57
58#define HUB_FNC_NAME "hub"
59
60/** Hub status-change endpoint description.
61 *
62 * For more information see section 11.15.1 of USB 1.1 specification.
63 */
64const usb_endpoint_description_t hub_status_change_endpoint_description =
65{
66 .transfer_type = USB_TRANSFER_INTERRUPT,
67 .direction = USB_DIRECTION_IN,
68 .interface_class = USB_CLASS_HUB,
69 .interface_subclass = 0,
70 .interface_protocol = 0,
71 .flags = 0
72};
73
74/** Standard get hub global status request */
75static const usb_device_request_setup_packet_t get_hub_status_request = {
76 .request_type = USB_HUB_REQ_TYPE_GET_HUB_STATUS,
77 .request = USB_HUB_REQUEST_GET_STATUS,
78 .index = 0,
79 .value = 0,
80 .length = sizeof(usb_hub_status_t),
81};
82
83static int usb_set_first_configuration(usb_device_t *usb_device);
84static int usb_hub_process_hub_specific_info(usb_hub_dev_t *hub_dev);
85static void usb_hub_over_current(const usb_hub_dev_t *hub_dev,
86 usb_hub_status_t status);
87static void usb_hub_global_interrupt(const usb_hub_dev_t *hub_dev);
88static void usb_hub_polling_terminated_callback(usb_device_t *device,
89 bool was_error, void *data);
90
91static bool usb_hub_polling_error_callback(usb_device_t *dev, int err_code, void *arg)
92{
93 assert(dev);
94 assert(arg);
95 usb_hub_dev_t *hub = arg;
96
97 usb_log_error("Device %s polling error: %s", usb_device_get_name(dev),
98 str_error(err_code));
99
100 /* Continue polling until the device is about to be removed. */
101 return hub->running && !hub->poll_stop;
102}
103
104/**
105 * Initialize hub device driver structure.
106 *
107 * Creates hub representation and fibril that periodically checks hub's status.
108 * Hub representation is passed to the fibril.
109 * @param usb_dev generic usb device information
110 * @return error code
111 */
112int usb_hub_device_add(usb_device_t *usb_dev)
113{
114 assert(usb_dev);
115 /* Create driver soft-state structure */
116 usb_hub_dev_t *hub_dev =
117 usb_device_data_alloc(usb_dev, sizeof(usb_hub_dev_t));
118 if (hub_dev == NULL) {
119 usb_log_error("Failed to create hub driver structure.\n");
120 return ENOMEM;
121 }
122 hub_dev->usb_device = usb_dev;
123 hub_dev->pending_ops_count = 0;
124 hub_dev->running = false;
125 hub_dev->poll_stop = false;
126 fibril_mutex_initialize(&hub_dev->pending_ops_mutex);
127 fibril_mutex_initialize(&hub_dev->poll_guard);
128 fibril_condvar_initialize(&hub_dev->pending_ops_cv);
129 fibril_condvar_initialize(&hub_dev->poll_cv);
130
131 /* Set hub's first configuration. (There should be only one) */
132 int opResult = usb_set_first_configuration(usb_dev);
133 if (opResult != EOK) {
134 usb_log_error("Could not set hub configuration: %s\n",
135 str_error(opResult));
136 return opResult;
137 }
138
139 /* Get port count and create attached_devices. */
140 opResult = usb_hub_process_hub_specific_info(hub_dev);
141 if (opResult != EOK) {
142 usb_log_error("Could process hub specific info, %s\n",
143 str_error(opResult));
144 return opResult;
145 }
146
147 /* Create hub control function. */
148 usb_log_debug("Creating DDF function '" HUB_FNC_NAME "'.\n");
149 hub_dev->hub_fun = usb_device_ddf_fun_create(hub_dev->usb_device,
150 fun_exposed, HUB_FNC_NAME);
151 if (hub_dev->hub_fun == NULL) {
152 usb_log_error("Failed to create hub function.\n");
153 return ENOMEM;
154 }
155
156 /* Bind hub control function. */
157 opResult = ddf_fun_bind(hub_dev->hub_fun);
158 if (opResult != EOK) {
159 usb_log_error("Failed to bind hub function: %s.\n",
160 str_error(opResult));
161 ddf_fun_destroy(hub_dev->hub_fun);
162 return opResult;
163 }
164
165 /* Start hub operation. */
166 const usb_device_auto_polling_t auto_polling = {
167 .debug = 1,
168 .auto_clear_halt = true,
169 .delay = -1,
170 .max_failures = 3,
171 .on_data = hub_port_changes_callback,
172 .on_polling_end = usb_hub_polling_terminated_callback,
173 .on_error = usb_hub_polling_error_callback,
174 .arg = hub_dev,
175 };
176
177 usb_endpoint_mapping_t *epm =
178 usb_device_get_mapped_ep_desc(hub_dev->usb_device,
179 &hub_status_change_endpoint_description);
180 opResult = usb_device_auto_polling(hub_dev->usb_device, epm,
181 &auto_polling, ((hub_dev->port_count + 1 + 7) / 8));
182
183 if (opResult != EOK) {
184 /* Function is already bound */
185 ddf_fun_unbind(hub_dev->hub_fun);
186 ddf_fun_destroy(hub_dev->hub_fun);
187 usb_log_error("Failed to create polling fibril: %s.\n",
188 str_error(opResult));
189 return opResult;
190 }
191 hub_dev->running = true;
192 usb_log_info("Controlling hub '%s' (%p: %zu ports).\n",
193 usb_device_get_name(hub_dev->usb_device), hub_dev,
194 hub_dev->port_count);
195
196 return EOK;
197}
198
199/**
200 * Turn off power to all ports.
201 *
202 * @param usb_dev generic usb device information
203 * @return error code
204 */
205int usb_hub_device_remove(usb_device_t *usb_dev)
206{
207 assert(usb_dev);
208 usb_hub_dev_t *hub = usb_device_data_get(usb_dev);
209 assert(hub);
210
211 usb_log_info("(%p) USB hub will be removed.", hub);
212
213 /* Instruct the hub to stop once endpoints are unregistered. */
214 hub->poll_stop = true;
215 return EOK;
216}
217
218static int usb_hub_cleanup(usb_hub_dev_t *hub)
219{
220 assert(!hub->running);
221
222 for (size_t port = 0; port < hub->port_count; ++port) {
223 const int ret = usb_hub_port_fini(&hub->ports[port], hub);
224 if (ret != EOK)
225 return ret;
226 }
227 free(hub->ports);
228
229 const int ret = ddf_fun_unbind(hub->hub_fun);
230 if (ret != EOK) {
231 usb_log_error("(%p) Failed to unbind '%s' function: %s.",
232 hub, HUB_FNC_NAME, str_error(ret));
233 return ret;
234 }
235 ddf_fun_destroy(hub->hub_fun);
236
237 usb_log_info("(%p) USB hub driver stopped and cleaned.", hub);
238
239 /* Device data (usb_hub_dev_t) will be freed by usbdev. */
240 return EOK;
241}
242
243int usb_hub_device_removed(usb_device_t *usb_dev)
244{
245 assert(usb_dev);
246 usb_hub_dev_t *hub = usb_device_data_get(usb_dev);
247 assert(hub);
248
249 usb_log_info("(%p) USB hub was removed, joining polling fibril.", hub);
250
251 /* Join polling fibril. */
252 fibril_mutex_lock(&hub->poll_guard);
253 while (hub->running)
254 fibril_condvar_wait(&hub->poll_cv, &hub->poll_guard);
255 fibril_mutex_unlock(&hub->poll_guard);
256
257 usb_log_info("(%p) USB hub polling stopped, freeing memory.", hub);
258
259 /* Destroy hub. */
260 return usb_hub_cleanup(hub);
261}
262
263/**
264 * Remove all attached devices
265 * @param usb_dev generic usb device information
266 * @return error code
267 */
268int usb_hub_device_gone(usb_device_t *usb_dev)
269{
270 assert(usb_dev);
271 usb_hub_dev_t *hub = usb_device_data_get(usb_dev);
272 assert(hub);
273
274 hub->poll_stop = true;
275 usb_log_info("(%p) USB hub gone, joining polling fibril.", hub);
276
277 /* Join polling fibril. */
278 fibril_mutex_lock(&hub->poll_guard);
279 while (hub->running)
280 fibril_condvar_wait(&hub->poll_cv, &hub->poll_guard);
281 fibril_mutex_unlock(&hub->poll_guard);
282
283 /* Destroy hub. */
284 return usb_hub_cleanup(hub);
285}
286
287/** Callback for polling hub for changes.
288 *
289 * @param dev Device where the change occured.
290 * @param change_bitmap Bitmap of changed ports.
291 * @param change_bitmap_size Size of the bitmap in bytes.
292 * @param arg Custom argument, points to @c usb_hub_dev_t.
293 * @return Whether to continue polling.
294 */
295bool hub_port_changes_callback(usb_device_t *dev,
296 uint8_t *change_bitmap, size_t change_bitmap_size, void *arg)
297{
298 usb_hub_dev_t *hub = arg;
299 assert(hub);
300
301 /* It is an error condition if we didn't receive enough data */
302 if (change_bitmap_size == 0) {
303 return false;
304 }
305
306 /* Lowest bit indicates global change */
307 const bool change = change_bitmap[0] & 1;
308 if (change) {
309 usb_hub_global_interrupt(hub);
310 }
311
312 /* N + 1 bit indicates change on port N */
313 for (size_t port = 0; port < hub->port_count; ++port) {
314 const size_t bit = port + 1;
315 const bool change = (change_bitmap[bit / 8] >> (bit % 8)) & 1;
316 if (change) {
317 usb_hub_port_process_interrupt(&hub->ports[port], hub);
318 }
319 }
320 return true;
321}
322
323/**
324 * Load hub-specific information into hub_dev structure and process if needed
325 *
326 * Read port count and initialize structures holding per port information.
327 * If there are any non-removable devices, start initializing them.
328 * This function is hub-specific and should be run only after the hub is
329 * configured using usb_set_first_configuration function.
330 * @param hub_dev hub representation
331 * @return error code
332 */
333static int usb_hub_process_hub_specific_info(usb_hub_dev_t *hub_dev)
334{
335 assert(hub_dev);
336
337 /* Get hub descriptor. */
338 usb_log_debug("(%p): Retrieving descriptor.", hub_dev);
339 usb_pipe_t *control_pipe =
340 usb_device_get_default_pipe(hub_dev->usb_device);
341
342 usb_hub_descriptor_header_t descriptor;
343 size_t received_size;
344 int opResult = usb_request_get_descriptor(control_pipe,
345 USB_REQUEST_TYPE_CLASS, USB_REQUEST_RECIPIENT_DEVICE,
346 USB_DESCTYPE_HUB, 0, 0, &descriptor,
347 sizeof(usb_hub_descriptor_header_t), &received_size);
348 if (opResult != EOK) {
349 usb_log_error("(%p): Failed to receive hub descriptor: %s.\n",
350 hub_dev, str_error(opResult));
351 return opResult;
352 }
353
354 usb_log_debug("(%p): Setting port count to %d.\n", hub_dev,
355 descriptor.port_count);
356 hub_dev->port_count = descriptor.port_count;
357
358 hub_dev->ports = calloc(hub_dev->port_count, sizeof(usb_hub_port_t));
359 if (!hub_dev->ports) {
360 return ENOMEM;
361 }
362
363 for (size_t port = 0; port < hub_dev->port_count; ++port) {
364 usb_hub_port_init(
365 &hub_dev->ports[port], port + 1, control_pipe);
366 }
367
368 hub_dev->power_switched =
369 !(descriptor.characteristics & HUB_CHAR_NO_POWER_SWITCH_FLAG);
370 hub_dev->per_port_power =
371 descriptor.characteristics & HUB_CHAR_POWER_PER_PORT_FLAG;
372
373 if (!hub_dev->power_switched) {
374 usb_log_info("(%p): Power switching not supported, "
375 "ports always powered.", hub_dev);
376 return EOK;
377 }
378
379 usb_log_info("(%p): Hub port power switching enabled (%s).\n", hub_dev,
380 hub_dev->per_port_power ? "per port" : "ganged");
381
382 for (unsigned int port = 0; port < hub_dev->port_count; ++port) {
383 usb_log_debug("(%p): Powering port %u.", hub_dev, port);
384 const int ret = usb_hub_port_set_feature(
385 &hub_dev->ports[port], USB_HUB_FEATURE_PORT_POWER);
386
387 if (ret != EOK) {
388 usb_log_error("(%p-%u): Cannot power on port: %s.\n",
389 hub_dev, hub_dev->ports[port].port_number,
390 str_error(ret));
391 } else {
392 if (!hub_dev->per_port_power) {
393 usb_log_debug("(%p) Ganged power switching, "
394 "one port is enough.", hub_dev);
395 break;
396 }
397 }
398 }
399 return EOK;
400}
401
402/**
403 * Set configuration of and USB device
404 *
405 * Check whether there is at least one configuration and sets the first one.
406 * This function should be run prior to running any hub-specific action.
407 * @param usb_device usb device representation
408 * @return error code
409 */
410static int usb_set_first_configuration(usb_device_t *usb_device)
411{
412 assert(usb_device);
413 /* Get number of possible configurations from device descriptor */
414 const size_t configuration_count =
415 usb_device_descriptors(usb_device)->device.configuration_count;
416 usb_log_debug("Hub has %zu configurations.\n", configuration_count);
417
418 if (configuration_count < 1) {
419 usb_log_error("There are no configurations available\n");
420 return EINVAL;
421 }
422
423 const size_t config_size =
424 usb_device_descriptors(usb_device)->full_config_size;
425 const usb_standard_configuration_descriptor_t *config_descriptor =
426 usb_device_descriptors(usb_device)->full_config;
427
428 if (config_size < sizeof(usb_standard_configuration_descriptor_t)) {
429 usb_log_error("Configuration descriptor is not big enough"
430 " to fit standard configuration descriptor.\n");
431 return EOVERFLOW;
432 }
433
434 /* Set configuration. Use the configuration that was in
435 * usb_device->descriptors.configuration i.e. The first one. */
436 const int opResult = usb_request_set_configuration(
437 usb_device_get_default_pipe(usb_device),
438 config_descriptor->configuration_number);
439 if (opResult != EOK) {
440 usb_log_error("Failed to set hub configuration: %s.\n",
441 str_error(opResult));
442 } else {
443 usb_log_debug("\tUsed configuration %d\n",
444 config_descriptor->configuration_number);
445 }
446 return opResult;
447}
448
449/**
450 * Process hub over current change
451 *
452 * This means either to power off the hub or power it on.
453 * @param hub_dev hub instance
454 * @param status hub status bitmask
455 * @return error code
456 */
457static void usb_hub_over_current(const usb_hub_dev_t *hub_dev,
458 usb_hub_status_t status)
459{
460 if (status & USB_HUB_STATUS_OVER_CURRENT) {
461 /* Hub should remove power from all ports if it detects OC */
462 usb_log_warning("(%p) Detected hub over-current condition, "
463 "all ports should be powered off.", hub_dev);
464 return;
465 }
466
467 /* Ports are always powered. */
468 if (!hub_dev->power_switched)
469 return;
470
471 /* Over-current condition is gone, it is safe to turn the ports on. */
472 for (size_t port = 0; port < hub_dev->port_count; ++port) {
473 const int ret = usb_hub_port_set_feature(
474 &hub_dev->ports[port], USB_HUB_FEATURE_PORT_POWER);
475 if (ret != EOK) {
476 usb_log_warning("(%p-%u): HUB OVER-CURRENT GONE: Cannot"
477 " power on port: %s\n", hub_dev,
478 hub_dev->ports[port].port_number, str_error(ret));
479 } else {
480 if (!hub_dev->per_port_power)
481 return;
482 }
483 }
484
485}
486
487/**
488 * Process hub interrupts.
489 *
490 * The change can be either in the over-current condition or local-power change.
491 * @param hub_dev hub instance
492 */
493static void usb_hub_global_interrupt(const usb_hub_dev_t *hub_dev)
494{
495 assert(hub_dev);
496 assert(hub_dev->usb_device);
497 usb_log_debug("(%p): Global interrupt on th hub.", hub_dev);
498 usb_pipe_t *control_pipe =
499 usb_device_get_default_pipe(hub_dev->usb_device);
500
501 usb_hub_status_t status;
502 size_t rcvd_size;
503 /* NOTE: We can't use standard USB GET_STATUS request, because
504 * hubs reply is 4byte instead of 2 */
505 const int opResult = usb_pipe_control_read(control_pipe,
506 &get_hub_status_request, sizeof(get_hub_status_request),
507 &status, sizeof(usb_hub_status_t), &rcvd_size);
508 if (opResult != EOK) {
509 usb_log_error("(%p): Could not get hub status: %s.", hub_dev,
510 str_error(opResult));
511 return;
512 }
513 if (rcvd_size != sizeof(usb_hub_status_t)) {
514 usb_log_error("(%p): Received status has incorrect size: "
515 "%zu != %zu", hub_dev, rcvd_size, sizeof(usb_hub_status_t));
516 return;
517 }
518
519 /* Handle status changes */
520 if (status & USB_HUB_STATUS_C_OVER_CURRENT) {
521 usb_hub_over_current(hub_dev, status);
522 /* Ack change in hub OC flag */
523 const int ret = usb_request_clear_feature(
524 control_pipe, USB_REQUEST_TYPE_CLASS,
525 USB_REQUEST_RECIPIENT_DEVICE,
526 USB_HUB_FEATURE_C_HUB_OVER_CURRENT, 0);
527 if (ret != EOK) {
528 usb_log_error("(%p): Failed to clear hub over-current "
529 "change flag: %s.\n", hub_dev, str_error(opResult));
530 }
531 }
532
533 if (status & USB_HUB_STATUS_C_LOCAL_POWER) {
534 /* NOTE: Handling this is more complicated.
535 * If the transition is from bus power to local power, all
536 * is good and we may signal the parent hub that we don't
537 * need the power.
538 * If the transition is from local power to bus power
539 * the hub should turn off all the ports and devices need
540 * to be reinitialized taking into account the limited power
541 * that is now available.
542 * There is no support for power distribution in HelenOS,
543 * (or other OSes/hub devices that I've seen) so this is not
544 * implemented.
545 * Just ACK the change.
546 */
547 const int ret = usb_request_clear_feature(
548 control_pipe, USB_REQUEST_TYPE_CLASS,
549 USB_REQUEST_RECIPIENT_DEVICE,
550 USB_HUB_FEATURE_C_HUB_LOCAL_POWER, 0);
551 if (opResult != EOK) {
552 usb_log_error("(%p): Failed to clear hub power change "
553 "flag: %s.\n", hub_dev, str_error(ret));
554 }
555 }
556}
557
558/**
559 * callback called from hub polling fibril when the fibril terminates
560 *
561 * Does not perform cleanup, just marks the hub as not running.
562 * @param device usb device afected
563 * @param was_error indicates that the fibril is stoped due to an error
564 * @param data pointer to usb_hub_dev_t structure
565 */
566static void usb_hub_polling_terminated_callback(usb_device_t *device,
567 bool was_error, void *data)
568{
569 usb_hub_dev_t *hub = data;
570 assert(hub);
571
572 fibril_mutex_lock(&hub->pending_ops_mutex);
573
574 /* The device is dead. However there might be some pending operations
575 * that we need to wait for.
576 * One of them is device adding in progress.
577 * The respective fibril is probably waiting for status change
578 * in port reset (port enable) callback.
579 * Such change would never come (otherwise we would not be here).
580 * Thus, we would flush all pending port resets.
581 */
582 if (hub->pending_ops_count > 0) {
583 for (size_t port = 0; port < hub->port_count; ++port) {
584 usb_hub_port_reset_fail(&hub->ports[port]);
585 }
586 }
587 /* And now wait for them. */
588 while (hub->pending_ops_count > 0) {
589 fibril_condvar_wait(&hub->pending_ops_cv,
590 &hub->pending_ops_mutex);
591 }
592 fibril_mutex_unlock(&hub->pending_ops_mutex);
593 hub->running = false;
594
595 /* Signal polling end to joining thread. */
596 fibril_mutex_lock(&hub->poll_guard);
597 fibril_condvar_signal(&hub->poll_cv);
598 fibril_mutex_unlock(&hub->poll_guard);
599}
600/**
601 * @}
602 */
Note: See TracBrowser for help on using the repository browser.