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