ARM: Fix broken highmem support

Currently, highmem is selectable, and you can request an increased
vmalloc area.  However, none of this has any effect on the memory
layout since a patch in the highmem series was accidentally dropped.
Moreover, even if you did want highmem, all memory would still be
registered as lowmem, possibly resulting in overflow of the available
virtual mapping space.

The highmem boundary is determined by the highest allowed beginning
of the vmalloc area, which depends on its configurable minimum size
(see commit 60296c71f6 for details on
this).

We should create mappings and initialize bootmem only for low memory,
while the zone allocator must still be told about highmem.

Currently, memory nodes which are completely located in high memory
are not supported.  This is not a huge limitation since systems
relying on highmem support are unlikely to have discontiguous memory
with large holes.

[ A similar patch was meant to be merged before commit 5f0fbf9eca
  and be available  in Linux v2.6.30, however some git rebase screw-up
  of mine dropped the first commit of the series, and that goofage
  escaped testing somehow as well. -- Nico ]

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
Reviewed-by: Nicolas Pitre <nico@marvell.com>
This commit is contained in:
Russell King 2009-08-15 12:36:00 +01:00 committed by Russell King
parent 3b3119fc54
commit dde5828f56
3 changed files with 83 additions and 47 deletions

View file

@ -201,7 +201,8 @@ static struct tagtable __tagtable_##fn __tag = { tag, fn }
struct membank { struct membank {
unsigned long start; unsigned long start;
unsigned long size; unsigned long size;
int node; unsigned short node;
unsigned short highmem;
}; };
struct meminfo { struct meminfo {

View file

@ -120,6 +120,32 @@ void show_mem(void)
printk("%d pages swap cached\n", cached); printk("%d pages swap cached\n", cached);
} }
static void __init find_node_limits(int node, struct meminfo *mi,
unsigned long *min, unsigned long *max_low, unsigned long *max_high)
{
int i;
*min = -1UL;
*max_low = *max_high = 0;
for_each_nodebank(i, mi, node) {
struct membank *bank = &mi->bank[i];
unsigned long start, end;
start = bank_pfn_start(bank);
end = bank_pfn_end(bank);
if (*min > start)
*min = start;
if (*max_high < end)
*max_high = end;
if (bank->highmem)
continue;
if (*max_low < end)
*max_low = end;
}
}
/* /*
* FIXME: We really want to avoid allocating the bootmap bitmap * FIXME: We really want to avoid allocating the bootmap bitmap
* over the top of the initrd. Hopefully, this is located towards * over the top of the initrd. Hopefully, this is located towards
@ -210,40 +236,24 @@ static inline void map_memory_bank(struct membank *bank)
#endif #endif
} }
static unsigned long __init bootmem_init_node(int node, struct meminfo *mi) static void __init bootmem_init_node(int node, struct meminfo *mi,
unsigned long start_pfn, unsigned long end_pfn)
{ {
unsigned long start_pfn, end_pfn, boot_pfn; unsigned long boot_pfn;
unsigned int boot_pages; unsigned int boot_pages;
pg_data_t *pgdat; pg_data_t *pgdat;
int i; int i;
start_pfn = -1UL;
end_pfn = 0;
/* /*
* Calculate the pfn range, and map the memory banks for this node. * Map the memory banks for this node.
*/ */
for_each_nodebank(i, mi, node) { for_each_nodebank(i, mi, node) {
struct membank *bank = &mi->bank[i]; struct membank *bank = &mi->bank[i];
unsigned long start, end;
start = bank_pfn_start(bank); if (!bank->highmem)
end = bank_pfn_end(bank); map_memory_bank(bank);
if (start_pfn > start)
start_pfn = start;
if (end_pfn < end)
end_pfn = end;
map_memory_bank(bank);
} }
/*
* If there is no memory in this node, ignore it.
*/
if (end_pfn == 0)
return end_pfn;
/* /*
* Allocate the bootmem bitmap page. * Allocate the bootmem bitmap page.
*/ */
@ -260,7 +270,8 @@ static unsigned long __init bootmem_init_node(int node, struct meminfo *mi)
for_each_nodebank(i, mi, node) { for_each_nodebank(i, mi, node) {
struct membank *bank = &mi->bank[i]; struct membank *bank = &mi->bank[i];
free_bootmem_node(pgdat, bank_phys_start(bank), bank_phys_size(bank)); if (!bank->highmem)
free_bootmem_node(pgdat, bank_phys_start(bank), bank_phys_size(bank));
memory_present(node, bank_pfn_start(bank), bank_pfn_end(bank)); memory_present(node, bank_pfn_start(bank), bank_pfn_end(bank));
} }
@ -269,8 +280,6 @@ static unsigned long __init bootmem_init_node(int node, struct meminfo *mi)
*/ */
reserve_bootmem_node(pgdat, boot_pfn << PAGE_SHIFT, reserve_bootmem_node(pgdat, boot_pfn << PAGE_SHIFT,
boot_pages << PAGE_SHIFT, BOOTMEM_DEFAULT); boot_pages << PAGE_SHIFT, BOOTMEM_DEFAULT);
return end_pfn;
} }
static void __init bootmem_reserve_initrd(int node) static void __init bootmem_reserve_initrd(int node)
@ -297,33 +306,39 @@ static void __init bootmem_reserve_initrd(int node)
static void __init bootmem_free_node(int node, struct meminfo *mi) static void __init bootmem_free_node(int node, struct meminfo *mi)
{ {
unsigned long zone_size[MAX_NR_ZONES], zhole_size[MAX_NR_ZONES]; unsigned long zone_size[MAX_NR_ZONES], zhole_size[MAX_NR_ZONES];
unsigned long start_pfn, end_pfn; unsigned long min, max_low, max_high;
pg_data_t *pgdat = NODE_DATA(node);
int i; int i;
start_pfn = pgdat->bdata->node_min_pfn; find_node_limits(node, mi, &min, &max_low, &max_high);
end_pfn = pgdat->bdata->node_low_pfn;
/* /*
* initialise the zones within this node. * initialise the zones within this node.
*/ */
memset(zone_size, 0, sizeof(zone_size)); memset(zone_size, 0, sizeof(zone_size));
memset(zhole_size, 0, sizeof(zhole_size));
/* /*
* The size of this node has already been determined. If we need * The size of this node has already been determined. If we need
* to do anything fancy with the allocation of this memory to the * to do anything fancy with the allocation of this memory to the
* zones, now is the time to do it. * zones, now is the time to do it.
*/ */
zone_size[0] = end_pfn - start_pfn; zone_size[0] = max_low - min;
#ifdef CONFIG_HIGHMEM
zone_size[ZONE_HIGHMEM] = max_high - max_low;
#endif
/* /*
* For each bank in this node, calculate the size of the holes. * For each bank in this node, calculate the size of the holes.
* holes = node_size - sum(bank_sizes_in_node) * holes = node_size - sum(bank_sizes_in_node)
*/ */
zhole_size[0] = zone_size[0]; memcpy(zhole_size, zone_size, sizeof(zhole_size));
for_each_nodebank(i, mi, node) for_each_nodebank(i, mi, node) {
zhole_size[0] -= bank_pfn_size(&mi->bank[i]); int idx = 0;
#ifdef CONFIG_HIGHMEM
if (mi->bank[i].highmem)
idx = ZONE_HIGHMEM;
#endif
zhole_size[idx] -= bank_pfn_size(&mi->bank[i]);
}
/* /*
* Adjust the sizes according to any special requirements for * Adjust the sizes according to any special requirements for
@ -331,13 +346,13 @@ static void __init bootmem_free_node(int node, struct meminfo *mi)
*/ */
arch_adjust_zones(node, zone_size, zhole_size); arch_adjust_zones(node, zone_size, zhole_size);
free_area_init_node(node, zone_size, start_pfn, zhole_size); free_area_init_node(node, zone_size, min, zhole_size);
} }
void __init bootmem_init(void) void __init bootmem_init(void)
{ {
struct meminfo *mi = &meminfo; struct meminfo *mi = &meminfo;
unsigned long memend_pfn = 0; unsigned long min, max_low, max_high;
int node, initrd_node; int node, initrd_node;
/* /*
@ -345,11 +360,29 @@ void __init bootmem_init(void)
*/ */
initrd_node = check_initrd(mi); initrd_node = check_initrd(mi);
max_low = max_high = 0;
/* /*
* Run through each node initialising the bootmem allocator. * Run through each node initialising the bootmem allocator.
*/ */
for_each_node(node) { for_each_node(node) {
unsigned long end_pfn = bootmem_init_node(node, mi); unsigned long node_low, node_high;
find_node_limits(node, mi, &min, &node_low, &node_high);
if (node_low > max_low)
max_low = node_low;
if (node_high > max_high)
max_high = node_high;
/*
* If there is no memory in this node, ignore it.
* (We can't have nodes which have no lowmem)
*/
if (node_low == 0)
continue;
bootmem_init_node(node, mi, min, node_low);
/* /*
* Reserve any special node zero regions. * Reserve any special node zero regions.
@ -362,12 +395,6 @@ void __init bootmem_init(void)
*/ */
if (node == initrd_node) if (node == initrd_node)
bootmem_reserve_initrd(node); bootmem_reserve_initrd(node);
/*
* Remember the highest memory PFN.
*/
if (end_pfn > memend_pfn)
memend_pfn = end_pfn;
} }
/* /*
@ -383,7 +410,7 @@ void __init bootmem_init(void)
for_each_node(node) for_each_node(node)
bootmem_free_node(node, mi); bootmem_free_node(node, mi);
high_memory = __va((memend_pfn << PAGE_SHIFT) - 1) + 1; high_memory = __va((max_low << PAGE_SHIFT) - 1) + 1;
/* /*
* This doesn't seem to be used by the Linux memory manager any * This doesn't seem to be used by the Linux memory manager any
@ -393,7 +420,8 @@ void __init bootmem_init(void)
* Note: max_low_pfn and max_pfn reflect the number of _pages_ in * Note: max_low_pfn and max_pfn reflect the number of _pages_ in
* the system, not the maximum PFN. * the system, not the maximum PFN.
*/ */
max_pfn = max_low_pfn = memend_pfn - PHYS_PFN_OFFSET; max_low_pfn = max_low - PHYS_PFN_OFFSET;
max_pfn = max_high - PHYS_PFN_OFFSET;
} }
static inline int free_area(unsigned long pfn, unsigned long end, char *s) static inline int free_area(unsigned long pfn, unsigned long end, char *s)

View file

@ -687,13 +687,19 @@ __early_param("vmalloc=", early_vmalloc);
static void __init sanity_check_meminfo(void) static void __init sanity_check_meminfo(void)
{ {
int i, j; int i, j, highmem = 0;
for (i = 0, j = 0; i < meminfo.nr_banks; i++) { for (i = 0, j = 0; i < meminfo.nr_banks; i++) {
struct membank *bank = &meminfo.bank[j]; struct membank *bank = &meminfo.bank[j];
*bank = meminfo.bank[i]; *bank = meminfo.bank[i];
#ifdef CONFIG_HIGHMEM #ifdef CONFIG_HIGHMEM
if (__va(bank->start) > VMALLOC_MIN ||
__va(bank->start) < (void *)PAGE_OFFSET)
highmem = 1;
bank->highmem = highmem;
/* /*
* Split those memory banks which are partially overlapping * Split those memory banks which are partially overlapping
* the vmalloc area greatly simplifying things later. * the vmalloc area greatly simplifying things later.
@ -714,6 +720,7 @@ static void __init sanity_check_meminfo(void)
i++; i++;
bank[1].size -= VMALLOC_MIN - __va(bank->start); bank[1].size -= VMALLOC_MIN - __va(bank->start);
bank[1].start = __pa(VMALLOC_MIN - 1) + 1; bank[1].start = __pa(VMALLOC_MIN - 1) + 1;
bank[1].highmem = highmem = 1;
j++; j++;
} }
bank->size = VMALLOC_MIN - __va(bank->start); bank->size = VMALLOC_MIN - __va(bank->start);