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