/* * Copyright. All rights is diminished * * $Author: August 2011-10-20 * * * Ethtool for adsl-switch-0412 */ #include "rtl.h" #ifdef CONFIG_RTL_MULTI_LAN_DEV /* source back-compat hooks */ #define SET_ETHTOOL_OPS(netdev,ops) \ ( (netdev)->ethtool_ops = (ops) ) static struct { const char str[ETH_GSTRING_LEN]; } ethtool_stats_keys[] = { { "rx_octets" }, { "rx_discards" }, { "rx_ucast_packets" }, { "rx_mcast_packets" }, { "rx_bcast_packets" }, { "tx_octets" }, { "tx_discards" }, { "tx_ucast_packets" }, { "tx_mcast_packets" }, { "tx_bcast_packets" }, }; /**************************************** * check whether the port is detected * * user output: Link detected: yes/no ****************************************/ static u32 rtl_ethtool_get_port_link(struct net_device *dev) { uint32 portnum = 0; uint32 regData; //AUG_DBG("dev: %s\n", dev->name); //the user space has check that whether dev exist. // DEV_TO_PORT(dev, portnum); portnum = ethtool_get_port_by_dev(dev); regData = READ_MEM32(PSRP0+((portnum)<<2)); return (regData & PortStatusLinkUp); } /************************************************ * get the Speed, Link Mode, Duplex, * N-way(Auto-neg), etc. * * ************************************************/ static int rtl_ethtool_get_settings(struct net_device *dev, struct ethtool_cmd *ecmd) { uint32 regData; uint32 portnum = 0; uint32 tmp_spd; uint32 phycap = 0; uint32 phystatus = 0; portnum = ethtool_get_port_by_dev(dev); /*read supported speed, duplex, and ability*/ rtl8651_getAsicEthernetPHYSupportedAbility(portnum, &phycap); rtl8651_getAsicEthernetPHYStatus(portnum, &phystatus); ecmd->supported = 0; /*set supported speed and duplex*/ if (phycap & (1 << HALF_DUPLEX_10M)) { ecmd->supported |= SUPPORTED_10baseT_Half; } if (phycap & (1 << DUPLEX_10M)) { ecmd->supported |= SUPPORTED_10baseT_Full; } if (phycap & (1 << HALF_DUPLEX_100M)) { ecmd->supported |= SUPPORTED_100baseT_Half; } if (phycap & (1 << DUPLEX_100M)) { ecmd->supported |= SUPPORTED_100baseT_Full; } if (phycap & (1 << HALF_DUPLEX_1000M)) { ecmd->supported |= SUPPORTED_1000baseT_Half; } if (phycap & (1 << DUPLEX_1000M)) { ecmd->supported |= SUPPORTED_1000baseT_Full; } /*set supported ability*/ if (phystatus & STS_CAPABLE_NWAY_AUTONEGO) { ecmd->supported |= SUPPORTED_Autoneg; } ecmd->supported |= (SUPPORTED_TP |SUPPORTED_Pause); /*read advertising speed and duplex*/ rtl8651_getAsicEthernetPHYAdvCapability(portnum, &phycap); ecmd->advertising = 0; /*set supported speed and duplex*/ if (phycap & (1 << HALF_DUPLEX_10M)) { ecmd->advertising |= SUPPORTED_10baseT_Half; } if (phycap & (1 << DUPLEX_10M)) { ecmd->advertising |= SUPPORTED_10baseT_Full; } if (phycap & (1 << HALF_DUPLEX_100M)) { ecmd->advertising |= SUPPORTED_100baseT_Half; } if (phycap & (1 << DUPLEX_100M)) { ecmd->advertising |= SUPPORTED_100baseT_Full; } if (phycap & (1 << HALF_DUPLEX_1000M)) { ecmd->advertising |= SUPPORTED_1000baseT_Half; } if (phycap & (1 << DUPLEX_1000M)) { ecmd->advertising |= SUPPORTED_1000baseT_Full; } ecmd->advertising |= (ADVERTISED_Autoneg | ADVERTISED_Pause | ADVERTISED_TP); /*read port status register*/ regData = READ_MEM32(PSRP0+((portnum)<<2)); ecmd->port = PORT_TP; ecmd->autoneg = (regData & PortStatusNWayEnable) ? AUTONEG_ENABLE : AUTONEG_DISABLE; ecmd->duplex = (regData & PortStatusDuplex) ? DUPLEX_FULL : DUPLEX_HALF; ecmd->transceiver = XCVR_INTERNAL; ecmd->phy_address = portnum; tmp_spd = regData & PortStatusLinkSpeed_MASK; //if the port is not linked, we set the speed as "UNKOWN!". if(rtl_ethtool_get_port_link(dev)) ecmd->speed = tmp_spd==PortStatusLinkSpeed100M ? SPEED_100 : ( tmp_spd==PortStatusLinkSpeed1000M? SPEED_1000 : SPEED_10); else ecmd->speed = 0; return 0; } #define SPEED10M 0 #define SPEED100M 1 #define SPEED1000M 2 #define EMB_PHY_ID 0xbb804050 static int rtl_ethtool_set_settings(struct net_device *dev, struct ethtool_cmd *cmd) { int port; unsigned int phyid; int forceLinkSpeed = 0; uint32 phyCap = 0; port = ethtool_get_port_by_dev(dev); if (port < 0) return -ENODEV; //printk("%s(%d) cmd->autoneg = %d, cmd->speed = %d, cmd->duplex = %d\n", __func__, __LINE__, cmd->autoneg, cmd->speed, cmd->duplex); phyid = (REG32(EMB_PHY_ID) >> (port*5)) & 0x1f; if (cmd->autoneg) { switch (cmd->speed) { case SPEED_1000: phyCap |= ((1 << HALF_DUPLEX_1000M) | ((cmd->duplex)? (1 << DUPLEX_1000M):0)); forceLinkSpeed = SPEED1000M; case SPEED_100: phyCap |= ((1 << HALF_DUPLEX_100M) | ((cmd->duplex)? (1 << DUPLEX_100M):0)); forceLinkSpeed = ((forceLinkSpeed > SPEED100M)? forceLinkSpeed:SPEED100M); case SPEED_10: phyCap |= ((1 << HALF_DUPLEX_10M) | ((cmd->duplex)? (1 << DUPLEX_10M):0)); forceLinkSpeed = ((forceLinkSpeed > SPEED10M)? forceLinkSpeed:SPEED10M); break; default: return -EINVAL; } } else { switch (cmd->speed) { case SPEED_1000: phyCap |= (cmd->duplex)? (1 << DUPLEX_1000M):(1 << HALF_DUPLEX_1000M); forceLinkSpeed = SPEED1000M; break; case SPEED_100: phyCap |= (cmd->duplex)? (1 << DUPLEX_100M):(1 << HALF_DUPLEX_100M); forceLinkSpeed = SPEED100M; break; case SPEED_10: phyCap |= (cmd->duplex)? (1 << DUPLEX_10M):(1 << HALF_DUPLEX_10M); forceLinkSpeed = SPEED10M; break; default: return -EINVAL; } } //printk("%s(%d): autoneg = %s, phyCap = 0x%02x\n", __func__, __LINE__, cmd->autoneg?"on":"off", phyCap); rtl865xC_setAsicEthernetForceModeRegs(port, !cmd->autoneg, 1, forceLinkSpeed, cmd->duplex); if (cmd->autoneg) { rtl8651_setAsicEthernetPHYNwayAbility(port, phyCap); } else { rtl8651_setAsicEthernetPHYSpeed(port, forceLinkSpeed); } rtl8651_setAsicEthernetPHYDuplex(port, phyCap); rtl8651_setAsicEthernetPHYAutoNeg(port, cmd->autoneg); rtl8651_setAsicEthernetPHYAdvCapability(port, phyCap); rtl8651_restartAsicEthernetPHYNway(port); return 0; } /************************************************ * get the pauseparam. * * shell output: # ethtool --show-pause eth0.2 Pause parameters for eth0.2: Autonegotiate: on RX: off TX: off * ************************************************/ static void rtl_ethtool_get_pauseparam(struct net_device *dev, struct ethtool_pauseparam *pause) { uint32 regData; uint32 portnum = 0; //DEV_TO_PORT(dev, portnum); portnum = ethtool_get_port_by_dev(dev); regData = READ_MEM32(PSRP0+((portnum)<<2)); pause->autoneg = (regData & PortStatusNWayEnable) ? AUTONEG_ENABLE : AUTONEG_DISABLE; pause->rx_pause = (regData & PortStatusRXPAUSE) ? 1 : 0; pause->tx_pause = (regData & PortStatusTXPAUSE) ? 1 : 0; } static int rtl_ethtool_set_pauseparam(struct net_device *dev, struct ethtool_pauseparam *pause) { uint32 port; uint32 pauseFC = 0; uint32 offset; port = ethtool_get_port_by_dev(dev); offset = port << 2; if (pause->rx_pause && pause->tx_pause) pauseFC |= PauseFlowControlEtxErx; else if (pause->rx_pause && !pause->tx_pause) pauseFC |= PauseFlowControlDtxErx; else if (!pause->rx_pause && pause->tx_pause) pauseFC |= PauseFlowControlEtxDrx; else if (!pause->rx_pause && !pause->tx_pause) pauseFC = 0; WRITE_MEM32(PCRP0+offset, (~(PauseFlowControl_MASK)&(READ_MEM32(PCRP0+offset)))|pauseFC); TOGGLE_BIT_IN_REG_TWICE(PCRP0 + offset,EnForceMode); rtl8651_restartAsicEthernetPHYNway(port); return 0; } static void rtl_ethtool_get_strings(struct net_device *dev, u32 stringset, u8 *data) { memcpy(data, ethtool_stats_keys, sizeof(ethtool_stats_keys)); } static int rtl_ethtool_get_sset_count(struct net_device *dev, int sset) { switch (sset) { case ETH_SS_STATS: return NR_ASIC_DIAG_COUNTER_USED; default: return -EOPNOTSUPP; } } static void rtl_ethtool_get_ethtool_stats(struct net_device *dev, struct ethtool_stats *stats, u64 * data) { int idx = 0; const uint32 addrOffset_fromP0 = ethtool_get_port_by_dev(dev) * MIB_ADDROFFSETBYPORT; data[idx++] = rtl865xC_returnAsicCounter64(OFFSET_IFINOCTETS_P0 + addrOffset_fromP0); data[idx++] = rtl8651_returnAsicCounter(OFFSET_DOT1DTPPORTINDISCARDS_P0 + addrOffset_fromP0); data[idx++] = rtl8651_returnAsicCounter(OFFSET_IFINUCASTPKTS_P0 + addrOffset_fromP0); data[idx++] = rtl8651_returnAsicCounter(OFFSET_ETHERSTATSMULTICASTPKTS_P0 + addrOffset_fromP0); data[idx++] = rtl8651_returnAsicCounter(OFFSET_ETHERSTATSBROADCASTPKTS_P0 + addrOffset_fromP0); data[idx++] = rtl865xC_returnAsicCounter64(OFFSET_IFOUTOCTETS_P0 + addrOffset_fromP0); data[idx++] = rtl8651_returnAsicCounter(OFFSET_IFOUTDISCARDS + addrOffset_fromP0); data[idx++] = rtl8651_returnAsicCounter(OFFSET_IFOUTUCASTPKTS_P0 + addrOffset_fromP0); data[idx++] = rtl8651_returnAsicCounter(OFFSET_IFOUTMULTICASTPKTS_P0 + addrOffset_fromP0); data[idx++] = rtl8651_returnAsicCounter(OFFSET_IFOUTBROADCASTPKTS_P0 + addrOffset_fromP0); } static void rtl_ethtool_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *drvinfo) { /*printk("\033[9m %s \033[m\n", "RTL Rights Reserved!");*/ } static const struct ethtool_ops rtl_ethtool_ops = { .get_link = ethtool_op_get_link,//rtl_ethtool_get_port_link, .get_drvinfo = rtl_ethtool_get_drvinfo, .get_settings = rtl_ethtool_get_settings, .set_settings = rtl_ethtool_set_settings, .get_pauseparam = rtl_ethtool_get_pauseparam, .set_pauseparam = rtl_ethtool_set_pauseparam, .get_strings = rtl_ethtool_get_strings, .get_sset_count = rtl_ethtool_get_sset_count, .get_ethtool_stats = rtl_ethtool_get_ethtool_stats, }; void rtl_set_ethtool_ops(struct net_device *netdev) { SET_ETHTOOL_OPS(netdev, &rtl_ethtool_ops); } #endif //make wrappers of netlink funcs and make a protocol of kernel-2-user communication struct sock *global_rtlmsg_sock; #ifdef CONFIG_RTL_NLMSG_PROTOCOL void rtl_nlmsg_handler(struct sk_buff *skb) { printk("Nothing to do!!!\n"); return; } int rtl_gbl_rtlmsg_sock_init(void) { global_rtlmsg_sock = netlink_kernel_create(&init_net, NETLINK_RTL_NLMSG, 0, rtl_nlmsg_handler, NULL, THIS_MODULE); if(!global_rtlmsg_sock) { printk("Fail to create netlink socket.\n"); return -1; } return 0; } struct sk_buff* rtl_nlmsg_alloc_skb(const RTL_ST_NLMSG* p_nlmsg) { int len; struct sk_buff *skb; len = NLMSG_SPACE(RTL_NLMSG_HEAD_SZ + p_nlmsg->length); skb = alloc_skb(len, GFP_ATOMIC); return skb; } void rtl_nlmsg_set_skb(struct sk_buff *skb, const RTL_ST_NLMSG* p_nlmsg, u32 pid, u32 seq) { struct nlmsghdr *nlhdr; int len; RTL_ST_NLMSG* tmp_nlmsg; len = NLMSG_SPACE(RTL_NLMSG_HEAD_SZ + p_nlmsg->length); nlhdr = __nlmsg_put(skb, pid, seq, 0, (len - sizeof(struct nlmsghdr)), 0); tmp_nlmsg = (RTL_ST_NLMSG *)(NLMSG_DATA(nlhdr)); memcpy(tmp_nlmsg, p_nlmsg, RTL_NLMSG_HEAD_SZ + p_nlmsg->length); return; } int rtl_nlmsg_broadcast(struct sock *ssk, struct sk_buff *skb, RTL_NL_GRPS dest_grp, gfp_t allocation) { //august : the param 'dest_group' is uesd strangly, but there it is. //august : I 've checked linux version from 2.6.30 to 3.1.0, the usage and codes do not change! //august : this will be sended to linux-dev-mailist. return netlink_broadcast(ssk, skb, 0, (1 << (dest_grp - 1)), GFP_KERNEL); } /***@@@@@@@@@@@%%%%%%%%%######### SERIOUS NOTE: This func is called in the bottom half func. @@@@@@@@@@@%%%%%%%%%#########***/ int rtl_nl_send_lkchg_msg(const unsigned int old_ptmks, const unsigned int new_ptmks) { struct sk_buff *skb; RTL_NLMSG_LINKCHG link; link.rtl_magic = RTL_NL_SRC_KENERL | RTL_NL_MAGIC_NUM; link.type = RTL_NL_TYPE_LINKCHANGE; link.length = EXTSIZE_RTL_NLMSG_LINKCHG; link.extended.new_ptmks = new_ptmks; link.extended.old_ptmks = old_ptmks; skb = rtl_nlmsg_alloc_skb((RTL_ST_NLMSG*)&link); if(!skb) { printk("alloc skb failed!\n"); return -55; } rtl_nlmsg_set_skb(skb, (RTL_ST_NLMSG*)&link, 0, 0); return rtl_nlmsg_broadcast(global_rtlmsg_sock, skb, RTL_BASIC_GRP, GFP_KERNEL); } #endif