千兆網口Freescale-ETSEC-+-Marvell-88E1111-uboot-Linux-驅動分析
1 千兆以太網的物理層
千兆以太網的物理層分爲物理編碼子層PCS(Physical Coding Sublayer)、物理介質連接子層PMA(Physical Medium Attachment)和物理介質相關子層PMD(Physical Medium Dependent)三層,如下圖所示:
其中PCS子層負責8b10b編碼,它可以把從GMII口接收到的8位並行的數據轉換成10位並行的數據輸出。因爲10比特的數據能有效地減小直流分量,降低誤碼率,另外採用8b10b編碼便於在數據中提取時鐘和進行首發同步。可以把PCS兩頭看成GMII接口和TBI接口。
PMA子層進一步將PCS子層的編碼結果向各種物理媒體傳送,主要是負責完成串並轉換。PCS層以125M的速率並行傳送10位代碼到PMA層,由PMA層轉換爲1.25Gbps的串行數據流進行發送,以便實際能得到1Gbps的千兆以太網傳送速率。可以把PMA子層的兩頭分別看做TBI接口和SGMII接口。
PMD子層將對各種實際的物理媒體完成接口,完成真正的物理連接。由於1000BASE-X支持多種物理媒介,如光纖和屏蔽雙絞線,它們的物理接口顯然不會相同。有的要進行光電轉換,有的要完成從不平衡到平衡的轉換。PMD層將對這些具體的連接器作出規定。
2 Freescale 的ETSEC與PHY之間的接口
Freescale的MPC8314和P2020都自帶了三速以太網控制器ETSEC,可以提供10M,100M,1000M三種速率的接口。當作爲以太網時,需要外部的PHY芯片或者Serdes設備與其相連接。每個ETSEC都支持多標準的MII接口,總體結構如下圖所示,可以提供GMII,RGMII,MII,RMII,RTBI,SGMII 六種接口,下圖爲從MPC8314 datasheet中截取的ETSEC的結構圖。
如果CPU與PHY之間是GMII接口或RGMII接口,那麼PHY將提供完整的PCS,PMA,PMD三層工作;如果CPU與PHY之間是RTBI接口,那麼PCS層的工作在ETSEC中已經做完了,ETSEC中的TBI模塊可以做PCS層的工作,PHY只需要做PMA和PMD的工作即可;如果CPU與PHY之間是SGMII接口,那麼PHY只需要完成PMD的工作,ETSEC中的PCS由TBI完成,而PMA由CPU自帶的Serdes模塊完成。
3 BD表結構
在千兆以太網的驅動中,現在一般都使用一個叫BD表的東西來管理MAC層發送和接收的內存區域,如下圖所示:
在IMMR映射的寄存器空間中有兩組寄存器TBASEn和RBASEn,分別爲TxBD Ringn 和 RxBD Ringn的指針。MPC8314的ETSEC允許有8個TxBD Ring和8個RxBD Ring,他們都存放在內存的某個區域中。每個Buffer Descriptor 都是有8個字節構成,兩個字節的狀態,兩個字節的數據長度和四個字節的數據指針,這個指針指向內存的另一塊地方,這纔是真正存儲發送接收數據的地方。Buffer Descriptor必須在網口初始化的時候初始化,並將自己的地址賦給TBASEn和RBASEn。
在網口驅動程序中可以看到,每個BD Ring中的BD數量是可變的(我們設爲64),而他們之間並沒有指針連接,只是一段連續的空間,順序下來的,所謂的環只是一個虛擬的概念,在最後一個BD時,需要將BD狀態位中的W位(Wrap)置一,表示這是最後一個BD,他的下一個BD就是第一個BD。如下圖所示:
下面一節將結合uboot源碼分析一下網口初始化以及PHY配置的過程,再下一節會分析內核中的驅動。爲什麼先說uboot,因爲在我看來,驅動程序就是分爲兩個部分,1 按照Datasheet的說明去配置寄存器,2 添加符合操作系統規範去融入操作系統。在uboot下系統很簡單,代碼一目瞭然,所以我們應該在boot下先把寄存器配置好,再去分析複雜的多的內核代碼。
這節分析uboot中的網口驅動代碼。
1 網口驅動函數列表
函數名 |
函數用途 |
tsec_initialize() |
網口初始化函數 |
tsec_init() |
網口啓動函數 |
tsec_local_mdio_write() |
MDIO口寫函數 |
tsec_local_mdio_read() |
MDIO口讀函數 |
tsec_send() |
網口發送函數 |
tsec_recv() |
網口接收函數 |
tsec_configure_serdes() |
配置TBI PHY的函數 |
fsl_serdes_init() |
Serdes模塊初始化函數 |
init_phy() |
PHY初始化函數 |
adjust_link() |
根據PHY狀態配置MAC的函數 |
2 tsec_initialize()函數
該函數爲ETSEC的初始化函數,在該函數中要初始化eth_device結構和私有的tsec_private結構,並初始化PHY。
int tsec_initialize(bd_t * bis, int index, char *devname)
{
struct eth_device *dev;
int i;
struct tsec_private *priv;
/*爲dev分配空間*/
dev = (struct eth_device *)malloc(sizeof *dev);
if (NULL == dev)
return 0;
memset(dev, 0, sizeof *dev);
/*爲priv分配空間*/
priv = (struct tsec_private *)malloc(sizeof(*priv));
if (NULL == priv)
return 0;
/*從tsec_info 數組中取合適的值去初始化私有結構tsec_private*/
privlist[num_tsecs++] = priv;
priv->regs = tsec_info[index].regs; //每個tsec寄存器的基址
priv->phyregs = tsec_info[index].miiregs; //PHY MDIO讀寫狀態寄存器基址
/*TBI PHY的MDIO讀寫狀態寄存器基址*/
priv->phyregs_sgmii = tsec_info[index].miiregs_sgmii;
priv->phyaddr = tsec_info[index].phyaddr; //PHY 地址
priv->flags = tsec_info[index].flags;
priv->ID = index;
/*使用將priv結構體掛到dev結構體下,掛載tsec的打開、關閉、發送、接收函數*/
sprintf(dev->name, tsec_info[index].devname);
dev->iobase = 0;
dev->priv = priv;
dev->init = tsec_init;
dev->halt = tsec_halt;
dev->send = tsec_send;
dev->recv = tsec_recv;
/*初始化IP地址*/
for (i = 0; i < 6; i++)
dev->enetaddr[i] = 0;
/*設置當前活躍的網口名相當於 set ethact eTSECn,將多個網口級聯*/
eth_register(dev);
/* 通過設置MACCFG1寄存器重啓 MAC */
priv->regs->maccfg1 |= MACCFG1_SOFT_RESET;
udelay(2); /* Soft Reset must be asserted for 3 TX clocks */
priv->regs->maccfg1 &= ~(MACCFG1_SOFT_RESET);
/*掛載MII口的讀寫函數*/
#if defined(CONFIG_MII) || defined(CONFIG_CMD_MII) /
&& !defined(BITBANGMII)
miiphy_register(dev->name, tsec_miiphy_read, tsec_miiphy_write);
#endif
/* 初始化PHY,並返回 */
return init_phy(dev);
}
3 init_phy()
static int init_phy(struct eth_device *dev)
{
struct tsec_private *priv = (struct tsec_private *)dev->priv;
struct phy_info *curphy;
volatile tsec_t *regs = priv->regs;
/*在TBIPA的寄存器中寫入TBI PHY的地址*/
regs->tbipa = CONFIG_SYS_TBIPA_VALUE + priv->ID;
asm("sync");
/* 重啓MII接口 */
priv->phyregs->miimcfg = MIIMCFG_RESET;
asm("sync");
priv->phyregs->miimcfg = MIIMCFG_INIT_VALUE;
asm("sync");
while (priv->phyregs->miimind & MIIMIND_BUSY) ;
/* 通過讀PHY的2號3號寄存器獲得該ETSEC外連的PHY的ID,搜索phy_info數組,找到符合ID的PHY信息返回。 */
curphy = get_phy_info(dev);
if (curphy == NULL)
{
priv->phyinfo = NULL;
printf("%s: No PHY found/n", dev->name);
return 0;
}
/*如果是SGMII的接口,需要使用TBI PHY,初始化TBI PHY,注意這裏名字竟然叫serdes配置,Linux裏面也這麼叫,真是誤人子弟啊。*/
if (regs->ecntrl & ECNTRL_SGMII_MODE)
tsec_configure_serdes(priv);
/*在符合條件的PHY的phy_info數組中調用其初始化配置函數*/
priv->phyinfo = curphy;
phy_run_commands(priv, priv->phyinfo->config);
return 1;
}
4 phy_info結構
Uboot中使用這個結構來完成phy的操作,所有的phy都要使用這個結構表示,下面是88E1111的phy_info結構:
struct phy_info phy_info_M88E1111S = {
0x01410cc, // PHY ID
"Marvell 88E1111S", // PHY名稱
4,
(struct phy_cmd[])
{
/* 配置數組,在調用priv->phyinfo->config時將依次調用下面的內容,每個大括號內,第一個爲PHY寄存器地址,第二個爲value*/
/* Reset and configure the PHY */
{MIIM_CONTROL, MIIM_CONTROL_RESET, NULL},
/* Delay RGMII TX and RX */
{MIIM_GBIT_CONTROL, MIIM_GBIT_CONTROL_INIT, NULL},
{MIIM_ANAR, MIIM_ANAR_INIT, NULL},
{MIIM_CONTROL, MIIM_CONTROL_RESET, NULL},
{MIIM_CONTROL, MIIM_CONTROL_INIT, &mii_cr_init},
{miim_end,}
},
(struct phy_cmd[])
{
/* 啓動數組,在ETSEC啓動的時候要依次調用。 */
/* Status is read once to clear old link state */
{MIIM_STATUS, miim_read, NULL},
/* Auto-negotiate */
{MIIM_STATUS, miim_read, &mii_parse_sr},
/* Read the status */
{MIIM_88E1011_PHY_STATUS, miim_read, &mii_parse_88E1011_psr},
{miim_end,} },
(struct phy_cmd[])
{
/* shutdown */
{miim_end,}
},
};
需要注意的是,這個數組時uboot的源碼中提供的,但是由於PHY與MAC之間接口使用的不同,這個數組中的內容需要根據需要,參考相應PHY的datasheet作出一定的修改。
5 tsec_init()
該函數不會在初始化的時候調用,它在每當你使用網口的時候被調用,使用網口,不管是ping,還是tftp。
int tsec_init(struct eth_device *dev, bd_t * bd)
{
uint tempval;
char tmpbuf[MAC_ADDR_LEN];
int i;
struct tsec_private *priv = (struct tsec_private *)dev->priv;
volatile tsec_t *regs = priv->regs;
/* 初始化MACCFG2和ECNTRL兩個寄存器,這兩個寄存器非常重要,它們主要是用來是配置MAC對PHY的接口。在這裏先給個初始化的值,默認爲GMII。*/
tsec_halt(dev);
regs->maccfg2 = MACCFG2_INIT_SETTINGS;
regs->ecntrl = ECNTRL_INIT_SETTINGS;
/* 配置MAC地址。 */
for (i = 0; i < MAC_ADDR_LEN; i++)
{
tmpbuf[MAC_ADDR_LEN - 1 - i] = dev->enetaddr[i];
}
tempval = (tmpbuf[0] << 24) | (tmpbuf[1] << 16) | (tmpbuf[2] << 8) |
tmpbuf[3];
regs->macstnaddr1 = tempval;
tempval = *((uint *) (tmpbuf + 4));
regs->macstnaddr2 = tempval;
/* reset the indices to zero */
rxIdx = 0;
txIdx = 0;
/* 清除其它的寄存器 */
init_registers(regs);
/* 啓動tsec */
startup_tsec(dev);
/* If there's no link, fail */
return (priv->link ? 0 : -1);
}
6 startup_tsec()
static void startup_tsec(struct eth_device *dev)
{
int i;
struct tsec_private *priv = (struct tsec_private *)dev->priv;
volatile tsec_t *regs = priv->regs;
/* 初始化BD表基址指針 */
regs->tbase = (unsigned int)(&rtx.txbd[txIdx]);
regs->rbase = (unsigned int)(&rtx.rxbd[rxIdx]);
/* 初始化RX BD*/
for (i = 0; i < PKTBUFSRX; i++) {
rtx.rxbd[i].status =( RXBD_EMPTY | RXBD_INTERRUPT);
rtx.rxbd[i].length = 0;
rtx.rxbd[i].bufPtr = (uint) NetRxPackets[i];
}
rtx.rxbd[PKTBUFSRX - 1].status |= RXBD_WRAP;
/*初始化TX BD*/
for (i = 0; i < TX_BUF_CNT; i++) {
rtx.txbd[i].status = 0;
rtx.txbd[i].length = 0;
rtx.txbd[i].bufPtr = 0;
}
rtx.txbd[TX_BUF_CNT - 1].status |= TXBD_WRAP;
/*又要去找phy_info數組了,這次調用的是startup中的命令和函數*/
if(priv->phyinfo)
phy_run_commands(priv, priv->phyinfo->startup);
/*根據PHY的Copper側值配置MAC寄存器*/
adjust_link(dev);
/* 使能MACCFG1中的發送接收使能 */
regs->maccfg1 |= (MACCFG1_RX_EN | MACCFG1_TX_EN);
/* 讓DMA知道可以準備搬運了這裏的DMA是ETSEC內部的,並不是CPU中的DMA單元。*/
regs->dmactrl |= DMACTRL_INIT_SETTINGS;
regs->tstat = TSTAT_CLEAR_THALT;
regs->dmactrl &= ~(DMACTRL_GRS | DMACTRL_GTS);
}
參照上面的phy_info數組的startup中的內容得知這裏phy_run_commands(priv, priv->phyinfo->startup)要調用兩個函數mii_parse_sr和mii_parse_88E1011_psr。
這兩個函數主要是配置三個重要的priv結構體中的成員
priv->link
priv->speed
priv-> duplexity
分別是link狀態,速率和雙工。具體的代碼就不分析了,主要是讀PHY的Copper側寄存器,然後根據寄存器的值去配置這三個成員,在後面的adjust_link函數中會根據這三個成員的值去配置MAC的MACCFG2和ECNTRL寄存器。
在uboot階段,沒有掛載中斷,接收通過輪詢來實現的,所以發送和接收這兩個過程跟Linux內核中有區別。
在發送階段,網口將被啓動,發送函數首先找到一個可用的Buffer Descriptor,將上層軟件組好的包的地址賦給該BD的指針,置相應的標誌位和長度,然後通知DMA來搬運。搬運結束後,發送函數會清除相應的BD標識位。DMA將數據從內存搬運到Tx FIFO後, MAC會給其加上數據鏈路層的首部後通過GMII口發送到PHY層。
在接收階段,硬件會檢測TSECn_RX_DV和TSECn_COL信號,並會檢查有效的preamble,若找到,則檢查MAC地址,校驗等等,若都合格,則剝掉鏈路層的包頭後,塞給Rx FIFO,DMA會將其搬到現在一個有效的Rx BD中,我們的接收程序會輪詢該Buffer Descriptor,直到它有數據時,便將數據提交到上層,然後清除BD的一些狀態位。
static int tsec_send(struct eth_device *dev, volatile void *packet, int length)
{
int i;
int result = 0;
struct tsec_private *priv = (struct tsec_private *)dev->priv;
volatile tsec_t *regs = priv->regs;
/*找一塊空的Buffer Descriptor*/
for (i = 0; rtx.txbd[txIdx].status & TXBD_READY; i++) {
if (i >= TOUT_LOOP) {
printf("%s: tsec: tx buffers full/n", dev->name);
return result;
}
}
rtx.txbd[txIdx].bufPtr = (uint) packet;
rtx.txbd[txIdx].length = length;
rtx.txbd[txIdx].status |=
(TXBD_READY | TXBD_LAST | TXBD_CRC | TXBD_INTERRUPT);
/* 通過設置寄存器讓DMA來從BD中搬運到FIFO中 */
regs->tstat = TSTAT_CLEAR_THALT;
/* 等到BD搬運完成,清除標誌位*/
for (i = 0; rtx.txbd[txIdx].status & TXBD_READY; i++) {
if (i >= TOUT_LOOP) {
printf("%s: tsec: tx error/n", dev->name);
return result;
}
}
txIdx = (txIdx + 1) % TX_BUF_CNT;
result = rtx.txbd[txIdx].status & TXBD_STATS;
return result;
}
static int tsec_recv(struct eth_device *dev)
{
int length;
struct tsec_private *priv = (struct tsec_private *)dev->priv;
volatile tsec_t *regs = priv->regs;
while (!(rtx.rxbd[rxIdx].status & RXBD_EMPTY)) {
length = rtx.rxbd[rxIdx].length;
/* 有數據來時,檢測BD的status,如果沒有報錯,就扔給上層協議棧 */
if (!(rtx.rxbd[rxIdx].status & RXBD_STATS)) {
NetReceive(NetRxPackets[rxIdx], length - 4);
} else {
printf("Got error %x/n",
(rtx.rxbd[rxIdx].status & RXBD_STATS));
}
rtx.rxbd[rxIdx].length = 0;
/* 如果是最後一個BD就設置W位 */
rtx.rxbd[rxIdx].status =
RXBD_EMPTY | (((rxIdx + 1) == PKTBUFSRX) ? RXBD_WRAP : 0);
rxIdx = (rxIdx + 1) % PKTBUFSRX;
}
if (regs->ievent & IEVENT_BSY) {
regs->ievent = IEVENT_BSY;
regs->rstat = RSTAT_CLEAR_RHALT;
}
return -1;
}
Uboot下的網口驅動就這麼多內容。
Linux 網絡驅動設備模型
Linux網絡設備模型如下圖,從上到下可以劃分爲4層,分別是網絡協議接口層,網絡設備接口層,設備驅動功能層和網絡設備媒介層。
網絡協議接口層向網絡層協議提供統一的數據包收發接口,通過dev_queue_xmit()函數發送數據,並通過netif_rx()函數接收數據。
網絡設備結構層向協議接口層提供統一的用於描述具體網絡設備屬性和操作的結構體net_device。
設備驅動功能層各函數是網絡設備結構層net_device數據結構的具體成員,通過hard_start_xmit()函數發送,通過中斷觸發接收函數。
網絡設備媒介層就是完成數據包發送和接收的物理實體。
套接字緩衝區
套接字緩衝區 sk_buff的結構體非常重要,用於在Linux網絡子系統中的各層之間傳遞數據,是Linux網絡子系統數據傳遞的“中樞神經”。
當發送數據時,Linux內核的網絡處理模塊必須建立一個包含要傳輸的數據包sk_buff,然後將sk_buff遞交給下層,各層在sk_buff中添加不同的協議頭直至交給網絡設備發送。同樣,當網絡設備從網絡媒介上接收數據包後,它必須將接收到的數據轉化爲sk_buff數據結構並傳遞給上層,各層剝去相應的協議頭,直至交給用戶。
Skb有四個指針,head和end分別指向數據緩衝區的啓始地址和結尾地址,而data和tail分別指向有效數據的開始地址和結尾地址。
Skb的操作有:alloc_skb()分配一個套接字緩衝區和一個數據緩衝區;Kree_skb進行套接字緩衝區的釋放;skb_push()將data指針上移,主要用於添加協議頭部;skb_pull將data指針下移,用於剝去頭部。
1 dtb文件解析,生成資源單項列表。
start_kernel à setup_arch à unflatten_device_tree
該函數可以解析dtb文件,構建一個由device_node結構連接而成的單項鍊表。如下在此函數執行過後,在內存中會存在一個如下的鏈表:
後面所有的函數,如果需要從of tree結構上讀取設備資料的,都將從這個鏈表中遍歷並讀取。
2 Of_platform總線的註冊:
Arch/powerpc/kernel/of_platform.c
postcore_initcall(of_bus_driver_init);
of_bus_type_init(&of_platform_bus_type, "of_platform")
à bus_register(of_platform_bus_type)
同時:bus->match = of_platform_bus_match;
bus->probe = of_platform_device_probe;
of_platform_bus_type總線註冊完畢。
3 mdio總線的註冊
/driver/net/Phy_device.c
subsys_initcall(phy_init)
phy_init à mdio_bus_init à bus_register(&mdio_bus_type)
總線註冊後,在總線上註冊了一個默認的phy的驅動 genphy_driver:
.phy_id = 0xffffffff,
.phy_id_mask = 0xffffffff,
.name = "Generic PHY",
Mdio總線註冊完畢。
4 of_platform總線上的設備註冊:
Arch/powerpc/platform/83xx/Mpc831x_rdb.c
machine_device_initcall(mpc831x_rdb, declare_of_platform_devices);
declare_of_platform_devices à of_platform_bus_probe(NULL, of_bus_ids, NULL)
Arch/powerpc/kernel/of_platform.c
遍歷第一步中在內存中生成鏈表的所有soc的子節點,將所有的soc子節點設備添加到of_platform總線。
of_platform_bus_probe à of_platform_device_create à of_device_register à device_add
of_platform總線上的所有設備添加完畢,e0024000.ethernet,e0024520.mdio等設備現在都在總線上。
5 mdio總線上驅動的添加
/driver/net/phy/marvell.c
module_init(marvell_init)
marvell_init à phy_driver_register(&marvell_drivers[i]) à driver_register
前面第三步,註冊mdio總線後,已經添加了一個默認的phy的驅動,現在要將所有的phy驅動添加到總線上,這裏將所有的marvell的phy都添加。
這步過後,內核的/sys/bus/mdio/driver裏面就有了各種phy的驅動,但這時還沒有和具體的設備綁定。
6 of_platform總線上Mdio設備驅動(該驅動的目的是在mdio總線上添加PHY設備)的添加,並綁定設備:e0024520.mdio和e0025520.mdio
/driver/net/fsl_pq_mdio.c
module_init(fsl_pq_mdio_init)
fsl_pq_mdio_init à of_register_platform_driver(&fsl_pq_mdio_driver) à of_register_driver à driver_registerà bus_add_driver à driver_attach
遍歷整個of_platform總線,尋找與之相匹配的設備,找到e0024520.mdio
driver_attach à __driver_attach à driver_match_device
將driver的match_table裏的信息和dev_nod中的做比較,若符合就進入driver的probe,也就是fsl_pq_mdio_probe。
現在of_platform總線上的設備e0024520.mdio和e0025520.mdio已經綁定了驅動。
7 Mdio總線上的設備的添加,尋找並綁定相應的驅動。
/driver/net/fsl_pq_mdio.c
fsl_pq_mdio_probe à of_mdiobus_register à phy_device_register à device_register(&phydev->dev) àdevice_add à bus_probe_device à device_attach àbus_for_each_drv
掃描mdio總線上的所有的驅動,若找到匹配的,就綁定,並probe。
__device_attach à driver_probe_device à really_probe à phy_probe
將所有的phy和tbi-phy的設備都添加到mdio總線上,並且兩個phy設備和兩個tbi-phy設備都會根據其自己的PHYID找到各自的驅動
8 of_platform總線上gianfar設備驅動添加,並綁定設備e0024000.ethernet和e0025000.ethernet:
/driver/net/gianfar.c
module_init(gfar_init);
gfar_init à of_register_platform_driver(&gfar_driver) à of_register_driver à driver_register àbus_add_driver à driver_attach
遍歷整個of_platform總線,尋找與之相匹配的設備
driver_attach à __driver_attach à driver_match_device
將driver的match_table裏的信息和dev_nod中的做比較,若符合就進入driver的probe,也就是gfar_probe
現在設備e0024000.ethernet和e0025000.ethernet都有了他們自己的驅動。
到這步,of_platform上的gianfar設備和mdio設備都有其各自的驅動,mdio總線上的phy設備和tbi-phy設備都有了其驅動程序,但是phy設備和gianfar設備之間還沒有任何聯繫,phy和gianfar都沒有初始化。現在要調用相應的驅動去初始化各個設備,連接gianfar和phy。
9 gianfar_probe 初始化gianfar設備,填充dev和priv結構體。其中gfar_of_init 會從of結構中讀出priv->phy_node
10 phy的初始化,phy 和gianfar的連接
/net/ipv4/ipconfig.c
late_initcall(ip_auto_config)
ip_auto_config à ic_open_devs à dev_change_flags à __dev_change_flags à __dev_open à ops->ndo_open à gfar_enet_open
在gfar設備的打開函數中會去初始化phy,並connect to gianfar
u gfar_enet_open à init_phy
static int init_phy(struct net_device *dev)
{
。 。 。 。 。 。
interface = gfar_get_interface(dev);
/*PHY連接和初始化*/
priv->phydev = of_phy_connect(dev, priv->phy_node, &adjust_link, 0,
。 。 。 。 。 。
/*配置TBI-PHY*/
if (interface == PHY_INTERFACE_MODE_SGMII)
gfar_configure_serdes(dev);
. . . . .
return 0;
}
u of_phy_connect函數
priv->phy_node是從of結構中讀出的phy的信息,還不是真正的phy,所以這裏要在mdio_bus_type總線上再找一次匹配的phy。若找到phy_device *phy指針就不爲空。
struct phy_device *of_phy_connect(struct net_device *dev,
struct device_node *phy_np,
void (*hndlr)(struct net_device *), u32 flags,
phy_interface_t iface)
{
/*從mdio總線上找到和of tree上讀出的phy_node相匹配的phy設備*/
struct phy_device *phy = of_phy_find_device(phy_np);
if (!phy)
return NULL;
/*phy的初始化和phy的某些操作*/
return phy_connect_direct(dev, phy, hndlr, flags, iface) ? NULL : phy;
}
u phy_connect_direct函數
int phy_connect_direct(struct net_device *dev, struct phy_device *phydev,
void (*handler)(struct net_device *), u32 flags,
phy_interface_t interface)
{
int rc;
/*phy 連接和初始化*/
rc = phy_attach_direct(dev, phydev, flags, interface);
if (rc)
return rc;
/*掛載PHY狀態改變後修改gianfar驅動寄存器的回調函數*/
phy_prepare_link(phydev, handler);
/*PHY的狀態機開啓*/
phy_start_machine(phydev, NULL);
if (phydev->irq > 0)
{
/*PHY中斷的開啓*/
phy_start_interrupts(phydev);
}
return 0;
}
u phy_attach_direct函數
int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,
u32 flags, phy_interface_t interface)
{
struct device *d = &phydev->dev;
/* 如何該phy沒有驅動,就使用genphy的驅動 */
if (NULL == d->driver) {
int err;
d->driver = &genphy_driver.driver;
err = d->driver->probe(d);
if (err >= 0)
err = device_bind_driver(d);
if (err)
return err;
}
/*如果phy已經和gianfar連接 返回*/
if (phydev->attached_dev) {
dev_err(&dev->dev, "PHY already attached/n");
return -EBUSY;
}
/*連接phy和gianfar*/
phydev->attached_dev = dev;
phydev->dev_flags = flags;
phydev->interface = interface;
/*使用phy的驅動中的初始化函數去初始化phy設備。*/
return phy_init_hw(phydev);
}
到這裏,所有的gianfar,phy,tbi-phy設備都已經註冊,驅動已經加載,gianfar和phy已經連接,並初始化完成。