source: mainline/uspace/drv/usbhub/ports.c@ 98caf49

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 98caf49 was 4708657, checked in by Matus Dekanek <smekideki@…>, 14 years ago

removed global hub powering (not needed and actually errorneous)

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