當前位置:首頁 » 操作系統 » uart驅動linux

uart驅動linux

發布時間: 2022-04-25 18:49:52

1. 求教,linux下網口虛擬串口驅動程序

開發虛擬串口驅動程序

虛擬串口就是當本地並沒有對應的串口硬體設備,而為應用層提供串口設備一樣的系統調用介面,以兼容原本使用本地串口的應用軟體的「虛」設備。本文作者給出了一種在Windows平台上實現虛擬串口的方法,由此實現的「串口」具有真實串口完全相同的系統調用介面。
在很多應用中需要用到虛擬串口,如在Modem卡出現之前,已經有了接在計算機串口上的外部Modem,而且各種拔號程序也是通過串口與外部Modem通信的。為了讓已有的拔號程序不做修改,像使用外部Modem一樣使用內置卡,就需要內置卡的驅動程序虛擬一個串口設備。又如當前工業界使用的一些串口伺服器,往往有8個或16個甚至更多的串口,以連接多個串口設備,再通過一個網卡直接連入乙太網。與它在同一網路上的計算機就通過乙太網與串口伺服器上掛接的串口設備通信。為了讓計算機中原來使用本地串口的軟體兼容,就需要在計算機上提供虛擬串口驅動。
虛擬串口的設計關鍵在於,該「串口」實現後必須具有與真實串口完全相同的系統調用介面。要做到這點,從已有的串口設備驅動程序上做修改是最佳捷徑。下文就介紹以Windows NT上的串口驅動程序為基礎,開發可運行於Windows NT、Windows 2000、Windows XP的各個版本虛擬串口驅動程序。
串口驅動中使用的幾個鏈表
由於串口是雙工設備,在一個讀請求發出來還沒有完成之前,同時可以發出寫請求,加上在驅動程序層所有I/O請求都要求非同步完成,即前一個請求尚沒有完成,下一個相同的請求可能又來了。為此,串口驅動程序需要使用多個雙向鏈表數據結構來處理各種IRP(I/O Request Packet,I/O請求包)。當收到一個IRP,先判斷是否可立即完成,可以馬上處理並返回,如果不允許則將IRP插在相應鏈表尾,在適當的時候如設備有空閑時處理,這時往往會產生一個硬體中斷,激發DPC(Deferred Procere Call,暫緩過程調用)過程,由DPC處理函數逐個從鏈表頭取出IRP並試著完成它。串口驅動中有以下幾個鏈表和DPC(在serial.h中有定義):
ReadQueue 和 CompleteReadDpc
用於保存Read IRP的鏈表和用於調度的DPC,與DPC對應的處理函數是SerialCompleteRead,它在read.c文件中,該函數的主要任務就是從ReadQueue中提取下一個IRP,並試著完成它。
WriteQueue 和 CompleteWriteDpc
用於保存Write IRP的鏈表和對應的DPC,與DPC對應的函數是SeriaCompleteWrite,它的實現在write.c中,該函數負責從WriteQueue中提取IRP,並試著完成它。
MaskQueue 和 CommWaitDpc
這一對鏈表用於處理Windows串口驅動的一個特性:事件驅動機制。它允許應用程序預設一個事件標志,而後等待與標志對應事件發生。DPC所調用的函數是SerialCompleteWait,它實現在Waitmask.c文件中,該函數也是試著從MaskQueue中提取IRP並完成它。
PurgeQueue
該鏈表與前面幾個稍有不同,它沒有與之相對應的DPC機制,而是在每次收到Purge請求時從PurgeQueue中逐個提取IRP並試著完成,因某種原因不能完成時則插入鏈表。相應的函數是purge.c文件中的SerialStartPurge。
以上機制是串口驅動程序的重要實現方法,在虛擬串口驅動中需要保留,但不同的是,硬體串口驅動中是ISR(中斷服務程序)根據收、發或MODEM中斷來激發相應的DPC,而在虛擬串口驅動中將因實際情況不同會有不同的激發機制。
DriverEntry的實現
DriverEntry是驅動程序的入口函數,相當於應用程序C語言中的main函數,開發一個虛擬串口驅動首先要修改的就是它。它的函數實體在initunlo.c文件中。只是在虛擬串口驅動中由於不與具體的硬體打交道,就不存在硬體資源分析、硬體初始化、判斷其工作狀態等處理,只需要為虛擬串建立設備對象、符號鏈接和初始化數據結構。一個典型函數實現大體如下:
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
/*填寫DriverObject->MajorFunction[]數組*/
/*建立設備對象*/
/*初始化SERIAL_DEVCIE_EXETENSION數據結構*/
Status = IoCreateDevice(DriverObject, sizeof(SERIAL_DEVICE_EXTENSION), &uniNameString, FILE_DEVICE_SERIAL_PORT, 0,TRUE,&deviceObject);
//初始化所有鏈表
InitializeListHead(&extension->ReadQueue);
InitializeListHead(…);
…;
//初始化所有DPC
KeInitializeDpc(&extension->CompleteReadDpc,SerailCompleteRead,extension);
KeInitializeDpc(…);
/*建立符號鏈接*/
SerialSetupExternalNaming(extension);
return Status;
}
SerialRead和SerialCompleteRead的實現
函數SerailRead和SerialCompleteRead決定了對Read IRP的響應策略,它們都存於read.c中。以串口伺服器要用的虛擬串口為例,當串口伺服器收到來自外部數據時將通過網路發至計算機,計算機則產生相應的網路中斷並進行協議數據處理。網路接收線程緩存新收到的數據並激活CompleteReadDpc,從而SerialCompleteReadIrp得到調用,它再調用CompleteReadIrp對每個IRP進行處理。它們的實現大體如下:
NTSTATUS SerialRead(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp)
{
/*此處略去變數聲明和初始化*/
/*提取IRP中相關的數據*/
stack = IoGetCurrentIrpStackLocation(Irp);
ReadLen = stack->Parameters.Read.Length;
/*先看本地緩沖有數據否?有的話先讀取*/
if(Extension->InCounter > 0 )
{ //注意這里要加鎖,以防數據訪問沖突
KeAcquireSpinLock(&Extension->
ReadBufferLock,&lIrql);
FirstRead = (ReadLen>Extension->
InCounter)? Extension->InCounter: ReadLen;
RtlCopyMemory(Irp->AssociatedIrp.
SystemBuffer,Extension->pInBuffer,FirstRead);
Extension->InCounter -= FirstRead;
ReadLen -= FirstRead;
KeReleaseSpinLock(&Extension->
ReadBufferLock,lIrql);//釋放鎖
}
/*是否已讀到足夠數據?是的話則完成該IRP*/
if( 0 == ReadLen)
{
status=STATUS_SUCCESS;
Irp->IoStatus.Status = status;
Irp->IoStatus.Information = FirstRead;
IoCompleteRequest(Irp,0);
return status;
}
/*沒有則將IRP插入隊列中,通過網路向串口伺服器發出讀數據請求*/
IoMarkIrpPending(Irp);
InsertWaitList(Extension->ReadQueue,Irp);
status = TdiSendAsync(Extension->ComChannel,pAckPacket,PacketLen(pAckPacket),(PVOID)ReadAckComplete,Irp);
/*返回PENDING,表示該IRP尚沒有完成*/
return STATUS_PENDING;
}
Void CompleteReadIrp(IN PSERIAL_DEVICE_EXTENSION extension,IN PIRP Irp,IN PUCHAR pInData,IN ULONG Length )
{
/*此處略去變數聲明和初始化*/
/*讀取新數據*/
ReadLen = (ReadLen > Length)? Length : ReadLen;
if(ReadLen != 0)
{
RtlCopyMemory(pReadAsync->
pReadBuffer,pInData,ReadLen);
pReadAsync->pReadBuffer += ReadLen;
pReadAsync->ReadAlready += ReadLen;
extension->PerfStats.ReceivedCount +=
ReadLen;
}
else
{
/*因為串口伺服器端只有在已經有了相應的數據或超過時間(此時,Length=0)才會發來應答並激活本DPC過程,所以此時已經超時,為了便於結束本IRP,這里有意改變TotalNeedRead,造成接收完畢的假象*/
pReadAsync->TotalNeedRead =
pReadAsync->ReadAlready;
}
if(pReadAsync->TotalNeedRead == pReadAsync->ReadAlready)
{
/*該IRP是否已經接收完畢,是的話則結束該
IRP*/
EndReadIrp(Irp);
/*從ReadQueue中取下一個IRP*/
}
/*本IRP沒有完成也沒有超時,則繼續等待本DPC下次被激活,注意此時要判斷IRP是否被要求取消*/
}
SerialWrite和SerailCompleteWrite的實現
SerialWrite和SerailCompleteWrite決定了Write IRP的實現。在SerialWrite中調用了網路發送函數TdiSendAsync,當該發送完成後將激活CompleteWriteDpc,調度SerialCompleteWrite函數,而它主要就是取出當前的WriteIRP,設置已經發送的數據數量,調用CompleteWriteIrp做該IRP的進一步處理。它們大體如下:
NTSTATUS SerialWrite(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp)
{
/*此處略去變數聲明和初始化*/
/*從IRP中提取有關數據*/
stack=IoGetCurrentIrpStackLocation(Irp);
SendLen = stack->Parameters.Write.Length;
/*為網路發送和非同步操作分配緩沖,在CompleteWrite中全部數據發送完後釋放*/
pWriteAsync = ExAllocatePool(NonPagedPool,
SendLen+PACKET_HEADER_LEN+sizeof(WRITE_ASYNC));
if(pWriteAsync == NULL)
{
//錯誤處理
}
//保存非同步數據

//設置網路發送數據包
BuildDataPacket(pPacket,WRITE,(USHORT)SendLen,pWriteAsync->pWriteBuffer);
/*先將IRP暫時阻塞並插入隊列,在CompleteWrite中完成*/
IoMarkIrpPending(Irp);
InsertWaitList(extension->WriteQueue, Irp);
/*將寫請求和相關數據通過網路發向串口伺服器,由它負責將數據傳到具體串口設備*/
status = TdiSendAsync(Extension->ComChannel,pPacket,PacketLen(pPacket),(PVOID)CompleteWriteIrp,Irp);
//統計數據累加
Extension->PerfStats.TransmittedCount += SendLen;
return STATUS_PENDING;
}

NTSTATUS CompleteWriteIrp(IN PDEVICE_OBJECT deviceobject,IN PIRP pIrp,IN PVOID context)
{
/*此處略去變數聲明和初始化*/
SendLen=pWriteAsync->TotalNeedWrite - pWriteAsync->WroteAlready;
if(SendLen == 0)//全部數據發送完畢
{
EndWaitWriteIrp(pWriteIrp,STATUS_SUCCESS,
pWriteAsync->WroteAlready,pWriteAsync);
//從WriteQueue中取下一個IRP;
}
else //發送剩餘數據
{
if(pWriteIrp->Cancel)
{
//IRP被要求取消,完成WriteIrp
EndWaitWriteIrp(pWriteIrp,STATUS_CANCELLED,
pWriteAsync->WroteAlready,pWriteAsync);
return STATUS_CANCELED;
}
else
{
//再次設置網路數據包並發送
BuildDataPacket(…);
status = TdiSendAsync(…);
//統計數據累加
Extension->PerfStats.TransmittedCount +=
SendLen;
return STATUS_MORE_PROCESSING_REQUIRED;
}
}
}
其他幾個介面函數的實現
除Read/Write外,SerialUnload、SerialCreateOpen、 SerialClose、SerialCleanup、SerailFlush等調用介面是硬體相關性比較弱的介面函數,基本不要修改,直接刪除原來操作硬體的部分即可。復雜一點就是SerialIoControl,該介面函數包含有大量設置、讀取串口硬體狀態的處理,可建立一個本地數據結構隨時保存虛擬串口的當前硬體狀態。同時為了保證串口伺服器端的真實串口狀態和上層軟體要求的一致,需要將所有設置請求通過網路發送到伺服器端,由它負責改變真實硬體的狀態。

2. 如何移植USB驅動到Linux系統中

Linux的UART驅動建立在TTY驅動程序之上,程序源代碼主要在</driver/tty/serial/>目錄下。如果從TTY開始對UART驅動進行分析將會很復雜,而實現一個晶元的UART驅動,無需接觸TTY,所以建議不涉及TTY驅動,而僅限定在Serial子系統中。具體參考ZLG《嵌入式Linux開發教程(下冊)》第8章。

3. linux 串口驅動程序術語介紹

在Linux中經常碰到「控制台」、「終端」、「console」、「tty」、「terminal」等術語,也經常使用到這些設備文件:ldevconsole、/dev/ttySACO、/dev/tty0等。要理解這些術語,需要從以前的計算機說起。
最初的計算機價格昂貴,一台計算機通常連接上多套鍵盤和顯示器供多人使用。在以前專門有這種可以連上一台電腦的設備,它只有顯示器和鍵盤,外加簡單的處理電路,本身不具有處理計算機信息的能力。用戶通過它連接到計算機上(通常是通過串口),然後登錄系統,並對計算機進行操作。這樣一台只有輸入、顯示部件(比如鍵盤和顯示器)並能夠連接到計算機的設備就叫做終端。tty 是Teletype 的縮寫,Teletype是最早出現的一種終端設備,很像電傳打字機。在Linux中,就用tty來表示「終端」,比如內核文件tty_io.c、tty _ioctl.c等都是與「終端」相關的驅動程序;設備文件/dev/ttySACO、/dev/tty0等也表示某類終端設備。「console」的意思即為「控制台」,顧名思義,控制台就是用戶與系統進行交互的設備,這和終端的作用相似。實際上,控制台與終端相比,也只是多了一項功能:它可以顯示系統信息,比如內核消息、後台服務消息。從硬體上看,控制台與終端都是具備輸入、顯示功能的設備,沒有區別。「控制台」、「終端」、「控制終端」這些名詞經常混著用,表示的是同一個意思。
控制台與終端的區別體現在軟體.上,Linux內核從很早以前發展而來,代碼中仍保留了「控制台」、「終端」的概念。啟動Linux內核前傳入的命令行參數「console=…」就是用來指定「控制台」的。控制台在tty 驅動初始化之前就可以使用了,它最開始的時候被用來顯示內核消息(比如 printk 函數輸出的消息)。

4. linux 內核 uart driver 只有fifo滿才向用戶buf傳遞數據嗎

對於串口驅動的移植准備自己分析一下源代碼的,但是發現自己好多地方都只知道一 些皮毛,不明白其中的道理,所以我上網搜的時候發現有好多人寫了很多很好的文章了,下面我轉載的這篇就非常不錯,一個困惱我好久的問題是驅動代碼中只是注 冊了platform驅動,而platform設備注冊在哪裡?這個問題困惱我好久,源代碼中一直沒找到,下面文章就解決了這個問題。當然文章中詳細了講 述了很多細節的知識。
原文地址 http://chxxxyg.blog.163.com/blog/static/150281193201082044140894/

(1)串口移植
S3C2440共有3個串口,在SMDK2440平台上串口0和串口1都作為普通串口使用,串口2工作在紅外收發模式。TQ2440開發板將它們都作為普通串口,目前所需要的只有串口0,作為控制終端,所以此處不作修改。
在文件 linux/arch/arm/plat-s3c24xx/devs.c中定義了三個串口的硬體資源。
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);
………………………………
}
串口驅動其實是一個典型的tty驅動
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驅動注冊到內核
………………………………
}
其實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 = _from_read_buf(tty, &b, &nr);
uncopied += _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
schele_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 中。

5. linux2.4內核的UARTLite驅動

兩著歉容的 你可以自己編譯內核的基本的驅動自己可以配置進去

6. linux 怎麼禁用uart驅動

驅動軟體你可以去官網下載安裝,如果覺得麻煩,你可以嘗試使用第三方驅動管理軟體,像驅動人生就可以。

7. 手機linux驅動的流程是什麼主要想在手機驅動開發方面發展,請問我下一步怎麼學習

我現在是從事linux驅動這塊的,現跟你說說在公司我的主要工作內容吧。
驅動開發,就是去驅動一個設備,比如公司的一個產品,一個手機,公司老大說這個屏顯示不好,要換一個,找屏的廠家,屏提供硬體,軟體,這個軟體就是所謂的驅動代碼,當然這個在你的產品上是跑不起來的,你需要根據你的產品來修改驅動代碼,其實驅動開發就是大部分工作就是驅動移植,當然關鍵是你要知道怎麼移植,這個是重點。通常驅動代碼的分類:字元、塊、網路,這些分類是從軟體上來分類的,實際上應用最多的是硬體介面的分類:i2c 驅動,spi驅動,uart驅動,內核中有對應的子系統:比如說i2c系統。
想快速出去工作,我個你提供一條路,看i2c設備驅動:i2c協議(這個當面試的時候肯定會問你的,如果你說你會i2c驅動)、linux系統的i2c子系統,市面上很多設備都是i2c的,而且這部分設備也經常改動,因為硬體電路簡單。

8. LINUX怎麼添加USB串口驅動

Linux發行版自帶usb
to
serial驅動,以模塊方式編譯驅動,在內核源代碼目錄下運行Make
MenuConfig選擇Devces
drivers-->USB
seupport-->
USB
Serial
Converter
support
--

9. linux下usb轉串口的驅動怎麼裝啊

Linux發行版自帶usb
to
serial驅動,以模塊方式編譯驅動,在內核源代碼目錄下運行Make
MenuConfig選擇Devces
drivers-->USB
seupport-->
<M>USB
Serial
Converter
support
-->
<M>
USB
driver
for
GSM
and
CDMA
modems
&
[*]USB
Generic
Serial
Driver,保存退出。運行make
moles,編譯成功後可找到usbtoserial.ko及option.ko兩個驅動(2.6以上內核版本模塊驅動用.ko表示)。

10. linux下的UART

1.通用串列數據匯流排,用於非同步傳輸,就是電腦串口之類的
2.硬體層一樣,驅動層架構不同,因為操作系統給的介面不同
3.除了必須的模塊載入卸載函數外,還有read函數,write函數,ioctl函數,最重要的就是這幾個,以方便你以文件方式調用文件讀寫API

熱點內容
易語言網路驗證源碼 發布:2024-10-03 14:24:53 瀏覽:365
平板電腦安卓444很卡怎麼辦 發布:2024-10-03 14:20:31 瀏覽:604
如何查安卓app最初發布時間 發布:2024-10-03 14:20:31 瀏覽:562
安卓如何進文件夾 發布:2024-10-03 14:19:55 瀏覽:801
c語言年份 發布:2024-10-03 13:42:03 瀏覽:569
電視尺寸演算法 發布:2024-10-03 13:30:58 瀏覽:65
內網自己搭建伺服器 發布:2024-10-03 13:13:31 瀏覽:669
雲存儲看不清 發布:2024-10-03 13:06:20 瀏覽:220
hld編程 發布:2024-10-03 13:03:18 瀏覽:179
android自定義drawable 發布:2024-10-03 13:03:08 瀏覽:640