diff --git a/README b/README
index 5e479c7..5ba0e23 100644
--- a/README
+++ b/README
@@ -1,7 +1,7 @@
 skiboot
 -------
 
-Firmware for OpenPower systems
+Firmware for OpenPower systems.
 
 Source:
 https://github.com/open-power/skiboot
@@ -43,22 +43,52 @@ make OPAL calls. A TODO item is to extensively document this API.
 
 See doc/overview.txt for a more in depth overview of skiboot.
 
+Building
+--------
+You can build on a linux host. Modern Debian and Ubuntu are well known
+to be suitable. Build and testing on x86 is fine. You do not need a POWER
+host to build and test skiboot.
 
-Hacking
--------
-You will need a C compiler (gcc 4.8) for ppc64 (big endian).
-You will need a POWER8 system that you can deploy new firmware to.
+You will need a C compiler for big endian ppc64. If your distro does
+not provide one, crosstool built compilers work well:
+https://www.kernel.org/pub/tools/crosstool/
+
+You should then be able to just (where 4=nr cpu cores of your machine)
+$ make -j4
+$ make -j4 check
+
+If using crosstool compilers, add /opt/cross/gcc-4.8.0-nolibc/powerpc64-linux/bin/
+to your PATH.
+
+If using packaged cross compilers on Ubuntu, you may need to set the
+following environment variable:
+CROSS=powerpc-linux-gnu-
 
-You may also use the IBM POWER8 Functional Simulator to test on.
+Testing
+-------
+To test in a simulator, install the IBM POWER8 Functional Simulator from:
 http://www-304.ibm.com/support/customercare/sas/f/pwrfs/home.html
 
 Qemu (as of 2.2.0) is not suitable as it does not (yet) implement
 the HyperVisor mode of the POWER8 processor.
 
+To run a boot-to-bootloader test, you'll need a zImage.papr built using
+the mambo_defconfig config for op-build. See
+https://github.com/open-power/op-build/ on howto build. Drop zImage.epapr
+in the skiboot directory and the skiboot test suite will automatically pick
+it up.
+
+To test on real hardware, you will need to understand how to flash new
+skiboot onto your system. This will vary from platform to platform.
+
+Hacking
+-------
 All patches should be sent to the mailing list with linux-kernel style
-'Signed-Off-By'.
+'Signed-Off-By'. The following git commands are your friends:
+- git commit -s
+- git format-patch
 
-You probably want to read the linux documentation/SubmittingPatches as
+You probably want to read the linux Documentation/SubmittingPatches as
 much of it applies to skiboot.
 
 License
diff --git a/ccan/Makefile.check b/ccan/Makefile.check
index 767b338..2bc1f1d 100644
--- a/ccan/Makefile.check
+++ b/ccan/Makefile.check
@@ -39,5 +39,5 @@ ccan-test-clean:
 		$(CCAN_TEST:%=%-gcov) \
 		$(CCAN_TEST:%=%.d) \
 		$(CCAN_TEST:%=%.o) \
-		$(CCAN_TEST:%=%-gcov.gcda) \
-		$(CCAN_TEST:%=%-gcov.gcno)
\ No newline at end of file
+		$(CCAN_TEST:%=%.gcda) \
+		$(CCAN_TEST:%=%.gcno)
diff --git a/include/opal.h b/include/opal.h
index 0cbdf4d..2da0929 100644
--- a/include/opal.h
+++ b/include/opal.h
@@ -51,7 +51,6 @@
 #define OPAL_I2C_STOP_ERR	-24
 
 /* API Tokens (in r0) */
-#define OPAL_INVALID_CALL		       -1
 #define OPAL_TEST				0
 #define OPAL_CONSOLE_WRITE			1
 #define OPAL_CONSOLE_READ			2
@@ -164,7 +163,6 @@
 #ifndef __ASSEMBLY__
 
 /* Other enums */
-
 enum OpalVendorApiTokens {
 	OPAL_START_VENDOR_API_RANGE = 1000, OPAL_END_VENDOR_API_RANGE = 1999
 };
@@ -256,6 +254,7 @@ enum OpalMmioWindowType {
 	OPAL_M64_WINDOW_TYPE = 2,
 	OPAL_IO_WINDOW_TYPE  = 3
 };
+
 enum OpalShpcSlotState {
 	OPAL_SHPC_DEV_NOT_PRESENT = 0,
 	OPAL_SHPC_DEV_PRESENT	  = 1
diff --git a/libc/test/Makefile.check b/libc/test/Makefile.check
index f37fe93..eb6fac8 100644
--- a/libc/test/Makefile.check
+++ b/libc/test/Makefile.check
@@ -57,6 +57,15 @@ $(LIBC_DUALLIB_TEST:%=%-gcov-test.o): %-gcov-test.o : %-test.c %
 clean: libc-test-clean
 
 libc-test-clean:
-	$(RM) -f libc/test/*.[od] $(LIBC_TEST) $(LIBC_TEST:%=%-gcov)
-	$(RM) -f $(LIBC_DUALLIB_TEST) $(LIBC_DUALLIB_TEST:%=%-gcov) \
+	$(RM) -f libc/test/*.[od]
+	$(RM) -f $(LIBC_TEST) \
+		$(LIBC_TEST:%=%-gcov) \
+		$(LIBC_TEST:%=%.gcda) \
+		$(LIBC_TEST:%=%.gcno)
+	$(RM) -f $(LIBC_DUALLIB_TEST) \
+		$(LIBC_DUALLIB_TEST:%=%-gcov) \
+		$(LIBC_DUALLIB_TEST:%=%-gcov.gcda) \
+		$(LIBC_DUALLIB_TEST:%=%-gcov.gcno) \
+		$(LIBC_DUALLIB_TEST:%=%-gcov-test.gcda) \
+		$(LIBC_DUALLIB_TEST:%=%-gcov-test.gcno) \
 		$(LIBC_DUALLIB_TEST:%=%-test.o)
diff --git a/platforms/astbmc/astbmc.h b/platforms/astbmc/astbmc.h
index cee475a..7e33f61 100644
--- a/platforms/astbmc/astbmc.h
+++ b/platforms/astbmc/astbmc.h
@@ -24,5 +24,6 @@ extern int64_t astbmc_ipmi_power_down(uint64_t request);
 extern void astbmc_init(void);
 extern void astbmc_ext_irq(unsigned int chip_id);
 extern int pnor_init(void);
+extern bool pnor_load_resource(enum resource_id id, void *buf, size_t *len);
 
 #endif /* __ASTBMC_H */
diff --git a/platforms/astbmc/habanero.c b/platforms/astbmc/habanero.c
index d442d1f..a19aafd 100644
--- a/platforms/astbmc/habanero.c
+++ b/platforms/astbmc/habanero.c
@@ -49,4 +49,5 @@ DECLARE_PLATFORM(habanero) = {
 	.external_irq		= astbmc_ext_irq,
 	.cec_power_down         = astbmc_ipmi_power_down,
 	.cec_reboot             = astbmc_ipmi_reboot,
+	.load_resource		= pnor_load_resource,
 };
diff --git a/platforms/astbmc/palmetto.c b/platforms/astbmc/palmetto.c
index a0030e8..cfa7236 100644
--- a/platforms/astbmc/palmetto.c
+++ b/platforms/astbmc/palmetto.c
@@ -51,4 +51,5 @@ DECLARE_PLATFORM(palmetto) = {
 	.cec_power_down         = astbmc_ipmi_power_down,
 	.cec_reboot             = astbmc_ipmi_reboot,
 	.elog_commit		= ipmi_elog_commit,
+	.load_resource		= pnor_load_resource,
 };
diff --git a/platforms/astbmc/pnor.c b/platforms/astbmc/pnor.c
index f6e7a5d..2cdb29b 100644
--- a/platforms/astbmc/pnor.c
+++ b/platforms/astbmc/pnor.c
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-
 #include <skiboot.h>
 #include <device.h>
 #include <console.h>
@@ -85,3 +84,58 @@ int pnor_init(void)
 	return rc;
 }
 
+static const struct {
+	enum resource_id id;
+	char name[PART_NAME_MAX+1];
+} part_name_map[] = {
+	{ RESOURCE_ID_KERNEL, "KERNEL" },
+	{ RESOURCE_ID_INITRAMFS, "ROOTFS" },
+};
+
+bool pnor_load_resource(enum resource_id id, void *buf, size_t *len)
+{
+	int i, rc, part_num, part_size, part_start;
+	const char *name;
+
+	if (!pnor_ffs || !pnor_chip)
+		return false;
+
+	for (i = 0, name = NULL; i < ARRAY_SIZE(part_name_map); i++) {
+		if (part_name_map[i].id == id) {
+			name = part_name_map[i].name;
+			break;
+		}
+	}
+	if (!name) {
+		prerror("PLAT: Couldn't find partition for id %d\n", id);
+		return false;
+	}
+
+	rc = ffs_lookup_part(pnor_ffs, name, &part_num);
+	if (rc) {
+		prerror("PLAT: No %s partition in PNOR\n", name);
+		return false;
+	}
+	rc = ffs_part_info(pnor_ffs, part_num, NULL,
+			   &part_start, &part_size, NULL);
+	if (rc) {
+		prerror("PLAT: Failed to get %s partition info\n", name);
+		return false;
+	}
+
+	if (part_size > *len) {
+		prerror("PLAT: %s image too large (%d > %zd)\n", name,
+			part_size, *len);
+		return false;
+	}
+
+	rc = flash_read(pnor_chip, part_start, buf, part_size);
+	if (rc) {
+		prerror("PLAT: failed to read %s partition\n", name);
+		return false;
+	}
+
+	*len = part_size;
+
+	return true;
+}
