根据报错信息,`__mod_i2c__i2c_id_table_device_table`在两个不同的目标文件中被定义了多次,具体是在`goodix_brl_i2c_psd.o`和`goodix_brl_i2c.o`这两个文件中。这种错误通常是由于以下几个原因之一:
1. 在代码中重复定义了同一个变量或符号。
2. 在多个文件中包含了同一个头文件,而且头文件中有非`static`的全局变量定义。
3. 链接器链接目标文件时发现重复的符号定义。
### 改正方法
1. **检查重复定义**:
确认代码中是否有重复的变量或符号定义。如果有,保留其中一个,删除其他重复的定义。
2. **使用`static`关键字**:
如果相同的符号在多个源文件中使用但不需要全局可见性,可以将这些变量或函数声明为`static`,以限制它们的作用域仅限于各自的文件。
3. **保证头文件的保护**:
确保头文件中的变量声明使用了`extern`关键字,同时避免在头文件中定义变量。可以使用以下方法声明和定义变量:
**在头文件(.h)中声明变量:**
```c
// goodix_brl_i2c.h
extern struct i2c_device_id i2c_id_table[];
```
**在源文件(.c)中定义变量:**
```c
// goodix_brl_i2c.c 或 goodix_brl_i2c_psd.c
struct i2c_device_id i2c_id_table[] = {
// 初始化表内容
};
```
4. **条件编译**:
使用条件编译指令确保同一个定义不会在多个文件中同时出现。例如:
```c
// goodix_brl_i2c.h
#ifndef GOODIX_BRL_I2C_H
#define GOODIX_BRL_I2C_H
extern struct i2c_device_id i2c_id_table[];
#endif // GOODIX_BRL_I2C_H
```
### 具体解决步骤
1. 打开报错提到的两个文件`goodix_brl_i2c_psd.c`和`goodix_brl_i2c.c`。
2. 检查`__mod_i2c__i2c_id_table_device_table`是否被重复定义。
3. 如果有重复定义,删除其中一个定义或者使用上面提到的`extern`和源文件定义的方法。
4. 使用`static`关键字限定变量或函数的作用域,确保它们的定义在各自的文件内有效。
通过以上步骤,可以避免重复定义的错误,让代码正确编译通过。
`MODULE_DEVICE_TABLE`是Linux内核中的一个宏,用于生成设备表并将这些表的信息导出给用户空间工具,例如`modinfo`。它帮助内核模块自动注册设备驱动程序,使驱动程序与合适的设备匹配和关联。这对于设备的自动加载和驱动程序的自动绑定非常重要。
### `MODULE_DEVICE_TABLE`的用途
该宏用于定义设备ID表,例如PCI设备ID、USB设备ID、I2C设备ID等,并将这些ID信息与模块相关联。这样,当设备插入系统时,内核可以通过这些表向设备分配适当的驱动程序。
### 使用例子
#### 1. PCI设备
```c
#include <linux/module.h>
#include <linux/pci.h>
// 定义设备ID表
static const struct pci_device_id my_pci_tbl[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82599) },
{ } // 终结符
};
MODULE_DEVICE_TABLE(pci, my_pci_tbl);
```
#### 2. USB设备
```c
#include <linux/module.h>
#include <linux/usb.h>
// 定义设备ID表
static const struct usb_device_id my_usb_tbl[] = {
{ USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER) },
{ } // 终结符
};
MODULE_DEVICE_TABLE(usb, my_usb_tbl);
```
#### 3. I2C设备
```c
#include <linux/module.h>
#include <linux/i2c.h>
// 定义设备ID表
static const struct i2c_device_id my_i2c_id_table[] = {
{ "my_i2c_device", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, my_i2c_id_table);
```
### 工作原理
- **定义设备ID表**:在驱动程序中定义一个设备ID表,该表列出了驱动程序支持的所有设备的ID。
- **使用`MODULE_DEVICE_TABLE`宏**:该宏将设备ID表信息与模块进行关联。这使得内核在加载模块时,可以根据设备ID表来匹配设备。
- **设备和驱动程序绑定**:当系统中插入一个设备时,内核会根据设备ID表查找适合的驱动程序,并尝试加载相应的模块,从而完成设备和驱动程序的绑定。
### `MODULE_DEVICE_TABLE`实现自动加载
`MODULE_DEVICE_TABLE`的使用使得用户空间工具(如`udev`)能够自动加载相应的内核模块。当插入新设备时,工具会查找设备的ID,并根据ID找到对应的驱动程序名称,进而加载相应的内核模块。
### 典型用法总结
1. **定义设备ID表**:首先在驱动程序中定义支持的设备ID。
2. **使用`MODULE_DEVICE_TABLE`宏**:将设备ID表信息与模块关联。
3. **自动加载**:当设备插入系统时,内核基于ID表信息自动加载驱动模块。
通过`MODULE_DEVICE_TABLE`,内核模块可以实现自动匹配和加载驱动程序,简化了驱动程序的管理和设备的使用。
分析内核中builtin(内置)驱动的Call Trace时,可以按照类似分析外部模块的步骤进行处理,只是主要针对的是内核本身的代码。以下是详细步骤和示例:
### 查看Call Trace
调用跟踪通常在内核日志中,可以通过以下命令查看:
1. **`dmesg`命令输出**:
```bash
dmesg | less
```
2. **系统日志文件**,如`/var/log/messages`或`/var/log/syslog`:
```bash
less /var/log/messages
```
### Call Trace 输出示例
假设我们有如下调用跟踪输出:
```
Call Trace:
[<ffffffff8105c8fd>] ? warn_slowpath_common+0x7d/0xb0
[<ffffffff8105c982>] ? warn_slowpath_null+0x22/0x30
[<ffffffff814fb2a3>] ? netif_receive_skb_internal+0x43/0x1b0
[<ffffffff8150a9b1>] ? __netif_receive_skb+0x101/0x3a0
[<ffffffff8150ad1c>] ? napi_gro_receive+0x8c/0xa0
[<ffffffffa03c8123>] ? e1000_clean_rx_irq+0x153/0x2a0 [e1000]
[<ffffffffa03c8f98>] ? e1000_poll+0x318/0x470 [e1000]
[<ffffffff8150b4f3>] ? net_rx_action+0x103/0x2f0
[<ffffffff8108e110>] ? __do_softirq+0xd0/0x2c0
[<ffffffff810acef3>] ? irq_exit+0x113/0x120
[<ffffffff815db2bc>] ? ret_from_intr+0x7c/0xb0
```
### 分析步骤
#### 步骤 1: 确定Call Trace的起始点和终止点
例如,在上面的示例中,`e1000_clean_rx_irq`和`e1000_poll`是e1000驱动程序中的函数。
#### 步骤 2: 确定主要功能点
我们看到`e1000_clean_rx_irq`和`e1000_poll`在调用跟踪中间,这提示问题可能与e1000网络驱动有关。
#### 步骤 3: 反汇编和符号解析
为了进一步分析,可以使用`addr2line`工具将地址转换回源代码行。
在这个例子中,我们需要内核对象文件`vmlinux`和驱动程序的源代码行信息。
假设我们的内核对象文件位于`/usr/lib/debug/boot/vmlinux-$(uname -r)`。
例如:
```bash
addr2line -e /usr/lib/debug/boot/vmlinux-$(uname -r) ffffffff8105c8fd
addr2line -e /usr/lib/debug/boot/vmlinux-$(uname -r) ffffffff8105c982
addr2line -e /usr/lib/debug/boot/vmlinux-$(uname -r) ffffffff814fb2a3
addr2line -e /usr/lib/debug/boot/vmlinux-$(uname -r) ffffffff8150a9b1
addr2line -e /usr/lib/debug/boot/vmlinux-$(uname -r) ffffffff8150ad1c
```
### 具体示例
1. **识别函数在源代码中的位置**:
```bash
addr2line -e /usr/lib/debug/boot/vmlinux-$(uname -r) ffffffff814fb2a3
```
输出可能类似于:
```
/path/to/kernel/source/net/core/dev.c:1234
```
2. **查看内核源代码**:
打开相应的文件并查看对应行的代码:
```bash
less +1234 /path/to/kernel/source/net/core/dev.c
```
### 使用GDB进行详细调试
可以将`vmlinux`加载到GDB中,进行更详细的调试和反汇编。
```bash
gdb /usr/lib/debug/boot/vmlinux-$(uname -r)
(gdb) list *0xffffffff8105c8fd
(gdb) list *0xffffffff8105c982
(gdb) list *0xffffffff814fb2a3
(gdb) list *0xffffffff8150a9b1
(gdb) list *0xffffffff8150ad1c
```
GDB会显示这些地址对应的源代码行。
### 准备工作
确保你有内核调试符号安装,例如在Debian/Ubuntu上,你可以安装:
```bash
sudo apt-get install linux-image-$(uname -r)-dbg
```
这将安装调试符号文件(如`vmlinux`),有助于进行地址到源代码行的映射。
### 总结
通过以上步骤,你可以将内核的调用跟踪地址映射到源代码行,理解内核崩溃或严重错误的根本原因。这对于调试builtin驱动和内核开发非常重要。
[ 62.316241] [GTP-INF][goodix_ts_core_init:2387] Core layer init:v1.3.1.6a
[ 62.316248] [GTP-INF][goodix_i2c_bus_init:289] Goodix i2c driver init
[ 62.326212] [GTP-INF][goodix_i2c_probe:172] goodix i2c probe in
[ 62.326219] [GTP-INF][goodix_get_ic_type:206] ic type fix to IC_TYPE_BERLIN_B for debug
[ 62.327259] [GTP-INF][goodix_i2c_probe:210] i2c dev register probe success
[ 62.327322] [GTP-INF][goodix_i2c_bus_init_psd:314] psd Goodix i2c driver init
[ 62.328104] [GTP-INF][goodix_i2c_probe:172] psd goodix i2c probe in
[ 62.328110] [GTP-INF][goodix_get_ic_type:206] ic type fix to IC_TYPE_BERLIN_B for debug
[ 62.328119] sysfs: cannot create duplicate filename '/devices/platform/goodix_ts'
[ 62.328122] CPU: 0 PID: 11495 Comm: insmod Kdump: loaded Tainted: G O 6.1.49-machine-ca3b62ec-release #1
[ 62.328126] Hardware name: Default string Default string/Default string, BIOS 0.8.29 07/15/2024
[ 62.328128] Call Trace:
[ 62.328133] <TASK>
[ 62.328139] dump_stack_lvl+0x38/0x4d
[ 62.328153] dump_stack+0x10/0x16
[ 62.328156] sysfs_warn_dup.cold+0x17/0x2b
[ 62.328164] sysfs_create_dir_ns+0xbc/0xd0
[ 62.328173] kobject_add_internal+0xb1/0x2b0
[ 62.328178] kobject_add+0x7e/0xb0
[ 62.328182] device_add+0x110/0x890
[ 62.328187] platform_device_add+0x10e/0x240
[ 62.328191] platform_device_register+0x81/0x90
[ 62.328196] goodix_i2c_probe+0xed/0x166 [goodix_berlin]
[ 62.328227] i2c_device_probe+0x2c3/0x340
[ 62.328234] ? goodix_i2c_write.cold+0x23/0x23 [goodix_berlin]
[ 62.328243] really_probe+0xed/0x3a0
[ 62.328246] ? pm_runtime_barrier+0x55/0x90
[ 62.328251] __driver_probe_device+0x7e/0x140
[ 62.328253] driver_probe_device+0x23/0xa0
[ 62.328256] __driver_attach+0xe4/0x1e0
[ 62.328258] ? __device_attach_driver+0x110/0x110
[ 62.328261] bus_for_each_dev+0x7f/0xd0
[ 62.328263] driver_attach+0x1e/0x30
[ 62.328265] bus_add_driver+0x1b6/0x210
[ 62.328268] driver_register+0x95/0x100
[ 62.328270] i2c_register_driver+0x47/0xc0
[ 62.328273] goodix_i2c_bus_init_psd+0xb9/0xc1 [goodix_berlin]
[ 62.328282] ? 0xffffffffc0292000
[ 62.328285] goodix_ts_core_init+0x34/0x1000 [goodix_berlin]
[ 62.328293] ? 0xffffffffc0292000
[ 62.328295] do_one_initcall+0x49/0x210
[ 62.328302] ? kmalloc_trace+0x2a/0xa0
[ 62.328308] do_init_module+0x52/0x200
[ 62.328316] load_module+0x1e75/0x2090
[ 62.328322] __do_sys_finit_module+0xc8/0x140
[ 62.328325] ? __do_sys_finit_module+0xc8/0x140
[ 62.328330] __x64_sys_finit_module+0x18/0x20
[ 62.328333] do_syscall_64+0x3b/0x90
[ 62.328336] entry_SYSCALL_64_after_hwframe+0x4c/0xb6
[ 62.328344] RIP: 0033:0x7fb11b104d5d
[ 62.328348] Code: 5b 41 5c c3 66 0f 1f 84 00 00 00 00 00 f3 0f 1e fa 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 8b 0d 93 b0 0e 00 f7 d8 64 89 01 48
[ 62.328351] RSP: 002b:00007fffc59302a8 EFLAGS: 00000246 ORIG_RAX: 0000000000000139
[ 62.328355] RAX: ffffffffffffffda RBX: 000056496f9bfb60 RCX: 00007fb11b104d5d
[ 62.328357] RDX: 0000000000000000 RSI: 000056496efea270 RDI: 0000000000000003
[ 62.328359] RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000000002
[ 62.328360] R10: 0000000000000003 R11: 0000000000000246 R12: 000056496efea270
[ 62.328362] R13: 0000000000000000 R14: 000056496efe8738 R15: 0000000000000000
[ 62.328365] </TASK>
[ 62.329187] kobject_add_internal failed for goodix_ts with -EEXIST, don't try to register things with the same name in the same directory.
[ 62.329191] [GTP-ERR][goodix_i2c_probe:207] psd failed register goodix platform device, -17
[ 62.329194] [GTP-INF][goodix_i2c_probe:216] psd i2c dev register probe failed, -17
[ 62.329197] gtx8_i2c_psd: probe of 11-005d failed with error -17
[ 62.329290] [GTP-INF][goodix_ts_probe:2238] IN
[ 62.329294] [GTP-INF][goodix_ts_probe:2256] dev_name(bus_interface->dev) = 0-005d, bus_interface->is_csd = 0
[ 62.329299] [GTP-INF][goodix_ts_gpio_setup:1439] GPIO setup,reset-gpio:0, irq-gpio:707
[ 62.329310] [GTP-INF][goodix_ts_power_init:1357] Power init
[ 62.329311] [GTP-INF][goodix_ts_power_init:1368] Avdd name is NULL
[ 62.329313] [GTP-INF][goodix_ts_power_init:1380] iovdd name is NULL
[ 62.329315] [GTP-INF][goodix_ts_power_on:1395] Device power on
[ 62.437997] [GTP-INF][goodix_tools_init:494] Debug tools miscdev register success
[ 62.438465] [GTP-INF][goodix_ts_probe:2313] goodix_ts_core probe success
[ 62.440272] [GTP-INF][brl_read_version:677] rom_pid:BERLIN
[ 62.440280] [GTP-INF][brl_read_version:678] rom_vid:01 00 05
[ 62.440283] [GTP-INF][brl_read_version:680] pid:6873F
[ 62.440284] [GTP-INF][brl_read_version:681] vid:00 20 ff 17
[ 62.440286] [GTP-INF][brl_read_version:683] sensor_id:0
[ 62.440288] [GTP-INF][goodix_read_cfg_bin:114] cfg_bin_name:goodix_cfg_group.bin
[ 62.440339] goodix_ts goodix_ts: Direct firmware load for goodix_cfg_group.bin failed with error -2
[ 62.440344] [GTP-INF][goodix_read_cfg_bin:120] get cfg bin retry:[1]
[ 62.593795] [Dlink0-215-215] dfs_handle_notify_server_r dfs disable
[ 62.593805] [Dlink0-215-215] dfs_handle_notify_server_r no new msg and wait [0 0]
[ 62.649886] goodix_ts goodix_ts: Direct firmware load for goodix_cfg_group.bin failed with error -2
[ 62.649900] [GTP-INF][goodix_read_cfg_bin:120] get cfg bin retry:[2]
[ 62.857806] goodix_ts goodix_ts: Direct firmware load for goodix_cfg_group.bin failed with error -2
[ 62.857819] [GTP-INF][goodix_read_cfg_bin:120] get cfg bin retry:[3]
[ 63.065769] [GTP-ERR][goodix_read_cfg_bin:124] failed get cfg bin[goodix_cfg_group.bin] error:-2
[ 63.065776] [GTP-ERR][goodix_get_config_data:314] failed get valid config bin data
[ 63.065779] [GTP-INF][goodix_later_init_thread:2143] no valid ic config found
[ 63.065792] [GTP-INF][goodix_later_init_thread:2154] update flag: 0x42
[ 63.065795] [GTP-INF][goodix_fw_update_thread:1331] Firmware request update starts
[ 63.065796] [GTP-INF][goodix_request_firmware:1286] Request firmware image [goodix_firmware.bin]
[ 63.066737] goodix_ts goodix_ts: Direct firmware load for goodix_firmware.bin failed with error -2
[ 63.066750] [GTP-INF][goodix_request_firmware:1292] get fw bin retry:[1]
[ 63.273843] goodix_ts goodix_ts: Direct firmware load for goodix_firmware.bin failed with error -2
[ 63.273860] [GTP-INF][goodix_request_firmware:1292] get fw bin retry:[2]
[ 63.481836] goodix_ts goodix_ts: Direct firmware load for goodix_firmware.bin failed with error -2
[ 63.481848] [GTP-INF][goodix_request_firmware:1292] get fw bin retry:[3]
[ 63.689765] [GTP-ERR][goodix_request_firmware:1296] Firmware image [goodix_firmware.bin] not available,errno:-2
[ 63.689773] [GTP-ERR][goodix_fw_update_thread:1374] fw update failed, -2
[ 63.689778] [GTP-INF][goodix_do_fw_update:1405] fw update return -2
[ 63.689821] [GTP-ERR][goodix_later_init_thread:2158] failed do fw update
[ 63.691625] [GTP-INF][brl_read_version:677] rom_pid:BERLIN
[ 63.691628] [GTP-INF][brl_read_version:678] rom_vid:01 00 05
[ 63.691632] [GTP-INF][brl_read_version:680] pid:6873F
[ 63.691634] [GTP-INF][brl_read_version:681] vid:00 20 ff 17
[ 63.691637] [GTP-INF][brl_read_version:683] sensor_id:0
[ 63.702073] [GTP-INF][print_ic_info:825] ic_info_length: 189
[ 63.702084] [GTP-INF][print_ic_info:827] info_customer_id: 0x1
[ 63.702086] [GTP-INF][print_ic_info:829] info_version_id: 0x0
[ 63.702088] [GTP-INF][print_ic_info:831] ic_die_id: 0x2
[ 63.702090] [GTP-INF][print_ic_info:833] ic_version_id: 0x0
[ 63.702091] [GTP-INF][print_ic_info:835] config_id: 0x66A1FDBE
[ 63.702093] [GTP-INF][print_ic_info:837] config_version: 0x3
[ 63.702094] [GTP-INF][print_ic_info:839] frame_data_customer_id: 0x1
[ 63.702095] [GTP-INF][print_ic_info:841] frame_data_version_id: 0x0
[ 63.702096] [GTP-INF][print_ic_info:843] touch_data_customer_id: 0x1
[ 63.702098] [GTP-INF][print_ic_info:845] touch_data_version_id: 0x0
[ 63.702099] [GTP-INF][print_ic_info:848] freqhop_feature: 0x0000
[ 63.702101] [GTP-INF][print_ic_info:850] calibration_feature: 0x0000
[ 63.702102] [GTP-INF][print_ic_info:852] gesture_feature: 0x0000
[ 63.702104] [GTP-INF][print_ic_info:854] side_touch_feature: 0x0000
[ 63.702105] [GTP-INF][print_ic_info:856] stylus_feature: 0x0000
[ 63.702106] [GTP-INF][print_ic_info:859] Drv*Sen,Button,Force num: 33 x 58, 0, 0
[ 63.702108] [GTP-INF][print_ic_info:863] Cmd: 0x10174, 16
[ 63.702109] [GTP-INF][print_ic_info:865] Cmd-Reply: 0x10184, 16
[ 63.702111] [GTP-INF][print_ic_info:867] FW-State: 0x10218, 92
[ 63.702112] [GTP-INF][print_ic_info:869] FW-Buffer: 0x13B74, 17524
[ 63.702113] [GTP-INF][print_ic_info:871] Touch-Data: 0x10274, 8
[ 63.702114] [GTP-INF][print_ic_info:873] point_struct_len: 8
[ 63.702116] [GTP-INF][print_ic_info:875] mutual_rawdata_addr: 0x1A67C
[ 63.702117] [GTP-INF][print_ic_info:877] mutual_diffdata_addr: 0x1C14C
[ 63.702118] [GTP-INF][print_ic_info:879] self_rawdata_addr: 0x18AC0
[ 63.702119] [GTP-INF][print_ic_info:881] self_diffdata_addr: 0x189D4
[ 63.702120] [GTP-INF][print_ic_info:883] stylus_rawdata_addr: 0x24E80, 0
[ 63.702121] [GTP-INF][print_ic_info:885] esd_addr: 0x10170
[ 63.702123] [GTP-INF][goodix_send_ic_config:2088] no valid normal config found
[ 63.702331] input: goodix_ts as /devices/virtual/input/input5
[ 63.702985] [GTP-INF][goodix_ts_irq_setup:1331] IRQ:125,flags:0
[ 63.703252] [GTP-INF][goodix_ts_stage2_init:2044] success register irq
[ 63.703523] [GTP-INF][goodix_ts_esd_on:1674] esd on
[ 63.703534] [GTP-INF][goodix_register_ext_module_no_wait:218] IN
[ 63.703537] [GTP-INF][__do_register_ext_module:152] Module [Goodix_gsx_gesture] registered,priority:2
[ 63.703540] [GTP-INF][gesture_module_init:418] gesture module init success
[ 63.703544] [GTP-INF][inspect_module_init:2922] inspect module init success
[ 63.703549] [GTP-INF][brl_hw_inspect_b2:1479] poweron inspect doesn't supported on this chip
[ 63.703553] [GTP-INF][goodix_later_init_thread:2195] success do HW inspect result 0x0
分析上方报错原因并给出解决方案。
根据你提供的日志信息,我们可以看到在尝试加载 `goodix` 设备驱动时发生了错误,具体错误表现在以下几部分:
### 错误分析:
1. **Duplicate Filename Error**:
```
[ 62.328119] sysfs: cannot create duplicate filename '/devices/platform/goodix_ts'
```
这表明系统尝试在 `/sys/devices/platform/` 中创建一个名为 `goodix_ts` 的目录,但是该目录已经存在。这通常是因为在同一个目录中已经存在一个具有相同名称的设备。
2. **EEXIST Error**:
```
[ 62.329187] kobject_add_internal failed for goodix_ts with -EEXIST, don't try to register things with the same name in the same directory.
```
该错误信息确认了 `kobject_add_internal` 函数由于名称重复(-EEXIST)而失败,提示不要尝试在同一个目录中注册相同名称的对象。
### 原因总结:
这些错误通常是由于以下几种情况引起的:
- 系统中已经存在一个同名的设备,可能是之前加载的驱动或设备未正确移除。
- 驱动在执行 `probe` 操作时,未能正确处理设备名称的重复性。
### 解决方案:
1. **检查与移除重复设备**:
如果之前加载的设备或驱动未正确卸载,可能会导致名称重复。可以尝试移除之前的设备或驱动,确保名称空间干净。
```sh
# 找到并移除已存在的设备
echo 1 > /sys/devices/platform/goodix_ts/remove
```
2. **修改设备命名**:
如果可以,在驱动中修改设备的名称,确保每个设备都有唯一的名称。例如,可以在设备名称中加入一个唯一的标识符。
```c
// 在驱动代码中修改设备名称
struct platform_device *pdev;
pdev = platform_device_alloc("goodix_ts_unique_name", -1);
```
3. **确保驱动正确卸载**:
在尝试加载新的驱动之前,确保先卸载旧的驱动。
```sh
# 卸载之前的驱动
rmmod goodix_berlin
```
4. **调试驱动代码**:
调试并确保在 `platform_device_register` 之前没有已存在的同名设备注册。
```c
// 检查设备是否已经存在
if (platform_get_device_id("goodix_ts") != NULL) {
printk(KERN_ERR "Device already registered\n");
return -EEXIST;
}
```
### 总结:
你遇到的错误是由于在 `/sys/devices/platform/` 路径下尝试创建一个已经存在的设备目录。解决方案是确保设备名称的唯一性,检查并移除重复的设备,并在必要时调整驱动代码以避免名称冲突。
在处理触摸屏驱动和系统文件系统(如 `procfs` 和 `sysfs`)时,存在几个关键点和最佳实践。下面是对你问题的详细解答:
### 1. 一个Touch IC驱动,但有两个屏幕触摸,是否需要创建两个input设备?
如果你有一个Touch IC,但连接了两个触摸屏,通常最佳实践是为每个触摸屏创建一个独立的输入设备(input device)。这样可以确保每个触摸屏的事件独立处理,避免混淆。
#### 举例:
假设你的驱动已经通过I2C或其他总线与Touch IC通信。你可以在驱动中为每个触摸屏创建两个输入设备:
```c
struct input_dev *input_dev1;
struct input_dev *input_dev2;
// 初始化第一个输入设备
input_dev1 = input_allocate_device();
if (!input_dev1)
{
return -ENOMEM;
}
input_dev1->name = "touchscreen1";
input_set_drvdata(input_dev1, data);
// 配置input_dev1的其他属性...
input_register_device(input_dev1);
// 初始化第二个输入设备
input_dev2 = input_allocate_device();
if (!input_dev2)
{
input_unregister_device(input_dev1);
return -ENOMEM;
}
input_dev2->name = "touchscreen2";
input_set_drvdata(input_dev2, data);
// 配置input_dev2的其他属性...
input_register_device(input_dev2);
```
### 2. 在Touch驱动中创建的`procfs`和`sysfs`是否会冲突?
`procfs`和`sysfs`是两个独立的文件系统,它们的用途和设计约束有些不同,但在正确使用的情况下,它们不会冲突。
#### `procfs`:
- 通常用于暴露内核信息和统计数据。
- 主要用于调试和诊断,不推荐用于配置和控制。
#### `sysfs`:
- 用于暴露系统设备模型和驱动程序接口。
- 推荐用于设备属性的配置和控制。
#### 示例:
##### `procfs` 示例代码:
```c
struct proc_dir_entry *proc_entry;
static ssize_t proc_read(struct file *file, char __user *buffer,
size_t count, loff_t *off) {
// 实现读取操作...
return 0;
}
static const struct file_operations proc_fops = {
.owner = THIS_MODULE,
.read = proc_read,
};
proc_entry = proc_create("touchscreen_info", 0444, NULL, &proc_fops);
if (!proc_entry) {
printk(KERN_ERR "Cannot create /proc/touchscreen_info\n");
return -ENOMEM;
}
```
##### `sysfs` 示例代码:
```c
static ssize_t attr_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
// 实现show函数...
return 0;
}
static ssize_t attr_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
// 实现store函数...
return count;
}
static DEVICE_ATTR(touch_attr, 0644, attr_show, attr_store);
int create_sysfs_entries(struct device *dev) {
int err;
err = device_create_file(dev, &dev_attr_touch_attr);
if (err) {
printk(KERN_ERR "Failed to create sysfs attribute\n");
return err;
}
return 0;
}
```
### 综上:
1. **创建两个输入设备**:如果有两个显示屏触摸,应该为每个触摸屏创建独立的输入设备以确保独立处理。
2. **`procfs` 和 `sysfs` 不冲突**:在正确使用的情况下,`procfs` 和 `sysfs` 不会冲突,因为它们是独立的文件系统。确保 `procfs` 主要用于信息暴露,而 `sysfs` 用于配置和控制。
通过合理设计驱动程序和文件系统接口,你可以有效且无冲突地管理多个触摸屏的输入事件和设备属性。
在处理一个Touch IC驱动连接两个屏幕触摸和文件系统创建的问题时,我们需要从两个方面来考虑:输入设备的创建和文件系统(`procfs`和`sysfs`)的使用。
### 1. 一个Touch IC驱动连接两个屏幕触摸,需要创建两个input设备吗?
如果一个Touch IC连接了两个独立的触摸屏,理论上每个触摸屏应该对应一个独立的输入设备(input device),这样可以分别处理两个触摸屏的输入事件,避免混淆。
#### 为什么需要两个input设备?
- **独立管理事件**:每个输入设备可以独立处理自己的触摸事件,避免事件交叉。
- **用户空间应用程序**:用户空间中的应用程序可以通过独立的输入设备文件(如`/dev/input/eventX`)来分别读取每个触摸屏的事件。
#### 如何创建两个input设备?
你可以在驱动中为每个触摸屏分别创建一个输入设备,如下所示:
```c
struct input_dev *input_dev1;
struct input_dev *input_dev2;
// 初始化第一个输入设备
input_dev1 = input_allocate_device();
if (!input_dev1)
{
return -ENOMEM;
}
input_dev1->name = "touchscreen1";
input_register_device(input_dev1);
// 初始化第二个输入设备
input_dev2 = input_allocate_device();
if (!input_dev2)
{
input_unregister_device(input_dev1);
return -ENOMEM;
}
input_dev2->name = "touchscreen2";
input_register_device(input_dev2);
```
### 2. 在驱动中创建相同名称的procfs和sysfs,是否会冲突?
#### `procfs`和`sysfs`的区别:
- **`procfs`**:主要用于暴露内核信息和统计数据,通常用于调试和诊断。
- **`sysfs`**:用于暴露系统设备模型和驱动程序接口,推荐用于设备属性的配置和控制。
#### 重名冲突问题:
在实际情况中,如果你在`procfs`和`sysfs`中创建了相同名称的文件,虽然它们属于不同的文件系统,但这种做法仍然不推荐,因为:
1. **代码可读性和维护性差**:相同名称的文件会让代码维护变得困难,尤其在调试时,很难立刻分辨出问题出在`procfs`还是`sysfs`。
2. **不符合最佳实践**:通常,`procfs`用于读取统计数据和调试信息,而`sysfs`用于动态配置设备属性。混用它们可能导致设计不清晰。
#### 解决方案:
- **避免重名**:使`procfs`和`sysfs`中的文件名具有唯一性。
- **合理设计结构**:确保每个文件系统中的文件反映了其特定用途。
#### 示例代码:
##### `procfs` 示例:
```c
struct proc_dir_entry *proc_entry;
static ssize_t proc_read(struct file *file, char __user *buffer, size_t count, loff_t *offset)
{
// 实现读取操作...
return 0;
}
static const struct file_operations proc_fops = {
.owner = THIS_MODULE,
.read = proc_read,
};
proc_entry = proc_create("touchscreen_info_proc", 0444, NULL, &proc_fops);
if (!proc_entry) {
printk(KERN_ERR "Cannot create /proc/touchscreen_info_proc\n");
return -ENOMEM;
}
```
##### `sysfs` 示例:
```c
static ssize_t attr_show(struct device *dev, struct device_attribute *attr, char *buf)
{
// 实现show函数...
return 0;
}
static ssize_t attr_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
// 实现store函数...
return count;
}
static DEVICE_ATTR(touch_attr_sys, 0644, attr_show, attr_store);
int create_sysfs_entries(struct device *dev) {
int err;
err = device_create_file(dev, &dev_attr_touch_attr_sys);
if (err) {
printk(KERN_ERR "Failed to create sysfs attribute\n");
return err;
}
return 0;
}
```
### 总结:
1. **创建两个输入设备**:对于一个Touch IC连接两个屏幕触摸的情况,应该为每个触摸屏创建独立的输入设备。
2. **避免文件系统冲突**:在`procfs`和`sysfs`中创建文件时,确保文件名唯一,避免名称冲突。同时,根据用途合理选择使用`procfs`或`sysfs`。
`kobject_create_and_add` 函数用于创建一个 `kobject` 并将其添加到内核对象层次结构中的指定位置。生成的位置主要由指定的父 `kobject` 决定,函数签名如下:
```c
struct kobject *kobject_create_and_add(const char *name, struct kobject *parent);
```
### 参数
- `name`:这是新 `kobject` 的名称。该名称将出现在 `/sys` 文件系统中。
- `parent`:这是新 `kobject` 的父 `kobject`。如果为 `NULL`,新 `kobject` 将被添加到根目录 `/sys` 下。
### 返回值
- 成功时返回新创建的 `kobject` 的指针。
- 失败时返回 `NULL`。
### 生成位置
- 如果 `parent` 参数不为 `NULL`,新 `kobject` 将被添加到该父 `kobject` 的目录下。
- 如果 `parent` 参数为 `NULL`,新 `kobject` 将被添加到根目录 `/sys` 下。
### 示例解释
让我们解析之前的示例代码,以了解 `kobject_create_and_add` 的生成位置。
```c
static int __init example_init(void)
{
int retval;
example_kobj = kobject_create_and_add("example_kobject", kernel_kobj);
if (!example_kobj)
return -ENOMEM;
retval = sysfs_create_file(example_kobj, &example_attribute.attr);
if (retval) {
kobject_put(example_kobj);
}
return retval;
}
```
在这段代码中,调用了 `kobject_create_and_add("example_kobject", kernel_kobj)`,因此:
- `name` 是 `"example_kobject"`。
- `parent` 是 `kernel_kobj`。
`kernel_kobj` 是内核中一个很重要的 `kobject`,它通常表示 `/sys/kernel` 目录。因此,新创建的 `kobject` 将出现在 `/sys/kernel/example_kobject` 下。
### 实验步骤
通过实际操作,可以验证上述解释。假设你已经编写并编译了模块,可以通过加载模块并查看 `/sys` 文件系统中的变化来验证。
1. 编译并加载模块:
```shell
make
sudo insmod your_module.ko
```
2. 使用 `ls` 命令查看 `/sys/kernel` 目录:
```shell
ls /sys/kernel/
# 你应该能看到 example_kobject 目录
```
3. 查看 `example_kobject` 下的文件:
```shell
ls /sys/kernel/example_kobject/
# 你应该能看到 example_value 属性
```
4. 读写 `example_value` 属性:
```shell
cat /sys/kernel/example_kobject/example_value
echo 42 > /sys/kernel/example_kobject/example_value
cat /sys/kernel/example_kobject/example_value # 应该看到 42
```
5. 卸载模块:
```shell
sudo rmmod your_module
```
通过这些步骤,你能确认 `kobject_create_and_add` 的确将 `example_kobject` 创建在 `/sys/kernel` 目录下,并且属性文件 `example_value` 也位于该目录下。
### 总结
使用 `kobject_create_and_add` 函数可以方便地创建并添加一个 `kobject` 到内核对象层次结构中的指定位置。具体生成的位置由其父 `kobject` 决定。如果父 `kobject` 是 `NULL`,新 `kobject` 将添加到 `/sys` 根目录下,否则将添加到指定父 `kobject` 的目录下。通过实验验证,可以确保这部分内核代码如预期工作。
在请求中断处理函数时,共用一个Touch驱动但两个屏幕的情况下,有关 `request_threaded_irq` 函数中传递相同的 `devname` 参数,这确实可以引发问题。
### `request_threaded_irq` 函数的参数
`request_threaded_irq` 的参数如下:
```c
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn, unsigned long flags,
const char *name, void *dev);
```
- **irq**: 中断号
- **handler**: 顶半部中断处理函数
- **thread_fn**: 线程化的底半部处理函数
- **flags**: 标志位
- **name**: 中断名称
- **dev**: 设备实例指针
### 使用相同的`devname`会发生的问题:
如果你为两个屏幕触摸使用相同的 `devname`,可能会遇到以下问题:
1. **命名冲突**:内核依赖 `devname` 来区分不同的中断源。如果两个中断请求具有相同的 `devname`,则内核无法正确区分这些中断,可能会覆盖其中一个或引发其他冲突。
2. **中断处理混淆**:如果两个中断处理程序混淆了 `devname`,可能会导致中断处理程序被错误地调用,导致无法正确处理触摸事件,或者出现竞争和资源访问冲突。
3. **调试困难**:相同的 `devname` 会使日志消息和调试信息混淆,增加问题排查的难度。
### 解决方案:
确保 `devname` 是唯一且可识别的,可以避免这些问题。你可以为每个屏幕触摸实例分别命名。下面是如何在同一个驱动中处理两个屏幕触摸,并为它们分别注册中断处理程序的示例代码:
#### 示例代码:
```c
// 假设两个屏幕共享一个Touch IC和同一个中断号
#define IRQ_TOUCH 42
struct my_touch_data {
struct input_dev *input_dev;
const char *devname;
// 其他设备相关的数据
};
// 中断处理函数 (顶半部)
irqreturn_t my_irq_handler(int irq, void *dev_id) {
struct my_touch_data *data = dev_id;
// 处理顶半部中断
return IRQ_WAKE_THREAD; // 运行底半部处理
}
// 中断处理函数 (底半部)
irqreturn_t my_threaded_irq_handler(int irq, void *dev_id) {
struct my_touch_data *data = dev_id;
// 处理底半部中断
return IRQ_HANDLED;
}
int register_touch_irq(struct my_touch_data *data, int irq) {
int ret;
ret = request_threaded_irq(irq, my_irq_handler,
my_threaded_irq_handler,
IRQF_ONESHOT, data->devname, data);
if (ret) {
printk(KERN_ERR "Failed to request IRQ for %s\n", data->devname);
}
return ret;
}
void my_driver_init(void) {
struct my_touch_data data1 = {
.devname = "touchscreen1",
// 初始化其他字段
};
struct my_touch_data data2 = {
.devname = "touchscreen2",
// 初始化其他字段
};
register_touch_irq(&data1, IRQ_TOUCH);
register_touch_irq(&data2, IRQ_TOUCH);
}
```
在上面的示例中,为`data1`和`data2`分别指定了不同的 `devname` (`"touchscreen1"` 和 `"touchscreen2"`),以确保它们的中断请求是独立的。
### 总结:
- **唯一的 `devname`**:为每个设备实例提供唯一的 `devname` 是至关重要的。
- **避免冲突和混淆**:独特的 `devname` 可以避免内核和调试时的混淆,确保中断处理程序的正确工作。
- **适当的资源管理**:确保在中断处理程序和驱动初始化过程中正确管理和区分设备资源以避免冲突。
X11(X Window System)作为一个传统的图形显示服务器协议,最初的设计并没有提供对触摸输入的原生支持。然而,随着触摸屏和多点触控设备的普及,X11通过扩展和驱动程序添加了对触摸输入的支持。
### 触摸输入的检测和管理
在X11中,触摸输入的检测和管理通常涉及以下几个组件:
1. **内核驱动(Kernel Drivers)**:
- Linux内核可以通过不同的驱动程序支持各种触摸屏设备。这些驱动程序通常会被编译为内核模块(`.ko`文件)或者直接内嵌在内核中(built-in)。
- 通常,这些驱动程序会通过内核的输入子系统(Input subsystem)将触摸事件(如触摸点的位置、压力等)报告给用户空间。
2. **evdev 驱动**:
- 在用户空间中,最常见的输入驱动程序是 `evdev` 驱动,它从内核的输入子系统接收事件。
- `evdev` 驱动程序可以处理标准的输入设备事件,并将它们转换为X11可以理解的事件格式。
3. **XInput 扩展**:
- X11通过XInput扩展来管理复杂的输入设备,包括触摸屏。XInput 2.0及以上版本引入了对多点触控设备的支持。
- XInput扩展允许应用程序查询和配置输入设备,并处理复杂的输入事件如多点触控手势。
4. **设备描述文件(Device Descriptor Files)和设置**:
- 有时,触摸屏设备可能需要特定的配置文件或者校准数据,这些信息通常在 `/usr/share/X11/xorg.conf.d/` 或者 `/etc/X11/xorg.conf.d/` 目录下配置。
- 例如,可能会有一些特定于设备的配置文件,如 `10-evdev.conf` 或 `40-libinput.conf` 文件,用于配置输入设备的行为。
### Touch 驱动和内核模块的关系
触摸驱动和内核模块确实有很大关系:
- **内核模块(.ko 文件)**:
- 这些模块是设备的底层驱动程序,它们负责直接与硬件交互,并将硬件事件传递到内核的输入子系统。
- 例如,`hid-multitouch.ko` 是一个常见的多点触控驱动模块,用于许多触摸屏设备。
- **内嵌驱动**:
- 有些驱动程序可以在编译内核时作为 built-in 模块直接包含在内核映像中,而不是作为可加载模块。
- 无论是 built-in 还是模块化的驱动程序,它们的功能基本相同,都是提供设备支持并报告输入事件。
总结起来,X11通过内核驱动、`evdev` 驱动、和 XInput 扩展来检测和管理触摸输入。内核驱动程序(无论是模块还是内嵌)负责硬件层面的输入事件处理,而用户空间的驱动和X11扩展则处理这些事件并将其传递给应用程序。
触摸驱动加载为内核模块(ko)或作为内核内嵌驱动(built-in)确实会影响X11初始化和检测事件的时机,但不会根本性地改变事件管理的方式。具体来说:
### 触摸驱动作为 .ko 模块
如果触摸驱动程序被编译为可加载模块(.ko 文件),那么设备的检测和初始化过程分为以下几个步骤:
1. **内核模块加载**:
- 在操作系统启动时或者在设备连接时,内核模块(.ko 文件)被加载并初始化设备。
- 这可以是通过 `modprobe` 或 `insmod` 命令手动加载,或者通过 udev 规则自动加载。
2. **设备注册**:
- 内核模块加载后,驱动程序会注册设备并将其信息发布到内核的输入子系统中。
- 设备文件通常会出现在 `/dev/input/` 目录下,名称类似 `eventX`。
3. **X11 服务器启动**:
- 当X11服务器启动时,`evdev` 驱动程序或其他输入驱动程序会扫描 `/dev/input/` 目录,识别和初始化所有已注册的输入设备。
- 这时,X11会通过 `XInput` 扩展来管理这些设备并为应用程序提供事件接口。
### 触摸驱动作为内嵌驱动(built-in)
如果触摸驱动程序是内嵌在内核中的,则设备的检测和初始化过程稍有不同:
1. **内核启动**:
- 在系统启动时,内嵌驱动程序会被自动初始化并加载,无需额外的用户干预。
- 和模块化驱动一样,设备会被注册到内核的输入子系统中,设备文件也会出现在 `/dev/input/` 目录下。
2. **设备文件生成**:
- 内嵌驱动会在系统启动过程中自动创建对应的 `eventX` 设备文件,确保设备在X11服务器启动之前已经可用。
3. **X11 服务器启动**:
- 当X11服务器启动时,它会与模块化驱动程序类似,扫描 `/dev/input/` 目录,识别并初始化输入设备。
### 对X11初始化次eventX事件检测的影响
无论触摸驱动是作为可加载模块还是内嵌在内核中,对X11初始化次事件检测的主要影响是**时机**:
- **模块化驱动**:如果驱动模块在X11服务器启动之后才被加载,那么这些设备可能在X11启动时未被检测到。这可能需要重新启动X11服务器或者通过某种方式重新扫描设备。
- **内嵌驱动**:内嵌驱动确保在系统启动时所有设备都已经被初始化和注册,因此X11在启动时能即时检测到所有输入设备。
### 总结
- **模块化驱动**:需要确保在X11启动之前加载,以便X11能够检测到设备。
- **内嵌驱动**:一般会在系统启动时自动加载,不需要额外干预,确保设备在X11启动时可用。
两者都不会根本性地影响X11事件管理的机制,只是初始化和检测设备的时机有所不同。关键是确保设备在X11服务器启动时已经正确注册并可用。
`struct input_dev` 中的 `phys` 成员代表设备在系统层次结构中的物理路径。具体来说,它是一个字符串,表示输入设备在硬件或总线上的位置。
例如,对于USB设备,它可能类似于 `usb-0000:00:1d.0-1`。
这个路径通常由驱动程序生成,用于唯一标识设备的物理位置。`phys` 成员对于调试和设备管理非常重要,因为它可以帮助识别和区分系统中多个类似的设备。
当两个屏幕共享一个Touch驱动时,是否可以将 `phys` 变量设置为相同取决于驱动程序和系统对这个字段的使用方式。
如果 `phys` 字段仅用于调试和唯一标识设备,那么设置为相同可能不会对功能产生直接影响。但是,需要注意以下几点:
1. **唯一性**:为了避免混淆和潜在的冲突,通常应确保每个输入设备的 `phys` 字段唯一。
即使是同一个驱动管理的不同设备,最好也给予不同的 `phys` 值,以便系统和开发人员可以准确地识别和区分它们。
2. **驱动程序依赖**:某些驱动程序可能依赖 `phys` 字段来执行特定操作,如资源分配、事件处理等。
在这种情况下,如果两个设备的 `phys` 字段相同,可能会导致驱动程序行为异常。
3. **用户空间工具**:许多用户空间工具(如 `libinput`、`udev` 等)可能使用 `phys` 字段进行设备管理和配置。
如果两个设备的 `phys` 字段相同,这些工具可能无法正确区分和管理设备。
所以,总的来说,为了确保系统的稳定和正确行为,建议为每个输入设备分配唯一的 `phys` 字段,即使它们共享同一个驱动程序。
一个常见的做法是基于设备的具体连接信息(如总线号、端口号)来生成唯一的 `phys` 路径。
在触摸屏(Touch Panel,简称TP)驱动中,`gesture_xxx` 通常是用于实现手势识别和处理功能的标识或函数。这些手势可能包括单指点击、多指点击、滑动、捏合(缩放)、旋转等。下面是一些常见的手势功能及其可能的实现方式:
### 1. 单指点击 (Single Tap)
单指点击通常是屏幕上某一位置的快速触摸和释放。
```c
void gesture_single_tap(struct input_dev *dev, int x, int y) {
// 模拟单击事件
input_report_key(dev, BTN_TOUCH, 1);
input_report_abs(dev, ABS_X, x);
input_report_abs(dev, ABS_Y, y);
input_sync(dev);
input_report_key(dev, BTN_TOUCH, 0);
input_sync(dev);
}
```
### 2. 多指点击 (Multi Tap)
多指点击涉及在屏幕上同时触摸多个点。
```c
void gesture_multi_tap(struct input_dev *dev, int x1, int y1, int x2, int y2) {
// 模拟两个触点的多点触控
input_mt_slot(dev, 0);
input_mt_report_slot_state(dev, MT_TOOL_FINGER, true);
input_report_abs(dev, ABS_MT_POSITION_X, x1);
input_report_abs(dev, ABS_MT_POSITION_Y, y1);
input_mt_slot(dev, 1);
input_mt_report_slot_state(dev, MT_TOOL_FINGER, true);
input_report_abs(dev, ABS_MT_POSITION_X, x2);
input_report_abs(dev, ABS_MT_POSITION_Y, y2);
input_sync(dev);
// 结束触摸
input_mt_slot(dev, 0);
input_mt_report_slot_state(dev, MT_TOOL_FINGER, false);
input_mt_slot(dev, 1);
input_mt_report_slot_state(dev, MT_TOOL_FINGER, false);
input_sync(dev);
}
```
### 3. 滑动 (Swipe)
滑动手势通常是从一个点到另一个点的连续移动。
```c
void gesture_swipe(struct input_dev *dev, int start_x, int start_y, int end_x, int end_y) {
int i, steps = 10;
for (i = 0; i <= steps; i++) {
int x = start_x + (end_x - start_x) * i / steps;
int y = start_y + (end_y - start_y) * i / steps;
input_report_key(dev, BTN_TOUCH, 1);
input_report_abs(dev, ABS_X, x);
input_report_abs(dev, ABS_Y, y);
input_sync(dev);
}
input_report_key(dev, BTN_TOUCH, 0);
input_sync(dev);
}
```
### 4. 捏合/缩放 (Pinch/Zoom)
捏合通常是两个手指靠近(缩小)或远离(放大)的动作。
```c
void gesture_pinch_zoom(struct input_dev *dev, bool zoom_in) {
int steps = 10, i;
for (i = 0; i <= steps; i++) {
int offset = zoom_in ? i : (steps - i);
input_mt_slot(dev, 0);
input_mt_report_slot_state(dev, MT_TOOL_FINGER, true);
input_report_abs(dev, ABS_MT_POSITION_X, 100 - offset);
input_report_abs(dev, ABS_MT_POSITION_Y, 100 - offset);
input_mt_slot(dev, 1);
input_mt_report_slot_state(dev, MT_TOOL_FINGER, true);
input_report_abs(dev, ABS_MT_POSITION_X, 200 + offset);
input_report_abs(dev, ABS_MT_POSITION_Y, 200 + offset);
input_sync(dev);
}
input_mt_slot(dev, 0);
input_mt_report_slot_state(dev, MT_TOOL_FINGER, false);
input_mt_slot(dev, 1);
input_mt_report_slot_state(dev, MT_TOOL_FINGER, false);
input_sync(dev);
}
```
### 5. 旋转 (Rotate)
旋转手势是两个手指绕一个中心点进行旋转。
```c
void gesture_rotate(struct input_dev *dev, int center_x, int center_y, int angle) {
int steps = 10, i;
for (i = 0; i <= steps; i++) {
int a = angle * i / steps;
int x1 = center_x + 50 * cos(a);
int y1 = center_y + 50 * sin(a);
int x2 = center_x - 50 * cos(a);
int y2 = center_y - 50 * sin(a);
input_mt_slot(dev, 0);
input_mt_report_slot_state(dev, MT_TOOL_FINGER, true);
input_report_abs(dev, ABS_MT_POSITION_X, x1);
input_report_abs(dev, ABS_MT_POSITION_Y, y1);
input_mt_slot(dev, 1);
input_mt_report_slot_state(dev, MT_TOOL_FINGER, true);
input_report_abs(dev, ABS_MT_POSITION_X, x2);
input_report_abs(dev, ABS_MT_POSITION_Y, y2);
input_sync(dev);
}
input_mt_slot(dev, 0);
input_mt_report_slot_state(dev, MT_TOOL_FINGER, false);
input_mt_slot(dev, 1);
input_mt_report_slot_state(dev, MT_TOOL_FINGER, false);
input_sync(dev);
}
```
这些函数需要与驱动程序的事件处理逻辑整合,以便在检测到相应的手势时触发相应的动作。这些例子只是基本的手势实现,实际应用中可能需要更复杂的逻辑来处理边界条件和优化用户体验。
`kobj` 是 `struct kobject` 的缩写,代表内核对象(kernel object)。`kobject` 是 Linux 内核中的基本数据结构之一,主要用于内核对象的管理和 sysfs(虚拟文件系统)目录的创建及管理。`kobject` 提供了一个统一的机制,用于管理内核中的对象并方便地导出这些对象的信息到用户空间。
### `kobject` 的作用
1. **对象管理**:
- `kobject` 是 Linux 内核对象管理的核心,提供了一种通用的方式来管理内核中的各类对象,如设备、驱动、模块等。
2. **sysfs 支持**:
- `kobject` 与 sysfs 关联紧密,允许内核对象通过 sysfs 导出到用户空间,以便用户查看和操作内核对象的属性。
3. **引用计数**:
- `kobject` 内部集成了引用计数功能,用于管理内核对象的生命周期,防止对象被多次释放或在多个地方使用时意外释放。
4. **层次结构**:
- `kobject` 支持对象的层次结构,允许内核对象以树的形式组织,这对于复杂系统(如设备树)特别有用。
### `kobject` 相关的常用函数
以下是一些常用的 `kobj_xx` 系列函数及其用途:
1. **kobject_init()**:
- 初始化一个 `kobject` 结构,但不增加引用计数。
```c
void kobject_init(struct kobject *kobj, struct kobj_type *ktype);
```
2. **kobject_add()**:
- 将 `kobject` 添加到系统中。此函数会增加引用计数并在 sysfs 中创建相应的条目。
```c
int kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...);
```
3. **kobject_put()**:
- 减少 `kobject` 的引用计数。当引用计数降到零时,释放 `kobject` 及其关联的资源。
```c
void kobject_put(struct kobject *kobj);
```
4. **kobject_get()**:
- 增加 `kobject` 的引用计数,确保对象在使用过程中不会被释放。
```c
struct kobject *kobject_get(struct kobject *kobj);
```
5. **kobject_create_and_add()**:
- 创建并初始化一个 `kobject`,同时将其添加到系统中。
```c
struct kobject *kobject_create_and_add(const char *name, struct kobject *parent);
```
6. **kobject_del()**:
- 从系统中删除一个 `kobject`,但不释放其内存。
```c
void kobject_del(struct kobject *kobj);
```
7. **kobject_uevent()**:
- 生成 uevent 事件,通知用户空间有新设备或内核对象发生变化。
```c
int kobject_uevent(struct kobject *kobj, enum kobject_action action);
```
### 示例代码
以下是一个简单的示例代码,演示如何使用 `kobject` 相关函数:
```c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
static struct kobject *example_kobj;
static ssize_t example_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "Hello, world!\n");
}
static struct kobj_attribute example_attr = __ATTR_RO(example);
static int __init example_init(void)
{
int retval;
// 创建并添加 kobject
example_kobj = kobject_create_and_add("example", kernel_kobj);
if (!example_kobj)
return -ENOMEM;
// 创建 sysfs 属性文件
retval = sysfs_create_file(example_kobj, &example_attr.attr);
if (retval) {
kobject_put(example_kobj);
return retval;
}
return 0;
}
static void __exit example_exit(void)
{
// 删除 sysfs 属性文件
sysfs_remove_file(example_kobj, &example_attr.attr);
// 释放 kobject
kobject_put(example_kobj);
}
module_init(example_init);
module_exit(example_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Author");
MODULE_DESCRIPTION("A simple kobject example");
```
在这个例子中,我们创建了一个名为 "example" 的 `kobject`,并在其下创建了一个只读属性文件。当加载模块时,这个属性文件将显示 "Hello, world!"。这个示例涵盖了 `kobject_create_and_add`、`sysfs_create_file` 和 `kobject_put` 等函数的基本使用。
### 总结
`kobject` 是 Linux 内核中用于管理内核对象和支持 sysfs 功能的重要结构。通过 `kobj_xx` 系列函数,内核模块可以方便地创建、管理和销毁内核对象,并将对象信息导出到用户空间。掌握 `kobject` 及其相关函数的使用对于内核开发尤其是驱动开发至关重要。
`kobject` 是 Linux 内核中用于管理对象的基础结构,它提供了一种统一的方式来表示系统中的对象,并支持对象的引用计数和生命周期管理。`kobject` 相关的函数通常以 `kobject_` 前缀命名,这些函数提供了各种操作 `kobject` 的手段。以下是几个常见的 `kobject` 函数及其作用和使用场景:
1. **`kobject_init`**:初始化一个 `kobject`。
2. **`kobject_add`**:将一个 `kobject` 添加到系统中。
3. **`kobject_put`**:减少 `kobject` 的引用计数,如果引用计数变为 0,则释放该 `kobject`。
4. **`kobject_get`**:增加 `kobject` 的引用计数。
5. **`kobject_del`**:从系统中删除一个 `kobject`。
6. **`kobject_create_and_add`**:分配、初始化和添加一个 `kobject`。
7. **`kobject_uevent`**:生成用户空间事件。
8. **`kobject_init_and_add`**:初始化和添加一个 `kobject`。
### 示例代码
以下是一个详细的示例代码,演示如何使用 `kobject` 相关的函数在 Linux 内核模块中管理对象:
```c
#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/slab.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple example using kobject");
static struct kobject *example_kobj;
static int example_value;
static ssize_t example_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", example_value);
}
static ssize_t example_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
{
sscanf(buf, "%du", &example_value);
return count;
}
static struct kobj_attribute example_attribute = __ATTR(example_value, 0664, example_show, example_store);
static int __init example_init(void)
{
int retval;
example_kobj = kobject_create_and_add("example_kobject", kernel_kobj);
if (!example_kobj)
return -ENOMEM;
retval = sysfs_create_file(example_kobj, &example_attribute.attr);
if (retval) {
kobject_put(example_kobj);
}
return retval;
}
static void __exit example_exit(void)
{
kobject_put(example_kobj);
}
module_init(example_init);
module_exit(example_exit);
```
### 解析
1. **模块引入和宏定义**:
```c
#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/slab.h>
```
2. **模块信息**:
```c
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple example using kobject");
```
3. **全局变量**:
```c
static struct kobject *example_kobj;
static int example_value;
```
4. **属性操作函数**:
```c
static ssize_t example_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", example_value);
}
static ssize_t example_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
{
sscanf(buf, "%du", &example_value);
return count;
}
```
5. **定义 kobject 属性**:
```c
static struct kobj_attribute example_attribute = __ATTR(example_value, 0664, example_show, example_store);
```
6. **模块初始化函数**:
```c
static int __init example_init(void)
{
int retval;
example_kobj = kobject_create_and_add("example_kobject", kernel_kobj);
if (!example_kobj)
return -ENOMEM;
retval = sysfs_create_file(example_kobj, &example_attribute.attr);
if (retval) {
kobject_put(example_kobj);
}
return retval;
}
```
7. **模块退出函数**:
```c
static void __exit example_exit(void)
{
kobject_put(example_kobj);
}
```
8. **模块入口和出口**:
```c
module_init(example_init);
module_exit(example_exit);
```
### 使用场景
1. **管理复杂设备和驱动**:例如,PCI 或 USB 设备。
2. **创建内核与用户空间的接口**:如通过 sysfs 文件系统。
3. **组织和管理内核对象的层次结构**:如设备树。
此示例模块在加载时会创建一个名为 `example_kobject` 的 `kobject`,并在 `/sys/kernel/example_kobject` 下创建一个 `example_value` 属性,可以通过系统的 sysfs 文件系统进行读写操作。
从您提供的日志来看,触摸屏驱动程序 (`goodix_berlin`) 在初始化过程中尝试创建 sysfs 节点时遇到了错误。
这些错误的根本原因是尝试创建重复的 sysfs 节点,具体地说,`gesture` 和 `get_rawdata` 节点已经存在,导致创建新节点失败。以下是对日志中主要错误的具体分析:
### 关键日志信息
1. **Duplicate Filename for `gesture` Node**
```
[ 103.192818] sysfs: cannot create duplicate filename '/devices/platform/goodix_ts_psd/gesture'
[ 103.192939] kobject_add_internal failed for gesture with -EEXIST, don't try to register things with the same name in the same directory.
[ 103.192941] [GTP-ERR][gesture_module_init:399] failed create gesture sysfs node!
[ 103.192942] [GTP-ERR][gesture_module_init:423] gesture module init failed!
```
这些日志信息表明,驱动程序在尝试创建名为 `gesture` 的 sysfs 节点时失败了,原因是该节点已经存在。
2. **Duplicate Filename for `get_rawdata` Node**
```
[ 103.192945] sysfs: cannot create duplicate filename '/devices/platform/goodix_ts_psd/get_rawdata'
[ 103.192989] [GTP-ERR][inspect_module_init:2917] create sysfs of get_rawdata failed
[ 103.192990] [GTP-ERR][inspect_module_init:2926] inspect module init failed!
```
类似地,这些日志信息表示,驱动程序在尝试创建名为 `get_rawdata` 的 sysfs 节点时也失败了,因为该节点已经存在。
### 详细分析
- **重复创建 sysfs 节点**:日志显示初始化线程 `goodix_init_thr` 在尝试创建 sysfs 节点时失败,原因是这些节点已经存在。
sysfs 是 Linux 内核用于导出内核对象层次结构的虚拟文件系统,每个节点必须具有唯一的名称。在试图创建具有相同名称的节点时,会导致 `-EEXIST` 错误。
- **错误处理**:从日志中可以看出,驱动程序在 `gesture_module_init` 和 `inspect_module_init` 函数中处理节点创建失败的情况。这表明触摸屏驱动在初始化手势和检查模块时遇到了问题。
### 建议的解决方法
1. **检查重复加载**:确保驱动程序没有被重复加载。在某些情况下,驱动程序可能会由于某些错误或配置问题而被多次加载,导致重复的节点创建。
2. **确保唯一性**:确保每个 sysfs 节点的名称在系统中是唯一的。如果多个设备共享同一个驱动程序,确保为每个设备生成唯一的 sysfs 节点名称。
3. **排查设备重用**:如果系统中存在多个类似设备(如多个触摸屏),请确认驱动程序具有适当的逻辑来区分这些设备并为它们创建独立的 sysfs 节点。
4. **调试信息**:添加调试信息以进一步调查在何时、为何节点已经存在。这些信息可以帮助确定问题的根本原因。
### 示例修改
假设是节点名称冲突导致的问题,可以修改节点名称以确保独特性,下面是示例代码修改:
```c
// 假设在 gesture_module_init 中创建节点
int gesture_module_init(void) {
static int instance = 0; // 用于确保唯一性
char gesture_name[32];
snprintf(gesture_name, sizeof(gesture_name), "gesture%d", instance++);
// 创建 sysfs 节点
ret = sysfs_create_file(&dev->kobj, &gesture_attribute.attr);
if (ret) {
pr_err("failed create gesture sysfs node!\n");
return ret;
}
return 0;
}
// 同样修改 inspect_module_init
int inspect_module_init(void) {
static int instance = 0;
char rawdata_name[32];
snprintf(rawdata_name, sizeof(rawdata_name), "get_rawdata%d", instance++);
// 创建 sysfs 节点
ret = sysfs_create_file(&dev->kobj, &rawdata_attribute.attr);
if (ret) {
pr_err("create sysfs of get_rawdata failed\n");
return ret;
}
return 0;
}
```
这些修改确保每个节点名称在系统中是唯一的,从而避免了创建重复节点的问题。当然,具体的修改需根据实际代码结构和逻辑进行调整。
在 Linux 内核中,无论是通过设备树(Device Tree)还是非设备树(如平台设备,Platform Device)的方式注册设备,
都需要初始化 `struct device` 结构和它的成员 `kobj`(`struct kobject`)。
### 有设备树的情况
在使用设备树的情况下,设备的注册通常通过设备树节点来描述和初始化。在这种情况下,设备树驱动会从设备树中获取设备信息,并将其注册到内核中。
1. **通过 `of_platform_populate()` 函数**:
当设备树设备被发现时,内核调用 `of_platform_populate()` 函数来为设备树中的每个设备节点创建设备。
这个过程中,`struct device` 的初始化包括调用 `device_initialize()`,同时初始化 `kobj`。
2. **调用设备树驱动的 `probe` 函数**:
在设备树驱动的 `probe` 函数中,通常会通过 `of_find_device_by_node()` 等函数将设备节点与驱动绑定,这时会初始化 `kobj`。
以下是一个使用设备树的设备初始化过程的简化示例:
```c
static int my_driver_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
// 其他初始化操作
return 0;
}
static const struct of_device_id my_driver_of_match[] = {
{ .compatible = "myvendor,mydevice", },
{ }
};
MODULE_DEVICE_TABLE(of, my_driver_of_match);
static struct platform_driver my_driver = {
.probe = my_driver_probe,
.driver = {
.name = "my_driver",
.of_match_table = my_driver_of_match,
},
};
module_platform_driver(my_driver);
```
在这个过程中,设备树解析后会调用 `my_driver_probe` 函数,其中 `pdev->dev` 的 `kobj` 成员会在设备树设备初始化时由内核自动初始化。
### 无设备树的情况
对于没有使用设备树的情况,例如平台设备直接在代码中注册的情况,设备的初始化通常是通过手动创建和注册的平台设备结构。
1. **平台设备初始化**:
当调用 `platform_device_register()` 时,会分配和初始化 `struct device`,包括其 `kobj` 成员。
这通常在 `platform_device_register()` 或 `platform_device_register_full()` 中完成。
2. **调用平台设备驱动的 `probe` 函数**:
与设备树类似,平台设备驱动的 `probe` 函数会被调用,在该函数中也会进行设备的初始化。
以下是一个不使用设备树的设备初始化过程的简化示例:
```c
static struct platform_device my_device = {
.name = "my_device",
.id = -1,
// 其他设备初始化
};
static int my_driver_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
// 其他初始化操作
return 0;
}
static struct platform_driver my_driver = {
.probe = my_driver_probe,
.driver = {
.name = "my_device",
},
};
static int __init my_init(void)
{
platform_device_register(&my_device);
return platform_driver_register(&my_driver);
}
static void __exit my_exit(void)
{
platform_driver_unregister(&my_driver);
platform_device_unregister(&my_device);
}
module_init(my_init);
module_exit(my_exit);
```
在这种情况下,调用 `platform_device_register(&my_device)` 时,会初始化 `my_device.dev` 结构中的 `kobj` 成员。
### 总结
无论是设备树还是非设备树的方式,`struct device` 中的 `kobj` 成员都会在设备初始化过程中被内核适当地初始化:
- **设备树情况**:由设备树解析和匹配机制自动处理。
- **非设备树情况**:通过平台设备注册函数(如 `platform_device_register`)处理。
这两个过程的主要区别在于设备描述信息的来源(设备树节点 vs 代码硬编码)以及设备初始化的触发时机。
内核中的设备模型和对象模型确保了 `kobj` 在设备注册过程中始终得到正确的初始化和管理。
在 Linux 内核中,从一个 `kobject` 对象获取设备名称通常可以通过以下几种方式实现:
1. **通过 `kobject` 的 `name` 字段**:
- 每个 `kobject` 都有一个 `name` 字段,该字段通常可以用于获取设备名称。
2. **通过 `kobject_name()` 函数**:
- 内核提供了一个函数 `kobject_name()`,可以直接返回 `kobject` 对象的名称。
以下是具体的实现方法:
### 方法1: 直接访问 `kobject` 的 `name` 字段
如果你有一个指向 `kobject` 的指针,可以直接访问它的 `name` 字段。以下是一个简单的示例:
```c
#include <linux/kobject.h>
struct kobject *kobj;
const char *name;
// 假设 kobj 已经被初始化和赋值
name = kobj->name;
printk("kobject name: %s\n", name);
```
### 方法2: 使用 `kobject_name()` 函数
`kobject_name()` 是一个方便函数,用于获取 `kobject` 的名称。以下是一个示例:
```c
#include <linux/kobject.h>
struct kobject *kobj;
const char *name;
// 假设 kobj 已经被初始化和赋值
name = kobject_name(kobj);
printk("kobject name: %s\n", name);
```
### 示例代码
下面是一个综合示例,展示如何获取 `kobject` 的名称:
```c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kobject.h>
// 模拟的 kobject 对象
static struct kobject example_kobj;
static int __init example_init(void)
{
const char *name;
// 初始化 kobject
kobject_init(&example_kobj, NULL);
example_kobj.name = "example_object";
// 使用 kobject_name() 函数获取名称
name = kobject_name(&example_kobj);
printk("kobject name using kobject_name: %s\n", name);
// 直接访问 kobject 的 name 字段
name = example_kobj.name;
printk("kobject name using direct access: %s\n", name);
return 0;
}
static void __exit example_exit(void)
{
// 清理 kobject
kobject_put(&example_kobj);
}
module_init(example_init);
module_exit(example_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Author");
MODULE_DESCRIPTION("A simple kobject example");
```
在这个示例中,`example_init` 函数演示了如何通过 `kobject_name()` 函数和直接访问 `kobject` 的 `name` 字段获取设备名称。
### 重要注意事项
- 确保 `kobject` 已经被正确初始化并赋值,否则访问其字段可能会导致未定义行为或内核崩溃。
- 在对 `kobject` 进行任何操作之前,确保它的生命周期在当前上下文中有效。
通过上述方法,你可以轻松地从 `kobject` 获取设备名称。
大端模式(Big-endian)和小端模式(Little-endian)是计算机系统中存储多字节数据的一种约定方式,决定了数据的字节序排列。
1. **大端模式(Big-endian)**:
在大端模式中,高位字节存储在低地址处,低位字节存储在高地址处。这种模式像人类书写数字的方式,从高位到低位。
例如,一个32位整数 `0x12345678` 在大端模式下的存储顺序如下:
```
地址: 00 01 02 03
内容: 0x12 0x34 0x56 0x78
```
2. **小端模式(Little-endian)**:
在小端模式中,低位字节存储在低地址处,高位字节存储在高地址处。这种模式的排序与一般直观的排序方式相反。
例如,同样一个32位整数 `0x12345678` 在小端模式下的存储顺序如下:
```
地址: 00 01 02 03
内容: 0x78 0x56 0x34 0x12
```
### 读写数据:
**读数据**:
- 从存储器中读取数据时,计算机系统会根据预定的字节序(大端或小端)将字节还原成正确的数值。
- 例如,在小端系统中读取0x12345678,会依次读取 `0x78`、`0x56`、`0x34`、`0x12` 并重新组合成 `0x12345678`。
**写数据**:
- 将数据写入存储器时,系统会根据预定的字节序(大端或小端)将数值的各字节按正确顺序存储。
- 例如,在小端系统中写入 `0x12345678`,会拆分成 `0x78`、`0x56`、`0x34`、`0x12` 并按顺序存储在相应的地址中。
### 系统和应用:
- **大端模式**:许多网络协议(如TCP/IP)使用大端字节序,也被称为网络字节序。
- **小端模式**:许多现代个人计算机(如x86架构)使用小端字节序。
### 混用问题:
- 不同系统间进行数据交换时需要注意字节序的问题。例如,网络传输中会使用大端字节序,但接收端可能是小端系统,这时需要进行字节序转换才能正确理解数据。
### 代码示例(C语言):
以下是如何在C语言中处理字节序的简单示例:
```c
#include <stdio.h>
// 判断系统字节序
int is_little_endian() {
unsigned int x = 1;
char *c = (char*)&x;
return *c; // 如果系统是小端序,则返回1;否则返回0.
}
int main() {
if (is_little_endian()) {
printf("系统是小端字节序\n");
} else {
printf("系统是大端字节序\n");
}
unsigned int num = 0x12345678;
char *ptr = (char*)#
printf("内存中的存储顺序: ");
for (int i = 0; i < sizeof(num); i++) {
printf("0x%x ", ptr[i]);
}
printf("\n");
return 0;
}
```
这个程序首先检测系统的字节序,然后打印一个32位整数在内存中的存储顺序。
`le16_to_cpu` 和 `le32_to_cpu` 是用于将小端(Little Endian)格式的数据转换为主机字节序(Host Byte Order)的函数。
它们主要用于处理不同字节序平台之间的数据交换,常见于内核开发和驱动开发中,特别是在处理硬件数据或网络数据时。
以下是这两个函数的使用场景及示例:
### 使用场景
1. **硬件交互**:在与硬件设备通信时,设备可能使用小端格式来存储数据,而主机可能使用大端格式。这种情况下,需要将设备提供的数据转换为主机能够理解的格式。
2. **网络协议**:某些网络协议明确规定了采用小端格式的数据,这时接收到的数据需要转换为主机格式来处理。
3. **文件格式**:一些文件格式使用小端存储数据,当读取这些文件时,需要进行相应的字节序转换。
### 示例
#### le16_to_cpu
假设从某硬件设备读取了一个16位的小端数据,需要将其转换为主机字节序:
```c
#include <linux/types.h> // for __le16 and __le32
#include <linux/byteorder.h> // for le16_to_cpu, le32_to_cpu
void process_device_data(void) {
__le16 raw_data_from_device;
u16 processed_data;
// 假设从设备读取了原始小端数据
raw_data_from_device = read_data_from_device(); // 伪函数
// 转换为主机字节序
processed_data = le16_to_cpu(raw_data_from_device);
// 使用 processed_data 进行后续操作
do_something_with_data(processed_data); // 伪函数
}
```
#### le32_to_cpu
类似地,假设从某文件读取了一个32位的小端数据,需要将其转换为主机字节序:
```c
#include <linux/types.h> // for __le16 and __le32
#include <linux/byteorder.h> // for le16_to_cpu, le32_to_cpu
void process_file_data(void) {
__le32 raw_data_from_file;
u32 processed_data;
// 假设从文件读取了原始小端数据
raw_data_from_file = read_data_from_file(); // 伪函数
// 转换为主机字节序
processed_data = le32_to_cpu(raw_data_from_file);
// 使用 processed_data 进行后续操作
do_something_with_data(processed_data); // 伪函数
}
```
### 注意事项
- 确保包含了适当的头文件,比如 `<linux/types.h>` 和 `<linux/byteorder.h>`,以便使用 `__le16`, `__le32`, `le16_to_cpu`, 和 `le32_to_cpu` 等定义。
- 这些函数在用户空间程序中不可用,它们主要用于内核空间。
如果在用户空间处理同样的问题,可以使用标准库函数,如 `ntohs`(网络字节序到主机字节序,Short)和 `ntohl`(网络字节序到主机字节序,Long)。
通过了解 `le16_to_cpu` 和 `le32_to_cpu` 的这些使用场景和示例,能够帮助你在需要处理字节序转换的场景中正确应用它们。
`sysfs_create_group` 是 Linux 内核中用于在 sysfs 文件系统中创建一组属性(attributes)的函数。sysfs 是一种内核子系统的文件系统,通常挂载在 `/sys`,用于提供内核对象(如设备、驱动程序等)的信息和接口。
### 使用场景
`sysfs_create_group` 常用于以下场景:
1. **驱动程序开发**:在编写设备驱动时,可以通过它向 sysfs 暴露设备属性,允许用户空间应用程序通过文件系统接口与设备进行交互。
2. **调试和监控**:内核模块可以向 sysfs 提供调试信息和参数,方便开发者和系统管理员监控和调整系统行为。
3. **系统配置**:某些内核模块可能需要在运行时提供配置接口,sysfs 是一个常见的选择。
### 示例
以下是一个使用 `sysfs_create_group` 的简单示例。假设我们要编写一个简单的内核模块,它在 sysfs 中创建一个属性组,包含两个属性:`foo` 和 `bar`。
首先,定义属性读写函数及属性:
```c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/init.h>
static struct kobject *example_kobj;
static int foo;
static int bar;
static ssize_t foo_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", foo);
}
static ssize_t foo_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
{
sscanf(buf, "%du", &foo);
return count;
}
static ssize_t bar_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", bar);
}
static ssize_t bar_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
{
sscanf(buf, "%du", &bar);
return count;
}
static struct kobj_attribute foo_attribute =
__ATTR(foo, 0664, foo_show, foo_store);
static struct kobj_attribute bar_attribute =
__ATTR(bar, 0664, bar_show, bar_store);
static struct attribute *attrs[] = {
&foo_attribute.attr,
&bar_attribute.attr,
NULL, // 必须以NULL结束
};
static struct attribute_group attr_group = {
.attrs = attrs,
};
```
接下来,在模块加载和卸载函数中创建和删除属性组:
```c
static int __init example_init(void)
{
int retval;
// 创建 kobject
example_kobj = kobject_create_and_add("example", kernel_kobj);
if (!example_kobj)
return -ENOMEM;
// 创建属性组
retval = sysfs_create_group(example_kobj, &attr_group);
if (retval)
kobject_put(example_kobj); // 创建失败时,释放 kobject
return retval;
}
static void __exit example_exit(void)
{
// 删除属性组及 kobject
kobject_put(example_kobj);
}
module_init(example_init);
module_exit(example_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Sysfs create group example");
MODULE_AUTHOR("Your Name");
```
### 编译和测试
1. 将上述代码保存为 `example.c`。
2. 编译内核模块:
```sh
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
```
3. 加载内核模块:
```sh
sudo insmod example.ko
```
4. 查看 sysfs 中的属性:
```sh
cat /sys/kernel/example/foo
echo 42 > /sys/kernel/example/foo
cat /sys/kernel/example/foo
```
5. 卸载内核模块:
```sh
sudo rmmod example
```
通过上述步骤,你可以在 sysfs 文件系统中创建并管理属性组,允许用户空间与内核模块之间进行交互。
`DEVICE_ATTR` 和 `__ATTR` 都是用于定义 sysfs 属性的宏,但它们有不同的使用场景和定义方式。
理解这两个宏的区别有助于正确选择和使用它们为内核对象(如设备、驱动程序等)创建属性。
### `DEVICE_ATTR`
`DEVICE_ATTR` 是专门用于定义设备相关属性的宏。它常用于设备驱动程序中,为特定设备创建 sysfs 属性。
**定义方式**:
```c
#define DEVICE_ATTR(_name, _mode, _show, _store) \
struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
```
`DEVICE_ATTR` 定义了一个 `device_attribute` 结构,该结构包含属性的名称、权限、show 和 store 方法。
**使用场景**:
适用于需要为设备对象创建 sysfs 属性的情况。
**示例**:
```c
#include <linux/device.h>
#include <linux/module.h>
#include <linux/init.h>
static ssize_t foo_show(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf, "foo\n");
}
static ssize_t foo_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
// 写入操作
return count;
}
static DEVICE_ATTR(foo, 0664, foo_show, foo_store);
static int __init example_init(void)
{
struct device *dev = /* 获取设备对象 */;
int retval;
retval = device_create_file(dev, &dev_attr_foo);
if (retval)
pr_err("Failed to create foo attribute\n");
return retval;
}
static void __exit example_exit(void)
{
struct device *dev = /* 获取设备对象 */;
device_remove_file(dev, &dev_attr_foo);
}
module_init(example_init);
module_exit(example_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("DEVICE_ATTR example");
MODULE_AUTHOR("Your Name");
```
### `__ATTR`
`__ATTR` 是一个通用宏,用于定义任意内核对象(如 kobject)的属性。
它不局限于设备,因此具有更广泛的应用范围。
**定义方式**:
```c
#define __ATTR(_name, _mode, _show, _store) { \
.attr = {.name = __stringify(_name), .mode = _mode }, \
.show = _show, \
.store = _store, \
}
```
`__ATTR` 定义了一个 `attribute` 结构,该结构包含属性的名称、权限、show 和 store 方法。
**使用场景**:
适用于需要为任意 kobject 创建 sysfs 属性的情况。
**示例**:
```c
#include <linux/kobject.h>
#include <linux/module.h>
#include <linux/init.h>
static struct kobject *example_kobj;
static ssize_t foo_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "foo\n");
}
static ssize_t foo_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
{
// 写入操作
return count;
}
static struct kobj_attribute foo_attribute =
__ATTR(foo, 0664, foo_show, foo_store);
static int __init example_init(void)
{
int retval;
example_kobj = kobject_create_and_add("example", kernel_kobj);
if (!example_kobj)
return -ENOMEM;
retval = sysfs_create_file(example_kobj, &foo_attribute.attr);
if (retval)
kobject_put(example_kobj);
return retval;
}
static void __exit example_exit(void)
{
kobject_put(example_kobj);
}
module_init(example_init);
module_exit(example_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("__ATTR example");
MODULE_AUTHOR("Your Name");
```
### 总结
- **`DEVICE_ATTR`**:用于为设备创建 sysfs 属性。
- 示例中的 `device_create_file` 和 `device_remove_file` 是具体的设备操作函数。
- **`__ATTR`**:用于为任意 kobject 创建 sysfs 属性。
- 示例中的 `sysfs_create_file` 和 `sysfs_remove_file` 是通用的 kobject 操作函数。
根据不同的应用场景选择相应的宏,可以方便地为内核对象添加所需的 sysfs 属性。
`/proc/interrupts` 文件是 Linux 系统中的一个虚拟文件,它包含了系统中各种中断(interrupts)的信息,包括硬件中断和软件中断。该文件对于了解系统中断行为,调试硬件问题,性能调优等非常有用。下面是对 `cat /proc/interrupts` 输出的详细解析。
### 示例输出
下面是一个典型的 `/proc/interrupts` 文件内容示例:
```plaintext
CPU0 CPU1 CPU2 CPU3
0: 11 2 3 4 IO-APIC-edge timer
1: 1 0 0 0 IO-APIC-edge i8042
8: 0 1 0 0 IO-APIC-edge rtc0
9: 0 0 0 0 IO-APIC-fasteoi acpi
12: 5 0 0 0 IO-APIC-edge i8042
16: 1234 5678 9101 1121 IO-APIC-fasteoi eth0
23: 10 0 0 0 IO-APIC-fasteoi ehci_hcd:usb1, ehci_hcd:usb2
40: 6789 4321 5678 8765 PCI-MSI xhci_hcd
NMI: 12 14 15 16 Non-maskable interrupts
LOC: 345678 123456 234567 345678 Local timer interrupts
SPU: 0 0 0 0 Spurious interrupts
PMI: 10 11 12 13 Performance monitoring interrupts
IWI: 0 0 0 0 IRQ work interrupts
RTR: 1 2 1 2 APIC ICR read retries
RES: 10 11 12 13 Rescheduling interrupts
CAL: 20 21 22 23 Function call interrupts
TLB: 30 31 32 33 TLB shootdowns
TRM: 0 0 0 0 Thermal event interrupts
THR: 0 0 0 0 Threshold APIC interrupts
DFR: 0 0 0 0 Deferred Error APIC interrupts
MCE: 0 0 0 0 Machine check exceptions
MCP: 0 0 0 0 Machine check polls
ERR: 0
MIS: 0
```
### 字段解释
1. **中断号码**(如 `0`, `1`, `8` 等):中断请求号(IRQ),用于标识不同的中断源。
2. **CPU中断计数**:每个 CPU 对应的中断计数,表示在每个 CPU 上接收到的中断次数。在示例中,`CPU0`, `CPU1`, `CPU2`, `CPU3` 对应的列显示了每个 CPU 收到的中断次数。
3. **中断类型**:描述中断的类型(如 `IO-APIC-edge`, `IO-APIC-fasteoi` 等)。
4. **中断源**:描述触发中断的设备或中断控制器名称(如 `timer`, `i8042`, `eth0` 等)。
### 详细解释
- **`CPU0, CPU1, CPU2, CPU3`**:这些列显示了每个 CPU 上处理的中断数量。每一列对应一个 CPU 核心。
- **`IO-APIC-edge`** 和 **`IO-APIC-fasteoi`**:这两个是中断控制器类型。`edge` 是一种触发模式,表示电平变化触发中断;`fasteoi` 是用于高级编程可编址中断控制器(APIC)的触发模式,通常用于复杂的中断处理。
- **中断源说明**:
- `timer`:系统定时器中断。
- `i8042`:键盘控制器中断。
- `rtc0`:实时时钟中断。
- `acpi`:ACPI(高级配置与电源接口)中断。
- `eth0`:网络接口 eth0 中断。
- `ehci_hcd:usb1, ehci_hcd:usb2`:USB 控制器中断。
- `xhci_hcd`:扩展主机控制器接口(USB 3.0 控制器)中断。
- **特殊中断**:
- `NMI`:不可屏蔽中断,用于紧急情况,如硬件故障。
- `LOC`:本地定时器中断。
- `SPU`:伪中断,表示意外中断或错误中断。
- `PMI`:性能监控中断。
- `IWI`:中断工作(IRQ work)中断。
- `RTR`:APIC ICR 读取重试中断。
- `RES`:重新调度中断,用于进程调度。
- `CAL`:函数调用中断。
- `TLB`:TLB(Translation Lookaside Buffer)失效中断。
- `TRM`:热事件中断。
- `THR`:阈值 APIC 中断。
- `DFR`:延迟错误 APIC 中断。
- `MCE`:机器检查异常中断。
- `MCP`:机器检查轮询中断。
- **`ERR`** 和 **`MIS`**:
- `ERR`:表示中断处理过程中发生的错误计数。
- `MIS`:表示丢失的中断计数。
通过 `cat /proc/interrupts` 可以获取系统中断的详细信息,有助于分析系统性能,调试硬件问题和优化系统行为。
解析 `cat /proc/interrupts` 输出中的特定行,可以帮助理解系统中断的实际使用情况。以下是你提供的输出的详细解释:
```plaintext
112: 0 0 0 0 321 0 0 0 0 0 0 0 IO-APIC 15-fasteoi ACP3x_I2S_IRQ
114: 0 446758 0 0 0 0 0 0 0 0 0 0 PCI-MSI 4194304-edge amdgpu
116: 0 0 2166 0 0 0 0 0 0 0 0 0 PCI-MSI 4198400-edge psp-1
118: 0 0 0 0 0 0 4963 0 0 0 0 0 amd_gpio 40 cpvt_dm_irq
119: 0 0 0 0 0 0 0 0 0 0 0 0 amd_gpio 265 gpiolib
120: 0 0 0 0 0 0 658 0 0 0 0 0 amd_gpio 3 goodix_ts
121: 0 0 0 0 0 0 0 0 0 0 0 0 amd_gpio 103 goodix_ts_psd
127: 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 4722688-edge enp9s0f2
128: 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 4722689-edge enp9s0f2-ecc
129: 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 4722690-edge enp9s0f2-i2c
```
### 字段解释
1. **中断号码**(如 `112`, `114`, `116` 等):表示特定中断请求号(IRQ)。
2. **CPU中断计数**:显示每个 CPU 核心上处理该中断的次数。列数目根据系统中的 CPU 数量而变化。
3. **中断类型**:描述中断的类型(如 `IO-APIC`, `PCI-MSI` 等)。
4. **中断控制器和触发类型**:如 `15-fasteoi` 和 `4194304-edge` 等,指示中断控制器和触发方式。
5. **中断源**:描述触发中断的设备或驱动程序名称(如 `ACP3x_I2S_IRQ`, `amdgpu` 等)。
### 每行的详细解析
**112**:
```plaintext
112: 0 0 0 0 321 0 0 0 0 0 0 0 IO-APIC 15-fasteoi ACP3x_I2S_IRQ
```
- **中断号码**:112
- **CPU中断计数**:只有第五个 CPU 接收到 321 次中断,其他 CPU 没有接收到该中断。
- **中断类型**:`IO-APIC`
- **触发方式**:`15-fasteoi`
- **中断源**:`ACP3x_I2S_IRQ`,可能是与 I2S 音频子系统相关的中断。
**114**:
```plaintext
114: 0 446758 0 0 0 0 0 0 0 0 0 0 PCI-MSI 4194304-edge amdgpu
```
- **中断号码**:114
- **CPU中断计数**:第二个 CPU 接收到 446758 次中断,其他 CPU 没有接收到该中断。
- **中断类型**:`PCI-MSI`
- **触发方式**:`4194304-edge`
- **中断源**:`amdgpu`,与 AMD GPU 驱动程序相关的中断。
**116**:
```plaintext
116: 0 0 2166 0 0 0 0 0 0 0 0 0 PCI-MSI 4198400-edge psp-1
```
- **中断号码**:116
- **CPU中断计数**:第三个 CPU 接收到 2166 次中断,其他 CPU 没有接收到该中断。
- **中断类型**:`PCI-MSI`
- **触发方式**:`4198400-edge`
- **中断源**:`psp-1`,可能与平台安全处理器(PSP)相关。
**118**:
```plaintext
118: 0 0 0 0 0 0 4963 0 0 0 0 0 amd_gpio 40 cpvt_dm_irq
```
- **中断号码**:118
- **CPU中断计数**:第七个 CPU 接收到 4963 次中断,其他 CPU 没有接收到该中断。
- **中断类型**:`amd_gpio`
- **触发方式**:`40`
- **中断源**:`cpvt_dm_irq`,可能与 GPIO 相关的中断。
**119**:
```plaintext
119: 0 0 0 0 0 0 0 0 0 0 0 0 amd_gpio 265 gpiolib
```
- **中断号码**:119
- **CPU中断计数**:所有 CPU 没有接收到该中断。
- **中断类型**:`amd_gpio`
- **触发方式**:`265`
- **中断源**:`gpiolib`,可能与 GPIO 库相关的中断。
**120**:
```plaintext
120: 0 0 0 0 0 0 658 0 0 0 0 0 amd_gpio 3 goodix_ts
```
- **中断号码**:120
- **CPU中断计数**:第七个 CPU 接收到 658 次中断,其他 CPU 没有接收到该中断。
- **中断类型**:`amd_gpio`
- **触发方式**:`3`
- **中断源**:`goodix_ts`,可能是 Goodix 触摸屏的中断。
**121**:
```plaintext
121: 0 0 0 0 0 0 0 0 0 0 0 0 amd_gpio 103 goodix_ts_psd
```
- **中断号码**:121
- **CPU中断计数**:所有 CPU 没有接收到该中断。
- **中断类型**:`amd_gpio`
- **触发方式**:`103`
- **中断源**:`goodix_ts_psd`,可能是 Goodix 触摸屏的另一个中断。
**127**:
```plaintext
127: 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 4722688-edge enp9s0f2
```
- **中断号码**:127
- **CPU中断计数**:所有 CPU 没有接收到该中断。
- **中断类型**:`PCI-MSI`
- **触发方式**:`4722688-edge`
- **中断源**:`enp9s0f2`,与网络接口 enp9s0f2 相关的中断。
**128**:
```plaintext
128: 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 4722689-edge enp9s0f2-ecc
```
- **中断号码**:128
- **CPU中断计数**:所有 CPU 没有接收到该中断。
- **中断类型**:`PCI-MSI`
- **触发方式**:`4722689-edge`
- **中断源**:`enp9s0f2-ecc`,与网络接口 enp9s0f2 的 ECC 相关的中断。
**129**:
```plaintext
129: 0 0 0 0 0 0 0 0 0 0 0 0 PCI-MSI 4722690-edge enp9s0f2-i2c
```
- **中断号码**:129
- **CPU中断计数**:所有 CPU 没有接收到该中断。
- **中断类型**:`PCI-MSI`
- **触发方式**:`4722690-edge`
- **中断源**:`enp9s0f2-i2c`,与网络接口 enp9s0f2 的 I2C 相关的中断。
### 总结
通过解析这些输出,可以看出系统中各种类型的中断,包括硬件中断(如 PCIe 设备的中断)、GPIO 中断和其他设备相关的中断(如触摸屏、网络接口等)。这对于调试硬件问题、优化系统性能等方面非常有帮助。
`devm_request_threaded_irq` 是 Linux 内核中的一个函数,用于请求中断,并将中断处理程序注册为一个线程化的中断处理程序。它是一种管理资源的机制,确保在设备驱动程序卸载时自动释放这些资源。该函数包含在 `linux/interrupt.h` 头文件中。
以下是 `devm_request_threaded_irq` 函数的原型以及各个参数的含义:
```c
int devm_request_threaded_irq(struct device *dev, unsigned int irq,
irq_handler_t handler, irq_handler_t thread_fn,
unsigned long irqflags, const char *devname,
void *dev_id);
```
### 参数解释
1. **`struct device *dev`**:
- 指向请求中断的设备结构体的指针。用于关联中断和设备,以方便资源管理。
2. **`unsigned int irq`**:
- 要请求的中断号。表示哪个硬件中断请求(IRQ)将由中断处理程序处理。
3. **`irq_handler_t handler`**:
- 顶半部(ISR,Interrupt Service Routine)处理程序的指针。当中断发生时,该处理程序将被立即调用。这个处理程序应该尽量简短,因为它运行在中断上下文中。
4. **`irq_handler_t thread_fn`**:
- 线程化的中断处理程序(底半部)。这部分处理程序将在一个独立的线程中执行,允许较长时间的处理。可以为 `NULL`,表示没有线程化处理程序。
5. **`unsigned long irqflags`**:
- 中断请求标志,指示如何处理中断。常见的标志包括:
- `IRQF_SHARED`: 允许中断向量共享。
- `IRQF_TRIGGER_RISING`, `IRQF_TRIGGER_FALLING`, `IRQF_TRIGGER_HIGH`, `IRQF_TRIGGER_LOW`: 指定触发方式(上升沿、下降沿、高电平、低电平)。
6. **`const char *devname`**:
- 中断处理程序的名字。用于调试和诊断。这通常是设备的名字。
7. **`void *dev_id`**:
- 设备标识。通常是设备结构体的指针或其他用于识别设备的数据。这个参数在释放中断时用于标识这个特定的中断请求。
### 返回值
`devm_request_threaded_irq` 返回0表示成功,否则返回负值表示错误码。
### 示例代码
下面是一个使用 `devm_request_threaded_irq` 的示例:
```c
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/platform_device.h>
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
// 顶半部中断处理程序 (ISR)
printk(KERN_INFO "IRQ %d: Interrupt occurred.\n", irq);
return IRQ_WAKE_THREAD; // 最好返回这个来唤醒线程化的中断处理程序
}
static irqreturn_t my_irq_thread_fn(int irq, void *dev_id)
{
// 线程化的中断处理程序 (Bottom Half)
printk(KERN_INFO "IRQ %d: Threaded interrupt handler.\n", irq);
return IRQ_HANDLED;
}
static int my_probe(struct platform_device *pdev)
{
int irq;
int ret;
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "Failed to get IRQ\n");
return irq;
}
ret = devm_request_threaded_irq(&pdev->dev, irq, my_irq_handler,
my_irq_thread_fn, IRQF_SHARED,
"my_device", pdev);
if (ret) {
dev_err(&pdev->dev, "Failed to request IRQ\n");
return ret;
}
return 0;
}
static int my_remove(struct platform_device *pdev)
{
// 资源会自动释放,无需手动释放中断
return 0;
}
static struct platform_driver my_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "my_driver",
},
};
module_platform_driver(my_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple example of devm_request_threaded_irq usage");
```
在这个示例中,`my_irq_handler` 是顶半部(ISR),`my_irq_thread_fn` 是线程化的中断处理程序(底半部)。函数 `devm_request_threaded_irq` 将中断处理程序关联到设备,并在设备驱动程序卸载时自动释放中断资源。
在 Linux 系统中使用 `devm_request_threaded_irq` 成功注册 IRQ 后,如果在硬件中断触发后未调用 `irq_handler` 函数,可能有多种原因导致这种情况。以下是一些常见的原因及其排查方法:
### 1. 硬件未正确触发中断
硬件问题是导致中断处理程序未被调用的一个常见原因。检查硬件部分是否正确触发中断。
- 确保硬件确实在触发中断。
- 使用示波器或其他硬件调试工具验证中断信号。
### 2. 中断未被正确配置
确保中断被正确配置和使能。
- 检查中断控制器的配置是否正确。
- 确保中断未被屏蔽或禁用。
### 3. 中断共享冲突
共享中断时,其他设备的中断处理程序可能会屏蔽你的中断请求。
- 检查是否有其他设备共享相同的中断号。
- 确保所有共享中断的处理程序都能正确处理中断。
### 4. 中断类型和触发方式不匹配
中断类型(如边沿触发、电平触发)和触发方式可能与硬件配置不匹配。
- 确保使用的中断类型与硬件配置匹配。
- 检查设备树或平台数据中的中断配置。
### 5. 中断处理程序未正确注册
尽管 `devm_request_threaded_irq` 返回成功,仍可能存在注册问题。
- 检查 `irq_handler` 是否正确声明和实现。
- 确保中断处理程序的符号匹配,避免编译器优化导致的问题。
### 6. 中断被屏蔽
中断控制器或内核可能屏蔽了中断。
- 检查中断控制器的寄存器配置。
- 使用 `cat /proc/interrupts` 确认中断计数是否增加。
### 示例代码检查
以下是 `devm_request_threaded_irq` 使用的示例,确保代码配置正确:
```c
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/platform_device.h>
static irqreturn_t my_irq_handler(int irq, void *dev_id) {
printk(KERN_INFO "IRQ Handler Called\n");
return IRQ_HANDLED;
}
static irqreturn_t my_thread_fn(int irq, void *dev_id) {
printk(KERN_INFO "IRQ Thread Function Called\n");
return IRQ_HANDLED;
}
static int my_probe(struct platform_device *pdev) {
int irq;
int ret;
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "Failed to get IRQ\n");
return irq;
}
ret = devm_request_threaded_irq(&pdev->dev, irq, my_irq_handler, my_thread_fn, IRQF_ONESHOT, "my_device", NULL);
if (ret) {
dev_err(&pdev->dev, "Failed to request IRQ\n");
return ret;
}
return 0;
}
static int my_remove(struct platform_device *pdev) {
return 0;
}
static const struct of_device_id my_of_match[] = {
{ .compatible = "myvendor,mydevice", },
{ }
};
MODULE_DEVICE_TABLE(of, my_of_match);
static struct platform_driver my_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "my_driver",
.of_match_table = my_of_match,
},
};
module_platform_driver(my_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Author");
MODULE_DESCRIPTION("IRQ Handling Example");
```
### 排查步骤
1. **检查硬件**
- 确认硬件确实生成了中断信号。
- 检查硬件手册,确保硬件配置正确。
2. **检查中断配置**
- 使用 `cat /proc/interrupts` 确认中断号是否注册。
- 确认中断号的计数是否增加。
3. **检查驱动程序**
- 确认中断处理程序和线程函数的符号和类型声明正确。
- 确认 `platform_get_irq` 返回的中断号正确。
- 确认 `devm_request_threaded_irq` 的标志和参数配置正确。
4. **调试输出**
- 在 `irq_handler` 和 `my_thread_fn` 中添加 `printk` 输出,确认是否被调用。
- 确认内核日志中是否有相关输出,使用 `dmesg` 命令查看内核日志。
5. **检查中断标志**
- 使用合适的中断标志,如 `IRQF_SHARED` 或 `IRQF_TRIGGER_*` (边沿、电平触发)。
- 根据具体硬件和中断控制器的设置调整中断标志。
通过以上步骤,逐步排查问题并验证中断配置,应该能确定 `irq_handler` 函数未被调用的具体原因并进行修正。
杂项合集,参考
最新推荐文章于 2024-08-15 15:00:02 发布