linux TTY子系統 --- bug來了,稍候再整理
Version: 0.9
Date: 2018-09-27
Nemo.Han
目錄
1.學習出發點及目標
本文主要記錄uart與tty的關係,學習目標如下:
- TTY子系統
- LINUX下TTY、CONSOLE、串列埠之間是怎樣的層次關係?具體的函式介面是怎樣的?
- UART怎麼註冊到TTY的
- TTY是怎麼在核心系統中工作的
- 上層配置資料程式碼流程
- 上層資料下發流程
- 驅動資料上報流程
- poll機制實現
- 為什麼Linux系統中會有那麼多的TTY
- struct tty_struct是在什麼時候分配並初始化的
- open_wait在哪裡完成的?應該是在驅動層
- 同時在4.4版本中,uart_open會呼叫uart_startup,而4.14中沒有呼叫,那uart_startup是在哪裡呼叫的呢?
- tty_insert_file_char函式流程需要梳理,buf初始化,buf管理
- tty_update_time中涉及到的timer有什麼用,tty核心是怎麼用來管理的?
- 可獨立設計serial驅動,並能很好的工作
- 驅動中允許DMA進行資料收發
- 使用QT開發串列埠助手,並且能夠很好的進行工作,併網上釋出工具
- 輸出文件,提供層次結構圖、資料流程圖、關鍵函式註釋
2 什麼是TTY
在Linux中,TTY也許是跟終端有關係的最為混亂的術語。TTY是TeleTYpe的一個老縮寫。Teletypes,或者telety pewriters,原來指的是電傳打字機,是通過序列線用印表機鍵盤通過閱讀和傳送資訊的東西,和古老的電報機區別並不是很大。之後,當計算機只能以批處理方式執行時(當時穿孔卡片閱讀器是唯一一種使程式載入執行的方式),電傳打字機成為唯一能夠被使用的“實時”輸入/輸出裝置。最終,電傳打字機被鍵盤和顯示器終端所取代,但在終端或TTY接插的地方,作業系統仍然需要一個程式來監視串列埠。一個getty“Get TTY”的處理過程是:一個程式監視物理的TTY/終端介面。對一個虛擬網路控制檯(VNC)來說,一個偽裝的TTY(Pseudo-TTY,即假冒的TTY,也叫做“PTY”)是等價的終端。當你執行一個xterm(終端模擬程式)或GNOME終端程式時,PTY對虛擬的使用者或者如xterm一樣的偽終端來說,就像是一個TTY在執行。“Pseudo”的意思是“duplicating in a fake way”(用偽造的方法複製),它相比“virtual”或“emulated”更能真實的說明問題。而在的計算中,它卻處於被放棄的階段。
tty也是一個Unix命令,用來給出當前終端裝置的名稱。
終端是一種字元型裝置,它有多種型別,通常使用tty來簡稱各種型別的終端裝置。
在Linux系統的裝置特殊檔案目錄/dev/下,終端特殊裝置檔案一般有以下幾種:串列埠、偽終端、控制終端、控制檯、虛擬終端、其他型別。
2.1 串列埠
串列埠終端(Serial Port Terminal)是使用計算機串列埠連線的終端裝置。計算機把每個串列埠都看作是一個字元裝置。有段時間這些串列埠裝置通常被稱為終端裝置,因為那時它的最大用途就是用來連線終端。這些串列埠所對應的裝置名稱是/dev/tts/0(或/dev/ttyS0),/dev/tts/1(或/dev/ttyS1)等,裝置號分別是(4,0),(4,1)等,分別對應於DOS系統下的COM1、COM2等。若要向一個埠傳送資料,可以在命令列上把標準輸出重定向到這些特殊檔名上即可。例如,在命令列提示符下鍵入:echo test > /dev/ttyS1會把單詞”test”傳送到連線在ttyS1(COM2)埠的裝置上。
2.2 偽終端
偽終端(Pseudo Terminal)是成對的邏輯終端裝置(即master和slave裝置,對master的操作會反映到slave上)。
例如/dev/ptyp3和/dev/ttyp3(或者在裝置檔案系統中分別是/dev/pty /m3和 /dev/pty/s3)。它們與實際物理裝置並不直接相關。如果一個程式把ptyp3(master裝置)看作是一個串列埠裝置,則它對該埠的讀/ 寫操作會反映在該邏輯終端裝置對應的另一個ttyp3(slave裝置)上面。而ttyp3則是另一個程式用於讀寫操作的邏輯裝置。telnet主機A就是通過“偽終端”與主機A的登入程式進行通訊。
2.3 控制終端
如果當前程序有控制終端(Controlling Terminal)的話,那麼/dev/tty就是當前程序的控制終端的裝置特殊檔案。可以使用命令”ps –ax”來檢視程序與哪個控制終端相連。對於你登入的shell,/dev/tty就是你使用的終端,裝置號是(5,0)。使用命令”tty”可以檢視它具體對應哪個實際終端裝置。/dev/tty有些類似於到實際所使用終端裝置的一個聯接。
2.4 虛擬終端
在Xwindow模式下的偽終端.如在Kubuntu下用konsole,就是用的虛擬終端,用tty命令可看到/dev/pts/name,name為當前使用者名稱。
2.5 其他型別
Linux系統中還針對很多不同的字元裝置存在有很多其它種類的終端裝置特殊檔案。例如針對ISDN裝置的/dev/ttyIn終端裝置等。
tty裝置包括虛擬控制檯,串列埠以及偽終端裝置。
/dev/tty代表當前tty裝置,在當前的終端中輸入 echo “hello” > /dev/tty ,都會直接顯示在當前的終端中。
3 TTY子系統
tty裝置有四層:tty核心、tty線路規程、uart核心層、tty驅動。我們寫驅動只負責最底層的tty驅動,線路規程的設定也是在底層的tty驅動,tty核心是封裝好的。使用者空間主要是通過裝置檔案同tty核心互動,而tty核心根據使用者空間的操作再選擇跟tty線路規程或者tty驅動互動,例:設定硬體的ioctl指令就直接交給tty驅動處理,read和write操作交給tty線路規程處理。
3.1 TTY核心
所有tty型別的驅動的頂層構架,嚮應用層提供了統一的介面,應用層的read/write等呼叫首先會到達這裡。此層由核心實現,程式碼主要分佈在drivers/char目錄下的n_tty.c,tty_io.c等檔案中。
3.2 tty_register_driver
int tty_register_driver(struct tty_driver *driver)
{
int error;
int i;
dev_t dev;
struct device *d;
//初始化sprd_uart_driver時,major與minor都為0
//接下來按照字元裝置的註冊流程,註冊一個cdev
if (!driver->major) {
error = alloc_chrdev_region(&dev, driver->minor_start,driver->num, driver->name);
if (!error) {
driver->major = MAJOR(dev);
driver->minor_start = MINOR(dev);
}
} else {
dev = MKDEV(driver->major, driver->minor_start);
error = register_chrdev_region(dev, driver->num, driver->name);
}
if (error < 0)
goto err;
//在uart_register_driver函式中,normal->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV
if (driver->flags & TTY_DRIVER_DYNAMIC_ALLOC) {
error = tty_cdev_add(driver, dev, 0, driver->num);
}
mutex_lock(&tty_mutex);
list_add(&driver->tty_drivers, &tty_drivers); //新增驅動到全域性tty驅動連結串列中
mutex_unlock(&tty_mutex);
/*根據動態裝置標記決定是否此時註冊tty裝置,就是uart_register_driver裡的normal->flags,在這裡uart_register_driver中有設定,它是在sprd_probe裡註冊的,device註冊後將在sys中形成層次結構,系統啟動後,udev系統(android中為vold)將在/dev/下面生成節點檔案,這些節點檔案操作都是按照該字元裝置的tty_fops進行*/
if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {
for (i = 0; i < driver->num; i++) {
d = tty_register_device(driver, i, NULL);
……
}
}
proc_tty_register_driver(driver); à driver->proc_entry = ent;
driver->flags |= TTY_DRIVER_INSTALLED;
return 0;
……
}
註冊tty裝置:如果TTY驅動程式的標誌設定了TTY_DRIVER_DYNAMIC_DEV標誌位,則需要呼叫此函式註冊單個TTY裝置;如果未設定此標誌位,tty驅動程式不應當呼叫此函式。
3.3 tty_register_device
struct device *tty_register_device(struct tty_driver *driver, unsigned index,struct device *device)
{
return tty_register_device_attr(driver, index, device, NULL, NULL);
}
tty_register_device的實際執行函式為tty_regitser_device_attr。
3.4 tty_register_device_attr
struct device *tty_register_device_attr(struct tty_driver *driver, unsigned index, struct device *device, void *drvdata, const struct attribute_group **attr_grp)
{
char name[64];
dev_t devt = MKDEV(driver->major, driver->minor_start) + index;
struct device *dev = NULL;
int retval = -ENODEV;
bool cdev = false;
if (index >= driver->num) {
printk(KERN_ERR "Attempt to register invalid tty line number "" (%d).\n", index);
return ERR_PTR(-EINVAL);
}
if (driver->type == TTY_DRIVER_TYPE_PTY)
pty_line_name(driver, index, name);
else
tty_line_name(driver, index, name);
//在uart_register_driver中,flag欄位設定為TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV,此處會呼叫tty_cdev_add
if (!(driver->flags & TTY_DRIVER_DYNAMIC_ALLOC)) {
retval = tty_cdev_add(driver, devt, index, 1);
if (retval)
goto error;
cdev = true;
}
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev) {
retval = -ENOMEM;
goto error;
}
dev->devt = devt;
dev->class = tty_class;
dev->parent = device;
dev->release = tty_device_create_release;
dev_set_name(dev, "%s", name);
dev->groups = attr_grp;
dev_set_drvdata(dev, drvdata);
retval = device_register(dev);
if (retval)
goto err_put;
if (!(driver->flags & TTY_DRIVER_DYNAMIC_ALLOC)) {
/*
* Free any saved termios data so that the termios state is
* reset when reusing a minor number.
*/
tp = driver->termios[index];
if (tp) {
driver->termios[index] = NULL;
kfree(tp);
}
retval = tty_cdev_add(driver, devt, index, 1);
if (retval)
goto err_del;
}
dev_set_uevent_suppress(dev, 0);
kobject_uevent(&dev->kobj, KOBJ_ADD);
return dev;
err_del:
device_del(dev);
err_put:
put_device(dev);
return ERR_PTR(retval);
}
3.5 tty_port_register_device_attr_serdev
此函式為4.14中新增加的函式,在4.4中uart_add_one_port直接呼叫了tty_register_device_attr。
struct device *tty_port_register_device_attr_serdev(struct tty_port *port,struct tty_driver *driver, unsigned index,struct device *device, void *drvdata,const struct attribute_group **attr_grp)
{
struct device *dev;
tty_port_link_device(port, driver, index);
dev = serdev_tty_port_register(port, device, driver, index);
if (PTR_ERR(dev) != -ENODEV) {
/* Skip creating cdev if we registered a serdev device */
return dev;
}
return tty_register_device_attr(driver, index, device, drvdata,attr_grp);
}
3.6 tty_cdev_add
static int tty_cdev_add(struct tty_driver *driver, dev_t dev, unsigned int index, unsigned int count)
{
int err;
/* init here, since reused cdevs cause crashes */
driver->cdevs[index] = cdev_alloc();
if (!driver->cdevs[index])
return -ENOMEM;
//關聯裝置操作函式
driver->cdevs[index]->ops = &tty_fops;
driver->cdevs[index]->owner = driver->owner;
//向核心新增字元裝置,之後使用者空間通過/dev/ttySn節點open時,會呼叫tty_fops成員函式
err = cdev_add(driver->cdevs[index], dev, count);
if (err)
kobject_put(&driver->cdevs[index]->kobj);
return err;
}
3.7 tty_port_init
初始化port相關的資料成員,主要是buf相關。
void tty_port_init(struct tty_port *port)
{
memset(port, 0, sizeof(*port));
tty_buffer_init(port);
struct tty_bufhead *buf = &port->buf;
tty_buffer_reset(&buf->sentinel, 0);
buf->head = &buf->sentinel;
buf->tail = &buf->sentinel;
init_llist_head(&buf->free);
atomic_set(&buf->mem_used, 0);
atomic_set(&buf->priority, 0);
//驅動上報資料時,將資料推送到線路規程層通過flush_to_ldisc實現
INIT_WORK(&buf->work, flush_to_ldisc);
buf->mem_limit = TTYB_DEFAULT_MEM_LIMIT;
init_waitqueue_head(&port->open_wait);
init_waitqueue_head(&port->delta_msr_wait);
mutex_init(&port->mutex);
mutex_init(&port->buf_mutex);
spin_lock_init(&port->lock);
port->close_delay = (50 * HZ) / 100;
port->closing_wait = (3000 * HZ) / 100;
}
3.7 tty_open
之前有提到,在tty_cdev_add函式中,將裝置檔案與tty_fops進行了關聯。在使用者通過裝置節點開啟tty裝置時,會呼叫到tty_open函式。
static int tty_open(struct inode *inode, struct file *filp)
{
struct tty_struct *tty;
int noctty, retval;
dev_t device = inode->i_rdev;
unsigned saved_flags = filp->f_flags;
nonseekable_open(inode, filp);
retry_open:
retval = tty_alloc_file(filp);
if (retval)
return -ENOMEM;
//tty的獲取部分未理清,tty在哪裡申請的記憶體,怎麼和tty_driver關聯的?
//在uart_register_driver時,會建立tty_struct,之後與tty_driver,tty_ldisc等結構體進行關聯,具體可以參考tty_init_dev函式的具體實現
tty = tty_open_current_tty(device, filp);
if (!tty)
tty = tty_open_by_driver(device, inode, filp);
if (IS_ERR(tty)) {
tty_free_file(filp);
retval = PTR_ERR(tty);
if (retval != -EAGAIN || signal_pending(current))
return retval;
schedule();
goto retry_open;
}
tty_add_file(tty, filp);
check_tty_count(tty, __func__);
tty_debug_hangup(tty, "opening (count=%d)\n", tty->count);
//呼叫到uart_ops中的open成員
if (tty->ops->open)
retval = tty->ops->open(tty, filp);
else
retval = -ENODEV;
filp->f_flags = saved_flags;
if (retval) {
tty_debug_hangup(tty, "open error %d, releasing\n", retval);
tty_unlock(tty); /* need to call tty_release without BTM */
tty_release(inode, filp);
if (retval != -ERESTARTSYS)
return retval;
if (signal_pending(current))
return retval;
schedule();
/*
* Need to reset f_op in case a hangup happened.
*/
if (tty_hung_up_p(filp))
filp->f_op = &tty_fops;
goto retry_open;
}
clear_bit(TTY_HUPPED, &tty->flags);
noctty = (filp->f_flags & O_NOCTTY) ||
(IS_ENABLED(CONFIG_VT) && device == MKDEV(TTY_MAJOR, 0)) ||
device == MKDEV(TTYAUX_MAJOR, 1) ||
(tty->driver->type == TTY_DRIVER_TYPE_PTY &&
tty->driver->subtype == PTY_TYPE_MASTER);
if (!noctty)
tty_open_proc_set_tty(filp, tty);
tty_unlock(tty);
return 0;
}
3.8 tty_write
static ssize_t tty_write(struct file *file, const char __user *buf,size_t count, loff_t *ppos)
{
struct tty_struct *tty = file_tty(file);
if (!ld->ops->write) ret = -EIO;
else ret = do_tty_write(ld->ops->write, tty, file, buf, count);
if (tty->write_cnt < chunk)
{
buf_chunk = kmalloc(chunk, GFP_KERNEL);
kfree(tty->write_buf);
tty->write_cnt = chunk;
tty->write_buf = buf_chunk;
//這裡呼叫到ld->ops->wirte函式,即為線路規程中的n_tty_write。
ret = write(tty, file, tty->write_buf, size);
}
}
3.9 tty_read
使用者通過read系統呼叫執行到tty_read,之後呼叫到線路規程層的n_tty_read函式。
static ssize_t tty_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
struct inode *inode = file_inode(file);
struct tty_struct *tty = file_tty(file);
//呼叫執行緒規程層的read函式,即n_tty_read
if (ld->ops->read) i = ld->ops->read(tty, file, buf, count);
}
3.10 tty_insert_filp_char
當前的tty_buffer空間不夠時呼叫tty_insert_flip_string_flags,在這個函式裡會去查詢下一個tty_buffer,並將資料放到下一個tty_buffer的char_buf_ptr裡。這裡char_buf_ptr的資料是如何放到線路規程的read_buf中的呢?是在tty open操作的時候,tty_init_dev -> initialize_tty_struct -> initialize_tty_struct -> tty_buffer_init
int tty_insert_flip_char(struct tty_port *port, unsigned char ch, char flag)
{
struct tty_buffer *tb = port->buf.tail;
int change;
change = (tb->flags & TTYB_NORMAL) && (flag != TTY_NORMAL);
if (!change && tb->used < tb->size) {
if (~tb->flags & TTYB_NORMAL)
*flag_buf_ptr(tb, tb->used) = flag;
*char_buf_ptr(tb, tb->used++) = ch;
return 1;
}
return __tty_insert_flip_char(port, ch, flag);
}
3.11 tty_filp_buf_push
將終端緩衝區推送到線路規程層。可以在IRQ/原子上下文中呼叫。如果佇列忙,工作佇列將會暫停並稍候進行重試。
void tty_flip_buffer_push(struct tty_port *port)
{
tty_schedule_flip(port);
struct tty_bufhead *buf = &port->buf;
smp_store_release(&buf->tail->commit, buf->tail->used);
//此處呼叫到tty_port_init時初關聯的flush_to_ldisc
queue_work(system_unbound_wq, &buf->work);
}
tty_schedule_flip:
{
struct tty_bufhead *buf = &port->buf;
smp_store_release(&buf->tail->commit, buf->tail->used);
//此處呼叫到tty_port_init時初關聯的flush_to_ldisc
queue_work(system_unbound_wq, &buf->work);
}
3.12 flush_to_ldisc
從軟中斷中呼叫此例程,將資料從緩衝連結串列中推送到線路規程層。對於每個tty例項,receive_buf方法都是單執行緒的。驅動層通過呼叫tty_filp_buf_push喚醒buf->work,會呼叫到此函式。
static void flush_to_ldisc(struct work_struct *work)
{
struct tty_port *port = container_of(work, struct tty_port, buf.work);
struct tty_bufhead *buf = &port->buf;
while(1)
{
struct tty_buffer *head = buf->head;
struct tty_buffer *next;
next = smp_load_acquire(&head->next);
count = smp_load_acquire(&head->commit) - head->read;
if (!count) {
if (next == NULL)
break;
buf->head = next;
tty_buffer_free(port, head);
continue;
}
count = receive_buf(port, head, count);
//receive_buf呼叫了client_ops中的成員函式,即為defatlu_client_ops的成員函式
return port->client_ops->receive_buf(port, p, f, count);
if (!count) break;
head->read += count;
}
3.13 tty_port_default_receive_buf
static int tty_port_default_receive_buf(struct tty_port *port, const unsigned char *p, const unsigned char *f, size_t count)
{
int ret;
struct tty_struct *tty;
struct tty_ldisc *disc;
tty = READ_ONCE(port->itty);
if (!tty)
return 0;
disc = tty_ldisc_ref(tty);
if (!disc)
return 0;
ret = tty_ldisc_receive_buf(disc, p, (char *)f, count);
tty_ldisc_deref(disc);
return ret;
}
int tty_ldisc_receive_buf(struct tty_ldisc *ld, const unsigned char *p, char *f, int count)
{
if (ld->ops->receive_buf2)
count = ld->ops->receive_buf2(ld->tty, p, f, count);
else {
count = min_t(int, count, ld->tty->receive_room);
if (count && ld->ops->receive_buf)
//由tty核心層呼叫到線路規程層,對應到n_tty_ops的相關成員函式
ld->ops->receive_buf(ld->tty, p, f, count);
}
return count;
}
4 UART核心層
4.1 uart_register_driver
使用UART核心層註冊驅動程式。依次向TTY核心層註冊,並初始化核心驅動程式pre-port狀態;
int uart_register_driver(struct uart_driver *drv) { struct tty_driver *normal; …… drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL); …… normal = alloc_tty_driver(drv->nr); …… drv->tty_driver = normal; 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); ---ànormal->ops = 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; //初始化port->buf,及port->buf.work,在uart收到資料上報時,會使用port->buf以及work tty_port_init(port); port->ops = &uart_port_ops; à uart_driver->state->port->ops = uart_port_ops; } retval = tty_register_driver(normal); …… put_tty_driver(normal); …… } |
attach驅動程式定義的埠結構。這允許驅動程式使用核心驅動程式註冊自己的uart_port結構,主要目的是允許低級別的uart驅動程式擴充套件uart_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; int num_groups; …… state = drv->state + uport->line; port = &state->port; mutex_lock(&port_mutex); mutex_lock(&port->mutex); if (state->uart_port) { ret = -EINVAL; goto out; } /* Link the port to the driver state table and vice versa */ atomic_set(&state->refcount, 1); init_waitqueue_head(&state->remove_wait); state->uart_port = uport; uport->state = state; state->pm_state = UART_PM_STATE_UNDEFINED; uport->cons = drv->cons; uport->minor = drv->tty_driver->minor_start + uport->line; uport->name = kasprintf(GFP_KERNEL, "%s%d", drv->dev_name, drv->tty_driver->name_base + uport->line); …… if (!(uart_console(uport) && (uport->cons->flags & CON_ENABLED))) { spin_lock_init(&uport->lock); lockdep_set_class(&uport->lock, &port_lock_key); } if (uport->cons && uport->dev) of_console_check(uport->dev->of_node, uport->cons->name, uport->line); uart_configure_port(drv, state, uport);此函式可以解釋一下:最終呼叫port->ops->config_port函式; port->console = uart_console(uport); num_groups = 2; if (uport->attr_group) num_groups++; uport->tty_groups = kcalloc(num_groups, sizeof(*uport->tty_groups), GFP_KERNEL); if (!uport->tty_groups) { ret = -ENOMEM; goto out; } uport->tty_groups[0] = &tty_dev_attr_group; if (uport->attr_group) uport->tty_groups[1] = uport->attr_group; /* * Register the port whether it's detected or not. This allows * setserial to be used to alter this port's parameters. */ tty_dev = tty_port_register_device_attr_serdev(port, drv->tty_driver, uport->line, uport->dev, port, uport->tty_groups); if (likely(!IS_ERR(tty_dev))) { device_set_wakeup_capable(tty_dev, 1); } else { dev_err(uport->dev, "Cannot register tty device on line %d\n", uport->line); } /* * Ensure UPF_DEAD is not set. */ uport->flags &= ~UPF_DEAD; out: mutex_unlock(&port->mutex); mutex_unlock(&port_mutex); return ret; } |
static void uart_configure_port(struct uart_driver *drv, struct uart_state *state, struct uart_port *port) { unsigned int flags; /* * If there isn't a port here, don't do anything further. */ if (!port->iobase && !port->mapbase && !port->membase) return; /* * Now do the auto configuration stuff. Note that config_port * is expected to claim the resources and map the port for us. */ flags = 0; if (port->flags & UPF_AUTO_IRQ) flags |= UART_CONFIG_IRQ; if (port->flags & UPF_BOOT_AUTOCONF) { if (!(port->flags & UPF_FIXED_TYPE)) { port->type = PORT_UNKNOWN; flags |= UART_CONFIG_TYPE; } port->ops->config_port(port, flags); //呼叫serial_sprd_ops中的sprd_config_port函式 } if (port->type != PORT_UNKNOWN) { unsigned long flags; //給出除錯資訊 uart_report_port(drv, port); /* Power up port for set_mctrl() */ uart_change_pm(state, UART_PM_STATE_ON); /* * Ensure that the modem control lines are de-activated. * keep the DTR setting that is set in uart_set_options() * We probably don't need a spinlock around this, but */ spin_lock_irqsave(&port->lock, flags); port->ops->set_mctrl(port, port->mctrl & TIOCM_DTR); spin_unlock_irqrestore(&port->lock, flags); /* * If this driver supports console, and it hasn't been * successfully registered yet, try to re-register it. * It may be that the port was not available. */ //在sprd_console的定義中,flasg為CON_PRINTBUFFER,此處會註冊sprd_console,這裡會呼叫到printk.c中的函式,暫不分析 if (port->cons && !(port->cons->flags & CON_ENABLED)) register_console(port->cons); /* * Power down all ports by default, except the * console if we have one. */ if (!uart_console(port)) uart_change_pm(state, UART_PM_STATE_OFF); } } |
呼叫在tty_open流程中,會呼叫到ops->open函式,此處ops賦值是在前面的uart_register_driver函式中,所以進入uart_ops結構體的open函式,這裡就是從tty核心轉到serial核心,往下走了一層。uart_open函式會等待open_wait完成。同時在4.4版本中,uart_open會呼叫uart_startup,而4.14中沒有呼叫,那uart_startup是在哪裡呼叫的呢?
static int uart_open(struct tty_struct *tty, struct file *filp) struct uart_driver *drv = tty->driver->driver_state; int retval, line = tty->index; struct uart_state *state = drv->state + line; tty->driver_data = state; retval = tty_port_open(&state->port, tty, filp); ++port->count; tty_port_tty_set(port, tty) port->tty = tty_kref_get(tty); :此處會導致port->itty與port->tty指向同一個tty_struct,為什麼 //是否會呼叫到activate呢?port->ops中實現了activate成員函式,推測是會呼叫的,需要分析 if (port->ops->activate) { int retval = port->ops->activate(port, tty);} return tty_port_block_til_ready(port, tty, filp);//此函式暫不做展開 |
int uart_port_activate(struct tty_port *port, struct tty_struct *tty) struct uart_state *state = container_of(port, struct uart_state, port); uport = uart_port_check(state); return uart_startup(tty, state, 0); |
int uart_startup(struct tty_struct *tty, struct uart_state *state, int init_hw) struct tty_port *port = &state->port; retval = uart_port_startup(tty, state, init_hw); uart_change_pm(state, UART_PM_STATE_ON); retval = uport->ops->startup(uport);//呼叫驅動層的startup:sprd_startup uart_change_speed(tty, state, NULL); //會呼叫到uport->ops->set_termios函式 |
此函式將資料放到到緩衝區中,之後呼叫驅動層實現的uart_ops結構體:serial_sprd_ops中的成員函式。
static int uart_write(struct tty_struct *tty,
const unsigned char *buf, int count)
struct uart_state *state = tty->driver_data;
circ = &state->xmit;
port = uart_port_lock(state, flags); //port = state->uart_port
memcpy(circ->buf + circ->head, buf, c);
circ->head = (circ->head + c) & (UART_XMIT_SIZE - 1);
__uart_start(tty);
struct uart_state *state = tty->driver_data;
struct uart_port *port = state->uart_port;
//此處呼叫到驅動層的serial_sprd_ops中的sprd_start_tx函式 port->ops->start_tx(port);
|
void uart_insert_char(struct uart_port *port, unsigned int status, unsigned int overrun, unsigned int ch, unsigned int flag) { struct tty_port *tport = &port->state->port; if ((status & port->ignore_status_mask & ~overrun) == 0) if (tty_insert_flip_char(tport, ch, flag) == 0) ++port->icount.buf_overrun; if (status & ~port->ignore_status_mask & overrun) if (tty_insert_flip_char(tport, 0, TTY_OVERRUN) == 0) ++port->icount.buf_overrun; } |
ine discipline(LDISC) 線路規程,是linux和類unix系統終端子系統的一個軟體驅動層。line discipline把TTY核心層和tty driver關聯在一起,策略的分離使得TTY核心層和tty driver不需要關注資料語法處理,tty driver可以被相同的硬體複用,而只需更改line discipline。
linux終端裝置預設的線路規程是N_TTY,它從tty driver和應用程式接收資料,按照終端設定處理資料。對於輸入資料,它處理特殊的中斷字元(比如Control-C),刪除字元(backspace, delete)等等;對於輸出資料,它用CR/LF序列替換LF字元。當uart port用做普通串列埠時,使用N_TTY線路規程。當uart port裝置用做serial modem 的internet撥號連線時,使用PPP線路規程處理資料;ppp LDISC把從uart core來的串列埠資料組裝為PPP輸入packet,然後分發給網路協議棧;ppp LDISC把從網路協議棧傳送來的資料拆包傳送給uart port。
向核心註冊一個新的線路規程,預設的線路規程是在初始化階段完成註冊的,索引為0。預設的tty_ldiscs資料長度為30,在tty.h中定義了各種裝置的索引值,目前核心使用了27個。
Int tty_register_ldisc(int disc, struct tty_ldisc_ops *new_ldisc) tty_ldiscs[disc] = new_ldisc; new_ldisc->num = disc; new_ldisc->refcount = 0; |
執行緒規程初始化的相關函式是在tty_open時才會呼叫,在註冊流程中並未發現申請相關結構體及初始化的地方。此函式主要完成新tty的ldisc的記憶體申請及設定:為新申請的tty_struct設定執行緒規程物件,呼叫此函式時,tty_struct結構還未完全設定。初始化時,傳遞的N_TTY,在tty.h中定義為0,最終會從全域性的tty_ldiscs中取出對應的tty_ldisc_ops進行初始化。
int tty_ldisc_init(struct tty_struct *tty) struct tty_ldisc *ld = tty_ldisc_get(tty, N_TTY); ldops = get_ldops(disc); tty->ldisc = ld; |
static struct tty_ldisc *tty_ldisc_get(struct tty_struct *tty, int disc) ldops = get_ldops(disc); ld = kmalloc(sizeof(struct tty_ldisc), GFP_KERNEL | __GFP_NOFAIL); ld->ops = ldops; ld->tty = tty; |
從全域性陣列tty_ldiscs中,根據索引取出tty_ldisc_ops
struct tty_ldisc_ops *get_ldops(int disc) ldops = tty_ldiscs[disc]; |
終端裝置的寫功能函式。對資料進行處理,之後呼叫到uart_ops中的uart_write函式
static ssize_t n_tty_write(struct tty_struct *tty, struct file *file, const unsigned char *buf, size_t nr) DEFINE_WAIT_FUNC(wait, woken_wake_function); down_read(&tty->termios_rwsem); process_echoes(tty); add_wait_queue(&tty->write_wait, &wait); //此處兩種寫法有什麼區別,需要進行分析,不過最終都會呼叫到tty->ops->write,即為uart_ops中的uart_write if (O_OPOST(tty)){……} else{ c = tty->ops->write(tty, b, nr); } |
n_tty_receice_buf和n_tty_receive_buf2都是通過呼叫n_tty_receive_buf_common實現。n_tty_receice_buf會檢查room,而n_tty_receice_buf2不會檢查room。
int n_tty_receive_buf_common(struct tty_struct *tty, const unsigned char *cp, char *fp, int count, int flow) struct n_tty_data *ldata = tty->disc_data; __receive_buf(tty, cp, fp, n); //receice_buf最終呼叫到put_tty_queue,從read_buf中讀取資料 put_tty_queue *read_buf_addr(ldata, ldata->read_head) = c; return &ldata->read_buf[i & (N_TTY_BUF_SIZE - 1)]; ldata->read_head++; wake_up_interruptible_poll(&tty->read_wait, POLLIN); tty->receive_room = room; |
ssize_t n_tty_read(struct tty_struct *tty, struct file *file, unsigned char __user *buf, size_t nr) struct n_tty_data *ldata = tty->disc_data; unsigned char __user *b = buf; DEFINE_WAIT_FUNC(wait, woken_wake_function); c = job_control(tty, file); packet = tty->packet; tail = ldata->read_tail; add_wait_queue(&tty->read_wait, &wait); uncopied = copy_from_read_buf(tty, &b, &nr); uncopied += copy_from_read_buf(tty, &b, &nr); //將資料從ldata->read_buf中讀出來傳遞到使用者層 const unsigned char *from = read_buf_addr(ldata, tail); return &ldata->read_buf[i & (N_TTY_BUF_SIZE - 1)]; retval = copy_to_user(*b, from, n); |
在初次開啟tty/pty時呼叫,用來設定執行緒規程並繫結到tty。
int tty_ldisc_setup(struct tty_struct *tty, struct tty_struct *o_tty) int retval = tty_ldisc_open(tty, tty->ldisc); if (ld->ops->open) { //呼叫到線路規程層中n_tty_open ret = ld->ops->open(tty); } |
開啟一個ldisc。線上路規程與終端裝置匹配時呼叫。允許睡眠。
static int n_tty_open(struct tty_struct *tty) struct n_tty_data *ldata; ldata = vmalloc(sizeof(*ldata)); tty->disc_data = ldata; reset_buffer_flags(tty->disc_data); n_tty_set_termios(tty, NULL); |
本文以展訊平臺串列埠驅動為例做說明。
個人習慣從probe函式進行分析。此處為裝置驅動匹配的第一步,在probe函式中使用到的結構體如下:
static int sprd_probe(struct platform_device *pdev) { struct uart_port *up; …… //找到空閒的結構體指標 for (index = 0; index < ARRAY_SIZE(sprd_port); index++) if (sprd_porst[index] == NULL) break; //申請記憶體空間 sprd_port[index] = devm_kzalloc(&pdev->dev, sizeof(*sprd_port[index]), GFP_KERNEL); …… //初始化uart_port up = &sprd_port[index]->port; up->dev = &pdev->dev; up->line = index; up->type = PORT_SPRD; up->iotype = UPIO_MEM; up->uartclk = SPRD_DEF_RATE; up->fifosize = SPRD_FIFO_SIZE; up->ops = &serial_sprd_ops; up->flags = UPF_BOOT_AUTOCONF; …… up->mapbase = res->start; up->membase = devm_ioremap_resource(&pdev->dev, res); …… //probe時,當sprd_ports_num為零時,需要重新註冊sprd_uart_driver //如果有裝置remove,在remove時,如果sprd_ports_num為零,則解除安裝sprd_uart_driver if (!sprd_ports_num) { //註冊sprd_uart_driver驅動 ret = uart_register_driver(&sprd_uart_driver); uart_register_driver呼叫到tty_register_driver,會在proc下建立節點,之後返回。按我的理解,tty_register_driver是註冊了一個tty的驅動,這個驅動有了邏輯能力,但是這個時候這個驅動還沒有對應任何裝置,所以後續還要新增對應的埠(也就是晶片的物理串列埠),並建立/dev/下的裝置節點,上層用tty_driver驅動的邏輯來操作對應的埠 …… } sprd_ports_num++; ret = uart_add_one_port(&sprd_uart_driver, up); …… platform_set_drvdata(pdev, up); return ret; } |
通過程式碼可以看到,sprd_probe函