转:Linux网络子系统之---- PHY 配置
2016-08-04 19:50阅读:
MII即媒体独立接口,也叫介质无关接口。
它包括一个数据接口,以及一个MAC和PHY之间的管理接口(图1)。
数据接口包括分别用于发送器和接收器的两条独立信道。每条信道都有自己的数据、时钟和控制信号。MII数据接口
总共需16个信号。
管理接口是个双信号接口:一个是时钟信号,另一个是数据信号。通过管理接口,上层能监视和控制PHY。
RMII口是用两根线来传输数据的,
MII口是用4根线来传输数据的,
GMII是用8根线来传输数据的。
GMII (Gigabit MII)
GMII是8bit并行同步收发接口,采用8位接口数据,工作时钟125MHz,因此传输速率可达1000Mbps。同时兼容MII所规定的10/100
Mbps工作方式。
GMII接口
数据结构符合IEEE以太网标准。该接口定义见IEEE802.3-2000。
发送器:
◇ GTXCLK——吉比特TX..信号的时钟信号(125MHz)
◇ TXCLK——10/100M信号时钟
◇ TXD[7..0]——被发送数据 ------- mii
为4,所以一个信道是8,两个是16
◇ TXEN——发送器使能信号
◇ TXER——发送器错误(用于破坏一个数据包)
注:在千兆速率下,向PHY提供GTXCLK信号,TXD、TXEN、TXER信号与此时钟信号同步。否则,在10/100M速率下,PHY提供
TXCLK时钟信号,其它信号与此信号同步。其工作频率为25MHz(100M网络)或2.5MHz(10M网络)。
接收器:
◇ RXCLK——接收时钟信号(从收到的数据中提取,因此与GTXCLK无关联)
◇ RXD[7..0]——接收数据
◇ RXDV——接收数据有效指示
◇ RXER——接收数据出错指示
◇ COL——冲突检测(仅用于半双工状态)
管理配置
◇ MDC——配置接口时钟
◇ MDIO——配置接口I/O
管理配置接口控制PHY的特性。该接口有32个寄存器地址,每个地址16位。其中前16个已经在“IEEE
802.3,2000-22.2.4Management Functions”中规定了用途,其余的则由各器件自己指定。
MII/RMII只是一种接口,对于10M线速,MII的速率是2.5M,RMII则是5M;对于100M线速,MII的速率是25M,RMII则是50M。
SGMII--Serial Gigabit Media IndependentInterface
SGMII是PHY与MAC之间的接口,类似与GMII和RGMII,只不过GMII和RGMII都是并行的,而且需要随路时钟,PCB布线相对麻烦,而且不适应背板应用。
而SGMII是串行的,不需要提供另外的时钟,MAC和PHY都需要CDR去恢复时钟。另外SGMII是有8B/10b编码的,速率是1.25G
在 linux 配置PHY
drivers/net/phy
配置的参数
自适应, 1000M, 全双工。
phydev-> autonet, speed, duplex.
1. MDIO简介
The MDIO interface is a simple, two-wire, serial interface
to connect a management entity and a managed PHY for the purposes
of controlling the PHY and gathering status from the PHY.
The two lines include the MDC line [Management Data
Clock], and the MDIO line [Management Data Input/Output]. The clock
is point-to-point, while the data line is a bi-directional
multi-drop interface.
The data line is Tri-state able and can drive 32
devices.
MDIO接口,MAC与PHY间的管理接口(MII是数据接口),有2根线:时钟线MDC,数据线MDIO(双向)
MDIO工作流程:
* Preamle(PRE)
在没有传输数据的空闲状态时,数据线MDIO处于高阻态(一直为1)。
* Start of Frame(ST)
MAC驱动MDIO线,出现一个2bit的开始标识码(01)。
* Operation Code(OP)
MAC驱动MDIO线,出现一个2bit数据来标识是读操作(10)还是写操作(01)。
* PHY Address(PHYAD)
MAC驱动MDIO线,
出现一个5bit数据标识PHY的地址。
* Reg Address(REGAD)
MAC驱动MDIO线,
出现一个5bitPHY寄存器地址。
* Turnaround(TA)
写操作的话,MAC驱动MDIO线,出现10
读操作的话,MDIO pin of MAC must be put in high-impedance state
在第二个周期,PHY驱动MDIO线,出现0
* Data
MDIO串行读出/写入16bit的寄存器数据。
* MDIO恢复成空闲状态,同时MDIO进入高阻状态。
下面是PHY芯片 BCM5461 的一个例子:
2. PowerPC对MDIO的支持
PowerPC操作MDIO时,涉及以下寄存器:
MIIMCFG
配置寄存器
MIIMCOM
命令寄存器
MIIMADD
地址寄存器
MIIMCON
控制寄存器
MIIMSTAT 状态寄存器
MIIMIND
指示寄存器
以MPC8560举例,这些寄存器在CCSR中的位置如下:

2.1 MIIMCFG:配置寄存器

ResetMgmt:
用于重置MDIO模块
MgmtClockSet:时钟设置,是CCB的 2的n次方之一
2.2 MIIMCOM
命令寄存器

ReadCycle: 0->1 触发MDIO读时序
2.3 MIIMADD
地址寄存器

PHYaddr:PHY地址,共5bit,系统最多联31个PHY(地址0为保留)
REGaddr:寄存器地址,共5bit,一个PHY上最多32个寄存器地址(可以使用shadow
value技术,访问更多的寄存器)
2.4 MIIMCON
控制寄存器

PHYcontrol:在写流程时,这里存放要写入寄存器的值
2.5 MIIMSTAT 状态寄存器

PHYstatus:读流程时,PHY reg的内容会放到此
2.6 MIIMIND
指示寄存器

NotVal:若置1,表示读流程结束,可以去读MIIMSTAT
Scan:
若置1,表示扫描流程进行中
Busy:
只有置0时,才能进行新的读写流程
3. linux中MDIO的实现
读写PHY寄存器时通过2个函数
phy_read()和phy_write(),
最终调用
int gfar_local_mdio_read(struct gfar_mii *regs, int mii_id, int
regnum)
int gfar_local_mdio_write(struct gfar_mii *regs, int mii_id, int
regnum, u16 value)
参数regs就是MDIO相关寄存器:
- struct gfar_mii
{
- u32 miimcfg;
- u32 miimcom;
- u32 miimadd;
- u32 miimcon;
- u32 miimstat;
- u32 miimind;
- };
参数mii_id,就是PHY的id
参数regnum,就是寄存器地址
上代码,简单不解释
- int
gfar_local_mdio_read(struct
gfar_mii *regs,
int mii_id,
int
regnum)
- {
- u16
value;
-
-
gfar_write(®s->miimadd,
(mii_id
<</span><</span>
8) |
regnum);
-
-
gfar_write(®s->miimcom,
0);
-
gfar_write(®s->miimcom,
MII_READ_COMMAND);
-
- while
(gfar_read(®s->miimind)
& (MIIMIND_NOTVALID
|
MIIMIND_BUSY))
-
cpu_relax();
-
- value =
gfar_read(®s->miimstat);
- return
value;
- }
- int
gfar_local_mdio_write(struct
gfar_mii *regs,
int
mii_id,
-
int
regnum, u16
value)
- {
-
-
gfar_write(®s->miimadd,
(mii_id
<</span><</span>
8) |
regnum);
-
-
gfar_write(®s->miimcon,
value);
-
- while
(gfar_read(®s->miimind)
&
MIIMIND_BUSY)
-
cpu_relax();
- return
0;
- }
内核启动时的准备工作
4.1
初始化网络相关的全局数据结构,并挂载处理网络相关软中断的钩子函数
start_kernel()
--> rest_init()
--> do_basic_setup()
--> do_initcall
-->net_dev_init
__init
net_dev_init()
{
//每个CPU都有一个CPU私有变量
_get_cpu_var(softnet_data)
//_get_cpu_var(softnet_data).poll_list很重要,软中断中需要遍历它的
for_each_possible_cpu(i)
{
struct softnet_data *queue;
queue
= &per_cpu(softnet_data, i);
skb_queue_head_init(&queue->input_pkt_queue);
queue->completion_queue = NULL;
INIT_LIST_HEAD(&queue->poll_list);
queue->backlog.poll = process_backlog;
queue->backlog.weight = weight_p;
}
open_softirq(NET_TX_SOFTIRQ,
net_tx_action, NULL);
//在软中断上挂网络发送handler
open_softirq(NET_RX_SOFTIRQ,
net_rx_action, NULL);
//在软中断上挂网络接收handler
}
4.2
加载网络设备的驱动
NOTE:这里的网络设备是指MAC层的网络设备,即TSEC和PCI网卡(bcm5461是phy)
在网络设备驱动中创建net_device数据结构,并初始化其钩子函数 open(),close()
等
挂载TSEC的驱动的入口函数是
gfar_probe
// 平台设备 TSEC 的数据结构
static struct platform_driver gfar_driver = {
.probe =
gfar_probe,
.remove = gfar_remove,
.driver = {
.name
= 'fsl-gianfar',
},
};
int
gfar_probe(struct
platform_device *pdev)
{
dev = alloc_etherdev(sizeof (*priv));
// 创建net_device数据结构
dev->open =
gfar_enet_open;
dev->hard_start_xmit =
gfar_start_xmit;
dev->tx_timeout =
gfar_timeout;
dev->watchdog_timeo =
TX_TIMEOUT;
#ifdef CONFIG_GFAR_NAPI
netif_napi_add(dev,
&priv->napi,gfar_poll,GFAR_DEV_WEIGHT);
//软中断里会调用poll钩子函数
#endif
#ifdef CONFIG_NET_POLL_CONTROLLER
dev->poll_controller =
gfar_netpoll;
#endif
dev->stop =
gfar_close;
dev->change_mtu =
gfar_change_mtu;
dev->mtu = 1500;
dev->set_multicast_list =
gfar_set_multi;
dev->set_mac_address =
gfar_set_mac_address;
dev->ethtool_ops =
&gfar_ethtool_ops;
}
五、启用网络设备
5.1
用户调用ifconfig等程序,然后通过ioctl系统调用进入内核
socket的ioctl()系统调用
--> sock_ioctl()
--> dev_ioctl()
//判断SIOCSIFFLAGS
--> __dev_get_by_name(net, ifr->ifr_name)
//根据名字选net_device
--> dev_change_flags()
//判断IFF_UP
--> dev_open(net_device)
//调用open钩子函数
对于TSEC来说,挂的钩子函数是
gfar_enet_open(net_device)
5.2
在网络设备的open钩子函数里,分配接收bd,挂中断ISR(包括rx、tx、err),对于TSEC来说
gfar_enet_open
--> 给Rx Tx Bd 分配一致性DMA内存
--> 把Rx
Bd的“EA地址”赋给数据结构,物理地址赋给TSEC寄存器
--> 把Tx
Bd的“EA地址”赋给数据结构,物理地址赋给TSEC寄存器
--> 给 tx_skbuff 指针数组
分配内存,并初始化为NULL
--> 给 rx_skbuff 指针数组
分配内存,并初始化为NULL
--> 初始化Tx Bd
--> 初始化Rx
Bd,提前分配存储以太网包的skb,这里使用的是一次性dma映射
(注意:#define DEFAULT_RX_BUFFER_SIZE
1536保证了skb能存一个以太网包)
rxbdp
= priv->rx_bd_base;
for
(i = 0; i < priv->rx_ring_size; i++) {
struct sk_buff *skb =
NULL;
rxbdp->status = 0;
//这里真正分配skb,并且初始化rxbpd->bufPtr, rxbdpd->length
skb =
gfar_new_skb(dev, rxbdp);
priv->rx_skbuff[i] =
skb;
rxbdp++;
}
rxbdp--;
rxbdp->status |= RXBD_WRAP; //
给最后一个bd设置标记WRAP标记
--> 注册TSEC相关的中断handler:
错误,接收,发送
request_irq(priv->interruptError,
gfar_error, 0,
'enet_error', dev)
request_irq(priv->interruptTransmit,
gfar_transmit, 0,
'enet_tx', dev)//包发送完
request_irq(priv->interruptReceive,
gfar_receive, 0, 'enet_rx',
dev) //包接收完
-->gfar_start(net_device)
//
使能Rx、Tx
// 开启TSEC的
DMA 寄存器
// Mask
掉我们不关心的中断event
最终,TSEC相关的Bd等数据结构应该是下面这个样子的

六、中断里接收以太网包
TSEC的RX已经使能了,网络数据包进入内存的流程为:
网线 --> Rj45网口 --> MDI 差分线
--> bcm5461(PHY芯片进行数模转换) --> MII总线
--> TSEC的DMA Engine 会自动检查下一个可用的Rx bd
--> 把网络数据包 DMA 到 Rx bd
所指向的内存,即skb->data
接收到一个完整的以太网数据包后,TSEC会根据event mask触发一个 Rx
外部中断。
cpu保存现场,根据中断向量,开始执行外部中断处理函数do_IRQ()
do_IRQ 伪代码
{
上半部处理硬中断
查看中断源寄存器,得知是网络外设产生了外部中断
执行网络设备的rx中断handler(设备不同,函数不同,但流程类似,TSEC是gfar_receive)
1. mask 掉 rx event,再来数据包就不会产生rx中断
2. 给napi_struct.state加上 NAPI_STATE_SCHED
状态
3.
挂网络设备自己的napi_struct结构到cpu私有变量_get_cpu_var(softnet_data).poll_list
4. 触发网络接收软中断
下半部处理软中断
依次执行所有软中断handler,包括timer,tasklet等等
执行网络接收的软中断handler
net_rx_action
1. 遍历cpu私有变量_get_cpu_var(softnet_data).poll_list
2. 取出poll_list上面挂的napi_struct
结构,执行钩子函数napi_struct.poll()
(设备不同,钩子函数不同,流程类似,TSEC是gfar_poll)
3. 若poll钩子函数处理完所有包,则打开rx event
mask,再来数据包的话会产生rx中断
4. 调用napi_complete(napi_struct *n)
把napi_struct
结构从_get_cpu_var(softnet_data).poll_list 上移走
同时去掉 napi_struct.state 的
NAPI_STATE_SCHED 状态
}
6.1 TSEC的接收中断处理函数
gfar_receive
{
#ifdef CONFIG_GFAR_NAPI
//
test_and_set当前net_device的napi_struct.state 为 NAPI_STATE_SCHED
// 在软中断里调用 net_rx_action 会检查状态
napi_struct.state
if
(netif_rx_schedule_prep(dev,
&priv->napi)) {
tempval = gfar_read(&priv->regs->imask);
tempval &= IMASK_RX_DISABLED;
//mask掉rx,不再产生rx中断
gfar_write(&priv->regs->imask, tempval);
//
将当前net_device的 napi_struct.poll_list 挂到
//
CPU私有变量__get_cpu_var(softnet_data).poll_list 上,并触发软中断
//
所以,在软中断中调用 net_rx_action 的时候,就会执行当前net_device的
//
napi_struct.poll()钩子函数,即 gfar_poll()
__netif_rx_schedule(dev,
&priv->napi);
}
#else
gfar_clean_rx_ring(dev,
priv->rx_ring_size);
#endif
}