diff --git a/orchagent/port.h b/orchagent/port.h index db9f2b7bffb..cbdc8fa9d74 100644 --- a/orchagent/port.h +++ b/orchagent/port.h @@ -113,6 +113,7 @@ class Port uint32_t m_speed = 0; // Mbps std::string m_learn_mode = "hardware"; int m_autoneg = -1; // -1 means not set, 0 = disabled, 1 = enabled + int m_link_training = -1; // -1 means not set, 0 = disabled, 1 = enabled bool m_admin_state_up = false; bool m_init = false; bool m_l3_vni = false; diff --git a/orchagent/portsorch.cpp b/orchagent/portsorch.cpp index 5a6ba61e5cf..ba58fac07b5 100755 --- a/orchagent/portsorch.cpp +++ b/orchagent/portsorch.cpp @@ -61,6 +61,7 @@ extern string gMyAsicName; #define DEFAULT_VLAN_ID 1 #define MAX_VALID_VLAN_ID 4094 +#define PORT_STAT_POLLING_SEC 3 #define PORT_STAT_FLEX_COUNTER_POLLING_INTERVAL_MS 1000 #define PORT_BUFFER_DROP_STAT_POLLING_INTERVAL_MS 60000 #define QUEUE_STAT_FLEX_COUNTER_POLLING_INTERVAL_MS 10000 @@ -112,6 +113,26 @@ static map autoneg_mode_map = { "off", 0 } }; +static map link_training_mode_map = +{ + { "on", 1 }, + { "off", 0 } +}; + +static map link_training_failure_map = +{ + { SAI_PORT_LINK_TRAINING_FAILURE_STATUS_NO_ERROR, "none" }, + { SAI_PORT_LINK_TRAINING_FAILURE_STATUS_FRAME_LOCK_ERROR, "frame_lock"}, + { SAI_PORT_LINK_TRAINING_FAILURE_STATUS_SNR_LOWER_THRESHOLD, "snr_low"}, + { SAI_PORT_LINK_TRAINING_FAILURE_STATUS_TIME_OUT, "timeout"} +}; + +static map link_training_rx_status_map = +{ + { SAI_PORT_LINK_TRAINING_RX_STATUS_NOT_TRAINED, "not_trained" }, + { SAI_PORT_LINK_TRAINING_RX_STATUS_TRAINED, "trained"} +}; + // Interface type map used for gearbox static map interface_type_map = { @@ -304,9 +325,11 @@ static bool isValidPortTypeForLagMember(const Port& port) PortsOrch::PortsOrch(DBConnector *db, DBConnector *stateDb, vector &tableNames, DBConnector *chassisAppDb) : Orch(db, tableNames), m_portStateTable(stateDb, STATE_PORT_TABLE_NAME), + m_stateLinkTrainingTable(stateDb, STATE_LINK_TRAINING_TABLE_NAME), port_stat_manager(PORT_STAT_COUNTER_FLEX_COUNTER_GROUP, StatsMode::READ, PORT_STAT_FLEX_COUNTER_POLLING_INTERVAL_MS, false), port_buffer_drop_stat_manager(PORT_BUFFER_DROP_STAT_FLEX_COUNTER_GROUP, StatsMode::READ, PORT_BUFFER_DROP_STAT_POLLING_INTERVAL_MS, false), - queue_stat_manager(QUEUE_STAT_COUNTER_FLEX_COUNTER_GROUP, StatsMode::READ, QUEUE_STAT_FLEX_COUNTER_POLLING_INTERVAL_MS, false) + queue_stat_manager(QUEUE_STAT_COUNTER_FLEX_COUNTER_GROUP, StatsMode::READ, QUEUE_STAT_FLEX_COUNTER_POLLING_INTERVAL_MS, false), + m_timer(new SelectableTimer(timespec { .tv_sec = PORT_STAT_POLLING_SEC, .tv_nsec = 0 })) { SWSS_LOG_ENTER(); @@ -571,6 +594,10 @@ PortsOrch::PortsOrch(DBConnector *db, DBConnector *stateDb, vector (new LagIdAllocator(chassisAppDb)); } + + auto executor = new ExecutableTimer(m_timer, this, "PORT_STATUS_POLL"); + Orch::addExecutor(executor); + m_timer->start(); } void PortsOrch::removeDefaultVlanMembers() @@ -1905,6 +1932,24 @@ void PortsOrch::initPortSupportedSpeeds(const std::string& alias, sai_object_id_ m_portStateTable.set(alias, v); } +void PortsOrch::initPortCapabilityLinkTraining(const Port &port) +{ + sai_status_t status; + sai_attribute_t attr; + + attr.id = SAI_PORT_ATTR_SUPPORTED_LINK_TRAINING_MODE; + status = sai_port_api->set_port_attribute(port.m_port_id, &attr); + if (status == SAI_STATUS_SUCCESS) + { + string supp = attr.value.booldata ? "yes" : "no"; + m_stateLinkTrainingTable.hset(port.m_alias, "supported", supp); + } + else + { + SWSS_LOG_NOTICE("Unable to get %s LT capability", port.m_alias.c_str()); + } +} + /* * If Gearbox is enabled and this is a Gearbox port then set the attributes accordingly. */ @@ -2173,6 +2218,44 @@ task_process_status PortsOrch::setPortAutoNeg(sai_object_id_t id, int an) return task_success; } +bool PortsOrch::getPortCapabilityLinkTraining(const Port& port) +{ + string supp; + + if (m_stateLinkTrainingTable.hget(port.m_alias, "supported", supp)) + { + return (supp == "yes") ? true : false; + } + return false; +} + +task_process_status PortsOrch::setPortLinkTraining(const Port &port, bool state) +{ + SWSS_LOG_ENTER(); + + if (port.m_type != Port::PHY) + { + return task_failed; + } + + sai_attribute_t attr; + attr.id = SAI_PORT_ATTR_LINK_TRAINING_ENABLE; + attr.value.booldata = state; + + string op = state ? "on" : "off"; + sai_status_t status = sai_port_api->set_port_attribute(port.m_port_id, &attr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to set LT %s to port pid:%" PRIx64, + op.c_str(), port.m_port_id); + return handleSaiSetStatus(SAI_API_PORT, status); + } + + SWSS_LOG_INFO("Set LT %s to port pid:%" PRIx64, op.c_str(), port.m_port_id); + + return task_success; +} + bool PortsOrch::setHostIntfsOperStatus(const Port& port, bool isUp) const { SWSS_LOG_ENTER(); @@ -2396,6 +2479,9 @@ bool PortsOrch::initPort(const string &alias, const string &role, const int inde /* Create associated Gearbox lane mapping */ initGearboxPort(p); + /* Initialize port capabilities */ + initPortCapabilityLinkTraining(p); + /* Add port to port list */ m_portList[alias] = p; saiOidToAlias[id] = alias; @@ -2672,7 +2758,9 @@ void PortsOrch::doPortTask(Consumer &consumer) uint32_t speed = 0; string learn_mode; string an_str; + string lt_str; int an = -1; + int lt = -1; int index = -1; string role; string adv_speeds_str; @@ -2763,6 +2851,11 @@ void PortsOrch::doPortTask(Consumer &consumer) { adv_interface_types_str = fvValue(i); } + /* Set link training */ + else if (fvField(i) == "link_training") + { + lt_str = fvValue(i); + } /* Set port serdes Pre-emphasis */ else if (fvField(i) == "preemphasis") { @@ -2975,6 +3068,48 @@ void PortsOrch::doPortTask(Consumer &consumer) } } + if (!lt_str.empty() && (p.m_type == Port::PHY)) + { + if (link_training_mode_map.find(lt_str) == link_training_mode_map.end()) + { + SWSS_LOG_ERROR("Failed to parse LT value: %s", lt_str.c_str()); + // Invalid link training mode configured, don't retry + it = consumer.m_toSync.erase(it); + continue; + } + + lt = link_training_mode_map[lt_str]; + if (lt != p.m_link_training) + { + if (!getPortCapabilityLinkTraining(p)) + { + SWSS_LOG_ERROR("%s: LT is not supported by the ASIC", alias.c_str()); + // Don't retry + it = consumer.m_toSync.erase(it); + continue; + } + + auto status = setPortLinkTraining(p, lt > 0 ? true : false); + if (status != task_success) + { + SWSS_LOG_ERROR("Failed to set port %s LT from %d to %d", alias.c_str(), p.m_link_training, lt); + if (status == task_need_retry) + { + it++; + } + else + { + it = consumer.m_toSync.erase(it); + } + continue; + } + m_stateLinkTrainingTable.hset(p.m_alias, "status", lt > 0 ? "on" : "off"); + SWSS_LOG_NOTICE("Set port %s LT from %d to %d", alias.c_str(), p.m_link_training, lt); + p.m_link_training = lt; + m_portList[alias] = p; + } + } + if (speed != 0) { if (speed != p.m_speed) @@ -5958,6 +6093,54 @@ bool PortsOrch::getPortOperSpeed(const Port& port, sai_uint32_t& speed) const return true; } +bool PortsOrch::getPortLinkTrainingRxStatus(const Port &port, sai_port_link_training_rx_status_t &rx_status) +{ + SWSS_LOG_ENTER(); + + if (port.m_type != Port::PHY) + { + return false; + } + + sai_attribute_t attr; + attr.id = SAI_PORT_ATTR_LINK_TRAINING_RX_STATUS; + attr.value.u32 = 0; + + sai_status_t ret = sai_port_api->get_port_attribute(port.m_port_id, 1, &attr); + if (ret != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to get LT rx status for %s", port.m_alias.c_str()); + return false; + } + + rx_status = static_cast(attr.value.u32); + return true; +} + +bool PortsOrch::getPortLinkTrainingFailure(const Port &port, sai_port_link_training_failure_status_t &failure) +{ + SWSS_LOG_ENTER(); + + if (port.m_type != Port::PHY) + { + return false; + } + + sai_attribute_t attr; + attr.id = SAI_PORT_ATTR_LINK_TRAINING_FAILURE_STATUS; + attr.value.u32 = 0; + + sai_status_t ret = sai_port_api->get_port_attribute(port.m_port_id, 1, &attr); + if (ret != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to get LT failure status for %s", port.m_alias.c_str()); + return false; + } + + failure = static_cast(attr.value.u32); + return true; +} + bool PortsOrch::getSaiAclBindPointType(Port::Type type, sai_acl_bind_point_type_t &sai_acl_bind_type) { @@ -6943,3 +7126,53 @@ bool PortsOrch::decrFdbCount(const std::string& alias, int count) } return true; } + +void PortsOrch::updatePortLinkTrainingStatus(const Port &port) +{ + SWSS_LOG_ENTER(); + + if (port.m_type != Port::Type::PHY) + { + return; + } + + string status = "off"; + + if ((port.m_link_training > 0) && getPortCapabilityLinkTraining(port)) + { + sai_port_link_training_rx_status_t rx_status; + sai_port_link_training_failure_status_t failure; + + if (!getPortLinkTrainingRxStatus(port, rx_status)) + { + status = "on"; // LT is enabled, while the rx status is unavailable + } + else if (rx_status == SAI_PORT_LINK_TRAINING_RX_STATUS_TRAINED) + { + status = link_training_rx_status_map.at(rx_status); + } + else + { + if (getPortLinkTrainingFailure(port, failure) && + failure != SAI_PORT_LINK_TRAINING_FAILURE_STATUS_NO_ERROR) + { + status = link_training_failure_map.at(failure); + } + else + { + status = link_training_rx_status_map.at(rx_status); + } + } + } + m_stateLinkTrainingTable.hset(port.m_alias, "status", status); +} + +void PortsOrch::doTask(swss::SelectableTimer &timer) +{ + SWSS_LOG_ENTER(); + + for (auto it = m_portList.begin(); it != m_portList.end(); ++it) + { + updatePortLinkTrainingStatus(it->second); + } +} diff --git a/orchagent/portsorch.h b/orchagent/portsorch.h index 2848cdcb917..fc2f2f81b72 100755 --- a/orchagent/portsorch.h +++ b/orchagent/portsorch.h @@ -185,6 +185,7 @@ class PortsOrch : public Orch, public Subject unique_ptr m_flexCounterTable; unique_ptr m_flexCounterGroupTable; Table m_portStateTable; + Table m_stateLinkTrainingTable; std::string getQueueWatermarkFlexCounterTableKey(std::string s); std::string getPriorityGroupWatermarkFlexCounterTableKey(std::string s); @@ -249,6 +250,8 @@ class PortsOrch : public Orch, public Subject NotificationConsumer* m_portStatusNotificationConsumer; + swss::SelectableTimer *m_timer = nullptr; + void doTask() override; void doTask(Consumer &consumer); void doPortTask(Consumer &consumer); @@ -258,6 +261,7 @@ class PortsOrch : public Orch, public Subject void doLagMemberTask(Consumer &consumer); void doTask(NotificationConsumer &consumer); + void doTask(swss::SelectableTimer &timer); void removePortFromLanesMap(string alias); void removePortFromPortListMap(sai_object_id_t port_id); @@ -290,6 +294,9 @@ class PortsOrch : public Orch, public Subject bool initPort(const string &alias, const string &role, const int index, const set &lane_set); void deInitPort(string alias, sai_object_id_t port_id); + void initPortCapabilityLinkTraining(const Port &port); + bool getPortCapabilityLinkTraining(const Port& port); + bool setPortAdminStatus(Port &port, bool up); bool getPortAdminStatus(sai_object_id_t id, bool& up); bool setPortMtu(sai_object_id_t id, sai_uint32_t mtu); @@ -329,12 +336,17 @@ class PortsOrch : public Orch, public Subject bool setPortFecMode(sai_object_id_t id, int fec); task_process_status setPortInterfaceType(sai_object_id_t id, sai_port_interface_type_t interface_type); task_process_status setPortAdvInterfaceTypes(sai_object_id_t id, std::vector &interface_types); + task_process_status setPortLinkTraining(const Port& port, bool state); void updatePortOperStatus(Port &port, sai_port_oper_status_t status); bool getPortOperSpeed(const Port& port, sai_uint32_t& speed) const; void updateDbPortOperSpeed(Port &port, sai_uint32_t speed); + bool getPortLinkTrainingRxStatus(const Port &port, sai_port_link_training_rx_status_t &rx_status); + bool getPortLinkTrainingFailure(const Port &port, sai_port_link_training_failure_status_t &failure); + void updatePortLinkTrainingStatus(const Port &port); + void getPortSerdesVal(const std::string& s, std::vector &lane_values); bool getPortAdvSpeedsVal(const std::string &s, std::vector &speed_values); bool getPortInterfaceTypeVal(const std::string &s, sai_port_interface_type_t &interface_type); diff --git a/tests/test_port_lt.py b/tests/test_port_lt.py new file mode 100644 index 00000000000..98e1251a1db --- /dev/null +++ b/tests/test_port_lt.py @@ -0,0 +1,139 @@ +import time +import os +import pytest + +from swsscommon import swsscommon + + +class TestPortLinkTraining(object): + def test_PortLinkTrainingForce(self, dvs, testlog): + + db = swsscommon.DBConnector(0, dvs.redis_sock, 0) + adb = dvs.get_asic_db() + + tbl = swsscommon.ProducerStateTable(db, "PORT_TABLE") + fvs = swsscommon.FieldValuePairs([("link_training","off")]) + tbl.set("Ethernet0", fvs) + + tbl = swsscommon.ProducerStateTable(db, "PORT_TABLE") + fvs = swsscommon.FieldValuePairs([("link_training","on")]) + tbl.set("Ethernet4", fvs) + + # validate if autoneg false is pushed to asic db when set first time + port_oid = adb.port_name_map["Ethernet0"] + expected_fields = {"SAI_PORT_ATTR_LINK_TRAINING_ENABLE":"false"} + adb.wait_for_field_match("ASIC_STATE:SAI_OBJECT_TYPE_PORT", port_oid, expected_fields) + + # validate if autoneg true is pushed to asic db when set first time + port_oid = adb.port_name_map["Ethernet4"] + expected_fields = {"SAI_PORT_ATTR_LINK_TRAINING_ENABLE":"true"} + adb.wait_for_field_match("ASIC_STATE:SAI_OBJECT_TYPE_PORT", port_oid, expected_fields) + + def test_PortLinkTrainingCold(self, dvs, testlog): + db = swsscommon.DBConnector(0, dvs.redis_sock, 0) + + tbl = swsscommon.ProducerStateTable(db, "PORT_TABLE") + + # set link_training = true + fvs = swsscommon.FieldValuePairs([("link_training","on")]) + + tbl.set("Ethernet0", fvs) + + time.sleep(1) + + adb = swsscommon.DBConnector(1, dvs.redis_sock, 0) + + atbl = swsscommon.Table(adb, "ASIC_STATE:SAI_OBJECT_TYPE_PORT") + (status, fvs) = atbl.get(dvs.asicdb.portnamemap["Ethernet0"]) + assert status == True + + assert "SAI_PORT_ATTR_LINK_TRAINING_ENABLE" in [fv[0] for fv in fvs] + for fv in fvs: + if fv[0] == "SAI_PORT_ATTR_LINK_TRAINING_ENABLE": + assert fv[1] == "true" + + # change link_training to false + fvs = swsscommon.FieldValuePairs([("link_training","off")]) + + tbl.set("Ethernet0", fvs) + + time.sleep(1) + + (status, fvs) = atbl.get(dvs.asicdb.portnamemap["Ethernet0"]) + assert status == True + + assert "SAI_PORT_ATTR_LINK_TRAINING_ENABLE" in [fv[0] for fv in fvs] + for fv in fvs: + if fv[0] == "SAI_PORT_ATTR_LINK_TRAINING_ENABLE": + assert fv[1] == "false" + + def test_PortLinkTrainingWarm(self, dvs, testlog): + + db = swsscommon.DBConnector(0, dvs.redis_sock, 0) + cdb = swsscommon.DBConnector(4, dvs.redis_sock, 0) + sdb = swsscommon.DBConnector(6, dvs.redis_sock, 0) + + tbl = swsscommon.ProducerStateTable(db, "PORT_TABLE") + ctbl = swsscommon.Table(cdb, "PORT") + stbl = swsscommon.Table(sdb, "PORT_TABLE") + + # set link_training = true + fvs = swsscommon.FieldValuePairs([("link_training","on")]) + ctbl.set("Ethernet0", fvs) + + time.sleep(1) + + adb = swsscommon.DBConnector(1, dvs.redis_sock, 0) + + atbl = swsscommon.Table(adb, "ASIC_STATE:SAI_OBJECT_TYPE_PORT") + (status, fvs) = atbl.get(dvs.asicdb.portnamemap["Ethernet0"]) + assert status == True + + assert "SAI_PORT_ATTR_LINK_TRAINING_ENABLE" in [fv[0] for fv in fvs] + for fv in fvs: + if fv[0] == "SAI_PORT_ATTR_AUTO_NEG_MODE": + assert fv[1] == "true" + + # set admin up + cfvs = swsscommon.FieldValuePairs([("admin_status", "up")]) + ctbl.set("Ethernet0", cfvs) + + # enable warm restart + (exitcode, result) = dvs.runcmd("config warm_restart enable swss") + assert exitcode == 0 + + # freeze orchagent for warm restart + (exitcode, result) = dvs.runcmd("/usr/bin/orchagent_restart_check") + assert result == "RESTARTCHECK succeeded\n" + time.sleep(2) + + try: + # restart orchagent + # clean port state + dvs.stop_swss() + ports = stbl.getKeys() + for port in ports: + stbl._del(port) + dvs.start_swss() + time.sleep(2) + + # check ASIC DB after warm restart + (status, fvs) = atbl.get(dvs.asicdb.portnamemap["Ethernet0"]) + assert status == True + + assert "SAI_PORT_ATTR_LINK_TRAINING_ENABLE" in [fv[0] for fv in fvs] + for fv in fvs: + if fv[0] == "SAI_PORT_ATTR_LINK_TRAINING_ENABLE": + assert fv[1] == "true" + + finally: + # disable warm restart + dvs.runcmd("config warm_restart disable swss") + # slow down crm polling + dvs.runcmd("crm config polling interval 10000") + + +# Add Dummy always-pass test at end as workaroud +# for issue when Flaky fail on final test it invokes module tear-down before retrying +def test_nonflaky_dummy(): + pass