jz2440_字符设备按键驱动程序

1. 字符设备驱动程序

本程序基于《字符设备驱动程序》的基础上进行改进。增加了按键触发点亮LED的功能,分为:

  1. 查询方式;
  2. 中断方式;
  3. poll/select方式;
  4. 异步通知方式(基于信号);

四个程序

2. 查询方式

2.1 驱动程序的编写

  1. 编写init函数,添加按键GIPO引脚的配置;

    设置新的file_operations结构体:

    static struct file_operations s3c2440_buttons_fops = {
    	.owner	 = THIS_MODULE,
    	.open 	 = s3c2440_buttons_open,
    	.read	 = s3c2440_buttons_read,
    	.release = s3c2440_buttons_close,
    };
    

    init函数的编写:

    static int __init s3c2440_buttons_init(void)
    {
    	major = register_chrdev(0, DEVICE_NAME, &s3c2440_buttons_fops);
        if (major < 0) {
    	      printk(DEVICE_NAME " can't register major number\n");
    	      return major;
        }
    	
    	s3c2440_buttons_class = class_create(THIS_MODULE, "s3c2440_buttons");
    	if (IS_ERR(s3c2440_buttons_class)) 
    		return PTR_ERR(s3c2440_buttons_class);
    
    	/* 在/sys/s3c2440_leds目录下创建一个设备,设备的目录为leds
    	 * 同时触发mdev在/dev目录下生成一个名为leds的设备节点
    	 */
    	s3c2440_buttons_dev = class_device_create(s3c2440_buttons_class, NULL, 
                                                  MKDEV(major, 0), NULL, "buttons");
    	if (unlikely(IS_ERR(s3c2440_buttons_dev)))
    		return PTR_ERR(s3c2440_buttons_dev);
    
    	gpfcon = (volatile unsigned long*)ioremap(S3C2440_GPFCON, 32);
    	gpfdat = gpfcon + 1;
    	gpfup  = gpfdat + 1;
    	
    	gpgcon = gpfcon + ((S3C2440_GPGCON - S3C2440_GPFCON) >> 2);
    	gpgdat = gpgcon + 1;
    	gpgup  = gpgdat + 1;
    	
    	printk(DEVICE_NAME " initialized!\n");
    	return 0;
    }
    

    按键引脚配置原理图和数据手册参考第二点。

    GPGCON的物理地址紧跟在GPGCON的后面,可以将它们的物理地址映射到同一块虚拟内存块中。(在我们的Linux系统的一块虚拟内存页是4K大小)ioremap每次映射都是以页为单位。

  2. 编写open函数,添加按键GIPO引脚的配置;

    GPG数据手册引脚配置:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6iCeAtHf-1658150705843)(images\s3c2440_字符设备按键驱动程序\gpg.png)]

    open函数:

    static ssize_t s3c2440_buttons_open(struct inode *inode, 
                                        struct file *file)
    {
    	/* 配置LED:GPIO4\5\6为输出引脚 */
    	*gpfcon &= ~((0x3 << 8) | (0x3 << 10) | (0x3 << 12)); /* 清零 */
    	*gpfcon |=  ((0x1 << 8) | (0x1 << 10) | (0x1 << 12)); /* 设置为输出 */
    
    	/* 配置按键:GPF0\2和GPG3 */
    	*gpfcon	&= ~((0x3 << 0) | (0x3 << 4));	/* 为零就是输入 */
    	*gpfcon &= ~((0x3 << 6));
    	
    	return 0;
    }
    
  3. 编写read函数,使得应用程序可以获取按键状态。

    static ssize_t s3c2440_buttons_read(struct file *file, 
                                        char __user *buf, size_t count, loff_t *ppos)
    {
    	unsigned long ret;
    	unsigned char key_vals[3] = {0};
    	if (count < sizeof(key_vals))
    		return -EINVAL;
    	/* GPF0\2 */
    	key_vals[0] = (*gpfdat & (1)) ? 1 : 0;
    	key_vals[1] = (*gpfdat & (1 << 2)) ? 1 : 0;
    	/* GPG3 */
    	key_vals[2] = (*gpgdat & (1 << 3)) ? 1 : 0;
    	ret = copy_to_user(buf, key_vals, sizeof(key_vals));
    	
    	return ret;
    }
    

2.2 测试程序的编写

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>

int main(int argc, char **argv)
{
	unsigned char key_vals[3] = {0};
	int fd;
	fd = open("/dev/buttons", O_RDWR);
	if (!fd)
	{
		printf("open /dev/buttons failed!\n");
		return 0;
	}

	while (1) {
		read(fd, key_vals, sizeof(key_vals));
		if (!key_vals[0] || !key_vals[1] || !key_vals[2])
			printf("btn1: %d, btn2: %d, btn3: %d\n", 
                   key_vals[0], key_vals[1], key_vals[2]);
	}
	return 0;
}

我们的测试程序以轮询的方式不断的去读取按键的状态,这样的代价是非常消耗CPU的资源。为了解决这个问题,我们需要引入一种机制,当按键状态发送改变,可以由驱动程序来提醒我们的应用程序

3. 中断方式

3.1 s3c2440 中断控制器分析

3.2 中断体系结构

中断体系结构原理分析

Linux内核的中断原理分析篇:Linux中断体系结构分析

注册 irqaction

要往 irq_desc 数组上添加 irqaction 结构,我们需要使用 request_irq 函数进行注册,其中 dev_id 可以视为该 irqaction 的标识符,在卸载时需要用到。该函数的定义如下:

int request_irq(unsigned int irq, irq_handler_t handler,
		unsigned long irqflags, const char *devname, void *dev_id)
{
	/* ...省略... */

    /* 动态申请一个irqaction数据结构 */
	action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC);
	if (!action)
		return -ENOMEM;

    /* 进行填充 */
	action->handler = handler;
	action->flags = irqflags;
	cpus_clear(action->mask);
	action->name = devname;
	action->next = NULL;
	action->dev_id = dev_id;

	select_smp_affinity(irq);
    
    /* 往数组的irqaction链表新添一个action */
	retval = setup_irq(irq, action);
	
    /* ...省略... */
}

setup_irq 函数的定义如下:

int setup_irq(unsigned int irq, struct irqaction *new)
{
	struct irq_desc *desc = irq_desc + irq;
	struct irqaction *old, **p;
	const char *old_name = NULL;
	unsigned long flags;
	int shared = 0;

	if (irq >= NR_IRQS)		/* 如果所请求注册的中断号超出系统中断数,返回错误 */
		return -EINVAL;

	if (desc->chip == &no_irq_chip) /* 如果所请求注册的中断号的硬件操作未设置 */
		return -ENOSYS;
	/* ...省略... */

	p = &desc->action;
	old = *p;
	if (old) {	
        /* 如果该中断号已经设置过action了,就需要判断是否为共享中断 
         * 同时还需要判断,是否是相同类型的中断,即
         * 是否都为共享中断,中断的触发方式是否相同,中断的优先级是否相同等,
         * 只有以上都满足了,才能够在一个已有action的中断号上新添一个新的aciton
         */
        
		/*
		 * Can't share interrupts unless both agree to and are
		 * the same type (level, edge, polarity). So both flag
		 * fields must have IRQF_SHARED set and the bits which
		 * set the trigger type must match.
		 */
		if (!((old->flags & new->flags) & IRQF_SHARED) ||
		    ((old->flags ^ new->flags) & IRQF_TRIGGER_MASK)) {
			old_name = old->name;
			goto mismatch;
		}

		/* add new interrupt at end of irq queue */
		do {
			p = &old->next;
			old = *p;
		} while (old);
		shared = 1;
	}

	/* ...省略... */
    
    /* 如果是第一次设置irqaction */
	if (!shared) {
		irq_chip_set_defaults(desc->chip);
        
		/* Setup the type (level, edge polarity) if configured: */
		if (new->flags & IRQF_TRIGGER_MASK) {
			if (desc->chip && desc->chip->set_type)
                /* 设置引脚的类型,将引脚配置为中断引脚 */
				desc->chip->set_type(irq,
						new->flags & IRQF_TRIGGER_MASK);
		} else
			compat_irq_chip_set_default_handler(desc);

        /* 设置desc的状态 */
		desc->status &= ~(IRQ_AUTODETECT | IRQ_WAITING |
				  IRQ_INPROGRESS);

		if (!(desc->status & IRQ_NOAUTOEN)) {
			desc->depth = 0;
			desc->status &= ~IRQ_DISABLED;
			if (desc->chip->startup)
				desc->chip->startup(irq);	/* 配置引脚为中断引脚 */
			else
				desc->chip->enable(irq);	/* 配置引脚为中断引脚 */
		} else
			/* Undo nested disables: */
			desc->depth = 1;
	}
}

通过分析 setup_irq 函数,可以知道其完成的任务如下:

  1. 由中断号 irq,从全局中断数组中获取中断数据结构 irq_desc;

  2. 判断 irq_desc 是否已经设置了 irqaction。

    若已经设置,则进行 irqaction 兼容性检测,即是中断方式(边沿或者电平触发)、中断优先级、是否为共享中断等;

    若没有设置,则为第一次设置 irqaction。需要对设置 irq_chip 的操作函数进行设置,对于没有被指定的操作函数,会设置为内核中已经设置好了的默认操作函数。同时需要配置硬件引脚,使其变为中断引脚;

  3. 调用 set_type 函数设置配置引脚,将引脚设置为中断引脚,同时设置中断的触发模式;

  4. 最后将 irqaction 添加到 irq_desc 的 irqaciton 链表上;

set_type 函数的定义:

s3c_irqext_type(unsigned int irq, unsigned int type)
{
	void __iomem *extint_reg;
	void __iomem *gpcon_reg;
	unsigned long gpcon_offset, extint_offset;
	unsigned long newvalue = 0, value;

	if ((irq >= IRQ_EINT0) && (irq <= IRQ_EINT3))
	{
		gpcon_reg = S3C2410_GPFCON;
		extint_reg = S3C24XX_EXTINT0;
		gpcon_offset = (irq - IRQ_EINT0) * 2;
		extint_offset = (irq - IRQ_EINT0) * 4;
	}
	else if ((irq >= IRQ_EINT4) && (irq <= IRQ_EINT7))
	{
		gpcon_reg = S3C2410_GPFCON;
		extint_reg = S3C24XX_EXTINT0;
		gpcon_offset = (irq - (EXTINT_OFF)) * 2;
		extint_offset = (irq - (EXTINT_OFF)) * 4;
	}
	else if ((irq >= IRQ_EINT8) && (irq <= IRQ_EINT15))
	{
		gpcon_reg = S3C2410_GPGCON;
		extint_reg = S3C24XX_EXTINT1;
		gpcon_offset = (irq - IRQ_EINT8) * 2;
		extint_offset = (irq - IRQ_EINT8) * 4;
	}
	else if ((irq >= IRQ_EINT16) && (irq <= IRQ_EINT23))
	{
		gpcon_reg = S3C2410_GPGCON;
		extint_reg = S3C24XX_EXTINT2;
		gpcon_offset = (irq - IRQ_EINT8) * 2;
		extint_offset = (irq - IRQ_EINT16) * 4;
	} else
		return -1;

	/* Set the GPIO to external interrupt mode */
	value = __raw_readl(gpcon_reg);
	value = (value & ~(3 << gpcon_offset)) | (0x02 << gpcon_offset);
	__raw_writel(value, gpcon_reg);

	/* Set the external interrupt to pointed trigger type */
	switch (type)
	{
		case IRQT_NOEDGE:
			printk(KERN_WARNING "No edge setting!\n");
			break;

		case IRQT_RISING:
			newvalue = S3C2410_EXTINT_RISEEDGE;
			break;

		case IRQT_FALLING:
			newvalue = S3C2410_EXTINT_FALLEDGE;
			break;

		case IRQT_BOTHEDGE:
			newvalue = S3C2410_EXTINT_BOTHEDGE;
			break;

		case IRQT_LOW:
			newvalue = S3C2410_EXTINT_LOWLEV;
			break;

		case IRQT_HIGH:
			newvalue = S3C2410_EXTINT_HILEV;
			break;

		default:
			printk(KERN_ERR "No such irq type %d", type);
			return -1;
	}

	value = __raw_readl(extint_reg);
	value = (value & ~(7 << extint_offset)) | (newvalue << extint_offset);
	__raw_writel(value, extint_reg);

	return 0;
}

卸载 irqation

当不需要中断了,可以通过 free_irq 函数卸载掉注册在 irq_desc 中的 irqaction。该函数的定义如下:

void free_irq(unsigned int irq, void *dev_id)
{
	/* ...省略... */
	desc = irq_desc + irq;
	
	p = &desc->action;
	for (;;) {
		struct irqaction *action = *p;

		if (action) {
			struct irqaction **pp = p;

			p = &action->next;
            /* 如果dev_id相同就说明找到了 */
			if (action->dev_id != dev_id)
				continue;

			/* Found it - now remove it from the list of entries */
			*pp = action->next;

			/* Currently used only by UML, might disappear one day.*/

			if (!desc->action) {	/* 如果链表已经空了 */
				desc->status |= IRQ_DISABLED;	
				if (desc->chip->shutdown)
					desc->chip->shutdown(irq);	/* 关闭硬件引脚的中断 */
				else
					desc->chip->disable(irq);	/* 关闭硬件引脚的中断 */
			}

			unregister_handler_proc(irq, action);	/* 从irq_desc移除irqaction */

			if (action->flags & IRQF_SHARED)
				handler = action->handler;
			kfree(action);	/* 释放irqaction */
			return;
		}
		/* ...省略... */
	}
	/* ...省略... */
}

从函数的接口可以知道,这个需要两个参数一个的中断号,另一个是 dev_id,

3.3 中断驱动程序

  1. open 函数:注册 irqaction 的 irq_desc 中

    定义 pins_desc 数据结构,该数据结构内定义了按键对应的值:

    struct pin_desc 
    {
    	unsigned int  pin;
    	unsigned char key_val;
    };
    
    /* 按键按下的值: 0x01 0x02 0x03 0x04
     * 按键松开的值: 0x81 0x82 0x83 0x84
     */
    static struct pin_desc pins_desc[4] = {
    	{S3C2410_GPF0,  0x01},
    	{S3C2410_GPF2,  0x02},
    	{S3C2410_GPG3,  0x03},
    	{S3C2410_GPG11, 0x04},
    };
    

    open 函数:

    static ssize_t s3c2440_buttons_irq_open(struct inode *inode, struct file *file)
    {
    	/* 配置按键:GPF0\2和GPG3的引脚为中断引脚
    	 * 注册irqaction处理函数 
    	 */
    	request_irq(IRQ_EINT0,  s3c2440_buttons_irq_handler, 
                    IRQT_BOTHEDGE, "s2", &pins_desc[0]);
    	request_irq(IRQ_EINT2,  s3c2440_buttons_irq_handler, 
                    IRQT_BOTHEDGE, "s3", &pins_desc[1]);
    	request_irq(IRQ_EINT11, s3c2440_buttons_irq_handler, 
                    IRQT_BOTHEDGE, "s4", &pins_desc[2]);
    	request_irq(IRQ_EINT19, s3c2440_buttons_irq_handler, 
                    IRQT_BOTHEDGE, "s5", &pins_desc[3]);
    	
    	return 0;
    }
    

    s3c2440_buttons_irq_handler 函数:

    static irqreturn_t s3c2440_buttons_irq_handler(int irq, void *dev_id)
    {
    	struct pin_desc *pindesc = (struct pin_desc *)dev_id;
    	unsigned char pinval;
    	pinval = s3c2410_gpio_getpin(pindesc->pin);	/* 读出引脚值 */
    	if (pinval)
    	{
    		/* 按键松开 */
    		key_val = 0x80 | pindesc->key_val;
    	}
    	else
    	{	
    		/* 按键按下 */
    		key_val = pindesc->key_val;
    	}
    	ev_press = 1;
    	wake_up_interruptible(&button_waitq);
    	return IRQ_HANDLED;
    }
    

    s3c2410_gpio_getpin 函数:内核中实现的一个用于读取 s3c24xx 芯片引脚的一个函数,简化了用户对引脚进行的操作;

    该函数将引脚的状态读入,存储在全局变量 key_val 中。如果 s2 按键被按下,key_val 的值就为0x01,如果 s2 被松开,key_val 的值就为0x81。s3、s4、s5 不赘述。

    wake_up_interruptible(&button_waitq); 会在后面进行说明

  2. release 函数:卸载 irqaction

    static ssize_t s3c2440_buttons_irq_close(struct inode *inode, struct file *file)
    {
    	free_irq(IRQ_EINT0,  &pins_desc[0]);
    	free_irq(IRQ_EINT2,  &pins_desc[1]);
    	free_irq(IRQ_EINT11, &pins_desc[2]);
    	free_irq(IRQ_EINT19, &pins_desc[3]);
    	return 0;
    }
    
  3. read 函数:负责读取引脚的状态值,然后将状态返回给应用程序

    当中断触发时,会先调用 irq_desc 中的 handle 函数,也就是 s3c2440_buttons_irq_handler 将值存储在 key_val;

    而当应用程序调用 read 函数时,如果此时没有中断产生,应用程序应该进入阻塞状态,否则就会使得 while 循环不断调用 read 去获取按键值(查询方式的驱动程序)。

    为了解决这个消耗 CPU 资源的行为,我们需要使 read 函数会阻塞。

    声明一个等待队列 button_waitq:

    static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
    /* 中断事件标志,中断服务程序将它设置为1,read函数内将它清0 */
    static unsigned long ev_press = 0;
    

    Linux内核等待队列详细说明:Linux 内核等待队列

    read 函数:

    static ssize_t s3c2440_buttons_irq_read(struct file *file, char __user *buf, 
                                            size_t count, loff_t *ppos)
    {
    	unsigned long ret;
    	if (count < sizeof(key_val))
    		return -EINVAL;
    
    	/* 如果没有按键按下,就休眠 */
    	wait_event_interruptible(button_waitq, ev_press);
    
    	/* 如果有按键被按下,从休眠状态唤醒 */
    	ret = copy_to_user(buf, &key_val, sizeof(key_val));
    	ev_press = 0;
    	return ret;
    }
    

    wait_event_interruptible(button_waitq, ev_press); 判断条件 ev_press 是否成立(为true成立,false不成立),若不成立,使当前程序进程被挂载到阻塞队列 button_waitq 上。

    当有按键按下时,由 s3c2440_buttons_irq_handler 负责唤醒 button_waitq 阻塞队列上的进程。

    static irqreturn_t s3c2440_buttons_irq_handler(int irq, void *dev_id)
    {
    	/* ...省略... */
    	ev_press = 1;
    	wake_up_interruptible(&button_waitq);	/* 唤醒进程 */
    	return IRQ_HANDLED;
    }
    

3.4 测试程序

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>

int main(int argc, char **argv)
{
	unsigned long key_val = 0;
	int fd;
	fd = open("/dev/buttons_irq", O_RDWR);
	if (!fd)
	{
		printf("open /dev/buttons failed!\n");
		return 0;
	}

	while (1) {
		read(fd, &key_val, sizeof(key_val));
		printf("key_val = 0x%x\n", key_val);
	}
	return 0;
}

4. poll机制的驱动程序

4.1 poll 机制分析

Linux内核poll机制分析

4.2 驱动程序

在中断驱动程序的基础上进行修改:

  1. 添加 file operation 中的 poll 机制:

    static unsigned s3c2440_buttons_poll(struct file *file, poll_table *wait)
    {
    	unsigned int mask = 0;
    	poll_wait(file, &button_waitq, wait);
    
    	if (ev_press)
    		mask |= POLLIN | POLLRDBAND;
    	return mask;
    }
    

4.3 应用程序

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>


/* forthdrvtest 
  */
int main(int argc, char **argv)
{
	int fd;
	unsigned char key_val;
	int ret;

	struct pollfd fds[1];
	
	fd = open("/dev/buttons_poll", O_RDWR);
	if (fd < 0)
	{
		printf("can't open!\n");
		return 0;
	}

	fds[0].fd     = fd;
	fds[0].events = POLLIN;
	while (1)
	{
		ret = poll(fds, 1, 5000);
		if (ret == 0)
		{
			printf("time out\n");
		}
		else
		{
			read(fd, &key_val, 1);
			printf("key_val = 0x%x\n", key_val);
		}
	}
	
	return 0;
}

5. 异步通知

使用信号的方式,通知应用程序,当前的驱动可操作。应用程序再调用 read/write 等操作函数去读或者写应用程序。

5.1 驱动程序

为了使设备支持异步通知机制,驱动程序中涉及以下3项工作:

  1. 支持 F_SETOWN 命令,能在这个控制命令处理中设置 filp->f_owner 为对应进程 ID。

    不过此项工作已由内核来完成,设备驱动无需处理。

  2. 支持 F_SETFL 命令的处理,每当 FASYNC 标志改变时,驱动程序中的 fasync() 函数将得以执行。

    驱动中应该实现 fasync() 函数。

  3. 在设备资源可获得时,调用 kill_fasync() 函数激发相应的信号。

在 poll 驱动程序的基础上,修改驱动程序:

  1. 定义全局变量 s3c2440_buttons_async_queue

    #define DEVICE_NAME		"s3c2440_buttons_poll"
    
    static unsigned int major;
    static struct class *s3c2440_buttons_class;
    static struct class_device *s3c2440_buttons_dev;
    
    static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
    static struct fasync_struct *s3c2440_buttons_async_queue; /* 新添加定义 */
    
  2. 添加 fasync 函数:

    int s3c2440_buttons_fasync(int fd, struct file * file, int on)
    {		
    	return fasync_helper(fd, file, on, s3c2440_buttons_async_queue);
    }
    
    static struct file_operations s3c2440_buttons_fops = {
    	.owner	 = THIS_MODULE,
    	.open 	 = s3c2440_buttons_open,
    	.read	 = s3c2440_buttons_read,
    	.write	 = s3c2440_buttons_write,
    	.release = s3c2440_buttons_close,
    	.poll	 = s3c2440_buttons_poll,
    	.fasync  = s3c2440_buttons_fasync,	/* 新添加 */
    };
    
  3. 在中断处理函数(s3c2440_buttons_handler )内添加发送信号的代码

    static irqreturn_t s3c2440_buttons_handler(int irq, void *dev_id)
    {
    	struct pin_desc *pindesc = (struct pin_desc *)dev_id;
    	unsigned char pinval;
    	pinval = s3c2410_gpio_getpin(pindesc->pin);	/* 读出引脚值 */
    	if (pinval)
    	{
    		key_val = 0x80 | pindesc->key_val;	/* 按键松开 */
    	}
    	else
    	{	
    		key_val = pindesc->key_val;			/* 按键按下 */
    	}
    	ev_press = 1;
    	wake_up_interruptible(&button_waitq);
        /* 发送信号 */
    	kill_fasync(s3c2440_buttons_async_queue, SIGIO, POLL_IN);
    	return IRQ_HANDLED;
    }
    

5.2 中断机制

在驱动程序的 s3c2440_buttons_handler 中断处理函数内,我们通过 kill_fasync 来向应用程序发送信号,那么应用程序是如何获取到信号的呢?

fasync_struct:我们新定义了一个 s3c2440_buttons_async_queue 类型的队列。

struct fasync_struct {
	int	magic;
	int	fa_fd;
	struct	fasync_struct	*fa_next; /* singly linked list */
	struct	file 			*fa_file;
};

s3c2440_buttons_fasync:在我们的驱动程序中,该函数的作用是初始化 s3c2440_buttons_async_queue 这个数据结构,通过调用 fasync_helper(fd, file, on, s3c2440_buttons_async_queue) 来进行初始化,fasync_helper 的源码如下:

int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
{
	struct fasync_struct *fa, **fp;
	struct fasync_struct *new = NULL;
	int result = 0;

	if (on) {
		new = kmem_cache_alloc(fasync_cache, GFP_KERNEL);
		if (!new)
			return -ENOMEM;
	}
	write_lock_irq(&fasync_lock);
    
    /* 释放原有的s3c2440_buttons_async_queue中的内容 */
	for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next) {
		if (fa->fa_file == filp) {
			if(on) {
				fa->fa_fd = fd;
				kmem_cache_free(fasync_cache, new);
			} else {
				*fp = fa->fa_next;
				kmem_cache_free(fasync_cache, fa);
				result = 1;
			}
			goto out;
		}
	}

    /* 重新设置s3c2440_buttons_async_queue
     * 或者初始化s3c2440_buttons_async_queue */
	if (on) {
		new->magic = FASYNC_MAGIC;
		new->fa_file = filp;
		new->fa_fd = fd;
		new->fa_next = *fapp;
		*fapp = new;
		result = 1;
	}
out:
	write_unlock_irq(&fasync_lock);
	return result;
}

FASYNC_MAGIC:用于标识该对象特定用于 fasync 时使用。可见这个数据结构是内核中一个通用的数据结构,通过这个 magic 进行唯一标识其具体的用途;

向应用程序发送信号时调用 kill_fasync(s3c2440_buttons_async_queue, SIGIO, POLL_IN) 这个函数进行发送,kill_fasync 调用 __kill_fasync 进行操作。__kill_fasync 的源码如下:

void __kill_fasync(struct fasync_struct *fa, int sig, int band)
{
	while (fa) {
		struct fown_struct * fown;
		if (fa->magic != FASYNC_MAGIC) {
			printk(KERN_ERR "kill_fasync: bad magic number in "
			       "fasync_struct!\n");
			return;
		}
		fown = &fa->fa_file->f_owner;
		/* Don't send SIGURG to processes which have not set a
		   queued signum: SIGURG has its own default signalling
		   mechanism. */
		if (!(sig == SIGURG && fown->signum == 0))
			send_sigio(fown, fa->fa_fd, band);
		fa = fa->fa_next;
	}
}
  • fown = &fa->fa_file->f_owner 获取了当前驱动的所有者。其定义如下:

    struct fown_struct {
    	rwlock_t lock;          /* protects pid, uid, euid fields */
    	struct pid *pid;	/* pid or -pgrp where SIGIO should be sent */
    	enum pid_type pid_type;	/* Kind of process group SIGIO should be sent to */
    	uid_t uid, euid;	/* uid/euid of process setting the owner */
    	int signum;		/* posix.1b rt signal to be delivered on IO */
    };
    
  • 接着调用 send_sigio(fown, fa->fa_fd, band) 发送信号;

send_sigio 的定义如下:

void send_sigio(struct fown_struct *fown, int fd, int band)
{
	struct task_struct *p;
	enum pid_type type;
	struct pid *pid;
	
	read_lock(&fown->lock);
	type = fown->pid_type;	/*  */
	pid = fown->pid;		/* 获取应用程序的进程ID */
	if (!pid)
		goto out_unlock_fown;
	
	read_lock(&tasklist_lock);
	do_each_pid_task(pid, type, p) {	/* 对驱动程序的应用程序“们”, 都发送信号 */
		send_sigio_to_task(p, fown, fd, band);
	} while_each_pid_task(pid, type, p);
	read_unlock(&tasklist_lock);
 out_unlock_fown:
	read_unlock(&fown->lock);
}
  • do_each_pid_task:会对 fown 文件所有者们,都调用 send_sigio_to_task 发送信号给这些所有者。
  • 这些所有者接收到信号后就会触发信号的 handler 函数被执行。

那么 fown 是如何设置进驱动程序的呢?

  1. 由应用程序通过 fcntl(fd, F_SETOWN, getpid()) 的方式,将当前进程注册进驱动程序的所有者(f_owner)项上;
  2. 驱动程序通过 file 结构体的 f_owner 获取到所有者;

fcntl 会调用 sys_fcntl 函数,其定义如下:

asmlinkage long sys_fcntl(unsigned int fd, unsigned int cmd, unsigned long arg)
{	
	struct file *filp;
	/* ...省略... */
	filp = fget(fd);
	/* ...省略... */
	err = do_fcntl(fd, cmd, arg, filp);
	/* ...省略... */	
}

核心功能在 do_fcntl 函数中完成。do_fcntl 定义如下:(简略版本)

static long do_fcntl(int fd, unsigned int cmd, unsigned long arg,
		struct file *filp)
{
	/* ...省略... */
	case F_SETOWN:
		err = f_setown(filp, arg, 1);
		break;
	/* ...省略... */
}

通过调用 f_setown 函数将进程设置进去,此时的 arg 为应用程序 getpid() 时获取到的进程 ID 号。

int f_setown(struct file *filp, unsigned long arg, int force)
{
	enum pid_type type;
	struct pid *pid;
	/* ...省略... */
	pid = find_pid(who);
	result = __f_setown(filp, pid, type, force);
	/* ...省略... */
}
  • find_pid(who):通过进程的进程号,获取该进程的一个 pid 数据结构。struct pid 的定义如下:

    struct pid
    {
    	atomic_t count;
    	/* Try to keep pid_chain in the same cacheline as nr for find_pid */
    	int nr;
    	struct hlist_node pid_chain;
    	/* lists of tasks that use this pid */
    	struct hlist_head tasks[PIDTYPE_MAX];
    	struct rcu_head rcu;
    };
    
  • __f_setown(filp, pid, type, force):设置文件(驱动程序)的所有者。

    int __f_setown(struct file *filp, struct pid *pid, 
                   enum pid_type type, int force)
    {
        /* ...省略... */
    	f_modown(filp, pid, type, current->uid, current->euid, force);
    	return 0;
    }
    

f_modown 函数的定义如下:由于我们的 force 在 f_setown(filp, arg, 1) 时指定为了1,因此会强制设置驱动设备的所有者为发出 fcntl 的应用程序。

static void f_modown(struct file *filp, struct pid *pid, enum pid_type type,
                     uid_t uid, uid_t euid, int force)
{
	write_lock_irq(&filp->f_owner.lock);
	if (force || !filp->f_owner.pid) {
		put_pid(filp->f_owner.pid);
		filp->f_owner.pid = get_pid(pid);
		filp->f_owner.pid_type = type;
		filp->f_owner.uid = uid;
		filp->f_owner.euid = euid;
	}
	write_unlock_irq(&filp->f_owner.lock);
}

这样就将应用程序设置为了驱动设备的所有者。

然后应用层再通过 fcntl(fd, F_SETFL, oflags | FASYNC) 调用到应用程序的 s3c2440_buttons_fasync 函数。

int s3c2440_buttons_fasync(int fd, struct file * file, int on)
{		
	return fasync_helper(fd, file, on, s3c2440_buttons_async_queue);
}

再在 fasync_helper 函数内获取到这个 owner 设置进 s3c2440_buttons_async_queue 这个结构体中,从而在后续调用 kill_fasync 命令时通过 s3c2440_buttons_async_queue 这个结构体向应用程序发送信号。

6. 完整的驱动程序

按键驱动程序完整程序

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值