source: mainline/uspace/drv/usbhub/ports.c@ 4166fb1

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 4166fb1 was af6136d, checked in by Vojtech Horky <vojtechhorky@…>, 14 years ago

Guard hub ports with mutex

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