blob: f114e1b7d9adb24d15f1effb31cd9c912274f34f [file] [log] [blame]
qs.xiongdf2e8c02025-07-02 16:50:13 +08001#!/usr/bin/env python3
2# ASR1806 Partition Configuration Generator (Final Correct Format)
3
4import re
5import json
6import sys
7import os
8import argparse
9from collections import OrderedDict
10from pathlib import Path
11
12def validate_file(path, description):
13 """Validate input file exists and is readable"""
14 path_obj = Path(path)
15 if not path_obj.exists():
16 raise FileNotFoundError(f"{description} not found: {path}")
17 if not path_obj.is_file():
18 raise IOError(f"{description} path is not a file: {path}")
19 if not os.access(str(path_obj), os.R_OK):
20 raise PermissionError(f"Cannot read {description}: {path}")
21 return path_obj
22
23def parse_dtsi_partitions(dtsi_file):
24 """Parse partition information from DTSI file"""
25 partitions = OrderedDict()
26 try:
27 with open(dtsi_file, 'r', encoding='utf-8') as f:
28 content = f.read()
29
30 pattern = re.compile(
31 r'partition@([0-9A-Fa-f]+)\s*{\s*.*?label\s*=\s*"([^"]+)";\s*.*?reg\s*=\s*<([^>]+)>;\s*.*?};',
32 re.DOTALL
33 )
34
35 for match in pattern.finditer(content):
36 try:
37 start_addr = int(match.group(1), 16)
38 label = match.group(2).strip()
39 reg_values = [int(x, 16) for x in match.group(3).split()]
40
41 if not reg_values:
42 print(f"Invalid reg value for partition {label}", file=sys.stderr)
43 continue
44
45 size = reg_values[-1] if len(reg_values) > 1 else reg_values[0]
46
47 partitions[label] = {
48 'start_addr': f"0x{start_addr:08X}",
49 'size': f"0x{size:08X}",
50 'size_bytes': size
51 }
52 except ValueError as e:
53 print(f"Failed to parse partition data: {e}", file=sys.stderr)
54 continue
55
56 if not partitions:
57 raise ValueError("No valid partitions found in DTSI file")
58
59 print(f"Parsed {len(partitions)} partitions from {dtsi_file}")
60 return partitions
61 except Exception as e:
62 raise RuntimeError(f"DTSI parse failed: {str(e)}") from e
63
64def load_config(config_file):
65 """Load and validate partition configuration"""
66 try:
67 with open(config_file, 'r', encoding='utf-8') as f:
68 config = json.load(f, object_pairs_hook=OrderedDict)
69
70 required = {
71 'mtdparts_order': list,
72 'partition_aliases': dict
73 }
74 for key, typ in required.items():
75 if key not in config:
76 raise KeyError(f"Missing required key: {key}")
77 if not isinstance(config[key], typ):
78 raise TypeError(f"{key} must be {typ.__name__}")
79
80 print(f"Loaded config from {config_file}")
81 return config
82 except json.JSONDecodeError as e:
83 raise RuntimeError(f"Invalid JSON format: {str(e)}") from e
84 except Exception as e:
85 raise RuntimeError(f"Config load failed: {str(e)}") from e
86
87def generate_complete_config(partitions, config):
88 """Generate the complete configuration block with exact formatting"""
89 # 1. Generate partition defines
90 defines = []
91 for part_name, alias in config['partition_aliases'].items():
92 if part_name not in partitions:
93 print(f"Config references unknown partition: {part_name}", file=sys.stderr)
94 continue
95 defines.append(f'#define {alias} "{part_name}"')
96 defines_section = "\n".join(defines)
97
98 # 2. Generate MTDPARTS content with exact formatting
99 mtdparts = []
100 valid_parts = [p for p in config['mtdparts_order'] if p in partitions and p in config['partition_aliases']]
101 num_parts = len(valid_parts)
102
103 for i, part_name in enumerate(valid_parts):
104 part = partitions[part_name]
105 alias = config['partition_aliases'][part_name]
106
107 # 构建每个分区的定义,格式为 "0x00180000@0x02600000("UBOOT_MTD_PART_A"),"
108 mtd_entry = f'0x{part["size"][2:]}@0x{part["start_addr"][2:]}("{alias}")'
109 if i < num_parts - 1:
110 mtd_entry += ',' # 除最后一个分区外,每个分区后加逗号
111
112 # 每个分区定义用双引号包裹,并添加行继续符和缩进
113 if i == 0:
114 # 第一个分区不需要额外缩进
115 mtdparts.append(f'"mtdparts=nand_mtd:{mtd_entry}"\\')
116 else:
117 # 后续分区需要 5 个空格缩进
118 mtdparts.append(f' "{mtd_entry}"\\')
119
120 # 最后一行需要单独处理,确保没有双引号和反斜杠
121 if mtdparts:
122 last_line = mtdparts.pop() # 移除最后一行
123 # 构建不带双引号和反斜杠的最后一行
124 mtdparts.append(last_line.replace('\\"', '').replace('\\', ''))
125
126 # 连接所有分区定义
127 mtdparts_joined = '\n'.join(mtdparts)
128
129 # 构建最终的 MTDPARTS 部分
130 mtdparts_section = f"""#ifdef CONFIG_CMD_FASTBOOT
131#define CONFIG_MTDPARTS \\
132{mtdparts_joined}
133#endif /* CONFIG_CMD_FASTBOOT */"""
134
135 # 3. Combine into complete block
136 complete_content = f"""{defines_section}
137
138{mtdparts_section}"""
139
140 return complete_content
141
142def update_header_file(header_file, complete_content):
143 """Replace ONLY the content between dynamic markers"""
144 try:
145 header_file.parent.mkdir(parents=True, exist_ok=True)
146
147 if not header_file.exists():
148 raise RuntimeError(f"Header file does not exist: {header_file}")
149
150 # Read existing content
151 with open(header_file, 'r', encoding='utf-8') as f:
152 content = f.read()
153 original_mode = header_file.stat().st_mode
154
155 # Pattern to find the exact block to replace
156 pattern = re.compile(
157 r'(/\* add mtd parts dynamically \*/\n)(.*?)(\n#else)',
158 re.DOTALL
159 )
160
161 match = pattern.search(content)
162 if not match:
163 raise RuntimeError("Could not find the replacement markers ('/* add mtd parts dynamically */' to '#else')")
164
165 # Replace only the middle part
166 updated_content = (
167 content[:match.start(2)] +
168 complete_content +
169 content[match.end(2):]
170 )
171
172 # Write updated content
173 with open(header_file, 'w', encoding='utf-8') as f:
174 f.write(updated_content)
175 header_file.chmod(original_mode)
176
177 print(f"Successfully updated {header_file}")
178 return True
179
180 except Exception as e:
181 raise RuntimeError(f"Header update failed: {str(e)}") from e
182
183def main():
184 parser = argparse.ArgumentParser(
185 description='Generate partition configuration between markers',
186 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
187
188 parser.add_argument('--dtsi', required=True,
189 help='Input DTSI file with partition layout')
190 parser.add_argument('--config', required=True,
191 help='JSON config file with partition aliases')
192 parser.add_argument('--header', required=True,
193 help='Output header file path')
194 parser.add_argument('--verbose', action='store_true',
195 help='Show detailed processing info')
196
197 args = parser.parse_args()
198
199 try:
200 dtsi_path = validate_file(args.dtsi, "DTSI file")
201 config_path = validate_file(args.config, "Config file")
202 header_path = Path(args.header).resolve()
203
204 if args.verbose:
205 print(f"Processing:\n- DTSI: {dtsi_path}\n- Config: {config_path}\n- Output: {header_path}")
206
207 partitions = parse_dtsi_partitions(dtsi_path)
208 config = load_config(config_path)
209
210 # Verify all configured partitions exist
211 missing = [p for p in config['mtdparts_order'] if p not in partitions]
212 if missing:
213 print(f"Missing partitions in DTSI: {', '.join(missing)}", file=sys.stderr)
214
215 complete_content = generate_complete_config(partitions, config)
216
217 if args.verbose:
218 print("\nGenerated content to insert:")
219 print("-" * 40)
220 print(complete_content)
221 print("-" * 40)
222
223 updated = update_header_file(header_path, complete_content)
224
225 if updated:
226 print("Update completed successfully")
227
228 return 0
229
230 except Exception as e:
231 print(f"Error: {str(e)}", file=sys.stderr)
232 return 1
233
234if __name__ == "__main__":
235 sys.exit(main())