如何为内核 5.10.x 模块驱动程序版本替换 set_fs(KERNEL_DS)

Posted

技术标签:

【中文标题】如何为内核 5.10.x 模块驱动程序版本替换 set_fs(KERNEL_DS)【英文标题】:How to replace set_fs(KERNEL_DS) for a kernel 5.10.x module driver version 【发布时间】:2021-04-16 10:40:17 【问题描述】:

我一直在将自定义模块驱动程序更新为 5.10.x linux 内核版本。我的驱动程序在 cdc-acm 设备上添加了一个层。为了复制行为,使用了下一个小驱动程序。

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/file.h>
#include <linux/tty.h>
#include <linux/version.h>
#include <linux/uaccess.h>



/* Device major umber */
static int major;

/* ttyACM file descriptor */
static struct file *fd;


/* Private functions ---------------------------------------------------------*/
static int usbox_serial_baudrate_set(struct file *fd)

    int ret;
    mm_segment_t old_fs;
    struct termios newtio;
    //struct termios __user newtio;
    //void __user *unewtio = (void __user *) &newtio;

    memset(&newtio, 0, sizeof(newtio));
    newtio.c_cflag = (B115200 | CS8 | CLOCAL | CREAD);

#if LINUX_VERSION_CODE < KERNEL_VERSION(5,0,0)
    old_fs = get_fs();
    set_fs( get_ds() );
#elif LINUX_VERSION_CODE < KERNEL_VERSION(5,10,0)
    old_fs = get_fs();
    set_fs( KERNEL_DS );
#else
    old_fs = force_uaccess_begin();
#endif

    if (fd->f_op->unlocked_ioctl) 
        ret = fd->f_op->unlocked_ioctl(fd, TCSETS, (unsigned long int) &newtio);
        pr_info("_unlocked_ioctl: %d\n", ret);
     else 
        ret = -ENOTTY;
    

#if LINUX_VERSION_CODE < KERNEL_VERSION(5,10,0)
    set_fs(old_fs);
#else
    force_uaccess_end(old_fs);
#endif

    pr_info("ret: %d\n", ret );
    return ret;



/* Driver Methods ------------------------------------------------------------*/
static ssize_t testdrv_read(struct file *filp,
               char __user *buf, size_t count, loff_t *ppos)

    return 0;


static ssize_t testdrv_write(struct file *filp,
                const char __user *buf, size_t count, loff_t *ppos)

    return 0;


static int testdrv_open(struct inode *inode, struct file *filp)

    pr_info("testdrv_open\n");

    fd = filp_open( "/dev/ttyACM0", O_RDWR|O_NOCTTY, 0);
    if (IS_ERR(fd)) 
        pr_info("error from filp_open()\n");
        return -ENODEV;
    

    pr_info ("fd      : %p\n", fd);
    pr_info ("fd->f_op: %p\n", fd->f_op);
    pr_info ("ioctl   : %p\n", fd->f_op->unlocked_ioctl);

    if ((fd->f_op == NULL) || (fd->f_op->unlocked_ioctl == NULL)) 
        pr_info("errno: ENODEV\n");
        return -ENODEV;
    

    // Set baudrate.
    if (usbox_serial_baudrate_set(fd) != 0 ) 
        filp_close(fd, NULL);
        pr_info("errno: EINVAL\n");
        return -EINVAL;
    

    return 0;


static int testdrv_release(struct inode *inode, struct file *filp)

    pr_info("testdrv_release\n");

    if (fd != NULL) 
        filp_close(fd, NULL);
        fd = NULL;
    

    return 0;


static struct file_operations testdrv_fops = 
    .owner      = THIS_MODULE,
    .read       = testdrv_read,
    .write      = testdrv_write,
    .open       = testdrv_open,
    .release    = testdrv_release
;


/* Module stuff --------------------------------------------------------------*/

static int __init testdrv_init(void)

    int ret;

    ret = register_chrdev(0, "testdrv", &testdrv_fops);
    if (ret < 0) 
        pr_err("Error %d\n", ret);
        return ret;
    
    major = ret;
    fd = NULL;
    pr_info("Major %d\n", major);

    return 0;


static void __exit testdrv_exit(void)

    unregister_chrdev(major, "testdrv");


module_init(testdrv_init);
module_exit(testdrv_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Unknonw");
MODULE_DESCRIPTION("testdrv");

这里,当testdrv打开时,驱动打开ttyACM相关。然后它调用'usbox_serial_baudrate_set'来设置波特率。此函数从填充描述符中调用“unlocked_ioctl”。为了能够使用这个调用,我必须使用

#if LINUX_VERSION_CODE < KERNEL_VERSION(5,0,0)
    old_fs = get_fs();
    set_fs( get_ds() );
#elif LINUX_VERSION_CODE < KERNEL_VERSION(5,10,0)
    old_fs = get_fs();
    set_fs( KERNEL_DS );
#else
    old_fs = force_uaccess_begin();
#endif
...
        ret = fd->f_op->unlocked_ioctl(fd, TCSETS, (unsigned long int) &newtio);
...
#if LINUX_VERSION_CODE < KERNEL_VERSION(5,10,0)
    set_fs(old_fs);
#else
    force_uaccess_end(old_fs);
#endif

在 5.10.x 之前,此代码可以正常工作。我不得不使用 KERNEL_DS 对 5.4.x 做一些小改动,但现在我总是从 'unlocked_ioctl' 获得 EFAULT。我试过删除 'force_uaccess_begin / force_uaccess_end' 没有运气。 'unlocked_ioctl' 最终调用 /drivers/tty/tty_ioctl.c 'set_termios' 函数并失败:

#ifdef TCGETS2
     else if (opt & TERMIOS_OLD) 
        if (user_termios_to_kernel_termios_1(&tmp_termios,
                        (struct termios __user *)arg))
            return -EFAULT;
     else 

函数'user_termios_to_kernel_termios_1'是一个宏

#define user_termios_to_kernel_termios_1(k, u) copy_from_user(k, u, sizeof(struct termios))

使用 4.15.0 内核,一旦插入模块并 cat 相关设备,我会在 dmesg 中收到下一条消息:

[370099.242677] testdrv:testdrv_init: Major 237
[370103.032357] testdrv:testdrv_open: testdrv_open
[370103.032635] testdrv:testdrv_open: fd      : 0000000034db75d4
[370103.032637] testdrv:testdrv_open: fd->f_op: 00000000c761e065
[370103.032638] testdrv:testdrv_open: ioctl   : 00000000608ed60c
[370103.032643] testdrv:usbox_serial_baudrate_set: _unlocked_ioctl: 0
[370103.032645] testdrv:usbox_serial_baudrate_set: ret: 0
[370103.032685] testdrv:testdrv_release: testdrv_release

并带有 5.10.1

[  294.418308] testdrv:testdrv_init: got major 244
[  296.574583] testdrv:testdrv_open: testdrv_open
[  296.575949] testdrv:testdrv_open: fd      : 00000000c35e59c0
[  296.575955] testdrv:testdrv_open: fd->f_op: 0000000041840a0e
[  296.575957] testdrv:testdrv_open: ioctl   : 000000005e21689c
[  296.575965] testdrv:usbox_serial_baudrate_set: _unlocked_ioctl: -14
[  296.575967] testdrv:usbox_serial_baudrate_set: ret: -14
[  296.575970] testdrv:testdrv_open: errno: EINVAL

任何人都可以帮助我了解发生了什么?混合用户空间/内核空间数据有问题吗?我该如何解决?

【问题讨论】:

使用 函数代替从内核访问串行。看看 serdev-ttyport.c 【参考方案1】:

您可以使用 CONFIG_SET_FS=y [1] 构建内核。至少应该是 5.10 的选项。

[1]https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/asm-generic/uaccess.h?h=v5.10#n99

【讨论】:

虽然这段代码可以解决问题,including an explanation 解决问题的方式和原因将真正有助于提高您的帖子质量,并可能导致更多的赞成票。请记住,您正在为将来的读者回答问题,而不仅仅是现在提问的人。请edit您的答案添加解释并说明适用的限制和假设。

以上是关于如何为内核 5.10.x 模块驱动程序版本替换 set_fs(KERNEL_DS)的主要内容,如果未能解决你的问题,请参考以下文章

如何为不同的 linux 内核编译一个 linux 内核模块

我应该如何为 linux 内核模块构建设置额外的包含路径?

如何为python安装不同版本的模块

Angular:如何为 Angular 应用程序中的功能创建测试模块?

如何为多个 Python 版本和平台构建编译模块

如何为我的引导加载程序制作内核?