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