最近在一个项目中,集成一个交换机芯片的时候,遇到一些麻烦,发现交换机的性能总是上
不去,100M的交换机,实际交换能力只有10M。跟做硬件的同事一起,花了几周时间调试,
才找到问题。原来是接到交换机芯片上的几个子系统,用的Micrel 8041PHY芯片,默认关闭
了硬件流控,导致交换机无法通过流控来控制网络数据交换,结果使得其性能下降。而交换
机每个端口的PHY与子系统的PHY都使用的Auto Negotiation来协商链接状态,子系统默认不
支持流控,交换机也关闭了流控。
    解决方法实际上很简单,就是在子系统端打开PHY的流控,其实也就是寄存器的一个位而已,
却花了我们几周时间。究其原因,都是由于大部分PHY芯片默认都打开流控,所以导致我们
一开始没怀疑到这上面,而且也没有引出交换机的控制口查看其状态。

  子系统是跑的嵌入式Linux系统,所以在这几周的调试过程中,着重查看了Linux Kernel中
PHY的驱动,以及相关的一些系统调用。有些心得,记录下来。

  Linux系统提供了两类ioctl系统调用SIOCETHTOOL和SIOCXMIIXXX,用于控制或者获取网卡
PHY的状态。这两类系统调用的实现取决于PHY驱动中对应ioctl的实现,一般的PHY驱动都
会实现至少其中的一类。下面以获取网卡的Link状态来说明这两类系统调用的使用。

0. Create socket handler
由于这两类调用都要使用socket handler,我们先建立一个socket handler用于下面的系统
调用。

/*--------------------------------------------------------------------------*/
 /**
   @brief    Detect eth link status from ETHTOOL API and MII reg.
   @param    ifname -- interface name.
   @return   Return true if link is up, return false if link is down,
             This function will first try to get eth link status from ETHTOOL API.
   If it fails, it will try to get eth link status from MII reg.
  */
 /*--------------------------------------------------------------------------*/
 int get_netlink_status(char *ifname)
 {
     int skfd = -1;
       if (ifname == NULL) {
         ifname = "eth0";
      
     if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
         nl_err("socket error\n");
         return -1;
       link_status = detect_ethtool(skfd, ifname);
     if (link_status < 0)
           close(skfd);
     return link_status;
 }


1. SIOCETHTOOL ioctl
下面是SIOCETHTOLL系统调用的用法。

/*--------------------------------------------------------------------------*/
 /**
   @brief    Detect eth link status from ETHTOOL API.
   @param    skfd   -- socket handler.
   @param    ifname -- interface name.
   @return   Return true if link is up, return false if link is down,
             or return -1 if errors occur.
  */
 /*--------------------------------------------------------------------------*/
 int detect_ethtool(int skfd, char *ifname)
 {
     struct ifreq ifr;
       memset(&ifr, 0, sizeof(ifr));
       strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)-1);
       if (ioctl(skfd, SIOCETHTOOL, &ifr) == -1) {
         nl_err("ETHTOOL_GLINK failed: %s\n", strerror(errno));
         return -1;
       return (edata.data ? 1 : 0);
 }

对应内核代码[linux/net/core/dev.c]中dev_ioctl()函数,对应的SIOCETHTOOL,又调用了
[linux/net/core/ethtool.c]中的实现,而dev_ethtool()函数,而这个函数最终又会调用
相应if驱动的ethtool_ops结构体中注册的函数来实现寄存器的操作。当然,不同的PHY驱动
对此有不同的操作,甚至有些PHY驱动根本没有注册这个结构体,这时,我们就要尝试下面
的方法了。


2. SIOCxMIIxxx ioctl
下面是SIOCxMIIxxx系统调用的用法。

/*--------------------------------------------------------------------------*/
 /**
   @brief    Detect eth link status from MII status reg.
   @param    skfd   -- socket handler.
   @param    ifname -- interface name.
   @return   Return true if link is up, return false if link is down,
             or return -1 if errors occur.
  */
 /*--------------------------------------------------------------------------*/
 static int detect_mii(int skfd, char *ifname)
 {
     struct ifreq           ifr;
      
     strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
     if (ioctl(skfd, SIOCGMIIPHY, &ifr) < 0) {
         fprintf(stderr, "SIOCGMIIPHY on %s failed: %s\n", ifname,
                 strerror(errno));
         (void) close(skfd);
         return -1;
       mii_val = (struct mii_ioctl_data *)(&ifr.ifr_data);
     mii_val->reg_num = MII_BMSR;       if (ioctl(skfd, SIOCGMIIREG, &ifr) < 0) {
         nl_err("SIOCGMIIREG on %s failed: %s\n", ifr.ifr_name,
                strerror(errno));
         return -1;
       if ((!(mii_val->val_out & BMSR_RFAULT)) &&
           (mii_val->val_out & BMSR_LSTATUS) &&
         (!(mii_val->val_out & BMSR_JCD))) {
         return 1;
     } else {
         return 0;
     }
 }

对应内核代码[linux/net/core/dev.c]中dev_ioctl()函数,对应的SIOCxMIIxxx,又调用了
dev_ifsioc()函数,这个函数最终调用PHY驱动中注册的do_ioctl()函数来实现PHY寄存器的
读写。