diff options
Diffstat (limited to 'arch')
-rw-r--r-- | arch/s390/kernel/crash_dump.c | 51 |
1 files changed, 47 insertions, 4 deletions
diff --git a/arch/s390/kernel/crash_dump.c b/arch/s390/kernel/crash_dump.c index f703d91bf72..d8f35565717 100644 --- a/arch/s390/kernel/crash_dump.c +++ b/arch/s390/kernel/crash_dump.c @@ -21,6 +21,48 @@ #define PTR_SUB(x, y) (((char *) (x)) - ((unsigned long) (y))) #define PTR_DIFF(x, y) ((unsigned long)(((char *) (x)) - ((unsigned long) (y)))) + +/* + * Return physical address for virtual address + */ +static inline void *load_real_addr(void *addr) +{ + unsigned long real_addr; + + asm volatile( + " lra %0,0(%1)\n" + " jz 0f\n" + " la %0,0\n" + "0:" + : "=a" (real_addr) : "a" (addr) : "cc"); + return (void *)real_addr; +} + +/* + * Copy up to one page to vmalloc or real memory + */ +static ssize_t copy_page_real(void *buf, void *src, size_t csize) +{ + size_t size; + + if (is_vmalloc_addr(buf)) { + BUG_ON(csize >= PAGE_SIZE); + /* If buf is not page aligned, copy first part */ + size = min(roundup(__pa(buf), PAGE_SIZE) - __pa(buf), csize); + if (size) { + if (memcpy_real(load_real_addr(buf), src, size)) + return -EFAULT; + buf += size; + src += size; + } + /* Copy second part */ + size = csize - size; + return (size) ? memcpy_real(load_real_addr(buf), src, size) : 0; + } else { + return memcpy_real(buf, src, csize); + } +} + /* * Copy one page from "oldmem" * @@ -32,6 +74,7 @@ ssize_t copy_oldmem_page(unsigned long pfn, char *buf, size_t csize, unsigned long offset, int userbuf) { unsigned long src; + int rc; if (!csize) return 0; @@ -43,11 +86,11 @@ ssize_t copy_oldmem_page(unsigned long pfn, char *buf, src < OLDMEM_BASE + OLDMEM_SIZE) src -= OLDMEM_BASE; if (userbuf) - copy_to_user_real((void __force __user *) buf, (void *) src, - csize); + rc = copy_to_user_real((void __force __user *) buf, + (void *) src, csize); else - memcpy_real(buf, (void *) src, csize); - return csize; + rc = copy_page_real(buf, (void *) src, csize); + return (rc == 0) ? csize : rc; } /* |