本文主要介绍在Linux操作系统下面,驱动程序内部的中断函数产生中断时被调用以后,如何通过信号量来和应用程序之间进行实时的通讯。本文所有程序的硬件平台是基于PPC405EP开发板的,运行的内核是Linux
2.4.25。
以DPRAM的驱动程序为例(DPRAM驱动程序的介绍和说明请参照“PowerPC405EP
FPGA开发板补充说明v2.doc”文档),标准的DPRAM在PPC405下面可以正常的读写,使用驱动函数里面的实现的read和write函数可以完成对DPRAM某个地址下,连续的某些存储空间的读写操作。但是在实际的通讯过程中,往往总是依靠外部其他设备完成对DPRAM的读写以后,再向PPC405发送一次中断来通知PPC405
DPRAM中的数据已经准备好,或者已经读取结束。PPC405应该响应该中断,应用程序应该按照程序流程和中断作出进一步的判断。
中断函数是位于DPRAM驱动里面的,当驱动加载时,DPRAM的驱动内部的init函数应该向Linux系统注册中断号和中断函数,比如:
request_irq(DPRAM_IRQ, dpram_interrupt, SA_INTERRUPT, "dpram",
NULL);
其中,DPRAM_IRQ是DPRAM驱动中使用到的中断号,实际中采用28和29,对应PPC405的EIRQ4和EIRQ5。而dpram_interrupt是某个中断号对应的中断程序,当该中断号对应的中断引脚接收到低电平脉冲时,这个被注册的中断程序就会被调用。而在该中断程序里面,应该向应用程序发送信号量,通知应用程序某个中断发生,由应用程序进行进一步的判断和操作。
实际中驱动程序中注册中断部分摘要如下所示:
#define DPRAM_IRQ_1 28 //EIRQ4 of PPC405EP
#define DPRAM_IRQ_2 29 //EIRQ5 of PPC405EP
//register the IRQ for DPRAM system
result = request_irq(DPRAM_IRQ_1, ppc_dpram_interrupt_1,
SA_INTERRUPT, "dpram_irq_1", NULL);
if (0 != result)
{
printk(KERN_INFO "PPC DPRAM
driver interrupt handler 1 install failed.\n");
}
result = request_irq(DPRAM_IRQ_2, ppc_dpram_interrupt_2,
SA_INTERRUPT, "dpram_irq_2", NULL);
if (0 != result)
{
printk(KERN_INFO "PPC DPRAM
driver interrupt handler 2 install failed.\n");
}
启动Linux以后,应该可以 cat /proc/inetrrupts 看到如下图所示:
其中,dpram_irq_1和dpram_irq_2就是在DPRAM的驱动程序中注册的中断,分别对应28和29的中断号。现在中断函数的简单处理如下:
static void ppc_dpram_interrupt_1(int irq, void *dev_id,
struct pt_regs *regs)
{
printk("EIRQ 4 of PPC405EP\n");//for debug
}
static void ppc_dpram_interrupt_2(int irq, void *dev_id,
struct pt_regs *regs)
{
printk("EIRQ 5 of PPC405EP\n");//for debug
}
这样系统启动以后,在IRQ4引脚上如果输入低电平的脉冲,在串口终端应该可以看到如下显示:
驱动程序内部可以加入信号量来和应用程序通讯,具体的关于信号量通讯的技术文档,可以参照下面的几个链接来学习,本文中只是应用,更多的细节需要用户自己学习,引申和掌握。
(信号量通讯的参考资料:
http://linux_kernel.blog.com/921870/
http://ftp.xjtu.edu.cn/ftp/pub/document/linux-book/Linux��̰�Ƥ��/05.pdf
)
在驱动的中断函数中,可以依靠向应用程序进程发送信号量来通知程序进程接收数据或者执行其他操作。这部分代码如下所示:
#define COMMAND_CHANGE_PID 0xAA
#define DPRAM_SIGNAL_1 46
#define DPRAM_SIGNAL_2 47
static int app_pid;
static struct task_struct
*ptask;
static void ppc_dpram_interrupt_1(int irq, void *dev_id,
struct pt_regs *regs)
{
printk("EIRQ 4 of PPC405EP\n");//for debug
//send a signal to application, tell it that a
eirq4 received.
ptask = find_task_by_pid(app_pid);
if (ptask != NULL)
{
send_sig(DPRAM_SIGNAL_1, ptask, 1);//send signal
DPRAM_SIGNAL_1 to application by EIRQ4
}
}
static void ppc_dpram_interrupt_2(int irq, void *dev_id,
struct pt_regs *regs)
{
printk("EIRQ 5 of PPC405EP\n");//for debug
//send a signal to application, tell it that a
eirq4 received.
ptask = find_task_by_pid(app_pid);
if (ptask != NULL)
{
send_sig(DPRAM_SIGNAL_2, ptask, 1);//send signal
DPRAM_SIGNAL_2 to application by EIRQ5
}
}
因为应用程序的进程号每次启动应用程序时是不定的,而信号量的发送,必须知道应用程序的进程号(send_sig(DPRAM_SIGNAL_2,
ptask, 1);中ptask即是应用程序的进程号)。因此在ioctrl函数里面,实现了应用程序设置进程号的功能:
case COMMAND_CHANGE_PID:
app_pid = arg;
break;
同时,在应用程序中,应该完成信号量函数,注册信号量,向驱动写入应用程序进程号等工作,相关的代码如下:
#include
static struct sigaction irq1_act;
static struct sigaction irq2_act;
static int dpram_fd;
static unsigned char buffer[1024];
//signal process for IRQ1
void irq1_drvSigHandler(int signo)
{
unsigned char *buffer;
unsigned char i;
int tom;
printf("signal is %d.\n", signo);
if(46 == signo)
{
//TODO: do something
here.
}
}
//signal process for IRQ2
void irq2_drvSigHandler(int signo)
{
unsigned char *buffer;
unsigned char i;
int tom;
printf("signal is %d.\n", signo);
if(47 == signo)
{
//TODO: do something
here.
}
}
下面是main函数中的注册信号量部分代码:
//register signal
printf("register the signal process
function.\n");
//irq1
irq1_act.sa_handler = irq1_drvSigHandler;
irq1_act.sa_flags = 0;
sigemptyset(&irq1_act.sa_mask);
//irq2
irq2_act.sa_handler = irq2_drvSigHandler;
irq2_act.sa_flags = 0;
sigemptyset(&irq2_act.sa_mask);
//register
ret = sigaction(46, &irq1_act, NULL);
if (ret == -1)
printf("Request signal 46
failed.\n");
ret = sigaction(47, &irq2_act, NULL);
if (ret == -1)
printf("Request signal 47
failed.\n");
//open a device
dpram_fd = open("/dev/dpram",O_SYNC |
O_RDWR,0);
printf("Transmit pid of %d to driver.\n",
getpid());
ret = ioctl(dpram_fd, 0xAA, getpid());
由此,在系统启动以后,EIRQ4上如果输入低电平脉冲,应该可以看到串口如下类似的输出:
附录:PPC405EP平台GPIO输出控制使用说明
如果在应用中需要向其他处理器或者LED灯等输出一个电平信号,可以使用PPC405EP的GPIO来完成。DPRAM驱动的ioctrl函数内部,实现了GPIO7和GPIO8控制外部设备,输出高低电平的功能。
驱动中的这部分代码如下所示:
#define PPC_DPRAM_BIT_1 0x01000000
#define PPC_DPRAM_BIT_2 0x00800000
static int ppc_dpram_ioctl(struct inode *inode, struct file
*filp,
unsigned
int cmd, unsigned long arg)
{
unsigned long i;
switch (cmd) {
case 0:
i = in_be32((volatile
unsigned*)GPIO0_OR);/
i = in_be32((volatile
unsigned*)GPIO0_OR);//read the GPIO0_OR register
i = i
|(PPC_DPRAM_BIT_1);
out_be32((volatile
unsigned*)GPIO0_OR, i);//send out to GPIO0_OR register
break;
case 3:
i = in_be32((volatile
unsigned*)GPIO0_OR);/
i = in_be32((volatile
unsigned*)GPIO0_OR);//read the GPIO0_OR register
i = i
|(PPC_DPRAM_BIT_2);
out_be32((volatile
unsigned*)GPIO0_OR, i);//send out to GPIO0_OR register
break;
而在测试程序中,可以加入下面代码来输出一个低电平:
else if(my=='1')
{
printf("Output a low pulse on
output1.\n");
ioctl(dpram_fd, 1, 0);//set
output1 high
ioctl(dpram_fd, 0, 0);//set
output1 low
ioctl(dpram_fd, 1, 0);//set
output1 high
}
else if(my=='2')
{
printf("Output a low pulse on
output2.\n");
ioctl(dpram_fd, 4, 0);//set
output2 high
ioctl(dpram_fd, 3, 0);//set
output2 low
ioctl(dpram_fd, 4, 0);//set
output2 high
}
用示波器测量,应该看到这两个引脚输出低电平约2.6uS的脉冲一次。