Linux 内核调试篇3

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、启动阶段
  1. 内核启动时初始化控制台:当 Linux 内核启动时,控制台驱动会被初始化。内核会根据配置选择合适的控制台驱动(如字符模式控制台或 framebuffer 控制台)。
  2. 创建虚拟控制台:内核会创建多个虚拟控制台(如 /dev/tty1, /dev/tty2 等),并将它们与 TTY 子系统关联。
2、显示输出(console
  1. 内核输出到控制台:内核可以通过 printk 函数将调试信息或错误信息输出到控制台。printk 会将信息传递给 TTY 子系统,然后显示在相应的虚拟控制台上。
  2. 字符设备输出:虚拟控制台通过字符设备驱动将字符显示到屏幕上。如果是图形模式控制台,则通过 framebuffer 驱动输出字符。
3、用户输入(keyboard
  1. 键盘输入到控制台:当用户在控制台上按下键盘键时,输入会被 Linux 输入子系统(input subsystem)捕获。
  2. TTY 驱动接收输入:输入子系统将键盘事件传递给相应的 TTY 驱动。
  3. 缓冲区管理:TTY 驱动维护输入缓冲区,并根据配置(如行模式、字符模式)对输入进行处理。
4、光标控制
  1. 光标位置:控制台驱动通过操作光标的位置来实现文本的输入和输出。在字符模式下,光标位置是通过改变显示缓冲区中的位置来控制的。
  2. 光标的上下左右移动:通过特殊的控制字符(如 \033[A 表示上移光标)来进行光标移动。
5、切换虚拟控制台
  1. 切换控制台:用户可以通过 Ctrl+Alt+F1(到 /dev/tty1)等快捷键切换不同的虚拟控制台。内核通过 TTY 子系统管理多个控制台的切换。
  2. 切换过程:当用户切换到另一个控制台时,内核会切换当前显示的虚拟控制台,并将焦点移到目标控制台。
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 上显示字符。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值