source: mainline/uspace/lib/usb/src/port.c@ 94f8c363

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

usbhub: extract the port state machine to the usb library

  • Property mode set to 100644
File size: 5.8 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
139void usb_port_disabled(usb_port_t *port, usb_port_remove_t handler)
140{
141 assert(port);
142 fibril_mutex_lock(&port->guard);
143
144 switch (port->state) {
145 case PORT_ENUMERATED:
146 handler(port);
147 port->state = PORT_DISABLED;
148 break;
149
150 case PORT_CONNECTING:
151 port->state = PORT_ERROR;
152 /* fallthrough */
153 case PORT_ERROR:
154 fibril_condvar_wait(&port->finished_cv, &port->guard);
155 /* fallthrough */
156 case PORT_DISABLED:
157 break;
158 }
159
160 assert(port->state == PORT_DISABLED);
161 fibril_mutex_unlock(&port->guard);
162}
163
164void usb_port_fini(usb_port_t *port)
165{
166 assert(port);
167
168 fibril_mutex_lock(&port->guard);
169 switch (port->state) {
170 case PORT_ENUMERATED:
171 /*
172 * We shall inform the HC that the device is gone.
173 * However, we can't wait for it, because if the device is hub,
174 * it would have to use the same IPC handling fibril as we do.
175 * But we cannot even defer it to another fibril, because then
176 * the HC would assume our driver didn't cleanup properly, and
177 * will remove those devices by himself.
178 *
179 * So the solutions seems to be to behave like a bad driver and
180 * leave the work for HC.
181 */
182 port->state = PORT_DISABLED;
183 /* fallthrough */
184 case PORT_DISABLED:
185 break;
186
187 /* We first have to stop the fibril in progress. */
188 case PORT_CONNECTING:
189 port->state = PORT_ERROR;
190 /* fallthrough */
191 case PORT_ERROR:
192 fibril_condvar_wait(&port->finished_cv, &port->guard);
193 break;
194 }
195 fibril_mutex_unlock(&port->guard);
196}
197
198int usb_port_condvar_wait_timeout(usb_port_t *port, fibril_condvar_t *cv, suseconds_t timeout)
199{
200 assert(port);
201 assert(port->state == PORT_CONNECTING);
202 assert(fibril_mutex_is_locked(&port->guard));
203
204 if (fibril_condvar_wait_timeout(cv, &port->guard, timeout))
205 return ETIMEOUT;
206
207 return port->state == PORT_CONNECTING ? EOK : EINTR;
208}
209
210/**
211 * @}
212 */
Note: See TracBrowser for help on using the repository browser.