四.TTY層內核代碼TTY驅動程序有三種:控制臺、串口和pty。在此我們主要分析Mini2440串口驅動。
本文引用地址:http://www.eepw.com.cn/article/201611/319917.htm我們現在跟蹤uart_register_driver和uart_add_one_port發現,他們的原函數定義在TTY層驅動serial_core.o中。
int uart_register_driver(struct uart_driver *drv)
{
struct tty_driver *normal = NULL;
int i, retval;
BUG_ON(drv->state);
drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);
retval = -ENOMEM;
if (!drv->state)
goto out;
normal = alloc_tty_driver(drv->nr); //分配TTY驅動
if (!normal)
goto out;
drv->tty_driver = normal;
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; //初始的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驅動操作
for (i = 0; i < drv->nr; i++) { //初始化UART狀態
struct uart_state *state = drv->state + i;
struct tty_port *port = &state->port;
tty_port_init(port);
port->close_delay = 500;
port->closing_wait = 30000;
tasklet_init(&state->tlet, uart_tasklet_action,
(unsigned long)state);
}
retval = tty_register_driver(normal); //註冊TTY驅動
out:
if (retval < 0) {
put_tty_driver(normal);
kfree(drv->state);
}
return retval;
}
在上面uart_register_driver這個函數裡我們首先分配了TTY驅動,然後對其進行填充,初始的termios,並設置TTY驅動操作,最後註冊TTY驅動。其中設置TTY驅動操作時用到uart_ops,我們看看這個uart_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,
.set_ldisc = uart_set_ldisc,
.stop = uart_stop,
.start = uart_start,
.hangup = uart_hangup,
.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,
.tiocmset = uart_tiocmset,
#ifdef CONFIG_CONSOLE_POLL
.poll_init = uart_poll_init,
.poll_get_char = uart_poll_get_char,
.poll_put_char = uart_poll_put_char,
#endif
};
終端設備可以完成收發數據的功能,當用戶在有數據發送給終端設備時候,通過」write()系統調用—tty核心—線路規程」的層層調用,最終調用tty_driver結構體中的write()函數完成發送。因為傳輸速度和tty硬體緩衝區容量的原因,不是所有的寫程序要求的字符都可以在調用寫函數時候被發送出去,因此,寫函數應當返回能夠發給硬體的字節數以便用戶程序檢查是否所有的數據被真正寫入。如果在write()調用期間發生任何錯誤,一個負的錯誤碼應當被返回。在上面的uart_ops結構體中,我們先看看寫函數uart_write的實現吧。
static int uart_write(struct tty_struct *tty, const unsigned char *buf, int count)
{
struct uart_state *state = tty->driver_data; //獲取設備私有信息結構體
struct uart_port *port;
struct circ_buf *circ;
unsigned long flags;
int c, ret = 0;
if (!state) {
WARN_ON(1);
return -EL3HLT;
}
port = state->uart_port; //UART埠
circ = &state->xmit; //數據緩衝區
if (!circ->buf)
return 0;
spin_lock_irqsave(&port->lock, flags); //獲取UART埠操作的鎖
while (1) {
//返回可用緩存空間的大小
c = CIRC_SPACE_TO_END(circ->head, circ->tail, UART_XMIT_SIZE);
if (count < c)
c = count;
if (c <= 0) //緩存區太小則退出
break;
//將用戶空間buf中大小為c的內容拷貝到緩存中
memcpy(circ->buf + circ->head, buf, c);
circ->head = (circ->head + c) & (UART_XMIT_SIZE - 1);
buf += c; //緩存區指針後移
count -= c; //當一次發送的字節過多,需要分次發送
ret += c; //已經發送的字節數
}
spin_unlock_irqrestore(&port->lock, flags); //釋放UART埠操作的鎖
uart_start(tty); //開始發送
return ret;
}
根據上面對uart_write的分析,我們知道tty_driver的write()函數接收三個參數tty_struct,發送數據指針和要發送的字節數。uart_state作為這個驅動tty的私有數據,其中circ_buf定義了緩衝區,我們向這個緩衝區拷貝待發送的內容後,執行uart_start(tty)進行發送數據。那我們繼續看跟蹤uart_start函數
static void uart_start(struct tty_struct *tty)
{
struct uart_state *state = tty->driver_data;
struct uart_port *port = state->uart_port;
unsigned long flags;
spin_lock_irqsave(&port->lock, flags); //獲取UART埠操作的鎖
__uart_start(tty);
spin_unlock_irqrestore(&port->lock, flags); //釋放UART埠操作的鎖
}
static void __uart_start(struct tty_struct *tty)
{
struct uart_state *state = tty->driver_data;
struct uart_port *port = state->uart_port;
if (!uart_circ_empty(&state->xmit) && state->xmit.buf &&
!tty->stopped && !tty->hw_stopped) //緩衝區有數據並開啟發送狀態
port->ops->start_tx(port); //調用uart_ops下的start_tx,即s3c24xx_serial_start_tx
}
注意__uart_start函數中的port->ops->start_tx(port)便實現了tty層和uart層的相連,由tty層的write()調用uart層的write()。
好了,上面講的是發送數據,讀者可能注意到struct tty_operations uart_ops中沒有提到read()函數。因為發送是用戶主動的,而接收拾用戶調用read()從一片緩衝區讀取已經放好的數據,這個緩衝區由struct tty_flip_buffer結構體實現。因為tty核提供了這樣的緩衝邏輯,所以每個tty驅動並非一定要實現它自身的緩衝邏輯。Tty驅動不需要關注struct tty_flip_buffe的細節,從tty驅動接收到的來自硬體層的字符將被tty_insert_filp_char()函數插入filp緩衝區。如果傳輸的字節數count大於或等於TTY_FLIPBUF_SIEZE,這個flip緩衝區就需要被刷新到用戶,刷新是通過調用tty_flip_buffer_push()實現的。
接著,我們繼續看struct tty_operations uart_ops中對termios的設置函數set_termios,即uart_set_termios。這個set_termios需要根據用戶對termios的設置完成實際的硬體設置。新的設置被保存在tty_struct中,舊的設置被保存在old參數中,若新舊參數相同,則什麼都不需要做,對於被改的設置,需要完成硬體上的設置。好了,下面我們還是看看uart_set_termios的實現吧。
static void uart_set_termios(struct tty_struct *tty,
struct ktermios *old_termios)
{
struct uart_state *state = tty->driver_data; //獲取私有數據
unsigned long flags;
unsigned int cflag = tty->termios->c_cflag; //獲取當前線路設置
#define RELEVANT_IFLAG(iflag) ((iflag) & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK))
//如果新舊線路設置的控制狀態,輸入輸出速度等信息一樣,則退出
if ((cflag ^ old_termios->c_cflag) == 0 &&
tty->termios->c_ospeed == old_termios->c_ospeed &&
tty->termios->c_ispeed == old_termios->c_ispeed &&
RELEVANT_IFLAG(tty->termios->c_iflag ^ old_termios->c_iflag) == 0) {
return;
}
uart_change_speed(state, old_termios); //用新的線路規程的速度更新舊的線路規程
//處理波特率為B0情況
if ((old_termios->c_cflag & CBAUD) && !(cflag & CBAUD))
uart_clear_mctrl(state->uart_port, TIOCM_RTS | TIOCM_DTR);
/處理波特率為非B0情況
if (!(old_termios->c_cflag & CBAUD) && (cflag & CBAUD)) {
unsigned int mask = TIOCM_DTR;
if (!(cflag & CRTSCTS) ||
!test_bit(TTY_THROTTLED, &tty->flags))
mask |= TIOCM_RTS;
uart_set_mctrl(state->uart_port, mask); //設置modem控制
}
//處理無數據流控制情況
if ((old_termios->c_cflag & CRTSCTS) && !(cflag & CRTSCTS)) {
spin_lock_irqsave(&state->uart_port->lock, flags);
tty->hw_stopped = 0;
__uart_start(tty);
spin_unlock_irqrestore(&state->uart_port->lock, flags);
}
//處理有數據流控制情況
if (!(old_termios->c_cflag & CRTSCTS) && (cflag & CRTSCTS)) {
spin_lock_irqsave(&state->uart_port->lock, flags);
if (!(state->uart_port->ops->get_mctrl(state->uart_port) & TIOCM_CTS)) {
tty->hw_stopped = 1;
state->uart_port->ops->stop_tx(state->uart_port);
}
spin_unlock_irqrestore(&state->uart_port->lock, flags);
}
}
好了我們已經講解了write,set_termiox,下面我們講講tiocmget和tiocmset。Tiocmget()函數用於獲取tty設備的線路設置,對應的tiocmset()用於設置tty設備的線路設置。
static int uart_tiocmget(struct tty_struct *tty, struct file *file)
{
struct uart_state *state = tty->driver_data;
struct tty_port *port = &state->port;
struct uart_port *uport = state->uart_port;
int result = -EIO;
mutex_lock(&port->mutex); //獲取對tty_port操作的鎖
if ((!file || !tty_hung_up_p(file)) &&
!(tty->flags & (1 << TTY_IO_ERROR))) {
result = uport->mctrl;
spin_lock_irq(&uport->lock);
result |= uport->ops->get_mctrl(uport); //調用UART層get_mctrl獲取modem控制
spin_unlock_irq(&uport->lock);
}
mutex_unlock(&port->mutex); //釋放對tty_port操作的鎖
return result;
}
static int uart_tiocmset(struct tty_struct *tty, struct file *file,
unsigned int set, unsigned int clear)
{
struct uart_state *state = tty->driver_data;
struct uart_port *uport = state->uart_port;
struct tty_port *port = &state->port;
int ret = -EIO;
mutex_lock(&port->mutex); //獲取對tty_port操作的鎖
if ((!file || !tty_hung_up_p(file)) &&
!(tty->flags & (1 << TTY_IO_ERROR))) {
uart_update_mctrl(uport, set, clear); //獲取modem控制
ret = 0;
}
mutex_unlock(&port->mutex); //釋放對tty_port操作的鎖
return ret;
}
上面uart_tiocmset中調用了uart_update_mctrl(uport, set, clear)函數,它最終是通過調用port->ops->set_mctrl(port, port->mctrl)完成,而set_mctrl在UART層的uart_ops實現了。
綜上,TTY層的ops中的uart_tiocmget和uart_tiocmset其實最終是調用UART層uart_ops中的get_mctrl和set_mctrl實現的。
當用戶在tty設備節點上進行ioctl調用時,tty_operations中的ioctl()函數會被tty核心調用。我們接下來看看struct tty_operations uart_ops下的.ioctl也就是uart_ioctl。
static int uart_ioctl(struct tty_struct *tty, struct file *filp, unsigned int cmd,
unsigned long arg)
{
struct uart_state *state = tty->driver_data;
struct tty_port *port = &state->port;
void __user *uarg = (void __user *)arg;
int ret = -ENOIOCTLCMD;
switch (cmd) { //這些ioctl不依賴硬體
case TIOCGSERIAL: //獲得串口線信息
ret = uart_get_info(state, uarg);
break;
case TIOCSSERIAL: //設置串口線信息
ret = uart_set_info(state, uarg);
break;
case TIOCSERCONFIG: //自動配置
ret = uart_do_autoconfig(state);
break;
case TIOCSERGWILD:
case TIOCSERSWILD:
ret = 0;
break;
}
if (ret != -ENOIOCTLCMD)
goto out;
if (tty->flags & (1 << TTY_IO_ERROR)) {
ret = -EIO;
goto out;
}
switch (cmd) { //這些ioctl依賴硬體
case TIOCMIWAIT: //等待MSR改變
ret = uart_wait_modem_status(state, arg);
break;
case TIOCGICOUNT: //獲得中斷計數
ret = uart_get_count(state, uarg);
break;
}
if (ret != -ENOIOCTLCMD)
goto out;
mutex_lock(&port->mutex);
if (tty_hung_up_p(filp)) {
ret = -EIO;
goto out_up;
}
switch (cmd) { //這些ioctl依賴硬體,並且需要保護,房子tty被掛起
case TIOCSERGETLSR: //獲得這個tty設備的線路狀態寄存器LSR的值
ret = uart_get_lsr_info(state, uarg);
break;
default: {
struct uart_port *uport = state->uart_port;
if (uport->ops->ioctl)
ret = uport->ops->ioctl(uport, cmd, arg);
break;
}
}
out_up:
mutex_unlock(&port->mutex);
out:
return ret;
}
當TTY核心想知道由TTY驅動程序提供的可用寫入緩衝區的大小時,就會調用write_room。在清空寫緩衝區,或者調用write函數向緩衝區添加數據時,該值是變化的。接下來我們看看TTY層ops中write_room,也就是uart_write_room。跟蹤發現其實這個函數實現主要是把緩衝區頭尾相減得到剩餘空間大小。
除了write_room外,還有其他一些緩衝函數,例如TTY層ops中chars_in_buffer,也就是uart_chars_in_buffer,當tty核心在tty驅動程序的寫緩衝區中還有多少個需要傳輸的字符時調用該函數。
除此之外TTY層ops中還有三個回調函數,用來刷新驅動程序保存的任何數據,並不一定要實現,但是如果tty驅動程序能在發送給硬體前緩衝數據,還是推薦實現它們的,它們分別是flush_buffer,wait_until_sent,flush_buffer。
回顧一下,我們在TTY層的ops中,主要講了write,set_termiox, tiocmget,tiocmset,ioctl,五個函數,還簡單介紹了write_room,chars_in_buffer,flush_buffer,wait_until_sent,flush_buffer五個函數。到目前為止,我們已經分析好了uart_register_driver函數,現在該分析uart_add_one_port函數了。
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_add_one_port,我們發現其中最核心的一句代碼就是tty_register_device,僅有tty_driver是不夠的,驅動必須依附於設備,tty_register_device函數用於註冊關聯於tty_driver的設備。
總結下,TTY層的uart_register_driver和uart_register_port最終調用線路規程的tty_register_driver和tty_register_device。而tty_register_driver和tty_register_device的實現在線路規程中。
對於TTY驅動主要涉及如下幾個重要結構體,struct tty_struct包含了和打開的tty相關的所有狀態信息。其中一個重要的成員就是struct tty_bufhead buf,它是數據收集和處理機制的中樞,其定義如下
struct tty_bufhead {
struct delayed_work work;
spinlock_t lock;
struct tty_buffer *head;
struct tty_buffer *tail;
struct tty_buffer *free;
int memory_used;
};
另一個重要結構體是struct tty_driver,它規定了tty驅動程序和高層之間的編程接口。在我們這個TTY層,由uart_register_driver下的tty_register_driver註冊入內核,其中這個結構體中的成員部分是通過拷貝uart_driver中的參數得到。
好了,對於TTY層驅動,一般而言,我們需要完成如下兩個任務:
其一,終端設備驅動模塊的加載函數和卸載函數,完成註冊和註銷tty_driver,初始化和釋放終端設備對應的tty_driver結構體成員和硬體資源。
其二,實現tty_operations結構體中的一系列成員函數,主要的是實現open()、close()、 write()、 tiocmget()、 tiocmset()。