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

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

usbhub: Add error codes to debug messages

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