#include <xen/sched.h>
#include <asm/irq.h>
#include <asm/regs.h>
+#include <xen/errno.h>
+#include <xen/device_tree.h>
+#include <xen/libfdt/libfdt.h>
+#include <xen/guest_access.h>
#include "gic.h"
#include "kernel.h"
static unsigned int __initdata opt_dom0_max_vcpus;
integer_param("dom0_max_vcpus", opt_dom0_max_vcpus);
+/*
+ * Amount of extra space required to dom0's device tree. No new nodes
+ * are added (yet) but one terminating reserve map entry (16 bytes) is
+ * added.
+ */
+#define DOM0_FDT_EXTRA_SIZE (sizeof(struct fdt_reserve_entry))
+
struct vcpu *__init alloc_dom0_vcpu0(void)
{
if ( opt_dom0_max_vcpus == 0 )
return alloc_vcpu(dom0, 0, 0);
}
-extern void guest_mode_entry(void);
+static int set_memory_reg(struct domain *d, struct kernel_info *kinfo,
+ const void *fdt, const u32 *cell, int len,
+ int address_cells, int size_cells, u32 *new_cell)
+{
+ int reg_size = (address_cells + size_cells) * sizeof(*cell);
+ int l = 0;
+ u64 start;
+ u64 size;
+
+ while ( kinfo->unassigned_mem > 0 && l + reg_size <= len
+ && kinfo->mem.nr_banks < NR_MEM_BANKS )
+ {
+ device_tree_get_reg(&cell, address_cells, size_cells, &start, &size);
+ if ( size > kinfo->unassigned_mem )
+ size = kinfo->unassigned_mem;
+ device_tree_set_reg(&new_cell, address_cells, size_cells, start, size);
+
+ printk("Populate P2M %#llx->%#llx\n", start, start + size);
+ p2m_populate_ram(d, start, start + size);
+ kinfo->mem.bank[kinfo->mem.nr_banks].start = start;
+ kinfo->mem.bank[kinfo->mem.nr_banks].size = size;
+ kinfo->mem.nr_banks++;
+ kinfo->unassigned_mem -= size;
+
+ l += reg_size;
+ }
+
+ return l;
+}
+
+static int write_properties(struct domain *d, struct kernel_info *kinfo,
+ const void *fdt,
+ int node, const char *name, int depth,
+ u32 address_cells, u32 size_cells)
+{
+ int prop;
+
+ for ( prop = fdt_first_property_offset(fdt, node);
+ prop >= 0;
+ prop = fdt_next_property_offset(fdt, prop) )
+ {
+ const struct fdt_property *p;
+ const char *prop_name;
+ const char *prop_data;
+ int prop_len;
+ char *new_data = NULL;
+
+ p = fdt_get_property_by_offset(fdt, prop, NULL);
+ prop_name = fdt_string(fdt, fdt32_to_cpu(p->nameoff));
+ prop_data = p->data;
+ prop_len = fdt32_to_cpu(p->len);
+
+ /*
+ * In chosen node: replace bootargs with value from
+ * xen,dom0-bootargs.
+ */
+ if ( device_tree_node_matches(fdt, node, "chosen") )
+ {
+ if ( strcmp(prop_name, "bootargs") == 0 )
+ continue;
+ if ( strcmp(prop_name, "xen,dom0-bootargs") == 0 )
+ prop_name = "bootargs";
+ }
+ /*
+ * In a memory node: adjust reg property.
+ */
+ else if ( device_tree_node_matches(fdt, node, "memory") )
+ {
+ if ( strcmp(prop_name, "reg") == 0 )
+ {
+ new_data = xzalloc_bytes(prop_len);
+ if ( new_data == NULL )
+ return -FDT_ERR_XEN(ENOMEM);
+
+ prop_len = set_memory_reg(d, kinfo, fdt,
+ (u32 *)prop_data, prop_len,
+ address_cells, size_cells,
+ (u32 *)new_data);
+ prop_data = new_data;
+ }
+ }
+
+ /*
+ * TODO: Should call map_mmio_regions() for all devices in the
+ * tree that have a "reg" parameter (except cpus). This
+ * requires looking into the parent node's "ranges" property
+ * to translate the bus address in the "reg" value into
+ * physical addresses. Regions also need to be rounded up to
+ * whole pages.
+ */
+
+ fdt_property(kinfo->fdt, prop_name, prop_data, prop_len);
+
+ xfree(new_data);
+ }
+
+ if ( prop == -FDT_ERR_NOTFOUND )
+ return 0;
+ return prop;
+}
-static void setup_linux_atag(paddr_t tags, paddr_t ram_s, paddr_t ram_e)
+static int write_nodes(struct domain *d, struct kernel_info *kinfo,
+ const void *fdt)
{
- paddr_t ma = gvirt_to_maddr(tags);
- void *map = map_domain_page(ma>>PAGE_SHIFT);
- void *p = map + (tags & (PAGE_SIZE - 1));
- char cmdline[] = "earlyprintk=xenboot console=ttyAMA1 root=/dev/mmcblk0 debug rw";
+ int node;
+ int depth = 0, last_depth = -1;
+ u32 address_cells[DEVICE_TREE_MAX_DEPTH];
+ u32 size_cells[DEVICE_TREE_MAX_DEPTH];
+ int ret;
- /* not enough room on this page for all the tags */
- BUG_ON(PAGE_SIZE - (tags & (PAGE_SIZE - 1)) < 8 * sizeof(uint32_t));
+ for ( node = 0, depth = 0;
+ node >= 0 && depth >= 0;
+ node = fdt_next_node(fdt, node, &depth) )
+ {
+ const char *name;
-#define TAG(type, val) *(type*)p = val; p+= sizeof(type)
+ name = fdt_get_name(fdt, node, NULL);
- /* ATAG_CORE */
- TAG(uint32_t, 2);
- TAG(uint32_t, 0x54410001);
+ if ( depth >= DEVICE_TREE_MAX_DEPTH )
+ {
+ printk("warning: node `%s' is nested too deep\n", name);
+ continue;
+ }
- /* ATAG_MEM */
- TAG(uint32_t, 4);
- TAG(uint32_t, 0x54410002);
- TAG(uint32_t, (ram_e - ram_s) & 0xFFFFFFFF);
- TAG(uint32_t, ram_s & 0xFFFFFFFF);
+ while ( last_depth-- >= depth )
+ fdt_end_node(kinfo->fdt);
- /* ATAG_CMDLINE */
- TAG(uint32_t, 2 + ((strlen(cmdline) + 4) >> 2));
- TAG(uint32_t, 0x54410009);
- memcpy(p, cmdline, strlen(cmdline) + 1);
- p += ((strlen(cmdline) + 4) >> 2) << 2;
+ address_cells[depth] = device_tree_get_u32(fdt, node, "#address-cells");
+ size_cells[depth] = device_tree_get_u32(fdt, node, "#size-cells");
- /* ATAG_NONE */
- TAG(uint32_t, 0);
- TAG(uint32_t, 0);
+ fdt_begin_node(kinfo->fdt, name);
-#undef TAG
+ ret = write_properties(d, kinfo, fdt, node, name, depth,
+ address_cells[depth-1], size_cells[depth-1]);
+ if ( ret < 0 )
+ return ret;
- unmap_domain_page(map);
+ last_depth = depth;
+ }
+
+ while ( last_depth-- >= 0 )
+ fdt_end_node(kinfo->fdt);
+
+ return 0;
+}
+
+static int prepare_dtb(struct domain *d, struct kernel_info *kinfo)
+{
+ void *fdt;
+ int new_size;
+ int ret;
+
+ fdt = device_tree_flattened;
+
+ new_size = fdt_totalsize(fdt) + DOM0_FDT_EXTRA_SIZE;
+ kinfo->fdt = xmalloc_bytes(new_size);
+ if ( kinfo->fdt == NULL )
+ return -ENOMEM;
+
+ ret = fdt_create(kinfo->fdt, new_size);
+ if ( ret < 0 )
+ goto err;
+
+ fdt_finish_reservemap(kinfo->fdt);
+
+ ret = write_nodes(d, kinfo, fdt);
+ if ( ret < 0 )
+ goto err;
+
+ ret = fdt_finish(kinfo->fdt);
+ if ( ret < 0 )
+ goto err;
+
+ /*
+ * Put the device tree at the beginning of the first bank. It
+ * must be below 4 GiB.
+ */
+ kinfo->dtb_paddr = kinfo->mem.bank[0].start + 0x100;
+ if ( kinfo->dtb_paddr + fdt_totalsize(kinfo->fdt) > (1ull << 32) )
+ {
+ printk("Not enough memory below 4 GiB for the device tree.");
+ ret = -FDT_ERR_XEN(EINVAL);
+ goto err;
+ }
+
+ return 0;
+
+ err:
+ printk("Device tree generation failed (%d).\n", ret);
+ xfree(kinfo->fdt);
+ return -EINVAL;
+}
+
+static void dtb_load(struct kernel_info *kinfo)
+{
+ void * __user dtb_virt = (void *)(u32)kinfo->dtb_paddr;
+
+ raw_copy_to_guest(dtb_virt, kinfo->fdt, fdt_totalsize(kinfo->fdt));
+ xfree(kinfo->fdt);
}
int construct_dom0(struct domain *d)
printk("*** LOADING DOMAIN 0 ***\n");
- /* 128M at 2G physical */
- /* TODO size and location from DT. */
- kinfo.ram_start = 0x80000000;
- kinfo.ram_end = 0x88000000;
+ d->max_pages = ~0U;
- rc = kernel_prepare(&kinfo);
- if (rc < 0)
+ if ( (rc = p2m_alloc_table(d)) != 0 )
return rc;
- d->max_pages = ~0U;
+ kinfo.unassigned_mem = 0x08000000; /* XXX */
- if ( (rc = p2m_alloc_table(d)) != 0 )
+ rc = prepare_dtb(d, &kinfo);
+ if ( rc < 0 )
return rc;
- printk("Populate P2M %#llx->%#llx\n", kinfo.ram_start, kinfo.ram_end);
- p2m_populate_ram(d, kinfo.ram_start, kinfo.ram_end);
+ rc = kernel_prepare(&kinfo);
+ if ( rc < 0 )
+ return rc;
printk("Map CS2 MMIO regions 1:1 in the P2M %#llx->%#llx\n", 0x18000000ULL, 0x1BFFFFFFULL);
map_mmio_regions(d, 0x18000000, 0x1BFFFFFF, 0x18000000);
/* Enable second stage translation */
WRITE_CP32(READ_CP32(HCR) | HCR_VM, HCR); isb();
- /* The following load uses domain's p2m */
+ /* The following loads use the domain's p2m */
p2m_load_VTTBR(d);
+ dtb_load(&kinfo);
kernel_load(&kinfo);
- setup_linux_atag(kinfo.ram_start + 0x100, kinfo.ram_start, kinfo.ram_end);
-
clear_bit(_VPF_down, &v->pause_flags);
memset(regs, 0, sizeof(*regs));
regs->r0 = 0; /* SBZ */
regs->r1 = 2272; /* Machine NR: Versatile Express */
- regs->r2 = kinfo.ram_start + 0x100; /* ATAGS */
+ regs->r2 = kinfo.dtb_paddr;
WRITE_CP32(SCTLR_BASE, SCTLR);
struct dt_early_info __initdata early_info;
void *device_tree_flattened;
-static bool_t __init node_matches(const void *fdt, int node, const char *match)
+bool_t device_tree_node_matches(const void *fdt, int node, const char *match)
{
const char *name;
size_t match_len;
}
}
-static void __init get_register(const u32 **cell,
- u32 address_cells, u32 size_cells,
- u64 *start, u64 *size)
+void device_tree_get_reg(const u32 **cell, u32 address_cells, u32 size_cells,
+ u64 *start, u64 *size)
{
get_val(cell, address_cells, start);
get_val(cell, size_cells, size);
}
-static u32 __init prop_by_name_u32(const void *fdt, int node,
- const char *prop_name)
+static void set_val(u32 **cell, u32 cells, u64 val)
+{
+ u32 c = cells;
+
+ while ( c-- )
+ {
+ (*cell)[c] = cpu_to_fdt32(val);
+ val >>= 32;
+ }
+ (*cell) += cells;
+}
+
+void device_tree_set_reg(u32 **cell, u32 address_cells, u32 size_cells,
+ u64 start, u64 size)
+{
+ set_val(cell, address_cells, start);
+ set_val(cell, size_cells, size);
+}
+
+u32 device_tree_get_u32(const void *fdt, int node, const char *prop_name)
{
const struct fdt_property *prop;
return fdt32_to_cpu(*(uint32_t*)prop->data);
}
-#define MAX_DEPTH 16
-
/**
* device_tree_for_each_node - iterate over all device tree nodes
* @fdt: flat device tree.
{
int node;
int depth;
- u32 address_cells[MAX_DEPTH];
- u32 size_cells[MAX_DEPTH];
+ u32 address_cells[DEVICE_TREE_MAX_DEPTH];
+ u32 size_cells[DEVICE_TREE_MAX_DEPTH];
int ret;
for ( node = 0, depth = 0;
node >=0 && depth >= 0;
node = fdt_next_node(fdt, node, &depth) )
{
- if ( depth >= MAX_DEPTH )
+ if ( depth >= DEVICE_TREE_MAX_DEPTH )
continue;
- address_cells[depth] = prop_by_name_u32(fdt, node, "#address-cells");
- size_cells[depth] = prop_by_name_u32(fdt, node, "#size-cells");
+ address_cells[depth] = device_tree_get_u32(fdt, node, "#address-cells");
+ size_cells[depth] = device_tree_get_u32(fdt, node, "#size-cells");
ret = func(fdt, node, fdt_get_name(fdt, node, NULL), depth,
address_cells[depth-1], size_cells[depth-1], data);
static int dump_node(const void *fdt, int node, const char *name, int depth,
u32 address_cells, u32 size_cells, void *data)
{
- char prefix[2*MAX_DEPTH + 1] = "";
+ char prefix[2*DEVICE_TREE_MAX_DEPTH + 1] = "";
int i;
int prop;
for ( i = 0; i < banks && early_info.mem.nr_banks < NR_MEM_BANKS; i++ )
{
- get_register(&cell, address_cells, size_cells, &start, &size);
+ device_tree_get_reg(&cell, address_cells, size_cells, &start, &size);
early_info.mem.bank[early_info.mem.nr_banks].start = start;
early_info.mem.bank[early_info.mem.nr_banks].size = size;
early_info.mem.nr_banks++;
u32 address_cells, u32 size_cells,
void *data)
{
- if ( node_matches(fdt, node, "memory") )
+ if ( device_tree_node_matches(fdt, node, "memory") )
process_memory_node(fdt, node, name, address_cells, size_cells);
return 0;