日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux系统TTY串口驱动实例详解

發布時間:2024/8/1 linux 50 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux系统TTY串口驱动实例详解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

簡介

在Linux系統中,終端是一類字符型設備,它包括多種類型,通常使用tty來簡稱各種類型的終端設備。由于串口也是一種終端,因此這里引入終端這個概念 。
Linux tty子系統包含:tty核心,tty線路規程和tty驅動。tty核心是對整個tty設備的抽象,對用戶提供統一的接口,tty線路規程是對傳輸數據的格式化,tty驅動則是面向tty設備的硬件驅動。它們的關系如下圖:

簡單來分的話可以說成兩層,一層是下層我們的串口驅動層,它直接與硬件相接觸,我們需要填充一個 struct uart_ops 的結構體,另一層是上層 tty 層,包括 tty 核心以及線路規程,它們各自都有一個 ops 結構,用戶空通過間是 tty 注冊的字符設備節點來訪問,這么說來如上圖所示涉及到了4個 ops 結構了,層層跳轉。下面,就來分析分析它們的層次結構。

源碼詳解

從整個串口驅動注冊程序來看,該程序只做了兩件事:
1、注冊uart_driver;
2、注冊platform_driver,也就是uart_add_one_port();
具體請細看下面的源碼分析:
一、uart_driver的注冊:
在 s3c2440 平臺,它是這樣來注冊串口驅動的,分配一個struct uart_driver 簡單填充,并調用uart_register_driver 注冊到內核中去。

static struct uart_driver s3c24xx_uart_drv = {.owner = THIS_MODULE,.dev_name = "s3c2410_serial",.nr = CONFIG_SERIAL_SAMSUNG_UARTS,.cons = S3C24XX_SERIAL_CONSOLE,.driver_name = S3C24XX_SERIAL_NAME,.major = S3C24XX_SERIAL_MAJOR,.minor = S3C24XX_SERIAL_MINOR, }; static int __init s3c24xx_serial_modinit(void) {int ret;ret = uart_register_driver(&s3c24xx_uart_drv);if (ret < 0) {printk(KERN_ERR "failed to register UART driver\n");return -1;}return 0; }

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 代表的是上層,它會在 register_uart_driver 中的過程中賦值,而uart_state 則代表下層,uart_state 也會在register_uart_driver 的過程中分配空間,但是它里面真正設置硬件相關的東西是 uart_state->uart_port ,這個uart_port 是需要我們從其它地方調用 uart_add_one_port 來添加的。

下面先分析串口驅動層的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; // 對應于一個串口設備 };

分配空間uart_driver->uart_state[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 */ #endifupf_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 */ };

這個結構體,是需要我們自己來填充的,比如我們 s3c2440 有3個串口,那么就需要填充3個 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 ,這個也是需要我們自己來實現的,后面代碼會詳細分析從用戶層到硬件層是如何一步步通過ops進行調用的。

struct uart_ops {unsigned int (*tx_empty)(struct uart_port *); /* 串口的Tx FIFO緩存是否為空 */void (*set_mctrl)(struct uart_port *, unsigned int mctrl); /* 設置串口modem控制 */unsigned int (*get_mctrl)(struct uart_port *); /* 獲取串口modem控制 */void (*stop_tx)(struct uart_port *); /* 禁止串口發送數據 */void (*start_tx)(struct uart_port *); /* 使能串口發送數據 */ void (*send_xchar)(struct uart_port *, char ch); /* 發送xChar */void (*stop_rx)(struct uart_port *); /* 禁止串口接收數據 */void (*enable_ms)(struct uart_port *); /* 使能modem的狀態信號 */void (*break_ctl)(struct uart_port *, int ctl); /* 設置break信號 */int (*startup)(struct uart_port *); /* 啟動串口,應用程序打開串口設備文件時,該函數會被調用 */void (*shutdown)(struct uart_port *);/* 關閉串口,應用程序關閉串口設備文件時,該函數會被調用 */void (*flush_buffer)(struct uart_port *);void (*set_termios)(struct uart_port *, struct ktermios *new,struct ktermios *old); /* 設置串口參數 */void (*set_ldisc)(struct uart_port *);/* 設置線路規程 */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內存資源,必要時還可以重新映射串口端口 */void (*config_port)(struct uart_port *, int); /* 執行串口所需的自動配置 */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 是在注冊過程中構建的,我們也就順便了解了注冊過程。

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);/** 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);}

uart_register_driver(&s3c24xx_uart_drv)注冊驅動時會有下面幾個操作:
1、分配空間uart_driver->uart_state[nr]空間,即申請 nr 個 uart_state 空間,用來存放驅動所支持的串口(端口)的物理信息;
2、分配tty_driver,將dev_name/major/minor/nr賦值給tty_driver;
3、設置ty_core層的ops為strucy tty_operations uart_ops,設置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驅動的注冊,實際就是tty_driver的注冊,都是將uart的參數傳給tty_driver,然后注冊字符設備、分配設備文件、將驅動寫進總線管理的tty_driver鏈表中;

注:從代碼可以看到uart_register_drive()函數中最終要調用tty_register_driver()函數來注冊tty驅動,因此與用戶空間打交道的工作完全交給了 tty_driver ,而且這一部分都是內核實現好的,我們不需要修改,了解一下工作原理即可;

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

這個是 tty 核心的 ops ,簡單一看,后面分析調用關系時,我們在來看具體的里邊的函數,下面來看 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);}/* 如果沒有主設備號則申請 */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; }

從上面源碼可以分析到:
1、為線路規程和termios分配空間,并使 tty_driver 相應的成員指向它們。
2、注冊字符設備,名字是 uart_driver->name 我們這里是“ttySAC”,文件操作函數集是 tty_fops。
3、將該 uart_driver->tty_drivers 添加到全局鏈表 tty_drivers 。
4、向 proc 文件系統添加 driver。

二、注冊platform_driver驅動
該函數就是實現將驅動掛到platform總線上去,總線接管設備和驅動的管理工作。

int s3c24xx_serial_init(struct platform_driver *drv,struct s3c24xx_uart_info *info) {dbg("s3c24xx_serial_init(%p,%p)\n", drv, info);#ifdef CONFIG_PMdrv->suspend = s3c24xx_serial_suspend;drv->resume = s3c24xx_serial_resume; #endifreturn platform_driver_register(drv); } static void __exit s3c2440_serial_exit(void) {platform_driver_unregister(&s3c2440_serial_driver); }module_init(s3c2440_serial_init); module_exit(s3c2440_serial_exit);

然后我們分析一下platform_driver函數:

static struct platform_driver s3c2440_serial_driver = {.probe = s3c2440_serial_probe,.remove = __devexit_p(s3c24xx_serial_remove),.driver = {.name = "s3c2440-uart",.owner = THIS_MODULE,}, };

在平臺設備注冊驅動的時候,總線會對驅動和設備匹配,如果匹配成功,將調用驅動的prob函數,我們具體追一下s3c2440_serial_driver 的prob函數:

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數組中尋找一個元素,這個數組里保存的是各個串口的信息。
假如說找到了串口0,拿到串口0后調用s3c24xx_serial_init_port完成串口的初始化,看看初始化函數:

/* s3c24xx_serial_init_port** initialise a single serial port from the platform device given*/static int s3c24xx_serial_init_port(struct s3c24xx_uart_port *ourport,struct s3c24xx_uart_info *info,struct platform_device *platdev) {struct uart_port *port = &ourport->port;struct s3c2410_uartcfg *cfg;struct resource *res;int ret;dbg("s3c24xx_serial_init_port: port=%p, platdev=%p\n", port, platdev);if (platdev == NULL)return -ENODEV;cfg = s3c24xx_dev_to_cfg(&platdev->dev);if (port->mapbase != 0)return 0;if (cfg->hwport > CONFIG_SERIAL_SAMSUNG_UARTS) {printk(KERN_ERR "%s: port %d bigger than %d\n", __func__,cfg->hwport, CONFIG_SERIAL_SAMSUNG_UARTS);return -ERANGE;}/* setup info for port */port->dev = &platdev->dev;ourport->info = info;/* copy the info in from provided structure */ourport->port.fifosize = info->fifosize;dbg("s3c24xx_serial_init_port: %p (hw %d)...\n", port, cfg->hwport);port->uartclk = 1;if (cfg->uart_flags & UPF_CONS_FLOW) {dbg("s3c24xx_serial_init_port: enabling flow control\n");port->flags |= UPF_CONS_FLOW;}/* sort our the physical and virtual addresses for each UART */res = platform_get_resource(platdev, IORESOURCE_MEM, 0);// 取得物理地址if (res == NULL) {printk(KERN_ERR "failed to find memory resource for uart\n");return -EINVAL;}dbg("resource %p (%lx..%lx)\n", res, res->start, res->end);port->mapbase = res->start;port->membase = S3C_VA_UART + res->start - (S3C_PA_UART & 0xfff00000); // 靜態映射ret = platform_get_irq(platdev, 0);if (ret < 0)port->irq = 0;else {port->irq = ret;ourport->rx_irq = ret;ourport->tx_irq = ret + 1;}ret = platform_get_irq(platdev, 1);if (ret > 0)ourport->tx_irq = ret;ourport->clk = clk_get(&platdev->dev, "uart");dbg("port: map=%08x, mem=%08x, irq=%d (%d,%d), clock=%ld\n",port->mapbase, port->membase, port->irq,ourport->rx_irq, ourport->tx_irq, port->uartclk);/* reset the fifos (and setup the uart) */s3c24xx_serial_resetport(port, cfg);return 0; }

上面代碼主要完成3項工作:
1、取串口的基地址
2、取串口的中斷號
3、復位FIFO
在回到s3c24xx_serial_probe函數,在初始化串口后,接下來完成下面的操作:
1、添加端口uart_add_one_port
2、添加屬性文件,這樣在sys下面就可以看到串口的信息了
3、初始化動態頻率調節s3c24xx_serial_cpufreq_register。

至此uart_driver和platform_driver都已注冊完畢,后續章節會細講TTY驅動數據的收發過程。

總結

以上是生活随笔為你收集整理的Linux系统TTY串口驱动实例详解的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。