discover/platform-powerpc: Implement direct IPMI interface
authorJeremy Kerr <jk@ozlabs.org>
Mon, 15 Dec 2014 01:47:39 +0000 (09:47 +0800)
committerJeremy Kerr <jk@ozlabs.org>
Mon, 15 Dec 2014 07:47:40 +0000 (15:47 +0800)
This change adds a direct IPMI interface to the /dev/ipmi0 device node,
which is present on OpenPower machines.

Signed-off-by: Jeremy Kerr <jk@ozlabs.org>
discover/ipmi.c
discover/ipmi.h
discover/platform-powerpc.c

index b8f18301ecc4c75aaccdb0cdc62c84aba898348e..710a47d1c6e8108a8c0d923a8b3df82bfa885dee 100644 (file)
@@ -1,6 +1,28 @@
 
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include <linux/ipmi.h>
+
+#include <log/log.h>
+#include <talloc/talloc.h>
+
 #include "ipmi.h"
 
+struct ipmi {
+       int     fd;
+       long    seq;
+};
+
+static const char *ipmi_devnode = "/dev/ipmi0";
+
 bool ipmi_bootdev_is_valid(int x)
 {
        switch (x) {
@@ -16,8 +38,166 @@ bool ipmi_bootdev_is_valid(int x)
        return false;
 }
 
+static int ipmi_send(struct ipmi *ipmi, uint8_t netfn, uint8_t cmd,
+               uint8_t *buf, uint16_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 = ipmi->seq++;
+
+       req.msg.data = buf;
+       req.msg.data_len = len;
+       req.msg.netfn = netfn;
+       req.msg.cmd = cmd;
+
+       rc = ioctl(ipmi->fd, IPMICTL_SEND_COMMAND, &req);
+       if (rc < 0) {
+               pb_log("IPMI: send (netfn %d, cmd %d, %d bytes) failed: %m\n",
+                               netfn, cmd, len);
+               return -1;
+       }
+
+       return 0;
+}
+
+static int ipmi_recv(struct ipmi *ipmi, uint8_t *netfn, uint8_t *cmd,
+               long *seq, uint8_t *buf, uint16_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(ipmi->fd, IPMICTL_RECEIVE_MSG_TRUNC, &recv);
+       if (rc < 0 && errno != EMSGSIZE) {
+               pb_log("IPMI: recv (%d bytes) failed: %m\n", *len);
+               return -1;
+       } else if (rc < 0 && errno == EMSGSIZE) {
+               pb_debug("IPMI: truncated message (netfn %d, cmd %d, "
+                               "size %d), 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 ipmi_transaction(struct ipmi *ipmi, uint8_t netfn, uint8_t cmd,
+               uint8_t *req_buf, uint16_t req_len,
+               uint8_t *resp_buf, uint16_t *resp_len,
+               int timeout_ms)
+{
+       struct timeval start, now, delta;
+       struct pollfd pollfds[1];
+       int expired_ms, rc;
+
+       rc = ipmi_send(ipmi, netfn, cmd, req_buf, req_len);
+       if (rc)
+               return rc;
+
+       pollfds[0].fd = ipmi->fd;
+       pollfds[0].events = POLLIN;
+
+       gettimeofday(&start, NULL);
+       expired_ms = 0;
+
+       for (;;) {
+               uint8_t resp_netfn, resp_cmd;
+               long seq;
+
+               rc = poll(pollfds, 1, timeout_ms - expired_ms);
+
+               if (rc < 0) {
+                       pb_log("IPMI: poll() error %m");
+                       break;
+               }
+               if (rc == 0) {
+                       pb_log("IPMI: timeout waiting for response "
+                                       "(netfn %d, cmd %d)\n", netfn, cmd);
+                       break;
+               }
+
+               if (!(pollfds[0].revents & POLLIN)) {
+                       pb_log("IPMI: unexpected fd status from poll?\n");
+                       break;
+               }
+
+               rc = ipmi_recv(ipmi, &resp_netfn, &resp_cmd, &seq,
+                               resp_buf, resp_len);
+               if (rc)
+                       break;
+
+               if (seq != ipmi->seq - 1) {
+                       pb_log("IPMI: out-of-sequence reply: "
+                                       "exp %ld, got %ld\n",
+                                       ipmi->seq, seq);
+
+                       if (timeout_ms) {
+                               gettimeofday(&now, NULL);
+                               timersub(&now, &start, &delta);
+                               expired_ms = (delta.tv_sec * 1000) +
+                                               (delta.tv_usec / 1000);
+
+                               if (expired_ms >= timeout_ms)
+                                       break;
+                       }
+               } else {
+                       pb_debug("IPMI: netfn(%x->%x), cmd(%x->%x)\n",
+                                       netfn, resp_netfn, cmd, resp_cmd);
+                       return 0;
+               }
+       }
+
+       return -1;
+}
+
+static int ipmi_destroy(void *p)
+{
+       struct ipmi *ipmi = p;
+       close(ipmi->fd);
+       return 1;
+}
+
+struct ipmi *ipmi_open(void *ctx)
+{
+       struct ipmi *ipmi;
+       int fd;
+
+       fd = open(ipmi_devnode, O_RDWR);
+       if (fd < 0) {
+               pb_log("IPMI: can't open IPMI device %s: %m\n", ipmi_devnode);
+               return NULL;
+       }
+
+       ipmi = talloc(ctx, struct ipmi);
+       ipmi->fd = fd;
+       ipmi->seq = 0;
+
+       talloc_set_destructor(ipmi, ipmi_destroy);
+
+       return ipmi;
+}
+
 bool ipmi_present(void)
 {
-       return false;
+       return !access(ipmi_devnode, R_OK | W_OK);
 }
 
index 157cca898685f36e906ba1969f4a705457dbd4ab..e60ff619215984c58da52293df3315ad9de773ce 100644 (file)
@@ -4,16 +4,33 @@
 #include <stdbool.h>
 #include <stdint.h>
 
+enum ipmi_netfn {
+       IPMI_NETFN_CHASSIS = 0x0,
+};
+
+enum ipmi_cmd {
+       IPMI_CMD_CHASSIS_SET_SYSTEM_BOOT_OPTIONS        = 0x08,
+       IPMI_CMD_CHASSIS_GET_SYSTEM_BOOT_OPTIONS        = 0x09,
+};
+
 enum ipmi_bootdev {
-       IPMI_BOOTDEV_NONE = 0x0,
-       IPMI_BOOTDEV_NETWORK = 0x1,
+       IPMI_BOOTDEV_NONE = 0x00,
+       IPMI_BOOTDEV_NETWORK = 0x01,
        IPMI_BOOTDEV_DISK = 0x2,
        IPMI_BOOTDEV_SAFE = 0x3,
        IPMI_BOOTDEV_CDROM = 0x5,
        IPMI_BOOTDEV_SETUP = 0x6,
 };
 
-bool ipmi_bootdev_is_valid(int x);
+struct ipmi;
+
 bool ipmi_present(void);
+bool ipmi_bootdev_is_valid(int x);
+struct ipmi *ipmi_open(void *ctx);
+
+int ipmi_transaction(struct ipmi *ipmi, uint8_t netfn, uint8_t cmd,
+               uint8_t *req_buf, uint16_t req_len,
+               uint8_t *resp_buf, uint16_t *resp_len,
+               int timeout_ms);
 
 #endif /* _IPMI_H */
index 5f6772f6b455dd71d5eb0d629332b6217c4dc372..0440eb4d5d0e78a03e67f3f3d2ae6076bd40c2d5 100644 (file)
@@ -21,6 +21,7 @@
 static const char *partition = "common";
 static const char *sysparams_dir = "/sys/firmware/opal/sysparams/";
 static const char *devtree_dir = "/proc/device-tree/";
+static const int ipmi_timeout = 500; /* milliseconds */
 
 struct param {
        char                    *name;
@@ -31,6 +32,8 @@ struct param {
 
 struct platform_powerpc {
        struct list     params;
+       struct ipmi     *ipmi;
+       bool            ipmi_bootdev_persistent;
        int             (*get_ipmi_bootdev)(
                                struct platform_powerpc *platform,
                                uint8_t *bootdev, bool *persistent);
@@ -705,6 +708,91 @@ static int get_ipmi_bootdev_sysparams(
        return 0;
 }
 
+static int clear_ipmi_bootdev_ipmi(struct platform_powerpc *platform)
+{
+       uint16_t resp_len;
+       uint8_t resp[1];
+       uint8_t req[] = {
+               0x05, /* parameter selector: boot flags */
+               0x80, /* data 1: valid */
+               0x00, /* data 2: bootdev: no override */
+               0x00, /* data 3: system defaults */
+               0x00, /* data 4: no request for shared mode, mux defaults */
+               0x00, /* data 5: no instance request */
+       };
+
+       resp_len = sizeof(resp);
+
+       ipmi_transaction(platform->ipmi, IPMI_NETFN_CHASSIS,
+                       IPMI_CMD_CHASSIS_SET_SYSTEM_BOOT_OPTIONS,
+                       req, sizeof(req),
+                       resp, &resp_len,
+                       ipmi_timeout);
+       return 0;
+}
+
+static int get_ipmi_bootdev_ipmi(struct platform_powerpc *platform,
+               uint8_t *bootdev, bool *persistent)
+{
+       uint16_t resp_len;
+       uint8_t resp[8];
+       int rc;
+       uint8_t req[] = {
+               0x05, /* parameter selector: boot flags */
+               0x00, /* no set selector */
+               0x00, /* no block selector */
+       };
+
+       resp_len = sizeof(resp);
+       rc = ipmi_transaction(platform->ipmi, IPMI_NETFN_CHASSIS,
+                       IPMI_CMD_CHASSIS_GET_SYSTEM_BOOT_OPTIONS,
+                       req, sizeof(req),
+                       resp, &resp_len,
+                       ipmi_timeout);
+       if (rc) {
+               pb_log("platform: error reading IPMI boot options\n");
+               return -1;
+       }
+
+       if (resp_len != sizeof(resp)) {
+               pb_log("platform: unexpected length (%d) in "
+                               "boot options response\n", resp_len);
+               return -1;
+       }
+
+       if (resp[0] != 0) {
+               pb_log("platform: non-zero completion code %d from IPMI req\n",
+                               resp[0]);
+               return -1;
+       }
+
+       /* check for correct parameter version */
+       if ((resp[1] & 0xf) != 0x1) {
+               pb_log("platform: unexpected version (0x%x) in "
+                               "boot options response\n", resp[0]);
+               return -1;
+       }
+
+       /* check for valid paramters */
+       if (resp[2] & 0x80) {
+               pb_debug("platform: boot options are invalid/locked\n");
+               return -1;
+       }
+
+       *persistent = false;
+
+       /* check for valid flags */
+       if (!(resp[3] & 0x80)) {
+               pb_debug("platform: boot flags are invalid, ignoring\n");
+               return 0;
+       }
+
+       *persistent = resp[3] & 0x40;
+       *bootdev = (resp[4] >> 2) & 0x0f;
+       return 0;
+}
+
+
 static int load_config(struct platform *p, struct config *config)
 {
        struct platform_powerpc *platform = to_platform_powerpc(p);
@@ -792,7 +880,13 @@ static bool probe(struct platform *p, void *ctx)
 
        p->platform_data = platform;
 
-       if (!stat(sysparams_dir, &statbuf)) {
+       if (ipmi_present()) {
+               pb_debug("platform: using direct IPMI for IPMI paramters\n");
+               platform->ipmi = ipmi_open(platform);
+               platform->get_ipmi_bootdev = get_ipmi_bootdev_ipmi;
+               platform->clear_ipmi_bootdev = clear_ipmi_bootdev_ipmi;
+
+       } else if (!stat(sysparams_dir, &statbuf)) {
                pb_debug("platform: using sysparams for IPMI paramters\n");
                platform->get_ipmi_bootdev = get_ipmi_bootdev_sysparams;
                platform->clear_ipmi_bootdev = clear_ipmi_bootdev_sysparams;
@@ -800,6 +894,7 @@ static bool probe(struct platform *p, void *ctx)
        } else {
                pb_log("platform: no IPMI parameter support\n");
        }
+
        return true;
 }