firmware-utils: ptgen: add GPT support
author李国 <uxgood.org@gmail.com>
Thu, 26 Mar 2020 05:47:40 +0000 (13:47 +0800)
committerPetr Štetiar <ynezz@true.cz>
Tue, 31 Mar 2020 14:20:47 +0000 (16:20 +0200)
Add GPT support to ptgen, so we can generate EFI bootable images.

Introduced two options:
    -g        generate GPT partition table
    -G GUID   use GUID for disk and increase last bit for all partitions

We drop The alternate partition table to reduce size, This may cause
problems when generate vmdk images or vdi images. We have to pad enough
sectors when generate these images.

Signed-off-by: 李国 <uxgood.org@gmail.com>
[fixed compilation on macOS]
Signed-off-by: Petr Štetiar <ynezz@true.cz>
tools/firmware-utils/Makefile
tools/firmware-utils/src/ptgen.c

index 97c89eec279cc18bc7b0bea5e9332bd16ce3c75e..561d6d868fe8293194d7227e62d19997105511b5 100644 (file)
@@ -32,7 +32,7 @@ define Host/Compile
        $(call cc,dgfirmware)
        $(call cc,mksenaofw md5, -Wall --std=gnu99)
        $(call cc,trx2usr)
-       $(call cc,ptgen)
+       $(call cc,ptgen cyg_crc32)
        $(call cc,srec2bin)
        $(call cc,mkmylofw)
        $(call cc,mkcsysimg)
index 0192bb65e514f3f6acde3f59b184e0726aa47087..83067c104dddb873077d11fe3182c934b942b30a 100644 (file)
@@ -5,6 +5,9 @@
  * uses parts of afdisk
  * Copyright (C) 2002 by David Roetzel <david@roetzel.de>
  *
+ * UUID/GUID definition stolen from kernel/include/uapi/linux/uuid.h
+ * Copyright (C) 2010, Intel Corp. Huang Ying <ying.huang@intel.com>
+ *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
 #include <stdint.h>
 #include <stdbool.h>
 #include <ctype.h>
+#include <inttypes.h>
 #include <fcntl.h>
 #include <stdint.h>
+#include "cyg_crc.h"
 
 #if __BYTE_ORDER == __BIG_ENDIAN
+#define cpu_to_le16(x) bswap_16(x)
 #define cpu_to_le32(x) bswap_32(x)
+#define cpu_to_le64(x) bswap_64(x)
 #elif __BYTE_ORDER == __LITTLE_ENDIAN
+#define cpu_to_le16(x) (x)
 #define cpu_to_le32(x) (x)
+#define cpu_to_le64(x) (x)
 #else
 #error unknown endianness!
 #endif
 
+#define swap(a, b) \
+       do { typeof(a) __tmp = (a); (a) = (b); (b) = __tmp; } while (0)
+
+typedef struct {
+       uint8_t b[16];
+} guid_t;
+
+#define GUID_INIT(a, b, c, d0, d1, d2, d3, d4, d5, d6, d7)                     \
+((guid_t)                                                              \
+{{ (a) & 0xff, ((a) >> 8) & 0xff, ((a) >> 16) & 0xff, ((a) >> 24) & 0xff, \
+   (b) & 0xff, ((b) >> 8) & 0xff,                                      \
+   (c) & 0xff, ((c) >> 8) & 0xff,                                      \
+   (d0), (d1), (d2), (d3), (d4), (d5), (d6), (d7) }})
+
+#define GUID_STRING_LENGTH      36
+
+#define GPT_SIGNATURE 0x5452415020494645ULL
+#define GPT_REVISION 0x00010000
+
+#define GUID_PARTITION_SYSTEM \
+       GUID_INIT( 0xC12A7328, 0xF81F, 0x11d2, \
+                       0xBA, 0x4B, 0x00, 0xA0, 0xC9, 0x3E, 0xC9, 0x3B)
+
+#define GUID_PARTITION_BASIC_DATA \
+       GUID_INIT( 0xEBD0A0A2, 0xB9E5, 0x4433, \
+                       0x87, 0xC0, 0x68, 0xB6, 0xB7, 0x26, 0x99, 0xC7)
+
+#define GUID_PARTITION_BIOS_BOOT \
+       GUID_INIT( 0x21686148, 0x6449, 0x6E6F, \
+                       0x74, 0x4E, 0x65, 0x65, 0x64, 0x45, 0x46, 0x49)
+
+#define GPT_HEADER_SIZE         92
+#define GPT_ENTRY_SIZE          128
+#define GPT_ENTRY_MAX           128
+#define GPT_ENTRY_NAME_SIZE     72
+
+#define GPT_HEADER_SECTOR       1
+#define GPT_FIRST_ENTRY_SECTOR  2
+
+#define MBR_ENTRY_MAX           4
+#define MBR_DISK_SIGNATURE_OFFSET  440
+#define MBR_PARTITION_ENTRY_OFFSET 446
+#define MBR_BOOT_SIGNATURE_OFFSET  510
+
+#define DISK_SECTOR_SIZE        512
+
 /* Partition table entry */
 struct pte {
        uint8_t active;
@@ -55,13 +110,43 @@ struct partinfo {
        int type;
 };
 
+/* GPT Partition table header */
+struct gpth {
+       uint64_t signature;
+       uint32_t revision;
+       uint32_t size;
+       uint32_t crc32;
+       uint32_t reserved;
+       uint64_t self;
+       uint64_t alternate;
+       uint64_t first_usable;
+       uint64_t last_usable;
+       guid_t disk_guid;
+       uint64_t first_entry;
+       uint32_t entry_num;
+       uint32_t entry_size;
+       uint32_t entry_crc32;
+} __attribute__((packed));
+
+/* GPT Partition table entry */
+struct gpte {
+       guid_t type;
+       guid_t guid;
+       uint64_t start;
+       uint64_t end;
+       uint64_t attr;
+       char name[GPT_ENTRY_NAME_SIZE];
+} __attribute__((packed));
+
+
 int verbose = 0;
 int active = 1;
 int heads = -1;
 int sectors = -1;
 int kb_align = 0;
 bool ignore_null_sized_partition = false;
-struct partinfo parts[4];
+bool use_guid_partition_table = false;
+struct partinfo parts[GPT_ENTRY_MAX];
 char *filename = NULL;
 
 
@@ -91,7 +176,7 @@ static long to_kbytes(const char *string)
                end++;
 
        if (*end) {
-               fprintf(stderr, "garbage after end of number\n");
+               fputs("garbage after end of number\n", stderr);
                return 0;
        }
 
@@ -132,20 +217,73 @@ static inline unsigned long round_to_kb(long sect) {
         return ((sect - 1) / kb_align + 1) * kb_align;
 }
 
+/* Compute a CRC for guid partition table */
+static inline unsigned long gpt_crc32(void *buf, unsigned long len)
+{
+       return cyg_crc32_accumulate(~0L, buf, len) ^ ~0L;
+}
+
+/* Parse a guid string to guid_t struct */
+static inline int guid_parse(char *buf, guid_t *guid)
+{
+       char b[4] = {0};
+       char *p = buf;
+       unsigned i = 0;
+       if (strnlen(buf, GUID_STRING_LENGTH) != GUID_STRING_LENGTH)
+               return -1;
+       for (i = 0; i < sizeof(guid_t); i++) {
+               if (*p == '-')
+                       p++;
+               if (*p == '\0')
+                       return -1;
+               memcpy(b, p, 2);
+               guid->b[i] = strtol(b, 0, 16);
+               p += 2;
+       }
+       swap(guid->b[0], guid->b[3]);
+       swap(guid->b[1], guid->b[2]);
+       swap(guid->b[4], guid->b[5]);
+       swap(guid->b[6], guid->b[7]);
+       return 0;
+}
+
+/* init an utf-16 string from utf-8 string */
+static inline void init_utf16(char *str, uint16_t *buf, unsigned bufsize)
+{
+       unsigned i, n = 0;
+       for (i = 0; i < bufsize; i++) {
+               if (str[n] == 0x00) {
+                       buf[i] = 0x00;
+                       return ;
+               } else if ((str[n] & 0x80) == 0x00) {//0xxxxxxx
+                       buf[i] = cpu_to_le16(str[n++]);
+               } else if ((str[n] & 0xE0) == 0xC0) {//110xxxxx
+                       buf[i] = cpu_to_le16((str[n] & 0x1F) << 6 | (str[n + 1] & 0x3F));
+                       n += 2;
+               } else if ((str[n] & 0xF0) == 0xE0) {//1110xxxx
+                       buf[i] = cpu_to_le16((str[n] & 0x0F) << 12 | (str[n + 1] & 0x3F) << 6 | (str[n + 2] & 0x3F));
+                       n += 3;
+               } else {
+                       buf[i] = cpu_to_le16('?');
+                       n++;
+               }
+       }
+}
+
 /* check the partition sizes and write the partition table */
 static int gen_ptable(uint32_t signature, int nr)
 {
-       struct pte pte[4];
-       unsigned long sect = 0;
-       int i, fd, ret = -1, start, len;
+       struct pte pte[MBR_ENTRY_MAX];
+       unsigned long start, len, sect = 0;
+       int i, fd, ret = -1;
 
-       memset(pte, 0, sizeof(struct pte) * 4);
+       memset(pte, 0, sizeof(struct pte) * MBR_ENTRY_MAX);
        for (i = 0; i < nr; i++) {
                if (!parts[i].size) {
                        if (ignore_null_sized_partition)
                                continue;
                        fprintf(stderr, "Invalid size in partition %d!\n", i);
-                       return -1;
+                       return ret;
                }
 
                pte[i].active = ((i + 1) == active) ? 0x80 : 0;
@@ -165,32 +303,175 @@ static int gen_ptable(uint32_t signature, int nr)
                to_chs(start + len - 1, pte[i].chs_end);
 
                if (verbose)
-                       fprintf(stderr, "Partition %d: start=%ld, end=%ld, size=%ld\n", i, (long)start * 512, ((long)start + (long)len) * 512, (long)len * 512);
-               printf("%ld\n", (long)start * 512);
-               printf("%ld\n", (long)len * 512);
+                       fprintf(stderr, "Partition %d: start=%ld, end=%ld, size=%ld\n",
+                                       i,
+                                       (long)start * DISK_SECTOR_SIZE,
+                                       (long)(start + len) * DISK_SECTOR_SIZE,
+                                       (long)len * DISK_SECTOR_SIZE);
+               printf("%ld\n", (long)start * DISK_SECTOR_SIZE);
+               printf("%ld\n", (long)len * DISK_SECTOR_SIZE);
        }
 
        if ((fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0644)) < 0) {
                fprintf(stderr, "Can't open output file '%s'\n",filename);
-               return -1;
+               return ret;
+       }
+
+       lseek(fd, MBR_DISK_SIGNATURE_OFFSET, SEEK_SET);
+       if (write(fd, &signature, sizeof(signature)) != sizeof(signature)) {
+               fputs("write failed.\n", stderr);
+               goto fail;
+       }
+
+       lseek(fd, MBR_PARTITION_ENTRY_OFFSET, SEEK_SET);
+       if (write(fd, pte, sizeof(struct pte) * MBR_ENTRY_MAX) != sizeof(struct pte) * MBR_ENTRY_MAX) {
+               fputs("write failed.\n", stderr);
+               goto fail;
+       }
+       lseek(fd, MBR_BOOT_SIGNATURE_OFFSET, SEEK_SET);
+       if (write(fd, "\x55\xaa", 2) != 2) {
+               fputs("write failed.\n", stderr);
+               goto fail;
+       }
+
+       ret = 0;
+fail:
+       close(fd);
+       return ret;
+}
+
+/* check the partition sizes and write the guid partition table */
+static int gen_gptable(uint32_t signature, guid_t guid, unsigned nr)
+{
+       struct pte pte;
+       struct gpth gpth = {
+               .signature = cpu_to_le64(GPT_SIGNATURE),
+               .revision = cpu_to_le32(GPT_REVISION),
+               .size = cpu_to_le32(GPT_HEADER_SIZE),
+               .self = cpu_to_le64(GPT_HEADER_SECTOR),
+               .first_usable = cpu_to_le64(GPT_FIRST_ENTRY_SECTOR + GPT_ENTRY_SIZE * GPT_ENTRY_MAX / DISK_SECTOR_SIZE),
+               .first_entry = cpu_to_le64(GPT_FIRST_ENTRY_SECTOR),
+               .disk_guid = guid,
+               .entry_num = cpu_to_le32(GPT_ENTRY_MAX),
+               .entry_size = cpu_to_le32(GPT_ENTRY_SIZE),
+       };
+       struct gpte  gpte[GPT_ENTRY_MAX];
+       uint64_t start, end, sect = 0;
+       int fd, ret = -1;
+       unsigned i;
+
+       memset(gpte, 0, GPT_ENTRY_SIZE * GPT_ENTRY_MAX);
+       for (i = 0; i < nr; i++) {
+               if (!parts[i].size) {
+                       if (ignore_null_sized_partition)
+                               continue;
+                       fprintf(stderr, "Invalid size in partition %d!\n", i);
+                       return ret;
+               }
+               start = sect + sectors;
+               if (kb_align != 0)
+                       start = round_to_kb(start);
+               gpte[i].start = cpu_to_le64(start);
+
+               sect = start + parts[i].size * 2;
+               if (kb_align == 0)
+                       sect = round_to_cyl(sect);
+               gpte[i].end = cpu_to_le64(sect -1);
+               gpte[i].guid = guid;
+               gpte[i].guid.b[sizeof(guid_t) -1] += i + 1;
+               if (parts[i].type == 0xEF || (i + 1) == (unsigned)active) {
+                       gpte[i].type = GUID_PARTITION_SYSTEM;
+                       init_utf16("EFI System Partition", (uint16_t *)gpte[i].name, GPT_ENTRY_NAME_SIZE / sizeof(uint16_t));
+               } else {
+                       gpte[i].type = GUID_PARTITION_BASIC_DATA;
+               }
+
+               if (verbose)
+                       fprintf(stderr, "Partition %d: start=%" PRIu64 ", end=%" PRIu64 ", size=%"  PRIu64 "\n",
+                                       i,
+                                       start * DISK_SECTOR_SIZE, sect * DISK_SECTOR_SIZE,
+                                       (sect - start) * DISK_SECTOR_SIZE);
+               printf("%" PRIu64 "\n", start * DISK_SECTOR_SIZE);
+               printf("%" PRIu64 "\n", (sect - start) * DISK_SECTOR_SIZE);
+       }
+
+       gpte[GPT_ENTRY_MAX - 1].start = cpu_to_le64(GPT_FIRST_ENTRY_SECTOR + GPT_ENTRY_SIZE * GPT_ENTRY_MAX / DISK_SECTOR_SIZE);
+       gpte[GPT_ENTRY_MAX - 1].end = cpu_to_le64((kb_align ? round_to_kb(sectors) : (unsigned long)sectors) - 1);
+       gpte[GPT_ENTRY_MAX - 1].type = GUID_PARTITION_BIOS_BOOT;
+       gpte[GPT_ENTRY_MAX - 1].guid = guid;
+       gpte[GPT_ENTRY_MAX - 1].guid.b[sizeof(guid_t) -1] += GPT_ENTRY_MAX;
+
+       end = sect + sectors - 1;
+
+       pte.type = 0xEE;
+       pte.start = cpu_to_le32(GPT_HEADER_SECTOR);
+       pte.length = cpu_to_le32(end);
+       to_chs(GPT_HEADER_SECTOR, pte.chs_start);
+       to_chs(end, pte.chs_end);
+
+       gpth.last_usable = cpu_to_le64(end - GPT_ENTRY_SIZE * GPT_ENTRY_MAX / DISK_SECTOR_SIZE - 1);
+       gpth.alternate = cpu_to_le64(end);
+       gpth.entry_crc32 = cpu_to_le32(gpt_crc32(gpte, GPT_ENTRY_SIZE * GPT_ENTRY_MAX));
+       gpth.crc32 = cpu_to_le32(gpt_crc32((char *)&gpth, GPT_HEADER_SIZE));
+
+       if ((fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0644)) < 0) {
+               fprintf(stderr, "Can't open output file '%s'\n",filename);
+               return ret;
        }
 
-       lseek(fd, 440, SEEK_SET);
+       lseek(fd, MBR_DISK_SIGNATURE_OFFSET, SEEK_SET);
        if (write(fd, &signature, sizeof(signature)) != sizeof(signature)) {
-               fprintf(stderr, "write failed.\n");
+               fputs("write failed.\n", stderr);
                goto fail;
        }
 
-       lseek(fd, 446, SEEK_SET);
-       if (write(fd, pte, sizeof(struct pte) * 4) != sizeof(struct pte) * 4) {
-               fprintf(stderr, "write failed.\n");
+       lseek(fd, MBR_PARTITION_ENTRY_OFFSET, SEEK_SET);
+       if (write(fd, &pte, sizeof(struct pte)) != sizeof(struct pte)) {
+               fputs("write failed.\n", stderr);
                goto fail;
        }
-       lseek(fd, 510, SEEK_SET);
+
+       lseek(fd, MBR_BOOT_SIGNATURE_OFFSET, SEEK_SET);
        if (write(fd, "\x55\xaa", 2) != 2) {
-               fprintf(stderr, "write failed.\n");
+               fputs("write failed.\n", stderr);
+               goto fail;
+       }
+
+       if (write(fd, &gpth, GPT_HEADER_SIZE) != GPT_HEADER_SIZE) {
+               fputs("write failed.\n", stderr);
+               goto fail;
+       }
+
+       lseek(fd, GPT_FIRST_ENTRY_SECTOR * DISK_SECTOR_SIZE, SEEK_SET);
+       if (write(fd, &gpte, GPT_ENTRY_SIZE * GPT_ENTRY_MAX) != GPT_ENTRY_SIZE * GPT_ENTRY_MAX) {
+               fputs("write failed.\n", stderr);
+               goto fail;
+       }
+
+#ifdef WANT_ALTERNATE_PTABLE
+       /* The alternate partition table (We omit it by default) */
+       swap(gpth.self, gpth.alternate);
+       gpth.first_entry = cpu_to_le64(end - GPT_ENTRY_SIZE * GPT_ENTRY_MAX / DISK_SECTOR_SIZE),
+       gpth.crc32 = 0;
+       gpth.crc32 = cpu_to_le32(gpt_crc32(&gpth, GPT_HEADER_SIZE));
+
+       lseek(fd, end * DISK_SECTOR_SIZE - GPT_ENTRY_SIZE * GPT_ENTRY_MAX, SEEK_SET);
+       if (write(fd, &gpte, GPT_ENTRY_SIZE * GPT_ENTRY_MAX) != GPT_ENTRY_SIZE * GPT_ENTRY_MAX) {
+               fputs("write failed.\n", stderr);
+               goto fail;
+       }
+
+       lseek(fd, end * DISK_SECTOR_SIZE, SEEK_SET);
+       if (write(fd, &gpth, GPT_HEADER_SIZE) != GPT_HEADER_SIZE) {
+               fputs("write failed.\n", stderr);
+               goto fail;
+       }
+       lseek(fd, (end + 1) * DISK_SECTOR_SIZE -1, SEEK_SET);
+       if (write(fd, "\x00", 1) != 1) {
+               fputs("write failed.\n", stderr);
                goto fail;
        }
+#endif
 
        ret = 0;
 fail:
@@ -200,18 +481,20 @@ fail:
 
 static void usage(char *prog)
 {
-       fprintf(stderr, "Usage: %s [-v] [-n] -h <heads> -s <sectors> -o <outputfile> [-a 0..4] [-l <align kB>] [[-t <type>] -p <size>...] \n", prog);
+       fprintf(stderr, "Usage: %s [-v] [-n] [-g] -h <heads> -s <sectors> -o <outputfile> [-a 0..4] [-l <align kB>] [-G <guid>] [[-t <type>] -p <size>...] \n", prog);
        exit(EXIT_FAILURE);
 }
 
 int main (int argc, char **argv)
 {
-       char type = 0x83;
+       unsigned char type = 0x83;
        int ch;
        int part = 0;
        uint32_t signature = 0x5452574F; /* 'OWRT' */
+       guid_t guid = GUID_INIT( signature, 0x2211, 0x4433, \
+                       0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0x00);
 
-       while ((ch = getopt(argc, argv, "h:s:p:a:t:o:vnl:S:")) != -1) {
+       while ((ch = getopt(argc, argv, "h:s:p:a:t:o:vngl:S:G:")) != -1) {
                switch (ch) {
                case 'o':
                        filename = optarg;
@@ -222,6 +505,9 @@ int main (int argc, char **argv)
                case 'n':
                        ignore_null_sized_partition = true;
                        break;
+               case 'g':
+                       use_guid_partition_table = 1;
+                       break;
                case 'h':
                        heads = (int)strtoul(optarg, NULL, 0);
                        break;
@@ -229,8 +515,8 @@ int main (int argc, char **argv)
                        sectors = (int)strtoul(optarg, NULL, 0);
                        break;
                case 'p':
-                       if (part > 3) {
-                               fprintf(stderr, "Too many partitions\n");
+                       if (part > GPT_ENTRY_MAX - 1 || (!use_guid_partition_table && part > 3)) {
+                               fputs("Too many partitions\n", stderr);
                                exit(EXIT_FAILURE);
                        }
                        parts[part].size = to_kbytes(optarg);
@@ -250,6 +536,12 @@ int main (int argc, char **argv)
                case 'S':
                        signature = strtoul(optarg, NULL, 0);
                        break;
+               case 'G':
+                       if (guid_parse(optarg, &guid)) {
+                               fputs("Invalid guid string\n", stderr);
+                               exit(EXIT_FAILURE);
+                       }
+                       break;
                case '?':
                default:
                        usage(argv[0]);
@@ -259,5 +551,8 @@ int main (int argc, char **argv)
        if (argc || (heads <= 0) || (sectors <= 0) || !filename)
                usage(argv[0]);
 
+       if (use_guid_partition_table)
+               return gen_gptable(signature, guid, part) ? EXIT_FAILURE : EXIT_SUCCESS;
+
        return gen_ptable(signature, part) ? EXIT_FAILURE : EXIT_SUCCESS;
 }