物理层定义数据发送和接收所需要电与光信号,线路状态,时钟基准,编码和电路等,并向数据链路设备提供标准接口,物理层芯片称为PHY。
数据链路层则提供寻址机构,数据帧的构建,数据差错检查,传送控制向网络层提供标准的数据接口功能。
IEEE802.3描述物理层和数据链路层的MAC子层的实现方法。
以太网接口的实质是MAC通过MII总线控制PHY的过程。
MII(媒体独立接口),媒体独立表明在不对mac硬件重新设计或替换的情况下,任何类型的PHY设备都可以正常工作。
MAC通过SMI总线不断读取PHY的状态寄存器以得知目前PHY的状态,比如连接速度,双工的能力等。
PHY是物理接口收发器,它实现物理层,包括MII/GMII子层,PCS(物理编码子层),PMA(物理介质附加子层),PMD(物理介质相关子层),MDI子层。比较重要的是实现CSMA/CD部分功能。
PCI总线接MAC总线,MAC接PHY,PHY接网线。
PHY整合了大量的模拟硬件,MAC是典型的全数字器件。
每次内核加载,他知道从/etc/fstab开始mount文件系统,每次系统启动会根据该文件自动挂载。
在linux网络分为两层,分别是网络堆栈协议层以及接收和发送网络协议的设备驱动程序层。网络堆栈式硬件独立出来部分,主要用来TCP/IP等多种协议,而网络设备驱动层时连接网络堆栈协议层和网络硬件的中间层。
net_device结构体存储一个网络接口重要信息,它是系统中网络设备代表。
sk_buff是socket buffer在网络传输过程中起重要作用,内核封装成socket buffer向网络硬件发送,当网络硬件接收到数据包时,再把数据包封装成socket buffer向内核发送。
write()->sk_buff缓冲区->hard_start_xmit()->DMA->硬件接口
硬件一般都会在上层数据发送之前加上自己的硬件帧头,比如以太网就有14字节的帧头。
所有套接字的输入输出缓冲区是sk_buff形成的链表。
大多数实际的物理连接的网络技术提供载波状态信息,载波存在意味着硬件功能是正常的。
在init函数中这次有两个platform_driver_register();一个是有关stmphy,一个是有关stmmac,再 stmphy_dvr_probe()仅仅是打印信息。
在stmmac_dvr_probe函数中,一开始获取linux中的硬件资源,分配一个网络设备的内存空间alloc_etherdev(本质还是调用alloc_netdev_mqs()).,接下来比较重要的是stmmac_mac_device_setup()函数。
struct mac_device_info {
const struct stmmac_ops *mac;
const struct stmmac_desc_ops *desc;
const struct stmmac_dma_ops *dma;
struct mii_regs mii; /* MII register Addresses */
struct mac_link link;
};
里面将mac_device_info中的三个operations注册了回调函数,以便让接下来的函数stmmac_probe()进行调用。在该函数中比较重要的是ether_setup():
/**
* ether_setup - setup Ethernet network device
* @dev: network device
* Fill in the fields of the device structure with Ethernet-generic values.
*/
void ether_setup(struct net_device *dev)
{
dev->header_ops = _header_ops;
dev->type = ARPHRD_ETHER;
dev->hard_header_len = ETH_HLEN;
dev->mtu = ETH_DATA_LEN;
dev->addr_len = ETH_ALEN;
dev->tx_queue_len = 1000; /* Ethernet wants good queues */
dev->flags = IFF_BROADCAST|IFF_MULTICAST;
dev->priv_flags = IFF_TX_SKB_SHARING;
memset(dev->broadcast, 0xFF, ETH_ALEN);
}
EXPORT_SYMBOL(ether_setup);
默认赋值很多以太网设备的参数。
接下来就是注册最最重要的net_device_ops中的一系列回调函数
static const struct net_device_ops stmmac_netdev_ops = {
.ndo_open = stmmac_open,
.ndo_start_xmit = stmmac_xmit,
.ndo_stop = stmmac_release,
.ndo_change_mtu = stmmac_change_mtu,
.ndo_fix_features = stmmac_fix_features,
.ndo_set_multicast_list = stmmac_multicast_list,
.ndo_tx_timeout = stmmac_tx_timeout,
.ndo_do_ioctl = stmmac_ioctl,
.ndo_set_config = stmmac_config,
#ifdef STMMAC_VLAN_TAG_USED
.ndo_vlan_rx_register = stmmac_vlan_rx_register,
#endif
#ifdef CONFIG_NET_POLL_CONTROLLER
.ndo_poll_controller = stmmac_poll_controller,
#endif
.ndo_set_mac_address = eth_mac_addr,
};
在stmmac_open函数中进行物理层(phy)设备的初始化,主要是执行phy_connect()这个函数来链接以太网设备和物理层的设备。接下来主要是DMA Descriptor的初始化和进行DMA分配和映射。(dma_alloc_coherent,dma_map_single).也为每一个接收的dma分配一个skb(网络通信中数据存储的核心数据结构)netdev_alloc_skb_ip_align().
在open函数中需要申请中断request_irq(),来判断是否有数据要接受或者发送的数据已经完成。中断处理函数为stmmac_interrupt(),然后调用stmmac_dma_interrupt(),最终调用了一个很关键的函数napi_sechedule().(napi是指new api,在数据传输量不大时采用中断的方式,但是在数据传输很频繁时会采用poll轮询的方式)。
/**
* napi_schedule - schedule NAPI poll
* @n: napi context
*
* Schedule NAPI poll routine to be called if it is not already
* running.
*/
static inline void napi_schedule(struct napi_struct *n)
{
if (napi_schedule_prep(n))
__napi_schedule(n);
} napi添加的方法在之后会调用netif_napi_add()。传入的函数参数为stmmc_poll()里面调用了真正的数据接收的函数stmmac_rx().
phy_start()开启phy设备。实际上就是设置一下phy状态。
/**
* phy_start - start or restart a PHY device
* @phydev: target phy_device struct
*
* Description: Indicates the attached device's readiness to
* handle PHY-related work. Used during startup to start the
* PHY, and after a call to phy_stop() to resume operation.
* Also used to indicate the MDIO bus has cleared an error
* condition.
*/
void phy_start(struct phy_device *phydev)
{
mutex_lock(&phydev->lock);
switch (phydev->state) {
case PHY_STARTING:
phydev->state = PHY_PENDING;
break;
case PHY_READY:
phydev->state = PHY_UP;
break;
case PHY_HALTED:
phydev->state = PHY_RESUMING;
default:
break;
}
mutex_unlock(&phydev->lock);
}
EXPORT_SYMBOL(phy_start); napi_enable().使能NAPI调度
netif_start_queue().允许数据传输
/**
* netif_start_queue - allow transmit
* @dev: network device
*
* Allow upper layers to call the device hard_start_xmit routine.
*/
static inline void netif_start_queue(struct net_device *dev)
{
netif_tx_start_queue(netdev_get_tx_queue(dev, 0));
}
stmmac_xmit();是驱动程序发送的入口点。里面有一个特别点是skb_shinfo();来查找所需要的分散数据片段(scatter/gather)最终调用dma_map_page来发送数据,注意参数里的一个结构体skb_frag_struct;
struct skb_frag_struct {
struct page *page;
#if (BITS_PER_LONG > 32) || (PAGE_SIZE >= 65536)
__u32 page_offset;
__u32 size;
#else
__u16 page_offset;
__u16 size;
#endif
};
接下来设置ethtool方法,ethtool是为系统管理员提供的用语控制网络接口的工具,控制速度,介质类型,双工操作,DMA设置,硬件校验,LAN唤醒等操作。
SET_ETHTOOL_OPS(netdev, &stmmac_ethtool_ops);
net_device中的feature设置也是比较重要的它相当于是一些操作的开关,比如说只有在device结构的feature设置了NETIF_F_SG标志位,内核才能将分散的数据包传递给stmmac_xmit函数。
最后就是一个将net_device注册到网络子系统中去.register_netdevice().里面调用了register_netdevice().
为创建网络设备创建sysfs入口等操作。
之后注册MDIO总线。因为phy设备和mac控制器之间是需要总线进行通信的,介质无关接口(MII)是一个IEEE802.3标准,描述了以太网收发器是如何与网络控制器链接的。
在stmmac_mdio_register中先分配mii_bus内存空间,然后设置相对应的参数(名字,读写等回调函数)
<span style="white-space:pre"> </span> new_bus->name = "STMMAC MII Bus";
new_bus->read = &stmmac_mdio_read;
new_bus->write = &stmmac_mdio_write;
new_bus->reset = &stmmac_mdio_reset;
snprintf(new_bus->id, MII_BUS_ID_SIZE, "%x", priv->plat->bus_id);
new_bus->priv = ndev;
new_bus->irq = irqlist;
new_bus->phy_mask = priv->phy_mask;
new_bus->parent = priv->device;
err = mdiobus_register(new_bus);
if (err != 0) {
pr_err("%s: Cannot register as MDIO bus\n", new_bus->name);
goto bus_register_fail;
}
最后进行mdiobus_register()注册,先注册设备device_register(),,d然后调用mdiobus_scan(),扫描总线上地址获取phy_device(实际上是根据地址获得phy_id,然后用phy_id创建phy设备,最后注册phy设备).
struct phy_device *mdiobus_scan(struct mii_bus *bus, int addr)
{
struct phy_device *phydev;
int err;
phydev = get_phy_device(bus, addr);
if (IS_ERR(phydev) || phydev == NULL)
return phydev;
err = phy_device_register(phydev);
if (err) {
phy_device_free(phydev);
return NULL;
}
return phydev;
}
EXPORT_SYMBOL(mdiobus_scan);
mdio的读写最后还是通过phy_mii_ioctl(),而这个函数被封装在net_device_ops中的ndo_do_ioctl()的回调函数中。
网卡期望在内存中建有一个循环缓冲区,并与处理器共享;每个输入的数据包都放入缓冲器环中的下一个可用缓冲器中,然后引发中断。
用过向内核传递“mem=参数”的办法保留顶部的RAM,为缓冲区分配内存。
使用DMA的设备驱动程序将于连接到总线接口上的硬件通信,硬件使用的是物理地址,而程序代码使用的是虚拟地址(基于DMA硬件使用总线地址,而不是硬件地址)。
DMA操作最终会分配缓冲区,并将总线地址传递给设备。
如果设备支持常见的32位DMA操作,没有必要调用dma_set_mask();(该函数设置寻址范围)
一个DMA映射是要分配的DMA缓冲区与该缓冲区生成的,设备可访问地址的组合。
当驱动程序要试图在外围设备不可访问的地址上执行DMA时,将创建回弹缓冲区。//是内存中的独立区域,它可被设备访问。
如果设备改变了主内存中的区域,则任何覆盖该区域的处理器缓存都将无效,否则处理器将使用不正确的主内存映射,从而产生不正确的数据。
dma_addr_t来表示总线地址,该类型的变量对驱动程序是不透明的。
一致性映射的缓冲区必须可同时被CPU和外围设备访问,一致性映射必须保存在一致性缓存中。
dma_alloc_coherent(struct device *dev, size_t size, dam_addr_t *dma_handle, int flag);
前面两个参数是device结构和所需缓冲区大小,函数返回值是缓冲区的虚拟内核地址,可以被驱动程序使用,而与其相关的总线地址返回时保存在dma_handle中。
DMA池是一个生成小型,一致性DMA映射的机制。
流式DMA映射:dma_map_single();一旦缓冲区被映射,它将属于设备,而不是处理器。知道缓冲区被撤销映射前,驱动程序不能以任何方式访问其中的内容。
与其说是一个网络驱动,倒不如网络子系统,在linux系统起来时,就会调用/driver/core/dev.c中的subsys_init(net_dev_init);
子系统的事情以后再慢慢研究,驱动的问题就研究到这里了。
|