source: mainline/uspace/lib/usb/src/port.c@ 6ff23ff

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

More comment fixing (ccheck).

  • Property mode set to 100644
File size: 6.6 KB
Line 
1/*
2 * Copyright (c) 2018 Ondrej Hlavaty
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) ? PORT_DISABLED : PORT_ENUMERATED;
87
88out:
89 fibril_condvar_broadcast(&port->finished_cv);
90 fibril_mutex_unlock(&port->guard);
91 return EOK; // This is a fibril worker. No one will read the value.
92}
93
94int usb_port_connected(usb_port_t *port, usb_port_enumerate_t handler)
95{
96 assert(port);
97 int ret = ENOMEM;
98
99 fibril_mutex_lock(&port->guard);
100
101 if (port->state != PORT_DISABLED) {
102 usb_log_warning("a connected event come for port that is not disabled.");
103 ret = EINVAL;
104 goto out;
105 }
106
107 struct enumerate_worker_args *args = malloc(sizeof(*args));
108 if (!args)
109 goto out;
110
111 fid_t fibril = fibril_create(&enumerate_worker, args);
112 if (!fibril) {
113 free(args);
114 goto out;
115 }
116
117 args->port = port;
118 args->handler = handler;
119
120 port->state = PORT_CONNECTING;
121 fibril_add_ready(fibril);
122out:
123 fibril_mutex_unlock(&port->guard);
124 return ret;
125}
126
127void usb_port_enabled(usb_port_t *port)
128{
129 assert(port);
130
131 fibril_mutex_lock(&port->guard);
132 fibril_condvar_broadcast(&port->enabled_cv);
133 fibril_mutex_unlock(&port->guard);
134}
135
136struct remove_worker_args {
137 usb_port_t *port;
138 usb_port_remove_t handler;
139};
140
141static int remove_worker(void *arg)
142{
143 struct remove_worker_args *const args = arg;
144 usb_port_t *port = args->port;
145 usb_port_remove_t handler = args->handler;
146 free(args);
147
148 fibril_mutex_lock(&port->guard);
149 assert(port->state == PORT_DISCONNECTING);
150
151 handler(port);
152
153 port->state = PORT_DISABLED;
154 fibril_condvar_broadcast(&port->finished_cv);
155 fibril_mutex_unlock(&port->guard);
156 return EOK;
157}
158
159static void fork_remove_worker(usb_port_t *port, usb_port_remove_t handler)
160{
161 struct remove_worker_args *args = malloc(sizeof(*args));
162 if (!args)
163 return;
164
165 fid_t fibril = fibril_create(&remove_worker, args);
166 if (!fibril) {
167 free(args);
168 return;
169 }
170
171 args->port = port;
172 args->handler = handler;
173
174 port->state = PORT_DISCONNECTING;
175 fibril_add_ready(fibril);
176}
177
178void usb_port_disabled(usb_port_t *port, usb_port_remove_t handler)
179{
180 assert(port);
181 fibril_mutex_lock(&port->guard);
182
183 switch (port->state) {
184 case PORT_ENUMERATED:
185 fork_remove_worker(port, handler);
186 break;
187
188 case PORT_CONNECTING:
189 port->state = PORT_ERROR;
190 fibril_condvar_broadcast(&port->enabled_cv);
191 /* fallthrough */
192 case PORT_ERROR:
193 fibril_condvar_wait(&port->finished_cv, &port->guard);
194 /* fallthrough */
195 case PORT_DISCONNECTING:
196 case PORT_DISABLED:
197 break;
198 }
199
200 fibril_mutex_unlock(&port->guard);
201}
202
203void usb_port_fini(usb_port_t *port)
204{
205 assert(port);
206
207 fibril_mutex_lock(&port->guard);
208 switch (port->state) {
209 case PORT_ENUMERATED:
210 /*
211 * We shall inform the HC that the device is gone.
212 * However, we can't wait for it, because if the device is hub,
213 * it would have to use the same IPC handling fibril as we do.
214 * But we cannot even defer it to another fibril, because then
215 * the HC would assume our driver didn't cleanup properly, and
216 * will remove those devices by himself.
217 *
218 * So the solutions seems to be to behave like a bad driver and
219 * leave the work for HC.
220 */
221 port->state = PORT_DISABLED;
222 /* fallthrough */
223 case PORT_DISABLED:
224 break;
225
226 case PORT_CONNECTING:
227 /* We first have to stop the fibril in progress. */
228 port->state = PORT_ERROR;
229 fibril_condvar_broadcast(&port->enabled_cv);
230 /* fallthrough */
231 case PORT_ERROR:
232 case PORT_DISCONNECTING:
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.