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

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 741bcdeb 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
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
30/** @addtogroup drvusbhub
31 * @{
32 */
33/** @file
34 * Hub ports functions.
35 */
36
37#include <stdbool.h>
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
45#include "port.h"
46#include "usbhub.h"
47#include "status.h"
48
49/** Information for fibril for device discovery. */
50struct add_device_phase1 {
51 usb_hub_dev_t *hub;
52 usb_hub_port_t *port;
53 usb_speed_t speed;
54};
55
56static int usb_hub_port_device_gone(usb_hub_port_t *port, usb_hub_dev_t *hub);
57static void usb_hub_port_reset_completed(usb_hub_port_t *port,
58 usb_hub_dev_t *hub, usb_port_status_t status);
59static int get_port_status(usb_hub_port_t *port, usb_port_status_t *status);
60static int add_device_phase1_worker_fibril(void *arg);
61static int create_add_device_fibril(usb_hub_port_t *port, usb_hub_dev_t *hub,
62 usb_speed_t speed);
63
64int usb_hub_port_fini(usb_hub_port_t *port, usb_hub_dev_t *hub)
65{
66 assert(port);
67 if (port->device_attached)
68 return usb_hub_port_device_gone(port, hub);
69 return EOK;
70}
71
72/**
73 * Clear feature on hub port.
74 *
75 * @param port Port structure.
76 * @param feature Feature selector.
77 * @return Operation result
78 */
79int usb_hub_port_clear_feature(
80 usb_hub_port_t *port, usb_hub_class_feature_t feature)
81{
82 assert(port);
83 const usb_device_request_setup_packet_t clear_request = {
84 .request_type = USB_HUB_REQ_TYPE_CLEAR_PORT_FEATURE,
85 .request = USB_DEVREQ_CLEAR_FEATURE,
86 .value = feature,
87 .index = port->port_number,
88 .length = 0,
89 };
90 return usb_pipe_control_write(port->control_pipe, &clear_request,
91 sizeof(clear_request), NULL, 0);
92}
93
94/**
95 * Set feature on hub port.
96 *
97 * @param port Port structure.
98 * @param feature Feature selector.
99 * @return Operation result
100 */
101int usb_hub_port_set_feature(
102 usb_hub_port_t *port, usb_hub_class_feature_t feature)
103{
104 assert(port);
105 const usb_device_request_setup_packet_t clear_request = {
106 .request_type = USB_HUB_REQ_TYPE_SET_PORT_FEATURE,
107 .request = USB_DEVREQ_SET_FEATURE,
108 .index = port->port_number,
109 .value = feature,
110 .length = 0,
111 };
112 return usb_pipe_control_write(port->control_pipe, &clear_request,
113 sizeof(clear_request), NULL, 0);
114}
115
116/**
117 * Mark reset process as failed due to external reasons
118 *
119 * @param port Port structure
120 */
121void usb_hub_port_reset_fail(usb_hub_port_t *port)
122{
123 assert(port);
124 fibril_mutex_lock(&port->mutex);
125 if (port->reset_status == IN_RESET)
126 port->reset_status = RESET_FAIL;
127 fibril_condvar_broadcast(&port->reset_cv);
128 fibril_mutex_unlock(&port->mutex);
129}
130
131/**
132 * Process interrupts on given port
133 *
134 * Accepts connection, over current and port reset change.
135 * @param port port structure
136 * @param hub hub representation
137 */
138void usb_hub_port_process_interrupt(usb_hub_port_t *port, usb_hub_dev_t *hub)
139{
140 assert(port);
141 assert(hub);
142 usb_log_debug2("(%p-%u): Interrupt.\n", hub, port->port_number);
143
144 usb_port_status_t status = 0;
145 const int opResult = get_port_status(port, &status);
146 if (opResult != EOK) {
147 usb_log_error("(%p-%u): Failed to get port status: %s.\n", hub,
148 port->port_number, str_error(opResult));
149 return;
150 }
151
152 /* Connection change */
153 if (status & USB_HUB_PORT_C_STATUS_CONNECTION) {
154 const bool connected =
155 (status & USB_HUB_PORT_STATUS_CONNECTION) != 0;
156 usb_log_debug("(%p-%u): Connection change: device %s.\n", hub,
157 port->port_number, connected ? "attached" : "removed");
158
159 /* ACK the change */
160 const int opResult = usb_hub_port_clear_feature(port,
161 USB_HUB_FEATURE_C_PORT_CONNECTION);
162 if (opResult != EOK) {
163 usb_log_warning("(%p-%u): Failed to clear "
164 "port-change-connection flag: %s.\n", hub,
165 port->port_number, str_error(opResult));
166 }
167
168 if (connected) {
169 const int opResult = create_add_device_fibril(port, hub,
170 usb_port_speed(status));
171 if (opResult != EOK) {
172 usb_log_error("(%p-%u): Cannot handle change on"
173 " port: %s.\n", hub, port->port_number,
174 str_error(opResult));
175 }
176 } else {
177 /* Handle the case we were in reset */
178 // FIXME: usb_hub_port_reset_fail(port);
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)) {
182 usb_hub_port_device_gone(port, hub);
183 }
184 }
185 }
186
187 /* Enable change, ports are automatically disabled on errors. */
188 if (status & USB_HUB_PORT_C_STATUS_ENABLED) {
189 // TODO: maybe HS reset failed?
190 usb_log_info("(%p-%u): Port disabled because of errors.\n", hub,
191 port->port_number);
192 usb_hub_port_device_gone(port, hub);
193 const int rc = usb_hub_port_clear_feature(port,
194 USB_HUB_FEATURE_C_PORT_ENABLE);
195 if (rc != EOK) {
196 usb_log_error("(%p-%u): Failed to clear port enable "
197 "change feature: %s.", hub, port->port_number,
198 str_error(rc));
199 }
200
201 }
202
203 /* Suspend change */
204 if (status & USB_HUB_PORT_C_STATUS_SUSPEND) {
205 usb_log_error("(%p-%u): Port went to suspend state, this should"
206 " NOT happen as we do not support suspend state!", hub,
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) {
211 usb_log_error("(%p-%u): Failed to clear port suspend "
212 "change feature: %s.", hub, port->port_number,
213 str_error(rc));
214 }
215 }
216
217 /* Over current */
218 if (status & USB_HUB_PORT_C_STATUS_OC) {
219 usb_log_debug("(%p-%u): Port OC reported!.", hub,
220 port->port_number);
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
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) {
229 usb_log_error("(%p-%u): Failed to clear port OC change "
230 "feature: %s.\n", hub, port->port_number,
231 str_error(rc));
232 }
233 if (!(status & ~USB_HUB_PORT_STATUS_OC)) {
234 const int rc = usb_hub_port_set_feature(
235 port, USB_HUB_FEATURE_PORT_POWER);
236 if (rc != EOK) {
237 usb_log_error("(%p-%u): Failed to set port "
238 "power after OC: %s.", hub,
239 port->port_number, str_error(rc));
240 }
241 }
242 }
243
244 /* Port reset, set on port reset complete. */
245 if (status & USB_HUB_PORT_C_STATUS_RESET) {
246 usb_hub_port_reset_completed(port, hub, status);
247 }
248
249 usb_log_debug2("(%p-%u): Port status %#08" PRIx32, hub,
250 port->port_number, status);
251}
252
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.
259 * @param port port structure
260 * @param hub hub representation
261 */
262int usb_hub_port_device_gone(usb_hub_port_t *port, usb_hub_dev_t *hub)
263{
264 assert(port);
265 assert(hub);
266 async_exch_t *exch = usb_device_bus_exchange_begin(hub->usb_device);
267 if (!exch)
268 return ENOMEM;
269 const int rc = usb_device_remove(exch, port->port_number);
270 usb_device_bus_exchange_end(exch);
271 if (rc == EOK)
272 port->device_attached = false;
273 return rc;
274
275}
276
277/**
278 * Process port reset change
279 *
280 * After this change port should be enabled, unless some problem occurred.
281 * This functions triggers second phase of enabling new device.
282 * @param port Port structure
283 * @param status Port status mask
284 */
285void usb_hub_port_reset_completed(usb_hub_port_t *port, usb_hub_dev_t *hub,
286 usb_port_status_t status)
287{
288 assert(port);
289 fibril_mutex_lock(&port->mutex);
290 const bool enabled = (status & USB_HUB_PORT_STATUS_ENABLED) != 0;
291 /* Finalize device adding. */
292
293 if (enabled) {
294 port->reset_status = RESET_OK;
295 usb_log_debug("(%p-%u): Port reset complete.\n", hub,
296 port->port_number);
297 } else {
298 port->reset_status = RESET_FAIL;
299 usb_log_warning("(%p-%u): Port reset complete but port not "
300 "enabled.", hub, port->port_number);
301 }
302 fibril_condvar_broadcast(&port->reset_cv);
303 fibril_mutex_unlock(&port->mutex);
304
305 /* Clear the port reset change. */
306 int rc = usb_hub_port_clear_feature(port, USB_HUB_FEATURE_C_PORT_RESET);
307 if (rc != EOK) {
308 usb_log_error("(%p-%u): Failed to clear port reset change: %s.",
309 hub, port->port_number, str_error(rc));
310 }
311}
312
313/** Retrieve port status.
314 *
315 * @param[in] port Port structure
316 * @param[out] status Where to store the port status.
317 * @return Error code.
318 */
319static int get_port_status(usb_hub_port_t *port, usb_port_status_t *status)
320{
321 assert(port);
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)*/
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,
329 .index = uint16_host2usb(port->port_number),
330 .length = sizeof(usb_port_status_t),
331 };
332 size_t recv_size;
333 usb_port_status_t status_tmp;
334
335 const int rc = usb_pipe_control_read(port->control_pipe,
336 &request, sizeof(usb_device_request_setup_packet_t),
337 &status_tmp, sizeof(status_tmp), &recv_size);
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}
352
353static int port_enable(usb_hub_port_t *port, usb_hub_dev_t *hub, bool enable)
354{
355 if (enable) {
356 int rc =
357 usb_hub_port_set_feature(port, USB_HUB_FEATURE_PORT_RESET);
358 if (rc != EOK) {
359 usb_log_error("(%p-%u): Port reset request failed: %s.",
360 hub, port->port_number, str_error(rc));
361 return rc;
362 }
363 /* Wait until reset completes. */
364 fibril_mutex_lock(&port->mutex);
365 port->reset_status = IN_RESET;
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;
369 fibril_mutex_unlock(&port->mutex);
370 return rc;
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 sequence.", hub, port->port_number);
397
398 async_exch_t *exch = usb_device_bus_exchange_begin(hub->usb_device);
399 if (!exch) {
400 usb_log_error("(%p-%u): Failed to begin bus exchange.", hub,
401 port->port_number);
402 ret = ENOMEM;
403 goto out;
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) {
411 usb_log_error("(%p-%u): Failed to reserve default address: %s",
412 hub, port->port_number, str_error(ret));
413 goto out;
414 }
415
416 usb_log_debug("(%p-%u): Got default address reseting port.", hub,
417 port->port_number);
418 /* Reset port */
419 ret = port_enable(port, hub, true);
420 if (ret != EOK) {
421 usb_log_error("(%p-%u): Failed to reset port.", hub,
422 port->port_number);
423 if (usb_release_default_address(exch) != EOK)
424 usb_log_warning("(%p-%u): Failed to release default "
425 "address.", hub, port->port_number);
426 ret = EIO;
427 goto out;
428 }
429 usb_log_debug("(%p-%u): Port reset, enumerating device", hub,
430 port->port_number);
431
432 ret = usb_device_enumerate(exch, port->port_number);
433 if (ret != EOK) {
434 usb_log_error("(%p-%u): Failed to enumerate device: %s", hub,
435 port->port_number, str_error(ret));
436 const int ret = port_enable(port, hub, false);
437 if (ret != EOK) {
438 usb_log_warning("(%p-%u)Failed to disable port (%s), "
439 "NOT releasing default address.", hub,
440 port->port_number, str_error(ret));
441 } else {
442 const int ret = usb_release_default_address(exch);
443 if (ret != EOK)
444 usb_log_warning("(%p-%u): Failed to release "
445 "default address: %s", hub,
446 port->port_number, str_error(ret));
447 }
448 } else {
449 usb_log_debug("(%p-%u): Device enumerated", hub,
450 port->port_number);
451 port->device_attached = true;
452 if (usb_release_default_address(exch) != EOK)
453 usb_log_warning("(%p-%u): Failed to release default "
454 "address", hub, port->port_number);
455 }
456out:
457 usb_device_bus_exchange_end(exch);
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
465 return ret;
466}
467
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 */
477static int create_add_device_fibril(usb_hub_port_t *port, usb_hub_dev_t *hub,
478 usb_speed_t speed)
479{
480 assert(hub);
481 assert(port);
482 struct add_device_phase1 *data
483 = malloc(sizeof(struct add_device_phase1));
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 }
496 fibril_mutex_lock(&hub->pending_ops_mutex);
497 ++hub->pending_ops_count;
498 fibril_mutex_unlock(&hub->pending_ops_mutex);
499 fibril_add_ready(fibril);
500
501 return EOK;
502}
503
504/**
505 * @}
506 */
Note: See TracBrowser for help on using the repository browser.