驱动开发

说明

问题:自己在学习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字符设备驱动

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容