tty驅動框架分析
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,提供除錯節點。