前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux下编写ENC28J60网卡驱动,完善网络设备框架

Linux下编写ENC28J60网卡驱动,完善网络设备框架

作者头像
DS小龙哥
发布2022-10-31 17:09:41
4.2K0
发布2022-10-31 17:09:41
举报
文章被收录于专栏:嵌入式项目开发

一、框架模型

linux下设备驱动都有一套标准的结构,字符设备,块设备,网络设备都是自己的一套框架。编写驱动只需要把内核的框架搞清楚,然后照着结构填入参数,注册进内核,在应用层就可以按照标准的形式调用了。 对于网络设备而言,主要目的就是网络数据的收发,编写驱动时将linux网络设备驱动里的接口与实际网卡硬件的操作接口对应上,应用层就可以操作网卡完成网络通信了。底层驱动里编写网卡驱动与单片机一样。

这是网络设备驱动注册的一些函数:

代码语言:javascript
复制
动态分配空间
#define alloc_etherdev(sizeof_priv) alloc_etherdev_mq(sizeof_priv, 1)
函数参数:分配的空间大小。如果自己没有定义自己的结构体,就直接填 sizeof(struct net_device)
函数返回值:执行成功返回申请的空间地址。
空间分配的函数还有一个 alloc_netdev()函数。
alloc_etherdev()是 alloc_netdev()针对以太网的"快捷"函数


注册网络设备
int register_netdev(struct net_device *dev)
函数形参:网络设备信息 struct net_device
函数返回值:执行成功返回 0。



注册网络设备示例
static struct net_device_ops netdev_ops_test= //网络设备虚拟文件操作集合
{
 .ndo_open = test_ndo_open,
.ndo_stop = test_ndo_stop,
.ndo_start_xmit = ndo_start_xmit,
};
net = alloc_etherdev(sizeof(*net));
//网络设备的名称,使用 ifconfig -a 可以查看到。
strcpy(net->name, "eth888"); 
net->netdev_ops=&netdev_ops_test; //虚拟文件操作集合


注销网络设备
void unregister_netdev(struct net_device *dev)
功能:注销网络设备
参数:注销的网络设备结构体

当前用来测试的网卡选用ENC28J60 ,这是带有行业标准串行外设接口(Serial Peripheral Interface,SPI)的独立以太网 控制器。

可作为任何配备有 SPI 的控制器的以太网接口。ENC28J60 符合 IEEE 802.3 的全部规范,采用了一系列包过滤机制以对传入数据包进行限制。 它还提供了一个内部 DMA 模块, 以实现快速数据吞吐和硬件支持的 IP 校验和计算。 与主控制器的通信通过两个中断引脚和 SPI 实现,数据传输速率高达 10 Mb/s。两个专用的引脚用 于连接 LED,进行网络活动状态指示。

与开发板的硬件连接:

二、驱动代码

2.1 ENC28J60网卡驱动+网络设备框架+中断接收数据.c

代码语言:javascript
复制
#include <linux/init.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/delay.h>
#include "enc28j60.h"
#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/timer.h>

/*
以下是ENC28J60驱动移植接口:
SPI0接口:
	GPB_0--SCK
	GPB_1--CS
	GPB_2--MISO
	GPB_3--MOSI
    GPB(4)--复位
	GPX3(2)--中断
*/
static u32 ENC28J60_IRQ; //中断编号

/*SPI底层硬件IO定义*/
#define Tiny4412_GPIO_SPI_SCK EXYNOS4_GPB(0)
#define Tiny4412_GPIO_SPI_CS EXYNOS4_GPB(1)
#define Tiny4412_GPIO_SPI_MISO EXYNOS4_GPB(2)
#define Tiny4412_GPIO_SPI_MOSI EXYNOS4_GPB(3)
#define ENC28J60_GPIO_REST EXYNOS4_GPB(4)
#define ENC28J60_IRQ_NUMBER EXYNOS4_GPX3(2)

#define ENC28J60_CS(x)	if(x){gpio_set_value(Tiny4412_GPIO_SPI_CS,1);}else{gpio_set_value(Tiny4412_GPIO_SPI_CS,0);}	//ENC28J60片选信号
#define ENC28J60_RST(x)	if(x){gpio_set_value(ENC28J60_GPIO_REST,1);}else{gpio_set_value(ENC28J60_GPIO_REST,0);}	//ENC28J60复位信号
#define ENC28J60_MOSI(x) if(x){gpio_set_value(Tiny4412_GPIO_SPI_MOSI,1);}else{gpio_set_value(Tiny4412_GPIO_SPI_MOSI,0);}  //输出
#define ENC28J60_MISO (gpio_get_value(Tiny4412_GPIO_SPI_MISO))    //输入
#define ENC28J60_SCLK(x) if(x){gpio_set_value(Tiny4412_GPIO_SPI_SCK,1);}else{gpio_set_value(Tiny4412_GPIO_SPI_SCK,0);}  //时钟线

static u8 ENC28J60BANK;
static u32 NextPacketPtr;
static struct timer_list timer_date;
//网卡MAC地址,必须唯一
u8 ENC28J60_MacAddr[6]={0x04,0x02,0x35,0x00,0x00,0x01};	//MAC地址
static struct net_device *tiny4412_net=NULL;  //网络设备指针结构


/*
函数功能:底层SPI接口收发一个字节
说    明:模拟SPI时序,ENC28J60时钟线空闲电平为低电平,在第一个下降沿采集数据
*/
u8 ENC28J60_SPI_ReadWriteOneByte(u8 tx_data)
{
   u8 rx_data=0;				 
   u8 i;
   for(i=0;i<8;i++)
	{
		if(tx_data&0x80){ENC28J60_MOSI(1);}
		else {ENC28J60_MOSI(0);}
		tx_data<<=1;	
		{ENC28J60_SCLK(1); } 
		rx_data<<=1;
		if(ENC28J60_MISO)rx_data|=0x01;
		{ENC28J60_SCLK(0);}//第一个下降沿采集数据
	}
	return rx_data;		
}


/*
函数功能:复位ENC28J60,包括SPI初始化/IO初始化等
MISO--->PA6----主机输入
MOSI--->PA7----主机输出
SCLK--->PA5----时钟信号
CS----->PA4----片选
RESET-->PG15---复位
*/
void ENC28J60_Reset(void)
{
	/*释放GPIO*/
	gpio_free(Tiny4412_GPIO_SPI_SCK);	
	gpio_free(Tiny4412_GPIO_SPI_CS);
	gpio_free(Tiny4412_GPIO_SPI_MISO);	
	gpio_free(Tiny4412_GPIO_SPI_MOSI);
	gpio_free(ENC28J60_GPIO_REST);
	
	/*1. 配置GPIO模式*/
	printk("%d\n",gpio_request(Tiny4412_GPIO_SPI_SCK, "Tiny4412_Tiny4412_SPI_SCK"));
	printk("%d\n",gpio_request(Tiny4412_GPIO_SPI_CS, "Tiny4412_Tiny4412_SPI_CS"));
	printk("%d\n",gpio_request(Tiny4412_GPIO_SPI_MISO, "Tiny4412_Tiny4412_SPI_MISO"));
	printk("%d\n",gpio_request(Tiny4412_GPIO_SPI_MOSI, "Tiny4412_Tiny4412_SPI_MOSI"));
	printk("%d\n",gpio_request(ENC28J60_GPIO_REST, "Tiny4412_Tiny4412_SPI_REST"));

	printk("%d\n",s3c_gpio_cfgpin(Tiny4412_GPIO_SPI_SCK, S3C_GPIO_OUTPUT));
	printk("%d\n",s3c_gpio_cfgpin(Tiny4412_GPIO_SPI_CS, S3C_GPIO_OUTPUT));
	printk("%d\n",s3c_gpio_cfgpin(Tiny4412_GPIO_SPI_MISO, S3C_GPIO_INPUT));
	printk("%d\n",s3c_gpio_cfgpin(Tiny4412_GPIO_SPI_MOSI, S3C_GPIO_OUTPUT));
	printk("%d\n",s3c_gpio_cfgpin(ENC28J60_GPIO_REST, S3C_GPIO_OUTPUT));

	ENC28J60_RST(0);			//复位ENC28J60
	mdelay(10);	 
	ENC28J60_RST(1);			//复位结束				    
	mdelay(10);	 
}

/*
函数功能:读取ENC28J60寄存器(带操作码) 
参	  数:
 		 op:操作码
		 addr:寄存器地址/参数
返 回 值:读到的数据
*/
u8 ENC28J60_Read_Op(u8 op,u8 addr)
{
	u8 dat=0;	 
	ENC28J60_CS(0);	 
	dat=op|(addr&ADDR_MASK);
	ENC28J60_SPI_ReadWriteOneByte(dat);
	dat=ENC28J60_SPI_ReadWriteOneByte(0xFF);
	//如果是读取MAC/MII寄存器,则第二次读到的数据才是正确的,见手册29页
 	if(addr&0x80)dat=ENC28J60_SPI_ReadWriteOneByte(0xFF);
	ENC28J60_CS(1);
	return dat;
}

/*
函数功能:读取ENC28J60寄存器(带操作码) 
参    数:
		op:操作码
		addr:寄存器地址
		data:参数
*/
void ENC28J60_Write_Op(u8 op,u8 addr,u8 data)
{
	u8 dat = 0;	    
	ENC28J60_CS(0);			   
	dat=op|(addr&ADDR_MASK);
	ENC28J60_SPI_ReadWriteOneByte(dat);	  
	ENC28J60_SPI_ReadWriteOneByte(data);
	ENC28J60_CS(1);
}



/*
函数功能:读取ENC28J60接收缓存数据
参    数:
		len:要读取的数据长度
		data:输出数据缓存区(末尾自动添加结束符)
*/
void ENC28J60_Read_Buf(u32 len,u8* data)
{
	ENC28J60_CS(0);			 
	ENC28J60_SPI_ReadWriteOneByte(ENC28J60_READ_BUF_MEM);
	while(len)
	{
		len--;			  
		*data=(u8)ENC28J60_SPI_ReadWriteOneByte(0);
		data++;
	}
	*data='\0';
	ENC28J60_CS(1);
}


/*
函数功能:向ENC28J60写发送缓存数据
参    数:
		len:要写入的数据长度
		data:数据缓存区
*/
void ENC28J60_Write_Buf(u32 len,u8* data)
{
	ENC28J60_CS(0);			   
	ENC28J60_SPI_ReadWriteOneByte(ENC28J60_WRITE_BUF_MEM);		 
	while(len)
	{
		len--;
		ENC28J60_SPI_ReadWriteOneByte(*data);
		data++;
	}
	ENC28J60_CS(1);
}

/*
函数功能:设置ENC28J60寄存器Bank
参    数:
				ban:要设置的bank
*/
void ENC28J60_Set_Bank(u8 bank)
{								    
	if((bank&BANK_MASK)!=ENC28J60BANK)//和当前bank不一致的时候,才设置
	{				  
		ENC28J60_Write_Op(ENC28J60_BIT_FIELD_CLR,ECON1,(ECON1_BSEL1|ECON1_BSEL0));
		ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,ECON1,(bank&BANK_MASK)>>5);
		ENC28J60BANK=(bank&BANK_MASK);
	}
}


/*
函数功能:读取ENC28J60指定寄存器
参    数:addr:寄存器地址
返 回 值:读到的数据
*/
u8 ENC28J60_Read(u8 addr)
{						  
	ENC28J60_Set_Bank(addr);//设置BANK		 
	return ENC28J60_Read_Op(ENC28J60_READ_CTRL_REG,addr);
}


/*
函数功能:向ENC28J60指定寄存器写数据
参    数:
		addr:寄存器地址
		data:要写入的数据		
*/
void ENC28J60_Write(u8 addr,u8 data)
{					  
	ENC28J60_Set_Bank(addr);		 
	ENC28J60_Write_Op(ENC28J60_WRITE_CTRL_REG,addr,data);
}


/*
函数功能:向ENC28J60的PHY寄存器写入数据
参    数:
		addr:寄存器地址
		data:要写入的数据	
*/
void ENC28J60_PHY_Write(u8 addr,u32 data)
{
	u16 retry=0;
	ENC28J60_Write(MIREGADR,addr);	//设置PHY寄存器地址
	ENC28J60_Write(MIWRL,data);		//写入数据
	ENC28J60_Write(MIWRH,data>>8);		   
	while((ENC28J60_Read(MISTAT)&MISTAT_BUSY)&&retry<0XFFF)retry++;//等待写入PHY结束		  
}


/*
函数功能:初始化ENC28J60
参    数:macaddr:MAC地址
返 回 值:	 
		0,初始化成功;
        1,初始化失败;
*/
u8 ENC28J60_Init(u8* macaddr)
{		
	u16 retry=0;		  
	ENC28J60_Reset(); //复位底层引脚接口
	ENC28J60_Write_Op(ENC28J60_SOFT_RESET,0,ENC28J60_SOFT_RESET);//软件复位
	while(!(ENC28J60_Read(ESTAT)&ESTAT_CLKRDY)&&retry<500)//等待时钟稳定
	{
		retry++;
		mdelay(1);
	};
	if(retry>=500)return 1;//ENC28J60初始化失败
	// do bank 0 stuff
	// initialize receive buffer
	// 16-bit transfers,must write low byte first
	// set receive buffer start address	   设置接收缓冲区地址  8K字节容量
	NextPacketPtr=RXSTART_INIT;
	// Rx start
	//接收缓冲器由一个硬件管理的循环FIFO 缓冲器构成。
	//寄存器对ERXSTH:ERXSTL 和ERXNDH:ERXNDL 作
	//为指针,定义缓冲器的容量和其在存储器中的位置。
	//ERXST和ERXND指向的字节均包含在FIFO缓冲器内。
	//当从以太网接口接收数据字节时,这些字节被顺序写入
	//接收缓冲器。 但是当写入由ERXND 指向的存储单元
	//后,硬件会自动将接收的下一字节写入由ERXST 指向
	//的存储单元。 因此接收硬件将不会写入FIFO 以外的单
	//元。
	//设置接收起始字节
	ENC28J60_Write(ERXSTL,RXSTART_INIT&0xFF);	
	ENC28J60_Write(ERXSTH,RXSTART_INIT>>8);	  
	//ERXWRPTH:ERXWRPTL 寄存器定义硬件向FIFO 中
	//的哪个位置写入其接收到的字节。 指针是只读的,在成
	//功接收到一个数据包后,硬件会自动更新指针。 指针可
	//用于判断FIFO 内剩余空间的大小  8K-1500。 
	//设置接收读指针字节
	ENC28J60_Write(ERXRDPTL,RXSTART_INIT&0xFF);
	ENC28J60_Write(ERXRDPTH,RXSTART_INIT>>8);
	//设置接收结束字节
	ENC28J60_Write(ERXNDL,RXSTOP_INIT&0xFF);
	ENC28J60_Write(ERXNDH,RXSTOP_INIT>>8);
	//设置发送起始字节
	ENC28J60_Write(ETXSTL,TXSTART_INIT&0xFF);
	ENC28J60_Write(ETXSTH,TXSTART_INIT>>8);
	//设置发送结束字节
	ENC28J60_Write(ETXNDL,TXSTOP_INIT&0xFF);
	ENC28J60_Write(ETXNDH,TXSTOP_INIT>>8);
	// do bank 1 stuff,packet filter:
	// For broadcast packets we allow only ARP packtets
	// All other packets should be unicast only for our mac (MAADR)
	//
	// The pattern to match on is therefore
	// Type     ETH.DST
	// ARP      BROADCAST
	// 06 08 -- ff ff ff ff ff ff -> ip checksum for theses bytes=f7f9
	// in binary these poitions are:11 0000 0011 1111
	// This is hex 303F->EPMM0=0x3f,EPMM1=0x30
	//接收过滤器
	//UCEN:单播过滤器使能位
	//当ANDOR = 1 时://1 = 目标地址与本地MAC 地址不匹配的数据包将被丢弃
	//0 = 禁止过滤器
	//当ANDOR = 0 时://1 = 目标地址与本地MAC 地址匹配的数据包会被接受
	//0 = 禁止过滤器
	//CRCEN:后过滤器CRC 校验使能位//1 = 所有CRC 无效的数据包都将被丢弃
	//0 = 不考虑CRC 是否有效
	//PMEN:格式匹配过滤器使能位
	//当ANDOR = 1 时:	//1 = 数据包必须符合格式匹配条件,否则将被丢弃
	//0 = 禁止过滤器
	//当ANDOR = 0 时:	//1 = 符合格式匹配条件的数据包将被接受
	//0 = 禁止过滤器
	ENC28J60_Write(ERXFCON,ERXFCON_UCEN|ERXFCON_CRCEN|ERXFCON_PMEN);
	ENC28J60_Write(EPMM0,0x3f);
	ENC28J60_Write(EPMM1,0x30);
	ENC28J60_Write(EPMCSL,0xf9);
	ENC28J60_Write(EPMCSH,0xf7);
	// do bank 2 stuff
	// enable MAC receive
	//bit 0 MARXEN:MAC 接收使能位	//1 = 允许MAC 接收数据包
	//0 = 禁止数据包接收
	//bit 3 TXPAUS:暂停控制帧发送使能位	//1 = 允许MAC 发送暂停控制帧(用于全双工模式下的流量控制)
	//0 = 禁止暂停帧发送
	//bit 2 RXPAUS:暂停控制帧接收使能位	//1 = 当接收到暂停控制帧时,禁止发送(正常操作)
	//0 = 忽略接收到的暂停控制帧
	ENC28J60_Write(MACON1,MACON1_MARXEN|MACON1_TXPAUS|MACON1_RXPAUS);
	// bring MAC out of reset
	//将MACON2 中的MARST 位清零,使MAC 退出复位状态。
	ENC28J60_Write(MACON2,0x00);
	// enable automatic padding to 60bytes and CRC operations
	//bit 7-5 PADCFG2:PACDFG0:自动填充和CRC 配置位
	//111 = 用0 填充所有短帧至64 字节长,并追加一个有效的CRC
	//110 = 不自动填充短帧
	//101 = MAC 自动检测具有8100h 类型字段的VLAN 协议帧,并自动填充到64 字节长。如果不
	//是VLAN 帧,则填充至60 字节长。填充后还要追加一个有效的CRC
	//100 = 不自动填充短帧
	//011 = 用0 填充所有短帧至64 字节长,并追加一个有效的CRC
	//010 = 不自动填充短帧
	//001 = 用0 填充所有短帧至60 字节长,并追加一个有效的CRC
	//000 = 不自动填充短帧
	//bit 4 TXCRCEN:发送CRC 使能位	//1= 不管PADCFG如何,MAC都会在发送帧的末尾追加一个有效的CRC。 如果PADCFG规定要
	//追加有效的CRC,则必须将TXCRCEN 置1。
	//0 = MAC不会追加CRC。 检查最后4 个字节,如果不是有效的CRC 则报告给发送状态向量。
	//bit 0 FULDPX:MAC 全双工使能位	//1 = MAC工作在全双工模式下。 PHCON1.PDPXMD 位必须置1。
	//0 = MAC工作在半双工模式下。 PHCON1.PDPXMD 位必须清零。
	ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,MACON3,MACON3_PADCFG0|MACON3_TXCRCEN|MACON3_FRMLNEN|MACON3_FULDPX);
	// set inter-frame gap (non-back-to-back)
	//配置非背对背包间间隔寄存器的低字节
	//MAIPGL。 大多数应用使用12h 编程该寄存器。
	//如果使用半双工模式,应编程非背对背包间间隔
	//寄存器的高字节MAIPGH。 大多数应用使用0Ch
	//编程该寄存器。
	ENC28J60_Write(MAIPGL,0x12);
	ENC28J60_Write(MAIPGH,0x0C);
	// set inter-frame gap (back-to-back)
	//配置背对背包间间隔寄存器MABBIPG。当使用
	//全双工模式时,大多数应用使用15h 编程该寄存
	//器,而使用半双工模式时则使用12h 进行编程。
	ENC28J60_Write(MABBIPG,0x15);
	// Set the maximum packet size which the controller will accept
	// Do not send packets longer than MAX_FRAMELEN:
	// 最大帧长度  1500
	ENC28J60_Write(MAMXFLL,MAX_FRAMELEN&0xFF);	
	ENC28J60_Write(MAMXFLH,MAX_FRAMELEN>>8);
	// do bank 3 stuff
	// write MAC address
	// NOTE: MAC address in ENC28J60 is byte-backward
	//设置MAC地址
	ENC28J60_Write(MAADR5,macaddr[0]);	
	ENC28J60_Write(MAADR4,macaddr[1]);
	ENC28J60_Write(MAADR3,macaddr[2]);
	ENC28J60_Write(MAADR2,macaddr[3]);
	ENC28J60_Write(MAADR1,macaddr[4]);
	ENC28J60_Write(MAADR0,macaddr[5]);
	//配置PHY为全双工  LEDB为拉电流
	ENC28J60_PHY_Write(PHCON1,PHCON1_PDPXMD);	 
	// no loopback of transmitted frames	 禁止环回
	//HDLDIS:PHY 半双工环回禁止位
	//当PHCON1.PDPXMD = 1 或PHCON1.PLOOPBK = 1 时:
	//此位可被忽略。
	//当PHCON1.PDPXMD = 0 且PHCON1.PLOOPBK = 0 时:	//1 = 要发送的数据仅通过双绞线接口发出
	//0 = 要发送的数据会环回到MAC 并通过双绞线接口发出
	ENC28J60_PHY_Write(PHCON2,PHCON2_HDLDIS);
	// switch to bank 0
	//ECON1 寄存器
	//寄存器3-1 所示为ECON1 寄存器,它用于控制
	//ENC28J60 的主要功能。 ECON1 中包含接收使能、发
	//送请求、DMA 控制和存储区选择位。	   
	ENC28J60_Set_Bank(ECON1);
	// enable interrutps
	//EIE: 以太网中断允许寄存器
	//bit 7 INTIE: 全局INT 中断允许位	//1 = 允许中断事件驱动INT 引脚
	//0 = 禁止所有INT 引脚的活动(引脚始终被驱动为高电平)
	//bit 6 PKTIE: 接收数据包待处理中断允许位	//1 = 允许接收数据包待处理中断
	//0 = 禁止接收数据包待处理中断
	ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,EIE,EIE_INTIE|EIE_PKTIE);
	// enable packet reception
	//bit 2 RXEN:接收使能位	//1 = 通过当前过滤器的数据包将被写入接收缓冲器
	//0 = 忽略所有接收的数据包
	ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,ECON1,ECON1_RXEN);
	if(ENC28J60_Read(MAADR5)== macaddr[0])return 0;//初始化成功
	else return 1; 	  

}

/*
函数功能:读取EREVID
*/
u8 ENC28J60_Get_EREVID(void)
{
	//在EREVID 内也存储了版本信息。 EREVID 是一个只读控
	//制寄存器,包含一个5 位标识符,用来标识器件特定硅片
	//的版本号
	return ENC28J60_Read(EREVID);
}

/*
函数功能:通过ENC28J60发送数据包到网络
参    数:
		len   :数据包大小
        packet:数据包
*/
void ENC28J60_Packet_Send(u32 len,u8* packet)
{
	//设置发送缓冲区地址写指针入口
	ENC28J60_Write(EWRPTL,TXSTART_INIT&0xFF);
	ENC28J60_Write(EWRPTH,TXSTART_INIT>>8);
	//设置TXND指针,以对应给定的数据包大小	   
	ENC28J60_Write(ETXNDL,(TXSTART_INIT+len)&0xFF);
	ENC28J60_Write(ETXNDH,(TXSTART_INIT+len)>>8);
	//写每包控制字节(0x00表示使用macon3的设置) 
	ENC28J60_Write_Op(ENC28J60_WRITE_BUF_MEM,0,0x00);
	//复制数据包到发送缓冲区
	//printf("len:%d\r\n",len);	//监视发送数据长度
 	ENC28J60_Write_Buf(len,packet);
 	//发送数据到网络
	ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,ECON1,ECON1_TXRTS);
	//复位发送逻辑的问题。参见Rev. B4 Silicon Errata point 12.
	if((ENC28J60_Read(EIR)&EIR_TXERIF))ENC28J60_Write_Op(ENC28J60_BIT_FIELD_CLR,ECON1,ECON1_TXRTS);
}

/*
函数功能:从网络获取一个数据包内容
函数参数:
		maxlen:数据包最大允许接收长度
		packet:数据包缓存区
返 回 值:收到的数据包长度(字节)	
*/
u32 ENC28J60_Packet_Receive(u32 maxlen,u8* packet)
{
	u32 rxstat;
	u32 len;    													 
	if(ENC28J60_Read(EPKTCNT)==0)return 0;  //是否收到数据包?	   
	//设置接收缓冲器读指针
	ENC28J60_Write(ERDPTL,(NextPacketPtr));
	ENC28J60_Write(ERDPTH,(NextPacketPtr)>>8);	   
	// 读下一个包的指针
	NextPacketPtr=ENC28J60_Read_Op(ENC28J60_READ_BUF_MEM,0);
	NextPacketPtr|=ENC28J60_Read_Op(ENC28J60_READ_BUF_MEM,0)<<8;
	//读包的长度
	len=ENC28J60_Read_Op(ENC28J60_READ_BUF_MEM,0);
	len|=ENC28J60_Read_Op(ENC28J60_READ_BUF_MEM,0)<<8;
 	len-=4; //去掉CRC计数
	//读取接收状态
	rxstat=ENC28J60_Read_Op(ENC28J60_READ_BUF_MEM,0);
	rxstat|=ENC28J60_Read_Op(ENC28J60_READ_BUF_MEM,0)<<8;
	//限制接收长度	
	if (len>maxlen-1)len=maxlen-1;	
	//检查CRC和符号错误
	// ERXFCON.CRCEN为默认设置,一般我们不需要检查.
	if((rxstat&0x80)==0)len=0;//无效
	else ENC28J60_Read_Buf(len,packet);//从接收缓冲器中复制数据包	    
	//RX读指针移动到下一个接收到的数据包的开始位置 
	//并释放我们刚才读出过的内存
	ENC28J60_Write(ERXRDPTL,(NextPacketPtr));
	ENC28J60_Write(ERXRDPTH,(NextPacketPtr)>>8);
	//递减数据包计数器标志我们已经得到了这个包 
 	ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,ECON2,ECON2_PKTDEC);
	return(len);
}

/*--------------------------工作队列、定时器、中断服务函数---------------------------------------*/

static struct work_struct work_list;

/*
工作队列处理函数
以下函数用于读取网卡里的数据。
读取完毕之后,再通过netif_rx()函数上报到应用层
*/

u8 Enc28j60_Rx_Buff[1518];
static void workqueue_function(struct work_struct *work)
{
	int length;
	length=ENC28J60_Packet_Receive(1518,Enc28j60_Rx_Buff); //接收ENC28J60的数据
	if(length<=0)
	{
		return;
	}

	/*2. 分配新的套接字缓冲区*/
    struct sk_buff *skb = dev_alloc_skb(length+NET_IP_ALIGN);
    skb_reserve(skb, NET_IP_ALIGN); //对齐
    skb->dev = tiny4412_net;

    /* 读取硬件上接收到的数据 */
	// skb_put(skb, length)//存放网卡里读取数据的缓冲区地址
	memcpy(skb_put(skb, length),Enc28j60_Rx_Buff,length);
	
    /* 获取上层协议类型 */
    skb->protocol = eth_type_trans(skb,tiny4412_net);

    /* 把数据包交给上层 */
    netif_rx(skb);

    /* 记录接收时间戳 */
    tiny4412_net->last_rx = jiffies;
	
	//printk("工作队列处理函数调用成功!\n");
}


/*
函数功能:  中断服务函数
*/
irqreturn_t ENC28J60_irq_handler(int irq, void *dev)
{
	 /*共享工作队列调度*/
	 //printk("进入到中断服务函数!\n");
	 /*使用的ENC28J60网卡中断不好使,程序就使用定时器轮询接收了*/
	 schedule_work(&work_list);
	 return IRQ_HANDLED;
}

static void timer_function(unsigned long data)
{
	/*共享工作队列调度*/
	 schedule_work(&work_list);
	/*修改定时器超时*/
	mod_timer(&timer_date,jiffies+usecs_to_jiffies(100));		
}


/*----------------------------网络设备相关代码--------------------------------------*/

/*1. 设备初始化调用,该函数在注册成功后会调用一次,可以编写网卡初始化相关代码*/
static int tiny4412_ndo_init(struct net_device * dev)
{
	/*1. ENC28J60网卡初始化*/
	u8 stat=ENC28J60_Init(ENC28J60_MacAddr);
	if(stat)
	{
		 printk("ENC28J60网卡初始化失败!\r\n");
	}
	/*2. 获取中断编号*/
	ENC28J60_IRQ=gpio_to_irq(ENC28J60_IRQ_NUMBER);
	printk("ENC28J60_IRQ=%d\n",ENC28J60_IRQ);
	
	/*3. 初始化工作队列*/
	INIT_WORK(&work_list,workqueue_function);

	/*4. 注册中断*/
	if(request_irq(ENC28J60_IRQ,ENC28J60_irq_handler,IRQ_TYPE_EDGE_FALLING,"ENC28J60_NET",NULL)!=0)
	{
		printk("ENC28J60中断注册失败!\n");
	}

	 /*使用定时器100ms*/
	 timer_date.expires=jiffies+usecs_to_jiffies(100);
	 timer_date.function=timer_function;
	 
	 /*5. 初始化定时器*/
	 init_timer(&timer_date);

	 /*6. 添加定时器到内核并启动*/
	 add_timer(&timer_date);
	 
	printk("网络设备初始化!\n");
	return 0;
}

/*2. 打开网络接口,对应ifconfig up命令,编写网络设备硬件初始化的相关代码*/
static int	tiny4412_ndo_open(struct net_device *dev)
{
	printk("网络设备打开成功!\n");
	return 0;
}

/*3. 关闭网络设备,对应ifconfig down命令,实现的内容与OPEN相反*/
static int	tiny4412_ndo_stop(struct net_device *dev)
{
	printk("网络设备关闭成功!\n");
	return 0;
}

/*4. 启动网络数据包传输的方法*/
static netdev_tx_t	tiny4412_ndo_start_xmit(struct sk_buff *skb,struct net_device *dev)
{
	int len;
	char *data, shortpkt[ETH_ZLEN];
    /* 获得有效数据指针和长度 */
    data = skb->data;
    len = skb->len;
    if(len < ETH_ZLEN)
	{
        /* 如果帧长小于以太网帧最小长度,补0 */
        memset(shortpkt,0,ETH_ZLEN);
        memcpy(shortpkt,skb->data,skb->len);
        len = ETH_ZLEN;
	    data = shortpkt;
    }
    dev->trans_start = jiffies;     //记录发送时间戳
    
    /* 设置硬件寄存器让硬件将数据发出去 */
    ENC28J60_Packet_Send(len,data);
	return NETDEV_TX_OK; //这是个枚举状态。
}


/*5. 设置MAC地址,对应的命令: ifconfig eth888 hw ether 00:AA:BB:CC:DD:EE  */
static int tiny4412_set_mac_address(struct net_device *dev, void *addr)
{
	struct sockaddr *address = addr;
	memcpy(dev->dev_addr, address->sa_data, dev->addr_len);
	printk("修改的MAC地址如下:\n");
	printk("%X-%X-%X-%X-%X-%X\n",
		tiny4412_net->dev_addr[0],
		tiny4412_net->dev_addr[1],
		tiny4412_net->dev_addr[2],
		tiny4412_net->dev_addr[3],
		tiny4412_net->dev_addr[4],
		tiny4412_net->dev_addr[5]);
	//设置MAC地址
	ENC28J60_Write(MAADR5,tiny4412_net->dev_addr[0]);	
	ENC28J60_Write(MAADR4,tiny4412_net->dev_addr[1]);
	ENC28J60_Write(MAADR3,tiny4412_net->dev_addr[2]);
	ENC28J60_Write(MAADR2,tiny4412_net->dev_addr[3]);
	ENC28J60_Write(MAADR1,tiny4412_net->dev_addr[4]);
	ENC28J60_Write(MAADR0,tiny4412_net->dev_addr[5]);
	return 0;
}

/*网络设备虚拟文件操作集合*/
static  struct net_device_ops netdev_ops_test= 
{
	.ndo_open			= tiny4412_ndo_open,
	.ndo_stop			= tiny4412_ndo_stop,
	.ndo_start_xmit 	= tiny4412_ndo_start_xmit,
	.ndo_init       	= tiny4412_ndo_init,
	.ndo_set_mac_address= tiny4412_set_mac_address,
};

/*--------------------------驱动框架------------------------------------*/
static int __init Net_test_init(void)
{
	/*1. 分配及初始化net_device对象,参数:私有数据大小(单位:字节数)*/
	tiny4412_net=alloc_etherdev(sizeof(struct net_device));

	/*2. net结构体赋值*/
  	strcpy(tiny4412_net->name, "eth888");//网络设备的名称,使用ifconfig -a可以查看到。
  	tiny4412_net->netdev_ops=&netdev_ops_test; //虚拟文件操作集合
	tiny4412_net->if_port = IF_PORT_10BASET; 	 //协议规范
	tiny4412_net->watchdog_timeo = 4 * HZ;     //看门狗超时时间
	
  	/*3. 随机生成MAC地址*/
	eth_hw_addr_random(tiny4412_net);
    printk("随机生成的MAC地址如下:\n");
	printk("%X-%X-%X-%X-%X-%X\n",
		tiny4412_net->dev_addr[0],
		tiny4412_net->dev_addr[1],
		tiny4412_net->dev_addr[2],
		tiny4412_net->dev_addr[3],
		tiny4412_net->dev_addr[4],
		tiny4412_net->dev_addr[5]);
	ENC28J60_MacAddr[0]=tiny4412_net->dev_addr[0];
	ENC28J60_MacAddr[1]=tiny4412_net->dev_addr[1];
	ENC28J60_MacAddr[2]=tiny4412_net->dev_addr[2];
	ENC28J60_MacAddr[3]=tiny4412_net->dev_addr[3];
	ENC28J60_MacAddr[4]=tiny4412_net->dev_addr[4];
	ENC28J60_MacAddr[5]=tiny4412_net->dev_addr[5];
	
	/*注册网络设备*/
	register_netdev(tiny4412_net);
	
	printk("网络设备注册成功!\n");	
	return 0;
}


static void __exit Net_test_exit(void)
{	
	//注销网络设备
	unregister_netdev(tiny4412_net);
	
	/*1. 释放GPIO口使用权*/
	gpio_free(Tiny4412_GPIO_SPI_SCK);	
	gpio_free(Tiny4412_GPIO_SPI_CS);
	gpio_free(Tiny4412_GPIO_SPI_MISO);
	gpio_free(Tiny4412_GPIO_SPI_MOSI);
	gpio_free(ENC28J60_GPIO_REST);	

	/*2. 释放中断号*/
	free_irq(ENC28J60_IRQ,NULL);

	/*3. 停止定时器*/
	del_timer_sync(&timer_date);
		
	/*4. 清除工作*/
	cancel_work_sync(&work_list);
	
	printk("网络设备注销成功!\n");	
}

module_init(Net_test_init);
module_exit(Net_test_exit);
MODULE_AUTHOR("xiaolong");
MODULE_LICENSE("GPL");

2.2  对应enc28j60.h代码

代码语言:javascript
复制
#ifndef __ENC28J60_H
#define __ENC28J60_H	  


// ENC28J60 Control Registers
// Control register definitions are a combination of address,
// bank number, and Ethernet/MAC/PHY indicator bits.
// - Register address         (bits 0-4)
// - Bank number              (bits 5-6)
// - MAC/PHY indicator        (bit 7)
#define ADDR_MASK        0x1F
#define BANK_MASK        0x60
#define SPRD_MASK        0x80
// All-bank registers
#define EIE              0x1B
#define EIR              0x1C
#define ESTAT            0x1D
#define ECON2            0x1E
#define ECON1            0x1F
// Bank 0 registers
#define ERDPTL           (0x00|0x00)
#define ERDPTH           (0x01|0x00)
#define EWRPTL           (0x02|0x00)
#define EWRPTH           (0x03|0x00)
#define ETXSTL           (0x04|0x00)
#define ETXSTH           (0x05|0x00)
#define ETXNDL           (0x06|0x00)
#define ETXNDH           (0x07|0x00)
#define ERXSTL           (0x08|0x00)
#define ERXSTH           (0x09|0x00)
#define ERXNDL           (0x0A|0x00)
#define ERXNDH           (0x0B|0x00)
//ERXWRPTH:ERXWRPTL 寄存器定义硬件向FIFO 中
//的哪个位置写入其接收到的字节。 指针是只读的,在成
//功接收到一个数据包后,硬件会自动更新指针。 指针可
//用于判断FIFO 内剩余空间的大小。
#define ERXRDPTL         (0x0C|0x00)
#define ERXRDPTH         (0x0D|0x00)
#define ERXWRPTL         (0x0E|0x00)
#define ERXWRPTH         (0x0F|0x00)
#define EDMASTL          (0x10|0x00)
#define EDMASTH          (0x11|0x00)
#define EDMANDL          (0x12|0x00)
#define EDMANDH          (0x13|0x00)
#define EDMADSTL         (0x14|0x00)
#define EDMADSTH         (0x15|0x00)
#define EDMACSL          (0x16|0x00)
#define EDMACSH          (0x17|0x00)
// Bank 1 registers
#define EHT0             (0x00|0x20)
#define EHT1             (0x01|0x20)
#define EHT2             (0x02|0x20)
#define EHT3             (0x03|0x20)
#define EHT4             (0x04|0x20)
#define EHT5             (0x05|0x20)
#define EHT6             (0x06|0x20)
#define EHT7             (0x07|0x20)
#define EPMM0            (0x08|0x20)
#define EPMM1            (0x09|0x20)
#define EPMM2            (0x0A|0x20)
#define EPMM3            (0x0B|0x20)
#define EPMM4            (0x0C|0x20)
#define EPMM5            (0x0D|0x20)
#define EPMM6            (0x0E|0x20)
#define EPMM7            (0x0F|0x20)
#define EPMCSL           (0x10|0x20)
#define EPMCSH           (0x11|0x20)
#define EPMOL            (0x14|0x20)
#define EPMOH            (0x15|0x20)
#define EWOLIE           (0x16|0x20)
#define EWOLIR           (0x17|0x20)
#define ERXFCON          (0x18|0x20)
#define EPKTCNT          (0x19|0x20)
// Bank 2 registers
#define MACON1           (0x00|0x40|0x80)
#define MACON2           (0x01|0x40|0x80)
#define MACON3           (0x02|0x40|0x80)
#define MACON4           (0x03|0x40|0x80)
#define MABBIPG          (0x04|0x40|0x80)
#define MAIPGL           (0x06|0x40|0x80)
#define MAIPGH           (0x07|0x40|0x80)
#define MACLCON1         (0x08|0x40|0x80)
#define MACLCON2         (0x09|0x40|0x80)
#define MAMXFLL          (0x0A|0x40|0x80)
#define MAMXFLH          (0x0B|0x40|0x80)
#define MAPHSUP          (0x0D|0x40|0x80)
#define MICON            (0x11|0x40|0x80)
#define MICMD            (0x12|0x40|0x80)
#define MIREGADR         (0x14|0x40|0x80)
#define MIWRL            (0x16|0x40|0x80)
#define MIWRH            (0x17|0x40|0x80)
#define MIRDL            (0x18|0x40|0x80)
#define MIRDH            (0x19|0x40|0x80)
// Bank 3 registers
#define MAADR1           (0x00|0x60|0x80)
#define MAADR0           (0x01|0x60|0x80)
#define MAADR3           (0x02|0x60|0x80)
#define MAADR2           (0x03|0x60|0x80)
#define MAADR5           (0x04|0x60|0x80)
#define MAADR4           (0x05|0x60|0x80)
#define EBSTSD           (0x06|0x60)
#define EBSTCON          (0x07|0x60)
#define EBSTCSL          (0x08|0x60)
#define EBSTCSH          (0x09|0x60)
#define MISTAT           (0x0A|0x60|0x80)
#define EREVID           (0x12|0x60)
#define ECOCON           (0x15|0x60)
#define EFLOCON          (0x17|0x60)
#define EPAUSL           (0x18|0x60)
#define EPAUSH           (0x19|0x60)
// PHY registers
#define PHCON1           0x00
#define PHSTAT1          0x01
#define PHHID1           0x02
#define PHHID2           0x03
#define PHCON2           0x10
#define PHSTAT2          0x11
#define PHIE             0x12
#define PHIR             0x13
#define PHLCON           0x14	   
// ENC28J60 ERXFCON Register Bit Definitions
#define ERXFCON_UCEN     0x80
#define ERXFCON_ANDOR    0x40
#define ERXFCON_CRCEN    0x20
#define ERXFCON_PMEN     0x10
#define ERXFCON_MPEN     0x08
#define ERXFCON_HTEN     0x04
#define ERXFCON_MCEN     0x02
#define ERXFCON_BCEN     0x01
// ENC28J60 EIE Register Bit Definitions
#define EIE_INTIE        0x80
#define EIE_PKTIE        0x40
#define EIE_DMAIE        0x20
#define EIE_LINKIE       0x10
#define EIE_TXIE         0x08
#define EIE_WOLIE        0x04
#define EIE_TXERIE       0x02
#define EIE_RXERIE       0x01
// ENC28J60 EIR Register Bit Definitions
#define EIR_PKTIF        0x40
#define EIR_DMAIF        0x20
#define EIR_LINKIF       0x10
#define EIR_TXIF         0x08
#define EIR_WOLIF        0x04
#define EIR_TXERIF       0x02
#define EIR_RXERIF       0x01
// ENC28J60 ESTAT Register Bit Definitions
#define ESTAT_INT        0x80
#define ESTAT_LATECOL    0x10
#define ESTAT_RXBUSY     0x04
#define ESTAT_TXABRT     0x02
#define ESTAT_CLKRDY     0x01
// ENC28J60 ECON2 Register Bit Definitions
#define ECON2_AUTOINC    0x80
#define ECON2_PKTDEC     0x40
#define ECON2_PWRSV      0x20
#define ECON2_VRPS       0x08
// ENC28J60 ECON1 Register Bit Definitions
#define ECON1_TXRST      0x80
#define ECON1_RXRST      0x40
#define ECON1_DMAST      0x20
#define ECON1_CSUMEN     0x10
#define ECON1_TXRTS      0x08
#define ECON1_RXEN       0x04
#define ECON1_BSEL1      0x02
#define ECON1_BSEL0      0x01
// ENC28J60 MACON1 Register Bit Definitions
#define MACON1_LOOPBK    0x10
#define MACON1_TXPAUS    0x08
#define MACON1_RXPAUS    0x04
#define MACON1_PASSALL   0x02
#define MACON1_MARXEN    0x01
// ENC28J60 MACON2 Register Bit Definitions
#define MACON2_MARST     0x80
#define MACON2_RNDRST    0x40
#define MACON2_MARXRST   0x08
#define MACON2_RFUNRST   0x04
#define MACON2_MATXRST   0x02
#define MACON2_TFUNRST   0x01
// ENC28J60 MACON3 Register Bit Definitions
#define MACON3_PADCFG2   0x80
#define MACON3_PADCFG1   0x40
#define MACON3_PADCFG0   0x20
#define MACON3_TXCRCEN   0x10
#define MACON3_PHDRLEN   0x08
#define MACON3_HFRMLEN   0x04
#define MACON3_FRMLNEN   0x02
#define MACON3_FULDPX    0x01
// ENC28J60 MICMD Register Bit Definitions
#define MICMD_MIISCAN    0x02
#define MICMD_MIIRD      0x01
// ENC28J60 MISTAT Register Bit Definitions
#define MISTAT_NVALID    0x04
#define MISTAT_SCAN      0x02
#define MISTAT_BUSY      0x01
// ENC28J60 PHY PHCON1 Register Bit Definitions
#define PHCON1_PRST      0x8000
#define PHCON1_PLOOPBK   0x4000
#define PHCON1_PPWRSV    0x0800
#define PHCON1_PDPXMD    0x0100
// ENC28J60 PHY PHSTAT1 Register Bit Definitions
#define PHSTAT1_PFDPX    0x1000
#define PHSTAT1_PHDPX    0x0800
#define PHSTAT1_LLSTAT   0x0004
#define PHSTAT1_JBSTAT   0x0002
// ENC28J60 PHY PHCON2 Register Bit Definitions
#define PHCON2_FRCLINK   0x4000
#define PHCON2_TXDIS     0x2000
#define PHCON2_JABBER    0x0400
#define PHCON2_HDLDIS    0x0100

// ENC28J60 Packet Control Byte Bit Definitions
#define PKTCTRL_PHUGEEN  0x08
#define PKTCTRL_PPADEN   0x04
#define PKTCTRL_PCRCEN   0x02
#define PKTCTRL_POVERRIDE 0x01

// SPI operation codes
#define ENC28J60_READ_CTRL_REG       0x00
#define ENC28J60_READ_BUF_MEM        0x3A
#define ENC28J60_WRITE_CTRL_REG      0x40
#define ENC28J60_WRITE_BUF_MEM       0x7A
#define ENC28J60_BIT_FIELD_SET       0x80
#define ENC28J60_BIT_FIELD_CLR       0xA0
#define ENC28J60_SOFT_RESET          0xFF

// The RXSTART_INIT should be zero. See Rev. B4 Silicon Errata
// buffer boundaries applied to internal 8K ram
// the entire available packet buffer space is allocated
//
// start with recbuf at 0/
#define RXSTART_INIT     0x0
// receive buffer end
#define RXSTOP_INIT      (0x1FFF-1518-1)
// start TX buffer at 0x1FFF-0x0600, pace for one full ethernet frame (0~1518 bytes)
#define TXSTART_INIT     (0x1FFF-1518)
// stp TX buffer at end of mem
#define TXSTOP_INIT      0x1FFF
// max frame length which the conroller will accept:
#define   MAX_FRAMELEN    1518        // (note: maximum ethernet frame length would be 1518)


void ENC28J60_Reset(void);
u8 ENC28J60_Read_Op(u8 op,u8 addr);
void ENC28J60_Write_Op(u8 op,u8 addr,u8 data);
void ENC28J60_Read_Buf(u32 len,u8* data);
void ENC28J60_Write_Buf(u32 len,u8* data);
void ENC28J60_Set_Bank(u8 bank);
u8 ENC28J60_Read(u8 addr);
void ENC28J60_Write(u8 addr,u8 data);
void ENC28J60_PHY_Write(u8 addr,u32 data);
u8 ENC28J60_Init(u8* macaddr);
u8 ENC28J60_Get_EREVID(void);
void ENC28J60_Packet_Send(u32 len,u8* packet);
u32 ENC28J60_Packet_Receive(u32 maxlen,u8* packet);  
#endif
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-10-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、框架模型
  • 二、驱动代码
    • 2.1 ENC28J60网卡驱动+网络设备框架+中断接收数据.c
      • 2.2  对应enc28j60.h代码
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档