Skip to content

Commit f145bda

Browse files
committed
Add Script to generate or update FvUpdate.xml file with Firmware entries
The current FvUpdate.xml was created as part of a proof of concept to demonstrate EFI capsule functionality. For real HW, this file is much more detailed and can be difficult / error prone to create. There are already partition files in the https://github.com/qualcomm-linux/qcom-ptool repo for most supported Qualcomm Linux HW and various storage types (emmc and UFS). Let's introduce a script which parses one of those `partitions.conf` files as a way of generating an initial `FvUpdate.xml` which can then be customized by the user as needed. The script takes either: - a file path to a local partition file - parameters specifying the type of HW and storage type in order to choose the reference partition.conf from the online qcom-ptool repo NOTE: Once the new FvUpdate.xml file is generated, please update the operation to be performed on each firmware entry. By default the operation type will be set to IGNORE. Signed-off-by: Venkat Thogaru <[email protected]>
1 parent 458e158 commit f145bda

File tree

3 files changed

+227
-4
lines changed

3 files changed

+227
-4
lines changed

uefi_capsule_generation/README.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,26 @@ BintoHex.py <InputFile> <OutputFile>
7171
```
7272
- Then above command will print the Firmware Verions in the .bin file
7373

74+
3. **Generate/Update FvUpdate.xml with Firmware entries:**
75+
76+
This script will generate FvUdpate.xml file, with Firmware Entries,
77+
```sh
78+
python3 UpdateFvXml.py -S <StorageType> -T <Target>
79+
80+
or
81+
82+
python3 UpdateFvXml.py -F <Local partition.conf file> -S <StorageType>
83+
```
84+
StorageType[-S]: Can be EMMC/UFS<p>
85+
Local partition.conf file [-F]: Path of partition.conf file<p>
86+
Target[-T]: Can be ***QCS6490*** / ***QCS9100*** / ***QCS8300*** / ***QCS615*** <p>
87+
Usage: UpdateFvXml.py [-h] [-S {UFS,EMMC}] [-T TARGET | -F PARTITIONS_CONF]<p>
88+
89+
***Once the FvUpdate.xml file is generated, please update the Operation to be performed on the each Firmware entry***<p>
90+
***By default the Operation type will be set to IGNORE***<p>
7491

92+
93+
7594
3. **Create Firmware Volume (FV):**
7695

7796
***\* Ensure the /Images folder contains all the required firmware images for Capsule generation***<p>
@@ -89,7 +108,7 @@ BintoHex.py <InputFile> <OutputFile>
89108

90109
4. **Update JSON Parameters:**
91110
```sh
92-
python3 UpdateJsonParameters.py -j config.json -f SYS_FW -b SYSFW_VERSION.bin -pf firmware.fv -p Certificates/QcFMPCert.pem -x Certificates/QcFMPRoot.pub.pem -oc Certificates/QcFMPSub.pub.pem -g <ESRT GUID>
111+
python3 UpdateJsonParameters.py -j config.json -f SYS_FW -b SYSFW_VERSION.bin -pf firmware.fv -p Certificates/QcFMPCert.pem -x Certificates/QcFMPRoot.pub.pem -oc Certificates/QcFMPSub.pub.pem -g <ESRT GUID> -S <StorageType> -T <Target>
93112
```
94113

95114

@@ -100,13 +119,17 @@ BintoHex.py <InputFile> <OutputFile>
100119
-p Certificates/QcFMPCert.pem: Certificate file.<p>
101120
-x Certificates/QcFMPRoot.pub.pem: Root public certificate.<p>
102121
-oc Certificates/QcFMPSub.pub.pem: Subordinate public certificate.<p>
122+
-S <StorageType>: EMMC/UFS <p>
123+
-T <Target>: ***QCS6490*** / ***QCS9100*** / ***QCS8300*** / ***QCS615*** <p>
103124
-g <ESRT GUID>: ESRT GUID.<p>
104125
ESRT GUIDs :<p>
105126
- QCS6490 ESRT GUID: 6F25BFD2-A165-468B-980F-AC51A0A45C52<p>
106127
- QCS9100 ESRT GUID: 78462415-6133-431C-9FAE-48F2BAFD5C71<p>
107128
- QCS8300 ESRT GUID: 8BF4424F-E842-409C-80BE-1418E91EF343<p>
108129
- QCS615 ESRT GUID: 9FD379D2-670E-4BB3-86A1-40497E6E17B0<p>
109130

131+
132+
110133
5. **Generate the Capsule File:**
111134
```sh
112135
python3 GenerateCapsule.py -e -j config.json -o <capsule_name>.cap --capflag PersistAcrossReset -v
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
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()

uefi_capsule_generation/capsule_creator.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,16 @@ def main(args):
2424
# Step 1: Generate SYSFW_VERSION.bin
2525
run_command(f"python3 SYSFW_VERSION_program.py -Gen -FwVer {args.fwver} -LFwVer {args.lfwver} -O SYSFW_VERSION.bin")
2626

27-
# Step 2: Create firmware volume
27+
# Step 2: Create FvUpdate.xml
28+
run_command(f'python3 UpdateFvXml.py -S {args.S} -T {args.t}')
29+
30+
# Step 3: Create firmware volume
2831
run_command(f'python3 FVCreation.py firmware.fv "-FvType" "SYS_FW" "FvUpdate.xml" SYSFW_VERSION.bin {args.images}')
2932

30-
# Step 3: Update JSON parameters
33+
# Step 4: Update JSON parameters
3134
run_command(f'python3 UpdateJsonParameters.py -j {args.config} -f SYS_FW -b SYSFW_VERSION.bin -pf firmware.fv -p {args.p} -x {args.x} -oc {args.oc} -g {args.guid}')
3235

33-
# Step 4: Generate capsule
36+
# Step 5: Generate capsule
3437
run_command(f'python3 GenerateCapsule.py -e -j {args.config} -o {args.capsule} --capflag PersistAcrossReset -v')
3538

3639
if __name__ == "__main__":
@@ -45,6 +48,8 @@ def main(args):
4548
parser.add_argument('-capsule', required=True, help='Output capsule file name')
4649
parser.add_argument('-images', required=True, help='Images directory')
4750
parser.add_argument('-setup', action='store_true', help='Run capsule setup script')
51+
parser.add_argument("-S", "--StorageType", choices=["UFS", "EMMC"], required=True, help="Specify storage type: UFS or EMMC")
52+
parser.add_argument("-T", "--target", required=True, help="Specify target platform (e.g., QCS6490)")
4853

4954
args = parser.parse_args()
5055
main(args)

0 commit comments

Comments
 (0)