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

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

usbhub: Fix device initialization error paths.

  • Property mode set to 100644
File size: 14.9 KB
Line 
1/*
2 * Copyright (c) 2010 Matus Dekanek
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/** @addtogroup drvusbhub
29 * @{
30 */
31/** @file
32 * @brief usb hub main functionality
33 */
34
35#include <ddf/driver.h>
36#include <bool.h>
37#include <errno.h>
38#include <str_error.h>
39#include <inttypes.h>
40
41#include <usb_iface.h>
42#include <usb/ddfiface.h>
43#include <usb/descriptor.h>
44#include <usb/dev/recognise.h>
45#include <usb/dev/request.h>
46#include <usb/classes/hub.h>
47#include <usb/dev/poll.h>
48#include <stdio.h>
49
50#include "usbhub.h"
51#include "usbhub_private.h"
52#include "port_status.h"
53#include <usb/usb.h>
54#include <usb/dev/pipes.h>
55#include <usb/classes/classes.h>
56
57#define HUB_FNC_NAME "hub"
58
59static usb_hub_info_t * usb_hub_info_create(usb_device_t *usb_dev);
60static int usb_hub_process_hub_specific_info(usb_hub_info_t *hub_info);
61static int usb_hub_set_configuration(usb_hub_info_t *hub_info);
62static int usb_process_hub_over_current(usb_hub_info_t *hub_info,
63 usb_hub_status_t status);
64static int usb_process_hub_local_power_change(usb_hub_info_t *hub_info,
65 usb_hub_status_t status);
66static void usb_hub_process_global_interrupt(usb_hub_info_t *hub_info);
67static void usb_hub_polling_terminated_callback(usb_device_t *device,
68 bool was_error, void *data);
69
70
71//*********************************************
72//
73// hub driver code, initialization
74//
75//*********************************************
76
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_add_device(usb_device_t *usb_dev)
86{
87 if (!usb_dev) return EINVAL;
88 usb_hub_info_t *hub_info = usb_hub_info_create(usb_dev);
89
90 //create hc connection
91 usb_log_debug("Initializing USB wire abstraction.\n");
92 int opResult = usb_hc_connection_initialize_from_device(
93 &hub_info->connection, hub_info->usb_device->ddf_dev);
94 if (opResult != EOK) {
95 usb_log_error("Could not initialize connection to device: %s\n",
96 str_error(opResult));
97 free(hub_info);
98 return opResult;
99 }
100
101 //set hub configuration
102 opResult = usb_hub_set_configuration(hub_info);
103 if (opResult != EOK) {
104 usb_log_error("Could not set hub configuration: %s\n",
105 str_error(opResult));
106 free(hub_info);
107 return opResult;
108 }
109
110 //get port count and create attached_devs
111 opResult = usb_hub_process_hub_specific_info(hub_info);
112 if (opResult != EOK) {
113 usb_log_error("Could process hub specific info, %s\n",
114 str_error(opResult));
115 free(hub_info);
116 return opResult;
117 }
118
119 usb_log_debug("Creating DDF function '" HUB_FNC_NAME "'.\n");
120 ddf_fun_t *hub_fun = ddf_fun_create(hub_info->usb_device->ddf_dev,
121 fun_exposed, HUB_FNC_NAME);
122 if (hub_fun == NULL) {
123 usb_log_error("Failed to create hub function.\n");
124 free(hub_info);
125 return ENOMEM;
126 }
127
128 opResult = ddf_fun_bind(hub_fun);
129 if (opResult != EOK) {
130 usb_log_error("Failed to bind hub function: %s.\n",
131 str_error(opResult));
132 free(hub_info);
133 ddf_fun_destroy(hub_fun);
134 return opResult;
135 }
136
137 opResult = usb_device_auto_poll(hub_info->usb_device, 0,
138 hub_port_changes_callback, ((hub_info->port_count + 1) / 8) + 1,
139 usb_hub_polling_terminated_callback, hub_info);
140 if (opResult != EOK) {
141 ddf_fun_destroy(hub_fun);
142 free(hub_info);
143 usb_log_error("Failed to create polling fibril: %s.\n",
144 str_error(opResult));
145 return opResult;
146 }
147 usb_log_info("Controlling hub '%s' (%zu ports).\n",
148 hub_info->usb_device->ddf_dev->name, hub_info->port_count);
149
150 return EOK;
151}
152
153/** Callback for polling hub for changes.
154 *
155 * @param dev Device where the change occured.
156 * @param change_bitmap Bitmap of changed ports.
157 * @param change_bitmap_size Size of the bitmap in bytes.
158 * @param arg Custom argument, points to @c usb_hub_info_t.
159 * @return Whether to continue polling.
160 */
161bool hub_port_changes_callback(usb_device_t *dev,
162 uint8_t *change_bitmap, size_t change_bitmap_size, void *arg) {
163 usb_log_debug("hub_port_changes_callback\n");
164 usb_hub_info_t *hub = (usb_hub_info_t *) arg;
165
166 /* FIXME: check that we received enough bytes. */
167 if (change_bitmap_size == 0) {
168 goto leave;
169 }
170
171 bool change;
172 change = ((uint8_t*) change_bitmap)[0] & 1;
173 if (change) {
174 usb_hub_process_global_interrupt(hub);
175 }
176
177 size_t port;
178 for (port = 1; port < hub->port_count + 1; port++) {
179 bool change = (change_bitmap[port / 8] >> (port % 8)) % 2;
180 if (change) {
181 usb_hub_process_port_interrupt(hub, port);
182 }
183 }
184leave:
185 /* FIXME: proper interval. */
186 async_usleep(1000 * 250);
187
188 return true;
189}
190
191
192//*********************************************
193//
194// support functions
195//
196//*********************************************
197
198/**
199 * create usb_hub_info_t structure
200 *
201 * Does only basic copying of known information into new structure.
202 * @param usb_dev usb device structure
203 * @return basic usb_hub_info_t structure
204 */
205static usb_hub_info_t * usb_hub_info_create(usb_device_t *usb_dev)
206{
207 usb_hub_info_t *result = malloc(sizeof (usb_hub_info_t));
208 if (!result)
209 return NULL;
210
211 result->usb_device = usb_dev;
212 result->status_change_pipe = usb_dev->pipes[0].pipe;
213 result->control_pipe = &usb_dev->ctrl_pipe;
214 result->is_default_address_used = false;
215
216 result->ports = NULL;
217 result->port_count = (size_t) - 1;
218 fibril_mutex_initialize(&result->port_mutex);
219
220 fibril_mutex_initialize(&result->pending_ops_mutex);
221 fibril_condvar_initialize(&result->pending_ops_cv);
222 result->pending_ops_count = 0;
223
224 return result;
225}
226
227/**
228 * Load hub-specific information into hub_info structure and process if needed
229 *
230 * Particularly read port count and initialize structure holding port
231 * information. If there are non-removable devices, start initializing them.
232 * This function is hub-specific and should be run only after the hub is
233 * configured using usb_hub_set_configuration function.
234 * @param hub_info hub representation
235 * @return error code
236 */
237int usb_hub_process_hub_specific_info(usb_hub_info_t *hub_info)
238{
239 // get hub descriptor
240 usb_log_debug("Retrieving descriptor\n");
241 uint8_t serialized_descriptor[USB_HUB_MAX_DESCRIPTOR_SIZE];
242 int opResult;
243
244 size_t received_size;
245 opResult = usb_request_get_descriptor(hub_info->control_pipe,
246 USB_REQUEST_TYPE_CLASS, USB_REQUEST_RECIPIENT_DEVICE,
247 USB_DESCTYPE_HUB, 0, 0, serialized_descriptor,
248 USB_HUB_MAX_DESCRIPTOR_SIZE, &received_size);
249
250 if (opResult != EOK) {
251 usb_log_error("Failed to receive hub descriptor: %s.\n",
252 str_error(opResult));
253 return opResult;
254 }
255 usb_log_debug2("Parsing descriptor\n");
256 usb_hub_descriptor_t descriptor;
257 opResult = usb_deserialize_hub_desriptor(
258 serialized_descriptor, received_size, &descriptor);
259 if (opResult != EOK) {
260 usb_log_error("Could not parse descriptor: %s\n",
261 str_error(opResult));
262 return opResult;
263 }
264 usb_log_debug("Setting port count to %d.\n", descriptor.ports_count);
265 hub_info->port_count = descriptor.ports_count;
266
267 hub_info->ports =
268 malloc(sizeof(usb_hub_port_t) * (hub_info->port_count + 1));
269 if (!hub_info->ports) {
270 return ENOMEM;
271 }
272
273 size_t port;
274 for (port = 0; port < hub_info->port_count + 1; ++port) {
275 usb_hub_port_init(&hub_info->ports[port]);
276 }
277
278 const bool is_power_switched =
279 !(descriptor.hub_characteristics & HUB_CHAR_NO_POWER_SWITCH_FLAG);
280 if (is_power_switched) {
281 usb_log_debug("Hub power switched\n");
282 const bool per_port_power = descriptor.hub_characteristics
283 & HUB_CHAR_POWER_PER_PORT_FLAG;
284
285 for (port = 1; port <= hub_info->port_count; ++port) {
286 usb_log_debug("Powering port %zu.\n", port);
287 opResult = usb_hub_set_port_feature(
288 hub_info->control_pipe,
289 port, USB_HUB_FEATURE_PORT_POWER);
290 if (opResult != EOK) {
291 usb_log_error("Cannot power on port %zu: %s.\n",
292 port, str_error(opResult));
293 } else {
294 if (!per_port_power) {
295 usb_log_debug(
296 "Ganged power switching mode, "
297 "one port is enough.\n");
298 break;
299 }
300 }
301 }
302
303 } else {
304 usb_log_debug("Power not switched, not going to be powered\n");
305 }
306 return EOK;
307}
308
309/**
310 * Set configuration of hub
311 *
312 * Check whether there is at least one configuration and sets the first one.
313 * This function should be run prior to running any hub-specific action.
314 * @param hub_info hub representation
315 * @return error code
316 */
317static int usb_hub_set_configuration(usb_hub_info_t *hub_info) {
318 //device descriptor
319 usb_standard_device_descriptor_t *std_descriptor
320 = &hub_info->usb_device->descriptors.device;
321 usb_log_debug("Hub has %d configurations\n",
322 std_descriptor->configuration_count);
323 if (std_descriptor->configuration_count < 1) {
324 usb_log_error("There are no configurations available\n");
325 return EINVAL;
326 }
327
328 usb_standard_configuration_descriptor_t *config_descriptor
329 = (usb_standard_configuration_descriptor_t *)
330 hub_info->usb_device->descriptors.configuration;
331
332 /* Set configuration. */
333 int opResult = usb_request_set_configuration(
334 &hub_info->usb_device->ctrl_pipe,
335 config_descriptor->configuration_number);
336
337 if (opResult != EOK) {
338 usb_log_error("Failed to set hub configuration: %s.\n",
339 str_error(opResult));
340 return opResult;
341 }
342 usb_log_debug("\tUsed configuration %d\n",
343 config_descriptor->configuration_number);
344
345 return EOK;
346}
347
348//*********************************************
349//
350// change handling functions
351//
352//*********************************************
353
354/**
355 * process hub over current change
356 *
357 * This means either to power off the hub or power it on.
358 * @param hub_info hub instance
359 * @param status hub status bitmask
360 * @return error code
361 */
362static int usb_process_hub_over_current(usb_hub_info_t *hub_info,
363 usb_hub_status_t status) {
364 int opResult;
365 if (usb_hub_is_status(status, USB_HUB_FEATURE_HUB_OVER_CURRENT)) {
366 //poweroff all ports
367 unsigned int port;
368 for (port = 1; port <= hub_info->port_count; ++port) {
369 opResult = usb_hub_clear_port_feature(
370 hub_info->control_pipe, port,
371 USB_HUB_FEATURE_PORT_POWER);
372 if (opResult != EOK) {
373 usb_log_warning(
374 "Cannot power off port %d; %s\n",
375 port, str_error(opResult));
376 }
377 }
378 } else {
379 //power all ports
380 unsigned int port;
381 for (port = 1; port <= hub_info->port_count; ++port) {
382 opResult = usb_hub_set_port_feature(
383 hub_info->control_pipe, port,
384 USB_HUB_FEATURE_PORT_POWER);
385 if (opResult != EOK) {
386 usb_log_warning(
387 "Cannot power off port %d; %s\n",
388 port, str_error(opResult));
389 }
390 }
391 }
392 return opResult;
393}
394
395/**
396 * process hub local power change
397 *
398 * This change is ignored.
399 * @param hub_info hub instance
400 * @param status hub status bitmask
401 * @return error code
402 */
403static int usb_process_hub_local_power_change(usb_hub_info_t *hub_info,
404 usb_hub_status_t status) {
405 int opResult = EOK;
406 opResult = usb_hub_clear_feature(hub_info->control_pipe,
407 USB_HUB_FEATURE_C_HUB_LOCAL_POWER);
408 if (opResult != EOK) {
409 usb_log_error("Cannnot clear hub power change flag: "
410 "%s\n",
411 str_error(opResult));
412 }
413 return opResult;
414}
415
416/**
417 * process hub interrupts
418 *
419 * The change can be either in the over-current condition or
420 * local-power change.
421 * @param hub_info hub instance
422 */
423static void usb_hub_process_global_interrupt(usb_hub_info_t *hub_info) {
424 usb_log_debug("Global interrupt on a hub\n");
425 usb_pipe_t *pipe = hub_info->control_pipe;
426 int opResult;
427
428 usb_port_status_t status;
429 size_t rcvd_size;
430 usb_device_request_setup_packet_t request;
431 //int opResult;
432 usb_hub_set_hub_status_request(&request);
433 //endpoint 0
434
435 opResult = usb_pipe_control_read(
436 pipe,
437 &request, sizeof (usb_device_request_setup_packet_t),
438 &status, 4, &rcvd_size
439 );
440 if (opResult != EOK) {
441 usb_log_error("Could not get hub status: %s\n",
442 str_error(opResult));
443 return;
444 }
445 if (rcvd_size != sizeof (usb_port_status_t)) {
446 usb_log_error("Received status has incorrect size\n");
447 return;
448 }
449 //port reset
450 if (
451 usb_hub_is_status(status, 16 + USB_HUB_FEATURE_C_HUB_OVER_CURRENT)) {
452 usb_process_hub_over_current(hub_info, status);
453 }
454 if (
455 usb_hub_is_status(status, 16 + USB_HUB_FEATURE_C_HUB_LOCAL_POWER)) {
456 usb_process_hub_local_power_change(hub_info, status);
457 }
458}
459
460/**
461 * callback called from hub polling fibril when the fibril terminates
462 *
463 * Should perform a cleanup - deletes hub_info.
464 * @param device usb device afected
465 * @param was_error indicates that the fibril is stoped due to an error
466 * @param data pointer to usb_hub_info_t structure
467 */
468static void usb_hub_polling_terminated_callback(usb_device_t *device,
469 bool was_error, void *data) {
470 usb_hub_info_t * hub = data;
471 assert(hub);
472
473 fibril_mutex_lock(&hub->pending_ops_mutex);
474
475 /* The device is dead. However there might be some pending operations
476 * that we need to wait for.
477 * One of them is device adding in progress.
478 * The respective fibril is probably waiting for status change
479 * in port reset (port enable) callback.
480 * Such change would never come (otherwise we would not be here).
481 * Thus, we would flush all pending port resets.
482 */
483 if (hub->pending_ops_count > 0) {
484 fibril_mutex_lock(&hub->port_mutex);
485 size_t port;
486 for (port = 0; port < hub->port_count; port++) {
487 usb_hub_port_t *the_port = hub->ports + port;
488 fibril_mutex_lock(&the_port->reset_mutex);
489 the_port->reset_completed = true;
490 the_port->reset_okay = false;
491 fibril_condvar_broadcast(&the_port->reset_cv);
492 fibril_mutex_unlock(&the_port->reset_mutex);
493 }
494 fibril_mutex_unlock(&hub->port_mutex);
495 }
496 /* And now wait for them. */
497 while (hub->pending_ops_count > 0) {
498 fibril_condvar_wait(&hub->pending_ops_cv,
499 &hub->pending_ops_mutex);
500 }
501 fibril_mutex_unlock(&hub->pending_ops_mutex);
502
503 usb_device_destroy(hub->usb_device);
504
505 free(hub->ports);
506 free(hub);
507}
508
509
510
511
512/**
513 * @}
514 */
Note: See TracBrowser for help on using the repository browser.