一、字符设备驱动3-poll机制按键驱动

按键驱动:poll机制

所有的系统调用,基于都可以在它的名字前加上sys_”前缀,这就是它在内核中对应的函数。比如系统调用openreadwritepoll,与之对应的内核函数为:sys_opensys_readsys_writesys_poll

内核框架

对于系统调用pollselect,它们对应的内核函数都是sys_poll。分析sys_poll,即可理解poll机制。

sys_poll():

Linux 2.6内核中,位于fs/select.c。在linux 3.10中,没有找到,只找到声明,在/include/linux/syscalls.h中。

这个函数只对传入的参数稍作处理,然后调用do_sys_poll();

do_sys_poll():位于/fs/select.c

int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, s64 *timeout)
{  
    struct poll_wqueues table;  
    ...  
    poll_initwait(&table);  
    fdcount = do_poll(nfds, head, &table, end_time);  
    ...  
}  

poll_initwait(&table)结果:table->pt->qproc = __pollwait,即注册了一个回调函数。

__pollwait将在驱动的poll函数里用到。

问:怎么用?

答:驱动里的poll需要调用poll_wait,这个函数最终会调用到__pollwait

问:为什么?

答:查阅do_poll()函数源码,函数退出的条件是do_pollfd(pfd, pt)非零值,或超时。若这2个条件都不满足,那么进程就会休眠,直到超时唤醒。若进程休眠后,等待的事件到来怎么办?显然,还得有另外的办法来唤醒进程,那就是驱动程序来唤醒。故驱动调用poll_wait的意义就是:让驱动程序知道该唤醒谁。这也是为什么poll_wait()不会休眠的原因,它只是用来把当前进程挂到某一队列,以便驱动程序知道唤醒谁。

真正的休眠发生在:应用程序调用poll > sys_poll > do_sys_poll > poll_initwait,do_poll > do_pollfd > 我们自己写的poll函数后,再调用schedule_timeout进入休眠。

do_poll():

static int do_poll(unsigned int nfds,  struct poll_list *list,  
           struct poll_wqueues *wait, struct timespec *end_time)  
{  
    ...  
    for (;;) {  
        ...  
            for (; pfd != pfd_end; pfd++) {  
                /* 
                 * Fish for events. If we found one, record it 
                 * and kill poll_table->_qproc, so we don't 
                 * needlessly register any other waiters after 
                 * this. They'll get immediately deregistered 
                 * when we break out and return. 
                 */  
                if (do_pollfd(pfd, pt)) {  
                    count++;  
                    pt->_qproc = NULL;  
                }  
            }  
          
        ...  
        if (count || timed_out) // 若条件满足,在这里跳出循环  
            break;  
  
        ...  
  
  // 进程休眠,一切正常的话将会超时唤醒。  
  // 若期间期望的事件到来,将由驱动程序唤醒
 if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack)) 
            timed_out = 1;  
    }  
    return count;  
}

do_pollfd():

static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait)  
{  
    ...  
            if (f.file->f_op && f.file->f_op->poll) {  
                pwait->_key = pollfd->events|POLLERR|POLLHUP;  
                // 此处调用驱动里注册的poll  
                mask = f.file->f_op->poll(f.file, pwait);  
            }  
    ...  
  
    return mask;  
}

可见,此处将会调用驱动里注册的poll函数。

问:由此引发驱动的poll函数怎么写?

答:根据已知的结果推断:

A. 把进程挂到某一队列,让驱动知道事件就绪时,唤醒谁。

B. 看一下此刻事件有没有就绪,若就绪则返回非零值,否则返回0。此处就对应着上述do_poll()里的15行,这种情况下,进程没机会休眠就直接获得结果了。

现在来总结一下poll机制:

1. poll > sys_poll > do_sys_poll > poll_initwait,poll_initwait函数注册一下回调函数__pollwait,它就是我们的驱动程序执行poll_wait时,真正被调用的函数。

2. 接下来执行file->f_op->poll,即我们驱动程序里自己实现的poll函数

它会调用poll_wait把自己挂入某个队列,这个队列也是我们的驱动自己定义的;

它还判断一下设备是否就绪。

3. 如果设备未就绪,do_sys_poll里会让进程休眠一定时间

4. 进程被唤醒的条件有2:一是上面说的“一定时间”到了,二是被驱动程序唤醒。驱动程序发现条件就绪时,就把“某个队列”上挂着的进程唤醒,这个队列,就是前面通过poll_wait把本进程挂过去的队列。

5. 如果驱动程序没有去唤醒进程,直到应用程序的poll调用传入的时间到达也会被唤醒。

驱动:

/*
 * 引脚:PI0,1,2
 */

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/leds.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/module.h>
#include <linux/pinctrl/consumer.h>
#include <linux/err.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/uaccess.h>
#include <linux/interrupt.h>
#include <asm/io.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>


struct pin_desc{
	int pin;
	int val;
};

/*
 * 按下时,返回:0x81, 0x82, 0x83
 * 
 */

static struct pin_desc pins_desc[3] = {
	{NUC970_PI0, 0x1},
	{NUC970_PI1, 0x2},
	{NUC970_PI2, 0x3},
};

static unsigned char val;
static DECLARE_WAIT_QUEUE_HEAD(buttons_waitq);
int evpress = 0;

static irqreturn_t buttons_handler(int irq, void *dev_id)
{
	struct pin_desc *pin = (struct pin_desc *)dev_id;
	//int res;

	//res = gpio_get_value(pin->pin);
	
	val = 0x80 | pin->val;

	evpress = 1;
	wake_up_interruptible(&buttons_waitq);
	
	return IRQ_HANDLED;
}

static int buttons_open(struct inode *inode, struct file *filp)
{
	// request_irq会自动设置引脚,此处不再配置

	request_irq(gpio_to_irq(pins_desc[0].pin), buttons_handler, IRQF_TRIGGER_FALLING, "S1", &pins_desc[0]);
	request_irq(gpio_to_irq(pins_desc[1].pin), buttons_handler, IRQF_TRIGGER_FALLING, "S2", &pins_desc[1]);
	request_irq(gpio_to_irq(pins_desc[2].pin), buttons_handler, IRQF_TRIGGER_FALLING, "S3", &pins_desc[2]);
	
	return 0;
}

static ssize_t buttons_read (struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
	if (count != 1)
	{
		return -EINVAL;
	}

	wait_event_interruptible(buttons_waitq, evpress);
	evpress = 0;

	copy_to_user(buf, &val, 1);
	
	return 1;
}

int buttons_release (struct inode *inode, struct file *filp)
{
	free_irq(gpio_to_irq(pins_desc[0].pin), &pins_desc[0]);
	free_irq(gpio_to_irq(pins_desc[1].pin), &pins_desc[1]);
	free_irq(gpio_to_irq(pins_desc[2].pin), &pins_desc[2]);
	
	return 0;
}

static unsigned int buttons_poll(struct file *file, poll_table *wait)
{
	unsigned int mask = 0;

	poll_wait(file, &buttons_waitq, wait); // 不会立即休眠,只是把进程挂到buttons_waitq队列

	if (evpress)
	{
		mask = POLLIN | POLLRDNORM;
	}

	return mask;
}


static struct file_operations buttons_fops = {
	.owner   = THIS_MODULE,
	.open    = buttons_open,
	.read    = buttons_read,
	.release = buttons_release,
	.poll    = buttons_poll,
};

static int major;
static struct class *buttons_class;
static struct device *button_device;

static int buttons_init(void)
{
	major = register_chrdev(0, "buttons", &buttons_fops);
	
	buttons_class = class_create(THIS_MODULE, "buttons");
	button_device = device_create(buttons_class, NULL, MKDEV(major, 0), NULL, "buttons");

	return 0;
}

static void buttons_exit(void)
{
	device_destroy(buttons_class, MKDEV(major, 0));
	class_destroy(buttons_class);

	unregister_chrdev(major, "buttons");
}

module_init(buttons_init);
module_exit(buttons_exit);
MODULE_LICENSE("GPL");

测试程序:

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

int main(void)
{
	int fd;
	unsigned char key_val;
	struct pollfd fds[1];
	int res;

	fd = open("/dev/buttons", O_RDONLY);
	if (fd < 0)
	{
		printf("Can't open /dev/buttons\n");
		return -1;
	}

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


JFM7VX690T型SRAM型现场可编程门阵列技术手册主要介绍的是上海复旦微电子集团股份有限公司(简称复旦微电子)生产的高性能FPGA产品JFM7VX690T。该产品属于JFM7系列,具有现场可编程特性,集成了功能强大且可以灵活配置组合的可编程资源,适用于实现多种功能,如输入输出接口、通用数字逻辑、存储器、数字信号处理和时钟管理等。JFM7VX690T型FPGA适用于复杂、高速的数字逻辑电路,广泛应用于通讯、信息处理、工业控制、数据中心、仪表测量、医疗仪器、人工智能、自动驾驶等领域。 产品特点包括: 1. 可配置逻辑资源(CLB),使用LUT6结构。 2. 包含CLB模块,可用于实现常规数字逻辑和分布式RAM。 3. 含有I/O、BlockRAM、DSP、MMCM、GTH等可编程模块。 4. 提供不同的封装规格和工作温度范围的产品,便于满足不同的使用环境。 JFM7VX690T产品系列中,有多种型号可供选择。例如: - JFM7VX690T80采用FCBGA1927封装,尺寸为45x45mm,使用锡银焊球,工作温度范围为-40°C到+100°C。 - JFM7VX690T80-AS同样采用FCBGA1927封装,但工作温度范围更广,为-55°C到+125°C,同样使用锡银焊球。 - JFM7VX690T80-N采用FCBGA1927封装和铅锡焊球,工作温度范围与JFM7VX690T80-AS相同。 - JFM7VX690T36的封装规格为FCBGA1761,尺寸为42.5x42.5mm,使用锡银焊球,工作温度范围为-40°C到+100°C。 - JFM7VX690T36-AS使用锡银焊球,工作温度范围为-55°C到+125°C。 - JFM7VX690T36-N使用铅锡焊球,工作温度范围与JFM7VX690T36-AS相同。 技术手册中还包含了系列详细的技术参数,包括极限参数、推荐工作条件、电特性参数、ESD等级、MSL等级、重量等。在产品参数章节中,还特别强调了封装类型,包括外形图和尺寸、引出端定义等。引出端定义是指对FPGA芯片上的各个引脚的功能和接线规则进行说明,这对于FPGA的正确应用和电路设计至关重要。 应用指南章节涉及了FPGA在不同应用场景下的推荐使用方法。其中差异说明部分可能涉及产品之间的性能差异;关键性能对比可能包括功耗与速度对比、上电浪涌电流测试情况说明、GTH Channel Loss性能差异说明、GTH电源性能差异说明等。此外,手册可能还提供了其他推荐应用方案,例如不使用的BANK接法推荐、CCLK信号PCB布线推荐、JTAG级联PCB布线推荐、系统工作的复位方案推荐等,这些内容对于提高系统性能和稳定性有着重要作用。 焊接及注意事项章节则针对产品的焊接过程提供了指导,强调焊接过程中的注意事项,以确保产品在组装过程中的稳定性和可靠性。手册还明确指出,未经复旦微电子的许可,不得翻印或者复制全部或部分本资料的内容,且不承担采购方选择与使用本文描述的产品和服务的责任。 上海复旦微电子集团股份有限公司拥有相关的商标和知识产权。该公司在中国发布的技术手册,版权为上海复旦微电子集团股份有限公司所有,未经许可不得进行复制或传播。 技术手册提供了上海复旦微电子集团股份有限公司销售及服务网点的信息,方便用户在需要时能够联系到相应的服务机构,获取最新信息和必要的支持。同时,用户可以访问复旦微电子的官方网站(***以获取更多产品信息和公司动态。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值