驱动程序学习

驱动模型


在这里插入图片描述

驱动分类


  • 字符设备驱动
  • 网络设备驱动
  • 块设备驱动

vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv

  • 字符设备

字符设备是一种按照字节来访问的设备,字符设备则是负责驱动字符设备,这样的驱动通常实现open close read write 系统调用。

  • 块设备

在大部分的Unix系统,块设备不能按字节处理数据,只能一次一次或着对个长度为512字节(或一个更大的2次幂的数)的整块数据。

而Linux则允许块设备传送任意数目的字节。因此,块设备和字符设备的区别仅仅是驱动与内核的接口不同。

块的大小我们在制作的时候可以配置,如512KB/块、1024KB/块……

  • 网络接口

任何网络事务都通过一个接口来进行,一个接口通常是一个硬件设备(eth0),但是它也可以是一个纯粹的软件设备,比如回环接口(lo),一个网络接口负责发送和接收数据报文。

驱动程序安装


  • 模块方式(动态安装)
  • 直接编译进入内核
  1. 直接编译进内核

    将helloworld 编译进内核?

    修改: KconfigMakefile 两个文件就可以

    (1) 进入内核源码包linux-2.6.32.2 ,找到我们需要修改的两个文件:

    在这里插入图片描述

    ​ ---->Kconfig文件:用于配置在执行make menuconfig时的菜单。

    ​ 我们要加入到内核的模块驱动文件为:mini2440_hello_module.c

    ​ 那么我们在内核文件中,Kconfig我们这么来配置:

    config MINI2440_HELLO_MODULE
    	tristate "Mini2440 module sample"
    	depends on MACH_MINI2440
    	default m if MACH_MINI2440
    	help
    	  Mini2440 module sample.
    

    ​ 如下,是我配置好的:

在这里插入图片描述

​ 至此,我们就配置好了菜单了,下面执行make menuconfig,我们看看是什么效果:

在这里插入图片描述

Kconfig 用于配置项目菜单项的

​ ---->Makefile文件:见我们需要编译成模块的文件加入编译项目文件Makefile中

​ 在Makefile中,我们加入这样的成分:

obj-$(CONFIG_MINI2440_HELLO_MODULE) += mini2440_hello_module.o

​ 添加完成后的效果是:

在这里插入图片描述

  1. 用户程序如何调用驱动

    在这里插入图片描述

    对驱动的操作,实际是对设备文件的操作!

    A:Linux用户程序通过设备文件(设备节点)来使用驱动程序操作字符设备和块设备。

    Q:(字符、块)设备文件在哪儿?

    A:文件放置在/dev/文件夹下。

    如下,我们看下,在dev目录下的设备文件(设备节点)

    在这里插入图片描述

    可以看到,在我们这里已经有了很多的设备驱动,如:ADC、buttons、camera、leds、还有一些通信协议的如i2c、spi等

    那么我们就可以操作这里的设备文件,来让对应的驱动做出相应的输出动作,实现对外设的控制。至于具体我们如何使用,在我们后面讲解了字符设备驱动的程序结构,我们再来解释。

字符设备程序设计


在设计字符设备驱动时,我们从下面角度来进一步了解:

  1. 设备号

    字符设备通过字符设备文件来存取。字符设备文件可以ls -l 的输出的第一列“C”标示,使用命令,会看到在设备文件项中有2个数(由逗号分隔开)这就是设备的主次设备编号。

    在这里插入图片描述

    上图作为实例,两个数字10,62 这就是设备号了!!!

    =》设备号用来做什么?

    主设备号用来反映设备类型

    次设备号用来区分同类型的设备

    Q:内核中如何描述设备号?

    A:dev_t(其实质是32位无符号的整数,高12为主设备号,低20为次设备号)

    Q:如何从dev_t中解释出设备号?

    A:MAJOR(dev_t dev)

    Q:如何从dev_t中解释出设备号?

    A:MINOR(dev_t dev)

    =》分配主设备号

    • 静态申请

      (1)根据Documentation/device.txt来确定一个没有使用的主设备号。

      (2)使用register_chrdev_region函数注册设备号。

      int register_chrdev_region(dev_t from, unsigned count, const char *name)
      
        功能:申请使用从from开始的count个设备号(主设备号不变,次设备号递增)
        参数:
        		from		希望申请使用的设备号
        		count		希望申请使用设备号数目
        		name		设备名(体现在/proc/devices)
      
    • 动态分配

      直接使用 alloc_chrdev_region 由内核分配设备号。

      安装驱动后,从/proc/devices 中查询设备号

      int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
        
        功能:请求内核动态的分配count个设备号,且次设备号从baseminor开始。
        参数:
          	dev					分配到的设备号
        		baseminor		起始次设备号	
            count				需要分配的设备号数目
        		name				设备名(体现在/proc/devices)
      
    • 注销设备号

      在不使用该设备时,都要释放这些设备号

      void unregister_chrdev_region(dev_t from, unsigned count)
        
        功能:释放从from开始的count个设备号
       
      
  2. 创建设备文件

    • 手工创建

      使用mknod命令手工创建

       mknod filename type major minor
       
       参数:
        		filename:	设备文件名
        		type:			设备文件类型
        		major:		主设备号
        		minor:		次设备号
      

      举例:创建两个串口设备

      mknod serial0 c 200 0
      mknod serial1 c 200 1

      =》设备号均为200,说明都是由同一个驱动程序来控制的

      =》设备号不同,用于区分不同的操作对象

    • 自动创建

  3. 设备注册

    【分配cdev】 —> 【初始化cdev】 —> 【添加cdev】

    1. 分配cdev

      struct cdev *cdev_alloc(void)

    2. 初始化

      void cdev_init(struct cdev *cdev, const struct file_operations *fops)

      cdev:待初始化的cdev结构

      fops:设备对应的操作函数集

    3. 注册

      int cdev_add(struct cdev *p, dev_t dev, unsigned count)

      p:待添加到内核的字符设备结构

      dev:设备号

      count:添加的设备个数

    4. 设备操作

      int (*open)(struct inode *,struct file *)

      在设备文件上的第一个操作,并不要求驱动程序一定要实现这个方法。如果该项目为NULL,设备的打开操作永远成功。

      void (*release)(struct inode *, struct file *)

      当设备文件被关闭时调用这个操作。与open相仿,release也可以没有。

      ssize_t (*read)(struct file *, char __user *, size_t, loff_t *)

      从设备中读取数据。

      ssize_t (*write)(struct file *, const __user *, size_t, loff_t *)

      向设备发送数据

      unsigned int (*poll)(struct file *, struct poll_table_struct *)

      对应select系统调用

      int (*ioctl)(struct inode *, struct file *, unsigned int, unsigned long)

      控制设备

      int (*mmap)(struct file *, struct vm_area_struct *)

      将设备映射到进程虚拟地址空间中

      off_t (*llseek)(struct file *,loff_t int)

      修改文件的当前读写位置,并将新位置作为返回值。

      • Open方法

        open方法是驱动程序用来为以后的操作完成初始化准备工作的,一般:【初始化】 --> 【表明次设备号】

      • Release方法

        关闭设备。

      • 读和写

        ssize_t xxx_read(struct file * filp, char __user * buff, size_t count, loff_t * offp);

        ssize_t xxx_write(struct file * filp, char __user * buff, size_t count, loff_t * offp);

        filp是文件指针,count是请求传输的数据量,buff 参数指向数据缓存,offp指出文件当前的访问位置。

        Read和Write方法的buff参数是用户空间指针。因此,它不能被内核代码直接引用,原因是用户空间指针在内核空间时可能根本是无效的----没有那个地址的映射。

        内核提供了专门的函数用于访问用户空间的指针,

        int copy_from_user(void *to, const void __user *from, int n)
        int copy_to_user(void __user *to,const void *from, int n)
        
        
    5. 设备注销

    字符设备的注销使用cdev_del函数完成。

    int cdev_del(struct cdev *p)

    p:要注销的字符设备结构。

  4. 源码分析:下面是mini2440的6个按键的驱动程序

    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/fs.h>
    #include <linux/init.h>
    #include <linux/delay.h>
    #include <linux/poll.h>
    #include <linux/irq.h>
    #include <asm/irq.h>
    #include <linux/interrupt.h>
    #include <asm/uaccess.h>
    #include <mach/regs-gpio.h>
    #include <mach/hardware.h>
    #include <linux/platform_device.h>
    #include <linux/cdev.h>
    #include <linux/miscdevice.h>
    #include <linux/sched.h>
    #include <linux/gpio.h>
    
    #define DEVICE_NAME     "buttons"
    
    struct button_irq_desc {	/*这里定义了一个按键的结构体*/
        int irq;
        int pin;
        int pin_setting;
        int number;
        char *name;	
    };
    
    /*将开发板上的六个按键规划到数组当中*/
    static struct button_irq_desc button_irqs [] = {
        {IRQ_EINT8 , S3C2410_GPG(0) ,  S3C2410_GPG0_EINT8  , 0, "KEY0"},
        {IRQ_EINT11, S3C2410_GPG(3) ,  S3C2410_GPG3_EINT11 , 1, "KEY1"},
        {IRQ_EINT13, S3C2410_GPG(5) ,  S3C2410_GPG5_EINT13 , 2, "KEY2"},
        {IRQ_EINT14, S3C2410_GPG(6) ,  S3C2410_GPG6_EINT14 , 3, "KEY3"},
        {IRQ_EINT15, S3C2410_GPG(7) ,  S3C2410_GPG7_EINT15 , 4, "KEY4"},
        {IRQ_EINT19, S3C2410_GPG(11),  S3C2410_GPG11_EINT19, 5, "KEY5"},
    };
    static volatile char key_values [] = {'0', '0', '0', '0', '0', '0'};
    
    static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
    
    static volatile int ev_press = 0;
    
    
    static irqreturn_t buttons_interrupt(int irq, void *dev_id)
    {
        struct button_irq_desc *button_irqs = (struct button_irq_desc *)dev_id;
        int down;
    
        // udelay(0);
        down = !s3c2410_gpio_getpin(button_irqs->pin);
    
        if (down != (key_values[button_irqs->number] & 1)) { // Changed
    
    	key_values[button_irqs->number] = '0' + down;
    	
            ev_press = 1;
            wake_up_interruptible(&button_waitq);
        }
        
        return IRQ_RETVAL(IRQ_HANDLED);
    }
    
    
    static int s3c24xx_buttons_open(struct inode *inode, struct file *file)
    {
        int i;
        int err = 0;
        
        for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) {
    	if (button_irqs[i].irq < 0) {
    		continue;
    	}
            err = request_irq(button_irqs[i].irq, buttons_interrupt, IRQ_TYPE_EDGE_BOTH, 
                              button_irqs[i].name, (void *)&button_irqs[i]);
            if (err)
                break;
        }
    
        if (err) {
            i--;
            for (; i >= 0; i--) {
    	    if (button_irqs[i].irq < 0) {
    		continue;
    	    }
    	    disable_irq(button_irqs[i].irq);
                free_irq(button_irqs[i].irq, (void *)&button_irqs[i]);
            }
            return -EBUSY;
        }
    
        ev_press = 1;
        
        return 0;
    }
    
    
    static int s3c24xx_buttons_close(struct inode *inode, struct file *file)
    {
        int i;
        
        for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) {
    	if (button_irqs[i].irq < 0) {
    	    continue;
    	}
    	free_irq(button_irqs[i].irq, (void *)&button_irqs[i]);
        }
    
        return 0;
    }
    
    
    static int s3c24xx_buttons_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
    {
        unsigned long err;
    
        if (!ev_press) {
    	if (filp->f_flags & O_NONBLOCK)
    	    return -EAGAIN;
    	else
    	    wait_event_interruptible(button_waitq, ev_press);
        }
        
        ev_press = 0;
    
        err = copy_to_user(buff, (const void *)key_values, min(sizeof(key_values), count));
    
        return err ? -EFAULT : min(sizeof(key_values), count);
    }
    
    static unsigned int s3c24xx_buttons_poll( struct file *file, struct poll_table_struct *wait)
    {
        unsigned int mask = 0;
        poll_wait(file, &button_waitq, wait);
        if (ev_press)
            mask |= POLLIN | POLLRDNORM;
        return mask;
    }
    
    
    static struct file_operations dev_fops = {
        .owner   =   THIS_MODULE,
        .open    =   s3c24xx_buttons_open,
        .release =   s3c24xx_buttons_close, 
        .read    =   s3c24xx_buttons_read,
        .poll    =   s3c24xx_buttons_poll,
    };
    
    static struct miscdevice misc = {
    	.minor = MISC_DYNAMIC_MINOR,
    	.name = DEVICE_NAME,
    	.fops = &dev_fops,
    };
    
    static int __init dev_init(void)
    {
    	int ret;
    
    	ret = misc_register(&misc);
    
    	printk (DEVICE_NAME"\tinitialized\n");
    
    	return ret;
    }
    
    static void __exit dev_exit(void)
    {
    	misc_deregister(&misc);
    }
    
    module_init(dev_init);
    module_exit(dev_exit);
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("FriendlyARM Inc.");
    
    

    下面,我们再来看看一个led的驱动以及调用驱动的测试程序:

    #include <linux/miscdevice.h>
    #include <linux/delay.h>
    #include <asm/irq.h>
    #include <mach/regs-gpio.h>
    #include <mach/hardware.h>
    #include <linux/kernel.h>
    #include <linux/module.h>
    #include <linux/init.h>
    #include <linux/mm.h>
    #include <linux/fs.h>
    #include <linux/types.h>
    #include <linux/delay.h>
    #include <linux/moduleparam.h>
    #include <linux/slab.h>
    #include <linux/errno.h>
    #include <linux/ioctl.h>
    #include <linux/cdev.h>
    #include <linux/string.h>
    #include <linux/list.h>
    #include <linux/pci.h>
    #include <linux/gpio.h>
    #include <asm/uaccess.h>
    #include <asm/atomic.h>
    #include <asm/unistd.h>
    
    
    #define DEVICE_NAME "leds"
    
    static unsigned long led_table [] = {
    	S3C2410_GPB(5),
    	S3C2410_GPB(6),
    	S3C2410_GPB(7),
    	S3C2410_GPB(8),
    };
    
    static unsigned int led_cfg_table [] = {
    	S3C2410_GPIO_OUTPUT,
    	S3C2410_GPIO_OUTPUT,
    	S3C2410_GPIO_OUTPUT,
    	S3C2410_GPIO_OUTPUT,
    };
    
    static int sbc2440_leds_ioctl(
    	struct inode *inode, 
    	struct file *file, 
    	unsigned int cmd, 
    	unsigned long arg)
    {
    	switch(cmd) {
    	case 0:
    	case 1:
    		if (arg > 4) {
    			return -EINVAL;
    		}
    		s3c2410_gpio_setpin(led_table[arg], !cmd);
    		return 0;
    	default:
    		return -EINVAL;
    	}
    }
    
    static struct file_operations dev_fops = {
    	.owner	=	THIS_MODULE,
    	.ioctl	=	sbc2440_leds_ioctl,
    };
    
    static struct miscdevice misc = {
    	.minor = MISC_DYNAMIC_MINOR,
    	.name = DEVICE_NAME,
    	.fops = &dev_fops,
    };
    
    static int __init dev_init(void)
    {
    	int ret;
    
    	int i;
    	
    	for (i = 0; i < 4; i++) {
    		s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]);
    		s3c2410_gpio_setpin(led_table[i], 0);
    	}
    
    	ret = misc_register(&misc);
    
    	printk (DEVICE_NAME"\tinitialized\n");
    
    	return ret;
    }
    
    static void __exit dev_exit(void)
    {
    	misc_deregister(&misc);
    }
    
    module_init(dev_init);
    module_exit(dev_exit);
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("FriendlyARM Inc.");
    
    

下面是leds的测试程序:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <string.h>

static int led_fd;
static int type = 1;

static void push_leds(void)
{
	static unsigned step;
	unsigned led_bitmap;
	int i;

	switch(type) {
	case 0:
		if (step >= 6) {
			step = 0;
		}
		if (step < 3) {
			led_bitmap = 1 << step;
		} else {
			led_bitmap = 1 << (6 - step);
		}
		break;
	case 1:
		if (step > 255) {
			step = 0;
		}
		led_bitmap = step;
		break;
	default:
		led_bitmap = 0;
	}
	step++;
	for (i = 0; i < 4; i++) {
		ioctl(led_fd, led_bitmap & 1, i);
		led_bitmap >>= 1;
	}
}

int main(void)
{
	int led_control_pipe;
	int null_writer_fd; // for read endpoint not blocking when control process exit

	double period = 0.5;

	led_fd = open("/dev/leds0", 0);
	if (led_fd < 0) {
		led_fd = open("/dev/leds", 0);
	}
	if (led_fd < 0) {
		perror("open device leds");
		exit(1);
	}
	unlink("/tmp/led-control");
	mkfifo("/tmp/led-control", 0666);

	led_control_pipe = open("/tmp/led-control", O_RDONLY | O_NONBLOCK);
	if (led_control_pipe < 0) {
		perror("open control pipe for read");
		exit(1);
	}
	null_writer_fd = open("/tmp/led-control", O_WRONLY | O_NONBLOCK);
	if (null_writer_fd < 0) {
		perror("open control pipe for write");
		exit(1);
	}

	for (;;) {
		fd_set rds;
		struct timeval step;
		int ret;

		FD_ZERO(&rds);
		FD_SET(led_control_pipe, &rds);
		step.tv_sec  = period;
		step.tv_usec = (period - step.tv_sec) * 1000000L;

		ret = select(led_control_pipe + 1, &rds, NULL, NULL, &step);
		if (ret < 0) {
			perror("select");
			exit(1);
		}
		if (ret == 0) {
			push_leds();
		} else if (FD_ISSET(led_control_pipe, &rds)) {
			static char buffer[200];
			for (;;) {
				char c;
				int len = strlen(buffer);
				if (len >= sizeof buffer - 1) {
					memset(buffer, 0, sizeof buffer);
					break;
				}
				if (read(led_control_pipe, &c, 1) != 1) {
					break;
				}
				if (c == '\r') {
					continue;
				}
				if (c == '\n') {
					int tmp_type;
					double tmp_period;
					if (sscanf(buffer,"%d%lf", &tmp_type, &tmp_period) == 2) {
						type = tmp_type;
						period = tmp_period;
					}
					fprintf(stderr, "type is %d, period is %lf\n", type, period);
					memset(buffer, 0, sizeof buffer);
					break;
				}
				buffer[len] = c;
			}
		}
	}

	close(led_fd);
	return 0;
}


重要的结构


  1. Struct file

    代表一个打开的文件。系统中每个打开的文件在内核空间都有一个关联的struct file。它由内核在打开文件的时候创建,在文件关闭后释放。

    重要的组成成员:

    ​ loff_t f_pos /*文件读写位置*/

    ​ struct file_operations *f_op

  2. Struct inode

    用来记录文件的物理上的信息。因此,它和代表打开的file结构是不同的。一个文件可以对应多个file结构,但只有一个inode结构

    重要的组成成员:

    ​ dev_t i_rdev : 设备号

  3. Struct file_operations

    一个函数指针的集合,定义能在设备上进行的操作,结构中的成员指向驱动中的函数,这些函数实现一个特别的操作,对于不支持的操作保留为NULL

    例如:

    Struct file_operations mem_fops={
      .owner=THIS_MODULE,
      .llseek=mem_seek,
      .read=mem_read,
      .write=mem_write,
      .ioctl=mem_ioctl,
      .open=mem_open,
      .release=mem_release,
    };
    

    =》把应用程序上的操作转化成驱动程序的对应函数操作!!!

  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值