版本 | 日期 | 作者 | 变更表述 |
1.0 | 2024/10/27 | 于忠军 | 文档创建 |
背景:由于苹果有一套MFI IAP2的蓝牙私有协议,这个协议是基于BR/EDR的RFCOMM自定义UUID来实现IAP2协议的通信,中间会牵扯到苹果加密芯片的I2C读取,所以我们借此机会来研究下Linux I2C子系统。
一. I2C协议介绍
I2C 通讯协议(Inter-Integrated Circuit)是由 Phiilps 公司开发的,由于它引脚少,硬件实现简单,可扩展性强,不需要 USART、 CAN 等通讯协议的外部收发设备,现在被广泛地使用在系统内多个集成电路(IC)间的通讯。
1. IIC物理层
I2C 通讯设备之间的常用连接方式见图 :
(1) 它是一个支持多设备的总线。“总线”指多个设备共用的信号线。在一个 I2C 通讯总线中,可连接多个 I2C 通讯设备,支持多个通讯主机及多个通讯从机。
(2) 一个 I2C 总线只使用两条总线线路,一条双向串行数据线(SDA) ,一条串行时钟线(SCL)。数据线即用来表示数据,时钟线用于数据收发同步。
(3) 每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访问。
(4) 总线通过上拉电阻接到电源。当 I2C 设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平
(5) 多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线。
(6) 具有三种传输模式:标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s ,高速模式下可达 1Mbit/s,但目前大多 I2C 设备尚不支持高速模式。
(7) 连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制 。
2. IIC协议层
I2C 的协议定义了通讯的起始和停止信号、数据有效性、响应、仲裁、时钟同步和地址广播等环节。
a. 读写的基本过程
先看看 I2C 通讯过程的基本结构,它的通讯过程见图
这些图表示的是主机和从机通讯时, SDA 线的数据包序列。
其中 S 表示由主机的 I2C 接口产生的传输起始信号(S),这时连接到 I2C 总线上的所有从机都会接收到这个信号。
起始信号产生后,所有从机就开始等待主机紧接下来广播 的从机地址信号(SLAVE_ADDRESS)。 在 I2C 总线上,每个设备的地址都是唯一的, 当主机广播的地址与某个设备地址相同时,这个设备就被选中了,没被选中的设备将会忽略之后的数据信号。根据 I2C 协议,这个从机地址可以是 7 位或 10 位。
在地址位之后,是传输方向的选择位,该位为 0 时,表示后面的数据传输方向是由主机传输至从机,即主机向从机写数据。该位为 1 时,则相反,即主机由从机读数据。
从机接收到匹配的地址后,主机或从机会返回一个应答(ACK)或非应答(NACK)信号,只有接收到应答信号后,主机才能继续发送或接收数据。
若配置的方向传输位为“写数据”方向, 即第一幅图的情况, 广播完地址,接收到应答信号后, 主机开始正式向从机传输数据(DATA),数据包的大小为 8 位,主机每发送完一个字节数据,都要等待从机的应答信号(ACK),重复这个过程,可以向从机传输 N 个数据,这个 N 没有大小限制。当数据传输结束时,主机向从机发送一个停止传输信号(P),表示不再传输数据。
若配置的方向传输位为“读数据”方向, 即第二幅图的情况, 广播完地址,接收到应答信号后, 从机开始向主机返回数据(DATA),数据包大小也为 8 位,从机每发送完一个数据,都会等待主机的应答信号(ACK),重复这个过程,可以返回 N 个数据,这个 N 也没有大小限制。当主机希望停止接收数据时,就向从机返回一个非应答信号(NACK),则从机自动停止数据传输。
除了基本的读写, I2C 通讯更常用的是复合格式,即第三幅图的情况,该传输过程有两次起始信号(S)。一般在第一次传输中,主机通过 SLAVE_ADDRESS 寻找到从设备后,发送一段“数据”,这段数据通常用于表示从设备内部的寄存器或存储器地址(注意区分它与 SLAVE_ADDRESS 的区别);在第二次的传输中,对该地址的内容进行读或写。也就是说,第一次通讯是告诉从机读写地址,第二次则是读写的实际内容。
以上通讯流程中包含的各个信号分解如下
b. 通讯的起始和停止信号
前文中提到的起始(S)和停止(P)信号是两种特殊的状态,当 SCL 线是高电平时 SDA 线从高电平向低电平切换,这个情况表示通讯的起始。
当 SCL 是高电平时 SDA线由低电平向高电平切换,表示通讯的停止。起始和停止信号一般由主机产生。 如下图:
c. 数据有效性
I2C 使用 SDA 信号线来传输数据,使用 SCL 信号线进行数据同步。 SDA数据线在 SCL 的每个时钟周期传输一位数据。传输时, SCL 为高电平的时候 SDA表示的数据有效,即此时的 SDA 为高电平时表示数据“1”,为低电平时表示数据“0”。当 SCL为低电平时, SDA 的数据无效,一般在这个时候 SDA进行电平切换,为下一次表示数据做好准备 ,如下图:
d. 地址及数据方向
I2C 总线上的每个设备都有自己的独立地址,主机发起通讯时,通过 SDA 信号线发送设备地址(SLAVE_ADDRESS)来查找从机。 I2C 协议规定设备地址可以是 7 位或 10 位,实际中 7 位的地址应用比较广泛。紧跟设备地址的一个数据位用来表示数据传输方向,它是
数据方向位(R/W),第 8 位或第 11 位。数据方向位为“1”时表示主机由从机读数据,该位为“0”时表示主机向从机写数据。见图
读数据方向时,主机会释放对 SDA 信号线的控制,由从机控制 SDA 信号线,主机接收信号,写数据方向时, SDA 由主机控制,从机接收信号。
e. 响应
I2C 的数据和地址传输都带响应。响应包括“应答(ACK)”和“非应答(NACK)”两种信号。作为数据接收端时,当设备(无论主从机)接收到 I2C 传输的一个字节数据或地址后,若希望对方继续发送数据,则需要向对方发送“应答(ACK)”信号,发送方会继续发送下一个数据;若接收端希望结束数据传输,则向对方发送“非应答(NACK)”信号,发送方接收到该信号后会产生一个停止信号,结束信号传输。见图
传输时主机产生时钟,在第 9 个时钟时,数据发送端会释放 SDA 的控制权,由数据接收端控制 SDA,若 SDA 为高电平,表示非应答信号(NACK),低电平表示应答信号(ACK)。
二. SMBUS协议介绍
1. 介绍
SMBus: System Management Bus,系统管理总线。
SMBus 最初的目的是为智能电池、充电电池、其他微控制器之间的通信链路而定义的。
SMBus 也被用来连接各种设备,包括电源相关设备,系统传感器, EEPROM 通讯设备等等。
SMBus 为系统和电源管理这样的任务提供了一条控制总线,使用 SMBus 的系统,设备之间发送和接收消息都是通过 SMBus,而不是使用单独的控制线,这样可以节省设备的管脚数。
SMBus 是基于 I2C 协议的, SMBus 要求更严格, SMBus 是 I2C 协议的子集。
2. 与i2c子系统的差别
SMBus 有哪些更严格的要求?跟一般的 I2C 协议有哪些差别?
VDD 的极限值不一样
- I2C 协议:范围很广,甚至讨论了高达 12V 的情况
- SMBus: 1.8V~5V
最小时钟频率、最大的 Clock Stretching
Clock Stretching 含义:某个设备需要更多时间进行内部的处理时,它可以把 SCL 拉低占住 I2C 总线
- I2C 协议:时钟频率最小值无限制, Clock Stretching 时长也没有限制
- SMBus:时钟频率最小值是 10KHz, Clock Stretching 的最大时间值也有限制
地址回应(Address Acknowledge): 一个 I2C 设备接收到它的设备地址后,是否必须发出回应信号?
- I2C 协议:没有强制要求必须发出回应信号
- SMBus:强制要求必须发出回应信号,这样对方才知道该设备的状态:busy, failed,或是被移除了
SMBus 协议明确了数据的传输格式
- I2C 协议:它只定义了怎么传输数据,但是并没有定义数据的格式,这完全由设备来定义
- SMBus:定义了几种数据格式(后面分析)
REPEATED START Condition(重复发出 S 信号)
SMBus Low Power Version: SMBus 也有低功耗的版本
3. 协议层分析
对于 I2C 协议,它只定义了怎么传输数据,但是并没有定义数据的格式,
这完全由设备来定义。对于 SMBus 协议,它定义了几种数据格式。
首先我们先来看下一些名词,如下:
a. SMBus Quick Command
只是用来发送一位数据: R/W#本意是用来表示读或写,但是在 SMBus 里可以用来表示其他含义。比如某些开关设备,可以根据这一位来决定是打开还是关闭。
对应的Linux的Functionality flag: I2C_FUNC_SMBUS_QUICK
b. SMBus Receive Byte
I2C-tools 中的函数: i2c_smbus_read_byte()。读取一个字节, Hostadapter 接收到一个字节后不需要发出回应信号 。
对应的Linux的Functionality flag: I2C_FUNC_SMBUS_READ_BYTE
c. SMBus Send Byte
I2C-tools 中的函数: i2c_smbus_write_byte()。发送一个字节。
对应的Linux的Functionality flag: I2C_FUNC_SMBUS_WRITE_BYTE
d. SMBus Read Byte
I2C-tools 中的函数: i2c_smbus_read_byte_data()。先发出 Command Code(它一般表示芯片内部的寄存器地址),再读取一个字节的数据。上面介绍的SMBus Receive Byte 是不发送 Comand,直接读取数据。
对应的Linux的Functionality flag: I2C_FUNC_SMBUS_READ_BYTE_DATA
e. SMBus Read Word
I2C-tools 中的函数: i2c_smbus_read_word_data()。先发出 Command Code(它一般表示芯片内部的寄存器地址),再读取 2 个字节的数据。
对应的Linux的Functionality flag: I2C_FUNC_SMBUS_READ_WORD_DATA
f. SMBus Write Byte
I2C-tools 中的函数: i2c_smbus_write_byte_data()。先发出 Command Code(它一般表示芯片内部的寄存器地址),再发出 1 个字节的数据。
对应的Linux的Functionality flag: I2C_FUNC_SMBUS_WRITE_BYTE_DATA
g. SMBus Write Word
I2C-tools 中的函数: i2c_smbus_write_word_data()。先发出 Command Code(它一般表示芯片内部的寄存器地址),再发出 1 个字节的数据。
对应的Linux的Functionality flag: I2C_FUNC_SMBUS_WRITE_WORD_DATA
h. SMBus Block Read
I2C-tools 中的函数: i2c_smbus_read_block_data()。先发出 Command Code(它一般表示芯片内部的寄存器地址),再发起度操作:
- 先读到一个字节(Block Count),表示后续要读的字节数
- 然后读取全部数据
对应的Linux的Functionality flag: I2C_FUNC_SMBUS_READ_BLOCK_DATA
i. SMBus Block Write
I2C-tools 中的函数: i2c_smbus_write_block_data()。先发出 Command Code(它一般表示芯片内部的寄存器地址),再发出 1 个字节的 Byte Conut(表示后续要发出的数据字节数),最后发出全部数据。
对应的Linux的Functionality flag: I2C_FUNC_SMBUS_WRITE_BLOCK_DATA
j. I2C Block Read
在一般的 I2C 协议中,也可以连续读出多个字节。它跟 SMBus Block Read 的差别在于设备发出的第 1 个数据不是长度 N,如下图所示:
I2C-tools 中 的 函 数 : i2c_smbus_read_i2c_block_data() 。 先 发 出 Command Code(它一般表示芯片内部的寄存器地址),再发出 1 个字节的 Byte Conut(表示后续要发出的数据字节数),最后发出全部数据。
对应的Linux的Functionality flag: I2C_FUNC_SMBUS_READ_I2C_BLOCK
k. I2C Block Write
在一般的 I2C 协议中,也可以连续发出多个字节。它跟 SMBus Block Write的差别在于发出的第 1 个数据不是长度 N,如下图所示:
I2C-tools 中的函数: i2c_smbus_write_i2c_block_data()。先发出Command Code(它一般表示芯片内部的寄存器地址),再发出 1 个字节的 Byte Conut(表示后续要发出的数据字节数),最后发出全部数据。
对应的Linux的Functionality flag: I2C_FUNC_SMBUS_WRITE_I2C_BLOCK
l. SMBus Block Write - Block Read Process Call
先写一块数据,再读一块数据。
对应的Linux的Functionality flag: I2C_FUNC_SMBUS_BLOCK_PROC_CALL
m. Packet Error Checking (PEC)
PEC 是一种错误校验码,如果使用 PEC,那么在 P 信号之前,数据发送方要发送一个字节的 PEC 码(它是 CRC-8 码)。以 SMBus Send Byte 为例,下图中,一个未使用 PEC,另一个使用 PEC:
三. Linux I2C驱动框架介绍
1. Linux源码目录介绍
目前Linux i2c子系统一共分为3个地方:
- I2C Core
- I2C Header File
- I2C device driver
a. I2C Core
整个I2C Core的源码在 driver/i2c 中,如图所示:
i2c - drivers/i2c - Linux source code (v6.11.5) - Bootlin
b. I2C Header File
headfile在/include/linux下面
linux - include/linux - Linux source code (v6.11.5) - Bootlin
c. 特定的芯片驱动
特定的芯片要去查看特别的路径
2. Linux I2C框架介绍
其中可以分为几个部分:
1)User Space,也就是以上图示绿色背景的地方
2)Kernel Space,也就是以上蓝牙背景的地方
3)Hardware Space,也就是以上黄色背景的地方
其中Hardware我们已经介绍了I2C协议以及SMBUS协议,所以我们就不做介绍了,我们主要说下User space以及kernel space层面,我们先从Kernel space说起来,然后再慢慢引导出来User space怎样编写程序。
a. Kernenl Space
可以看到kernel space分为如下图几个部分
你可以看到其实在APP1、APP2对应的跟APP3在底层driver还是稍微有点不同的,原因在于并不是所有的I2C设备都需要写driver的,所以分为User Driver跟Kernel Default drier,那这部分怎样实现呢?自己写driver这个很好理解,我们不做解释,但是不需要写driver是怎样做到的呢?是因为Linux kernel对于I2C设备有默认的映射为字符设备驱动了,让你直接可以用open/read/write/ioctl在应用层访问,这个文件就是I2C-Core中的
i2c-dev.c - drivers/i2c/i2c-dev.c - Linux source code v6.11.5 - Bootlin,所以你就理解了吧?
从上到下分别为:
- Client/User Driver: Client就是对应的真实的I2C设备描述,可以通过集中方式来创建,分别是dts以及sysfs来创建,这个后面来介绍,User Driver这个就是对应真实的I2C设备对应的驱动。
- Client/Kernel Default Driver: Client就是对应的真实的I2C设备描述,可以通过集中方式来创建,分别是dts以及sysfs来创建,这个后面来介绍,Kernel Default Driver这个上面刚刚介绍了,就是不需要我们自己来写特定的I2C设备的驱动,直接使用i2c-dev.c提供的字符设备驱动来直接访问设备。
- I2C-Core: I2C协议的核心部分,这个我们在后面来慢慢介绍
- Adapter:这个就是真实的I2C总线,比如是挂在I2C0或者I2C1中等等,这个要看主控中有几条I2C bus以及特定的设备挂在哪个I2C总线中。
- Algorithm:这个的设计是这样的,我们确定了挂在哪个I2C设备上,但是你要想不通的主控访问I2C的方法以及寄存器肯定不同,比如NXP的跟ST的或者Rockchip他们的I2C ontroller寄存器肯定不同,Linux为了抽象,所以实现了I2C访问算法,说白了就是实现了特定的主控来跟I2C设备来通信,再说直白点,就是I2C的read/write。
好了,原理都介绍清楚了,我们来一一看下
ⅰ. client的宣称
1. client要素
这个在上面已经介绍,在linux i2c子系统中client就是描述一个真实的i2c ic,比如我们说的苹果加密芯片,想描述一个I2C IC有几个要素呢?肯定有设备地址以及挂在哪个I2C Controller是吧?那么我们来看下Linux kernel源码,这个结构体定义在include/linux/i2c.h 文件中
/*** struct i2c_client - represent an I2C slave device* @flags: see I2C_CLIENT_* for possible flags* @addr: Address used on the I2C bus connected to the parent adapter.* @name: Indicates the type of the device, usually a chip name that's* generic enough to hide second-sourcing and compatible revisions.* @adapter: manages the bus segment hosting this I2C device* @dev: Driver model device node for the slave.* @init_irq: IRQ that was set at initialization* @irq: indicates the IRQ generated by this device (if any)* @detected: member of an i2c_driver.clients list or i2c-core's* userspace_devices list* @slave_cb: Callback when I2C slave mode of an adapter is used. The adapter* calls it to pass on slave events to the slave driver.* @devres_group_id: id of the devres group that will be created for resources* acquired when probing this device.** An i2c_client identifies a single device (i.e. chip) connected to an* i2c bus. The behaviour exposed to Linux is defined by the driver* managing the device.*/
struct i2c_client {unsigned short flags; /* div., see below */
#define I2C_CLIENT_PEC 0x04 /* Use Packet Error Checking */
#define I2C_CLIENT_TEN 0x10 /* we have a ten bit chip address *//* Must equal I2C_M_TEN below */
#define I2C_CLIENT_SLAVE 0x20 /* we are the slave */
#define I2C_CLIENT_HOST_NOTIFY 0x40 /* We want to use I2C host notify */
#define I2C_CLIENT_WAKE 0x80 /* for board_info; true iff can wake */
#define I2C_CLIENT_SCCB 0x9000 /* Use Omnivision SCCB protocol *//* Must match I2C_M_STOP|IGNORE_NAK */unsigned short addr; /* chip address - NOTE: 7bit *//* addresses are stored in the *//* _LOWER_ 7 bits */char name[I2C_NAME_SIZE];struct i2c_adapter *adapter; /* the adapter we sit on */struct device dev; /* the device structure */int init_irq; /* irq set at initialization */int irq; /* irq issued by device */struct list_head detected;
#if IS_ENABLED(CONFIG_I2C_SLAVE)i2c_slave_cb_t slave_cb; /* callback for slave mode */
#endifvoid *devres_group_id; /* ID of probe devres group */
};
可以看到我们提出来的两个疑问,在上面的结构体中都能找到答案。
其中表示设备地址的就是:
unsigned short addr;
其中表示具体挂在哪个I2C Controller就是
struct i2c_adapter *adapter; /* the adapter we sit on */
OK,以上问题我们解决后,那么我们接下来会遇到另外,一个问题,怎样宣称某一个I2C IC是一个client呢?
自然引导出来这个,那么答案是:client的宣称可以有三种方式:
1)DTS
2)Client driver
3)Sysfs
幸运的是:在现代 linux 中, i2c 设备不再需要手动创建,而是使用设备树机制引入,设备树节点
是与 paltform 总线匹配后Linux kernel会自动创建出client结构,但是为了我们了解的更全面,我们还是都介绍一下。
2. DTS、SYSFS宣称i2c ic client
其中sysfs宣称i2c lient命令行如下:
echo mfi_acp 0x10 > /sys/bus/i2c/devices/i2c-0/new_device
echo 0x10 > /sys/bus/i2c/devices/i2c-0/delete_device
其中mfi_acp跟驱动中的如下匹配:
static const struct i2c_device_id mfi_acp_ids[] = {{ "mfi_acp", (kernel_ulong_t )NULL },{ /* END OF LIST */ }
};
.id_table = mfi_acp_ids,
通过DTS宣称也很容易,DTS如下:
ⅱ. User Driver
这个用户写代码,说白了,基本都是i2c转字符设备驱动的套路,我们就来看下I2C 的api就好了
1. i2c_add_driver() 宏
这个就是向Linux内核I2C bus中注册一个driver,这个主要是用于跟i2c client match, 这个宏是调用的i2c_register_driver函数
static int __init mfi_acp_init(void)
{printk("mfi_acp_init\r\n");return i2c_add_driver(&mfi_acp_driver);
}
其中i2c_driver的结构体如下:
struct i2c_driver {unsigned int class;/* Notifies the driver that a new bus has appeared. You should avoid* using this, it will be removed in a near future.*/int (*attach_adapter)(struct i2c_adapter *) __deprecated;/* Standard driver model interfaces */int (*probe)(struct i2c_client *, const struct i2c_device_id *);int (*remove)(struct i2c_client *);/* driver model interfaces that don't relate to enumeration */void (*shutdown)(struct i2c_client *);/* Alert callback, for example for the SMBus alert protocol.* The format and meaning of the data value depends on the protocol.* For the SMBus alert protocol, there is a single bit of data passed* as the alert response's low bit ("event flag").*/void (*alert)(struct i2c_client *, unsigned int data);/* a ioctl like command that can be used to perform specific functions* with the device.*/int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);struct device_driver driver;const struct i2c_device_id *id_table;/* Device detection callback for automatic device creation */int (*detect)(struct i2c_client *, struct i2c_board_info *);const unsigned short *address_list;struct list_head clients;
};
其中决定跟i2c client probe的有两个核心点:
1)of_match_table 跟 dts来match
2)mfi_acp_ids 跟sysfs来match
一旦上面两个条件满足之一,就会自动调用probe
2. i2c_del_driver
对应从i2c bus中删除i2c driver
3. i2c_master_recv
I2C 数据接收函数
4. i2c_master_send
I2C数据发送函数
5. i2c_transfer
I2C传输函数,其中不管是recv跟send都是基于i2c_transfer这个函数
ⅲ. Linux default driver
这个为什么我叫做linux default driver,我相信你看到这里,i2c怎样驱动一个ic已经有了基本的认知,可以使用自己写驱动+app的方式,也可以用linux i2c子系统中默认的驱动来当做驱动,只需要编写一个app就可以了,而linux默认的i2c驱动的思路就是i2c转字符设备驱动,生成的设备节点格式为:/dev/i2c-x,应用层可以直接操作这个节点,这个架构主要实现在i2c-dev.c中,所以我们一般叫做i2c-dev架构,这个代码如果懂基本的字符设备驱动,就可以完全看懂,我觉得不需要额外的做介绍了,可以自己去看下代码
ⅳ. I2C core
i2c core部分,其实不算一个抽象独立的部分,就是一个承上启下的作用,比如我们上面说的一些api,都是这块实现的,有兴趣的可以去研究下。
ⅴ. I2C adapter
I2C adapter其实就是对应的真实主控AP中的i2c bus,不如特定单板上的i2c1等,我们来看下adapter结构体:
/** i2c_adapter is the structure used to identify a physical i2c bus along* with the access algorithms necessary to access it.*/
struct i2c_adapter {struct module *owner;unsigned int class; /* classes to allow probing for */const struct i2c_algorithm *algo; /* the algorithm to access the bus */void *algo_data;/* data fields that are valid for all devices */struct rt_mutex bus_lock;int timeout; /* in jiffies */int retries;struct device dev; /* the adapter device */int nr;char name[48];struct completion dev_released;struct mutex userspace_clients_lock;struct list_head userspace_clients;struct i2c_bus_recovery_info *bus_recovery_info;const struct i2c_adapter_quirks *quirks;
};
其中最重要的就是const struct i2c_algorithm *algo;结构体,我们在下个小节介绍。
ⅵ. I2C Algorithm
结构体如下:
/*** struct i2c_algorithm - represent I2C transfer method* @master_xfer: Issue a set of i2c transactions to the given I2C adapter* defined by the msgs array, with num messages available to transfer via* the adapter specified by adap.* @smbus_xfer: Issue smbus transactions to the given I2C adapter. If this* is not present, then the bus layer will try and convert the SMBus calls* into I2C transfers instead.* @functionality: Return the flags that this algorithm/adapter pair supports* from the I2C_FUNC_* flags.* @reg_slave: Register given client to I2C slave mode of this adapter* @unreg_slave: Unregister given client from I2C slave mode of this adapter** The following structs are for those who like to implement new bus drivers:* i2c_algorithm is the interface to a class of hardware solutions which can* be addressed using the same bus algorithms - i.e. bit-banging or the PCF8584* to name two of the most common.** The return codes from the @master_xfer field should indicate the type of* error code that occurred during the transfer, as documented in the kernel* Documentation file Documentation/i2c/fault-codes.*/
struct i2c_algorithm {/* If an adapter algorithm can't do I2C-level access, set master_xferto NULL. If an adapter algorithm can do SMBus access, setsmbus_xfer. If set to NULL, the SMBus protocol is simulatedusing common I2C messages *//* master_xfer should return the number of messages successfullyprocessed, or a negative value on error */int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,int num);int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,unsigned short flags, char read_write,u8 command, int size, union i2c_smbus_data *data);/* To determine what the adapter supports */u32 (*functionality) (struct i2c_adapter *);#if IS_ENABLED(CONFIG_I2C_SLAVE)int (*reg_slave)(struct i2c_client *client);int (*unreg_slave)(struct i2c_client *client);
#endif
};
其中master_xfer 就是i2c的传输(发送、接收)
functionality主要是设备的能力
你可以想想为啥有这个问题呢,Linux怎么设计呢?其实你可以看到i2c子系统已经把特定的i2c芯片已经通过i2c client抽象出来了,已经把i2c协议实现出来了,那么不同的单板主控的i2c 寄存器肯定不同,那么怎样实现抽象分离呢,就是通过Algorithm来实现的!
这块要真的根据真实单板芯片的寄存器手册来实现了,这块就是单纯的裸板编程了!
b. User Space
这块主要是针对i2c设备的应用程序或者Linux自带的i2c工具。
i2ctool介绍
应用程序编程
五. MFI苹果解密芯片介绍
苹果的加密芯片的主要作用是用于MFI,只要用苹果的一些私有功能就需要MFI认证,比如我们用的Carplay,airplay2,homekit,ipod mp4,通过蓝牙传输自定义数据等,需要用到此芯片。目前我接触到的苹果的芯片有以下版本:2.0B/2.0C/3.0等。这个加密芯片是I2C接口的,电气特性,封装我就不做介绍了。我们主要介绍下软件工程师关注的点。
1. I2C interface
a. Speed
The I2C Interface operates at 100 kHz (standard mode) or 400 kHz (full speed) and consists of two signals: SDA: Data SCL: Clock , SCL跟SDA的总线的上拉电阻最大12KΩ
b. 芯片地址
我们在i2c协议中已经介绍了,i2c地址一共是7个bit address + r/w, 可以看到芯片的真实地址为:0x10
2. 寄存器介绍
苹果的加密芯片一共有以下这些寄存器
Name | Address | Block | Type | Power-Up Valve | Bytes | Access |
Device Version | 0x00 | 0 | uint8 | ACP 3.0: 0x07 ACP 2.0C: 0x05 | 1 | R |
Authentication Revision | 0x01 | 0 | uint8 | 0x01 | 1 | R |
Authentication Protocol Major Version | 0x02 | 0 | uint8 | ACP 3.0:0x03 ACP 2.0C: 0x02 | 1 | R |
Authentication Protocol Minor Version | 0x03 | 0 | uint8 | 0x00 | 1 | R |
Device ID | 0x04 | 0 | uint8 | ACP 3.0: 00 00 03 00 ACP 2.0C: 00 00 02 00 | 4 | R |
Error Code | 0x05 | 0 | uint8 | 0x00 | 1 | R |
Authentication Control and Status | 0x10 | 1 | uint8 | ACP 3.0: 0x00 ACP 2.0C:128 | 1 | R/W |
Challenge Response Data Length | 0x11 | 1 | uint16 | 0 | 2 | R |
Challenge Response Data | 0x12 | 1 | uint8 | Undefined | 64 | R |
Challenge Data Length | 0x20 | 2 | uint16 | ACP 3.0: 0 | 2 | R |
Challenge Data | 0x21 | 2 | uint8 | Undefined | 32 | R/W |
Accessory Certificate Data Length | 0x30 | 3 | uint16 | ACP 3.0: 607-609 ACP 2.0C:<=1280 | 3 | R |
Accessory Certificate Data 1 | 0x31 | 3 | uint8 | Certificate | 128 | R |
Accessory Certificate Data 2 | 0x32 | 3 | uint8 | Certificate | 128 | R |
Accessory Certificate Data 3 | 0x33 | 3 | uint8 | Certificate | 128 | R |
Accessory Certificate Data 4 | 0x34 | 3 | uint8 | Certificate | 128 | R |
Accessory Certificate Data 5 | 0x35 | 3 | uint8 | Certificate | 128 | R |
Self-Test Status | 0x40 | 4 | uint8 | 0x00 | 1 | R |
Device Certificate Serial Number | 0x4E | 4 | uint8 | Certificate | 32 | R |
六. 综合示例
1. 测试环境
因为我手里刚好有正点原子的linux开发板,所以就直接用这个开发板来测试了!
a. 软件环境
Linux kernel的内核版本为:4.1.15
我们直接把苹果的加密芯片挂在i2c1上,可以看到pinctl为: SCL为UART4 TX的引脚复用功能,SDA为UART4 RX的引脚复用功能
pinctrl_i2c1: i2c1grp {fsl,pins = <MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0>;
};
b. 硬件环境
我是通过我之前移植到stm32f429一个单片机上linux系统来验证的
通过pinctl系统看i2c1的引脚为:SCL为UART4 TX的引脚复用功能,SDA为UART4 RX
我手里是一个苹果加密芯片,是熟人送的,插针式的,所以比较好验证
2. 自己写驱动
ⅰ. 驱动代码
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>#define MFI_ACP_CNT 1
#define MFI_ACP_NAME "mfi_acp"struct mfi_acp_dev {dev_t devid;struct cdev cdev;struct class *class;struct device *device;struct device_node *nd;int major;void *private_data;
};static struct mfi_acp_dev mfiacpcdev;#define MFI_MAGIC 'm'
#define MFI_SET_ADDRESS _IOR(MFI_MAGIC, 1, int)static int mfi_acp_open(struct inode *inode, struct file *filp)
{printk("mfi_acp_open\r\n");filp->private_data = &mfiacpcdev;return 0;
}static ssize_t mfi_acp_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{printk("mfi_acp_read\r\n");char *tmp;int ret;struct mfi_acp_dev *dev = (struct mfi_acp_dev *)filp->private_data;struct i2c_client *client = (struct i2c_client *)dev->private_data;tmp = kmalloc(cnt, GFP_KERNEL);if (tmp == NULL)return -ENOMEM;ret = i2c_master_recv(client, tmp, cnt);if (ret >= 0)ret = copy_to_user(buf, tmp, cnt) ? -EFAULT : ret;kfree(tmp);return ret;
}static ssize_t mfi_acp_write(struct file *filp, const char __user *buf,size_t count, loff_t *offset)
{printk("mfi_acp_write\r\n");int ret;char *tmp;struct mfi_acp_dev *dev = (struct mfi_acp_dev *)filp->private_data;struct i2c_client *client = (struct i2c_client *)dev->private_data;tmp = memdup_user(buf, count);if (IS_ERR(tmp))return PTR_ERR(tmp);ret = i2c_master_send(client, tmp, count);kfree(tmp);return ret;
}static long mfi_acp_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{printk("mfi_acp_ioctl\r\n");struct mfi_acp_dev *dev = (struct mfi_acp_dev *)filp->private_data;struct i2c_client *client = (struct i2c_client *)dev->private_data;switch (cmd) {case MFI_SET_ADDRESS:client->addr = arg;return 0;default:return -ENOTTY;}return 0;
}static int mfi_acp_release(struct inode *inode, struct file *filp)
{printk("mfi_acp_release\r\n");return 0;
}static const struct file_operations mfi_acp_ops = {.owner = THIS_MODULE,.open = mfi_acp_open,.read = mfi_acp_read,.write = mfi_acp_write,.release = mfi_acp_release,.unlocked_ioctl = mfi_acp_ioctl,
};static int mfi_acp_probe(struct i2c_client *client, const struct i2c_device_id *id)
{printk("mfi_acp_probe\r\n");alloc_chrdev_region(&mfiacpcdev.devid, 0, MFI_ACP_CNT , MFI_ACP_NAME);mfiacpcdev.major = MAJOR(mfiacpcdev.devid);cdev_init(&mfiacpcdev.cdev, &mfi_acp_ops);cdev_add(&mfiacpcdev.cdev, mfiacpcdev.devid, MFI_ACP_CNT);mfiacpcdev.class = class_create(THIS_MODULE, MFI_ACP_NAME);if (IS_ERR(mfiacpcdev.class)) {return PTR_ERR(mfiacpcdev.class);}mfiacpcdev.device = device_create(mfiacpcdev.class, NULL, mfiacpcdev.devid, NULL, MFI_ACP_NAME);if (IS_ERR(mfiacpcdev.device)) {return PTR_ERR(mfiacpcdev.device);}mfiacpcdev.private_data = client;return 0;
}static int mfi_acp_remove(struct i2c_client *client)
{printk("mfi_acp_remove\r\n");cdev_del(&mfiacpcdev.cdev);unregister_chrdev_region(mfiacpcdev.devid, MFI_ACP_CNT);device_destroy(mfiacpcdev.class, mfiacpcdev.devid);class_destroy(mfiacpcdev.class);return 0;
}static const struct of_device_id mfi_acp_of_match[] = {{ .compatible = "Quectel,mfi_acp" },{ /* END OF LIST */ }
};static const struct i2c_device_id mfi_acp_ids[] = {{ "mfi_acp", (kernel_ulong_t )NULL },{ /* END OF LIST */ }
};static struct i2c_driver mfi_acp_driver = {.probe = mfi_acp_probe,.remove = mfi_acp_remove,.driver = {.owner = THIS_MODULE,.name = "mfi_acp",.of_match_table = mfi_acp_of_match, },.id_table = mfi_acp_ids,
};static int __init mfi_acp_init(void)
{printk("mfi_acp_init\r\n");return i2c_add_driver(&mfi_acp_driver);
}static void __exit mfi_acpc_exit(void)
{printk("mfi_acpc_exit\r\n");i2c_del_driver(&mfi_acp_driver);
}module_init(mfi_acp_init);
module_exit(mfi_acpc_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Quectel Zhongjun.Yu");
ⅱ. dts或者sys增加i2c client
驱动写好后,我们就可以通过sys或者dts来让kernel自己创建出来i2c client,我们先来使用sysfs的方式来创建
echo mfi_acp 0x10 > /sys/bus/i2c/devices/i2c-0/new_device
echo 0x10 > /sys/bus/i2c/devices/i2c-0/delete_device
输入这个以后就有这个probe调用以及字符设备节点了
ⅲ. 应用程序
#include <unistd.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>typedef uint8_t osi_err_t;#define OSI_ERR_NONE (0U)
#define OSI_ERR_TIMEOUT (1U)
#define OSI_ERR_BUSY (2U)
#define OSI_ERR_NO_RESOURCE (3U)
#define OSI_ERR_NOT_SUPPORTED (4U)
#define OSI_ERR_NOT_IMPLEMENTED (5U)
#define OSI_ERR_INV_PARMS (6U)
#define OSI_ERR_INV_HANDLE (7U)
#define OSI_ERR_INTERNAL (8U)
#define OSI_ERR_INITIALIZED (9U)
#define OSI_ERR_NOT_EQUAL (10U)
#define OSI_ERR_NOT_INITIALIZED (11U)
#define OSI_ERR_INTERRUPTED (12U)
#define OSI_ERR_NOT_FOUND (13U)
#define OSI_ERR_UNDERRUN (14U)
#define OSI_ERR_NOT_STARTED (15U)
#define OSI_ERR_NOT_STOPPED (16U)#define ACP3_0_CERT_REG_NUM 5 /* Register ID:0x31~0x35 */
#define ACP3_0_REG_CERT_DATA_LEN 2
#define ACP3_0_REG_CERT_DATA_SIZE 128
#define ACP3_0_REG_SERIAL_NUM_SIZE 32
#define ACP3_0_REG_CHALLENGE_DATA_LEN_SIZE 2
#define ACP3_0_REG_CTL_STATUS_CHA_RSP_SUCCESS (1<<4)int mfi_fd = -1;//#define I2C_NODE_NAME "/dev/i2c-2"
#define I2C_MFI_ADDRESS 0x10osi_err_t osi_mfi_i2c_open(char *i2c_name)
{mfi_fd = open(i2c_name, O_RDWR);if(mfi_fd < 0){return OSI_ERR_INV_PARMS;}if (ioctl(mfi_fd, I2C_SLAVE, I2C_MFI_ADDRESS) < 0) {close(mfi_fd);return OSI_ERR_INV_PARMS;}return OSI_ERR_NONE;
}
osi_err_t osi_mfi_i2c_close(void)
{if (mfi_fd != -1) {close(mfi_fd);mfi_fd = -1;}return OSI_ERR_NONE;
}
osi_err_t osi_mfi_i2c_read(uint8_t reg, uint8_t *data, uint16_t data_len)
{usleep(2*1000);if (write(mfi_fd, ®, 1) != 1) {return OSI_ERR_INV_PARMS;}usleep(1*1000);if (read(mfi_fd, data, data_len) != data_len) {return OSI_ERR_INV_PARMS;}usleep(2*1000);return OSI_ERR_NONE;
}
osi_err_t osi_mfi_i2c_write(uint8_t reg, uint8_t *data, uint16_t data_len)
{uint16_t write_len = 1 + data_len;uint8_t *write_data = (uint8_t*)malloc(write_len);write_data[0] = reg;memcpy(write_data+1, data, data_len);usleep(2*1000);if (write(mfi_fd, write_data, write_len) != data_len) {return OSI_ERR_INV_PARMS;}usleep(2*1000);free(data);return OSI_ERR_NONE;
}enum acp_register_index_e
{acp3_0_reg_device_version = 0x00,acp3_0_reg_auth_revison = 0x01,acp3_0_reg_auth_major_version = 0x02,acp3_0_reg_auth_minor_version = 0x03,acp3_0_reg_device_id = 0x04,acp3_0_reg_error_code = 0x05,acp3_0_reg_control_status = 0x10,acp3_0_reg_challenge_response_data_len = 0x11,acp3_0_reg_challenge_response_data = 0x12,acp3_0_reg_challenge_data_len = 0x20,acp3_0_reg_challenge_data = 0x21,acp3_0_reg_acc_certificate_data_len = 0x30,acp3_0_reg_acc_certificate_data1 = 0x31,acp3_0_reg_acc_certificate_data2 = 0x32,acp3_0_reg_acc_certificate_data3 = 0x33,acp3_0_reg_acc_certificate_data4 = 0x34,acp3_0_reg_acc_certificate_data5 = 0x35,acp3_0_reg_self_test_status = 0x40,acp3_0_reg_device_certificate_ser_num = 0x4e,acp3_0_reg_sleep = 0x60,
};uint32_t iap2_be_read_16( const uint8_t * buffer, int pos)
{return (uint16_t)(((uint16_t) buffer[(pos)+1]) | (((uint16_t)buffer[ pos]) << 8));
}#define MAX_COL 16U
#define SHOW_LINE_SIZE 16void bt_mgr_hex_dump(const uint8_t *data, uint32_t len)
{uint32_t line;uint32_t curline = 0U;uint32_t curcol = 0U;char showline[SHOW_LINE_SIZE];uint32_t data_pos = 0U;if ((len % MAX_COL) != 0U) {line = (len / MAX_COL) + 1U;} else {line = len / MAX_COL;}for (curline = 0U; curline < line; curline++) {(void) sprintf(showline, "%08xh:", curline * MAX_COL);(void) printf("%s", showline);for (curcol = 0; curcol < MAX_COL; curcol++) {if (data_pos < len) {(void) printf("%02x ", data[data_pos]);data_pos++;continue;} else {break;}}(void) printf("\n");}
}int main(int argc, char *argv[])
{uint8_t acp_major_version = 0;uint16_t retry = 0xff;uint8_t challenge_status = 0;uint8_t start_new_challenge = 1;uint8_t *challenge_rsp_data;uint16_t challenge_data_len = 32;uint16_t chanllenge_rsp_data_len = 0;uint8_t challenge_rsp_data_len_buf[ACP3_0_REG_CHALLENGE_DATA_LEN_SIZE] = {0};unsigned char challenge_data[32] = {0x01, 0xe1, 0x54, 0xc1, 0x2b, 0x26, 0xb3, 0xff,0xca, 0x12, 0x49, 0x42, 0x88, 0xdb, 0x07, 0x7b, 0x4e, 0xf0, 0x7b,0x88, 0xa4, 0xd0, 0xaf, 0x9f, 0x33, 0xa0, 0xe7, 0x9a, 0xeb, 0xe6, 0x53, 0x20};if(argc != 2){printf("usage: %s /dev/i2c-x\r\n",argv[0]);return -1;}osi_mfi_i2c_open(argv[1]);osi_mfi_i2c_read(acp3_0_reg_auth_major_version,&acp_major_version,1);if(acp_major_version == 3)printf("ACP version is 3.0\r\n");else if(acp_major_version == 2)printf("ACP version is 2.0\r\n");else{printf("ACP version is unknown version %d\r\n",acp_major_version);return -1;}osi_mfi_i2c_write(acp3_0_reg_challenge_data,challenge_data,32);osi_mfi_i2c_write(acp3_0_reg_control_status,&start_new_challenge,1);while(retry){usleep(1*1000);osi_mfi_i2c_read(acp3_0_reg_control_status,&challenge_status,1);if(challenge_status == ACP3_0_REG_CTL_STATUS_CHA_RSP_SUCCESS){printf("challenge_status %d\r\n",challenge_status);break;}retry--;}if(retry == 0){uint8_t err_code = 0;osi_mfi_i2c_read(acp3_0_reg_error_code,&err_code,1);printf("challenge_status:0x%x err_code:0x%x\r\n",challenge_status,err_code);osi_mfi_i2c_close();return -1;}osi_mfi_i2c_read(acp3_0_reg_challenge_response_data_len,challenge_rsp_data_len_buf,ACP3_0_REG_CHALLENGE_DATA_LEN_SIZE);chanllenge_rsp_data_len = iap2_be_read_16(challenge_rsp_data_len_buf,0);printf("chanllenge_rsp_data_len %d\r\n",chanllenge_rsp_data_len);challenge_rsp_data = malloc(chanllenge_rsp_data_len);osi_mfi_i2c_read(acp3_0_reg_challenge_response_data,challenge_rsp_data,chanllenge_rsp_data_len);bt_mgr_hex_dump(challenge_rsp_data,chanllenge_rsp_data_len);osi_mfi_i2c_close();return 0;
}
3. 通过默认i2c-dev.c来实现
这种方式就是使用i2c-dev的架构,来让linux kernel自己生成字符设备驱动,我们已经在前面介绍过源码,在这边,我们直接来使用。
a. 通过i2c tool来验证应用程序
我们在使用i2c tool验证之前,我们先来介绍下这个tool. I2C tools包含一套用于Linux应用层测试各种各样I2C功能的工具。它的主要功能包括:总线探测工具、SMBus访问帮助程序、EEPROM解码脚本、EEPROM编程工具和用于SMBus访问的python模块。只要你所使用的内核中包含I2C设备驱动,那么就可以在你的板子中正常使用这个测试工具。i2c tool下载路径为:Index of /pub/software/utils/i2c-tools/
试想一个场景:你拿到开发板或者是从公司的硬件同事拿到一个带有I2C外设的板子,我们应该如何最快速的使用起来这个I2C设备呢?
既然我们总是说这个I2C总线在嵌入式开发中被广泛的使用,那么是否有现成的测试工具帮我们完成这个快速使用板子的I2C设备呢?答案是有的,而且这个测试工具的代码还是开源的,它被广泛的应用在linux应用层来快速验证I2C外设是否可用,为我们测试I2C设备提供了很好的捷径。
另外i2c tool包含以下几个测试工具:i2cdetect/i2cget/i2cset/i2cdump/i2ctransfer
编译源码:如果你想编译静态版本,你可以输入命令:make USE_STATIC_LIB=1;如果使用动态库的话,可以直接输入make进行编译。安装命令为:make install,如果你想要让最后生成的二进制文件最小的话,可以在“make install”之前运行“make strip”。但是,这将不能生成任何调试库,也就不能尝试进一步调试。然后将tools目录下的5个可执行文件i2cdetect,i2cdump,i2cget,i2cset和i2ctransfer复制到板子的/usr/sbin/中;将lib目录下的libi2c.so.0.1.1文件复制到板子的/usr/lib/libi2c.so.0。之后别忘了将上面的文件修改为可执行的权限。
ⅰ. i2cdetect
i2cdetect的主要功能就是I2C设备查询,它用于扫描I2C总线上的设备。它输出一个表,其中包含指定总线上检测到的设备的列表。
该命令的常用格式为:i2cdetect [-y] [-a] [-q|-r] i2cbus [first last]。具体参数的含义如下:
-y | 取消交互模式。默认情况下,i2cdetect将等待用户的确认,当使用此标志时,它将直接执行操作。 |
-a | 强制扫描非规则地址。一般不推荐。 |
-q | 使用SMBus“快速写入”命令进行探测。一般不推荐。 |
-r | 使用SMBus“接收字节”命令进行探测。一般不推荐。 |
-F | 显示适配器实现的功能列表并退出。 |
-V | 显示I2C工具的版本并推出。 |
-l | 显示已经在系统中使用的I2C总线。 |
i2cbus | 表示要扫描的I2C总线的编号或名称。 |
first last | 表示要扫描的从设备地址范围。 |
该功能的常用方式:
第一,先通过i2cdetect -l查看当前系统中的I2C的总线情况:
第二,若总线上挂载I2C从设备,可通过i2cdetect扫描某个I2C总线上的所有设备。可通过控制台输入
i2cdetect -y bus_number
其中"--"表示地址被探测到了,但没有芯片应答; "UU"因为这个地址目前正在被一个驱动程序使用,探测被省略;而16进制的地址表示被探测到了,但是没有对应的i2c驱动.
我们看到0x1e就是正点原子设备树中的ap3216c
第三,查询I2C总线1 (I2C -1)的功能,命令为
i2cdetect -F bus_number
这个就是我们在SMBUS协议中介绍的linux capability.
ⅱ. i2cget
i2cget的主要功能是获取I2C外设某一寄存器的内容。该命令的常用格式为:
i2cget [-f] [-y] [-a] i2cbus chip-address [data-address [mode]]。具体参数的含义如下:
-f | 强制访问设备,即使它已经很忙。 默认情况下,i2cget将拒绝访问已经在内核驱动程序控制下的设备。 |
-y | 取消交互模式。默认情况下,i2cdetect将等待用户的确认,当使用此标志时,它将直接执行操作。 |
-a | 允许在0x00 - 0x07和0x78 - 0x7f之间使用地址。一般不推荐。 |
i2cbus | 表示要扫描的I2C总线的编号或名称。这个数字应该与i2cdetect -l列出的总线之一相对应。 |
chip-address | 要操作的外设从地址。 |
data-address | 被查看外设的寄存器地址。 |
mode | 显示数据的方式: b (read byte data, default) w (read word data) c (write byte/read byte) |
下面是完成读取0总线上从地址为0x50的外设的0x10寄存器的数据,命令为:
i2cget -y -f 0 0x50 0x10
ⅲ. i2cset
i2cset的主要功能是通过I2C总线设置设备中某寄存器的值。该命令的常用格式为:
i2cset [-f] [-y] [-m mask] [-r] i2cbus chip-address data-address [value] ...[mode]
具体参数的含义如下:
-f | 强制访问设备,即使它已经很忙。 默认情况下,i2cget将拒绝访问已经在内核驱动程序控制下的设备。 |
-r | 在写入值之后立即读取它,并将结果与写入的值进行比较。 |
-y | 取消交互模式。默认情况下,i2cdetect将等待用户的确认,当使用此标志时,它将直接执行操作。 |
-V | 显示I2C工具的版本并推出。 |
i2cbus | 表示要扫描的I2C总线的编号或名称。这个数字应该对应于i2cdetect -l列出的总线之一。 |
-m mask | 如果指定mask参数,那么描述哪些value位将是实际写入data-addres的。掩码中设置为1的位将从值中取出,而设置为0的位将从数据地址中读取,从而由操作保存。 |
mode | b: 单个字节 w:16位字 s:SMBus模块 i:I2C模块的读取大小 c: 连续读取所有字节,对于具有地址自动递增功能的芯片(如EEPROM)非常有用。 W与 w类似,只是读命令只能在偶数寄存器地址上发出;这也是主要用于EEPROM的。 |
下面是完成向0总线上从地址为0x50的eeprom的0x10寄存器写入0x55,命令为:
i2cset -y -f 0 0x50 0x10 0x55
然后用i2cget读取0总线上从地址为0x50的eeprom的0x10寄存器的数据,命令为:i2cget -y -f 0 0x50 0x10
ⅳ. i2cdump
i2cdump的主要功能查看I2C从设备器件所有寄存器的值。 该命令的常用格式为:
i2cdump [-f] [-r first-last] [-y] [-a] i2cbus address [mode [bank [bankreg]]]。
具体参数的含义如下:
-f | 强制访问设备,即使它已经很忙。 默认情况下,i2cget将拒绝访问已经在内核驱动程序控制下的设备。 |
-r | 限制正在访问的寄存器范围。此选项仅在模式b,w,c和W中可用。对于模式W,first必须是偶数,last必须是奇数。 |
-y | 取消交互模式。默认情况下,i2cdetect将等待用户的确认,当使用此标志时,它将直接执行操作。 |
-V | 显示I2C工具的版本并推出。 |
i2cbus | 表示要扫描的I2C总线的编号或名称。这个数字应该对应于i2cdetect -l列出的总线之一。 |
first last | 表示要扫描的从设备地址范围。 |
mode | b: 单个字节 w:16位字 s:SMBus模块 i:I2C模块的读取大小 c: 连续读取所有字节,对于具有地址自动递增功能的芯片(如EEPROM)非常有用。 W与 w类似,只是读命令只能在偶数寄存器地址上发出;这也是主要用于EEPROM的。 |
下面是完成读取0总线上从地址为0x50的eeprom的数据,命令为:
i2cdump -f -y 0 0x50
ⅴ. i2ctransfer
i2ctransfer的主要功能是在一次传输中发送用户定义的I2C消息。i2ctransfer是一个创建I2C消息并将其合并为一个传输发送的程序。对于读消息,接收缓冲区的内容被打印到stdout,每个读消息一行。
该命令的常用格式为:
i2ctransfer [-f] [-y] [-v] [-a] i2cbus desc [data] [desc [data]]
具体参数的含义如下:
-f | 强制访问设备,即使它已经很忙。 默认情况下,i2cget将拒绝访问已经在内核驱动程序控制下的设备。 |
-y | 取消交互模式。默认情况下,i2cdetect将等待用户的确认,当使用此标志时,它将直接执行操作。 |
-v | 启用详细输出。它将打印所有信息发送,即不仅为读消息,也为写消息。 |
-V | 显示I2C工具的版本并推出。 |
-a | 允许在0x00 - 0x02和0x78 - 0x7f之间使用地址。一般不推荐。 |
i2cbus | 表示要扫描的I2C总线的编号或名称。这个数字应该对应于i2cdetect -l列出的总线之一。 |
下面是完成向0总线上从地址为0x50的eeprom的0x20开始的4个寄存器写入0x01,0x02,0x03,0x04命令为:
i2ctransfer -f -y 0 w5@0x50 0x20 0x01 0x02 0x03 0x04
然后再通过命令i2ctransfer -f -y 0 w1@0x50 0x20 r4将0x20地址的4个寄存器数据读出来,见下图:
ⅵ. 验证苹果加密芯片
NOTED: 有的时候也不能盲目迷信工具,我使用正常的程序可以验证,但是用这个工具失败,可能是SMBUS协议了,但是我就没有是深究了!也只能通过i2cdect探测到
b. 使用i2c-dev通过自己编写应用程序来实现
我直接使用的这种架构,也就是默认的i2c-dev的架构
#include <unistd.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>typedef uint8_t osi_err_t;#define OSI_ERR_NONE (0U)
#define OSI_ERR_TIMEOUT (1U)
#define OSI_ERR_BUSY (2U)
#define OSI_ERR_NO_RESOURCE (3U)
#define OSI_ERR_NOT_SUPPORTED (4U)
#define OSI_ERR_NOT_IMPLEMENTED (5U)
#define OSI_ERR_INV_PARMS (6U)
#define OSI_ERR_INV_HANDLE (7U)
#define OSI_ERR_INTERNAL (8U)
#define OSI_ERR_INITIALIZED (9U)
#define OSI_ERR_NOT_EQUAL (10U)
#define OSI_ERR_NOT_INITIALIZED (11U)
#define OSI_ERR_INTERRUPTED (12U)
#define OSI_ERR_NOT_FOUND (13U)
#define OSI_ERR_UNDERRUN (14U)
#define OSI_ERR_NOT_STARTED (15U)
#define OSI_ERR_NOT_STOPPED (16U)#define ACP3_0_CERT_REG_NUM 5 /* Register ID:0x31~0x35 */
#define ACP3_0_REG_CERT_DATA_LEN 2
#define ACP3_0_REG_CERT_DATA_SIZE 128
#define ACP3_0_REG_SERIAL_NUM_SIZE 32
#define ACP3_0_REG_CHALLENGE_DATA_LEN_SIZE 2
#define ACP3_0_REG_CTL_STATUS_CHA_RSP_SUCCESS (1<<4)int mfi_fd = -1;//#define I2C_NODE_NAME "/dev/i2c-2"
#define I2C_MFI_ADDRESS 0x10osi_err_t osi_mfi_i2c_open(char *i2c_name)
{mfi_fd = open(i2c_name, O_RDWR);if(mfi_fd < 0){return OSI_ERR_INV_PARMS;}if (ioctl(mfi_fd, I2C_SLAVE, I2C_MFI_ADDRESS) < 0) {close(mfi_fd);return OSI_ERR_INV_PARMS;}return OSI_ERR_NONE;
}
osi_err_t osi_mfi_i2c_close(void)
{if (mfi_fd != -1) {close(mfi_fd);mfi_fd = -1;}return OSI_ERR_NONE;
}
osi_err_t osi_mfi_i2c_read(uint8_t reg, uint8_t *data, uint16_t data_len)
{usleep(2*1000);if (write(mfi_fd, ®, 1) != 1) {return OSI_ERR_INV_PARMS;}usleep(1*1000);if (read(mfi_fd, data, data_len) != data_len) {return OSI_ERR_INV_PARMS;}usleep(2*1000);return OSI_ERR_NONE;
}
osi_err_t osi_mfi_i2c_write(uint8_t reg, uint8_t *data, uint16_t data_len)
{uint16_t write_len = 1 + data_len;uint8_t *write_data = (uint8_t*)malloc(write_len);write_data[0] = reg;memcpy(write_data+1, data, data_len);usleep(2*1000);if (write(mfi_fd, write_data, write_len) != data_len) {return OSI_ERR_INV_PARMS;}usleep(2*1000);free(data);return OSI_ERR_NONE;
}enum acp_register_index_e
{acp3_0_reg_device_version = 0x00,acp3_0_reg_auth_revison = 0x01,acp3_0_reg_auth_major_version = 0x02,acp3_0_reg_auth_minor_version = 0x03,acp3_0_reg_device_id = 0x04,acp3_0_reg_error_code = 0x05,acp3_0_reg_control_status = 0x10,acp3_0_reg_challenge_response_data_len = 0x11,acp3_0_reg_challenge_response_data = 0x12,acp3_0_reg_challenge_data_len = 0x20,acp3_0_reg_challenge_data = 0x21,acp3_0_reg_acc_certificate_data_len = 0x30,acp3_0_reg_acc_certificate_data1 = 0x31,acp3_0_reg_acc_certificate_data2 = 0x32,acp3_0_reg_acc_certificate_data3 = 0x33,acp3_0_reg_acc_certificate_data4 = 0x34,acp3_0_reg_acc_certificate_data5 = 0x35,acp3_0_reg_self_test_status = 0x40,acp3_0_reg_device_certificate_ser_num = 0x4e,acp3_0_reg_sleep = 0x60,
};uint32_t iap2_be_read_16( const uint8_t * buffer, int pos)
{return (uint16_t)(((uint16_t) buffer[(pos)+1]) | (((uint16_t)buffer[ pos]) << 8));
}#define MAX_COL 16U
#define SHOW_LINE_SIZE 16void bt_mgr_hex_dump(const uint8_t *data, uint32_t len)
{uint32_t line;uint32_t curline = 0U;uint32_t curcol = 0U;char showline[SHOW_LINE_SIZE];uint32_t data_pos = 0U;if ((len % MAX_COL) != 0U) {line = (len / MAX_COL) + 1U;} else {line = len / MAX_COL;}for (curline = 0U; curline < line; curline++) {(void) sprintf(showline, "%08xh:", curline * MAX_COL);(void) printf("%s", showline);for (curcol = 0; curcol < MAX_COL; curcol++) {if (data_pos < len) {(void) printf("%02x ", data[data_pos]);data_pos++;continue;} else {break;}}(void) printf("\n");}
}int main(int argc, char *argv[])
{uint8_t acp_major_version = 0;uint16_t retry = 0xff;uint8_t challenge_status = 0;uint8_t start_new_challenge = 1;uint8_t *challenge_rsp_data;uint16_t challenge_data_len = 32;uint16_t chanllenge_rsp_data_len = 0;uint8_t challenge_rsp_data_len_buf[ACP3_0_REG_CHALLENGE_DATA_LEN_SIZE] = {0};unsigned char challenge_data[32] = {0x01, 0xe1, 0x54, 0xc1, 0x2b, 0x26, 0xb3, 0xff,0xca, 0x12, 0x49, 0x42, 0x88, 0xdb, 0x07, 0x7b, 0x4e, 0xf0, 0x7b,0x88, 0xa4, 0xd0, 0xaf, 0x9f, 0x33, 0xa0, 0xe7, 0x9a, 0xeb, 0xe6, 0x53, 0x20};if(argc != 2){printf("usage: %s /dev/i2c-x\r\n",argv[0]);return -1;}osi_mfi_i2c_open(argv[1]);osi_mfi_i2c_read(acp3_0_reg_auth_major_version,&acp_major_version,1);if(acp_major_version == 3)printf("ACP version is 3.0\r\n");else if(acp_major_version == 2)printf("ACP version is 2.0\r\n");else{printf("ACP version is unknown version %d\r\n",acp_major_version);return -1;}osi_mfi_i2c_write(acp3_0_reg_challenge_data,challenge_data,32);osi_mfi_i2c_write(acp3_0_reg_control_status,&start_new_challenge,1);while(retry){usleep(1*1000);osi_mfi_i2c_read(acp3_0_reg_control_status,&challenge_status,1);if(challenge_status == ACP3_0_REG_CTL_STATUS_CHA_RSP_SUCCESS){printf("challenge_status %d\r\n",challenge_status);break;}retry--;}if(retry == 0){uint8_t err_code = 0;osi_mfi_i2c_read(acp3_0_reg_error_code,&err_code,1);printf("challenge_status:0x%x err_code:0x%x\r\n",challenge_status,err_code);osi_mfi_i2c_close();return -1;}osi_mfi_i2c_read(acp3_0_reg_challenge_response_data_len,challenge_rsp_data_len_buf,ACP3_0_REG_CHALLENGE_DATA_LEN_SIZE);chanllenge_rsp_data_len = iap2_be_read_16(challenge_rsp_data_len_buf,0);printf("chanllenge_rsp_data_len %d\r\n",chanllenge_rsp_data_len);challenge_rsp_data = malloc(chanllenge_rsp_data_len);osi_mfi_i2c_read(acp3_0_reg_challenge_response_data,challenge_rsp_data,chanllenge_rsp_data_len);bt_mgr_hex_dump(challenge_rsp_data,chanllenge_rsp_data_len);osi_mfi_i2c_close();return 0;
}