discover/platform-powerpc: read bootdev config from IPMI boot mailbox
authorSamuel Mendoza-Jonas <sam@mendozajonas.com>
Thu, 29 Nov 2018 04:36:31 +0000 (15:36 +1100)
committerSamuel Mendoza-Jonas <sam@mendozajonas.com>
Thu, 13 Dec 2018 00:26:10 +0000 (11:26 +1100)
The IPMI Get System Boot Options commands includes parameter 7, the
"boot initiator mailbox". This can be used to hold arbitrary data to
influence the boot order.

Use this to provide an alternate bootdev configuration to Petitboot that
will override the one saved to NVRAM. This provides more fine grained
override options than the existing device-type based overrides.

Signed-off-by: Samuel Mendoza-Jonas <sam@mendozajonas.com>
discover/platform-powerpc.c
discover/platform.c
discover/platform.h
lib/types/types.h

index a43dd676494a18e28dab0ea53b7029898644e07d..5d7cc597697f683fda11a439a0e2cc278d24f493 100644 (file)
@@ -8,6 +8,7 @@
 #include <sys/wait.h>
 #include <sys/stat.h>
 #include <asm/byteorder.h>
+#include <limits.h>
 
 #include <file/file.h>
 #include <talloc/talloc.h>
@@ -28,12 +29,18 @@ static const char *devtree_dir = "/proc/device-tree/";
 struct platform_powerpc {
        struct param_list *params;
        struct ipmi     *ipmi;
+       char            *ipmi_mailbox_original_config;
        int             (*get_ipmi_bootdev)(
                                struct platform_powerpc *platform,
                                uint8_t *bootdev, bool *persistent);
        int             (*clear_ipmi_bootdev)(
                                struct platform_powerpc *platform,
                                bool persistent);
+       int             (*get_ipmi_boot_mailbox)(
+                               struct platform_powerpc *platform,
+                               char **buf);
+       int             (*clear_ipmi_boot_mailbox)(
+                               struct platform_powerpc *platform);
        int             (*set_os_boot_sensor)(
                                struct platform_powerpc *platform);
        void            (*get_platform_versions)(struct system_info *info);
@@ -429,6 +436,211 @@ static int get_ipmi_bootdev_ipmi(struct platform_powerpc *platform,
        return 0;
 }
 
+static int get_ipmi_boot_mailbox_block(struct platform_powerpc *platform,
+               char *buf, uint8_t block)
+{
+       size_t blocksize = 16;
+       uint8_t resp[3 + 16];
+       uint16_t resp_len;
+       char *debug_buf;
+       int rc;
+       uint8_t req[] = {
+               0x07,  /* parameter selector: boot initiator mailbox */
+               block, /* 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)) {
+               if (resp_len < 3) {
+                       pb_log("platform: unexpected length (%d) in "
+                                       "boot options mailbox response\n",
+                                       resp_len);
+                       return -1;
+               }
+
+               if (resp_len == 4) {
+                       pb_debug_fn("block %hu empty\n", block);
+                       return 0;
+               }
+
+               blocksize = sizeof(resp) - 3;
+               pb_debug_fn("Mailbox block %hu returns only %zu bytes in block\n",
+                               block, blocksize);
+       }
+
+       debug_buf = format_buffer(platform, resp, resp_len);
+       pb_debug_fn("IPMI bootdev mailbox block %hu:\n%s\n", block, debug_buf);
+       talloc_free(debug_buf);
+
+       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 mailbox response\n", resp[0]);
+               return -1;
+       }
+
+       /* check for valid paramters */
+       if (resp[2] & 0x80) {
+               pb_debug("platform: boot mailbox parameters are invalid/locked\n");
+               return -1;
+       }
+
+       memcpy(buf, &resp[3], blocksize);
+
+       return blocksize;
+}
+
+static int get_ipmi_boot_mailbox(struct platform_powerpc *platform,
+               char **buf)
+{
+       char *mailbox_buffer, *prefix;
+       const size_t blocksize = 16;
+       char block_buffer[blocksize];
+       size_t mailbox_size;
+       int content_size;
+       uint8_t i;
+       int rc;
+
+       mailbox_buffer = NULL;
+       mailbox_size = 0;
+
+       /*
+        * The BMC may hold up to 255 blocks of data but more likely the number
+        * will be closer to the minimum of 5 set by the specification and error
+        * on higher numbers.
+        */
+       for (i = 0; i < UCHAR_MAX; i++) {
+               rc = get_ipmi_boot_mailbox_block(platform, block_buffer, i);
+               if (rc < 3 && i == 0) {
+                       /*
+                        * Immediate failure, no blocks read or missing IANA
+                        * number.
+                        */
+                       return -1;
+               }
+               if (rc < 1) {
+                       /* Error or no bytes read */
+                       break;
+               }
+
+               if (i == 0) {
+                       /*
+                        * The first three bytes of block zero are an IANA
+                        * Enterprise ID number. Check it matches the IBM
+                        * number, '2'.
+                        */
+                       if (block_buffer[0] != 0x02 ||
+                               block_buffer[1] != 0x00 ||
+                               block_buffer[2] != 0x00) {
+                               pb_log_fn("IANA number unrecognised: 0x%x:0x%x:0x%x\n",
+                                               block_buffer[0],
+                                               block_buffer[1],
+                                               block_buffer[2]);
+                               return -1;
+                       }
+               }
+
+               mailbox_buffer = talloc_realloc(platform, mailbox_buffer,
+                               char, mailbox_size + rc);
+               if (!mailbox_buffer) {
+                       pb_log_fn("Failed to allocate mailbox buffer\n");
+                       return -1;
+               }
+               memcpy(mailbox_buffer + mailbox_size, block_buffer, rc);
+               mailbox_size += rc;
+       }
+
+       if (i < 5)
+               pb_log_fn("Only %hu blocks read, spec requires at least 5.\n"
+                         "Send a bug report to your preferred BMC vendor!\n",
+                         i);
+       else
+               pb_debug_fn("%hu blocks read (%zu bytes)\n", i, mailbox_size);
+
+       if (mailbox_size < 3 + strlen("petitboot,bootdevs="))
+               return -1;
+
+       prefix = talloc_strndup(mailbox_buffer, mailbox_buffer + 3,
+                       strlen("petitboot,bootdevs="));
+       if (!prefix) {
+               pb_log_fn("Couldn't check prefix\n");
+               talloc_free(mailbox_buffer);
+               return -1;
+       }
+
+       if (strncmp(prefix, "petitboot,bootdevs=",
+                               strlen("petitboot,bootdevs=")) != 0 ) {
+               /* Empty or garbage */
+               pb_debug_fn("Buffer looks unconfigured\n");
+               talloc_free(mailbox_buffer);
+               *buf = NULL;
+               return 0;
+       }
+
+       /* Don't include IANA number in buffer */
+       content_size = mailbox_size - 3 - strlen("petitboot,bootdevs=");
+       *buf = talloc_memdup(platform,
+                       mailbox_buffer + 3 + strlen("petitboot,bootdevs="),
+                       content_size + 1);
+       (*buf)[content_size] = '\0';
+
+       talloc_free(mailbox_buffer);
+       return 0;
+}
+
+static int clear_ipmi_boot_mailbox(struct platform_powerpc *platform)
+{
+       uint8_t req[18] = {0}; /* req (2) + blocksize (16) */
+       uint16_t resp_len;
+       uint8_t resp[1];
+       uint8_t i;
+       int rc;
+
+       req[0] = 0x07;  /* parameter selector: boot initiator mailbox */
+
+       resp_len = sizeof(resp);
+
+       for (i = 0; i < UCHAR_MAX; i++) {
+               req[1] = i; /* set selector */
+               rc = ipmi_transaction(platform->ipmi, IPMI_NETFN_CHASSIS,
+                               IPMI_CMD_CHASSIS_SET_SYSTEM_BOOT_OPTIONS,
+                               req, sizeof(req),
+                               resp, &resp_len,
+                               ipmi_timeout);
+
+               if (rc || resp[0]) {
+                       if (i == 0) {
+                               pb_log_fn("error clearing IPMI boot mailbox, "
+                                               "rc %d resp[0] %hu\n",
+                                               rc, resp[0]);
+                               return -1;
+                       }
+                       break;
+               }
+       }
+
+       pb_debug_fn("Cleared %hu blocks\n", i);
+
+       return 0;
+}
+
 static int set_ipmi_os_boot_sensor(struct platform_powerpc *platform)
 {
        int sensor_number;
@@ -606,6 +818,31 @@ static int load_config(struct platform *p, struct config *config)
        if (rc)
                pb_log_fn("Failed to parse nvram\n");
 
+       /*
+        * If we have an IPMI mailbox configuration available use it instead of
+        * the boot order found in NVRAM.
+        */
+       if (platform->get_ipmi_boot_mailbox) {
+               char *mailbox;
+               struct param *param;
+               rc = platform->get_ipmi_boot_mailbox(platform, &mailbox);
+               if (!rc && mailbox) {
+                       platform->ipmi_mailbox_original_config =
+                               talloc_strdup(
+                                       platform,
+                                       param_list_get_value(
+                                               platform->params, "petitboot,bootdevs"));
+                       param_list_set(platform->params, "petitboot,bootdevs",
+                                       mailbox, false);
+                       param = param_list_get_param(platform->params,
+                                       "petitboot,bootdevs");
+                       /* Avoid writing this to NVRAM */
+                       param->modified = false;
+                       config->ipmi_bootdev_mailbox = true;
+                       talloc_free(mailbox);
+               }
+       }
+
        config_populate_all(config, platform->params);
 
        if (platform->get_ipmi_bootdev) {
@@ -639,6 +876,7 @@ static int save_config(struct platform *p, struct config *config)
 {
        struct platform_powerpc *platform = to_platform_powerpc(p);
        struct config *defaults;
+       struct param *param;
 
        if (config->ipmi_bootdev == IPMI_BOOTDEV_INVALID &&
            platform->clear_ipmi_bootdev) {
@@ -648,6 +886,23 @@ static int save_config(struct platform *p, struct config *config)
                config->ipmi_bootdev_persistent = false;
        }
 
+       if (!config->ipmi_bootdev_mailbox &&
+                       platform->ipmi_mailbox_original_config) {
+               param = param_list_get_param(platform->params,
+                               "petitboot,bootdevs");
+               /* Restore old boot order if unmodified */
+               if (!param->modified) {
+                       param_list_set(platform->params, "petitboot,bootdevs",
+                                       platform->ipmi_mailbox_original_config,
+                                       false);
+                       param->modified = false;
+                       config_populate_bootdev(config, platform->params);
+               }
+               platform->clear_ipmi_boot_mailbox(platform);
+               talloc_free(platform->ipmi_mailbox_original_config);
+               platform->ipmi_mailbox_original_config = NULL;
+       }
+
        defaults = talloc_zero(platform, struct config);
        config_set_defaults(defaults);
 
@@ -744,6 +999,8 @@ static bool probe(struct platform *p, void *ctx)
                platform->ipmi = ipmi_open(platform);
                platform->get_ipmi_bootdev = get_ipmi_bootdev_ipmi;
                platform->clear_ipmi_bootdev = clear_ipmi_bootdev_ipmi;
+               platform->get_ipmi_boot_mailbox = get_ipmi_boot_mailbox;
+               platform->clear_ipmi_boot_mailbox = clear_ipmi_boot_mailbox;
                platform->set_os_boot_sensor = set_ipmi_os_boot_sensor;
        } else if (!stat(sysparams_dir, &statbuf)) {
                pb_debug("platform: using sysparams for IPMI paramters\n");
index 7712ef14028772c07f39eb4d2d2a320eef1eba45..8ce52fc8c539f8d567581822bc487f1e266121ad 100644 (file)
@@ -468,7 +468,7 @@ static int read_bootdev(void *ctx, char **pos, struct autoboot_option *opt)
        return rc;
 }
 
-static void config_populate_bootdev(struct config *config,
+void config_populate_bootdev(struct config *config,
        const struct param_list *pl)
 {
        struct autoboot_option *opt, *new = NULL;
index f7d3d1c48324161f5ebf62fe6f3cce38c1b0a906..5a5c9907d04679ddeb5c98c1318423398d4d53f9 100644 (file)
@@ -31,6 +31,8 @@ const struct config *config_get(void);
 int config_set(struct config *config);
 void config_set_defaults(struct config *config);
 void config_set_autoboot(bool autoboot_enabled);
+void config_populate_bootdev(struct config *config,
+       const struct param_list *pl);
 void config_populate_all(struct config *config, const struct param_list *pl);
 
 void params_update_network_values(struct param_list *pl,
index 39760d91848d150b9c01cc50bc609f77d2ceaf14..9d83d87dee748f4b3553759285424167e2e858af 100644 (file)
@@ -188,6 +188,7 @@ struct config {
 
        unsigned int            ipmi_bootdev;
        bool                    ipmi_bootdev_persistent;
+       bool                    ipmi_bootdev_mailbox;
 
        char                    *http_proxy;
        char                    *https_proxy;