上一篇博客分析了串口驱动初始化部分,下面逐步分析串口驱动中的打开串口,数据发送和接收!

初始化主要工作流程:

先来分析串口打开操作流程,还是先上图:

这里分析还是离不开上篇博客中的两张重要的图:

串口操作重要的数据结构:

由上一篇串口驱动分析可知在samsung.c中模块初始化中有一项工作是注册一个串口驱动,

跳到这个函数中uart_register_driver可以看到有一个函数:

retval = tty_register_driver(normal);

跳到这个函数中,这里贴上源码:

int tty_register_driver(struct tty_driver *driver)
{int error;int i;dev_t dev;void **p = NULL;struct device *d;if (!(driver->flags & TTY_DRIVER_DEVPTS_MEM) && driver->num) {p = kzalloc(driver->num * 2 * sizeof(void *), GFP_KERNEL);if (!p)return -ENOMEM;}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 {dev = MKDEV(driver->major, driver->minor_start);error = register_chrdev_region(dev, driver->num, driver->name);}if (error < 0) {kfree(p);return error;}if (p) {driver->ttys = (struct tty_struct **)p;driver->termios = (struct ktermios **)(p + driver->num);} else {driver->ttys = NULL;driver->termios = NULL;}cdev_init(&driver->cdev, &tty_fops);driver->cdev.owner = driver->owner;error = cdev_add(&driver->cdev, dev, driver->num);if (error) {unregister_chrdev_region(dev, driver->num);driver->ttys = NULL;driver->termios = NULL;kfree(p);return error;}mutex_lock(&tty_mutex);list_add(&driver->tty_drivers, &tty_drivers);mutex_unlock(&tty_mutex);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;}}}proc_tty_register_driver(driver);driver->flags |= TTY_DRIVER_INSTALLED;return 0;err:for (i--; i >= 0; i--)tty_unregister_device(driver, i);mutex_lock(&tty_mutex);list_del(&driver->tty_drivers);mutex_unlock(&tty_mutex);unregister_chrdev_region(dev, driver->num);driver->ttys = NULL;driver->termios = NULL;kfree(p);return error;
}

可以看到这个函数内部实现其实就是注册一个字符设备!
看看这一行: cdev_init(&driver->cdev, &tty_fops);

从这个tty_fops找到串口open函数的接口:

可以看到open操作对应的是tty_open(这里的tty_fops就是字符设备的file_operations)

跳到这个函数中可以看到箭头所指向的一行:

这个ops实际上是struct   tty_operations  类型的:

这里总结一下:应用程序空间的打开串口open操作调用了tty_ops中的tty_open,然后tty_open又对应的调用了uart_ops中的uart_open这个函数,这个函数还是tty层次里面的还不涉及驱动层!

下面跳到uart_open这个函数里面:

static int uart_open(struct tty_struct *tty, struct file *filp)
{struct uart_driver *drv = (struct uart_driver *)tty->driver->driver_state;struct uart_state *state;struct tty_port *port;int retval, line = tty->index;BUG_ON(!tty_locked());pr_debug("uart_open(%d) called\n", line);state = uart_get(drv, line);if (IS_ERR(state)) {retval = PTR_ERR(state);goto fail;}port = &state->port;tty->driver_data = state;state->uart_port->state = state;tty->low_latency = (state->uart_port->flags & UPF_LOW_LATENCY) ? 1 : 0;tty->alt_speed = 0;tty_port_tty_set(port, tty);/** If the port is in the middle of closing, bail out now.*/if (tty_hung_up_p(filp)) {retval = -EAGAIN;port->count--;mutex_unlock(&port->mutex);goto fail;}/** Make sure the device is in D0 state.*/if (port->count == 1)uart_change_pm(state, 0);/** 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);fail:return retval;

可以看到这个函数调用一个uart_startup函数,这个函数任然是tty里面的还不涉及串口驱动层!

这个函数比较长,截取重要的部分,上篇文章中也有提到过,看第二个红色箭头所指部分:uport的ops的startup

uport类型可以从第一个箭头所指部分看到是struct uart_port类型的,一个uart_port对应的是一个串口,在这个数据结构中是针对这个串口的函数操作集,而这些函数操作集就是由串口驱动来实现的!

所以现在就是要找出串口操作集里面的start_up,而这个就要从驱动里面去找了!

又串口初始化分析可以找到串口初始化中的port是从probe这个函数获取的:

而这个数组结构定义如下:每个port代表一个串口

下面再来看看这个红色箭头所指向的串口驱动操作集里面的内容:

上面截图中信息量比较多,左边和右边的可以对比着看,一个是函数指针,一个是函数指针对应的函数名字!

至此总结一下串口open操作的函数调用关系:

open ---> tty_open(tty_ops里面的) ---> uart_open(uart_ops里面的) ---> uart_start  --->   上图中红色箭头所指部分(这个就是相当于驱动层里面的open)

下面跳转到这个函数中:

代码量不多对照着代码分析总结如下图:

以上就是整个串口打开操作的实现!

下面再来分析串口驱动的发送操作,还是先上图:

整体分析流程和open函数一样!

write---> tty_write ---> n_tty_write(线路规程里面) ---> uart_write ---> uart_start ---> 向上看第四张图,也就是驱动层对应的write操作

这里直接跳到s3c24xx_serial_start_tx这个函数:

从上面的源码中可以看到这里没有操作寄存器的发送部分!这里有个小窍门!关键之处在enable_irq(ourport->tx_irq)这个地方!

当发送中断时会有中断处理程序来处理发送!这里的只是起一个激活中断发送处理程序!

在这个函数中可以看到注册了一个发送中断处理程序!跳到这个函数里面看看

上面的代码中可以看到寄存器操作部分!总体简要总结:应用层的write串口操作最终会调用上面的s3c24xx_serial_start_tx函数,而这个函数仅仅是起一个激活发送中断的功能,具体数据发送过程又是在注册发送中断来实现的!

下面这张图就是根据上面的这个函数实现的总结:

分析完了发送,下面来分析接收read函数对应的操作:

函数整个调用流程对应的和write一样!

有了上面的基础,下面可以来思考下面的两个问题:

1. tty子系统是如何响应用户的读数据请求?

2. 串口驱动又是如何来接收处理的?

其实是同write操作一样!下面还是简要的分析一下:

做为响应用户空间的read函数的第一个结点还是struct  file_operations结构中的tty_read:

下面跳到这个函数里面来看看源码:

红色箭头部分可以看到这一行其实是调用了线路规程里面的read,ops的数据类型:

再来看看read所在的结构体类型:

其实这个被调用的read函数对应的是线路规程里面的read.

下面再来看看线路规程struct tty_ldisc_ops tty_ldisc_N_TTY这个结构:

可以看到这里tty_read又由线路规程里面的n_tty_read来响应!

n_tty_read这个函数代码比较多!这里暂不截全图!只分析其中比较重要的三个部分

箭头所指部分是设置应用程序这个进程为阻塞状态!(这行代码还不会立即阻塞)

然后箭头下面的第二个if语句里面有个判断,input_available_p判断是否有数据读!

当没有数据可读的时候,将会阻塞,不会被CPU调度占用CPU。结合上面的就是如果没数据就让其阻塞生效

如果有数据将会从read_buf中读走数据

看看这个函数内部实现:

其实这个read_buf和驱动是紧密相关的,当驱动里面有数据的时候,驱动就将数据往read_buf里面送!下面再来看驱动是怎么收到数据的!

还是和write函数一样在驱动模块初始化里面的有个注册发送中断函数,然后跳到s3c24xx_serial_startup函数

在这个函数里面有个request_irq函数,这个函数里面其中一个函数指针参数就是s3c24xx_serial_rx_chars函数

分析完了下面来着手撸驱动代码了!!!这个只是在原有代码的基础上根据上面的分析流程来自己实现串口驱动的重要部分:

这里来实现两个最核心的功能:

1. 串口驱动发送中断处理程序

2. 串口驱动接收中断处理程序

打开samsung.c,找到static irqreturn_t s3c24xx_serial_tx_chars(int irq, void *id)发送中断函数,将里面的代码删除,然后根据上面的分析流程实现!

发送中断处理程序代码:

第一步:1. 判断x_char是否为0,如果不为0,则发送x_char

x_char成员在port结构中,为xon或者xoff(这个是和流控相关的)

<span style="font-size:18px;">static irqreturn_t s3c24xx_serial_tx_chars(int irq, void *id)
{struct s3c24xx_uart_port *ourport = id;struct uart_port *port = &ourport->port;struct circ_buf *xmit = &port->state->xmit;//循环缓冲int count = 256;//1. 判断x_char是否为0,如果不为0,则发送x_charif(port->x_char){wr_regb(port, S3C2410_UTXH,  port->x_char);//发送一个字符实际上就是将数据写到UTXH寄存器里面goto out;}//2. 判断发送缓冲是否为空或者驱动被设置为停止发送的状态 则取消发送if( (uart_circ_empty(xmit)) || (uart_tx_stopped(port)) ){s3c24xx_serial_stop_tx(port);goto out;}//3. 循环发送,循环条件:发送缓冲不为空while( (!uart_circ_empty(xmit)) || (count--) > 0 ){//3.1 发送fifo如果满,退出发送if( rd_regl(port, S3C2410_UFSTAT) & (1 << 14) )//这里要查datasheet UFSTAT寄存器14位break;//3.2 将要发送的字符写入发送寄存器wr_regb(port, S3C2410_UTXH, xmit->buf[xmit->tail]);//从尾巴里面取出数据xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);//循环,如果到最后一位又从第一位开始发送//3.3 修改循环缓冲的尾部位置port->icount.tx++;//更新发送的统计量}//4. 如果发送缓冲中的剩余数据量uart_circ_chars_pending<256//则唤醒之前阻塞的发送进程uart_write_wakeupif (uart_circ_chars_pending(xmit) < 256)uart_write_wakeup(port);//5. 如果发送缓冲为空,则关闭发送使能if (uart_circ_empty(xmit))s3c24xx_serial_stop_tx(port);out:return IRQ_HANDLED;//函数出口,表示中断已经处理
}</span>

上面的代码除了参考之前的分析流程图还有之前的源码,还有datasheet,这里就不一一截图祥举了!

然后make uImage ARCH=arm COMPELE_CROSS=arm-linux-编译内核源码!将uImage下载到开发板启动内核!会发现这里有个小问题!不过对照内核源码看看可以解决!

串口驱动接收中断处理程序:

s3c24xx_serial_rx_chars1111(int irq, void *dev_id)
{struct s3c24xx_uart_port *ourport = dev_id;struct uart_port *port = &ourport->port;struct tty_struct *tty = port->state->port.tty;unsigned int ufcon, ch, flag, ufstat, uerstat;int max_count = 64;//一次最多接收的字符数while (max_count-- > 0) {ufcon = rd_regl(port, S3C2410_UFCON);//1. 读取UPCON寄存器ufstat = rd_regl(port, S3C2410_UFSTAT);//2. 读取UPSTAT寄存器//3. 如果接收fifo里的数据量为0,则退出if (s3c24xx_serial_rx_fifocnt(ourport, ufstat) == 0)break;uerstat = rd_regl(port, S3C2410_UERSTAT);//4. 读取UERSTAT寄存器ch = rd_regb(port, S3C2410_URXH);//取出字符if (port->flags & UPF_CONS_FLOW) {//6. 流控制处理int txe = s3c24xx_serial_txempty_nofifo(port);if (rx_enabled(port)) {if (!txe) {rx_enabled(port) = 0;continue;}} else {if (txe) {ufcon |= S3C2410_UFCON_RESETRX;wr_regl(port, S3C2410_UFCON, ufcon);rx_enabled(port) = 1;goto out;}continue;}}/* insert the character into the buffer */flag = TTY_NORMAL;port->icount.rx++;if (unlikely(uerstat & S3C2410_UERSTAT_ANY)) {dbg("rxerr: port ch=0x%02x, rxs=0x%08x\n",ch, uerstat);/* check for break */if (uerstat & S3C2410_UERSTAT_BREAK) {dbg("break!\n");port->icount.brk++;if (uart_handle_break(port))goto ignore_char;}if (uerstat & S3C2410_UERSTAT_FRAME)port->icount.frame++;if (uerstat & S3C2410_UERSTAT_OVERRUN)port->icount.overrun++;uerstat &= port->read_status_mask;if (uerstat & S3C2410_UERSTAT_BREAK)flag = TTY_BREAK;else if (uerstat & S3C2410_UERSTAT_PARITY)flag = TTY_PARITY;else if (uerstat & (S3C2410_UERSTAT_FRAME |S3C2410_UERSTAT_OVERRUN))flag = TTY_FRAME;}if (uart_handle_sysrq_char(port, ch))goto ignore_char;//9. 将接收到的字符发送到串口驱动的bufuart_insert_char(port, uerstat, S3C2410_UERSTAT_OVERRUN,ch, flag);ignore_char:continue;}//10. 把串口驱动收到的数据发送到线路规程的read_buftty_flip_buffer_push(tty);out:return IRQ_HANDLED;
}

还是表示鸭梨山大!任重道远!

串口驱动程序设计详解---串口打开、发送、接收(下)相关推荐

  1. ubuntu系统安装串口转485驱动步骤详解

    ubuntu系统安装串口转485驱动步骤详解 本人使用的是宸曜的工控机,自带com DB9形式的串口,通过BIOS F2进入设置页面,将com0设置成485 模式2 线模式. 此为电脑设置截图 采用4 ...

  2. 陶晶驰stm32_陶晶驰HMI3.5寸串口屏的详解之梳理F4总结系列准备篇(二)

    基于stm32的串口屏教程.用过的人都说好,哈哈哈,减少了我们很多不太必要的麻烦步骤. 程序资料包CSDN下载地址:https://download.csdn.net/download/qq_3835 ...

  3. linux 驱动器发送信号,Linux设备驱动并发控制详解(自旋锁,信号量)

    转发:Linux设备驱动并发控制详解(自旋锁,信号量) 作者:jinhaijun 提交日期:2008-3-12 14:08:00 | 分类: | 访问量:144 link:http://www.emb ...

  4. 《Linux设备驱动开发详解(第2版)》隆重出版

    Linux设备驱动开发详解(第2版)(前一版狂销3万册,畅销书最新升级) [新品] 点击看大图     基本信息 * 作者: 宋宝华       * 出版社:人民邮电出版社     * ISBN:97 ...

  5. 《Linux设备驱动开发详解 A》一一2.3 接口与总线

    本节书摘来华章计算机出版社<Linux设备驱动开发详解 A>一书中的第2章,第2.3节,作者:宋宝华 更多章节内容可以访问云栖社区"华章计算机"公众号查看.1 2.3 ...

  6. linux 设备驱动 ppt,linux设备驱动开发详解讲座ppt

    PPT内容 这是linux设备驱动开发详解讲座ppt下载,主要介绍了设备驱动简介:建立和运行模块:字符驱动:调试技术:并发和竞争:分配内存:硬件通讯:中断处理:块设备驱动,欢迎点击下载. 嵌入式Lin ...

  7. linux设备驱动开发详解源码,linux设备驱动开发详解光盘源码.rar

    压缩包 : linux设备驱动开发详解光盘源码.rar 列表 19/busybox源代码/busybox-1.2.1.tar.bz2 19/MTD工具/mtd-utils-1.0.0.tar.gz 1 ...

  8. python中selenium模块驱动谷歌详解

    python中selenium模块驱动谷歌详解 Selenium的介绍.配置和调用 Selenium(浏览器自动化测试框架) 是一个用于Web应用程序测试的工具.Selenium测试直接运行在浏览器中 ...

  9. linux设备驱动开发详解孔夫子,Linux设备驱动开发详解

    [内容简介] <Linux设备驱动开发详解(第2版)>是一本介绍linux设备驱动开发理论.框架与实例的书,<Linux设备驱动开发详解(第2版)>基于ldd6410开发板,以 ...

最新文章

  1. 每天一个linux命令(8):cp 命令
  2. [转载]安装Oracle11gR2先决条件检查失败的详细解决处理过程
  3. 在业务层实现校验请求参数
  4. socket 编程入门教程(一)TCP server 端:3、sockaddr与sockaddr_in
  5. 验证登陆信息的合法性
  6. 正则表达式 以字符串开头_干货-Shell编程之正则表达式
  7. 【王道考研操作系统】—文件的基本操作
  8. 枚举编写单例是可以保证在多线程中的安全性
  9. 第 39 章 ThinkPHP--模型初步(下)
  10. Linux环境下通过gstack命令查看进程的运行堆栈信息
  11. Codeblocks中文乱码解决方法。
  12. oracle用户有关操作,oracle用户会话操作
  13. 华为交换机关闭网口_关闭端口的命令 怎么开启华为交换机关闭端口,命令谁知道啊。...
  14. 基于javaweb的前台展示+后台管理结合的在线购书系统(java+springboot+ssm+mysql)
  15. 英国脱欧对GDPR的实施有影响吗?
  16. 虚拟机配置NAT网络后ping不通
  17. Nginx反向代理:多域名跳转多台服务器(研发需求)
  18. centerOs底下安装mysql
  19. 40页PPT详解:京东大数据基础构架与创新应用
  20. vuepress-theme-reco 博客主题使用

热门文章

  1. 金三银四如何抱佛脚?2022 最新大厂 Java 面试真题合集(附权威答案)
  2. UE4蓝图切换玩家行走跑步姿势
  3. 嵌入式设计与开发项目-液位检测告警系统
  4. 考研:研究生考试(十五天学完)之【数学考试】—《高等数学-上册/下册》、《线性代数与空间解析几何》、《概率与统计》的研究生学霸重点知识点总结之考试内容各科占比及其知识结构重点
  5. 个人项目-论文查重/3120005470
  6. 2010年北京理工大学中国象棋棋软比赛的冠军引擎新旋风三代升级到Tornado v1.0.4.0, 加密采用Safengine Licensor v1.8.4
  7. 世界元宇宙大会—李伯虎院士主旨报告:工业元宇宙模式、技术与应用初探
  8. 《面试八股文》之GitHub中文社区Java 领域又一份备战神器,开冲金三银四
  9. 什么是TTFF、暖开机、冷开机、热开机?
  10. 前端:request.js?b775:105 Uncaught (in promise) Error: nested exception is org.apache.ibatis.exceptions