Linux 内核调试篇3(基于Linux6.6)---串口驱动分析
一、前情回顾
上一节分析了bootoader中传过来的cmdline中的命令是如何解析并执行的。同时也是对bootloader中传过来的console进行了记录。console也就是我们所说的控制台,可以是任何常见的输出设备,比如serial,比如framebuffer,比如网口。
本节主要以最常见的串口为例说明。绝大多数的console都是串口设备。
所以内核对用作串口程序进行了进一步的封装,抽象出的serial_core.c文件中,对console的注册留有了接口。
console的注册函数是register_console,这个函数后面说。
二、数据结构
先简单看一下串口驱动。串口驱动主要由下面这个来表示:
include/linux/serial_core.h
struct uart_driver {
struct module *owner;
const char *driver_name;
const char *dev_name;
int major;
int minor;
int nr;
struct console *cons;
/*
* these are private; the low level driver should not
* touch these; they should be initialised to NULL
*/
struct uart_state *state;
struct tty_driver *tty_driver;
};
NXP的实现如下,这里的console是console使用的
drivers/tty/serial/imx.c
static struct uart_driver imx_uart_uart_driver;
static struct console imx_uart_console = {
.name = DEV_NAME,
.write = imx_uart_console_write,
.device = uart_console_device,
.setup = imx_uart_console_setup,
.exit = imx_uart_console_exit,
.flags = CON_PRINTBUFFER,
.index = -1,
.data = &imx_uart_uart_driver,
};
#define IMX_CONSOLE &imx_uart_console
接下来再看一下一个uart端口是如何描述:
内核抽象出了公共的部分。
include/linux/serial_core.h
struct uart_port {
spinlock_t lock; /* port lock */
unsigned long iobase; /* in/out[bwl] */
unsigned char __iomem *membase; /* read/write[bwl] */
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,
const 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 ktermios *termios,
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 */
/* flags must be updated while holding port mutex */
upf_t flags;
/*
* These flags must be equivalent to the flags defined in
* include/uapi/linux/tty_flags.h which are the userspace definitions
* assigned from the serial_struct flags in uart_set_info()
* [for bit definitions in the UPF_CHANGE_MASK]
*
* Bits [0..ASYNCB_LAST_USER] are userspace defined/visible/changeable
* The remaining bits are serial-core specific and not modifiable by
* userspace.
*/
#define UPF_FOURPORT ((__force upf_t) ASYNC_FOURPORT /* 1 */ )
#define UPF_SAK ((__force upf_t) ASYNC_SAK /* 2 */ )
#define UPF_SPD_HI ((__force upf_t) ASYNC_SPD_HI /* 4 */ )
#define UPF_SPD_VHI ((__force upf_t) ASYNC_SPD_VHI /* 5 */ )
#define UPF_SPD_CUST ((__force upf_t) ASYNC_SPD_CUST /* 0x0030 */ )
#define UPF_SPD_WARP ((__force upf_t) ASYNC_SPD_WARP /* 0x1010 */ )
#define UPF_SPD_MASK ((__force upf_t) ASYNC_SPD_MASK /* 0x1030 */ )
#define UPF_SKIP_TEST ((__force upf_t) ASYNC_SKIP_TEST /* 6 */ )
#define UPF_AUTO_IRQ ((__force upf_t) ASYNC_AUTO_IRQ /* 7 */ )
#define UPF_HARDPPS_CD ((__force upf_t) ASYNC_HARDPPS_CD /* 11 */ )
#define UPF_SPD_SHI ((__force upf_t) ASYNC_SPD_SHI /* 12 */ )
#define UPF_LOW_LATENCY ((__force upf_t) ASYNC_LOW_LATENCY /* 13 */ )
#define UPF_BUGGY_UART ((__force upf_t) ASYNC_BUGGY_UART /* 14 */ )
#define UPF_MAGIC_MULTIPLIER ((__force upf_t) ASYNC_MAGIC_MULTIPLIER /* 16 */ )
#define UPF_NO_THRE_TEST ((__force upf_t) BIT_ULL(19))
/* Port has hardware-assisted h/w flow control */
#define UPF_AUTO_CTS ((__force upf_t) BIT_ULL(20))
#define UPF_AUTO_RTS ((__force upf_t) BIT_ULL(21))
#define UPF_HARD_FLOW ((__force upf_t) (UPF_AUTO_CTS | UPF_AUTO_RTS))
/* Port has hardware-assisted s/w flow control */
#define UPF_SOFT_FLOW ((__force upf_t) BIT_ULL(22))
#define UPF_CONS_FLOW ((__force upf_t) BIT_ULL(23))
#define UPF_SHARE_IRQ ((__force upf_t) BIT_ULL(24))
#define UPF_EXAR_EFR ((__force upf_t) BIT_ULL(25))
#define UPF_BUG_THRE ((__force upf_t) BIT_ULL(26))
/* The exact UART type is known and should not be probed. */
#define UPF_FIXED_TYPE ((__force upf_t) BIT_ULL(27))
#define UPF_BOOT_AUTOCONF ((__force upf_t) BIT_ULL(28))
#define UPF_FIXED_PORT ((__force upf_t) BIT_ULL(29))
#define UPF_DEAD ((__force upf_t) BIT_ULL(30))
#define UPF_IOREMAP ((__force upf_t) BIT_ULL(31))
#define UPF_FULL_PROBE ((__force upf_t) BIT_ULL(32))
#define __UPF_CHANGE_MASK 0x17fff
#define UPF_CHANGE_MASK ((__force upf_t) __UPF_CHANGE_MASK)
#define UPF_USR_MASK ((__force upf_t) (UPF_SPD_MASK|UPF_LOW_LATENCY))
#if __UPF_CHANGE_MASK > ASYNC_FLAGS
#error Change mask not equivalent to userspace-visible bit defines
#endif
/*
* Must hold termios_rwsem, port mutex and port lock to change;
* can hold any one lock to read.
*/
upstat_t status;
#define UPSTAT_CTS_ENABLE ((__force upstat_t) (1 << 0))
#define UPSTAT_DCD_ENABLE ((__force upstat_t) (1 << 1))
#define UPSTAT_AUTORTS ((__force upstat_t) (1 << 2))
#define UPSTAT_AUTOCTS ((__force upstat_t) (1 << 3))
#define UPSTAT_AUTOXOFF ((__force upstat_t) (1 << 4))
#define UPSTAT_SYNC_FIFO ((__force upstat_t) (1 << 5))
int hw_stopped; /* sw-assisted CTS flow state */
unsigned int mctrl; /* current modem ctrl settings */
unsigned int frame_time; /* frame timing in ns */
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 long sysrq; /* sysrq timeout */
unsigned int sysrq_ch; /* char for sysrq */
unsigned char has_sysrq;
unsigned char sysrq_seq; /* index in sysrq_toggle_seq */
unsigned char hub6; /* this should be in the 8250 driver */
unsigned char suspended;
unsigned char console_reinit;
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_rs485 rs485_supported; /* Supported mask for serial_rs485 */
struct gpio_desc *rs485_term_gpio; /* enable RS485 bus termination */
struct serial_iso7816 iso7816;
void *private_data; /* generic platform data pointer */
};
不同厂家在内核的基础上继续封装实现自己独特的部分。
drivers/tty/serial/imx.c
struct imx_port {
struct uart_port port;
struct timer_list timer;
unsigned int old_status;
unsigned int have_rtscts:1;
unsigned int have_rtsgpio:1;
unsigned int dte_mode:1;
unsigned int inverted_tx:1;
unsigned int inverted_rx:1;
struct clk *clk_ipg;
struct clk *clk_per;
const struct imx_uart_data *devdata;
struct mctrl_gpios *gpios;
/* shadow registers */
unsigned int ucr1;
unsigned int ucr2;
unsigned int ucr3;
unsigned int ucr4;
unsigned int ufcr;
/* DMA fields */
unsigned int dma_is_enabled:1;
unsigned int dma_is_rxing:1;
unsigned int dma_is_txing:1;
struct dma_chan *dma_chan_rx, *dma_chan_tx;
struct scatterlist rx_sgl, tx_sgl[2];
void *rx_buf;
struct circ_buf rx_ring;
unsigned int rx_buf_size;
unsigned int rx_period_length;
unsigned int rx_periods;
dma_cookie_t rx_cookie;
unsigned int tx_bytes;
unsigned int dma_tx_nents;
unsigned int saved_reg[10];
bool context_saved;
enum imx_tx_state tx_state;
struct hrtimer trigger_start_tx;
struct hrtimer trigger_stop_tx;
};
重点在操作方法,也就是ops。
这是一个很复杂的结构,里面函数很多,但基本都是硬件操作相关的。这里就不再细看。
drivers/tty/serial/imx.c
static const struct uart_ops imx_uart_pops = {
.tx_empty = imx_uart_tx_empty,
.set_mctrl = imx_uart_set_mctrl,
.get_mctrl = imx_uart_get_mctrl,
.stop_tx = imx_uart_stop_tx,
.start_tx = imx_uart_start_tx,
.stop_rx = imx_uart_stop_rx,
.enable_ms = imx_uart_enable_ms,
.break_ctl = imx_uart_break_ctl,
.startup = imx_uart_startup,
.shutdown = imx_uart_shutdown,
.flush_buffer = imx_uart_flush_buffer,
.set_termios = imx_uart_set_termios,
.type = imx_uart_type,
.config_port = imx_uart_config_port,
.verify_port = imx_uart_verify_port,
#if defined(CONFIG_CONSOLE_POLL)
.poll_init = imx_uart_poll_init,
.poll_get_char = imx_uart_poll_get_char,
.poll_put_char = imx_uart_poll_put_char,
#endif
};
static struct imx_port *imx_uart_ports[UART_NR];
这里看一下驱动入口,
drivers/tty/serial/imx.c
static const struct of_device_id imx_uart_dt_ids[] = {
{ .compatible = "fsl,imx6q-uart", .data = &imx_uart_devdata[IMX6Q_UART], },
{ .compatible = "fsl,imx53-uart", .data = &imx_uart_devdata[IMX53_UART], },
{ .compatible = "fsl,imx1-uart", .data = &imx_uart_devdata[IMX1_UART], },
{ .compatible = "fsl,imx21-uart", .data = &imx_uart_devdata[IMX21_UART], },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, imx_uart_dt_ids);
static struct platform_driver imx_uart_platform_driver = {
.probe = imx_uart_probe,
.remove = imx_uart_remove,
.driver = {
.name = "imx-uart",
.of_match_table = imx_uart_dt_ids,
.pm = &imx_uart_pm_ops,
},
};
static int __init imx_uart_init(void)
{
int ret = uart_register_driver(&imx_uart_uart_driver);
if (ret)
return ret;
ret = platform_driver_register(&imx_uart_platform_driver);
if (ret != 0)
uart_unregister_driver(&imx_uart_uart_driver);
return ret;
}
static void __exit imx_uart_exit(void)
{
platform_driver_unregister(&imx_uart_platform_driver);
uart_unregister_driver(&imx_uart_uart_driver);
}
module_init(imx_uart_init);
module_exit(imx_uart_exit);
这里我们看一下不同的soc都绑定了自己的硬件特有的信息,比如fifo大小等。
设备树配置:
uart7: serial@2018000 {
compatible = "fsl,imx6ul-uart",
"fsl,imx6q-uart";
reg = <0x02018000 0x4000>;
interrupts = <GIC_SPI 39 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_UART7_IPG>,
<&clks IMX6UL_CLK_UART7_SERIAL>;
clock-names = "ipg", "per";
status = "disabled";
};
uart1: serial@2020000 {
compatible = "fsl,imx6ul-uart",
"fsl,imx6q-uart";
reg = <0x02020000 0x4000>;
interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_UART1_IPG>,
<&clks IMX6UL_CLK_UART1_SERIAL>;
clock-names = "ipg", "per";
status = "disabled";
};
uart8: serial@2024000 {
compatible = "fsl,imx6ul-uart",
"fsl,imx6q-uart";
reg = <0x02024000 0x4000>;
interrupts = <GIC_SPI 40 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_UART8_IPG>,
<&clks IMX6UL_CLK_UART8_SERIAL>;
clock-names = "ipg", "per";
status = "disabled";
};
接下来这里就直接看probe。
drivers/tty/serial/imx.c
static int imx_uart_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct imx_port *sport;
void __iomem *base;
u32 dma_buf_conf[2];
int ret = 0;
u32 ucr1;
struct resource *res;
int txirq, rxirq, rtsirq;
sport = devm_kzalloc(&pdev->dev, sizeof(*sport), GFP_KERNEL);
if (!sport)
return -ENOMEM;
sport->devdata = of_device_get_match_data(&pdev->dev);
ret = of_alias_get_id(np, "serial");
if (ret < 0) {
dev_err(&pdev->dev, "failed to get alias id, errno %d\n", ret);
return ret;
}
sport->port.line = ret;
if (of_get_property(np, "uart-has-rtscts", NULL) ||
of_get_property(np, "fsl,uart-has-rtscts", NULL) /* deprecated */)
sport->have_rtscts = 1;
if (of_get_property(np, "fsl,dte-mode", NULL))
sport->dte_mode = 1;
if (of_get_property(np, "rts-gpios", NULL))
sport->have_rtsgpio = 1;
if (of_get_property(np, "fsl,inverted-tx", NULL))
sport->inverted_tx = 1;
if (of_get_property(np, "fsl,inverted-rx", NULL))
sport->inverted_rx = 1;
if (!of_property_read_u32_array(np, "fsl,dma-info", dma_buf_conf, 2)) {
sport->rx_period_length = dma_buf_conf[0];
sport->rx_periods = dma_buf_conf[1];
} else {
sport->rx_period_length = RX_DMA_PERIOD_LEN;
sport->rx_periods = RX_DMA_PERIODS;
}
if (sport->port.line >= ARRAY_SIZE(imx_uart_ports)) {
dev_err(&pdev->dev, "serial%d out of range\n",
sport->port.line);
return -EINVAL;
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
rxirq = platform_get_irq(pdev, 0);
if (rxirq < 0)
return rxirq;
txirq = platform_get_irq_optional(pdev, 1);
rtsirq = platform_get_irq_optional(pdev, 2);
sport->port.dev = &pdev->dev;
sport->port.mapbase = res->start;
sport->port.membase = base;
sport->port.type = PORT_IMX;
sport->port.iotype = UPIO_MEM;
sport->port.irq = rxirq;
sport->port.fifosize = 32;
sport->port.has_sysrq = IS_ENABLED(CONFIG_SERIAL_IMX_CONSOLE);
sport->port.ops = &imx_uart_pops;
sport->port.rs485_config = imx_uart_rs485_config;
/* RTS is required to control the RS485 transmitter */
if (sport->have_rtscts || sport->have_rtsgpio)
sport->port.rs485_supported = imx_rs485_supported;
else
sport->port.rs485_supported = imx_no_rs485;
sport->port.flags = UPF_BOOT_AUTOCONF;
timer_setup(&sport->timer, imx_uart_timeout, 0);
sport->gpios = mctrl_gpio_init(&sport->port, 0);
if (IS_ERR(sport->gpios))
return PTR_ERR(sport->gpios);
sport->clk_ipg = devm_clk_get(&pdev->dev, "ipg");
if (IS_ERR(sport->clk_ipg)) {
ret = PTR_ERR(sport->clk_ipg);
dev_err(&pdev->dev, "failed to get ipg clk: %d\n", ret);
return ret;
}
sport->clk_per = devm_clk_get(&pdev->dev, "per");
if (IS_ERR(sport->clk_per)) {
ret = PTR_ERR(sport->clk_per);
dev_err(&pdev->dev, "failed to get per clk: %d\n", ret);
return ret;
}
sport->port.uartclk = clk_get_rate(sport->clk_per);
/* For register access, we only need to enable the ipg clock. */
ret = clk_prepare_enable(sport->clk_ipg);
if (ret) {
dev_err(&pdev->dev, "failed to enable per clk: %d\n", ret);
return ret;
}
/* initialize shadow register values */
sport->ucr1 = readl(sport->port.membase + UCR1);
sport->ucr2 = readl(sport->port.membase + UCR2);
sport->ucr3 = readl(sport->port.membase + UCR3);
sport->ucr4 = readl(sport->port.membase + UCR4);
sport->ufcr = readl(sport->port.membase + UFCR);
ret = uart_get_rs485_mode(&sport->port);
if (ret) {
clk_disable_unprepare(sport->clk_ipg);
return ret;
}
if (sport->port.rs485.flags & SER_RS485_ENABLED &&
(!sport->have_rtscts && !sport->have_rtsgpio))
dev_err(&pdev->dev, "no RTS control, disabling rs485\n");
/*
* If using the i.MX UART RTS/CTS control then the RTS (CTS_B)
* signal cannot be set low during transmission in case the
* receiver is off (limitation of the i.MX UART IP).
*/
if (sport->port.rs485.flags & SER_RS485_ENABLED &&
sport->have_rtscts && !sport->have_rtsgpio &&
(!(sport->port.rs485.flags & SER_RS485_RTS_ON_SEND) &&
!(sport->port.rs485.flags & SER_RS485_RX_DURING_TX)))
dev_err(&pdev->dev,
"low-active RTS not possible when receiver is off, enabling receiver\n");
/* Disable interrupts before requesting them */
ucr1 = imx_uart_readl(sport, UCR1);
ucr1 &= ~(UCR1_ADEN | UCR1_TRDYEN | UCR1_IDEN | UCR1_RRDYEN | UCR1_RTSDEN);
imx_uart_writel(sport, ucr1, UCR1);
if (!imx_uart_is_imx1(sport) && sport->dte_mode) {
/*
* The DCEDTE bit changes the direction of DSR, DCD, DTR and RI
* and influences if UCR3_RI and UCR3_DCD changes the level of RI
* and DCD (when they are outputs) or enables the respective
* irqs. So set this bit early, i.e. before requesting irqs.
*/
u32 ufcr = imx_uart_readl(sport, UFCR);
if (!(ufcr & UFCR_DCEDTE))
imx_uart_writel(sport, ufcr | UFCR_DCEDTE, UFCR);
/*
* Disable UCR3_RI and UCR3_DCD irqs. They are also not
* enabled later because they cannot be cleared
* (confirmed on i.MX25) which makes them unusable.
*/
imx_uart_writel(sport,
IMX21_UCR3_RXDMUXSEL | UCR3_ADNIMP | UCR3_DSR,
UCR3);
} else {
u32 ucr3 = UCR3_DSR;
u32 ufcr = imx_uart_readl(sport, UFCR);
if (ufcr & UFCR_DCEDTE)
imx_uart_writel(sport, ufcr & ~UFCR_DCEDTE, UFCR);
if (!imx_uart_is_imx1(sport))
ucr3 |= IMX21_UCR3_RXDMUXSEL | UCR3_ADNIMP;
imx_uart_writel(sport, ucr3, UCR3);
}
clk_disable_unprepare(sport->clk_ipg);
hrtimer_init(&sport->trigger_start_tx, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
hrtimer_init(&sport->trigger_stop_tx, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
sport->trigger_start_tx.function = imx_trigger_start_tx;
sport->trigger_stop_tx.function = imx_trigger_stop_tx;
/*
* Allocate the IRQ(s) i.MX1 has three interrupts whereas later
* chips only have one interrupt.
*/
if (txirq > 0) {
ret = devm_request_irq(&pdev->dev, rxirq, imx_uart_rxint, 0,
dev_name(&pdev->dev), sport);
if (ret) {
dev_err(&pdev->dev, "failed to request rx irq: %d\n",
ret);
return ret;
}
ret = devm_request_irq(&pdev->dev, txirq, imx_uart_txint, 0,
dev_name(&pdev->dev), sport);
if (ret) {
dev_err(&pdev->dev, "failed to request tx irq: %d\n",
ret);
return ret;
}
ret = devm_request_irq(&pdev->dev, rtsirq, imx_uart_rtsint, 0,
dev_name(&pdev->dev), sport);
if (ret) {
dev_err(&pdev->dev, "failed to request rts irq: %d\n",
ret);
return ret;
}
} else {
ret = devm_request_irq(&pdev->dev, rxirq, imx_uart_int, 0,
dev_name(&pdev->dev), sport);
if (ret) {
dev_err(&pdev->dev, "failed to request irq: %d\n", ret);
return ret;
}
}
imx_uart_ports[sport->port.line] = sport;
platform_set_drvdata(pdev, sport);
return uart_add_one_port(&imx_uart_uart_driver, &sport->port);
}
三、串口驱动的注册
int ret = uart_register_driver(&imx_uart_uart_driver);
if (ret)
return ret;
ret = platform_driver_register(&imx_uart_platform_driver);
if (ret != 0)
uart_unregister_driver(&imx_uart_uart_driver);
串口在linux系统中属于tty设备,tty设备是从古老的纸带打印机流传下来的。当时的输入,输出都是依靠纸带来传输。
后面有了串口之后,输入输出就都通过串口来传输。当然后面又有了新的显示器,键盘之类作为新的输入输出设备。
drivers/tty/serial/serial_core.c
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 = tty_alloc_driver(drv->nr, TTY_DRIVER_REAL_RAW |
TTY_DRIVER_DYNAMIC_DEV);
if (IS_ERR(normal)) {
retval = PTR_ERR(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->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);
tty_driver_kref_put(normal);
out_kfree:
kfree(drv->state);
out:
return retval;
}
EXPORT_SYMBOL(uart_register_driver);
这个函数是通过serial_core来内核帮我们定义好的,可以看到,主要做三件事,申请tty设备,初始化tty设备,注册tty设备。
四、注册console驱动
uart_add_one_port(&imx_uart_uart_driver, &sport->port);
drivers/tty/serial/serial_core.c
int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
{
struct uart_state *state;
struct tty_port *port;
int ret = 0;
struct device *tty_dev;
int num_groups;
if (uport->line >= drv->nr)
return -EINVAL;
state = drv->state + uport->line;
port = &state->port;
mutex_lock(&port_mutex);
mutex_lock(&port->mutex);
if (state->uart_port) {
ret = -EINVAL;
goto out;
}
/* Link the port to the driver state table and vice versa */
atomic_set(&state->refcount, 1);
init_waitqueue_head(&state->remove_wait);
state->uart_port = uport;
uport->state = state;
state->pm_state = UART_PM_STATE_UNDEFINED;
uport->cons = drv->cons;
uport->minor = drv->tty_driver->minor_start + uport->line;
uport->name = kasprintf(GFP_KERNEL, "%s%d", drv->dev_name,
drv->tty_driver->name_base + uport->line);
if (!uport->name) {
ret = -ENOMEM;
goto out;
}
/*
* If this port is in use as a console then the spinlock is already
* initialised.
*/
if (!uart_console_enabled(uport))
uart_port_spin_lock_init(uport);
if (uport->cons && uport->dev)
of_console_check(uport->dev->of_node, uport->cons->name, uport->line);
tty_port_link_device(port, drv->tty_driver, uport->line);
uart_configure_port(drv, state, uport);
port->console = uart_console(uport);
num_groups = 2;
if (uport->attr_group)
num_groups++;
uport->tty_groups = kcalloc(num_groups, sizeof(*uport->tty_groups),
GFP_KERNEL);
if (!uport->tty_groups) {
ret = -ENOMEM;
goto out;
}
uport->tty_groups[0] = &tty_dev_attr_group;
if (uport->attr_group)
uport->tty_groups[1] = uport->attr_group;
/*
* Register the port whether it's detected or not. This allows
* setserial to be used to alter this port's parameters.
*/
tty_dev = tty_port_register_device_attr_serdev(port, drv->tty_driver,
uport->line, uport->dev, port, uport->tty_groups);
if (!IS_ERR(tty_dev)) {
device_set_wakeup_capable(tty_dev, 1);
} else {
dev_err(uport->dev, "Cannot register tty device on line %d\n",
uport->line);
}
/*
* Ensure UPF_DEAD is not set.
*/
uport->flags &= ~UPF_DEAD;
out:
mutex_unlock(&port->mutex);
mutex_unlock(&port_mutex);
return ret;
}
EXPORT_SYMBOL(uart_add_one_port);
上面就是注册端口了,这里要注意,端口是在uart_register_driver申请和创建的,地址在status里面。
接下来就是调用这个串口配置这个端口.
uart_configure_port(drv, state, uport);
drivers/tty/serial/serial_core.c
static void
uart_configure_port(struct uart_driver *drv, struct uart_state *state,
struct uart_port *port)
{
unsigned int flags;
/*
* If there isn't a port here, don't do anything further.
*/
if (!port->iobase && !port->mapbase && !port->membase)
return;
/*
* Now do the auto configuration stuff. Note that config_port
* is expected to claim the resources and map the port for us.
*/
flags = 0;
if (port->flags & UPF_AUTO_IRQ)
flags |= UART_CONFIG_IRQ;
if (port->flags & UPF_BOOT_AUTOCONF) {
if (!(port->flags & UPF_FIXED_TYPE)) {
port->type = PORT_UNKNOWN;
flags |= UART_CONFIG_TYPE;
}
port->ops->config_port(port, flags);
}
if (port->type != PORT_UNKNOWN) {
unsigned long flags;
uart_report_port(drv, port);
/* Power up port for set_mctrl() */
uart_change_pm(state, UART_PM_STATE_ON);
/*
* Ensure that the modem control lines are de-activated.
* keep the DTR setting that is set in uart_set_options()
* We probably don't need a spinlock around this, but
*/
spin_lock_irqsave(&port->lock, flags);
port->mctrl &= TIOCM_DTR;
if (!(port->rs485.flags & SER_RS485_ENABLED))
port->ops->set_mctrl(port, port->mctrl);
else
uart_rs485_config(port);
spin_unlock_irqrestore(&port->lock, flags);
/*
* If this driver supports console, and it hasn't been
* successfully registered yet, try to re-register it.
* It may be that the port was not available.
*/
if (port->cons && !(port->cons->flags & CON_ENABLED))
register_console(port->cons);
/*
* Power down all ports by default, except the
* console if we have one.
*/
if (!uart_console(port))
uart_change_pm(state, UART_PM_STATE_OFF);
}
}
配置端口主要还是注册console
register_console(port->cons);
上面我们注意下面这个函数,会打印一些调试信息
uart_report_port(drv, port);
drivers/tty/serial/serial_core.c
static inline void
uart_report_port(struct uart_driver *drv, struct uart_port *port)
{
char address[64];
switch (port->iotype) {
case UPIO_PORT:
snprintf(address, sizeof(address), "I/O 0x%lx", port->iobase);
break;
case UPIO_HUB6:
snprintf(address, sizeof(address),
"I/O 0x%lx offset 0x%x", port->iobase, port->hub6);
break;
case UPIO_MEM:
case UPIO_MEM16:
case UPIO_MEM32:
case UPIO_MEM32BE:
case UPIO_AU:
case UPIO_TSI:
snprintf(address, sizeof(address),
"MMIO 0x%llx", (unsigned long long)port->mapbase);
break;
default:
strscpy(address, "*unknown*", sizeof(address));
break;
}
pr_info("%s%s%s at %s (irq = %d, base_baud = %d) is a %s\n",
port->dev ? dev_name(port->dev) : "",
port->dev ? ": " : "",
port->name,
address, port->irq, port->uartclk / 16, uart_type(port));
/* The magic multiplier feature is a bit obscure, so report it too. */
if (port->flags & UPF_MAGIC_MULTIPLIER)
pr_info("%s%s%s extra baud rates supported: %d, %d",
port->dev ? dev_name(port->dev) : "",
port->dev ? ": " : "",
port->name,
port->uartclk / 8, port->uartclk / 4);
}
kernel/printk/printk.c
void register_console(struct console *newcon)
{
struct console *con;
bool bootcon_enabled = false;
bool realcon_enabled = false;
int err;
for_each_console(con) {
if (WARN(con == newcon, "console '%s%d' already registered\n",
con->name, con->index))
return;
}
for_each_console(con) {
if (con->flags & CON_BOOT)
bootcon_enabled = true;
else
realcon_enabled = true;
}
/* Do not register boot consoles when there already is a real one. */
if (newcon->flags & CON_BOOT && realcon_enabled) {
pr_info("Too late to register bootconsole %s%d\n",
newcon->name, newcon->index);
return;
}
/*
* See if we want to enable this console driver by default.
*
* Nope when a console is preferred by the command line, device
* tree, or SPCR.
*
* The first real console with tty binding (driver) wins. More
* consoles might get enabled before the right one is found.
*
* Note that a console with tty binding will have CON_CONSDEV
* flag set and will be first in the list.
*/
if (preferred_console < 0) {
if (!console_drivers || !console_drivers->device ||
console_drivers->flags & CON_BOOT) {
try_enable_default_console(newcon);
}
}
/* See if this console matches one we selected on the command line */
err = try_enable_preferred_console(newcon, true);
/* If not, try to match against the platform default(s) */
if (err == -ENOENT)
err = try_enable_preferred_console(newcon, false);
/* printk() messages are not printed to the Braille console. */
if (err || newcon->flags & CON_BRL)
return;
/*
* If we have a bootconsole, and are switching to a real console,
* don't print everything out again, since when the boot console, and
* the real console are the same physical device, it's annoying to
* see the beginning boot messages twice
*/
if (bootcon_enabled &&
((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV)) {
newcon->flags &= ~CON_PRINTBUFFER;
}
/*
* Put this console in the list - keep the
* preferred driver at the head of the list.
*/
console_lock();
if ((newcon->flags & CON_CONSDEV) || console_drivers == NULL) {
newcon->next = console_drivers;
console_drivers = newcon;
if (newcon->next)
newcon->next->flags &= ~CON_CONSDEV;
/* Ensure this flag is always set for the head of the list */
newcon->flags |= CON_CONSDEV;
} else {
newcon->next = console_drivers->next;
console_drivers->next = newcon;
}
newcon->dropped = 0;
if (newcon->flags & CON_PRINTBUFFER) {
/* Get a consistent copy of @syslog_seq. */
mutex_lock(&syslog_lock);
newcon->seq = syslog_seq;
mutex_unlock(&syslog_lock);
} else {
/* Begin with next message. */
newcon->seq = prb_next_seq(prb);
}
console_unlock();
console_sysfs_notify();
/*
* By unregistering the bootconsoles after we enable the real console
* we get the "console xxx enabled" message on all the consoles -
* boot consoles, real consoles, etc - this is to ensure that end
* users know there might be something in the kernel's log buffer that
* went to the bootconsole (that they do not see on the real console)
*/
con_printk(KERN_INFO, newcon, "enabled\n");
if (bootcon_enabled &&
((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV) &&
!keep_bootcon) {
for_each_console(con)
if (con->flags & CON_BOOT)
unregister_console(con);
}
}
EXPORT_SYMBOL(register_console);
注册console,其实就是把console使用链表方式next连接起来。
这就回到上一节的了,把注册的这个console和上节,uboot通过cmdline传过来的那些console和串口注册的做比较,如果名字和index都相同的话,则把这个串口表示使能CON_ENABLED,后面使用printk或printf的时候,就会调用使能的console打印信息。
#define MAX_CMDLINECONSOLES 8
static struct console_cmdline console_cmdline[MAX_CMDLINECONSOLES];
在串口配置之前是要执行上面这个函数的,因为设备树传参的。
if (uport->cons && uport->dev)
of_console_check(uport->dev->of_node, uport->cons->name, uport->line);
uart_configure_port(drv, state, uport);
kernel/printk/printk.c
bool of_console_check(struct device_node *dn, char *name, int index)
{
if (!dn || dn != of_stdout || console_set_on_cmdline)
return false;
/*
* XXX: cast `options' to char pointer to suppress complication
* warnings: printk, UART and console drivers expect char pointer.
*/
return !add_preferred_console(name, index, (char *)of_stdout_options);
}
EXPORT_SYMBOL_GPL(of_console_check);
int add_preferred_console(char *name, int idx, char *options)
{
return __add_preferred_console(name, idx, options, NULL, false);
}
static int __add_preferred_console(char *name, int idx, char *options,
char *brl_options, bool user_specified)
{
struct console_cmdline *c;
int i;
/*
* See if this tty is not yet registered, and
* if we have a slot free.
*/
for (i = 0, c = console_cmdline;
i < MAX_CMDLINECONSOLES && c->name[0];
i++, c++) {
if (strcmp(c->name, name) == 0 && c->index == idx) {
if (!brl_options)
preferred_console = i;
set_user_specified(c, user_specified);
return 0;
}
}
if (i == MAX_CMDLINECONSOLES)
return -E2BIG;
if (!brl_options)
preferred_console = i;
strlcpy(c->name, name, sizeof(c->name));
c->options = options;
set_user_specified(c, user_specified);
braille_set_options(c, brl_options);
c->index = idx;
return 0;
}
可以看到这里是要判断的,只有名字以及port->line和console_cmdline[x].index匹配上,preferred_console 不为-1,而是和console_cmdline匹配上的下标值
比如uboot传的值是"console = ttyS0",所以ttyS0匹配不成功,才会匹配下一个。
五、总结
5.1、Linux Console 体系结构
Linux 控制台体系结构包括几个关键部分,它们协同工作来提供终端控制功能:
1、虚拟控制台 (Virtual Consoles)
Linux 支持多个虚拟控制台(VC)。每个 VC 都代表一个独立的终端(类似于独立的屏幕)。默认情况下,Linux 提供 6 个虚拟控制台,分别从 /dev/tty1
到 /dev/tty6
,可以通过键盘切换。
2、TTY 子系统 (TTY Subsystem)
TTY 是 "Teletypewriter" 的缩写,指代终端设备的抽象层。TTY 子系统负责管理控制台输入输出,提供标准的终端接口。TTY 层包括两个主要部分:
- TTY 驱动:负责管理每个虚拟控制台的输入输出、缓冲区、以及字符的接收和发送。
- TTY 设备:每个虚拟控制台对应一个
tty
设备,例如/dev/tty1
。这些设备通过文件系统暴露给用户空间,允许用户与控制台进行交互。
3、Console 驱动
Console 驱动是操作系统中负责管理显示和输入的驱动。它主要提供以下功能:
- 显示输出:将字符输出到显示设备(如屏幕)。
- 处理输入:接收并处理键盘输入。
- 控制光标:管理光标的移动和状态。
常见的 console 驱动有:
- framebuffer 驱动:为图形显示提供支持。
- 字符模式控制台驱动:提供基于字符的输出。
5.2、Console 驱动的工作流程
下面描述了 Linux 控制台驱动的工作流程,从输入、输出到屏幕显示的整个过程:
1、启动阶段
- 内核启动时初始化控制台:当 Linux 内核启动时,控制台驱动会被初始化。内核会根据配置选择合适的控制台驱动(如字符模式控制台或 framebuffer 控制台)。
- 创建虚拟控制台:内核会创建多个虚拟控制台(如
/dev/tty1
,/dev/tty2
等),并将它们与 TTY 子系统关联。
2、显示输出(console
)
- 内核输出到控制台:内核可以通过
printk
函数将调试信息或错误信息输出到控制台。printk
会将信息传递给 TTY 子系统,然后显示在相应的虚拟控制台上。 - 字符设备输出:虚拟控制台通过字符设备驱动将字符显示到屏幕上。如果是图形模式控制台,则通过 framebuffer 驱动输出字符。
3、用户输入(keyboard
)
- 键盘输入到控制台:当用户在控制台上按下键盘键时,输入会被 Linux 输入子系统(
input subsystem
)捕获。 - TTY 驱动接收输入:输入子系统将键盘事件传递给相应的 TTY 驱动。
- 缓冲区管理:TTY 驱动维护输入缓冲区,并根据配置(如行模式、字符模式)对输入进行处理。
4、光标控制
- 光标位置:控制台驱动通过操作光标的位置来实现文本的输入和输出。在字符模式下,光标位置是通过改变显示缓冲区中的位置来控制的。
- 光标的上下左右移动:通过特殊的控制字符(如
\033[A
表示上移光标)来进行光标移动。
5、切换虚拟控制台
- 切换控制台:用户可以通过
Ctrl+Alt+F1
(到/dev/tty1
)等快捷键切换不同的虚拟控制台。内核通过 TTY 子系统管理多个控制台的切换。 - 切换过程:当用户切换到另一个控制台时,内核会切换当前显示的虚拟控制台,并将焦点移到目标控制台。
6、TTY 子系统与 Console 驱动的关系
Console 驱动负责将字符从内核输出到屏幕,而 TTY 子系统则负责管理虚拟终端、接收输入并将其交给应用程序处理。它们之间通过 console
接口进行交互,控制台驱动通过 TTY 层获取输入和输出。
5.3、控制台驱动的实现
控制台驱动的实现方式有很多,具体取决于你使用的硬件和显示方式。Linux 控制台可以工作在字符模式或图形模式下。
1、字符模式控制台驱动
- 早期的 Linux 控制台通常工作在字符模式下,显示内容是纯文本。
- 这类驱动会管理屏幕上的字符输出和输入缓冲区。
关键函数:
console_print()
:用于输出字符到控制台。console_flush()
:刷新控制台输出缓冲区。console_lock()
和console_unlock()
:用于同步控制台的访问。
2、Framebuffer 控制台驱动
- 当图形硬件支持时,Linux 可以将控制台切换到图形模式,这通常依赖于 framebuffer 驱动。
fbcon
(Framebuffer Console)提供了基于 framebuffer 的控制台输出功能,可以在图形显示设备上绘制文本和图形。
关键函数:
fb_con_init()
:初始化 framebuffer 控制台。fb_con_display()
:在 framebuffer 上显示字符。