在Linux中,UART串口驱动完全遵循tty驱动的框架结构,但是进行了底层操作的再次封装,所以先介绍tty终端设备驱动。
一、终端设备
1.串行端口终端(/dev/ttySACn)
2.伪终端(/dev/pty/)
3.控制台终端(/dev/ttyn,/dev/console)
二、驱动结构
1.tty分层结构如下图所示:
包含tty核心、tty线路规程、tty驱动,其中tty规程的工作是以特殊的方式格式化从一个用户或者硬件接收到的数据,常采用一个协议转换的形式,如PPP、Bluetooth。
2.tty主要源文件关系及数据流向如下图:
特定的tty设备驱动的主体工作是填充tty_driver结构体中的成员,实现tty_operations结构体中的一系列成员函数
struct tty_driver {int magic; /* magic number for this structure */struct kref kref; /* Reference management */struct cdev cdev;struct module *owner;const char *driver_name;//驱动名字const char *name;//驱动设备结点名字int name_base; /* offset of printed name */int major; /* major device number */int minor_start; /* start of minor device number */int minor_num; /* number of *possible* devices */int num; /* number of devices allocated */short type; /* type of tty driver */short subtype; /* subtype of tty driver */struct ktermios init_termios; /* Initial termios */int flags; /* tty driver flags */struct proc_dir_entry *proc_entry; /* /proc fs entry */struct tty_driver *other; /* only used for the PTY driver *//** Pointer to the tty data structures*/struct tty_struct **ttys;struct ktermios **termios;struct ktermios **termios_locked;void *driver_state;/** Driver methods*/const struct tty_operations *ops;struct list_head tty_drivers; };
tty_operations中的成员函数需在特定设备tty驱动模块初始化函数中被赋值;
tty_struct结构体被tty核心用来保存当前tty端口的状态。
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);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);void (*unthrottle)(struct tty_struct * tty);void (*stop)(struct tty_struct *tty);void (*start)(struct tty_struct *tty);void (*hangup)(struct tty_struct *tty);int (*break_ctl)(struct tty_struct *tty, int state);void (*flush_buffer)(struct tty_struct *tty);void (*set_ldisc)(struct tty_struct *tty);void (*wait_until_sent)(struct tty_struct *tty, int timeout);void (*send_xchar)(struct tty_struct *tty, char ch);int (*tiocmget)(struct tty_struct *tty, struct file *file);int (*tiocmset)(struct tty_struct *tty, struct file *file,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); #ifdef CONFIG_CONSOLE_POLLint (*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); #endifconst struct file_operations *proc_fops; };
struct tty_struct {int magic;struct kref kref;struct tty_driver *driver;const struct tty_operations *ops;int index;/* Protects ldisc changes: Lock tty not pty */struct mutex ldisc_mutex;struct tty_ldisc *ldisc;struct mutex termios_mutex;spinlock_t ctrl_lock;/* Termios values are protected by the termios mutex */struct ktermios *termios, *termios_locked;struct termiox *termiox; /* May be NULL for unsupported */char name[64];struct pid *pgrp; /* Protected by ctrl lock */struct pid *session;unsigned long flags;int count;struct winsize winsize; /* termios mutex */unsigned char stopped:1, hw_stopped:1, flow_stopped:1, packet:1;unsigned char low_latency:1, warned:1;unsigned char ctrl_status; /* ctrl_lock */unsigned int receive_room; /* Bytes free for queue */struct tty_struct *link;struct fasync_struct *fasync;struct tty_bufhead buf; /* Locked internally */int alt_speed; /* For magic substitution of 38400 bps */wait_queue_head_t write_wait;wait_queue_head_t read_wait;struct work_struct hangup_work;void *disc_data;void *driver_data;struct list_head tty_files;#define N_TTY_BUF_SIZE 4096/** The following is data for the N_TTY line discipline. For* historical reasons, this is included in the tty structure.* Mostly locked by the BKL.*/unsigned int column;unsigned char lnext:1, erasing:1, raw:1, real_raw:1, icanon:1;unsigned char closing:1;unsigned char echo_overrun:1;unsigned short minimum_to_wake;unsigned long overrun_time;int num_overrun;unsigned long process_char_map[256/(8*sizeof(unsigned long))];char *read_buf;int read_head;int read_tail;int read_cnt;unsigned long read_flags[N_TTY_BUF_SIZE/(8*sizeof(unsigned long))];unsigned char *echo_buf;unsigned int echo_pos;unsigned int echo_cnt;int canon_data;unsigned long canon_head;unsigned int canon_column;struct mutex atomic_read_lock;struct mutex atomic_write_lock;struct mutex output_lock;struct mutex echo_lock;unsigned char *write_buf;int write_cnt;spinlock_t read_lock;/* If the tty has a pending do_SAK, queue it here - akpm */struct work_struct SAK_work;struct tty_port *port; };
tty_io.c:
【1】定义了tty 设备通用的file_operations结构体:
static const struct file_operations tty_fops = {.llseek = no_llseek,.read = tty_read,.write = tty_write,.poll = tty_poll,.unlocked_ioctl = tty_ioctl,.compat_ioctl = tty_compat_ioctl,.open = tty_open,.release = tty_release,.fasync = tty_fasync, };
【2】实现了接口函数alloc_tty_driver()用于分配tty驱动:
/*tty driver 的所有操作都包含在 tty_driver 中。内核即供了一个名叫 alloc_tty_driver() 来分配这个 tty_driver。当然我们也可以在自己的驱动中将它定义成一个静态的结构。对 tty_driver 进行一些必要的初始化之后,调用 tty_register_driver()将其注册. */ 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; }
【3】实现了接口函数tty_set_operations()用于设置tty驱动操作:
/*将tty_operations结构体中的函数指针拷贝给tty_driver对应的函数指针*/ void tty_set_operations(struct tty_driver *driver,const struct tty_operations *op) {driver->ops = op; };
【4】实现了接口函数tty_register_driver()用于注册tty设备:
/** Called by a tty driver to register itself. * 注册 tty 驱动成功时返回 0;参数为由 alloc_tty_driver()分配的 tty_driver 结构体指针。 *这个函数操作比较简单。就是为 tty_driver 创建字符设备。然后将字符设备的操作集指定 *为 tty_fops.并且将 tty_driver 挂载到 tty_drivers 链表中。以设备号为关键字找到对应 *的 driver. */ int tty_register_driver(struct tty_driver *driver) {int error;int i;dev_t dev;void **p = NULL;/*TTY_DRIVER_DEVPTS_MEM:使用 devpts 进行动态内存映射*/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) {/*如果没有指定 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) {/*设置tty数据结构指针*/driver->ttys = (struct tty_struct **)p;/*tty_struct结构体被tty核心用来保存当前端口的状态*/driver->termios = (struct ktermios **)(p + driver->num);/*ktermios保存当前的线路设置*/} 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);/*将 tty_driver 挂载到 tty_drivers 链表中*/mutex_unlock(&tty_mutex);/*如果没有指定 TTY_DRIVER_DYNAMIC_DEV.即动态设备管理 */if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {for (i = 0; i < driver->num; i++)tty_register_device(driver, i, NULL);/*注册tty设备*/}/** This function is called by tty_register_driver() to handle* registering the driver's /proc handler into /proc/tty/driver/<foo>*/proc_tty_register_driver(driver);driver->flags |= TTY_DRIVER_INSTALLED;return 0; }