WDS1期第12课 字符设备驱动 5 key 中断 exec 在irq中读取引脚 休眠 DECLARE_WAIT_QUEUE_HEAD wake_up_ wait_event_interrupti


单片机开发

  1. 有按键按下
  2. cpu中断,强制跳转到中断入口执行
  3. 在中断入口函数中
    3.1 保存现场(各种寄存器的值)
    3.2 执行中断处理函数
    3.3 恢复现场

他人优秀总结 字符设备驱动三 中断框架
中断描述符表(Interrupt Descriptor Table,IDT)大佬的链接
整个流程:
在这里插入图片描述
在这里插入图片描述

Linux
设置异常入口,linux异常向量的基地址为0xFFFF0000(8字节),这个是虚拟地址,建立与物理地址的映射之后需要把异常向量表复制到0xFFFF0000对应的物理地址,
arch/arm/kernel/traps.c,

void __init trap_init(void)
{
	/*
	 * Copy the vectors, stubs and kuser helpers (in entry-armv.S)
	 * into the vector page, mapped at 0xffff0000, and ensure these
	 * are visible to the instruction stream.
	 */
	memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
	memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
	memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);
}

其中,
vecotrs的值在include/linux/autoconf.h中定义#define CONFIG_VECTORS_BASE 0xffff0000
__vectors_start在arch/arm/kernel/entry-armv.S中表示如下,就是汇编指令跳转到某个地址,

	.globl	__vectors_start
__vectors_start:
	swi	SYS_ERROR0
	b	vector_und + stubs_offset
	ldr	pc, .LCvswi + stubs_offset
	b	vector_pabt + stubs_offset
	b	vector_dabt + stubs_offset
	b	vector_addrexcptn + stubs_offset
	b	vector_irq + stubs_offset
	b	vector_fiq + stubs_offset

vector_und也在entry-armv.S中,

	vector_stub	und, UND_MODE
	...
	.macro	vector_stub, name, mode, correction=0
	.align	5

vector_\name:
	.if \correction
	sub	lr, lr, #\correction
	.endif

	@
	@ Save r0, lr_<exception> (parent PC) and spsr_<exception>
	@ (parent CPSR)
	@
	stmia	sp, {r0, lr}		@ save r0, lr
	mrs	lr, spsr
	str	lr, [sp, #8]		@ save spsr

	@
	@ Prepare for SVC32 mode.  IRQs remain disabled.
	@
	mrs	r0, cpsr
	eor	r0, r0, #(\mode ^ SVC_MODE)
	msr	spsr_cxsf, r0

展开,是一个宏定义的,

vector_und:
	@
	@ Save r0, lr_<exception> (parent PC) and spsr_<exception>
	@ (parent CPSR)
	@
	stmia	sp, {r0, lr}		@ save r0, lr
	mrs	lr, spsr
	str	lr, [sp, #8]		@ save spsr

	@
	@ Prepare for SVC32 mode.  IRQs remain disabled.
	@
	mrs	r0, cpsr
	eor	r0, r0, #(\mode ^ SVC_MODE)
	msr	spsr_cxsf, r0

trap_init拷贝异常向量的表到0xffff0000,
跳转到具体的某个向量时,计算返回地址—保存寄存器值—调用处理函数(asm_do_IRQ[C函数])—恢复
。流程如下图,完全开发手册第415页
在这里插入图片描述

解析asm_do_IRQ

在单片机开发中,发生中断后,cpu干什么

  1. 判断是哪个中断
  2. 执行对应中断函数
  3. 清中断

在linux内核中这三步都在asm_do_IRQ完成,它在arch/arm26/kernel/irq.c中,

asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
	struct pt_regs *old_regs = set_irq_regs(regs);
	struct irq_desc *desc = irq_desc + irq;

	/*
	 * Some hardware gives randomly wrong interrupts.  Rather
	 * than crashing, do something sensible.
	 */
	if (irq >= NR_IRQS)
		desc = &bad_irq_desc;

	irq_enter();

	desc_handle_irq(irq, desc);

	/* AT91 specific workaround */
	irq_finish(irq);

	irq_exit();
	set_irq_regs(old_regs);
}
struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {

irq_desc在kernel/irq/handle.c中,irq_desc是中断描述数组,以中断号为下标。asm_do_IRQ首先根据中断号在irq_desc取到一项,然后执行desc_handle_irq里的desc->handle_irq(irq, desc);,
在kernel/irq/chip.c中__set_irq_handler函数中对其赋值desc->handle_irq = handle;
在arch/arm/plat-s3c24xx/irq.c中的void __init s3c24xx_init_irq(void)

desc就是那个全局数组的某一项,那三步都在asm_do_IRQ中的desc->handle(irq, desc, regs);实现
在这里插入图片描述

听不懂照搬代码吧,跟之前比做了一点点修改

1. 简单irq函数 request_irq free_irq 用exec打开设备节点

只改了驱动,没有该user,这小部分没有用到user

// 发生中断之后会调用这个函数
static irqreturn_t keys_irq(int irq, void *dev_id)
{
    printk("irq = %d\n", irq);
    printk("--%s--%s--%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
// GPF0/2 GPG3/11 配置成输入模式
static int keys_open (struct inode *inode, struct file *filep)
{
    // GPFCON &= ~((0x3<<0*2) | (0x3<<2*2));  // 清零
    // GPGCON &= ~((0x3<<3*2) | (0x3<<11*2)); // 清零

    /*
    *	@irq: Interrupt line to allocate
    *	@handler: Function to be called when the IRQ occurs
    *	@irqflags: Interrupt type flags
    *	@devname: An ascii name for the claiming device
    *	@dev_id: A cookie passed back to the handler function
    */
	// irq:中断号
	// handler:处理函数
	// irqflags:上升沿触发,下降沿触发,边沿触发等。指定了快速中断或中断共享等中断处理属性.
	// *devname:中断名字。通常是设备驱动程序的名称。改值用在 /proc/interrupt 系统 (虚拟)
	// 			文件上,或内核发生中断错误时使用。
	// dev_id 可作为共享中断时的中断区别参数,也可以用来指定中断服务函数需要参考的数据地址。也用于卸载action    
	
    // 中断引脚,自动设备为中断引脚,
    //  IRQ_EINT0在include/asm-arm/arch-s3c2410/irqs.h中定义
    //  keys_irq
    //  IRQT_BOTHEDGE双边沿触发
    //  "EINT0_S2"
    request_irq(IRQ_EINT0,  keys_irq, IRQT_BOTHEDGE, "EINT0_S2", 1);
    request_irq(IRQ_EINT2,  keys_irq, IRQT_BOTHEDGE, "EINT0_S3", 1);
    request_irq(IRQ_EINT11, keys_irq, IRQT_BOTHEDGE, "EINT0_S4", 1);
    request_irq(IRQ_EINT19, keys_irq, IRQT_BOTHEDGE, "EINT0_S5", 1);

    return 0;
}

// 释放中断的配置
// 关闭中断,也就是取消中断处理函数,在全局数组irq_desc寻找到对应的action链表,
//   通过id将其从链表中删除.如果链表空了,则禁止中断
int key_close(struct inode *inode, struct file *filp)
{
    free_irq(IRQ_EINT0, 1);
    free_irq(IRQ_EINT2, 1);
    free_irq(IRQ_EINT11,1);
    free_irq(IRQ_EINT19,1);
    return 0;
}
static struct file_operations fops = {
    .owner   = THIS_MODULE,
    .open    = keys_open,
    .read    = keys_read,
    .release = key_close,
};

2. 插入/exec打开关闭设备节点

查看设备的主设备号和驱动名称,
在这里插入图片描述
查看设备节点信息,
在这里插入图片描述
打开设备,这里不是在用户代码中调用open,用命令 exec 5<dev/keys_intnode,打开设备节点,按下按键,弹起,双边沿触发,不同按键对应不同的中断号,比如EINT0在代码中就是16,

#define S3C2410_CPUIRQ_OFFSET	 (16)
#define S3C2410_IRQ(x) ((x) + S3C2410_CPUIRQ_OFFSET)
/* main cpu interrupts */
#define IRQ_EINT0      S3C2410_IRQ(0)	    /* 16 */

在这里插入图片描述
执行cat proc/interrupts查看中IRQ名称断设备节点,可以看到EINT0_S2等 中断次数等
在这里插入图片描述
当前的进程是shell,查看shell的id ps / top, pid为769,ls -l proc/769/fd 可以看到那个5就指向了keys_intnode设备节点,
在这里插入图片描述
在这里插入图片描述
关闭用exec打开的那个设备exec 5<&- 然后再查看ls proc/769/fd -l,用cat proc/interrupts查看那四个IRQ的名称中断设备节点也没有了,再调用free_irq释放了中断。
在这里插入图片描述
在这里插入图片描述

在open中加入休眠等待

read之后进入内核态的驱动程序里,如果没有按键中断,休眠;
有按键中断,进入ISR得到按键值,在read中继续执行,将按键值传递到用户空间

休眠函数如下,condition=0才休眠,定义在include/linux/wait.h,condition参数这里是ev_press:

#define wait_event_interruptible(wq, condition)				\
({									\
	int __ret = 0;							\
	if (!(condition))						\
		__wait_event_interruptible(wq, condition, __ret);	\
	__ret;								\
})

1. 驱动代码 keys_int_wait.c 在irq中更新kay_val wake_up_interruptible(&button_waitq)

#include <linux/init.h>     // module_init module_exit
#include <linux/module.h>   // MODULE_LICENSE
#include <linux/fs.h>       // file_operations
#include <linux/cdev.h>     // cdev
#include <linux/kernel.h>
#include <linux/device.h>   // class_device
#include <linux/irq.h>      // IRQ_EINTx
#include <asm/uaccess.h>    // copy_from_user copy_to_user
#include <asm/io.h>         // ioremap  iounmap
#include <asm/irq.h>
#include <asm/arch/regs-gpio.h> // S3C2410_GPFx

#define DEVICE_NAME      "k_int_wait"
int major;

volatile unsigned long *gpfcon = NULL;
volatile unsigned long *gpfdat = NULL;
volatile unsigned long *gpgcon = NULL;
volatile unsigned long *gpgdat = NULL;
#define GPFCON      (*gpfcon)
#define GPFDAT      (*gpfdat)
#define GPGCON      (*gpgcon)
#define GPGDAT      (*gpgdat)

// 用于自动创建设备节点的结构class 和 class_device
static struct class *keys_class;
static struct class_device *keys_class_dev;

// 定义一个事件叫button_waitq 生成一个等待队列头wait_queue_head_t,名字为button_waitq
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
// 定义 中断事件标志,与read中的wait_event_interruptible(button_waitq, ev_press)配合 ISR将它置1, read 清0
static volatile int ev_press = 0;
// 自定义引脚描述的结构
struct pin_desc
{
    unsigned int pin;
    unsigned int key_val;
};
/*键值:按下,0x01,0x02,0x03,0x04
  键值:松开,0x81,0x82,0x83,0x84*/
static unsigned char key_val;
struct pin_desc pins_desc[4] = {
    {S3C2410_GPF0,  0x01},
    {S3C2410_GPF2,  0x02},
    {S3C2410_GPG3,  0x03},
    {S3C2410_GPG11, 0x04}
};
// ISR 发生中断之后会调用这个函数, irq就是对应的中断号
static irqreturn_t keys_irq(int irq, void *dev_id)
{
    struct pin_desc *pindesc = (struct pin_desc *)dev_id;
    unsigned int pin_status;
    // 获取引脚状态 Linux内部有系统函数s3c2410_gpio_getpin能够读取GPIO的值
    pin_status = s3c2410_gpio_getpin(pindesc->pin);
    printk("irq = %d\n", irq);
    // 判断引脚,给全局变量赋对应引脚的状态值 (状态--> 所属引脚)
    if (pin_status) // 松开
    {
        key_val = 0x80 | pindesc->key_val; // 存入自定义全局变量,在read中to_user
    }
    else            // 按下
    {
        key_val = 0x00 | pindesc->key_val; // 存入自定义全局变量,在read中to_user
    }
    ev_press = 1; // 中断事件标志,ISR将它置1, read 清0
    wake_up_interruptible(&button_waitq);

    return IRQ_RETVAL(IRQ_HANDLED);
}
// GPF0/2 GPG3/11 申请中断
static int keys_open (struct inode *inode, struct file *filep)
{
    /*
    *	@irq: Interrupt line to allocate
    *	@handler: Function to be called when the IRQ occurs
    *	@irqflags: Interrupt type flags
    *	@devname: An ascii name for the claiming device
    *	@dev_id: A cookie passed back to the handler function
    */
    // 中断引脚,自动设备为中断引脚,
    //  IRQ_EINT0在include/asm-arm/arch-s3c2410/irqs.h中定义
    //  keys_irq
    //  IRQT_BOTHEDGE双边沿触发
    //  "EINT0_S2"
    // IRQ_EINT0 &pins_desc[0]将被传入到中断服务程序
    request_irq(IRQ_EINT0,  keys_irq, IRQT_BOTHEDGE, "EINT0_S2", &pins_desc[0]);
    request_irq(IRQ_EINT2,  keys_irq, IRQT_BOTHEDGE, "EINT0_S3", &pins_desc[1]);
    request_irq(IRQ_EINT11, keys_irq, IRQT_BOTHEDGE, "EINT0_S4", &pins_desc[2]);
    request_irq(IRQ_EINT19, keys_irq, IRQT_BOTHEDGE, "EINT0_S5", &pins_desc[3]);

    return 0;
}
// read函数会被用户空间调用,可能while(1), 防止占用资源
ssize_t keys_read (struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
    // 判断缓冲区为1
    if (size != 1)
        return -EINVAL;

    // 没有按键动作就休眠, 内部判断ev_press是否为0,为0则休眠,否则往下执行
    //  有休眠就有唤醒,唤醒在ISR中
    wait_event_interruptible(button_waitq, ev_press); // ev_press在这里跟wait_event_interruptible函数绑定在一起了,该函数会判断第二参数的值而确定是否等待
    // 将键值返回到用户空间
    copy_to_user(buf, &key_val, 1);
    // 中断事件标志,ISR将它置1, read 清0
    ev_press = 0;

    return 1;
}
// 释放中断的配置
int key_close(struct inode *inode, struct file *filp)
{
    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;
}
static struct file_operations fops = {
    .owner   = THIS_MODULE,
    .open    = keys_open,
    .read    = keys_read,
    .release = key_close,
};

static int keys_init(void)
{
    major = register_chrdev(0, DEVICE_NAME, &fops);
    if (major < 0)
    {
        printk(DEVICE_NAME " can't register major number.\n");
        return major;
    }

    keys_class = class_create(THIS_MODULE, "keys_class");
    if (IS_ERR(keys_class))
        return PTR_ERR(keys_class);
    // 2. 在class里边创建一个设备叫xxx,然后mdev自动创建设备节点/dev/xxx
    //  在/dev目录下创建相应的设备节点,
    keys_class_dev = class_device_create(keys_class, NULL, MKDEV(major, 0), NULL, "k_int_w_node"); 
    if (unlikely(IS_ERR(keys_class_dev)))
        return PTR_ERR(keys_class_dev); 

    gpfcon = (volatile unsigned long*)ioremap(0x56000050, 16);
    gpfdat = gpfcon + 1;
    gpgcon = (volatile unsigned long*)ioremap(0x56000060, 16);
    gpgdat = gpgcon + 1;

    printk(DEVICE_NAME " device initialized successfully...\n\n");

    return 0;
}

static void keys_exit(void)
{
    // 对应卸载
    unregister_chrdev(major, DEVICE_NAME);

    class_device_unregister(keys_class_dev); 
    class_destroy(keys_class);

    iounmap(gpfcon);
    iounmap(gpgcon);
}

module_init(keys_init);
module_exit(keys_exit);
MODULE_LICENSE("GPL");

2. 用户代码 user_keys_int_wait.c read会睡眠 进程资源占用0%

#include <fcntl.h>
#include <stdio.h>

int main(int argc, char **argv)
{
    int fd;
    int cnt = 0;
    unsigned char key_val;
    
    if (argc != 1)
    {
        printf("No args required.\n\n");
        return 0;
    }

    fd = open("/dev/k_int_w_node", O_RDWR);
    if (fd < 0)
    {
        printf("can't open '/dev/k_int_w_node'.\n");
        return 0;
    }
    printf("Waiting press the keys, will print xxxx.\n\n");
    while(1)
    {
        // read之后进入内核态的驱动程序里,如果没有按键中断,休眠;
        //  有按键中断,进入ISR得到按键值,在read中继续执行,将按键值传递到用户空间
        read(fd, &key_val, 1);
        printf("key_val: 0x%x\n", key_val); // 输出16进制
    }
    return 0;
}

3. 插入模块/查看设备节点/中断注册情况/资源占用情况

查看主设备号和驱动设备,
在这里插入图片描述
查看设备节点,
在这里插入图片描述
查看中断注册情况,cat proc/interrupts
在这里插入图片描述
后台运行,按下按键,查看资源占用情况,
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值