IMX6DL 串口 open 操作过程分析

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(&current->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(&current->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件事:

  1. 分配 struct tty_file_private , 并 link 到 struct file *filp->private_data。
  2. 调用 tty_open_current_tty() , 打开当前 tty 设备,如果打开的设备不是 /dev/tty , 返回 NULL 。
  3. 如果上述函数返回 NULL , 则根据设备号 dev_t 在driver 链表中找到当前 dev_t 对应的 driver 。
  4. 根据 tty driver 找到对应的 tty struct 。
  5. 若 struct tty 存在, 则 reopen tty struct 。
  6. 若不在,调用 tty_init_dev() alloc tty struct 。
  7. 最后,通过 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件事:

  1. 调用 alloc_tty_struct() 分配 struct tty ,并初始化线路规则 (line displine) 以及其他结构体成员。
  2. 调用 tty_driver_install_tty() , link tty struct to tty driver 。
  3. 调用 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件事:

  1. 调用 tty_port_tty_set() , 目前搞不懂。
  2. 调用 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;
}
  1. 调用 uart_change_pm() ,改变 uart 的 power management state 。
  2. 调用 get_zeroed_page() ,分配一页内存给发送 buff 。
  3. 通过调用 uport->ops->startup 最终调用 imx_startup() 。
  4. imx_startup() 函数与硬件强相关,完成了硬件的初始化工作,并申请了中断处理例程。需要注意的是,硬件此时只打开了接收中断。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值