source: mainline/uspace/drv/bus/usb/ohci/ohci_batch.c@ 1d758fc

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

usb: rethinking DMA buffers

  • Property mode set to 100644
File size: 12.1 KB
Line 
1/*
2 * Copyright (c) 2011 Jan Vesely
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 drvusbohci
30 * @{
31 */
32/** @file
33 * @brief OHCI driver USB transaction structure
34 */
35
36#include <assert.h>
37#include <errno.h>
38#include <macros.h>
39#include <mem.h>
40#include <stdbool.h>
41
42#include <usb/usb.h>
43#include <usb/debug.h>
44#include <usb/host/utils/malloc32.h>
45
46#include "ohci_batch.h"
47#include "ohci_bus.h"
48
49static void (*const batch_setup[])(ohci_transfer_batch_t*);
50
51/** Safely destructs ohci_transfer_batch_t structure
52 *
53 * @param[in] ohci_batch Instance to destroy.
54 */
55void ohci_transfer_batch_destroy(ohci_transfer_batch_t *ohci_batch)
56{
57 assert(ohci_batch);
58 dma_buffer_free(&ohci_batch->ohci_dma_buffer);
59 free(ohci_batch);
60}
61
62/** Allocate memory and initialize internal data structure.
63 *
64 * @param[in] ep Endpoint for which the batch will be created
65 * @return Valid pointer if all structures were successfully created,
66 * NULL otherwise.
67 */
68ohci_transfer_batch_t * ohci_transfer_batch_create(endpoint_t *ep)
69{
70 assert(ep);
71
72 ohci_transfer_batch_t *ohci_batch =
73 calloc(1, sizeof(ohci_transfer_batch_t));
74 if (!ohci_batch) {
75 usb_log_error("Failed to allocate OHCI batch data.");
76 return NULL;
77 }
78
79 usb_transfer_batch_init(&ohci_batch->base, ep);
80
81 return ohci_batch;
82}
83
84/** Prepares a batch to be sent.
85 *
86 * Determines the number of needed transfer descriptors (TDs).
87 * Prepares a transport buffer (that is accessible by the hardware).
88 * Initializes parameters needed for the transfer and callback.
89 */
90int ohci_transfer_batch_prepare(ohci_transfer_batch_t *ohci_batch)
91{
92 assert(ohci_batch);
93 usb_transfer_batch_t *usb_batch = &ohci_batch->base;
94
95 if (!batch_setup[usb_batch->ep->transfer_type])
96 return ENOTSUP;
97
98 ohci_batch->td_count = (usb_batch->size + OHCI_TD_MAX_TRANSFER - 1)
99 / OHCI_TD_MAX_TRANSFER;
100 /* Control transfer need Setup and Status stage */
101 if (usb_batch->ep->transfer_type == USB_TRANSFER_CONTROL) {
102 ohci_batch->td_count += 2;
103 }
104
105 /* Alloc one more to NULL terminate */
106 ohci_batch->tds = calloc(ohci_batch->td_count + 1, sizeof(td_t *));
107 if (!ohci_batch->tds)
108 return ENOMEM;
109
110 const size_t td_size = ohci_batch->td_count * sizeof(td_t);
111 const size_t setup_size = (usb_batch->ep->transfer_type == USB_TRANSFER_CONTROL)
112 ? USB_SETUP_PACKET_SIZE
113 : 0;
114
115 if (dma_buffer_alloc(&ohci_batch->ohci_dma_buffer, td_size + setup_size)) {
116 usb_log_error("Failed to allocate OHCI DMA buffer.");
117 return ENOMEM;
118 }
119
120 td_t *tds = ohci_batch->ohci_dma_buffer.virt;
121
122 for (size_t i = 0; i < ohci_batch->td_count; i++)
123 ohci_batch->tds[i] = &tds[i];
124
125 /* Presence of this terminator makes TD initialization easier */
126 ohci_batch->tds[ohci_batch->td_count] = NULL;
127
128 ohci_batch->setup_buffer = (void *) (&tds[ohci_batch->td_count]);
129 memcpy(ohci_batch->setup_buffer, usb_batch->setup.buffer, setup_size);
130
131 ohci_batch->data_buffer = usb_batch->dma_buffer.virt;
132
133 batch_setup[usb_batch->ep->transfer_type](ohci_batch);
134
135 return EOK;
136}
137
138/** Check batch TDs' status.
139 *
140 * @param[in] ohci_batch Batch structure to use.
141 * @return False, if there is an active TD, true otherwise.
142 *
143 * Walk all TDs (usually there is just one). Stop with false if there is an
144 * active TD. Stop with true if an error is found. Return true if the walk
145 * completes with the last TD.
146 */
147bool ohci_transfer_batch_check_completed(ohci_transfer_batch_t *ohci_batch)
148{
149 assert(ohci_batch);
150
151 usb_transfer_batch_t *usb_batch = &ohci_batch->base;
152 ohci_endpoint_t *ohci_ep = ohci_endpoint_get(usb_batch->ep);
153 assert(ohci_ep);
154
155 usb_log_debug("Batch %p checking %zu td(s) for completion.",
156 ohci_batch, ohci_batch->td_count);
157 usb_log_debug2("ED: %08x:%08x:%08x:%08x.",
158 ohci_ep->ed->status, ohci_ep->ed->td_head,
159 ohci_ep->ed->td_tail, ohci_ep->ed->next);
160
161 if (!ed_inactive(ohci_ep->ed) && ed_transfer_pending(ohci_ep->ed))
162 return false;
163
164 /* Now we may be sure that either the ED is inactive because of errors
165 * or all transfer descriptors completed successfully */
166
167 /* Assume all data got through */
168 usb_batch->transferred_size = usb_batch->size;
169
170 /* Check all TDs */
171 for (size_t i = 0; i < ohci_batch->td_count; ++i) {
172 assert(ohci_batch->tds[i] != NULL);
173 usb_log_debug("TD %zu: %08x:%08x:%08x:%08x.", i,
174 ohci_batch->tds[i]->status, ohci_batch->tds[i]->cbp,
175 ohci_batch->tds[i]->next, ohci_batch->tds[i]->be);
176
177 usb_batch->error = td_error(ohci_batch->tds[i]);
178 if (usb_batch->error == EOK) {
179 /* If the TD got all its data through, it will report
180 * 0 bytes remain, the sole exception is INPUT with
181 * data rounding flag (short), i.e. every INPUT.
182 * Nice thing is that short packets will correctly
183 * report remaining data, thus making this computation
184 * correct (short packets need to be produced by the
185 * last TD)
186 * NOTE: This also works for CONTROL transfer as
187 * the first TD will return 0 remain.
188 * NOTE: Short packets don't break the assumption that
189 * we leave the very last(unused) TD behind.
190 */
191 usb_batch->transferred_size
192 -= td_remain_size(ohci_batch->tds[i]);
193 } else {
194 usb_log_debug("Batch %p found error TD(%zu):%08x.",
195 ohci_batch, i, ohci_batch->tds[i]->status);
196
197 /* ED should be stopped because of errors */
198 assert((ohci_ep->ed->td_head & ED_TDHEAD_HALTED_FLAG) != 0);
199
200 /* We don't care where the processing stopped, we just
201 * need to make sure it's not using any of the TDs owned
202 * by the transfer.
203 *
204 * As the chain is terminated by a TD in ownership of
205 * the EP, set it.
206 */
207 ed_set_head_td(ohci_ep->ed, ohci_ep->tds[0]);
208
209 /* Clear the halted condition for the next transfer */
210 ed_clear_halt(ohci_ep->ed);
211 break;
212 }
213 }
214 assert(usb_batch->transferred_size <= usb_batch->size);
215
216 /* Make sure that we are leaving the right TD behind */
217 assert(addr_to_phys(ohci_ep->tds[0]) == ed_tail_td(ohci_ep->ed));
218 assert(ed_tail_td(ohci_ep->ed) == ed_head_td(ohci_ep->ed));
219
220 return true;
221}
222
223/** Starts execution of the TD list
224 *
225 * @param[in] ohci_batch Batch structure to use
226 */
227void ohci_transfer_batch_commit(const ohci_transfer_batch_t *ohci_batch)
228{
229 assert(ohci_batch);
230
231 ohci_endpoint_t *ohci_ep = ohci_endpoint_get(ohci_batch->base.ep);
232
233 usb_log_debug("Using ED(%p): %08x:%08x:%08x:%08x.", ohci_ep->ed,
234 ohci_ep->ed->status, ohci_ep->ed->td_tail,
235 ohci_ep->ed->td_head, ohci_ep->ed->next);
236
237 /*
238 * According to spec, we need to copy the first TD to the currently
239 * enqueued one.
240 */
241 memcpy(ohci_ep->tds[0], ohci_batch->tds[0], sizeof(td_t));
242 ohci_batch->tds[0] = ohci_ep->tds[0];
243
244 td_t *last = ohci_batch->tds[ohci_batch->td_count - 1];
245 td_set_next(last, ohci_ep->tds[1]);
246
247 ed_set_tail_td(ohci_ep->ed, ohci_ep->tds[1]);
248
249 /* Swap the EP TDs for the next transfer */
250 td_t *tmp = ohci_ep->tds[0];
251 ohci_ep->tds[0] = ohci_ep->tds[1];
252 ohci_ep->tds[1] = tmp;
253}
254
255/** Prepare generic control transfer
256 *
257 * @param[in] ohci_batch Batch structure to use.
258 * @param[in] dir Communication direction
259 *
260 * Setup stage with toggle 0 and direction BOTH(SETUP_PID)
261 * Data stage with alternating toggle and direction supplied by parameter.
262 * Status stage with toggle 1 and direction supplied by parameter.
263 */
264static void batch_control(ohci_transfer_batch_t *ohci_batch)
265{
266 assert(ohci_batch);
267
268 usb_direction_t dir = ohci_batch->base.dir;
269 assert(dir == USB_DIRECTION_IN || dir == USB_DIRECTION_OUT);
270
271 static const usb_direction_t reverse_dir[] = {
272 [USB_DIRECTION_IN] = USB_DIRECTION_OUT,
273 [USB_DIRECTION_OUT] = USB_DIRECTION_IN,
274 };
275
276 int toggle = 0;
277 const usb_direction_t data_dir = dir;
278 const usb_direction_t status_dir = reverse_dir[dir];
279
280 /* Setup stage */
281 td_init(
282 ohci_batch->tds[0], ohci_batch->tds[1], USB_DIRECTION_BOTH,
283 ohci_batch->setup_buffer, USB_SETUP_PACKET_SIZE, toggle);
284 usb_log_debug("Created CONTROL SETUP TD: %08x:%08x:%08x:%08x.",
285 ohci_batch->tds[0]->status, ohci_batch->tds[0]->cbp,
286 ohci_batch->tds[0]->next, ohci_batch->tds[0]->be);
287
288 /* Data stage */
289 size_t td_current = 1;
290 const char* buffer = ohci_batch->data_buffer;
291 size_t remain_size = ohci_batch->base.size;
292 while (remain_size > 0) {
293 const size_t transfer_size =
294 min(remain_size, OHCI_TD_MAX_TRANSFER);
295 toggle = 1 - toggle;
296
297 td_init(ohci_batch->tds[td_current],
298 ohci_batch->tds[td_current + 1],
299 data_dir, buffer, transfer_size, toggle);
300 usb_log_debug("Created CONTROL DATA TD: %08x:%08x:%08x:%08x.",
301 ohci_batch->tds[td_current]->status,
302 ohci_batch->tds[td_current]->cbp,
303 ohci_batch->tds[td_current]->next,
304 ohci_batch->tds[td_current]->be);
305
306 buffer += transfer_size;
307 remain_size -= transfer_size;
308 assert(td_current < ohci_batch->td_count - 1);
309 ++td_current;
310 }
311
312 /* Status stage */
313 assert(td_current == ohci_batch->td_count - 1);
314 td_init(ohci_batch->tds[td_current], ohci_batch->tds[td_current + 1],
315 status_dir, NULL, 0, 1);
316 usb_log_debug("Created CONTROL STATUS TD: %08x:%08x:%08x:%08x.",
317 ohci_batch->tds[td_current]->status,
318 ohci_batch->tds[td_current]->cbp,
319 ohci_batch->tds[td_current]->next,
320 ohci_batch->tds[td_current]->be);
321 usb_log_debug2(
322 "Batch %p %s %s " USB_TRANSFER_BATCH_FMT " initialized.", \
323 &ohci_batch->base,
324 usb_str_transfer_type(ohci_batch->base.ep->transfer_type),
325 usb_str_direction(dir),
326 USB_TRANSFER_BATCH_ARGS(ohci_batch->base));
327}
328
329/** Prepare generic data transfer
330 *
331 * @param[in] ohci_batch Batch structure to use.
332 * @paramp[in] dir Communication direction.
333 *
334 * Direction is supplied by the associated ep and toggle is maintained by the
335 * OHCI hw in ED.
336 */
337static void batch_data(ohci_transfer_batch_t *ohci_batch)
338{
339 assert(ohci_batch);
340
341 usb_direction_t dir = ohci_batch->base.dir;
342 assert(dir == USB_DIRECTION_IN || dir == USB_DIRECTION_OUT);
343
344 size_t td_current = 0;
345 size_t remain_size = ohci_batch->base.size;
346 char *buffer = ohci_batch->data_buffer;
347 while (remain_size > 0) {
348 const size_t transfer_size = remain_size > OHCI_TD_MAX_TRANSFER
349 ? OHCI_TD_MAX_TRANSFER : remain_size;
350
351 td_init(
352 ohci_batch->tds[td_current], ohci_batch->tds[td_current + 1],
353 dir, buffer, transfer_size, -1);
354
355 usb_log_debug("Created DATA TD: %08x:%08x:%08x:%08x.",
356 ohci_batch->tds[td_current]->status,
357 ohci_batch->tds[td_current]->cbp,
358 ohci_batch->tds[td_current]->next,
359 ohci_batch->tds[td_current]->be);
360
361 buffer += transfer_size;
362 remain_size -= transfer_size;
363 assert(td_current < ohci_batch->td_count);
364 ++td_current;
365 }
366 usb_log_debug2(
367 "Batch %p %s %s " USB_TRANSFER_BATCH_FMT " initialized.", \
368 &ohci_batch->base,
369 usb_str_transfer_type(ohci_batch->base.ep->transfer_type),
370 usb_str_direction(dir),
371 USB_TRANSFER_BATCH_ARGS(ohci_batch->base));
372}
373
374/** Transfer setup table. */
375static void (*const batch_setup[])(ohci_transfer_batch_t*) =
376{
377 [USB_TRANSFER_CONTROL] = batch_control,
378 [USB_TRANSFER_BULK] = batch_data,
379 [USB_TRANSFER_INTERRUPT] = batch_data,
380 [USB_TRANSFER_ISOCHRONOUS] = NULL,
381};
382/**
383 * @}
384 */
Note: See TracBrowser for help on using the repository browser.