目录:
1、驱动与应用程序的设计思想
2、字符设备驱动编写步骤与规范
3、操作寄存器地址:readl / writel
4、实例:实现LED灯闪烁
1、驱动与应用程序的设计思想
用户态:应用程序
玩策略: 怎么去做
1, 一闪一闪
2,10s闪一次,也可以1s闪一次
3,一直亮
4,跑马灯
控制权是在应用程序(程序员)
--------------------------------------
内核态:驱动
玩机制: 能做什么
led:亮 和 灭
2、字符设备驱动编写步骤与规范
1)步骤
1,实现模块加载和卸载入口函数
module_init(chr_dev_init);
module_exit(chr_dev_exit);
2,在模块加载入口函数中
a,申请主设备号 (内核中用于区分和管理不同字符设备)
register_chrdev(dev_major, "chr_dev_test", &my_fops);
b,创建设备节点文件 (为用户提供一个可操作到文件接口--open())
struct class *class_create(THIS_MODULE, "chr_cls");
struct device *device_create(devcls, NULL, MKDEV(dev_major, 0), NULL, "chr2");
c, 硬件的初始化
1,地址的映射
gpx2conf = ioremap(GPX2_CON, GPX2_SIZE);
2,中断到申请
3,实现硬件的寄存器的初始化
// 需要配置gpio功能为输出
*gpx2conf &= ~(0xf<<28);
*gpx2conf |= (0x1<<28);
e,实现file_operations
const struct file_operations my_fops = {
.open = chr_drv_open,
.read = chr_drv_read,
.write = chr_drv_write,
.release = chr_drv_close,
};
2)规范
1,面向对象编程思想
用一个结构体来表示一个对象
//设计一个类型,描述一个设备的信息
struct led_desc{
unsigned int dev_major; //设备号
struct class *cls;
struct device *dev; //创建设备文件
void *reg_virt_base;
};
struct led_desc *led_dev;//表示一个全局的设备对象
// 0(在init中第一步做), 实例化全局的设备对象--分配空间
// GFP_KERNEL 如果当前内存不够用到时候,该函数会一直阻塞(休眠)
// #include <linux/slab.h>
led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL);
if(led_dev == NULL)
{
printk(KERN_ERR "malloc error\n");
return -ENOMEM;
}
led_dev->dev_major = 250;
2,做出错处理
在某个位置出错了,要将之前申请到资源进行释放
led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL);
led_dev->dev_major = register_chrdev(0, "led_dev_test", &my_fops);
if(led_dev->dev_major < 0)
{
printk(KERN_ERR "register_chrdev error\n");
ret = -ENODEV;
goto err_0;
}
err_0:
kfree(led_dev);
return ret;
引用:我们知道内核有些函数是返回指针的,如Kmalloc分配内存,如果分配不到内核就会返回NULL指针,这样我们可以通过判断是否是NULL指针来判断Kmalloc执行成功与否。但是有些函数返回错误时,我们不仅需要知道函数错了,还需要知道错在哪里了,也就是说我们要或得错误码。在用户空间编程的时候,因为每个线程都有一个error变量,我们可以通过访问这个变量来得到错误码。但是在内核就没有这个变量,所以不能这样或得函数执行的错误码。内核开发者就根据内核的地址空间特点用了一种新的方法来或得错误码,那就是PTR_ERR,ERR_PTR,IS_ERR这三个宏
对错误处理的详解见:解读PTR_ERR,ERR_PTR,IS_ERR
3、操作寄存器地址的方式:readl / writel
1)传统的方式
volatile unsigned long *gpxcon;
*gpxcon &= ~(0xf<<28);
2)内核提供的方式
readl/writel();
u32 readl(const volatile void __iomem *addr) //从地址中读取地址空间的值
//都是读取/写入32位数据,即4字节
void writel(unsigned long value , const volatile void __iomem *addr) // 将value的值写入到addr地址
例子1:
// gpio的输出功能的配置
u32 value = readl(led_dev->reg_virt_base);
value &= ~(0xf<<28);
value |= (0x1<<28);
writel(value, led_dev->reg_virt_bas);
例子2:
*gpx2dat |= (1<<7);
替换成:
writel( readl(led_dev->reg_virt_base + 4) | (1<<7), led_dev->reg_virt_base + 4 );
实际上传统方法和内核提供的方式并无差别,甚至内核的方式还有点繁杂,但是为了标准化与可移植性,推荐使用内核提供的方法来实现相应功能。
4、实例:实现led灯闪烁
1)led_drv.c
1 #include <linux/init.h>
2 #include <linux/module.h>
3 #include <linux/fs.h>
4 #include <linux/device.h>
5 #include <asm/uaccess.h>
6 #include <asm/io.h>
7 #include <linux/slab.h>
8
9 //设计一个类型,描述一个设备的信息
10 struct led_desc{
11 unsigned int dev_major; //设备号
12 struct class *cls; //创建设备文件的类
13 struct device *dev; //创建设备文件
14 void *reg_virt_base; //寄存器地址的基准值, void *型,地址偏移时+4
15 };
16
17 //物理地址
18 #define GPX2CON 0x11000C40
19 #define GPX2_SIZE 8
20
21
22 struct led_desc *led_dev; //声明全局的设备对象
23
24 static int kernal_val = 555;
25
26 // 用户空间向内核空间传数据
27 ssize_t led_drv_write (struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
28 {
29
30 int ret;
31 int value; //从用户空间获取到的数据存在value中
32 ret = copy_from_user(&value, buf, count);
33 if(ret > 0)
34 {
35 printk("copy_from_user");
36 return -EFAULT;
37 }
38
39 if(value) //根据用户传递的值决定灯的亮灭
40 {
41 writel((readl(led_dev->reg_virt_base+4) | (0x1<<7)), led_dev->reg_virt_base+4);
42
43 }else
44 {
45 writel((readl(led_dev->reg_virt_base+4) & ~(0x1<<7)), led_dev->reg_virt_base+4);
46 }
47
48 return 0;
49 }
50
51 // read(fd, buf, size) 用户空间获取内核空间的数据
52 ssize_t led_drv_read (struct file *filp, char __user *buf, size_t count, loff_t *fops)
53 {
54 printk("---------read %s-------------\n",__FUNCTION__);
55
56 int ret;
57 ret = copy_to_user(buf, &kernal_val, count);
58 if(ret > 0)
59 {
60 printk("copy_to_user");
61 return -EFAULT;
62 }
63
64 return 0;
65 }
66
67 int led_drv_open (struct inode *inode, struct file *filp)
68 {
69 printk("---------open %s-------------\n",__FUNCTION__);
70
71 return 0;
72 }
73
74 int led_drv_close (struct inode *inode, struct file *filp)
75 {
76 printk("---------close %s-------------\n",__FUNCTION__);
77
78 return 0;
79 }
80
81
82
83 const struct file_operations my_fops = {
84 .open = led_drv_open,
85 .read = led_drv_read,
86 .write = led_drv_write,
87 .release = led_drv_close,
88 };
89
90 static int __init led_dev_init(void)
91 {
92
93 int ret;
94
95 //1、实例化全局的设备对象 -- 分配空间
96 //GFP_KERNEL 若当前内存不够用,该函数会一直阻塞
97 led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL);
98 if(led_dev == NULL)
99 {
100 printk(KERN_ERR "malloc error\n");
101 return -ENOMEM;
102 }
103
104 //装载一般都是申请设备号资源
105 //2、动态申请设备号
106 led_dev->dev_major = register_chrdev(0, "led_dev_test", &my_fops);
107 if(led_dev->dev_major < 0)
108 {
109 printk(KERN_ERR "register_led_dev error\n");
110 ret = -ENODEV;
111 goto err_0;
112 //return -ENODEV; //如果直接返回的话,kmalloc分配的空间不会释放,造成内存泄漏
113 }
114
115 //3、自动创建设备节点
116 led_dev->cls = class_create(THIS_MODULE, "led_cls");
117
118 if(IS_ERR(led_dev->cls))
119 {
120 printk(KERN_ERR"class create error\n");
121 ret = PTR_ERR(led_dev->cls); //将指针出错的具体原因转换成一个出错码
122 goto err_1; //此步出错不仅要释放kmalloc空间,还要注销注册成功的设备号
123 }
124
125 led_dev->dev = device_create(led_dev->cls,NULL,MKDEV(led_dev->dev_major,0),NULL,"led%d",0); //后两参数:->led0
126
127 if(IS_ERR(led_dev->dev))
128 {
129 printk(KERN_ERR"device create error\n");
130 ret = PTR_ERR(led_dev->dev); //将指针出错的具体原因转换成一个出错码
131 goto err_2; //此步出错不仅要释放kmalloc空间,还要注销注册成功的设备号
132 }
133
134 //4、硬件初始化
135 //对地址进行映射
136 led_dev->reg_virt_base = ioremap(GPX2CON, GPX2_SIZE); //映射两个寄存器
137 if(led_dev->reg_virt_base == NULL)
138 {
139 printk(KERN_ERR"ioremap error\n");
140 ret = -ENOMEM;
141 goto err_3;
142 }
143
144 //GPIO输出功能的配置,使用标准写法
145 u32 value = readl(led_dev->reg_virt_base);
146 value &= ~(0xf << 28);
147 value |= (0x1 << 28);
148 writel(value, led_dev->reg_virt_base);
149
150 return 0;
151
152 //出错处理
153 err_3:
154 device_destroy(led_dev->cls, MKDEV(led_dev->dev_major,0));
155
156 err_2:
157 class_destroy(led_dev->cls);
158
159 err_1:
160 unregister_chrdev(led_dev->dev_major, "led_dev_test");
161 //err1放在err0前面,标签会顺序执行,err_1 -> err_0,进而释放kmalloc的资源
162
163 err_0:
164 kfree(led_dev);
165 return ret; //释放完空间后,再返回错误码
166
167 }
168
169 static void __exit led_dev_exit(void)
170 {
171 //卸载一般都是释放资源
172 iounmap(gpx2conf);
173
174 //销毁节点和类
175 device_destroy(led_dev->cls,MKDEV(led_dev->dev_major,0));
176 class_destroy(led_dev->cls);
177 //释放设备号
178 unregister_chrdev(led_dev->dev_major, "led_dev_test");
179 kfree(led_dev);
180 }
181
182 module_init(led_dev_init);
183 module_exit(led_dev_exit);
184 MODULE_LICENSE("GPL");
led_chr.c
2) led_test.c
1 #include <stdio.h>
2 #include <string.h>
3 #include <stdlib.h>
4 #include <unistd.h>
5 #include <sys/types.h>
6 #include <sys/stat.h>
7 #include <fcntl.h>
8
9
10 int main(int argc, char * argv [ ])
11 {
12 //调用驱动
13 int fd;
14 int value = 0; //buf
15
16 fd = open("/dev/led0", O_RDWR); //设备文件名在驱动中创建设备文件时指定
17 if(fd < 0)
18 {
19 perror("open");
20 exit(1);
21 }
22
23 read(fd, &value, 4); //应用程序从内核获取4字节数据存放在value中
24 printf("__USER__: value = %d\n", value); //打印获取的值
25 sleep(1); //sleep1秒,防止串口打印信息不完整
26
27 //应用程序控制灯
28 while(1)
29 {
30 value = 0;
31 write(fd, &value, 4);
32 sleep(1);
33
34 value = 1;
35 write(fd, &value, 4);
36 sleep(1);
37 }
38
39
40 close(fd);
41
42 return 0;
43 }
led_test.c
3) Makefile
1 ROOTFS_DIR = /home/linux/source/rootfs#根文件系统路径
2
3 APP_NAME = chr_test
4 MODULE_NAME = chr_drv1
5
6 CROSS_COMPILE = /home/linux/toolchains/gcc-4.6.4/bin/arm-none-linux-gnueabi-
7 CC = $(CROSS_COMPILE)gcc
8
9 ifeq ($(KERNELRELEASE),)
10
11 KERNEL_DIR = /home/linux/kernel/linux-3.14-fs4412 #编译过的内核源码的路径
12 CUR_DIR = $(shell pwd) #当前路径
13
14 all:
15 make -C $(KERNEL_DIR) M=$(CUR_DIR) modules #把当前路径编成modules
16 $(CC) $(APP_NAME).c -o $(APP_NAME)
17 @#make -C 进入到内核路径
18 @#M 指定当前路径(模块位置)
19
20 clean:
21 make -C $(KERNEL_DIR) M=$(CUR_DIR) clean
22
23 install:
24 sudo cp -raf *.ko $(APP_NAME) $(ROOTFS_DIR)/drv_module #把当前的所有.ko文件考到根文件系统的drv_module目录
25
26 else
27
28 obj-m += MODULE_NAME #指定内核要把哪个文件编译成ko
29
30 endif
Makefile
实验结果: