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

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

usbhub: Rework port reset a bit.

return error if port reset did not end up with enabled port

  • 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 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 request failed: %s.",
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, &port->mutex);
367 rc = port->reset_status == RESET_OK ? EOK : ESTALL;
368 fibril_mutex_unlock(&port->mutex);
369 return rc;
370 } else {
371 return usb_hub_port_clear_feature(port,
372 USB_HUB_FEATURE_PORT_ENABLE);
373 }
374}
375
376/** Fibril for adding a new device.
377 *
378 * Separate fibril is needed because the port reset completion is announced
379 * via interrupt pipe and thus we cannot block here.
380 *
381 * @param arg Pointer to struct add_device_phase1.
382 * @return 0 Always.
383 */
384int add_device_phase1_worker_fibril(void *arg)
385{
386 struct add_device_phase1 *params = arg;
387 assert(params);
388
389 int ret = EOK;
390 usb_hub_dev_t *hub = params->hub;
391 usb_hub_port_t *port = params->port;
392 const usb_speed_t speed = params->speed;
393 free(arg);
394
395 usb_log_debug("(%p-%u): New device sequence.", hub, port->port_number);
396
397 async_exch_t *exch = usb_device_bus_exchange_begin(hub->usb_device);
398 if (!exch) {
399 usb_log_error("(%p-%u): Failed to begin bus exchange.", hub,
400 port->port_number);
401 ret = ENOMEM;
402 goto out;
403 }
404
405 /* Reserve default address */
406 while ((ret = usb_reserve_default_address(exch, speed)) == ENOENT) {
407 async_usleep(1000000);
408 }
409 if (ret != EOK) {
410 usb_log_error("(%p-%u): Failed to reserve default address: %s",
411 hub, port->port_number, str_error(ret));
412 goto out;
413 }
414
415 usb_log_debug("(%p-%u): Got default address reseting port.", hub,
416 port->port_number);
417 /* Reset port */
418 ret = port_enable(port, hub, true);
419 if (ret != EOK) {
420 usb_log_error("(%p-%u): Failed to reset port.", hub,
421 port->port_number);
422 if (usb_release_default_address(exch) != EOK)
423 usb_log_warning("(%p-%u): Failed to release default "
424 "address.", hub, port->port_number);
425 ret = EIO;
426 goto out;
427 }
428 usb_log_debug("(%p-%u): Port reset, enumerating device", hub,
429 port->port_number);
430
431 ret = usb_device_enumerate(exch, port->port_number);
432 if (ret != EOK) {
433 usb_log_error("(%p-%u): Failed to enumerate device: %s", hub,
434 port->port_number, str_error(ret));
435 const int ret = port_enable(port, hub, false);
436 if (ret != EOK) {
437 usb_log_warning("(%p-%u)Failed to disable port (%s), "
438 "NOT releasing default address.", hub,
439 port->port_number, str_error(ret));
440 } else {
441 const int ret = usb_release_default_address(exch);
442 if (ret != EOK)
443 usb_log_warning("(%p-%u): Failed to release "
444 "default address: %s", hub,
445 port->port_number, str_error(ret));
446 }
447 } else {
448 usb_log_debug("(%p-%u): Device enumerated", hub,
449 port->port_number);
450 port->device_attached = true;
451 if (usb_release_default_address(exch) != EOK)
452 usb_log_warning("(%p-%u): Failed to release default "
453 "address", hub, port->port_number);
454 }
455out:
456 usb_device_bus_exchange_end(exch);
457
458 fibril_mutex_lock(&hub->pending_ops_mutex);
459 assert(hub->pending_ops_count > 0);
460 --hub->pending_ops_count;
461 fibril_condvar_signal(&hub->pending_ops_cv);
462 fibril_mutex_unlock(&hub->pending_ops_mutex);
463
464 return ret;
465}
466
467/** Start device adding when connection change is detected.
468 *
469 * This fires a new fibril to complete the device addition.
470 *
471 * @param hub Hub where the change occured.
472 * @param port Port index (starting at 1).
473 * @param speed Speed of the device.
474 * @return Error code.
475 */
476static int create_add_device_fibril(usb_hub_port_t *port, usb_hub_dev_t *hub,
477 usb_speed_t speed)
478{
479 assert(hub);
480 assert(port);
481 struct add_device_phase1 *data
482 = malloc(sizeof(struct add_device_phase1));
483 if (data == NULL) {
484 return ENOMEM;
485 }
486 data->hub = hub;
487 data->port = port;
488 data->speed = speed;
489
490 fid_t fibril = fibril_create(add_device_phase1_worker_fibril, data);
491 if (fibril == 0) {
492 free(data);
493 return ENOMEM;
494 }
495 fibril_mutex_lock(&hub->pending_ops_mutex);
496 ++hub->pending_ops_count;
497 fibril_mutex_unlock(&hub->pending_ops_mutex);
498 fibril_add_ready(fibril);
499
500 return EOK;
501}
502
503/**
504 * @}
505 */
Note: See TracBrowser for help on using the repository browser.