千兆网口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已经连接,并初始化完成。