[Skiboot] [PATCH 13/13] external/opal-prd: Add userspace support for PRD facility

Jeremy Kerr jk at ozlabs.org
Fri Feb 27 20:11:06 AEDT 2015


This change adds an application in external/opal-prd, implementing the
userspace portion of a PRD stack.

This code is responsible for loading the HBRT code from reserved memory,
and provding hostboot runtime functionality through a set of
callbacks.

Because we may be running little-endian (and expect the HBRT code to be
big-endian), we need to thunk the endianness between calls through the
HBRT interface.

Includes multiple contributions from:

  Joel Stanley <joel at jms.id.au>
  Vaidyanathan Srinivasan <svaidy at linux.vnet.ibm.com>
  Benjamin Herrenschmidt <benh at kernel.crashing.org

Signed-off-by: Jeremy Kerr <jk at ozlabs.org>
Signed-off-by: Vaidyanathan Srinivasan <svaidy at linux.vnet.ibm.com>
Signed-off-by: Joel Stanley <joel at jms.id.au>
Signed-off-by: Benjamin Herrenschmidt <benh at kernel.crashing.org>

---
 external/opal-prd/.gitignore           |    4 
 external/opal-prd/Makefile             |   56 +
 external/opal-prd/config.h             |   19 
 external/opal-prd/hostboot-interface.h |  426 ++++++++
 external/opal-prd/i2c.c                |  262 +++++
 external/opal-prd/i2c.h                |   14 
 external/opal-prd/opal-prd.c           | 1206 +++++++++++++++++++++++++
 external/opal-prd/pnor.c               |  301 ++++++
 external/opal-prd/pnor.h               |   25 
 external/opal-prd/test/test_pnor.c     |   49 +
 external/opal-prd/test/test_pnor_ops.c |  235 ++++
 external/opal-prd/thunk.S              |  178 +++
 12 files changed, 2775 insertions(+)

diff --git a/external/opal-prd/.gitignore b/external/opal-prd/.gitignore
new file mode 100644
index 0000000..5b6d97a
--- /dev/null
+++ b/external/opal-prd/.gitignore
@@ -0,0 +1,4 @@
+opal-prd
+/ccan
+/libflash
+/test/test_pnor
diff --git a/external/opal-prd/Makefile b/external/opal-prd/Makefile
new file mode 100644
index 0000000..7006431
--- /dev/null
+++ b/external/opal-prd/Makefile
@@ -0,0 +1,56 @@
+CC = $(CROSS_COMPILE)gcc
+
+CFLAGS = -m64 -Werror -Wall -g2 -ggdb
+LDFLAGS = -m64
+ASFLAGS = -m64
+CPPFLAGS = -I. -I../../include -I../../
+
+# Use make V=1 for a verbose build.
+ifndef V
+        Q_CC=	@echo '    CC ' $@;
+        Q_LINK=	@echo '  LINK ' $@;
+        Q_LN=   @echo '    LN ' $@;
+endif
+
+OBJS = opal-prd.o thunk.o pnor.o i2c.o libffs.o libflash.o ecc.o
+
+all: opal-prd
+
+LINKS = ccan
+
+ifdef KERNEL_DIR
+LINKS += asm/opal-prd.h
+endif
+
+ccan:
+	$(Q_LN)ln -sfr ../../ccan ./ccan
+
+asm/opal-prd.h:
+	$(Q_LN)ln -sfr $(KERNEL_DIR)/arch/powerpc/include/uapi/asm/opal-prd.h \
+			asm/opal-prd.h
+
+$(OBJS): $(LINKS)
+
+%.o: %.c
+	$(Q_CC)$(COMPILE.c) $< -o $@
+
+%.o: ../../libflash/%.c
+	$(Q_CC)$(COMPILE.c) $< -o $@
+
+%.o: %.S
+	$(Q_CC)$(COMPILE.S) $< -o $@
+
+opal-prd: $(OBJS)
+	$(Q_LINK)$(LINK.o) -o $@ $^
+
+test: test/test_pnor
+
+test/test_pnor: test/test_pnor.o pnor.o libflash/libflash.o libflash/libffs.o
+	$(Q_LINK)$(LINK.o) -o $@ $^
+
+clean:
+	$(RM) *.[odsa] opal-prd
+	$(RM) test/*.[odsa] test/test_pnor
+
+distclean: clean
+	$(RM) -f $(LINKS) asm
diff --git a/external/opal-prd/config.h b/external/opal-prd/config.h
new file mode 100644
index 0000000..a132a01
--- /dev/null
+++ b/external/opal-prd/config.h
@@ -0,0 +1,19 @@
+/* For CCAN */
+
+#include <endian.h>
+#include <byteswap.h>
+
+#define HAVE_TYPEOF			1
+#define HAVE_BUILTIN_TYPES_COMPATIBLE_P	1
+
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#define HAVE_BIG_ENDIAN         0
+#define HAVE_LITTLE_ENDIAN      1
+#else
+#define HAVE_BIG_ENDIAN         1
+#define HAVE_LITTLE_ENDIAN      0
+#endif
+
+#define HAVE_BYTESWAP_H 1
+#define HAVE_BSWAP_64	1
diff --git a/external/opal-prd/hostboot-interface.h b/external/opal-prd/hostboot-interface.h
new file mode 100644
index 0000000..1088178
--- /dev/null
+++ b/external/opal-prd/hostboot-interface.h
@@ -0,0 +1,426 @@
+/* Copyright 2013-2014 IBM Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 	http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <stdint.h>
+
+/* Hostboot runtime interface */
+/* Derived from src/include/runtime/interface.h in Hostboot */
+
+#define HOSTBOOT_RUNTIME_INTERFACE_VERSION 1
+
+/** Memory error types defined for memory_error() interface. */
+enum MemoryError_t
+{
+	/** Hardware has reported a solid memory CE that is
+	 * correctable, but continues to report errors on subsequent
+	 * reads. A second CE on that cache line will result in memory
+	 * UE. Therefore, it is advised to migrate off of the address
+	 * range as soon as possible. */
+	MEMORY_ERROR_CE = 0,
+
+	/** Hardware has reported an uncorrectable error in memory
+	 * (memory UE, channel failure, etc). The hypervisor should
+	 * migrate any partitions off this address range as soon as
+	 * possible. Note that these kind of errors will most likely
+	 * result in partition failures. It is advised that the
+	 * hypervisor waits some time for PRD to handle hardware
+	 * attentions so that the hypervisor will know all areas of
+	 * memory that are impacted by the failure. */
+	MEMORY_ERROR_UE = 1,
+};
+
+struct host_interfaces {
+	/** Interface version. */
+	uint64_t interface_version;
+
+	/** Put a string to the console. */
+	void (*puts)(const char*);
+	/** Critical failure in runtime execution. */
+	void (*assert)(void);
+
+	/** OPTIONAL. Hint to environment that the page may be executed. */
+	int (*set_page_execute)(void*);
+
+	/** malloc */
+	void *(*malloc)(size_t);
+	/** free */
+	void (*free)(void*);
+	/** realloc */
+	void *(*realloc)(void*, size_t);
+
+	/**
+	 * @brief Send a PEL to the FSP
+	 * @param[in] plid Platform Log identifier
+	 * @param[in] data size in bytes
+	 * @param[in] pointer to data
+	 * @return 0 on success else error code
+	 * @platform FSP
+	 */
+	int (*send_error_log)(uint32_t,uint32_t,void *);
+
+	/**
+	 * @brief Scan communication read
+	 * @param[in] chip_id (based on devtree defn)
+	 * @param[in] address
+	 * @param[in] pointer to 8-byte data buffer
+	 * @return 0 on success else return code
+	 * @platform FSP,OpenPOWER
+	 */
+	int (*scom_read)(uint64_t, uint64_t, void*);
+
+	/**
+	 * @brief Scan communication write
+	 * @param[in] chip_id (based on devtree defn)
+	 * @param[in] address
+	 * @param[in] pointer to 8-byte data buffer
+	 * @return 0 on success else return code
+	 * @platform FSP,OpenPOWER
+	 */
+	int (*scom_write)(uint64_t, uint64_t, const void *);
+
+	/**
+	 *  @brief Load a LID from PNOR, FSP, etc.
+	 *
+	 *  @param[in] LID number.
+	 *  @param[out] Allocated buffer for LID.
+	 *  @param[out] Size of LID (in bytes).
+	 *
+	 *  @return 0 on success, else RC.
+	 *  @platform FSP
+	 */
+	int (*lid_load)(uint32_t lid, void **buf, size_t *len);
+
+	/**
+	 *  @brief Release memory from previously loaded LID.
+	 *
+	 *  @param[in] Allocated buffer for LID to release.
+	 *
+	 *  @return 0 on success, else RC.
+	 *  @platform FSP
+	 */
+	int (*lid_unload)(void *buf);
+
+	/**
+	 *  @brief Get the address of a reserved memory region by its devtree
+	 *  name.
+	 *
+	 *  @param[in] Devtree name (ex. "ibm,hbrt-vpd-image")
+	 *  @return physical address of region (or NULL).
+	 *  @platform FSP,OpenPOWER
+	 */
+	uint64_t (*get_reserved_mem)(const char*);
+
+	/**
+	 * @brief  Force a core to be awake, or clear the force
+	 * @param[in] i_core  Core to wake up (pid)
+	 * @param[in] i_mode  0=force awake
+	 *				1=clear force
+	 *				2=clear all previous forces
+	 * @return rc  non-zero on error
+	 * @platform FSP
+	 */
+	int (*wakeup)( uint32_t i_core, uint32_t i_mode );
+
+	/**
+	 * @brief Delay/sleep for at least the time given
+	 * @param[in] seconds
+	 * @param[in] nano seconds
+	 * @platform FSP,OpenPOWER
+	 */
+	void (*nanosleep)(uint64_t i_seconds, uint64_t i_nano_seconds);
+
+	/**
+	 * @brief Report an OCC error to the host
+	 * @param[in] Failing status that identifies the nature of the fail
+	 * @param[in] Identifier that specifies the failing part
+	 * @platform FSP
+	 */
+	void (*report_occ_failure)( uint64_t i_status, uint64_t i_partId );
+
+	/**
+	 *  @brief Reads the clock value from a POSIX clock.
+	 *  @param[in]  i_clkId - The clock ID to read.
+	 *  @param[out] o_tp - The timespec struct to store the clock value in.
+	 *
+	 *  @return 0 or -(errno).
+	 *  @retval 0 - SUCCESS.
+	 *  @retval -EINVAL - Invalid clock requested.
+	 *  @retval -EFAULT - NULL ptr given for timespec struct.
+	 *
+	 * @platform OpenPOWER
+	 */
+	int (*clock_gettime)( clockid_t i_clkId, struct timespec* o_tp );
+
+	/**
+	 * @brief Read Pnor
+	 * @param[in] i_proc: processor Id
+	 * @param[in] i_partitionName: name of the partition to read
+	 * @param[in] i_offset: offset within the partition
+	 * @param[out] o_data: pointer to the data read
+	 * @param[in] i_sizeBytes: size of data to read
+	 * @retval rc - non-zero on error
+	 * @platform OpenPOWER
+	 */
+	int (*pnor_read) ( uint32_t i_proc, const char* i_partitionName,
+			uint64_t i_offset, void* o_data, size_t i_sizeBytes );
+
+	/**
+	 * @brief Write to Pnor
+	 * @param[in] i_proc: processor Id
+	 * @param[in] i_partitionName: name of the partition to write
+	 * @param[in] i_offset: offset withing the partition
+	 * @param[in] i_data: pointer to the data to write
+	 * @param[in] i_sizeBytes: size of data to write
+	 * @retval rc - non-zero on error
+	 * @platform OpenPOWER
+	 */
+	int (*pnor_write) ( uint32_t i_proc, const char* i_partitionName,
+			uint64_t i_offset, void* i_data, size_t i_sizeBytes );
+
+
+	/**
+	 * i2c master description: chip, engine and port packed into
+	 * a single 64-bit argument
+	 *
+	 * ---------------------------------------------------
+	 * |         chip         |  reserved  |  eng | port |
+         * |         (32)         |    (16)    |  (8) | (8)  |
+	 * ---------------------------------------------------
+	 */
+#define HBRT_I2C_MASTER_CHIP_SHIFT	32
+#define HBRT_I2C_MASTER_CHIP_MASK	(0xfffffffful << 32)
+#define HBRT_I2C_MASTER_ENGINE_SHIFT	8
+#define HBRT_I2C_MASTER_ENGINE_MASK	(0xfful << 8)
+#define HBRT_I2C_MASTER_PORT_SHIFT	0
+#define HBRT_I2C_MASTER_PORT_MASK	(0xfful)
+
+	/**
+	 * @brief Read data from an i2c device
+	 * @param[in] i_master - Chip/engine/port of i2c bus
+	 * @param[in] i_devAddr - I2C address of device
+	 * @param[in] i_offsetSize - Length of offset (in bytes)
+	 * @param[in] i_offset - Offset within device to read
+	 * @param[in] i_length - Number of bytes to read
+	 * @param[out] o_data - Data that was read
+	 * @return 0 on success else return code
+	 * @platform OpenPOWER
+	 */
+	int (*i2c_read)( uint64_t i_master, uint16_t i_devAddr,
+			 uint32_t i_offsetSize, uint32_t i_offset,
+			 uint32_t i_length, void* o_data );
+
+	/**
+	 * @brief Write data to an i2c device
+	 * @param[in] i_master - Chip/engine/port of i2c bus
+	 * @param[in] i_devAddr - I2C address of device
+	 * @param[in] i_offsetSize - Length of offset (in bytes)
+	 * @param[in] i_offset - Offset within device to write
+	 * @param[in] i_length - Number of bytes to write
+	 * @param[in] Data to write
+	 * @return 0 on success else return code
+	 * @platform OpenPOWER
+	 */
+	int (*i2c_write)( uint64_t i_master, uint16_t i_devAddr,
+			  uint32_t i_offsetSize, uint32_t i_offset,
+			  uint32_t i_length, void* i_data );
+
+	/**
+	 * Perform an IPMI transaction
+	 * @param[in] netfn The IPMI netfn byte
+	 * @param[in] cmd The IPMI cmd byte
+	 * @param[in] tx_buf The IPMI packet to send to the host
+	 * @param[in] tx_size The number of bytes, to send
+	 * @param[in] rx_buf A buffer to be populated with the IPMI
+	 *		response.
+	 * @param[inout] rx_size The allocated size of the rx buffer on
+	 *		input, updated to the size of the response on output.
+	 *		This should always begin with the IPMI completion
+	 *		code.
+	 */
+	int (*ipmi_msg)(uint8_t netfn, uint8_t cmd,
+			void *tx_buf, size_t tx_size,
+			void *rx_buf, size_t *rx_size);
+
+
+	/**
+	 * @brief Hardware has reported a memory error. This function requests
+	 * the hypervisor to remove the all addresses within the address range
+	 * given (including endpoints) from the available memory space.
+	 *
+	 * It is understood that the hypervisor may not be able to immediately
+	 * deallocate the memory because it may be in use by a partition.
+	 * Therefore, the hypervisor should cache all requests and deallocate
+	 * the memory once it has been freed.
+	 *
+	 * @param  i_startAddr The beginning address of the range.
+	 * @param  i_endAddr   The end address of the range.
+	 * @param  i_errorType See enum MemoryError_t.
+	 *
+	 * @return 0 if the request is successfully received. Any value other
+	 *	than 0 on failure. The hypervisor should cache the request and
+	 *	return immediately. It should not wait for the request to be
+	 *	applied. See note above.
+	 */
+	int (*memory_error)( uint64_t i_startAddr, uint64_t i_endAddr,
+					  enum MemoryError_t i_errorType );
+
+
+};
+
+struct runtime_interfaces {
+	/** Interface version. */
+	uint64_t interface_version;
+
+	/**
+	 * @brief Execute CxxTests that may be contained in the image.
+	 *
+	 * @param[in] - Pointer to CxxTestStats structure for results reporting.
+	 */
+	void (*cxxtestExecute)(void *);
+
+	/**
+	 * @brief Get a list of lids numbers of the lids known to HostBoot
+	 *
+	 * @param[out] o_num - the number of lids in the list
+	 * @return a pointer to the list
+	 * @platform FSP
+	 */
+	const uint32_t * (*get_lid_list)(size_t * o_num);
+
+	/**
+	 * @brief Load OCC Image and common data into mainstore, also setup OCC
+	 * BARSs
+	 *
+	 * @param[in] i_homer_addr_phys - The physical mainstore address of the
+	 *	start of the HOMER image
+	 * @param[in] i_homer_addr_va - Virtual memory address of the HOMER
+	 *	image
+	 * @param[in] i_common_addr_phys - The physical mainstore address
+	 *	of the OCC common area.
+	 * @param[in] i_common_addr_va - Virtual memory address of the common
+	 *	area
+	 * @param[in] i_chip - The HW chip id (XSCOM chip ID)
+	 * @return 0 on success else return code
+	 * @platform FSP
+	 */
+	int (*occ_load)(uint64_t i_homer_addr_phys,
+			 uint64_t i_homer_addr_va,
+			 uint64_t i_common_addr_phys,
+			 uint64_t i_common_addr_va,
+			 uint64_t i_chip);
+
+	/**
+	 * @brief Start OCC on all chips, by module
+	 *
+	 *  @param[in] i_chip - Array of functional HW chip ids
+	 *  @Note The caller must include a complete modules worth of chips
+	 *  @param[in] i_num_chips - Number of chips in the array
+	 *  @return 0 on success else return code
+	 *  @platform FSP
+	 */
+	int (*occ_start)(uint64_t* i_chip, size_t i_num_chips);
+
+	/**
+	 * @brief Stop OCC hold OCCs in reset
+	 *
+	 *  @param[in] i_chip - Array of functional HW chip ids
+	 *  @Note The caller must include a complete modules worth of chips
+	 *  @param[in] i_num_chips - Number of chips in the array
+	 *  @return 0 on success else return code
+	 *  @platform FSP
+	 */
+	int (*occ_stop)(uint64_t* i_chip, size_t i_num_chips);
+
+	/**
+	 * @brief Notify HTMGT that an OCC has an error to report
+	 *
+	 * @details  When an OCC has encountered an error that it wants to
+	 *		   be reported, this interface will be called to trigger
+	 *		   HTMGT to collect and commit the error.
+	 *
+	 * @param[i] i_chipId - Id of processor with failing OCC
+	 * @platform OpenPower
+	 */
+	void (*process_occ_error) (uint64_t i_chipId);
+
+	/**
+	 *  @brief Enable chip attentions
+	 *
+	 *  @return 0 on success else return code
+	 *  @platform OpenPower
+	 */
+	int (*enable_attns)(void);
+
+	/**
+	 *  @brief Disable chip attentions
+	 *
+	 *  @return 0 on success else return code
+	 *  @platform OpenPower
+	 */
+	int (*disable_attns)(void);
+
+	/**
+	 *  @brief handle chip attentions
+	 *
+	 *  @param[in] i_proc - processor chip id at attention XSCOM chip id
+	 *	based on devtree defn
+	 *  @param[in] i_ipollStatus - processor chip Ipoll status
+	 *  @param[in] i_ipollMask   - processor chip Ipoll mask
+	 *  @return 0 on success else return code
+	 *  @platform OpenPower
+	 */
+	int (*handle_attns)(uint64_t i_proc, uint64_t i_ipollStatus,
+			uint64_t i_ipollMask);
+
+	/**
+	 * @brief Notify HTMGT that an OCC has failed and needs to be reset
+	 *
+	 * @details  When BMC detects an OCC failure that requires a reset,
+	 * this interface will be called to trigger the OCC reset.  HTMGT
+	 * maintains a reset count and if there are additional resets
+	 * available, the OCCs get reset/reloaded.  If the recovery attempts
+	 * have been exhauseted or the OCC fails to go active, an unrecoverable
+	 * error will be logged and the system will remain in safe mode.
+	 *
+	 * @param[in]  i_chipId  ChipID which identifies the OCC reporting an
+	 *	error
+	 * @platform OpenPOWER
+	 */
+	void (*process_occ_reset)(uint64_t  i_chipId);
+
+	/**
+	 * @brief Change the OCC state
+	 *
+	 * @details  This is a blocking call that will change the OCC state.
+	 * The OCCs will only actuate (update processor frequency/ voltages)
+	 * when in Active state.  The OCC will only be monitoring/observing
+	 * when in Observation state.
+	 *
+	 * @note When the OCCs are initially started, the state will
+	 * default to Active.  If the state is changed to Observation, that
+	 * state will be retained until the next IPL. (If the OCC would get
+	 * reset, it would return to the last requested state)
+	 *
+	 * @param[in]  i_occActivation  set to true to move OCC to Active state
+	 *	or false to move OCC to Observation state
+	 *
+	 * @return  0 on success, or return code if the state did not change.
+	 * @platform OpenPower
+	 */
+	int (*enable_occ_actuation)(bool i_occActivation);
+
+	/* Reserve some space for future growth. */
+	void (*reserved[32])(void);
+};
diff --git a/external/opal-prd/i2c.c b/external/opal-prd/i2c.c
new file mode 100644
index 0000000..436ae04
--- /dev/null
+++ b/external/opal-prd/i2c.c
@@ -0,0 +1,262 @@
+/* Copyright 2013-2015 IBM Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 	http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _GNU_SOURCE         /* for aspritnf */
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <dirent.h>
+#include <sys/param.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <linux/i2c.h>
+#include <linux/i2c-dev.h>
+#include <ccan/list/list.h>
+
+#include "i2c.h"
+
+struct i2c_bus {
+	uint32_t		chip_id;
+	uint8_t			engine;
+	uint8_t			port;
+	const char		*devpath;
+	int			fd;
+	struct list_node	link;
+};
+
+static struct list_head bus_list = LIST_HEAD_INIT(bus_list);
+
+static int i2c_get_dev(uint32_t chip, uint8_t eng, uint8_t port, uint16_t dev)
+{
+	struct i2c_bus *b, *bus = NULL;
+
+	list_for_each(&bus_list, b, link) {
+		if (b->chip_id == chip && b->engine == eng && b->port == port) {
+			bus = b;
+			break;
+		}
+	}
+	if (!bus) {
+		printf("I2C: Bus %08x/%d/%d not found\n", chip, eng, port);
+		return -1;
+	}
+	if (bus->fd < 0) {
+		bus->fd = open(bus->devpath, O_RDWR);
+		if (bus->fd < 0) {
+			fprintf(stderr, "Failed to open %s: %s\n",
+				bus->devpath, strerror(errno));
+			return -1;
+		}
+	}
+
+	/* XXX We could use the I2C_SLAVE ioctl to check if the device
+	 * is currently in use by a kernel driver...
+	 */
+
+	return bus->fd;
+}
+
+int i2c_read(uint32_t chip_id, uint8_t engine, uint8_t port,
+	     uint16_t device, uint32_t offset_size, uint32_t offset,
+	     uint32_t length, void* data)
+{
+	struct i2c_rdwr_ioctl_data ioargs;
+	struct i2c_msg	msgs[2];
+	uint8_t obuf[4];
+	int fd, i, midx = 0;
+
+	if (offset_size > 4) {
+		fprintf(stderr,"I2C: Invalid offset_size %d\n", offset_size);
+		return -1;
+	}
+	fd = i2c_get_dev(chip_id, engine, port, device);
+	if (fd == -1)
+		return -1;
+
+	/* If we have an offset, build a message for it */
+	if (offset_size) {
+		/* The offset has a variable size so let's handle this properly
+		 * as it has to be laid out in memory MSB first
+		 */
+		for (i = 0; i < offset_size; i++)
+			obuf[i] = offset >> (8 * (offset_size - i - 1));
+		msgs[0].addr = device;
+		msgs[0].flags = 0;
+		msgs[0].buf = obuf;
+		msgs[0].len = offset_size;
+		midx = 1;
+	}
+
+	/* Build the message for the data portion */
+	msgs[midx].addr = device;
+	msgs[midx].flags = I2C_M_RD;
+	msgs[midx].buf = data;
+	msgs[midx].len = length;
+	midx++;
+
+	ioargs.msgs = msgs;
+	ioargs.nmsgs = midx;
+	if (ioctl(fd, I2C_RDWR, &ioargs) < 0) {
+		fprintf(stderr, "I2C: Read error: %s\n", strerror(errno));
+		return -1;
+	}
+	printf("I2C: Read from %08x:%d:%d@%02x+0x%x %d bytes ok\n",
+	       chip_id, engine, port, device, offset_size ? offset : 0, length);
+
+	return 0;
+}
+
+int i2c_write(uint32_t chip_id, uint8_t engine, uint8_t port,
+	      uint16_t device, uint32_t offset_size, uint32_t offset,
+	      uint32_t length, void* data)
+{
+	struct i2c_rdwr_ioctl_data ioargs;
+	struct i2c_msg msg;
+	int fd, size, i, rc;
+	uint8_t *buf;
+
+	if (offset_size > 4) {
+		fprintf(stderr,"I2C: Invalid offset_size %d\n", offset_size);
+		return -1;
+	}
+	fd = i2c_get_dev(chip_id, engine, port, device);
+	if (fd == -1)
+		return -1;
+
+	/* Not all kernel driver versions support breaking up a write into
+	 * two components (offset, data), so we coalesce them first and
+	 * issue a single write. The offset is layed out in BE format.
+	 */
+	size = offset_size + length;
+	buf = malloc(size);
+	if (!buf) {
+		fprintf(stderr, "I2C: Out of memory !\n");
+		return -1;
+	}
+
+	/* The offset has a variable size so let's handle this properly
+	 * as it has to be laid out in memory MSB first
+	 */
+	for (i = 0; i < offset_size; i++)
+		buf[i] = offset >> (8 * (offset_size - i - 1));
+
+	/* Copy the remaining data */
+	memcpy(buf + offset_size, data, length);
+
+	/* Build the message */
+	msg.addr = device;
+	msg.flags = 0;
+	msg.buf = buf;
+	msg.len = size;
+	ioargs.msgs = &msg;
+	ioargs.nmsgs = 1;
+	rc = ioctl(fd, I2C_RDWR, &ioargs);
+	free(buf);
+	if (rc < 0) {
+		fprintf(stderr, "I2C: Write error: %s\n", strerror(errno));
+		return -1;
+	}
+
+	return 0;
+}
+
+static void i2c_add_bus(uint32_t chip, uint32_t engine, uint32_t port,
+			const char *devname)
+{
+	struct i2c_bus *b = malloc(sizeof(struct i2c_bus));
+	char *dn;
+
+	if (asprintf(&dn, "/dev/%s", devname) < 0) {
+		fprintf(stderr, "Error creating devpath for %s: %s\n",
+			devname, strerror(errno));
+		return;
+	}
+
+	memset(b, 0, sizeof(*b));
+	b->chip_id = chip;
+	b->engine = engine;
+	b->port = port;
+	b->devpath = dn;
+	b->fd = -1;
+	list_add(&bus_list, &b->link);
+}
+
+void i2c_init(void)
+{
+#define SYSFS	"/sys"	/* XXX Find it ? */
+	DIR *devsdir;
+	struct dirent *devent;
+	char dpath[NAME_MAX];
+	char busname[256];
+	char *s;
+	FILE *f;
+	unsigned int chip, engine, port;
+
+	/* Ensure i2c-dev is loaded (must be root ! might need to
+	 * move that to some helper script or something ...)
+	 */
+	system("modprobe -a i2c-dev i2c-opal");
+
+	/* Get directory of i2c char devs in sysfs */
+	devsdir = opendir(SYSFS "/class/i2c-dev");
+	if (!devsdir) {
+		fprintf(stderr, "Error opening " SYSFS "/class/i2c-dev: %s\n",
+			strerror(errno));
+		return;
+	}
+	while ((devent = readdir(devsdir)) != NULL) {
+		if (!strcmp(devent->d_name, "."))
+			continue;
+		if (!strcmp(devent->d_name, ".."))
+			continue;
+
+		/* Get bus name */
+		sprintf(dpath, SYSFS "/class/i2c-dev/%s/name", devent->d_name);
+		f = fopen(dpath, "r");
+		if (!f) {
+			fprintf(stderr, "Can't open %s: %s, skipping...\n",
+				dpath, strerror(errno));
+			continue;
+		}
+		s = fgets(busname, sizeof(busname), f);
+		fclose(f);
+		if (!s) {
+			fprintf(stderr, "Failed to read %s, skipping...\n",
+				dpath);
+			continue;
+		}
+
+		/* Is this a P8 or Centaur i2c bus ? No -> move on */
+		if (strncmp(s, "p8_", 3) == 0)
+			sscanf(s, "p8_%x_e%dp%d", &chip, &engine, &port);
+		else if (strncmp(s, "cen_", 4) == 0)
+			sscanf(s, "cen_%x_e%dp%d", &chip, &engine, &port);
+		else
+			continue;
+
+		printf("I2C: Found Chip: %08x engine %d port %d\n",
+		       chip, engine, port);
+		i2c_add_bus(chip, engine, port, devent->d_name);
+	}
+	closedir(devsdir);
+}
+
diff --git a/external/opal-prd/i2c.h b/external/opal-prd/i2c.h
new file mode 100644
index 0000000..d31bc0e
--- /dev/null
+++ b/external/opal-prd/i2c.h
@@ -0,0 +1,14 @@
+#ifndef __I2C_H
+#define __I2C_H
+
+int i2c_read(uint32_t chip_id, uint8_t engine, uint8_t port,
+	     uint16_t device, uint32_t offset_size, uint32_t offset,
+	     uint32_t length, void* data);
+
+int i2c_write(uint32_t chip_id, uint8_t engine, uint8_t port,
+	      uint16_t device, uint32_t offset_size, uint32_t offset,
+	      uint32_t length, void* data);
+
+void i2c_init(void);
+
+#endif /* __I2c_H */
diff --git a/external/opal-prd/opal-prd.c b/external/opal-prd/opal-prd.c
new file mode 100644
index 0000000..402820a
--- /dev/null
+++ b/external/opal-prd/opal-prd.c
@@ -0,0 +1,1206 @@
+/* Copyright 2014-2015 IBM Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *	http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied.
+ * See the License for the specific language governing permissions and
+ * imitations under the License.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <time.h>
+#include <err.h>
+#include <poll.h>
+
+#include <endian.h>
+
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <linux/ipmi.h>
+#include <linux/limits.h>
+
+#include <asm/opal-prd.h>
+#include <opal.h>
+
+#include "hostboot-interface.h"
+#include "pnor.h"
+#include "i2c.h"
+
+struct opal_prd_ctx {
+	int			fd;
+	int			socket;
+	struct opal_prd_info	info;
+	long			page_size;
+	void			*code_addr;
+	size_t			code_size;
+	bool			debug;
+	bool			allow_fsp_calls;
+	struct pnor		pnor;
+	char			*hbrt_file_name;
+};
+
+enum control_msg_type {
+	CONTROL_MSG_ENABLE_OCCS		= 0x00,
+	CONTROL_MSG_DISABLE_OCCS	= 0x01,
+	CONTROL_MSG_TEMP_OCC_RESET	= 0x02,
+	CONTROL_MSG_TEMP_OCC_ERROR	= 0x03,
+};
+
+struct control_msg {
+	enum control_msg_type	type;
+	uint64_t		response;
+};
+
+static struct opal_prd_ctx *ctx;
+
+static const char *opal_prd_devnode = "/dev/opal-prd";
+static const char *opal_prd_socket = "/run/opal-prd-control";
+static const char *hbrt_code_region_name = "ibm,hbrt-code-image";
+static const int opal_prd_version = 1;
+static const uint64_t opal_prd_ipoll = 0xf000000000000000;
+
+static const char *ipmi_devnode = "/dev/ipmi0";
+static const int ipmi_timeout_ms = 2000;
+
+/* Memory error handling */
+static const char *mem_offline_soft =
+		"/sys/devices/system/memory/soft_offline_page";
+static const char *mem_offline_hard =
+		"/sys/devices/system/memory/hard_offline_page";
+
+#define ADDR_STRING_SZ 20 /* Hold %16lx */
+
+/* This is the "real" HBRT call table for calling into HBRT as
+ * provided by it. It will be used by the assembly thunk
+ */
+struct runtime_interfaces *hservice_runtime;
+struct runtime_interfaces hservice_runtime_fixed;
+
+/* This is the callback table provided by assembly code */
+extern struct host_interfaces hinterface;
+
+/* Create opd to call hostservice init */
+struct func_desc {
+	void *addr;
+	void *toc;
+} hbrt_entry;
+
+static struct opal_prd_range *find_range(const char *name)
+{
+	struct opal_prd_range *range;
+	unsigned int i;
+
+	for (i = 0; i < OPAL_PRD_MAX_RANGES; i++) {
+		range = &ctx->info.ranges[i];
+
+		if (!strncmp(range->name, name, sizeof(range->name)))
+			return range;
+	}
+
+	return NULL;
+}
+
+static void pr_debug(struct opal_prd_ctx *ctx, const char *fmt, ...)
+{
+	va_list ap;
+
+	if (!ctx->debug)
+		return;
+
+	va_start(ap, fmt);
+	vfprintf(stderr, fmt, ap);
+	va_end(ap);
+}
+
+/* HBRT init wrappers */
+extern struct runtime_interfaces *call_hbrt_init(struct host_interfaces *);
+
+/* hservice Call wrappers */
+
+extern void call_cxxtestExecute(void *);
+extern int call_handle_attns(uint64_t i_proc,
+			uint64_t i_ipollStatus,
+			uint64_t i_ipollMask);
+extern void call_process_occ_error (uint64_t i_chipId);
+extern int call_enable_attns(void);
+extern int call_enable_occ_actuation(bool i_occActivation);
+extern void call_process_occ_reset(uint64_t i_chipId);
+
+/* Dummy calls for hservices */
+static inline int __fsp_only_assert(const char *name)
+{
+	printf("error: %s is only implemented for FSP\n", name);
+	if (!ctx->allow_fsp_calls)
+		exit(EXIT_FAILURE);
+	return 0;
+}
+#define fsp_stub(name) \
+	int hservice_ ##name(void) { return __fsp_only_assert(#name); }
+
+fsp_stub(send_error_log);
+fsp_stub(lid_load);
+fsp_stub(lid_unload);
+fsp_stub(wakeup);
+fsp_stub(report_occ_failure);
+
+void hservice_puts(const char *str)
+{
+	printf("%s\n", str);
+}
+
+void hservice_assert(void)
+{
+	fprintf(stderr, "ERR: assert! exiting.\n");
+	exit(EXIT_FAILURE);
+}
+
+void *hservice_malloc(size_t size)
+{
+	return malloc(size);
+}
+
+void hservice_free(void *ptr)
+{
+	free(ptr);
+}
+
+void *hservice_realloc(void *ptr, size_t size)
+{
+	return realloc(ptr, size);
+}
+
+int hservice_scom_read(uint64_t chip_id, uint64_t addr, void *buf)
+{
+	int rc;
+	struct opal_prd_scom scom;
+
+	scom.chip = chip_id;
+	scom.addr = addr;
+
+	rc = ioctl(ctx->fd, OPAL_PRD_SCOM_READ, &scom);
+	if (rc) {
+		perror("ioctl scom_read");
+		return 0;
+	}
+
+	pr_debug(ctx, "scom read: chip %lx addr %lx val %lx\n",
+			chip_id, addr, scom.data);
+
+	*(uint64_t *)buf = htobe64(scom.data);
+
+	return 0;
+}
+
+int hservice_scom_write(uint64_t chip_id, uint64_t addr,
+                               const void *buf)
+{
+	int rc;
+	struct opal_prd_scom scom;
+
+	scom.chip = chip_id;
+	scom.addr = addr;
+	scom.data = be64toh(*(uint64_t *)buf);
+
+	rc = ioctl(ctx->fd, OPAL_PRD_SCOM_WRITE, &scom);
+	if (rc) {
+		perror("ioctl scom_write");
+		return 0;
+	}
+
+	pr_debug(ctx, "scom write: chip %lx addr %lx val %lx\n",
+			chip_id, addr, scom.data);
+
+	return 0;
+}
+
+uint64_t hservice_get_reserved_mem(const char *name)
+{
+	uint64_t align_physaddr, offset;
+	struct opal_prd_range *range;
+	void *addr;
+
+	pr_debug(ctx, "hservice_get_reserved_mem: %s\n", name);
+
+	range = find_range(name);
+	if (!range) {
+		warnx("get_reserved_mem: no such range %s", name);
+		return 0;
+	}
+
+	pr_debug(ctx, "Mapping 0x%016lx 0x%08lx %s\n",
+			range->physaddr, range->size, range->name);
+
+	align_physaddr = range->physaddr & ~(ctx->page_size-1);
+	offset = range->physaddr & (ctx->page_size-1);
+	addr = mmap(NULL, range->size, PROT_WRITE | PROT_READ,
+				MAP_SHARED, ctx->fd, align_physaddr);
+
+	if (addr == MAP_FAILED) {
+		perror("mmap");
+		return 0;
+	}
+
+	pr_debug(ctx, "hservice_get_reserved_mem: %s address %p\n", name, addr);
+	if (addr)
+		return (uint64_t)addr + offset;
+
+	return 0;
+}
+
+void hservice_nanosleep(uint64_t i_seconds, uint64_t i_nano_seconds)
+{
+	const struct timespec ns = {
+		.tv_sec = i_seconds,
+		.tv_nsec = i_nano_seconds
+	};
+
+	nanosleep(&ns, NULL);
+}
+
+int hservice_set_page_execute(void *addr)
+{
+	pr_debug(ctx, "FIXME: hservice_set_page_execute(%p)\n", addr);
+	return -1;
+}
+
+int hservice_clock_gettime(clockid_t i_clkId, struct timespec *o_tp)
+{
+	struct timespec tmp;
+	int rc;
+
+	rc = clock_gettime(i_clkId, &tmp);
+	if (rc)
+		return rc;
+
+	o_tp->tv_sec = htobe64(tmp.tv_sec);
+	o_tp->tv_nsec = htobe64(tmp.tv_nsec);
+
+	return 0;
+}
+
+int hservice_pnor_read(uint32_t i_proc, const char* i_partitionName,
+		uint64_t i_offset, void* o_data, size_t i_sizeBytes)
+{
+	return pnor_operation(&ctx->pnor, i_partitionName, i_offset, o_data,
+			      i_sizeBytes, PNOR_OP_READ);
+}
+
+int hservice_pnor_write(uint32_t i_proc, const char* i_partitionName,
+		uint64_t i_offset, void* o_data, size_t i_sizeBytes)
+{
+	return pnor_operation(&ctx->pnor, i_partitionName, i_offset, o_data,
+			      i_sizeBytes, PNOR_OP_WRITE);
+}
+
+int hservice_i2c_read(uint64_t i_master, uint8_t i_engine, uint8_t i_port,
+		uint16_t i_devAddr, uint32_t i_offsetSize, uint32_t i_offset,
+		uint32_t i_length, void* o_data)
+{
+	uint32_t chip_id;
+	uint8_t engine, port;
+
+	chip_id = (i_master & HBRT_I2C_MASTER_CHIP_MASK) >>
+		HBRT_I2C_MASTER_CHIP_SHIFT;
+	engine = (i_master & HBRT_I2C_MASTER_ENGINE_MASK) >>
+		HBRT_I2C_MASTER_ENGINE_SHIFT;
+	port = (i_master & HBRT_I2C_MASTER_PORT_MASK) >>
+		HBRT_I2C_MASTER_PORT_SHIFT;
+	return i2c_read(chip_id, engine, port, i_devAddr, i_offsetSize,
+			i_offset, i_length, o_data);
+}
+
+int hservice_i2c_write(uint64_t i_master, uint8_t i_engine, uint8_t i_port,
+		uint16_t i_devAddr, uint32_t i_offsetSize, uint32_t i_offset,
+		uint32_t i_length, void* i_data)
+{
+	uint32_t chip_id;
+	uint8_t engine, port;
+
+	chip_id = (i_master & HBRT_I2C_MASTER_CHIP_MASK) >>
+		HBRT_I2C_MASTER_CHIP_SHIFT;
+	engine = (i_master & HBRT_I2C_MASTER_ENGINE_MASK) >>
+		HBRT_I2C_MASTER_ENGINE_SHIFT;
+	port = (i_master & HBRT_I2C_MASTER_PORT_MASK) >>
+		HBRT_I2C_MASTER_PORT_SHIFT;
+	return i2c_write(chip_id, engine, port, i_devAddr, i_offsetSize,
+			 i_offset, i_length, i_data);
+}
+
+static int ipmi_send(int fd, uint8_t netfn, uint8_t cmd, long seq,
+		uint8_t *buf, size_t len)
+{
+	struct ipmi_system_interface_addr addr;
+	struct ipmi_req req;
+	int rc;
+
+	memset(&addr, 0, sizeof(addr));
+	addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+	addr.channel = IPMI_BMC_CHANNEL;
+
+	memset(&req, 0, sizeof(req));
+	req.addr = (unsigned char *)&addr;
+	req.addr_len = sizeof(addr);
+
+	req.msgid = seq;
+	req.msg.netfn = netfn;
+	req.msg.cmd = cmd;
+	req.msg.data = buf;
+	req.msg.data_len = len;
+
+	rc = ioctl(fd, IPMICTL_SEND_COMMAND, &req);
+	if (rc < 0)
+		return -1;
+
+	return 0;
+}
+
+static int ipmi_recv(int fd, uint8_t *netfn, uint8_t *cmd, long *seq,
+		uint8_t *buf, size_t *len)
+{
+	struct ipmi_recv recv;
+	struct ipmi_addr addr;
+	int rc;
+
+	recv.addr = (unsigned char *)&addr;
+	recv.addr_len = sizeof(addr);
+	recv.msg.data = buf;
+	recv.msg.data_len = *len;
+
+	rc = ioctl(fd, IPMICTL_RECEIVE_MSG_TRUNC, &recv);
+	if (rc < 0 && errno != EMSGSIZE) {
+		warn("IPMI: recv (%zd bytes) failed: %m\n", *len);
+		return -1;
+	} else if (rc < 0 && errno == EMSGSIZE) {
+		warn("IPMI: truncated message (netfn %d, cmd %d, "
+				"size %zd), continuing anyway\n",
+				recv.msg.netfn, recv.msg.cmd, *len);
+	}
+
+	*netfn = recv.msg.netfn;
+	*cmd = recv.msg.cmd;
+	*seq = recv.msgid;
+	*len = recv.msg.data_len;
+
+	return 0;
+}
+
+int hservice_ipmi_msg(uint8_t netfn, uint8_t cmd,
+		void *tx_buf, size_t tx_size,
+		void *rx_buf, size_t *rx_size)
+{
+	struct timeval start, now, delta;
+	struct pollfd pollfds[1];
+	static long seq;
+	size_t size;
+	int rc, fd;
+
+	size = be64toh(*rx_size);
+
+	fd = open(ipmi_devnode, O_RDWR);
+	if (fd < 0) {
+		warn("Failed to open IPMI device %s", ipmi_devnode);
+		return -1;
+	}
+
+	seq++;
+	pr_debug(ctx, "IPMI: sending %zd bytes (netfn 0x%02x, cmd 0x%02x)\n",
+			tx_size, netfn, cmd);
+
+	rc = ipmi_send(fd, netfn, cmd, seq, tx_buf, tx_size);
+	if (rc) {
+		warnx("IPMI: send failed");
+		goto out;
+	}
+
+	gettimeofday(&start, NULL);
+
+	pollfds[0].fd = fd;
+	pollfds[0].events = POLLIN;
+
+	for (;;) {
+		long rx_seq;
+		int timeout;
+
+		gettimeofday(&now, NULL);
+		timersub(&now, &start, &delta);
+		timeout = ipmi_timeout_ms - ((delta.tv_sec * 1000) +
+				(delta.tv_usec / 1000));
+		if (timeout < 0)
+			timeout = 0;
+
+		rc = poll(pollfds, 1, timeout);
+		if (rc < 0) {
+			warn("poll(%s)", ipmi_devnode);
+			break;
+		}
+
+		if (rc == 0) {
+			warnx("IPMI response timeout");
+			rc = -1;
+			break;
+		}
+
+		rc = ipmi_recv(fd, &netfn, &cmd, &rx_seq, rx_buf, &size);
+		if (rc)
+			break;
+
+		if (seq != rx_seq) {
+			pr_debug(ctx, "IPMI: out-of-sequence reply: %ld, "
+					"expected %ld. Dropping message.\n",
+					rx_seq, seq);
+			continue;
+		}
+
+		pr_debug(ctx, "IPMI: received %zd bytes\n", tx_size);
+		*rx_size = be64toh(size);
+		rc = 0;
+		break;
+	}
+
+out:
+	close(fd);
+	return rc;
+}
+
+int hservice_memory_error(uint64_t i_start_addr, uint64_t i_endAddr,
+		enum MemoryError_t i_errorType)
+{
+	char buf[ADDR_STRING_SZ];
+	const char *sysfsfile;
+	int memfd, rc, n;
+	uint64_t addr;
+
+	pr_debug(ctx, "Memory error addr:%016lx-%016lx type: %d\n",
+			i_start_addr, i_endAddr, i_errorType);
+
+	switch(i_errorType) {
+	case MEMORY_ERROR_CE:
+		sysfsfile = mem_offline_soft;
+		break;
+	case MEMORY_ERROR_UE:
+		sysfsfile = mem_offline_hard;
+		break;
+	default:
+		warn("Invalid memory error type %d", i_errorType);
+		return -1;
+	}
+
+	memfd = open(sysfsfile, O_WRONLY);
+	if (memfd < 0) {
+		warn("Unable to open sysfs: %s", sysfsfile);
+		return -1;
+	}
+
+	for (addr = i_start_addr; addr <= i_endAddr; addr += ctx->page_size) {
+		n = snprintf(buf, ADDR_STRING_SZ, "0x%lx", addr);
+		rc = write(memfd, buf, n);
+		if (rc != n) {
+			warn("Memory offine of addr: %016lx type: %d failed",
+				addr, i_errorType);
+			return rc;
+		}
+	}
+
+	return 0;
+}
+
+void hservices_init(struct opal_prd_ctx *ctx, void *code)
+{
+	uint64_t *s, *d;
+	int i, sz;
+
+	pr_debug(ctx, "Code Address : [%p]\n", code);
+
+	/* We enter at 0x100 into the image. */
+	/* Load func desc in BE since we reverse it in thunk */
+
+	hbrt_entry.addr = (void *)htobe64((unsigned long)code + 0x100);
+	hbrt_entry.toc = 0; /* No toc for init entry point */
+
+	if (memcmp(code, "HBRTVERS", 8) != 0)
+		errx(EXIT_FAILURE, "HBRT: Bad signature for "
+				"ibm,hbrt-code-image! exiting\n");
+
+	pr_debug(ctx, "HBRT: calling ibm,hbrt_init() %p\n", hservice_runtime);
+	hservice_runtime = call_hbrt_init(&hinterface);
+	pr_debug(ctx, "HBRT: hbrt_init passed..... %p version %016lx\n",
+			hservice_runtime, hservice_runtime->interface_version);
+
+	sz = sizeof(struct runtime_interfaces)/sizeof(uint64_t);
+	s = (uint64_t *)hservice_runtime;
+	d = (uint64_t *)&hservice_runtime_fixed;
+	/* Byte swap the function pointers */
+	for (i = 0; i < sz; i++)
+		d[i] = be64toh(s[i]);
+}
+
+static void fixup_hinterface_table(void)
+{
+	uint64_t *t64;
+	unsigned int i, sz;
+
+	/* Swap interface version */
+	hinterface.interface_version =
+		htobe64(hinterface.interface_version);
+
+	/* Swap OPDs */
+	sz = sizeof(struct host_interfaces) / sizeof(uint64_t);
+	t64 = (uint64_t *)&hinterface;
+	for (i = 1; i < sz; i++) {
+		uint64_t *opd = (uint64_t *)t64[i];
+		if (!opd)
+			continue;
+		t64[i] = htobe64(t64[i]);
+		opd[0] = htobe64(opd[0]);
+		opd[1] = htobe64(opd[1]);
+		opd[2] = htobe64(opd[2]);
+	}
+}
+
+static int map_hbrt_file(struct opal_prd_ctx *ctx, const char *name)
+{
+	struct stat statbuf;
+	int fd, rc;
+	void *buf;
+
+	fd = open(name, O_RDONLY);
+	if (fd < 0) {
+		warn("open(%s)", name);
+		return -1;
+	}
+
+	rc = fstat(fd, &statbuf);
+	if (rc < 0) {
+		warn("fstat(%s)", name);
+		close(fd);
+		return -1;
+	}
+
+	buf = mmap(NULL, statbuf.st_size, PROT_READ | PROT_WRITE | PROT_EXEC,
+			MAP_PRIVATE, fd, 0);
+	close(fd);
+
+	if (buf == MAP_FAILED) {
+		warn("mmap(%s)", name);
+		return -1;
+	}
+
+	ctx->code_addr = buf;
+	ctx->code_size = statbuf.st_size;
+	return -0;
+}
+
+static int map_hbrt_physmem(struct opal_prd_ctx *ctx, const char *name)
+{
+	struct opal_prd_range *range;
+	void *buf;
+
+	range = find_range(name);
+	if (!range) {
+		warnx("can't find code region %s\n", name);
+		return -1;
+	}
+
+	buf = mmap(NULL, range->size, PROT_READ | PROT_WRITE | PROT_EXEC,
+			MAP_PRIVATE, ctx->fd, range->physaddr);
+	if (buf == MAP_FAILED) {
+		warn("mmap(range:%s)\n", name);
+		return -1;
+	}
+
+	ctx->code_addr = buf;
+	ctx->code_size = range->size;
+	return 0;
+}
+
+static void dump_hbrt_map(struct opal_prd_ctx *ctx)
+{
+	const char *dump_name = "hbrt.bin";
+	int fd, rc;
+
+	if (!ctx->debug)
+		return;
+
+	fd = open(dump_name, O_WRONLY | O_CREAT, 0644);
+	if (fd < 0)
+		err(EXIT_FAILURE, "couldn't open %s for writing", dump_name);
+
+	ftruncate(fd, 0);
+	rc = write(fd, ctx->code_addr, ctx->code_size);
+	close(fd);
+
+	if (rc != ctx->code_size)
+		warn("write to %s failed", dump_name);
+	else
+		pr_debug(ctx, "dumped HBRT binary to %s\n", dump_name);
+}
+
+static int prd_init(struct opal_prd_ctx *ctx)
+{
+	int rc;
+
+	ctx->page_size = sysconf(_SC_PAGE_SIZE);
+
+	/* set up the device, and do our get_info ioctl */
+	ctx->fd = open(opal_prd_devnode, O_RDWR);
+	if (ctx->fd < 0) {
+		warn("Can't open PRD device %s\n", opal_prd_devnode);
+		return -1;
+	}
+
+	rc = ioctl(ctx->fd, OPAL_PRD_GET_INFO, &ctx->info);
+	if (rc) {
+		warn("Can't get PRD info");
+		return -1;
+	}
+
+	return 0;
+}
+
+static int handle_msg_attn(struct opal_prd_ctx *ctx, struct opal_prd_msg *msg)
+{
+	uint64_t proc, ipoll_mask, ipoll_status;
+	int rc;
+
+	proc = be64toh(msg->attn.proc);
+	ipoll_status = be64toh(msg->attn.ipoll_status);
+	ipoll_mask = be64toh(msg->attn.ipoll_mask);
+
+	if (!hservice_runtime->handle_attns) {
+		fprintf(stderr, "no handle_attns call\n");
+		return -1;
+	}
+
+	rc = call_handle_attns(proc, ipoll_status, ipoll_mask);
+	if (rc) {
+		fprintf(stderr, "enable_attns(%lx,%lx,%lx) failed, rc %d",
+				proc, ipoll_status, ipoll_mask, rc);
+		return -1;
+	}
+
+	/* send the response */
+	msg->type = OPAL_PRD_MSG_TYPE_ATTN_ACK;
+	msg->attn_ack.proc = htobe64(proc);
+	msg->attn_ack.ipoll_ack = htobe64(ipoll_status);
+	rc = write(ctx->fd, msg, sizeof(*msg));
+
+	if (rc != sizeof(*msg)) {
+		warn("write(ATTN_ACK) failed");
+		return -1;
+	}
+
+	return 0;
+}
+
+static int handle_msg_occ_error(struct opal_prd_ctx *ctx,
+		struct opal_prd_msg *msg)
+{
+	uint32_t proc;
+
+	proc = be64toh(msg->occ_error.chip);
+
+	if (!hservice_runtime->process_occ_error) {
+		fprintf(stderr, "no process_occ_error call\n");
+		return -1;
+	}
+
+	call_process_occ_error(proc);
+	return 0;
+}
+
+static int handle_msg_occ_reset(struct opal_prd_ctx *ctx,
+		struct opal_prd_msg *msg)
+{
+	uint32_t proc;
+
+	proc = be64toh(msg->occ_reset.chip);
+
+	if (!hservice_runtime->process_occ_reset) {
+		fprintf(stderr, "no handle_reset call\n");
+		return -1;
+	}
+
+	call_process_occ_reset(proc);
+	return 0;
+}
+
+static int handle_prd_msg(struct opal_prd_ctx *ctx)
+{
+	struct opal_prd_msg msg;
+	int rc;
+
+	rc = read(ctx->fd, &msg, sizeof(msg));
+	if (rc < 0 && errno == EAGAIN)
+		return -1;
+
+	if (rc != sizeof(msg)) {
+		warn("read on opal prd device failed");
+		return -1;
+	}
+
+	switch (msg.type) {
+	case OPAL_PRD_MSG_TYPE_ATTN:
+		rc = handle_msg_attn(ctx, &msg);
+		break;
+	case OPAL_PRD_MSG_TYPE_OCC_RESET:
+		rc = handle_msg_occ_reset(ctx, &msg);
+		break;
+	case OPAL_PRD_MSG_TYPE_OCC_ERROR:
+		rc = handle_msg_occ_error(ctx, &msg);
+		break;
+	default:
+		warn("Invalid incoming message type 0x%x\n", msg.type);
+		return -1;
+	}
+
+	return 0;
+}
+
+static int handle_prd_control(struct opal_prd_ctx *ctx, int fd)
+{
+	struct control_msg msg;
+	bool enabled;
+	int rc;
+
+	rc = recv(fd, &msg, sizeof(msg), MSG_TRUNC);
+	if (rc != sizeof(msg)) {
+		warn("recvfrom");
+		return -1;
+	}
+
+	enabled = false;
+	rc = -1;
+
+	switch (msg.type) {
+	case CONTROL_MSG_ENABLE_OCCS:
+		enabled = true;
+		/* fall through */
+	case CONTROL_MSG_DISABLE_OCCS:
+		if (!hservice_runtime->enable_occ_actuation) {
+			fprintf(stderr, "no enable_occ_actuation call\n");
+		} else {
+			pr_debug(ctx, "calling enable_occ_actuation(%s)\n",
+					enabled ? "true" : "false");
+			rc = call_enable_occ_actuation(enabled);
+			pr_debug(ctx, " -> %d\n", rc);
+		}
+		break;
+	case CONTROL_MSG_TEMP_OCC_RESET:
+		if (hservice_runtime->process_occ_reset) {
+			pr_debug(ctx, "calling process_occ_reset(0)\n");
+			call_process_occ_reset(0);
+			rc = 0;
+		} else {
+			fprintf(stderr, "no process_occ_reset call\n");
+		}
+		break;
+	case CONTROL_MSG_TEMP_OCC_ERROR:
+		if (hservice_runtime->process_occ_error) {
+			pr_debug(ctx, "calling process_occ_error(0)\n");
+			call_process_occ_error(0);
+			rc = 0;
+		} else {
+			fprintf(stderr, "no process_occ_error call\n");
+		}
+		break;
+	default:
+		fprintf(stderr, "Unknown control message action %d",
+				msg.type);
+	}
+
+	/* send a response */
+	msg.response = rc;
+	rc = send(fd, &msg, sizeof(msg), MSG_DONTWAIT | MSG_NOSIGNAL);
+	if (rc && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EPIPE))
+		pr_debug(ctx, "control send() returned %d, ignoring failure\n",
+				rc);
+	else if (rc != sizeof(msg))
+		warn("control socket send failed");
+
+	return 0;
+}
+
+static int run_attn_loop(struct opal_prd_ctx *ctx)
+{
+	struct pollfd pollfds[2];
+	struct opal_prd_msg msg;
+	int rc, fd;
+
+	if (hservice_runtime->enable_attns) {
+		pr_debug(ctx, "calling enable_attns()\n");
+		rc = call_enable_attns();
+		if (rc) {
+			fprintf(stderr, "enable_attns() failed, aborting\n");
+			return -1;
+		}
+	}
+
+	/* send init message, to unmask interrupts */
+	msg.type = OPAL_PRD_MSG_TYPE_INIT;
+	msg.init.version = htobe64(opal_prd_version);
+	msg.init.ipoll = htobe64(opal_prd_ipoll);
+
+	pr_debug(ctx, "writing init message\n");
+	rc = write(ctx->fd, &msg, sizeof(msg));
+	if (rc != sizeof(msg)) {
+		warn("init message failed, aborting");
+		return -1;
+	}
+
+	pollfds[0].fd = ctx->fd;
+	pollfds[0].events = POLLIN | POLLERR;
+	pollfds[1].fd = ctx->socket;
+	pollfds[1].events = POLLIN | POLLERR;
+
+	for (;;) {
+		rc = poll(pollfds, 2, -1);
+		if (rc < 0)
+			err(EXIT_FAILURE, "poll");
+
+		if (!rc)
+			continue;
+
+		if (pollfds[0].revents & POLLIN)
+			handle_prd_msg(ctx);
+
+		if (pollfds[1].revents & POLLIN) {
+			fd = accept(ctx->socket, NULL, NULL);
+			if (fd < 0) {
+				warn("accept");
+				continue;
+			}
+			handle_prd_control(ctx, fd);
+			close(fd);
+		}
+	}
+
+	return 0;
+}
+
+static int init_control_socket(struct opal_prd_ctx *ctx)
+{
+	struct sockaddr_un addr;
+	int fd, rc;
+
+	unlink(opal_prd_socket);
+
+	addr.sun_family = AF_UNIX;
+	strcpy(addr.sun_path, opal_prd_socket);
+
+	fd = socket(AF_LOCAL, SOCK_STREAM, 0);
+	if (fd < 0) {
+		warn("Can't open control socket %s", opal_prd_socket);
+		return -1;
+	}
+
+	rc = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
+	if (rc) {
+		warn("Can't bind control socket %s", opal_prd_socket);
+		close(fd);
+		return -1;
+	}
+
+	rc = listen(fd, 0);
+	if (rc) {
+		warn("Can't listen on control socket %s", opal_prd_socket);
+		close(fd);
+		return -1;
+	}
+
+	ctx->socket = fd;
+	return 0;
+}
+
+static int run_prd_daemon(struct opal_prd_ctx *ctx)
+{
+	int rc;
+
+	ctx->fd = -1;
+	ctx->socket = -1;
+
+	i2c_init();
+
+#ifdef DEBUG_I2C
+	{
+		uint8_t foo[128];
+		int i;
+
+		rc = i2c_read(0, 1, 2, 0x50, 2, 0x10, 128, foo);
+		printf("read rc: %d\n", rc);
+		for (i = 0; i < sizeof(foo); i += 8) {
+			printf("%02x %02x %02x %02x %02x %02x %02x %02x\n",
+			       foo[i + 0], foo[i + 1], foo[i + 2], foo[i + 3],
+			       foo[i + 4], foo[i + 5], foo[i + 6], foo[i + 7]);
+		}
+	}
+#endif
+	rc = init_control_socket(ctx);
+	if (rc) {
+		warnx("Error initialising PRD control");
+		goto out_close;
+	}
+
+
+	rc = prd_init(ctx);
+	if (rc) {
+		warnx("Error initialising PRD setup");
+		goto out_close;
+	}
+
+
+	if (ctx->hbrt_file_name) {
+		rc = map_hbrt_file(ctx, ctx->hbrt_file_name);
+		if (rc) {
+			warnx("can't access hbrt file %s", ctx->hbrt_file_name);
+			goto out_close;
+		}
+	} else {
+		rc = map_hbrt_physmem(ctx, hbrt_code_region_name);
+		if (rc) {
+			warn("can't access hbrt physical memory");
+			goto out_close;
+		}
+		dump_hbrt_map(ctx);
+	}
+
+	pr_debug(ctx, "hbrt map at %p, size 0x%zx\n",
+			ctx->code_addr, ctx->code_size);
+
+	fixup_hinterface_table();
+
+	pr_debug(ctx, "calling hservices_init\n");
+	hservices_init(ctx, ctx->code_addr);
+	pr_debug(ctx, "hservices_init done\n");
+
+	if (ctx->pnor.path) {
+		rc = pnor_init(&ctx->pnor);
+		if (rc) {
+			printf("Failed to open pnor\n");
+			goto out_close;
+		}
+	}
+
+	/* Test a scom */
+	if (ctx->debug) {
+		uint64_t val;
+		printf("trying scom read\n");
+		fflush(stdout);
+		hservice_scom_read(0x00, 0xf000f, &val);
+		printf("f00f: %lx\n", be64toh(val));
+	}
+
+	run_attn_loop(ctx);
+	rc = 0;
+
+out_close:
+	pnor_close(&ctx->pnor);
+	if (ctx->fd != -1)
+		close(ctx->fd);
+	if (ctx->socket != -1)
+		close(ctx->socket);
+	return rc;
+}
+
+static int send_occ_control(struct opal_prd_ctx *ctx, const char *str)
+{
+	struct sockaddr_un addr;
+	struct control_msg msg;
+	int sd, rc;
+
+	memset(&msg, 0, sizeof(msg));
+
+	if (!strcmp(str, "enable")) {
+		msg.type = CONTROL_MSG_ENABLE_OCCS;
+	} else if (!strcmp(str, "disable")) {
+		msg.type = CONTROL_MSG_DISABLE_OCCS;
+	} else if (!strcmp(str, "reset")) {
+		msg.type = CONTROL_MSG_TEMP_OCC_RESET;
+	} else if (!strcmp(str, "process-error")) {
+		msg.type = CONTROL_MSG_TEMP_OCC_ERROR;
+	} else {
+		fprintf(stderr, "Invalid OCC action '%s'\n", str);
+		return -1;
+	}
+
+	sd = socket(AF_UNIX, SOCK_STREAM, 0);
+	if (!sd) {
+		warn("Failed to create control socket");
+		return -1;
+	}
+
+	addr.sun_family = AF_UNIX;
+	strcpy(addr.sun_path, opal_prd_socket);
+
+	rc = connect(sd, (struct sockaddr *)&addr, sizeof(addr));
+	if (rc) {
+		warn("Failed to connect to prd daemon");
+		goto out_close;
+	}
+
+	rc = send(sd, &msg, sizeof(msg), 0);
+	if (rc != sizeof(msg)) {
+		warn("send");
+		rc = -1;
+		goto out_close;
+	}
+
+	/* wait for our reply */
+	rc = recv(sd, &msg, sizeof(msg), 0);
+	if (rc < 0) {
+		warn("control socket receive failed");
+		goto out_close;
+
+	} else if (rc != sizeof(msg)) {
+		warnx("short read from control socket");
+		rc = -1;
+		goto out_close;
+	}
+
+	if (msg.response || ctx->debug) {
+		warnx("OCC action %s returned status %ld\n",
+				str, msg.response);
+	}
+
+	rc = msg.response;
+
+out_close:
+	close(sd);
+	return rc;
+}
+
+static void usage(const char *progname)
+{
+	printf("Usage:\n");
+	printf("\t%s [--debug] [--file <hbrt-image>] [--pnor <device>]\n"
+			"\t\t[--allow-fsp-calls]\n",
+			progname);
+	printf("\t%s occ <enable|disable>\n", progname);
+	printf("\n");
+	printf("Options:\n"
+"\t--debug            verbose logging for debug information\n"
+"\t--pnor DEVICE      use PNOR MTD device\n"
+"\t--file FILE        use FILE for hostboot runtime code (instead of code\n"
+"\t                     exported by firmware)\n"
+"\t--allow-fsp-calls  don't exit on FSP-only callbacks from HBRT code, but\n"
+"\t                     return success instead. Intended for workarounds\n"
+"\t                     during PRD testing only.\n");
+}
+
+static struct option opal_diag_options[] = {
+	{"file", required_argument, NULL, 'f'},
+	{"pnor", required_argument, NULL, 'p'},
+	{"debug", no_argument, NULL, 'd'},
+	{"allow-fsp-calls", no_argument, NULL, 'a'},
+	{"help", no_argument, NULL, 'h'},
+	{ 0 },
+};
+
+enum action {
+	ACTION_RUN_DAEMON,
+	ACTION_OCC_CONTROL,
+};
+
+static int parse_action(const char *str, enum action *action)
+{
+	if (!strcmp(str, "occ")) {
+		*action = ACTION_OCC_CONTROL;
+		return 0;
+	}
+
+	if (!strcmp(str, "daemon")) {
+		*action = ACTION_RUN_DAEMON;
+		return 0;
+	}
+
+	fprintf(stderr, "unknown argument '%s'\n", str);
+	return -1;
+}
+
+int main(int argc, char *argv[])
+{
+	struct opal_prd_ctx _ctx;
+	enum action action;
+	int rc;
+
+	ctx = &_ctx;
+	memset(ctx, 0, sizeof(*ctx));
+
+	/* Parse options */
+	for (;;) {
+		int c;
+
+		c = getopt_long(argc, argv, "f:p:dh", opal_diag_options, NULL);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'f':
+			ctx->hbrt_file_name = optarg;
+			break;
+		case 'd':
+			ctx->debug = true;
+			break;
+		case 'p':
+			ctx->pnor.path = strndup(optarg, PATH_MAX);
+			break;
+		case 'a':
+			ctx->allow_fsp_calls = true;
+			break;
+		case 'h':
+			usage(argv[0]);
+			return EXIT_SUCCESS;
+		case '?':
+		default:
+			usage(argv[0]);
+			return EXIT_FAILURE;
+		}
+	}
+
+	if (optind < argc) {
+		rc = parse_action(argv[optind], &action);
+		if (rc)
+			return EXIT_FAILURE;
+	} else {
+		action = ACTION_RUN_DAEMON;
+	}
+
+	if (action == ACTION_RUN_DAEMON) {
+		rc = run_prd_daemon(ctx);
+
+	} else if (action == ACTION_OCC_CONTROL) {
+
+		if (optind + 1 >= argc) {
+			fprintf(stderr, "occ command requires an argument\n");
+			return EXIT_FAILURE;
+		}
+
+		rc = send_occ_control(ctx, argv[optind + 1]);
+	}
+
+	return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
diff --git a/external/opal-prd/pnor.c b/external/opal-prd/pnor.c
new file mode 100644
index 0000000..0eca693
--- /dev/null
+++ b/external/opal-prd/pnor.c
@@ -0,0 +1,301 @@
+/* Copyright 2013-2015 IBM Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 	http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <libflash/libffs.h>
+#include <errno.h>
+#include <err.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <mtd/mtd-user.h>
+
+#include <pnor.h>
+
+int pnor_init(struct pnor *pnor)
+{
+	int rc, fd;
+	mtd_info_t mtd_info;
+
+	if (!pnor)
+		return -1;
+
+	/* Open device and ffs */
+	fd = open(pnor->path, O_RDWR);
+	if (fd < 0) {
+		perror(pnor->path);
+		return -1;
+	}
+
+	/* Hack so we can test on non-mtd file descriptors */
+#if defined(__powerpc__)
+	rc = ioctl(fd, MEMGETINFO, &mtd_info);
+	if (rc < 0) {
+		fprintf(stderr, "PNOR: ioctl failed to get pnor info\n");
+		goto out;
+	}
+	pnor->size = mtd_info.size;
+	pnor->erasesize = mtd_info.erasesize;
+#else
+	pnor->size = lseek(fd, 0, SEEK_END);
+	if (pnor->size < 0) {
+		perror(pnor->path);
+		goto out;
+	}
+	/* Fake it */
+	pnor->erasesize = 1024;
+#endif
+
+	printf("Found PNOR: %d bytes (%d blocks)\n", pnor->size,
+	       pnor->erasesize);
+
+	rc = ffs_open_image(fd, pnor->size, 0, &pnor->ffsh);
+	if (rc)
+		fprintf(stderr, "Failed to open pnor partition table\n");
+
+out:
+	close(fd);
+
+	return rc;
+}
+
+void pnor_close(struct pnor *pnor)
+{
+	if (!pnor)
+		return;
+
+	if (pnor->ffsh)
+		ffs_close(pnor->ffsh);
+
+	if (pnor->path)
+		free(pnor->path);
+}
+
+void dump_parts(struct ffs_handle *ffs) {
+	int i, rc;
+	uint32_t start, size, act_size;
+	char *name;
+
+	printf(" %10s %8s %8s %8s\n", "name", "start", "size", "act_size");
+	for (i = 0; ; i++) {
+		rc = ffs_part_info(ffs, i, &name, &start,
+				&size, &act_size, NULL);
+		if (rc)
+			break;
+		printf(" %10s %08x %08x %08x\n", name, start, size, act_size);
+		free(name);
+	}
+}
+
+int mtd_write(struct pnor *pnor, int fd, void *data, uint64_t offset,
+	      size_t len)
+{
+	int write_start, write_len, start_waste, rc;
+	bool end_waste = false;
+	uint8_t *buf;
+	struct erase_info_user erase;
+
+	if (len > pnor->size || offset > pnor->size ||
+	    len + offset > pnor->size)
+		return -1;
+
+	start_waste = offset % pnor->erasesize;
+	write_start = offset - start_waste;
+
+	/* Align size to multiple of block size */
+	write_len = (len + start_waste) & ~(pnor->erasesize - 1);
+	if ((len + start_waste) > write_len) {
+		end_waste = true;
+		write_len += pnor->erasesize;
+	}
+
+	buf = malloc(write_len);
+
+	if (start_waste) {
+		rc = lseek(fd, write_start, SEEK_SET);
+		if (rc < 0) {
+			perror("lseek write_start");
+			goto out;
+		}
+
+		read(fd, buf, pnor->erasesize);
+	}
+
+	if (end_waste)  {
+		rc = lseek(fd, write_start + write_len - pnor->erasesize,
+			   SEEK_SET);
+		if (rc < 0) {
+			perror("lseek last write block");
+			goto out;
+		}
+
+		read(fd, buf + write_len - pnor->erasesize, pnor->erasesize);
+	}
+
+	/* Put data in the correct spot */
+	memcpy(buf + start_waste, data, len);
+
+	/* Not sure if this is required */
+	rc = lseek(fd, 0, SEEK_SET);
+	if (rc < 0) {
+		perror("lseek 0");
+		goto out;
+	}
+
+	/* Erase */
+	erase.start = write_start;
+	erase.length = write_len;
+
+	rc = ioctl(fd, MEMERASE, &erase);
+	if (rc < 0) {
+		perror("ioctl MEMERASE");
+		goto out;
+	}
+
+	/* Write */
+	rc = lseek(fd, write_start, SEEK_SET);
+	if (rc < 0) {
+		perror("lseek write_start");
+		goto out;
+	}
+
+	rc = write(fd, buf, write_len);
+	if (rc < 0) {
+		perror("write to fd");
+		goto out;
+	}
+
+	/* We have succeded, report the requested write size */
+	rc = len;
+
+out:
+	free(buf);
+	return rc;
+}
+
+int mtd_read(struct pnor *pnor, int fd, void *data, uint64_t offset,
+	     size_t len)
+{
+	int read_start, read_len, start_waste, rc;
+	int mask = pnor->erasesize - 1;
+	void *buf;
+
+	if (len > pnor->size || offset > pnor->size ||
+	    len + offset > pnor->size)
+		return -1;
+
+	/* Align start to erase block size */
+	start_waste = offset % pnor->erasesize;
+	read_start = offset - start_waste;
+
+	/* Align size to multiple of block size */
+	read_len = (len + start_waste) & ~mask;
+	if ((len + start_waste) > read_len)
+		read_len += pnor->erasesize;
+
+	/* Ensure read is not out of bounds */
+	if (read_start + read_len > pnor->size) {
+		fprintf(stderr, "PNOR: read out of bounds\n");
+		return -1;
+	}
+
+	buf = malloc(read_len);
+
+	rc = lseek(fd, read_start, SEEK_SET);
+	if (rc < 0) {
+		perror("lseek read_start");
+		goto out;
+	}
+
+	rc = read(fd, buf, read_len);
+	if (rc < 0) {
+		perror("read from fd");
+		goto out;
+	}
+
+	/* Copy data into destination, cafefully avoiding the extra data we
+	 * added to align to block size */
+	memcpy(data, buf + start_waste, len);
+	rc = len;
+out:
+	free(buf);
+	return rc;
+}
+
+int pnor_operation(struct pnor *pnor, const char *name, uint64_t offset,
+		   void *data, size_t size, enum pnor_op op)
+{
+	int rc, fd;
+	uint32_t pstart, psize, idx;
+
+	if (!pnor->ffsh)
+		return -1;
+
+	rc = ffs_lookup_part(pnor->ffsh, name, &idx);
+	if (rc)
+		return -1;
+
+	ffs_part_info(pnor->ffsh, idx, NULL, &pstart, &psize, NULL, NULL);
+	if (rc)
+		return -1;
+
+	if (size > psize || offset > psize || size + offset > psize)
+		return -1;
+
+	fd = open(pnor->path, O_RDWR);
+	if (fd < 0) {
+		perror(pnor->path);
+		return fd;
+	}
+
+	rc = lseek(fd, pstart, SEEK_SET);
+	if (rc < 0) {
+		perror(pnor->path);
+		goto out;
+	}
+
+	switch (op) {
+	case PNOR_OP_READ:
+		rc = mtd_read(pnor, fd, data, offset, size);
+		break;
+	case PNOR_OP_WRITE:
+		rc = mtd_write(pnor, fd, data, offset, size);
+		break;
+	default:
+		rc  = -1;
+		fprintf(stderr, "PNOR: Invalid operation\n");
+		goto out;
+	}
+
+	if (rc < 0)
+		warn("PNOR: MTD operation failed");
+	else if (rc != size)
+		warnx("PNOR: mtd operation returned %d, expected %zd",
+				rc, size);
+	else
+		rc = 0;
+
+
+out:
+	close(fd);
+
+	return rc;
+}
diff --git a/external/opal-prd/pnor.h b/external/opal-prd/pnor.h
new file mode 100644
index 0000000..06219dc
--- /dev/null
+++ b/external/opal-prd/pnor.h
@@ -0,0 +1,25 @@
+#ifndef PNOR_H
+#define PNOR_H
+
+#include <libflash/libffs.h>
+
+struct pnor {
+	char			*path;
+	struct ffs_handle	*ffsh;
+	uint32_t		size;
+	uint32_t		erasesize;
+};
+
+enum pnor_op {
+	PNOR_OP_READ,
+	PNOR_OP_WRITE,
+};
+
+extern int pnor_operation(struct pnor *pnor, const char *name,
+			  uint64_t offset, void *data, size_t size,
+			  enum pnor_op);
+
+extern int pnor_init(struct pnor *pnor);
+extern void pnor_close(struct pnor *pnor);
+
+#endif /*PNOR_H*/
diff --git a/external/opal-prd/test/test_pnor.c b/external/opal-prd/test/test_pnor.c
new file mode 100644
index 0000000..f4b0a6d
--- /dev/null
+++ b/external/opal-prd/test/test_pnor.c
@@ -0,0 +1,49 @@
+/* Copyright 2013-2015 IBM Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 	http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <linux/limits.h>
+
+#include <libflash/libffs.h>
+#include <pnor.h>
+
+extern void dump_parts(struct ffs_handle *ffs);
+
+int main(int argc, char **argv)
+{
+	struct pnor pnor;
+	int rc;
+
+	if (argc != 2) {
+		printf("usage: %s [pnor file]\n", argv[0]);
+		exit(EXIT_FAILURE);
+	}
+
+
+	pnor.path = strndup(argv[1], PATH_MAX);
+
+	rc = pnor_init(&pnor);
+	assert(rc);
+
+	dump_parts(pnor.ffsh);
+
+	pnor_close(&pnor);
+
+	return 0;
+}
diff --git a/external/opal-prd/test/test_pnor_ops.c b/external/opal-prd/test/test_pnor_ops.c
new file mode 100644
index 0000000..8d82a87
--- /dev/null
+++ b/external/opal-prd/test/test_pnor_ops.c
@@ -0,0 +1,235 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <sys/ioctl.h>
+#include <mtd/mtd-user.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#undef ioctl
+#define ioctl(d, req, arg) test_ioctl(d, req, arg)
+
+int test_ioctl(int fd, int req, void *arg)
+{
+	if (req == MEMERASE) {
+		uint8_t *buf;
+		struct erase_info_user *erase = arg;
+
+		buf = malloc(erase->length);
+		memset(buf, 'E', erase->length);
+
+		lseek(fd, erase->start, SEEK_SET);
+		write(fd, buf, erase->length); 
+
+		free(buf);
+	}
+
+	return 0;
+}
+
+#include "../pnor.c"
+
+bool compare_data(int fd, const uint8_t *check)
+{
+	uint8_t buf[16];
+	int offset = 0;
+	int bytes_read;
+	int i;
+
+	lseek(fd, 0, SEEK_SET);
+
+	do {
+		bytes_read = read(fd, buf, sizeof(buf));
+		i = 0;
+		while (i < bytes_read)
+			if (buf[i++] != check[offset++])
+				return false;
+	} while (bytes_read == sizeof(buf));
+
+out:
+	lseek(fd, 0, SEEK_SET);
+
+	return true;
+}
+
+void print_buf(uint8_t *buf, size_t len)
+{
+	int i;
+
+	for (i = 0; i < len; i++) {
+		if (i % 16 == 0)
+			printf("\n%06x : ", i);
+
+		printf("%c ", buf[i]);
+	}
+	printf("\n");
+}
+
+void print_file(int fd)
+{
+	uint8_t buf[16];
+	int offset = 0;
+	int bytes_read;
+	int i;
+
+	lseek(fd, 0, SEEK_SET);
+
+	do {
+		bytes_read = read(fd, buf, sizeof(buf)); 
+		if (bytes_read == 0)
+			break;
+		printf ("%06x : ", offset); 
+		for (i = 0; i < bytes_read; ++i) 
+			printf("%c ", buf[i]);
+		printf("\n");
+		offset += bytes_read; 
+	} while (bytes_read == sizeof(buf));
+
+	lseek(fd, 0, SEEK_SET);
+}
+
+const uint8_t empty[32] = {
+	'E', 'E', 'E', 'E', 'E', 'E', 'E', 'E',
+	'E', 'E', 'E', 'E', 'E', 'E', 'E', 'E',
+	'E', 'E', 'E', 'E', 'E', 'E', 'E', 'E',
+	'E', 'E', 'E', 'E', 'E', 'E', 'E', 'E'};
+
+const uint8_t test_one[32] = {
+	'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A',
+	'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A',
+	'A', 'A', 'A', 'A', 'A', 'A', 'A', 'E',
+	'E', 'E', 'E', 'E', 'E', 'E', 'E', 'E'};
+
+const uint8_t test_three[32] = {
+	'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A',
+	'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A',
+	'A', 'A', 'A', 'A', 'A', 'A', 'A', 'E',
+	'M', 'M', 'M', 'M', 'M', 'M', 'M', 'M'};
+
+int main(int argc, char **argv)
+{
+	int fd, i, rc;
+	struct pnor pnor;
+	uint8_t data[24];
+	char filename[24];
+
+	strcpy(filename, "/tmp/pnor-XXXXXX");
+
+	fd = mkstemp(filename);
+	if (fd < 0) {
+		perror("mkstemp");
+		return EXIT_FAILURE;
+	}
+	/* So the file dissapears when we exit */
+	unlink(filename);
+
+	/* E for empty */
+	memset(data, 'E', sizeof(data));
+	for (i = 0; i < 2; i++)
+		write(fd, data, 16);
+
+	/* Adjust this if making the file smaller */
+	pnor.size = 32;
+	
+	/* This is fake. Make it smaller than the size */
+	pnor.erasesize = 4;
+
+	printf("Write: ");
+	memset(data, 'A', sizeof(data));
+	rc = mtd_write(&pnor, fd, data, 0, 23);
+	if (rc == 23 && compare_data(fd, test_one))
+		printf("PASS\n");
+	else
+		printf("FAIL: %d\n", rc);
+
+	printf("Read: ");
+	memset(data, '0', sizeof(data));
+	rc = mtd_read(&pnor, fd, data, 7, 24);
+	if (rc == 24 && !memcmp(data, &test_one[7], 24))
+		printf("PASS\n");
+	else
+		printf("FAIL\n");
+
+	printf("Write with offset: ");
+	memset(data, 'M', sizeof(data));
+	rc = mtd_write(&pnor, fd, data, 24, 8);
+	if (rc == 8 && compare_data(fd, test_three))
+		printf("PASS\n");
+	else
+		printf("FAIL\n");
+
+	printf("Write size past the end: ");
+	rc = mtd_write(&pnor, fd, data, 0, 64);
+	if (rc == -1 && compare_data(fd, test_three))
+		printf("PASS\n");
+	else
+		printf("FAIL: %d\n", rc);
+
+	printf("Write size past the end with offset: ");
+	rc = mtd_write(&pnor, fd, data, 24, 24);
+	if (rc == -1 && compare_data(fd, test_three))
+		printf("PASS\n");
+	else
+		printf("FAIL\n");
+
+	printf("Write with offset past the end: ");
+	rc = mtd_write(&pnor, fd, data, 64, 12);
+	if (rc == -1 && compare_data(fd, test_three))
+		printf("PASS\n");
+	else
+		printf("FAIL\n");
+
+	printf("Zero sized write: ");
+	rc = mtd_write(&pnor, fd, data, 0, 0);
+	if (rc == 0 && compare_data(fd, test_three))
+		printf("PASS\n");
+	else
+		printf("FAIL\n");
+
+	printf("Zero sized write with offset: ");
+	rc = mtd_write(&pnor, fd, data, 12, 0);
+	if (rc == 0 && compare_data(fd, test_three))
+		printf("PASS\n");
+	else
+		printf("FAIL\n");
+
+	printf("Read size past the end: ");
+	rc = mtd_read(&pnor, fd, data, 0, 64);
+	if (rc != 0 && compare_data(fd, test_three))
+		printf("PASS\n");
+	else
+		printf("FAIL\n");
+
+
+	printf("Read size past the end with offset: ");
+	rc = mtd_read(&pnor, fd, data, 24, 24);
+	if (rc != 0 && compare_data(fd, test_three))
+		printf("PASS\n");
+	else
+		printf("FAIL\n");
+
+	printf("Read with offset past the end: ");
+	rc = mtd_read(&pnor, fd, data, 64, 12);
+	if (rc != 0 && compare_data(fd, test_three))
+		printf("PASS\n");
+	else
+		printf("FAIL\n");
+
+	printf("Zero sized read: ");
+	rc = mtd_read(&pnor, fd, data, 0, 0);
+	if (rc == 0 && compare_data(fd, test_three))
+		printf("PASS\n");
+	else
+		printf("FAIL\n");
+
+	printf("Zero sized read with offset: ");
+	rc = mtd_read(&pnor, fd, data, 12, 0);
+	if (rc == 0 && compare_data(fd, test_three))
+		printf("PASS\n");
+	else
+		printf("FAIL\n");
+
+	return 0;
+}
diff --git a/external/opal-prd/thunk.S b/external/opal-prd/thunk.S
new file mode 100644
index 0000000..064138c
--- /dev/null
+++ b/external/opal-prd/thunk.S
@@ -0,0 +1,178 @@
+#include <endian.h>
+
+	.text
+
+	/*
+	 * Call into a HBRT BE function
+	 * Func desc (opd) will be in BE
+	 * Use ldbrx to load from opd
+	 */
+
+call_be:
+
+#define __NR_fast_endian_switch		0x1ebe
+
+	/* Before we switch, we need to perform some ABI
+	 * conversion. We are currently running LE with the
+	 * new ABI v2. The GPR content is the same, we do
+	 * need save/restore and adjust r2. At this point r11
+	 * contain the OPD
+	 */
+	nop
+	nop
+
+	/* We first create a stack frame compatible with BE, we
+	 * do a big one just in case... we save LR into our caller's
+	 * frame and r2 in our own frame. This is a BE formatted
+	 * frame so we store it as 40(r1), not 24(r1)
+	 */
+	stdu %r1,-128(%r1)
+	mflr %r0
+	std %r0,(128 + 16)(%r1)
+	std %r2,40(%r1)
+
+	/* Grab the target r2 and function pointer */
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+	ldbrx %r0, 0, %r11
+	li %r2, 8
+	ldbrx %r2, %r2, %r11
+#else
+	ld %r0,0(%r11)
+	ld %r2,8(%r11)
+#endif
+
+	mtctr %r0
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+	/* Switch to the "other endian" */
+	li %r0,__NR_fast_endian_switch
+	sc
+
+	/* Branch to CTR */
+	.long 0x2104804e /* (byteswapped bctrl) */
+
+	/* Switch endian back */
+	.long 0xbe1e0038 /* byteswapped li %r0,__NR_fast_endian_switch */
+	.long 0x02000044 /* byteswapped sc */
+#else
+	bctrl
+#endif
+	/* Recover our r2, LR, undo stack frame ... */
+	ld %r2,40(%r1)
+	ld  %r0,(128+16)(%r1)
+	addi %r1,%r1,128
+	mtlr %r0
+	blr
+
+#define CALL_THUNK(name, idx) 		 	 \
+	.globl call_##name			;\
+call_##name:					;\
+	ld %r11,hservice_runtime_fixed at got(%r2)	;\
+	ld %r11,(idx * 8)(%r11)			;\
+	b call_be
+
+	/* Instanciate call to HBRT thunks */
+	CALL_THUNK(cxxtestExecute, 1)
+	CALL_THUNK(get_lid_list, 2)
+	CALL_THUNK(occ_load, 3)
+	CALL_THUNK(occ_start, 4)
+	CALL_THUNK(occ_stop, 5)
+	CALL_THUNK(process_occ_error, 6)
+	CALL_THUNK(enable_attns, 7)
+	CALL_THUNK(disable_attns, 8)
+	CALL_THUNK(handle_attns, 9)
+	CALL_THUNK(process_occ_reset, 10)
+	CALL_THUNK(enable_occ_actuation, 11)
+
+	.globl call_hbrt_init
+call_hbrt_init:
+	ld %r11,hbrt_entry at got(%r2)
+	b call_be
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+	/* Callback from HBRT, stack conversion and call into C code,
+	 * we arrive here from the thunk macro with r11 containing the
+	 * target function and r2 already set from the OPD.
+	 */
+call_le:
+	/* Create a LE stack frame, save LR */
+	stdu %r1,-32(%r1)
+	mflr %r0
+	std %r0,(32+16)(%r1)
+
+	/* Branch to original function */
+	mtlr	%r11
+	blrl
+
+	/* Restore stack and LR */
+	ld  %r0,(32+16)(%r1)
+	addi %r1,%r1,32
+	mtlr %r0
+
+	/* Switch endian back to BE */
+	li %r0,__NR_fast_endian_switch
+	sc
+
+	/* Return to BE */
+	.long 0x2000804e /* byteswapped blr */
+
+	/* Callback from HBRT. There is one entry point per function.
+	 *
+	 * We assume the proper r2 is already set via the OPD, so we grab our
+	 * target function pointer in r11 and jump to call_le
+	 */
+#define CALLBACK_THUNK(name)							 \
+	.pushsection ".text","ax" 						;\
+	.globl	name##_thunk							;\
+name##_thunk:									;\
+	.long 0xbe1e0038 /* byteswapped li %r0,__NR_fast_endian_switch */       ;\
+	.long 0x02000044 /* byteswapped sc */					;\
+	ld %r11,name at got(%r2)							;\
+	b call_le								;\
+	.popsection								;\
+	.pushsection ".data.thunk_opd","aw" 					;\
+1:	.llong name##_thunk, .TOC., 0 						;\
+	.popsection								;\
+	.llong 1b
+#else /* __BYTE_ORDER == __LITTLE_ENDIAN */
+#define CALLBACK_THUNK(name)							 \
+	.llong name
+#endif
+
+	/* Here's the callback table generation. It creates the table and
+	 * all the thunks for all the callbacks from HBRT to us
+	 */
+	.data
+	.globl hinterface
+hinterface:
+	/* HBRT interface version */
+	.llong 1
+
+	/* Callout pointers */
+	CALLBACK_THUNK(hservice_puts)
+	CALLBACK_THUNK(hservice_assert)
+	CALLBACK_THUNK(hservice_set_page_execute)
+	CALLBACK_THUNK(hservice_malloc)
+	CALLBACK_THUNK(hservice_free)
+	CALLBACK_THUNK(hservice_realloc)
+	CALLBACK_THUNK(hservice_send_error_log)
+	CALLBACK_THUNK(hservice_scom_read)
+	CALLBACK_THUNK(hservice_scom_write)
+	CALLBACK_THUNK(hservice_lid_load)
+	CALLBACK_THUNK(hservice_lid_unload)
+	CALLBACK_THUNK(hservice_get_reserved_mem)
+	CALLBACK_THUNK(hservice_wakeup)
+	CALLBACK_THUNK(hservice_nanosleep)
+	CALLBACK_THUNK(hservice_report_occ_failure)
+	CALLBACK_THUNK(hservice_clock_gettime)
+	CALLBACK_THUNK(hservice_pnor_read)
+	CALLBACK_THUNK(hservice_pnor_write)
+	CALLBACK_THUNK(hservice_i2c_read)
+	CALLBACK_THUNK(hservice_i2c_write)
+	CALLBACK_THUNK(hservice_ipmi_msg)
+	CALLBACK_THUNK(hservice_memory_error)
+	/* Reserved space for future growth */
+	.space 32*8,0
+	/* Eye catcher for debugging */
+	.llong 0xdeadbeef
+


More information about the Skiboot mailing list