版本 | 日期 | 作者 | 变更表述 |
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 */
#endif
void *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_xfer
to NULL. If an adapter algorithm can do SMBus access, set
smbus_xfer. If set to NULL, the SMBus protocol is simulated
using common I2C messages */
/* master_xfer should return the number of messages successfully
processed, 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 0x4001b8b0
MX6UL_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 0x10
osi_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 16
void 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 0x10
osi_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 16
void 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;
}