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