1e7cc9a73SMagnus Damm /* 2e7cc9a73SMagnus Damm * Trapped io support 3e7cc9a73SMagnus Damm * 4e7cc9a73SMagnus Damm * Copyright (C) 2008 Magnus Damm 5e7cc9a73SMagnus Damm * 6e7cc9a73SMagnus Damm * Intercept io operations by trapping. 7e7cc9a73SMagnus Damm * 8e7cc9a73SMagnus Damm * This file is subject to the terms and conditions of the GNU General Public 9e7cc9a73SMagnus Damm * License. See the file "COPYING" in the main directory of this archive 10e7cc9a73SMagnus Damm * for more details. 11e7cc9a73SMagnus Damm */ 12e7cc9a73SMagnus Damm #include <linux/kernel.h> 13e7cc9a73SMagnus Damm #include <linux/mm.h> 14e7cc9a73SMagnus Damm #include <linux/bitops.h> 15e7cc9a73SMagnus Damm #include <linux/vmalloc.h> 16ecc14e8cSPaul Mundt #include <linux/module.h> 17eeee7853SPaul Mundt #include <linux/init.h> 18e7cc9a73SMagnus Damm #include <asm/system.h> 19e7cc9a73SMagnus Damm #include <asm/mmu_context.h> 20e7cc9a73SMagnus Damm #include <asm/uaccess.h> 21e7cc9a73SMagnus Damm #include <asm/io.h> 22e7cc9a73SMagnus Damm #include <asm/io_trapped.h> 23e7cc9a73SMagnus Damm 24e7cc9a73SMagnus Damm #define TRAPPED_PAGES_MAX 16 25e7cc9a73SMagnus Damm 26e7cc9a73SMagnus Damm #ifdef CONFIG_HAS_IOPORT 27e7cc9a73SMagnus Damm LIST_HEAD(trapped_io); 28ecc14e8cSPaul Mundt EXPORT_SYMBOL_GPL(trapped_io); 29e7cc9a73SMagnus Damm #endif 30e7cc9a73SMagnus Damm #ifdef CONFIG_HAS_IOMEM 31e7cc9a73SMagnus Damm LIST_HEAD(trapped_mem); 32ecc14e8cSPaul Mundt EXPORT_SYMBOL_GPL(trapped_mem); 33e7cc9a73SMagnus Damm #endif 34e7cc9a73SMagnus Damm static DEFINE_SPINLOCK(trapped_lock); 35e7cc9a73SMagnus Damm 36eeee7853SPaul Mundt static int trapped_io_disable __read_mostly; 37eeee7853SPaul Mundt 38eeee7853SPaul Mundt static int __init trapped_io_setup(char *__unused) 39eeee7853SPaul Mundt { 40eeee7853SPaul Mundt trapped_io_disable = 1; 41eeee7853SPaul Mundt return 1; 42eeee7853SPaul Mundt } 43eeee7853SPaul Mundt __setup("noiotrap", trapped_io_setup); 44eeee7853SPaul Mundt 45b2839ed8SPaul Mundt int register_trapped_io(struct trapped_io *tiop) 46e7cc9a73SMagnus Damm { 47e7cc9a73SMagnus Damm struct resource *res; 48e7cc9a73SMagnus Damm unsigned long len = 0, flags = 0; 49e7cc9a73SMagnus Damm struct page *pages[TRAPPED_PAGES_MAX]; 50e7cc9a73SMagnus Damm int k, n; 51e7cc9a73SMagnus Damm 52eeee7853SPaul Mundt if (unlikely(trapped_io_disable)) 53eeee7853SPaul Mundt return 0; 54eeee7853SPaul Mundt 55e7cc9a73SMagnus Damm /* structure must be page aligned */ 56e7cc9a73SMagnus Damm if ((unsigned long)tiop & (PAGE_SIZE - 1)) 57e7cc9a73SMagnus Damm goto bad; 58e7cc9a73SMagnus Damm 59e7cc9a73SMagnus Damm for (k = 0; k < tiop->num_resources; k++) { 60e7cc9a73SMagnus Damm res = tiop->resource + k; 61e7cc9a73SMagnus Damm len += roundup((res->end - res->start) + 1, PAGE_SIZE); 62e7cc9a73SMagnus Damm flags |= res->flags; 63e7cc9a73SMagnus Damm } 64e7cc9a73SMagnus Damm 65e7cc9a73SMagnus Damm /* support IORESOURCE_IO _or_ MEM, not both */ 66e7cc9a73SMagnus Damm if (hweight_long(flags) != 1) 67e7cc9a73SMagnus Damm goto bad; 68e7cc9a73SMagnus Damm 69e7cc9a73SMagnus Damm n = len >> PAGE_SHIFT; 70e7cc9a73SMagnus Damm 71e7cc9a73SMagnus Damm if (n >= TRAPPED_PAGES_MAX) 72e7cc9a73SMagnus Damm goto bad; 73e7cc9a73SMagnus Damm 74e7cc9a73SMagnus Damm for (k = 0; k < n; k++) 75e7cc9a73SMagnus Damm pages[k] = virt_to_page(tiop); 76e7cc9a73SMagnus Damm 77e7cc9a73SMagnus Damm tiop->virt_base = vmap(pages, n, VM_MAP, PAGE_NONE); 78e7cc9a73SMagnus Damm if (!tiop->virt_base) 79e7cc9a73SMagnus Damm goto bad; 80e7cc9a73SMagnus Damm 81e7cc9a73SMagnus Damm len = 0; 82e7cc9a73SMagnus Damm for (k = 0; k < tiop->num_resources; k++) { 83e7cc9a73SMagnus Damm res = tiop->resource + k; 84e7cc9a73SMagnus Damm pr_info("trapped io 0x%08lx overrides %s 0x%08lx\n", 85e7cc9a73SMagnus Damm (unsigned long)(tiop->virt_base + len), 86e7cc9a73SMagnus Damm res->flags & IORESOURCE_IO ? "io" : "mmio", 87e7cc9a73SMagnus Damm (unsigned long)res->start); 88e7cc9a73SMagnus Damm len += roundup((res->end - res->start) + 1, PAGE_SIZE); 89e7cc9a73SMagnus Damm } 90e7cc9a73SMagnus Damm 91e7cc9a73SMagnus Damm tiop->magic = IO_TRAPPED_MAGIC; 92e7cc9a73SMagnus Damm INIT_LIST_HEAD(&tiop->list); 93e7cc9a73SMagnus Damm spin_lock_irq(&trapped_lock); 9486e4dd5aSPaul Mundt #ifdef CONFIG_HAS_IOPORT 95e7cc9a73SMagnus Damm if (flags & IORESOURCE_IO) 96e7cc9a73SMagnus Damm list_add(&tiop->list, &trapped_io); 9786e4dd5aSPaul Mundt #endif 9886e4dd5aSPaul Mundt #ifdef CONFIG_HAS_IOMEM 99e7cc9a73SMagnus Damm if (flags & IORESOURCE_MEM) 100e7cc9a73SMagnus Damm list_add(&tiop->list, &trapped_mem); 10186e4dd5aSPaul Mundt #endif 102e7cc9a73SMagnus Damm spin_unlock_irq(&trapped_lock); 103e7cc9a73SMagnus Damm 104e7cc9a73SMagnus Damm return 0; 105e7cc9a73SMagnus Damm bad: 106e7cc9a73SMagnus Damm pr_warning("unable to install trapped io filter\n"); 107e7cc9a73SMagnus Damm return -1; 108e7cc9a73SMagnus Damm } 109ecc14e8cSPaul Mundt EXPORT_SYMBOL_GPL(register_trapped_io); 110e7cc9a73SMagnus Damm 111e7cc9a73SMagnus Damm void __iomem *match_trapped_io_handler(struct list_head *list, 112e7cc9a73SMagnus Damm unsigned long offset, 113e7cc9a73SMagnus Damm unsigned long size) 114e7cc9a73SMagnus Damm { 115e7cc9a73SMagnus Damm unsigned long voffs; 116e7cc9a73SMagnus Damm struct trapped_io *tiop; 117e7cc9a73SMagnus Damm struct resource *res; 118e7cc9a73SMagnus Damm int k, len; 119fd78a76aSStuart Menefy unsigned long flags; 120e7cc9a73SMagnus Damm 121fd78a76aSStuart Menefy spin_lock_irqsave(&trapped_lock, flags); 122e7cc9a73SMagnus Damm list_for_each_entry(tiop, list, list) { 123e7cc9a73SMagnus Damm voffs = 0; 124e7cc9a73SMagnus Damm for (k = 0; k < tiop->num_resources; k++) { 125e7cc9a73SMagnus Damm res = tiop->resource + k; 126e7cc9a73SMagnus Damm if (res->start == offset) { 127fd78a76aSStuart Menefy spin_unlock_irqrestore(&trapped_lock, flags); 128e7cc9a73SMagnus Damm return tiop->virt_base + voffs; 129e7cc9a73SMagnus Damm } 130e7cc9a73SMagnus Damm 131e7cc9a73SMagnus Damm len = (res->end - res->start) + 1; 132e7cc9a73SMagnus Damm voffs += roundup(len, PAGE_SIZE); 133e7cc9a73SMagnus Damm } 134e7cc9a73SMagnus Damm } 135fd78a76aSStuart Menefy spin_unlock_irqrestore(&trapped_lock, flags); 136e7cc9a73SMagnus Damm return NULL; 137e7cc9a73SMagnus Damm } 138ecc14e8cSPaul Mundt EXPORT_SYMBOL_GPL(match_trapped_io_handler); 139e7cc9a73SMagnus Damm 140e7cc9a73SMagnus Damm static struct trapped_io *lookup_tiop(unsigned long address) 141e7cc9a73SMagnus Damm { 142e7cc9a73SMagnus Damm pgd_t *pgd_k; 143e7cc9a73SMagnus Damm pud_t *pud_k; 144e7cc9a73SMagnus Damm pmd_t *pmd_k; 145e7cc9a73SMagnus Damm pte_t *pte_k; 146e7cc9a73SMagnus Damm pte_t entry; 147e7cc9a73SMagnus Damm 148e7cc9a73SMagnus Damm pgd_k = swapper_pg_dir + pgd_index(address); 149e7cc9a73SMagnus Damm if (!pgd_present(*pgd_k)) 150e7cc9a73SMagnus Damm return NULL; 151e7cc9a73SMagnus Damm 152e7cc9a73SMagnus Damm pud_k = pud_offset(pgd_k, address); 153e7cc9a73SMagnus Damm if (!pud_present(*pud_k)) 154e7cc9a73SMagnus Damm return NULL; 155e7cc9a73SMagnus Damm 156e7cc9a73SMagnus Damm pmd_k = pmd_offset(pud_k, address); 157e7cc9a73SMagnus Damm if (!pmd_present(*pmd_k)) 158e7cc9a73SMagnus Damm return NULL; 159e7cc9a73SMagnus Damm 160e7cc9a73SMagnus Damm pte_k = pte_offset_kernel(pmd_k, address); 161e7cc9a73SMagnus Damm entry = *pte_k; 162e7cc9a73SMagnus Damm 163e7cc9a73SMagnus Damm return pfn_to_kaddr(pte_pfn(entry)); 164e7cc9a73SMagnus Damm } 165e7cc9a73SMagnus Damm 166e7cc9a73SMagnus Damm static unsigned long lookup_address(struct trapped_io *tiop, 167e7cc9a73SMagnus Damm unsigned long address) 168e7cc9a73SMagnus Damm { 169e7cc9a73SMagnus Damm struct resource *res; 170e7cc9a73SMagnus Damm unsigned long vaddr = (unsigned long)tiop->virt_base; 171e7cc9a73SMagnus Damm unsigned long len; 172e7cc9a73SMagnus Damm int k; 173e7cc9a73SMagnus Damm 174e7cc9a73SMagnus Damm for (k = 0; k < tiop->num_resources; k++) { 175e7cc9a73SMagnus Damm res = tiop->resource + k; 176e7cc9a73SMagnus Damm len = roundup((res->end - res->start) + 1, PAGE_SIZE); 177e7cc9a73SMagnus Damm if (address < (vaddr + len)) 178e7cc9a73SMagnus Damm return res->start + (address - vaddr); 179e7cc9a73SMagnus Damm vaddr += len; 180e7cc9a73SMagnus Damm } 181e7cc9a73SMagnus Damm return 0; 182e7cc9a73SMagnus Damm } 183e7cc9a73SMagnus Damm 184e7cc9a73SMagnus Damm static unsigned long long copy_word(unsigned long src_addr, int src_len, 185e7cc9a73SMagnus Damm unsigned long dst_addr, int dst_len) 186e7cc9a73SMagnus Damm { 187e7cc9a73SMagnus Damm unsigned long long tmp = 0; 188e7cc9a73SMagnus Damm 189e7cc9a73SMagnus Damm switch (src_len) { 190e7cc9a73SMagnus Damm case 1: 1919d56dd3bSPaul Mundt tmp = __raw_readb(src_addr); 192e7cc9a73SMagnus Damm break; 193e7cc9a73SMagnus Damm case 2: 1949d56dd3bSPaul Mundt tmp = __raw_readw(src_addr); 195e7cc9a73SMagnus Damm break; 196e7cc9a73SMagnus Damm case 4: 1979d56dd3bSPaul Mundt tmp = __raw_readl(src_addr); 198e7cc9a73SMagnus Damm break; 199e7cc9a73SMagnus Damm case 8: 2009d56dd3bSPaul Mundt tmp = __raw_readq(src_addr); 201e7cc9a73SMagnus Damm break; 202e7cc9a73SMagnus Damm } 203e7cc9a73SMagnus Damm 204e7cc9a73SMagnus Damm switch (dst_len) { 205e7cc9a73SMagnus Damm case 1: 2069d56dd3bSPaul Mundt __raw_writeb(tmp, dst_addr); 207e7cc9a73SMagnus Damm break; 208e7cc9a73SMagnus Damm case 2: 2099d56dd3bSPaul Mundt __raw_writew(tmp, dst_addr); 210e7cc9a73SMagnus Damm break; 211e7cc9a73SMagnus Damm case 4: 2129d56dd3bSPaul Mundt __raw_writel(tmp, dst_addr); 213e7cc9a73SMagnus Damm break; 214e7cc9a73SMagnus Damm case 8: 2159d56dd3bSPaul Mundt __raw_writeq(tmp, dst_addr); 216e7cc9a73SMagnus Damm break; 217e7cc9a73SMagnus Damm } 218e7cc9a73SMagnus Damm 219e7cc9a73SMagnus Damm return tmp; 220e7cc9a73SMagnus Damm } 221e7cc9a73SMagnus Damm 222e7cc9a73SMagnus Damm static unsigned long from_device(void *dst, const void *src, unsigned long cnt) 223e7cc9a73SMagnus Damm { 224e7cc9a73SMagnus Damm struct trapped_io *tiop; 225e7cc9a73SMagnus Damm unsigned long src_addr = (unsigned long)src; 226e7cc9a73SMagnus Damm unsigned long long tmp; 227e7cc9a73SMagnus Damm 228e7cc9a73SMagnus Damm pr_debug("trapped io read 0x%08lx (%ld)\n", src_addr, cnt); 229e7cc9a73SMagnus Damm tiop = lookup_tiop(src_addr); 230e7cc9a73SMagnus Damm WARN_ON(!tiop || (tiop->magic != IO_TRAPPED_MAGIC)); 231e7cc9a73SMagnus Damm 232e7cc9a73SMagnus Damm src_addr = lookup_address(tiop, src_addr); 233e7cc9a73SMagnus Damm if (!src_addr) 234e7cc9a73SMagnus Damm return cnt; 235e7cc9a73SMagnus Damm 236f1cdd63fSPaul Mundt tmp = copy_word(src_addr, 237f1cdd63fSPaul Mundt max_t(unsigned long, cnt, 238f1cdd63fSPaul Mundt (tiop->minimum_bus_width / 8)), 239e7cc9a73SMagnus Damm (unsigned long)dst, cnt); 240e7cc9a73SMagnus Damm 241e7cc9a73SMagnus Damm pr_debug("trapped io read 0x%08lx -> 0x%08llx\n", src_addr, tmp); 242e7cc9a73SMagnus Damm return 0; 243e7cc9a73SMagnus Damm } 244e7cc9a73SMagnus Damm 245e7cc9a73SMagnus Damm static unsigned long to_device(void *dst, const void *src, unsigned long cnt) 246e7cc9a73SMagnus Damm { 247e7cc9a73SMagnus Damm struct trapped_io *tiop; 248e7cc9a73SMagnus Damm unsigned long dst_addr = (unsigned long)dst; 249e7cc9a73SMagnus Damm unsigned long long tmp; 250e7cc9a73SMagnus Damm 251e7cc9a73SMagnus Damm pr_debug("trapped io write 0x%08lx (%ld)\n", dst_addr, cnt); 252e7cc9a73SMagnus Damm tiop = lookup_tiop(dst_addr); 253e7cc9a73SMagnus Damm WARN_ON(!tiop || (tiop->magic != IO_TRAPPED_MAGIC)); 254e7cc9a73SMagnus Damm 255e7cc9a73SMagnus Damm dst_addr = lookup_address(tiop, dst_addr); 256e7cc9a73SMagnus Damm if (!dst_addr) 257e7cc9a73SMagnus Damm return cnt; 258e7cc9a73SMagnus Damm 259e7cc9a73SMagnus Damm tmp = copy_word((unsigned long)src, cnt, 260f1cdd63fSPaul Mundt dst_addr, max_t(unsigned long, cnt, 261f1cdd63fSPaul Mundt (tiop->minimum_bus_width / 8))); 262e7cc9a73SMagnus Damm 263e7cc9a73SMagnus Damm pr_debug("trapped io write 0x%08lx -> 0x%08llx\n", dst_addr, tmp); 264e7cc9a73SMagnus Damm return 0; 265e7cc9a73SMagnus Damm } 266e7cc9a73SMagnus Damm 267e7cc9a73SMagnus Damm static struct mem_access trapped_io_access = { 268e7cc9a73SMagnus Damm from_device, 269e7cc9a73SMagnus Damm to_device, 270e7cc9a73SMagnus Damm }; 271e7cc9a73SMagnus Damm 272e7cc9a73SMagnus Damm int handle_trapped_io(struct pt_regs *regs, unsigned long address) 273e7cc9a73SMagnus Damm { 274e7cc9a73SMagnus Damm mm_segment_t oldfs; 2752bcfffa4SPaul Mundt insn_size_t instruction; 276e7cc9a73SMagnus Damm int tmp; 277e7cc9a73SMagnus Damm 27808b36c4aSPaul Mundt if (trapped_io_disable) 27908b36c4aSPaul Mundt return 0; 280e7cc9a73SMagnus Damm if (!lookup_tiop(address)) 281e7cc9a73SMagnus Damm return 0; 282e7cc9a73SMagnus Damm 283e7cc9a73SMagnus Damm WARN_ON(user_mode(regs)); 284e7cc9a73SMagnus Damm 285e7cc9a73SMagnus Damm oldfs = get_fs(); 286e7cc9a73SMagnus Damm set_fs(KERNEL_DS); 287e7cc9a73SMagnus Damm if (copy_from_user(&instruction, (void *)(regs->pc), 288e7cc9a73SMagnus Damm sizeof(instruction))) { 289e7cc9a73SMagnus Damm set_fs(oldfs); 290e7cc9a73SMagnus Damm return 0; 291e7cc9a73SMagnus Damm } 292e7cc9a73SMagnus Damm 2934aa5ac4eSMatt Fleming tmp = handle_unaligned_access(instruction, regs, 294*ace2dc7dSPaul Mundt &trapped_io_access, 1, address); 295e7cc9a73SMagnus Damm set_fs(oldfs); 296e7cc9a73SMagnus Damm return tmp == 0; 297e7cc9a73SMagnus Damm } 298