discover: Integrate ipmi bootdev settings into the priority system
[petitboot] / discover / platform-powerpc.c
1
2 #include <assert.h>
3 #include <string.h>
4 #include <stdlib.h>
5 #include <limits.h>
6 #include <errno.h>
7 #include <sys/types.h>
8 #include <sys/wait.h>
9 #include <sys/fcntl.h>
10 #include <sys/stat.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 "platform.h"
19 #include "ipmi.h"
20
21 static const char *partition = "common";
22 static const char *sysparams_dir = "/sys/firmware/opal/sysparams/";
23 static const char *devtree_dir = "/proc/device-tree/";
24
25 struct param {
26         char                    *name;
27         char                    *value;
28         bool                    modified;
29         struct list_item        list;
30 };
31
32 struct platform_powerpc {
33         struct list     params;
34         int             (*get_ipmi_bootdev)(
35                                 struct platform_powerpc *platform,
36                                 uint8_t *bootdev, bool *persistent);
37         int             (*clear_ipmi_bootdev)(
38                                 struct platform_powerpc *platform);
39 };
40
41 static const char *known_params[] = {
42         "auto-boot?",
43         "petitboot,network",
44         "petitboot,timeout",
45         "petitboot,bootdev",
46         "petitboot,language",
47         "petitboot,debug?",
48         NULL,
49 };
50
51 #define to_platform_powerpc(p) \
52         (struct platform_powerpc *)(p->platform_data)
53
54 /* a partition max a max size of 64k * 16bytes = 1M */
55 static const int max_partition_size = 64 * 1024 * 16;
56
57 static bool param_is_known(const char *param, unsigned int len)
58 {
59         const char *known_param;
60         unsigned int i;
61
62         for (i = 0; known_params[i]; i++) {
63                 known_param = known_params[i];
64                 if (len == strlen(known_param) &&
65                                 !strncmp(param, known_param, len))
66                         return true;
67         }
68
69         return false;
70 }
71
72 static int parse_nvram_params(struct platform_powerpc *platform,
73                 char *buf, int len)
74 {
75         char *pos, *name, *value;
76         unsigned int paramlen;
77         int i, count;
78
79         /* discard 2 header lines:
80          * "common" partiton"
81          * ------------------
82          */
83         pos = buf;
84         count = 0;
85
86         for (i = 0; i < len; i++) {
87                 if (pos[i] == '\n')
88                         count++;
89                 if (count == 2)
90                         break;
91         }
92
93         if (i == len) {
94                 fprintf(stderr, "failure parsing nvram output\n");
95                 return -1;
96         }
97
98         for (pos = buf + i; pos < buf + len; pos += paramlen + 1) {
99                 unsigned int namelen;
100                 struct param *param;
101                 char *newline;
102
103                 newline = strchr(pos, '\n');
104                 if (!newline)
105                         break;
106
107                 *newline = '\0';
108
109                 paramlen = strlen(pos);
110
111                 name = pos;
112                 value = strchr(pos, '=');
113                 if (!value)
114                         continue;
115
116                 namelen = value - name;
117                 if (namelen == 0)
118                         continue;
119
120                 if (!param_is_known(name, namelen))
121                         continue;
122
123                 value++;
124
125                 param = talloc(platform, struct param);
126                 param->modified = false;
127                 param->name = talloc_strndup(platform, name, namelen);
128                 param->value = talloc_strdup(platform, value);
129                 list_add(&platform->params, &param->list);
130         }
131
132         return 0;
133 }
134
135 static int parse_nvram(struct platform_powerpc *platform)
136 {
137         struct process *process;
138         const char *argv[5];
139         int rc;
140
141         argv[0] = "nvram";
142         argv[1] = "--print-config";
143         argv[2] = "--partition";
144         argv[3] = partition;
145         argv[4] = NULL;
146
147         process = process_create(platform);
148         process->path = "nvram";
149         process->argv = argv;
150         process->keep_stdout = true;
151
152         rc = process_run_sync(process);
153
154         if (rc || !process_exit_ok(process)) {
155                 fprintf(stderr, "nvram process returned "
156                                 "non-zero exit status\n");
157                 rc = -1;
158         } else {
159                 rc = parse_nvram_params(platform, process->stdout_buf,
160                                             process->stdout_len);
161         }
162
163         process_release(process);
164         return rc;
165 }
166
167 static int write_nvram(struct platform_powerpc *platform)
168 {
169         struct process *process;
170         struct param *param;
171         const char *argv[6];
172         int rc;
173
174         argv[0] = "nvram";
175         argv[1] = "--update-config";
176         argv[2] = NULL;
177         argv[3] = "--partition";
178         argv[4] = partition;
179         argv[5] = NULL;
180
181         process = process_create(platform);
182         process->path = "nvram";
183         process->argv = argv;
184
185         list_for_each_entry(&platform->params, param, list) {
186                 char *paramstr;
187
188                 if (!param->modified)
189                         continue;
190
191                 paramstr = talloc_asprintf(platform, "%s=%s",
192                                 param->name, param->value);
193                 argv[2] = paramstr;
194
195                 rc = process_run_sync(process);
196
197                 talloc_free(paramstr);
198
199                 if (rc || !process_exit_ok(process)) {
200                         rc = -1;
201                         pb_log("nvram update process returned "
202                                         "non-zero exit status\n");
203                         break;
204                 }
205         }
206
207         process_release(process);
208         return rc;
209 }
210
211 static const char *get_param(struct platform_powerpc *platform,
212                 const char *name)
213 {
214         struct param *param;
215
216         list_for_each_entry(&platform->params, param, list)
217                 if (!strcmp(param->name, name))
218                         return param->value;
219         return NULL;
220 }
221
222 static void set_param(struct platform_powerpc *platform, const char *name,
223                 const char *value)
224 {
225         struct param *param;
226
227         list_for_each_entry(&platform->params, param, list) {
228                 if (strcmp(param->name, name))
229                         continue;
230
231                 if (!strcmp(param->value, value))
232                         return;
233
234                 talloc_free(param->value);
235                 param->value = talloc_strdup(param, value);
236                 param->modified = true;
237                 return;
238         }
239
240
241         param = talloc(platform, struct param);
242         param->modified = true;
243         param->name = talloc_strdup(platform, name);
244         param->value = talloc_strdup(platform, value);
245         list_add(&platform->params, &param->list);
246 }
247
248 static int parse_hwaddr(struct interface_config *ifconf, char *str)
249 {
250         int i;
251
252         if (strlen(str) != strlen("00:00:00:00:00:00"))
253                 return -1;
254
255         for (i = 0; i < HWADDR_SIZE; i++) {
256                 char byte[3], *endp;
257                 unsigned long x;
258
259                 byte[0] = str[i * 3 + 0];
260                 byte[1] = str[i * 3 + 1];
261                 byte[2] = '\0';
262
263                 x = strtoul(byte, &endp, 16);
264                 if (endp != byte + 2)
265                         return -1;
266
267                 ifconf->hwaddr[i] = x & 0xff;
268         }
269
270         return 0;
271 }
272
273 static int parse_one_interface_config(struct config *config,
274                 char *confstr)
275 {
276         struct interface_config *ifconf;
277         char *tok, *saveptr;
278
279         ifconf = talloc_zero(config, struct interface_config);
280
281         if (!confstr || !strlen(confstr))
282                 goto out_err;
283
284         /* first token should be the mac address */
285         tok = strtok_r(confstr, ",", &saveptr);
286         if (!tok)
287                 goto out_err;
288
289         if (parse_hwaddr(ifconf, tok))
290                 goto out_err;
291
292         /* second token is the method */
293         tok = strtok_r(NULL, ",", &saveptr);
294         if (!tok || !strlen(tok) || !strcmp(tok, "ignore")) {
295                 ifconf->ignore = true;
296
297         } else if (!strcmp(tok, "dhcp")) {
298                 ifconf->method = CONFIG_METHOD_DHCP;
299
300         } else if (!strcmp(tok, "static")) {
301                 ifconf->method = CONFIG_METHOD_STATIC;
302
303                 /* ip/mask, [optional] gateway */
304                 tok = strtok_r(NULL, ",", &saveptr);
305                 if (!tok)
306                         goto out_err;
307                 ifconf->static_config.address =
308                         talloc_strdup(ifconf, tok);
309
310                 tok = strtok_r(NULL, ",", &saveptr);
311                 if (tok) {
312                         ifconf->static_config.gateway =
313                                 talloc_strdup(ifconf, tok);
314                 }
315
316         } else {
317                 pb_log("Unknown network configuration method %s\n", tok);
318                 goto out_err;
319         }
320
321         config->network.interfaces = talloc_realloc(config,
322                         config->network.interfaces,
323                         struct interface_config *,
324                         ++config->network.n_interfaces);
325
326         config->network.interfaces[config->network.n_interfaces - 1] = ifconf;
327
328         return 0;
329 out_err:
330         talloc_free(ifconf);
331         return -1;
332 }
333
334 static int parse_one_dns_config(struct config *config,
335                 char *confstr)
336 {
337         char *tok, *saveptr;
338
339         for (tok = strtok_r(confstr, ",", &saveptr); tok;
340                         tok = strtok_r(NULL, ",", &saveptr)) {
341
342                 char *server = talloc_strdup(config, tok);
343
344                 config->network.dns_servers = talloc_realloc(config,
345                                 config->network.dns_servers, const char *,
346                                 ++config->network.n_dns_servers);
347
348                 config->network.dns_servers[config->network.n_dns_servers - 1]
349                                 = server;
350         }
351
352         return 0;
353 }
354
355 static void populate_network_config(struct platform_powerpc *platform,
356                 struct config *config)
357 {
358         char *val, *saveptr = NULL;
359         const char *cval;
360         int i;
361
362         cval = get_param(platform, "petitboot,network");
363         if (!cval || !strlen(cval))
364                 return;
365
366         val = talloc_strdup(config, cval);
367
368         for (i = 0; ; i++) {
369                 char *tok;
370
371                 tok = strtok_r(i == 0 ? val : NULL, " ", &saveptr);
372                 if (!tok)
373                         break;
374
375                 if (!strncasecmp(tok, "dns,", strlen("dns,")))
376                         parse_one_dns_config(config, tok + strlen("dns,"));
377                 else
378                         parse_one_interface_config(config, tok);
379
380         }
381
382         talloc_free(val);
383 }
384
385 static void populate_bootdev_config(struct platform_powerpc *platform,
386                 struct config *config)
387
388 {
389         const char *val;
390
391         config->boot_device = NULL;
392
393         val = get_param(platform, "petitboot,bootdev");
394         if (!val || !strlen(val))
395                 return;
396
397         if (!strncmp(val, "uuid:", strlen("uuid:"))) {
398                 config->boot_device = talloc_strdup(config,
399                                                 val + strlen("uuid:"));
400
401         } else if (!strncmp(val, "mac:", strlen("mac:"))) {
402                 config->boot_device = talloc_strdup(config,
403                                                 val + strlen("mac:"));
404
405         } else {
406                 pb_log("bootdev config is in an unknown format "
407                                 "(expected uuid:... or mac:...)");
408         }
409 }
410
411 static void populate_config(struct platform_powerpc *platform,
412                 struct config *config)
413 {
414         const char *val;
415         char *end;
416         unsigned long timeout;
417
418         /* if the "auto-boot?' property is present and "false", disable auto
419          * boot */
420         val = get_param(platform, "auto-boot?");
421         config->autoboot_enabled = !val || strcmp(val, "false");
422
423         val = get_param(platform, "petitboot,timeout");
424         if (val) {
425                 timeout = strtoul(val, &end, 10);
426                 if (end != val) {
427                         if (timeout >= INT_MAX)
428                                 timeout = INT_MAX;
429                         config->autoboot_timeout_sec = (int)timeout;
430                 }
431         }
432
433         val = get_param(platform, "petitboot,language");
434         config->lang = val ? talloc_strdup(config, val) : NULL;
435
436         populate_network_config(platform, config);
437
438         populate_bootdev_config(platform, config);
439
440         if (!config->debug) {
441                 val = get_param(platform, "petitboot,debug?");
442                 config->debug = val && !strcmp(val, "true");
443         }
444 }
445
446 static char *iface_config_str(void *ctx, struct interface_config *config)
447 {
448         char *str;
449
450         /* todo: HWADDR size is hardcoded as 6, but we may need to handle
451          * different hardware address formats */
452         str = talloc_asprintf(ctx, "%02x:%02x:%02x:%02x:%02x:%02x,",
453                         config->hwaddr[0], config->hwaddr[1],
454                         config->hwaddr[2], config->hwaddr[3],
455                         config->hwaddr[4], config->hwaddr[5]);
456
457         if (config->ignore) {
458                 str = talloc_asprintf_append(str, "ignore");
459
460         } else if (config->method == CONFIG_METHOD_DHCP) {
461                 str = talloc_asprintf_append(str, "dhcp");
462
463         } else if (config->method == CONFIG_METHOD_STATIC) {
464                 str = talloc_asprintf_append(str, "static,%s%s%s",
465                                 config->static_config.address,
466                                 config->static_config.gateway ? "," : "",
467                                 config->static_config.gateway ?: "");
468         }
469         return str;
470 }
471
472 static char *dns_config_str(void *ctx, const char **dns_servers, int n)
473 {
474         char *str;
475         int i;
476
477         str = talloc_strdup(ctx, "dns,");
478         for (i = 0; i < n; i++) {
479                 str = talloc_asprintf_append(str, "%s%s",
480                                 i == 0 ? "" : ",",
481                                 dns_servers[i]);
482         }
483
484         return str;
485 }
486
487 static void update_string_config(struct platform_powerpc *platform,
488                 const char *name, const char *value)
489 {
490         const char *cur;
491
492         cur = get_param(platform, name);
493
494         /* don't set an empty parameter if it doesn't already exist */
495         if (!cur && !strlen(value))
496                 return;
497
498         set_param(platform, name, value);
499 }
500
501 static void update_network_config(struct platform_powerpc *platform,
502         struct config *config)
503 {
504         unsigned int i;
505         char *val;
506
507         val = talloc_strdup(platform, "");
508
509         for (i = 0; i < config->network.n_interfaces; i++) {
510                 char *iface_str = iface_config_str(platform,
511                                         config->network.interfaces[i]);
512                 val = talloc_asprintf_append(val, "%s%s",
513                                 *val == '\0' ? "" : " ", iface_str);
514                 talloc_free(iface_str);
515         }
516
517         if (config->network.n_dns_servers) {
518                 char *dns_str = dns_config_str(platform,
519                                                 config->network.dns_servers,
520                                                 config->network.n_dns_servers);
521                 val = talloc_asprintf_append(val, "%s%s",
522                                 *val == '\0' ? "" : " ", dns_str);
523                 talloc_free(dns_str);
524         }
525
526         update_string_config(platform, "petitboot,network", val);
527
528         talloc_free(val);
529 }
530
531 static void update_bootdev_config(struct platform_powerpc *platform,
532                 struct config *config)
533 {
534         char *val, *tmp = NULL;
535
536         if (!config->boot_device)
537                 val = "";
538         else
539                 tmp = val = talloc_asprintf(platform,
540                                 "uuid:%s", config->boot_device);
541
542         update_string_config(platform, "petitboot,bootdev", val);
543         talloc_free(tmp);
544 }
545
546 static int update_config(struct platform_powerpc *platform,
547                 struct config *config, struct config *defaults)
548 {
549         char *tmp = NULL;
550         const char *val;
551
552         if (config->autoboot_enabled == defaults->autoboot_enabled)
553                 val = "";
554         else
555                 val = config->autoboot_enabled ? "true" : "false";
556         update_string_config(platform, "auto-boot?", val);
557
558         if (config->autoboot_timeout_sec == defaults->autoboot_timeout_sec)
559                 val = "";
560         else
561                 val = tmp = talloc_asprintf(platform, "%d",
562                                 config->autoboot_timeout_sec);
563
564         update_string_config(platform, "petitboot,timeout", val);
565         if (tmp)
566                 talloc_free(tmp);
567
568         val = config->lang ?: "";
569         update_string_config(platform, "petitboot,language", val);
570
571         update_network_config(platform, config);
572
573         update_bootdev_config(platform, config);
574
575         return write_nvram(platform);
576 }
577
578 static void set_ipmi_bootdev(struct config *config, enum ipmi_bootdev bootdev,
579                 bool persistent)
580 {
581         config->ipmi_bootdev = bootdev;
582         config->ipmi_bootdev_persistent = persistent;
583
584         switch (bootdev) {
585         case IPMI_BOOTDEV_NONE:
586         case IPMI_BOOTDEV_DISK:
587         case IPMI_BOOTDEV_NETWORK:
588         case IPMI_BOOTDEV_CDROM:
589                 break;
590         case IPMI_BOOTDEV_SETUP:
591                 config->autoboot_enabled = false;
592                 break;
593         case IPMI_BOOTDEV_SAFE:
594                 config->autoboot_enabled = false;
595                 config->safe_mode = true;
596                 break;
597         }
598 }
599
600 static int read_bootdev_sysparam(const char *name, uint8_t *val)
601 {
602         uint8_t buf[2];
603         char path[50];
604         int fd, rc;
605
606         strcpy(path, sysparams_dir);
607         assert(strlen(name) < sizeof(path) - strlen(path));
608         strcat(path, name);
609
610         fd = open(path, O_RDONLY);
611         if (fd < 0) {
612                 pb_debug("powerpc: can't access sysparam %s\n",
613                                 name);
614                 return -1;
615         }
616
617         rc = read(fd, buf, sizeof(buf));
618
619         close(fd);
620
621         /* bootdev definitions should only be one byte in size */
622         if (rc != 1) {
623                 pb_debug("powerpc: sysparam %s read returned %d\n",
624                                 name, rc);
625                 return -1;
626         }
627
628         pb_debug("powerpc: sysparam %s: 0x%02x\n", name, buf[0]);
629
630         if (!ipmi_bootdev_is_valid(buf[0]))
631                 return -1;
632
633         *val = buf[0];
634         return 0;
635 }
636
637 static int write_bootdev_sysparam(const char *name, uint8_t val)
638 {
639         char path[50];
640         int fd, rc;
641
642         strcpy(path, sysparams_dir);
643         assert(strlen(name) < sizeof(path) - strlen(path));
644         strcat(path, name);
645
646         fd = open(path, O_WRONLY);
647         if (fd < 0) {
648                 pb_debug("powerpc: can't access sysparam %s for writing\n",
649                                 name);
650                 return -1;
651         }
652
653         for (;;) {
654                 errno = 0;
655                 rc = write(fd, &val, sizeof(val));
656                 if (rc == sizeof(val)) {
657                         rc = 0;
658                         break;
659                 }
660
661                 if (rc <= 0 && errno != EINTR) {
662                         pb_log("powerpc: error updating sysparam %s: %s",
663                                         name, strerror(errno));
664                         rc = -1;
665                         break;
666                 }
667         }
668
669         close(fd);
670
671         if (!rc)
672                 pb_debug("powerpc: set sysparam %s: 0x%02x\n", name, val);
673
674         return rc;
675 }
676
677 static int clear_ipmi_bootdev_sysparams(
678                 struct platform_powerpc *platform __attribute__((unused)))
679 {
680         /* invalidate next-boot-device setting */
681         write_bootdev_sysparam("next-boot-device", 0xff);
682         return 0;
683 }
684
685 static int get_ipmi_bootdev_sysparams(
686                 struct platform_powerpc *platform __attribute__((unused)),
687                 uint8_t *bootdev, bool *persistent)
688 {
689         uint8_t next_bootdev, default_bootdev;
690         bool next_valid, default_valid;
691         int rc;
692
693         rc = read_bootdev_sysparam("next-boot-device", &next_bootdev);
694         next_valid = rc == 0;
695
696         rc = read_bootdev_sysparam("default-boot-device", &default_bootdev);
697         default_valid = rc == 0;
698
699         /* nothing valid? no need to change the config */
700         if (!next_valid && !default_valid)
701                 return -1;
702
703         *persistent = !next_valid;
704         *bootdev = next_valid ? next_bootdev : default_bootdev;
705         return 0;
706 }
707
708 static int load_config(struct platform *p, struct config *config)
709 {
710         struct platform_powerpc *platform = to_platform_powerpc(p);
711         int rc;
712
713         rc = parse_nvram(platform);
714         if (rc)
715                 return rc;
716
717         populate_config(platform, config);
718
719         if (platform->get_ipmi_bootdev) {
720                 bool bootdev_persistent;
721                 uint8_t bootdev;
722                 rc = platform->get_ipmi_bootdev(platform, &bootdev,
723                                 &bootdev_persistent);
724                 if (!rc && ipmi_bootdev_is_valid(bootdev)) {
725                         set_ipmi_bootdev(config, bootdev, bootdev_persistent);
726                 }
727         }
728
729         return 0;
730 }
731
732 static int save_config(struct platform *p, struct config *config)
733 {
734         struct platform_powerpc *platform = to_platform_powerpc(p);
735         struct config *defaults;
736         int rc;
737
738         defaults = talloc_zero(platform, struct config);
739         config_set_defaults(defaults);
740
741         rc = update_config(platform, config, defaults);
742
743         talloc_free(defaults);
744         return rc;
745 }
746
747 static void finalise_config(struct platform *p, const struct config *config)
748 {
749         struct platform_powerpc *platform = to_platform_powerpc(p);
750
751         if (config->ipmi_bootdev_persistent && platform->clear_ipmi_bootdev)
752                 platform->clear_ipmi_bootdev(platform);
753 }
754
755 static int get_sysinfo(struct platform *p, struct system_info *sysinfo)
756 {
757         struct platform_powerpc *platform = p->platform_data;
758         char *buf, *filename;
759         int len, rc;
760
761         filename = talloc_asprintf(platform, "%smodel", devtree_dir);
762         rc = read_file(platform, filename, &buf, &len);
763         if (rc == 0)
764                 sysinfo->type = talloc_steal(sysinfo, buf);
765         talloc_free(filename);
766
767         filename = talloc_asprintf(platform, "%ssystem-id", devtree_dir);
768         rc = read_file(platform, filename, &buf, &len);
769         if (rc == 0)
770                 sysinfo->identifier = talloc_steal(sysinfo, buf);
771         talloc_free(filename);
772
773         return 0;
774 }
775
776 static bool probe(struct platform *p, void *ctx)
777 {
778         struct platform_powerpc *platform;
779         struct stat statbuf;
780         int rc;
781
782         /* we need a device tree */
783         rc = stat("/proc/device-tree", &statbuf);
784         if (rc)
785                 return false;
786
787         if (!S_ISDIR(statbuf.st_mode))
788                 return false;
789
790         platform = talloc(ctx, struct platform_powerpc);
791         list_init(&platform->params);
792
793         p->platform_data = platform;
794
795         if (!stat(sysparams_dir, &statbuf)) {
796                 pb_debug("platform: using sysparams for IPMI paramters\n");
797                 platform->get_ipmi_bootdev = get_ipmi_bootdev_sysparams;
798                 platform->clear_ipmi_bootdev = clear_ipmi_bootdev_sysparams;
799
800         } else {
801                 pb_log("platform: no IPMI parameter support\n");
802         }
803         return true;
804 }
805
806
807 static struct platform platform_powerpc = {
808         .name                   = "powerpc",
809         .dhcp_arch_id           = 0x000e,
810         .probe                  = probe,
811         .load_config            = load_config,
812         .save_config            = save_config,
813         .finalise_config        = finalise_config,
814         .get_sysinfo            = get_sysinfo,
815 };
816
817 register_platform(platform_powerpc);