source: mainline/uspace/drv/bus/usb/uhcirh/port.c@ ffa96c2

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since ffa96c2 was ffa96c2, checked in by Jiri Svoboda <jiri@…>, 11 years ago

Convert libusbdev away from DDF_DATA_IMPLANT.

  • Property mode set to 100644
File size: 11.2 KB
Line 
1/*
2 * Copyright (c) 2011 Jan Vesely
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/** @addtogroup drvusbuhcirh
29 * @{
30 */
31/** @file
32 * @brief UHCI root hub port routines
33 */
34#include <ddi.h>
35#include <fibril_synch.h> /* async_usleep */
36#include <errno.h>
37#include <str_error.h>
38#include <async.h>
39
40#include <usb/usb.h> /* usb_address_t */
41#include <usb/debug.h>
42
43#include "port.h"
44
45#define MAX_ERROR_COUNT 5
46
47static int uhci_port_check(void *port);
48static int uhci_port_reset_enable(void *arg);
49static int uhci_port_new_device(uhci_port_t *port, usb_speed_t speed);
50static int uhci_port_remove_device(uhci_port_t *port);
51static int uhci_port_set_enabled(uhci_port_t *port, bool enabled);
52static void uhci_port_print_status(
53 uhci_port_t *port, const port_status_t value);
54
55/** Register reading helper function.
56 *
57 * @param[in] port Structure to use.
58 * @return Error code. (Always EOK)
59 */
60static inline port_status_t uhci_port_read_status(uhci_port_t *port)
61{
62 assert(port);
63 return pio_read_16(port->address);
64}
65
66/** Register writing helper function.
67 *
68 * @param[in] port Structure to use.
69 * @param[in] val New register value.
70 * @return Error code. (Always EOK)
71 */
72static inline void uhci_port_write_status(uhci_port_t *port, port_status_t val)
73{
74 assert(port);
75 pio_write_16(port->address, val);
76}
77
78/** Initialize UHCI root hub port instance.
79 *
80 * @param[in] port Memory structure to use.
81 * @param[in] address Address of I/O register.
82 * @param[in] number Port number.
83 * @param[in] usec Polling interval.
84 * @param[in] rh Pointer to ddf instance of the root hub driver.
85 * @return Error code.
86 *
87 * Creates and starts the polling fibril.
88 */
89int uhci_port_init(uhci_port_t *port,
90 port_status_t *address, unsigned number, unsigned usec, ddf_dev_t *rh)
91{
92 assert(port);
93 char *id_string;
94 asprintf(&id_string, "Port (%p - %u)", port, number);
95 if (id_string == NULL) {
96 return ENOMEM;
97 }
98
99 port->id_string = id_string;
100 port->address = address;
101 port->number = number;
102 port->wait_period_usec = usec;
103 port->attached_device.fun = NULL;
104 port->attached_device.address = -1;
105 port->rh = rh;
106
107 int ret =
108 usb_hc_connection_initialize_from_device(&port->hc_connection, rh);
109 if (ret != EOK) {
110 usb_log_error("%s: failed to initialize connection to HC.",
111 port->id_string);
112 free(id_string);
113 return ret;
114 }
115
116 port->checker = fibril_create(uhci_port_check, port);
117 if (port->checker == 0) {
118 usb_log_error("%s: failed to create polling fibril.",
119 port->id_string);
120 free(id_string);
121 return ENOMEM;
122 }
123
124 fibril_add_ready(port->checker);
125 usb_log_debug("%s: Started polling fibril (%" PRIun ").\n",
126 port->id_string, port->checker);
127 return EOK;
128}
129
130/** Cleanup UHCI root hub port instance.
131 *
132 * @param[in] port Memory structure to use.
133 *
134 * Stops the polling fibril.
135 */
136void uhci_port_fini(uhci_port_t *port)
137{
138 assert(port);
139 free(port->id_string);
140 // TODO: Kill fibril here
141 return;
142}
143
144/** Periodically checks port status and reports new devices.
145 *
146 * @param[in] port Port structure to use.
147 * @return Error code.
148 */
149int uhci_port_check(void *port)
150{
151 uhci_port_t *instance = port;
152 int rc;
153 assert(instance);
154
155 unsigned allowed_failures = MAX_ERROR_COUNT;
156
157 while (1) {
158 async_usleep(instance->wait_period_usec);
159
160 /* Read register value */
161 const port_status_t port_status =
162 uhci_port_read_status(instance);
163
164 /* Print the value if it's interesting */
165 if (port_status & ~STATUS_ALWAYS_ONE)
166 uhci_port_print_status(instance, port_status);
167
168 if ((port_status & STATUS_CONNECTED_CHANGED) == 0)
169 continue;
170
171 usb_log_debug("%s: Connected change detected: %x.\n",
172 instance->id_string, port_status);
173
174 rc = usb_hc_connection_open(&instance->hc_connection);
175 if (rc != EOK) {
176 usb_log_error("%s: Failed to connect to HC %s.\n",
177 instance->id_string, str_error(rc));
178 if (!(allowed_failures-- > 0))
179 goto fatal_error;
180 continue;
181 }
182
183 /* Remove any old device */
184 if (instance->attached_device.fun) {
185 uhci_port_remove_device(instance);
186 }
187
188 if ((port_status & STATUS_CONNECTED) != 0) {
189 /* New device, this will take care of WC bits */
190 const usb_speed_t speed =
191 ((port_status & STATUS_LOW_SPEED) != 0) ?
192 USB_SPEED_LOW : USB_SPEED_FULL;
193 uhci_port_new_device(instance, speed);
194 } else {
195 /* Write one to WC bits, to ack changes */
196 uhci_port_write_status(instance, port_status);
197 usb_log_debug("%s: status change ACK.\n",
198 instance->id_string);
199 }
200
201 rc = usb_hc_connection_close(&instance->hc_connection);
202 if (rc != EOK) {
203 usb_log_error("%s: Failed to disconnect from HC %s.\n",
204 instance->id_string, str_error(rc));
205 if (!(allowed_failures-- > 0))
206 goto fatal_error;
207 continue;
208 }
209 }
210
211 return EOK;
212
213fatal_error:
214 usb_log_fatal("Maximum number of failures reached, bailing out.\n");
215 return rc;
216}
217
218/** Callback for enabling port during adding a new device.
219 *
220 * @param portno Port number (unused).
221 * @param arg Pointer to uhci_port_t of port with the new device.
222 * @return Error code.
223 *
224 * Resets and enables the ub port.
225 */
226int uhci_port_reset_enable(void *arg)
227{
228 uhci_port_t *port = arg;
229 assert(port);
230
231 usb_log_debug2("%s: new_device_enable_port.\n", port->id_string);
232
233 /*
234 * Resets from root ports should be nominally 50ms (USB spec 7.1.7.3)
235 */
236 {
237 usb_log_debug("%s: Reset Signal start.\n", port->id_string);
238 port_status_t port_status = uhci_port_read_status(port);
239 port_status |= STATUS_IN_RESET;
240 uhci_port_write_status(port, port_status);
241 async_usleep(50000);
242 port_status = uhci_port_read_status(port);
243 port_status &= ~STATUS_IN_RESET;
244 uhci_port_write_status(port, port_status);
245 while (uhci_port_read_status(port) & STATUS_IN_RESET);
246 }
247 /* PIO delay, should not be longer than 3ms as the device might
248 * enter suspend state. */
249 udelay(10);
250 /* Enable the port. */
251 uhci_port_set_enabled(port, true);
252 return EOK;
253}
254
255/** Initialize and report connected device.
256 *
257 * @param[in] port Port structure to use.
258 * @param[in] speed Detected speed.
259 * @return Error code.
260 *
261 * Uses libUSB function to do the actual work.
262 */
263int uhci_port_new_device(uhci_port_t *port, usb_speed_t speed)
264{
265 assert(port);
266
267 usb_log_debug("%s: Detected new device.\n", port->id_string);
268
269 int ret, count = MAX_ERROR_COUNT;
270 do {
271 port->attached_device.fun = ddf_fun_create(port->rh, fun_inner,
272 NULL);
273 if (port->attached_device.fun == NULL) {
274 ret = ENOMEM;
275 continue;
276 }
277
278 ret = usb_hc_new_device_wrapper(port->rh,
279 port->attached_device.fun,
280 &port->hc_connection,
281 speed, uhci_port_reset_enable, port,
282 &port->attached_device.address, NULL);
283
284 if (ret != EOK) {
285 ddf_fun_destroy(port->attached_device.fun);
286 port->attached_device.fun = NULL;
287 }
288
289 } while (ret != EOK && count-- > 0);
290
291 if (ret != EOK) {
292 usb_log_error("%s: Failed(%d) to add device: %s.\n",
293 port->id_string, ret, str_error(ret));
294 uhci_port_set_enabled(port, false);
295 return ret;
296 }
297
298 usb_log_info("%s: New device, address %d (handle %" PRIun ").\n",
299 port->id_string, port->attached_device.address,
300 ddf_fun_get_handle(port->attached_device.fun));
301 return EOK;
302}
303
304/** Remove device.
305 *
306 * @param[in] port Port instance to use.
307 * @return Error code.
308 */
309int uhci_port_remove_device(uhci_port_t *port)
310{
311 assert(port);
312 /* There is nothing to remove. */
313 if (port->attached_device.fun == NULL) {
314 usb_log_warning("%s: Removed a ghost device.\n",
315 port->id_string);
316 assert(port->attached_device.address == -1);
317 return EOK;
318 }
319
320 usb_log_debug("%s: Removing device.\n", port->id_string);
321
322 /* Stop driver first */
323 int ret = ddf_fun_unbind(port->attached_device.fun);
324 if (ret != EOK) {
325 usb_log_error("%s: Failed to remove child function: %s.\n",
326 port->id_string, str_error(ret));
327 return ret;
328 }
329 ddf_fun_destroy(port->attached_device.fun);
330 port->attached_device.fun = NULL;
331
332 /* Driver stopped, free used address */
333 ret = usb_hub_unregister_device(&port->hc_connection,
334 &port->attached_device);
335 if (ret != EOK) {
336 usb_log_error("%s: Failed to unregister address of removed "
337 "device: %s.\n", port->id_string, str_error(ret));
338 return ret;
339 }
340 port->attached_device.address = -1;
341
342 usb_log_info("%s: Removed attached device.\n", port->id_string);
343 return EOK;
344}
345
346/** Enable or disable root hub port.
347 *
348 * @param[in] port Port structure to use.
349 * @param[in] enabled Port status to set.
350 * @return Error code. (Always EOK)
351 */
352int uhci_port_set_enabled(uhci_port_t *port, bool enabled)
353{
354 assert(port);
355
356 /* Read register value */
357 port_status_t port_status = uhci_port_read_status(port);
358
359 /* Set enabled bit */
360 if (enabled) {
361 port_status |= STATUS_ENABLED;
362 } else {
363 port_status &= ~STATUS_ENABLED;
364 }
365
366 /* Write new value. */
367 uhci_port_write_status(port, port_status);
368
369 /* Wait for port to become enabled */
370 do {
371 port_status = uhci_port_read_status(port);
372 } while ((port_status & STATUS_CONNECTED) &&
373 !(port_status & STATUS_ENABLED));
374
375 usb_log_debug("%s: %sabled port.\n",
376 port->id_string, enabled ? "En" : "Dis");
377 return EOK;
378}
379
380/** Print the port status value in a human friendly way
381 *
382 * @param[in] port Port structure to use.
383 * @param[in] value Port register value to print.
384 * @return Error code. (Always EOK)
385 */
386void uhci_port_print_status(uhci_port_t *port, const port_status_t value)
387{
388 assert(port);
389 usb_log_debug2("%s Port status(%#x):%s%s%s%s%s%s%s%s%s%s%s.\n",
390 port->id_string, value,
391 (value & STATUS_SUSPEND) ? " SUSPENDED," : "",
392 (value & STATUS_RESUME) ? " IN RESUME," : "",
393 (value & STATUS_IN_RESET) ? " IN RESET," : "",
394 (value & STATUS_LINE_D_MINUS) ? " VD-," : "",
395 (value & STATUS_LINE_D_PLUS) ? " VD+," : "",
396 (value & STATUS_LOW_SPEED) ? " LOWSPEED," : "",
397 (value & STATUS_ENABLED_CHANGED) ? " ENABLED-CHANGE," : "",
398 (value & STATUS_ENABLED) ? " ENABLED," : "",
399 (value & STATUS_CONNECTED_CHANGED) ? " CONNECTED-CHANGE," : "",
400 (value & STATUS_CONNECTED) ? " CONNECTED," : "",
401 (value & STATUS_ALWAYS_ONE) ? " ALWAYS ONE" : " ERR: NO ALWAYS ONE"
402 );
403}
404/**
405 * @}
406 */
Note: See TracBrowser for help on using the repository browser.