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

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since f543804 was 41df71f9, checked in by Ondřej Hlavatý <aearsis@…>, 8 years ago

ddf: split usb interface to usb and usbhc

  • Property mode set to 100644
File size: 14.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
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#include <usbhc_iface.h>
43
44#include <usb/debug.h>
45
46#include "port.h"
47#include "usbhub.h"
48#include "status.h"
49
50/** Information for fibril for device discovery. */
51struct add_device_phase1 {
52 usb_hub_dev_t *hub;
53 usb_hub_port_t *port;
54 usb_speed_t speed;
55};
56
57static int usb_hub_port_device_gone(usb_hub_port_t *port, usb_hub_dev_t *hub);
58static void usb_hub_port_reset_completed(usb_hub_port_t *port,
59 usb_hub_dev_t *hub, usb_port_status_t status);
60static int get_port_status(usb_hub_port_t *port, usb_port_status_t *status);
61static int add_device_phase1_worker_fibril(void *arg);
62static int create_add_device_fibril(usb_hub_port_t *port, usb_hub_dev_t *hub,
63 usb_speed_t speed);
64
65int usb_hub_port_fini(usb_hub_port_t *port, usb_hub_dev_t *hub)
66{
67 assert(port);
68 if (port->device_attached)
69 return usb_hub_port_device_gone(port, hub);
70 return EOK;
71}
72
73/**
74 * Clear feature on hub port.
75 *
76 * @param port Port structure.
77 * @param feature Feature selector.
78 * @return Operation result
79 */
80int usb_hub_port_clear_feature(
81 usb_hub_port_t *port, usb_hub_class_feature_t feature)
82{
83 assert(port);
84 const usb_device_request_setup_packet_t clear_request = {
85 .request_type = USB_HUB_REQ_TYPE_CLEAR_PORT_FEATURE,
86 .request = USB_DEVREQ_CLEAR_FEATURE,
87 .value = feature,
88 .index = port->port_number,
89 .length = 0,
90 };
91 return usb_pipe_control_write(port->control_pipe, &clear_request,
92 sizeof(clear_request), NULL, 0);
93}
94
95/**
96 * Set feature on hub port.
97 *
98 * @param port Port structure.
99 * @param feature Feature selector.
100 * @return Operation result
101 */
102int usb_hub_port_set_feature(
103 usb_hub_port_t *port, usb_hub_class_feature_t feature)
104{
105 assert(port);
106 const usb_device_request_setup_packet_t clear_request = {
107 .request_type = USB_HUB_REQ_TYPE_SET_PORT_FEATURE,
108 .request = USB_DEVREQ_SET_FEATURE,
109 .index = port->port_number,
110 .value = feature,
111 .length = 0,
112 };
113 return usb_pipe_control_write(port->control_pipe, &clear_request,
114 sizeof(clear_request), NULL, 0);
115}
116
117/**
118 * Mark reset process as failed due to external reasons
119 *
120 * @param port Port structure
121 */
122void usb_hub_port_reset_fail(usb_hub_port_t *port)
123{
124 assert(port);
125 fibril_mutex_lock(&port->mutex);
126 if (port->reset_status == IN_RESET)
127 port->reset_status = RESET_FAIL;
128 fibril_condvar_broadcast(&port->reset_cv);
129 fibril_mutex_unlock(&port->mutex);
130}
131
132/**
133 * Process interrupts on given port
134 *
135 * Accepts connection, over current and port reset change.
136 * @param port port structure
137 * @param hub hub representation
138 */
139void usb_hub_port_process_interrupt(usb_hub_port_t *port, usb_hub_dev_t *hub)
140{
141 assert(port);
142 assert(hub);
143 usb_log_debug2("(%p-%u): Interrupt.\n", hub, port->port_number);
144
145 usb_port_status_t status = 0;
146 const int opResult = get_port_status(port, &status);
147 if (opResult != EOK) {
148 usb_log_error("(%p-%u): Failed to get port status: %s.\n", hub,
149 port->port_number, str_error(opResult));
150 return;
151 }
152
153 /* Connection change */
154 if (status & USB_HUB_PORT_C_STATUS_CONNECTION) {
155 const bool connected =
156 (status & USB_HUB_PORT_STATUS_CONNECTION) != 0;
157 usb_log_debug("(%p-%u): Connection change: device %s.\n", hub,
158 port->port_number, connected ? "attached" : "removed");
159
160 /* ACK the change */
161 const int opResult = usb_hub_port_clear_feature(port,
162 USB_HUB_FEATURE_C_PORT_CONNECTION);
163 if (opResult != EOK) {
164 usb_log_warning("(%p-%u): Failed to clear "
165 "port-change-connection flag: %s.\n", hub,
166 port->port_number, str_error(opResult));
167 }
168
169 if (connected) {
170 const int opResult = create_add_device_fibril(port, hub,
171 usb_port_speed(status));
172 if (opResult != EOK) {
173 usb_log_error("(%p-%u): Cannot handle change on"
174 " port: %s.\n", hub, port->port_number,
175 str_error(opResult));
176 }
177 } else {
178 /* Handle the case we were in reset */
179 // FIXME: usb_hub_port_reset_fail(port);
180 /* If enabled change was reported leave the removal
181 * to that handler, it shall ACK the change too. */
182 if (!(status & USB_HUB_PORT_C_STATUS_ENABLED)) {
183 usb_hub_port_device_gone(port, hub);
184 }
185 }
186 }
187
188 /* Enable change, ports are automatically disabled on errors. */
189 if (status & USB_HUB_PORT_C_STATUS_ENABLED) {
190 // TODO: maybe HS reset failed?
191 usb_log_info("(%p-%u): Port disabled because of errors.\n", hub,
192 port->port_number);
193 usb_hub_port_device_gone(port, hub);
194 const int rc = usb_hub_port_clear_feature(port,
195 USB_HUB_FEATURE_C_PORT_ENABLE);
196 if (rc != EOK) {
197 usb_log_error("(%p-%u): Failed to clear port enable "
198 "change feature: %s.", hub, port->port_number,
199 str_error(rc));
200 }
201
202 }
203
204 /* Suspend change */
205 if (status & USB_HUB_PORT_C_STATUS_SUSPEND) {
206 usb_log_error("(%p-%u): Port went to suspend state, this should"
207 " NOT happen as we do not support suspend state!", hub,
208 port->port_number);
209 const int rc = usb_hub_port_clear_feature(port,
210 USB_HUB_FEATURE_C_PORT_SUSPEND);
211 if (rc != EOK) {
212 usb_log_error("(%p-%u): Failed to clear port suspend "
213 "change feature: %s.", hub, port->port_number,
214 str_error(rc));
215 }
216 }
217
218 /* Over current */
219 if (status & USB_HUB_PORT_C_STATUS_OC) {
220 usb_log_debug("(%p-%u): Port OC reported!.", hub,
221 port->port_number);
222 /* According to the USB specs:
223 * 11.13.5 Over-current Reporting and Recovery
224 * Hub device is responsible for putting port in power off
225 * mode. USB system software is responsible for powering port
226 * back on when the over-current condition is gone */
227 const int rc = usb_hub_port_clear_feature(port,
228 USB_HUB_FEATURE_C_PORT_OVER_CURRENT);
229 if (rc != EOK) {
230 usb_log_error("(%p-%u): Failed to clear port OC change "
231 "feature: %s.\n", hub, port->port_number,
232 str_error(rc));
233 }
234 if (!(status & ~USB_HUB_PORT_STATUS_OC)) {
235 const int rc = usb_hub_port_set_feature(
236 port, USB_HUB_FEATURE_PORT_POWER);
237 if (rc != EOK) {
238 usb_log_error("(%p-%u): Failed to set port "
239 "power after OC: %s.", hub,
240 port->port_number, str_error(rc));
241 }
242 }
243 }
244
245 /* Port reset, set on port reset complete. */
246 if (status & USB_HUB_PORT_C_STATUS_RESET) {
247 usb_hub_port_reset_completed(port, hub, status);
248 }
249
250 usb_log_debug2("(%p-%u): Port status %#08" PRIx32, hub,
251 port->port_number, status);
252}
253
254/**
255 * routine called when a device on port has been removed
256 *
257 * If the device on port had default address, it releases default address.
258 * Otherwise does not do anything, because DDF does not allow to remove device
259 * from it`s device tree.
260 * @param port port structure
261 * @param hub hub representation
262 */
263int usb_hub_port_device_gone(usb_hub_port_t *port, usb_hub_dev_t *hub)
264{
265 assert(port);
266 assert(hub);
267 async_exch_t *exch = usb_device_bus_exchange_begin(hub->usb_device);
268 if (!exch)
269 return ENOMEM;
270 const int rc = usbhc_device_remove(exch, port->port_number);
271 usb_device_bus_exchange_end(exch);
272 if (rc == EOK)
273 port->device_attached = false;
274 return rc;
275
276}
277
278/**
279 * Process port reset change
280 *
281 * After this change port should be enabled, unless some problem occurred.
282 * This functions triggers second phase of enabling new device.
283 * @param port Port structure
284 * @param status Port status mask
285 */
286void usb_hub_port_reset_completed(usb_hub_port_t *port, usb_hub_dev_t *hub,
287 usb_port_status_t status)
288{
289 assert(port);
290 fibril_mutex_lock(&port->mutex);
291 const bool enabled = (status & USB_HUB_PORT_STATUS_ENABLED) != 0;
292 /* Finalize device adding. */
293
294 if (enabled) {
295 port->reset_status = RESET_OK;
296 usb_log_debug("(%p-%u): Port reset complete.\n", hub,
297 port->port_number);
298 } else {
299 port->reset_status = RESET_FAIL;
300 usb_log_warning("(%p-%u): Port reset complete but port not "
301 "enabled.", hub, port->port_number);
302 }
303 fibril_condvar_broadcast(&port->reset_cv);
304 fibril_mutex_unlock(&port->mutex);
305
306 /* Clear the port reset change. */
307 int rc = usb_hub_port_clear_feature(port, USB_HUB_FEATURE_C_PORT_RESET);
308 if (rc != EOK) {
309 usb_log_error("(%p-%u): Failed to clear port reset change: %s.",
310 hub, port->port_number, str_error(rc));
311 }
312}
313
314/** Retrieve port status.
315 *
316 * @param[in] port Port structure
317 * @param[out] status Where to store the port status.
318 * @return Error code.
319 */
320static int get_port_status(usb_hub_port_t *port, usb_port_status_t *status)
321{
322 assert(port);
323 /* USB hub specific GET_PORT_STATUS request. See USB Spec 11.16.2.6
324 * Generic GET_STATUS request cannot be used because of the difference
325 * in status data size (2B vs. 4B)*/
326 const usb_device_request_setup_packet_t request = {
327 .request_type = USB_HUB_REQ_TYPE_GET_PORT_STATUS,
328 .request = USB_HUB_REQUEST_GET_STATUS,
329 .value = 0,
330 .index = uint16_host2usb(port->port_number),
331 .length = sizeof(usb_port_status_t),
332 };
333 size_t recv_size;
334 usb_port_status_t status_tmp;
335
336 const int rc = usb_pipe_control_read(port->control_pipe,
337 &request, sizeof(usb_device_request_setup_packet_t),
338 &status_tmp, sizeof(status_tmp), &recv_size);
339 if (rc != EOK) {
340 return rc;
341 }
342
343 if (recv_size != sizeof (status_tmp)) {
344 return ELIMIT;
345 }
346
347 if (status != NULL) {
348 *status = status_tmp;
349 }
350
351 return EOK;
352}
353
354static int port_enable(usb_hub_port_t *port, usb_hub_dev_t *hub, bool enable)
355{
356 if (enable) {
357 int rc =
358 usb_hub_port_set_feature(port, USB_HUB_FEATURE_PORT_RESET);
359 if (rc != EOK) {
360 usb_log_error("(%p-%u): Port reset request failed: %s.",
361 hub, port->port_number, str_error(rc));
362 return rc;
363 }
364 /* Wait until reset completes. */
365 fibril_mutex_lock(&port->mutex);
366 port->reset_status = IN_RESET;
367 while (port->reset_status == IN_RESET)
368 fibril_condvar_wait(&port->reset_cv, &port->mutex);
369 rc = port->reset_status == RESET_OK ? EOK : ESTALL;
370 fibril_mutex_unlock(&port->mutex);
371 return rc;
372 } else {
373 return usb_hub_port_clear_feature(port,
374 USB_HUB_FEATURE_PORT_ENABLE);
375 }
376}
377
378/** Fibril for adding a new device.
379 *
380 * Separate fibril is needed because the port reset completion is announced
381 * via interrupt pipe and thus we cannot block here.
382 *
383 * @param arg Pointer to struct add_device_phase1.
384 * @return 0 Always.
385 */
386int add_device_phase1_worker_fibril(void *arg)
387{
388 struct add_device_phase1 *params = arg;
389 assert(params);
390
391 int ret = EOK;
392 usb_hub_dev_t *hub = params->hub;
393 usb_hub_port_t *port = params->port;
394 const usb_speed_t speed = params->speed;
395 free(arg);
396
397 usb_log_debug("(%p-%u): New device sequence.", hub, 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 = usbhc_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 (usbhc_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 = usbhc_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 = usbhc_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 (usbhc_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.