-
Notifications
You must be signed in to change notification settings - Fork 25
/
Linux驱动.c
394 lines (332 loc) · 16.2 KB
/
Linux驱动.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
一、烧写Linux系统到inand
1、烧写u-boot到inand
tftp 30008000 u-boot.bin
movi write u-boot 30008000
2、烧写Linux内核到inand
tftp 30008000 zImage-qt
movi write kernal 30008000
3、烧写文件系统到inand
开发板中已经有文件系统,无须再烧,设置启动参数即可。
4、设置启动环境变量
setenv bootcmd "movi read kernel 30008000;bootm 30008000"
setenv bootargs "console=ttySAC2,115200 root=/dev/mmcblk0p2 rw init=/linuxrc rootfstype=ext3"
二、挂载ubuntu系统的文件夹到开发板
目的:是为了把编译好驱动程序下载到共享目下。
1、进入开发板系统 root 123456
2、检查开板的ip地址
3、在启动脚本添加以下内容:vi /etc/init.d/rcS
mount -t nfs -o nolock 192.168.1.66:/nfs /mnt
三、Linux内核下的C语言编程
1、命名习惯
普通代码:
#define PI 3.14159
int minVal,maxVal;
void sendData(void);
内核代码:
#define PI 3.14159
int min_val,max_val;
void send_data(void);
2、GNU C与ANSI C
GNU编译器是专门为Linux内核所设计的一款编译器,而且Linux内核中也使用大量只有GNU编译才支持的C语言语法,因此只有GNU编译器才能编译内核代码和Linux驱动程序。
a、零长数组
typedef struct {
int len;
char data[0];
}Data;
Data* dp = malloc(sizeof(Data)+1024);
dp->len = 1024;
send(sockfd,dp,sizeof(dp->len+sizeof(*dp)),0);
b、case范围
switch(n)
{
case 1 ... 5: break;
case 6 ... 9: break;
}
c、typeof
定义一个宏求两个变量的最大值。
#define MIN(a,b) ((a)>(b)?(a):(b))
#define MIN(type,a,b) ({type _a=num1++;type _b=num2++;_a>_b?_a:_b;})
MIN(int,num1++,num2++) => num1++ > num2++ ? num1++ : num2++;
#define MIN(a,b) ({typeof(a) _a=a; typeof(b) _b=b; _a>_b?_a:_b;})
定义一个宏交换两个变量值。
#define swap(a,b) ({typeof(a) t=a; a=b; b =t;})
d、可变参长数宏
int printf(const char *format, ...);
#define debug(ftm,arg,...) printf(ftm,##arg)
e、标号元素
ANSI C
struct Student stu = {
name:"hehe",
sex:'m',
age:18
};
GNU C
struct Student stu = {
.name = "hehe",
.sex = 'm',
.age = 18
};
3、do{ } while(0)
定义一个宏函数,用来释放堆内存。
#define FREE_HEAP(p) do{free(p); p = NULL;}while(0)
int func(void)
{
if(NULL != p)
FREE_HEAP(p)
else
return -1;
}
4、goto语句
一般的企业代码中禁止使用goto语句,因为goto可以跳转到函数内的任意位置,会打破原有分支、循环结构、或业务逻辑,所以goto是应用层代码中是一种非常危险的语句,但在内核中却有它独到的功能。
int example(void)
{
if(!register_a())
{
goto err1;
}
if(!register_b())
{
goto err2:
}
if(!register_c())
{
goto err3:
}
// work
err3:
unregister_c();
err2:
unregister_b();
err1:
unretister_a();
err:
return ret;
}
在内核中,资源的申请与释放必须是逆序,而goto语句能够把错误处理的代码写的安全、严谨而有简洁。
四、内核模块
通过分析Linux内核的编译过程,了解到一个实事:Linux内核是由很多模块组成了。
1、内核以模块的形式组成的好处
a、可以灵活的进行裁剪
b、可以动态加载或卸载
c、可以尽量控制内核的大小
2、内核模块的特点
a、内核模块代码最终是要加入内核中的
b、内核模块代码工作在内核态。
c、内核模块代码一旦出错,可能会导致整个内核(操作)崩溃。
d、在Linux系统中驱动程序是一个内核模块,它按照内核模块的格式进行编写。
3、内核模块的结构
a、模块加载函数(必须)
模块加载内核时自动执行此函数,相当于内核模块的入口函数。
此函数一般要做些初始化工作、申请内存、资源,注册相关信息。
b、模块卸载函数(必须)
模块被卸载时自动执行。
此函数一般要做些资源的释放、销毁的工作。
c、模块声明许可证(必须)
d、模块作者信息声明(可选)
e、其它一些自定义函数,可以被模块加载函数调用。
4、内核模块的加载与卸载
insmod hello.ko
rmmod hello.ko
五、printfk函数
printfk是在内核中运行的向控制台输出显示信息的函数,与printf函数的用法基本一致。
内核中的消息是分等级的,定义在include/linux/kernal.h
#define KERN_EMERG "<0>" /* system is unusable */
#define KERN_ALERT "<1>" /* action must be taken immediately */
#define KERN_CRIT "<2>" /* critical conditions */
#define KERN_ERR "<3>" /* error conditions */
#define KERN_WARNING "<4>" /* warning conditions */
#define KERN_NOTICE "<5>" /* normal but significant condition */
#define KERN_INFO "<6>" /* informational */
#define KERN_DEBUG "<7>" /* debug-level messages */
printk 默认的等级是 KERN_NOTICE
在操作系统中可以设置显示消息等级,数字越大,级别超低。
cat /proc/sys/kernel/printk 可以看当前系统设置的消息等级
echo "5" > /proc/sys/kernel/printk
一、模块参数
在加载模块时可以像应用程序一样附加一些参数。
定义变量:用来存储数据。
注册变量:让内核允许加载模块时变量被赋值。
module_param(变量名,类型,权限);
类型:
byte、short、ushort、int、uint、long、ulong、charp
权限:
#define S_IRUSR 00400
#define S_IWUSR 00200
#define S_IXUSR 00100
传递参数:insmod xxx.ko 变量名=数据
模块导出符号:
EXPORT_SYMBOL_GPL(符号);
导出的符号(变量名、函数名)可以被其他模块使用,使用前声明一下即可。
二、驱动概述
什么是驱动:就是控制硬件工作的软件。
在Linux系统下,驱动程序工作在内核中,以内核模块形式存在,它能能够控制硬件工作,或者提供控制硬件的接口。
驱动程序提供接口归内核调用,然后内核再统一抽象成文件(设备文件),代应用的程序以操作文件式进行控制硬件。
应用程序工作在用户态,驱动程序工作在内核态,它们之间不能直接联系,需要按照内核提供的接口(open/read/write/close)来传递数据。
在Linux系统下一切皆文件。
三、驱动的类型
字符设备:在IO过程中以字符或字节为单位进行数据传输,数据也要按照一定的顺序进行读写。
操作这种设备的接口有:open/close/read/write
常见的字符设备有:鼠标、键盘、串口、LED灯、温度传感器、湿度传感器等。
字符设备由于功能杂乱,设备也混乱,一般都是小厂家生产的没有统一的接口,所以大多数据需要编写驱动的都是字符设备。
块设备:以块为单位进行数据的读写,用记在读写数据时必须最少读一块,这种设备一般都带缓冲区,而且数据量一般都很大,数据在读写时一般不会立即到达硬件。
操作这种设备的接口有:open/close/read/write/sync/fsync/fdatasync。
常见的块设备有:硬盘、SD卡、Nand、iNand。
虽然存储介质不同,但都有一个控制器不需要外部直接操作,只需要按照控制器的接口来操作即可。
网络设备:具有网络通信协议的设备,网络设备没有设备文件,必须使用socket进行操作。
操作这种设备的接口有:send/recv/sendto/recvfrom。
网络设备都非常标准的、规范,所以有了万能的网卡驱动,顶多需要移植一下,也不需要编写驱动。
四、设备文件
在Linux系统会把硬件抽象成文件来制作(网上除外)。
查看设备文件:ls -l /dev
每个字母表示文件的类型:
- 普通文件
d 目录文件
l 链接文件
c 字符设备文件
b 块设备文件
p 管道文件
s socket文件
设备号:主设备号+从设备号组成
主设备号:表示硬件的类型,1~254
从设备号:硬件的编号,0~255
它是在驱动程序中确定的,可以让内核自动生成,也可以手动确定,或者手动创建。
注意:设备不能重复。
五、字符设备驱动
1、字符设备驱动分析
drivers\char\Cs5535_gpio.c
a、按照内核模块格式编写
b、创建设备号、初始化、添加
c、实现文件操作函数
2、设备号
dev_t dev_id = MKDEV(主设备号,从设备号);
功能:生成一个设备号
返回值:主设备号+从设备号的合体
注意:不一定能使用。
int register_chrdev_region(dev_t from, unsigned count, const char *name)
功能:注册希望使用的设备号,如果已经被占用则失败。
from:希望使用的设备号,MKDEV的返回值
count:设备的数量
name:设备文件名
返回值:成功返回0
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)
dev:内核分配的设备号,返回值
baseminor:设备号的起始值
count:设备的数量
name:设备名
返回值:成功返回0
MAJOR(dev_id) 解析出主设备号
MINOR(dev_id) 解析出从设备号
void unregister_chrdev_region(dev_t from, unsigned count)
功能:释放设备号
from:设备号
count:数量
3、字符设备结构体
struct cdev {
struct kobject kobj; // 内嵌的对象
struct module *owner; // 模块名
const struct file_operations *ops; // 文件操作结构体(函数指针)
struct list_head list;
dev_t dev; // 设备号
unsigned int count; // 设备数量
};
void cdev_init(struct cdev *cdev, const struct file_operations *fops);
功能:初始化字符设备结构体
cdev:字符设备结构体
fops:文件操作结构体
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
功能:添加字符设备到内核
p:被初始化过的字符设备结构体
dev:注册过的设备号
count:设备数量
struct file_operations {
// 模块名
struct module *owner;
// 设备文件位置指针
loff_t (*llseek) (struct file *, loff_t, int);
// read函数
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
// write
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
// poll
unsigned int (*poll) (struct file *, struct poll_table_struct *);
// ioctl
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
// mmap
int (*mmap) (struct file *, struct vm_area_struct *);
// open
int (*open) (struct inode *, struct file
// flush
int (*flush) (struct file *, fl_owner_t id);
// close
int (*release) (struct inode *, struct file *);
}
六、设备文件
1、手动创建
mknod 创建设备文件的命令。
cat /proc/devices 查看内核分配的主设备号
mknod /dev/char_v1 c 250 0
2、自动创建
内核提供一组函数用于自动创建设备文件,设备类对动对应的数据结构:struct class
class_create->*__class_create
struct class *__class_create(struct module *owner, const char *name,struct lock_class_key *key)
功能:在内核中创建设备类 /sys/class/xxx
owner:模块名
key:设备类的名字
返回值:设备类指针
struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...)
功能:/sys/class/xxx/在dev目录下自动创建设备文件
class:设备类指针,class_create函数的返回值
parent:设备结构体指针,兼容各种类型的设备,此处填写字符设备结构指针。
devt:设备号
drvdata:创建设备文件时的附加信息
fmt:设备文件名
void device_destroy(struct class *class, dev_t devt)
功能:删除设备
class:设备类指针,class_create函数的返回值
devt:设备号
void class_destroy(struct class *cls)
功能:从内核中删除设备类
一、驱动程序中操作GPIO
在内核中已经把各个GPIO定义为宏,不需要再找地址了,在头文件 arch/arm/mach-sv5pv210/include/mach/gpio.h。
int gpio_request(unsigned gpio, const char *label)
功能:申请GPIO资源
gpio:GPIO管脚的编号,在gpio.h头文件中有定义
label:给GPIO管脚取个名字
void gpio_free(unsigned gpio)
功能:释放GPIO资源
int gpio_direction_input(unsigned gpio)
功能:设置GPIO管脚为输入模式
int gpio_direction_output(unsigned gpio, int value)
功能:设置GPIO管脚为输出模式
int gpio_get_value(unsigned gpio)
功能:从GPIO管脚读取数据
void gpio_set_value(unsigned gpio, int value)
功能:向GPIO管脚写入数据
二、ioctl
int ioctl(int d, int request, ...);
功能:从应用层来调用内核层的ioctl函数,对设备进行设置。
d:文件描述符,open函数的返回值
request:命令
...:附加参数,存储在用户层,以指针的方式交给内核层。
返回值:成功返回0,失败返回-1。
int (*ioctl) (struct inode *, struct file *, unsigned int cmd, unsigned long arg);
功能:响应应用层的ioctl函数,对硬件进行设置。
cmd:对应应用层request
arg:实际上是一个指针,它第指向用户层的一段数据。
三、内核的内存管理
unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n)
功能:从用户层拷贝数据到内核层
to:指向内核层空间的指针
from:指向用户层空间的指针
n:要拷贝的字节数据
返回值:成功拷贝的字节数
long copy_to_user(void __user *to,const void *from, unsigned long n)
功能:从内核层拷贝数据到用户层
to:指向用户层空间的指针
from:指向内核层空间的指针
n:要拷贝的字节数据
返回值:成功拷贝的字节数
void *kmalloc(int size)
功能:与标准C中的用法一致,其实就是调用malloc,kmalloc只是一个宏名。
void kfree(void *where)
功能:释放内存