Linux学习笔记(17.11)—— 按键的中断与mmap

本文深入探讨了内存映射的概念,解释了CPU的虚拟地址如何通过MMU映射到物理地址,以及在不同进程中相同虚拟地址的处理方式。通过分析一个简单的Linux驱动示例,展示了mmap函数在创建内存映射时的作用,包括设置缓存和缓冲器的属性。此外,还介绍了ARM架构的一级和二级页表映射机制,以及在按键设备驱动中的应用。
摘要由CSDN通过智能技术生成

本文主要参考韦东山Linux文档

  1. 内存映射现象与数据结构

    编写mmap_test.c测试程序

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    
    int a;
    int main(int argc, char **argv)
    {
        if (argc != 2) {
            printf ("Usage: %s <number>\r", argv[0]);
            return -1;
        }
    
        a = strtol (argv[1], NULL, 0);
        printf ("a's address = 0x%lx, a's value = %d\n", &a, a);
        while (1) {
            sleep(10);
        }
        return 0;
    }
    

    对测试程序编译,并在ubuntu 20.04上运行如下:

    glen@ubuntu:~/linux/imx6ull/glen$ ./mmap_test 12 &
    [1] 3133
    glen@ubuntu:~/linux/imx6ull/glen$ a's address = 0x4c3300, a's value = 12
    
    glen@ubuntu:~/linux/imx6ull/glen$ ./mmap_test 123 &
    [2] 3135
    glen@ubuntu:~/linux/imx6ull/glen$ a's address = 0x4c3300, a's value = 123
    
    glen@ubuntu:~/linux/imx6ull/glen$ ps
        PID TTY          TIME CMD
       3032 pts/1    00:00:00 bash
       3133 pts/1    00:00:00 mmap_test
       3135 pts/1    00:00:00 mmap_test
       3138 pts/1    00:00:00 ps
    glen@ubuntu:~/linux/imx6ull/glen$ cat /proc/3133/maps 
    00400000-00401000 r--p 00000000 08:05 4718756   /home/glen/linux/imx6ull/glen/mmap_test
    00401000-00495000 r-xp 00001000 08:05 4718756   /home/glen/linux/imx6ull/glen/mmap_test
    00495000-004bc000 r--p 00095000 08:05 4718756   /home/glen/linux/imx6ull/glen/mmap_test
    004bd000-004c0000 r--p 000bc000 08:05 4718756   /home/glen/linux/imx6ull/glen/mmap_test
    004c0000-004c3000 rw-p 000bf000 08:05 4718756   /home/glen/linux/imx6ull/glen/mmap_test
    004c3000-004c4000 rw-p 00000000 00:00 0 
    0126e000-01291000 rw-p 00000000 00:00 0                                  [heap]
    7ffd4202a000-7ffd4204b000 rw-p 00000000 00:00 0                          [stack]
    7ffd42101000-7ffd42105000 r--p 00000000 00:00 0                          [vvar]
    7ffd42105000-7ffd42107000 r-xp 00000000 00:00 0                          [vdso]
    ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0                  [vsyscall]
    
    glen@ubuntu:~/linux/imx6ull/glen$ cat /proc/3135/maps 
    00400000-00401000 r--p 00000000 08:05 4718756    /home/glen/linux/imx6ull/glen/mmap_test
    00401000-00495000 r-xp 00001000 08:05 4718756    /home/glen/linux/imx6ull/glen/mmap_test
    00495000-004bc000 r--p 00095000 08:05 4718756    /home/glen/linux/imx6ull/glen/mmap_test
    004bd000-004c0000 r--p 000bc000 08:05 4718756    /home/glen/linux/imx6ull/glen/mmap_test
    004c0000-004c3000 rw-p 000bf000 08:05 4718756    /home/glen/linux/imx6ull/glen/mmap_test
    004c3000-004c4000 rw-p 00000000 00:00 0 
    01b1c000-01b3f000 rw-p 00000000 00:00 0                                  [heap]
    7ffc7dcbb000-7ffc7dcdc000 rw-p 00000000 00:00 0                          [stack]
    7ffc7dd66000-7ffc7dd6a000 r--p 00000000 00:00 0                          [vvar]
    7ffc7dd6a000-7ffc7dd6c000 r-xp 00000000 00:00 0                          [vdso]
    ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0                  [vsyscall]
    
    

    观察到这些现象:
    ① 2个程序同时运行,它们的变量a的地址都是一样的:0x4c3300;
    ② 2个程序同时运行,它们的变量a的值是不一样的,一个是12,另一个是123。

    疑问来了:
    ① 这2个程序同时在内存中运行,它们的值不一样,所以变量a的地址肯定不同;
    ② 但是打印出来的变量a的地址却是一样的。

    怎么回事?
    ​ 这里要引入虚拟地址的概念:CPU发出的地址是虚拟地址,它经过MMU(Memory Manage Unit,内存管理单元)映射到物理地址上,对于不同进程的同一个虚拟地址,MMU会把它们映射到不同的物理地址。如下图:
    在这里插入图片描述

    当前运行的是app1时,MMU会把CPU发出的虚拟地址addr映射为物理地址paddr1,用paddr1去访问内存。

    当前运行的是app2时,MMU会把CPU发出的虚拟地址addr映射为物理地址paddr2,用paddr2去访问内存。
    MMU负责把虚拟地址映射为物理地址,虚拟地址映射到哪个物理地址去?
    可以执行ps命令查看进程ID,然后执行cat /proc/3133/maps得到映射关系。
    每一个APP在内核里都有一个tast_struct,这个结构体中保存有内存信息:mm_struct。而虚拟地址、物理地址的映射关系保存在页目录表中,如下图所示:
    在这里插入图片描述 解析如下:
    ① 每个APP在内核中都有一个task_struct结构体,它用来描述一个进程;
    ② 每个APP都要占据内存,在task_struct中用mm_struct来管理进程占用的内存;
    内存有虚拟地址、物理地址,mm_struct中用mmap来描述虚拟地址,用pgd来描述对应的物理地址。
    注意pgd,Page Global Directory,页目录。
    ③ 每个APP都有一系列的VMA:virtual memory

    ​ 比如APP含有代码段、数据段、BSS段、栈等等,还有共享库。这些单元会保存在内存里,它们的地址空间不同,权限不同(代码段是只读的可运行的、数据段可读可写),内核用一系列的vm_area_struct来描述它们。
    vm_area_struct中的vm_startvm_end是虚拟地址。
    vm_area_struct中虚拟地址如何映射到物理地址去?
    每一个APP的虚拟地址可能相同,物理地址不相同,这些对应关系保存在pgd中。

  2. ARM架构内存映射简介

    ​ ARM架构支持一级页表映射,也就是说MMU根据CPU发来的虚拟地址可以找到第1个页表,从第1个页表里就可以知道这个虚拟地址对应的物理地址。一级页表里地址映射的最小单位是1M。

    ​ ARM架构还支持二级页表映射,也就是说MMU根据CPU发来的虚拟地址先找到第1个页表,从第1个页表里就可以知道第2级页表在哪里;再取出第2级页表,从第2个页表里才能确定这个虚拟地址对应的物理地址。二级页表地址映射的最小单位有4K、1K,Linux使用4K。

    一级页表项里的内容,决定了它是指向一块物理内存,还是指问二级页表,如下图:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pfCLRqDo-1647438354386)(E:/Glen/Linux/Linux%E9%A9%B1%E5%8A%A8%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/Linux%E8%AE%BE%E5%A4%87%E9%A9%B1%E5%8A%A8%E4%B9%8Bmmap.assets/wps8841.tmp.jpg)]

    2.1 一级页表映射过程

    ​ 一级页表中每一个表项用来设置1M的空间,对于32位的系统,虚拟地址空间有4G,4G/1M=4096。所以一级页表要映射整个4G空间的话,需要4096个页表项。

    第0个页表项用来表示虚拟地址第0个1M(虚拟地址为0~0xFFFFF)对应哪一块物理内存,并且有一些权限设置;

    第1个页表项用来表示虚拟地址第1个1M(虚拟地址为0x100000~0x2FFFFF)对应哪一块物理内存,并且有一些权限设置;

    依此类推…

    ​ 使用一级页表时,先在内存里设置好各个页表项,然后把页表基地址告诉MMU,就可以启动MMU了。

    以下图为例介绍地址映射过程:

    ① CPU发出虚拟地址vaddr,假设为0x12345678

    ② MMU根据vaddr[31:20]找到一级页表项:

    虚拟地址0x12345678是虚拟地址空间里第0x123个1M,所以找到页表里第0x123项,根据此项内容知道它是一个段页表项。

    段内偏移是0x45678。

    ③ 从这个表项里取出物理基地址:Section Base Address,假设是0x81000000

    ④ 物理基地址加上段内偏移得到:0x81045678

    所以CPU要访问虚拟地址0x12345678时,实际上访问的是0x81045678的物理地址

    在这里插入图片描述

    2.2 二级页表映射过程

    首先设置好一级页表、二级页表,并且把一级页表的首地址告诉MMU。

    以下图为例介绍地址映射过程:

    ① CPU发出虚拟地址vaddr,假设为0x12345678

    ② MMU根据vaddr[31:20]找到一级页表项:

    虚拟地址0x12345678是虚拟地址空间里第0x123个1M,所以找到页表里第0x123项。根据此项内容知道它是一个二级页表项。

    ③ 从这个表项里取出地址,假设是address,这表示的是二级页表项的物理地址;

    ④ vaddr[19:12]表示的是二级页表项中的索引index即0x45,在二级页表项中找到第0x45项;

    ⑤ 二级页表项格式如下:
    在这里插入图片描述

    里面含有这4K或1K物理空间的基地址page base addr,假设是0x81889000:

    它跟vaddr[11:0]组合得到物理地址:0x81889000 + 0x678 = 0x81889678。

    所以CPU要访问虚拟地址0x12345678时,实际上访问的是0x81889678的物理地址

    在这里插入图片描述

  3. APP新建一块内存映射

    3.1 mmap调用过程

    ​ 从上面内存映射的过程可以知道,要给APP新开辟一块虚拟内存,并且让它指向某块内核buffer,我们要做这些事:

    ① 得到一个vm_area_struct,它表示APP的一块虚拟内存空间;

    ​ APP调用mmap系统函数时,内核就帮我们构造了一个vm_area_stuct结构体。里面含有虚拟地址的地址范围、权限。

    ② 确定物理地址:映射某个内核buffer,需要得到它的物理地址,这得由你提供。

    ③ 给vm_area_struct和物理地址建立映射关系:内核提供有相关函数。

    ​ APP里调用mmap时,导致的内核相关函数调用过程如下:

    app: mmap (addr, length, prot, flags, fd, offset)
    -----------------------------------------------------------------------------------------
    SYSCALL_DEFINE6(mmap_pgoff, unsigned long,....	/* mm/mmap.c */
        vm_mmap_pgoff(file, addr, len, prot, flags, pgoff);	/* mm/util.c */
            do_mmap_pgoff(file, addr, len, prot, flag, pgoff, &populate); /* mm/mmap.c */
                addr = get_unmapped_area(file, addr, len, pgoff, flags);  /* mm/mmap.c */
                addr = mmap_region(file, addr, len, vm_flags, pgoff);     /* mm/mmap.c */
                    vma = kmem_cache_zalloc(vm_area_cachep, GFP_KERNEL);  /* 分配新的 vm_area_struct */
                    /* 设置vm_area_struct */
                    vma->vm_mm = mm;
                    vma->vm_start = addr;
                    vma->vm_end = addr + len;
                    vma->vm_flags = vm_flags;
                    vma->vm_page_prot = vm_get_page_prot(vm_flags);
                    vma->vm_pgoff = pgoff;
                    INIT_LIST_HEAD(&vma->anon_vma_chain);
                    /* 调用驱动程序的mmap */
                    error = file->f_op->mmap(file, vma);
    	
    

    ​ 我们只需要实现驱动的mmap函数:

    ① 提供物理地址
    ② 设置属性:cache, buffer
    ③ 给vm_area_struct和物理地址建立映射

    3.2 cachebuffer
    ​ 使用mmap时,需要有cache、buffer的知识。下图是CPU和内存之间的关系,有cache、buffer(缓冲器)。Cache是一块高速内存;缓冲器相当于一个FIFO,可以把多个写操作集合起来一次写入内存。
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v4S9ckvC-1647438354392)(E:/Glen/Linux/Linux%E9%A9%B1%E5%8A%A8%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/Linux%E8%AE%BE%E5%A4%87%E9%A9%B1%E5%8A%A8%E4%B9%8Bmmap.assets/image-20220315055552112.png)] ​ 程序运行时有“局部性原理”,这又分为时间局部性、空间局部性。

    ① 时间局部性:在某个时间点访问了存储器的特定位置,很可能在一小段时间里,会反复地访问这个位置。
    ② 空间局部性:访问了存储器的特定位置,很可能在不久的将来访问它附近的位置。
    ​ 而CPU的速度非常快,内存的速度相对来说很慢。CPU要读写比较慢的内存时,怎样可以加快速度?根据“局部性原理”,可以引入cache。

    ① 读取内存addr处的数据时:
    ​ 先看看cache中有没有addr的数据,如果有就直接从cache里返回数据:这被称为cache命中。

    ​ 如果cache中没有addr的数据,则从内存里把数据读入,注意:它不是仅仅读入一个数据,而是读入一行数据(cache line)。

    ​ 而CPU很可能会再次用到这个addr的数据,或是会用到它附近的数据,这时就可以快速地从cache中获得数据。

    ② 写数据:

    ​ CPU要写数据时,可以直接写内存,这很慢;也可以先把数据写入cache,这很快。

    但是cache中的数据终究是要写入内存的啊,这有2种写策略:

    a. 写通(write through):

    ​ 数据要同时写入cache和内存,所以cache和内存中的数据保持一致,但是它的效率很低。能改进吗?可以!使用“缓冲器”:cache大哥,你把数据给我就可以了,我来慢慢写,保证帮你写完。

    ​ 有些缓冲器有“写合并”的功能,比如CPU执行了4条写指令:写第0、1、2、3个字节,每次写1字节;写缓冲器会把这4个写操作合并成一个写操作:写word。对于内存来说,这没什么差别,但是对于硬件寄存器,这就有可能导致问题。

    ​ 所以对于寄存器操作,不会启动buffer功能;对于内存操作,比如LCD的显存,可以启用buffer功能。

    b. 写回(write back):

    ​ 新数据只是写入cache,不会立刻写入内存,cache和内存中的数据并不一致。

    ​ 新数据写入cache时,这一行cache被标为“脏”(dirty);当cache不够用时,才需要把“脏”的数据写入内存。

    ​ 使用写回功能,可以大幅提高效率。但是要注意cache和内存中的数据很可能不一致。这在很多时间要小心处理:比如CPU产生了新数据,DMA把数据从内存搬到网卡,这时候就要CPU执行命令先把新数据从cache刷到内存。反过来也是一样的,DMA从网卡得过了新数据存在内存里,CPU读数据之前先把cache中的数据丢弃。

    ​ 是否使用cache、是否使用buffer,就有4种组合(Linux内核文件arch\arm\include\asm\pgtable-2level.h):

    #define L_PTE_MT_UNCACHED	(_AT(pteval_t, 0x00) << 2)	/* 0000 */
    #define L_PTE_MT_BUFFERABLE	(_AT(pteval_t, 0x01) << 2)	/* 0001 */
    #define L_PTE_MT_WRITETHROUGH	(_AT(pteval_t, 0x02) << 2)	/* 0010 */
    #define L_PTE_MT_WRITEBACK	(_AT(pteval_t, 0x03) << 2)	/* 0011 */
    

    第1种 不使用cache也不使用buffer,读写时都直达硬件,这适合寄存器的读写。

    第2种 不使用cache但是使用buffer,写数据时会用buffer进行优化,可能会有“写合并”,这适合显存的操作。因为对显存很少有读操作,基本都是写操作,而写操作即使被“合并”也没有关系。

    第3种 使用cache不使用buffer,就是“write through”,适用于只读设备:在读数据时用cache加速,基本不需要写。

    第4种 既使用cache又使用buffer,适合一般的内存读写。

  4. 以按键进行编程实践

    驱动程序要做的事情有3点:
    ① 确定物理地址
    ② 确定属性:是否使用cache、buffer
    ③ 建立映射关系

    4.1 按键设备驱动文件
    button_drv.c文件中,

    • button_drv_init函数中调用kmallockbuf分配1024 * 8个字节数据,并赋值;

    • file_operations button_drv_ops结构体对象中将 button_drv_mmap函数赋给.mmap成员。同时在button_drv_mmap函数中获取物理地址、设置属性cache buffermap

    • file_operations button_drv_ops结构体对象中将 button_drv_write函数赋给.write成员。同时在button_drv_write函数将用户空间的数据写到内核空间kbuf中。

/**
 * 文件    : button_drv.c
 * 作者    : glen  
 * 描述    : button driver文件
 */
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/poll.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/fs.h>
#include <linux/time.h>
#include <linux/timer.h>
#include <linux/proc_fs.h>
#include <linux/stat.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/workqueue.h>

#define KEY_BUF_LEN 16
#define NEXT_POS(x) ((x+1) % KEY_BUF_LEN)

struct gbtn_irq {
    int gpio;
    struct gpio_desc *gpiod;
    int flag;
    int irq;
    int idx;
    char kval[KEY_BUF_LEN];
    int r, w;
    struct fasync_struct *fp;
    struct timer_list key_timer;
    struct tasklet_struct tasklet;
    struct work_struct wq;
};

struct button_drv {
    struct class *class;
    struct gbtn_irq *gbtn_irq;
    
    char  *name;
    int count;
    int major;
};

static struct button_drv *btn_drv;

static char *kbuf;
static int bufsz = 1024 * 8;

#define MIN(a, b) (a < b ? a : b)

static int is_key_buf_empty(void *arg)
{
    struct gbtn_irq *p = (struct gbtn_irq *)arg;

    return (p->r == p->w);
}

static int is_key_buf_full(void *arg)
{
    struct gbtn_irq *p = (struct gbtn_irq *)arg;

    return (p->r == NEXT_POS(p->w));
}

static void put_key(char key, void *arg)
{
    struct gbtn_irq *p = (struct gbtn_irq *)arg;

    if (!is_key_buf_full(arg)) {
        p->kval[p->w] = key;
        p->w = NEXT_POS(p->w);
    }
}

static char get_key(void *arg)
{
    char key = 'N';
    struct gbtn_irq *p = (struct gbtn_irq *)arg;

    if (!is_key_buf_full(arg)) {
        key = p->kval[p->r];
        p->r = NEXT_POS(p->r);
    }

    return key;
}

/* 等待队列头的静态初始化 */
static DECLARE_WAIT_QUEUE_HEAD(gpio_button_wait);

static void key_timer_expire(unsigned long data)
{
    int val;
    char key;
    struct gbtn_irq *ops = (struct gbtn_irq *)data;

    /* 读取按键的值 */
    val = gpiod_get_value(ops->gpiod);

    printk("button%d %d %d\n", ops->idx, ops->gpio, val);
    key = (ops->gpio << 4) | val;

    put_key(key, ops);

    /* 唤醒等待队列 */
    wake_up_interruptible(&gpio_button_wait);

    kill_fasync(&ops->fp, SIGIO, POLL_IN);

    /* enable btn*/
    enable_irq(ops->irq);
}

/* 实现file_operations结构体成员 read 函数 */
ssize_t button_drv_read (struct file *filp, char __user *buf, size_t size, loff_t *pos)
{
    int minor = iminor(filp->f_inode);
    struct gbtn_irq *ops = (struct gbtn_irq *)filp->private_data;
    char kval;
    u16  sz = size;

    size = (size >= 1) ? 1 : 0;
    if (ops == NULL) {
        printk("Please register button instance %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
        return -EIO;
    }

    if (is_key_buf_empty(ops) && (filp->f_flags & O_NONBLOCK))
        return -EAGAIN;

    wait_event_interruptible(gpio_button_wait, !is_key_buf_empty(ops));
    kval = get_key(ops);

    /* if (copy_to_user(buf, &kval, size))
        return -EFAULT;
    */

    if (copy_to_user(kbuf, buf, MIN(1024, sz)))
        return -EFAULT;

    printk("Read button%d value successfully:", minor);
    return (sz);
}

/* 实现file_operations结构体成员 open 函数 */
int button_drv_open(struct inode *nd, struct file *filp)
{
    int ret;
    int minor = iminor(nd);
    struct gbtn_irq *ops; 
    
    if (btn_drv == NULL) {
        printk("Please register button instance %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
        return -EIO;
    }

    ops = &btn_drv->gbtn_irq[minor];

    ret = gpiod_direction_input(ops->gpiod);
    if (ret) 
        printk("Set the button pin as input error %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
    else 
        printk("Set the button%d pin as input successfully!\n", minor);

    filp->private_data = ops;

    return 0;
}

/* 实现file_operations结构体成员 release 函数 */
int button_drv_release (struct inode *nd, struct file *filp)
{
    struct gbtn_irq *ops = (struct gbtn_irq *)filp->private_data;

    if (ops == NULL) {
        printk("Please register button instance %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
        return -EIO;
    }

    filp->private_data = NULL;

    return 0;
}

/* 实现file_operations结构体成员 poll 函数 */
unsigned int button_drv_poll (struct file *filp, struct poll_table_struct * wait)
{
    int minor = iminor(filp->f_inode);

    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    poll_wait(filp, &gpio_button_wait, wait);
    return (is_key_buf_empty(&btn_drv->gbtn_irq[minor]) ? 0 : POLLIN | POLLRDNORM);
}

static int button_drv_fasync(int fd, struct file *filp, int on)
{
    struct gbtn_irq *ops = (struct gbtn_irq *)filp->private_data;

	if (fasync_helper(fd, filp, on, &ops->fp) >= 0)
		return 0;
	else
		return -EIO;
}

static int button_drv_mmap(struct file *filp, struct vm_area_struct *vma)
{
    unsigned long phy = virt_to_phys(kbuf);

    vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);

    if (remap_pfn_range(vma, vma->vm_start, phy >> PAGE_SHIFT,
        vma->vm_end - vma->vm_start, vma->vm_page_prot)) {
        printk("mmap remap_pfn_range failed\n");
        return -ENOBUFS;
    }

    return 0;

}

static ssize_t button_drv_write (struct file *filp, const char __user *buf, size_t size, loff_t *off)
{
    int err;

    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    err = copy_from_user(kbuf, buf, MIN(1024, size));
    return MIN(1024, size);
}

/**
 * 1. 构造file_operations结构体 
 */
static struct file_operations button_drv_ops = {
    .owner   = THIS_MODULE,
    .read    = button_drv_read,
    .write   = button_drv_write,
    .open    = button_drv_open,
    .release = button_drv_release,
    .poll    = button_drv_poll,
    .fasync  = button_drv_fasync,
    .mmap    = button_drv_mmap,
};

/* 中断服务函数 */
static irqreturn_t gpio_btn_isr (int irq, void *dev_id)
{
    struct gbtn_irq *ops = dev_id;

    // printk("gpio_btn_isr key %d irq happened\n", ops->gpio);
    tasklet_schedule(&ops->tasklet);
    mod_timer(&ops->key_timer, jiffies + HZ / 50);
    schedule_work(&ops->wq);
    disable_irq_nosync(irq);
    
    return IRQ_WAKE_THREAD;
}

/* tasklet action function */
static void button_tasklet_func (unsigned long data)
{
    int val;
    struct gbtn_irq *ops = (struct gbtn_irq *)data;

    /* 读取按键的值 */
    val = gpiod_get_value(ops->gpiod);

    printk("button_tasklet_func key%d %d %d\n", ops->idx, ops->gpio, val);

}

static void button_work_func (struct work_struct *work)
{
    int val;
    struct gbtn_irq *ops = container_of(work, struct gbtn_irq, wq);

    /* 读取按键的值 */
    val = gpiod_get_value(ops->gpiod);
    printk("button_work_func: the process is %s pid %d\n", current->comm, current->pid);
    printk("button_work_func key%d %d %d\n", ops->idx, ops->gpio, val);
}

static irqreturn_t button_threaded_isr_fun (int irq, void *dev)
{
    int val;
    struct gbtn_irq *ops = dev;

    /* 读取按键的值 */
    val = gpiod_get_value(ops->gpiod);
    printk("button_threaded_isr_fun: the process is %s pid %d\n", current->comm, current->pid);
    printk("button_threaded_isr_fun key%d %d %d\n", ops->idx, ops->gpio, val);
    return IRQ_HANDLED;
}

/* platform_driver结构体的 probe成员函数实现 */
int btn_hw_drv_probe (struct platform_device *pdev)
{
    int i;
    int ret;
    int count;
    // enum of_gpio_flags flag;
    struct device_node *node = pdev->dev.of_node;

    /* 从设备节点获取gpio数量 */
    count = of_gpio_count(node);
    if (!count) {
        printk("%s %s line %d, there isn't any gpio available!\n", __FILE__, __FUNCTION__, __LINE__);
        return -EIO;
    }
    
    btn_drv = kzalloc(sizeof(struct button_drv), GFP_KERNEL);
    if (btn_drv == NULL) 
        return -ENOMEM;

    btn_drv->gbtn_irq = kzalloc(sizeof(struct gbtn_irq) * count, GFP_KERNEL);
    if (btn_drv->gbtn_irq == NULL)
        return -ENOMEM;

    for (i = 0; i < count; i++) {
        btn_drv->gbtn_irq[i].gpiod = gpiod_get_index_optional(&pdev->dev, NULL, i, GPIOD_ASIS);
        if (btn_drv->gbtn_irq[i].gpiod == NULL) {
            printk("%s %s line %d, gpiod_get_index_optional failed!\n", __FILE__, __FUNCTION__, __LINE__);
            return -EIO;
        }

        btn_drv->gbtn_irq[i].irq = gpiod_to_irq(btn_drv->gbtn_irq[i].gpiod);
        btn_drv->gbtn_irq[i].gpio = desc_to_gpio(btn_drv->gbtn_irq[i].gpiod);

        setup_timer(&btn_drv->gbtn_irq[i].key_timer, key_timer_expire, &btn_drv->gbtn_irq[i]);
        btn_drv->gbtn_irq[i].key_timer.expires = ~0;
        add_timer(&btn_drv->gbtn_irq[i].key_timer);

        tasklet_init(&btn_drv->gbtn_irq[i].tasklet, button_tasklet_func, &btn_drv->gbtn_irq[i]);

        INIT_WORK(&btn_drv->gbtn_irq[i].wq, button_work_func);

        btn_drv->gbtn_irq[i].idx = i;
    }

    for (i = 0; i < count; i++) 
        /* 申请irq中断, 将中断服务程序注册到上半部 */
        // ret = request_irq(btn_drv->gbtn_irq[i].irq, gpio_btn_isr, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, 
        //                  "gpio_btn", &btn_drv->gbtn_irq[i]);

        request_threaded_irq(btn_drv->gbtn_irq[i].irq, gpio_btn_isr, button_threaded_isr_fun, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "gpio_btn", &btn_drv->gbtn_irq[i]);
        

    /* 注册file_operationss结构体对象 -- button_drv_ops  */
    btn_drv->major = register_chrdev(btn_drv->major, "gbtn", &button_drv_ops);
    btn_drv->class = class_create(THIS_MODULE, "gbtn");
    if (IS_ERR(btn_drv->class)) {
        printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
        unregister_chrdev(btn_drv->major, "gbtn");
        return PTR_ERR(btn_drv->class);
    }

    for (i = 0; i < count; i++)
        device_create(btn_drv->class, NULL, MKDEV(btn_drv->major, i), NULL, "gbtn%d", i);

    btn_drv->count = count;
        
    return 0;
}

/* platform_driver结构体的 remove成员函数实现 */
int btn_hw_drv_remove(struct platform_device *pdev)
{
    int i;
    struct device_node *node = pdev->dev.of_node;
    int count = of_gpio_count(node);

    for (i = 0; i < count; i++) {
        device_destroy(btn_drv->class, MKDEV(btn_drv->major, i));
        free_irq(btn_drv->gbtn_irq[i].irq, &btn_drv->gbtn_irq[i]);
        del_timer(&btn_drv->gbtn_irq[i].key_timer);
        tasklet_kill(&btn_drv->gbtn_irq[i].tasklet);
    }
    class_destroy(btn_drv->class);
    unregister_chrdev(btn_drv->major, "gbtn");

    kfree(btn_drv);
    return 0;
}

/* 构造用于配置的设备属性 */
static const struct of_device_id gbtns_id[] = {
    {.compatible = "glen,gbtn"},
    { },
};

/* 构造(初始化)file_operations结构体 */
static struct platform_driver btn_hw_drv = {
    .driver = {
        .name = "gbtn",
        .of_match_table = gbtns_id,
    },
    .probe = btn_hw_drv_probe,
    .remove = btn_hw_drv_remove,
};

/* 初始化 */
static int __init button_drv_init(void)
{
    int ret;

    kbuf = kmalloc(bufsz, GFP_KERNEL);
    strcpy(kbuf, "button_drv_init()");

    ret = platform_driver_register(&btn_hw_drv);
    if (ret)
        pr_err("Unable to initialize button driver\n");
    else
        pr_info("The button driver is registered.\n");

    
    return 0;
}
module_init(button_drv_init);

static void __exit button_drv_exit(void)
{
    platform_driver_unregister(&btn_hw_drv);
    printk(" %s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    kfree(kbuf);
}
module_exit(button_drv_exit);

/* insert author information for module */
MODULE_AUTHOR("glen");
/* insert license for module */
MODULE_LICENSE("GPL");
 

probe函数采用了先获取按键节点数量,然后分别读取gpio描述符并通过其获取为gpio和irq号,并申请注册中断服务程序。

4.2 设备树文件(不作更改)

 		pinctrl_btn0:btn0 {
 			fsl,pins = <
 				MX6UL_PAD_UART1_CTS_B__GPIO1_IO18	0xF080	/* KEY0 */ 
 			>;
 		};
 
 		pinctrl_btn1:btn1 {
 			fsl,pins = <
                 MX6UL_PAD_GPIO1_IO03__GPIO1_IO03    0xF080	/* KEY1  此按键不存在 */
 			>;
 		};
     /* 在根节点下添加基于pinctrl的gbtns设备节点 */
     gbtns {
         compatible = "glen,gbtn";
         #address-cells = <1>;
 
         pinctrl-names = "default";
         pinctrl-0 = <&pinctrl_btn0 
 		             &pinctrl_btn1>;
 
         gpio-controller;
         #gpio-cells = <2>;
         gpios = <&gpio1 18 GPIO_ACTIVE_LOW /* button0 */
                  &gpio1 3 GPIO_ACTIVE_LOW>;   /* button1 */
 
     };
 
  • 取消了gpios前缀“xxx-",相应地,在驱动程序用gpiod_get_index_optional函数获取gpio描述符时,第2个形参 ”const char *con_id“ 传递NULL即可;

  • 将pinctrl-0、gpios属性值由 “<>,<>;” 改为 “<>;",效果是一样的

4.3 应用程序

应用程序文件button_drv_test.c提供:

  • 定义sig_fun信号处理函数并注册信号,以后APP收到SIGIO信号时,这个函数会被自动调用;
  • fcntl(fd, F_SETOWN, getpid()); 把APP的PID(进程ID)告诉驱动程序,这个调用不涉及驱动程序,在内核的文件系统层次记录PID;
  • oflags = fcntl(fd, F_GETFL); 读取驱动程序文件oflags
  • fcntl(fd, F_SETFL, oflags | FASYNC); 设置oflags里面的FASYNC位为1:当FASYNC位发生变化时,会导致驱动程序的fasync被调用
/*
 * 文件名   :  button_drv_test.c
 * 作者     :  glen
 * 描述     :  button_drv应用程序
 */

#include "stdio.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "stdlib.h"
#include "string.h"
#include "poll.h"
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

int fd;

static void sig_fun(int sig)
{
    char kval;
    read(fd, &kval, 1);
    printf("The glen button value is: %d!\n", kval);
}

/**
 * @brief   : main函数
 * @par     : argc  argv数组元素的个数
 *            argv  参数数组
 * @retval  : 0 成功    其它 失败
 */
int main(int argc, char *argv[])
{
    int ret;
    int oflags;
    char *filename;
    char kval;
    int i;

    char *buf;
    int len;
    char str[1024];

    if (argc != 2) {
        printf("Error Usage!\r\n");
        return -1;
    }

    signal(SIGIO, sig_fun);

    filename = argv[1];

    /* 打开驱动文件 */
    fd = open(filename, O_RDWR);
    if (fd < 0) {
        printf("Can't open file %s\r\n", filename);
        return -1;
    }

    //fcntl(fd, F_SETOWN, getpid());
    oflags = fcntl(fd, F_GETFL);
    fcntl(fd, F_SETFL, oflags  & ~O_NONBLOCK);

    /* 2. mmap 
	 * MAP_SHARED  : 多个APP都调用mmap映射同一块内存时, 对内存的修改大家都可以看到。
	 *               就是说多个APP、驱动程序实际上访问的都是同一块内存
	 * MAP_PRIVATE : 创建一个copy on write的私有映射。
	 *               当APP对该内存进行修改时,其他程序是看不到这些修改的。
	 *               就是当APP写内存时, 内核会先创建一个拷贝给这个APP, 
	 *               这个拷贝是这个APP私有的, 其他APP、驱动无法访问。
	 */
	buf =  mmap(NULL, 1024*8, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
	if (buf == MAP_FAILED) {
		printf("can not mmap file /dev/gbtn\n");
		return -1;
	}

	printf("mmap address = 0x%x\n", buf);
	printf("buf origin data = %s\n", buf); /* old */

	/* 3. write */
	strcpy(buf, "new");

	/* 4. read & compare */
	/* 对于MAP_SHARED映射:  str = "new" 
	 * 对于MAP_PRIVATE映射: str = "old" 
	 */
	read(fd, str, 1024);  
	if (strcmp(buf, str) == 0) {
		/* 对于MAP_SHARED映射,APP写的数据驱动可见
		 * APP和驱动访问的是同一个内存块
		 */
		printf("compare ok!\n");
	} else {
		/* 对于MAP_PRIVATE映射,APP写数据时, 是写入原来内存块的"拷贝"
		 */
		printf("compare err!\n");
		printf("str = %s!\n", str);  /* old */
		printf("buf = %s!\n", buf);  /* new */
	}

    while (1) {
        /*
        if (read(fd, &kval, 1) == 1)  
            printf("get button: 0x%x\n", kval);
        else 
            printf("get button: -1\n");
        */
        sleep(10);
    }

    munmap(buf, 1024 * 8);

    /* 关闭文件 */
    ret = close(fd);
    if (ret < 0) {
        printf("file %s close failed!\r\n", argv[1]);
        return -1;
    }
    return 0;
}

4.4 在alientek_linux_alpha开发板实测验证如下

/drv_module # insmod button_drv.ko
The button driver is registered.
/drv_module # ./btn_drv_test /dev/gbtn0
Set the button0 pin as input successfully!
mmap address = 0x76fc9000
buf origin data = button_drv_init()
button_tasklet_func key0 18 1
button_threaded_isr_fun: the process is irq/49-gpio_btn pid 80
button_threaded_isr_fun key0 18 1
button_work_func: the process is kworker/0:2 pid 45
button_work_func key0 18 1
button0 18 1
compare err!
str = !
buf = new!

/drv_module # ./btn_drv_test /dev/gbtn0
Set the button0 pin as input successfully!
mmap address = 0x76f96000
buf origin data = new
compare err!
str = !
buf = new!
button_tasklet_func key0 18 1
button_threaded_isr_fun: the process is irq/49-gpio_btn pid 80
button_threaded_isr_fun key0 18 1
button_work_func: the process is kworker/0:2 pid 45
button_work_func key0 18 1
button0 18 1

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值