1 open 操作过程分析
代码平台:IMX6DL
代码入口:tty_open()
1.1 函数调用关系总览
tty_open(struct inode *inode, struct file *filp)
tty_alloc_file(flip) // alloc tty_file_private *priv, link it to file->private_data
tty_open_current_tty() // device is not /dev/tty, retrun NULL
tty_lookup_driver() // find tty driver based on dev_t
tty_driver_lookup_tty() // get tty struct based on tty driver
tty_reopen() // if tty struct exist, reopen tty struct
tty_init_dev() // if tty struct not exist, alloc tty struct
alloc_tty_struct() // alloc tty struct
tty_ldisc_init() // setup the line discipline objects for a newly allocated tty
tty->driver = driver // link tty driver to tty struct
tty->ops = driver->ops // link driver ops to tty struct ops
tty_driver_install_tty() // link tty struct to tty driver
tty_ldisc_setup() // open line discipline
tty_ldisc_open() // open a line discipline, n_tty_open()
tty_add_file() // link file *filp to tty struct
tty->ops->open() // call uart_open()
tty_port_tty_set() // no idea
uart_startup() // start up the serial port
uart_port_startup() // start up the prot
uart_change_pm(state, UART_PM_STATE_ON) // set uart power state
get_zeroed_page() // alloc one page memory for send buf
uport->ops->startup() // call imx_startup()
对 tty 设备的打开操作,首先经过 vfs 层、字符设备层,到达 tty_open() 函数。tty_open() 函数如下:
static int tty_open(struct inode *inode, struct file *filp)
{
struct tty_struct *tty;
int noctty, retval;
struct tty_driver *driver = NULL;
int index;
dev_t device = inode->i_rdev;
unsigned saved_flags = filp->f_flags;
nonseekable_open(inode, filp);
retry_open:
retval = tty_alloc_file(filp); // alloc tty_file_private for file *filp
if (retval)
return -ENOMEM;
noctty = filp->f_flags & O_NOCTTY;
index = -1;
retval = 0;
tty = tty_open_current_tty(device, filp); // device is not /dev/tty , return NULL
if (!tty) {
mutex_lock(&tty_mutex);
driver = tty_lookup_driver(device, filp, &noctty, &index); // find tty driver based on dev_t
if (IS_ERR(driver)) {
retval = PTR_ERR(driver);
goto err_unlock;
}
/* check whether we're reopening an existing tty */
tty = tty_driver_lookup_tty(driver, inode, index); // get tty struct based on tty driver
if (IS_ERR(tty)) {
retval = PTR_ERR(tty);
goto err_unlock;
}
if (tty) { // if tty struct exist
mutex_unlock(&tty_mutex);
tty_lock(tty);
/* safe to drop the kref from tty_driver_lookup_tty() */
tty_kref_put(tty);
retval = tty_reopen(tty); // reopen tty struct
if (retval < 0) {
tty_unlock(tty);
tty = ERR_PTR(retval);
}
} else { /* Returns with the tty_lock held for now */
tty = tty_init_dev(driver, index); // tty struct not exist,alloc tty struct
mutex_unlock(&tty_mutex);
}
tty_driver_kref_put(driver);
}
if (IS_ERR(tty)) {
retval = PTR_ERR(tty);
goto err_file;
}
tty_add_file(tty, filp);
check_tty_count(tty, __func__);
if (tty->driver->type == TTY_DRIVER_TYPE_PTY &&
tty->driver->subtype == PTY_TYPE_MASTER)
noctty = 1;
#ifdef TTY_DEBUG_HANGUP
printk(KERN_DEBUG "%s: opening %s...\n", __func__, tty->name);
#endif
if (tty->ops->open) // call uart_open
retval = tty->ops->open(tty, filp);
else
retval = -ENODEV;
filp->f_flags = saved_flags;
if (retval) { // uasually retval equals to 0
#ifdef TTY_DEBUG_HANGUP
printk(KERN_DEBUG "%s: error %d in opening %s...\n", __func__,
retval, tty->name);
#endif
tty_unlock(tty); /* need to call tty_release without BTM */
tty_release(inode, filp);
if (retval != -ERESTARTSYS)
return retval;
if (signal_pending(current))
return retval;
schedule();
/*
* Need to reset f_op in case a hangup happened.
*/
if (tty_hung_up_p(filp))
filp->f_op = &tty_fops;
goto retry_open;
}
clear_bit(TTY_HUPPED, &tty->flags);
read_lock(&tasklist_lock);
spin_lock_irq(¤t->sighand->siglock);
if (!noctty &&
current->signal->leader &&
!current->signal->tty &&
tty->session == NULL) {
/*
* Don't let a process that only has write access to the tty
* obtain the privileges associated with having a tty as
* controlling terminal (being able to reopen it with full
* access through /dev/tty, being able to perform pushback).
* Many distributions set the group of all ttys to "tty" and
* grant write-only access to all terminals for setgid tty
* binaries, which should not imply full privileges on all ttys.
*
* This could theoretically break old code that performs open()
* on a write-only file descriptor. In that case, it might be
* necessary to also permit this if
* inode_permission(inode, MAY_READ) == 0.
*/
if (filp->f_mode & FMODE_READ)
__proc_set_tty(tty);
}
spin_unlock_irq(¤t->sighand->siglock);
read_unlock(&tasklist_lock);
tty_unlock(tty);
return 0;
err_unlock:
mutex_unlock(&tty_mutex);
/* after locks to avoid deadlock */
if (!IS_ERR_OR_NULL(driver))
tty_driver_kref_put(driver);
err_file:
tty_free_file(filp);
return retval;
}
tty_open() 函数主要做了以下7件事:
- 分配 struct tty_file_private , 并 link 到 struct file *filp->private_data。
- 调用 tty_open_current_tty() , 打开当前 tty 设备,如果打开的设备不是 /dev/tty , 返回 NULL 。
- 如果上述函数返回 NULL , 则根据设备号 dev_t 在driver 链表中找到当前 dev_t 对应的 driver 。
- 根据 tty driver 找到对应的 tty struct 。
- 若 struct tty 存在, 则 reopen tty struct 。
- 若不在,调用 tty_init_dev() alloc tty struct 。
- 最后,通过 tty->ops->open() 调用 uart_open() 函数。
struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx)
{
struct tty_struct *tty;
int retval;
/*
* First time open is complex, especially for PTY devices.
* This code guarantees that either everything succeeds and the
* TTY is ready for operation, or else the table slots are vacated
* and the allocated memory released. (Except that the termios
* and locked termios may be retained.)
*/
if (!try_module_get(driver->owner))
return ERR_PTR(-ENODEV);
tty = alloc_tty_struct(driver, idx); // alloc tty struct and initial it
if (!tty) {
retval = -ENOMEM;
goto err_module_put;
}
tty_lock(tty);
retval = tty_driver_install_tty(driver, tty); // link tty struct to tty driver
if (retval < 0)
goto err_deinit_tty;
if (!tty->port)
tty->port = driver->ports[idx];
WARN_RATELIMIT(!tty->port,
"%s: %s driver does not set tty->port. This will crash the kernel later. Fix the driver!\n",
__func__, tty->driver->name);
tty->port->itty = tty;
/*
* Structures all installed ... call the ldisc open routines.
* If we fail here just call release_tty to clean up. No need
* to decrement the use counts, as release_tty doesn't care.
*/
retval = tty_ldisc_setup(tty, tty->link); //drivers/tty/tty_ldisc.c/line730
if (retval)
goto err_release_tty;
/* Return the tty locked so that it cannot vanish under the caller */
return tty;
err_deinit_tty:
tty_unlock(tty);
deinitialize_tty_struct(tty);
free_tty_struct(tty);
err_module_put:
module_put(driver->owner);
return ERR_PTR(retval);
/* call the tty release_tty routine to clean out this slot */
err_release_tty:
tty_unlock(tty);
printk_ratelimited(KERN_INFO "tty_init_dev: ldisc open failed, "
"clearing slot %d\n", idx);
release_tty(tty, idx);
return ERR_PTR(retval);
}
tty_init_dev() 函数主要完成以下3件事:
- 调用 alloc_tty_struct() 分配 struct tty ,并初始化线路规则 (line displine) 以及其他结构体成员。
- 调用 tty_driver_install_tty() , link tty struct to tty driver 。
- 调用 tty_ldisc_setup() ,此函数最终调用 n_tty_open() 。
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;
pr_debug("uart_open(%d) called\n", line);
spin_lock_irq(&port->lock);
++port->count;
spin_unlock_irq(&port->lock);
/*
* We take the semaphore here to guarantee that we won't be re-entered
* while allocating the state structure, or while we request any IRQs
* that the driver may need. This also has the nice side-effect that
* it delays the action of uart_hangup, so we can guarantee that
* state->port.tty will always contain something reasonable.
*/
if (mutex_lock_interruptible(&port->mutex)) {
retval = -ERESTARTSYS;
goto end;
}
if (!state->uart_port || state->uart_port->flags & UPF_DEAD) {
retval = -ENXIO;
goto err_unlock;
}
tty->driver_data = state;
state->uart_port->state = state;
state->port.low_latency =
(state->uart_port->flags & UPF_LOW_LATENCY) ? 1 : 0;
tty_port_tty_set(port, tty);
/*
* Start up the serial port.
*/
retval = uart_startup(tty, state, 0);
/*
* If we succeeded, wait until the port is ready.
*/
mutex_unlock(&port->mutex);
if (retval == 0)
retval = tty_port_block_til_ready(port, tty, filp);
end:
return retval;
err_unlock:
mutex_unlock(&port->mutex);
goto end;
}
uart_open() 函数主要完成2件事:
- 调用 tty_port_tty_set() , 目前搞不懂。
- 调用 uart_startup() , 该函数最终调用 uart_port_startup() 。
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;
/*
* Make sure the device is in D0 state.
*/
uart_change_pm(state, UART_PM_STATE_ON);
/*
* Initialise and allocate the transmit and temporary
* buffer.
*/
if (!state->xmit.buf) {
/* This is protected by the per port mutex */
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;
}
/*
* Initialise the hardware port settings.
*/
uart_change_speed(tty, state, NULL);
if (init_hw) {
/*
* Setup the RTS and DTR signals once the
* port is open and ready to respond.
*/
if (tty->termios.c_cflag & CBAUD)
uart_set_mctrl(uport, TIOCM_RTS | TIOCM_DTR);
}
}
/*
* This is to allow setserial on this port. People may want to set
* port/irq/type and then reconfigure the port properly if it failed
* now.
*/
if (retval && capable(CAP_SYS_ADMIN))
return 1;
return retval;
}
- 调用 uart_change_pm() ,改变 uart 的 power management state 。
- 调用 get_zeroed_page() ,分配一页内存给发送 buff 。
- 通过调用 uport->ops->startup 最终调用 imx_startup() 。
- imx_startup() 函数与硬件强相关,完成了硬件的初始化工作,并申请了中断处理例程。需要注意的是,硬件此时只打开了接收中断。