新聞中心

EEPW首頁 > 嵌入式系統(tǒng) > 設(shè)計(jì)應(yīng)用 > Linux下的串口總線驅(qū)動(dòng)(二)

Linux下的串口總線驅(qū)動(dòng)(二)

作者: 時(shí)間:2016-11-22 來源:網(wǎng)絡(luò) 收藏
四.TTY層內(nèi)核代碼

TTY驅(qū)動(dòng)程序有三種:控制臺、串口和pty。在此我們主要分析Mini2440串口驅(qū)動(dòng)。

本文引用地址:http://www.2s4d.com/article/201611/319917.htm

我們現(xiàn)在跟蹤uart_register_driver和uart_add_one_port發(fā)現(xiàn),他們的原函數(shù)定義在TTY層驅(qū)動(dòng)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驅(qū)動(dòng)

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;//控制模式設(shè)置

normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600; //設(shè)置輸入/出速度

normal->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;

normal->driver_state = drv; //私有數(shù)據(jù)

tty_set_operations(normal, &uart_ops); //設(shè)置TTY驅(qū)動(dòng)操作

for (i = 0; i < drv->nr; i++) { //初始化UART狀態(tài)

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驅(qū)動(dòng)

out:

if (retval < 0) {

put_tty_driver(normal);

kfree(drv->state);

}

return retval;

}

在上面uart_register_driver這個(gè)函數(shù)里我們首先分配了TTY驅(qū)動(dòng),然后對其進(jìn)行填充,初始的termios,并設(shè)置TTY驅(qū)動(dòng)操作,最后注冊TTY驅(qū)動(dòng)。其中設(shè)置TTY驅(qū)動(dòng)操作時(shí)用到uart_ops,我們看看這個(gè)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

};

終端設(shè)備可以完成收發(fā)數(shù)據(jù)的功能,當(dāng)用戶在有數(shù)據(jù)發(fā)送給終端設(shè)備時(shí)候,通過”write()系統(tǒng)調(diào)用—tty核心—線路規(guī)程”的層層調(diào)用,最終調(diào)用tty_driver結(jié)構(gòu)體中的write()函數(shù)完成發(fā)送。因?yàn)閭鬏斔俣群蛅ty硬件緩沖區(qū)容量的原因,不是所有的寫程序要求的字符都可以在調(diào)用寫函數(shù)時(shí)候被發(fā)送出去,因此,寫函數(shù)應(yīng)當(dāng)返回能夠發(fā)給硬件的字節(jié)數(shù)以便用戶程序檢查是否所有的數(shù)據(jù)被真正寫入。如果在write()調(diào)用期間發(fā)生任何錯(cuò)誤,一個(gè)負(fù)的錯(cuò)誤碼應(yīng)當(dāng)被返回。在上面的uart_ops結(jié)構(gòu)體中,我們先看看寫函數(shù)uart_write的實(shí)現(xiàn)吧。

static int uart_write(struct tty_struct *tty, const unsigned char *buf, int count)

{

struct uart_state *state = tty->driver_data; //獲取設(shè)備私有信息結(jié)構(gòu)體

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; //數(shù)據(jù)緩沖區(qū)

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) //緩存區(qū)太小則退出

break;

//將用戶空間buf中大小為c的內(nèi)容拷貝到緩存中

memcpy(circ->buf + circ->head, buf, c);

circ->head = (circ->head + c) & (UART_XMIT_SIZE - 1);

buf += c; //緩存區(qū)指針后移

count -= c; //當(dāng)一次發(fā)送的字節(jié)過多,需要分次發(fā)送

ret += c; //已經(jīng)發(fā)送的字節(jié)數(shù)

}

spin_unlock_irqrestore(&port->lock, flags); //釋放UART端口操作的鎖

uart_start(tty); //開始發(fā)送

return ret;

}

根據(jù)上面對uart_write的分析,我們知道tty_driver的write()函數(shù)接收三個(gè)參數(shù)tty_struct,發(fā)送數(shù)據(jù)指針和要發(fā)送的字節(jié)數(shù)。uart_state作為這個(gè)驅(qū)動(dòng)tty的私有數(shù)據(jù),其中circ_buf定義了緩沖區(qū),我們向這個(gè)緩沖區(qū)拷貝待發(fā)送的內(nèi)容后,執(zhí)行uart_start(tty)進(jìn)行發(fā)送數(shù)據(jù)。那我們繼續(xù)看跟蹤uart_start函數(shù)

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) //緩沖區(qū)有數(shù)據(jù)并開啟發(fā)送狀態(tài)

port->ops->start_tx(port); //調(diào)用uart_ops下的start_tx,即s3c24xx_serial_start_tx

}

注意__uart_start函數(shù)中的port->ops->start_tx(port)便實(shí)現(xiàn)了tty層和uart層的相連,由tty層的write()調(diào)用uart層的write()。

好了,上面講的是發(fā)送數(shù)據(jù),讀者可能注意到struct tty_operations uart_ops中沒有提到read()函數(shù)。因?yàn)榘l(fā)送是用戶主動(dòng)的,而接收拾用戶調(diào)用read()從一片緩沖區(qū)讀取已經(jīng)放好的數(shù)據(jù),這個(gè)緩沖區(qū)由struct tty_flip_buffer結(jié)構(gòu)體實(shí)現(xiàn)。因?yàn)閠ty核提供了這樣的緩沖邏輯,所以每個(gè)tty驅(qū)動(dòng)并非一定要實(shí)現(xiàn)它自身的緩沖邏輯。Tty驅(qū)動(dòng)不需要關(guān)注struct tty_flip_buffe的細(xì)節(jié),從tty驅(qū)動(dòng)接收到的來自硬件層的字符將被tty_insert_filp_char()函數(shù)插入filp緩沖區(qū)。如果傳輸?shù)淖止?jié)數(shù)count大于或等于TTY_FLIPBUF_SIEZE,這個(gè)flip緩沖區(qū)就需要被刷新到用戶,刷新是通過調(diào)用tty_flip_buffer_push()實(shí)現(xiàn)的。

接著,我們繼續(xù)看struct tty_operations uart_ops中對termios的設(shè)置函數(shù)set_termios,即uart_set_termios。這個(gè)set_termios需要根據(jù)用戶對termios的設(shè)置完成實(shí)際的硬件設(shè)置。新的設(shè)置被保存在tty_struct中,舊的設(shè)置被保存在old參數(shù)中,若新舊參數(shù)相同,則什么都不需要做,對于被改的設(shè)置,需要完成硬件上的設(shè)置。好了,下面我們還是看看uart_set_termios的實(shí)現(xiàn)吧。

static void uart_set_termios(struct tty_struct *tty,

struct ktermios *old_termios)

{

struct uart_state *state = tty->driver_data; //獲取私有數(shù)據(jù)

unsigned long flags;

unsigned int cflag = tty->termios->c_cflag; //獲取當(dāng)前線路設(shè)置

#define RELEVANT_IFLAG(iflag) ((iflag) & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK))

//如果新舊線路設(shè)置的控制狀態(tài),輸入輸出速度等信息一樣,則退出

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); //用新的線路規(guī)程的速度更新舊的線路規(guī)程

//處理波特率為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); //設(shè)置modem控制

}

//處理無數(shù)據(jù)流控制情況

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);

}

//處理有數(shù)據(jù)流控制情況

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);

}

}

好了我們已經(jīng)講解了write,set_termiox,下面我們講講tiocmget和tiocmset。Tiocmget()函數(shù)用于獲取tty設(shè)備的線路設(shè)置,對應(yīng)的tiocmset()用于設(shè)置tty設(shè)備的線路設(shè)置。

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); //調(diào)用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中調(diào)用了uart_update_mctrl(uport, set, clear)函數(shù),它最終是通過調(diào)用port->ops->set_mctrl(port, port->mctrl)完成,而set_mctrl在UART層的uart_ops實(shí)現(xiàn)了。

綜上,TTY層的ops中的uart_tiocmget和uart_tiocmset其實(shí)最終是調(diào)用UART層uart_ops中的get_mctrl和set_mctrl實(shí)現(xiàn)的。

當(dāng)用戶在tty設(shè)備節(jié)點(diǎn)上進(jìn)行ioctl調(diào)用時(shí),tty_operations中的ioctl()函數(shù)會被tty核心調(diào)用。我們接下來看看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: //設(shè)置串口線信息

ret = uart_set_info(state, uarg);

break;

case TIOCSERCONFIG: //自動(dòng)配置

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: //獲得中斷計(jì)數(shù)

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依賴硬件,并且需要保護(hù),房子tty被掛起

case TIOCSERGETLSR: //獲得這個(gè)tty設(shè)備的線路狀態(tài)寄存器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;

}

當(dāng)TTY核心想知道由TTY驅(qū)動(dòng)程序提供的可用寫入緩沖區(qū)的大小時(shí),就會調(diào)用write_room。在清空寫緩沖區(qū),或者調(diào)用write函數(shù)向緩沖區(qū)添加數(shù)據(jù)時(shí),該值是變化的。接下來我們看看TTY層ops中write_room,也就是uart_write_room。跟蹤發(fā)現(xiàn)其實(shí)這個(gè)函數(shù)實(shí)現(xiàn)主要是把緩沖區(qū)頭尾相減得到剩余空間大小。

除了write_room外,還有其他一些緩沖函數(shù),例如TTY層ops中chars_in_buffer,也就是uart_chars_in_buffer,當(dāng)tty核心在tty驅(qū)動(dòng)程序的寫緩沖區(qū)中還有多少個(gè)需要傳輸?shù)淖址麜r(shí)調(diào)用該函數(shù)。

除此之外TTY層ops中還有三個(gè)回調(diào)函數(shù),用來刷新驅(qū)動(dòng)程序保存的任何數(shù)據(jù),并不一定要實(shí)現(xiàn),但是如果tty驅(qū)動(dòng)程序能在發(fā)送給硬件前緩沖數(shù)據(jù),還是推薦實(shí)現(xiàn)它們的,它們分別是flush_buffer,wait_until_sent,flush_buffer。

回顧一下,我們在TTY層的ops中,主要講了write,set_termiox, tiocmget,tiocmset,ioctl,五個(gè)函數(shù),還簡單介紹了write_room,chars_in_buffer,flush_buffer,wait_until_sent,flush_buffer五個(gè)函數(shù)。到目前為止,我們已經(jīng)分析好了uart_register_driver函數(shù),現(xiàn)在該分析uart_add_one_port函數(shù)了。

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;

//如果這個(gè)端口是控制臺,那么這個(gè)鎖就已經(jīng)初始化了

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 %dn",

uport->line);

//確保UPF_DEAD沒有被置位

uport->flags &= ~UPF_DEAD;

out:

mutex_unlock(&port->mutex);

mutex_unlock(&port_mutex);

return ret;

}

對于uart_add_one_port,我們發(fā)現(xiàn)其中最核心的一句代碼就是tty_register_device,僅有tty_driver是不夠的,驅(qū)動(dòng)必須依附于設(shè)備,tty_register_device函數(shù)用于注冊關(guān)聯(lián)于tty_driver的設(shè)備。

總結(jié)下,TTY層的uart_register_driver和uart_register_port最終調(diào)用線路規(guī)程的tty_register_driver和tty_register_device。而tty_register_driver和tty_register_device的實(shí)現(xiàn)在線路規(guī)程中。

對于TTY驅(qū)動(dòng)主要涉及如下幾個(gè)重要結(jié)構(gòu)體,struct tty_struct包含了和打開的tty相關(guān)的所有狀態(tài)信息。其中一個(gè)重要的成員就是struct tty_bufhead buf,它是數(shù)據(jù)收集和處理機(jī)制的中樞,其定義如下

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;

};

另一個(gè)重要結(jié)構(gòu)體是struct tty_driver,它規(guī)定了tty驅(qū)動(dòng)程序和高層之間的編程接口。在我們這個(gè)TTY層,由uart_register_driver下的tty_register_driver注冊入內(nèi)核,其中這個(gè)結(jié)構(gòu)體中的成員部分是通過拷貝uart_driver中的參數(shù)得到。

好了,對于TTY層驅(qū)動(dòng),一般而言,我們需要完成如下兩個(gè)任務(wù):

其一,終端設(shè)備驅(qū)動(dòng)模塊的加載函數(shù)和卸載函數(shù),完成注冊和注銷tty_driver,初始化和釋放終端設(shè)備對應(yīng)的tty_driver結(jié)構(gòu)體成員和硬件資源。

其二,實(shí)現(xiàn)tty_operations結(jié)構(gòu)體中的一系列成員函數(shù),主要的是實(shí)現(xiàn)open()、close()、 write()、 tiocmget()、 tiocmset()。



關(guān)鍵詞: Linux串口總線驅(qū)

評論


技術(shù)專區(qū)

關(guān)閉