aboutsummaryrefslogtreecommitdiffstats
path: root/roms/skiboot/external/opal-prd
diff options
context:
space:
mode:
authorAngelos Mouzakitis <a.mouzakitis@virtualopensystems.com>2023-10-10 14:33:42 +0000
committerAngelos Mouzakitis <a.mouzakitis@virtualopensystems.com>2023-10-10 14:33:42 +0000
commitaf1a266670d040d2f4083ff309d732d648afba2a (patch)
tree2fc46203448ddcc6f81546d379abfaeb323575e9 /roms/skiboot/external/opal-prd
parente02cda008591317b1625707ff8e115a4841aa889 (diff)
Add submodule dependency filesHEADmaster
Change-Id: Iaf8d18082d3991dec7c0ebbea540f092188eb4ec
Diffstat (limited to 'roms/skiboot/external/opal-prd')
-rw-r--r--roms/skiboot/external/opal-prd/.gitignore5
-rw-r--r--roms/skiboot/external/opal-prd/Makefile75
-rw-r--r--roms/skiboot/external/opal-prd/config.h24
-rw-r--r--roms/skiboot/external/opal-prd/hostboot-interface.h759
-rw-r--r--roms/skiboot/external/opal-prd/i2c.c257
-rw-r--r--roms/skiboot/external/opal-prd/i2c.h17
-rw-r--r--roms/skiboot/external/opal-prd/module.c49
-rw-r--r--roms/skiboot/external/opal-prd/module.h10
-rw-r--r--roms/skiboot/external/opal-prd/opal-prd.878
-rw-r--r--roms/skiboot/external/opal-prd/opal-prd.c2799
-rw-r--r--roms/skiboot/external/opal-prd/opal-prd.h15
-rw-r--r--roms/skiboot/external/opal-prd/opal-prd.service11
-rw-r--r--roms/skiboot/external/opal-prd/pnor.c217
-rw-r--r--roms/skiboot/external/opal-prd/pnor.h31
-rw-r--r--roms/skiboot/external/opal-prd/test/test_pnor.c46
-rw-r--r--roms/skiboot/external/opal-prd/test/test_pnor_ops.c238
-rw-r--r--roms/skiboot/external/opal-prd/thunk.S213
17 files changed, 4844 insertions, 0 deletions
diff --git a/roms/skiboot/external/opal-prd/.gitignore b/roms/skiboot/external/opal-prd/.gitignore
new file mode 100644
index 000000000..d98511f93
--- /dev/null
+++ b/roms/skiboot/external/opal-prd/.gitignore
@@ -0,0 +1,5 @@
+opal-prd
+/ccan
+/libflash
+/test/test_pnor
+common \ No newline at end of file
diff --git a/roms/skiboot/external/opal-prd/Makefile b/roms/skiboot/external/opal-prd/Makefile
new file mode 100644
index 000000000..fb9402f8f
--- /dev/null
+++ b/roms/skiboot/external/opal-prd/Makefile
@@ -0,0 +1,75 @@
+# SPDX-License-Identifier: Apache-2.0
+CFLAGS += -m64 -Werror -Wall -g2 -ggdb
+LDFLAGS += -m64
+ASFLAGS = -m64
+CPPFLAGS += -I. -I../../include -I../../
+
+prefix = /usr/local/
+sbindir = $(prefix)/sbin
+datadir = $(prefix)/share
+mandir = $(datadir)/man
+
+all: links arch_links | opal-prd
+
+GET_ARCH = ../../external/common/get_arch.sh
+include ../../external/common/rules.mk
+
+LIBFLASH_OBJS = libflash-blocklevel.o libflash-libffs.o \
+ libflash-libflash.o libflash-ecc.o \
+ libflash-file.o
+
+OBJS = opal-prd.o thunk.o pnor.o i2c.o module.o version.o \
+ $(LIBFLASH_OBJS) common-arch_flash.o
+
+OPAL_PRD_VERSION ?= $(shell ../../make_version.sh opal-prd)
+
+ifdef KERNEL_DIR
+links += asm/opal-prd.h
+endif
+
+asm/opal-prd.h:
+ $(Q_MKDIR)mkdir -p asm
+ $(Q_LN)ln -sfr $(KERNEL_DIR)/arch/powerpc/include/uapi/asm/opal-prd.h \
+ asm/opal-prd.h
+
+%.o: %.c
+ $(Q_CC)$(COMPILE.c) $< -o $@
+
+$(LIBFLASH_OBJS): libflash-%.o : libflash/%.c
+ $(Q_CC)$(COMPILE.c) $< -o $@
+
+%.o: %.S
+ $(Q_CC)$(COMPILE.S) $< -o $@
+
+opal-prd: $(OBJS)
+ $(Q_LINK)$(LINK.o) -o $@ $^
+
+version.c: ../../make_version.sh .version
+ @(if [ "a$(OPAL_PRD_VERSION)" = "a" ]; then \
+ echo "#error You need to set OPAL_PRD_VERSION environment variable" > $@ ;\
+ else \
+ echo "const char version[] = \"$(OPAL_PRD_VERSION)\";" ;\
+ fi) > $@
+
+.PHONY: VERSION-always
+.version: VERSION-always
+ @echo $(OPAL_PRD_VERSION) > $@.tmp
+ @cmp -s $@ $@.tmp || cp $@.tmp $@
+ @rm -f $@.tmp
+
+test: links test/test_pnor
+
+test/test_pnor: test/test_pnor.o pnor.o $(LIBFLASH_OBJS) common-arch_flash.o
+ $(Q_LINK)$(LINK.o) -o $@ $^
+
+install: all
+ install -D opal-prd $(DESTDIR)$(sbindir)/opal-prd
+ install -D -m 0644 opal-prd.8 $(DESTDIR)$(mandir)/man8/opal-prd.8
+
+clean:
+ $(RM) *.[odsa] opal-prd
+ $(RM) test/*.[odsa] test/test_pnor
+
+distclean: clean
+ $(RM) -f $(LINKS) asm
+ $(RM) -f libflash ccan version.c .version common
diff --git a/roms/skiboot/external/opal-prd/config.h b/roms/skiboot/external/opal-prd/config.h
new file mode 100644
index 000000000..e7f377353
--- /dev/null
+++ b/roms/skiboot/external/opal-prd/config.h
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/*
+ * For CCAN
+ *
+ * Copyright 2015 IBM Corp.
+ */
+
+#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/roms/skiboot/external/opal-prd/hostboot-interface.h b/roms/skiboot/external/opal-prd/hostboot-interface.h
new file mode 100644
index 000000000..59f9ff444
--- /dev/null
+++ b/roms/skiboot/external/opal-prd/hostboot-interface.h
@@ -0,0 +1,759 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/*
+ * Hostboot runtime interface
+ *
+ * Derived from src/include/runtime/interface.h in Hostboot
+ *
+ * Copyright 2013-2018 IBM Corp.
+ */
+#include <stdint.h>
+
+#define HOSTBOOT_RUNTIME_INTERFACE_VERSION 0x9002
+
+/** 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,
+
+ /** Firmware has predictively requested service on a part in the memory
+ * subsystem. The partitions may not have been affected, but it is
+ * advised to migrate off of the address range as soon as possible to
+ * avoid potential partition outages. */
+ MEMORY_ERROR_PREDICTIVE = 2,
+};
+
+/** Capability sets, for get_interface_capabilities */
+#define HBRT_CAPS_SET0_COMMON 0
+#define HBRT_CAPS_SET1_OPAL 1
+#define HBRT_CAPS_SET2_PHYP 2
+
+/* Capability flags */
+
+/**
+ * xscom_read and xscom_write return proper return codes on error.
+ * Previous implementations may have incorrectly ignored failures.
+ */
+#define HBRT_CAPS_OPAL_HAS_XSCOM_RC (1ul << 0)
+
+/**
+ * OPAL supports wakeup interface
+ */
+#define HBRT_CAPS_OPAL_HAS_WAKEUP_SUPPORT (1ul << 1)
+
+/**
+ * OPAL supports '2=clear all previous forces' argument
+ */
+#define HBRT_CAPS_OPAL_HAS_WAKEUP_CLEAR (1ul << 2)
+
+/********************/
+
+
+/**
+ * Load types for the load_pm_complex() interface
+ * HBRT_PM_LOAD: initial load of all lids/sections from scratch,
+ * preserve nothing
+ * HBRT_PM_RELOAD: concurrent reload of all lids/sections,
+ * but preserve runtime updates
+ */
+#define HBRT_PM_LOAD 0
+#define HBRT_PM_RELOAD 1
+
+/** Common return codes for scom_read(), scom_write(). */
+#define HBRT_RC_RANGE__SCOM 0x1000
+
+/* RC for a piberr is equal to 0x1000 plus the pib error value,
+ made into a negative */
+#define HBRT_RC_PIBERR_MASK (0x00000000u - 0x00001007u) /* 0xFFFF_EFF9 */
+
+#define HBRT_RC_PIBERR_001_BUSY (0x00000000u - 0x00001001u) /* 0xFFFF_EFFF */
+#define HBRT_RC_PIBERR_010_OFFLINE (0x00000000u - 0x00001002u) /* 0xFFFF_EFFE */
+#define HBRT_RC_PIBERR_011_PGOOD (0x00000000u - 0x00001003u) /* 0xFFFF_EFFD */
+#define HBRT_RC_PIBERR_100_INVALIDADDR (0x00000000u - 0x00001004u) /* 0xFFFF_EFFC */
+#define HBRT_RC_PIBERR_101_CLOCKERR (0x00000000u - 0x00001005u) /* 0xFFFF_EFFB */
+#define HBRT_RC_PIBERR_110_PARITYERR (0x00000000u - 0x00001006u) /* 0xFFFF_EFFA */
+#define HBRT_RC_PIBERR_111_TIMEOUT (0x00000000u - 0x00001007u) /* 0xFFFF_EFF9 */
+
+/* Memory channel failure caused an error out to buffer chip. */
+#define HBRT_RC_CHANNEL_FAILURE (0x00000000u - 0x00001008u) /* 0xFFFF_EFF8 */
+
+/* Any host-specific RCs will be this value or bigger */
+#define HBRT_RC_NEXT_OPEN_RC (0x00000000u - 0x00001009u) /* 0xFFFF_EFF7 */
+
+/********************/
+
+
+/** Common return codes for firmware_request(). -0x2000 */
+#define HBRT_RC_RANGE__FIRMWARE_REQUEST 0x2000
+
+/* FSP failed due to a a reset/reload. Only applicable when
+ * hostInterfaces::hbrt_fw_msg::io_type is set to
+ * HBRT_FW_MSG_HBRT_FSP_REQ
+ */
+#define HBRT_RC_FSPDEAD -8193 //0x2001
+
+/********************/
+
+
+/** Common return codes for wakeup(). -0x3000 */
+#define HBRT_RC_RANGE__WAKEUP 0x3000
+
+/* Wakeup was rejected because core was in checkstop statte */
+#define HBRT_RC_WAKEUP_INVALID_ON_CORE_XSTOP -12289 /* -0x3001 */
+
+/********************/
+
+
+/* FSP failed due to a a reset/reload. Only applicable when
+ * hostInterfaces::hbrt_fw_msg::io_type is set to
+ * HBRT_FW_MSG_HBRT_FSP_REQ
+ */
+#define HBRT_RC_FSPDEAD -8193 //0x2001
+
+/********************/
+
+
+
+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")
+ * @param[in] Devtree instance
+ * @return physical address of region (or NULL).
+ * @platform FSP,OpenPOWER
+ */
+ uint64_t (*get_reserved_mem)(const char *name, uint32_t instance);
+
+ /**
+ * @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
+ *
+ * The sleep time must be normalised; i_nano_seconds should be between
+ * 0 and 999999999.
+ *
+ * @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 - number of bytes read, or 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 within the partition
+ * @param[in] i_data: pointer to the data to write
+ * @param[in] i_sizeBytes: size of data to write
+ * @retval rc - number of bytes written, or 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 );
+
+ /**
+ * @brief Query the prd infrastructure for interface capabilities.
+ * @param[in] i_set The set of capabilites to retrieve
+ *
+ * @return a bitmask containing the relevant HBRT_CAPS_* for
+ * this implementation and the specified set.
+ */
+ uint64_t (*get_interface_capabilities)(uint64_t i_set);
+
+ /**
+ * @brief Map a physical address space into usable memory
+ * @note Repeated calls to map the same memory should not return an
+ * error
+ * @param[in] i_physMem Physical address
+ * @param[in] i_bytes Number of bytes to map in
+ * @return NULL on error, else pointer to usable memory
+ * @platform FSP, OpenPOWER
+ */
+ void* (*map_phys_mem)(uint64_t i_physMem, size_t i_bytes);
+
+ /**
+ * @brief Unmap a physical address space from usable memory
+ * @param[in] i_ptr Previously mapped pointer
+ * @return 0 on success, else RC
+ * @platform FSP, OpenPOWER
+ */
+ int (*unmap_phys_mem)(void* i_ptr);
+
+ /**
+ * @brief Modify the SCOM restore section of the HCODE image with the
+ * given register data
+ *
+ * @note The Hypervisor should perform the following actions:
+ * - insert the data into the HCODE image (p9_stop_api)
+ *
+ * @pre HBRT is responsible for enabling special wakeup on the
+ * associated core(s) before calling this interface
+ *
+ * @param i_chipId processor chip ID
+ * plus ID type, always proc (0x0)
+ * @param i_section runtime section to update
+ * (passthru to pore_gen_scom)
+ * @param i_operation type of operation to perform
+ * (passthru to pore_gen_scom)
+ * @param i_scomAddr fully qualified scom address
+ * @param i_scomData data for operation
+ *
+ * @return 0 if the request is successfully received.
+ * Any value other than 0 on failure.
+ * @platform FSP, OpenPOWER
+ */
+ int (*hcode_scom_update)(uint64_t i_chipId,
+ uint32_t i_section,
+ uint32_t i_operation,
+ uint64_t i_scomAddr,
+ uint64_t i_scomData);
+
+ /**
+ * @brief Send a request to firmware, and receive a response
+ * @details
+ * req_len bytes are sent to runtime firmware, and resp_len
+ * bytes received in response.
+ *
+ * Both req and resp are allocated by the caller. If resp_len
+ * is not large enough to contain the full response, an error
+ * is returned.
+ *
+ * @param[in] i_reqLen length of request data
+ * @param[in] i_req request data
+ * @param[inout] o_respLen in: size of request data buffer
+ * out: length of request data
+ * @param[in] o_resp response data
+ * @return 0 on success, else RC
+ * @platform FSP, OpenPOWER
+ */
+ int (*firmware_request)(uint64_t i_reqLen, void *i_req,
+ uint64_t *o_respLen, void *o_resp);
+
+ /* Reserve some space for future growth. */
+ void (*reserved[27])(void);
+};
+
+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);
+
+ /**
+ * @brief Apply a set of attribute overrides
+ *
+ * @param[in] pointer to binary override data
+ * @param[in] length of override data (bytes)
+ * @returns 0 on success, or return code if the command failed
+ *
+ * @platform OpenPower
+ */
+ int (*apply_attr_override)(uint8_t *i_data, size_t size);
+
+ /**
+ * @brief Send a pass-through command to HTMGT
+ *
+ * @details This is a blocking call that will send a command to
+ * HTMGT.
+ *
+ * @note If o_rspLength is returned with a non-zero value, the
+ * data at the o_rspData should be dumped to stdout in a
+ * hex dump format.
+ * @note The maximum response data returned will be 4096 bytes
+ *
+ * @param[in] i_cmdLength number of bytes in pass-thru command data
+ * @param[in] *i_cmdData pointer to pass-thru command data
+ * @param[out] *o_rspLength pointer to number of bytes returned in
+ * o_rspData
+ * @param[out] *o_rspData pointer to a 4096 byte buffer that will
+ * contain the response data from the command
+ *
+ * @returns 0 on success, or return code if the command failed
+ * @platform OpenPower
+ */
+ int (*mfg_htmgt_pass_thru)(uint16_t i_cmdLength, uint8_t *i_cmdData,
+ uint16_t *o_rspLength, uint8_t *o_rspData);
+
+ /**
+ * @brief Execute an arbitrary command inside hostboot runtime
+ * @param[in] Number of arguments (standard C args)
+ * @param[in] Array of argument values (standard C args)
+ * @param[out] Response message (NULL terminated), memory allocated
+ * by hbrt, if o_outString is NULL then no response will
+ * be sent
+ * @return 0 on success, else error code
+ */
+ int (*run_command)(int argc, const char **argv, char **o_outString);
+
+ /**
+ * @brief Verify integrity of a secure container
+ * @param[in] i_pContainer Pointer to a valid secure container,
+ * Must not be NULL. Container is assumed to be stripped of any
+ * ECC and must start with a valid secure header (which contains
+ * the container size information)
+ * @param[in] i_pHwKeyHash Pointer to a valid hardware keys' hash.
+ * Must not be NULL.
+ * @param[in] i_hwKeyHashSize Size of the hardware keys' hash.
+ * A value which incorrectly states the size of the hardware keys'
+ * hash will be detected as a verification error or worse, an
+ * illegal memory access. Must not be 0.
+ * @note If secureboot is compiled out, the function pointer will be
+ * set to NULL. If caller's secureboot support is compiled in and
+ * secureboot is enabled by policy, then caller should treat a NULL
+ * pointer as a verification failure.
+ * @return Integer error code indicating success or failure
+ * @retval 0 Container verified correctly
+ * @retval !0 API error or otherwise failed to verify container
+ * @platform FSP, OpenPOWER
+ */
+ int (*verify_container)(const void *i_pContainer,
+ const void *i_pHwKeyHash,
+ size_t i_hwKeyHashSize);
+
+ /**
+ * @brief SBE message passing
+ *
+ * @details
+ * This is a blocking call that will pass an SBE message
+ * with a pass-through command through HBRT to code that
+ * will process the command and provide a response.
+ *
+ * @param[in] i_procChipId Chip ID of the processor whose SBE is
+ * passing the message and sent the interrupt
+ *
+ * @return 0 on success, or return code if the command failed
+ * @platform FSP, OpenPOWER
+ */
+ int (*sbe_message_passing)(uint32_t i_procChipId);
+
+ /**
+ * @brief Load OCC/HCODE images into mainstore
+ *
+ * @param[in] i_chip the HW chip id (XSCOM chip ID)
+ * @param[in] i_homer_addr the physical mainstore address of the
+ * start of the HOMER image,
+ * @param[in] i_occ_common_addr the physical mainstore address of the
+ * OCC common area, 8MB, used for
+ * OCC-OCC communication (1 per node)
+ * @param[in] i_mode selects initial load vs concurrent
+ * reloads
+ * HBRT_PM_LOAD:
+ * load all lids/sections from scratch,
+ * preserve nothing
+ * HBRT_PM_RELOAD:
+ * reload all lids/sections,
+ * but preserve runtime updates
+ * @return 0 on success else return code
+ * @platform FSP, OpenPOWER
+ */
+ int (*load_pm_complex)(uint64_t i_chip,
+ uint64_t i_homer_addr,
+ uint64_t i_occ_common_addr,
+ uint32_t i_mode);
+
+ /**
+ * @brief Start OCC/HCODE on the specified chip
+ * @param[in] i_chip the HW chip id
+ * @return 0 on success else return code
+ * @platform FSP, OpenPOWER
+ */
+ int (*start_pm_complex)(uint64_t i_chip);
+
+ /**
+ * @brief Reset OCC/HCODE on the specified chip
+ * @param[in] i_chip the HW chip id
+ * @return 0 on success else return code
+ * @platform FSP, OpenPOWER
+ */
+ int (*reset_pm_complex)(uint64_t i_chip);
+
+ /**
+ * @brief Query the IPOLL event mask supported by HBRT
+ *
+ * @details This call allows the wrapper application to query
+ * the ipoll event mask to set when the HBRT instance is running. Bits
+ * that are *set* in this bitmask represent events that will be
+ * forwarded to the handle_attn() callback.
+ *
+ * @return The IPOLL event bits to enable during HBRT execution
+ * @platform FSP, OpenPOWER
+ */
+ uint64_t (*get_ipoll_events)(void);
+
+ /**
+ * @brief Receive an async notification from firmware
+ * @param[in] i_len length of notification data
+ * @param[in] i_data notification data
+ * @platform FSP, OpenPOWER
+ */
+ void (*firmware_notify)(uint64_t len, void *data);
+
+ /**
+ * @brief Prepare for HBRT concurrent code update
+ *
+ * @details This call allows the Host to inform HBRT that a concurrent
+ * code update has been initiated. HBRT then prepares updated targeting
+ * data for use by the updated HBRT code.
+ *
+ * @return 0 on success else return code
+ * @platform FSP
+ */
+ int (*prepare_hbrt_update)( void );
+
+
+ /* Reserve some space for future growth. */
+ void (*reserved[21])(void);
+};
diff --git a/roms/skiboot/external/opal-prd/i2c.c b/roms/skiboot/external/opal-prd/i2c.c
new file mode 100644
index 000000000..f4c7f27a4
--- /dev/null
+++ b/roms/skiboot/external/opal-prd/i2c.c
@@ -0,0 +1,257 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/*
+ * I2C operations for opal-prd
+ *
+ * Copyright 2013-2018 IBM Corp.
+ */
+
+#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 "opal-prd.h"
+#include "module.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) {
+ pr_log(LOG_WARNING, "I2C: Bus %08x/%d/%d not found",
+ chip, eng, port);
+ return -1;
+ }
+ if (bus->fd < 0) {
+ bus->fd = open(bus->devpath, O_RDWR);
+ if (bus->fd < 0) {
+ pr_log(LOG_ERR, "I2C: Failed to open %s: %m",
+ bus->devpath);
+ 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) {
+ pr_log(LOG_ERR, "I2C: Invalid write offset_size %d",
+ 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) {
+ pr_log(LOG_ERR, "I2C: Read error: %m");
+ return -1;
+ }
+ pr_debug("I2C: Read from %08x:%d:%d@%02x+0x%x %d bytes ok",
+ 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) {
+ pr_log(LOG_ERR, "I2C: Invalid write offset_size %d",
+ 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 laid out in BE format.
+ */
+ size = offset_size + length;
+ buf = malloc(size);
+ if (!buf) {
+ pr_log(LOG_ERR, "I2C: Out of memory");
+ 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) {
+ pr_log(LOG_ERR, "I2C: Write error: %m");
+ 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) {
+ pr_log(LOG_ERR, "I2C: Error creating devpath for %s: %m",
+ devname);
+ free(b);
+ 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[PATH_MAX];
+ char busname[256];
+ char *s;
+ FILE *f;
+ unsigned int chip, engine, port;
+
+ /* Ensure i2c-dev is loaded */
+ insert_module("i2c-dev");
+
+ /* Get directory of i2c char devs in sysfs */
+ devsdir = opendir(SYSFS "/class/i2c-dev");
+ if (!devsdir) {
+ pr_log(LOG_ERR, "I2C: Error opening "
+ SYSFS "/class/i2c-dev: %m");
+ 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) {
+ pr_log(LOG_NOTICE, "I2C: Can't open %s: %m, skipping.",
+ dpath);
+ continue;
+ }
+ s = fgets(busname, sizeof(busname), f);
+ fclose(f);
+ if (!s) {
+ pr_log(LOG_NOTICE, "Failed to read %s, skipping.",
+ 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;
+
+ pr_log(LOG_INFO, "I2C: Found Chip: %08x engine %d port %d",
+ chip, engine, port);
+ i2c_add_bus(chip, engine, port, devent->d_name);
+ }
+ closedir(devsdir);
+}
+
diff --git a/roms/skiboot/external/opal-prd/i2c.h b/roms/skiboot/external/opal-prd/i2c.h
new file mode 100644
index 000000000..ee73f906e
--- /dev/null
+++ b/roms/skiboot/external/opal-prd/i2c.h
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/* Copyright 2015 IBM Corp */
+
+#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/roms/skiboot/external/opal-prd/module.c b/roms/skiboot/external/opal-prd/module.c
new file mode 100644
index 000000000..9c9c0af84
--- /dev/null
+++ b/roms/skiboot/external/opal-prd/module.c
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/*
+ * Load kernel modules needed for opal-prd
+ *
+ * Copyright 2015 IBM Corp.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <err.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include "module.h"
+#include "opal-prd.h"
+
+int insert_module(const char *module)
+{
+ int status;
+ pid_t pid;
+
+ pid = fork();
+ if (!pid) {
+ execlp("modprobe", "modprobe", module, NULL);
+ err(EXIT_FAILURE, "Failed to run modprobe");
+ }
+
+ pid = waitpid(pid, &status, 0);
+ if (pid < 0) {
+ pr_log(LOG_ERR, "KMOD: waitpid failed for "
+ "modprobe process: %m");
+ return -1;
+ }
+
+ if (!WIFEXITED(status)) {
+ pr_log(LOG_WARNING, "KMOD: modprobe %s: process didn't "
+ "exit cleanly", module);
+ return -1;
+ }
+
+ if (WEXITSTATUS(status) != 0) {
+ pr_log(LOG_WARNING, "KMOD: modprobe %s failed, status %d",
+ module, WEXITSTATUS(status));
+ return -1;
+ }
+
+ return 0;
+}
+
diff --git a/roms/skiboot/external/opal-prd/module.h b/roms/skiboot/external/opal-prd/module.h
new file mode 100644
index 000000000..2771bf0e8
--- /dev/null
+++ b/roms/skiboot/external/opal-prd/module.h
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/* Copyright 2015 IBM Corp. */
+
+#ifndef MODULES_H
+#define MODULES_H
+
+int insert_module(const char *module);
+
+#endif /* MODULES_H */
+
diff --git a/roms/skiboot/external/opal-prd/opal-prd.8 b/roms/skiboot/external/opal-prd/opal-prd.8
new file mode 100644
index 000000000..72027f5c5
--- /dev/null
+++ b/roms/skiboot/external/opal-prd/opal-prd.8
@@ -0,0 +1,78 @@
+.TH opal-prd 8 ""
+.SH NAME
+opal-prd \- Processor recovery diagnostics daemon for OpenPower hardware
+.SH SYNOPSIS
+.SY opal\-prd
+.OP \-\-debug
+.OP \-\-file <hbrt\-image>
+.OP \-\-pnor <device>
+.OP daemon
+.
+.SY opal\-prd
+.I <command>
+.OP arguments
+.YS
+.SH DESCRIPTION
+\fBopal-prd\fP is a daemon that listens for hardware diagnostic events (by
+listening on the \fI/dev/opal-prd\fP device), and executes firmware-provided
+executable code to handle these events. Only one instance of the daemon
+can be running at a time.
+
+.PP
+If no arguments are provided, or the \fIdaemon\fP command is used, then
+the PRD daemon will be started and will listen for incoming hardware events.
+Generally, this will be run from init as a background service, and not
+be run as a user or with user interaction.
+
+.PP
+\fIopal-prd\fP will log to syslog, using the LOG_DAEMON facility. Messages will
+use the string "opal-prd" for their syslog ident.
+
+.PP
+For debugging, run the daemon with the \fI--debug\fP and \fI--stdio\fP
+options. This will log to stdout (instead of syslog), and enable extra
+debugging information.
+
+.PP
+A running opal-prd daemon will also listen for control messages from
+the user; these are sent using the same \fIopal-prd\fP executable, run
+with the <command> argument:
+
+.RS
+ opal-prd <command> [arguments]
+.RE
+
+.PP
+Note that the daemon must be running in the background here, as a separate
+process.
+
+.PP
+Currently, there's one command available, 'occ', for controlling the
+on-chip-controllers. That has 3 possible sub-commands: \fIreset\fP,
+\fIenable\fP, and \fIdisable\fP.
+
+.SH OPTIONS
+.TP
+\fB\-\-debug\fR
+verbose logging for debug information
+.TP
+\fB\-\-pnor\fR DEVICE
+use PNOR MTD device
+.TP
+\fB\-\-file\fR FILE
+use FILE for hostboot runtime code (instead of code
+exported by firmware)
+.TP
+\fB\-\-stdio\fR
+log to stdio, instead of syslog
+
+.SH FILES
+.PD 0
+.B /dev/opal-prd
+.br
+.B /run/opal-prd-control
+.br
+.PD
+
+.SH "SEE ALSO"
+syslog(3)
diff --git a/roms/skiboot/external/opal-prd/opal-prd.c b/roms/skiboot/external/opal-prd/opal-prd.c
new file mode 100644
index 000000000..1c610da4c
--- /dev/null
+++ b/roms/skiboot/external/opal-prd/opal-prd.c
@@ -0,0 +1,2799 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/*
+ * OPAL Processor Runtime Diagnostics (PRD)
+ * Runs Hostboot RunTime (HBRT) code in a userspace wrapper
+ *
+ * Firmware in userspace? Brilliant!
+ *
+ * Copyright 2014-2019 IBM Corp.
+ */
+
+#define _GNU_SOURCE
+
+#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 <poll.h>
+#include <signal.h>
+#include <dirent.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-api.h>
+#include <types.h>
+
+#include <ccan/list/list.h>
+
+#include "opal-prd.h"
+#include "hostboot-interface.h"
+#include "module.h"
+#include "pnor.h"
+#include "i2c.h"
+
+struct prd_range {
+ const char *name;
+ uint64_t physaddr;
+ uint64_t size;
+ void *buf;
+ bool multiple;
+ uint32_t instance;
+};
+
+struct prd_msgq_item {
+ struct list_node list;
+ struct opal_prd_msg msg;
+};
+
+struct opal_prd_ctx {
+ int fd;
+ int socket;
+ struct opal_prd_info info;
+ struct prd_range *ranges;
+ int n_ranges;
+ bool fw_range_instances;
+ long page_size;
+ void *code_addr;
+ size_t code_size;
+ bool debug;
+ struct pnor pnor;
+ char *hbrt_file_name;
+ bool use_syslog;
+ bool expert_mode;
+ struct list_head msgq;
+ struct opal_prd_msg *msg;
+ size_t msg_alloc_len;
+ void (*vlog)(int, const char *, va_list);
+};
+
+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,
+ CONTROL_MSG_ATTR_OVERRIDE = 0x04,
+ CONTROL_MSG_HTMGT_PASSTHRU = 0x05,
+ CONTROL_MSG_RUN_CMD = 0x30,
+};
+
+struct control_msg {
+ enum control_msg_type type;
+ int response;
+ union {
+ struct {
+ unsigned int argc;
+ } run_cmd;
+ struct {
+ uint64_t chip;
+ } occ_reset;
+ struct {
+ uint64_t chip;
+ } occ_error;
+ };
+ unsigned int data_len;
+ unsigned char data[];
+
+};
+
+#define MAX_CONTROL_MSG_BUF 4096
+
+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 = "hbrt-code-image";
+static const char *hbrt_code_region_name_ibm = "ibm,hbrt-code-image";
+static const int opal_prd_version = 1;
+static uint64_t opal_prd_ipoll = 0xf000000000000000;
+
+static const int max_msgq_len = 16;
+
+static const char *ipmi_devnode = "/dev/ipmi0";
+static const int ipmi_timeout_ms = 5000;
+
+static const char *devicetree_base =
+ "/sys/firmware/devicetree/base";
+
+/* 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 int nr_chips;
+static u64 chips[256];
+
+static int read_prd_msg(struct opal_prd_ctx *ctx);
+
+static struct prd_range *find_range(const char *name, uint32_t instance)
+{
+ struct prd_range *range;
+ unsigned int i;
+
+ for (i = 0; i < ctx->n_ranges; i++) {
+ range = &ctx->ranges[i];
+
+ if (strcmp(range->name, name))
+ continue;
+
+ if (range->multiple && range->instance != instance)
+ continue;
+
+ return range;
+ }
+
+ return NULL;
+}
+
+static void pr_log_stdio(int priority, const char *fmt, va_list ap)
+{
+ if (!ctx->debug && priority >= LOG_DEBUG)
+ return;
+
+ vprintf(fmt, ap);
+ printf("\n");
+
+ if (ctx->debug)
+ fflush(stdout);
+}
+
+/* standard logging prefixes:
+ * HBRT: Messages from hostboot runtime code
+ * FW: Interactions with OPAL firmware
+ * IMAGE: HBRT image loading
+ * MEM: Memory failure interface
+ * SCOM: Chip SCOM interface
+ * IPMI: IPMI interface
+ * PNOR: PNOR interface
+ * I2C: i2c interface
+ * PM: PM/OCC interface
+ * CTRL: User-triggered control events
+ * KMOD: Kernel module functions
+ */
+
+void pr_log(int priority, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ ctx->vlog(priority, fmt, ap);
+ va_end(ap);
+}
+
+static void pr_log_nocall(const char *name)
+{
+ pr_log(LOG_WARNING, "HBRT: Call %s not provided", name);
+}
+
+static void hexdump(const uint8_t *data, uint32_t len)
+{
+ int i;
+
+ for (i = 0; i < len; i++) {
+ if (i % 16 == 0)
+ printf("\n");
+ else if(i % 4 == 0)
+ printf(" ");
+
+ printf("%02x", data[i]);
+ }
+
+ printf("\n");
+}
+
+static void pr_log_daemon_init(void)
+{
+ if (ctx->use_syslog) {
+ openlog("opal-prd", LOG_NDELAY, LOG_DAEMON);
+ ctx->vlog = vsyslog;
+ }
+}
+
+/* Check service processor type */
+static bool is_fsp_system(void)
+{
+ bool fsp_system = true;
+ char *path;
+ int rc;
+
+ rc = asprintf(&path, "%s/fsps", devicetree_base);
+ if (rc < 0) {
+ pr_log(LOG_ERR, "FW: error creating '/fsps' path %m");
+ return false;
+ }
+
+ if (access(path, F_OK))
+ fsp_system = false;
+
+ free(path);
+ return fsp_system;
+}
+
+/**
+ * ABI check that we can't perform at build-time: we want to ensure that the
+ * layout of struct host_interfaces matches that defined in the thunk.
+ */
+static void check_abi(void)
+{
+ extern unsigned char __hinterface_start, __hinterface_pad,
+ __hinterface_end;
+
+ /* ensure our struct size matches the thunk definition */
+ assert((&__hinterface_end - &__hinterface_start)
+ == sizeof(struct host_interfaces));
+
+ /* ensure the padding layout is as expected */
+ assert((void *)&__hinterface_start == (void *)&hinterface);
+ assert((void *)&__hinterface_pad == (void *)&hinterface.reserved);
+}
+
+/* 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);
+extern int call_mfg_htmgt_pass_thru(uint16_t i_cmdLength, uint8_t *i_cmdData,
+ uint16_t *o_rspLength, uint8_t *o_rspData);
+extern int call_apply_attr_override(uint8_t *i_data, size_t size);
+extern int call_run_command(int argc, const char **argv, char **o_outString);
+extern int call_sbe_message_passing(uint32_t i_chipId);
+extern uint64_t call_get_ipoll_events(void);
+extern int call_firmware_notify(uint64_t len, void *data);
+extern int call_reset_pm_complex(uint64_t chip);
+extern int call_load_pm_complex(u64 chip, u64 homer, u64 occ_common, u32 mode);
+extern int call_start_pm_complex(u64 chip);
+
+void hservice_puts(const char *str)
+{
+ int priority = LOG_INFO;
+
+ /* Interpret the 2-character ERR_MRK/FAIL_MRK/WARN_MRK prefixes that
+ * may be present on HBRT log messages, and bump the log priority as
+ * appropriate.
+ */
+ if (strlen(str) >= 2 && str[1] == '>') {
+ switch (str[0]) {
+ case 'E':
+ case 'F':
+ priority = LOG_ERR;
+ break;
+ case 'W':
+ priority = LOG_WARNING;
+ break;
+ }
+ }
+
+ pr_log(priority, "HBRT: %s", str);
+}
+
+void hservice_assert(void)
+{
+ pr_log(LOG_ERR, "HBRT: Failed assertion! exiting.");
+ 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) {
+ pr_log(LOG_ERR, "SCOM: ioctl read(chip 0x%lx, addr 0x%lx) "
+ "failed: %m", chip_id, addr);
+ return 0;
+ }
+ rc = (int)scom.rc;
+
+ pr_debug("SCOM: read: chip 0x%lx, addr 0x%lx, val 0x%lx, rc %d",
+ chip_id, addr, scom.data, rc);
+
+ *(uint64_t *)buf = htobe64(scom.data);
+
+ return rc;
+}
+
+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) {
+ pr_log(LOG_ERR, "SCOM: ioctl write(chip 0x%lx, addr 0x%lx) "
+ "failed: %m", chip_id, addr);
+ return 0;
+ }
+ rc = (int)scom.rc;
+
+ pr_debug("SCOM: write: chip 0x%lx, addr 0x%lx, val 0x%lx, rc %d",
+ chip_id, addr, scom.data, rc);
+
+ return rc;
+}
+
+uint64_t hservice_get_reserved_mem(const char *name, uint32_t instance)
+{
+ struct prd_range *range;
+
+ pr_debug("IMAGE: hservice_get_reserved_mem: %s, %d", name, instance);
+
+ range = find_range(name, instance);
+ if (!range) {
+ pr_log(LOG_WARNING, "IMAGE: get_reserved_mem: "
+ "no such range %s", name);
+ return 0;
+ }
+
+ if (!range->buf) {
+ uint64_t align_physaddr, offset;
+
+ pr_debug("IMAGE: Mapping 0x%016lx 0x%08lx %s[%d]",
+ range->physaddr, range->size,
+ range->name, range->instance);
+
+ align_physaddr = range->physaddr & ~(ctx->page_size-1);
+ offset = range->physaddr & (ctx->page_size-1);
+ range->buf = mmap(NULL, range->size, PROT_WRITE | PROT_READ,
+ MAP_SHARED, ctx->fd, align_physaddr);
+
+ if (range->buf == MAP_FAILED)
+ pr_log(LOG_ERR,
+ "IMAGE: mmap of %s[%d](0x%016lx) failed: %m",
+ name, instance, range->physaddr);
+ else
+ range->buf += offset;
+ }
+
+ if (range->buf == MAP_FAILED) {
+ pr_log(LOG_WARNING,
+ "IMAGE: get_reserved_mem: %s[%d] has no vaddr",
+ name, instance);
+ return 0;
+ }
+
+ pr_debug(
+ "IMAGE: hservice_get_reserved_mem: %s[%d](0x%016lx) address %p",
+ name, range->instance, range->physaddr,
+ range->buf);
+
+ return (uint64_t)range->buf;
+}
+
+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)
+{
+ /* HBRT calls this on the pages that are already being executed,
+ * nothing to do here */
+ 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, 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, 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);
+}
+
+int hservice_wakeup(u32 core, u32 mode)
+{
+ struct opal_prd_msg msg;
+
+ msg.hdr.type = OPAL_PRD_MSG_TYPE_CORE_SPECIAL_WAKEUP;
+ msg.hdr.size = htobe16(sizeof(msg));
+ msg.spl_wakeup.core = htobe32(core);
+ msg.spl_wakeup.mode = htobe32(mode);
+
+ if (write(ctx->fd, &msg, sizeof(msg)) != sizeof(msg)) {
+ pr_log(LOG_ERR, "FW: Failed to send CORE_SPECIAL_WAKEUP msg %x : %m\n",
+ core);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void pnor_load_module(struct opal_prd_ctx *ctx)
+{
+ insert_module("powernv_flash");
+}
+
+static void ipmi_init(struct opal_prd_ctx *ctx)
+{
+ insert_module("ipmi_devintf");
+}
+
+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) {
+ pr_log(LOG_WARNING, "IPMI: recv (%zd bytes) failed: %m", *len);
+ return -1;
+ } else if (rc < 0 && errno == EMSGSIZE) {
+ pr_log(LOG_NOTICE, "IPMI: truncated message (netfn %d, cmd %d, "
+ "size %zd), continuing anyway",
+ 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) {
+ pr_log(LOG_WARNING, "IPMI: Failed to open IPMI device %s: %m",
+ ipmi_devnode);
+ return -1;
+ }
+
+ seq++;
+ pr_debug("IPMI: sending %zd bytes (netfn 0x%02x, cmd 0x%02x)",
+ tx_size, netfn, cmd);
+
+ rc = ipmi_send(fd, netfn, cmd, seq, tx_buf, tx_size);
+ if (rc) {
+ pr_log(LOG_WARNING, "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) {
+ pr_log(LOG_ERR, "IPMI: poll(%s) failed: %m",
+ ipmi_devnode);
+ break;
+ }
+
+ if (rc == 0) {
+ pr_log(LOG_WARNING, "IPMI: response timeout (>%dms)",
+ ipmi_timeout_ms);
+ rc = -1;
+ break;
+ }
+
+ rc = ipmi_recv(fd, &netfn, &cmd, &rx_seq, rx_buf, &size);
+ if (rc)
+ break;
+
+ if (seq != rx_seq) {
+ pr_log(LOG_NOTICE, "IPMI: out-of-sequence reply: %ld, "
+ "expected %ld. Dropping message.",
+ rx_seq, seq);
+ continue;
+ }
+
+ pr_debug("IPMI: received %zd bytes", tx_size);
+ *rx_size = be64toh(size);
+ rc = 0;
+ break;
+ }
+
+out:
+ close(fd);
+ return rc;
+}
+
+static int memory_error_worker(const char *sysfsfile, const char *type,
+ uint64_t i_start_addr, uint64_t i_endAddr)
+{
+ int memfd, rc, n, ret = 0;
+ char buf[ADDR_STRING_SZ];
+ uint64_t addr;
+
+ memfd = open(sysfsfile, O_WRONLY);
+ if (memfd < 0) {
+ pr_log(LOG_CRIT, "MEM: Failed to offline memory! "
+ "Unable to open sysfs node %s: %m", 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) {
+ pr_log(LOG_CRIT, "MEM: Failed to offline memory! "
+ "page addr: %016lx type: %s: %m",
+ addr, type);
+ ret = 1;
+ }
+ }
+ pr_log(LOG_CRIT, "MEM: Offlined %016lx,%016lx, type %s: %m\n",
+ i_start_addr, addr, type);
+
+ close(memfd);
+ return ret;
+}
+
+int hservice_memory_error(uint64_t i_start_addr, uint64_t i_endAddr,
+ enum MemoryError_t i_errorType)
+{
+ const char *sysfsfile, *typestr;
+ pid_t pid;
+
+ switch(i_errorType) {
+ case MEMORY_ERROR_CE:
+ sysfsfile = mem_offline_soft;
+ typestr = "correctable";
+ break;
+ case MEMORY_ERROR_UE:
+ sysfsfile = mem_offline_hard;
+ typestr = "uncorrectable";
+ break;
+ default:
+ pr_log(LOG_WARNING, "MEM: Invalid memory error type %d",
+ i_errorType);
+ return -1;
+ }
+
+ pr_log(LOG_ERR, "MEM: Memory error: range %016lx-%016lx, type: %s",
+ i_start_addr, i_endAddr, typestr);
+
+ /*
+ * HBRT expects the memory offlining process to happen in the background
+ * after the notification is delivered.
+ */
+ pid = fork();
+ if (pid > 0)
+ exit(memory_error_worker(sysfsfile, typestr, i_start_addr, i_endAddr));
+
+ if (pid < 0) {
+ perror("MEM: unable to fork worker to offline memory!\n");
+ return -1;
+ }
+
+ pr_log(LOG_INFO, "MEM: forked off %d to handle mem error\n", pid);
+ return 0;
+}
+
+uint64_t hservice_get_interface_capabilities(uint64_t set)
+{
+ if (set == HBRT_CAPS_SET1_OPAL)
+ return HBRT_CAPS_OPAL_HAS_XSCOM_RC ||
+ HBRT_CAPS_OPAL_HAS_WAKEUP_SUPPORT;
+
+ return 0;
+}
+
+uint64_t hservice_firmware_request(uint64_t req_len, void *req,
+ uint64_t *resp_lenp, void *resp)
+{
+ struct opal_prd_msg *msg = ctx->msg;
+ uint64_t resp_len;
+ size_t size;
+ int rc, n;
+
+ resp_len = be64_to_cpu(*resp_lenp);
+
+ pr_log(LOG_DEBUG,
+ "HBRT: firmware request: %lu bytes req, %lu bytes resp",
+ req_len, resp_len);
+
+ /* sanity check for potential overflows */
+ if (req_len > 0xffff || resp_len > 0xffff)
+ return -1;
+
+ size = sizeof(msg->hdr) + sizeof(msg->token) +
+ sizeof(msg->fw_req) + req_len;
+
+ /* we need the entire message to fit within the 2-byte size field */
+ if (size > 0xffff)
+ return -1;
+
+ /* variable sized message, so we may need to expand our buffer */
+ if (size > ctx->msg_alloc_len) {
+ msg = realloc(ctx->msg, size);
+ if (!msg) {
+ pr_log(LOG_ERR,
+ "FW: failed to expand message buffer: %m");
+ return -1;
+ }
+ ctx->msg = msg;
+ ctx->msg_alloc_len = size;
+ }
+
+ memset(msg, 0, size);
+
+ /* construct request message... */
+ msg->hdr.type = OPAL_PRD_MSG_TYPE_FIRMWARE_REQUEST;
+ msg->hdr.size = htobe16(size);
+ msg->fw_req.req_len = htobe64(req_len);
+ msg->fw_req.resp_len = htobe64(resp_len);
+ memcpy(msg->fw_req.data, req, req_len);
+
+ hexdump((void *)msg, size);
+
+ /* ... and send to firmware */
+ rc = write(ctx->fd, msg, size);
+ if (rc != size) {
+ pr_log(LOG_WARNING,
+ "FW: Failed to send FIRMWARE_REQUEST message: %m");
+ return -1;
+ }
+
+ /* We have an "inner" poll loop here, as we want to ensure that the
+ * next entry into HBRT is the return from this function. So, only
+ * read from the prd fd, and queue anything that isn't a response
+ * to this request
+ */
+ n = 0;
+ for (;;) {
+ struct prd_msgq_item *item;
+
+ rc = read_prd_msg(ctx);
+ if (rc)
+ return -1;
+
+ msg = ctx->msg;
+ if (msg->hdr.type == OPAL_PRD_MSG_TYPE_FIRMWARE_RESPONSE) {
+ size = be64toh(msg->fw_resp.len);
+ if (size > resp_len)
+ return -1;
+
+ /* success! a valid response that fits into HBRT's
+ * resp buffer */
+ memcpy(resp, msg->fw_resp.data, size);
+ *resp_lenp = htobe64(size);
+ return 0;
+ }
+
+ /* not a response? queue up for later consumption */
+ if (++n > max_msgq_len) {
+ pr_log(LOG_ERR,
+ "FW: too many messages queued (%d) while "
+ "waiting for FIRMWARE_RESPONSE", n);
+ return -1;
+ }
+ size = be16toh(msg->hdr.size);
+ item = malloc(sizeof(*item) + size);
+ memcpy(&item->msg, msg, size);
+ list_add_tail(&ctx->msgq, &item->list);
+ }
+}
+
+int hservices_init(struct opal_prd_ctx *ctx, void *code)
+{
+ uint64_t *s, *d;
+ int i, sz;
+
+ pr_debug("IMAGE: code address: %p", 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) {
+ pr_log(LOG_ERR, "IMAGE: Bad signature for "
+ "ibm,hbrt-code-image! exiting");
+ return -1;
+ }
+
+ pr_debug("IMAGE: calling ibm,hbrt_init()");
+ hservice_runtime = call_hbrt_init(&hinterface);
+ if (!hservice_runtime) {
+ pr_log(LOG_ERR, "IMAGE: hbrt_init failed, exiting");
+ return -1;
+ }
+
+ pr_log(LOG_NOTICE, "IMAGE: hbrt_init complete, version %016lx",
+ 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]);
+
+ return 0;
+}
+
+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) {
+ pr_log(LOG_ERR, "IMAGE: HBRT file open(%s) failed: %m", name);
+ return -1;
+ }
+
+ rc = fstat(fd, &statbuf);
+ if (rc < 0) {
+ pr_log(LOG_ERR, "IMAGE: HBRT file fstat(%s) failed: %m", 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) {
+ pr_log(LOG_ERR, "IMAGE: HBRT file mmap(%s, 0x%zx) failed: %m",
+ name, statbuf.st_size);
+ 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 prd_range *range;
+ int rc;
+ void *buf;
+ void *ro_buf;
+
+ range = find_range(name, 0);
+ if (!range) {
+ pr_log(LOG_ERR, "IMAGE: can't find code region %s", name);
+ return -1;
+ }
+
+ ro_buf = mmap(NULL, range->size, PROT_READ,
+ MAP_PRIVATE, ctx->fd, range->physaddr);
+ if (ro_buf == MAP_FAILED) {
+ pr_log(LOG_ERR, "IMAGE: mmap(range:%s, "
+ "phys:0x%016lx, size:0x%016lx) failed: %m",
+ name, range->physaddr, range->size);
+ return -1;
+ }
+
+ buf = mmap(NULL, range->size, PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_ANONYMOUS, -1 , 0);
+ if (buf == MAP_FAILED) {
+ pr_log(LOG_ERR, "IMAGE: anon mmap(size:0x%016lx) failed: %m",
+ range->size);
+ return -1;
+ }
+
+ memcpy(buf, ro_buf, range->size);
+
+ rc = munmap(ro_buf, range->size);
+ if (rc < 0) {
+ pr_log(LOG_ERR, "IMAGE: munmap("
+ "phys:0x%016lx, size:0x%016lx) failed: %m",
+ range->physaddr, range->size);
+ return -1;
+ }
+
+ /*
+ * FIXME: We shouldn't be mapping the memory as RWX, but HBRT appears to
+ * require the ability to write into the image at runtime.
+ */
+ rc = mprotect(buf, range->size, PROT_READ | PROT_WRITE | PROT_EXEC);
+ if (rc < 0) {
+ pr_log(LOG_ERR, "IMAGE: mprotect(phys:%p, "
+ "size:0x%016lx, rwx) failed: %m",
+ buf, range->size);
+ 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) {
+ pr_log(LOG_NOTICE, "IMAGE: couldn't debug image %s for writing",
+ dump_name);
+ return;
+ }
+
+ rc = ftruncate(fd, 0);
+ if (rc < 0) {
+ pr_log(LOG_NOTICE, "IMAGE: couldn't truncate image %s for writing",
+ dump_name);
+ return;
+ }
+ rc = write(fd, ctx->code_addr, ctx->code_size);
+ close(fd);
+
+ if (rc != ctx->code_size)
+ pr_log(LOG_NOTICE, "IMAGE: write to %s failed: %m", dump_name);
+ else
+ pr_debug("IMAGE: dumped HBRT binary to %s", dump_name);
+}
+
+static int open_and_read(const char *path, void **bufp, int *lenp)
+{
+ struct stat statbuf;
+ int fd, rc, bytes;
+ void *buf;
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ return -1;
+
+ rc = fstat(fd, &statbuf);
+ if (rc) {
+ close(fd);
+ return -1;
+ }
+
+ buf = malloc(statbuf.st_size);
+ if (!buf) {
+ close(fd);
+ return -1;
+ }
+
+ for (rc = bytes = 0; bytes < statbuf.st_size; bytes += rc) {
+ rc = read(fd, buf + bytes, statbuf.st_size - bytes);
+ if (rc < 0) {
+ if (errno == EINTR)
+ continue;
+ break;
+ } else if (rc == 0)
+ break;
+ }
+
+ if (bytes == statbuf.st_size)
+ rc = 0;
+
+ if (rc == 0) {
+ if (lenp)
+ *lenp = bytes;
+ if (bufp)
+ *bufp = buf;
+ } else {
+ free(buf);
+ }
+
+ close(fd);
+
+ return rc == 0 ? 0 : -1;
+}
+
+static int prd_init_one_range(struct opal_prd_ctx *ctx, const char *path,
+ struct dirent *dirent)
+{
+ char *label_path, *reg_path, *instance_path;
+ struct prd_range *range;
+ int label_len, len, rc;
+ __be64 *reg;
+ char *label;
+ void *buf;
+
+ rc = asprintf(&label_path, "%s/%s/ibm,prd-label", path, dirent->d_name);
+ if (rc < 0) {
+ pr_log(LOG_ERR, "FW: error creating 'ibm,prd-label' path "
+ "node: %m");
+ return -1;
+ }
+ rc = asprintf(&instance_path, "%s/%s/ibm,prd-instance",
+ path, dirent->d_name);
+ if (rc < 0) {
+ pr_log(LOG_ERR, "FW: error creating 'ibm,prd-instance' path "
+ "node: %m");
+ return -1;
+ }
+ rc = asprintf(&reg_path, "%s/%s/reg", path, dirent->d_name);
+ if (rc < 0) {
+ pr_log(LOG_ERR, "FW: error creating 'reg' path "
+ " node: %m");
+ return -1;
+ }
+
+ reg = NULL;
+ label = NULL;
+ rc = -1;
+
+ rc = open_and_read(label_path, &buf, &label_len);
+ if (rc)
+ goto out_free;
+
+ label = buf;
+
+ if (label[label_len-1] != '\0')
+ pr_log(LOG_INFO, "FW: node %s has invalid ibm,prd-label - "
+ "not nul-terminated",
+ dirent->d_name);
+
+ rc = open_and_read(reg_path, &buf, &len);
+ if (rc)
+ goto out_free;
+
+ reg = buf;
+
+ if (len != 2 * sizeof(*reg)) {
+ pr_log(LOG_ERR, "FW: node %s has invalid 'reg' size: %d",
+ dirent->d_name, len);
+ goto out_free;
+ }
+
+
+ ctx->ranges = realloc(ctx->ranges, ++ctx->n_ranges * sizeof(*range));
+ range = &ctx->ranges[ctx->n_ranges - 1];
+ range->name = strndup(label, label_len);
+ range->physaddr = be64toh(reg[0]);
+ range->size = be64toh(reg[1]);
+ range->buf = NULL;
+ range->multiple = false;
+ range->instance = 0;
+
+ /* optional instance */
+ rc = open_and_read(instance_path, &buf, &len);
+ if (!rc && len == sizeof(uint32_t)) {
+ range->multiple = true;
+ range->instance = be32toh(*(uint32_t *)buf);
+ ctx->fw_range_instances = true;
+ }
+ rc = 0;
+
+out_free:
+ free(reg);
+ free(label);
+ free(instance_path);
+ free(reg_path);
+ free(label_path);
+ return rc;
+}
+
+static int compare_ranges(const void *ap, const void *bp)
+{
+ const struct prd_range *a = ap, *b = bp;
+ int rc;
+
+ rc = strcmp(a->name, b->name);
+ if (rc)
+ return rc;
+
+ if (a->physaddr < b->physaddr)
+ return -1;
+ else if (a->physaddr > b->physaddr)
+ return 1;
+
+ return 0;
+}
+
+static void assign_range_instances(struct opal_prd_ctx *ctx)
+{
+ int i;
+
+ if (!ctx->n_ranges)
+ return;
+
+ ctx->ranges[0].multiple = false;
+ ctx->ranges[0].instance = 0;
+
+ for (i = 1; i < ctx->n_ranges; i++) {
+ struct prd_range *cur, *prev;
+
+ cur = &ctx->ranges[i];
+ prev = &ctx->ranges[i-1];
+
+ if (!strcmp(cur->name, prev->name)) {
+ prev->multiple = true;
+ cur->multiple = true;
+ cur->instance = prev->instance + 1;
+ } else {
+ cur->multiple = false;
+ cur->instance = 0;
+ }
+ }
+}
+
+static void print_ranges(struct opal_prd_ctx *ctx)
+{
+ int i;
+
+ if (ctx->n_ranges == 0)
+ pr_log(LOG_INFO, "FW: No PRD ranges");
+
+ pr_log(LOG_DEBUG, "FW: %d PRD ranges, instances assigned by %s",
+ ctx->n_ranges,
+ ctx->fw_range_instances ? "firmware" : "userspace");
+
+ for (i = 0; i < ctx->n_ranges; i++) {
+ struct prd_range *range = &ctx->ranges[i];
+ char instance_str[20];
+
+ if (range->multiple)
+ snprintf(instance_str, sizeof(instance_str),
+ " [%d]", range->instance);
+ else
+ instance_str[0] = '\0';
+
+ pr_log(LOG_DEBUG, "FW: %016lx-%016lx %s%s", range->physaddr,
+ range->physaddr + range->size - 1,
+ range->name,
+ instance_str);
+ }
+}
+
+static int chip_init(void)
+{
+ struct dirent *dirent;
+ char *path;
+ DIR *dir;
+ __be32 *chipid;
+ void *buf;
+ int rc, len, i;
+
+ dir = opendir(devicetree_base);
+ if (!dir) {
+ pr_log(LOG_ERR, "FW: Can't open %s", devicetree_base);
+ return -1;
+ }
+
+ for (;;) {
+ dirent = readdir(dir);
+ if (!dirent)
+ break;
+
+ if (strncmp("xscom", dirent->d_name, 5))
+ continue;
+
+ rc = asprintf(&path, "%s/%s/ibm,chip-id", devicetree_base,
+ dirent->d_name);
+ if (rc < 0) {
+ pr_log(LOG_ERR, "FW: Failed to create chip-id path");
+ return -1;
+ }
+
+ rc = open_and_read(path, &buf, &len);
+ if (rc) {
+ pr_log(LOG_ERR, "FW; Failed to read chipid");
+ return -1;
+ }
+ chipid = buf;
+ chips[nr_chips++] = be32toh(*chipid);
+ }
+
+ pr_log(LOG_DEBUG, "FW: Chip init");
+ for (i = 0; i < nr_chips; i++)
+ pr_log(LOG_DEBUG, "FW: Chip 0x%lx", chips[i]);
+
+ return 0;
+}
+
+static int prd_init_ranges(struct opal_prd_ctx *ctx)
+{
+ struct dirent *dirent;
+ char *path;
+ DIR *dir;
+ int rc;
+
+ rc = asprintf(&path, "%s/reserved-memory", devicetree_base);
+ if (rc < 0) {
+ pr_log(LOG_ERR, "FW: error creating 'reserved-memory' path "
+ "node: %m");
+ return -1;
+ }
+
+ rc = -1;
+
+ dir = opendir(path);
+ if (!dir) {
+ pr_log(LOG_ERR, "FW: can't open reserved-memory device-tree "
+ "node: %m");
+ goto out_free;
+ }
+
+ for (;;) {
+ dirent = readdir(dir);
+ if (!dirent)
+ break;
+
+ prd_init_one_range(ctx, path, dirent);
+ }
+
+ rc = 0;
+ /* sort ranges and assign instance numbers for duplicates (if the
+ * firmware doesn't number instances for us) */
+ qsort(ctx->ranges, ctx->n_ranges, sizeof(struct prd_range),
+ compare_ranges);
+
+ if (!ctx->fw_range_instances)
+ assign_range_instances(ctx);
+
+ print_ranges(ctx);
+
+out_free:
+ free(path);
+ closedir(dir);
+ return rc;
+}
+
+bool find_string(const char *buffer, size_t len, const char *s)
+{
+ const char *c, *end;
+
+ if (!buffer)
+ return false;
+ c = buffer;
+ end = c + len;
+
+ while (c < end) {
+ if (!strcasecmp(s, c))
+ return true;
+ c += strlen(c) + 1;
+ }
+ return false;
+}
+
+static int is_prd_supported(void)
+{
+ char *path;
+ int rc;
+ int len;
+ char *buf;
+
+ rc = asprintf(&path, "%s/ibm,opal/diagnostics/compatible",
+ devicetree_base);
+ if (rc < 0) {
+ pr_log(LOG_ERR, "FW: error creating 'compatible' node path: %m");
+ return -1;
+ }
+
+ rc = open_and_read(path, (void *) &buf, &len);
+ if (rc)
+ goto out_free;
+
+ if (buf[len - 1] != '\0')
+ pr_log(LOG_INFO, "FW: node %s is not nul-terminated", path);
+
+ rc = find_string(buf, len, "ibm,opal-prd") ? 0 : -1;
+
+ free(buf);
+out_free:
+ free(path);
+ return rc;
+}
+
+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) {
+ pr_log(LOG_ERR, "FW: Can't open PRD device %s: %m",
+ opal_prd_devnode);
+ return -1;
+ }
+
+ rc = ioctl(ctx->fd, OPAL_PRD_GET_INFO, &ctx->info);
+ if (rc) {
+ pr_log(LOG_ERR, "FW: Can't query PRD information: %m");
+ return -1;
+ }
+
+ rc = prd_init_ranges(ctx);
+ if (rc) {
+ pr_log(LOG_ERR, "FW: can't parse PRD memory information");
+ return -1;
+ }
+
+ rc = chip_init();
+ if (rc)
+ pr_log(LOG_ERR, "FW: Failed to initialize chip IDs");
+
+ 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) {
+ pr_log_nocall("handle_attns");
+ return -1;
+ }
+
+ rc = call_handle_attns(proc, ipoll_status, ipoll_mask);
+ if (rc) {
+ pr_log(LOG_ERR, "HBRT: handle_attns(%lx,%lx,%lx) failed, rc %d",
+ proc, ipoll_status, ipoll_mask, rc);
+ return -1;
+ }
+
+ /* send the response */
+ msg->hdr.type = OPAL_PRD_MSG_TYPE_ATTN_ACK;
+ msg->hdr.size = htobe16(sizeof(*msg));
+ 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)) {
+ pr_log(LOG_WARNING, "FW: Failed to send ATTN_ACK message: %m");
+ 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);
+
+ pr_debug("FW: firmware signaled OCC error for proc 0x%x", proc);
+
+ if (!hservice_runtime->process_occ_error) {
+ pr_log_nocall("process_occ_error");
+ return -1;
+ }
+
+ call_process_occ_error(proc);
+ return 0;
+}
+
+static int pm_complex_load_start(void)
+{
+ struct prd_range *range;
+ u64 homer, occ_common;
+ int rc = -1, i;
+
+ if (!hservice_runtime->load_pm_complex) {
+ pr_log_nocall("load_pm_complex");
+ return rc;
+ }
+
+ if (!hservice_runtime->start_pm_complex) {
+ pr_log_nocall("start_pm_complex");
+ return rc;
+ }
+
+ range = find_range("ibm,occ-common-area", 0);
+ if (!range) {
+ range = find_range("occ-common-area", 0);
+ if (!range) {
+ pr_log(LOG_ERR, "PM: occ-common-area not found");
+ return rc;
+ }
+ }
+ occ_common = range->physaddr;
+
+ for (i = 0; i < nr_chips; i++) {
+ range = find_range("ibm,homer-image", chips[i]);
+ if (!range) {
+ range = find_range("homer-image", chips[i]);
+ if (!range) {
+ pr_log(LOG_ERR, "PM: homer-image not found 0x%lx",
+ chips[i]);
+ return -1;
+ }
+ }
+ homer = range->physaddr;
+
+ pr_debug("PM: calling load_pm_complex(0x%lx, 0x%lx, 0x%lx, LOAD)",
+ chips[i], homer, occ_common);
+ rc = call_load_pm_complex(chips[i], homer, occ_common, 0);
+ if (rc) {
+ pr_log(LOG_ERR, "PM: Failed load_pm_complex(0x%lx) %m",
+ chips[i]);
+ return rc;
+ }
+ }
+
+ for (i = 0; i < nr_chips; i++) {
+ pr_debug("PM: calling start_pm_complex(0x%lx)", chips[i]);
+ rc = call_start_pm_complex(chips[i]);
+ if (rc) {
+ pr_log(LOG_ERR, "PM: Failed start_pm_complex(0x%lx): %m",
+ chips[i]);
+ return rc;
+ }
+ }
+
+ return rc;
+}
+
+static int pm_complex_reset(uint64_t chip)
+{
+ int rc;
+
+ /*
+ * FSP system -> reset_pm_complex
+ * BMC system -> process_occ_reset
+ */
+ if (is_fsp_system()) {
+ int i;
+
+ if (!hservice_runtime->reset_pm_complex) {
+ pr_log_nocall("reset_pm_complex");
+ return -1;
+ }
+
+ for (i = 0; i < nr_chips; i++) {
+ pr_debug("PM: calling pm_complex_reset(%ld)", chips[i]);
+ rc = call_reset_pm_complex(chip);
+ if (rc) {
+ pr_log(LOG_ERR, "PM: Failed pm_complex_reset(%ld): %m",
+ chips[i]);
+ return rc;
+ }
+ }
+
+ rc = pm_complex_load_start();
+ } else {
+ if (!hservice_runtime->process_occ_reset) {
+ pr_log_nocall("process_occ_reset");
+ return -1;
+ }
+
+ pr_debug("PM: calling process_occ_reset(%ld)", chip);
+ call_process_occ_reset(chip);
+ rc = 0;
+ }
+
+ return rc;
+}
+
+static int handle_msg_occ_reset(struct opal_prd_ctx *ctx,
+ struct opal_prd_msg *msg)
+{
+ uint32_t proc;
+ int rc;
+
+ proc = be64toh(msg->occ_reset.chip);
+
+ pr_debug("FW: firmware requested OCC reset for proc 0x%x", proc);
+
+ rc = pm_complex_reset(proc);
+
+ return rc;
+}
+
+static int handle_msg_firmware_notify(struct opal_prd_ctx *ctx,
+ struct opal_prd_msg *msg)
+{
+ uint64_t len;
+ void *buf;
+
+ len = be64toh(msg->fw_notify.len);
+ buf = msg->fw_notify.data;
+
+ pr_debug("FW: firmware notification, %ld bytes", len);
+
+ if (!hservice_runtime->firmware_notify) {
+ pr_log_nocall("firmware_notify");
+ return -1;
+ }
+
+ call_firmware_notify(len, buf);
+
+ return 0;
+}
+
+static int handle_msg_sbe_passthrough(struct opal_prd_ctx *ctx,
+ struct opal_prd_msg *msg)
+{
+ uint32_t proc;
+ int rc;
+
+ proc = be64toh(msg->sbe_passthrough.chip);
+
+ pr_debug("FW: firmware sent SBE pass through command for proc 0x%x\n",
+ proc);
+
+ if (!hservice_runtime->sbe_message_passing) {
+ pr_log_nocall("sbe_message_passing");
+ return -1;
+ }
+
+ rc = call_sbe_message_passing(proc);
+ return rc;
+}
+
+static int handle_msg_fsp_occ_reset(struct opal_prd_msg *msg)
+{
+ struct opal_prd_msg omsg;
+ int rc = -1, i;
+
+ pr_debug("FW: FSP requested OCC reset");
+
+ if (!hservice_runtime->reset_pm_complex) {
+ pr_log_nocall("reset_pm_complex");
+ return rc;
+ }
+
+ for (i = 0; i < nr_chips; i++) {
+ pr_debug("PM: calling pm_complex_reset(0x%lx)", chips[i]);
+ rc = call_reset_pm_complex(chips[i]);
+ if (rc) {
+ pr_log(LOG_ERR, "PM: Failed pm_complex_reset(0x%lx) %m",
+ chips[i]);
+ break;
+ }
+ }
+
+ omsg.hdr.type = OPAL_PRD_MSG_TYPE_FSP_OCC_RESET_STATUS;
+ omsg.hdr.size = htobe16(sizeof(omsg));
+ omsg.fsp_occ_reset_status.chip = msg->occ_reset.chip;
+ omsg.fsp_occ_reset_status.status = htobe64(rc);
+
+ if (write(ctx->fd, &omsg, sizeof(omsg)) != sizeof(omsg)) {
+ pr_log(LOG_ERR, "FW: Failed to send FSP_OCC_RESET_STATUS msg: %m");
+ return -1;
+ }
+
+ return rc;
+}
+
+static int handle_msg_fsp_occ_load_start(struct opal_prd_msg *msg)
+{
+ struct opal_prd_msg omsg;
+ int rc;
+
+ pr_debug("FW: FSP requested OCC load/start");
+ rc = pm_complex_load_start();
+
+ omsg.hdr.type = OPAL_PRD_MSG_TYPE_FSP_OCC_LOAD_START_STATUS;
+ omsg.hdr.size = htobe16(sizeof(omsg));
+ omsg.fsp_occ_reset_status.chip = msg->occ_reset.chip;
+ omsg.fsp_occ_reset_status.status = htobe64(rc);
+
+ if (write(ctx->fd, &omsg, sizeof(omsg)) != sizeof(omsg)) {
+ pr_log(LOG_ERR, "FW: Failed to send FSP_OCC_LOAD_START_STATUS msg: %m");
+ return -1;
+ }
+
+ return rc;
+}
+
+static int handle_prd_msg(struct opal_prd_ctx *ctx, struct opal_prd_msg *msg)
+{
+ int rc = -1;
+
+ switch (msg->hdr.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;
+ case OPAL_PRD_MSG_TYPE_FIRMWARE_NOTIFY:
+ rc = handle_msg_firmware_notify(ctx, msg);
+ break;
+ case OPAL_PRD_MSG_TYPE_SBE_PASSTHROUGH:
+ rc = handle_msg_sbe_passthrough(ctx, msg);
+ break;
+ case OPAL_PRD_MSG_TYPE_FSP_OCC_RESET:
+ rc = handle_msg_fsp_occ_reset(msg);
+ break;
+ case OPAL_PRD_MSG_TYPE_FSP_OCC_LOAD_START:
+ rc = handle_msg_fsp_occ_load_start(msg);
+ break;
+ default:
+ pr_log(LOG_WARNING, "Invalid incoming message type 0x%x",
+ msg->hdr.type);
+ }
+
+ return rc;
+}
+
+#define list_for_each_pop(h, i, type, member) \
+ for (i = list_pop((h), type, member); \
+ i; \
+ i = list_pop((h), type, member))
+
+
+static int process_msgq(struct opal_prd_ctx *ctx)
+{
+ struct prd_msgq_item *item;
+
+ list_for_each_pop(&ctx->msgq, item, struct prd_msgq_item, list) {
+ handle_prd_msg(ctx, &item->msg);
+ free(item);
+ }
+
+ return 0;
+}
+
+static int read_prd_msg(struct opal_prd_ctx *ctx)
+{
+ struct opal_prd_msg *msg;
+ int size;
+ int rc;
+
+ msg = ctx->msg;
+
+ rc = read(ctx->fd, msg, ctx->msg_alloc_len);
+ if (rc < 0 && errno == EAGAIN)
+ return -1;
+
+ /* we need at least enough for the message header... */
+ if (rc < 0) {
+ pr_log(LOG_WARNING, "FW: error reading from firmware: %m");
+ return -1;
+ }
+
+ if (rc < sizeof(msg->hdr)) {
+ pr_log(LOG_WARNING, "FW: short message read from firmware");
+ return -1;
+ }
+
+ /* ... and for the reported message size to be sane */
+ size = htobe16(msg->hdr.size);
+ if (size < sizeof(msg->hdr)) {
+ pr_log(LOG_ERR, "FW: Mismatched message size "
+ "between opal-prd and firmware "
+ "(%d from FW, %zd expected)",
+ size, sizeof(msg->hdr));
+ return -1;
+ }
+
+ /* expand our message buffer if necessary... */
+ if (size > ctx->msg_alloc_len) {
+ msg = realloc(ctx->msg, size);
+ if (!msg) {
+ pr_log(LOG_ERR,
+ "FW: Can't expand PRD message buffer: %m");
+ return -1;
+ }
+ ctx->msg = msg;
+ ctx->msg_alloc_len = size;
+ }
+
+ /* ... and complete the read */
+ if (size > rc) {
+ size_t pos;
+
+ for (pos = rc; pos < size;) {
+ rc = read(ctx->fd, msg + pos, size - pos);
+
+ if (rc < 0 && errno == EAGAIN)
+ continue;
+
+ if (rc <= 0) {
+ pr_log(LOG_WARNING,
+ "FW: error reading from firmware: %m");
+ return -1;
+ }
+
+ pos += rc;
+ }
+ }
+
+ return 0;
+}
+
+static void handle_prd_control_occ_error(struct control_msg *send_msg,
+ struct control_msg *recv_msg)
+{
+ uint64_t chip;
+
+ if (!hservice_runtime->process_occ_error) {
+ pr_log_nocall("process_occ_error");
+ return;
+ }
+
+ chip = recv_msg->occ_error.chip;
+
+ pr_debug("CTRL: calling process_occ_error(%lu)", chip);
+ call_process_occ_error(chip);
+
+ send_msg->data_len = 0;
+ send_msg->response = 0;
+}
+
+static void handle_prd_control_occ_reset(struct control_msg *send_msg,
+ struct control_msg *msg)
+{
+ struct opal_prd_msg omsg;
+ uint64_t chip;
+ int rc;
+
+ /* notify OPAL of the impending reset */
+ memset(&omsg, 0, sizeof(omsg));
+ omsg.hdr.type = OPAL_PRD_MSG_TYPE_OCC_RESET_NOTIFY;
+ omsg.hdr.size = htobe16(sizeof(omsg));
+ rc = write(ctx->fd, &omsg, sizeof(omsg));
+ if (rc != sizeof(omsg))
+ pr_log(LOG_WARNING, "FW: Failed to send OCC_RESET message: %m");
+
+ chip = msg->occ_reset.chip;
+
+ /* do reset */
+ pr_debug("CTRL: Calling OCC reset on chip %ld", chip);
+ pm_complex_reset(chip);
+
+ send_msg->data_len = 0;
+ send_msg->response = 0;
+}
+
+static void handle_prd_control_occ_actuation(struct control_msg *msg,
+ bool enable)
+{
+ if (!hservice_runtime->enable_occ_actuation) {
+ pr_log_nocall("enable_occ_actuation");
+ return;
+ }
+
+ pr_debug("CTRL: calling enable_occ_actuation(%s)",
+ enable ? "true" : "false");
+ msg->data_len = 0;
+ msg->response = call_enable_occ_actuation(enable);
+}
+
+static void handle_prd_control_attr_override(struct control_msg *send_msg,
+ struct control_msg *recv_msg)
+{
+ if (!hservice_runtime->apply_attr_override) {
+ pr_log_nocall("apply_attr_override");
+ return;
+ }
+
+ pr_debug("CTRL: calling apply_attr_override");
+ send_msg->response = call_apply_attr_override(
+ recv_msg->data, recv_msg->data_len);
+ send_msg->data_len = 0;
+}
+
+static void handle_prd_control_htmgt_passthru(struct control_msg *send_msg,
+ struct control_msg *recv_msg)
+{
+ uint16_t rsp_len;
+
+ if (!hservice_runtime->mfg_htmgt_pass_thru) {
+ pr_log_nocall("mfg_htmgt_pass_thru");
+ return;
+ }
+
+ pr_debug("CTRL: calling mfg_htmgt_pass_thru");
+ send_msg->response = call_mfg_htmgt_pass_thru(recv_msg->data_len,
+ recv_msg->data, &rsp_len,
+ send_msg->data);
+ send_msg->data_len = be16toh(rsp_len);
+ if (send_msg->data_len > MAX_CONTROL_MSG_BUF) {
+ pr_log(LOG_ERR, "CTRL: response buffer overrun, data len: %d",
+ send_msg->data_len);
+ send_msg->data_len = MAX_CONTROL_MSG_BUF;
+ }
+}
+
+static void handle_prd_control_run_cmd(struct control_msg *send_msg,
+ struct control_msg *recv_msg)
+{
+ char *runcmd_output, *s;
+ const char **argv;
+ int i, argc;
+ size_t size;
+
+ if (!hservice_runtime->run_command) {
+ pr_log_nocall("run_command");
+ return;
+ }
+
+ argc = recv_msg->run_cmd.argc;
+ pr_debug("CTRL: run_command, argc:%d\n", argc);
+
+ argv = malloc(argc * sizeof(*argv));
+ if (!argv) {
+ pr_log(LOG_ERR, "CTRL: argv buffer malloc failed: %m");
+ return;
+ }
+
+ s = (char *)recv_msg->data;
+ size = 0;
+ for (i = 0; i < argc; i++) {
+ argv[i] = (char *)htobe64((uint64_t)&s[size]);
+ size += (strlen(&s[size]) + 1);
+ }
+
+ /* Call HBRT */
+ send_msg->response = call_run_command(argc, argv, &runcmd_output);
+ runcmd_output = (char *)be64toh((uint64_t)runcmd_output);
+ free(argv);
+
+ s = (char *)send_msg->data;
+ if (runcmd_output) {
+ size = strlen(runcmd_output);
+ if (size >= MAX_CONTROL_MSG_BUF) {
+ pr_log(LOG_WARNING, "CTRL: output message truncated");
+ runcmd_output[MAX_CONTROL_MSG_BUF] = '\0';
+ size = MAX_CONTROL_MSG_BUF;
+ }
+
+ strcpy(s, runcmd_output);
+ send_msg->data_len = size + 1;
+ free(runcmd_output);
+ } else {
+ strcpy(s, "Null");
+ send_msg->data_len = strlen("Null") + 1;
+ }
+}
+
+static void handle_prd_control(struct opal_prd_ctx *ctx, int fd)
+{
+ struct control_msg msg, *recv_msg, *send_msg;
+ bool enabled = false;
+ int rc, size;
+
+ /* Default reply, in the error path */
+ send_msg = &msg;
+
+ /* Peek into the socket to ascertain the size of the available data */
+ rc = recv(fd, &msg, sizeof(msg), MSG_PEEK);
+ if (rc != sizeof(msg)) {
+ pr_log(LOG_WARNING, "CTRL: failed to receive control "
+ "message: %m");
+ msg.response = -1;
+ msg.data_len = 0;
+ goto out_send;
+ }
+
+ size = sizeof(*recv_msg) + msg.data_len;
+
+ /* Default reply, in the error path */
+ msg.data_len = 0;
+ msg.response = -1;
+
+ recv_msg = malloc(size);
+ if (!recv_msg) {
+ pr_log(LOG_ERR, "CTRL: message buffer malloc failed: %m");
+ goto out_send;
+ }
+
+ rc = recv(fd, recv_msg, size, MSG_TRUNC);
+ if (rc != size) {
+ pr_log(LOG_WARNING, "CTRL: failed to receive control "
+ "message: %m");
+ goto out_free_recv;
+ }
+
+ send_msg = malloc(sizeof(*send_msg) + MAX_CONTROL_MSG_BUF);
+ if (!send_msg) {
+ pr_log(LOG_ERR, "CTRL: message buffer malloc failed: %m");
+ send_msg = &msg;
+ goto out_free_recv;
+ }
+
+ send_msg->type = recv_msg->type;
+ send_msg->response = -1;
+ switch (recv_msg->type) {
+ case CONTROL_MSG_ENABLE_OCCS:
+ enabled = true;
+ /* fall through */
+ case CONTROL_MSG_DISABLE_OCCS:
+ handle_prd_control_occ_actuation(send_msg, enabled);
+ break;
+ case CONTROL_MSG_TEMP_OCC_RESET:
+ handle_prd_control_occ_reset(send_msg, recv_msg);
+ break;
+ case CONTROL_MSG_TEMP_OCC_ERROR:
+ handle_prd_control_occ_error(send_msg, recv_msg);
+ break;
+ case CONTROL_MSG_ATTR_OVERRIDE:
+ handle_prd_control_attr_override(send_msg, recv_msg);
+ break;
+ case CONTROL_MSG_HTMGT_PASSTHRU:
+ handle_prd_control_htmgt_passthru(send_msg, recv_msg);
+ break;
+ case CONTROL_MSG_RUN_CMD:
+ handle_prd_control_run_cmd(send_msg, recv_msg);
+ break;
+ default:
+ pr_log(LOG_WARNING, "CTRL: Unknown control message action %d",
+ recv_msg->type);
+ send_msg->data_len = 0;
+ break;
+ }
+
+out_free_recv:
+ free(recv_msg);
+out_send:
+ size = sizeof(*send_msg) + send_msg->data_len;
+ rc = send(fd, send_msg, size, MSG_DONTWAIT | MSG_NOSIGNAL);
+ if (rc && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EPIPE))
+ pr_debug("CTRL: control send() returned %d, ignoring failure",
+ rc);
+ else if (rc != size)
+ pr_log(LOG_NOTICE, "CTRL: Failed to send control response: %m");
+
+ if (send_msg != &msg)
+ free(send_msg);
+}
+
+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("HBRT: calling enable_attns");
+ rc = call_enable_attns();
+ if (rc) {
+ pr_log(LOG_ERR, "HBRT: enable_attns() failed, "
+ "aborting");
+ return -1;
+ }
+ }
+
+ if (hservice_runtime->get_ipoll_events) {
+ pr_debug("HBRT: calling get_ipoll_events");
+ opal_prd_ipoll = call_get_ipoll_events();
+ }
+
+ pr_debug("HBRT: enabling IPOLL events 0x%016lx", opal_prd_ipoll);
+
+ /* send init message, to unmask interrupts */
+ msg.hdr.type = OPAL_PRD_MSG_TYPE_INIT;
+ msg.hdr.size = htobe16(sizeof(msg));
+ msg.init.version = htobe64(opal_prd_version);
+ msg.init.ipoll = htobe64(opal_prd_ipoll);
+
+ pr_debug("FW: writing init message");
+ rc = write(ctx->fd, &msg, sizeof(msg));
+ if (rc != sizeof(msg)) {
+ pr_log(LOG_ERR, "FW: Init message failed: %m. Aborting.");
+ return -1;
+ }
+
+ pollfds[0].fd = ctx->fd;
+ pollfds[0].events = POLLIN | POLLERR;
+ pollfds[1].fd = ctx->socket;
+ pollfds[1].events = POLLIN | POLLERR;
+
+ for (;;) {
+ /* run through any pending messages */
+ process_msgq(ctx);
+
+ rc = poll(pollfds, 2, -1);
+ if (rc < 0) {
+ pr_log(LOG_ERR, "FW: event poll failed: %m");
+ exit(EXIT_FAILURE);
+ }
+
+ if (!rc)
+ continue;
+
+ if (pollfds[0].revents & POLLIN) {
+ rc = read_prd_msg(ctx);
+ if (!rc)
+ handle_prd_msg(ctx, ctx->msg);
+ }
+
+ if (pollfds[1].revents & POLLIN) {
+ fd = accept(ctx->socket, NULL, NULL);
+ if (fd < 0) {
+ pr_log(LOG_NOTICE, "CTRL: accept failed: %m");
+ 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) {
+ pr_log(LOG_WARNING, "CTRL: Can't open control socket %s: %m",
+ opal_prd_socket);
+ return -1;
+ }
+
+ rc = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
+ if (rc) {
+ pr_log(LOG_WARNING, "CTRL: Can't bind control socket %s: %m",
+ opal_prd_socket);
+ close(fd);
+ return -1;
+ }
+
+ rc = listen(fd, 0);
+ if (rc) {
+ pr_log(LOG_WARNING, "CTRL: Can't listen on "
+ "control socket %s: %m", opal_prd_socket);
+ close(fd);
+ return -1;
+ }
+
+ pr_log(LOG_INFO, "CTRL: Listening on control socket %s",
+ opal_prd_socket);
+
+ ctx->socket = fd;
+ return 0;
+}
+
+static struct sigaction sigchild_action = {
+ .sa_flags = SA_NOCLDWAIT | SA_RESTART,
+ .sa_handler = SIG_DFL,
+};
+
+static int run_prd_daemon(struct opal_prd_ctx *ctx)
+{
+ char *opal_msg_path;
+ void *buf;
+ int rc, len;
+
+ /* log to syslog */
+ pr_log_daemon_init();
+
+ pr_debug("CTRL: Starting PRD daemon\n");
+
+ ctx->fd = -1;
+ ctx->socket = -1;
+
+ /*
+ * Set up our message buffer. Use opal-msg-size device tree
+ * property to get message buffer size.
+ */
+ rc = asprintf(&opal_msg_path,
+ "%s/ibm,opal/opal-msg-size", devicetree_base);
+ if (rc > 0) {
+ rc = open_and_read(opal_msg_path, &buf, &len);
+ if (rc == 0) {
+ ctx->msg_alloc_len = be32toh(*(__be32 *)buf);
+ free(buf);
+ }
+
+ free(opal_msg_path);
+ }
+
+ if (ctx->msg_alloc_len == 0)
+ ctx->msg_alloc_len = sizeof(*ctx->msg);
+
+ ctx->msg = malloc(ctx->msg_alloc_len);
+ if (!ctx->msg) {
+ pr_log(LOG_ERR, "FW: Can't allocate PRD message buffer: %m");
+ return -1;
+ }
+ memset(ctx->msg, 0, ctx->msg_alloc_len);
+
+ list_head_init(&ctx->msgq);
+
+ i2c_init();
+
+#ifdef DEBUG_I2C
+ {
+ uint8_t foo[128];
+ int i;
+
+ rc = i2c_read(0, 1, 2, 0x50, 2, 0x10, 128, foo);
+ pr_debug("I2C: read rc: %d", rc);
+ for (i = 0; i < sizeof(foo); i += 8) {
+ pr_debug("I2C: %02x %02x %02x %02x %02x %02x %02x %02x",
+ 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) {
+ pr_log(LOG_WARNING, "CTRL: Error initialising PRD control: %m");
+ goto out_close;
+ }
+
+
+ rc = prd_init(ctx);
+ if (rc) {
+ pr_log(LOG_ERR, "FW: Error initialising PRD channel");
+ goto out_close;
+ }
+
+ if (ctx->hbrt_file_name) {
+ rc = map_hbrt_file(ctx, ctx->hbrt_file_name);
+ if (rc) {
+ pr_log(LOG_ERR, "IMAGE: 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) {
+ /* Fallback to old style ibm,prd-label */
+ rc = map_hbrt_physmem(ctx, hbrt_code_region_name_ibm);
+ if (rc) {
+ pr_log(LOG_ERR, "IMAGE: Can't access hbrt "
+ "physical memory");
+ goto out_close;
+ }
+ }
+ dump_hbrt_map(ctx);
+ }
+
+ pr_debug("IMAGE: hbrt map at %p, size 0x%zx",
+ ctx->code_addr, ctx->code_size);
+
+ fixup_hinterface_table();
+
+ if (!is_fsp_system()) {
+ pnor_load_module(ctx);
+
+ rc = pnor_init(&ctx->pnor);
+ if (rc) {
+ pr_log(LOG_ERR, "PNOR: Failed to open pnor: %m");
+ goto out_close;
+ }
+ } else {
+ /* Disable PNOR function pointers */
+ hinterface.pnor_read = NULL;
+ hinterface.pnor_write = NULL;
+ }
+
+ ipmi_init(ctx);
+
+ pr_debug("HBRT: calling hservices_init");
+ rc = hservices_init(ctx, ctx->code_addr);
+ if (rc) {
+ pr_log(LOG_ERR, "HBRT: Can't initialise HBRT");
+ goto out_close;
+ }
+ pr_debug("HBRT: hservices_init done");
+
+ /* Test a scom */
+ if (ctx->debug) {
+ uint64_t val;
+ pr_debug("SCOM: trying scom read");
+ fflush(stdout);
+ hservice_scom_read(0x00, 0xf000f, &val);
+ pr_debug("SCOM: f00f: %lx", be64toh(val));
+ }
+
+ /*
+ * Setup the SIGCHLD handler to automatically reap the worker threads
+ * we use for memory offlining. We can't do this earlier since the
+ * modprobe helper spawns workers and wants to check their exit status
+ * with waitpid(). Auto-reaping breaks that so enable it just before
+ * entering the attn loop.
+ *
+ * We also setup system call restarting on SIGCHLD since opal-prd
+ * doesn't make any real attempt to handle blocking functions exiting
+ * due to EINTR.
+ */
+ if (sigaction(SIGCHLD, &sigchild_action, NULL)) {
+ pr_log(LOG_ERR, "CTRL: Failed to register signal handler %m\n");
+ return -1;
+ }
+
+ run_attn_loop(ctx);
+ rc = 0;
+
+out_close:
+ pr_debug("CTRL: stopping PRD daemon\n");
+ pnor_close(&ctx->pnor);
+ if (ctx->fd != -1)
+ close(ctx->fd);
+ if (ctx->socket != -1)
+ close(ctx->socket);
+ if (ctx->msg)
+ free(ctx->msg);
+ return rc;
+}
+
+static int send_prd_control(struct control_msg *send_msg,
+ struct control_msg **recv_msg)
+{
+ struct sockaddr_un addr;
+ struct control_msg *msg;
+ int sd, rc, size;
+
+ sd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (!sd) {
+ pr_log(LOG_ERR, "CTRL: Failed to create control socket: %m");
+ return -1;
+ }
+
+ addr.sun_family = AF_UNIX;
+ strcpy(addr.sun_path, opal_prd_socket);
+
+ rc = connect(sd, (struct sockaddr *)&addr, sizeof(addr));
+ if (rc) {
+ pr_log(LOG_ERR, "CTRL: Failed to connect to prd daemon: %m");
+ goto out_close;
+ }
+
+ size = sizeof(*send_msg) + send_msg->data_len;
+ rc = send(sd, send_msg, size, 0);
+ if (rc != size) {
+ pr_log(LOG_ERR, "CTRL: Failed to send control message: %m");
+ rc = -1;
+ goto out_close;
+ }
+
+ size = sizeof(*msg) + MAX_CONTROL_MSG_BUF;
+ msg = malloc(size);
+ if (!msg) {
+ pr_log(LOG_ERR, "CTRL: msg buffer malloc failed: %m");
+ rc = -1;
+ goto out_close;
+ }
+
+ *recv_msg = msg;
+
+ /* wait for our reply */
+ rc = recv(sd, msg, size, 0);
+ if (rc < 0) {
+ pr_log(LOG_ERR, "CTRL: Failed to receive control message: %m");
+ goto out_close;
+
+ } else if (rc != (sizeof(*msg) + msg->data_len)) {
+ pr_log(LOG_WARNING, "CTRL: Short read from control socket");
+ rc = -1;
+ goto out_close;
+ }
+
+ rc = msg->response;
+
+out_close:
+ close(sd);
+ return rc;
+}
+
+static int send_occ_control(struct opal_prd_ctx *ctx, int argc, char *argv[])
+{
+ struct control_msg send_msg, *recv_msg = NULL;
+ unsigned long chip = 0;
+ const char *op;
+ int rc;
+
+ assert(argc >= 1);
+ op = argv[0];
+
+ /* some commands accept a 'chip' argument, so parse it here */
+ if (argc > 1) {
+ char *arg, *end;
+ arg = argv[1];
+ chip = strtoul(arg, &end, 0);
+ if (end == arg) {
+ pr_log(LOG_ERR, "CTRL: invalid argument %s", arg);
+ return -1;
+ }
+ }
+
+ memset(&send_msg, 0, sizeof(send_msg));
+
+ if (!strcmp(op, "enable"))
+ send_msg.type = CONTROL_MSG_ENABLE_OCCS;
+ else if (!strcmp(op, "disable"))
+ send_msg.type = CONTROL_MSG_DISABLE_OCCS;
+
+ else if (!strcmp(op, "reset")) {
+ send_msg.type = CONTROL_MSG_TEMP_OCC_RESET;
+ send_msg.occ_reset.chip = (uint64_t)chip;
+
+ } else if (!strcmp(op, "process-error")) {
+ send_msg.type = CONTROL_MSG_TEMP_OCC_ERROR;
+ send_msg.occ_error.chip = (uint64_t)chip;
+ } else {
+ pr_log(LOG_ERR, "CTRL: Invalid OCC action '%s'", op);
+ return -1;
+ }
+
+ rc = send_prd_control(&send_msg, &recv_msg);
+ if (recv_msg) {
+ if (recv_msg->response || ctx->debug)
+ pr_debug("CTRL: OCC action %s returned status %d", op,
+ recv_msg->response);
+ free(recv_msg);
+ }
+
+ return rc;
+}
+
+static int send_attr_override(struct opal_prd_ctx *ctx, uint32_t argc,
+ char *argv[])
+{
+ struct control_msg *send_msg, *recv_msg = NULL;
+ struct stat statbuf;
+ size_t sz;
+ FILE *fd;
+ int rc;
+
+ rc = stat(argv[0], &statbuf);
+ if (rc) {
+ pr_log(LOG_ERR, "CTRL: stat() failed on the file: %m");
+ return -1;
+ }
+
+ send_msg = malloc(sizeof(*send_msg) + statbuf.st_size);
+ if (!send_msg) {
+ pr_log(LOG_ERR, "CTRL: msg buffer malloc failed: %m");
+ return -1;
+ }
+
+ send_msg->type = CONTROL_MSG_ATTR_OVERRIDE;
+ send_msg->data_len = statbuf.st_size;
+
+ fd = fopen(argv[0], "r");
+ if (!fd) {
+ pr_log(LOG_NOTICE, "CTRL: can't open %s: %m", argv[0]);
+ rc = -1;
+ goto out_free;
+ }
+
+ sz = fread(send_msg->data, 1, send_msg->data_len, fd);
+ fclose(fd);
+ if (sz != statbuf.st_size) {
+ pr_log(LOG_ERR, "CTRL: short read from the file");
+ rc = -1;
+ goto out_free;
+ }
+
+ rc = send_prd_control(send_msg, &recv_msg);
+ if (recv_msg) {
+ if (recv_msg->response || ctx->debug)
+ pr_debug("CTRL: attribute override returned status %d",
+ recv_msg->response);
+ free(recv_msg);
+ }
+
+out_free:
+ free(send_msg);
+ return rc;
+}
+
+static int send_htmgt_passthru(struct opal_prd_ctx *ctx, int argc, char *argv[])
+{
+ struct control_msg *send_msg, *recv_msg = NULL;
+ int rc, i;
+
+ if (!ctx->expert_mode) {
+ pr_log(LOG_WARNING, "CTRL: need to be in expert mode");
+ return -1;
+ }
+
+ send_msg = malloc(sizeof(*send_msg) + argc);
+ if (!send_msg) {
+ pr_log(LOG_ERR, "CTRL: message buffer malloc failed: %m");
+ return -1;
+ }
+
+ send_msg->type = CONTROL_MSG_HTMGT_PASSTHRU;
+ send_msg->data_len = argc;
+
+ if (ctx->debug)
+ pr_debug("CTRL: HTMGT passthru arguments:");
+
+ for (i = 0; i < argc; i++) {
+ if (ctx->debug)
+ pr_debug("argv[%d] = %s", i, argv[i]);
+
+ sscanf(argv[i], "%hhx", &send_msg->data[i]);
+ }
+
+ rc = send_prd_control(send_msg, &recv_msg);
+ free(send_msg);
+
+ if (recv_msg) {
+ if (recv_msg->response || ctx->debug)
+ pr_debug("CTRL: HTMGT passthru returned status %d",
+ recv_msg->response);
+ if (recv_msg->response == 0 && recv_msg->data_len)
+ hexdump(recv_msg->data, recv_msg->data_len);
+
+ free(recv_msg);
+ }
+
+ return rc;
+}
+
+static int send_run_command(struct opal_prd_ctx *ctx, int argc, char *argv[])
+{
+ struct control_msg *send_msg, *recv_msg = NULL;
+ uint32_t size = 0;
+ int rc, i;
+ char *s;
+
+ if (!ctx->expert_mode) {
+ pr_log(LOG_WARNING, "CTRL: need to be in expert mode");
+ return -1;
+ }
+
+ if (ctx->debug) {
+ pr_debug("CTRL: run command arguments:");
+ for (i=0; i < argc; i++)
+ pr_debug("argv[%d] = %s", i, argv[i]);
+ }
+
+ for (i = 0; i < argc; i++)
+ size += (strlen(argv[i]) + 1);
+
+ send_msg = malloc(sizeof(*send_msg) + size);
+ if (!send_msg) {
+ pr_log(LOG_ERR, "CTRL: msg buffer malloc failed: %m");
+ return -1;
+ }
+
+ /* Setup message */
+ send_msg->type = CONTROL_MSG_RUN_CMD;
+ send_msg->run_cmd.argc = argc;
+ send_msg->data_len = size;
+ s = (char *)send_msg->data;
+ for (i = 0; i < argc; i++) {
+ strcpy(s, argv[i]);
+ s = s + strlen(argv[i]) + 1;
+ }
+
+ rc = send_prd_control(send_msg, &recv_msg);
+ free(send_msg);
+ if (recv_msg) {
+ if (!rc)
+ pr_log(LOG_INFO, "Received: %s", recv_msg->data);
+
+ if (recv_msg->response || ctx->debug)
+ pr_debug("CTRL: run command returned status %d",
+ recv_msg->response);
+ free(recv_msg);
+ }
+
+ return rc;
+}
+
+static void usage(const char *progname)
+{
+ printf("Usage:\n");
+ printf("\t%s [--debug] [--file <hbrt-image>] [--pnor <device>]\n",
+ progname);
+ printf("\t%s occ <enable|disable|reset [chip]>\n", progname);
+ printf("\t%s pm-complex reset [chip]>\n", progname);
+ printf("\t%s htmgt-passthru <bytes...>\n", progname);
+ printf("\t%s override <FILE>\n", progname);
+ printf("\t%s run [arg 0] [arg 1]..[arg n]\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--stdio log to stdio, instead of syslog\n");
+}
+
+static void print_version(void)
+{
+ extern const char version[];
+ printf("opal-prd %s\n", version);
+}
+
+static struct option opal_diag_options[] = {
+ {"file", required_argument, NULL, 'f'},
+ {"pnor", required_argument, NULL, 'p'},
+ {"debug", no_argument, NULL, 'd'},
+ {"help", no_argument, NULL, 'h'},
+ {"version", no_argument, NULL, 'v'},
+ {"stdio", no_argument, NULL, 's'},
+ {"expert-mode", no_argument, NULL, 'e'},
+ { 0 },
+};
+
+enum action {
+ ACTION_RUN_DAEMON,
+ ACTION_OCC_CONTROL,
+ ACTION_ATTR_OVERRIDE,
+ ACTION_HTMGT_PASSTHRU,
+ ACTION_RUN_COMMAND,
+};
+
+static int parse_action(const char *str, enum action *action)
+{
+ int rc;
+
+ if (!strcmp(str, "occ")) {
+ *action = ACTION_OCC_CONTROL;
+ rc = 0;
+
+ if (is_fsp_system()) {
+ pr_log(LOG_ERR, "CTRL: occ commands are not "
+ "supported on this system");
+ rc = -1;
+ }
+ } else if (!strcmp(str, "pm-complex")) {
+ *action = ACTION_OCC_CONTROL;
+ rc = 0;
+
+ if (!is_fsp_system()) {
+ pr_log(LOG_ERR, "CTRL: pm-complex commands are not "
+ "supported on this system");
+ rc = -1;
+ }
+ } else if (!strcmp(str, "daemon")) {
+ *action = ACTION_RUN_DAEMON;
+ rc = 0;
+ } else if (!strcmp(str, "override")) {
+ *action = ACTION_ATTR_OVERRIDE;
+ rc = 0;
+ } else if (!strcmp(str, "htmgt-passthru")) {
+ *action = ACTION_HTMGT_PASSTHRU;
+ rc = 0;
+ } else if (!strcmp(str, "run")) {
+ *action = ACTION_RUN_COMMAND;
+ return 0;
+ } else {
+ pr_log(LOG_ERR, "CTRL: unknown argument '%s'", str);
+ rc = -1;
+ }
+
+ return rc;
+}
+
+int main(int argc, char *argv[])
+{
+ struct opal_prd_ctx _ctx;
+ enum action action;
+ int rc;
+
+ check_abi();
+
+ ctx = &_ctx;
+ memset(ctx, 0, sizeof(*ctx));
+ ctx->vlog = pr_log_stdio;
+ ctx->use_syslog = true;
+
+ /* Parse options */
+ for (;;) {
+ int c;
+
+ c = getopt_long(argc, argv, "f:p:dhse", 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 's':
+ ctx->use_syslog = false;
+ break;
+ case 'h':
+ usage(argv[0]);
+ return EXIT_SUCCESS;
+ case 'e':
+ ctx->expert_mode = true;
+ break;
+ case 'v':
+ print_version();
+ 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;
+ optind++;
+ } else {
+ action = ACTION_RUN_DAEMON;
+ }
+
+ if (is_prd_supported() < 0) {
+ pr_log(LOG_ERR, "CTRL: PowerNV OPAL runtime diagnostic "
+ "is not supported on this system");
+ return -1;
+ }
+
+ switch (action) {
+ case ACTION_RUN_DAEMON:
+ rc = run_prd_daemon(ctx);
+ break;
+ case ACTION_OCC_CONTROL:
+ if (optind >= argc) {
+ pr_log(LOG_ERR, "CTRL: occ command requires "
+ "an argument");
+ return EXIT_FAILURE;
+ }
+
+ rc = send_occ_control(ctx, argc - optind, &argv[optind]);
+ break;
+ case ACTION_ATTR_OVERRIDE:
+ if (optind >= argc) {
+ pr_log(LOG_ERR, "CTRL: attribute override command "
+ "requires an argument");
+ return EXIT_FAILURE;
+ }
+
+ rc = send_attr_override(ctx, argc - optind, &argv[optind]);
+ break;
+ case ACTION_HTMGT_PASSTHRU:
+ if (optind >= argc) {
+ pr_log(LOG_ERR, "CTRL: htmgt passthru requires at least "
+ "one argument");
+ return EXIT_FAILURE;
+ }
+
+ rc = send_htmgt_passthru(ctx, argc - optind, &argv[optind]);
+ break;
+ case ACTION_RUN_COMMAND:
+ if (optind >= argc) {
+ pr_log(LOG_ERR, "CTRL: run command requires "
+ "argument(s)");
+ return EXIT_FAILURE;
+ }
+
+ rc = send_run_command(ctx, argc - optind, &argv[optind]);
+ break;
+ default:
+ break;
+ }
+
+ return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/roms/skiboot/external/opal-prd/opal-prd.h b/roms/skiboot/external/opal-prd/opal-prd.h
new file mode 100644
index 000000000..606317d74
--- /dev/null
+++ b/roms/skiboot/external/opal-prd/opal-prd.h
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/* Copyright 2015 IBM Corp. */
+
+#ifndef OPAL_PRD_H
+#define OPAL_PRD_H
+
+#include <syslog.h>
+
+#define pr_debug(fmt, ...) pr_log(LOG_DEBUG, fmt, ## __VA_ARGS__)
+
+void pr_log(int priority, const char *fmt, ...)
+ __attribute__((format(printf, 2, 3)));
+
+#endif /* OPAL_PRD_H */
+
diff --git a/roms/skiboot/external/opal-prd/opal-prd.service b/roms/skiboot/external/opal-prd/opal-prd.service
new file mode 100644
index 000000000..dce0dd262
--- /dev/null
+++ b/roms/skiboot/external/opal-prd/opal-prd.service
@@ -0,0 +1,11 @@
+[Unit]
+Description=OPAL PRD daemon
+ConditionVirtualization=false
+ConditionPathExists=/sys/firmware/devicetree/base/ibm,opal/diagnostics
+
+[Service]
+ExecStart=/usr/sbin/opal-prd
+Restart=always
+
+[Install]
+WantedBy=multi-user.target
diff --git a/roms/skiboot/external/opal-prd/pnor.c b/roms/skiboot/external/opal-prd/pnor.c
new file mode 100644
index 000000000..b2da7134c
--- /dev/null
+++ b/roms/skiboot/external/opal-prd/pnor.c
@@ -0,0 +1,217 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/*
+ * PNOR Access (/dev/mtd) for opal-prd
+ *
+ * Copyright 2013-2017 IBM Corp.
+ */
+
+#include <libflash/libffs.h>
+#include <common/arch_flash.h>
+
+#include <errno.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"
+#include "opal-prd.h"
+
+#define FDT_FLASH_PATH "/proc/device-tree/chosen/ibm,system-flash"
+
+bool pnor_available(struct pnor *pnor)
+{
+ /* --pnor is specified */
+ if (pnor->path) {
+ if (access(pnor->path, R_OK | W_OK) == 0)
+ return true;
+
+ pr_log(LOG_ERR, "PNOR: Does not have permission to read pnor: %m");
+ return false;
+ }
+
+ if (access(FDT_FLASH_PATH, R_OK) == 0)
+ return true;
+
+ return false;
+}
+
+int pnor_init(struct pnor *pnor)
+{
+ int rc;
+
+ if (!pnor)
+ return -1;
+
+ rc = arch_flash_init(&(pnor->bl), pnor->path, false);
+ if (rc) {
+ pr_log(LOG_ERR, "PNOR: Flash init failed");
+ return -1;
+ }
+
+ rc = blocklevel_get_info(pnor->bl, NULL, &(pnor->size), &(pnor->erasesize));
+ if (rc) {
+ pr_log(LOG_ERR, "PNOR: blocklevel_get_info() failed. Can't use PNOR");
+ goto out;
+ }
+
+ rc = ffs_init(0, pnor->size, pnor->bl, &pnor->ffsh, 0);
+ if (rc) {
+ pr_log(LOG_ERR, "PNOR: Failed to open pnor partition table");
+ goto out;
+ }
+
+ return 0;
+out:
+ arch_flash_close(pnor->bl, pnor->path);
+ pnor->bl = NULL;
+ return -1;
+}
+
+void pnor_close(struct pnor *pnor)
+{
+ if (!pnor)
+ return;
+
+ if (pnor->ffsh)
+ ffs_close(pnor->ffsh);
+
+ if (pnor->bl)
+ arch_flash_close(pnor->bl, pnor->path);
+
+ if (pnor->path)
+ free(pnor->path);
+}
+
+void dump_parts(struct ffs_handle *ffs) {
+ int i, rc;
+ uint32_t start, size, act_size;
+ char *name;
+
+ pr_debug("PNOR: %10s %8s %8s %8s",
+ "name", "start", "size", "act_size");
+ for (i = 0; ; i++) {
+ rc = ffs_part_info(ffs, i, &name, &start,
+ &size, &act_size, NULL);
+ if (rc)
+ break;
+ pr_debug("PNOR: %10s %08x %08x %08x",
+ name, start, size, act_size);
+ free(name);
+ }
+}
+
+static int mtd_write(struct pnor *pnor, void *data, uint64_t offset,
+ size_t len)
+{
+ int rc;
+
+ if (len > pnor->size || offset > pnor->size ||
+ len + offset > pnor->size)
+ return -ERANGE;
+
+ rc = blocklevel_smart_write(pnor->bl, offset, data, len);
+ if (rc)
+ return -errno;
+
+ return len;
+}
+
+static int mtd_read(struct pnor *pnor, void *data, uint64_t offset,
+ size_t len)
+{
+ int rc;
+
+ if (len > pnor->size || offset > pnor->size ||
+ len + offset > pnor->size)
+ return -ERANGE;
+
+ rc = blocklevel_read(pnor->bl, offset, data, len);
+ if (rc)
+ return -errno;
+
+ return len;
+}
+
+/* Similar to read(2), this performs partial operations where the number of
+ * bytes read/written may be less than size.
+ *
+ * Returns number of bytes written, or a negative value on failure. */
+int pnor_operation(struct pnor *pnor, const char *name, uint64_t offset,
+ void *data, size_t requested_size, enum pnor_op op)
+{
+ int rc;
+ uint32_t pstart, psize, idx;
+ int size;
+
+ if (!pnor->ffsh) {
+ pr_log(LOG_ERR, "PNOR: ffs not initialised");
+ return -EBUSY;
+ }
+
+ rc = ffs_lookup_part(pnor->ffsh, name, &idx);
+ if (rc) {
+ pr_log(LOG_WARNING, "PNOR: no partiton named '%s'", name);
+ return -ENOENT;
+ }
+
+ ffs_part_info(pnor->ffsh, idx, NULL, &pstart, &psize, NULL, NULL);
+ if (rc) {
+ pr_log(LOG_ERR, "PNOR: unable to fetch partition info for %s",
+ name);
+ return -ENOENT;
+ }
+
+ if (offset > psize) {
+ pr_log(LOG_WARNING, "PNOR: partition %s(size 0x%x) "
+ "offset (0x%lx) out of bounds",
+ name, psize, offset);
+ return -ERANGE;
+ }
+
+ /* Large requests are trimmed */
+ if (requested_size > psize)
+ size = psize;
+ else
+ size = requested_size;
+
+ if (size + offset > psize)
+ size = psize - offset;
+
+ if (size < 0) {
+ pr_log(LOG_WARNING, "PNOR: partition %s(size 0x%x) "
+ "read size (0x%zx) and offset (0x%lx) "
+ "out of bounds",
+ name, psize, requested_size, offset);
+ return -ERANGE;
+ }
+
+ switch (op) {
+ case PNOR_OP_READ:
+ rc = mtd_read(pnor, data, pstart + offset, size);
+ break;
+ case PNOR_OP_WRITE:
+ rc = mtd_write(pnor, data, pstart + offset, size);
+ break;
+ default:
+ rc = -EIO;
+ pr_log(LOG_ERR, "PNOR: Invalid operation");
+ goto out;
+ }
+
+ if (rc < 0)
+ pr_log(LOG_ERR, "PNOR: MTD operation failed");
+ else if (rc != size)
+ pr_log(LOG_WARNING, "PNOR: mtd operation "
+ "returned %d, expected %d",
+ rc, size);
+
+out:
+ return rc;
+}
diff --git a/roms/skiboot/external/opal-prd/pnor.h b/roms/skiboot/external/opal-prd/pnor.h
new file mode 100644
index 000000000..aaaf9c487
--- /dev/null
+++ b/roms/skiboot/external/opal-prd/pnor.h
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/* Copyright 2015-2017 IBM Corp */
+
+#ifndef PNOR_H
+#define PNOR_H
+
+#include <libflash/libffs.h>
+#include <libflash/blocklevel.h>
+
+struct pnor {
+ char *path;
+ struct ffs_handle *ffsh;
+ uint64_t size;
+ uint32_t erasesize;
+ struct blocklevel_device *bl;
+};
+
+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);
+extern bool pnor_available(struct pnor *pnor);
+
+#endif /*PNOR_H*/
diff --git a/roms/skiboot/external/opal-prd/test/test_pnor.c b/roms/skiboot/external/opal-prd/test/test_pnor.c
new file mode 100644
index 000000000..bc7234ecf
--- /dev/null
+++ b/roms/skiboot/external/opal-prd/test/test_pnor.c
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/* Copyright 2013-2015 IBM Corp. */
+
+#include <stdarg.h>
+#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);
+
+void pr_log(int priority, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+}
+
+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/roms/skiboot/external/opal-prd/test/test_pnor_ops.c b/roms/skiboot/external/opal-prd/test/test_pnor_ops.c
new file mode 100644
index 000000000..913f2ecf7
--- /dev/null
+++ b/roms/skiboot/external/opal-prd/test/test_pnor_ops.c
@@ -0,0 +1,238 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/* Copyright 2015-2016 IBM Corp */
+
+#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 disappears 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/roms/skiboot/external/opal-prd/thunk.S b/roms/skiboot/external/opal-prd/thunk.S
new file mode 100644
index 000000000..46355c033
--- /dev/null
+++ b/roms/skiboot/external/opal-prd/thunk.S
@@ -0,0 +1,213 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/* Copyright 2015-2017 IBM Corp */
+
+#include <endian.h>
+#include <asm/unistd.h>
+
+#ifndef __NR_switch_endian
+#define __NR_switch_endian 363
+#endif
+
+/* a constant to use in the SI field of a little-endian D-form instruction */
+#define le_si16(x) (((x & 0xff) << 24) | ((x & 0xff00) << 8))
+
+ .text
+
+ /*
+ * Call into a HBRT BE function
+ * Func desc (opd) will be in BE
+ * Use ldbrx to load from opd
+ */
+
+call_be:
+
+ /* 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
+
+ mtlr %r0
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+ /* Switch to the "other endian" */
+ li %r0,__NR_switch_endian
+ sc
+
+ /* Branch to LR */
+ .long 0x2100804e /* (byteswapped blrl) */
+
+ /* Switch endian back */
+ .long 0x00000038 | le_si16(__NR_switch_endian)
+ /* byteswapped li %r0,__NR_switch_endian */
+ .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@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)
+ CALL_THUNK(apply_attr_override, 12)
+ CALL_THUNK(mfg_htmgt_pass_thru, 13)
+ CALL_THUNK(run_command, 14)
+ CALL_THUNK(verify_container, 15)
+ CALL_THUNK(sbe_message_passing, 16)
+ CALL_THUNK(load_pm_complex, 17)
+ CALL_THUNK(start_pm_complex, 18)
+ CALL_THUNK(reset_pm_complex, 19)
+ CALL_THUNK(get_ipoll_events, 20)
+ CALL_THUNK(firmware_notify, 21)
+ CALL_THUNK(prepare_hbrt_update, 22)
+
+ .globl call_hbrt_init
+call_hbrt_init:
+ ld %r11,hbrt_entry@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 %r12
+ blrl
+
+ /* Restore stack and LR */
+ ld %r0,(32+16)(%r1)
+ addi %r1,%r1,32
+ mtlr %r0
+
+ /* Switch endian back to BE */
+ li %r0,__NR_switch_endian
+ 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 r12 and jump to call_le
+ */
+#define CALLBACK_THUNK(name) \
+ .pushsection ".text","ax" ;\
+ .globl name##_thunk ;\
+name##_thunk: ;\
+ .long 0x00000038 | le_si16(__NR_switch_endian) ;\
+ /* byteswapped li %r0,__NR_switch_endian */ ;\
+ .long 0x02000044 /* byteswapped sc */ ;\
+ ld %r12,name@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
+
+#define DISABLED_THUNK(name) .llong 0x0
+
+ /* 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
+ .globl __hinterface_start
+__hinterface_start:
+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)
+ DISABLED_THUNK(hservice_send_error_log)
+ CALLBACK_THUNK(hservice_scom_read)
+ CALLBACK_THUNK(hservice_scom_write)
+ DISABLED_THUNK(hservice_lid_load)
+ DISABLED_THUNK(hservice_lid_unload)
+ CALLBACK_THUNK(hservice_get_reserved_mem)
+ CALLBACK_THUNK(hservice_wakeup)
+ CALLBACK_THUNK(hservice_nanosleep)
+ DISABLED_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)
+ CALLBACK_THUNK(hservice_get_interface_capabilities)
+ DISABLED_THUNK(hservice_map_phys_mem)
+ DISABLED_THUNK(hservice_unmap_phys_mem)
+ DISABLED_THUNK(hservice_hcode_scom_update)
+ CALLBACK_THUNK(hservice_firmware_request)
+.globl __hinterface_pad
+__hinterface_pad:
+ /* Reserved space for future growth */
+ .space 27*8,0
+.globl __hinterface_end
+__hinterface_end:
+ /* Eye catcher for debugging */
+ .llong 0xdeadbeef
+