rjw | 1f88458 | 2022-01-06 17:20:42 +0800 | [diff] [blame^] | 1 | #!/usr/bin/env python3 |
| 2 | |
| 3 | from __future__ import print_function |
| 4 | import binascii |
| 5 | import json |
| 6 | import os |
| 7 | import struct |
| 8 | import sys |
| 9 | import uuid |
| 10 | import xml.dom.minidom |
| 11 | |
| 12 | NumberOfPartitionEntries = 64 |
| 13 | SizeOfPartitionEntry = 128 |
| 14 | |
| 15 | def write(path, data): |
| 16 | with open(path, "wb+") as f: |
| 17 | f.write(data) |
| 18 | |
| 19 | def crc32(data): |
| 20 | return binascii.crc32(data) & 0xffffffff |
| 21 | |
| 22 | def padding(data, size): |
| 23 | return data + b'\0' * (size - len(data)) |
| 24 | |
| 25 | def gen_gpt(partition): |
| 26 | pmbr = (b'\0' * 446 + |
| 27 | b"\x00\x00\x02\x00\xee\xff\xff\xff\x01\x00\x00\x00\xff\xff\xff\xff" + |
| 28 | b'\0' * 48 + b"\x55\xaa") |
| 29 | entries = b'' |
| 30 | entries2k = b'' |
| 31 | lba = partition.getAttribute("lba") |
| 32 | lba = lba and eval(lba) or 130560 |
| 33 | lbs = partition.getAttribute("lbs") |
| 34 | lbs = lbs and eval(lbs) or 4096 |
| 35 | for node in partition.childNodes: |
| 36 | if node.nodeName != "entry": |
| 37 | continue |
| 38 | type = uuid.UUID(node.getAttribute("type")) |
| 39 | uniq = node.getAttribute("uuid") |
| 40 | uniq = uniq and uuid.UUID(uniq) or uuid.uuid4() |
| 41 | start = eval(node.getAttribute("start")) |
| 42 | end = eval(node.getAttribute("end")) |
| 43 | attr = node.getAttribute("attributes") |
| 44 | attr = attr and eval(attr) or 0 |
| 45 | name = node.getAttribute("name") |
| 46 | if (name == b"userdata") and (end == 0): |
| 47 | end = lba - 1 |
| 48 | if attr == 4: |
| 49 | start2k = (start / 256) * 256 + 8 |
| 50 | entries2k += struct.pack("<16s16sQQQ72s", |
| 51 | type.bytes_le, |
| 52 | uniq.bytes_le, |
| 53 | int(start2k), end, 4, |
| 54 | name.encode("utf-16le")) |
| 55 | if ((start % 256) == 0): |
| 56 | start = (start / 256) * 256 + 4 |
| 57 | entries += struct.pack("<16s16sQQQ72s", |
| 58 | type.bytes_le, |
| 59 | uniq.bytes_le, |
| 60 | int(start), end, attr, |
| 61 | name.encode("utf-16le")) |
| 62 | entries = padding(entries, NumberOfPartitionEntries * SizeOfPartitionEntry) |
| 63 | entries2k = padding(entries2k, 2048) |
| 64 | FirstUsableLBA = 2 + (NumberOfPartitionEntries * SizeOfPartitionEntry / lbs) |
| 65 | uniq = partition.getAttribute("uuid") |
| 66 | uniq = uniq and uuid.UUID(uniq) or uuid.uuid4() |
| 67 | if lbs == 4096: |
| 68 | pmbr = padding(pmbr, 2048) |
| 69 | else: |
| 70 | pmbr = padding(pmbr, lbs) |
| 71 | header2k = struct.pack("<8sIIIIQQQQ16sQIII", |
| 72 | b"EFI PART", 0x00010000, 92, 0, 0, |
| 73 | 1, # MyLBA |
| 74 | 1, #lba - 1, # AlternateLBA |
| 75 | int(FirstUsableLBA), |
| 76 | int(lba - FirstUsableLBA), # LastUsableLBA |
| 77 | uniq.bytes_le, |
| 78 | 3, 4, int(SizeOfPartitionEntry), crc32(entries2k)) |
| 79 | header2k = padding(header2k[:16] + |
| 80 | struct.pack('I', crc32(header2k)) + |
| 81 | header2k[20:], 2048) |
| 82 | header = struct.pack("<8sIIIIQQQQ16sQIII", |
| 83 | b"EFI PART", 0x00010000, 92, 0, 0, |
| 84 | 1, # MyLBA |
| 85 | 1, #lba - 1, # AlternateLBA |
| 86 | int(FirstUsableLBA), |
| 87 | int(lba - FirstUsableLBA), # LastUsableLBA |
| 88 | uniq.bytes_le, |
| 89 | 2, int(NumberOfPartitionEntries), int(SizeOfPartitionEntry), crc32(entries)) |
| 90 | if lbs == 4096: |
| 91 | header = padding(header[:16] + |
| 92 | struct.pack('I', crc32(header)) + |
| 93 | header[20:], 2048) |
| 94 | else: |
| 95 | header = padding(header[:16] + |
| 96 | struct.pack('I', crc32(header)) + |
| 97 | header[20:], lbs) |
| 98 | if lbs == 4096: |
| 99 | return pmbr + header2k + header + entries2k + entries |
| 100 | else: |
| 101 | return pmbr + header + entries |
| 102 | |
| 103 | def write_scatter_partition(f, entry): |
| 104 | if entry.get("file_name", "NONE") != "NONE": |
| 105 | entry.setdefault("is_download", True) |
| 106 | entry.setdefault("operation_type", "UPDATE") |
| 107 | entry.setdefault("type", "NORMAL_ROM") |
| 108 | f.write( |
| 109 | """- partition_index: %s |
| 110 | partition_name: %s |
| 111 | file_name: %s |
| 112 | is_download: %s |
| 113 | type: %s |
| 114 | linear_start_addr: %s |
| 115 | physical_start_addr: %s |
| 116 | partition_size: %s |
| 117 | region: %s |
| 118 | storage: %s |
| 119 | boundary_check: %s |
| 120 | is_reserved: %s |
| 121 | operation_type: %s |
| 122 | d_type: LOW_PAGE |
| 123 | slc_percentage: 0 |
| 124 | is_upgradable: true |
| 125 | reserve: %s |
| 126 | |
| 127 | """ % (entry["partition_index"], entry["partition_name"], entry.get("file_name", "NONE"), |
| 128 | entry.get("is_download", False) and "true" or "false", entry.get("type", "NONE"), hex(entry["linear_start_addr"]), |
| 129 | hex(entry["physical_start_addr"]), hex(entry["partition_size"]), entry.get("region", "NONE"), |
| 130 | entry.get("storage", "HW_STORAGE_NAND"), entry.get("boundary_check", True) and "true" or "false", |
| 131 | entry.get("is_reserved", False) and "true" or "false", entry.get("operation_type", "PROTECTED"), |
| 132 | hex(entry.get("reserve", 0)))) |
| 133 | |
| 134 | def write_scatter(f, partition, d): |
| 135 | f.write( |
| 136 | """############################################################################################################ |
| 137 | # |
| 138 | # General Setting |
| 139 | # |
| 140 | ############################################################################################################ |
| 141 | - general: MTK_PLATFORM_CFG |
| 142 | info: |
| 143 | - config_version: %s |
| 144 | platform: %s |
| 145 | project: %s |
| 146 | storage: %s |
| 147 | boot_channel: %s |
| 148 | block_size: %s |
| 149 | skip_pmt_operate: %s |
| 150 | ############################################################################################################ |
| 151 | # |
| 152 | # Layout Setting |
| 153 | # |
| 154 | ############################################################################################################ |
| 155 | """ % (d["MTK_PLATFORM_CFG"]["config_version"], d["MTK_PLATFORM_CFG"]["platform"], d["MTK_PLATFORM_CFG"]["project"], |
| 156 | d["MTK_PLATFORM_CFG"]["storage"], d["MTK_PLATFORM_CFG"]["boot_channel"], d["MTK_PLATFORM_CFG"]["block_size"], |
| 157 | d["MTK_PLATFORM_CFG"].get("skip_pmt_operate", True) and "true" or "false")) |
| 158 | # d["PRELOADER"]["partition_index"] = "SYS0" |
| 159 | d["MBR"]["partition_index"] = "SYS0" |
| 160 | # write_scatter_partition(f, d["PRELOADER"]) |
| 161 | write_scatter_partition(f, d["MBR"]) |
| 162 | lbs = partition.getAttribute("lbs") |
| 163 | lbs = lbs and eval(lbs) or 4096 |
| 164 | i = 1 |
| 165 | for node in partition.childNodes: |
| 166 | if node.nodeName != "entry": |
| 167 | continue |
| 168 | start = eval(node.getAttribute("start")) |
| 169 | end = eval(node.getAttribute("end")) |
| 170 | name = node.getAttribute("name") |
| 171 | if name not in d: |
| 172 | continue |
| 173 | entry = d[name] |
| 174 | entry["partition_name"] = name |
| 175 | entry["partition_index"] = "SYS%d" % i |
| 176 | i += 1 |
| 177 | entry["linear_start_addr"] = start * lbs |
| 178 | entry["physical_start_addr"] = start * lbs |
| 179 | if end != start: |
| 180 | entry["partition_size"] = (end + 1 - start) * lbs |
| 181 | else: |
| 182 | entry["partition_size"] = 0 |
| 183 | write_scatter_partition(f, entry) |
| 184 | if (d["MTK_PLATFORM_CFG"].get("skip_pmt_operate", True) and "true" or "false") == "false": |
| 185 | d["sgpt"]["partition_index"] = "SYS%d" % i |
| 186 | write_scatter_partition(f, d["sgpt"]) |
| 187 | |
| 188 | |
| 189 | def sanity_check(path, partition): |
| 190 | err = 0 |
| 191 | lba = partition.getAttribute("lba") |
| 192 | lba = lba and eval(lba) or 130560 |
| 193 | lbs = partition.getAttribute("lbs") |
| 194 | lbs = lbs and eval(lbs) or 4096 |
| 195 | FirstUsableLBA = 2 + (NumberOfPartitionEntries * SizeOfPartitionEntry / lbs) |
| 196 | # usable = (FirstUsableLBA, lba - FirstUsableLBA) |
| 197 | usable = (0, lba) |
| 198 | used = {} |
| 199 | for node in partition.childNodes: |
| 200 | if node.nodeName != "entry": |
| 201 | continue |
| 202 | name = node.getAttribute("name") |
| 203 | start = eval(node.getAttribute("start")) |
| 204 | end = eval(node.getAttribute("end")) |
| 205 | if (name == "userdata") and (end == 0): |
| 206 | end = lba - 1 |
| 207 | if start > end: |
| 208 | print("%s: error: partition '%s': start lba (%d) > end lba (%d)" % |
| 209 | (path, name, start, end), file = sys.stderr) |
| 210 | err += 1 |
| 211 | if start < usable[0] or end > usable[1]: |
| 212 | print("%s: error: partition '%s': (%d...%d) out of usable range (%d...%d)" % |
| 213 | (path, name, start, end, usable[0], usable[1]), file = sys.stderr) |
| 214 | err += 1 |
| 215 | for i in used: |
| 216 | if (used[i][0] <= start and start <= used[i][1] or |
| 217 | used[i][0] <= end and end <= used[i][1]): |
| 218 | print("%s: error: partition '%s': (%d...%d) overlapped with partition '%s' (%d...%d)" % |
| 219 | (path, name, start, end, i, used[i][0], used[i][1]), file = sys.stderr) |
| 220 | err += 1 |
| 221 | used[name] = (start, end) |
| 222 | return err |
| 223 | |
| 224 | def main(argv): |
| 225 | if len(argv) == 5: # normal argument list len(argv) = 5 # |
| 226 | print ("len:%d .py=%s .xml=%s .json=%s MBR=%s .txt=%s" % |
| 227 | (len(argv), argv[0], argv[1], argv[2], argv[3], argv[4])) |
| 228 | if len(argv) <= 3: # len(argv) = 4 for mt2701 spi nor boot (scatter.txt isn't needed) # |
| 229 | print("Usage: len:%d,%s partition_*.xml scatter_*.json [MBR] [scatter.txt] " % (len(argv), argv[0])) |
| 230 | exit(1) |
| 231 | root = xml.dom.minidom.parse(argv[1]) |
| 232 | for partition in root.childNodes: |
| 233 | if partition.nodeName == "partition": |
| 234 | break |
| 235 | else: |
| 236 | raise Exception("partition not found") |
| 237 | if sanity_check(argv[1], partition): |
| 238 | return 1 |
| 239 | write(argv[3], gen_gpt(partition)) |
| 240 | if len(argv) == 5: |
| 241 | with open(os.path.join(os.path.dirname(__file__), argv[2]), "r") as f: |
| 242 | d = json.load(f) |
| 243 | with open(argv[4], "w") as f: |
| 244 | write_scatter(f, partition, d) |
| 245 | return 0 |
| 246 | |
| 247 | if __name__ == "__main__": |
| 248 | sys.exit(main(sys.argv)) |