tty_driver-open
1. tty_init
作为字符设备的一员, tty 设备的初始化函数 tty_init
在 drivers/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
接收三个参数,第三个参数 filp 供 tty_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_device
从 console_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
的执行路径相同。
作者:glob
出处:http://www.cnblogs.com/adera/
欢迎访问我的个人博客:https://blog.globs.site/
本文版权归作者和博客园共有,转载请注明出处。