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

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since eaf5e86 was 58563585, checked in by Martin Decky <martin@…>, 9 years ago

code review and cstyle cleanup (no change in functionality)

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