Reference
https://www.kernel.org/doc/Documentation/serial/driver
Introduction
波特率115200,bps每秒传输的bit数。
每一位1/115200秒,传输1byte需要10位(start, data, stop),那么每秒能传11520byte。
115200,8n1。8:data,n:校验位不用,1:停止位。
TTY体系中设备节点的差别
不关心终端是真实的还是虚拟的,都可以通过/dev/tty找到当前终端。
/dev/console
内核的打印信息可以通过cmdline来选择打印到哪个设备。
console=ttyS0 console=tty
console=ttyS0时,/dev/console就是ttyS0
console=ttyN时,/dev/console就是/dev/ttyN
console=tty时,/dev/console就是前台程序的虚拟终端
console=tty0时,/dev/console就是前台程序的虚拟终端
console有多个取值时,使用最后一个取值来判断。
/dev/tty 和/dev/tty0区别
/dev/tty
表示当前进程的控制终端,也就是当前进程与用户交互的终端。
/dev/tty0
则是当前所使用虚拟终端的一个别名
Linux串口应用编程
https://digilander.libero.it/robang/rubrica/serial.htm
struct termios options;
open("/dev/ttyS1", O_RDWR | O_NOCTTY | O_NDELAY)// O_NOCTTY: 不用作控制终端 O_NDELAY: 使I/O变成非阻塞模式
fcntl(fd, F_SETFL, 0): //读数据时,没有数据则阻塞等待
fcntl(fd, F_SETFL, FNDELAY): //读数据时不等待,没有数据就返回0
/* c_cflag: Control Options */
options.c_cflag |= (CLOCAL | CREAD); // 必须打开 Enable the receiver and set local mode
options.c_cflag &= ~CSIZE; /* Mask the character size bits */
options.c_cflag |= CS8; /* Select 8 data bits */
cfsetispeed(&options, B19200); //设置input output波特率
cfsetospeed(&options, B19200);
options.c_cflag &= ~PARENB // no parity
options.c_cflag &= ~CSTOPB
options.c_cflag &= ~CNEW_RTSCTS; // RTS CTS
/* c_lflag: Local Options */
options.c_lflag |= (ICANON | ECHO | ECHOE); // 选择规范输入 Canonical Input
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // 选择原始输入 Raw Input
/* c_iflag: Input Options*/
opt.c_iflag &= ~INPCK;
/* c_oflag: Input Options*/
options.c_oflag &= ~OPOST; // raw output. When the OPOST option is disabled, all other option bits in c_oflag are ignored.
tcsetattr(fd, TCSANOW, &options);
VMIN: 读数据时的最小字节数,没读到这些数据就不返回
VTIME: 等待第一个数据的时间,比如VTIME=1,表示10秒内一个数据都没有的话就返回,如果10秒内至少读到一个字节,就继续等待,完全读到VMIN个数据返回。 VTIME=0表示一直等待。
Timeouts are ignored in canonical input mode or when the *NDELAY* option is set on the file via *open* or *fcntl*.
raw mode可以设置VMIN和VTIME,canonical input mode不用设置。
8250 Uart driver
几个重要的结构体
uart_driver:通过int uart_register_driver(struct uart_driver *drv)
注册进内核。
static struct uart_driver serial8250_reg = {
.owner = THIS_MODULE, ///拥有该uart_driver的模块,一般为THIS_MODULE
.driver_name = "serial", ///串口驱动名,串口设备文件名以驱动名为基础
.dev_name = "ttyS", /// 串口设备名
.major = TTY_MAJOR,
.minor = 64,
.cons = SERIAL8250_CONSOLE, /// 其对应的console.若该uart_driver支持serial console,否则为NULL
};
serial8250_reg.nr = UART_NR; /// 支持最大的串口数量 UART_NR == 3
uart_port:对应一个串口设备,通过int uart_add_one_port(struct uart_driver *drv,struct uart_port *port)
向该驱动添加uart_port
uart_ops:串口操作函数,为uart_port的成员。
uart_8250_port:8250 serial driver抽象出来的结构体,包含了uart_port以及别的8250需要的成员。
dw8250_data:dw 820 serial driver抽象出来的结构体
注意到uart_port
和uart_ops
中有相同的回调函数,这里的调用顺序为:首先uart_ops
会检查uart_port
是否实现了相同的回调函数,如果实现了则调用uart_port
,否则调用uart_ops
。具体可以参考8250_dw.c
中的p->set_termios = dw8250_set_termios
和8250_port.c
中的.set_termios = serial8250_set_termios
8250_core.c
注册uart_driver
注册uart_port(后面更底层的8250_dw.c会注册uart_port替换)
//8250_core.c
struct uart_8250_port *up = &serial8250_ports[i];
struct uart_port *port = &up->port;
port->line = i;
port->ops = &serial8250_pops;
port->has_sysrq = IS_ENABLED(CONFIG_SERIAL_8250_CONSOLE);
up->cur_iotype = 0xFF;
port->ops = &univ8250_port_ops;
up->dl_read = default_serial_dl_read;
up->dl_write = default_serial_dl_write;
port->serial_in = io_serial_in;
port->serial_out = io_serial_out;
up->cur_iotype = p->iotype; /// p->iotype=0
univ8250_port_ops = *base_ops; /// 相当于又赋值回来port->ops = &serial8250_pops;
serial8250_init(); /// 内核打印: Serial: 8250/16550 driver, 3 ports, IRQ sharing disabled
serial8250_isa_init_ports();
serial8250_init_port(); // 8250_port.c
serial8250_set_defaults();
set_io_from_upio();
uart_register_driver();
tty_register_driver();
serial8250_register_ports(); /// 注册三个uart_port(serial8250_ports)进uart_driver.
uart_add_one_port(drv, &up->port);
uart_configure_port(); // 会直接返回 因为port->iobase没初始化
serial8250_probe();
console_initcall(univ8250_console_init);
serial8250_isa_init_ports
register_console
8250_dw.c
在8250_core之后注册。
// 8250_dw.c
struct uart_8250_port uart = {}, *up = &uart;
struct uart_port *p = &up->port;
p->type = PORT_8250;
p->flags = UPF_SHARE_IRQ | UPF_FIXED_PORT | UPF_BOOT_AUTOCONF; //最后一个在之后append
p->dev = dev;
p->iotype = UPIO_MEM;
p->set_ldisc = dw8250_set_ldisc;
p->set_termios = dw8250_set_termios; /// 在dw8250_quirks中又p->set_termios=NULL
p->membase = devm_ioremap(dev, regs->start, resource_size(regs));
p->private_data = &data->data;
p->iotype = UPIO_MEM32;
p->serial_in = dw8250_serial_in32;
p->serial_out = dw8250_serial_out32;
p->line = id;
p->set_termios = NULL;
port->type = PORT_16550A;
up->capabilities = UART_CAP_FIFO;
dw8250_probe();
dw8250_quirks();
serial8250_register_8250_port();
serial8250_find_match_or_unused();
uart_remove_one_port();
unregister_console(); // 内核打印:printk: console [ttyS1] disabled
serial8250_set_defaults();
uart_add_one_port();
uart_configure_port();
port->ops->config_port(port, flags); // 调用到8250_port.c中uart_ops的config_port
autoconfig();
autoconfig_16550a();
uart_report_port();// 打印: ...ttyS1 at MMIO 0x18810100 (irq = 13, base_baud = 1500000) is a 16550A
register_console();// 内核打印:printk: console [ttyS1] enabled
try_enable_new_console();
newcon->setup(); // 这里调用到8250_core.c中univ8250_console.setup创建console
unregister_console();//内核打印:bootconsole [earlycon0] disabled
// 中断处理
p->handle_irq = dw8250_handle_irq;
dw8250_handle_irq();
serial8250_handle_irq();
serial8250_rx_chars(); // 读数据
serial8250_tx_chars(); // 发数据
Serial_core.c
注册一个tty driver
, 提供了static const struct tty_operations uart_ops
static const struct tty_port_operations uart_port_ops
供上层调用。
Printk
以linux5.10为例。(注意到最新的linux6.3 printk流程不一样了)
//kernel/printk/printk.c
printk;
vprintk_func // printk_safe.c
vprintk_default
vprintk_emit
vprintk_store // 把要打印的信息保存在log_buf中
log_output
console_unlock
call_console_drivers
con->write(con, text, len); // 调用console driver的write函数
// 8250 consle
static struct console univ8250_console = {
.name = "ttyS",
.write = univ8250_console_write,
.device = uart_console_device,
.setup = univ8250_console_setup,
.match = univ8250_console_match,
.flags = CON_PRINTBUFFER | CON_ANYTIME,
.index = -1,
.data = &serial8250_reg,
};
univ8250_console_write();
serial8250_console_write();
uart_console_write();
putchar();//即serial8250_console_putchar();
serial_port_out(port, UART_TX, ch); // 往UART_TX寄存器写数据
up->serial_out(up, offset, value); // 调用到8250_dw.c中写寄存器函数
Early printk
打开CONFIG_EARLY_PRINTK
如果在设备树cmdline中添加了earlyprintk
,会进入/arch/arm/kernel/early_printk.c
中early_param(" earlyprintk", setup_early_printk);
指定的setup_early_printk函数。可以看到这个earlyprintk是在arch arm上实现的,比如其他risc-v是没有的(通过earlycon实现)。
setup_early_printk();
register_console(&early_console_dev); // 注册了一个console,printk会调用console的.write回调函数
early_console_write();
early_write();
printascii(); // arch/arm/kernel/debug.S
addruart_current
addruart
ldr \rp, =CONFIG_DEBUG_UART_PHYS // 需要在linux menuconfig中手动设定earlyprink的Uart串口地址
ldr \rv, =CONFIG_DEBUG_UART_VIRT
waituarttxrdy
senduart
busyuart
register_console
pr_info("%sconsole [%s%d] enabled\n",
(newcon->flags & CON_BOOT) ? "boot" : "" ,
newcon->name, newcon->index);
Earlycon
- command line中
earlycon
如果不带参数,参数在下面的stdout-path
中,所以要解析设备树。
使用"/chosen"下的"stdout-path"找到节点,根据节点的"compatible"
找到对应的OF_EARLYCON_DECLARE
,进入setup函数。
- 如果
earlycon=xxx
含参数,无需设备树。
可以利用EARLYCON_DECLARE
宏,根据name(xxx)
,找到对应的setup函数。
例如earlycon-riscv-sbi.c
中EARLYCON_DECLARE(sbi, early_sbi_setup);
只需在设备树中指定earlycon=sbi
Question
两个串口对接,一个串口tx发送16bytes 0x55给另一个串口,为什么另一个串口会回16bytes 0x55回来到rx?