if (((addr + size) > d_start) && (addr <= d_end))
                return 1;
 
-       return (addr <= fw_dump.boot_memory_size);
+       return (addr <= fw_dump.boot_mem_top);
 }
 
 int should_fadump_crash(void)
  */
 bool is_fadump_boot_mem_contiguous(void)
 {
-       return is_fadump_mem_area_contiguous(0, fw_dump.boot_memory_size);
+       unsigned long d_start, d_end;
+       bool ret = false;
+       int i;
+
+       for (i = 0; i < fw_dump.boot_mem_regs_cnt; i++) {
+               d_start = fw_dump.boot_mem_addr[i];
+               d_end   = d_start + fw_dump.boot_mem_sz[i];
+
+               ret = is_fadump_mem_area_contiguous(d_start, d_end);
+               if (!ret)
+                       break;
+       }
+
+       return ret;
 }
 
 /*
 /* Print firmware assisted dump configurations for debugging purpose. */
 static void fadump_show_config(void)
 {
+       int i;
+
        pr_debug("Support for firmware-assisted dump (fadump): %s\n",
                        (fw_dump.fadump_supported ? "present" : "no support"));
 
        pr_debug("Dump section sizes:\n");
        pr_debug("    CPU state data size: %lx\n", fw_dump.cpu_state_data_size);
        pr_debug("    HPTE region size   : %lx\n", fw_dump.hpte_region_size);
-       pr_debug("Boot memory size  : %lx\n", fw_dump.boot_memory_size);
+       pr_debug("    Boot memory size   : %lx\n", fw_dump.boot_memory_size);
+       pr_debug("    Boot memory top    : %llx\n", fw_dump.boot_mem_top);
+       pr_debug("Boot memory regions cnt: %llx\n", fw_dump.boot_mem_regs_cnt);
+       for (i = 0; i < fw_dump.boot_mem_regs_cnt; i++) {
+               pr_debug("[%03d] base = %llx, size = %llx\n", i,
+                        fw_dump.boot_mem_addr[i], fw_dump.boot_mem_sz[i]);
+       }
 }
 
 /**
        return size;
 }
 
+static int __init add_boot_mem_region(unsigned long rstart,
+                                     unsigned long rsize)
+{
+       int i = fw_dump.boot_mem_regs_cnt++;
+
+       if (fw_dump.boot_mem_regs_cnt > FADUMP_MAX_MEM_REGS) {
+               fw_dump.boot_mem_regs_cnt = FADUMP_MAX_MEM_REGS;
+               return 0;
+       }
+
+       pr_debug("Added boot memory range[%d] [%#016lx-%#016lx)\n",
+                i, rstart, (rstart + rsize));
+       fw_dump.boot_mem_addr[i] = rstart;
+       fw_dump.boot_mem_sz[i] = rsize;
+       return 1;
+}
+
+/*
+ * Firmware usually has a hard limit on the data it can copy per region.
+ * Honour that by splitting a memory range into multiple regions.
+ */
+static int __init add_boot_mem_regions(unsigned long mstart,
+                                      unsigned long msize)
+{
+       unsigned long rstart, rsize, max_size;
+       int ret = 1;
+
+       rstart = mstart;
+       max_size = fw_dump.max_copy_size ? fw_dump.max_copy_size : msize;
+       while (msize) {
+               if (msize > max_size)
+                       rsize = max_size;
+               else
+                       rsize = msize;
+
+               ret = add_boot_mem_region(rstart, rsize);
+               if (!ret)
+                       break;
+
+               msize -= rsize;
+               rstart += rsize;
+       }
+
+       return ret;
+}
+
+static int __init fadump_get_boot_mem_regions(void)
+{
+       unsigned long base, size, cur_size, hole_size, last_end;
+       unsigned long mem_size = fw_dump.boot_memory_size;
+       struct memblock_region *reg;
+       int ret = 1;
+
+       fw_dump.boot_mem_regs_cnt = 0;
+
+       last_end = 0;
+       hole_size = 0;
+       cur_size = 0;
+       for_each_memblock(memory, reg) {
+               base = reg->base;
+               size = reg->size;
+               hole_size += (base - last_end);
+
+               if ((cur_size + size) >= mem_size) {
+                       size = (mem_size - cur_size);
+                       ret = add_boot_mem_regions(base, size);
+                       break;
+               }
+
+               mem_size -= size;
+               cur_size += size;
+               ret = add_boot_mem_regions(base, size);
+               if (!ret)
+                       break;
+
+               last_end = base + size;
+       }
+       fw_dump.boot_mem_top = PAGE_ALIGN(fw_dump.boot_memory_size + hole_size);
+
+       return ret;
+}
+
 int __init fadump_reserve_mem(void)
 {
        u64 base, size, mem_boundary, bootmem_min, align = PAGE_SIZE;
                               fw_dump.boot_memory_size, bootmem_min);
                        goto error_out;
                }
+
+               if (!fadump_get_boot_mem_regions()) {
+                       pr_err("Too many holes in boot memory area to enable fadump\n");
+                       goto error_out;
+               }
        }
 
        /*
        else
                mem_boundary = memblock_end_of_DRAM();
 
-       base = fw_dump.boot_memory_size;
+       base = fw_dump.boot_mem_top;
        size = get_fadump_area_size();
        fw_dump.reserve_dump_area_size = size;
        if (fw_dump.dump_active) {
 {
        struct memblock_region *reg;
        u64 start, end;
-       int ret;
+       int i, ret;
 
        pr_debug("Setup crash memory ranges.\n");
        crash_mrange_info.mem_range_cnt = 0;
 
        /*
-        * add the first memory chunk (0 through boot_memory_size) as
-        * a separate memory chunk. The reason is, at the time crash firmware
-        * will move the content of this memory chunk to different location
-        * specified during fadump registration. We need to create a separate
-        * program header for this chunk with the correct offset.
+        * Boot memory region(s) registered with firmware are moved to
+        * different location at the time of crash. Create separate program
+        * header(s) for this memory chunk(s) with the correct offset.
         */
-       ret = fadump_add_mem_range(&crash_mrange_info,
-                                  0, fw_dump.boot_memory_size);
-       if (ret)
-               return ret;
+       for (i = 0; i < fw_dump.boot_mem_regs_cnt; i++) {
+               start = fw_dump.boot_mem_addr[i];
+               end = start + fw_dump.boot_mem_sz[i];
+               ret = fadump_add_mem_range(&crash_mrange_info, start, end);
+               if (ret)
+                       return ret;
+       }
 
        for_each_memblock(memory, reg) {
                start = (u64)reg->base;
                end = start + (u64)reg->size;
 
                /*
-                * skip the first memory chunk that is already added
-                * (0 through boot_memory_size).
+                * skip the memory chunk that is already added
+                * (0 through boot_memory_top).
                 */
-               if (start < fw_dump.boot_memory_size) {
-                       if (end > fw_dump.boot_memory_size)
-                               start = fw_dump.boot_memory_size;
+               if (start < fw_dump.boot_mem_top) {
+                       if (end > fw_dump.boot_mem_top)
+                               start = fw_dump.boot_mem_top;
                        else
                                continue;
                }
  */
 static inline unsigned long fadump_relocate(unsigned long paddr)
 {
-       if ((paddr > 0) && (paddr < fw_dump.boot_memory_size))
-               return fw_dump.boot_mem_dest_addr + paddr;
-       else
-               return paddr;
+       unsigned long raddr, rstart, rend, rlast, hole_size;
+       int i;
+
+       hole_size = 0;
+       rlast = 0;
+       raddr = paddr;
+       for (i = 0; i < fw_dump.boot_mem_regs_cnt; i++) {
+               rstart = fw_dump.boot_mem_addr[i];
+               rend = rstart + fw_dump.boot_mem_sz[i];
+               hole_size += (rstart - rlast);
+
+               if (paddr >= rstart && paddr < rend) {
+                       raddr += fw_dump.boot_mem_dest_addr - hole_size;
+                       break;
+               }
+
+               rlast = rend;
+       }
+
+       pr_debug("vmcoreinfo: paddr = 0x%lx, raddr = 0x%lx\n", paddr, raddr);
+       return raddr;
 }
 
 static int fadump_create_elfcore_headers(char *bufp)
 {
-       struct elfhdr *elf;
+       unsigned long long raddr, offset;
        struct elf_phdr *phdr;
-       int i;
+       struct elfhdr *elf;
+       int i, j;
 
        fadump_init_elfcore_header(bufp);
        elf = (struct elfhdr *)bufp;
        (elf->e_phnum)++;
 
        /* setup PT_LOAD sections. */
-
+       j = 0;
+       offset = 0;
+       raddr = fw_dump.boot_mem_addr[0];
        for (i = 0; i < crash_mrange_info.mem_range_cnt; i++) {
                u64 mbase, msize;
 
                phdr->p_flags   = PF_R|PF_W|PF_X;
                phdr->p_offset  = mbase;
 
-               if (mbase == 0) {
+               if (mbase == raddr) {
                        /*
                         * The entire real memory region will be moved by
                         * firmware to the specified destination_address.
                         * Hence set the correct offset.
                         */
-                       phdr->p_offset = fw_dump.boot_mem_dest_addr;
+                       phdr->p_offset = fw_dump.boot_mem_dest_addr + offset;
+                       if (j < (fw_dump.boot_mem_regs_cnt - 1)) {
+                               offset += fw_dump.boot_mem_sz[j];
+                               raddr = fw_dump.boot_mem_addr[++j];
+                       }
                }
 
                phdr->p_paddr = mbase;
        fadump_cleanup();
        mutex_unlock(&fadump_mutex);
 
-       fadump_release_memory(fw_dump.boot_memory_size, memblock_end_of_DRAM());
+       fadump_release_memory(fw_dump.boot_mem_top, memblock_end_of_DRAM());
        fadump_free_cpu_notes_buf();
 
        /*
 
 static void opal_fadump_get_config(struct fw_dump *fadump_conf,
                                   const struct opal_fadump_mem_struct *fdm)
 {
+       unsigned long base, size, last_end, hole_size;
        int i;
 
        if (!fadump_conf->dump_active)
                return;
 
+       last_end = 0;
+       hole_size = 0;
        fadump_conf->boot_memory_size = 0;
 
        pr_debug("Boot memory regions:\n");
        for (i = 0; i < fdm->region_cnt; i++) {
-               pr_debug("\t%d. base: 0x%llx, size: 0x%llx\n",
-                        (i + 1), fdm->rgn[i].src, fdm->rgn[i].size);
+               base = fdm->rgn[i].src;
+               size = fdm->rgn[i].size;
+               pr_debug("\t[%03d] base: 0x%lx, size: 0x%lx\n", i, base, size);
 
-               fadump_conf->boot_memory_size += fdm->rgn[i].size;
+               fadump_conf->boot_mem_addr[i] = base;
+               fadump_conf->boot_mem_sz[i] = size;
+               fadump_conf->boot_memory_size += size;
+               hole_size += (base - last_end);
+
+               last_end = base + size;
        }
 
        /*
                pr_warn("WARNING: If the unsaved regions contain kernel pages, the vmcore will be corrupted.\n");
        }
 
+       fadump_conf->boot_mem_top = (fadump_conf->boot_memory_size + hole_size);
+       fadump_conf->boot_mem_regs_cnt = fdm->region_cnt;
        opal_fadump_update_config(fadump_conf, fdm);
 }
 
 
 static u64 opal_fadump_init_mem_struct(struct fw_dump *fadump_conf)
 {
-       int max_copy_size, cur_size, size;
-       u64 src_addr, dest_addr;
+       u64 addr = fadump_conf->reserve_dump_area_start;
+       int i;
 
        opal_fdm = __va(fadump_conf->kernel_metadata);
        opal_fadump_init_metadata(opal_fdm);
 
-       /*
-        * Firmware supports 32-bit field for size. Align it to PAGE_SIZE
-        * and request firmware to copy multiple kernel boot memory regions.
-        */
-       max_copy_size = _ALIGN_DOWN(U32_MAX, PAGE_SIZE);
-
        /* Boot memory regions */
-       src_addr = 0;
-       dest_addr = fadump_conf->reserve_dump_area_start;
-       size = fadump_conf->boot_memory_size;
-       while (size) {
-               cur_size = size > max_copy_size ? max_copy_size : size;
-
-               opal_fdm->rgn[opal_fdm->region_cnt].src  = src_addr;
-               opal_fdm->rgn[opal_fdm->region_cnt].dest = dest_addr;
-               opal_fdm->rgn[opal_fdm->region_cnt].size = cur_size;
+       for (i = 0; i < fadump_conf->boot_mem_regs_cnt; i++) {
+               opal_fdm->rgn[i].src    = fadump_conf->boot_mem_addr[i];
+               opal_fdm->rgn[i].dest   = addr;
+               opal_fdm->rgn[i].size   = fadump_conf->boot_mem_sz[i];
 
                opal_fdm->region_cnt++;
-               dest_addr       += cur_size;
-               src_addr        += cur_size;
-               size            -= cur_size;
+               addr += fadump_conf->boot_mem_sz[i];
        }
 
        /*
 
        opal_fadump_update_config(fadump_conf, opal_fdm);
 
-       return dest_addr;
+       return addr;
 }
 
 static u64 opal_fadump_get_metadata_size(void)
         * by a kernel that intends to preserve crash'ed kernel's memory.
         */
        ret = opal_mpipl_register_tag(OPAL_MPIPL_TAG_BOOT_MEM,
-                                     fadump_conf->boot_memory_size);
+                                     fadump_conf->boot_mem_top);
        if (ret != OPAL_SUCCESS) {
                pr_err("Failed to set boot memory tag!\n");
                err = -EPERM;
        fadump_conf->ops                = &opal_fadump_ops;
        fadump_conf->fadump_supported   = 1;
 
+       /*
+        * Firmware supports 32-bit field for size. Align it to PAGE_SIZE
+        * and request firmware to copy multiple kernel boot memory regions.
+        */
+       fadump_conf->max_copy_size = _ALIGN_DOWN(U32_MAX, PAGE_SIZE);
+
        /*
         * Check if dump has been initiated on last reboot.
         */