tty_driver-open

1. tty_init

作为字符设备的一员, tty 设备的初始化函数 tty_initdrivers/char/mem.c 中的 chr_dev_init 调用。后者声明为 fs_initcall(chr_dev_init);

tty_init 的详细说明如下:

int __init tty_init(void)
{
    // 初始化 tty 字符设备,绑定文件操作结构体
	cdev_init(&tty_cdev, &tty_fops);
	if (cdev_add(&tty_cdev, MKDEV(TTYAUX_MAJOR, 0), 1) ||
	    register_chrdev_region(MKDEV(TTYAUX_MAJOR, 0), 1, "/dev/tty") < 0)
		panic("Couldn't register /dev/tty driver\n");
	device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 0), NULL, "tty");

    // 初始化 console 字符设备,绑定文件操作结构体
	cdev_init(&console_cdev, &console_fops);
	if (cdev_add(&console_cdev, MKDEV(TTYAUX_MAJOR, 1), 1) ||
	    register_chrdev_region(MKDEV(TTYAUX_MAJOR, 1), 1, "/dev/console") < 0)
		panic("Couldn't register /dev/console driver\n");
	consdev = device_create_with_groups(tty_class, NULL,
					    MKDEV(TTYAUX_MAJOR, 1), NULL,
					    cons_dev_groups, "console");
	if (IS_ERR(consdev))
		consdev = NULL;

#ifdef CONFIG_VT
	vty_init(&console_fops);
#endif
	return 0;
}

2. tty_register_driver

uart_register_driver 初始化一个 struct tty_driver 后,调用 tty_register_driver 注册。

int tty_register_driver(struct tty_driver *driver)
{
	int error;
	int i;
	dev_t dev;
	struct device *d;

	// 如果注册的 driver 没有主设备号,分配一个空闲的
	if (!driver->major) {
		error = alloc_chrdev_region(&dev, driver->minor_start,
						driver->num, driver->name);
		if (!error) {
			driver->major = MAJOR(dev);
			driver->minor_start = MINOR(dev);
		}
	} else {
        // major: 4 , minor_start: 64 , 4 << 20 | 64 
		dev = MKDEV(driver->major, driver->minor_start);
        // driver->num: 48, driver->name: "ttyS"
		error = register_chrdev_region(dev, driver->num, driver->name);
	}
	if (error < 0)
		goto err;

    // uart_register_driver 调用时条件不成立
	if (driver->flags & TTY_DRIVER_DYNAMIC_ALLOC) {
		error = tty_cdev_add(driver, dev, 0, driver->num);
		if (error)
			goto err_unreg_char;
	}

	mutex_lock(&tty_mutex);
    // 将当前 driver 添加到 tty_drivers 列表
	list_add(&driver->tty_drivers, &tty_drivers);
	mutex_unlock(&tty_mutex);

    // uart_register_driver 调用时成立
	if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {
		for (i = 0; i < driver->num; i++) {
			d = tty_register_device(driver, i, NULL);
			if (IS_ERR(d)) {
				error = PTR_ERR(d);
				goto err_unreg_devs;
			}
		}
	}
	proc_tty_register_driver(driver);
	driver->flags |= TTY_DRIVER_INSTALLED;
	return 0;

err_unreg_devs:
	for (i--; i >= 0; i--)
		tty_unregister_device(driver, i);

	mutex_lock(&tty_mutex);
	list_del(&driver->tty_drivers);
	mutex_unlock(&tty_mutex);

err_unreg_char:
	unregister_chrdev_region(dev, driver->num);
err:
	return error;
}
EXPORT_SYMBOL(tty_register_driver);

2.1. tty_register_device

tty_register_driver 执行时,如果 tty_driver 设置了 TTY_DRIVER_DYNAMIC_DEV ,即动态分配设备标志,就会调用 tty_register_device 48 次。

后者的主要功能通过 tty_register_device_attr 实现:

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 ktermios *tp;
	struct device *dev;
	int retval;

	if (index >= driver->num) {
		pr_err("%s: Attempt to register invalid tty line number (%d)\n",
		       driver->name, index);
		return ERR_PTR(-EINVAL);
	}

	if (driver->type == TTY_DRIVER_TYPE_PTY)
		pty_line_name(driver, index, name);
	else	// name = "ttySn"
		tty_line_name(driver, index, name);

	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
	if (!dev)
		return ERR_PTR(-ENOMEM);

	dev->devt = devt;
	dev->class = tty_class;
	dev->parent = device;
	dev->release = tty_device_create_release;
	dev_set_name(dev, "%s", name);
	dev->groups = attr_grp;
	dev_set_drvdata(dev, drvdata);

	dev_set_uevent_suppress(dev, 1);

	retval = device_register(dev);
	if (retval)
		goto err_put;

	// serial8250 调用时条件成立
	if (!(driver->flags & TTY_DRIVER_DYNAMIC_ALLOC)) {
		/*
		 * Free any saved termios data so that the termios state is
		 * reset when reusing a minor number.
		 */
		tp = driver->termios[index];
		if (tp) {
			driver->termios[index] = NULL;
			kfree(tp);
		}

		// 初始化 driver->cdevs ,即分配字符设备
		retval = tty_cdev_add(driver, devt, index, 1);
		if (retval)
			goto err_del;
	}

	dev_set_uevent_suppress(dev, 0);
	kobject_uevent(&dev->kobj, KOBJ_ADD);

	return dev;

err_del:
	device_del(dev);
err_put:
	put_device(dev);

	return ERR_PTR(retval);
}
EXPORT_SYMBOL_GPL(tty_register_device_attr);

3. open 举例

strace 的输出来看, Linux 命令 cat 调用 openat 系统调用,最终调用设备文件的 open 函数。

文件打开成功后调用 read 函数,读取打开的文件。

3.1. cat /dev/ttyS1

设备文件 /dev/ttyS1 通过 tty_register_device_attr 函数中的 dev_t devt = MKDEV(driver->major, driver->minor_start) + index; 创建。

对于 tty driver serial , major 为 TTY_MAJOR , minor_start 为 64 ,所以 /dev/ttyS1 的设备号为 ( 4 << 20 | 64 ) + 1 ,即 0x400041 。

tty_open 执行时, tty_open_current_tty 返回 NULL ,执行 tty_open_by_driver

tty_open_by_driver 调用 tty_lookup_driver -> get_tty_driver ,根据主次设备号的范围从 tty_drivers 找到匹配的 tty driver —— 即由 serial8250_init 注册的 tty driver 。

tty_lookup_driver 返回后, tty_open_by_driver 接着调用 tty_driver_lookup_tty ,由于 lookup 函数未定义,返回 drivers->ttys[idx]
而这个变量未初始化,因此 tty_open_by_driver 执行 tty_init_dev 创建一个 struct tty_struct 对象,绑定到 drivers->ttys[idx]

tty_open_by_driver 返回后,依次调用 uart_open -> tty_port_open

3.1.1. tty_port_open

tty_port_open 接收三个参数,第三个参数 filptty_port_block_til_ready 使用,判断当前的文件打开操作是否可以阻塞。

int tty_port_open(struct tty_port *port, struct tty_struct *tty,
							struct file *filp)
{
	spin_lock_irq(&port->lock);

	// 增加引用计数
	++port->count;
	spin_unlock_irq(&port->lock);

	// port->tty = tty
	tty_port_tty_set(port, tty);

	/*
	 * Do the device-specific open only if the hardware isn't
	 * already initialized. Serialize open and shutdown using the
	 * port mutex.
	 */

	mutex_lock(&port->mutex);

	// port 没有进行初始化
	if (!tty_port_initialized(port)) {
		clear_bit(TTY_IO_ERROR, &tty->flags);
		/*
		 * uart_port_activate -> uart_startup -> uart_port_startup
		 * uart_port_startup 执行的操作有:
		 * 1. uart_change_pm 设置为 poweron 状态
		 * 2. 如果 state->xmit.buf 未初始化,分配一个 page
		 * 3. 调用 serial8250_startup -> serial8250_do_startup
		 */
		if (port->ops->activate) {
			int retval = port->ops->activate(port, tty);
			if (retval) {
				mutex_unlock(&port->mutex);
				return retval;
			}
		}
		// 设置已初始化标志
		tty_port_set_initialized(port, 1);
	}
	mutex_unlock(&port->mutex);
	// tty_port_block_til_ready 是 tty_open 的等待逻辑
	return tty_port_block_til_ready(port, tty, filp);
}

EXPORT_SYMBOL(tty_port_open);

3.1.2. serial8250_do_startup

针对 serial8250 设备,打开文件的操作最终通过 serial8250_do_startup 完成。

整个函数代码很多,去掉和 16550A 无关的部分:

int serial8250_do_startup(struct uart_port *port)
{
	struct uart_8250_port *up = up_to_u8250p(port);
	unsigned long flags;
	unsigned char lsr, iir;
	int retval;

	if (!port->fifosize)
		port->fifosize = uart_config[port->type].fifo_size;
	if (!up->tx_loadsz)
		up->tx_loadsz = uart_config[port->type].tx_loadsz;
	if (!up->capabilities)
		up->capabilities = uart_config[port->type].flags;
	up->mcr = 0;

	if (port->iotype != up->cur_iotype)
		set_io_from_upio(port);

	serial8250_rpm_get(up);
	
	/*
	 * Clear the FIFO buffers and disable them.
	 * (they will be reenabled in set_termios())
	 */
	serial8250_clear_fifos(up);

	/*
	 * Clear the interrupt registers.
	 */
	serial_port_in(port, UART_LSR);
	serial_port_in(port, UART_RX);
	serial_port_in(port, UART_IIR);
	serial_port_in(port, UART_MSR);
	
	/*
	 * At this point, there's no way the LSR could still be 0xff;
	 * if it is, then bail out, because there's likely no UART
	 * here.
	 */
	if (!(port->flags & UPF_BUGGY_UART) &&
	    (serial_port_in(port, UART_LSR) == 0xff)) {
		printk_ratelimited(KERN_INFO "ttyS%d: LSR safety check engaged!\n",
				   serial_index(port));
		retval = -ENODEV;
		goto out;
	}

	/*
	 * irq 测试:
	 * 1. 等待 LSR_THRI 置位,最多 10ms
	 * 2. 置位 IER_THRI ,等待 1us
	 * 3. 读取 IIR ,应该有发送中断产生
	 * 4. 设置 IER 为 0 ,关闭所有中断
	 * 5. 置位 IER_THRI ,等待 1us
	 * 6. 再次读取 IIR 
	 * 7. 设置 IER 为 0 ,关闭所有中断
	 * 8. 如果两次读取 IIR 都没有中断,设置 up->bugs |= UART_BUG_THRE
	 */ 
	if (port->irq && !(up->port.flags & UPF_NO_THRE_TEST)) {
		unsigned char iir1;
		/*
		 * Test for UARTs that do not reassert THRE when the
		 * transmitter is idle and the interrupt has already
		 * been cleared.  Real 16550s should always reassert
		 * this interrupt whenever the transmitter is idle and
		 * the interrupt is enabled.  Delays are necessary to
		 * allow register changes to become visible.
		 */
		spin_lock_irqsave(&port->lock, flags);
		if (up->port.irqflags & IRQF_SHARED)
			disable_irq_nosync(port->irq);

		wait_for_xmitr(up, UART_LSR_THRE);
		serial_port_out_sync(port, UART_IER, UART_IER_THRI);
		udelay(1); /* allow THRE to set */
		iir1 = serial_port_in(port, UART_IIR);
		serial_port_out(port, UART_IER, 0);
		serial_port_out_sync(port, UART_IER, UART_IER_THRI);
		udelay(1); /* allow a working UART time to re-assert THRE */
		iir = serial_port_in(port, UART_IIR);
		serial_port_out(port, UART_IER, 0);

		if (port->irqflags & IRQF_SHARED)
			enable_irq(port->irq);
		spin_unlock_irqrestore(&port->lock, flags);

		/*
		 * If the interrupt is not reasserted, or we otherwise
		 * don't trust the iir, setup a timer to kick the UART
		 * on a regular basis.
		 */
		if ((!(iir1 & UART_IIR_NO_INT) && (iir & UART_IIR_NO_INT)) ||
		    up->port.flags & UPF_BUG_THRE) {
			up->bugs |= UART_BUG_THRE;
		}
	}

	/*
	 * univ8250_setup_irq :
	 * 如果 up->bugs & UART_BUG_THRE ,设置 
	 * up->timer.function = serila8250_backup_timeout ;
	 * 如果 up->port.irq == 0 , mod_timer ;
	 * 否则调用 serial_link_irq_chain 注册中断处理函数
	 */
	retval = up->ops->setup_irq(up);
	if (retval)
		goto out;

	/*
	 * Now, initialize the UART
	 */
	serial_port_out(port, UART_LCR, UART_LCR_WLEN8);

	spin_lock_irqsave(&port->lock, flags);
	if (up->port.flags & UPF_FOURPORT) {
		if (!up->port.irq)
			up->port.mctrl |= TIOCM_OUT1;
	} else
		/*
		 * Most PC uarts need OUT2 raised to enable interrupts.
		 */
		if (port->irq)
			up->port.mctrl |= TIOCM_OUT2;

	serial8250_set_mctrl(port, port->mctrl);

	/*
	 * Serial over Lan (SoL) hack:
	 * Intel 8257x Gigabit ethernet chips have a 16550 emulation, to be
	 * used for Serial Over Lan.  Those chips take a longer time than a
	 * normal serial device to signalize that a transmission data was
	 * queued. Due to that, the above test generally fails. One solution
	 * would be to delay the reading of iir. However, this is not
	 * reliable, since the timeout is variable. So, let's just don't
	 * test if we receive TX irq.  This way, we'll never enable
	 * UART_BUG_TXEN.
	 */
	if (up->port.quirks & UPQ_NO_TXEN_TEST)
		goto dont_test_tx_en;

	/*
	 * Do a quick test to see if we receive an interrupt when we enable
	 * the TX irq.
	 */
	serial_port_out(port, UART_IER, UART_IER_THRI);
	lsr = serial_port_in(port, UART_LSR);
	iir = serial_port_in(port, UART_IIR);
	serial_port_out(port, UART_IER, 0);

	// 发送缓冲区为空,置位 THRI 没有产生中断,设置 UART_BUG_TXEN 标志
	if (lsr & UART_LSR_TEMT && iir & UART_IIR_NO_INT) {
		if (!(up->bugs & UART_BUG_TXEN)) {
			up->bugs |= UART_BUG_TXEN;
			pr_debug("ttyS%d - enabling bad tx status workarounds\n",
				 serial_index(port));
		}
	} else {
		up->bugs &= ~UART_BUG_TXEN;
	}

dont_test_tx_en:
	spin_unlock_irqrestore(&port->lock, flags);

	/*
	 * Clear the interrupt registers again for luck, and clear the
	 * saved flags to avoid getting false values from polling
	 * routines or the previous session.
	 */
	serial_port_in(port, UART_LSR);
	serial_port_in(port, UART_RX);
	serial_port_in(port, UART_IIR);
	serial_port_in(port, UART_MSR);
	up->lsr_saved_flags = 0;
	up->msr_saved_flags = 0;

	/*
	 * Request DMA channels for both RX and TX.
	 */
	if (up->dma) {
		retval = serial8250_request_dma(up);
		if (retval) {
			pr_warn_ratelimited("ttyS%d - failed to request DMA\n",
					    serial_index(port));
			up->dma = NULL;
		}
	}

	/*
	 * Set the IER shadow for rx interrupts but defer actual interrupt
	 * enable until after the FIFOs are enabled; otherwise, an already-
	 * active sender can swamp the interrupt handler with "too much work".
	 */
	up->ier = UART_IER_RLSI | UART_IER_RDI;

	retval = 0;
out:
	serial8250_rpm_put(up);
	return retval;
}
EXPORT_SYMBOL_GPL(serial8250_do_startup);

3.1.2.1. univ8250_setup_irq

univ8250_setup_irq 设置 8250 的中断系统。

如果设置了 UART_BUG_THRE 标志,表明在 serial8250_do_startup 函数里测试中断时异常,改用 timer 系统:

up->timer.function = serial8250_backup_timeout;
mod_timer(&up->timer, jiffies +
		uart_poll_timeout(port) + HZ / 5);

如果 port 使用的 irq 为 0 ,执行 mod_timer(&up->timer, jiffies + uart_poll_timeout(port));

否则调用 serial_link_irq_chain 注册中断处理函数到 irq 。

3.1.3. tty_port_block_til_ready

tty_port_open 执行的最后直接调用 tty_port_block_til_ready ,完成后返回。

tty_port_block_til_ready 把当前进程添加到等待队列,设置为 TASK_INTERRUPTIBLE 状态,执行调度程序,转而执行其他进程,等待被唤醒。

如果设置了 CLOCAL 标志,即忽略 modem control line ,直接退出循环,结束等待。

int tty_port_block_til_ready(struct tty_port *port,
				struct tty_struct *tty, struct file *filp)
{
	int do_clocal = 0, retval;
	unsigned long flags;

	// 定义一个 struct wiat_queue_entry wait , autoremove_wake_function
	DEFINE_WAIT(wait);

	/* if non-blocking mode is set we can pass directly to open unless
	   the port has just hung up or is in another error state */
	// uart_startup 执行时, uart_port_startup 执行失败,会设置 TTY_IO_ERROR
	if (tty_io_error(tty)) {
		tty_port_set_active(port, 1);
		return 0;
	}
	// 打开 /dev/console 和 /dev/tty 设置 O_NONBLOCK 标志
	if (filp == NULL || (filp->f_flags & O_NONBLOCK)) {
		/* Indicate we are open */
		if (C_BAUD(tty))	// 表明设置了波特率
			// uart_dtr_rts(port, 1)
			tty_port_raise_dtr_rts(port);
		tty_port_set_active(port, 1);
		return 0;
	}

	// 忽略 modem control line
	if (C_CLOCAL(tty))
		do_clocal = 1;

	/* Block waiting until we can proceed. We may need to wait for the
	   carrier, but we must also wait for any close that is in progress
	   before the next open may complete */

	retval = 0;

	/* The port lock protects the port counts */
	spin_lock_irqsave(&port->lock, flags);
	port->count--;
	port->blocked_open++;
	spin_unlock_irqrestore(&port->lock, flags);

	while (1) {
		/* Indicate we are open */
		if (C_BAUD(tty) && tty_port_initialized(port))
			tty_port_raise_dtr_rts(port);

		/*
		 * 添加 wait 到等待队列 port->open_wait 中,设置当前进程的状态
		 * 为 TASK_INTERRUPTIBLE
		 */
		prepare_to_wait(&port->open_wait, &wait, TASK_INTERRUPTIBLE);
		/* Check for a hangup or uninitialised port.
							Return accordingly */
		if (tty_hung_up_p(filp) || !tty_port_initialized(port)) {
			if (port->flags & ASYNC_HUP_NOTIFY)
				retval = -EAGAIN;
			else
				retval = -ERESTARTSYS;
			break;
		}
		/*
		 * Probe the carrier. For devices with no carrier detect
		 * tty_port_carrier_raised will always return true.
		 * Never ask drivers if CLOCAL is set, this causes troubles
		 * on some hardware.
		 */
		// tty_port_carrier_raised -> uart_carrier_raised
		if (do_clocal || tty_port_carrier_raised(port))
			break;
		if (signal_pending(current)) {
			retval = -ERESTARTSYS;
			break;
		}
		tty_unlock(tty);
		schedule();
		tty_lock(tty);
	}
	// 设置当前进程为 TASK_RUNNING ,从等待队列移除 wait
	finish_wait(&port->open_wait, &wait);

	/* Update counts. A parallel hangup will have set count to zero and
	   we must not mess that up further */
	spin_lock_irqsave(&port->lock, flags);
	if (!tty_hung_up_p(filp))
		port->count++;
	port->blocked_open--;
	spin_unlock_irqrestore(&port->lock, flags);
	if (retval == 0)
		tty_port_set_active(port, 1);
	return retval;
}
EXPORT_SYMBOL(tty_port_block_til_ready);

3.2. cat /dev/tty

tty_open 执行时, tty_open_current_tty 调用 get_current_tty ,如果获取当前进程的 tty 失败,返回 -ENXIO ;否则设置 filp->f_flags |= O_NONBLOCK ,放置 /dev/tty 阻塞,然后调用 tty_reopen

3.3. cat /dev/tty1

tty_open 执行时, tty_open_current_tty 返回 NULL ,执行 tty_open_by_driver

tty_open_by_driver 调用 tty_lookup_driver ,后者执行的分支为:

// 打开的设备文件是 /dev/tty0 ,直接返回 vt.c 中的 console_driver
#ifdef CONFIG_VT
	case MKDEV(TTY_MAJOR, 0): {
		extern struct tty_driver *console_driver;
		driver = tty_driver_kref_get(console_driver);
		*index = fg_console;
		break;
	}
#endif

default:
		// 根据设备号匹配 tty_driver , /dev/ttyn 设备返回 console_driver
		driver = get_tty_driver(device, index);
		if (!driver)
			return ERR_PTR(-ENODEV);
		break;
	}
	return driver;

找到设备文件使用的驱动后,调用 tty = tty_driver_lookup_tty(driver, filp, index); 获取对应的 tty struct ,对于 /dev/ttyn 设备, vty_init 调用 alloc_tty_driver 时分配的 console_driver->ttys 没有初始化,因此 tty_open_by_driver 会执行 tty_init_dev

tty_init_dev 会初始化一个 struct tty_struct 对象,绑定到传入的 struct tty_driver ,函数会调用很多函数设置一些重要的成员变量,包括:

struct tty_struct {
	// 以下变量由 alloc_tty_struct 设置
	// ldisc = tty_ldisc[N_TTY]
	struct tty_ldisc *ldisc;

	// driver = console_driver , vt.c
	struct tty_driver *driver;

	// ops = console_driver->ops = con_ops , vt.c
	struct tty_operations *ops;
	
	// name = ttyn
	char name[64];

	// index = idx
	int index;

	
	// 以下变量由 tty_driver_install_tty 设置
	// termios = console_driver->init_termios = tty_std_termios
	struct ktermios termios;

	// count ++
	int count;

	// 还会设置 console_driver->ttys[tty->index] = tty


	// 以下变量由 tty_ldisc_setup -> tty_ldisc_open -> n_tty_open 设置
	/* 
	 * disc_data = ldata;
	 * 初始化 struct n_tty_data *ldata 的所有成员
	 * 调用 n_tty_set_termios 设置 tty 参数
	 * 如果设置了 TTY_THROTTLED ,调用 tty_driver->ops->unthrottle
	 */
	void *disc_data;
}

至此,设备文件 /dev/tty1 的驱动准备就绪,需要的各种操作函数指针完成设置。

3.4. cat /dev/console

如果打开的文件是 /dev/console ,调用的函数和 /dev/ttyS1 类似。

tty_open 执行时, tty_open_current_tty 返回 NULL ,执行 tty_open_by_driver

tty_open_by_driver 调用 tty_lookup_driver ,后者执行的分支为:

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 && filp) {
				/* Don't let /dev/console block */
				filp->f_flags |= O_NONBLOCK;
				break;
			}
		}
		return ERR_PTR(-ENODEV);
	}

console_deviceconsole_drivers 中依次调用已经注册的 console 的 device 方法。针对 univ8250_console ,调用的函数为 uart_console_device

struct tty_driver *uart_console_device(struct console *co, int *index)
{
	// p = &serial8250_reg
	struct uart_driver *p = co->data;

	// *index = 1
	*index = co->index;

	// p->tty_driver = normal , uart_register_driver 初始化
	return p->tty_driver;
}

tty_lookup_driver 返回后, tty_open_by_driver 接着调用 tty_driver_lookup_tty ,由于 lookup 函数未定义,返回 drivers->ttys[idx]

如果这个变量已经初始化 ( 在打开 /dev/console 之前,已经打开过 /dev/ttyS1 ),调用 tty_reopen 。否则和 cat /dev/ttyS1 的执行路径相同。

posted @ 2020-08-07 11:23  glob  阅读(708)  评论(0编辑  收藏  举报