programmer-core 1.0.29__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- programmer_core/__init__.py +22 -0
- programmer_core/activate_device.py +246 -0
- programmer_core/adapters.py +23 -0
- programmer_core/constants.py +50 -0
- programmer_core/debug_device.py +278 -0
- programmer_core/detect_flash_type.py +316 -0
- programmer_core/enter_rma.py +264 -0
- programmer_core/factory_program_device.py +667 -0
- programmer_core/full_flash_erase.py +268 -0
- programmer_core/get_fuse_data.py +277 -0
- programmer_core/memory_validation_test.py +357 -0
- programmer_core/partial_fuse_programming.py +342 -0
- programmer_core/program_device.py +775 -0
- programmer_core/query_device.py +274 -0
- programmer_core/report.py +921 -0
- programmer_core/utility.py +1822 -0
- programmer_core-1.0.29.dist-info/METADATA +146 -0
- programmer_core-1.0.29.dist-info/RECORD +20 -0
- programmer_core-1.0.29.dist-info/WHEEL +5 -0
- programmer_core-1.0.29.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1822 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from os.path import (
|
|
3
|
+
isabs,
|
|
4
|
+
abspath,
|
|
5
|
+
join,
|
|
6
|
+
dirname,
|
|
7
|
+
basename,
|
|
8
|
+
dirname,
|
|
9
|
+
join,
|
|
10
|
+
isdir,
|
|
11
|
+
isfile,
|
|
12
|
+
normcase,
|
|
13
|
+
normpath,
|
|
14
|
+
exists,
|
|
15
|
+
)
|
|
16
|
+
from os import getcwd, makedirs, remove
|
|
17
|
+
import re
|
|
18
|
+
from sys import executable
|
|
19
|
+
from construct import Int16ul, Int32ul, Int8ul, Padding, Struct, GreedyBytes
|
|
20
|
+
import sys
|
|
21
|
+
import logging
|
|
22
|
+
import pathlib
|
|
23
|
+
import json
|
|
24
|
+
from tabulate import tabulate
|
|
25
|
+
import shutil
|
|
26
|
+
|
|
27
|
+
from communicator_core import (
|
|
28
|
+
CC35XXE,
|
|
29
|
+
CC37XXR,
|
|
30
|
+
CC33XX,
|
|
31
|
+
CC35XX,
|
|
32
|
+
CC36XXR,
|
|
33
|
+
LifeCycleStateReg,
|
|
34
|
+
CC35xxELifeCycleStateReg,
|
|
35
|
+
CC3xxxRLifeCycleStateReg,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
from flash_images_builder_core import (
|
|
39
|
+
ProgInstImgBuilder,
|
|
40
|
+
GPEBuilder,
|
|
41
|
+
BootSectorFromImgBuilder,
|
|
42
|
+
VendorImageBuilder,
|
|
43
|
+
ActionReqBuilder,
|
|
44
|
+
TBLImageBuilder,
|
|
45
|
+
BL3ImageBuilder,
|
|
46
|
+
CleanImageBuilder,
|
|
47
|
+
WSOCImageBuilder,
|
|
48
|
+
SUPPORTED_FLASH_TYPES,
|
|
49
|
+
PsramOption,
|
|
50
|
+
FIBUtility,
|
|
51
|
+
KEY_INFO_MAGIC_DEFAULT,
|
|
52
|
+
KEY_INFO_VERSION_DEFAULT,
|
|
53
|
+
KEY_INFO_SVN_DEFAULT,
|
|
54
|
+
KEY_INFO_SVN_POLICY_DEFAULT,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
CC33XX = "CC33XX"
|
|
58
|
+
CC35X1 = "CC35X1"
|
|
59
|
+
CC35X1E = "CC35X1E"
|
|
60
|
+
CC360XR = "CC360XR"
|
|
61
|
+
CC370XR = "CC370XR"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
PROGRAMMER_DIR = "programmer"
|
|
65
|
+
PROGRAMMER_TBL_DIR = "bl2"
|
|
66
|
+
PROGRAMMER_WSOC_DIR = "ti_wireless_firmware"
|
|
67
|
+
VENDOR_APP_FOLDER = "pre_complied_application"
|
|
68
|
+
PROGRAMMING_FILES_FOLDER_NAME = "programming"
|
|
69
|
+
ACTIVATION_FILES_FOLDER_NAME = "activation_n_initial_programming"
|
|
70
|
+
SDK_EXAMPLE_KEY_FILES_FOLDER_NAME = "sdk_example_key"
|
|
71
|
+
AUTHENTICATION_BYPASS_FILES_FOLDER_NAME = "authentication_bypass"
|
|
72
|
+
VENDOR_KEY_FILES_FOLDER_NAME = "vendor_key"
|
|
73
|
+
CONFIG_FOLDER_NAME = "configuration_files"
|
|
74
|
+
TOOL_SETTINGS_TEMPLATE = "tool_settings.template.json"
|
|
75
|
+
TOOL_SETTINGS = "tool_settings.json"
|
|
76
|
+
CONNECTION_INFO_FILE_NAME = "connection_info.json"
|
|
77
|
+
ACTION_PARAMS_TEMPLATE = "action_params.template.json"
|
|
78
|
+
ACTION_PARAMS = "action_params.json"
|
|
79
|
+
BINARIES_FOLDER_NAME = "binaries"
|
|
80
|
+
|
|
81
|
+
# files that are inside jsons folder
|
|
82
|
+
EXTMEM_JSON_NAME = "flash_disc_param_ext_mem.json"
|
|
83
|
+
OTFDE_JSON_NAME = "flash_disc_param_otfde.json"
|
|
84
|
+
XSPI_JSON_NAME = "flash_disc_param_xspi.json"
|
|
85
|
+
|
|
86
|
+
# files that are inside jsons folder
|
|
87
|
+
EXTMEM_JSON_MOD_NAME = "flash_disc_param_ext_mem.mod.json"
|
|
88
|
+
OTFDE_JSON_MOD_NAME = "flash_disc_param_otfde.mod.json"
|
|
89
|
+
XSPI_JSON_MOD_NAME = "flash_disc_param_xspi.mod.json"
|
|
90
|
+
|
|
91
|
+
# files that are inside images folder
|
|
92
|
+
VENDOR_OUT_NAME_CC35XX = "vendor_app.CC35XX.out"
|
|
93
|
+
VENDOR_OUT_NAME_CC35XXE = "vendor_app.CC35XXE.out"
|
|
94
|
+
VENDOR_OUT_NAME_CC37XXR = "vendor_app.CC37XXR.out"
|
|
95
|
+
|
|
96
|
+
# files that are inside ti_wireless_firmware folder
|
|
97
|
+
WSOC_NAME_CC35XX = "ti_wireless_firmware.CC35XX.bin"
|
|
98
|
+
WSOC_NAME_CC35XXE = "ti_wireless_firmware.CC35XXE.bin"
|
|
99
|
+
WSOC_NAME_CC37XXR = "ti_wireless_firmware.CC37XXR.bin"
|
|
100
|
+
CONF_BIN_NAME_CC35XXE = "ti_wireless_firmware.CC35XXE.conf.bin"
|
|
101
|
+
CONF_BIN_NAME_CC37XXR = "ti_wireless_firmware.CC37XXR.conf.bin"
|
|
102
|
+
|
|
103
|
+
# files that in fuze folder
|
|
104
|
+
FUSE_PROG_NAME_CC35XX = "fuse_prog_inst_param.CC35XX.json"
|
|
105
|
+
FUSE_PROG_NAME_CC35XXE = "fuse_prog_inst_param.CC35XXE.json"
|
|
106
|
+
FUSE_PROG_NAME_CC37XXR = "fuse_prog_inst_param.CC37XXR.json"
|
|
107
|
+
FUSE_PROG_NAME_CC35XXE_TEMPLATE = "fuse_prog_inst_param.template.json"
|
|
108
|
+
|
|
109
|
+
# CC35XX BL2
|
|
110
|
+
TI_BOOTLOADER_CC35XX = "ti_bootloader.CC35XX.sign.bin"
|
|
111
|
+
TI_PROGRAMMER_CC35XX = "ti_programmer.CC35XX.sign.bin"
|
|
112
|
+
TI_DEBUG_CC35XX = "ti_programmer.CC35XX.sign.bin"
|
|
113
|
+
TI_RMA_CC35XX = "ti_programmer.CC35XX.sign.bin"
|
|
114
|
+
TI_ACTIVATION_CC35XX = "ti_programmer.CC35XX.sign.bin"
|
|
115
|
+
|
|
116
|
+
# CC35XXE BL2
|
|
117
|
+
TI_BOOTLOADER_CC35XXE = "ti_bootloader.CC35XXE.sign.bin"
|
|
118
|
+
TI_PROGRAMMER_CC35XXE = "ti_programmer.CC35XXE.sign.bin"
|
|
119
|
+
TI_DEBUG_CC35XXE = "ti_debug.CC35XXE.sign.bin"
|
|
120
|
+
TI_RMA_CC35XXE = "ti_rma.CC35XXE.sign.bin"
|
|
121
|
+
TI_ACTIVATION_CC35XXE = "ti_activation.CC35XXE.sign.bin"
|
|
122
|
+
|
|
123
|
+
# CC37XXR BL2
|
|
124
|
+
TI_BOOTLOADER_CC37XXR = "ti_bootloader.CC37XXR.sign.bin"
|
|
125
|
+
TI_PROGRAMMER_CC37XXR = "ti_programmer.CC37XXR.sign.bin"
|
|
126
|
+
TI_ACTIVATION_CC37XXR = "ti_activation.CC37XXR.sign.bin"
|
|
127
|
+
TI_DEBUG_CC37XXR = "ti_debug.CC37XXR.sign.bin"
|
|
128
|
+
|
|
129
|
+
# Short names
|
|
130
|
+
# TBL = TI BootLoader
|
|
131
|
+
# TPR = TI PRogrammer
|
|
132
|
+
# TAC = TI ACtivation
|
|
133
|
+
# TDB = TI DeBug
|
|
134
|
+
# TRM = TI RMA
|
|
135
|
+
|
|
136
|
+
# Supported country codes (ISO 3166-1 alpha-2)
|
|
137
|
+
VALID_COUNTRY_CODES = {
|
|
138
|
+
"WW",
|
|
139
|
+
"US",
|
|
140
|
+
"JP",
|
|
141
|
+
"AF",
|
|
142
|
+
"AL",
|
|
143
|
+
"DZ",
|
|
144
|
+
"AS",
|
|
145
|
+
"AD",
|
|
146
|
+
"AI",
|
|
147
|
+
"AR",
|
|
148
|
+
"AM",
|
|
149
|
+
"AW",
|
|
150
|
+
"AU",
|
|
151
|
+
"AT",
|
|
152
|
+
"AZ",
|
|
153
|
+
"BS",
|
|
154
|
+
"BH",
|
|
155
|
+
"BD",
|
|
156
|
+
"BB",
|
|
157
|
+
"BY",
|
|
158
|
+
"BE",
|
|
159
|
+
"BZ",
|
|
160
|
+
"BM",
|
|
161
|
+
"BT",
|
|
162
|
+
"BO",
|
|
163
|
+
"BA",
|
|
164
|
+
"BR",
|
|
165
|
+
"BN",
|
|
166
|
+
"BG",
|
|
167
|
+
"BF",
|
|
168
|
+
"KH",
|
|
169
|
+
"CA",
|
|
170
|
+
"KY",
|
|
171
|
+
"CF",
|
|
172
|
+
"TD",
|
|
173
|
+
"CL",
|
|
174
|
+
"CN",
|
|
175
|
+
"CX",
|
|
176
|
+
"CO",
|
|
177
|
+
"CR",
|
|
178
|
+
"CI",
|
|
179
|
+
"HR",
|
|
180
|
+
"CU",
|
|
181
|
+
"CY",
|
|
182
|
+
"CZ",
|
|
183
|
+
"DK",
|
|
184
|
+
"DM",
|
|
185
|
+
"DO",
|
|
186
|
+
"EC",
|
|
187
|
+
"EG",
|
|
188
|
+
"SV",
|
|
189
|
+
"EE",
|
|
190
|
+
"ET",
|
|
191
|
+
"FI",
|
|
192
|
+
"FR",
|
|
193
|
+
"GF",
|
|
194
|
+
"PF",
|
|
195
|
+
"GE",
|
|
196
|
+
"DE",
|
|
197
|
+
"GH",
|
|
198
|
+
"GR",
|
|
199
|
+
"GL",
|
|
200
|
+
"GD",
|
|
201
|
+
"GP",
|
|
202
|
+
"GU",
|
|
203
|
+
"GT",
|
|
204
|
+
"GY",
|
|
205
|
+
"HT",
|
|
206
|
+
"HN",
|
|
207
|
+
"HK",
|
|
208
|
+
"HU",
|
|
209
|
+
"IS",
|
|
210
|
+
"IN",
|
|
211
|
+
"ID",
|
|
212
|
+
"IR",
|
|
213
|
+
"IE",
|
|
214
|
+
"IL",
|
|
215
|
+
"IT",
|
|
216
|
+
"JM",
|
|
217
|
+
"JO",
|
|
218
|
+
"KZ",
|
|
219
|
+
"KE",
|
|
220
|
+
"KP",
|
|
221
|
+
"KR",
|
|
222
|
+
"KW",
|
|
223
|
+
"LV",
|
|
224
|
+
"LB",
|
|
225
|
+
"LS",
|
|
226
|
+
"LI",
|
|
227
|
+
"LT",
|
|
228
|
+
"LU",
|
|
229
|
+
"MO",
|
|
230
|
+
"MK",
|
|
231
|
+
"MG",
|
|
232
|
+
"MW",
|
|
233
|
+
"MY",
|
|
234
|
+
"MV",
|
|
235
|
+
"MT",
|
|
236
|
+
"MH",
|
|
237
|
+
"MQ",
|
|
238
|
+
"MR",
|
|
239
|
+
"MU",
|
|
240
|
+
"YT",
|
|
241
|
+
"MX",
|
|
242
|
+
"FM",
|
|
243
|
+
"MD",
|
|
244
|
+
"MC",
|
|
245
|
+
"MN",
|
|
246
|
+
"ME",
|
|
247
|
+
"MA",
|
|
248
|
+
"NP",
|
|
249
|
+
"NL",
|
|
250
|
+
"AN",
|
|
251
|
+
"NZ",
|
|
252
|
+
"NI",
|
|
253
|
+
"NG",
|
|
254
|
+
"MP",
|
|
255
|
+
"NO",
|
|
256
|
+
"OM",
|
|
257
|
+
"PK",
|
|
258
|
+
"PW",
|
|
259
|
+
"PA",
|
|
260
|
+
"PG",
|
|
261
|
+
"PY",
|
|
262
|
+
"PE",
|
|
263
|
+
"PH",
|
|
264
|
+
"PL",
|
|
265
|
+
"PT",
|
|
266
|
+
"PR",
|
|
267
|
+
"QA",
|
|
268
|
+
"RE",
|
|
269
|
+
"RO",
|
|
270
|
+
"RU",
|
|
271
|
+
"RW",
|
|
272
|
+
"BL",
|
|
273
|
+
"KN",
|
|
274
|
+
"LC",
|
|
275
|
+
"PM",
|
|
276
|
+
"VC",
|
|
277
|
+
"WS",
|
|
278
|
+
"SA",
|
|
279
|
+
"SN",
|
|
280
|
+
"RS",
|
|
281
|
+
"SG",
|
|
282
|
+
"SK",
|
|
283
|
+
"SI",
|
|
284
|
+
"ZA",
|
|
285
|
+
"ES",
|
|
286
|
+
"LK",
|
|
287
|
+
"SR",
|
|
288
|
+
"SE",
|
|
289
|
+
"CH",
|
|
290
|
+
"SY",
|
|
291
|
+
"TW",
|
|
292
|
+
"TZ",
|
|
293
|
+
"TH",
|
|
294
|
+
"TG",
|
|
295
|
+
"TT",
|
|
296
|
+
"TN",
|
|
297
|
+
"TR",
|
|
298
|
+
"TC",
|
|
299
|
+
"UG",
|
|
300
|
+
"UA",
|
|
301
|
+
"AE",
|
|
302
|
+
"GB",
|
|
303
|
+
"UY",
|
|
304
|
+
"UZ",
|
|
305
|
+
"VU",
|
|
306
|
+
"VE",
|
|
307
|
+
"VN",
|
|
308
|
+
"VI",
|
|
309
|
+
"WF",
|
|
310
|
+
"YE",
|
|
311
|
+
"ZW",
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
# For display purposes (sorted list)
|
|
315
|
+
COUNTRY_CODES_LIST = sorted(VALID_COUNTRY_CODES)
|
|
316
|
+
|
|
317
|
+
PROGRAMMER_UTILITY_LOGGER = logging.getLogger("programmer-utility")
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def get_lifecycle_state_reg_class(device_type):
|
|
321
|
+
"""
|
|
322
|
+
Get the appropriate LifeCycleStateReg class based on device type.
|
|
323
|
+
|
|
324
|
+
This function selects the correct CFGAP register implementation for each
|
|
325
|
+
device variant (CC35XX, CC35XXE, CC37XXR, etc.), ensuring correct field
|
|
326
|
+
parsing for device-specific register layouts.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
device_type (str): Device type constant (CC35XX, CC35XXE, CC37XXR, CC36XXR)
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
class: Device-appropriate LifeCycleStateReg class
|
|
333
|
+
"""
|
|
334
|
+
if device_type == CC35XXE:
|
|
335
|
+
return CC35xxELifeCycleStateReg
|
|
336
|
+
elif device_type in [CC37XXR, CC36XXR]:
|
|
337
|
+
return CC3xxxRLifeCycleStateReg
|
|
338
|
+
else:
|
|
339
|
+
# Default to CC35XX for CC35XX and any other devices
|
|
340
|
+
return LifeCycleStateReg
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
class ProgrammerUtility:
|
|
344
|
+
def __init__(
|
|
345
|
+
self, programmer_artifacts_path, fib_artifacts_path, is_internal_release=True
|
|
346
|
+
):
|
|
347
|
+
# Convert to Path object if it's not already
|
|
348
|
+
self.is_internal_release = is_internal_release
|
|
349
|
+
|
|
350
|
+
self.programmer_path = pathlib.Path(programmer_artifacts_path)
|
|
351
|
+
self.fib_utility = FIBUtility(
|
|
352
|
+
fib_artifacts_path, is_internal_release=self.is_internal_release
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
def get_device_type_from_family(self, device_family):
|
|
356
|
+
"""Map device family string to device type constant.
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
device_family: Device family string (e.g., "CC35X1E", "CC35X1", "CC370XR", "CC360XR", "CC33XX")
|
|
360
|
+
|
|
361
|
+
Returns:
|
|
362
|
+
Device type constant (CC35XXE, CC35XX, CC37XXR, CC36XXR or CC33XX)
|
|
363
|
+
"""
|
|
364
|
+
device_family_upper = device_family.upper()
|
|
365
|
+
if device_family_upper in [CC35X1E, CC35XXE]:
|
|
366
|
+
return CC35XXE
|
|
367
|
+
elif device_family_upper in [CC370XR, CC37XXR]:
|
|
368
|
+
return CC37XXR
|
|
369
|
+
elif device_family_upper in [CC360XR, CC36XXR]:
|
|
370
|
+
# TODO: temporary - CC360XR currently not supported
|
|
371
|
+
return CC36XXR
|
|
372
|
+
elif device_family_upper in [CC33XX]:
|
|
373
|
+
return CC33XX
|
|
374
|
+
elif device_family_upper in [CC35X1, CC35XX]:
|
|
375
|
+
return CC35XX
|
|
376
|
+
else:
|
|
377
|
+
return None
|
|
378
|
+
|
|
379
|
+
def get_programmer_dir_path(self):
|
|
380
|
+
return str(self.programmer_path)
|
|
381
|
+
|
|
382
|
+
def get_tbl_dir_path(self):
|
|
383
|
+
binaries_path = self.get_binaries_folder()
|
|
384
|
+
return join(binaries_path, PROGRAMMER_TBL_DIR)
|
|
385
|
+
|
|
386
|
+
def get_wsoc_dir_path(self):
|
|
387
|
+
binaries_path = self.get_binaries_folder()
|
|
388
|
+
return join(binaries_path, PROGRAMMER_WSOC_DIR)
|
|
389
|
+
|
|
390
|
+
def get_programming_tmp_folder(self):
|
|
391
|
+
tmp_dir = self.get_tmp_folder()
|
|
392
|
+
prog_tmp_folder = join(tmp_dir, PROGRAMMING_FILES_FOLDER_NAME)
|
|
393
|
+
makedirs(prog_tmp_folder, exist_ok=True)
|
|
394
|
+
|
|
395
|
+
return prog_tmp_folder
|
|
396
|
+
|
|
397
|
+
def get_tmp_folder(self):
|
|
398
|
+
programmer_dir = self.get_programmer_dir_path()
|
|
399
|
+
tmp_folder = join(programmer_dir, "tmp")
|
|
400
|
+
makedirs(tmp_folder, exist_ok=True)
|
|
401
|
+
|
|
402
|
+
return tmp_folder
|
|
403
|
+
|
|
404
|
+
def get_prog_tools_settings(self):
|
|
405
|
+
prog_tmp_folder = self.get_programming_tmp_folder()
|
|
406
|
+
return join(prog_tmp_folder, TOOL_SETTINGS)
|
|
407
|
+
|
|
408
|
+
def get_tool_settings_template(self):
|
|
409
|
+
return join(self.get_config_folder(), TOOL_SETTINGS_TEMPLATE)
|
|
410
|
+
|
|
411
|
+
def move_flash_json_files(
|
|
412
|
+
self,
|
|
413
|
+
flash_type_files_folder,
|
|
414
|
+
flash_device,
|
|
415
|
+
enable_ota=False,
|
|
416
|
+
):
|
|
417
|
+
tmp_dir = self.get_programming_tmp_folder()
|
|
418
|
+
|
|
419
|
+
if flash_device in SUPPORTED_FLASH_TYPES:
|
|
420
|
+
jsons_flash_type_files_folder = join(flash_type_files_folder, flash_device)
|
|
421
|
+
|
|
422
|
+
xspi_json = self.copy_file(
|
|
423
|
+
jsons_flash_type_files_folder, tmp_dir, XSPI_JSON_NAME
|
|
424
|
+
)
|
|
425
|
+
otfde_json = self.copy_file(
|
|
426
|
+
jsons_flash_type_files_folder, tmp_dir, OTFDE_JSON_NAME
|
|
427
|
+
)
|
|
428
|
+
ext_mem_json = self.copy_file(
|
|
429
|
+
jsons_flash_type_files_folder, tmp_dir, EXTMEM_JSON_NAME
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
return xspi_json, otfde_json, ext_mem_json
|
|
433
|
+
|
|
434
|
+
def get_mod_flash_jsons(self):
|
|
435
|
+
tmp_dir = self.get_programming_tmp_folder()
|
|
436
|
+
|
|
437
|
+
xspi_json = join(tmp_dir, XSPI_JSON_MOD_NAME)
|
|
438
|
+
otfde_json = join(tmp_dir, OTFDE_JSON_MOD_NAME)
|
|
439
|
+
ext_mem_json = join(tmp_dir, EXTMEM_JSON_MOD_NAME)
|
|
440
|
+
|
|
441
|
+
return xspi_json, otfde_json, ext_mem_json
|
|
442
|
+
|
|
443
|
+
def get_act_n_init_tmp_folder(self):
|
|
444
|
+
tmp_folder = self.get_tmp_folder()
|
|
445
|
+
act_tmp_folder = join(tmp_folder, ACTIVATION_FILES_FOLDER_NAME)
|
|
446
|
+
makedirs(act_tmp_folder, exist_ok=True)
|
|
447
|
+
|
|
448
|
+
return act_tmp_folder
|
|
449
|
+
|
|
450
|
+
def get_flash_jsons(
|
|
451
|
+
self,
|
|
452
|
+
flash_type_files_folder,
|
|
453
|
+
flash_device,
|
|
454
|
+
tmp_dir_path,
|
|
455
|
+
psram_type=PsramOption.NO_PSRAM.name,
|
|
456
|
+
enable_ota=False,
|
|
457
|
+
):
|
|
458
|
+
if flash_device in SUPPORTED_FLASH_TYPES:
|
|
459
|
+
jsons_flash_type_files_folder = join(flash_type_files_folder, flash_device)
|
|
460
|
+
|
|
461
|
+
xspi_json = join(jsons_flash_type_files_folder, XSPI_JSON_NAME)
|
|
462
|
+
otfde_json = join(jsons_flash_type_files_folder, OTFDE_JSON_NAME)
|
|
463
|
+
ext_mem_json = join(jsons_flash_type_files_folder, EXTMEM_JSON_NAME)
|
|
464
|
+
|
|
465
|
+
updated_xspi = self.update_xspi_to_psram_type(
|
|
466
|
+
xspi_json_path=xspi_json,
|
|
467
|
+
psram_type=psram_type,
|
|
468
|
+
output_dir_path=tmp_dir_path,
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
return updated_xspi, otfde_json, ext_mem_json
|
|
472
|
+
|
|
473
|
+
def get_default_tbl_img(self, device_type):
|
|
474
|
+
tbl_folder_name = self.get_tbl_dir_path()
|
|
475
|
+
if device_type == CC35XXE:
|
|
476
|
+
path_to_tbl = join(tbl_folder_name, TI_BOOTLOADER_CC35XXE)
|
|
477
|
+
elif device_type == CC37XXR:
|
|
478
|
+
path_to_tbl = join(tbl_folder_name, TI_BOOTLOADER_CC37XXR)
|
|
479
|
+
else:
|
|
480
|
+
path_to_tbl = join(tbl_folder_name, TI_BOOTLOADER_CC35XX)
|
|
481
|
+
return path_to_tbl
|
|
482
|
+
|
|
483
|
+
def get_default_vendor_app(self, device_type, enable_ota=False):
|
|
484
|
+
vendor_folder = join(self.get_binaries_folder(), VENDOR_APP_FOLDER)
|
|
485
|
+
|
|
486
|
+
# Map device type to vendor app filename
|
|
487
|
+
vendor_app_map = {
|
|
488
|
+
CC35XX: VENDOR_OUT_NAME_CC35XX,
|
|
489
|
+
CC35XXE: VENDOR_OUT_NAME_CC35XXE,
|
|
490
|
+
CC37XXR: VENDOR_OUT_NAME_CC37XXR,
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
vendor_app_name = vendor_app_map.get(device_type)
|
|
494
|
+
|
|
495
|
+
# For advanced devices (CC35XXE and CC37XXR), use OTA-specific folders
|
|
496
|
+
if device_type in (CC35XXE, CC37XXR):
|
|
497
|
+
source_dir = "vendor_app_ota" if enable_ota else "vendor_app_no_ota"
|
|
498
|
+
return join(vendor_folder, source_dir, vendor_app_name)
|
|
499
|
+
|
|
500
|
+
# For CC35XX, use flat structure
|
|
501
|
+
return join(vendor_folder, vendor_app_name)
|
|
502
|
+
|
|
503
|
+
def get_default_conf_bin(self, device_type):
|
|
504
|
+
wsoc_folder = self.get_wsoc_dir_path()
|
|
505
|
+
|
|
506
|
+
conf_map = {
|
|
507
|
+
CC35XXE: CONF_BIN_NAME_CC35XXE,
|
|
508
|
+
CC37XXR: CONF_BIN_NAME_CC37XXR,
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
conf_name = conf_map.get(device_type, None)
|
|
512
|
+
if conf_name is None:
|
|
513
|
+
return None
|
|
514
|
+
return join(wsoc_folder, conf_name)
|
|
515
|
+
|
|
516
|
+
return path_to_conf_bin
|
|
517
|
+
|
|
518
|
+
def get_default_prog(self, device_type):
|
|
519
|
+
tbl_folder_name = self.get_tbl_dir_path()
|
|
520
|
+
if device_type == CC35XXE:
|
|
521
|
+
path_to_ti_prog = join(tbl_folder_name, TI_PROGRAMMER_CC35XXE)
|
|
522
|
+
elif device_type == CC37XXR:
|
|
523
|
+
path_to_ti_prog = join(tbl_folder_name, TI_PROGRAMMER_CC37XXR)
|
|
524
|
+
else:
|
|
525
|
+
path_to_ti_prog = join(tbl_folder_name, TI_PROGRAMMER_CC35XX)
|
|
526
|
+
return path_to_ti_prog
|
|
527
|
+
|
|
528
|
+
def get_default_activator(self, device_type):
|
|
529
|
+
tbl_folder_name = self.get_tbl_dir_path()
|
|
530
|
+
if device_type == CC35XXE:
|
|
531
|
+
path_to_tac = join(tbl_folder_name, TI_ACTIVATION_CC35XXE)
|
|
532
|
+
elif device_type == CC37XXR:
|
|
533
|
+
path_to_tac = join(tbl_folder_name, TI_ACTIVATION_CC37XXR)
|
|
534
|
+
else:
|
|
535
|
+
path_to_tac = join(tbl_folder_name, TI_ACTIVATION_CC35XX)
|
|
536
|
+
return path_to_tac
|
|
537
|
+
|
|
538
|
+
def get_default_wsoc(self, device_type):
|
|
539
|
+
wsoc_folder = self.get_wsoc_dir_path()
|
|
540
|
+
|
|
541
|
+
# Map device type to WSOC firmware filename
|
|
542
|
+
wsoc_map = {
|
|
543
|
+
CC35XX: WSOC_NAME_CC35XX,
|
|
544
|
+
CC35XXE: WSOC_NAME_CC35XXE,
|
|
545
|
+
CC37XXR: WSOC_NAME_CC37XXR,
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
wsoc_name = wsoc_map.get(device_type)
|
|
549
|
+
if wsoc_name is None:
|
|
550
|
+
return None
|
|
551
|
+
return join(wsoc_folder, wsoc_name)
|
|
552
|
+
|
|
553
|
+
def get_action_params_template(self):
|
|
554
|
+
jsons_folder = self.get_config_folder()
|
|
555
|
+
action_params_template = join(jsons_folder, ACTION_PARAMS_TEMPLATE)
|
|
556
|
+
|
|
557
|
+
return action_params_template
|
|
558
|
+
|
|
559
|
+
def get_binaries_folder(self):
|
|
560
|
+
programmer_dir = self.get_programmer_dir_path()
|
|
561
|
+
binaries_folder = join(programmer_dir, BINARIES_FOLDER_NAME)
|
|
562
|
+
|
|
563
|
+
return binaries_folder
|
|
564
|
+
|
|
565
|
+
def get_config_folder(self):
|
|
566
|
+
programmer_dir = self.get_programmer_dir_path()
|
|
567
|
+
jsons_folder = join(programmer_dir, CONFIG_FOLDER_NAME)
|
|
568
|
+
|
|
569
|
+
return jsons_folder
|
|
570
|
+
|
|
571
|
+
def get_fuses_params_template(self):
|
|
572
|
+
fuses_param_json_template_path = (
|
|
573
|
+
pathlib.Path(self.get_config_folder()) / FUSE_PROG_NAME_CC35XXE_TEMPLATE
|
|
574
|
+
)
|
|
575
|
+
with open(fuses_param_json_template_path, "r") as json_file:
|
|
576
|
+
fuses_dict = json.loads(json_file.read())
|
|
577
|
+
|
|
578
|
+
return fuses_dict
|
|
579
|
+
|
|
580
|
+
def get_fuse_json_name(self, device_type):
|
|
581
|
+
|
|
582
|
+
fuses_param_map = {
|
|
583
|
+
CC35XX: FUSE_PROG_NAME_CC35XX,
|
|
584
|
+
CC35XXE: FUSE_PROG_NAME_CC35XXE,
|
|
585
|
+
CC37XXR: FUSE_PROG_NAME_CC37XXR,
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
fuses_param_name = fuses_param_map.get(device_type, None)
|
|
589
|
+
if fuses_param_name is None:
|
|
590
|
+
return None
|
|
591
|
+
|
|
592
|
+
return fuses_param_name
|
|
593
|
+
|
|
594
|
+
def create_new_fuses_json_file(self, dict_updated_data, full_path_to_save):
|
|
595
|
+
"""
|
|
596
|
+
Creates a JSON file from the provided dictionary data.
|
|
597
|
+
|
|
598
|
+
Args:
|
|
599
|
+
dict_updated_data (dict): The dictionary containing the updated fuse parameters.
|
|
600
|
+
full_path_to_save (str): The full path to save the JSON file.
|
|
601
|
+
"""
|
|
602
|
+
fuses_dict = self.get_fuses_params_template()
|
|
603
|
+
|
|
604
|
+
# Iterate through the updated dictionary and update existing keys in the template
|
|
605
|
+
for key, value in dict_updated_data.items():
|
|
606
|
+
if key in fuses_dict:
|
|
607
|
+
fuses_dict[key] = value
|
|
608
|
+
|
|
609
|
+
self.dict_to_json(fuses_dict, full_path_to_save)
|
|
610
|
+
|
|
611
|
+
def copy_file(self, source_path, destination_folder, filename):
|
|
612
|
+
"""Copy PS image to destination folder"""
|
|
613
|
+
try:
|
|
614
|
+
# Ensure destination folder exists
|
|
615
|
+
makedirs(destination_folder, exist_ok=True)
|
|
616
|
+
|
|
617
|
+
# Build full destination path
|
|
618
|
+
destination_path = join(destination_folder, filename)
|
|
619
|
+
source_path = join(source_path, filename)
|
|
620
|
+
|
|
621
|
+
# Copy file
|
|
622
|
+
shutil.copy(source_path, destination_path)
|
|
623
|
+
|
|
624
|
+
return destination_path
|
|
625
|
+
except Exception as e:
|
|
626
|
+
print(f"Error copying file: {e}")
|
|
627
|
+
return None
|
|
628
|
+
|
|
629
|
+
def get_container_version(self, path_to_container):
|
|
630
|
+
container_struct = Struct(
|
|
631
|
+
# header
|
|
632
|
+
"magic_number" / Int32ul,
|
|
633
|
+
"record_type" / Int16ul,
|
|
634
|
+
"reserved" / Padding(2, b"\xff"),
|
|
635
|
+
"h_len" / Int32ul,
|
|
636
|
+
# payload
|
|
637
|
+
"container_format_version" / Int8ul,
|
|
638
|
+
"container_type" / Int16ul,
|
|
639
|
+
"container_type_format" / Int8ul,
|
|
640
|
+
"major_version" / Int8ul,
|
|
641
|
+
"minor_version" / Int8ul,
|
|
642
|
+
"rest" / GreedyBytes,
|
|
643
|
+
)
|
|
644
|
+
|
|
645
|
+
with open(path_to_container, "rb") as f:
|
|
646
|
+
container_cont = f.read()
|
|
647
|
+
|
|
648
|
+
parsed_container = container_struct.parse(container_cont)
|
|
649
|
+
|
|
650
|
+
return (parsed_container.major_version, parsed_container.minor_version)
|
|
651
|
+
|
|
652
|
+
def file_path_exist(self, path, image_name):
|
|
653
|
+
if path is None or path == "":
|
|
654
|
+
error_message = f"Path to {image_name} not exist"
|
|
655
|
+
elif not isfile(path):
|
|
656
|
+
error_message = f"Path to {image_name} is invalid"
|
|
657
|
+
else:
|
|
658
|
+
error_message = None
|
|
659
|
+
|
|
660
|
+
return error_message
|
|
661
|
+
|
|
662
|
+
@classmethod
|
|
663
|
+
def fix_path(cls, path, alt_base_path=None):
|
|
664
|
+
fixed_path = path
|
|
665
|
+
|
|
666
|
+
if not isabs(path):
|
|
667
|
+
cwd = getcwd()
|
|
668
|
+
fixed_path = normpath(normcase(join(cwd, path)))
|
|
669
|
+
if exists(fixed_path):
|
|
670
|
+
return fixed_path
|
|
671
|
+
PROGRAMMER_UTILITY_LOGGER.debug(
|
|
672
|
+
f"Unable to locate '{path}' relative to CWD ({cwd}). Continuing search..."
|
|
673
|
+
)
|
|
674
|
+
|
|
675
|
+
if alt_base_path is not None:
|
|
676
|
+
fixed_path = normpath(normcase(join(alt_base_path, path)))
|
|
677
|
+
if exists(fixed_path):
|
|
678
|
+
return fixed_path
|
|
679
|
+
PROGRAMMER_UTILITY_LOGGER.debug(
|
|
680
|
+
f"Unable to locate '{path}' relative to tool_settings.json ({alt_base_path}). Continuing search..."
|
|
681
|
+
)
|
|
682
|
+
|
|
683
|
+
if getattr(sys, "frozen", False):
|
|
684
|
+
if isabs(executable):
|
|
685
|
+
fixed_path = join(dirname(executable), path)
|
|
686
|
+
else:
|
|
687
|
+
fixed_path = abspath(path)
|
|
688
|
+
|
|
689
|
+
return fixed_path
|
|
690
|
+
|
|
691
|
+
def build_programming_image(
|
|
692
|
+
self,
|
|
693
|
+
version,
|
|
694
|
+
pub_key_path,
|
|
695
|
+
activation_type,
|
|
696
|
+
dir_out_path,
|
|
697
|
+
fuses_programming_instructions,
|
|
698
|
+
flash_discovery_config_xspi,
|
|
699
|
+
flash_discovery_config_otfde,
|
|
700
|
+
flash_discovery_config_ext_mem,
|
|
701
|
+
device,
|
|
702
|
+
signing_module=None,
|
|
703
|
+
logging_level=logging.DEBUG,
|
|
704
|
+
signing_mode="classic_rot",
|
|
705
|
+
):
|
|
706
|
+
# combin xspi json and otfde json to one file
|
|
707
|
+
flash_discovery_config_path = None
|
|
708
|
+
dir_out_path = pathlib.Path(dir_out_path)
|
|
709
|
+
if flash_discovery_config_xspi:
|
|
710
|
+
flash_discovery_config_path = (
|
|
711
|
+
dir_out_path / "flash_discovery_configuration.json"
|
|
712
|
+
)
|
|
713
|
+
|
|
714
|
+
with open(flash_discovery_config_xspi, "r") as f:
|
|
715
|
+
xspi_data = json.load(f)
|
|
716
|
+
|
|
717
|
+
with open(flash_discovery_config_otfde, "r") as f:
|
|
718
|
+
otfde_data = json.load(f)
|
|
719
|
+
|
|
720
|
+
with open(flash_discovery_config_ext_mem, "r") as f:
|
|
721
|
+
ext_mem_data = json.load(f)
|
|
722
|
+
|
|
723
|
+
with open(flash_discovery_config_path, "w") as f:
|
|
724
|
+
xspi_data.update(otfde_data)
|
|
725
|
+
xspi_data.update({"otfde_external_memory_map": ext_mem_data})
|
|
726
|
+
f.write(json.dumps(xspi_data, indent=4))
|
|
727
|
+
|
|
728
|
+
tmp_fuses_programming_instructions = fuses_programming_instructions
|
|
729
|
+
if activation_type is not None:
|
|
730
|
+
tmp_fuses_programming_instructions = (
|
|
731
|
+
self.fib_utility.update_rot_in_fuses_for_programming_instructions(
|
|
732
|
+
fuses_programming_instructions,
|
|
733
|
+
dir_out_path,
|
|
734
|
+
activation_type,
|
|
735
|
+
signing_module,
|
|
736
|
+
signing_mode,
|
|
737
|
+
)
|
|
738
|
+
)
|
|
739
|
+
|
|
740
|
+
# build image
|
|
741
|
+
prog_inst_img_builder = ProgInstImgBuilder(
|
|
742
|
+
flash_fuse_programming_inst_json=tmp_fuses_programming_instructions,
|
|
743
|
+
flash_discovery_conf_file=flash_discovery_config_path,
|
|
744
|
+
version=version,
|
|
745
|
+
abs_dir_out_path=dir_out_path,
|
|
746
|
+
pub_key_path=pub_key_path,
|
|
747
|
+
device=device,
|
|
748
|
+
verbose=False,
|
|
749
|
+
signing_mode=signing_mode,
|
|
750
|
+
)
|
|
751
|
+
if not prog_inst_img_builder.build():
|
|
752
|
+
return None, None
|
|
753
|
+
|
|
754
|
+
# save image with dummy sign
|
|
755
|
+
programming_unsign_image = (
|
|
756
|
+
prog_inst_img_builder.abs_dir_out_path
|
|
757
|
+
/ "programming_instructions_image.unsign.bin"
|
|
758
|
+
)
|
|
759
|
+
with open(programming_unsign_image, "wb") as fb:
|
|
760
|
+
fb.write(prog_inst_img_builder.programming_image_gpe_content_raw)
|
|
761
|
+
PROGRAMMER_UTILITY_LOGGER.log(
|
|
762
|
+
logging_level,
|
|
763
|
+
f"Saved programming instructions image with dummy signature bin file at {programming_unsign_image}",
|
|
764
|
+
)
|
|
765
|
+
|
|
766
|
+
# save image to sign
|
|
767
|
+
programming_image_to_sign_out_path = (
|
|
768
|
+
prog_inst_img_builder.abs_dir_out_path
|
|
769
|
+
/ "programming_instructions_image.forsign.bin"
|
|
770
|
+
)
|
|
771
|
+
with open(programming_image_to_sign_out_path, "wb") as fb:
|
|
772
|
+
fb.write(prog_inst_img_builder.programming_image_to_sign_gpe_content_raw)
|
|
773
|
+
PROGRAMMER_UTILITY_LOGGER.log(
|
|
774
|
+
logging_level,
|
|
775
|
+
f"Saved part of programming instructions image bin file to sign at {programming_image_to_sign_out_path}",
|
|
776
|
+
)
|
|
777
|
+
|
|
778
|
+
if isfile(tmp_fuses_programming_instructions):
|
|
779
|
+
remove(tmp_fuses_programming_instructions)
|
|
780
|
+
|
|
781
|
+
return programming_unsign_image, programming_image_to_sign_out_path
|
|
782
|
+
|
|
783
|
+
# use always classic_rot when calling this function - multi key chaining not relevant to programming image
|
|
784
|
+
def sign_programming_image(
|
|
785
|
+
self,
|
|
786
|
+
programming_unsign_image,
|
|
787
|
+
programming_forsign_image,
|
|
788
|
+
dir_out_path,
|
|
789
|
+
private_key=None,
|
|
790
|
+
public_key=None,
|
|
791
|
+
activation_type=None,
|
|
792
|
+
signing_module=None,
|
|
793
|
+
signature_bin=None,
|
|
794
|
+
logging_level=logging.DEBUG,
|
|
795
|
+
signing_mode="classic_rot",
|
|
796
|
+
magic=KEY_INFO_MAGIC_DEFAULT,
|
|
797
|
+
key_info_version=KEY_INFO_VERSION_DEFAULT,
|
|
798
|
+
svn=KEY_INFO_SVN_DEFAULT,
|
|
799
|
+
svn_policy=KEY_INFO_SVN_POLICY_DEFAULT,
|
|
800
|
+
):
|
|
801
|
+
dir_out_path = pathlib.Path(dir_out_path).absolute()
|
|
802
|
+
basename_programming_unsign_image = str(
|
|
803
|
+
programming_unsign_image.stem.split(".")[0]
|
|
804
|
+
)
|
|
805
|
+
|
|
806
|
+
if activation_type is not None:
|
|
807
|
+
private_key = None
|
|
808
|
+
public_key, signature_bin = self.fib_utility.get_public_key_and_signature(
|
|
809
|
+
activation_type,
|
|
810
|
+
signing_module,
|
|
811
|
+
programming_forsign_image,
|
|
812
|
+
dir_out_path,
|
|
813
|
+
basename_programming_unsign_image,
|
|
814
|
+
signing_mode,
|
|
815
|
+
)
|
|
816
|
+
|
|
817
|
+
# Build KEY_INFO_TLV for multi-level mode (before calling sign_gpe)
|
|
818
|
+
key_info_tlv_bin = None
|
|
819
|
+
if signing_mode == "multi_level" and signing_module:
|
|
820
|
+
# Build KEY_INFO_TLV on-the-fly or get pre-built from signing module
|
|
821
|
+
key_info_tlv_bin = self.fib_utility.get_key_info_tlv(
|
|
822
|
+
signing_module_path=signing_module,
|
|
823
|
+
magic=magic,
|
|
824
|
+
key_info_version=key_info_version,
|
|
825
|
+
svn_policy_str=svn_policy,
|
|
826
|
+
svn=svn,
|
|
827
|
+
verbose=False,
|
|
828
|
+
)
|
|
829
|
+
if key_info_tlv_bin is None:
|
|
830
|
+
PROGRAMMER_UTILITY_LOGGER.log(
|
|
831
|
+
logging_level,
|
|
832
|
+
"Failed to build/get KEY_INFO_TLV for multi-level signing mode",
|
|
833
|
+
)
|
|
834
|
+
return None
|
|
835
|
+
|
|
836
|
+
# sign programming image
|
|
837
|
+
prog_image = GPEBuilder.sign_gpe(
|
|
838
|
+
unsign_image=programming_unsign_image,
|
|
839
|
+
private_key=private_key,
|
|
840
|
+
signature_bin=signature_bin,
|
|
841
|
+
public_key=public_key,
|
|
842
|
+
log_level=logging.DEBUG,
|
|
843
|
+
signing_mode=signing_mode,
|
|
844
|
+
key_info_tlv_bin=key_info_tlv_bin,
|
|
845
|
+
)
|
|
846
|
+
|
|
847
|
+
# save sign programming image file
|
|
848
|
+
programming_sign_image = dir_out_path / (
|
|
849
|
+
basename_programming_unsign_image + ".sign.bin"
|
|
850
|
+
)
|
|
851
|
+
with open(programming_sign_image, "wb") as f:
|
|
852
|
+
f.write(prog_image)
|
|
853
|
+
|
|
854
|
+
PROGRAMMER_UTILITY_LOGGER.log(
|
|
855
|
+
logging_level,
|
|
856
|
+
f"Saved sign programming image bin file at {programming_sign_image}",
|
|
857
|
+
)
|
|
858
|
+
|
|
859
|
+
return programming_sign_image
|
|
860
|
+
|
|
861
|
+
def build_vendor_image(
|
|
862
|
+
self,
|
|
863
|
+
vendor_out_file,
|
|
864
|
+
conf_bin_file,
|
|
865
|
+
dir_out_path,
|
|
866
|
+
device,
|
|
867
|
+
pub_key_path=None,
|
|
868
|
+
version="0.0.1.0",
|
|
869
|
+
logging_level=logging.DEBUG,
|
|
870
|
+
slot=None,
|
|
871
|
+
signing_mode="classic_rot",
|
|
872
|
+
):
|
|
873
|
+
dir_out_path = pathlib.Path(dir_out_path).absolute()
|
|
874
|
+
vendor_image_builder = VendorImageBuilder(
|
|
875
|
+
vendor_out_file_path=vendor_out_file,
|
|
876
|
+
conf_bin_file=conf_bin_file,
|
|
877
|
+
abs_dir_out_path=dir_out_path,
|
|
878
|
+
version=version,
|
|
879
|
+
pub_key_path=pub_key_path,
|
|
880
|
+
device=device,
|
|
881
|
+
verbose=False,
|
|
882
|
+
signing_mode=signing_mode,
|
|
883
|
+
)
|
|
884
|
+
|
|
885
|
+
if vendor_image_builder.build():
|
|
886
|
+
base_file_name = "vendor_image"
|
|
887
|
+
if slot is not None:
|
|
888
|
+
base_file_name += f"_slot{slot}"
|
|
889
|
+
|
|
890
|
+
# save image with dummy sign
|
|
891
|
+
vendor_image_out_path_unsign = (
|
|
892
|
+
vendor_image_builder.abs_dir_out_path / f"{base_file_name}.unsign.bin"
|
|
893
|
+
)
|
|
894
|
+
with open(vendor_image_out_path_unsign, "wb") as fb:
|
|
895
|
+
fb.write(vendor_image_builder.vendor_image_gpe_content_raw)
|
|
896
|
+
PROGRAMMER_UTILITY_LOGGER.log(
|
|
897
|
+
logging_level,
|
|
898
|
+
f"Saved vendor image with dummy signature bin file at {vendor_image_out_path_unsign}",
|
|
899
|
+
)
|
|
900
|
+
|
|
901
|
+
# save image to sign
|
|
902
|
+
vendor_image_out_path_for_sign = (
|
|
903
|
+
vendor_image_builder.abs_dir_out_path / f"{base_file_name}.forsign.bin"
|
|
904
|
+
)
|
|
905
|
+
with open(vendor_image_out_path_for_sign, "wb") as fb:
|
|
906
|
+
fb.write(vendor_image_builder.vendor_image_to_sign_gpe_content_raw)
|
|
907
|
+
PROGRAMMER_UTILITY_LOGGER.log(
|
|
908
|
+
logging_level,
|
|
909
|
+
f"Saved part of vendor image bin file to sign at {vendor_image_out_path_for_sign}",
|
|
910
|
+
)
|
|
911
|
+
|
|
912
|
+
return vendor_image_out_path_unsign, vendor_image_out_path_for_sign
|
|
913
|
+
|
|
914
|
+
def sign_vendor_image(
|
|
915
|
+
self,
|
|
916
|
+
vendor_unsign_image,
|
|
917
|
+
vendor_forsign_image,
|
|
918
|
+
dir_out_path,
|
|
919
|
+
device,
|
|
920
|
+
private_key=None,
|
|
921
|
+
public_key=None,
|
|
922
|
+
activation_type=None,
|
|
923
|
+
signing_module=None,
|
|
924
|
+
signature_bin=None,
|
|
925
|
+
logging_level=logging.DEBUG,
|
|
926
|
+
signing_mode="classic_rot",
|
|
927
|
+
magic=KEY_INFO_MAGIC_DEFAULT,
|
|
928
|
+
key_info_version=KEY_INFO_VERSION_DEFAULT,
|
|
929
|
+
svn=KEY_INFO_SVN_DEFAULT,
|
|
930
|
+
svn_policy=KEY_INFO_SVN_POLICY_DEFAULT,
|
|
931
|
+
):
|
|
932
|
+
dir_out_path = pathlib.Path(dir_out_path).absolute()
|
|
933
|
+
basename_vendor_unsign_image = str(
|
|
934
|
+
pathlib.Path(vendor_unsign_image).stem.split(".")[0]
|
|
935
|
+
)
|
|
936
|
+
|
|
937
|
+
if activation_type is not None:
|
|
938
|
+
private_key = None
|
|
939
|
+
public_key, signature_bin = self.fib_utility.get_public_key_and_signature(
|
|
940
|
+
activation_type,
|
|
941
|
+
signing_module,
|
|
942
|
+
vendor_forsign_image,
|
|
943
|
+
dir_out_path,
|
|
944
|
+
basename_vendor_unsign_image,
|
|
945
|
+
signing_mode,
|
|
946
|
+
)
|
|
947
|
+
|
|
948
|
+
# Build KEY_INFO_TLV for multi-level mode (before calling sign_gpe)
|
|
949
|
+
key_info_tlv_bin = None
|
|
950
|
+
if signing_mode == "multi_level" and signing_module:
|
|
951
|
+
# Build KEY_INFO_TLV on-the-fly or get pre-built from signing module
|
|
952
|
+
key_info_tlv_bin = self.fib_utility.get_key_info_tlv(
|
|
953
|
+
signing_module_path=signing_module,
|
|
954
|
+
magic=magic,
|
|
955
|
+
key_info_version=key_info_version,
|
|
956
|
+
svn_policy_str=svn_policy,
|
|
957
|
+
svn=svn,
|
|
958
|
+
verbose=False,
|
|
959
|
+
)
|
|
960
|
+
if key_info_tlv_bin is None:
|
|
961
|
+
PROGRAMMER_UTILITY_LOGGER.log(
|
|
962
|
+
logging_level,
|
|
963
|
+
"Failed to build/get KEY_INFO_TLV for multi-level signing mode",
|
|
964
|
+
)
|
|
965
|
+
return None
|
|
966
|
+
|
|
967
|
+
# sign vendor image
|
|
968
|
+
vendor_image = GPEBuilder.sign_gpe(
|
|
969
|
+
unsign_image=vendor_unsign_image,
|
|
970
|
+
private_key=private_key,
|
|
971
|
+
signature_bin=signature_bin,
|
|
972
|
+
public_key=public_key,
|
|
973
|
+
log_level=False,
|
|
974
|
+
device=device,
|
|
975
|
+
signing_mode=signing_mode,
|
|
976
|
+
key_info_tlv_bin=key_info_tlv_bin,
|
|
977
|
+
)
|
|
978
|
+
|
|
979
|
+
# save sign vendor image file
|
|
980
|
+
vendor_image_out_path = dir_out_path / (
|
|
981
|
+
basename_vendor_unsign_image + ".sign.bin"
|
|
982
|
+
)
|
|
983
|
+
with open(vendor_image_out_path, "wb") as f:
|
|
984
|
+
f.write(vendor_image)
|
|
985
|
+
PROGRAMMER_UTILITY_LOGGER.log(
|
|
986
|
+
logging_level,
|
|
987
|
+
f"Saved sign vendor image bin file at {vendor_image_out_path}",
|
|
988
|
+
)
|
|
989
|
+
|
|
990
|
+
return vendor_image_out_path
|
|
991
|
+
|
|
992
|
+
def build_action_request_image(
|
|
993
|
+
self,
|
|
994
|
+
params_json,
|
|
995
|
+
type,
|
|
996
|
+
dir_out_path,
|
|
997
|
+
device,
|
|
998
|
+
logging_level=logging.DEBUG,
|
|
999
|
+
):
|
|
1000
|
+
dir_out_path = pathlib.Path(dir_out_path).absolute()
|
|
1001
|
+
# extract the neccesery data by type
|
|
1002
|
+
with open(params_json, "r") as f:
|
|
1003
|
+
action_req_param = json.load(f)
|
|
1004
|
+
|
|
1005
|
+
action_req_param = action_req_param[type]
|
|
1006
|
+
# make sure public key in action params is ""
|
|
1007
|
+
action_req_param["pub_key"] = ""
|
|
1008
|
+
|
|
1009
|
+
# build action request
|
|
1010
|
+
action_req_builder = ActionReqBuilder.from_dict(
|
|
1011
|
+
action_req_dict=action_req_param,
|
|
1012
|
+
type=type,
|
|
1013
|
+
device_type=device,
|
|
1014
|
+
verbose=False,
|
|
1015
|
+
)
|
|
1016
|
+
action_req_builder.build()
|
|
1017
|
+
|
|
1018
|
+
# save action req file with dummy sig
|
|
1019
|
+
action_req_out_path = dir_out_path / f"{type}_action_request.unsign.bin"
|
|
1020
|
+
with open(action_req_out_path, "wb") as f:
|
|
1021
|
+
f.write(action_req_builder.action_req_content_raw)
|
|
1022
|
+
PROGRAMMER_UTILITY_LOGGER.log(
|
|
1023
|
+
logging_level, f"Saved action request bin file at {action_req_out_path}"
|
|
1024
|
+
)
|
|
1025
|
+
|
|
1026
|
+
# save action req to sign
|
|
1027
|
+
action_req_to_sign_out_path = (
|
|
1028
|
+
dir_out_path / f"{type}_action_request.forsign.bin"
|
|
1029
|
+
)
|
|
1030
|
+
with open(action_req_to_sign_out_path, "wb") as f:
|
|
1031
|
+
f.write(action_req_builder.action_req_wo_sign_content_raw)
|
|
1032
|
+
PROGRAMMER_UTILITY_LOGGER.log(
|
|
1033
|
+
logging_level,
|
|
1034
|
+
f"Saved action request to sign bin file at {action_req_to_sign_out_path}",
|
|
1035
|
+
)
|
|
1036
|
+
|
|
1037
|
+
return action_req_out_path, action_req_to_sign_out_path
|
|
1038
|
+
|
|
1039
|
+
def sign_action_request_image(
|
|
1040
|
+
self,
|
|
1041
|
+
unsign_request,
|
|
1042
|
+
forsign_request,
|
|
1043
|
+
dir_out_path,
|
|
1044
|
+
private_key=None,
|
|
1045
|
+
public_key=None,
|
|
1046
|
+
activation_type=None,
|
|
1047
|
+
signing_module=None,
|
|
1048
|
+
udid="0000-0000-0000-0000",
|
|
1049
|
+
signature_bin=None,
|
|
1050
|
+
logging_level=logging.DEBUG,
|
|
1051
|
+
signing_mode="classic_rot",
|
|
1052
|
+
):
|
|
1053
|
+
dir_out_path = pathlib.Path(dir_out_path).absolute()
|
|
1054
|
+
basename_unsign_request = str(pathlib.Path(unsign_request).stem.split(".")[0])
|
|
1055
|
+
|
|
1056
|
+
if activation_type is not None:
|
|
1057
|
+
private_key = None
|
|
1058
|
+
public_key, signature_bin = self.fib_utility.get_public_key_and_signature(
|
|
1059
|
+
activation_type,
|
|
1060
|
+
signing_module,
|
|
1061
|
+
forsign_request,
|
|
1062
|
+
dir_out_path,
|
|
1063
|
+
basename_unsign_request,
|
|
1064
|
+
signing_mode,
|
|
1065
|
+
)
|
|
1066
|
+
|
|
1067
|
+
# sign action request
|
|
1068
|
+
sign_action_request, type = ActionReqBuilder.sign_action_req(
|
|
1069
|
+
unsign_request=unsign_request,
|
|
1070
|
+
private_key=private_key,
|
|
1071
|
+
signature_bin=signature_bin,
|
|
1072
|
+
public_key=public_key,
|
|
1073
|
+
udid=udid,
|
|
1074
|
+
log_level=False,
|
|
1075
|
+
)
|
|
1076
|
+
|
|
1077
|
+
# save sign action req file
|
|
1078
|
+
action_req_out_path = dir_out_path / (basename_unsign_request + ".sign.bin")
|
|
1079
|
+
with open(action_req_out_path, "wb") as f:
|
|
1080
|
+
f.write(sign_action_request)
|
|
1081
|
+
PROGRAMMER_UTILITY_LOGGER.log(
|
|
1082
|
+
logging_level,
|
|
1083
|
+
f"Saved sign action request bin file at {action_req_out_path}",
|
|
1084
|
+
)
|
|
1085
|
+
|
|
1086
|
+
return action_req_out_path
|
|
1087
|
+
|
|
1088
|
+
def get_invalid_size_flags(
|
|
1089
|
+
self,
|
|
1090
|
+
image_paths,
|
|
1091
|
+
bl2_size_kb,
|
|
1092
|
+
wsoc_size_kb,
|
|
1093
|
+
vendor_size_kb,
|
|
1094
|
+
boot_sector_size_kb,
|
|
1095
|
+
vendor_bl3_size_kb=None,
|
|
1096
|
+
protected_storage_size_kb=None,
|
|
1097
|
+
):
|
|
1098
|
+
"""Returns a dict mapping each slot key whose built image exceeds its
|
|
1099
|
+
allocated size to the corresponding size-validation error message
|
|
1100
|
+
(via fib_utility.validate_images_sizes)."""
|
|
1101
|
+
_, failed_images = self.fib_utility.validate_images_sizes(
|
|
1102
|
+
primary_tbl_path=image_paths.get("primary_tbl"),
|
|
1103
|
+
secondary_tbl_path=image_paths.get("secondary_tbl"),
|
|
1104
|
+
tbl_size_kb=bl2_size_kb,
|
|
1105
|
+
primary_wsoc_path=image_paths.get("primary_ti_wsoc"),
|
|
1106
|
+
secondary_wsoc_path=image_paths.get("secondary_ti_wsoc"),
|
|
1107
|
+
wsoc_size_kb=wsoc_size_kb,
|
|
1108
|
+
primary_vendor_path=image_paths.get("primary_vendor_image"),
|
|
1109
|
+
secondary_vendor_path=image_paths.get("secondary_vendor_image"),
|
|
1110
|
+
vendor_size_kb=vendor_size_kb,
|
|
1111
|
+
primary_vendor_bl3_path=image_paths.get("primary_vendor_sbl"),
|
|
1112
|
+
secondary_vendor_bl3_path=image_paths.get("secondary_vendor_sbl"),
|
|
1113
|
+
vendor_bl3_size_kb=vendor_bl3_size_kb,
|
|
1114
|
+
boot_sector_path=image_paths.get("boot_sector"),
|
|
1115
|
+
boot_sector_size_kb=boot_sector_size_kb,
|
|
1116
|
+
protected_storage_path=image_paths.get("protected_storage"),
|
|
1117
|
+
protected_storage_size_kb=protected_storage_size_kb,
|
|
1118
|
+
)
|
|
1119
|
+
|
|
1120
|
+
failed_messages = {
|
|
1121
|
+
image_path: self.fib_utility.get_size_validation_error_msg(actual_kb, limit_kb)
|
|
1122
|
+
for _, image_path, actual_kb, limit_kb in failed_images
|
|
1123
|
+
}
|
|
1124
|
+
return {
|
|
1125
|
+
slot: failed_messages[path]
|
|
1126
|
+
for slot, path in image_paths.items()
|
|
1127
|
+
if path in failed_messages
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
def get_connection_info(self):
|
|
1131
|
+
"""Returns the last-saved connection info dict, or None if none was saved yet."""
|
|
1132
|
+
connection_info_file = join(self.get_tmp_folder(), CONNECTION_INFO_FILE_NAME)
|
|
1133
|
+
if not isfile(connection_info_file):
|
|
1134
|
+
return None
|
|
1135
|
+
with open(connection_info_file, "r") as f:
|
|
1136
|
+
return json.load(f)
|
|
1137
|
+
|
|
1138
|
+
def update_connection_info(self, connection_info_data):
|
|
1139
|
+
connection_info_file = join(self.get_tmp_folder(), CONNECTION_INFO_FILE_NAME)
|
|
1140
|
+
with open(connection_info_file, "w") as f:
|
|
1141
|
+
json.dump(connection_info_data, f, indent=4)
|
|
1142
|
+
return connection_info_data
|
|
1143
|
+
|
|
1144
|
+
def build_tbl_image(
|
|
1145
|
+
self,
|
|
1146
|
+
ti_sign_container,
|
|
1147
|
+
dir_out_path,
|
|
1148
|
+
device,
|
|
1149
|
+
logging_level=logging.DEBUG,
|
|
1150
|
+
slot=None,
|
|
1151
|
+
):
|
|
1152
|
+
dir_out_path = pathlib.Path(dir_out_path).absolute()
|
|
1153
|
+
|
|
1154
|
+
tbl_image_builder = TBLImageBuilder(
|
|
1155
|
+
ti_container_file_path=ti_sign_container,
|
|
1156
|
+
abs_dir_out_path=dir_out_path,
|
|
1157
|
+
device=device,
|
|
1158
|
+
verbose=False,
|
|
1159
|
+
)
|
|
1160
|
+
tbl_image_builder.build()
|
|
1161
|
+
|
|
1162
|
+
base_file_name = "tbl_image"
|
|
1163
|
+
if slot is not None:
|
|
1164
|
+
base_file_name += f"_slot{slot}"
|
|
1165
|
+
|
|
1166
|
+
# save image
|
|
1167
|
+
tbl_image_out_path = (
|
|
1168
|
+
pathlib.Path(tbl_image_builder.abs_dir_out_path) / f"{base_file_name}.bin"
|
|
1169
|
+
)
|
|
1170
|
+
with open(tbl_image_out_path, "wb") as fb:
|
|
1171
|
+
fb.write(tbl_image_builder.ti_image_gpe_content_raw)
|
|
1172
|
+
PROGRAMMER_UTILITY_LOGGER.log(
|
|
1173
|
+
logging_level,
|
|
1174
|
+
f"Saved ti bootloader image GPE bin file at {tbl_image_out_path}",
|
|
1175
|
+
)
|
|
1176
|
+
|
|
1177
|
+
return tbl_image_out_path
|
|
1178
|
+
|
|
1179
|
+
def build_vendor_bl3_image(
|
|
1180
|
+
self,
|
|
1181
|
+
bl3_bin_path,
|
|
1182
|
+
dir_out_path,
|
|
1183
|
+
device,
|
|
1184
|
+
version,
|
|
1185
|
+
pub_key_path=None,
|
|
1186
|
+
slot=None,
|
|
1187
|
+
signing_mode="classic_rot",
|
|
1188
|
+
logging_level=logging.DEBUG,
|
|
1189
|
+
):
|
|
1190
|
+
"""Build vendor BL3 image from binary file
|
|
1191
|
+
|
|
1192
|
+
Args:
|
|
1193
|
+
bl3_bin_path: Path to vendor BL3 binary file
|
|
1194
|
+
dir_out_path: Output directory path
|
|
1195
|
+
device: Device type (CC35XX, CC35XXE, CC37XXR)
|
|
1196
|
+
version: Image version (e.g., "1.0.0.0")
|
|
1197
|
+
pub_key_path: Path to public key for VA TLV (optional)
|
|
1198
|
+
slot: Target slot number (0 or 1), None for auto-naming
|
|
1199
|
+
signing_mode: Signing mode ("classic_rot" or "multi_level")
|
|
1200
|
+
logging_level: Logging level
|
|
1201
|
+
|
|
1202
|
+
Returns:
|
|
1203
|
+
Path to built vendor BL3 image
|
|
1204
|
+
"""
|
|
1205
|
+
dir_out_path = pathlib.Path(dir_out_path).absolute()
|
|
1206
|
+
|
|
1207
|
+
# Build vendor BL3 image
|
|
1208
|
+
bl3_builder = BL3ImageBuilder(
|
|
1209
|
+
bl3_out_file_path=bl3_bin_path,
|
|
1210
|
+
abs_dir_out_path=str(dir_out_path),
|
|
1211
|
+
version=version,
|
|
1212
|
+
pub_key_path=pub_key_path,
|
|
1213
|
+
device=device,
|
|
1214
|
+
verbose=False,
|
|
1215
|
+
signing_mode=signing_mode,
|
|
1216
|
+
)
|
|
1217
|
+
|
|
1218
|
+
if not bl3_builder.build():
|
|
1219
|
+
raise Exception("Failed to build vendor BL3 image")
|
|
1220
|
+
|
|
1221
|
+
base_file_name = "vendor_bl3_image"
|
|
1222
|
+
if slot is not None:
|
|
1223
|
+
base_file_name += f"_slot{slot}"
|
|
1224
|
+
|
|
1225
|
+
# save image with dummy sign
|
|
1226
|
+
vendor_bl3_out_path_unsign = dir_out_path / f"{base_file_name}.unsign.bin"
|
|
1227
|
+
with open(vendor_bl3_out_path_unsign, "wb") as fb:
|
|
1228
|
+
fb.write(bl3_builder.bl3_image_gpe_content_raw)
|
|
1229
|
+
PROGRAMMER_UTILITY_LOGGER.log(
|
|
1230
|
+
logging_level,
|
|
1231
|
+
f"Saved vendor BL3 image with dummy signature bin file at {vendor_bl3_out_path_unsign}",
|
|
1232
|
+
)
|
|
1233
|
+
|
|
1234
|
+
# save image to sign
|
|
1235
|
+
vendor_bl3_out_path_for_sign = dir_out_path / f"{base_file_name}.forsign.bin"
|
|
1236
|
+
with open(vendor_bl3_out_path_for_sign, "wb") as fb:
|
|
1237
|
+
fb.write(bl3_builder.bl3_image_to_sign_gpe_content_raw)
|
|
1238
|
+
PROGRAMMER_UTILITY_LOGGER.log(
|
|
1239
|
+
logging_level,
|
|
1240
|
+
f"Saved part of vendor BL3 image bin file to sign at {vendor_bl3_out_path_for_sign}",
|
|
1241
|
+
)
|
|
1242
|
+
|
|
1243
|
+
return vendor_bl3_out_path_unsign, vendor_bl3_out_path_for_sign
|
|
1244
|
+
|
|
1245
|
+
def sign_vendor_bl3_image(
|
|
1246
|
+
self,
|
|
1247
|
+
vendor_bl3_unsign_image,
|
|
1248
|
+
vendor_bl3_forsign_image,
|
|
1249
|
+
dir_out_path,
|
|
1250
|
+
device,
|
|
1251
|
+
private_key=None,
|
|
1252
|
+
public_key=None,
|
|
1253
|
+
activation_type=None,
|
|
1254
|
+
signing_module=None,
|
|
1255
|
+
signature_bin=None,
|
|
1256
|
+
logging_level=logging.DEBUG,
|
|
1257
|
+
signing_mode="classic_rot",
|
|
1258
|
+
magic=KEY_INFO_MAGIC_DEFAULT,
|
|
1259
|
+
key_info_version=KEY_INFO_VERSION_DEFAULT,
|
|
1260
|
+
svn=KEY_INFO_SVN_DEFAULT,
|
|
1261
|
+
svn_policy=KEY_INFO_SVN_POLICY_DEFAULT,
|
|
1262
|
+
):
|
|
1263
|
+
dir_out_path = pathlib.Path(dir_out_path).absolute()
|
|
1264
|
+
basename_vendor_unsign_image = str(
|
|
1265
|
+
pathlib.Path(vendor_bl3_unsign_image).stem.split(".")[0]
|
|
1266
|
+
)
|
|
1267
|
+
|
|
1268
|
+
if activation_type is not None:
|
|
1269
|
+
private_key = None
|
|
1270
|
+
public_key, signature_bin = self.fib_utility.get_public_key_and_signature(
|
|
1271
|
+
activation_type,
|
|
1272
|
+
signing_module,
|
|
1273
|
+
vendor_bl3_forsign_image,
|
|
1274
|
+
dir_out_path,
|
|
1275
|
+
basename_vendor_unsign_image,
|
|
1276
|
+
signing_mode,
|
|
1277
|
+
)
|
|
1278
|
+
|
|
1279
|
+
# Build KEY_INFO_TLV for multi-level mode (before calling sign_gpe)
|
|
1280
|
+
key_info_tlv_bin = None
|
|
1281
|
+
if signing_mode == "multi_level" and signing_module:
|
|
1282
|
+
key_info_tlv_bin = self.fib_utility.get_key_info_tlv(
|
|
1283
|
+
signing_module_path=signing_module,
|
|
1284
|
+
magic=magic,
|
|
1285
|
+
key_info_version=key_info_version,
|
|
1286
|
+
svn_policy_str=svn_policy,
|
|
1287
|
+
svn=svn,
|
|
1288
|
+
verbose=False,
|
|
1289
|
+
)
|
|
1290
|
+
if key_info_tlv_bin is None:
|
|
1291
|
+
PROGRAMMER_UTILITY_LOGGER.log(
|
|
1292
|
+
logging_level,
|
|
1293
|
+
"Failed to build/get KEY_INFO_TLV for multi-level signing mode",
|
|
1294
|
+
)
|
|
1295
|
+
return None
|
|
1296
|
+
|
|
1297
|
+
# sign vendor bl3 image
|
|
1298
|
+
vendor_bl3_image = GPEBuilder.sign_gpe(
|
|
1299
|
+
unsign_image=vendor_bl3_unsign_image,
|
|
1300
|
+
private_key=private_key,
|
|
1301
|
+
signature_bin=signature_bin,
|
|
1302
|
+
public_key=public_key,
|
|
1303
|
+
log_level=logging.DEBUG,
|
|
1304
|
+
device=device,
|
|
1305
|
+
signing_mode=signing_mode,
|
|
1306
|
+
key_info_tlv_bin=key_info_tlv_bin,
|
|
1307
|
+
)
|
|
1308
|
+
|
|
1309
|
+
# save sign vendor image file
|
|
1310
|
+
vendor_bl3_image_out_path = dir_out_path / (
|
|
1311
|
+
basename_vendor_unsign_image + ".sign.bin"
|
|
1312
|
+
)
|
|
1313
|
+
with open(vendor_bl3_image_out_path, "wb") as f:
|
|
1314
|
+
f.write(vendor_bl3_image)
|
|
1315
|
+
PROGRAMMER_UTILITY_LOGGER.log(
|
|
1316
|
+
logging_level,
|
|
1317
|
+
f"Saved sign Vendor BL3 image bin file at {vendor_bl3_image_out_path}",
|
|
1318
|
+
)
|
|
1319
|
+
|
|
1320
|
+
return vendor_bl3_image_out_path
|
|
1321
|
+
|
|
1322
|
+
def get_vendor_bl3_version(self, vendor_bl3_image_path, device_type):
|
|
1323
|
+
"""Extract version from vendor BL3 image
|
|
1324
|
+
|
|
1325
|
+
Args:
|
|
1326
|
+
vendor_bl3_image_path: Path to vendor BL3 image
|
|
1327
|
+
device_type: Device type enum
|
|
1328
|
+
|
|
1329
|
+
Returns:
|
|
1330
|
+
Version string (e.g., "1.0.0.0") or "N/A" if cannot extract
|
|
1331
|
+
"""
|
|
1332
|
+
try:
|
|
1333
|
+
version = BL3ImageBuilder.get_image_version(
|
|
1334
|
+
vendor_bl3_image_path, device=device_type
|
|
1335
|
+
)
|
|
1336
|
+
return version if version != "-1.-1.-1.-1" else "N/A"
|
|
1337
|
+
except Exception as e:
|
|
1338
|
+
PROGRAMMER_UTILITY_LOGGER.error(
|
|
1339
|
+
f"Failed to extract version from vendor BL3 image: {e}"
|
|
1340
|
+
)
|
|
1341
|
+
return "N/A"
|
|
1342
|
+
|
|
1343
|
+
def build_boot_sector(
|
|
1344
|
+
self,
|
|
1345
|
+
programming_sign_image_path,
|
|
1346
|
+
dir_out_path,
|
|
1347
|
+
device_type,
|
|
1348
|
+
version="0.0.1.0",
|
|
1349
|
+
logging_level=logging.DEBUG,
|
|
1350
|
+
):
|
|
1351
|
+
# build boot sector for initial programming
|
|
1352
|
+
dir_out_path = pathlib.Path(dir_out_path).absolute()
|
|
1353
|
+
|
|
1354
|
+
boot_sector_builder = BootSectorFromImgBuilder(
|
|
1355
|
+
image_file_path=programming_sign_image_path,
|
|
1356
|
+
abs_dir_out_path=dir_out_path,
|
|
1357
|
+
version=version,
|
|
1358
|
+
device=device_type,
|
|
1359
|
+
verbose=False,
|
|
1360
|
+
)
|
|
1361
|
+
boot_sector_builder.build()
|
|
1362
|
+
boot_sector_path = boot_sector_builder.boot_sector_image_out_path
|
|
1363
|
+
PROGRAMMER_UTILITY_LOGGER.log(
|
|
1364
|
+
logging_level, f"Saved boot sector image file at {boot_sector_path}"
|
|
1365
|
+
)
|
|
1366
|
+
|
|
1367
|
+
return boot_sector_path
|
|
1368
|
+
|
|
1369
|
+
def get_supported_devices_family_list(self, supported_devices):
|
|
1370
|
+
ret = []
|
|
1371
|
+
if any(
|
|
1372
|
+
[device in ["CC350x", "CC355x", "CC356x"] for device in supported_devices]
|
|
1373
|
+
):
|
|
1374
|
+
ret.append(CC35X1)
|
|
1375
|
+
|
|
1376
|
+
if any(
|
|
1377
|
+
[
|
|
1378
|
+
device in ["CC350xE", "CC355xE", "CC356xE"]
|
|
1379
|
+
for device in supported_devices
|
|
1380
|
+
]
|
|
1381
|
+
):
|
|
1382
|
+
ret.append(CC35X1E)
|
|
1383
|
+
|
|
1384
|
+
# if any([device in ["CC360xR"] for device in supported_devices]):
|
|
1385
|
+
# ret.append(CC360XR)
|
|
1386
|
+
|
|
1387
|
+
if any([device in ["CC370xR"] for device in supported_devices]):
|
|
1388
|
+
ret.append(CC370XR)
|
|
1389
|
+
|
|
1390
|
+
if any(
|
|
1391
|
+
[device in ["CC330x", "CC335x", "CC336x"] for device in supported_devices]
|
|
1392
|
+
):
|
|
1393
|
+
ret.append(CC33XX)
|
|
1394
|
+
|
|
1395
|
+
return ret
|
|
1396
|
+
|
|
1397
|
+
def format_nested(self, obj):
|
|
1398
|
+
if obj is None:
|
|
1399
|
+
return "None"
|
|
1400
|
+
elif isinstance(obj, dict):
|
|
1401
|
+
if not obj:
|
|
1402
|
+
return "None"
|
|
1403
|
+
else:
|
|
1404
|
+
return tabulate(
|
|
1405
|
+
[
|
|
1406
|
+
[k, self.format_nested(v)]
|
|
1407
|
+
for k, v in obj.items()
|
|
1408
|
+
if not k.startswith("_")
|
|
1409
|
+
],
|
|
1410
|
+
tablefmt="grid",
|
|
1411
|
+
)
|
|
1412
|
+
else:
|
|
1413
|
+
return str(obj)
|
|
1414
|
+
|
|
1415
|
+
def create_table_from_json(self, json_obj):
|
|
1416
|
+
if not isinstance(json_obj, dict):
|
|
1417
|
+
return self.format_nested(json_obj)
|
|
1418
|
+
else:
|
|
1419
|
+
table_data = [
|
|
1420
|
+
[k, self.format_nested(v)]
|
|
1421
|
+
for k, v in json_obj.items()
|
|
1422
|
+
if not k.startswith("_")
|
|
1423
|
+
]
|
|
1424
|
+
return tabulate(
|
|
1425
|
+
table_data,
|
|
1426
|
+
tablefmt="grid",
|
|
1427
|
+
headers=["Key", "Value"],
|
|
1428
|
+
)
|
|
1429
|
+
|
|
1430
|
+
def build_clean_image(
|
|
1431
|
+
self, image_file_name, image_size, dir_out_path, logging_level=logging.DEBUG
|
|
1432
|
+
):
|
|
1433
|
+
# build boot sector for initial programming
|
|
1434
|
+
dir_out_path = pathlib.Path(dir_out_path).absolute()
|
|
1435
|
+
|
|
1436
|
+
clean_image_builder = CleanImageBuilder(
|
|
1437
|
+
image_size=image_size,
|
|
1438
|
+
image_file_name=image_file_name,
|
|
1439
|
+
abs_dir_out_path=dir_out_path,
|
|
1440
|
+
verbose=False if logging_level == logging.DEBUG else True,
|
|
1441
|
+
)
|
|
1442
|
+
clean_image_builder.build()
|
|
1443
|
+
clean_image_path = clean_image_builder.clean_image_path
|
|
1444
|
+
|
|
1445
|
+
PROGRAMMER_UTILITY_LOGGER.log(
|
|
1446
|
+
logging_level, f"Clean image file created at {clean_image_path}"
|
|
1447
|
+
)
|
|
1448
|
+
|
|
1449
|
+
return clean_image_path
|
|
1450
|
+
|
|
1451
|
+
def build_wsoc_image(
|
|
1452
|
+
self,
|
|
1453
|
+
ti_sign_container,
|
|
1454
|
+
dir_out_path,
|
|
1455
|
+
device,
|
|
1456
|
+
logging_level=logging.DEBUG,
|
|
1457
|
+
slot=None,
|
|
1458
|
+
):
|
|
1459
|
+
dir_out_path = pathlib.Path(dir_out_path).absolute()
|
|
1460
|
+
|
|
1461
|
+
wsoc_image_builder = WSOCImageBuilder(
|
|
1462
|
+
ti_container_file_path=ti_sign_container,
|
|
1463
|
+
abs_dir_out_path=dir_out_path,
|
|
1464
|
+
device=device,
|
|
1465
|
+
verbose=False,
|
|
1466
|
+
)
|
|
1467
|
+
|
|
1468
|
+
wsoc_image_builder.build()
|
|
1469
|
+
|
|
1470
|
+
base_file_name = "ti_wireless_fw_image"
|
|
1471
|
+
if slot is not None:
|
|
1472
|
+
base_file_name += f"_slot{slot}"
|
|
1473
|
+
|
|
1474
|
+
# save image
|
|
1475
|
+
wsoc_image_out_path = (
|
|
1476
|
+
pathlib.Path(wsoc_image_builder.abs_dir_out_path) / f"{base_file_name}.bin"
|
|
1477
|
+
)
|
|
1478
|
+
with open(wsoc_image_out_path, "wb") as fb:
|
|
1479
|
+
fb.write(wsoc_image_builder.ti_image_gpe_content_raw)
|
|
1480
|
+
PROGRAMMER_UTILITY_LOGGER.log(
|
|
1481
|
+
logging_level,
|
|
1482
|
+
f"Saved TI Wireless FW image GPE bin file at {wsoc_image_out_path}",
|
|
1483
|
+
)
|
|
1484
|
+
|
|
1485
|
+
return wsoc_image_out_path
|
|
1486
|
+
|
|
1487
|
+
def update_xspi_to_psram_type(self, xspi_json_path, psram_type, output_dir_path):
|
|
1488
|
+
# create updated temp jsons depends on the psram type
|
|
1489
|
+
with open(xspi_json_path, "r") as f:
|
|
1490
|
+
xspi_data = json.load(f)
|
|
1491
|
+
|
|
1492
|
+
xspi_data["xspi_header"]["ram_name"] = psram_type
|
|
1493
|
+
|
|
1494
|
+
# save updated jsons
|
|
1495
|
+
updated_xspi = join(output_dir_path, basename(xspi_json_path))
|
|
1496
|
+
|
|
1497
|
+
with open(updated_xspi, "w") as f:
|
|
1498
|
+
json.dump(xspi_data, f, indent=4)
|
|
1499
|
+
|
|
1500
|
+
return updated_xspi
|
|
1501
|
+
|
|
1502
|
+
def check_if_file_exist_else_exit(self, key, path):
|
|
1503
|
+
if not isfile(path):
|
|
1504
|
+
PROGRAMMER_UTILITY_LOGGER.error(
|
|
1505
|
+
f"ERROR: Path to {key} after path correction couldn't be found at {path}"
|
|
1506
|
+
)
|
|
1507
|
+
sys.exit(1)
|
|
1508
|
+
|
|
1509
|
+
def fix_paths_in_tool_settings(self, tool_settings_path):
|
|
1510
|
+
tool_settings_path_obj = pathlib.Path(tool_settings_path)
|
|
1511
|
+
|
|
1512
|
+
if tool_settings_path_obj.is_absolute():
|
|
1513
|
+
base_path = tool_settings_path_obj.parent
|
|
1514
|
+
else:
|
|
1515
|
+
base_path = pathlib.Path(getcwd())
|
|
1516
|
+
|
|
1517
|
+
mod_tool_settings = tool_settings_path.replace(".json", ".mod.json")
|
|
1518
|
+
|
|
1519
|
+
mod_tool_settings_content = json.load(open(tool_settings_path, "r"))
|
|
1520
|
+
|
|
1521
|
+
## Fix path in programming_debug_and_ota_signed_components
|
|
1522
|
+
if (
|
|
1523
|
+
"programming_debug_and_ota_signed_components"
|
|
1524
|
+
not in mod_tool_settings_content
|
|
1525
|
+
):
|
|
1526
|
+
PROGRAMMER_UTILITY_LOGGER.error(
|
|
1527
|
+
"ERROR: Provided tool_settings.json is incorrect format, 'programming_debug_and_ota_signed_components' is missing."
|
|
1528
|
+
)
|
|
1529
|
+
sys.exit(1)
|
|
1530
|
+
|
|
1531
|
+
programming_debug_and_ota_signed_components = mod_tool_settings_content[
|
|
1532
|
+
"programming_debug_and_ota_signed_components"
|
|
1533
|
+
]
|
|
1534
|
+
|
|
1535
|
+
for key, value in programming_debug_and_ota_signed_components.items():
|
|
1536
|
+
if type(value) is str and value != "":
|
|
1537
|
+
p = pathlib.Path(value)
|
|
1538
|
+
if not p.is_absolute():
|
|
1539
|
+
p = self.fix_path(str(p), alt_base_path=str(base_path))
|
|
1540
|
+
programming_debug_and_ota_signed_components[key] = p
|
|
1541
|
+
|
|
1542
|
+
self.check_if_file_exist_else_exit(key, str(p))
|
|
1543
|
+
|
|
1544
|
+
elif type(value) is dict:
|
|
1545
|
+
for value_key, value_value in value.items():
|
|
1546
|
+
if type(value_value) is str and value_value != "":
|
|
1547
|
+
p = pathlib.Path(value_value)
|
|
1548
|
+
if not p.is_absolute():
|
|
1549
|
+
p = self.fix_path(str(p), alt_base_path=str(base_path))
|
|
1550
|
+
value[value_key] = p
|
|
1551
|
+
|
|
1552
|
+
self.check_if_file_exist_else_exit(key, str(p))
|
|
1553
|
+
|
|
1554
|
+
else:
|
|
1555
|
+
pass
|
|
1556
|
+
|
|
1557
|
+
else:
|
|
1558
|
+
pass
|
|
1559
|
+
|
|
1560
|
+
json.dump(mod_tool_settings_content, open(mod_tool_settings, "w"), indent=4)
|
|
1561
|
+
|
|
1562
|
+
return mod_tool_settings
|
|
1563
|
+
|
|
1564
|
+
def dict_to_json(self, dict_data, json_file_path, indent=4):
|
|
1565
|
+
with open(json_file_path, "w") as f:
|
|
1566
|
+
json.dump(dict_data, f, indent=indent)
|
|
1567
|
+
|
|
1568
|
+
def enable_ota_supported(self, enable_ota):
|
|
1569
|
+
"""
|
|
1570
|
+
Validates and converts the enable_ota parameter (0/1, "yes"/"no", True/False)
|
|
1571
|
+
to a standardized integer flag (0 or 1).
|
|
1572
|
+
"""
|
|
1573
|
+
VALID_OPTIONS = {
|
|
1574
|
+
"yes": 1,
|
|
1575
|
+
"no": 0,
|
|
1576
|
+
"true": 1,
|
|
1577
|
+
"false": 0,
|
|
1578
|
+
1: 1,
|
|
1579
|
+
0: 0,
|
|
1580
|
+
True: 1,
|
|
1581
|
+
False: 0,
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
# Handle string input (case-insensitive)
|
|
1585
|
+
if isinstance(enable_ota, str):
|
|
1586
|
+
input_lower = enable_ota.lower()
|
|
1587
|
+
if input_lower in VALID_OPTIONS:
|
|
1588
|
+
return VALID_OPTIONS[input_lower]
|
|
1589
|
+
|
|
1590
|
+
# Handle integer or boolean input
|
|
1591
|
+
elif isinstance(enable_ota, (int, bool)):
|
|
1592
|
+
if enable_ota in VALID_OPTIONS:
|
|
1593
|
+
return VALID_OPTIONS[enable_ota]
|
|
1594
|
+
|
|
1595
|
+
# If none of the above match, raise an error
|
|
1596
|
+
raise ValueError(
|
|
1597
|
+
f"Invalid value for 'enable_ota': '{enable_ota}'. "
|
|
1598
|
+
f"Expected 0, 1, True, False, 'yes', 'no', 'true', or 'false'."
|
|
1599
|
+
)
|
|
1600
|
+
|
|
1601
|
+
def normalize_restrict_ini_hex_to_int(self, restrict_ini):
|
|
1602
|
+
"""
|
|
1603
|
+
Validates that the input for 'restrict_ini' is a hex value (string or int),
|
|
1604
|
+
ensures it fits within a 0 to 0xFFFFFFFF, and converts it to an integer.
|
|
1605
|
+
"""
|
|
1606
|
+
if restrict_ini == "" or restrict_ini is None:
|
|
1607
|
+
return 0
|
|
1608
|
+
|
|
1609
|
+
if isinstance(restrict_ini, int):
|
|
1610
|
+
if 0 <= restrict_ini <= 0xFFFFFFFF:
|
|
1611
|
+
return restrict_ini
|
|
1612
|
+
else:
|
|
1613
|
+
raise argparse.ArgumentTypeError( # Changed from ValueError
|
|
1614
|
+
f"Integer value for 'restrict_ini' out of range (0 to 0xFFFFFFFF): {restrict_ini}"
|
|
1615
|
+
)
|
|
1616
|
+
|
|
1617
|
+
if not isinstance(restrict_ini, str):
|
|
1618
|
+
raise argparse.ArgumentTypeError( # Changed from TypeError
|
|
1619
|
+
f"Invalid input type for 'restrict_ini': {type(restrict_ini)}. Expected string or int."
|
|
1620
|
+
)
|
|
1621
|
+
|
|
1622
|
+
restrict_ini = restrict_ini.upper()
|
|
1623
|
+
restrict_ini_normalized_hex = restrict_ini.replace("0X", "")
|
|
1624
|
+
|
|
1625
|
+
if not re.fullmatch(r"^[0-9A-F]+$", restrict_ini_normalized_hex):
|
|
1626
|
+
raise argparse.ArgumentTypeError( # Changed from ValueError
|
|
1627
|
+
f"Invalid hexadecimal characters in 'restrict_ini': '{restrict_ini}'"
|
|
1628
|
+
)
|
|
1629
|
+
|
|
1630
|
+
try:
|
|
1631
|
+
restrict_ini_int_value = int(restrict_ini_normalized_hex, 16)
|
|
1632
|
+
except ValueError:
|
|
1633
|
+
raise argparse.ArgumentTypeError( # Changed from ValueError
|
|
1634
|
+
f"Failed to convert 'restrict_ini' value '{restrict_ini}' to integer."
|
|
1635
|
+
)
|
|
1636
|
+
|
|
1637
|
+
MAX_32BIT_VALUE = 0xFFFFFFFF
|
|
1638
|
+
if restrict_ini_int_value > MAX_32BIT_VALUE or restrict_ini_int_value < 0:
|
|
1639
|
+
raise argparse.ArgumentTypeError( # Changed from ValueError
|
|
1640
|
+
f"'restrict_ini' value out of range (0 to 0xFFFFFFFF). Value: 0x{restrict_ini_normalized_hex} (Decimal: {restrict_ini_int_value})."
|
|
1641
|
+
)
|
|
1642
|
+
|
|
1643
|
+
return restrict_ini_int_value
|
|
1644
|
+
|
|
1645
|
+
def validate_hex(self, value, max_length):
|
|
1646
|
+
"""
|
|
1647
|
+
Validate hex value.
|
|
1648
|
+
- Must be hexadecimal characters only
|
|
1649
|
+
- Validate max length of max_length hex characters
|
|
1650
|
+
- Accepts with or without 0x prefix
|
|
1651
|
+
- Returns normalized value with 0x prefix, or None if empty
|
|
1652
|
+
"""
|
|
1653
|
+
if value is None or value == "":
|
|
1654
|
+
return None
|
|
1655
|
+
|
|
1656
|
+
value_str = str(value)
|
|
1657
|
+
|
|
1658
|
+
# Check if it starts with 0x prefix
|
|
1659
|
+
if value_str.lower().startswith("0x"):
|
|
1660
|
+
hex_part = value_str[2:]
|
|
1661
|
+
else:
|
|
1662
|
+
hex_part = value_str
|
|
1663
|
+
|
|
1664
|
+
# Validate hexadecimal characters only
|
|
1665
|
+
if not re.match(r"^[0-9a-fA-F]*$", hex_part):
|
|
1666
|
+
raise argparse.ArgumentTypeError(
|
|
1667
|
+
f"Invalid hexadecimal value '{value}'. Must contain only hexadecimal characters (0-9, a-f, A-F)."
|
|
1668
|
+
)
|
|
1669
|
+
|
|
1670
|
+
# Check max length
|
|
1671
|
+
if len(hex_part) > max_length:
|
|
1672
|
+
raise argparse.ArgumentTypeError(
|
|
1673
|
+
f"Invalid hexadecimal value '{value}'. Maximum length is {max_length} hexadecimal characters ({max_length * 4} bits), got {len(hex_part)} characters."
|
|
1674
|
+
)
|
|
1675
|
+
|
|
1676
|
+
# Return normalized with 0x prefix
|
|
1677
|
+
return "0x" + hex_part if hex_part else None
|
|
1678
|
+
|
|
1679
|
+
def convert_mac_to_int(self, mac_address):
|
|
1680
|
+
"""
|
|
1681
|
+
Converts a string MAC address (e.g., "00:1A:2B:3C:4D:5E") into a 64-bit integer.
|
|
1682
|
+
Accepts formats: XX:XX:XX:XX:XX:XX, XX-XX-XX-XX-XX-XX, or XXXXXXXXXXXX
|
|
1683
|
+
|
|
1684
|
+
Used by both CLI and REST API.
|
|
1685
|
+
"""
|
|
1686
|
+
|
|
1687
|
+
if mac_address == "":
|
|
1688
|
+
return 0
|
|
1689
|
+
|
|
1690
|
+
# Remove any separators (':', '-', or '.') from the string
|
|
1691
|
+
mac_address_clean = re.sub(r"[:\-.]", "", str(mac_address)).upper()
|
|
1692
|
+
|
|
1693
|
+
# Ensure the string is exactly 12 hexadecimal characters long
|
|
1694
|
+
if len(mac_address_clean) != 12:
|
|
1695
|
+
raise argparse.ArgumentTypeError(
|
|
1696
|
+
f"Invalid MAC address length: '{mac_address}'. Must be 12 hexadecimal digits."
|
|
1697
|
+
)
|
|
1698
|
+
|
|
1699
|
+
# Validate hex characters
|
|
1700
|
+
if not re.match(r"^[0-9A-F]{12}$", mac_address_clean):
|
|
1701
|
+
raise argparse.ArgumentTypeError(
|
|
1702
|
+
f"Invalid MAC address: '{mac_address}'. Must contain only hexadecimal digits (0-9, A-F)."
|
|
1703
|
+
)
|
|
1704
|
+
|
|
1705
|
+
try:
|
|
1706
|
+
# Convert the hex string to an integer
|
|
1707
|
+
mac_address_int = int(mac_address_clean, 16)
|
|
1708
|
+
return mac_address_int
|
|
1709
|
+
except ValueError:
|
|
1710
|
+
raise argparse.ArgumentTypeError(
|
|
1711
|
+
f"Failed to convert MAC address to integer: '{mac_address}'"
|
|
1712
|
+
)
|
|
1713
|
+
|
|
1714
|
+
def normalize_wifi6_disable(self, wifi6_disable):
|
|
1715
|
+
"""
|
|
1716
|
+
Validates and converts the wifi6_disable parameter (0/1 or "yes"/"no")
|
|
1717
|
+
to a standardized integer flag (0 or 1).
|
|
1718
|
+
"""
|
|
1719
|
+
VALID_OPTIONS = {"yes": 1, "no": 0, "1": 1, "0": 0, 1: 1, 0: 0}
|
|
1720
|
+
|
|
1721
|
+
if wifi6_disable == "":
|
|
1722
|
+
return 0
|
|
1723
|
+
|
|
1724
|
+
if isinstance(wifi6_disable, str):
|
|
1725
|
+
input_lower = wifi6_disable.lower()
|
|
1726
|
+
if input_lower in VALID_OPTIONS:
|
|
1727
|
+
return VALID_OPTIONS[input_lower]
|
|
1728
|
+
elif isinstance(wifi6_disable, int):
|
|
1729
|
+
if wifi6_disable in VALID_OPTIONS:
|
|
1730
|
+
return VALID_OPTIONS[wifi6_disable]
|
|
1731
|
+
|
|
1732
|
+
raise ValueError(
|
|
1733
|
+
f"Invalid value for 'wifi6_disable': '{wifi6_disable}'. "
|
|
1734
|
+
f"Expected 0, 1, 'yes', or 'no'."
|
|
1735
|
+
)
|
|
1736
|
+
|
|
1737
|
+
def is_valid_country_code(self, code):
|
|
1738
|
+
"""Check if country code is valid"""
|
|
1739
|
+
if not code:
|
|
1740
|
+
return False
|
|
1741
|
+
return code.upper() in VALID_COUNTRY_CODES
|
|
1742
|
+
|
|
1743
|
+
def get_country_codes_list(self):
|
|
1744
|
+
return COUNTRY_CODES_LIST
|
|
1745
|
+
|
|
1746
|
+
def calculate_device_country_code_int(self, country_code):
|
|
1747
|
+
"""
|
|
1748
|
+
Converts a 2-char country code string to a 16-bit integer.
|
|
1749
|
+
Validates against predefined list of supported country codes.
|
|
1750
|
+
Args:
|
|
1751
|
+
country_code: 2-letter country code (e.g., 'US', 'GB') or 0
|
|
1752
|
+
Returns:
|
|
1753
|
+
int: 16-bit integer representation (big-endian ASCII)
|
|
1754
|
+
Examples:
|
|
1755
|
+
'US' -> 21845 (0x5553)
|
|
1756
|
+
'GB' -> 18242 (0x4742)
|
|
1757
|
+
0 or '0' -> 0
|
|
1758
|
+
"""
|
|
1759
|
+
# Handle zero/empty cases
|
|
1760
|
+
if country_code in (0, -1, "0", "-1", "", None):
|
|
1761
|
+
return 0
|
|
1762
|
+
|
|
1763
|
+
# Reject integer input (except 0)
|
|
1764
|
+
if isinstance(country_code, int):
|
|
1765
|
+
if country_code == 0:
|
|
1766
|
+
return 0
|
|
1767
|
+
raise argparse.ArgumentTypeError(
|
|
1768
|
+
f"Country code must be a 2-letter string (e.g., US, GB), not integer: {country_code}"
|
|
1769
|
+
)
|
|
1770
|
+
|
|
1771
|
+
# Validate type
|
|
1772
|
+
if not isinstance(country_code, str):
|
|
1773
|
+
raise argparse.ArgumentTypeError(
|
|
1774
|
+
f"Invalid type: {type(country_code).__name__}. Expected 2-letter country code string."
|
|
1775
|
+
)
|
|
1776
|
+
|
|
1777
|
+
# Validate length
|
|
1778
|
+
if len(country_code) != 2:
|
|
1779
|
+
raise argparse.ArgumentTypeError(
|
|
1780
|
+
f"Invalid country code length: '{country_code}'. Must be exactly 2 letters.\n"
|
|
1781
|
+
f"Examples: US, GB, FR, DE, JP, CN"
|
|
1782
|
+
)
|
|
1783
|
+
|
|
1784
|
+
# Validate characters (only letters)
|
|
1785
|
+
if not country_code.isalpha():
|
|
1786
|
+
raise argparse.ArgumentTypeError(
|
|
1787
|
+
f"Invalid characters in '{country_code}'. Only letters (A-Z) allowed."
|
|
1788
|
+
)
|
|
1789
|
+
|
|
1790
|
+
# Convert to uppercase
|
|
1791
|
+
country_code_upper = country_code.upper()
|
|
1792
|
+
|
|
1793
|
+
# Validate against supported country codes
|
|
1794
|
+
if country_code_upper not in VALID_COUNTRY_CODES:
|
|
1795
|
+
raise argparse.ArgumentTypeError(
|
|
1796
|
+
f"Unsupported country code: '{country_code}'. "
|
|
1797
|
+
f"Must be one of the supported codes (e.g., US, GB, FR, DE, JP, CN).\n"
|
|
1798
|
+
)
|
|
1799
|
+
|
|
1800
|
+
if country_code_upper == "WW":
|
|
1801
|
+
country_code_upper = "00"
|
|
1802
|
+
|
|
1803
|
+
# Calculate 16-bit integer (big-endian)
|
|
1804
|
+
char1_ascii = ord(country_code_upper[0])
|
|
1805
|
+
char2_ascii = ord(country_code_upper[1])
|
|
1806
|
+
result = (char1_ascii << 8) | char2_ascii # Changed to big-endian
|
|
1807
|
+
|
|
1808
|
+
return result
|
|
1809
|
+
|
|
1810
|
+
def split_mac_address(self, mac_address):
|
|
1811
|
+
if isinstance(mac_address, str):
|
|
1812
|
+
clean_mac = mac_address.replace(":", "").upper()
|
|
1813
|
+
first_part = clean_mac[:8]
|
|
1814
|
+
second_part = clean_mac[8:]
|
|
1815
|
+
|
|
1816
|
+
return int(first_part, 16), int(second_part, 16)
|
|
1817
|
+
|
|
1818
|
+
else:
|
|
1819
|
+
mac_address_31_0 = mac_address & 0xFFFFFFFF
|
|
1820
|
+
mac_address_47_32 = (mac_address >> 32) & 0xFFFF
|
|
1821
|
+
|
|
1822
|
+
return mac_address_31_0, mac_address_47_32
|