- http://www.wowotech.net/tty_framework/435.html
- https://www.codeleading.com/article/13543009429/
1.UART 框架图
串口驱动相关数据结构:
struct uart_driver; //串口驱动结构
struct uart_port; //端口结构,串口驱动只有一个,端口却有多个,一个端口对应一个实际的串口
struct uart_ops; //函数操作集
struct uart_state; //状态结构
struct uart_info; //串口信息结构
2.8250/16550 chip
- drivers/tty/serial/8250/8250_of.c
- drivers/tty/serial/8250/8250_core.c
- drivers/tty/serial/8250/8250_port.c
代码分析:
-
serial8250_init()—>uart_register_driver(&serial8250_reg)
-
serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev)
-
serial8250_probe(struct platform_device *dev)
2.1.串口驱动初始化
初始化函数为serial8250_init(),代码如下:
drivers/tty/serial/8250/8250_core.c:
1142 static int __init serial8250_init(void)
1143 {
1144 int ret;
1149 serial8250_isa_init_ports();
1150
1154 #ifdef CONFIG_SPARC
1155 ret = sunserial_register_minors(&serial8250_reg, UART_NR);
1156 #else
1157 serial8250_reg.nr = UART_NR;
1158 ret = uart_register_driver(&serial8250_reg);
1159 #endif
1163 ret = serial8250_pnp_init();
1167 serial8250_isa_devs = platform_device_alloc("serial8250",
1168 PLAT8250_DEV_LEGACY);
1174 ret = platform_device_add(serial8250_isa_devs);
1178 serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);
1180 ret = platform_driver_register(&serial8250_isa_driver);
1181 }
uart_register_driver(&serial8250_reg); 函数将参数serial8250_reg注册进了tty层。具体代码如下所示:
2486 int uart_register_driver(struct uart_driver *drv)
2487 {
2488 struct tty_driver *normal;
2489 int i, retval;
2497 drv->state = kcalloc(drv->nr, sizeof(struct uart_state), GFP_KERNEL);
2500
2501 normal = alloc_tty_driver(drv->nr);
2504
2505 drv->tty_driver = normal;
2506
2507 normal->driver_name = drv->driver_name;
2508 normal->name = drv->dev_name;
2509 normal->major = drv->major;
2510 normal->minor_start = drv->minor;
2511 normal->type = TTY_DRIVER_TYPE_SERIAL;
2512 normal->subtype = SERIAL_TYPE_NORMAL;
2513 normal->init_termios = tty_std_termios;
2514 normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
2515 normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;
2516 normal->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
2517 normal->driver_state = drv;
2518 tty_set_operations(normal, &uart_ops);
2523 for (i = 0; i < drv->nr; i++) {
2524 struct uart_state *state = drv->state + i;
2525 struct tty_port *port = &state->port;
2526
2527 tty_port_init(port);
2528 port->ops = &uart_port_ops;
2529 }
2530
2531 retval = tty_register_driver(normal);
2534
2535 for (i = 0; i < drv->nr; i++)
2536 tty_port_destroy(&drv->state[i].port);
2537 put_tty_driver(normal);
2540 out:
2541 return -ENOMEM;
2542 }
从上面代码可以看出,uart_driver中很多数据结构其实就是tty_driver中的,将数据转换为tty_driver之后,tty_register_driver注册tty_driver。然后初始化uart_driver->state的存储空间。这里有两个地方我们需要特别关注:
第一个需要关注的地方:
normal->driver_state = drv;
将参数的ops关系都赋给了serial_core层。也就是说在后面serial_core会根据uart_ops关系找到8250.c中所对应的操作,而参数中的ops是在哪被赋值的呢?这个一定是会在8250.c,定位到了8250.c中的serial8250_ops结构体,初始化如下:
drivers/tty/serial/8250/8250_port.c:
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, //串口clk,波特率,数据位等参数设置
.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
};
同理:
static struct uart_ops <strong>s3c24xx_serial_ops </strong>={
.pm =s3c24xx_serial_pm, //电源管理函数
.tx_empty = s3c24xx_serial_tx_empty, //检车发送FIFO缓冲区是否空
.get_mctrl = s3c24xx_serial_get_mctrl, //是否串口流控
.set_mctrl = s3c24xx_serial_set_mctrl, //是否设置串口流控cts
.stop_tx =s3c24xx_serial_stop_tx, //停止发送
.start_tx =s3c24xx_serial_start_tx, //启动发送
.stop_rx =s3c24xx_serial_stop_rx, //停止接收
.enable_ms = s3c24xx_serial_enable_ms, //空函数
.break_ctl = s3c24xx_serial_break_ctl, //发送break信号
.startup =s3c24xx_serial_startup, //串口发送/接收,以及中断申请初始配置函数
.shutdown = s3c24xx_serial_shutdown, //关闭串口
.set_termios = s3c24xx_serial_set_termios,//串口clk,波特率,数据位等参数设置
.type = s3c24xx_serial_type, // CPU类型关于串口
.release_port =s3c24xx_serial_release_port, //释放串口
.request_port =s3c24xx_serial_request_port, //申请串口
.config_port = s3c24xx_serial_config_port, //串口的一些配置信息info
.verify_port = s3c24xx_serial_verify_port, //串口检测
.wake_peer = s3c24xx_serial_wake_peer,
};
这样一来只要将serial8250_ops结构体成员的值赋给uart_dirver就可以了,那么这个过程在哪呢?就是在uart_add_one_port()函数中,这个函数是从serial8250_init->serial8250_register_ports()->uart_add_one_port()逐步调用过来的,这一步就将port和uart_driver联系起来了。serial8250_pops 定义硬件操作实现:
-
tx_empty: serial8250_tx_empty
读取并判断LSR的第THRE、TEMT位是否为1 -
set_mctrl: serial8250_set_mctrl
将位设置(RTS、DTR、OUT1、OUT2、LOOP)写入MCR -
get_mctrl: serial8250_get_mctrl
读取MSR, 即Modem Interface的当前状态 -
stop_tx: serial8250_stop_tx
禁用IER的THRI/ETBEI位 -
start_tx: serial8250_start_tx
启用IER的THRI/ETBEI位; 当LSR的THRE位为1, 通过操作THR将circ_buf的数据搬运至UART -
stop_rx: serial8250_stop_rx
禁用IER的RLSI/ELSI和RDI/ERBFI位 -
enable_ms: serial8250_enable_ms
启用IER的MSI/EDSSI -
break_ctl: serial8250_break_ctl
启动或者禁用LCR的SBC/SetBreak位 -
startup: serial8250_startup
- 设置FCR清空FIFO缓冲区, 清空中断寄存器(LSR、RX、IIR、MSR), 初始化相关寄存器
- 调用uart_8250_ops::setup_irq(univ8250_setup_irq)
- 设置MCR寄存器
- 为TX/RX请求DMA通道
-
set_termios: serial8250_set_termios
设置相关寄存器(…) -
set_ldisc: serial8250_set_ldisc
如果没有设置了Modem状态, 则禁用IER的MSI位 -
type: serial8250_type
获取硬件名称 -
release_port: serial8250_release_port
释放端口占用物理资源, 如Memory, I/O -
request_port: serial8250_request_port
请求物理资源 -
config_port: serial8250_config_port
按照传入参数配置端口 -
verify_port: serial8250_verify_port
校验端口配置是否有效
第二个需要关注的地方:
tty_set_operations(normal, &uart_ops);
将tty_driver的操作集统一设为了uart_ops,这样就使得从用户空间下来的操作可以找到正确的serial_core的操作函数,这样就保证了调用关系的通畅。uart_ops是在serial_core.c中的:
static const struct tty_operations uart_ops = {
.open = uart_open,
.close = uart_close,
.write = uart_write,
.put_char = uart_put_char,
.flush_chars = uart_flush_chars,
.write_room = uart_write_room,
.chars_in_buffer= uart_chars_in_buffer,
.flush_buffer = uart_flush_buffer,
.ioctl = uart_ioctl,
.throttle = uart_throttle,
.unthrottle = uart_unthrottle,
.send_xchar = uart_send_xchar,
.set_termios = uart_set_termios,
.set_ldisc = uart_set_ldisc,
.stop = uart_stop,
.start = uart_start,
.hangup = uart_hangup,
.break_ctl = uart_break_ctl,
.wait_until_sent= uart_wait_until_sent,
#ifdef CONFIG_PROC_FS
.read_proc = uart_read_proc,
#endif
.tiocmget = uart_tiocmget,
.tiocmset = uart_tiocmset,
#ifdef CONFIG_CONSOLE_POLL
.poll_init = uart_poll_init,
.poll_get_char = uart_poll_get_char,
.poll_put_char = uart_poll_put_char,
#endif
};
2.2.注册uart_driver
对应uart_driver的结构为serial8250_reg,定义如下:
703 static struct uart_driver serial8250_reg = {
704 .owner = THIS_MODULE,
705 .driver_name = "serial",
706 .dev_name = "ttyS",
707 .major = TTY_MAJOR,
708 .minor = 64,
709 .cons = SERIAL8250_CONSOLE,
710 };
从上面可以看出,串口对应的设备节点为/dev/ttyS0 ~ /dev/ttyS0(UART_NR),设备节点号为(4 64)起始的UART_NR个节点。
2.3.初始化并注册platform_device
龙芯3a4000 CPU有两个串口驱动,设备定义如下:
arch/mips/loongson64/common/serial.c:
54 static struct platform_device uart8250_device = {
55 .name = "serial8250",
56 .id = PLAT8250_DEV_PLATFORM,
57 };
注册设备:
platform_device_register(&uart8250_device);
另外添加设备serial8250_isa_devs:
serial8250_isa_devs = platform_device_alloc("serial8250", PAT8250_DEV_LEGACY);
platform_device_add(serial8250_isa_devs);
serial8250_isa_devs->name为serial8250,这个参数是在匹配platform_device和platform_driver使用的。
2.4.为uart-driver添加port
相关代码如下:
serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev)
569 static void __init
570 serial8250_register_ports(struct uart_driver *drv, struct device *dev)
571 {
572 int i;
573
574 for (i = 0; i < nr_uarts; i++) {
575 struct uart_8250_port *up = &serial8250_ports[i];
576
577 if (up->port.type == PORT_8250_CIR)
578 continue;
579
580 if (up->port.dev)
581 continue;
582
583 up->port.dev = dev;
584
585 serial8250_apply_quirks(up);
586 uart_add_one_port(drv, &up->port);
587 }
588 }
初始化port,然后将挂添加到uart-driver中。生成的deivce节点,在sysfs中是位于platform_deivce对应目录的下面。
serial8250_isa_init_ports()代码如下所示:
503 static void __init serial8250_isa_init_ports(void)
504 {
505 struct uart_8250_port *up;
506 static int first = 1;
507 int i, irqflag = 0;
518
519 for (i = 0; i < nr_uarts; i++) {
520 struct uart_8250_port *up = &serial8250_ports[i];
521 struct uart_port *port = &up->port;
522
523 port->line = i;
524 serial8250_init_port(up); //port->ops = &serial8250_pops;
525 if (!base_ops)
526 base_ops = port->ops;
527 port->ops = &univ8250_port_ops;
528
529 timer_setup(&up->timer, serial8250_timeout, 0);
530
531 up->ops = &univ8250_driver_ops;
532
533 /*
534 * ALPHA_KLUDGE_MCR needs to be killed.
535 */
536 up->mcr_mask = ~ALPHA_KLUDGE_MCR;
537 up->mcr_force = ALPHA_KLUDGE_MCR;
538 }
539
540 /* chain base port ops to support Remote Supervisor Adapter */
541 univ8250_port_ops = *base_ops;
542 univ8250_rsa_support(&univ8250_port_ops);
543
547 for (i = 0, up = serial8250_ports;
548 i < ARRAY_SIZE(old_serial_port) && i < nr_uarts;
549 i++, up++) {
550 struct uart_port *port = &up->port;
551
552 port->iobase = old_serial_port[i].port;
553 port->irq = irq_canonicalize(old_serial_port[i].irq);
554 port->irqflags = 0;
555 port->uartclk = old_serial_port[i].baud_base * 16;
556 port->flags = old_serial_port[i].flags;
557 port->hub6 = 0;
558 port->membase = old_serial_port[i].iomem_base;
559 port->iotype = old_serial_port[i].io_type;
560 port->regshift = old_serial_port[i].iomem_reg_shift;
561 serial8250_set_defaults(up);
562
563 port->irqflags |= irqflag;
564 if (serial8250_isa_config != NULL)
565 serial8250_isa_config(i, &up->port, &up->capabilities);
566 }
567 }
两个for循环内的操作:
- 第一个for循环功能是初始化定时器功能函数up->timer.function = serial8250_timeout; 初始化serial8250 port的操作函数 port->ops = &serial8250_pops; 这个接口serial8250_pops内有关于port操作函数挂载。
- 第二个for循环功能是初始化默认port硬件参数old_serial_port[i].port。
2.5.注册platform_driver
相关代码如下:
platform_driver_register(&serial8250_isa_driver);
serial8250_isa_driver定义如下:
900 static struct platform_driver serial8250_isa_driver = {
901 .probe = serial8250_probe,
902 .remove = serial8250_remove,
903 .suspend = serial8250_suspend,
904 .resume = serial8250_resume,
905 .driver = {
906 .name = "serial8250",
907 },
908 };
这个platform的name为” serial8250”,刚好跟前面注册的platform_device相匹配,会调用platform_driver-> probe,在这里对应的接口为: serial8250_probe(),代码如下:
811 static int serial8250_probe(struct platform_device *dev)
812 {
813 struct plat_serial8250_port *p = dev_get_platdata(&dev->dev);
814 struct uart_8250_port uart;
815 int ret, i, irqflag = 0;
816
817 memset(&uart, 0, sizeof(uart));
818
819 if (share_irqs)
820 irqflag = IRQF_SHARED;
821
822 for (i = 0; p && p->flags != 0; p++, i++) {
823 uart.port.iobase = p->iobase;
824 uart.port.membase = p->membase;
825 uart.port.irq = p->irq;
826 uart.port.irqflags = p->irqflags;
827 uart.port.uartclk = p->uartclk;
828 uart.port.regshift = p->regshift;
829 uart.port.iotype = p->iotype;
830 uart.port.flags = p->flags;
831 uart.port.mapbase = p->mapbase;
832 uart.port.hub6 = p->hub6;
833 uart.port.private_data = p->private_data;
834 uart.port.type = p->type;
835 uart.port.serial_in = p->serial_in;
836 uart.port.serial_out = p->serial_out;
837 uart.port.handle_irq = p->handle_irq;
838 uart.port.handle_break = p->handle_break;
839 uart.port.set_termios = p->set_termios;
840 uart.port.set_ldisc = p->set_ldisc;
841 uart.port.get_mctrl = p->get_mctrl;
842 uart.port.pm = p->pm;
843 uart.port.dev = &dev->dev;
844 uart.port.irqflags |= irqflag;
845 ret = serial8250_register_8250_port(&uart);
853 return 0;
854 }
注册调用流程:
serial8250_register_8250_port->uart_add_one_port
/* 将 uart_port 注册到 uart_driver */
2755 int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
2756 {
2757 struct uart_state *state;
2758 struct tty_port *port;
2759 int ret = 0;
2760 struct device *tty_dev;
2761 int num_groups;
2804 ...
/* 实际调用 port->ops->config_port(port, flags) 稍后再看 */
2805 uart_configure_port(drv, state, uport);
2807 port->console = uart_console(uport);
2827 tty_dev = tty_port_register_device_attr_serdev(port, drv->tty_driver,
2828 uport->line, uport->dev, port, uport->tty_groups);
2835
2845 return ret;
- uart_configure_port
- port->ops->config_port(port, flags); //serial8250_config_port
3.Changing serial ports configuration
- To get current configuration
# stty -F /dev/ttyS0 -a
speed 57600 baud; rows 0; columns 0; line = 0;
intr = <undef>; quit = <undef>; erase = <undef>; kill = <undef>; eof = <undef>; eol = <undef>; eol2 = <undef>; swtch = <undef>; start = <undef>; stop = <undef>; susp = <undef>; rprnt = <undef>; werase = <undef>;
lnext = <undef>; discard = <undef>; min = 1; time = 1;
-parenb -parodd cs8 -hupcl -cstopb cread clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr -icrnl -ixon -ixoff -iuclc -ixany -imaxbel -iutf8
-opost -olcuc -ocrnl -onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
-isig -icanon -iexten -echo -echoe -echok -echonl -noflsh -xcase -tostop -echoprt -echoctl -echoke -flusho -extproc
- To only get actual speed:
# stty -F /dev/ttyS0 speed
115200
- To change baudrate to 115200 :
# stty -F /dev/ttyS0 ispeed 115200 ospeed 115200 cs8
3.1.Sending/Receiving data
- Sending data to it:
echo “HELLO” > /dev/ttyS3
代码调用流程:
uart_open
->tty_port_open
->int retval = port->ops->activate(port, tty);//uart_port_activate
->uart_startup
->retval = uport->ops->startup(uport); //serial8250_startup
->serial8250_startup
->serial8250_do_startup
->retval = up->ops->setup_irq(up);
-->univ8250_setup_irq
->serial_link_irq_chain // ret = request_irq(up->port.irq, serial8250_interrupt, irq_flags, up->port.name, i);
->serial8250_interrupt
->port->handle_irq(port)
->serial8250_handle_irq
->serial8250_rx_chars
->serial8250_tx_chars
- To receive data (ASCII in that case):
cat /dev/ttyS3
3.2.Statistics
# cat /proc/tty/driver/serial
serinfo:1.0 driver revision:
0: uart:ST16650 mmio:0x1FE001E0 irq:88 tx:211 rx:0 RTS|CTS|DTR|DSR|CD|RI
1: uart:ST16650 mmio:0x1FE001E8 irq:58 tx:0 rx:0 CTS|DSR|CD|RI
2: uart:Loongson 16550 mmio:0x10080000 irq:24 tx:211 rx:0 RTS|DTR|CD|RI
3: uart:unknown port:00000000 irq:0
4: uart:unknown port:00000000 irq:0
5: uart:unknown port:00000000 irq:0
6: uart:unknown port:00000000 irq:0
7: uart:unknown port:00000000 irq:0
8: uart:unknown port:00000000 irq:0
9: uart:unknown port:00000000 irq:0
10: uart:unknown port:00000000 irq:0
11: uart:unknown port:00000000 irq:0
12: uart:unknown port:00000000 irq:0
13: uart:unknown port:00000000 irq:0
14: uart:unknown port:00000000 irq:0
15: uart:unknown port:00000000 irq:0
3.3. ttyS0 节点信息
#/sys/class/tty/ttyS0$ ls -l
总用量 0
-r--r----- 1 root root 16384 11月 12 14:08 close_delay
-r--r----- 1 root root 16384 11月 12 14:08 closing_wait
-r--r----- 1 root root 16384 11月 12 14:08 custom_divisor
-r--r--r-- 1 root root 16384 11月 12 14:08 dev
lrwxrwxrwx 1 root root 0 11月 12 13:47 device -> ../../../serial8250.0
-r--r----- 1 root root 16384 11月 12 14:08 flags
-r--r----- 1 root root 16384 11月 12 14:08 iomem_base
-r--r----- 1 root root 16384 11月 12 14:08 iomem_reg_shift
-r--r----- 1 root root 16384 11月 12 14:08 io_type
-r--r----- 1 root root 16384 11月 12 14:08 irq
-r--r----- 1 root root 16384 11月 12 14:08 line
-r--r----- 1 root root 16384 11月 12 14:08 port
drwxr-xr-x 2 root root 0 11月 12 14:08 power
lrwxrwxrwx 1 root root 0 11月 12 13:47 subsystem -> ../../../../../class/tty
-r--r----- 1 root root 16384 11月 12 14:08 type
-r--r----- 1 root root 16384 11月 12 14:08 uartclk
-rw-r--r-- 1 root root 16384 11月 12 13:47 uevent
-r--r----- 1 root root 16384 11月 12 14:08 xmit_fifo_size
uos@uos-PC:/sys/class/tty/ttyS0$ cat uartclk
3.4.设置波特率
将串口1(/dev/ttyS0)设置成115200波特率,8位数据模式。
# stty -F /dev/ttyS0 ispeed 115200 ospeed 115200 cs8
# stty -F /dev/ttyS0 -a //查看设置信息
stty应用程序调用流程:
tty_ioctl->tty_mode_ioctl->set_termios->uart_set_termios
3.5.Use the setserial command to check and use serial ports
The setserial is a program designed to set and/or report the configuration information associated with a serial port. This information includes what I/O port and IRQ a particular serial port is using, and whether or not the break key should be interpreted as the Secure Attention Key, and so on.
Using setserial to list serial ports and devices
Now we installed required package. Open the termial and then type the following setserial command:
$ setserial -g /dev/ttyS[0123]
If you get an error/warning that reads as “Permission denied,” try running the command as the root user. For example, I am running it using the sudo command/su command:
$ sudo setserial -g /dev/ttyS[0123]
The setserial with -g option help to find out what physical serial ports your Linux box has.
refer to
- https://tldp.org/HOWTO/Serial-HOWTO.html
- http://www.wowotech.net/tty_framework/435.html
- https://www.geek-share.com/detail/2677828880.html
- https://developer.aliyun.com/article/375411
- https://www.cnblogs.com/hzl6255/p/9560138.html
- http://www.armadeus.org/wiki/index.php?title=Serial_ports_usage_on_Linux