UART驱动分析
在linux用户层上要操作底层串口需要对/dev/ttySxxx操作,这里的ttySx指实际的终端串口。
以下以全志A64为实例,分析UART驱动以及浅谈TTY架构。
linux-3.10/drivers/tty/serial/sunxi-uart.c:
1 static const struct of_device_id sunxi_uart_match[] = { 2 { .compatible = "allwinner,sun8i-uart", }, 3 { .compatible = "allwinner,sun50i-uart", }, 4 {}, 5 }; 6 MODULE_DEVICE_TABLE(of, sunxi_uart_match); //设备树查找device并注册 7 8 static struct uart_driver sw_uart_driver = { 9 .owner = THIS_MODULE, 10 .driver_name = SUNXI_UART_DEV_NAME, //"uart" 11 .dev_name = "ttyS", 12 .nr = SUNXI_UART_NUM, //6 13 .cons = SW_CONSOLE, 14 }; 15 16 static int __init sunxi_uart_init(void) 17 { 18 int ret; 19 20 ret = uart_register_driver(&sw_uart_driver); //注册tty_driver 21 if (unlikely(ret)) { 22 SERIAL_MSG("driver initializied\n"); 23 return ret; 24 } 25 26 return platform_driver_register(&sw_uport_platform_driver); //硬件平台相关相关进入probe 27 }
先看看注册tty_driver里面做了什么操作,删减部分代码linux-3.10/drivers/tty/serial/serial_core.c:
1 static const struct tty_operations uart_ops = { 2 .open = uart_open, 3 .close = uart_close, 4 .write = uart_write, 5 .put_char = uart_put_char, 6 .flush_chars = uart_flush_chars, 7 .write_room = uart_write_room, 8 .chars_in_buffer = uart_chars_in_buffer, 9 .flush_buffer = uart_flush_buffer, 10 .ioctl = uart_ioctl, 11 .throttle = uart_throttle, 12 .unthrottle = uart_unthrottle, 13 .send_xchar = uart_send_xchar, 14 .set_termios = uart_set_termios, 15 .set_ldisc = uart_set_ldisc, 16 .stop = uart_stop, 17 .start = uart_start, 18 .hangup = uart_hangup, 19 .break_ctl = uart_break_ctl, 20 .wait_until_sent = uart_wait_until_sent, 21 #ifdef CONFIG_PROC_FS 22 .proc_fops = &uart_proc_fops, 23 #endif 24 .tiocmget = uart_tiocmget, 25 .tiocmset = uart_tiocmset, 26 .get_icount = uart_get_icount, 27 #ifdef CONFIG_CONSOLE_POLL 28 .poll_init = uart_poll_init, 29 .poll_get_char = uart_poll_get_char, 30 .poll_put_char = uart_poll_put_char, 31 #endif 32 }; 33 34 int uart_register_driver(struct uart_driver *drv) 35 { 36 struct tty_driver *normal; 37 int i, retval; 38 39 drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL); 40 41 normal = alloc_tty_driver(drv->nr); 42 43 drv->tty_driver = normal; 44 45 normal->driver_name = drv->driver_name; 46 normal->name = drv->dev_name; 47 normal->major = drv->major; 48 normal->minor_start = drv->minor; 49 normal->type = TTY_DRIVER_TYPE_SERIAL; 50 normal->subtype = SERIAL_TYPE_NORMAL; 51 normal->init_termios = tty_std_termios; 52 normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; 53 normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600; 54 normal->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; 55 //TTY_DRIVER_DYNAMIC_DEV不立刻注册设备,只初始化 56 normal->driver_state = drv; 57 tty_set_operations(normal, &uart_ops); //normal->ops = uart_ops 58 59 /* 60 * Initialise the UART state(s). 61 */ 62 for (i = 0; i < drv->nr; i++) { 63 struct uart_state *state = drv->state + i; 64 struct tty_port *port = &state->port; 65 //初始化tty_port的等待队列open_wait,close_wait,delta_msr_wait和buffer工作队列flush_to_ldisc 66 tty_port_init(port); 67 68 port->ops = &uart_port_ops; //tty_port->ops = uart_port_ops 69 port->close_delay = HZ / 2; /* .5 seconds */ 70 port->closing_wait = 30 * HZ;/* 30 seconds */ 71 } 72 73 retval = tty_register_driver(normal); //初始化tty字符设备但不注册 74 return retval;
以上完成tty的初始化,从以上代码可以知道tty_driver->ops = uart_ops,uart_ops是串口底层操作。
回到硬件平台的probe函数,部分删减代码linux-3.10/drivers/tty/serial/sunxi-uart.c:
1 static int sw_uart_probe(struct platform_device *pdev) 2 { 3 struct device_node *np = pdev->dev.of_node; 4 struct uart_port *port; 5 struct sw_uart_port *sw_uport; 6 struct sw_uart_pdata *pdata; 7 struct resource *res; 8 char uart_para[16] = {0}; 9 int ret = -1; 10 11 pdev->id = of_alias_get_id(np, "serial"); //通过别名获取UART的ID 12 13 port = &sw_uart_ports[pdev->id].port; 14 port->dev = &pdev->dev; 15 pdata = &sw_uport_pdata[pdev->id]; 16 sw_uport = UART_TO_SPORT(port); 17 sw_uport->pdata = pdata; 18 sw_uport->id = pdev->id; 19 sw_uport->ier = 0; 20 sw_uport->lcr = 0; 21 sw_uport->mcr = 0; 22 sw_uport->fcr = 0; 23 sw_uport->dll = 0; 24 sw_uport->dlh = 0; 25 snprintf(sw_uport->name, 16, SUNXI_UART_DEV_NAME"%d", pdev->id); 26 pdev->dev.init_name = sw_uport->name; 27 pdev->dev.platform_data = sw_uport->pdata; 28 29 sw_uport->mclk = of_clk_get(np, 0); 30 31 port->uartclk = clk_get_rate(sw_uport->mclk); 32 33 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 34 35 port->mapbase = res->start; 36 37 port->irq = platform_get_irq(pdev, 0); 38 39 snprintf(uart_para, sizeof(uart_para), "uart%d_port", pdev->id); 40 ret = of_property_read_u32(np, uart_para, &port->line); //设备树uartx_port定义第几路 41 42 snprintf(uart_para, sizeof(uart_para), "uart%d_type", pdev->id); 43 ret = of_property_read_u32(np, uart_para, &pdata->io_num); //设备树uartx_type是2线还是4线 44 45 pdata->used = 1; 46 port->iotype = UPIO_MEM; 47 port->type = PORT_SUNXI; 48 port->flags = UPF_BOOT_AUTOCONF; 49 port->ops = &sw_uart_ops; 50 port->fifosize = 64; 51 platform_set_drvdata(pdev, port); 52 53 sunxi_uart_sysfs(pdev); 54 55 return uart_add_one_port(&sw_uart_driver, port); //把串口底层代码向上映射 56 }
以上代码基本是从设备树获取硬件相关的初始化代码,最后的uart_add_one_port把driver和uart_port参数作为形参向上映射。
删减部分代码linux-3.10/drivers/tty/serial/serial_core.c:
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; state = drv->state + uport->line; port = &state->port; state->uart_port = uport; state->pm_state = UART_PM_STATE_UNDEFINED; uport->cons = drv->cons; uport->state = state; //通过port->ops->config_port =sw_uart_config_port通过设备树申请复用io口资源 uart_configure_port(drv, state, uport); /* * Register the port whether it's detected or not. This allows * setserial to be used to alter this ports parameters. */ tty_dev = tty_port_register_device_attr(port, drv->tty_driver, //注册ttySx字符设备 uport->line, uport->dev, port, tty_dev_attr_groups); device_set_wakeup_capable(tty_dev, 1); /* * Ensure UPF_DEAD is not set. */ uport->flags &= ~UPF_DEAD; return ret; }
现在可以在用户层看到/dev多了ttySx字符设备,可以像普通的字符设备进行读写操作了。但事实并不是tty->uart这样,在tty_driver->ops里面并不是直接调用uart_port->ops,其中又用映射线路规程(Line discipline)。
在kernel初始化终端的时候会调用:
1 void tty_ldisc_begin(void) 2 { 3 /* Setup the default TTY line discipline. */ 4 (void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY); //串口都是tty_ldisc_N_TTY 5 }
串口都会用默认线路N_TTY进行规程。
对/dev/ttySxxx字符设备操作的时候,通过N_TTY---->TTY_DRIVER---->UART_PORT---->硬件平台相关。
对N_TTY分析后续补上,未完!