内核tty框架_串口_tty_shell的关系

框架

串口驱动、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宏。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值