一.I.MX6 UART驱动
文件路径:\linux_IMX6_CoreC_3..35_for_Linux\drivers\tty\serial\imx.c
.驱动入口函数:imx_serial_init()
1.1 static int __init imx_serial_init(void)
ret = uart_register_driver(&imx_reg); //驱动加载的时候调用了这个函数注册串口驱动,将参数imx_reg注册进了tty层
struct tty_driver *normal;
normal = alloc_tty_driver(drv->nr); //申请tty驱动,串口设备套上了一层tty驱动的外壳
...
tty_set_operations(normal, &uart_ops);
//上面这段代码可以得出一个结论,uart_driver的数据类型其实就是tty_driver,两者进行数据转换之后注册进了tty层。
//tty_set_operations(normal, &uart_ops);将uart的操作函数和tty关联起来,应用层对于tty的操作都将对应到uart的操作 retval = tty_register_driver(normal); //向TTY核心层注册一个TTY驱动,所以串口设备其实就是一个tty类型的设备
alloc_chrdev_region(&dev, driver->minor_start, driver->num, driver->name);
register_chrdev_region(dev, driver->num, driver->name);
cdev_init(&driver->cdev, &tty_fops); //初始化设备,注意这里将指针调用关系赋给了cdev ret = platform_driver_register(&serial_imx_driver); //注册平台驱动
//serial_imx_driver变量类型如下
static struct platform_driver serial_imx_driver = {
.probe = serial_imx_probe, //当匹配到设备之后,这句函数得到调用,转去分析这个函数
.remove = serial_imx_remove, .suspend = serial_imx_suspend,
.resume = serial_imx_resume,
.driver = {
.name = "imx-uart",
.owner = THIS_MODULE,
},
};
1.2 serial_imx_probe
static int serial_imx_probe(struct platform_device *pdev)
sport->port.dev = &pdev->dev;
//imx_pops是串口接收数据、发送数据的相关函数(注意这里要区别前面的串口操作函数)
//我觉得前面的串口操作函数是从应用层或者tty的角度来看,使用者也仅仅是应用层(tty层)
//这里的imx_pops里面众多的操作函数是从底层的角度去看,也就是底层和硬件相关的中断接收、发送相关。
sport->port.ops = &imx_pops;
sport->port.flags = UPF_BOOT_AUTOCONF;
sport->port.line = pdev->id;
init_timer(&sport->timer);
sport->timer.function = imx_timeout;
sport->timer.data = (unsigned long)sport;
...
...
...
ret = uart_add_one_port(&imx_reg, &sport->port); //关键:为uart_driver增加一个端口
//列出imx_pops重要的几个函数
static struct uart_ops imx_pops = {
...
.stop_tx = imx_stop_tx,
.start_tx = imx_start_tx, //串口发送
.stop_rx = imx_stop_rx,
.startup = imx_startup, //中断发送相关的函数,接下来我们分析这个发送函数
...
}; 1.3 static int imx_startup(struct uart_port *port) //做一些串口的初始化操作
{
struct imx_port *sport = (struct imx_port *)port;
struct tty_struct *tty;
...
clk_enable(sport->clk); //使能时钟 /* disable the DREN bit (Data Ready interrupt enable) before
* requesting IRQs
*/
temp = readl(sport->port.membase + UCR4);
...
writel(temp & ~UCR4_DREN, sport->port.membase + UCR4);
...
/*
* Allocate the IRQ(s) i.MX1 has three interrupts whereas later
* chips only have one interrupt.
*/
retval = request_irq(sport->rxirq, imx_rxint, , DRIVER_NAME, sport);//关键:在这里申请了接收中断函数(同时注册了中断接收函数imx_rxint)
...
retval = request_irq(sport->txirq, imx_txint, , DRIVER_NAME, sport);//申请注册发送中断函数(这次分析我们不关心发送中断)
/* Enable the SDMA for uart. */
if (sport->enable_dma) //如果配置串口使用DMA
{
int ret;
ret = imx_uart_dma_init(sport); //DMA的初始化
//下面几句代码都是DMA初始化相关,平台通用,不展开说明啦
sport->dma_data.priority = DMA_PRIO_HIGH;
sport->dma_data.dma_request = pdata->dma_req_rx;
sport->dma_data.peripheral_type = IMX_DMATYPE_UART;
sport->dma_chan_rx = dma_request_channel(mask, imx_uart_filter, sport);
...
slave_config.direction = DMA_DEV_TO_MEM;
slave_config.src_addr = sport->port.mapbase + URXD0;
//注意:如果配置串口使用DMA,这里初始化了一个工作队列(中断下半部的一种),来进行数据的接收和发送
//听说3.0.35版本的内核DMA驱动有问题,不知道是不是真的,待证实,实际使用的时候,确实是发现DMA在连续传输大量数据的时候会出现数据丢失的情况
INIT_WORK(&sport->tsk_dma_tx, dma_tx_work);
INIT_WORK(&sport->tsk_dma_rx, dma_rx_work); //基于DMA的串口中断接收函数,也是在这里注册了一个工作队列
dma_rx_work(struct work_struct *w)
tty_insert_flip_string(tty, sport->rx_buf, sport->rx_bytes);//关键的函数调用:将数据放到tty数据缓冲区
tty_insert_flip_string_fixed_flag(tty, chars, TTY_NORMAL, size);
struct tty_buffer *tb = tty->buf.tail;
memcpy(tb->char_buf_ptr + tb->used, chars, space);
memset(tb->flag_buf_ptr + tb->used, flag, space);
//如果不调用下面的函数,只是将数据放到tty缓冲区,tty也是获取不到数据的
tty_flip_buffer_push(tty); //将缓冲区的数据推到tty当中,其实内部是一个工作队列的调度(在tty初始化的时候注册了这个工作队列),等于是通知tty的线路规程获取数据的意思
schedule_work(&tty->buf.work); //工作调度,实际上调用了flush_to_ldisc函数(这个函数好像不能在中断中调用
//,但是我发现也有其他驱动直接在中断调用了,实际上我在中断中调用业一直没发现有什么问题)
init_waitqueue_head(&sport->dma_wait);
}
...
spin_lock_irqsave(&sport->port.lock, flags);
/*
* Finally, clear and enable interrupts
*/
writel(USR1_RTSD, sport->port.membase + USR1); //使能中断 } 1.4 //在前面刚进入函数的时候,我们申请了串口中断,同时注册了imx_rxint接收函数,接下来分析这个函数
retval = request_irq(sport->rxirq, imx_rxint, , DRIVER_NAME, sport);
spin_lock_irqsave(&sport->port.lock,flags); //先获取自旋锁 while (readl(sport->port.membase + USR2) & USR2_RDR) //判断串口寄存器状态,开始接收字节数据
{
flg = TTY_NORMAL;
sport->port.icount.rx++;
rx = readl(sport->port.membase + URXD0);
temp = readl(sport->port.membase + USR2);
...
if (uart_handle_sysrq_char(&sport->port, (unsigned char)rx))
continue;
if (rx & URXD_BRK)
flg = TTY_BREAK;
else if (rx & URXD_PRERR)
flg = TTY_PARITY;
else if (rx & URXD_FRMERR)
flg = TTY_FRAME;
if (rx & URXD_OVRRUN)
flg = TTY_OVERRUN;
}
tty_insert_flip_char(tty, rx, flg);
tty_insert_flip_string_flags(tty, &ch, &flag, );
struct tty_buffer *tb = tty->buf.tail;
memcpy(tb->char_buf_ptr + tb->used, chars, space);
memcpy(tb->flag_buf_ptr + tb->used, flags, space);
out:
spin_unlock_irqrestore(&sport->port.lock,flags);//释放自旋锁
tty_flip_buffer_push(tty);//里面有工作队列的调度,将数据“刷新”到tty
return IRQ_HANDLED; .uart操作函数结构体
static const struct tty_operations uart_ops = {
.open = uart_open,
.close = uart_close,
.write = uart_write,
.put_char = uart_put_char,
...
...
...
};
//从结构体的类型我们知道,uart_ops的数据类型就是tty_operations 类型,对应tty的文件操作函数
//所以tty的操作路径就对应着tty_ops-->uart_driver-->uart->ops .uart_open函数
static int uart_open(struct tty_struct *tty, struct file *filp)
struct uart_driver *drv = (struct uart_driver *)tty->driver->driver_state; .uart_close函数
static void uart_close(struct tty_struct *tty, struct file *filp)
struct uart_state *state = tty->driver_data; .uart_write函数
static int uart_write(struct tty_struct *tty, const unsigned char *buf, int count)
struct uart_state *state = tty->driver_data;
//在前面,normal->driver_state = drv;将串口的私有数据赋值给tty
//上面3、4、5这几个函数是在我们打开串口或者做串口操作的时候,将数据从tty拿出来(因为打开串口的时候,
//就是tty去操作设备号对应将设备的信息传递到驱动中来)
//这几个函数都是应用层在操作串口的时候才会调用,与串口数据发送没有关系,所以就不展开分析了。 二、tty驱动分析(不针对特定处理器平台)
文件路径:\linux_IMX6_CoreC_3..35_for_Linux\drivers\tty\tty_io.c
\linux_IMX6_CoreC_3..35_for_Linux\drivers\tty\tty_buffer.c
\linux_IMX6_CoreC_3..35_for_Linux\drivers\tty\tty_ldisc.c
tty由上往下分为tty核心层、tty线路规程、tty驱动,用户空间对应tty设备的操作函数定义及实现都在tty_io.c中。
tty核心层不能直接从tty驱动获取数据,底层的数据经过tty驱动,在经过tty线路规程,再到tty核心层。
. 线路规程的初始化
//线路规程的初始化是在内核刚启动的时候完成的,文件路径是\init\main.c
asmlinkage void __init start_kernel(void)
console_init();
/* Setup the default TTY line discipline. */
tty_ldisc_begin();
/* Setup the default TTY line discipline. */
(void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);
//分析tty_ldisc_N_TTY结构体变量(属于tty_ldisc_ops类型,接着分析tty_ldisc_ops)
1.1 tty_ldisc_ops(线路规程操作函数结构体)
struct tty_ldisc_ops tty_ldisc_N_TTY =
{
.magic = TTY_LDISC_MAGIC,
.name = "n_tty",
.open = n_tty_open,
.close = n_tty_close,
.flush_buffer = n_tty_flush_buffer,
.chars_in_buffer = n_tty_chars_in_buffer,
.read = n_tty_read,
.write = n_tty_write,
.ioctl = n_tty_ioctl,
.set_termios = n_tty_set_termios,
.poll = n_tty_poll,
.receive_buf = n_tty_receive_buf,
.write_wakeup = n_tty_write_wakeup
};
//以上这些函数就是tty线路规程在操作数据时候用到的函数,从tty应用层操作向下看就是tty_ops--->tty_ldisc_ops
//前面分析uart驱动时候uart_ops和实际的数据接收和发送函数是分开的,在这里也是类似。
//操作设备是一组函数,实际做数据收发的又是另外一组函数。
1.1. n_tty_open函数
static int n_tty_open(struct tty_struct *tty)
tty->read_buf = kzalloc(N_TTY_BUF_SIZE, GFP_KERNEL);//分配tty缓冲区给tty_read_buf,其中N_TTY_BUF_SIZE为4096
...
reset_buffer_flags(tty);//初始化头尾指针、读计数等
tty->read_head = tty->read_tail = tty->read_cnt = ;
1.1. n_tty_write函数
static ssize_t n_tty_write(struct tty_struct *tty, struct file *file, const unsigned char *buf, size_t nr)
...
//这里调用到了tty_driver操作函数,因为在之前的tty_open函数中有了tty->ops=driver->ops这样的操作。
//注册的时候初始化了ops
tty->ops->flush_chars(tty);
...
c = tty->ops->write(tty, b, nr); 1.1. n_tty_read函数
static ssize_t n_tty_read(struct tty_struct *tty, struct file *file, unsigned char __user *buf, size_t nr)
...
...
while (nr)
{
c = tty->read_buf[tty->read_tail];//从tty读缓冲区获取数据
...
tty_put_user(tty, c, b++);
tty_audit_add_data(tty, &x, );
put_user(x, ptr);
uncopied = copy_from_read_buf(tty, &b, &nr);
uncopied += copy_from_read_buf(tty, &b, &nr);
}
//注意这里tty线路规程对数据的读操作并没有调用到tty驱动层的读函数。
//和tty线路规程对数据的写操作要做个对比和区别。
//tty的数据读取是“被动”读取的,简单讲就是通过串口中断接收字节,然后调度tty初始化时候注册的工作队列来实现数据的读取,
//对应的函数是:
flush_to_ldisc();
...
disc->ops->receive_buf(tty, char_buf,flag_buf, count);//具体调用的函数是n_tty_receive_buf函数
...
. int __init tty_init(void)
//初始化tty设备,tty设备本质上也是一个字符设备
//用户空间对于tty设备的操作都对应到tty_fops结构体里面的函数
cdev_init(&tty_cdev, &tty_fops);//我们重点分析tty操作函数结构体里面的函数集合
cdev_add(&tty_cdev, MKDEV(TTYAUX_MAJOR, ), );
register_chrdev_region(MKDEV(TTYAUX_MAJOR, ), , "/dev/tty");
device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, ), NULL, "tty");//创建设备
//初始化tty设终端(关于tty的终端,我们暂时不分析)
cdev_init(&console_cdev, &console_fops);
register_chrdev_region(MKDEV(TTYAUX_MAJOR, ), , "/dev/console");
consdev = device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, ), NULL, "console");
...
2.1 tty_buffer_init()函数
INIT_WORK(&tty_buf.work, flush_to_ldisc);//flush_to_ldisc函数绑定到工作队列,当tty_filp_buffer_push函数中调用schedule_work(&tty->buf.work);时候,函数得到调用
2.2 tty_fops结构体
static const struct file_operations tty_fops = {
.llseek = no_llseek,
.read = tty_read, //注意这里的read函数不是对应uart操作函数里面的read函数(需要做关联),后面会讲到
.write = tty_write, //同上
.poll = tty_poll,
.unlocked_ioctl = tty_ioctl,
.compat_ioctl = tty_compat_ioctl,
.open = tty_open,
.release = tty_release,
.fasync = tty_fasync,
}; 2.3 tty_open函数(打开tty设备时候做的一些初始化操作,建立tty核心和线路规程以及硬件驱动的联系)
static int tty_open(struct inode *inode, struct file *filp)
struct tty_driver *driver;
int index;
dev_t device = inode->i_rdev;
...
tty = get_current_tty();
tty_free_file(filp);
...
driver = tty_driver_kref_get(tty->driver);
...
driver = get_tty_driver(device, &index); //根据设备号,来查找到tty_driver
tty = tty_init_dev(driver, index, ); //初始化tty结构体
initialize_tty_struct(tty, driver, idx); //关键的一个初始化,在里面建立tty核心层和线路规程的联系
tty_ldisc_init(tty); //初始化线路规程
tty->buf.head = tty->buf.tail = NULL; //缓冲区头尾指空
tty_buffer_init(tty); //初始化tty缓冲区
spin_lock_init(&tty->buf.lock);
tty->buf.head= NULL;
tty->buf.tail= NULL;
tty->buf.free= NULL;
tty->buf.memory_used= ;
//初始化了一个工作队列
//当在驱动层里receive_chars的最后调用了tty_flip_buffer_push这个函数的时候这个队里得到调度
INIT_DELAYED_WORK(&tty->buf.work, flush_to_ldisc);//在中断下半部中进行调度时候,flush_to_ldisc会得到调用,接下来分析这个函数
...
tty->ops = driver->ops; //tty操作指向tty设备操作(注意这里是指向具体的硬件设备驱动操作)
tty->dev = tty_get_device(tty);
dev_t devt = tty_devnum(tty); //获得设备号
...
retval = tty_driver_install_tty(driver, tty);
...
retval = tty_ldisc_setup(tty, tty->link); //设置线路规程
retval = tty_ldisc_open(tty, ld); //打开线路规程
tty_ldisc_enable(tty); //使能线路规程
...
tty_add_file(tty, filp);
check_tty_count(tty, "tty_open"); //检查tty被打开多少次
...
retval = tty->ops->open(tty, filp); //实际上就是driver->ops->open(注意这里的driver指的是具体硬件的driver)
2.3. tty_flip_buffer_push函数
void tty_flip_buffer_push(struct tty_struct *tty)
...
...
...
if (tty->low_latency)
flush_to_ldisc(&tty->buf.work.work);
else
schedule_work(&tty->buf.work);//工作队列调度,内核线程???会调用flush_to_ldisc 2.3. flush_to_ldisc函数
//从tty_buffer中找到数据缓冲区char_buf_ptr,并将这个缓冲区指针传递给线路规程的操作函数receive_buf。
//拷贝数据进tty的read_buf
flush_to_ldisc(struct work_struct *work)
disc->ops->receive_buf(tty, char_buf, flag_buf,count);
n_tty_receive_buf(struct tty_struct *tty, const unsigned char*cp, char *fp, int count);
n_tty_receive_char(tty,*p);
...
if(tty->ops->flush_chars)
tty->ops->flush_chars(tty); 2.4 tty_write函数(应用层调用)
static ssize_t tty_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
struct tty_struct *tty = file_tty(file);
struct tty_ldisc *ld; //tty线路规程结构体,这个结构体很重要
...
ld = tty_ldisc_ref_wait(tty); //等待tty线路规程(所有从tty驱动上来的数据或者从应用层到tty驱动的数据,都要经过线路规程)
ret = do_tty_write(ld->ops->write, tty, file, buf, count); //do_tty_write这里实际上是调用了ld->ops->write(也就是线路规程的write函数,将数据写入tty缓冲区)
//等价于调用n_tty_write函数
//在n_tty_write函数中调用下面tty driver中的操作函数
tty->ops->flush_chars(tty);
...
c = tty->ops->write(tty, b, nr);
copy_from_user(tty->write_buf, buf, size); //从用户空间拷贝数据到tty写缓冲中 2.5 tty_read函数(应用层调用)
static ssize_t tty_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
...
struct tty_struct *tty = file_tty(file);
...
i = (ld->ops->read)(tty, file, buf, count);//调用线路规程里面的read函数(n_tty_read)
//下面这几句代码是n_tty_read函数的
while (nr)
{
c = tty->read_buf[tty->read_tail];//从tty读缓冲区获取数据,实际上是在tty->read_buf的末尾tty->read_tail中读取数据
...
tty_put_user(tty, c, b++);
tty_audit_add_data(tty, &x, );
put_user(x, ptr);
uncopied = copy_from_read_buf(tty, &b, &nr);
uncopied += copy_from_read_buf(tty, &b, &nr);
}

一、uart&tty驱动的更多相关文章

  1. 十一、UART&TTY驱动

    Linux系统中UART驱动和TTY驱动两者有着紧密的关系,它们不像I2C和SPI驱动是单独一个模块,分析时应当将它们看成一个整体来分析.UART驱动部分依赖于硬件平台,而TTY驱动和具体的平台无关. ...

  2. [tty与uart]3.tty驱动分析

    转自:http://www.wowotech.net/linux_kenrel/183.html 目录: 1 首先分析设备驱动的注册 1.1 uart_register_driver分析 1.2 tt ...

  3. [uart]3.tty驱动分析

    转自:http://www.wowotech.net/linux_kenrel/183.html 目录: 1 首先分析设备驱动的注册 1.1 uart_register_driver分析 1.2 tt ...

  4. linux UART串口驱动开发文档

    转:http://www.360doc.com/content/10/0417/18/829197_23519037.shtml linux UART串口驱动开发文档时间:2010-01-09 14: ...

  5. Linux TTY驱动--Serial Core层【转】

    转自:http://blog.csdn.net/sharecode/article/details/9197567 版权声明:本文为博主原创文章,未经博主允许不得转载. 接上一节: Linux TTY ...

  6. tiny4412 串口驱动分析六 --- TTY驱动架构

    转载: http://www.linuxidc.com/Linux/2013-11/92639.htm 参考: http://blog.csdn.net/lamdoc/article/details/ ...

  7. Linux tty驱动架构

    Linux tty子系统包含:tty核心,tty线路规程和tty驱动.tty核心是对整个tty设备的抽象,对用户提供统一的接口,tty线路规程是对传输数据的格式化,tty驱动则是面向tty设备的硬件驱 ...

  8. Linux TTY驱动--Uart_driver底层【转】

    转自:http://blog.csdn.net/sharecode/article/details/9196591 版权声明:本文为博主原创文章,未经博主允许不得转载. Linux 中将串口驱动进行了 ...

  9. usb 转 uart cp210x 驱动解析

    USB 转 uart (cp210x.c) 驱动解析 * usb_serial_driver 结构体解析 include/linux/usb/serial.h /** 描述一个usb 串口设备驱动 * ...

随机推荐

  1. VMWARE虚拟机安装系统提示CPU已被客户机操作系统禁用和secureCUT乱码

    错误:VMWARE虚拟机安装系统提示CPU已被客户机操作系统禁用 改正:找到虚拟机的位置找到下图灰色的部分:打开 .vmx后缀的操作系统配置文件,加入以下代码: cpuid.1.eax = :: 2. ...

  2. Android 登陆功能的实现(访问WebServices 解析返回的JSON结果)

    1. WebServices返回的JSON结果 { , "Result": [{ ", ", ", " }] } 2.访问WEB服务代码 i ...

  3. wr720n v4 折腾笔记(五):终篇-编译安装openwrt附带njit

    前言: 由于没有多余的U盘,所以想直接压缩生成一个带njit-client的openwrt固件.其中按照网上的教程走,遇到了一些问题,这里前面的步骤来源于网络.后面给出处理问题的方法. 一.准备工作 ...

  4. [模拟]Educational Codeforces Round 2A Extract Numbers

    Extract Numbers time limit per test 2 seconds memory limit per test 256 megabytes input standard inp ...

  5. 结构化学习(Structured Learning)

    本博客是针对李宏毅教授在youtube上上传的Machine Learning课程视频的学习笔记.课程链接 目录 引入 线性模型 结构化SVM 给序列贴标签 引入 我们之前学习到的学习模型的输入与输出 ...

  6. Spss统计描述分析

    总觉得有些技能学会了是不会忘的,但是还是要记录一下,怕记忆力不像狗皮膏药,并不那么牢固. 1.文件的合并 两个数据文件的合并 点击添加个案,这一步按照自己的需求选择,也可以打开外部数据集,在这里打开的 ...

  7. MySQL5.6 选项和变量整理

    MySQL5.6 选项和变量整理  --allow-suspicious-udfs 这个选项控制是否用户定义函数只有一个xxx符号用于主函数加载.默认,该选项是关闭并且只具有至少一个辅助符号的UDFs ...

  8. 仅用200个样本就能得到当前最佳结果:手写字符识别新模型TextCaps

    由于深度学习近期取得的进展,手写字符识别任务对一些主流语言来说已然不是什么难题了.但是对于一些训练样本较少的非主流语言来说,这仍是一个挑战性问题.为此,本文提出新模型TextCaps,它每类仅用200 ...

  9. SpringCloud服务的注册发现--------Eureka

    1,什么叫做服务的注册与发现 服务的注册与发现基于注册中心,注册中心本身是一个服务,也相当于一个载体,其他服务的注册需要注册到这个注册中心上. 注册:当服务器启动的时候,会将自己的服务器信息,通过别名 ...

  10. Java工程师常用Linux命令

    本文所列的Linux常用命令包含:文件相关(目录操作,内容查看,查找与比较,压缩与解压),进程管理,网络操作,系统管理,性能监测与优化,Java常用工具多个方面概述. 文件目录基本操作 ls 命令用来 ...