动手做一个linux字符驱动

      最近学习了一本书《linux 设备驱动开发详解》——宋宝华老师写的,不过买的竟然是第二版的,里面是关于处理器s3c6410的,本来看这书的第一版是讲s3c2410,后来发现其实内容差不多了。

    学习了一下里面第2篇,设备驱动的核心理论,涉及很多,基础的知识需要反复的推敲才能明白。

    主要内容:

1、模块加载函数

static int __init combine_init(void)
{
//注册设备号和设备名
	int result;
	dev_t dev_num= MKDEV(combine_major,0);	
	//dev_t是cdev结构体的成员,定义了设备号,MAJOR(dev_t),MINOR(dev_t)可以分别获得主设备号和次设备号
	char dev_name[] = "combine";
	if(combine_major)
	{
		result = register_chrdev_region(dev_num, 1, dev_name);	
		//int register_chrdev_region(dev_t from,unsigned count,const char *name);	
		//第一个参数:设备号;第二个参数:连续分配的设备号;第三个参数:设备名}	
	else
	{
		result = alloc_chrdev_region(&dev_num,0,1,dev_name);		
		//int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char *name);	
		combine_major = MAJOR(dev_num);	
	}	
	if(result < 0)	
	{	
		printk("combine:unable to get major %d\n",combine_major);return result;	
	}	
	
//关联设备结构体cdev和文件操作结构体fops    	
    cdev_init(&combine_cdev, &combine_fops);	
    //static struct cdev combine_cdev; 	
    //void cdev_init(struct cdev *,struct file_operation *);	
    result = cdev_add(&combine_cdev, dev_num, 1);	
    //int cdev_add(struct cdev *,dev_t,unsigned);	
    if(result <0 )//加上函数的出错处理	
    {	
        printk("combine:unable to add cdev\n");	
    }	
    printk("combine device installed \nwith major %d,%s\n",combine_major,dev_name);	
    return 0;	
}


2、模块卸载函数

static void __exit combine_exit(void)
{
	cdev_del(&combine_cdev);	
	//void cdev_del(struct cdev *);
	unregister_chrdev_region(MKDEV(combine_major,0), 1);
	//void unregister_chrdev_region(dev_t from,unsigned count);
	printk("combine device uninstalled\n");}

3、很重要的一个结构体:file_operations文件操作结构体

static struct file_operations combine_fops = 
{
	.owner = THIS_MODULE,
	.open = combine_open,
	.release = combine_release,
	.read = combine_read,
	.write = combine_write,
	.ioctl = combine_ioctl,
};

然后就是在驱动函数中利用

中断屏蔽local_irq_disable(),local_irq_enable(),

自旋锁spinlock_t,

信号量struct semaphore sem,

完成量struct completion,

互斥体struct mutex来解决并发与竞态问题。

4、阻塞操作

当应用程序进行read(),write()等系统调用时,若设备的资源不能获取,而用户又希望以阻塞的方式访问设备,驱动程序应在设备驱动的xxx_read(),xxx_write()等操作中将进程阻塞直到资源可以获取,此后应用程序的read(),write()等调用才返回。

用于设备阻塞操作可以使用等待队列wait_queue_head_t my_queue;

将事件加入等待队列wait_event_interrupt(my_queue,condition);

将事件从等待队列中唤醒wake_up_interrupt(&my_queue);


或者利用unsigned int poll(struct file *filp,struct poll_table *wait);查询是否可对设备进行无阻塞访问

struct unsigned int xxx_poll(struct file *filp,poll_table *wait)
{
	unsigned int mask=0;
	struct xxx_dev *dev = filp->private_data;//获取设备结构体指针
	poll_wait(filp,my_queue,wait);
	//这个函数加了之后再read中就不用再wait_event_interrupt,
	//因为在这里已经实现了阻塞,一旦资源可用就可以在read中实现无阻塞访问	
	//像read中wait_event_interrupt一样被wake_up_interrupt唤醒

	if(...)//可读	
            mask |= POLLIN | POLLRDNORM;//标示数据可获得	
        if(...)//可写	
            mask |= POLLOUT | POLLWRNORM;//标示数据可写入	
        return mask;<pre name="code" class="cpp">}
 
 


5、linux中断编程

<pre name="code" class="html">struct key_irq_desc 
{
	unsigned int irq;
	int pin;
	int pin_setting;
	int number;
	char *name;
};
static struct key_irq_desc key_irqs[] = 
{
	{IRQ_EINT8,	S3C2410_GPG(0),	S3C2410_GPG0_EINT8,    0,	"key1"},
	{IRQ_EINT11,	S3C2410_GPG(3),	S3C2410_GPG3_EINT11,   1,	"key2"},
	{IRQ_EINT13,	S3C2410_GPG(5),	S3C2410_GPG5_EINT13,   2,	"key3"},
	{IRQ_EINT14,	S3C2410_GPG(6),	S3C2410_GPG6_EINT14,   3,	"key4"},
	{IRQ_EINT15,	S3C2410_GPG(7),	S3C2410_GPG7_EINT15,   4,	"key5"},
	{IRQ_EINT19,	S3C2410_GPG(11),S3C2410_GPG11_EINT19,  5,	"key6"},
};
static volatile int key_values[] = {0,0,0,0,0,0};
static DECLARE_WAIT_QUEUE_HEAD(key_waitq);
static volatile int ev_press = 0;
static void my_tasklet_func(unsigned long data)
{
	printk("key do tasklet\n");
}
static DECLARE_TASKLET(my_tasklet,my_tasklet_func,0);

static irqreturn_t key_interrupt(int irq,void *dev_id)//中断顶半部函数
{
	struct key_irq_desc *key_irqs = (struct key_irq_desc *)dev_id;
	int up = s3c2410_gpio_getpin(key_irqs->pin);

	printk("<1>up=%d\n",up);
	if(up)//up 按下去是0,没按是1
		key_values[key_irqs->number] = (key_irqs->number + 1) + 0x80;
	else
		key_values[key_irqs->number] += 1;

	ev_press = 1;
	wake_up_interruptible(&key_waitq);
	
	tasklet_schedule(&my_tasklet);//在顶半部函数中调度执行底半部函数

	return IRQ_RETVAL(IRQ_HANDLED);
}

static int combine_open(struct inode *inode,struct file *file)
{
	int i,err;
	
	for(i = 0;i < sizeof(key_irqs) / sizeof(key_irqs[0]);i++)
	{
		s3c2410_gpio_cfgpin(key_irqs[i].pin,key_irqs[i].pin_setting);
		err = request_irq(key_irqs[i].irq, key_interrupt, 0, key_irqs[i].name, (void *)&key_irqs[i]);	
		//int request_irq(unsigned int irq,irq_handler_t handler,unsigned long irqflags,const char *devname,void *dev_id);		//handler是向系统登记的中断处理顶半部函数,dev_id是传给他的参数			
		set_irq_type(key_irqs[i].irq,IRQ_TYPE_EDGE_BOTH);
		if(err) break;
	}
	if(err)
	{
		i--;
		for(;i >= 0;i--)
		{
			disable_irq(key_irqs[i].irq);
			free_irq(key_irqs[i].irq, (void *)&key_irqs[i]);
			//void free_irq(unsigned int irq,void *dev_id);	
		}return -EBUSY;}return 0;}
 
 


6、下面是完整的按键与蜂鸣器结合的字符驱动函数,资源上传于:http://download.csdn.net/detail/luckywang1103/6537111

<pre name="code" class="cpp">/*********************************************************************************************
* File:		conbine.c
* Author:		luckywang
* Desc:	组合各种驱动,驱动6个按键,和beep驱动,但是现在没有将两者结合起来,只是各自独立驱动  
* History:	2013.11.09
*********************************************************************************************/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/serio.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/miscdevice.h>
#include <linux/gpio.h>
#include <asm/io.h>
#include <linux/irq.h>
#include <asm/irq.h>
#include <asm/uaccess.h>
#include <mach/regs-clock.h>
#include <plat/regs-timer.h>
#include <mach/regs-gpio.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <mach/hardware.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/wait.h>

static int combine_major = 0;
static struct cdev combine_cdev; 

#define BEEP_MAGIC 'k'
#define BEEP_START_CMD _IO(BEEP_MAGIC,1)
#define BEEP_STOP_CMD _IO(BEEP_MAGIC,2)

struct key_irq_desc 
{
	unsigned int irq;
	int pin;
	int pin_setting;
	int number;
	char *name;
};

static struct key_irq_desc key_irqs[] = 
{
	{IRQ_EINT8,	S3C2410_GPG(0),	S3C2410_GPG0_EINT8,	    0,	"key1"},
	{IRQ_EINT11,	S3C2410_GPG(3),	S3C2410_GPG3_EINT11,   1,	"key2"},
	{IRQ_EINT13,	S3C2410_GPG(5),	S3C2410_GPG5_EINT13,   2,	"key3"},
	{IRQ_EINT14,	S3C2410_GPG(6),	S3C2410_GPG6_EINT14,   3,	"key4"},
	{IRQ_EINT15,	S3C2410_GPG(7),	S3C2410_GPG7_EINT15,   4,	"key5"},
	{IRQ_EINT19,	S3C2410_GPG(11),S3C2410_GPG11_EINT19,5,	"key6"},
};

static volatile int key_values[] = {0,0,0,0,0,0};
static DECLARE_WAIT_QUEUE_HEAD(key_waitq);
static volatile int ev_press = 0;

static void my_tasklet_func(unsigned long data)
{
	printk("key do tasklet\n");
}
static DECLARE_TASKLET(my_tasklet,my_tasklet_func,0);

static irqreturn_t key_interrupt(int irq,void *dev_id)
{
	struct key_irq_desc *key_irqs = (struct key_irq_desc *)dev_id;
	int up = s3c2410_gpio_getpin(key_irqs->pin);

	printk("<1>up=%d\n",up);
	if(up)//up 按下去是0,没按是1
		key_values[key_irqs->number] = (key_irqs->number + 1) + 0x80;
	else
		key_values[key_irqs->number] += 1;

	ev_press = 1;
	wake_up_interruptible(&key_waitq);
	
	tasklet_schedule(&my_tasklet);

	return IRQ_RETVAL(IRQ_HANDLED);
}

static int combine_open(struct inode *inode,struct file *file)
{
	int i,err;
	
	for(i = 0;i < sizeof(key_irqs) / sizeof(key_irqs[0]);i++)
	{
		s3c2410_gpio_cfgpin(key_irqs[i].pin,key_irqs[i].pin_setting);
		err = request_irq(key_irqs[i].irq, key_interrupt, 0, key_irqs[i].name, (void *)&key_irqs[i]);		
		set_irq_type(key_irqs[i].irq,IRQ_TYPE_EDGE_BOTH);
		if(err) break;
	}
	if(err)
	{
		i--;
		for(;i >= 0;i--)
		{
			disable_irq(key_irqs[i].irq);
			free_irq(key_irqs[i].irq, (void *)&key_irqs[i]);
		}
		return -EBUSY;
	}
	return 0;
}

static int combine_release(struct inode *inode,struct file *file)
{
	int i;
	for(i = 0;i < sizeof(key_irqs) / sizeof(key_irqs[i]);i++)
	{
		disable_irq(key_irqs[i].irq);
		free_irq(key_irqs[i].irq,(void *)&key_irqs[i]);
	}
	return 0;
}

static int combine_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(key_waitq, ev_press);
	}

	ev_press = 0;
	err = copy_to_user(buff,(const void *)key_values,min(sizeof(key_values),count));
	memset((void *)key_values,0,sizeof(key_values));
	
	return err ? -EFAULT : min(sizeof(key_values),count);
}

static int combine_write(struct file *file,const char __user *buff,size_t count,loff_t *offp)
{
	
	return 0;
}

void beep_start(void)
{
	s3c2410_gpio_pullup(S3C2410_GPB(0),1);
	s3c2410_gpio_cfgpin(S3C2410_GPB(0), S3C2410_GPIO_OUTPUT);
	s3c2410_gpio_setpin(S3C2410_GPB(0),1);
}

void beep_stop(void)
{
	s3c2410_gpio_cfgpin(S3C2410_GPB(0), S3C2410_GPIO_OUTPUT);
	s3c2410_gpio_setpin(S3C2410_GPB(0),0);

}

static int combine_ioctl(struct inode *inode,struct file *file,unsigned int cmd,unsigned long arg)
{
	switch(cmd)
	{
		case BEEP_START_CMD:
			beep_start();
			break;
		case BEEP_STOP_CMD:
			beep_stop();
			break;
		default:			
			break;
				
	}
	
	return 0;
}

static struct file_operations combine_fops = 
{
	.owner = THIS_MODULE,
	.open = combine_open,
	.release = combine_release,
	.read = combine_read,
	.write = combine_write,
	.ioctl = combine_ioctl,
};
	
static int __init combine_init(void)
{
//注册设备号和设备名
	int result;
	dev_t dev_num= MKDEV(combine_major,0);
	char dev_name[] = "combine";
	if(combine_major)
	{
		result = register_chrdev_region(dev_num, 1, dev_name);
	}
	else
	{
		result = alloc_chrdev_region(&dev_num,0,1,dev_name);
		combine_major = MAJOR(dev_num);
	}
	if(result < 0)
	{
		printk("combine:unable to get major %d\n",combine_major);
		return result;
	}

//关联设备结构体cdev和文件操作结构体fops
	cdev_init(&combine_cdev, &combine_fops);
	result = cdev_add(&combine_cdev, dev_num, 1);
	if(result <0 )//加上函数的出错处理
	{
		printk("combine:unable to add cdev\n");
	}
	
	printk("combine device installed \nwith major %d,%s\n",combine_major,dev_name);
	
	return 0;
}

static void __exit combine_exit(void)
{
	//printk("combine driver exit\n");
	cdev_del(&combine_cdev);
	unregister_chrdev_region(MKDEV(combine_major,0), 1);

	printk("combine device uninstalled\n");
}

module_init(combine_init);
module_exit(combine_exit);

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("luckywang");
MODULE_DESCRIPTION("s3c2440 combine driver");
MODULE_VERSION("V0.1");


 
 


测试程序

<p></p><p><span style="font-size:14px;"></span></p><pre name="code" class="cpp">/*********************************************************************************************
* File:	combine_test.c
* Author:	luckkywang
* Desc:   combine test code
* History:2013.11.10
*********************************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <linux/ioctl.h>
#include<errno.h>

#define BEEP_MAGIC 'k'
#define BEEP_START_CMD _IO (BEEP_MAGIC, 1)
#define BEEP_STOP_CMD _IO (BEEP_MAGIC, 2)

int main()
{
	int i = 0;
	int key_values[] = {0,0,0,0,0,0};
	int dev_fd;
	int ret;
	dev_fd = open("/dev/combine",O_RDWR | O_NONBLOCK);
	if ( dev_fd == -1 ) {
		printf("Cann't open file /dev/beep\n");
		exit(1);
	}
	while(1)
	{
		ret = read(dev_fd,key_values,sizeof(key_values));
		if(ret != sizeof(key_values))
		{
			if(errno != EAGAIN)
				perror("read key\n");
			continue;
		}
		else
		{
			for(i = 0;i < 6;i++)
			{
				printf("K%d %s,key_value=0x%02x\n",i+1,(key_values[i] & 0x80) ? "released" : key_values[i] ? "pressed down":"",key_values[i]);
			}
			key_values[i] = 0;
		}
	}	

	
	/*printf("Start beep\n");
	ioctl (dev_fd, BEEP_START_CMD,0);
	getchar();
	ioctl (dev_fd, BEEP_STOP_CMD,0);
	printf("Stop beep and Close device\n");*/
	close(dev_fd);
	return 0;
}

 

你可能感兴趣的:(动手做一个linux字符驱动)