原文地址: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;
 
}

Linux驱动、应用调试技巧的更多相关文章

  1. Linux驱动开发调试 -- 打开dev_dbg()【转】

    本文转载自:https://blog.csdn.net/kunkliu/article/details/78048618 转载地址:http://blog.chinaunix.net/uid-2284 ...

  2. linux驱动调试--段错误之oops信息分析

    linux驱动调试--段错误之oops信息分析 http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=29401328&id= ...

  3. Linux c c++ 开发调试技巧

    看到一篇介绍 linux c/c++ 开发调试技巧的文章,感觉挺使用,哪来和大家分享. 通向 UNIX 天堂的 10 个阶梯Author: Arpan Sen, 高级技术人员, Systems Doc ...

  4. linux驱动调试记录

    linux驱动调试 linux 目录 /proc 下面可以配置驱动的调试信息,比如给proc目录的自己定制的驱动的一文件设置一个变量,然后驱动程序跟了proc的参数值来配置调试级别.类似于内核调试的级 ...

  5. linux驱动调试--修改系统时钟终端来定位僵死问题【转】

    本文转载自:http://blog.chinaunix.net/uid-20671208-id-4940381.html 原文地址:linux驱动调试--修改系统时钟终端来定位僵死问题 作者:枫露清愁 ...

  6. 【转】linux驱动开发的经典书籍

    原文网址:http://www.cnblogs.com/xmphoenix/archive/2012/03/27/2420044.html Linux驱动学习的最大困惑在于书籍的缺乏,市面上最常见的书 ...

  7. 《Debug Hacks》和调试技巧【转】

    转自:https://blog.csdn.net/sdulibh/article/details/46462529 Debug Hacks 作者为吉冈弘隆.大和一洋.大岩尚宏.安部东洋.吉田俊辅,有中 ...

  8. linux驱动工程面试必问知识点

    linux内核原理面试必问(由易到难) 简单型 1:linux中内核空间及用户空间的区别?用户空间与内核通信方式有哪些? 2:linux中内存划分及如何使用?虚拟地址及物理地址的概念及彼此之间的转化, ...

  9. Linux 系统内核的调试

    http://www.ibm.com/developerworks/cn/linux/l-kdb/index.html 本文将首先介绍 Linux 内核上的一些内核代码监视和错误跟踪技术,这些调试和跟 ...

  10. linux驱动开发的经典书籍

    转载于:http://www.cnblogs.com/xmphoenix/archive/2012/03/27/2420044.html 参加实习也近一个月了,严重感觉知识不够,真是后悔学校里浪费那么 ...

随机推荐

  1. P5690 [CSP-S2019 江西] 日期

    简要题意 给你一个格式为 \(\texttt{MM-DD}\) 的日期.你每一次可以更改一个整数,花费 \(1\) 的代价.求将该日期改为一个合法的日期的最小代价.(注:\(2\) 月视为 \(28\ ...

  2. 如何通过Terraform Associate考试并获得证书

    1 什么是Terraform? Terraform是一个IaC工具,IaC全称为Infrastructure as Code,基础设施即代码.它的理念是通过代码来管理基础设施,如服务器.数据库等,更多 ...

  3. 跟AWS学极致服务

    春节期间,除了还在看技术书籍外,我一直抽空断断续续地在看<极致服务:创造不可思议的客户体验>一书.之前创业的经历,让我一直反思,除了赛道和落地之外,是否在服务质量上也有缺失. 书里从一个商 ...

  4. 初探富文本之OT协同实例

    初探富文本之OT协同实例 在前边初探富文本之OT协同算法一文中我们探讨了为什么需要协同.为什么仅有原子化的操作并不能实现协同.为什么要有操作变换.如何进行操作变换.什么时候能够应用操作.服务端如何进行 ...

  5. 字符编码和字符集-FileReader读取jbk格式的文件

    字符编码和字符集 字符编码 计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的数字.英文.标点符号.汉字等字符是二进制数转换之后的结果.按照某种规则,将字符存储到计算机中,称为编码.反之,将 ...

  6. Java入门与进阶P-4.3+P-4.4

    循环控制 素数 只能被1和自己整除的数,不包括1 2 3 5 7 11 13 17 19 ... 1.循环控制语句 可以在满足循环条件的请款下,反复的执行某一段代码,这段被重复执行的代码被称为循环体语 ...

  7. KingbaseES在线wal日志

    KingbaseES数据库日志文件记录数据库的历史操作信息, 包含恢复数据库中的所有事务所需的信息. KingbaseES在线WAL日志: WAL日志: 预写式日志(Write-Ahead Loggi ...

  8. mybatis-plus 多租户

    package com.ruoyi.framework.config; import org.springframework.context.annotation.Bean; import org.s ...

  9. avue入门

    <html> <head> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.min. ...

  10. 【TS】函数重载--可选参数--默认参数

    可选参数--默认参数 在ts中定义的数据类型,某些情况下只需要传入定义数据类型的一部分参数,比如:id .name.age.address,此时需要修改用户的名称,那么只需要传入id.name就够了: ...