title: Linux总线与接口驱动面试精选 tags:
答案要点:
详细解答: Linux I2C驱动框架采用分层架构,主要分为三层:
// I2C总线数据结构
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
};
i2c_adapter结构体,实现i2c_algorithm中的master_xfer传输函数。通常由半导体厂商编写。i2c_driver结构体中的probe和remove函数。i2c_add_adapter、i2c_del_adapter、i2c_add_driver等API。追问:
i2c_algorithm结构体中的master_xfer函数有什么作用?答案要点:
i2c_device_match函数完成匹配详细解答:
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
// 设备树匹配
if (of_driver_match_device(dev, drv))
return 1;
// ACPI匹配
if (acpi_driver_match_device(dev, drv))
return 1;
// 传统ID表匹配
if (driver->id_table)
return i2c_match_id(driver->id_table, client) != NULL;
return 0;
}
匹配顺序:
compatible属性与驱动的of_device_id表i2c_device_id的name字段追问:
答案要点:
详细解答:
// probe函数
static int xxx_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
// 初始化设备
// 注册字符设备
return 0;
}
// remove函数
static int xxx_remove(struct i2c_client *client)
{
// 注销字符设备
return 0;
}
// 传统匹配ID列表
static const struct i2c_device_id xxx_id[] = {
{"xxx", 0},
{}
};
// 设备树匹配列表
static const struct of_device_id xxx_of_match[] = {
{ .compatible = "xxx" },
{ /* Sentinel */ }
};
// i2c驱动结构体
static struct i2c_driver xxx_driver = {
.probe = xxx_probe,
.remove = xxx_remove,
.driver = {
.owner = THIS_MODULE,
.name = "xxx",
.of_match_table = xxx_of_match,
},
.id_table = xxx_id,
};
// 驱动入口函数
static int __init xxx_init(void)
{
return i2c_add_driver(&xxx_driver);
}
// 驱动出口函数
static void __exit xxx_exit(void)
{
i2c_del_driver(&xxx_driver);
}
module_init(xxx_init);
module_exit(xxx_exit);
追问:
i2c_add_driver宏的实现是什么?答案要点:
详细解答:
I.MX6U的I2C适配器驱动在drivers/i2c/busses/i2c-imx.c中实现:
static struct platform_driver i2c_imx_driver = {
.probe = i2c_imx_probe,
.remove = i2c_imx_remove,
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = i2c_imx_dt_ids,
},
.id_table = imx_i2c_devtype,
};
probe函数主要工作:
i2c_adapter结构体i2c_algorithm中的传输函数i2c_add_numbered_adapter注册适配器追问:
i2c_add_adapter和i2c_add_numbered_adapter有什么区别?答案要点:
详细解答:
// I2C时序关键点
起始条件: SCL=1, SDA↓
停止条件: SCL=1, SDA↑
应答信号: 第9个SCL高电平时SDA=0
非应答: 第9个SCL高电平时SDA=1
// I2C数据传输格式
| S | 7bit地址 | R/W | ACK | 8bit数据 | ACK | ... | P |
I2C通信特点:
追问:
答案要点:
详细解答:
// SPI总线数据结构
struct bus_type spi_bus_type = {
.name = "spi",
.dev_groups = spi_dev_groups,
.match = spi_match_device,
.uevent = spi_uevent,
};
// SPI设备驱动结构体
struct spi_driver {
const struct spi_device_id *id_table;
int (*probe)(struct spi_device *spi);
int (*remove)(struct spi_device *spi);
void (*shutdown)(struct spi_device *spi);
struct device_driver driver;
};
与I2C的主要区别:
| 特性 | SPI | I2C |
|---|---|---|
| 通信方式 | 全双工 | 半双工 |
| 片选 | 需要CS线 | 通过地址 |
| 速率 | 更高 | 较低 |
| 信号线 | 4根 | 2根 |
追问:
答案要点:
详细解答:
// SPI数据传输步骤
struct spi_transfer t;
struct spi_message m;
// 1. 初始化spi_message
spi_message_init(&m);
// 2. 设置spi_transfer
memset(&t, 0, sizeof(t));
t.tx_buf = tx_data; // 发送缓冲区
t.rx_buf = rx_data; // 接收缓冲区
t.len = len; // 数据长度
// 3. 添加到spi_message
spi_message_add_tail(&t, &m);
// 4. 同步传输
spi_sync(spi, &m);
spi_transfer结构体关键字段:
tx_buf:发送数据缓冲区rx_buf:接收数据缓冲区len:数据长度speed_hz:传输速率bits_per_word:每字位数追问:
答案要点:
详细解答:
// SPI控制器节点
ecspi3: ecspi@02010000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi";
reg = <0x02010000 0x4000>;
interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_ECSPI3>,
<&clks IMX6UL_CLK_ECSPI3>;
clock-names = "ipg", "per";
dmas = <&sdma 7 7 1>, <&sdma 8 7 2>;
dma-names = "rx", "tx";
status = "disabled";
};
// SPI设备节点
&ecspi3 {
fsl,spi-num-chipselects = <1>;
cs-gpios = <&gpio4 9 0>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi3>;
status = "okay";
icm20608@0 {
compatible = "invensense,icm20608";
spi-max-frequency = <1000000>;
reg = <0>;
};
};
追问:
答案要点:
详细解答:
I.MX6U的SPI适配器驱动在drivers/spi/spi-imx.c中实现:
static struct platform_driver spi_imx_driver = {
.driver = {
.name = DRIVER_NAME,
.of_match_table = spi_imx_dt_ids,
.pm = IMX_SPI_PM,
},
.id_table = spi_imx_devtype,
.probe = spi_imx_probe,
.remove = spi_imx_remove,
};
数据传输函数调用链:
spi_imx_transfer
-> spi_imx_pio_transfer
-> spi_imx_push
-> spi_imx->tx (发送函数)
不同位宽的发送函数:
spi_imx_buf_tx_u8:8位发送spi_imx_buf_tx_u16:16位发送spi_imx_buf_tx_u32:32位发送追问:
答案要点:
详细解答:
| 应用场景 | 选择SPI | 选择I2C |
|---|---|---|
| 高速数据采集 | ✓ | |
| Flash存储器 | ✓ | |
| 传感器(低速) | ✓ | |
| EEPROM | ✓ | |
| LCD显示屏 | ✓ | |
| 多设备连接 | ✓ |
选择依据:
追问:
答案要点:
详细解答:
USB拓扑结构:
┌─────────────────────────────────────┐
│ Host (Root Hub) │
│ ┌──────┬──────┬──────┬──────┐ │
│ │Port1 │Port2 │Port3 │Port4 │ │
└────┴──┬───┴──┬───┴──┬───┴──┬───┘ │
│ │ │ │
┌────┴──┐ ┌─┴───┐ ┌┴────┐ │
│Device │ │ Hub │ │Dev │ │
└───────┘ └──┬──┘ └─────┘ │
┌────┴────┬───────┐│
│Dev│Dev│Dev│Dev ││
└───┴───┴───┴────┘│
关键概念:
USB特点:
追问:
答案要点:
详细解答:
// USB传输类型对比
| 传输类型 | 特点 | 应用场景 | 是否保证带宽 |
|-----------|---------------|-----------------|-------------|
| 控制传输 | 可靠、有序 | 设备配置、状态查询 | 否 |
| 批量传输 | 大量、可靠 | 打印机、U盘 | 否 |
| 中断传输 | 周期性、低延迟 | 键盘、鼠标 | 是 |
| 同步传输 | 固定速率、允许丢包 | 音视频流 | 是 |
USB端点类型:
追问:
答案要点:
详细解答:
// USB枚举过程
1. 设备插入 → Hub检测到电平变化
2. Host复位端口 → 设备进入Default状态
3. Host发送GET_DESCRIPTOR请求 → 获取设备描述符
4. Host分配地址 → SET_ADDRESS请求
5. Host获取配置描述符 → 了解设备能力
6. Host加载驱动 → 匹配成功调用probe
USB描述符层次:
Device Descriptor
└── Configuration Descriptor
├── Interface Descriptor (0)
│ └── Endpoint Descriptor
└── Interface Descriptor (1)
└── Endpoint Descriptor
追问:
答案要点:
详细解答:
// USB OTG模式切换
ID引脚状态:
- ID = 0 → Host模式
- ID = 1 → Device模式
// OTG线连接方式
OTG线:USB A头 → Mini USB/Type-C头
- USB A头:连接USB设备
- Mini USB头:连接OTG接口
- OTG线会将ID引脚拉低
I.MX6ULL USB OTG实现:
// 硬件连接
USB_OTG1_ID引脚:
- 高电平:Device模式
- 低电平:Host模式
// 通过MT9700HT5芯片控制VBUS电源
- Host模式:VBUS输出5V
- Device模式:VBUS关闭
追问:
答案要点:
详细解答:
// Linux USB驱动层次
┌─────────────────────────────────┐
│ USB设备驱动 │
│ (U盘驱动、鼠标驱动等) │
├─────────────────────────────────┤
│ USB核心层 │
│ (usb_core、usb接口驱动) │
├─────────────────────────────────┤
│ USB主机驱动(HCD) │
│ (EHCI、xHCI控制器驱动) │
├─────────────────────────────────┤
│ USB硬件控制器 │
│ (I.MX6ULL USB OTG) │
└─────────────────────────────────┘
USB主机控制器标准:
追问:
答案要点:
详细解答:
// Linux CAN驱动层次
┌─────────────────────────────────┐
│ CAN应用层 │
│ (SocketCAN接口) │
├─────────────────────────────────┤
│ CAN协议层 │
│ (CAN raw、CAN bcm等) │
├─────────────────────────────────┤
│ CAN核心层 │
│ (can_dev.c) │
├─────────────────────────────────┤
│ CAN控制器驱动 │
│ (SJA1000、MCP2515等) │
└─────────────────────────────────┘
CAN网络设备结构:
struct can_dev {
struct net_device *netdev;
struct can_device *can;
can_tx_function_t tx;
can_rx_function_t rx;
// ...
};
追问:
答案要点:
详细解答:
// CAN帧格式对比
标准帧:
| SOF | 11bit ID | RTR | IDE | r0 | DLC | Data(0-8) | CRC | ACK | EOF |
扩展帧:
| SOF | 11bit ID | SRR | IDE | 18bit ID | RTR | r1 | r0 | DLC | Data(0-8) | CRC | ACK | EOF |
// CAN帧结构
struct can_frame {
canid_t can_id; // CAN ID + 标志位
__u8 can_dlc; // 数据长度
__u8 __pad; // 填充
__u8 __res0; // 保留
__u8 __res1; // 保留
__u8 data[8]; // 数据
};
追问:
答案要点:
详细解答:
// CAN错误类型
| 错误类型 | 检测方法 | 说明 |
|-----------|----------------|-------------------|
| 位错误 | 发送与监听比较 | 发送的位与总线不同 |
| 填充错误 | 连续6个相同位 | 违反位填充规则 |
| CRC错误 | CRC校验 | 接收CRC与计算不同 |
| 形式错误 | 固定格式位 | 格式字段出错 |
| 应答错误 | ACK槽 | 发送方未收到ACK |
// 错误状态
主动错误 → 检测到错误发送错误帧
被动错误 → 检测到错误不发送错误帧
总线关闭 → 退出总线,等待恢复
追问:
答案要点:
详细解答:
// CAN网络设备操作集
static const struct net_device_ops can_netdev_ops = {
.ndo_open = can_open,
.ndo_stop = can_close,
.ndo_start_xmit = can_xmit,
.ndo_do_ioctl = can_ioctl,
};
// CAN设备注册
struct net_device *alloc_candev(int sizeof_priv, unsigned int echo_skb_max)
{
struct net_device *dev;
// 分配网络设备
// 初始化can_dev结构
return dev;
}
CAN数据接收:
// 中断处理中接收CAN帧
static irqreturn_t can_rx_irq(int irq, void *dev_id)
{
struct can_frame *frame = &priv->rx_frame;
// 读取CAN帧
// 调用netif_rx上报
netif_rx_ni(&priv->skb);
}
追问:
答案要点:
详细解答:
| 特性 | CAN | SPI |
|---|---|---|
| 通信距离 | 最远10km | 板级 |
| 最高速率 | 1Mbps | 几十MHz |
| 多主支持 | 支持 | 不支持 |
| 错误检测 | 完善 | 无 |
| 通信方式 | 差分信号 | 单端信号 |
| 应用场景 | 汽车、工业 | 传感器、Flash |
选择依据:
追问:
答案要点:
详细解答:
// Linux串口驱动层次
┌─────────────────────────────────┐
│ 用户空间 │
│ (ttyS0, ttyS1等) │
├─────────────────────────────────┤
│ TTY核心层 │
│ (tty_io.c) │
├─────────────────────────────────┤
│ 串口核心层 │
│ (serial_core.c) │
├─────────────────────────────────┤
│ 串口驱动层 │
│ (8250_dw.c, imx.c等) │
└─────────────────────────────────┘
串口操作集:
static const struct uart_ops serial_imx_pops = {
.tx_empty = imx_tx_empty,
.set_mctrl = imx_set_mctrl,
.get_mctrl = imx_get_mctrl,
.stop_tx = imx_stop_tx,
.start_tx = imx_start_tx,
.stop_rx = imx_stop_rx,
.ioctl = imx_ioctl,
.break_ctl = imx_break_ctl,
.startup = imx_startup,
.shutdown = imx_shutdown,
.set_termios = imx_set_termios,
};
追问:
答案要点:
详细解答:
// UART数据帧格式
| 起始位(1) | 数据位(5-8) | 校验位(0-1) | 停止位(1-2) |
// 常见配置
数据位: 7, 8
校验位: None, Even, Odd, Mark, Space
停止位: 1, 1.5, 2
波特率计算:
波特率 = 时钟频率 / (16 * 分频系数)
常用波特率:
追问:
答案要点:
详细解答:
// I.MX6ULL串口驱动注册
static struct platform_driver imx_uart_driver = {
.driver = {
.name = "imx-uart",
.of_match_table = imx_uart_dt_ids,
},
.probe = imx_probe,
.remove = imx_remove,
};
// probe函数主要工作
static int imx_probe(struct platform_device *pdev)
{
struct uart_port *sport;
// 1. 获取资源
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
// 2. 映射寄存器
sport->membase = devm_ioremap_resource(&pdev->dev, res);
// 3. 获取时钟
sport->clk = devm_clk_get(&pdev->dev, NULL);
// 4. 注册串口
uart_add_one_port(&imx_reg, sport);
}
追问:
答案要点:
详细解答:
// 发送流程
用户空间 write()
→ tty_write()
→ uart_write()
→ imx_start_tx()
→ 写入THR寄存器
→ 硬件发送
// 接收流程
硬件接收
→ 触发中断
→ imx_rxint()
→ 读取RBR寄存器
→ tty_insert_flip_char()
→ tty_flip_buffer_push()
→ 用户空间 read()
DMA传输:
// DMA发送
static void imx_dma_tx(struct uart_port *sport)
{
// 配置DMA通道
dmaengine_submit();
// 启动DMA传输
dma_async_issue_pending();
}
追问:
答案要点:
详细解答:
| 特性 | UART | I2C | SPI |
|---|---|---|---|
| 时钟 | 异步 | 同步 | 同步 |
| 通信方式 | 全双工 | 半双工 | 全双工 |
| 信号线 | 2根 | 2根 | 4根 |
| 设备数量 | 点对点 | 多设备 | 需CS线 |
| 速率 | 中等 | 低 | 高 |
| 复杂度 | 简单 | 中等 | 中等 |
应用场景:
追问:
答案要点:
详细解答:
// I2C vs SPI详细对比
| 对比项 | I2C | SPI |
|-----------|------------------------|------------------------|
| 信号线 | SDA, SCL (2根) | MOSI, MISO, SCK, CS (4根) |
| 通信方式 | 半双工 | 全双工 |
| 最高速率 | 400KHz (Fast) | 几十MHz |
| 设备寻址 | 7/10位地址 | 片选信号 |
| 多设备 | 支持,无需额外引脚 | 需要独立CS线 |
| 时钟同步 | 支持 | 不支持 |
| 总线仲裁 | 支持 | 不支持 |
| 功耗 | 低(开漏输出) | 较高(推挽输出) |
| 硬件复杂度| 简单 | 中等 |
| 软件复杂度| 中等(ACK、仲裁) | 简单 |
选择建议:
- 传感器、EEPROM → I2C
- Flash、LCD → SPI
- 多设备、引脚少 → I2C
- 高速、全双工 → SPI
追问:
答案要点:
详细解答:
// 三种总线综合对比
| 对比项 | SPI | I2C | UART |
|-----------|--------------|--------------|--------------|
| 信号线 | 4根 | 2根 | 2根 |
| 时钟 | 同步 | 同步 | 异步 |
| 通信方式 | 全双工 | 半双工 | 全双工 |
| 最高速率 | >10MHz | 400KHz | 1Mbps |
| 设备数量 | 需CS线 | 多设备 | 点对点 |
| 通信距离 | 板级 | 板级 | 可远距离 |
| 错误检测 | 无 | 有(ACK) | 有(校验) |
| 典型应用 | Flash, LCD | 传感器,EEPROM| 调试, GPS |
选择决策树:
需要高速通信?
├── 是 → SPI
└── 否 → 需要多设备?
├── 是 → I2C
└── 否 → 需要远距离?
├── 是 → UART
└── 否 → I2C或SPI均可
追问:
答案要点:
详细解答:
// 各总线的bus_type定义
// I2C
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
};
// SPI
struct bus_type spi_bus_type = {
.name = "spi",
.match = spi_match_device,
};
// Platform
struct bus_type platform_bus_type = {
.name = "platform",
.match = platform_match,
.probe = platform_probe,
};
匹配流程对比:
// 三种总线匹配流程相似
1. of_driver_match_device() // 设备树匹配
2. acpi_driver_match_device() // ACPI匹配
3. xxx_match_id() // 传统ID表匹配
// 匹配成功后调用probe
xxx_probe(struct xxx_client *client, ...)
追问:
答案要点:
详细解答:
// 各总线错误处理对比
| 总线 | 错误检测机制 | 错误恢复方式 |
|------|------------------|------------------|
| I2C | ACK/NACK | 重传、总线恢复 |
| SPI | 无内置机制 | 软件实现 |
| UART | 奇偶校验、帧错误 | 重传、重置 |
| CAN | CRC、位错误、填充错误 | 错误帧、错误计数器 |
// I2C错误处理
if (!client->adapter->algo->master_xfer)
return -EOPNOTSUPP;
// CAN错误处理
if (can_id & CAN_ERR_FLAG) {
// 处理错误帧
dev_err(dev, "CAN error: 0x%x\n", can_id);
}
// UART错误处理
if (lsr & UART_LSR_FE)
port->ic_frame_errors++;
追问:
答案要点:
详细解答:
// 实时性对比
| 总线 | 时钟同步 | 全双工 | 硬件FIFO | 中断响应 | 实时性评级 |
|------|---------|--------|----------|----------|----------|
| SPI | 是 | 是 | 可选 | 快 | 最好 |
| I2C | 是 | 否 | 无 | 中 | 中等 |
| UART | 否 | 是 | 可选 | 中 | 中等 |
| CAN | 是 | 否 | 有 | 快 | 好 |
// 实时性优化方法
1. 使用DMA传输减少CPU占用
2. 使用硬件FIFO缓冲数据
3. 提高中断优先级
4. 使用轮询方式处理关键数据
// SPI实时性优化
struct spi_transfer t = {
.speed_hz = 10000000, // 高速传输
.tx_buf = data,
.len = len,
};
spi_sync(spi, &m); // 同步传输,等待完成
追问: