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