一文了解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	*/
#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
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 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, &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 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, &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 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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring之依赖注入(DI)和控制反转(IoC)——配置文件、纯注解

依赖注入 依赖注入(Dependency Injection&#xff0c;简称 DI)与控制反转(loC)的含义相同&#xff0c;只不过这两 个称呼是从两个角度描述的同一个概念。对于一个 Spring 初学者来说&#xff0c;这两种称呼很难理解, 下面我们将通过简单的语言来描述这两个概念。 当Java对象&…

Ubuntu 22.04安装部署

一、部署环境 表 1‑1 环境服务版本号系统Ubuntu22.04 server lts运行环境1JDK1.8前端WEBNginx1.8数据库postgresqlpostgresql13postgis3.1pgrouting3.1消息队列rabbitmq3.X(3.0以上)运行环境2erlang23.3.3.1 二、安装系统 2.1安装 1.安装方式&#xff0c;选第一条。 2.选择…

基于ResNet50模型的船型识别与分类系统研究

项目源码获取方式见文章末尾&#xff01; 600多个深度学习项目资料&#xff0c;快来加入社群一起学习吧。 《------往期经典推荐------》 项目名称 1.【LSTM模型实现光伏发电功率的预测】 2.【卫星图像道路检测DeepLabV3Plus模型】 3.【GAN模型实现二次元头像生成】 4.【CNN模…

信息学科平台系统开发:基于Spring Boot的最佳实践

3系统分析 3.1可行性分析 通过对本基于保密信息学科平台系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本基于保密信息学科平台系统采用Spring Boot框架&a…

Cityscapes数据集:如何将像素级的多边形标注的分割数据标注转为目标检测的bbox标注

Cityscapes数据集官网下载地址&#xff1a; https://www.cityscapes-dataset.com/ 相关介绍&#xff1a;从官网下载这三个压缩包文件leftImg8bit_trainvaltest.zip、gtCoarse.zip、gtFine_trainvaltest.zip 1&#xff09;leftImg8bit_trainvaltest.zip分为train、val以及tes…

【周末推荐】Windows无缝连接iPhone

关注“ONE生产力”&#xff0c;获取更多精彩推荐&#xff01; 又到了周末推荐时间了&#xff0c;今天我们介绍一个Windows内置的功能&#xff0c;能够帮助大家将自己的电脑和iPhone连接在一起。 很多用Windows的小伙伴羡慕macOS可以和iPhone无缝连接&#xff0c;轻松阅读和回…

JDBC/ODBC—数据库连接API概述

JDBC/ODBC概述 在数据库连接领域&#xff0c;有两种广泛使用的技术&#xff1a;ODBC&#xff08;Open Database Connectivity - 开放数据库连接&#xff09;和 JDBC&#xff08;Java Database Connectivity - Java 数据库连接&#xff09;。 一、什么是 ODBC&#xff1f; Ope…

Vagrant使用教程:创建CentOS 8虚拟机

目录 简介准备工作下载配置Vagrant修改环境变量创建VAGRANT_HOME环境变量修改virturalBox新建虚拟机文件的默认生成路径修改Vagrant配置支持VirtualBox7.1.x版本创建Vagrant文件添加镜像 初始化并开机初始化开发环境开机 其他配置项宿主机的交换目录修改虚拟机内存修改 访问方式…

使用Django Channels实现WebSocket实时通信

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 使用Django Channels实现WebSocket实时通信 Django Channels 简介 环境搭建 安装 Django 和 Channels 创建 Django 项目 配置 A…

【JAVA 笔记】11 ch08_opp_intermediate 第8章 面向对象编程(中级部分)

第8章 面向对象编程(中级部分) IDEA 常用快捷键 包 包的三大作用 包基本语法 包的本质分析 包的命名 常用的包 如何引入包 注意事项和使用细节 访问修饰符 基本介绍 访问修饰符的访问范围! 使用的注意事项 面向对象编程三大特征 基本介绍 封装介绍 封装的理解和好处 封装的实现…

面试题:JVM(四)

new对象流程&#xff1f;&#xff08;龙湖地产&#xff09; 对象创建方法&#xff0c;对象的内存分配。&#xff08;360安全&#xff09; 1. 对象实例化 创建对象的方式有几种&#xff1f; 创建对象的步骤 指针碰撞&#xff1a;以指针为分界线&#xff0c;一边是已连续使用的…

无人机螺旋桨动平衡分析测试台

产品简介 Flight Stand系列动力测试台全部支持螺旋桨动平衡分析测试功能&#xff0c;用户仅需几个简单的操作步骤&#xff0c;轻松实现电机和螺旋桨ISO 21940-11:2016标准级的动平衡精度。 功能说明 测试台一体化集成有三坐标振动传感器和转速传感器&#xff0c;通过测量动力…

qt QTextEdit详解

QTextEdit是Qt框架中的一个文本编辑控件类&#xff0c;它提供了丰富的功能用于编辑和显示纯文本以及富文本。 重要方法 setPlainText(const QString &text)&#xff1a;设置纯文本内容。toPlainText()&#xff1a;获取纯文本内容。setHtml(const QString &text)&#…

杂项——USB键盘与鼠标流量分析——BUUCTF——流量分析

第一次做USB键盘与鼠标流量分析的题目&#xff0c;现在来好好做一个总结 1. 基础知识 USB流量指的是USB设备接口的流量&#xff0c;攻击者能够通过监听usb接口流量获取键盘敲击键、鼠标移动与点击、存储设备的铭文传输通信、USB无线网卡网络传输内容等等。 在正式介绍 USB H…

Windows部署rabbitmq

本次安装环境&#xff1a; 系统&#xff1a;Windows 11 软件建议版本&#xff1a; erlang OPT 26.0.2rabbitmq 3.12.4 一、下载 1.1 下载erlang 官网下载地址&#xff1a; 1.2 下载rabbitmq 官网下载地址&#xff1a; 建议使用解压版&#xff0c;安装版可能会在安装软件…

HTML静态网页成品作业(HTML+CSS)——自行车介绍网页设计制作(1个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码CSS部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;未使用Javacsript代码&#xff0c;共有1个页面。 二、作品…

工厂电气及PLC【1章各种元件符号】

交流接触器的线圈通电后&#xff0c;线圈电流会产生磁场&#xff0c;衔铁在磁吸引力作用下带动触点动作&#xff1a;常开的主触点闭合&#xff0c;接通主电路&#xff1b;同时&#xff0c;常开的辅助触点闭合&#xff0c;常闭的辅助触点断开。当线圈失电或电压显著降低时&#…

使用GraphQL构建现代API

使用GraphQL构建现代API GraphQL简介 安装GraphQL 使用npm安装GraphQL 使用Yarn安装GraphQL 创建GraphQL服务器 定义Schema 编写Resolver 查询数据 变更数据 使用Apollo Client GraphQL订阅 数据验证 错误处理 分页查询 拆分和组合Schema 总结 随着API的发展&#xff0c;传统…

用Python设置、更新和获取Excel单元格的值

Excel工作簿作为一款广泛使用的数据管理工具&#xff0c;与Python相结合&#xff0c;可以使得自动化处理大量数据成为可能。通过Python来设置、更新以及读取Excel单元格的值&#xff0c;不仅可以极大地提高工作效率&#xff0c;减少重复劳动&#xff0c;还能增强数据处理流程的…

利用ChatGPT完成2024年MathorCup大数据挑战赛-赛道A初赛:台风预测与分析

利用ChatGPT完成2024年MathorCup大数据挑战赛-赛道A初赛&#xff1a;台风预测与分析 引言 在2024年MathorCup大数据挑战赛中&#xff0c;赛道A聚焦于气象数据分析&#xff0c;特别是台风的生成、路径预测、和降水风速特性等内容。本次比赛的任务主要是建立一个分类评价模型&…