tty设备驱动程序设计

1. linux内核中,tty层次结构包括了:

a. tty 核心

b. tty 线路规程(discipline): 以协议转换的方式,格式化从一个用户或硬件收到的数据, 如PPP协议或蓝牙协议。

c. tty驱动

三层之间数据流通关系如下图所示:


tty设备发送数据流程: tty核心从用户获取想要发送给一个tty设备的数据,tty核心将数据传递给tty线路规程驱动,然后数据被传递到tty驱动,tty驱动将数据转换为可以发送给硬件的格式。

tty设备接收数据流程: 从硬件接收到的数据向上交给tty驱动,然后进入tty线路规程,再进入tty核心,在这里被一个用户获取。

尽管大多数时候tty核心和tty之间的数据传输会经历tty线路规程的转换,但是tty驱动与tty核心之间也可以直接传输数据。

2. tty_driver 和 tty_operation 结构体:

tty设备驱动的主要工作是填充tty_driver结构体中的成员,实现tty_operations结构体中的成员函数。

struct tty_driver {
    int    magic;        /* magic number for this structure */ //在alloc_tty_driver()函数中被初始化
    struct kref kref;    /* Reference management */
    struct cdev cdev;    //对应字符设备的cdev
    struct module    *owner;     //这个驱动模块的拥有者
    const char    *driver_name;   //驱动名,在/proc/tty 和 /sysfs 中使用 
    const char    *name;     //设备名,在 /dev 下使用,即驱动的设备节点名,如:ttySn
    int    name_base;    /* offset of printed name */ //为创建设备名字而使用的开始编号,在内核创建一个tty设备名称时使用。
    int    major;        /* major device number */ //驱动程序的主设备号
    int    minor_start;    /* start of minor device number */ //最小次设备号,通常与 name_base 值相同,经常为0.
    int    minor_num;    /* number of *possible* devices */ //此设备号的个数,也就是拥有相同驱动的设备的个数
    int    num;        /* number of devices allocated */ //同minor_num.
    short    type;        /* type of tty driver */
    short    subtype;    /* subtype of tty driver */
    struct ktermios init_termios; /* Initial termios */ //初始线路规程设置,用来提供一个线路(line)设置集合。
    int    flags;        /* tty driver flags */
    struct proc_dir_entry *proc_entry; /* /proc fs entry */ //proc入口结构体,如果驱动程序实现write_proc()或read_proc(),它将由tty 核心创建。
    struct tty_driver *other; /* only used for the PTY driver */ //指向tty从属设备驱动程序的指针,只能被pty驱动程序使用。

    /*
     * Pointer to the tty data structures
     */
    struct tty_struct **ttys;
    struct ktermios **termios;
    struct ktermios **termios_locked;
    void *driver_state;   //tty 驱动程序内部的状态,也只能被 pty 驱动程序使用。

    /*
     * Driver methods
     */
    const struct tty_operations *ops;
    struct list_head tty_drivers; //构成一个 tty 驱动程序链表。把所有不同的tty驱动程序连接起来。
};

--- type :

--- subtype :

描述 tty 驱动程序的类型,subtype 取决于 type. type 可以有以下几种类型:

TTY_DRIVER_TYPE_SYSTEM --- 在tty子系统内部使用,表明tty子系统正在处理一个内部的tty驱动程序。这种类型不能被常规的tty驱动程序使用

    subtype 值可以是:SYSTEM_TYPE_TTY, SYSTEM_TYPE_CONSOLE, SYSTEM_TYPE_SYSCONS, SYSTEM_TYPE_SYSPTMX

TTY_DRIVER_TYPE_CONSOLE --- 只被控制台驱动程序使用,CONSOLE.

TTY_DRIVER_TYPE_SERIAL --- 可以被任何串行类驱动程序使用。这是type最常用的设置之一。

   subtype 值可以为:SERIAL_TYPE_NORMAL, SERIAL_TYPE_CALLOUT,这取决于驱动程序的类型。

TTY_DRIVER_TYPE_PTY --- 伪终端接口(pty)使用。

   subtype值可以为:PTY_TYPE_MASTER, PTY_TYPE_SLAVE.

--- flags  :  tty驱动和 tty 核心都使用flags 来表明当前驱动程序的状态及该tty驱动程序的类型,可以是:

TTY_DRIVER_RESET_TERMIOS --- 该标志表示当最后一个进程关闭该设备时, tty 核心对 termios 复位。

   这对控制台和pty驱动程序非常有用。比如一个用户将终端设置为非常规状态,如果设置了该位,当用户退出时,或控制该会话进程关闭时,终端能自动恢复常规值。

TTY_DRIVER_REAL_RAW --- 该标志表示驱动程序使用奇偶校验或中断字符线路规程。

   这使得线路规程能比较快的方式接收字符,所有的tty驱动程序通常都设该位。

TTY_DRIVER_NO_DEVFS --- 该位表示 当tty_register_driver()时,tty核心不需要为tty驱动程序创建任何的 devfs 入口。

    这对于那些需要动态创建和删除次设备的驱动程序来说非常有用,比如:USB转串口驱动程序,USB调制解调器驱动程序,USB蓝牙tty驱动程序

TTY_DRIVER_INSTALLED --- 它由tty核心控制, 当tty驱动程序注册后,tty核心置该位,而不是tty驱动程序。

--- init_termios : 初始线路设置,也是termios结构体

--- termios :当前的线路设置,线路设置控制当前的波特率,数据大小,流控制等参数。

struct tty_operations {
    struct tty_struct * (*lookup)(struct tty_driver *driver,
            struct inode *inode, int idx);
    int (*install)(struct tty_driver *driver, struct tty_struct *tty);
    void (*remove)(struct tty_driver *driver, struct tty_struct *tty);
    int (*open)(struct tty_struct * tty, struct file * filp);
    void (*close)(struct tty_struct * tty, struct file * filp);
    void (*shutdown)(struct tty_struct *tty);
    void (*cleanup)(struct tty_struct *tty);
    int (*write)(struct tty_struct * tty,
         const unsigned char *buf, int count);
    int (*put_char)(struct tty_struct *tty, unsigned char ch);//单个字节写函数,把单个字节写入设备。
    void (*flush_chars)(struct tty_struct *tty);  //与wait_until_sent()一样,刷新数据到硬件。
    int (*write_room)(struct tty_struct *tty);   //表示有多少缓冲区空闲,
    int (*chars_in_buffer)(struct tty_struct *tty);  //缓冲区长度
    int (*ioctl)(struct tty_struct *tty, struct file * file,
         unsigned int cmd, unsigned long arg);
    long (*compat_ioctl)(struct tty_struct *tty, struct file * file,
             unsigned int cmd, unsigned long arg);
    void (*set_termios)(struct tty_struct *tty, struct ktermios * old);
    void (*throttle)(struct tty_struct * tty);   //用于控制tty核心输入缓存,当tty核心输入缓存满时,throttle()函数将被调用,tty驱动试图通知设备不该发送字符给它了。
    void (*unthrottle)(struct tty_struct * tty); //当tty核心输入缓冲被清空后,unthrottle()函数被调用,暗示设备可以接受数据了,
    void (*stop)(struct tty_struct *tty);
    void (*start)(struct tty_struct *tty);
//stop()和start(),跟throttle()和unthrottle()类似,他们表示tty驱动应该停止发送数据给设备,和恢复发送数据给设备。
    void (*hangup)(struct tty_struct *tty); //tty驱动挂起tty设备,在此函数中进行相关的硬件操作。
    int (*break_ctl)(struct tty_struct *tty, int state);  //线路中断控制函数被调用
    void (*flush_buffer)(struct tty_struct *tty);   //用于刷新缓冲区,并丢弃任何剩下的数据。
    void (*set_ldisc)(struct tty_struct *tty);      //设置线路规程,当tty核心改变tty驱动的线路规程时,这个函数被调用。
    void (*wait_until_sent)(struct tty_struct *tty, int timeout);
    void (*send_xchar)(struct tty_struct *tty, char ch);   //X-类型字符发送函数。
    int (*tiocmget)(struct tty_struct *tty, struct file *file);   //获取tty设备的线路设置
    int (*tiocmset)(struct tty_struct *tty, struct file *file,    //设置tty设备的线路设置, 参数set和clear包含了要设置或清除的线路设置。
            unsigned int set, unsigned int clear);
    int (*resize)(struct tty_struct *tty, struct winsize *ws);
    int (*set_termiox)(struct tty_struct *tty, struct termiox *tnew); //当设备的termios设置被改变时,set_termios()函数被tty核心调用。
    int (*get_icount)(struct tty_struct *tty,
                struct serial_icounter_struct *icount);
#ifdef CONFIG_CONSOLE_POLL
    int (*poll_init)(struct tty_driver *driver, int line, char *options);
    int (*poll_get_char)(struct tty_driver *driver, int line);
    void (*poll_put_char)(struct tty_driver *driver, int line, char ch);
#endif
    const struct file_operations *proc_fops;
};

3.
termios 结构体:

用来描述当前线路设置,这些线路设置控制当前波特率,数据大小,数据流控制 等。

typedef unsigned char    cc_t;
typedef unsigned int    speed_t;
typedef unsigned int    tcflag_t;

#define NCCS 19
struct termios {
    tcflag_t c_iflag;        /* input mode flags */  //输入模式标志
    tcflag_t c_oflag;        /* output mode flags */  //输出模式标志
    tcflag_t c_cflag;        /* control mode flags */ //控制模式标志
    tcflag_t c_lflag;        /* local mode flags */   //本地模式标志
    cc_t c_line;            /* line discipline */     //线路规程类型
    cc_t c_cc[NCCS];        /* control characters */  //控制字符数组
};

struct termios2 {
    tcflag_t c_iflag;        /* input mode flags */
    tcflag_t c_oflag;        /* output mode flags */
    tcflag_t c_cflag;        /* control mode flags */
    tcflag_t c_lflag;        /* local mode flags */
    cc_t c_line;            /* line discipline */
    cc_t c_cc[NCCS];        /* control characters */
    speed_t c_ispeed;        /* input speed */
    speed_t c_ospeed;        /* output speed */
};

struct ktermios {
    tcflag_t c_iflag;        /* input mode flags */
    tcflag_t c_oflag;        /* output mode flags */
    tcflag_t c_cflag;        /* control mode flags */
    tcflag_t c_lflag;        /* local mode flags */
    cc_t c_line;            /* line discipline */
    cc_t c_cc[NCCS];        /* control characters */
    speed_t c_ispeed;        /* input speed */
    speed_t c_ospeed;        /* output speed */
};
驱动会用一个标准的数值集初始化这个成员,这个数值集源于tty_std_termios变量:

struct ktermios tty_std_termios = {    /* for the benefit of tty drivers */
    .c_iflag = ICRNL | IXON,  //输入模式
    .c_oflag = OPOST | ONLCR,  //输出模式
    .c_cflag = B38400 | CS8 | CREAD | HUPCL,   //控制模式
    .c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK |   
         ECHOCTL | ECHOKE | IEXTEN,  //本地模式
    .c_cc = INIT_C_CC,               //控制字符,用来修改终端的特殊字符映射。
    .c_ispeed = 38400,
    .c_ospeed = 38400
};

EXPORT_SYMBOL(tty_std_termios); 

4. Linux内核提供了一组操作 tty_driver 和 tty设备的函数:

4.1 struct tty_driver alloc_tty_driver(int lines);  ---分配tty驱动

该函数返回 tty_driver指针,

参数lines 是要分配的设备数量,lines会被赋值给tty_driver的num成员。

magic 成员会在这里赋值,如TTY_DRIVER_MAGIC。

定义如下:

struct tty_driver *alloc_tty_driver(int lines)
{
	struct tty_driver *driver;

	driver = kzalloc(sizeof(struct tty_driver), GFP_KERNEL);
	if (driver) {
		kref_init(&driver->kref);
		driver->magic = TTY_DRIVER_MAGIC;
		driver->num = lines;
		/* later we'll move allocation of tables here */
	}
	return driver;
}
EXPORT_SYMBOL(alloc_tty_driver);
使用例子:

xxx_tty_driver = alloc_tty_driver(XXX_TTY_MINORS);
if(!xxx_tty_driver)    //分配失败
    return -ENOMEM;

4.2 int tty_register_driver(struct tty_driver *driver); --- 注册tty驱动

成功时返回0,参数为 由 alloc_tty_driver()分配的 tty_driver 指针。

定义如下:

  1. /*
     * Called by a tty driver to register itself.
     */
    int tty_register_driver(struct tty_driver *driver)
    {
        int error;
        int i;
        dev_t dev;
        void **p = NULL;
        struct device *d;
    
        if (!(driver->flags & TTY_DRIVER_DEVPTS_MEM) && driver->num) {
            p = kzalloc(driver->num * 2 * sizeof(void *), GFP_KERNEL);
            if (!p)
                return -ENOMEM;
        }
    
        if (!driver->major) {
            error = alloc_chrdev_region(&dev, driver->minor_start,   //如果没有主设备号,则直接注册一系列字符设备号
                            driver->num, driver->name);
            if (!error) {
                driver->major = MAJOR(dev);                         //然后获得主次设备号。
                driver->minor_start = MINOR(dev);
            }
        } else {
            dev = MKDEV(driver->major, driver->minor_start);         //如果有主设备号,则直接获取设备号
            error = register_chrdev_region(dev, driver->num, driver->name); //再注册一系列字符设备
        }
        if (error < 0) {     //如果注册字符设备失败,则出错处理
            kfree(p);
            return error;
        }
    
        if (p) {
            driver->ttys = (struct tty_struct **)p;    //tty_struct 赋值
            driver->termios = (struct ktermios **)(p + driver->num); // 设置线路规程 tty_driver->termios
        } else {
            driver->ttys = NULL;
            driver->termios = NULL;
        }
    
        cdev_init(&driver->cdev, &tty_fops);    //字符设备初始化
        driver->cdev.owner = driver->owner;
        error = cdev_add(&driver->cdev, dev, driver->num);   //添加字符设备
        if (error) {
            unregister_chrdev_region(dev, driver->num);
            driver->ttys = NULL;
            driver->termios = NULL;
            kfree(p);
            return error;
        }
    
        mutex_lock(&tty_mutex);
        list_add(&driver->tty_drivers, &tty_drivers);   //添加到链表
        mutex_unlock(&tty_mutex);
    
        if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) { //如果是动态设备
            for (i = 0; i < driver->num; i++) {
                d = tty_register_device(driver, i, NULL);
                if (IS_ERR(d)) {
                    error = PTR_ERR(d);
                    goto err;
                }
            }
        }
        proc_tty_register_driver(driver);    //procfs 注册
        driver->flags |= TTY_DRIVER_INSTALLED;  //完成后,flags 表示向tty core注册driver了。
        return 0;
    
    err:
        for (i--; i >= 0; i--)
            tty_unregister_device(driver, i);
    
        mutex_lock(&tty_mutex);
        list_del(&driver->tty_drivers);
        mutex_unlock(&tty_mutex);
    
        unregister_chrdev_region(dev, driver->num);
        driver->ttys = NULL;
        driver->termios = NULL;
        kfree(p);
        return error;
    }
    EXPORT_SYMBOL(tty_register_driver);
    
    例子:
    retval = tty_register_driver(xxx_tty_driver);
    if(retval) {
    	printk(KERN_ERR "failed to register tiny tty driver");
    	put_tty_driver(xxx_tty_driver); //减小tty_driver的引用计数
    	return retval;
    }
    int tty_unregister_driver(struct tty_driver *driver); --- 注销tty驱动;
    /*
     * Called by a tty driver to unregister itself.
     */
    int tty_unregister_driver(struct tty_driver *driver)
    {
    #if 0
        /* FIXME */
        if (driver->refcount)
            return -EBUSY;
    #endif
        unregister_chrdev_region(MKDEV(driver->major, driver->minor_start),
                    driver->num);
        mutex_lock(&tty_mutex);
        list_del(&driver->tty_drivers);
        mutex_unlock(&tty_mutex);
        return 0;
    }
    EXPORT_SYMBOL(tty_unregister_driver);
    

    4.3 void tty_register_device(struct tty_driver *driver, unsigned index, struct device *device); --- 注册tty设备
    仅有 tty_driver 是不够的,驱动必须依附于设备。

    index --- 设备的索引号
    定义如下:

    /**
     *    tty_register_device - register a tty device
     *    @driver: the tty driver that describes the tty device
     *    @index: the index in the tty driver for this tty device
     *    @device: a struct device that is associated with this tty device.
     *        This field is optional, if there is no known struct device
     *        for this tty device it can be set to NULL safely.
     *
     *    Returns a pointer to the struct device for this tty device
     *    (or ERR_PTR(-EFOO) on error).
     *
     *    This call is required to be made to register an individual tty device
     *    if the tty driver's flags have the TTY_DRIVER_DYNAMIC_DEV bit set. If
     *    that bit is not set, this function should not be called by a tty
     *    driver.
     *
     *    Locking: ??
     */
    struct device *tty_register_device(struct tty_driver *driver, unsigned index,
                     struct device *device)
    {
        char name[64];
        dev_t dev = MKDEV(driver->major, driver->minor_start) + index;
    
        if (index >= driver->num) {
            printk(KERN_ERR "Attempt to register invalid tty line number "
             " (%d).\n", index);
            return ERR_PTR(-EINVAL);
        }
    
        if (driver->type == TTY_DRIVER_TYPE_PTY)   //如果是pty设备,伪终端
            pty_line_name(driver, index, name);
        else
            tty_line_name(driver, index, name);
    
        return device_create(tty_class, device, dev, NULL, name); //在/sys/device下建立tty类
    }
    EXPORT_SYMBOL(tty_register_device);
    

    例子:

    for (i = 0; i < XXX_TTY_MINORS; ++i)
    	tty_register_device(xxx_tty_driver, i, NULL);
    void tty_unregister_device(struct tty_driver *driver, unsigned index);  --- 注销tty设备

    /**
     *     tty_unregister_device - unregister a tty device
     *     @driver: the tty driver that describes the tty device
     *     @index: the index in the tty driver for this tty device
     *
     *     If a tty device is registered with a call to tty_register_device() then
     *    this function must be called when the tty device is gone.
     *
     *    Locking: ??
     */
    
    void tty_unregister_device(struct tty_driver *driver, unsigned index)
    {
        device_destroy(tty_class,
            MKDEV(driver->major, driver->minor_start) + index);
    }
    EXPORT_SYMBOL(tty_unregister_device);
    

    例子:

    for (i = 0; i < XXX_TTY_MINORS; ++i)
    	tty_unregister_device(xxx_tty_driver, i);
    
     4.4  void tty_set_operations(struct tty_driver *driver, struct tty_operations *op);   --- 设置 tty 驱动操作函数:

    1. 该函数会把 tty_operaions 结构体中的函数指针,赋值到 tty_driver 对应的函数指针。
      在具体 tty 驱动中,通常会定义一个设备特定的 tty_operations。

    1. void tty_set_operations(struct tty_driver *driver,
                  const struct tty_operations *op)
      {
          driver->ops = op;
      };
      EXPORT_SYMBOL(tty_set_operations);
      
      

      终端设备驱动都围绕tty_driver结构体展开,一般应包含:
      1. 终端设备驱动模块加载和卸载函数:完成注册和注销tty_driver,初始化和释放终端设备对应的tty_driver结构体和硬件资源。
      2. 实现tty_operations结构体中的一系列成员:主要有open(),close(),write(),tiocmget(),tiocmset().


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值