美文网首页
驱动开发

驱动开发

作者: 独倚烟花笑_70156 | 来源:发表于2022-09-20 12:57 被阅读0次

    说明

    问题:自己在学习linux的实话,发现很多时候,都是看别人的博客,没有阅读过代码,理解其中的机制,更没有进行实践过,或者通过代码验证过。

    作用:希望通过这个文档,建立一套模板,可以快速的实践,学到的东西,从而加深理解。

    一、驱动模板

    1.1 简单驱动

    hello.c

    #include <linux/init.h>

    #include <linux/module.h>

    #include <linux/kernel.h>

    static int hello_init(void)

    {

        printk(KERN_INFO "Hello - init\n");

        return 0;

    }

    static void hello_exit(void)

    {

        printk(KERN_INFO "Hello - exit\n");

    }

    module_init(hello_init);

    module_exit(hello_exit);

    MODULE_LICENSE("GPL");

    MODULE_AUTHOR("WEI_TEST");

    MODULE_DESCRIPTION("Hello World Module");

    Makefile

    PWD := $(shell pwd)

    ANDROID_DIR := /home/user/code/xxxx/LINUX/android

    KERNEL_OUT := $(ANDROID_DIR)/out/target/product/xxx/obj/KERNEL_OBJ

    MODULE_SIGN_FILE := $(KERNEL_OUT)/scripts/sign-file

    MODSECKEY := $(KERNEL_OUT)/certs/signing_key.pem

    MODPUBKEY := $(KERNEL_OUT)/certs/signing_key.x509

    obj-m := hello.o

    .PHONY : hello clean

    hello:

            make -C $(KERNEL_OUT) ARCH=arm64 CROSS_COMPILE=$(ANDROID_DIR)/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/bin/aarch64-linux-androidkernel- REAL_CC=$(ANDROID_DIR)/vendor/qcom/proprietary/llvm-arm-toolchain-ship/10.0/bin/clang CLANG_TRIPLE=aarch64-linux-gnu- M=$(PWD) modules

            cp hello.ko hello.ko.unsigned

            $(MODULE_SIGN_FILE) sha512 $(MODSECKEY) $(MODPUBKEY) hello.ko

    clean:

            @rm -f *.o *.ko *.order *.symvers *.unsigned *.mod.c

    安装与卸载:

    安装:

    insmod  hello.ko

    [ 1367.237628] Hello - init

    [ 1367.237695] Loaded hello: module init layout addresses range: 0xffffffac34959000-0xffffffac3495afff

    [ 1367.237709] hello: core layout addresses range: 0xffffffac3494d000-0xffffffac34950fff

    查看:

    lsmod

    Module                Size  Used by

    hello                  16384  0

    卸载:

    rmmod hello

    [ 1451.056954] Hello - exit

    [ 1451.059449] Unloaded hello: module core layout address range: 0xffffffac3494d000-0xffffffac34950fff

    modprobe可以根据依赖关系自动加载依赖的模块,在编译模块时在make … modules后加上modules_install可调用depmod生成模块依赖文件modules.dep

    modprobe –d 模块目录 hello

    modprobe –d 模块目录 –r hello

    ko文件格式:ELF格式,包含代码段(.txt),数据段(.data),符号表(.symtab),模块信息(.modinfo)等内容

    动态加载模块流程:

    调用insmod命令后,读取ko文件到内存

    调用init_module系统调用(syscall),传入模块的内存地址,模块长度,和参数地址。传入的地址都是用户态地址。

    init_module根据内核版本的不同实现的细节也有差别,主要的流程如下:

    检查运行权限( capabilities )

    从用户空间读取模块到内核空间

    检查是否是有效的ELF文件

    获取模块的基本信息,如名称,section索引等

    检查是否在模块黑名单,检查签名

    为各section分配内存

    将模块加入到模块列表

    根据内核符号表设置模块使用的符号对应的逻辑地址,然后刷新指令缓存

    从用户空间拷贝参数到内核

    链接模块到sysfs

    执行模块入口函数

    说明:

    如果是编译成ko,就按上面的步骤写makefile,如果在源码目录下编译,直接修改makefile就行。

    添加模块源代码的object文件到Makefile的obj-m变量中 (obj-m += hello.o)

    如果创建了Makefile,需要将目录添加到上级目录Makefile的obj-m变量中(obj-m += hello  hello为目录名称)

    1.2 字符驱动

    上面的驱动,看起来没啥价值,只能说,可以看下驱动的安装和卸载,还有什么事模块。有个概念。

    cdev_hello.c

    #include <linux/module.h>     

    #include <linux/init.h>       

    #include <linux/fs.h>

    #include <linux/device.h>

    #include <linux/uaccess.h>

    //#include <asm/uaccess.h>

    #include <asm/io.h>

    #include <linux/slab.h>

    struct led_desc{

        unsigned int dev_major;        //主设备号

        struct class* myclass;         

        struct device* mydev;        //创建设备文件的类和设备

        void *reg_virt_base;          //表示进行虚拟映射后寄存器地址的基准值

    }*led_dev;                        //声明全局的设备对象

    #define GPJ0CON 0xE0200240      // GPJ0CON寄存器物理地址

    #define GPJ0_SIZE  8

    static int kernel_val = 555;    //内核空间定义的一个值,可以看成是一段4字节的空间,模拟和用户空间进行数据交互

    ssize_t led_dev_read (struct file *filp, char __user *buf, size_t count, loff_t *fpos)

    {

        int ret;

        printk("-----%s-----\n",__FUNCTION__);

        ret = copy_to_user(buf,&kernel_val, count);

        if(ret > 0)

        {

            printk("copy_to_user error.\n");

            return -EFAULT;

        } 

        return 0;

    }

    ssize_t led_dev_write (struct file *filp, const char __user *buf, size_t count, loff_t *fpos)

    {

        int ret;

        int value;

        printk("-----%s-----\n",__FUNCTION__);

        ret = copy_from_user(&value,buf,count);

        if(ret > 0)

        {

            printk("copy_from_user error.\n");

            return -EFAULT;

        }

        if(value)        //根据用户空间传过来的value实现LED的亮灭

        {

            writel(readl(led_dev->reg_virt_base + 4) & ~(1<<3),led_dev->reg_virt_base + 4);    //led亮

        }

        else

        {

            writel(readl(led_dev->reg_virt_base + 4) | (1<<3),led_dev->reg_virt_base + 4);    //led灭

        }

        return 0;

    }

    int led_dev_open (struct inode *inode, struct file *filp)

    {

        printk("-----%s-----\n",__FUNCTION__);

        return 0;

    }

    int led_dev_close (struct inode *inode, struct file *filp)

    {

        printk("-----%s-----\n",__FUNCTION__);

        return 0;

    }

    const struct file_operations my_fops = {

        .open = led_dev_open,

        .read = led_dev_read,

        .write = led_dev_write,

        .release = led_dev_close,

    };

    static int __init led_dev_init(void)  //一般都是申请系统资源

        int ret;

        u32 value;

        //0-实例化全局的设备对象----分配空间

        led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL);

        if(led_dev == NULL)

        {

            printk(KERN_ERR "malloc error.\n");

            return -ENOMEM;

        }

        // 1-申请设备号

        led_dev->dev_major = register_chrdev(0, "led_dev_test", &my_fops);  //动态分配主设备号,且分配成功返回主设备号

        if(led_dev->dev_major < 0)

        {

            printk(KERN_ERR "register_chrdev error.\n");

            ret = -ENODEV;

            goto err_0;

        }

        // 2-创建设备结点

        led_dev->myclass = class_create(THIS_MODULE, "dev_class");

        if(IS_ERR(led_dev->myclass))  //IS_ERR判断指针是否出错

        {

            printk(KERN_ERR "class_create error.\n");

            ret = PTR_ERR(led_dev->myclass);  //PTR_ERR将指针的错误原因转换成错误码

            goto err_1;

        }

        led_dev->mydev = device_create(led_dev->myclass, NULL, MKDEV(led_dev->dev_major,0), NULL, "led%d",0);

        if(IS_ERR(led_dev->mydev)) 

        {

            printk(KERN_ERR "device_create error.\n");

            ret = PTR_ERR(led_dev->mydev);

            goto err_2;

        }

        // 3-硬件初始化

        //对地址进行映射

        led_dev->reg_virt_base = ioremap(GPJ0CON, GPJ0_SIZE);

        if(led_dev->reg_virt_base == NULL) 

        {

            printk(KERN_ERR "ioremap error.\n");

            ret = -ENOMEM;

            goto err_3;

        }

        //gpio的输出功能的配置

        value = readl(led_dev->reg_virt_base);

        value &= ~(0xf<<12);  //先清零

        value |= (0x1<<12);   

        writel(value,led_dev->reg_virt_base);  //设置GPJ0的[15:12]为输出模式

        return 0;

      //错误处理

        err_3:

            device_destroy(led_dev->myclass, MKDEV(led_dev->dev_major,0));

        err_2:

            class_destroy(led_dev->myclass);

        err_1:

            unregister_chrdev(led_dev->dev_major,"led_dev_test");

        err_0:

            kfree(led_dev);

        return 0;

    }

    static void __exit led_dev_exit(void)

    {

        //一般都是释放资源

        iounmap(led_dev->reg_virt_base);

        device_destroy(led_dev->myclass, MKDEV(led_dev->dev_major,0));

        class_destroy(led_dev->myclass);

        unregister_chrdev(led_dev->dev_major,"led_dev_test");

        kfree(led_dev);

        printk("--------%s-------\n",__FUNCTION__);

    }

    module_init(led_dev_init);

    module_exit(led_dev_exit);

    MODULE_LICENSE("GPL");              // 描述模块的许可证

    Makefile

    obj-m:=cdev_hello.o

    KERNELDIR:=/lib/modules/`uname -r`/build

    PWD :=$(shell pwd)

    modules:

    $(MAKE)  -C  $(KERNELDIR)  M=$(PWD)  modules

    clean:

    rm -rf *o *.mod.c *.order *.symvers *.ur-safe

    测试结果:

    电脑端:编译ok,安装也ok

    嵌入式端:编译ok,安装出现下面的错误。

    (这个错误原因是,在iomap的时候出错,因为每个板子的的io地址不一样,所以可能导致地址冲突,需要看手册确认io地址,这样才能操作硬件。)

    [  204.175630] ------------[ cut here ]------------

    [  204.175647] WARNING: CPU: 6 PID: 8273 at arch/arm64/mm/ioremap.c:58 __ioremap_caller+0xc4/0xcc

    [  204.175651] Modules linked in: hello(O+) wlan(O) cpe_lsm_dlkm(O) wcd937x_slave_dlkm(O) machine_dlkm(O) wcd937x_dlkm(O) wcd934x_dlkm(O) wcd9335_dlkm(O) mbhc_dlkm(O) wcd9xxx_dlkm(O) wcd_cpe_dlkm(O) tx_macro_dlkm(O) rx_macro_dlkm(O) va_macro_dlkm(O) wsa_macro_dlkm(O) swr_ctrl_dlkm(O) bolero_cdc_dlkm(O) wsa881x_dlkm(O) wcd_core_dlkm(O) stub_dlkm(O) wcd_spi_dlkm(O) hdmi_dlkm(O) swr_dlkm(O) pinctrl_wcd_dlkm(O) usf_dlkm(O) native_dlkm(O) platform_dlkm(O) q6_dlkm(O) adsp_loader_dlkm(O) apr_dlkm(O) snd_event_dlkm(O) q6_notifier_dlkm(O) q6_pdr_dlkm(O) wglink_dlkm(O) msm_11ad_proxy

    [  204.175722] CPU: 6 PID: 8273 Comm: insmod Tainted: G S        O    4.14.190+ #1

    [  204.175725] Hardware name: Qualcomm Technologies, Inc. trinket pm6125 + pmi632 Lenovo PVT1 (DT)

    [  204.175730] task: 00000000c84407f8 task.stack: 0000000020fdc8d7

    [  204.175735] pc : __ioremap_caller+0xc4/0xcc

    [  204.175739] lr : __ioremap_caller+0x60/0xcc

    [  204.175742] sp : ffffff80240ebad0 pstate : 00400145

    [  204.175745] x29: ffffff80240ebad0 x28: ffffff80240ebe18

    [  204.175754] x27: 00000000014080c0 x26: 0000000000000002

    [  204.175763] x25: 0000000000000002 x24: 0000000000000240

    [  204.175771] x23: 00000000e0200240 x22: 0000000000001000

    [  204.175779] x21: ffffff9f151c41ac x20: 00000000e0200000

    [  204.175787] x19: 0068000000000f07 x18: 0000000000000006

    [  204.175796] x17: 0000000000aab4cc x16: ffffff8022eaa648

    [  204.175804] x15: d29ced2031b099b6 x14: 88584c0fe2ffffff

    [  204.175812] x13: 0000000080000000 x12: 00000000ffc00000

    [  204.175820] x11: 0000000000000018 x10: 00000000ffffffff

    [  204.175828] x9 : 0000000000000001 x8 : 0000000000000000

    [  204.175836] x7 : bbbbbbbbbbbbbbbb x6 : 0000000000000040

    [  204.175844] x5 : 0000000000000000 x4 : 0000000000000001

    [  204.175853] x3 : ffffff9f151c41ac x2 : 0068000000000f07

    [  204.175861] x1 : 00000000e0200000 x0 : 0000000000000000

    [  204.175869] \x0aPC: 0xffffff8022eaa714:

    [  204.175872] a714  aa1303e3 8b1502c1 aa1503e0 944736ff 34000120 aa1503e0 9406a915 aa1f03e0

    [  204.175900] a734  a9434ff4 a94257f6 a9415ff8 a8c47bfd d65f03c0 8b150300 17fffffa aa1f03e0

    [  204.175928] a754  d4210000 17fffff7 a9be7bfd f9000bf3 910003fd aa0003f3 d503201f 9274ce73

    [  204.175955] a774  aa1303e0 9406a145 34000060 aa1303e0 9406a8ff f9400bf3 a8c27bfd d65f03c0

    [  204.175982] \x0aLR: 0xffffff8022eaa6b0:

    [  204.175985] a6b0  aa0003f7 d503201f 92402ef8 8b180288 9274cef4 913ffd08 9274cd16 8b160288

    [  204.176012] a6d0  d1000508 d370fd09 f100013f fa400ac4 fa541100 54000263 d34cfee0 97fffddd

    [  204.176039] a6f0  35000300 aa1603e0 52800021 aa1503e2 9406a84b b4000180 f9400415 aa1403e2

    [  204.176066] a710  f9001814 aa1303e3 8b1502c1 aa1503e0 944736ff 34000120 aa1503e0 9406a915

    [  204.176094] \x0aSP: 0xffffff80240eba90:

    [  204.176096] ba90  22eaa754 ffffff80 00400145 00000000 e0200000 00000000 000e0200 00000000

    [  204.176124] bab0  ffffffff 0000007f 22eaa6f0 ffffff80 240ebad0 ffffff80 22eaa754 ffffff80

    [  204.176151] bad0  240ebb10 ffffff80 22eaa680 ffffff80 00000002 00000000 00000000 00000000

    [  204.176178] baf0  e0200240 00000000 00000008 00000000 00000f07 00680000 151c41ac ffffff9f

    [  204.176205]

    [  204.176209] Call trace:

    [  204.176214] __ioremap_caller+0xc4/0xcc

    [  204.176218] __ioremap+0x38/0x48

    [  204.176226] init_module+0x1ac/0x1000 [hello]

    [  204.176232] do_one_initcall+0xe0/0x1b8

    [  204.176237] do_init_module+0x60/0x1f8

    [  204.176241] load_module+0x2424/0x27e4

    [  204.176245] SyS_finit_module+0xc0/0x114

    [  204.176248] el0_svc_naked+0x34/0x38

    [  204.176252] ---[ end trace 16b224f349a8d83e ]---

    [  204.176256] ioremap error.

    [  204.177299] Loaded hello: module init layout addresses range: 0xffffff9f151c4000-0xffffff9f151c5fff

    [  204.177305] hello: core layout addresses range: 0xffffff9f151bf000-0xffffff9f151c2fff

    二、应用模板

    2.1 源代码

    #include <sys/types.h>

    #include <sys/stat.h>

    #include <fcntl.h>

    #include <stdio.h>

    #include <stdlib.h>

    #include <string.h>

    #include <unistd.h>

    void print_info()

    {

            printf("this file is %s,line is %d\n",__FILE__,__LINE__);

            printf("this file build time is:%s %s\n",__DATE__,__TIME__);

    }

    int main(int argc, char const *argv[])

    {

            int fd;

            int value;

            printf("this is a test\n");

            print_info();

            fd = open("/dev/led0",O_RDWR);

            //根据 device_create的最后一个参数确定设备结点的名字

            if(fd < 0)

                    {

                            perror("open");

                            exit(-1);

                    }

            read(fd,&value,4);

            printf("---USER----:%d\n",value);

            //应用程序去控制LED的亮灭

            while(1)

            {

                    value = 0;

                    write(fd,&value,4);

                    sleep(1);

                    value = 1;

                    write(fd,&value,4);

                    sleep(1);

                    }

            close(fd);

            return 0;

    }

    这里面我用了编译器相关的宏__FILE__  __LINE__  __DATE__  __TIME__,可以用于调试,非常方便。

    2.2 交叉编译器的安装

    这部分,主要看正点原子的教程,我只简单的写一下步骤。

    Linaro Releases下载:gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar

    sudo mkdir /usr/local/arm

    sudo cp gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz /usr/local/arm/ -f

    sudo tar -vxf gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz

    sudo vi /etc/profile

    export PATH=$PATH:/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin

    sudo apt-get install lsb-core lib32stdc++6  (在使用交叉编译器之前还需要安装一下其它的库)

    arm-linux-gnueabihf-gcc -v  有显示gcc的版本号,就代表交叉编译环境安装搭建成功。

    2.3 测试结果

    makefile

    ARCH ?= x86

    ifeq ($(ARCH),x86) #注意看!!!ifeq后边有空格,没有不对

        cc=gcc

    else

        cc=arm-linux-gnueabihf-gcc

    endif

    TARGET=test

    OBJS=test.o

    $(TARGET):$(OBJS)

            $(cc) $^ -o $@ --static

    %.o:%.c

            $(cc) -c $< -o $@

    .PHONY:clean

    clean:

            rm $(TARGET) $(OBJS)

    注意:这里,我在生成可执行文件的时候,加了参数--static,目前还不知道为什么,因为只有这样,编译出的可执行文件在嵌入式端能使用

    2.3.1 电脑端

    编译:gcc  xxx.c

    测试:ok

    2.3.2 嵌入式端

    编译:arm-linux-gnueabihf-gcc -o helloworld3 test3.c -static    (暂且还不知道,这个staic 是什么意思,如果不加它,编译出来的程序无法运行)

    测试:ok

    待补充:

    嵌入式端makefile的编写

    嵌入式端如何编译库的呢?如何使用库的呢?

    三、hal库的引入

    3.1 引入概述

    上面的应用程序,就可以操作操作到硬件了,似乎可以万事大吉了。那么如果出现以下情况呢?

    如果驱动程序的节点有变化,那用户的程序是不是要跟着变化?

    由于linux是开源的,对于数据的一些处理可能需要用到算法,如果写在驱动里也必须开源,但我这些算法不想让别人知道,那怎么办?

    遇到这些情况,该怎么办?我们可以将上面的部分抽取出来,编译成库,用户只需要我提供给他的接口就行,所以就有了HAL 库的出现。

    3.2 hal 库的几个点

    用头文件暴露接口给用户。

    内部使用的函数,需要加限制。

    3.3 简单的例子

    四、什么是分层

    五、再讲驱动

    5.1 字符设备驱动开发框架

    要素:

    必须有一个设备号,用于在众多设备驱动中进行区分。

    用户必须知道设备驱动对应的设备节点。(设备文件)

    对设备操作,就是对文件操作,用户空间的open、read等函数是和驱动中的open、read对应的。

    5.2 IO 有哪些接口

    驱动填充这些接口,用户程序操作这些接口,从而就操作了硬件。

    5.3 用户程序和硬件的交互

    5.3.1 用户空间和内核空间的数据交互

    copy_to_user(void __user * to, const void * from, unsigned long n)

    copy_from_user(void * to, const void __user * from, unsigned long n)

    5.3.2 用户程序操作硬件

    内核驱动中是通过虚拟地址转换操作寄存器:

    void* ioremap(cookie, size);    //映射函数

    void iounmap(void __iomen*addr);  //去映射函数

    映射函数参数:

    参数1:物理地址

    参数2:长度

    返回值:虚拟地址

    去映射函数参数:映射成功的虚拟地址

    头文件包含:#include

    参考链接

    Linux驱动开发之字符设备驱动_big__C的博客-CSDN博客_linux字符设备驱动

    相关文章

      网友评论

          本文标题:驱动开发

          本文链接:https://www.haomeiwen.com/subject/htjbortx.html