驱动开发
根据linux
的各种驱动框架进行开发, 一低要满足框架, 驱动最终的表现就是/dev/xxx文件, 就是文件的打开关闭读写, 现在新的内核支持设备树, dts文件描述板子的设备信息
分类, 有三种
- 字符设备, 最多的
- 块设备, 以固定的大小进行
- 网络设备驱动
一个设备可能不止属于一种类型,
记录uboot
=> print
baudrate=115200
board_name=EVK
board_rev=14X14
boot_fdt=try
bootargs=console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw
bootcmd=tftp 80800000 zImage; tftp 83000000 imx6ull-alientek-emmc.dtb; bootz 80800000 - 83000000
bootcmd_mfg=run mfgtool_args;bootz ${loadaddr} ${initrd_addr} ${fdt_addr};
bootdelay=3
bootfile=imx6ull-alientek-emmc.dtb
bootscript=echo Running bootscript from mmc ...; source
console=ttymxc0
ethact=FEC1
ethaddr=00:04:9f:04:d2:25
ethprime=FEC
fdt_addr=0x83000000
fdt_file=undefined
fdt_high=0xffffffff
fileaddr=83000000
filesize=8d32
findfdt=if test $fdt_file = undefined; then if test $board_name = EVK && test $board_rev = 9X9; then setenv fdt_file imx6ull-9x9-evk.dtb; fi; if test $board_name = EVK && test $board_rev = 14X14; then setenv fdt_file imx6ull-14x14-evk.dtb; fi; if test $fdt_file = undefined; then echo WARNING: Could not determine dtb to use; fi; fi;
gatewayip=192.168.137.164
image=zImage
initrd_addr=0x83800000
initrd_high=0xffffffff
ip_dyn=yes
ipaddr=192.168.137.164
loadaddr=0x80800000
loadbootscript=fatload mmc ${mmcdev}:${mmcpart} ${loadaddr} ${script};
loadfdt=fatload mmc ${mmcdev}:${mmcpart} ${fdt_addr} ${fdt_file}
loadimage=fatload mmc ${mmcdev}:${mmcpart} ${loadaddr} ${image}
mfgtool_args=setenv bootargs console=${console},${baudrate} rdinit=/linuxrc g_mass_storage.stall=0 g_mass_storage.removable=1 g_mass_storage.file=/fat g_mass_storage.ro=1 g_mass_storage.idVendor=0x066F g_mass_storage.idProduct=0x37FF g_mass_storage.iSerialNumber="" clk_ignore_unused
mmcargs=setenv bootargs console=${console},${baudrate} root=${mmcroot}
mmcautodetect=yes
mmcboot=echo Booting from mmc ...; run mmcargs; if test ${boot_fdt} = yes || test ${boot_fdt} = try; then if run loadfdt; then bootz ${loadaddr} - ${fdt_addr}; else if test ${boot_fdt} = try; then bootz; else echo WARN: Cannot load the DT; fi; fi; else bootz; fi;
mmcdev=1
mmcpart=1
mmcroot=/dev/mmcblk1p2 rootwait rw
netargs=setenv bootargs console=${console},${baudrate} root=/dev/nfs ip=dhcp nfsroot=${serverip}:${nfsroot},v3,tcp
netboot=echo Booting from net ...; run netargs; if test ${ip_dyn} = yes; then setenv get_cmd dhcp; else setenv get_cmd tftp; fi; ${get_cmd} ${image}; if test ${boot_fdt} = yes || test ${boot_fdt} = try; then if ${get_cmd} ${fdt_addr} ${fdt_file}; then bootz ${loadaddr} - ${fdt_addr}; else if test ${boot_fdt} = try; then bootz; else echo WARN: Cannot load the DT; fi; fi; else bootz; fi;
netmask=255.255.255.0
panel=TFT7016
script=boot.scr
serverip=192.168.31.187
Environment size: 2589/8188 bytes
驱动开发测试
驱动就是获取外设, 传感器数据, 控制外设, 数据会提供给应用程序, Linux, 既要编写驱动, 又要编写简单的应用程序, Linux下这两方面是分开的
用户空间和内核空间, Linux操作系统在内核空间, 应用程序在用户空间, 只要是为了安全, 应用想要访问内核可以通过系统调用, 中断, 异常三种, 通常情况下是通过API调用, 比如说C库POSIX等
每一个系统调用号, 根据号码进行调用, 使用软终端
Linux一切皆文件, 使用open打开一个文件的时候, 应用程序通过write写入数据进行操作, 关闭使用close, 编写驱动的时候, 也需要编写对应的open, write, close函数, 使用file_operations进行编写
框架
字符设备编写主要就是对应的file_operations, 保存在内核include/linux/fs.h文件夹中, 对其成员变量的实现
加载卸载
可以编译到内核里面, 也可以编译为模块.ko, 测试的时候加载模块就可以了, 使用两个函数进行
module_init(xxx_init);
moudle_exit(xxx_exit);
模块的加载卸载
Makefile
1 KERNELDIR:=/home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
2 CURRENT_PATH:=$(shell pwd)
3 obj-m:=chrdevbase.o
4
5 build:kernel_modules
6
7 kernel_modules:
8 $(MAKE)-C $(KERNELDIR) M=$(CURRENT_PATH) modules
9 clean:
10 $(MAKE)-C $(KERNELDIR) M=$(CURRENT_PATH)clean
-C切换make使用的目录, M=要编译的模块的目录,
fatload mmc 1:1 80800000 zImage; fatload mmc 1:1 83000000 imx6ull-alientek-emmc.dtb; bootz 80800000 - 830000008300
调试
加载模块有两个命令, insmode和modprobe, 前者不能处理依赖关系
默认回到会到/lib/moudles/目录中查找模块, 创建对应的文件夹, 然后创建4.1.15文件夹存放模块, 使用depmod
进行创建
这里使用
/lib/modules # tftp -g -r chardevbase.ko 192.168.31.187
chardevbase.ko 100% |********************************| 2827 0:00:00 ETA
上传
tftp -p -l c:\User\Administrator\Download 1.2.3.4
depmod: 加载新的
lsmod: 查看当前的驱动
modprobe: 加载存在的
rmmod: 卸载
第一次加载提示licence, 需要在编写的时候添加
MODULE_LICENSE("GPL")
测试记载退出函数的时候使用printk, 这是内核中的函数, printf是在用户态时候使用的函数, 在内核中输出的时候使用, 可以根据等级对信息进行分类
有八个等级
- #define KERN_SOH”\001”
- #define KERN_EMERG KERN_SOH “0”/* 紧急事件,一般是内核崩*/
- #define KERN_ALERT KERN_SOH “1”/* 必须立即采取行动*/
- #define KERN_CRIT KERN_SOH “2”/* 临界条件,比如严重的软件或硬件错误*/
- #define KERN_ERR KERN_SOH “3”/* 错误状态,一般设备驱动程序中使用KERN_ERR报告硬件错误*/
- #define KERN_WARNING KERN_SOH “4”/* 警告信息,不会对系统造成严重影响*/
- #define KERN_NOTICE KERN_SOH “5”/* 有必要进行提示的一些信息*/
- #define KERN_INFO KERN_SOH “6”/* 提示性的信息*/
- #define KERN_DEBUG KERN_SOH “7”/* 调试信息*/
printk(KERN_EMERG "gsmi: Log Shutdown Reason\n");
printk默认MESSAGE_LOGLEVEL_DEFAULT, MESSAGE_LOGLEVEL_DEFAULT默认为4
CONSOLE_LOGLEVEL_DEFAULT控制着哪些级别的消息可以显示在控制台上,此宏默认为7,意味着只有优先级高于7的消息才能显示在控制台上
在include/linux/printk.h
真正的开发
在加载的时候注册字符设备, 退出的时候就会注销字符设备, 内核提供
static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
static inline void unregister_chrdev(unsigned int major, const char *name)
major是设备号, 每一个设备都有
name: 设备名字
fops: file_operations类型的指针, 有设备操作函数的集合
设备号, 为了方便管理, , 有主设备号次设备号, 通过dev_t进行管理, 在文件/include/linux/types.h文件中定义, 是一个u32_t类型, 高12位主设备号, 低20位次设备号, , 提供了三个宏进行操作, 在/include/linux/kdev_t.h
- MAJOR(dev)
- MINOR(dev): 通过dev_t获取两个设备号
- MKDEV(ma,mi): 组装设备号
注册字符设备的函数会把主设备号下面的所有次设备号进行使用, 所以很少使用
可以查看/proc/devices查看已经使用
要求实现的函数
.owner = YHIS_MODULE,
.open = ,
.realse = ,
.read = ,
.write = ,
read在返回的时候不能直接给用户原来的函数, 使用函数copy_to_user
写的时候使用copy_to_user, 因为用户不能访问内核空间
APP编写
Linux man手册
手册分为很多的段
1是普通的命令 2是系统调用,如open,write之类的(通过这个,至少可以很方便的查到调用这个函数,需要加什么头文件) 3是库函数,如printf,fread 4是特殊文件,也就是/dev下的各种设备文件 5是指文件的格式,比如passwd, 就会说明这个文件中各个字段的含义 6是给游戏留的,由各个游戏自己定义 7是附件还有一些变量,比如向environ这种全局变量在这里就有说明 8是系统管理用的命令,这些命令只能由root使用,如ifconfig
手册中可以参看使用的头文件, 相关的参数
测试
加载驱动, 但是这时候没有显示在/dev目录下
/lib/modules/4.1.15 # modprobe chardevbase.ko
chrdevbase_init
/lib/modules/4.1.15 # lsmod
Module Size Used by Tainted: G
chardevbase 1418 0
因为没有创建设备节点, 使用mknod命令mknod /dev/设备名 c 主设备号, 次设备号
, c代表字符型设备
/lib/modules/4.1.15 # mknod /dev/chardevbase c 200 0
可以使用atoi()函数把字符串转化为数字
file_operation
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iterate) (struct file *, struct dir_context *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*mremap)(struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
unsigned (*mmap_capabilities)(struct file *);
#endif
};