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