物聯網之內核及驅動開發初級四(中斷編程)

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;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章