linux uart驱动基础知识下面链接这篇文章写得很完备,我没必要再介绍了,就写目前项目的代码,方便以后重温。
Linux的tty架构及UART驱动详解
本项目驱动文件包括:
/kernel/drivers/sstar/serial/ms_uart.c # 主要实现文件
/kernel/drivers/sstar/serial/infinity2m/ms_uart.h # 头文件,定义串口号
/kernel/drivers/sstar/serial/infinity2m/uart_pads.c # 硬件相关
uart dts(设备树)
本项目 uart dts文件路径:
/kernel/arch/arm/boot/dts/infinity2m-doublenet.dtsi
和uart 相关的内容如下,注意注释的内容:
aliases {
console = &uart0;
serial0 = &uart0; // 别名,驱动会用到serial, 用户层文件系统对应:/dev/ttySx
serial1 = &uart1; // 对应 of_alias_get_id(pdev->dev.of_node, "serial"), /dev/ttyS1
serial2 = &fuart;
serial3 = &uart2;
};
...
soc { // 注意uart是在soc下的,是平台设备,所以要注册平台驱动
...
uart0: uart0@1F221000 { // 节点头部格式:<name>[@<unit-address>]
compatible = "sstar,uart"; // 与驱动程序的 ".of_match_table" 对应
reg = <0x1F221000 0x100>; // platform_get_resource(pdev, IORESOURCE_MEM, 0)
interrupts = <GIC_SPI INT_IRQ_UART_0 IRQ_TYPE_LEVEL_HIGH>; // platform_get_resource(pdev, IORESOURCE_IRQ, 0)
clocks = <&CLK_uart0>; // of_clk_get(pdev->dev.of_node, 0)
status = "ok";
};
uart1: uart1@1F221200 {
compatible = "sstar,uart";
reg = <0x1F221200 0x100>;
interrupts = <GIC_SPI INT_IRQ_UART_1 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&CLK_uart1>;
status = "ok";
};
fuart: uart2@1F220400 {
compatible = "sstar,uart";
reg = <0x1F220400 0x100>, <0x1F220600 0x100>;
interrupts = <GIC_SPI INT_IRQ_FUART IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI INT_IRQ_URDMA IRQ_TYPE_LEVEL_HIGH>;
clocks = <&CLK_fuart>;
dma = <0>;
sctp_enable = <0>;//rts cts enable is 1
status = "ok";
};
uart2: uart2@1F221400 {
compatible = "sstar,uart";
reg = <0x1F221400 0x100>;
interrupts = <GIC_SPI INT_IRQ_UART_2 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&CLK_uart2>;
status = "disabled"; // 没有使能,所以没有这个设备
};
...
}
数据结构
参考文章里面提到几个重要数据结构:struct uart_driver, struct console, struct uart_state, struct uart_port, struct uart_ops.在项目代码里面uart_port被封装进struct ms_uart_port结构体了,如下:
struct ms_uart_port {
struct uart_port port; // 这个是 uart_add_one_port 的参数
struct ms_urdma *urdma;
struct device *dev;
struct clk *clk;
int use_dma;
#if UART_TX_TASK
struct tasklet_struct xmit_tasklet;
#endif
int rx_guard;
u8 backupIER;
u8 backupLCR;
u8 backupMCR;
u16 backupDivisor;
u8 padmux;
u8 pad_mode;
u8 rs485_gpio_flag; // 这个标志位对RS485应用比较重要
struct timer_list timer; /* "no irq" timer */
u16 bugs; /* port bugs */
CamOsThread urdma_task;
};
static struct ms_uart_port console_port;
static struct uart_driver ms_uart_driver; // 全局uart驱动
/* Serial Console Structure Definition */
static struct console ms_uart_console = // 全局console
{
.name = MS_CONSOLE_DEV,
.write = ms_uart_console_write,
.setup = ms_uart_console_setup,
.flags = CON_PRINTBUFFER,
.device = uart_console_device,
.data = &ms_uart_driver, // uart 驱动
.index = -1,
#if CONSOLE_DMA
.match = ms_uart_console_match,
#endif
};
static struct uart_driver ms_uart_driver = {
.owner = THIS_MODULE,
.driver_name = "ms_uart",
.dev_name = "ttyS",
.nr = 8,
.cons = &ms_uart_console, // console
};
以上面代码可以看到,uart驱动包括了console,console里面的data也指向了uart 驱动,uart_port 与driver的关系要看uart 驱动的注册过程。
驱动注册
static const struct of_device_id ms_uart_of_match_table[] = {
{ .compatible = "sstar,uart" },
{}
};
MODULE_DEVICE_TABLE(of, ms_uart_of_match_table);
static struct platform_driver ms_uart_platform_driver = {
.remove = ms_uart_remove,
.probe = ms_uart_probe,
#ifdef CONFIG_PM
.suspend = ms_uart_suspend,
.resume = ms_uart_resume,
#endif
.driver = {
.name = "ms_uart",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(ms_uart_of_match_table),
},
};
static s32 __init ms_uart_module_init(void)
{
int ret;
ret = uart_register_driver(&ms_uart_driver);
if (ret != 0)
return ret;
ret = platform_driver_register(&ms_uart_platform_driver);
if (ret != 0)
{
// UART_ERR("[ms_uart]platform_driver_register failed!!\n");
uart_unregister_driver(&ms_uart_driver);
}
return ret;
}
static void __exit ms_uart_module_exit(void)
{
platform_driver_unregister(&ms_uart_platform_driver);
uart_unregister_driver(&ms_uart_driver);
}
module_init(ms_uart_module_init);
module_exit(ms_uart_module_exit);
从上面代码来看,module_init 时注册了 ms_uart_driver 和 ms_uart_platform_driver 驱动,ms_uart_platform_driver 指定了设备匹配的table(如下),当内核启动到解析设备树文件时,会调用驱动的probe函数(ms_uart_probe),在probe函数里面,设备和驱动会绑定和初始化。
static const struct of_device_id ms_uart_of_match_table[] = {
{ .compatible = "sstar,uart" },
{}
};
probe 过程
下面是经过简化处理的探测函数,读一下注释基本了解执行过程。
/* UART Operations */
static struct uart_ops ms_uart_ops =
{
.tx_empty = ms_uart_tx_empty, // 串口的Tx FIFO缓存是否为空
.set_mctrl = ms_uart_set_mctrl, // 设置串口modem控制
.get_mctrl = ms_uart_get_mctrl, // 获取串口modem控制
.stop_tx = ms_uart_stop_tx, // 禁止串口发送数据
.start_tx = ms_uart_start_tx, // 使能串口发送数据
.stop_rx = ms_uart_stop_rx, // 禁止串口接收数据
.enable_ms = ms_uart_enable_ms, // 使能modem的状态信号
.break_ctl = ms_uart_break_ctl, // 设置break信号
.startup = ms_uart_startup, // 启动串口,应用程序打开串口设备文件时,该函数会被调用
.shutdown = ms_uart_shutdown, // 关闭串口,应用程序关闭串口设备文件时,该函数会被调用
.set_termios = ms_uart_set_termios, // 设置串口参数
.type = ms_uart_type, // 返回一描述串口类型的字符串
.release_port = ms_uart_release_port, // 释放串口已申请的IO端口/IO内存资源,必要时还需iounmap
.request_port = ms_uart_request_port, // 申请必要的IO端口/IO内存资源,必要时还可以重新映射串口端口
.config_port = ms_uart_config_port, // 执行串口所需的自动配置
.verify_port = ms_uart_verify_port, // 核实新串口的信息
//.ioctl // IO控制
};
static s32 ms_uart_probe(struct platform_device *pdev)
{
struct ms_uart_port *mp;
struct resource *res;
// new 了一个 ms_uart_port
mp = devm_kzalloc(&pdev->dev, sizeof(*mp), GFP_KERNEL);
// 初始化 uart_port 的spin_lock
spin_lock_init(&mp->port.lock);
// 读取 dts 里面的 "serialx = &uartx", mp->port.line = x
mp->port.line = of_alias_get_id(pdev->dev.of_node, "serial");
pdev->id=mp->port.line;
// 读取dts的 "clocks = <&CLK_uart0>;"
mp->clk = of_clk_get(pdev->dev.of_node, 0);
//enable clk in probe, because if UART no clk, it can not be accessed.
clk_prepare_enable(mp->clk);
mp->port.uartclk = clk_get_rate(mp->clk);
// 读取dts的 "reg = <0x1F221000 0x100>;"
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
mp->port.membase = (void *)res->start;
// 读取dts的 "interrupts = <GIC_SPI INT_IRQ_UART_0 IRQ_TYPE_LEVEL_HIGH>;"
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
mp->port.irq = res->start;
// 读取dts的"dma = <0>;"
of_property_read_u32(pdev->dev.of_node, "dma", &mp->use_dma);
if (mp->use_dma)
{ // dts里面值为0,所有dma相关的代码都忽略了
}
// 继续初始化 struct uart_port 结构体
mp->port.type = PORT_8250;
mp->port.dev = &pdev->dev;
mp->port.ops=&ms_uart_ops; // 这里指定了上面定义的所有操作函数
mp->port.regshift = 0;
mp->port.fifosize = 16; // 16 字节,可能跟硬件相关,没datasheet不清楚
mp->port.timeout =HZ;
mp->port.iotype=UPIO_MEM;
mp->port.rs485_config = ms_uart_rs485_config; // rs485 配置函数,用户层可能会调用到
// 绑定硬件,因为dts没指定"pad",所以of_property_read_u32返回非0值
if (of_property_read_u32(pdev->dev.of_node, "pad", &tx_pad)) //read property failed
{
//set default pad
if(mp->port.line==0)
mp->padmux=MUX_PM_UART;
else if(mp->port.line==1)
mp->padmux=MUX_UART1;
else if(mp->port.line==2)
mp->padmux=MUX_FUART;
else if(mp->port.line==3)
mp->padmux=MUX_UART2;
else
ret = -EINVAL;
}
else //read property successfully
{
if (ms_uart_get_padmux(tx_pad, &(mp->padmux), &(mp->pad_mode)) != 0)
ret = -EINVAL;
}
// 把new出来的 struct ms_uart_port 保存到串口设备去
platform_set_drvdata(pdev, mp);
// 关键在这里,把struct uart_port 加到 uart_driver去了
ret = uart_add_one_port(&ms_uart_driver, &mp->port);
return 0;
}
数据发送接收流程
tty设备发送、接收数据流程图如下(别人的图):
发送
从上面 ms_uart_ops 可知,用户层发送数据会调用 “.start_tx = ms_uart_start_tx”。基本流程是先把RS485使能(如果是RS485口),然后使能uart发送缓存为空中断,在中断处理函数 ms_uart_interrupt 里面调用 ms_putchar 函数,然后从“ **struct circ_buf xmit = struct uart_port p->state->xmit ”环回缓存里面取数据并塞到uart 数据发送缓存寄存器,发送完就调用 ms_uart_stop_tx 停止发送。发送数据流程如下图(别人的图):
接收
在中断处理函数ms_uart_interrupt 接收分支里面调用 ms_getchar 函数,从寄存器读取数据,调用 uart_insert_char 函数往上层送。接收数据流程图如下(别人的图):
其它的开发内容基本是 ms_uart_ops 回调函数的实现,不一一解读了。
end