source: mainline/uspace/lib/usbhost/src/endpoint.c@ 3dd80f8

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

usb: fix wrong design of transfer aborting

Apparently, we didn't do a good job in thinking through the problem.
In older HCs, it was done just wrong - the UHCI implementation commited
a batch that could have been already aborted, and EHCI+OHCI might miss
an interrupt because they commited the batch sooner than they added it
to their checked list.

This commit takes everything from the other end, which is probably the
only right one. Instead of an endpoint having an extra mutex, it
inherits a mutex from the outside. It never locks it though, it just
checks if the mutex is locked and uses it for waiting on condition
variables.

This mutex is supposed to be the one which the HC driver uses for
locking its structures in scheduling. This way, we avoid the ABBA
deadlock completely, while preserving the synchronization on an
endpoint.

The good thing is that this implementation is much easier to extend with
multiple active batches per endpoint.

  • Property mode set to 100644
File size: 8.3 KB
Line 
1/*
2 * Copyright (c) 2011 Jan Vesely
3 * Copyright (c) 2017 Ondrej Hlavaty <aearsis@eideo.cz>
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * - Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * - Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * - The name of the author may not be used to endorse or promote products
16 * derived from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30/** @addtogroup libusbhost
31 * @{
32 */
33/** @file
34 * @brief UHCI host controller driver structure
35 */
36
37#include <assert.h>
38#include <atomic.h>
39#include <mem.h>
40#include <stdlib.h>
41#include <str_error.h>
42#include <usb/debug.h>
43#include <usb/descriptor.h>
44#include <usb/host/hcd.h>
45#include <usb/host/utility.h>
46
47#include "usb_transfer_batch.h"
48#include "bus.h"
49
50#include "endpoint.h"
51
52/**
53 * Initialize provided endpoint structure.
54 */
55void endpoint_init(endpoint_t *ep, device_t *dev, const usb_endpoint_descriptors_t *desc)
56{
57 memset(ep, 0, sizeof(endpoint_t));
58
59 assert(dev);
60 ep->device = dev;
61
62 atomic_set(&ep->refcnt, 0);
63 fibril_condvar_initialize(&ep->avail);
64
65 ep->endpoint = USB_ED_GET_EP(desc->endpoint);
66 ep->direction = USB_ED_GET_DIR(desc->endpoint);
67 ep->transfer_type = USB_ED_GET_TRANSFER_TYPE(desc->endpoint);
68 ep->max_packet_size = USB_ED_GET_MPS(desc->endpoint);
69 ep->packets_per_uframe = USB_ED_GET_ADD_OPPS(desc->endpoint) + 1;
70
71 /** Direction both is our construct never present in descriptors */
72 if (ep->transfer_type == USB_TRANSFER_CONTROL)
73 ep->direction = USB_DIRECTION_BOTH;
74
75 ep->max_transfer_size = ep->max_packet_size * ep->packets_per_uframe;
76
77 ep->bandwidth = endpoint_count_bw(ep, ep->max_transfer_size);
78}
79
80/**
81 * Get the bus endpoint belongs to.
82 */
83static inline const bus_ops_t *get_bus_ops(endpoint_t *ep)
84{
85 return ep->device->bus->ops;
86}
87
88/**
89 * Increase the reference count on endpoint.
90 */
91void endpoint_add_ref(endpoint_t *ep)
92{
93 atomic_inc(&ep->refcnt);
94}
95
96/**
97 * Call the desctruction callback. Default behavior is to free the memory directly.
98 */
99static inline void endpoint_destroy(endpoint_t *ep)
100{
101 const bus_ops_t *ops = BUS_OPS_LOOKUP(get_bus_ops(ep), endpoint_destroy);
102 if (ops) {
103 ops->endpoint_destroy(ep);
104 } else {
105 assert(ep->active_batch == NULL);
106
107 /* Assume mostly the eps will be allocated by malloc. */
108 free(ep);
109 }
110}
111
112/**
113 * Decrease the reference count.
114 */
115void endpoint_del_ref(endpoint_t *ep)
116{
117 if (atomic_predec(&ep->refcnt) == 0) {
118 endpoint_destroy(ep);
119 }
120}
121
122/**
123 * Mark the endpoint as online. Supply a guard to be used for this endpoint
124 * synchronization.
125 */
126void endpoint_set_online(endpoint_t *ep, fibril_mutex_t *guard)
127{
128 ep->guard = guard;
129 ep->online = true;
130}
131
132/**
133 * Mark the endpoint as offline. All other fibrils waiting to activate this
134 * endpoint will be interrupted.
135 */
136void endpoint_set_offline_locked(endpoint_t *ep)
137{
138 assert(ep);
139 assert(fibril_mutex_is_locked(ep->guard));
140
141 ep->online = false;
142 fibril_condvar_broadcast(&ep->avail);
143}
144
145/**
146 * Wait until a transfer finishes. Can be used even when the endpoint is
147 * offline (and is interrupted by the endpoint going offline).
148 */
149void endpoint_wait_timeout_locked(endpoint_t *ep, suseconds_t timeout)
150{
151 assert(ep);
152 assert(fibril_mutex_is_locked(ep->guard));
153
154 if (ep->active_batch == NULL)
155 return;
156
157 fibril_condvar_wait_timeout(&ep->avail, ep->guard, timeout);
158}
159
160/**
161 * Mark the endpoint as active and block access for further fibrils. If the
162 * endpoint is already active, it will block on ep->avail condvar.
163 *
164 * Call only under endpoint guard. After you activate the endpoint and release
165 * the guard, you must assume that particular transfer is already
166 * finished/aborted.
167 *
168 * Activation and deactivation is not done by the library to maximize
169 * performance. The HC might want to prepare some memory buffers prior to
170 * interfering with other world.
171 *
172 * @param batch Transfer batch this endpoint is blocked by.
173 */
174int endpoint_activate_locked(endpoint_t *ep, usb_transfer_batch_t *batch)
175{
176 assert(ep);
177 assert(batch);
178 assert(batch->ep == ep);
179 assert(ep->guard);
180 assert(fibril_mutex_is_locked(ep->guard));
181
182 while (ep->online && ep->active_batch != NULL)
183 fibril_condvar_wait(&ep->avail, ep->guard);
184
185 if (!ep->online)
186 return EINTR;
187
188 assert(ep->active_batch == NULL);
189 ep->active_batch = batch;
190 return EOK;
191}
192
193/**
194 * Mark the endpoint as inactive and allow access for further fibrils.
195 */
196void endpoint_deactivate_locked(endpoint_t *ep)
197{
198 assert(ep);
199 assert(fibril_mutex_is_locked(ep->guard));
200
201 ep->active_batch = NULL;
202 fibril_condvar_signal(&ep->avail);
203}
204
205/**
206 * Call the bus operation to count bandwidth.
207 *
208 * @param ep Endpoint on which the transfer will take place.
209 * @param size The payload size.
210 */
211ssize_t endpoint_count_bw(endpoint_t *ep, size_t size)
212{
213 assert(ep);
214
215 const bus_ops_t *ops = BUS_OPS_LOOKUP(get_bus_ops(ep), endpoint_count_bw);
216 if (!ops)
217 return 0;
218
219 return ops->endpoint_count_bw(ep, size);
220}
221
222/**
223 * Initiate a transfer on an endpoint. Creates a transfer batch, checks the
224 * bandwidth requirements and schedules the batch.
225 *
226 * @param endpoint Endpoint for which to send the batch
227 * @param target The target of the transfer.
228 * @param direction A direction of the transfer.
229 * @param data A pointer to the data buffer.
230 * @param size Size of the data buffer.
231 * @param setup_data Data to use in the setup stage (Control communication type)
232 * @param on_complete Callback which is called after the batch is complete
233 * @param arg Callback parameter.
234 * @param name Communication identifier (for nicer output).
235 */
236int endpoint_send_batch(endpoint_t *ep, usb_target_t target,
237 usb_direction_t direction, char *data, size_t size, uint64_t setup_data,
238 usbhc_iface_transfer_callback_t on_complete, void *arg, const char *name)
239{
240 if (!ep)
241 return EBADMEM;
242
243 if (ep->transfer_type == USB_TRANSFER_CONTROL) {
244 usb_log_debug("%s %d:%d %zu/%zuB, setup %#016" PRIx64, name,
245 target.address, target.endpoint, size, ep->max_packet_size,
246 setup_data);
247 } else {
248 usb_log_debug("%s %d:%d %zu/%zuB", name, target.address,
249 target.endpoint, size, ep->max_packet_size);
250 }
251
252 device_t * const device = ep->device;
253 if (!device) {
254 usb_log_warning("Endpoint detached");
255 return EAGAIN;
256 }
257
258 const bus_ops_t *ops = BUS_OPS_LOOKUP(device->bus->ops, batch_schedule);
259 if (!ops) {
260 usb_log_error("HCD does not implement scheduler.");
261 return ENOTSUP;
262 }
263
264 /* Offline devices don't schedule transfers other than on EP0. */
265 if (!device->online && ep->endpoint > 0) {
266 return EAGAIN;
267 }
268
269 const size_t bw = endpoint_count_bw(ep, size);
270 /* Check if we have enough bandwidth reserved */
271 if (ep->bandwidth < bw) {
272 usb_log_error("Endpoint(%d:%d) %s needs %zu bw "
273 "but only %zu is reserved.\n",
274 device->address, ep->endpoint, name, bw, ep->bandwidth);
275 return ENOSPC;
276 }
277
278 usb_transfer_batch_t *batch = usb_transfer_batch_create(ep);
279 if (!batch) {
280 usb_log_error("Failed to create transfer batch.");
281 return ENOMEM;
282 }
283
284 batch->target = target;
285 batch->buffer = data;
286 batch->buffer_size = size;
287 batch->setup.packed = setup_data;
288 batch->dir = direction;
289 batch->on_complete = on_complete;
290 batch->on_complete_data = arg;
291
292 const int ret = ops->batch_schedule(batch);
293 if (ret != EOK) {
294 usb_log_warning("Batch %p failed to schedule: %s", batch, str_error(ret));
295 usb_transfer_batch_destroy(batch);
296 }
297
298 return ret;
299}
300
301/**
302 * @}
303 */
Note: See TracBrowser for help on using the repository browser.