Linux驅動開發之中斷編程
1,中斷號--就是一個號碼,需要通過一定的方式去獲取到
在3.14.0內核中,從設備樹中獲取
獲取中斷號的方法:
1, 宏定義
IRQ_EINT(號碼)
2,設備樹文件中
arch/arm/boot/dts/exynos4412-fs4412.dts
硬件連接:
key ---- gpx1_2--- EINT10
打開設備樹文件:arch/arm/boot/dts/exynos4x12-pinctrl.dtsi
找到gpx1:(該代碼已經編譯好,無需人工編寫,此處作爲分析源碼)
gpx1: gpx1 {
gpio-controller;//描述信息:這是一個gpio口控制器
#gpio-cells = <2>;//
interrupt-controller;//描述信息:這是一箇中斷控制器
interrupt-parent = <&gic>;//中斷繼承於gic(gpio具有中斷功能,繼承了一些中斷特性)
interrupts = <0 24 0>, <0 25 0>, <0 26 0>, <0 27 0>,
<0 28 0>, <0 29 0>, <0 30 0>, <0 31 0>;//中斷號的一些表述(見芯片手冊中斷部分的說明,
//上圖右下角)
#interrupt-cells = <2>;
};
在編程過程中,需要定義自己的節點--描述當前設備用的中斷號(此處需要人工編寫完成)
arch/arm/boot/dts/exynos4412-fs4412.dts +51
key_int_node{
compatible = "test_key";//可以通過compatible 來搜索這個節點
interrupt-parent = <&gpx1>;//繼承gpx1
interrupts = <2 4>;//這裏設置gpx1中的<0 26 0>,該成員位於gpx1中interrupts 的第2個,所以第一個位置
//填寫2第二個位置填寫的是觸發方式(0:上升沿 2:下降沿 4:高電平 8:低電平,
//具體待查,可以不用管)
};
編譯設備樹文件:
make dtbs
更新dtbs文件:
cp -raf arch/arm/boot/dts/exynos4412-fs4412.dtb /tftpboot/
2,在驅動中去通過代碼獲取到中斷號,並且申請中斷(實現中斷處理方法)
a,獲取到中斷號碼:
int get_irqno_from_node(void)
{
// 獲取到設備樹中的節點
struct device_node *np = of_find_node_by_path("/key_int_node");
if(np){
printk("find node ok\n");
}else{
printk("find node failed\n");
}
// 通過節點去獲取到中斷號碼
int irqno = irq_of_parse_and_map(np, 0);
printk("irqno = %d\n", irqno);
return irqno;
}
b,申請中斷
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char * name, void * dev)
參數1: 設備對應的中斷號
參數2: 中斷的處理函數
typedef irqreturn_t (*irq_handler_t)(int, void *);
參數3:觸發方式
#define IRQF_TRIGGER_NONE 0x00000000 //內部控制器觸發中斷的時候的標誌
#define IRQF_TRIGGER_RISING 0x00000001 //上升沿
#define IRQF_TRIGGER_FALLING 0x00000002 //下降沿
#define IRQF_TRIGGER_HIGH 0x00000004 // 高點平
#define IRQF_TRIGGER_LOW 0x00000008 //低電平觸發
參數4:中斷的描述,自定義,主要是給用戶查看的
/proc/interrupts
參數5:傳遞給參數2中函數指針的值
返回值: 正確爲0,錯誤非0
參數2的賦值:
irqreturn_t key_irq_handler(int irqno, void *devid)
{
return IRQ_HANDLED;
}
釋放中斷:
void free_irq(unsigned int irq, void *dev_id)
參數1: 設備對應的中斷號
參數2:與request_irq中第5個參數保持一致
3,實現字符設備驅動的框架
// 1,設定一個全局的設備對象
key_dev = kzalloc(sizeof(struct key_desc), GFP_KERNEL);
// 2,申請主設備號
key_dev->dev_major = register_chrdev(0, "key_drv", &key_fops);
// 3,創建設備節點文件
key_dev->cls = class_create(THIS_MODULE, "key_cls");
key_dev->dev = device_create(key_dev->cls, NULL,MKDEV(key_dev->dev_major,0), NULL, "key0");
4,驅動中將硬件所產生的數據傳遞給用戶
a,硬件如何獲取數據
key: 按下和擡起: 1/0
讀取key對應的gpio的狀態,可以判斷按下還是擡起
讀取key對應gpio的寄存器--數據寄存器
//讀取數據寄存器
int value = readl(key_dev->reg_base + 4) & (1<<2);
b,驅動如何傳遞給用戶
在中斷處理中填充數據:
key_dev->event.code = KEY_ENTER;
key_dev->event.value = 0;//按鍵的值(0/1)
在xxx_read中將數據傳遞給用戶
ret = copy_to_user(buf, &key_dev->event, count);
c,用戶如何拿到--編寫應用程序
while(1)
{
read(fd, &event, sizeof(struct key_event));
if(event.code == KEY_ENTER)
{
if(event.value)
{
printf("APP__ key enter pressed\n");
}else{
printf("APP__ key enter up\n");
}
}
}
5,實現文件IO模型之一阻塞,等同於休眠
文件io模型:
1,非阻塞
2,阻塞
3,多路複用--select/poll
4, 異步信號通知faync
阻塞: 當進程在讀取外部設備的資源(數據),資源沒有準備好,進程就會休眠
linux應用中,大部分的函數接口都是阻塞
scanf();
read();
write();
accept();
驅動中需要調用
1,將當前進程加入到等待隊列頭中
add_wait_queue(wait_queue_head_t * q, wait_queue_t * wait)
2,將當前進程狀態設置成TASK_INTERRUPTIBLE
set_current_state(TASK_INTERRUPTIBLE)
3,讓出調度--休眠
schedule(void)
更加智能的接口,等同於上面的三個接口:
wait_event_interruptible(wq, condition)
驅動如何去寫代碼(參考:https://blog.csdn.net/yikai2009/article/details/8653578)
1,等待隊列頭
wait_queue_head_t wq_head;//定義等待隊列
init_waitqueue_head(&key_dev->wq_head);//初始化等待隊列。函數原型:init_waitqueue_head(wait_queue_head_t *q);
struct key_disc
{
unsigned int dev_major;
struct class * cls;
struct device * dev;
int irqno;
void * reg_base;
struct key_event event;
wait_queue_head_t wq_head;//定義一個等待隊列變量
int key_state;//等待隊列頭的標誌位
}* key_dev;
static int __init key_drv_init(void)
{
int ret;
key_dev = kzalloc(sizeof(struct key_disc), GFP_KERNEL);
key_dev->dev_major = register_chrdev(0, "key_dev_test", &my_fops);
key_dev->cls = class_create(THIS_MODULE, "key_cls");
key_dev->dev = device_create(key_dev->cls, NULL, MKDEV(key_dev->dev_major,0), NULL, "key%d",0);
key_dev->irqno = get_irqno_from_node();
key_dev->reg_base = ioremap(GPXCON_REG,8);
ret = request_irq(key_dev->irqno, key_irq_handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, "key3_eint10", NULL);
if(ret != 0)
{
printk("request_irq error\n");
return -EBUSY;
}
init_waitqueue_head(&key_dev->wq_head);//初始化等待隊列
return 0;
}
2,在需要等待(沒有數據)的時候,進行休眠
wait_event_interruptible(wait_queue_head_t wq, condition) // 內部會構建一個等待隊列項/節點wait_queue_t
參數1: 等待隊列頭
參數2: 條件,如果是爲假,就會等待(進程進入 TASK_INTERRUPTIBLE 模式睡眠,並掛在 queue 參數所指定的等待隊列上),如果爲真,就不會等待
可以用一標誌位,來表示是否有數據
ssize_t key_drv_read (struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
//printk("********%s********\n",__FUNCTION__);
int ret;
wait_event_interruptible(key_dev->wq_head, key_dev->key_state);/*等待隊列頭,當按鍵按下或擡起時會產生中斷,
key_dev->key_state會被置1,不再等待,直接往下運行代碼*/
ret = copy_to_user(buf, &key_dev->event, count);
if(ret > 0)
{
printk("copy_to_user error\n");
return -EFAULT;
}
memset(&key_dev->event, 0, sizeof(key_dev->event));
key_dev->key_state = 0;
flag = 1;
return count;
}
3,在一個合適的時候(有數據),會將進程喚醒
wake_up_interruptible(wait_queue_head_t *q)// 從等待隊列 q 中喚醒狀態爲 TASK_INTERRUPTIBLE 的進程
用法:
wake_up_interruptible(&key_dev->wq_head);
//同時設置標誌位
key_dev->key_state = 1;
irqreturn_t key_irq_handler(int irqno, void * devid)//按鍵中斷處理函數
{
//printk("---------%s----------\n", __FUNCTION__);
//printk("***********************************************\n");
if(flag){
int value = readl(key_dev->reg_base + 4) & (0x1<<2);
if(value)
{
printk("k3 up\n");
key_dev->event.code = KEY_ENTER;
key_dev->event.value = 0;
}
else
{
printk("k3 pressed\n");
key_dev->event.code = KEY_ENTER;
key_dev->event.value = 1;
}
wake_up_interruptible(&key_dev->wq_head);//喚醒等待
key_dev->key_state = 1;}
return IRQ_HANDLED;
}
6, 非阻塞: 在讀寫的時候,如果沒有數據,立刻返回,並且返回一個出錯碼
用的會比較少,因爲比較耗資源
open("/dev/key0", O_RDWR|O_NONBLOCK);
------------------------------------
驅動中需要去區分,當前模式是阻塞還是非阻塞
//如果當前是非阻塞模式,並且沒有數據,立馬返回一個出錯碼
if(filp->f_flags & O_NONBLOCK && !key_dev->key_state)
return -EAGAIN;
7,多路複用--select和poll
poll的應用:
1, 需要打開多個文件(多個設備)
2, 利用poll來實現監控fd的讀,寫,出錯
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
參數1: 表示多個文件描述符集合
struct pollfd描述的是文件描述符的信息
struct pollfd {
int fd; //文件描述符
short events; //希望監控fd的什麼事件:讀,寫,出錯
POLLIN 讀,
POLLOUT 寫,
POLLERR出錯
short revents; //結果描述,表示當前的fd是否有讀,寫,出錯
//用於判斷,是內核自動賦值
POLLIN 讀,
POLLOUT 寫,
POLLERR出錯
};
參數2:被監控的fd的個數
參數3: 監控的時間:
正: 表示監控多少ms
負數: 無限的時間去監控
0: 等待0ms,類似於非阻賽
返回值: 負數:出錯
大於0,表示fd中有數據
等於0: 時間到
8,如果應用中使用poll對設備文件進行了監控,那麼設備驅動就必須實現poll接口
unsigned int key_drv_poll(struct file *filp, struct poll_table_struct *pts)
{
// 返回一個mask值
unsigned int mask;
// 調用poll_wait,將當前的等待隊列註冊到系統中
poll_wait(filp, &key_dev->wq_head, pts);
// 1,當沒有數據的時候返回一個0
if(!key_dev->key_state)
mask = 0;
// 2,有數據返回一個POLLIN
if(key_dev->key_state)
mask |= POLLIN;
return mask;
}
const struct file_operations key_fops = {
.poll = key_drv_poll,
};
//設計一個全局設備對象--描述按鍵信息
struct key_desc{
unsigned int dev_major;
struct class *cls;
struct device *dev;
int irqno;
void *reg_base;
struct key_event event;
wait_queue_head_t wq_head;
int key_state; //表示是否有數據
};
unsigned int key_drv_poll(struct file *filp, struct poll_table_struct *pts)
{
// 返回一個mask值
unsigned int mask;
// 調用poll_wait,將當前的等待隊列註冊系統中
poll_wait(filp, &key_dev->wq_head, pts);
// 1,當沒有數據到時候返回一個0
if(!key_dev->key_state)
mask = 0;
// 2,有數據返回一個POLLIN
if(key_dev->key_state)
mask |= POLLIN;
return mask;
}
const struct file_operations key_fops = {
.open = key_drv_open,
.read = key_drv_read,
.write = key_drv_write,
.release = key_drv_close,
.poll = key_drv_poll,
};
static int __init key_drv_init(void)//入口函數
{
//演示如何獲取到中斷號
int ret;
// 1,設定一個全局的設備對象
key_dev = kzalloc(sizeof(struct key_desc), GFP_KERNEL);
// 2,申請主設備號
key_dev->dev_major = register_chrdev(0, "key_drv", &key_fops);
// 3,創建設備節點文件
key_dev->cls = class_create(THIS_MODULE, "key_cls");
key_dev->dev = device_create(key_dev->cls, NULL, MKDEV(key_dev->dev_major,0), NULL, "key0");
// 4,硬件的初始化--地址映射或者中斷申請
key_dev->irqno = get_irqno_from_node();
ret = request_irq(key_dev->irqno, key_irq_handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
"key3_eint10", NULL);
if(ret != 0)
{
printk("request_irq error\n");
return ret;
}
// a,硬件如何獲取數據--gpx1
key_dev->reg_base = ioremap(GPXCON_REG, 8);
// 初始化等待隊列頭
init_waitqueue_head(&key_dev->wq_head);
return 0;
}
/*按鍵測試程序,配合驅動使用*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <poll.h>
struct key_event{
int code; // 表示按鍵的類型: home, esc, Q,W,E,R,T, ENTER
int value; // 表示按下還是擡起 1 / 0
};
#define KEY_ENTER 28
int main(int argc, char *argv[])
{
int ret;
struct key_event event;
char in_buf[128];
int fd = open("/dev/key0", O_RDWR);
if(fd < 0)
{
perror("open");
exit(1);
}
//監控多個文件fd
struct pollfd pfd[2];
pfd[0].fd = fd; //監控按鍵設備
pfd[0].events = POLLIN;
pfd[1].fd = 0; //標準輸入
pfd[1].events = POLLIN;
while(1)
{
ret = poll(pfd, 2, -1); // 驅動需要去實現poll接口,類似於open, read,
printf("ret = %d\n", ret);
if(ret > 0)
{
if(pfd[0].revents & POLLIN)
{
read(pfd[0].fd, &event, sizeof(struct key_event));
if(event.code == KEY_ENTER)
{
if(event.value)
{
printf("APP__ key enter pressed\n");
}else
{
printf("APP__ key enter up\n");
}
}
}
if(pfd[1].revents & POLLIN)
{
fgets(in_buf, 128, stdin);
printf("in_buf = %s\n", in_buf);
}
}else{
perror("poll");
exit(1);
}
}
close(pfd[0].fd);
return 0;
}
9,異步信號通知: 當有數據到時候,驅動會發送信號(SIGIO)給應用,就可以異步去讀寫數據,不用主動去讀寫
a,應用--處理信號,主要是讀寫數據
void catch_signale(int signo)
{
if(signo == SIGIO)
{
printf("we got sigal SIGIO");
// 讀取數據
read(fd, &event, sizeof(struct key_event));
if(event.code == KEY_ENTER)
{
if(event.value)
{
printf("APP__ key enter pressed\n");
}else
{
printf("APP__ key enter up\n");
}
}
}
}
// 1,設置信號處理方法
signal(SIGIO,catch_signale);
// 2,將當前進程設置成SIGIO的屬主進程
fcntl(fd, F_SETOWN, getpid());
// 3,將io模式設置成異步模式
int flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | FASYNC );
b,驅動--發送信號
1,需要和進程進行關聯--記錄信號該發送給誰
實現一個fasync的接口
int key_drv_fasync(int fd, struct file *filp, int on)
{
//只需要調用一個函數記錄信號該發送給誰
return fasync_helper(fd, filp, on, &key_dev->faysnc);
}
2,在某個特定的時候去發送信號,在有數據的時候
//發送信號
kill_fasync(&key_dev->faysnc, SIGIO, POLLIN);
/*驅動部分代碼*/
//設計一個全局設備對象--描述按鍵信息
struct key_desc{
unsigned int dev_major;
struct class *cls;
struct device *dev;
int irqno;
void *reg_base;
struct key_event event;
wait_queue_head_t wq_head;
int key_state; //表示是否有數據
struct fasync_struct *faysnc;
};
struct key_desc *key_dev;
irqreturn_t key_irq_handler(int irqno, void *devid)
{
printk("-------%s-------------\n", __FUNCTION__);
//讀取數據寄存器
int value = readl(key_dev->reg_base + 4) & (1<<2);
if(value){ // 擡起
printk("key3 up\n");
key_dev->event.code = KEY_ENTER;
key_dev->event.value = 0;
}else{//按下
printk("key3 pressed\n");
key_dev->event.code = KEY_ENTER;
key_dev->event.value = 1;
}
// 表示有數據,需要去喚醒整個進程/等待隊列
wake_up_interruptible(&key_dev->wq_head);
//同時設置標誌位
key_dev->key_state = 1;
//發送信號
kill_fasync(&key_dev->faysnc, SIGIO, POLLIN);
return IRQ_HANDLED;
}
ssize_t key_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
//printk("-------%s-------------\n", __FUNCTION__);
//如果當前是非阻塞模式,並且沒有數據,立馬返回一個出錯碼
if(filp->f_flags & O_NONBLOCK && !key_dev->key_state)
return -EAGAIN;
int ret;
//2,在需要等待(沒有數據)的時候,進行休眠
wait_event_interruptible(key_dev->wq_head, key_dev->key_state);
// 表示有數據
ret = copy_to_user(buf, &key_dev->event, count);
if(ret > 0)
{
printk("copy_to_user error\n");
return -EFAULT;
}
// 傳遞給用戶數據之後,將數據清除掉
memset(&key_dev->event, 0, sizeof(key_dev->event));
key_dev->key_state = 0;
return count;
}
unsigned int key_drv_poll(struct file *filp, struct poll_table_struct *pts)
{
// 返回一個mask值
unsigned int mask;
// 調用poll_wait,將當前到等待隊列註冊系統中
poll_wait(filp, &key_dev->wq_head, pts);
// 1,當沒有數據到時候返回一個0
if(!key_dev->key_state)
mask = 0;
// 2,有數據返回一個POLLIN
if(key_dev->key_state)
mask |= POLLIN;
return mask;
}
int key_drv_fasync(int fd, struct file *filp, int on)
{
//只需要調用一個函數記錄信號該發送給誰
return fasync_helper(fd, filp, on, &key_dev->faysnc);
}
const struct file_operations key_fops = {
.open = key_drv_open,
.read = key_drv_read,
.write = key_drv_write,
.release = key_drv_close,
.poll = key_drv_poll,
.fasync = key_drv_fasync,
};
static int __init key_drv_init(void)
{
//演示如何獲取到中斷號
int ret;
// 1,設定一個全局的設備對象
key_dev = kzalloc(sizeof(struct key_desc), GFP_KERNEL);
// 2,申請主設備號
key_dev->dev_major = register_chrdev(0, "key_drv", &key_fops);
// 3,創建設備節點文件
key_dev->cls = class_create(THIS_MODULE, "key_cls");
key_dev->dev = device_create(key_dev->cls, NULL,
MKDEV(key_dev->dev_major,0), NULL, "key0");
// 獲取中斷號
key_dev->irqno = get_irqno_from_node();
ret = request_irq(key_dev->irqno,key_irq_handler,IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, "key3_eint10", NULL);
if(ret != 0)
{
printk("request_irq error\n");
return ret;
}
// a,硬件如何獲取數據--gpx1(地址映射)
key_dev->reg_base = ioremap(GPXCON_REG, 8);
// 初始化等待隊列頭
init_waitqueue_head(&key_dev->wq_head);
return 0;
}
/*測試文件*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <poll.h>
#include <signal.h>
struct key_event{
int code; // 表示按鍵的類型: home, esc, Q,W,E,R,T, ENTER
int value; // 表示按下還是擡起 1 / 0
};
#define KEY_ENTER 28
static int fd;
static struct key_event event;
void catch_signale(int signo)
{
if(signo == SIGIO)
{
printf("we got sigal SIGIO\n");
// 讀取數據
read(fd, &event, sizeof(struct key_event));
if(event.code == KEY_ENTER)
{
if(event.value)
{
printf("APP__ key enter pressed\n");
}else
{
printf("APP__ key enter up\n");
}
}
}
}
int main(int argc, char *argv[])
{
int ret;
fd = open("/dev/key0", O_RDWR);
if(fd < 0)
{
perror("open");
exit(1);
}
// 1,設置信號處理方法
signal(SIGIO,catch_signale);
// 2,將當前進程設置成SIGIO的屬主進程
fcntl(fd, F_SETOWN, getpid());
// 3,將io模式設置成異步模式
int flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | FASYNC );
while(1)
{
// 可以做其他的事情
printf("I am waiting......\n");
sleep(1);
}
close(fd);
return 0;
}
10-中斷的下半部
1,softirq: 處理比較快,但是內核級別的機制,需要修改整個內核源碼,不推薦也不常用
2,tasklet: 內部實現實際調用了softirq
3, workqueue: 工作隊列
1,tasklet:
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long); // 下半部的實現邏輯
unsigned long data; // 傳遞給func的參數
};
a, 初始化
struct tasklet_struct mytasklet;
tasklet_init(struct tasklet_struct * t, void(* func)(unsigned long), unsigned long data)
例子:
void key_tasklet_half_irq(unsigned long data)
{
// 表示有數據,需要去喚醒整個進程/等待隊列
wake_up_interruptible(&key_dev->wq_head);
//同時設置標誌位
key_dev->key_state = 1;
//發送信號
kill_fasync(&key_dev->faysnc, SIGIO, POLLIN);
}
tasklet_init(&key_dev->mytasklet, key_tasklet_half_irq, 45);
b,在上半部中放入到內核線程中--啓動
// 啓動下半步
tasklet_schedule(&key_dev->mytasklet);
c,模塊卸載的時候:
tasklet_kill(&key_dev->mytasklet);
/*驅動部分代碼*/
//設計一個全局設備對象--描述按鍵信息
struct key_desc{
unsigned int dev_major;
struct class *cls;
struct device *dev;
int irqno;
void *reg_base;
struct key_event event;
wait_queue_head_t wq_head;
int key_state; //表示是否有數據
struct fasync_struct *faysnc;
struct tasklet_struct mytasklet;
};
struct key_desc *key_dev;
void key_tasklet_half_irq(unsigned long data)//中斷的下半部
{
printk("-------%s-------------\n", __FUNCTION__);
// 表示有數據,需要去喚醒整個進程/等待隊列
wake_up_interruptible(&key_dev->wq_head);
//同時設置標誌位
key_dev->key_state = 1;
//發送信號
kill_fasync(&key_dev->faysnc, SIGIO, POLLIN);
}
irqreturn_t key_irq_handler(int irqno, void *devid)//中斷的上半部
{
printk("-------%s-------------\n", __FUNCTION__);
//讀取數據寄存器
int value = readl(key_dev->reg_base + 4) & (1<<2);
if(value){ // 擡起
printk("key3 up\n");
key_dev->event.code = KEY_ENTER;
key_dev->event.value = 0;
}else{//按下
printk("key3 pressed\n");
key_dev->event.code = KEY_ENTER;
key_dev->event.value = 1;
}
// 啓動下半步
tasklet_schedule(&key_dev->mytasklet);
return IRQ_HANDLED;
}
ssize_t key_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
//printk("-------%s-------------\n", __FUNCTION__);
//如果當前是非阻塞模式,並且沒有數據,立馬返回一個出錯碼
if(filp->f_flags & O_NONBLOCK && !key_dev->key_state)
return -EAGAIN;
int ret;
//2,在需要等待(沒有數據)的時候,進行休眠
wait_event_interruptible(key_dev->wq_head, key_dev->key_state);
// 表示有數據
ret = copy_to_user(buf, &key_dev->event, count);
if(ret > 0)
{
printk("copy_to_user error\n");
return -EFAULT;
}
// 傳遞給用戶數據之後,將數據清除掉
memset(&key_dev->event, 0, sizeof(key_dev->event));
key_dev->key_state = 0;
return count;
}
static int __init key_drv_init(void)
{
//演示如何獲取到中斷號
int ret;
// 1,設定一個全局的設備對象
key_dev = kzalloc(sizeof(struct key_desc), GFP_KERNEL);
// 2,申請主設備號
key_dev->dev_major = register_chrdev(0, "key_drv", &key_fops);
// 3,創建設備節點文件
key_dev->cls = class_create(THIS_MODULE, "key_cls");
key_dev->dev = device_create(key_dev->cls, NULL,
MKDEV(key_dev->dev_major,0), NULL, "key0");
// 4,硬件的初始化--地址映射或者中斷申請
key_dev->irqno = get_irqno_from_node();
ret = request_irq(key_dev->irqno, key_irq_handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
"key3_eint10", NULL);
if(ret != 0)
{
printk("request_irq error\n");
return ret;
}
// a,硬件如何獲取數據--gpx1
key_dev->reg_base = ioremap(GPXCON_REG, 8);
// 初始化等待隊列頭
init_waitqueue_head(&key_dev->wq_head);
//初始化tasklet
tasklet_init(&key_dev->mytasklet, key_tasklet_half_irq, 45);
return 0;
}
2,工作隊列和工作
typedef void (*work_func_t)(struct work_struct *work);
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func;
};
a, 初始化
void work_irq_half(struct work_struct *work)
{
printk("-------%s-------------\n", __FUNCTION__);
// 表示有數據,需要去喚醒整個進程/等待隊列
wake_up_interruptible(&key_dev->wq_head);
//同時設置標誌位
key_dev->key_state = 1;
//發送信號
kill_fasync(&key_dev->faysnc, SIGIO, POLLIN);
}
struct work_struct mywork;
INIT_WORK(struct work_struct *work, work_func_t func);
b, 在上半部中放入到內核線程中--啓動
schedule_work(&key_dev->mywork);
/*驅動部分代碼*/
//設計一個全局設備對象--描述按鍵信息
struct key_desc{
unsigned int dev_major;
struct class *cls;
struct device *dev;
int irqno;
void *reg_base;
struct key_event event;
wait_queue_head_t wq_head;
int key_state; //表示是否有數據
struct fasync_struct *faysnc;
struct work_struct mywork;
};
struct key_desc *key_dev;
void work_irq_half(struct work_struct *work)//代碼的下半部
{
printk("-------%s-------------\n", __FUNCTION__);
// 表示有數據,需要去喚醒整個進程/等待隊列
wake_up_interruptible(&key_dev->wq_head);
//同時設置標誌位
key_dev->key_state = 1;
//發送信號
kill_fasync(&key_dev->faysnc, SIGIO, POLLIN);
}
irqreturn_t key_irq_handler(int irqno, void *devid)//代碼的上半部
{
printk("-------%s-------------\n", __FUNCTION__);
//讀取數據寄存器
int value = readl(key_dev->reg_base + 4) & (1<<2);
if(value){ // 擡起
printk("key3 up\n");
key_dev->event.code = KEY_ENTER;
key_dev->event.value = 0;
}else{//按下
printk("key3 pressed\n");
key_dev->event.code = KEY_ENTER;
key_dev->event.value = 1;
}
// 啓動下半步
schedule_work(&key_dev->mywork);
return IRQ_HANDLED;
}
ssize_t key_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
//printk("-------%s-------------\n", __FUNCTION__);
//如果當前是非阻塞模式,並且沒有數據,立馬返回一個出錯碼
if(filp->f_flags & O_NONBLOCK && !key_dev->key_state)
return -EAGAIN;
int ret;
//2,在需要等待(沒有數據)的時候,進行休眠
wait_event_interruptible(key_dev->wq_head, key_dev->key_state);
// 表示有數據
ret = copy_to_user(buf, &key_dev->event, count);
if(ret > 0)
{
printk("copy_to_user error\n");
return -EFAULT;
}
// 傳遞給用戶數據之後,將數據清除掉
memset(&key_dev->event, 0, sizeof(key_dev->event));
key_dev->key_state = 0;
return count;
}
static int __init key_drv_init(void)
{
//演示如何獲取到中斷號
int ret;
// 1,設定一個全局的設備對象
key_dev = kzalloc(sizeof(struct key_desc), GFP_KERNEL);
// 2,申請主設備號
key_dev->dev_major = register_chrdev(0, "key_drv", &key_fops);
// 3,創建設備節點文件
key_dev->cls = class_create(THIS_MODULE, "key_cls");
key_dev->dev = device_create(key_dev->cls, NULL, MKDEV(key_dev->dev_major,0), NULL, "key0");
// 4,硬件的初始化--地址映射或者中斷申請
key_dev->irqno = get_irqno_from_node();
ret = request_irq(key_dev->irqno, key_irq_handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, "key3_eint10", NULL);
if(ret != 0)
{
printk("request_irq error\n");
return ret;
}
// a,硬件如何獲取數據--gpx1
key_dev->reg_base = ioremap(GPXCON_REG, 8);
// 初始化等待隊列頭
init_waitqueue_head(&key_dev->wq_head);
INIT_WORK(&key_dev->mywork, work_irq_half);//初始化工作隊列
return 0;
}