source: mainline/uspace/drv/usbhub/ports.c@ e78660f

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

hub driver: fix for endless polling

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