zoukankan      html  css  js  c++  java
  • Linux驱动学习——网络接口DM9000驱动学习 mini2440

    网络接口DM9000驱动学习:

    /drivers/input/touchscreen/s3c2410_ts.c
    /drivers/input/s3c2410_ts.c

    参考:

    http://blogold.chinaunix.net/u3/118227/showart_2353723.html
    http://blog.csdn.net/ypoflyer/archive/2011/02/26/6209922.aspx

    等其他网络资料
    首先看一下DM9000的引脚和MINI2440的引脚连接

    DM9000  MINI2440 功能描述
    SD0   DATA0  
    数据信号
     |      |
    SD15  DATA15  
    数据信号
    CMD  ADDR2  
    识别为地址还是数据
    INT   EINT7  中断
    IOR#   nOE   
    读命令使能
    IOW#  nWE   
    写命令使能
    AEN   nGCS4  
    片选使能

    可以看出连接了16条数据线,1条地址线,而这唯一的一条地址线用于判断数据线传输的是地址还是数据,所以这16条数据线为数据和地址复用

    而片选信号使用的BANK4,则访问0x2000 0000 – 0x27FF FFFF这个范围的地址时会激活片选使能信号nGCS4

    而在MINI2440提供的内核中,DM9000的地址IO地址为0x20000000,数据IO0x2000 0004

    一、Mini2440开发板上DM9000的电气连接和Mach-mini2440.c文件的关系
    Mini2440开发板上DM9000S3C2440的连接关系如下:  
     

    其中片选信号AEN使用了nGCS4,所以网卡的内存区域在BANK4,也就是从地址0x20000000开始。DM9000TXD[2:0]作为strap pin(表带引脚)在电路图中是空接的,所以IO base300H。中断使用了EINT7

    /arch/arm/plat-s3c24xx/devs.c

                 /arch/arm/mach-s3c2410/include/mach/map.h

          platform_device结构体定义如下:Probe函数中获取地址和数据资源,探测,检测网口是否在线。 platform_get_resource

    二、两个重要的结构体简单介绍:sk_buffnet_device

       *sk_buff

       如果把网络传输看成是运送货物的话,那么sk_buff就是这个货物了,所有经手这个货物的人都要干点什么事儿,要么加个包装,要么印个戳儿等等。收 货的时候就要拆掉这些包装,得到我们需要的货物(payload data)。没有货物你还运输什么呢?由此可见sk_buff的重要性了。关于sk_buff的详细介绍和几个操作它的函数,参考本博客转载的一篇文 章:“linux内核sk_buff的结构分析,写得非常明白了。赞一个~

      *net_device

       又是一个庞大的结构体。好吧,我承认我从来就没有看全过这个结构体。它在内核中就是指代了一个网络设备。驱动程序需要在探测的时候分配并初始化这个结构体,然后使用register_netdev来注册它,这样就可以把操作硬件的函数与内核挂接在一起。

    定义一个platform_driver结构,该结构类似普通char类型驱动中的fops

    该结构的定义如下/include/linux/platform_device.h


    
    

    dm9000_ethtool_ops变量。是ethtool_ops结构体变量,为了支持ethtool,其中的函数主要是用于查询和设置网卡参数(当然也有的驱动程序可能不支持ethtool)。代码清单如下:

    Probe函数,探测,检测网口是否在线。

       主要完成的任务是:探测设备获得并保存资源信息,根据这些信息申请内存和中断,最后调用register_netdev注册这个网络设备。以下是代码清单,可以分成几个部分来看:

       1) 首先定义了几个局部变量:

       2) 初始化一个网络设备。关键系统函数:alloc_etherdev()

       2.1)将platform_devicenet_device关联起来

          SET_NETDEV_DEV(ndev,&pdev->dev);
    3)
    获得资源信息并将其保存在board_info变量db中。关键系统函数:netdev_priv(), platform_get_resource()

    使用INIT_DELAYED_WORK来初始化一个延迟工作队列并关联了一个操作函数m9000_poll_work()

       4) 根据资源信息分配内存,申请中断等等,并将申请后的资源信息也保存到db中,并且填充ndev中的参数。 关键系统函数:request_mem_region(), ioremap()。 自定义函数:dm9000_set_io()

       5)完成了第4步以后,回顾一下dbndev中都有了什么:

        6) 设备复位。硬件操作函数dm9000_reset()

    7) 读一下生产商和制造商的IDVIDPID,应该是0x90000A46。 关键函数:ior()

        8) 读一下芯片类型。读取CHIPR,确定是DM900A-B-E

        ========以上步骤结束后我们可以认为已经找到了DM9000========

         9)借助ether_setup()函数来部分初始化ndev。因为对以太网设备来讲,很多操作与属性是固定的,内核可以帮助完成。

       10) 手动初始化ndevopsdbmii部分。

    11) (如果有的话)EEPROM中读取节点地址。这里可以看到mini2440这个板子上没有为DM9000外挂EEPROM,所以读取出来的全部是 0xff。见函数dm9000_read_eeprom。 关于外挂EEPROM,可以参考datasheet上的7.EEPROM Format一节。

       12)  很显然ndev是我们在probe函数中定义的局部变量,如果我想在其他地方使用它怎么办呢?这就需要把它保存起来。内核提供了这个方法,使用函数platform_set_drvdata()可以将ndev保存成平台总线设备的私有数据。以后再要使用它时只需调用platform_get_drvdata()就可以了。

           platform_set_drvdata(pdev, ndev);

       13) 使用register_netdev()注册ndev

           ret= register_netdev(ndev);

    ——————————————————————————————

    dm9000_open

    进行的工作有向内核注册中断,复位并初始化dm9000,检查MII接口,使能传输等。代码清单如下:

    向内核注册中断,中断服务函数为dm9000_interrupt

    复位,初始化

    dm9000_init_dm9000

    读取IO模式寄存器

    power up PHY,

    Tx Control Reg, Back Presure,…

    清楚TX的状态位。

    设置广播地址,hash table

    设置中断掩码寄存器,使能SRAM地址自动归零,Packet TxPacket Rx中断,把imrdbboard info),同时,配置DM9000

    初始化驱动变量

    MII检测媒介状态,主要检测Link状态,这里使用msg可以选择获取许多信息

    mii_check_media

    如果是强制媒介,自动谈判关闭,那么退出。

    检测新的媒介

    mii_link_ok

    检测phy寄存器的状态位,可以检测到link状态

    这里使用了mii->mdio_read函数,该函数在dm9000_probe中定义:

    dm9000_phy_readdm9000.c中就有定义了,可以看出phy寄存器的读取方式采用了间接的方式

    由于dm9000没有区分普通寄存器和phy寄存器,所以,通过控制寄存器触发dm9000读取phy,然后放入一个数据寄存器中,就可以读取phy了,phy是不能直接读取的

    如果新媒介和旧媒介一样,那么不用改变

    如果没有媒介,那么输出信息,返回。

    读取MII advertise and LPAlinkpartner ability

    打印状态信息

    告诉上层网络驱动层驱动空间有缓冲区可用,开始发送数据包到驱动层。

           netif_start_queue(dev);

    启动设备工作队列

    /*之前在probe函数中已经使用INIT_DELAYED_WORK来初始化一个延迟工作队列并关联了一个操作函数dm9000_poll_work() 此时运行schedule来调用这个函数*/  

           dm9000_schedule_poll(db);

    dm9000_close

    进行的工作有向内核注册中断,复位并初始化dm9000,检查MII接口,使能传输等。代码清单如下:

    dm9000_start_xmit()

        重要的发送数据包函数。从上层发送sk_buff包。在看代码之前先来看一下DM9000是如何发送数据包的。

       

    如上图所示,在DM9000内部SRAM中,地址0x0000~0x0BFFTX Buffer,地址0x0C00~0x3FFFRX Buffer。在发送一个包之前,包中的有效数据必须先被存储到TXBuffer中并且使用输出端口命令来选择MWCMD寄存器。包的长度定义在TXPLLTXPLH中。最后设置TXCR寄存器的bit[0] TXREQ来自动发送包。如果设置了IMR寄存器的PTM位,则DM9000会产生一个中断触发在ISR寄存器的bit[1]=PTS=1,同时设置一个完成标志在NSR寄存器的bit[2]=TX1END或者 bit[3]=TX2END,表示包已经发送完了。发送一个包的具体步骤如下:

    Step 1检查存储数据宽度。通过读取中断状态寄存器(ISR)的bit[7:6]来确定是8bit16bit还是32bit

    Step 2写数据到TX SRAM

    Step 3写传输长度到TXPLLTXPLH寄存器中

    Step 4: 设置TXCR寄存器的bit[0]TXREQ来开始发送一个包。

    代码清单如下,让我们看看在获得自旋锁这段期间都干了些什么:

    保存中断的当然状态,并禁止本地中断,然后再去获取指定的锁


    下面四行代码将skb中的data部分写入DM9000TX RAM,并更新已发送字节数和发送计数

    如果发送的是第一个包,则设置一下包的长度后直接发送

    如果发送的是第二个数据包(表明队列中此时有包发送),则将其加入队列中:将 skb->lenskb->ip_summed(控制校验操作)赋值给board_info_t中有关队列的相关成员。调用函数 netif_stop_queue(dev),通知内核现在queue已满,不能再将发送数据传到队列中,注:第二个包的发送将在tx_done中实现。


    dm9000_rx

    读取最新数据的第一个字节byte

    检测第一个byte的值

    DM9000_PKT_RDY定义是0x01,如果第一个字节大于0x01,则不是正确的状态。因为第一个字节只能是01h00h

    一次性读入四个字节的内容到rxhdr变量,获得接收数据长度

                 dm9000_rxhdr是接收数据的头,一共4byte

    检测接收数据长度有效性,太小了不行(不是完整的Eth包?),超过一个包的最大长度也不行

    检测接收数据的状态字节



    关键的代码就是这里啦。使用到了上面提到的sk_buff。将RX SRAM中的data段数据放入sk_buff,然后发送给上层,至于怎么发送,不用去驱动操心了。sk_buffprotocol全部搞定。

    开始dev_alloc_skb,由于len不包括前4个字节,所以,多分配了4个字节。

    skb_reserve用于将数据起始指针向后移2个字节,因为

    skb socket buffer通过移动起始指针将帧头去掉

    中断处理相关函数

      DM9000的驱动程序采用了中断方式而非轮询方式。触发中断的时机发生在:1DM9000接收到一个包以后。2DM9000发送完了一个包以后。

       中断处理函数在open的时候被注册进内核。代码清单如下:


    进中断都要使用自旋锁?

    屏蔽所有中断,读取中断状态,清楚中断,如果打印中断信息,则打印

    如果发生收到包中断,那么接收数据

    如果发送包中断,那么调用dm9000_tx_done

    中断恢复


    dm9000_tx_done

    如果queue中有3个,那么第1个发送完毕,触发中断,调用done,发送第2个,第2个发送完毕,触发中断,调用done,继续发送第3个。

    注:dm9000可以发送两个数据包,当发送一个数据包产生中断后,要确认一下队列中有没有第2个包需要发送。

       1)读取dm9000寄存器NSRNetwork Status Register)获取发送的状态,存在变量tx_status中;

       2)如果发送状态为NSR_TX2END(第2个包发送完毕)或者NSR_TX1END(第1个包发送完毕),则将待发送的数据包数量(db->tx_pkt_cnt 1已发送的数据包数量(dev->stats.tx_packets1

       3)检查变量db->tx_pkt_cnt(待发送的数据包)是否大于0(表明还有数据包要发送),则调用函数dm9000_send_packet发送队列中的数据包;

       4)调用函数netif_wake_queue(dev)通知内核可以将待发送的数据包进入发送队列。


    7.一些操作硬件细节的函数。

       在看函数之前还是先来看一下DM9000 CMD PinProcessor并行总线的连接关系。CMD管脚用来设置命令类型。

    CMD管脚拉高时,这个命令周期访问DATA_PORT

    如果拉低, 则这个命令周期访问ADDR_PORT。见下图:

    当然,内存映射的I/O空间读写还是采用最基本的readb(), readw(), readl(), writeb(), writew(), writel() , readsb(),readsw(), readsl(), writesb(), writesw(), writesl()

    DM9000的驱动中还自定义了几个函数,方便操作。

    IO端口读一个字节。代码清单如下:

    IO端口写一个字节。代码清单如下:



    
    
  • 相关阅读:
    idea无法clean报错Error running 'lizi-user-api [clean]': No valid Maven installation found. Either set the home directory in the configuration dialog or set the M2_HOME environment variable on your system.
    maven项目无法下载依赖jar包
    JPA封装baseDao
    forward和redirect的区别
    java的三个体系
    Java基本修饰符
    SpringMVC 中,当前台传入多个参数时,可将参数封装成一个bean类
    注解@RequestParam——取请求参数
    冒泡排序
    为什么要使用线程池?
  • 原文地址:https://www.cnblogs.com/yanhc/p/2175234.html
Copyright © 2011-2022 走看看