驱动第一天---驱动基础
目录:
- 开发环境搭建
- 利用source insight创建Linux源码工程
- 驱动模块开发基础
- 字符设备驱动基础
开发环境搭建
一、tftp 服务器搭建(ubuntu版)
1.安装tftp-server
sudo apt-get install tftpd-hpa
2.配置TFTP服务器
sudo vim /etc/default/tftpd-hpa
将原来的内容改为:
TFTP_USERNAME="tftp"
TFTP_ADDRESS="0.0.0.0:69"
TFTP_DIRECTORY="tftp根目录" #服务器目录,需要设置权限为777,chomd 777
TFTP_OPTIONS="-l -c -s"
3.重新启动TFTP服务
sudo service tftpd-hpa restart
4.测试
sudo netstat -a | grep tftp
可以看到:
udp0 0 0.0.0.0:tftp0.0.0.0:*
udp6 0 0 [::]:tftp [::]:*
则表示tftpserver搭建成功了。
二、开发板的不同启动方式
1、通过网络加载内核和文件系统
a)将 镜像文件/uImage拷贝到ubuntu的 /tftpboot下
b)将 镜像文件/rootfs.tar.xz拷贝到ubuntu的 /source下并解压
c)将 镜像文件/exynos4412-fs4412.dtb拷贝到ubuntu的 /tftpboot下
d)修改虚拟机nfs配置文件/etc/exports,添加如下内容并重启nfs服务
/source/rootfs *(rw,sync,no_subtree_check,no_root_squash)
e)重新驱动nfs服务
$ sudo /etc/init.d/nfs-kernel-server restart
f)设置启动参数
# setenv serverip 192.168.9.120
# setenv ipaddr 192.168.9.233
# setenv bootcmd tftp 41000000 uImage;tftp 42000000 exynos4412-
fs4412.dtb;bootm 41000000-42000000
#setenv bootargs root=/dev/nfs nfsroot=192.168.9.120:/source/rootfs
rw console=ttySAC2,115200 init=/linuxrc ip=192.168.9.233
# saveenv
注意:这两个ip应该根据自己的实际情况适当修改
192.168.9.120 对应Ubuntu的ip;
192.168.9.233 对应板子的ip
2、从EMMC加载内核和文件系统
a)拷贝镜像文件/ramdisk.img拷贝到虚拟机/tftpboot目录下 b)烧写内核镜像到EMMC上
tftp 41000000 uImage
movi write kernel 41000000
c)烧写设备树文件到EMMC上
tftp 41000000 exynos4412-fs4412.dtb
movi write dtb 41000000
d)烧写文件系统镜像到EMMC上
tftp 41000000 ramdisk.img
movi write rootfs 41000000 300000
e)设置启动参数
setenv bootcmd movi read kernel 41000000\;movi read dtb 42000000\;
movi read rootfs 43000000 300000\;bootm 41000000 43000000 42000000
saveenv
f)重新启动开发板,u-boot自动加载、执行内核
三、开发板挂载NFS服务器
首先,在ubuntu上安装:
sudo apt-get install nfs-kernel-server
修改虚拟机nfs配置文件/etc/exports,添加如下内容并重启nfs服务
/xx/xx *(rw,sync,no_subtree_check,no_root_squash)
注意:xx/xx是你要做为nfs服务器的目录
重新驱动nfs服务
$ sudo /etc/init.d/nfs-kernel-server restart
在开发板上,输入以下命令:
mount -t nfs -o nolock 192.168.10.10:/home/ubuntu/share/nfs /mnt/
利用source insight创建Linux源码工程
参考链接:http://bbs.elecfans.com/forum.php?mod=viewthread&tid=1114963&extra=
驱动模块开发基础
内核模块是什么? Kconfig Makefile 的含义? 以任意一个选项的字符串去内核 里面查找相对的源码:
在内核工程里面直接使用如下命令可以生成
mkdir -p module_install #创建一个文件夹用于存放编译生成的模块的安装目录
make INSTALL_MOD_PATH=module_install modules #编译模块
make INSTALL_MOD_PATH=module_install modules_install #安装模块到指定目录
驱动模块开发:
驱动开发四步走
1,头文件
2,驱动模块装载和卸载函数入口到声明
3,实现模块装载和卸载函数入口
4,GPL声明
最简单的驱动Demo:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/stat.h>
static int __init hello_drv_init(void)
{
//一般做系统申请资源
printk("-------%s-------------\n", __FUNCTION__);
return 0;
}
static void __exit hello_drv_exit(void)
{
//一般做系统释放资源
printk("-------%s-------------\n", __FUNCTION__);
}
module_init(hello_drv_init);
module_exit(hello_drv_exit);
MODULE_LICENSE("GPL");
驱动的makefile
ROOTFS_DIR = /home/ubuntu/share/nfs
ifeq ($(KERNELRELEASE), )
KERNEL_DIR = /home/ubuntu/work/linux-3.14-fs4412
CUR_DIR = $(shell pwd)
all :
make -C $(KERNEL_DIR) M=$(CUR_DIR) modules
clean :
make -C $(KERNEL_DIR) M=$(CUR_DIR) clean
install:
cp -raf *.ko $(ROOTFS_DIR)
else
obj-m += hello_drv.o
#obj-m += math.o
endif
PS:如果要编译多源码构成的一个模块时,可以使用: vers-objs = xx.o xxx.o ....
驱动相关命令:
安装:insmod xxxx.ko
查看模块安装列表:lsmod
卸载:rmmod xxx
信息:modinfo 查看模块相应的信息以及依赖项
modprobe 安装命令,1、解决依赖项 2、可以直接在目录下搜索相关的驱动,不需要带.ko即可
卸载驱动出错:
rmmod fs4412_led
rmmod: can't change directory to '/lib/modules': No such file or directory
解决方案:
mkdir -p /lib/modules/3.14.0
1,参数传递
加载ko: insmod hello.ko myname="avd" myvalue=33
用途: wifi驱动,wifiname wifipass
在代码如何处理参数:
module_param(name, type, perm)
参数1:表示参数到名字,比如myname, myvalue
参数2:参数到类型, charp, int,bool...
参数3: /sys/modules/表示文件到权限: 0666
用法:
module_param(myvalue, int, 0666);
module_param(myname, charp, S_IRUGO|S_IWUGO|S_IXUGO);
2,符号导出
#include <linux/module.h>
#include <linux/init.h>
//不需要模块加载和卸载到入口声明,直接定义好一些封装的函数
int my_add(int a, int b)
{
return a+b;
}
EXPORT_SYMBOL(my_add);
int my_sub(int a, int b)
{
return a-b;
}
EXPORT_SYMBOL(my_sub);
MODULE_LICENSE("GPL");
字符设备驱动基础
一、 linux 设备驱动类型主要有以下四种:字符设备,块设备,网络设备,杂项设备
二、设备号:
一个设备有主次设备号;主设备号可以理解成为一类,而次设备可以理解成为具体某个对象
以下面的为例:
7:0 -> ../loop0
7:1 -> ../loop1
7:2 -> ../loop2
7:3 -> ../loop3
7:4 -> ../loop4
7:5 -> ../loop5
7:6 -> ../loop6
7:7 -> ../loop7
7代表主设备号,0~7代表各个设备的次设备号
三、Linux提供的字符设备驱动接口
3.1、申请注册一个设备(char 字符设备):
static inline int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops);
参数1、major 表设备号(32位的值 由主+次设备号构成)高12位主设备号+低20位的次设备号构成 exp: 主设备号:1 次设备号:2 设备号=1<<20|2 =12构成了设备号
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
//注:ma表示主设备号,mi表示次设备号
参数2:name 表示设备名字
/dev/下面的设备名字 /dev/xxx
参数3:file_operations
表示文件操作集:比如常见的open,read,write,close,ioctl.....
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 (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
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 (*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 **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
int (*show_fdinfo)(struct seq_file *m, struct file *f);
};
3.2、释放一个设备(char 字符设备):
static inline void unregister_chrdev(unsigned int major, const char *name)
参数1、major 表设备号
参数2:name 表示设备名字
3.3、创建设备节点:
3.3.1、手动创建一个设备节点:mknod
mknod: invalid option -- 'h'
BusyBox v1.22.1 (2014-08-10 15:54:57 CST) multi-call binary.
Usage: mknod [-m MODE] NAME TYPE MAJOR MINOR
Create a special file (block, character, or pipe)
-m MODE Creation mode (default a=rw)
TYPE:
b Block device
c or u Character device
p Named pipe (MAJOR and MINOR are ignored)
用法: mknod /dev/设备名 类型 主设备号 次设备号
比如:
mknod /dev/chr0 c 250 0
注:手动创建--缺点/dev/目录中文件都是在内存中,断电后/dev/文件就会消失
3.3.2、自动创建(通过udev/mdev机制)
接口一:创建一个类
struct class *class_create(owner, name)//创建一个类
参数1: THIS_MODULE
参数2: 字符串名字,自定义
返回一个class指针
举例:
class_create(THIS_MODULE,"hello_class")
注:调试信息: cat /sys/class/
可以看到:与class_create中对应的hello_class
bdi/ hello_class/ misc/ rtc/ tty/
block/ i2c-adapter/ mmc_host/ scsi_device/ vc/
dma/ input/ net/ scsi_disk/ vtconsole/
firmware/ mdio_bus/ phy/ scsi_generic/
graphics/ mem/ regulator/ scsi_host/
接口二:销毁一个类
void class_destroy(devcls);
参数1: class结构体,class_create调用之后到返回值
接口三:创建一个设备文件
struct device *device_create(struct class * class, struct device *
parent, dev_t devt, void * drvdata, const char * fmt,...)
参数1: class结构体,class_create调用之后到返回值
参数2:表示父亲,一般直接填NULL
参数3: 设备号类型 dev_t
参数4:私有数据,一般直接填NULL
参数5和6:表示可变参数,字符串,表示设备节点名字:(exp)led2、gpio ---->>/dev/led2 、gpio
举例:
device_create(hello_class, NULL,MKDEV(my_major,0), NULL,"hello_dev");
注:调试信息: ls /dev/hello_dev -l
可以看到:与device_create中对应的主、次设备号,以及设备节点名称
crw-r--r--1 00 253, 0 Jan 1 00:08 /dev/hello_dev
接口四:销毁一个设备文件
原型:
void device_destroy(struct class *class, dev_t devt)
参数1: class结构体,class_create调用之后到返回值
参数2: 设备号类型 dev_t
例子:
void device_destroy(devcls, MKDEV(dev_major, 0));
3.4、 应用程序需要传递数据给驱动
int copy_to_user(void __user * to, const void * from, unsigned long n)
将数据从内核空间拷贝到用户空间,一般是在驱动中chr_drv_read()用
参数1:应用驱动中的一个buffer
参数2:内核空间到一个buffer
参数3:个数
返回值:大于0,表示出错,剩下多少个没有拷贝成功;;等于0,表示正确
int copy_from_user(void * to, const void __user * from, unsigned long n)
将数据从用户空间拷贝到内核空间,一般是在驱动中chr_drv_write()用
参数1:内核驱动中的一个buffer
参数2:应用空间到一个buffer
参数3:大小
3.5、IOCTL的用法:
驱动层: static long hello_ioctl(struct file *file, unsigned int cmd, unsigned long arg) {
switch(cmd)
{
case cmd_test1:
printk("cmd is cmd_test1\n");
printk("cmd test1 's arg:%ld\n",arg);
break;
case cmd_test2:
printk("cmd is cmd_test2\n");
break;
default:
printk("out of cmd!\n");
return -1;
break;
}
return 0;
}
应用层:
ioctl(fd,cmd,arg);
3.6 printk 等级说明:
#define KERN_EMERG KERN_SOH "0" /* system is unusable */
#define KERN_ALERT KERN_SOH "1" /* action must be taken immediately */
#define KERN_CRIT KERN_SOH "2" /* critical conditions */
#define KERN_ERR KERN_SOH "3" /* error conditions */
#define KERN_WARNING KERN_SOH "4" /* warning conditions */
#define KERN_NOTICE KERN_SOH "5" /* normal but significant condition */
#define KERN_INFO KERN_SOH "6" /* informational */
#define KERN_DEBUG KERN_SOH "7" /* debug-level messages */
#define KERN_DEFAULT KERN_SOH "d" /* the default kernel loglevel */
3.7 字符设备驱动框架总结:
字符设备驱动的框架:
1、完善最基本的框架: 头文件
入出口声明
MODULE_LICENSE("GPL");
2、在入口函数里面:
2.1 申请一个字符设备:register_chrdev
2.1.1 补充相应的参数,设备号,名字,文件操作集:file_operations
2.2 申请一个类:class_create
2.3 申请一个设备:device_create
3、在出口函数里面:
3.1 释放设备: device_destroy 3.2 释放类: class_destroy 3.3 释放字符设备: unregister_chrdev
4、文件操作集:file_operations 接口完善
open,write,read,ioctl.......
3.8 出错码提示:
static inline long __must_check IS_ERR(__force const void *ptr)
注:关于指针是否出错?
static inline long __must_check PTR_ERR(__force const void *ptr)
注:将指针出错的具体原因转换成一个出错码
3.9 在驱动程序中向内核申请内存空间:
static __always_inline void *kmalloc(size_t size, gfp_t flags)
flags 取 GFP_KERNEL:
代表向内核申请内存资源,如果出错了,就会一直阻塞(休眠)
4.0 硬件重映射
led 对应的gpiox2_7 con :0x11000c40 led 对应的gpiox2_7 dat :0x11000c44
ioremap();
原型: static inline void __iomem *ioremap(phys_addr_t offset, unsigned long size)
参数1:物理地址 参数2:需要映射的大小
iounmap();//把ioremap的操作取消
对于寄存器操作的API:
readl(addr)//从地址addr中读取出其值并返回
writel(addr,value) //把值 value写入到addr地址中