blob: 1771965808032e8d4ba6e5e9a974f7daf8e71b7d [file] [log] [blame]
/*
* Copyright (c) 2000, 2003 Silicon Graphics, Inc. All rights reserved.
* Copyright (c) 2001 Intel Corp.
* Copyright (c) 2001 Tony Luck <tony.luck@intel.com>
* Copyright (c) 2002 NEC Corp.
* Copyright (c) 2002 Kimio Suganuma <k-suganuma@da.jp.nec.com>
*/
/*
* Platform initialization for Discontig Memory
*/
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/bootmem.h>
#include <linux/mmzone.h>
#include <linux/acpi.h>
#include <linux/efi.h>
#include <asm/pgalloc.h>
#include <asm/tlb.h>
/*
* Round an address upward to the next multiple of GRANULE size.
*/
#define GRANULEROUNDDOWN(n) ((n) & ~(IA64_GRANULE_SIZE-1))
#define GRANULEROUNDUP(n) (((n)+IA64_GRANULE_SIZE-1) & ~(IA64_GRANULE_SIZE-1))
/*
* Used to locate BOOT_DATA prior to initializing the node data area.
*/
#define BOOT_NODE_DATA(node) pg_data_ptr[node]
/*
* To prevent cache aliasing effects, align per-node structures so that they
* start at addresses that are strided by node number.
*/
#define NODEDATA_ALIGN(addr, node) ((((addr) + 1024*1024-1) & ~(1024*1024-1)) + (node)*PAGE_SIZE)
static struct ia64_node_data *boot_node_data[NR_NODES] __initdata;
static pg_data_t *pg_data_ptr[NR_NODES] __initdata;
static bootmem_data_t bdata[NR_NODES] __initdata;
static unsigned long boot_pernode[NR_NODES] __initdata;
static unsigned long boot_pernodesize[NR_NODES] __initdata;
extern int filter_rsvd_memory (unsigned long start, unsigned long end, void *arg);
extern struct cpuinfo_ia64 *_cpu_data[NR_CPUS];
/*
* We allocate one of the bootmem_data_t structs for each piece of memory
* that we wish to treat as a contiguous block. Each such block must start
* on a GRANULE boundary. Multiple banks per node is not supported.
* (Note: on SN2, all memory on a node is trated as a single bank.
* Holes within the bank are supported. This works because memory
* from different banks is not interleaved. The bootmap bitmap
* for the node is somewhat large but not too large).
*/
static int __init
build_maps(unsigned long start, unsigned long end, int node)
{
bootmem_data_t *bdp;
unsigned long cstart, epfn;
bdp = &bdata[node];
epfn = GRANULEROUNDUP(__pa(end)) >> PAGE_SHIFT;
cstart = GRANULEROUNDDOWN(__pa(start));
if (!bdp->node_low_pfn) {
bdp->node_boot_start = cstart;
bdp->node_low_pfn = epfn;
} else {
bdp->node_boot_start = min(cstart, bdp->node_boot_start);
bdp->node_low_pfn = max(epfn, bdp->node_low_pfn);
}
min_low_pfn = min(min_low_pfn, bdp->node_boot_start>>PAGE_SHIFT);
max_low_pfn = max(max_low_pfn, bdp->node_low_pfn);
return 0;
}
/*
* Count the number of cpus on the node
*/
static __inline__ int
count_cpus(int node)
{
int cpu, n=0;
for (cpu=0; cpu < NR_CPUS; cpu++)
if (node == node_cpuid[cpu].nid)
n++;
return n;
}
/*
* Find space on each node for the bootmem map & other per-node data structures.
*
* Called by efi_memmap_walk to find boot memory on each node. Note that
* only blocks that are free are passed to this routine (currently filtered by
* free_available_memory).
*/
static int __init
find_pernode_space(unsigned long start, unsigned long end, int node)
{
unsigned long mapsize, pages, epfn, map=0, cpu, cpus;
unsigned long pernodesize=0, pernode;
unsigned long cpu_data, mmu_gathers;
unsigned long pstart, length;
bootmem_data_t *bdp;
pstart = __pa(start);
length = end - start;
epfn = (pstart + length) >> PAGE_SHIFT;
bdp = &bdata[node];
if (pstart < bdp->node_boot_start || epfn > bdp->node_low_pfn)
return 0;
if (!boot_pernode[node]) {
cpus = count_cpus(node);
pernodesize += PAGE_ALIGN(sizeof(struct cpuinfo_ia64)) * cpus;
pernodesize += L1_CACHE_ALIGN(sizeof(mmu_gather_t)) * cpus;
pernodesize += L1_CACHE_ALIGN(sizeof(pg_data_t));
pernodesize += L1_CACHE_ALIGN(sizeof(struct ia64_node_data));
pernodesize = PAGE_ALIGN(pernodesize);
pernode = NODEDATA_ALIGN(pstart, node);
if (pstart + length > (pernode + pernodesize)) {
boot_pernode[node] = pernode;
boot_pernodesize[node] = pernodesize;
memset(__va(pernode), 0, pernodesize);
cpu_data = pernode;
pernode += PAGE_ALIGN(sizeof(struct cpuinfo_ia64)) * cpus;
mmu_gathers = pernode;
pernode += L1_CACHE_ALIGN(sizeof(mmu_gather_t)) * cpus;
pg_data_ptr[node] = __va(pernode);
pernode += L1_CACHE_ALIGN(sizeof(pg_data_t));
boot_node_data[node] = __va(pernode);
pernode += L1_CACHE_ALIGN(sizeof(struct ia64_node_data));
pg_data_ptr[node]->bdata = &bdata[node];
pernode += L1_CACHE_ALIGN(sizeof(pg_data_t));
for (cpu=0; cpu < NR_CPUS; cpu++) {
if (node == node_cpuid[cpu].nid) {
_cpu_data[cpu] = __va(cpu_data);
_cpu_data[cpu]->node_data = boot_node_data[node];
_cpu_data[cpu]->nodeid = node;
_cpu_data[cpu]->mmu_gathers = __va(mmu_gathers);
cpu_data += PAGE_ALIGN(sizeof(struct cpuinfo_ia64));
mmu_gathers += L1_CACHE_ALIGN(sizeof(mmu_gather_t));
}
}
}
}
pernode = boot_pernode[node];
pernodesize = boot_pernodesize[node];
if (pernode && !bdp->node_bootmem_map) {
pages = bdp->node_low_pfn - (bdp->node_boot_start>>PAGE_SHIFT);
mapsize = bootmem_bootmap_pages(pages) << PAGE_SHIFT;
if (pernode - pstart > mapsize)
map = pstart;
else if (pstart + length - pernode - pernodesize > mapsize)
map = pernode + pernodesize;
if (map) {
init_bootmem_node(
BOOT_NODE_DATA(node),
map>>PAGE_SHIFT,
bdp->node_boot_start>>PAGE_SHIFT,
bdp->node_low_pfn);
}
}
return 0;
}
/*
* Free available memory to the bootmem allocator.
*
* Note that only blocks that are free are passed to this routine (currently
* filtered by free_available_memory).
*
*/
static int __init
discontig_free_bootmem_node(unsigned long start, unsigned long end, int node)
{
free_bootmem_node(BOOT_NODE_DATA(node), __pa(start), end - start);
return 0;
}
/*
* Reserve the space used by the bootmem maps.
*/
static void __init
discontig_reserve_bootmem(void)
{
int node;
unsigned long base, size, pages;
bootmem_data_t *bdp;
for (node = 0; node < numnodes; node++) {
bdp = BOOT_NODE_DATA(node)->bdata;
pages = bdp->node_low_pfn - (bdp->node_boot_start>>PAGE_SHIFT);
size = bootmem_bootmap_pages(pages) << PAGE_SHIFT;
base = __pa(bdp->node_bootmem_map);
reserve_bootmem_node(BOOT_NODE_DATA(node), base, size);
size = boot_pernodesize[node];
base = __pa(boot_pernode[node]);
reserve_bootmem_node(BOOT_NODE_DATA(node), base, size);
}
}
/*
* Initialize per-node data
*
* Finish setting up the node data for this node, then copy it to the other nodes.
*
*/
static void __init
initialize_pernode_data(void)
{
int cpu, node;
memcpy(boot_node_data[0]->pg_data_ptrs, pg_data_ptr, sizeof(pg_data_ptr));
memcpy(boot_node_data[0]->node_data_ptrs, boot_node_data, sizeof(boot_node_data));
for (node=1; node < numnodes; node++) {
memcpy(boot_node_data[node], boot_node_data[0], sizeof(struct ia64_node_data));
boot_node_data[node]->node = node;
}
for (cpu=0; cpu < NR_CPUS; cpu++) {
node = node_cpuid[cpu].nid;
_cpu_data[cpu]->node_data = boot_node_data[node];
_cpu_data[cpu]->nodeid = node;
}
}
/*
* Called early in boot to setup the boot memory allocator, and to
* allocate the node-local pg_data & node-directory data structures..
*/
void __init
discontig_mem_init(void)
{
if (numnodes == 0) {
printk("node info missing!\n");
numnodes = 1;
}
min_low_pfn = -1;
max_low_pfn = 0;
efi_memmap_walk(filter_rsvd_memory, build_maps);
efi_memmap_walk(filter_rsvd_memory, find_pernode_space);
efi_memmap_walk(filter_rsvd_memory, discontig_free_bootmem_node);
discontig_reserve_bootmem();
initialize_pernode_data();
}