3.字符设备驱动高级
转自 https://edu.csdn.net/lecturer/505 朱老师物联网大讲堂
《5.linux驱动开发-第3部分-5.3.字符设备驱动高级》
第一部分、章节目录
5.3.1.注册字符设备驱动新接口1
5.3.2.注册字符设备驱动新接口2
5.3.3.注册字符设备驱动新接口3
5.3.4.注册字符设备驱动新接口4
5.3.5.字符设备驱动注册代码分析1
5.3.6.字符设备驱动注册代码分析2
5.3.7.自动创建字符设备驱动的设备文件
5.3.8.设备类相关代码分析1
5.3.9.设备类相关代码分析2
5.3.10.静态映射表建立过程分析
5.3.11.动态映射结构体方式操作寄存器
5.3.12.内核提供的读写寄存器接口
第二部分、章节介绍
5.3.1.注册字符设备驱动新接口1
本节介绍内核中提供的字符设备驱动注册的新接口cdev,并且讲了相关的接口函数,最后实践编写代码。
5.3.2.注册字符设备驱动新接口2
本节对上节讲述的知识进行实践编程测试。
5.3.3.注册字符设备驱动新接口3
本节讲述新接口如何自动分配设备号,以及其他一些编程细节如错误的逐级处理技巧。
5.3.4.注册字符设备驱动新接口4
本节讲述cdev_alloc和cdev_init这两个接口,同时引申讲解了C语言如何以面向对象的编程方式来实现linux内核。
5.3.5.字符设备驱动注册代码分析1
本节带大家浏览分析内核源码中与字符设备驱动相关的接口,使用SourceInsight逐级追踪的方式进入内核源码中。
5.3.6.字符设备驱动注册代码分析2
本节继续上节分析字符设备驱动注册相关的接口函数,目的是教大家学习如何从源码中去学习。
5.3.7.自动创建字符设备驱动的设备文件
本节实践编程演示如何使用class_create和device_create这两个接口来让字符设备驱动借助设备类自动创建及删除设备文件。
5.3.8.设备类相关代码分析1
本节开始分析class_create和device_create内部的实现原理。
5.3.9.设备类相关代码分析2
本节接上节继续分析,通过分析让大家对sysfs有所了解,知晓内核如果通过sysfs和udev进行通信以实现设备文件的自动创建和删除。
5.3.10.静态映射表建立过程分析
本节分析内核源码中与虚拟地址静态映射建立有关的代码,通过分析大家可以进一步掌握静态映射的实现细节。
5.3.11.动态映射结构体方式操作寄存器
本节对5.2.17中使用动态映射方式得到多个寄存器虚拟地址的代码进行改进,使用结构体封装的方式让我们能够方便的映射多个寄存器。
5.3.12.内核提供的读写寄存器接口
本节介绍内核提供的writel/readl和iowrite32/ioread32等读写寄存器的接口,并且对之前的驱动进行改进,和内核中典型的驱动程序进行对比学习。
第三部分、随堂记录
5.3.1.注册字符设备驱动新接口1
5.3.1.1、新接口与老接口
(1)老接口:register_chrdev
(2)新接口:register_chrdev_region/alloc_chrdev_region + cdev
(3)为什么需要新接口
5.3.1.2、cdev介绍
(1)结构体
(2)相关函数:cdev_alloc、cdev_init、cdev_add、cdev_del
5.3.1.3、设备号
(1)主设备号和次设备号
(2)dev_t类型
(3)MKDEV、MAJOR、MINOR三个宏
5.3.1.4、编程实践
(1)使用register_chrdev_region + cdev_init + cdev_add进行字符设备驱动注册
#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
arm-linux-gcc app.c -o app
cp:
cp *.ko /root/porting_x210/rootfs/rootfs/driver_test
cp app /root/porting_x210/rootfs/rootfs/driver_test
.PHONY: clean
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf app
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h> // arch/arm/mach-s5pv210/include/mach/gpio-bank.h
#include <linux/string.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/cdev.h>
#define MYMAJOR 200
#define MYCNT 1
#define MYNAME "testchar"
#define GPJ0CON S5PV210_GPJ0CON
#define GPJ0DAT S5PV210_GPJ0DAT
#define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)
#define GPJ0CON_PA 0xe0200240
#define GPJ0DAT_PA 0xe0200244
unsigned int *pGPJ0CON;
unsigned int *pGPJ0DAT;
//**********************************************/
int mymajor;
static dev_t mydev;
static struct cdev test_cdev;
//***********************************************/
char kbuf[100]; // 内核空间的buf
static int test_chrdev_open(struct inode *inode, struct file *file)
{
// 这个函数中真正应该放置的是打开这个设备的硬件操作代码部分
// 但是现在暂时我们写不了这么多,所以用一个printk打印个信息来做代表。
printk(KERN_INFO "test_chrdev_open\n");
rGPJ0CON = 0x11111111;
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); // 亮
return 0;
}
static int test_chrdev_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "test_chrdev_release\n");
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
return 0;
}
ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_read\n");
ret = copy_to_user(ubuf, kbuf, count);
if (ret)
{
printk(KERN_ERR "copy_to_user fail\n");
return -EINVAL;
}
printk(KERN_INFO "copy_to_user success..\n");
return 0;
}
// 写函数的本质就是将应用层传递过来的数据先复制到内核中,然后将之以正确的方式写入硬件完成操作。
static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf,
size_t count, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_write\n");
// 使用该函数将应用层传过来的ubuf中的内容拷贝到驱动空间中的一个buf中
//memcpy(kbuf, ubuf); // 不行,因为2个不在一个地址空间中
memset(kbuf, 0, sizeof(kbuf));
ret = copy_from_user(kbuf, ubuf, count);
if (ret)
{
printk(KERN_ERR "copy_from_user fail\n");
return -EINVAL;
}
printk(KERN_INFO "copy_from_user success..\n");
if (kbuf[0] == '1')
{
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
}
else if (kbuf[0] == '0')
{
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
}
return 0;
}
//*****************************************************************************/
// 自定义一个file_operations结构体变量,并且去填充
static const struct file_operations test_fops = {
.owner = THIS_MODULE, // 惯例,直接写即可
.open = test_chrdev_open, // 将来应用open打开这个设备时实际调用的
.release = test_chrdev_release, // 就是这个.open对应的函数
.write = test_chrdev_write,
.read = test_chrdev_read,
};
//*****************************************************************************/
static int __init chrdev_init(void)
{
int retval;
printk(KERN_INFO "chrdev_init helloworld init\n");
// 使用新的cdev接口来注册字符设备驱动
// 新的接口注册字符设备驱动需要2步
//*****************************************************************************/
* // 第1步:注册/分配主次设备号
* mydev = MKDEV(MYMAJOR, 0);
* retval = register_chrdev_region(mydev, MYCNT, MYNAME);
* if (retval) {
* printk(KERN_ERR "Unable to register minors for %s\n", MYNAME);
* return -EINVAL;
* }
* printk(KERN_INFO "register_chrdev_region success\n");
* // 第2步:注册字符设备驱动
* cdev_init(&test_cdev, &test_fops);
* retval = cdev_add(&test_cdev, mydev, MYCNT);
* if (retval) {
* printk(KERN_ERR "Unable to cdev_add\n");
* return -EINVAL;
* }
* printk(KERN_INFO "cdev_add success\n");
//*****************************************************************************/
// 使用动态映射的方式来操作寄存器
if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON"))
return -EINVAL;
if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0CON"))
return -EINVAL;
pGPJ0CON = ioremap(GPJ0CON_PA, 4);
pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);
*pGPJ0CON = 0x11111111;
*pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); // 亮
return 0;
}
// 模块下载函数
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "chrdev_exit helloworld exit\n");
*pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
// 解除映射
iounmap(pGPJ0CON);
iounmap(pGPJ0DAT);
release_mem_region(GPJ0CON_PA, 4);
release_mem_region(GPJ0DAT_PA, 4);
/*
// 在module_exit宏调用的函数中去注销字符设备驱动
unregister_chrdev(mymajor, MYNAME);
*/
//*****************************************************************************/
// 使用新的接口来注销字符设备驱动
// 注销分2步:
// 第一步真正注销字符设备驱动用cdev_del
cdev_del(&test_cdev);
// 第二步去注销申请的主次设备号
unregister_chrdev_region(mydev, MYCNT);
//*****************************************************************************/
}
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 <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);
/*
// 读写文件
write(fd, "on", 2);
sleep(2);
write(fd, "off", 3);
sleep(2);
write(fd, "on", 2);
sleep(2);
*/
/*
write(fd, "1", 1);
sleep(2);
write(fd, "0", 1);
sleep(2);
write(fd, "1", 1);
sleep(2);
*/
while (1)
{
memset(buf, 0 , sizeof(buf));
printf("请输入 on | off \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(1);
write(fd, "0", 1);
sleep(1);
}
}
else if (!strcmp(buf, "quit"))
{
break;
}
}
// 关闭文件
close(fd);
return 0;
}
5.3.2.注册字符设备驱动新接口2
5.3.2.1、实践编程
5.3.2.2、测试
5.3.3.注册字符设备驱动新接口3
5.3.2.1、使用alloc_chrdev_region自动分配设备号
(1)register_chrdev_region是在事先知道要使用的主、次设备号时使用的;要先查看cat /proc/devices去查看没有使用的。
(2)更简便、更智能的方法是让内核给我们自动分配一个主设备号,使用alloc_chrdev_region就可以自动分配了。
(3)自动分配的设备号,我们必须去知道他的主次设备号,否则后面没法去mknod创建他对应的设备文件。
5.3.2.2、得到分配的主设备号和次设备号
(1)使用MAJOR宏和MINOR宏从dev_t得到major和minor
(2)反过来使用MKDEV宏从major和minor得到dev_t。
(3)使用这些宏的代码具有可移植性
5.3.2.3、中途出错的倒影式错误处理方法
(1)内核中很多函数中包含了很多个操作,这些操作每一步都有可能出错,而且出错后后面的步骤就没有进行下去的必要性了。
#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
arm-linux-gcc app.c -o app
cp:
cp *.ko /root/porting_x210/rootfs/rootfs/driver_test
cp app /root/porting_x210/rootfs/rootfs/driver_test
.PHONY: clean
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf app
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h> // arch/arm/mach-s5pv210/include/mach/gpio-bank.h
#include <linux/string.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/cdev.h>
//#define MYMAJOR 200
#define MYCNT 1
#define MYNAME "testchar"
#define GPJ0CON S5PV210_GPJ0CON
#define GPJ0DAT S5PV210_GPJ0DAT
#define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)
#define GPJ0CON_PA 0xe0200240
#define GPJ0DAT_PA 0xe0200244
unsigned int *pGPJ0CON;
unsigned int *pGPJ0DAT;
//int mymajor;
static dev_t mydev;
static struct cdev test_cdev;
char kbuf[100]; // 内核空间的buf
static int test_chrdev_open(struct inode *inode, struct file *file)
{
// 这个函数中真正应该放置的是打开这个设备的硬件操作代码部分
// 但是现在暂时我们写不了这么多,所以用一个printk打印个信息来做代表。
printk(KERN_INFO "test_chrdev_open\n");
rGPJ0CON = 0x11111111;
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); // 亮
return 0;
}
static int test_chrdev_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "test_chrdev_release\n");
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
return 0;
}
ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_read\n");
ret = copy_to_user(ubuf, kbuf, count);
if (ret)
{
printk(KERN_ERR "copy_to_user fail\n");
return -EINVAL;
}
printk(KERN_INFO "copy_to_user success..\n");
return 0;
}
// 写函数的本质就是将应用层传递过来的数据先复制到内核中,然后将之以正确的方式写入硬件完成操作。
static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf,
size_t count, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_write\n");
// 使用该函数将应用层传过来的ubuf中的内容拷贝到驱动空间中的一个buf中
//memcpy(kbuf, ubuf); // 不行,因为2个不在一个地址空间中
memset(kbuf, 0, sizeof(kbuf));
ret = copy_from_user(kbuf, ubuf, count);
if (ret)
{
printk(KERN_ERR "copy_from_user fail\n");
return -EINVAL;
}
printk(KERN_INFO "copy_from_user success..\n");
if (kbuf[0] == '1')
{
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
}
else if (kbuf[0] == '0')
{
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
}
/*
// 真正的驱动中,数据从应用层复制到驱动中后,我们就要根据这个数据
// 去写硬件完成硬件的操作。所以这下面就应该是操作硬件的代码
if (!strcmp(kbuf, "on"))
{
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
}
else if (!strcmp(kbuf, "off"))
{
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
}
*/
return 0;
}
// 自定义一个file_operations结构体变量,并且去填充
static const struct file_operations test_fops = {
.owner = THIS_MODULE, // 惯例,直接写即可
.open = test_chrdev_open, // 将来应用open打开这个设备时实际调用的
.release = test_chrdev_release, // 就是这个.open对应的函数
.write = test_chrdev_write,
.read = test_chrdev_read,
};
// 模块安装函数
static int __init chrdev_init(void)
{
int retval;
printk(KERN_INFO "chrdev_init helloworld init\n");
// 使用新的cdev接口来注册字符设备驱动
// 新的接口注册字符设备驱动需要2步
// 第1步:分配主次设备号
retval = alloc_chrdev_region(&mydev, 12, MYCNT, MYNAME);
if (retval < 0)
{
printk(KERN_ERR "Unable to alloc minors for %s\n", MYNAME);
goto flag1;
}
printk(KERN_INFO "alloc_chrdev_region success\n");
printk(KERN_INFO "major = %d, minor = %d.\n", MAJOR(mydev), MINOR(mydev));
// 第2步:注册字符设备驱动
cdev_init(&test_cdev, &test_fops);
retval = cdev_add(&test_cdev, mydev, MYCNT);
if (retval) {
printk(KERN_ERR "Unable to cdev_add\n");
goto flag2;
}
printk(KERN_INFO "cdev_add success\n");
// 使用动态映射的方式来操作寄存器
if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON"))
// return -EINVAL;
goto flag3;
if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0CON"))
// return -EINVAL;
goto flag3;
pGPJ0CON = ioremap(GPJ0CON_PA, 4);
pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);
*pGPJ0CON = 0x11111111;
*pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); // 亮
//goto flag0:
return 0;
// 如果第4步才出错跳转到这里来
flag4:
release_mem_region(GPJ0CON_PA, 4);
release_mem_region(GPJ0DAT_PA, 4);
// 如果第3步才出错跳转到这里来
flag3:
cdev_del(&test_cdev);
// 如果第2步才出错跳转到这里来
flag2:
// 在这里把第1步做成功的东西给注销掉
unregister_chrdev_region(mydev, MYCNT);
// 如果第1步才出错跳转到这里来
flag1:
return -EINVAL;
//flag0:
// return 0;
}
// 模块下载函数
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "chrdev_exit helloworld exit\n");
*pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
// 解除映射
iounmap(pGPJ0CON);
iounmap(pGPJ0DAT);
release_mem_region(GPJ0CON_PA, 4);
release_mem_region(GPJ0DAT_PA, 4);
/*
// 在module_exit宏调用的函数中去注销字符设备驱动
unregister_chrdev(mymajor, MYNAME);
*/
// 使用新的接口来注销字符设备驱动
// 注销分2步:
// 第一步真正注销字符设备驱动用cdev_del
cdev_del(&test_cdev);
// 第二步去注销申请的主次设备号
unregister_chrdev_region(mydev, MYCNT);
}
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 <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);
/*
// 读写文件
write(fd, "on", 2);
sleep(2);
write(fd, "off", 3);
sleep(2);
write(fd, "on", 2);
sleep(2);
*/
/*
write(fd, "1", 1);
sleep(2);
write(fd, "0", 1);
sleep(2);
write(fd, "1", 1);
sleep(2);
*/
while (1)
{
memset(buf, 0 , sizeof(buf));
printf("请输入 on | off \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(1);
write(fd, "0", 1);
sleep(1);
}
}
else if (!strcmp(buf, "quit"))
{
break;
}
}
// 关闭文件
close(fd);
return 0;
}
5.3.4.注册字符设备驱动新接口4
5.3.4.1、使用cdev_alloc
(1)cdev_alloc的编程实践
(2)从内存角度体会cdev_alloc用与不用的差别
(3)这就是非面向对象的语言和面向对象的代码
(4)再次感叹C语言的博大精深,好好去看《4.C语言高级专题》
5.3.4.2、cdev_init的替代
(1)cdev_init源码分析
(2)不使用cdev_init时的编程
(3)为什么讲这个
#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
arm-linux-gcc app.c -o app
cp:
cp *.ko /root/porting_x210/rootfs/rootfs/driver_test
cp app /root/porting_x210/rootfs/rootfs/driver_test
.PHONY: clean
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf app
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h> // arch/arm/mach-s5pv210/include/mach/gpio-bank.h
#include <linux/string.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/cdev.h>
//#define MYMAJOR 200
#define MYCNT 1
#define MYNAME "testchar"
#define GPJ0CON S5PV210_GPJ0CON
#define GPJ0DAT S5PV210_GPJ0DAT
#define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)
#define GPJ0CON_PA 0xe0200240
#define GPJ0DAT_PA 0xe0200244
unsigned int *pGPJ0CON;
unsigned int *pGPJ0DAT;
//int mymajor;
static dev_t mydev;
//static struct cdev test_cdev;
static struct cdev *pcdev;
char kbuf[100]; // 内核空间的buf
static int test_chrdev_open(struct inode *inode, struct file *file)
{
// 这个函数中真正应该放置的是打开这个设备的硬件操作代码部分
// 但是现在暂时我们写不了这么多,所以用一个printk打印个信息来做代表。
printk(KERN_INFO "test_chrdev_open\n");
rGPJ0CON = 0x11111111;
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); // 亮
return 0;
}
static int test_chrdev_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "test_chrdev_release\n");
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
return 0;
}
ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_read\n");
ret = copy_to_user(ubuf, kbuf, count);
if (ret)
{
printk(KERN_ERR "copy_to_user fail\n");
return -EINVAL;
}
printk(KERN_INFO "copy_to_user success..\n");
return 0;
}
// 写函数的本质就是将应用层传递过来的数据先复制到内核中,然后将之以正确的方式写入硬件完成操作。
static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf,
size_t count, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_write\n");
// 使用该函数将应用层传过来的ubuf中的内容拷贝到驱动空间中的一个buf中
//memcpy(kbuf, ubuf); // 不行,因为2个不在一个地址空间中
memset(kbuf, 0, sizeof(kbuf));
ret = copy_from_user(kbuf, ubuf, count);
if (ret)
{
printk(KERN_ERR "copy_from_user fail\n");
return -EINVAL;
}
printk(KERN_INFO "copy_from_user success..\n");
if (kbuf[0] == '1')
{
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
}
else if (kbuf[0] == '0')
{
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
}
/*
// 真正的驱动中,数据从应用层复制到驱动中后,我们就要根据这个数据
// 去写硬件完成硬件的操作。所以这下面就应该是操作硬件的代码
if (!strcmp(kbuf, "on"))
{
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
}
else if (!strcmp(kbuf, "off"))
{
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
}
*/
return 0;
}
// 自定义一个file_operations结构体变量,并且去填充
static const struct file_operations test_fops = {
.owner = THIS_MODULE, // 惯例,直接写即可
.open = test_chrdev_open, // 将来应用open打开这个设备时实际调用的
.release = test_chrdev_release, // 就是这个.open对应的函数
.write = test_chrdev_write,
.read = test_chrdev_read,
};
// 模块安装函数
static int __init chrdev_init(void)
{
int retval;
printk(KERN_INFO "chrdev_init helloworld init\n");
// 使用新的cdev接口来注册字符设备驱动
// 新的接口注册字符设备驱动需要2步
// 第1步:分配主次设备号
retval = alloc_chrdev_region(&mydev, 12, MYCNT, MYNAME);
if (retval < 0)
{
printk(KERN_ERR "Unable to alloc minors for %s\n", MYNAME);
goto flag1;
}
printk(KERN_INFO "alloc_chrdev_region success\n");
printk(KERN_INFO "major = %d, minor = %d.\n", MAJOR(mydev), MINOR(mydev));
// 第2步:注册字符设备驱动
pcdev = cdev_alloc(); // 给pcdev分配内存,指针实例化
//cdev_init(pcdev, &test_fops);
pcdev->owner = THIS_MODULE;
pcdev->ops = &test_fops;
retval = cdev_add(pcdev, mydev, MYCNT);
if (retval) {
printk(KERN_ERR "Unable to cdev_add\n");
goto flag2;
}
printk(KERN_INFO "cdev_add success\n");
// 使用动态映射的方式来操作寄存器
if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON"))
// return -EINVAL;
goto flag3;
if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0CON"))
// return -EINVAL;
goto flag3;
pGPJ0CON = ioremap(GPJ0CON_PA, 4);
pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);
*pGPJ0CON = 0x11111111;
*pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); // 亮
//goto flag0:
return 0;
// 如果第4步才出错跳转到这里来
flag4:
release_mem_region(GPJ0CON_PA, 4);
release_mem_region(GPJ0DAT_PA, 4);
// 如果第3步才出错跳转到这里来
flag3:
cdev_del(pcdev);
// 如果第2步才出错跳转到这里来
flag2:
// 在这里把第1步做成功的东西给注销掉
unregister_chrdev_region(mydev, MYCNT);
// 如果第1步才出错跳转到这里来
flag1:
return -EINVAL;
//flag0:
// return 0;
}
// 模块下载函数
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "chrdev_exit helloworld exit\n");
*pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
// 解除映射
iounmap(pGPJ0CON);
iounmap(pGPJ0DAT);
release_mem_region(GPJ0CON_PA, 4);
release_mem_region(GPJ0DAT_PA, 4);
/*
// 在module_exit宏调用的函数中去注销字符设备驱动
unregister_chrdev(mymajor, MYNAME);
*/
// 使用新的接口来注销字符设备驱动
// 注销分2步:
// 第一步真正注销字符设备驱动用cdev_del
cdev_del(pcdev);
// 第二步去注销申请的主次设备号
unregister_chrdev_region(mydev, MYCNT);
}
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 <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);
/*
// 读写文件
write(fd, "on", 2);
sleep(2);
write(fd, "off", 3);
sleep(2);
write(fd, "on", 2);
sleep(2);
*/
/*
write(fd, "1", 1);
sleep(2);
write(fd, "0", 1);
sleep(2);
write(fd, "1", 1);
sleep(2);
*/
while (1)
{
memset(buf, 0 , sizeof(buf));
printf("请输入 on | off \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(1);
write(fd, "0", 1);
sleep(1);
}
}
else if (!strcmp(buf, "quit"))
{
break;
}
}
// 关闭文件
close(fd);
return 0;
}
5.3.5.字符设备驱动注册代码分析1
5.3.5.1、老接口分析
register_chrdev
__register_chrdev
__register_chrdev_region
cdev_alloc
cdev_add
5.3.5.2、新接口分析
register_chrdev_region
__register_chrdev_region
alloc_chrdev_region
__register_chrdev_region
5.3.6.字符设备驱动注册代码分析2
5.3.7.自动创建字符设备驱动的设备文件
5.3.7.1、问题描述:
(1)整体流程回顾
(2)使用mknod创建设备文件的缺点
(3)能否自动生成和删除设备文件
5.3.7.2、解决方案:udev(嵌入式中用的是mdev)
(1)什么是udev?应用层的一个应用程序
(2)内核驱动和应用层udev之间有一套信息传输机制(netlink协议)
(3)应用层启用udev,内核驱动中使用相应接口
(4)驱动注册和注销时信息会被传给udev,由udev在应用层进行设备文件的创建和删除
5.3.7.3、内核驱动设备类相关函数
(1)class_create
(2)device_create
5.3.7.4、编程实践
#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
arm-linux-gcc app.c -o app
cp:
cp *.ko /root/porting_x210/rootfs/rootfs/driver_test
cp app /root/porting_x210/rootfs/rootfs/driver_test
.PHONY: clean
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf app
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h> // arch/arm/mach-s5pv210/include/mach/gpio-bank.h
#include <linux/string.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/cdev.h>
**#include <linux/device.h>**
//#define MYMAJOR 200
#define MYCNT 1
#define MYNAME "testchar"
#define GPJ0CON S5PV210_GPJ0CON
#define GPJ0DAT S5PV210_GPJ0DAT
#define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)
#define GPJ0CON_PA 0xe0200240
#define GPJ0DAT_PA 0xe0200244
unsigned int *pGPJ0CON;
unsigned int *pGPJ0DAT;
//int mymajor;
static dev_t mydev;
//static struct cdev test_cdev;
static struct cdev *pcdev;
static struct class *test_class;
char kbuf[100]; // 内核空间的buf
static int test_chrdev_open(struct inode *inode, struct file *file)
{
// 这个函数中真正应该放置的是打开这个设备的硬件操作代码部分
// 但是现在暂时我们写不了这么多,所以用一个printk打印个信息来做代表。
printk(KERN_INFO "test_chrdev_open\n");
rGPJ0CON = 0x11111111;
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); // 亮
return 0;
}
static int test_chrdev_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "test_chrdev_release\n");
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
return 0;
}
ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_read\n");
ret = copy_to_user(ubuf, kbuf, count);
if (ret)
{
printk(KERN_ERR "copy_to_user fail\n");
return -EINVAL;
}
printk(KERN_INFO "copy_to_user success..\n");
return 0;
}
// 写函数的本质就是将应用层传递过来的数据先复制到内核中,然后将之以正确的方式写入硬件完成操作。
static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf,
size_t count, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_write\n");
// 使用该函数将应用层传过来的ubuf中的内容拷贝到驱动空间中的一个buf中
//memcpy(kbuf, ubuf); // 不行,因为2个不在一个地址空间中
memset(kbuf, 0, sizeof(kbuf));
ret = copy_from_user(kbuf, ubuf, count);
if (ret)
{
printk(KERN_ERR "copy_from_user fail\n");
return -EINVAL;
}
printk(KERN_INFO "copy_from_user success..\n");
if (kbuf[0] == '1')
{
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
}
else if (kbuf[0] == '0')
{
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
}
/*
// 真正的驱动中,数据从应用层复制到驱动中后,我们就要根据这个数据
// 去写硬件完成硬件的操作。所以这下面就应该是操作硬件的代码
if (!strcmp(kbuf, "on"))
{
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
}
else if (!strcmp(kbuf, "off"))
{
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
}
*/
return 0;
}
// 自定义一个file_operations结构体变量,并且去填充
static const struct file_operations test_fops = {
.owner = THIS_MODULE, // 惯例,直接写即可
.open = test_chrdev_open, // 将来应用open打开这个设备时实际调用的
.release = test_chrdev_release, // 就是这个.open对应的函数
.write = test_chrdev_write,
.read = test_chrdev_read,
};
// 模块安装函数
static int __init chrdev_init(void)
{
int retval;
printk(KERN_INFO "chrdev_init helloworld init\n");
// 使用新的cdev接口来注册字符设备驱动
// 新的接口注册字符设备驱动需要2步
// 第1步:分配主次设备号
retval = alloc_chrdev_region(&mydev, 12, MYCNT, MYNAME);
if (retval < 0)
{
printk(KERN_ERR "Unable to alloc minors for %s\n", MYNAME);
goto flag1;
}
printk(KERN_INFO "alloc_chrdev_region success\n");
printk(KERN_INFO "major = %d, minor = %d.\n", MAJOR(mydev), MINOR(mydev));
// 第2步:注册字符设备驱动
pcdev = cdev_alloc(); // 给pcdev分配内存,指针实例化
//cdev_init(pcdev, &test_fops);
pcdev->owner = THIS_MODULE;
pcdev->ops = &test_fops;
retval = cdev_add(pcdev, mydev, MYCNT);
if (retval) {
printk(KERN_ERR "Unable to cdev_add\n");
goto flag2;
}
printk(KERN_INFO "cdev_add success\n");
// 注册字符设备驱动完成后,添加设备类的操作,以让内核帮我们发信息
// 给udev,让udev自动创建和删除设备文件
**test_class = class_create(THIS_MODULE, "aston_class");
if (IS_ERR(test_class))
return -EINVAL;
// 最后1个参数字符串,就是我们将来要在/dev目录下创建的设备文件的名字
// 所以我们这里要的文件名是/dev/test
device_create(test_class, NULL, mydev, NULL, "test111");**
// 使用动态映射的方式来操作寄存器
if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON"))
// return -EINVAL;
goto flag3;
if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0CON"))
// return -EINVAL;
goto flag3;
pGPJ0CON = ioremap(GPJ0CON_PA, 4);
pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);
*pGPJ0CON = 0x11111111;
*pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); // 亮
//goto flag0:
return 0;
// 如果第4步才出错跳转到这里来
release_mem_region(GPJ0CON_PA, 4);
release_mem_region(GPJ0DAT_PA, 4);
// 如果第3步才出错跳转到这里来
flag3:
cdev_del(pcdev);
// 如果第2步才出错跳转到这里来
flag2:
// 在这里把第1步做成功的东西给注销掉
unregister_chrdev_region(mydev, MYCNT);
// 如果第1步才出错跳转到这里来
flag1:
return -EINVAL;
//flag0:
// return 0;
}
// 模块下载函数
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "chrdev_exit helloworld exit\n");
*pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
// 解除映射
iounmap(pGPJ0CON);
iounmap(pGPJ0DAT);
release_mem_region(GPJ0CON_PA, 4);
release_mem_region(GPJ0DAT_PA, 4);
/*
// 在module_exit宏调用的函数中去注销字符设备驱动
unregister_chrdev(mymajor, MYNAME);
*/
**device_destroy(test_class, mydev);
class_destroy(test_class);**
// 使用新的接口来注销字符设备驱动
// 注销分2步:
// 第一步真正注销字符设备驱动用cdev_del
cdev_del(pcdev);
// 第二步去注销申请的主次设备号
unregister_chrdev_region(mydev, MYCNT);
}
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 <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#define FILE "/dev/test111" // 刚才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);
/*
// 读写文件
write(fd, "on", 2);
sleep(2);
write(fd, "off", 3);
sleep(2);
write(fd, "on", 2);
sleep(2);
*/
/*
write(fd, "1", 1);
sleep(2);
write(fd, "0", 1);
sleep(2);
write(fd, "1", 1);
sleep(2);
*/
while (1)
{
memset(buf, 0 , sizeof(buf));
printf("请输入 on | off \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(1);
write(fd, "0", 1);
sleep(1);
}
}
else if (!strcmp(buf, "quit"))
{
break;
}
}
// 关闭文件
close(fd);
return 0;
}
5.3.8.设备类相关代码分析1
5.3.8.1、sys文件系统简介
(1)sys文件系统的设计思想
(2)设备类的概念
(3)/sys/class/xxx/中的文件的作用
5.3.8.2、
(1)
class_create
__class_create
__class_register
kset_register
kobject_uevent
(2)
device_create
device_create_vargs
kobject_set_name_vargs
device_register
device_add
kobject_add
device_create_file
device_create_sys_dev_entry
devtmpfs_create_node
device_add_class_symlinks
device_add_attrs
device_pm_add
kobject_uevent
5.3.9.设备类相关代码分析2
5.3.10.静态映射表建立过程分析
5.3.10.1、建立映射表的三个关键部分
(1)映射表具体物理地址和虚拟地址的值相关的宏定义
(2)映射表建立函数。该函数负责由(1)中的映射表来建立linux内核的页表映射关系。
在kernel/arch/arm/mach-s5pv210/mach-smdkc110.c中的smdkc110_map_io函数
smdkc110_map_io
s5p_init_io
iotable_init
结论:经过分析,真正的内核移植时给定的静态映射表在arch/arm/plat-s5p/cpu.c中的s5p_iodesc,本质是一个结构体数组,数组中每一个元素就是一个映射,这个映射描述了一段物理地址到虚拟地址之间的映射。这个结构体数组所记录的几个映射关系被iotable_init所使用,该函数负责将这个结构体数组格式的表建立成MMU所能识别的页表映射关系,这样在开机后可以直接使用相对应的虚拟地址来访问对应的物理地址。
(3)开机时调用映射表建立函数
问题:开机时(kernel启动时)smdkc110_map_io怎么被调用的?
start_kernel
setup_arch
paging_init
devicemaps_init
if (mdesc->map_io)
mdesc->map_io();
5.3.11.动态映射结构体方式操作寄存器
5.3.11.1、问题描述
(1)仿效真实驱动中,用结构体封装的方式来进行单次多寄存器的地址映射。来代替我们5.2.17节中讲的多次映射。
5.3.11.2、实践编码
5.3.11.3、分析和总结
#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
arm-linux-gcc app.c -o app
cp:
cp *.ko /root/porting_x210/rootfs/rootfs/driver_test
cp app /root/porting_x210/rootfs/rootfs/driver_test
.PHONY: clean
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf app
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h> // arch/arm/mach-s5pv210/include/mach/gpio-bank.h
#include <linux/string.h>
#include <linux/io.h>
#include <linux/ioport.h>
typedef struct GPJ0REG
{
volatile unsigned int gpj0con;
volatile unsigned int gpj0dat;
}gpj0_reg_t;
#define MYMAJOR 200
#define MYNAME "testchar"
#define GPJ0CON S5PV210_GPJ0CON
#define GPJ0DAT S5PV210_GPJ0DAT
#define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)
//#define GPJ0CON_PA 0xe0200240
//#define GPJ0DAT_PA 0xe0200244
#define GPJ0_REGBASE 0xe0200240
//unsigned int *pGPJ0CON;
//unsigned int *pGPJ0DAT;
gpj0_reg_t *pGPJ0REG;
int mymajor;
char kbuf[100]; // 内核空间的buf
static int test_chrdev_open(struct inode *inode, struct file *file)
{
// 这个函数中真正应该放置的是打开这个设备的硬件操作代码部分
// 但是现在暂时我们写不了这么多,所以用一个printk打印个信息来做代表。
printk(KERN_INFO "test_chrdev_open\n");
rGPJ0CON = 0x11111111;
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); // 亮
return 0;
}
static int test_chrdev_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "test_chrdev_release\n");
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
return 0;
}
ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_read\n");
ret = copy_to_user(ubuf, kbuf, count);
if (ret)
{
printk(KERN_ERR "copy_to_user fail\n");
return -EINVAL;
}
printk(KERN_INFO "copy_to_user success..\n");
return 0;
}
// 写函数的本质就是将应用层传递过来的数据先复制到内核中,然后将之以正确的方式写入硬件完成操作。
static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf,
size_t count, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_write\n");
// 使用该函数将应用层传过来的ubuf中的内容拷贝到驱动空间中的一个buf中
//memcpy(kbuf, ubuf); // 不行,因为2个不在一个地址空间中
memset(kbuf, 0, sizeof(kbuf));
ret = copy_from_user(kbuf, ubuf, count);
if (ret)
{
printk(KERN_ERR "copy_from_user fail\n");
return -EINVAL;
}
printk(KERN_INFO "copy_from_user success..\n");
if (kbuf[0] == '1')
{
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
}
else if (kbuf[0] == '0')
{
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
}
/*
// 真正的驱动中,数据从应用层复制到驱动中后,我们就要根据这个数据
// 去写硬件完成硬件的操作。所以这下面就应该是操作硬件的代码
if (!strcmp(kbuf, "on"))
{
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
}
else if (!strcmp(kbuf, "off"))
{
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
}
*/
return 0;
}
// 自定义一个file_operations结构体变量,并且去填充
static const struct file_operations test_fops = {
.owner = THIS_MODULE, // 惯例,直接写即可
.open = test_chrdev_open, // 将来应用open打开这个设备时实际调用的
.release = test_chrdev_release, // 就是这个.open对应的函数
.write = test_chrdev_write,
.read = test_chrdev_read,
};
// 模块安装函数
static int __init chrdev_init(void)
{
printk(KERN_INFO "chrdev_init helloworld init\n");
// 在module_init宏调用的函数中去注册字符设备驱动
// major传0进去表示要让内核帮我们自动分配一个合适的空白的没被使用的主设备号
// 内核如果成功分配就会返回分配的主设备好;如果分配失败会返回负数
mymajor = register_chrdev(0, MYNAME, &test_fops);
if (mymajor < 0)
{
printk(KERN_ERR "register_chrdev fail\n");
return -EINVAL;
}
printk(KERN_INFO "register_chrdev success... mymajor = %d.\n", mymajor);
/*
// 使用动态映射的方式来操作寄存器
if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON"))
return -EINVAL;
if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0CON"))
return -EINVAL;
pGPJ0CON = ioremap(GPJ0CON_PA, 4);
pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);
*pGPJ0CON = 0x11111111;
*pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); // 亮
*/
// 2步完成了映射
if (!request_mem_region(GPJ0_REGBASE, sizeof(gpj0_reg_t), "GPJ0REG"))
return -EINVAL;
pGPJ0REG = ioremap(GPJ0_REGBASE, sizeof(gpj0_reg_t));
// 映射之后用指向结构体的指针来进行操作
// 指针使用->结构体内元素的方式来操作各个寄存器
pGPJ0REG->gpj0con = 0x11111111;
pGPJ0REG->gpj0dat = ((0<<3) | (0<<4) | (0<<5)); // 亮
return 0;
}
// 模块下载函数
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "chrdev_exit helloworld exit\n");
// *pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
pGPJ0REG->gpj0dat = ((1<<3) | (1<<4) | (1<<5));
// 解除映射
/*
iounmap(pGPJ0CON);
iounmap(pGPJ0DAT);
release_mem_region(GPJ0CON_PA, 4);
release_mem_region(GPJ0DAT_PA, 4);
*/
iounmap(pGPJ0REG);
release_mem_region(GPJ0_REGBASE, sizeof(gpj0_reg_t));
// 在module_exit宏调用的函数中去注销字符设备驱动
unregister_chrdev(mymajor, MYNAME);
// rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
}
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 <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);
/*
// 读写文件
write(fd, "on", 2);
sleep(2);
write(fd, "off", 3);
sleep(2);
write(fd, "on", 2);
sleep(2);
*/
/*
write(fd, "1", 1);
sleep(2);
write(fd, "0", 1);
sleep(2);
write(fd, "1", 1);
sleep(2);
*/
while (1)
{
memset(buf, 0 , sizeof(buf));
printf("请输入 on | off \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(1);
write(fd, "0", 1);
sleep(1);
}
}
else if (!strcmp(buf, "quit"))
{
break;
}
}
// 关闭文件
close(fd);
return 0;
}
5.3.12.内核提供的读写寄存器接口
5.3.12.1、前面访问寄存器的方式
(1)行不行
(2)好不好
5.3.12.2、内核提供的寄存器读写接口
(1)writel和readl
(2)iowrite32和ioread32
5.3.12.3、代码实践
#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
arm-linux-gcc app.c -o app
cp:
cp *.ko /root/porting_x210/rootfs/rootfs/driver_test
cp app /root/porting_x210/rootfs/rootfs/driver_test
.PHONY: clean
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf app
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h> // arch/arm/mach-s5pv210/include/mach/gpio-bank.h
#include <linux/string.h>
#include <linux/io.h>
#include <linux/ioport.h>
#define MYMAJOR 200
#define MYNAME "testchar"
#define GPJ0CON S5PV210_GPJ0CON
#define GPJ0DAT S5PV210_GPJ0DAT
#define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)
#define GPJ0CON_PA 0xe0200240
#define GPJ0DAT_PA 0xe0200244
#define S5P_GPJ0REG(x) (x)
#define S5P_GPJ0CON S5P_GPJ0REG(0)
#define S5P_GPJ0DAT S5P_GPJ0REG(4)
unsigned int *pGPJ0CON;
unsigned int *pGPJ0DAT;
static void __iomem *baseaddr; // 寄存器的虚拟地址的基地址
int mymajor;
char kbuf[100]; // 内核空间的buf
static int test_chrdev_open(struct inode *inode, struct file *file)
{
// 这个函数中真正应该放置的是打开这个设备的硬件操作代码部分
// 但是现在暂时我们写不了这么多,所以用一个printk打印个信息来做代表。
printk(KERN_INFO "test_chrdev_open\n");
rGPJ0CON = 0x11111111;
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); // 亮
return 0;
}
static int test_chrdev_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "test_chrdev_release\n");
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
return 0;
}
ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_read\n");
ret = copy_to_user(ubuf, kbuf, count);
if (ret)
{
printk(KERN_ERR "copy_to_user fail\n");
return -EINVAL;
}
printk(KERN_INFO "copy_to_user success..\n");
return 0;
}
// 写函数的本质就是将应用层传递过来的数据先复制到内核中,然后将之以正确的方式写入硬件完成操作。
static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf,
size_t count, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_write\n");
// 使用该函数将应用层传过来的ubuf中的内容拷贝到驱动空间中的一个buf中
//memcpy(kbuf, ubuf); // 不行,因为2个不在一个地址空间中
memset(kbuf, 0, sizeof(kbuf));
ret = copy_from_user(kbuf, ubuf, count);
if (ret)
{
printk(KERN_ERR "copy_from_user fail\n");
return -EINVAL;
}
printk(KERN_INFO "copy_from_user success..\n");
if (kbuf[0] == '1')
{
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
}
else if (kbuf[0] == '0')
{
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
}
/*
// 真正的驱动中,数据从应用层复制到驱动中后,我们就要根据这个数据
// 去写硬件完成硬件的操作。所以这下面就应该是操作硬件的代码
if (!strcmp(kbuf, "on"))
{
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
}
else if (!strcmp(kbuf, "off"))
{
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
}
*/
return 0;
}
// 自定义一个file_operations结构体变量,并且去填充
static const struct file_operations test_fops = {
.owner = THIS_MODULE, // 惯例,直接写即可
.open = test_chrdev_open, // 将来应用open打开这个设备时实际调用的
.release = test_chrdev_release, // 就是这个.open对应的函数
.write = test_chrdev_write,
.read = test_chrdev_read,
};
// 模块安装函数
static int __init chrdev_init(void)
{
printk(KERN_INFO "chrdev_init helloworld init\n");
// 在module_init宏调用的函数中去注册字符设备驱动
// major传0进去表示要让内核帮我们自动分配一个合适的空白的没被使用的主设备号
// 内核如果成功分配就会返回分配的主设备好;如果分配失败会返回负数
mymajor = register_chrdev(0, MYNAME, &test_fops);
if (mymajor < 0)
{
printk(KERN_ERR "register_chrdev fail\n");
return -EINVAL;
}
printk(KERN_INFO "register_chrdev success... mymajor = %d.\n", mymajor);
/*
// 使用动态映射的方式来操作寄存器
if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON"))
return -EINVAL;
if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0CON"))
return -EINVAL;
pGPJ0CON = ioremap(GPJ0CON_PA, 4);
pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);
*/
// *pGPJ0CON = 0x11111111;
// *pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); // 亮
// 测试1:用2次ioremap得到的动态映射虚拟地址来操作,测试成功
// writel(0x11111111, pGPJ0CON);
// writel(((0<<3) | (0<<4) | (0<<5)), pGPJ0DAT);
// 测试2:用静态映射的虚拟地址来操作,测试成功
// writel(0x11111111, GPJ0CON);
// writel(((0<<3) | (0<<4) | (0<<5)), GPJ0DAT);
// 测试3:用1次ioremap映射多个寄存器得到虚拟地址,测试成功
if (!request_mem_region(GPJ0CON_PA, 8, "GPJ0BASE"))
return -EINVAL;
baseaddr = ioremap(GPJ0CON_PA, 8);
writel(0x11111111, baseaddr + S5P_GPJ0CON);
writel(((0<<3) | (0<<4) | (0<<5)), baseaddr + S5P_GPJ0DAT);
return 0;
}
// 模块下载函数
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "chrdev_exit helloworld exit\n");
//*pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
//writel(((1<<3) | (1<<4) | (1<<5)), pGPJ0DAT);
//writel(((1<<3) | (1<<4) | (1<<5)), GPJ0DAT);
writel(((1<<3) | (1<<4) | (1<<5)), baseaddr + S5P_GPJ0DAT);
/*
// 解除映射
iounmap(pGPJ0CON);
iounmap(pGPJ0DAT);
release_mem_region(GPJ0CON_PA, 4);
release_mem_region(GPJ0DAT_PA, 4);
*/
iounmap(baseaddr);
release_mem_region(baseaddr, 8);
// 在module_exit宏调用的函数中去注销字符设备驱动
unregister_chrdev(mymajor, MYNAME);
// rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
}
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 <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);
/*
// 读写文件
write(fd, "on", 2);
sleep(2);
write(fd, "off", 3);
sleep(2);
write(fd, "on", 2);
sleep(2);
*/
/*
write(fd, "1", 1);
sleep(2);
write(fd, "0", 1);
sleep(2);
write(fd, "1", 1);
sleep(2);
*/
while (1)
{
memset(buf, 0 , sizeof(buf));
printf("请输入 on | off \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(1);
write(fd, "0", 1);
sleep(1);
}
}
else if (!strcmp(buf, "quit"))
{
break;
}
}
// 关闭文件
close(fd);
return 0;
}
5.3.12.4、分析和总结