驱动大全之UART子系统

2022-05-09 20:46:11 浏览数 (1)

百问网韦东山的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开始阅读代码。

代码语言:javascript复制
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

代码语言:javascript复制
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对应一个串口

代码语言:javascript复制
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

代码语言:javascript复制
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);
	}

0 人点赞