一文了解Linux内核I2C子系统,驱动苹果MFI加密芯片

版本

日期

作者

变更表述

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
ACP 2.0C:20

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, &reg, 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, &reg, 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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/906626.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Windows 部署非安装版Redis

1.下载Redis https://github.com/microsoftarchive/redis/releases 选择下载zip包&#xff0c;如Redis-x64-3.0.504.zip&#xff0c;并解压 2.启动非安装版redis服务 进入到redis目录&#xff0c;打开cmd 执行命令 redis-server.exe redis.windows.conf 3.登录redis客户端…

多个玩家在线游戏

这张图片列出了多人游戏的两种主要网络架构类型&#xff1a; 1. Peer-to-Peer (P2P)&#xff1a; 点对点网络&#xff0c;其中每个玩家的游戏客户端直接与其他玩家的游戏客户端通信。这种架构通常用于小型或中型规模的多人游戏。 2. Client-Server&#xff1a; 客户端-服务器…

JavaIO流操作

目录 简介 字节输入流 获取字节输入流 读 关闭输入流 字节输出流 获取字节输出流 写 换行符 刷新 关闭输出流 字符流输入流 获取字符输入流 读 关闭输入流 字符输出流 获取字符输出流 写 换行符 刷新 关闭输出流 简介 IO流分为两大派系&#xff1a; …

并查集与LRUCache(Java数据结构)

前言&#xff1a; 学习过二叉树之后就应该知道了如何构建一颗二叉树&#xff0c;双亲结点和孩子节点的关系&#xff0c;甚至可以放在顺序表中去构建一棵二叉树&#xff01; 接下来我们要以另一种方式去组织一棵树&#xff1a; 如何表示一棵树之间的关系&#xff1f;(这棵…

Nature Communications|基于深度学习的HE染色组织向特殊染色的转换

工作速览 病理学是通过视觉检查组织切片来进行的&#xff0c;这些切片通常用组织化学染色法染色。虽然苏木精和伊红&#xff08;H&E&#xff09;染色最为常用&#xff0c;但特殊染色可以为不同的组织成分提供额外的对比度。 **在这里&#xff0c;作者展示了从H&E染色…

阿里国际2025届校园招聘 0826算法岗笔试

目录 1. 第一题2. 第二题3. 第三题 ⏰ 时间&#xff1a;2024/08/26 &#x1f504; 输入输出&#xff1a;ACM格式 ⏳ 时长&#xff1a;100min 本试卷分为单选&#xff0c;多选&#xff0c;编程三部分&#xff0c;这里只展示编程题。 1. 第一题 题目描述 小红有一个大小为 n …

goframe开发一个企业网站 模版界面5

html或者说是模板的控制 以下是是系统的设置 server:address: ":8000"serverRoot: "resource/public" #这里要加上&#xff0c;为以后的静态文件的引入准备openapiPath: "/api.json"swaggerPath: "/swagger"cookieMaxAge: "365…

适配器模式:类适配器与对象适配器

适配器模式是一种结构性设计模式&#xff0c;旨在将一个接口转换成客户端所期望的另一种接口。它通常用于解决由于接口不兼容而导致的类之间的通信问题。适配器模式主要有两种实现方式&#xff1a;类适配器和对象适配器。下面&#xff0c;我们将详细探讨这两种方式的优缺点及适…

如何在Linux系统中使用Ansible进行自动化部署

如何在Linux系统中使用Ansible进行自动化部署 Ansible简介 安装Ansible 在Debian/Ubuntu系统中安装 在CentOS/RHEL系统中安装 Ansible的基本概念 Inventory文件 Playbooks Modules 创建Inventory文件 编写第一个Playbook 创建Playbook文件 运行Playbook 使用Handlers 编写包…

Spring Boot 3.x 整合 Druid 数据库连接池(含密码加密)

Spring Boot 3.x 整合 Druid 数据库连接池&#xff08;含密码加密&#xff09; 1. 为什么需要数据库连接池&#xff1f; 在传统的数据库连接中&#xff0c;每一次与数据库连接都会消耗大量的系统资源和时间。数据库连接池会提前创建一定数量的数据库连接保存在池中&#xff0…

【论文#码率控制】Rate Control for H.264 Video With Enhanced Rate and Distortion Models

目录 摘要1.前言2.编码器框架3.增强RD模型3.1 头部比特的速率模型3.2 源比特的码率模型3.3 失真模型3.4 块类型的确定 4.面向H264 Baseline Profile的码率控制算法5.实验结果6.结论和未来工作 《Rate Control for H.264 Video With Enhanced Rate and Distortion Models》 Auth…

vue和django接口联调

vue访问服务端接口 配置跨域 前端跨域 打开vite.config.js&#xff0c;在和resolve同级的地方添加配置。 proxy代表代理的意思 "/api"是以/api开头的路径走这个配置 target代表目标 changeOrigin: true,是开启跨域请求 rewrite是编辑路径。 (path) > pa…

HTML鼠标移动的波浪线动画——页面将会初始化一个Canvas元素,并使用JavaScript代码在Canvas上绘制响应鼠标移动的波浪线动画

代码如下 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Wave Animation</title><style&…

玩转Docker | Docker基础入门与常用命令指南

玩转Docker | Docker基础入门与常用命令指南 引言基本概念help帮助信息常用命令管理镜像运行容器构建镜像其他Docker命令整理结语引言 Docker 是一种开源的应用容器引擎,它允许开发者将应用程序及其依赖打包进一个可移植的容器中,然后发布到任何流行的 Linux 机器上。这大大简…

信息学科平台设计与实现:Spring Boot技术详解

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

现代化水电管理:Spring Boot在大学城的实践

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

Rust 力扣 - 289. 生命游戏

文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 我们记录上一行和当前行转换之后的状态&#xff0c;当前行转换之后的状态计算完毕后调整上一行状态&#xff0c;直至最后一行状态计算完毕后调整最后一行状态 题解代码 pub fn game_of_life(board: &mut V…

015:地理信息系统开发平台ArcGIS Engine10.2与ArcGIS SDK for the Microsoft .NET Framework安装教程

摘要&#xff1a;本文详细介绍地理信息系统开发平台ArcGIS Engine10.2与ArcGIS SDK for the Microsoft .NET Framework的安装流程。 一、软件介绍 ArcGIS Engine 10.2是由Esri公司开发的一款强大的GIS&#xff08;地理信息系统&#xff09;开发平台。该软件基于ArcGIS 10.2 fo…

如何进行PDF高效合并?盘点11款PDF编辑器给你

是不是经常觉得烦&#xff1a;一个PDF文件特别长&#xff0c;里面好多章节或者报告&#xff0c;每次要看或者给别人发都得翻半天&#xff1f;别担心&#xff0c;今天我就来教你们几个合并PDF的小技巧&#xff0c;保证让你在2024年变成整理文件的高手&#xff01; 1、福昕PDF文…

PPT素材、模板免费下载!

做PPT一定要收藏好这6个网站&#xff0c;PPT模板、素材、图表、背景等超多素材全部免费下载。 1、菜鸟图库 ppt模板免费下载|ppt背景图片 - 菜鸟图库 菜鸟图库网有非常丰富的免费素材&#xff0c;像设计类、办公类、自媒体类等素材都很丰富。PPT模板种类很多&#xff0c;全部都…