Skip to content

Commit 58cb69a

Browse files
Merge pull request #6301 from maramsmurthy/nvme-dynamic-flbas-and-type-fix
Nvme dynamic flbas and type fix
2 parents 9017b3b + 7009224 commit 58cb69a

2 files changed

Lines changed: 246 additions & 14 deletions

File tree

avocado/utils/nvme.py

Lines changed: 241 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,18 @@ def get_current_ns_ids(controller_name):
110110
output = process.run(cmd, shell=True, sudo=True, ignore_status=True).stdout_text
111111
for line in output.splitlines():
112112
if line.startswith("["):
113-
namespaces.append(int(line.split()[1].split("]")[0]) + 1)
113+
# Format is: [ 0]:0x1 where 0x1 is the namespace ID in hex
114+
if ':' in line:
115+
ns_id_hex = line.split(':')[-1].strip()
116+
# Convert hex string (e.g., '0x1') to integer
117+
try:
118+
namespaces.append(int(ns_id_hex, 16))
119+
except ValueError:
120+
# If not hex, try decimal
121+
try:
122+
namespaces.append(int(ns_id_hex))
123+
except ValueError:
124+
LOGGER.warning(f"Could not parse namespace ID from: {line}")
114125
return namespaces
115126

116127

@@ -280,18 +291,142 @@ def attach_ns(ns_id, controller_name, cont_id):
280291
"""
281292
attach the namespace_id to specified controller
282293
283-
:param ns_id: namespace ID
294+
:param ns_id: namespace ID (string or int)
284295
:param controller_name: controller name
285296
:param cont_id: controller_ID
286297
"""
298+
# Ensure ns_id is an integer for consistent comparison
299+
ns_id = int(ns_id)
287300
cmd = f"nvme attach-ns /dev/{controller_name} --namespace-id={ns_id} -controllers={cont_id}"
288301
if not process.run(cmd, shell=True, ignore_status=True):
289302
raise NvmeException("namespaces attach command failed")
290303
ns_rescan(controller_name)
304+
# Add delay to allow kernel to update namespace list after rescan
305+
time.sleep(2)
291306
if not is_ns_exists(controller_name, ns_id):
292307
raise NvmeException("namespaces attached but not listing")
293308

294309

310+
def get_supported_lba_formats(controller_name):
311+
"""
312+
Query and return supported LBA formats for the NVMe controller.
313+
314+
This function attempts to retrieve the LBA Format (LBAF) array from an
315+
existing namespace on the controller. The LBAF array is controller-wide,
316+
meaning all namespaces on the same controller share the same set of
317+
supported formats, though each namespace can select a different format.
318+
319+
:param controller_name: Name of the controller (e.g., 'nvme0')
320+
:return: List of dicts containing format details, each with keys:
321+
'index', 'block_size', 'metadata_size', 'relative_performance', 'valid'
322+
:rtype: list
323+
:raises: NvmeException if unable to query formats
324+
"""
325+
# Try to get formats from an existing namespace
326+
namespaces = get_current_ns_list(controller_name)
327+
328+
if namespaces:
329+
# Query first available namespace for LBAF array
330+
namespace = get_namespace_absolute_path(namespaces[0])
331+
cmd = f"nvme id-ns {namespace} -o json"
332+
try:
333+
result = process.run(cmd, shell=True, ignore_status=False, sudo=True)
334+
ns_data = json.loads(result.stdout_text)
335+
336+
# Extract and parse LBAF array
337+
lba_formats = []
338+
for idx, lbaf in enumerate(ns_data.get('lbafs', [])):
339+
# Check if format is valid (ds > 0 means valid data size)
340+
ds = lbaf.get('ds', 0)
341+
if ds > 0:
342+
lba_formats.append({
343+
'index': idx,
344+
'block_size': 2 ** ds, # Convert power-of-2 to actual size
345+
'metadata_size': lbaf.get('ms', 0),
346+
'relative_performance': lbaf.get('rp', 0),
347+
'valid': True
348+
})
349+
350+
if lba_formats:
351+
LOGGER.debug(f"Found {len(lba_formats)} valid LBA formats for {controller_name}")
352+
return lba_formats
353+
354+
except (process.CmdError, json.JSONDecodeError, KeyError) as e:
355+
LOGGER.warning(f"Failed to query LBA formats from namespace: {e}")
356+
357+
# Fallback: If no namespace exists or query failed, return common formats
358+
# Most NVMe devices support at least 512B and 4KB formats at indices 0 and 1
359+
# This is a safe assumption based on NVMe specification common implementations
360+
LOGGER.warning(
361+
f"No namespace found on {controller_name}, using common format assumptions. "
362+
f"This fallback assumes FLBAS indices 0 and 1 are valid with 512B and 4KB blocks respectively."
363+
)
364+
return [
365+
{'index': 0, 'block_size': 512, 'metadata_size': 0,
366+
'relative_performance': 0, 'valid': True},
367+
{'index': 1, 'block_size': 4096, 'metadata_size': 0,
368+
'relative_performance': 0, 'valid': True}
369+
]
370+
371+
372+
def get_optimal_flbas(controller_name):
373+
"""
374+
Determine the optimal FLBAS (Formatted LBA Size) index for namespace creation.
375+
376+
FLBAS is a namespace-specific field (bits 3:0) that selects which LBA format
377+
from the controller's LBAF array should be used. This function implements
378+
an intelligent selection strategy:
379+
380+
1. Prefer FLBAS index 0 if it's valid (most common default)
381+
2. If index 0 is invalid, select first format with metadata_size=0
382+
3. Among formats with no metadata, prefer common block sizes (512B, 4KB)
383+
384+
:param controller_name: Name of the controller (e.g., 'nvme0')
385+
:return: FLBAS index (0-15) to use for namespace creation
386+
:rtype: int
387+
:raises: NvmeException if no valid format is found
388+
"""
389+
try:
390+
formats = get_supported_lba_formats(controller_name)
391+
392+
if not formats:
393+
raise NvmeException(f"No valid LBA formats found for {controller_name}")
394+
395+
# Strategy 1: Try index 0 first (most common default)
396+
for fmt in formats:
397+
if fmt['index'] == 0 and fmt['valid']:
398+
LOGGER.info(f"Using FLBAS index 0 (block_size={fmt['block_size']}B) "
399+
f"for {controller_name}")
400+
return 0
401+
402+
# Strategy 2: Find first format with no metadata
403+
formats_no_metadata = [f for f in formats if f['metadata_size'] == 0]
404+
405+
if formats_no_metadata:
406+
# Prefer common block sizes: 512B or 4KB
407+
for preferred_size in [512, 4096]:
408+
for fmt in formats_no_metadata:
409+
if fmt['block_size'] == preferred_size:
410+
LOGGER.info(f"Using FLBAS index {fmt['index']} "
411+
f"(block_size={fmt['block_size']}B) for {controller_name}")
412+
return fmt['index']
413+
414+
# If no preferred size found, use first format without metadata
415+
selected = formats_no_metadata[0]
416+
LOGGER.info(f"Using FLBAS index {selected['index']} "
417+
f"(block_size={selected['block_size']}B) for {controller_name}")
418+
return selected['index']
419+
420+
# Strategy 3: Last resort - use first valid format even with metadata
421+
selected = formats[0]
422+
LOGGER.warning(f"All formats have metadata. Using FLBAS index {selected['index']} "
423+
f"(block_size={selected['block_size']}B, metadata={selected['metadata_size']}B)")
424+
return selected['index']
425+
426+
except Exception as e:
427+
raise NvmeException(f"Failed to determine optimal FLBAS for {controller_name}: {e}")
428+
429+
295430
def create_full_capacity_ns(controller_name, shared_ns=False):
296431
"""
297432
Creates one namespace with full capacity
@@ -304,19 +439,112 @@ def create_full_capacity_ns(controller_name, shared_ns=False):
304439
create_one_ns("1", controller_name, ns_size, shared_ns=shared_ns)
305440

306441

307-
def create_one_ns(ns_id, controller_name, ns_size, shared_ns=False):
308-
"""
309-
creates a single namespaces with given size and controller_id
310-
311-
:param ns_id: Namespace ID
312-
:param controller_name: name of the controller like nvme0/nvme1 etc..
313-
:param ns_size: Size of the namespace that is going to be created
314-
"""
315-
cmd = f"nvme create-ns /dev/{controller_name} --nsze={ns_size} --ncap={ns_size} --flbas=0 --dps=0"
442+
def create_one_ns(ns_id, controller_name, ns_size, shared_ns=False, flbas=0):
443+
"""
444+
Creates a single namespace with given size and controller_id.
445+
446+
This function supports dynamic FLBAS (Formatted LBA Size) selection with
447+
intelligent fallback behavior:
448+
449+
**FLBAS Selection Modes:**
450+
451+
1. **Default Mode (flbas=0)**: Backward compatible behavior
452+
- First attempts namespace creation with FLBAS=0
453+
- If FLBAS=0 fails, automatically detects optimal FLBAS and retries
454+
- Recommended for most use cases
455+
456+
2. **Auto-Detect Mode (flbas=-1)**: Skip FLBAS=0 attempt
457+
- Immediately detects and uses optimal FLBAS value
458+
- Useful for devices known to have non-standard FLBAS configurations
459+
- Saves one failed attempt on such devices
460+
461+
3. **Explicit Mode (flbas=1-15)**: Use specific FLBAS index
462+
- Uses the specified FLBAS value without auto-detection
463+
- No retry on failure
464+
- For advanced users who know their device's FLBAS requirements
465+
466+
:param ns_id: Namespace ID (typically 1-based)
467+
:param controller_name: Name of the controller like nvme0/nvme1 etc..
468+
:param ns_size: Size of the namespace in blocks (based on selected LBA format)
469+
:param shared_ns: Whether to create a shared namespace (default: False)
470+
:param flbas: FLBAS index to use:
471+
- 0 (default): Try FLBAS=0, auto-retry on failure
472+
- -1: Skip FLBAS=0, immediately use auto-detected optimal value
473+
- 1-15: Use explicit FLBAS index, no auto-detection
474+
:raises: NvmeException if namespace creation fails with all attempted FLBAS values
475+
476+
:example:
477+
# Standard usage (backward compatible)
478+
create_one_ns("1", "nvme0", 2097152)
479+
480+
# Force auto-detection for non-standard devices
481+
create_one_ns("1", "nvme0", 2097152, flbas=-1)
482+
483+
# Use explicit FLBAS value
484+
create_one_ns("1", "nvme0", 2097152, flbas=2)
485+
"""
486+
# Determine retry strategy based on flbas parameter
487+
# Only enable auto-detection fallback for default flbas=0
488+
should_retry_with_auto_detect = (flbas == 0)
489+
490+
# Handle explicit auto-detection request (flbas=-1)
491+
# This skips the FLBAS=0 attempt and goes straight to optimal detection
492+
if flbas == -1:
493+
try:
494+
flbas = get_optimal_flbas(controller_name)
495+
LOGGER.info(f"Auto-detected FLBAS={flbas} for namespace creation on {controller_name}")
496+
except NvmeException as e:
497+
LOGGER.error(f"Failed to auto-detect FLBAS: {e}")
498+
raise NvmeException(f"Cannot create namespace: FLBAS auto-detection failed: {e}")
499+
500+
# Build and execute namespace creation command
501+
cmd = f"nvme create-ns /dev/{controller_name} --nsze={ns_size} --ncap={ns_size} --flbas={flbas} --dps=0"
316502
if shared_ns:
317503
cmd = f"{cmd} -m 1"
318-
if process.system(cmd, shell=True, ignore_status=True):
319-
raise NvmeException(f"namespace create command failed {cmd}")
504+
505+
result = process.system(cmd, shell=True, ignore_status=True)
506+
507+
# Intelligent retry: If creation failed with default FLBAS=0, try auto-detection
508+
# This provides automatic fallback for devices with non-standard FLBAS configurations
509+
if result != 0 and should_retry_with_auto_detect:
510+
LOGGER.warning(f"Namespace creation failed with FLBAS=0 on {controller_name}")
511+
LOGGER.info("Attempting auto-detection of optimal FLBAS value...")
512+
513+
try:
514+
optimal_flbas = get_optimal_flbas(controller_name)
515+
516+
# Only retry if optimal FLBAS is different from what we tried
517+
if optimal_flbas != 0:
518+
LOGGER.info(f"Retrying with auto-detected FLBAS={optimal_flbas}")
519+
cmd = f"nvme create-ns /dev/{controller_name} --nsze={ns_size} --ncap={ns_size} --flbas={optimal_flbas} --dps=0"
520+
if shared_ns:
521+
cmd = f"{cmd} -m 1"
522+
523+
result = process.system(cmd, shell=True, ignore_status=True)
524+
525+
if result == 0:
526+
LOGGER.info(f"Namespace creation succeeded with FLBAS={optimal_flbas}")
527+
flbas = optimal_flbas # Update for logging
528+
else:
529+
raise NvmeException(
530+
f"Namespace creation failed with both FLBAS=0 and auto-detected FLBAS={optimal_flbas}. "
531+
f"Command: {cmd}"
532+
)
533+
else:
534+
raise NvmeException(
535+
f"Namespace creation failed with FLBAS=0 and auto-detection also suggests FLBAS=0. "
536+
f"Command: {cmd}"
537+
)
538+
except NvmeException as e:
539+
raise NvmeException(f"Namespace creation failed: {e}")
540+
elif result != 0:
541+
# Failed with user-specified FLBAS (not 0), don't retry
542+
raise NvmeException(
543+
f"Namespace create command failed with FLBAS={flbas}. "
544+
f"Command: {cmd}. Exit code: {result}"
545+
)
546+
547+
# Attach namespace to controller(s)
320548
cont_id = get_controller_id(controller_name)
321549
if shared_ns:
322550
ctrls = get_alternate_controller_name(controller_name)

avocado/utils/pci.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,10 @@ def get_slot_from_sysfs(full_pci_address):
192192
193193
:return: Removed port related details using re, only returns till
194194
physical slot of the adapter.
195+
Examples::
196+
197+
U78CC.001.FZHAK92-P2-C3
198+
U50EE.001.WZS0011-P3-C20-R2
195199
"""
196200
if not os.path.isfile(f"/sys/bus/pci/devices/{full_pci_address}/devspec"):
197201
return None
@@ -201,7 +205,7 @@ def get_slot_from_sysfs(full_pci_address):
201205
if not os.path.isfile(f"/proc/device-tree/{devspec}/ibm,loc-code"):
202206
return None
203207
slot = genio.read_file(f"/proc/device-tree/{devspec}/ibm,loc-code")
204-
slot_ibm = re.match(r"((\w+)[.])+(\w+)-[PC(\d+)-]*C(\d+)", slot)
208+
slot_ibm = re.match(r"((\w+)[.])+(\w+)-[PC(\d+)-]*C(\d+)(?:-R\d+)?", slot)
205209
if slot_ibm:
206210
return slot_ibm.group()
207211
slot_openpower = re.match(r"(\w+)[\s]*(\w+)(\d*)", slot)

0 commit comments

Comments
 (0)