source: mainline/uspace/drv/bus/usb/usbhub/ports.c@ d6e2938

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

usbhub: Refactor handling of port changes.

  • Property mode set to 100644
File size: 11.8 KB
RevLine 
[2ad98fd]1/*
2 * Copyright (c) 2011 Vojtech Horky
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup drvusbhub
30 * @{
31 */
32/** @file
33 * Hub ports functions.
34 */
35
36#include <bool.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 "ports.h"
45#include "usbhub.h"
[193da9d6]46#include "utils.h"
[2ad98fd]47#include "port_status.h"
48
49/** Information for fibril for device discovery. */
50struct add_device_phase1 {
51 usb_hub_info_t *hub;
52 size_t port;
53 usb_speed_t speed;
54};
55
[d6e2938]56static void usb_hub_removed_device(usb_hub_info_t *hub, size_t port);
[5c1a65e]57static void usb_hub_port_reset_completed(usb_hub_info_t *hub,
58 uint16_t port, uint32_t status);
[2ad98fd]59static int get_port_status(usb_pipe_t *ctrl_pipe, size_t port,
60 usb_port_status_t *status);
61static int enable_port_callback(int port_no, void *arg);
62static int add_device_phase1_worker_fibril(void *arg);
63static int create_add_device_fibril(usb_hub_info_t *hub, size_t port,
64 usb_speed_t speed);
65
66/**
67 * Process interrupts on given hub port
68 *
69 * Accepts connection, over current and port reset change.
70 * @param hub hub representation
71 * @param port port number, starting from 1
72 */
[d6e2938]73void usb_hub_process_port_interrupt(usb_hub_info_t *hub, size_t port)
[983e135]74{
[d6e2938]75 usb_log_debug("Interrupt at port %zu\n", port);
[2ad98fd]76
77 usb_port_status_t status;
[b3433a2]78 const int opResult =
79 get_port_status(&hub->usb_device->ctrl_pipe, port, &status);
[2ad98fd]80 if (opResult != EOK) {
81 usb_log_error("Failed to get port %zu status: %s.\n",
[d6e2938]82 port, str_error(opResult));
[2ad98fd]83 return;
84 }
[d6e2938]85
86 /* Connection change */
87 if (status & USB_HUB_PORT_C_STATUS_CONNECTION) {
88 const bool device_connected =
89 (status & USB_HUB_PORT_STATUS_CONNECTION) != 0;
90 usb_log_debug("Connection change on port %zu: device %s.\n",
91 port, device_connected ? "attached" : "removed");
92 /* ACK the change */
93 const int opResult =
94 usb_hub_clear_port_feature(hub->control_pipe,
95 port, USB_HUB_FEATURE_C_PORT_CONNECTION);
96 if (opResult != EOK) {
97 usb_log_warning("Failed to clear "
98 "port-change-connection flag: %s.\n",
99 str_error(opResult));
100 }
[2ad98fd]101
102 if (device_connected) {
[b3433a2]103 const int opResult = create_add_device_fibril(hub, port,
[2ad98fd]104 usb_port_speed(status));
105 if (opResult != EOK) {
106 usb_log_error(
107 "Cannot handle change on port %zu: %s.\n",
[d6e2938]108 port, str_error(opResult));
[2ad98fd]109 }
110 } else {
111 usb_hub_removed_device(hub, port);
112 }
113 }
[d6e2938]114
115 /* Enable change, ports are automatically disabled on errors. */
116 if (status & USB_HUB_PORT_C_STATUS_ENABLED) {
117 // TODO: Remove device that was connected
118 // TODO: Clear feature C_PORT_ENABLE
119
[2ad98fd]120 }
[d6e2938]121
122 /* Suspend change */
123 if (status & USB_HUB_PORT_C_STATUS_SUSPEND) {
124 usb_log_error("Port %zu went to suspend state, this should"
125 "NOT happen as we do not support suspend state!", port);
126 // TODO: Clear feature C_PORT_SUSPEND
[2ad98fd]127 }
[d6e2938]128
129 /* Over current */
130 if (status & USB_HUB_PORT_C_STATUS_OC) {
131 /* According to the USB specs:
132 * 11.13.5 Over-current Reporting and Recovery
133 * Hub device is responsible for putting port in power off
134 * mode. USB system software is responsible for powering port
135 * back on when the over-curent condition is gone */
136 if (!(status & ~USB_HUB_PORT_STATUS_OC)) {
137 // TODO: Power port on, this will cause connect
138 // change and device initialization.
[d0c060b]139 }
[d6e2938]140 // TODO: Ack over-power change.
[d0c060b]141 }
[d6e2938]142
143 /* Port reset, set on port reset complete. */
144 if (status & USB_HUB_PORT_C_STATUS_RESET) {
145 usb_hub_port_reset_completed(hub, port, status);
[192ba25]146 }
[d6e2938]147
148 usb_log_debug("Port %zu status 0x%08" PRIx32 "\n", port, status);
[2ad98fd]149}
150
151/**
152 * routine called when a device on port has been removed
153 *
154 * If the device on port had default address, it releases default address.
155 * Otherwise does not do anything, because DDF does not allow to remove device
156 * from it`s device tree.
157 * @param hub hub representation
158 * @param port port number, starting from 1
159 */
[d6e2938]160static void usb_hub_removed_device(usb_hub_info_t *hub, size_t port)
161{
[2ad98fd]162
163 /** \TODO remove device from device manager - not yet implemented in
164 * devide manager
165 */
166
167 //close address
[af6136d]168
169 usb_hub_port_t *the_port = hub->ports + port;
170
171 fibril_mutex_lock(&hub->port_mutex);
172
173 if (the_port->attached_device.address >= 0) {
174 usb_log_warning("Device unplug on `%s' (port %zu): " \
175 "not implemented.\n", hub->usb_device->ddf_dev->name,
176 (size_t) port);
177 the_port->attached_device.address = -1;
178 the_port->attached_device.handle = 0;
[2ad98fd]179 } else {
[46e078a]180 usb_log_warning("Device removed before being registered.\n");
181
182 /*
183 * Device was removed before port reset completed.
184 * We will announce a failed port reset to unblock the
185 * port reset callback from new device wrapper.
186 */
187 fibril_mutex_lock(&the_port->reset_mutex);
188 the_port->reset_completed = true;
189 the_port->reset_okay = false;
190 fibril_condvar_broadcast(&the_port->reset_cv);
191 fibril_mutex_unlock(&the_port->reset_mutex);
[2ad98fd]192 }
[af6136d]193
194 fibril_mutex_unlock(&hub->port_mutex);
[2ad98fd]195}
196
197/**
198 * Process port reset change
199 *
200 * After this change port should be enabled, unless some problem occured.
201 * This functions triggers second phase of enabling new device.
202 * @param hub
203 * @param port
204 * @param status
205 */
[5c1a65e]206static void usb_hub_port_reset_completed(usb_hub_info_t *hub,
[d6e2938]207 uint16_t port, uint32_t status)
208{
[4125b7d]209 usb_log_debug("Port %zu reset complete.\n", (size_t) port);
[2ad98fd]210 if (usb_port_is_status(status, USB_HUB_FEATURE_PORT_ENABLE)) {
211 /* Finalize device adding. */
212 usb_hub_port_t *the_port = hub->ports + port;
213 fibril_mutex_lock(&the_port->reset_mutex);
214 the_port->reset_completed = true;
[46e078a]215 the_port->reset_okay = true;
[2ad98fd]216 fibril_condvar_broadcast(&the_port->reset_cv);
217 fibril_mutex_unlock(&the_port->reset_mutex);
218 } else {
219 usb_log_warning(
220 "Port %zu reset complete but port not enabled.\n",
[4125b7d]221 (size_t) port);
[2ad98fd]222 }
[d0c060b]223 /* Clear the port reset change. */
224 int rc = usb_hub_clear_port_feature(hub->control_pipe,
225 port, USB_HUB_FEATURE_C_PORT_RESET);
226 if (rc != EOK) {
227 usb_log_error("Failed to clear port %d reset feature: %s.\n",
228 port, str_error(rc));
229 }
[2ad98fd]230}
231
232/** Retrieve port status.
233 *
234 * @param[in] ctrl_pipe Control pipe to use.
235 * @param[in] port Port number (starting at 1).
236 * @param[out] status Where to store the port status.
237 * @return Error code.
238 */
239static int get_port_status(usb_pipe_t *ctrl_pipe, size_t port,
[b3433a2]240 usb_port_status_t *status)
241{
[2ad98fd]242 size_t recv_size;
243 usb_port_status_t status_tmp;
[d6e2938]244 /* USB hub specific GET_PORT_STATUS request. See USB Spec 11.16.2.6
245 * Generic GET_STATUS request cannot be used because of the difference
246 * in status data size (2B vs. 4B)*/
[b3433a2]247 const usb_device_request_setup_packet_t request = {
248 .request_type = USB_HUB_REQ_TYPE_GET_PORT_STATUS,
249 .request = USB_HUB_REQUEST_GET_STATUS,
250 .value = 0,
251 .index = port,
252 .length = sizeof(usb_port_status_t),
253 };
254
255 const int rc = usb_pipe_control_read(ctrl_pipe,
256 &request, sizeof(usb_device_request_setup_packet_t),
257 &status_tmp, sizeof(status_tmp), &recv_size);
[2ad98fd]258 if (rc != EOK) {
259 return rc;
260 }
261
262 if (recv_size != sizeof (status_tmp)) {
263 return ELIMIT;
264 }
265
266 if (status != NULL) {
267 *status = status_tmp;
268 }
269
270 return EOK;
271}
[d6e2938]272/*----------------------------------------------------------------------------*/
[2ad98fd]273/** Callback for enabling a specific port.
274 *
275 * We wait on a CV until port is reseted.
276 * That is announced via change on interrupt pipe.
277 *
278 * @param port_no Port number (starting at 1).
279 * @param arg Custom argument, points to @c usb_hub_info_t.
280 * @return Error code.
281 */
[d6e2938]282static int enable_port_callback(int port_no, void *arg)
283{
[2ad98fd]284 usb_hub_info_t *hub = arg;
[d6e2938]285 assert(hub);
[2ad98fd]286 usb_hub_port_t *my_port = hub->ports + port_no;
287
[d6e2938]288 usb_device_request_setup_packet_t request;
[2ad98fd]289 usb_hub_set_reset_port_request(&request, port_no);
[d6e2938]290
291 const int rc = usb_pipe_control_write(hub->control_pipe,
[5c1a65e]292 &request, sizeof (request), NULL, 0);
[2ad98fd]293 if (rc != EOK) {
294 usb_log_warning("Port reset failed: %s.\n", str_error(rc));
295 return rc;
296 }
297
298 /*
299 * Wait until reset completes.
300 */
301 fibril_mutex_lock(&my_port->reset_mutex);
302 while (!my_port->reset_completed) {
303 fibril_condvar_wait(&my_port->reset_cv, &my_port->reset_mutex);
304 }
305 fibril_mutex_unlock(&my_port->reset_mutex);
306
[46e078a]307 if (my_port->reset_okay) {
308 return EOK;
309 } else {
310 return ESTALL;
311 }
[2ad98fd]312}
313
314/** Fibril for adding a new device.
315 *
316 * Separate fibril is needed because the port reset completion is announced
317 * via interrupt pipe and thus we cannot block here.
318 *
319 * @param arg Pointer to struct add_device_phase1.
320 * @return 0 Always.
321 */
[d6e2938]322static int add_device_phase1_worker_fibril(void *arg)
323{
324 struct add_device_phase1 *data = arg;
325 assert(data);
[2ad98fd]326
327 usb_address_t new_address;
328 devman_handle_t child_handle;
329
[d6e2938]330 const int rc = usb_hc_new_device_wrapper(data->hub->usb_device->ddf_dev,
[2ad98fd]331 &data->hub->connection, data->speed,
332 enable_port_callback, (int) data->port, data->hub,
333 &new_address, &child_handle,
334 NULL, NULL, NULL);
335
336 if (rc != EOK) {
337 usb_log_error("Failed registering device on port %zu: %s.\n",
338 data->port, str_error(rc));
339 goto leave;
340 }
341
[af6136d]342 fibril_mutex_lock(&data->hub->port_mutex);
[2ad98fd]343 data->hub->ports[data->port].attached_device.handle = child_handle;
344 data->hub->ports[data->port].attached_device.address = new_address;
[af6136d]345 fibril_mutex_unlock(&data->hub->port_mutex);
[2ad98fd]346
347 usb_log_info("Detected new device on `%s' (port %zu), "
348 "address %d (handle %" PRIun ").\n",
349 data->hub->usb_device->ddf_dev->name, data->port,
350 new_address, child_handle);
351
352leave:
353 free(arg);
354
[3fb5a3e]355 fibril_mutex_lock(&data->hub->pending_ops_mutex);
356 assert(data->hub->pending_ops_count > 0);
357 data->hub->pending_ops_count--;
358 fibril_condvar_signal(&data->hub->pending_ops_cv);
359 fibril_mutex_unlock(&data->hub->pending_ops_mutex);
360
361
[2ad98fd]362 return EOK;
363}
364
365/** Start device adding when connection change is detected.
366 *
367 * This fires a new fibril to complete the device addition.
368 *
369 * @param hub Hub where the change occured.
370 * @param port Port index (starting at 1).
371 * @param speed Speed of the device.
372 * @return Error code.
373 */
374static int create_add_device_fibril(usb_hub_info_t *hub, size_t port,
[d6e2938]375 usb_speed_t speed)
376{
[2ad98fd]377 struct add_device_phase1 *data
[5c1a65e]378 = malloc(sizeof (struct add_device_phase1));
[2ad98fd]379 if (data == NULL) {
380 return ENOMEM;
381 }
382 data->hub = hub;
383 data->port = port;
384 data->speed = speed;
385
386 usb_hub_port_t *the_port = hub->ports + port;
387
388 fibril_mutex_lock(&the_port->reset_mutex);
389 the_port->reset_completed = false;
390 fibril_mutex_unlock(&the_port->reset_mutex);
391
392 fid_t fibril = fibril_create(add_device_phase1_worker_fibril, data);
393 if (fibril == 0) {
394 free(data);
395 return ENOMEM;
396 }
[3fb5a3e]397 fibril_mutex_lock(&hub->pending_ops_mutex);
398 hub->pending_ops_count++;
399 fibril_mutex_unlock(&hub->pending_ops_mutex);
[2ad98fd]400 fibril_add_ready(fibril);
401
402 return EOK;
403}
404
405/**
406 * @}
407 */
Note: See TracBrowser for help on using the repository browser.