1.原理概述
1.1 网卡驱动概述
一块以太网网卡包括OSI模型的两个层:物理层和数据链路层。数据链路层的芯片简称为MAC控制器,物理层的芯片简称为PHY。
MAC主要负责控制与连接物理层的物理介质。在发送数据时,MAC先判断是否可以发送数据,如果可以发送,给数据加上控制信息,最终将数据及控制信息按规定的格式发送到物理层;在接收数据的时候,MAC先判断信息是否发生传输错误,如果没有错误,去掉控制信息发送至LLC层。该层协议由IEEE-802.3以太网标准定义。
PHY是物理接口收发器,它实现物理层。IEEE-802.3标准定义了以太网PHY。在发送数据时,收到MAC数据,把并行数据转化为串行流数据,再按照物理层的编码规则编码,再变为模拟信号发送数据。收数据时流程反之。PHY还有个重要的功能是实现CSMA/CD的部分功能。
PHY和MAC之间的关系是PCI总线接MAC总线,MAC接PHY,PHY接网线。
在以太网网卡驱动中,完全可以将PHY芯片的驱动抽象,做成通用接口。(IEEE802.3定义PHY芯片地址0-15寄存器的功能,地址16-31寄存器留给芯片制造商自由定义)。
2.技术实现
2.1 PHY驱动目录
PHY芯片驱动在SylixOS的Base中已经给出,如图 3.1所示。
图 3.1 PHY驱动目录
SylixOS提供的PHY芯片驱动能支持10Mb,100Mb,1000Mb的链接能力。
2.2 PHY驱动框架
PHY驱动大致流程为:
初始化组件库(信号量,定时器)。
选择自动查找/指定查找PHY设备。
选择自动协商/指定链接模式。
启用定时器每隔一段时间查询链接状态。
如图 3.2所示。
图 3.2 PHY驱动框架
在网卡驱动中,先对程序清单 3.1进行传参,再调用API_MiiPhyInit接口就能完成PHY芯片初始化。
程序清单 3.1传参
pmiidrv->MIID_phydev.PHY_pPhyDrvFunc->PHYF_pfuncWrite = (FUNCPTR)__miiPhyWrite;
pmiidrv->MIID_phydev.PHY_pPhyDrvFunc->PHYF_pfuncRead = (FUNCPTR)__miiPhyRead;
pmiidrv->MIID_phydev.PHY_pPhyDrvFunc->PHYF_pfuncLinkDown = (FUNCPTR)__miiLinkStatus;
pmiidrv->MIID_phydev.PHY_pvMacDrv = pmiidrv;
pmiidrv->MIID_phydev.PHY_ucPhyAddr = ENET_PHYADDR;
pmiidrv->MIID_phydev.PHY_uiPhyID = FEC_ENET_PHYID;
pmiidrv->MIID_phydev.PHY_uiPhyIDMask =0x00000000; /* 同系列芯片不同信号识别 */
pmiidrv->MIID_phydev.PHY_uiTryMax = 100;
pmiidrv->MIID_phydev.PHY_uiLinkDelay = 100; /* 延时100毫秒自动协商过程 */
pmiidrv->MIID_phydev.PHY_uiPhyFlags = MII_PHY_AUTO | /* 自动协商标志 */
MII_PHY_FD | /* 全双工模式 */
MII_PHY_100 | /* 100Mbit */
MII_PHY_10 | /* 10Mbit */
MII_PHY_HD | /* 半双工模式 */
MII_PHY_MONITOR; /* 启用自动监视功能 */
MAC能对PHY芯片进行读写操作(miiPhyWrite函数,miiPhyRead函数)。
链接状态变化函数(miiLinkStatus函数,打印链接信息并进行相关操作)。
控制参数传参(PHY驱动会根据传入参数进行不同操作,如是否开启自动协商,选择速度/双工模式,开启自动监视功能)。
2.3 具体实现
2.3.1 API_MiiPhyInit函数实现
API_MiiPhyInit函数会根据传入参数分别执行初始化组件库,查找PHY设备,设置链接能力。
PHY驱动提供链表将有效PHY设备加入到MII链表,能对多个PHY芯片进行操作。
如程序清单 3.2所示。
程序清单 3.2 API_MiiPhyInit函数实现
INTAPI_MiiPhyInit (PHY_DEV *pPhyDev)
{
……
if (API_MiiLibInit() == MII_ERROR) {
return (MII_ERROR);
}
if (pPhyDev->PHY_ucPhyAddr == 0) { /* Auto scan phydevice */
if (API_MiiPhyScan(pPhyDev) == MII_ERROR) {
_DebugHandle(__ERRORMESSAGE_LEVEL, "can notfind phy device.\r\n");
return (MII_ERROR);
}
} else { /* Test specific phy device */
if (API_MiiPhyProbe(pPhyDev) != MII_OK) {
MII_DEBUG_ADDR("can notfind phy device. addr[%02x]\r\n",
pPhyDev->PHY_ucPhyAddr);
return (MII_ERROR);
}
if (API_MiiPhyDiagnostic(pPhyDev) != MII_OK) {
return (MII_ERROR);
}
}
……
if (API_MiiPhyLinkSet(pPhyDev) != MII_OK) {
MII_DEBUG_ADDR("mii:found phy [%02x], but Link-Down.\r\n",
pPhyDev->PHY_ucPhyAddr);
}
usPhyStatus = pPhyDev->PHY_usPhyStatus; /* Remember Link Status */
/* Get The New Status */
……
return (iRet); /* MII_ERROR orMII_OK */
}
2.3.2 API_MiiLibInit函数实现
API_MiiLibInit函数初始化组件库。大致为创建信号量,定时器,并开启定时器,检测链接能力。(__miiPhyMonitor函数主要读取PHY芯片状态,更新状态信息)。
如程序清单 3.3所示。
程序清单 3.3 API_MiiLibInit函数实现
INTAPI_MiiLibInit (VOID)
{
……
_G_hMiiMSem = API_SemaphoreMCreate("mii_lock", LW_PRIO_DEF_CEILING,
LW_OPTION_WAIT_PRIORITY | LW_OPTION_DELETE_SAFE |
LW_OPTION_INHERIT_PRIORITY | LW_OPTION_OBJECT_GLOBAL,
LW_NULL); /* Create MIIMutex Semaphore */
_G_hMiiTimer = API_TimerCreate("mii_timer", LW_OPTION_ITIMER | LW_OPTION_OBJECT_GLOBAL, LW_NULL);
if (_G_hMiiTimer == 0) { /* Create mii timer */
return (MII_ERROR);
}
……
if (API_TimerStart(_G_hMiiTimer, /* Start Phy Monitor */
(MII_LINK_CHK_DELAY * LW_TICK_HZ),
LW_OPTION_AUTO_RESTART,
(PTIMER_CALLBACK_ROUTINE)__miiPhyMonitor,
LW_NULL)) {
API_SemaphoreMDelete(&_G_hMiiMSem);
return (MII_ERROR);
}
……
return (MII_OK);
}
2.3.3 API_MiiPhyScan函数实现
API_MiiPhyScan函数自动查找PHY设备。大致为从0-32对所有PHY设备地址进行查找,若存在设备,测试PHY是否有效。
如程序清单 3.4所示。
程序清单 3.4 API_MiiPhyScan函数实现
INTAPI_MiiPhyScan (PHY_DEV *pPhyDev)
{
……
for (i = 0; i < MII_MAX_PHY_NUM; i++, pPhyDev->PHY_ucPhyAddr++) {
iRet = API_MiiPhyProbe(pPhyDev);
if (iRet != MII_OK) {
continue;
}
if (API_MiiPhyDiagnostic(pPhyDev) != MII_OK) {
return (MII_ERROR);
}
return (MII_OK); /* Found aValid PHY */
}
return (MII_PHY_NULL);
}
2.3.4 API_MiiPhyProbe,API_MiiPhyDiagnostic函数实现
API_MiiPhyProbe函数检测PHY设备是否存在,大致为读取PHY寄存器ID,进行匹配,返回是否成功。如程序清单 3.5所示。
程序清单 3.5 API_MiiPhyProbe函数实现
INTAPI_MiiPhyProbe (PHY_DEV *pPhyDev)
{
……
if (MII_READ(pPhyDev, MII_PHY_ID1_REG, &usID1) == MII_ERROR) {
return (MII_ERROR);
}
if (MII_READ(pPhyDev, MII_PHY_ID2_REG, &usID2) == MII_ERROR) {
return (MII_ERROR);
}
uiPhyID = usID1 | (usID2 << 16);
if ((pPhyDev->PHY_uiPhyID & pPhyDev->PHY_uiPhyIDMask) !=
(uiPhyID & pPhyDev->PHY_uiPhyIDMask)) {
return (MII_PHY_NULL); /* phyId不匹配 */
}
return (MII_OK); /* phyId匹配 */
}
API_MiiPhyDiagnostic函数测试PHY是否有效,具体为复位PHY是否成功,检测PHY是否处于物理隔离状态。如程序清单 3.6所示。
程序清单 3.6 API_MiiPhyDiagnostic函数实现
INTAPI_MiiPhyDiagnostic (PHY_DEV *pPhyDev)
{
……
iRet = MII_WRITE(pPhyDev, ucRegAddr, usData); /* Reset thePHY */
if (iRet != MII_OK) {
return (MII_ERROR);
}
for (i = 0; i < pPhyDev->PHY_uiTryMax; i++) {
API_TimeMSleep(pPhyDev->PHY_uiLinkDelay);
if (MII_READ(pPhyDev, ucRegAddr, &usData) == MII_ERROR) {
return (MII_ERROR);
}
}
……
usData = MII_CTRL_NORM_EN; /* re-enable the chip */
if (MII_WRITE(pPhyDev, ucRegAddr, usData) == MII_ERROR) {
return (MII_ERROR);
}
for (i = 0; i < pPhyDev->PHY_uiTryMax; i++) {
API_TimeMSleep(pPhyDev->PHY_uiLinkDelay);
if (MII_READ(pPhyDev, ucRegAddr, &usData) == MII_ERROR) {
return (MII_ERROR);
}
return (MII_OK);
}
2.3.5 API_MiiPhyLinkSet函数实现
API_MiiPhyLinkSet函数设置PHY链接模式。如程序清单 3.7所示。
程序清单 3.7 API_MiiPhyLinkSet函数实现
INTAPI_MiiPhyLinkSet (PHY_DEV *pPhyDev)
{
……
if (__miiAbilFlagUpdate(pPhyDev) == MII_ERROR) {
return (MII_ERROR);
}
……
iRet = API_MiiPhyModeSet(pPhyDev);
……
return (MII_OK);
}
API_MiiPhyModeSet函数根据参数选择自动协商/手动设置。
如程序清单 3.8所示。
程序清单 3.8 API_MiiPhyModeSet函数实现
INTAPI_MiiPhyModeSet (PHY_DEV *pPhyDev)
{
if (pPhyDev->PHY_uiPhyFlags & MII_PHY_AUTO) { /* AutoNegotiationenabled */
if (__miiAutoNegotiate(pPhyDev) == MII_OK) {
return (MII_OK);
}
} else { /* 未开启自动协商功能 */
if (__miiModeForce(pPhyDev) == MII_OK) {
if (__miiFlagsHandle(pPhyDev) == MII_OK) { /* handlesome flags */
return (MII_OK);
}
}
}
return (MII_ERROR);
}
__miiAutoNegotiate函数进行自动协商并检查协商是否成功。__miiBasicCheck用于检查是否link up/remote fault。如程序清单 3.9所示。
程序清单 3.9 __miiAutoNegotiate函数实现
staticINT__miiAutoNegotiate (PHY_DEV *pPhyDev)
{
……
/*
* start theauto-negotiation process: return
* only in caseof fatal error.
*/
iRet = __miiAutoNegStart(pPhyDev);
……
/* check the negotiation was successful */
if (!(pPhyDev->PHY_uiPhyFlags & MII_PHY_NWAIT_STAT)) {
if (__miiAnCheck(pPhyDev) == MII_OK) {
return (MII_OK);
}
}
return (MII_ERROR);
}
__miiAutoNegStart函数开始自动协商。具体为开始自动协商并根据传入参数选择等待自动协商结束/立即返回。如程序清单 3.10所示。
程序清单 3.10 __miiAutoNegStart函数实现
staticINT__miiAutoNegStart (PHY_DEV *pPhyDev)
{
……
/*
* restart the auto-negotiation process
*/
ucRegAddr = MII_CTRL_REG;
usData = (MII_CTRL_RESTART | MII_CTRL_AUTO_EN);
if (MII_WRITE(pPhyDev, ucRegAddr, usData) != MII_OK) {
return (MII_ERROR);
}
/*
* let's check the PHY status forcompletion
*/
if (!(pPhyDev->PHY_uiPhyFlags & MII_PHY_NWAIT_STAT)) {
ucRegAddr = MII_STAT_REG;
do { /* spin until it is done */
API_TimeMSleep(pPhyDev->PHY_uiLinkDelay);
if (i++ == pPhyDev->PHY_uiTryMax)
break;
if (MII_READ(pPhyDev, ucRegAddr, &usPhyStatus) != MII_OK) {
return (MII_ERROR);
}
} while ((usPhyStatus & MII_SR_AUTO_NEG) != MII_SR_AUTO_NEG);
……
return (MII_OK);
}
2.3.6 __miiModeForce函数实现
__miiModeForce函数根据传入参数,设成指定链接模式(若多个参数,指定最高链接模式)。如程序清单 3.11所示。
程序清单 3.11 __miiModeForce函数实现
staticINT__miiModeForce (PHY_DEV *pPhyDev)
{
…… /* 100Mb/s full */
if (MII_PHY_FLAGS_JUDGE(MII_PHY_100) && MII_PHY_FLAGS_JUDGE(MII_PHY_FD)) {
usData = MII_CTRL_NORM_EN;
usData |= MII_CTRL_100;
usData |= MII_CTRL_FDX;
__miiForceAttempt(pPhyDev, usData);
MII_PHY_ABILITY_FLAGS_SET(MII_PHY_100 | MII_PHY_FD);
return (MII_OK);
}
……
return (MII_ERROR);
}
__miiForceAttempt函数设成指定链接模式,并检查PHY状态。__miiBasicCheck函数用于检查PHY状态是否正确。如程序清单 3.12所示。
程序清单 3.12 __miiForceAttempt函数实现
staticINT__miiForceAttempt (PHY_DEV *pPhyDev, UINT16 usData)
{
if (MII_WRITE(pPhyDev, MII_CTRL_REG, usData) != MII_OK) {
return (MII_ERROR);
}
if (__miiBasicCheck(pPhyDev) != MII_OK) {
return (MII_ERROR);
}
return (MII_OK);
}
2.3.7 __miiPhyMonitor函数实现
__miiPhyMonitor函数持续监测PHY状态。具体为每隔一段时间检测PHY链接状态并更新状态信息,当检测到为失去链接时调用PHYF_pfuncLinkDown函数(实际为之前的miiLinkStatus函数)。如程序清单 3.13所示。
程序清单 3.13 __miiPhyMonitor函数实现
staticINT__miiPhyMonitor (VOID)
{
……
iRet = MII_READ(pPhyDev, MII_STAT_REG, &usPhyStatus);
if (iRet == MII_ERROR) {
goto __mii_monitor_exit;
}
/*
* is the PHY's status linkchanged?
*/
if ((pPhyDev->PHY_usPhyStatus & MII_SR_LINK_STATUS) !=
(usPhyStatus & MII_SR_LINK_STATUS)) {
if (usPhyStatus & MII_SR_LINK_STATUS) {
if (pPhyDev->PHY_uiPhyFlags & MII_PHY_AUTO) {
__miiAbilFlagUpdate(pPhyDev);
__miiPhyUpdate(pPhyDev);
} else {
__miiFlagsHandle(pPhyDev);
}
}
if (pPhyDev->PHY_pPhyDrvFunc->PHYF_pfuncLinkDown != LW_NULL) {
API_NetJobAdd((VOIDFUNCPTR)(pPhyDev->PHY_pPhyDrvFunc->PHYF_pfuncLinkDown),
(PVOID)(pPhyDev->PHY_pvMacDrv), 0, 0, 0, 0,0);
pPhyDev->PHY_usPhyStatus = usPhyStatus;
}
……
return (iRet);
}
2.4 实际运用
__phyInit函数为在实际网卡驱动中编写的PHY芯片初始化驱动程序。具体为调用miiDrvInit函数写入参数,调用API_MiiPhyInit函数进行PHY芯片初始化。
如程序清单 3.14所示。
程序清单 3.14 __phyInit函数实现
staticINT__phyInit (struct netdev *pNetDev)
{
……
pEnet = &_G_enetInfo;
pnetdev = &pEnet->ENET_netdev;
pmiidrv = miiDrvInit();
if (!pmiidrv) {
return (PX_ERROR);
}
pEnet->ENET_miidrv = pmiidrv;
pmiidrv->MIID_enet = pEnet;
pnetdev->priv = (PVOID)pEnet;
iRet = API_MiiPhyInit(&(pEnet->ENET_miidrv->MIID_phydev));
if (iRet == MII_OK) {
pEnet->ENET_iMiiInit = 1;
}
return (ERROR_NONE);
}
miiDrvInit函数将API_MiiPhyInit中需要用的参数进行传参。封装MAC对PHY的读写函数,指定工作模式。如程序清单 3.15所示。
程序清单 3.15 miiDrvInit函数实现
MII_DRV *miiDrvInit (VOID)
{
……
pmiidrv->MIID_phydev.PHY_pPhyDrvFunc->PHYF_pfuncWrite = (FUNCPTR)miiPhyWrite;
pmiidrv->MIID_phydev.PHY_pPhyDrvFunc->PHYF_pfuncRead = (FUNCPTR)miiPhyRead;
pmiidrv->MIID_phydev.PHY_pPhyDrvFunc->PHYF_pfuncLinkDown = (FUNCPTR)miiLinkStatus;
pmiidrv->MIID_phydev.PHY_pvMacDrv = pmiidrv;
pmiidrv->MIID_phydev.PHY_ucPhyAddr = ENET_PHYADDR;
pmiidrv->MIID_phydev.PHY_uiPhyID = FEC_ENET_PHYID;
pmiidrv->MIID_phydev.PHY_uiPhyIDMask =0x00000000; /* 同系列芯片不同信号识别 */
pmiidrv->MIID_phydev.PHY_uiTryMax = 100;
pmiidrv->MIID_phydev.PHY_uiLinkDelay = 100; /* 延时100毫秒自动协商过程 */
pmiidrv->MIID_phydev.PHY_uiPhyFlags = MII_PHY_AUTO | /* 自动协商标志 */
MII_PHY_FD | /* 全双工模式 */
MII_PHY_100 | /* 100Mbit */
MII_PHY_10 | /* 10Mbit */
MII_PHY_HD | /* 半双工模式 */
MII_PHY_MONITOR; /* 启用自动监视功能 */
return (pmiidrv);
}
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。