source: mainline/uspace/lib/usb/src/port.c@ 5bccec3

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

usb port: disconnect handler shall run in separate fibril too

  • Property mode set to 100644
File size: 6.7 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 call interface to announce events, and a callback structure for
44 * implementations to supply the hardware-dependent part.
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, usb_speed_t speed)
130{
131 assert(port);
132
133 fibril_mutex_lock(&port->guard);
134 port->speed = speed;
135 fibril_condvar_broadcast(&port->enabled_cv);
136 fibril_mutex_unlock(&port->guard);
137}
138
139struct remove_worker_args {
140 usb_port_t *port;
141 usb_port_remove_t handler;
142};
143
144static int remove_worker(void *arg)
145{
146 struct remove_worker_args * const args = arg;
147 usb_port_t *port = args->port;
148 usb_port_remove_t handler = args->handler;
149 free(args);
150
151 fibril_mutex_lock(&port->guard);
152 assert(port->state == PORT_DISCONNECTING);
153
154 handler(port);
155
156 port->state = PORT_DISABLED;
157 fibril_condvar_broadcast(&port->finished_cv);
158 fibril_mutex_unlock(&port->guard);
159 return EOK;
160}
161
162static void fork_remove_worker(usb_port_t *port, usb_port_remove_t handler)
163{
164 struct remove_worker_args *args = malloc(sizeof(*args));
165 if (!args)
166 return;
167
168 fid_t fibril = fibril_create(&remove_worker, args);
169 if (!fibril) {
170 free(args);
171 return;
172 }
173
174 args->port = port;
175 args->handler = handler;
176
177 port->state = PORT_DISCONNECTING;
178 fibril_add_ready(fibril);
179}
180
181void usb_port_disabled(usb_port_t *port, usb_port_remove_t handler)
182{
183 assert(port);
184 fibril_mutex_lock(&port->guard);
185
186 switch (port->state) {
187 case PORT_ENUMERATED:
188 fork_remove_worker(port, handler);
189 break;
190
191 case PORT_CONNECTING:
192 port->state = PORT_ERROR;
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 assert(port->state == PORT_DISABLED || port->state == PORT_DISCONNECTING);
203 fibril_mutex_unlock(&port->guard);
204}
205
206void usb_port_fini(usb_port_t *port)
207{
208 assert(port);
209
210 fibril_mutex_lock(&port->guard);
211 switch (port->state) {
212 case PORT_ENUMERATED:
213 /*
214 * We shall inform the HC that the device is gone.
215 * However, we can't wait for it, because if the device is hub,
216 * it would have to use the same IPC handling fibril as we do.
217 * But we cannot even defer it to another fibril, because then
218 * the HC would assume our driver didn't cleanup properly, and
219 * will remove those devices by himself.
220 *
221 * So the solutions seems to be to behave like a bad driver and
222 * leave the work for HC.
223 */
224 port->state = PORT_DISABLED;
225 /* fallthrough */
226 case PORT_DISABLED:
227 break;
228
229 /* We first have to stop the fibril in progress. */
230 case PORT_CONNECTING:
231 port->state = PORT_ERROR;
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.