source: mainline/uspace/drv/bus/usb/usbhub/usbhub.c@ 0d4b110

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 0d4b110 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: 16.0 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/** @addtogroup drvusbhub
30 * @{
31 */
32/** @file
33 * @brief usb hub main functionality
34 */
35
36#include <ddf/driver.h>
37#include <stdbool.h>
38#include <errno.h>
39#include <str_error.h>
40#include <inttypes.h>
41#include <stdio.h>
42
43#include <usb/usb.h>
44#include <usb/debug.h>
45#include <usb/dev/pipes.h>
46#include <usb/classes/classes.h>
47#include <usb/descriptor.h>
48#include <usb/dev/recognise.h>
49#include <usb/dev/request.h>
50#include <usb/classes/hub.h>
51#include <usb/dev/poll.h>
52#include <usb_iface.h>
53
54#include "usbhub.h"
55#include "status.h"
56
57#define HUB_FNC_NAME "hub"
58/** Hub status-change endpoint description.
59 *
60 * For more information see section 11.15.1 of USB 1.1 specification.
61 */
62const usb_endpoint_description_t hub_status_change_endpoint_description =
63{
64 .transfer_type = USB_TRANSFER_INTERRUPT,
65 .direction = USB_DIRECTION_IN,
66 .interface_class = USB_CLASS_HUB,
67 .interface_subclass = 0,
68 .interface_protocol = 0,
69 .flags = 0
70};
71
72/** Standard get hub global status request */
73static const usb_device_request_setup_packet_t get_hub_status_request = {
74 .request_type = USB_HUB_REQ_TYPE_GET_HUB_STATUS,
75 .request = USB_HUB_REQUEST_GET_STATUS,
76 .index = 0,
77 .value = 0,
78 .length = sizeof(usb_hub_status_t),
79};
80
81static int usb_set_first_configuration(usb_device_t *usb_device);
82static int usb_hub_process_hub_specific_info(usb_hub_dev_t *hub_dev);
83static void usb_hub_over_current(const usb_hub_dev_t *hub_dev,
84 usb_hub_status_t status);
85static void usb_hub_global_interrupt(const usb_hub_dev_t *hub_dev);
86static void usb_hub_polling_terminated_callback(usb_device_t *device,
87 bool was_error, void *data);
88
89/**
90 * Initialize hub device driver structure.
91 *
92 * Creates hub representation and fibril that periodically checks hub's status.
93 * Hub representation is passed to the fibril.
94 * @param usb_dev generic usb device information
95 * @return error code
96 */
97int usb_hub_device_add(usb_device_t *usb_dev)
98{
99 assert(usb_dev);
100 /* Create driver soft-state structure */
101 usb_hub_dev_t *hub_dev =
102 usb_device_data_alloc(usb_dev, sizeof(usb_hub_dev_t));
103 if (hub_dev == NULL) {
104 usb_log_error("Failed to create hub driver structure.\n");
105 return ENOMEM;
106 }
107 hub_dev->usb_device = usb_dev;
108 hub_dev->pending_ops_count = 0;
109 hub_dev->running = false;
110 fibril_mutex_initialize(&hub_dev->pending_ops_mutex);
111 fibril_condvar_initialize(&hub_dev->pending_ops_cv);
112
113 /* Set hub's first configuration. (There should be only one) */
114 int opResult = usb_set_first_configuration(usb_dev);
115 if (opResult != EOK) {
116 usb_log_error("Could not set hub configuration: %s\n",
117 str_error(opResult));
118 return opResult;
119 }
120
121 /* Get port count and create attached_devices. */
122 opResult = usb_hub_process_hub_specific_info(hub_dev);
123 if (opResult != EOK) {
124 usb_log_error("Could process hub specific info, %s\n",
125 str_error(opResult));
126 return opResult;
127 }
128
129 /* Create hub control function. */
130 usb_log_debug("Creating DDF function '" HUB_FNC_NAME "'.\n");
131 hub_dev->hub_fun = usb_device_ddf_fun_create(hub_dev->usb_device,
132 fun_exposed, HUB_FNC_NAME);
133 if (hub_dev->hub_fun == NULL) {
134 usb_log_error("Failed to create hub function.\n");
135 return ENOMEM;
136 }
137
138 /* Bind hub control function. */
139 opResult = ddf_fun_bind(hub_dev->hub_fun);
140 if (opResult != EOK) {
141 usb_log_error("Failed to bind hub function: %s.\n",
142 str_error(opResult));
143 ddf_fun_destroy(hub_dev->hub_fun);
144 return opResult;
145 }
146
147 /* Start hub operation. */
148 opResult = usb_device_auto_poll_desc(hub_dev->usb_device,
149 &hub_status_change_endpoint_description,
150 hub_port_changes_callback, ((hub_dev->port_count + 1 + 7) / 8),
151 -1, usb_hub_polling_terminated_callback, hub_dev);
152 if (opResult != EOK) {
153 /* Function is already bound */
154 ddf_fun_unbind(hub_dev->hub_fun);
155 ddf_fun_destroy(hub_dev->hub_fun);
156 usb_log_error("Failed to create polling fibril: %s.\n",
157 str_error(opResult));
158 return opResult;
159 }
160 hub_dev->running = true;
161 usb_log_info("Controlling hub '%s' (%zu ports).\n",
162 usb_device_get_name(hub_dev->usb_device), hub_dev->port_count);
163
164 return EOK;
165}
166
167/**
168 * Turn off power to all ports.
169 *
170 * @param usb_dev generic usb device information
171 * @return error code
172 */
173int usb_hub_device_remove(usb_device_t *usb_dev)
174{
175 return ENOTSUP;
176}
177
178/**
179 * Remove all attached devices
180 * @param usb_dev generic usb device information
181 * @return error code
182 */
183int usb_hub_device_gone(usb_device_t *usb_dev)
184{
185 assert(usb_dev);
186 usb_hub_dev_t *hub = usb_device_data_get(usb_dev);
187 assert(hub);
188 unsigned tries = 10;
189 while (hub->running) {
190 async_usleep(100000);
191 if (!tries--) {
192 usb_log_error("Can't remove hub, still running.\n");
193 return EINPROGRESS;
194 }
195 }
196
197 assert(!hub->running);
198
199 for (size_t port = 0; port < hub->port_count; ++port) {
200 const int ret = usb_hub_port_fini(&hub->ports[port], hub);
201 if (ret != EOK)
202 return ret;
203 }
204 free(hub->ports);
205
206 const int ret = ddf_fun_unbind(hub->hub_fun);
207 if (ret != EOK) {
208 usb_log_error("Failed to unbind '%s' function: %s.\n",
209 HUB_FNC_NAME, str_error(ret));
210 return ret;
211 }
212 ddf_fun_destroy(hub->hub_fun);
213
214 usb_log_info("USB hub driver, stopped and cleaned.\n");
215 return EOK;
216}
217
218/** Callback for polling hub for changes.
219 *
220 * @param dev Device where the change occured.
221 * @param change_bitmap Bitmap of changed ports.
222 * @param change_bitmap_size Size of the bitmap in bytes.
223 * @param arg Custom argument, points to @c usb_hub_dev_t.
224 * @return Whether to continue polling.
225 */
226bool hub_port_changes_callback(usb_device_t *dev,
227 uint8_t *change_bitmap, size_t change_bitmap_size, void *arg)
228{
229 usb_log_debug("hub_port_changes_callback\n");
230 usb_hub_dev_t *hub = arg;
231 assert(hub);
232
233 /* It is an error condition if we didn't receive enough data */
234 if (change_bitmap_size == 0) {
235 return false;
236 }
237
238 /* Lowest bit indicates global change */
239 const bool change = change_bitmap[0] & 1;
240 if (change) {
241 usb_hub_global_interrupt(hub);
242 }
243
244 /* N + 1 bit indicates change on port N */
245 for (size_t port = 0; port < hub->port_count; ++port) {
246 const size_t bit = port + 1;
247 const bool change = (change_bitmap[bit / 8] >> (bit % 8)) & 1;
248 if (change) {
249 usb_hub_port_process_interrupt(&hub->ports[port], hub);
250 }
251 }
252 return true;
253}
254
255/**
256 * Load hub-specific information into hub_dev structure and process if needed
257 *
258 * Read port count and initialize structures holding per port information.
259 * If there are any non-removable devices, start initializing them.
260 * This function is hub-specific and should be run only after the hub is
261 * configured using usb_set_first_configuration function.
262 * @param hub_dev hub representation
263 * @return error code
264 */
265static int usb_hub_process_hub_specific_info(usb_hub_dev_t *hub_dev)
266{
267 assert(hub_dev);
268
269 /* Get hub descriptor. */
270 usb_log_debug("Retrieving descriptor\n");
271 usb_pipe_t *control_pipe =
272 usb_device_get_default_pipe(hub_dev->usb_device);
273
274 usb_hub_descriptor_header_t descriptor;
275 size_t received_size;
276 int opResult = usb_request_get_descriptor(control_pipe,
277 USB_REQUEST_TYPE_CLASS, USB_REQUEST_RECIPIENT_DEVICE,
278 USB_DESCTYPE_HUB, 0, 0, &descriptor,
279 sizeof(usb_hub_descriptor_header_t), &received_size);
280 if (opResult != EOK) {
281 usb_log_error("Failed to receive hub descriptor: %s.\n",
282 str_error(opResult));
283 return opResult;
284 }
285
286 usb_log_debug("Setting port count to %d.\n", descriptor.port_count);
287 hub_dev->port_count = descriptor.port_count;
288
289 hub_dev->ports = calloc(hub_dev->port_count, sizeof(usb_hub_port_t));
290 if (!hub_dev->ports) {
291 return ENOMEM;
292 }
293
294 for (size_t port = 0; port < hub_dev->port_count; ++port) {
295 usb_hub_port_init(
296 &hub_dev->ports[port], port + 1, control_pipe);
297 }
298
299 hub_dev->power_switched =
300 !(descriptor.characteristics & HUB_CHAR_NO_POWER_SWITCH_FLAG);
301 hub_dev->per_port_power =
302 descriptor.characteristics & HUB_CHAR_POWER_PER_PORT_FLAG;
303
304 if (!hub_dev->power_switched) {
305 usb_log_info(
306 "Power switching not supported, ports always powered.\n");
307 return EOK;
308 }
309
310 usb_log_info("Hub port power switching enabled (%s).\n",
311 hub_dev->per_port_power ? "per port" : "ganged");
312
313 for (size_t port = 0; port < hub_dev->port_count; ++port) {
314 usb_log_debug("Powering port %zu.\n", port);
315 const int ret = usb_hub_port_set_feature(
316 &hub_dev->ports[port], USB_HUB_FEATURE_PORT_POWER);
317
318 if (ret != EOK) {
319 usb_log_error("Cannot power on port %u: %s.\n",
320 hub_dev->ports[port].port_number, str_error(ret));
321 } else {
322 if (!hub_dev->per_port_power) {
323 usb_log_debug("Ganged power switching, "
324 "one port is enough.\n");
325 break;
326 }
327 }
328 }
329 return EOK;
330}
331
332/**
333 * Set configuration of and USB device
334 *
335 * Check whether there is at least one configuration and sets the first one.
336 * This function should be run prior to running any hub-specific action.
337 * @param usb_device usb device representation
338 * @return error code
339 */
340static int usb_set_first_configuration(usb_device_t *usb_device)
341{
342 assert(usb_device);
343 /* Get number of possible configurations from device descriptor */
344 const size_t configuration_count =
345 usb_device_descriptors(usb_device)->device.configuration_count;
346 usb_log_debug("Hub has %zu configurations.\n", configuration_count);
347
348 if (configuration_count < 1) {
349 usb_log_error("There are no configurations available\n");
350 return EINVAL;
351 }
352
353 // TODO: Make sure that the cast is correct
354 const size_t config_size =
355 usb_device_descriptors(usb_device)->full_config_size;
356 const usb_standard_configuration_descriptor_t *config_descriptor =
357 usb_device_descriptors(usb_device)->full_config;
358
359 if (config_size < sizeof(usb_standard_configuration_descriptor_t)) {
360 usb_log_error("Configuration descriptor is not big enough"
361 " to fit standard configuration descriptor.\n");
362 return EOVERFLOW;
363 }
364
365 /* Set configuration. Use the configuration that was in
366 * usb_device->descriptors.configuration i.e. The first one. */
367 const int opResult = usb_request_set_configuration(
368 usb_device_get_default_pipe(usb_device),
369 config_descriptor->configuration_number);
370 if (opResult != EOK) {
371 usb_log_error("Failed to set hub configuration: %s.\n",
372 str_error(opResult));
373 } else {
374 usb_log_debug("\tUsed configuration %d\n",
375 config_descriptor->configuration_number);
376 }
377 return opResult;
378}
379
380/**
381 * Process hub over current change
382 *
383 * This means either to power off the hub or power it on.
384 * @param hub_dev hub instance
385 * @param status hub status bitmask
386 * @return error code
387 */
388static void usb_hub_over_current(const usb_hub_dev_t *hub_dev,
389 usb_hub_status_t status)
390{
391 if (status & USB_HUB_STATUS_OVER_CURRENT) {
392 /* Hub should remove power from all ports if it detects OC */
393 usb_log_warning("Detected hub over-current condition, "
394 "all ports should be powered off.");
395 return;
396 }
397
398 /* Ports are always powered. */
399 if (!hub_dev->power_switched)
400 return;
401
402 /* Over-current condition is gone, it is safe to turn the ports on. */
403 for (size_t port = 0; port < hub_dev->port_count; ++port) {
404 const int ret = usb_hub_port_set_feature(
405 &hub_dev->ports[port], USB_HUB_FEATURE_PORT_POWER);
406 if (ret != EOK) {
407 usb_log_warning("HUB OVER-CURRENT GONE: Cannot power on"
408 " port %u: %s\n", hub_dev->ports[port].port_number,
409 str_error(ret));
410 } else {
411 if (!hub_dev->per_port_power)
412 return;
413 }
414 }
415
416}
417
418/**
419 * Process hub interrupts.
420 *
421 * The change can be either in the over-current condition or local-power change.
422 * @param hub_dev hub instance
423 */
424static void usb_hub_global_interrupt(const usb_hub_dev_t *hub_dev)
425{
426 assert(hub_dev);
427 assert(hub_dev->usb_device);
428 usb_log_debug("Global interrupt on a hub\n");
429 usb_pipe_t *control_pipe =
430 usb_device_get_default_pipe(hub_dev->usb_device);
431
432 usb_hub_status_t status;
433 size_t rcvd_size;
434 /* NOTE: We can't use standard USB GET_STATUS request, because
435 * hubs reply is 4byte instead of 2 */
436 const int opResult = usb_pipe_control_read(control_pipe,
437 &get_hub_status_request, sizeof(get_hub_status_request),
438 &status, sizeof(usb_hub_status_t), &rcvd_size);
439 if (opResult != EOK) {
440 usb_log_error("Could not get hub status: %s\n",
441 str_error(opResult));
442 return;
443 }
444 if (rcvd_size != sizeof(usb_hub_status_t)) {
445 usb_log_error("Received status has incorrect size\n");
446 return;
447 }
448
449 /* Handle status changes */
450 if (status & USB_HUB_STATUS_C_OVER_CURRENT) {
451 usb_hub_over_current(hub_dev, status);
452 /* Ack change in hub OC flag */
453 const int ret = usb_request_clear_feature(
454 control_pipe, USB_REQUEST_TYPE_CLASS,
455 USB_REQUEST_RECIPIENT_DEVICE,
456 USB_HUB_FEATURE_C_HUB_OVER_CURRENT, 0);
457 if (ret != EOK) {
458 usb_log_error("Failed to clear hub over-current "
459 "change flag: %s.\n", str_error(opResult));
460 }
461 }
462
463 if (status & USB_HUB_STATUS_C_LOCAL_POWER) {
464 /* NOTE: Handling this is more complicated.
465 * If the transition is from bus power to local power, all
466 * is good and we may signal the parent hub that we don't
467 * need the power.
468 * If the transition is from local power to bus power
469 * the hub should turn off all the ports and devices need
470 * to be reinitialized taking into account the limited power
471 * that is now available.
472 * There is no support for power distribution in HelenOS,
473 * (or other OSes/hub devices that I've seen) so this is not
474 * implemented.
475 * Just ACK the change.
476 */
477 const int ret = usb_request_clear_feature(
478 control_pipe, USB_REQUEST_TYPE_CLASS,
479 USB_REQUEST_RECIPIENT_DEVICE,
480 USB_HUB_FEATURE_C_HUB_LOCAL_POWER, 0);
481 if (opResult != EOK) {
482 usb_log_error("Failed to clear hub power change "
483 "flag: %s.\n", str_error(ret));
484 }
485 }
486}
487
488/**
489 * callback called from hub polling fibril when the fibril terminates
490 *
491 * Does not perform cleanup, just marks the hub as not running.
492 * @param device usb device afected
493 * @param was_error indicates that the fibril is stoped due to an error
494 * @param data pointer to usb_hub_dev_t structure
495 */
496static void usb_hub_polling_terminated_callback(usb_device_t *device,
497 bool was_error, void *data)
498{
499 usb_hub_dev_t *hub = data;
500 assert(hub);
501
502 fibril_mutex_lock(&hub->pending_ops_mutex);
503
504 /* The device is dead. However there might be some pending operations
505 * that we need to wait for.
506 * One of them is device adding in progress.
507 * The respective fibril is probably waiting for status change
508 * in port reset (port enable) callback.
509 * Such change would never come (otherwise we would not be here).
510 * Thus, we would flush all pending port resets.
511 */
512 if (hub->pending_ops_count > 0) {
513 for (size_t port = 0; port < hub->port_count; ++port) {
514 usb_hub_port_reset_fail(&hub->ports[port]);
515 }
516 }
517 /* And now wait for them. */
518 while (hub->pending_ops_count > 0) {
519 fibril_condvar_wait(&hub->pending_ops_cv,
520 &hub->pending_ops_mutex);
521 }
522 fibril_mutex_unlock(&hub->pending_ops_mutex);
523 hub->running = false;
524}
525/**
526 * @}
527 */
Note: See TracBrowser for help on using the repository browser.