ZYNQ异常与中断(二)
1.前言
上一节讲了ZYNQ的异常中断原理,不是特别详细,但也总算把大概的知识体系过了一遍,有了大概的知识体系,就可以开始异常中断的使用了。
这里是以ZYNQ7000为例,进行总结。
2.中断的嵌入式程序实现
ZYNQ7000是一款SOC,所以他的嵌入式开发流程和大部分单片机的开发流程是一样的,总结一下就是以下步骤:
-
在做某一个完整的嵌入式项目时,应该先结合数据手册,把项目中需要用的的底层资源写好,配置好各个相应的寄存器。
-
当所有的底层驱动都调试完成后,就可以开始着手构思整个项目的框架了。
-
当逻辑框架整理完成之后,按照框架将整个项目代码分成一个个小的模块来写。
-
当所有的代码基本上都写完之后,调试到没有语法错误,能够编译、连接、运行通过,烧录到单片机中进行仿真调试,根据实际中出现的Bug及项目要求,进行代码的修改和完善。
2.1 底层寄存器的编写
ZYNQ开发的软件通用的都是Vivado,网上有很多vivado的安装教程,就不赘述了。
在Vivado里集成了硬件设计和软件设计的功能,同样,也帮我们把底层逻辑写好了,就不需要我们去写,不然超级麻烦 XD!
- 打开vivado新建一个工程,点击Launch SDK运行SDK
- 可以看到有一个新的硬件平台信息,在SDK中新建一个工程
- 会跳出一个system.mss文件,在这里可以找到很多的例程,其中就包括中断的使用方法。
- 在这里找一个定时器中断的例子,进行学习
- 打开之后生成了个文件夹
- ps_timer_test
- ps_timer_test_bsp
- …example_1
其中 ps_timer_test_bsp就是vivado提供对应CPU的底层逻辑,里面包含了CPU各个模块寄存器的接口信息
大概就是这些东西,其实底层逻辑编写很简单,也很繁琐,随便打开一个文件xparameters.h,可以看到里面根据CPU的底层寄存器信息进行一大堆的定义,这些是连接程序和硬件的接口,我们需要用哪些东西,就可以在这写bsp文件夹里去找。
2.2 中断控制器的用法
了解一下中断控制器的使用,主要分为几个步骤,
- 初始化中断控制器 GIC
- 初始化中断异常
- 中断服务函数注册
- 在中断控制器中使能中断
- 使能外设中断
- 使能中断异常 。
有两步需要注意, 在中断控制器中使能中断 是要根据中断号使能相应的中断,比如本章介绍的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种形式:
- [label:]property-name;
属性为空值。如示例代码中20行到26行的autorepeat。 - [label:]property-name = ;
用<>括起来的值内容是32位数据的合集。如示例代码11行的reg = <0x0 0x40000000>。 - [label:]property-name = “string”;
用””包含的表示字符串,如第五行的compatible = “xlnx,zynq-zc702”, “xlnx,zynq-7000”。 - [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的数,即0x00000001、0x00000002这两个数据都是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上直连中段的范例,其中33,34表示中断号
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)
- 主动唤醒等待事件
进程休眠后使用下面两个函数来主动唤醒整个队列:
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. }