source: mainline/uspace/lib/usb/src/port.c@ 7138a78b

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 7138a78b was 7138a78b, checked in by Ondřej Hlavatý <aearsis@…>, 8 years ago

usb: do not fail just because of wrong assert

If the events come fast enough, the coditions may change between
signalling the condvar and waking up from waiting. As this function is
called to destroy the device on a port, it is sufficient to know if that
particular device is gone - not that there's no device just now.

  • Property mode set to 100644
File size: 6.5 KB
RevLine 
[94f8c363]1/*
2 * Copyright (c) 2018 HelenOS xHCI team
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 libusb
30 * @{
31 */
32/** @file
33 * An USB hub port state machine.
34 *
35 * This helper structure solves a repeated problem in USB world: management of
36 * USB ports. A port is an object which receives events (connect, disconnect,
37 * reset) which are to be handled in an asynchronous way. The tricky part is
38 * that response to events has to wait for different events - the most notable
39 * being USB 2 port requiring port reset to be enabled. This problem is solved
40 * by launching separate fibril for taking the port up.
41 *
42 * This subsystem abstracts the rather complicated state machine, and offers
[0f79283b]43 * a simple interface to announce events and leave the fibril management on the
44 * library.
[94f8c363]45 */
46
47#include <stdlib.h>
48#include <fibril.h>
49#include <assert.h>
50#include <usb/debug.h>
51
52#include <usb/port.h>
53
54void usb_port_init(usb_port_t *port)
55{
56 fibril_mutex_initialize(&port->guard);
57 fibril_condvar_initialize(&port->finished_cv);
58 fibril_condvar_initialize(&port->enabled_cv);
59}
60
61struct enumerate_worker_args {
62 usb_port_t *port;
63 usb_port_enumerate_t handler;
64};
65
66static int enumerate_worker(void *arg)
67{
68 struct enumerate_worker_args * const args = arg;
69 usb_port_t *port = args->port;
70 usb_port_enumerate_t handler = args->handler;
71 free(args);
72
73 fibril_mutex_lock(&port->guard);
74
75 if (port->state == PORT_ERROR) {
76 /*
77 * The device was removed faster than this fibril acquired the
78 * mutex.
79 */
80 port->state = PORT_DISABLED;
81 goto out;
82 }
83
84 assert(port->state == PORT_CONNECTING);
85
86 port->state = handler(port)
87 ? PORT_DISABLED
88 : PORT_ENUMERATED;
89
90out:
91 fibril_condvar_broadcast(&port->finished_cv);
92 fibril_mutex_unlock(&port->guard);
93 return EOK; // This is a fibril worker. No one will read the value.
94}
95
96int usb_port_connected(usb_port_t *port, usb_port_enumerate_t handler)
97{
98 assert(port);
99 int ret = ENOMEM;
100
101 fibril_mutex_lock(&port->guard);
102
103 if (port->state != PORT_DISABLED) {
[5bccec3]104 usb_log_warning("a connected event come for port that is not disabled.");
[94f8c363]105 ret = EINVAL;
106 goto out;
107 }
108
109 struct enumerate_worker_args *args = malloc(sizeof(*args));
110 if (!args)
111 goto out;
112
113 fid_t fibril = fibril_create(&enumerate_worker, args);
114 if (!fibril) {
115 free(args);
116 goto out;
117 }
118
119 args->port = port;
120 args->handler = handler;
121
122 port->state = PORT_CONNECTING;
123 fibril_add_ready(fibril);
124out:
125 fibril_mutex_unlock(&port->guard);
126 return ret;
127}
128
[0f79283b]129void usb_port_enabled(usb_port_t *port)
[94f8c363]130{
131 assert(port);
132
133 fibril_mutex_lock(&port->guard);
134 fibril_condvar_broadcast(&port->enabled_cv);
135 fibril_mutex_unlock(&port->guard);
136}
137
[5bccec3]138struct remove_worker_args {
139 usb_port_t *port;
140 usb_port_remove_t handler;
141};
142
143static int remove_worker(void *arg)
144{
145 struct remove_worker_args * const args = arg;
146 usb_port_t *port = args->port;
147 usb_port_remove_t handler = args->handler;
148 free(args);
149
150 fibril_mutex_lock(&port->guard);
151 assert(port->state == PORT_DISCONNECTING);
152
153 handler(port);
154
155 port->state = PORT_DISABLED;
156 fibril_condvar_broadcast(&port->finished_cv);
157 fibril_mutex_unlock(&port->guard);
158 return EOK;
159}
160
161static void fork_remove_worker(usb_port_t *port, usb_port_remove_t handler)
162{
163 struct remove_worker_args *args = malloc(sizeof(*args));
164 if (!args)
165 return;
166
167 fid_t fibril = fibril_create(&remove_worker, args);
168 if (!fibril) {
169 free(args);
170 return;
171 }
172
173 args->port = port;
174 args->handler = handler;
175
176 port->state = PORT_DISCONNECTING;
177 fibril_add_ready(fibril);
178}
179
[94f8c363]180void usb_port_disabled(usb_port_t *port, usb_port_remove_t handler)
181{
182 assert(port);
183 fibril_mutex_lock(&port->guard);
184
185 switch (port->state) {
186 case PORT_ENUMERATED:
[5bccec3]187 fork_remove_worker(port, handler);
[94f8c363]188 break;
189
190 case PORT_CONNECTING:
191 port->state = PORT_ERROR;
192 /* fallthrough */
193 case PORT_ERROR:
194 fibril_condvar_wait(&port->finished_cv, &port->guard);
195 /* fallthrough */
[5bccec3]196 case PORT_DISCONNECTING:
[94f8c363]197 case PORT_DISABLED:
198 break;
199 }
200
201 fibril_mutex_unlock(&port->guard);
202}
203
204void usb_port_fini(usb_port_t *port)
205{
206 assert(port);
207
208 fibril_mutex_lock(&port->guard);
209 switch (port->state) {
210 case PORT_ENUMERATED:
211 /*
212 * We shall inform the HC that the device is gone.
213 * However, we can't wait for it, because if the device is hub,
214 * it would have to use the same IPC handling fibril as we do.
215 * But we cannot even defer it to another fibril, because then
216 * the HC would assume our driver didn't cleanup properly, and
217 * will remove those devices by himself.
218 *
219 * So the solutions seems to be to behave like a bad driver and
220 * leave the work for HC.
221 */
222 port->state = PORT_DISABLED;
223 /* fallthrough */
224 case PORT_DISABLED:
225 break;
226
227 /* We first have to stop the fibril in progress. */
228 case PORT_CONNECTING:
229 port->state = PORT_ERROR;
230 /* fallthrough */
231 case PORT_ERROR:
[5bccec3]232 case PORT_DISCONNECTING:
[94f8c363]233 fibril_condvar_wait(&port->finished_cv, &port->guard);
234 break;
235 }
236 fibril_mutex_unlock(&port->guard);
237}
238
239int usb_port_condvar_wait_timeout(usb_port_t *port, fibril_condvar_t *cv, suseconds_t timeout)
240{
241 assert(port);
242 assert(port->state == PORT_CONNECTING);
243 assert(fibril_mutex_is_locked(&port->guard));
244
245 if (fibril_condvar_wait_timeout(cv, &port->guard, timeout))
246 return ETIMEOUT;
247
248 return port->state == PORT_CONNECTING ? EOK : EINTR;
249}
250
251/**
252 * @}
253 */
Note: See TracBrowser for help on using the repository browser.