I2C 驱动 --- 控制器
示例
- 使用说明
# use i2c-bcm2711
BCM2711 I2C manageri2c-bcm2711 [options]Options:
-p addr I2C base address(Default: 0)
-i irq I2C interrupt (Default: 149)
-c clock Default to 5000(100KHz)
-v Set verbose(Default: 2)
-s slave addr Slave address(only 7-bit addr supported. Default: 0)Generic options:
--b bus_speed Default bus speed. (Default: 100000)
--u unit Unit number. Number to append to device nameprefix (/dev/i2c). (Default: 0)Example:
i2c-bcm2711 -p0xfe804000 --b100000 --u1
-
示例1
# Specify a port of address 0xFE804000 with an IRQ of 149 to the first unit i2c-bcm2711 -p0xfe804000 --b100000
日志查看
# 查看设备
$ ls /dev/i2c*
/dev/i2c0 /dev/i2c1# 查看日志
$ slog2info -r -b i2c_bcm2711
定位难点:运行时没有任何打印信息。
源码跟踪
main 入口
$ grep -rn main src/hardware/i2c/
grep: src/hardware/i2c/bcm2711/aarch64/le/i2c-bcm2711: binary file matches
反汇编
尝试通过 反汇编 方式将 i2c-bcm2711 运行流程进制一个大致梳理。
先从当前环境正常编译过程中的最后链接命令入手:
/home/gaoyang3513/Workspaces/qnx800/host/linux/x86_64/usr/bin/qcc -Vgcc_ntoaarch64 -Wl,--no-keep-memory \-o /home/gaoyang3513/Source/05-Raspi/02-Raspi/02-Projects/SDK_QNX_Raspi4B_BSP/src/hardware/i2c/bcm2711/aarch64/le/i2c-bcm2711 \bus_speed.o fini.o info.o init.o lib.o options.o recv.o send.o slave_addr.o version.o wait.o \-L. -L/home/gaoyang3513/Source/05-Raspi/02-Raspi/02-Projects/SDK_QNX_Raspi4B_BSP/src/hardware/i2c/../../../install/aarch64le/lib \-L/home/gaoyang3513/Source/05-Raspi/02-Raspi/02-Projects/SDK_QNX_Raspi4B_BSP/src/hardware/i2c/../../../install/aarch64le/usr/lib \-L/home/gaoyang3513/Workspaces/qnx800/target/qnx/aarch64le/lib -L/home/gaoyang3513/Workspaces/qnx800/target/qnx/aarch64le/usr/lib \-Wl,--rpath-link,. -Wl,--rpath-link,/home/gaoyang3513/Source/05-Raspi/02-Raspi/02-Projects/SDK_QNX_Raspi4B_BSP/src/hardware/i2c/../../../install/aarch64le/lib \-Wl,--rpath-link,/home/gaoyang3513/Source/05-Raspi/02-Raspi/02-Projects/SDK_QNX_Raspi4B_BSP/src/hardware/i2c/../../../install/aarch64le/usr/lib \-Wl,--rpath-link,/home/gaoyang3513/Workspaces/qnx800/target/qnx/aarch64le/lib \-Wl,--rpath-link,/home/gaoyang3513/Workspaces/qnx800/target/qnx/aarch64le/usr/lib \-li2c-master -ldrvr -lsecpol -EL
$ find ~/Workspaces/qnx800 -name "*i2c-master*"
/home/gaoyang3513/Workspaces/qnx800/target/qnx/x86_64/lib/libi2c-master.a
/home/gaoyang3513/Workspaces/qnx800/target/qnx/aarch64le/lib/libi2c-master.a
三个库文件 libi2c-master.a、libdrvr.a、 libsecpol.so 都以预编译文件形式存放于 qnx800/target/qnx/<aarch84le> 目录下。另外命令中没有显式的链接脚本(后缀 .lds),所以使用隐性的连接方式。对 i2c-bcm2711 进制反汇编,发现因未启用调试信息导致生成dump文件没有C源码做对比参考,方便阅读,在编译命令中加入 -g 重新生成带调试信息(C源码)的 i2c-bcm2711,命令如下:
-
编译命令
# 清理 make -C src/hardware/i2c/bcm2711/ clean # 重新生成,添加调试信息(-g) make -C src/hardware/i2c/bcm2711/ CCFLAGS=-g -
反汇编命令
$ aarch64-unknown-nto-qnx8.0.0-objdump -xdS src/hardware/i2c/bcm2711/aarch64/le/i2c-bcm2711 > i2c-bcm2711.dump- -x, --all-headers Display the contents of all headers,显示所有头部信息;
- -d, --disassemble Display assembler contents of executable sections,显示执行区的汇编代码;
- -S, --source Intermix source code with disassembly,混合C与反汇编代码;
以下摘取反汇编文件 i2c-bcm2711.dump 关键信息,梳理启动流程:
#--> i2c-bcm2711.dump
... # 6
start address 0x00000000000018a0 # 入口地址 18a0
... # 1419
Disassembly of section .text:00000000000014f0 <main>: # main ... # 14431544: 940003df bl 24c0 <_i2c_options> # 调用 options 检查入参... # 1449155c: 94000599 bl 2bc0 <_i2c_initlib> # 初始化库文件... # 14961618: 97ffff86 bl 1430 <dispatch_create@plt> # 分发器创建... # 1513165c: 97ffff85 bl 1470 <iofunc_func_init@plt> # iofunc 初始化... # 155316fc: 97fffed1 bl 1240 <resmgr_context_alloc@plt> # 资源管理器 上下文申请... # 161017e0: d0000023 adrp x3, 7000 <__FRAME_END__+0xd74>17e4: f947e863 ldr x3, [x3, #4048]17e8: d2800021 mov x1, #0x1 17ec: f0000000 adrp x0, 4000 <_i2c_master_sendrecv+0x2d0> 17f0: d2800342 mov x2, #0x1a 17f4: 91312000 add x0, x0, #0xc4817f8: 97ffff16 bl 1450 <fwrite@plt> ... # 1659
00000000000018a0 <_start>: # C入口, (0... # 8911920: 97fffeb8 bl 1400 <_init_libc@plt>... # 8961934: 97fffe4f bl 1270 <_preinit_array@plt> ... # 9011948: 97fffebe bl 1440 <_fini_array@plt>... # 906195c: 97fffe35 bl 1230 <_init_array@plt>1960: 97fffe94 bl 13b0 <__get_errno_ptr@plt> 1964: b900001f str wzr, [x0]1968: 2a1303e0 mov w0, w19196c: aa1403e1 mov x1, x201970: aa1503e2 mov x2, x211974: aa1f03fd mov x29, xzr1978: 97fffef2 bl 1540 <main> # main 入口 (1 197c: 97fffed1 bl 14c0 <exit@plt>1980: 14000000 b 1980 <_start+0x90>... # 1378
0000000000001df0 <i2c_master_getfuncs>:... # 1404I2C_ADD_FUNC(i2c_master_funcs_t, funcs, init, bcm2711_init, tabsize); # .init 函数指针,指向 bcm2711_init1e14: d0000022 adrp x2, 7000 <__FRAME_END__+0xa5c>1e18: f947e442 ldr x2, [x2, #4040]1e1c: f9000802 str x2, [x0, #16]... # 1414I2C_ADD_FUNC(i2c_master_funcs_t, funcs, send, bcm2711_send, tabsize); # .sedn 函数指针,赋值 bcm2711_send1e34: 71009c3f cmp w1, #0x271e38: 54fffe49 b.ls 1e00 <i2c_master_getfuncs+0x10> // b.plast1e3c: d0000022 adrp x2, 7000 <__FRAME_END__+0xa5c>1e40: f947c042 ldr x2, [x2, #3968]1e44: f9001002 str x2, [x0, #32]I2C_ADD_FUNC(i2c_master_funcs_t, funcs, recv, bcm2711_recv, tabsize);1e48: 7100bc3f cmp w1, #0x2f1e4c: 54fffda9 b.ls 1e00 <i2c_master_getfuncs+0x10> // b.plast1e50: d0000022 adrp x2, 7000 <__FRAME_END__+0xa5c>1e54: f947bc42 ldr x2, [x2, #3960]1e58: f9001402 str x2, [x0, #40] ... # 2721
0000000000002ce0 <_i2c_initlib>:... # 27532d5c: f9004e61 str x1, [x19, #152]2d60: a900a80b stp x11, x10, [x0, #8]2d64: a9018809 stp x9, x2, [x0, #24]2d68: a902a002 stp x2, x8, [x0, #40]2d6c: a9039807 stp x7, x6, [x0, #56]2d70: a9049005 stp x5, x4, [x0, #72]2d74: f9002c03 str x3, [x0, #88]2d78: 97fffc1e bl 1df0 <i2c_master_getfuncs> # 调用 i2c_master_getfuncs (2...
使用AI工具,逆向一部分main函数的实现
#include "proto.h"// 全局变量 'done',用于控制主循环退出
int done = 0; // 位于 .bss 段,偏移 0x800c// 信号处理函数,用于设置 'done' 标志以退出程序
void exit_signal(int signo) {atomic_set(&done, 1); // 原子地设置 done 为 1
}int main(int argc, char *argv[]) {// 局部变量声明 (根据栈帧布局和使用推断)bcm2711_dev_t local_dev; // 用于传递给 bcm2711_options 和 bcm2711_init 的设备句柄i2c_master_funcs_t master_funcs; // I2C 主机功能函数表resmgr_attr_t resmgr_attr; // 资源管理器属性char path[256]; // 资源管理器路径字符串 "/dev/i2c-bcm2711"struct sigevent event; // 信号事件结构体sigset_t mask; // 信号集struct sigaction act, oact; // 信号动作结构体dispatch_t *dpp = NULL; // 调度器句柄resmgr_context_t *ctp = NULL; // 资源管理器上下文int resmgr_id = -1; // 资源管理器 IDvoid *hdl_from_init = NULL; // bcm2711_init 返回的设备句柄int ret = 0; // 函数返回值,最终作为 main 的返回值// 初始化 master_funcs 结构体为零memset(&master_funcs, 0, sizeof(master_funcs));// 调用 _i2c_options 处理命令行参数// local_dev 用于存储解析后的选项ret = bcm2711_options(&local_dev, argc, argv);if (ret == -1) {goto cleanup;}// 调用 _i2c_initlib 初始化 I2C 库函数表// master_funcs 结构体将被填充 I2C 驱动的实际函数指针ret = _i2c_initlib(&master_funcs);if (ret == -1) {goto cleanup;}// 调用 I2C 驱动的初始化函数// bcm2711_init 返回一个新分配的 bcm2711_dev_t 句柄hdl_from_init = master_funcs.init(&local_dev, argc, argv); // 传入 local_dev 作为 hdl_unusedif (hdl_from_init == NULL) {goto cleanup;}// 设置从设备地址 (根据命令行选项)// 假设 master_funcs.set_slave_addr 的第二个参数是地址,第三个参数是格式// 这里使用 local_dev.slave_addr 和 I2C_ADDRFMT_7BITif (master_funcs.set_slave_addr(hdl_from_init, local_dev.slave_addr, I2C_ADDRFMT_7BIT) == -1) {fprintf(_Stderr, "main: Error setting slave address: %s\n", strerror(errno));ret = -1;goto cleanup_with_fini;}// 设置总线速度 (根据命令行选项)unsigned int actual_speed = 0;if (master_funcs.set_bus_speed(hdl_from_init, local_dev.speed, &actual_speed) == -1) {fprintf(_Stderr, "main: Error setting bus speed: %s\n", strerror(errno));ret = -1;goto cleanup_with_fini;}// 创建调度器dpp = dispatch_create();if (dpp == NULL) {perror("dispatch_create");ret = -1;goto cleanup_with_fini;}// 初始化 I/O 函数和资源管理器属性iofunc_func_init(dpp, &resmgr_attr.io_funcs, &resmgr_attr.attr, 0x21, 0x9); // 0x21, 0x9 是 QNX 特定的常量iofunc_attr_init_sized(&master_funcs, sizeof(bcm2711_dev_t), &resmgr_attr.attr); // master_funcs 作为 attr 的 base// 格式化资源管理器路径snprintf(path, sizeof(path), "/dev/i2c-bcm2711");// 打开安全策略secpol_open(0, 1); // 0, 1 是 QNX 特定的常量// 附加资源管理器resmgr_id = secpol_resmgr_attach(dpp, NULL, path, 0, 0, &master_funcs, &resmgr_attr.attr, NULL);if (resmgr_id == -1) {perror("resmgr_attach");ret = -1;goto cleanup_with_fini;}// 转换安全策略类型secpol_transition_type(0, 0, 0);// 分配资源管理器上下文ctp = resmgr_context_alloc(dpp);if (ctp == NULL) {perror("resmgr_context_alloc");ret = -1;goto cleanup_with_fini;}// 设置 SIGTERM 信号处理函数sigemptyset(&mask);sigaddset(&mask, SIGTERM);act.sa_handler = exit_signal;act.sa_flags = 0;sigaction(SIGTERM, &act, &oact);// 进程守护化procmgr_daemon(0, 0);// 资源管理器主循环while (!done) {resmgr_block(ctp); // 阻塞等待消息resmgr_handler(ctp); // 处理消息}cleanup_with_fini:// 调用 I2C 驱动的结束函数if (hdl_from_init != NULL) {master_funcs.fini(hdl_from_init);}cleanup:// 恢复寄存器和栈帧,执行栈保护检查,并返回return ret;
}
可知:从main分析看来,进制应该阻塞等待资源管理器回调的,但实际执行 i2c-bcm2711 直接返回,这个表示不解。对资源做一个简单梳理

可知当前驱动主要是 I2C 控制器的驱动,而非某一 I2C 设备的驱动。但从 i2c-bcm2711 的代码分析:其应长驻后台运行,这样才能处理 i2c 设备的收发需求,从日志发现其长驻后台:
...
Welcome to QNX 8.0.0 on RaspberryPi4B !Starting wdtkick ...
Starting I2C driver ... # I2C 控制器驱动加载
...
跟读源码:
$ grep -rn "Starting I2C driver" ./
./images/rpi4.build:46: display_msg "Starting I2C driver ..."
grep: ./images/ifs-rpi4.bin: binary file matches#--> images/rpi4.build
... # 15
[+script] startup-script = {... # 46display_msg "Starting I2C driver ..."i2c-bcm2711 -p0xfe804000 --b100000 --u1
使用 pidin 命令可看到其在后台的状态
# pidin -p i2c-bcm2711 pid tid name prio STATE Blocked 20488 1 sbin/i2c-bcm2711 10r RECEIVE 1
其状态为:阻塞等待信息
设备驱动
向 I2C 控制器进制发送发送数据,才能最终向从物理层面。需要查找设备端可用的API接口,尝试在下篇编写一个 EEPROM 的驱动。
备注
-
BSP 包自带的’i2c-bcm2711.sym’文件进制反汇编,'.sym’就是带符号的可执行文件。
$ aarch64-unknown-nto-qnx8.0.0-objdump -xDS ./binary_files_with_symbols/aarch64le/sbin/i2c-bcm2711.sym > i2c-bcm2711.dump2
参考
-
__FRAME_END__是编译器和链接器自动生成的一个符号,常见于 ELF 格式的可执行文件或目标文件中。它的作用如下:- 标记栈回溯信息的结束位置
__FRAME_END__通常用于标记 .eh_frame 段(异常处理帧信息段)的结束。该段保存了函数调用栈的回溯信息,供异常处理、栈展开(unwind)、调试等使用。 - 调试和异常处理支持
在程序发生异常(如C++异常、断点、崩溃)时,调试器或运行时库会利用 .eh_frame 段中的信息进行栈回溯。FRAME_END 作为该段的终止符号,帮助工具正确解析和遍历帧信息。 - 链接器定位
链接器通过 FRAME_END 确定 .eh_frame 段的边界,便于生成和合并调试信息。
简要总结:
__FRAME_END__主要用于标记异常处理帧信息的结束位置,支持调试、异常处理和栈回溯等功能。对于普通开发者来说,它一般是自动生成和使用的,无需手动干预。 - 标记栈回溯信息的结束位置
