当前位置:首页>Linux>Linux 底层读写函数解析

Linux 底层读写函数解析

  • 2026-10-21 13:27:45
Linux 底层读写函数解析
大家好,我是王鸽,今天总结一下底层驱动这块读写函数代码总结,有个大概的认识,驱动和硬件的交互,最后都要回归到寄存器的读写操作上,所以对驱动的读写函数了解还是很必要的。

那就开始介绍几种读写。

内存映射I/O (MMIO)

Linux 设备驱动中最基础的硬件交互方式,用于直接访问CPU内存空间的寄存器(如片上外设),提供了最高效的硬件控制手段。

// 读操作u8 readb(constvolatilevoid __iomem *addr);  // 8位u16 readw(constvolatilevoid __iomem *addr); // 16位u32 readl(constvolatilevoid __iomem *addr); // 32位// 写操作voidwriteb(u8 value, volatilevoid __iomem *addr);voidwritew(u16 value, volatilevoid __iomem *addr);voidwritel(u32 value, volatilevoid __iomem *addr);

底层实现:

通过ioremap()将物理地址映射到内核虚拟地址。

底层使用汇编指令(如ARM的ldr/str,x86的mov)直接操作内存。

包含内存屏障(如__iormb()/__iowmb())保证访问顺序。

具体的例子

void writeb(u8 value, volatile void __iomem *addr);

功能

向指定内存映射 I/O 地址写入 1 字节(8位) 数据。

参数

参数

类型

说明

value

u8 (unsigned char)

要写入的 8 位数据

addr

volatile void __iomem *

内存映射 I/O 的目标地址

关键特性

volatile:告诉编译器不要优化此操作(硬件寄存器值可能随时改变)

__iomem:表示这是 I/O 内存地址(非普通内存)

原子操作:保证单次写入不可分割

无返回值:只执行写入操作

代码示例

// 启用设备中断 (设置寄存器的第 0 位)void enable_interrupts(struct device *dev){    u8 ctrl_reg;    // 1. 读取当前控制寄存器值    ctrl_reg = readb(dev->regs + CONTROL_REG_OFFSET);    // 2. 设置中断使能位 (BIT0)    ctrl_reg |= 0x01;    // 3. 写回控制寄存器    writeb(ctrl_reg, dev->regs + CONTROL_REG_OFFSET);}// 发送命令到设备void send_device_command(struct device *dev, u8 cmd){    // 直接写入命令寄存器    writeb(cmd, dev->regs + COMMAND_REG_OFFSET);}

而u16 readw(const volatile void __iomem *addr); // 16位使用

// 读取设备状态寄存器u16 get_device_status(struct device *dev){    // 直接读取16位状态寄存器    return readw(dev->regs + STATUS_REG_OFFSET);}// 从数据寄存器读取温度值intread_temperature(struct device *dev){    u16 raw_value;    // 1. 启动温度转换    writeb(START_CONV_CMD, dev->regs + COMMAND_REG);    // 2. 等待转换完成    while (!(readw(dev->regs + STATUS_REG) & CONV_COMPLETE_BIT))        udelay(10);    // 3. 读取16位温度值    raw_value = readw(dev->regs + DATA_REG);    return convert_raw_temp(raw_value);}

另外读取出来的数据需要考虑大小端转化,字节序处理。

u16 val = readw(addr);u16 host_val = le16_to_cpu(val); // 小端设备转主机序

位操作

// 安全修改特定位u8 reg = readb(ctrl_reg);reg |= BIT(3);    // 设置第3位reg &= ~BIT(5);   // 清除第5位writeb(reg, ctrl_reg);

I2C总线读写

通过I2C协议读写从设备寄存器。

函数

// 单次读写inti2c_master_send(struct i2c_client *client, constchar *buf, int count);inti2c_master_recv(struct i2c_client *client, char *buf, int count);// SMBus封装(推荐)s32 i2c_smbus_read_byte_data(struct i2c_client *client, u8 reg);s32 i2c_smbus_write_byte_data(struct i2c_client *client, u8 reg, u8 value);s32 i2c_smbus_read_word_data(struct i2c_client *client, u8 reg);

SMBus 是基于 I2C 的变种协议,常用于主板上的低速设备管理(如温度传感器、电源管理等)。SMBus 是 I2C 的子集,有更严格的时序要求,所有 SMBus 设备兼容 I2C,但反之不一定成立。SMBus 统一不同 I2C 控制器的访问方式。

I2C总线底层的实现

i2c_smbus_read_byte_data(const struct i2c_client *client, u8 command)函数

s32 i2c_smbus_read_byte_data(conststruct i2c_client *client, u8 command){ union i2c_smbus_data data; int status; status = i2c_smbus_xfer(client->adapter, client->addr, client->flags, I2C_SMBUS_READ, command, I2C_SMBUS_BYTE_DATA, &data); return (status < 0) ? status : data.byte;}EXPORT_SYMBOL(i2c_smbus_read_byte_data);

client:指向 I2C 设备的结构体指针,command:要读取的寄存器地址或命令码。

I2C_SMBUS_READ:指定读操作
I2C_SMBUS_BYTE_DATA:指定传输类型
&data:用于存储读取结果

其中i2c_smbus_xfer函数是这样子的:

i2c_smbus_read_byte_data工作流程

使用场景

// 读取温度传感器(地址0x48)的温度寄存器(命令码0x00)struct i2c_client *client = ...; u8 temp_reg = 0x00;int temperature = i2c_smbus_read_byte_data(client, temp_reg);if (temperature < 0) {    dev_err(&client->dev, "读取失败: %d\n", temperature);} else {    dev_info(&client->dev, "当前温度: %d°C\n", temperature);}

s32 i2c_smbus_write_byte_data函数

s32 i2c_smbus_write_byte_data(const struct i2c_client *client, u8 command,      u8 value){ union i2c_smbus_data data; data.byte = value; return i2c_smbus_xfer(client->adapter, client->addr, client->flags,      I2C_SMBUS_WRITE, command,      I2C_SMBUS_BYTE_DATA, &data);}EXPORT_SYMBOL(i2c_smbus_write_byte_data);

I2C_SMBUS_BYTE_DATA这个是参数类型

传输类型对照:

函数类型
协议
I2C_SMBUS_BYTE_DATA
Write Byte + Read Byte
I2C_SMBUS_WORD_DATA
Write Byte + Read Word
I2C_SMBUS_BLOCK_DATA
Write Byte + Read Block

错误码示例

-EIO:总线通信错误

-ENODEV:设备无响应

-EAGAIN:临时错误(可重试)

status = i2c_smbus_xfer(client->adapter, client->addr, client->flags,                              I2C_SMBUS_READ, command,                                I2C_SMBUS_WORD_DATA, &data);

SPI总线读写

通过SPI协议读写从设备寄存器。

// 单次传输intspi_write(struct spi_device *spi, constvoid *buf, size_t len);intspi_read(struct spi_device *spi, void *buf, size_t len);// 先写后读(常用)intspi_write_then_read(struct spi_device *spi, constvoid *txbuf, size_t n_tx, void *rxbuf, size_t n_rx);

SPI总线读写底层实现

介绍一下SPI的传输结构体

spi_write函数是 Linux 内核中用于同步 SPI 数据写入的标准实现。它是一个简化封装函数,用于通过 SPI 总线发送数据。spi_write向指定的 SPI 设备同步写入数据(阻塞式操作,直到传输完成)

staticinlineintspi_write(struct spi_device *spi, constvoid *buf, size_t len){ struct spi_transfer t = {  //描述单个 SPI 传输操作的结构体 .tx_buf = buf, // 发送数据缓冲区 .len = len, // 发送数据长度 }; struct spi_message m; spi_message_init(&m); // 初始化 SPI 消息 spi_message_add_tail(&t, &m); // 将传输加入消息队列 return spi_sync(spi, &m); //阻塞式函数,会等待整个传输完成 0 表示成功,负数表示错误码       //实际驱动中应检查 spi_sync() 的错误码,根据错误码判断问题。}

具体的工作流程

同样的spi_read代码

staticinlineintspi_read(struct spi_device *spi, void *buf, size_t len){ struct spi_transfer t = { .rx_buf = buf, .len = len, }; struct spi_message m; spi_message_init(&m); spi_message_add_tail(&t, &m); return spi_sync(spi, &m);}

// 先写后读(常用)

int spi_write_then_read(struct spi_device *spi, const void *txbuf, size_t n_tx, void *rxbuf, size_t n_rx);

staticinlineintspi_write_and_read(struct spi_device *spi, constvoid *tx_buf, void *rx_buf, size_t len){        struct spi_transfer     t = {                        .tx_buf         = tx_buf,                        .rx_buf         = rx_buf,                        .len            = len,                };        struct spi_message      m;        spi_message_init(&m);        spi_message_add_tail(&t, &m);        return spi_sync(spi, &m);}

通用的读写API

简化寄存器访问的统一框架,支持I2C/SPI/MMIO等总线。管它什么外设,直接就一套API。

// 初始化Regmapstruct regmap_config config = {.reg_bits = 8, // 寄存器地址位数.val_bits = 16, // 寄存器值位数};
struct regmap *regmap = devm_regmap_init_i2c(client, &config); // I2C设备// 读写寄存器intregmap_write(struct regmap *map, unsignedint reg, unsignedint val);intregmap_read(struct regmap *map, unsignedint reg, unsignedint *val);

regmap_write函数原型

/** * regmap_write(): Write a value to a single register * * @map: Register map to write to * @reg: Register to write to * @val: Value to be written * * A value of zero will be returned on success, a negative errno will * be returned in error cases. */int regmap_write(struct regmap *map, unsigned int reg, unsigned int val){ int ret; if (reg % map->reg_stride) return -EINVAL; map->lock(map->lock_arg); ret = _regmap_write(map, reg, val); map->unlock(map->lock_arg); return ret;}EXPORT_SYMBOL_GPL(regmap_write);

regmap_write的底层调用流程

regmap_read函数原型

/** * regmap_read(): Read a value from a single register * * @map: Register map to write to * @reg: Register to be read from * @val: Pointer to store read value * * A value of zero will be returned on success, a negative errno will * be returned in error cases. */int regmap_read(struct regmap *map, unsigned int reg, unsigned int *val){ int ret; if (reg % map->reg_stride) return -EINVAL; map->lock(map->lock_arg); ret = _regmap_read(map, reg, val); map->unlock(map->lock_arg); return ret;}EXPORT_SYMBOL_GPL(regmap_read);

优势:

自动处理总线细节(如I2C的Repeated Start)。

提供缓存、批量读写等高级功能。

底层共通机制

互斥锁:所有传输函数内部使用锁保证线程安全。
延时处理:SPI/I2C函数可能休眠,不可用于原子上下文。

错误处理:返回负数错误码(如-EIO表示传输失败);

DMA支持:大块数据可能通过DMA传输(如SPI的dma_map_single())

总结

访问类型
核心函数
适用场景
MMIO
readl()/writel()
内存映射寄存器
I2C
i2c_smbus_read_byte_data()
I2C传感器、编解码器
SPI
spi_write_then_read()
SPI Flash、ADC/DAC
Regmap
regmap_read()/regmap_write()
跨总线统一访问

实际开发中:

优先使用Regmap:简化代码,支持多种总线。
注意上下文:SPI/I2C函数可能休眠,中断上下文需使用spi_async()等异步接口。
检查返回值:所有函数返回非零需处理错误。
就到这里,谢谢点赞关注。

最新文章

随机文章