[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