1,第三方驱动 黑盒移植
1.1,驱动编译进内核Makefile
1、首先找内核中是否已经支持设备的驱动 选配
2、内核中没有的驱动移植进来 --第三方驱动移植
将第三方驱动代码放到linux源码树中的driver目录中
修改 Makefile Kconfig(界面配置)
修改过的和新添加的代码会重新编译
程序需要在板子上运行 就需要使用交叉编译工具编译
mknod /dev/led c或者b 主设备号 次设备号
1.1.1, 选择驱动存放目录 (或任意目录)
拷贝LED驱动程序至drivers目录
LED属于字符设备,所以放在drivers/char/
目录下
1.1.2, 改Makefile让驱动编译进内核(同步修改,对应目录下的Makefile)
1.1.3,改Kconfig (界面可配置)
1.1.3.1,测试驱动
驱动程序测试代码
#include
#include
#include
#include
#include
#define LED_MAGIC 'L'
#define LED_ON _IOW(LED_MAGIC, 1, int)
#define LED_OFF _IOW(LED_MAGIC, 2, int)
int main(int argc, char **argv)
{
int fd;
fd = open("/dev/led", O_RDWR);
if (fd < 0) {
perror("open");
exit(1);
}
printf("open led ok\n");
//实现LED灯闪烁
while(1)
{
ioctl(fd, LED_ON); //点亮灯
usleep(100000);
ioctl(fd, LED_OFF); //灭灯
usleep(100000);
}
return 0;
}
在开发板上运行该测试程序
分析这个打印信息的来源
需要创建设备文件
1.1.3.2,创建设备文件
mknod /dev/led c 501 0
创建好设备文件后,再运行测试程序,就可以看到LED灯在闪烁了
1.2,图形化配置Kconfig
1.2.1,make menuconfig、Makefile、Kconfig、.config 三者的关系
menuconfig 运行的时候,会读取内核下面所有的Kconfig,根据Kconfig生成对应的图形界面
Makfile 具体编译文件
Kconfig 选配,生成图形化界面(在界面中显示一些选项)
在Kconfig中添加一个灯的选项
界面修改好了, 还需要再次修改 Makfile
[] 只有两种选项编译(y)或者不编译(n)
<> 三种选项 编译(y)、不编译(n)或者编译成模块(m)
obj-y obj-n obj-$(CONFIG_XXX)
方便驱动管理
1.2.2,配置Kconfig
1.2.2.1,在Kconfig中添加一个灯的选项
linux@linux:~/linux-3.14.79$ make menuconfig
1.2.2.2,修改Makefile
1.2.2.3,在图形界面选择不编译LED驱动,看是否成功
1.3,编译驱动为独立的模块
驱动模块学习 详细视频
linux 内核模块 http://www.makeru.com.cn/live/1392_586.html
驱动初级也会详细讲解
1.3.1,C程序代码编写
创建驱动程序的文件夹,将文件拷贝进来,并为之编写Makfile
1.3.2, Makefile 编写
1.3.3,编译出 ko 模块
1.3.4,配置为模块方式
1.3.5,make modules 编译所有的模块
1.3.6,在linux运行时,可以装载和卸载模块
1.3.6.1,拷贝LED驱动模块到根文件系统
1.3.6.2,通过 手动加载模块 insmod *.ko
卸载是rmmod
1.3.6.3,创建设备节点(应用访问驱动的入口)
1.3.6.4,运行测试驱动的应用程序
2,白盒移植
2.1,黑白盒对比
黑盒移植
两种方式
1. 驱动编译进内核
选则源码目录
修改Makefile
修改Kconfig
2. 将驱动编译成独立的模块
配置为模块方式 通过修改Kconfig
使用make modules 编译为模块
装载模块 insmod xxx.ko
创建设备结点 mknod /dev/xxx c xx xx
运行测试驱动的应用程序
白盒移植
需要阅读阅读源码,熟悉驱动框架
1. 字符设备 2. 平台设备
字符设备框架
application
User Mode ||
\/
--------------------------------------------------
System Call Interface
||
Kernel Mode \/
Virture File System(VFS)
| | |
Character Block Network
| | |
Device interface
||
\/
-------------------------------------------------
Hardware Physical Device (Hardware)
2.2,驱动框架
2.2.1,字符设备框架
2.2.1.1,字符设备引入
1.一切设备皆文件
对设备的操作的就是 对设备文件的 read write
2. open read write ioctl
3. 将设备进行编号 设备号(主次设备号组成)
2.2.1.2,字符设备驱动相关
1. 注册获取设备号
2. 初始化设备
3. 操作设备 file_operations -- open release read write ioctl...
4. 两个宏定义 module_init module_exit 两个命令 insmod rmmod
5. 注册设备号 register_chrdev_region
6. cdev_init 初始化字符设备
7. cdev_add 添加字符设备到系统
驱动是被动调用的,是被应用程序触发的
应用程序的 open 调用到驱动的file_operations 的 open
应用程序的 read 调用到驱动的file_operations 的 read
应用程序的 write 调用到驱动的file_operations 的 write
应用程序的 ioctl 调用到驱动的file_operations 的 ioctl
应用程序的 close 调用到驱动的file_operations 的 release
2.2.1.3,LED驱动代码—fs4412_led_drv.c
#include
#include
#include
#include
#include
#define LED_MAGIC 'L'
#define LED_ON _IOW(LED_MAGIC, 1, int)
#define LED_OFF _IOW(LED_MAGIC, 2, int)
#define LED_MA 501
#define LED_MI 0
#define LED_NUM 1
#define LED_CON 0x11000C20
#define LED_DAT 0x11000C24
struct cdev cdev;
unsigned int *ledcon;
unsigned int *leddat;
int led_open (struct inode *inode, struct file *file)
{
printk("led_open\n");
ledcon = ioremap(LED_CON, 4); //内核访问硬件需要地址映射
if(ledcon == NULL)
{
printk("ioremap LED_CON error\n");
return -1;
}
writel(0x01, ledcon); //设置LED3 GPX1_0 为输出模式
leddat = ioremap(LED_DAT, 4); //内核访问硬件需要地址映射
if(leddat == NULL)
{
printk("ioremap LED_DAT error\n");
return -1;
}
writel(1, leddat); //设置LED3 GPX1_0 输出高电平
return 0;
}
int led_release(struct inode *inode, struct file *file)
{
printk("led_close\n");
return 0;
}
long led_ioctl(struct file *file, unsigned int cmd, unsigned long args)
{
switch(cmd)
{
case LED_ON:
printk("led on ..\n");
writel(1, leddat); //设置LED3 GPX1_0 输出高电平
break;
case LED_OFF:
printk("led off ..\n");
writel(0, leddat); //设置LED3 GPX1_0 输出高电平
break;
default:
printk("no command\n");
break;
}
return 0;
}
struct file_operations led_fops = { //文件操作
.owner = THIS_MODULE,
.open = led_open,
.release =led_release,
.unlocked_ioctl = led_ioctl,
};
static led_init(void)
{
int ret;
dev_t devno = MKDEV(LED_MA, LED_MI);
ret= register_chrdev_region(devno, LED_NUM, "newled"); //注册设备号
if(ret)
{
printk("register_chrdev_region error\n");
return -1;
}
cdev_init(&cdev, &led_fops); //初始化字符设备
ret = cdev_add(&cdev, devno, LED_NUM); //添加字符设备到系统中
if (ret < 0) {
printk("cdev_add error\n");
return -1;
}
printk("Led init 5 \n");
return 0;
}
static void led_exit(void)
{
dev_t devno = MKDEV(LED_MA, LED_MI);
cdev_del(&cdev);
unregister_chrdev_region(devno, LED_NUM); //取消注册
printk("Led exit\n");
}
module_init(led_init); //模块加载入口 对应命令insmod
module_exit(led_exit); //模块卸载入口 对应命令rmmod
MODULE_LICENSE("Dual BSD/GPL"); //免费开源声明
2.2.2,平台设备框架
2.2.2.1,平台设备引入
1. 寄存器的地址值是根据芯片手册 和原理图找到的
2. 对寄存器地址内容操作不能直接使用物理地址,需要ioremap
3. ioremap 将物理地址 映射到虚拟地址
4. platform 用于将硬件信息 和驱动代码做分离
5. 通过名字匹配,匹配成功执行 probe函数
6. 驱动通过 platform_get_resource 获取硬件设备资源
7. 作用 容易维护