tty驱动详解
来源:互联网 发布:多媒体网络触控一体机 编辑:程序博客网 时间:2024/05/16 10:37
/dev/tty代表当前tty设备,在当前的终端中输入 echo “hello” > /dev/tty ,都会直接显示在当前的终端中。/dev/ttyS*是串行终端设备。
/dev/pty*即伪终端,所谓伪终端是逻辑上的终端设备,多用于模拟终端程序。例如,我们在X Window下打开的终端,以及我们在Windows使用telnet 或ssh等方式登录Linux主机,此时均在使用pty设备(准确的说应该pty从设备)。
/dev/tty0代表当前虚拟控制台,而/dev/tty1等代表第一个虚拟控制台,例如当使用ALT+F2进行切换时,系统的虚拟控制台为/dev/tty2 ,当前的控制台则指向/dev/tty2
使用tty命令可以确定当前的终端或者控制台。
在2.1.71之前,/dev/console根据不同系统的设定可以链接到/dev/tty0或者其他tty*上,在2.1.71版本之后则完全由内核控制。目前,只有在单用户模式下可以登录/dev/console(可以在单用户模式下输入tty命令进行确认)。
只有2410的2.6才叫ttySAC0,9200等的还是叫ttyS0
在驱动起来的时候调用了uart_register_driver(&serial8250_reg);函数将参数serial8250_reg注册进了tty层。
int uart_register_driver(struct uart_driver *drv)
{
struct tty_driver *normal = NULL;
int i, retval;
drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);
normal = alloc_tty_driver(drv->nr);
drv->tty_driver = normal;
normal->driver_state = drv;
tty_set_operations(normal, &uart_ops);
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);
tty_port_init(&state->info.port);
init_waitqueue_head(&state->info.delta_msr_wait);
tasklet_init(&state->info.tlet, uart_tasklet_action,
(unsigned long)state);
}
retval = tty_register_driver(normal);
}
uart_driver中很多数据结构其实就是tty_driver中的,将数据转换为tty_driver之后,注册tty_driver。然后初始化uart_driver->state的存储空间。
normal->driver_state = drv; 这一句将参数的ops关系都赋给了serial_core层。也就是说在后面serial_core会根据uart_ops关系找到我们的8250.c中所对应的操作,而我们参数中的ops是在哪被赋值的呢?定位到了8250.c中的serial8250_ops结构体,初始化如下:
static struct uart_ops serial8250_pops = {
.tx_empty = serial8250_tx_empty,
.set_mctrl = serial8250_set_mctrl,
.get_mctrl = serial8250_get_mctrl,
.stop_tx = serial8250_stop_tx,
.start_tx = serial8250_start_tx,
.stop_rx = serial8250_stop_rx,
.enable_ms = serial8250_enable_ms,
.break_ctl = serial8250_break_ctl,
.set_termios = serial8250_set_termios,
.pm = serial8250_pm,
.request_port = serial8250_request_port,
.config_port = serial8250_config_port,
};
在uart_add_one_port()函数中,从serial8250_init->serial8250_register_ports()->uart_add_
one_port()逐步调用过来的,这一步就将port和uart_driver联系起来了。
tty_set_operations(normal, &uart_ops); 将tty_driver的操作集统一设为了uart_ops.这样就使得从用户空间下来的操作可以找到正确的serial_core的操作函数,uart_ops是在serial_core.c中的:
static const struct tty_operations uart_ops = {
.open = uart_open,
.write = uart_write,
.put_char = uart_put_char,
.flush_chars = uart_flush_chars,
.write_room = uart_write_room,
.flush_buffer = uart_flush_buffer,
.ioctl = uart_ioctl,
.send_xchar = uart_send_xchar,
.set_termios = uart_set_termios,
.set_ldisc = uart_set_ldisc,
.start = uart_start,
.break_ctl = uart_break_ctl,
.wait_until_sent= uart_wait_until_sent,
.tiocmget = uart_tiocmget,
.tiocmset = uart_tiocmset,
};
这样就保证了调用关系的通畅。
2 自顶向下
tty_core是所有tty类型的驱动的顶层构架,向用户应用层提供了统一的接口,应用层的read/write等调用首先会到达这里。代码主要分布在drivers/char目录下的n_tty.c,tty_io.c等文件中:
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,
};
此层调用函数的结构体,在uart_register_driver()函数中我们调用了每个tty类型的驱动注册时都会调用的tty_register_driver函数:
int tty_register_driver(struct tty_driver * driver)
{
cdev_init(&driver->cdev, &tty_fops); //将指针调用关系赋给了cdev
}
Read和write操作就会交给line discipline处理:
static ssize_t tty_read(struct file *file, char __user *buf, size_t count,
loff_t *ppos)
{
ld = tty_ldisc_ref_wait(tty);
if (ld->ops->read)
i = (ld->ops->read)(tty, file, buf, count); //调用到了ldisc层(线路规程)的read函数
tty_ldisc_deref(ld);
}
static ssize_t tty_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
ld = tty_ldisc_ref_wait(tty);
ret = do_tty_write(ld->ops->write, tty, file, buf, count);
tty_ldisc_deref(ld);
return ret;
}
static inline ssize_t do_tty_write(ssize_t (*write)(struct tty_struct *, struct file *, const unsigned char *, size_t), struct tty_struct *tty, struct file *file, const char __user *buf, size_t count)
{
for (;;) {
copy_from_user(tty->write_buf, buf, size);
ret = write(tty, file, tty->write_buf, size);//调用到了ldisc层的write函数
}
}
在drivers/char/n_tty.c文件中,下面的代码是线路规程中的write函数:
static ssize_t n_tty_write(struct tty_struct *tty, struct file *file,
const unsigned char *buf, size_t nr)
{
add_wait_queue(&tty->write_wait, &wait);//将当前进程放到等待队列中
while (1) {
if (O_OPOST(tty) && !(test_bit(TTY_HW_COOK_OUT, &tty->flags))) {
while (nr > 0) {
ssize_t num = process_output_block(tty, b, nr);
b += num;
nr -= num;
c = *b;
if (tty->ops->flush_chars)
tty->ops->flush_chars(tty);
} else {
while (nr > 0) {
c = tty->ops->write(tty, b, nr); //调用到具体的驱动中的write函数
b += c;
nr -= c;
}
}
/* 假如是以非阻塞的方式打开的,那么也直接返回。否则,让出cpu,等条件满足以后再继续执行。*/
schedule();
}
}
c = tty->ops->write(tty, b, nr)是调用了serial_core的write()函数,在tty_core进行open的时候悄悄把tty->ops指针给赋值了。具体的代码就在driver/char/tty_io.c中,调用关系如下所示:
tty_open -> tty_init_dev -> initialize_tty_struct,initialize_tty_struct()函数的代码在下面:
void initialize_tty_struct(struct tty_struct *tty,
struct tty_driver *driver, int idx)
{
tty->ops = driver->ops;
}
这里就将serial_core层的操作调用关系指针值付给了tty_core层,这样tty->ops->write()其实调用到了具体的驱动的write函数
在open操作调用到serial_core层的时候有下面的代码:
static int uart_open(struct tty_struct *tty, struct file *filp)
{
struct uart_driver *drv = (struct uart_driver *)tty->driver->driver_state; //在uart_register_driver()的时候所做的一些事情,那时我们是放进去,现在是拿出来而已。
struct uart_state *state;
int retval, line = tty->index;
……
uart_update_termios(state);
}
}
tty_driver也就是serial_core交互
在tty设备文件的操作中。Open操作会进行一系统初始化。然后调用ldsic->open tty_driver->open。在write和read调用中只tty_core只会用到 ldisc->wirte/ldisc->read。ioctl操作,以及它封装的几个 termios。这些ioctl类的操作会直接和tty_driver相关联.
tty 和console这些概念主要是一些虚设备的概念,而串口更多的是指一个真正的设备驱动Tty实际是一类终端I/O设备的抽象,它实际上更多的是一个管理的概念,它和tty_ldisc(行规程)和tty_driver(真实设备驱动)组合在一起。每个描述tty设备的 tty_struct在初始化时必然挂了某个具体芯片的字符设备驱动,可以是很多,包括显卡或串口chip,这些驱动实际上都有而console是一个缓冲的概念,它的目的有一点类似于tty实际上 console不仅和tty连在一起,还和framebuffer连在一起。Tty的一个子集需要使用 console(典型的如主设备号4,次设备号1―64),但是要注意的是没有console的tty是存在的,而串口则指的是tty_driver。
printk不一定是将信息往控制台上输出,设置kernel的启动参数可能可以打到将信息送到显示器的效果。
Linux内核中 tty的层次结构图所示,包含tty核心、tty线路规程和tty驱动。tty核心是对整个tty设备的抽象,对用户提供统一的接口,tty线路规程是对传输数据的格式化,tty驱动则是面向tty设备的驱动,这是对设备的驱动,应该要我们来实现的。
tty设备发送数据的流程为:tty核心从一个用户获取将要发送给一个 tty设备的数据,tty核心将数据传递给tty线路规程驱动,接着数据被传递到tty驱动,tty驱动将数据转换为可以发送给硬件的格式。
接收数据的流程为: 从tty硬件接收到的数据向上交给tty驱动,进入tty线路规程驱动,再进入 tty 核心,在这里它被一个用户获取。尽管大多数时候tty核心和tty之间的数据传输会经历tty线路规程的转换,但是tty驱动与tty核心之间也可以直接传输数据。
三、tty设备驱动结构
tty_io.c定义了tty 设备通用的file_operations结构体并实现了接口函数。tty_io.c也提供了tty_register_ldisc()接口函数用于注册线路规程,n_tty.c文件则实现了tty_disc结构体中的成员。特定tty设备驱动的主体工作是填充tty_driver结构体中的成员,实现其中的成员函数。
四、串口驱动核心层
由tty设备驱动到串口驱动中间经过了一层serial_core ,从tty设备驱动中需要填充的是tty_driver结构,经过串口核心层后就转变成了实现xxx_uart.c 。到现在linux系统已经封装了终端设备(tty)的驱动,而我们只需要实现串口驱动就能实现整个串口终端驱动。
五、主要结构与关系
对于tty驱动层主要有几个重要的结构
serial_core实现了UART设备的通用TTY驱动层(称为串口核心层),这样UART驱动的主要任务演变成了实现serial_core中定义的一组uart_xxx接口而非tty_xxx接口。
----------------设备方法-----------------------------设备注册------------------------设备信息
------------tty_operations------------------------tty_driver---------------------------tty-struct // tty核心层定义,serial_core(串口核心层实现的结构体)
--------------uart_ops-----------------------------uart_driver---------------------------uart_port // 串口核心层定义,需要我们实现
----------s3c24xx_serial-ops-----------------s3c24xx_uart_drv-----------------s3c24xx_uart_port
s3c24xx_uart_port主要包含一下几个结构
uart_port
s3c24xx_uart_info ——>主要是UART中的FIFO信息,需要修改
s3c34xx_uart_clksrc ——>UART时钟、频率 ,需要修改
另外在加上一个结构体 s3c2410_uartcfg ——>硬件端口、寄存器 ,这个需要修改
用户空间主要是通过设备文件同tty_core交互.tty_core根据用空间操作的类型再选择跟line discipline和tty_driver交互.设置硬件的ioctl指令就直接交给tty_driver处理。Read和write操作就会交给 line discipline处理.
Line discipline是线路规程的意思,它表示的是这条终端”线程”的输入与输出规范设置.主要用来进行输入/输出数据的预处理。处理之后就会将数据交给tty_driver.
没有为tty_driver提供read操作。也就是说tty_core 和line discipline都没有办法从tty_driver里直接读终端信息。在line discipline中有一个输入缓存区。并提供了一个名叫receive_buf()的接口函数。只要调用line discipine的receiver_buf函数,将数据写入到输入缓存区就可以了。
三:tty驱动接口分析
tty driver的所有操作都包含在tty_driver中。alloc_tty_driver()来分配这个tty_driver,
tty_register_driver() 将其注册.
alloc_tty_driver()接口代码如下所示:
struct tty_driver *alloc_tty_driver(int lines)
{
struct tty_driver *driver;
driver = kzalloc(sizeof(struct tty_driver), GFP_KERNEL);
if (driver) {
driver->magic = TTY_DRIVER_MAGIC;
driver->num = lines;
}
return driver;
}
line的个数即次设备号的个数。注意每个设备文件都会对应一个line.
tty_register_driver()用来注册一个tty_driver。代码如下:
int tty_register_driver(struct tty_driver *driver)
{
dev_t dev;
void **p = kzalloc(driver->num * 3 * sizeof(void *), GFP_KERNEL);
//注册字符设备号
dev = MKDEV(driver->major, driver->minor_start);
error = register_chrdev_region(dev, driver->num, driver->name);
if (p) {
driver->ttys = (struct tty_struct **)p;
driver->termios = (struct ktermios **)(p + driver->num);
driver->termios_locked = (struct ktermios **)
(p + driver->num * 2);
}
//注册字符设备
cdev_init(&driver->cdev, &tty_fops);
driver->cdev.owner = driver->owner;
error = cdev_add(&driver->cdev, dev, driver->num);
//指定默认的put_char
if (!driver->put_char)
driver->put_char = tty_default_put_char;
list_add(&driver->tty_drivers, &tty_drivers); //将tty_driver 挂载到tty_drivers链表中.以设备号为关键字找到对应的driver.
//如果没有指定TTY_DRIVER_DYNAMIC_DEV.即动态设备管理
proc_tty_register_driver(driver);
return 0;
}
四:设备文件的操作
设备文件的操作是本节分析的重点。它的主要操作是将各项操作对应到ldsic或者是tty_driver.
4.1:打开tty设备的操作
从注册的过程可以看到,所有的操作都会对应到tty_fops中。Open操作对应的操作接口是tty_open()。代码如下:
static int tty_open(struct inode *inode, struct file *filp)
{
struct tty_struct *tty;
int noctty, retval;
struct tty_driver *driver;
int index;
dev_t device = inode->i_rdev;
unsigned short saved_flags = filp->f_flags;
nonseekable_open(inode, filp);
retry_open:
//O_NOCTTY 如果路径名指向终端设备,不要把这个设备用作控制终端
//noctty:需不需要更改当前进程的控制终端
noctty = filp->f_flags & O_NOCTTY;
//设备号(5,0) 即/dev/tty.表示当前进程的控制终端
if (device == MKDEV(TTYAUX_MAJOR, 0)) {
tty = get_current_tty();
//取得当前进程的tty_driver
driver = tty->driver;
index = tty->index;
filp->f_flags |= O_NONBLOCK; /* Don't let /dev/tty block */
goto got_driver;
}
#ifdef CONFIG_VT
//设备号(4,0).即/dev/tty0:表示当前的控制台
if (device == MKDEV(TTY_MAJOR, 0)) {
extern struct tty_driver *console_driver;
driver = console_driver;
//fg_console: 表示当前的控制台
index = fg_console;
noctty = 1;
goto got_driver;
}
#endif
//设备号(5,1).即/dev/console.表示外接的控制台. 通过regesit_console()
if (device == MKDEV(TTYAUX_MAJOR, 1)) {
driver = console_device(&index);
if (driver) {
/* Don't let /dev/console block */
filp->f_flags |= O_NONBLOCK;
noctty = 1;
goto got_driver;
}
}
//以文件的设备号为关键字,到tty_drivers中搜索所注册的driver
driver = get_tty_driver(device, &index);
got_driver:
//index表示它的次设备号
retval = init_dev(driver, index, &tty);
filp->private_data = tty;
file_move(filp, &tty->tty_files);
check_tty_count(tty, "tty_open");
if (tty->driver->type == TTY_DRIVER_TYPE_PTY &&
tty->driver->subtype == PTY_TYPE_MASTER)
noctty = 1;
if (!retval) {
if (tty->driver->open)
retval = tty->driver->open(tty, filp);
}
filp->f_flags = saved_flags;
}
注意在这里有个容易忽略的操作:init_dev()。
Init_dev() ----> initialize_tty_struct() -----> tty_ldisc_assign(tty, tty_ldisc_get(N_TTY));
看一下tty_ldisc_assign(tty, tty_ldisc_get(N_TTY))的操作:
Tty_ldisc_get()到tty_ldiscs[ ]找到对应项.这个数组中的成员是调用tty_register_ldisc()将其设置进去的.
tty_ldisc_assign操作如下:
static void tty_ldisc_assign(struct tty_struct *tty, struct tty_ldisc *ld)
{
tty->ldisc = *ld;
tty->ldisc.refcount = 0;
}
即将取出来的idisc作为tty->ldisc字段.
在这段代码中涉及到了tty_driver,tty_struct, struct tty_ldisc.
tty_struct的ldisc是默认指定为tty_ldiscs[N_TTY].该ldisc对应的是控制终端的线路规范。可以在用空间用带TIOCSETD的ioctl调用进行更改.
4.2:设备文件的write操作
设备文件的write操作对应tty_fops->write即tty_write().代码如下:
static ssize_t tty_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
struct tty_struct *tty;
struct inode *inode = file->f_path.dentry->d_inode;
ssize_t ret;
struct tty_ldisc *ld;
tty = (struct tty_struct *)file->private_data;
ld = tty_ldisc_ref_wait(tty);
ret = do_tty_write(ld->write, tty, file, buf, count);
tty_ldisc_deref(ld);
}
在open的过程中,将tty_struct存放在file的私有区。在write中,从file的私有区中就可以取到要操作的tty_struct.如果一切正常,递增ldsic的引用计数。用do_tty_wirte()再行写操作。写完之后,再递减ldsic的引用计数.
Do_tty_write代码分段分析如下:
static inline ssize_t do_tty_write( ssize_t (*write)(struct tty_struct *, struct file *, const unsigned char *, size_t),
struct tty_struct *tty, struct file *file, const char __user *buf, size_t count)
{
ssize_t ret, written = 0;
unsigned int chunk;
ret = tty_write_lock(tty, file->f_flags & O_NDELAY);
chunk = 2048;
if (test_bit(TTY_NO_WRITE_SPLIT, &tty->flags))
chunk = 65536;
if (count)
chunk = count;
tty->write_cnt = chunk;
tty->write_buf = buf;
}
默认一次写数据的大小为2K.如果设置了TTY_NO_WRITE_SPLIT.则将一次写的数据量扩大为65536.Tty->write_buf是写操作的临时缓存区。即将用户空的数据暂时存放到这里。
Tty->write_cnt是临时缓存区的大小。必须要根据一次写的数据量对这个临时缓存区做调整
/* Do the write .. */
for (;;) {
size_t size = count;
if (size > chunk)
size = chunk;
copy_from_user(tty->write_buf, buf, size);
ret = write(tty, file, tty->write_buf, size);
written += ret;
buf += ret;
count -= ret;
if (signal_pending(current))
break;
cond_resched();
}
if (written) {
struct inode *inode = file->f_path.dentry->d_inode;
inode->i_mtime = current_fs_time(inode->i_sb);
ret = written;
}
}
将用户空间的数据copy到临时缓存区,然后再调用ldisc->write()完成这次写操作.最后再更新设备结点的时间戳。在ldisc中对写入的数据做预处理过后,还是会调用tty_driver->write()将其写入硬件.
4.3:设备文件的read操作
static ssize_t tty_read(struct file *file, char __user *buf, size_t count,
loff_t *ppos)
{
int i;
struct tty_struct *tty;
struct inode *inode;
struct tty_ldisc *ld;
tty = (struct tty_struct *)file->private_data;
inode = file->f_path.dentry->d_inode;
i = (ld->read)(tty, file, buf, count);
}
直接调用ldsic->read()完成工作
- tty驱动详解
- linux2.6.28-tty设备驱动学习(二) tty设备驱动详解
- Linux TTY 驱动
- TTY设备驱动结构
- tty驱动初始化
- tty驱动快速参考
- tty驱动调试<一>
- 四 linux tty驱动
- 四 linux tty驱动
- linux 终端 tty驱动
- linux驱动子系统--TTY
- Linux tty 终端设备驱动
- [2]tty驱动列子
- tty驱动框架分析
- s3c2440 tty驱动
- uart驱动与tty驱动
- dev/tty文件详解
- Linux tty驱动学习 - LDD3的tty驱动
- LOOPS(hdu3853,概率DP入门)
- Cenos 系统进不去 方法大全
- Fragment
- JAVA_WEB Struts2学习:Struts2常用标签
- 多校十Hdu 4704 Sum
- tty驱动详解
- android软键盘的一些控制
- hpux- hp小型机日常硬件故障处理case(三)
- Collecting Bugs(poj2096)
- 快速幂取模讲解&模板
- 机器学习_算法_kmeans聚类
- cocos2d-x for android:SimpleGame分析
- 得到数组的最后一个数的趣味实现
- Football(poj3071,概率DP)