discover: Add platform-dummy
[petitboot] / discover / platform-powerpc.c
1
2 #include <assert.h>
3 #include <string.h>
4 #include <stdlib.h>
5 #include <errno.h>
6 #include <fcntl.h>
7 #include <sys/types.h>
8 #include <sys/wait.h>
9 #include <sys/stat.h>
10 #include <asm/byteorder.h>
11
12 #include <file/file.h>
13 #include <talloc/talloc.h>
14 #include <list/list.h>
15 #include <log/log.h>
16 #include <process/process.h>
17
18 #include "hostboot.h"
19 #include "platform.h"
20 #include "ipmi.h"
21 #include "dt.h"
22
23 static const char *partition = "common";
24 static const char *sysparams_dir = "/sys/firmware/opal/sysparams/";
25 static const char *devtree_dir = "/proc/device-tree/";
26
27 struct platform_powerpc {
28         struct param_list params;
29         struct ipmi     *ipmi;
30         bool            ipmi_bootdev_persistent;
31         int             (*get_ipmi_bootdev)(
32                                 struct platform_powerpc *platform,
33                                 uint8_t *bootdev, bool *persistent);
34         int             (*clear_ipmi_bootdev)(
35                                 struct platform_powerpc *platform,
36                                 bool persistent);
37         int             (*set_os_boot_sensor)(
38                                 struct platform_powerpc *platform);
39         void            (*get_platform_versions)(struct system_info *info);
40 };
41
42 #define to_platform_powerpc(p) \
43         (struct platform_powerpc *)(p->platform_data)
44
45 static int parse_nvram_params(struct platform_powerpc *platform,
46                 char *buf, int len)
47 {
48         char *pos, *name, *value;
49         unsigned int paramlen;
50         int i, count;
51
52         /* discard 2 header lines:
53          * "common" partiton"
54          * ------------------
55          */
56         pos = buf;
57         count = 0;
58
59         for (i = 0; i < len; i++) {
60                 if (pos[i] == '\n')
61                         count++;
62                 if (count == 2)
63                         break;
64         }
65
66         if (i == len) {
67                 fprintf(stderr, "failure parsing nvram output\n");
68                 return -1;
69         }
70
71         for (pos = buf + i; pos < buf + len; pos += paramlen + 1) {
72                 unsigned int namelen;
73                 char *newline;
74
75                 newline = strchr(pos, '\n');
76                 if (!newline)
77                         break;
78
79                 *newline = '\0';
80
81                 paramlen = strlen(pos);
82
83                 name = pos;
84                 value = strchr(pos, '=');
85                 if (!value)
86                         continue;
87
88                 namelen = value - name;
89                 if (namelen == 0)
90                         continue;
91
92                 if (!param_list_is_known_n(&platform->params, name, namelen))
93                         continue;
94
95                 *value = '\0';
96                 value++;
97
98                 param_list_set(&platform->params, name, value, false);
99         }
100
101         return 0;
102 }
103
104 static int parse_nvram(struct platform_powerpc *platform)
105 {
106         struct process_stdout *stdout;
107         const char *argv[5];
108         int rc;
109
110         argv[0] = "nvram";
111         argv[1] = "--print-config";
112         argv[2] = "--partition";
113         argv[3] = partition;
114         argv[4] = NULL;
115
116         rc = process_get_stdout_argv(NULL, &stdout, argv);
117
118         if (rc) {
119                 fprintf(stderr, "nvram process returned "
120                                 "non-zero exit status\n");
121                 rc = -1;
122         } else {
123                 rc = parse_nvram_params(platform, stdout->buf, stdout->len);
124         }
125
126         talloc_free(stdout);
127         return rc;
128 }
129
130 static int write_nvram(struct platform_powerpc *platform)
131 {
132         struct process *process;
133         struct param *param;
134         const char *argv[6];
135         int rc = 0;
136
137         argv[0] = "nvram";
138         argv[1] = "--update-config";
139         argv[2] = NULL;
140         argv[3] = "--partition";
141         argv[4] = partition;
142         argv[5] = NULL;
143
144         process = process_create(platform);
145         process->path = "nvram";
146         process->argv = argv;
147
148         param_list_for_each(&platform->params, param) {
149                 char *paramstr;
150
151                 if (!param->modified)
152                         continue;
153
154                 paramstr = talloc_asprintf(platform, "%s=%s",
155                                 param->name, param->value);
156                 argv[2] = paramstr;
157
158                 rc = process_run_sync(process);
159
160                 talloc_free(paramstr);
161
162                 if (rc || !process_exit_ok(process)) {
163                         rc = -1;
164                         pb_log("nvram update process returned "
165                                         "non-zero exit status\n");
166                         break;
167                 }
168         }
169
170         process_release(process);
171         return rc;
172 }
173
174 static void params_update_all(struct param_list *pl,
175         const struct config *config, const struct config *defaults)
176 {
177         char *tmp = NULL;
178         const char *val;
179
180         if (config->autoboot_enabled == defaults->autoboot_enabled)
181                 val = "";
182         else
183                 val = config->autoboot_enabled ? "true" : "false";
184
185         param_list_set_non_empty(pl, "auto-boot?", val, true);
186
187         if (config->autoboot_timeout_sec == defaults->autoboot_timeout_sec)
188                 val = "";
189         else
190                 val = tmp = talloc_asprintf(pl, "%d",
191                         config->autoboot_timeout_sec);
192
193         param_list_set_non_empty(pl, "petitboot,timeout", val, true);
194         if (tmp)
195                 talloc_free(tmp);
196
197         val = config->lang ?: "";
198         param_list_set_non_empty(pl, "petitboot,language", val, true);
199
200         if (config->allow_writes == defaults->allow_writes)
201                 val = "";
202         else
203                 val = config->allow_writes ? "true" : "false";
204         param_list_set_non_empty(pl, "petitboot,write?", val, true);
205
206         if (!config->manual_console) {
207                 val = config->boot_console ?: "";
208                 param_list_set_non_empty(pl, "petitboot,console", val, true);
209         }
210
211         val = config->http_proxy ?: "";
212         param_list_set_non_empty(pl, "petitboot,http_proxy", val, true);
213         val = config->https_proxy ?: "";
214         param_list_set_non_empty(pl, "petitboot,https_proxy", val, true);
215
216         params_update_network_values(pl, "petitboot,network", config);
217         params_update_bootdev_values(pl, "petitboot,bootdevs", config);
218 }
219
220 static void config_set_ipmi_bootdev(struct config *config, enum ipmi_bootdev bootdev,
221                 bool persistent)
222 {
223         config->ipmi_bootdev = bootdev;
224         config->ipmi_bootdev_persistent = persistent;
225
226         if (bootdev == IPMI_BOOTDEV_SAFE)
227                 config->safe_mode = true;
228 }
229
230 static int read_bootdev_sysparam(const char *name, uint8_t *val)
231 {
232         uint8_t buf[2];
233         char path[50];
234         int fd, rc;
235
236         assert(strlen(sysparams_dir) + strlen(name) < sizeof(path));
237         snprintf(path, sizeof(path), "%s%s", sysparams_dir, name);
238
239         fd = open(path, O_RDONLY);
240         if (fd < 0) {
241                 pb_debug("powerpc: can't access sysparam %s\n",
242                                 name);
243                 return -1;
244         }
245
246         rc = read(fd, buf, sizeof(buf));
247
248         close(fd);
249
250         /* bootdev definitions should only be one byte in size */
251         if (rc != 1) {
252                 pb_debug("powerpc: sysparam %s read returned %d\n",
253                                 name, rc);
254                 return -1;
255         }
256
257         pb_debug("powerpc: sysparam %s: 0x%02x\n", name, buf[0]);
258
259         if (!ipmi_bootdev_is_valid(buf[0]))
260                 return -1;
261
262         *val = buf[0];
263         return 0;
264 }
265
266 static int write_bootdev_sysparam(const char *name, uint8_t val)
267 {
268         char path[50];
269         int fd, rc;
270
271         assert(strlen(sysparams_dir) + strlen(name) < sizeof(path));
272         snprintf(path, sizeof(path), "%s%s", sysparams_dir, name);
273
274         fd = open(path, O_WRONLY);
275         if (fd < 0) {
276                 pb_debug("powerpc: can't access sysparam %s for writing\n",
277                                 name);
278                 return -1;
279         }
280
281         for (;;) {
282                 errno = 0;
283                 rc = write(fd, &val, sizeof(val));
284                 if (rc == sizeof(val)) {
285                         rc = 0;
286                         break;
287                 }
288
289                 if (rc <= 0 && errno != EINTR) {
290                         pb_log("powerpc: error updating sysparam %s: %s",
291                                         name, strerror(errno));
292                         rc = -1;
293                         break;
294                 }
295         }
296
297         close(fd);
298
299         if (!rc)
300                 pb_debug("powerpc: set sysparam %s: 0x%02x\n", name, val);
301
302         return rc;
303 }
304
305 static int clear_ipmi_bootdev_sysparams(
306                 struct platform_powerpc *platform __attribute__((unused)),
307                 bool persistent)
308 {
309         if (persistent) {
310                 /* invalidate default-boot-device setting */
311                 write_bootdev_sysparam("default-boot-device", 0xff);
312         } else {
313                 /* invalidate next-boot-device setting */
314                 write_bootdev_sysparam("next-boot-device", 0xff);
315         }
316         return 0;
317 }
318
319 static int get_ipmi_bootdev_sysparams(
320                 struct platform_powerpc *platform __attribute__((unused)),
321                 uint8_t *bootdev, bool *persistent)
322 {
323         uint8_t next_bootdev, default_bootdev;
324         bool next_valid, default_valid;
325         int rc;
326
327         rc = read_bootdev_sysparam("next-boot-device", &next_bootdev);
328         next_valid = rc == 0;
329
330         rc = read_bootdev_sysparam("default-boot-device", &default_bootdev);
331         default_valid = rc == 0;
332
333         /* nothing valid? no need to change the config */
334         if (!next_valid && !default_valid)
335                 return -1;
336
337         *persistent = !next_valid;
338         *bootdev = next_valid ? next_bootdev : default_bootdev;
339         return 0;
340 }
341
342 static int clear_ipmi_bootdev_ipmi(struct platform_powerpc *platform,
343                                    bool persistent __attribute__((unused)))
344 {
345         uint16_t resp_len;
346         uint8_t resp[1];
347         uint8_t req[] = {
348                 0x05, /* parameter selector: boot flags */
349                 0x80, /* data 1: valid */
350                 0x00, /* data 2: bootdev: no override */
351                 0x00, /* data 3: system defaults */
352                 0x00, /* data 4: no request for shared mode, mux defaults */
353                 0x00, /* data 5: no instance request */
354         };
355
356         resp_len = sizeof(resp);
357
358         ipmi_transaction(platform->ipmi, IPMI_NETFN_CHASSIS,
359                         IPMI_CMD_CHASSIS_SET_SYSTEM_BOOT_OPTIONS,
360                         req, sizeof(req),
361                         resp, &resp_len,
362                         ipmi_timeout);
363         return 0;
364 }
365
366 static int get_ipmi_bootdev_ipmi(struct platform_powerpc *platform,
367                 uint8_t *bootdev, bool *persistent)
368 {
369         uint16_t resp_len;
370         uint8_t resp[8];
371         int rc;
372         uint8_t req[] = {
373                 0x05, /* parameter selector: boot flags */
374                 0x00, /* no set selector */
375                 0x00, /* no block selector */
376         };
377
378         resp_len = sizeof(resp);
379         rc = ipmi_transaction(platform->ipmi, IPMI_NETFN_CHASSIS,
380                         IPMI_CMD_CHASSIS_GET_SYSTEM_BOOT_OPTIONS,
381                         req, sizeof(req),
382                         resp, &resp_len,
383                         ipmi_timeout);
384         if (rc) {
385                 pb_log("platform: error reading IPMI boot options\n");
386                 return -1;
387         }
388
389         if (resp_len != sizeof(resp)) {
390                 pb_log("platform: unexpected length (%d) in "
391                                 "boot options response\n", resp_len);
392                 return -1;
393         }
394
395         pb_debug("IPMI get_bootdev response:\n");
396         for (int i = 0; i < resp_len; i++)
397                 pb_debug("%x ", resp[i]);
398         pb_debug("\n");
399
400         if (resp[0] != 0) {
401                 pb_log("platform: non-zero completion code %d from IPMI req\n",
402                                 resp[0]);
403                 return -1;
404         }
405
406         /* check for correct parameter version */
407         if ((resp[1] & 0xf) != 0x1) {
408                 pb_log("platform: unexpected version (0x%x) in "
409                                 "boot options response\n", resp[0]);
410                 return -1;
411         }
412
413         /* check for valid paramters */
414         if (resp[2] & 0x80) {
415                 pb_debug("platform: boot options are invalid/locked\n");
416                 return -1;
417         }
418
419         *persistent = false;
420
421         /* check for valid flags */
422         if (!(resp[3] & 0x80)) {
423                 pb_debug("platform: boot flags are invalid, ignoring\n");
424                 return -1;
425         }
426
427         *persistent = resp[3] & 0x40;
428         *bootdev = (resp[4] >> 2) & 0x0f;
429         return 0;
430 }
431
432 static int set_ipmi_os_boot_sensor(struct platform_powerpc *platform)
433 {
434         int sensor_number;
435         uint16_t resp_len;
436         uint8_t resp[1];
437         uint8_t req[] = {
438                 0x00, /* sensor number: os boot */
439                 0xA9, /* operation: set everything */
440                 0x00, /* sensor reading: none */
441                 0x40, /* assertion mask lsb: set state 6 */
442                 0x00, /* assertion mask msb: none */
443                 0x00, /* deassertion mask lsb: none */
444                 0x00, /* deassertion mask msb: none */
445                 0x00, /* event data 1: none */
446                 0x00, /* event data 2: none */
447                 0x00, /* event data 3: none */
448         };
449
450         sensor_number = get_ipmi_sensor(platform, IPMI_SENSOR_ID_OS_BOOT);
451         if (sensor_number < 0) {
452                 pb_log("Couldn't find OS boot sensor in device tree\n");
453                 return -1;
454         }
455
456         req[0] = sensor_number;
457
458         resp_len = sizeof(resp);
459
460         ipmi_transaction(platform->ipmi, IPMI_NETFN_SE,
461                         IPMI_CMD_SENSOR_SET,
462                         req, sizeof(req),
463                         resp, &resp_len,
464                         ipmi_timeout); return 0;
465
466         return 0;
467 }
468
469 static void get_ipmi_network_override(struct platform_powerpc *platform,
470                         struct config *config)
471 {
472         uint16_t min_len = 12, resp_len = 53, version;
473         const uint32_t magic_value = 0x21706221;
474         uint8_t resp[resp_len];
475         uint32_t cookie;
476         bool persistent;
477         int i, rc;
478         uint8_t req[] = {
479                 0x61, /* parameter selector: OEM section (network) */
480                 0x00, /* no set selector */
481                 0x00, /* no block selector */
482         };
483
484         rc = ipmi_transaction(platform->ipmi, IPMI_NETFN_CHASSIS,
485                         IPMI_CMD_CHASSIS_GET_SYSTEM_BOOT_OPTIONS,
486                         req, sizeof(req),
487                         resp, &resp_len,
488                         ipmi_timeout);
489
490         pb_debug("IPMI net override resp [%d][%d]:\n", rc, resp_len);
491         if (resp_len > 0) {
492                 for (i = 0; i < resp_len; i++) {
493                         pb_debug(" %02x", resp[i]);
494                         if (i && (i + 1) % 16 == 0 && i != resp_len - 1)
495                                 pb_debug("\n");
496                         else if (i && (i + 1) % 8 == 0)
497                                 pb_debug(" ");
498                 }
499                 pb_debug("\n");
500         }
501
502         if (rc) {
503                 pb_debug("IPMI network config option unavailable\n");
504                 return;
505         }
506
507         if (resp_len < min_len) {
508                 pb_debug("IPMI net response too small\n");
509                 return;
510         }
511
512         if (resp[0] != 0) {
513                 pb_log("platform: non-zero completion code %d from IPMI network req\n",
514                        resp[0]);
515                 return;
516         }
517
518         /* Check for correct parameter version */
519         if ((resp[1] & 0xf) != 0x1) {
520                 pb_log("platform: unexpected version (0x%x) in network override response\n",
521                        resp[0]);
522                 return;
523         }
524
525         /* Check that the parameters are valid */
526         if (resp[2] & 0x80) {
527                 pb_debug("platform: network override is invalid/locked\n");
528                 return;
529         }
530
531         /* Check for valid parameters in the boot flags section */
532         if (!(resp[3] & 0x80)) {
533                 pb_debug("platform: network override valid flag not set\n");
534                 return;
535         }
536         /* Read the persistent flag; if it is set we need to save this config */
537         persistent = resp[3] & 0x40;
538         if (persistent)
539                 pb_debug("platform: network override is persistent\n");
540
541         /* Check 4-byte cookie value */
542         i = 4;
543         memcpy(&cookie, &resp[i], sizeof(cookie));
544         cookie = __be32_to_cpu(cookie);
545         if (cookie != magic_value) {
546                 pb_log_fn("Incorrect cookie %x\n", cookie);
547                 return;
548         }
549         i += sizeof(cookie);
550
551         /* Check 2-byte version number */
552         memcpy(&version, &resp[i], sizeof(version));
553         version = __be16_to_cpu(version);
554         i += sizeof(version);
555         if (version != 1) {
556                 pb_debug("Unexpected version: %u\n", version);
557                 return;
558         }
559
560         /* Interpret the rest of the interface config */
561         rc = parse_ipmi_interface_override(config, &resp[i], resp_len - i);
562
563         if (!rc && persistent) {
564                 /* Write this new config to NVRAM */
565                 params_update_network_values(&platform->params,
566                         "petitboot,network", config);
567                 rc = write_nvram(platform);
568                 if (rc)
569                         pb_log("platform: Failed to save persistent interface override\n");
570         }
571 }
572
573 static void config_get_active_consoles(struct config *config)
574 {
575         struct stat sbuf;
576         char *fsp_prop = NULL;
577
578         config->n_consoles = 2;
579         config->consoles = talloc_array(config, char *, config->n_consoles);
580         if (!config->consoles)
581                 goto err;
582
583         config->consoles[0] = talloc_asprintf(config->consoles,
584                                         "/dev/hvc0 [IPMI / Serial]");
585         config->consoles[1] = talloc_asprintf(config->consoles,
586                                         "/dev/tty1 [VGA]");
587
588         fsp_prop = talloc_asprintf(config, "%sfsps", devtree_dir);
589         if (stat(fsp_prop, &sbuf) == 0) {
590                 /* FSP based machines also have a separate serial console */
591                 config->consoles = talloc_realloc(config, config->consoles,
592                                                 char *, config->n_consoles + 1);
593                 if (!config->consoles)
594                         goto err;
595                 config->consoles[config->n_consoles++] = talloc_asprintf(
596                                                 config->consoles,
597                                                 "/dev/hvc1 [Serial]");
598         }
599
600         return;
601 err:
602         config->n_consoles = 0;
603         pb_log("Failed to allocate memory for consoles\n");
604 }
605
606 static int load_config(struct platform *p, struct config *config)
607 {
608         struct platform_powerpc *platform = to_platform_powerpc(p);
609         int rc;
610
611         rc = parse_nvram(platform);
612         if (rc)
613                 pb_log_fn("Failed to parse nvram\n");
614
615         config_populate_all(config, &platform->params);
616
617         if (platform->get_ipmi_bootdev) {
618                 bool bootdev_persistent;
619                 uint8_t bootdev = IPMI_BOOTDEV_INVALID;
620                 rc = platform->get_ipmi_bootdev(platform, &bootdev,
621                                 &bootdev_persistent);
622                 if (!rc && ipmi_bootdev_is_valid(bootdev)) {
623                         config_set_ipmi_bootdev(config, bootdev,
624                                 bootdev_persistent);
625                 }
626         }
627
628         if (platform->ipmi)
629                 get_ipmi_network_override(platform, config);
630
631         config_get_active_consoles(config);
632
633         return 0;
634 }
635
636 static int save_config(struct platform *p, struct config *config)
637 {
638         struct platform_powerpc *platform = to_platform_powerpc(p);
639         struct config *defaults;
640
641         if (config->ipmi_bootdev == IPMI_BOOTDEV_INVALID &&
642             platform->clear_ipmi_bootdev) {
643                 platform->clear_ipmi_bootdev(platform,
644                                 config->ipmi_bootdev_persistent);
645                 config->ipmi_bootdev = IPMI_BOOTDEV_NONE;
646                 config->ipmi_bootdev_persistent = false;
647         }
648
649         defaults = talloc_zero(platform, struct config);
650         config_set_defaults(defaults);
651
652         params_update_all(&platform->params, config, defaults);
653
654         talloc_free(defaults);
655         return write_nvram(platform);
656 }
657
658 static void pre_boot(struct platform *p, const struct config *config)
659 {
660         struct platform_powerpc *platform = to_platform_powerpc(p);
661
662         if (!config->ipmi_bootdev_persistent && platform->clear_ipmi_bootdev)
663                 platform->clear_ipmi_bootdev(platform, false);
664
665         if (platform->set_os_boot_sensor)
666                 platform->set_os_boot_sensor(platform);
667 }
668
669 static int get_sysinfo(struct platform *p, struct system_info *sysinfo)
670 {
671         struct platform_powerpc *platform = p->platform_data;
672         char *buf, *filename;
673         int len, rc;
674
675         filename = talloc_asprintf(platform, "%smodel", devtree_dir);
676         rc = read_file(platform, filename, &buf, &len);
677         if (rc == 0)
678                 sysinfo->type = talloc_steal(sysinfo, buf);
679         talloc_free(filename);
680
681         filename = talloc_asprintf(platform, "%ssystem-id", devtree_dir);
682         rc = read_file(platform, filename, &buf, &len);
683         if (rc == 0)
684                 sysinfo->identifier = talloc_steal(sysinfo, buf);
685         talloc_free(filename);
686
687         sysinfo->bmc_mac = talloc_zero_size(sysinfo, HWADDR_SIZE);
688
689         if (platform->ipmi) {
690                 ipmi_get_bmc_mac(platform->ipmi, sysinfo->bmc_mac);
691                 ipmi_get_bmc_versions(platform->ipmi, sysinfo);
692         }
693
694         if (platform->get_platform_versions)
695                 platform->get_platform_versions(sysinfo);
696
697         return 0;
698 }
699
700 static bool probe(struct platform *p, void *ctx)
701 {
702         struct platform_powerpc *platform;
703         struct stat statbuf;
704         bool bmc_present;
705         int rc;
706
707         /* we need a device tree */
708         rc = stat("/proc/device-tree", &statbuf);
709         if (rc)
710                 return false;
711
712         if (!S_ISDIR(statbuf.st_mode))
713                 return false;
714
715         platform = talloc_zero(ctx, struct platform_powerpc);
716         param_list_init(&platform->params, common_known_params());
717
718         p->platform_data = platform;
719
720         bmc_present = stat("/proc/device-tree/bmc", &statbuf) == 0;
721
722         if (ipmi_present() && bmc_present) {
723                 pb_debug("platform: using direct IPMI for IPMI paramters\n");
724                 platform->ipmi = ipmi_open(platform);
725                 platform->get_ipmi_bootdev = get_ipmi_bootdev_ipmi;
726                 platform->clear_ipmi_bootdev = clear_ipmi_bootdev_ipmi;
727                 platform->set_os_boot_sensor = set_ipmi_os_boot_sensor;
728         } else if (!stat(sysparams_dir, &statbuf)) {
729                 pb_debug("platform: using sysparams for IPMI paramters\n");
730                 platform->get_ipmi_bootdev = get_ipmi_bootdev_sysparams;
731                 platform->clear_ipmi_bootdev = clear_ipmi_bootdev_sysparams;
732
733         } else {
734                 pb_log("platform: no IPMI parameter support\n");
735         }
736
737         if (bmc_present)
738                 platform->get_platform_versions = hostboot_load_versions;
739
740         return true;
741 }
742
743
744 static struct platform platform_powerpc = {
745         .name                   = "powerpc",
746         .dhcp_arch_id           = 0x000e,
747         .probe                  = probe,
748         .load_config            = load_config,
749         .save_config            = save_config,
750         .pre_boot               = pre_boot,
751         .get_sysinfo            = get_sysinfo,
752 };
753
754 register_platform(platform_powerpc);