串口驱动研究

串口驱动研究

1、串口硬件研究

1.1、串口三个模式

(1)Uart模式

普通的串口模式,在普通串口模式下,具有自动请求发送(RTS)、自动清除发送(CTS)等功能,这里只是用了三线制的串口,调制解调器不再考虑,仅考虑TXD、RXD即可。普通串口模式需要再进一步的研究其发送方式、中断方式等。

(2)IrDA模式

红外模式,遵循IrDA标准协议,不使用,不深入研究。

(3)CIR模式

消费电子的红外遥控,本次不使用,不深入研究。

串口模块连接关系如下图所示。

1.2、普通串口模式研究

image-20200608110155996

可以看到串口控制器在内部有着关于FIFO的接口、中断接口、EDMA接口,后面要一步步分析这些接口。时钟、波特率等这里不做深入研究,配置为标准的时钟、115200波特率即可。

串口模块概况

串口模块分为3大部分:FIFO管理、模式选择、协议格式。

FIFO管理分为两个模式:功能模式(传输数据),寄存器模式(访问寄存器)。

模式选择就是一开始的3个模式。

协议格式有3个子类:时钟发生,数据协议,中断管理。

image-20200608141108422

根据以上图片可以看到整个uart的概况。

中断请求

所有的中断都可以通过写interrupt enable register(IER)相应的寄存机进行使能、下使能,还可以通过interrupt identitification register(IIR)读取当前中断的状态。

串口模式有7个可能用到的中断,有6个优先级。

image-20200608151625460

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寄存器进行使用。

image-20200608163515202

其中UARTi.UART_TLR控制FIFO的触发等级,并且使能DMA和中断。FIFO触发等级可以由UART_SCR和UART_TLR、UARTi

image-20200608164747444

1.2.1.1、FIFO中断模式

FIFO会不断的接受数据,当数据达到threshold时,就会产生中断,然后MPU响应中断,将数据从FIFO中取出,等待数据重新达到threshold时,再次产生中断。

image-20200608165457635

发送模式时,FIFO不断积累数据,当数据积累到threshold时,产生中断,将数据发出,数据发出到低于threshold时,中断取消。

image-20200608170211091

1.2.1.2、FIFO轮询模式

该模式下取消所有中断,通过轮询的方式来代替中断模式。

1.2.1.3、FIFO DMA模式

image-20200609095121768

在接收模式时,当RXFIFO到达在trigger level register定义的阈值时则产生DMA请求,当DMA读取到一定字节数之后,请求被取消。

image-20200609100018923

在发送模式下,当TX FIFO 为空DMA请求会自动产生,直到达到一定字节数之后才会取消请求。

image-20200609101726169

上图是DMA发送数据时的流程,UART模块向DMA发送请求,DAM从内存取得数据至TX FIFO当中,然后将数据发送出去。

image-20200609101934472

上图是DMA读取数据的流程,数据进入RX FIFO,然后DMA将数据搬运到内存当中。

1.2.2、 模式选择

模式分为三个:操作模式、设置模式A/B。

在不同模式下,寄存器的地址也有所不同,具体可看芯片手册。在寄存器中还可以选择之前提到的串口的三个模式UART、CIR、IrDA等。关于各个模式下用到的寄存器,在芯片手册中也有列出。

1.2.3、协议格式

协议格式也是分为多个模式,串口模式主要是设置波特率以及自动流控制等,三线制主要用到了波特率设置。还有自动波特率匹配功能,这里不做研究。

波特率设置可参考芯片手册给出的数据进行设置。

image-20200609135227178

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

塔通天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值