Linux串口驱动(3) - open详解

1. 用户空间open的操作实现

        串口设备是被注册为字符设备的,在注册过程中填充了struct file_operations tty_fops结构体,该结构体中的成员open、read、write等就是驱动和应用交互的接口,应用程序打开串口设备时,经过系统调用,最后会调用到tty_fops.open函数,即tty_open。

tty_open()
*************************************** 分析线路1 ***************************************************
	-->tty = tty_open_current_tty(device, filp)  //获取当前设备对应的tty_struct
	-->driver = tty_lookup_driver(device, filp, &noctty, &index);
		-->driver = get_tty_driver(device, index);	  //从全局链表tty_drivers中找出匹配的tty_driver
	-->tty = tty_driver_lookup_tty(driver, inode, index); 
		-->return driver->ttys[idx];			//返回NULL,因为ttys[]数组的填充在后面
*************************************** 分析线路2-1 ***************************************************
	-->tty_init_dev(driver, index);
		-->initialize_tty_struct(tty, driver, idx);
			-->tty_ldisc_init(tty);
				-->ld = tty_ldisc_get(N_TTY);	//获取串口的ldisc
					-->ldops = get_ldops(disc);
						-->ldops = tty_ldiscs[disc];
					-->ld->ops = ldops;
				-->tty->ldisc = ld;		//设置串口的ldisc,后面read/write操作都直接从tty->ldisc获取线路规程
			-->tty->ops = driver->ops;          //这里的ops交接很关键键
*************************************** 分析线路2-2 ***************************************************
		-->tty_driver_install_tty(driver, tty);
			-->tty_standard_install(driver, tty)
				-->tty_init_termios(tty)
				-->driver->ttys[tty->index] = tty;   //填充ttys[]数组
*************************************** 分析线路2-3 ***************************************************
		-->tty_ldisc_setup(tty, tty->link)
			-->tty_ldisc_open(tty, ld);
				-->ld->ops->open(tty)           //即n_tty_open
					-->n_tty_open
						-->n_tty_set_termios
                            -->start_rx_dma  //开启串口接收DMA搬运
    							-->dma_rx_callback
                                    -->dma_rx_work
										-->dma_rx_push_data
*************************************** 分析线路3 ***************************************************
	-->tty->ops->open(tty, filp);      //即uart_open
		-->uart_open
			-->uart_startup
				-->uart_port_startup
					-->uport->ops->startup(uport)    //即imx_startup
						-->imx_startup		
							-->imx_uart_dma_init(sport);    //配置发送消息时DMA搬运的目标地址,接收消息时DMA搬运的源地址
							-->INIT_DELAYED_WORK(&sport->tsk_dma_tx, dma_tx_work);   //DMA搬运线程

2. 具体代码分析

        先找到tty_fops 结构体的定义

//tty_io.c
static const struct file_operations tty_fops = {
	.read		= tty_read,
	.write		= tty_write,
	.open		= tty_open,
	.release	= tty_release,
     ······
};

        然后分析tty_fops.open函数即tty_open()函数

static int tty_open(struct inode *inode, struct file *filp)
{
	struct tty_struct *tty;
   struct tty_driver *driver = NULL;
   
	tty = tty_open_current_tty(device, filp);  /* 得到的tty为NULL */
	if (IS_ERR(tty)) {
		retval = PTR_ERR(tty);
		goto err_unlock;
	} else if (!tty) {
		driver = tty_lookup_driver(device, filp, &noctty, &index);	/*driver等于《Linux串口驱动(1) - serial层》中注册的tty_driver(即normal),并将索引号放到index */
          tty = tty_driver_lookup_tty(driver, inode, index);        //tty=NULL
          if (tty) {								
    		······
    	} else	
		tty = tty_init_dev(driver, index);	/*tty->ops=driver->ops*/
	}
 
	if (tty->ops->open)	/*即uart_open*/
		retval = tty->ops->open(tty, filp);
	
	return 0;
}

        分析线路1 如下:

static struct tty_struct *tty_open_current_tty(dev_t device, struct file *filp)
{
	if (device != MKDEV(TTYAUX_MAJOR, 0))  //串口的主设备号是SERIAL_IMX_MAJOR,这个在《Linux串口驱动(1) - serial层》中有分析到
         return NULL;
         ······
}    

static struct tty_driver *tty_lookup_driver(dev_t device, struct file *filp,
		int *noctty, int *index)
{
	struct tty_driver *driver;

	switch (device) {
	case MKDEV(TTYAUX_MAJOR, 1): {
		······
	}
	default://上面的分支都不符合
		driver = get_tty_driver(device, index);	/* ---> */
		break;
	}
	return driver;
} 

static struct tty_driver *get_tty_driver(dev_t device, int *index)
{
	struct tty_driver *p;

	list_for_each_entry(p, &tty_drivers, tty_drivers) {       //从全局链表tty_drivers中找出匹配的tty_driver,这里找到的应该是《Linux串口驱动(1) - serial层》中注册的uart_ops操作集
		dev_t base = MKDEV(p->major, p->minor_start);
		if (device < base || device >= base + p->num)
			continue;
		*index = device - base;
		return tty_driver_kref_get(p);
	}
	return NULL;
}

static struct tty_struct *tty_driver_lookup_tty(struct tty_driver *driver,
		struct inode *inode, int idx)
{
	if (driver->ops->lookup)	/* tty_operations里面没有lookup这个成员 */
		return driver->ops->lookup(driver, inode, idx);

	return driver->ttys[idx];		/* 返回具体的tty_struct(这个tty_struct在tty_init_dev函数中被放入ttys[]数组) */
}	/* 因为tty_init_dev函数在这之前还未被调用,而且目前为止未发现其他位置初始化tty_struct并放入ttys[],所以这里返回NULL */

        分析线路2 如下:

struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx)
{
	struct tty_struct *tty;
	int retval;

	tty = alloc_tty_struct();
	initialize_tty_struct(tty, driver, idx);   //设置串口的线路规程ldisc
	retval = tty_driver_install_tty(driver, tty);	/* 将tty_struct放到driver->ttys[] */
	retval = tty_ldisc_setup(tty, tty->link);
	return tty;
}

        分析线路2-1 如下:

void initialize_tty_struct(struct tty_struct *tty,
		struct tty_driver *driver, int idx)
{
	tty_ldisc_init(tty);   //取出串口的线路规程指针,即取出tty_ldiscs[N_TTY],N_TTY这一项在前面注册了的,可以回看Linux串口驱动(2) - 线路规程
	init_waitqueue_head(&tty->write_wait);
	init_waitqueue_head(&tty->read_wait);
	INIT_LIST_HEAD(&tty->tty_files);
	INIT_WORK(&tty->SAK_work, do_SAK_work);

    tty->driver = driver;    //把driver放到tty中 
	tty->ops = driver->ops;            //ops的交接,这里很重要,后面会通过tty-ops->open、tty->ops->write来调用uart_open和uart_write
}

void tty_ldisc_init(struct tty_struct *tty)
{
	struct tty_ldisc *ld = tty_ldisc_get(N_TTY);   //获取tty_ldiscs[N_TTY]
	tty->ldisc = ld;      //将取出的线路规程指针赋值给tty->ldisc,后面read/write操作都直接从tty->ldisc获取线路规程
}

static struct tty_ldisc *tty_ldisc_get(int disc)
{
	struct tty_ldisc *ld;
	struct tty_ldisc_ops *ldops;

	ldops = get_ldops(disc);   //获取tty_ldiscs[N_TTY]
	ld = kmalloc(sizeof(struct tty_ldisc), GFP_KERNEL);
	ld->ops = ldops;          //  
	return ld;
}

static struct tty_ldisc_ops *get_ldops(int disc)
{
	unsigned long flags;
	struct tty_ldisc_ops *ldops, *ret;

	ldops = tty_ldiscs[disc];   //获取tty_ldiscs[N_TTY]
	return ret;
} 

        分析线路2-2 如下:

static int tty_driver_install_tty(struct tty_driver *driver,
						struct tty_struct *tty)
{
	return driver->ops->install ? driver->ops->install(driver, tty) :
		tty_standard_install(driver, tty);  /*driver->ops即uart_ops,uart->ops中并没有insrall成员*/
}

int tty_standard_install(struct tty_driver *driver, struct tty_struct *tty)
{
	int ret = tty_init_termios(tty);

	tty_driver_kref_get(driver);     /* 从tty中提取driver */
	tty->count++;
	driver->ttys[tty->index] = tty;		/* 将tty填充到ttys[]数组 */
	return 0;
}

        分析线路2-3 如下:

int tty_ldisc_setup(struct tty_struct *tty, struct tty_struct *o_tty)
{
	struct tty_ldisc *ld = tty->ldisc;
	int retval;

	retval = tty_ldisc_open(tty, ld);
}

static int tty_ldisc_open(struct tty_struct *tty, struct tty_ldisc *ld)
{
	if (ld->ops->open) {
		ret = ld->ops->open(tty);		/* 即n_tty_open */
	}
}

static int n_tty_open(struct tty_struct *tty)
{
        struct n_tty_data *ldata;

        ldata = kzalloc(sizeof(*ldata), GFP_KERNEL);
        ldata->read_buf = kzalloc(N_TTY_BUF_SIZE, GFP_KERNEL);
        ldata->echo_buf = kzalloc(N_TTY_BUF_SIZE, GFP_KERNEL);
        tty->disc_data = ldata;
        reset_buffer_flags(tty->disc_data);
        ldata->column = 0;
        tty->minimum_to_wake = 1;
        n_tty_set_termios(tty, NULL);   /* ---> */
        
        return 0;
}

static void imx_set_termios(struct uart_port *port, struct ktermios *termios, struct ktermios *old)
{
	struct imx_port *sport = (struct imx_port *)port;
    /* 这里有超级多的寄存器配置操作,譬如波特率、时钟等等,全部删掉了 */
    
	if (sport->dma_is_inited && !sport->dma_is_enabled) {
        //如果开启了DMA,则对dma进行初始化并开启RX的dma搬运
		imx_enable_dma(sport);
		start_rx_dma(sport);     /* 开启串口接收数据时的DMA搬运 */
	}
    if (!sport->dma_is_enabled) {
        //如果没有开启DMA,则设置串口的中断使能位
		ucr2 = readl(sport->port.membase + UCR2);
		writel(ucr2 | UCR2_ATEN, sport->port.membase + UCR2);
	}
}

static int start_rx_dma(struct imx_port *sport)
{
	struct dma_chan	*chan = sport->dma_chan_rx;
	struct dma_async_tx_descriptor *desc;

	desc = dmaengine_prep_dma_cyclic(chan, sport->rx_buf.dmaaddr,
		sport->rx_buf.buf_len, sport->rx_buf.period_len,
		DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT);

	desc->callback = dma_rx_callback;    //DMA完成一次搬运后,会调用这个回调函数
	dmaengine_submit(desc);         //将该描述符插入dmaengine驱动的传输队列
	dma_async_issue_pending(chan);   //启动对应DMA通道上的传输

	sport->dma_is_rxing = 1;
	return 0;
}

static void dma_rx_callback(void *data)
{
    dma_rx_work(sport);
}

static void dma_rx_work(struct imx_port *sport)
{
	struct tty_struct *tty = sport->port.state->port.tty;
	unsigned int cur_idx = sport->rx_buf.cur_idx;

	dma_rx_push_data(sport, tty, 0, cur_idx);    /* 最终串口接收到的数据都被放到了tty ldisc的一个buffer里,用户空间读的时候会从这个buffer里取 */
}

        分析线路3 如下:

static int uart_open(struct tty_struct *tty, struct file *filp)
{
	struct uart_driver *drv = (struct uart_driver *)tty->driver->driver_state;
	int retval;
	struct uart_state *state = drv->state + line;

	retval = uart_startup(tty, state, 0);	/*--->*/
}

static int uart_startup(struct tty_struct *tty, struct uart_state *state,
		int init_hw)
{
	retval = uart_port_startup(tty, state, init_hw);	/*--->*/
}

static int uart_port_startup(struct tty_struct *tty, struct uart_state *state,
		int init_hw)
{
	struct uart_port *uport = state->uart_port;
	if (!state->xmit.buf) {
		page = get_zeroed_page(GFP_KERNEL);		/*分配了一页内存*/
		state->xmit.buf = (unsigned char *) page;	/*串口底层发送缓冲区*/
		uart_circ_clear(&state->xmit);
	}

	retval = uport->ops->startup(uport);		/*即imx_startup,在《Linux串口驱动(2) - 线路规程》的serial_imx_probe函数中做的交接*/
	return retval;
}

static int imx_startup(struct uart_port *port)
{
	struct imx_port *sport = (struct imx_port *)port;
	int retval, i;
	unsigned long flags, temp;

    /* 本函数中有很多的通过寄存器读写操作来进行的硬件配置动作,此处删去了很多 */
	/* Can we enable the DMA support? */
	if (is_imx6q_uart(sport) && !uart_console(port)
		&& !sport->dma_is_inited)
		imx_uart_dma_init(sport);  //配置发送消息时DMA搬运的目标地址,接收消息时DMA搬运的源地址

	if (sport->dma_is_inited)
		INIT_DELAYED_WORK(&sport->tsk_dma_tx, dma_tx_work);   //定义了一个延后工作任务,DMA发送搬运,在串口发送数据会唤醒调度
	return 0;
}

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值