1 Introduction
In a formal project, I encountered a problem during serial communication where a sequence of data was unexpectedly interrupted by a 00. The data received from the hardware FIFO was correct, but the data received at the upper layer was abnormal. This issue might be obvious to someone with experience, but for those who have not looked at the relevant kernel code, it can be quite confusing. What is going on here?
2 Linux Serial Framework
The software serial framework in Linux (the author also captured this image from the internet) is basically as follows:
The overall Linux TTY architecture:
Here, let me unveil the mystery of the serial port for everyone. I will send out all the kernel-related code based on the diagram.2.1 UART Signal The protocol signals on the hardware are what everyone is primarily concerned about:
We can see that this data is AA55. The actual data I captured from the logic analyzer was:
One low level as the start bit, and the final high level as the stop bit.OK, quite simple, right?2.2 Concept of UARTUART is a universal asynchronous serial data bus used for asynchronous communication. This bus supports bidirectional communication and can achieve full-duplex transmission and reception.
As a type of asynchronous serial communication protocol, UART works by transmitting each character of data one bit at a time. The significance of each bit is as follows:
- Start Bit: A logical “0” signal is sent first, indicating the start of character transmission.
- Data Bits: These follow immediately after the start bit. The number of data bits can be 5, 6, 7, 8, etc., forming a character. ASCII code is typically used. Transmission starts from the least significant bit, synchronized by the clock.
- Parity Bit: This bit, added to the data bits, ensures that the number of “1” bits is even (even parity) or odd (odd parity), thus verifying the correctness of data transmission.
- Stop Bit: This is a character data end marker. It can be a high level of 1 bit, 1.5 bits, or 2 bits.
Baud Rate is a measure of the rate of data transmission. It indicates the number of symbols transmitted per second (symbol). The amount of information represented by a symbol (in bits) depends on the order of the symbol. For example, if 256-order symbols are used, with each symbol representing 8 bits, and the data transmission rate is 120 characters/second, then the baud rate is 120 baud, and the bit rate is 120*8=960bit/s. These two concepts are easily confused.
The reception and transmission of UART occur at the same baud rate. The clock frequency generated by the baud rate generator is not the baud rate clock frequency, but rather 16 times the baud rate clock frequency, to allow for precise sampling during reception to extract asynchronous serial data. Based on the given crystal oscillator clock and the required baud rate, the baud rate division count value can be calculated.
2.3 SOC UART ConfigurationNow let’s take a look at the Pinmux in the F1S200C chip.


Here we can see that the serial port PA2 and PA3 are used as the serial port. In Linux, the device tree is as follows:
pio: pinctrl@1c20800 { compatible = "allwinner,suniv-f1c100s-pinctrl"; reg = <0x01c20800 0x400>; interrupts = <38>, <39>, <40>; clocks = <&ccu 37>, <&osc24M>, <&osc32k>; clock-names = "apb", "hosc", "losc"; gpio-controller; interrupt-controller; #interrupt-cells = <3>; #gpio-cells = <3>; uart0_pe_pins: uart0-pe-pins { pins = "PE0", "PE1"; function = "uart0"; bias-pull-up; }; uart1_pa_pins: uart1-pins { pins = "PA2", "PA3"; function = "uart1"; bias-pull-up; }; uart1_pa_rcts_pins: uart1-rcts-pins { pins = "PA0", "PA1"; function = "uart1"; bias-pull-up; }; }; uart0: serial@1c25000 { compatible = "snps,dw-apb-uart"; reg = <0x01c25000 0x400>; interrupts = <1>; reg-shift = <2>; reg-io-width = <4>; clocks = <&ccu 38>; resets = <&ccu 24>; status = "disabled";}; uart1: serial@1c25400 { compatible = "snps,dw-apb-uart"; reg = <0x01c25400 0x400>; interrupts = <2>; reg-shift = <2>; reg-io-width = <4>; clocks = <&ccu 39>; resets = <&ccu 25>; status = "disabled";}; uart2: serial@1c25800 { compatible = "snps,dw-apb-uart"; reg = <0x01c25800 0x400>; interrupts = <3>; reg-shift = <2>; reg-io-width = <4>; clocks = <&ccu 40>; resets = <&ccu 26>; status = "disabled";};
Actual effective:
/ { model = "Allwinnerck Pi"; compatible = "allwinner,suniv-f1c200s", "allwinner,suniv-f1c100s"; aliases { serial0 = &uart1; }; chosen {#address-cells = <1>;#size-cells = <1>; ranges; stdout-path = "serial0:115200n8"; };};&uart1 { pinctrl-names = "default"; pinctrl-0 = <&uart1_pa_pins>; status = "okay";}; &uart0 { pinctrl-names = "default"; pinctrl-0 = <&uart0_pe_pins>; status = "okay";};
The device tree is already set up, so let’s take a look at how the driver is implemented.2.4 8250 Serial Driver Implementation First, let’s look at the relevant device that can matchsnps,dw-apb-uart function implementation. Indrivers\tty\serial\8250, we can see in 8250_dw.c:
static const struct of_device_id dw8250_of_match[] = { { .compatible = "snps,dw-apb-uart" }, { .compatible = "cavium,octeon-3860-uart" }, { .compatible = "marvell,armada-38x-uart" }, { .compatible = "renesas,rzn1-uart" }, { /* Sentinel */ }};
It is clear that it can be matched:
static struct platform_driver dw8250_platform_driver = { .driver = { .name = "dw-apb-uart", .pm = &dw8250_pm_ops, .of_match_table = dw8250_of_match, .acpi_match_table = dw8250_acpi_match, }, .probe = dw8250_probe, .remove = dw8250_remove,}; module_platform_driver(dw8250_platform_driver);
Thus, we can start studying the dw8250_probe function.
struct uart_8250_port uart = {}, *up = &uart;struct resource *regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);//Here we can get the reg from the device tree struct uart_port *p = &up->port;struct device *dev = &pdev->dev;struct dw8250_data *data;int irq;int err;u32 val; if (!regs) { dev_err(dev, "no registers defined\n"); return -EINVAL;} irq = platform_get_irq(pdev, 0); if (irq < 0) return irq; spin_lock_init(&p->lock); p->mapbase = regs->start;//Base address from the device tree p->irq = irq; p->handle_irq = dw8250_handle_irq; p->pm = dw8250_do_pm; p->type = PORT_8250; p->flags = UPF_SHARE_IRQ | UPF_FIXED_PORT; p->dev = dev; p->iotype = UPIO_MEM; p->serial_in = dw8250_serial_in; p->serial_out = dw8250_serial_out; p->set_ldisc = dw8250_set_ldisc; p->set_termios = dw8250_set_termios; p->membase = devm_ioremap(dev, regs->start, resource_size(regs)); if (!p->membase) return -ENOMEM;......
So how do we implement the corresponding device initialization? Let’s first look at the register:
At this point, we have a general understanding of the registers. The main operations on the registers are done using thep->membase, which is the base address obtained from ioremap.Here, there is a key structure, namely uart_8250_port, which can be considered as the entire port’s driver description, as follows:
/* * This should be used by drivers which want to register * their own 8250 ports without registering their own * platform device. Using these will make your driver * dependent on the 8250 driver. */ 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 */ unsigned short bugs; /* port bugs */ bool fifo_bug; /* min RX trigger if enabled */ 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 mcr_mask; /* mask of user bits */ unsigned char mcr_force; /* mask of forced bits */ 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 unsigned char lsr_saved_flags; #define MSR_SAVE_FLAGS UART_MSR_ANY_DELTA unsigned char msr_saved_flags; struct uart_8250_dma *dma; const struct uart_8250_ops *ops; /* 8250 specific callbacks */ int (*dl_read)(struct uart_8250_port *); void (*dl_write)(struct uart_8250_port *, int); struct uart_8250_em485 *em485; /* Serial port overrun backoff */ struct delayed_work overrun_backoff; u32 overrun_backoff_time_ms;};
And the subsequent structure we will often use isuart_port:
struct uart_port { spinlock_t lock; /* port lock */ unsigned long iobase; /* in/out[bwl] */ unsigned char __iomem *membase; /* read/write[bwl] */ //Serial port iomem unsigned int(*serial_in)(struct uart_port *, int); void (*serial_out)(struct uart_port *, int, int); void (*set_termios)(struct uart_port *, struct ktermios *new, struct ktermios *old); void (*set_ldisc)(struct uart_port *, struct ktermios *); unsigned int(*get_mctrl)(struct uart_port *); void (*set_mctrl)(struct uart_port *, unsigned int); unsigned int(*get_divisor)(struct uart_port *, unsigned int baud, unsigned int *frac); void (*set_divisor)(struct uart_port *, unsigned int baud, unsigned int quot, unsigned int quot_frac); int (*startup)(struct uart_port *port); void (*shutdown)(struct uart_port *port); void (*throttle)(struct uart_port *port); void (*unthrottle)(struct uart_port *port); int (*handle_irq)(struct uart_port *); void (*pm)(struct uart_port *, unsigned int state, unsigned int old); void (*handle_break)(struct uart_port *); int (*rs485_config)(struct uart_port *,struct serial_rs485 *rs485); int (*iso7816_config)(struct uart_port *, struct serial_iso7816 *iso7816); unsigned int irq; /* irq number */ unsigned long irqflags; /* irq flags */ unsigned int uartclk; /* base uart clock */ unsigned int fifosize; /* tx fifo size */ unsigned char x_char; /* xon/xoff char */ unsigned char regshift; /* reg offset shift */ unsigned char iotype; /* io access style */ unsigned char quirks; /* internal quirks */ #define UPIO_PORT (SERIAL_IO_PORT) /* 8b I/O port access */ #define UPIO_HUB6 (SERIAL_IO_HUB6) /* Hub6 ISA card */ #define UPIO_MEM (SERIAL_IO_MEM) /* driver-specific */ #define UPIO_MEM32 (SERIAL_IO_MEM32) /* 32b little endian */ #define UPIO_AU (SERIAL_IO_AU) /* Au1x00 and RT288x type IO */ #define UPIO_TSI (SERIAL_IO_TSI) /* Tsi108/109 type IO */ #define UPIO_MEM32BE (SERIAL_IO_MEM32BE) /* 32b big endian */ #define UPIO_MEM16 (SERIAL_IO_MEM16) /* 16b little endian */ /* quirks must be updated while holding port mutex */ #define UPQ_NO_TXEN_TEST BIT(0) unsigned int read_status_mask; /* driver specific */ unsigned int ignore_status_mask; /* driver specific */ struct uart_state *state; /* pointer to parent state */ struct uart_icount icount; /* statistics */ struct console *cons; /* struct console, if any */ #if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(SUPPORT_SYSRQ) unsigned long sysrq; /* sysrq timeout */ unsigned int sysrq_ch; /* char for sysrq */ #endif /* flags must be updated while holding port mutex */ upf_t flags;................(省略) int hw_stopped; /* sw-assisted CTS flow state */ unsigned int mctrl; /* current modem ctrl settings */ unsigned int timeout; /* character-based timeout */ unsigned int type; /* port type */ const struct uart_ops *ops; unsigned int custom_divisor; unsigned int line; /* port index */ unsigned int minor; resource_size_t mapbase; /* for ioremap */ resource_size_t mapsize; struct device *dev; /* parent device */ unsigned char hub6; /* this should be in the 8250 driver */ unsigned char suspended; unsigned char unused[2]; const char *name; /* port name */ struct attribute_group *attr_group; /* port specific attributes */ const struct attribute_group **tty_groups; /* all attributes (serial core use only) */ struct serial_rs485 rs485; struct serial_iso7816 iso7816; void *private_data; /* generic platform data pointer */};
Here, the probe function mainly uses private data, as follows:
struct dw8250_data { struct dw8250_port_data data; u8 usr_reg; int msr_mask_on; int msr_mask_off; struct clk *clk; struct clk *pclk; struct reset_control *rst; unsigned int skip_autocfg:1; unsigned int uart_16550_compatible:1;};
Here we can see in the probe function:
if (!data->skip_autocfg) dw8250_setup_port(p);
void dw8250_setup_port(struct uart_port *p){ struct uart_8250_port *up = up_to_u8250p(p); u32 reg; /* * If the Component Version Register returns zero, we know that * ADDITIONAL_FEATURES are not enabled. No need to go any further. */ reg = dw8250_readl_ext(p, DW_UART_UCV); if (!reg) return; dev_dbg(p->dev, "Designware UART version %c.%c%c\n", (reg >> 24) && 0xff, (reg >> 16) && 0xff, (reg >> 8) && 0xff); dw8250_writel_ext(p, DW_UART_DLF, ~0U); reg = dw8250_readl_ext(p, DW_UART_DLF); dw8250_writel_ext(p, DW_UART_DLF, 0); if (reg) { struct dw8250_port_data *d = p->private_data; d->dlf_size = fls(reg); p->get_divisor = dw8250_get_divisor; p->set_divisor = dw8250_set_divisor; } reg = dw8250_readl_ext(p, DW_UART_CPR); if (!reg) return; /* Select the type based on FIFO */ if (reg && DW_UART_CPR_FIFO_MODE) { p->type = PORT_16550A; p->flags |= UPF_FIXED_TYPE; p->fifosize = DW_UART_CPR_FIFO_SIZE(reg); up->capabilities = UART_CAP_FIFO; } if (reg && DW_UART_CPR_AFCE_MODE) up->capabilities |= UART_CAP_AFE; if (reg && DW_UART_CPR_SIR_MODE) up->capabilities |= UART_CAP_IRDA; } EXPORT_SYMBOL_GPL(dw8250_setup_port);
However, here the author cannot see the corresponding register description related to this. The only register information I found is publicly available online. From the current information, it seems that the usr register in the probe function should be 0x7C, while the actual one we are using is:
#define DW_UART_USR 0x1f /* UART Status Register */
Why is this the case? Let’s continue to look at the dw8250_handle_irq function:
if ((iir && UART_IIR_BUSY) == UART_IIR_BUSY) {/* Clear the USR */ (void)p->serial_in(p, d->usr_reg); return 1; }
And the serial_in function points to:
static unsigned int dw8250_serial_in(struct uart_port *p, int offset){ unsigned int value = readb(p->membase + (offset << p->regshift)); return dw8250_modify_msr(p, offset, value);}
It is clear that the offset is also shifted a few bits. This comes from:
err = device_property_read_u32(dev, "reg-shift", &val); if (!err) p->regshift = val;
Just in line with the device tree. After resolving the register doubts, we continue to analyze. However, there is no description of the register, and I am helpless. For example, in the dw8250_setup_port, the following is used:
/* Offsets for the DesignWare specific registers */ #define DW_UART_DLF 0xc0 /* Divisor Latch Fraction Register */ #define DW_UART_CPR 0xf4 /* Component Parameter Register */ #define DW_UART_UCV 0xf8 /* UART Component Version */
Therefore, let’s shift our focus to another function in the probe function:
int serial8250_register_8250_port(struct uart_8250_port *up) its main function is to register the serial port. In this function, there are two functions that need to be focused on:
Setting initial data:
serial8250_set_defaults(uart);
Earlier in the probe function, we saw:
p->iotype = UPIO_MEM; p->flags = UPF_SHARE_IRQ | UPF_FIXED_PORT;
Following this:
void serial8250_set_defaults(struct uart_8250_port *up){ struct uart_port *port = &up->port; if (up->port.flags && UPF_FIXED_TYPE) { unsigned int type = up->port.type; if (!up->port.fifosize) up->port.fifosize = uart_config[type].fifo_size; if (!up->tx_loadsz) up->tx_loadsz = uart_config[type].tx_loadsz; if (!up->capabilities) up->capabilities = uart_config[type].flags; } set_io_from_upio(port); /* default dma handlers */ if (up->dma) { if (!up->dma->tx_dma) up->dma->tx_dma = serial8250_tx_dma; if (!up->dma->rx_dma) up->dma->rx_dma = serial8250_rx_dma; }} EXPORT_SYMBOL_GPL(serial8250_set_defaults);
Thus, some initial configurations can be set. Another function that is also crucial is the bridge that connects the serial port driver and the TTY driver:
ret = uart_add_one_port(&serial8250_reg,&uart->port);
Its main function is to register this serial port driver pointing to TTY.
However, there is also an interrupt function that is extremely critical, namely the probe function:
irq = platform_get_irq(pdev, 0); if (irq < 0) return irq; p->irq = irq; p->handle_irq = dw8250_handle_irq; //Hardware interrupt service function
The actual service interrupt function is:
static irqreturn_t serial8250_interrupt(int irq, void *dev_id)
Inside, there isport->handle_irq(port) to handle all interrupts.
static int dw8250_handle_irq(struct uart_port *p){ struct uart_8250_port *up = up_to_u8250p(p); struct dw8250_data *d = to_dw8250_data(p->private_data); unsigned int iir = p->serial_in(p, UART_IIR); unsigned int status; unsigned long flags; /* * There are ways to get Designware-based UARTs into a state where * they are asserting UART_IIR_RX_TIMEOUT but there is no actual * data available. If we see such a case then we'll do a bogus * read. If we don't do this then the "RX TIMEOUT" interrupt will * fire forever. * * This problem has only been observed so far when not in DMA mode * so we limit the workaround only to non-DMA mode. */ if (!up->dma && ((iir && 0x3f) == UART_IIR_RX_TIMEOUT)) { spin_lock_irqsave(&p->lock, flags); status = p->serial_in(p, UART_LSR); if (!(status && (UART_LSR_DR | UART_LSR_BI))) (void) p->serial_in(p, UART_RX); spin_unlock_irqrestore(&p->lock, flags); } if (serial8250_handle_irq(p, iir)) return 1; if ((iir && UART_IIR_BUSY) == UART_IIR_BUSY) {/* Clear the USR */ (void)p->serial_in(p, d->usr_reg); return 1; } return 0;}
Then continue to track each interrupt service as follows:
/* * This handles the interrupt from one port. */ int serial8250_handle_irq(struct uart_port *port, unsigned int iir){ unsigned char status; unsigned long flags; struct uart_8250_port *up = up_to_u8250p(port); bool skip_rx = false; if (iir && UART_IIR_NO_INT) return 0; spin_lock_irqsave(&port->lock, flags); status = serial_port_in(port, UART_LSR); /* * If port is stopped and there are no error conditions in the * FIFO, then don't drain the FIFO, as this may lead to TTY buffer * overflow. Not servicing, RX FIFO would trigger auto HW flow * control when FIFO occupancy reaches preset threshold, thus * halting RX. This only works when auto HW flow control is * available. */ if (!(status && (UART_LSR_FIFOE | UART_LSR_BRK_ERROR_BITS)) && (port->status && (UPSTAT_AUTOCTS | UPSTAT_AUTORTS)) && !(port->read_status_mask && UART_LSR_DR)) skip_rx = true; if (status && (UART_LSR_DR | UART_LSR_BI) && !skip_rx) { if (!up->dma || handle_rx_dma(up, iir)) status = serial8250_rx_chars(up, status); } serial8250_modem_status(up); if ((!up->dma || up->dma->tx_err) && (status && UART_LSR_THRE) && (up->ier && UART_IER_THRI)) serial8250_tx_chars(up); uart_unlock_and_check_sysrq(port, flags); return 1; } EXPORT_SYMBOL_GPL(serial8250_handle_irq);
At this point, note that the serial8250_rx_chars function can be temporarily set aside. Next, let’s analyze other parts. In the following text, all functions will be linked together.
2.5 Linking TTY and Serial8250In theuart_add_one_port function, there is the following function:
tty_dev = tty_port_register_device_attr_serdev(port, drv->tty_driver, uport->line, uport->dev, port, uport->tty_groups);
This function loads the serial port onto TTY.In serial_core.c, theuart_register_driver function is very important.
#define UART_NR CONFIG_SERIAL_8250_NR_UARTS static struct uart_8250_port serial8250_ports[UART_NR];
CONFIG_SERIAL_8250_NR_UARTS comes from the kernel’s defconfig options. In the probe function, it can be noted:
ret = uart_register_driver(&serial8250_reg);
This is the memory initialization for TTY:
int uart_register_driver(struct uart_driver *drv){ struct tty_driver *normal; int i, retval = -ENOMEM; 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 = kcalloc(drv->nr, sizeof(struct uart_state), GFP_KERNEL); if (!drv->state) goto out; normal = alloc_tty_driver(drv->nr); if (!normal) goto out_kfree; drv->tty_driver = normal; normal->driver_name = drv->driver_name; normal->name = drv->dev_name; normal->major = drv->major; normal->minor_start = drv->minor; normal->type = TTY_DRIVER_TYPE_SERIAL; normal->subtype = SERIAL_TYPE_NORMAL; normal->init_termios = tty_std_termios; normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600; normal->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; normal->driver_state = drv; tty_set_operations(normal, &uart_ops); /* * Initialise the UART state(s). */ 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; } retval = tty_register_driver(normal); if (retval >= 0) return retval; for (i = 0; i < drv->nr; i++) tty_port_destroy(&drv->state[i].port); put_tty_driver(normal); out_kfree: kfree(drv->state); out: return retval;}
OK, we can see thattty_set_operations(normal, &uart_ops); function, let’s take a look at
static const struct tty_operations uart_ops = { .install = uart_install, .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_show = uart_proc_show,#endif .tiocmget = uart_tiocmget, .tiocmset = uart_tiocmset, .set_serial = uart_set_info_user, .get_serial = uart_get_info_user, .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};
This is the tty_operations. Looking deeper, these functions ultimately lead to the tty_core.c functions such astty_port_install,tty_port_open, etc. Here we can see that when TTY is finally scheduled, it actually ends up in the 8250 driver functions.2.6 Linux Console So how does Linux print in the kernel log? With this question, we continue to explore the kernel part.
/** * of_alias_scan - Scan all properties of the 'aliases' node * * The function scans all the properties of the 'aliases' node and populates * the global lookup table with the properties. It returns the * number of alias properties found, or an error code in case of failure. * * @dt_alloc: An allocator that provides a virtual address to memory * for storing the resulting tree */ void of_alias_scan(void * (*dt_alloc)(u64 size, u64 align)){ struct property *pp; of_aliases = of_find_node_by_path("/aliases"); of_chosen = of_find_node_by_path("/chosen"); if (of_chosen == NULL) of_chosen = of_find_node_by_path("/chosen@0"); if (of_chosen) {/* linux,stdout-path and /aliases/stdout are for legacy compatibility */ const char *name = NULL; if (of_property_read_string(of_chosen, "stdout-path", &name)) of_property_read_string(of_chosen, "linux,stdout-path", &name); if (IS_ENABLED(CONFIG_PPC) && !name) of_property_read_string(of_aliases, "stdout", &name); if (name) of_stdout = of_find_node_opts_by_path(name, &of_stdout_options); } if (!of_aliases) return; for_each_property_of_node(of_aliases, pp) { const char *start = pp->name; const char *end = start + strlen(start); struct device_node *np; struct alias_prop *ap; int id, len; /* Skip those we do not want to proceed */ if (!strcmp(pp->name, "name") || !strcmp(pp->name, "phandle") || !strcmp(pp->name, "linux,phandle")) continue; np = of_find_node_by_path(pp->value); if (!np) continue; /* walk the alias backwards to extract the id and work out * the 'stem' string */ while (isdigit(*(end-1)) && end > start) end--; len = end - start; if (kstrtoint(end, 10, &id) < 0) continue; /* Allocate an alias_prop with enough space for the stem */ ap = dt_alloc(sizeof(*ap) + len + 1, __alignof__(*ap)); if (!ap) continue; memset(ap, 0, sizeof(*ap) + len + 1); ap->alias = start; of_alias_add(ap, np, id, start, len); }}
In this case, we can see that the driver mainly grabs the device tree’s
stdout-path = "serial0:115200n8"
Here, the output is serial0, and serial0 corresponds to uart1. This function is called byunflatten_device_tree, and this function will be scheduled insetup.c duringsetup_arch(char **cmdline_p).
extern struct device_node *of_chosen
Now, let’s return to the kernel initialization function, and we can see theconsole_init function, which can be traced to kernel/printk.c, where we can see:
/* Setup the default TTY line discipline. */ n_tty_init();
We can see that it callstty_register_ldisc(N_TTY, &n_tty_ops);
static struct tty_ldisc_ops n_tty_ops = { .magic = TTY_LDISC_MAGIC, .name = "n_tty", .open = n_tty_open, .close = n_tty_close, .flush_buffer = n_tty_flush_buffer, .read = n_tty_read, .write = n_tty_write, .ioctl = n_tty_ioctl, .set_termios = n_tty_set_termios, .poll = n_tty_poll, .receive_buf = n_tty_receive_buf, .write_wakeup = n_tty_write_wakeup, .receive_buf2 = n_tty_receive_buf2,};
Thus, it binds with TTY, establishing the system interface.2.7 Summary Previously, we have basically taken a look at the entire TTY code. However, we still feel unclear about how the entire serial port is constructed. Let’s draw a diagram to review the overall architecture.
The registration process mainly does the following operations:
- According to the maximum number of devices supported by the driver (this can be configured in the kernel config), allocate n uart_state spaces, each uart_state has a uart_port.
- Allocate a tty_driver and point uart_driver->tty_driver to it.
- Set up the tty_driver, including default baud rate, parity, etc., and an important ops structure, the registration of the tty_operation, which is the interface for communication between the TTY core and the serial port driver.
- Initialize each uart_state’s tty_port;
- Register the tty_driver. Registering the uart_driver actually registers the tty_driver, and all interactions with user space are handled by the tty_driver, which is a part of the kernel that does not need modification.
As we saw earlier, registering TTY:
Register a uart port to the uart driver, allowing the uart driver to access the corresponding uart port for data transmission and reception.So how do we unregister these serial ports?To unregister a uart port from the uart driver, this interface is called in the remove function of the uart driver. The process of removing a uart port is as follows:
Then there is the exit phase of the uart driver:
Now let’s look at this structure from the top down:2.7.1 open /dev/ttySx
2.7.2 write /dev/ttySx
2.7.3 read /dev/ttySx
2.7.4 close /dev/ttySx
OK, we have basically clarified everything. Of course, I have also put some comments I saw on GitHub.3 Serial Port Issues
Returning to our problem: why does a good piece of data suddenly have a 00 inserted? It turns out that an overrun occurred. How did it come about? You can see in 8250_port.c that the serial8250_rx_chars calls the serial8250_read_char function.
if (lsr && UART_LSR_OE) port->icount.overrun++;
When the device detects the UART_LSR_OE mark, this problem will occur, which is indicated in the UART Line Status Register. The specific bit explanation is as follows:

Thus, it will calluart_insert_char(port, lsr, UART_LSR_OE, ch, flag);
/** 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) //When overrun occurs, insert a byte 00 ++port->icount.buf_overrun;
end
One Linux
Follow, reply 【1024】 to receive a wealth of Linux materials
Collection of Wonderful Articles
Recommended Articles
☞【Collection】ARM☞【Collection】Fan Q&A☞【Collection】All Originals☞【Collection】LinuxGetting Started☞【Collection】Computer Networks☞【Collection】Linux Drivers☞【Essentials】Embedded Driver Engineer Learning Path☞【Essentials】All Knowledge Points of Linux Embedded – Mind Map