diff mbox

[kvm-unit-tests,v4,12/17] powerpc/ppc64: adapt arm's setup

Message ID 1455544166-19766-13-git-send-email-drjones@redhat.com
State Superseded
Headers show

Commit Message

Andrew Jones Feb. 15, 2016, 1:49 p.m. UTC
Copy arm's setup code (also DT based) over to powerpc, adapting
it a bit. Also bring over arm's setup selftest, giving powerpc
its first test.

Signed-off-by: Andrew Jones <drjones@redhat.com>
Reviewed-by: Thomas Huth <thuth@redhat.com>
Tested-by: Laurent Vivier <lvivier@redhat.com>
---
 lib/powerpc/asm/setup.h |  36 +++++++++++++++
 lib/powerpc/setup.c     | 119 ++++++++++++++++++++++++++++++++++++++++++++++++
 lib/ppc64/asm/setup.h   |   1 +
 powerpc/Makefile.common |   1 +
 powerpc/cstart64.S      |  16 +++++++
 powerpc/selftest.c      |  63 +++++++++++++++++++++++--
 6 files changed, 233 insertions(+), 3 deletions(-)
 create mode 100644 lib/powerpc/asm/setup.h
 create mode 100644 lib/powerpc/setup.c
 create mode 100644 lib/ppc64/asm/setup.h

Comments

David Gibson Feb. 17, 2016, 2:11 a.m. UTC | #1
On Mon, Feb 15, 2016 at 02:49:21PM +0100, Andrew Jones wrote:
> Copy arm's setup code (also DT based) over to powerpc, adapting
> it a bit. Also bring over arm's setup selftest, giving powerpc
> its first test.
> 
> Signed-off-by: Andrew Jones <drjones@redhat.com>
> Reviewed-by: Thomas Huth <thuth@redhat.com>
> Tested-by: Laurent Vivier <lvivier@redhat.com>
> ---
>  lib/powerpc/asm/setup.h |  36 +++++++++++++++
>  lib/powerpc/setup.c     | 119 ++++++++++++++++++++++++++++++++++++++++++++++++
>  lib/ppc64/asm/setup.h   |   1 +
>  powerpc/Makefile.common |   1 +
>  powerpc/cstart64.S      |  16 +++++++
>  powerpc/selftest.c      |  63 +++++++++++++++++++++++--
>  6 files changed, 233 insertions(+), 3 deletions(-)
>  create mode 100644 lib/powerpc/asm/setup.h
>  create mode 100644 lib/powerpc/setup.c
>  create mode 100644 lib/ppc64/asm/setup.h
> 
> diff --git a/lib/powerpc/asm/setup.h b/lib/powerpc/asm/setup.h
> new file mode 100644
> index 0000000000000..0b9f04b4b7289
> --- /dev/null
> +++ b/lib/powerpc/asm/setup.h
> @@ -0,0 +1,36 @@
> +#ifndef _ASMPOWERPC_SETUP_H_
> +#define _ASMPOWERPC_SETUP_H_
> +/*
> + * Copyright (C) 2016, Red Hat Inc, Andrew Jones <drjones@redhat.com>
> + *
> + * This work is licensed under the terms of the GNU LGPL, version 2.
> + */
> +#include <libcflat.h>
> +#include <alloc.h>	/* phys_addr_t */
> +
> +#define NR_CPUS			8	/* arbitrarily set for now */
> +extern u32 cpus[NR_CPUS];
> +extern int nr_cpus;
> +
> +#define NR_MEM_REGIONS		8
> +#define MR_F_PRIMARY		(1U << 0)
> +struct mem_region {
> +	phys_addr_t start;
> +	phys_addr_t end;
> +	unsigned int flags;
> +};
> +extern struct mem_region mem_regions[NR_MEM_REGIONS];
> +extern phys_addr_t __physical_start, __physical_end;
> +
> +#define PHYSICAL_START		(__physical_start)
> +#define PHYSICAL_END		(__physical_end)
> +
> +#ifdef __powerpc64__
> +#define L1_CACHE_SHIFT		7
> +#else
> +#define L1_CACHE_SHIFT		5
> +#endif

These aren't necessarily true for all systems, although it's probably
right for all BE, BookS systems supporting KVM at present.  It would
be better to get the cache alignment from the device tree if possible.

Also, is this correct for LE, which I imagine you'll want to support
soon (I don't recall if __powerpc64__ is defined for both BE and LE
systems).

> +#define L1_CACHE_BYTES		(1 << L1_CACHE_SHIFT)
> +#define SMP_CACHE_BYTES		L1_CACHE_BYTES
> +
> +#endif /* _ASMPOWERPC_SETUP_H_ */
> diff --git a/lib/powerpc/setup.c b/lib/powerpc/setup.c
> new file mode 100644
> index 0000000000000..2514be4fa273c
> --- /dev/null
> +++ b/lib/powerpc/setup.c
> @@ -0,0 +1,119 @@
> +/*
> + * Initialize machine setup information and I/O.
> + *
> + * After running setup() unit tests may query how many cpus they have
> + * (nr_cpus), how much memory they have (PHYSICAL_END - PHYSICAL_START),
> + * may use dynamic memory allocation (malloc, etc.), printf, and exit.
> + * Finally, argc and argv are also ready to be passed to main().
> + *
> + * Copyright (C) 2016, Red Hat Inc, Andrew Jones <drjones@redhat.com>
> + *
> + * This work is licensed under the terms of the GNU LGPL, version 2.
> + */
> +#include <libcflat.h>
> +#include <libfdt/libfdt.h>
> +#include <devicetree.h>
> +#include <alloc.h>
> +#include <asm/setup.h>
> +#include <asm/page.h>
> +
> +extern unsigned long stacktop;
> +extern void io_init(void);
> +extern void setup_args(const char *args);
> +
> +u32 cpus[NR_CPUS] = { [0 ... NR_CPUS-1] = (~0U) };
> +int nr_cpus;
> +
> +struct mem_region mem_regions[NR_MEM_REGIONS];
> +phys_addr_t __physical_start, __physical_end;
> +
> +static void cpu_set(int fdtnode __unused, u32 regval, void *info __unused)
> +{
> +	int cpu = nr_cpus++;
> +
> +	if (cpu >= NR_CPUS) {
> +		printf("Number cpus exceeds maximum supported (%d).\n",
> +			NR_CPUS);
> +		assert(0);
> +	}
> +	cpus[cpu] = regval;
> +}
> +
> +static void cpu_init(void)
> +{
> +	int ret;
> +
> +	nr_cpus = 0;
> +	ret = dt_for_each_cpu_node(cpu_set, NULL);
> +	assert(ret == 0);
> +}
> +
> +static void mem_init(phys_addr_t freemem_start)
> +{
> +	struct dt_pbus_reg regs[NR_MEM_REGIONS];
> +	struct mem_region primary, mem = {
> +		.start = (phys_addr_t)-1,
> +	};
> +	int nr_regs, i;
> +
> +	nr_regs = dt_get_memory_params(regs, NR_MEM_REGIONS);
> +	assert(nr_regs > 0);
> +
> +	primary.end = 0;
> +
> +	for (i = 0; i < nr_regs; ++i) {
> +		mem_regions[i].start = regs[i].addr;
> +		mem_regions[i].end = regs[i].addr + regs[i].size;
> +
> +		/*
> +		 * pick the region we're in for our primary region
> +		 */
> +		if (freemem_start >= mem_regions[i].start
> +				&& freemem_start < mem_regions[i].end) {
> +			mem_regions[i].flags |= MR_F_PRIMARY;
> +			primary = mem_regions[i];
> +		}
> +
> +		/*
> +		 * set the lowest and highest addresses found,
> +		 * ignoring potential gaps
> +		 */
> +		if (mem_regions[i].start < mem.start)
> +			mem.start = mem_regions[i].start;
> +		if (mem_regions[i].end > mem.end)
> +			mem.end = mem_regions[i].end;
> +	}
> +	assert(primary.end != 0);
> +//	assert(!(mem.start & ~PHYS_MASK) && !((mem.end - 1) & ~PHYS_MASK));
> +
> +	__physical_start = mem.start;	/* PHYSICAL_START */
> +	__physical_end = mem.end;	/* PHYSICAL_END */
> +
> +	phys_alloc_init(freemem_start, primary.end - freemem_start);
> +	phys_alloc_set_minimum_alignment(SMP_CACHE_BYTES);
> +}
> +
> +void setup(const void *fdt)
> +{
> +	const char *bootargs;
> +	u32 fdt_size;
> +	int ret;
> +
> +	/*
> +	 * Move the fdt to just above the stack. The free memory
> +	 * then starts just after the fdt.
> +	 */
> +	fdt_size = fdt_totalsize(fdt);
> +	ret = fdt_move(fdt, &stacktop, fdt_size);
> +	assert(ret == 0);
> +	ret = dt_init(&stacktop);
> +	assert(ret == 0);
> +
> +	mem_init(PAGE_ALIGN((unsigned long)&stacktop + fdt_size));
> +	io_init();
> +	cpu_init();
> +
> +	ret = dt_get_bootargs(&bootargs);
> +	assert(ret == 0);
> +	setup_args(bootargs);
> +}
> diff --git a/lib/ppc64/asm/setup.h b/lib/ppc64/asm/setup.h
> new file mode 100644
> index 0000000000000..20192985928a4
> --- /dev/null
> +++ b/lib/ppc64/asm/setup.h
> @@ -0,0 +1 @@
> +#include "../../powerpc/asm/setup.h"
> diff --git a/powerpc/Makefile.common b/powerpc/Makefile.common
> index 89610b525f0c1..de4d1ef3ac816 100644
> --- a/powerpc/Makefile.common
> +++ b/powerpc/Makefile.common
> @@ -31,6 +31,7 @@ cflatobjs += lib/alloc.o
>  cflatobjs += lib/devicetree.o
>  cflatobjs += lib/powerpc/io.o
>  cflatobjs += lib/powerpc/hcall.o
> +cflatobjs += lib/powerpc/setup.o
>  
>  libgcc := $(shell $(CC) $(machine) --print-libgcc-file-name)
>  start_addr := $(shell printf "%x\n" $$(( $(phys_base) + $(kernel_offset) )))
> diff --git a/powerpc/cstart64.S b/powerpc/cstart64.S
> index 623fd693b02d1..6d47eb59bb53e 100644
> --- a/powerpc/cstart64.S
> +++ b/powerpc/cstart64.S
> @@ -19,11 +19,27 @@
>  
>  .section .init
>  
> +/*
> + * start is the entry point. r3 points to the DTB
> + */
>  .globl start
>  start:
>  	LOAD_REG_IMMEDIATE(r1, stackptr)
>  	LOAD_REG_IMMEDIATE(r2, tocptr)
> +
> +	/* save DTB pointer */
> +	std	r3, 56(r1)
> +
>  	bl	hcall_patch_broken_sc1
> +
> +	/* complete setup */
> +	ld	r3, 56(r1)
> +	bl	setup
> +
> +	/* run the test */
> +	LOAD_REG_IMMEDIATE(r5, __argc)
> +	LOAD_REG_IMMEDIATE(r4, __argv)
> +	lwz	r3, 0(r5)
>  	bl	main
>  	bl	exit
>  	b	halt
> diff --git a/powerpc/selftest.c b/powerpc/selftest.c
> index 2f2a5215dd55c..84867e482d2a2 100644
> --- a/powerpc/selftest.c
> +++ b/powerpc/selftest.c
> @@ -1,7 +1,64 @@
> +/*
> + * Test the framework itself. These tests confirm that setup works.
> + *
> + * Copyright (C) 2016, Red Hat Inc, Andrew Jones <drjones@redhat.com>
> + *
> + * This work is licensed under the terms of the GNU LGPL, version 2.
> + */
>  #include <libcflat.h>
> +#include <util.h>
> +#include <asm/setup.h>
>  
> -int main(void)
> +static void check_setup(int argc, char **argv)
>  {
> -	printf("hello world\n");
> -	return 0;
> +	int nr_tests = 0, len, i;
> +	long val;
> +
> +	for (i = 0; i < argc; ++i) {
> +
> +		len = parse_keyval(argv[i], &val);
> +		if (len == -1)
> +			continue;
> +
> +		argv[i][len] = '\0';
> +		report_prefix_push(argv[i]);
> +
> +		if (strcmp(argv[i], "mem") == 0) {
> +
> +			phys_addr_t memsize = PHYSICAL_END - PHYSICAL_START;
> +			phys_addr_t expected = ((phys_addr_t)val)*1024*1024;
> +
> +			report("size = %d MB", memsize == expected,
> +							memsize/1024/1024);
> +			++nr_tests;
> +
> +		} else if (strcmp(argv[i], "smp") == 0) {
> +
> +			report("nr_cpus = %d", nr_cpus == (int)val, nr_cpus);
> +			++nr_tests;
> +		}
> +
> +		report_prefix_pop();
> +	}
> +
> +	if (nr_tests < 2)
> +		report_abort("missing input");
> +}
> +
> +int main(int argc, char **argv)
> +{
> +	report_prefix_push("selftest");
> +
> +	if (!argc)
> +		report_abort("no test specified");
> +
> +	report_prefix_push(argv[0]);
> +
> +	if (strcmp(argv[0], "setup") == 0) {
> +
> +		check_setup(argc-1, &argv[1]);
> +
> +	}
> +
> +	return report_summary();
>  }
Andrew Jones Feb. 17, 2016, 12:45 p.m. UTC | #2
On Wed, Feb 17, 2016 at 01:11:58PM +1100, David Gibson wrote:
> On Mon, Feb 15, 2016 at 02:49:21PM +0100, Andrew Jones wrote:
> > Copy arm's setup code (also DT based) over to powerpc, adapting
> > it a bit. Also bring over arm's setup selftest, giving powerpc
> > its first test.
> > 
> > Signed-off-by: Andrew Jones <drjones@redhat.com>
> > Reviewed-by: Thomas Huth <thuth@redhat.com>
> > Tested-by: Laurent Vivier <lvivier@redhat.com>
> > ---
> >  lib/powerpc/asm/setup.h |  36 +++++++++++++++
> >  lib/powerpc/setup.c     | 119 ++++++++++++++++++++++++++++++++++++++++++++++++
> >  lib/ppc64/asm/setup.h   |   1 +
> >  powerpc/Makefile.common |   1 +
> >  powerpc/cstart64.S      |  16 +++++++
> >  powerpc/selftest.c      |  63 +++++++++++++++++++++++--
> >  6 files changed, 233 insertions(+), 3 deletions(-)
> >  create mode 100644 lib/powerpc/asm/setup.h
> >  create mode 100644 lib/powerpc/setup.c
> >  create mode 100644 lib/ppc64/asm/setup.h
> > 
> > diff --git a/lib/powerpc/asm/setup.h b/lib/powerpc/asm/setup.h
> > new file mode 100644
> > index 0000000000000..0b9f04b4b7289
> > --- /dev/null
> > +++ b/lib/powerpc/asm/setup.h
> > @@ -0,0 +1,36 @@
> > +#ifndef _ASMPOWERPC_SETUP_H_
> > +#define _ASMPOWERPC_SETUP_H_
> > +/*
> > + * Copyright (C) 2016, Red Hat Inc, Andrew Jones <drjones@redhat.com>
> > + *
> > + * This work is licensed under the terms of the GNU LGPL, version 2.
> > + */
> > +#include <libcflat.h>
> > +#include <alloc.h>	/* phys_addr_t */
> > +
> > +#define NR_CPUS			8	/* arbitrarily set for now */
> > +extern u32 cpus[NR_CPUS];
> > +extern int nr_cpus;
> > +
> > +#define NR_MEM_REGIONS		8
> > +#define MR_F_PRIMARY		(1U << 0)
> > +struct mem_region {
> > +	phys_addr_t start;
> > +	phys_addr_t end;
> > +	unsigned int flags;
> > +};
> > +extern struct mem_region mem_regions[NR_MEM_REGIONS];
> > +extern phys_addr_t __physical_start, __physical_end;
> > +
> > +#define PHYSICAL_START		(__physical_start)
> > +#define PHYSICAL_END		(__physical_end)
> > +
> > +#ifdef __powerpc64__
> > +#define L1_CACHE_SHIFT		7
> > +#else
> > +#define L1_CACHE_SHIFT		5
> > +#endif
> 
> These aren't necessarily true for all systems, although it's probably
> right for all BE, BookS systems supporting KVM at present.  It would
> be better to get the cache alignment from the device tree if possible.

Ah, I hadn't considered being able to do that. I presume I can expect
i-cache-line-size to always == d-cache-line-size, right?

> 
> Also, is this correct for LE, which I imagine you'll want to support
> soon (I don't recall if __powerpc64__ is defined for both BE and LE
> systems).

This use of __powerpc64__ will go away with the above change, but I
have another one in rtas.c. I don't know if it's valid for LE, but
I'd prefer to ignore LE for now, and then create a new series that
modifies everything necessary for enabling both BE and LE compiles.

Thanks,
drew
--
To unsubscribe from this list: send the line "unsubscribe kvm-ppc" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Andrew Jones Feb. 17, 2016, 5:34 p.m. UTC | #3
On Wed, Feb 17, 2016 at 01:45:40PM +0100, Andrew Jones wrote:
> On Wed, Feb 17, 2016 at 01:11:58PM +1100, David Gibson wrote:
> > These aren't necessarily true for all systems, although it's probably
> > right for all BE, BookS systems supporting KVM at present.  It would
> > be better to get the cache alignment from the device tree if possible.
> 
> Ah, I hadn't considered being able to do that. I presume I can expect
> i-cache-line-size to always == d-cache-line-size, right?
>

I see that I can't. There's at least one QEMU processor that has them
different. I don't need to make that assumption anyway. I just need to
know the greater of the two, and can just look at both to figure that
out.

drew
--
To unsubscribe from this list: send the line "unsubscribe kvm-ppc" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
David Gibson Feb. 18, 2016, 12:05 a.m. UTC | #4
On Wed, Feb 17, 2016 at 01:45:40PM +0100, Andrew Jones wrote:
> On Wed, Feb 17, 2016 at 01:11:58PM +1100, David Gibson wrote:
> > On Mon, Feb 15, 2016 at 02:49:21PM +0100, Andrew Jones wrote:
> > > Copy arm's setup code (also DT based) over to powerpc, adapting
> > > it a bit. Also bring over arm's setup selftest, giving powerpc
> > > its first test.
> > > 
> > > Signed-off-by: Andrew Jones <drjones@redhat.com>
> > > Reviewed-by: Thomas Huth <thuth@redhat.com>
> > > Tested-by: Laurent Vivier <lvivier@redhat.com>
> > > ---
> > >  lib/powerpc/asm/setup.h |  36 +++++++++++++++
> > >  lib/powerpc/setup.c     | 119 ++++++++++++++++++++++++++++++++++++++++++++++++
> > >  lib/ppc64/asm/setup.h   |   1 +
> > >  powerpc/Makefile.common |   1 +
> > >  powerpc/cstart64.S      |  16 +++++++
> > >  powerpc/selftest.c      |  63 +++++++++++++++++++++++--
> > >  6 files changed, 233 insertions(+), 3 deletions(-)
> > >  create mode 100644 lib/powerpc/asm/setup.h
> > >  create mode 100644 lib/powerpc/setup.c
> > >  create mode 100644 lib/ppc64/asm/setup.h
> > > 
> > > diff --git a/lib/powerpc/asm/setup.h b/lib/powerpc/asm/setup.h
> > > new file mode 100644
> > > index 0000000000000..0b9f04b4b7289
> > > --- /dev/null
> > > +++ b/lib/powerpc/asm/setup.h
> > > @@ -0,0 +1,36 @@
> > > +#ifndef _ASMPOWERPC_SETUP_H_
> > > +#define _ASMPOWERPC_SETUP_H_
> > > +/*
> > > + * Copyright (C) 2016, Red Hat Inc, Andrew Jones <drjones@redhat.com>
> > > + *
> > > + * This work is licensed under the terms of the GNU LGPL, version 2.
> > > + */
> > > +#include <libcflat.h>
> > > +#include <alloc.h>	/* phys_addr_t */
> > > +
> > > +#define NR_CPUS			8	/* arbitrarily set for now */
> > > +extern u32 cpus[NR_CPUS];
> > > +extern int nr_cpus;
> > > +
> > > +#define NR_MEM_REGIONS		8
> > > +#define MR_F_PRIMARY		(1U << 0)
> > > +struct mem_region {
> > > +	phys_addr_t start;
> > > +	phys_addr_t end;
> > > +	unsigned int flags;
> > > +};
> > > +extern struct mem_region mem_regions[NR_MEM_REGIONS];
> > > +extern phys_addr_t __physical_start, __physical_end;
> > > +
> > > +#define PHYSICAL_START		(__physical_start)
> > > +#define PHYSICAL_END		(__physical_end)
> > > +
> > > +#ifdef __powerpc64__
> > > +#define L1_CACHE_SHIFT		7
> > > +#else
> > > +#define L1_CACHE_SHIFT		5
> > > +#endif
> > 
> > These aren't necessarily true for all systems, although it's probably
> > right for all BE, BookS systems supporting KVM at present.  It would
> > be better to get the cache alignment from the device tree if possible.
> 
> Ah, I hadn't considered being able to do that. I presume I can expect
> i-cache-line-size to always == d-cache-line-size, right?

In theory, no.  In practice, I don't think I've ever seen a machine
with them different, at least not anything even vaguely recent.

> > Also, is this correct for LE, which I imagine you'll want to support
> > soon (I don't recall if __powerpc64__ is defined for both BE and LE
> > systems).
> 
> This use of __powerpc64__ will go away with the above change, but I
> have another one in rtas.c. I don't know if it's valid for LE, but
> I'd prefer to ignore LE for now, and then create a new series that
> modifies everything necessary for enabling both BE and LE compiles.

Ok.
diff mbox

Patch

diff --git a/lib/powerpc/asm/setup.h b/lib/powerpc/asm/setup.h
new file mode 100644
index 0000000000000..0b9f04b4b7289
--- /dev/null
+++ b/lib/powerpc/asm/setup.h
@@ -0,0 +1,36 @@ 
+#ifndef _ASMPOWERPC_SETUP_H_
+#define _ASMPOWERPC_SETUP_H_
+/*
+ * Copyright (C) 2016, Red Hat Inc, Andrew Jones <drjones@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2.
+ */
+#include <libcflat.h>
+#include <alloc.h>	/* phys_addr_t */
+
+#define NR_CPUS			8	/* arbitrarily set for now */
+extern u32 cpus[NR_CPUS];
+extern int nr_cpus;
+
+#define NR_MEM_REGIONS		8
+#define MR_F_PRIMARY		(1U << 0)
+struct mem_region {
+	phys_addr_t start;
+	phys_addr_t end;
+	unsigned int flags;
+};
+extern struct mem_region mem_regions[NR_MEM_REGIONS];
+extern phys_addr_t __physical_start, __physical_end;
+
+#define PHYSICAL_START		(__physical_start)
+#define PHYSICAL_END		(__physical_end)
+
+#ifdef __powerpc64__
+#define L1_CACHE_SHIFT		7
+#else
+#define L1_CACHE_SHIFT		5
+#endif
+#define L1_CACHE_BYTES		(1 << L1_CACHE_SHIFT)
+#define SMP_CACHE_BYTES		L1_CACHE_BYTES
+
+#endif /* _ASMPOWERPC_SETUP_H_ */
diff --git a/lib/powerpc/setup.c b/lib/powerpc/setup.c
new file mode 100644
index 0000000000000..2514be4fa273c
--- /dev/null
+++ b/lib/powerpc/setup.c
@@ -0,0 +1,119 @@ 
+/*
+ * Initialize machine setup information and I/O.
+ *
+ * After running setup() unit tests may query how many cpus they have
+ * (nr_cpus), how much memory they have (PHYSICAL_END - PHYSICAL_START),
+ * may use dynamic memory allocation (malloc, etc.), printf, and exit.
+ * Finally, argc and argv are also ready to be passed to main().
+ *
+ * Copyright (C) 2016, Red Hat Inc, Andrew Jones <drjones@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2.
+ */
+#include <libcflat.h>
+#include <libfdt/libfdt.h>
+#include <devicetree.h>
+#include <alloc.h>
+#include <asm/setup.h>
+#include <asm/page.h>
+
+extern unsigned long stacktop;
+extern void io_init(void);
+extern void setup_args(const char *args);
+
+u32 cpus[NR_CPUS] = { [0 ... NR_CPUS-1] = (~0U) };
+int nr_cpus;
+
+struct mem_region mem_regions[NR_MEM_REGIONS];
+phys_addr_t __physical_start, __physical_end;
+
+static void cpu_set(int fdtnode __unused, u32 regval, void *info __unused)
+{
+	int cpu = nr_cpus++;
+
+	if (cpu >= NR_CPUS) {
+		printf("Number cpus exceeds maximum supported (%d).\n",
+			NR_CPUS);
+		assert(0);
+	}
+	cpus[cpu] = regval;
+}
+
+static void cpu_init(void)
+{
+	int ret;
+
+	nr_cpus = 0;
+	ret = dt_for_each_cpu_node(cpu_set, NULL);
+	assert(ret == 0);
+}
+
+static void mem_init(phys_addr_t freemem_start)
+{
+	struct dt_pbus_reg regs[NR_MEM_REGIONS];
+	struct mem_region primary, mem = {
+		.start = (phys_addr_t)-1,
+	};
+	int nr_regs, i;
+
+	nr_regs = dt_get_memory_params(regs, NR_MEM_REGIONS);
+	assert(nr_regs > 0);
+
+	primary.end = 0;
+
+	for (i = 0; i < nr_regs; ++i) {
+		mem_regions[i].start = regs[i].addr;
+		mem_regions[i].end = regs[i].addr + regs[i].size;
+
+		/*
+		 * pick the region we're in for our primary region
+		 */
+		if (freemem_start >= mem_regions[i].start
+				&& freemem_start < mem_regions[i].end) {
+			mem_regions[i].flags |= MR_F_PRIMARY;
+			primary = mem_regions[i];
+		}
+
+		/*
+		 * set the lowest and highest addresses found,
+		 * ignoring potential gaps
+		 */
+		if (mem_regions[i].start < mem.start)
+			mem.start = mem_regions[i].start;
+		if (mem_regions[i].end > mem.end)
+			mem.end = mem_regions[i].end;
+	}
+	assert(primary.end != 0);
+//	assert(!(mem.start & ~PHYS_MASK) && !((mem.end - 1) & ~PHYS_MASK));
+
+	__physical_start = mem.start;	/* PHYSICAL_START */
+	__physical_end = mem.end;	/* PHYSICAL_END */
+
+	phys_alloc_init(freemem_start, primary.end - freemem_start);
+	phys_alloc_set_minimum_alignment(SMP_CACHE_BYTES);
+}
+
+void setup(const void *fdt)
+{
+	const char *bootargs;
+	u32 fdt_size;
+	int ret;
+
+	/*
+	 * Move the fdt to just above the stack. The free memory
+	 * then starts just after the fdt.
+	 */
+	fdt_size = fdt_totalsize(fdt);
+	ret = fdt_move(fdt, &stacktop, fdt_size);
+	assert(ret == 0);
+	ret = dt_init(&stacktop);
+	assert(ret == 0);
+
+	mem_init(PAGE_ALIGN((unsigned long)&stacktop + fdt_size));
+	io_init();
+	cpu_init();
+
+	ret = dt_get_bootargs(&bootargs);
+	assert(ret == 0);
+	setup_args(bootargs);
+}
diff --git a/lib/ppc64/asm/setup.h b/lib/ppc64/asm/setup.h
new file mode 100644
index 0000000000000..20192985928a4
--- /dev/null
+++ b/lib/ppc64/asm/setup.h
@@ -0,0 +1 @@ 
+#include "../../powerpc/asm/setup.h"
diff --git a/powerpc/Makefile.common b/powerpc/Makefile.common
index 89610b525f0c1..de4d1ef3ac816 100644
--- a/powerpc/Makefile.common
+++ b/powerpc/Makefile.common
@@ -31,6 +31,7 @@  cflatobjs += lib/alloc.o
 cflatobjs += lib/devicetree.o
 cflatobjs += lib/powerpc/io.o
 cflatobjs += lib/powerpc/hcall.o
+cflatobjs += lib/powerpc/setup.o
 
 libgcc := $(shell $(CC) $(machine) --print-libgcc-file-name)
 start_addr := $(shell printf "%x\n" $$(( $(phys_base) + $(kernel_offset) )))
diff --git a/powerpc/cstart64.S b/powerpc/cstart64.S
index 623fd693b02d1..6d47eb59bb53e 100644
--- a/powerpc/cstart64.S
+++ b/powerpc/cstart64.S
@@ -19,11 +19,27 @@ 
 
 .section .init
 
+/*
+ * start is the entry point. r3 points to the DTB
+ */
 .globl start
 start:
 	LOAD_REG_IMMEDIATE(r1, stackptr)
 	LOAD_REG_IMMEDIATE(r2, tocptr)
+
+	/* save DTB pointer */
+	std	r3, 56(r1)
+
 	bl	hcall_patch_broken_sc1
+
+	/* complete setup */
+	ld	r3, 56(r1)
+	bl	setup
+
+	/* run the test */
+	LOAD_REG_IMMEDIATE(r5, __argc)
+	LOAD_REG_IMMEDIATE(r4, __argv)
+	lwz	r3, 0(r5)
 	bl	main
 	bl	exit
 	b	halt
diff --git a/powerpc/selftest.c b/powerpc/selftest.c
index 2f2a5215dd55c..84867e482d2a2 100644
--- a/powerpc/selftest.c
+++ b/powerpc/selftest.c
@@ -1,7 +1,64 @@ 
+/*
+ * Test the framework itself. These tests confirm that setup works.
+ *
+ * Copyright (C) 2016, Red Hat Inc, Andrew Jones <drjones@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2.
+ */
 #include <libcflat.h>
+#include <util.h>
+#include <asm/setup.h>
 
-int main(void)
+static void check_setup(int argc, char **argv)
 {
-	printf("hello world\n");
-	return 0;
+	int nr_tests = 0, len, i;
+	long val;
+
+	for (i = 0; i < argc; ++i) {
+
+		len = parse_keyval(argv[i], &val);
+		if (len == -1)
+			continue;
+
+		argv[i][len] = '\0';
+		report_prefix_push(argv[i]);
+
+		if (strcmp(argv[i], "mem") == 0) {
+
+			phys_addr_t memsize = PHYSICAL_END - PHYSICAL_START;
+			phys_addr_t expected = ((phys_addr_t)val)*1024*1024;
+
+			report("size = %d MB", memsize == expected,
+							memsize/1024/1024);
+			++nr_tests;
+
+		} else if (strcmp(argv[i], "smp") == 0) {
+
+			report("nr_cpus = %d", nr_cpus == (int)val, nr_cpus);
+			++nr_tests;
+		}
+
+		report_prefix_pop();
+	}
+
+	if (nr_tests < 2)
+		report_abort("missing input");
+}
+
+int main(int argc, char **argv)
+{
+	report_prefix_push("selftest");
+
+	if (!argc)
+		report_abort("no test specified");
+
+	report_prefix_push(argv[0]);
+
+	if (strcmp(argv[0], "setup") == 0) {
+
+		check_setup(argc-1, &argv[1]);
+
+	}
+
+	return report_summary();
 }