/* mdesc.c: Sun4V machine description handling.
*
* Copyright (C) 2007, 2008 David S. Miller <davem@davemloft.net>
*/
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/memblock.h>
#include <linux/log2.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/miscdevice.h>
#include <linux/bootmem.h>
#include <asm/cpudata.h>
#include <asm/hypervisor.h>
#include <asm/mdesc.h>
#include <asm/prom.h>
#include <asm/oplib.h>
#include <asm/smp.h>
/* Unlike the OBP device tree, the machine description is a full-on
* DAG. An arbitrary number of ARCs are possible from one
* node to other nodes and thus we can't use the OBP device_node
* data structure to represent these nodes inside of the kernel.
*
* Actually, it isn't even a DAG, because there are back pointers
* which create cycles in the graph.
*
* mdesc_hdr and mdesc_elem describe the layout of the data structure
* we get from the Hypervisor.
*/
struct mdesc_hdr {
u32 version; /* Transport version */
u32 node_sz; /* node block size */
u32 name_sz; /* name block size */
u32 data_sz; /* data block size */
} __attribute__((aligned(16)));
struct mdesc_elem {
u8 tag;
#define MD_LIST_END 0x00
#define MD_NODE 0x4e
#define MD_NODE_END 0x45
#define MD_NOOP 0x20
#define MD_PROP_ARC 0x61
#define MD_PROP_VAL 0x76
#define MD_PROP_STR 0x73
#define MD_PROP_DATA 0x64
u8 name_len;
u16 resv;
u32 name_offset;
union {
struct {
u32 data_len;
u32 data_offset;
} data;
u64 val;
} d;
};
struct mdesc_mem_ops {
struct mdesc_handle *(*alloc)(unsigned int mdesc_size);
void (*free)(struct mdesc_handle *handle);
};
struct mdesc_handle {
struct list_head list;
struct mdesc_mem_ops *mops;
void *self_base;
atomic_t refcnt;
unsigned int handle_size;
struct mdesc_hdr mdesc;
};
static void mdesc_handle_init(struct mdesc_handle *hp,
unsigned int handle_size,
void *base)
{
BUG_ON(((unsigned long)&hp->mdesc) & (16UL - 1));
memset(hp, 0, handle_size);
INIT_LIST_HEAD(&hp->list);
hp->self_base = base;
atomic_set(&hp->refcnt, 1);
hp->handle_size = handle_size;
}
static struct mdesc_handle * __init mdesc_memblock_alloc(unsigned int mdesc_size)
{
unsigned int handle_size, alloc_size;
struct mdesc_handle *hp;
unsigned long paddr;
handle_size = (sizeof(struct mdesc_handle) -
sizeof(struct mdesc_hdr) +
mdesc_size);
alloc_size = PAGE_ALIGN(handle_size);
paddr = memblock_alloc(alloc_size, PAGE_SIZE);
hp = NULL;
if (paddr) {
hp = __va(paddr);
mdesc_handle_init(hp, handle_size, hp);
}
return hp;
}
static void mdesc_memblock_free(struct mdesc_handle *hp)
{
unsigned int alloc_size;
unsigned long start;
BUG_ON(atomic_read(&hp->refcnt) != 0);
BUG_ON(!list_empty(&hp->list));
alloc_size = PAGE_ALIGN(hp->handle_size);
start = __pa(hp);
free_bootmem_late(start, alloc_size);
}
static struct mdesc_mem_ops memblock_mdesc_ops = {
.alloc = mdesc_memblock_alloc,
.free = mdesc_memblock_free,
};
static struct mdesc_handle *mdesc_kmalloc(unsigned int mdesc_size)
{
unsigned int handle_size;
void *base;
handle_size = (sizeof(struct mdesc_handle) -
sizeof(struct mdesc_hdr) +
mdesc_size);
base = kmalloc(handle_size + 15, GFP_KERNEL | __GFP_NOFAIL);
if (base) {
struct mdesc_handle *hp;
unsigned long addr;
addr = (unsigned long)base;
addr = (addr + 15UL) & ~15UL;
hp = (struct mdesc_handle *) addr;
mdesc_handle_init(hp, handle_size, base);
return hp;
}
return NULL;
}
static void mdesc_kfree(struct mdesc_handle *hp)
{
BUG_ON(atomic_read(&hp->refcnt) != 0);
BUG_ON(!list_empty(&hp->list));
kfree(hp->self_base);
}
static struct mdesc_mem_ops kmalloc_mdesc_memops = {
.alloc = mdesc_kmalloc,
.free = mdesc_kfree,
};
static struct mdesc_handle *mdesc_alloc(unsigned int mdesc_size,
struct mdesc_mem_ops *mops)
{
struct mdesc_handle *hp = mops->alloc(mdesc_size);
if (hp)
hp->mops = mops;
return hp;
}
static void mdesc_free(struct mdesc_handle *hp)