百问网韦东山的UART学习笔记
- 0. 说明
- 1. 从哪里入手? 思路是怎样的?
- 2. TTY/Terminal/UART/Console等概念
- 3. 各类数据结构
- 4. tty_open
- 5. tty_read
- 5.1 ldisc read
- 5.2 怎么得到数据的?来自中断?
- 6. tty_write
- 7. 不同的tty设备
- 8. register_console
0. 说明
有些同学想知道我是怎么分析驱动的,我正要研究UART子系统,所以写了这个笔记。 笔记并不是完整的教程,前后可能也没有关联,只是笔记,不要期望太多。
1. 从哪里入手? 思路是怎样的?
我们录制的驱动大全,进入到UART子系统了。我们基于IMX6ULL的内核进行分析,从Linux-4.9.88driversttyserialimx.c
开始阅读代码。
imx_serial_init
uart_register_driver(&imx_reg);
struct tty_driver *normal;
normal = alloc_tty_driver(drv->nr);
normal->driver_state = drv; // uart_driver
上述代码涉及两个概念:tty_driver、uart_driver,它们有什么关系?继续阅读代码前,需要搞清楚。
我分析驱动时,思路是:
- 先弄清楚数据流向:APP open/read/write会导致哪些驱动被调用
- 数据从哪里来?从中断得来,就从中断分析数据流向
2. TTY/Terminal/UART/Console等概念
请参考解密TTY 请仔细阅读此文章,里面有一个图总结得非常好。
3. 各类数据结构
tty_driver、uart_driver互相指定对方,这2个结构体都没有硬件的操作函数。
从uart_register_driver开始分析:
代码语言:javascript复制struct uart_driver *drv;
struct tty_driver *normal;
normal->driver_name = drv->driver_name;
normal->name = drv->dev_name;
normal->major = drv->major;
normal->minor_start = drv->minor;
drv->tty_driver = normal;
normal->driver_state = drv; // uart_driver
还要给tty设置操作函数:
代码语言:javascript复制tty_set_operations(normal, &uart_ops);
normal->ops = &uart_ops;
一个uart_driver可以支持多个port,在uart_driver中有一个uart_state数组,每个数组项对应一个port:
代码语言:javascript复制 /*
* Initialise the UART state(s).
*/
for (i = 0; i < drv->nr; i ) {
struct uart_state *state = drv->state i;
struct tty_port *port = &state->port;
tty_port_init(port); // 初始化环形buffer等
port->ops = &uart_port_ops;
}
注册tty_driver:
代码语言:javascript复制 retval = tty_register_driver(normal);
error = register_chrdev_region(dev, driver->num, driver->name);
d = tty_register_device(driver, i, NULL);
tty_register_device_attr(driver, index, device, NULL, NULL);
retval = tty_cdev_add(driver, devt, index, 1);
driver->cdevs[index]->ops = &tty_fops;
err = cdev_add(driver->cdevs[index], dev, count);
4. tty_open
文件:driversttytty_io.c
多个进程共用一个tty?一个tty只有一个ldisc?一个ldisc中只有一个buffer?
代码语言:javascript复制tty_fops.open
tty_open
struct tty_struct *tty;
// tty属于当前进程的, 多进程可以共用tty吗?
// 这里tty跟硬件对应,比如/dev/ttySAC0, /dev/ttySAC1?
// 多个进程可以共用多个tty
tty = tty_open_current_tty(device, filp);
if (!tty)
tty = tty_open_by_driver(device, inode, filp); // 不涉及硬件操作? tty是软件概念?
struct tty_struct *tty;
struct tty_driver *driver = NULL;
// 根据设备节点找到tty_driver
driver = tty_lookup_driver(device, filp, &index);
/* check whether we're reopening an existing tty */
// 一个tty_driver可以被多次使用,有多个tty
tty = tty_driver_lookup_tty(driver, filp, index);
if (tty)
retval = tty_reopen(tty);
else
tty = tty_init_dev(driver, index);
tty = alloc_tty_struct(driver, idx);
tty->driver = driver;
tty->ops = driver->ops;
tty->index = idx;
retval = tty_driver_install_tty(driver, tty);
// vt.c con_ops跟uart_ops是并列的关系
// 为每一个APP分配vc(virtual console)
driver->ops->install(driver, tty)
或
// uart_ops没那么复杂
tty_standard_install
driver->ttys[tty->index] = tty;
retval = tty_ldisc_setup(tty, tty->link);
tty_ldisc_open(tty, tty->ldisc);
ret = ld->ops->open(tty);
if (tty->ops->open)
// 操作硬件, uart_ops.open, uart_open
// vt.c con_ops跟uart_ops是并列的关系,它更复杂: 为每一个APP分配vc(virtual console)
retval = tty->ops->open(tty, filp);
tty_port_open
int retval = port->ops->activate(port, tty);
if (filp->f_mode & FMODE_READ)
__proc_set_tty(tty);
current->signal->tty = tty_kref_get(tty);
5. tty_read
文件:driversttytty_io.c
5.1 ldisc read
文件:driversttyn_tty.c
函数:n_tty_read
copy_from_read_buf
const unsigned char *from = read_buf_addr(ldata, tail);
return &ldata->read_buf[i & (N_TTY_BUF_SIZE - 1)];
retval = copy_to_user(*b, from, n);
5.2 怎么得到数据的?来自中断?
文件:driversttyserialimx.c
函数:imx_rxint
, 数据存入对应的tty_port,也就是每个tty_port对应一个串口
imx_rxint
struct imx_port *sport = dev_id;
struct tty_port *port = &sport->port.state->port;
tty_insert_flip_char(port, rx, flg) // 只是存入tty_port->buf.tail里
tty_flip_buffer_push(port); // 通知ldisc处理
// includelinuxtty_flip.h
static inline int tty_insert_flip_char(struct tty_port *port,
unsigned char ch, char flag)
{
struct tty_buffer *tb = port->buf.tail; // Active buffer
// 难道这个buffer会改变?有多个buffer?
int change;
change = (tb->flags & TTYB_NORMAL) && (flag != TTY_NORMAL);
if (!change && tb->used < tb->size) {
if (~tb->flags & TTYB_NORMAL)
*flag_buf_ptr(tb, tb->used) = flag;
*char_buf_ptr(tb, tb->used ) = ch;
return 1;
}
return __tty_insert_flip_char(port, ch, flag);
}
// driversttytty_buffer.c
/**
* tty_flip_buffer_push - terminal
* @port: tty port to push
*
* Queue a push of the terminal flip buffers to the line discipline.
* Can be called from IRQ/atomic context.
*
* In the event of the queue being busy for flipping the work will be
* held off and retried later.
*/
void tty_flip_buffer_push(struct tty_port *port)
{
// 之前INIT_WORK(&buf->work, flush_to_ldisc);
// 导致flush_to_ldisc被调用
tty_schedule_flip(port);
}
// driversttytty_buffer.c
flush_to_ldisc
// 示例ldisc的函数处理后,放入ldata->read_buf
// struct n_tty_data *ldata = tty->disc_data;
struct tty_port *port = container_of(work, struct tty_port, buf.work);
struct tty_bufhead *buf = &port->buf;
struct tty_buffer *head = buf->head; // 唤醒buffer头部
struct tty_struct *tty;
struct tty_ldisc *disc;
tty = READ_ONCE(port->itty);
disc = tty_ldisc_ref(tty);
count = receive_buf(disc, head, count);
unsigned char *p = char_buf_ptr(head, head->read);
tty_ldisc_receive_buf(ld, p, f, count);
// driversttytty_buffer.c
int tty_ldisc_receive_buf(struct tty_ldisc *ld, unsigned char *p,
char *f, int count)
{
if (ld->ops->receive_buf2)
count = ld->ops->receive_buf2(ld->tty, p, f, count);
else {
count = min_t(int, count, ld->tty->receive_room);
if (count && ld->ops->receive_buf)
ld->ops->receive_buf(ld->tty, p, f, count);
}
return count;
}
// driversttyn_tty.c
n_tty_receive_buf2 // 从tty_port->buf.head中得到数据,经ldisc处理,放入tty->disc_data->read_buf
n_tty_receive_buf_common
__receive_buf(tty, cp, fp, n);
n_tty_receive_buf_standard(tty, cp, fp, count); // 回显、退格....
struct n_tty_data *ldata = tty->disc_data;
n_tty_receive_char_inline(tty, c);
put_tty_queue(c, ldata);
*read_buf_addr(ldata, ldata->read_head) = c;
ldata->read_head ;
一个tty_port->buf.tail确实指向Active buffer,在当前buffer不够用、分配新的buffer时,tail才切换到新buffer。
一个tty_port确实只有一个buffer!
6. tty_write
7. 不同的tty设备
/dev/ttymxc0、/dev/tty0、/dev/tty1、/dev/tty这些设备的主、次设备号是:
代码语言:javascript复制crw------- 1 root root 5, 1 Jan 1 00:00 /dev/console
crw-rw-rw- 1 root tty 5, 0 Jan 1 00:00 /dev/tty
crw--w---- 1 root tty 4, 0 Jan 1 00:00 /dev/tty0
crw--w---- 1 root tty 4, 1 Jan 1 00:00 /dev/tty1
crw------- 1 root root 207, 16 Jan 1 04:56 /dev/ttymxc0
打开/dev/ttymxc0、/dev/tty0、/dev/tty1、/dev/tty时,对应的都是:tty_fops.open即tty_open
代码语言:javascript复制1. open /dev/tty ((5,0))
// (5,0)对应当前tty,当前tty可能是串口可能是vt
tty_open
tty = tty_open_current_tty(device, filp);
if (device != MKDEV(TTYAUX_MAJOR, 0)) // (5,0)对应当前tty
return NULL;
tty = get_current_tty(); // open /dev/tty前,如果没有open过其他TTY则失败
if (!tty)
return ERR_PTR(-ENXIO);
2. open /dev/tty0 (4,0)
// vt.c中console_driver->major=4,console_driver->minor_start = 1;
// (4,0)对应的是console_driver.ttys[fg_console]
// (4,1)对应的是console_driver.ttys[0]
// (4,2)对应的是console_driver.ttys[1]
tty_open
tty = tty_open_by_driver(device, inode, filp);
struct tty_driver *driver = NULL;
driver = tty_lookup_driver(device, filp, &index);
case MKDEV(TTY_MAJOR, 0): {
extern struct tty_driver *console_driver;
driver = tty_driver_kref_get(console_driver);
*index = fg_console;
break;
}
3. open /dev/tty1 (4,1)
// (4,1)对应的是console_driver.ttys[0]
// (4,2)对应的是console_driver.ttys[1]
tty_open
tty = tty_open_by_driver(device, inode, filp);
struct tty_driver *driver = NULL;
driver = tty_lookup_driver(device, filp, &index);
get_tty_driver(dev_t device, int *index)
struct tty_driver *p;
dev_t base = MKDEV(p->major, p->minor_start);
*index = device - base;
4. open /dev/console (5,1)
对应console_fops.open, 还是tty_open
tty_open
tty = tty_open_by_driver(device, inode, filp);
struct tty_driver *driver = NULL;
driver = tty_lookup_driver(device, filp, &index);
case MKDEV(TTYAUX_MAJOR, 1): {
struct tty_driver *console_driver = console_device(index);
if (console_driver) {
driver = tty_driver_kref_get(console_driver);
if (driver) {
/* Don't let /dev/console block */
filp->f_flags |= O_NONBLOCK;
break;
}
}
return ERR_PTR(-ENODEV);
}
// 重点在于:console_device
// kernelprintkprintk.c
/*
* Return the console tty driver structure and its associated index
*/
struct tty_driver *console_device(int *index)
{
struct console *c;
struct tty_driver *driver = NULL;
console_lock();
for_each_console(c) {
if (!c->device)
continue;
driver = c->device(c, index); // imx_console/vt_console_driver都会成功,是谁?
if (driver)
break;
}
console_unlock();
return driver;
}
// 问题: register_console注册了那么多的console,/dev/console对应谁?
// bootargs中可以有多个:console=/dev/tty console=/dev/ttySAC0
// 我们在APP中使用/dev/console时,就是去console_drivers链表中找到tty_driver
// console_drivers链表中有那么多tty_driver,选谁?选第1个!第1个是谁?
// register_console时,谁排在console_drivers链表的第1位?
// 处理bootargs中的信息时,最后一个"console=xxx"就排在console_drivers链表第1位
// /dev/console对应的是bootargs中最后一个"console=xxx"
8. register_console
register_console是理解printk、/dev/console的关键。
串口、vt.c,都会register_console,那么谁放在console_drivers链表的第1位?
文件:kernelprintkprintk.c
register_console(struct console *newcon)
// 1. 如果注册过,就不要重复注册了
if (console_drivers)
for_each_console(bcon)
if (WARN(bcon == newcon,
"console '%s%d' already registeredn",
bcon->name, bcon->index))
return;
// 2. 有两类console:bootconsoles(用于earyly_printk)、real consoles(bootconsole之外)
// bootconsoles用于早期的打印,可以注册多个bootconsoles
// 但是一旦注册real console后,就无法再注册bootconsoles,并且会unregister所有的bootconsoles
/* 如果已经注册了real console,那么就不能注册bootconsole
* before we register a new CON_BOOT console, make sure we don't
* already have a valid console
*/
if (console_drivers && newcon->flags & CON_BOOT) {
/* find the last or real console */
for_each_console(bcon) {
if (!(bcon->flags & CON_BOOT)) {
pr_info("Too late to register bootconsole %s%dn",
newcon->name, newcon->index);
return;
}
}
}
// 现在都是boot console ?
if (console_drivers && console_drivers->flags & CON_BOOT)
bcon = console_drivers;
// selected_console对应的是bootargs中最后一个"console=xxx"
if (preferred_console < 0 || bcon || !console_drivers)
preferred_console = selected_console;
// 如果你都没有在bootargs中指定"console=xxx",那我们就使用第1个注册的console
/*
* See if we want to use this console driver. If we
* didn't select a console we take the first one
* that registers here.
*/
if (preferred_console < 0) {
if (newcon->index < 0)
// newcon->index本意是:你想用这个tty_driver的哪个port作为console?
// 没设置的话,就设为0
newcon->index = 0;
if (newcon->setup == NULL ||
newcon->setup(newcon, NULL) == 0) {
newcon->flags |= CON_ENABLED;
if (newcon->device) {
newcon->flags |= CON_CONSDEV;
preferred_console = 0;
}
}
}
/*
* See if this console matches one we selected on
* the command line.
*/
// bootargs中指定"console=/dev/ttySAC0 console=/dev/tty"
// 注册console时,如果跟bootargs中某项匹配的话,如下设置
if (newcon->setup &&
newcon->setup(newcon, c->options) != 0) // 初始化
break;
newcon->flags |= CON_ENABLED;
if (i == selected_console) { // 如果跟最后一项"console=xxx"匹配的话
newcon->flags |= CON_CONSDEV;
preferred_console = selected_console;
}
// 对应bootargs中没有使用"console=xxx"指定的console,不注册
if (!(newcon->flags & CON_ENABLED))
return;
/*
* If we have a bootconsole, and are switching to a real console,
* don't print everything out again, since when the boot console, and
* the real console are the same physical device, it's annoying to
* see the beginning boot messages twice
*/
// 早期打印的信息,就不要在newcon上再打印一次了
if (bcon && ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV))
newcon->flags &= ~CON_PRINTBUFFER;
/*
* Put this console in the list - keep the
* preferred driver at the head of the list.
*/
// 如果在bootargs中指定了"console=xxx console=yyy"
// 1. 第一次注册console时,它当然在console_drivers链表的头部
// 2. bootargs中最后一个console(flag为CON_CONSDEV)放到console_drivers链表的头部
console_lock();
if ((newcon->flags & CON_CONSDEV) || console_drivers == NULL) {
newcon->next = console_drivers;
console_drivers = newcon;
if (newcon->next)
newcon->next->flags &= ~CON_CONSDEV;
} else {
newcon->next = console_drivers->next;
console_drivers->next = newcon;
}
if (newcon->flags & CON_PRINTBUFFER) {
/*
* console_unlock(); will print out the buffered messages
* for us.
*/
raw_spin_lock_irqsave(&logbuf_lock, flags);
console_seq = syslog_seq;
console_idx = syslog_idx;
console_prev = syslog_prev;
raw_spin_unlock_irqrestore(&logbuf_lock, flags);
/*
* We're about to replay the log buffer. Only do this to the
* just-registered console to avoid excessive message spam to
* the already-registered consoles.
*/
exclusive_console = newcon; // 下面的console_unlock用到
}
console_unlock(); // 把buffered messages在exclusive_console的console上打印出来
console_sysfs_notify();
/*
* By unregistering the bootconsoles after we enable the real console
* we get the "console xxx enabled" message on all the consoles -
* boot consoles, real consoles, etc - this is to ensure that end
* users know there might be something in the kernel's log buffer that
* went to the bootconsole (that they do not see on the real console)
*/
pr_info("%sconsole [%s%d] enabledn",
(newcon->flags & CON_BOOT) ? "boot" : "" ,
newcon->name, newcon->index);
// 已经注册了real console的话,就unregister bootconsole
if (bcon &&
((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV) &&
!keep_bootcon) {
/* We need to iterate through all boot consoles, to make
* sure we print everything out, before we unregister them.
*/
for_each_console(bcon)
if (bcon->flags & CON_BOOT)
unregister_console(bcon);
}