Skip to content

Commit 42a9a16

Browse files
committed
[cmis] Separate VDM basic and statistic observables
<!-- Provide a general summary of your changes in the Title above --> This PR must be merged along with sonic-net/sonic-platform-daemons#750 #### Description <!-- Describe your changes in detail --> Currently, the CMIS API treats all VDM observables uniformly, requiring freeze operations for all reads. However, CMIS specification distinguishes between: - Basic observables (instantaneous values like current BER, temperature) - Statistic observables (min/max/avg values that require freeze to capture consistently) This prevents optimization in xcvrd, which currently requires freeze operation even for modules that only support basic observables (where freeze may not be supported or necessary). Additionally, the API performs repeated EEPROM reads to check VDM support status, even though this capability bit is static for a given module. #### Motivation and Context <!-- Why is this change required? What problem does it solve? If this pull request closes/resolves an open Issue, make sure you include the text "fixes #xxxx", "closes #xxxx" or "resolves #xxxx" here --> 1. VDM_TYPE Enhancement (codes/public/cmis.py) ---------------------------------------------- - Added 4th element to VDM_TYPE tuples: 'B' (basic) or 'S' (statistic) - Serves as single source of truth for observable classification 2. Observable Type Constants (api/public/cmisVDM.py) -------------------------------------------------- - VDM_OBSERVABLE_BASIC = 0x1 - VDM_OBSERVABLE_STATISTIC = 0x2 - VDM_OBSERVABLE_ALL = 0x3 3. New CmisApi Methods (api/public/cmis.py) ----------------------------------------- a) get_transceiver_vdm_real_value_basic() - Reads only basic (instantaneous) VDM observables - No freeze required b) get_transceiver_vdm_real_value_statistic() - Reads only statistic (min/max/avg) VDM observables - Requires freeze operation (caller responsible) c) is_vdm_statistic_supported() - Checks if module supports VDM statistic observables - Uses @read_only_cached_api_return for performance 4. Enhanced get_vdm_page() (api/public/cmisVDM.py) ------------------------------------------------ - Added optional observable_type parameter - Filters VDM observables based on type (basic/statistic/all) 5. Performance Optimization ------------------------- - Added @read_only_cached_api_return to is_transceiver_vdm_supported() - Prevents repeated EEPROM access for static capability bits #### How Has This Been Tested? <!-- Please describe in detail how you tested your changes. Include details of your testing environment, and the tests you ran to see how your change affects other areas of the code, etc. --> Please refer to the test details in sonic-net/sonic-platform-daemons#750 #### Additional Information (Optional) MSFT ADO - 36798630
1 parent e4ad2cd commit 42a9a16

File tree

8 files changed

+481
-79
lines changed

8 files changed

+481
-79
lines changed

sonic_platform_base/sonic_xcvr/api/public/cmis.py

Lines changed: 100 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,27 @@ def get_cdb_fw_handler(self):
167167
def _get_vdm_key_to_db_prefix_map(self):
168168
return CMIS_VDM_KEY_TO_DB_PREFIX_KEY_MAP
169169

170+
def _get_vdm_key_to_db_prefix_map_by_observable_type(self, observable_type):
171+
"""
172+
Returns the VDM key-to-DB-prefix map filtered by observable type,
173+
using the 'B'/'S' classification from VDM_TYPE as the single source of truth.
174+
175+
Args:
176+
observable_type: 'B' for basic (instantaneous), 'S' for statistic (min/max/avg)
177+
178+
Returns:
179+
dict: Filtered subset of _get_vdm_key_to_db_prefix_map()
180+
"""
181+
vdm_type_dict = self.xcvr_eeprom.mem_map.codes.VDM_TYPE
182+
matching_names = {
183+
info[0] for info in vdm_type_dict.values()
184+
if len(info) > 3 and info[3] == observable_type
185+
}
186+
return {
187+
k: v for k, v in self._get_vdm_key_to_db_prefix_map().items()
188+
if k in matching_names
189+
}
190+
170191
@staticmethod
171192
def _strip_str(val):
172193
return val.rstrip() if isinstance(val, str) else val
@@ -1678,20 +1699,45 @@ def is_cdb_supported(self):
16781699

16791700
return False
16801701

1702+
@read_only_cached_api_return
16811703
def is_transceiver_vdm_supported(self):
16821704
'''
16831705
This function returns whether VDM is supported
16841706
'''
16851707
return self.vdm is not None and self.xcvr_eeprom.read(consts.VDM_SUPPORTED)
16861708

1687-
def get_vdm(self, field_option=None):
1709+
@read_only_cached_api_return
1710+
def is_vdm_statistic_supported(self):
1711+
'''
1712+
This function returns whether the optic advertises any VDM statistic
1713+
observable types (min/max/avg) in its VDM descriptor pages.
1714+
1715+
Returns:
1716+
bool: True if at least one statistic observable type is advertised, False otherwise.
16881717
'''
1689-
This function returns all the VDM items, including real time monitor value, threholds and flags
1718+
if self.vdm is None:
1719+
return False
1720+
return self.vdm.is_vdm_statistic_supported()
1721+
1722+
def get_vdm(self, field_option=None, observable_type=None):
16901723
'''
1724+
This function returns all the VDM items, including real time monitor value, thresholds and flags
1725+
1726+
Args:
1727+
field_option: Bitmask to select real value, threshold, and/or flag fields.
1728+
Defaults to ALL_FIELD (all fields).
1729+
observable_type: Bitmask to filter by observable type.
1730+
VDM_OBSERVABLE_BASIC (0x1) for basic (instantaneous) types,
1731+
VDM_OBSERVABLE_STATISTIC (0x2) for statistic (min/max/avg) types,
1732+
VDM_OBSERVABLE_ALL (0x3) for both. Defaults to VDM_OBSERVABLE_ALL.
1733+
'''
1734+
if self.vdm is None:
1735+
return {}
16911736
if field_option is None:
16921737
field_option = self.vdm.ALL_FIELD
1693-
vdm = self.vdm.get_vdm_allpage(field_option) if self.vdm is not None else {}
1694-
return vdm
1738+
if observable_type is None:
1739+
observable_type = self.vdm.VDM_OBSERVABLE_ALL
1740+
return self.vdm.get_vdm_allpage(field_option, observable_type) or {}
16951741

16961742
def get_module_firmware_fault_state_changed(self):
16971743
'''
@@ -2382,7 +2428,20 @@ def get_transceiver_loopback(self):
23822428

23832429
def get_transceiver_vdm_real_value(self):
23842430
"""
2385-
Retrieves VDM real value for this xcvr
2431+
Retrieves all VDM real values (both basic and statistic) for this xcvr.
2432+
This is a convenience method that merges basic and statistic results.
2433+
2434+
Returns:
2435+
Dictionary with all VDM real values.
2436+
"""
2437+
result = {}
2438+
result.update(self.get_transceiver_vdm_real_value_basic())
2439+
result.update(self.get_transceiver_vdm_real_value_statistic())
2440+
return result
2441+
2442+
def get_transceiver_vdm_real_value_basic(self):
2443+
"""
2444+
Retrieves basic VDM real values for this xcvr.
23862445
23872446
Returns:
23882447
A dict containing the following keys/values :
@@ -2394,21 +2453,9 @@ def get_transceiver_vdm_real_value(self):
23942453
esnr_host_input{lane_num} = FLOAT ; eSNR value in dB for host input
23952454
pam4_level_transition_media_input{lane_num} = FLOAT ; PAM4 level transition parameter in dB for media input
23962455
pam4_level_transition_host_input{lane_num} = FLOAT ; PAM4 level transition parameter in dB for host input
2397-
prefec_ber_min_media_input{lane_num} = FLOAT ; Pre-FEC BER minimum value for media input
2398-
prefec_ber_max_media_input{lane_num} = FLOAT ; Pre-FEC BER maximum value for media input
2399-
prefec_ber_avg_media_input{lane_num} = FLOAT ; Pre-FEC BER average value for media input
24002456
prefec_ber_curr_media_input{lane_num} = FLOAT ; Pre-FEC BER current value for media input
2401-
prefec_ber_min_host_input{lane_num} = FLOAT ; Pre-FEC BER minimum value for host input
2402-
prefec_ber_max_host_input{lane_num} = FLOAT ; Pre-FEC BER maximum value for host input
2403-
prefec_ber_avg_host_input{lane_num} = FLOAT ; Pre-FEC BER average value for host input
24042457
prefec_ber_curr_host_input{lane_num} = FLOAT ; Pre-FEC BER current value for host input
2405-
errored_frames_min_media_input{lane_num} = FLOAT ; Errored frames minimum value for media input
2406-
errored_frames_max_media_input{lane_num} = FLOAT ; Errored frames maximum value for media input
2407-
errored_frames_avg_media_input{lane_num} = FLOAT ; Errored frames average value for media input
24082458
errored_frames_curr_media_input{lane_num} = FLOAT ; Errored frames current value for media input
2409-
errored_frames_min_host_input{lane_num} = FLOAT ; Errored frames minimum value for host input
2410-
errored_frames_max_host_input{lane_num} = FLOAT ; Errored frames maximum value for host input
2411-
errored_frames_avg_host_input{lane_num} = FLOAT ; Errored frames average value for host input
24122459
errored_frames_curr_host_input{lane_num} = FLOAT ; Errored frames current value for host input
24132460
24142461
;C-CMIS specific fields
@@ -2433,8 +2480,40 @@ def get_transceiver_vdm_real_value(self):
24332480
========================================================================
24342481
"""
24352482
vdm_real_value_dict = dict()
2436-
vdm_raw_dict = self.get_vdm(self.vdm.VDM_REAL_VALUE)
2437-
for vdm_observable_type, db_key_name_prefix in self._get_vdm_key_to_db_prefix_map().items():
2483+
vdm_raw_dict = self.get_vdm(self.vdm.VDM_REAL_VALUE, self.vdm.VDM_OBSERVABLE_BASIC)
2484+
for vdm_observable_type, db_key_name_prefix in self._get_vdm_key_to_db_prefix_map_by_observable_type('B').items():
2485+
for lane in range(1, self.NUM_CHANNELS + 1):
2486+
db_key_name = f"{db_key_name_prefix}{lane}"
2487+
self._update_vdm_dict(vdm_real_value_dict, db_key_name, vdm_raw_dict, vdm_observable_type,
2488+
VdmSubtypeIndex.VDM_SUBTYPE_REAL_VALUE, lane)
2489+
return vdm_real_value_dict
2490+
2491+
def get_transceiver_vdm_real_value_statistic(self):
2492+
"""
2493+
Retrieves statistic (min/max/avg) VDM real values for this xcvr.
2494+
2495+
Returns:
2496+
A dict containing the following keys/values :
2497+
========================================================================
2498+
key = TRANSCEIVER_VDM_REAL_VALUE|ifname ; information module VDM sample on port
2499+
; field = value
2500+
prefec_ber_min_media_input{lane_num} = FLOAT ; Pre-FEC BER minimum value for media input
2501+
prefec_ber_max_media_input{lane_num} = FLOAT ; Pre-FEC BER maximum value for media input
2502+
prefec_ber_avg_media_input{lane_num} = FLOAT ; Pre-FEC BER average value for media input
2503+
prefec_ber_min_host_input{lane_num} = FLOAT ; Pre-FEC BER minimum value for host input
2504+
prefec_ber_max_host_input{lane_num} = FLOAT ; Pre-FEC BER maximum value for host input
2505+
prefec_ber_avg_host_input{lane_num} = FLOAT ; Pre-FEC BER average value for host input
2506+
errored_frames_min_media_input{lane_num} = FLOAT ; Errored frames minimum value for media input
2507+
errored_frames_max_media_input{lane_num} = FLOAT ; Errored frames maximum value for media input
2508+
errored_frames_avg_media_input{lane_num} = FLOAT ; Errored frames average value for media input
2509+
errored_frames_min_host_input{lane_num} = FLOAT ; Errored frames minimum value for host input
2510+
errored_frames_max_host_input{lane_num} = FLOAT ; Errored frames maximum value for host input
2511+
errored_frames_avg_host_input{lane_num} = FLOAT ; Errored frames average value for host input
2512+
========================================================================
2513+
"""
2514+
vdm_real_value_dict = dict()
2515+
vdm_raw_dict = self.get_vdm(self.vdm.VDM_REAL_VALUE, self.vdm.VDM_OBSERVABLE_STATISTIC)
2516+
for vdm_observable_type, db_key_name_prefix in self._get_vdm_key_to_db_prefix_map_by_observable_type('S').items():
24382517
for lane in range(1, self.NUM_CHANNELS + 1):
24392518
db_key_name = f"{db_key_name_prefix}{lane}"
24402519
self._update_vdm_dict(vdm_real_value_dict, db_key_name, vdm_raw_dict, vdm_observable_type,
@@ -2495,7 +2574,7 @@ def get_transceiver_vdm_thresholds(self):
24952574
rxsigpower_xxx{lane_num} = FLOAT ; rx signal power in dbm (high/low alarm/warning) ========================================================================
24962575
"""
24972576
vdm_thresholds_dict = dict()
2498-
vdm_raw_dict = self.get_vdm(self.vdm.VDM_THRESHOLD)
2577+
vdm_raw_dict = self.get_vdm(self.vdm.VDM_THRESHOLD, self.vdm.VDM_OBSERVABLE_ALL)
24992578
for vdm_observable_type, db_key_name_prefix in self._get_vdm_key_to_db_prefix_map().items():
25002579
for lane in range(1, self.NUM_CHANNELS + 1):
25012580
for vdm_threshold_type in range(VdmSubtypeIndex.VDM_SUBTYPE_HALARM_THRESHOLD.value, VdmSubtypeIndex.VDM_SUBTYPE_LWARN_THRESHOLD.value + 1):
@@ -2562,7 +2641,7 @@ def get_transceiver_vdm_flags(self):
25622641
rxsigpower_xxx{lane_num} = FLOAT ; rx signal power in dbm (high/low alarm/warning flag)
25632642
"""
25642643
vdm_flags_dict = dict()
2565-
vdm_raw_dict = self.get_vdm(self.vdm.VDM_FLAG)
2644+
vdm_raw_dict = self.get_vdm(self.vdm.VDM_FLAG, self.vdm.VDM_OBSERVABLE_ALL)
25662645
for vdm_observable_type, db_key_name_prefix in self._get_vdm_key_to_db_prefix_map().items():
25672646
for lane in range(1, self.NUM_CHANNELS + 1):
25682647
for vdm_flag_type in range(VdmSubtypeIndex.VDM_SUBTYPE_HALARM_FLAG.value, VdmSubtypeIndex.VDM_SUBTYPE_LWARN_FLAG.value + 1):

sonic_platform_base/sonic_xcvr/api/public/cmisVDM.py

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ class CmisVdmApi(XcvrApi):
2424
VDM_FLAG = 0x4
2525
ALL_FIELD = 0xff
2626

27+
# Observable type filters
28+
VDM_OBSERVABLE_BASIC = 0x1 # Basic (instantaneous) observable types
29+
VDM_OBSERVABLE_STATISTIC = 0x2 # Statistic (min/max/avg) observable types
30+
VDM_OBSERVABLE_ALL = 0x3 # Both basic and statistic
31+
2732
def __init__(self, xcvr_eeprom):
2833
super(CmisVdmApi, self).__init__(xcvr_eeprom)
2934

@@ -36,7 +41,7 @@ def get_F16(self, value):
3641
result = mantissa*10**(scale_exponent-24)
3742
return result
3843

39-
def get_vdm_page(self, page, VDM_flag_page, field_option=ALL_FIELD):
44+
def get_vdm_page(self, page, VDM_flag_page, field_option=ALL_FIELD, observable_type=VDM_OBSERVABLE_ALL):
4045
'''
4146
This function returns VDM items from a specific VDM page.
4247
Output format is a dictionary. Key is observable type; value is a dictionary.
@@ -52,6 +57,15 @@ def get_vdm_page(self, page, VDM_flag_page, field_option=ALL_FIELD):
5257
vdm_high_warn_flag,
5358
vdm_low_warn_flag
5459
]
60+
61+
Args:
62+
page: VDM descriptor page (0x20-0x23)
63+
VDM_flag_page: Raw flag page data or None
64+
field_option: Bitmask to select real value, threshold, and/or flag fields
65+
observable_type: Bitmask to filter by observable type.
66+
VDM_OBSERVABLE_BASIC (0x1) for basic (instantaneous) types,
67+
VDM_OBSERVABLE_STATISTIC (0x2) for statistic (min/max/avg) types,
68+
VDM_OBSERVABLE_ALL (0x3) for both.
5569
'''
5670
if page not in [0x20, 0x21, 0x22, 0x23]:
5771
raise ValueError('Page not in VDM Descriptor range!')
@@ -75,6 +89,14 @@ def get_vdm_page(self, page, VDM_flag_page, field_option=ALL_FIELD):
7589
continue
7690

7791
vdm_info_dict = VDM_TYPE_DICT[typeID]
92+
93+
# Filter by observable type (basic vs statistic)
94+
vdm_obs_type = vdm_info_dict[3] if len(vdm_info_dict) > 3 else 'B'
95+
if vdm_obs_type == 'B' and not (observable_type & self.VDM_OBSERVABLE_BASIC):
96+
continue
97+
if vdm_obs_type == 'S' and not (observable_type & self.VDM_OBSERVABLE_STATISTIC):
98+
continue
99+
78100
thrshID = VDM_thresholdID[index]
79101
vdm_type = vdm_info_dict[0]
80102
vdm_format = vdm_info_dict[1]
@@ -178,7 +200,7 @@ def get_vdm_page(self, page, VDM_flag_page, field_option=ALL_FIELD):
178200
vdm_low_warn_flag]
179201
return vdm_Page_data
180202

181-
def get_vdm_allpage(self, field_option=ALL_FIELD ):
203+
def get_vdm_allpage(self, field_option=ALL_FIELD, observable_type=VDM_OBSERVABLE_ALL):
182204
'''
183205
This function returns VDM items from all advertised VDM pages.
184206
Output format is a dictionary. Key is observable type; value is a dictionary.
@@ -194,6 +216,13 @@ def get_vdm_allpage(self, field_option=ALL_FIELD ):
194216
vdm_high_warn_flag,
195217
vdm_low_warn_flag
196218
]
219+
220+
Args:
221+
field_option: Bitmask to select real value, threshold, and/or flag fields
222+
observable_type: Bitmask to filter by observable type.
223+
VDM_OBSERVABLE_BASIC (0x1) for basic (instantaneous) types,
224+
VDM_OBSERVABLE_STATISTIC (0x2) for statistic (min/max/avg) types,
225+
VDM_OBSERVABLE_ALL (0x3) for both.
197226
'''
198227
vdm_pages_supported = self.xcvr_eeprom.read(consts.VDM_SUPPORTED)
199228
if not vdm_pages_supported:
@@ -210,6 +239,37 @@ def get_vdm_allpage(self, field_option=ALL_FIELD ):
210239
vdm_flag_page = None
211240

212241
for page in range(VDM_START_PAGE, VDM_START_PAGE + vdm_groups_supported_raw + 1):
213-
vdm_current_page = self.get_vdm_page(page, vdm_flag_page, field_option)
242+
vdm_current_page = self.get_vdm_page(page, vdm_flag_page, field_option, observable_type)
214243
vdm.update(vdm_current_page)
215244
return vdm
245+
246+
def is_vdm_statistic_supported(self):
247+
'''
248+
Checks whether the optic advertises any VDM statistic observable types
249+
by scanning the VDM descriptor pages for type IDs classified as 'S' in VDM_TYPE.
250+
251+
Returns:
252+
bool: True if at least one statistic observable type is advertised, False otherwise.
253+
'''
254+
vdm_pages_supported = self.xcvr_eeprom.read(consts.VDM_SUPPORTED)
255+
if not vdm_pages_supported:
256+
return False
257+
vdm_groups_supported_raw = self.xcvr_eeprom.read(consts.VDM_SUPPORTED_PAGE)
258+
if vdm_groups_supported_raw is None:
259+
return False
260+
261+
VDM_START_PAGE = 0x20
262+
VDM_TYPE_DICT = self.xcvr_eeprom.mem_map.codes.VDM_TYPE
263+
264+
for page in range(VDM_START_PAGE, VDM_START_PAGE + vdm_groups_supported_raw + 1):
265+
vdm_descriptor = self.xcvr_eeprom.read_raw(page * PAGE_SIZE + PAGE_OFFSET, PAGE_SIZE)
266+
if not vdm_descriptor:
267+
continue
268+
# Odd addresses contain the VDM observable type IDs
269+
vdm_typeIDs = vdm_descriptor[1::2]
270+
for typeID in vdm_typeIDs:
271+
if typeID in VDM_TYPE_DICT:
272+
info = VDM_TYPE_DICT[typeID]
273+
if len(info) > 3 and info[3] == 'S':
274+
return True
275+
return False

sonic_platform_base/sonic_xcvr/api/xcvr_api.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,13 +222,31 @@ def is_transceiver_vdm_supported(self):
222222
"""
223223
raise NotImplementedError
224224

225+
def is_vdm_statistic_supported(self):
226+
"""
227+
Retrieves whether the optic advertises any VDM statistic observables (applicable for CMIS and C-CMIS)
228+
"""
229+
raise NotImplementedError
230+
225231
def get_transceiver_vdm_real_value(self):
226232
"""
227-
Retrieves VDM real (sample) values for this xcvr (applicable for CMIS and C-CMIS)
233+
Retrieves all VDM real (sample) values for this xcvr (applicable for CMIS and C-CMIS)
228234
Specifically, it retrieves sample data from pages 24h to 27h
229235
"""
230236
raise NotImplementedError
231237

238+
def get_transceiver_vdm_real_value_basic(self):
239+
"""
240+
Retrieves basic (instantaneous) VDM real values for this xcvr (applicable for CMIS and C-CMIS)
241+
"""
242+
raise NotImplementedError
243+
244+
def get_transceiver_vdm_real_value_statistic(self):
245+
"""
246+
Retrieves statistic (min/max/avg) VDM real values for this xcvr (applicable for CMIS and C-CMIS)
247+
"""
248+
raise NotImplementedError
249+
232250
def get_transceiver_vdm_thresholds(self):
233251
"""
234252
Retrieves VDM thresholds for this xcvr (applicable for CMIS and C-CMIS)

0 commit comments

Comments
 (0)