驱动开发笔记
1;环境搭建
1)ping通
修改网络配置:vi /etc/network/interfaces 修改成static ip为192.168.1.141
修改网络虚拟编辑器:为有线网卡
重启网卡(ifdown eth0 ifup eth0)或者重启ubuntu(shutdown -r now )
2)设置bootcmd使开发板通过tftp下载自己建立的内核源码树编译得到的
set bootcmd ‘tftp 0x30008000 zImage;bootm 0x30008000’(这个是tftp下载的)
bootcmd=movi read kernel 30008000; bootm 30008000(只是烧录到inand中启动的)
我的tftp目录在/tftpboot下,编译好的zImage要复制到这来,每次开发板启动都是动态到这来下载内核
3)将我们配置的rootfs放到我们配置的nfs服务器的目录下
showmount localhost -e查看我们nfs配置的目录
4)make menuconfig 修改配置 支持nfs
5)修改开发板上环境变量
serverip 要保证为我们tftp服务器ip
bootcmd 设置为tftp下载
bootarg 设置为rootfs从nfs下载
setenv bootargs=console=ttySAC2,115200 root=/dev/mmcblk0p2 rw init=/linuxrc rootfstype=ext3
setenv bootargs root=/dev/nfs nfsroot=192.168.1.141:/root/rootfs/rootfs ip=192.168.1.20:192.168.1.141:192.168.1.1:255.255.255.0::eth0:off init=/linuxrc console=ttySAC2,115200
6)修改开发板上命令行密码,将密码去掉
root/rootfs/rootfs/etc/shadow文件
7)rootfs的文件都在开发板下的根目录下 cd /即可,将建立的app可执行程序和.ko模块都放在根文件下
剩下的就是insmod lmod modinfo
8)创建设备文件
mknod /dev/xxx c 主设备号 次设备号
9)应用程序中通过操作设备文件来调用驱动中对应函数
10)驱动文件中操作硬件寄存器应才用虚拟地址
两种方法 静态虚拟映射表和动态虚拟地址映射表,将地址转换一下,操作跟逻辑里面的可以一样
注意事项
1;编译.ko模块的内核要和等下去运行的内核要是同一个
代码
makefile基本是模版不用改
#ubuntu的内核源码树,如果要编译在ubuntu中安装的模块就打开这2个
#KERN_VER = $(shell uname -r)
#KERN_DIR = /lib/modules/$(KERN_VER)/build
# 开发板的linux内核的源码树目录
KERN_DIR = /root/derive/kernel
#-m表示编译成模块
obj-m += module_test.o
#-C表示进入目录 M记录原路径便于返回
all:
make -C $(KERN_DIR) M=`pwd` modules
arm-linux-gcc app.c -o app
cp:
cp *.ko /root/rootfs/rootfs/driver_test
cp app /root/rootfs/rootfs/driver_test
.PHONY: clean
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm app
总结
模块的makefile非常简单,本身并不能完成模块的编译,而是通过make -C进入到内核源码树下借用内核源码的体系来完成模块的编译链接的。这个Makefile本身是非常模式化的,3和4部分是永远不用动的,只有1和2需要动。1是内核源码树的目录,你必须根据自己的编译环境
/
驱动代码,尽量只包含驱动,操作寄存器代码,逻辑代码尽量放到应用层去
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h> //包含file_operations
#include <asm/uaccess.h>
#include <mach/gpio-bank.h>
#include <mach/regs-gpio.h>
#include <linux/ioport.h>
#include <linux/io.h>
#define GPJ0CON S5PV210_GPJ0CON
#define GPJ0DAT S5PV210_GPJ0DAT
#define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)
#define GPJ0CON_pa 0xE0200240
unsigned int *rGPJ0CON_pa;
char kernelbuf[100];
static int test_open(struct inode *inode, struct file *file)
{
printk(KERN_INFO "chrdev_init helloworld init\n");
rGPJ0CON = 0x11111111;
//rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
return 0;
}
static int test_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "chrdev_exit helloworld exit\n");
//rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
return 0;
}
ssize_t test_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
printk(KERN_INFO "test_read\n");
copy_to_user(buf, kernelbuf, size);
}
//buf是应用层的要需要写入的buf,size需要写入的大小
static ssize_t test_write(struct file *filp, const char __user *buf,size_t size, loff_t *ppos)
{
printk(KERN_INFO "test_write\n");
copy_from_user(kernelbuf, buf, size);//这里的大小一般是两个buf中取小的
if(kernelbuf[0] == '1')
{
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
}
else if(kernelbuf[0] == '0')
{
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
}
}
//第一步定义file_operations结构体 查内核复制
static const struct file_operations test_fops = {
.owner = THIS_MODULE,
.open = test_open,
.release = test_release,
.read = test_read,
.write = test_write,
};
int myret = -1;
//第二步定义注册注销 查内核复制
// 模块安装函数
static int __init chrdev_init(void)
{
myret = register_chrdev(0, "test_modu", &test_fops); //margin传0表示由内核自动分配
if(myret < 0)
{
printk(KERN_ERR "chrdev_init helloworld init\n"); //关于打印级别或者错误编号意义
return -EINVAL; //errno-base.h中定义了错误的编号 如果不知道可以去搜一个然后追
//定义所有的头文件
}
printk(KERN_INFO "open major %d\n", myret);
//test_open();
//动态映射
if (!request_mem_region(GPJ0CON_pa, 8, "GPJ0_LED"))
{
printk(KERN_ERR "request_mem_region error\n");
return -EINVAL;
}
printk(KERN_INFO "request_mem_region success\n");
rGPJ0CON_pa = ioremap(GPJ0CON_pa, 8);
*rGPJ0CON_pa = 0x11111111;
*(rGPJ0CON_pa + 1) = ((0<<3) | (0<<4) | (0<<5));
return 0;
}
// 模块卸载函数
static void __exit chrdev_exit(void)
{
unregister_chrdev(myret, "test_modu");
//test_release();
*(rGPJ0CON_pa + 1) = ((1<<3) | (1<<4) | (1<<5));
iounmap(rGPJ0CON_pa);
release_mem_region(GPJ0CON_pa, 8);
}
module_init(chrdev_init);
module_exit(chrdev_exit);
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("aston"); // 描述模块的作者
MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息
MODULE_ALIAS("alias xxx"); // 描述模块的别名信息
应用层
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define Myname "/dev/test" //mknod的那个对应的设备文件
char appbuf[100];
int main()
{
int ret = -1;
int i = 0;
ret = open(Myname, O_RDWR);
if(ret < 0)
{
printf("open %s error\n", Myname);
return -1;
}
printf("open %s success\n", Myname);
//读写文件间接操作驱动的读写函数
//write(ret, "hellowhold", 10);
//(ret, appbuf, 10);
//sleep(10);
printf("下面驱动led灯 输入on or off or flash\n");
while(1)
{
scanf("%s",appbuf);
if(!strcmp(appbuf, "on"))
{
write(ret, "1", 1);
}
else if(!strcmp(appbuf, "off"))
{
write(ret, "0", 1);
}
else if(!strcmp(appbuf, "flash"))
{
for(i = 0; i < 3; i++)
{
write(ret, "1", 1);
sleep(1);
write(ret, "0", 1);
sleep(1);
}
}
}
//关闭
close(ret);
printf("read appbuf = %s\n", appbuf);
return 0;
}
总结:
1;完成MODULE_xxx的一些宏(描述模块的一下基本信息
2;module_init(chrdev_init);module_exit(chrdev_exit);绑定模块建立和撤销时对应insmod和rmmod调用的对应函数
3;注册驱动调用register_chrdev,
关键参数
主设备号若传0则表示内核自动分配有返回值返回,
file_operations结构体指针(里面只要是一些函数指针)用来挂接驱动和内核api的,就是应用层调用这个函数则会调用该结构体指向的对应函数,从内核复制并修改,并且完成内核函数指针指向的函数进行定义。
4;创建应用程序,主要是io操作,操作的就是mknod建立的设备文件,这样才会完成在应用层对该文件操作,在驱动层则可以接收,如应用层open设备文件,则在驱动层相当于调用file_operations结构体中open元素指向的函数。
5;在驱动层操作寄存器的虚拟地址来进行读写操作来操作硬件
采用驱动新接口,注册注销时都分两步,设备号注册和驱动注册两步
代码,只需要修改init绑定的函数
// 模块安装函数
static int __init chrdev_init(void)
{
/*
旧接口的注册
myret = register_chrdev(0, "test_modu", &test_fops); //margin传0表示由内核自动分配
if(myret < 0)
{
printk(KERN_ERR "chrdev_init helloworld init\n"); //关于打印级别或者错误编号意义
return -EINVAL; //errno-base.h中定义了错误的编号 如果不知道可以去搜一个然后追
//定义所有的头文件
}
printk(KERN_INFO "open major %d\n", myret);
*/
//新接口注册
//第一步注册设备号 先MKDEV得到设备号,再利用register_chrdev_region来注册
int retval;
//mydev = MKDEV(200,0);//将主设备号和次设备号的起始序列合并成设备号
//retval = register_chrdev_region(mydev, MYCOUNT, "test_modu");
//分配设备号
retval = alloc_chrdev_region(&mydev, 0, MYCOUNT, "test_modu");
if (retval < 0) {
printk(KERN_ERR "Unable to register minors for test_modu\n");
goto flag1;
}
printk(KERN_INFO "register_chrdev_region success\n");
printk(KERN_INFO "major %d minors %d\n", MAJOR(mydev), MINOR(mydev));
//绑定注册驱动
pmycdev = cdev_alloc(); //相当对内核里面申请堆内存
if (!pmycdev)
{
printk(KERN_ERR "cdev_alloc error\n");
goto flag2;
}
cdev_init(pmycdev, &test_fops);//绑定初始化mycdev
retval = cdev_add(pmycdev, mydev, MYCOUNT);//注册将file_operation结构体与设备号绑定
if (retval) {
printk(KERN_ERR "Unable to get testmodu major \n");
goto flag3;
}
//test_open();
//动态映射
if (!request_mem_region(GPJ0CON_pa, 8, "GPJ0_LED"))
{
printk(KERN_ERR "request_mem_region error\n");
goto flag4;
}
printk(KERN_INFO "request_mem_region success\n");
rGPJ0CON_pa = ioremap(GPJ0CON_pa, 8);
*rGPJ0CON_pa = 0x11111111;
*(rGPJ0CON_pa + 1) = ((0<<3) | (0<<4) | (0<<5));
return 0;
//一个中途出错的倒影式错误处理方法
flag4:
cdev_del(pmycdev);
flag3:
cdev_del(pmycdev);
flag2:
unregister_chrdev_region(mydev, MYCOUNT);
flag1:
return -EINVAL;
}
// 模块卸载函数
static void __exit chrdev_exit(void)
{
/*
旧的注销方法
unregister_chrdev(myret, "test_modu");
*/
//驱动注销
cdev_del(pmycdev);
//设备号注销
unregister_chrdev_region(mydev, MYCOUNT);
//test_release();
*(rGPJ0CON_pa + 1) = ((1<<3) | (1<<4) | (1<<5));
iounmap(rGPJ0CON_pa);
release_mem_region(GPJ0CON_pa, 8);
}