谷动谷力

 找回密码
 立即注册
查看: 1298|回复: 0
收起左侧

【盘点】Linux下网络设备驱动框架

[复制链接]
发表于 2022-12-15 22:59:48 | 显示全部楼层 |阅读模式
【盘点】Linux下网络设备驱动框架
, i3 P% O/ ^# v& i6 n% v9 n3 A

  E% Y/ [. {  R0 s" u
1.1 Linux下网络相关命令1.1.1 ifconfig命令:设置网卡IP地址
  • 功能ifconfig用于查看和更改网络接口的地址和参数,包括IP地址、网络掩码、广播地址,使用权限是超级用户。
  • 语法:ifconfig -interface [options] address
  • 主要参数' P5 s' g/ t6 a/ Q, \
-interface指定的网络接口名,如eth0和eth1。
up激活指定的网络接口卡。
down关闭指定的网络接口。
broadcast address设置接口的广播地址。
pointopoint启用点对点方式。
address设置指定接口设备的IP地址。
netmask address设置接口的子网掩码。
( b1 h' S. f# f" ~# ~9 o
  • 应用说明ifconfig是用来设置和配置网卡的命令行工具。为了手工配置网络,这是一个必须掌握的命令。使用该命令的好处是无须重新启动机器。要赋给eth0接口IP地址207.164.186.2,并且马上激活它,使用下面命令:
    ; ?/ |. h  N& K* H# O
#fconfig eth0 210.34.6.89 netmask 255.255.255.128 broadcast 210.34.6.127

( N" N* p. f! Z! y# [& B该命令的作用是设置网卡eth0的IP地址、网络掩码和网络的本地广播地址。若运行不带任何参数的ifconfig命令,这个命令将显示机器所有激活接口的信息。带有“-a”参数的命令则显示所有接口的信息,包括没有激活的接口。注意,用ifconfig命令配置的网络设备参数,机器重新启动以后将会丢失。
a. 查看网卡的IP地址信息
# ifconfig //查看当前已经启动的网卡信息 # ifconfig -a//查看所有网卡的信息。包含未启动的网卡。# ifconfig eth0 //查看eth0网卡的信息
a. 关闭与启动网卡
# ifconfig eth0 up //激活名称为eth0的网卡# ifconfig eth0 down //关闭名称为eth0的网卡

# y$ @& l7 T, za. 修改网卡MAC地址
修改网卡MAC地址首先必须关闭网卡设备:ifconfig eth0 down修改MAC地址:ifconfig eth0 hw ether 00:AA:BB:CCD:EE重新启用网卡:ifconfig eht0 up这样网卡的MAC地址就更改完成了。每张网卡的MAC地址是惟一,但不是不能修改的,只要保证在网络中的MAC地址的惟一性就可以了。a. 在一张网卡上绑定多个IP地址在Linux下,可以使用ifconfig方便地绑定多个IP地址到一张网卡。例如,eth0接口的原有IP地址为192.168.0 .254,可以执行下面命令:
ifconfig eth0:0 192.168.0.253 netmask 255.255.255.0ifconfig eth0:1 192.168.0.252 netmask 255.255.255.0......
7 }% d6 m1 k' }$ Y3 U- `
1.1.2 ping命令
  • 功能:ping检测主机网络接口状态,使用权限是所有用户。
  • 语法:ping [-dfnqrRv][-c][-i][-I][-l][-p][-s][-t] IP地址
  • 主要参数& p2 s& Z5 f# w$ E) {5 }/ \
-d使用Socket的SO_DEBUG功能。
-c设置完成要求回应的次数。
-f极限检测。
-i指定收发信息的间隔秒数。
-I网络界面使用指定的网络界面送出数据包。
-l前置载入,设置在送出要求信息之前,先行发出的数据包。
-n只输出数值。
-p设置填满数据包的范本样式。
-q不显示指令执行过程,开头和结尾的相关信息除外。
-r忽略普通的Routing Table,直接将数据包送到远端主机上。
-R记录路由过程。
-s设置数据包的大小。
-t‍设置存活数值TTL的大小。
-v详细显示指令的执行过程。
/ T; R3 t! q4 r
ping命令是使用最多的网络指令,通常我们使用它检测网络是否连通,它使用ICMP协议。但是有时会有这样的情况,我们可以浏览器查看一个网页,但是却无法ping通,这是因为一些网站处于安全考虑安装了防火墙。
使用实例
# ping 192.168.11.123

9 V7 }4 v1 ~2 R3 b" ~1.1.3 网卡启动与关闭
除了使用ifconfig配置之外,也可以使用ifup、ifdown命令来实现。
# ifup eth0 //开启eth0网卡# ifdown eth0 //关闭eth0网卡
9 ^9 n% I- b) B5 O+ w
1.2 查看内核已经支持的网卡驱动
进入到内核配置菜单目录下: [root@wbyq linux-3.5]# make menuconfig
Device Drivers --->
  • Network device support --->………………………………..USB Network Adapters ---> //支持的USB网卡设备<*> USB Pegasus/Pegasus-II based ethernet device support< > USB RTL8150 based ethernet device support (EXPERIMENTAL)<*> ASIX AX88xxx Based USB 2.0 Ethernet Adapters<*> Davicom DM9601 based USB 1.1 10/100 ethernet devices<*> Davicom DM9620 USB2.0 Fast Ethernet devices (开发板本身的自带网卡)< > SMSC LAN75XX based USB 2.0 gigabit ethernet devices< > SMSC LAN95XX based USB 2.0 10/100 ethernet devices< > GeneSys GL620USB-A based cables< > Prolific PL-2301/2302/25A1 based cables< > MosChip MCS7830 based Ethernet adapters
  • 1 ^; L( r9 r8 D. S% k( ^
    1.3 移植ENC28J60网卡驱动
    1.3.1 ENC28J60芯片介绍ENC28J60 是带有行业标准串行外设接口(Serial Peripheral Interface,SPI)的独立以太网 控制器。它可作为任何配备有 SPI 的控制器的以太网接口。ENC28J60 符合 IEEE 802.3 的全部规范,采用了一系列包过滤机制以对传入数据包进行限制。它还提供了一个内部 DMA 模块, 以实现快速数据吞吐和硬件支持的 IP 校验和计算。与主控制器的通信通过两个中断引脚和 SPI 实现,数据传输速率高达 10 Mb/s。两个专用的引脚用于连接 LED,进行网络活动状态指示。ENC28J60 总共只有 28 脚,提供 QFN/TF。的主要特点如下:
    • 兼容 IEEE802.3 协议的以太网控制器
    • 集成 MAC 和 10 BASE-T 物理层
    • 支持全双工和半双工模式
    • 数据冲突时可编程自动重发
    • SPI 接口速度可达 10Mbps
    • 8K 数据接收和发送双端口 RAM
    • 提供快速数据移动的内部 DMA 控制器
    • 可配置的接收和发送缓冲区大小
    • 两个可编程 LED 输出
    • 带7个中断源的两个中断引脚
    • TTL 电平输入
    • 提供多种封装:SOIC/SSOP/SPDIP/QFN 等。- n- _& E7 g0 N) @; f+ ]- E) M4 [
    ENC28J60 的典型应用电路如下图: 640?wx_fmt=png.jpg ENC28J60 由七个主要功能模块组成:1) SPI 接口,充当主控制器和 ENC28J60 之间通信通道。2) 控制寄存器,用于控制和监视 ENC28J60。3) 双端口 RAM 缓冲器,用于接收和发送数据包。4) 判优器,当 DMA、发送和接收模块发出请求时对 RAM 缓冲器的访问进行控制。5) 总线接口,对通过 SPI 接收的数据和命令进行解析。6) MAC(Medium Access Control)模块,实现符合 IEEE 802.3 标准的 MAC 逻辑。7) PHY(物理层)模块,对双绞线上的模拟数据进行编码和译码。ENC28J60 还包括其他支持模块,诸如振荡器、片内稳压器、电平变换器(提供可以接受 5V 电压的 I/O 引脚)和系统控制逻辑。引脚功能说明: 640?wx_fmt=png.jpg 1.3.2 ENC28J60以太网模块介绍ENC28J60 网络模块采用 ENC28J60 作为主芯片,单芯片即可实现以太网接入, 利用该模块,基本上只要是个单片机,就可以实现以太网连接。模块实物图如下:
    • 模块的主要引脚功能:0 X. l/ s; M: T/ t0 v
    其中 GND 和 V3.3 用于给模块供电,MISO/MOSI/SCK 用于 SPI 通信,CS 是片选信号,INT 为中断输出引脚,RST 为模块复位信号。1.3.3 查看内核已经支持的网卡源码在内核linux-3.5/drivers/net/ethernet源码目录下可以查看已经支持的网卡源码。ENC28J60网卡源码就存放在: /linux-3.5/drivers/net/ethernet/microchip目录下
    [root@wbyq microchip]# pwd/work/Tiny4412/linux-3.5/drivers/net/ethernet/microchip[root@wbyq microchip]# lsenc28j60.c enc28j60_hw.h Kconfig Makefile
    1.3.4 配置内核SPI总线设备端
    : s9 @- l$ Z* z* T1. ENC28J60使用的是SPI总线通信,先查看内核SPI总线板级注册是否支持。进入到内核配置菜单: [root@wbyq linux-3.5]# make menuconfig
    Device Drivers --->
  • SPI support ---><*> Samsung S3C64XX series type SPI[*] Samsung S3C64XX Channel 0 Support./选中SP0总线支持*/

  •   ^: Q: k+ m( R1 p+ N因为开发板引出的SPI接口只有SPI0,所以只能配置SPI0总线。
    640?wx_fmt=png.jpg 2. 修改SPI0总线板级注册信息2 d6 C- w0 `* i  u$ n5 y
    打开开发板底层板级配置文件:3 `" A5 D6 p3 Y0 M& C* X
    [root@wbyq linux-3.5]# vim arch/arm/mach-exynos/mach-tiny4412.c +1449

    $ G% n% Z+ k7 }1 _+ p* k修改SPI设备端名称:
    1447 static struct spi_board_info spi0_board_info[] __initdata = {1448 {1449 .modalias = "spidev_enc28j60", /*修改设备端名称*/1450 .platform_data = NULL,1451 .max_speed_hz = 10*1000*1000,1452 .bus_num = 0,1453 .chip_select = 0,1454 .mode = SPI_MODE_0,1455 .controller_data = &spi0_csi[0],1456 }1457 };
    SPI子系统匹配使用的是平台设备模型,驱动端与设备端的名称需要一致。+ d. w8 f+ z$ G+ ~! d
    3. 修改完以上两步配置之后,再重新编译内核,烧写内核。1.3.5 修改ENC28J60驱动代码将/drivers/net/ethernet/microchip目录下的ENC28J60源码复制出来,单独修改。编写Makefile文件,负责编译成模块 640?wx_fmt=png.jpg 修改ENC28J60驱动源码里的名称与SPI总线设备端保持一致。 640?wx_fmt=png.jpg 3. 修改驱动端的probe函数,增加对SPI模式配置与中断号获取,正常情况下可以直接在SPI设备端直接修改,驱动端直接获取信息即可。
    static int __devinit enc28j60_probe(struct spi_device *spi){spi->irq=gpio_to_irq(EXYNOS4_GPX3(2)); /*获取中断号*//*配置SPI模式*/spi->bits_per_word = 8;spi->mode = SPI_MODE_1;spi->max_speed_hz=50000;/*1*100000; //50000*/if(spi_setup(spi)<0)//配置{printk("SPI配置失败!\n");}………………………….}
    除了修改以上信息之外,其他信息不用修改,直接编译驱动安装即可。1.3.6 驱动安装测试3 t* h/ D( D+ b' U9 G
    [root@XiaoLong /code]# insmod enc28j60.ko[ 31.640000] SPI Probe函数匹配成功,SPI总线编号: 0[ 31.640000] spidev_enc28j60 spi0.0: spidev_enc28j60 Ethernet driver 1.01 loaded[ 31.655000] spi->irq=442[ 31.710000] net eth1: spidev_enc28j60 driver registered[root@XiaoLong /code]# ifconfig -aeth0 Link encap:Ethernet HWaddr 00:00:FF:FF:00:00inet addr:192.168.10.123 Bcast:192.168.10.255 Mask:255.255.255.0inet6 addr: fe80::200:ffff:feff:0/64 ScopeinkUP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1RX packets:2841 errors:0 dropped:0 overruns:0 frame:0TX packets:1641 errors:0 dropped:0 overruns:0 carrier:0collisions:0 txqueuelen:1000RX bytes:2695524 (2.5 MiB) TX bytes:295408 (288.4 KiB); `8 W( W! a& b' X+ X: g
    eth1 Link encap:Ethernet HWaddr CE:89:65:5A:91:93 //新生成的网卡名称BROADCAST MULTICAST MTU:1500 Metric:1RX packets:0 errors:0 dropped:0 overruns:0 frame:0TX packets:0 errors:0 dropped:0 overruns:0 carrier:0collisions:0 txqueuelen:1000RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)Interrupt:186
      {! ~# ]7 _  |ip6tnl0 Link encap:UNSPEC HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00NOARP MTU:1452 Metric:1RX packets:0 errors:0 dropped:0 overruns:0 frame:0TX packets:0 errors:0 dropped:0 overruns:0 carrier:0collisions:0 txqueuelen:0RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)1 Q9 o: k1 U. c1 R2 t
    lo Link encapocal Loopbackinet addr:127.0.0.1 Mask:255.0.0.0inet6 addr: ::1/128 Scope:HostUP LOOPBACK RUNNING MTU:16436 Metric:1RX packets:0 errors:0 dropped:0 overruns:0 frame:0TX packets:0 errors:0 dropped:0 overruns:0 carrier:0collisions:0 txqueuelen:0RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)- l& D3 `$ J6 c; ]8 G
    sit0 Link encap:IPv6-in-IPv4NOARP MTU:1480 Metric:1RX packets:0 errors:0 dropped:0 overruns:0 frame:0TX packets:0 errors:0 dropped:0 overruns:0 carrier:0collisions:0 txqueuelen:0RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)[root@XiaoLong /code]# ifconfig eth1 192.168.1.100 //设置网卡IP地址[ 76.460000] net eth1: link down[ 76.460000] net eth1: multicast mode[ 76.460000] net eth1: multicast mode[ 76.460000] net eth1: multicast mode[ 76.460000] IPv6: ADDRCONF(NETDEV_UP): eth1: link is not ready[root@XiaoLong /code]# udhcpc -i eth1 //自动获取IP地址
    ( k6 L& A6 e; e$ m# ?( w% W1 N' M
    1.4 网络设备相关API函数介绍! |" H' u9 |; S, ?. D) j+ W
    1.4.1 动态分配net_device结构
    #define alloc_etherdev(sizeof_priv) alloc_etherdev_mq(sizeof_priv, 1)

    : ?5 ]* Z7 _9 D3 u函数参数:分配的空间大小。如果自己没有定义自己的结构体,就直接填sizeof(struct net_device)
    函数返回值:执行成功返回申请的空间地址。空间分配的函数还有一个alloc_netdev()函数。alloc_etherdev()是alloc_netdev()针对以太网的"快捷"函数1.4.2 释放net_device结构3 p) I1 x- g7 ]- @
    void free_netdev(struct net_device *dev)

    - m/ L6 u, U7 j- h) d该函数用于释放alloc_etherdev分配的net_device结构体,与alloc_etherdev成对使用。
    1.4.3 注册网络设备
    int register_netdev(struct net_device *dev)
    函数形参:网络设备信息struct net_device5 c: W0 ?0 g& e
    函数返回值:执行成功返回0。
    • struct net_device结构体原型如下:& F; K9 }: o/ S8 a# O
    struct net_device {charname[IFNAMSIZ]; 网卡名字,ifconfig查看的名称*/unsigned longmem_end;/* shared mem end */unsigned longmem_start;/* shared mem start*/. o2 [0 H  |; W8 A
    这两个变量描述设备与内核通信所用到的内存边界。它们由设备驱动初始化,并且只能被设备驱动访问;高层协议不需要关心这块内存。  l/ c. Y# U# e) i- e/ E. I+ C
    unsigned longbase_addr;/* 存放网络设备基地址,就是物理地址,用来将设备映射到内存空间*/unsigned intirq;/*设备中断号。它可以被多个设备共享。设备驱动调用request_irq来分配这个值,并调用free_irq来释放它*/* f0 X! ?. m& S5 l  V
    const struct net_device_ops *netdev_ops;//网络设备的虚拟文件操作集合,很重要的结构。$ }# S8 w3 B7 X. K/ t7 K+ N% C
    const struct ethtool_ops *ethtool_ops; //可选的设备操作
      \% h$ Q3 B: n  s+ q+ I. Y9 x! j
    unsigned char*dev_addr;/*MAC地址*/unsigned charbroadcast[MAX_ADDR_LEN];/*广播地址*/unsigned longlast_rx;/*最后收到数据包的时间,用于判断超时*/5 m. |% x- k, w6 E# N6 J, c" Q
    unsigned charif_port;/*接口的端口类型。*/unsigned chardma; /* DMA channel*/
    & G4 O2 X6 E( p; t9 u6 [4 T2 |
    /*设备所使用的DMA通道。为获取和释放一个DMA通道,内核在kernel/dma.c中定义了两个函数request_dma和free_dma。为了在获取dma通道后,启用或者停止dma通道,内核定义了两个函数enable_dma和disable_dma。这两个函数的实现与体系结构相关,所以在include/asm-architecture下有相关的文件(例如include/asm-i386)。这些函数被ISA设备使用;PCI设备不使用这些函数,它们使用其他函数。并不是所有的设备都可以使用dma,因为有些总线不支持dma。*/
    : k; F! X; k- j& g! ^9 q: b% aunsigned longtrans_start;/* 数据包发送的起始时间-jiffies表示*/1 p+ a' Y( }% \4 \5 g7 L9 @4 _
    intwatchdog_timeo;被 by dev_watchdog()函数使用,用于定义超时 */struct timer_listwatchdog_timer; /*看门狗定时器*/};
    5 r: _- D2 P3 w& h3 Q" Y
    • const struct net_device_ops 网络设备虚拟文件操作集合:
      $ Q: l' b+ ^8 d- E) ^' A' I  D
    struct net_device_ops {/*初始化注册网络设备的时候调用*/int (*ndo_init)(struct net_device *dev);
    / K# M8 A( G8 A9 J0 V) ?+ g3 T) Y' N% e3 Z# W
    /*释放设备的时候调用*/void (*ndo_uninit)(struct net_device *dev);2 b0 W, X9 |3 S* F3 j
    /*打开网络接口,对应ifconfig up命令,编写网络设备硬件初始化的相关代码*/int (*ndo_open)(struct net_device *dev);* b+ M- D1 o; ^* I- S5 V" \, O$ T
    /*关闭网络设备,对应ifconfig down命令,实现的内容与OPEN相反*/int (*ndo_stop)(struct net_device *dev);+ |) q, u* a3 z9 D$ \
    /*启动网络数据包传输的方法*,返回值必须返回NETDEV_TX_OK, NETDEV_TX_BUSY /netdev_tx_t (*ndo_start_xmit) (struct sk_buff *skb,struct net_device *dev);- G; K) g2 g- L$ n: G
    /*网络数据包没有在规定的时间内发送出去,产生超时事件时的处理函数,它应当处理超时问题,并恢复报文发送*/void (*ndo_tx_timeout) (struct net_device *dev);
    & t& {0 `, {7 l……………………省略…………………………….}

    + Q* Z9 o1 l, o! ?/ t' [
    • 分配net_device结构体之后初始化示例2 E2 O5 X: j/ X; @4 a0 b. T
    /*1. 分配及初始化net_device对象,参数:私有数据大小(单位:字节数)*/tiny4412_net=alloc_etherdev(sizeof(struct net_device));
    " P' C2 {# i4 H, A% ]8 x/*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; //看门狗超时时间

    ( R$ y9 m+ E  _9 q1.4.4注销网络设备
    void unregister_netdev(struct net_device *dev)
    $ N" S& ?" ~+ b. @: h: B
    功能:注销网络设备
    参数:注销的网络设备结构体1.4.5 随机生成MAC地址
    void eth_hw_addr_random(struct net_device *dev)

    - Q! ]6 O5 Z- ?0 a% F; y该函数使用软件方式随机生成一个MAC地址,并给传入的net_device 结构体内部成员dev_addr赋值。
    示例:
    /*随机生成MAC地址*/eth_hw_addr_random(tiny4412_net); // struct net_device *tiny4412_net;随机生成的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];

    9 P$ g+ \/ J. ]3 K$ G1.4.6 以太网最小一帧数据长度定义
    #define ETH_ALEN 6 //定义了以太网接口的MAC地址的长度为6个字节#define ETH_HLAN 14 //定义了以太网帧的头长度为14个字节#define ETH_ZLEN 60 //定义了以太网帧的最小长度为 ETH_ZLEN + ETH_FCS_LEN = 64个字节#define ETH_DATA_LEN 1500 //定义了以太网帧的最大负载为1500个字节#define ETH_FRAME_LEN 1514定义了以太网正的最大长度为ETH_DATA_LEN + ETH_FCS_LEN = 1518个字节#define ETH_FCS_LEN 4 //定义了以太网帧的CRC值占4个字节
    , ~9 W5 Q# ~( Q; a5 a( f( X6 V, k
    使用网卡发送数据时,如何发现发送的实际数据小于以太网规定的最小长度,需要进行补齐:
    static netdev_tx_ttiny4412_ndo_start_xmit(struct sk_buff *skb,struct net_device *dev){int len;char *data, shortpkt[ETH_ZLEN];
    ( J! z/ K. g' A) ?3 v获得有效数据指针和长度 */获取将要发送出去的数据指针*/获取将要发送出去的数据长度*/. \' M& y* x  }1 v& j4 O
    if(len < ETH_ZLEN){如果帧长小于以太网帧最小长度,补0 */memset(shortpkt,0,ETH_ZLEN);memcpy(shortpkt,skb->data,skb->len);len = ETH_ZLEN;data = shortpkt;}………………省略……………………………..}

    5 u! d4 I- R$ @5 z1.4.7 分配新的套接字缓冲区
    struct sk_buff *dev_alloc_skb(unsigned int length)
    函数用于分配新的套接字缓冲区,用于存放即将上报给上层(TCP/IP协议层)的网络数据。3 L3 T2 G# b9 R# I7 M* V
    示例:
    / M: R$ O5 p3 k. a
    /*从ENC28J60的寄存器里读取接收到的数据*/length=ENC28J60_Packet_Receive(1518,Enc28j60_Rx_Buff);if(length<=0){return;}
    - e# V% w' }! M/*分配新的套接字缓冲区*/struct sk_buff *skb = dev_alloc_skb(length+NET_IP_ALIGN);        skb_reserve(skb, NET_IP_ALIGN); //对齐        skb->dev = tiny4412_net;        /*将硬件上接收到的数据拷贝到sk_buff里*/        memcpy(skb_put(skb, length),Enc28j60_Rx_Buff,length);
    " g$ f$ G) V' _! Z- S  C8 ~/ ^! F' z       说明: skb_put(skb, length)返回sk_buff数据缓冲区首地址,保存即将上报给应用层的数据。       参考: smsc-ircc2.c文件1461行
    1 A' J/ o/ y7 w
    1.4.8 获取数据包的协议ID
    eth_type_trans(struct sk_buff *skb, struct net_device *dev)
    从网卡里读取到数据包之后,可以通过该函数获取数据包的协议类型。示例:
    将硬件上接收到的数据拷贝到sk_buff里*/memcpy(skb_put(skb, length),Enc28j60_Rx_Buff,length); // Enc28j60_Rx_Buff是网卡收到的实际数据4 ?+ p% w0 F6 G+ K
    /* 获取上层协议类型 */skb->protocol = eth_type_trans(skb,tiny4412_net);
    : ?( v7 q, {0 m+ \) O3 _
    1.5 网络设备框架介绍1.5.1 网络设备驱动框图图1.5.1 网络设备驱动框架图1.5.2 ndo_start_xmit函数接口代码编写示例
    /*启动网络数据包传输的方法*/static netdev_tx_ttiny4412_ndo_start_xmit(struct sk_buff *skb,struct net_device *dev){int len;char *data, shortpkt[ETH_ZLEN];& t( g! r# u4 j1 c
    获得有效数据指针和长度 */获取将要发送出去的数据指针*/获取将要发送出去的数据长度*/( H1 D  `4 Q3 o- {0 n& `
    if(len < ETH_ZLEN){如果帧长小于以太网帧最小长度,补0 */memset(shortpkt,0,ETH_ZLEN);memcpy(shortpkt,skb->data,skb->len);len = ETH_ZLEN;data = shortpkt;}+ k( H7 _% ]& _* _' @. {  c
    /*记录发送时间戳*/dev->trans_start = jiffies;
      i& Q. c0 C) i) v' {) M- U设置硬件寄存器让硬件将数据发出去 */ENC28J60_Packet_Send(len,data);
    7 [% @( z0 r9 F/ c/*释放skb*/dev_kfree_skb(skb);, p' J7 Y: _0 V$ ~) r
    /*更新统计信息:记录发送的包数量*/dev->stats.tx_packets++;
    ! e4 }' w: i' c; q9 u/*更新统计信息:记录发送的字节数量*/dev->stats.tx_bytes += skb->len;9 B9 {  a4 m6 Z9 C4 Z/ H1 q! a
    return NETDEV_TX_OK; //这是个枚举状态。}

    $ E( Y+ V, O' p5 E7 v1.5.3 通过netif_rx函数上报数据代码编写示例
    /*工作队列处理函数以下函数用于读取网卡里的数据。读取完毕之后,再通过netif_rx()函数上报到应用层*/u8 Enc28j60_Rx_Buff[1518]; /*ENC28J60最大可接收的字节*/static void workqueue_function(struct work_struct *work){int length;/*从ENC28J60的寄存器里读取接收到的数据*/length=ENC28J60_Packet_Receive(1518,Enc28j60_Rx_Buff);if(length<=0){return;}
    2 `- j: P- D9 v$ H: e/*2. 分配新的套接字缓冲区*/struct sk_buff *skb = dev_alloc_skb(length+NET_IP_ALIGN);对齐skb->dev = tiny4412_net;" J: L8 _( l! L* G$ ?# X4 ?( [! ^
    将硬件上接收到的数据拷贝到sk_buff里*/memcpy(skb_put(skb, length),Enc28j60_Rx_Buff,length);! ]  p7 w# O6 t, {; X" H- R+ Y4 Z
    获取上层协议类型 */skb->protocol = eth_type_trans(skb,tiny4412_net);+ S  h6 z" }  K6 c
    记录接收时间戳 */tiny4412_net->last_rx = jiffies;
    + p% D9 K/ _* L2 r5 O/*接收的数据包*/tiny4412_net->stats.rx_packets++;3 y: y! p- w, B7 m; m
    /*接收的字节数量*/tiny4412_net->stats.rx_bytes += skb->len;
    / ]. j0 V. k# v/* 把数据包交给上层 */netif_rx(skb);}
    8 S! k, }; a' i. W# [, l
    1.6 网络设备驱动框架代码1.6.1 网络设备驱动编程步骤1. 调用alloc_etherdev函数,分配net_device对象2. 对返回的net_device结构指针进行初始化赋值,比如:网卡名称,MAC地址,文件操作集合等等。如果网卡没有固定的MAC地址,可以通过eth_hw_addr_random函数随机生成。3. 网络设备文件操作集合实现的接口如下:
    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,};
    " r9 l6 r$ V* o7 @* L: ]; K
    4. 调用register_netdev函数完成网络设备注册。
    注销函数: unregister_netdev5. 网卡收到数据通过netif_rx函数上传给应用层1.6.2 网络设备驱动框架代码以下代码是一个网络设备驱动模型,演示了网卡如何获取上层应用程序传递下来的数据并发送出去,网卡接收到数据如何传递给上层应用程序。
    #include <linux/init.h>#include <linux/module.h>#include <linux/netdevice.h>#include <linux/etherdevice.h>
    + D5 O3 o% q( J& l% {; O  Sstatic struct net_device *tiny4412_net=NULL; //网络设备指针结构
    6 c1 @1 M0 o4 x  V/*1. 设备初始化调用,该函数在注册成功后会调用一次,可以编写网卡初始化相关代码*/static int tiny4412_ndo_init(struct net_device * dev){printk("网络设备初始化!\n");return 0;}
    5 {; P8 x0 l+ c1 t  X% t/ }/*2. 打开网络接口,对应ifconfig up命令,编写网络设备硬件初始化的相关代码*/static inttiny4412_ndo_open(struct net_device *dev){printk("网络设备打开成功!\n");return 0;}
    ' h" ~. @- h3 D* H- Y7 M/*3. 关闭网络设备,对应ifconfig down命令,实现的内容与OPEN相反*/static inttiny4412_ndo_stop(struct net_device *dev){printk("网络设备关闭成功!\n");return 0;}* X% Z+ \; J0 B$ ^' B) R
    /*4. 启动网络数据包传输的方法当应用层的TCP/IP需要发送数据时,就调用该函数*/static netdev_tx_ttiny4412_ndo_start_xmit(struct sk_buff *skb,struct net_device *dev){int len,i;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;}记录发送时间戳
    7 O8 F0 `* w! D6 D3 vprintk("\n发送的数据:");for(i=0;i<len;i++){printk("%X ",data);}printk("\n");设置硬件寄存器让硬件将数据发出去 */// xxx_hw_tx(data,len,dev);return NETDEV_TX_OK; //这是个枚举状态。}
    6 M) \5 b8 \  J  c/*5. 设置MAC地址,对应的命令: ifconfig eth888 hw ether 00:AA:BB:CCD: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]);return 0;}
    $ x! @1 U9 U% O# _& f$ a/*以下函数在网卡的接收中断中调用,用于读取网卡里的数据。读取完毕之后,再通过netif_rx()函数上报到应用层*/static void data_rx(struct net_device* dev){int length;/*1. 读取硬件网卡接收到的数据*///length = get_rev_len(...);& s0 Q4 [% b7 J7 O) g
    /*2. 分配新的套接字缓冲区*/struct sk_buff *skb = dev_alloc_skb(length+NET_IP_ALIGN);对齐skb->dev = dev;
    9 C) B9 {. T2 }2 m) C; Y读取硬件上接收到的数据 *///skb_put(skb, length) //存放网卡里读取数据的缓冲区地址
    * u, i% D8 F3 I5 R- r; s9 O
    //ENC28J60_Read(length,skb_put(skb, length));
    % q/ y; Q' N( `获取上层协议类型 */skb->protocol = eth_type_trans(skb,dev);
    $ h' y' j0 P/ e把数据包交给上层 */netif_rx(skb);( n2 f1 D1 F9 V
    记录接收时间戳 */dev->last_rx = jiffies;}0 G+ _0 G1 B0 U# S+ w; m, I7 C
    /*网络设备虚拟文件操作集合*/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,};8 Y+ d/ e: ~& u! c8 ^5 c6 Q& y
    static int __init Net_test_init(void){
    # d/ B2 u" U; a3 x5 s% K1 e% E/*1. 分配及初始化net_device对象,参数:私有数据大小(单位:字节数)*/tiny4412_net=alloc_etherdev(sizeof(struct net_device));/ t2 v$ |5 y: c) E  S' S' I
    /*2. net结构体赋值*/strcpy(tiny4412_net->name, "eth888");//网络设备的名称,使用ifconfig -a可以查看到。tiny4412_net->netdev_ops=&netdev_ops_test; //虚拟文件操作集合
    8 p5 ?. s4 k& t/*3. 随机生成MAC地址*/eth_hw_addr_random(tiny4412_net);随机生成的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]);
    + K- u0 B0 x- Y" K# f2 Q; k/*注册网络设备*/register_netdev(tiny4412_net);
    & Y/ ]3 r+ v, y: lprintk("网络设备注册成功!\n");return 0;}
    / s6 Q9 t9 Y. \% n" `" n$ vstatic void __exit Net_test_exit(void){//注销网络设备unregister_netdev(tiny4412_net);printk("网络设备注销成功!\n");}
    ; e1 T4 C6 l# G; d) e3 Imodule_init(Net_test_init);module_exit(Net_test_exit);MODULE_AUTHOR("xiaolong");MODULE_LICENSE("GPL");
    / }  D3 v9 x' g4 c
    1.6.3 ENC28J60网卡驱动代码以下代码,在上面的网络设备驱动模型里加入了ENC28J60驱动代码,实现了完整的网卡驱动程序。以下代码中的ENC28J60驱动直接是使用模拟SPI时序,没有使用SPI子系统。由于测试的ENC28J60网卡中断无法正常产生,故使用内核定时器进行轮询读取网卡数据,读取之后再上传给应用层。网卡驱动安装后应用层测试效果如下:
    [root@XiaoLong /code]# insmod enc28j60_network_drv.ko //安装网卡[ 52.075000] 随机生成的MAC地址如下:[ 52.075000] 2E-2F-7-5-A0-DC网络设备初始化![ 52.100000] 网络设备注册成功![root@XiaoLong /code]#[root@XiaoLong /code]# ifconfig eth888 //查看网卡信息eth888 Link encap:Ethernet HWaddr 2E:2F:07:05:A0CBROADCAST MULTICAST MTU:1500 Metric:1RX packets:0 errors:0 dropped:0 overruns:0 frame:0TX packets:0 errors:0 dropped:0 overruns:0 carrier:0collisions:0 txqueuelen:1000RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)7 b  \; I( t  t/ L# Y4 j+ S
    [root@XiaoLong /code]# udhcpc -i eth888 //自动分配IP地址udhcpc (v1.23.2) startedSetting IP address 0.0.0.0 on eth888[ 92.310000] 网络设备打开成功!Sending discover...Sending select for 192.168.1.102...Lease of 192.168.1.102 obtained, lease time 7200Setting IP address 192.168.1.102 on eth888Deleting routersroute: SIOCDELRT: No such processAdding router 192.168.1.1Recreating /etc/resolv.confAdding DNS server 192.168.1.1[root@XiaoLong /code]# ifconfig //查看分配成功并设置成功的IP地址eth888 Link encap:Ethernet HWaddr 2E:2F:07:05:A0Cinet addr:192.168.1.102 Bcast:192.168.1.255 Mask:255.255.255.0inet6 addr: fe80::2c2f:7ff:fe05:a0dc/64 ScopeinkUP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1RX packets:0 errors:0 dropped:0 overruns:0 frame:0TX packets:0 errors:0 dropped:0 overruns:0 carrier:0collisions:0 txqueuelen:1000RX bytes:0 (0.0 B) TX bytes:0 (0.0 B), q% G, Y1 F/ I. e% [
    [root@XiaoLong /code]# ping 192.168.1.112 //ping局域网内的其他主机PING 192.168.1.1 (192.168.1.1): 56 data bytes64 bytes from 192.168.1.1: seq=0 ttl=64 time=12.099 ms64 bytes from 192.168.1.1: seq=1 ttl=64 time=12.932 ms64 bytes from 192.168.1.1: seq=2 ttl=64 time=9.035 ms--- 192.168.1.1 ping statistics ---3 packets transmitted, 3 packets received, 0% packet lossround-trip min/avg/max = 9.035/11.355/12.932 ms

      j# E9 u4 p: R$ P& y! l/ K, {
    • Enc28j60.h文件代码
      0 h; d7 o! e: O
    上下滑动查看更多
    #ifndef __ENC28J60_H8 E' G' O; ^/ U/ I7 g7 j1 s% w8 s
    #define __ENC28J60_H2 e1 e0 L2 I2 n& b
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 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
    ( q7 `/ s3 d' G( d, }#define MACON1_LOOPBK 0x10, M2 `9 l0 t0 C0 Y, q
    #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
    - L. f$ L2 Z, V/ K8 O( e8 ~$ k#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" Y- z; q2 s6 ~" ~' O9 G
    #define PHCON2_TXDIS 0x2000#define PHCON2_JABBER 0x0400#define PHCON2_HDLDIS 0x0100
    0 ]8 W/ x- q* z# Z/ J// ENC28J60 Packet Control Byte Bit Definitions#define PKTCTRL_PHUGEEN 0x08#define PKTCTRL_PPADEN 0x04#define PKTCTRL_PCRCEN 0x02#define PKTCTRL_POVERRIDE 0x010 \9 z- U" I, ~! `6 f
    // 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. y7 B9 D. X( X7 T, L  |
    // 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)////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    9 ~9 y' |6 |3 A4 Y  P+ }- N0 @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
    ) L/ K  o% P' A$ B
    • Enc28j60.c文件代码:
      2 }1 g1 L# v4 g" q, q9 z
    上下滑动查看更多
    #include <linux/init.h>
      K& s9 n. I4 ]/ u( ]3 O/ r" U
    #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>
    * O. T1 `) P& N3 f( ?* {  I
    /*
    参考的网卡程序: cs89x0.c与Enc28j60.c
    */

    , N3 ?5 c! z5 l9 [) b' j
    /*
    以下是ENC28J60驱动移植接口:
    SPI0接口:
    GPB_0--SCK
    GPB_1--CS
    GPB_2--MISO
    GPB_3--MOSI
    GPX1(0)--中断
    */
    static u32 ENC28J60_IRQ; //中断编号

    % E6 M/ l( W" h
    /*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_IRQ_NUMBER EXYNOS4_GPX1(0) /*Tiny4412开发板引出的IO口第9个IO口*/
    / G2 P1 B/ {& _
    #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_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;
    }
    . i: t2 E  F7 f: o. J
    /*
    函数功能:复位ENC28J60,包括SPI初始化/IO初始化等
    MISO--->A6----主机输入
    MOSI--->A7----主机输出
    SCLK--->A5----时钟信号
    CS----->A4----片选
    RESET-->G15---复位
    */
    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);
    /*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",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));
    mdelay(100);
    }

    ! d- }! }. g2 K: r! I
    /*
    函数功能:读取ENC28J60寄存器(带操作码)
    参 数:
    :操作码
    寄存器地址/参数
    返 回 值:读到的数据
    */
    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,初始化成功;
    初始化失败;
    */
    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 address8K字节容量
    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 PADCFG2ACDFG0:自动填充和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 :数据包大小
    数据包
    */
    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);
    }

    2 |4 o( r$ n# U1 Y
    /*
    函数功能:从网络获取一个数据包内容
    函数参数:
    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);
    }

    8 z6 W- z% ~( T9 d7 y: W6 C0 z
    /*--------------------------工作队列、定时器、中断服务函数---------------------------------------*/
    static struct work_struct work_list;
    ' F3 u1 I' E, |# D
    /*
    工作队列处理函数
    以下函数用于读取网卡里的数据。
    读取完毕之后,再通过netif_rx()函数上报到应用层
    */

    " E& B3 ?. V2 j8 _  W) E
    u8 Enc28j60_Rx_Buff[1518]; /*ENC28J60最大可接收的字节*/
    static void workqueue_function(struct work_struct *work)
    {
    int length;
    /*从ENC28J60的寄存器里读取接收到的数据*/
    length=ENC28J60_Packet_Receive(1518,Enc28j60_Rx_Buff);
    if(length<=0)
    {
    return;
    }

    0 b% s0 ^/ A: h3 M2 I2 J
    /*2. 分配新的套接字缓冲区*/
    struct sk_buff *skb = dev_alloc_skb(length+NET_IP_ALIGN);
    对齐
    skb->dev = tiny4412_net;
    将硬件上接收到的数据拷贝到sk_buff里*/
    memcpy(skb_put(skb, length),Enc28j60_Rx_Buff,length);

    % t: t9 c; C8 A
    获取上层协议类型 */
    skb->protocol = eth_type_trans(skb,tiny4412_net);

    / |4 @0 ~0 f& R
    记录接收时间戳 */
    tiny4412_net->last_rx = jiffies;
      q0 ]7 Y8 @! g8 G5 S/ |* q
    /*接收的数据包*/
    tiny4412_net->stats.rx_packets++;
    - ?5 r/ B! W6 b3 v
    /*接收的字节数量*/
    tiny4412_net->stats.rx_bytes += skb->len;
    3 x) u. x- ^0 V5 c% T$ \' J
    /* 把数据包交给上层 */
    netif_rx(skb);
    }
    4 W: \- F2 o# `! z
    /*
    函数功能: 中断服务函数
    */
    irqreturn_t ENC28J60_irq_handler(int irq, void *dev)
    {
    schedule_work(&work_list);
    return IRQ_HANDLED;
    }
    # y" |# D4 c- E4 ]) A1 F4 Y
    static void timer_function(unsigned long data)
    {
    /*共享工作队列调度*/
    schedule_work(&work_list);
    /*修改定时器超时*/
    mod_timer(&timer_date,jiffies+usecs_to_jiffies(100));
    /*注明: ENC28J60的中断不灵敏,就使用定时器轮询弥补*/
    }

    0 d; W2 s$ u8 X+ s* O
    /*----------------------------网络设备相关代码--------------------------------------*/
    /*1. 设备初始化调用,该函数在注册成功后会调用一次,可以编写网卡初始化相关代码*/
    static int tiny4412_ndo_init(struct net_device * dev)
    {
    /*1. ENC28J60网卡初始化*/
    u8 stat=ENC28J60_Init(ENC28J60_MacAddr);
    if(stat)
    {
    网卡初始化失败!\r\n");
    }
    /*2. 获取中断编号*/
    ENC28J60_IRQ=gpio_to_irq(ENC28J60_IRQ_NUMBER);
    printk("ENC28J60_IRQ=%d\n",ENC28J60_IRQ);
    $ j' D" b2 Z# T# W) L5 ?, @+ W
    /*3. 初始化工作队列*/
    INIT_WORK(&work_list,workqueue_function);
    , g& {' @) n% h2 k
    /*4. 注册中断*/
    if(request_irq(ENC28J60_IRQ,ENC28J60_irq_handler,IRQ_TYPE_EDGE_FALLING,"ENC28J60_NET",NULL)!=0)
    {
    printk("ENC28J60中断注册失败!\n");
    }
    0 K0 R' B" T: X6 ^: j2 p/ |  ]
    使用定时器100ms*/
    timer_date.expires=jiffies+usecs_to_jiffies(100);
    timer_date.functinotallow=timer_function;

    & k. X: k8 B9 [# Z
    初始化定时器*/
    init_timer(&timer_date);

    $ i3 o% p% M1 d& e9 G
    添加定时器到内核并启动*/
    add_timer(&timer_date);

    0 ^& l# M% A; Y/ p
    printk("网络设备初始化!\n");
    return 0;
    }

    : T- f8 g- R- r0 |# Z$ u" D
    /*2. 打开网络接口,对应ifconfig up命令,编写网络设备硬件初始化的相关代码*/
    static inttiny4412_ndo_open(struct net_device *dev)
    {
    printk("网络设备打开成功!\n");
    return 0;
    }
    - |' u! f3 }+ p. ]: @8 t: X
    /*3. 关闭网络设备,对应ifconfig down命令,实现的内容与OPEN相反*/
    static inttiny4412_ndo_stop(struct net_device *dev)
    {
    printk("网络设备关闭成功!\n");
    return 0;
    }

    7 |5 H$ D7 ^1 G& v" C: y  u
    /*4. 启动网络数据包传输的方法*/
    static netdev_tx_ttiny4412_ndo_start_xmit(struct sk_buff *skb,struct net_device *dev)
    {
    int len;
    char *data, shortpkt[ETH_ZLEN];
    5 _* X$ P6 X! N" J+ u
    获得有效数据指针和长度 */
    获取将要发送出去的数据指针*/
    获取将要发送出去的数据长度*/
    4 s$ @  [  V' b" A" _
    if(len < ETH_ZLEN)
    {
    如果帧长小于以太网帧最小长度,补0 */
    memset(shortpkt,0,ETH_ZLEN);
    memcpy(shortpkt,skb->data,skb->len);
    len = ETH_ZLEN;
    data = shortpkt;
    }
    ! Q- E  W' R/ R) e$ x9 s0 F7 G
    /*记录发送时间戳*/
    dev->trans_start = jiffies;

    % |- g& a$ _2 h. I0 ?
    设置硬件寄存器让硬件将数据发出去 */
    ENC28J60_Packet_Send(len,data);

    : _+ _  n$ @+ T; h4 ~+ K+ S
    /*释放skb*/
    dev_kfree_skb(skb);
    0 H& O5 V3 e7 r" X! D) C5 Q
    /*更新统计信息:记录发送的包数量*/
    dev->stats.tx_packets++;
    9 M! {& E! f! q0 W
    /*更新统计信息:记录发送的字节数量*/
    dev->stats.tx_bytes += skb->len;

    & F( @  r  Q/ p/ C, \; A( W
    return NETDEV_TX_OK; //这是个枚举状态。
    }
      u* G8 S. j: D( |* |
    /*5. 设置MAC地址,对应的命令: ifconfig eth888 hw ether 00:AA:BB:CCD: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;
    }
    4 G% U1 A/ x" P% d/ z" V! H7 i
    /*网络设备虚拟文件操作集合*/
    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,
    };
    : Y  g8 M) n7 E7 z4 f& R) ?
    /*--------------------------驱动框架------------------------------------*/
    static int __init Net_test_init(void)
    {
    /*1. 分配及初始化net_device对象,参数:私有数据大小(单位:字节数)*/
    tiny4412_net=alloc_etherdev(sizeof(struct net_device));
    & q0 S. S0 c3 e; |& M- ^# ^( m9 l
    /*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; //看门狗超时时间
    ; f+ U9 m' {. e: M- H2 W+ Y
    /*3. 随机生成MAC地址*/
    eth_hw_addr_random(tiny4412_net);
    随机生成的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];
    ; d9 z  X8 h* p1 q- H4 E  A. Y
    /*注册网络设备*/
    register_netdev(tiny4412_net);
    printk("网络设备注册成功!\n");
    return 0;
    }

    ! C8 x3 N9 C0 B$ ?
    static void __exit Net_test_exit(void)
    {
    //注销网络设备
    unregister_netdev(tiny4412_net);
    free_netdev(tiny4412_net);
    % C# ^4 J( J6 D* Y/ i4 e
    /*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);

    * D$ d' }+ ^2 d" p- M
    /*2. 释放中断号*/
    free_irq(ENC28J60_IRQ,NULL);

    ; b5 }6 f+ c5 D/ P9 J  o0 B! _* U
    /*3. 停止定时器*/
    del_timer_sync(&timer_date);
    . Z, C& G% O! z  N
    /*4. 清除工作*/
    cancel_work_sync(&work_list);
    4 X; V8 k: @; A* n0 D' H) m
    printk("网络设备注销成功!\n");
    }
      R1 \: ^" R; Y
    module_init(Net_test_init);
    module_exit(Net_test_exit);
    MODULE_AUTHOR("xiaolong");
    MODULE_LICENSE("GPL");

    - ~& `/ ]2 a/ h/ N( \
    # b+ ?, r+ P0 h( E0 g6 n1 y# v7 ^
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|Archiver|手机版|深圳市光明谷科技有限公司|光明谷商城|Sunshine Silicon Corpporation ( 粤ICP备14060730号|Sitemap

    GMT+8, 2024-3-29 17:54 , Processed in 0.154624 second(s), 37 queries .

    Powered by Discuz! X3.2 Licensed

    © 2001-2013 Comsenz Inc.

    快速回复 返回顶部 返回列表