Files
Eduardo Alberti 8a77c31594 Clean pins structure in case of disconnected pin
In cases when only one type of device (PSS or EEC) is connected, the ptp
notification app stay stuck in FAILURE-NOFIX even when the disconnected
device change to connected status.

To solve this, the pins structure is cleaned when only one device is
connected (PSS or EEC) forcing a new pin read on the next validation.

TEST PLAN:
PASS: Status change to FAILURE_NOFIX in case of disconnected pin
PASS: Status change to LOCK_HO_ACQ in case of connected pins in EEC and
      PPS devices

Closes-bug: #2121704

Change-Id: Ib78331f2526add179a65f094e2646b472e27be5c
Signed-off-by: Eduardo Alberti <eduardo.alberti@windriver.com>
2025-08-29 14:03:58 -03:00

332 lines
11 KiB
Python

#
# Copyright (c) 2022-2025 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import logging
import os
import re
from trackingfunctionsdk.common.helpers import constants
from trackingfunctionsdk.common.helpers import log_helper
from pynetlink import NetlinkDPLL, DpllPins, NetlinkException
from pynetlink import DeviceType, PinState, PinDirection, LockStatus
LOG = logging.getLogger(__name__)
log_helper.config_logger(LOG)
class CguHandler:
"""Class that implements methods to access CGU information"""
def __init__(self, config_file, nmea_serialport=None, pci_addr=None,
cgu_path=None, clock_id=None):
self._config_file = config_file
self._nmea_serialport_mask = nmea_serialport
self._nmea_serialport = self._prune_reconfigured_suffix(nmea_serialport)
self._pci_addr = pci_addr
self._cgu_path = cgu_path
self._clock_id = clock_id
self._pins = DpllPins()
self._dpll = NetlinkDPLL()
self._is_serial_module = False
# Try to get clock-id
try:
if self._nmea_serialport and "tty" in self._nmea_serialport:
self._is_serial_module = True
LOG.debug("Searching for clock id")
if self._clock_id is None:
self._get_clock_id()
except Exception as err:
LOG.error(err)
def _prune_reconfigured_suffix(self, nmea_serialport):
# truncate suffix .pty (gpspipe output device) if any, to get the actual device path
return nmea_serialport.removesuffix(".pty") if nmea_serialport else None
def _get_gnss_nmea_serialport_from_ts2phc_config(self):
"""Read a ts2phc config file and return the ts2phc.nmea_serialport"""
nmea_serialport = None
try:
with open(self._config_file, 'r', encoding='utf-8') as infile:
for line in infile:
if constants.NMEA_SERIALPORT in line:
nmea_serialport = line.split(' ')[1].strip('\n')
break
self._nmea_serialport_mask = nmea_serialport
self._nmea_serialport = self._prune_reconfigured_suffix(nmea_serialport)
self._is_serial_module = "tty" in nmea_serialport
return
except (FileNotFoundError, PermissionError) as err:
LOG.error(err)
raise
def _convert_nmea_serialport_to_pci_addr(self):
"""Parse the nmea_serialport value into a PCI address so that we can
later find the cgu.
"""
# Validate if NMEA serial port is set
if self._nmea_serialport is None:
self._get_gnss_nmea_serialport_from_ts2phc_config()
# Remove the /dev portion of the path
nmea_serialport = self._nmea_serialport.split('/')[2]
LOG.debug('Looking for nmea_serialport value: %s', nmea_serialport)
try:
with open(constants.UEVENT_FILE.format(nmea_serialport), 'r',
encoding='utf-8') as file:
for line in file:
if constants.PCI_SLOT_NAME in line:
# Get the portion after the '=' sign
pci_addr = re.split('=', line)[1].strip('\n')
LOG.debug("Found with PCI addr: %s", pci_addr)
break
except (FileNotFoundError, PermissionError) as err:
LOG.error(err)
raise
self._pci_addr = pci_addr
def _get_clock_id_by_pci_addr(self):
"""Get clock id by network card pci address"""
# Validate if PCI address is set
if self._pci_addr is None:
self._convert_nmea_serialport_to_pci_addr()
# Validate if PCI address have more than one network device
net_devices = os.listdir(constants.SYS_DEV_NET.format(self._pci_addr))
if len(net_devices) != 1:
LOG.error("Error during PCI address translation. Net_devices = %s",
net_devices)
return
try:
with open(constants.SYS_DEV_NET_ADDR.format(self._pci_addr,
net_devices[0]),
'r', encoding='utf-8') as file:
phys_switch_id = file.read()
except (FileNotFoundError, PermissionError) as err:
LOG.error(err)
raise
self._clock_id = int(phys_switch_id.strip(), 16)
LOG.info("Mac Address %s translated to clock id %s", phys_switch_id,
self._clock_id)
def _get_clock_id_for_tty_dev(self):
"""Determine the clock_id based on ZL 3073 device"""
try:
with open(constants.ZL_MODULE_PATH_CLKID, 'r', encoding='utf-8') \
as infile:
clock_id = infile.read()
if not clock_id:
raise ValueError("Clock ID is empty")
except (FileNotFoundError, PermissionError, ValueError) as err:
LOG.error(err)
raise
self._clock_id = int(clock_id.replace("\n",""))
LOG.info("Module Address %s translated to clock id %s",
self._nmea_serialport,
self._clock_id)
def _get_clock_id(self):
"""Get clock_id for the device"""
if self._nmea_serialport is None:
self._get_gnss_nmea_serialport_from_ts2phc_config()
if self._is_serial_module:
self._get_clock_id_for_tty_dev()
else:
self._get_clock_id_by_pci_addr()
def _read_all_devices(self):
"""Read CGU information using netlink interface. Consider that there is
no information saved from the last read.
NetlinkException: error when Netlink wasn't initialized, or no info
is read.
"""
LOG.debug("Reading all devices")
try:
get_pins = self._dpll.get_all_pins()\
.filter_by_device_clock_id(self._clock_id)\
.filter_by_pin_state(PinState.CONNECTED)\
.filter_by_pin_direction(PinDirection.INPUT)
if len(get_pins) == 0:
get_pins = None
LOG.error("No pins found for clock id")
except NetlinkException as err:
get_pins = None
LOG.error(err)
self._pins = get_pins
def _read_only_filtered(self):
"""Read CGU information using netlink interface. Consider the last used
pins to save time.
Raises:
NetlinkException: error when Netlink wasn't initialized, or no info
is read.
"""
LOG.debug("Reading only filtered pins")
try:
new_pins = DpllPins()
# Create a set of pins to read
pin_ids = {x.pin_id for x in self._pins}
LOG.debug("Filtered pin IDs: %s", pin_ids)
for pin_id in pin_ids:
# Get pin (for each pin_id) and filter by device_id.
aux = self._dpll.get_pins_by_id(pin_id)
if not aux:
LOG.debug("Saved pin isn't accessible anymore. Forcing "
"full reading.")
return self._read_all_devices()
dev_ids = {x.dev_id for x in self._pins if x.pin_id == pin_id}
filtered_pins = {x for x in aux if x.dev_id in dev_ids}
# If any pin is not connected, force full reading
if len(filtered_pins) == 0 or \
any(x.pin_state != PinState.CONNECTED
for x in filtered_pins):
LOG.debug("Saved pin is not connected anymore. Forcing "
"full reading.")
return self._read_all_devices()
new_pins.update(filtered_pins)
except NetlinkException as err:
new_pins = None
LOG.error(err)
self._pins = new_pins
def read_cgu(self):
"""Read the CGU information using netlink interface."""
if self._dpll is None:
LOG.debug("Netlink family not initialized.")
return
if self._clock_id is None:
LOG.debug("Isn't possible to obtain the status of "
"the device. Clock ID is None.")
return
# To avoid unnecessary reads and save time, pins used by the device
# will be saved and read separately.
# Pins used will be saved during the reading.
if not self._pins or len(self._pins) == 0:
self._read_all_devices()
else:
self._read_only_filtered()
def _get_status(self, device_type: DeviceType) -> str:
"""Get the device status from Netlink DPLL dictionary
Args:
device_type (DeviceType): Type of the device (eec or pps)
Returns:
str: device status
"""
if not self._pins or len(self._pins) == 0:
return LockStatus.UNDEFINED.value
pin = self._pins.filter_by_device_type(device_type)
if len(pin) == 0:
self._pins = None
return LockStatus.UNDEFINED.value
return next(iter(pin)).lock_status.value
def get_eec_status(self) -> str:
"""Get EEC status from Netlink DPLL dictionary
Returns:
str: EEC status
"""
return self._get_status(DeviceType.EEC)
def get_pps_status(self) -> str:
"""Get PPS status from Netlink DPLL dictionary
Returns:
str: PPS status
"""
return self._get_status(DeviceType.PPS)
def _get_current_reference(self, device_type: DeviceType) -> str:
"""Get the device status from Netlink DPLL dictionary
Args:
device_type (DeviceType): Type of the device (eec or pps)
Returns:
str: device status
"""
if not self._pins or len(self._pins) == 0:
return 'undefined'
pin = self._pins.filter_by_device_type(device_type)
if len(pin) == 0:
return 'undefined'
return next(iter(pin)).pin_board_label
def get_pps_current_ref(self) -> str:
"""Get PPS current reference from Netlink DPLL dictionary
Returns:
str: PPS current reference
"""
return self._get_current_reference(DeviceType.PPS)
def get_eec_current_ref(self) -> str:
"""Get EEC current reference from Netlink DPLL dictionary
Returns:
str: EEC current reference
"""
return self._get_current_reference(DeviceType.EEC)
def _get_current_pin_type(self, device_type: DeviceType) -> str:
"""Get the pin type from Netlink DPLL dictionary
Args:
device_type (DeviceType): Type of the device (eec or pps)
Returns:
str: pin type
"""
if not self._pins or len(self._pins) == 0:
return 'undefined'
pin = self._pins.filter_by_device_type(device_type)
if len(pin) == 0:
return 'undefined'
return next(iter(pin)).pin_type
def get_pps_pin_type(self) -> str:
"""Get PPS current pin type from Netlink DPLL dictionary
Returns:
str: PPS current pin type
"""
return self._get_current_pin_type(DeviceType.PPS)
def get_eec_pin_type(self) -> str:
"""Get EEC current pin type from Netlink DPLL dictionary
Returns:
str: EEC current pin type
"""
return self._get_current_pin_type(DeviceType.EEC)