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
struct uart_8250_port {
struct uart_port port;
struct timer_list timer; /* "no irq" timer */
struct list_head list; /* ports on this IRQ */
u32 capabilities; /* port capabilities */
u16 bugs; /* port bugs */
unsigned int tx_loadsz; /* transmit fifo load size */
unsigned char acr;
unsigned char fcr;
unsigned char ier;
unsigned char lcr;
unsigned char mcr;
unsigned char cur_iotype; /* Running I/O type */
unsigned int rpm_tx_active;
unsigned char canary; /* non-zero during system sleep
* if no_console_suspend
*/
unsigned char probe;
struct mctrl_gpios *gpios;
#define UART_PROBE_RSA (1 << 0)
/*
* Some bits in registers are cleared on a read, so they must
* be saved whenever the register is read but the bits will not
* be immediately processed.
*/
#define LSR_SAVE_FLAGS UART_LSR_BRK_ERROR_BITS
u16 lsr_saved_flags;
u16 lsr_save_mask;
#define MSR_SAVE_FLAGS UART_MSR_ANY_DELTA
unsigned char msr_saved_flags;
struct uart_8250_dma *dma;
const struct uart_8250_ops *ops;
u32 (*dl_read)(struct uart_8250_port *up);
void (*dl_write)(struct uart_8250_port *up, u32 value);
struct uart_8250_em485 *em485;
void (*rs485_start_tx)(struct uart_8250_port *);
void (*rs485_stop_tx)(struct uart_8250_port *);
struct delayed_work overrun_backoff;
u32 overrun_backoff_time_ms;
};
timer: 使用 polling mode 时的 timer。
list: ISA shared interrupts 需要把 shared interrupts 挂入一个链表。
tx_loadsz: tx fifo load size.
capabilities: UART_CAP_XXX.
bugs: UART_BUG_XXX.
8250_core.c
- 通过 uart_register_driver() 注册 uart_driver
- 通过 serial8250_register_ports()->uart_add_one_port(). 注册 8250 所有的 uart_port(后面更底层的 8250_dw.c 会注册 uart_port 替换)
- 通过 univ8250_console_init() 注册 univ8250_console
//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
APIs:
int serial8250_register_8250_port(const struct uart_8250_port *); // 注册 8250 port
void serial8250_unregister_port(int line); // 注销 8250 port
void serial8250_suspend_port(int line);
void serial8250_resume_port(int line);
8250_port.c
作为 8250_core.c 的 backend。
- 实现 uart_port->uart_ops callbacks.
- 实现 univ8250_console 的 callbacks.
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