目录
1. 应用程序将数据传递给驱动
1.1. 函数分析
1.2. 编写驱动.c文件
1.3. 编写编译驱动的makefile文件
1.4. 执行make命令,并安装驱动,生成设备文件
1.5. 写应用层.c文件
1.6. 执行可执行文件验证
2. 驱动操作LED灯
2.1. 函数分析
2.2. 手册和原理图
2.3. 编写驱动.c文件
2.4. 编写驱动的makefile文件
2.5. 执行make命令,生成.ko文件
2.6. 将驱动文件放到nfs同步到开发板中
2.7. 在开发板上安装此驱动
1. 应用程序将数据传递给驱动
1.1. 函数分析
//记忆技巧:从用户角度看
》1.函数原型:int copy_from_user(void *to, const void __user *from, int n)
功能:从用户空间拷贝数据到内核空间(from user)
参数:
to:内核空间首地址
from:用户空间首地址
n:拷贝数据的长度
返回值:
成功:0
失败:未拷贝字符个数
》2.函数原型:int copy_to_user(void __user *to,const void *from,int n)
功能:从内核空间到用户空间(to user)
参数:
to:用户空间
from:内核空间
n:拷贝数据长度
返回值:
成功:0
失败:未拷贝字符个数
1.2. 编写驱动.c文件
//应用程序和驱动程序传参
#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/fs.h>
#include <linux/uaccess.h> //添加头文件
unsigned int major =0;
#define NAME "hello"
int error_num=0;//定义变量用来判错
char kbuf[128]={0};//定义数组用来传输数据
ssize_t mycdev_read (struct file *file, char __user *ubuf, size_t size, loff_t * offs)
{
if(size>sizeof(kbuf)) //添加判断让数据不过大
{
size=sizeof(kbuf);
}
//站在用户角度,数据到用户
error_num=copy_to_user(ubuf,kbuf,size);
if(error_num)
{
printk("copy_to_user error\n");
return -1;
}
return 0;
}
ssize_t mycdev_write (struct file *file, const char __user *ubuf, size_t size, loff_t *offs)
{
if(size>sizeof(kbuf)) //添加判断让数据不过大
{
size=sizeof(kbuf);
}
//站在用户角度,用户写是数据来自用户
error_num=copy_from_user(kbuf,ubuf,size);
if(error_num)
{
printk("copy_from_user error\n");
return -1;
}
return 0;
}
int mycdev_open(struct inode *inode, struct file *file)
{
printk("hello open\n");
return 0;
}
int mycdev_release (struct inode *inode, struct file *file)
{
printk("hello close\n");
return 0;
}
struct file_operations fops={
.open=mycdev_open,
.write=mycdev_write,
.release=mycdev_release,
.read=mycdev_read,
};
static int __init hello_init(void)
{
major= register_chrdev(major, NAME, &fops);
if(major<0)
{
printk("register_chrdev error\n");
return major;
}
return 0;
}
static void __exit hello_exit(void)
{
unregister_chrdev(major, NAME);
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
1.3. 编写编译驱动的makefile文件
KERNELDIR:=/lib/modules/$(shell uname -r)/build
#KERNELDIR:=/home/hq/temp/kernel-3.4.39/
PWD:=$(shell pwd)
all:
make -C $(KERNELDIR) M=$(PWD) modules
clean:
make -C $(KERNELDIR) M=$(PWD) clean
obj-m:=hello.o
1.4. 执行make命令,并安装驱动,生成设备文件
》1.执行make命令
》2.安装驱动
sudo insmod hello.ko 安装驱动命令
cat /proc/devices 查看主设备号
》3.生成设备文件
sudo mknod hello c 244 1 生成设备文件
添加设备文件权限(不能忘记)
1.5. 写应用层.c文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
char buf[128]={"my name is southernbird"};//写入数据
int main(int argc,const char *argv[])
{
int fd;
fd=open("./hello",O_RDWR);
if(fd==-1)
{
perror("open error");
return -1;
}
write(fd,buf,sizeof(buf));
memset(buf,0,sizeof(buf)); //清空
read(fd,buf,sizeof(buf));
printf("%s\n",buf);//用来验证
close(fd);
return 0;
}
使用gcc编译器进行编译,生成可执行文件
1.6. 执行可执行文件验证
》1.使用普通文件hello的情况
》2.使用设备文件
数据从应用层到驱动,又从驱动又回到了应用层
2. 驱动操作LED灯
2.1. 函数分析
驱动如何操作寄存器:
Led灯的寄存器是物理地址
Linux内核启动之后,操作的全是虚拟地址
这就牵扯到了虚拟地址和物理地址之间的转换
》1.函数原型:void * ioremap(phys_addr_t offset, unsigned long size)
功能:将物理地址映射成虚拟地址
参数:
offset:要映射的地址
size:间下方解释
返回值:
成功:返回虚拟地址
失败:NULL
》2.函数原型:void iounmap(void *addr)
功能:将映射取消
参数:虚拟地址
返回值:
无
2.2. 手册和原理图
1个为4字节,大小就应该写36
红灯引脚
绿灯引脚
蓝灯引脚
想要让灯灭,将三个引脚输出低电平就可
2.3. 编写驱动.c文件
#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>//添加内存映射的函数
unsigned int major =0;
#define NAME "hello"
int error_num=0;
char kbuf[128]={0};
//定义三个灯的基地址
#define RED_BASE 0XC001A000
#define BLUE_BASE 0XC001B000
#define GREEN_BASE 0XC001E000
//定义三个指针用来存放映射后的虚拟地址
unsigned int * red_base=NULL;
unsigned int * blue_base=NULL;
unsigned int * green_base=NULL;
ssize_t mycdev_read (struct file *file, char __user *ubuf, size_t size, loff_t * offs)
{
if(size>sizeof(kbuf))
{
size=sizeof(kbuf);
}
error_num=copy_to_user(ubuf,kbuf,size);
if(error_num)
{
printk("copy_to_user error\n");
return -1;
}
return 0;
}
ssize_t mycdev_write (struct file *file, const char __user *ubuf, size_t size, loff_t *offs)
{
if(size>sizeof(kbuf))
{
size=sizeof(kbuf);
}
error_num=copy_from_user(kbuf,ubuf,size);
if(error_num)
{
printk("copy_from_user error\n");
return -1;
}
return 0;
}
int mycdev_open(struct inode *inode, struct file *file)
{
printk("hello open\n");
return 0;
}
int mycdev_release (struct inode *inode, struct file *file)
{
printk("hello close\n");
return 0;
}
struct file_operations fops={
.open=mycdev_open,
.write=mycdev_write,
.release=mycdev_release,
.read=mycdev_read,
};
static int __init hello_init(void)
{
major= register_chrdev(major, NAME, &fops);
if(major<0)
{
printk("register_chrdev error\n");
return major;
}
//在入口处将物理地址映射成虚拟地址
red_base=ioremap(RED_BASE, 36);//大小最大就是36
if(red_base ==NULL)
{
printk("ioremap error RED_BASE\n");
return -ENOMEM; //返回内存溢出
}
//注意解引用
*red_base&=~(1<<28); //灭红灯
*(red_base+1)|=(1<<28);//设置为输出模式
*(red_base+9)&=~(3<<24);//选择function 0
green_base=ioremap(GREEN_BASE,36);
if(green_base==NULL)
{
printk("ioremap error GREEN_BASE\n");
return -ENOMEM;
}
*green_base&=~(1<<13);//灭绿灯
*(green_base+1)|=(1<<13);//设置为输出模式
*(green_base+8)&=~(3<<26);//选择function 0
blue_base=ioremap(BLUE_BASE,36);
if(blue_base==NULL)
{
printk("ioremap error BLUE_BASE\n");
return -ENOMEM;
}
*blue_base&=~(1<<12);//灭蓝灯
*(blue_base+1)|=(1<<12);//设置为输出模式
*(blue_base+8)|=(1<<25);//将25位置为1
*(blue_base+8)&=~(1<<24);//将24位置为0
return 0;
}
static void __exit hello_exit(void)
{
//注销内存
iounmap(blue_base);//先注册的后注销
iounmap(green_base);
iounmap(red_base);
unregister_chrdev(major, NAME);
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
2.4. 编写驱动的makefile文件
因为驱动是基于开发板写的,编译的时候使用开发板内核
#KERNELDIR:=/lib/modules/$(shell uname -r)/build
KERNELDIR:=/home/hq/temp/kernel-3.4.39/
PWD:=$(shell pwd)
all:
make -C $(KERNELDIR) M=$(PWD) modules
clean:
make -C $(KERNELDIR) M=$(PWD) clean
obj-m:=hello.o
2.5. 执行make命令,生成.ko文件
2.6. 将驱动文件放到nfs同步到开发板中
结合我驱动系统移植篇的博客可以知道我的nfs搭建在
/opt/6818/rootfs/rootfs下
将hello.ko放到此文件下
2.7. 在开发板上安装此驱动
下面是观测的现象