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> 16e7cc9a73SMagnus Damm #include <asm/system.h> 17e7cc9a73SMagnus Damm #include <asm/mmu_context.h> 18e7cc9a73SMagnus Damm #include <asm/uaccess.h> 19e7cc9a73SMagnus Damm #include <asm/io.h> 20e7cc9a73SMagnus Damm #include <asm/io_trapped.h> 21e7cc9a73SMagnus Damm 22e7cc9a73SMagnus Damm #define TRAPPED_PAGES_MAX 16 23e7cc9a73SMagnus Damm 24e7cc9a73SMagnus Damm #ifdef CONFIG_HAS_IOPORT 25e7cc9a73SMagnus Damm LIST_HEAD(trapped_io); 26e7cc9a73SMagnus Damm #endif 27e7cc9a73SMagnus Damm #ifdef CONFIG_HAS_IOMEM 28e7cc9a73SMagnus Damm LIST_HEAD(trapped_mem); 29e7cc9a73SMagnus Damm #endif 30e7cc9a73SMagnus Damm static DEFINE_SPINLOCK(trapped_lock); 31e7cc9a73SMagnus Damm 32e7cc9a73SMagnus Damm int __init register_trapped_io(struct trapped_io *tiop) 33e7cc9a73SMagnus Damm { 34e7cc9a73SMagnus Damm struct resource *res; 35e7cc9a73SMagnus Damm unsigned long len = 0, flags = 0; 36e7cc9a73SMagnus Damm struct page *pages[TRAPPED_PAGES_MAX]; 37e7cc9a73SMagnus Damm int k, n; 38e7cc9a73SMagnus Damm 39e7cc9a73SMagnus Damm /* structure must be page aligned */ 40e7cc9a73SMagnus Damm if ((unsigned long)tiop & (PAGE_SIZE - 1)) 41e7cc9a73SMagnus Damm goto bad; 42e7cc9a73SMagnus Damm 43e7cc9a73SMagnus Damm for (k = 0; k < tiop->num_resources; k++) { 44e7cc9a73SMagnus Damm res = tiop->resource + k; 45e7cc9a73SMagnus Damm len += roundup((res->end - res->start) + 1, PAGE_SIZE); 46e7cc9a73SMagnus Damm flags |= res->flags; 47e7cc9a73SMagnus Damm } 48e7cc9a73SMagnus Damm 49e7cc9a73SMagnus Damm /* support IORESOURCE_IO _or_ MEM, not both */ 50e7cc9a73SMagnus Damm if (hweight_long(flags) != 1) 51e7cc9a73SMagnus Damm goto bad; 52e7cc9a73SMagnus Damm 53e7cc9a73SMagnus Damm n = len >> PAGE_SHIFT; 54e7cc9a73SMagnus Damm 55e7cc9a73SMagnus Damm if (n >= TRAPPED_PAGES_MAX) 56e7cc9a73SMagnus Damm goto bad; 57e7cc9a73SMagnus Damm 58e7cc9a73SMagnus Damm for (k = 0; k < n; k++) 59e7cc9a73SMagnus Damm pages[k] = virt_to_page(tiop); 60e7cc9a73SMagnus Damm 61e7cc9a73SMagnus Damm tiop->virt_base = vmap(pages, n, VM_MAP, PAGE_NONE); 62e7cc9a73SMagnus Damm if (!tiop->virt_base) 63e7cc9a73SMagnus Damm goto bad; 64e7cc9a73SMagnus Damm 65e7cc9a73SMagnus Damm len = 0; 66e7cc9a73SMagnus Damm for (k = 0; k < tiop->num_resources; k++) { 67e7cc9a73SMagnus Damm res = tiop->resource + k; 68e7cc9a73SMagnus Damm pr_info("trapped io 0x%08lx overrides %s 0x%08lx\n", 69e7cc9a73SMagnus Damm (unsigned long)(tiop->virt_base + len), 70e7cc9a73SMagnus Damm res->flags & IORESOURCE_IO ? "io" : "mmio", 71e7cc9a73SMagnus Damm (unsigned long)res->start); 72e7cc9a73SMagnus Damm len += roundup((res->end - res->start) + 1, PAGE_SIZE); 73e7cc9a73SMagnus Damm } 74e7cc9a73SMagnus Damm 75e7cc9a73SMagnus Damm tiop->magic = IO_TRAPPED_MAGIC; 76e7cc9a73SMagnus Damm INIT_LIST_HEAD(&tiop->list); 77e7cc9a73SMagnus Damm spin_lock_irq(&trapped_lock); 78e7cc9a73SMagnus Damm if (flags & IORESOURCE_IO) 79e7cc9a73SMagnus Damm list_add(&tiop->list, &trapped_io); 80e7cc9a73SMagnus Damm if (flags & IORESOURCE_MEM) 81e7cc9a73SMagnus Damm list_add(&tiop->list, &trapped_mem); 82e7cc9a73SMagnus Damm spin_unlock_irq(&trapped_lock); 83e7cc9a73SMagnus Damm 84e7cc9a73SMagnus Damm return 0; 85e7cc9a73SMagnus Damm bad: 86e7cc9a73SMagnus Damm pr_warning("unable to install trapped io filter\n"); 87e7cc9a73SMagnus Damm return -1; 88e7cc9a73SMagnus Damm } 89e7cc9a73SMagnus Damm 90e7cc9a73SMagnus Damm void __iomem *match_trapped_io_handler(struct list_head *list, 91e7cc9a73SMagnus Damm unsigned long offset, 92e7cc9a73SMagnus Damm unsigned long size) 93e7cc9a73SMagnus Damm { 94e7cc9a73SMagnus Damm unsigned long voffs; 95e7cc9a73SMagnus Damm struct trapped_io *tiop; 96e7cc9a73SMagnus Damm struct resource *res; 97e7cc9a73SMagnus Damm int k, len; 98e7cc9a73SMagnus Damm 99e7cc9a73SMagnus Damm spin_lock_irq(&trapped_lock); 100e7cc9a73SMagnus Damm list_for_each_entry(tiop, list, list) { 101e7cc9a73SMagnus Damm voffs = 0; 102e7cc9a73SMagnus Damm for (k = 0; k < tiop->num_resources; k++) { 103e7cc9a73SMagnus Damm res = tiop->resource + k; 104e7cc9a73SMagnus Damm if (res->start == offset) { 105e7cc9a73SMagnus Damm spin_unlock_irq(&trapped_lock); 106e7cc9a73SMagnus Damm return tiop->virt_base + voffs; 107e7cc9a73SMagnus Damm } 108e7cc9a73SMagnus Damm 109e7cc9a73SMagnus Damm len = (res->end - res->start) + 1; 110e7cc9a73SMagnus Damm voffs += roundup(len, PAGE_SIZE); 111e7cc9a73SMagnus Damm } 112e7cc9a73SMagnus Damm } 113e7cc9a73SMagnus Damm spin_unlock_irq(&trapped_lock); 114e7cc9a73SMagnus Damm return NULL; 115e7cc9a73SMagnus Damm } 116e7cc9a73SMagnus Damm 117e7cc9a73SMagnus Damm static struct trapped_io *lookup_tiop(unsigned long address) 118e7cc9a73SMagnus Damm { 119e7cc9a73SMagnus Damm pgd_t *pgd_k; 120e7cc9a73SMagnus Damm pud_t *pud_k; 121e7cc9a73SMagnus Damm pmd_t *pmd_k; 122e7cc9a73SMagnus Damm pte_t *pte_k; 123e7cc9a73SMagnus Damm pte_t entry; 124e7cc9a73SMagnus Damm 125e7cc9a73SMagnus Damm pgd_k = swapper_pg_dir + pgd_index(address); 126e7cc9a73SMagnus Damm if (!pgd_present(*pgd_k)) 127e7cc9a73SMagnus Damm return NULL; 128e7cc9a73SMagnus Damm 129e7cc9a73SMagnus Damm pud_k = pud_offset(pgd_k, address); 130e7cc9a73SMagnus Damm if (!pud_present(*pud_k)) 131e7cc9a73SMagnus Damm return NULL; 132e7cc9a73SMagnus Damm 133e7cc9a73SMagnus Damm pmd_k = pmd_offset(pud_k, address); 134e7cc9a73SMagnus Damm if (!pmd_present(*pmd_k)) 135e7cc9a73SMagnus Damm return NULL; 136e7cc9a73SMagnus Damm 137e7cc9a73SMagnus Damm pte_k = pte_offset_kernel(pmd_k, address); 138e7cc9a73SMagnus Damm entry = *pte_k; 139e7cc9a73SMagnus Damm 140e7cc9a73SMagnus Damm return pfn_to_kaddr(pte_pfn(entry)); 141e7cc9a73SMagnus Damm } 142e7cc9a73SMagnus Damm 143e7cc9a73SMagnus Damm static unsigned long lookup_address(struct trapped_io *tiop, 144e7cc9a73SMagnus Damm unsigned long address) 145e7cc9a73SMagnus Damm { 146e7cc9a73SMagnus Damm struct resource *res; 147e7cc9a73SMagnus Damm unsigned long vaddr = (unsigned long)tiop->virt_base; 148e7cc9a73SMagnus Damm unsigned long len; 149e7cc9a73SMagnus Damm int k; 150e7cc9a73SMagnus Damm 151e7cc9a73SMagnus Damm for (k = 0; k < tiop->num_resources; k++) { 152e7cc9a73SMagnus Damm res = tiop->resource + k; 153e7cc9a73SMagnus Damm len = roundup((res->end - res->start) + 1, PAGE_SIZE); 154e7cc9a73SMagnus Damm if (address < (vaddr + len)) 155e7cc9a73SMagnus Damm return res->start + (address - vaddr); 156e7cc9a73SMagnus Damm vaddr += len; 157e7cc9a73SMagnus Damm } 158e7cc9a73SMagnus Damm return 0; 159e7cc9a73SMagnus Damm } 160e7cc9a73SMagnus Damm 161e7cc9a73SMagnus Damm static unsigned long long copy_word(unsigned long src_addr, int src_len, 162e7cc9a73SMagnus Damm unsigned long dst_addr, int dst_len) 163e7cc9a73SMagnus Damm { 164e7cc9a73SMagnus Damm unsigned long long tmp = 0; 165e7cc9a73SMagnus Damm 166e7cc9a73SMagnus Damm switch (src_len) { 167e7cc9a73SMagnus Damm case 1: 168e7cc9a73SMagnus Damm tmp = ctrl_inb(src_addr); 169e7cc9a73SMagnus Damm break; 170e7cc9a73SMagnus Damm case 2: 171e7cc9a73SMagnus Damm tmp = ctrl_inw(src_addr); 172e7cc9a73SMagnus Damm break; 173e7cc9a73SMagnus Damm case 4: 174e7cc9a73SMagnus Damm tmp = ctrl_inl(src_addr); 175e7cc9a73SMagnus Damm break; 176e7cc9a73SMagnus Damm case 8: 177e7cc9a73SMagnus Damm tmp = ctrl_inq(src_addr); 178e7cc9a73SMagnus Damm break; 179e7cc9a73SMagnus Damm } 180e7cc9a73SMagnus Damm 181e7cc9a73SMagnus Damm switch (dst_len) { 182e7cc9a73SMagnus Damm case 1: 183e7cc9a73SMagnus Damm ctrl_outb(tmp, dst_addr); 184e7cc9a73SMagnus Damm break; 185e7cc9a73SMagnus Damm case 2: 186e7cc9a73SMagnus Damm ctrl_outw(tmp, dst_addr); 187e7cc9a73SMagnus Damm break; 188e7cc9a73SMagnus Damm case 4: 189e7cc9a73SMagnus Damm ctrl_outl(tmp, dst_addr); 190e7cc9a73SMagnus Damm break; 191e7cc9a73SMagnus Damm case 8: 192e7cc9a73SMagnus Damm ctrl_outq(tmp, dst_addr); 193e7cc9a73SMagnus Damm break; 194e7cc9a73SMagnus Damm } 195e7cc9a73SMagnus Damm 196e7cc9a73SMagnus Damm return tmp; 197e7cc9a73SMagnus Damm } 198e7cc9a73SMagnus Damm 199e7cc9a73SMagnus Damm static unsigned long from_device(void *dst, const void *src, unsigned long cnt) 200e7cc9a73SMagnus Damm { 201e7cc9a73SMagnus Damm struct trapped_io *tiop; 202e7cc9a73SMagnus Damm unsigned long src_addr = (unsigned long)src; 203e7cc9a73SMagnus Damm unsigned long long tmp; 204e7cc9a73SMagnus Damm 205e7cc9a73SMagnus Damm pr_debug("trapped io read 0x%08lx (%ld)\n", src_addr, cnt); 206e7cc9a73SMagnus Damm tiop = lookup_tiop(src_addr); 207e7cc9a73SMagnus Damm WARN_ON(!tiop || (tiop->magic != IO_TRAPPED_MAGIC)); 208e7cc9a73SMagnus Damm 209e7cc9a73SMagnus Damm src_addr = lookup_address(tiop, src_addr); 210e7cc9a73SMagnus Damm if (!src_addr) 211e7cc9a73SMagnus Damm return cnt; 212e7cc9a73SMagnus Damm 213*f1cdd63fSPaul Mundt tmp = copy_word(src_addr, 214*f1cdd63fSPaul Mundt max_t(unsigned long, cnt, 215*f1cdd63fSPaul Mundt (tiop->minimum_bus_width / 8)), 216e7cc9a73SMagnus Damm (unsigned long)dst, cnt); 217e7cc9a73SMagnus Damm 218e7cc9a73SMagnus Damm pr_debug("trapped io read 0x%08lx -> 0x%08llx\n", src_addr, tmp); 219e7cc9a73SMagnus Damm return 0; 220e7cc9a73SMagnus Damm } 221e7cc9a73SMagnus Damm 222e7cc9a73SMagnus Damm static unsigned long to_device(void *dst, const void *src, unsigned long cnt) 223e7cc9a73SMagnus Damm { 224e7cc9a73SMagnus Damm struct trapped_io *tiop; 225e7cc9a73SMagnus Damm unsigned long dst_addr = (unsigned long)dst; 226e7cc9a73SMagnus Damm unsigned long long tmp; 227e7cc9a73SMagnus Damm 228e7cc9a73SMagnus Damm pr_debug("trapped io write 0x%08lx (%ld)\n", dst_addr, cnt); 229e7cc9a73SMagnus Damm tiop = lookup_tiop(dst_addr); 230e7cc9a73SMagnus Damm WARN_ON(!tiop || (tiop->magic != IO_TRAPPED_MAGIC)); 231e7cc9a73SMagnus Damm 232e7cc9a73SMagnus Damm dst_addr = lookup_address(tiop, dst_addr); 233e7cc9a73SMagnus Damm if (!dst_addr) 234e7cc9a73SMagnus Damm return cnt; 235e7cc9a73SMagnus Damm 236e7cc9a73SMagnus Damm tmp = copy_word((unsigned long)src, cnt, 237*f1cdd63fSPaul Mundt dst_addr, max_t(unsigned long, cnt, 238*f1cdd63fSPaul Mundt (tiop->minimum_bus_width / 8))); 239e7cc9a73SMagnus Damm 240e7cc9a73SMagnus Damm pr_debug("trapped io write 0x%08lx -> 0x%08llx\n", dst_addr, tmp); 241e7cc9a73SMagnus Damm return 0; 242e7cc9a73SMagnus Damm } 243e7cc9a73SMagnus Damm 244e7cc9a73SMagnus Damm static struct mem_access trapped_io_access = { 245e7cc9a73SMagnus Damm from_device, 246e7cc9a73SMagnus Damm to_device, 247e7cc9a73SMagnus Damm }; 248e7cc9a73SMagnus Damm 249e7cc9a73SMagnus Damm int handle_trapped_io(struct pt_regs *regs, unsigned long address) 250e7cc9a73SMagnus Damm { 251e7cc9a73SMagnus Damm mm_segment_t oldfs; 252e7cc9a73SMagnus Damm opcode_t instruction; 253e7cc9a73SMagnus Damm int tmp; 254e7cc9a73SMagnus Damm 255e7cc9a73SMagnus Damm if (!lookup_tiop(address)) 256e7cc9a73SMagnus Damm return 0; 257e7cc9a73SMagnus Damm 258e7cc9a73SMagnus Damm WARN_ON(user_mode(regs)); 259e7cc9a73SMagnus Damm 260e7cc9a73SMagnus Damm oldfs = get_fs(); 261e7cc9a73SMagnus Damm set_fs(KERNEL_DS); 262e7cc9a73SMagnus Damm if (copy_from_user(&instruction, (void *)(regs->pc), 263e7cc9a73SMagnus Damm sizeof(instruction))) { 264e7cc9a73SMagnus Damm set_fs(oldfs); 265e7cc9a73SMagnus Damm return 0; 266e7cc9a73SMagnus Damm } 267e7cc9a73SMagnus Damm 268e7cc9a73SMagnus Damm tmp = handle_unaligned_access(instruction, regs, &trapped_io_access); 269e7cc9a73SMagnus Damm set_fs(oldfs); 270e7cc9a73SMagnus Damm return tmp == 0; 271e7cc9a73SMagnus Damm } 272