source: mainline/uspace/drv/bus/usb/usbhub/port.c@ 1c1b577

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

usbhub: Remove flawed implementation of dev_remove.

  • Property mode set to 100644
File size: 15.3 KB
RevLine 
[2ad98fd]1/*
2 * Copyright (c) 2011 Vojtech Horky
[3b617579]3 * Copyright (c) 2011 Jan Vesely
[2ad98fd]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 * Hub ports functions.
34 */
35
36#include <bool.h>
[d650494]37#include <devman.h>
[2ad98fd]38#include <errno.h>
39#include <str_error.h>
40#include <inttypes.h>
41#include <fibril_synch.h>
42
43#include <usb/debug.h>
[d650494]44#include <usb/dev/hub.h>
[2ad98fd]45
[a590a23]46#include "port.h"
[2ad98fd]47#include "usbhub.h"
[400f363]48#include "status.h"
[2ad98fd]49
50/** Information for fibril for device discovery. */
51struct add_device_phase1 {
[6626bba9]52 usb_hub_dev_t *hub;
[aefa0d5]53 usb_hub_port_t *port;
[2ad98fd]54 usb_speed_t speed;
55};
56
[a825eeb0]57static int usb_hub_port_device_gone(usb_hub_port_t *port, usb_hub_dev_t *hub);
[442fa6b]58static void usb_hub_port_reset_completed(usb_hub_port_t *port,
59 usb_port_status_t status);
60static int get_port_status(usb_hub_port_t *port, usb_port_status_t *status);
[32ec5671]61static int enable_port_callback(void *arg);
[2ad98fd]62static int add_device_phase1_worker_fibril(void *arg);
[6626bba9]63static int create_add_device_fibril(usb_hub_port_t *port, usb_hub_dev_t *hub,
[2ad98fd]64 usb_speed_t speed);
65
[a825eeb0]66int usb_hub_port_fini(usb_hub_port_t *port, usb_hub_dev_t *hub)
67{
68 assert(port);
69 if (port->attached_device.fun)
70 return usb_hub_port_device_gone(port, hub);
71 return EOK;
72}
73/*----------------------------------------------------------------------------*/
[aff1880]74/**
75 * Clear feature on hub port.
76 *
[a825eeb0]77 * @param port Port structure.
78 * @param feature Feature selector.
[aff1880]79 * @return Operation result
80 */
[442fa6b]81int usb_hub_port_clear_feature(
[c0587d90]82 usb_hub_port_t *port, usb_hub_class_feature_t feature)
[aff1880]83{
[c0587d90]84 assert(port);
[a825eeb0]85 const usb_device_request_setup_packet_t clear_request = {
[aff1880]86 .request_type = USB_HUB_REQ_TYPE_CLEAR_PORT_FEATURE,
87 .request = USB_DEVREQ_CLEAR_FEATURE,
88 .value = feature,
[c0587d90]89 .index = port->port_number,
[aff1880]90 .length = 0,
91 };
[c0587d90]92 return usb_pipe_control_write(port->control_pipe, &clear_request,
[aff1880]93 sizeof(clear_request), NULL, 0);
94}
95/*----------------------------------------------------------------------------*/
96/**
[a825eeb0]97 * Set feature on hub port.
[aff1880]98 *
[a825eeb0]99 * @param port Port structure.
100 * @param feature Feature selector.
[aff1880]101 * @return Operation result
102 */
[442fa6b]103int usb_hub_port_set_feature(
[c0587d90]104 usb_hub_port_t *port, usb_hub_class_feature_t feature)
[aff1880]105{
[c0587d90]106 assert(port);
[a825eeb0]107 const usb_device_request_setup_packet_t clear_request = {
[aff1880]108 .request_type = USB_HUB_REQ_TYPE_SET_PORT_FEATURE,
109 .request = USB_DEVREQ_SET_FEATURE,
[c0587d90]110 .index = port->port_number,
[aff1880]111 .value = feature,
112 .length = 0,
113 };
[c0587d90]114 return usb_pipe_control_write(port->control_pipe, &clear_request,
[aff1880]115 sizeof(clear_request), NULL, 0);
116}
[442fa6b]117/*----------------------------------------------------------------------------*/
[a825eeb0]118/**
119 * Mark reset process as failed due to external reasons
120 *
121 * @param port Port structure
122 */
[442fa6b]123void usb_hub_port_reset_fail(usb_hub_port_t *port)
124{
125 assert(port);
126 fibril_mutex_lock(&port->mutex);
127 port->reset_completed = true;
128 port->reset_okay = false;
129 fibril_condvar_broadcast(&port->reset_cv);
130 fibril_mutex_unlock(&port->mutex);
131}
132/*----------------------------------------------------------------------------*/
[2ad98fd]133/**
[a825eeb0]134 * Process interrupts on given port
[2ad98fd]135 *
136 * Accepts connection, over current and port reset change.
[a825eeb0]137 * @param port port structure
[2ad98fd]138 * @param hub hub representation
139 */
[6626bba9]140void usb_hub_port_process_interrupt(usb_hub_port_t *port, usb_hub_dev_t *hub)
[983e135]141{
[0212751]142 assert(port);
143 assert(hub);
144 usb_log_debug("Interrupt at port %zu\n", port->port_number);
[2ad98fd]145
146 usb_port_status_t status;
[0212751]147 const int opResult = get_port_status(port, &status);
[2ad98fd]148 if (opResult != EOK) {
149 usb_log_error("Failed to get port %zu status: %s.\n",
[0212751]150 port->port_number, str_error(opResult));
[2ad98fd]151 return;
152 }
[d6e2938]153
154 /* Connection change */
155 if (status & USB_HUB_PORT_C_STATUS_CONNECTION) {
[0212751]156 const bool connected =
[d6e2938]157 (status & USB_HUB_PORT_STATUS_CONNECTION) != 0;
158 usb_log_debug("Connection change on port %zu: device %s.\n",
[0212751]159 port->port_number, connected ? "attached" : "removed");
[d650494]160
[d6e2938]161 /* ACK the change */
[0212751]162 const int opResult = usb_hub_port_clear_feature(port,
163 USB_HUB_FEATURE_C_PORT_CONNECTION);
[d6e2938]164 if (opResult != EOK) {
[0212751]165 usb_log_warning("Failed to clear port-change-connection"
166 " flag: %s.\n", str_error(opResult));
[d6e2938]167 }
[2ad98fd]168
[0212751]169 if (connected) {
[aefa0d5]170 const int opResult = create_add_device_fibril(port, hub,
171 usb_port_speed(status));
[2ad98fd]172 if (opResult != EOK) {
173 usb_log_error(
174 "Cannot handle change on port %zu: %s.\n",
[0212751]175 port->port_number, str_error(opResult));
[2ad98fd]176 }
177 } else {
[d650494]178 /* If enabled change was reported leave the removal
179 * to that handler, it shall ACK the change too. */
180 if (!(status & USB_HUB_PORT_C_STATUS_ENABLED)) {
[a825eeb0]181 usb_hub_port_device_gone(port, hub);
[d650494]182 }
[2ad98fd]183 }
184 }
[d6e2938]185
186 /* Enable change, ports are automatically disabled on errors. */
187 if (status & USB_HUB_PORT_C_STATUS_ENABLED) {
[d650494]188 usb_log_info("Port %zu, disabled because of errors.\n",
189 port->port_number);
[a825eeb0]190 usb_hub_port_device_gone(port, hub);
[0212751]191 const int rc = usb_hub_port_clear_feature(port,
192 USB_HUB_FEATURE_C_PORT_ENABLE);
193 if (rc != EOK) {
194 usb_log_error(
195 "Failed to clear port %zu enable change feature: "
196 "%s.\n", port->port_number, str_error(rc));
197 }
[d6e2938]198
[2ad98fd]199 }
[d6e2938]200
201 /* Suspend change */
202 if (status & USB_HUB_PORT_C_STATUS_SUSPEND) {
203 usb_log_error("Port %zu went to suspend state, this should"
[0212751]204 "NOT happen as we do not support suspend state!",
205 port->port_number);
206 const int rc = usb_hub_port_clear_feature(port,
207 USB_HUB_FEATURE_C_PORT_SUSPEND);
208 if (rc != EOK) {
209 usb_log_error(
210 "Failed to clear port %zu suspend change feature: "
211 "%s.\n", port->port_number, str_error(rc));
212 }
[2ad98fd]213 }
[d6e2938]214
215 /* Over current */
216 if (status & USB_HUB_PORT_C_STATUS_OC) {
217 /* According to the USB specs:
218 * 11.13.5 Over-current Reporting and Recovery
219 * Hub device is responsible for putting port in power off
220 * mode. USB system software is responsible for powering port
[0212751]221 * back on when the over-current condition is gone */
222 const int rc = usb_hub_port_clear_feature(port,
223 USB_HUB_FEATURE_C_PORT_OVER_CURRENT);
224 if (rc != EOK) {
225 usb_log_error(
226 "Failed to clear port %zu OC change feature: %s.\n",
227 port->port_number, str_error(rc));
228 }
[d6e2938]229 if (!(status & ~USB_HUB_PORT_STATUS_OC)) {
[0212751]230 const int rc = usb_hub_port_set_feature(
231 port, USB_HUB_FEATURE_PORT_POWER);
232 if (rc != EOK) {
233 usb_log_error(
[e231d26]234 "Failed to set port %zu power after OC:"
[0212751]235 " %s.\n", port->port_number, str_error(rc));
236 }
[d0c060b]237 }
238 }
[d6e2938]239
240 /* Port reset, set on port reset complete. */
241 if (status & USB_HUB_PORT_C_STATUS_RESET) {
[0212751]242 usb_hub_port_reset_completed(port, status);
[192ba25]243 }
[d6e2938]244
[0212751]245 usb_log_debug("Port %zu status 0x%08" PRIx32 "\n",
246 port->port_number, status);
[2ad98fd]247}
[a825eeb0]248/*----------------------------------------------------------------------------*/
[2ad98fd]249/**
250 * routine called when a device on port has been removed
251 *
252 * If the device on port had default address, it releases default address.
253 * Otherwise does not do anything, because DDF does not allow to remove device
254 * from it`s device tree.
[a825eeb0]255 * @param port port structure
[2ad98fd]256 * @param hub hub representation
257 */
[a825eeb0]258int usb_hub_port_device_gone(usb_hub_port_t *port, usb_hub_dev_t *hub)
[d6e2938]259{
[442fa6b]260 assert(port);
[d650494]261 assert(hub);
[a825eeb0]262 if (port->attached_device.address < 0) {
[d650494]263 usb_log_warning(
264 "Device on port %zu removed before being registered.\n",
265 port->port_number);
[46e078a]266
267 /*
268 * Device was removed before port reset completed.
269 * We will announce a failed port reset to unblock the
270 * port reset callback from new device wrapper.
271 */
[442fa6b]272 usb_hub_port_reset_fail(port);
[a825eeb0]273 return EOK;
[2ad98fd]274 }
275
[a825eeb0]276 fibril_mutex_lock(&port->mutex);
277 assert(port->attached_device.fun);
278 usb_log_debug("Removing device on port %zu.\n", port->port_number);
279 int ret = ddf_fun_unbind(port->attached_device.fun);
280 if (ret != EOK) {
281 usb_log_error("Failed to unbind child function on port"
282 " %zu: %s.\n", port->port_number, str_error(ret));
283 fibril_mutex_unlock(&port->mutex);
284 return ret;
285 }
286
287 ddf_fun_destroy(port->attached_device.fun);
288 port->attached_device.fun = NULL;
289
[344a0ac]290 ret = usb_hc_connection_open(&hub->connection);
291 if (ret == EOK) {
292 ret = usb_hc_unregister_device(&hub->connection,
293 port->attached_device.address);
294 if (ret != EOK) {
295 usb_log_warning("Failed to unregister address of the "
296 "removed device: %s.\n", str_error(ret));
297 }
298 ret = usb_hc_connection_close(&hub->connection);
299 if (ret != EOK) {
300 usb_log_warning("Failed to close hc connection %s.\n",
301 str_error(ret));
302 }
303
304 } else {
305 usb_log_warning("Failed to open hc connection %s.\n",
306 str_error(ret));
[a825eeb0]307 }
[344a0ac]308
[a825eeb0]309 port->attached_device.address = -1;
310 fibril_mutex_unlock(&port->mutex);
311 usb_log_info("Removed device on port %zu.\n", port->port_number);
312 return EOK;
313}
314/*----------------------------------------------------------------------------*/
[2ad98fd]315/**
316 * Process port reset change
317 *
[d650494]318 * After this change port should be enabled, unless some problem occurred.
[2ad98fd]319 * This functions triggers second phase of enabling new device.
[a825eeb0]320 * @param port Port structure
321 * @param status Port status mask
[2ad98fd]322 */
[a825eeb0]323void usb_hub_port_reset_completed(usb_hub_port_t *port,
[442fa6b]324 usb_port_status_t status)
[d6e2938]325{
[442fa6b]326 assert(port);
327 fibril_mutex_lock(&port->mutex);
[aff1880]328 /* Finalize device adding. */
[442fa6b]329 port->reset_completed = true;
330 port->reset_okay = (status & USB_HUB_PORT_STATUS_ENABLED) != 0;
[aff1880]331
[442fa6b]332 if (port->reset_okay) {
333 usb_log_debug("Port %zu reset complete.\n", port->port_number);
[2ad98fd]334 } else {
335 usb_log_warning(
[442fa6b]336 "Port %zu reset complete but port not enabled.\n",
337 port->port_number);
[2ad98fd]338 }
[442fa6b]339 fibril_condvar_broadcast(&port->reset_cv);
340 fibril_mutex_unlock(&port->mutex);
[aff1880]341
[d0c060b]342 /* Clear the port reset change. */
[442fa6b]343 int rc = usb_hub_port_clear_feature(port, USB_HUB_FEATURE_C_PORT_RESET);
[d0c060b]344 if (rc != EOK) {
[442fa6b]345 usb_log_error(
[e231d26]346 "Failed to clear port %zu reset change feature: %s.\n",
[442fa6b]347 port->port_number, str_error(rc));
[d0c060b]348 }
[2ad98fd]349}
[aff1880]350/*----------------------------------------------------------------------------*/
[2ad98fd]351/** Retrieve port status.
352 *
[a825eeb0]353 * @param[in] port Port structure
[2ad98fd]354 * @param[out] status Where to store the port status.
355 * @return Error code.
356 */
[442fa6b]357static int get_port_status(usb_hub_port_t *port, usb_port_status_t *status)
[b3433a2]358{
[442fa6b]359 assert(port);
[d6e2938]360 /* USB hub specific GET_PORT_STATUS request. See USB Spec 11.16.2.6
361 * Generic GET_STATUS request cannot be used because of the difference
362 * in status data size (2B vs. 4B)*/
[b3433a2]363 const usb_device_request_setup_packet_t request = {
364 .request_type = USB_HUB_REQ_TYPE_GET_PORT_STATUS,
365 .request = USB_HUB_REQUEST_GET_STATUS,
366 .value = 0,
[442fa6b]367 .index = port->port_number,
[b3433a2]368 .length = sizeof(usb_port_status_t),
369 };
[442fa6b]370 size_t recv_size;
371 usb_port_status_t status_tmp;
[b3433a2]372
[442fa6b]373 const int rc = usb_pipe_control_read(port->control_pipe,
[b3433a2]374 &request, sizeof(usb_device_request_setup_packet_t),
375 &status_tmp, sizeof(status_tmp), &recv_size);
[2ad98fd]376 if (rc != EOK) {
377 return rc;
378 }
379
380 if (recv_size != sizeof (status_tmp)) {
381 return ELIMIT;
382 }
383
384 if (status != NULL) {
385 *status = status_tmp;
386 }
387
388 return EOK;
389}
[d6e2938]390/*----------------------------------------------------------------------------*/
[2ad98fd]391/** Callback for enabling a specific port.
392 *
393 * We wait on a CV until port is reseted.
394 * That is announced via change on interrupt pipe.
395 *
396 * @param port_no Port number (starting at 1).
[6626bba9]397 * @param arg Custom argument, points to @c usb_hub_dev_t.
[2ad98fd]398 * @return Error code.
399 */
[32ec5671]400static int enable_port_callback(void *arg)
[d6e2938]401{
[c0587d90]402 usb_hub_port_t *port = arg;
[cae002c]403 assert(port);
[c0587d90]404 const int rc =
[442fa6b]405 usb_hub_port_set_feature(port, USB_HUB_FEATURE_PORT_RESET);
[2ad98fd]406 if (rc != EOK) {
407 usb_log_warning("Port reset failed: %s.\n", str_error(rc));
408 return rc;
409 }
410
411 /*
412 * Wait until reset completes.
413 */
[442fa6b]414 fibril_mutex_lock(&port->mutex);
[c0587d90]415 while (!port->reset_completed) {
[442fa6b]416 fibril_condvar_wait(&port->reset_cv, &port->mutex);
[2ad98fd]417 }
[442fa6b]418 fibril_mutex_unlock(&port->mutex);
[2ad98fd]419
[a825eeb0]420 return port->reset_okay ? EOK : ESTALL;
[2ad98fd]421}
[a825eeb0]422/*----------------------------------------------------------------------------*/
[2ad98fd]423/** Fibril for adding a new device.
424 *
425 * Separate fibril is needed because the port reset completion is announced
426 * via interrupt pipe and thus we cannot block here.
427 *
428 * @param arg Pointer to struct add_device_phase1.
429 * @return 0 Always.
430 */
[a825eeb0]431int add_device_phase1_worker_fibril(void *arg)
[d6e2938]432{
433 struct add_device_phase1 *data = arg;
434 assert(data);
[2ad98fd]435
436 usb_address_t new_address;
[90994fa]437 ddf_fun_t *child_fun;
[2ad98fd]438
[d6e2938]439 const int rc = usb_hc_new_device_wrapper(data->hub->usb_device->ddf_dev,
[32ec5671]440 &data->hub->connection, data->speed, enable_port_callback,
[013517b]441 data->port, &new_address, NULL, NULL, &child_fun);
[2ad98fd]442
[cae002c]443 if (rc == EOK) {
444 fibril_mutex_lock(&data->port->mutex);
445 data->port->attached_device.fun = child_fun;
446 data->port->attached_device.address = new_address;
447 fibril_mutex_unlock(&data->port->mutex);
448
449 usb_log_info("Detected new device on `%s' (port %zu), "
450 "address %d (handle %" PRIun ").\n",
451 data->hub->usb_device->ddf_dev->name,
452 data->port->port_number, new_address, child_fun->handle);
453 } else {
[2ad98fd]454 usb_log_error("Failed registering device on port %zu: %s.\n",
[aefa0d5]455 data->port->port_number, str_error(rc));
[2ad98fd]456 }
457
458
[3fb5a3e]459 fibril_mutex_lock(&data->hub->pending_ops_mutex);
460 assert(data->hub->pending_ops_count > 0);
[aefa0d5]461 --data->hub->pending_ops_count;
[3fb5a3e]462 fibril_condvar_signal(&data->hub->pending_ops_cv);
463 fibril_mutex_unlock(&data->hub->pending_ops_mutex);
464
[aefa0d5]465 free(arg);
[3fb5a3e]466
[a825eeb0]467 return rc;
[2ad98fd]468}
[a825eeb0]469/*----------------------------------------------------------------------------*/
[2ad98fd]470/** Start device adding when connection change is detected.
471 *
472 * This fires a new fibril to complete the device addition.
473 *
474 * @param hub Hub where the change occured.
475 * @param port Port index (starting at 1).
476 * @param speed Speed of the device.
477 * @return Error code.
478 */
[6626bba9]479static int create_add_device_fibril(usb_hub_port_t *port, usb_hub_dev_t *hub,
[d6e2938]480 usb_speed_t speed)
481{
[aefa0d5]482 assert(hub);
483 assert(port);
[2ad98fd]484 struct add_device_phase1 *data
[6f05705]485 = malloc(sizeof(struct add_device_phase1));
[2ad98fd]486 if (data == NULL) {
487 return ENOMEM;
488 }
489 data->hub = hub;
490 data->port = port;
491 data->speed = speed;
492
[aefa0d5]493 fibril_mutex_lock(&port->mutex);
494 port->reset_completed = false;
495 fibril_mutex_unlock(&port->mutex);
[2ad98fd]496
497 fid_t fibril = fibril_create(add_device_phase1_worker_fibril, data);
498 if (fibril == 0) {
499 free(data);
500 return ENOMEM;
501 }
[3fb5a3e]502 fibril_mutex_lock(&hub->pending_ops_mutex);
[aefa0d5]503 ++hub->pending_ops_count;
[3fb5a3e]504 fibril_mutex_unlock(&hub->pending_ops_mutex);
[2ad98fd]505 fibril_add_ready(fibril);
506
507 return EOK;
508}
509
510/**
511 * @}
512 */
Note: See TracBrowser for help on using the repository browser.