schedule_work(&pcpu_balance_work);
 }
 
+/**
+ * pcpu_addr_in_first_chunk - address check for first chunk's dynamic region
+ * @addr: percpu address of interest
+ *
+ * The first chunk is considered to be the dynamic region of the first chunk.
+ * While the true first chunk is composed of the static, dynamic, and
+ * reserved regions, it is the chunk that serves the dynamic region that is
+ * circulated in the chunk slots.
+ *
+ * The reserved chunk has a separate check and the static region addresses
+ * should never be passed into the percpu allocator.
+ *
+ * RETURNS:
+ * True if the address is in the dynamic region of the first chunk.
+ */
 static bool pcpu_addr_in_first_chunk(void *addr)
 {
-       void *first_start = pcpu_first_chunk->base_addr;
+       void *start_addr = pcpu_first_chunk->base_addr +
+                          pcpu_first_chunk->start_offset;
+       void *end_addr = pcpu_first_chunk->base_addr +
+                        pcpu_first_chunk->nr_pages * PAGE_SIZE -
+                        pcpu_first_chunk->end_offset;
 
-       return addr >= first_start && addr < first_start + pcpu_unit_size;
+       return addr >= start_addr && addr < end_addr;
 }
 
+/**
+ * pcpu_addr_in_reserved_chunk - address check for reserved region
+ *
+ * The reserved region is a part of the first chunk and primarily serves
+ * static percpu variables from kernel modules.
+ *
+ * RETURNS:
+ * True if the address is in the reserved region.
+ */
 static bool pcpu_addr_in_reserved_chunk(void *addr)
 {
-       void *first_start = pcpu_first_chunk->base_addr;
+       void *start_addr, *end_addr;
+
+       if (!pcpu_reserved_chunk)
+               return false;
 
-       return addr >= first_start &&
-               addr < first_start + pcpu_first_chunk->start_offset;
+       start_addr = pcpu_reserved_chunk->base_addr +
+                    pcpu_reserved_chunk->start_offset;
+       end_addr = pcpu_reserved_chunk->base_addr +
+                  pcpu_reserved_chunk->nr_pages * PAGE_SIZE -
+                  pcpu_reserved_chunk->end_offset;
+
+       return addr >= start_addr && addr < end_addr;
 }
 
 static int __pcpu_size_to_slot(int size)
        return pcpu_unit_map[cpu] * pcpu_unit_pages + page_idx;
 }
 
+static unsigned long pcpu_unit_page_offset(unsigned int cpu, int page_idx)
+{
+       return pcpu_unit_offsets[cpu] + (page_idx << PAGE_SHIFT);
+}
+
 static unsigned long pcpu_chunk_addr(struct pcpu_chunk *chunk,
                                     unsigned int cpu, int page_idx)
 {
-       return (unsigned long)chunk->base_addr + pcpu_unit_offsets[cpu] +
-               (page_idx << PAGE_SHIFT);
+       return (unsigned long)chunk->base_addr +
+              pcpu_unit_page_offset(cpu, page_idx);
 }
 
 static void __maybe_unused pcpu_next_unpop(struct pcpu_chunk *chunk,
        pcpu_chunk_relocate(chunk, oslot);
 }
 
-static struct pcpu_chunk * __init pcpu_alloc_first_chunk(void *base_addr,
-                                                        int start_offset,
+static struct pcpu_chunk * __init pcpu_alloc_first_chunk(unsigned long tmp_addr,
                                                         int map_size,
                                                         int *map,
                                                         int init_map_size)
 {
        struct pcpu_chunk *chunk;
-       int region_size;
+       unsigned long aligned_addr;
+       int start_offset, region_size;
+
+       /* region calculations */
+       aligned_addr = tmp_addr & PAGE_MASK;
+
+       start_offset = tmp_addr - aligned_addr;
 
        region_size = PFN_ALIGN(start_offset + map_size);
 
+       /* allocate chunk */
        chunk = memblock_virt_alloc(pcpu_chunk_struct_size, 0);
+
        INIT_LIST_HEAD(&chunk->list);
        INIT_LIST_HEAD(&chunk->map_extend_list);
-       chunk->base_addr = base_addr;
+
+       chunk->base_addr = (void *)aligned_addr;
        chunk->start_offset = start_offset;
        chunk->end_offset = region_size - chunk->start_offset - map_size;
+
+       chunk->nr_pages = pcpu_unit_pages;
+
        chunk->map = map;
        chunk->map_alloc = init_map_size;
 
        chunk->nr_populated = pcpu_unit_pages;
 
        chunk->contig_hint = chunk->free_size = map_size;
-       chunk->map[0] = 1;
-       chunk->map[1] = chunk->start_offset;
-       chunk->map[2] = (chunk->start_offset + chunk->free_size) | 1;
-       chunk->map_used = 2;
+
+       if (chunk->start_offset) {
+               /* hide the beginning of the bitmap */
+               chunk->map[0] = 1;
+               chunk->map[1] = chunk->start_offset;
+               chunk->map_used = 1;
+       }
+
+       /* set chunk's free region */
+       chunk->map[++chunk->map_used] =
+               (chunk->start_offset + chunk->free_size) | 1;
 
        if (chunk->end_offset) {
                /* hide the end of the bitmap */
        chunk->free_size = pcpu_unit_size;
        chunk->contig_hint = pcpu_unit_size;
 
+       chunk->nr_pages = pcpu_unit_pages;
+
        return chunk;
 }
 
  * pcpu_chunk_addr_search - determine chunk containing specified address
  * @addr: address for which the chunk needs to be determined.
  *
+ * This is an internal function that handles all but static allocations.
+ * Static percpu address values should never be passed into the allocator.
+ *
  * RETURNS:
  * The address of the found chunk.
  */
 static struct pcpu_chunk *pcpu_chunk_addr_search(void *addr)
 {
-       /* is it in the first chunk? */
-       if (pcpu_addr_in_first_chunk(addr)) {
-               /* is it in the reserved area? */
-               if (pcpu_addr_in_reserved_chunk(addr))
-                       return pcpu_reserved_chunk;
+       /* is it in the dynamic region (first chunk)? */
+       if (pcpu_addr_in_first_chunk(addr))
                return pcpu_first_chunk;
-       }
+
+       /* is it in the reserved region? */
+       if (pcpu_addr_in_reserved_chunk(addr))
+               return pcpu_reserved_chunk;
 
        /*
         * The address is relative to unit0 which might be unused and
         * The following test on unit_low/high isn't strictly
         * necessary but will speed up lookups of addresses which
         * aren't in the first chunk.
+        *
+        * The address check is against full chunk sizes.  pcpu_base_addr
+        * points to the beginning of the first chunk including the
+        * static region.  Assumes good intent as the first chunk may
+        * not be full (ie. < pcpu_unit_pages in size).
         */
-       first_low = pcpu_chunk_addr(pcpu_first_chunk, pcpu_low_unit_cpu, 0);
-       first_high = pcpu_chunk_addr(pcpu_first_chunk, pcpu_high_unit_cpu,
-                                    pcpu_unit_pages);
+       first_low = (unsigned long)pcpu_base_addr +
+                   pcpu_unit_page_offset(pcpu_low_unit_cpu, 0);
+       first_high = (unsigned long)pcpu_base_addr +
+                    pcpu_unit_page_offset(pcpu_high_unit_cpu, pcpu_unit_pages);
        if ((unsigned long)addr >= first_low &&
            (unsigned long)addr < first_high) {
                for_each_possible_cpu(cpu) {
  * The caller should have mapped the first chunk at @base_addr and
  * copied static data to each unit.
  *
- * If the first chunk ends up with both reserved and dynamic areas, it
- * is served by two chunks - one to serve the core static and reserved
- * areas and the other for the dynamic area.  They share the same vm
- * and page map but uses different area allocation map to stay away
- * from each other.  The latter chunk is circulated in the chunk slots
- * and available for dynamic allocation like any other chunks.
+ * The first chunk will always contain a static and a dynamic region.
+ * However, the static region is not managed by any chunk.  If the first
+ * chunk also contains a reserved region, it is served by two chunks -
+ * one for the reserved region and one for the dynamic region.  They
+ * share the same vm, but use offset regions in the area allocation map.
+ * The chunk serving the dynamic region is circulated in the chunk slots
+ * and available for dynamic allocation like any other chunk.
  *
  * RETURNS:
  * 0 on success, -errno on failure.
        unsigned int cpu;
        int *unit_map;
        int group, unit, i;
-       int map_size, start_offset;
+       int map_size;
+       unsigned long tmp_addr;
 
 #define PCPU_SETUP_BUG_ON(cond)        do {                                    \
        if (unlikely(cond)) {                                           \
                INIT_LIST_HEAD(&pcpu_slot[i]);
 
        /*
-        * Initialize static chunk.  If reserved_size is zero, the
-        * static chunk covers static area + dynamic allocation area
-        * in the first chunk.  If reserved_size is not zero, it
-        * covers static area + reserved area (mostly used for module
-        * static percpu allocation).
+        * Initialize first chunk.
+        * If the reserved_size is non-zero, this initializes the reserved
+        * chunk.  If the reserved_size is zero, the reserved chunk is NULL
+        * and the dynamic region is initialized here.  The first chunk,
+        * pcpu_first_chunk, will always point to the chunk that serves
+        * the dynamic region.
         */
-       start_offset = ai->static_size;
+       tmp_addr = (unsigned long)base_addr + ai->static_size;
        map_size = ai->reserved_size ?: ai->dyn_size;
-       chunk = pcpu_alloc_first_chunk(base_addr, start_offset, map_size, smap,
+       chunk = pcpu_alloc_first_chunk(tmp_addr, map_size, smap,
                                       ARRAY_SIZE(smap));
 
        /* init dynamic chunk if necessary */
        if (ai->reserved_size) {
                pcpu_reserved_chunk = chunk;
 
-               start_offset = ai->static_size + ai->reserved_size;
+               tmp_addr = (unsigned long)base_addr + ai->static_size +
+                          ai->reserved_size;
                map_size = ai->dyn_size;
-               chunk = pcpu_alloc_first_chunk(base_addr, start_offset,
-                                              map_size, dmap,
+               chunk = pcpu_alloc_first_chunk(tmp_addr, map_size, dmap,
                                               ARRAY_SIZE(dmap));
        }