/* * ecnt_prvt.c - Econet switch private utilities * * Copyright (C) 2022 iopsys Software Solutions AB. All rights reserved. * * Author: maxim.menshikov@iopsys.eu * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA */ #include <ctype.h> #include <fcntl.h> #include <linux/mii.h> #include <net/if.h> #include <stdarg.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <sys/types.h> #include <unistd.h> #include <easy/easy.h> #include <sys/ioctl.h> #include <net/if.h> #include <linux/sockios.h> #include "../ethernet.h" #include "ecnt_prvt.h" #ifdef STATIC_ANALYSIS #include "stub_libapi_lib_switchmgr.h" #else #include "libapi_lib_switchmgr.h" #endif #include "libapi_lib_fe.h" #define TC3162_MAX_LINE_LEN (100) #define TC3162_DUPLEX_MODE_LEN (32) #define TC3162_SPEED_MODE_LEN (32) #define TC3162_ETH_PORTMAP_PATH "/proc/tc3162/eth_portmap" #define IFNAME_ETH0 "eth0." #define IFNAME_NAS "nas" #define IFNAME_AE_WAN "ae_wan" #define IFNAME_PON "pon" #define DRIVER_NAME "hsgmii_lan" #define DRIVER_NAME_LEN 20 struct hsgmii_lookup_table { const unsigned int idx; char *ifnames[IFNAMSIZ]; const char iftype[IFNAMSIZ]; }; const struct hsgmii_lookup_table hsgmii_lookup_tbl[] = { { 2, { "eth0.5", "eth1", NULL }, "usb" }, { 0, { "eth0.6", "eth2", NULL }, "pcie0" }, { 1, { "eth0.7", "eth3", NULL }, "pcie1" }, { 3, { "eth0.8", "eth4", NULL }, "eth" }, }; /* Not defined in Econet library */ ECNT_SWITCHMGR_RET switchmgr_lib_get_port_link_state(u8 port, ECNT_SWITCHMGR_LINK_STATE *p_link_state, ECNT_SWITCHMGR_LINK_SPEED *p_speed); static int get_drv_info_by_ifname(char *ifname, char *buffer) { int fd; int ret = -1; struct ifreq ifr; struct ethtool_drvinfo info; if (ifname == NULL || buffer == NULL) return ret; fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) return ret; memset(&info, 0, sizeof(info)); info.cmd = ETHTOOL_GDRVINFO; memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name) - 1); ifr.ifr_data = (void *)&info; if (ioctl(fd, SIOCETHTOOL, &ifr) != 0) goto exit; strncpy(buffer, info.driver, DRIVER_NAME_LEN - 1); ret = 0; exit: close(fd); return ret; } static int get_pause_stats_from_proc(const char *ifprefix, const char *iftype, uint64_t *rx_pause_packets, uint64_t *tx_pause_packets) { uint64_t rx_pause_on, rx_pause_off, tx_pause_on, tx_pause_off; char cmdbuf[512] = {0}; int ret; chrCmd(cmdbuf, sizeof(cmdbuf), "cat /proc/tc3162/%s%s_mac_dbg | grep PAUSE", ifprefix, iftype); if (cmdbuf[0] == '\0') return -1; ret = sscanf(cmdbuf, /* Flawfinder: ignore */ "TXMBI_PAUSEON_CNT\t\t= %" SCNx64 ", TXMBI_PAUSEOFF_CNT\t\t= %" SCNx64 " RX_PAUSEON_CNT\t\t\t= %" SCNx64 ", RX_PAUSEOFF_CNT\t\t\t= %" SCNx64, &tx_pause_on, &tx_pause_off, &rx_pause_on, &rx_pause_off); if (ret < 4) return -1; *tx_pause_packets = tx_pause_on + tx_pause_off; *rx_pause_packets = rx_pause_on + rx_pause_off; return 0; } static void fill_stats_tx_from_gdma(struct eth_stats *stats, struct eth_rmon_stats *rstats, ECNT_FEMGR_GDMA2_TX_STATISTICS *tx_stats) { if (tx_stats == NULL) return; if (stats != NULL) { stats->tx_bytes = tx_stats->frame_len; stats->tx_packets = tx_stats->frame_cnt; stats->tx_errors = 0; stats->tx_ucast_packets = tx_stats->frame_cnt - (tx_stats->broadcast + tx_stats->multicast); stats->tx_bcast_packets = tx_stats->broadcast; stats->tx_mcast_packets = tx_stats->multicast; stats->tx_discard_packets = tx_stats->drop_cnt; } if (rstats != NULL) { rstats->tx.packets = tx_stats->frame_cnt; rstats->tx.bytes = tx_stats->frame_len; rstats->tx.bcast_packets = tx_stats->broadcast; rstats->tx.mcast_packets = tx_stats->multicast; rstats->tx.crc_err_packets = 0; rstats->tx.under_sz_packets = 0; rstats->tx.over_sz_packets = 0; rstats->tx.packets_64bytes = tx_stats->eq_64; rstats->tx.packets_65to127bytes = tx_stats->from_65_to_127; rstats->tx.packets_256to511bytes = tx_stats->from_256_to_511; rstats->tx.packets_512to1023bytes = tx_stats->from_512_to_1023; rstats->tx.packets_1024to1518bytes = tx_stats->from_1024_to_1518; } } static void fill_stats_rx_from_gdma(struct eth_stats *stats, struct eth_rmon_stats *rstats, ECNT_FEMGR_GDMA2_RX_STATISTICS *rx_stats, bool oversize_is_ok) { if (rx_stats == NULL) return; if (stats != NULL) { stats->rx_bytes = rx_stats->frame_len; stats->rx_packets = rx_stats->frame_cnt; stats->rx_errors = rx_stats->crc + rx_stats->jabber + rx_stats->fragment + rx_stats->undersize + (oversize_is_ok == false ? rx_stats->oversize : 0); stats->rx_ucast_packets = rx_stats->frame_cnt - (rx_stats->broadcast + rx_stats->multicast); stats->rx_bcast_packets = rx_stats->broadcast; stats->rx_mcast_packets = rx_stats->multicast; stats->rx_discard_packets = rx_stats->drop_cnt; stats->rx_unknown_packets = 0; } if (rstats != NULL) { rstats->rx.packets = rx_stats->frame_cnt; rstats->rx.bytes = rx_stats->frame_len; rstats->rx.bcast_packets = rx_stats->broadcast; rstats->rx.mcast_packets = rx_stats->multicast; rstats->rx.crc_err_packets = rx_stats->crc; rstats->rx.under_sz_packets = rx_stats->undersize; rstats->rx.over_sz_packets = rx_stats->oversize; rstats->rx.packets_64bytes = rx_stats->eq_64; rstats->rx.packets_65to127bytes = rx_stats->from_65_to_127; rstats->rx.packets_256to511bytes = rx_stats->from_256_to_511; rstats->rx.packets_512to1023bytes = rx_stats->from_512_to_1023; rstats->rx.packets_1024to1518bytes = rx_stats->from_1024_to_1518; } } int hsgmii_lan_prvt_get_port_statistics(char *ifname, struct eth_stats *stats, struct eth_rmon_stats *rstats) { ECNT_FEMGR_GDMA2_TX_STATISTICS tx_stats; ECNT_FEMGR_GDMA2_RX_STATISTICS rx_stats; char driver_name[DRIVER_NAME_LEN] = {0}; if (get_drv_info_by_ifname(ifname, driver_name)) return -1; if (!strncmp(driver_name, DRIVER_NAME, DRIVER_NAME_LEN)) { int i = 0, hsgmii_index = -1; const char *hsgmii_iftype; char cmdbuf[512] = {0}; for (; i < ARRAY_SIZE(hsgmii_lookup_tbl); i++) { int j = 0; while (hsgmii_lookup_tbl[i].ifnames[j] != NULL) { if (!strcmp(ifname, hsgmii_lookup_tbl[i].ifnames[j])) { hsgmii_index = hsgmii_lookup_tbl[i].idx; hsgmii_iftype = hsgmii_lookup_tbl[i].iftype; break; } j++; } if (hsgmii_index != -1) break; } if (hsgmii_index == -1) return -1; memset(&tx_stats, 0, sizeof(ECNT_FEMGR_GDMA2_TX_STATISTICS)); memset(&rx_stats, 0, sizeof(ECNT_FEMGR_GDMA2_RX_STATISTICS)); /* Handle unbound interface statistics */ chrCmd(cmdbuf, sizeof(cmdbuf), "cat /proc/tc3162/%s_rebind | awk '{ print $3 }'", ifname); if (cmdbuf[0] != '\0' && strtoul(cmdbuf, NULL, 0) == 1) return 0; if (!fe_lib_get_hsgmii_tx_statistics(&tx_stats, hsgmii_index)) { fill_stats_tx_from_gdma(stats, rstats, &tx_stats); } if (!fe_lib_get_hsgmii_rx_statistics(&rx_stats, hsgmii_index)) { fill_stats_rx_from_gdma(stats, rstats, &rx_stats, false); } if (rstats != NULL) { get_pause_stats_from_proc("hsgmii_", hsgmii_iftype, &rstats->rx.pause_packets, &rstats->tx.pause_packets); } return 0; } return -1; } int ae_wan_prvt_get_port_statistics(struct eth_stats *stats, struct eth_rmon_stats *rstats) { char cmdbuf[512] = {0}; ECNT_FEMGR_GDMA2_TX_STATISTICS tx_stats; ECNT_FEMGR_GDMA2_RX_STATISTICS rx_stats; memset(&tx_stats, 0, sizeof(ECNT_FEMGR_GDMA2_TX_STATISTICS)); memset(&rx_stats, 0, sizeof(ECNT_FEMGR_GDMA2_RX_STATISTICS)); chrCmd(cmdbuf, sizeof(cmdbuf), "cat /proc/tc3162/ae_wan_switch_hsgmii_lan"); if (cmdbuf[0] == '\0') { return -1; } else if (strcmp(cmdbuf, "pon") != 0) { int i = 0, hsgmii_index = -1; for (; i < ARRAY_SIZE(hsgmii_lookup_tbl); i++) { if (!strcmp(cmdbuf, hsgmii_lookup_tbl[i].iftype)) { hsgmii_index = hsgmii_lookup_tbl[i].idx; break; } } if (hsgmii_index == -1) return -1; if (!fe_lib_get_hsgmii_tx_statistics(&tx_stats, hsgmii_index)) { fill_stats_tx_from_gdma(stats, rstats, &tx_stats); } if (!fe_lib_get_hsgmii_rx_statistics(&rx_stats, hsgmii_index)) { fill_stats_rx_from_gdma(stats, rstats, &rx_stats, false); } if (rstats != NULL) { get_pause_stats_from_proc("hsgmii_", cmdbuf, &rstats->rx.pause_packets, &rstats->tx.pause_packets); } } else { if (!fe_lib_get_gdma2_tx_statistics(&tx_stats)) { fill_stats_tx_from_gdma(stats, rstats, &tx_stats); } if (!fe_lib_get_gdma2_rx_statistics(&rx_stats)) { fill_stats_rx_from_gdma(stats, rstats, &rx_stats, false); } if (rstats != NULL) { get_pause_stats_from_proc("", "ae_wan", &rstats->rx.pause_packets, &rstats->tx.pause_packets); } } return 0; } int pon_prvt_get_port_statistics(struct eth_stats *stats, struct eth_rmon_stats *rstats) { ECNT_FEMGR_GDMA2_TX_STATISTICS tx_stats; ECNT_FEMGR_GDMA2_RX_STATISTICS rx_stats; char driver_name[DRIVER_NAME_LEN] = {0}; if (get_drv_info_by_ifname(IFNAME_PON, driver_name)) return -1; memset(&tx_stats, 0, sizeof(ECNT_FEMGR_GDMA2_TX_STATISTICS)); memset(&rx_stats, 0, sizeof(ECNT_FEMGR_GDMA2_RX_STATISTICS)); if (!fe_lib_get_gdma2_tx_statistics(&tx_stats)) { fill_stats_tx_from_gdma(stats, rstats, &tx_stats); } if (!fe_lib_get_gdma2_rx_statistics(&rx_stats)) { fill_stats_rx_from_gdma(stats, rstats, &rx_stats, true); } return 0; } int ecnt_prvt_get_port_statistics(uint32_t port, struct eth_stats *stats, struct eth_rmon_stats *rstats) { int ret; ECNT_SWITCHMGR_PORT_STATISTICS portcnt; if (port == ECNT_PRVT_PORT_NUM_INVALID || (stats == NULL && rstats == NULL)) { return -1; } if (port >= 256) { return -1; } memset(&portcnt, 0, sizeof(portcnt)); ret = switchmgr_lib_get_port_statistics((uint8_t)port, &portcnt); if (ret != ECNT_SWITCHMGR_SUCCESS) { return -1; } if (stats != NULL) { stats->tx_bytes = (((uint64_t)portcnt.TxBytesCnt_Hi) << 32) + portcnt.TxBytesCnt_Lo; stats->rx_bytes = (((uint64_t)portcnt.RxBytesCnt_Hi) << 32) + portcnt.RxBytesCnt_Lo; stats->tx_packets = portcnt.TxPktsCnt; stats->rx_packets = portcnt.RxPktsCnt; stats->tx_errors = 0; stats->rx_errors = portcnt.RxAlignmentErrorCnt + portcnt.RxCRCFramesCnt + portcnt.RxUnderSizePktsCnt + portcnt.RxFragmentErrorCnt + portcnt.RxOverSizePktsCnt; stats->tx_ucast_packets = portcnt.TxUniPktsCnt; stats->rx_ucast_packets = portcnt.RxUniPktsCnt; stats->tx_mcast_packets = portcnt.TxMultiPktsCnt; stats->rx_mcast_packets = portcnt.RxMultiPktsCnt; stats->tx_bcast_packets = portcnt.TxBroadPktsCnt; stats->rx_bcast_packets = portcnt.RxBroadPktsCnt; stats->tx_discard_packets = portcnt.TxDropFramesCnt; stats->rx_discard_packets = portcnt.RxDropFramesCnt; stats->rx_unknown_packets = 0; } if (rstats != NULL) { #define FILL_RSTATS_FOR_DIRECTION(__rmon_field, __ecnt_prefix) \ do { \ rstats->__rmon_field.drop_events = \ portcnt.__ecnt_prefix ## DropFramesCnt; \ rstats->__rmon_field.bytes = \ (((uint64_t)portcnt.__ecnt_prefix ## BytesCnt_Hi) << 32) \ + portcnt.__ecnt_prefix ## BytesCnt_Lo; \ rstats->__rmon_field.packets = portcnt.__ecnt_prefix ## PktsCnt;\ rstats->__rmon_field.bcast_packets = \ portcnt.__ecnt_prefix ## BroadPktsCnt; \ rstats->__rmon_field.mcast_packets = \ portcnt.__ecnt_prefix ## MultiPktsCnt; \ rstats->__rmon_field.crc_err_packets = \ portcnt.__ecnt_prefix ## CRCFramesCnt; \ rstats->__rmon_field.under_sz_packets = \ portcnt.__ecnt_prefix ## UnderSizePktsCnt; \ rstats->__rmon_field.over_sz_packets = \ portcnt.__ecnt_prefix ## OverSizePktsCnt; \ rstats->__rmon_field.pause_packets = \ portcnt.__ecnt_prefix ## PauseFramesCnt; \ rstats->__rmon_field.packets_64bytes = \ portcnt.__ecnt_prefix ## 64BytePktsCnt; \ rstats->__rmon_field.packets_65to127bytes = \ portcnt.__ecnt_prefix ## 65_127BytePktsCnt; \ rstats->__rmon_field.packets_256to511bytes = \ portcnt.__ecnt_prefix ## 256_511BytePktsCnt; \ rstats->__rmon_field.packets_512to1023bytes = \ portcnt.__ecnt_prefix ## 512_1023BytePktsCnt; \ rstats->__rmon_field.packets_1024to1518bytes = \ portcnt.__ecnt_prefix ## 1024_1518BytePktsCnt; \ } while (0) FILL_RSTATS_FOR_DIRECTION(tx, Tx); FILL_RSTATS_FOR_DIRECTION(rx, Rx); #undef FILL_RSTATS_FOR_DIRECTION } return 0; } /* Read port number from proc interface */ static uint32_t read_port_from_proc(bool wan, uint32_t eth_port_num) { FILE *f; int tmp; int ret; char buf[TC3162_MAX_LINE_LEN]; uint32_t new_port_num = ECNT_PRVT_PORT_NUM_INVALID; f = fopen(TC3162_ETH_PORTMAP_PATH, "r"); if (f == NULL) { return ECNT_PRVT_PORT_NUM_INVALID; } if (wan) { /* WAN is the very first in the proc dump */ ret = fscanf(f, "%d", &tmp); if (ret != 1) { goto out; } new_port_num = (uint32_t)tmp; } else { char *retstr; bool inside_map = false; /* First we search for the beginning of switch port map */ while (true) { retstr = fgets(buf, sizeof(buf), f); if (retstr == NULL) { break; } inside_map = strstr(buf, "switch_port_map") != NULL; if (inside_map) { break; } } if (!inside_map) { goto out; } /* * Then we sequentially read all ports until the needed port line is * found * Line format: <virtual port> <switch port> */ while (true) { int port; int swport; if (fscanf(f, "%d %d", &port, &swport) != 2) { break; } if (port == eth_port_num) { new_port_num = (uint32_t)swport; goto out; } } } out: fclose(f); return new_port_num; } uint32_t ecnt_prvt_get_port_num(const char *ifname) { bool is_wan; uint32_t eth_port_num = 0; /* * This algorithm is not accurate if numbers differ for target hardware. * There must be a generic way to get port IDs reliably based on interface * names. */ if (strncmp(ifname, IFNAME_ETH0, strlen(IFNAME_ETH0)) == 0) { char *end; is_wan = false; eth_port_num = (uint32_t)strtol(&ifname[strlen(IFNAME_ETH0)], &end, 10); if (end == NULL || *end != '\0') { return ECNT_PRVT_PORT_NUM_INVALID; } } else if (strncmp(ifname, IFNAME_NAS, strlen(IFNAME_NAS)) == 0 || strncmp(ifname, IFNAME_AE_WAN, strlen(IFNAME_AE_WAN)) == 0 || strncmp(ifname, IFNAME_PON, strlen(IFNAME_PON)) == 0) { is_wan = true; } else { return ECNT_PRVT_PORT_NUM_INVALID; } return read_port_from_proc(is_wan, eth_port_num); } enum eth_speed ecnt_prvt_link_speed2lib(int speed) { switch (speed) { #define MAP_ECNT_LINK_SPEED(__val, __speed, __duplex) \ case (__val): \ { \ return (__speed); \ } #include "map_link_speed.def" #undef MAP_ECNT_LINK_SPEED default: return ETH_10_Half; } } uint32_t ecnt_prvt_bitrate2capability(char *speed_str, char *duplex_mode_str) { int speed; bool full_duplex; char *end; full_duplex = strcmp(duplex_mode_str, "Full") == 0; if (strcmp(speed_str, "Auto") == 0) { goto err; } speed = strtol(speed_str, &end, 10); if (end == NULL || *end != '\0') { goto err; } /* Convert bitrate and duplex mode to actual capability */ #define MAP_ECNT_BITRATE_RANGE_BOUNDARY(__bound, __full_cap, __half_cap) \ do { \ if (speed >= (__bound)) { \ if (full_duplex) { \ return __full_cap; \ } else { \ return __half_cap; \ } \ } \ } while (0); #include "map_bitrate.def" #undef MAP_ECNT_BITRATE_RANGE_BOUNDARY err: /* Fail-safe defaults */ return full_duplex ? ETH_100_Full : ETH_100_Half; } int ecnt_prvt_get_link_settings(uint32_t port_num, struct eth_link *link) { uint8_t up; uint8_t autoneg; char duplex_mode[TC3162_DUPLEX_MODE_LEN]; char speed_str[TC3162_SPEED_MODE_LEN]; ECNT_SWITCHMGR_LINK_STATE link_state; ECNT_SWITCHMGR_LINK_SPEED link_speed; if (port_num >= 256) { return -1; } if (switchmgr_lib_get_port_admin((uint8_t)port_num, &up) != ECNT_SWITCHMGR_SUCCESS || switchmgr_lib_get_port_autoneg_enable((uint8_t)port_num, &autoneg) != ECNT_SWITCHMGR_SUCCESS || switchmgr_lib_get_port_link_state((uint8_t)port_num, &link_state, &link_speed) != ECNT_SWITCHMGR_SUCCESS || switchmgr_lib_get_port_duplex((uint8_t)port_num, duplex_mode) != ECNT_SWITCHMGR_SUCCESS || switchmgr_lib_get_port_max_bitrate((uint8_t)port_num, speed_str) != ECNT_SWITCHMGR_SUCCESS) { return -1; } link->portid = (int)port_num; link->capability = ecnt_prvt_bitrate2capability(speed_str, duplex_mode); link->autoneg = autoneg; link->speed = ecnt_prvt_link_speed2lib(link_speed); link->fullduplex = (strcmp(duplex_mode, "Full") == 0) ? true : false; link->down = (link_state == ECNT_SWITCHMGR_LINK_UP) ? false : true; link->prio_tagged = false; return 0; } int ecnt_prvt_set_port_state(uint32_t port_num, bool state) { int ret; if (port_num >= 256) { return -1; } ret = switchmgr_lib_set_port_admin((uint8_t)port_num, state ? 1 : 0); return (ret == ECNT_SWITCHMGR_SUCCESS) ? 0 : (-1); }