3 # SPDX-License-Identifier: GPL-2.0-only
5 import sys
, os
, struct
, uuid
, zlib
, io
7 # This script wraps the bootblock in a GPT partition, because that's what
8 # SiFive's bootrom will load.
11 # Size of a GPT disk block, in bytes
13 BLOCK_MASK
= BLOCK_SIZE
- 1
15 # Size of the bootcode part of the MBR
16 MBR_BOOTCODE_SIZE
= 0x1be
18 # MBR trampoline to bootblock
19 MBR_BOOTCODE
= bytes([
21 0x6f, 0x00, 0x10, 0x00,
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,
38 # A "protective MBR"[1], which may also contain some boot code.
39 # [1]: https://en.wikipedia.org/wiki/GUID_Partition_Table#PROTECTIVE-MBR
42 self
.bootcode
= MBR_BOOTCODE
+ bytes(MBR_BOOTCODE_SIZE
- len(MBR_BOOTCODE
))
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
51 # Generate a GUID from a string
52 class GUID(uuid
.UUID
):
53 def __init__(self
, string
):
54 super().__init
__(string
)
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')
65 # https://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_table_header_(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
78 def pack_with_crc(self
, crc
):
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
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'))
95 # A GPT partition entry.
96 # https://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_entries_(LBA_2-33)
99 self
.type = GUID('00000000-0000-0000-0000-000000000000')
100 self
.uniq
= GUID('00000000-0000-0000-0000-000000000000')
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
117 # The final image consists of:
120 # - A few GPT partition entries
121 # - The content of the bootblock
123 self
.mbr
= ProtectiveMBR()
124 self
.header
= GPTHeader()
125 self
.partitions
= [ GPTPartition() for i
in range(8) ]
129 # Fix up a few numbers to ensure consistency between the different
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
)
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
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
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())
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
)
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],
171 with
open(sys
.argv
[1], 'rb') as f
:
172 image
.bootblock
= f
.read()
176 # Verify if first partition is at expected lba, otherwise trampoline will
178 if image
.partitions
[0].first_lba
!= 4:
179 print('Warning: First partition not at expected location (LBA 4)')
182 with
open(sys
.argv
[2], 'wb') as f
: