summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRalf Baechle <ralf@linux-mips.org>2001-06-03 00:09:32 +0000
committerRalf Baechle <ralf@linux-mips.org>2001-06-03 00:09:32 +0000
commit66cf76d26171830e98cd602965fcd1b17a637ebd (patch)
tree60ec6ac074abb533410c41c651bece1adcfa6aa6
parentc78b8b4b763386457d0ee0e3bade0494fbf46d7a (diff)
Ioremap for 32-bit kernel.
-rw-r--r--arch/mips/mm/Makefile4
-rw-r--r--arch/mips/mm/ioremap.c176
-rw-r--r--include/asm-mips/io.h36
3 files changed, 185 insertions, 31 deletions
diff --git a/arch/mips/mm/Makefile b/arch/mips/mm/Makefile
index 5976ee150..aa1d667f5 100644
--- a/arch/mips/mm/Makefile
+++ b/arch/mips/mm/Makefile
@@ -9,8 +9,8 @@
O_TARGET := mm.o
-export-objs += umap.o
-obj-y += extable.o init.o fault.o loadmmu.o
+export-objs += ioremap.o umap.o
+obj-y += extable.o init.o ioremap.o fault.o loadmmu.o
obj-$(CONFIG_CPU_R3000) += r2300.o
obj-$(CONFIG_CPU_R4300) += r4xx0.o
diff --git a/arch/mips/mm/ioremap.c b/arch/mips/mm/ioremap.c
new file mode 100644
index 000000000..97bb6a734
--- /dev/null
+++ b/arch/mips/mm/ioremap.c
@@ -0,0 +1,176 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * (C) Copyright 1995 1996 Linus Torvalds
+ * (C) Copyright 2001 Ralf Baechle
+ */
+#include <linux/module.h>
+#include <asm/addrspace.h>
+#include <asm/byteorder.h>
+
+#include <linux/vmalloc.h>
+#include <asm/io.h>
+#include <asm/pgalloc.h>
+
+static inline void remap_area_pte(pte_t * pte, unsigned long address, unsigned long size,
+ unsigned long phys_addr, unsigned long flags)
+{
+ unsigned long end;
+ pgprot_t pgprot = __pgprot(_PAGE_GLOBAL | _PAGE_PRESENT | __READABLE
+ | __WRITEABLE | flags);
+
+ address &= ~PMD_MASK;
+ end = address + size;
+ if (end > PMD_SIZE)
+ end = PMD_SIZE;
+ if (address >= end)
+ BUG();
+ do {
+ if (!pte_none(*pte)) {
+ printk("remap_area_pte: page already exists\n");
+ BUG();
+ }
+ set_pte(pte, mk_pte_phys(phys_addr, pgprot));
+ address += PAGE_SIZE;
+ phys_addr += PAGE_SIZE;
+ pte++;
+ } while (address && (address < end));
+}
+
+static inline int remap_area_pmd(pmd_t * pmd, unsigned long address, unsigned long size,
+ unsigned long phys_addr, unsigned long flags)
+{
+ unsigned long end;
+
+ address &= ~PGDIR_MASK;
+ end = address + size;
+ if (end > PGDIR_SIZE)
+ end = PGDIR_SIZE;
+ phys_addr -= address;
+ if (address >= end)
+ BUG();
+ do {
+ pte_t * pte = pte_alloc(&init_mm, pmd, address);
+ if (!pte)
+ return -ENOMEM;
+ remap_area_pte(pte, address, end - address, address + phys_addr, flags);
+ address = (address + PMD_SIZE) & PMD_MASK;
+ pmd++;
+ } while (address && (address < end));
+ return 0;
+}
+
+static int remap_area_pages(unsigned long address, unsigned long phys_addr,
+ unsigned long size, unsigned long flags)
+{
+ int error;
+ pgd_t * dir;
+ unsigned long end = address + size;
+
+ phys_addr -= address;
+ dir = pgd_offset(&init_mm, address);
+ flush_cache_all();
+ if (address >= end)
+ BUG();
+ spin_lock(&init_mm.page_table_lock);
+ do {
+ pmd_t *pmd;
+ pmd = pmd_alloc(&init_mm, dir, address);
+ error = -ENOMEM;
+ if (!pmd)
+ break;
+ if (remap_area_pmd(pmd, address, end - address,
+ phys_addr + address, flags))
+ break;
+ error = 0;
+ address = (address + PGDIR_SIZE) & PGDIR_MASK;
+ dir++;
+ } while (address && (address < end));
+ spin_unlock(&init_mm.page_table_lock);
+ flush_tlb_all();
+ return error;
+}
+
+/*
+ * Generic mapping function (not visible outside):
+ */
+
+/*
+ * Remap an arbitrary physical address space into the kernel virtual
+ * address space. Needed when the kernel wants to access high addresses
+ * directly.
+ *
+ * NOTE! We need to allow non-page-aligned mappings too: we will obviously
+ * have to convert them into an offset in a page-aligned mapping, but the
+ * caller shouldn't need to know that small detail.
+ */
+
+#define IS_LOW512(addr) (!((unsigned long)(addr) & ~0x1fffffffUL))
+
+void * __ioremap(unsigned long phys_addr, unsigned long size)
+{
+ void * addr;
+ struct vm_struct * area;
+ unsigned long offset, last_addr, flags = _CACHE_UNCACHED;
+
+ /* Don't allow wraparound or zero size */
+ last_addr = phys_addr + size - 1;
+ if (!size || last_addr < phys_addr)
+ return NULL;
+
+ /*
+ * Map objects in the low 512mb of address space using KSEG1, otherwise
+ * map using page tables.
+ */
+ if (IS_LOW512(phys_addr) && IS_LOW512(phys_addr + size - 1))
+ return (void *) KSEG1ADDR(phys_addr);
+
+ /*
+ * Don't allow anybody to remap normal RAM that we're using..
+ */
+ if (phys_addr < virt_to_phys(high_memory)) {
+ char *t_addr, *t_end;
+ struct page *page;
+
+ t_addr = __va(phys_addr);
+ t_end = t_addr + (size - 1);
+
+ for(page = virt_to_page(t_addr); page <= virt_to_page(t_end); page++)
+ if(!PageReserved(page))
+ return NULL;
+ }
+
+ /*
+ * Mappings have to be page-aligned
+ */
+ offset = phys_addr & ~PAGE_MASK;
+ phys_addr &= PAGE_MASK;
+ size = PAGE_ALIGN(last_addr) - phys_addr;
+
+ /*
+ * Ok, go for it..
+ */
+ area = get_vm_area(size, VM_IOREMAP);
+ if (!area)
+ return NULL;
+ addr = area->addr;
+ if (remap_area_pages(VMALLOC_VMADDR(addr), phys_addr, size, flags)) {
+ vfree(addr);
+ return NULL;
+ }
+
+ return (void *) (offset + (char *)addr);
+}
+
+#define IS_KSEG1(addr) (((unsigned long)(addr) & ~0x1fffffffUL) == KSEG1)
+
+void iounmap(void *addr)
+{
+ if (!IS_KSEG1(addr))
+ return vfree((void *) (PAGE_MASK & (unsigned long) addr));
+}
+
+EXPORT_SYMBOL(__ioremap);
+EXPORT_SYMBOL(iounmap);
diff --git a/include/asm-mips/io.h b/include/asm-mips/io.h
index 1a89c2cf0..f89b4c754 100644
--- a/include/asm-mips/io.h
+++ b/include/asm-mips/io.h
@@ -111,9 +111,6 @@ extern inline void * phys_to_virt(unsigned long address)
return (void *)KSEG0ADDR(address);
}
-extern void * ioremap(unsigned long phys_addr, unsigned long size);
-extern void iounmap(void *addr);
-
/*
* IO bus memory addresses are also 1:1 with the physical address
*/
@@ -133,39 +130,20 @@ extern inline void * bus_to_virt(unsigned long address)
*/
extern unsigned long isa_slot_offset;
-/*
- * readX/writeX() are used to access memory mapped devices. On some
- * architectures the memory mapped IO stuff needs to be accessed
- * differently. On the x86 architecture, we just read/write the
- * memory location directly.
- *
- * On MIPS, we have the whole physical address space mapped at all
- * times, so "ioremap()" and "iounmap()" do not need to do anything.
- * (This isn't true for all machines but we still handle these cases
- * with wired TLB entries anyway ...)
- *
- * We cheat a bit and always return uncachable areas until we've fixed
- * the drivers to handle caching properly.
- */
-extern inline void * ioremap(unsigned long offset, unsigned long size)
-{
- return (void *) KSEG1ADDR(offset);
-}
+extern void * __ioremap(unsigned long offset, unsigned long size);
-/*
- * This one maps high address device memory and turns off caching for that area.
- * it's useful if some control registers are in such an area and write combining
- * or read caching is not desirable:
- */
-extern inline void * ioremap_nocache (unsigned long offset, unsigned long size)
+extern inline void *ioremap(unsigned long offset, unsigned long size)
{
- return (void *) KSEG1ADDR(offset);
+ return __ioremap(offset, size);
}
-extern inline void iounmap(void *addr)
+extern inline void *ioremap_nocache(unsigned long offset, unsigned long size)
{
+ return __ioremap(offset, size);
}
+extern void iounmap(void *addr);
+
/*
* XXX We need system specific versions of these to handle EISA address bits
* 24-31 on SNI.