• 驱动开发之模块与外部编译


    驱动开发一:

    概要:

    1、模块、外部编译
    2、字符设备框架(函数接口和结构体的关系)
    3、字符设备框架、platform框架
    4、设备树、led驱动、蜂鸣器驱动
    5、内核中断子系统,按键驱动,中断上下半部。
    6、adc驱动,内核的IO模型(阻塞、非阻塞、异步通知、多路复用)
    7、I2C总线驱动、I2C设备驱动
    8、输入子系统

    知识补充:追内核:

    make tags 
    vi -t xxx

    一、什么是驱动?driver老司机

      可以操作硬件,同时还会给应用程序提供交互的接口。

    二、上层的程序如何操作硬件

    1.系统调用:本质上是一个函数接口
    2.函数的声明:存放在指定头文件中
    3.函数的定义:在内核空间中
    4.函数的调用:应用程序中使用

    三、模块基本特性

    1、什么是模块?

    运行在内核空间中的一段代码

               应用程序                   模块
    运行空间            用户空间                内核空间
    入口函数            main               加载函数
    调用接口      库函数或者系统调用       内核函数(主要是fs提供)
    空间释放              自动释放               手动释放

    模块不是驱动,它是实现驱动的一种方法
    在内核中驱动、文件系统、网络协议栈都可以用模块来实现

    2、模块的三要素:模块的声明、加载函数、卸载函数

    模块的声明:MODULE_LICENSE("协议名称");
    常见的协议:GPL BSD
    加载函数:
    -------->默认加载函数
    1 int init_module(void);//源码是由驱动的开发者定义的。 
    -------->自定义加载函数 

    297 #define module_init(initfn) 
    298 static inline initcall_t __inittest(void) 
    299 { return initfn; } 
    300 int init_module(void) __attribute__((alias(#initfn)));
    /*给默认加载函数去别名,叫做initfn*/
    1 分析:
    2 int a[2][3];
    3 int (*p)[3]; <==> int (*)[3] p;
    4 
    5 int (*p)(void) <==> int (*)(void) p; 
    6 
    7 int (*initcall_t)(void);<==> typedef int (*)(void) initcall_t 函数指针类型
    8 initfn是一个函数名,类型和initcall_t类型一致。
    9 initfn的返回值为int,形参为void

    卸载函数:
    --------->默认卸载函数

    void cleanup_module(void);

    --------->自定义卸载函数

    1 303 #define module_exit(exitfn) 
    2 304 static inline exitcall_t __exittest(void) //inline 内联函数,不进行入栈出栈操作,操作几次占几份内存(避免循环使用)
    3 305 { return exitfn; } 
    4 306 void cleanup_module(void) __attribute__((alias(#exitfn)));
    5 
    6 typedef void (*exitcall_t)(void);

    3、模块的编译方法

    内部编译:驱动源码存放在内核的指定目录下进行编译

     1 a.cp demo.c drivers/char 
     2 b.vi drivers/char/Kconfig 
     3 ----->添加选项
     4         config DEMO
     5         tristate "选项名称"
     6 c.vi drivers/char/Makefile
     7         添加:obj-$(CONFIG_DEMO) += demo.o 
     8 d.进入menuconfig中,找到选项选为M
     9 e.make modules
    10 f.cp drivers/char/demo.ko /rootfs 
    11 g.开发板启动后在开发板上执行insmod  demo.ko                         

    外部编译:驱动源码不在内核指定目录下(比内部编译方法方便)

     1 #include <linux/init.h>
     2 #include <linux/module.h>
     3 MODULE_LICENSE("GPL");
     4 
     5 //如果不使用__init加载函数直接被编译到.text分段中
     6 //如果使用__init加载函数会被编译到.text.init分段中
     7 int __init demo_init(void)//自定义加载函数
     8 {
     9 
    10     printk("demo_init
    ");
    11     return 0;
    12 }
    13 module_init(demo_init);//给默认加载函数取别名
    14 
    15 //如果不使用__exit,当将驱动代码直接编译到内核中时,卸载函数也会参与编译
    16 //如果使用__exit,当驱动代码直接编译到内核中,卸载函数不会参与编译
    17 void __exit demo_exit(void)
    18 {
    19     printk("demo_exit
    ");
    20 }
    21 module_exit(demo_exit);

    1、自己写Makefile

    2、调用到内核提供的模块编译方法
    3、通知内核模块编译方法哪些源文件参与编译

    内核顶层目录Makefile中:

    1248 # The following are the only valid targets when building external
    1249 # modules.
    1250 # make M=dir clean Delete all automatically generated files
    1251 # make M=dir modules Make all modules in specified dir 
    1252 # make M=dir Same as 'make M=dir modules'

    自己的Makefile:

     1 ifeq ($(KERNELRELEASE),)                      /*避免死循环执行makefile*/
     2 PWD = $(shell pwd)                          /*当前路径模块*/
     3 #KERNEL_DIR = /home/linux/linux-3.14/         /*用这个目录,需要将模块文件拷贝到开发板上执行*/
     4 KERNEL_DIR = /lib/modules/$(shell uname -r)/build/      /*需要在ubuntu中使用模块文件*/
     5 
     6 7 modules:
     8     make -C $(KERNEL_DIR) M=$(PWD) modules     /*-C进入路径KERNEL_DIR,找到寻找modules目标,最后回来*/
     9 

    11 clean:

    12   make -C $(KERNEL_DIR) M=$(PWD) clean

    13 else
    14
    obj-m += demo1.o //(-m模块 -y -n)
    //xxx-objs := ogj1.o,obj2.o
    //obj-m += xxx.o (打包生成xxx.ko文件)
    15 endif

    模块符号表导出:
    符号本质就是一个函数名或者变量名

    1 EXPORT_SYMBOL_GPL();
    2 EXPORT_SYMBOL();
    3 功能:将符号信息存放到Module.symvers文件中

    假设B模块要使用A模块中的一个函数。

    1、模块A的函数下调用EXPORT_SYMBOL_GPL(函数名);
    2、编译模块A
    3、加载模块A
    4、拷贝模块A的Module.symvers文件给模块B
    5、编译模块B
    6、加载模块B

    运行过程:

    1、执行自己的Makefile
    2、进入内核顶层目录的Makefile,寻找modules目标
    3、进入scripts/Makefile.modpost
    4、回到自己的Makefile

     

    4、模块的命令

    1 加载模块:insmod xxx.ko
    
    2 卸载模块:rmmod xxx
    
    3 dmesg 显示内核打印信息到终端上
    
    4 dmesg -c清空内核打印信息 

    printk(printf)的级别问题:

      消息级别: 0 1 2 3 4 5 6 7
      控制台级别: 1 2 3 4 5 6 7 8
        ----------》数字越小级别越高《-------------------
        如果需要直接打印数据到终端上,那么必须保证消息级别大于控制台级别

    ubuntu内核:      4(当前控制台级别)      4(当前消息级别)     1(控制台级别的最小值)     7(默认的控制台级别)
    Linux—3.14内核: 7 4 1 7

    地址映射函数:
    static
    inline void __iomem *ioremap(phys_addr_t offset, unsigned long size)
    功能:地址映射
    参数1:物理地址
    参数2:映射的字节数
    返回值:虚拟地址

    /----------------------------------------作业部分-------------------------------------/

    实验点亮exynos4412-fs4412的led2

    1.vi dome.c

      1 #include <linux/module.h>
      2 #include <linux/init.h>
      3 #include <asm/io.h>    /* vi -t 追到两个相关头文件(/include/asm-generic/io.h)如果使用此头文件,会报错*/
      4 MODULE_LICENSE("GPL");
      5 int __init demo_init(void)
      6 {
      7     void __iomem *CON = (void __iomem*)ioremap(0x11000c20,4);
      8     void __iomem *DAT = (void __iomem*)ioremap(0x11000c24,4); //也可通过地址偏移来实现(void *DAT = CON + 4)  
      9     //*CON = (*CON & (~(0xf << 0))) | (1 << 0);
         writel(readl(CON &(~(0xf << 0))) | (1 << 0)),CON);//驱动程序的写法,(从内存映射的i/o空间读/写数据)
    10 //*DAT = *DAT | (1 << 0);
         writel(readl(DAT | (1 << 0)),DAT);  //readl中--->l:4byte w:2byte b:1byte
    11 printk("This is sb! "); 12 return 0; 13 } 14 module_init(demo_init); 15 16 void __exit demo_exit(void) 17 { 18    19 } 20 module_exit(demo_exit);

    2.vi makefile

      1 ifeq ($(KERNELRELEASE),)
      2 PWD = $(shell pwd)
      3 KERNEL_DIR = /home/linux/linux-3.14/
      4 modules:
      5     make -C $(KERNEL_DIR) M=$(PWD) modules
      6 clean:
      7     make -C $(KERNEL_DIR) M=$(PWD) clean
      8 else
      9 obj-m += demo.o                                                      
     10 endif

    3.make 

    linux@ubuntu:~/lxq/class/drivers/1day$ make
    make -C /home/linux/linux-3.14/  M=/home/linux/lxq/class/drivers/1day modules
    make[1]: Entering directory `/home/linux/linux-3.14'
      CC [M]  /home/linux/lxq/class/drivers/1day/demo.o
      Building modules, stage 2.
      MODPOST 1 modules
      CC      /home/linux/lxq/class/drivers/1day/demo.mod.o
      LD [M]  /home/linux/lxq/class/drivers/1day/demo.ko  /*生成成功*/

    4. cp ~/lxq/class/drivers/1day/demo.ko   /rootfs/ 

    5.make clean

    6.开发板启动后在开发板上执行insmod demo.ko   -------------->led灯点亮

     

    --------------------------------->学习路漫漫<------------------------------

  • 相关阅读:
    SVN使用之分支、合并
    eq相等 ,ne、neq不相等 EL表达式
    PLSQL:plsql中文乱码,显示问号
    mysql中实现行号,oracle中的rowid
    安装完QQ必须要删除掉的几个恐怖文件
    关于table的一些兼容性问题
    Javascript事件绑定的几种方式
    jQuery中的一些正则匹配表达式
    将java类的泛型集合转换成json对象
    python登录豆瓣,发帖
  • 原文地址:https://www.cnblogs.com/hslixiqian/p/9637409.html
Copyright © 2020-2023  润新知