一、基本概念
终端是一种字符型设备,通常使用tty简称各种类型的终端。linux的终端类型:
/dev/ttySn,串行口终端
/dev/pty,伪终端
/dev/tty,当前进程的控制终端,可以是介绍的其它任何一种终端
/dev/ttyn,tty1~tty6是虚拟终端,tty0当前虚拟终端的别名。
/dev/console,控制台终端(显示器)
二、设备驱动结构
tty驱动中,除了tty核心,tty驱动,还有一个tty线路规程。tty线路规程的主要工作,将数据格式转换成协议格式。
特定tty设备驱动主体工作是填充tty_driver结构体成员。
struct tty_driver {
int magic; /* magic number for this structure */
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 */
int refcount; /* for loadable tty drivers */
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;
};
magic,设为TTY_DRIVER_MAGIC
name,设备节点名
driver_name,驱动名
type,subtype,描述tty驱动的类型和子类型。
type | subtype |
TTY_DRIVER_TYPE_SYSTEM | SYSTEM_TYPE_TTY |
SYSTEM_TYPE_CONSOLE | |
SYSTEM_TYPE_SYSCONS | |
SYSTEM_TYPE_SYSPTMX | |
TTY_DRIVER_TYPE_CONSOLE |
|
TTY_DRIVER_TYPE_SERIAL | SERIAL_TYPE_NORMAL |
TTY_DRIVER_TYPE_PTY | PTY_TYPE_MASTER/PTY_TYPE_SLAVE |
TTY_DRIVER_TYPE_SCC |
|
init_termios,为一个termios结构体,这个成员提供线路设置的集合。驱动会copy tty_std_termios变量来初始化这个结构成员。
ops,为一个tty_operations结构体,提供驱动的操作函数。
struct tty_operations {
int (*open)(struct tty_struct * tty, struct file * filp);
void (*close)(struct tty_struct * tty, struct file * filp);
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 (*read_proc)(char *page, char **start, off_t off,
int count, int *eof, void *data);
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 tty_struct *real_tty,
struct winsize *ws);
#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
};
flush_chars()与wait_until_sent()函数都用于刷新数据到硬件
write_room()指示有多少缓冲区空闲
char_in_buffer()指示缓冲中包含的数据数
set_termios()函数用于改变termios设置
throttle(),unthrottle(),当tty核心的输入缓冲区满时,throttle()函数将被调用,tty驱动试图通知设备不应当发送字符给它。当输入缓冲区清空,unthrottle()调用。
stop(),start(),停止和恢复数据发送
hangup(),挂起tty设备
breakctl()有关于RS-232端口状态
flush_buffer()刷新缓冲区并丢弃任何剩下的数据。
set_ldisc()设置线路规程(通常不需要被实现)
send_xchar(),”X- ”类型字符发送函数,(XON或XOFF)要发送的字符在第2个参数中指定。
read_proc()和write_proc(),/proc接口函数
tiocmget(),tiocmset()获得和设置tty线路规程的设置
相关的操作函数
1) 分配tty驱动
struct tty_driver *alloc_tty_driver(int lines);
2)注册tty驱动
int tty_register_driver(struct tty_driver *driver);
3)注销tty驱动
int tty_unregister_driver(struct tty_driver *driver);
4)注册tty设备
void tty_register_device(struct tty_driver *driver,unsigned index,struct device *device);
5)注销tty设备
void tty_unregister_device(struct tty_driver *driver,unsigned index);
6)设置tty驱动操作
void tty_set_operations(struct tty_driver *driver,struct tty_operations *op);
三、驱动编写
终端设备驱动围绕tty_driver结构体展开,一般完成以下两个步骤
1. 模块加载和卸载函数,完成注册和注销tty_driver, 初始化和释放终端设备对应的tty_driver结构体成员及硬件资源。
2. 实现tty_operations成员函数。
第一步实现实例(关于硬件资源申请,有待以后研究)
int uart_register_driver(struct uart_driver *drv)
{
struct tty_driver *normal = NULL;
int i, retval;
BUG_ON(drv->state);
/*
* Maybe we should be using a slab cache for this, especially if
* we have a large number of ports to handle.
*/
drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);
retval = -ENOMEM;
if (!drv->state)
goto out;
normal = alloc_tty_driver(drv->nr);
if (!normal)
goto out;
drv->tty_driver = normal;
normal->owner = drv->owner;
normal->driver_name = drv->driver_name;
normal->name = drv->dev_name;
normal->major = drv->major;
normal->minor_start = drv->minor;
normal->type = TTY_DRIVER_TYPE_SERIAL;
normal->subtype = SERIAL_TYPE_NORMAL;
normal->init_termios = tty_std_termios;
normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;
normal->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
normal->driver_state = drv;
tty_set_operations(normal, &uart_ops);
/*
* Initialise the UART state(s).
*/
for (i = 0; i < drv->nr; i++) {
struct uart_state *state = drv->state + i;
state->close_delay = 500; /* .5 seconds */
state->closing_wait = 30000; /* 30 seconds */
mutex_init(&state->mutex);
}
retval = tty_register_driver(normal);
out:
if (retval < 0) {
put_tty_driver(normal);
kfree(drv->state);
}
return retval;
}
关于tty_operations各成员函数,以后再做研究。tty_struct结构体被tty核心用来保存当前tty端口的状态。其中有一个driver_data的数据指针,可以用来指向设备的”私有”结构指针。
write实例(从buf指向的用户空间发送count个字节)
uart_write(struct tty_struct *tty, const unsigned char *buf, int count)
{
struct uart_state *state = tty->driver_data;
struct uart_port *port;
struct circ_buf *circ;
unsigned long flags;
int c, ret = 0;
/*
* This means you called this function _after_ the port was
* closed. No cookie for you.
*/
if (!state || !state->info) {
WARN_ON(1);
return -EL3HLT;
}
port = state->port;
circ = &state->info->xmit;
if (!circ->buf)
return 0;
spin_lock_irqsave(&port->lock, flags);
while (1) {
c = CIRC_SPACE_TO_END(circ->head, circ->tail, UART_XMIT_SIZE);
if (count < c)
c = count;
if (c <= 0)
break;
memcpy(circ->buf + circ->head, buf, c);
circ->head = (circ->head + c) & (UART_XMIT_SIZE - 1);
buf += c;
count -= c;
ret += c;
}
spin_unlock_irqrestore(&port->lock, flags);
uart_start(tty);
return ret;
}
tty_operations结构体中没有提供read()函数,tty核心在struct tty_filp_buffer结构中缓冲数据。调用tty_filp_buffer_push()可以将filp缓冲区刷新到用户。
模板实例
for(i=0;i<data_size;++i){
if (tty->flip.count>=TTY_FLIPBUF_SIZE)
tty_filp_buffer_push(tty);
tty_insert_flip_char(tty,data[i],TTY_NORMAL);
}
tty_flip_buffer_push(tty);
有关于TTY线路设置
用户空间有两种方式可以获得和设置当前线路设置:调用termios库函数和对tty设备节点进行ioctl调用。大部分termios用户空间函数被库函数转换为对驱动节点ioctl()调用。即用户空间的termios库函数首先转换成ioctl调用,然后再转换成对内核空间的iotctl调用。而tty ioctl中的大部分命令会被tty核心转换为set_termios()函数的调用。
void(*set_termios)(struct tty_stuct *tty,struct ktermios *old)
对于TIOCMGET,TIOCMSET,TIOCMBIC,TIOCMBIS则会转换为对tiocmget()和tiocmset()函数的调用。它们用于读取和设置Modem控制的设置。
还有TIOCSERGETLSR,TIOCGSERIAL,TIOCMIWAIT,TIOCGICOUNT这些命令则是直接调用驱动程序ioctl()函数调用。
四、UART设备驱动
由于linux已存在文件serial_core.c,它对tty设备中的UART设备驱动进行了一次封装(具体节省了多少力气,待议),编写tty驱动,就转化为:
1. 定义uart_driver,uart_ops,uart_port,初始化它们模块加载和卸载时调用uart_register_driver()和uart_add_one_port(),uart_unregister_driver()和uart_remove_one_port()
2. 实现uart_ops的成员函数
三个重要的结构体
1) uart_driver,它封装了一个tty_driver。
2) uart_port用于描述uart的物理端口信息
3) uart_ops,它的实现相当于tty_ops。
相关操作函数
注册和注销驱动
int uart_register_driver(struct uart_driver *drv);
void uart_unregister_driver(struct uart_driver *drv);
添加/去除端口
int uart_add_one_port(struct uart_driver *drv,struct uart_port *port);
int uart_remove_one_port(struct uart_driver *drv,struct uart_port *port);
上面两个函数包含了设备注册和注销函数
内核printk支持8个级别:KERN_EMERG,ERNEL_ALERT,KERN_CRIT,
KERN_ERR,KERN_WARNING,KERN_NOTICE,KERN_INFO,KERN_DEBUG
在linux中,用于printk输出的是内核console,专门用console结构体来描述。
struct console {
char name[16];
void (*write)(struct console *, const char *, unsigned);
int (*read)(struct console *, char *, unsigned);
struct tty_driver *(*device)(struct console *, int *);
void (*unblank)(void);
int (*setup)(struct console *, char *);
int (*early_setup)(void);
short flags;
short index;
int cflag;
void *data;
struct console *next;
};
write()用于将打印信息写入console,setup()用于设置console的特性,如波特率,停止位等。
内核用以下函数来注册/注销console
void register_console(struct console *);
int unregister_console(struct console *);
驱动使用的printk()函数经过层层调用,最终使用console的write()将控制台的消息打印出去。内核初始化时会调用console_init()函数,该函数会调用放在”.con_initcall.init”节中的代码。放置的方法是在驱动中使用console_initcall(void *function)。
为了在console_init()函数被调用前使用printk(),可以使用内核的“early printk”,对于early printk的console注册往往通过内核的early_param完成。
early_param(“earlycon”,setup_early_serial8250_console)
定义一个earlycon的内核参数,内核解析这个参数时调用setup_early_serial8250_console()函数。