uart驱动linux
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