回顾:内核竞态与并发
创新互联建站网络公司拥有10余年的成都网站开发建设经验,成百上千客户的共同信赖。提供成都网站设计、网站制作、外贸营销网站建设、网站开发、网站定制、外链、建网站、网站搭建、成都响应式网站建设、网页设计师打造企业风格,提供周到的售前咨询和贴心的售后服务
什么情况下会产生竞态
1)SMP
2)单CPU支持任务抢占
3)中断和进程之间
4)中断和中断之间
解决竞态的方法
1)中断屏蔽
2)原子操作
位原子操作
×××原子操作 atomic{ int。。。。}
3)自旋锁
优点:一旦可以获取锁,立即获取
缺点:长时间获取锁不成功,会消耗CPU资源
它所保护的临界资源(代码段)通常比较短
4)信号量
down(。。。)会导致睡眠
等等看前一章中
自旋锁只允许一个持有者,信号量可以有多个持有者
信号量保护的临界资源(代码段)通常比较长
2,等待队列 #include
Read() recv()
Wait_event_interrptible //阻塞
Wake_up_interruptible //唤醒
阻塞/非阻塞
实际上,应用程序并不关心驱动里面read/write具体实现,只管调用并获取返回值
如果设备没有准备好数据给应用程序读或者没有准备好接受用户程序写,驱动程序应当阻塞进程,使它进入睡眠,直到请求可以得到满足
阻塞读
在阻塞型驱动程序中,如果进程调用read设备操作,但是设备没有数据或数据不足,进程应该被阻塞,当有新数据到达后,唤醒被阻塞进程
阻塞写
在阻塞型驱动程序中,如果进程调用write设备操作,但是设备没有足够的空间供其写入,进程该被阻塞,但设备中的数据读走后,缓冲区中空出部分空间,应该唤醒被阻塞进程
应用程序非阻塞读
阻塞方式是文件读写操作的默认方式
应用程序可以通过使用O_NONBLOCK标志来人为的设置读写操作为非阻塞方式
定义在
如果设置了O_NONBLOCK标志,read和write的处理行为相同
if(0==pcdevp->)&&(O_NONBLICK&file->f_flags){
printk(KERN_ALET “Char......”);
return -EAGAIN;
---------------------------------------------------------------
多路监听侦测 select #include
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
Nfds 要监听的文件描述符最大值+1
Readfds,要监听的读文件描述符集合
Writefds要监听的写文件描述符集合
Exceptfds 要监听的异常文件描述集合
Timeout 监听的超时时间
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
内核中要实现的函数
Unsigned int(*poll)()
Fd_set rfds;
Struct timeval tv
先
FD_ZERO()
加入集合FD_SET()
POLLIN
有数据可读。
POLLRDNORM
有普通数据可读。
POLLRDBAND
有优先数据可读。
POLLPRI
有紧迫数据可读。
POLLOUT
写数据不会导致阻塞。
POLLWRNORM
写普通数据不会导致阻塞。
POLLWRBAND
写优先数据不会导致阻塞。
POLLMSG
SIGPOLL 消息可用。
此外,revents域中还可能返回下列事件:
POLLER
指定的文件描述符发生错误。
POLLHUP
指定的文件描述符挂起事件。
POLLNVAL
指定的文件描述符非法。
这些事件在events域中无意义,因为它们在合适的时候总是会从revents中返回。使用poll()和select()不一样,你不需要显式地请求异常情况报告。
POLLIN | POLLPRI等价于select()的读事件,POLLOUT |POLLWRBAND等价于select()的写事件。POLLIN等价于POLLRDNORM |POLLRDBAND,而POLLOUT则等价于POLLWRNORM。
中断
驱动需要包含 #include
判断一个IO是否合法:int gpio_is_valid(int number);
设置GPIO的方向,如果是输出同时设置电平:
/* set as input or output, returning 0 or negative errno */
int gpio_direction_input(unsigned gpio);
int gpio_direction_output(unsigned gpio, int value);
获取输入引脚的电平:
/* GPIO INPUT: return zero or nonzero */
int gpio_get_value(unsigned gpio);
/* GPIO OUTPUT */
void gpio_set_value(unsigned gpio, int value);
int gpio_cansleep(unsigned gpio);
To access such GPIOs, a different set of accessors is defined:
/* GPIO INPUT: return zero or nonzero, might sleep */
int gpio_get_value_cansleep(unsigned gpio);
/* GPIO OUTPUT, might sleep */
void gpio_set_value_cansleep(unsigned gpio, int value);
获取一个GPIO并声明标签:
/* request GPIO, returning 0 or negative errno.
* non-null labels may be useful for diagnostics.
*/
int gpio_request(unsigned gpio, const char *label);
/* release previously-claimed GPIO */
void gpio_free(unsigned gpio);
将GPIO映射为IRQ中断:
/* map GPIO numbers to IRQ numbers */
int gpio_to_irq(unsigned gpio);
/* map IRQ numbers to GPIO numbers (avoid using this) */
int irq_to_gpio(unsigned irq);
设置GPIO IRQ中断类型:
if (!sw->both_edges) {
if (gpio_get_value(sw->gpio))
set_irq_type(gpio_to_irq(sw->gpio), IRQ_TYPE_EDGE_FALLING);
else
set_irq_type(gpio_to_irq(sw->gpio), IRQ_TYPE_EDGE_RISING);
在驱动中使用延时函数mdelay,需要包含
对于硬件产生的变化,需要CPI进行处理,通过情况下CPU获取该变化的方式有种
1)轮询
2)中断 #include
Request irq(...)
注册中断request_irq(...)
Irq 要注册的中断号 arm/mach-xxxxx/include/mach/irqs.h
Gpio_to_irq()
handle 中断处理函数
Irqreturn_t xxxxx(int irq,void *dev_id)
Irqflags 中断标志
Cat /proc/interrupts
IRQF_SHARED
flag定义描述
IRQF_TRIGGER_XXX描述该interrupt line触发类型的flag
IRQF_DISABLED首先要说明的是这是一个废弃的flag,在新的内核中,该flag没有任何的作用了。具体可以参考:Disabling IRQF_DISABLED
旧的内核(2.6.35版本之前)认为有两种interrupt handler:slow handler和fast handle。在request irq的时候,对于fast handler,需要传递IRQF_DISABLED的参数,确保其中断处理过程中是关闭CPU的中断,因为是fast handler,执行很快,即便是关闭CPU中断不会影响系统的性能。但是,并不是每一种外设中断的handler都是那么快(例如磁盘),因此就有 slow handler的概念,说明其在中断处理过程中会耗时比较长。对于这种情况,在执行interrupt handler的时候不能关闭CPU中断,否则对系统的performance会有影响。
新的内核已经不区分slow handler和fast handle,都是fast handler,都是需要关闭CPU中断的,那些需要后续处理的内容推到threaded interrupt handler中去执行。
IRQF_SHARED这是flag用来描述一个interrupt line是否允许在多个设备中共享。如果中断控制器可以支持足够多的interrupt source,那么在两个外设间共享一个interrupt request line是不推荐的,毕竟有一些额外的开销(发生中断的时候要逐个询问是不是你的中断,软件上就是遍历action list),因此外设的irq handler中最好是一开始就启动判断,看看是否是自己的中断,如果不是,返回IRQ_NONE,表示这个中断不归我管。 早期PC时代,使用8259中断控制器,级联的8259最多支持15个外部中断,但是PC外设那么多,因此需要irq share。现在,ARM平台上的系统设计很少会采用外设共享IRQ方式,毕竟一般ARM SOC提供的有中断功能的GPIO非常的多,足够用的。 当然,如果确实需要两个外设共享IRQ,那也只能如此设计了。对于HW,中断控制器的一个interrupt source的引脚要接到两个外设的interrupt request line上,怎么接?直接连接可以吗?当然不行,对于低电平触发的情况,我们可以考虑用与门连接中断控制器和外设。
IRQF_PROBE_SHAREDIRQF_SHARED用来表示该interrupt action descriptor是允许和其他device共享一个interrupt line(IRQ number),但是实际上是否能够share还是需要其他条件:例如触发方式必须相同。有些驱动程序可能有这样的调用场景:我只是想scan一个irq table,看看哪一个是OK的,这时候,如果即便是不能和其他的驱动程序share这个interrupt line,我也没有关系,我就是想scan看看情况。这时候,caller其实可以预见sharing mismatche的发生,因此,不需要内核打印“Flags mismatch irq……“这样冗余的信息
IRQF_PERCPU在SMP的架构下,中断有两种mode,一种中断是在所有processor之间共享的,也就是global的,一旦中断产生,interrupt controller可以把这个中断送达多个处理器。当然,在具体实现的时候不会同时将中断送达多个CPU,一般是软件和硬件协同处理,将中断送达一个CPU处理。但是一段时间内产生的中断可以平均(或者按照既定的策略)分配到一组CPU上。这种interrupt mode下,interrupt controller针对该中断的operational register是global的,所有的CPU看到的都是一套寄存器,一旦一个CPU ack了该中断,那么其他的CPU看到的该interupt source的状态也是已经ack的状态。
和global对应的就是per cpu interrupt了,对于这种interrupt,不是processor之间共享的,而是特定属于一个CPU的。例如GIC中interrupt ID等于30的中断就是per cpu的(这个中断event被用于各个CPU的local timer),这个中断号虽然只有一个,但是,实际上控制该interrupt ID的寄存器有n组(如果系统中有n个processor),每个CPU看到的是不同的控制寄存器。在具体实现中,这些寄存器组有两种形态,一种是banked,所有CPU操作同样的寄存器地址,硬件系统会根据访问的cpu定向到不同的寄存器,另外一种是non banked,也就是说,对于该interrupt source,每个cpu都有自己独特的访问地址。
IRQF_NOBALANCING这也是和multi-processor相关的一个flag。对于那些可以在多个CPU之间共享的中断,具体送达哪一个processor是有策略的,我们可以在多个CPU之间进行平衡。如果你不想让你的中断参与到irq balancing的过程中那么就设定这个flag
IRQF_IRQPOLL
IRQF_ONESHOTone shot本身的意思的只有一次的,结合到中断这个场景,则表示中断是一次性触发的,不能嵌套。对于primary handler,当然是不会嵌套,但是对于threaded interrupt handler,我们有两种选择,一种是mask该interrupt source,另外一种是unmask该interrupt source。一旦mask住该interrupt source,那么该interrupt source的中断在整个threaded interrupt handler处理过程中都是不会再次触发的,也就是one shot了。这种handler不需要考虑重入问题。
具体是否要设定one shot的flag是和硬件系统有关的,我们举一个例子,比如电池驱动,电池里面有一个电量计,是使用HDQ协议进行通信的,电池驱动会注册一个threaded interrupt handler,在这个handler中,会通过HDQ协议和电量计进行通信。对于这个handler,通过HDQ进行通信是需要一个完整的HDQ交互过程,如果中间被打断,整个通信过程会出问题,因此,这个handler就必须是one shot的。
IRQF_NO_SUSPEND这个flag比较好理解,就是说在系统suspend的时候,不用disable这个中断,如果disable,可能会导致系统不能正常的resume。
IRQF_FORCE_RESUME在系统resume的过程中,强制必须进行enable的动作,即便是设定了IRQF_NO_SUSPEND这个flag。这是和特定的硬件行为相关的。
IRQF_NO_THREAD有些low level的interrupt是不能线程化的(例如系统timer的中断),这个flag就是起这个作用的。另外,有些级联的interrupt controller对应的IRQ也是不能线程化的(例如secondary GIC对应的IRQ),它的线程化可能会影响一大批附属于该interrupt controller的外设的中断响应延迟。
IRQF_EARLY_RESUME
IRQF_TIMER
如何判断哪个按键触发的中断
如何判断怎么触发(上升沿,下降沿,高电平,低电平)中断的
使用管脚的输入功能(配置成输入功能)判断电平
重新配置为外部中断功能
如何去抖动
Linux内核中中断处理程序的一般结构
顶半部:完成尽可能少的紧急功能,往往是简单的读取寄存器,清除中断标志。登记底半部。
底半部:完成中断处理程序中绝大部分工作,通常这部分都比较耗时。
1)软中断
2)tasklet(利用了软中断的机制)
struct tasklet_struct{
Void(*func)(unsigned long);//底半部完成函数
Unsigned long data;
。。。
}
tasklet_scedule(。。。)//完成底半部的登记
DECLARE_TASKLET 定义并初始化
3)工作者队列
Struct work_struct{
}
INIT_WAORK//初始化work
Schrdiule_work //登记work
flush_work//
Tasklet和工作者队列有啥区别
Asklet中的func函数工作于中断上下文,func不允许睡眠的,work中的func工作于进程上下文
IO与内存:
统一编址(ARM):
MOV
独立编址(X86):
MOV R0 [0X100]
IN/OUT 0X100
ARM PowePC MPIS 都是用统一编址
X86使用独立编址
Linux编程使用到的都是虚拟地址,驱动开发时,从芯片手册得到的物理地址,需要转换成虚拟地址后再使用
/*包含初始化宏定义的头文件,代码中的module_init和module_exit在此文件中*/
#include
/*包含初始化加载模块的头文件,代码中的MODULE_LICENSE在此头文件中*/
#include
/*定义module_param module_param_array的头文件*/
#include
/*定义module_param module_param_array中perm的头文件*/
#include
/*三个字符设备函数*/
#include
/*MKDEV转换设备号数据类型的宏定义*/
#include
/*定义字符设备的结构体*/
#include
/*分配内存空间函数头文件*/
#include
/*包含函数device_creatchar_driver_ledse 结构体class等头文件*/
#include
#include
/*自定义头文件*/
#include "char_driver_leds.h"
#include
//#include
#include
#include
#include
#include
/*Linux中申请GPIO的头文件*/
#include
/*三星平台的GPIO配置函数头文件*/
/*三星平台EXYNOS系列平台,GPIO配置参数宏定义头文件*/
#include
/*三星平台4412平台,GPIO宏定义头文件*/
#include
MODULE_LICENSE("Dual BSD/GPL");
/*声明是开源的,没有内核版本限制*/
MODULE_AUTHOR("songmao");
/*声明作者*/
static int led_gpios[] = {
EXYNOS4_GPL2(0),EXYNOS4_GPK1(1),
};
#define LED_NUM ARRAY_SIZE(led_gpios)
static int irq_gpio[] ={
EXYNOS4_GPX1(1),EXYNOS4_GPX1(2),
EXYNOS4_GPX1(0),EXYNOS4_GPX1(1)
};
#define IRQ_NUM ARRAY_SIZE(irq_gpio)
int led_num[4];
int numdev_major = DEV_MAJOR;
int numdev_minor = DEV_MINOR;
/*输入主设备号*/
module_param(numdev_major,int,S_IRUSR);
/*输入次设备号*/
module_param(numdev_minor,int,S_IRUSR);
static struct class *myclass;
struct reg_dev *my_devices;
/*打开操作*/
static int chardevnode_open(struct inode *inode, struct file *file)
{
struct reg_dev *reg_devp =NULL;
reg_devp =container_of(inode->i_cdev,struct reg_dev,cdev);
file->private_data=reg_devp;
printk(KERN_EMERG "chardevnode_open is success!\n");
/*if(!atomic_dec_and_test(&(reg_devp->atc)))
{
printk(KERN_ERR "atomic:device can open only once!");
atomic_inc(&(reg_devp->atc));
return -EBUSY;
}*/
spin_lock(&(reg_devp->lock));
if(OPEN_NUM<=reg_devp->open_num){
spin_unlock(&(reg_devp->lock));
printk(KERN_ERR "atomic:device can open over num!");
return -EBUSY;
}
reg_devp->open_num++;
spin_unlock(&(reg_devp->lock));
return 0;
}
/*关闭操作*/
static int chardevnode_release(struct inode *inode, struct file *file)
{
struct reg_dev *reg_devp =NULL;
reg_devp=file->private_data;
printk(KERN_EMERG "chardevnode_release is success!\n");
//atomic_inc(&(reg_devp->atc));
/*spin_lock(&(reg_devp->lock));
reg_devp->open_num--;
spin_unlock(&(reg_devp->lock));*/
up(&(reg_devp->sem_open));
return 0;
}
/*IO操作*/
static long chardevnode_ioctl(struct file *file, unsigned int cmd, unsigned long arg){
struct reg_dev *reg_devp =NULL;
reg_devp=file->private_data;
switch(cmd)
{
case 0:
case 1:
if (arg > LED_NUM) {
return -EINVAL;
}
gpio_set_value(led_gpios[arg], cmd);
break;
default:
return -EINVAL;
}
printk(KERN_EMERG "chardevnode_ioctl is success! cmd is %d,arg is %d \n",cmd,arg);
up(&(reg_devp->sem_read));
up(&(reg_devp->sem_write));
return 0;
}
ssize_t chardevnode_read(struct file *file, char __user *buf, size_t count, loff_t *f_ops)
{
struct reg_dev *reg_devp =NULL;
reg_devp=file->private_data;
//down_interruptible(&(reg_devp->sem_read));
wait_event_interruptible(reg_devp->wqh,0!=reg_devp->led);
reg_devp->led=0;
printk(KERN_INFO"chardevnode_read success");
return 0;
}
ssize_t chardevnode_write(struct file *file, const char __user *buf, size_t count, loff_t *f_ops)
{
struct reg_dev *reg_devp =NULL;
reg_devp=file->private_data;
//down_interruptible(&(reg_devp->sem_write));
reg_devp->led=1;
wake_up_interruptible(&(reg_devp->wqh));
printk(KERN_INFO"chardevnode_write success");
return 0;
}
loff_t chardevnode_llseek(struct file *file, loff_t offset, int ence){
return 0;
}
struct file_operations my_fops = {
.owner = THIS_MODULE,
.open = chardevnode_open,
.release = chardevnode_release,
.unlocked_ioctl = chardevnode_ioctl,
.read = chardevnode_read,
.write = chardevnode_write,
.llseek = chardevnode_llseek,
};
static void chardeviced_cdd_work_func(struct work_struct *work)
{
int i = 0,j,ledss;
struct reg_dev *pcdevp = container_of(work,struct reg_dev,cdd_work);
printk(KERN_INFO" CharDeviceDriver: Entry cdd_work");
printk(KERN_ALERT"CharDeviceDriver:CDD work name:%s \n",pcdevp->cdd_work_name);
for(i=0;i<4;i++)
{
if(led_num[i]>0)
{
j=i%2;
if(i>1)
ledss=1;
else
ledss=0;
gpio_set_value(led_gpios[j],ledss);
}
}
static void chardevicedriver_cdd_delayed_work_func(struct work_struct *work)
{
struct reg_dev *pcdevp = container_of(work,struct reg_dev,cdd_delayed_work);
printk(KERN_INFO" CharDeviceDriver: Entry chardeviceddriver_cdd_delayed_work_func");
printk(KERN_ALERT"CharDeviceDriver:CDD delayed work namename:%s \n",pcdevp->cdd_delayed_work_name);
}
static irq_handler_t chardevnode_irq(int irq,void *dev_id)
{
struct reg_dev *reg_devp = (struct reg_dev *)dev_id;
for(i=0;i s3c_gpio_cfgpin(irq_gpio[i], S3C_GPIO_SFN(0X0)); led_num[i] = gpio_get_value(irq_gpio[i]); s3c_gpio_cfgpin(irq_gpio[i], S3C_GPIO_SFN(0X0)); } tasklet_schedule(&(my_devices)); schedule_work(&(reg_devp->cdd_work)); queue_delayed_work(reg_devp->cdd_workqueue,&(reg_devp->cdd_delayed_work),3*HZ); return IRQ_HANDLED; } /*设备注册到系统*/ static void reg_init_cdev(struct reg_dev *dev,int index){ int err; int devno = MKDEV(numdev_major,numdev_minor+index); /*数据初始化*/ cdev_init(&dev->cdev,&my_fops); dev->cdev.owner = THIS_MODULE; dev->cdev.ops = &my_fops; /*注册到系统*/ err = cdev_add(&dev->cdev,devno,1); if(err){ printk(KERN_EMERG "cdev_add %d is fail! %d\n",index,err); } else{ printk(KERN_EMERG "cdev_add %d is success!\n",numdev_minor+index); } } static int gpio_init(void){ int i=0,ret; for(i=0;i ret = gpio_request(led_gpios[i], "LED"); if (ret) { printk("%s: request GPIO %d for LED failed, ret = %d\n", DEVICE_NAME,i,ret); return -1; } else{ s3c_gpio_cfgpin(led_gpios[i], S3C_GPIO_OUTPUT); gpio_set_value(led_gpios[i], 1); } } for(i=0;i ret = gpio_request(irq_gpio[i], "IRQ"); if (ret) { printk("%s: request GPIO %d for IRQ failed, ret = %d\n", DEVICE_NAME,i,ret); return -1; } else{ s3c_gpio_cfgpin(irq_gpio[i], S3C_GPIO_SFN(0X0F)); } } ret = request_irq(IRQ_EINT(9),chardevnode_irq,IRQF_SAMPLE_RANDOM|IRQF_TRIGGER_LOW,"interrupt0",my_devices); if(0>ret){ printk(KERN_ERR "Chardevnode_irq:Failure is IRQ_EINT(9)"); goto FAIL_IRQ_EINT; } ret = request_irq(IRQ_EINT(10),chardevnode_irq,IRQF_SAMPLE_RANDOM|IRQF_TRIGGER_LOW,"interrupt1",my_devices); if(0>ret){ printk(KERN_ERR "Chardevnode_irq:Failure is IRQ_EINT(10)"); } ret = request_irq(IRQ_EINT(17),chardevnode_irq,IRQF_SAMPLE_RANDOM|IRQF_TRIGGER_LOW,"interrupt2",my_devices); if(0>ret){ printk(KERN_ERR "Chardevnode_irq:Failure is IRQ_EINT(17)"); } ret = request_irq(IRQ_EINT(18),chardevnode_irq,IRQF_SAMPLE_RANDOM|IRQF_TRIGGER_LOW,"interrupt3",my_devices); if(0>ret){ printk(KERN_ERR "Chardevnode_irq:Failure is IRQ_EINT(17)"); } printk(KERN_INFO"Chardevnode_irq:Success is IRQ_EINT"); return 0; FAIL_IRQ_EINT: free_irq(IRQ_EINT(9),my_devices); free_irq(IRQ_EINT(10),my_devices); free_irq(IRQ_EINT(17),my_devices); free_irq(IRQ_EINT(18),my_devices); for(i=0;i gpio_free(led_gpios[i]); for(i=0;i ret = gpio_free(irq_gpio[i]); } static int __init scdev_init(void) { int ret = 0,i; dev_t num_dev; printk(KERN_EMERG "numdev_major is %d!\n",numdev_major); printk(KERN_EMERG "numdev_minor is %d!\n",numdev_minor); if(numdev_major){ num_dev = MKDEV(numdev_major,numdev_minor); ret = register_chrdev_region(num_dev,DEVICE_MINOR_NUM,DEVICE_NAME); } else{ /*动态注册设备号*/ ret = alloc_chrdev_region(&num_dev,numdev_minor,DEVICE_MINOR_NUM,DEVICE_NAME); /*获得主设备号*/ numdev_major = MAJOR(num_dev); printk(KERN_EMERG "adev_region req %d !\n",numdev_major); } if(ret<0){ printk(KERN_EMERG "register_chrdev_region req %d is failed!\n",numdev_major); } myclass = class_create(THIS_MODULE,DEVICE_NAME); my_devices = kmalloc(DEVICE_MINOR_NUM * sizeof(struct reg_dev),GFP_KERNEL); if(!my_devices){ ret = -ENOMEM; goto fail; } memset(my_devices,0,DEVICE_MINOR_NUM * sizeof(struct reg_dev)); /*设备初始化*/ for(i=0;i my_devices[i].data = kmalloc(REGDEV_SIZE,GFP_KERNEL); memset(my_devices[i].data,0,REGDEV_SIZE); /*设备注册到系统*/ reg_init_cdev(&my_devices[i],i); /*创建设备节点*/ device_create(myclass,NULL,MKDEV(numdev_major,numdev_minor+i),NULL,DEVICE_NAME"%d",i); //atomic_inc(&(my_devices[i].atc)); /*spin_lock_init(&(my_devices[i].lock));*/ my_devices[i].open_num=0; /*sema_init(&(my_devices[i].sem_open,2); sema_init(&(my_devices[i].sem_read,1); sema_init(&(my_devices[i].sem_write,0);*/ //init_waitqueue_head(&(my_devices[i].wqh)); //strcpy( &(my_devices[i].cdd_work_name),"cdd_work_name"); } ret = gpio_init(); if(ret){ printk(KERN_EMERG "gpio_init failed!\n"); } INIT_WORK(&(my_devices->cdd_work,chardeviced_cdd_work_func,NULL); //my_devices[i].cdd_workqueue; create_workqueue("cdd_workqueue"); if(IS_ERR(my_devices->cdd_workqueue)){ print(KERN_ERR"ChardeviceDriver: Failure to create work_queue!\n"); ret=PTR_ERR(my_devices->cdd_workqueue); goto failure_creat_work; } printk(KERN_INFO"ChardeviceDriver:Success to create work_queue!\n"); strcpy( &(my_devices->cdd_work_name),"cdd_delayed_work_name"); INIT_DELAYED_WORK(my_devices->cdd_delayed_work,chardevicedriver_cdd_delayed_work_func); printk(KERN_EMERG "scdev_init!\n"); /*打印信息,KERN_EMERG表示紧急信息*/ return 0; failure_creat_work: free_irq(IRQ_EINT(9),my_devices); free_irq(IRQ_EINT(10),my_devices); free_irq(IRQ_EINT(17),my_devices); free_irq(IRQ_EINT(18),my_devices); for(i=0;i gpio_free(led_gpios[i]) for(i=0;i ret = gpio_free(irq_gpio[i]); fail: /*注销设备号*/ unregister_chrdev_region(MKDEV(numdev_major,numdev_minor),DEVICE_MINOR_NUM); printk(KERN_EMERG "kmalloc is fail!\n"); return ret; } static void __exit scdev_exit(void) { int i; printk(KERN_EMERG "scdev_exit!\n"); free_irq(IRQ_EINT(9),my_devices); free_irq(IRQ_EINT(10),my_devices); free_irq(IRQ_EINT(17),my_devices); free_irq(IRQ_EINT(18),my_devices); /*除去字符设备*/ for(i=0;i cdev_del(&(my_devices[i].cdev)); /*摧毁设备节点函数d*/ device_destroy(myclass,MKDEV(numdev_major,numdev_minor+i)); } /*释放设备class*/ class_destroy(myclass); /*释放内存*/ kfree(my_devices); /*释放GPIO*/ for(i=0;i gpio_free(led_gpios[i]); } unregister_chrdev_region(MKDEV(numdev_major,numdev_minor),DEVICE_MINOR_NUM); } module_init(scdev_init); /*初始化函数*/ module_exit(scdev_exit); /*卸载函数*/ 1)申请I/O内存 #include request_mem_region(start,n,name)物理地址 Start 待申请的起始地址(物理地址) n,从start开始地址的字节数 Name, 2)映射ioremap(phys,addr,size) phys_addr,等同于1)中的start size等同于n return value:映射后的虚拟地址 vir_addr 如:vir_addr=ioremap(0xE0200008,4) ioread8 ioread16 ioread32 ioreite8 iowrite16 iowrite32 ((volatile unsigned int *)vir_addr) =0xXXXX; volatile 寄存器变量,硬件地址,中断或者多线程中共享的全局变量,防止编译器错误优化 4)取消映射 ioumap(*addr) addr, ioremap返回的虚拟地址vir_addr 5)释放I/O内存 release_mem_region(start,n) 回顾:内核竞态与并发 什么情况下会产生竞态 1)SMP 2)单CPU支持任务抢占 3)中断和进程之间 4)中断和中断之间 解决竞态的方法 1)中断屏蔽 2)原子操作 位原子操作 ×××原子操作 atomic{ int。。。。} 3)自旋锁 优点:一旦可以获取锁,立即获取 缺点:长时间获取锁不成功,会消耗CPU资源 它所保护的临界资源(代码段)通常比较短 4)信号量 down(。。。)会导致睡眠 等等看前一章中 自旋锁只允许一个持有者,信号量可以有多个持有者 信号量保护的临界资源(代码段)通常比较长 2,等待队列 #include Read() recv() Wait_event_interrptible //阻塞 Wake_up_interruptible //唤醒 阻塞/非阻塞 实际上,应用程序并不关心驱动里面read/write具体实现,只管调用并获取返回值 如果设备没有准备好数据给应用程序读或者没有准备好接受用户程序写,驱动程序应当阻塞进程,使它进入睡眠,直到请求可以得到满足 阻塞读 在阻塞型驱动程序中,如果进程调用read设备操作,但是设备没有数据或数据不足,进程应该被阻塞,当有新数据到达后,唤醒被阻塞进程 阻塞写 在阻塞型驱动程序中,如果进程调用write设备操作,但是设备没有足够的空间供其写入,进程该被阻塞,但设备中的数据读走后,缓冲区中空出部分空间,应该唤醒被阻塞进程 应用程序非阻塞读 阻塞方式是文件读写操作的默认方式 应用程序可以通过使用O_NONBLOCK标志来人为的设置读写操作为非阻塞方式 定义在 如果设置了O_NONBLOCK标志,read和write的处理行为相同 if(0==pcdevp->)&&(O_NONBLICK&file->f_flags){ printk(KERN_ALET “Char......”); return -EAGAIN; --------------------------------------------------------------- 多路监听侦测 select #include int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); Nfds 要监听的文件描述符最大值+1 Readfds,要监听的读文件描述符集合 Writefds要监听的写文件描述符集合 Exceptfds 要监听的异常文件描述集合 Timeout 监听的超时时间 void FD_CLR(int fd, fd_set *set); int FD_ISSET(int fd, fd_set *set); void FD_SET(int fd, fd_set *set); void FD_ZERO(fd_set *set); 内核中要实现的函数 Unsigned int(*poll)() Fd_set rfds; Struct timeval tv 先 FD_ZERO() 加入集合FD_SET() POLLIN 有数据可读。 POLLRDNORM 有普通数据可读。 POLLRDBAND 有优先数据可读。 POLLPRI 有紧迫数据可读。 POLLOUT 写数据不会导致阻塞。 POLLWRNORM 写普通数据不会导致阻塞。 POLLWRBAND 写优先数据不会导致阻塞。 POLLMSG SIGPOLL 消息可用。 此外,revents域中还可能返回下列事件: POLLER 指定的文件描述符发生错误。 POLLHUP 指定的文件描述符挂起事件。 POLLNVAL 指定的文件描述符非法。 这些事件在events域中无意义,因为它们在合适的时候总是会从revents中返回。使用poll()和select()不一样,你不需要显式地请求异常情况报告。 POLLIN | POLLPRI等价于select()的读事件,POLLOUT |POLLWRBAND等价于select()的写事件。POLLIN等价于POLLRDNORM |POLLRDBAND,而POLLOUT则等价于POLLWRNORM。 中断 驱动需要包含 #include 判断一个IO是否合法:int gpio_is_valid(int number); 设置GPIO的方向,如果是输出同时设置电平: /* set as input or output, returning 0 or negative errno */ int gpio_direction_input(unsigned gpio); int gpio_direction_output(unsigned gpio, int value); 获取输入引脚的电平: /* GPIO INPUT: return zero or nonzero */ int gpio_get_value(unsigned gpio); /* GPIO OUTPUT */ void gpio_set_value(unsigned gpio, int value); int gpio_cansleep(unsigned gpio); To access such GPIOs, a different set of accessors is defined: /* GPIO INPUT: return zero or nonzero, might sleep */ int gpio_get_value_cansleep(unsigned gpio); /* GPIO OUTPUT, might sleep */ void gpio_set_value_cansleep(unsigned gpio, int value); 获取一个GPIO并声明标签: /* request GPIO, returning 0 or negative errno. * non-null labels may be useful for diagnostics. */ int gpio_request(unsigned gpio, const char *label); /* release previously-claimed GPIO */ void gpio_free(unsigned gpio); 将GPIO映射为IRQ中断: /* map GPIO numbers to IRQ numbers */ int gpio_to_irq(unsigned gpio); /* map IRQ numbers to GPIO numbers (avoid using this) */ int irq_to_gpio(unsigned irq); 设置GPIO IRQ中断类型: if (!sw->both_edges) { if (gpio_get_value(sw->gpio)) set_irq_type(gpio_to_irq(sw->gpio), IRQ_TYPE_EDGE_FALLING); else set_irq_type(gpio_to_irq(sw->gpio), IRQ_TYPE_EDGE_RISING); 在驱动中使用延时函数mdelay,需要包含 对于硬件产生的变化,需要CPI进行处理,通过情况下CPU获取该变化的方式有种 1)轮询 2)中断 #include Request irq(...) 注册中断request_irq(...) Irq 要注册的中断号 arm/mach-xxxxx/include/mach/irqs.h Gpio_to_irq() handle 中断处理函数 Irqreturn_t xxxxx(int irq,void *dev_id) Irqflags 中断标志 Cat /proc/interrupts IRQF_SHARED flag定义描述 IRQF_TRIGGER_XXX描述该interrupt line触发类型的flag IRQF_DISABLED首先要说明的是这是一个废弃的flag,在新的内核中,该flag没有任何的作用了。具体可以参考:Disabling IRQF_DISABLED 旧的内核(2.6.35版本之前)认为有两种interrupt handler:slow handler和fast handle。在request irq的时候,对于fast handler,需要传递IRQF_DISABLED的参数,确保其中断处理过程中是关闭CPU的中断,因为是fast handler,执行很快,即便是关闭CPU中断不会影响系统的性能。但是,并不是每一种外设中断的handler都是那么快(例如磁盘),因此就有 slow handler的概念,说明其在中断处理过程中会耗时比较长。对于这种情况,在执行interrupt handler的时候不能关闭CPU中断,否则对系统的performance会有影响。 新的内核已经不区分slow handler和fast handle,都是fast handler,都是需要关闭CPU中断的,那些需要后续处理的内容推到threaded interrupt handler中去执行。 IRQF_SHARED这是flag用来描述一个interrupt line是否允许在多个设备中共享。如果中断控制器可以支持足够多的interrupt source,那么在两个外设间共享一个interrupt request line是不推荐的,毕竟有一些额外的开销(发生中断的时候要逐个询问是不是你的中断,软件上就是遍历action list),因此外设的irq handler中最好是一开始就启动判断,看看是否是自己的中断,如果不是,返回IRQ_NONE,表示这个中断不归我管。 早期PC时代,使用8259中断控制器,级联的8259最多支持15个外部中断,但是PC外设那么多,因此需要irq share。现在,ARM平台上的系统设计很少会采用外设共享IRQ方式,毕竟一般ARM SOC提供的有中断功能的GPIO非常的多,足够用的。 当然,如果确实需要两个外设共享IRQ,那也只能如此设计了。对于HW,中断控制器的一个interrupt source的引脚要接到两个外设的interrupt request line上,怎么接?直接连接可以吗?当然不行,对于低电平触发的情况,我们可以考虑用与门连接中断控制器和外设。 IRQF_PROBE_SHAREDIRQF_SHARED用来表示该interrupt action descriptor是允许和其他device共享一个interrupt line(IRQ number),但是实际上是否能够share还是需要其他条件:例如触发方式必须相同。有些驱动程序可能有这样的调用场景:我只是想scan一个irq table,看看哪一个是OK的,这时候,如果即便是不能和其他的驱动程序share这个interrupt line,我也没有关系,我就是想scan看看情况。这时候,caller其实可以预见sharing mismatche的发生,因此,不需要内核打印“Flags mismatch irq……“这样冗余的信息 IRQF_PERCPU在SMP的架构下,中断有两种mode,一种中断是在所有processor之间共享的,也就是global的,一旦中断产生,interrupt controller可以把这个中断送达多个处理器。当然,在具体实现的时候不会同时将中断送达多个CPU,一般是软件和硬件协同处理,将中断送达一个CPU处理。但是一段时间内产生的中断可以平均(或者按照既定的策略)分配到一组CPU上。这种interrupt mode下,interrupt controller针对该中断的operational register是global的,所有的CPU看到的都是一套寄存器,一旦一个CPU ack了该中断,那么其他的CPU看到的该interupt source的状态也是已经ack的状态。 和global对应的就是per cpu interrupt了,对于这种interrupt,不是processor之间共享的,而是特定属于一个CPU的。例如GIC中interrupt ID等于30的中断就是per cpu的(这个中断event被用于各个CPU的local timer),这个中断号虽然只有一个,但是,实际上控制该interrupt ID的寄存器有n组(如果系统中有n个processor),每个CPU看到的是不同的控制寄存器。在具体实现中,这些寄存器组有两种形态,一种是banked,所有CPU操作同样的寄存器地址,硬件系统会根据访问的cpu定向到不同的寄存器,另外一种是non banked,也就是说,对于该interrupt source,每个cpu都有自己独特的访问地址。 IRQF_NOBALANCING这也是和multi-processor相关的一个flag。对于那些可以在多个CPU之间共享的中断,具体送达哪一个processor是有策略的,我们可以在多个CPU之间进行平衡。如果你不想让你的中断参与到irq balancing的过程中那么就设定这个flag IRQF_IRQPOLL IRQF_ONESHOTone shot本身的意思的只有一次的,结合到中断这个场景,则表示中断是一次性触发的,不能嵌套。对于primary handler,当然是不会嵌套,但是对于threaded interrupt handler,我们有两种选择,一种是mask该interrupt source,另外一种是unmask该interrupt source。一旦mask住该interrupt source,那么该interrupt source的中断在整个threaded interrupt handler处理过程中都是不会再次触发的,也就是one shot了。这种handler不需要考虑重入问题。 具体是否要设定one shot的flag是和硬件系统有关的,我们举一个例子,比如电池驱动,电池里面有一个电量计,是使用HDQ协议进行通信的,电池驱动会注册一个threaded interrupt handler,在这个handler中,会通过HDQ协议和电量计进行通信。对于这个handler,通过HDQ进行通信是需要一个完整的HDQ交互过程,如果中间被打断,整个通信过程会出问题,因此,这个handler就必须是one shot的。 IRQF_NO_SUSPEND这个flag比较好理解,就是说在系统suspend的时候,不用disable这个中断,如果disable,可能会导致系统不能正常的resume。 IRQF_FORCE_RESUME在系统resume的过程中,强制必须进行enable的动作,即便是设定了IRQF_NO_SUSPEND这个flag。这是和特定的硬件行为相关的。 IRQF_NO_THREAD有些low level的interrupt是不能线程化的(例如系统timer的中断),这个flag就是起这个作用的。另外,有些级联的interrupt controller对应的IRQ也是不能线程化的(例如secondary GIC对应的IRQ),它的线程化可能会影响一大批附属于该interrupt controller的外设的中断响应延迟。 IRQF_EARLY_RESUME IRQF_TIMER 如何判断哪个按键触发的中断 如何判断怎么触发(上升沿,下降沿,高电平,低电平)中断的 使用管脚的输入功能(配置成输入功能)判断电平 重新配置为外部中断功能 如何去抖动 Linux内核中中断处理程序的一般结构 顶半部:完成尽可能少的紧急功能,往往是简单的读取寄存器,清除中断标志。登记底半部。 底半部:完成中断处理程序中绝大部分工作,通常这部分都比较耗时。 1)软中断 2)tasklet(利用了软中断的机制) struct tasklet_struct{ Void(*func)(unsigned long);//底半部完成函数 Unsigned long data; 。。。 } tasklet_scedule(。。。)//完成底半部的登记 DECLARE_TASKLET 定义并初始化 3)工作者队列 Struct work_struct{ } INIT_WAORK//初始化work Schrdiule_work //登记work flush_work// Tasklet和工作者队列有啥区别 Asklet中的func函数工作于中断上下文,func不允许睡眠的,work中的func工作于进程上下文 IO与内存: 统一编址(ARM): MOV 独立编址(X86): MOV R0 [0X100] IN/OUT 0X100 ARM PowePC MPIS 都是用统一编址 X86使用独立编址 Linux编程使用到的都是虚拟地址,驱动开发时,从芯片手册得到的物理地址,需要转换成虚拟地址后再使用 /*包含初始化宏定义的头文件,代码中的module_init和module_exit在此文件中*/ #include /*包含初始化加载模块的头文件,代码中的MODULE_LICENSE在此头文件中*/ #include /*定义module_param module_param_array的头文件*/ #include /*定义module_param module_param_array中perm的头文件*/ #include /*三个字符设备函数*/ #include /*MKDEV转换设备号数据类型的宏定义*/ #include /*定义字符设备的结构体*/ #include /*分配内存空间函数头文件*/ #include /*包含函数device_creatchar_driver_ledse 结构体class等头文件*/ #include #include /*自定义头文件*/ #include "char_driver_leds.h" #include //#include #include #include #include #include /*Linux中申请GPIO的头文件*/ #include /*三星平台的GPIO配置函数头文件*/ /*三星平台EXYNOS系列平台,GPIO配置参数宏定义头文件*/ #include /*三星平台4412平台,GPIO宏定义头文件*/ #include MODULE_LICENSE("Dual BSD/GPL"); /*声明是开源的,没有内核版本限制*/ MODULE_AUTHOR("songmao"); /*声明作者*/ static int led_gpios[] = { EXYNOS4_GPL2(0),EXYNOS4_GPK1(1), }; #define LED_NUM ARRAY_SIZE(led_gpios) static int irq_gpio[] ={ EXYNOS4_GPX1(1),EXYNOS4_GPX1(2), EXYNOS4_GPX1(0),EXYNOS4_GPX1(1) }; #define IRQ_NUM ARRAY_SIZE(irq_gpio) int led_num[4]; int numdev_major = DEV_MAJOR; int numdev_minor = DEV_MINOR; /*输入主设备号*/ module_param(numdev_major,int,S_IRUSR); /*输入次设备号*/ module_param(numdev_minor,int,S_IRUSR); static struct class *myclass; struct reg_dev *my_devices; /*打开操作*/ static int chardevnode_open(struct inode *inode, struct file *file) { struct reg_dev *reg_devp =NULL; reg_devp =container_of(inode->i_cdev,struct reg_dev,cdev); file->private_data=reg_devp; printk(KERN_EMERG "chardevnode_open is success!\n"); /*if(!atomic_dec_and_test(&(reg_devp->atc))) { printk(KERN_ERR "atomic:device can open only once!"); atomic_inc(&(reg_devp->atc)); return -EBUSY; }*/ spin_lock(&(reg_devp->lock)); if(OPEN_NUM<=reg_devp->open_num){ spin_unlock(&(reg_devp->lock)); printk(KERN_ERR "atomic:device can open over num!"); return -EBUSY; } reg_devp->open_num++; spin_unlock(&(reg_devp->lock)); return 0; } /*关闭操作*/ static int chardevnode_release(struct inode *inode, struct file *file) { struct reg_dev *reg_devp =NULL; reg_devp=file->private_data; printk(KERN_EMERG "chardevnode_release is success!\n"); //atomic_inc(&(reg_devp->atc)); /*spin_lock(&(reg_devp->lock)); reg_devp->open_num--; spin_unlock(&(reg_devp->lock));*/ up(&(reg_devp->sem_open)); return 0; } /*IO操作*/ static long chardevnode_ioctl(struct file *file, unsigned int cmd, unsigned long arg){ struct reg_dev *reg_devp =NULL; reg_devp=file->private_data; switch(cmd) { case 0: case 1: if (arg > LED_NUM) { return -EINVAL; } gpio_set_value(led_gpios[arg], cmd); break; default: return -EINVAL; } printk(KERN_EMERG "chardevnode_ioctl is success! cmd is %d,arg is %d \n",cmd,arg); up(&(reg_devp->sem_read)); up(&(reg_devp->sem_write)); return 0; } ssize_t chardevnode_read(struct file *file, char __user *buf, size_t count, loff_t *f_ops) { struct reg_dev *reg_devp =NULL; reg_devp=file->private_data; //down_interruptible(&(reg_devp->sem_read)); wait_event_interruptible(reg_devp->wqh,0!=reg_devp->led); reg_devp->led=0;
文章标题:中断处理I/O内存
标题路径:http://kswsj.cn/article/ipseso.html