source: mainline/uspace/lib/usb/src/port.c@ 3e6ff9a

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

usb port: skip the timeout on error

  • Property mode set to 100644
File size: 6.6 KB
Line 
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
43 * a simple interface to announce events and leave the fibril management on the
44 * library.
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) {
104 usb_log_warning("a connected event come for port that is not disabled.");
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
129void usb_port_enabled(usb_port_t *port)
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
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
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:
187 fork_remove_worker(port, handler);
188 break;
189
190 case PORT_CONNECTING:
191 port->state = PORT_ERROR;
192 fibril_condvar_broadcast(&port->enabled_cv);
193 /* fallthrough */
194 case PORT_ERROR:
195 fibril_condvar_wait(&port->finished_cv, &port->guard);
196 /* fallthrough */
197 case PORT_DISCONNECTING:
198 case PORT_DISABLED:
199 break;
200 }
201
202 fibril_mutex_unlock(&port->guard);
203}
204
205void usb_port_fini(usb_port_t *port)
206{
207 assert(port);
208
209 fibril_mutex_lock(&port->guard);
210 switch (port->state) {
211 case PORT_ENUMERATED:
212 /*
213 * We shall inform the HC that the device is gone.
214 * However, we can't wait for it, because if the device is hub,
215 * it would have to use the same IPC handling fibril as we do.
216 * But we cannot even defer it to another fibril, because then
217 * the HC would assume our driver didn't cleanup properly, and
218 * will remove those devices by himself.
219 *
220 * So the solutions seems to be to behave like a bad driver and
221 * leave the work for HC.
222 */
223 port->state = PORT_DISABLED;
224 /* fallthrough */
225 case PORT_DISABLED:
226 break;
227
228 /* We first have to stop the fibril in progress. */
229 case PORT_CONNECTING:
230 port->state = PORT_ERROR;
231 fibril_condvar_broadcast(&port->enabled_cv);
232 /* fallthrough */
233 case PORT_ERROR:
234 case PORT_DISCONNECTING:
235 fibril_condvar_wait(&port->finished_cv, &port->guard);
236 break;
237 }
238 fibril_mutex_unlock(&port->guard);
239}
240
241int usb_port_condvar_wait_timeout(usb_port_t *port, fibril_condvar_t *cv, suseconds_t timeout)
242{
243 assert(port);
244 assert(port->state == PORT_CONNECTING);
245 assert(fibril_mutex_is_locked(&port->guard));
246
247 if (fibril_condvar_wait_timeout(cv, &port->guard, timeout))
248 return ETIMEOUT;
249
250 return port->state == PORT_CONNECTING ? EOK : EINTR;
251}
252
253/**
254 * @}
255 */
Note: See TracBrowser for help on using the repository browser.