同一时间只有一个运用程序使用这个驱动,如下介绍两种方法
原子操作,该操作可以避免数据在读取、修改和存储过程中不被其他线程打断;
信号量获取,要是获取不到该运用程序就会休眠,等前面运用程序释放信号量,该程序会重新运用;
一、原子操作
1、驱动程序.c
#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 <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
#include <asm/io.h>
#include <linux/sched.h>
//主设备号
int major;
//udev自动创建节点
static struct class *keyirqdrv_class;
static struct class_device *keyirqdrv_class_dev;
// 操作管脚为gpio14_2
// 该管脚采用boot_sel[1:0]硬件配置复用功能,现单板已经配置为gpio,默认高电平,按键按下为低电平
static void __iomem *key_base; //寄存器基地址
#define gpio_phy_base 0x1214E000 //key管脚物理地址
#define gpio_data key_base + 0x010 //GPIO 数据寄存器 ,0b00_0001_0000
#define gpio_dir key_base + 0x400 //GPIO 方向控制寄存器
#define gpio_is key_base + 0x404 //GPIO 中断触发寄存器
#define gpio_ibe key_base + 0x408 //GPIO 双沿触发中断寄存器
#define gpio_iev key_base + 0x40C //GPIO 触发中断条件寄存器
#define gpio_ie key_base + 0x410 //GPIO 中断屏蔽寄存器
#define gpio_ris key_base + 0x414 //GPIO 原始中断状态寄存器
#define gpio_mis key_base + 0x418 //GPIO 屏蔽状态中断寄存器
#define gpio_ic key_base + 0x41C //GPIO 中断清除寄存器
#define gpio_in 0x00 //配置为输入,1输出 0输入def
#define gpio_out_h 0xFF
#define gpio_out_l 0x00
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
/* 中断事件标志, 中断服务程序置1 不进休眠,drv_read将它清0 进休眠*/
static volatile int ev_press = 0;
unsigned int val; //中断值
static atomic_t canopen = ATOMIC_INIT(1);//定义原子变量 canopen = 1
//引脚描述结构体
struct pin_irq_desc
{
unsigned int irq; //中断号
unsigned int pin; //中断标志寄存器,有中断产生时为1,无中断时为0
unsigned int number; //编号
char *name; //名称
};
struct pin_irq_desc pins_desc[] =
{
{76, 0x04, 0x01, "key_irq"},
};
static irqreturn_t irq_interrupt(int irq, void *dev_id)
{
static unsigned int cnt = 0;
unsigned int key;
// printk("cnt = %d\n", cnt++);
key = ioread8(gpio_ris); //读取中断标志位
if (key == 0x04)
{
val = key;
ev_press = 1; //不进去休眠
wake_up_interruptible(&button_waitq); //唤醒休眠的进程
}
iowrite8(0xFF, gpio_ic); //清除中断
return IRQ_HANDLED;
}
static int key_drv_open(struct inode *inode, struct file *filp)
{
if(!atomic_dec_and_test(&canopen))//自减后测试是否为0,为0返回true,否则返回false
{
atomic_inc(&canopen);
return -EBUSY;
}
iowrite8(gpio_in, gpio_dir); //gpio14_2 配置为输入
iowrite8(0x04, gpio_is); //配置电平触发
iowrite8(0x00, gpio_iev); //配置下降沿触发或者是低电平触发
iowrite8(0x00, gpio_ibe); //配置单边沿触发
iowrite8(0xFF, gpio_ic); //清除中断
iowrite8(0x04, gpio_ie); //使能gpio14_2中断
//Hi3519v101好几组GPIO只有一个中断号,所以这里得是共享中断IRQF_SHARED,作为共享中断,dev用来区分不同的中断
request_irq(pins_desc->irq, irq_interrupt, IRQF_SHARED, pins_desc->name, (void *)&pins_desc);
printk("set key rising irq mode\n");
return 0;
}
static int key_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
//如果没有按键动作, 休眠
wait_event_interruptible(button_waitq, ev_press);
__copy_to_user(buf, &val, 1);
ev_press = 0; //数据拷贝给运用之后,一次中断完成,进去休眠
return 0;
}
int key_drv_close(struct inode *inode, struct file *file)
{
atomic_inc(&canopen);//重新恢复canopen的值
free_irq(pins_desc->irq, (void *)&pins_desc);
return 0;
}
static struct file_operations key_drv_fops =
{
.owner = THIS_MODULE,
.open = key_drv_open,
.read = key_drv_read,
.release = key_drv_close,
};
int key_drv_int(void)
{
major = register_chrdev(0, "key_drv", &key_drv_fops);
keyirqdrv_class = class_create(THIS_MODULE, "key_drv");
keyirqdrv_class_dev = device_create(keyirqdrv_class, NULL, MKDEV(major, 0), NULL, "key"); //dev/key
key_base = ioremap(gpio_phy_base, 0x1000);
printk("key_data = 0x%x\n", (unsigned int)gpio_data);
printk("key_dir = 0x%x\n", (unsigned int)gpio_dir);
return 0;
}
void key_drv_exit(void)
{
unregister_chrdev(major, "key_drv");
device_unregister(keyirqdrv_class_dev);
class_destroy(keyirqdrv_class);
}
module_init(key_drv_int);
module_exit(key_drv_exit);
MODULE_LICENSE("GPL");
2、运用程序.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv)
{
int fd;
unsigned int val = 0;
fd = open("/dev/key", O_RDWR);
if(fd < 0)
{
printf("can`t open!\n");
return -1;
}
while (1)
{
read(fd, &val, 1);
printf("key down!get val = %d\n", val);
}
return 0;
}
3、Makefile
#内核路径
KERN_DIR = /home/osrc/Hi3519V101_SDK_V1.0.4.0/osdrv/opensource/kernel/linux-3.18.y
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += key_irq_atomic_drv.o
4、测试
第二次运行./test_key_irq_block_drv设备不能打开!
/ # insmod key_irq_block_drv.ko
key_data = 0xfea4e010
key_dir = 0xfea4e400
/ # ./test_key_irq_block_drv &
/ # set key rising irq mode
/ #
/ # key down!get val = 4
key down!get val = 4
key down!get val = 4
/ #
/ # ./test_key_irq_block_drv
can`t open!
/ #
/ #
/ #
/ # ./test_key_irq_block_drv
can`t open!
二、信号量
1、驱动.c
#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 <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
#include <asm/io.h>
#include <linux/sched.h>
//主设备号
int major;
//udev自动创建节点
static struct class *keyirqdrv_class;
static struct class_device *keyirqdrv_class_dev;
// 操作管脚为gpio14_2
// 该管脚采用boot_sel[1:0]硬件配置复用功能,现单板已经配置为gpio,默认高电平,按键按下为低电平
static void __iomem *key_base; //寄存器基地址
#define gpio_phy_base 0x1214E000 //key管脚物理地址
#define gpio_data key_base + 0x010 //GPIO 数据寄存器 ,0b00_0001_0000
#define gpio_dir key_base + 0x400 //GPIO 方向控制寄存器
#define gpio_is key_base + 0x404 //GPIO 中断触发寄存器
#define gpio_ibe key_base + 0x408 //GPIO 双沿触发中断寄存器
#define gpio_iev key_base + 0x40C //GPIO 触发中断条件寄存器
#define gpio_ie key_base + 0x410 //GPIO 中断屏蔽寄存器
#define gpio_ris key_base + 0x414 //GPIO 原始中断状态寄存器
#define gpio_mis key_base + 0x418 //GPIO 屏蔽状态中断寄存器
#define gpio_ic key_base + 0x41C //GPIO 中断清除寄存器
#define gpio_in 0x00 //配置为输入,1输出 0输入def
#define gpio_out_h 0xFF
#define gpio_out_l 0x00
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
/* 中断事件标志, 中断服务程序置1 不进休眠,drv_read将它清0 进休眠*/
static volatile int ev_press = 0;
unsigned int val; //中断值
static DEFINE_SEMAPHORE(button_lock); //定义互斥锁
//引脚描述结构体
struct pin_irq_desc
{
unsigned int irq; //中断号
unsigned int pin; //中断标志寄存器,有中断产生时为1,无中断时为0
unsigned int number; //编号
char *name; //名称
};
struct pin_irq_desc pins_desc[] =
{
{76, 0x04, 0x01, "key_irq"},
};
static irqreturn_t irq_interrupt(int irq, void *dev_id)
{
static unsigned int cnt = 0;
unsigned int key;
// printk("cnt = %d\n", cnt++);
key = ioread8(gpio_ris); //读取中断标志位
if (key == 0x04)
{
val = key;
ev_press = 1; //不进去休眠
wake_up_interruptible(&button_waitq); //唤醒休眠的进程
}
iowrite8(0xFF, gpio_ic); //清除中断
return IRQ_HANDLED;
}
static int key_drv_open(struct inode *inode, struct file *filp)
{
/* 获取信号量 */
down(&button_lock);
iowrite8(gpio_in, gpio_dir); //gpio14_2 配置为输入
iowrite8(0x04, gpio_is); //配置电平触发
iowrite8(0x00, gpio_iev); //配置下降沿触发或者是低电平触发
iowrite8(0x00, gpio_ibe); //配置单边沿触发
iowrite8(0xFF, gpio_ic); //清除中断
iowrite8(0x04, gpio_ie); //使能gpio14_2中断
//Hi3519v101好几组GPIO只有一个中断号,所以这里得是共享中断IRQF_SHARED,作为共享中断,dev用来区分不同的中断
request_irq(pins_desc->irq, irq_interrupt, IRQF_SHARED, pins_desc->name, (void *)&pins_desc);
printk("set key rising irq mode\n");
return 0;
}
static int key_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
//如果没有按键动作, 休眠
wait_event_interruptible(button_waitq, ev_press);
__copy_to_user(buf, &val, 1);
ev_press = 0; //数据拷贝给运用之后,一次中断完成,进去休眠
return 0;
}
int key_drv_close(struct inode *inode, struct file *file)
{
free_irq(pins_desc->irq, (void *)&pins_desc);
up(&button_lock);
return 0;
}
static struct file_operations key_drv_fops =
{
.owner = THIS_MODULE,
.open = key_drv_open,
.read = key_drv_read,
.release = key_drv_close,
};
int key_drv_int(void)
{
major = register_chrdev(0, "key_drv", &key_drv_fops);
keyirqdrv_class = class_create(THIS_MODULE, "key_drv");
keyirqdrv_class_dev = device_create(keyirqdrv_class, NULL, MKDEV(major, 0), NULL, "key"); //dev/key
key_base = ioremap(gpio_phy_base, 0x1000);
printk("key_data = 0x%x\n", (unsigned int)gpio_data);
printk("key_dir = 0x%x\n", (unsigned int)gpio_dir);
return 0;
}
void key_drv_exit(void)
{
unregister_chrdev(major, "key_drv");
device_unregister(keyirqdrv_class_dev);
class_destroy(keyirqdrv_class);
}
module_init(key_drv_int);
module_exit(key_drv_exit);
MODULE_LICENSE("GPL");
测试程序,Makefile同上
2、测试
第一个运用程序线程为160,第二个运用程序线程为161
/ # insmod key_irq_block_drv.ko
key_data = 0xfea4e010
key_dir = 0xfea4e400
/ # ./test_key_irq_block_drv &
/ # set key rising irq mode
/ #
/ # ps
PID USER TIME COMMAND
1 root 0:00 {linuxrc} init
2 root 0:00 [kthreadd]
3 root 0:00 [ksoftirqd/0]
4 root 0:00 [kworker/0:0]
5 root 0:00 [kworker/0:0H]
6 root 0:00 [kworker/u4:0]
7 root 0:00 [rcu_sched]
8 root 0:00 [rcu_bh]
9 root 0:00 [migration/0]
10 root 0:00 [migration/1]
11 root 0:00 [ksoftirqd/1]
12 root 0:00 [kworker/1:0]
13 root 0:00 [kworker/1:0H]
14 root 0:00 [khelper]
15 root 0:00 [kdevtmpfs]
16 root 0:00 [netns]
17 root 0:00 [writeback]
18 root 0:00 [bioset]
19 root 0:00 [kblockd]
20 root 0:00 [spi0]
21 root 0:00 [kworker/u4:1]
24 root 0:00 [spi1]
29 root 0:00 [spi2]
32 root 0:00 [spi3]
35 root 0:00 [rpciod]
36 root 0:00 [kworker/1:1]
37 root 0:00 [kswapd0]
38 root 0:00 [fsnotify_mark]
39 root 0:00 [nfsiod]
47 root 0:00 [kpsmoused]
48 root 0:00 [cfinteractive]
49 root 0:00 [ipv6_addrconf]
50 root 0:00 [deferwq]
51 root 0:00 [kworker/u4:2]
52 root 0:00 [mmcqd/0]
53 root 0:00 [mmcqd/0boot0]
54 root 0:00 [mmcqd/0boot1]
55 root 0:00 [kworker/1:2]
56 root 0:00 [kworker/0:1]
57 root 0:00 [kworker/1:3]
71 root 0:00 udevd --daemon
131 root 0:00 udevd --daemon
135 root 0:00 -sh
156 root 0:00 udevd --daemon
160 root 0:00 ./test_key_irq_block_drv
161 root 0:00 ./test_key_irq_block_drv
162 root 0:00 ps
/ # ./test_key_irq_block_drv &
/ # ps
PID USER TIME COMMAND
1 root 0:00 {linuxrc} init
2 root 0:00 [kthreadd]
3 root 0:00 [ksoftirqd/0]
4 root 0:00 [kworker/0:0]
5 root 0:00 [kworker/0:0H]
6 root 0:00 [kworker/u4:0]
7 root 0:00 [rcu_sched]
8 root 0:00 [rcu_bh]
9 root 0:00 [migration/0]
10 root 0:00 [migration/1]
11 root 0:00 [ksoftirqd/1]
12 root 0:00 [kworker/1:0]
13 root 0:00 [kworker/1:0H]
14 root 0:00 [khelper]
15 root 0:00 [kdevtmpfs]
16 root 0:00 [netns]
17 root 0:00 [writeback]
18 root 0:00 [bioset]
19 root 0:00 [kblockd]
20 root 0:00 [spi0]
21 root 0:00 [kworker/u4:1]
24 root 0:00 [spi1]
29 root 0:00 [spi2]
32 root 0:00 [spi3]
35 root 0:00 [rpciod]
36 root 0:00 [kworker/1:1]
37 root 0:00 [kswapd0]
38 root 0:00 [fsnotify_mark]
39 root 0:00 [nfsiod]
47 root 0:00 [kpsmoused]
48 root 0:00 [cfinteractive]
49 root 0:00 [ipv6_addrconf]
50 root 0:00 [deferwq]
51 root 0:00 [kworker/u4:2]
52 root 0:00 [mmcqd/0]
53 root 0:00 [mmcqd/0boot0]
54 root 0:00 [mmcqd/0boot1]
55 root 0:00 [kworker/1:2]
56 root 0:00 [kworker/0:1]
57 root 0:00 [kworker/1:3]
71 root 0:00 udevd --daemon
131 root 0:00 udevd --daemon
135 root 0:00 -sh
156 root 0:00 udevd --daemon
160 root 0:00 ./test_key_irq_block_drv
161 root 0:00 ./test_key_irq_block_drv
162 root 0:00 ps
/ # kill -9 160
set key rising irq mode
/ #
/ #