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

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

ohci: use dma memory responsibly

Instead of leaving arbitrary TD behind to be used by next transfer,
prepare two of them in endpoint and use them in a cyclic manner. This
reduces the number of pages allocated per transfer to one.

  • Property mode set to 100644
File size: 12.5 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->buffer_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 + usb_batch->buffer_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 = ohci_batch->setup_buffer + setup_size;
132 if (usb_batch->dir == USB_DIRECTION_OUT)
133 memcpy(ohci_batch->data_buffer, usb_batch->buffer, usb_batch->buffer_size);
134
135 batch_setup[usb_batch->ep->transfer_type](ohci_batch);
136
137 return EOK;
138}
139
140/** Check batch TDs' status.
141 *
142 * @param[in] ohci_batch Batch structure to use.
143 * @return False, if there is an active TD, true otherwise.
144 *
145 * Walk all TDs (usually there is just one). Stop with false if there is an
146 * active TD. Stop with true if an error is found. Return true if the walk
147 * completes with the last TD.
148 */
149bool ohci_transfer_batch_check_completed(ohci_transfer_batch_t *ohci_batch)
150{
151 assert(ohci_batch);
152
153 ohci_endpoint_t *ohci_ep = ohci_endpoint_get(ohci_batch->base.ep);
154 assert(ohci_ep);
155
156 usb_log_debug("Batch %p checking %zu td(s) for completion.",
157 &ohci_batch->base, ohci_batch->td_count);
158 usb_log_debug2("ED: %08x:%08x:%08x:%08x.",
159 ohci_ep->ed->status, ohci_ep->ed->td_head,
160 ohci_ep->ed->td_tail, ohci_ep->ed->next);
161
162 if (!ed_inactive(ohci_ep->ed) && ed_transfer_pending(ohci_ep->ed))
163 return false;
164
165 /* Now we may be sure that either the ED is inactive because of errors
166 * or all transfer descriptors completed successfully */
167
168 /* Assume all data got through */
169 ohci_batch->base.transferred_size = ohci_batch->base.buffer_size;
170
171 /* Check all TDs */
172 for (size_t i = 0; i < ohci_batch->td_count; ++i) {
173 assert(ohci_batch->tds[i] != NULL);
174 usb_log_debug("TD %zu: %08x:%08x:%08x:%08x.", i,
175 ohci_batch->tds[i]->status, ohci_batch->tds[i]->cbp,
176 ohci_batch->tds[i]->next, ohci_batch->tds[i]->be);
177
178 ohci_batch->base.error = td_error(ohci_batch->tds[i]);
179 if (ohci_batch->base.error == EOK) {
180 /* If the TD got all its data through, it will report
181 * 0 bytes remain, the sole exception is INPUT with
182 * data rounding flag (short), i.e. every INPUT.
183 * Nice thing is that short packets will correctly
184 * report remaining data, thus making this computation
185 * correct (short packets need to be produced by the
186 * last TD)
187 * NOTE: This also works for CONTROL transfer as
188 * the first TD will return 0 remain.
189 * NOTE: Short packets don't break the assumption that
190 * we leave the very last(unused) TD behind.
191 */
192 ohci_batch->base.transferred_size
193 -= td_remain_size(ohci_batch->tds[i]);
194 } else {
195 usb_log_debug("Batch %p found error TD(%zu):%08x.",
196 &ohci_batch->base, i, ohci_batch->tds[i]->status);
197
198 /* ED should be stopped because of errors */
199 assert((ohci_ep->ed->td_head & ED_TDHEAD_HALTED_FLAG) != 0);
200
201 /* We don't care where the processing stopped, we just
202 * need to make sure it's not using any of the TDs owned
203 * by the transfer.
204 *
205 * As the chain is terminated by a TD in ownership of
206 * the EP, set it.
207 */
208 ed_set_head_td(ohci_ep->ed, ohci_ep->tds[0]);
209
210 /* Clear the halted condition for the next transfer */
211 ed_clear_halt(ohci_ep->ed);
212 break;
213 }
214 }
215 assert(ohci_batch->base.transferred_size <=
216 ohci_batch->base.buffer_size);
217
218 if (ohci_batch->base.dir == USB_DIRECTION_IN)
219 memcpy(ohci_batch->base.buffer,
220 ohci_batch->data_buffer,
221 ohci_batch->base.transferred_size);
222
223 /* Make sure that we are leaving the right TD behind */
224 assert(addr_to_phys(ohci_ep->tds[0]) == ed_tail_td(ohci_ep->ed));
225 assert(ed_tail_td(ohci_ep->ed) == ed_head_td(ohci_ep->ed));
226
227 return true;
228}
229
230/** Starts execution of the TD list
231 *
232 * @param[in] ohci_batch Batch structure to use
233 */
234void ohci_transfer_batch_commit(const ohci_transfer_batch_t *ohci_batch)
235{
236 assert(ohci_batch);
237
238 ohci_endpoint_t *ohci_ep = ohci_endpoint_get(ohci_batch->base.ep);
239
240 usb_log_debug("Using ED(%p): %08x:%08x:%08x:%08x.", ohci_ep->ed,
241 ohci_ep->ed->status, ohci_ep->ed->td_tail,
242 ohci_ep->ed->td_head, ohci_ep->ed->next);
243
244 /*
245 * According to spec, we need to copy the first TD to the currently
246 * enqueued one.
247 */
248 memcpy(ohci_ep->tds[0], ohci_batch->tds[0], sizeof(td_t));
249 ohci_batch->tds[0] = ohci_ep->tds[0];
250
251 td_t *last = ohci_batch->tds[ohci_batch->td_count - 1];
252 td_set_next(last, ohci_ep->tds[1]);
253
254 ed_set_tail_td(ohci_ep->ed, ohci_ep->tds[1]);
255
256 /* Swap the EP TDs for the next transfer */
257 td_t *tmp = ohci_ep->tds[0];
258 ohci_ep->tds[0] = ohci_ep->tds[1];
259 ohci_ep->tds[1] = tmp;
260}
261
262/** Prepare generic control transfer
263 *
264 * @param[in] ohci_batch Batch structure to use.
265 * @param[in] dir Communication direction
266 *
267 * Setup stage with toggle 0 and direction BOTH(SETUP_PID)
268 * Data stage with alternating toggle and direction supplied by parameter.
269 * Status stage with toggle 1 and direction supplied by parameter.
270 */
271static void batch_control(ohci_transfer_batch_t *ohci_batch)
272{
273 assert(ohci_batch);
274
275 usb_direction_t dir = ohci_batch->base.dir;
276 assert(dir == USB_DIRECTION_IN || dir == USB_DIRECTION_OUT);
277
278 static const usb_direction_t reverse_dir[] = {
279 [USB_DIRECTION_IN] = USB_DIRECTION_OUT,
280 [USB_DIRECTION_OUT] = USB_DIRECTION_IN,
281 };
282
283 int toggle = 0;
284 const usb_direction_t data_dir = dir;
285 const usb_direction_t status_dir = reverse_dir[dir];
286
287 /* Setup stage */
288 td_init(
289 ohci_batch->tds[0], ohci_batch->tds[1], USB_DIRECTION_BOTH,
290 ohci_batch->setup_buffer, USB_SETUP_PACKET_SIZE, toggle);
291 usb_log_debug("Created CONTROL SETUP TD: %08x:%08x:%08x:%08x.",
292 ohci_batch->tds[0]->status, ohci_batch->tds[0]->cbp,
293 ohci_batch->tds[0]->next, ohci_batch->tds[0]->be);
294
295 /* Data stage */
296 size_t td_current = 1;
297 const char* buffer = ohci_batch->data_buffer;
298 size_t remain_size = ohci_batch->base.buffer_size;
299 while (remain_size > 0) {
300 const size_t transfer_size =
301 min(remain_size, OHCI_TD_MAX_TRANSFER);
302 toggle = 1 - toggle;
303
304 td_init(ohci_batch->tds[td_current],
305 ohci_batch->tds[td_current + 1],
306 data_dir, buffer, transfer_size, toggle);
307 usb_log_debug("Created CONTROL DATA TD: %08x:%08x:%08x:%08x.",
308 ohci_batch->tds[td_current]->status,
309 ohci_batch->tds[td_current]->cbp,
310 ohci_batch->tds[td_current]->next,
311 ohci_batch->tds[td_current]->be);
312
313 buffer += transfer_size;
314 remain_size -= transfer_size;
315 assert(td_current < ohci_batch->td_count - 1);
316 ++td_current;
317 }
318
319 /* Status stage */
320 assert(td_current == ohci_batch->td_count - 1);
321 td_init(ohci_batch->tds[td_current], ohci_batch->tds[td_current + 1],
322 status_dir, NULL, 0, 1);
323 usb_log_debug("Created CONTROL STATUS TD: %08x:%08x:%08x:%08x.",
324 ohci_batch->tds[td_current]->status,
325 ohci_batch->tds[td_current]->cbp,
326 ohci_batch->tds[td_current]->next,
327 ohci_batch->tds[td_current]->be);
328 usb_log_debug2(
329 "Batch %p %s %s " USB_TRANSFER_BATCH_FMT " initialized.", \
330 &ohci_batch->base,
331 usb_str_transfer_type(ohci_batch->base.ep->transfer_type),
332 usb_str_direction(dir),
333 USB_TRANSFER_BATCH_ARGS(ohci_batch->base));
334}
335
336/** Prepare generic data transfer
337 *
338 * @param[in] ohci_batch Batch structure to use.
339 * @paramp[in] dir Communication direction.
340 *
341 * Direction is supplied by the associated ep and toggle is maintained by the
342 * OHCI hw in ED.
343 */
344static void batch_data(ohci_transfer_batch_t *ohci_batch)
345{
346 assert(ohci_batch);
347
348 usb_direction_t dir = ohci_batch->base.dir;
349 assert(dir == USB_DIRECTION_IN || dir == USB_DIRECTION_OUT);
350
351 size_t td_current = 0;
352 size_t remain_size = ohci_batch->base.buffer_size;
353 char *buffer = ohci_batch->data_buffer;
354 while (remain_size > 0) {
355 const size_t transfer_size = remain_size > OHCI_TD_MAX_TRANSFER
356 ? OHCI_TD_MAX_TRANSFER : remain_size;
357
358 td_init(
359 ohci_batch->tds[td_current], ohci_batch->tds[td_current + 1],
360 dir, buffer, transfer_size, -1);
361
362 usb_log_debug("Created DATA TD: %08x:%08x:%08x:%08x.",
363 ohci_batch->tds[td_current]->status,
364 ohci_batch->tds[td_current]->cbp,
365 ohci_batch->tds[td_current]->next,
366 ohci_batch->tds[td_current]->be);
367
368 buffer += transfer_size;
369 remain_size -= transfer_size;
370 assert(td_current < ohci_batch->td_count);
371 ++td_current;
372 }
373 usb_log_debug2(
374 "Batch %p %s %s " USB_TRANSFER_BATCH_FMT " initialized.", \
375 &ohci_batch->base,
376 usb_str_transfer_type(ohci_batch->base.ep->transfer_type),
377 usb_str_direction(dir),
378 USB_TRANSFER_BATCH_ARGS(ohci_batch->base));
379}
380
381/** Transfer setup table. */
382static void (*const batch_setup[])(ohci_transfer_batch_t*) =
383{
384 [USB_TRANSFER_CONTROL] = batch_control,
385 [USB_TRANSFER_BULK] = batch_data,
386 [USB_TRANSFER_INTERRUPT] = batch_data,
387 [USB_TRANSFER_ISOCHRONOUS] = NULL,
388};
389/**
390 * @}
391 */
Note: See TracBrowser for help on using the repository browser.