写点什么

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

作者:DS小龙哥
  • 2022-10-15
    重庆
  • 本文字数:16499 字

    阅读完需:约 1 分钟

一、框架模型

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


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


动态分配空间#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

#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 代码

#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
复制代码


发布于: 刚刚阅读数: 4
用户头像

DS小龙哥

关注

之所以觉得累,是因为说的比做的多。 2022-01-06 加入

熟悉C/C++、51单片机、STM32、Linux应用开发、Linux驱动开发、音视频开发、QT开发. 目前已经完成的项目涉及音视频、物联网、智能家居、工业控制领域

评论

发布
暂无评论
Linux下编写ENC28J60网卡驱动,完善网络设备框架_10月月更_DS小龙哥_InfoQ写作社区