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

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

usb: Switch to using port number as device identifier.

Port number info will be needed for HS SPLIT transactions. Moreover,
it can be used to produce predictable, persistent device names.

Switch port_number from size_t to unsigned. There are max 256 ports so
even short would be enough.

  • Property mode set to 100644
File size: 13.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_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\n",
419 port->port_number);
420 if (port_enable(port, false) != EOK) {
421 usb_log_warning("Failed to disable port %u, NOT "
422 "releasing default address.\n", port->port_number);
423 } else {
424 if (usb_release_default_address(exch) != EOK)
425 usb_log_warning(
426 "Failed to release default address\n");
427 }
428 } else {
429 port->device_attached = true;
430 if (usb_release_default_address(exch) != EOK)
431 usb_log_warning("Failed to release default address\n");
432 }
433out:
434 usb_device_bus_exchange_end(exch);
435
436 fibril_mutex_lock(&hub->pending_ops_mutex);
437 assert(hub->pending_ops_count > 0);
438 --hub->pending_ops_count;
439 fibril_condvar_signal(&hub->pending_ops_cv);
440 fibril_mutex_unlock(&hub->pending_ops_mutex);
441
442 return ret;
443}
444
445/** Start device adding when connection change is detected.
446 *
447 * This fires a new fibril to complete the device addition.
448 *
449 * @param hub Hub where the change occured.
450 * @param port Port index (starting at 1).
451 * @param speed Speed of the device.
452 * @return Error code.
453 */
454static int create_add_device_fibril(usb_hub_port_t *port, usb_hub_dev_t *hub,
455 usb_speed_t speed)
456{
457 assert(hub);
458 assert(port);
459 struct add_device_phase1 *data
460 = malloc(sizeof(struct add_device_phase1));
461 if (data == NULL) {
462 return ENOMEM;
463 }
464 data->hub = hub;
465 data->port = port;
466 data->speed = speed;
467
468 fibril_mutex_lock(&port->mutex);
469 port->reset_completed = false;
470 fibril_mutex_unlock(&port->mutex);
471
472 fid_t fibril = fibril_create(add_device_phase1_worker_fibril, data);
473 if (fibril == 0) {
474 free(data);
475 return ENOMEM;
476 }
477 fibril_mutex_lock(&hub->pending_ops_mutex);
478 ++hub->pending_ops_count;
479 fibril_mutex_unlock(&hub->pending_ops_mutex);
480 fibril_add_ready(fibril);
481
482 return EOK;
483}
484
485/**
486 * @}
487 */
Note: See TracBrowser for help on using the repository browser.