两种访问eeprom的例子
在Linux应用层访问eeprom的方法,并给出示例代码方便大家理解。第一个方法是通过eeprom的设备文件进行访问。这两个方法分别对应了i2c设备驱动的两个不同的实现,第二个方法是通过sysfs文件系统对eeprom进行访问。
第一种通过devfs访问eeprom的方法是linux i2c提供的一种通用的方法,是有i2c子系统提供的,访问设备的能力有限,通过devfs访问eeprom的方法则需要了解eeprom的读写时序。对写应用程序开发人员的要求较高。
第二种通过sysfs文件系统的二进制结点访问eeprom的方法是由eeprom的设备驱动实现的,是一种专有的方法。大体来讲就是关于时序的操作已经由驱动程序完成了,并以bin_attribute的方式开放到用户空间,应用程序开发人员只按文件操作的方式操作设备;通过sysfs文件系统访问eeprom操作简单,无需了解eeprom的硬件特性以及访问时序。
通过devfs访问I2C设备
介绍这个之前,先来看一下:i2c-dev.c 文件完全可以被看作一个 I2C 设备驱动,它实现的一个 i2c_client 是虚拟、临时的,随着设备文件的打开而产生,并随设备文件的关闭而撤销,并没有被添加到 i2c_adapter 的 clients
链表中。i2c-dev.c 针对每个 I2C 适配器生成一个主设备号为 89 的设备文件,实现了i2c_driver 的成员函数以及文件操作接口,所以 i2c-dev.c 的主体是“i2c_driver 成员函数 + 字符设备驱动” 。
i2c-dev.c 中提供 i2cdev_read()、i2cdev_write()函数来对应用户空间要使用的 read()和 write()文件操作接口,这两个函数分别调用 I2C 核心的 i2c_master_recv()和i2c_master_send()函数来构造一条 I2C 消息并引发适配器 algorithm 通信函数的调用,完成消息的传输,对应于如图 15.7 所示的时序。但是,很遗憾,大多数稍微复杂一点I2C 设备的读写流程并不对应于一条消息,往往需要两条甚至更多的消息来进行一次读写周期,这种情况下,在应用层仍然调用 read()、write()文件 API 来读写 I2C 设备,将不能正确地读写。许多工程师碰到过类似的问题,往往经过相当长时间的调试都没法解决 I2C 设备的读写,连错误的原因也无法找到,显然是对 i2cdev_read()和 i2cdev_write()函数的作用有所误解。
鉴于上述原因,i2c-dev.c 中 i2cdev_read()和 i2cdev_write()函数不具备太强的通用性,没有太大的实用价值,只能适用于非 RepStart 模式的情况。对于两条以上消息组成的读写,在用户空间需要组织 i2c_msg 消息数组并调用 I2C_RDWR IOCTL 命令。代码清单 15.21 所示为 i2cdev_ioctl()函数的框架,其中详细列出了 I2C_RDWR 命令的处理过程。
#include <stdio.h>
#include <linux/types.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <errno.h>
#define I2C_RETRIES 0x0701
#define I2C_TIMEOUT 0x0702
#define I2C_RDWR 0x0707
#define I2C_ADDR 0x50
struct i2c_msg
{
unsigned short addr;
unsigned short flags;
#define I2C_M_TEN 0x0010
#define I2C_M_RD 0x0001
unsigned short len;
unsigned char *buf;
};
struct i2c_rdwr_ioctl_data
{
struct i2c_msg *msgs;
int nmsgs;
};
/***********main***********/
int main(){
int fd,ret;
struct i2c_rdwr_ioctl_data e2prom_data;
//disable WP
system("echo 103 > /sys/class/gpio/export");
system("echo \"out\" > /sys/class/gpio/gpio103/direction");
system("echo 0 > /sys/class/gpio/gpio103/value");
fd=open("/dev/i2c-0",O_RDWR);//打开eeprom设备文件结点
if(fd<0){
perror("open error");
}
e2prom_data.nmsgs=2;
e2prom_data.msgs=(struct i2c_msg*)malloc(e2prom_data.nmsgs*sizeof(struct i2c_msg));
if(!e2prom_data.msgs)
{
perror("malloc error");
exit(1);
}
ioctl(fd,I2C_TIMEOUT,1);/*超时时间*/
ioctl(fd,I2C_RETRIES,2);/*重复次数*/
/***write data to e2prom**/
e2prom_data.nmsgs=1;//由前面eeprom读写分析可知,写eeprom需要一条消息
(e2prom_data.msgs[0]).len=5;//此消息的长度为5个字节,第一,二个字节是要写入数据的地址,第三,四,五,字节是要写入的数据
(e2prom_data.msgs[0]).addr=I2C_ADDR;//e2prom 设备地址
(e2prom_data.msgs[0]).flags=0; //write
(e2prom_data.msgs[0]).buf=(unsigned char*)malloc(5);
(e2prom_data.msgs[0]).buf[0]=0x00;// e2prom addr[15:8]
(e2prom_data.msgs[0]).buf[1]=0x00;//e2prom addr[7:0]
(e2prom_data.msgs[0]).buf[2]=0x55;//the data to write byte0
(e2prom_data.msgs[0]).buf[3]=0x66;//the data to write byte1
(e2prom_data.msgs[0]).buf[4]=0x77;//the data to write byte2
ret=ioctl(fd,I2C_RDWR,(unsigned long)&e2prom_data);//通过ioctl进行实际写入操作
if(ret<0)
{
perror("ioctl error1");
}
sleep(1);
/******read data from e2prom*******/
e2prom_data.nmsgs=2;//读eeprom需要两条消息
(e2prom_data.msgs[0]).len=1;//第一条消息实际是写eeprom,需要告诉eeprom需要读数据的地址,因此长度为1个字节
(e2prom_data.msgs[0]).addr=I2C_ADDR; // e2prom 设备地址
(e2prom_data.msgs[0]).flags=0;//write
(e2prom_data.msgs[0]).buf=(unsigned char*)malloc(2);
(e2prom_data.msgs[0]).buf[0]=0x00;// e2prom addr[15:8]
(e2prom_data.msgs[0]).buf[1]=0x00;//e2prom addr[7:0]
(e2prom_data.msgs[1]).len=6;
(e2prom_data.msgs[1]).addr=I2C_ADDR;
(e2prom_data.msgs[1]).flags=I2C_M_RD;
(e2prom_data.msgs[1]).buf=(unsigned char*)malloc(6);
(e2prom_data.msgs[1]).buf[0]=0;
(e2prom_data.msgs[1]).buf[1]=0;
(e2prom_data.msgs[1]).buf[2]=0;
(e2prom_data.msgs[1]).buf[3]=0;
(e2prom_data.msgs[1]).buf[4]=0;
(e2prom_data.msgs[1]).buf[5]=0;
ret=ioctl(fd,I2C_RDWR,(unsigned long)&e2prom_data);//通过ioctl进行实际的读操作
if(ret<0)
{
perror("ioctl error2");
}
printf("Read back:0x%02X\n\r",(e2prom_data.msgs[1]).buf[0]);
printf("Read back:0x%02X\n\r",(e2prom_data.msgs[1]).buf[1]);
printf("Read back:0x%02X\n\r",(e2prom_data.msgs[1]).buf[2]);
close(fd);
system("echo 103 > /sys/class/gpio/unexport");
return 0;
}
通过 通过sysfs文件系统访问I2C设备
eeprom的设备驱动在/sys/devices/44000000.ocp/44e0b000.i2c/i2c-0/0-0050/eeprom目录下把eeprom设备映射为一个二进制节点,文件名为eeprom。对这个eeprom文件的读写就是对eeprom进行读写。
#define EEPROM_DEVICE "/sys/devices/44000000.ocp/44e0b000.i2c/i2c-0/0-0050/eeprom"
#define TEST_STR "eeprom write/read test!"
int main(void)
{
int fd;
struct stat eeprom_stat;
char read_buf[32] = { '\0' };
fd = open(EEPROM_DEVICE, O_RDWR);
if (fd < 0) {
printf("open eeprom unsuccess\n");
return;
}
if (stat(EEPROM_DEVICE, &eeprom_stat) < 0) {
perror("failed to get eeprom status");
} else {
printf("\neeprom size: %d KB\n\n", eeprom_stat.st_size/1024);
}
printf("write \'%s\' to eeprom\n", TEST_STR);
if (write(fd, TEST_STR, strlen(TEST_STR)) < 0) {
perror("write to eeprom failed\n");
return;
}
lseek(fd, 0, SEEK_SET);
if (read(fd, read_buf, strlen(TEST_STR)) < 0) {
printf("read back failed\n");
return;
}
printf("\nget the following string from eeprom:\n%s\n\n", read_buf);
return 0;
}
eeprom驱动分析
在drivers/misc/eeprom/at24.c中可以看到eeprom驱动对i2c_driver结构的实例化。
static struct i2c_driver at24_driver = {
.driver = {
.name = "at24",
.owner = THIS_MODULE,
},
.probe = at24_probe,
.remove = at24_remove,
.id_table = at24_ids,
};
其中probe和remove会在模块初始化和卸载的时候被调用。
static int __init at24_init(void)
{
if (!io_limit) {
pr_err("at24: io_limit must not be 0!\n");
return -EINVAL;
}
io_limit = rounddown_pow_of_two(io_limit);
return i2c_add_driver(&at24_driver);
}
module_init(at24_init);
static void __exit at24_exit(void)
{
i2c_del_driver(&at24_driver);
}
at24_probe()
static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct at24_platform_data chip;
bool writable;
int use_smbus = 0;
struct at24_data *at24;
int err;
unsigned i, num_addresses;
kernel_ulong_t magic;
//获取板级设备信息
if (client->dev.platform_data) {
chip = *(struct at24_platform_data *)client->dev.platform_data;
} else {
if (!id->driver_data) {
err = -ENODEV;
goto err_out;
}
magic = id->driver_data;
chip.byte_len = BIT(magic & AT24_BITMASK(AT24_SIZE_BYTELEN));
magic >>= AT24_SIZE_BYTELEN;
chip.flags = magic & AT24_BITMASK(AT24_SIZE_FLAGS);
/*
* This is slow, but we can't know all eeproms, so we better
* play safe. Specifying custom eeprom-types via platform_data
* is recommended anyhow.
*/
chip.page_size = 1;
chip.setup = NULL;
chip.context = NULL;
}
//检查参数,必须为2的幂
if (!is_power_of_2(chip.byte_len))
dev_warn(&client->dev,
"byte_len looks suspicious (no power of 2)!\n");
if (!is_power_of_2(chip.page_size))
dev_warn(&client->dev,
"page_size looks suspicious (no power of 2)!\n");
/* Use I2C operations unless we're stuck with SMBus extensions. */
//检查是否支持I2C协议,如果不支持则检查是否支持SMBUS
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
if (chip.flags & AT24_FLAG_ADDR16) {
err = -EPFNOSUPPORT;
goto err_out;
}
if (i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_READ_I2C_BLOCK)) {
use_smbus = I2C_SMBUS_I2C_BLOCK_DATA;
} else if (i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_READ_WORD_DATA)) {
use_smbus = I2C_SMBUS_WORD_DATA;
} else if (i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_READ_BYTE_DATA)) {
use_smbus = I2C_SMBUS_BYTE_DATA;
} else {
err = -EPFNOSUPPORT;
goto err_out;
}
}
if (chip.flags & AT24_FLAG_TAKE8ADDR)//检查时候使用8个地址
num_addresses = 8;
else
num_addresses = DIV_ROUND_UP(chip.byte_len,//AT24C01使用一个地址
(chip.flags & AT24_FLAG_ADDR16) ? 65536 : 256);
at24 = kzalloc(sizeof(struct at24_data) +
num_addresses * sizeof(struct i2c_client *), GFP_KERNEL);//为at24_data分配内存,同时根据地址个数分配i2c_client
if (!at24) {
err = -ENOMEM;
goto err_out;
}
mutex_init(&at24->lock);
//初始化at24_data,也就是填充此结构体
at24->use_smbus = use_smbus;
at24->chip = chip;
at24->num_addresses = num_addresses;
/*
* Export the EEPROM bytes through sysfs, since that's convenient.
* By default, only root should see the data (maybe passwords etc)
*/
//以二进制结点的形式呈现eeprom的数据
sysfs_bin_attr_init(&at24->bin);
at24->bin.attr.name = "eeprom";//结点名字
at24->bin.attr.mode = chip.flags & AT24_FLAG_IRUGO ? S_IRUGO : S_IRUSR;
at24->bin.read = at24_bin_read;//绑定读函数
at24->bin.size = chip.byte_len;
at24->macc.read = at24_macc_read;
//判断是否可写
writable = !(chip.flags & AT24_FLAG_READONLY);
if (writable) {//如果可写
if (!use_smbus || i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_WRITE_I2C_BLOCK)) {
unsigned write_max = chip.page_size;
at24->macc.write = at24_macc_write;
at24->bin.write = at24_bin_write;//绑定写函数
at24->bin.attr.mode |= S_IWUSR;//文件拥有者可写
if (write_max > io_limit)//一次最多写io_limit个字节
write_max = io_limit;
if (use_smbus && write_max > I2C_SMBUS_BLOCK_MAX)
write_max = I2C_SMBUS_BLOCK_MAX;
at24->write_max = write_max;
/* buffer (data + address at the beginning) */
at24->writebuf = kmalloc(write_max + 2, GFP_KERNEL);//分配缓冲区,多余两个字节用于保存寄存器地址
if (!at24->writebuf) {
err = -ENOMEM;
goto err_struct;
}
} else {
dev_warn(&client->dev,
"cannot write due to controller restrictions.");
}
}
at24->client[0] = client;
/* use dummy devices for multiple-address chips */
for (i = 1; i < num_addresses; i++) {
at24->client[i] = i2c_new_dummy(client->adapter,
client->addr + i);
if (!at24->client[i]) {
dev_err(&client->dev, "address 0x%02x unavailable\n",
client->addr + i);
err = -EADDRINUSE;
goto err_clients;
}
}
//向sysfs文件系统注册二进制结点
err = sysfs_create_bin_file(&client->dev.kobj, &at24->bin);
if (err)
goto err_clients;
//保存驱动数据
i2c_set_clientdata(client, at24);
dev_info(&client->dev, "%zu byte %s EEPROM %s\n",
at24->bin.size, client->name,
writable ? "(writable)" : "(read-only)");
if (use_smbus == I2C_SMBUS_WORD_DATA ||
use_smbus == I2C_SMBUS_BYTE_DATA) {
dev_notice(&client->dev, "Falling back to %s reads, "
"performance will suffer\n", use_smbus ==
I2C_SMBUS_WORD_DATA ? "word" : "byte");
}
dev_dbg(&client->dev,
"page_size %d, num_addresses %d, write_max %d, use_smbus %d\n",
chip.page_size, num_addresses,
at24->write_max, use_smbus);
/* export data to kernel code */
if (chip.setup)
chip.setup(&at24->macc, chip.context);
return 0;
err_clients:
for (i = 1; i < num_addresses; i++)
if (at24->client[i])
i2c_unregister_device(at24->client[i]);
kfree(at24->writebuf);
err_struct:
kfree(at24);
err_out:
dev_dbg(&client->dev, "probe error %d\n", err);
return err;
}
如果你正在开发的设备驱动程序中需要与用户层的接口,一般可选的方法有:
注册虚拟的字符设备文件,以这个虚拟设备上的 read/write/ioctl 等接口与用户交互;但 read/write 一般只能做一件事情, ioctl 可以根据 cmd 参数做多个功能(使用i2c-dev.c中的函数就是这个道理),但其缺点是很明显的: ioctl 接口无法直接在 Shell 脚本中使用,为了使用 ioctl 的功能,还必须编写配套的 C语言的虚拟设备操作程序, ioctl 的二进制数据接口也是造成大小端问题 (big endian与little endian)、32位/64位不可移植问题的根源;
注册 proc 接口,接受用户的 read/write/ioctl 操作;同样的,一个 proc 项通常使用其 read/write/ioctl 接口,它所存在的问题与上面的虚拟字符设备的的问题相似;
注册 sysfs 属性;
最重要的是,添加虚拟字符设备支持和注册 proc 接口支持这两者所需要增加的代码量都并不少,最好的方法还是使用 sysfs 属性支持,一切在用户层是可见的透明,且增加的代码量是最少的,可维护性也最好;
at24_probe()函数主要的工作是在sys目录在创建bin结点文件,也就是前面通过sysfs文件系统访问i2c设备中提到的/sys/bus/i2c/devices/0-0050/eeprom文件,用户可以用此文件来操作eeprom,提供读/写操作方法,在probe里面读写操作已经与二进制结点绑定,读操作函数是at24_bin_read(),写操作函数是at24_bin_write()。
其中有个重要的结构体:
struct at24_data {
struct at24_platform_data chip;
struct memory_accessor macc;
int use_smbus;
/*
* Lock protects against activities from other Linux tasks,
* but not from changes by other I2C masters.
*/
struct mutex lock;
struct bin_attribute bin;//二进制结点
u8 *writebuf;//写缓冲区
unsigned write_max;
unsigned num_addresses;
/*
* Some chips tie up multiple I2C addresses; dummy devices reserve
* them for us, and we'll use them with SMBus calls.
*/
struct i2c_client *client[];
};
at24_data是此驱动的一些私有数据的封装,包括二进制结点,以及写缓冲区。
而对at24_bin_read(),at24_bin_write()的进一步追踪可知:at24_bin_read()—->at24_read—->at24_eeprom_read;at24_bin_write—->at24_write—->at24_eeprom_write—->2c_transfer i2c_transfer