1+ # --------------------------------------------------------------------
2+ # Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved.
3+ # SPDX-License-Identifier: BSD-3-Clause-Clear
4+ # --------------------------------------------------------------------
5+
6+ import os
7+ import re
8+ import argparse
9+ import subprocess
10+ from xml .dom import minidom
11+ import sys
12+
13+ def safe_clone (repo_url , repo_dir ):
14+ if not os .path .exists (repo_dir ):
15+ try :
16+ subprocess .run (["git" , "clone" , repo_url ], check = True )
17+ except subprocess .CalledProcessError as e :
18+ print (f"Error cloning repo: { e } " )
19+ sys .exit (1 )
20+
21+ def read_partitions_conf (partition_conf_path ):
22+ try :
23+ with open (partition_conf_path , 'r' ) as f :
24+ return f .readlines ()
25+ except FileNotFoundError :
26+ print (f"Error: { partition_conf_path } not found." )
27+ sys .exit (1 )
28+
29+ def detect_storage_type_from_conf (lines ):
30+ for line in lines :
31+ if re .search (r'--type=ufs' , line , re .IGNORECASE ):
32+ return "UFS"
33+ elif re .search (r'--type=emmc' , line , re .IGNORECASE ):
34+ return "EMMC"
35+ print ("Error: Could not detect StorageType from partitions.conf." )
36+ sys .exit (1 )
37+
38+ def parse_partition_info (args , lines , storage_type ):
39+ partition_info = {}
40+ if storage_type == "EMMC" and args .F :
41+ pattern = re .compile (
42+ r"--partition\s+--name=([\w\-]+)\s+--size=\d+KB\s+--type-guid=([\w\-]+)(?:\s+--type=emmc)?(?:\s+--filename=([\w\.\-]+))?" ,
43+ re .IGNORECASE
44+ )
45+ for line in lines :
46+ match = pattern .search (line )
47+ if match :
48+ name , guid , filename = match .groups ()
49+ partition_info [name ] = {
50+ "guid" : guid ,
51+ "filename" : filename if filename else f"{ name } .img"
52+ }
53+ elif storage_type == "UFS" or (storage_type == "EMMC" and not args .F ):
54+ pattern = re .compile (
55+ r"--partition\s+--lun=(\d+)\s+--name=([\w\-]+)\s+--size=\d+KB\s+--type-guid=([\w\-]+)(?:\s+--type=ufs)?(?:\s+--filename=([\w\.\-]+))?" ,
56+ re .IGNORECASE
57+ )
58+ for line in lines :
59+ match = pattern .search (line )
60+ if match :
61+ lun , name , guid , filename = match .groups ()
62+ if lun in ['1' , '4' ]:
63+ partition_info [name ] = {
64+ "lun" : lun ,
65+ "guid" : guid ,
66+ "filename" : filename if filename else f"{ name } .img"
67+ }
68+ return partition_info
69+
70+ def find_base_names (partition_info ):
71+ base_names = set ()
72+ for name in partition_info :
73+ if name .endswith ("_a" ) and name [:- 2 ] + "_b" in partition_info :
74+ base_names .add (name [:- 2 ])
75+ return base_names
76+
77+ def create_xml (args , base_names , partition_info ):
78+ doc = minidom .Document ()
79+ fvitems = doc .createElement ("FVItems" )
80+ doc .appendChild (fvitems )
81+
82+ metadata = doc .createElement ("Metadata" )
83+ for tag , text in [("BreakingChangeNumber" , "0" ), ("FlashType" , args .StorageType )]:
84+ elem = doc .createElement (tag )
85+ elem .appendChild (doc .createTextNode (text ))
86+ metadata .appendChild (elem )
87+ fvitems .appendChild (metadata )
88+
89+ for base in sorted (base_names ):
90+ part_a = partition_info [f"{ base } _a" ]
91+ part_b = partition_info [f"{ base } _b" ]
92+
93+ if args .StorageType == "UFS" :
94+ disk_type = f"UFS_LUN{ part_a ['lun' ]} "
95+ else :
96+ disk_type = "EMMC_PARTITION_USER_DATA"
97+
98+ fw_entry = doc .createElement ("FwEntry" )
99+ for tag , text in [
100+ ("InputBinary" , part_a ["filename" ]),
101+ ("InputPath" , "Images" ),
102+ ("Operation" , "IGNORE" ),
103+ ("UpdateType" , "UPDATE_PARTITION" ),
104+ ("BackupType" , "BACKUP_PARTITION" )
105+ ]:
106+ elem = doc .createElement (tag )
107+ elem .appendChild (doc .createTextNode (text ))
108+ fw_entry .appendChild (elem )
109+
110+ dest = doc .createElement ("Dest" )
111+ for tag , text in [
112+ ("DiskType" , disk_type ),
113+ ("PartitionName" , f"{ base } _a" ),
114+ ("PartitionTypeGUID" , part_a ["guid" ])
115+ ]:
116+ elem = doc .createElement (tag )
117+ elem .appendChild (doc .createTextNode (text ))
118+ dest .appendChild (elem )
119+ fw_entry .appendChild (dest )
120+
121+ backup = doc .createElement ("Backup" )
122+ for tag , text in [
123+ ("DiskType" , disk_type ),
124+ ("PartitionName" , f"{ base } _b" ),
125+ ("PartitionTypeGUID" , part_b ["guid" ])
126+ ]:
127+ elem = doc .createElement (tag )
128+ elem .appendChild (doc .createTextNode (text ))
129+ backup .appendChild (elem )
130+ fw_entry .appendChild (backup )
131+
132+ fvitems .appendChild (fw_entry )
133+ return doc
134+
135+ def write_xml (doc , output_file = "FvUpdate.xml" ):
136+ with open (output_file , "w" , encoding = "utf-8" ) as f :
137+ f .write ('<?xml version="1.0" encoding="utf-8"?>\n ' )
138+ xml_str = doc .toprettyxml (indent = " " )
139+ xml_str = re .sub (r'<\?xml.*?\?>\n?' , '' , xml_str )
140+ f .write (xml_str )
141+
142+ def main ():
143+ parser = argparse .ArgumentParser (description = "Generate FvUpdate.xml from partitions.conf" )
144+ custom_usage = "UpdateFvXml.py [-h] (-T TARGET & -S {UFS,EMMC}) | [-F PARTITIONS_CONF]"
145+ parser = argparse .ArgumentParser (usage = custom_usage )
146+ parser .add_argument ('-T' , metavar = 'TARGET' , help = 'Target argument' )
147+ parser .add_argument ("-S" , "--StorageType" , choices = ["UFS" , "EMMC" ], help = "Specify storage type: UFS or EMMC" )
148+ parser .add_argument ('-F' , metavar = 'PARTITIONS_CONF' , help = 'Partitions config argument' )
149+ args = parser .parse_args ()
150+
151+ if args .F :
152+ if args .StorageType :
153+ print ("Error: Do not provide -S/--StorageType when using -F/--partitions_conf. It will be auto-detected." )
154+ sys .exit (1 )
155+ if args .T :
156+ print ("Error: Do not provide -T/--StorageType when using -F/--partitions_conf." )
157+ sys .exit (1 )
158+ partition_conf_path = args .F
159+ lines = read_partitions_conf (partition_conf_path )
160+ args .StorageType = detect_storage_type_from_conf (lines )
161+ elif args .T :
162+ if not args .StorageType :
163+ print ("Error: You must provide -S/--StorageType when using -T/--target." )
164+ sys .exit (1 )
165+ repo_url = "https://github.com/qualcomm-linux/qcom-ptool.git"
166+ repo_dir = "qcom-ptool"
167+ safe_clone (repo_url , repo_dir )
168+ if args .T == "QCS6490" :
169+ target = "qcs6490-rb3gen2"
170+ elif args .T == "QCS9100" :
171+ target = "qcs9100-ride-sx"
172+ elif args .T == "QSC8300" :
173+ target = "qcs8300-ride-sx"
174+ elif args .T == "QCS615" :
175+ target = "qcs615-adp-air"
176+ else :
177+ print (f"Provided target is Unknown !!! Please re-check" )
178+ sys .exit (1 )
179+ partition_conf_path = os .path .join (repo_dir , "platforms" , target , "ufs" , "partitions.conf" )
180+ lines = read_partitions_conf (partition_conf_path )
181+ else :
182+ print ("Error: Invalid argument combination." )
183+ parser .print_usage ()
184+ sys .exit (1 )
185+
186+ partition_info = parse_partition_info (args , lines , args .StorageType )
187+ base_names = find_base_names (partition_info )
188+ if not base_names :
189+ print ("Warning: No partition pairs (_a/_b) found. FvUpdate.xml will not contain FwEntry blocks." )
190+ doc = create_xml (args , base_names , partition_info )
191+ write_xml (doc )
192+ print (f"FvUpdate.xml has been created successfully with StorageType={ args .StorageType } ." )
193+
194+ if __name__ == "__main__" :
195+ main ()
0 commit comments