1#!/usr/bin/env python3 2# 3# SPDX-License-Identifier: GPL-2.0-only 4 5import 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 12BLOCK_SIZE = 512 13BLOCK_MASK = BLOCK_SIZE - 1 14 15# Size of the bootcode part of the MBR 16MBR_BOOTCODE_SIZE = 0x1be 17 18# MBR trampoline to bootblock 19MBR_BOOTCODE = bytes([ 20 # j pc + 0x0800 21 0x6f, 0x00, 0x10, 0x00, 22]) 23 24# A protecive MBR, without the bootcode part 25PROTECTIVE_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 40class 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 52class GUID(uuid.UUID): 53 def __init__(self, string): 54 super().__init__(string) 55 56 def get_bytes(self): 57 return self.bytes_le 58 59DUMMY_GUID_DISK_UNIQUE = GUID('17145242-abaa-441d-916a-3f26c970aba2') 60DUMMY_GUID_PART_UNIQUE = GUID('7552133d-c8de-4a20-924c-0e85f5ea81f2') 61GUID_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) 66class 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) 97class 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 116class 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 162if __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) 184