// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2021 Intel Corporation * Author: Johannes Berg <johannes@sipsolutions.net> */ #include <linux/types.h> #include <linux/slab.h> #include <linux/logic_iomem.h> #include <asm/io.h> struct logic_iomem_region { const struct resource *res; const struct logic_iomem_region_ops *ops; struct list_head list; }; struct logic_iomem_area { const struct logic_iomem_ops *ops; void *priv; }; #define AREA_SHIFT 24 #define MAX_AREA_SIZE (1 << AREA_SHIFT) #define MAX_AREAS ((1U << 31) / MAX_AREA_SIZE) #define AREA_BITS ((MAX_AREAS - 1) << AREA_SHIFT) #define AREA_MASK (MAX_AREA_SIZE - 1) #ifdef CONFIG_64BIT #define IOREMAP_BIAS 0xDEAD000000000000UL #define IOREMAP_MASK 0xFFFFFFFF00000000UL #else #define IOREMAP_BIAS 0x80000000UL #define IOREMAP_MASK 0x80000000UL #endif static DEFINE_MUTEX(regions_mtx); static LIST_HEAD(regions_list); static struct logic_iomem_area mapped_areas[MAX_AREAS]; int logic_iomem_add_region(struct resource *resource, const struct logic_iomem_region_ops *ops) { struct logic_iomem_region *rreg; int err; if (WARN_ON(!resource || !ops)) return -EINVAL; if (WARN_ON((resource->flags & IORESOURCE_TYPE_BITS) != IORESOURCE_MEM)) return -EINVAL; rreg = kzalloc(sizeof(*rreg), GFP_KERNEL); if (!rreg) return -ENOMEM; err = request_resource(&iomem_resource, resource); if (err) { kfree(rreg); return -ENOMEM; } mutex_lock(®ions_mtx); rreg->res = resource; rreg->ops = ops; list_add_tail(&rreg->list, ®ions_list); mutex_unlock(®ions_mtx); return 0; } EXPORT_SYMBOL(logic_iomem_add_region); #ifndef CONFIG_INDIRECT_IOMEM_FALLBACK static void __iomem *real_ioremap(phys_addr_t offset, size_t size) { WARN(1, "invalid ioremap(0x%llx, 0x%zx)\n", (unsigned long long)offset, size); return NULL; } static void real_iounmap(volatile void __iomem *addr) { WARN(1, "invalid iounmap for addr 0x%llx\n", (unsigned long long)(uintptr_t __force)addr); } #endif /* CONFIG_INDIRECT_IOMEM_FALLBACK */ void __iomem *ioremap(phys_addr_t offset, size_t size) { void __iomem *ret = NULL; struct logic_iomem_region *rreg, *found = NULL; int i; mutex_lock(®ions_mtx); list_for_each_entry(rreg, ®ions_list, list) { if (rreg->res->start > offset) continue; if (rreg->res->end < offset + size - 1) continue; found = rreg; break; } if (!found) goto out; for (i = 0; i < MAX_AREAS; i++) { long offs; if (mapped_areas[i].ops) continue; offs = rreg->ops->map(offset - found->res->start, size, &mapped_areas[i].ops, &mapped_areas[i].priv); if (offs < 0) { mapped_areas[i].ops = NULL; break; } if (WARN_ON(!mapped_areas[i].ops)) { mapped_areas[i].ops = NULL; break; } ret = (void __iomem *)(IOREMAP_BIAS + (i << AREA_SHIFT) + offs); break; } out: mutex_unlock(®ions_mtx); if (ret) return ret; return real_ioremap(offset, size); } EXPORT_SYMBOL(ioremap); static inline struct logic_iomem_area * get_area(const volatile void __iomem *addr) { unsigned long a = (unsigned long)addr; unsigned int idx; if (WARN_ON((a & IOREMAP_MASK) != IOREMAP_BIAS)) return NULL; idx = (a & AREA_BITS) >> AREA_SHIFT; if (mapped_areas[idx].ops) return &mapped_areas[idx]; return NULL; } void iounmap(volatile void __iomem *addr) { struct logic_iomem_area *area = get_area(addr); if (!area) { real_iounmap(addr); return; } if (area->ops->unmap) area->ops->unmap(area->priv); mutex_lock(®ions_mtx); area->ops = NULL; area->priv = NULL; mutex_unlock(®ions_mtx); } EXPORT_SYMBOL(iounmap); #ifndef CONFIG_INDIRECT_IOMEM_FALLBACK #define MAKE_FALLBACK(op, sz) \ static u##sz real_raw_read ## op(const volatile void __iomem *addr) \ { \ WARN(1, "Invalid read" #op " at address %llx\n", \ (unsigned long long)(uintptr_t __force)addr); \ return (u ## sz)~0ULL; \ } \ \ static void real_raw_write ## op(u ## sz val, \ volatile void __iomem *addr) \ { \ WARN(1, "Invalid writeq" #op " of 0x%llx at address %llx\n", \ (unsigned long long)val, \ (unsigned long long)(uintptr_t __force)addr);\ } \ MAKE_FALLBACK(b, 8); MAKE_FALLBACK(w, 16); MAKE_FALLBACK(l, 32); #ifdef CONFIG_64BIT MAKE_FALLBACK(q, 64); #endif static void real_memset_io(volatile void __iomem *addr, int value, size_t size) { WARN(1, "Invalid memset_io at address 0x%llx\n", (unsigned long long)(uintptr_t __force)addr); } static void real_memcpy_fromio(void *buffer, const volatile void __iomem *addr, size_t size) { WARN(1, "Invalid memcpy_fromio at address 0x%llx\n", (unsigned long long)(uintptr_t __force)addr); memset(buffer, 0xff, size); } static void real_memcpy_toio(volatile void __iomem *addr, const void *buffer, size_t size) { WARN(1, "Invalid memcpy_toio at address 0x%llx\n", (unsigned long long)(uintptr_t __force)addr); } #endif /* CONFIG_INDIRECT_IOMEM_FALLBACK */ #define MAKE_OP(op, sz) \ u##sz __raw_read ## op(const volatile void __iomem *addr) \ { \ struct logic_iomem_area *area = get_area(addr); \ \ if (!area) \ return real_raw_read ## op(addr); \ \ return (u ## sz) area->ops->read(area->priv, \ (unsigned long)addr & AREA_MASK,\ sz / 8); \ } \ EXPORT_SYMBOL(__raw_read ## op); \ \ void __raw_write ## op(u ## sz val, volatile void __iomem *addr) \ { \ struct logic_iomem_area *area = get_area(addr); \ \ if (!area) { \ real_raw_write ## op(val, addr); \ return; \ } \ \ area->ops->write(area->priv, \ (unsigned long)addr & AREA_MASK, \ sz / 8, val); \ } \ EXPORT_SYMBOL(__raw_write ## op) MAKE_OP(b, 8); MAKE_OP(w, 16); MAKE_OP(l, 32); #ifdef CONFIG_64BIT MAKE_OP(q, 64); #endif void memset_io(volatile void __iomem *addr, int value, size_t size) { struct logic_iomem_area *area = get_area(addr); unsigned long offs, start; if (!area) { real_memset_io(addr, value, size); return; } start = (unsigned long)addr & AREA_MASK; if (area->ops->set) { area->ops->set(area->priv, start, value, size); return; } for (offs = 0; offs < size; offs++) area->ops->write(area->priv, start + offs, 1, value); } EXPORT_SYMBOL(memset_io); void memcpy_fromio(void *buffer, const volatile void __iomem *addr, size_t size) { struct logic_iomem_area *area = get_area(addr); u8 *buf = buffer; unsigned long offs, start; if (!area) { real_memcpy_fromio(buffer, addr, size); return; } start = (unsigned long)addr & AREA_MASK; if (area->ops->copy_from) { area->ops->copy_from(area->priv, buffer, start, size); return; } for (offs = 0; offs < size; offs++) buf[offs] = area->ops->read(area->priv, start + offs, 1); } EXPORT_SYMBOL(memcpy_fromio); void memcpy_toio(volatile void __iomem *addr, const void *buffer, size_t size) { struct logic_iomem_area *area = get_area(addr); const u8 *buf = buffer; unsigned long offs, start; if (!area) { real_memcpy_toio(addr, buffer, size); return; } start = (unsigned long)addr & AREA_MASK; if (area->ops->copy_to) { area->ops->copy_to(area->priv, start, buffer, size); return; } for (offs = 0; offs < size; offs++) area->ops->write(area->priv, start + offs, 1, buf[offs]); } EXPORT_SYMBOL(memcpy_toio);