0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

linux系统和驱动中按键驱动的编写详解

454398 来源:AI加速 作者:AI加速 2020-11-21 11:03 次阅读

引言

随着深度学习5G的应用,对FPGA的功能要求越来越多。因此近几年FPGA大厂纷纷将自己的器件集成了更多的内核,比如赛灵思的zynq系列就集成了armGPU,PCIE,射频处理模块等等,用于满足各种各样的需求。出身FPGA的工程师们也必须拥抱这些变化,不仅仅要精通FPGA开发,还需要了解其他方面的知识。比如基于zynq的开发者,就需要了解arm,linux驱动以及linux系统。做深度学习加速的还需要了解深度学习网络以及网络压缩等知识。学习这些知识会让你的眼界更加开阔,会站得高看得远,在开始一项任务的时候不再是盲人摸象,而是高瞻远瞩,把握全局。

这是我介绍linux系统和驱动的第4篇文章,如有不恰当的地方欢迎指正,因为本人也是处于学习入门阶段。三人行,必有我师焉。讨论的多了,问题也就清晰了。这一篇主要介绍按键驱动的编写,了解中断的处理过程,以及设备树的修改。

1. vivado工程搭建

工程搭建很简单,就是配置完zynq核的外设后,增加一个axi_gpio模块,作为外部按键的接口。虽然按键并不是直接连接到arm的IO上,但是axi_gpio也被映射到zynq系统的内存空间中,linux驱动通过读写key对应的映射内存来控制和检测。要检测到key被按下我们必须开启中断,因此axi_gpio模块设置如下图。Address editor是gpio的对应的内存空间。Zynq已经为不同外设类型分配了可选的内存映射,通常工程建立完后,由软件工具自行分配就好了。如果一些外设内存有冲突,是无法生成hdf的。




配置完成后,进行管脚约束,然后综合实现,生成bit文件。再导出hdf文件,打开SDK来生成fsbl,u-boot还使用前几篇介绍驱动中的u-boot。U-boot通常不会因为驱动的新增而修改。但是由于增加了key按键,我们需要去设备树中修改对应的配置。

具体如何修改设备树,可以到linux驱动文件夹Documentation/devicetree中去寻找对应的外设文件,其中有设备树修改的介绍。Gpio的修改可以到gpio文件夹下的gpio-zynq.txt查看。其基本形式为:

         gpio@e000a000 { #gpio-cells = <2>;
                   compatible = "xlnx,zynq-gpio-1.0";
                   clocks = <&clkc 42>;
                   gpio-controller;
                   interrupt-parent = <&intc>;
                   interrupts = <0 20 4>;
                   interrupt-controller;
                   #interrupt-cells = <2>;
                   reg = <0xe000a000 0x1000>; };

其中compatible主要是用于linux驱动去匹配设备树中相应的节点,后面我们会介绍,这个名字和那一块程序有关。主要是配置中断,其中interrupts-cell指定了interrupts有多少个属性。Interrupts的第一个属性是中断类型,第二个是中断号,最后一个表示触发类型:高电平触发、低电平触发、上升沿触发和下降沿触发四种类型。Interrupt-parent是中断所属的中断控制器。我们在SDK中产生了设备树,我们看到按键的相应节点位于amba_pl节点下,其中amba_pl是PL端的总线节点,而amba是PS端的总线节点,修改pl.dtsi中的gpio内容:

我们改了compitable的内容,同时要关注inerrupts,xlnx,all-inputs,xlnx,gpio-width这些属性。Gpio-width是宽度,all-inputs是表示为输入。

设备树修改完后就可以编译设备树文件,然后用fsbl,u-boot,设备树来制作boot.bin了。放到SD卡,启动linux系统。接下来进入关键环节,key驱动的编写。

2. 按键驱动代码剖析

对于一个刚刚入门的人来说,其实了解了驱动的基本框架就好了。每个驱动都按照它的框架进行编写和修改。能理解驱动的各个模块功能,在驱动调试或者编写中就能有的放矢。一个简单的驱动的构成也很复杂,代码也很多,篇幅有限,我只介绍主要部分。

1)platform框架

Platform是一种虚拟的平台,提供了驱动和具体硬件交互的接口。Platform_device类似于虚拟的总线,IIC,LCD,GPIO等外设都可以看做platform_device,通过它可以遍历所有的总线设备,而对应的驱动就是platform_driver。基本流程是:先注册platform_device,再注册platform_driver,然后匹配设备和驱动,最后注册整个驱动。

在linux3以前的版本,需要定义platform_device结构体,然后通过platform_device_register函数来注册设备。但是linux3.0以后出现了设备树,内核函数of_platform_default_populate_init会在内核启动后遍历设备树,自动注册每个节点对应的设备。因此只需要修改设备树参数就行了。首先看这个结构体:

static const struct of_device_id key_of_match[] __devinitdata={
         {.compatible="xlnx,gpio-keys",},
         {/*end of list*/},
};

这实际上定义了设备的匹配号,compatible就是在设备树节点axi-gpio中对应的节点匹配名称。我们只要让compitable和设备树中对应节点的值匹配上就可以将节点对应的设备注册到总线上了。

platform_driver用于对设备的搜索和配置,主要就是去解析设备树,根据设备树中节点信息来填充设备结构体对应信息或者直接对设备完成配置。

static struct platform_driver key_driver={
         .driver={
                   .name=DRIVER_NAME,
                   .owner=THIS_MODULE,
                   .of_match_table=key_of_match,
         },
         .probe=key_probe,
         .remove=key_remove,
};

我们主要关注其三个变量,of_match_table就是of_device_id结构体定义的,用来匹配节点。Probe函数用来解析节点,配置设备。Remove主要是释放在probe中使用的资源等。

编写key驱动主要就是去填充probe和remove两个函数。

来看probe函数是如何查找到设备的一些属性的,比如我们要确定key键的数量,那么我们可以这样来做:

         if(of_property_read_u32(node, "xlnx,gpio-width", &width)){
                   printk(KERN_ERR "get the gpio-width/n");
         }

通过匹配“xlnx,gpio-width”来获得key的位宽,这个属性就在设备中定义的。

如果我们要操作key,需要获得key设备的内存映射空间,这个可以通过函数platform_get_resource函数来完成。

mem=platform_get_resource(pdev, IORESOURCE_MEM, 0);
         if(!mem){
                   printk(KERN_ERR "get memory resource/n");
                   return -ENODEV;
         }

第一个参数pdev是platform_device结构体,在进入probe函数之前就已经被注册了,其指向的就是key对应的设备。第二个参数是类型,主要有IORESOURCE_MEM, IORESOURCE_IRQ等。最后一个参数是号码,指示platform_device结构体中不同的资源类型,即IORESOURCE类型。

以上获得的mem就是在设备树中由reg指定的内存映射:

reg = <0x41210000 0x10000>;

中断的获得可以通过函数:

         r_irq=irq_of_parse_and_map(node, 0);
         if(!r_irq){
                   printk(KERN_ERR "get interrupt/n");
         }

其中device_node就是设备节点,在platform_device注册的时候,含有该节点,所以可以通过该结构体获得。第二个参数表示一个设备树节点有多个中断时的索引

通过设备树获得了硬件信息后,我们将其填充到key_device中,key_device定义如下:

struct key_dev{
         struct cdev dev;
         struct work_struct work;                 
         int irq;
         int major;
         unsigned long start_addr;
         unsigned long size;
         void __iomem *baseaddr;
         int width;
         int inout;
         int key_prs;
};

其中irq为中断号,cdev是字符设备结构体,因为key等属于字符设备。填充如下:

        lp->start_addr=mem->start;
         lp->size=mem->end-mem->start;
         lp->irq=r_irq;
         lp->width=width;

2)中断处理

在platform中我们谈到了对中断号的获取,那么取得了中断号之后如何来检测中断事件呢?中断处理过程可以被分成两部分:顶半部和底半步。顶半部主要处理硬件上比较紧急的事物,比如检测中断,底半部用于处理中断产生之后需要进行的事务处理。在底半部处理过程中不会耽误检测下一个中断。这两个部分不是绝对的,也可以只有一个部分。

驱动中首先需要定义一个中断函数,用于中断产生后进行的操作。然后申请中断,实现函数:

err=request_irq(k_dev->irq,key_interrupt, IRQF_SHARED|IRQF_TRIGGER_RISING, DRIVER_NAME, k_dev);

第一个是中断号,第二个为中断处理函数,第三个参数为中断产生类型,上升沿下降沿一类,第四个为名字,可以命名中断,最后一个是设备结构体。

释放中断就通过free_irq(unsigned int irq, void *dev_id)来完成。

实现中断底半部处理机制主要有tasklet,工作队列,软中断和线程化irq。中断机制较为复杂,任何一种机制都可以让你竭尽脑汁。入门者还是循序渐进,所以我也只用了一种简单的方法。类似锁机制,我们定义一个事件:

static DECLARE_WAIT_QUEUE_HEAD(press_queue);

然后在中断函数中唤醒这个事件,在其他函数中可以通过等待这个事件来进行中断处理。我们的中断函数为:

static irqreturn_t key_interrupt(int irq, void *dev_id)
{
         struct key_dev *dev=dev_id;
         dev->key_prs++;
         printk(KERN_INFO "interruptted/n");
         wake_up_interruptible(&press_queue);
         return IRQ_HANDLED;
}

而等待该事件放在key_read函数中:

ssize_t key_read(struct file *filp, char __user *buf, size_t count, loff_t *fops)
{
         int err;     
         struct key_dev *dev=filp->private_data;        
         wait_event_interruptible(press_queue, event_press);
         event_press=0;
         err=copy_to_user(buf, &dev->key_prs, count);
         return err ? -EFAULT : 0;
}

3)文件结构

Linux一切皆文件,任何驱动最终都被封装为一个文件,用户空间通过读写文件来操作驱动。文件操作包括打开,关闭,读和写等。我们不做具体介绍,简单列出文件结构体为:

struct file_operations key_fops={
         .owner=THIS_MODULE,
         .open=key_open,
         .read=key_read,
         .release=key_close,
};

总结

对以上进行总结就是:
1) 首先进行设备树节点属性修改;
2) 填充platform框架下的probe,remove等函数,并定义of_device_id和platform_driver结构体;
3) 申请中断,释放中断,编写中断函数等;
4) 填充文件结构,编写open,close,read等函数;

编辑:hfy


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • FPGA
    +关注

    关注

    1629

    文章

    21736

    浏览量

    603270
  • Linux
    +关注

    关注

    87

    文章

    11304

    浏览量

    209467
  • 5G
    5G
    +关注

    关注

    1354

    文章

    48453

    浏览量

    564202
  • 深度学习
    +关注

    关注

    73

    文章

    5503

    浏览量

    121154
收藏 人收藏

    评论

    相关推荐

    Linux系统驱动格式基本编写方法

    今天主要和大家聊一聊,编写Linux驱动格式与方法。
    发表于 12-02 09:34 575次阅读

    Linux模块相关命令 Linux驱动模块的编写与挂载

    Linux模块相关命令 Linux驱动模块的编写与挂载
    发表于 10-01 12:20 479次阅读
    <b class='flag-5'>Linux</b>模块相关命令 <b class='flag-5'>Linux</b><b class='flag-5'>驱动</b>模块的<b class='flag-5'>编写</b>与挂载

    实时系统VxWorks下设备驱动程序的编写详解

    实时系统VxWorks下设备驱动程序的编写详解
    发表于 03-29 12:26 15次下载

    Linux设备驱动开发详解》第17章、Linux音频设备驱动

    Linux设备驱动开发详解》第17章、Linux音频设备驱动
    发表于 10-27 11:14 17次下载
    《<b class='flag-5'>Linux</b>设备<b class='flag-5'>驱动</b>开发<b class='flag-5'>详解</b>》第17章、<b class='flag-5'>Linux</b>音频设备<b class='flag-5'>驱动</b>

    Linux设备驱动开发详解》第16章、Linux网络设备驱动

    Linux设备驱动开发详解》第16章、Linux网络设备驱动
    发表于 10-27 11:17 5次下载
    《<b class='flag-5'>Linux</b>设备<b class='flag-5'>驱动</b>开发<b class='flag-5'>详解</b>》第16章、<b class='flag-5'>Linux</b>网络设备<b class='flag-5'>驱动</b>

    Linux设备驱动开发详解》第14章、Linux终端设备驱动

    Linux设备驱动开发详解》第14章、Linux终端设备驱动
    发表于 10-27 11:22 8次下载
    《<b class='flag-5'>Linux</b>设备<b class='flag-5'>驱动</b>开发<b class='flag-5'>详解</b>》第14章、<b class='flag-5'>Linux</b>终端设备<b class='flag-5'>驱动</b>

    Linux设备驱动开发详解》第13章、Linux块设备驱动

    Linux设备驱动开发详解》第13章、Linux块设备驱动
    发表于 10-27 11:24 18次下载
    《<b class='flag-5'>Linux</b>设备<b class='flag-5'>驱动</b>开发<b class='flag-5'>详解</b>》第13章、<b class='flag-5'>Linux</b>块设备<b class='flag-5'>驱动</b>

    Linux设备驱动开发详解》第9章、Linux设备驱动的异步通知与异步IO

    Linux设备驱动开发详解》第9章、Linux设备驱动的异步通知与异步IO
    发表于 10-27 11:33 0次下载
    《<b class='flag-5'>Linux</b>设备<b class='flag-5'>驱动</b>开发<b class='flag-5'>详解</b>》第9章、<b class='flag-5'>Linux</b>设备<b class='flag-5'>驱动</b><b class='flag-5'>中</b>的异步通知与异步IO

    Linux设备驱动开发详解》第8章、Linux设备驱动的阻塞与非阻塞IO

    Linux设备驱动开发详解》第8章、Linux设备驱动的阻塞与非阻塞IO
    发表于 10-27 11:35 9次下载
    《<b class='flag-5'>Linux</b>设备<b class='flag-5'>驱动</b>开发<b class='flag-5'>详解</b>》第8章、<b class='flag-5'>Linux</b>设备<b class='flag-5'>驱动</b><b class='flag-5'>中</b>的阻塞与非阻塞IO

    Linux设备驱动开发详解》第7章、Linux设备驱动的并发控制

    Linux设备驱动开发详解》第7章、Linux设备驱动的并发控制
    发表于 10-27 11:37 10次下载
    《<b class='flag-5'>Linux</b>设备<b class='flag-5'>驱动</b>开发<b class='flag-5'>详解</b>》第7章、<b class='flag-5'>Linux</b>设备<b class='flag-5'>驱动</b><b class='flag-5'>中</b>的并发控制

    Linux设备驱动开发详解》第5章、Linux文件系统与设备文件系统

    Linux设备驱动开发详解》第5章、Linux文件系统与设备文件系统
    发表于 10-27 14:13 0次下载
    《<b class='flag-5'>Linux</b>设备<b class='flag-5'>驱动</b>开发<b class='flag-5'>详解</b>》第5章、<b class='flag-5'>Linux</b>文件<b class='flag-5'>系统</b>与设备文件<b class='flag-5'>系统</b>

    如何编写Linux 下Nand Flash驱动

    如何编写Linux 下Nand Flash驱动
    发表于 10-30 08:36 15次下载
    如何<b class='flag-5'>编写</b><b class='flag-5'>Linux</b> 下Nand Flash<b class='flag-5'>驱动</b>

    Linux系统网络驱动程序的编写

    驱动程序编写 一.Linux系统设备驱动程序概述 1.1 Linux设备
    发表于 11-07 10:40 0次下载

    Linux驱动开发-编写按键驱动

    这篇文章介绍,如何使用杂项设备框架编写一个简单的按键驱动,完成编写、编译、安装、测试等流程,了解一个杂项字符设备驱动的开发流程。
    的头像 发表于 09-17 15:08 1652次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>驱动</b>开发-<b class='flag-5'>编写</b><b class='flag-5'>按键</b><b class='flag-5'>驱动</b>

    Linux设备驱动开发详解

    Linux设备驱动开发详解
    发表于 10-28 11:03 50次下载