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

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 7dddd7b was 5e2b1ae6, checked in by Ondřej Hlavatý <aearsis@…>, 8 years ago

usbhost: reserve default speed in library

  • Property mode set to 100644
File size: 14.8 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 */
[58563585]29
[2ad98fd]30/** @addtogroup drvusbhub
31 * @{
32 */
33/** @file
34 * Hub ports functions.
35 */
36
[3e6a98c5]37#include <stdbool.h>
[2ad98fd]38#include <errno.h>
39#include <str_error.h>
40#include <inttypes.h>
41#include <fibril_synch.h>
[41df71f9]42#include <usbhc_iface.h>
[2ad98fd]43
44#include <usb/debug.h>
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,
[e98e5fc]59 usb_hub_dev_t *hub, usb_port_status_t status);
[442fa6b]60static int get_port_status(usb_hub_port_t *port, usb_port_status_t *status);
[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);
[0918382f]68 if (port->device_attached)
[a825eeb0]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);
[96323d2]126 if (port->reset_status == IN_RESET)
127 port->reset_status = RESET_FAIL;
[442fa6b]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);
[96323d2]143 usb_log_debug2("(%p-%u): Interrupt.\n", hub, port->port_number);
[2ad98fd]144
[c4f7bf6]145 usb_port_status_t status = 0;
[0212751]146 const int opResult = get_port_status(port, &status);
[2ad98fd]147 if (opResult != EOK) {
[96323d2]148 usb_log_error("(%p-%u): Failed to get port status: %s.\n", hub,
[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;
[96323d2]157 usb_log_debug("(%p-%u): Connection change: device %s.\n", hub,
[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) {
[96323d2]164 usb_log_warning("(%p-%u): Failed to clear "
165 "port-change-connection flag: %s.\n", hub,
166 port->port_number, 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) {
[96323d2]173 usb_log_error("(%p-%u): Cannot handle change on"
174 " port: %s.\n", hub, port->port_number,
175 str_error(opResult));
[2ad98fd]176 }
177 } else {
[96323d2]178 /* Handle the case we were in reset */
[58563585]179 // FIXME: usb_hub_port_reset_fail(port);
[d650494]180 /* If enabled change was reported leave the removal
181 * to that handler, it shall ACK the change too. */
182 if (!(status & USB_HUB_PORT_C_STATUS_ENABLED)) {
[a825eeb0]183 usb_hub_port_device_gone(port, hub);
[d650494]184 }
[2ad98fd]185 }
186 }
[d6e2938]187
188 /* Enable change, ports are automatically disabled on errors. */
189 if (status & USB_HUB_PORT_C_STATUS_ENABLED) {
[58563585]190 // TODO: maybe HS reset failed?
[96323d2]191 usb_log_info("(%p-%u): Port disabled because of errors.\n", hub,
[d650494]192 port->port_number);
[a825eeb0]193 usb_hub_port_device_gone(port, hub);
[0212751]194 const int rc = usb_hub_port_clear_feature(port,
195 USB_HUB_FEATURE_C_PORT_ENABLE);
196 if (rc != EOK) {
[96323d2]197 usb_log_error("(%p-%u): Failed to clear port enable "
198 "change feature: %s.", hub, port->port_number,
199 str_error(rc));
[0212751]200 }
[d6e2938]201
[2ad98fd]202 }
[d6e2938]203
204 /* Suspend change */
205 if (status & USB_HUB_PORT_C_STATUS_SUSPEND) {
[96323d2]206 usb_log_error("(%p-%u): Port went to suspend state, this should"
207 " NOT happen as we do not support suspend state!", hub,
[0212751]208 port->port_number);
209 const int rc = usb_hub_port_clear_feature(port,
210 USB_HUB_FEATURE_C_PORT_SUSPEND);
211 if (rc != EOK) {
[96323d2]212 usb_log_error("(%p-%u): Failed to clear port suspend "
213 "change feature: %s.", hub, port->port_number,
214 str_error(rc));
[0212751]215 }
[2ad98fd]216 }
[d6e2938]217
218 /* Over current */
219 if (status & USB_HUB_PORT_C_STATUS_OC) {
[96323d2]220 usb_log_debug("(%p-%u): Port OC reported!.", hub,
221 port->port_number);
[d6e2938]222 /* According to the USB specs:
223 * 11.13.5 Over-current Reporting and Recovery
224 * Hub device is responsible for putting port in power off
225 * mode. USB system software is responsible for powering port
[0212751]226 * back on when the over-current condition is gone */
227 const int rc = usb_hub_port_clear_feature(port,
228 USB_HUB_FEATURE_C_PORT_OVER_CURRENT);
229 if (rc != EOK) {
[96323d2]230 usb_log_error("(%p-%u): Failed to clear port OC change "
231 "feature: %s.\n", hub, port->port_number,
232 str_error(rc));
[0212751]233 }
[d6e2938]234 if (!(status & ~USB_HUB_PORT_STATUS_OC)) {
[0212751]235 const int rc = usb_hub_port_set_feature(
236 port, USB_HUB_FEATURE_PORT_POWER);
237 if (rc != EOK) {
[96323d2]238 usb_log_error("(%p-%u): Failed to set port "
239 "power after OC: %s.", hub,
240 port->port_number, str_error(rc));
[0212751]241 }
[d0c060b]242 }
243 }
[d6e2938]244
245 /* Port reset, set on port reset complete. */
246 if (status & USB_HUB_PORT_C_STATUS_RESET) {
[e98e5fc]247 usb_hub_port_reset_completed(port, hub, status);
[192ba25]248 }
[d6e2938]249
[96323d2]250 usb_log_debug2("(%p-%u): Port status %#08" PRIx32, hub,
[0212751]251 port->port_number, status);
[2ad98fd]252}
[76fbd9a]253
[2ad98fd]254/**
255 * routine called when a device on port has been removed
256 *
257 * If the device on port had default address, it releases default address.
258 * Otherwise does not do anything, because DDF does not allow to remove device
259 * from it`s device tree.
[a825eeb0]260 * @param port port structure
[2ad98fd]261 * @param hub hub representation
262 */
[a825eeb0]263int usb_hub_port_device_gone(usb_hub_port_t *port, usb_hub_dev_t *hub)
[d6e2938]264{
[442fa6b]265 assert(port);
[d650494]266 assert(hub);
[0f4bff8]267 async_exch_t *exch = usb_device_bus_exchange_begin(hub->usb_device);
[3df8ea9]268 if (!exch)
269 return ENOMEM;
[41df71f9]270 const int rc = usbhc_device_remove(exch, port->port_number);
[0f4bff8]271 usb_device_bus_exchange_end(exch);
[3df8ea9]272 if (rc == EOK)
[0918382f]273 port->device_attached = false;
[3df8ea9]274 return rc;
275
[a825eeb0]276}
[76fbd9a]277
[2ad98fd]278/**
279 * Process port reset change
280 *
[d650494]281 * After this change port should be enabled, unless some problem occurred.
[2ad98fd]282 * This functions triggers second phase of enabling new device.
[a825eeb0]283 * @param port Port structure
284 * @param status Port status mask
[2ad98fd]285 */
[e98e5fc]286void usb_hub_port_reset_completed(usb_hub_port_t *port, usb_hub_dev_t *hub,
[442fa6b]287 usb_port_status_t status)
[d6e2938]288{
[442fa6b]289 assert(port);
290 fibril_mutex_lock(&port->mutex);
[96323d2]291 const bool enabled = (status & USB_HUB_PORT_STATUS_ENABLED) != 0;
[aff1880]292 /* Finalize device adding. */
293
[96323d2]294 if (enabled) {
295 port->reset_status = RESET_OK;
[e98e5fc]296 usb_log_debug("(%p-%u): Port reset complete.\n", hub,
297 port->port_number);
[2ad98fd]298 } else {
[96323d2]299 port->reset_status = RESET_FAIL;
[e98e5fc]300 usb_log_warning("(%p-%u): Port reset complete but port not "
301 "enabled.", hub, port->port_number);
[2ad98fd]302 }
[442fa6b]303 fibril_condvar_broadcast(&port->reset_cv);
304 fibril_mutex_unlock(&port->mutex);
[aff1880]305
[d0c060b]306 /* Clear the port reset change. */
[442fa6b]307 int rc = usb_hub_port_clear_feature(port, USB_HUB_FEATURE_C_PORT_RESET);
[d0c060b]308 if (rc != EOK) {
[e98e5fc]309 usb_log_error("(%p-%u): Failed to clear port reset change: %s.",
310 hub, port->port_number, str_error(rc));
[d0c060b]311 }
[2ad98fd]312}
[76fbd9a]313
[2ad98fd]314/** Retrieve port status.
315 *
[a825eeb0]316 * @param[in] port Port structure
[2ad98fd]317 * @param[out] status Where to store the port status.
318 * @return Error code.
319 */
[442fa6b]320static int get_port_status(usb_hub_port_t *port, usb_port_status_t *status)
[b3433a2]321{
[442fa6b]322 assert(port);
[d6e2938]323 /* USB hub specific GET_PORT_STATUS request. See USB Spec 11.16.2.6
324 * Generic GET_STATUS request cannot be used because of the difference
325 * in status data size (2B vs. 4B)*/
[b3433a2]326 const usb_device_request_setup_packet_t request = {
327 .request_type = USB_HUB_REQ_TYPE_GET_PORT_STATUS,
328 .request = USB_HUB_REQUEST_GET_STATUS,
329 .value = 0,
[815b244a]330 .index = uint16_host2usb(port->port_number),
[b3433a2]331 .length = sizeof(usb_port_status_t),
332 };
[442fa6b]333 size_t recv_size;
334 usb_port_status_t status_tmp;
[b3433a2]335
[442fa6b]336 const int rc = usb_pipe_control_read(port->control_pipe,
[b3433a2]337 &request, sizeof(usb_device_request_setup_packet_t),
338 &status_tmp, sizeof(status_tmp), &recv_size);
[2ad98fd]339 if (rc != EOK) {
340 return rc;
341 }
342
343 if (recv_size != sizeof (status_tmp)) {
344 return ELIMIT;
345 }
346
347 if (status != NULL) {
348 *status = status_tmp;
349 }
350
351 return EOK;
352}
[76fbd9a]353
[e98e5fc]354static int port_enable(usb_hub_port_t *port, usb_hub_dev_t *hub, bool enable)
[3df8ea9]355{
356 if (enable) {
[8351f9a4]357 int rc =
[3df8ea9]358 usb_hub_port_set_feature(port, USB_HUB_FEATURE_PORT_RESET);
359 if (rc != EOK) {
[8351f9a4]360 usb_log_error("(%p-%u): Port reset request failed: %s.",
[e98e5fc]361 hub, port->port_number, str_error(rc));
[96323d2]362 return rc;
[3df8ea9]363 }
[96323d2]364 /* Wait until reset completes. */
365 fibril_mutex_lock(&port->mutex);
366 port->reset_status = IN_RESET;
[8351f9a4]367 while (port->reset_status == IN_RESET)
368 fibril_condvar_wait(&port->reset_cv, &port->mutex);
369 rc = port->reset_status == RESET_OK ? EOK : ESTALL;
[96323d2]370 fibril_mutex_unlock(&port->mutex);
[8351f9a4]371 return rc;
[3df8ea9]372 } else {
373 return usb_hub_port_clear_feature(port,
374 USB_HUB_FEATURE_PORT_ENABLE);
375 }
376}
377
[2ad98fd]378/** Fibril for adding a new device.
379 *
380 * Separate fibril is needed because the port reset completion is announced
381 * via interrupt pipe and thus we cannot block here.
382 *
383 * @param arg Pointer to struct add_device_phase1.
384 * @return 0 Always.
385 */
[a825eeb0]386int add_device_phase1_worker_fibril(void *arg)
[d6e2938]387{
[3df8ea9]388 struct add_device_phase1 *params = arg;
389 assert(params);
390
[a5a470c]391 int ret = EOK;
[3df8ea9]392 usb_hub_dev_t *hub = params->hub;
393 usb_hub_port_t *port = params->port;
394 const usb_speed_t speed = params->speed;
395 free(arg);
396
[8351f9a4]397 usb_log_debug("(%p-%u): New device sequence.", hub, port->port_number);
[96323d2]398
[0f4bff8]399 async_exch_t *exch = usb_device_bus_exchange_begin(hub->usb_device);
[3df8ea9]400 if (!exch) {
[96323d2]401 usb_log_error("(%p-%u): Failed to begin bus exchange.", hub,
402 port->port_number);
[a5a470c]403 ret = ENOMEM;
404 goto out;
[3df8ea9]405 }
406
407 /* Reserve default address */
[5e2b1ae6]408 while ((ret = usbhc_reserve_default_address(exch, speed)) == EAGAIN) {
[3df8ea9]409 async_usleep(1000000);
410 }
411 if (ret != EOK) {
[96323d2]412 usb_log_error("(%p-%u): Failed to reserve default address: %s",
413 hub, port->port_number, str_error(ret));
[a5a470c]414 goto out;
[3df8ea9]415 }
416
[96323d2]417 usb_log_debug("(%p-%u): Got default address reseting port.", hub,
418 port->port_number);
[3df8ea9]419 /* Reset port */
[e98e5fc]420 ret = port_enable(port, hub, true);
[96323d2]421 if (ret != EOK) {
[e98e5fc]422 usb_log_error("(%p-%u): Failed to reset port.", hub,
423 port->port_number);
[41df71f9]424 if (usbhc_release_default_address(exch) != EOK)
[e98e5fc]425 usb_log_warning("(%p-%u): Failed to release default "
426 "address.", hub, port->port_number);
[a5a470c]427 ret = EIO;
428 goto out;
[3df8ea9]429 }
[96323d2]430 usb_log_debug("(%p-%u): Port reset, enumerating device", hub,
431 port->port_number);
[3df8ea9]432
[41df71f9]433 ret = usbhc_device_enumerate(exch, port->port_number);
[3df8ea9]434 if (ret != EOK) {
[96323d2]435 usb_log_error("(%p-%u): Failed to enumerate device: %s", hub,
[b300d2b]436 port->port_number, str_error(ret));
[e98e5fc]437 const int ret = port_enable(port, hub, false);
[b300d2b]438 if (ret != EOK) {
[96323d2]439 usb_log_warning("(%p-%u)Failed to disable port (%s), "
440 "NOT releasing default address.", hub,
441 port->port_number, str_error(ret));
[3df8ea9]442 } else {
[41df71f9]443 const int ret = usbhc_release_default_address(exch);
[b300d2b]444 if (ret != EOK)
[96323d2]445 usb_log_warning("(%p-%u): Failed to release "
446 "default address: %s", hub,
447 port->port_number, str_error(ret));
[3df8ea9]448 }
[c9467b0]449 } else {
[96323d2]450 usb_log_debug("(%p-%u): Device enumerated", hub,
451 port->port_number);
[0918382f]452 port->device_attached = true;
[41df71f9]453 if (usbhc_release_default_address(exch) != EOK)
[96323d2]454 usb_log_warning("(%p-%u): Failed to release default "
455 "address", hub, port->port_number);
[3df8ea9]456 }
[a5a470c]457out:
[0f4bff8]458 usb_device_bus_exchange_end(exch);
[c9467b0]459
460 fibril_mutex_lock(&hub->pending_ops_mutex);
461 assert(hub->pending_ops_count > 0);
462 --hub->pending_ops_count;
463 fibril_condvar_signal(&hub->pending_ops_cv);
464 fibril_mutex_unlock(&hub->pending_ops_mutex);
465
[3df8ea9]466 return ret;
[2ad98fd]467}
[76fbd9a]468
[2ad98fd]469/** Start device adding when connection change is detected.
470 *
471 * This fires a new fibril to complete the device addition.
472 *
473 * @param hub Hub where the change occured.
474 * @param port Port index (starting at 1).
475 * @param speed Speed of the device.
476 * @return Error code.
477 */
[6626bba9]478static int create_add_device_fibril(usb_hub_port_t *port, usb_hub_dev_t *hub,
[d6e2938]479 usb_speed_t speed)
480{
[aefa0d5]481 assert(hub);
482 assert(port);
[2ad98fd]483 struct add_device_phase1 *data
[6f05705]484 = malloc(sizeof(struct add_device_phase1));
[2ad98fd]485 if (data == NULL) {
486 return ENOMEM;
487 }
488 data->hub = hub;
489 data->port = port;
490 data->speed = speed;
491
492 fid_t fibril = fibril_create(add_device_phase1_worker_fibril, data);
493 if (fibril == 0) {
494 free(data);
495 return ENOMEM;
496 }
[3fb5a3e]497 fibril_mutex_lock(&hub->pending_ops_mutex);
[aefa0d5]498 ++hub->pending_ops_count;
[3fb5a3e]499 fibril_mutex_unlock(&hub->pending_ops_mutex);
[2ad98fd]500 fibril_add_ready(fibril);
501
502 return EOK;
503}
504
505/**
506 * @}
507 */
Note: See TracBrowser for help on using the repository browser.