linux3.4.2 之DM9000网卡驱动

本文详细解析DM9000网卡的硬件连接、时序分析、存储控制器配置、驱动框架及发包流程,涵盖S3C2440与DM9000的接口细节、内核驱动配置与平台设备注册过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

DM9000硬件连接分析

DM9000时序分析与S3C2440存储控制器分析

网卡驱动框架

DM9000发包分析

配置使用内核自带DM9000驱动


DM9000硬件连接分析

s3c2440与DM9000的连接关系如下:

从上图可得出下面几个关键点:

  • 1.中断线与GPF7相连,对应IRQ_EINT7
  • 2.片选与nGCS4相连,基地址对应0x2000_0000
  • 3.CMD线与地址线ADDR2相连,意味着要使CMD高电平,需要ADDR2 = 1,所以往0x20000004中写数据就会拉高CMD。(基地址0x2000_0000,偏移地址0x4,对应二进制"100“,所以此时地址ADDR2=1)
    从DM9000数据手册可知:
    当CMD为高时,DM9000认为传输的是数据;当CMD为低时,DM9000认为传输的是寄存器地址。

    所以在控制DM9000时,往0x20000004地址写命令,往0x20000000地址写寄存器地址。

DM9000时序分析与S3C2440存储控制器分析

DM9000读取时序图

DM9000写时序图:

BWSCON寄存器

  • ST4 : UB/LB是指一个u16的数据分高低字节两次发送,由于我们是16位数据线可一次发送,所以ST4 = 0
  • WS4: 硬件没有连接。所以WS4 = 0
  • DW4: 16位数据线,所以DW4 = 01

BANKCON寄存器
通过分析DM9000读写时序图,设置参数如下:(HCLK=100MHZ, 1个时钟等于10ns)
Tacs :nGCSn前的地址建立时间,观察到DM9000的CS#与CMD为同一根线,所以Tacs = 0
Tcos:发送读命令前的片选建立时间,DM9000C的T1>=0ns,所以Tcos = 0
Tacc:读写信号脉冲时间,DM9000C的T2>=10ns = 1 clock,所以Tacc = 1
Tcoh:当读写信号(IOR# / IOW#)变为高电平后,片选信号还要维持多长时间:
           写DM9000C时, IOW#变为高电平之后, 数据线上的数据最小维持3ns,T4 = 3
           读DM9000C时, IOR#变为高电平之后, 数据线上的数据最大位置6ns,T4 = 6
           所以Tcoh = 1 clock = 10ns
Tcah: nGCS变为高电平后,地址信号维持时间,理由同Tacs。所以Tcah = 0
Tcap: page模式下访问周期,不是用page模式,所以Tcap = 0
PMC:正常模式

网卡驱动框架

1.通过alloc_etherdev()函数分配,初始化net_device,同时分配一段私有数据空间,通过netdev_priv函数指向board_info结构体
2.设置net_device结构体
3.设置board_info结构体,以及MII接口

/* Structure/enum declaration ------------------------------- */
typedef struct board_info { 
	u32 io_addr;    /* Register I/O base address,命令寄存器IO基地址 */
	u32 io_data;    /* Data I/O address ,数据寄存器IO地址*/
	u8 op_mode;     /* PHY operation mode ,PHY操作模式*/
	u8 io_mode;     /* 0:word, 2:byte ,IO模式,0位字,而2为字节*/
	u8 Speed;	    /* current speed,当前的速度 */
	u8 chip_revision;
	int rx_csum;    /* 0:disable, 1:enable,接收计数使能,0为不使能,1为使能 */
	
	u32 reset_counter;   /* counter: RESET */ 
	u32 reset_tx_timeout;/* 发送数据超时 */
	int tx_pkt_cnt;                     /* 传输包计数 */
	int cont_rx_pkt_cnt;                /* 当前接受的数据包计数*/
	struct net_device_stats stats;      /* 设备传输统计 */
	
	struct timer_list timer;            /* 时间列表 */
	unsigned char srom[128];
	spinlock_t lock;                    /* 自旋锁 */
	struct mii_if_info mii;             /* MII总线信息 */
} board_info_t;

4.实现ethtool_ops结构体,该结构体为net_device结构体中的ethtool_ops成员

static struct ethtool_ops dmfe_ethtool_ops = {
  .get_drvinfo    = dmfe_get_drvinfo,
  .get_settings   = dmfe_get_settings,
  .set_settings   = dmfe_set_settings,
  .get_link     = dmfe_get_link,
  .nway_reset   = dmfe_nway_reset,
};

5.实现net_device_ops结构体,该结构体为net_device结构体中的netdev_ops成员

static const struct net_device_ops dm9k_netdev_ops = {
  .ndo_open   = dmfe_open,                //打开DM9000调用的函数
  .ndo_stop   = dmfe_stop,
  .ndo_start_xmit   = dmfe_start_xmit,  //发送包函数
  .ndo_tx_timeout   = dmfe_timeout,     //当watchdog超时时调用该函数。
  .ndo_set_rx_mode  = dm9000_hash_table,//设置DM9000的组播地址
  .ndo_do_ioctl   = dmfe_do_ioctl,//dm9000的ioctl实际上是使用了mii的ioctl
  .ndo_change_mtu   = eth_change_mtu,
  .ndo_get_stats = dmfe_get_stats,
  .ndo_validate_addr  = eth_validate_addr,
  .ndo_set_mac_address  = eth_mac_addr,
};

6.注册net_device结构体:register_netdev()

DM9000发包分析

    

DM9000共16k的SRAM中,地址0x0000~0x0BFF(3k)是TX Buffer, 地址0x0C00~0x3FFF(13k)是RX Buffer。发送一个包的具体步骤如下:
Step 1: 检查存储数据宽度。通过读取中断状态寄存器(ISR)的bit[7:6]来确定是8bit,16bit还是32bit。
Step 2: 写数据到TX SRAM中。
Step 3: 写传输长度到TXPLL和TXPLH寄存器中。
Step 4: 设置TXCR寄存器的bit[0]TXREQ来开始发送一个包。

配置使用内核自带DM9000驱动

  • 内核配置

   -> Device Drivers 
        -> Network device support
            -> Ethernet driver support 
                -> DM9000 support

  • arch/arm/mach-s3c24xx/mach-smdk2440.c 添加资源与平台设备。
#include <linux/dm9000.h>
#define MACH_SMDK2440_DM9K_BASE    (S3C2410_CS4 + 0x300)
static struct resource smdk2440_dm9k_resource[] = {
       [0] = {
              .start = MACH_SMDK2440_DM9K_BASE,
              .end   = MACH_SMDK2440_DM9K_BASE + 3,
              .flags = IORESOURCE_MEM
       },
       [1] = {
              .start = MACH_SMDK2440_DM9K_BASE + 4,
              .end   = MACH_SMDK2440_DM9K_BASE + 7,
              .flags = IORESOURCE_MEM
       },
       [2] = {
              .start = IRQ_EINT7,
              .end   = IRQ_EINT7,
              .flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHEDGE,
       }
};
/*
 * The DM9000 has no eeprom, and it's MAC address is set by
 * the bootloader before starting the kernel.
 */
static struct dm9000_plat_data SMDK2440_dm9k_pdata = {
       .flags             = (DM9000_PLATF_16BITONLY | DM9000_PLATF_NO_EEPROM),
};

static struct platform_device smdk2440_device_eth = {
       .name            = "dm9000",
       .id           = -1,
       .num_resources      = ARRAY_SIZE(smdk2440_dm9k_resource),
       .resource = smdk2440_dm9k_resource,
       .dev        = {
              .platform_data       = &SMDK2440_dm9k_pdata,
       },
};
  • 将定义好的平台设备添加到初始化数组当中
static struct platform_device *smdk2440_devices[] __initdata = {
       ......
       &smdk2440_device_eth,                     //添加该句
};
  • 重新编译内核 make uImage ,并复制到tftp目录下 cp arch/arm/boot/uImage /home/ningjw/tftpboot
  • 烧录内核
tftp 0x30000000 uImage
nand erase.part kernel
nand write.jffs2 0x30000000 0x60000 $filesize
  • 重启开发板,还能够启动网络文件系统,一直DM9000成功

 

 

这里提供一个简单的DMA驱动程序,仅供参考。需要注意的是,该程序仅适用于特定硬件平台,不可直接复制使用。 ```c #include <linux/module.h> #include <linux/init.h> #include <linux/platform_device.h> #include <linux/dma-mapping.h> #include <linux/dmaengine.h> #include <linux/slab.h> #define DMA_BUF_SIZE 4096 struct dma_buf { dma_addr_t dma_addr; void *virt_addr; }; struct dma_dev { struct device *dev; struct dma_chan *chan; struct dma_buf buf; }; static int dma_dev_probe(struct platform_device *pdev) { struct dma_dev *dev; struct resource *res; int err = -ENOMEM; dev = kzalloc(sizeof(*dev), GFP_KERNEL); if (!dev) goto fail_alloc_dev; dev->dev = &pdev->dev; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(dev->dev, "failed to get memory resource\n"); goto fail_get_resource; } dev->buf.virt_addr = dma_alloc_coherent(dev->dev, DMA_BUF_SIZE, &dev->buf.dma_addr, GFP_KERNEL); if (!dev->buf.virt_addr) { dev_err(dev->dev, "failed to allocate DMA buffer\n"); goto fail_dma_alloc; } dev->chan = dma_request_channel(DMA_MEMCPY); if (!dev->chan) { dev_err(dev->dev, "failed to request DMA channel\n"); goto fail_dma_request; } return 0; fail_dma_request: dma_free_coherent(dev->dev, DMA_BUF_SIZE, dev->buf.virt_addr, dev->buf.dma_addr); fail_dma_alloc: fail_get_resource: kfree(dev); fail_alloc_dev: return err; } static int dma_dev_remove(struct platform_device *pdev) { struct dma_dev *dev = platform_get_drvdata(pdev); dma_release_channel(dev->chan); dma_free_coherent(dev->dev, DMA_BUF_SIZE, dev->buf.virt_addr, dev->buf.dma_addr); kfree(dev); return 0; } static struct platform_driver dma_dev_driver = { .driver = { .name = "dma_dev", }, .probe = dma_dev_probe, .remove = dma_dev_remove, }; static int __init dma_dev_init(void) { return platform_driver_register(&dma_dev_driver); } static void __exit dma_dev_exit(void) { platform_driver_unregister(&dma_dev_driver); } module_init(dma_dev_init); module_exit(dma_dev_exit); MODULE_AUTHOR("Your Name"); MODULE_LICENSE("GPL"); ``` 在上述代码中,我们定义了一个`dma_dev`结构体,其中包含了一个`dma_addr_t`类型的DMA地址和一个指向DMA缓冲区的虚拟地址。在`dma_dev_probe`函数中,我们首先通过`kzalloc`函数分配了一个`dma_dev`结构体,并将其与设备对象相关联。然后,我们通过`platform_get_resource`函数获取设备的内存资源,并通过`dma_alloc_coherent`函数分配一段大小为`DMA_BUF_SIZE`的DMA缓冲区。接下来,我们调用`dma_request_channel`函数请求DMA通道,并将其与`dma_dev`结构体相关联。 在`dma_dev_remove`函数中,我们首先获取`dma_dev`结构体,并释放DMA通道和DMA缓冲区。最后,我们通过`platform_driver_register`函数将驱动程序注册到Linux内核中,并提供了一个`__exit`函数来卸载驱动程序。 需要注意的是,这段代码并不完整,缺少了一些必要的细节,例如创建设备节点、使用DMA通道传输数据等等。实际上,DMA驱动程序的编写涉及到非常复杂的硬件操作,需要根据具体硬件平台进行调整。因此,在编写DMA驱动程序之前,建议先学习相关的硬件知识和Linux内核编程基础知识。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值