/ util / riscv / sifive-gpt.py
sifive-gpt.py
  1  #!/usr/bin/env python3
  2  #
  3  # SPDX-License-Identifier: GPL-2.0-only
  4  
  5  import sys, os, struct, uuid, zlib, io
  6  
  7  # This script wraps the bootblock in a GPT partition, because that's what
  8  # SiFive's bootrom will load.
  9  
 10  
 11  # Size of a GPT disk block, in bytes
 12  BLOCK_SIZE = 512
 13  BLOCK_MASK = BLOCK_SIZE - 1
 14  
 15  # Size of the bootcode part of the MBR
 16  MBR_BOOTCODE_SIZE = 0x1be
 17  
 18  # MBR trampoline to bootblock
 19  MBR_BOOTCODE = bytes([
 20      # j pc + 0x0800
 21      0x6f, 0x00, 0x10, 0x00,
 22  ])
 23  
 24  # A protecive MBR, without the bootcode part
 25  PROTECTIVE_MBR_FOOTER = bytes([
 26      0x00, 0x00, 0x02, 0x00, 0xee, 0xff, 0xff, 0xff,
 27      0x01, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
 28      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 29      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 30      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 31      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 32      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 33      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 34      0x55, 0xaa
 35  ])
 36  
 37  
 38  # A "protective MBR"[1], which may also contain some boot code.
 39  # [1]: https://en.wikipedia.org/wiki/GUID_Partition_Table#PROTECTIVE-MBR
 40  class ProtectiveMBR:
 41      def __init__(self):
 42          self.bootcode = MBR_BOOTCODE + bytes(MBR_BOOTCODE_SIZE - len(MBR_BOOTCODE))
 43  
 44      def generate(self, stream):
 45          assert len(self.bootcode) == MBR_BOOTCODE_SIZE
 46          mbr = self.bootcode + PROTECTIVE_MBR_FOOTER
 47          assert len(mbr) == BLOCK_SIZE
 48          stream.write(mbr)
 49  
 50  
 51  # Generate a GUID from a string
 52  class GUID(uuid.UUID):
 53      def __init__(self, string):
 54          super().__init__(string)
 55  
 56      def get_bytes(self):
 57          return self.bytes_le
 58  
 59  DUMMY_GUID_DISK_UNIQUE = GUID('17145242-abaa-441d-916a-3f26c970aba2')
 60  DUMMY_GUID_PART_UNIQUE = GUID('7552133d-c8de-4a20-924c-0e85f5ea81f2')
 61  GUID_TYPE_FSBL = GUID('5B193300-FC78-40CD-8002-E86C45580B47')
 62  
 63  
 64  # A GPT disk header
 65  # https://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_table_header_(LBA_1)
 66  class GPTHeader:
 67      def __init__(self):
 68          self.current_lba = 1
 69          self.backup_lba = 1
 70          self.first_usable_lba = 2
 71          self.last_usable_lba = 0xff     # dummy value
 72          self.uniq = DUMMY_GUID_DISK_UNIQUE
 73          self.part_entries_lba = 2
 74          self.part_entries_number = 0
 75          self.part_entries_crc32 = 0
 76          self.part_entry_size = 128
 77  
 78      def pack_with_crc(self, crc):
 79          header_size = 92
 80          header = struct.pack('<8sIIIIQQQQ16sQIII',
 81                  b'EFI PART', 0x10000, header_size, crc, 0,
 82                  self.current_lba, self.backup_lba, self.first_usable_lba,
 83                  self.last_usable_lba, self.uniq.get_bytes(),
 84                  self.part_entries_lba, self.part_entries_number,
 85                  self.part_entry_size, self.part_entries_crc32)
 86          assert len(header) == header_size
 87          return header
 88  
 89      def generate(self, stream):
 90          crc = zlib.crc32(self.pack_with_crc(0))
 91          header = self.pack_with_crc(crc)
 92          stream.write(header.ljust(BLOCK_SIZE, b'\0'))
 93  
 94  
 95  # A GPT partition entry.
 96  # https://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_entries_(LBA_2-33)
 97  class GPTPartition:
 98      def __init__(self):
 99          self.type = GUID('00000000-0000-0000-0000-000000000000')
100          self.uniq = GUID('00000000-0000-0000-0000-000000000000')
101          self.first_lba = 0
102          self.last_lba = 0
103          self.attr = 0
104          self.name = ''
105  
106      def generate(self, stream):
107          name_utf16 = self.name.encode('UTF-16LE')
108          part = struct.pack('<16s16sQQQ72s',
109                  self.type.get_bytes(), self.uniq.get_bytes(),
110                  self.first_lba, self.last_lba, self.attr,
111                  name_utf16.ljust(72, b'\0'))
112          assert len(part) == 128
113          stream.write(part)
114  
115  
116  class GPTImage:
117      # The final image consists of:
118      # - A protective MBR
119      # - A GPT header
120      # - A few GPT partition entries
121      # - The content of the bootblock
122      def __init__(self):
123          self.mbr = ProtectiveMBR()
124          self.header = GPTHeader()
125          self.partitions = [ GPTPartition() for i in range(8) ]
126          self.bootblock = b''
127  
128  
129      # Fix up a few numbers to ensure consistency between the different
130      # components.
131      def fixup(self):
132          # Align the bootblock to a whole number to LBA blocks
133          bootblock_size = (len(self.bootblock) + BLOCK_SIZE - 1) & ~BLOCK_MASK
134          self.bootblock = self.bootblock.ljust(bootblock_size)
135  
136          # Propagate the number of partition entries
137          self.header.part_entries_number = len(self.partitions)
138          self.header.first_usable_lba = 2 + self.header.part_entries_number // 4
139  
140          # Create a partition entry for the bootblock
141          self.partitions[0].type = GUID_TYPE_FSBL
142          self.partitions[0].uniq = DUMMY_GUID_PART_UNIQUE
143          self.partitions[0].first_lba = self.header.first_usable_lba
144          self.partitions[0].last_lba = \
145              self.header.first_usable_lba + bootblock_size // BLOCK_SIZE
146  
147          # Calculate the CRC32 checksum of the partitions array
148          partition_array = io.BytesIO()
149          for part in self.partitions:
150              part.generate(partition_array)
151          self.header.part_entries_crc32 = zlib.crc32(partition_array.getvalue())
152  
153  
154      def generate(self, stream):
155          self.mbr.generate(stream)
156          self.header.generate(stream)
157          for part in self.partitions:
158              part.generate(stream)
159          stream.write(self.bootblock)
160  
161  
162  if __name__ == '__main__':
163      if len(sys.argv) != 3:
164          print('Usage:', file=sys.stderr)
165          print('    %s bootblock.raw.bin bootblock.bin' % sys.argv[0],
166                  file=sys.stderr)
167          sys.exit(1)
168  
169      image = GPTImage()
170  
171      with open(sys.argv[1], 'rb') as f:
172          image.bootblock = f.read()
173  
174      image.fixup()
175  
176      # Verify if first partition is at expected lba, otherwise trampoline will
177      # fail
178      if image.partitions[0].first_lba != 4:
179          print('Warning: First partition not at expected location (LBA 4)')
180          sys.exit(1)
181  
182      with open(sys.argv[2], 'wb') as f:
183          image.generate(f)