tty框架如下图所示:
整个 uart 框架大概的样子如上图所示,大致可以分为四层,一层是下层我们的串口驱动层,它直接与硬件相接触,我们需要填充一个 struct uart_ops 的结构体,再向上是tty核心层,在向上是线路规程,再向上是是直接和用户空间对接的,它们每一层都有一个 Ops 结构,用户空间通过tty注册的字符设备节点来访问,这么说来如上图所示涉及到了4个 ops 结构了,层层跳转。其中我们想要添加一个驱动时,主要完成的工作就是底层驱动,其他三层内核已经实现。下面,就来分析分析它们的层次结构。以amba_pl011驱动为例。
用户空间通过函数open、write等首先会调用到tty用户空间的tty_open函数,然后tty_open函数会调用线路规程函数,然后线路规程的函数会调用tty核心层的函数,tty核心层的函数也可以直接调用tty核心层的函数(不经过线路规程),最终tty驱动层的函数在调用tty设备驱动。即:
tty_open->n_tty_open->uart_open->pl011_startup或者是
tty_open->uart_open->pl011_startup
在amba_pl011中,它是这样来注册串口驱动的,分配一个struct uart_driver 简单填充,并调用uart_register_driver 注册到内核中去。
static struct uart_driver amba_reg = {
.owner = THIS_MODULE,
.driver_name = "ttyAMA",
.dev_name = "ttyAMA",
.major = SERIAL_AMBA_MAJOR,
.minor = SERIAL_AMBA_MINOR,
.nr = UART_NR,
.cons = AMBA_CONSOLE,
};
static int __init pl011_init(void)
{
int ret;
printk(KERN_INFO "Serial: AMBA PL011 UART driver\n");
ret = uart_register_driver(&amba_reg);
if (ret == 0) {
ret = amba_driver_register(&pl011_driver);
if (ret)
uart_unregister_driver(&amba_reg);
}
return ret;
}
uart_driver 中,我们只是填充了一些名字、设备号等信息,这些都是不涉及底层硬件访问的,那是怎么回事呢?来看一下完整的 uart_driver 结构或许就明白了。
struct uart_driver {
struct module *owner; /* 拥有该uart_driver的模块,一般为THIS_MODULE */
const char *driver_name; /* 串口驱动名,串口设备文件名以驱动名为基础 */
const char *dev_name; /* 串口设备名 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
int nr; /* 该uart_driver支持的串口个数(最大) */
struct console *cons; /* 其对应的console.若该uart_driver支持serial console,否则为NULL */
/* 下面这俩,它们应该被初始化为NULL */
struct uart_state *state;//下层,串口驱动层
struct tty_driver *tty_driver; /* tty相关 */
};
在我们上边填充的结构体中,有两个成员未被赋值,他们分别代表底层和上层,tty_driver 代表的是上层,他是tty_register_driver函数的参数,咱们注册的是一个tty驱动,tty_driver是tty驱动的结构体,它会在register_uart_driver中的过程中赋值。uart_state则代表下层,uart_state会在register_uart_driver 的过程中分配空间(他会根据支持的最大串口数量开辟相应的空间uart_driver->nr),它里面真正和硬件相关的结构是是uart_state->uart_port,这个uart_port是需要我们从其它地方调用uart_add_one_port来添加的。 在amba_pl011中是用probe函数添加uart_port。
1、设备驱动层(下层)
struct uart_state {
struct tty_port port;
int pm_state;
struct circ_buf xmit;//缓冲区,发送数据和接收数据的存储空间
struct tasklet_struct tlet;
struct uart_port *uart_port; //对应于一个实际的串口设备
};
在注册 driver 时,会根据 uart_driver->nr 来申请 nr 个 uart_state 空间,用来存放驱动所支持的串口(端口)的物理信息。
struct uart_port {
spinlock_t lock; /* port lock */
unsigned long iobase; /* io端口基地址(物理) */
unsigned char __iomem *membase; /* io内存基地址(虚拟) */
unsigned int (*serial_in)(struct uart_port *, int);
void (*serial_out)(struct uart_port *, int, int);
unsigned int irq; /* 中断号 */
unsigned long irqflags; /* 中断标志 */
unsigned int uartclk; /* 串口时钟 */
unsigned int fifosize; /* 串口缓冲区大小 */
unsigned char x_char; /* xon/xoff char */
unsigned char regshift; /* 寄存器位移 */
unsigned char iotype; /* IO访问方式 */
unsigned char unused1;
unsigned int read_status_mask; /* 关心 Rx error status */
unsigned int ignore_status_mask; /* 忽略 Rx error status */
struct uart_state *state; /* pointer to parent state */
struct uart_icount icount; /* 串口信息计数器 */
struct console *cons; /* struct console, if any */
#if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(SUPPORT_SYSRQ)
unsigned long sysrq; /* sysrq timeout */
#endif
upf_t flags;
unsigned int mctrl; /* 当前的Moden 设置 */
unsigned int timeout; /* character-based timeout */
unsigned int type; /* 端口类型 */
const struct uart_ops *ops; /* 串口端口操作函数 */
unsigned int custom_divisor;
unsigned int line; /* 端口索引 */
resource_size_t mapbase; /* io内存物理基地址 */
struct device *dev; /* 父设备 */
unsigned char hub6; /* this should be in the 8250 driver */
unsigned char suspended;
unsigned char unused[2];
void *private_data; /* generic platform data pointer */
};
这个结构体,是需要我们自己来填充的,有几个串口,那么就需要填充几个uart_port ,并且通过 uart_add_one_port 添加到 uart_driver->uart_state->uart_port 中去。当然uart_driver 有多个 uart_state ,每个 uart_state 有一个 uart_port 。在 uart_port 里还有一个非常重要的成员 struct uart_ops *ops ,这个是具体的寄存器操作函数,是需要我们自己来实现的,也就和裸机驱动差不多。
static struct uart_ops amba_pl011_pops = {
.tx_empty = pl01x_tx_empty, //串口的tx_fifo是否为空
.set_mctrl = pl011_set_mctrl, //设置串口的modem控制,xyz
.get_mctrl = pl01x_get_mctrl, //获取modem设置
.stop_tx = pl011_stop_tx, //停止传输
.start_tx = pl011_start_tx, //开始传输
.stop_rx = pl011_stop_rx, //停止接收
.enable_ms = pl011_enable_ms, //使能modem的状态信号
.break_ctl = pl011_break_ctl, //设置break信号
.startup = pl011_startup, //使能串口,用户调用open使最终会调用此函数
.shutdown = pl011_shutdown, // 关闭串口,应用程序关闭串口设备文件时,该函数会被调用
.flush_buffer = pl011_dma_flush_buffer,
.set_termios = pl011_set_termios, //设置串口属性,包括波特率等
.type = pl011_type, //判断串口类型是否为amba
.release_port = pl010_release_port, //释放端口使用的内存
.request_port = pl010_request_port, //请求端口使用的内存
.config_port = pl010_config_port, //设置端口类型并申请端口使用的内存
.verify_port = pl010_verify_port, //检验串口属性,包括总线类型和波特率
#ifdef CONFIG_CONSOLE_POLL
.poll_get_char = pl010_get_poll_char, //获取console的输入
.poll_put_char = pl010_put_poll_char, //将数据显示到console中
#endif
};
2、tty核心层(上层)
底层驱动层和tty层之间的联系需要从register_uart_driver中分析,tty_driver是在uart_driver注册过程中构建的。
int uart_register_driver(struct uart_driver *drv)
{
struct tty_driver *normal = NULL;
int i, retval;
/* 根据driver支持的最大设备数,申请n个 uart_state 空间,每一个 uart_state
都有一个uart_port,支持多少个串口,就开辟多少块空间 */
drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);
/* tty层:分配一个 tty_driver ,并将drv->tty_driver 指向它 */
normal = alloc_tty_driver(drv->nr);
drv->tty_driver = normal;
/* 对 tty_driver 进行设置 */
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);//设置tty驱动层的处理函数
/*
* Initialise the UART state(s).
*/
for (i = 0; i < drv->nr; i++) {
struct uart_state *state = drv->state + i;
struct tty_port *port = &state->port; /* driver->state->tty_port */
tty_port_init(port);
port->close_delay = 500; /* .5 seconds */
port->closing_wait = 30000; /* 30 seconds */
/* 初始化 tasklet */
tasklet_init(&state->tlet, uart_tasklet_action, (unsigned long)state);
}
/* tty层:注册 driver->tty_driver */
retval = tty_register_driver(normal);
}
注册过程主要做的工作如下所示:
1、根据driver支持的最大设备数,申请n个uart_state空间,每一个uart_state都对应一个 uart_port 。
2、分配一个tty_driver空间并指向drv->tty_driver。
3、对tty_driver进行设置,其中包括默认波特率、校验方式等,还有一个重要的 Ops ,uart_ops ,它是tty核心与我们串口驱动通信的接口。
4、初始化每一个 uart_state 的 tasklet 。
5、注册 tty_driver 。
注册uart_driver实际上是注册一个tty_driver的过程,最终生成的设备节点也是tty的,与用户空间打交道的工作也是由用户空间层的函数实现的,好在这一部分内核已经帮我们实现好的,我们只需要知道他们需要什么机构,套用一下他们的框架就可以了。
如下所示为tty核心层的函数:这一层的函数通过如下方式调用设备驱动层
uart_state->uart_port->ops
static const struct tty_operations uart_ops = {
.open = uart_open,
.close = uart_close,
.write = uart_write,
.put_char = uart_put_char, // 单字节写函数
.flush_chars = uart_flush_chars, // 刷新数据到硬件函数
.write_room = uart_write_room, // 指示多少缓冲空闲的函数
.chars_in_buffer= uart_chars_in_buffer, // 只是多少缓冲满的函数
.flush_buffer = uart_flush_buffer, // 刷新数据到硬件
.ioctl = uart_ioctl,
.throttle = uart_throttle,
.unthrottle = uart_unthrottle,
.send_xchar = uart_send_xchar,
.set_termios = uart_set_termios, // 当termios设置被改变时又tty核心调用
.set_ldisc = uart_set_ldisc, // 设置线路规程函数
.stop = uart_stop,
.start = uart_start,
.hangup = uart_hangup, // 挂起函数,当驱动挂起tty设备时调用
.break_ctl = uart_break_ctl, // 线路中断控制函数
.wait_until_sent= uart_wait_until_sent,
#ifdef CONFIG_PROC_FS
.proc_fops = &uart_proc_fops,
#endif
.tiocmget = uart_tiocmget, // 获得当前tty的线路规程的设置
.tiocmset = uart_tiocmset, // 设置当前tty线路规程的设置
#ifdef CONFIG_CONSOLE_POLL
.poll_init = uart_poll_init,
.poll_get_char = uart_poll_get_char,
.poll_put_char = uart_poll_put_char,
#endif
};
3、用户空间函数分析
int tty_register_driver(struct tty_driver *driver)
{
int error;
int i;
dev_t dev;
void **p = NULL;
if (!(driver->flags & TTY_DRIVER_DEVPTS_MEM) && driver->num) {
p = kzalloc(driver->num * 2 * sizeof(void *), GFP_KERNEL);
}
/* 申请设备号 */
if (!driver->major) {//设备号未知的情况
error = alloc_chrdev_region(&dev, driver->minor_start,
driver->num, driver->name);
} else {//设备号已知的情况
dev = MKDEV(driver->major, driver->minor_start);
error = register_chrdev_region(dev, driver->num, driver->name);
}
if (p) { /* 为线路规程和termios分配空间 */
driver->ttys = (struct tty_struct **)p;
driver->termios = (struct ktermios **)(p + driver->num);
} else {
driver->ttys = NULL;
driver->termios = NULL;
}
/* 创建字符设备,使用 tty_fops */
cdev_init(&driver->cdev, &tty_fops);
driver->cdev.owner = driver->owner;
error = cdev_add(&driver->cdev, dev, driver->num);
mutex_lock(&tty_mutex);
/* 将该 driver->tty_drivers 添加到全局链表 tty_drivers */
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++)
tty_register_device(driver, i, NULL);
}
/* proc 文件系统注册driver */
proc_tty_register_driver(driver);
driver->flags |= TTY_DRIVER_INSTALLED;
return 0;
}
tty_driver 注册过程干了哪些事:
1、为线路规程和termios分配空间,并使 tty_driver 相应的成员指向它们。
2、注册字符设备,名字是 uart_driver->name 我们这里是“ttyAMA”,文件操作函数集是 tty_fops。
3、将该 uart_driver->tty_drivers 添加到全局链表 tty_drivers 。
4、向proc文件系统添加driver,提供调试节点。