1.简介
UART驱动是我们每个人都会遇到的,输出log都少不了UART,这个是我们和系统给交流的窗口。下面我们在看一下解析一下这个驱动的code.
软件:linux 4.14.98
硬件: imx8q
UART总体驱动框图如下图所示:
2.设备树
在这里以lpuart0为例,通过设备树(device tree)的相关代码,查看该设备是怎么配置的。废话不多少,上代码:
file:fsl-imx8qxp-mek.dtsi
#include "fsl-imx8qxp.dtsi"
/ {
model = "Freescale i.MX8QXP MEK";
compatible = "fsl,imx8qxp-mek", "fsl,imx8qxp";
chosen {
bootargs = "console=ttyLP0,115200 earlycon=lpuart32,0x5a060000,115200";
stdout-path = &lpuart0;
};
pinctrl_lpuart0: lpuart0grp {
fsl,pins = <
SC_P_UART0_RX_ADMA_UART0_RX 0x06000020
SC_P_UART0_TX_ADMA_UART0_TX 0x06000020
>;
};
........................
&pd_dma_lpuart0 {
debug_console;
};
&lpuart0 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_lpuart0>;
status = "okay";
};
.............................
file:fsl-imx8dx.dtsi中有对lpuart0的定义
.....................................
/ {
model = "Freescale i.MX8DX";
compatible = "fsl,imx8dx", "fsl,imx8qxp";
interrupt-parent = <&gic>;
#address-cells = <2>;
#size-cells = <2>;
pmu {
interrupt-affinity = <&A35_0>, <&A35_1>;
};
aliases {
csi0 = &mipi_csi_0;
dpu0 = &dpu1;
ethernet0 = &fec1;
ethernet1 = &fec2;
dsi_phy0 = &mipi_dsi_phy1;
dsi_phy1 = &mipi_dsi_phy2;
mipi_dsi0 = &mipi_dsi1;
mipi_dsi1 = &mipi_dsi2;
ldb0 = &ldb1;
ldb1 = &ldb2;
isi0 = &isi_0;
isi1 = &isi_1;
isi2 = &isi_2;
isi3 = &isi_3;
isi4 = &isi_4;
isi5 = &isi_5;
isi6 = &isi_6;
isi7 = &isi_7;
serial0 = &lpuart0;
serial1 = &lpuart1;
serial2 = &lpuart2;
serial3 = &lpuart3;
mmc0 = &usdhc1;
mmc1 = &usdhc2;
mmc2 = &usdhc3;
can0 = &flexcan1;
can1 = &flexcan2;
can2 = &flexcan3;
i2c1 = &i2c_rpbus_1;
i2c5 = &i2c_rpbus_5;
i2c12 = &i2c_rpbus_12;
i2c13 = &i2c_rpbus_13;
i2c14 = &i2c_rpbus_14;
i2c15 = &i2c_rpbus_15;
};
..............................................
pd_dma_lpuart0: PD_DMA_UART0 {
reg = <SC_R_UART_0>;
#power-domain-cells = <0>;
power-domains = <&pd_dma>;
wakeup-irq = <345>;
};
.........................
lpuart0: serial@5a060000 {
compatible = "fsl,imx8qm-lpuart";
reg = <0x0 0x5a060000 0x0 0x1000>;
interrupts = <GIC_SPI 345 IRQ_TYPE_LEVEL_HIGH>;
interrupt-parent = <&wu>;
clocks = <&clk IMX8QXP_UART0_CLK>,
<&clk IMX8QXP_UART0_IPG_CLK>;
clock-names = "per", "ipg";
assigned-clocks = <&clk IMX8QXP_UART0_CLK>;
assigned-clock-rates = <80000000>;
power-domains = <&pd_dma_lpuart0>;
status = "disabled";
};
在这一部分定义了lpuart设备相关的寄存器,管脚等参数。
2.TTY 层初始化
2.1线路规程初始化
线路规程初始化,是在console_init()函数中实现的。
kernel/printk/printk.c :void __init console_init(void)
/*
* Initialize the console device. This is called *early*, so
* we can't necessarily depend on lots of kernel help here.
* Just do some early initializations, and do the complex setup
* later.
*/
void __init console_init(void)
{
initcall_t *call;
/* Setup the default TTY line discipline. */
n_tty_init();
/*
* set up the console device so that later boot sequences can
* inform about problems etc..
*/
call = __con_initcall_start;
while (call < __con_initcall_end) {
(*call)();
call++;
}
}
drivers/tty/n_tty.c:n_tty_init()函数
void __init n_tty_init(void)
{
tty_register_ldisc(N_TTY, &n_tty_ops);
}
drivers/tty/tty_ldisc.c: tty_register_ldisc(N_TTY, &n_tty_ops);
/**
* tty_register_ldisc - install a line discipline
* @disc: ldisc number
* @new_ldisc: pointer to the ldisc object
*
* Installs a new line discipline into the kernel. The discipline
* is set up as unreferenced and then made available to the kernel
* from this point onwards.
*
* Locking:
* takes tty_ldiscs_lock to guard against ldisc races
*/
int tty_register_ldisc(int disc, struct tty_ldisc_ops *new_ldisc)
{
unsigned long flags;
int ret = 0;
if (disc < N_TTY || disc >= NR_LDISCS)
return -EINVAL;
raw_spin_lock_irqsave(&tty_ldiscs_lock, flags);
tty_ldiscs[disc] = new_ldisc;
new_ldisc->num = disc;
new_ldisc->refcount = 0;
raw_spin_unlock_irqrestore(&tty_ldiscs_lock, flags);
return ret;
}
EXPORT_SYMBOL(tty_register_ldisc);
这个tty_ldisc初始化到这一步就暂时放在一边,待到后面通过open函数中再来调用此处已注册的的tty_disc线路规程。
2.2 tty_driver相关部分的初始化
tty_driver的初始化从lpuart_serial_init()函数看起。
“drivers/tty/serial/fsl_lpuart.c” 这个文件代码有点多,现在我们只看自己需要的代码:从module初始化和退出的函数开始看起
static int __init lpuart_serial_init(void)
{
int ret = uart_register_driver(&lpuart_reg);
if (ret)
return ret;
ret = platform_driver_register(&lpuart_driver);
if (ret)
uart_unregister_driver(&lpuart_reg);
return ret;
}
static void __exit lpuart_serial_exit(void)
{
platform_driver_unregister(&lpuart_driver);
uart_unregister_driver(&lpuart_reg);
}
我们看初始化函数**:int ret = uart_register_driver(&lpuart_reg);**
其中 lpuart_reg的定义如下:
static struct uart_driver lpuart_reg = {
.owner = THIS_MODULE,
.driver_name = DRIVER_NAME,
.dev_name = DEV_NAME,
.nr = ARRAY_SIZE(lpuart_ports),
.cons = LPUART_CONSOLE,
};
其中各个变量的定义:
........................
#ifdef MODULE
extern struct module __this_module;
#define THIS_MODULE (&__this_module)
#else
#define THIS_MODULE ((struct module *)0)
#endif
...............................
#define DRIVER_NAME "fsl-lpuart"
#define DEV_NAME "ttyLP"
#define LPUART_CONSOLE (&lpuart_console)
....................................
static struct console lpuart_console = {
.name = DEV_NAME,
.write = lpuart_console_write,
.device = uart_console_device,
.setup = lpuart_console_setup,
.flags = CON_PRINTBUFFER,
.index = -1,
.data = &lpuart_reg,
};
.......................................
然后转到drivers/tty/serial/serial_core.c,查看函数uart_register_driver(),函数的定义:
/**
* uart_register_driver - register a driver with the uart core layer
* @drv: low level driver structure
*
* Register a uart driver with the core driver. We in turn register
* with the tty layer, and initialise the core driver per-port state.
*
* We have a proc file in /proc/tty/driver which is named after the
* normal driver.
*
* drv->port should be NULL, and the per-port structures should be
* registered using uart_add_one_port after this call has succeeded.
*/
int uart_register_driver(struct uart_driver *drv)
{
struct tty_driver *normal;
int i, retval;
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 = kzalloc(sizeof(struct uart_state) * drv->nr, 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 -ENOMEM;
}
下面我们看一下 tty_register_driver(normal);这这里申请dev结构体并添加driver。
/*
* Called by a tty driver to register itself.
*/
int tty_register_driver(struct tty_driver *driver)
{
int error;
int i;
dev_t dev;
struct device *d;
if (!driver->major) {
error = alloc_chrdev_region(&dev, driver->minor_start,
driver->num, driver->name);
if (!error) {
driver->major = MAJOR(dev);
driver->minor_start = MINOR(dev);
}
} else {
dev = MKDEV(driver->major, driver->minor_start);
error = register_chrdev_region(dev, driver->num, driver->name);
}
if (error < 0)
goto err;
if (driver->flags & TTY_DRIVER_DYNAMIC_ALLOC) {
error = tty_cdev_add(driver, dev, 0, driver->num);
if (error)
goto err_unreg_char;
}
mutex_lock(&tty_mutex);
list_add(&driver->tty_drivers, &tty_drivers);
mutex_unlock(&tty_mutex);
if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {
for (i = 0; i < driver->num; i++) {
d = tty_register_device(driver, i, NULL);
if (IS_ERR(d)) {
error = PTR_ERR(d);
goto err_unreg_devs;
}
}
}
proc_tty_register_driver(driver);
driver->flags |= TTY_DRIVER_INSTALLED;
return 0;
err_unreg_devs:
for (i--; i >= 0; i--)
tty_unregister_device(driver, i);
mutex_lock(&tty_mutex);
list_del(&driver->tty_drivers);
mutex_unlock(&tty_mutex);
err_unreg_char:
unregister_chrdev_region(dev, driver->num);
err:
return error;
}
EXPORT_SYMBOL(tty_register_driver);
从上述代码中我们可以看到,tty driver 的参数是从uart_driver中来的,这就相当于在tty_driver外面就加了一层uart_driver。而uart_driver中的部分字段和tty_driver中的部分字段数值是一样的。
tty_set_operations(normal, &uart_ops);指定了tty_driver的操作集tty_operation为uart_ops。
3 Uart driver 初始化
Uart driver初始化中有个重要的架构体uart_state,uart_state中有两个重要的变量,其中包含uart_port、tty_port。tty_port在之前uart_register_driver()中进行了初始化。而uart_port是在另一个probe函数中进行的。
下面我们看platform_driver_register()函数
ret = platform_driver_register(&lpuart_driver);
/*
* use a macro to avoid include chaining to get THIS_MODULE
*/
#define platform_driver_register(drv) \
__platform_driver_register(drv, THIS_MODULE)
extern int __platform_driver_register(struct platform_driver *,
struct module *);
对这个函数我们就不细讲了,现在看一下,lpuart_driver 结构体。
static struct platform_driver lpuart_driver = {
.probe = lpuart_probe,
.remove = lpuart_remove,
.driver = {
.name = "fsl-lpuart",
.of_match_table = lpuart_dt_ids,
.pm = SERIAL_LPUART_PM_OPS,
},
};
下面看lpuart_probe函数
static int lpuart_probe(struct platform_device *pdev)
{
const struct of_device_id *of_id = of_match_device(lpuart_dt_ids,
&pdev->dev);
const struct lpuart_soc_data *sdata = of_id->data;
struct device_node *np = pdev->dev.of_node;
struct lpuart_port *sport;
struct resource *res;
unsigned long cr_32;
unsigned char cr_8;
int ret;
sport = devm_kzalloc(&pdev->dev, sizeof(*sport), GFP_KERNEL);
if (!sport)
return -ENOMEM;
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;
}
if (ret >= ARRAY_SIZE(lpuart_ports)) {
dev_err(&pdev->dev, "serial%d out of range\n", ret);
return -EINVAL;
}
sport->port.line = ret;
sport->dma_eeop = of_device_is_compatible(np, "fsl,imx8qm-lpuart");
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
sport->port.membase = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(sport->port.membase))
return PTR_ERR(sport->port.membase);
sport->regbase = sport->port.membase;
sport->port.membase += sdata->reg_off;
sport->port.mapbase = res->start + sdata->reg_off;
sport->port.dev = &pdev->dev;
sport->port.type = PORT_LPUART;
ret = platform_get_irq(pdev, 0);
if (ret < 0) {
dev_err(&pdev->dev, "cannot obtain irq\n");
return ret;
}
sport->port.irq = ret;
sport->port.iotype = sdata->iotype;
if (lpuart_is_32(sport))
sport->port.ops = &lpuart32_pops;
else
sport->port.ops = &lpuart_pops;
sport->port.flags = UPF_BOOT_AUTOCONF;
if (!lpuart_is_32(sport))
sport->port.rs485_config = lpuart_config_rs485;
sport->ipg_clk = devm_clk_get(&pdev->dev, "ipg");
if (IS_ERR(sport->ipg_clk)) {
ret = PTR_ERR(sport->ipg_clk);
dev_err(&pdev->dev, "failed to get ipg clk: %d\n", ret);
return ret;
}
sport->per_clk = devm_clk_get(&pdev->dev, "per");
if (IS_ERR(sport->per_clk))
sport->per_clk = NULL;
ret = clk_prepare_enable(sport->ipg_clk);
if (ret) {
dev_err(&pdev->dev, "failed to enable uart ipg clk: %d\n", ret);
return ret;
}
ret = clk_prepare_enable(sport->per_clk);
if (ret) {
clk_disable_unprepare(sport->ipg_clk);
dev_err(&pdev->dev, "failed to enable uart clk: %d\n", ret);
return ret;
}
if (sport->per_clk)
sport->port.uartclk = clk_get_rate(sport->per_clk);
else
sport->port.uartclk = clk_get_rate(sport->ipg_clk);
lpuart_ports[sport->port.line] = sport;
platform_set_drvdata(pdev, &sport->port);
/* Disable interrupts before request irq */
if (lpuart_is_32(sport)) {
cr_32 = lpuart32_read(&sport->port, UARTCTRL);
cr_32 &= ~(UARTCTRL_TIE | UARTCTRL_TCIE | UARTCTRL_RIE | UARTCTRL_ILIE);
lpuart32_write(&sport->port, cr_32, UARTCTRL);
} else {
cr_8 = readb(sport->port.membase + UARTCR2);
cr_8 &= ~(UARTCR2_TIE | UARTCR2_TCIE | UARTCR2_RIE | UARTCR2_ILIE);
writeb(cr_8, sport->port.membase + UARTCR2);
}
if (lpuart_is_32(sport)) {
lpuart_reg.cons = LPUART32_CONSOLE;
ret = devm_request_irq(&pdev->dev, sport->port.irq, lpuart32_int, 0,
DRIVER_NAME, sport);
} else {
lpuart_reg.cons = LPUART_CONSOLE;
ret = devm_request_irq(&pdev->dev, sport->port.irq, lpuart_int, 0,
DRIVER_NAME, sport);
}
if (ret)
goto failed_irq_request;
pm_runtime_use_autosuspend(&pdev->dev);
pm_runtime_set_autosuspend_delay(&pdev->dev, UART_AUTOSUSPEND_TIMEOUT);
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
ret = uart_add_one_port(&lpuart_reg, &sport->port);
if (ret)
goto failed_attach_port;
ret = lpuart_hw_reset(sport);
if (ret)
goto failed_attach_port;
sport->dma_tx_chan = dma_request_slave_channel(sport->port.dev, "tx");
if (!sport->dma_tx_chan)
dev_info(sport->port.dev, "NO DMA tx channel, run at cpu mode\n");
sport->dma_rx_chan = dma_request_slave_channel(sport->port.dev, "rx");
if (!sport->dma_rx_chan)
dev_info(sport->port.dev, "NO DMA rx channel, run at cpu mode\n");
if (!lpuart_is_32(sport) &&
of_property_read_bool(np, "linux,rs485-enabled-at-boot-time")) {
sport->port.rs485.flags |= SER_RS485_ENABLED;
sport->port.rs485.flags |= SER_RS485_RTS_ON_SEND;
writeb(UARTMODEM_TXRTSE, sport->port.membase + UARTMODEM);
}
return 0;
failed_attach_port:
pm_runtime_disable(&pdev->dev);
pm_runtime_set_suspended(&pdev->dev);
pm_runtime_dont_use_autosuspend(&pdev->dev);
failed_irq_request:
clk_disable_unprepare(sport->per_clk);
clk_disable_unprepare(sport->ipg_clk);
return ret;
}
在这里我们重点看一下ret = uart_add_one_port(&lpuart_reg, &sport->port)函数这个就是我们要添加的串口,比如你有三个串口就是添加三个。一个 uart driver可以有多个uart_state。
* @uport: uart port structure to use for this port.
*
* This allows the driver to register its own uart_port structure
* with the core driver. The main purpose is to allow the low
* level uart drivers to expand uart_port, rather than having yet
* more levels of structures.
*/
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;
BUG_ON(in_interrupt());
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 a console, then the spinlock is already
* initialised.
*/
if (!(uart_console(uport) && (uport->cons->flags & CON_ENABLED))) {
spin_lock_init(&uport->lock);
lockdep_set_class(&uport->lock, &port_lock_key);
}
if (uport->cons && uport->dev)
of_console_check(uport->dev->of_node, uport->cons->name, 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 (likely(!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;
}
这样uart的初始化,就基本上结束了。