嵌入式linux-嵌入式内核及驱动开发,中断编程,添加(定义)设备节点,获取中断号,申请中断,字符设备驱动框架,传递数据给用户

文章目录

  • 1,中断号和中断申请
    • 1.1,中断号
    • 1.2,获取中断号的方法(添加设备节点):
      • 1.2.1,系统中已经定义好的
        • 1.2.1.1,查看原理图,找到按键所对应的中断号SPI Port No
        • 1.2.1.2,可以在设备树文件```arch/arm/boot/dts/exynos4x12-pinctrl.dtsi```中看到
      • 1.2.2,在编程过程中,需要定义自己的节点--描述当前设备用的中断号
    • 1.3,在驱动中去通过代码获取到中断号,并且申请中断(实现中断处理方法)
    • 1.4,实现字符设备驱动框架
    • 1.5,驱动中将硬件所产生的数据传递给用户
      • 1.5.1,硬件如何获取数据
      • 1.5.2,驱动如何传递给用户
      • 1.5.3,用户如何拿到--编写应用程序
    • 1.6,例---按键
      • 1.6.1,驱动代码 key_drv.c
      • 1.6.2,应用程序 key_test.c
      • 1.6.3,终端信息

https://blog.csdn.net/m0_37542524/article/details/85808651

1,中断号和中断申请

1.1,中断号

就是一个号码,需要通过一定的方式去获取到
在3.14.0内核中,从设备树中获取
嵌入式linux-嵌入式内核及驱动开发,中断编程,添加(定义)设备节点,获取中断号,申请中断,字符设备驱动框架,传递数据给用户_第1张图片
嵌入式linux-嵌入式内核及驱动开发,中断编程,添加(定义)设备节点,获取中断号,申请中断,字符设备驱动框架,传递数据给用户_第2张图片

  1. 在ARM裸机代码中需要配置:

    I/O口为中断模式,触发方式,I/O口中断使能
    设置GIC中断使能,分发配置,分发总使能,CPU外部中断接口使能,中断优先级
    
  2. 在linux内核中,只需要:

    中断号是什么,怎么得到中断号
    中断处理方法
    

1.2,获取中断号的方法(添加设备节点):

1.2.1,系统中已经定义好的

	1, 宏定义
			IRQ_EINT(号码)
	2,设备树文件中
		arch/arm/boot/dts/exynos4412-fs4412.dts

硬件连接:
		key3 ---- gpx1_2--- EINT10



设备树文件:arch/arm/boot/dts/exynos4x12-pinctrl.dtsi
	 gpx1: gpx1 {
                    gpio-controller;
                    #gpio-cells = <2>;

                    interrupt-controller;
                    interrupt-parent = <&gic>;
                    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>;
            };

1.2.1.1,查看原理图,找到按键所对应的中断号SPI Port No

嵌入式linux-嵌入式内核及驱动开发,中断编程,添加(定义)设备节点,获取中断号,申请中断,字符设备驱动框架,传递数据给用户_第3张图片

嵌入式linux-嵌入式内核及驱动开发,中断编程,添加(定义)设备节点,获取中断号,申请中断,字符设备驱动框架,传递数据给用户_第4张图片
在这里插入图片描述
嵌入式linux-嵌入式内核及驱动开发,中断编程,添加(定义)设备节点,获取中断号,申请中断,字符设备驱动框架,传递数据给用户_第5张图片

1.2.1.2,可以在设备树文件arch/arm/boot/dts/exynos4x12-pinctrl.dtsi中看到

嵌入式linux-嵌入式内核及驱动开发,中断编程,添加(定义)设备节点,获取中断号,申请中断,字符设备驱动框架,传递数据给用户_第6张图片

1.2.2,在编程过程中,需要定义自己的节点–描述当前设备用的中断号

	 arch/arm/boot/dts/exynos4412-fs4412.dts  +51
		
		 key_int_node{
            compatible = "test_key";
            interrupt-parent = <&gpx1>;
            interrupts = <2 4>;  	//2表示第几个中断号,4表示触发方式为下降沿
		};
编译设备树文件:
	make dtbs
更新dtbs文件:
	cp -raf arch/arm/boot/dts/exynos4412-fs4412.dtb  /tftpboot/
  • interrupts = <2 4>;中,中断号2的定位方法有两种:

    1.可以在设备树文件arch/arm/boot/dts/exynos4x12-pinctrl.dtsi数是第几个
    2.I/O引脚是gpx1_2,中断号就是2;I/O引脚是gpx1_7,中断号就是7

嵌入式linux-嵌入式内核及驱动开发,中断编程,添加(定义)设备节点,获取中断号,申请中断,字符设备驱动框架,传递数据给用户_第7张图片
在这里插入图片描述
开发板上电重启后,可以在/proc/device-tree/目录中看到我们添加的按键中断节点key_int_node
嵌入式linux-嵌入式内核及驱动开发,中断编程,添加(定义)设备节点,获取中断号,申请中断,字符设备驱动框架,传递数据给用户_第8张图片

1.3,在驱动中去通过代码获取到中断号,并且申请中断(实现中断处理方法)

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: irq 	设备对应的中断号
	参数2: handler 	中断的处理函数
			typedef irqreturn_t (*irq_handler_t)(int, void *);
	参数3:flags 	触发方式
			#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:name 	中断的描述,自定义,主要是给用户查看的
			/proc/interrupts
	参数5:dev 	传递给参数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个参数保持一致

嵌入式linux-嵌入式内核及驱动开发,中断编程,添加(定义)设备节点,获取中断号,申请中断,字符设备驱动框架,传递数据给用户_第9张图片
在这里插入图片描述

1.4,实现字符设备驱动框架

// 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.地址映射
		b.中断申请

1.5,驱动中将硬件所产生的数据传递给用户

1.5.1,硬件如何获取数据

	key: 按下和抬起: 1/0
	读取key对应的gpio的状态,可以判断按下还是抬起
	
	读取key对应gpio的寄存器--数据寄存器
	
	//读取数据寄存器
	int value = readl(key_dev->reg_base + 4) & (1<<2);

1.5.2,驱动如何传递给用户

	在中断处理中填充数据:
		key_dev->event.code = KEY_ENTER;
		key_dev->event.value = 0;


	在xxx_read中奖数据传递给用户
		ret = copy_to_user(buf, &key_dev->event,  count);

1.5.3,用户如何拿到–编写应用程序

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

1.6,例—按键

1.6.1,驱动代码 key_drv.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
//#include 

#define KEY_ENTER 28
#define GPX1_CON 0X11000C20

//设计一个描述按键的数据对象
struct key_event{
	int code; 	//表示按键的类型:home, ESC,Q,W,E,R,T, ENTER
	int value; 	//表示按下还是抬起 1/0
};

//设计一个类型,描述一个设备的信息
struct key_desc{
	unsigned int dev_major; 	//主设备号
	struct class *cls;
	struct device *dev; 	//创建设备文件
	int irqno; 	//中断号
	void *reg_base;
	struct key_event event;
};
struct key_desc *key_dev; 

irqreturn_t key_irq_handler(int irqno, void *devid)
{
	int value;
	//printk("---------------%s-------------------\n",__FUNCTION__);

	//读取按键状态
	value = readl(key_dev->reg_base + 4) & (0x01 << 2);
	if(value){
		printk("key up\n");
		key_dev->event.code = KEY_ENTER;
		key_dev->event.value = 0;
	}else{
		printk("key pressed\n");
		key_dev->event.code = KEY_ENTER;
		key_dev->event.value = 1;
	}
	
	return IRQ_HANDLED;
}

int get_irqno_from_node(void)
{
	int irqno;
	
	//获取设备树中的节点
	struct device_node *np = of_find_node_by_path("/key_int_node");
	if(np){
		printk("find node ok\n");
	}else{
		printk("find node ifaled\n");
	}

	//通过节点获取中断号
	irqno = irq_of_parse_and_map(np, 0); 	//0表示拿第0个,我们只定义了1个,所以填0
	printk("irqno = %d\n", irqno);

	return irqno;
}

ssize_t key_drv_read (struct file *filep, char __user *buf, size_t count, loff_t *foops)
{
	int ret;
	//printk("--------------%s-------------------\n",__FUNCTION__);

	ret = copy_to_user(buf, &key_dev->event, count);
	if(ret > 0)
	{
		printk(KERN_ERR "copy_to_usr error\n");
		return -EFAULT;
	}

	//数据传递给用户之后,需要将数据清除
	memset(&key_dev->event, 0, sizeof(key_dev->event));

	return count;
}

ssize_t key_drv_write (struct file *filep, const char __user *buf, size_t count, loff_t *fops)
{
	printk("--------------%s-------------------\n",__FUNCTION__);

	return 0;
}

int key_drv_open (struct inode *inode, struct file *filep)
{
	printk("--------------%s-------------------\n",__FUNCTION__);


	return 0;
}

int key_drv_close (struct inode *inode, struct file *filep)
{
	printk("--------------%s-------------------\n",__FUNCTION__);

	return 0;
}

const struct file_operations key_fops = {
	.read = key_drv_read,
	.write = key_drv_write,
	.open = key_drv_open,
	.release = key_drv_close,
};

static int __init key_drv_init(void)
{
	int ret;

	//1.实例化全局设备对象---分配空间
	key_dev = kzalloc(sizeof(struct key_desc), GFP_KERNEL);   //kzalloc()等价于kmalloc()之后,再把空间清零
	if(key_dev == NULL)
	{
		printk(KERN_ERR "malloc error\n");
		return -ENOMEM;
	}

	//2.动态申请主设备号
	key_dev->dev_major = register_chrdev(0, "key_dev_test", &key_fops);
	if(key_dev->dev_major < 0){
		printk(KERN_ERR "register_chrdev error\n");
		ret = -ENODEV;
		goto err_0;
	}else{
		printk("register_chrdev ok\n");
	}

	//3.自动创建设备节点文件
	key_dev->cls = class_create(THIS_MODULE, "key_cls");
	if(IS_ERR(key_dev->cls))
	{
		printk(KERN_ERR "class_create error\n");
		ret = PTR_ERR(key_dev->cls);
		goto err_1;
	}
	key_dev->dev = device_create(key_dev->cls, NULL, MKDEV(key_dev->dev_major, 3), NULL, "key%d",3);
	if(IS_ERR(key_dev->dev))
	{
		printk(KERN_ERR "device_create error\n");
		ret = PTR_ERR(key_dev->dev);
		goto err_2;
	}

	//4.硬件初始化---地址映射或中断申请
	
	//映射中断对应的DATA寄存器,用以获取按键状态
		key_dev->reg_base = ioremap(GPX1_CON, 8);
		if(key_dev->reg_base == NULL)
		{
			printk(KERN_ERR "ioremap error\n");
			ret = -ENOMEM;
			goto err_4;
		}
		else
		{
			printk("ioremap ok\n");
		}
		
	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");
		goto err_3;
	}
	
	return 0;
err_4:
	free_irq(key_dev->irqno, NULL);
err_3:
	device_destroy(key_dev->cls,  MKDEV(key_dev->dev_major, 3));
err_2:
	class_destroy(key_dev->cls);
err_1:
	unregister_chrdev(key_dev->dev_major, "key_dev_test");
err_0:
	kfree(key_dev);
	return ret;
}

static void __exit key_drv_exit(void)
{
	//释放中断
	free_irq(key_dev->irqno, NULL);

	//销毁类和节点
	device_destroy(key_dev->cls,  MKDEV(key_dev->dev_major, 3));
	class_destroy(key_dev->cls);

	//释放主设备号
	unregister_chrdev(key_dev->dev_major, "key_dev_test");

	//释放动态内存
	kfree(key_dev);

}

module_init(key_drv_init);
module_exit(key_drv_exit);
MODULE_LICENSE("GPL");

1.6.2,应用程序 key_test.c

#include 
#include 
#include 
#include 

#define KEY_ENTER 28

struct key_event{
	int code; 	//表示按键的类型:home, ESC,Q,W,E,R,T, ENTER
	int value; 	//表示按下还是抬起 1/0
};

int main(int argc, char *argv[])
{
	struct key_event event;
	int fd;

	fd = open("/dev/key3", O_RDWR);
	if(fd < 0)
	{
		perror("open");
		exit(1);
	}

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

	close(fd);

	
	return 0;
}

1.6.3,终端信息

嵌入式linux-嵌入式内核及驱动开发,中断编程,添加(定义)设备节点,获取中断号,申请中断,字符设备驱动框架,传递数据给用户_第10张图片

你可能感兴趣的:(嵌入式linux-嵌入式内核及驱动开发,中断编程,添加(定义)设备节点,获取中断号,申请中断,字符设备驱动框架,传递数据给用户)