一、uart&tty驱动
一.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驱动的更多相关文章
- 十一、UART&TTY驱动
Linux系统中UART驱动和TTY驱动两者有着紧密的关系,它们不像I2C和SPI驱动是单独一个模块,分析时应当将它们看成一个整体来分析.UART驱动部分依赖于硬件平台,而TTY驱动和具体的平台无关. ...
- [tty与uart]3.tty驱动分析
转自:http://www.wowotech.net/linux_kenrel/183.html 目录: 1 首先分析设备驱动的注册 1.1 uart_register_driver分析 1.2 tt ...
- [uart]3.tty驱动分析
转自:http://www.wowotech.net/linux_kenrel/183.html 目录: 1 首先分析设备驱动的注册 1.1 uart_register_driver分析 1.2 tt ...
- linux UART串口驱动开发文档
转:http://www.360doc.com/content/10/0417/18/829197_23519037.shtml linux UART串口驱动开发文档时间:2010-01-09 14: ...
- Linux TTY驱动--Serial Core层【转】
转自:http://blog.csdn.net/sharecode/article/details/9197567 版权声明:本文为博主原创文章,未经博主允许不得转载. 接上一节: Linux TTY ...
- tiny4412 串口驱动分析六 --- TTY驱动架构
转载: http://www.linuxidc.com/Linux/2013-11/92639.htm 参考: http://blog.csdn.net/lamdoc/article/details/ ...
- Linux tty驱动架构
Linux tty子系统包含:tty核心,tty线路规程和tty驱动.tty核心是对整个tty设备的抽象,对用户提供统一的接口,tty线路规程是对传输数据的格式化,tty驱动则是面向tty设备的硬件驱 ...
- Linux TTY驱动--Uart_driver底层【转】
转自:http://blog.csdn.net/sharecode/article/details/9196591 版权声明:本文为博主原创文章,未经博主允许不得转载. Linux 中将串口驱动进行了 ...
- usb 转 uart cp210x 驱动解析
USB 转 uart (cp210x.c) 驱动解析 * usb_serial_driver 结构体解析 include/linux/usb/serial.h /** 描述一个usb 串口设备驱动 * ...
随机推荐
- [组件封装]微信小程序-图片批量上传照片墙
描述 批量上传图片, 可设置最大上传个数, 可删除, 可设置默认值. 效果 源码 pictures-wall.wxml <view class="picturesWall"& ...
- Journal of Proteome Research | Proteomic Profiling of Rhabdomyosarcoma-Derived Exosomes Yield Insights into Their Functional Role in Paracrine Signaling (解读人:孙国莹)
文献名:Proteomic Profiling of Rhabdomyosarcoma-Derived Exosomes Yield Insights into Their Functional Ro ...
- JSFinder:一个在js文件中提取URL和子域名的脚本
JSFinder介绍 JSFinder是一款用作快速在网站的js文件中提取URL,子域名的脚本工具. 支持用法 简单爬取 深度爬取 批量指定URL/指定JS 其他参数 以往我们子域名多数使用爆破或DN ...
- intern()方法的使用
intern() intern方法的作用是:如果字符串常量池中已经包含一个字符串等于此String对象的字符串,则返回常量池中的这个String对应的对象, 否则将其添加到常量池并返回常量池中的引用. ...
- 北邮OJ 89. 统计时间间隔 java版
89. 统计时间间隔 时间限制 1000 ms 内存限制 65536 KB 题目描述 给出两个时间(24小时制),求第一个时间至少要经过多久才能到达第二个时间.给出的时间一定满足的形式,其中x和y分别 ...
- mongodb的更新语句
MongoDB 使用 update() 和 save() 方法来更新集合中的文档: update()方法: update() 方法用于更新已存在的文档.语法格式如下: db.collection.up ...
- 关于visocode 自动保存时自动添加分号问题
先下载插件: Vueter 打开设置的配置文件,写入一下代码: // //是否需要保存时自动格式化 "editor.formatOnSave": true, //使js 文件保存 ...
- 3.Metasploit攻击流程及命令介绍
Metasploit 进阶第一讲 攻击流程及命令介绍 01.渗透测试过程环节(PTES) 1.前期交互阶段:与客户组织进行交互讨论,确定范围,目标等 2.情报搜集阶段:获取更多目标组织信 ...
- [斯坦福大学2014机器学习教程笔记]第五章-控制语句:for,while,if语句
在本节中,我们将学习如何为Octave程序写控制语句. 首先,我们先学习如何使用for循环.我们将v设为一个10行1列的零向量. 接着,我们写一个for循环,让i等于1到10.写出来就是for i = ...
- PTA数据结构与算法题目集(中文) 7-42整型关键字的散列映射 (25 分)
PTA数据结构与算法题目集(中文) 7-42整型关键字的散列映射 (25 分) 7-42 整型关键字的散列映射 (25 分) 给定一系列整型关键字和素数P,用除留余数法定义的散列函数将关键字映射 ...