应用层中启用异步通知机制
其实在应用层启用异步通知只三个步骤:
1)signal(SIGIO, sig_handler);
调用signal函数,让指定的信号SIGIO与处理函数sig_handler对应。
2)fcntl(fd, F_SET_OWNER, getpid());
指定一个进程作为文件的“属主(filp->owner)”,这样内核才知道信号要发给哪个进程。
3)f_flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, f_flags | FASYNC);
在设备文件中添加FASYNC标志,驱动中就会调用将要实现的test_fasync函数。
三个步骤执行后,一旦有信号产生,相应的进程就会收到。
参考代码:
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/fcntl.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/proc_fs.h>
#include <linux/workqueue.h>
#include <asm/uaccess.h>
#include <asm/system.h>
#include <asm/io.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#define DEVICE_NAME "adc_device"
#define ms 30
typedef struct adc_str{
void __iomem *adc_base;
struct clk *adc_clk;
volatile int is_adc_read;
int adc_data;
struct fasync_struct *async_queue;
struct timer_list my_timer;
}adc_str;
adc_str adc_entity ={.is_adc_read = 1};
static void start_adc(void);
void my_timer_func(unsigned long expires)
{
//while(readl(adc_entity.adc_base + S3C2410_ADCCON)&0x01);
while(!(readl(adc_entity.adc_base + S3C2410_ADCCON) & 0x8000));
adc_entity.adc_data = readl(adc_entity.adc_base + S3C2410_ADCDAT0) & 0x3ff;
printk("adc_data = %d\n",adc_entity.adc_data);
kill_fasync(&adc_entity.async_queue, SIGIO, POLL_IN);
//printk("enter timer intterupt\n");
mod_timer(&adc_entity.my_timer,jiffies + ms);
}
static irqreturn_t adc_irq_handle(int irq, void *dev_id)
{
if(adc_entity.is_adc_read)
{
adc_entity.adc_data = readl(adc_entity.adc_base + S3C2410_ADCDAT0) & 0x3ff;
adc_entity.is_adc_read = 0;
//wake_up_interruptible(&adc_waitq); 读完以后通知应用程序读取数据
}
kill_fasync(&adc_entity.async_queue, SIGIO, POLL_IN);
printk("enter interrupt,adc data = %d\n",adc_entity.adc_data);
start_adc();
return IRQ_HANDLED;
}
int adc_conf_init()
{
adc_entity.adc_clk = clk_get(NULL, "adc");
if (!adc_entity.adc_clk)
{
printk(KERN_ERR "failed to find adc clock source\n");
return -ENOENT;
}
clk_enable(adc_entity.adc_clk);
adc_entity.adc_base = ioremap(S3C2410_PA_ADC, 0x20);
if (adc_entity.adc_base == NULL)
{
printk(KERN_ERR "Failed to remap register block\n");
iounmap(adc_entity.adc_base);
}
//tmp = (1 << 14) | (255 << 6) | (2 << 3);
//writel(tmp, adc_entity.adc_base + S3C2410_ADCCON);
return 0;
}
int adc_int_init()
{
int ret;
ret = request_irq(IRQ_ADC, adc_irq_handle, IRQF_SHARED, DEVICE_NAME, (void *)1);
if (ret)
{
printk(KERN_ERR "IRQ%d error %d\n", IRQ_ADC, ret);
return -EINVAL;
}
return 0;
}
static void start_adc(void)
{
unsigned int tmp;
tmp = (1 << 14) | (255 << 6) | (2 << 3);
writel(tmp, adc_entity.adc_base + S3C2410_ADCCON);
tmp = readl(adc_entity.adc_base + S3C2410_ADCCON);
tmp = tmp | (1 << 0);
writel(tmp, adc_entity.adc_base + S3C2410_ADCCON);
}
static int adc_fasync(int fd, struct file *filp, int mode)
{
printk("touch fasync\n");
return fasync_helper(fd,filp,mode,&adc_entity.async_queue);//struct fasync_struct **
}
int adc_release(struct inode *inode, struct file *filp)
{
#if adc_int
free_irq(IRQ_ADC, (void *)1);
#else
del_timer(&adc_entity.my_timer);
adc_fasync(-1,filp,0);
#endif
return 0;
}
static int adc_open(struct inode *inode, struct file *file)
{
printk("adc_open \n");
adc_conf_init();
#if adc_int
adc_int_init();
printk("#if adc_int\n");
start_adc();
#else
start_adc();
init_timer(&adc_entity.my_timer);
adc_entity.my_timer.function = my_timer_func;
add_timer(&adc_entity.my_timer);
#endif
return 0;
}
static ssize_t adc_read(struct file *filp, char *buffer, size_t count, loff_t *ppos)
{
adc_entity.is_adc_read = 1;
copy_to_user(buffer, (char *)&adc_entity.adc_data, sizeof(adc_entity.adc_data));
adc_entity.adc_data = 0;
return sizeof(adc_entity.adc_data);
}
ssize_t adc_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
return 1;
}
struct file_operations adc_fops = {
.owner = THIS_MODULE,
.read = adc_read,
.write = adc_write,
//.ioctl = adc_ioctl,
.open = adc_open,
.release = adc_release,
.fasync = adc_fasync,
};
static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &adc_fops,
};
int adc_init(void)
{
int ret;
printk("adc init NOW\n");
ret = misc_register(&misc);
if (ret < 0)
{
printk(DEVICE_NAME " can't register\n");
return ret;
}
writel(0,S3C2410_GPBCON);
writel((1<<10)| (1<<12) | (1<<14) | (1<<16)| (1<<18) | (1 << 20),S3C2410_GPBCON);
writel(0,S3C2410_GPBUP);
writel(0,S3C2410_GPBDAT);
gpio_set_value(S3C2410_GPB5,1);
gpio_set_value(S3C2410_GPB7,0);
printk("S3C2410_GPB5:%d\t S3C2410_GPB7:%d\n",gpio_get_value(S3C2410_GPB5),gpio_get_value(S3C2410_GPB7));
printk (DEVICE_NAME" initialized success\n");
return ret;
}
void adc_exit(void)
{
writel(0,S3C2410_GPBDAT);
gpio_set_value(S3C2410_GPB5,0);
gpio_set_value(S3C2410_GPB7,1);
printk("S3C2410_GPB5:%d\t S3C2410_GPB7:%d\n",gpio_get_value(S3C2410_GPB5),gpio_get_value(S3C2410_GPB7));
printk("adc exit\n");
misc_deregister(&misc);
}
module_init(adc_init);
module_exit(adc_exit);
MODULE_AUTHOR("ysj");
MODULE_LICENSE("Dual BSD/GPL");
adc_test.c
#include <stdio.h>
#include <ctype.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
static int adc_data = 0;
static int fd;
void sig_handler()
{
unsigned int i;
unsigned int ret;
unsigned int val;
ret = read(fd,&adc_data, sizeof(int));
if (ret < 0)
{
printf("read err!\n");
return 0;
}
printf("application adc number = %d\n",adc_data);
adc_data = 0;
}
int main(int argc, char **argv)
{
int ret;
int f_flags;
fd = open("/dev/adc_device", O_RDWR);
if (fd < 0) {
printf("Can't open /dev/adc_device\n");
perror("/dev/adc_device:");
return -1;
}
signal(SIGIO, sig_handler); //指定的信号SIGIO
fcntl(fd, F_SETOWN, getpid());
f_flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, f_flags | FASYNC);
printf("adc_device is = %d\n",fd);
while (1) {
sleep(10);
printf("after 1000\n");
}
close(fd);
return 0;
}
驱动中需要实现的异步通知
3.1 异步通知内核实现
实现异步通知,内核需要知道几个东西:哪个文件(filp),什么信号(SIGIIO),发给哪个进程(pid),收到信号后做什么(sig_handler)。这些都由上述前两个步骤完成了,而这前两个步骤内核帮忙实现了,所以,我们只需要实现第三个步骤的一个简单的传参。
3.2 fasync_struct结构体
要实现传参,我们需要把一个结构体struct fasync_struct添加到内核的异步队列中,这个结构体用来存放对应设备文件的信息(如fd, filp)并交给内核来管理。一但收到信号,内核就会在这个所谓的异步队列头找到相应的文件(fd),并在filp->owner中找到对应的进程PID,并且调用对应的sig_handler了。
struct fasync_struct {
int magic;
int fa_fd;
struct fasync_struct *fa_next; /* singly linked list */
struct file *fa_file;
};
3.3 内核中我们的工作
上面说了前两个步骤会由内核完成,所以我们只要做两件事情:
1)定义结构体fasync_struct。
struct fasync_struct *async_queue;
2)实现test_fasync,把函数fasync_helper将fd,filp和定义的结构体传给内核。
int test_fasync (int fd, struct file *filp, int mode)
{
struct _test_t *dev = filp->private_data;
return fasync_helper(fd, filp, mode, &dev->async_queue);
}
函数fasync_helper的定义为:
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
前面的三个参数其实就是teat_fasync的三个参数,所以只要我们定义好的fasync_struct结构体也传进去就可以了。
3.4 其余的工作
另外还有两件事:
3)当设备可写时,调用函数kill_fasync发送信号SIGIO给内核。
if (dev->async_queue){
kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
}
讲解一下这个函数:
void kill_fasync(struct fasync_struct **fp, int sig, int band)
sig就是我们要发送的信号。
band(带宽),一般都是使用POLL_IN,表示设备可读,如果设备可写,使用POLL_OUT
4)当设备关闭时,需要将fasync_struct从异步队列中删除:
test_fasync(-1, filp, 0);
删除也是调用test_fasync,不过改了一下参数而已。
参考代码:
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/fcntl.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/proc_fs.h>
#include <linux/workqueue.h>
#include <asm/uaccess.h>
#include <asm/system.h>
#include <asm/io.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#define DEVICE_NAME "adc_device"
#define ms 30
typedef struct adc_str{
void __iomem *adc_base;
struct clk *adc_clk;
volatile int is_adc_read;
int adc_data;
struct fasync_struct *async_queue;
struct timer_list my_timer;
}adc_str;
adc_str adc_entity ={.is_adc_read = 1};
static void start_adc(void);
void my_timer_func(unsigned long expires)
{
//while(readl(adc_entity.adc_base + S3C2410_ADCCON)&0x01);
while(!(readl(adc_entity.adc_base + S3C2410_ADCCON) & 0x8000));
adc_entity.adc_data = readl(adc_entity.adc_base + S3C2410_ADCDAT0) & 0x3ff;
printk("adc_data = %d\n",adc_entity.adc_data);
kill_fasync(&adc_entity.async_queue, SIGIO, POLL_IN);
//printk("enter timer intterupt\n");
mod_timer(&adc_entity.my_timer,jiffies + ms);
}
static irqreturn_t adc_irq_handle(int irq, void *dev_id)
{
if(adc_entity.is_adc_read)
{
adc_entity.adc_data = readl(adc_entity.adc_base + S3C2410_ADCDAT0) & 0x3ff;
adc_entity.is_adc_read = 0;
//wake_up_interruptible(&adc_waitq); 读完以后通知应用程序读取数据
}
kill_fasync(&adc_entity.async_queue, SIGIO, POLL_IN);
printk("enter interrupt,adc data = %d\n",adc_entity.adc_data);
start_adc();
return IRQ_HANDLED;
}
int adc_conf_init()
{
adc_entity.adc_clk = clk_get(NULL, "adc");
if (!adc_entity.adc_clk)
{
printk(KERN_ERR "failed to find adc clock source\n");
return -ENOENT;
}
clk_enable(adc_entity.adc_clk);
adc_entity.adc_base = ioremap(S3C2410_PA_ADC, 0x20);
if (adc_entity.adc_base == NULL)
{
printk(KERN_ERR "Failed to remap register block\n");
iounmap(adc_entity.adc_base);
}
//tmp = (1 << 14) | (255 << 6) | (2 << 3);
//writel(tmp, adc_entity.adc_base + S3C2410_ADCCON);
return 0;
}
int adc_int_init()
{
int ret;
ret = request_irq(IRQ_ADC, adc_irq_handle, IRQF_SHARED, DEVICE_NAME, (void *)1);
if (ret)
{
printk(KERN_ERR "IRQ%d error %d\n", IRQ_ADC, ret);
return -EINVAL;
}
return 0;
}
static void start_adc(void)
{
unsigned int tmp;
tmp = (1 << 14) | (255 << 6) | (2 << 3);
writel(tmp, adc_entity.adc_base + S3C2410_ADCCON);
tmp = readl(adc_entity.adc_base + S3C2410_ADCCON);
tmp = tmp | (1 << 0);
writel(tmp, adc_entity.adc_base + S3C2410_ADCCON);
}
static int adc_fasync(int fd, struct file *filp, int mode)
{
printk("touch fasync\n");
return fasync_helper(fd,filp,mode,&adc_entity.async_queue);//struct fasync_struct **
}
int adc_release(struct inode *inode, struct file *filp)
{
#if adc_int
free_irq(IRQ_ADC, (void *)1);
#else
del_timer(&adc_entity.my_timer);
adc_fasync(-1,filp,0);
#endif
return 0;
}
static int adc_open(struct inode *inode, struct file *file)
{
printk("adc_open \n");
adc_conf_init();
#if adc_int
adc_int_init();
printk("#if adc_int\n");
start_adc();
#else
start_adc();
init_timer(&adc_entity.my_timer);
adc_entity.my_timer.function = my_timer_func;
add_timer(&adc_entity.my_timer);
#endif
return 0;
}
static ssize_t adc_read(struct file *filp, char *buffer, size_t count, loff_t *ppos)
{
adc_entity.is_adc_read = 1;
copy_to_user(buffer, (char *)&adc_entity.adc_data, sizeof(adc_entity.adc_data));
adc_entity.adc_data = 0;
return sizeof(adc_entity.adc_data);
}
ssize_t adc_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
return 1;
}
struct file_operations adc_fops = {
.owner = THIS_MODULE,
.read = adc_read,
.write = adc_write,
//.ioctl = adc_ioctl,
.open = adc_open,
.release = adc_release,
.fasync = adc_fasync,
};
static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &adc_fops,
};
int adc_init(void)
{
int ret;
printk("adc init NOW\n");
ret = misc_register(&misc);
if (ret < 0)
{
printk(DEVICE_NAME " can't register\n");
return ret;
}
writel(0,S3C2410_GPBCON);
writel((1<<10)| (1<<12) | (1<<14) | (1<<16)| (1<<18) | (1 << 20),S3C2410_GPBCON);
writel(0,S3C2410_GPBUP);
writel(0,S3C2410_GPBDAT);
gpio_set_value(S3C2410_GPB5,1);
gpio_set_value(S3C2410_GPB7,0);
printk("S3C2410_GPB5:%d\t S3C2410_GPB7:%d\n",gpio_get_value(S3C2410_GPB5),gpio_get_value(S3C2410_GPB7));
printk (DEVICE_NAME" initialized success\n");
return ret;
}
void adc_exit(void)
{
writel(0,S3C2410_GPBDAT);
gpio_set_value(S3C2410_GPB5,0);
gpio_set_value(S3C2410_GPB7,1);
printk("S3C2410_GPB5:%d\t S3C2410_GPB7:%d\n",gpio_get_value(S3C2410_GPB5),gpio_get_value(S3C2410_GPB7));
printk("adc exit\n");
misc_deregister(&misc);
}
module_init(adc_init);
module_exit(adc_exit);
MODULE_AUTHOR("ysj");
MODULE_LICENSE("Dual BSD/GPL");
adc_test.c
#include <stdio.h>
#include <ctype.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
static int adc_data = 0;
static int fd;
void sig_handler()
{
unsigned int i;
unsigned int ret;
unsigned int val;
ret = read(fd,&adc_data, sizeof(int));
if (ret < 0)
{
printf("read err!\n");
return 0;
}
printf("application adc number = %d\n",adc_data);
adc_data = 0;
}
int main(int argc, char **argv)
{
int ret;
int f_flags;
fd = open("/dev/adc_device", O_RDWR);
if (fd < 0) {
printf("Can't open /dev/adc_device\n");
perror("/dev/adc_device:");
return -1;
}
signal(SIGIO, sig_handler); //指定的信号SIGIO
fcntl(fd, F_SETOWN, getpid());
f_flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, f_flags | FASYNC);
printf("adc_device is = %d\n",fd);
while (1) {
sleep(10);
printf("after 1000\n");
}
close(fd);
return 0;
}