ZYNQ异常与中断(二)


1.前言

上一节讲了ZYNQ的异常中断原理,不是特别详细,但也总算把大概的知识体系过了一遍,有了大概的知识体系,就可以开始异常中断的使用了。
这里是以ZYNQ7000为例,进行总结。

2.中断的嵌入式程序实现

ZYNQ7000是一款SOC,所以他的嵌入式开发流程和大部分单片机的开发流程是一样的,总结一下就是以下步骤:


  1. 在做某一个完整的嵌入式项目时,应该先结合数据手册,把项目中需要用的的底层资源写好,配置好各个相应的寄存器

  2. 当所有的底层驱动都调试完成后,就可以开始着手构思整个项目的框架了。

  3. 当逻辑框架整理完成之后,按照框架将整个项目代码分成一个个小的模块来写

  4. 当所有的代码基本上都写完之后,调试到没有语法错误,能够编译、连接、运行通过,烧录到单片机中进行仿真调试,根据实际中出现的Bug及项目要求,进行代码的修改和完善


2.1 底层寄存器的编写

ZYNQ开发的软件通用的都是Vivado,网上有很多vivado的安装教程,就不赘述了。
在Vivado里集成了硬件设计和软件设计的功能,同样,也帮我们把底层逻辑写好了,就不需要我们去写,不然超级麻烦 XD!

  • 打开vivado新建一个工程,点击Launch SDK运行SDK
    在这里插入图片描述
  • 可以看到有一个新的硬件平台信息,在SDK中新建一个工程
    在这里插入图片描述
  • 会跳出一个system.mss文件,在这里可以找到很多的例程,其中就包括中断的使用方法。
    在这里插入图片描述
  • 在这里找一个定时器中断的例子,进行学习
    在这里插入图片描述
  • 打开之后生成了个文件夹
    1. ps_timer_test
    2. ps_timer_test_bsp
    3. …example_1

在这里插入图片描述
其中 ps_timer_test_bsp就是vivado提供对应CPU的底层逻辑,里面包含了CPU各个模块寄存器的接口信息
在这里插入图片描述
大概就是这些东西,其实底层逻辑编写很简单,也很繁琐,随便打开一个文件xparameters.h,可以看到里面根据CPU的底层寄存器信息进行一大堆的定义,这些是连接程序和硬件的接口,我们需要用哪些东西,就可以在这写bsp文件夹里去找。

在这里插入图片描述

2.2 中断控制器的用法

了解一下中断控制器的使用,主要分为几个步骤,

  1. 初始化中断控制器 GIC
  2. 初始化中断异常
  3. 中断服务函数注册
  4. 在中断控制器中使能中断
  5. 使能外设中断
  6. 使能中断异常 。

有两步需要注意, 在中断控制器中使能中断 是要根据中断号使能相应的中断,比如本章介绍的Timer 为私有定时器,中断号为 29 ,是在中断控制器 GIC 中的操作,而后面的 使能外设中断 是指在外设中打开它的中断,正常情况下是不打开的,打开之后就可以产生中断传递到中断控制器 GIC 。在以后的实验中可以借鉴这种写法。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
同样的,这些使能的函数也已经在底层逻辑文件里写好了,只需要在对应文件里找到对应的函数就行了,在进行程序编写的时候,只要注意格式规范,总体还是不难的。

3.petalinux系统下的中断实现

3.1 什么是petalinux?

PetalLinux是Xilinx公司推出的嵌入式Linux开发工具,专门针对Xilinx公司的FPGA SoC芯片和开发板,用户可以在PetaLinux工具的帮助下进行完整的开发流程。
Petalinux 可以非常方便地定制嵌入式 Linux 系统,只需要 Vivado 软件把硬件信息导出,然后 Petalinux 根据这些信息来配置 uboot ,内核、文件系统等。

3.2 设备树

设备树,应用程序和驱动程序是在linux开发里绕不开的3个步骤。
在这里插入图片描述
先说设备树,设备树是驱动程序连接硬件的接口,ARM Linux3.0以前是没有应用讴备树的,那时ARM Linux使用者们用c语言的数据结构来描述板子上的讴备,每一个设备就对应到一个描述文件。ARM的板子种类不计其数,而Linux社区为了保证内核源码的通用性,把这些描述文件全部塞了迕去,导致Linux内核充满了垃圾代码,震怒了linux之父。之后ARM社区就学习PowerPC引入了设备树来替代以前的方法。
在驱动对应的硬件有变动时,不需要重新编译内核或驱动程序,只需要单独编译设备树文件,在启动板卡时把设备树结果传给内核即可。

遗憾的是,返个优势在petalinux中没有得到体现,petalinux编译出来的结果中,u-boot、内核和设备树被一并打包迕了image.ub里,而不是单独提供的。所以变更设备树时,往往会使用petalinux-build命令把内核整体重新编译。

设备树即树状的设备结构,“树”是个形象的比喻,以系统总线为主干,其他挂在在系统总线上的如I2C、SPI、GPIO控制器等设备为分支,而返些挂载在主干上的分支又有他们自身挂载的讴备,如I2C上挂载了EEPROM、RTC等设备,返样的树干分叉结构就如同树一般。

#include "zynq-7000.dtsi" 

/ { 
	model = "Zynq ZC702 Development Board"; 
	compatible = "xlnx,zynq-zc702", "xlnx,zynq-7000"; 
…… 
    memory@0 { 
   		device_type = "memory"; 
   		reg = <0x0 0x40000000>; 
   			 }; 
…… 
gpio-keys {
	compatible = "gpio-keys"; 
				#address-cells = <1>; 
				#size-cells = <0>; 
				autorepeat; 
    sw14 { 
	   			 label = "sw14"; 
				 gpios = <&gpio0 12 0>; 
				 linux,code = <108>; /* down */ 
				wakeup-source;
				autorepeat; 
				};
				  …… 
			 };
};

				 

3.2.1 节点

   		[label:] node-name[@unit-address] { 
   					[properties definitions] 
   					[child nodes] 
   					};

**通用节点格式**
    1) node-name是设备节点名称,根节点用”/”表示。
    2)unit-address一般是设备地址或寄存器首地址,
    3)label是设备别称,用亍便捷访问节点。
    	如一个名称为slcr@f8000000的节点,正常要访问的话,需要用名称slcr@f8000000去访问,
    	如果给这个节点加上标签slcr1: slcr@f8000000,访问节点叧需要使用&slcr1即可。
    4{}里的是节点的内容,节点内容有两类,[properties definitions]是节点属性。
    	[child nodes]是返个挂在在返个讴备节点上的子节点。子节点的格式和上面的一样。
	5[]中的部分是非必要项
	 aliases { 
	 				ethernet0 = "&gem0"; 
	 				serial0 = "&uart1"; 
 		};
 
  **特殊节点aliases**
    aliases节点用于定义别名,作用与label标签相似,格式如下:
  之后便可以使用&gem来访问节点ethernet。

3.2.2 属性

节点属性[properties definitions]有4种形式:

  1. [label:]property-name;
    属性为空值。如示例代码中20行到26行的autorepeat。
  2. [label:]property-name = ;
    用<>括起来的值内容是32位数据的合集。如示例代码11行的reg = <0x0 0x40000000>。
  3. [label:]property-name = “string”;
    用””包含的表示字符串,如第五行的compatible = “xlnx,zynq-zc702”, “xlnx,zynq-7000”。
  4. [label:]property-name = [bytestring];
    用[]括起来的表示字符序列,这个比较少见,举个例子,假设有属性表示为memory-addr = [0011223344];,这个就等同于memory-addr = [00 11 22 33 44];他的值是5个byte型的数组成的序列,并且这个数据类型是16进制。

属性可以用户自定义,也有很多标准属性,下面是几种常见的标准属性:

  • compatible属性
    compatible属性也叫兼容性,他的值是字符串列表,是设备驱动关联的关键,在驱动代码中定义一个OF匹配表来和compatible属性对应。如示例代码17行,节点gpio-keys的 compatible = “gpio-keys”,那在对应的驱动代码中,就会有以下对应:
			static const struct of_device_id gpiokeys_ids[] = { 
							 { .compatible = "gpio-keys", }, 
							 { /* sentinel */ } 
 							};

			of_device_id是OF匹配表的数据类型。
			当驱动程序OF匹配表中的compatible 值与设备树节点中的compatible 对应时,
			这个节点就会使用这个驱动程序。
			根节点中的compatible 属性表示这个板子兼容哪些平台。
			一般有两个值,前者表示板子的型号,后者表示使用的芯片。
  • model属性
    model属性的值也是字符串,一般用来表示板子的名称,和根节点中的compatible属性类似。

  • #address-cells、#size-cells和reg属性

    • #address-cells属性表示当前节点子节点的reg属性中,使用多少个u32整数来描述地址address。
    • #size-cells属性表示当前节点子节点的reg属性中, 使用多少个u32整数来描述大小length。
    • reg属性一般用亍描述某个外设的寄存器地址范围信息,格式为
   	 reg = <address1 length1 address2 length2 address3 length3……>


   	 父节点中的#address-cells属性表示reg属性中address的大小,
   	 		#size-cells表示reg属性中length的大小。 
       例子: 

   		ax-parent {
   			 #address-cells = <2>; 
 		 	  #size-cells = <1>; 
 				……
 				ax-son {
 						reg = <0x00000001 0x00000002 0x00000003 
 									0x00000004 0x00000005 0x00000006>; 
				 …… 
 		 				} 
 				  }

  			子节点ax-son的reg属性的有6个u32的数据,父节点ax-parent中#address-cells等亍2,
  			因此子节点ax-son的reg属性的值中表示:
  			address的有两个u32的数,即0x000000010x00000002这两个数据都是address的值。
  			同理#size-cells等于1,所以length1的值仅有一个u32的数据等于0x00000003。
  			而后面的三个u32数据则是address2和length2的值。
  • device_type属性
    返个属性现在只能用于 cpu 节点或者 memory 节点。在cpu节点中device_type = “cpu”,在memory节点中device_type = “memory”。
  • phandle属性
    phandle属性的取值值必须是唯一的,他的作用不label标签相似,用来引用节点。
   	 ax-node-1 {
   	 		 phandle = <1>; 
   	 		 interrupt-controller; 
   	 		  }
   	  ax-node-2 { 
   	    interrupt-parent = <1>;
   	    			 }
   		在节点ax-node-1中有phandle属性为<1>,
   		在ax-node-2中interrupt-parent属性需要指定父节点,赋值为<1>即可。

3.2.3 接口

设备树是内核连接硬件的接口,同样,驱动连接内核也是需要一写接口的,
inux内核提供了of凼数来让我们获得设备树中的信息,of函数来自这些函数的前缀”of_”。of凼数的原型定义在内核目录include/linux/of.h中,下面是一些常见的接口。

  • struct device_node *of_find_node_by_phandle(phandle handle);
  • struct device_node *of_get_parent(const struct device_node_ *node);
  • of_get_child_count()
  • of_property_read_u32_array()
  • of_property_read_u64()
  • of_property_read_string()
  • of_property_read_string_array()
  • of_property_read_bool()

3.2.4 中断在设备树中

#include <dt-bindings/media/xilinx-vip.h>
/include/ "system-conf.dtsi"

/ {
	model = "Zynq ALINX Development Board";
	compatible = "alinx,an5642", "xlnx,zynq-7000";
	usb_phy0: usb_phy@0 
	{
		compatible = "ulpi-phy";
		#phy-cells = <0>;
		reg = <0xe0002000 0x1000>;
		view-port = <0x0170>;
		drv-vbus;
	};
   	irq:irq@0{
		compatible = "hello,irq";
		interrupts = <0 33 0>;
		 };
	irqn:irq@1{
		compatible = "negedge,irq";
		interrupts = <0 34 0>;
		  };
	##这是ZYNQSOC上直连中段的范例,其中3334表示中断号
	
	irq2 {
        compatible = "irq2";
        alinxled-gpios = <&gpio0 54 0>;
    };
    irq3 {
        compatible = "irq3";
        alinxkey-gpios = <&gpio0 55 0>;
    };
    ##如果中断都被用光了怎么办?我们也可以用GPIO代替。

3.3 应用程序和驱动程序

Linux 应用程序调用驱动程序的步骤大致如下:
在这里插入图片描述

3.3.1 驱动程序的开发框架

Linux 驱动程序是有框架的,现有框架方便的我们增加新的驱动程序,如下:


  Linux驱动的基本框架包含两部分,“模块入口、出口的注册”和“模块入口、出口函数的实现”,如下方代码。  
		 1 static int __init shanwuyan_init(void)    //驱动入口函数
 		 2 {
 		 3     return 0;
		 4 }
 		 5 
 		 6 static void __exit shanwuyan_exit(void)    //驱动出口函数
		 7 {
		 8 
		 9 }
		10 
		11 module_init(shanwuyan_init);    //注册入口函数
		12 module_exit(shanwuyan_exit);    //注册出口函数

3.3.2 异步IO

3.3.2.1 驱动中的实现

在这里插入图片描述

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/types.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/delay.h>
 
#include <linux/dma-mapping.h>
 
#include <linux/pm.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/gfp.h>
#include <linux/mm.h>
#include <linux/dma-buf.h>
#include <linux/string.h>
#include <linux/uaccess.h>
#include <linux/dmaengine.h>
#include <linux/completion.h>
#include <linux/wait.h>
#include <linux/init.h>
 
#include <linux/sched.h>
#include <linux/pagemap.h>
#include <linux/errno.h>	/* error codes */
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/vmalloc.h>
 
#include <linux/moduleparam.h>
#include <linux/miscdevice.h>
#include <linux/ioport.h>
#include <linux/notifier.h>
#include <linux/init.h>
#include <linux/pci.h>
 
#include <linux/time.h>
#include <linux/timer.h>
 
 
 
 
//
static char 			devname[16];
static int 				major;
static int             	mijor;
static struct class*	cls;
static void __iomem*	base_address;	
static resource_size_t  remap_size;  
static int	            irq;
static struct device*	dev;           
 
//
 
 
#define DEVICE_NAME "irq_drv"
 
 
static volatile int irq_is_open = 0;
 
static struct fasync_struct *irq_async;
 
 
static int irq_drv_open(struct inode *Inode, struct file *File)
{
	irq_is_open = 1;
	return 0;
}
 
 
int irq_drv_release (struct inode *inode, struct file *file)
{
	irq_is_open = 0;
	return 0;
}
 
 
 
static ssize_t irq_drv_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
	return 0;
}
 
static ssize_t irq_drv_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
	return 0;
}
 
static int irq_drv_fasync (int fd, struct file *filp, int on)
{
	return fasync_helper (fd, filp, on, &irq_async);
}
 
 
static struct file_operations irq_fops = {	
	.owner  		= THIS_MODULE,
	.open 			= irq_drv_open,
	.read 			= irq_drv_read, 
	.write 			= irq_drv_write,
	.fasync		 	= irq_drv_fasync,
	.release		= irq_drv_release,
};
 
static irqreturn_t irq_interrupt(int irq, void *dev_id)
{
	printk("irq = %d\n", irq);
	if(irq_is_open)
	{
		kill_fasync (&irq_async, SIGIO, POLL_IN);
	}
	return IRQ_HANDLED;
}
 
static int irq_probe(struct platform_device *pdev)
{
	int					err;
	struct device *tmp_dev;
	memset(devname,0,16);
	strcpy(devname, DEVICE_NAME);
	
	major = register_chrdev(0, devname, &irq_fops);
 
	cls = class_create(THIS_MODULE, devname);
	mijor = 1;
	tmp_dev = device_create(cls, &pdev->dev, MKDEV(major, mijor), NULL, devname);
	if (IS_ERR(tmp_dev)) {
		class_destroy(cls);
		unregister_chrdev(major, devname);
		return 0;
	}
	
	
	irq = platform_get_irq(pdev,0);
	if (irq <= 0)
		return -ENXIO;
 
	dev = &pdev->dev;
 
	err = request_threaded_irq(irq, NULL,
				irq_interrupt,
				IRQF_TRIGGER_RISING | IRQF_ONESHOT,
				devname, NULL);				   
	if (err) {
		printk(KERN_ALERT "irq_probe irq	error=%d\n", err);
 
		goto fail;
	}
	else
	{
		printk("irq = %d\n", irq);
		printk("devname = %s\n", devname);
	}
 
 
	//保存dev
	//platform_set_drvdata(pdev, &xxx);	
 
 
 
	return 0;
 
fail:
	
	free_irq(irq, NULL);
 
	device_destroy(cls, MKDEV(major, mijor));	
	class_destroy(cls);
	unregister_chrdev(major, devname);
 
	return -ENOMEM;
 
}
 
static int irq_remove(struct platform_device *pdev)
{
	device_destroy(cls, MKDEV(major, mijor));	
	class_destroy(cls);
	unregister_chrdev(major, devname);
	
 
	free_irq(irq, NULL);
	printk("irq = %d\n", irq);
 
	return 0;
}
 
static int irq_suspend(struct device *dev)
{
 
	return 0;
}
 
static int irq_resume(struct device *dev)
{
 
	return 0;
}
 
static const struct dev_pm_ops irq_pm_ops = {
	.suspend = irq_suspend,
	.resume  = irq_resume,
};
 
 
//MODULE_DEVICE_TABLE(platform, irq_driver_ids);
 
static const struct of_device_id irq_of_match[] = {
	{.compatible = "hello,irq" },
	{ }
};
MODULE_DEVICE_TABLE(of, irq_of_match);
 
 
static struct platform_driver irq_driver = {
	.probe = irq_probe,
	.remove	= irq_remove,
	.driver = {
		.owner   		= THIS_MODULE,
		.name	 		= "irq@0",
		.pm    			= &irq_pm_ops,
		.of_match_table	= irq_of_match,		
	},
};
 
module_platform_driver(irq_driver);
MODULE_LICENSE("GPL v2");
3.3.2.2 应用程序

在这里插入图片描述

void input_handler(int num) 

//处理函数,没什么好讲的,用户自己定义 

{ 

     char data[MAX_LEN]; 

     int len; 

     //读取并输出STDIN_FILENO上的输入 

     len = read(STDIN_FILENO, &data, MAX_LEN); 

     data[len] = 0; 

     printf("input available:%s\n", data); 

} 

main() 

{ 

        int oflags; 

        //启动信号驱动机制 

        signal(SIGIO, input_handler); 
        //fcntl的F_SETOWN指令设置当前进程为设备文件owner

        fcntl(STDIN_FILENO, F_SETOWN, getpid()); 
        //fcntl的F_SETFL指令设置FASYNC标志

        oflags = fcntl(STDIN_FILENO, F_GETFL); 

        fcntl(STDIN_FILENO, F_SETFL, oflags | FASYNC); 

        //最后进入一个死循环,程序什么都不干了,只有信号能激发input_handler的运行 

        //如果程序中没有这个死循环,会立即执行完毕 

        while (1); 

} 

3.3.3 阻塞IO

返里说的阻塞IO实际上是同步阻塞IO。Linux的阻塞式访问中,应用程序调用read()函数从设备中读取数据时,如果设备或者数据没有准备好,就会进入休眠让出CPU资源,准备好时就会唤醒并返回数据给应用程序。 内核提供了等待队列机制来实现返回的休眠唤醒工作。

3.3.3.1 驱动中的实现

等待队列

1) 等待队列也就是进程组成的队列,Linux在系统执行会根据不同的状态把进程分成不同的队列,等待队列就是其中之一。 在驱动中使用等待队列步骤如下:
创建并初始化等待队列
创建等待队列的方式为创建一个等待队列头,往队列头下添加项即为队列。队列头定义再include/linux/wait.h中,详情如下:

	1. struct __wait_queue_head { 
	2. spinlock_t lock; 
	3. struct list_head task_list; 
	4.  };
	5.  typedef struct __wait_queue_head wait_queue_head_t;

定义好队列头之后,使用下面的函数来初始化队列头:

void init_waitqueue_head(wait_queue_head_t *q)

也可以使用宏定义

DECLARE_WAIT_QUEUE_HEAD_ONSTACK(name)

一次性完成队列头的创建和初始化,name为队列头的名字。
2) 创建代表进程的等待队列项
等待队列项也定义在include/linux/wait.h头文件中,可以用宏定义:

DECLARE_WAITQUEUE(name, tsk)

一次性完成队列项的定义和初始化,name为队列项的名字,tsk为队列项指代代的进程,一般设置为current。current是内核中的一个全局变量,表示当前进程。
3) 添加或移除等待队列项到等待队列中并进入休眠
设备数据不可访问时,就把进程添加进队列,使用接口函数:

void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)

q为需要加入的队列头,wait就是需要加入的队列项。 添加完成后使用函数

__set_current_state (state_value);

来设置进程状态,state_value 可以为:
TASK_UNINTERRUPTIBLE休眠不可被信号打断;
TASK_INTERRUPTIBLE休眠可被信号打断。 之后调用任务切换函数

schedule();

使当前进程进入休眠。如果被唤醒就会接着这个函数的位置往下运行。 紧接着,如果进程被设置成了TASK_INTERRUPTIBLE状态,有必要的话,还需要判断进程是否是被信号唤醒,如果是的话那就是误唤醒,需要让进程重新休眠。 使用函数

signal_pending(current)

来判断当前进程是否为信号唤醒,current就是当前进程,如果是则返回真。进程被唤醒后,使用

set_current_state(TASK_RUNNING)

设置当前进程为运行状态。 如过设备可以访问了,队列项从队列头中移除,使用函数:

void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
  1. 主动唤醒等待事件
    进程休眠后使用下面两个函数来主动唤醒整个队列:
void wake_up(wait_queue_head_t *q)
void wake_up_interruptible(wait_queue_head_t *q)

除了主动唤醒外,还可以设置成等待某个条件满足后自动唤醒,Linux提供了这些宏:

3.3.4 非阻塞IO

非阻塞IO的处理方式是轮询。Linux中提供了应用程序的轮询机制和相应的驱动程序供系统调用。

3.3.4.1 驱动中的实现

应用程序中提供了三种轮询的方法:select、poll、epoll。实际上他们也是多路复用IO的解决方法



#include <linux/module.h>  
#include <linux/kernel.h>
#include <linux/init.h>  
#include <linux/ide.h>  
#include <linux/types.h>  
#include <linux/errno.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <asm/uaccess.h>
#include <asm/mach/map.h>
#include <asm/io.h>
  
/* 设备节点名称 */  
#define DEVICE_NAME       "nio_key"
/* 设备号个数 */  
#define DEVID_COUNT       1
/* 驱动个数 */  
#define DRIVE_COUNT       1
/* 主设备号 */
#define MAJOR_U
/* 次设备号 */
#define MINOR_U           0

/* 把驱动代码中会用到的数据打包进设备结构体 */
struct alinx_char_dev {
/** 字符设备框架 **/
    dev_t              devid;             //设备号
    struct cdev        cdev;              //字符设备
    struct class       *class;            //类
    struct device      *device;           //设备
    struct device_node *nd;               //设备树的设备节点
/** gpio **/    
    int                alinx_key_gpio;    //gpio号
/** 并发处理 **/
    atomic_t           key_sts;           //记录按键状态, 为1时被按下
/** 中断 **/
    unsigned int       irq;               //中断号
/** 定时器 **/
    struct timer_list  timer;             //定时器
/** 等待队列 **/
    wait_queue_head_t  wait_q_h;          //等待队列头
};
/* 声明设备结构体 */
static struct alinx_char_dev alinx_char = {
    .cdev = {
        .owner = THIS_MODULE,
    },
};

/** 回掉 **/
/* 中断服务函数 */
static irqreturn_t key_handler(int irq, void *dev)
{
    /* 按键按下或抬起时会进入中断 */
    /* 开启50毫秒的定时器用作防抖动 */
    mod_timer(&alinx_char.timer, jiffies + msecs_to_jiffies(50));
    return IRQ_RETVAL(IRQ_HANDLED);
}

/* 定时器服务函数 */
void timer_function(unsigned long arg)
{
    /* value用于获取按键值 */
    unsigned char value;
    /* 获取按键值 */
    value = gpio_get_value(alinx_char.alinx_key_gpio);
    if(value == 0)
    {
        /* 按键按下, 状态置1 */
        atomic_set(&alinx_char.key_sts, 1);
/** 等待队列 **/
        /* 唤醒进程 */
        wake_up_interruptible(&alinx_char.wait_q_h);
    }
    else
    {
        /* 按键抬起 */
    }
}

/** 系统调用实现 **/
/* open函数实现, 对应到Linux系统调用函数的open函数 */  
static int char_drv_open(struct inode *inode_p, struct file *file_p)  
{  
    printk("gpio_test module open\n");  
    return 0;  
}  
  
/* read函数实现, 对应到Linux系统调用函数的write函数 */  
static ssize_t char_drv_read(struct file *file_p, char __user *buf, size_t len, loff_t *loff_t_p)  
{  
    unsigned int keysts = 0;
    int ret;
    
    /* 读取key的状态 */
    keysts = atomic_read(&alinx_char.key_sts);
    /* 判断文件打开方式 */
    if(file_p->f_flags & O_NONBLOCK)
    {
        /* 如果是非阻塞访问, 说明以满足读取条件 */
    }
    /* 判断当前按键状态 */
    else if(!keysts)
    {
        /* 按键未被按下(数据未准备好) */
        /* 以当前进程创建并初始化为队列项 */
        DECLARE_WAITQUEUE(queue_mem, current);
        /* 把当前进程的队列项添加到队列头 */
        add_wait_queue(&alinx_char.wait_q_h, &queue_mem);
        /* 设置当前进成为可被信号打断的状态 */
        __set_current_state(TASK_INTERRUPTIBLE);
        /* 切换进程, 是当前进程休眠 */
        schedule();
        
        /* 被唤醒, 修改当前进程状态为RUNNING */
        set_current_state(TASK_RUNNING);
        /* 把当前进程的队列项从队列头中删除 */
        remove_wait_queue(&alinx_char.wait_q_h, &queue_mem);
        
        /* 判断是否是被信号唤醒 */
        if(signal_pending(current))
        {
            /* 如果是直接返回错误 */
            return -ERESTARTSYS;
        }
        else
        {
            /* 被按键唤醒 */
        }
    }
    else
    {
        /* 按键被按下(数据准备好了) */
    }    
      
    /* 读取key的状态 */
    keysts = atomic_read(&alinx_char.key_sts);
    /* 返回按键状态值 */
    ret = copy_to_user(buf, &keysts, sizeof(keysts));
    /* 清除按键状态 */
    atomic_set(&alinx_char.key_sts, 0);
    return 0;  
}  

/* poll函数实现 */  
unsigned int char_drv_poll(struct file *filp, struct poll_table_struct *wait)
{
	unsigned int ret = 0;
	
    /* 将应用程序添添加到等待队列中 */
    poll_wait(filp, &alinx_char.wait_q_h, wait);
	
    /* 判断key的状态 */
	if(atomic_read(&alinx_char.key_sts))
	{
        /* key准备好了, 返回数据可读 */
		ret = POLLIN;
	}
    else
    {
        
    }
	
	return ret;
}
  
/* release函数实现, 对应到Linux系统调用函数的close函数 */  
static int char_drv_release(struct inode *inode_p, struct file *file_p)  
{  
    printk("gpio_test module release\n");
    return 0;  
}  
      
/* file_operations结构体声明, 是上面open、write实现函数与系统调用函数对应的关键 */  
static struct file_operations ax_char_fops = {  
    .owner   = THIS_MODULE,  
    .open    = char_drv_open,  
    .read    = char_drv_read,   
	.poll    = char_drv_poll,  
    .release = char_drv_release,   
};  
  
/* 模块加载时会调用的函数 */  
static int __init char_drv_init(void)  
{
    /* 用于接受返回值 */
    u32 ret = 0;
    
/** 并发处理 **/
    /* 初始化原子变量 */
    atomic_set(&alinx_char.key_sts, 0);
    
/** gpio框架 **/   
    /* 获取设备节点 */
    alinx_char.nd = of_find_node_by_path("/alinxkey");
    if(alinx_char.nd == NULL)
    {
        printk("alinx_char node not find\r\n");
        return -EINVAL;
    }
    else
    {
        printk("alinx_char node find\r\n");
    }
    
    /* 获取节点中gpio标号 */
    alinx_char.alinx_key_gpio = of_get_named_gpio(alinx_char.nd, "alinxkey-gpios", 0);
    if(alinx_char.alinx_key_gpio < 0)
    {
        printk("can not get alinxkey-gpios");
        return -EINVAL;
    }
    printk("alinxkey-gpio num = %d\r\n", alinx_char.alinx_key_gpio);
    
    /* 申请gpio标号对应的引脚 */
    ret = gpio_request(alinx_char.alinx_key_gpio, "alinxkey");
    if(ret != 0)
    {
        printk("can not request gpio\r\n");
        return -EINVAL;
    }
    
    /* 把这个io设置为输入 */
    ret = gpio_direction_input(alinx_char.alinx_key_gpio);
    if(ret < 0)
    {
        printk("can not set gpio\r\n");
        return -EINVAL;
    }

/** 中断 **/
    /* 获取中断号 */
    alinx_char.irq = gpio_to_irq(alinx_char.alinx_key_gpio);
    /* 申请中断 */
    ret = request_irq(alinx_char.irq,
                      key_handler,
                      IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
                      "alinxkey", 
                      NULL);
    if(ret < 0)
    {
        printk("irq %d request failed\r\n", alinx_char.irq);
        return -EFAULT;
    }
    
/** 定时器 **/
    alinx_char.timer.function = timer_function;
    init_timer(&alinx_char.timer);
    
/** 等待队列 **/
    init_waitqueue_head(&alinx_char.wait_q_h);

/** 字符设备框架 **/    
    /* 注册设备号 */
    alloc_chrdev_region(&alinx_char.devid, MINOR_U, DEVID_COUNT, DEVICE_NAME);
    
    /* 初始化字符设备结构体 */
    cdev_init(&alinx_char.cdev, &ax_char_fops);
    
    /* 注册字符设备 */
    cdev_add(&alinx_char.cdev, alinx_char.devid, DRIVE_COUNT);
    
    /* 创建类 */
    alinx_char.class = class_create(THIS_MODULE, DEVICE_NAME);
    if(IS_ERR(alinx_char.class)) 
    {
        return PTR_ERR(alinx_char.class);
    }
    
    /* 创建设备节点 */
    alinx_char.device = device_create(alinx_char.class, NULL, 
                                      alinx_char.devid, NULL, 
                                      DEVICE_NAME);
    if (IS_ERR(alinx_char.device)) 
    {
        return PTR_ERR(alinx_char.device);
    }
    
    return 0;  
}

/* 卸载模块 */  
static void __exit char_drv_exit(void)  
{  
/** gpio **/
    /* 释放gpio */
    gpio_free(alinx_char.alinx_key_gpio);

/** 中断 **/
    /* 释放中断 */
    free_irq(alinx_char.irq, NULL);

/** 定时器 **/
    /* 删除定时器 */   
    del_timer_sync(&alinx_char.timer);

/** 字符设备框架 **/
    /* 注销字符设备 */
    cdev_del(&alinx_char.cdev);
    
    /* 注销设备号 */
    unregister_chrdev_region(alinx_char.devid, DEVID_COUNT);
    
    /* 删除设备节点 */
    device_destroy(alinx_char.class, alinx_char.devid);
    
    /* 删除类 */
    class_destroy(alinx_char.class);
    
    printk("timer_led_dev_exit_ok\n");  
}  
  
/* 标记加载、卸载函数 */  
module_init(char_drv_init);  
module_exit(char_drv_exit);  
  
/* 驱动描述信息 */  
MODULE_AUTHOR("Alinx");  
MODULE_ALIAS("alinx char");  
MODULE_DESCRIPTION("NIO LED driver");  
MODULE_VERSION("v1.0");  
MODULE_LICENSE("GPL");  

3.3.5 阻塞IO和非阻塞IO的应用程序

1.#include "stdio.h" 
2.#include "unistd.h" 
3.#include "sys/types.h" 
4.#include "sys/stat.h" 
5.#include "fcntl.h" 
6.#include "stdlib.h"
7.#include "string.h" 
8.#include "poll.h" 
9. #include "sys/select.h" 
10. #include "sys/time.h" 
11. #include "linux/ioctl.h" 
12. int main(int argc, char *argv[]) 
13. 
14. 
15. /* ret获取返回值, fd获取文件句柄 */
16. int ret, fd, fd_l;
17. /* 定义一个监视文件读变化的描述符合集 */ 
18. fd_set readfds; 
19.  /* 定义一个超时时间结构体 */ 
20.  struct timeval timeout; 
21.  char *filename, led_value = 0;
22.  unsigned int key_value;
23. if(argc != 2) 
24. { 
25.  printf("Error Usage\r\n"); 
26. return -1; 
27. }
28. filename = argv[1];
29. /* 获取文件句柄, O_NONBLOCK表示非阻塞访问 */
30.  fd = open(filename, O_RDWR | O_NONBLOCK);
31.  if(fd < 0) 
32.  {
33.  printf("can not open file %s\r\n", filename);
34.  return -1; 
35.  } 
36. while(1) 
37.  { 
38. /* 初始化描述符合集 */ 
39.  FD_ZERO(&readfds);
40.  /* 把文件句柄fd指向的文件添加到描述符 */ 
41.  FD_SET(fd, &readfds);
42. /* 超时时间初始化为1.5秒 */ 
43. timeout.tv_sec = 1; 
44. timeout.tv_usec = 500000;
45.  /* 调用select, 注意第一个参数为fd+1 */ 
46. ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
47.  switch (ret) 
48.  { 
49.  case 0: 
50.  { 
51.  /* 超时 */ 
52. break;
53. }
54.  case -1: 
55.  { 
56.  /* 出错 */ 
57.  break; 
58. }
59.  default: 
60.  { 
61. /* 监视的文件可操作 */ 
62.  /* 判断可操作的文件是不是文件句柄fd指向的文件 */ 
63. if(FD_ISSET(fd, &readfds)) 
64.  { 
65.  /* 操作文件 */
66.  ret = read(fd, &key_value, sizeof(key_value)); 
67.  if(ret < 0) 
68. { 
69.  printf("read failed\r\n");
70.  break; 
71.  } 
72. printf("key_value = %d\r\n", key_value); 
73.  if(1 == key_value) 
74.  { 
75.  printf("ps_key1 press\r\n");
76.  led_value = !led_value; 
77.  fd_l = open("/dev/gpio_leds", O_RDWR); 
78.  if(fd_l < 0) 
79.  {
80.  printf("file /dev/gpio_leds open failed\r\n");
81.  break; 
82.  } 
83.  ret = write(fd_l, &led_value, sizeof(led_value)); 
84.  if(ret < 0) 
85.  { 
86. printf("write failed\r\n");
87.  break; 
88.  } 
89. ret = close(fd_l); 
90.  if(ret < 0) 
91.  { 
92.  printf("file /dev/gpio_leds close failed\r\n"); 
93.  break; 
94.  } 
95.  } 
96.  } 
97.  break;
98. } 
99.  } 
100.  } 
101.  close(fd);
102.  return ret; 
103.  }
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值