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=NULLif (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);   //设置串口的线路规程ldiscretval = 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;
}

Linux串口驱动(3) - open详解相关推荐

  1. linux串口通信参数宏详解实例

    详解linux下的串口通讯开发 串行口是计算机一种常用的接口,具有连接线少,通讯简单,得到广泛的使用.常用的串口是RS-232-C接口(又称EIA RS-232-C)它是在1970年由美国电子工业协会 ...

  2. Linux串口编程最全详解-一个努力奋斗的少年

    NAME termios, tcgetattr, tcsetattr, tcsendbreak, tcdrain, tcflush, tcflow, cfmakeraw, cfgetospeed, c ...

  3. linux 串口 延迟,linux串口操作及设置详解

    串口操作需要的头文件 #include #include #include #include #include #include #include #include 1.打开串口 在前面已经提到lin ...

  4. linux两个进程同时打开串口,linux串口操作及设置详解

    串口操作需要的头文件 #include /*标准输入输出定义*/ #include /*标准函数库定义*/ #include /*Unix标准函数定义*/ #include #include #inc ...

  5. linux usb驱动教学视频教程,详解linux usb host驱动编写入门

    usb协议是一个复杂的协议,目前涉及到的版本就有usb1.0, usb2.0, usb3.0.大家如果打开kernel usb host目录,就会发现下面包含了ohci,uhci,ehci,xhci, ...

  6. linux驱动之总线详解

    linux驱动 第一章 linux驱动之设备与驱动 第二章 linux驱动之设备树与GPIO子系统 linux驱动之总线详解 linux驱动 一.总线bus 1.bus在linux中文件结构 2.bu ...

  7. 迅为linux下串口,迅为iMX6UL开发板多路串口开发平台接口详解

    原标题:迅为iMX6UL开发板多路串口开发平台接口详解 iMX6UL开发板 核心板参数 尺寸:38mm*42mm CPU:iMX6UL 主频528MHz ARM Cortex-A7架构 单核 内存:5 ...

  8. Linux文件系统的目录结构详解(转)

    Linux文件系统的目录结构详解(转) 原文链接https://www.cnblogs.com/cyjaysun/p/4462325.html 一.前 言 文章对Linux下所有目录一一说明,对比较重 ...

  9. ceph存储原理_赠书 | Linux 开源存储全栈详解——从Ceph到容器存储

    // 留言点赞赠书我有书,你有故事么?留言说出你的存储故事留言点赞前两名,免费送此书截止日期12.27号12.30号公布名单 // 内容简介 本书致力于帮助读者形成有关Linux开源存储世界的细致的拓 ...

最新文章

  1. linux单体内核,加载Linux单体内核的笔记
  2. 设计模式-组合+策略模式
  3. linux服务器备份软件下载,Linux服务器的常用备份方法 服务器系统备份方法
  4. Linux网络编程 | 事件处理模式:Reactor模式、Proactor模式
  5. [iOS]利用通知实现监听系统键盘
  6. 在单节点和多节点上的Hadoop设置
  7. java 友好时间显示_仿微信的IM聊天时间显示格式(含iOS/Android/Web实现)[图文+源码]...
  8. 冬天你的车热的正确吗?如何正确热车?
  9. python语言中、外部模块先导入、再使用_python引入导入自定义模块和外部文件--转载Sumomo的博客...
  10. 程序员必备的七个快捷键【一分钟掌握】
  11. 管网平差c语言编程,哈工大给水管网平差软件
  12. ITEXT PDF文件的拆分与合并
  13. python保存对话框_python打开文件对话框的方法
  14. 【JAVA语言学习】Java面向对象Ⅲ
  15. HTML点击图片播放音乐
  16. 在IE浏览器中如何直接显示word文档
  17. 一切发生的事,都是好事(19年总结)
  18. DHCP-snooping的原理、配置、案例
  19. 硬盘接口IDE、SATA、SCSI
  20. STM32芯片超时无应答 无法连接(USB转串口有黄色感叹号)

热门文章

  1. python网络爬虫课程设计题目_山东建筑大学计算机网络课程设计《基于Python的网络爬虫设计》...
  2. 生成毛笔字体图片http://i-gccs.fst.umac.mo/gccs/big5_dict.php
  3. python学校_python爬取学校教务系统
  4. [AHK]同花顺xiadan专业版全仓下单
  5. 【转载】读音复杂的姓氏 -- 覃
  6. 谷歌地图标注_网友使用99部智能手机在谷歌地图制造虚拟交通拥堵
  7. FZ(File Zilla)服务发布
  8. CentOS-7-x86_64-DVD-1511.iso
  9. 七月之美:璀璨时光的缤纷绽放
  10. QT 多线程创建方法及应用实例