字符设备驱动学习(2)

本文介绍如何将基于查询的按键驱动程序改造成使用中断的方式。详细解释了Linux内核中的中断处理机制,包括中断的初始化、注册及处理过程,并提供了一个具体的实现案例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

学习目标:在字符设备驱动学习(1)的按键查询基础上改为按键中断。

1、什么是中断
中断是异常的一种,异常就是可以打断cpu正常运行流程的一些事情,比如外部中断、未定义指令、swi指令。cup暂停当前的程序,先处理异常事件,然后再继续执行被中断的程序。

2、linux内核对异常的设置
内核在strrt_kernel函数中调用了trap_init函数和init_IRQ函数来设置异常的处理函数。
①memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
这两个函数对异常向量的拷贝,__vectors_end 至 __vectors_start之间为异常向量表
位于arch/arm/kernel/entry-armv.S中:拷贝到0xffff0000,将异常处理函数从__stubs_start至__stubs_end拷贝到0xffff0000+0x200处。
②.equ stubs_offset, __vectors_start + 0x200 - __stubs_start 复制完后,重新定位跳转的位置。(向量被复制到0xffff0000处,跳转的目的代码被复制到xffff0000+0x200处)
注:定义静态符号: .equ 命令用于把常量值设置为可以在文本段中使用的符号

如:
.equ factor, 3
.equ LINUX_SYS_CALL, 0x80
经过设置之后,数据符号值是不能在 程序中改动的。

3、中断处理的体系结构(内核分析)
内核中将所有的中断统一编号,使用一个irq_desc的结构数组来描述中断,数组中每项元素对应一个中断,也可能是一组中断,它们公用一个中断号,里面记录了中断名称、状态、标记(类型、是否共享)并提供中断的底层硬件访问函数(清除、屏蔽、使能中断),可提供了这个中断的处理函数入口,通过它可以调用用户注册的中断处理函数。
中断处理体系结构的初始化(irq_desc结构体注组成的数组的初始化):init_IRQ()用于初始化:

void __init init_IRQ(void)
{
    int irq;

    for (irq = 0; irq < NR_IRQS; irq++)
        irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;

    ...
    init_arch_irq();
}

1、for循环是初始化数组元素的状态
2、init_arch_irq():跟体系结构相关,不同平台对应函数不一样。最终调用与平台相关的函数——>XXX_init_irq(),主要功能是对数组中每项irq_desc中的元素赋值,chip、handle_irq等。

用户注册中断处理函数(驱动程序):通过request_irq()函数向内核注册处理函数。根据中断号,找到对应中断的irq_desc成员,在irq_desc的成员action(链表)添加一个节点。

int request_irq(unsigned int irq, irq_handler_t handler,
        unsigned long irqflags, const char *devname, void *dev_id)
{
    struct irqaction *action;
    int retval;
    ........
    action = kmalloc(sizeof(struct irqaction), 
    ...
    action->handler = handler;
    action->flags = irqflags;
    cpus_clear(action->mask);
    action->name = devname;
    action->next = NULL;
    action->dev_id = dev_id;
    ....
    retval = setup_irq(irq, action);
    if (retval)
        kfree(action);

    return retval;

4、在查询基础上改为中断

#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/irq.h>

#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <asm/irq.h>
#include <asm/arch/regs-gpio.h>


#define KEY_DRIVER1_MAJOR 0


/*休眠队列*/
static DECLARE_WAIT_QUEUE_HEAD(boutton_qeueue);
/*中断事件标志,中断中进行置1,read中将它清0*/
static volatile int en_press = 0;


/*声明用到的结构体和变量*/
struct cdev key_driver1_cdev;
static int key_driver1_major = KEY_DRIVER1_MAJOR;
dev_t key_dev_num;//dev_t类型设备号
static struct class *key_drv_class;
static struct class_device *key_drv_class_dev;
/*用于地址映射*/
volatile unsigned long *gpfcon;
volatile unsigned long *gpfdat;

volatile unsigned long *gpgcon;
volatile unsigned long *gpgdat;

#define rGPFCON 0X56000050
#define rGPGCON 0X56000060

struct pin_desc{
    unsigned int pin;
    unsigned char key_val;
};

/*按下时:0x01,02,03,04*/
struct pin_desc pin_desc[4] = {
        {S3C2410_GPF0, 0X01},//刚开始吧0写错为1,导致按下按键总是一个值
        {S3C2410_GPF2, 0X02},
        {S3C2410_GPG3, 0X03},
        {S3C2410_GPG11,0X04},
};

static unsigned char key_val = 0;//按键值

/*
 *
 */
static irqreturn_t button_irq(int irq, void *dev_id)
{   struct pin_desc *p = (struct pin_desc *)dev_id; 
    unsigned int pin_val;
    pin_val = s3c2410_gpio_getpin(p->pin);
    key_val = 0;
    if(pin_val)
        {
            /*松开*/
            key_val = 0x80 | p->key_val;
            //printk("kernel key_val  = 0x%x\n",key_val);//调试用的
        }   
    else
        {
            /*按下*/
            key_val = p->key_val;
            //printk("kernel anxia key_val  = 0x%x\n",key_val);//调试用的
        }

    en_press = 1; //表示中断发生
    wake_up_interruptible(&boutton_qeueue);//唤醒休眠的进程
    return IRQ_HANDLED;
}






static int key_driver1_open1(struct inode *node, struct file *file)
{

    /*采用查询方式,将按键的引脚进行配置为输入*/
    //*gpfcon &= ~((0x3 << (0*2)) | (0x3 << (2*2)));

    //*gpgcon &= ~((0x3 << (3*2)) | (0x3 << (11*2)));
    /*中断注册函数*/
    request_irq(IRQ_EINT0,button_irq,IRQT_BOTHEDGE,"S2",&pin_desc[0]);
    request_irq(IRQ_EINT2,button_irq,IRQT_BOTHEDGE,"S3",&pin_desc[1]);
    request_irq(IRQ_EINT11,button_irq,IRQT_BOTHEDGE,"S4",&pin_desc[2]);
    request_irq(IRQ_EINT19,button_irq,IRQT_BOTHEDGE,"S5",&pin_desc[3]);


    return 0;
}

static int key_driver1_close(struct inode *inode, struct file *file)
{

    free_irq(IRQ_EINT0,&pin_desc[0]);
    free_irq(IRQ_EINT2,&pin_desc[1]);
    free_irq(IRQ_EINT11,&pin_desc[2]);
    free_irq(IRQ_EINT19,&pin_desc[3]);
    return 0;
}


static int key_driver1_read (struct file * file, char __user * buf, size_t size, loff_t *ppos)
{

    int ret;

    if(size != 1)
        return -EINVAL;

    /* 如果没有按键安动作,休眠,如果有按键按下,就直接返回*/
    wait_event_interruptible(boutton_qeueue,en_press);

    ret = copy_to_user(buf,&key_val,1);
    en_press = 0;   

    return 1;

}


/*文件操作结构体*/
static const struct file_operations key_driver1_fops =
{
  .owner   = THIS_MODULE,
  .read    = key_driver1_read,
  .open    = key_driver1_open1,
  .release = key_driver1_close
};


/*设备驱动模块加载函数*/
int key_driver1_init(void)
{
  int result;
  int err;
  key_dev_num = MKDEV(key_driver1_major, 0);

  /* 申请设备号*/
  if (key_driver1_major)
    result = register_chrdev_region(key_dev_num, 1, "key_driver1");
  else  /* 动态申请设备号 */
  {
    result = alloc_chrdev_region(&key_dev_num, 0, 1, "key_driver1");
    key_driver1_major = MAJOR(key_dev_num);
  }  
  if (result < 0)
    return result;
  /*
    *进行cdev的初始化,将cdev与key_driver1_fops关联


  */
  cdev_init(&key_driver1_cdev, &key_driver1_fops);
  key_driver1_cdev.owner = THIS_MODULE;
  key_driver1_cdev.ops = &key_driver1_fops;
  err = cdev_add(&key_driver1_cdev, key_dev_num, 1);
  if (err)
    printk(KERN_NOTICE "Error %d ", err);

  /*
       * 为设备创建类
       * 加载驱动模块时在/sys/class   目录下自动创建car_class  文件夹
       */
      key_drv_class = class_create(THIS_MODULE, "key_driver1");
      if(IS_ERR(key_drv_class))
      {
          printk("cannot create key_drv_class!\n");
          return 0;
      }
      /*创建设备文件节点,避免需要手动创建*/
      key_drv_class_dev = class_device_create(key_drv_class, NULL,key_dev_num, NULL, "key_driver1");

    /*将GPIO  的物理地址映射到内核空间*/
    gpfcon = (volatile unsigned long *)ioremap(rGPFCON, 16);
    gpfdat = gpfcon + 1;

    gpgcon = (volatile unsigned long *)ioremap(rGPGCON, 16);
    gpgdat = gpgcon + 1;


  return 0;

}

/*模块卸载函数*/
void key_driver1_exit(void)
{

  cdev_del(&key_driver1_cdev);   /*注销cdev*/
  unregister_chrdev_region(key_dev_num, 1); /*释放设备号*/

  /*删除设备文件和dev/class  目录下的相应文件夹*/
    class_device_unregister(key_drv_class_dev);
    class_destroy(key_drv_class);
    /*解除GPIO  映射*/
    iounmap(gpfcon);
    iounmap(gpgcon);

}

MODULE_AUTHOR("healer");
MODULE_LICENSE("Dual BSD/GPL");

module_init(key_driver1_init);
module_exit(key_driver1_exit);

应用程序代码

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


int main(int arvc,char **argv)
{
    int fd;
    int count = 1;

    unsigned char key_val = 0;
    fd = open("/dev/key_driver1",O_RDWR);
    if(fd < 0)
        {
            printf("can not open!\n");
        }
    while(1)
        {   
            read(fd,&key_val,1);

            printf("key_val =0x%x\n",key_val);      
        }

    return 0;

Makefile

ifneq ($(KERNELRELEASE),)
obj-m := inter_key.o
else
PWD := $(shell pwd)
KDIR := /home/healer/myshare/linuxkernel/linux-2.6.22.6
all:
    make -C $(KDIR) M=$(PWD)
clean:  
    rm -rf *.o *.ko *.mod.c *.symvers *.c~ *~
endif

makefile代码:
obj-m := inter_key.o 根据自己编写的驱动文件名来定,xxx.c对应这里obj-m :=xxx.o
KDIR := /home/healer/myshare/linuxkernel/linux-2.6.22.6为内核代码所在目录(根据自己内核目录定),因为我们编写的驱动时以模块的形式,需要内核代码得支持。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值