加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 服务器 > Linux > 正文

微小的tty linux设备驱动程序的奇怪行为

发布时间:2020-12-14 01:41:12 所属栏目:Linux 来源:网络整理
导读:我在 Linux设备驱动程序一书中找到的这个小小的驱动程序遇到了麻烦.我必须略微采用代码以满足我的要求,因此踢掉了任何不相关的代码(参见下面的代码). 我使用一个内核线程将“hello world”写入TTY层.如果我使用cat命令在终端中打开设备文件,我会收到预期的字
我在 Linux设备驱动程序一书中找到的这个小小的驱动程序遇到了麻烦.我必须略微采用代码以满足我的要求,因此踢掉了任何不相关的代码(参见下面的代码).

我使用一个内核线程将“hello world”写入TTY层.如果我使用cat命令在终端中打开设备文件,我会收到预期的字符串.

但我面临两个问题:

>为什么在tty_insert_flip_char(…)时调用tiny_write(…)
在我的内核线程(tiny_thread)中调用?只有在写入设备文件时才应该调用tiny_write(…)函数吗?如果在读取或写入操作期间调用它,如何区分此函数?
>如果在设备文件上使用echo,为什么会出现错误?

echo test > /dev/tiny_tty
bash: echo: write error: Invalid argument

驱动程序在Raspberry Pi内核4.9.56-v7上运行.

非常感谢!

问候,
托马斯

更新:第一个问题(部分)使用tty_flip_buffer_push() sends data back to itself中的解决方案解决.有没有办法直接在设备驱动程序中执行此操作,因此用户不需要进行任何交互?

/*
 * Tiny TTY driver
 *
 * Base on tiny tty driver from Greg Kroah-Hartman
 */

#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/wait.h>
#include <linux/tty.h>
#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/serial.h>
#include <linux/sched.h>
#include <linux/seq_file.h>
#include <asm/uaccess.h>
#include <linux/kthread.h>
#include <linux/jiffies.h>

#define USE_SIMULATOR

#define DELAY_TIME      HZ * 2  /* 2 seconds per character */

#define TINY_TTY_MAJOR      240 /* experimental range */
#define TINY_TTY_MINORS     1   /* only have 4 devices */

#if defined(USE_SIMULATOR)
static struct task_struct *thread_id;
static wait_queue_head_t wq_thread;
static DECLARE_COMPLETION(on_exit);
#endif /* USE_SIMULATOR */

struct tiny_serial {
    struct tty_struct   *tty;       /* pointer to the tty for this device */
    int         open_count;         /* number of times this port has been opened */
    struct semaphore    sem;        /* locks this structure */
};

static struct tiny_serial *tiny_serial; /* initially all NULL */

#if defined(USE_SIMULATOR)
static int tiny_thread(void *thread_data)
{
    unsigned int timeoutMs;
    struct tiny_serial *tiny = (struct tiny_serial*)thread_data;
    struct tty_struct *tty;
    struct tty_port *port;
    char buf[] = "hello worldn";
    int i = 0;

    allow_signal(SIGTERM);    

    pr_info("%sn",__func__);

    tty = tiny->tty;
    port = tty->port;

    while(kthread_should_stop() == 0)
    {
        timeoutMs = 1000;
        timeoutMs = wait_event_interruptible_timeout(wq_thread,(timeoutMs==0),msecs_to_jiffies(timeoutMs));

        if(timeoutMs == -ERESTARTSYS)
        {
            pr_info("%s - signal breakn",__func__);
            up(&tiny->sem);
            break;
        }

        pr_info("%sn",__func__);

        down(&tiny->sem);

        if(tiny)
        {
            for (i = 0; i < strlen(buf); ++i)
            {
                if (!tty_buffer_request_room(tty->port,1))
                    tty_flip_buffer_push(tty->port);
                tty_insert_flip_char(tty->port,buf[i],TTY_NORMAL);

            }
            tty_flip_buffer_push(tty->port);
        }
        up(&tiny->sem);
    }

    complete_and_exit(&on_exit,0);
}
#endif /* USE_SIMULATOR */

static int tiny_open(struct tty_struct *tty,struct file *file)
{
    pr_info("%sn",__func__);

    /* initialize the pointer in case something fails */
    tty->driver_data = NULL;

    /* get the serial object associated with this tty pointer */
    if(tiny_serial == NULL) {
        /* first time accessing this device,let's create it */
        tiny_serial = kmalloc(sizeof(*tiny_serial),GFP_KERNEL);
        if (!tiny_serial)
            return -ENOMEM;

        sema_init(&tiny_serial->sem,1);
        tiny_serial->open_count = 0;
    }

    down(&tiny_serial->sem);

    /* save our structure within the tty structure */
    tty->driver_data = tiny_serial;
    tiny_serial->tty = tty;

    ++tiny_serial->open_count;
    if (tiny_serial->open_count == 1) {
        /* this is the first time this port is opened */
        /* do any hardware initialization needed here */
#if defined(USE_SIMULATOR)      
        if(thread_id == NULL)
            thread_id = kthread_create(tiny_thread,(void*)tiny_serial,"tiny_thread");
        wake_up_process(thread_id); 
#endif /* USE_SIMULATOR */        
    }

    up(&tiny_serial->sem);
    return 0;
}

static void do_close(struct tiny_serial *tiny)
{
    pr_info("%sn",__func__);

    down(&tiny->sem);

    if (!tiny->open_count) {
        /* port was never opened */
        goto exit;
    }

    --tiny->open_count;
    if (tiny->open_count <= 0) {
        /* The port is being closed by the last user. */
        /* Do any hardware specific stuff here */   

#if defined(USE_SIMULATOR)
        /* shut down our timer and free the memory */
        if(thread_id)
        {
            kill_pid(task_pid(thread_id),SIGTERM,1);
            wait_for_completion(&on_exit);
            thread_id = NULL;
        }
#endif /* USE_SIMULATOR */                  

    }
exit:
    up(&tiny->sem);
}

static void tiny_close(struct tty_struct *tty,struct file *file)
{
    struct tiny_serial *tiny = tty->driver_data;

    pr_info("%sn",__func__);

    if (tiny)
        do_close(tiny);
}   

static int tiny_write(struct tty_struct *tty,const unsigned char *buffer,int count)
{
    struct tiny_serial *tiny = tty->driver_data;
    int i;
    int retval = -EINVAL;

    if (!tiny)
        return -ENODEV;

    down(&tiny->sem);

    if (!tiny->open_count)
        /* port was not opened */
        goto exit;

    /* fake sending the data out a hardware port by
     * writing it to the kernel debug log.
     */
    printk(KERN_DEBUG "%s - ",__FUNCTION__);
    for (i = 0; i < count; ++i)
    {
        printk("%02x ",buffer[i]);        
    }
    printk("n");

exit:
    up(&tiny->sem);

    return retval;
}

static int tiny_write_room(struct tty_struct *tty) 
{
    struct tiny_serial *tiny = tty->driver_data;
    int room = -EINVAL;

    pr_info("%sn",__func__);

    if (!tiny)
        return -ENODEV;

    down(&tiny->sem);

    if (!tiny->open_count) {
        /* port was not opened */
        goto exit;
    }

    /* calculate how much room is left in the device */
    room = 255;

exit:
    up(&tiny->sem);
    return room;
}

static void tiny_set_termios(struct tty_struct *tty,struct ktermios *old_termios)
{
    pr_info("%sn",__func__);    
}

static int tiny_install(struct tty_driver *driver,struct tty_struct *tty)
{
    int retval = -ENOMEM;

    pr_info("%sn",__func__);

    tty->port = kmalloc(sizeof *tty->port,GFP_KERNEL);
    if (!tty->port)
        goto err;

    tty_init_termios(tty);
    driver->ttys[0] = tty;

    tty_port_init(tty->port);
    tty_buffer_set_limit(tty->port,8192);
    tty_driver_kref_get(driver);
    tty->count++;   

    return 0;

err:
    pr_info("%s - errn",__func__);
    kfree(tty->port);
    return retval;
}

static struct tty_operations serial_ops = {
    .open = tiny_open,.close = tiny_close,.write = tiny_write,.write_room = tiny_write_room,.set_termios = tiny_set_termios,.install        = tiny_install,};

static struct tty_driver *tiny_tty_driver;

static int __init tiny_init(void)
{
    int retval;

    pr_info("%sn",__func__);

#if defined(USE_SIMULATOR)
    init_waitqueue_head(&wq_thread);
    thread_id = NULL;
#endif /* USE_SIMULATOR */  

    /* allocate the tty driver */
    tiny_tty_driver = alloc_tty_driver(TINY_TTY_MINORS);
    if (!tiny_tty_driver)
        return -ENOMEM;

    /* initialize the tty driver */
    tiny_tty_driver->owner = THIS_MODULE;
    tiny_tty_driver->driver_name = "tiny_tty";
    tiny_tty_driver->name = "tiny_tty";
    tiny_tty_driver->major = TINY_TTY_MAJOR,tiny_tty_driver->type = TTY_DRIVER_TYPE_SYSTEM,tiny_tty_driver->subtype = SYSTEM_TYPE_CONSOLE,tiny_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV | TTY_DRIVER_UNNUMBERED_NODE,tiny_tty_driver->init_termios = tty_std_termios;
    tiny_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
    tty_set_operations(tiny_tty_driver,&serial_ops);

    /* register the tty driver */
    retval = tty_register_driver(tiny_tty_driver);
    if (retval) {
        printk(KERN_ERR "failed to register tiny tty driver");
        put_tty_driver(tiny_tty_driver);
        return retval;
    }

    tty_register_device(tiny_tty_driver,NULL);

    //tiny_install(tiny_tty_driver,tiny_table[0]->tty);
    return retval;
}

static void __exit tiny_exit(void)
{
    pr_info("%sn",__func__);

#if defined(USE_SIMULATOR)
    if(thread_id)
    {
        /* shut down our timer and free the memory */
        kill_pid(task_pid(thread_id),1);
        wait_for_completion(&on_exit);
    }
#endif /* USE_SIMULATOR */  

    tty_unregister_device(tiny_tty_driver,0);
    tty_unregister_driver(tiny_tty_driver);

    if (tiny_serial) {
        /* close the port */
        while (tiny_serial->open_count)
            do_close(tiny_serial);

        if(tiny_serial->tty->port)
        {
            kfree(tiny_serial->tty->port);
            tiny_serial->tty->port = NULL;
        }
        kfree(tiny_serial);
        tiny_serial = NULL;
    }
}

module_init(tiny_init);
module_exit(tiny_exit);

MODULE_AUTHOR("");
MODULE_DESCRIPTION("Tiny TTY driver");
MODULE_LICENSE("GPL");

解决方法

这个帖子已经有点陈旧了,但我偶然发现了同样的问题并决定分享解决方案:

>您可以通过在termios结构中设置适当的标志来关闭回声:

tiny_tty_driver->init_termios.c_lflag &= ~ECHO;

>这是因为tiny_write总是返回-EINVAL,设置retval = count;在返回修复此问题之前.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读