分类: LINUX
2015-01-04 23:44:50
原文地址:linux uart设备驱动结构分析 作者:Reny
这里的uart设备是指物理设备,例如MPC8xx中CPM中的SMC,这个设备可以工作在uart模式下。
在linux中,uart设备可以抽象成两类设备:serial设备和console设备。在cpm_uart_core.c中可以清楚的发现,对uart设备实现了两套驱动,分别针对这两类设备。Serial设备的抽象定义在serial_core.c中,而console设备的抽象定义在vt.c中。这两种抽象设备又统一于tty设备。tty设备是一种字符型设备。
Console设备只用于内核空间的printk打印;而serial设备则可用于用户空间。
struct uart_driver {
struct module *owner;
const char *driver_name;
const char *dev_name;
int major;
int minor;
int nr;
struct console *cons;
struct uart_state *state;
struct tty_driver *tty_driver;
};
其中tty_driver结构用于向tty层注册,而uart_state结构用于管理物理设备。
在cpm_uart_core.c中有如下定义:
static struct uart_driver cpm_reg = {
.owner = THIS_MODULE,
.driver_name = "ttyCPM",
.dev_name = "ttyCPM",
.major = SERIAL_CPM_MAJOR,
.minor = SERIAL_CPM_MINOR,
.cons = CPM_UART_CONSOLE,
.nr = UART_NR,
};
这个是模块的初始化函数,从这里开始分析。
传入的参数就是上面提到的cpm_reg。
(1)申请数据结构
申请UART_NR个uart_state,并使cpm_reg.state指针指向新申请的空间;
申请UART_NR个tty_driver,并使cpm_reg.tty_driver指针指向新申请的空间;
UART_NR,其值的实际大小定义在cpm_uart.h中:
#define UART_NR fs_uart_nr
fs_uart_nr定义在fs_uart_pd.h中:
enum fs_uart_id {
fsid_smc1_uart,
fsid_smc2_uart,
fsid_scc1_uart,
fsid_scc2_uart,
fsid_scc3_uart,
fsid_scc4_uart,
fs_uart_nr,
};
(2)初始化cpm_reg.tty_driver
1)结构中的变量;
2)使cpm_reg.tty_driver->driver_state指针指向cpm_reg;
3)tty_set_operations(normal, &uart_ops);
设定tty_driver的操作函数集为uart_ops。Uart_ops是定义在serial_core.c中静态变量:
static const struct tty_operations uart_ops = {
.open = uart_open,
.close = uart_close,
.write = uart_write,
.put_char = uart_put_char,
……
};
从这里可以看出,很明显serial设备层是对各种uart物理设备的一种抽象,统一使用同样的操作函数。
(3)初始化cpm_reg.state
1)结构变量值
2)cpm_reg.state->uart_info
3)tasklet下半部注册
传入的参数是cpm_reg->tty_driver。
(1)将tty_driver代表的tty设备注册为字符设备
(2)设定字符设备的操作函数集为静态定义的tty_fops
static const struct file_operations tty_fops = {
.llseek = no_llseek,
.read = tty_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,
};
从这里看出,tty设备层已经完成了对所有tty设备的抽象,提供了统一的操作函数集。
(3)将tty_driver加到系统的tty_driver链表
2.2中,主要完成了uart层、tty层、chardev层三层的注册工作,但是,经过层层调用,最终一定要调用到具体的硬件设备操作函数才行。现在就是初始化这一部分内容。
首先明确一点,硬件设备的驱动和cpm_reg->state->port相关连。很多硬件驱动函数的入参就是uart_port结构。数据结构的联系是:
Uart_driver
+ uart_state
+ uart_port
在2.2中,uart_port结构没有进行任何初始化,而这个工作就是下面要描述的过程。经过of_register_platform_driver的调用,会回调执行cpm_uart_probe函数,这个函数就是对uart_port数据结构的完善。
Uart_port结构中有一个操作函数集指针:
const struct uart_ops *ops;
这个指针应指向对应的物理设备的操作函数集,在cpm_uart_core.c中,就是指:
static struct uart_ops cpm_uart_pops = {
.tx_empty = cpm_uart_tx_empty,
.set_mctrl = cpm_uart_set_mctrl,
.get_mctrl = cpm_uart_get_mctrl,
.stop_tx = cpm_uart_stop_tx,
.start_tx = cpm_uart_start_tx,
.stop_rx = cpm_uart_stop_rx,
.enable_ms = cpm_uart_enable_ms,
.break_ctl = cpm_uart_break_ctl,
.startup = cpm_uart_startup,
.shutdown = cpm_uart_shutdown,
.set_termios = cpm_uart_set_termios,
.type = cpm_uart_type,
.release_port = cpm_uart_release_port,
.request_port = cpm_uart_request_port,
.config_port = cpm_uart_config_port,
.verify_port = cpm_uart_verify_port,
#ifdef CONFIG_CONSOLE_POLL
.poll_get_char = cpm_get_poll_char,
.poll_put_char = cpm_put_poll_char,
#endif
};
这个数据结构代表了物理设备,它在uart_port结构的基础上,增加很多物理设备自有的特性。cpm_reg->state->port最终会指向这里面的uart_port port。这样uart_driver层就和物理驱动层建立了联系。
struct uart_cpm_port {
struct uart_port port;
u16 rx_nrfifos;
u16 rx_fifosize;
u16 tx_nrfifos;
…….
u32 command;
int gpios[NUM_GPIOS];
};
struct uart_cpm_port cpm_uart_ports[UART_NR];
这个函数用于初始化cpm_uart_ports及其中的uart_port结构。这个地方会根据open firmware中的fdt树对uart接口的描述来进行初始化。
值得注意的是,用户对uart的一些初始化就可以写在这里。
uart_add_one_port(&cpm_reg, &pinfo->port);
这样其入参,cpm_reg自然是uart_driver,而pinfo是指cpm_uart_ports,其内部的port自然是uart_port。
这个函数最大的作用就是使cpm_reg->state->port指向这里面的uart_port port。
tty_io.c中的tty_init函数会调用vt.c中的vty_init函数。所以初始化的起点可以定为vty_init函数。
该函数的入参是
static const struct file_operations console_fops = {
.llseek = no_llseek,
.read = tty_read,
.write = redirected_tty_write,
.poll = tty_poll,
.unlocked_ioctl = tty_ioctl,
.compat_ioctl = tty_compat_ioctl,
.open = tty_open,
.release = tty_release,
.fasync = tty_fasync,
};
注册时,使用的操作函数集正是console_fops。这个console_fops和前面提到的tty_fops同样定义在tty_io.c中。仔细对比就会发现二者几乎相同。所以说,serial设备和console设备统一与tty设备。
tty_set_operations(console_driver, &con_ops);
这个con_ops定义在vt.c,
static const struct tty_operations con_ops = {
.open = con_open,
.close = con_close,
.write = con_write,
.write_room = con_write_room,
.put_char = con_put_char,
.flush_chars = con_flush_chars,
.chars_in_buffer = con_chars_in_buffer,
.ioctl = vt_ioctl,
.stop = con_stop,
.start = con_start,
.throttle = con_throttle,
.unthrottle = con_unthrottle,
.resize = vt_resize,
.shutdown = con_shutdown
};
其可类比于定义于serial_core.c中的uart_ops。很明显这是两个不同的tty驱动集。
在cpm_uart_core.c中,通过声明
console_initcall(cpm_uart_console_init);
那么,cpm_uart_console_init函数就会被调用。cpm_uart_console_init调用了register_console函数,调用形式如下:
register_console(&cpm_scc_uart_console);
其中cpm_scc_uart_console是console类型的静态数据结构变量:
static struct console cpm_scc_uart_console = {
.name = "ttyCPM",
.write = cpm_uart_console_write,
.device = uart_console_device,
.setup = cpm_uart_console_setup,
.flags = CON_PRINTBUFFER,
.index = -1,
.data = &cpm_reg,
};
很明显,里面的驱动函数是对uart硬件的又一套驱动。可以类比于static struct uart_ops cpm_uart_pops 定义。