我一直在将自定义模块驱动程序更新到 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 内核,插入模块并查找相关设备后,我会在 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
任何人都可以帮助我了解发生了什么事吗?混合用户空间/内核空间数据有问题吗?我该如何解决?
您可以使用 CONFIG_SET_FS=y (uaccess.h) 构建内核。至少应该是 5.10 的选项。