From 77b6bc39bebbd5fbe205da4e120a19ecbad358e9 Mon Sep 17 00:00:00 2001
From: Aravind Mani <53524901+aravindmani-1@users.noreply.github.com>
Date: Thu, 7 Oct 2021 22:47:36 -0700
Subject: [PATCH] DellEMC: Fix z9332f low power mode issue (#8693)

---
 .../plugins/sfputil.py                        | 177 +++++++++++++-----
 .../z9332f/sonic_platform/sfp.py              |  42 +++--
 2 files changed, 166 insertions(+), 53 deletions(-)

diff --git a/device/dell/x86_64-dellemc_z9332f_d1508-r0/plugins/sfputil.py b/device/dell/x86_64-dellemc_z9332f_d1508-r0/plugins/sfputil.py
index 9ac14d83d7..9cb9351d82 100644
--- a/device/dell/x86_64-dellemc_z9332f_d1508-r0/plugins/sfputil.py
+++ b/device/dell/x86_64-dellemc_z9332f_d1508-r0/plugins/sfputil.py
@@ -2,13 +2,12 @@
 #
 # Platform-specific SFP transceiver interface for SONiC
 #
-
 try:
-    import struct
     import sys
     import getopt
     import time
     import select
+    import io
     from sonic_sfp.sfputilbase import SfpUtilBase
     from os import *
     from mmap import *
@@ -19,6 +18,25 @@ except ImportError as e:
 # from xcvrd
 SFP_STATUS_REMOVED = '0'
 SFP_STATUS_INSERTED = '1'
+MEDIA_TYPE_OFFSET = 0
+MEDIA_TYPE_WIDTH = 1
+QSFP_DD_MODULE_ENC_OFFSET = 3
+QSFP_DD_MODULE_ENC_WIDTH = 1
+
+SFP_TYPE_LIST = [
+    '03' # SFP/SFP+/SFP28 and later
+]
+QSFP_TYPE_LIST = [
+    '0c', # QSFP
+    '0d', # QSFP+ or later
+    '11'  # QSFP28 or later
+]
+QSFP_DD_TYPE_LIST = [
+    '18' #QSFP_DD Type
+]
+OSFP_TYPE_LIST=[
+    '19' # OSFP 8X Type
+]
 
 
 class SfpUtil(SfpUtilBase):
@@ -87,18 +105,77 @@ class SfpUtil(SfpUtilBase):
     def port_to_eeprom_mapping(self):
         return self._port_to_eeprom_mapping
 
+    def _read_eeprom_bytes(self, eeprom_path, offset, num_bytes):
+        eeprom_raw = []
+        try:
+            eeprom = io.open(eeprom_path, mode="rb", buffering=0)
+        except IOError:
+            return None
+
+        for i in range(0, num_bytes):
+            eeprom_raw.append("0x00")
+
+        try:
+            eeprom.seek(offset)
+            raw = eeprom.read(num_bytes)
+        except IOError:
+            eeprom.close()
+            return None
+
+        try:
+            if isinstance(raw , str):
+                for n in range(0, num_bytes):
+                    eeprom_raw[n] = hex(ord(raw[n]))[2:].zfill(2)
+            else:
+                for n in range(0, num_bytes):
+                    eeprom_raw[n] = hex(raw[n])[2:].zfill(2)
+
+        except (OSError, IOError):
+            eeprom.close()
+            return None
+
+        eeprom.close()
+        return eeprom_raw
+
+    def _write_eeprom_bytes(self, eeprom_path, offset, num_bytes, value):
+        try:
+            with io.open(eeprom_path, mode='r+b', buffering=0) as f:
+                f.seek(offset)
+                f.write(value[0:num_bytes])
+        except (OSError, IOError):
+            return False
+        return True
+
+
+    def get_media_type(self, port_num):
+        """
+        Reads optic eeprom byte to determine media type inserted
+        """
+        eeprom_raw = []
+        eeprom_raw = self._read_eeprom_bytes(self.port_to_eeprom_mapping[port_num], MEDIA_TYPE_OFFSET,
+                     MEDIA_TYPE_WIDTH)
+        if eeprom_raw is not None:
+            if eeprom_raw[0] in SFP_TYPE_LIST:
+                sfp_type = 'SFP'
+            elif eeprom_raw[0] in QSFP_TYPE_LIST:
+                sfp_type = 'QSFP'
+            elif eeprom_raw[0] in QSFP_DD_TYPE_LIST:
+                sfp_type = 'QSFP_DD'
+            else:
+                #Set native port type if EEPROM type is not recognized/readable
+                sfp_type = 'QSFP_DD'
+        else:
+            sfp_type = 'QSFP_DD'
+
+        return sfp_type
+
     def pci_mem_read(self, mm, offset):
         mm.seek(offset)
-        read_data_stream = mm.read(4)
-        reg_val = struct.unpack('I', read_data_stream)
-        mem_val = str(reg_val)[1:-2]
-        # print "reg_val read:%x"%reg_val
-        return mem_val
+        return mm.read_byte()
 
     def pci_mem_write(self, mm, offset, data):
         mm.seek(offset)
-        # print "data to write:%x"%data
-        mm.write(struct.pack('I', data))
+        mm.write_byte(data)
 
     def pci_set_value(self, resource, val, offset):
         fd = open(resource, O_RDWR)
@@ -181,54 +258,70 @@ class SfpUtil(SfpUtilBase):
         # Check for invalid port_num
         if port_num < self.port_start or port_num > self.port_end:
             return False
-
-        # Port offset starts with 0x4000
-        port_offset = 16384 + ((port_num-1) * 16)
-
-        status = self.pci_get_value(self.BASE_RES_PATH, port_offset)
-        reg_value = int(status)
-
-        # Absence of status throws error
-        if (reg_value == ""):
+        if port_num > self.PORTS_IN_BLOCK:
             return False
-
-        # Mask off 4th bit for presence
-        mask = (1 << 6)
-
-        # LPMode is active high
-        if reg_value & mask == 0:
+        if self.get_media_type(port_num) == 'QSFP_DD':
+            lpmode = self._read_eeprom_bytes(self.port_to_eeprom_mapping[port_num], QSFP_DD_MODULE_ENC_OFFSET, 
+                     QSFP_DD_MODULE_ENC_WIDTH)
+            if lpmode is not None:
+                if int(lpmode[0])>>1 == 1:
+                   return True
             return False
+        else:
+            # Port offset starts with 0x4000
+            port_offset = 16384 + ((port_num-1) * 16)
 
-        return True
+            status = self.pci_get_value(self.BASE_RES_PATH, port_offset)
+            # Absence of status throws error
+            if (status == ""):
+                return False
+
+            reg_value = int(status)
+
+            # Mask off 4th bit for presence
+            mask = (1 << 6)
+
+            # LPMode is active high
+            if reg_value & mask == 0:
+                return False
+            return True
 
     def set_low_power_mode(self, port_num, lpmode):
 
         # Check for invalid port_num
         if port_num < self.port_start or port_num > self.port_end:
             return False
-
-        # Port offset starts with 0x4000
-        port_offset = 16384 + ((port_num-1) * 16)
-
-        status = self.pci_get_value(self.BASE_RES_PATH, port_offset)
-        reg_value = int(status)
-
-        # Absence of status throws error
-        if (reg_value == ""):
+        if port_num > self.PORTS_IN_BLOCK:
             return False
 
-        # Mask off 4th bit for presence
-        mask = (1 << 6)
+        if self.get_media_type(port_num) == 'QSFP_DD':
+            if lpmode is True:
+                write_val = 0x10
+            else:
+                write_val = 0x0
 
-        # LPMode is active high; set or clear the bit accordingly
-        if lpmode is True:
-            reg_value = reg_value | mask
+            self._write_eeprom_bytes(self.port_to_eeprom_mapping[port_num], 26, 1, bytearray([write_val]))
         else:
-            reg_value = reg_value & ~mask
+            # Port offset starts with 0x4000
+            port_offset = 16384 + ((port_num-1) * 16)
+            status = self.pci_get_value(self.BASE_RES_PATH, port_offset)
+            reg_value = int(status)
 
-        # Convert our register value back to a hex string and write back
-        status = self.pci_set_value(self.BASE_RES_PATH, reg_value, port_offset)
+            # Absence of status throws error
+            if (reg_value == ""):
+                return False
 
+            # Mask off 4th bit for presence
+            mask = (1 << 6)
+
+            # LPMode is active high; set or clear the bit accordingly
+            if lpmode is True:
+                reg_value = reg_value | mask
+            else:
+                reg_value = reg_value & ~mask
+
+            # Convert our register value back to a hex string and write back
+            status = self.pci_set_value(self.BASE_RES_PATH, reg_value, port_offset)
         return True
 
     def reset(self, port_num):
diff --git a/platform/broadcom/sonic-platform-modules-dell/z9332f/sonic_platform/sfp.py b/platform/broadcom/sonic-platform-modules-dell/z9332f/sonic_platform/sfp.py
index 744f472362..d70b9dd0d7 100644
--- a/platform/broadcom/sonic-platform-modules-dell/z9332f/sonic_platform/sfp.py
+++ b/platform/broadcom/sonic-platform-modules-dell/z9332f/sonic_platform/sfp.py
@@ -13,7 +13,6 @@ try:
     import re
     import time
     import subprocess
-    import struct
     import mmap
     from sonic_platform_base.sfp_base import SfpBase
     from sonic_platform_base.sonic_sfp.sff8436 import sff8436InterfaceId
@@ -60,6 +59,8 @@ QSFP_DD_APP1_ADV_OFFSET = 86
 QSFP_DD_APP1_ADV_WIDTH = 32
 QSFP_DD_APP2_ADV_OFFSET = 351
 QSFP_DD_APP2_ADV_WIDTH = 28
+QSFP_DD_MODULE_ENC_OFFSET = 3
+QSFP_DD_MODULE_ENC_WIDTH = 1
 
 QSFP_INFO_OFFSET = 128
 QSFP_DOM_OFFSET = 0
@@ -311,16 +312,12 @@ class Sfp(SfpBase):
 
     def pci_mem_read(self, mm, offset):
         mm.seek(offset)
-        read_data_stream = mm.read(4)
-        reg_val = struct.unpack('I', read_data_stream)
-        mem_val = str(reg_val)[1:-2]
-        # print "reg_val read:%x"%reg_val
-        return mem_val
+        return mm.read_byte()
 
     def pci_mem_write(self, mm, offset, data):
         mm.seek(offset)
         # print "data to write:%x"%data
-        mm.write(struct.pack('I', data))
+        mm.write_byte(data)
 
     def pci_set_value(self, resource, val, offset):
         fd = os.open(resource, os.O_RDWR)
@@ -338,6 +335,15 @@ class Sfp(SfpBase):
         os.close(fd)
         return val
 
+    def _write_eeprom_bytes(self, offset, num_bytes, value):
+        try:
+            with open(self.eeprom_path, mode='r+b', buffering=0) as f:
+                f.seek(offset)
+                f.write(value[0:num_bytes])
+        except (OSError, IOError):
+            return False
+        return True
+
     def _read_eeprom_bytes(self, eeprom_path, offset, num_bytes):
         eeprom_raw = []
         try:
@@ -994,7 +1000,13 @@ class Sfp(SfpBase):
         """
         lpmode_state = False
         try:
-            if self.sfp_type.startswith('QSFP'):
+            if self.sfp_type == 'QSFP_DD':
+                lpmode = self._read_eeprom_bytes(self.eeprom_path, QSFP_DD_MODULE_ENC_OFFSET, QSFP_DD_MODULE_ENC_WIDTH)
+                if lpmode is not None:
+                    if int(lpmode[0])>>1 == 1:
+                        return True
+                return False
+            else:
                 # Port offset starts with 0x4000
                 port_offset = 16384 + ((self.index-1) * 16)
 
@@ -1005,8 +1017,9 @@ class Sfp(SfpBase):
                 mask = (1 << 6)
 
                 lpmode_state = (reg_value & mask)
-        except  ValueError: pass
-        return lpmode_state
+        except ValueError:
+            pass
+        return bool(lpmode_state)
 
     def get_power_override(self):
         """
@@ -1231,7 +1244,14 @@ class Sfp(SfpBase):
         Sets the lpmode(low power mode) of this SFP
         """
         try:
-            if self.port_type == 'QSFP_DD':
+            if self.sfp_type == 'QSFP_DD':
+                if lpmode is True:
+                    write_val = 0x10
+                else:
+                    write_val = 0x0
+
+                self._write_eeprom_bytes(26, 1, bytearray([write_val]))
+            else:
                 # Port offset starts with 0x4000
                 port_offset = 16384 + ((self.index-1) * 16)