summaryrefslogtreecommitdiffstats
path: root/arch/s390x/mm
diff options
context:
space:
mode:
Diffstat (limited to 'arch/s390x/mm')
-rw-r--r--arch/s390x/mm/Makefile14
-rw-r--r--arch/s390x/mm/extable.c61
-rw-r--r--arch/s390x/mm/fault.c266
-rw-r--r--arch/s390x/mm/init.c405
-rw-r--r--arch/s390x/mm/ioremap.c129
5 files changed, 875 insertions, 0 deletions
diff --git a/arch/s390x/mm/Makefile b/arch/s390x/mm/Makefile
new file mode 100644
index 000000000..73e25bd30
--- /dev/null
+++ b/arch/s390x/mm/Makefile
@@ -0,0 +1,14 @@
+#
+# Makefile for the linux i386-specific parts of the memory manager.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definition is now in the main makefile...
+
+O_TARGET := mm.o
+
+obj-y := init.o fault.o ioremap.o extable.o
+
+include $(TOPDIR)/Rules.make
diff --git a/arch/s390x/mm/extable.c b/arch/s390x/mm/extable.c
new file mode 100644
index 000000000..774e86854
--- /dev/null
+++ b/arch/s390x/mm/extable.c
@@ -0,0 +1,61 @@
+/*
+ * arch/s390/mm/extable.c
+ *
+ * S390 version
+ * Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Author(s): Hartmut Penner (hp@de.ibm.com)
+ *
+ * Derived from "arch/i386/mm/extable.c"
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <asm/uaccess.h>
+
+extern const struct exception_table_entry __start___ex_table[];
+extern const struct exception_table_entry __stop___ex_table[];
+
+static inline unsigned long
+search_one_table(const struct exception_table_entry *first,
+ const struct exception_table_entry *last,
+ unsigned long value)
+{
+ while (first <= last) {
+ const struct exception_table_entry *mid;
+ long diff;
+
+ mid = (last - first) / 2 + first;
+ diff = mid->insn - value;
+ if (diff == 0)
+ return mid->fixup;
+ else if (diff < 0)
+ first = mid+1;
+ else
+ last = mid-1;
+ }
+ return 0;
+}
+
+unsigned long
+search_exception_table(unsigned long addr)
+{
+ unsigned long ret;
+
+#ifndef CONFIG_MODULES
+ /* There is only the kernel to search. */
+ ret = search_one_table(__start___ex_table, __stop___ex_table-1, addr);
+ if (ret) return FIX_PSW(ret);
+#else
+ /* The kernel is the last "module" -- no need to treat it special. */
+ struct module *mp;
+ for (mp = module_list; mp != NULL; mp = mp->next) {
+ if (mp->ex_table_start == NULL)
+ continue;
+ ret = search_one_table(mp->ex_table_start,
+ mp->ex_table_end - 1, addr);
+ if (ret) return FIX_PSW(ret);
+ }
+#endif
+
+ return 0;
+}
diff --git a/arch/s390x/mm/fault.c b/arch/s390x/mm/fault.c
new file mode 100644
index 000000000..4c324b690
--- /dev/null
+++ b/arch/s390x/mm/fault.c
@@ -0,0 +1,266 @@
+/*
+ * arch/s390/mm/fault.c
+ *
+ * S390 version
+ * Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Author(s): Hartmut Penner (hp@de.ibm.com)
+ *
+ * Derived from "arch/i386/mm/fault.c"
+ * Copyright (C) 1995 Linus Torvalds
+ */
+
+#include <linux/config.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/ptrace.h>
+#include <linux/mman.h>
+#include <linux/mm.h>
+#include <linux/smp.h>
+#include <linux/smp_lock.h>
+
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <asm/pgtable.h>
+#include <asm/hardirq.h>
+
+#ifdef CONFIG_SYSCTL
+extern int sysctl_userprocess_debug;
+#endif
+
+extern void die(const char *,struct pt_regs *,long);
+
+/*
+ * This routine handles page faults. It determines the address,
+ * and the problem, and then passes it off to one of the appropriate
+ * routines.
+ *
+ * error_code:
+ * ****0004 Protection -> Write-Protection (suprression)
+ * ****0010 Segment translation -> Not present (nullification)
+ * ****0011 Page translation -> Not present (nullification)
+ * ****003B Region third exception -> Not present (nullification)
+ */
+asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long error_code)
+{
+ struct task_struct *tsk;
+ struct mm_struct *mm;
+ struct vm_area_struct * vma;
+ unsigned long address;
+ unsigned long fixup;
+ int write;
+ unsigned long psw_mask;
+ unsigned long psw_addr;
+ int si_code = SEGV_MAPERR;
+ int kernel_address = 0;
+
+ /*
+ * get psw mask of Program old psw to find out,
+ * if user or kernel mode
+ */
+
+ psw_mask = S390_lowcore.program_old_psw.mask;
+ psw_addr = S390_lowcore.program_old_psw.addr;
+
+ /*
+ * get the failing address
+ * more specific the segment and page table portion of
+ * the address
+ */
+
+ address = S390_lowcore.trans_exc_code&-4096L;
+
+ tsk = current;
+ mm = tsk->mm;
+
+ if (in_interrupt() || !mm)
+ goto no_context;
+
+ /*
+ * Check which address space the address belongs to
+ */
+ switch (S390_lowcore.trans_exc_code & 3)
+ {
+ case 0: /* Primary Segment Table Descriptor */
+ kernel_address = 1;
+ goto no_context;
+
+ case 1: /* STD determined via access register */
+ if (S390_lowcore.exc_access_id == 0)
+ {
+ kernel_address = 1;
+ goto no_context;
+ }
+ if (regs && S390_lowcore.exc_access_id < NUM_ACRS)
+ {
+ if (regs->acrs[S390_lowcore.exc_access_id] == 0)
+ {
+ kernel_address = 1;
+ goto no_context;
+ }
+ if (regs->acrs[S390_lowcore.exc_access_id] == 1)
+ {
+ /* user space address */
+ break;
+ }
+ }
+ die("page fault via unknown access register", regs, error_code);
+ break;
+
+ case 2: /* Secondary Segment Table Descriptor */
+ case 3: /* Home Segment Table Descriptor */
+ /* user space address */
+ break;
+ }
+
+
+ /*
+ * When we get here, the fault happened in the current
+ * task's user address space, so we search the VMAs
+ */
+
+ down(&mm->mmap_sem);
+
+ vma = find_vma(mm, address);
+ if (!vma) {
+ printk("no vma for address %lX\n",address);
+ goto bad_area;
+ }
+ if (vma->vm_start <= address)
+ goto good_area;
+ if (!(vma->vm_flags & VM_GROWSDOWN))
+ goto bad_area;
+ if (expand_stack(vma, address))
+ goto bad_area;
+/*
+ * Ok, we have a good vm_area for this memory access, so
+ * we can handle it..
+ */
+good_area:
+ write = 0;
+ si_code = SEGV_ACCERR;
+
+ switch (error_code & 0xFF) {
+ case 0x04: /* write, present*/
+ write = 1;
+ break;
+ case 0x10: /* not present*/
+ case 0x11: /* not present*/
+ case 0x3B: /* not present*/
+ if (!(vma->vm_flags & (VM_READ | VM_EXEC | VM_WRITE)))
+ goto bad_area;
+ break;
+ default:
+ printk("code should be 4, 10 or 11 (%lX) \n",error_code&0xFF);
+ goto bad_area;
+ }
+
+ /*
+ * If for any reason at all we couldn't handle the fault,
+ * make sure we exit gracefully rather than endlessly redo
+ * the fault.
+ */
+ switch (handle_mm_fault(mm, vma, address, write)) {
+ case 1:
+ tsk->min_flt++;
+ break;
+ case 2:
+ tsk->maj_flt++;
+ break;
+ case 0:
+ goto do_sigbus;
+ default:
+ goto out_of_memory;
+ }
+
+ up(&mm->mmap_sem);
+ return;
+
+/*
+ * Something tried to access memory that isn't in our memory map..
+ * Fix it, but check if it's kernel or user first..
+ */
+bad_area:
+ up(&mm->mmap_sem);
+
+ /* User mode accesses just cause a SIGSEGV */
+ if (psw_mask & PSW_PROBLEM_STATE) {
+ struct siginfo si;
+ tsk->thread.prot_addr = address;
+ tsk->thread.trap_no = error_code;
+#ifndef CONFIG_SYSCTL
+#ifdef CONFIG_PROCESS_DEBUG
+ printk("User process fault: interruption code 0x%lX\n",error_code);
+ printk("failing address: %lX\n",address);
+ show_regs(regs);
+#endif
+#else
+ if (sysctl_userprocess_debug) {
+ printk("User process fault: interruption code 0x%lX\n",
+ error_code);
+ printk("failing address: %lX\n", address);
+ show_regs(regs);
+ }
+#endif
+ si.si_signo = SIGSEGV;
+ si.si_code = si_code;
+ si.si_addr = (void*) address;
+ force_sig_info(SIGSEGV, &si, tsk);
+ return;
+ }
+
+no_context:
+ /* Are we prepared to handle this kernel fault? */
+ if ((fixup = search_exception_table(regs->psw.addr)) != 0) {
+ regs->psw.addr = fixup;
+ return;
+ }
+
+/*
+ * Oops. The kernel tried to access some bad page. We'll have to
+ * terminate things with extreme prejudice.
+ */
+ if (kernel_address)
+ printk(KERN_ALERT "Unable to handle kernel pointer dereference"
+ " at virtual kernel address %016lx\n", address);
+ else
+ printk(KERN_ALERT "Unable to handle kernel paging request"
+ " at virtual user address %016lx\n", address);
+
+/*
+ * need to define, which information is useful here
+ */
+
+ die("Oops", regs, error_code);
+ do_exit(SIGKILL);
+
+
+/*
+ * We ran out of memory, or some other thing happened to us that made
+ * us unable to handle the page fault gracefully.
+*/
+out_of_memory:
+ up(&mm->mmap_sem);
+ printk("VM: killing process %s\n", tsk->comm);
+ if (psw_mask & PSW_PROBLEM_STATE)
+ do_exit(SIGKILL);
+ goto no_context;
+
+do_sigbus:
+ up(&mm->mmap_sem);
+
+ /*
+ * Send a sigbus, regardless of whether we were in kernel
+ * or user mode.
+ */
+ tsk->thread.prot_addr = address;
+ tsk->thread.trap_no = error_code;
+ force_sig(SIGBUS, tsk);
+
+ /* Kernel mode? Handle exceptions or die */
+ if (!(psw_mask & PSW_PROBLEM_STATE))
+ goto no_context;
+}
diff --git a/arch/s390x/mm/init.c b/arch/s390x/mm/init.c
new file mode 100644
index 000000000..91d66ccfe
--- /dev/null
+++ b/arch/s390x/mm/init.c
@@ -0,0 +1,405 @@
+/*
+ * arch/s390/mm/init.c
+ *
+ * S390 version
+ * Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Author(s): Hartmut Penner (hpenner@de.ibm.com)
+ *
+ * Derived from "arch/i386/mm/init.c"
+ * Copyright (C) 1995 Linus Torvalds
+ */
+
+#include <linux/config.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/ptrace.h>
+#include <linux/mman.h>
+#include <linux/mm.h>
+#include <linux/swap.h>
+#include <linux/smp.h>
+#include <linux/init.h>
+#ifdef CONFIG_BLK_DEV_INITRD
+#include <linux/blk.h>
+#endif
+#include <linux/pagemap.h>
+#include <linux/bootmem.h>
+
+#include <asm/processor.h>
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <asm/pgtable.h>
+#include <asm/pgalloc.h>
+#include <asm/dma.h>
+#include <asm/lowcore.h>
+
+static unsigned long totalram_pages;
+
+/*
+ * empty_bad_page is the page that is used for page faults when linux
+ * is out-of-memory. Older versions of linux just did a
+ * do_exit(), but using this instead means there is less risk
+ * for a process dying in kernel mode, possibly leaving an inode
+ * unused etc..
+ *
+ * empty_bad_pte_table is the accompanying page-table: it is initialized
+ * to point to BAD_PAGE entries.
+ *
+ * empty_bad_pmd_table is the accompanying segment table: it is initialized
+ * to point to empty_bad_pte_table page tables.
+ *
+ * ZERO_PAGE is a special page that is used for zero-initialized
+ * data and COW.
+ */
+
+pgd_t swapper_pg_dir[PTRS_PER_PGD] __attribute__((__aligned__(PAGE_SIZE)));
+char empty_bad_page[PAGE_SIZE] __attribute__((__aligned__(PAGE_SIZE)));
+char empty_zero_page[PAGE_SIZE] __attribute__((__aligned__(PAGE_SIZE)));
+pmd_t empty_bad_pmd_table[PTRS_PER_PMD] __attribute__((__aligned__(PAGE_SIZE)));
+pte_t empty_bad_pte_table[PTRS_PER_PTE] __attribute__((__aligned__(PAGE_SIZE)));
+
+static int test_access(unsigned long loc)
+{
+ static const int ssm_mask = 0x07000000L;
+ int rc, i;
+
+ rc = 0;
+ for (i=0; i<2; i++) {
+ __asm__ __volatile__(
+ " slgr %0,%0\n"
+ " ssm %1\n"
+ " tprot 0(%2),0\n"
+ "0: jne 1f\n"
+ " lghi %0,1\n"
+ "1: ssm %3\n"
+ ".section __ex_table,\"a\"\n"
+ " .align 8\n"
+ " .quad 0b,1b\n"
+ ".previous"
+ : "+&d" (rc) : "i" (0), "a" (loc), "m" (ssm_mask)
+ : "cc");
+ if (rc == 0)
+ break;
+ loc += 0x100000;
+ }
+ return rc;
+}
+
+static pmd_t *get_bad_pmd_table(void)
+{
+ pmd_t v;
+ int i;
+
+ pmd_set(&v, empty_bad_pte_table);
+
+ for (i = 0; i < PTRS_PER_PMD; i++)
+ empty_bad_pmd_table[i] = v;
+
+ return empty_bad_pmd_table;
+}
+
+static pte_t *get_bad_pte_table(void)
+{
+ pte_t v;
+ int i;
+
+ v = pte_mkdirty(mk_pte_phys(__pa(empty_bad_page), PAGE_SHARED));
+
+ for (i = 0; i < PAGE_SIZE/sizeof(pte_t); i++)
+ empty_bad_pte_table[i] = v;
+
+ return empty_bad_pte_table;
+}
+
+pmd_t *
+get_pmd_slow(pgd_t *pgd, unsigned long offset)
+{
+ pmd_t *pmd;
+ int i;
+
+ pmd = (pmd_t *) __get_free_pages(GFP_KERNEL,2);
+ if (pgd_none(*pgd)) {
+ if (pmd) {
+ for (i = 0; i < PTRS_PER_PMD; i++)
+ pmd_clear(pmd+i);
+ pgd_set(pgd, pmd);
+ return pmd + offset;
+ }
+ pmd = (pmd_t *) get_bad_pmd_table();
+ pgd_set(pgd, pmd);
+ return NULL;
+ }
+ free_pages((unsigned long)pmd,2);
+ if (pgd_bad(*pgd))
+ BUG();
+ return (pmd_t *) pgd_page(*pgd) + offset;
+}
+
+pte_t *get_pte_slow(pmd_t *pmd, unsigned long offset)
+{
+ pte_t *pte;
+ int i;
+
+ pte = (pte_t*) __get_free_page(GFP_KERNEL);
+ if (pmd_none(*pmd)) {
+ if (pte) {
+ for (i=0;i<PTRS_PER_PTE;i++)
+ pte_clear(pte+i);
+ pmd_set(pmd,pte);
+ return pte + offset;
+ }
+ pte = (pte_t*) get_bad_pte_table();
+ pmd_set(pmd,pte);
+ return NULL;
+ }
+ free_page(__pa(pte));
+ if (pmd_bad(*pmd))
+ BUG();
+ return (pte_t *) pmd_page(*pmd) + offset;
+}
+
+int do_check_pgt_cache(int low, int high)
+{
+ int freed = 0;
+ if(pgtable_cache_size > high) {
+ do {
+ if(pgd_quicklist)
+ free_pgd_slow(get_pgd_fast()), freed += 4;
+ if(pmd_quicklist)
+ free_pmd_slow(get_pmd_fast()), freed += 4;
+ if(pte_quicklist)
+ free_pte_slow(get_pte_fast()), freed++;
+ } while(pgtable_cache_size > low);
+ }
+ return freed;
+}
+
+void show_mem(void)
+{
+ int i, total = 0,reserved = 0;
+ int shared = 0, cached = 0;
+
+ printk("Mem-info:\n");
+ show_free_areas();
+ printk("Free swap: %6dkB\n",nr_swap_pages<<(PAGE_SHIFT-10));
+ i = max_mapnr;
+ while (i-- > 0) {
+ total++;
+ if (PageReserved(mem_map+i))
+ reserved++;
+ else if (PageSwapCache(mem_map+i))
+ cached++;
+ else if (page_count(mem_map+i))
+ shared += atomic_read(&mem_map[i].count) - 1;
+ }
+ printk("%d pages of RAM\n",total);
+ printk("%d reserved pages\n",reserved);
+ printk("%d pages shared\n",shared);
+ printk("%d pages swap cached\n",cached);
+ printk("%ld pages in page table cache\n",pgtable_cache_size);
+ show_buffers();
+}
+
+/* References to section boundaries */
+
+extern unsigned long _text;
+extern unsigned long _etext;
+extern unsigned long _edata;
+extern unsigned long __bss_start;
+extern unsigned long _end;
+
+extern unsigned long __init_begin;
+extern unsigned long __init_end;
+
+/*
+ * paging_init() sets up the page tables
+ */
+
+unsigned long last_valid_pfn;
+
+void __init paging_init(void)
+{
+ pgd_t * pg_dir;
+ pmd_t * pm_dir;
+ pte_t * pt_dir;
+ pte_t pte;
+ int i,j,k;
+ unsigned long address=0;
+ unsigned long pgdir_k = (__pa(swapper_pg_dir) & PAGE_MASK) |
+ _REGION_TABLE;
+ unsigned long end_mem = (unsigned long) __va(max_low_pfn*PAGE_SIZE);
+ static const int ssm_mask = 0x04000000L;
+
+ unsigned long zones_size[MAX_NR_ZONES] = {0, 0, 0};
+ unsigned long dma_pfn, high_pfn;
+
+ dma_pfn = MAX_DMA_ADDRESS >> PAGE_SHIFT;
+ high_pfn = max_low_pfn;
+
+ if (dma_pfn > high_pfn)
+ zones_size[ZONE_DMA] = high_pfn;
+ else {
+ zones_size[ZONE_DMA] = dma_pfn;
+ zones_size[ZONE_NORMAL] = high_pfn - dma_pfn;
+ }
+
+ /* Initialize mem_map[]. */
+ free_area_init(zones_size);
+
+
+ /*
+ * map whole physical memory to virtual memory (identity mapping)
+ */
+
+ pg_dir = swapper_pg_dir;
+
+ for (i = 0 ; i < PTRS_PER_PGD ; i++,pg_dir++) {
+
+ if (address >= end_mem) {
+ pgd_clear(pg_dir);
+ continue;
+ }
+
+ pm_dir = (pmd_t *) alloc_bootmem_low_pages(PAGE_SIZE*4);
+ pgd_set(pg_dir,pm_dir);
+
+ for (j = 0 ; j < PTRS_PER_PMD ; j++,pm_dir++) {
+ if (address >= end_mem) {
+ pmd_clear(pm_dir);
+ continue;
+ }
+
+ pt_dir = (pte_t *) alloc_bootmem_low_pages(PAGE_SIZE);
+ pmd_set(pm_dir,pt_dir);
+
+ for (k = 0 ; k < PTRS_PER_PTE ; k++,pt_dir++) {
+ pte = mk_pte_phys(address, PAGE_KERNEL);
+ if (address >= end_mem) {
+ pte_clear(&pte);
+ continue;
+ }
+ set_pte(pt_dir, pte);
+ address += PAGE_SIZE;
+ }
+ }
+ }
+
+ /* enable virtual mapping in kernel mode */
+ __asm__ __volatile__("lctlg 1,1,%0\n\t"
+ "lctlg 7,7,%0\n\t"
+ "lctlg 13,13,%0\n\t"
+ "ssm %1"
+ : :"m" (pgdir_k), "m" (ssm_mask));
+
+ local_flush_tlb();
+
+ return;
+}
+
+void __init mem_init(void)
+{
+ unsigned long codesize, reservedpages, datasize, initsize;
+ unsigned long tmp;
+
+ max_mapnr = num_physpages = max_low_pfn;
+ high_memory = (void *) __va(max_low_pfn * PAGE_SIZE);
+
+ /* clear the zero-page */
+ memset(empty_zero_page, 0, PAGE_SIZE);
+
+ /* this will put all low memory onto the freelists */
+ totalram_pages += free_all_bootmem();
+
+ /* mark usable pages in the mem_map[] and count reserved pages */
+ reservedpages = 0;
+ tmp = 0;
+ do {
+ if (tmp && (tmp & 0x1ff) == 0 &&
+ test_access(tmp * PAGE_SIZE) == 0) {
+ printk("2M Segment 0x%016lX not available\n",
+ tmp * PAGE_SIZE);
+ do {
+ set_bit(PG_reserved, &mem_map[tmp].flags);
+ reservedpages++;
+ tmp++;
+ } while (tmp < max_low_pfn && (tmp & 0x1ff));
+ } else {
+ if (PageReserved(mem_map+tmp))
+ reservedpages++;
+ tmp++;
+ }
+ } while (tmp < max_low_pfn);
+
+ codesize = (unsigned long) &_etext - (unsigned long) &_text;
+ datasize = (unsigned long) &_edata - (unsigned long) &_etext;
+ initsize = (unsigned long) &__init_end - (unsigned long) &__init_begin;
+ printk("Memory: %luk/%luk available (%ldk kernel code, %ldk reserved, %ldk data, %ldk init)\n",
+ (unsigned long) nr_free_pages() << (PAGE_SHIFT-10),
+ max_mapnr << (PAGE_SHIFT-10),
+ codesize >> 10,
+ reservedpages << (PAGE_SHIFT-10),
+ datasize >>10,
+ initsize >> 10);
+}
+
+void free_initmem(void)
+{
+ unsigned long addr;
+
+ addr = (unsigned long)(&__init_begin);
+ for (; addr < (unsigned long)(&__init_end); addr += PAGE_SIZE) {
+ ClearPageReserved(virt_to_page(addr));
+ set_page_count(virt_to_page(addr), 1);
+ free_page(addr);
+ totalram_pages++;
+ }
+ printk ("Freeing unused kernel memory: %ldk freed\n",
+ (&__init_end - &__init_begin) >> 10);
+}
+
+#ifdef CONFIG_BLK_DEV_INITRD
+void free_initrd_mem(unsigned long start, unsigned long end)
+{
+ if (start < end)
+ printk ("Freeing initrd memory: %ldk freed\n", (end - start) >> 10);
+ for (; start < end; start += PAGE_SIZE) {
+ ClearPageReserved(virt_to_page(start));
+ set_page_count(virt_to_page(start), 1);
+ free_page(start);
+ totalram_pages++;
+ }
+}
+#endif
+
+void si_meminfo(struct sysinfo *val)
+{
+ val->totalram = totalram_pages;
+ val->sharedram = 0;
+ val->freeram = nr_free_pages();
+ val->bufferram = atomic_read(&buffermem_pages);
+ val->totalhigh = 0;
+ val->freehigh = 0;
+ val->mem_unit = PAGE_SIZE;
+}
+
+/*
+ * Overrides for Emacs so that we follow Linus's tabbing style.
+ * Emacs will notice this stuff at the end of the file and automatically
+ * adjust the settings for this buffer only. This must remain at the end
+ * of the file.
+ * ---------------------------------------------------------------------------
+ * Local variables:
+ * c-indent-level: 4
+ * c-brace-imaginary-offset: 0
+ * c-brace-offset: -4
+ * c-argdecl-indent: 4
+ * c-label-offset: -4
+ * c-continued-statement-offset: 4
+ * c-continued-brace-offset: 0
+ * indent-tabs-mode: nil
+ * tab-width: 8
+ * End:
+ */
diff --git a/arch/s390x/mm/ioremap.c b/arch/s390x/mm/ioremap.c
new file mode 100644
index 000000000..38acc4a22
--- /dev/null
+++ b/arch/s390x/mm/ioremap.c
@@ -0,0 +1,129 @@
+/*
+ * arch/s390/mm/ioremap.c
+ *
+ * S390 version
+ * Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Author(s): Hartmut Penner (hp@de.ibm.com)
+ *
+ * Derived from "arch/i386/mm/extable.c"
+ * (C) Copyright 1995 1996 Linus Torvalds
+ *
+ * Re-map IO memory to kernel address space so that we can access it.
+ * This is needed for high PCI addresses that aren't mapped in the
+ * 640k-1MB IO memory area on PC's
+ */
+
+#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;
+
+ 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(_PAGE_PRESENT | flags)));
+ 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_kernel(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)
+{
+ pgd_t * dir;
+ unsigned long end = address + size;
+
+ phys_addr -= address;
+ dir = pgd_offset(&init_mm, address);
+ flush_cache_all();
+ if (address >= end)
+ BUG();
+ do {
+ pmd_t *pmd = pmd_alloc_kernel(dir, address);
+ if (!pmd)
+ return -ENOMEM;
+ if (remap_area_pmd(pmd, address, end - address,
+ phys_addr + address, flags))
+ return -ENOMEM;
+ set_pgdir(address, *dir);
+ address = (address + PGDIR_SIZE) & PGDIR_MASK;
+ dir++;
+ } while (address && (address < end));
+ flush_tlb_all();
+ return 0;
+}
+
+/*
+ * 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.
+ */
+void * __ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags)
+{
+ void * addr;
+ struct vm_struct * area;
+
+ if (phys_addr < virt_to_phys(high_memory))
+ return phys_to_virt(phys_addr);
+ if (phys_addr & ~PAGE_MASK)
+ return NULL;
+ size = PAGE_ALIGN(size);
+ if (!size || size > phys_addr + size)
+ return NULL;
+ 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 addr;
+}
+
+void iounmap(void *addr)
+{
+ if (addr > high_memory)
+ return vfree(addr);
+}