diff options
author | Angelos Mouzakitis <a.mouzakitis@virtualopensystems.com> | 2023-10-10 14:33:42 +0000 |
---|---|---|
committer | Angelos Mouzakitis <a.mouzakitis@virtualopensystems.com> | 2023-10-10 14:33:42 +0000 |
commit | af1a266670d040d2f4083ff309d732d648afba2a (patch) | |
tree | 2fc46203448ddcc6f81546d379abfaeb323575e9 /roms/skiboot/external/opal-prd | |
parent | e02cda008591317b1625707ff8e115a4841aa889 (diff) |
Change-Id: Iaf8d18082d3991dec7c0ebbea540f092188eb4ec
Diffstat (limited to 'roms/skiboot/external/opal-prd')
-rw-r--r-- | roms/skiboot/external/opal-prd/.gitignore | 5 | ||||
-rw-r--r-- | roms/skiboot/external/opal-prd/Makefile | 75 | ||||
-rw-r--r-- | roms/skiboot/external/opal-prd/config.h | 24 | ||||
-rw-r--r-- | roms/skiboot/external/opal-prd/hostboot-interface.h | 759 | ||||
-rw-r--r-- | roms/skiboot/external/opal-prd/i2c.c | 257 | ||||
-rw-r--r-- | roms/skiboot/external/opal-prd/i2c.h | 17 | ||||
-rw-r--r-- | roms/skiboot/external/opal-prd/module.c | 49 | ||||
-rw-r--r-- | roms/skiboot/external/opal-prd/module.h | 10 | ||||
-rw-r--r-- | roms/skiboot/external/opal-prd/opal-prd.8 | 78 | ||||
-rw-r--r-- | roms/skiboot/external/opal-prd/opal-prd.c | 2799 | ||||
-rw-r--r-- | roms/skiboot/external/opal-prd/opal-prd.h | 15 | ||||
-rw-r--r-- | roms/skiboot/external/opal-prd/opal-prd.service | 11 | ||||
-rw-r--r-- | roms/skiboot/external/opal-prd/pnor.c | 217 | ||||
-rw-r--r-- | roms/skiboot/external/opal-prd/pnor.h | 31 | ||||
-rw-r--r-- | roms/skiboot/external/opal-prd/test/test_pnor.c | 46 | ||||
-rw-r--r-- | roms/skiboot/external/opal-prd/test/test_pnor_ops.c | 238 | ||||
-rw-r--r-- | roms/skiboot/external/opal-prd/thunk.S | 213 |
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(®_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 + |