source: mainline/uspace/drv/bus/usb/usbhub/usbhub.c@ 90994fa

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

usbhub: Implement device gone routine.

Fix error path.

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