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