1. Linux EEPROM驱动编译与测试指南
在嵌入式系统和硬件开发中,EEPROM(电可擦可编程只读存储器)是存储配置数据的常见元件。93CX6系列EEPROM因其SPI/I2C接口和稳定的性能被广泛使用。本文将详细介绍如何在Linux环境下编译、安装和测试EEPROM驱动模块。
1.1 准备工作与环境配置
首先需要准备Linux内核源码和编译环境。推荐使用与当前运行系统相同版本的内核源码,可以减少兼容性问题。以下是具体步骤:
code复制# 安装编译依赖
sudo apt-get install -y build-essential bc flex bison libssl-dev libelf-dev \
pkg-config libncurses-dev linux-source-6.1
# 安装当前内核的头文件
sudo apt-get install -y linux-headers-$(uname -r)
获取内核源码有两种方式:
- 通过apt获取发行版提供的源码包
- 直接从kernel.org或GitHub克隆
对于EEPROM开发,建议使用发行版提供的源码,因为已经包含针对该发行版的补丁和配置。使用以下命令获取:
code复制apt install linux-source-6.1
cd /usr/src
tar xf linux-source-6.1.tar.xz
cd linux-source-6.1
1.2 内核配置与模块准备
在编译特定模块前,需要正确配置内核。使用当前系统的配置作为基础是最稳妥的方式:
code复制# 复制当前内核配置
cp -v /boot/config-$(uname -r) .config
# 准备模块编译环境
make modules_prepare
# 更新配置
yes "" | make oldconfig
对于EEPROM驱动,需要确保以下配置项启用:
- CONFIG_EEPROM_93CX6=m (模块方式编译)
- CONFIG_I2C=y (如果使用I2C接口)
- CONFIG_SPI=y (如果使用SPI接口)
可以使用menuconfig界面进行配置:
code复制make menuconfig
在界面中,导航到 Device Drivers > Misc devices > EEPROM support,选择93CX6系列EEPROM支持。
2. 驱动模块编译与安装
2.1 选择性编译EEPROM驱动
Linux内核采用Kbuild系统,支持单独编译特定目录下的模块。对于EEPROM驱动,位于drivers/misc/eeprom/目录下。
code复制# 仅编译EEPROM驱动模块
make -j$(nproc) M=drivers/misc/eeprom modules
编译完成后,生成的.ko文件位于drivers/misc/eeprom/目录下。常见的EEPROM驱动模块包括:
- eeprom_93cx6.ko
- at24.ko (I2C EEPROM通用驱动)
- at25.ko (SPI EEPROM通用驱动)
2.2 模块安装与设备节点创建
编译完成后,需要将模块安装到系统目录:
code复制# 查找模块文件
find /usr/src -name eeprom_93cx6.ko
# 安装模块
sudo insmod /path/to/eeprom_93cx6.ko
驱动加载后,通常会自动创建设备节点。检查设备文件:
code复制ls -l /dev/misc* /dev/eeprom*
如果没有自动创建,需要手动创建设备节点:
code复制# 查看驱动注册信息获取主次设备号
dmesg | grep eeprom
# 手动创建设备节点
sudo mknod /dev/eeprom93cx6 c 10 239
注意:主设备号(major)和次设备号(minor)因系统而异,请根据实际dmesg输出调整。
3. EEPROM测试与验证
3.1 基本功能测试
确认驱动加载成功:
code复制lsmod | grep eeprom
简单读写测试:
code复制# 读取256字节数据
sudo dd if=/dev/eeprom93cx6 of=eeprom_dump.bin bs=1 count=256
# 写入测试(谨慎操作)
sudo dd if=eeprom_dump.bin of=/dev/eeprom93cx6 bs=1 count=256
3.2 编写测试模块
对于更复杂的测试,可以编写内核模块直接与EEPROM交互。以下是测试模块示例:
c复制#include <linux/module.h>
#include <linux/i2c.h>
#define EEPROM_I2C_ADDR 0x50
static struct i2c_client *eeprom_client;
static void eeprom_test_read(void) {
u8 buf[8];
struct i2c_adapter *adapter = i2c_get_adapter(0);
eeprom_client = i2c_new_dummy_device(adapter, EEPROM_I2C_ADDR);
i2c_master_recv(eeprom_client, buf, sizeof(buf));
printk("EEPROM data: %02x %02x %02x %02x\n",
buf[0], buf[1], buf[2], buf[3]);
i2c_unregister_device(eeprom_client);
i2c_put_adapter(adapter);
}
static int __init test_init(void) {
eeprom_test_read();
return 0;
}
module_init(test_init);
MODULE_LICENSE("GPL");
对应的Makefile:
makefile复制obj-m := eeprom_test.o
KDIR := /usr/src/linux-source-6.1
PWD := $(shell pwd)
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
3.3 使用I2C工具测试
对于I2C接口的EEPROM,可以使用i2c-tools进行测试:
code复制sudo apt install i2c-tools
# 扫描I2C总线
i2cdetect -y 1
# 读取EEPROM数据
i2cget -y 1 0x50 0x00
i2cdump -y 1 0x50
4. 常见问题与解决方法
4.1 模块加载失败
问题现象:
code复制insmod: ERROR: could not insert module eeprom_93cx6.ko: Invalid parameters
解决方法:
- 检查内核版本匹配性
- 确认.config中相关配置已启用
- 运行
depmod -a更新模块依赖
4.2 设备节点权限问题
问题现象:
code复制dd: failed to open '/dev/eeprom93cx6': Permission denied
解决方法:
code复制sudo chmod 666 /dev/eeprom93cx6
或者创建udev规则自动设置权限。
4.3 I2C通信失败
问题现象:
code复制i2cget: Error: Read failed
解决方法:
- 确认I2C总线号正确
- 检查EEPROM地址(通常0x50-0x57)
- 确认硬件连接正常
- 检查i2c驱动是否加载(lsmod | grep i2c)
5. 高级应用与调试技巧
5.1 使用动态调试打印
在驱动开发阶段,可以启用动态调试:
code复制# 启用EEPROM驱动的调试信息
echo "file drivers/misc/eeprom/* +p" > /sys/kernel/debug/dynamic_debug/control
# 查看调试输出
dmesg -w
5.2 修改驱动参数
许多EEPROM驱动支持模块参数,加载时可以指定:
code复制# 查看可用参数
modinfo eeprom_93cx6
# 带参数加载模块
sudo insmod eeprom_93cx6.ko poll_interval=100
5.3 性能优化
对于频繁访问的EEPROM,可以考虑:
- 增加读写缓存
- 实现延迟写入
- 使用原子操作替代完整擦除
例如在驱动中添加:
c复制static u8 cache[256];
static bool dirty;
static ssize_t eeprom_write(struct file *filp, const char __user *buf,
size_t count, loff_t *ppos) {
// 先写入缓存
copy_from_user(cache + *ppos, buf, count);
dirty = true;
// 延迟5秒后实际写入EEPROM
schedule_delayed_work(&eeprom_work, 5 * HZ);
return count;
}
6. 实际应用案例
6.1 在嵌入式系统中的应用
在树莓派等嵌入式平台上使用EEPROM存储配置:
- 连接93CX6 EEPROM到SPI或I2C接口
- 编译并加载驱动
- 创建初始化脚本:
bash复制#!/bin/bash
# 加载EEPROM驱动
modprobe eeprom_93cx6
# 创建设备节点
mknod /dev/eeprom c 10 239
# 设置权限
chmod 666 /dev/eeprom
# 初始化默认配置
echo "default_config" > /dev/eeprom
6.2 与用户空间程序的交互
开发用户空间程序通过设备文件访问EEPROM:
c复制#include <fcntl.h>
#include <unistd.h>
int main() {
int fd = open("/dev/eeprom93cx6", O_RDWR);
char buf[256];
read(fd, buf, sizeof(buf));
// 处理数据...
write(fd, "new_data", 8);
close(fd);
return 0;
}
7. 驱动开发注意事项
- 并发控制:EEPROM操作需要加锁防止并发访问
- 错误处理:妥善处理I2C/SPI通信错误
- 电源管理:实现suspend/resume回调
- 延时处理:EEPROM写入需要时间,操作间应添加适当延迟
示例锁实现:
c复制static DEFINE_MUTEX(eeprom_lock);
static ssize_t eeprom_read(struct file *filp, char __user *buf,
size_t count, loff_t *ppos) {
mutex_lock(&eeprom_lock);
// 读操作...
mutex_unlock(&eeprom_lock);
return count;
}
8. 不同EEPROM型号的适配
93CX6系列有多个变种,主要区别在于:
- 容量:93C46(1K), 93C56(2K), 93C66(4K)
- 接口:SPI或Microwire
- 组织方式:8位或16位
在驱动中需要根据具体型号调整:
c复制struct eeprom_93cx6 {
u8 reg_width; // 8或16位
u16 size; // 容量
u8 opcode_width; // 指令长度
// ...
};
static void eeprom_93cx6_setup(struct eeprom_93cx6 *eeprom,
struct device *dev) {
if (strstr(dev->name, "93C46")) {
eeprom->size = 1024;
eeprom->reg_width = 8;
} else if (strstr(dev->name, "93C56")) {
eeprom->size = 2048;
eeprom->reg_width = 16;
}
// ...
}
9. 系统集成与自动化
将EEPROM驱动集成到系统镜像中:
- 修改内核配置,将EEPROM驱动编译为内置模块(=y)
- 创建udev规则自动创建设备节点:
code复制# /etc/udev/rules.d/99-eeprom.rules
KERNEL=="eeprom[0-9]*", MODE="0666"
-
添加initramfs脚本自动加载驱动
-
配置systemd服务管理EEPROM数据:
code复制[Unit]
Description=EEPROM Data Manager
[Service]
ExecStart=/usr/bin/eeprom_manager
Restart=always
[Install]
WantedBy=multi-user.target
10. 调试与性能分析
10.1 使用ftrace跟踪驱动
code复制# 启用函数跟踪
echo function > /sys/kernel/debug/tracing/current_tracer
echo eeprom_93cx6_* > /sys/kernel/debug/tracing/set_ftrace_filter
# 开始记录
echo 1 > /sys/kernel/debug/tracing/tracing_on
# 执行测试操作后查看结果
cat /sys/kernel/debug/tracing/trace
10.2 性能基准测试
使用dd命令测试读写速度:
code复制# 测试读取速度
dd if=/dev/eeprom93cx6 of=/dev/null bs=1k count=100
# 测试写入速度
dd if=/dev/zero of=/dev/eeprom93cx6 bs=1k count=100
对于I2C EEPROM,典型性能:
- 读取速度:100-400 kHz
- 写入速度:每页(通常32/64字节)5-10ms
11. 安全注意事项
- 访问控制:限制/dev/eeprom*的访问权限
- 写保护:硬件上使用WP引脚防止意外写入
- 数据校验:添加CRC校验防止数据损坏
- 加密存储:对敏感数据加密后再存储
实现示例:
c复制static int eeprom_write_secure(struct file *filp, const char __user *buf,
size_t count, loff_t *ppos) {
char encrypted[count];
encrypt_data(buf, encrypted, count); // 自定义加密函数
return raw_write(filp, encrypted, count, ppos);
}
12. 跨平台兼容性处理
不同架构可能需要特殊处理:
c复制#if defined(__ARM_ARCH)
// ARM平台的特殊处理
#define EEPROM_DELAY_US 50
#elif defined(__x86_64__)
// x86平台的特殊处理
#define EEPROM_DELAY_US 10
#endif
static void eeprom_wait_ready(void) {
udelay(EEPROM_DELAY_US);
}
对于设备树(DTS)支持的平台,应该在dts文件中定义EEPROM节点:
code复制eeprom: eeprom@50 {
compatible = "microchip,93cx6";
reg = <0x50>;
spi-max-frequency = <1000000>;
pagesize = <32>;
};
13. 驱动卸载与资源清理
完善的驱动应该正确处理资源释放:
c复制static void __exit eeprom_93cx6_exit(void) {
misc_deregister(&eeprom_miscdev);
i2c_del_driver(&eeprom_i2c_driver);
unregister_chrdev(eeprom_major, "eeprom");
kfree(eeprom_buffer);
}
module_exit(eeprom_93cx6_exit);
14. 用户空间工具开发
开发配套用户空间工具便于管理:
c复制// eeprom-tool.c
int main(int argc, char **argv) {
if (argc < 3) {
printf("Usage: %s <read|write> <offset> [data]\n", argv[0]);
return 1;
}
int fd = open("/dev/eeprom0", O_RDWR);
lseek(fd, atoi(argv[2]), SEEK_SET);
if (strcmp(argv[1], "read") == 0) {
char buf[256];
read(fd, buf, sizeof(buf));
printf("Data: %s\n", buf);
} else if (strcmp(argv[1], "write") == 0 && argc > 3) {
write(fd, argv[3], strlen(argv[3]));
}
close(fd);
return 0;
}
编译并安装:
code复制gcc eeprom-tool.c -o eeprom-tool
sudo cp eeprom-tool /usr/local/bin
15. 驱动持续维护建议
- 版本兼容:维护MODULE_VERSION宏
- 变更日志:维护详细的代码变更记录
- 文档注释:使用kernel-doc格式注释
- 测试用例:维护回归测试套件
示例kernel-doc注释:
c复制/**
* eeprom_93cx6_read - Read data from EEPROM
* @eeprom: EEPROM device structure
* @buf: Buffer to store read data
* @count: Number of bytes to read
*
* This function reads data from the EEPROM chip. The caller must hold
* the eeprom->lock mutex.
*
* Return: Number of bytes read, or negative error code.
*/
static int eeprom_93cx6_read(struct eeprom_93cx6 *eeprom,
u8 *buf, size_t count) {
// ...
}
通过以上完整的开发流程,可以构建稳定可靠的EEPROM驱动支持。在实际项目中,建议根据具体硬件特性和应用场景调整实现细节。
