欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

基于Mips架构linux下设备树串口驱动(二)

程序员文章站 2022-03-07 13:20:54
...

本设备树串口驱动基于linux3.0.4内核版本

serial8250_console_init()

从serial8250_console_init()函数实体开始,追踪分析标准设备驱动流程。函数原型如下,函数实体路径见./drivers/tty/serial/8250.c。可以看出该函数第一次初始化port,第二次注册
console驱动。

static int __init serial8250_console_init(void)
{
 if (nr_uarts > UART_NR)
  nr_uarts = UART_NR;
  
 serial8250_isa_init_ports();
 
 register_console(&serial8250_console);
 return 0;
}
console_initcall(serial8250_console_init);

serial8250_isa_init_ports()

继续追踪函数实体serial8250_isa_init_ports()实现,函数原型如下,函数路径见
./drivers/tty/serial/8250.c。博主主要关主两个for循环内的操作。第一个for循环功能是
初始化定时器功能函数up->timer.function = serial8250_timeout;初始化serial8250 port的操作函数 up->port.ops = &serial8250_pops;这个接口serial8250_pops内有关于port操作函数挂载。第二个for循环功能是初始化默认port硬件参数old_serial_port[i].port;

static void __init serial8250_isa_init_ports(void)
{
	 .................................
	 for (i = 0; i < nr_uarts; i++) {
	  struct uart_8250_port *up = &serial8250_ports[i];
	  up->port.line = i;
	  spin_lock_init(&up->port.lock);
	  init_timer(&up->timer);
	  up->timer.function = serial8250_timeout;
	  up->mcr_mask = ~ALPHA_KLUDGE_MCR;
	  up->mcr_force = ALPHA_KLUDGE_MCR;
	  up->port.ops = &serial8250_pops;
	 }
	 .................................
	 for (i = 0, up = serial8250_ports;
	      i < ARRAY_SIZE(old_serial_port) && i < nr_uarts;
	      i++, up++) {
	  up->port.iobase   = old_serial_port[i].port;
	  up->port.irq      = irq_canonicalize(old_serial_port[i].irq);
	  up->port.irqflags = old_serial_port[i].irqflags;
	  up->port.uartclk  = old_serial_port[i].baud_base * 16;
	  up->port.flags    = old_serial_port[i].flags;
	  up->port.hub6     = old_serial_port[i].hub6;
	  up->port.membase  = old_serial_port[i].iomem_base;
	  up->port.iotype   = old_serial_port[i].io_type;
	  up->port.regshift = old_serial_port[i].iomem_reg_shift;
	  set_io_from_upio(&up->port);
	  up->port.irqflags |= irqflag;
	  if (serial8250_isa_config != NULL)
	   serial8250_isa_config(i, &up->port, &up->capabilities);
	 }
	 .................................
	}

接下来,先分析第一个for循环 up->port.ops = &serial8250_pops;有关于定时器初始化本文不作赘述。函数原型如下,函数实体路径见./drivers/tty/serial/8250.c,重点分析结构体struct uart_ops成员变量.config_port = serial8250_config_port,这是个关于port与硬件配置相关的函数。

static struct uart_ops serial8250_pops = {
 .tx_empty = serial8250_tx_empty,
 .set_mctrl = serial8250_set_mctrl,
 .get_mctrl = serial8250_get_mctrl,
 .stop_tx = serial8250_stop_tx,
 .start_tx = serial8250_start_tx,
 .stop_rx = serial8250_stop_rx,
 .enable_ms = serial8250_enable_ms,
 .break_ctl = serial8250_break_ctl,
 .startup = serial8250_startup,
 .shutdown = serial8250_shutdown,
 .set_termios = serial8250_set_termios,
 .set_ldisc = serial8250_set_ldisc,
 .pm  = serial8250_pm,
 .type  = serial8250_type,
 .release_port = serial8250_release_port,
 .request_port = serial8250_request_port,
 .config_port = serial8250_config_port,
 .verify_port = serial8250_verify_port,
#ifdef CONFIG_CONSOLE_POLL
 .poll_get_char = serial8250_get_poll_char,
 .poll_put_char = serial8250_put_poll_char,
#endif
};

再分析第二个for循环函数,结构体struct old_serial_port默认初始值是串口号,串口波特率,串口时钟频率,串口中断号,以及flags。结合下方第二段代码段图例展示struct old_serial_port定义的内容初始值,思路就逐渐清晰了。而SERIAL_PORT_DFNS宏定义由开发者自行针对硬件平台进行设计,下方只是博主列出一个示例应用。

#define SERIAL_PORT_DFNS      \
       /* UART  CLK     PORT   IRQ            FLAGS */   \
 { 0, BASE_BAUD, 0x3F8, PLD_IRQ_UART0, STD_COM_FLAGS }, /* ttyS0 */ \
 { 0, BASE_BAUD, 0x2F8, PLD_IRQ_UART1, STD_COM_FLAGS }, /* ttyS1 */
 
static const struct old_serial_port old_serial_port[] = {
 SERIAL_PORT_DFNS /* defined in asm/serial.h */
};

struct old_serial_port定义如下,路径见./drivers/tty/serial/8250.h

struct old_serial_port {
 unsigned int uart;
 unsigned int baud_base;
 unsigned int port;
 unsigned int irq;
 unsigned int flags;
 unsigned char hub6;
 unsigned char io_type;
 unsigned char *iomem_base;
 unsigned short iomem_reg_shift;
 unsigned long irqflags;
};

serial8250_config_port()

现在我们回到.config_port = serial8250_config_port,继续追踪分析, 函数实体原型如下,函数路径见./drivers/tty/serial/8250.c,此函数本文只分析介绍三个子函数实现,分别为serial8250_request_std_resource(),serial8250_release_std_resource(),autoconfig()
其它函数本文不作赘述,第一个函数功能是将port的物理地址获取到转换成虚拟地址,类似于ioremap函数。第二个函数与之相反是取消port地址映射,类似于iounmap函数。第三个函树博主续追踪一下其实现。

static void serial8250_config_port(struct uart_port *port, int flags)
{
 struct uart_8250_port *up =
  container_of(port, struct uart_8250_port, port);
 int probeflags = PROBE_ANY;
 int ret;
 ret = serial8250_request_std_resource(up);
 if (ret < 0)
  return;
 ret = serial8250_request_rsa_resource(up);
 if (ret < 0)
  probeflags &= ~PROBE_RSA;
 if (up->port.iotype != up->cur_iotype)
  set_io_from_upio(port);
 if (flags & UART_CONFIG_TYPE)
  autoconfig(up, probeflags);
 /* if access method is AU, it is a 16550 with a quirk */
 if (up->port.type == PORT_16550A && up->port.iotype == UPIO_AU)
  up->bugs |= UART_BUG_NOMSR;
 if (up->port.type != PORT_UNKNOWN && flags & UART_CONFIG_IRQ)
  autoconfig_irq(up);
 if (up->port.type != PORT_RSA && probeflags & PROBE_RSA)
  serial8250_release_rsa_resource(up);
 if (up->port.type == PORT_UNKNOWN)
  serial8250_release_std_resource(up);
}

autoconfig()

继续追踪函数实体autoconfig(),函数原型如下,函数路径见./drivers/tty/serial/8250.c
,此函数主要实现关于硬件参数读取写入操作,此处不作赘述,重点关注UART_IIR值获取到后进行不同类型的串口驱动适配注册。

static void autoconfig(struct uart_8250_port *up, unsigned int probeflags)
{
	................................
	 scratch = serial_in(up, UART_IIR) >> 6;
	 DEBUG_AUTOCONF("iir=%d ", scratch);
	 switch (scratch) {
	 case 0:
	  autoconfig_8250(up);
	  break;
	 case 1:
	  up->port.type = PORT_UNKNOWN;
	  break;
	 case 2:
	  up->port.type = PORT_16550;
	  break;
	 case 3:
	  autoconfig_16550a(up);
	  break;
	 }
	 ................................
}

serial8250_console()

继续追踪serial8250_console()实现,函数原型如下,函数路径见./drivers/tty/serial/8250.c
,此结构体是第一步是cosole基本操作初始化接口,将uart驱动绑定到cosole驱动内。
.data = &serial8250_reg

static struct console serial8250_console = {
 .name  = "ttyS",
 .write  = serial8250_console_write,
 .device  = uart_console_device,
 .setup  = serial8250_console_setup,
 .early_setup = serial8250_console_early_setup,
 .flags  = CON_PRINTBUFFER | CON_ANYTIME,
 .index  = -1,
 .data  = &serial8250_reg,
};

至此底层硬件操作接口注册分析完成,小结如下:
第一步初始化serial8250底层操作函数接口,将serial8250操作函数绑定到uart驱动中。
up->port.ops = &serial8250_pops;
第二步初始化console用户层操作函数接口,将uart驱动操作函数绑定到console驱动中。
.data  = &serial8250_reg,

相关标签: 内核