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

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

usbhub: Add identifier to debug messages

Add explicit debug settings (makes it easier to switch)

  • Property mode set to 100644
File size: 14.7 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_hub_dev_t *hub, 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 if (port->reset_status == IN_RESET)
125 port->reset_status = RESET_FAIL;
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_debug2("(%p-%u): Interrupt.\n", hub, 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("(%p-%u): Failed to get port status: %s.\n", hub,
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("(%p-%u): Connection change: device %s.\n", hub,
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("(%p-%u): Failed to clear "
163 "port-change-connection flag: %s.\n", hub,
164 port->port_number, str_error(opResult));
165 }
166
167 if (connected) {
168 const int opResult = create_add_device_fibril(port, hub,
169 usb_port_speed(status));
170 if (opResult != EOK) {
171 usb_log_error("(%p-%u): Cannot handle change on"
172 " port: %s.\n", hub, port->port_number,
173 str_error(opResult));
174 }
175 } else {
176 /* Handle the case we were in reset */
177 usb_hub_port_reset_fail(port);
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)) {
181 usb_hub_port_device_gone(port, hub);
182 }
183 }
184 }
185
186 /* Enable change, ports are automatically disabled on errors. */
187 if (status & USB_HUB_PORT_C_STATUS_ENABLED) {
188 //TODO: maybe HS reset failed?
189 usb_log_info("(%p-%u): Port disabled because of errors.\n", hub,
190 port->port_number);
191 usb_hub_port_device_gone(port, hub);
192 const int rc = usb_hub_port_clear_feature(port,
193 USB_HUB_FEATURE_C_PORT_ENABLE);
194 if (rc != EOK) {
195 usb_log_error("(%p-%u): Failed to clear port enable "
196 "change feature: %s.", hub, port->port_number,
197 str_error(rc));
198 }
199
200 }
201
202 /* Suspend change */
203 if (status & USB_HUB_PORT_C_STATUS_SUSPEND) {
204 usb_log_error("(%p-%u): Port went to suspend state, this should"
205 " NOT happen as we do not support suspend state!", hub,
206 port->port_number);
207 const int rc = usb_hub_port_clear_feature(port,
208 USB_HUB_FEATURE_C_PORT_SUSPEND);
209 if (rc != EOK) {
210 usb_log_error("(%p-%u): Failed to clear port suspend "
211 "change feature: %s.", hub, port->port_number,
212 str_error(rc));
213 }
214 }
215
216 /* Over current */
217 if (status & USB_HUB_PORT_C_STATUS_OC) {
218 usb_log_debug("(%p-%u): Port OC reported!.", hub,
219 port->port_number);
220 /* According to the USB specs:
221 * 11.13.5 Over-current Reporting and Recovery
222 * Hub device is responsible for putting port in power off
223 * mode. USB system software is responsible for powering port
224 * back on when the over-current condition is gone */
225 const int rc = usb_hub_port_clear_feature(port,
226 USB_HUB_FEATURE_C_PORT_OVER_CURRENT);
227 if (rc != EOK) {
228 usb_log_error("(%p-%u): Failed to clear port OC change "
229 "feature: %s.\n", hub, port->port_number,
230 str_error(rc));
231 }
232 if (!(status & ~USB_HUB_PORT_STATUS_OC)) {
233 const int rc = usb_hub_port_set_feature(
234 port, USB_HUB_FEATURE_PORT_POWER);
235 if (rc != EOK) {
236 usb_log_error("(%p-%u): Failed to set port "
237 "power after OC: %s.", hub,
238 port->port_number, str_error(rc));
239 }
240 }
241 }
242
243 /* Port reset, set on port reset complete. */
244 if (status & USB_HUB_PORT_C_STATUS_RESET) {
245 usb_hub_port_reset_completed(port, hub, status);
246 }
247
248 usb_log_debug2("(%p-%u): Port status %#08" PRIx32, hub,
249 port->port_number, status);
250}
251
252/**
253 * routine called when a device on port has been removed
254 *
255 * If the device on port had default address, it releases default address.
256 * Otherwise does not do anything, because DDF does not allow to remove device
257 * from it`s device tree.
258 * @param port port structure
259 * @param hub hub representation
260 */
261int usb_hub_port_device_gone(usb_hub_port_t *port, usb_hub_dev_t *hub)
262{
263 assert(port);
264 assert(hub);
265 async_exch_t *exch = usb_device_bus_exchange_begin(hub->usb_device);
266 if (!exch)
267 return ENOMEM;
268 const int rc = usb_device_remove(exch, port->port_number);
269 usb_device_bus_exchange_end(exch);
270 if (rc == EOK)
271 port->device_attached = false;
272 return rc;
273
274}
275
276/**
277 * Process port reset change
278 *
279 * After this change port should be enabled, unless some problem occurred.
280 * This functions triggers second phase of enabling new device.
281 * @param port Port structure
282 * @param status Port status mask
283 */
284void usb_hub_port_reset_completed(usb_hub_port_t *port, usb_hub_dev_t *hub,
285 usb_port_status_t status)
286{
287 assert(port);
288 fibril_mutex_lock(&port->mutex);
289 const bool enabled = (status & USB_HUB_PORT_STATUS_ENABLED) != 0;
290 /* Finalize device adding. */
291
292 if (enabled) {
293 port->reset_status = RESET_OK;
294 usb_log_debug("(%p-%u): Port reset complete.\n", hub,
295 port->port_number);
296 } else {
297 port->reset_status = RESET_FAIL;
298 usb_log_warning("(%p-%u): Port reset complete but port not "
299 "enabled.", hub, port->port_number);
300 }
301 fibril_condvar_broadcast(&port->reset_cv);
302 fibril_mutex_unlock(&port->mutex);
303
304 /* Clear the port reset change. */
305 int rc = usb_hub_port_clear_feature(port, USB_HUB_FEATURE_C_PORT_RESET);
306 if (rc != EOK) {
307 usb_log_error("(%p-%u): Failed to clear port reset change: %s.",
308 hub, port->port_number, str_error(rc));
309 }
310}
311
312/** Retrieve port status.
313 *
314 * @param[in] port Port structure
315 * @param[out] status Where to store the port status.
316 * @return Error code.
317 */
318static int get_port_status(usb_hub_port_t *port, usb_port_status_t *status)
319{
320 assert(port);
321 /* USB hub specific GET_PORT_STATUS request. See USB Spec 11.16.2.6
322 * Generic GET_STATUS request cannot be used because of the difference
323 * in status data size (2B vs. 4B)*/
324 const usb_device_request_setup_packet_t request = {
325 .request_type = USB_HUB_REQ_TYPE_GET_PORT_STATUS,
326 .request = USB_HUB_REQUEST_GET_STATUS,
327 .value = 0,
328 .index = uint16_host2usb(port->port_number),
329 .length = sizeof(usb_port_status_t),
330 };
331 size_t recv_size;
332 usb_port_status_t status_tmp;
333
334 const int rc = usb_pipe_control_read(port->control_pipe,
335 &request, sizeof(usb_device_request_setup_packet_t),
336 &status_tmp, sizeof(status_tmp), &recv_size);
337 if (rc != EOK) {
338 return rc;
339 }
340
341 if (recv_size != sizeof (status_tmp)) {
342 return ELIMIT;
343 }
344
345 if (status != NULL) {
346 *status = status_tmp;
347 }
348
349 return EOK;
350}
351
352static int port_enable(usb_hub_port_t *port, usb_hub_dev_t *hub, bool enable)
353{
354 if (enable) {
355 const int rc =
356 usb_hub_port_set_feature(port, USB_HUB_FEATURE_PORT_RESET);
357 if (rc != EOK) {
358 usb_log_error("(%p-%u): Port reset failed: %s.\n",
359 hub, port->port_number, str_error(rc));
360 return rc;
361 }
362 /* Wait until reset completes. */
363 fibril_mutex_lock(&port->mutex);
364 port->reset_status = IN_RESET;
365 while (port->reset_status == IN_RESET) {
366 fibril_condvar_wait(&port->reset_cv,
367 &port->mutex);
368 }
369 fibril_mutex_unlock(&port->mutex);
370 return port->reset_status == RESET_OK ? EOK : ESTALL;
371 } else {
372 return usb_hub_port_clear_feature(port,
373 USB_HUB_FEATURE_PORT_ENABLE);
374 }
375}
376
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 */
385int add_device_phase1_worker_fibril(void *arg)
386{
387 struct add_device_phase1 *params = arg;
388 assert(params);
389
390 int ret = EOK;
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
396 usb_log_debug("(%p-%u): New device detected.", hub,
397 port->port_number);
398
399 async_exch_t *exch = usb_device_bus_exchange_begin(hub->usb_device);
400 if (!exch) {
401 usb_log_error("(%p-%u): Failed to begin bus exchange.", hub,
402 port->port_number);
403 ret = ENOMEM;
404 goto out;
405 }
406
407 /* Reserve default address */
408 while ((ret = usb_reserve_default_address(exch, speed)) == ENOENT) {
409 async_usleep(1000000);
410 }
411 if (ret != EOK) {
412 usb_log_error("(%p-%u): Failed to reserve default address: %s",
413 hub, port->port_number, str_error(ret));
414 goto out;
415 }
416
417 usb_log_debug("(%p-%u): Got default address reseting port.", hub,
418 port->port_number);
419 /* Reset port */
420 ret = port_enable(port, hub, true);
421 if (ret != EOK) {
422 usb_log_error("(%p-%u): Failed to reset port.", hub,
423 port->port_number);
424 if (usb_release_default_address(exch) != EOK)
425 usb_log_warning("(%p-%u): Failed to release default "
426 "address.", hub, port->port_number);
427 ret = EIO;
428 goto out;
429 }
430 usb_log_debug("(%p-%u): Port reset, enumerating device", hub,
431 port->port_number);
432
433 ret = usb_device_enumerate(exch, port->port_number);
434 if (ret != EOK) {
435 usb_log_error("(%p-%u): Failed to enumerate device: %s", hub,
436 port->port_number, str_error(ret));
437 const int ret = port_enable(port, hub, false);
438 if (ret != EOK) {
439 usb_log_warning("(%p-%u)Failed to disable port (%s), "
440 "NOT releasing default address.", hub,
441 port->port_number, str_error(ret));
442 } else {
443 const int ret = usb_release_default_address(exch);
444 if (ret != EOK)
445 usb_log_warning("(%p-%u): Failed to release "
446 "default address: %s", hub,
447 port->port_number, str_error(ret));
448 }
449 } else {
450 usb_log_debug("(%p-%u): Device enumerated", hub,
451 port->port_number);
452 port->device_attached = true;
453 if (usb_release_default_address(exch) != EOK)
454 usb_log_warning("(%p-%u): Failed to release default "
455 "address", hub, port->port_number);
456 }
457out:
458 usb_device_bus_exchange_end(exch);
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
466 return ret;
467}
468
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 */
478static int create_add_device_fibril(usb_hub_port_t *port, usb_hub_dev_t *hub,
479 usb_speed_t speed)
480{
481 assert(hub);
482 assert(port);
483 struct add_device_phase1 *data
484 = malloc(sizeof(struct add_device_phase1));
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 }
497 fibril_mutex_lock(&hub->pending_ops_mutex);
498 ++hub->pending_ops_count;
499 fibril_mutex_unlock(&hub->pending_ops_mutex);
500 fibril_add_ready(fibril);
501
502 return EOK;
503}
504
505/**
506 * @}
507 */
Note: See TracBrowser for help on using the repository browser.