一,ZYNQ中断底层分类详解
1,ZYNQ CPU 软件中断(SGI,Software generatedinterrupts):ZYNQ 共有两个 CPU,每个 CPU 具备各自的 16 个软件中断(中断号0-15)(16–26 reserved) :被路由到一个或者两个CPU上,通过写ICDSGIR寄存器产生SGI.
2,CPU私有外设中断(PPI,private peripheralinterrupts ):私有中断是固定的不能修改。这里有 2 个 PL 到 CPU 的快速中断 nFIQ(中断号27-31):每个CPU都有一组PPI,包括全局定时器、私有看门狗定时器、私有定时器和来自PL的FIQ/IRQ.
3,ZYNQ PS 和 PL 共享中断(SPI,shared peripheralinterrupts):共享中断就是一些端口共用一个中断请求线:(中断号32-95):由PS和PL上的各种I/O控制器和存储器控制器产生,这些中断信号被路由到相应的CPU, PL部分有16个共享中断,它们的触发方式可以设置
共享中断就是 PL 的中断可以发送给 PS 处理,总共16 个 PL 的中断,它们可以设置为高电平或者低电平触发。
二,中断的软件设置流程:
根据中断ID查找GIC中断配置——>初始化GIC中断控制器——>异常初始化——>将中断注册到异常处理——>使能异常——>将中断处理函数与中断异常连接起来——>设置中断优先级触发方式——>GIC中断使能
1,中断控制器实例
init.c定义了一个XScuGic类型的结构体变量,设计者需要为系统中的每个中断设备分配一个此类型的变量。其它中断相关的API需要指向此变量的指针作为传入参数。
中断初始化函数中又定义了一个指向XScuGic_Config类型的结构体变量的指针,这个结构体包含了中断设备的配置信息。两个结构体的原型如下(注意XScuGic_Config也是XscuGic的一个成员):
typedef struct
{
u16 DeviceId;
u32 CpuBaseAddress;
u32 DistBaseAddress;
XScuGic_VectorTableEntry HandlerTable[XSCUGIC_MAX_NUM_INTR_INPUTS];// Vector table of interrupt handlers
} XScuGic_Config;
typedef struct
{
XScuGic_Config *Config; /**
u32 IsReady;
u32 UnhandledInterrupts;
} XScuGic;
2,中断控制器初始化
中断初始化函数中使用XScuGic_LookupConfig()函数和XScuGic_CfgInitialize()函数完成中断控制器的初始化。其实上面这部分处理和GPIO部分的一样,只是处理对象从“GPIO设备”换成了“中断设备”。
XScuGic_LookupConfig()函数的原型接口XScuGic_Config *XScuGic_LookupConfig(u16 DeviceId){};传入参数为设备ID,返回的是一个XScuGic_Config类型的指针。如果存在该中断设备,则返回该配置信息的列表;未找到则返回NULL。
XScuGic_CfgInitialize()函数的原型接口如下。该函数用来初始化传入的XScuGic类型的变量(可称作中断控制器实例),初始化该结构体中的成员字段。第二个参数便是用于初始化中断控制器实例的配置信息表。第三个参数EffectiveAddr是虚拟内存地址空间中的设备基地址,如果没有使用地址转换功能,传入Config->BaseAddress即可。
s32 XScuGic_CfgInitialize(XScuGic *InstancePtr,
XScuGic_Config *ConfigPtr, u32 EffectiveAddr) {}3.中断异常处理
中断控制器初始化完成后,使用Xil_ExceptionRegisterHandler()和Xil_ExceptionEnable()两个函数设置中断异常处理功能。
Xil_ExceptionRegisterHandler()为异常注册处理函数。处理器运行过程中遇到该异常,便会调用设置的处理函数。该函数原型如下,第一个为异常的ID号,第二个为设置的处理函数,第三个是处理函数被调用时传给它的参数。
void Xil_ExceptionRegisterHandler(u32 Exception_id,
Xil_ExceptionHandler Handler, void *Data)
Xil_ExceptionEnable();启用IRQ异常,没有参数和返回值。异常处理机制并不是中断独有的,在xil_exception.h文件中有所有异常情况的ID列表。下面贴出init.c中的代码,更进一步理解:
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler, &INTCInst);
XIL_EXCEPTION_ID_INT表示IRQ中断异常。XScuGic_InterruptHandler是库中提供的函数,它可以解析那些中断时活跃的和启用的,并调用相应的中断处理程序,先服务优先级最高的中断。该函数是保证中断系统正常运行的基础,其原型如下:void XScuGic_InterruptHandler(XScuGic *InstancePtr)
它需要一个指向XscuGic类型的指针参数。在调用Xil_ExceptionRegisterHandler()时通过设置第三个参数便可完成参数传入。
4,中断源设置
之后使用XScuGic_Connect()函数将中断源和中断处理程序联系起来,当中断发生时会调用设置的中断处理程序。函数原型如下,Int_ID是中断源对应的ID号;Handler是中断处理程序;最后一个void*类型的CallBackRef在中断处理程序被调用时会作为参数传入。
s32 XScuGic_Connect(XScuGic *InstancePtr, u32 Int_Id,
Xil_InterruptHandler Handler, void *CallBackRef)
再之后便是用自定义的函数完成中断敏感类型的设置,主要是对中断相关寄存器的配置。最后使用XScuGic_Enable()函数启用中断源。函数原型如下,Int_Id为中断源对应的ID号。
void XScuGic_Enable(XScuGic *InstancePtr, u32 Int_Id)
5,中断处理程序
static void SW_intr_Handler(void *param)
{
int sw_id = (int)param;
printf("SW%d int\n\r", sw_id); //根据中断请求源打印相关信息
}
中断处理程序只能传入void*类型的参数,因此在内部使用时需要做适当的类型转换。我们通过两个按键中断传入不同的参数,便可在一个中断处理程序中识别出到底是哪个中断源发出了中断请求
三,linux内核中断接口函数
linux 内核中的中断框架使用已经相当便捷,一般需要做三件事申请中断、实现中断服务函数、使能中断。对应的接口函数如下:
1,中断申请和释放函数
(1)中断申请函数request_irq,该函数可能会导致睡眠,在申请成功后会使能中断,函数原型:
Int request_irq(unsignedintirq,irq_handler_t handler, unsigned long flags, const char*name,void*dev);
参数说明:
irq:申请的中断号,是中断的唯一标识,也是内核找到对应中断服务函数的依据。
handler:中断服务函数,中断触发后会执行的函数。
name:中断名称,申请中断成功后,在/proc/interrupts 文件中可以找到这个名字。
dev:flag设置IRQF_SHARED时,使用dev来区分不同的设备,dev的值会传递给中断服务函数的第二个参数。
返回值:0-申请成功,-EBUSY-中断已被占用,其他值都表示申请失败。
flags:中断标志,用于设置中断的触发方式和其它特性,常用的标识有:
/* 无触収 */
IRQF_TRIGGER_NONE
/* 上升沿触収 */
IRQF_TRIGGER_RISING
/* 下降沿触収 */
IRQF_TRIGGER_FALLING
/* 高电平触収 */
IRQF_TRIGGER_HIGH
/* 低电平触収 */
IRQF_TRIGGER_LOW
/* 单次中断 */
IRQF_ONESHOT
/* 作为定时器中断 */
IRQF_TIMER
/* 共享中断,多个讴备共享一个中断号时需要此标志 */
IRQF_SHARED
(2)中断释放函数free_irq,如果目标中断不是共享中断,那举free_irq函数在释放中断后,会禁止中断并删除中断服务凼数,原型:void free_irq(unsigned int irq, void *dev);
参数说明: irq:需要释放的中断。 dev:释放的中断如果是共享中断,用这个参数来区分具体的中断,只有共享中断下所有的dev都被释放时,free_irq函数才会禁止中断并删除中断服务函数。
2,实现服务申请函数:irqreturn_t (*irq_handler_t) (int, void *);
第一个参数int型时中断服务函数对应的中断号。 第二个参数是通用指针,需要和request_irq函数的参数 dev 一致。 返回值 irqreturn_t 为return IRQ_RETVAL(IRQ_HANDLED);
3,中断使能和禁止函数
(1) enable_irq(unsigned int irq),disable_irq(unsigned int irq),disable_irq_nosync(unsigned int irq) enable_irq和disable_irq 分别是中断使能和禁止函数,irq是目标中断号。disable_irq会等待目标中断的中断服务函数执行结束才会禁止中断,如果想禁止中断则可以使用 disable_irq_nosync()
(2) local_irq_enable()和local_irq_disable() local_irq_enable()函数用于使能当前处理器的中断系统。 local_irq_disable()函数用于禁止当前处理器的中断系统。
(3)local_irq_save(flags)和local_irq_restore(flags) local_irq_save(flags)凼数也是用于禁止当前处理器中断,但是会把进之前的中断状态保存在输入参数 flags 中。而 local_irq_restore(flags)函数则是把中断恢复到 flags 中记录的状态。
四,linux 的底半部机制(上半部机制就是顶半部机制,下半部机制就是地半部机制)
上半部下半部是为了尽量缩短中断服务函数的处理过程而引入的机制,上半部就是中断出发后立即执行的中断服务函数,而下半部就是对中断服务函数的延时处理。因为中断的优先级较高,如果处理内容过多,长时间占用处理器会影响其他代码的运行,所以上半部要尽量的短。裸机程序里,在中断处理函数中标记flag,然后到主程序大循环中去轮询判断这个flag再做相应的操作,就是一种上半部下半部的思想。linux 中的上半部就是指中断服务函数irq_handler_t。 至于那些任务放在上半部哪些放在下半部,没有明确的界限,一般我们把对时间敏感、步能被打断的任务放到上半部中,其他的都可以考虑放到下半部。下面是一些针对下半部机制的:
1,软中断:
软中断结构体定义在include/linux/interrupt.h文件中,具体如下:
struct softirq_action
{
void (*action)(struct softirq_action *);
};
内核在kernel/softirq.c文件中定义了全局的软中断向量表:
static struct softirq_action softirq_vec[NR_SOFTIRQS];
NR_SOFTIRQS 为枚举类型的最大值,该枚举类型定义在include/linux/interrupt.h中,代表了十个软中断,要使用软中断只能向内核定义的软中断向量表注册,注册软中断需要使用函数:
void (int nr, void (*action)(struct softirq_action *));
enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
IRQ_POLL_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ,
NR_SOFTIRQS
};
参数说明:
nr:小于NR_SOFTIRQS的枚举值。 action:对应的软中断服务凼数。 软中断必项在编译时静态注册,注册完成就需要使用raise_softirq(unsigned int nr)触发,nr 即为需要触发的软中断。 但下半部机制通常不用软中断,而是使用tasklets机制。
2,tasklets 机制
linux内核在softirq_int函数中初始化软中断,其中HI_SOFTIRQ和TASKLET_SOFTIRQ 是默认打开的。tasklets机制就是在这两个软中断基础上实现的。tasklets的结构体定义在头文linux/interrupt.h
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
其中func就是相当于是tasklet的中断服务函数,tasklet的定义初始化可以用下面的宏定义完成:
DECLARE_TASKLET( name//tasklet 的名字,
func//tasklet 触发时的处理函数,
data//传递给func 的输入参数);
初始化完成后,调用tasklet_schedule(struct tasklet_struct *t)激活tesklet。激活后 esklet的服务函数就会在合适的时间运行。用作中断的下半段时,就在上半段中调用该函数。如果要用优先级较高的tasklet,就用tasklet_hi_schedule(struct tasklet_struct *t)函数激活。tasklet 的下半部机制示例:
/* 定义 taselet */
struct tasklet_struct example;
/* tasklet 处理函数 */
void testtasklet_func(unsigned long data)
{
/* tasklet 具体处理内容 */
}
/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
/* 调度 tasklet */
tasklet_schedule(&example);
}
/* 驱动入口函数 */
static int __init xxxx_init(void)
{
/* 初始化 tasklet */
tasklet_init(&example, testtasklet_func, data);
/* 注册中断处理函数 */
request_irq(irq, test_handler, 0, "name", &dev);
}
3,工作队列
工作队列也是下半部的实现方案。与tasklet相对的,工作队列是可阻塞的,因此不能在中断上下文中运行。工作队列的队列实现我们可以不用去管,要使用工作队列,只要定一个工作即可。
工作结构体是work_struct,定义在/include/linux/workqueue.h 文件中:
struct work_struct
{
atomic_long_t data;
struct list_head entry;
work_func_t func;
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
func 即为需要处理的函数。可使用以下宏定义来创建并初始化工作:DECLARE_WORK(n, f);
n:需要创建并初始化的工作结构体work_struct 的名称。 f:工作队列需要处理的函数。
初始化完成后,使用bool schedule_work(struct work_struct *work)函数来调用工作队列。
work:需要调用的工作。 返回值:0 成功,1 失败。
工作队列workqueue的下半段机制使用示例:
/* 定义工作(work) */
struct work_struct example;
/* work 处理函数 */
void work_func_t(struct work_struct *work);
{
/* work 具体处理内容 */
}
/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
/* 调度 work */
schedule_work(&example);
}
/* 驱动入口函数 */
static int __init xxxx_init(void)
{
......
/* 初始化 work */
INIT_WORK(&example, work_func_t);
/* 注册中断处理函数 */
request_irq(irq, test_handler, 0, "name", &dev);
}
五,设备树的中断
设备树中,通用的中断设置方法可参考文档 Documentation/devicetree/bindings/arm/arm,gi c.txt。Xilinx的设备树中断控制器的讴置不 linux 内核的通用设置稍有区别,可以查看文档 Docu mentation/devicetree/bindings/arm/xilinx,intc.txt具体了解。
axi_intc_0: interrupt-controller@41800000
{
/*"#interrupt-cells"是中断控制器节点的属性,用来描述子节点中"interrupts"属性值的数量。
一般父节点的"#interrupt-cells"值为3,则子节点的"interrupts"一个cell的三个32位整数的值
为<中断域中断号触发方式>,如果父节点的该属性是2,则是<中断号出发方式>。*/
#interrupt-cells = <2>;
//属性"interrupt-controller"代表这个节点是一个中断控制器
compatible = "xlnx,xps-intc-1.00.a";
//"interrupt-parent"属性表明这个设备属于哪个中断控制器,
如果没有这个属性会自动依附于父节点的"interrupt-parent"。
interrupt-controller;
//"interrupts",第一个值为0表示SPI中断,1表示PPI中断。
在zynqmp中,第一个值如果是 0,则中断号等于第二个值加32。
interrupt-parent = <&ps7_scugic_0>;
//中断号
interrupts = <0 29 4>;
//暂时不清楚
reg = <0x41800000 0x10000>;
//"xlnx,kind-of-intr"表示为每个可能的中断指定中断类型,1 表示 edge,0 表示 level。
xlnx,kind-of-intr = <0x1>;
//"xlnx,num-intr-inputs"属性指定控制器的特定实现支持的中断数,范围是 1~32。
xlnx,num-intr-inputs = <0x1>;
};
我们需要从设备树中获取设备号信息,以向内核注册中断,of 函数中有对应的函数:
unsigned int irq_of_parse_and_map(struct device_node *dev, int index);
要使用这个函数的话,需要我们在对应的设备中设置好"interrupts"属性。
dev 是设备节点
index 是对属性"interrupts"元素的索引,因为中断号的位置有可能不同。
返回值就是中断号。
如果是gpio,内核提供了更方便的函数获取中断号:int gpio_to_irq(unsigned int gpio);
gpio 为需要申请中断号的 gpio 编号。gpio 为需要申请中断号的 gpio 编号。
zynqmp下gpio是共享的一个中断,针对单个io去设置"interrupts"属性比较复杂,gpio_to_ irq函数做了很多事,下面的gpio中断实验,我们就直接使用这个函数。
六,中断驱动程序(用 petalinux-config -c rootfs 命令选上新增的驱动程序)
关于自旋锁保护的对象,实际上就是 alinx_char.key_sts 这个值,因为这个值在读函数中操作了,在中断开启定时器回掉函数中也操作了,这两个操作是有可能同时发生的,因此需要保护。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* 设备节点名称 */
#define DEVICE_NAME "interrupt_led"
/* 设备号个数 */
#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; //设备树的设备节点
/** 并发处理 **/
spinlock_t lock; //自旋锁变量
/** gpio **/
int alinx_key_gpio; //gpio号
int key_sts; //记录按键状态, 为1时被按下
/** 中断 **/
unsigned int irq; //中断号
/** 定时器 **/
struct timer_list timer; //定时器
};
/* 声明设备结构体 */
static struct alinx_char_dev alinx_char =
{
.cdev =
{
.owner = THIS_MODULE,
},
};
/** 回掉 **/
/* 中断服务函数 */
static irqreturn_t key_handler(int irq, void *dev)
{
//实现key_handler,先是开启了一个 50ms 的 timer,然后返回IRQ_RETVAL(IRQ_HANDLED)就行了。
/* 按键按下或抬起时会进入中断 */
/* 开启50毫秒的定时器用作防抖动 */
mod_timer(&alinx_char.timer, jiffies + msecs_to_jiffies(50));
return IRQ_RETVAL(IRQ_HANDLED);
}
/* 定时器服务函数 */
void timer_function(struct timer_list *timer)
{
unsigned long flags;
/* 获取锁 */
spin_lock_irqsave(&alinx_char.lock, flags);
/* value用于获取按键值 */
unsigned char value;
/* 获取按键值 */
value = gpio_get_value(alinx_char.alinx_key_gpio);
if(value == 0)
{
/* 按键按下, 状态置1 */
alinx_char.key_sts = 1;
}
else
{
/* 按键抬起 */
}
/* 释放锁 */
spin_unlock_irqrestore(&alinx_char.lock, flags);
}
/** 系统调用实现 **/
/* 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 long flags;
int ret;
/* 获取锁 */
spin_lock_irqsave(&alinx_char.lock, flags);
/* keysts用于读取按键状态 */
/* 返回按键状态值 */
ret = copy_to_user(buf, &alinx_char.key_sts, sizeof(alinx_char.key_sts));
/* 清除按键状态 */
alinx_char.key_sts = 0;
/* 释放锁 */
spin_unlock_irqrestore(&alinx_char.lock, flags);
return 0;
}
/* 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,
.release = char_drv_release,
};
/* 模块加载时会调用的函数 */
static int __init char_drv_init(void)
{
/* 用于接受返回值 */
u32 ret = 0;
/** 并发处理 **/
/* 初始化自旋锁 */
spin_lock_init(&alinx_char.lock);
/** 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;
}
/** 中断 **/
//获取中断号,初始化gpio后,使用gpio_to_irq凼数通过gpio端口号来获取中断号。
alinx_char.irq = gpio_to_irq(alinx_char.alinx_key_gpio);
/*申请中断:通过中断号向内核申请中断。上升沿戒下降沿触发,命名为”alinxkey”,
中断服务凼数为key_handler。现在我们只要实现 key_handler凼数就可以了*/
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;
}
/** 定时器 **/
timer_setup(&alinx_char.timer, timer_function, NULL);
/** 字符设备框架 **/
/* 注册设备号 */
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("subomb");
MODULE_ALIAS("subomb char");
MODULE_DESCRIPTION("INTERRUPT LED driver");
MODULE_VERSION("v1.0");
MODULE_LICENSE("GPL");
七,linux中断测试程序
#include
#include
#include
#include
int main(int argc, char *argv[])
{
int fd, fd_l ,ret;
char *filename, led_value = 0;
unsigned int key_value;
if(argc != 2)
{
printf("Error Usage\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if(fd < 0)
{
printf("file %s open failed\r\n", argv[1]);
return -1;
}
while(1)
{
ret = read(fd, &key_value, sizeof(key_value));
if(ret < 0)
{
printf("read failed\r\n");
break;
}
if(1 == key_value)
{
printf("ps_key1 press\r\n");
led_value = !led_value;
fd_l = open("/dev/gpio_leds", O_RDWR);
if(fd_l < 0)
{
printf("file /dev/gpio_leds open failed\r\n");
break;
}
ret = write(fd_l, &led_value, sizeof(led_value));
if(ret < 0)
{
printf("write failed\r\n");
break;
}
ret = close(fd_l);
if(ret < 0)
{
printf("file /dev/gpio_leds close failed\r\n");
break;
}
}
}
ret = close(fd);
if(ret < 0)
{
printf("file %s close failed\r\n", argv[1]);
return -1;
}
return 0;
}