TA的每日心情 | 开心 2015-9-7 16:19 |
---|
签到天数: 15 天 连续签到: 1 天 [LV.4]偶尔看看III
|
TTY设备驱动
提到TTY设备,大家可能觉得挺洋气,如果换成一个不洋气的名字,就叫他——终端设备。
tty设备的名称是从过去的电传打字机的缩写出来的,最初是指连接到Unix系统上的物理或者虚拟中断。随着时间的推移,当通过串行口能够建立起终端连接后,这个名字也用来指任何的串口设备,比如、串口、USB到串口的转换器等。
Linux TTY驱动程序的核心紧挨在标准字符设备驱动层之下,并提供了一系列的功能,作为接口被终端类型设备使用,从另一个角度上讲,TTY设备算作是字符型设备。
这里有一点是大家要注意的,也是目前讲终端设备驱动的笔记或者书籍的一个通病,就是tty驱动、uart驱动混着讲,我们先来把基本的捋一下。tty设备包括uart,也就是说uart设备实在tty设备的上层,在Linux代码中,有一整套完整的tty设备驱动代码,uart设备可以利用这套代码来驱动自己,但是还有另外一个问题,开发者又为uart单独提供了一整套完整的驱动程序,两者相互独立,但是又相互联系,比如在uart设备可以使用自己独立的代码完成初始化,而在uart的驱动代码中,uart的一些结构体中就直接调用了tty设备的结构体,其实这个很正常,因为tty设备的结构体本身就能够描述清楚uart设备的相关属性,不过uart更多的是用了属于自己的一些代码。
说了这么多,在这篇笔记中,我们要讲的东西,就是上面我说的这两种情况,也是目前诸多参考文档或者书籍没有讲到的或者只讲了一部分的东西,在我参考的一些文档或者书籍中,通常只讲一部分,比如只讲纯用tty结构写的终端设备驱动,或者先讲一下tty设备的一些基础的概念,然后开始讲uart驱动程序。这样会对初学者造成一种干扰或者误导。而我这篇笔记要把这两方面整合起来,我们先来讲TTY设备驱动的相关代码,不涉及到具体器件和具体的平台,然后再讲一下使用uart设备的代码如何驱动串口,并将这两者进行综合阐述一下。以下几个文件是我们要参考的几个基本的源代码文件:
Tty.h (linux-3.2.12\include\linux)
Tty_buffer.c (linux-3.2.12\drivers\tty)
Tty_driver.h (linux-3.2.12\include\linux)
Tty_flip.h (linux-3.2.12\include\linux)
Tty_io.c (linux-3.2.12\drivers\tty)
Tty_ioctl.c (linux-3.2.12\drivers\tty)
Tty_port.c (linux-3.2.12\drivers\tty)
Serial_core.c (linux-3.2.12\drivers\tty\serial)
Serial_core.h (linux-3.2.12\include\linux)
然后我们先看一个框架结构,如下图:
tty核心从用户那里得到将被发往tty设备的数据,然后把数据发给tty线路规程驱动程序,该驱动程序负责把数据传递给tty驱动程序。tty驱动程序多数据进行格式化,然后才能发给硬件。从硬件那里接受的数据将回溯至tty驱动程序,然后进入tty线路规程驱动程序,接着是tty核心,最后用户从tty核心那里得到数据。有时候tty核心和tty驱动程序直接进行数据交互,但是一般情况是tty线路规程驱动程序在修改而这之间的数据流动方向。上图的金字塔形就是表示tty线路规程程序为中间者。
有三种类型的tty驱动程序:控制台、串口和pty。控制台和pty驱动程序已经被写好了,而且可能也不必为这两类tty驱动程序编写其他的驱动程序,这使得任何使用tty核心与用户和系统交互的新驱动程序都可被看成是串口驱动程序。
另外,在这里插上一句,本笔记所讲的tty驱动程序,是通用的一个tty设备程序框架,tty设备包含uart设备,也就是说,tty设备的驱动框架可以用来驱动uart设备,但是在Linux程序中,uart设备的驱动程序是单独的,他的执行框架其实在思想上和tty设备驱动的框架是一样的,但是只不过开发者把它单独写出来了,再有一点,这里的tty设备驱动程序是通用的,也就是说在不同的平台上一般是可以用的(不排除有不能用的,无非就是底层的寄存器可能需要更改),不是专门为6410或者是神马平台写的。
下面开始讲解驱动代码,之前我跟大家说过了,有两种方法可以驱动Uart设备,一种是使用tty驱动框架,另一种是使用Uart驱动框架加上tty驱动框架混合驱动,我们首先讲第一种驱动方式,就是单纯的使用tty驱动框架,来创建并加载一个虚拟的tty设备。
一、使用tty驱动框架驱动一个tty设备:
整个讲解过程按照一个完整的驱动过程来写的,即注册->添加->读写->关闭并删除。
当有一个tty设备被检测到后,便会告诉内核,这就是注册,注册时会分配主设备号和次设备号。主设备号标识设备对应的驱动程序,告诉Linux内核使用哪一个驱动程序为该设备(也就是/dev下的设备文件)服务,而次设备号则用来标识具体且唯一的某个设备,打个比方,就像人上火车一样,火车票上标着是硬卧、软卧、硬座站票啥的,这个就是主设备号,标着啥票上车就咋样,火车票上还有个座位号,就是在这个车厢里的唯一的一个号码,对号入座,假如列车工作人员有事情喊你,不用知道你的名字,直接喊某车某号码就可以。
任何tty驱动程序的主要数据结构是结构tty_driver (linux-3.2.12\include\linux),这个结构体就是用来向tty核心注册和注销驱动的,其代码如下:
struct tty_driver {
int magic; /* 该结构体的幻数,在函数alloc_tty_driver()函数中被初始化*/
struct kref kref; /*参考管理 */
struct cdev cdev; /*定义对应的字符型设备结构体 */
struct module *owner; /*定义对应的模块所有者*/
const char *driver_name; /*设备驱动名字,用在sysfs中 */
const char *name; /*设备名,表示驱动的设备节点名,用在/proc/tty中*/
int name_base; /* offset of printed name */
int major; /*主设备号 */
int minor_start; /* 开始次设备号*/
int minor_num; /*设备数量 */
int num; /* 被分配的设备数量 */
short type; /* tty驱动的类型 */
short subtype; /* tty驱动的子类型 */
struct ktermios init_termios; /* 初始线路设置 */
int flags; /* tty驱动标志*/
struct proc_dir_entry *proc_entry; /* /proc文件系统入口 */
struct tty_driver *other; /* 仅针对PTY驱动有意义 */
/*
* tty数据结构体指针
*/
struct tty_struct **ttys;
struct ktermios **termios;
struct ktermios **termios_locked;
void *driver_state;
/*
* 驱动方法
*/
const struct tty_operations *ops;
struct list_head tty_drivers;
};
在这里要注意一下,tty_driver这个结构体和tty_struct (linux-3.2.12\include\linux)这个结构体,这两个千万不要弄混了,后者模样长得挺像是初始化用的结构体,但是不是,它的作用是tty核心使用它来保存当前特定tty端口的状态,除了少数例外,tty_struct中所有的成员几乎都能只被tty核心使用。他的代码就不贴了,位置在linux-3.2.12\include\linux,大家可以去看看,这里我们还暂时用不到。
上面说到了,当有tty设备时,就向核心注册,光注册还是不够的,为了创建tty_driver结构的对象,必须把该驱动所支持的tty设备的数量作为参数调用函数alloc_tty_driver。下面代码是alloc_tty_driver原函数代码:
struct tty_driver *alloc_tty_driver(int lines)
{
struct tty_driver *driver;
driver = kzalloc(sizeof(struct tty_driver), GFP_KERNEL);
if (driver) {
kref_init(&driver->kref);
driver->magic = TTY_DRIVER_MAGIC;
driver->num = lines;
/* later we'll move allocation of tables here */
}
return driver;
}
从上面的函数我们可以看出,tty_driver结构将根据tty驱动程序的需求用正确的信息初始化。该函数调用kzalloc函数(用kzalloc申请内存的时候,效果等同于先是用kmalloc()申请空间,然后用memset()来初始化,所有申请的元素都被初始化为0),为tty_driver申请一块合适的内存空间,申请成功后,初始化driver并将其返回。现在,我们定义一个demo_tty_driver,来接受这个返回的driver,代码如下:
demo_tty_driver = alloc_tty_driver(DEMO_TTY_MINORS)
if(!tiny_tty_driver)
return -ENOMEM
这样,我的demo_tty_driver就初始化了一个demo_tty设备,但是这里有个问题,要使得一个tty设备能够正常工作,仅仅靠这个来初始化,还是不够的,虽然它包含了很多的成员,但是在初始化时,并没有把tty设备所有的属性都初始化了,也没有必要,但是涉及到具体的使用,就必须把所有的属性设置一下,这时需要调用tty_set_operations函数,代码如下:
void tty_set_operations(struct tty_driver *driver,const struct tty_operations *op)
{
driver->ops = op;
};
从上面的代码可以看出,该函数的两个参数,一个是之前的tty_driver结构体,还有一个是tty_operations结构体,下面是tty_operations (linux-3.2.12\include\linux\Tty_driver.h)结构体的代码:
struct tty_operations {
struct tty_struct * (*lookup)(struct tty_driver *driver,struct inode *inode, int idx);
int (*install)(struct tty_driver *driver, struct tty_struct *tty);
void (*remove)(struct tty_driver *driver, struct tty_struct *tty);
int (*open)(struct tty_struct * tty, struct file * filp);
void (*close)(struct tty_struct * tty, struct file * filp);
………………
};
上面的代码很冗长,所以就省略了一些,他的核心思想就是定义了一个tty设备的基本操作。
static struct tty_operstions serial_ops = {
.open = demo_open,
.close = demo_close,
.write = demo_write,
.write_room = demo_write_room,
.set_termios = demo_set_terimos,
};
…………
/*初始化tty驱动程序*/
demo_tty_driver->ower = THIS_MODULE;
demo_tty_driver->driver_name = "tiny_tty";
…………
tty_set_operations(demo_tty_driver,&demo_serial_ops);
上面列举了如何定义以及初始化一个tty设备的代码。
为了向tty核心注册这个驱动程序,必须将tty_driver结构传递给tty_register_driver (linux-3.2.12\drivers\tty\Tty_io.c)函数(务必注意,是注册驱动,不是注册设备),注册代码如下:
int tty_register_driver(struct tty_driver *driver)
{
int error;
int i;
dev_t dev;
void **p = NULL;
struct device *d;
…………
return error;
}
函数没有贴全,他的主要作用是根据tty驱动程序所拥有的所有次设备号,创建所有的不同sysfs tty文件。但是如果系统使用了devfs(在2.6版本以后引入sysfs,devfs逐渐被取代),而且没有设置TTY_DRIVER_NO_DEVFS标志位的情况下,也将创建devfs文件。如果要让用户看到已经真实存在的设备并且为用户保持更新,则可以调用tty_register_device (linux-3.2.12\drivers\tty\ Tty_io.c),并设置该标志位。
在使用tty_register_driver注册自身后,驱动程序使用tty_register_device函数注册他所控制的硬件设备。该函数如下:
/**
* tty_register_device – 注册一个tty设备
* @driver: 描述tty设备的tty驱动
* @index: 该tty设备驱动程序的索引,也就是次设备号
* @device: 与此设备关联的设备结构体.
* 此参数可变,如果该设备结构体未知,可以设置为NULL
* 若该tty驱动标志为使得 TTY_DRIVER_DYNAMIC_DEV位置位,该函数被调用来注册一个单独的tty设备。 若该位没有被置位, 此功能不应被一个tty驱动调用。
*/
struct device *tty_register_device(struct tty_driver *driver, unsigned index,
struct device *device)
{
char name[64];
dev_t dev = MKDEV(driver->major, driver->minor_start) + index;
if (index >= driver->num) {
printk(KERN_ERR "Attempt to register invalid tty line number " " (%d).\n", index);
return ERR_PTR(-EINVAL);
}
if (driver->type == TTY_DRIVER_TYPE_PTY)
pty_line_name(driver, index, name);
else
tty_line_name(driver, index, name);
return device_create(tty_class, device, dev, NULL, name);
}
因为我们的设备是虚拟的,所以我们的驱动程序将立刻注册所有的tty设备:
for(i = 0;i < DEMO_TTY_MINORS;++i)
tty_register_device(demo_tty_driver,I,NULL);
同样的,在设备使用结束之后,需要注销和移除所有设备(因为我们这个是虚拟的,所以把我们自己创建的都干掉),调用tty_unregister_device函数清除自身,然后调用tty_unregister_driver函数注销我们创建的demo_tty_driver.
前面讲了初始化的准备工作,现在,设备初始化好了,有四个基本的动作,打开(open)、关闭(release)、读取(read)、写入(write)。还有就是这四个操作涉及到的其他的一些晓得细节操作用到的函数(比如对buffer的操作)。
首先说一下tty_open (linux-3.2.12\drivers\tty\Tty_io.c)函数,部分代码如下:
/**
* tty_open - 打开一个tty设备
* @inode: 设备文件的inode
* @filp: 指向tty的文件指针
* tty_open and tty_release保持对tty的打开设备的计数. 我们不能使用结点计数因为不同的
* 结点可能指向同一个设备.
*/
static int tty_open(struct inode *inode, struct file *filp)
{
struct tty_struct *tty = NULL;
int noctty, retval;
struct tty_driver *driver;
int index;
dev_t device = inode->i_rdev;
unsigned saved_flags = filp->f_flags;
nonseekable_open(inode, filp);
retry_open:
retval = tty_alloc_file(filp);
if (retval)
&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;
&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;
&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;
spin_unlock_irq(&current->sighand->siglock);
tty_unlock();
mutex_unlock(&tty_mutex);
return 0;
}
相对应的就是tty_release (linux-3.2.12\drivers\tty\Tty_io.c)函数,代码如下:
/**
* tty_release - vfs用于关闭的回调函数
* @inode: inode of tty
* @filp: 处理tty的文件指针
* 即使释放tty结构是一件棘手的事.. 我们不得不非常小心,因为即使该设备完全的释放之后,中
* 断就有可能得到错误的指针
*/
int tty_release(struct inode *inode, struct file *filp)
{
struct tty_struct *tty = file_tty(filp);
struct tty_struct *o_tty;
int pty_master, tty_closing, o_tty_closing, do_sleep;
int devpts;
int idx;
char buf[64];
if (tty_paranoia_check(tty, inode, "tty_release_dev"))
return 0;
tty_lock();
&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;
&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;
tty_unlock();
return 0;
}
这两个函数分别是用来打开和释放具体的一个tty设备。
下面是tty设备写函数tty_write:
/*
* tty_write - tty设备文件写入方式
* @file: tty文件指针
* @buf: 要写入的用户数据
* @count: 要写入的字节数
* @ppos: 未被用到的
*
*/
static ssize_t tty_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
struct inode *inode = file->f_path.dentry->d_inode;
struct tty_struct *tty = file_tty(file);
struct tty_ldisc *ld;
ssize_t ret;
&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;
&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;
tty_ldisc_deref(ld);
return ret;
}
以上为写函数, 到这里还涉及到另一种东西&mdash;&mdash;buffer,在Linux源代码中,关于buffer的基本操作函数有如下几个Tty_buffer.c (linux-3.2.12\drivers\tty):
- Øvoid tty_buffer_init(struct tty_struct *tty)
该函数用于buffer的初始化
- Østatic struct tty_buffer *tty_buffer_alloc(struct tty_struct *tty, size_t size)
该函数用于申请一块size大小的buffer
- Østatic void tty_buffer_free(struct tty_struct *tty, struct tty_buffer *b)
该函数用于释放相对应tty的buffer
- Øvoid tty_buffer_flush(struct tty_struct *tty)
buffer刷新
最后就是tty_read(linux-3.2.12\drivers\tty\ Tty_io.c)函数,代码如下:
/**
* tty_read - tty设备文件写入函数
* @file: tty文件指针
* @buf: 用户buffer
* @count: 用户buffer大小
* @ppos: 未使用的
*
* 执行读取系统调用函数在这个终端设备.
*/
static ssize_t tty_read(struct file *file, char __user *buf, size_t count,loff_t *ppos)
{
int i;
struct inode *inode = file->f_path.dentry->d_inode;
struct tty_struct *tty = file_tty(file);
struct tty_ldisc *ld;
if (tty_paranoia_check(tty, inode, "tty_read"))
return -EIO;
if (!tty || (test_bit(TTY_IO_ERROR, &tty->flags)))
return -EIO;
ld = tty_ldisc_ref_wait(tty);
if (ld->ops->read)
i = (ld->ops->read)(tty, file, buf, count);
else
i = -EIO;
tty_ldisc_deref(ld);
if (i > 0)
inode->i_atime = current_fs_time(inode->i_sb);
return i;
}
tty设备的简单的操作已经讲完了,当然这只是基本操作,还有诸如更改波特率之类的细节函数并没有一一去讲。下面我们讲一下第二种驱动方法,严格来说,是使用了tty驱动框架和Uart独有的驱动代码混合驱动Uart设备。
二、使用Uart驱动框架和tty驱动框架混合驱动一个Uart设备:
Uart驱动程序围绕三个关键的数据结构展开的,在讲述第一种驱动方法的时候大家可能注意到了,有两个文件里的函数我们没有用到:
Serial_core.c (linux-3.2.12\drivers\tty\serial)
Serial_core.h (linux-3.2.12\include\linux)
这两个文件正是定义了Uart独立驱动的结构体和API的地方,我们先来说一下刚才提到的三个关键的结构体,他们在Serial_core.h (linux-3.2.12\include\linux)文件中。
(1)、特定Uart相关的驱动程序结构,struct uart_driver:
struct uart_driver {
struct module *owner;/*拥有这个结构体的模块*/
const char *driver_name; /*驱动名字*/
const char *dev_name; /*设备名字*/
int major; /*主设备号*/
int minor; /*次设备号*/
int nr;
struct console *cons;
/*
*这些是私有的; 等级低的驱动不能涉及到这些; 他们应该被初始化为NULL
*/
struct uart_state *state;
struct tty_driver *tty_driver;
};
(2)、struct uart_port,该结构体用来对Uart的端口进行初始化和配置,代码如下:
struct uart_port {
spinlock_t lock; /* 端口锁 */
unsigned long iobase; /*输入输出[bwl] */
unsigned char __iomem *membase; /* 读写[bwl] */
unsigned int (*serial_in)(struct uart_port *, int);
void (*serial_out)(struct uart_port *, int, int);
void (*set_termios)(struct uart_port *,struct ktermios *new,struct ktermios *old);
int (*handle_irq)(struct uart_port *);
void (*pm)(struct uart_port *, unsigned int state, unsigned int old);
unsigned int irq; /* 中断号 */
unsigned long irqflags; /* 中断标志位 */
unsigned int uartclk; /*uart基础时钟 */
unsigned int fifosize; /* 发送fifo深度 */
unsigned char x_char; /* xon/xoff char */
unsigned char regshift; /* reg offset shift */
unsigned char iotype; /* io通道类型 */
&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;
&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;
};
(3)、struct uart_ops结构体是每一个Uart驱动程序必须支持的物理硬件上可以完成的入口函数的超集,这里面包括了个猴子那个对Uart端口的操作函数,代码如下:
/*
* 该机构体描述了所有的能够被用在硬件上的操作
*/
struct uart_ops {
unsigned int (*tx_empty)(struct uart_port *);
void (*set_mctrl)(struct uart_port *, unsigned int mctrl);
unsigned int (*get_mctrl)(struct uart_port *);
void (*stop_tx)(struct uart_port *);
void (*start_tx)(struct uart_port *);
void (*send_xchar)(struct uart_port *, char ch);
void (*stop_rx)(struct uart_port *);
void (*enable_ms)(struct uart_port *);
void (*break_ctl)(struct uart_port *, int ctl);
int (*startup)(struct uart_port *);
void (*shutdown)(struct uart_port *);
void (*flush_buffer)(struct uart_port *);
void (*set_termios)(struct uart_port *, struct ktermios *new,struct ktermios *old);
void (*set_ldisc)(struct uart_port *, int new);
void (*pm)(struct uart_port *, unsigned int state,unsigned int oldstate);
int (*set_wake)(struct uart_port *, unsigned int state);
/*
* 返回一个描述该端口类型的字符串
*/
const char *(*type)(struct uart_port *);
/*
* 释放被端口占用的IO和存储器资源。.
*/
void (*release_port)(struct uart_port *);
/*
* Request IO and memory resources used by the port.
* This includes iomapping the port if necessary.
*/
int (*request_port)(struct uart_port *);
void (*config_port)(struct uart_port *, int);
int (*verify_port)(struct uart_port *, struct serial_struct *);
int (*ioctl)(struct uart_port *, unsigned int, unsigned long);
#ifdef CONFIG_CONSOLE_POLL
void (*poll_put_char)(struct uart_port *, unsigned char);
int (*poll_get_char)(struct uart_port *);
#endif
};
该说的结构体说完了,都是最基本的,然后就是操作了,和使用tty结构驱动一样,首先是注册,然后是添加,然后是使用,最后是移除并释放资源。
一个Uart要想使用,第一步是注册,就是要告诉kernel,我这里有个Uart,你给我登记,给我编号,这样在调用的时候直接调用该编号就可以了。注册函数为uart_register_driver(linux-3.2.12\drivers\tty\serial\Serial_core.c),以下为其源代码:
/**
* uart_register_driver –使用Uart层驱动注册一个Uart设备
* @drv: 低等级驱动结构体
*
* 使用核心驱动注册一个Uart驱动.我们依次注册tty层,并初始化这个核心驱动的每一个状态。
*
* drv->port 应该为NULL, 并且每一个端口均需要该函数注册,注册成功后使用uart_add_one_port函数添加该端口,完成注册。
*/
int uart_register_driver(struct uart_driver *drv)
{
struct tty_driver *normal;
&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;&hellip;
normal->owner = drv->owner;
normal->driver_name = drv->driver_name;
normal->name = drv->dev_name;
normal->major = drv->major;
normal->minor_start = drv->minor;
normal->type = TTY_DRIVER_TYPE_SERIAL;
normal->subtype = SERIAL_TYPE_NORMAL;
normal->init_termios = tty_std_termios;
normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;
normal->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
normal->driver_state = drv;
tty_set_operations(normal, &uart_ops);
/*
*初始化UART状态.
*/
for (i = 0; i < drv->nr; i++) {
struct uart_state *state = drv->state + i;
struct tty_port *port = &state->port;
tty_port_init(port);
port->ops = &uart_port_ops;
port->close_delay = 500; /* .5 seconds */
port->closing_wait = 30000; /* 30 seconds */
}
retval = tty_register_driver(normal);
if (retval >= 0)
return retval;
put_tty_driver(normal);
out_kfree:
kfree(drv->state);
out:
return -ENOMEM;
}
注册完之后,如程序里的解释说的一样,需要调用uart_add_one_port函数添加该端口,其代码如下:
/**
* uart_add_one_port -附加一个已定义的端口结构
* @drv:指向该端口的低等级驱动的uart的指针
* @uport: uart port structure to use for this port.
*
* This allows the driver to register its own uart_port structure
* with the core driver. The main purpose is to allow the low
* level uart drivers to expand uart_port, rather than having yet
* more levels of structures.
*/
int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
{
struct uart_state *state;
struct tty_port *port;
int ret = 0;
struct device *tty_dev;
BUG_ON(in_interrupt());
if (uport->line >= drv->nr)
return -EINVAL;
state = drv->state + uport->line;
port = &state->port;
mutex_lock(&port_mutex);
mutex_lock(&port->mutex);
if (state->uart_port) {
ret = -EINVAL;
goto out;
}
state->uart_port = uport;
state->pm_state = -1;
uport->cons = drv->cons;
uport->state = state;
/*
*如果该端口是控制台,那么自旋锁也将被初始化。
*/
if (!(uart_console(uport) && (uport->cons->flags & CON_ENABLED))) {
spin_lock_init(&uport->lock);
lockdep_set_class(&uport->lock, &port_lock_key);
}
uart_configure_port(drv, state, uport);
/*
* 注册该端口无论他是否被探测到. 这允许运行一些用于改变这个端口参数。
*/
tty_dev = tty_register_device(drv->tty_driver, uport->line, uport->dev);
if (likely(!IS_ERR(tty_dev))) {
device_init_wakeup(tty_dev, 1);
device_set_wakeup_enable(tty_dev, 0);
} else
printk(KERN_ERR "Cannot register tty device on line %d\n",
uport->line);
/*
*确保UPF_DEAD是没有被设置的.
*/
uport->flags &= ~UPF_DEAD;
out:
mutex_unlock(&port->mutex);
mutex_unlock(&port_mutex);
return ret;
}
Uart有注册和添加,就有移除和注销,移除函数为uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport) (linux-3.2.12\drivers\tty\serial\Serial_core.c),注销函数为uart_unregister_driver(struct uart_driver *drv)(linux-3.2.12\drivers\tty\serial\Serial_core.c),调用顺序是先移除后注销。
添加和移除说完了,下面说说Uart的功能函数。所谓的功能函数,就是用来实现Uart的功能的,我们这里只讲最基本的,就是发送、接收功能。
先讲发送功能,发送功能函数有两个,如下:
(1)static int uart_write(struct tty_struct *tty, const unsigned char *buf, int count)
该函数用来往buffer里面写字符串的,写完后将字符串发送出去。
(2)static int uart_put_char(struct tty_struct *tty, unsigned char ch)
该函数是用来发送单个字符的
另外注意一下,static void uart_send_xchar(struct tty_struct *tty, char ch)函数是用来发送一个高优先级的XON或者XOFF字符给tty设备,经常有人会把它搞混了,当成是发送单个字符。
下面说一下接收功能,发送用的是uart_write,接收是不是uart_read呢?答案是:不是!没有read函数的,因为发送是用户主动地,而接收(即用户调用read()函数)则是读一片缓冲区中已经放好的数据。tty核心在一个称为void tty_flip_buffer_push(struct tty_struct *tty)函数中的缓冲数据直到他被用户请求。因为tty核心提供了缓冲逻辑,因此每个tty驱动并非一定要实现他自身的缓冲逻辑。
对于buffer,有一下几个常用的基本函数,如下:
(1)、static struct tty_buffer *tty_buffer_alloc(struct tty_struct *tty, size_t size)(linux-3.2.12\drivers\tty\Tty_buffer.c)用于申请一个size大小的buffer
(2)、static void tty_buffer_free(struct tty_struct *tty, struct tty_buffer *b)(linux-3.2.12\drivers\tty\Tty_buffer.c)用于释放一块指定地址的buffer
(3)、void tty_buffer_flush(struct tty_struct *tty)(linux-3.2.12\drivers\tty\Tty_buffer.c)
用于刷新buffer
(4)、void tty_buffer_init(struct tty_struct *tty)(linux-3.2.12\drivers\tty\Tty_buffer.c)
用于buffer初始化
(5)、static inline int tty_insert_flip_char(struct tty_struct *tty,unsigned char ch, char flag)(linux-3.2.12\include\linux\Tty_flip.h)用于向buffer中插入单个字符
(6)、static inline int tty_insert_flip_string(struct tty_struct *tty, const unsigned char *chars, size_t size)(linux-3.2.12\include\linux\Tty_flip.h)用于向buffer中插入一个字符串
以上就是使用tty框架和Uart独立代码混合驱动一个Uart设备,这只是简单的驱动,没有涉及到很复杂的功能,我在看这部分的代码的时候发现还有加密等功能,笔记中没有讲到,因为一个tty设备说白了就是输入输出,这就是最基本的了。
dianzijiangren
QQ:991844552 |
|