Linux系统TTY串口驱动实例详解
簡介
在Linux系統(tǒng)中,終端是一類字符型設(shè)備,它包括多種類型,通常使用tty來簡稱各種類型的終端設(shè)備。由于串口也是一種終端,因此這里引入終端這個概念 。
Linux tty子系統(tǒng)包含:tty核心,tty線路規(guī)程和tty驅(qū)動。tty核心是對整個tty設(shè)備的抽象,對用戶提供統(tǒng)一的接口,tty線路規(guī)程是對傳輸數(shù)據(jù)的格式化,tty驅(qū)動則是面向tty設(shè)備的硬件驅(qū)動。它們的關(guān)系如下圖:
簡單來分的話可以說成兩層,一層是下層我們的串口驅(qū)動層,它直接與硬件相接觸,我們需要填充一個 struct uart_ops 的結(jié)構(gòu)體,另一層是上層 tty 層,包括 tty 核心以及線路規(guī)程,它們各自都有一個 ops 結(jié)構(gòu),用戶空通過間是 tty 注冊的字符設(shè)備節(jié)點來訪問,這么說來如上圖所示涉及到了4個 ops 結(jié)構(gòu)了,層層跳轉(zhuǎn)。下面,就來分析分析它們的層次結(jié)構(gòu)。
源碼詳解
從整個串口驅(qū)動注冊程序來看,該程序只做了兩件事:
1、注冊uart_driver;
2、注冊platform_driver,也就是uart_add_one_port();
具體請細(xì)看下面的源碼分析:
一、uart_driver的注冊:
在 s3c2440 平臺,它是這樣來注冊串口驅(qū)動的,分配一個struct uart_driver 簡單填充,并調(diào)用uart_register_driver 注冊到內(nèi)核中去。
uart_driver 中,我們只是填充了一些名字、設(shè)備號等信息,這些都是不涉及底層硬件訪問的,那是怎么回事呢?來看一下完整的 uart_driver 結(jié)構(gòu)或許就明白了。
struct uart_driver {struct module *owner; /* 擁有該uart_driver的模塊,一般為THIS_MODULE */const char *driver_name; /* 串口驅(qū)動名,串口設(shè)備文件名以驅(qū)動名為基礎(chǔ) */const char *dev_name; /* 串口設(shè)備名 */int major; /* 主設(shè)備號 */int minor; /* 次設(shè)備號 */int nr; /* 該uart_driver支持的串口個數(shù)(最大) */struct console *cons; /* 其對應(yīng)的console.若該uart_driver支持serial console,否則為NULL *//* 下面這倆,初始化為NULL */struct uart_state *state; /* 下層,串口驅(qū)動層 */struct tty_driver *tty_driver; /* tty相關(guān) */ };在我們上邊填充的結(jié)構(gòu)體中,有兩個成員未被賦值,對于tty_driver 代表的是上層,它會在 register_uart_driver 中的過程中賦值,而uart_state 則代表下層,uart_state 也會在register_uart_driver 的過程中分配空間,但是它里面真正設(shè)置硬件相關(guān)的東西是 uart_state->uart_port ,這個uart_port 是需要我們從其它地方調(diào)用 uart_add_one_port 來添加的。
下面先分析串口驅(qū)動層的uart_state:
struct uart_state {struct tty_port port;int pm_state;struct circ_buf xmit;struct tasklet_struct tlet;struct uart_port *uart_port; // 對應(yīng)于一個串口設(shè)備 };分配空間uart_driver->uart_state[nr]空間,即申請 nr 個 uart_state 空間,用來存放驅(qū)動所支持的串口(端口)的物理信息;
struct uart_port {spinlock_t lock; /* port lock */unsigned long iobase; /* io端口基地址(物理) */unsigned char __iomem *membase; /* io內(nèi)存基地址(虛擬) */unsigned int (*serial_in)(struct uart_port *, int);void (*serial_out)(struct uart_port *, int, int);unsigned int irq; /* 中斷號 */unsigned long irqflags; /* 中斷標(biāo)志 */unsigned int uartclk; /* 串口時鐘 */unsigned int fifosize; /* 串口緩沖區(qū)大小 */unsigned char x_char; /* xon/xoff char */unsigned char regshift; /* 寄存器位移 */unsigned char iotype; /* IO訪問方式 */unsigned char unused1;unsigned int read_status_mask; /* 關(guān)心 Rx error status */unsigned int ignore_status_mask; /* 忽略 Rx error status */struct uart_state *state; /* pointer to parent state */struct uart_icount icount; /* 串口信息計數(shù)器 */struct console *cons; /* struct console, if any */ #if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(SUPPORT_SYSRQ)unsigned long sysrq; /* sysrq timeout */ #endifupf_t flags;unsigned int mctrl; /* 當(dāng)前的Moden 設(shè)置 */unsigned int timeout; /* character-based timeout */unsigned int type; /* 端口類型 */const struct uart_ops *ops; /* 串口端口操作函數(shù) */unsigned int custom_divisor;unsigned int line; /* 端口索引 */resource_size_t mapbase; /* io內(nèi)存物理基地址 */struct device *dev; /* 父設(shè)備 */unsigned char hub6; /* this should be in the 8250 driver */unsigned char suspended;unsigned char unused[2];void *private_data; /* generic platform data pointer */ };這個結(jié)構(gòu)體,是需要我們自己來填充的,比如我們 s3c2440 有3個串口,那么就需要填充3個 uart_port ,并且通過 uart_add_one_port 添加uart_driver->uart_state->uart_port 中去。當(dāng)然 uart_driver 有多個 uart_state ,每個 uart_state 有一個 uart_port 。在 uart_port 里還有一個非常重要的成員 struct uart_ops *ops ,這個也是需要我們自己來實現(xiàn)的,后面代碼會詳細(xì)分析從用戶層到硬件層是如何一步步通過ops進(jìn)行調(diào)用的。
struct uart_ops {unsigned int (*tx_empty)(struct uart_port *); /* 串口的Tx FIFO緩存是否為空 */void (*set_mctrl)(struct uart_port *, unsigned int mctrl); /* 設(shè)置串口modem控制 */unsigned int (*get_mctrl)(struct uart_port *); /* 獲取串口modem控制 */void (*stop_tx)(struct uart_port *); /* 禁止串口發(fā)送數(shù)據(jù) */void (*start_tx)(struct uart_port *); /* 使能串口發(fā)送數(shù)據(jù) */ void (*send_xchar)(struct uart_port *, char ch); /* 發(fā)送xChar */void (*stop_rx)(struct uart_port *); /* 禁止串口接收數(shù)據(jù) */void (*enable_ms)(struct uart_port *); /* 使能modem的狀態(tài)信號 */void (*break_ctl)(struct uart_port *, int ctl); /* 設(shè)置break信號 */int (*startup)(struct uart_port *); /* 啟動串口,應(yīng)用程序打開串口設(shè)備文件時,該函數(shù)會被調(diào)用 */void (*shutdown)(struct uart_port *);/* 關(guān)閉串口,應(yīng)用程序關(guān)閉串口設(shè)備文件時,該函數(shù)會被調(diào)用 */void (*flush_buffer)(struct uart_port *);void (*set_termios)(struct uart_port *, struct ktermios *new,struct ktermios *old); /* 設(shè)置串口參數(shù) */void (*set_ldisc)(struct uart_port *);/* 設(shè)置線路規(guī)程 */void (*pm)(struct uart_port *, unsigned int state,unsigned int oldstate); /* 串口電源管理 */int (*set_wake)(struct uart_port *, unsigned int state);/** Return a string describing the type of the port*/const char *(*type)(struct uart_port *);/** Release IO and memory resources used by the port.* This includes iounmap if necessary.*/void (*release_port)(struct uart_port *);/** Request IO and memory resources used by the port.* This includes iomapping the port if necessary.*/int (*request_port)(struct uart_port *); /* 申請必要的IO端口/IO內(nèi)存資源,必要時還可以重新映射串口端口 */void (*config_port)(struct uart_port *, int); /* 執(zhí)行串口所需的自動配置 */int (*verify_port)(struct uart_port *, struct serial_struct *); /* 核實新串口的信息 */int (*ioctl)(struct uart_port *, unsigned int, unsigned long); #ifdef CONFIG_CONSOLE_POLLvoid (*poll_put_char)(struct uart_port *, unsigned char);int (*poll_get_char)(struct uart_port *); #endif };2、上層tty_core層
tty 層要從 register_uart_driver 來看起了,因為 tty_driver 是在注冊過程中構(gòu)建的,我們也就順便了解了注冊過程。
uart_register_driver(&s3c24xx_uart_drv)注冊驅(qū)動時會有下面幾個操作:
1、分配空間uart_driver->uart_state[nr]空間,即申請 nr 個 uart_state 空間,用來存放驅(qū)動所支持的串口(端口)的物理信息;
2、分配tty_driver,將dev_name/major/minor/nr賦值給tty_driver;
3、設(shè)置ty_core層的ops為strucy tty_operations uart_ops,設(shè)置flags為TTY_DRIVER_REAL_RAM|TTY_DRIVER_DYNAMIC_DEV;
4、初始化tty_port:uart_driver->uart_state[nr]->tty_port,如ttybuffer、flush_to_ldisc、tty_port->ops=&uart_port_ops(struct uart_port_ops uart_port_ops);
5、注冊tty_driver,后面從源碼中可以分析出uart驅(qū)動的注冊,實際就是tty_driver的注冊,都是將uart的參數(shù)傳給tty_driver,然后注冊字符設(shè)備、分配設(shè)備文件、將驅(qū)動寫進(jìn)總線管理的tty_driver鏈表中;
注:從代碼可以看到uart_register_drive()函數(shù)中最終要調(diào)用tty_register_driver()函數(shù)來注冊tty驅(qū)動,因此與用戶空間打交道的工作完全交給了 tty_driver ,而且這一部分都是內(nèi)核實現(xiàn)好的,我們不需要修改,了解一下工作原理即可;
這個是 tty 核心的 ops ,簡單一看,后面分析調(diào)用關(guān)系時,我們在來看具體的里邊的函數(shù),下面來看 tty_driver 的注冊。
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);}/* 如果沒有主設(shè)備號則申請 */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) { /* 為線路規(guī)程和termios分配空間 */driver->ttys = (struct tty_struct **)p;driver->termios = (struct ktermios **)(p + driver->num);} else {driver->ttys = NULL;driver->termios = NULL;}/* 創(chuàng)建字符設(shè)備,使用 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文件系統(tǒng)注冊driver */proc_tty_register_driver(driver);driver->flags |= TTY_DRIVER_INSTALLED;return 0; }從上面源碼可以分析到:
1、為線路規(guī)程和termios分配空間,并使 tty_driver 相應(yīng)的成員指向它們。
2、注冊字符設(shè)備,名字是 uart_driver->name 我們這里是“ttySAC”,文件操作函數(shù)集是 tty_fops。
3、將該 uart_driver->tty_drivers 添加到全局鏈表 tty_drivers 。
4、向 proc 文件系統(tǒng)添加 driver。
二、注冊platform_driver驅(qū)動
該函數(shù)就是實現(xiàn)將驅(qū)動掛到platform總線上去,總線接管設(shè)備和驅(qū)動的管理工作。
然后我們分析一下platform_driver函數(shù):
static struct platform_driver s3c2440_serial_driver = {.probe = s3c2440_serial_probe,.remove = __devexit_p(s3c24xx_serial_remove),.driver = {.name = "s3c2440-uart",.owner = THIS_MODULE,}, };在平臺設(shè)備注冊驅(qū)動的時候,總線會對驅(qū)動和設(shè)備匹配,如果匹配成功,將調(diào)用驅(qū)動的prob函數(shù),我們具體追一下s3c2440_serial_driver 的prob函數(shù):
int s3c24xx_serial_probe(struct platform_device *dev,struct s3c24xx_uart_info *info) {struct s3c24xx_uart_port *ourport;int ret;dbg("s3c24xx_serial_probe(%p, %p) %d\n", dev, info, probe_index);ourport = &s3c24xx_serial_ports[probe_index];probe_index++;dbg("%s: initialising port %p...\n", __func__, ourport);ret = s3c24xx_serial_init_port(ourport, info, dev);if (ret < 0)goto probe_err;dbg("%s: adding port\n", __func__);uart_add_one_port(&s3c24xx_uart_drv, &ourport->port);platform_set_drvdata(dev, &ourport->port);ret = device_create_file(&dev->dev, &dev_attr_clock_source);if (ret < 0)printk(KERN_ERR "%s: failed to add clksrc attr.\n", __func__);ret = s3c24xx_serial_cpufreq_register(ourport);if (ret < 0)dev_err(&dev->dev, "failed to add cpufreq notifier\n");return 0;probe_err:return ret; }我們來分析上面這段代碼:
這段代碼首先從s3c24xx_serial_ports數(shù)組中尋找一個元素,這個數(shù)組里保存的是各個串口的信息。
假如說找到了串口0,拿到串口0后調(diào)用s3c24xx_serial_init_port完成串口的初始化,看看初始化函數(shù):
上面代碼主要完成3項工作:
1、取串口的基地址
2、取串口的中斷號
3、復(fù)位FIFO
在回到s3c24xx_serial_probe函數(shù),在初始化串口后,接下來完成下面的操作:
1、添加端口uart_add_one_port
2、添加屬性文件,這樣在sys下面就可以看到串口的信息了
3、初始化動態(tài)頻率調(diào)節(jié)s3c24xx_serial_cpufreq_register。
至此uart_driver和platform_driver都已注冊完畢,后續(xù)章節(jié)會細(xì)講TTY驅(qū)動數(shù)據(jù)的收發(fā)過程。
總結(jié)
以上是生活随笔為你收集整理的Linux系统TTY串口驱动实例详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PLC编程:梯形图的转换设计法
- 下一篇: 31部黑客电影,你看过哪几部?