33from __future__ import annotations
44
55from collections .abc import Mapping
6- from dataclasses import dataclass
6+ from dataclasses import dataclass , replace
77from enum import Enum
88from typing import Any , cast
99
@@ -133,6 +133,14 @@ class _RangeProfile:
133133 "SD" ,
134134)
135135
136+ _MAX_RUNTIME_RANGE_PROBE_COUNT = 1_048_576
137+ _ZR_RUNTIME_FAMILIES = {
138+ SlmpDeviceRangeFamily .QCpu ,
139+ SlmpDeviceRangeFamily .LCpu ,
140+ SlmpDeviceRangeFamily .QnU ,
141+ SlmpDeviceRangeFamily .QnUDV ,
142+ }
143+
136144_ROWS : dict [str , _RangeRow ] = {
137145 "X" : _RangeRow (SlmpDeviceRangeCategory .Bit , (("X" , True ),), SlmpDeviceRangeNotation .Base16 ),
138146 "Y" : _RangeRow (SlmpDeviceRangeCategory .Bit , (("Y" , True ),), SlmpDeviceRangeNotation .Base16 ),
@@ -411,7 +419,7 @@ def _undefined(notes: str) -> _RangeValueSpec:
411419 "LT" : _unsupported ("Not supported on LCPU." ),
412420 "LST" : _unsupported ("Not supported on LCPU." ),
413421 "LC" : _unsupported ("Not supported on LCPU." ),
414- "Z" : _word ( 305 , "SD305" , "Requires ZZ = FFFFh for the reported upper bound. " ),
422+ "Z" : _fixed ( 20 , "Fixed family limit " ),
415423 "LZ" : _unsupported ("Not supported on LCPU." ),
416424 "ZR" : _dword (306 , "SD306-SD307 (32-bit)" ),
417425 "RD" : _unsupported ("Not supported on LCPU." ),
@@ -443,7 +451,7 @@ def _undefined(notes: str) -> _RangeValueSpec:
443451 "LT" : _unsupported ("Not supported on QnU." ),
444452 "LST" : _unsupported ("Not supported on QnU." ),
445453 "LC" : _unsupported ("Not supported on QnU." ),
446- "Z" : _word ( 305 , "SD305" , "Requires ZZ = FFFFh for the reported upper bound. " ),
454+ "Z" : _fixed ( 20 , "Fixed family limit " ),
447455 "LZ" : _unsupported ("Not supported on QnU." ),
448456 "ZR" : _dword (306 , "SD306-SD307 (32-bit)" ),
449457 "RD" : _unsupported ("Not supported on QnU." ),
@@ -475,7 +483,7 @@ def _undefined(notes: str) -> _RangeValueSpec:
475483 "LT" : _unsupported ("Not supported on QnUDV." ),
476484 "LST" : _unsupported ("Not supported on QnUDV." ),
477485 "LC" : _unsupported ("Not supported on QnUDV." ),
478- "Z" : _word ( 305 , "SD305" , "Requires ZZ = FFFFh for the reported upper bound. " ),
486+ "Z" : _fixed ( 20 , "Fixed family limit " ),
479487 "LZ" : _unsupported ("Not supported on QnUDV." ),
480488 "ZR" : _dword (306 , "SD306-SD307 (32-bit)" ),
481489 "RD" : _unsupported ("Not supported on QnUDV." ),
@@ -568,7 +576,8 @@ def read_device_range_catalog_for_family_sync(
568576 client .read_devices (DeviceRef ("SD" , profile .register_start ), profile .register_count , bit_unit = False ),
569577 )
570578 registers = {profile .register_start + index : int (value ) for index , value in enumerate (words )}
571- return build_device_range_catalog_for_family (normalized_family , registers )
579+ catalog = build_device_range_catalog_for_family (normalized_family , registers )
580+ return _resolve_runtime_limits_sync (client , catalog )
572581
573582
574583async def read_device_range_catalog_for_family (
@@ -584,7 +593,164 @@ async def read_device_range_catalog_for_family(
584593 await client .read_devices (DeviceRef ("SD" , profile .register_start ), profile .register_count , bit_unit = False ),
585594 )
586595 registers = {profile .register_start + index : int (value ) for index , value in enumerate (words )}
587- return build_device_range_catalog_for_family (normalized_family , registers )
596+ catalog = build_device_range_catalog_for_family (normalized_family , registers )
597+ return await _resolve_runtime_limits_async (client , catalog )
598+
599+
600+ def _resolve_runtime_limits_sync (client : Any , catalog : SlmpDeviceRangeCatalog ) -> SlmpDeviceRangeCatalog :
601+ if catalog .family not in _ZR_RUNTIME_FAMILIES :
602+ return catalog
603+
604+ if catalog .family is SlmpDeviceRangeFamily .QCpu :
605+ z_count = 16 if _can_read_one_word_sync (client , "Z15" ) else 10
606+ catalog = _replace_fixed_point_count (
607+ catalog ,
608+ "Z" ,
609+ z_count ,
610+ "Runtime access check" ,
611+ "QCPU Z register count is selected by probing Z15." ,
612+ )
613+
614+ zr_count = _resolve_readable_point_count_sync (client , "ZR" )
615+ catalog = _replace_fixed_point_count (
616+ catalog ,
617+ "ZR" ,
618+ zr_count ,
619+ "Runtime access check" ,
620+ "ZR register count is selected by probing readable ZR addresses." ,
621+ )
622+ return _replace_fixed_point_count (
623+ catalog ,
624+ "R" ,
625+ min (zr_count , 32768 ),
626+ "Runtime access check" ,
627+ "R register count follows the probed ZR count and is capped at R32767." ,
628+ )
629+
630+
631+ async def _resolve_runtime_limits_async (client : Any , catalog : SlmpDeviceRangeCatalog ) -> SlmpDeviceRangeCatalog :
632+ if catalog .family not in _ZR_RUNTIME_FAMILIES :
633+ return catalog
634+
635+ if catalog .family is SlmpDeviceRangeFamily .QCpu :
636+ z_count = 16 if await _can_read_one_word_async (client , "Z15" ) else 10
637+ catalog = _replace_fixed_point_count (
638+ catalog ,
639+ "Z" ,
640+ z_count ,
641+ "Runtime access check" ,
642+ "QCPU Z register count is selected by probing Z15." ,
643+ )
644+
645+ zr_count = await _resolve_readable_point_count_async (client , "ZR" )
646+ catalog = _replace_fixed_point_count (
647+ catalog ,
648+ "ZR" ,
649+ zr_count ,
650+ "Runtime access check" ,
651+ "ZR register count is selected by probing readable ZR addresses." ,
652+ )
653+ return _replace_fixed_point_count (
654+ catalog ,
655+ "R" ,
656+ min (zr_count , 32768 ),
657+ "Runtime access check" ,
658+ "R register count follows the probed ZR count and is capped at R32767." ,
659+ )
660+
661+
662+ def _resolve_readable_point_count_sync (client : Any , device : str ) -> int :
663+ if not _can_read_one_word_sync (client , f"{ device } 0" ):
664+ return 0
665+
666+ upper_limit = _MAX_RUNTIME_RANGE_PROBE_COUNT - 1
667+ low = 0
668+ high = 1
669+ while high < upper_limit and _can_read_one_word_sync (client , f"{ device } { high } " ):
670+ low = high
671+ high = min (upper_limit , (high * 2 ) + 1 )
672+
673+ if high == upper_limit and _can_read_one_word_sync (client , f"{ device } { high } " ):
674+ return _MAX_RUNTIME_RANGE_PROBE_COUNT
675+
676+ left = low + 1
677+ right = high - 1
678+ while left <= right :
679+ mid = left + ((right - left ) // 2 )
680+ if _can_read_one_word_sync (client , f"{ device } { mid } " ):
681+ low = mid
682+ left = mid + 1
683+ else :
684+ right = mid - 1
685+
686+ return low + 1
687+
688+
689+ async def _resolve_readable_point_count_async (client : Any , device : str ) -> int :
690+ if not await _can_read_one_word_async (client , f"{ device } 0" ):
691+ return 0
692+
693+ upper_limit = _MAX_RUNTIME_RANGE_PROBE_COUNT - 1
694+ low = 0
695+ high = 1
696+ while high < upper_limit and await _can_read_one_word_async (client , f"{ device } { high } " ):
697+ low = high
698+ high = min (upper_limit , (high * 2 ) + 1 )
699+
700+ if high == upper_limit and await _can_read_one_word_async (client , f"{ device } { high } " ):
701+ return _MAX_RUNTIME_RANGE_PROBE_COUNT
702+
703+ left = low + 1
704+ right = high - 1
705+ while left <= right :
706+ mid = left + ((right - left ) // 2 )
707+ if await _can_read_one_word_async (client , f"{ device } { mid } " ):
708+ low = mid
709+ left = mid + 1
710+ else :
711+ right = mid - 1
712+
713+ return low + 1
714+
715+
716+ def _can_read_one_word_sync (client : Any , address : str ) -> bool :
717+ try :
718+ client .read_devices (address , 1 , bit_unit = False )
719+ return True
720+ except SlmpError :
721+ return False
722+
723+
724+ async def _can_read_one_word_async (client : Any , address : str ) -> bool :
725+ try :
726+ await client .read_devices (address , 1 , bit_unit = False )
727+ return True
728+ except SlmpError :
729+ return False
730+
731+
732+ def _replace_fixed_point_count (
733+ catalog : SlmpDeviceRangeCatalog ,
734+ device : str ,
735+ point_count : int ,
736+ source : str ,
737+ note : str ,
738+ ) -> SlmpDeviceRangeCatalog :
739+ upper_bound = None if point_count <= 0 else point_count - 1
740+ entries = [
741+ replace (
742+ entry ,
743+ upper_bound = upper_bound ,
744+ point_count = point_count ,
745+ address_range = _format_address_range (entry .device , entry .notation , upper_bound ),
746+ source = source ,
747+ notes = note if not entry .notes else f"{ entry .notes } { note } " ,
748+ )
749+ if entry .device == device
750+ else entry
751+ for entry in catalog .entries
752+ ]
753+ return replace (catalog , entries = entries )
588754
589755
590756def _evaluate_point_count (spec : _RangeValueSpec , registers : Mapping [int , int ]) -> int | None :
0 commit comments