5.2.16.静态映射操作LED3
5.2.16.1、添加驱动中的写函数
(1)先定义好应用和驱动之间的控制接口,这个是由自己来定义的。譬如定义为:应用向驱动写"on"则驱动让LED亮,应用向驱动写"off",驱动就让LED灭
1. 驱动文件 module_test.c
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h>
#include <asm/uaccess.h> //copy_from_user
#include <linux/errno.h> // 错误码
#include <mach/regs-gpio.h>//arch/arm/mach-s5pv210/include/mach/regs-gpio.h 这两个顺序不能放错,c语言基础
#include <mach/gpio-bank.h> //arch/arm/mach-s5pv210/include/mach/gpio-bank.h
#include <linux/string.h>//包含 内核的 memset 、strcmp
#define MYMAJOR 200 /* 定义 register_chrdev 注册设备的 主设备号 */
#define MYNAME "test_char" /* 定义 register_chrdev 注册设备的 设备名字 */
#define GPJ0CON S5PV210_GPJ0CON // FD500240 虚拟地址
#define GPJ0DAT S5PV210_GPJ0DAT // FD500244
#define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)
int mymajor; /* 定义 register_chrdev 注册设备号*/
char kbuf[100];/* 内核空间的 buf*/
/* NOTE 自己定义函数指针 test_chrdev_open */
static int test_chrdev_open(struct inode *inode, struct file *file)
{
/* 这个函数中真正应该 放置 打开这个硬件设备的 操作代码 ,我们先 printk 代替一下 */
printk(KERN_INFO "test_chrdev_open module_test.c->test_chrdev_open \n");
/* 在应用app.c 执行open 时,就会执行 LED亮 */
rGPJ0CON = 0x11111111;
//rGPJ0DAT = ((0<<3)|(0<<4)|(0<<5)); //LED亮
return 0;
} /* test_chrdev_open() */
/* NOTE 自己定义函数指针 test_chrdev_release , release对应的就是 close */
static int test_chrdev_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "test_chrdev_release module_test.c->test_chrdev_release \n");
/* 在应用app.c 执行close 时,就会执行 LED灭 */
rGPJ0DAT = ((1<<3)|(1<<4)|(1<<5)); //LED灭
return 0;
}
static ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t size, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_release module_test.c->test_chrdev_read \n");
/* 从内核 的 kbuf, 复制到用户的 ubuf */
ret = copy_to_user(ubuf,kbuf,size); /* 成功后 就会拷贝到用户 ubuf */
if(ret) /* 如果 不成功复制则返回尚未成功复制剩下的字节数, 这里 就不做 纠错 机制了 */
{
printk(KERN_ERR "copy_to_user fail \n");
return -EINVAL;
}
printk(KERN_INFO "copy_to_user OK!!! module_test.c->test_chrdev_write\n");
return 0;
}
// 写函数的本质:将应用层 传递过来的数据先 复制到 内核中,然后将之正确的方式写入硬件完成的操作!(数据从应用层到驱动层的复制,)
// 内核有一个 虚拟地址空间,应用层有一个 虚拟地址空间
static ssize_t test_chrdev_write(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_release module_test.c->test_chrdev_write\n");
memset(kbuf,0,sizeof(kbuf)); /*清除kbuf */
//使用改函数将: 应用层传过来的 ubuf 中的内容 拷贝到驱动空间中的 一个 kbuf 中
/* 不能用memcpy(kbuf,buf); 因为 2 个 不在一个地址空间中,不能 比较 霍元甲和成龙 谁更厉害 ,不在一个年龄段*/
ret = copy_from_user(kbuf,user_buf,count); /* 成功后 就会 放到 kbuf 中 */
if(ret) /* 如果 不成功复制则返回尚未成功复制剩下的字节数, 这里 就不做 纠错 机制了 */
{
printk(KERN_ERR "copy_from_user fail \n");
return -EINVAL;
}
printk(KERN_INFO "copy_from_user OK!!! module_test.c->test_chrdev_write\n");
/* 真正的 驱动的 数据从 应用层 复制 到 驱动中后,我们就要根据这个数据去写硬件的操作,所以下面就应该操作硬件 */
if (!strcmp(kbuf,"on")) /*strcmp相等的话应该是 0 */
{
rGPJ0DAT = ((0<<3)|(0<<4)|(0<<5)); //LED亮
}
else if(!strcmp(kbuf,"off"))
{
rGPJ0DAT = ((1<<3)|(1<<4)|(1<<5)); //LED灭
}
return 0;
}
//自定义 file_operations 结构体 及其元素填充
/* NOTE 定义 register_chrdev 注册设备的 设备结构体 test_fops */
static const struct file_operations test_fops = {
.owner = THIS_MODULE, /* 所有的驱动 代码这一行不需要动,所有的都是这样,不是函数指针, 惯例直接写即可 */
.open = test_chrdev_open, /* 将来应用 open 打开这个设备时实际 调用的就是这个 .open 函数指针*/
.release = test_chrdev_release, /* release对应的就是 close 函数指针 */
.write = test_chrdev_write,
.read = test_chrdev_read,
};
// 模块安装函数
static int __init chrdev_init(void)
{
//int ret = -1; /* 定义 register_chrdev 的返回值 */
printk(KERN_INFO "chrdev_init helloworld init\n");
// 在 module_init 宏 调用函数中去注册字符串 设备驱动
mymajor = register_chrdev(0, "test_char", &test_fops); /* major设成0,内核帮我们自动分配空白的设备号,分配的值会 做返回值 ,负数还是返回失败 */
if(mymajor < 0)
{
printk(KERN_ERR "registe_chrdev fail \n");
return -EINVAL; /* 返回一个错误码 需要加 ’-‘负号*/
}
printk(KERN_INFO "自动分配 register_chrdev success....mymajor = %d \n",mymajor);
#if 0
/* 现在把 硬件操作放到这里,不用操作app.c 就可以操作硬件 */
rGPJ0CON = 0x11111111;
rGPJ0DAT = ((0<<3)|(0<<4)|(0<<5)); //LED亮
printk(KERN_INFO "S5PV210_GPJ0CON = %p \n",S5PV210_GPJ0CON);
printk(KERN_INFO "S5PV210_GPJ0DAT = %p \n",S5PV210_GPJ0DAT );
#endif
return 0;
}
// 模块卸载函数
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "chrdev_exit helloworld exit\n");
// 在 module_exit宏 调用函数中去注销 字符串 设备驱动
unregister_chrdev(mymajor, "test_char"); /* 这里不判断返回值 了,一般不会出错 */
#if 0
// 模块卸载 LED灭
rGPJ0DAT = ((1<<3)|(1<<4)|(1<<5)); //LED灭
#endif
}
module_init(chrdev_init);
module_exit(chrdev_exit);
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("aston"); // 描述模块的作者
MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息
MODULE_ALIAS("alias xxx"); // 描述模块的别名信息
/***********************************************************
如果 KERN_DEBUG 打印不出来,更改打印级别 或者
printk(KERN_DEBUG "chrdev_init helloworld init\n");
[root@liang_x210 driver_test]# cat /proc/sys/kernel/printk
7 4 1 7
[root@liang_x210 driver_test]# echo 8 > /proc/sys/kernel/printk
[root@liang_x210 driver_test]# cat /proc/sys/kernel/printk
8 4 1 7
************************************************************/
2. app.c(Mkfile无更改)
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define FILE "/dev/test" // 刚才mknod创建的设备文件 名,必须保持一致
char buf[100];
int main(void)
{
int fd = -1;
fd = open(FILE, O_RDWR);
if(fd < 0)
{
printf("open %s error \n",FILE);
return -1;
}
printf("open %s success..\n",FILE);
//读写文件
write(fd,"on",2);
sleep(2);
write(fd,"off",3);
sleep(2);
write(fd,"on",2);
sleep(2);
write(fd,"off",3);
sleep(2);
//关闭文件
close(fd);
return 0;
}
运行效果:
上面的代码可以正常运行,但是实际开发中,一般驱动中只要可以实现简单功能,更多的功能代码都放到 app.c 看下面的示例:
(2)应用和驱动的接口定义做的尽量简单,譬如用1个字目来表示。譬如定义为:应用写"1"(字符串)表示灯亮,写"0"表示让灯灭。
5.2.16.2、写应用来测试写函数
5.2.16.3、驱动和应用中来添加读功能
1. 驱动文件 module_test.c
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h>
#include <asm/uaccess.h> //copy_from_user
#include <linux/errno.h> // 错误码
#include <mach/regs-gpio.h>//arch/arm/mach-s5pv210/include/mach/regs-gpio.h 这两个顺序不能放错,c语言基础
#include <mach/gpio-bank.h> //arch/arm/mach-s5pv210/include/mach/gpio-bank.h
#include <linux/string.h>//包含 内核的 memset 、strcmp
#define MYMAJOR 200 /* 定义 register_chrdev 注册设备的 主设备号 */
#define MYNAME "test_char" /* 定义 register_chrdev 注册设备的 设备名字 */
#define GPJ0CON S5PV210_GPJ0CON // FD500240 虚拟地址
#define GPJ0DAT S5PV210_GPJ0DAT // FD500244
#define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)
int mymajor; /* 定义 register_chrdev 注册设备号*/
char kbuf[100];/* 内核空间的 buf*/
/* NOTE 自己定义函数指针 test_chrdev_open */
static int test_chrdev_open(struct inode *inode, struct file *file)
{
/* 这个函数中真正应该 放置 打开这个硬件设备的 操作代码 ,我们先 printk 代替一下 */
printk(KERN_INFO "test_chrdev_open module_test.c->test_chrdev_open \n");
/* 在应用app.c 执行open 时,就会执行 LED */
rGPJ0CON = 0x11111111;
//rGPJ0DAT = ((0<<3)|(0<<4)|(0<<5)); //LED亮
return 0;
} /* test_chrdev_open() */
/* NOTE 自己定义函数指针 test_chrdev_release , release对应的就是 close */
static int test_chrdev_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "test_chrdev_release module_test.c->test_chrdev_release \n");
/* 在应用app.c 执行close 时,就会执行 LED灭 */
rGPJ0DAT = ((1<<3)|(1<<4)|(1<<5)); //LED灭
return 0;
}
static ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t size, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_release module_test.c->test_chrdev_read \n");
/* 从内核 的 kbuf, 复制到用户的 ubuf */
ret = copy_to_user(ubuf,kbuf,size); /* 成功后 就会拷贝到用户 ubuf */
if(ret) /* 如果 不成功复制则返回尚未成功复制剩下的字节数, 这里 就不做 纠错 机制了 */
{
printk(KERN_ERR "copy_to_user fail \n");
return -EINVAL;
}
printk(KERN_INFO "copy_to_user OK!!! module_test.c->test_chrdev_write\n");
return 0;
}
// 写函数的本质:将应用层 传递过来的数据先 复制到 内核中,然后将之正确的方式写入硬件完成的操作!(数据从应用层到驱动层的复制,)
// 内核有一个 虚拟地址空间,应用层有一个 虚拟地址空间
static ssize_t test_chrdev_write(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_release module_test.c->test_chrdev_write\n");
memset(kbuf,0,sizeof(kbuf)); /*清除kbuf */
//使用改函数将: 应用层传过来的 ubuf 中的内容 拷贝到驱动空间中的 一个 kbuf 中
/* 不能用memcpy(kbuf,buf); 因为 2 个 不在一个地址空间中,不能 比较 霍元甲和成龙 谁更厉害 ,不在一个年龄段*/
ret = copy_from_user(kbuf,user_buf,count); /* 成功后 就会 放到 kbuf 中 */
if(ret) /* 如果 不成功复制则返回尚未成功复制剩下的字节数, 这里 就不做 纠错 机制了 */
{
printk(KERN_ERR "copy_from_user fail \n");
return -EINVAL;
}
printk(KERN_INFO "copy_from_user OK!!! module_test.c->test_chrdev_write\n");
/* 真正的 驱动的 数据从 应用层 复制 到 驱动中后,我们就要根据这个数据去写硬件的操作,所以下面就应该操作硬件 */
if(kbuf[0] == '1')
{
rGPJ0DAT = ((0<<3)|(0<<4)|(0<<5)); //LED亮
}
else if (kbuf[0]=='0')
{
rGPJ0DAT = ((1<<3)|(1<<4)|(1<<5)); //LED灭
}
return 0;
}
//自定义 file_operations 结构体 及其元素填充
/* NOTE 定义 register_chrdev 注册设备的 设备结构体 test_fops */
static const struct file_operations test_fops = {
.owner = THIS_MODULE, /* 所有的驱动 代码这一行不需要动,所有的都是这样,不是函数指针, 惯例直接写即可 */
.open = test_chrdev_open, /* 将来应用 open 打开这个设备时实际 调用的就是这个 .open 函数指针*/
.release = test_chrdev_release, /* release对应的就是 close 函数指针 */
.write = test_chrdev_write,
.read = test_chrdev_read,
};
// 模块安装函数
static int __init chrdev_init(void)
{
//int ret = -1; /* 定义 register_chrdev 的返回值 */
printk(KERN_INFO "chrdev_init helloworld init\n");
// 在 module_init 宏 调用函数中去注册字符串 设备驱动
mymajor = register_chrdev(0, "test_char", &test_fops); /* major设成0,内核帮我们自动分配空白的设备号,分配的值会 做返回值 ,负数还是返回失败 */
if(mymajor < 0)
{
printk(KERN_ERR "registe_chrdev fail \n");
return -EINVAL; /* 返回一个错误码 需要加 ’-‘负号*/
}
printk(KERN_INFO "自动分配 register_chrdev success....mymajor = %d \n",mymajor);
#if 0
/* 现在把 硬件操作放到这里,不用操作app.c 就可以操作硬件 */
rGPJ0CON = 0x11111111;
rGPJ0DAT = ((0<<3)|(0<<4)|(0<<5)); //LED亮
printk(KERN_INFO "S5PV210_GPJ0CON = %p \n",S5PV210_GPJ0CON);
printk(KERN_INFO "S5PV210_GPJ0DAT = %p \n",S5PV210_GPJ0DAT );
#endif
return 0;
}
// 模块卸载函数
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "chrdev_exit helloworld exit\n");
// 在 module_exit宏 调用函数中去注销 字符串 设备驱动
unregister_chrdev(mymajor, "test_char"); /* 这里不判断返回值 了,一般不会出错 */
#if 0
// 模块卸载 LED灭
rGPJ0DAT = ((1<<3)|(1<<4)|(1<<5)); //LED灭
#endif
}
module_init(chrdev_init);
module_exit(chrdev_exit);
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("aston"); // 描述模块的作者
MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息
MODULE_ALIAS("alias xxx"); // 描述模块的别名信息
/***********************************************************
如果 KERN_DEBUG 打印不出来,更改打印级别 或者
printk(KERN_DEBUG "chrdev_init helloworld init\n");
[root@liang_x210 driver_test]# cat /proc/sys/kernel/printk
7 4 1 7
[root@liang_x210 driver_test]# echo 8 > /proc/sys/kernel/printk
[root@liang_x210 driver_test]# cat /proc/sys/kernel/printk
8 4 1 7
************************************************************/
2. app.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#define FILE "/dev/test" // 刚才mknod创建的设备文件 名,必须保持一致
char buf[100];
int main(void)
{
int fd = -1;
int i= 0;
fd = open(FILE, O_RDWR);
if(fd < 0)
{
printf("open %s error \n",FILE);
return -1;
}
printf("open %s success..\n",FILE);
#if 0
//璇诲啓鏂囦欢
write(fd, "1",1);//亮
sleep(2);
write(fd, "0",1);//灭
sleep(2);
write(fd, "1",1);
sleep(2);
#endif
while(1)
{
memset(buf, 0, sizeof(buf));
printf("请输入 on|off \n\n");
scanf("%s",buf);
if (!strcmp(buf,"on"))
{
write(fd, "1",1);//亮
}
else if (!strcmp(buf,"off"))
{
write(fd, "0",1);//灭
}
else if (!strcmp(buf,"flash"))
{
for(i=0;i<3;i++)
{
write(fd, "1",1);//亮
sleep(2);
write(fd, "0",1);//灭
sleep(2);
}
}
else if (!strcmp(buf,"quit"))
{
break;
}
}
//鍏抽棴鏂囦欢
close(fd);
return 0;
}
3.(Mkfile无更改)
#ubuntu的内核源码树,如果要编译在ubuntu中安装的模块就打开这2个
#KERN_VER = $(shell uname -r)
#KERN_DIR = /lib/modules/$(KERN_VER)/build
# 开发板的linux内核的源码树目录
KERN_DIR = /root/driver/kernel
obj-m += module_test.o
all:
make -C $(KERN_DIR) M=`pwd` modules
# app 是在 开发板运行 所以 arm-linux-gcc
arm-linux-gcc app.c -o app
cp:
cp *.ko app /root/rootfs/rootfs/driver_test
#cp app /root/rootfs/rootfs/driver_test
.PHONY: clean
clean:
rm -rf app
make -C $(KERN_DIR) M=`pwd` modules clean
运行效果: