首页 > 代码库 > Linux 字符驱动程序(一)

Linux 字符驱动程序(一)

Linux 字符驱动程序(一)

在linux内核中设备主要有三种:
1 字符设备:   
     ?字符设备的读写以字节为单位,存取时没有缓存。
     ?对字符设备发出读写请求时,实际的硬件I/O紧接着就发生了。一般来说,字符设备不支持随机访问。
     ?典型的字符设备包括鼠标、键盘及串行口等。
2 块设备:
     ?块设备读写以块为单位,典型的块大小为512或1024字节。
     ?利用一块系统内存作为缓冲区,当用户进程对设备发出读写请求时,驱动程序先察看缓冲区中的内容,若缓冲区中的数据能满足用户的要求就返回相应的数据,否则就调用相应的请求函数来进行实际的I/O操作,以提高效率。
     ?块设备主要包括硬盘、软盘、CD-ROM等。
3 网络设备:
     ?Linux的网络系统主要基于BSD Unix的Socket机制。在系统和驱动程序之间定义有专门的数据结构进行数据的传递。系统里支持对发送数据和接收数据的缓存,提高流量控制机制,提供对多协议的支持。

4 每个设备对应一个文件,放在/dev目录下
5 每个设备文件都对应有两个设备号,存放在inode节点中
   ?主设备号标示设备的种类,也标识了该设备所使用的驱动程序;
   ?次设备号标识了使用同一设备驱动程序的不同硬件设备。
6 可以通过/proc/devices 来查看相应的设备号,通过mknod  /dev/xxx c major minor 来产生设备节点,从而将设备挂接到/dev目录下。或者在编写驱动程序时动态的获取主设备号以及动态产生设备节点。

7 下面具体分析一个led的驱动程序。(该程序沿用了2.6以前版本的驱动程序的书写方法,后面会介绍新的书写方法,但是本质是一样的)。
声明为static是为了避免数据对内和造成污染,仅对该模块有效。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
//相关的头文件,我们仿照其他模块加载即可。

#define DEVICE_NAME     "leds"  /* 加载模式后,执行”cat /proc/devices”命令看到的设备名称 */

//用于自动产生设备节点
static struct class *leds_class;
static struct class_device * leds_class_devs[4];

// LED的控制地址
volatile unsigned long *gpfcon = NULL;
volatile unsigned long *gpfdat = NULL;



//应用程序执行open时调用该函数;
static int first_drv_open(struct inode *inode, struct file *file)
{
      int minor = MINOR(inode->i_rdev); //MINOR(inode->i_cdev);

switch(minor)
{
       
  case 0: /* /dev/leds */
       
  {
            *gpfcon &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2)));
*gpfcon |= ((0x1<<(4*2)) | (0x1<<(5*2)) | (0x1<<(6*2)));
            break;
        }


        case 1: /* /dev/led1 */
        {
           
             *gpfcon &= ~((0x3<<(4*2));
*gpfcon |= ((0x1<<(4*2));

            break;
        }


        case 2: /* /dev/led2 */
        {
            *gpfcon &= ~ (0x3<<(5*2));
*gpfcon |= (0x1<<(5*2));

            break;
        }


        case 3: /* /dev/led3 */
        {
            *gpfcon &= ~(0x3<<(6*2));
*gpfcon |= (0x1<<(6*2));

            
            break;
        }
        
}
     

return 0;
}

//应用程序执行write时调用该函数;
static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
    int minor = MINOR(file->f_dentry->d_inode->i_rdev); // 获取次设备号
    int val;
    copy_from_user(&val, buf, count);

    switch (minor)
    {
        case 0: /* /dev/leds */
        {            
            if (val == 1)
   {
// 点灯
*gpfdat &= ~((1<<4) | (1<<5) | (1<<6));
   }
   else
   {
// 灭灯
*gpfdat |= (1<<4) | (1<<5) | (1<<6);
    }
            break;
        }


        case 1: /* /dev/led1 */
        {
            if (val == 1)
   {
// 点灯
*gpfdat &= ~(1<<4);
    }
    else
    {
// 灭灯
*gpfdat |= (1<<4);
    }
            break;
        }


        case 2: /* /dev/led2 */
        {
            if (val == 1)
    {
// 点灯
*gpfdat &= ~(1<<5);
    }
    else
    {
// 灭灯
*gpfdat |= (1<<5);
    }
            break;
        }


        case 3: /* /dev/led3 */
        {
            if (val == 1)
    {
// 点灯
*gpfdat &= ~(1<<6);
    }
    else
    {
// 灭灯
*gpfdat |= (1<<6);
     }
            break;
        }
        
    }

return 0;
}

//驱动程序与内核的接口;
static struct file_operations first_drv_fops = {
    .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    .open   =   first_drv_open,     
    .write =   first_drv_write,  
};


int major; //记录动态获取的设备号
/*
 * 执行insmod命令时就会调用这个函数 
 */
static int first_drv_init(void)
{
      int minor = 0; //次设备号
      major = register_chrdev(LED_MAJOR, DEVICE_NAME, &first_drv_fops); //注册设备

      if (major < 0) {
      printk(DEVICE_NAME " can‘t register major number\n");
      return major;
     }

leds_class = class_create(THIS_MODULE, "leds"); // 产生节点类,以leds_class声明的均为同一种设备
if (IS_ERR(leds_class))
return PTR_ERR(leds_class);
 
leds_class_devs[0] = class_device_create(leds_class, NULL, MKDEV(major, 0), NULL, "leds");
//产生不同的从设备,并以不同的名字挂接在/dev目录下;
for (minor = 1; minor < 4; minor++)
{
leds_class_devs[minor] = class_device_create(leds_class, NULL, MKDEV(major, minor), NULL, "led%d", minor);
if (unlikely(IS_ERR(leds_class_devs[minor])))
return PTR_ERR(leds_class_devs[minor]);
}
        
gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);//控制寄存器地址
gpfdat = gpfcon + 1;  // 0x56000054  //数据寄存器地址。

        printk(DEVICE_NAME " initialized\n");
return 0;
}

/*
 * 执行rmmod命令时就会调用这个函数 
 */
static void first_drv_exit(void)
{
int minor;
    /* 卸载驱动程序 */
       unregister_chrdev(major, DEVICE_NAME);


for (minor = 0; minor < 4; minor++)
{
class_device_unregister(leds_class_devs[minor]);
}
class_destroy(leds_class);
iounmap(gpfcon);
}

module_init(first_drv_init);
module_exit(first_drv_exit);
/* 描述驱动程序的一些信息,不是必须的 */
MODULE_AUTHOR("http://www.100ask.net");
MODULE_VERSION("0.1.0");
MODULE_DESCRIPTION("LED Driver");
MODULE_LICENSE("GPL");

我们写应用程序时,打开相应的设备/dev/leds , /dev/led1, /dev/led2, /dev/led3 向其中写1,0 就可以控制全部led或者某个led的亮灭.