1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Generic bounce buffer implementation
4 *
5 * Copyright (C) 2012 Marek Vasut <marex@denx.de>
6 */
7
8#include <common.h>
9#include <cpu_func.h>
10#include <log.h>
11#include <malloc.h>
12#include <errno.h>
13#include <bouncebuf.h>
14#include <asm/cache.h>
15#include <linux/dma-mapping.h>
16
17static int addr_aligned(struct bounce_buffer *state)
18{
19	const ulong align_mask = ARCH_DMA_MINALIGN - 1;
20
21	/* Check if start is aligned */
22	if ((ulong)state->user_buffer & align_mask) {
23		debug("Unaligned buffer address %p\n", state->user_buffer);
24		return 0;
25	}
26
27	/* Check if length is aligned */
28	if (state->len != state->len_aligned) {
29		debug("Unaligned buffer length %zu\n", state->len);
30		return 0;
31	}
32
33	/* Aligned */
34	return 1;
35}
36
37int bounce_buffer_start_extalign(struct bounce_buffer *state, void *data,
38				 size_t len, unsigned int flags,
39				 size_t alignment,
40				 int (*addr_is_aligned)(struct bounce_buffer *state))
41{
42	state->user_buffer = data;
43	state->bounce_buffer = data;
44	state->len = len;
45	state->len_aligned = roundup(len, alignment);
46	state->flags = flags;
47
48	if (!addr_is_aligned(state)) {
49		state->bounce_buffer = memalign(alignment,
50						state->len_aligned);
51		if (!state->bounce_buffer)
52			return -ENOMEM;
53
54		if (state->flags & GEN_BB_READ)
55			memcpy(state->bounce_buffer, state->user_buffer,
56				state->len);
57	}
58
59	/*
60	 * Flush data to RAM so DMA reads can pick it up,
61	 * and any CPU writebacks don't race with DMA writes
62	 */
63	dma_map_single(state->bounce_buffer,
64		       state->len_aligned,
65		       DMA_BIDIRECTIONAL);
66
67	return 0;
68}
69
70int bounce_buffer_start(struct bounce_buffer *state, void *data,
71			size_t len, unsigned int flags)
72{
73	return bounce_buffer_start_extalign(state, data, len, flags,
74					    ARCH_DMA_MINALIGN,
75					    addr_aligned);
76}
77
78int bounce_buffer_stop(struct bounce_buffer *state)
79{
80	if (state->flags & GEN_BB_WRITE) {
81		/* Invalidate cache so that CPU can see any newly DMA'd data */
82		dma_unmap_single((dma_addr_t)(uintptr_t)state->bounce_buffer,
83				 state->len_aligned,
84				 DMA_BIDIRECTIONAL);
85	}
86
87	if (state->bounce_buffer == state->user_buffer)
88		return 0;
89
90	if (state->flags & GEN_BB_WRITE)
91		memcpy(state->user_buffer, state->bounce_buffer, state->len);
92
93	free(state->bounce_buffer);
94
95	return 0;
96}
97