在Linux系统中,终端是一类字符型设备,它包括多种类型,通常使用tty来简称各种类型的终端设备。由于串口也是一种终端,因此这里引入终端这个概念 。
Linux tty子系统包含:tty核心,tty线路规程和tty驱动。tty核心是对整个tty设备的抽象,对用户提供统一的接口,tty线路规程是对传输数据的格式化,tty驱动则是面向tty设备的硬件驱动。它们的关系如下图:
简单来分的话可以说成两层,一层是下层我们的串口驱动层,它直接与硬件相接触,我们需要填充一个 struct uart_ops 的结构体,另一层是上层 tty 层,包括 tty 核心以及线路规程,它们各自都有一个 ops 结构,用户空通过间是 tty 注册的字符设备节点来访问,这么说来如上图所示涉及到了4个 ops 结构了,层层跳转。下面,就来分析分析它们的层次结构。
从整个串口驱动注册程序来看,该程序只做了两件事:
1、注册uart_driver;
2、注册platform_driver,也就是uart_add_one_port();
具体请细看下面的源码分析:
在 s3c2440 平台,它是这样来注册串口驱动的,分配一个struct uart_driver 简单填充,并调用uart_register_driver 注册到内核中去。
static struct uart_driver s3c24xx_uart_drv = {
.owner = THIS_MODULE,
.dev_name = "s3c2410_serial",
.nr = CONFIG_SERIAL_SAMSUNG_UARTS,
.cons = S3C24XX_SERIAL_CONSOLE,
.driver_name = S3C24XX_SERIAL_NAME,
.major = S3C24XX_SERIAL_MAJOR,
.minor = S3C24XX_SERIAL_MINOR,
};
static int __init s3c24xx_serial_modinit(void)
{
int ret;
ret = uart_register_driver(&s3c24xx_uart_drv);
if (ret < 0) {
printk(KERN_ERR "failed to register UART driver\n");
return -1;
}
return 0;
}
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 代表的是上层,它会在 register_uart_driver 中的过程中赋值,而uart_state 则代表下层,uart_state 也会在register_uart_driver 的过程中分配空间,但是它里面真正设置硬件相关的东西是 uart_state->uart_port ,这个uart_port 是需要我们从其它地方调用 uart_add_one_port 来添加的。
下面先分析串口驱动层的uart_state:
struct uart_state {
struct tty_port port;
int pm_state;
struct circ_buf xmit;
struct tasklet_struct tlet;
struct uart_port *uart_port; // 对应于一个串口设备
};
分配空间uart_driver->uart_state[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 */
};
这个结构体,是需要我们自己来填充的,比如我们 s3c2440 有3个串口,那么就需要填充3个 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 ,这个也是需要我们自己来实现的,后面代码会详细分析从用户层到硬件层是如何一步步通过ops进行调用的。
struct uart_ops {
unsigned int (*tx_empty)(struct uart_port *); /* 串口的Tx FIFO缓存是否为空 */
void (*set_mctrl)(struct uart_port *, unsigned int mctrl); /* 设置串口modem控制 */
unsigned int (*get_mctrl)(struct uart_port *); /* 获取串口modem控制 */
void (*stop_tx)(struct uart_port *); /* 禁止串口发送数据 */
void (*start_tx)(struct uart_port *); /* 使能串口发送数据 */
void (*send_xchar)(struct uart_port *, char ch); /* 发送xChar */
void (*stop_rx)(struct uart_port *); /* 禁止串口接收数据 */
void (*enable_ms)(struct uart_port *); /* 使能modem的状态信号 */
void (*break_ctl)(struct uart_port *, int ctl); /* 设置break信号 */
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 *);/* 设置线路规程 */
void (*pm)(struct uart_port *, unsigned int state,
unsigned int oldstate); /* 串口电源管理 */
int (*set_wake)(struct uart_port *, unsigned int state);
/*
* Return a string describing the type of the port
*/
const char *(*type)(struct uart_port *);
/*
* Release IO and memory resources used by the port.
* This includes iounmap if necessary.
*/
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 *); /* 申请必要的IO端口/IO内存资源,必要时还可以重新映射串口端口 */
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 层要从 register_uart_driver 来看起了,因为 tty_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);
/*
* 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);
}
uart_register_driver(&s3c24xx_uart_drv)注册驱动时会有下面几个操作:
1、分配空间uart_driver->uart_state[nr]空间,即申请 nr 个 uart_state 空间,用来存放驱动所支持的串口(端口)的物理信息;
2、分配tty_driver,将dev_name/major/minor/nr赋值给tty_driver;
3、设置ty_core层的ops为strucy tty_operations uart_ops,设置flags为TTY_DRIVER_REAL_RAM|TTY_DRIVER_DYNAMIC_DEV;
4、初始化tty_port:uart_driver->uart_state[nr]->tty_port,如ttybuffer、flush_to_ldisc、tty_port->ops=&uart_port_ops(struct uart_port_ops uart_port_ops);
5、注册tty_driver,后面从源码中可以分析出uart驱动的注册,实际就是tty_driver的注册,都是将uart的参数传给tty_driver,然后注册字符设备、分配设备文件、将驱动写进总线管理的tty_driver链表中;
注:从代码可以看到uart_register_drive()函数中最终要调用tty_register_driver()函数来注册tty驱动,因此与用户空间打交道的工作完全交给了 tty_driver ,而且这一部分都是内核实现好的,我们不需要修改,了解一下工作原理即可;
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
};
这个是 tty 核心的 ops ,简单一看,后面分析调用关系时,我们在来看具体的里边的函数,下面来看 tty_driver 的注册。
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;
}
从上面源码可以分析到:
1、为线路规程和termios分配空间,并使 tty_driver 相应的成员指向它们。
2、注册字符设备,名字是 uart_driver->name 我们这里是“ttySAC”,文件操作函数集是 tty_fops。
3、将该 uart_driver->tty_drivers 添加到全局链表 tty_drivers 。
4、向 proc 文件系统添加 driver。
该函数就是实现将驱动挂到platform总线上去,总线接管设备和驱动的管理工作。
int s3c24xx_serial_init(struct platform_driver *drv,
struct s3c24xx_uart_info *info)
{
dbg("s3c24xx_serial_init(%p,%p)\n", drv, info);
#ifdef CONFIG_PM
drv->suspend = s3c24xx_serial_suspend;
drv->resume = s3c24xx_serial_resume;
#endif
return platform_driver_register(drv);
}
static void __exit s3c2440_serial_exit(void)
{
platform_driver_unregister(&s3c2440_serial_driver);
}
module_init(s3c2440_serial_init);
module_exit(s3c2440_serial_exit);
然后我们分析一下platform_driver函数:
static struct platform_driver s3c2440_serial_driver = {
.probe = s3c2440_serial_probe,
.remove = __devexit_p(s3c24xx_serial_remove),
.driver = {
.name = "s3c2440-uart",
.owner = THIS_MODULE,
},
};
在平台设备注册驱动的时候,总线会对驱动和设备匹配,如果匹配成功,将调用驱动的prob函数,我们具体追一下s3c2440_serial_driver 的prob函数:
int s3c24xx_serial_probe(struct platform_device *dev,
struct s3c24xx_uart_info *info)
{
struct s3c24xx_uart_port *ourport;
int ret;
dbg("s3c24xx_serial_probe(%p, %p) %d\n", dev, info, probe_index);
ourport = &s3c24xx_serial_ports[probe_index];
probe_index++;
dbg("%s: initialising port %p...\n", __func__, ourport);
ret = s3c24xx_serial_init_port(ourport, info, dev);
if (ret < 0)
goto probe_err;
dbg("%s: adding port\n", __func__);
uart_add_one_port(&s3c24xx_uart_drv, &ourport->port);
platform_set_drvdata(dev, &ourport->port);
ret = device_create_file(&dev->dev, &dev_attr_clock_source);
if (ret < 0)
printk(KERN_ERR "%s: failed to add clksrc attr.\n", __func__);
ret = s3c24xx_serial_cpufreq_register(ourport);
if (ret < 0)
dev_err(&dev->dev, "failed to add cpufreq notifier\n");
return 0;
probe_err:
return ret;
}
我们来分析上面这段代码:
这段代码首先从s3c24xx_serial_ports数组中寻找一个元素,这个数组里保存的是各个串口的信息。
假如说找到了串口0,拿到串口0后调用s3c24xx_serial_init_port完成串口的初始化,看看初始化函数:
/* s3c24xx_serial_init_port
*
* initialise a single serial port from the platform device given
*/
static int s3c24xx_serial_init_port(struct s3c24xx_uart_port *ourport,
struct s3c24xx_uart_info *info,
struct platform_device *platdev)
{
struct uart_port *port = &ourport->port;
struct s3c2410_uartcfg *cfg;
struct resource *res;
int ret;
dbg("s3c24xx_serial_init_port: port=%p, platdev=%p\n", port, platdev);
if (platdev == NULL)
return -ENODEV;
cfg = s3c24xx_dev_to_cfg(&platdev->dev);
if (port->mapbase != 0)
return 0;
if (cfg->hwport > CONFIG_SERIAL_SAMSUNG_UARTS) {
printk(KERN_ERR "%s: port %d bigger than %d\n", __func__,
cfg->hwport, CONFIG_SERIAL_SAMSUNG_UARTS);
return -ERANGE;
}
/* setup info for port */
port->dev = &platdev->dev;
ourport->info = info;
/* copy the info in from provided structure */
ourport->port.fifosize = info->fifosize;
dbg("s3c24xx_serial_init_port: %p (hw %d)...\n", port, cfg->hwport);
port->uartclk = 1;
if (cfg->uart_flags & UPF_CONS_FLOW) {
dbg("s3c24xx_serial_init_port: enabling flow control\n");
port->flags |= UPF_CONS_FLOW;
}
/* sort our the physical and virtual addresses for each UART */
res = platform_get_resource(platdev, IORESOURCE_MEM, 0);// 取得物理地址
if (res == NULL) {
printk(KERN_ERR "failed to find memory resource for uart\n");
return -EINVAL;
}
dbg("resource %p (%lx..%lx)\n", res, res->start, res->end);
port->mapbase = res->start;
port->membase = S3C_VA_UART + res->start - (S3C_PA_UART & 0xfff00000); // 静态映射
ret = platform_get_irq(platdev, 0);
if (ret < 0)
port->irq = 0;
else {
port->irq = ret;
ourport->rx_irq = ret;
ourport->tx_irq = ret + 1;
}
ret = platform_get_irq(platdev, 1);
if (ret > 0)
ourport->tx_irq = ret;
ourport->clk = clk_get(&platdev->dev, "uart");
dbg("port: map=%08x, mem=%08x, irq=%d (%d,%d), clock=%ld\n",
port->mapbase, port->membase, port->irq,
ourport->rx_irq, ourport->tx_irq, port->uartclk);
/* reset the fifos (and setup the uart) */
s3c24xx_serial_resetport(port, cfg);
return 0;
}
上面代码主要完成3项工作:
1、取串口的基地址
2、取串口的中断号
3、复位FIFO
在回到s3c24xx_serial_probe函数,在初始化串口后,接下来完成下面的操作:
1、添加端口uart_add_one_port
2、添加属性文件,这样在sys下面就可以看到串口的信息了
3、初始化动态频率调节s3c24xx_serial_cpufreq_register。
至此uart_driver和platform_driver都已注册完毕,后续章节会细讲TTY驱动数据的收发过程。