工作中需要调试linux系统下的串口驱动,因特殊需求需要创建一些虚拟串口,工作中需要将串口驱动的整个流程全部梳理,中途中遇到了不少问题,现在问题已经基本解决,现将整个过程做个总结记录。
整体框图 首先给出整体框图,后面结合代码详细介绍各个流程
UART驱动注册 uart_driver数据结构 向kernel中注册一个串口驱动之前,需要先准备一个uart_driver结构该,uart_driver就是串口的驱动
1 2 3 4 5 6 7 static struct uart_driver virtual_uart_driver = { .owner = THIS_MODULE, .driver_name = "virtual_serial" , .dev_name = "COM" , .nr = VIRTUAL_UART_PORT_NR, };
driver_name:串口驱动的名字,可以自定义 dev_name:使用该驱动注册串口设备的设备名前缀,也可以自定义 nr:该驱动允许注册的设备数量,尽可能按照实际需求来写,注册串口驱动时会根据nr来申请资源
uart_register_driver注册驱动 准备好uart_driver数据结构后,调用uart_register_driver即可将其注册进kernel.
接下来看看uart_register_driver里做了哪些事情(请注意nr的使用 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 drv->state = kzalloc(sizeof (struct uart_state) * drv->nr, GFP_KERNEL); if (!drv->state) goto out; normal = alloc_tty_driver(drv->nr); if (!normal) goto out_kfree; drv->tty_driver = normal; normal->driver_name = drv->driver_name; normal->name = drv->dev_name; normal->major = drv->major; normal->minor_start = drv->minor; normal->type = TTY_DRIVER_TYPE_SERIAL; normal->subtype = SERIAL_TYPE_NORMAL; normal->init_termios = tty_std_termios; normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600 ; normal->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; normal->driver_state = drv; tty_set_operations(normal, &uart_ops); for (i = 0 ; i < drv->nr; i++) { struct uart_state *state = drv ->state + i ; struct tty_port *port = &state ->port ; tty_port_init(port); port->ops = &uart_port_ops; port->close_delay = HZ / 2 ; port->closing_wait = 30 * HZ; } retval = tty_register_driver(normal); if (retval >= 0 ) return retval;
uart_port数据结构 向kernel中添加串口之前,需要准备好uart_port数据结构
uart_port与底层的硬件密切相关,对于驱动框架而言,最重要的是uart_ops,定义了操作硬件的接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 static struct uart_ops virtual_uart_pops = { .tx_empty = virtual_tx_empty, .set_mctrl = virtual_set_mctrl, .get_mctrl = virtual_get_mctrl, .stop_tx = virtual_nothing, .start_tx = virtual_start_tx, .stop_rx = virtual_nothing, .break_ctl = virtual_break_ctl, .startup = virtual_startup, .shutdown = virtual_nothing, .set_termios = virtual_set_termios, .type = virtual_type, .release_port = virtual_nothing, .request_port = virtual_nothing1, .config_port = virtual_config_port, };
另外1个重要成员是line,指示该uart_port属于驱动中的第几个端口,对于到应用层的串口编号
uart_add_one_port 添加串口 uart_driver注册好后,需要将uart_port添加进去
uart_driver只是一框架,不具备与硬件打交道的能力,与硬件打交道是uart_port,因此一般调试驱动时更多的是在修改uart_port,但查找问题的过程一般要贯穿整个流程
1 2 3 4 5 6 7 8 9 10 for (i = 0 ; i < VIRTUAL_UART_PORT_NR; i++) { virtual_uart_port[i].ops = &virtual_uart_pops; virtual_uart_port[i].attr_group = &virtual_attr_group; virtual_uart_port[i].type = PORT_VIRTUAL; virtual_uart_port[i].line = i; ret = uart_add_one_port(&virtual_uart_driver, virtual_uart_port+i); if (unlikely(ret)) goto remove_port; }
现在来看看uart_add_one_port干了些什么事(以下只将重要部分展现出来 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int uart_add_one_port (struct uart_driver *drv, struct uart_port *uport) { struct uart_state *state ; struct tty_port *port ; struct device *tty_dev ; state = drv->state + uport->line; port = &state->port; state->uart_port = uport; uport->state = state; uart_configure_port(drv, state, uport); tty_dev = tty_port_register_device_attr(port, drv->tty_driver, uport->line, uport->dev, port, uport->tty_groups); }
再来跟踪一下tty_port_register_device_attr
1 2 3 4 5 6 7 8 9 10 11 struct device *tty_port_register_device_attr (struct tty_port *port, struct tty_driver *driver, unsigned index, struct device *device, void *drvdata, const struct attribute_group **attr_grp) { tty_port_link_device(port, driver, index); return tty_register_device_attr(driver, index, device, drvdata, attr_grp); }
继续跟踪tty_register_device_attr(以下只将重要部分展现出来 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 struct device *tty_register_device_attr (struct tty_driver *driver, unsigned index, struct device *device, void *drvdata, const struct attribute_group **attr_grp) { char name[64 ]; dev_t devt = MKDEV(driver->major, driver->minor_start) + index; struct device *dev = NULL ; if (driver->type == TTY_DRIVER_TYPE_PTY) pty_line_name(driver, index, name); else tty_line_name(driver, index, name); if (!(driver->flags & TTY_DRIVER_DYNAMIC_ALLOC)) { retval = tty_cdev_add(driver, devt, index, 1 ); } dev = kzalloc(sizeof (*dev), GFP_KERNEL); dev->devt = devt; dev_set_name(dev, "%s" , name); retval = device_register(dev); }
各数据结构的关系 经过一系列的注册及初始化过程后,可以得到以下关系图
open uart,打开串口 应用层打开串口通常是通过打开/dev/下的节点来实现,应用层打开串口后就可以像操作普通文件一样来操作串口了,下面详细讲述一下open串口的具体流程
## tty_open
首先根据VFS可以查找到tty_fops,因此打开/dev/COMx时会调用tty_open,tty_open会做许多初始化
申请一个tty_struct,每个串口只有1个tty_struct,如果多个应用打开同一个串口时,不会申请新的,直接使用已经申请好的;
将tty_struct与当前的文件描述符进行绑定(关闭串口时会用到,如果还有其它文件描述符占用tty_struct,关闭串口时则不会释放tty_struct,否则会释放);
调用tty_struct->ops->open打开tty,做进一步的打开操作(向下一层进攻,开始研究tty_struct的申请过程 )
其实对串口本身不相关的初始化
申请tty_struct 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 static int tty_open (struct inode *inode, struct file *filp) { struct tty_struct *tty ; struct tty_driver *driver = NULL ; int index; dev_t device = inode->i_rdev; driver = tty_lookup_driver(device, filp, &noctty, &index); tty = tty_driver_lookup_tty(driver, inode, index); if (IS_ERR(tty)) { retval = PTR_ERR(tty); goto err_unlock; } } if (tty) { tty_lock(tty); retval = tty_reopen(tty); if (retval < 0 ) { tty_unlock(tty); tty = ERR_PTR(retval); } } else tty = tty_init_dev(driver, index);
进一步来看tty_init_dev(以下只将重要部分展现出来 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 struct tty_struct *tty_init_dev (struct tty_driver *driver, int idx) { struct tty_struct *tty ; int retval; tty = alloc_tty_struct(driver, idx); retval = tty_driver_install_tty(driver, tty); if (!tty->port) tty->port = driver->ports[idx]; tty->port->itty = tty; retval = tty_ldisc_setup(tty, tty->link); return tty; }
来看看alloc_tty_struct,就是一系统的初始化(以下只将重要部分展现出来 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 struct tty_struct *alloc_tty_struct (struct tty_driver *driver, int idx) { struct tty_struct *tty ; tty = kzalloc(sizeof (*tty), GFP_KERNEL); tty->magic = TTY_MAGIC; tty_ldisc_init(tty); tty->driver = driver; tty->ops = driver->ops; tty->index = idx; tty_line_name(driver, idx, tty->name); return tty; }
n_tty 注意:tty的数据读取主要由tty_ldisc_N_TTY来完成
n_tty负责tty的读写控制,各种数据缓存、阻塞、调度处理等 另外也要处理tty的字符转换,比如回显、换行转回车等
在申请tty_struct时调用了tty_ldisc_setup,现在来看看tty_ldisc_setup(以下只将重要部分展现出来 )
1 2 3 4 5 6 7 8 9 10 int tty_ldisc_setup (struct tty_struct *tty, struct tty_struct *o_tty) { struct tty_ldisc *ld = tty ->ldisc ; int retval; retval = tty_ldisc_open(tty, ld); if (retval) return retval; return 0 ; }
n_tty_open会初始化处理数据需要的各类数据结构,本次工作没有对n_tty做深入研究,因此不作进一步展开
uart_open 打开串口的最后一重要部分就是执行uart_open,uart_open由serial_core.c提供,完成串口的初始操作
来看下uart_open干了哪些事(同样只将重要部分展现出来 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 static int uart_open (struct tty_struct *tty, struct file *filp) { struct uart_driver *drv = (struct uart_driver *)tty ->driver ->driver_state ; int retval, line = tty->index; struct uart_state *state = drv ->state + line ; struct tty_port *port = &state ->port ; port->count++; tty->driver_data = state; state->uart_port->state = state; tty_port_tty_set(port, tty); retval = uart_startup(tty, state, 0 ); return retval; }
uart_open没干什么事情,都丢给了uart_startup来处理
1 2 3 4 5 6 7 8 9 10 11 12 13 static int uart_startup (struct tty_struct *tty, struct uart_state *state, int init_hw) { struct tty_port *port = &state ->port ; if (port->flags & ASYNC_INITIALIZED) return 0 ; uart_port_startup(tty, state, init_hw); set_bit(ASYNCB_INITIALIZED, &port->flags); return retval; }
不过uart_startup也没干什么事,又丢给了uart_port_startup处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 static int uart_port_startup (struct tty_struct *tty, struct uart_state *state, int init_hw) { struct uart_port *uport = state ->uart_port ; unsigned long page; int retval = 0 ; if (uport->type == PORT_UNKNOWN) return 1 ; if (!state->xmit.buf) { page = get_zeroed_page(GFP_KERNEL); if (!page) return -ENOMEM; state->xmit.buf = (unsigned char *) page; uart_circ_clear(&state->xmit); } retval = uport->ops->startup(uport); if (retval == 0 ) { if (uart_console(uport) && uport->cons->cflag) { tty->termios.c_cflag = uport->cons->cflag; uport->cons->cflag = 0 ; } uart_change_speed(tty, state, NULL ); } return retval; }
分析到这里,就已经从应用层打开串口到底层硬件初始的所有流程,此时底层硬件的startup需要根据实际硬件来编写,此次工作中是一种特殊的虚拟串口驱动,因此startup会相对比较简单。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 static int virtual_startup (struct uart_port *port) { struct uart_port *phy_port ; phy_port = get_phy_uart_by_config(port->line); if (phy_port) { struct tty_struct *vir_tty = port ->state ->port .tty ; struct tty_struct *phy_tty = phy_port ->state ->port .tty ; vir_tty->termios.c_cflag = phy_tty->termios.c_cflag; } return 0 ; }
各数据结构的关系 经过一系统初始后,可以得到以下关系图
write uart,串口写流程 向串口中写数据分成几个层次,每一层都分工明确
tty_write do_tty_write n_tty_write uart_write/uart_put_char 底层硬件驱动_start_tx handle_tx
tty_write 功能相对比较简,tty_write调用do_tty_write
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 static inline ssize_t do_tty_write ( ssize_t (*write)(struct tty_struct *, struct file *, const unsigned char *, size_t ), struct tty_struct *tty, struct file *file, const char __user *buf, size_t count) { ssize_t ret, written = 0 ; unsigned int chunk; chunk = 2048 ; if (test_bit(TTY_NO_WRITE_SPLIT, &tty->flags)) chunk = 65536 ; if (count < chunk) chunk = count; if (tty->write_cnt < chunk) { unsigned char *buf_chunk; if (chunk < 1024 ) chunk = 1024 ; buf_chunk = kmalloc(chunk, GFP_KERNEL); if (!buf_chunk) { ret = -ENOMEM; goto out; } kfree(tty->write_buf); tty->write_cnt = chunk; tty->write_buf = buf_chunk; } for (;;) { size_t size = count; if (size > chunk) size = chunk; ret = -EFAULT; if (copy_from_user(tty->write_buf, buf, size)) break ; ret = write(tty, file, tty->write_buf, size); if (ret <= 0 ) break ; written += ret; buf += ret; count -= ret; if (!count) break ; ret = -ERESTARTSYS; if (signal_pending(current)) break ; cond_resched(); } return ret; }
do_tty_write,将数据分次发给n_tty_write,n_tty_write会做更加复杂的处理,因为linux将tty数据收发的主要工作交给了n_tty
##n_tty_write_
n_tty_write的代码比较多,这里就不贴出来了,描述一下它的几种重要流程
处理tty的回显
通过tty_write_room(用调用uart_write_room)获取底层xmit.buf可用空间
调用uart_write将数据写入串口的xmit.buf中
如果底层xmit.buf的空间不足导致数据没有发送完,则将当前进程挂起,等待唤醒,由tty->write_wait控制
uart_write uart_write负责将数据写入xmit.buf,然后调用uart_start启动底层发送
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 static int uart_write(struct tty_struct *tty, const unsigned char *buf, int count) { struct uart_state *state = tty->driver_data; struct uart_port *port; struct circ_buf *circ; unsigned long flags; int c, ret = 0; port = state->uart_port; circ = &state->xmit; spin_lock_irqsave(&port->lock, flags); while (1) { c = CIRC_SPACE_TO_END(circ->head, circ->tail, UART_XMIT_SIZE); if (count < c) c = count; //如果circ中没有可用空间了则退出,底层中断会不停的中取走数据 if (c <= 0) break; memcpy(circ->buf + circ->head, buf, c); circ->head = (circ->head + c) & (UART_XMIT_SIZE - 1); buf += c; count -= c; ret += c; } spin_unlock_irqrestore(&port->lock, flags); //通知底层,可以执行发送动作 uart_start(tty); return ret; }
uart_start uart_start简单的调用底层驱动的start_tx
底层驱动的start_tx一般只是打开发送中断(芯片tx fifo中没有数据则产生中断,数据发完后再关闭中断 )
handle_tx 底层驱动的发送中断函数,负责从circ_buf中取数据,然写入寄存器将数据发送到硬件端口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 static void handle_tx (struct uart_port *port) { struct circ_buf *xmit = &port ->state ->xmit ; int tx_count; unsigned int tf_pointer = 0 ; tx_count = uart_circ_chars_pending(xmit); while (tf_pointer < tx_count) { } if (uart_circ_empty(xmit)) msm_hsl_stop_tx(port); if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) { uart_write_wakeup(port); } }
发送流程 到此为止,整个发送流程已经梳理完毕,附上流程图
read uart,串口读流程 包含2部分
open uart时,底层驱动会打开串口的接收功能,硬件接收到数据后会调用rx中断服务函数,数据的入口既是中断服务函数:
handle_rx tty_insert_flip_xx flush_to_ldisc n_tty_receive_buf2
应用层读取串口数据
tty_read n_tty_read copy_from_read_buf
handle_rx 底层驱动的接收中断函数,负责从硬件FIFO中读取数据然后写入到驱动层的buf中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 static void handle_rx (struct uart_port *port, unsigned int misr) { struct tty_struct *tty = port ->state ->port .tty ; int count = 0 ; if (misr & UARTDM_ISR_RXSTALE_BMSK) { count = msm_hsl_read(port, regmap[vid][UARTDM_RX_TOTAL_SNAP]) - msm_hsl_port->old_snap_state; msm_hsl_port->old_snap_state = 0 ; } else { count = 4 * (msm_hsl_read(port, regmap[vid][UARTDM_RFWR])); msm_hsl_port->old_snap_state += count; } while (count > 0 ) { unsigned int c; tty_insert_flip_string(tty->port, (char *) &c, (count > 4 ) ? 4 : count); count -= 4 ; } tty_flip_buffer_push(tty->port); }
##flush_to_ldisc
flush_to_ldisc会调用 n_tty_receive_buf2处于接收到的数据,代码比较复杂,只做简单的功能众介绍
检查n_tty_data的可用空间,如果应用层没有及时将数据读走,则不会从驱动层读取数据,同时做流控制操作
有可用空间的情况下,会将驱动层的数据读取到n_tty_data中
根据当前tty的模式,会将接收到的数据做转换处理,要完成终端协议,这里的处理会比较多
唤醒应用层的读取挂起进程
##tty_read
功能相对比较简,tty_read调用n_tty_read(同样只将重要部分展现出来 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 static ssize_t n_tty_read (struct tty_struct *tty, struct file *file, unsigned char __user *buf, size_t nr) { struct n_tty_data *ldata = tty ->disc_data ; unsigned char __user *b = buf; DECLARE_WAITQUEUE(wait, current); int c; int minimum, time; ssize_t retval = 0 ; long timeout; unsigned long flags; int packet; minimum = time = 0 ; timeout = MAX_SCHEDULE_TIMEOUT; if (!ldata->icanon) { minimum = MIN_CHAR(tty); if (minimum) { time = (HZ / 10 ) * TIME_CHAR(tty); if (time) ldata->minimum_to_wake = 1 ; else if (!waitqueue_active(&tty->read_wait) || (ldata->minimum_to_wake > minimum)) ldata->minimum_to_wake = minimum; } else { timeout = (HZ / 10 ) * TIME_CHAR(tty); ldata->minimum_to_wake = minimum = 1 ; } } packet = tty->packet; add_wait_queue(&tty->read_wait, &wait); while (nr) { if (!input_available_p(tty, 0 )) { timeout = schedule_timeout(timeout); continue ; } uncopied = copy_from_read_buf(tty, &b, &nr); } return retval; }
读取流程 到此为止,整个接收流程已经梳理完毕,附上流程图