| // 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> |
| |
| 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 ((1ULL<<32) / 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 0 |
| #define IOREMAP_MASK 0 |
| #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_LOGIC_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(void __iomem *addr) |
| { |
| WARN(1, "invalid iounmap for addr 0x%llx\n", |
| (unsigned long long)addr); |
| } |
| #endif /* CONFIG_LOGIC_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(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_LOGIC_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)addr); \ |
| return (u ## sz)~0ULL; \ |
| } \ |
| \ |
| 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)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)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)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)addr); |
| } |
| #endif /* CONFIG_LOGIC_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); |