linux下网络驱动

物理层定义数据发送和接收所需要电与光信号,线路状态,时钟基准,编码和电路等,并向数据链路设备提供标准接口,物理层芯片称为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);

子系统的事情以后再慢慢研究,驱动的问题就研究到这里了。


你可能感兴趣的:(linux下网络驱动)