打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
源码开放学ARM 网卡驱动实验

Linux 网卡驱动实验

标准Board Linux启动 & uBuntu Linux 连接

  • pc linux ( 192.168.0.200 )

  • board linux ( 192.168.0.201 )

    ifconfig sudo ifconfig eth0 192.168.0.xxx

测试网络连接 对ping可以连通

uboot 启动 & uBuntu Linux 连接

  1. 开发板上的跳线 S2 拨到 SDBOOT 那边,通过 SD 卡启动

  2. 启动之后进入 uboot 选项,修改环境变量

    [FriendlyLEG-TINY210]# printenvbaudrate=115200bootdelay=3ethact=dm9000ethaddr=00:01:02:03:04:05gatewayip=192.168.0.1ipaddr=192.168.0.201netmask=255.255.255.0serverip=192.168.0.200stderr=serialstdin=serialstdout=serial[FriendlyLEG-TINY210]# setenv ipaddr 192.168.0.201[FriendlyLEG-TINY210]# setenv serverip 192.168.0.200[FriendlyLEG-TINY210]# saveenvSaving Environment to NAND...Erasing Nand...Erasing at 0x40000 -- 100% complete.Writing to Nand... done[FriendlyLEG-TINY210]# 
  3. 重启开发板,验证是否设置成功? 看到 is alive 表示成功

    FriendlyLEG-TINY210# ping 192.168.0.200
    dm9000 i/o: 0x88001000, id: 0x90000a46 DM9000: running in 16 bit mode
    MAC: 00:01:02:03:04:05
    operating at 100M full duplex mode
    Using dm9000 device
    host 192.168.0.200 is alive
    FriendlyLEG-TINY210#

uboot 和 uBuntu 之间的 tftp 连接

ubuntu下搭建tftp server

  1. Install tftpd and related packages.
    $ sudo apt-get install xinetd tftpd tftp

  2. Create /etc/xinetd.d/tftp and put this entry:

    service tftp{	protocol        = udp	port            = 69	socket_type     = dgram	wait            = yes	user            = nobody	server          = /usr/sbin/in.tftpd	server_args     = /tftpboot	disable         = no}
  3. Make /tftpboot directory
    $ sudo mkdir /tftpboot
    $ sudo chmod -R 777 /tftpboot
    $ sudo chown -R nobody /tftpboot

  4. Start tftpd through xinetd
    $ sudo /etc/init.d/xinetd restart

  5. test tftp in localhost
    $ cp something /tftpboot

    $ tftp localhost$ get something  Received 4315348 bytes in 0.4 seconds$ tftp> quit$ ls something
  6. test tftp in board $ tftp 21000000 zImage

    uboot 和 uBuntu 之间的 uImage 内核下载测试

设置 uboot 参数

# setenv bootargs root=/dev/mtdblock4 console=ttySAC0,115200 init=/linuxrc lcd=S70# setenv ipaddr 192.168.0.201# setenv serverip 192.168.0.200# saveenv# printenv

制作 uImage 内核文件

# tftp 21000000 uImage# bootm 21000000

获得 mkimage

DM9000 驱动代码实现

平台设备的注册 platform_device

arch/arm/mach-s5pv210/mach-mini210.c

平台驱动的注册 platform_driver

drivers/net/dm9000.c

头文件的包含

drivers/net/dm9000.h include/linux/dm9000.h

几个重要的文件

  • .config
    make menuconfig 的配置选项保存在这个文件中
    CONFIG_DM9000 = m

  • include/generated/autoconf.h
    make 执行时会通过脚本分析 .config 文件,生成 autoconf.h 参与内核编译

    #define CONFIG_DM9000 1

  • driver/net/makefile
    obj-$(CONFIG_DM9000) = dm9000.o

    Makefile

    obj-m := dm9000.o

    KDIR := /home/limingth/tiny210/src/linux-2.6.35.7

    modules: make -C $(KDIR) SUBDIRS=$(PWD) $@ ls -l dm9000.ko

    clean: -rm .o.ko

    step by step

  • install tftpd
    sudo apt-get install tftpd

  • install inetd
    sudo apt-get install openbsd-inetd

    server dir /srv/tftp

    sudo vi /etc/inetd.conf

    #:BOOT: TFTP service is provided primarily for booting.  Most sites#       run this only on machines acting as "boot servers."#tftp		dgram	udp	wait	nobody	/usr/sbin/tcpd	/usr/sbin/in.tftpd /srv/tftptftp		dgram	udp	wait	nobody	/usr/sbin/tcpd	/usr/sbin/in.tftpd /home/limingth/tiny210

    sudo /etc/init.d/openbsd-inetd restart

  • download Image
    limingth@ubuntu:~$ tftp 127.0.0.1
    tftp> get zImage2
    tftp> quit

  • change u-boot bootargs

    操作步骤

    FriendlyLEG-TINY210# setenv bootargs root=/dev/mtdblock4 console=ttySAC0,115200 init=/linuxrc lcd=S70 FriendlyLEG-TINY210# printenv baudrate=115200 bootargs=root=/dev/mtdblock4 console=ttySAC0,115200 init=/linuxrc lcd=S70 bootdelay=3 ethact=dm9000 ethaddr=08:0a:0a:0a:0a:0a gatewayip=192.168.0.1 ip=192.168.0.201 ipaddr=192.168.0.201 netmask=255.255.255.0 serverip=192.168.0.200 stderr=serial stdin=serial stdout=serial

    Environment size: 315/16380 bytes

* 启动命令
tftp 21000000 uImage bootm 21000000

修改内核源码

按以下方法修改内核以适应uboot,详参见liukun321的文章
http://blog.csdn.net/liukun321/article/details/7383669, 忽略此步骤将会出现加载kernel时卡死在 Uncompressing Linux… done, booting the kernel….

arch/arm/mach-s5pv210/include/mach/memory.h 文件26,27行内容,  将Maximum of 256MiB in one bank的限制改为Maximum of 512MiB in one bank 作如下修改:#defineSECTION_SIZE_BITS   29#defineNODE_MEM_SIZE_BITS   29

生成 uImage

limingth@ubuntu:~/tiny210/src$ ./mkimage Usage: ./mkimage -l image          -l ==> list image header information       ./mkimage [-x] -A arch -O os -T type -C comp -a addr -e ep -n name -d data_file[:data_file...] image          -A ==> set architecture to 'arch'          -O ==> set operating system to 'os'          -T ==> set image type to 'type'          -C ==> set compression type 'comp'          -a ==> set load address to 'addr' (hex)          -e ==> set entry point to 'ep' (hex)          -n ==> set image name to 'name'          -d ==> use image data from 'datafile'          -x ==> set XIP (execute in place)		         ./mkimage [-D dtc_options] -f fit-image.its fit-image       ./mkimage -V ==> print version information and exit       limingth@ubuntu:~/tiny210/src$ ./mkimage -A arm -O linux -T kernel -C none -a 21000000 -e 21000040 -n "linux-2.6.28" -d zImage2  myuImageImage Name:   linux-2.6.28Created:      Wed Aug 29 13:32:34 2012Image Type:   ARM Linux Kernel Image (uncompressed)Data Size:    4288476 Bytes = 4187.96 kB = 4.09 MBLoad Address: 21000000Entry Point:  21000040

/include/generated/mach-types.h
./arch/arm/include/asm/mach-types.h

limingth@ubuntu:~/tiny210/src/linux-2.6.35.7$ vi arch/arm/include/asm/mach-types.h limingth@ubuntu:~/tiny210/src/linux-2.6.35.7$ vi include/generated/mach-types.h in line 2950	#define MACH_TYPE_MINI210              3466

DM9000 驱动代码实现

#include <linux/module.h>#include <linux/ioport.h>#include <linux/netdevice.h>#include <linux/etherdevice.h>#include <linux/init.h>#include <linux/skbuff.h>#include <linux/spinlock.h>#include <linux/crc32.h>#include <linux/mii.h>#include <linux/ethtool.h>#include <linux/dm9000.h>#include <linux/delay.h>#include <linux/platform_device.h>#include <linux/irq.h>#include <linux/slab.h>#include <asm/delay.h>#include <asm/irq.h>#include <asm/io.h>#include "dm9000.h"MODULE_AUTHOR("AKAE");MODULE_DESCRIPTION("Sample DM9000 driver");MODULE_LICENSE("Dual BSD/GPL");#define DEVICE_NAME	"dm9000"/*board_info结构体,用来保存芯片相关的一些私有信息*/typedef struct board_info {	void __iomem	*io_addr;		/*映射到Linux内存空间的地址口虚拟地址*/	void __iomem	*io_data;		/*映射到Linux内存空间的数据口虚拟地址*/	u16		 irq;			/*IRQ*/	u16		tx_pkt_cnt;		/*待发送数据包数量,最多只能有两个*/	u16		queue_pkt_len;		/*待发送数据包长度*/	u8		imr_all;		/*用于给DM9000_IMR赋值的一个变量*/	struct resource	*addr_res;   		/*地址口地址*/	struct resource *data_res;		/*数据口地址*/	struct resource *irq_res;		/*IRQ*/	struct mutex	 addr_lock;		/*互斥信号两*/	spinlock_t	lock;			/*自旋锁*/} board_info_t;/*Read a byte from I/O port*/static u8 ior(board_info_t * db, int reg){		writeb(reg, db->io_addr);	return readb(db->io_data);}/*Write a byte to I/O port*/static void iow(board_info_t * db, int reg, int value){	writeb(reg, db->io_addr);	/*将寄存器地址写入映射进内存的i\o内存空间,通过地址找到相应的寄存器*/	writeb(value, db->io_data);	/*将数据写入寄存器*/}/*调用时机:当网卡有数据需要发送的时候,该函数被调用*//*第二个包的发送将在dm9000_tx_done中实现,这是因为当第一个数据发送完之后会产生一个中断,则会调用dm9000_tx_done对应的函数*/static int dm9000_start_xmit(struct sk_buff *skb, struct net_device *dev){	unsigned long flags;	board_info_t *db = netdev_priv(dev);	/*获得自旋锁并禁止中断,中断状态保存在flags中*/	spin_lock_irqsave(&db->lock, flags);	/*将io_addr写入寄存器MWCMD(写数据到DM9000内的发送SRAM中),进行这个操作后,向io_data写入的数据会传输到dm9000内部TX_SRAM中*/	writeb(DM9000_MWCMD, db->io_addr);	writesw(db->io_data, skb->data, (skb->len+1) >> 1);	/*待发送的数据包数量(tx_pkt_cnt)加1*/	db->tx_pkt_cnt++;	/* TX control: First packet immediately send, second packet queue */	if (db->tx_pkt_cnt == 1) {		iow(db, DM9000_TXPLL, skb->len);  		iow(db, DM9000_TXPLH, skb->len >> 8); 		iow(db, DM9000_TCR, TCR_TXREQ);	} else {		/* Second packet */		db->queue_pkt_len = skb->len;	}	/*释放自旋锁,并恢复中断之前的状态*/	spin_unlock_irqrestore(&db->lock, flags);	/*每个数据包写入网卡SRAM后都要释放skb*/  	dev_kfree_skb(skb);	return NETDEV_TX_OK;}static void dm9000_tx_done(struct net_device *dev, board_info_t *db){	/*读取dm9000寄存器NSR(NetworkStatus Register)获取发送的状态,存在变量tx_status中*/	int tx_status = ior(db, DM9000_NSR);	/*若有数据包发送完成,则进入*/	if (tx_status & (NSR_TX2END | NSR_TX1END)) {		/*待发送的数据包数量(tx_pkt_cnt)减1*/		db->tx_pkt_cnt--;		/*已发送的数据包数量(stats.tx_packets)加1*/		dev->stats.tx_packets++;		/*检查变量tx_pkt_cnt是否大于0(如果大于0,表明还有数据包要发送),则再次发送*/		if (db->tx_pkt_cnt > 0)		{				/*将要发送的数据包的长度写入DM9000的两个发送数据包长度寄存器中*/			iow(db, DM9000_TXPLL, db->queue_pkt_len);			iow(db, DM9000_TXPLH, db->queue_pkt_len >> 8);			/*提出发送请求,发送完成后该位自动清零*/			iow(db, DM9000_TCR, TCR_TXREQ);		}	}}/*该结构体封装了dm9000接收的数据包的头信息*/ struct dm9000_rxhdr {	u8	RxPktReady;	u8	RxStatus;	__le16	RxLen;} __attribute__((__packed__));/*告诉编译器取消结构在编译过程中的优化*/static void dm9000_rx(struct net_device *dev){	board_info_t *db = netdev_priv(dev);	struct dm9000_rxhdr rxhdr;	struct sk_buff *skb;	u8 rxbyte, *rdptr;	int RxLen;		/*接收数据包的长度*/	/* Check packet ready or not */	do {		/* 读取寄存器MRCMDX,MRCMDX寄存器地址保存在了db->io_addr中,下面要读取MRCMDX寄存器的值只需要读取db->io_data即可*/		ior(db, DM9000_MRCMDX);		/*读取MRCMDX寄存器的值,rxbyte为8位,因为MRCMDX寄存器存储的值就是8位*/		rxbyte = readb(db->io_data);		/*DM9000_PKT_RDY定义为0x01,而rxbyte为我们读取的第一个字节,其值只能是0x00(表示还没接收)或0x01(表示已经接收)*/		if (!(rxbyte & DM9000_PKT_RDY))			return;		/*将MRCMD寄存器地址写入db->io_addr*/		writeb(DM9000_MRCMD, db->io_addr);		/*一次性从MRCMD寄存器(即RX_SRAM)中读入四个字节的内容到rxhdr变量,*/		readsw(db->io_data, &rxhdr, (sizeof(rxhdr)+1) >> 1);		/*获取接收数据包的长度*/		RxLen = le16_to_cpu(rxhdr.RxLen);		skb = dev_alloc_skb(RxLen + 4);		skb_reserve(skb, 2);		rdptr = (u8 *) skb_put(skb, RxLen - 4);		/*读取数据放入rdptr所在位置,rdptr位置为skb_tail,所以这句话就是读取RX_SRAM内容到skb*/		readsw(db->io_data, rdptr, (RxLen+1) >> 1);		dev->stats.rx_bytes += RxLen;		/*函数eth_type_trans用于从以太网数据包中提取网络协议内容,并把它放入skb结构的相应位置*/		skb->protocol = eth_type_trans(skb, dev);		/*调用netif_rx将数据交给协议栈*/		netif_rx(skb);	} while (rxbyte & DM9000_PKT_RDY);}/*网卡有三种类型的中断:新报文到达中断,报文发送完成中断,出错中断*/static irqreturn_t dm9000_interrupt(int irq, void *dev_id){	struct net_device *dev = dev_id;	board_info_t *db = netdev_priv(dev);	int int_status;	unsigned long flags;	u8 reg_save;	/*获得自旋锁并禁止中断,中断状态保存在flags中*/	spin_lock_irqsave(&db->lock, flags);	/*保存中断前的状态*/	reg_save = readb(db->io_addr);	/* 禁用DM9000中断*/	iow(db, DM9000_IMR, IMR_PAR);	/*中断状态寄存器,当一个中断到来时,该寄存器存放着中断类型。DM9000中断处理函数通过读取该寄存器,得到目前中断信息*/	/*读取该中断状态寄存器之后,还需要将读取结果存放回该寄存器,也就是需要清除中断状态,否则将无法再次响应中断*/	int_status = ior(db, DM9000_ISR);	iow(db, DM9000_ISR, int_status);	/*检测中断状态寄存器,如果是由于收到数据而触发的中断,显然调用dm9000_rx()把数据取走,传递给上层*/	if (int_status & ISR_PRS)		dm9000_rx(dev);	/*检测中断状态寄存器,如果是由于发送完了数据而触发的中断,则调用dm9000_tx_done()函数*/	if (int_status & ISR_PTS)		dm9000_tx_done(dev, db);	/*重新使能DM9000各中断功能*/	iow(db, DM9000_IMR, db->imr_all);	/*恢复中断前的状态*/	writeb(reg_save, db->io_addr);	/*自旋锁解锁*/	spin_unlock_irqrestore(&db->lock, flags);	return IRQ_HANDLED;}/*当使用命令ifconfig eth0 up时,网卡被打开,执行这一函数,向内核注册中断,复位并初始化dm9000,检查MII接口,使能传输等*/static int dm9000_open(struct net_device *dev){	/*返回board_info_t的地址*/	board_info_t *db = netdev_priv(dev); 	/*IRQF_TRIGGER_MASK为中断触发方式,定义在Interrupt.h中*/  	unsigned long irqflags = db->irq_res->flags & IRQF_TRIGGER_MASK;	/*设置为共享中断*/	irqflags |= IRQF_SHARED;	/*申请中断并注册中断服务程序*/	request_irq(dev->irq, dm9000_interrupt, irqflags, dev->name, dev);	/*复位dm9000*/	iow(db, DM9000_NCR, 1);	do {		udelay(100);	} while (ior(db, DM9000_NCR) & 0x1);	/*初始化DM9000*/	/*DM9000的GPIO0默认为输出做POWER_DOWN功能,默认值为1,表明要使PHY层POWER_DOWN,即不启用PHY,	若希望启用PHY,则驱动程序需要通过写“0”将PWER_DOWN信号清零*/	iow(db, DM9000_GPR, 0);	/* Enable PHY */	/*使能数据包接收中断,使能数据包传输中断*/	db->imr_all = IMR_PAR | IMR_PTM | IMR_PRM;	iow(db, DM9000_IMR, db->imr_all);	/*设置DM9000的RCR接收控制寄存器的第0位为1,接收使能*/	iow(db, DM9000_RCR, RCR_RXEN);	return 0;}/*驱动支持的网卡设备操作函数*/static const struct net_device_ops dm9000_netdev_ops = {	.ndo_open		= dm9000_open,	.ndo_start_xmit		= dm9000_start_xmit,};/*内核加载驱动后,自动执行驱动的probe函数,进行资源的探测和申请资源*/static int __devinit dm9000_probe(struct platform_device *pdev){	struct board_info *db;		/*将获得的资源信息存放在这个结构体里*/	struct net_device *ndev;	/*定义一个网络设备*/	int iosize;	int i;	u32 id_val;	/*分配ndev,使用alloc_etherdev()函数分配一个网络设备的结构体,原型在include/linux/etherdevice.h */	ndev = alloc_etherdev(sizeof(struct board_info));	/*通过SET_NETDEV_DEV()将platform_device与net_device接洽关联起来*/	SET_NETDEV_DEV(ndev, &pdev->dev);	/*下面都是设置board_info结构体,将获得的资源信息存放在这个结构体里*/	/*struct board_info *db和struct net_device *ndev都是局部变量。但是又需要board_info和net_device二者建立一一对应关系,而	board_info 是一个自定义结构,通过netdev_priv(ndev)就把board_info放入net_device里,建立了联系。创建net_device 时已经为	board_info 留了空间:ndev = alloc_etherdev(sizeof(struct board_info));*/	db = netdev_priv(ndev);/*将网络设备结构与自定义设备结构建立关联,获取net_device结构的私有成员保存到struct board_info *db中*/	spin_lock_init(&db->lock);		/*初始化自旋锁*/  	mutex_init(&db->addr_lock);		/*初始化互斥信号量*/	db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);	/*获取DM9000地址口的资源地址*/	db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);	/*获取DM9000数据口的资源地址*/	db->irq_res  = platform_get_resource(pdev, IORESOURCE_IRQ, 0);	/*获取DM9000中断资源*/	iosize = resource_size(db->addr_res);	/*将地址口映射到linux内存空间的虚拟地址*/	db->io_addr = ioremap(db->addr_res->start, iosize);	iosize = resource_size(db->data_res);	/*将数据口映射到linux内存空间的虚拟地址*/	db->io_data = ioremap(db->data_res->start, iosize);/*****************************设置结构体board_info结束***************************************************/	/*填充net_device结构体*/	ndev->base_addr = (unsigned long)db->io_addr;	/*设置网络设备地址*/	ndev->irq	= db->irq_res->start;		/*设置网络设备中断资源地址*/	/*获取DM9000的ID号,需要多次获得以免失败*/	for (i = 0; i < 8; i++) {		id_val  = ior(db, DM9000_VIDL);		id_val |= (u32)ior(db, DM9000_VIDH) << 8;		id_val |= (u32)ior(db, DM9000_PIDL) << 16;		id_val |= (u32)ior(db, DM9000_PIDH) << 24;		if (id_val == DM9000_ID)			break;	}	/*借助ether_setup()函数来部分初始化ndev。因为对以太网设备来讲,很多操作与属性是固定的,内核可以帮助完成*/	ether_setup(ndev);	/*驱动支持的网卡设备操作函数*/	ndev->netdev_ops = &dm9000_netdev_ops;	/*注册ndev网络设备*/	register_netdev(ndev);	return 0;}static struct platform_driver dm9000_driver = {	.driver	= {		.name    = "dm9000",         /*驱动的名字*/		.owner	 = THIS_MODULE,	},	.probe   = dm9000_probe,             /*重要的函数:资源探测函数*/};/*第一步:加载网卡驱动首先要被执行的*/static int __init dm9000_init(void){	printk(KERN_INFO "%s Ethernet Driver\n", DEVICE_NAME);	return platform_driver_register(&dm9000_driver);	/*调用函数platform_driver_register()函数注册驱动*/}static void __exit dm9000_cleanup(void){	platform_driver_unregister(&dm9000_driver);}module_init(dm9000_init);module_exit(dm9000_cleanup);
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
使用uboot脚本自动化方便的调试uboot或者内核
ALSA SOC在Linux3.1上的一些改进
【Linux笔记】LED驱动程序
S3C2410的Linux下DMA驱动程序开发 &SEP4020 DMA驱动编写心得及使用流程
Linux内核开发之内存与I/O访问(二)
linux中的IO端口映射和IO内存映射
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服