Linux驅動之串口(UART)


<uart驅動程序概述>
在嵌入式Linux系統中,串口被看成終端設備,終端設備(tty)的驅動程序分為3部分:
tty_core
tty_disicipline 
tty_driver
 
包括3個結構體:uart_driver,uart_port,uart_ops(include/serial_core.h)。因此,實現一個平台的uart驅動程序只要實現這3個結構體即可。
 
<uart_driver和tty_driver之間的關系>
a:uart_driver結構體 
uart_driver包含了串口設備名、串口驅動名、主次設備號、串口控制台(可選)等信息,還封裝了tty_driver(底層串口驅動無需關心tty_driver)
struct uart_driver
 {

    struct module  *owner; //擁有該uart_driver的模塊,一般為THIS_MODULE 
    constchar *driver_name; // 串口驅動名,串口設備文件名以驅動名為基礎 
    constchar *dev_name; // 串口設備名 
    int major; //主設備號 
    int minor; //次設備號 
    int nr; // 該uart_driver支持的串口個數(最大) 
    struct console  *cons;// 其對應的console.若該uart_driver支持serial console,否則為NULL 
    .............................
    struct uart_state  *state;
    struct tty_driver  *tty_driver;   //uart_driver封裝了tty_driver,使底層uart驅動不用關心ttr_driver。

};

 

 
(1)一個tty驅動程序必須注冊/注銷tty_driver。
(2)一個uart驅動則變為注冊/注銷uart_driver。
使用如下接口:
int uart_register_driver(struct uart_driver *drv);
void uart_unregister_driver(struct uart_driver *drv);
int tty_register_driver(struct tty_driver *drv);
void tty_unregister_driver(struct tty_driver *drv);

 


 
實際上, uart_register_driver()和uart_unregister_driver()中分別包含了tty_register_driver()和tty_unregister_driver()的操作, 詳情如下:
 
 
<uart_port>
a:uart_port用於描述一個UART端口(直接對應於一個串口)的I/O端口或I/O內存地址、FIFO大小、端口類型等信息。
struct uart_port {
spinlock_t lock;/* 串口端口鎖 */
unsignedint iobase;/* IO端口基地址 */
unsignedchar __iomem *membase;/* IO內存基地址,經映射(如ioremap)后的IO內存虛擬基地址 */
unsignedint irq;/* 中斷號 */
unsignedint uartclk;/* 串口時鍾 */
unsignedint fifosize;/* 串口FIFO緩沖大小 */
unsignedchar x_char;/* xon/xoff字符 */
unsignedchar regshift;/* 寄存器位移 */
unsignedchar iotype;/* IO訪問方式 */
unsignedchar unused1;

#define UPIO_PORT (0)/* IO端口 */
#define UPIO_HUB6 (1)
#define UPIO_MEM (2)/* IO內存 */
#define UPIO_MEM32 (3)
#define UPIO_AU (4)/* Au1x00 type IO */
#define UPIO_TSI (5)/* Tsi108/109 type IO */
#define UPIO_DWAPB (6)/* DesignWare APB UART */
#define UPIO_RM9000 (7)/* RM9000 type IO */

unsignedint read_status_mask;/* 關心的Rx error status */
unsignedint ignore_status_mask;/* 忽略的Rx error status */
struct uart_info *info;        //重要,見下面
struct uart_icount  icount;   /* 計數器 uart_icount為串口信息計數器,包含了發送字符計數、接收字符計數等。在串口的發送中斷處理函數和接收中斷處理函數中,我們需要管理這些計數。*/ 

struct console *cons;/* console結構體 */
#ifdefCONFIG_SERIAL_CORE_CONSOLE
unsignedlong sysrq;/* sysrq timeout */
#endif

upf_t flags;

#define UPF_FOURPORT ((__forceupf_t)(1 << 1))
#define UPF_SAK ((__forceupf_t)(1 << 2))
#define UPF_SPD_MASK ((__forceupf_t)(0x1030))
#define UPF_SPD_HI ((__forceupf_t)(0x0010))
#define UPF_SPD_VHI ((__forceupf_t)(0x0020))
#define UPF_SPD_CUST ((__forceupf_t)(0x0030))
#define UPF_SPD_SHI ((__forceupf_t)(0x1000))
#define UPF_SPD_WARP ((__forceupf_t)(0x1010))
#define UPF_SKIP_TEST ((__forceupf_t)(1 << 6))
#define UPF_AUTO_IRQ ((__forceupf_t)(1 << 7))
#define UPF_HARDPPS_CD ((__forceupf_t)(1 << 11))
#define UPF_LOW_LATENCY ((__forceupf_t)(1 << 13))
#define UPF_BUGGY_UART ((__forceupf_t)(1 << 14))
#define UPF_MAGIC_MULTIPLIER((__force upf_t)(1 << 16))
#define UPF_CONS_FLOW ((__forceupf_t)(1 << 23))
#define UPF_SHARE_IRQ ((__forceupf_t)(1 << 24))
#define UPF_BOOT_AUTOCONF ((__forceupf_t)(1 << 28))
#define UPF_FIXED_PORT ((__forceupf_t)(1 << 29))
#define UPF_DEAD ((__forceupf_t)(1 << 30))
#define UPF_IOREMAP ((__forceupf_t)(1 << 31))

#define UPF_CHANGE_MASK ((__forceupf_t)(0x17fff))
#define UPF_USR_MASK ((__forceupf_t)(UPF_SPD_MASK|UPF_LOW_LATENCY))

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內存物理基地址,可用於ioremap */
struct device *dev;/* 父設備 */
unsigned char hub6;/* this should be in the 8250 driver */
unsigned char suspended;
unsigned char unused[2];
void*private_data;/* 端口私有數據,一般為platform數據指針 */
};

 


 
b:串口核心層提供如下函數來添加1個端口:
int uart_add_one_port(struct uart_driver *drv, struct uart_port *port);
(1)對上述函數的調用應該發生在uart_register_driver()之后,uart_add_one_port()的一個最重要作用是封裝了tty_register_device()。
(2)uart_add_one_port()的“反函數”是uart_remove_one_port(),其中會調用tty_unregister_device(),原型為:
int uart_remove_one_port(struct uart_driver *drv, struct uart_port *port);
 
c:struct uart_info{}
uart_info有兩個成員在底層串口驅動會用到:xmit和tty。用戶空間程序通過串口發送數據時,上層驅動將用戶數據保存在xmit;而串口發送中斷處理函數就是通過xmit獲取到用戶數據並將它們發送出去。串口接收中斷處理函數需要通過tty將接收到的數據傳遞給行規則層
struct uart_info {
struct tty_struct *tty;   //接受
struct circ_buf xmit;    //上層需要發送
uif_t flags;
/*
* Definitions for info->flags. These are _private_ to serial_core,and
* are specific to this structure. They may be queried by low leveldrivers.
*/
#define UIF_CHECK_CD ((__force uif_t)(<< 25))
#define UIF_CTS_FLOW ((__force uif_t)(<< 26))
#define UIF_NORMAL_ACTIVE ((__force uif_t)(<< 29))
#define UIF_INITIALIZED ((__force uif_t)(<< 31))
#define UIF_SUSPENDED ((__force uif_t)(<< 30))
int blocked_open;
struct tasklet_struct tlet;    //上層驅動任務等待隊列的
wait_queue_head_t open_wait;
wait_queue_head_t delta_msr_wait;
};
<uart_port >

Uart_port中有一個重要的uart_ops,底層硬件需要實現:

struct uart_ops {
unsignedint(*tx_empty)(struct uart_port *); /* 串口的Tx FIFO緩存是否為空 */
void(*set_mctrl)(struct uart_port *,unsignedint mctrl);/* 設置串口modem控制 */
unsignedint(*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(*set_termios)(struct uart_port *,struct ktermios *new,struct ktermios *old);/* 設置串口參數 */
void(*pm)(struct uart_port *,unsignedint state,
unsignedint oldstate);/* 串口電源管理 */
int(*set_wake)(struct uart_port *,unsignedint state);/* */
constchar*(*type)(struct uart_port *);/* 返回一描述串口類型的字符串 */
void(*release_port)(struct uart_port *);/* 釋放串口已申請的IO端口/IO內存資源,必要時還需iounmap */
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 *,unsignedint,unsignedlong);/* IO控制 */
};

 

 

 

 

 

 

 

uart_driver通過Serial Core層

int uart_register_driver(struct uart_driver *drv)

向Core注冊,通過int uart_add_one_port(struct uart_driver *drv,struct uart_port *port)向該驅動添加uart_port。

 

 

<串口驅動的主要工作>

a:分析
(1)在使用串口核心層這個通用串口tty驅動層的接口后,一個串口驅動要完成的主要工作將包括:
定義uart_driver、uart_ops、uart_port等結構體的實例,並在適當的地方根據具體硬件和驅動的情況初始化它們。
(當然具體設備xxx的驅動可以將這些結構套在新定義的xxx_uart_driver、xxx_uart_ops,xxx_uart_port之內)
 
(2) 在模塊初始化時調用uart_register_driver()和uart_add_one_port()以注冊UART驅動並添加端口,在模塊卸載時調用uart_unregister_driver()和uart_remove_one_port()以注銷UART驅動並移除端口。
 
(3) 根據具體硬件的datasheet實現uart_ops中的成員函數,這些函數的實現成為UART驅動的主體工作。
串口驅動初始化過程。 在S3C2410串口驅動的模塊加載函數中會調用uart_register_driver()注冊s3c24xx_uart_drv這個uart_driver。
初始化過程如下:
s3c2410_serial_init()
→platform_driver_register()
s3c24xx_serial_probe()被執行,而s3c24xx_serial_probe()函數中會調用
s3c24xx_serial_init_port()初始化UART端口並調用
uart_add_one_port()添加端口
 
(4)串口操作函數,uart_ops接口函數
S3C2410串口驅動uart_ops結構體的startup ()成員函數s3c24xx_serial_startup()用於啟動端口,申請端口的發送、接收中斷,使能端口的發送和接收。
 
 
b:代碼示例
(1)平台資源

static struct resource s3c2410_uart0_resource[] = {

   ………………………………

};

static struct resource s3c2410_uart1_resource[] = {

   ………………………………

};

static struct resource s3c2410_uart2_resource[] = {

   ………………………………

};

在文件linux/arch/arm/plat-samsung/dev-uart.c中定義了每個串口對應的平台設備。

static struct platform_device s3c24xx_uart_device0 = {

.id = 0,

};

static struct platform_device s3c24xx_uart_device1 = {

.id = 1,

};

static struct platform_device s3c24xx_uart_device2 = {

.id = 2,

};

在文件linux/arch/arm/mach-s3c2440/mach-smdk2440.c中有串口一些寄存器的初始化配置。

static struct s3c2410_uartcfg smdk2440_uartcfgs[] __initdata = {

[0] = {

…………………………

},

[1] = {

…………………………

},

/* IR port */

[2] = {

…………………………

}

};

在文件linux/arch/arm/mach-s3c2440/mach-smdk2440.c中將調用函數

s3c24xx_init_uarts()最終將上面的硬件資源,初始化配置,平台設備整合到一起。

在文件 linux/arch/arm/plat-s3c/init.c中有

static int __init s3c_arch_init(void)

{

………………………………

ret = platform_add_devices(s3c24xx_uart_devs, nr_uarts);

return ret;

}

這個函數將串口所對應的平台設備添加到了內核。

(2)串口設備驅動原理淺析

我認為任何設備在linux中的實現就“兩條線”。一是設備模型的建立,二是讀寫數據流。串口驅動也是這樣。

 

串口設備模型建立:

串口設備驅動的核心結構體在文件linux/drivers/serial/samsuing.c中如下

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)

{

………………………………

ret = uart_register_driver(&s3c24xx_uart_drv);

………………………………

}

int uart_register_driver(struct uart_driver *drv)

{

………………………………

//每一個端口對應一個state

drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);

………………………………

normal = alloc_tty_driver(drv->nr); //分配該串口驅動對應的tty_driver 

………………………………

drv->tty_driver = normal; //讓drv->tty_driver字段指向這個tty_driver

………………………………

normal->driver_name = drv->driver_name;

normal->name = drv->dev_name;

normal->major = drv->major;

normal->minor_start = drv->minor;

………………………………

//設置該tty驅動對應的操作函數集tty_operations (linux/drivers/char/core.c)

tty_set_operations(normal, &uart_ops); 

………………………………

retval = tty_register_driver(normal); //將tty驅動注冊到內核

………………………………

}

(3)串口本身是一個字符設備

其實tty驅動的本質是一個字符設備,在文件 linux/drivers/char/tty_io.c中,這樣才能通過操作設備文件操作硬件

 

int tty_register_driver(struct tty_driver *driver)

{

………………………………

cdev_init(&driver->cdev, &tty_fops);

driver->cdev.owner = driver->owner;

error = cdev_add(&driver->cdev, dev, driver->num);

………………………………

}

它所關聯的操作函數集tty_fops在文件linux/drivers/char/tty_io.c中實現

static const struct file_operations tty_fops = {

.llseek = no_llseek,

.read = tty_read,

.write = tty_write,

………………………………

.open = tty_open,

………………………………

};

到此串口的驅動作為tty_driver被注冊到了內核。前面提到串口的每一個端口都是作為平台設備被添加到內核的。那么這些平台設備就對應着有它們的平台設備驅動。在文件linux/drivers/serial/s3c2440.c中有:

 

static struct platform_driver s3c2440_serial_driver = {

.probe = s3c2440_serial_probe,

.remove = __devexit_p(s3c24xx_serial_remove),

.driver = {

.name = "s3c2440-uart",

.owner = THIS_MODULE,

},

};

 

當其驅動與設備匹配時就會調用他的探測函數

static int s3c2440_serial_probe(struct platform_device *dev)

{

return s3c24xx_serial_probe(dev, &s3c2440_uart_inf);

}

每一個端口都有一個描述它的結構體s3c24xx_uart_port 在 文件linux/drivers/serial/samsuing.c

static struct s3c24xx_uart_port s3c24xx_serial_ports[CONFIG_SERIAL_SAMSUNG_UARTS] = {

[0] = {

.port = {

.lock = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[0].port.lock),

.iotype = UPIO_MEM,

.irq = IRQ_S3CUART_RX0,  //該端口的中斷號

.uartclk = 0,

.fifosize = 16,

.ops = &s3c24xx_serial_ops, //該端口的操作函數集

.flags = UPF_BOOT_AUTOCONF,

.line = 0, //端口編號

}

},

………………………………

上面探測函數的具體工作是函數s3c24xx_serial_probe()來完成的

int s3c24xx_serial_probe(struct platform_device *dev,struct s3c24xx_uart_info *info)

 

{

………………………………

//根據平台設備提供的硬件資源等信息初始化端口描述結構體中的一些字段

ret = s3c24xx_serial_init_port(ourport, info, dev);

//前面注冊了串口驅動,這里便要注冊串口設備

uart_add_one_port(&s3c24xx_uart_drv, &ourport->port);

………………………………

}

int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)

{

 ………………………………

//前面說串口驅動是tty_driver,這里可以看到串口設備其實是tty_dev

tty_dev = tty_register_device(drv->tty_driver, uport->line, uport->dev);

………………………………

}

串口數據流分析:

    在串口設備模型建立中提到了三個操作函數集,uart_ops ,tty_fops,s3c24xx_serial_ops數據的流動便是這些操作函數間的調用,這些調用關系如下:

 

在對一個設備進行其他操作之前必須先打開它,linux/drivers/char/tty_io.c

static const struct file_operations tty_fops = {

………………………………

.open = tty_open,

………………………………

};

static int tty_open(struct inode *inode, struct file *filp)

{

………………………………

dev_t device = inode->i_rdev;

………………………………

driver = get_tty_driver(device, &index); //根據端口設備號獲取它的索引號

………………………………

if (tty) {

………………………………

} else

tty = tty_init_dev(driver, index, 0); //創建一個tty_struct 並初始化

………………………………

}

struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx,int first_ok)

{

………………………………

tty = alloc_tty_struct(); //分配一個tty_struct結構

//一些字段的初始化,

initialize_tty_struct(tty, driver, idx);

//完成的主要工作是driver->ttys[idx] = tty;

retval = tty_driver_install_tty(driver, tty);

………………………………

/*

下面函數主要做的就是調用線路規程的打開函數ld->ops->open(tty)。

在這個打開函數中分配了一個重要的數據緩存

tty->read_buf = kzalloc(N_TTY_BUF_SIZE, GFP_KERNEL);

*/

retval = tty_ldisc_setup(tty, tty->link);

}

void initialize_tty_struct(struct tty_struct *tty,struct tty_driver *driver, int idx)

{

………………………………

//獲取線路規程操作函數集tty_ldisc_N_TTY,並做這樣的工作tty->ldisc = ld;

tty_ldisc_init(tty);

………………………………

/*

下面函數的主要工作是INIT_DELAYED_WORK(&tty->buf.work, flush_to_ldisc);

初始化一個延時tty->buf.work 並關聯一個處理函數flush_to_ldisc(),這個函數將在

數據讀取的時候用到。

*/

tty_buffer_init(tty);

………………………………

tty->driver = driver; 

tty->ops = driver->ops; //這里的ops就是struct tty_operations uart_ops

tty->index = idx; //idx就是該tty_struct對應端口的索引號

tty_line_name(driver, idx, tty->name);

}

端口設備打開之后就可以進行讀寫操作了,這里只討論數據的讀取,在文件 linux/drivers/char/tty_io.c中,

static const struct file_operations tty_fops = {

………………………………

.read = tty_read,

………………………………

};

static ssize_t tty_read(struct file *file, char __user *buf, size_t count,

loff_t *ppos)

{

………………………………

ld = tty_ldisc_ref_wait(tty); //獲取線路規程結構體

if (ld->ops->read) //調用線路規程操作函數集中的n_tty_read()函數

i = (ld->ops->read)(tty, file, buf, count);

else

………………………………

}

在linux/drivers/char/N_tty.c中:

struct tty_ldisc_ops tty_ldisc_N_TTY = {

………………………………

.open            = n_tty_open,

………………………………

.read            = n_tty_read,

………………………………

};

static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,

 unsigned char __user *buf, size_t nr)

{

………………………………

while (nr) {

………………………………

if (tty->icanon && !L_EXTPROC(tty)) {

//如果設置了tty->icanon 就從緩存tty->read_buf[]中逐個數據讀取,並判斷讀出的每一個數//據的正確性或是其他數據類型等。

eol = test_and_clear_bit(tty->read_tail,tty->read_flags);

c = tty->read_buf[tty->read_tail];

………………………………

} else {

………………………………

//如果沒有設置tty->icanon就從緩存tty->read_buf[]中批量讀取數據,之所以要進行兩次讀

//取是因為緩存tty->read_buf[]是個環形緩存

uncopied = copy_from_read_buf(tty, &b, &nr);

uncopied += copy_from_read_buf(tty, &b, &nr);

………………………………

}

}

………………………………

}

用戶空間是從緩存tty->read_buf[]中讀取數據讀的,那么緩存tty->read_buf[]中的數據有是從那里來的呢?分析如下:

回到文件 linux/drivers/serial/samsuing.c中,串口數據接收中斷處理函數實現如下:

這是串口最原始的數據流入的地方

static irqreturn_t  s3c24xx_serial_rx_chars(int irq, void *dev_id)

{

………………………………

while (max_count-- > 0) {

………………………………

ch = rd_regb(port, S3C2410_URXH); //從數據接收緩存中讀取一個數據

………………………………

flag = TTY_NORMAL; //普通數據,還可能是其他數據類型在此不做討論

………………………………

/*

下面函數做的最主要工作是這樣

struct tty_buffer *tb = tty->buf.tail;

tb->flag_buf_ptr[tb->used] = flag;

tb->char_buf_ptr[tb->used++] = ch;

將讀取的數據和該數據對應標志插入 tty->buf。

*/

uart_insert_char(port, uerstat, S3C2410_UERSTAT_OVERRUN, ch, flag);

}

tty_flip_buffer_push(tty); //將讀取到的max_count個數據向上層傳遞。

 out:

return IRQ_HANDLED;

}

void tty_flip_buffer_push(struct tty_struct *tty)

{

………………………………

if (tty->low_latency)

flush_to_ldisc(&tty->buf.work.work);

else

schedule_delayed_work(&tty->buf.work, 1); 

//這里這個延時work在上面串口設備打開中提到過,該work的處理函數也是flush_to_ldisc。

}

static void flush_to_ldisc(struct work_struct *work)

{

………………………………

while ((head = tty->buf.head) != NULL) {

………………………………

char_buf = head->char_buf_ptr + head->read;

flag_buf = head->flag_buf_ptr + head->read;

………………………………

//剛才在串口接收中斷處理函數中,將接收到的數據和數據標志存到tty->buf中,現在將

//這些數據和標志用char_buf 和flag_buf指向進一步向上傳遞。

disc->ops->receive_buf(tty, char_buf,flag_buf, count);

spin_lock_irqsave(&tty->buf.lock, flags);

}

}

上面調用的函數disc->ops->receive_buf在文件linux/drivers/char/N_tty.c中實現

struct tty_ldisc_ops tty_ldisc_N_TTY = {

………………………………

.receive_buf     = n_tty_receive_buf,

………………………………

};

static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp, char *fp, int count)

{

………………………………

//現在可以看到緩沖區tty->read_buf 中數據的由來了。

if (tty->real_raw) {

//如果設置了tty->real_raw將上面講到的些傳入數據批量拷貝到tty->read_head中。

//對環形緩存區的數據拷貝需要進行兩次,第一次拷貝從當前位置考到緩存的末尾,如果還//有沒考完的數據而且緩存區開始出處還有剩余空間,就把沒考完的數據考到開始的剩余空

//間中。

spin_lock_irqsave(&tty->read_lock, cpuflags);

i = min(N_TTY_BUF_SIZE - tty->read_cnt,N_TTY_BUF_SIZE - tty->read_head);

i = min(count, i);

memcpy(tty->read_buf + tty->read_head, cp, i);

tty->read_head = (tty->read_head + i) & (N_TTY_BUF_SIZE-1);

tty->read_cnt += i;

cp += i;

count -= i;

i = min(N_TTY_BUF_SIZE - tty->read_cnt,

N_TTY_BUF_SIZE - tty->read_head);

i = min(count, i);

memcpy(tty->read_buf + tty->read_head, cp, i);

tty->read_head = (tty->read_head + i) & (N_TTY_BUF_SIZE-1);

tty->read_cnt += i;

spin_unlock_irqrestore(&tty->read_lock, cpuflags);

} else {

for (i = count, p = cp, f = fp; i; i--, p++) {

//如果沒有設置tty->real_raw,就根據傳入數據標志分類獲取數據。

………………………………

}

………………………………

}

………………………………

}

到此,數據讀取的整個過程就結束了。可以看出數據讀取可以分為兩個階段,一個階段是上層函數從環形緩存區tty->read_buf 讀取數據,第二階段是底層函數將接收的數據考到環形緩存區tty->read_buf 中。

 

 

串口驅動程序

 

串口驅動之tty

 

概念解析:

Linux中,終端是一類字符設備,他包括多種類型通常使用tty來簡稱各種中斷設備

串口終端(/dev/ttyS*):

串口終端是使用串口連接的終端設備,Linux中將每個串口設備 都看作一個字符設備,這些串行端口對應的設備名稱是/dev/ttySAC0 /dev/ttySAC1

 

控制台終端(/dev/console):

Linux,計算中的輸出設備設備通常被稱為控制台終端(console).這里特指printk()信息輸出的涉筆。注意:/dev/console 是一個虛擬的設備,他需要映射到真正的tty上。比如通過內核啟動參數console = ttySAC0”就是把console 映射到串口0,經常被內核所使用。

注意:這里的終端是一個虛擬設備,虛擬 設備必須和實際的設備聯系起來console = ttySAC0 系統啟動時候就關聯起來了

 

虛擬終端(/dev/tty*)

當用戶登錄的時候使用的是虛擬終端,使用快捷鍵組合:ctcl+alt+[F1-F6]組合鍵就可以切換到tty1,tty2,tty3等上面去。tty1-tty6等稱為虛擬終端,而tty0 是當前使用的終端的一個別名。主要是提供給應用程序使用。

 

 

tty架構

 

 

tty核心:

tty核心是對整個tty設備的抽象,並提供單一的接口

tty線路規划:

tty線路規程是對數據的傳輸的格式化,比如需要實現某種協議,就需要將協議的實現代碼放在該位置 
tty驅動:

是面向tty設備的硬件驅動

 

 

注意:Linux中的獲取回溯信息使用函數 dump_stack()用來顯示各種函數的調用信息。

 

 

串口驅動程序的結構

分析:串口驅動需要提供給用戶讀數據的功能,寫數據,打開串口和關閉串口的功能。打開之前需要對肯定需要對串口進行初始化的工作。

重要數據結構:

UART 驅動程序結構:struct uart_driver//一個串口對應一個串口驅動,用於描述串口結構

UART 端口結構:struct uart_port//有幾個串口就對應幾個port

UART 相關操作函數結構struct uart_ops//對應相關串口所支持的操作函集

UART 狀態結構:struct uart_state

UART 信息結構:struct uart_info

 

串口初始化:

(1)定義並描述串口

struct uart_driver

(2)注冊串口驅動程序

uart_register_driver

1)取出相應的串口

2)初始化該取出的串口

 

 

串口驅動之打開驅動:

系統調用過程:

用戶使用open()函數打開設備文件

{注意:

(1)打開設備文件肯定有對應的設備驅動文件打開函數:file_operations.

(2)在使用uart_register_driver ()注冊串口驅動的時候,該函數里面會調用函數tty_register_driver(),該函數會調用cdev_init()函數和cdev_add()

(3)從這里可以看出tty設備是屬於字符設備

}

 

——>內核調用cdev結構中的file-operations指針所指向結構里的tty-open()函數——>tty-open()函數緊接着調用tty_struct 結構中的tty_operations指針所指向的結構里的uart_open() 函數——>該函數接着調用uart_satarup()函數——>該函數會調用uart_port 結構中的ops 指針所指向是操作函數集合中文件操作函數。

 

打開步驟:

使能串口接收功能——>為數據的接收注冊中斷處理程序——>使能發送功能——>為發送數據注冊中斷處理程序

 

 

 

串口驅動之發送數據:

用戶通過使用write()函數實現發送數據

write()函數調用cdev結構中的file_operations 指針所指向的結構里tty_write()函數——>該函數會調用tty_ldisc_ops 結構中的write指針所指向的函數n_tty_write()函數——>該函數會調用uart_tty 結構中的指針write所指向的函數uart_write()函數——>該函數會調用函數uart_startup()函數——>該函數會調用uart_port結構中的ops指針所指的操作函數集合中的文件發送函數

 

 

注意:當使能中斷后,驅動程序發現FIFO 中數據量小於某一個值就會觸發中斷,在中斷處理程序中實現相應的數據發送

 

重要概念——循環緩沖

在使用函數write()的時候,會經過tty核的處理,在處理的過程中,會將要發現的數據放到一個地方,這個地方就叫做循環緩沖

 

注意:FIFO 中的數據是先入先出。對於循環緩沖中的數據,在沒有數據的時候 ,tail head 是在緩沖數據的同一個位置,當往其中裝入數據的時候,head 跟隨寫入數據的變化而變化,但是發送數據的起始位置是在tail  

 

數據發送過程:

(1)判斷是否有需要發送的x_char,如果用,通過UTXH寄存器發送

注意:x_char用於表示接收數據端的狀態用,是否滿足接收數據的條件

(2)判斷循環緩沖或串口狀態不允許發送數據則需要停止發送數據

(3)使用while()循環來發數據,發送規則<1>循環換緩沖有數據<2>發送的數據量不到256

注意:<1>當發送FIFO中數據為滿時停止發送

      <2>從循環緩沖中的尾部(tail)中取數據,並將數據送入到UTXH寄存中

      <3>使用自加運算來調整循環緩沖中的數據的位置

(4)如果循環緩沖中的數據量低於256 則喚醒在發送時阻塞的進程(在之前有進程需要王循環緩沖中發送數據,但是發現循環緩沖中的沒有足夠的空間進行來存放要發送的數據,則進程進入休眠狀態)

(5)如果循環緩沖中數據為空則需要關閉中斷,否則將不斷產生中斷影響系統的穩定運行

 

串口驅動程序之數據接收

應用程序要接收數據肯定要使用read() 函數

流程:

用戶程序調用函數read()——>read() 函數會調用結構file_operationsread指針所指向的tty_read()函數——>該函數會調用結構tty_ldisc_tty結構中的指針read所指向的n_tty_read()函數——>

 

 

n_tty_read()函數分析:

(1)將應用程序執行進程狀態設置成(TASK_INTERRUPTIBLE

注意:設置成該狀態但是程序並不會直接進入阻塞態,需要在進程調度中才會進入相應的狀態

(2)如果沒有數據可讀,這通過 調度程序實現將進程進入阻塞態度

(3)如果readbuf中有數據則從中讀取數據

 

驅動程序處理流程:

(1)讀取UFCON寄存器

(2)讀取UFSTAT寄存器

(3)UFSTAT中的fifocnt 的大小為零退出處理

(4)UFSTAT 中的UERSAT判斷錯誤類型

(5)URXH中取出接收到的數據

(6)進行流控處理

(7)根據UERSTAT的值記錄具體的錯誤類型

(8)如果接受到的是sysrq字符,則調用特殊處理函數-uart_handle_sysrq_chgr()

(9)把字符送入串口驅動的buffer,調用函數uart_insert_char

(10)將串口驅動buffer中的數據送進線路規程的read_buf ,調用函數tty_flip_buffer_push

 

重要概念——流控:

當發送數據給接收方,接收方的數據緩沖區里面沒有空余空間可以使用的時候就需要通知發送方停止發送數據,否則發送方的數據就不能被接收到。

流控方式:

軟件流控:接收端通過串行線向發送段發送數據x_off

 

硬件流控:通過使用硬件聯系連接起來,當不能接收數據的時候就會使  相應連線產生高電平,發送端每發送一次說都會檢測該引腳

<wiz_tmp_tag id="wiz-table-range-border" contenteditable="false" style="display: none;">


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM