Linux下網絡卡phy狀態檢測與控制
最近在一個專案中,整合一個交換機晶片的時候,遇到一些麻煩,發現交換機的效能總是上
不去,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,
or return -1 if errors occur.
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;
int link_status;
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)
link_status = detect_mii(skfd, ifname);
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;
struct ethtool_value edata;
memset(&ifr, 0, sizeof(ifr));
edata.cmd = ETHTOOL_GLINK;
strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)-1);
ifr.ifr_data = (char *) &edata;
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;
struct mii_ioctl_data *mii_val;
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暫存器的
讀寫。