1 // SPDX-License-Identifier: GPL-2.0
3 * Copyright (C) 2021 Intel Corporation
4 * Author: Johannes Berg <johannes@sipsolutions.net>
6 #include <linux/types.h>
7 #include <linux/slab.h>
8 #include <linux/logic_iomem.h>
11 struct logic_iomem_region {
12 const struct resource *res;
13 const struct logic_iomem_region_ops *ops;
14 struct list_head list;
17 struct logic_iomem_area {
18 const struct logic_iomem_ops *ops;
23 #define MAX_AREA_SIZE (1 << AREA_SHIFT)
24 #define MAX_AREAS ((1ULL<<32) / MAX_AREA_SIZE)
25 #define AREA_BITS ((MAX_AREAS - 1) << AREA_SHIFT)
26 #define AREA_MASK (MAX_AREA_SIZE - 1)
28 #define IOREMAP_BIAS 0xDEAD000000000000UL
29 #define IOREMAP_MASK 0xFFFFFFFF00000000UL
31 #define IOREMAP_BIAS 0
32 #define IOREMAP_MASK 0
35 static DEFINE_MUTEX(regions_mtx);
36 static LIST_HEAD(regions_list);
37 static struct logic_iomem_area mapped_areas[MAX_AREAS];
39 int logic_iomem_add_region(struct resource *resource,
40 const struct logic_iomem_region_ops *ops)
42 struct logic_iomem_region *rreg;
45 if (WARN_ON(!resource || !ops))
48 if (WARN_ON((resource->flags & IORESOURCE_TYPE_BITS) != IORESOURCE_MEM))
51 rreg = kzalloc(sizeof(*rreg), GFP_KERNEL);
55 err = request_resource(&iomem_resource, resource);
61 mutex_lock(®ions_mtx);
64 list_add_tail(&rreg->list, ®ions_list);
65 mutex_unlock(®ions_mtx);
69 EXPORT_SYMBOL(logic_iomem_add_region);
71 #ifndef CONFIG_LOGIC_IOMEM_FALLBACK
72 static void __iomem *real_ioremap(phys_addr_t offset, size_t size)
74 WARN(1, "invalid ioremap(0x%llx, 0x%zx)\n",
75 (unsigned long long)offset, size);
79 static void real_iounmap(void __iomem *addr)
81 WARN(1, "invalid iounmap for addr 0x%llx\n",
82 (unsigned long long __force)addr);
84 #endif /* CONFIG_LOGIC_IOMEM_FALLBACK */
86 void __iomem *ioremap(phys_addr_t offset, size_t size)
88 void __iomem *ret = NULL;
89 struct logic_iomem_region *rreg, *found = NULL;
92 mutex_lock(®ions_mtx);
93 list_for_each_entry(rreg, ®ions_list, list) {
94 if (rreg->res->start > offset)
96 if (rreg->res->end < offset + size - 1)
105 for (i = 0; i < MAX_AREAS; i++) {
108 if (mapped_areas[i].ops)
111 offs = rreg->ops->map(offset - found->res->start,
112 size, &mapped_areas[i].ops,
113 &mapped_areas[i].priv);
115 mapped_areas[i].ops = NULL;
119 if (WARN_ON(!mapped_areas[i].ops)) {
120 mapped_areas[i].ops = NULL;
124 ret = (void __iomem *)(IOREMAP_BIAS + (i << AREA_SHIFT) + offs);
128 mutex_unlock(®ions_mtx);
131 return real_ioremap(offset, size);
133 EXPORT_SYMBOL(ioremap);
135 static inline struct logic_iomem_area *
136 get_area(const volatile void __iomem *addr)
138 unsigned long a = (unsigned long)addr;
141 if (WARN_ON((a & IOREMAP_MASK) != IOREMAP_BIAS))
144 idx = (a & AREA_BITS) >> AREA_SHIFT;
146 if (mapped_areas[idx].ops)
147 return &mapped_areas[idx];
152 void iounmap(void __iomem *addr)
154 struct logic_iomem_area *area = get_area(addr);
161 if (area->ops->unmap)
162 area->ops->unmap(area->priv);
164 mutex_lock(®ions_mtx);
167 mutex_unlock(®ions_mtx);
169 EXPORT_SYMBOL(iounmap);
171 #ifndef CONFIG_LOGIC_IOMEM_FALLBACK
172 #define MAKE_FALLBACK(op, sz) \
173 static u##sz real_raw_read ## op(const volatile void __iomem *addr) \
175 WARN(1, "Invalid read" #op " at address %llx\n", \
176 (unsigned long long __force)addr); \
177 return (u ## sz)~0ULL; \
180 static void real_raw_write ## op(u ## sz val, \
181 volatile void __iomem *addr) \
183 WARN(1, "Invalid writeq" #op " of 0x%llx at address %llx\n", \
184 (unsigned long long)val, (unsigned long long __force)addr);\
188 MAKE_FALLBACK(w, 16);
189 MAKE_FALLBACK(l, 32);
191 MAKE_FALLBACK(q, 64);
194 static void real_memset_io(volatile void __iomem *addr, int value, size_t size)
196 WARN(1, "Invalid memset_io at address 0x%llx\n",
197 (unsigned long long __force)addr);
200 static void real_memcpy_fromio(void *buffer, const volatile void __iomem *addr,
203 WARN(1, "Invalid memcpy_fromio at address 0x%llx\n",
204 (unsigned long long __force)addr);
206 memset(buffer, 0xff, size);
209 static void real_memcpy_toio(volatile void __iomem *addr, const void *buffer,
212 WARN(1, "Invalid memcpy_toio at address 0x%llx\n",
213 (unsigned long long __force)addr);
215 #endif /* CONFIG_LOGIC_IOMEM_FALLBACK */
217 #define MAKE_OP(op, sz) \
218 u##sz __raw_read ## op(const volatile void __iomem *addr) \
220 struct logic_iomem_area *area = get_area(addr); \
223 return real_raw_read ## op(addr); \
225 return (u ## sz) area->ops->read(area->priv, \
226 (unsigned long)addr & AREA_MASK,\
229 EXPORT_SYMBOL(__raw_read ## op); \
231 void __raw_write ## op(u ## sz val, volatile void __iomem *addr) \
233 struct logic_iomem_area *area = get_area(addr); \
236 real_raw_write ## op(val, addr); \
240 area->ops->write(area->priv, \
241 (unsigned long)addr & AREA_MASK, \
244 EXPORT_SYMBOL(__raw_write ## op)
253 void memset_io(volatile void __iomem *addr, int value, size_t size)
255 struct logic_iomem_area *area = get_area(addr);
256 unsigned long offs, start;
259 real_memset_io(addr, value, size);
263 start = (unsigned long)addr & AREA_MASK;
265 if (area->ops->set) {
266 area->ops->set(area->priv, start, value, size);
270 for (offs = 0; offs < size; offs++)
271 area->ops->write(area->priv, start + offs, 1, value);
273 EXPORT_SYMBOL(memset_io);
275 void memcpy_fromio(void *buffer, const volatile void __iomem *addr,
278 struct logic_iomem_area *area = get_area(addr);
280 unsigned long offs, start;
283 real_memcpy_fromio(buffer, addr, size);
287 start = (unsigned long)addr & AREA_MASK;
289 if (area->ops->copy_from) {
290 area->ops->copy_from(area->priv, buffer, start, size);
294 for (offs = 0; offs < size; offs++)
295 buf[offs] = area->ops->read(area->priv, start + offs, 1);
297 EXPORT_SYMBOL(memcpy_fromio);
299 void memcpy_toio(volatile void __iomem *addr, const void *buffer, size_t size)
301 struct logic_iomem_area *area = get_area(addr);
302 const u8 *buf = buffer;
303 unsigned long offs, start;
306 real_memcpy_toio(addr, buffer, size);
310 start = (unsigned long)addr & AREA_MASK;
312 if (area->ops->copy_to) {
313 area->ops->copy_to(area->priv, start, buffer, size);
317 for (offs = 0; offs < size; offs++)
318 area->ops->write(area->priv, start + offs, 1, buf[offs]);
320 EXPORT_SYMBOL(memcpy_toio);