本文目的:编写Linux内核驱动基础知识
1.搭建开发环境
tftp
nfs
minicom
vim+.vimrc插件+.vim插件
ctags/cscope +类似于SI
openssh-server/openssh-client
gcc-arm-linux-androideabi
gcc-arm-linux-gnueabi
gcc-arm-linux-gnueabihf
gcc-arm-none-eabi
u-boot-tools
device-tree-compiler
libncurses5
2.搭建开发板的系统环境
2.1 固化UBOOT
解压源代码,make xxx_config,make ARCH=xxx CROSS_COMPILE=arm-xxxx-即可生成u-boot.bin,
会配置uboot和编译。用来学习提高自己。
2.2 启动kernel
官方提供源代码,
cp arch/xx/configs/xxx_deconfig .config 或者make xxx_defconfig
make menuconfig 修改 ->查看内核是否支持ARM平台、是否支持当前处理器、是否支持当前开发板
system type->
ARM
CPU(s5pv210)
board(cw210)
system type ->
->ARM system type(Samsung EXYNOS)
->board selection 对应UBOOT id号
make ARCH=xxx CROSS_COMPILE=arm-linux- zImage -j8
2.3 挂接文件系统
体力活,去多学习,熟练。有机会阅读busybox,这部分代码挺好 init.c开始看起。
2.4 设置启动参数
setenv bootcmd 加载并引导启动内核{网络加载,本地flash,本地sd卡启动等}
setenv bootargs 启动参数,UBOOT告诉内核挂接根文件系统信息
setenv bootcmd tftp 50008000 zImage;bootm 50008000
如nfs启动
setenv bootargs root=/dev/nfs nfsroot=主机ip:主机NFS共享目录
ip=板子IP:主机IP:网关:子网掩码::eth0
init=/linuxrc console=ttySAC0,115200
setenv bootargs root=/dev/nfs nfsroot=192.168.1.8:/opt/rootfs init=/linuxrc console=ttySAC0,115200
ip=192.168.1.110:192.168.1.8:192.168.1.1:255.255.255.0::eth0
如果提示:Root-NFS:Server returned error -13 while mounting /opt/rootfs 表示服务没有开启。
ipaddr=xxx 注:这个IP为uboot时使用,bootargs在启动时用
saveenv
注:
这部分好像也可以在内核中配置
570 CONFIG_CMDLINE="root=/dev/nfs rw nfsroot=192.168.1.222:/home/baiy/train/ITOP-learn/nfs-fs/system ip=192.168.1.230:192.168.1.222:192.168.1.1:255.255.255.0:iTOP-nfs:eth0 :off rootfstype=ext4 init=/linuxrc console=ttySAC2,115200"
2.5 系统起来后,就可以搞驱动和应用了。
案例:现在需要写一个LM77温度传感器的配置信息,在Make menuconfig中找到LM77的配置信息,怎么找源文件?
通过源文件找配置项?这部分必须熟练。
3.什么是驱动
驱动的本质:1.对硬件工作的封装 2.给用户提供可访问的操作方法
驱动的分类:无操作系统(裸板和单片机),应用程序直接调用函数即可。 驱动就是函数封装。
有操作系统(Linux,RTOS), 应用程序通过系统间接调用函数即可。驱动就是系统调用。
open->sys_open->drv_open,只需要完成drv_open和open即可。
Linux内核驱动的层次:
printf("hello, world\n")--->write------>filesystem---->uart_drivers--->硬件输出
3.1 Linux内核驱动开发方法:
3.1.1 驱动的入口函数 和 出口函数
#include <linux/init.h>
#include <linux/module.h>
static int __init xxx_init(void)
{
printk("xx_init:%s\n",__FUNCTION__);
return 0;//成功返回0,失败返回错误码
}
static void __exit xxx_exit(void)
{
printk("xxx_exit:%s\n",__FUNCTION__);
return;
}
module_init(xxx_init);
module_exit(xxx_exit); //修饰入口和出口函数
MODULE_LICENSE("GPL");
MODULE_AUTHOR("baiy <[email protected]>"); //作者信息,选填
MODULE_DESCRIPTION("This is a test driver"); //描述信息,选填
MODULE_VERSION("1.0.0.0"); //版本描述,选填
3.1.2 入口函数和出口函数什么时候被执行
当内核初始化时或者动态加载驱动模块时,执行驱动入口函数。 初始化工作。
当系统复位或者卸载驱动模块时调用驱动出口函数。扫尾工作。
注释:可通过request_module(module_name);函数去加载模块
3.2 Linux内核驱动模块编译
3.2.1 静态编译:
将驱动和内核绑定在一起,写入到Makefile中,将驱动和内核都压缩在zImage中
做法:
1.Kconfig和Makefile
Kconfig中找到配置项 config SENSORS_LM77。
通过此信息得到Makefile使用的配置项:CONFIG_SENSORS_LM77。
找到对应的配置项存在的内容,也就找到对应的源文件。
2.将驱动放在内核源代码中, cp xxx.c driver/char/
修改Kconfig和Makefile
Kconfig中加入
config HELLO_WORLD
#depend on //依赖某个驱动
default y
help
This is first drive
Makefile中加入
obj-${CONFIG_HELLO_WORLD} += hello.o
make zImage
查看.config和xxx.o文件是否生成。
重启
3.2.2 动态编译1:内核模块。
配置项写为三态,选为模块即可,最后make modules
make menuconfig 将*变成M
make zImage 去除方法1中的静态编译
make modules 编译模块,生成.ko文件,不产生依赖
make modules_install INSTALL_MOD_PATH=/opt/ //安装模块,并产生依赖关系,生成一个lib目录,包含了之间依赖关系。
cp /opt/lib/modules /rootfs/lib/ -frd 即可。 //安装模块后会生成/opt/lib/modules 文件,拷贝到/lib/下边即可
烧录zImage重启。
静态编译每次修改编译必须重新编译内核,重新启动系统,耗时较长,适用于产品发布阶段。
动态编译1每次修改只需要 make modules 重新编译,将对应.ko拷贝到设备上加载即可。不需要重新启动系统。
共性:都得去修改Kconfig和Makefile,且放到内核编译。
3.2.3 动态编译2:内核模块,将驱动源码和内核源码进行分离。
前两种方式:代码都在内核目录下,都需要修改Kconfig和Makefile
内核目录
driver/
char
helloworld.c 在内核代码中,
可以在外部编写,跳转到模块中
#Makefile:
# If KERNELRELEASE is defined, we've been invoked from the
# kernel build system and can use its language.
ifneq ($(KERNELRELEASE),)
#这部分第二次执行
obj-m := hello.o
# Otherwise we were called directly from the command
# line; invoke the kernel build system.
else
#这部分第一次执行
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
#install: //加不加伪目标都可以。这里可以加入mod_install信息
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install INSTALL_MOD_PATH=/opt/
endif
clean:
rm -rf *.ko .*.ko.cmd *.mod.c *.mod.o .*.mod.o.cmd *.o .*.o.cmd modules.order Module.symvers .tmp_versions
KERNELRELEASE 是在内核源码的顶层Makefile中定义的一个变量,在第一次读取执行此Makefile时,KERNELRELEASE没有被定义,所以make将读取执行else之后的内容。
当make的目标为all时,-C $(KDIR) 指明跳转到内核源码目录下读取那里的Makefile;M=$(PWD) 表明然后返回到当前目录继续读入、执行当前的Makefile。
当从内核源码目录返回时,KERNELRELEASE已被被定义,kbuild也被启动去解析kbuild语法的语句,make将继续读取else之前的内容,其为kbuild语法的语句, 指明模块源码中各文件的依赖关系,以及要生成的目标模块名。
所以也可以跳转到内核,然后make M=/home/baiy/code/driver-test/test01 modules,一样。
注:如果这里模块有多个文件,可以:
如何将多个.c生成一个.ko?
a.c和b.c生成d.ko
Makefile中
obj-m := d.o
d-objs = a.o b.o
或者
obj-m := ${MODNAME}.ko
${MODNAME}-objs += helloworld1.o
${MODNAME}-objs += helloworld2.o
因为同一个驱动,所以无依赖关系了
模块间的依赖关系
baiy@baiy-ThinkPad-X230:iTop4412_Kernel_3.0$ make modules_install INSTALL_MOD_PATH=/home/baiy/code/mod_install/
INSTALL crypto/ansi_cprng.ko
INSTALL drivers/char/baiyang_hello1.ko
INSTALL drivers/media/video/gspca/gspca_main.ko
INSTALL drivers/mtk_wcn_combo/common/mtk_hif_sdio.ko
INSTALL drivers/mtk_wcn_combo/common/mtk_stp_bt.ko
INSTALL drivers/mtk_wcn_combo/common/mtk_stp_gps.ko
INSTALL drivers/mtk_wcn_combo/common/mtk_stp_uart.ko
INSTALL drivers/mtk_wcn_combo/common/mtk_stp_wmt.ko
INSTALL drivers/mtk_wcn_combo/common/mtk_wmt_wifi.ko
INSTALL drivers/mtk_wcn_combo/drv_bt/hci_stp.ko
INSTALL drivers/mtk_wcn_combo/drv_fm/private/mtk_fm_priv.ko
INSTALL drivers/mtk_wcn_combo/drv_fm/public/mt6620_fm_drv.ko
INSTALL drivers/mtk_wcn_combo/drv_wlan/p2p/p2p.ko
INSTALL drivers/mtk_wcn_combo/drv_wlan/wlan/wlan.ko
INSTALL drivers/scsi/scsi_wait_scan.ko
INSTALL net/bluetooth/hidp/hidp.ko
DEPMOD 3.0.15 #生成模块依赖关系
Warning: you may need to install module-init-tools
See http://www.codemonkey.org.uk/docs/post-halloween-2.6.txt
baiy@baiy-ThinkPad-X230:iTop4412_Kernel_3.0$ tree ../mod_install/lib/
../mod_install/lib/
└── modules
└── 3.0.15
├── build -> /home/baiy/code/iTop4412_Kernel_3.0
├── kernel
│ ├── drivers
│ │ ├── char
│ │ │ └── baiyang_hello1.ko
。。。。。。
├── modules.alias
├── modules.alias.bin
├── modules.builtin
├── modules.builtin.bin
├── modules.dep #依赖信息
├── modules.dep.bin
├── modules.devname
├── modules.order
├── modules.softdep
├── modules.symbols
├── modules.symbols.bin
└── source -> /home/baiy/code/iTop4412_Kernel_3.0
注:如果内核有需要的驱动,则直接编译选项选中即可。
3.3 内核模块动态加载
3.3.1 加载模块
modprobe xxx 会默认到/lib/目录下寻找对应.ko文件,并且检测xxx.ko依赖关系,将所有依赖的.ko加载完成后,在加载xxx.ko。
注:
insmod ....ko 这里必须要写.ko的路径,且不会检查依赖关系。如所依赖的模块未加载,则直接出错。
3.3.2 卸载模块
modprobe -r xxx 这个卸载会将依赖一起卸载
rmmod xxx 这个删除模块只删除指定模块,不删除依赖
3.3.3 查看模块
lsmod
modinfo xxx.ko 即可。
注::这几个命令必须会
注:modprobe 加载和卸载时不需要加 .ko,且编译完内核必须进行模块安装且拷贝到/lib目录下
modprobe(选项)(参数)
选项
-a或--all:载入全部的模块;
-c或--show-conf:显示所有模块的设置信息;
-d或--debug:使用排错模式;
-l或--list:显示可用的模块;
-r或--remove:模块闲置不用时,即自动卸载模块;
-t或--type:指定模块类型;
-v或--verbose:执行时显示详细的信息;
-V或--version:显示版本信息;
-help:显示帮助。
参数: 加载或移除的模块名
modinfo [-adhpV][模块文件]
参数:
-a或--author 显示模块开发人员。
-d或--description 显示模块的说明。
-h或--help 显示modinfo的参数使用方法。
-p或--parameters 显示模块所支持的参数。
-V或--version 显示版本信息。
insmod(选项)(参数)
选项
-f:不检查目前kernel版本与模块编译时的kernel版本是否一致,强制将模块载入;
-k:将模块设置为自动卸除;
-m:输出模块的载入信息;
-o<模块名称>:指定模块的名称,可使用模块文件的文件名;
-p:测试模块是否能正确地载入kernel;
-s:将所有信息记录在系统记录文件中;
-v:执行时显示详细的信息;
-x:不要汇出模块的外部符号;
-X:汇出模块所有的外部符号,此为预设置。
1.insmod一次只能加载特定的一个设备驱动,且需要驱动的具体地址。写法为:insmod drv.ko
2.modprobe则可以一次将有依赖关系的驱动全部加载到内核。不加驱动的具体地址,但需要在安装文件
系统时是按照make modues_install的方式安装驱动模块的。驱动被安装在/lib/modules/$(uname -r)/...下。写法为:
modprob driver_name
3.modprobe可以解决load module时的依赖关系,比如load moudleA就必须先load mouduleB之类的,它是通过/lib/modules//modules.dep文件来查找依赖关系的。而insmod不能解决依赖问题。
4.modprobe默认会去/lib/下面查找module,而insmod只在给它的参数中去找module(默认在当前目录找)。
这样,有时insmod也有它的有用之处,举个例子吧:有/root/my-mod.ko这个module,cd /root/,然后用insmod my-mod.ko(insmod /root/my-mod.ko)就可以insert这个module了,
#经过测试,发现,modprobe默认会去/lib, /lib/modules/ , 指定路径,这三个地方去找对应的xxx.ko文件
#在下一章会发现,modprobe寻找依赖时,会先尝试将当前驱动加载(此时加载不上),然后将依赖加载,在将当前驱动加载
lsmod 中used by说明此驱动被哪些驱动依赖,需要调用这个驱动中的函数等;这个命令读取 cat /proc/modules 来显示;
驱动中需要关心 /sys/modules/$modulename/相关部分
/sys/module/hello/
├── coresize
├── holders #持有人,被哪些驱动引用
│ └── param -> ../../param
├── initsize
├── initstate #live 状态
├── notes
├── refcnt #引用计数,当为0时可被卸载
├── sections
│ ├── __ksymtab_gpl
│ ├── __ksymtab_strings
│ └── __mcount_loc
├── srcversion
├── taint
├── uevent
└── version
root@baiy-ThinkPad-T480:/home/baiy/workspace/testcode/driver/test01# cat /sys/module/hello/refcnt
0
root@baiy-ThinkPad-T480:/home/baiy/workspace/testcode/driver/test01# cat /sys/module/hello/initstate
live
#以下是转载的,找不到原创了
在搞Linux驱动移植/开发的时候,对于编译出来的驱动可以选择手动insmod,但是感觉很土:1. 需要指定路径; 2. 如果碰到存在依赖的,就丑陋不堪了。
但是modprobe可以很优雅的解决:直接$ modprobe XX_DRIVER_XX即可。
那么问题来了:modprobe自动加载的时候,如何知道驱动的路径和信息呢?以及,我自己编译的驱动,又如何能够modprobe,而不是山寨的insmod呢?
1. modprobe的信息依据
modprobe依赖于/lib/modules/$(uname -r)/modules.dep。而且该文件而不需要手动修改,而使用depmod即可实现自动化操作。
2. depmod
depmod执行不依赖于目录,效果是自动查找/lib/modules/$(uname -r)/下的驱动文件,以及分析彼此的依赖关系。
因此,如果使modprobe找得到的话:
STEP 1:添加驱动至/lib/modules/$(uname -r)/XX_PATH_XX
STEP 2:$ depmod //更新modules.dep信息
注意:以及,加载驱动时: $modprobe XX_DRIVER_XX 而不是 XX_DRIVER_XX.ko