天天看點

從ARM裸機看驅動之按鍵中斷方式控制LED(二)

硬體環境:Samsung Cortex-A9 Exynos4412 BSP

軟體環境:Linux3.14

=====================================================

從ARM裸機看驅動相關文章清單:

從ARM裸機看驅動之按鍵中斷方式控制LED(一) http://blog.csdn.net/u010872301/article/details/78494383

=====================================================

        為了解決既要中斷執行快,又要做事情多的沖突,在linux中将中斷處理分為上下半部。在上半部,中斷處理程式完成中斷請求的響應以及完成那些對時間要求緊迫的工作;在下半部,完成那些耗時的工作。從下半部分執行機制來看——不管是tasklet(運作于中斷上下文)還是工作隊列( 運作于程序上下文)——這些推後的工作總是在上半部分被調用,然後交給核心在适當的時間來完成。

void task_func(unsigned long data)   //tasklet 的中斷底半部處理函數
{
	int ret;
	static int gpx1_1dat;
	static int count1 = 0;
	
	ret = gpio_get_value(gpx1_1dat);
	printk("ret:%d\n",ret);
	if(ret){
		printk("key interrupt fail...\n");
	}else{
		mdelay(data);
		while(!gpio_get_value(gpx1_1dat));//等待按鍵擡起
		count1++;
		printk("key interrupting..., count;%d\n",count1);
	}
}

struct tasklet_struct task1={      //底半部結構體
	.func = task_func,
	.data = 5,
};

//DECLARE_TASKLET(task1,task_func,0);           //綁定tasklet結構體及其中斷處理函數
irqreturn_t key_handler(int irq, void *data)    //中斷頂半部處理函數
{
	tasklet_schedule(&task1);                    //在中斷頂半部處理函數中對底半部函數進行排程
	return IRQ_HANDLED;                          //中斷處理完成傳回标志
}

static int __init demo_init(void)
{
        int ret = 0;
。。。。。
	ret = request_irq(irq,key_handler,IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,DEV_NAME,NULL);//申請中斷号
	if(ret){
		printk("request irq fail!\n");
		return ret;
	}
。。。。。
}

           

       arm裸機中,在執行程式的過程中首先将寄存器的值壓入堆棧,儲存現場。然後調用do_IRQ函數,在do_IRQ函數中會調用到handle_IRQ_event函數,執行中斷服務程式。

        linux核心中,在do_IRQ函數中,通過對irq_desc結構體中handler_irq字段的引用,調用handler_irq所指向的服務程式;在這個服務程式中會調用hand_IRQ_event函數;在hand_IRQ_event函數中,通過對irqaction結構體中handler字段的引用最終調用我們所寫的中斷處理程式。

       三個資料結構關系:

(1)struct irq_chip描述了中斷最底層的部分,提供底層的中斷處理接口函數。

(2)struct irq_desc将中斷中的硬體相關的部分和軟體相關的部分連接配接起來,每個中斷源對應一個irq_desc結構體。

(3)struct irqacton則描述上層具體的中斷處理函數,每個裝置共享一條中斷請求(IRQ)線時,通過irqaction結構體區分不同裝置中斷服務程式(ISR)。

從ARM裸機看驅動之按鍵中斷方式控制LED(二)

功能實作

      通過中斷方式檢測按鍵是否按下并在中斷服務函數中寫處理函數,燈會閃爍一次。

(1)裝置樹配置

在fs4421開發闆的根節點下建立兩個子節點LED和KEY按鍵,并添加屬性資訊。裝置樹是從Linux3.x核心開始引入的概念,具體的使用請參考  ARM Linux 3.x的裝置樹(Device Tree)http://blog.csdn.net/u010872301/article/details/72599115

(2)使用jiffies消抖

按鍵按下時LED在閃爍,因為按下和釋放的瞬間都有抖動現象,為了穩定按一下燈亮再按一下滅,我們用一個jiffies時間內插補點做判斷(if(jiffies - last > 50)),進行消抖。

(3)在linux中裝置是裝置,驅動是驅動,根據linux裝置驅動分離思想,我們通過APP應用程式控制兩個的字元裝置驅動(LED、按鍵)。

源代碼

1、LED裝置驅動

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include "led.h"

MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("for linux driver led");

struct resource * gpx2con_res;
struct resource * gpx2dat_res;
unsigned int * gpx2con;
unsigned int * gpx2dat;
int major = 255;
int min = 0;
dev_t devno;
struct cdev cdev;
struct class * led_class;

static int led_open(struct inode * inodep, struct file * file)
{
    printk("led_open\n");
    return 0;
}
static int led_release(struct inode * inodep, struct file * file)
{
    printk("led_release\n");
    return 0;
}

static long led_ioctl(struct file * file, unsigned int cmd, unsigned long arg) 
{
	//printk("led_ioctl\n");
	switch(cmd){
	case LED_ON:
		printk("LED_ON\n");
		writel(readl(gpx2dat)|(0x1<<7), gpx2dat);
		break;
	case LED_OFF:
		writel((readl(gpx2dat)&(~(1<<7))), gpx2dat);
		break;
	default:
		break;
	}
	return 0;
}

struct file_operations led_fops = {
	.owner = THIS_MODULE,
        .open = led_open,
     .release = led_release,
        .unlocked_ioctl = led_ioctl,
};

static int led_probe(struct platform_device * dev)
{
	int ret;
	printk("platform: match ok!\n");

	gpx2con_res = platform_get_resource(dev, IORESOURCE_MEM, 0);
	if (gpx2con_res == NULL){
		printk("no resource gpx2con_res\n");
		return -1;
	}
	printk("start: %#x\n", gpx2con_res->start);
	printk("end: %#x\n", gpx2con_res->end);
	printk("+++++++++++++++++++++++++++++++++++++++\n");
	gpx2dat_res = platform_get_resource(dev, IORESOURCE_MEM, 1);
	if (gpx2dat_res == NULL){
		printk("no resource gpx2dat_res\n");
		return -1;
	}
	printk("start: %#x\n", gpx2dat_res->start);
	printk("end: %#x\n", gpx2dat_res->end);

	gpx2con = ioremap(gpx2con_res->start, gpx2con_res->end - gpx2con_res->start);
	if (gpx2con == NULL){
		printk("ioremap err\n");
		goto err1;
	}
	gpx2dat = ioremap(gpx2dat_res->start, gpx2dat_res->end - gpx2dat_res->start);
	if (gpx2dat == NULL){
		printk("ioremap err\n");
		goto err2;
	}

	writel((readl(gpx2con)&(~(0xf<<28)))|(0x1<<28), gpx2con);
	writel(readl(gpx2dat)|(0x1<<7), gpx2dat);

	devno = MKDEV(major, min);
	ret = register_chrdev_region(devno, 1, "led device");
	if (ret < 0){
		printk("register_chrdev_region err\n");
		goto err3;
	}

	cdev_init(&cdev, &led_fops);
	cdev.owner = THIS_MODULE;
	ret = cdev_add(&cdev, devno, 1);
	if (ret < 0){
		printk("cdev_add err\n");
		goto err4;
	}
	led_class = class_create(THIS_MODULE, "led_class");
	device_create(led_class, NULL, devno, NULL, "led");

	return 0;
err4:
	unregister_chrdev_region(devno, 1);
err3:
	iounmap(gpx2dat);
err2:
	iounmap(gpx2con);
err1:
	return -1;
}
static int led_remove(struct platform_device * dev)
{
	printk("platform: led_remove!\n");
	writel((readl(gpx2dat)&(~(1<<7))), gpx2dat);
	device_destroy(led_class, devno);
	class_destroy(led_class);
	cdev_del(&cdev);
	unregister_chrdev_region(devno, 1);
	iounmap(gpx2dat);
	iounmap(gpx2con);
	return 0;
}

struct of_device_id led2_dev[] = {
	{
		.compatible = "farsight, led2",
	},
	{},
};

struct platform_driver led_driver = {
	.probe = led_probe,
	.remove = led_remove,
	.driver = {
		.name = "11111111111111",
		.of_match_table = led2_dev, 
	},
};

static int led_init(void)
{
	printk("led_init\n");
	platform_driver_register(&led_driver);
	return 0;
}
static void led_exit(void)
{
	printk("led_exit\n");
	platform_driver_unregister(&led_driver);
	return;
}

module_init(led_init);
module_exit(led_exit);
           

2、KEY按鍵裝置驅動

#include <linux/init.h>
#include <linux/time.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/version.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/version.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <asm/uaccess.h> 
#include <asm/ioctl.h>
#include <linux/mm.h>
#include <linux/wait.h>

MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("for linux driver key");

unsigned int num1 = 0;
struct mycdev{
	dev_t devnum;
	struct cdev *cdev;
	wait_queue_head_t readque;
	int irqnum;
	int keyflag;
	int keycode;
}mydev;

int myopen(struct inode *inodep, struct file *filep)
{	
	printk("open: %d\n", iminor(inodep));
	return 0;
}

irqreturn_t myhandler(int num, void *data)
{
	static unsigned long last = 0;
	
	if(jiffies - last > 50){   
		if(num1 % 2){
			mydev.keycode = '0';
			num1++;
		} else {
			mydev.keycode = '1';
			num1++;
		}
		mydev.keyflag = 1;
		wake_up_interruptible(&mydev.readque);	
		last = jiffies;
	}
		
        return IRQ_HANDLED;
}

ssize_t myread(struct file *filep, char __user *ubufer, size_t size, loff_t *offset)
{
	wait_event_interruptible(mydev.readque, 0 != mydev.keyflag);
	put_user(mydev.keycode, ubufer);
	mydev.keyflag = 0;	

	return 1;
}

ssize_t mywrite(struct file *filep, const char __user *ubufer, size_t size, loff_t *offset)
{
    return 0;
}

int myclose(struct inode *inodep, struct file *filep)
{
    return 0;
}

long myunlocked_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
{
    return 0;
}

int mymmap(struct file *filep, struct vm_area_struct *vma)
{   
    return 0;
}

struct file_operations mycdev_ops = {
	.owner = THIS_MODULE,
	.open = myopen,
	.read = myread,
     .write = mywrite,
     .unlocked_ioctl = myunlocked_ioctl,
     .mmap = mymmap,
     .release = myclose,
};

int setup_mydev(void)
{
	int ret;

	ret = alloc_chrdev_region(&mydev.devnum, 0, 1, "mycdev_test");
	if(ret < 0)
	{
		printk("devnum alloc failed !\n");
		return ret; 
	}
	printk("num: %d\n", MAJOR(mydev.devnum) );
	
	init_waitqueue_head(&mydev.readque);
	mydev.keyflag = 0;

	mydev.cdev = cdev_alloc();
	mydev.cdev->ops  = &mycdev_ops;
	mydev.cdev->owner = THIS_MODULE;

	ret = cdev_add(mydev.cdev, mydev.devnum, 1);
	if(ret){	
		printk("devnum alloc failed !\n");
		goto add_failed;
	}

	if( request_irq(mydev.irqnum, 
			myhandler,
			IRQF_DISABLED | IRQF_TRIGGER_FALLING,
			"test irq",
			NULL) ){
		;//...
	}
	
	return 0;
	cdev_del(mydev.cdev);
add_failed:
	unregister_chrdev_region(mydev.devnum, 1);
	return ret;
}

void remove_mycdev(void)
{
	cdev_del(mydev.cdev);
	unregister_chrdev_region(mydev.devnum, 4);
}

struct platform_driver myplatformdriver = {0};
struct resource *myresource_irq;

int myprobe(struct platform_device *platformdev)
{
	printk("matched !\n");
	myresource_irq = platform_get_resource(platformdev, IORESOURCE_IRQ, 0);
	mydev.irqnum = myresource_irq->start;
	setup_mydev();
/*	
	cdev_add();
	...
	...
*/
	return 0;

}

int myremove(struct platform_device *platformdev)
{
	printk("dev removed !\n");
	free_irq(mydev.irqnum, NULL);
	remove_mycdev();
	return 0;
}

struct of_device_id my_dts_table[2] = {
	[0] = {
		.compatible = "xxx",
	},
};

static int mymodule_init(void)
{
	printk("module install\n");	
	myplatformdriver.driver.name = "aaaaaaaaaaa";
	myplatformdriver.driver.of_match_table = my_dts_table;
	
	myplatformdriver.driver.owner = THIS_MODULE;
	myplatformdriver.probe = myprobe;
	myplatformdriver.remove = myremove;

	platform_driver_register(&myplatformdriver);

	return 0;
}

static void mymodule_exit(void)
{
	printk("module release\n");
	free_irq(myresource_irq->start, NULL);
	platform_driver_unregister(&myplatformdriver);
}

module_init(mymodule_init);
module_exit(mymodule_exit);
           

3、應用程式

#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include "led.h"
int main(int argc, const char *argv[])
{
	int fd,fd1;
	char buf[64] = "";

	fd = open("/dev/key", O_RDWR);
	fd1 = open("/dev/led", O_RDWR);
	if (fd < 0){
		perror("open /dev/key err");
		return -1;
	}
	if (fd1 < 0)
	{
		perror("open /dev/led err");
		return -1;
	}
	//printf("open success\n");
	while(1)
	{
		read(fd, buf, sizeof(buf));		
    	//printf("buf = %s\n", buf);
		switch (buf[0]){
		case '1':
			printf("LED_OFF\n");
			ioctl(fd1, LED_OFF);	
			//printf("<%d>\n",cmd);
			break;
		case '0':	
			ioctl(fd1, LED_ON);
			break;
		default:
			break;
		}			
	//printf("buf = %s\n", buf);
    }
  	close(fd);
	return 0;
}
           

運作結果

1、 進入核心源碼修改 arm/arm/boot/dts/exynos4412-fs4412.dts 裝置樹檔案,添加如下内容

從ARM裸機看驅動之按鍵中斷方式控制LED(二)
從ARM裸機看驅動之按鍵中斷方式控制LED(二)

2、 重新編譯 dts 檔案并拷貝到arm開發闆共享的/tftpboot 目錄下

$ make dtbs

$ cp arch/arm/boot/dts/exynos4412-fs4412.dtb /tftpboot

3、 編譯驅動子產品

$ make

4、 将 ko 檔案和測試程式拷貝到根檔案系統中

$ cp *.ko /source/rootfs

$ cp a.out /source/rootfs

5、 闆子上插入led和按鍵子產品

# sudo insmod fs4412-led2.ko

# mknod /dev/led c 250 0

# sudo insmod platform_driver.ko

# mknod /dev/key c 251 0

6、 測試功能

# ./a.out

從ARM裸機看驅動之按鍵中斷方式控制LED(二)

下載下傳

從ARM裸機看驅動之按鍵中斷方式控制LED(二)(内含Makefile,直接編譯即可使用)

http://download.csdn.net/download/u010872301/10117177

繼續閱讀