@@ -321,13 +321,28 @@ def _perform_flash_operation(
321321 """
322322 with self ._busybox () as console :
323323 manifest = self .manifest
324- target = partition or self .call ("get_default_target" ) or manifest .spec .default_target
324+ # Check if partition is a valid target device name in the manifest
325+ # If not, treat it as a partition label and use the default target
326+ partition_label = None
327+ if partition and partition in manifest .spec .targets :
328+ target = partition
329+ else :
330+ # partition is a partition label, not a target device
331+ partition_label = partition
332+ target = self .call ("get_default_target" ) or manifest .spec .default_target
325333 if not target :
326334 raise ArgumentError ("No partition or default target specified" )
327335
328336 target_device = self ._get_target_device (target , manifest , console )
329337
330- self .logger .info (f"Using target block device: { target_device } " )
338+ # If a partition label is provided, construct the partition path
339+ flash_target = target_device
340+ if partition_label :
341+ flash_target = f"/dev/disk/by-partlabel/{ partition_label } "
342+ self .logger .info (f"Using target partition: { partition_label } ({ flash_target } )" )
343+ else :
344+ self .logger .info (f"Using target block device: { target_device } " )
345+
331346 console .sendline (f"export dhcp_addr={ self ._dhcp_details .ip_address } " )
332347 console .expect (manifest .spec .login .prompt , timeout = EXPECT_TIMEOUT_DEFAULT )
333348 console .sendline (f"export gw_addr={ self ._dhcp_details .gateway } " )
@@ -372,7 +387,7 @@ def _perform_flash_operation(
372387 manifest ,
373388 path ,
374389 image_url ,
375- target_device ,
390+ flash_target ,
376391 insecure_tls ,
377392 stored_cacert ,
378393 header_args ,
@@ -385,7 +400,7 @@ def _perform_flash_operation(
385400 manifest ,
386401 path ,
387402 image_url ,
388- target_device ,
403+ flash_target ,
389404 insecure_tls ,
390405 stored_cacert ,
391406 header_args ,
@@ -1162,15 +1177,90 @@ def _validate_bearer_token(self, token: str | None) -> str | None:
11621177
11631178 return token
11641179
1180+ def _resolve_flash_parameters (
1181+ self , file : str | None , partitions : tuple [str , ...] | None , block_device : str | None
1182+ ) -> list [tuple [str , str | None , str | None ]]:
1183+ """Resolve and validate flash parameters from CLI options.
1184+
1185+ Supports multiple modes:
1186+ 1. Single file to default block device:
1187+ flash image.img
1188+ 2. Single file to specific block device:
1189+ flash image.img --target usd
1190+ 3. Multiple file-partition pairs:
1191+ flash -t rootfs:rootfs.img -t boot:boot.img
1192+ 4. Multiple file-partition pairs to specific block device:
1193+ flash --target emmc -t rootfs:rootfs.img -t boot:boot.img
1194+
1195+ Args:
1196+ file: The image file argument (positional, optional). When provided alone,
1197+ flashes to the default block device.
1198+ partitions: The -t options in 'partition:file' format (repeatable).
1199+ Use this to specify partition and file together.
1200+ block_device: The --target option (block device name like 'usd', 'emmc').
1201+ Can be used with both file and -t options.
1202+
1203+ Returns:
1204+ list[tuple]: List of (image_file, target_partition, block_device) tuples
1205+
1206+ Raises:
1207+ click.UsageError: If parameters are invalid or conflicting
1208+ """
1209+ flash_ops : list [tuple [str , str | None , str | None ]] = []
1210+
1211+ # Mode 1 & 2: Single file with optional block device
1212+ if file :
1213+ if partitions :
1214+ raise click .UsageError (
1215+ "Cannot specify FILE argument with -t options. "
1216+ "Use either 'flash image.img' or 'flash -t partition:file'"
1217+ )
1218+ flash_ops .append ((file , None , block_device ))
1219+
1220+ # Mode 3 & 4: Multiple file-partition pairs with optional block device
1221+ elif partitions :
1222+ for spec in partitions :
1223+ if ':' not in spec :
1224+ raise click .UsageError (
1225+ f"Invalid flash spec format: '{ spec } '. "
1226+ "Expected 'partition:filename'"
1227+ )
1228+ partition_label , filename = spec .split (':' , 1 )
1229+ if not partition_label or not filename :
1230+ raise click .UsageError (
1231+ f"Invalid flash spec format: '{ spec } '. "
1232+ "Both partition label and filename are required"
1233+ )
1234+ flash_ops .append ((filename , partition_label , block_device ))
1235+
1236+ # No input provided
1237+ else :
1238+ raise click .UsageError (
1239+ "Must provide either FILE argument or -t options. "
1240+ "Use 'j storage flash --help' for usage examples"
1241+ )
1242+
1243+ return flash_ops
1244+
11651245 def cli (self ):
11661246 @driver_click_group (self )
11671247 def base ():
11681248 """Software-defined flasher interface"""
11691249 pass
11701250
11711251 @base .command ()
1172- @click .argument ("file" )
1173- @click .option ("--target" , type = str )
1252+ @click .argument ("file" , required = False )
1253+ @click .option (
1254+ "--target" ,
1255+ type = str ,
1256+ help = "Block device to flash to (e.g., 'usd', 'emmc'). If not provided, uses default target."
1257+ )
1258+ @click .option (
1259+ "-t" ,
1260+ "partitions" ,
1261+ multiple = True ,
1262+ help = "Flash file to partition: 'partition:filename'. Can be repeated for multiple partitions." ,
1263+ )
11741264 @click .option ("--os-image-checksum" , help = "SHA256 checksum of OS image (direct value)" )
11751265 @click .option (
11761266 "--os-image-checksum-file" ,
@@ -1219,6 +1309,7 @@ def base():
12191309 def flash (
12201310 file ,
12211311 target ,
1312+ partitions ,
12221313 os_image_checksum ,
12231314 os_image_checksum_file ,
12241315 console_debug ,
@@ -1233,31 +1324,66 @@ def flash(
12331324 fls_version ,
12341325 fls_binary_url ,
12351326 ):
1236- """Flash image to DUT from file"""
1237- if os_image_checksum_file and os .path .exists (os_image_checksum_file ):
1238- with open (os_image_checksum_file ) as f :
1239- os_image_checksum = f .read ().strip ().split ()[0 ]
1240- self .logger .info (f"Read checksum from file: { os_image_checksum } " )
1327+ """Flash image(s) to DUT
12411328
1242- self .set_console_debug (console_debug )
1329+ Usage examples:
1330+
1331+ - Flash to default block device and target
1332+
1333+ j storage flash image.img
1334+
1335+ - Flash to specific block device (e.g., 'emmc')
12431336
1244- headers = self ._parse_headers (header ) if header else None
1245-
1246- self .flash (
1247- file ,
1248- partition = target ,
1249- force_exporter_http = force_exporter_http ,
1250- force_flash_bundle = force_flash_bundle ,
1251- cacert_file = cacert ,
1252- insecure_tls = insecure_tls ,
1253- headers = headers ,
1254- bearer_token = bearer ,
1255- retries = retries ,
1256- method = method ,
1257- fls_version = fls_version ,
1258- fls_binary_url = fls_binary_url ,
1337+ j storage flash image.img --target emmc
1338+
1339+ - Flash to partition(s) on default block device
1340+
1341+ j storage flash -t rootfs:rootfs.img
1342+
1343+ - Flash to partition(s) on specific block device
1344+
1345+ j storage flash --target emmc -t rootfs:rootfs.img -t boot:boot.img
1346+ """
1347+ # Validate and resolve flash parameters
1348+ flash_operations = self ._resolve_flash_parameters (
1349+ file , partitions , target
12591350 )
12601351
1352+ # Setup common options
1353+ self .set_console_debug (console_debug )
1354+ headers_dict = self ._parse_headers (header ) if header else None
1355+
1356+ # Load checksum from file if provided (used for all operations)
1357+ checksum = os_image_checksum
1358+ if os_image_checksum_file and os .path .exists (os_image_checksum_file ):
1359+ with open (os_image_checksum_file ) as f :
1360+ checksum = f .read ().strip ().split ()[0 ]
1361+ self .logger .info (f"Read checksum from file: { checksum } " )
1362+
1363+ # Execute each flash operation
1364+ for idx , (image_file , target_partition ) in enumerate (flash_operations ):
1365+ op_num = f"{ idx + 1 } /{ len (flash_operations )} " if len (flash_operations ) > 1 else ""
1366+ op_desc = f"partition '{ target_partition } '" if target_partition else "default target"
1367+
1368+ self .logger .info (f"Flashing { op_num } { op_desc } with '{ image_file } '" .strip ())
1369+
1370+ # Perform the flash operation
1371+ self .flash (
1372+ image_file ,
1373+ partition = target_partition ,
1374+ os_image_checksum = checksum ,
1375+ force_exporter_http = force_exporter_http ,
1376+ force_flash_bundle = force_flash_bundle ,
1377+ cacert_file = cacert ,
1378+ insecure_tls = insecure_tls ,
1379+ headers = headers_dict ,
1380+ bearer_token = bearer ,
1381+ retries = retries ,
1382+ method = method ,
1383+ fls_version = fls_version ,
1384+ fls_binary_url = fls_binary_url ,
1385+ )
1386+
12611387 @base .command ()
12621388 @debug_console_option
12631389 def bootloader_shell (console_debug ):
0 commit comments