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

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

usbhub: Move feature set/clear to ports.h, refactor reset sequence.

  • Property mode set to 100644
File size: 13.0 KB
Line 
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"
46#include "utils.h"
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
56static void usb_hub_removed_device(usb_hub_info_t *hub, size_t port);
57static void usb_hub_port_reset_completed(const usb_hub_info_t *hub,
58 size_t port, usb_port_status_t status);
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 * Clear feature on hub port.
68 *
69 * @param hc Host controller telephone
70 * @param address Hub address
71 * @param port_index Port
72 * @param feature Feature selector
73 * @return Operation result
74 */
75int usb_hub_clear_port_feature(usb_pipe_t *pipe,
76 int port_index, usb_hub_class_feature_t feature)
77{
78 usb_device_request_setup_packet_t clear_request = {
79 .request_type = USB_HUB_REQ_TYPE_CLEAR_PORT_FEATURE,
80 .request = USB_DEVREQ_CLEAR_FEATURE,
81 .value = feature,
82 .index = port_index,
83 .length = 0,
84 };
85 return usb_pipe_control_write(pipe, &clear_request,
86 sizeof(clear_request), NULL, 0);
87}
88/*----------------------------------------------------------------------------*/
89/**
90 * Clear feature on hub port.
91 *
92 * @param hc Host controller telephone
93 * @param address Hub address
94 * @param port_index Port
95 * @param feature Feature selector
96 * @return Operation result
97 */
98int usb_hub_set_port_feature(usb_pipe_t *pipe,
99 int port_index, usb_hub_class_feature_t feature)
100{
101
102 usb_device_request_setup_packet_t clear_request = {
103 .request_type = USB_HUB_REQ_TYPE_SET_PORT_FEATURE,
104 .request = USB_DEVREQ_SET_FEATURE,
105 .index = port_index,
106 .value = feature,
107 .length = 0,
108 };
109 return usb_pipe_control_write(pipe, &clear_request,
110 sizeof(clear_request), NULL, 0);
111}
112
113/**
114 * Process interrupts on given hub port
115 *
116 * Accepts connection, over current and port reset change.
117 * @param hub hub representation
118 * @param port port number, starting from 1
119 */
120void usb_hub_process_port_interrupt(usb_hub_info_t *hub, size_t port)
121{
122 usb_log_debug("Interrupt at port %zu\n", port);
123
124 usb_port_status_t status;
125 const int opResult =
126 get_port_status(&hub->usb_device->ctrl_pipe, port, &status);
127 if (opResult != EOK) {
128 usb_log_error("Failed to get port %zu status: %s.\n",
129 port, str_error(opResult));
130 return;
131 }
132
133 /* Connection change */
134 if (status & USB_HUB_PORT_C_STATUS_CONNECTION) {
135 const bool device_connected =
136 (status & USB_HUB_PORT_STATUS_CONNECTION) != 0;
137 usb_log_debug("Connection change on port %zu: device %s.\n",
138 port, device_connected ? "attached" : "removed");
139 /* ACK the change */
140 const int opResult =
141 usb_hub_clear_port_feature(hub->control_pipe,
142 port, USB_HUB_FEATURE_C_PORT_CONNECTION);
143 if (opResult != EOK) {
144 usb_log_warning("Failed to clear "
145 "port-change-connection flag: %s.\n",
146 str_error(opResult));
147 }
148
149 if (device_connected) {
150 const int opResult = create_add_device_fibril(hub, port,
151 usb_port_speed(status));
152 if (opResult != EOK) {
153 usb_log_error(
154 "Cannot handle change on port %zu: %s.\n",
155 port, str_error(opResult));
156 }
157 } else {
158 usb_hub_removed_device(hub, port);
159 }
160 }
161
162 /* Enable change, ports are automatically disabled on errors. */
163 if (status & USB_HUB_PORT_C_STATUS_ENABLED) {
164 // TODO: Remove device that was connected
165 // TODO: Clear feature C_PORT_ENABLE
166
167 }
168
169 /* Suspend change */
170 if (status & USB_HUB_PORT_C_STATUS_SUSPEND) {
171 usb_log_error("Port %zu went to suspend state, this should"
172 "NOT happen as we do not support suspend state!", port);
173 // TODO: Clear feature C_PORT_SUSPEND
174 }
175
176 /* Over current */
177 if (status & USB_HUB_PORT_C_STATUS_OC) {
178 /* According to the USB specs:
179 * 11.13.5 Over-current Reporting and Recovery
180 * Hub device is responsible for putting port in power off
181 * mode. USB system software is responsible for powering port
182 * back on when the over-curent condition is gone */
183 if (!(status & ~USB_HUB_PORT_STATUS_OC)) {
184 // TODO: Power port on, this will cause connect
185 // change and device initialization.
186 }
187 // TODO: Ack over-power change.
188 }
189
190 /* Port reset, set on port reset complete. */
191 if (status & USB_HUB_PORT_C_STATUS_RESET) {
192 usb_hub_port_reset_completed(hub, port, status);
193 }
194
195 usb_log_debug("Port %zu status 0x%08" PRIx32 "\n", port, status);
196}
197
198/**
199 * routine called when a device on port has been removed
200 *
201 * If the device on port had default address, it releases default address.
202 * Otherwise does not do anything, because DDF does not allow to remove device
203 * from it`s device tree.
204 * @param hub hub representation
205 * @param port port number, starting from 1
206 */
207static void usb_hub_removed_device(usb_hub_info_t *hub, size_t port)
208{
209 /** \TODO remove device from device manager - not yet implemented in
210 * devide manager
211 */
212 usb_hub_port_t *the_port = hub->ports + port;
213
214 fibril_mutex_lock(&hub->port_mutex);
215
216 if (the_port->attached_device.address >= 0) {
217 usb_log_warning("Device unplug on `%s' (port %zu): " \
218 "not implemented.\n", hub->usb_device->ddf_dev->name,
219 (size_t) port);
220 the_port->attached_device.address = -1;
221 the_port->attached_device.handle = 0;
222 } else {
223 usb_log_warning("Device removed before being registered.\n");
224
225 /*
226 * Device was removed before port reset completed.
227 * We will announce a failed port reset to unblock the
228 * port reset callback from new device wrapper.
229 */
230 fibril_mutex_lock(&the_port->reset_mutex);
231 the_port->reset_completed = true;
232 the_port->reset_okay = false;
233 fibril_condvar_broadcast(&the_port->reset_cv);
234 fibril_mutex_unlock(&the_port->reset_mutex);
235 }
236
237 fibril_mutex_unlock(&hub->port_mutex);
238}
239
240/**
241 * Process port reset change
242 *
243 * After this change port should be enabled, unless some problem occured.
244 * This functions triggers second phase of enabling new device.
245 * @param hub
246 * @param port
247 * @param status
248 */
249static void usb_hub_port_reset_completed(const usb_hub_info_t *hub,
250 size_t port, usb_port_status_t status)
251{
252 usb_hub_port_t *the_port = hub->ports + port;
253 fibril_mutex_lock(&the_port->reset_mutex);
254 /* Finalize device adding. */
255 the_port->reset_completed = true;
256 the_port->reset_okay = (status & USB_HUB_PORT_STATUS_ENABLED) != 0;
257
258 if (the_port->reset_okay) {
259 usb_log_debug("Port %zu reset complete.\n", port);
260 } else {
261 usb_log_warning(
262 "Port %zu reset complete but port not enabled.\n", port);
263 }
264 fibril_condvar_broadcast(&the_port->reset_cv);
265 fibril_mutex_unlock(&the_port->reset_mutex);
266
267 /* Clear the port reset change. */
268 int rc = usb_hub_clear_port_feature(hub->control_pipe,
269 port, USB_HUB_FEATURE_C_PORT_RESET);
270 if (rc != EOK) {
271 usb_log_error("Failed to clear port %d reset feature: %s.\n",
272 port, str_error(rc));
273 }
274}
275/*----------------------------------------------------------------------------*/
276/** Retrieve port status.
277 *
278 * @param[in] ctrl_pipe Control pipe to use.
279 * @param[in] port Port number (starting at 1).
280 * @param[out] status Where to store the port status.
281 * @return Error code.
282 */
283static int get_port_status(usb_pipe_t *ctrl_pipe, size_t port,
284 usb_port_status_t *status)
285{
286 size_t recv_size;
287 usb_port_status_t status_tmp;
288 /* USB hub specific GET_PORT_STATUS request. See USB Spec 11.16.2.6
289 * Generic GET_STATUS request cannot be used because of the difference
290 * in status data size (2B vs. 4B)*/
291 const usb_device_request_setup_packet_t request = {
292 .request_type = USB_HUB_REQ_TYPE_GET_PORT_STATUS,
293 .request = USB_HUB_REQUEST_GET_STATUS,
294 .value = 0,
295 .index = port,
296 .length = sizeof(usb_port_status_t),
297 };
298
299 const int rc = usb_pipe_control_read(ctrl_pipe,
300 &request, sizeof(usb_device_request_setup_packet_t),
301 &status_tmp, sizeof(status_tmp), &recv_size);
302 if (rc != EOK) {
303 return rc;
304 }
305
306 if (recv_size != sizeof (status_tmp)) {
307 return ELIMIT;
308 }
309
310 if (status != NULL) {
311 *status = status_tmp;
312 }
313
314 return EOK;
315}
316/*----------------------------------------------------------------------------*/
317/** Callback for enabling a specific port.
318 *
319 * We wait on a CV until port is reseted.
320 * That is announced via change on interrupt pipe.
321 *
322 * @param port_no Port number (starting at 1).
323 * @param arg Custom argument, points to @c usb_hub_info_t.
324 * @return Error code.
325 */
326static int enable_port_callback(int port_no, void *arg)
327{
328 usb_hub_info_t *hub = arg;
329 assert(hub);
330 usb_hub_port_t *my_port = hub->ports + port_no;
331 const int rc = usb_hub_set_port_feature(hub->control_pipe,
332 port_no, USB_HUB_FEATURE_PORT_RESET);
333 if (rc != EOK) {
334 usb_log_warning("Port reset failed: %s.\n", str_error(rc));
335 return rc;
336 }
337
338 /*
339 * Wait until reset completes.
340 */
341 fibril_mutex_lock(&my_port->reset_mutex);
342 while (!my_port->reset_completed) {
343 fibril_condvar_wait(&my_port->reset_cv, &my_port->reset_mutex);
344 }
345 fibril_mutex_unlock(&my_port->reset_mutex);
346
347 if (my_port->reset_okay) {
348 return EOK;
349 } else {
350 return ESTALL;
351 }
352}
353
354/** Fibril for adding a new device.
355 *
356 * Separate fibril is needed because the port reset completion is announced
357 * via interrupt pipe and thus we cannot block here.
358 *
359 * @param arg Pointer to struct add_device_phase1.
360 * @return 0 Always.
361 */
362static int add_device_phase1_worker_fibril(void *arg)
363{
364 struct add_device_phase1 *data = arg;
365 assert(data);
366
367 usb_address_t new_address;
368 devman_handle_t child_handle;
369
370 const int rc = usb_hc_new_device_wrapper(data->hub->usb_device->ddf_dev,
371 &data->hub->connection, data->speed,
372 enable_port_callback, (int) data->port, data->hub,
373 &new_address, &child_handle,
374 NULL, NULL, NULL);
375
376 if (rc != EOK) {
377 usb_log_error("Failed registering device on port %zu: %s.\n",
378 data->port, str_error(rc));
379 goto leave;
380 }
381
382 fibril_mutex_lock(&data->hub->port_mutex);
383 data->hub->ports[data->port].attached_device.handle = child_handle;
384 data->hub->ports[data->port].attached_device.address = new_address;
385 fibril_mutex_unlock(&data->hub->port_mutex);
386
387 usb_log_info("Detected new device on `%s' (port %zu), "
388 "address %d (handle %" PRIun ").\n",
389 data->hub->usb_device->ddf_dev->name, data->port,
390 new_address, child_handle);
391
392leave:
393 free(arg);
394
395 fibril_mutex_lock(&data->hub->pending_ops_mutex);
396 assert(data->hub->pending_ops_count > 0);
397 data->hub->pending_ops_count--;
398 fibril_condvar_signal(&data->hub->pending_ops_cv);
399 fibril_mutex_unlock(&data->hub->pending_ops_mutex);
400
401
402 return EOK;
403}
404
405/** Start device adding when connection change is detected.
406 *
407 * This fires a new fibril to complete the device addition.
408 *
409 * @param hub Hub where the change occured.
410 * @param port Port index (starting at 1).
411 * @param speed Speed of the device.
412 * @return Error code.
413 */
414static int create_add_device_fibril(usb_hub_info_t *hub, size_t port,
415 usb_speed_t speed)
416{
417 struct add_device_phase1 *data
418 = malloc(sizeof (struct add_device_phase1));
419 if (data == NULL) {
420 return ENOMEM;
421 }
422 data->hub = hub;
423 data->port = port;
424 data->speed = speed;
425
426 usb_hub_port_t *the_port = hub->ports + port;
427
428 fibril_mutex_lock(&the_port->reset_mutex);
429 the_port->reset_completed = false;
430 fibril_mutex_unlock(&the_port->reset_mutex);
431
432 fid_t fibril = fibril_create(add_device_phase1_worker_fibril, data);
433 if (fibril == 0) {
434 free(data);
435 return ENOMEM;
436 }
437 fibril_mutex_lock(&hub->pending_ops_mutex);
438 hub->pending_ops_count++;
439 fibril_mutex_unlock(&hub->pending_ops_mutex);
440 fibril_add_ready(fibril);
441
442 return EOK;
443}
444
445/**
446 * @}
447 */
Note: See TracBrowser for help on using the repository browser.