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