精华内容
下载资源
问答
  • 嵌入式Linux字符设备驱动LED驱动编写

    千次阅读 2016-06-01 21:50:16
    嵌入式Linux字符设备驱动LED驱动编写 标签: linux内核 2015-04-30 14:41 105人阅读 评论(0) 收藏 举报  分类:   Linux开发 嵌入式Linux字符设备驱动LED驱动编写嵌入式Linux字符...

    嵌入式Linux字符设备驱动LED驱动编写

    标签: linux内核
      105人阅读  评论(0)  收藏  举报
      分类:
     

    嵌入式Linux字符设备驱动LED驱动编写嵌入式Linux字符设备驱动开发总结--LED驱动

    作者:英贝得教育02就业班   杨广东

     

    设备驱动程序是集成在内核中,处理硬件或操作硬件控制器的软件。 字符设备还是块设备都为内核 提供相同的调用接口,所以内核能以相同的方式处理不同的设备。字符设备提供给应用程序流控制接口有:open/read/write/ioctl/ ……,添加一个字符设备驱动程序,实际上是给上述操作添加对应的代码

    模块的概念:linux内核模块是一种可以被内核动态的加载和卸载的可执行的二进制代码。通过内核模块可以扩展内核的功能,通常内核模块被用于设备驱动,文件系统等。如果没有内核模块,需要向内核添加功能就需要修改代码,重新编译内核,安装新内核等,不仅繁琐而且容易出错,不易于调试。Linux内核是一个整体结构,各种功能结合在一起,linux内核的开发者设计了内核模块机制,从代码的角度看,内核模块是一组可以完成某种功能的函数集合。从执行的角度看,内核模块是一个已经编译但没有连接的程序。内核模块类似应用程序,但是与普通应用程序有所不同,区别在与:

    运行环境不同

    功能定位不同

    函数调用方式不同

     

    Linux设备驱动程序与外界的接口可以分为如下3个部分:

    1.驱动程序与内核操作系统内核的接口:通过数据结构:file_operation来完成的

    2.驱动程序与系统引导内核的接口:利用驱动程序对设备进行初始化

    3.驱动程序与设备的接口:描述了驱动程序如何与设备进行交互

     

    一.字符驱动的具体流程:

     

    所需要的头文件和宏定义:

    #include <linux/module.h>

    #include <linux/kernel.h>

    #include <linux/fs.h>

    #include <linux/cdev.h>

    #include <linux/types.h>

    #include <linux/init.h>

    #include <linux/delay.h>

    #include <asm/irq.h>

    #include <mach/regs-gpio.h>

    #include <asm/io.h>

    #define LED_MAJOR 100 //主设备号

    #define LED_SECOND 5 //次设备号

    #define IOCTL_LED_ON 0

    #define IOCTL_LED_OFF 1

    struct cdev led_cdev0; //定义cdev结构体,内核是通过这个结构体来访问驱 //动程序的

    dev_t led_t = 0; //无整型32位

     

    1.定义设备结构体变量:

     

    因为内核是通过cdev结构体来访问驱动的,所以要定义结构体:

    struct cdev my_cdve0;

    定义好描述字符IO设备的结构体后,就用该结构体来定义一个变量,在内核中就用该变量来代表我们的字符IO设备,在这里就代表的是LED灯。

     

    2.定义设备的操作接口和编写接口操作函数:

     

    每个设备都对应一些操作,应用程序及是通过这些接口操作函数来使用驱动程序完成对设备的控制,设备的操作接口定义如下:

    Struct file_operation led_ops = {

    .ownr = THIS_MODULE; //一个宏

    .open = myled_open,

    ioctl = myled_ioctl,

    .release = myled_close,

    };

     

    相应函数的实现:

    static int leds_open(struct inode *inode, struct file *file)

    {

    int i;

    i = (1 << 10) + (1 << 12) + (1 << 16) + (1 << 20);

    writel(i,S3C2410_GPBCON); //S3C2410_GPBCON在头文件<mach/regs-gpio.h>

    //中已定义

    i = 0x0; //灯全亮

    writel(i,S3C2410_GPBUP); //在内核中读写函数用writel()和readl();

    i = 0xfffffffe; //灯灭

    writel(i,S3C2410_GPBDAT);

    return 0;

    }

     

    tatic int leds_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)

    { //内核中的参数 //应用程序中传过来的参数

    switch(cmd)

    {

    case IOCTL_LED_ON :

    writel(0xffffffde,S3C2410_GPBDAT);

    break;

    case IOCTL_LED_ONa :

    writel(0xffffffbe,S3C2410_GPBDAT);

    break;

    case IOCTL_LED_ONb:

    writel(0xfffffefe,S3C2410_GPBDAT);

    break;

    case IOCTL_LED_ONc:

    writel((~(0x01<<10)&0xfffffffe),S3C2410_GPBDAT);

    break;

    case IOCTL_LED_ONd:

    writel(0x0,S3C2410_GPBDAT);

    break;

    case IOCTL_LED_OFF:

    writel(0xfffffffe,S3C2410_GPBDAT);

    break;

    default:

    break;

    }

    return 0;

    }

     

    一个LED灯对应一个字符设备驱动;

    tatic int leds_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)

    { //内核中的参数 //应用程序中传过来的参数

    Dev_t cur_dev_no = inode->i_rdev; //主设备号

    Dev_t minordev = MINOR(inode->i_rdev); //次设备号

    If(minordev == 0) //表示是第一个灯的驱动,后面以此类推

    {

    int i = 0;

    switch(cmd)

    {

    case IOCTL_LED_ON:

    writel((~(0x01)<<5)&&0xfffffffe,S3C2410_GPBDAT);

    Break;

    case IOCTL_LED_OFF:

    writel(0xfffffffe,S3C2410_GPBDAT);

    break;

    default:

    break;

    }

    }

    If(minordev == 1) //表示是第二个灯的驱动,后面以此类推

    {

    ……

    }

    return 0;

    }

     

    3.字符设备驱动模块的初始化和退出函数:

     

    字符驱动模块是一个linux模块,所以要遵循linux模块的框架。首先查看自己定义的主设备号在内核中是否已被占用,初始化字符设备结构体变量cdev,向内核注册该字符IO设备驱动,分别通过调用cdev_init(),cdev_add()函数来实现,对于字符驱动模块初始化函数定义如下:

     

    Static int__init myled_init(void)

    {

    led_t = MKDEV(LED_MAJOR,LED_SECOND);

    re = register_chrdev_region(LED_MAJOR,1,"my"); //查看此设备号是否已被占用返回值等于0分配成功

    if (re < 0)

    {

    printk(" can't register major number\n");

    return -1;

    }

    cdev_init(&led_cdev,&leds_fops); //初始化cdev结构体

    r = cdev_add(&led_cdev,led_t,4); //注册到内核中 4 为要共用一个设备驱动的次设备号

    if(r < 0)

    {

    printk("add wrong\n");

    return -1;

    }

    return 0;

    }

     

    字符模块退出函数组要就是删除内核中的字符设备:

    Static void__exit myled_exit(void)

    {

    Cdev_del(&led_cdev);

    Printk("myled exit now\n");

    }

     

    完成了模块的初始化和退出函数后,最后向内核申明myled_init和lyled_exit函数以及申明模块license:

    module_init(leds_init); 初始化字符驱动设备

    module_exit(leds_exit);

    MODULE_LICENSE("GPL"); 基于GPL库开发的软件在用到GPL库的这些代码必须开源

     

    二.测试程序:

     

    #include <stdio.h>

    #include <stdlib.h>

    #include <unistd.h>

    #include <sys/ioctl.h>

     

    #define IOCTL_LED_ON 0

    #define IOCTL_LED_OFF 1

     

    int main(int argc, char **argv)

    {

    int fd = 0;

    fd = open(argv[2],0);

    if(strcmp(argv[1],"on")==0)

    {

    ioctl(fd,IOCTL_LED_ON);

    }

    else if(strcmp(argv[1],"off")==0)

    {

    ioctl(fd,IOCTL_LED_OFF);

    }

    return 0;

    }

     

    如果是一个LED灯一个驱动,测试程序中的fd = open("/dev/ledS0",0)应改为fd = open(argv[2],0);用命令行的模式:

    ./LES_TEST on /dev/ledS0 这是第二个LED驱动

    ./LES_TEST on /dev/ledS1这是第二个LED驱动

    三.Makefile:

     

    obj-m:=dri_led.o

    KDIR = /home/kernel/linux-2.6.30.9 //将要使用的内核

     

    all:

    make -C $(KDIR) SUBDIRS=$(shell pwd) modules

    arm-linux-gcc -o led_test led_test.c //对led_test.c进行交叉编译,否则在开发板上不能运行

     

    clean:

    @rm -rf dri_led *.o

     

     

    四.字符IO设备驱动程序测试:

     

    Leds0驱动,测试程序,makefile写好之后,把linux-2.6.30.9内核重新编译,通过超级终端把生成的zImage下载到开发板kernel中区(开发板起来之后,6-4-Y-1-4),把在linux下编译好的测试程序的二进制文件和生成的KO文件下载到开发板中去(通过超级终端的菜单栏中的“传送”-“发送文件”),这时开发板中已经有所需要用到的文件。

    1.往内核添加驱动模块:

    在终端shell下通过执行 Insmod myled.ko

     

    2.改变环境变量,如下:

    在终端shell下通过执行chmod来改变环境变量

    chmod +x ledtest(把测试程序改成可执行的二进制文件)

     

    3.创建设备文件节点(一个LED灯一个驱动):

    在终端shell下通过执行mknod命令创建文件节点

    增加节点:mknod /dev/ledS0 c 100 0

    增加节点:mknod /dev/ledS1 c 100 1

    增加节点:mknod /dev/ledS2 c 100 2

    增加节点:mknod /dev/ledS3 c 100 3

     

    /dev/ledS0:字符设备名,包括路径

    C :设备类型,c表示字符设备

    100 :主设备号

    0 :次设备号

     

    4.在shell下执行led_test测试应用程序:

    ./LES_TEST on /dev/ledS0

     

    五:总结

     

    应用程序通过调用API:open(),ioctl()函数来访问内核,ioctl函数传过来的设备号,到内核中指针数组找相对应主设备号的,通过这个设备号找到该设备的结构体然后调用相应的函数以实现相应的功能:

     

    应用层:

    fd = open("/dev/ledS0",0);

    ioctl(fd,IOCTL_LED_OFF);

     

    内核层:

    标准输入

    标准输出

    出错

     

     

    &cdev

     

     

    0

    1

    2

     

     

    100 //主设备号与cdev结构体的首地址

    Cdev 结构体

    Struct file_operation*ops

    Struct moduleowner

    Dev_t dev

    Struct list_head list

     

     

     

     

     

    *open

    *write

    *ioctl

    *read

     

     

     

    Struct file_opertion*ops:(对应着相应的实现函数)

     

     

     

     

     

     

    六.配置menucofig

     

    1.把要加到内核中的驱动文件拷贝到drivers/char目录下

    2.修改kconfig文件添加如下代码:

    config HT_LED

    Bool "LED"

    default y

    ---help---

    This is my first drive -LED!

    3.修改makefile添加如下代码:

    obj-$(CONFIG_HT_LED) +=dri_led.o

     

     

    配置成功,重新编译生成zImage

    展开全文
  • Linux的LCD驱动

    千次阅读 2019-01-13 13:13:35
     Linux的源码中本身已经抽象出了LCD驱动的公共部分代码——drivers/video/fbmem.c,对于驱动开发人员来讲,只需要理解这部分的代码并会调用其提供的接口即可。驱动开发人员需要做的就是针对具体的SOC和LCD,设置...

    前言

            Linux的源码中本身已经抽象出了LCD驱动的公共部分代码——drivers/video/fbmem.c,对于驱动开发人员来讲,只需要理解这部分的代码并会调用其提供的接口即可。驱动开发人员需要做的就是针对具体的SOC和LCD,设置对应的LCD参数和寄存器值即可。

           至于fbmem.c的流程已经有很多文章介绍过了,我这里就不具体介绍了,可以参考一下这篇文章:Linux Framebuffer驱动剖析之二—驱动框架、接口实现和使用。下面我就具体介绍一下怎么针对具体的SOC和LCD进行编程。

    正文

    这里直接给出一个LCD驱动程序编写的流程吧:

    1. 分配 一个fb_info结构体:framebuffer_alloc
    
    2. 设置
    2.1 设置固定的参数
    2.2 设置可变的参数
    2.3 设置操作函数:和我们自己的fb_ops联系起来
    2.4 其他设置:比如设置调色板
    
    3. 硬件相关的设置
    3.1 配置GPIO用于LCD
    3.2 根据LCD手册设置LCD控制器,比如VCLK的频率等
    3.3 分配显存(framebuffer),并把地址告诉LCD控制器
    
    4. 注册:register_framebuffer()

    根据上面的步骤,下面我们直接给出代码再来解释:

    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/errno.h>
    #include <linux/string.h>
    #include <linux/mm.h>
    #include <linux/slab.h>
    #include <linux/delay.h>
    #include <linux/fb.h>
    #include <linux/init.h>
    #include <linux/dma-mapping.h>
    #include <linux/interrupt.h>
    #include <linux/workqueue.h>
    #include <linux/wait.h>
    #include <linux/platform_device.h>
    #include <linux/clk.h>
    
    #include <asm/io.h>
    #include <asm/uaccess.h>
    #include <asm/div64.h>
    
    #include <asm/mach/map.h>
    #include <asm/arch/regs-lcd.h>
    #include <asm/arch/regs-gpio.h>
    #include <asm/arch/fb.h>
    
    static struct fb_info *s3c_lcd;
    
    static volatile unsigned long *gpbcon;
    static volatile unsigned long *gpbdat;
    static volatile unsigned long *gpccon;
    static volatile unsigned long *gpdcon;
    static volatile unsigned long *gpgcon;
    
    struct lcd_regs {
    	unsigned long lcdcon1;
    	unsigned long lcdcon2;
    	unsigned long lcdcon3;
    	unsigned long lcdcon4;
    	unsigned long lcdcon5;
    	unsigned long lcdsaddr1;
    	unsigned long lcdsaddr2;
    	unsigned long lcdsaddr3;
    	unsigned long redlut;
    	unsigned long greenlut;
    	unsigned long bluelut;
    	unsigned long reserved[9];
    	unsigned long dithmode;
    	unsigned long tpal;
    	unsigned long lcdintpnd;
    	unsigned long lcdsrcpnd;
    	unsigned long lcdintmsk;
    	unsigned long lpcsel;
    };
    
    struct lcd_regs *lcd_regs;
    static u32 pseudo_palette[16];
    
    static inline unsigned int chan_to_field(unsigned int chan, const struct fb_bitfield *bf)
    {
    	chan &= 0xffff;
    	chan >>= 16 - bf->length; /*对应到s3c_lcd->var.red.length*/
    	return chan << bf->offset; /*对应到s3c_lcd->var.red.offset*/
    }
    
    static int s3c_lcdfb_setcolreg(unsigned int regno, unsigned int red,
    			     unsigned int green, unsigned int blue,
    			     unsigned int transp, struct fb_info *info)
    {
    	unsigned int val;
    	
    	if (regno > 16)
    		return 1;
    
    	val  = chan_to_field(red, &info->var.red);
    	val |= chan_to_field(green, &info->var.green);
    	val |= chan_to_field(blue, &info->var.blue);
    
    	pseudo_palette[regno] = val;
    	return 0;
    }
    
    static struct fb_ops s3c_fb_ops = {
    	.owner		= THIS_MODULE,
    	.fb_setcolreg	= s3c_lcdfb_setcolreg,
    	.fb_fillrect	= cfb_fillrect,
    	.fb_copyarea	= cfb_copyarea,
    	.fb_imageblit	= cfb_imageblit,
    };
    
    static void lcd_init(void)
    {
    	/*1. 分配 一个fb_info结构体*/
    	s3c_lcd = framebuffer_alloc(0, NULL);
    	if (!s3c_lcd)
    		return -ENOMEM;
    	printk("framebuffer alloc success\n");
    	
    	/*2. 设置*/
    	/*2.1 设置固定的参数*/
    	strcpy(s3c_lcd->fix.id, "mylcd");
    	s3c_lcd->fix.smem_len = 480 * 272 * 16 / 8; /*RGB565*/
    	s3c_lcd->fix.type     = FB_TYPE_PACKED_PIXELS;
    	s3c_lcd->fix.visual   = FB_VISUAL_TRUECOLOR;
    	s3c_lcd->fix.line_length = 480 * 2; /*一开始写成了320,造成了Segmentation fault*/
    	
    	/*2.2 设置可变的参数*/
    	s3c_lcd->var.xres = 480;
    	s3c_lcd->var.yres = 272;
    	s3c_lcd->var.xres_virtual = 480;
    	s3c_lcd->var.yres_virtual = 272;
    	s3c_lcd->var.bits_per_pixel = 16;
    
    	/*RGB565*/
    	s3c_lcd->var.red.offset   = 11;
    	s3c_lcd->var.red.length   = 5;
    
    	s3c_lcd->var.green.offset = 5;
    	s3c_lcd->var.green.length = 6;
    
    	s3c_lcd->var.blue.offset  = 0;
    	s3c_lcd->var.blue.length  = 5;
    
    	s3c_lcd->var.activate = FB_ACTIVATE_NOW;
    	
    	
    	/*2.3 设置操作函数*/
    	s3c_lcd->fbops = &s3c_fb_ops;
    	
    	/*2.4 其他设置*/
    	s3c_lcd->pseudo_palette = pseudo_palette;
    	//s3c_lcd->screen_base = ; /*显存的虚拟地址*/
    	s3c_lcd->screen_size = 480 * 272 * 16 / 8;
    	printk("start ot ioremap\n");
    	/*3. 硬件相关的设置*/
    	/*3.1 配置GPIO用于LCD*/
    	gpbcon = ioremap(0x56000010, 8);
    	gpbdat = gpbcon + 1;
    
    	gpccon = ioremap(0x56000020, 4);
    	gpdcon = ioremap(0x56000030, 4);
    	gpgcon = ioremap(0x56000060, 4);
    
    	*gpdcon = 0xaaaaaaaa; /*设置为output : 01 = Output*/
    	*gpccon = 0xaaaaaaaa;
    
    	*gpbcon &= ~(3<<0);
    	*gpbcon |= 1; /*output*/
    	*gpbdat &= ~1; /*输出低电平,关闭背光灯*/
    
    	*gpgcon &= ~(3<<8);
    	*gpgcon |= (3<<8);   //GPG4  : 11 = LCD_PWRDN
    
    	/*3.2 根据LCD手册设置LCD控制器,比如VCLK的频率等*/
    	lcd_regs = ioremap(0X4D000000, sizeof(struct lcd_regs));
    	printk("after ioremap the register\n");
    	/* bit[17:8] :    VCLK = HCLK / [(CLKVAL+1) x 2]
    	  *     (10MHz)100ns  = 100MHz / [(CLKVAL+1) x 2]
    	  *                 CLKVAL = 4
    	  * bit[6:5]  : 11 = TFT LCD panel
    	  * bit[4:1]  : 1100 = 16 bpp for TFT
    	  * bit[0]     : 0 = Disable the video output and the LCD control signal
    	  */
    	lcd_regs->lcdcon1 = (4<<8) | (3<<5) | (0xc<<1);
    
    	/*垂直方向的时间参数
    	  *bit[31:24] : VBPD, VSYNC之后过多长时间才能发出第一行数据
    	  *bit[23:14] : LINEVAL, 多少行数据
    	  *bit[13:6] : VFPD, 发出最后一行数据后,过多久发出VSYNC
    	  *bit[5:0] : VSPW, VSYNC的脉冲宽度
    	  */
    	lcd_regs->lcdcon2 = (1<<24) | (271<<14) | (1<<6) | (9);
    
    	
    	/*水平方向的时间参数
    	  *bit[25:19] : HBPD, HSYNC之后过多长时间才能发出第一个像素数据
    	  *bit[18:8] : HOZVAL, 多少列数据
    	  *bit[7:0] : HFPD, 发出最后一个像素后,过多久发出HSYNC
    	  *LCDCON4的bit[7:0] : HSPW, HSYNC的脉冲宽度
    	  */
    	lcd_regs->lcdcon3 = (1<<19) | (479<<8) | (1);
    	lcd_regs->lcdcon4 = (40);
    
    	/*信号的极性
            *bit[11] : 1 = 5:6:5 format
            *bit[10] : 0 = 低电平有效
            *bit[9]   : 1 = HSYNC信号要反转,即低电平有效
            *bit[8]   : 1 = VSYNC信号要反转,即低电平有效
            *bit[3]   : 0 = PWREN输出0
            *bit[1]   : 0 = BSWP
            *bit[0]   : 1 = HWSWP  2440芯片手册P413
    	  */
    	lcd_regs->lcdcon5 = (1<<11) | (1<<9) | (1<<8) | (1<<0);
    
    	/*3.3 分配显存(framebuffer),并把地址告诉LCD控制器*/
    	s3c_lcd->screen_base = dma_alloc_writecombine(NULL, s3c_lcd->fix.smem_len, &s3c_lcd->fix.smem_start, GFP_KERNEL);
    	lcd_regs->lcdsaddr1 = (s3c_lcd->fix.smem_start >> 1) & ~(3<<30);
    	lcd_regs->lcdsaddr2 = ((s3c_lcd->fix.smem_start + s3c_lcd->fix.smem_len)>>1) & (0x1fffff);
    	lcd_regs->lcdsaddr3 = 480 * 16 / 16; /* 一行的长度(单位是:2字节)*/
    	
    	//s3c_lcd->fix.smem_start = xxx;
    
    	/* 启动LCD */
    	lcd_regs->lcdcon5 |= (1<<3); /*使能LCD本身*/
    	lcd_regs->lcdcon1 |= (1<<0); /*使能LCD控制器*/
    	*gpbdat |= 1; /* 输出高电平,使能背光 */
    	printk("after set the lcdconx\n");
    	/*4. 注册*/
    	register_framebuffer(s3c_lcd);
    
    	return 0;
    }
    
    static void lcd_exit(void)
    {
    	unregister_framebuffer(s3c_lcd);
    	lcd_regs->lcdcon1 &= ~(1<<0); /*关掉LCD本身*/
    	*gpbdat &= ~1; /* 关掉背光 */
    	dma_free_writecombine(NULL , s3c_lcd->fix.smem_len, s3c_lcd->screen_base, s3c_lcd->fix.smem_start);
    	iounmap(lcd_regs);
    	iounmap(gpccon);
    	iounmap(gpdcon);
    	iounmap(gpgcon);
    	iounmap(gpbcon);
    	framebuffer_release(s3c_lcd);
    }
    
    module_init(lcd_init);
    module_exit(lcd_exit);
    MODULE_LICENSE("GPL");

    1、分配 一个fb_info结构体

    直接就调用framebuffer_alloc()函数来分配一个struct fb_info结构体就好了,没什么好讲的

    2、设置LCD的的固定和可变参数

            这两个结构体的参数,根据注释和已有的LCD驱动代码,我们是可以一个一个进行填充的。这里重点讲一下s3c_lcd->fix.smem_len(framebuffer大小),根据我们的4.3寸的LCD手册,像素是480*272的,且像素的格式是RGB565,,也就是16bit,所以framebuffer的大小就应该是480*272*16/8(单位:byte)。同理,s3c_lcd->fix.line_length代表每列的长度,单位是byte,因为像素为480*272,每个像素的大小为2byte,所以每列长度为480*2(单位:byte)。

    固定参数结构体:
    struct fb_fix_screeninfo {
    	char id[16];			/* identification string eg "TT Builtin" */
    	unsigned long smem_start;	/* Start of frame buffer mem */
    					/* (physical address) */
    	__u32 smem_len;			/* Length of frame buffer mem */
    	__u32 type;			/* see FB_TYPE_*		*/
    	__u32 type_aux;			/* Interleave for interleaved Planes */
    	__u32 visual;			/* see FB_VISUAL_*		*/ 
    	__u16 xpanstep;			/* zero if no hardware panning  */
    	__u16 ypanstep;			/* zero if no hardware panning  */
    	__u16 ywrapstep;		/* zero if no hardware ywrap    */
    	__u32 line_length;		/* length of a line in bytes    */
    	unsigned long mmio_start;	/* Start of Memory Mapped I/O   */
    					/* (physical address) */
    	__u32 mmio_len;			/* Length of Memory Mapped I/O  */
    	__u32 accel;			/* Indicate to driver which	*/
    					/*  specific chip/card we have	*/
    	__u16 reserved[3];		/* Reserved for future compatibility */
    };

    s3c_lcd->var.bits_per_pixel顾名思义就是每个像素有多少bit,我们这里是RGB565,也就是16 bit。假如每个像素格式如下:

    RRRRRGGGGGGBBBBB

     很明显,R的offset为从最低位(右起)偏移11位,占5bit,所以可变参数的设置就为:

    s3c_lcd->var.red.offset   = 11;
    s3c_lcd->var.red.length   = 5;
    

     同理,G和B的设置也就很容易了。

    可变参数结构体:
    struct fb_var_screeninfo {
    	__u32 xres;			/* visible resolution		*/
    	__u32 yres;
    	__u32 xres_virtual;		/* virtual resolution		*/
    	__u32 yres_virtual;
    	__u32 xoffset;			/* offset from virtual to visible */
    	__u32 yoffset;			/* resolution			*/
    
    	__u32 bits_per_pixel;		/* guess what			*/
    	__u32 grayscale;		/* != 0 Graylevels instead of colors */
    
    	struct fb_bitfield red;		/* bitfield in fb mem if true color, */
    	struct fb_bitfield green;	/* else only length is significant */
    	struct fb_bitfield blue;
    	struct fb_bitfield transp;	/* transparency			*/	
    
    	__u32 nonstd;			/* != 0 Non standard pixel format */
    
    	__u32 activate;			/* see FB_ACTIVATE_*		*/
    
    	__u32 height;			/* height of picture in mm    */
    	__u32 width;			/* width of picture in mm     */
    
    	__u32 accel_flags;		/* (OBSOLETE) see fb_info.flags */
    
    	/* Timing: All values in pixclocks, except pixclock (of course) */
    	__u32 pixclock;			/* pixel clock in ps (pico seconds) */
    	__u32 left_margin;		/* time from sync to picture	*/
    	__u32 right_margin;		/* time from picture to sync	*/
    	__u32 upper_margin;		/* time from sync to picture	*/
    	__u32 lower_margin;
    	__u32 hsync_len;		/* length of horizontal sync	*/
    	__u32 vsync_len;		/* length of vertical sync	*/
    	__u32 sync;			/* see FB_SYNC_*		*/
    	__u32 vmode;			/* see FB_VMODE_*		*/
    	__u32 rotate;			/* angle we rotate counter clockwise */
    	__u32 reserved[5];		/* Reserved for future compatibility */
    };

    3、操作函数的设置

    我是直接参考现有的LCD驱动:drivers/video/s3c2410fb.c。唯一需要我们自己改动的就是调色板的函数,后面再说

    4、 假的调色板

           为什么这里要说是假的调色板呢?因为我这里的LCD驱动程序实际上并没用到调色板这个东西,但是为了兼容,也模仿程序加了一个。至于什么是调色板,可以参考一下我以前的文章:S3C2440芯片的LCD控制器,里面有一小节降到了调色板的概念。

    看一下设置调色板的函数:

    static int s3c_lcdfb_setcolreg(unsigned int regno, unsigned int red,
    			     unsigned int green, unsigned int blue,
    			     unsigned int transp, struct fb_info *info)
    {
    	unsigned int val;
    	
    	if (regno > 16)
    		return 1;
    
    	val  = chan_to_field(red, &info->var.red);
    	val |= chan_to_field(green, &info->var.green);
    	val |= chan_to_field(blue, &info->var.blue);
    
    	pseudo_palette[regno] = val;
    	return 0;
    }

           函数的参数中有红蓝绿3种颜色,然后经过chan_to_field()函数的“调色”后,赋值给val,然后就放到“调色板“pseudo_palette中。我们再看一下chan_to_field函数中是怎么”调色“的。

    static inline unsigned int chan_to_field(unsigned int chan, const struct fb_bitfield *bf)
    {
    	chan &= 0xffff;
    	chan >>= 16 - bf->length; /*对应到s3c_lcd->var.red.length*/
    	return chan << bf->offset; /*对应到s3c_lcd->var.red.offset*/
    }

           其实也很简单,一个像素点的格式是RGB565,在设置fb_info结构体的可变参数时,我们曾经设置过RGB 3色的长度和偏移,这里就是从chan(表示R、G或者B的一种)得到一个像素点中R或者G或者B的值。最后将RGB拼凑成val放到pseudo_palette中。

    5、配置GPIO用于LCD

           每个板子LCD相关的GPIO都不一样,所以设置肯定各不相同,不过道理是一样的,根据硬件原理图,将LCD所用到的GPIO都ioremap一下,然后就根据芯片手册赋相关的值,使其成为输出还是输入引脚。

    6、配置LCD控制器

           一般来说,开发板是通过LCD控制器来控制LCD扫描的频率、垂直方向和水平方向的各种时间参数,所以我们还需要根据芯片手册来设置LCD控制器的各个寄存器的值,达到我们的要求。由于LCD控制器有很多寄存器,这里我就不一一列举了,下面示范一下怎么通过读芯片手册,来设置LCD控制器的寄存器。(下面的设置是根据3.5寸的LCD屏幕手册来设置的,上面的代码参数设置是4.3寸的屏幕,所以数值会有差异

    下面看一下寄存器LCDCON2(垂直方向参数)的各个bit的设置

    S3C2440芯片手册中的时序图:

     

    下面是LCD手册展示的时序图: 

     LCD参考手册中的时序的具体参考值如下:

    所以结合LCD参考手册中的时序图和具体的时序值,我们可以反推出LCDCON2中各个bit的值:

    VBPD + 1 = T0 - T2 - T0 = 327 - 322 - 1 = 4,所以 VBPD = 3

    LINEVAL + 1 = T5 = 320,所以LINEVAL = 319

    VFPD + 1 = T2 - T5 = 2,所以VFPD = 1

    VSPW + 1 = T1 = 1,所以VSPW = 0

     ******************************************************************************************************************

    下面看一下设置水平方向参数的寄存器LCDCON3和LCDCON4:

    下面是S3C2440芯片手册中的水平方向参数的时序图: 

    LCD手册中的水平参数的时序图:

    LCD手册中水平方向参数的具体值:

    所以结合LCD手册,我们可以得到LCDCON3寄存器各个bit的值:

    HBPD + 1 = T6 - T8 - T7 = 273 - 251 - 5 = 17,所以HBPD = 16

    HOZVAL + 1 = T11 = 240,所以HOZVAL = 239

    HFPD + 1 = T8 - T11 = 251 - 240 = 11,所以HFPD = 10

    HSPW + 1 = T7 = 5.所以HSPW = 4

    7、分配显存

           一般来说,配置高点的电子设备都有单独的显存来做显示的工作,不过我的开发板配置较低,需要从内存中直接划分一块出来做显存用。Linux已经为我们提供了一些接口函数,比如这里我们可以直接调用dma_alloc_writecombine()来分配一块由我们指定大小的内存,如果分配成功就会返回物理地址和虚拟地址的首地址。这个函数的作用可以参考一下这篇文章:、、dma_alloc_writecombine 和mmap函数。然后就是通过设置LCDADDRx寄存器,将显存的起始地址和大小告诉LCD控制器。

    8、注册framebuffer

    其实在Linux的整个LCD框架下编程,我们需要做的工作,也就是设置一些硬件相关的参数,这些工作都做完后,就用register_framebuffer()函数直接将设置好的fb_info结构体注册进系统。

    展开全文
  • linux按键驱动

    千次阅读 2016-03-22 20:39:39
    LINUX 按键驱动
    1 相关的数据结构和接口函数
    
    1.1 数据结构
    struct input_dev {
            void *private;                                //输入设备私有指针,一般指向用于描述设备驱动层的设备结构
            const char *name;                        //提供给用户的输入设备的名称
            const char *phys;                        //提供给编程者的设备节点的名称
            const char *uniq;                        //指定唯一的ID号,就像MAC地址一样
            struct input_id id;                        //输入设备标识ID,用于和事件处理层进行匹配
            unsigned long evbit[NBITS(EV_MAX)];                //位图,记录设备支持的事件类型
            unsigned long keybit[NBITS(KEY_MAX)];                //位图,记录设备支持的按键类型
            unsigned long relbit[NBITS(REL_MAX)];                //位图,记录设备支持的相对坐标
            unsigned long absbit[NBITS(ABS_MAX)];                //位图,记录设备支持的绝对坐标
            unsigned long mscbit[NBITS(MSC_MAX)];        //位图,记录设备支持的其他功能
            unsigned long ledbit[NBITS(LED_MAX)];                //位图,记录设备支持的指示灯
            unsigned long sndbit[NBITS(SND_MAX)];                //位图,记录设备支持的声音或警报
            unsigned long ffbit[NBITS(FF_MAX)];                //位图,记录设备支持的作用力功能
            unsigned long swbit[NBITS(SW_MAX)];                //位图,记录设备支持的开关功能
            unsigned int keycodemax;                //设备支持的最大按键值个数
            unsigned int keycodesize;                //每个按键的字节大小
            void *keycode;                                //指向按键池,即指向按键值数组首地址
            int (*setkeycode)(struct input_dev *dev, int scancode, int keycode);        //修改按键值
            int (*getkeycode)(struct input_dev *dev, int scancode, int *keycode);        //获取按键值
            struct ff_device *ff;                        //
            unsigned int repeat_key;                //支持重复按键
            struct timer_list timer;                //设置当有连击时的延时定时器
            int state;                //
            int sync;                //同步事件完成标识,为1说明事件同步完成
            int abs[ABS_MAX + 1];                //记录坐标的值
            int rep[REP_MAX + 1];                //记录重复按键的参数值
            unsigned long key[NBITS(KEY_MAX)];                //位图,按键的状态
            unsigned long led[NBITS(LED_MAX)];                //位图,led的状态
            unsigned long snd[NBITS(SND_MAX)];                //位图,声音的状态
            unsigned long sw[NBITS(SW_MAX)];                        //位图,开关的状态
            int absmax[ABS_MAX + 1];                                        //位图,记录坐标的最大值
            int absmin[ABS_MAX + 1];                                        //位图,记录坐标的最小值
            int absfuzz[ABS_MAX + 1];                                        //位图,记录坐标的分辨率
            int absflat[ABS_MAX + 1];                                        //位图,记录坐标的基准值
            int (*open)(struct input_dev *dev);                        //输入设备打开函数
            void (*close)(struct input_dev *dev);                        //输入设备关闭函数
            int (*flush)(struct input_dev *dev, struct file *file);        //输入设备断开后刷新函数
            int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);        //事件处理
            struct input_handle *grab;                //
            struct mutex mutex;                //用于open、close函数的连续访问互斥
            unsigned int users;                //
            struct class_device cdev;        //输入设备的类信息
            union {                                //设备结构体
                    struct device *parent;
            } dev;
            struct list_head        h_list;        //handle链表
            struct list_head        node;        //input_dev链表
    };
    这个结构是linux输入子系统的核心,通过这个结构定义子系统支持的事件,支持哪些按键。上报输入事件。
    这个结构很庞大,但是我们重点只需要关注下面几个成员
    const char *name; 这个是展示给用户的输入设备的名称
    const char *phys; 这个是设备节点的名称
    这两个名称通过cat /proc/bus/input/devices就可以查看的到。


    unsigned long evbit;是该设备支持的事件类型,在input.h里面定义了下面的类型
    #define EV_SYN   0x00   /* 能产生同步类事件 */
    #define EV_KEY   0x01   /*按键类*/
    #define EV_REL   0x02   /*相对位移类事件*/
    #define EV_ABS   0x03   /*绝对位移类事件*/
    #define EV_MSC   0x04   /*混杂事件*/
    #define EV_SW    0x05   /*开关类事件*/
    #define EV_LED   0x11   /*LED类事件*/
    #define EV_SND   0x12   /*输入声音类事件*/
    #define EV_REP   0x14   /* 能产生重复类事件 */
    #define EV_FF    0x15
    #define EV_PWR   0x16   /* 电源管理类事件 */
    #define EV_FF_STATUS  0x17


    对于按键就需要支持EV_KEY   ,如果需要长按按键能不断产生按键消息,就需要设置EV_REP 。
    定义这些宏是对应的位不是数值,例如我们要设置我们的驱动支持按键和重复按键,就需要这样写
    input_dev->evbit[0] = (1<<EV_KEY)|(1<<EV_REP );
    注意:如果我们设置了EV_REP,那么在驱动上报键按下和释放之间,每隔33毫秒,会发送一次按键。这个间隔时间是定义在input.c的input_register_device函数中的dev->rep[REP_PERIOD] = 33;这里的需要注意的是如果定义了EV_REP,那么我们的扫描间隔一定要少于33ms,否则可能释放了按键后还会收到按键的消息。例如我们设置的间隔时间是100毫秒,那么很可能释放了之后还会收到3个按键消息。
    另外对于重复键的设置,在我们的驱动中,扫描和上报按下和释放还是和原来的处理时一样的,重复按键的消息是有input子系统的core来完成的。


    unsigned long keybit:这个是设置我们需要支持的按键键值。我们可以用setbit来处理,例如set_bit(KEY_ENTER,  input->keybit);linux的键值定义在input.h头文件中。
    keybit通常是在初始化的时候设置的,但是因为某些原因我们需要动态的增加支持的按键,那么在调用了input_register_device之后,再调用setbit设置支持的按键也是没有问题的。但是一定要确保在调用input_register_device之前至少要设置了一个按键。


    void *private:这个是用户的私有数据,我们可以把我们自定义的一些控制结构、变量的地址保存用这个指针保存。


    在linux2.6.3之后,linux还定义了一个input_polled_dev的结构,这个数据结构支持轮询的方式。结构定义如下:
    struct input_polled_dev {
            void *private;
            
            void (*open)(struct input_polled_dev *dev);
            void (*close)(struct input_polled_dev *dev);
            void (*poll)(struct input_polled_dev *dev);
            unsigned int poll_interval; /* msec */
            unsigned int poll_interval_max; /* msec */
            unsigned int poll_interval_min; /* msec */
            
            struct input_dev *input;
            
            /* private: */
            struct delayed_work work;
    };
    这个结构我们重点关注下面几个成员
    void (*poll)(struct input_polled_dev *dev):轮询处理函数的指针,我们在这个函数中完成对按键的检测。
    unsigned int poll_interval;轮询的间隔时间,以毫秒为单位
    struct input_dev *input;这个就是上面介绍的input_dev结构。


    1.2重要的接口函数:
    struct input_dev *input_allocate_device(void);
    分配一个input_dev的结构,返回这个结构体的指针。


    void input_free_device(struct input_dev *dev);
    释放用input_allocate_device分配的结构。参数为input_dev的指针


    struct input_polled_dev *input_allocate_polled_device(void);
    分配一个input_polled_dev的结构,这个函数分配的input_polled_dev的时候,也同时分配了一个input_dev,并且将地址设置给input,所以我们调用完这个函数后不需要再调用*input_allocate_device分配。


    void input_free_polled_device(struct input_polled_dev *dev);
    释放input_allocate_polled_device分配的数据结构,参数为input_polled_dev的指针。


    int  input_register_device(struct input_dev * dev);
    注册一个input设备,参数为之前调用 input_allocate_device分配的结构。
    执行成功返回0 ,返回值小于0执行失败。


    void input_unregister_device(struct input_dev *dev);
    释放input_register_device注册的input设备。


    int input_register_polled_device(struct input_polled_dev *dev);
    注册是一个支持轮询的input设备,成功返回0,小于0执行失败


    void input_unregister_polled_device(struct input_polled_dev *dev);
    释放input_register_polled_device注册的设备。


    void input_report_key(struct input_dev *dev, unsigned int code, int value);
    上报按键 ,参数dev为之前注册的input_dev结构 ,code为按键的键值,value为1表示有键按下,0表示释放


    void input_sync(struct input_dev *dev);
    用于事件同步,它告知事件的接收者驱动已经发出了一个完整的报告。


    上面介绍的函数和结构实际上是linux input子系统。所以我们注册其他的Input设备也可以用到上面介绍的资料。


    2 示例代码:
    2.1 中断方式检测按键的例子
    首先我们注册一个平台设备,在probe函数中初始化按键接,注册input设备。当然我们也可以注册一个字符设备,在init函数中直接注册一个input设备也没有问题,这里不属于按键驱动的部分,不展开说明了。
    typedef struct
    {
            input_dev *input_keypd_dev ;
            struct work_struct work;
            .........
    }my_keypad;
    static int  my_key_probe(struct platform_device *pdev)
    {
            int i;
            int err =0;
            my_keypad *keypad_data;
            input_dev *keypd_dev ;
                    .....
            keypad_data= kzalloc(sizeof(my_keypad) ,
            GFP_KERNEL);
            if(!my_keypad )
            {
                    err  = -1 ; 
                    goto fail0;
            }
            keypd_dev = input_allocate_device();
            if (!keypd_dev) {
                    err  = -1 ; 
                    goto fail1;
            }
            
            keypd_dev->name = "my-keypad"; 
            keypd_dev->phys = "Keypad/input0";
            keypd_dev->id.bustype = BUS_HOST;
            keypd_dev->id.vendor = 0x0001;
            keypd_dev->id.product = 0x0001;
            keypd_dev->id.version = 0x0100;
            
            keypd_dev->evbit[0] = BIT_MASK(EV_KEY);
                 
                    .......
            set_bit(KEY_BACK, keypd_dev->keybit); 
            set_bit(KEY_MENU, keypd_dev->keybit);
                    .........
            keypad_data->input_keypd_dev = keypd_dev ;
            platform_set_drvdata(pdev, keypad_data);
            INIT_WORK(&(keypad_data->work), keypad_work_func);
            if (request_irq(KEYPAD_IRQNUM, keypad_irq_service, 0, "my_keypad",keypad_data)) 
            {
                    err = -EBUSY; 
                    goto fail2;
            }
            err = input_register_device(keypd_dev);
            if (err){
             
                    goto fail3;
            }
                    ............. 
            return 0;
    fail3: 
            free_irq(KEYPAD_IRQNUM, NULL);
    fail2: 
            input_free_device(keypd_dev);
            
    fail1:
            kfree(keypad_data);
    fail0:
            return err;
    }
    int __devexit my_key_remove(struct platform_device *pdev)
    {
            my_keypad *keypad_data;
            keypad_data = platform_get_drvdata(pdev);
            .......
            input_unregister_device(keypad_data->input_keypd_dev);
            kfree(keypad_data);
            free_irq(KEYPAD_IRQNUM, NULL);
            ...........
        return 0;
    }
    static struct platform_driver my_key_driver = {
            .driver         = {
                    .name   = "my-keypad",
                    .owner  = THIS_MODULE,
            },
            .probe          = my_key_probe,
            .remove         = my_key_remove,
            .suspend        = my_key_suspend,
            .resume         = my_key_resume,
    };
    int __init my_key_init(void)
    {
            .....
            
            res =  platform_driver_register(&my_key_driver );
            if(res)
            {
                    printk(KERN_INFO "fail : platrom driver %s (%d) \n", my_key_driver.driver.name, res);
                    return res;
            }
            .........
            return 0;
    }
    void __exit my_key_exit(void)
    {
            platform_driver_unregister(&my_key_driver);
    }


    module_init(my_key_init);
    module_exit(my_key_exit);


    然后我们再定义中断处理函数和工作队列的函数。
    因为我们检测按键往往需要去抖动,需要延时,但是在中断处理函数里面是不允许sleep的,否则会造成系统死掉。所以我们需要定义一个工作队列,将去抖动部分发在这里面处理。
    下面是处理函数示例


    irqreturn_t keypad_irq_service(int irq, void *para)
    {
            my_keypad * keypad_data = (my_keypad *)para  ;
            
            ......
                    //检测按键的状态
            schedule_work(&(keypad_data->work));
            return IRQ_HANDLED;
    }


    void keypad_work_func(struct work_struct *work)
    {
            my_keypad * keypad_data=container_of(work, my_keypad , work);
            disable_irq(KEYPAD_IRQNUM);
            //检测按键,获取键值
            if( key press)
            {
                    input_report_key(keypad_data->input_keypd_dev, key code, 1);
            }
            else
            {
                    input_report_key(keypad_data->input_keypd_dev, key code, 0);
            }
            input_sync(keypad_data->input_keypd_dev);
            enable_irq(KEYPAD_IRQNUM);
    }
    2.1轮询的方式检测按键
    对于有的设备,按键没有接到中断口上,就只能用轮询的方式了,下面是示例
    typedef struct
    {
            input_polled_dev *input_keypd_dev ;
            ........
    }my_keypad;
    static int  my_key_probe(struct platform_device *pdev)
    {
            int i;
            int err =0;
            my_keypad *keypad_data;
            input_polled_dev *keypd_dev ;
                    .....
            keypad_data = kzalloc(sizeof(my_keypad) ,
            GFP_KERNEL);
            if(!my_keypad )
            {
                    err  = -1 ; 
                    goto fail0;
            }
            keypd_dev = input_allocate_polled_device();
            if (!keypd_dev) {
             
                    err  = -1 ; 
                    goto fail1;
            }
            keypd_dev->poll = keypad_poll_callback;
                    keypd_dev->poll_interval = 15;   //轮询的时间间隔15ms
            keypd_dev->input->name = "my-keypad"; 
            keypd_dev->input->phys = "Keypad/input0";
            keypd_dev->input->id.bustype = BUS_HOST;
            keypd_dev->input->id.vendor = 0x0001;
            keypd_dev->input->id.product = 0x0001;
            keypd_dev->input->id.version = 0x0100;
            


            keypd_dev->input->evbit[0] = BIT_MASK(EV_KEY);
         
            .......
            set_bit(KEY_BACK, keypd_dev->input->keybit); 
            set_bit(KEY_MENU, keypd_dev->input->keybit);
                    .........
            keypad_data->input_keypd_dev = keypd_dev ;
            platform_set_drvdata(pdev, keypad_data);
     
            err = input_register_polled_device(keypd_dev);
            if (err){
             
                    goto fail2;
            }


            .............
            return 0;
    fail2: 
            input_free_polled_device(keypd_dev);


    fail1:
            kfree(keypad_data);
    fail0:


            return err;
    }
    int __devexit my_key_remove(struct platform_device *pdev)
    {
            my_keypad *keypad_data;
            keypad_data = platform_get_drvdata(pdev);
            .......
            input_unregister_polled_device(keypad_data->input_keypd_dev);
            kfree(keypad_data);
         
            ...........
            return 0;
    }
    轮询函数定义如下:
    void keypad_poll_callback(struct input_polled_dev *dev)
    {
            .......
            //检测按键,去抖动。。
            if(有键按下)
            {
                    input_report_key(dev->input, key code , 1 );
                 
            }
            else
            { 
                    input_report_key(dev->input, key code, 0 );     
            }
            input_sync(dev->input);    
    }


    最后需要注意的是input_polled_dev 只有在linux2.6.3之后才支持的,如果是之前的版本,那就只有自己起一个timer来定时轮询,同样的timer的处理函数中也不能有sleep延时,所以如果去抖动需要延时也需要开一个工作队列。下面是示例代码


    首先定义一个timer


    struct timer_list key_timer;
    参考“中断方式检测按键的例子”
    去掉probe函数中的中断部分的代码,改为
    init_timer(&(keypad_data->_key_timer));
    keypad_data->key_timer.expires = jiffies + 10;//jiffies是当前的毫秒数,这句的意思是10毫秒后timer到时
    keypad_data->key_timer.function = key_Timer;
    add_timer(&(keypad_data->key_timer));
    keypad_data->key_timer.data = (unsigned long) keypad_data;




    然后定义一个处理函数key_Timer
    void key_Timer(unsigned long data)
    {
        my_keypad *keypad_data = (my_keypad *) data;   
        schedule_work(&(keypad_data->work));
    }
    最后需要注意的是timer到时候不会自动重新载入的,所以我们还需要在工作队列的处理函数keypad_work_func的结尾加上下面的代码:
    init_timer(&(keypad_data->_key_timer));
    keypad_data->key_timer.expires = jiffies + 10; 
    add_timer(&(keypad_data->key_timer));
    展开全文
  • Linux驱动开发: 网络设备驱动开发

    千次阅读 2021-09-01 20:25:28
    Linux内核版本:3.5 一、Linux下网络相关命令 1.1 ifconfig命令:设置网卡IP地址 功能 ifconfig用于查看和更改网络接口的地址和参数,包括IP地址、网络掩码、广播地址,使用权限是超级用户。 语法:fconfig -...

    Linux内核版本: 3.5

    一、Linux下网络相关命令

    1.1  ifconfig命令:设置网卡IP地址

    功能

    ifconfig用于查看和更改网络接口的地址和参数,包括IP地址、网络掩码、广播地址,使用权限是超级用户。

    语法:fconfig -interface [options] address

    主要参数

    -interface

    指定的网络接口名,如eth0和eth1。

    up

    激活指定的网络接口卡。

    down

    关闭指定的网络接口。

    broadcast address

    设置接口的广播地址。

    pointopoint

    启用点对点方式。

    address

    设置指定接口设备的IP地址。

    netmask address

    设置接口的子网掩码。

         ifconfig是用来设置和配置网卡的命令行工具。为了手工配置网络,这是一个必须掌握的命令。使用该命令的好处是无须重新启动机器。要赋给eth0接口IP地址207.164.186.2,并且马上激活它,使用下面命令:

    #fconfig eth0 210.34.6.89 netmask 255.255.255.128 broadcast 210.34.6.127

         该命令的作用是设置网卡eth0的IP地址、网络掩码和网络的本地广播地址。若运行不带任何参数的ifconfig命令,这个命令将显示机器所有激活接口的信息。带有“-a”参数的命令则显示所有接口的信息,包括没有激活的接口。注意,用ifconfig命令配置的网络设备参数,机器重新启动以后将会丢失。

    查看网卡的IP地址信息

    # ifconfig    //查看当前已经启动的网卡信息

    # ifconfig -a  //查看所有网卡的信息。包含未启动的网卡。

    # ifconfig eth0 //查看eth0网卡的信息

    关闭与启动网卡

    # ifconfig eth0 up     //激活名称为eth0的网卡

    # ifconfig eth0 down   //关闭名称为eth0的网卡

    修改网卡MAC地址

    修改网卡MAC地址

    首先必须关闭网卡设备:ifconfig eth0 down

    修改MAC地址:ifconfig eth0 hw ether 00:AA:BB:CC:DD:EE

    重新启用网卡:ifconfig eht0 up

    这样网卡的MAC地址就更改完成了。每张网卡的MAC地址是惟一,但不是不能修改的,只要保证在网络中的MAC地址的惟一性就可以了。

    在一张网卡上绑定多个IP地址

    在Linux下,可以使用ifconfig方便地绑定多个IP地址到一张网卡。

    例如,eth0接口的原有IP地址为192.168.0 .254,可以执行下面命令:

    ifconfig eth0:0 192.168.0.253 netmask 255.255.255.0

    ifconfig eth0:1 192.168.0.252 netmask 255.255.255.0

    ......

    1.2 ping命令

    功能:ping检测主机网络接口状态,使用权限是所有用户。

    语法:ping [-dfnqrRv][-c][-i][-I][-l][-p][-s][-t] IP地址

    主要参数

    -d

    使用Socket的SO_DEBUG功能。

    -c

    设置完成要求回应的次数。

    -f

    极限检测。

    -i

    指定收发信息的间隔秒数。

    -I

    网络界面使用指定的网络界面送出数据包。

    -l

    前置载入,设置在送出要求信息之前,先行发出的数据包。

    -n

    只输出数值。

    -p

    设置填满数据包的范本样式。

    -q

    不显示指令执行过程,开头和结尾的相关信息除外。

    -r

    忽略普通的Routing Table,直接将数据包送到远端主机上。

    -R

    记录路由过程。

    -s

    设置数据包的大小。

    -t

    设置存活数值TTL的大小。

    -v

    详细显示指令的执行过程。

    ping命令是使用最多的网络指令,通常我们使用它检测网络是否连通,它使用ICMP协议。但是有时会有这样的情况,我们可以浏览器查看一个网页,但是却无法ping通,这是因为一些网站处于安全考虑安装了防火墙。

    使用实例

    # ping 192.168.11.123

    1.3 网卡启动与关闭

    除了使用ifconfig配置之外,也可以使用ifup、ifdown命令来实现。

    # ifup eth0      //开启eth0网卡

    # ifdown eth0   //关闭eth0网卡

    二、查看内核已经支持的网卡驱动

    进入到内核配置菜单目录下:

    [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

    三、移植ENC28J60网卡驱动

    3.1 ENC28J60芯片介绍

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

     ENC28J60 的主要特点如下:

    1. 兼容 IEEE802.3 协议的以太网控制器
    2. 集成 MAC 和 10 BASE-T 物理层
    3. 支持全双工和半双工模式
    4. 数据冲突时可编程自动重发
    5. SPI 接口速度可达 10Mbps
    6. 8K 数据接收和发送双端口 RAM
    7. 提供快速数据移动的内部 DMA 控制器
    8. 可配置的接收和发送缓冲区大小
    9. 两个可编程 LED 输出
    10. 带7个中断源的两个中断引脚
    11. TTL 电平输入
    12. 提供多种封装:SOIC/SSOP/SPDIP/QFN 等。

    ENC28J60 的典型应用电路如下图:

    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 引脚)和系统控制逻辑。

    引脚功能说明:

    3.2 ENC28J60以太网模块介绍

          ENC28J60 网络模块采用 ENC28J60 作为主芯片,单芯片即可实现以太网接入, 利用该模块,基本上只要是个单片机,就可以实现以太网连接。

        模块实物图如下:

    模块的主要引脚功能:

         其中 GND 和 V3.3 用于给模块供电,MISO/MOSI/SCK 用于 SPI 通信,CS 是片选信号,INT 为中断输出引脚,RST 为模块复位信号。

    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]# ls

    enc28j60.c  enc28j60_hw.h  Kconfig  Makefile

    3.4 配置内核SPI总线设备端

    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总线支持*/

    (使用的测试开发板是友善之臂的Tiny4412开发板)

       因为开发板引出的SPI接口只有SPI0,所以只能配置SPI0总线。

     

      1. 修改SPI0总线板级注册信息

    打开开发板底层板级配置文件:

    [root@wbyq linux-3.5]# vim arch/arm/mach-exynos/mach-tiny4412.c +1449

    2. 修改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子系统匹配使用的是平台设备模型,驱动端与设备端的名称需要一致。

    3. 修改完以上两步配置之后,再重新编译内核,烧写内核。

    3.5 修改ENC28J60驱动代码

    将/drivers/net/ethernet/microchip目录下的ENC28J60源码复制出来,单独修改。

    1. 编写Makefile文件,负责编译成模块。

    2. 修改ENC28J60驱动源码里的名称与SPI总线设备端保持一致。

    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");

            }

        ………………………….

    }

     除了修改以上信息之外,其他信息不用修改,直接编译驱动安装即可。

    3.6 驱动安装测试

    [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 -a

    eth0      Link encap:Ethernet  HWaddr 00:00:FF:FF:00:00 

              inet addr:192.168.10.123  Bcast:192.168.10.255  Mask:255.255.255.0

              inet6 addr: fe80::200:ffff:feff:0/64 Scope:Link

              UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1

              RX packets:2841 errors:0 dropped:0 overruns:0 frame:0

              TX packets:1641 errors:0 dropped:0 overruns:0 carrier:0

              collisions:0 txqueuelen:1000

              RX bytes:2695524 (2.5 MiB)  TX bytes:295408 (288.4 KiB)

    eth1      Link encap:Ethernet  HWaddr CE:89:65:5A:91:93    //新生成的网卡名称

              BROADCAST MULTICAST  MTU:1500  Metric:1

              RX packets:0 errors:0 dropped:0 overruns:0 frame:0

              TX packets:0 errors:0 dropped:0 overruns:0 carrier:0

              collisions:0 txqueuelen:1000

              RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

              Interrupt:186

    ip6tnl0   Link encap:UNSPEC  HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 

              NOARP  MTU:1452  Metric:1

              RX packets:0 errors:0 dropped:0 overruns:0 frame:0

              TX packets:0 errors:0 dropped:0 overruns:0 carrier:0

              collisions:0 txqueuelen:0

              RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

    lo        Link encap:Local Loopback 

              inet addr:127.0.0.1  Mask:255.0.0.0

              inet6 addr: ::1/128 Scope:Host

              UP LOOPBACK RUNNING  MTU:16436  Metric:1

              RX packets:0 errors:0 dropped:0 overruns:0 frame:0

              TX packets:0 errors:0 dropped:0 overruns:0 carrier:0

              collisions:0 txqueuelen:0

              RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

    sit0      Link encap:IPv6-in-IPv4 

              NOARP  MTU:1480  Metric:1

              RX packets:0 errors:0 dropped:0 overruns:0 frame:0

              TX packets:0 errors:0 dropped:0 overruns:0 carrier:0

              collisions:0 txqueuelen:0

              RX 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地址

    四、网络设备相关API函数介绍

    4.1 动态分配net_device结构

    #define alloc_etherdev(sizeof_priv) alloc_etherdev_mq(sizeof_priv, 1)

    函数参数:分配的空间大小。如果自己没有定义自己的结构体,就直接填sizeof(struct net_device)

    函数返回值:执行成功返回申请的空间地址。

    空间分配的函数还有一个alloc_netdev()函数。

    alloc_etherdev()是alloc_netdev()针对以太网的"快捷"函数

    4.2 释放net_device结构

    void free_netdev(struct net_device *dev)

    该函数用于释放alloc_etherdev分配的net_device结构体,与alloc_etherdev成对使用。

    4.3 注册网络设备

    int register_netdev(struct net_device *dev)

    函数形参:网络设备信息struct net_device

    函数返回值:执行成功返回0。

    struct net_device结构体原型如下:

    struct net_device {

    char                  name[IFNAMSIZ];  /*网卡名字,ifconfig查看的名称*/

    unsigned long          mem_end;       /* shared mem end    */

    unsigned long          mem_start;      /* shared mem start */

    这两个变量描述设备与内核通信所用到的内存边界。它们由设备驱动初始化,并且只能被设备驱动访问;高层协议不需要关心这块内存。

    unsigned long          base_addr;       /* 存放网络设备基地址,就是物理地址,用来将设备映射到内存空间*/

    unsigned int             irq;              

    /*设备中断号。它可以被多个设备共享。设备驱动调用request_irq来分配这个值,并调用free_irq来释放它*/

       

    const struct net_device_ops *netdev_ops; //网络设备的虚拟文件操作集合,很重要的结构。

    const struct ethtool_ops *ethtool_ops;   //可选的设备操作

    unsigned char          *dev_addr;      /*MAC地址*/

    unsigned char          broadcast[MAX_ADDR_LEN];       /*广播地址 */

    unsigned long          last_rx;      /*最后收到数据包的时间,用于判断超时*/

    unsigned char          if_port;      /*接口的端口类型。*/

    unsigned char          dma;      /* DMA channel         */

     /*

    设备所使用的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。*/

    unsigned long          trans_start;        /* 数据包发送的起始时间-jiffies表示        */

    int                     watchdog_timeo;    /* 被 by dev_watchdog()函数使用,用于定义超时 */

    struct timer_list       watchdog_timer; /* 看门狗定时器*/

    };

    const struct net_device_ops 网络设备虚拟文件操作集合:

    struct net_device_ops {

    /*初始化注册网络设备的时候调用*/

            int    (*ndo_init)(struct net_device *dev);

       

       /*释放设备的时候调用*/

            void (*ndo_uninit)(struct net_device *dev);

            /*打开网络接口,对应ifconfig up命令,编写网络设备硬件初始化的相关代码*/

            int    (*ndo_open)(struct net_device *dev);

            /*关闭网络设备,对应ifconfig down命令,实现的内容与OPEN相反*/

            int    (*ndo_stop)(struct net_device *dev);

            /*启动网络数据包传输的方法*,返回值必须返回NETDEV_TX_OK, NETDEV_TX_BUSY /

            netdev_tx_t     (*ndo_start_xmit) (struct sk_buff *skb,struct net_device *dev);

            /*网络数据包没有在规定的时间内发送出去,产生超时事件时的处理函数,它应当处理超时问题,并恢复报文发送*/

            void (*ndo_tx_timeout) (struct net_device *dev);

    ……………………省略…………………………….

    }

    分配net_device结构体之后初始化示例

    /*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;     //看门狗超时时间

    4.4 注销网络设备

    void unregister_netdev(struct net_device *dev)

    功能:注销网络设备

    参数:注销的网络设备结构体

    4.5 随机生成MAC地址

    void eth_hw_addr_random(struct net_device *dev)

        该函数使用软件方式随机生成一个MAC地址,并给传入的net_device 结构体内部成员dev_addr赋值。

    示例:

            /*随机生成MAC地址*/

            eth_hw_addr_random(tiny4412_net); // struct net_device *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];

    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个字节

     

    使用网卡发送数据时,如何发现发送的实际数据小于以太网规定的最小长度,需要进行补齐:

    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;

    }

    ………………省略……………………………..

    }

    4.7 分配新的套接字缓冲区

    struct sk_buff *dev_alloc_skb(unsigned int length)

    该函数用于分配新的套接字缓冲区,用于存放即将上报给上层(TCP/IP协议层)的网络数据。

    示例:

            /*从ENC28J60的寄存器里读取接收到的数据*/

            length=ENC28J60_Packet_Receive(1518,Enc28j60_Rx_Buff);

            if(length<=0)

            {

                     return;

            }

            /*分配新的套接字缓冲区*/

        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);

    说明: skb_put(skb, length)返回sk_buff数据缓冲区首地址,保存即将上报给应用层的数据。

    参考:  smsc-ircc2.c文件1461行

    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是网卡收到的实际数据

           

    /* 获取上层协议类型 */

    skb->protocol = eth_type_trans(skb,tiny4412_net);

    1.5 网络设备框架介绍

    5.1 网络设备驱动框图

    5.2 ndo_start_xmit函数接口代码编写示例

    /*启动网络数据包传输的方法*/

    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);

           

            /*释放skb*/

            dev_kfree_skb(skb);  

           

            /*更新统计信息:记录发送的包数量*/

            dev->stats.tx_packets++;      

           

            /*更新统计信息:记录发送的字节数量*/    

            dev->stats.tx_bytes += skb->len;

           

            return NETDEV_TX_OK; //这是个枚举状态。

    }

    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. 分配新的套接字缓冲区*/

        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);

           

        /* 获取上层协议类型 */

        skb->protocol = eth_type_trans(skb,tiny4412_net);

        /* 记录接收时间戳 */

        tiny4412_net->last_rx = jiffies;

           

            /*接收的数据包*/

            tiny4412_net->stats.rx_packets++;

           

            /*接收的字节数量*/

            tiny4412_net->stats.rx_bytes += skb->len;

           

            /* 把数据包交给上层 */

        netif_rx(skb);

    }

    六、 网络设备驱动框架代码

    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,

            };

    4. 调用register_netdev函数完成网络设备注册。

       注销函数: unregister_netdev

    5. 网卡收到数据通过netif_rx函数上传给应用层

    6.2 网络设备驱动框架代码

    以下代码是一个网络设备驱动模型,演示了网卡如何获取上层应用程序传递下来的数据并发送出去,网卡接收到数据如何传递给上层应用程序。

    #include <linux/init.h>

    #include <linux/module.h>

    #include <linux/netdevice.h>

    #include <linux/etherdevice.h>

    static struct net_device *tiny4412_net=NULL;  //网络设备指针结构

    /*1. 设备初始化调用,该函数在注册成功后会调用一次,可以编写网卡初始化相关代码*/

    static int tiny4412_ndo_init(struct net_device * dev)

    {

            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. 启动网络数据包传输的方法

    当应用层的TCP/IP需要发送数据时,就调用该函数

    */

    static netdev_tx_t   tiny4412_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;

        }

        dev->trans_start = jiffies;     //记录发送时间戳

            printk("\n发送的数据:");

            for(i=0;i<len;i++)

            {

                     printk("%X ",data[i]);

            }

            printk("\n");

        /* 设置硬件寄存器让硬件将数据发出去 */

         // xxx_hw_tx(data,len,dev);

            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]);

            return 0;

    }

    /*

    以下函数在网卡的接收中断中调用,用于读取网卡里的数据。

    读取完毕之后,再通过netif_rx()函数上报到应用层

    */

    static void data_rx(struct net_device* dev)

    {

            int length;

            /*1. 读取硬件网卡接收到的数据*/

        //length = get_rev_len(...);

            /*2. 分配新的套接字缓冲区*/

        struct sk_buff *skb = dev_alloc_skb(length+NET_IP_ALIGN);

        skb_reserve(skb, NET_IP_ALIGN); //对齐

        skb->dev = dev;

        /* 读取硬件上接收到的数据 */

            //skb_put(skb, length) //存放网卡里读取数据的缓冲区地址

           

            //ENC28J60_Read(length,skb_put(skb, length));

        /* 获取上层协议类型 */

        skb->protocol = eth_type_trans(skb,dev);

        /* 把数据包交给上层 */

        netif_rx(skb);

        /* 记录接收时间戳 */

        dev->last_rx = jiffies;

    }

    /*网络设备虚拟文件操作集合*/

    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; //虚拟文件操作集合

         

          /*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]);

           

            /*注册网络设备*/

            register_netdev(tiny4412_net);

           

            printk("网络设备注册成功!\n");  

            return 0;

    }

    static void __exit Net_test_exit(void)

    {

            //注销网络设备

            unregister_netdev(tiny4412_net);

            printk("网络设备注销成功!\n");  

    }

    module_init(Net_test_init);

    module_exit(Net_test_exit);

    MODULE_AUTHOR("xiaolong");

    MODULE_LICENSE("GPL");

    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] 网络设备初始化!

    [   52.100000] 网络设备注册成功!

    [root@XiaoLong /code]#

    [root@XiaoLong /code]# ifconfig eth888   //查看网卡信息

    eth888    Link encap:Ethernet  HWaddr 2E:2F:07:05:A0:DC 

              BROADCAST MULTICAST  MTU:1500  Metric:1

              RX packets:0 errors:0 dropped:0 overruns:0 frame:0

              TX packets:0 errors:0 dropped:0 overruns:0 carrier:0

              collisions:0 txqueuelen:1000

              RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

     [root@XiaoLong /code]# udhcpc -i eth888    //自动分配IP地址

    udhcpc (v1.23.2) started

    Setting 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 7200

    Setting IP address 192.168.1.102 on eth888

    Deleting routers

    route: SIOCDELRT: No such process

    Adding router 192.168.1.1

    Recreating /etc/resolv.conf

     Adding DNS server 192.168.1.1

    [root@XiaoLong /code]# ifconfig    //查看分配成功并设置成功的IP地址

    eth888    Link encap:Ethernet  HWaddr 2E:2F:07:05:A0:DC 

              inet addr:192.168.1.102  Bcast:192.168.1.255  Mask:255.255.255.0

              inet6 addr: fe80::2c2f:7ff:fe05:a0dc/64 Scope:Link

              UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1

              RX packets:0 errors:0 dropped:0 overruns:0 frame:0

              TX packets:0 errors:0 dropped:0 overruns:0 carrier:0

              collisions:0 txqueuelen:1000

              RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

    [root@XiaoLong /code]# ping 192.168.1.112   //ping局域网内的其他主机

    PING 192.168.1.1 (192.168.1.1): 56 data bytes

    64 bytes from 192.168.1.1: seq=0 ttl=64 time=12.099 ms

    64 bytes from 192.168.1.1: seq=1 ttl=64 time=12.932 ms

    64 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 loss

    round-trip min/avg/max = 9.035/11.355/12.932 ms

    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

    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>

    /*

    参考的网卡程序:  cs89x0.c与Enc28j60.c

    */

    /*

    以下是ENC28J60驱动移植接口:

    SPI0接口:

            GPB_0--SCK

            GPB_1--CS

            GPB_2--MISO

            GPB_3--MOSI

            GPX1(0)--中断

    */

    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_IRQ_NUMBER EXYNOS4_GPX1(0)  /*Tiny4412开发板引出的IO口第9个IO口*/

    #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;        

    }

    /*

    函数功能:复位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);

           

            /*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); 

    }

    /*

    函数功能:读取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]; /*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. 分配新的套接字缓冲区*/

        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);

           

        /* 获取上层协议类型 */

        skb->protocol = eth_type_trans(skb,tiny4412_net);

        /* 记录接收时间戳 */

        tiny4412_net->last_rx = jiffies;

           

            /*接收的数据包*/

            tiny4412_net->stats.rx_packets++;

           

            /*接收的字节数量*/

            tiny4412_net->stats.rx_bytes += skb->len;

           

            /* 把数据包交给上层 */

        netif_rx(skb);

    }

    /*

    函数功能:  中断服务函数

    */

    irqreturn_t ENC28J60_irq_handler(int irq, void *dev)

    {

             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));       

            /*注明: ENC28J60的中断不灵敏,就使用定时器轮询弥补*/

    }

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

    /*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);

           

            /*释放skb*/

            dev_kfree_skb(skb);  

           

            /*更新统计信息:记录发送的包数量*/

            dev->stats.tx_packets++;      

           

            /*更新统计信息:记录发送的字节数量*/    

            dev->stats.tx_bytes += skb->len;

           

            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);

            free_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);

            /*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");

     

     

     

     

     

     

     

     

     

     

    展开全文
  • linux gic驱动

    千次阅读 2016-05-30 15:31:02
    __irqchip_begin就是内核irq chip table的首地址,这个table也就保存了kernel支持的所有的中断控制器的of_device_id信息。of_irq_init函数执行之前,系统已经完成了device tree的初始化,因此系统中的所有的设备节点...
  • linux设备驱动框架

    万次阅读 多人点赞 2018-05-26 11:32:00
    一.Linux设备分类字符设备: 以字节为单位读写的设备。块设备 : 以块为单位(效率最高)读写的设备。网络设备 : 用于网络通讯设备。字符设备: 字符(char)设备是个能够像字节流(类似文件)一样被访问的设备,...
  • Linux音频设备驱动

    千次阅读 2010-08-20 15:16:00
    Linux中,先后出现了音频设备的两种框架OSS和ALSA,本节将在介绍数字音频设备及音频设备硬件接口的基础上,展现OSS和ALSA驱动的结构。17.1~17.2节讲解了音频设备及PCM、IIS和AC97硬件接口。17.3节阐述了Linux OSS...
  • Linux字符设备驱动中container_of宏的作用 首先看看这个宏的原型: container_of(ptr,type,member)  功能:根据一个结构体变量中的一个成员变量的指针来获取指向整个结构体变量的指针。 参数:  ptr:已知的...
  • Linux驱动分析之LCD驱动架构

    万次阅读 2020-08-06 08:15:00
    Linux设备中,LCD显示采用了帧缓冲(framebuffer)技术,所以LCD驱动也叫Framebuffer驱动,所以L...
  • imx6ull Linux 开发板spi驱动st7789

    千次阅读 2020-06-29 17:56:11
    如题:驱动st7789 240*240 程序如下: #include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/ide.h> #include <linux/init.h> #...
  • Linux网络设备驱动(一) _驱动模型

    千次阅读 2017-03-28 09:51:47
    Linux素来以其强大的网络功能著名,同时, 网络设备也作为三大设备之一, 成为Linux驱动学习中必不可少的设备类型, 此外, 由于历史原因, Linux并没有强制对网络设备贯彻其"一切皆文件"的思想, 网络设备不以/dev下的设备...
  • Linux块设备驱动(一) _驱动模型

    千次阅读 2017-03-21 21:10:04
    块设备是Linux三大设备之一,其驱动模型主要针对磁盘,Flash等存储类设备,本文以3.14为蓝本,探讨内核中的块设备驱动模型 框架 下图是Linux中的块设备模型示意图,应用层程序有两种方式访问一个块设备:/dev和...
  • linux UART串口驱动代分析

    千次阅读 2016-07-28 14:15:43
    UART驱动 1、对UART驱动添加设备信息 对于2440的UART,内核已经对其完整的配置不需要做写入任何的...对于平台设备,首先要说明的应该是s3c2410_uartcfg结构体,该结构体定义在,Serial_s3c.h(include\linux)文件中
  • Linux驱动开发: FrameBuffe(LCD)驱动开发

    千次阅读 多人点赞 2021-09-07 20:09:35
    linux系统中LCD这类设备称为帧缓冲设备,英文frameBuffer设备。 frameBuffer 是出现在2.2.xx 内核当中的一种驱动程序接口。 帧缓冲(framebuffer)是Linux 系统为显示设备提供的一个接口,它将显示缓冲区抽象,...
  • 禁用Nouveau,安装Linux Nvidia 显卡驱动

    千次阅读 2019-08-30 09:01:42
    干掉Nouveau安装Linux Nvidia显卡驱动 首先说明下什么是Nouveau,为什么有些系统安装N卡驱动的时候会提示“ERROR: The Nouveau kernel driver is currently in use by your system. This driver i...
  • 从零开始之驱动开发、linux驱动(一、驱动基础)

    万次阅读 多人点赞 2018-08-27 22:28:10
    准备19年在深圳这边找驱动相关的工作了,所以...同时该系列的驱动都是在前面新移植的linux3.16.57的基础上工作的。   之前的驱动模型学习和分析,从框架上了解的驱动的组织形式(在4.9的内核的基础上分析的)。 ...
  • Linux DMA 驱动学习总结

    千次阅读 2016-04-15 09:05:53
    Linux DMA驱动构架分析  以linux2.6.32中的S3C2440驱动为例进行分析,DMA驱动所对应的源码为linux-2.6.32.2\arch \arm\mach-s3c2440\dma.c,代码入口为: arch_initcall(s3c2440_dma_init); 205 static int...
  • 本文继上一篇文章《Linux Framebuffer驱动剖析之一—软件需求》,深入分析LinuxFramebuffer子系统的驱动框架、接口实现和使用。
  • Linux NAND FLASH驱动程序框架分析

    千次阅读 2011-06-11 09:06:00
    1.Linux-MTD Subsystem FLASH在嵌入式系统中是...在Linux内核中引入了MTD子系统为NOR FLASH和NAND FLASH设备提供统一的接口,从而使得FLASH驱动的设计大为简化。在引入MTD后Linux系统中FLASH设备驱动可分为四层,如图:
  • LINUX之GPIO驱动及接口使用

    千次阅读 2013-06-21 10:59:32
    http://www.linuxidc.com/Linux/2011-06/37904.htm 来做个LED驱动,虽然LED的原理简单得不能再简单了,但是要把kernel中针对于s3c24**的GPIO的一些数据结构,还有函数搞清楚也不是那么轻松的事,所以本文主要简单地...
  • Linux设备驱动——虚拟总线platform
  • 71 linux usb设备驱动

    千次阅读 2017-07-19 14:41:00
    //端点数组首地址, endpoint[i].desc可取到struct usb_endpoint_descriptor对象的信息 }; linux 描述 USB 配置使用结构 struct usb_host_config 和整个 USB 设备使用结构 struct usb_device. USB 设备驱动...
  • 干掉Nouveau安装Linux Nvidia显卡驱动

    万次阅读 2018-09-07 11:39:13
    干掉Nouveau安装Linux Nvidia显卡驱动  首先说明下什么是Nouveau,为什么有些系统安装N卡驱动的时候会提示“ERROR: The Nouveau kernel driver is currently in use by your system. This  dr...
  • linux设备驱动之8250串口驱动

    千次阅读 2017-11-30 17:37:08
    今天就在此基础上分析一下linux kernel自带的串口驱动。毕竟只有对比专业的驱动代码才能更好的进步,同以往一样,基于linix kernel2.6.25.相应驱动代码位于:linux-2.6.25/drivers/serial/8250.c。  二:8250串口...
  • linux lcd设备驱动剖析四

    千次阅读 2014-01-17 21:24:02
    在《linux lcd设备驱动剖析二》文章中,我们详细分析了s3c24xxfb_probe函数。 linux lcd设备驱动剖析二的文章链接:http://blog.csdn.net/lwj103862095/article/details/18189765 s3c2410fb.c中s3c24xxfb_probe是...
  •  Linux内核对网络驱动程序使用统一的接口,并且对于网络设备采用面向对象的思想设计。  Linux内核采用分层结构处理网络数据包。分层结构与网络协议的结构匹配,既能简化数据包处理流程,又便于扩展和维护。 ...
  • 转载请在文保留原文出处:EMC中文支持论坛 - https://community.emc.com/go/chinese 介绍   Linux FC/iSCSI存储设备管理系列,主要介绍...1. Linux磁盘设备驱动介绍 2. 磁盘设备管理(一):FC磁盘管理
  • 安装Linux Nvidia显卡驱动

    千次阅读 2012-08-03 17:06:58
    首先说明下什么是Nouveau,为什么有些系统安装N卡驱动的时候会提示“ERROR: The Nouveau kernel driver is currently in use by your system. This driver is incompatible with the NVIDIA driver……”之类的...
  • linux网卡驱动源码分析

    千次阅读 2014-01-10 11:26:08
    网络驱动是一种典型的PCI设备驱动,无论在嵌入式平台还是在PC领域,网络相关的项目开发有着比较广阔的前景,因此,分析当前Linux内核中网络设备的驱动,不但能了解网络相关的基本原理,而且可以借鉴Linux内核的先进...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 29,551
精华内容 11,820
关键字:

linux首地址驱动

linux 订阅