blob: 8c3365f26e51d6ddbc76c318ef5092faa1e6c971 [file] [log] [blame]
Johannes Bergca2e3342021-03-05 13:19:52 +01001// SPDX-License-Identifier: GPL-2.0
2/*
3 * Copyright (C) 2021 Intel Corporation
4 * Author: Johannes Berg <johannes@sipsolutions.net>
5 */
6#include <linux/types.h>
7#include <linux/slab.h>
8#include <linux/logic_iomem.h>
Johannes Berg68fdb642021-06-25 10:34:35 +02009#include <asm/io.h>
Johannes Bergca2e3342021-03-05 13:19:52 +010010
11struct logic_iomem_region {
12 const struct resource *res;
13 const struct logic_iomem_region_ops *ops;
14 struct list_head list;
15};
16
17struct logic_iomem_area {
18 const struct logic_iomem_ops *ops;
19 void *priv;
20};
21
22#define AREA_SHIFT 24
23#define MAX_AREA_SIZE (1 << AREA_SHIFT)
Johannes Berg4e8a5ed2021-09-15 20:30:22 +020024#define MAX_AREAS ((1U << 31) / MAX_AREA_SIZE)
Johannes Bergca2e3342021-03-05 13:19:52 +010025#define AREA_BITS ((MAX_AREAS - 1) << AREA_SHIFT)
26#define AREA_MASK (MAX_AREA_SIZE - 1)
27#ifdef CONFIG_64BIT
28#define IOREMAP_BIAS 0xDEAD000000000000UL
29#define IOREMAP_MASK 0xFFFFFFFF00000000UL
30#else
Johannes Berg4e8a5ed2021-09-15 20:30:22 +020031#define IOREMAP_BIAS 0x80000000UL
32#define IOREMAP_MASK 0x80000000UL
Johannes Bergca2e3342021-03-05 13:19:52 +010033#endif
34
35static DEFINE_MUTEX(regions_mtx);
36static LIST_HEAD(regions_list);
37static struct logic_iomem_area mapped_areas[MAX_AREAS];
38
39int logic_iomem_add_region(struct resource *resource,
40 const struct logic_iomem_region_ops *ops)
41{
42 struct logic_iomem_region *rreg;
43 int err;
44
45 if (WARN_ON(!resource || !ops))
46 return -EINVAL;
47
48 if (WARN_ON((resource->flags & IORESOURCE_TYPE_BITS) != IORESOURCE_MEM))
49 return -EINVAL;
50
51 rreg = kzalloc(sizeof(*rreg), GFP_KERNEL);
52 if (!rreg)
53 return -ENOMEM;
54
55 err = request_resource(&iomem_resource, resource);
56 if (err) {
57 kfree(rreg);
58 return -ENOMEM;
59 }
60
61 mutex_lock(&regions_mtx);
62 rreg->res = resource;
63 rreg->ops = ops;
64 list_add_tail(&rreg->list, &regions_list);
65 mutex_unlock(&regions_mtx);
66
67 return 0;
68}
69EXPORT_SYMBOL(logic_iomem_add_region);
70
71#ifndef CONFIG_LOGIC_IOMEM_FALLBACK
72static void __iomem *real_ioremap(phys_addr_t offset, size_t size)
73{
74 WARN(1, "invalid ioremap(0x%llx, 0x%zx)\n",
75 (unsigned long long)offset, size);
76 return NULL;
77}
78
Al Viro5f174ec2021-09-20 21:32:49 +000079static void real_iounmap(volatile void __iomem *addr)
Johannes Bergca2e3342021-03-05 13:19:52 +010080{
81 WARN(1, "invalid iounmap for addr 0x%llx\n",
Johannes Berg4e841392021-09-15 20:30:21 +020082 (unsigned long long)(uintptr_t __force)addr);
Johannes Bergca2e3342021-03-05 13:19:52 +010083}
84#endif /* CONFIG_LOGIC_IOMEM_FALLBACK */
85
86void __iomem *ioremap(phys_addr_t offset, size_t size)
87{
88 void __iomem *ret = NULL;
89 struct logic_iomem_region *rreg, *found = NULL;
90 int i;
91
92 mutex_lock(&regions_mtx);
93 list_for_each_entry(rreg, &regions_list, list) {
94 if (rreg->res->start > offset)
95 continue;
96 if (rreg->res->end < offset + size - 1)
97 continue;
98 found = rreg;
99 break;
100 }
101
102 if (!found)
103 goto out;
104
105 for (i = 0; i < MAX_AREAS; i++) {
106 long offs;
107
108 if (mapped_areas[i].ops)
109 continue;
110
111 offs = rreg->ops->map(offset - found->res->start,
112 size, &mapped_areas[i].ops,
113 &mapped_areas[i].priv);
114 if (offs < 0) {
115 mapped_areas[i].ops = NULL;
116 break;
117 }
118
119 if (WARN_ON(!mapped_areas[i].ops)) {
120 mapped_areas[i].ops = NULL;
121 break;
122 }
123
124 ret = (void __iomem *)(IOREMAP_BIAS + (i << AREA_SHIFT) + offs);
125 break;
126 }
127out:
128 mutex_unlock(&regions_mtx);
129 if (ret)
130 return ret;
131 return real_ioremap(offset, size);
132}
133EXPORT_SYMBOL(ioremap);
134
135static inline struct logic_iomem_area *
136get_area(const volatile void __iomem *addr)
137{
138 unsigned long a = (unsigned long)addr;
139 unsigned int idx;
140
141 if (WARN_ON((a & IOREMAP_MASK) != IOREMAP_BIAS))
142 return NULL;
143
144 idx = (a & AREA_BITS) >> AREA_SHIFT;
145
146 if (mapped_areas[idx].ops)
147 return &mapped_areas[idx];
148
149 return NULL;
150}
151
Al Viro5f174ec2021-09-20 21:32:49 +0000152void iounmap(volatile void __iomem *addr)
Johannes Bergca2e3342021-03-05 13:19:52 +0100153{
154 struct logic_iomem_area *area = get_area(addr);
155
156 if (!area) {
157 real_iounmap(addr);
158 return;
159 }
160
161 if (area->ops->unmap)
162 area->ops->unmap(area->priv);
163
164 mutex_lock(&regions_mtx);
165 area->ops = NULL;
166 area->priv = NULL;
167 mutex_unlock(&regions_mtx);
168}
169EXPORT_SYMBOL(iounmap);
170
171#ifndef CONFIG_LOGIC_IOMEM_FALLBACK
172#define MAKE_FALLBACK(op, sz) \
173static u##sz real_raw_read ## op(const volatile void __iomem *addr) \
174{ \
175 WARN(1, "Invalid read" #op " at address %llx\n", \
Johannes Berg4e841392021-09-15 20:30:21 +0200176 (unsigned long long)(uintptr_t __force)addr); \
Johannes Bergca2e3342021-03-05 13:19:52 +0100177 return (u ## sz)~0ULL; \
178} \
179 \
Johannes Berg68fdb642021-06-25 10:34:35 +0200180static void real_raw_write ## op(u ## sz val, \
181 volatile void __iomem *addr) \
Johannes Bergca2e3342021-03-05 13:19:52 +0100182{ \
183 WARN(1, "Invalid writeq" #op " of 0x%llx at address %llx\n", \
Johannes Berg4e841392021-09-15 20:30:21 +0200184 (unsigned long long)val, \
185 (unsigned long long)(uintptr_t __force)addr);\
Johannes Bergca2e3342021-03-05 13:19:52 +0100186} \
187
188MAKE_FALLBACK(b, 8);
189MAKE_FALLBACK(w, 16);
190MAKE_FALLBACK(l, 32);
191#ifdef CONFIG_64BIT
192MAKE_FALLBACK(q, 64);
193#endif
194
195static void real_memset_io(volatile void __iomem *addr, int value, size_t size)
196{
197 WARN(1, "Invalid memset_io at address 0x%llx\n",
Johannes Berg4e841392021-09-15 20:30:21 +0200198 (unsigned long long)(uintptr_t __force)addr);
Johannes Bergca2e3342021-03-05 13:19:52 +0100199}
200
201static void real_memcpy_fromio(void *buffer, const volatile void __iomem *addr,
202 size_t size)
203{
204 WARN(1, "Invalid memcpy_fromio at address 0x%llx\n",
Johannes Berg4e841392021-09-15 20:30:21 +0200205 (unsigned long long)(uintptr_t __force)addr);
Johannes Bergca2e3342021-03-05 13:19:52 +0100206
207 memset(buffer, 0xff, size);
208}
209
210static void real_memcpy_toio(volatile void __iomem *addr, const void *buffer,
211 size_t size)
212{
213 WARN(1, "Invalid memcpy_toio at address 0x%llx\n",
Johannes Berg4e841392021-09-15 20:30:21 +0200214 (unsigned long long)(uintptr_t __force)addr);
Johannes Bergca2e3342021-03-05 13:19:52 +0100215}
216#endif /* CONFIG_LOGIC_IOMEM_FALLBACK */
217
218#define MAKE_OP(op, sz) \
219u##sz __raw_read ## op(const volatile void __iomem *addr) \
220{ \
221 struct logic_iomem_area *area = get_area(addr); \
222 \
223 if (!area) \
224 return real_raw_read ## op(addr); \
225 \
226 return (u ## sz) area->ops->read(area->priv, \
227 (unsigned long)addr & AREA_MASK,\
228 sz / 8); \
229} \
230EXPORT_SYMBOL(__raw_read ## op); \
231 \
232void __raw_write ## op(u ## sz val, volatile void __iomem *addr) \
233{ \
234 struct logic_iomem_area *area = get_area(addr); \
235 \
236 if (!area) { \
237 real_raw_write ## op(val, addr); \
238 return; \
239 } \
240 \
241 area->ops->write(area->priv, \
242 (unsigned long)addr & AREA_MASK, \
243 sz / 8, val); \
244} \
245EXPORT_SYMBOL(__raw_write ## op)
246
247MAKE_OP(b, 8);
248MAKE_OP(w, 16);
249MAKE_OP(l, 32);
250#ifdef CONFIG_64BIT
251MAKE_OP(q, 64);
252#endif
253
254void memset_io(volatile void __iomem *addr, int value, size_t size)
255{
256 struct logic_iomem_area *area = get_area(addr);
257 unsigned long offs, start;
258
259 if (!area) {
260 real_memset_io(addr, value, size);
261 return;
262 }
263
264 start = (unsigned long)addr & AREA_MASK;
265
266 if (area->ops->set) {
267 area->ops->set(area->priv, start, value, size);
268 return;
269 }
270
271 for (offs = 0; offs < size; offs++)
272 area->ops->write(area->priv, start + offs, 1, value);
273}
274EXPORT_SYMBOL(memset_io);
275
276void memcpy_fromio(void *buffer, const volatile void __iomem *addr,
277 size_t size)
278{
279 struct logic_iomem_area *area = get_area(addr);
280 u8 *buf = buffer;
281 unsigned long offs, start;
282
283 if (!area) {
284 real_memcpy_fromio(buffer, addr, size);
285 return;
286 }
287
288 start = (unsigned long)addr & AREA_MASK;
289
290 if (area->ops->copy_from) {
291 area->ops->copy_from(area->priv, buffer, start, size);
292 return;
293 }
294
295 for (offs = 0; offs < size; offs++)
296 buf[offs] = area->ops->read(area->priv, start + offs, 1);
297}
298EXPORT_SYMBOL(memcpy_fromio);
299
300void memcpy_toio(volatile void __iomem *addr, const void *buffer, size_t size)
301{
302 struct logic_iomem_area *area = get_area(addr);
303 const u8 *buf = buffer;
304 unsigned long offs, start;
305
306 if (!area) {
307 real_memcpy_toio(addr, buffer, size);
308 return;
309 }
310
311 start = (unsigned long)addr & AREA_MASK;
312
313 if (area->ops->copy_to) {
314 area->ops->copy_to(area->priv, start, buffer, size);
315 return;
316 }
317
318 for (offs = 0; offs < size; offs++)
319 area->ops->write(area->priv, start + offs, 1, buf[offs]);
320}
321EXPORT_SYMBOL(memcpy_toio);