原文地址:https://hceng.cn/2019/04/08/Linux%E9%A9%B1%E5%8A%A8%E3%80%81%E5%BA%94%E7%94%A8%E8%B0%83%E8%AF%95%E6%8A%80%E5%B7%A7/

记录几个Linux驱动、应用调试技巧。

1.printk

printk都比较熟悉了,在日常中用得最多的一个。

示例:

1
printk(KERN_DEBUG "Passed %s %d \n",__FUNCTION__,__LINE__);

其中KERN_DEBUG表示log的级别,参考kern_levels.h:

1
2
3
4
5
6
7
8
#define KERN_EMERG KERN_SOH "0" /* system is unusable 紧急事件,一般是系统崩溃之前的提示消息 */
#define KERN_ALERT KERN_SOH "1" /* action must be taken immediately 必须立即采取行动 */
#define KERN_CRIT KERN_SOH "2" /* critical conditions 临界状态,通常涉及严重的硬件或者软件操作失败 */
#define KERN_ERR KERN_SOH "3" /* error conditions 报告错误状态,经常用来报告硬件错误 */
#define KERN_WARNING KERN_SOH "4" /* warning conditions 对可能出现问题的情况进行警告,通常不会对系统造成严重问题 */
#define KERN_NOTICE KERN_SOH "5" /* normal but significant condition 有必要的提示,通常用于安全相关的状况汇报 */
#define KERN_INFO KERN_SOH "6" /* informational 提示信息,驱动程序常用来打印硬件信息 */
#define KERN_DEBUG KERN_SOH "7" /* debug-level messages 用于调试信息 */

一个有8个等级,从0到7,优先级依次降低。
通常通过修改/proc/sys/kernel/printk来设置printk打印。

1
2
3
4
5
6
7
cat /proc/sys/kernel/printk
7 4 1 7
 
echo 8 > /proc/sys/kernel/printk
 
cat /proc/sys/kernel/printk
8 4 1 7

4个值的含义依次如下:
console_loglevel:当前console的log级别,只有更高优先级的log才被允许打印到console;
default_message_loglevel:当不指定log级别时,printk默认使用的log级别;
minimum_console_loglevel:console能设定的最高log级别;
default_console_loglevel:默认的console的log级别。

另外,关于printk格式化字符串形式,参考printk-formats.txt

使用dmesg命令,可以显示之前所有的打印信息,常配合grep来查找历史纪录。

2.dump_stack

在分析驱动源码的调用关系时,常遇到分支结构、回调函数,往往要多次添加打印来追溯调用过程。
这时,可以使用内核提供的dump_stack();函数来一次性打印调用过程,将该函数加在要调试位置,当运行到该函数时,就会打印出之前的调用关系。

加入dump_stack

1
2
3
4
5
6
static int spidevx_drv_init(void)
{
……
dump_stack();
……
}

效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# insmod spidev.ko
CPU: 0 PID: 198 Comm: insmod Tainted: G O 4.1.18-g4b7863b4-dirty #32
Hardware name: Generic AM33XX (Flattened Device Tree)
Backtrace:
[<c0012b48>] (dump_backtrace) from [<c0012d68>] (show_stack+0x18/0x1c)
r7:ddfb6100 r6:c0910960 r5:bf0012cc r4:c0910960
[<c0012d50>] (show_stack) from [<c06833d8>] (dump_stack+0x20/0x28)
[<c06833b8>] (dump_stack) from [<bf000fd4>] (spidevx_drv_init+0x18/0xe8 [spidev])
[<bf000fbc>] (spidevx_drv_init [spidev]) from [<c0009694>] (do_one_initcall+0x88/0x1e0)
r5:bf000fbc r4:c0910960
[<c000960c>] (do_one_initcall) from [<c068142c>] (do_init_module+0x60/0x1b0)
r10:bf001488 r9:00000001 r8:dddd5f40 r7:bf0014d0 r6:ddfb6040 r5:00000001
r4:bf001488
[<c06813cc>] (do_init_module) from [<c008f634>] (load_module+0x1bec/0x1e54)
r6:dddd5f48 r5:00000001 r4:ddcc3f48
[<c008da48>] (load_module) from [<c008fa74>] (SyS_finit_module+0x84/0x98)
r10:00000000 r9:ddcc2000 r8:c000f9c4 r7:0000017b r6:0002541e r5:00000003
r4:00000000
[<c008f9f0>] (SyS_finit_module) from [<c000f820>] (ret_fast_syscall+0x0/0x3c)
r6:00038d08 r5:00000000 r4:00000000

可以看到调用关系为:
ret_fast_syscall->SyS_finit_module->load_module->do_init_module->do_one_initcall->spidevx_drv_init

3.strace

strace是个功能强大的Linux调试分析诊断工具,可用于跟踪程序执行时进程系统调用(system call)和所接收的信号,尤其是针对源码不可读或源码无法再编译的程序。
在Linux系统中,用户程序运行在一个沙箱(sandbox)里,用户进程不能直接访问计算机硬件设备。当进程需要访问硬件设备(如读取磁盘文件或接收网络数据等)时,必须由用户态模式切换至内核态模式,通过系统调用访问硬件设备。
strace可跟踪进程产生的系统调用,包括参数、返回值和执行所消耗的时间。
strace常用参数:

1
2
3
4
5
6
strace:
-p <pid>: 跟踪一个PID进程
-f: 继续子进程的跟踪
-T: 打印出每次调用所花费的时间,单位:秒
-c: 统计和报告每个系统调用所执行的时间、调用次数和出错次数等
-o <outfile>: 指定保存strace输出信息的文件

示例,执行:

1
strace -o log.txt date

查看log.txt

1
2
3
4
5
6
7
8
9
10
……
open("/etc/localtime", O_RDONLY|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=127, ...}) = 0
fstat64(3, {st_mode=S_IFREG|0644, st_size=127, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb6f9f000
read(3, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\0\1\0\0\0\0"..., 1024) = 127
_llseek(3, -6, [121], SEEK_CUR) = 0
read(3, "\nUTC0\n", 1024) = 6
close(3) = 0
……

可以清楚的看到date先打开/etc/localtime,得到文件描述符为3,再去read文件,最后close文件。

4.应用层读写寄存器

在判断某个硬件是否按期望正常工作,最简单粗暴的就是直接读取对应寄存器值来分析。
Linux内核提供了一个/dev/mem节点来访问硬件寄存器,可以通过devmemdevmem2等应用程序来读写寄存器。
一些嵌入式的BusyBox包含了devmem,一些发行版的Linux,可以通过sudo apt install devmem2等方式安装,或者手动编译源码:

[devmem2.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
/*
* http://sources.buildroot.net/devmem2.c
*
* devmem2.c: Simple program to read/write from/to any location in memory.
*
* Copyright (C) 2000, Jan-Derk Bakker (J.D.Bakker@its.tudelft.nl)
*
*
* This software has been developed for the LART computing board
* (http://www.lart.tudelft.nl/). The development has been sponsored by
* the Mobile MultiMedia Communications (http://www.mmc.tudelft.nl/)
* and Ubiquitous Communications (http://www.ubicom.tudelft.nl/)
* projects.
*
* The author can be reached at:
*
* Jan-Derk Bakker
* Information and Communication Theory Group
* Faculty of Information Technology and Systems
* Delft University of Technology
* P.O. Box 5031
* 2600 GA Delft
* The Netherlands
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <ctype.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/mman.h>
 
#define FATAL do { fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", \
__LINE__, __FILE__, errno, strerror(errno)); exit(1); } while(0)
 
#define MAP_SIZE 4096UL
#define MAP_MASK (MAP_SIZE - 1)
 
int main(int argc, char **argv) {
int fd;
void *map_base, *virt_addr;
unsigned long read_result, writeval;
long int target;
int access_type = 'w';
 
/*
usage: ./devmem { address } [ type [ data ] ]
*/
if(argc < 2) {
fprintf(stderr, "\nUsage:\t%s { address } [ type [ data ] ]\n"
"\taddress : memory address to act upon\n"
"\ttype : access operation type : [b]yte, [h]alfword, [w]ord\n"
"\tdata : data to be written\n\n",
argv[0]);
exit(1);
}
target = strtoul(argv[1], 0, 0);
 
if(argc > 2)
access_type = tolower(argv[2][0]);
 
 
if((fd = open("/dev/mem", O_RDWR | O_SYNC)) == -1) FATAL;
printf("/dev/mem opened.\n");
fflush(stdout);
 
/* Map one page */
map_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, target & ~MAP_MASK);
if(map_base == (void *) -1) FATAL;
printf("Memory mapped at address %p.\n", map_base);
fflush(stdout);
 
virt_addr = map_base + (target & MAP_MASK);
switch(access_type) {
case 'b':
read_result = *((unsigned char *) virt_addr);
break;
case 'h':
read_result = *((unsigned short *) virt_addr);
break;
case 'w':
read_result = *((unsigned long *) virt_addr);
break;
default:
fprintf(stderr, "Illegal data type '%c'.\n", access_type);
exit(2);
}
printf("Value at address 0x%X (%p): 0x%X\n", target, virt_addr, read_result);
fflush(stdout);
 
if(argc > 3) {
writeval = strtoul(argv[3], 0, 0);
switch(access_type) {
case 'b':
*((unsigned char *) virt_addr) = writeval;
read_result = *((unsigned char *) virt_addr);
break;
case 'h':
*((unsigned short *) virt_addr) = writeval;
read_result = *((unsigned short *) virt_addr);
break;
case 'w':
*((unsigned long *) virt_addr) = writeval;
read_result = *((unsigned long *) virt_addr);
break;
}
printf("Written 0x%X; readback 0x%X\n", writeval, read_result);
fflush(stdout);
}
 
if(munmap(map_base, MAP_SIZE) == -1) FATAL;
close(fd);
return 0;
}

以操作一个LED为例,GPIO1_18的寄存器基地址为0x4804C000,数据输出寄存器偏移为0x13C

值得注意的是,这里使用的是AM335X,测试中发现不能直接操作数据输出寄存器,需要先操作GPIO控制寄存器,这里先通过GPIO子系统完成GPIO寄存器前期工作,也许换个SOC不会出现该情况,可以直接操作任意寄存器:

1
2
3
4
echo 50 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio50/direction
cat /sys/class/gpio/gpio50/value
0

可以看到,现在数据输出寄存器0x4804C13C的值为0,使用devmem2查看:

1
2
3
4
./devmem2 0x4804c13c w
/dev/mem opened.
Memory mapped at address 0xb6f86000.
Value at address 0x4804C13C (0xb6f8613c): 0x0

读取到的值和前面使用GPIO子系统的结果一致。

继续写操作测试:

1
2
3
4
5
6
7
8
./devmem2 0x4804c13c w 0x40000
/dev/mem opened.
Memory mapped at address 0xb6fcc000.
Value at address 0x4804C13C (0xb6fcc13c): 0x0
Written 0x40000; readback 0x40000
 
cat /sys/class/gpio/gpio50/value
1

使用devmem2操作寄存器,使用GPIO子系统查看发现确实被改变了。

使用devmem2还存在几个问题:
一是需要保证/dev/mem节点存在;
二是不能同时读取多个寄存器值;
三是必须依赖应用程序,不能直接echocat读写寄存器;

因此,编写一个新的驱动和应用程序,独立的实现读写寄存器的功能,以解决前面可能出现的情况。

驱动程序如下:

[ker_rw.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
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
 
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/types.h>
#include <asm/uaccess.h>
#include <linux/device.h>
#include <linux/ioport.h>
 
#define MAX_LEN 1000
 
#define REG_IOC_MAGIC 'r'
#define REG_IOC_R8 _IOWR(REG_IOC_MAGIC, 0, void *)
#define REG_IOC_R16 _IOWR(REG_IOC_MAGIC, 1, void *)
#define REG_IOC_R32 _IOWR(REG_IOC_MAGIC, 2, void *)
#define REG_IOC_W8 _IOWR(REG_IOC_MAGIC, 3, void *)
#define REG_IOC_W16 _IOWR(REG_IOC_MAGIC, 4, void *)
#define REG_IOC_W32 _IOWR(REG_IOC_MAGIC, 5, void *)
 
#define REG_ATTR(_name, _mode, _show, _store, _index) \
{ .dev_attr = __ATTR(_name, _mode, _show, _store), \
.index = _index }
 
 
static int reg_major;
static struct cdev reg_cdev;
static struct class *reg_class;
struct device* reg_device = NULL;
 
 
struct ker_rw_msg {
unsigned int val;
unsigned int addr;
unsigned int width;
unsigned int num;
 
struct mutex lock;
};
 
static struct ker_rw_msg rw_msg;
 
struct reg_device_attribute{
struct device_attribute dev_attr;
int index;
};
 
static ssize_t reg_num_show(struct device *dev, struct device_attribute *devattr,
char *buf)
{
return sprintf(buf, "%u\n", rw_msg.num);
}
 
static ssize_t reg_num_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned int num;
 
num = simple_strtoul(buf, NULL, 10);
 
if ((num > MAX_LEN) || (num == 0))
printk(KERN_ERR "%s: num range is 0~%d\n",__FUNCTION__, MAX_LEN);
else
rw_msg.num = num;
 
return count;
}
 
static ssize_t reg_width_show(struct device *dev, struct device_attribute *devattr,
char *buf)
{
return sprintf(buf, "%u\n", rw_msg.width);
}
 
static ssize_t reg_width_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned int width = 0;
 
width = simple_strtoul(buf, NULL, 10);
 
if ((width != 8) && (width != 16) && (width != 32))
printk(KERN_WARNING "Address width can only be 8 or 16 or 32.\n");
else
rw_msg.width = width;
 
return count;
}
 
static ssize_t reg_addr_show(struct device *dev, struct device_attribute *devattr,
char *buf)
{
return sprintf(buf, "0x%08x\n", rw_msg.addr);
}
 
static ssize_t reg_addr_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
rw_msg.addr = simple_strtoul(buf, NULL, 16);
 
return count;
}
 
static ssize_t reg_val_show(struct device *dev, struct device_attribute *devattr,
char *buf)
{
int i;
void __iomem *virtbase;
unsigned int addr;
unsigned int phy_addr[rw_msg.num];
unsigned int vir_addr[rw_msg.num];
unsigned int val[rw_msg.num];
volatile unsigned char *p8;
volatile unsigned short *p16;
volatile unsigned int *p32;
 
//if (!request_mem_region(rw_msg.addr, 4, "ker_rw"))
//return -EBUSY;
 
mutex_lock(&rw_msg.lock);
 
addr = rw_msg.addr;
virtbase = ioremap(addr, 4);
if (virtbase == NULL)
return -ENOMEM;
 
p8 = (volatile unsigned char *)virtbase;
p16 = (volatile unsigned short *)virtbase;
p32 = (volatile unsigned int *)virtbase;
 
for (i=0; i<rw_msg.num; i++)
{
if (rw_msg.width == 8)
{
phy_addr[i] = addr;
vir_addr[i] = (volatile unsigned int)p8;
val[i] = readb(p8); //val[i] = *p8;
p8++;
addr = addr + 1;
}
else if (rw_msg.width == 16)
{
phy_addr[i] = addr;
vir_addr[i] = (volatile unsigned int)p16;
val[i] = readw(p16); //val[i] = *p16;
p16++;
addr = addr + 2;
}
else if (rw_msg.width == 32)
{
phy_addr[i] = addr;
vir_addr[i] = (volatile unsigned int)p32;
val[i] = readl(p32); //val[i] = *p32;
p32++;
addr = addr + 4;
}
else
printk(KERN_WARNING "Please check the address width.\n");
 
sprintf(buf + strlen(buf), "phy_addr:0x%08x vir_addr:0x%08x val:0x%08x\n", phy_addr[i], vir_addr[i], val[i]);
}
 
iounmap(virtbase);
 
mutex_unlock(&rw_msg.lock);
 
return strlen(buf);
}
 
static ssize_t reg_val_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
void __iomem *virtbase;
 
mutex_lock(&rw_msg.lock);
 
rw_msg.val = simple_strtoul(buf, NULL, 16);
 
virtbase = ioremap(rw_msg.addr, 4);
if (virtbase == NULL)
return -ENOMEM;
 
if (rw_msg.width == 8)
writeb(rw_msg.val, virtbase);
else if (rw_msg.width == 16)
writew(rw_msg.val, virtbase);
else if (rw_msg.width == 32)
writel(rw_msg.val, virtbase);
else
printk(KERN_WARNING "Please check the address width.\n");
 
iounmap(virtbase);
 
mutex_unlock(&rw_msg.lock);
 
return count;
}
 
 
static struct reg_device_attribute reg_attribute[] = {
REG_ATTR(val, S_IRUGO | S_IWUSR, reg_val_show, reg_val_store, 1),
REG_ATTR(addr, S_IRUGO | S_IWUSR, reg_addr_show, reg_addr_store, 2),
REG_ATTR(width, S_IRUGO | S_IWUSR, reg_width_show, reg_width_store, 3),
REG_ATTR(num, S_IRUGO | S_IWUSR, reg_num_show, reg_num_store, 4),
};
 
 
static long ker_rw_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
volatile unsigned char *p8;
volatile unsigned short *p16;
volatile unsigned int *p32;
unsigned int val;
unsigned int addr;
 
unsigned int buf[2];
 
mutex_lock(&rw_msg.lock);
 
if (copy_from_user(buf, (const void __user *)arg, 8))
printk(KERN_ERR "%s: copy_from_user error in the %d \n",__FUNCTION__,__LINE__);
 
addr = buf[0];
val = buf[1];
 
p8 = (volatile unsigned char *)ioremap(addr, 4);
if (p8 == NULL)
return -ENOMEM;
p16 = (volatile unsigned short *)p8;
p32 = (volatile unsigned int *)p8;
 
switch (cmd)
{
case REG_IOC_R8:
{
val = *p8;
if (copy_to_user((void __user *)(arg+4), &val, 4))
{
printk(KERN_ERR "%s: copy_to_user error in the %d \n",__FUNCTION__,__LINE__);
return -EINVAL;
}
break;
}
case REG_IOC_R16:
{
val = *p16;
if (copy_to_user((void __user *)(arg+4), &val, 4))
{
printk(KERN_ERR "%s: copy_to_user error in the %d \n",__FUNCTION__,__LINE__);
return -EINVAL;
}
break;
}
case REG_IOC_R32:
{
val = *p32;
if (copy_to_user((void __user *)(arg+4), &val, 4))
{
printk(KERN_ERR "%s: copy_to_user error in the %d \n",__FUNCTION__,__LINE__);
return -EINVAL;
}
break;
}
case REG_IOC_W8:
{
*p8 = val;
break;
}
case REG_IOC_W16:
{
*p16 = val;
break;
}
case REG_IOC_W32:
{
*p32 = val;
break;
}
}
 
iounmap(p8);
 
mutex_unlock(&rw_msg.lock);
 
return 0;
}
 
static struct file_operations ker_rw_ops = {
.owner = THIS_MODULE,
.unlocked_ioctl = ker_rw_ioctl,
};
 
static int ker_rw_init(void)
{
int i = 0;
int ret = 0;
dev_t reg_devid = 0;
 
mutex_init(&rw_msg.lock);
mutex_lock(&rw_msg.lock);
 
if(alloc_chrdev_region(&reg_devid, 0, 1, "ker_rw") < 0)
{
printk(KERN_ERR "%s: alloc_chrdev_region error in the %d \n",__FUNCTION__,__LINE__);
return -EINVAL;
}
 
reg_major = MAJOR(reg_devid);
cdev_init(&reg_cdev, &ker_rw_ops);
 
ret = cdev_add(&reg_cdev, reg_devid, 1);
if (ret < 0)
{
printk(KERN_ALERT "%s: cdev_add error in the %d \n",__FUNCTION__,__LINE__);
goto error1;
}
 
reg_class = class_create(THIS_MODULE, "ker_rw");
if (IS_ERR(reg_class))
{
printk(KERN_ALERT "%s: device_create error in the %d \n",__FUNCTION__,__LINE__);
goto error2;
}
 
reg_device = device_create(reg_class, NULL, MKDEV(reg_major, 0), NULL, "ker_rw");
if (IS_ERR(reg_device))
{
printk(KERN_ALERT "%s: device_create error in the %d \n",__FUNCTION__,__LINE__);
goto error3;
}
 
 
for (i=0; i<4; i++)
{
ret = device_create_file(reg_device, &reg_attribute[i].dev_attr);
if (ret)
{
printk(KERN_ALERT "%s: device_create_file error in the %d \n",__FUNCTION__,__LINE__);
goto error4;
}
}
 
//Defaults
rw_msg.num = 1;
rw_msg.width = 32;
mutex_unlock(&rw_msg.lock);
 
return 0;
 
error4:
device_destroy(reg_class, MKDEV(reg_major, 0));
error3:
class_destroy(reg_class);
error2:
cdev_del(&reg_cdev);
error1:
unregister_chrdev_region(MKDEV(reg_major, 0), 1);
mutex_unlock(&rw_msg.lock);
 
return -EINVAL;
}
 
static void ker_rw_exit(void)
{
int i;
mutex_lock(&rw_msg.lock);
 
for (i=0; i<4; i++)
device_remove_file(reg_device, &reg_attribute[i].dev_attr);
 
device_destroy(reg_class, MKDEV(reg_major, 0));
class_destroy(reg_class);
 
unregister_chrdev_region(MKDEV(reg_major, 0), 1);
cdev_del(&reg_cdev);
 
mutex_unlock(&rw_msg.lock);
}
 
module_init(ker_rw_init);
module_exit(ker_rw_exit);
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR("hceng <huangcheng.job@foxmail.com>");
MODULE_DESCRIPTION("Read and write register.");
MODULE_VERSION("v1.0");

该驱动程序向用户层提供了两个接口:sysfs文件系统接口和devfs文件系统接口。
对于sysfs文件系统接口,加载驱动后,会在/sys/class/ker_rw/ker_rw/生成如下节点:

1
2
addr num subsystem val
dev power uevent width

其中,num用于设置一次读取的寄存器数量,范围为1~MAX_LEN(1000);
witdh用于设置每次读/写寄存器的宽度,支持8、16、32;
addr用于设置每次读/写寄存器的地址(16进制);
val用于设置每次读/写寄存器的值(16进制);

因此,查看操作前面的GPIO可执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# echo 0x4804c13c > addr
 
# cat val
phy_addr:0x4804c13c vir_addr:0xfa04c13c val:0x00000000
 
# echo 0x40000 > val
# cat val
phy_addr:0x4804c13c vir_addr:0xfa04c13c val:0x00040000
 
# echo 4 > num
 
# cat val
phy_addr:0x4804c13c vir_addr:0xfa04c13c val:0x00040000
phy_addr:0x4804c140 vir_addr:0xfa04c140 val:0x00000000
phy_addr:0x4804c144 vir_addr:0xfa04c144 val:0x00000000
phy_addr:0x4804c148 vir_addr:0xfa04c148 val:0x00000000

这里仍然保留了传统的devfs文件系统接口,需要编写应用程序访问:

[app_rw.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
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/ioctl.h>
 
#define REG_IOC_MAGIC 'r'
#define REG_IOC_R8 _IOWR(REG_IOC_MAGIC, 0, void *)
#define REG_IOC_R16 _IOWR(REG_IOC_MAGIC, 1, void *)
#define REG_IOC_R32 _IOWR(REG_IOC_MAGIC, 2, void *)
#define REG_IOC_W8 _IOWR(REG_IOC_MAGIC, 3, void *)
#define REG_IOC_W16 _IOWR(REG_IOC_MAGIC, 4, void *)
#define REG_IOC_W32 _IOWR(REG_IOC_MAGIC, 5, void *)
 
/* Usage:
* ./regeditor r8 addr [num]
* ./regeditor r16 addr [num]
* ./regeditor r32 addr [num]
*
* ./regeditor w8 addr val
* ./regeditor w16 addr val
* ./regeditor w32 addr val
*/
 
void print_usage(char *file)
{
printf("Usage:\n");
printf("%s <r8 | r16 | r32> <phy addr> [num]\n", file);
printf("%s <w8 | w16 | w32> <phy addr> <val>\n", file);
}
 
int main(int argc, char **argv)
{
int fd;
unsigned int buf[2];
unsigned int i;
unsigned int num;
int ret;
 
if ((argc != 3) && (argc != 4))
{
print_usage(argv[0]);
return -1;
}
 
fd = open("/dev/ker_rw", O_RDWR);
if (fd < 0)
{
printf("can't open /dev/ker_rw\n");
return -2;
}
 
/* addr */
buf[0] = strtoul(argv[2], NULL, 0);
 
if (argc == 4)
{
buf[1] = strtoul(argv[3], NULL, 0);
num = buf[1];
}
else
{
num = 1;
}
 
if (strcmp(argv[1], "r8") == 0)
{
for ( i = 0; i < num; i++)
{
ioctl(fd, REG_IOC_R8, buf); /* val = buf[1] */
printf("%02d. [%08x] = %02x\n", i, buf[0], (unsigned char)buf[1]);
buf[0] += 1;
}
}
else if (strcmp(argv[1], "r16") == 0)
{
for ( i = 0; i < num; i++)
{
ioctl(fd, REG_IOC_R16, buf); /* val = buf[1] */
printf("%02d. [%08x] = %04x\n", i, buf[0], (unsigned short)buf[1]);
buf[0] += 2;
}
}
else if (strcmp(argv[1], "r32") == 0)
{
for ( i = 0; i < num; i++)
{
ret = ioctl(fd, REG_IOC_R32, buf); /* val = buf[1] */
if (ret == -1)
{
printf("errno = %d\n", errno);
}
printf("%02d. [%08x] = %08x\n", i, buf[0], (unsigned int)buf[1]);
buf[0] += 4;
}
}
else if (strcmp(argv[1], "w8") == 0)
{
ioctl(fd, REG_IOC_W8, buf); /* val = buf[1] */
}
else if (strcmp(argv[1], "w16") == 0)
{
ioctl(fd, REG_IOC_W16, buf); /* val = buf[1] */
}
else if (strcmp(argv[1], "w32") == 0)
{
ioctl(fd, REG_IOC_W32, buf); /* val = buf[1] */
}
else
{
printf(argv[0]);
return -1;
}
 
return 0;
 
}

最新文章

  1. html5手机端的点击弹出侧边滑动菜单代码
  2. 【JavaScript】操作Canvas画图
  3. java多线程系类:基础篇:04synchronized关键字
  4. Freemarker使用入门
  5. SpringMVC @Value取值(取properties属性文件的属性值)
  6. PDO数据库
  7. sqlserver中的锁与事务
  8. call()与apply()传参需要注意的一点
  9. Qt学习一门:直接使用QT具
  10. [hashcat]基于字典和暴力破解尝试找到rar3-hp的压缩包密码
  11. android上instant app介绍 类似于微信小程序
  12. 5.使用std的迭代器访问并修改图像
  13. Django集成Markdown编辑器【附源码】
  14. Spring Boot 2.0 入门指南
  15. python编码问题分析
  16. VUE 关于理解$nextTick()的问题
  17. 深度剖析Dubbo源码
  18. tr,td高度不生效
  19. 【jersey】 spring 整合jersey 实现RESTful webservice
  20. Android项目包命名规则是怎样的?

热门文章

  1. golang在win10安装、环境配置 和 goland(IDE开发golang配置)
  2. vscode下无法使用fresh的问题
  3. CH32V307以太网(芯片内部10M)
  4. C#开发的资源文件程序(可国际化) - 开源研究系列文章
  5. ZROI3
  6. 电脑本地安装不同版本MySQL
  7. BFC是什么?有什么作用
  8. 【题解】[LNOI2022] 盒
  9. 行为型模式 - 解释器模式Interpreter
  10. CF1466H Finding satisfactory solutions