框架
串口驱动、tty、shell的关系
串口驱动负责硬件字符。
tty是提供字符缓冲,形成tty框架。不仅可以从串口接收字符,也可以从网络等,如pty设备。
shell是用户态程序,负责解析字符的含义,是解释器,和python一样。tty上可以挂不同的解释器,如可以挂login程序用户登录等。
/etc/inittab中可以指定应用与tty的绑定关系。
分层
tty框架分为3层,tty core层、tty ldisc线路规程,定义解析协议,如hdlc、n_tty。普通命令行使用n_tty线路规程,只是将字符中转到buffer中。底层tty_driver中对接硬件驱动,对于串口来说,又套一层uart驱动。串口驱动实际只需要注册一个uart驱动即可。 tty_port描述端口,多个tty_port可以对应同一个tty_driver。如/dev/ttyS0、/dev/ttyS1对应同一个tty驱动、tty_struct,但是对应不同的串口,通过tty_struct->index区分。
数据结构
核心数据结构:
tty_struct对应tty设备,index对应driver的哪个device;
tty_operation: 是tty设备的ops,每一层都有一个ops结构。uart驱动会注册一个uart_ops, 其中iinstall函数会将tty_port和ttydevice绑定;
tty_port对应一个串口;
tty_driver,自定义tty驱动,注册ops;
tty_ldisc: tty线路规程。
tty_ldisc_ops:线路规程的ops,上层应用读写tty设备时会调用到tty_ldisc_ops的读写操作。如n_tty就是线规程中的一种。可以注册线规程的ops。
tty_buffer: 每个tty_port对应一个tty_buffer,保存串口接收的字符串信息。
uart
uart相关数据结构uart_driver和uart_port是tty_driver和tty_port的派生。uart_ops描述硬件的操作集,如s3cs4xx就注册特定的ops。
static struct uart_ops s3c24xx_serial_ops = {
.pm = s3c24xx_serial_pm,
.tx_empty = s3c24xx_serial_tx_empty,
.get_mctrl = s3c24xx_serial_get_mctrl,
.set_mctrl = s3c24xx_serial_set_mctrl,
.stop_tx = s3c24xx_serial_stop_tx,
.start_tx = s3c24xx_serial_start_tx,
.stop_rx = s3c24xx_serial_stop_rx,
.break_ctl = s3c24xx_serial_break_ctl,
......
}
tty_buffer
硬件中断收报会将字符保存在tty_buffer中。tty_bufhead是头结构,其中会保存tty_buffer的链表头和尾,以及一个worker,用于将tty_buffer内容上交给ldisc层。ldisc层如n_tty,会将buffer保存到n_tty_data的recv_buf中。另外echo_buf用于回显。
tty_buffer:
used: 当前buffer使用的字节数;
size: buffer大小;
commit: worker 上次提交给ldisc的位置;
read: 当前需要上交给ldisc的位置。
data[]: buffer的数据区。
void tty_buffer_init(struct tty_port *port)
{
struct tty_bufhead *buf = &port->buf;
mutex_init(&buf->lock);
/*初始空buffer*/
tty_buffer_reset(&buf->sentinel, 0);
buf->head = &buf->sentinel;
buf->tail = &buf->sentinel;
init_llist_head(&buf->free);
atomic_set(&buf->mem_used, 0);
atomic_set(&buf->priority, 0);
/*flush work*/
INIT_WORK(&buf->work, flush_to_ldisc);
buf->mem_limit = TTYB_DEFAULT_MEM_LIMIT;
}
字符流处理以及OPS
open操作
字符设备的fops最终会调用tty_ops, uart驱动注册时会注册一个tty_ops。
字符流
sumsung的串口驱动的中断接收函数s3c24xx_serial_rx_chars_pio(此处只将PIO方式,还有可选的DMA方式)从硬件中接收到数据,然后通过uart_port查找tty_port,再找到tty_port对应的tty_buffer,将字符copy到tty_buffer中,然后唤起tty_buffer的work,work调用flush_to_ldisc将数据再从tty_buffer copy到线路规程中,以n_tty为例,copy到n_tty_data的recv buffer中。tty_buffer和n_tty_data的recv buf都维护写指针和读指针。上层应用读取tty设备时,从n_tty_data的recv_buf中读取,如果是阻塞读,在收到数据时,串口驱动的irq处理中会唤醒进程。
数据流
s3c24xx串口中断收包函数:
s3c24xx_serial_rx_chars_pio
-> s3c24xx_serial_rx_chars_pio
->->uart_insert_char(写入到tty_buffer)
->->tty_flip_buffer_push(唤醒work,commit tty_buffer)
收包的数据结构走向: tty_port --> tty_client_ops --> tty_ldsic --> tty_ldisc_ops
void uart_insert_char(struct uart_port *port, unsigned int status,
unsigned int overrun, unsigned int ch, unsigned int flag)
{
struct tty_port *tport = &port->state->port;
if ((status & port->ignore_status_mask & ~overrun) == 0)
if (tty_insert_flip_char(tport, ch, flag) == 0)
++port->icount.buf_overrun;
/*
* Overrun is special. Since it's reported immediately,
* it doesn't affect the current character.
*/
if (status & ~port->ignore_status_mask & overrun)
if (tty_insert_flip_char(tport, 0, TTY_OVERRUN) == 0)
++port->icount.buf_overrun;
}
static inline int tty_insert_flip_char(struct tty_port *port,
unsigned char ch, char flag)
{
struct tty_buffer *tb = port->buf.tail;
int change;
change = (tb->flags & TTYB_NORMAL) && (flag != TTY_NORMAL);
if (!change && tb->used < tb->size) {
if (~tb->flags & TTYB_NORMAL)
*flag_buf_ptr(tb, tb->used) = flag;
*char_buf_ptr(tb, tb->used++) = ch;
return 1;
}
return __tty_insert_flip_char(port, ch, flag);
}
tty_flip_buffer_push:
void tty_flip_buffer_push(struct tty_port *port)
{
tty_schedule_flip(port);
}
void tty_schedule_flip(struct tty_port *port)
{
struct tty_bufhead *buf = &port->buf;
/* paired w/ acquire in flush_to_ldisc(); ensures
* flush_to_ldisc() sees buffer data.
*/
smp_store_release(&buf->tail->commit, buf->tail->used);
/*调度flush_work*/
queue_work(system_unbound_wq, &buf->work);
}
flush_to_ldisc将tty_buffer提交到ldisc层:
flush_to_ldisc
->receive_buf
->-> port->client_ops->receive_buf : tty_port_default_receive_buf
->->-> tty_ldisc_receive_buf
->->->-> ld->ops->receive_buf
->->->->-> n_tty 写入n_tty_data
console和earlycon
console设备是printk打印的输出,用户态进程起的tty取决于父进程的tty。可以通过cmdline指定console设备,名称虽然可能是tty,但是却没有用tty那套代码,而是使用console。
如:console=ttySAC0;cat /proc/cmdline查看cmdline中bootargs。
console相关API及全局变量定义在printk.c中:
__setup("console=", console_setup);
console_setup设置全局console结构为对应的tty设备。
全局console_drivers列表,通过注册console。
console_cmdline[]: 数组,用于通过cmdline设置console。console_setup在console_cmdline中查找console。
prefer_console: 是实际设置的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 (*match)(struct console *, char *name, int idx, char *options);
short flags;
short index;
int cflag;
void *data;
struct console *next;
};
s3c24xx为例:提供串口的写函数以及设置setup函数即可。
static struct console s3c24xx_serial_console = {
.name = S3C24XX_SERIAL_NAME, //"ttySAC"
.device = uart_console_device,
.flags = CON_PRINTBUFFER,
.index = -1,
.write = s3c24xx_serial_console_write,
.setup = s3c24xx_serial_console_setup,
.data = &s3c24xx_uart_drv,
};
static int __init s3c24xx_serial_console_init(void)
{
register_console(&s3c24xx_serial_console);
return 0;
}
register_console函数:将console注册到console_drivers链表和console_cmdline数组中。
early_printk
内核早期调试,console还没有设置,printk打印无法使用,可以开启early_printk功能,打印早期内容。通过cmdline传递earlycon即可打开early printk。
全局变量early_console :平台需要注册一个early console的decice。
arch/arm/kernel/early_printk.c文件中的early_printk函数 调用early_console的write函数:
#ifdef CONFIG_EARLY_PRINTK
struct console *early_console;
asmlinkage __visible void early_printk(const char *fmt, ...)
{
va_list ap;
char buf[512];
int n;
if (!early_console)
return;
va_start(ap, fmt);
n = vscnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
early_console->write(early_console, buf, n);
}
#endif
early_write 调用printascii打印字符。
printascii在汇编代码中实现,在文件arm/kernel/debug.S中:
ENTRY(printascii)
addruart_current r3, r1, r2
1: teq r0, #0
ldrneb r1, [r0], #1
teqne r1, #0
reteq lr
2: teq r1, #'\n'
bne 3f
mov r1, #'\r'
waituart r2, r3
senduart r1, r3
busyuart r2, r3
mov r1, #'\n'
3: waituart r2, r3
senduart r1, r3
busyuart r2, r3
b 1b
ENDPROC(printascii)
需要定义定义waituart,senduart,busyuart宏。