关于FEC驱动

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/wangshh03/article/details/73176460

转载

说是网络,其实是网卡驱动。而且是针对于FREESCALE芯片的FEC端的驱动,我不知道别的芯片厂商的FEC模块是怎么样的,
但就我接触过的几款FREESCALE的芯片来看,比如基于POWERPC的860T和ARM系列的MX27等,他们的FEC有一个明显的特点就是
都是由BD和一个DMA缓冲组成,而这个DMA是专用的,也就是只是给FEC使用,区别于芯片内的DMAC模块。我们先来从fec.c这
个与硬件直接相关的看起:
首先找到module_init(fec_enet_module_init);这里fec_enet_module_init为入口点
fec_enet_module_init()
    首先调用fec_arch_init,它调用gpio_fec_active设置GPIO为FEC模式,然后如果有电源管理的话,就调用
mxc_fec_power_on开启电源。接着调用clk_get,clk_enable, clk_put设置FEC的CLOCK,这里退出fec_arch_init函数,接
着循环FEC_MAX_PORTS次,也就是有几个FEC就循环几次,在这里我们只有一个FEC,所以这个循环不用管。接下来因为我们
用的是以太网,所以调用dev = alloc_etherdev(sizeof(struct fec_enet_private));申请一个以太网设备描述,其中
struct fec_enet_private是用来描述FEC专有的数据结构。如下:
/* The FEC buffer descriptors track the ring buffers. The rx_bd_base and
 * tx_bd_base always point to the base of the buffer descriptors. The
 * cur_rx and cur_tx point to the currently available buffer.
 * The dirty_tx tracks the current buffer that is being sent by the
 * controller. The cur_tx and dirty_tx are equal under both completely
 * empty and completely full conditions. The empty/ready indicator in
 * the buffer descriptor determines the actual condition.
 */
struct fec_enet_private {
    /* Hardware registers of the FEC device */
    volatile fec_t    *hwp;
    /* The saved address of a sent-in-place packet/buffer, for skfree(). */
    unsigned char *tx_bounce[TX_RING_SIZE];
    struct    sk_buff* tx_skbuff[TX_RING_SIZE];
    struct sk_buff* rx_skbuff[RX_RING_SIZE];
    ushort    skb_cur;
    ushort    skb_dirty;
    /* CPM dual port RAM relative addresses.
    */
    void * cbd_mem_base; /* save the virtual base address of rx&tx buffer descripter */
    cbd_t    *rx_bd_base;        /* Address of Rx and Tx buffers. */
    cbd_t    *tx_bd_base;
    cbd_t    *cur_rx, *cur_tx;        /* The next free ring entry */
    cbd_t    *dirty_tx;    /* The ring entries to be free()ed. */
    struct    net_device_stats stats;
    uint    tx_full;
    spinlock_t lock;
    uint    phy_id;
    uint    phy_id_done;
    uint    phy_status;
    uint    phy_speed;
    phy_info_t const    *phy;
    struct work_struct phy_task;
    uint    sequence_done;
    uint    mii_phy_task_queued;
    uint    phy_addr;
    int    index;
    int    opened;
    int    link;
    int    old_link;
    int    full_duplex;
    struct clk *clk;
};
    调用完这个函数后我们就有eth0这人设备了,其中一些结构的初始化是在这个函数里的一个回调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->change_mtu        = eth_change_mtu;
    dev->hard_header    = eth_header;
    dev->rebuild_header     = eth_rebuild_header;
    dev->set_mac_address     = eth_mac_addr;
    dev->hard_header_cache    = eth_header_cache;
    dev->header_cache_update= eth_header_cache_update;
    dev->hard_header_parse    = eth_header_parse;
    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;
    
    memset(dev->broadcast, 0xFF, ETH_ALEN);
}
    alloc_etherdev完了之后,调用fec_enet_init函数,这里才是真正芯片级的代码:
    fec_enet_init()
        上锁spin_lock_init(&(fep->lock)),调用__get_free_page申请一页的内存,这个返回的地址为虚拟地
址,然后将这个地址值赋给fep->cbc_mem_base,然后将fecp和fep->hwp指向FEC硬件寄存器,fecp->fec_ecntrl = 1,复位
FEC模块,等待10us后调用fec_get_mac从FLASH中读取MAC地址,调用cbd_base = (cbd_t *)fec_map_uncache(mem_addr,
PAGE_SIZE);将刚才申请的一页进行映射(mem_addr不是虚拟地址么?为什么这里还要映射??)作为BD的初始地址并将FEC
的rx_bd_base指向该地址,同时将tx_bd_base指向RX_RING_SIZE之后的地址,这个RX_RING_SIZE定义如下:
            /* The number of Tx and Rx buffers. These are allocated from the page
             * pool. The code may assume these are power of two, so it it best
             * to keep them that size.
             * We don't need to allocate pages for the transmitter. We just use
             * the skbuffer directly.
             */
            #define FEC_ENET_RX_PAGES    8
            #define FEC_ENET_RX_FRSIZE    2048
            #define FEC_ENET_RX_FRPPG    (PAGE_SIZE / FEC_ENET_RX_FRSIZE)
            #define RX_RING_SIZE        (FEC_ENET_RX_FRPPG * FEC_ENET_RX_PAGES)
            #define FEC_ENET_TX_FRSIZE    2048
            #define FEC_ENET_TX_FRPPG    (PAGE_SIZE / FEC_ENET_TX_FRSIZE)
            #define TX_RING_SIZE        16    /* Must be power of two */
            #define TX_RING_MOD_MASK    15    /* for this to work */
            
            #if (((RX_RING_SIZE + TX_RING_SIZE) * 8) > PAGE_SIZE)
            #error "FEC: descriptor ring size constants too large"
            #endif
        其中PAGE_SIZE是为4096,所以这个RX_RING_SIZE的值也为16,意思好像是说有16个FEC_ENET_RX_FRSIZE?
接下来循环初始化RX_RING_SIZE个frame,循环中,调用dev_alloc_skb(FEC_ENET_RX_FRSIZE);初始化skb,并将赋给fep-
>rx_skbuff[i],接下来将pskb->data进行对齐,这个对齐要求是通过
            #define FEC_ADDR_ALIGNMENT(x) ((unsigned char *)(((unsigned long )(x) +
(FEC_ALIGNMENT)) & (~FEC_ALIGNMENT)))
        来实现的,也就是说这个对齐要求是由datasheet里的FEC模块规定的。注意的是最后将这个地址进行物理
地址变换后赋给cbd_bufaddr的,即cbd_bufaddr = __pa(pskb->data);然后将BD的cbd_sc状态描述置为BD_ENET_RX_EMPTY空
闲。
        接下来将最后一个BD的cbd_sc状态描述置上BD_SC_WRAP标志,这估计是datasheet里规定的吧??接下来
初始化发送BD,这里看不懂,先帖上来先
            bdp = fep->tx_bd_base;
            for (i=0, j=FEC_ENET_TX_FRPPG; i<TX_RING_SIZE; i++) {
                if (j >= FEC_ENET_TX_FRPPG) {
                    mem_addr = __get_free_page(GFP_KERNEL);
                    j = 1;
                } else {
                    mem_addr += FEC_ENET_TX_FRSIZE;
                    j++;
                }
                fep->tx_bounce[i] = (unsigned char *) mem_addr;
        
                /* Initialize the BD for every fragment in the page.
                */
                bdp->cbd_sc = 0;
                bdp->cbd_bufaddr = 0;
                bdp++;
            }
        
            /* Set the last buffer to wrap.
            */
            bdp--;
            bdp->cbd_sc |= BD_SC_WRAP;
            
            接着调用
                /* Set receive and transmit descriptor base.
                */
                fecp->fec_r_des_start = __pa((uint)(fep->cbd_mem_base));
                fecp->fec_x_des_start = __pa((uint)(fep->cbd_mem_base + RX_RING_SIZE*sizeof
(cbd_t)));
            将发送同接收地址值写入寄存器中。下面调用fec_request_intrs申请两个中断,一个为FEC的中
断static irqreturn_t fec_enet_interrupt(int irq, void * dev_id),用作发送接收、MII及出错等中断使用,另一个为
PHY端的中断static irqreturn_t mii_link_interrupt(int irq, void * dev_id),用作PHY芯片检测LINK DOWN或AUTO-
NEGO DONE中断,下面
                /* Clear and enable interrupts */
                fecp->fec_ievent = FEC_ENET_MASK;
                fecp->fec_imask = FEC_ENET_TXF | FEC_ENET_TXB | FEC_ENET_RXF | FEC_ENET_RXB
| FEC_ENET_MII;
            
                fecp->fec_hash_table_high = 0;
                fecp->fec_hash_table_low = 0;
                fecp->fec_r_buff_size = PKT_MAXBLR_SIZE;
                fecp->fec_ecntrl = 2;
                fecp->fec_r_des_active = 0x01000000;
            都是设置硬件寄存器,接着设置
                /* The FEC Ethernet specific entries in the device structure. */
                dev->open = fec_enet_open;
                dev->hard_start_xmit = fec_enet_start_xmit;
                dev->tx_timeout = fec_timeout;
                dev->watchdog_timeo = TX_TIMEOUT;
                dev->stop = fec_enet_close;
                dev->get_stats = fec_enet_get_stats;
                dev->set_multicast_list = set_multicast_list;
            这里是重载DEV的一些实现,当我们敲入ifconfig eth0 up的时候就会调用fec_enet_open。这里
要说明的是fec_timeout这个函数,这个函数运行在SOFTIRQ上下文,被当作网络的watchdog超时函数。而
set_multicast_list函数还不清楚是用来做什么的????接下来
                for (i=0; i<NMII-1; i++)
                    mii_cmds[i].mii_next = &mii_cmds[i+1];
                mii_free = mii_cmds;
            这是初始化mii的命令队列,这个其实是一个静态数组,然后通过内部的链表变量将这个数组链
起来。mii_free是一个链表头,指向第一个可用的mii_cmd元素。接着调用fec_set_mii初始化MII的速度:
                static void __inline__ fec_set_mii(struct net_device *dev, struct
fec_enet_private *fep)
                {
                    u32 rate;
                    struct clk *clk;
                    volatile fec_t *fecp;
                    fecp = fep->hwp;
                    fecp->fec_r_cntrl = OPT_FRAME_SIZE | 0x04;
                    fecp->fec_x_cntrl = 0x00;
                
                     /*
                     * Set MII speed to 2.5 MHz
                     */
                    clk = clk_get(NULL, "fec_clk");
                    rate = clk_get_rate(clk);
                    clk_put(clk);
                
                    fep->phy_speed =
                        ((((rate / 2 + 4999999) / 2500000) / 2) & 0x3F) << 1;
                    fecp->fec_mii_speed = fep->phy_speed;
                    fec_restart(dev, 0);
                }
            设置完clk后,调用fec_restart重新设置FEC的参数:
                    /* This function is called to start or restart the FEC during a link
                     * change. This only happens when switching between half and full
                     * duplex.
                     */
                    static void
                    fec_restart(struct net_device *dev, int duplex)
                    {
                        struct fec_enet_private *fep;
                        volatile cbd_t *bdp;
                        volatile fec_t *fecp;
                        int i;
                    
                        OUTDEB("entry!!!!!\n");
                        fep = netdev_priv(dev);
                        fecp = fep->hwp;
                    
                        /* Whack a reset. We should wait for this.
                        */
                        fecp->fec_ecntrl = 1;
                        udelay(10);
                    
                         /* Enable interrupts we wish to service.
                          */
                        fecp->fec_imask = FEC_ENET_TXF | FEC_ENET_TXB | FEC_ENET_RXF
| FEC_ENET_RXB | FEC_ENET_MII;
                    
                        /* Clear any outstanding interrupt.
                         *
                         */
                        fecp->fec_ievent = FEC_ENET_MASK;
                    #if 0/*modified by Zenith @ 2010.1.21 for int bug*/
                        fec_enable_phy_intr();
                    #endif
                        /* Set station address.
                        */
                        fec_set_mac_address(dev);
                    
                        /* Reset all multicast.
                        */
                        fecp->fec_hash_table_high = 0;
                        fecp->fec_hash_table_low = 0;
                    
                        /* Set maximum receive buffer size.
                        */
                        fecp->fec_r_buff_size = PKT_MAXBLR_SIZE;
                    
                        fec_localhw_setup();
                    
                        /* Set receive and transmit descriptor base.
                        */
                         fecp->fec_r_des_start = __pa((uint)(fep->cbd_mem_base));
                        fecp->fec_x_des_start = __pa((uint)(fep->cbd_mem_base +
RX_RING_SIZE*sizeof(cbd_t)));
                        
                        fep->dirty_tx = fep->cur_tx = fep->tx_bd_base;
                        fep->cur_rx = fep->rx_bd_base;
                    
                        /* Reset SKB transmit buffers.
                        */
                        fep->skb_cur = fep->skb_dirty = 0;
                        for (i=0; i<=TX_RING_MOD_MASK; i++) {
                            if (fep->tx_skbuff[i] != NULL) {
                                dev_kfree_skb_any(fep->tx_skbuff[i]);
                                fep->tx_skbuff[i] = NULL;
                            }
                        }
                    
                        /* Initialize the receive buffer descriptors.
                        */
                        bdp = fep->rx_bd_base;
                        for (i=0; i<RX_RING_SIZE; i++) {
                    
                            /* Initialize the BD for every fragment in the page.
                            */
                            bdp->cbd_sc = BD_ENET_RX_EMPTY;
                            bdp++;
                        }
                    
                        /* Set the last buffer to wrap.
                        */
                        bdp--;
                        bdp->cbd_sc |= BD_SC_WRAP;
                    
                        /* ...and the same for transmmit.
                        */
                        bdp = fep->tx_bd_base;
                        for (i=0; i<TX_RING_SIZE; i++) {
                    
                            /* Initialize the BD for every fragment in the page.
                            */
                            bdp->cbd_sc = 0;
                            bdp->cbd_bufaddr = 0;
                            bdp++;
                        }
                    
                        /* Set the last buffer to wrap.
                        */
                        bdp--;
                        bdp->cbd_sc |= BD_SC_WRAP;
                    
                        /* Enable MII mode.
                        */
                        if (duplex) {
                            fecp->fec_r_cntrl = OPT_FRAME_SIZE | 0x04;/* MII
enable */
                            fecp->fec_x_cntrl = 0x04;         /* FD
enable */
                        }
                        else {
                            /* MII enable|No Rcv on Xmit */
                            fecp->fec_r_cntrl = OPT_FRAME_SIZE | 0x06;
                            fecp->fec_x_cntrl = 0x00;
                        }
                        fep->full_duplex = duplex;
                    
                        /* Set MII speed.
                        */
                        fecp->fec_mii_speed = fep->phy_speed;
                    
                        /* And last, enable the transmit and receive processing.
                        */
                        fecp->fec_ecntrl = 2;
                        fecp->fec_r_des_active = 0x01000000;
                        
                        netif_start_queue(dev);
                    }
            这里的其实是一个内联函数:
                static inline void netif_start_queue(struct net_device *dev)
                {
                    clear_bit(__LINK_STATE_XOFF, &dev->state);
                }
            它清除state中的XOFF位。
        下面我们继续回到fec_enet_init函数中:
            /* Queue up command to detect the PHY and initialize the
             * remainder of the interface.
             */
            fep->phy_id_done = 0;
            fep->phy_addr = 0;
            mii_queue(dev, mk_mii_read(MII_REG_PHYIR1), mii_discover_phy);
        这里是将命令mk_mii_read(MII_REG_PHYIR1)插入mii的命令队列,并设置回调函数mii_discover_phy。
    end fec_enet_init()
    
end fec_enet_module_init()
我们来看一下mii_queue的实现:
static int
mii_queue(struct net_device *dev, int regval, void (*func)(uint, struct net_device *))
{
    struct fec_enet_private *fep;
    unsigned long    flags;
    mii_list_t    *mip;
    int        retval;
    /* Add PHY address to register command.
    */
    fep = netdev_priv(dev);
    regval |= fep->phy_addr << 23;
    retval = 0;
    spin_lock_irqsave(&fep->lock,flags);
    if ((mip = mii_free) != NULL) {
        mii_free = mip->mii_next;
        mip->mii_regval = regval;
        mip->mii_func = func;
        mip->mii_next = NULL;
        if (mii_head) {
            mii_tail->mii_next = mip;
            mii_tail = mip;
        }
        else {
            mii_head = mii_tail = mip;
            fep->hwp->fec_mii_data = regval;
        }
    }
    else {
        retval = 1;
    }
    spin_unlock_irqrestore(&fep->lock,flags);
    return(retval);
}
可以看到,它先是读取FEC硬件寄存器phy_addr的低八位,将它放入变量regval中,这里应该是取PHY的地址,然后先看下
mii_free队列中有无元素,也即是判断有无可用的MII命令队列,如果有,则把寄存器的值(即是要读写PHY的哪个寄存器)
及回调函数放入队列中,再判断mii_head有无元素,如果无的话就将mii_head指向当前这个元素,如果有的话,更新
mii_tail指向当前元素(mii_tail始终指向最后一个命令元素),所以当我们向MII命令队列插入第一个元素的时候,它就
直接读写PHY的那个寄存器(fep->hwp->fec_mii_data = regval;)。而我们前面FEC的MII初始化的时候已经置了MII的中断
,所以这里往fep->hwp->fec_mii_data寄存器写数据完后便会产生一个FEC中断,这个中断由fec_enet_interrupt函数里调
用fec_enet_mii来处理。
我们再来看下fec_enet_mii的实现:
/* called from interrupt context */
static void
fec_enet_mii(struct net_device *dev)
{
    struct    fec_enet_private *fep;
    volatile fec_t    *ep;
    mii_list_t    *mip;
    uint        mii_reg;
    fep = netdev_priv(dev);
    ep = fep->hwp;
    mii_reg = ep->fec_mii_data;
    spin_lock(&fep->lock);
    if ((mip = mii_head) == NULL) {
        printk("MII and no head!\n");
        goto unlock;
    }
    if (mip->mii_func != NULL)
        (*(mip->mii_func))(mii_reg, dev);
    mii_head = mip->mii_next;
    mip->mii_next = mii_free;
    mii_free = mip;
    if ((mip = mii_head) != NULL)
        ep->fec_mii_data = mip->mii_regval;
unlock:
    spin_unlock(&fep->lock);
}
可以看到,函数首先是读取FEC的硬件寄存器fec_mii_data,这里的值应该是PHY芯片通过MII接口返回上次要求读的寄存器
的值,然后如果有mip->mii_func的话就回调这个函数,接着移动mii_head和mii_free队列,最后再写入下一个要读(写)
的PHY的寄存器。
所以当我们在初始化函数中调用mii_queue(dev, mk_mii_read(MII_REG_PHYIR1), mii_discover_phy);将mii_discover_phy
插入命令队列时,它先将要读的寄存器MII_REG_PHYIR1通过宏mk_mii_read转换成MII寄存器的格式,然后写入fec_mii_data
寄存器,表示我想读取PHY芯片的MII_REG_PHYIR1寄存器的值,而读寄存器写完后,FEC的MII模块会产生一个中断表示说这
个PHY寄存器的读已经完成,然后中断处理函数fec_enet_mii中,会读取PHY返回的刚才我想要读取的寄存器的值给
fec_mii_data寄存器,后来再调用刚才插入的那个mii_discover_phy函数,mii_discover_phy函数就会根据这个返回的ID来
判断是哪个厂家的PHY芯片。这里将mii_discover_phy再帖出来:
/* Scan all of the MII PHY addresses looking for someone to respond
 * with a valid ID. This usually happens quickly.
 */
static void
mii_discover_phy(uint mii_reg, struct net_device *dev)
{
    struct fec_enet_private *fep;
    volatile fec_t *fecp;
    uint phytype;
    fep = netdev_priv(dev);
    fecp = fep->hwp;
    if (fep->phy_addr < 32) {
        if ((phytype = (mii_reg & 0xffff)) != 0xffff && phytype != 0) {
            /* Got first part of ID, now get remainder.
            */
            fep->phy_id = phytype << 16;
            mii_queue(dev, mk_mii_read(MII_REG_PHYIR2),
                            mii_discover_phy3);
        }
        else {
            fep->phy_addr++;
            mii_queue(dev, mk_mii_read(MII_REG_PHYIR1),
                            mii_discover_phy);
        }
    } else {
        printk("FEC: No PHY device found.\n");
        /* Disable external MII interface */
        fecp->fec_mii_speed = fep->phy_speed = 0;
        fec_disable_phy_intr();
    }
}
至于判断fep->phy_addr < 32与否,这个是说一个FEC可以接多个PHY,每个PHY有一个地址,用5个BIT来表示,这里是从
0~31去自加,然后查询对应的PHY地址上是否有PHY设备。
初始化过程中fec_enet_init调用完了之后,dev这个变量已经设置得差不多了,接下来是调用register_netdev(dev) 来注
册一个网络设备,后面fec_device[i] = dev;将这个DEV放入FEC的设备表中。到此,module_init完成。。。

展开阅读全文

没有更多推荐了,返回首页