注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Tsecer的回音岛

Tsecer的博客

 
 
 

日志

 
 

Linux下TTY设备  

2011-08-21 21:18:28|  分类: Linux内核 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

一、概述

在计算机网络出现之前,能够最早将不同的终端连接起来,并且最早支持的多用户均是使用终端来完成的。即使在现在,很多的比较常用的嵌入式系统中,终端依然扮演着比较重要的作用,而且从实现上看,内核中的控制台和键盘同样是作为终端来存在,甚至我们最终使用XWIndows系统同样和控制台有关。

二、终端的实现

在《Linux内核源代码情景分析》中的第8.7节中,对于这个tty设备进行了说明,其中非常重要具有指导性的说明就是位于下面的说明:
显然,伪终端设备不同于常规的终端设备,它们的驱动程序也理应有所不同。但是事实上却共享同一个file_operation结构,即tty_fops。事实上,主设备号为2、3、4、5的设备,以及分配给各家串口厂商的主设备号对应的字符设备,全部使用同一个file_operations数据结构。事实上,除了file_operations之外,每个设备还存在两种不同的设备
tty_driver,另一个是tty_ldisc
可以看到,tty_fops是直接调用ldisc中的write然后是链路层的ldisc才来调用驱动层的一个写入操作,也就是链路层是在中间一层,而driver层则是在最底层的一个分层结构

首先明确这个事实对理解终端的框架很重要,也是对分层理解的一个重要指导性说明。每种不同的终端,通过tty_driver_register借口来完成自己特有的(相对于所有的终端共享的tty_fops结构),这个结构是所有的终端设备最终区分于彼此的一个分水岭。

一般不同的终端通过tty_register_driver接口注册自己作为某种特殊tty设备的专有底层操作,但是按照一般的实现套路,在注册这个设备之前,都会通过tty_set_operation接口来设置这个tty_driver中的一些驱动操作。这个所有终端共享的tty_fops位于tty_io.c中,我们将会看到,通过tty_register_driver注册的tty_operation接口将会在tty_open函数中赋值给一个动态创建的tty_struct结构的同样类型的成员中

三、常见的8250串口终端配置

1、内核功能使能和配置

NS8250是PC中常见的标准串口配置,这一点在《Linux内核源代码剖析》中对这个硬件芯片进行了比较详细的说明。从这个命名上可以看到,好像和intel的命名规则比较类似,例如intel的8259A中断控制器、8253定时器等是一个系列的

由于这个是PC的标准配置,所以对于Linux2.6.37中对于386系统的default配置中默认选配了这个功能。

\linux-2.6.37.1\arch\x86\configs\i386_defconfig

#
# Serial drivers
#
CONFIG_SERIAL_8250=y
CONFIG_SERIAL_8250_CONSOLE=y
CONFIG_SERIAL_8250_PCI=y
CONFIG_SERIAL_8250_PNP=y
CONFIG_SERIAL_8250_NR_UARTS=4
CONFIG_SERIAL_8250_RUNTIME_UARTS=4

然后在linux-2.6.37.1\drivers\serial\Makefile

obj-$(CONFIG_SERIAL_8250) += 8250.o
obj-$(CONFIG_SERIAL_8250_CONSOLE) += 8250_early.o
obj-$(CONFIG_SERIAL_8250_PNP) += 8250_pnp.o
obj-$(CONFIG_SERIAL_8250_PCI) += 8250_pci.o

这也就是说我们的默认配置是配置了8250异步收发控制器芯片的驱动的,并且其中的8250.c是一定会被编译进入内核的,而且配置的串口的数目为4个,当然其它的配置我们以后用到再说。

2、初始化

serial8250_init---》》uart_register_driver

 tty_set_operations(normal, &uart_ops);
 retval = tty_register_driver(normal);

在tty_register_driver函数中,执行

 cdev_init(&driver->cdev, &tty_fops);这里非常霸道的把所有通过tty_register_driver注册的tty设备的文件操作全部设置成了我们说的所有tty设备共享的一个tty_fops结构,这个结构就是所有的设备通过fopen打开一个tty设备时执行的第一个操作。

3、设备的打开

所有的tty设备打开的时候执行的都是tty_open函数,这个函数将会完成一个tty_struct的分配、拼凑以及初始化工作。为什么动态创建呢?因为首先系统中的tty设备在没有使用的时候没有必要劳苦大众的创建,另外一个tty_struct结构作为一个动态生成的结构,它的tty_driver核tty_ldisc两个都是需要在运行时配置的,所以最好在打开的时候动态创建。

 driver = get_tty_driver(device, &index);首先通过设备号找到之前通过tty_register_driver注册的对应的该设备需要的tty_driver结构。

  tty = tty_init_dev(driver, index, 0);--》》

 tty = alloc_tty_struct();首先分配一个tty_struct结构,这个结构的分配没有什么可说的,就是分配了一个一穷二白的空白页,也就是这个tty_struct结构中什么实质性的内容都没有。

initialize_tty_struct该函数将会完成执行的大部分初始化操作---》》》

 tty_ldisc_init(tty);
void tty_ldisc_init(struct tty_struct *tty)
{
 struct tty_ldisc *ld = tty_ldisc_get(N_TTY);
 if (IS_ERR(ld))
  panic("n_tty: init_tty");
 tty_ldisc_assign(tty, ld);
}

在initialize_tty中一个不起眼的地方,同样执行了一个非常重要的操作,就是driver中设置的文件操作的复制和转移

 tty->ops = driver->ops;这个语句看起来不起眼的,但是完成将driver特有的操作向这个对应的特殊tty的转移

这里就使用了在tty_ldisc.c中注册的n_tty设备

void tty_ldisc_begin(void)
{
 /* Setup the default TTY line discipline. */
 (void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);
}
不同的tty驱动可以安装自己的intall函数,这个依然是由框架来负责完成tty_driver_install_tty。

 if (driver->ops->install) {
  ret = driver->ops->install(driver, tty);
  return ret;
 }

然后在tty_oepn函数中,直接调用了不同的驱动的open函数

 if (!retval) {
  if (tty->ops->open)
   retval = tty->ops->open(tty, filp);看来这个open还是驱动必须有的一个东东
  else
   retval = -ENODEV;
 }

 

4、设备的写入

tty_write----》》》

  ret = do_tty_write(ld->ops->write, tty, file, buf, count);这里传入链路层的写入操作函数,这个函数将会在接下来的do_tty_write中使用
  ret = write(tty, file, tty->write_buf, size);
由于这里是n_tty的设备操作,所以执行的操作为

n_tty_write

    c = tty->ops->write(tty, b, nr);调用driver设置的write接口
    if (c < 0) {
     retval = c;
     goto break_out;
    }
    if (!c)
     break;
    b += c;
    nr -= c;
   }
  }
  if (!nr)
   break;
  if (file->f_flags & O_NONBLOCK) {对文件设置这个标志,可以防止串口阻塞
   retval = -EAGAIN;
   break;
  }

5、8250的写入

由于Uart对于tty设备来说,它属于tty的一种,但是同样是uart设备,在不同的体系机构中又有不同的实现和不同的芯片,所以对于同样是uart操作,其中也定义了一类uart_ops,而系统中所有的uart设备对tty设备只提供了一个uart_ops的tty_operation类型的结构实例:

static const struct tty_operations uart_ops = {
 .open  = uart_open,
 .close  = uart_close,
 .write  = uart_write,
 .put_char = uart_put_char,
 .flush_chars = uart_flush_chars,
 .write_room = uart_write_room,
 .chars_in_buffer= uart_chars_in_buffer,
 .flush_buffer = uart_flush_buffer,
 .ioctl  = uart_ioctl,
 .throttle = uart_throttle,
 .unthrottle = uart_unthrottle,
 .send_xchar = uart_send_xchar,
 .set_termios = uart_set_termios,
 .set_ldisc = uart_set_ldisc,
 .stop  = uart_stop,
 .start  = uart_start,
 .hangup  = uart_hangup,
 .break_ctl = uart_break_ctl,
 .wait_until_sent= uart_wait_until_sent,
#ifdef CONFIG_PROC_FS
 .proc_fops = &uart_proc_fops,
#endif
 .tiocmget = uart_tiocmget,
 .tiocmset = uart_tiocmset,
 .get_icount = uart_get_icount,
#ifdef CONFIG_CONSOLE_POLL
 .poll_init = uart_poll_init,
 .poll_get_char = uart_poll_get_char,
 .poll_put_char = uart_poll_put_char,
#endif
};


static int uart_write(struct tty_struct *tty,
     const unsigned char *buf, int count)
--->>>

 uart_start(tty);

static void __uart_start(struct tty_struct *tty)
{
 struct uart_state *state = tty->driver_data;
 struct uart_port *port = state->uart_port;

 if (!uart_circ_empty(&state->xmit) && state->xmit.buf &&
     !tty->stopped && !tty->hw_stopped)
  port->ops->start_tx(port);
}

这个state是在uart_register_driver中初始化的

int uart_register_driver(struct uart_driver *drv)
{
 struct tty_driver *normal;
 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);
 if (!drv->state)
  goto out;
…………

for (i = 0; i < drv->nr; i++) {
  struct uart_state *state = drv->state + i;
  struct tty_port *port = &state->port;

  tty_port_init(port);
  port->ops = &uart_port_ops;
  port->close_delay     = 500; /* .5 seconds */
  port->closing_wait    = 30000; /* 30 seconds */
  tasklet_init(&state->tlet, uart_tasklet_action,
        (unsigned long)state);
 }

6、uart的写入linux-2.6.37.1\drivers\serial\8250.c

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,


static void serial8250_start_tx(struct uart_port *port)
{
 struct uart_8250_port *up = (struct uart_8250_port *)port;

 if (!(up->ier & UART_IER_THRI)) {* Enable Transmitter holding register int. */该寄存器使能发送保持中断,也就是当发送寄存器空闲的时候发出中断。
  up->ier |= UART_IER_THRI;
  serial_out(up, UART_IER, up->ier);

  if (up->bugs & UART_BUG_TXEN) {
   unsigned char lsr;
   lsr = serial_in(up, UART_LSR);
   up->lsr_saved_flags |= lsr & LSR_SAVE_FLAGS;
   if ((up->port.type == PORT_RM9000) ?
    (lsr & UART_LSR_THRE) :
    (lsr & UART_LSR_TEMT))
    transmit_chars(up);
  }
 }

 /*
  * Re-enable the transmitter if we disabled it.
  */
 if (up->port.type == PORT_16C950 && up->acr & UART_ACR_TXDIS) {
  up->acr &= ~UART_ACR_TXDIS;
  serial_icr_write(up, UART_ACR, up->acr);
 }
}
7、发送操作
static void transmit_chars(struct uart_8250_port *up)
{
 struct circ_buf *xmit = &up->port.state->xmit;
 int count;

 if (up->port.x_char) {
  serial_outp(up, UART_TX, up->port.x_char);
  up->port.icount.tx++;
  up->port.x_char = 0;
  return;
 }
 if (uart_tx_stopped(&up->port)) {
  serial8250_stop_tx(&up->port);
  return;
 }
 if (uart_circ_empty(xmit)) {
  __stop_tx(up);
  return;
 }

 count = up->tx_loadsz;/* transmit fifo load size */由于之后的一些改进型UART芯片可以在发送16甚至更多个字符之后才产生一个中断,所以这里尽可能多的一次性多发送一些数据
 do {
  serial_out(up, UART_TX, xmit->buf[xmit->tail]);
  xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
  up->port.icount.tx++;
  if (uart_circ_empty(xmit))
   break;
 } while (--count > 0);

 if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
  uart_write_wakeup(&up->port);

 DEBUG_INTR("THRE...");

 if (uart_circ_empty(xmit))
  __stop_tx(up);
}
8、中断的处理

对于中断函数位于serial8250_interrupt函数中,其中的处理也相当的淡定。

do {
  struct uart_8250_port *up;
  unsigned int iir;

  up = list_entry(l, struct uart_8250_port, list);

  iir = serial_in(up, UART_IIR);
  if (!(iir & UART_IIR_NO_INT)) {首先是读取中断标示寄存器,也就是判断此次中断是由于发送完成、接收到字符或者校验错误等生成的中断,然后调用响应的接口进行处理
   serial8250_handle_port(up);这个函数就是进行真正中断处理的函数,里面的逻辑也非常简单,大家有时间有兴趣的话可以看一下

9、波特率的设置

这个波特率首先是通过termios传递给内核,但是这个波特率的定时并不是内核通过软件的定时器实现的,而是通过寄存处的一个值写入UART芯片的一个特定寄存器中完成的。当然,这个值的计算就责无旁贷的落在了内核驱动的身上了。

对于8250来说 ,它的接入时钟频率为1.8432MHZ,然后内部再将这个频率降频16倍,从而形成自己的内部频率,然后波特率就要基于这个值来进行计算。注意,这个频率是各个硬件芯片自己确定的,并且必须是已知的,否则无法通过波特率计算出将要写入寄存器的。

对于我们最为熟悉的386处理器来说,其定义位于

linux-2.6.37.1\arch\x86\include\asm\serial.h

/*
 * This assumes you have a 1.8432 MHz clock for your UART.
 *
 * It'd be nice if someone built a serial card with a 24.576 MHz
 * clock, since the 16550A is capable of handling a top speed of 1.5
 * megabits/second; but this requires the faster clock.
 */
#define BASE_BAUD ( 1843200 / 16 )

/* Standard COM flags (except for COM4, because of the 8514 problem) */
#ifdef CONFIG_SERIAL_DETECT_IRQ
#define STD_COM_FLAGS (ASYNC_BOOT_AUTOCONF | ASYNC_SKIP_TEST | ASYNC_AUTO_IRQ)
#define STD_COM4_FLAGS (ASYNC_BOOT_AUTOCONF | ASYNC_AUTO_IRQ)
#else
#define STD_COM_FLAGS (ASYNC_BOOT_AUTOCONF | ASYNC_SKIP_TEST)
#define STD_COM4_FLAGS ASYNC_BOOT_AUTOCONF
#endif

#define SERIAL_PORT_DFNS   \
 /* UART CLK   PORT IRQ     FLAGS        */   \
 { 0, BASE_BAUD, 0x3F8, 4, STD_COM_FLAGS }, /* ttyS0 */ \
 { 0, BASE_BAUD, 0x2F8, 3, STD_COM_FLAGS }, /* ttyS1 */ \
 { 0, BASE_BAUD, 0x3E8, 4, STD_COM_FLAGS }, /* ttyS2 */ \
 { 0, BASE_BAUD, 0x2E8, 3, STD_COM4_FLAGS }, /* ttyS3 */

#endif /* _ASM_X86_SERIAL_H */

在函数serial8250_do_set_termios中,其中完成了通过波特率和硬件时钟计算出其设置的寄存器值的代码

 quot = serial8250_get_divisor(port, baud);---》》》  quot = (port->uartclk + (8 * baud)) / (16 * baud);
然后通过 serial_dl_write(up, quot);接口将这个值写入硬件中,从而完成波特率的设置

/* Uart divisor latch write */
static inline void _serial_dl_write(struct uart_8250_port *up, int value)
{
 serial_outp(up, UART_DLL, value & 0xff);
 serial_outp(up, UART_DLM, value >> 8 & 0xff);
}
#define UART_DLL  0 /* Out: Divisor Latch Low */
#define UART_DLM  1 /* Out: Divisor Latch High */

10、硬件驱动模式及原因
这里涉及到所有驱动都必须面对的一个问题:就是硬件的接受或者缓冲问题:
例如,对于8250控制起来说,它的接受和发送都是只有一个寄存器,这样我们发送一串字符的时候,只能先向发送寄存器中写入一个字符,此时寄存器被占满,然后只有等到这个寄存器被的内容被发送出去的消息发送出去之后才能继续下一次的发送。所以就需要在中断处理函数中判断是否发送队列还有数据需要发送,如果有的话就继续,否则退出。这一点在硬盘的编程中也可以看到这个模式,这些都是通过中断来进行中转和继续。

 

本质上都是由于硬件的缓存能力有限造成的。所以真正的触发只是一个导火索,开始一个之后,需要在中断处理中进行队列判断,并继续将这个多米诺骨牌一直传递下去,因为硬件不允许一次传递多个,只能等硬件缓冲使用完之后才开始下一次,而也只有中断才知道当前这次什么时候传递结束

  评论这张
 
阅读(3543)| 评论(1)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017