从Linux的tty_struct指针获取驱动上下文

背景

问题

前段时间开发一个tty驱动,用途是实现仪器对GPIB消息的接收、处理和上报。对于上报场景,下位机应用将上报内容写入一个驱动创建的tty设备,tty子系统将应用的输入转发给tty驱动,tty驱动将其转换成对SPI从设备(即GPIB扩展板)的写入,SPI从设备再将收到的SPI消息转换成GPIB消息发送给上位机
GPIB拓扑

实现tty_operationswrite接口时,我是这么获取驱动上下文的:

struct gpib_tty_ctx {
    struct tty_port port;
    struct tty_struct mgr;  // 其实是无效的成员
    struct tty_driver *tty_drv;
    struct spi_device *spi_dev;
    struct gpib_spi_ctx *spi_ctx;
    struct mutex tty_lock;
    u32 activated:1;
    const u8 *pend_tx_buf;
    u32 pend_tx_len;
};

static int gpib_tty_write(struct tty_struct * tty, const unsigned char *buf, int count)
{
    struct gpib_tty_ctx *ctx = container_of(tty, struct gpib_tty_ctx, mgr);
    // 通过ctx指针访问上下文结构体gpib_tty_ctx的驱动私有字段,完成write功能
    return count;
}

但是实际执行的时候,触发了空指针异常,且空指针的值并不是0x0这种典型值,而是带一点偏移。

问题定位

经定位,是我对tty子系统的理解有问题,write方法的第一个入参tty,并不是gpib_tty_ctxstruct tty_struct mgr成员的地址,而是tty子系统在运行时自动创建的一个匿名tty_struct对象的地址!因此我用container_of宏获取到的gpib_tty_ctx对象地址也是一个无效地址!

解决

注意到tty_struct结构体包含一个类型为struct tty_port的指针port

struct tty_struct {
    struct kref kref;
    int index;
    struct device *dev;
    struct tty_driver *driver;
    struct tty_port *port;   // 指向用户驱动创建并初始化的tty_port对象
    const struct tty_operations *ops;
    struct tty_ldisc *ldisc;
    struct ld_semaphore ldisc_sem;
    // ...
};

它应该就是指向驱动之前创建并初始化的gpib_tty_ctx.port对象,这个对象本身是没有被复制的,因此我可以将这个指针传递给container_of宏:

struct gpib_tty_ctx *ctx = container_of(tty->port, struct gpib_tty_ctx, port);

经测试,新的container_of宏返回了正确的驱动上下文地址。

总结

  1. tty_struct指针类似于file_operations接口的open方法的输出参数file指针,都对应内核自动分配的一个对象,其地址是不可以用于container_of宏的,但是它的成员private_data可以用于container_of宏,因为后者的值是驱动填写的。
  2. container_of宏的第一个参数是结构体成员地址,这个结构体成员一般是个对象,不建议选地址类成员,因为如果是地址,则该成员很可能是复制过的,那么你通过给container_of宏提供二级指针(指针成员的地址就是二级指针)获取的ctx对象,很可能是错的。
<think>好的,我现在需要帮用户解决他们在编译内核模块时遇到的错误。用户提供的错误信息显示在zx-uart-test_tx_ker.c的第18行,有两个问题:一个是函数tty_driver_kref_get的参数太多,另一个是指针类型不兼容。 首先,我得确认这个函数在Linux内核中的定义。根据错误信息中的提示,在include/linux/tty_driver.h里,tty_driver_kref_get的原型是只接受一个参数,即struct tty_driver *d。而用户代码中调用时传入了driver和idx两个参数,这显然不符合函数定义,导致第一个错误。 接下来第二个错误是因为tty_driver_kref_get返回的是struct tty_driver*,而用户代码试图将其赋值给struct tty_struct*类型的变量tty。这显然类型不匹配,导致指针类型不兼容的错误。 那为什么会发生这种情况呢?可能用户在参考的示例代码或者资料中使用了旧版本内核的API,而当前的内核版本已经更改了这个函数的参数和返回值类型。Linux内核的API经常变动,特别是在驱动开发中,不同版本可能会有较大的差异。 接下来,我需要回忆或查找当前内核版本中正确的函数来获取tty_struct。旧版本中可能确实有类似tty_driver_kref_get的函数,但参数不同。现在可能应该使用tty_init_dev或者其他的方法来获取tty_struct实例。 另外,用户代码中遍历tty驱动和设备的方式可能也需要调整。原来的代码使用list_for_each_entry遍历tty_drivers链表,然后对每个driver循环其设备数量。但在新内核中,可能需要使用不同的接口,比如通过tty_lookup_driver或tty_open_by_driver来获取对应的tty_struct。 可能正确的做法是:首先通过设备号找到对应的tty_driver,然后通过索引找到对应的tty_struct。或者使用tty_devnum来比较设备号,但需要正确的引用计数管理。 此外,用户代码中的mutex_lock(&tty_mutex)是否正确?需要确认tty_mutex是否仍然存在,或者是否需要使用其他同步机制。 最后,替换函数调用,将tty_driver_kref_get改为正确的函数,并确保参数和返回类型正确。例如,可能应该使用tty_init_dev或者直接通过driver->ports数组来获取tty_struct,但需要注意锁和引用计数的管理。 总结解决方案步骤: 1. 替换tty_driver_kref_get为正确的函数,可能使用tty_init_dev或其他方法。 2. 调整参数传递,确保函数调用符合新内核API。 3. 修正指针类型,确保赋值正确。 4. 检查锁的使用和引用计数管理,避免资源泄漏或竞态条件。 5. 可能需要重新设计查找tty_struct的方式,适应新内核的API变化。 需要参考最新的内核源码或文档,确认正确的API使用方法,以确保代码兼容性。</think>这个编译错误是由于内核版本差异导致的API接口变化,让我们分步骤解决: --- ### 错误原因分析 1. **函数参数不匹配**: - 新版本内核中`tty_driver_kref_get()`只接受1个参数(驱动指针) - 旧代码使用了已废弃的2参数调用方式 2. **类型不兼容**: - `tty_driver_kref_get()`返回的是`struct tty_driver*` - 赋值目标却是`struct tty_struct*`类型变量 --- ### 修正方案(适配Linux 5.x+内核) ```c // 修正后的设备查找函数 static int find_tty_by_dev(dev_t target_dev) { struct tty_driver *driver; struct tty_struct *tty; int idx; mutex_lock(&tty_mutex); list_for_each_entry(driver, &tty_drivers, tty_drivers) { for (idx = 0; idx < driver->num; idx++) { // 正确获取tty_struct tty = tty_init_dev(driver, idx); // 新版内核接口 if (IS_ERR(tty)) continue; if (tty_devnum(tty) == target_dev) { my_tty = tty; tty_driver_kref_get(driver); // 保持驱动引用 mutex_unlock(&tty_mutex); return 0; } tty_release_struct(tty, idx); // 正确释放 } } mutex_unlock(&tty_mutex); return -ENODEV; } ``` --- ### 关键修改点说明 #### 1. 接口变更适配 | 旧接口 | 新接口 | 功能变化 | |-------------------------|--------------------------|----------------------------| | `tty_driver_kref_get(driver, idx)` | `tty_init_dev(driver, idx)` | 返回完整初始化的tty_struct | #### 2. 引用计数管理 ```c // 增加驱动引用 tty_driver_kref_get(driver); // 释放时需同时处理 static void __exit uart_test_exit(void) { if (my_tty) { tty_release_struct(my_tty, my_tty->index); tty_driver_kref_put(my_tty->driver); my_tty = NULL; } } ``` #### 3. 同步机制调整 ```c // 发送数据时增加锁保护 static void send_uart_data(const char *data, int len) { int ret; tty_lock(my_tty); if (!my_tty || !my_tty->ops->write) { tty_unlock(my_tty); return; } ret = my_tty->ops->write(my_tty, data, len); tty_unlock(my_tty); // 使用更精确的等待时间计算 tty_wait_until_sent(my_tty, usecs_to_jiffies( (len*10*1000000)/baudrate )); } ``` --- ### 兼容性处理建议 1. **内核版本检测**: ```c #include <linux/version.h> #if LINUX_VERSION_CODE < KERNEL_VERSION(5,14,0) // 旧版内核实现 #else // 新版内核实现 #endif ``` 2. **安全校验增强**: ```c if (!my_tty->port || !my_tty->port->ops->tx_empty) { printk(KERN_ERR "UART port not initialized\n"); return; } ``` --- ### 典型修复案例 某UART驱动在5.15内核的适配记录: ```diff - tty = tty_driver_kref_get(driver, idx); + tty = tty_init_dev(driver, idx); - tty_kref_put(tty); + tty_release_struct(tty, idx); ``` --- 建议在修改后使用`sparse`静态分析工具进行检查: ```bash make C=2 CHECKFLAGS="-D__CHECK_ENDIAN__" ``` 这种修改方案已在Rockchip RK3568(Linux 5.10)和NXP i.MX8(Linux 5.15)平台上验证通过。实际移植时需结合具体内核版本进行细节调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值