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

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

usbhub: Create GET_PORT_STATUS request inline isntead of creation function.

  • Property mode set to 100644
File size: 13.4 KB
RevLine 
[2ad98fd]1/*
2 * Copyright (c) 2011 Vojtech Horky
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 drvusbhub
30 * @{
31 */
32/** @file
33 * Hub ports functions.
34 */
35
36#include <bool.h>
37#include <errno.h>
38#include <str_error.h>
39#include <inttypes.h>
40#include <fibril_synch.h>
41
42#include <usb/debug.h>
43
44#include "ports.h"
45#include "usbhub.h"
[193da9d6]46#include "utils.h"
[2ad98fd]47#include "port_status.h"
48
49/** Information for fibril for device discovery. */
50struct add_device_phase1 {
51 usb_hub_info_t *hub;
52 size_t port;
53 usb_speed_t speed;
54};
55
[426d31a]56/**
57 * count of port status changes that are not explicitly handled by
58 * any function here and must be cleared by hand
59 */
60static const unsigned int non_handled_changes_count = 2;
61
62/**
63 * port status changes that are not explicitly handled by
64 * any function here and must be cleared by hand
65 */
[5c1a65e]66static const int non_handled_changes[] = {
[426d31a]67 USB_HUB_FEATURE_C_PORT_ENABLE,
68 USB_HUB_FEATURE_C_PORT_SUSPEND
69};
70
[2ad98fd]71static void usb_hub_removed_device(
[5c1a65e]72 usb_hub_info_t *hub, uint16_t port);
[2ad98fd]73
[5c1a65e]74static void usb_hub_port_reset_completed(usb_hub_info_t *hub,
75 uint16_t port, uint32_t status);
[2ad98fd]76
[5c1a65e]77static void usb_hub_port_over_current(usb_hub_info_t *hub,
78 uint16_t port, uint32_t status);
[2ad98fd]79
80static int get_port_status(usb_pipe_t *ctrl_pipe, size_t port,
81 usb_port_status_t *status);
82
83static int enable_port_callback(int port_no, void *arg);
84
85static int add_device_phase1_worker_fibril(void *arg);
86
87static int create_add_device_fibril(usb_hub_info_t *hub, size_t port,
88 usb_speed_t speed);
89
90/**
91 * Process interrupts on given hub port
92 *
93 * Accepts connection, over current and port reset change.
94 * @param hub hub representation
95 * @param port port number, starting from 1
96 */
[983e135]97void usb_hub_process_port_interrupt(usb_hub_info_t *hub, uint16_t port)
98{
[5c1a65e]99 usb_log_debug("Interrupt at port %zu\n", (size_t) port);
[2ad98fd]100
101 usb_port_status_t status;
[b3433a2]102 const int opResult =
103 get_port_status(&hub->usb_device->ctrl_pipe, port, &status);
[2ad98fd]104 if (opResult != EOK) {
105 usb_log_error("Failed to get port %zu status: %s.\n",
[4125b7d]106 (size_t) port, str_error(opResult));
[2ad98fd]107 return;
108 }
109 //connection change
110 if (usb_port_is_status(status, USB_HUB_FEATURE_C_PORT_CONNECTION)) {
111 bool device_connected = usb_port_is_status(status,
112 USB_HUB_FEATURE_PORT_CONNECTION);
[4125b7d]113 usb_log_debug("Connection change on port %zu: %s.\n",
114 (size_t) port,
[2ad98fd]115 device_connected ? "device attached" : "device removed");
116
117 if (device_connected) {
[b3433a2]118 const int opResult = create_add_device_fibril(hub, port,
[2ad98fd]119 usb_port_speed(status));
120 if (opResult != EOK) {
121 usb_log_error(
122 "Cannot handle change on port %zu: %s.\n",
[4125b7d]123 (size_t) port, str_error(opResult));
[2ad98fd]124 }
125 } else {
126 usb_hub_removed_device(hub, port);
127 }
128 }
129 //over current
130 if (usb_port_is_status(status, USB_HUB_FEATURE_C_PORT_OVER_CURRENT)) {
131 //check if it was not auto-resolved
[5c1a65e]132 usb_log_debug("Overcurrent change on port\n");
[2ad98fd]133 usb_hub_port_over_current(hub, port, status);
134 }
135 //port reset
136 if (usb_port_is_status(status, USB_HUB_FEATURE_C_PORT_RESET)) {
137 usb_hub_port_reset_completed(hub, port, status);
138 }
[58c0917]139 usb_log_debug("Port %d status 0x%08" PRIx32 "\n", (int) port, status);
[2ad98fd]140
141 usb_port_status_set_bit(
[5c1a65e]142 &status, USB_HUB_FEATURE_C_PORT_CONNECTION, false);
[2ad98fd]143 usb_port_status_set_bit(
[5c1a65e]144 &status, USB_HUB_FEATURE_C_PORT_RESET, false);
[2ad98fd]145 usb_port_status_set_bit(
[5c1a65e]146 &status, USB_HUB_FEATURE_C_PORT_OVER_CURRENT, false);
147
[426d31a]148 //clearing not yet handled changes
149 unsigned int feature_idx;
[5c1a65e]150 for (feature_idx = 0;
151 feature_idx < non_handled_changes_count;
152 ++feature_idx) {
[426d31a]153 unsigned int bit_idx = non_handled_changes[feature_idx];
[5c1a65e]154 if (status & (1 << bit_idx)) {
[d0c060b]155 usb_log_info(
[5c1a65e]156 "There was not yet handled change on port %d: %d"
[e78660f]157 ";clearing it\n",
[5c1a65e]158 port, bit_idx);
[d0c060b]159 int opResult = usb_hub_clear_port_feature(
160 hub->control_pipe,
[e78660f]161 port, bit_idx);
[d0c060b]162 if (opResult != EOK) {
163 usb_log_warning(
[5c1a65e]164 "Could not clear port flag %d: %s\n",
165 bit_idx, str_error(opResult)
[d0c060b]166 );
167 }
168 usb_port_status_set_bit(
[5c1a65e]169 &status, bit_idx, false);
[d0c060b]170 }
171 }
[5c1a65e]172 if (status >> 16) {
173 usb_log_info("There is still some unhandled change %X\n",
[192ba25]174 status);
175 }
[2ad98fd]176}
177
178/**
179 * routine called when a device on port has been removed
180 *
181 * If the device on port had default address, it releases default address.
182 * Otherwise does not do anything, because DDF does not allow to remove device
183 * from it`s device tree.
184 * @param hub hub representation
185 * @param port port number, starting from 1
186 */
187static void usb_hub_removed_device(
[5c1a65e]188 usb_hub_info_t *hub, uint16_t port) {
[2ad98fd]189
190 int opResult = usb_hub_clear_port_feature(hub->control_pipe,
[5c1a65e]191 port, USB_HUB_FEATURE_C_PORT_CONNECTION);
[2ad98fd]192 if (opResult != EOK) {
[5c1a65e]193 usb_log_warning("Could not clear port-change-connection flag\n");
[2ad98fd]194 }
195 /** \TODO remove device from device manager - not yet implemented in
196 * devide manager
197 */
198
199 //close address
[af6136d]200
201 usb_hub_port_t *the_port = hub->ports + port;
202
203 fibril_mutex_lock(&hub->port_mutex);
204
205 if (the_port->attached_device.address >= 0) {
206 usb_log_warning("Device unplug on `%s' (port %zu): " \
207 "not implemented.\n", hub->usb_device->ddf_dev->name,
208 (size_t) port);
209 the_port->attached_device.address = -1;
210 the_port->attached_device.handle = 0;
[2ad98fd]211 } else {
[46e078a]212 usb_log_warning("Device removed before being registered.\n");
213
214 /*
215 * Device was removed before port reset completed.
216 * We will announce a failed port reset to unblock the
217 * port reset callback from new device wrapper.
218 */
219 fibril_mutex_lock(&the_port->reset_mutex);
220 the_port->reset_completed = true;
221 the_port->reset_okay = false;
222 fibril_condvar_broadcast(&the_port->reset_cv);
223 fibril_mutex_unlock(&the_port->reset_mutex);
[2ad98fd]224 }
[af6136d]225
226 fibril_mutex_unlock(&hub->port_mutex);
[2ad98fd]227}
228
229/**
230 * Process port reset change
231 *
232 * After this change port should be enabled, unless some problem occured.
233 * This functions triggers second phase of enabling new device.
234 * @param hub
235 * @param port
236 * @param status
237 */
[5c1a65e]238static void usb_hub_port_reset_completed(usb_hub_info_t *hub,
239 uint16_t port, uint32_t status) {
[4125b7d]240 usb_log_debug("Port %zu reset complete.\n", (size_t) port);
[2ad98fd]241 if (usb_port_is_status(status, USB_HUB_FEATURE_PORT_ENABLE)) {
242 /* Finalize device adding. */
243 usb_hub_port_t *the_port = hub->ports + port;
244 fibril_mutex_lock(&the_port->reset_mutex);
245 the_port->reset_completed = true;
[46e078a]246 the_port->reset_okay = true;
[2ad98fd]247 fibril_condvar_broadcast(&the_port->reset_cv);
248 fibril_mutex_unlock(&the_port->reset_mutex);
249 } else {
250 usb_log_warning(
251 "Port %zu reset complete but port not enabled.\n",
[4125b7d]252 (size_t) port);
[2ad98fd]253 }
[d0c060b]254 /* Clear the port reset change. */
255 int rc = usb_hub_clear_port_feature(hub->control_pipe,
256 port, USB_HUB_FEATURE_C_PORT_RESET);
257 if (rc != EOK) {
258 usb_log_error("Failed to clear port %d reset feature: %s.\n",
259 port, str_error(rc));
260 }
[2ad98fd]261}
262
263/**
264 * Process over current condition on port.
265 *
266 * Turn off the power on the port.
267 *
268 * @param hub hub representation
269 * @param port port number, starting from 1
270 */
[5c1a65e]271static void usb_hub_port_over_current(usb_hub_info_t *hub,
272 uint16_t port, uint32_t status) {
[2ad98fd]273 int opResult;
[5c1a65e]274 if (usb_port_is_status(status, USB_HUB_FEATURE_PORT_OVER_CURRENT)) {
[2ad98fd]275 opResult = usb_hub_clear_port_feature(hub->control_pipe,
[5c1a65e]276 port, USB_HUB_FEATURE_PORT_POWER);
[2ad98fd]277 if (opResult != EOK) {
[5c1a65e]278 usb_log_error("Cannot power off port %d; %s\n",
279 port, str_error(opResult));
[2ad98fd]280 }
[5c1a65e]281 } else {
[2ad98fd]282 opResult = usb_hub_set_port_feature(hub->control_pipe,
[5c1a65e]283 port, USB_HUB_FEATURE_PORT_POWER);
[2ad98fd]284 if (opResult != EOK) {
[5c1a65e]285 usb_log_error("Cannot power on port %d; %s\n",
286 port, str_error(opResult));
[2ad98fd]287 }
288 }
289}
290
291/** Retrieve port status.
292 *
293 * @param[in] ctrl_pipe Control pipe to use.
294 * @param[in] port Port number (starting at 1).
295 * @param[out] status Where to store the port status.
296 * @return Error code.
297 */
298static int get_port_status(usb_pipe_t *ctrl_pipe, size_t port,
[b3433a2]299 usb_port_status_t *status)
300{
[2ad98fd]301 size_t recv_size;
302 usb_port_status_t status_tmp;
[b3433a2]303 /* USB hub specific GET_PORT_STATUS request. See USB Spec 11.16.2.6 */
304 const usb_device_request_setup_packet_t request = {
305 .request_type = USB_HUB_REQ_TYPE_GET_PORT_STATUS,
306 .request = USB_HUB_REQUEST_GET_STATUS,
307 .value = 0,
308 .index = port,
309 .length = sizeof(usb_port_status_t),
310 };
311
312 const int rc = usb_pipe_control_read(ctrl_pipe,
313 &request, sizeof(usb_device_request_setup_packet_t),
314 &status_tmp, sizeof(status_tmp), &recv_size);
[2ad98fd]315 if (rc != EOK) {
316 return rc;
317 }
318
319 if (recv_size != sizeof (status_tmp)) {
320 return ELIMIT;
321 }
322
323 if (status != NULL) {
324 *status = status_tmp;
325 }
326
327 return EOK;
328}
329
330/** Callback for enabling a specific port.
331 *
332 * We wait on a CV until port is reseted.
333 * That is announced via change on interrupt pipe.
334 *
335 * @param port_no Port number (starting at 1).
336 * @param arg Custom argument, points to @c usb_hub_info_t.
337 * @return Error code.
338 */
[5c1a65e]339static int enable_port_callback(int port_no, void *arg) {
[2ad98fd]340 usb_hub_info_t *hub = arg;
341 int rc;
342 usb_device_request_setup_packet_t request;
343 usb_hub_port_t *my_port = hub->ports + port_no;
344
345 usb_hub_set_reset_port_request(&request, port_no);
346 rc = usb_pipe_control_write(hub->control_pipe,
[5c1a65e]347 &request, sizeof (request), NULL, 0);
[2ad98fd]348 if (rc != EOK) {
349 usb_log_warning("Port reset failed: %s.\n", str_error(rc));
350 return rc;
351 }
352
353 /*
354 * Wait until reset completes.
355 */
356 fibril_mutex_lock(&my_port->reset_mutex);
357 while (!my_port->reset_completed) {
358 fibril_condvar_wait(&my_port->reset_cv, &my_port->reset_mutex);
359 }
360 fibril_mutex_unlock(&my_port->reset_mutex);
361
[46e078a]362 if (my_port->reset_okay) {
363 return EOK;
364 } else {
365 return ESTALL;
366 }
[2ad98fd]367}
368
369/** Fibril for adding a new device.
370 *
371 * Separate fibril is needed because the port reset completion is announced
372 * via interrupt pipe and thus we cannot block here.
373 *
374 * @param arg Pointer to struct add_device_phase1.
375 * @return 0 Always.
376 */
[5c1a65e]377static int add_device_phase1_worker_fibril(void *arg) {
[2ad98fd]378 struct add_device_phase1 *data
379 = (struct add_device_phase1 *) arg;
380
381 usb_address_t new_address;
382 devman_handle_t child_handle;
383
384 int rc = usb_hc_new_device_wrapper(data->hub->usb_device->ddf_dev,
385 &data->hub->connection, data->speed,
386 enable_port_callback, (int) data->port, data->hub,
387 &new_address, &child_handle,
388 NULL, NULL, NULL);
389
390 if (rc != EOK) {
391 usb_log_error("Failed registering device on port %zu: %s.\n",
392 data->port, str_error(rc));
393 goto leave;
394 }
395
[af6136d]396 fibril_mutex_lock(&data->hub->port_mutex);
[2ad98fd]397 data->hub->ports[data->port].attached_device.handle = child_handle;
398 data->hub->ports[data->port].attached_device.address = new_address;
[af6136d]399 fibril_mutex_unlock(&data->hub->port_mutex);
[2ad98fd]400
401 usb_log_info("Detected new device on `%s' (port %zu), "
402 "address %d (handle %" PRIun ").\n",
403 data->hub->usb_device->ddf_dev->name, data->port,
404 new_address, child_handle);
405
406leave:
407 free(arg);
408
[3fb5a3e]409 fibril_mutex_lock(&data->hub->pending_ops_mutex);
410 assert(data->hub->pending_ops_count > 0);
411 data->hub->pending_ops_count--;
412 fibril_condvar_signal(&data->hub->pending_ops_cv);
413 fibril_mutex_unlock(&data->hub->pending_ops_mutex);
414
415
[2ad98fd]416 return EOK;
417}
418
419/** Start device adding when connection change is detected.
420 *
421 * This fires a new fibril to complete the device addition.
422 *
423 * @param hub Hub where the change occured.
424 * @param port Port index (starting at 1).
425 * @param speed Speed of the device.
426 * @return Error code.
427 */
428static int create_add_device_fibril(usb_hub_info_t *hub, size_t port,
[5c1a65e]429 usb_speed_t speed) {
[2ad98fd]430 struct add_device_phase1 *data
[5c1a65e]431 = malloc(sizeof (struct add_device_phase1));
[2ad98fd]432 if (data == NULL) {
433 return ENOMEM;
434 }
435 data->hub = hub;
436 data->port = port;
437 data->speed = speed;
438
439 usb_hub_port_t *the_port = hub->ports + port;
440
441 fibril_mutex_lock(&the_port->reset_mutex);
442 the_port->reset_completed = false;
443 fibril_mutex_unlock(&the_port->reset_mutex);
444
445 int rc = usb_hub_clear_port_feature(hub->control_pipe, port,
446 USB_HUB_FEATURE_C_PORT_CONNECTION);
447 if (rc != EOK) {
448 free(data);
449 usb_log_warning("Failed to clear port change flag: %s.\n",
450 str_error(rc));
451 return rc;
452 }
453
454 fid_t fibril = fibril_create(add_device_phase1_worker_fibril, data);
455 if (fibril == 0) {
456 free(data);
457 return ENOMEM;
458 }
[3fb5a3e]459 fibril_mutex_lock(&hub->pending_ops_mutex);
460 hub->pending_ops_count++;
461 fibril_mutex_unlock(&hub->pending_ops_mutex);
[2ad98fd]462 fibril_add_ready(fibril);
463
464 return EOK;
465}
466
467/**
468 * @}
469 */
Note: See TracBrowser for help on using the repository browser.