文章目录
串口驱动研究
1、串口硬件研究
1.1、串口三个模式
(1)Uart模式
普通的串口模式,在普通串口模式下,具有自动请求发送(RTS)、自动清除发送(CTS)等功能,这里只是用了三线制的串口,调制解调器不再考虑,仅考虑TXD、RXD即可。普通串口模式需要再进一步的研究其发送方式、中断方式等。
(2)IrDA模式
红外模式,遵循IrDA标准协议,不使用,不深入研究。
(3)CIR模式
消费电子的红外遥控,本次不使用,不深入研究。
串口模块连接关系如下图所示。
1.2、普通串口模式研究
可以看到串口控制器在内部有着关于FIFO的接口、中断接口、EDMA接口,后面要一步步分析这些接口。时钟、波特率等这里不做深入研究,配置为标准的时钟、115200波特率即可。
串口模块概况
串口模块分为3大部分:FIFO管理、模式选择、协议格式。
FIFO管理分为两个模式:功能模式(传输数据),寄存器模式(访问寄存器)。
模式选择就是一开始的3个模式。
协议格式有3个子类:时钟发生,数据协议,中断管理。
根据以上图片可以看到整个uart的概况。
中断请求
所有的中断都可以通过写interrupt enable register(IER)相应的寄存机进行使能、下使能,还可以通过interrupt identitification register(IIR)读取当前中断的状态。
串口模式有7个可能用到的中断,有6个优先级。
1)RX FIFO中出现OE,FE,PE,BI错误。
2)RX FIFO中有陈旧数据。
2)数据准备好,FIFO未使能。
3)UART_THR空。
4)调制解调器状态,看UART_MSR寄存器
5)接收到XOFF字符或者特殊字符。
6)CTS,RTS,DST等引脚状态发生改变引起的中断。
1.2.1、FIFO management
FIFO通过写UARTi.UART_RHR和UARTi.UART_THR寄存器进行使用。
其中UARTi.UART_TLR控制FIFO的触发等级,并且使能DMA和中断。FIFO触发等级可以由UART_SCR和UART_TLR、UARTi
1.2.1.1、FIFO中断模式
FIFO会不断的接受数据,当数据达到threshold时,就会产生中断,然后MPU响应中断,将数据从FIFO中取出,等待数据重新达到threshold时,再次产生中断。
发送模式时,FIFO不断积累数据,当数据积累到threshold时,产生中断,将数据发出,数据发出到低于threshold时,中断取消。
1.2.1.2、FIFO轮询模式
该模式下取消所有中断,通过轮询的方式来代替中断模式。
1.2.1.3、FIFO DMA模式
在接收模式时,当RXFIFO到达在trigger level register定义的阈值时则产生DMA请求,当DMA读取到一定字节数之后,请求被取消。
在发送模式下,当TX FIFO 为空DMA请求会自动产生,直到达到一定字节数之后才会取消请求。
上图是DMA发送数据时的流程,UART模块向DMA发送请求,DAM从内存取得数据至TX FIFO当中,然后将数据发送出去。
上图是DMA读取数据的流程,数据进入RX FIFO,然后DMA将数据搬运到内存当中。
1.2.2、 模式选择
模式分为三个:操作模式、设置模式A/B。
在不同模式下,寄存器的地址也有所不同,具体可看芯片手册。在寄存器中还可以选择之前提到的串口的三个模式UART、CIR、IrDA等。关于各个模式下用到的寄存器,在芯片手册中也有列出。
1.2.3、协议格式
协议格式也是分为多个模式,串口模式主要是设置波特率以及自动流控制等,三线制主要用到了波特率设置。还有自动波特率匹配功能,这里不做研究。
波特率设置可参考芯片手册给出的数据进行设置。
UARTi.UAR_LSR寄存器是错误检测,出现错误之后,该寄存器被读之后就会复位。这个地方暂时不做重点关注。
RX FIFO溢出问题需要关注,当FIFO溢出之后需要复位 RX FIFO,读取UARTi.UART_RESUME寄存器以清除内部标志。
time-out counter and break condition暂时不用关注,是关于串口电源管理的,不会引起串口无法使用的情况。
1.3、串口编程模型
首先是串口复位,对UARTi.UART_SYSC[1]SOFTRESET位置1,轮询UARTi.UART_SYSS[0] RESETDONE位为1结束复位。
设置FIFOs和DMA,设置协议、波特率、中断。还有硬件软件的流控制,三线制的UART可暂不深入研究。
2、串口驱动流程
硬件的研究已经完成,下一步重点研究串口在kernel中是如何工作的。经过浏览数据手册,发现5728与335x的串口模块基本相同,这里直接研究5728的内核代码。
2.1、第一阶段流程
在系统内核启动过程中,第一次调用关于串口的地方是在8250_core.c当中的以下函数,
static int __init univ8250_console_init(void)
{
//第一次调用关于串口的地方
if (nr_uarts == 0)
return -ENODEV;
printk(" [kernel dbug ] univ8250_console_init\n");
serial8250_isa_init_ports();//初始化ports
register_console(&univ8250_console);//注册console
return 0;
}
再进一步研究该函数,可以看到主要调用的serial8250_isa_init_ports函数,以下是该函数的分析
static void __init serial8250_isa_init_ports(void)
{
struct uart_8250_port *up;
static int first = 1;
int i, irqflag = 0;
printk(" [kernel dbug]:serial8250_isa_init_ports\n");
if (!first)
return;
first = 0;
printk(" [kernel dbug]:is first return\n");//只执行一次,第二次调用不执行
if (nr_uarts > UART_NR)
nr_uarts = UART_NR;
for (i = 0; i < nr_uarts; i++) {
struct uart_8250_port *up = &serial8250_ports[i];//传入uart_8250_port结构体
/*
serial8250_ports[]仅定义,
未进行各种赋值,各类赋值在初始化中进行
*/
struct uart_port *port = &up->port;//传入port结构体
printk(" [kernel dbug]:serial8250_isa_init_ports, i=%d\n",i);
port->line = i;//赋值
serial8250_init_port(up);//进入init函数,对up->port进一步赋值,对port->ops赋予大量操作函数
if (!base_ops)
base_ops = port->ops;//保存init的ops
port->ops = &univ8250_port_ops;//重新赋值ops
init_timer(&up->timer);//初始化定时器
up->timer.function = serial8250_timeout;//定时器中断处理函数
up->ops = &univ8250_driver_ops;//up的ops
/*
* ALPHA_KLUDGE_MCR needs to be killed.
*/
up->mcr_mask = ~ALPHA_KLUDGE_MCR;//
up->mcr_force = ALPHA_KLUDGE_MCR;
}
/* chain base port ops to support Remote Supervisor Adapter */
univ8250_port_ops = *base_ops;//得到第一个ops
univ8250_rsa_support(&univ8250_port_ops);//重定向部分函数
if (share_irqs)//是否共享中断
irqflag = IRQF_SHARED;
for (i = 0, up = serial8250_ports;
i < ARRAY_SIZE(old_serial_port) && i < nr_uarts;
i++, up++) {
struct uart_port *port = &up->port;
port->iobase = old_serial_port[i].port;//old_serial_port[]在头文件中进行了定义
port->irq = irq_canonicalize(old_serial_port[i].irq);
port->irqflags = old_serial_port[i].irqflags;
port->uartclk = old_serial_port[i].baud_base * 16;
port->flags = old_serial_port[i].flags;
port->hub6 = old_serial_port[i].hub6;
port->membase = old_serial_port[i].iomem_base;
port->iotype = old_serial_port[i].io_type;
port->regshift = old_serial_port[i].iomem_reg_shift;
serial8250_set_defaults(up);//做一些默认配置,包括FIFO、DMA
port->irqflags |= irqflag;
if (serial8250_isa_config != NULL)
serial8250_isa_config(i, &up->port, &up->capabilities);
}
}
最后调用以下代码,实现console从一开始的cmdline传入的TTYO2转换为TTYS2,关于一开始的TTYO2是关于early——printk的,这里不再深入研究,以此为TTYS2为起点继续研究。
static int __init omap8250_console_fixup(void)
{
char *omap_str;
char *options;
u8 idx;
printk(" [kernel dbug]:omap8250_console_fixup\n");
if (strstr(boot_command_line, "console=ttyS"))
/* user set a ttyS based name for the console */
return 0;
omap_str = strstr(boot_command_line, "console=ttyO");
if (!omap_str)
/* user did not set ttyO based console, so we don't care */
return 0;
omap_str += 12;
if ('0' <= *omap_str && *omap_str <= '9')
idx = *omap_str - '0';
else
return 0;
omap_str++;
if (omap_str[0] == ',') {
omap_str++;
options = omap_str;
} else {
options = NULL;
}
add_preferred_console("ttyS", idx, options);
pr_err("WARNING: Your 'console=ttyO%d' has been replaced by 'ttyS%d'\n",
idx, idx);
pr_err("This ensures that you still see kernel messages. Please\n");
pr_err("update your kernel commandline.\n");
return 0;
}
以下是前一阶段的调试信息。
[ 0.000794] Console: colour dummy device 80x30
[ 0.000803] [kernel dbug]:serial8250_isa_init_ports
[ 0.000810] [kernel dbug]:is first return
[ 0.000816] [kernel dbug]:serial8250_isa_init_ports, i=0
[ 0.000822] [kernel dbug]:serial8250_isa_init_ports, i=1
[ 0.000828] [kernel dbug]:serial8250_isa_init_ports, i=2
[ 0.000834] [kernel dbug]:serial8250_isa_init_ports, i=3
[ 0.000839] [kernel dbug]:serial8250_isa_init_ports, i=4
[ 0.000844] [kernel dbug]:serial8250_isa_init_ports, i=5
[ 0.000850] [kernel dbug]:serial8250_isa_init_ports, i=6
[ 0.000855] [kernel dbug]:serial8250_isa_init_ports, i=7
[ 0.000860] [kernel dbug]:serial8250_isa_init_ports, i=8
[ 0.000865] [kernel dbug]:serial8250_isa_init_ports, i=9
[ 0.000876] WARNING: Your 'console=ttyO2' has been replaced by 'ttyS2'
2.2、第二阶段流程
完成console的切换之后,即可继续查看内核信息。后面驱动中还会执行一些其他的中断等关于tty的初始化。在执行probe之前,首先执行init函数。
init函数如下,初始化函数执行了关于uart的注册、platform的注册、serial8250的注册,在init函数当中执行了较多的注册函数,这里不太清楚具体每个注册的作用,先捋清流程,后面再继续分析。
static int __init serial8250_init(void)
{
int ret;
printk(" [kernel dbug]:serial8250_init\n");
if (nr_uarts == 0)
return -ENODEV;
serial8250_isa_init_ports();//已经在之前初始化过,进入直接返回
printk(KERN_INFO "Serial: 8250/16550 driver, "
"%d ports, IRQ sharing %sabled\n", nr_uarts,
share_irqs ? "en" : "dis");//输出IRQ sharing状态
#ifdef CONFIG_SPARC
ret = sunserial_register_minors(&serial8250_reg, UART_NR);
#else
serial8250_reg.nr = UART_NR;
ret = uart_register_driver(&serial8250_reg);//注册uart
#endif
if (ret)
goto out;
ret = serial8250_pnp_init();//初始化PNP
if (ret)
goto unreg_uart_drv;
serial8250_isa_devs = platform_device_alloc("serial8250",PLAT8250_DEV_LEGACY);//创建一个platform设备
if (!serial8250_isa_devs) {
ret = -ENOMEM;
goto unreg_pnp;
}
ret = platform_device_add(serial8250_isa_devs);//添加platform设备到设备层
if (ret)
goto put_dev;
serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);//注册port
ret = platform_driver_register(&serial8250_isa_driver);//注册platform驱动
if (ret == 0)
goto out;
platform_device_del(serial8250_isa_devs);//删除platform设备,只有出错时才会执行
···
}
完成init初始化之后,下一步执行的是probe函数,根据之前的调试信息,首先执行的probe函数是:
static int serial8250_probe(struct platform_device *dev)
{
struct plat_serial8250_port *p = dev_get_platdata(&dev->dev);
struct uart_8250_port uart;
int ret, i, irqflag = 0;
printk(" [kernel dbug] serial8250_probe\n");
memset(&uart, 0, sizeof(uart));
printk(" [kernel dbug] end memset\n");
if (share_irqs)
irqflag = IRQF_SHARED;
for (i = 0; p && p->flags != 0; p++, i++) {
printk(" [kernel dbug] in for \n");
uart.port.iobase = p->iobase;
···
uart.port.irqflags |= irqflag;
printk(" [kernel dbug] start serial8250_register_8250_port \n");
ret = serial8250_register_8250_port(&uart);
printk(" [kernel dbug] end serial8250_register_8250_port \n");
if (ret < 0) {
dev_err(&dev->dev, "unable to register port at index %d "
"(IO%lx MEM%llx IRQ%d): %d\n", i,
p->iobase, (unsigned long long)p->mapbase,
p->irq, ret);
}
}
return 0;
}
通过加printk发现serial8250_probe函数并没有进入到for循环当中,而是结束了,然后omap8250_probe函数,omap8250_probe函数比较长
static int omap8250_probe(struct platform_device *pdev)
{
struct resource *regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
struct resource *irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
struct omap8250_priv *priv;
struct uart_8250_port up;
int ret;
void __iomem *membase;
printk(" [kernel dbug]:omap8250_probe\n");
if (!regs || !irq) {
dev_err(&pdev->dev, "missing registers or irq\n");
return -EINVAL;
}
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);//申请一块内存
if (!priv)
return -ENOMEM;
membase = devm_ioremap_nocache(&pdev->dev, regs->start,
resource_size(regs));//地址映射
if (!membase)
return -ENODEV;
memset(&up, 0, sizeof(up));//将up数据清零
up.port.dev = &pdev->dev;//up结构体是一个
up.port.mapbase = regs->start;//用于地址映射
up.port.membase = membase;//地址映射
up.port.irq = irq->start;//中断号
/*
* It claims to be 16C750 compatible however it is a little different.
* It has EFR and has no FCR7_64byte bit. The AFE (which it claims to
* have) is enabled via EFR instead of MCR. The type is set here 8250
* just to get things going. UNKNOWN does not work for a few reasons and
* we don't need our own type since we don't use 8250's set_termios()
* or pm callback.
*/
up.port.type = PORT_8250;//端口类型
up.port.iotype = UPIO_MEM;//io地址类型
up.port.flags = UPF_FIXED_PORT | UPF_FIXED_TYPE | UPF_SOFT_FLOW |
UPF_HARD_FLOW;//标志位
up.port.private_data = priv;//私有数据指针,在之前申请过内存
up.port.regshift = 2;//寄存器地址偏移
up.port.fifosize = 64;//fifo大小
up.tx_loadsz = 64;//TX fifo load size
up.capabilities = UART_CAP_FIFO;//使能fifo
#ifdef CONFIG_PM
/*
* Runtime PM is mostly transparent. However to do it right we need to a
* TX empty interrupt before we can put the device to auto idle. So if
* PM is not enabled we don't add that flag and can spare that one extra
* interrupt in the TX path.
*/
up.capabilities |= UART_CAP_RPM;//使能PM
#endif
up.port.set_termios = omap_8250_set_termios;//配置时钟波特率
up.port.set_mctrl = omap8250_set_mctrl;//关于RTS,可不考虑
up.port.pm = omap_8250_pm;//
up.port.startup = omap_8250_startup;
up.port.shutdown = omap_8250_shutdown;
up.port.throttle = omap_8250_throttle;
up.port.unthrottle = omap_8250_unthrottle;
if (pdev->dev.of_node) {
const struct of_device_id *id;
ret = of_alias_get_id(pdev->dev.of_node, "serial");
of_property_read_u32(pdev->dev.of_node, "clock-frequency",
&up.port.uartclk);
priv->wakeirq = irq_of_parse_and_map(pdev->dev.of_node, 1);
id = of_match_device(of_match_ptr(omap8250_dt_ids), &pdev->dev);
if (id && id->data)
priv->habit |= *(u8 *)id->data;
} else {
ret = pdev->id;
}
if (ret < 0) {
dev_err(&pdev->dev, "failed to get alias/pdev id\n");
return ret;
}
up.port.line = ret;
if (!up.port.uartclk) {
up.port.uartclk = DEFAULT_CLK_SPEED;
dev_warn(&pdev->dev,
"No clock speed specified: using default: %d\n",
DEFAULT_CLK_SPEED);
}
priv->latency = PM_QOS_CPU_DMA_LAT_DEFAULT_VALUE;
priv->calc_latency = PM_QOS_CPU_DMA_LAT_DEFAULT_VALUE;
pm_qos_add_request(&priv->pm_qos_request, PM_QOS_CPU_DMA_LATENCY,
priv->latency);
INIT_WORK(&priv->qos_work, omap8250_uart_qos_work);
spin_lock_init(&priv->rx_dma_lock);
device_init_wakeup(&pdev->dev, true);
pm_runtime_use_autosuspend(&pdev->dev);
pm_runtime_set_autosuspend_delay(&pdev->dev, -1);
pm_runtime_irq_safe(&pdev->dev);
pm_runtime_enable(&pdev->dev);
pm_runtime_get_sync(&pdev->dev);
omap_serial_fill_features_erratas(&up, priv);
up.port.handle_irq = omap8250_no_handle_irq;
#ifdef CONFIG_SERIAL_8250_DMA
if (pdev->dev.of_node) {
/*
* Oh DMA support. If there are no DMA properties in the DT then
* we will fall back to a generic DMA channel which does not
* really work here. To ensure that we do not get a generic DMA
* channel assigned, we have the the_no_dma_filter_fn() here.
* To avoid "failed to request DMA" messages we check for DMA
* properties in DT.
*/
ret = of_property_count_strings(pdev->dev.of_node, "dma-names");
if (ret == 2) {
up.dma = &priv->omap8250_dma;
priv->omap8250_dma.fn = the_no_dma_filter_fn;
priv->omap8250_dma.tx_dma = omap_8250_tx_dma;
priv->omap8250_dma.rx_dma = omap_8250_rx_dma;
priv->omap8250_dma.rx_size = RX_TRIGGER;
priv->omap8250_dma.rxconf.src_maxburst = RX_TRIGGER;
priv->omap8250_dma.txconf.dst_maxburst = TX_TRIGGER;
if (of_machine_is_compatible("ti,am33xx"))
priv->habit |= OMAP_DMA_TX_KICK;
/*
* pause is currently not supported atleast on omap-sdma
* whereas edma supports pause feature.
*/
if (omap_8250_get_dma_type(&up.port) != UART_EDMA)
priv->rx_dma_broken = true;
else
priv->habit |= OMAP_DMA_TX_KICK;
}
}
#endif
ret = serial8250_register_8250_port(&up);
if (ret < 0) {
dev_err(&pdev->dev, "unable to register 8250 port\n");
goto err;
}
priv->line = ret;
platform_set_drvdata(pdev, priv);
pm_runtime_mark_last_busy(&pdev->dev);
pm_runtime_put_autosuspend(&pdev->dev);
return 0;
err:
pm_runtime_put(&pdev->dev);
pm_runtime_disable(&pdev->dev);
return ret;
}
[ 0.600974] [kernel dbug]:serial8250_init
[ 0.600984] [kernel dbug]:serial8250_isa_init_ports
[ 0.600993] Serial: 8250/16550 driver, 10 ports, IRQ sharing disabled
[ 0.601146] [kernel dbug]:serial8250_register_ports, i=0
[ 0.601154] [kernel dbug] uart_add_one_port
[ 0.601161] [kernel dbug] uart_configure_port irq=0
···
[ 0.603289] [kernel dbug]:serial8250_register_ports, i=9
[ 0.603296] [kernel dbug] uart_add_one_port
[ 0.603302] [kernel dbug] uart_configure_port irq=0
[ 0.603680] [kernel dbug] serial8250_probe
[ 0.603687] [kernel dbug] end memset
[ 0.603787] [kernel dbug] end serial8250_probe
[ 0.603997] [kernel dbug]:omap8250_probe
[ 0.604237] [kernel dbug] serial8250_register_8250_port
[ 0.604736] [kernel dbug] uart_add_one_port
[ 0.604745] [kernel dbug] uart_configure_port irq=301
[ 0.604757] [kernel dbug] uart_report_port
[ 0.604767] 4806a000.serial: ttyS0 at MMIO 0x4806a000 (irq = 301, base_baud = 3000000) is a 8250
[ 0.605041] [kernel dbug]:omap8250_probe
[ 0.605206] [kernel dbug] serial8250_register_8250_port
[ 0.605647] console [ttyS2] disabled
[ 0.605686] [kernel dbug] uart_add_one_port
[ 0.605694] [kernel dbug] uart_configure_port irq=302
[ 0.605703] [kernel dbug] uart_report_port
[ 0.605712] 48020000.serial: ttyS2 at MMIO 0x48020000 (irq = 302, base_baud = 3000000) is a 8250
[ 1.919163] console [ttyS2] enabled
[ 1.922950] [kernel dbug]:omap8250_probe
[ 1.927166] [kernel dbug] serial8250_register_8250_port
[ 1.933045] [kernel dbug] uart_add_one_port
[ 1.937421] [kernel dbug] uart_configure_port irq=303
[ 1.942709] [kernel dbug] uart_report_port
[ 1.946999] 48422000.serial: ttyS7 at MMIO 0x48422000 (irq = 303, base_baud = 3000000) is a 8250
[ 1.956148] [kernel dbug]:omap8250_probe
[ 1.960362] [kernel dbug] serial8250_register_8250_port
[ 1.966258] [kernel dbug] uart_add_one_port
[ 1.970633] [kernel dbug] uart_configure_port irq=304
[ 1.975925] [kernel dbug] uart_report_port
[ 1.980216] 4ae2b000.serial: ttyS9 at MMIO 0x4ae2b000 (irq = 304, base_baud = 3000000) is a 8250