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

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

Mainline changes.

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