linux设备模型之uart驱动架构分析.docx
《linux设备模型之uart驱动架构分析.docx》由会员分享,可在线阅读,更多相关《linux设备模型之uart驱动架构分析.docx(19页珍藏版)》请在冰豆网上搜索。
![linux设备模型之uart驱动架构分析.docx](https://file1.bdocx.com/fileroot1/2023-2/2/ddb8d443-2586-426e-acb6-1f45e56be55b/ddb8d443-2586-426e-acb6-1f45e56be55b1.gif)
linux设备模型之uart驱动架构分析
一:
前言
接着前面的终端控制台分析,接下来分析serial的驱动.在linux中,serial也对应着终端,通常被称为串口终端.在shell上,我们看到的/dev/ttyS*就是串口终端所对应的设备节点.
在分析具体的serial驱动之前.有必要先分析uart驱动架构.uart是UniversalAsynchronousReceiverandTransmitter的缩写.翻译成中文即为”通用异步收发器”.它是串口设备驱动的封装层.
二:
uart驱动架构概貌
如下图所示:
上图中红色部份标识即为uart部份的操作.
从上图可以看到,uart设备是继tty_driver的又一层封装.实际上uart_driver就是对应tty_driver.在它的操作函数中,将操作转入uart_port.
在写操作的时候,先将数据放入一个叫做circ_buf的环形缓存区.然后uart_port从缓存区中取数据,将其写入到串口设备中.
当uart_port从serial设备接收到数据时,会将设备放入对应linediscipline的缓存区中.
这样.用户在编写串口驱动的时候,只先要注册一个uart_driver.它的主要作用是定义设备节点号.然后将对设备的各项操作封装在uart_port.驱动工程师没必要关心上层的流程,只需按硬件规范将uart_port中的接口函数完成就可以了.
三:
uart驱动中重要的数据结构及其关联
我们可以自己考虑下,基于上面的架构代码应该要怎么写.首先考虑以下几点:
1:
一个uart_driver通常会注册一段设备号.即在用户空间会看到uart_driver对应有多个设备节点.例如:
/dev/ttyS0/dev/ttyS1
每个设备节点是对应一个具体硬件的,从上面的架构来看,每个设备文件应该对应一个uart_port.
也就是说:
uart_device怎么同多个uart_port关系起来?
怎么去区分操作的是哪一个设备文件?
2:
每个uart_port对应一个circ_buf,所以uart_port必须要和这个缓存区关系起来
回忆tty驱动架构中.tty_driver有一个叫成员指向一个数组,即tty->ttys.每个设备文件对应设数组中的一项.而这个数组所代码的数据结构为tty_struct.相应的tty_struct会将tty_driver和ldisc关联起来.
那在uart驱动中,是否也可用相同的方式来处理呢?
将uart驱动常用的数据结构表示如下:
结合上面提出的疑问.可以很清楚的看懂这些结构的设计.
四:
uart_driver的注册操作
Uart_driver注册对应的函数为:
uart_register_driver()代码如下:
intuart_register_driver(structuart_driver*drv)
{
structtty_driver*normal=NULL;
inti,retval;
BUG_ON(drv->state);
/*
*Maybeweshouldbeusingaslabcacheforthis,especiallyif
*wehavealargenumberofportstohandle.
*/
drv->state=kzalloc(sizeof(structuart_state)*drv->nr,GFP_KERNEL);
retval=-ENOMEM;
if(!
drv->state)
gotoout;
normal=alloc_tty_driver(drv->nr);
if(!
normal)
gotoout;
drv->tty_driver=normal;
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);
/*
*InitialisetheUARTstate(s).
*/
for(i=0;inr;i){
structuart_state*state=drv->statei;
state->close_delay=500;/*.5seconds*/
state->closing_wait=30000;/*30seconds*/
mutex_init(&state->mutex);
}
retval=tty_register_driver(normal);
out:
if(retval
put_tty_driver(normal);
kfree(drv->state);
}
returnretval;
}
从上面代码可以看出.uart_driver中很多数据结构其实就是tty_driver中的.将数据转换为tty_driver之后,注册tty_driver.然后初始化uart_driver->state的存储空间.
这样,就会注册uart_driver->nr个设备节点.主设备号为uart_driver->major.开始的次设备号为uart_driver->minor.
值得注意的是.在这里将tty_driver的操作集统一设为了uart_ops.其次,在tty_driver->driver_state保存了这个uart_driver.这样做是为了在用户空间对设备文件的操作时,很容易转到对应的uart_driver.
另外:
tty_driver的flags成员值为:
TTY_DRIVER_REAL_RAW|TTY_DRIVER_DYNAMIC_DEV.里面包含有TTY_DRIVER_DYNAMIC_DEV标志.结合之前对tty的分析.如果包含有这个标志,是不会在初始化的时候去注册device.也就是说在/dev/下没有动态生成结点(如果是/dev下静态创建了这个结点就另当别论了^_^).
流程图如下:
五:
uart_add_one_port()操作
在前面提到.在对uart设备文件过程中.会将操作转换到对应的port上,这个port跟uart_driver是怎么关联起来的呢?
这就是uart_add_ont_port()的主要工作了.
顾名思义,这个函数是在uart_driver增加一个port.代码如下:
intuart_add_one_port(structuart_driver*drv,structuart_port*port)
{
structuart_state*state;
intret=0;
structdevice*tty_dev;
BUG_ON(in_interrupt());
if(port->line>=drv->nr)
return-EINVAL;
state=drv->stateport->line;
mutex_lock(&port_mutex);
mutex_lock(&state->mutex);
if(state->port){
ret=-EINVAL;
gotoout;
}
state->port=port;
state->pm_state=-1;
port->cons=drv->cons;
port->info=state->info;
/*
*Ifthisportisaconsole,thenthespinlockisalready
*initialised.
*/
if(!
(uart_console(port)&&(port->cons->flags&CON_ENABLED))){
spin_lock_init(&port->lock);
lockdep_set_class(&port->lock,&port_lock_key);
}
uart_configure_port(drv,state,port);
/*
*Registertheportwhetherit'sdetectedornot.Thisallows
*setserialtobeusedtoalterthisportsparameters.
*/
tty_dev=tty_register_device(drv->tty_driver,port->line,port->dev);
if(likely(!
IS_ERR(tty_dev))){
device_can_wakeup(tty_dev)=1;
device_set_wakeup_enable(tty_dev,0);
}else
printk(KERN_ERR"Cannotregisterttydeviceonline%d\n",
port->line);
/*
*EnsureUPF_DEADisnotset.
*/
port->flags&=~UPF_DEAD;
out:
mutex_unlock(&state->mutex);
mutex_unlock(&port_mutex);
returnret;
}
首先这个函数不能在中断环境中使用.Uart_port->line就是对uart设备文件序号.它对应的也就是uart_driver->state数组中的uart_port->line项.
它主要初始化对应uart_driver->state项.接着调用uart_configure_port()进行port的自动配置.然后注册tty_device.如果用户空间运行了udev或者已经配置好了hotplug.就会在/dev下自动生成设备文件了.
操作流程图如下所示:
六:
设备节点的open操作
在用户空间执行open操作的时候,就会执行uart_ops->open.Uart_ops的定义如下:
staticconststructtty_operationsuart_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,
.stop=uart_stop,
.start=uart_start,
.hangup=uart_hangup,
.break_ctl=uart_break_ctl,
.wait_until_sent=uart_wait_until_sent,
#ifdefCONFIG_PROC_FS
.read_proc=uart_read_proc,
#endif
.tiocmget=uart_tiocmget,
.tiocmset=uart_tiocmset,
};
对应open的操作接口为uart_open.代码如下:
staticintuart_open(structtty_struct*tty,structfile*filp)
{
structuart_driver*drv=(structuart_driver*)tty->driver->driver_state;
structuart_state*state;
intretval,line=tty->index;
BUG_ON(!
kernel_locked());
pr_debug("uart_open(%d)called\n",line);
/*
*tty->driver->numwon'tchange,sowewon'tfailherewith
*tty->driver_datasettosomethingnon-NULL(andtherefore
*wewon'tgetcaughtbyuart_close()).
*/
retval=-ENODEV;
if(line>=tty->driver->num)
gotofail;
/*
*Wetakethesemaphoreinsideuart_gettoguaranteethatwewon't
*bere-enteredwhileallocatingtheinfostructure,orwhilewe
*requestanyIRQsthatthedrivermayneed.Thisalsohasthenice
*side-effectthatitdelaystheactionofuart_hangup,sowecan
*guaranteethatinfo->ttywillalwayscontainsomethingreasonable.
*/
state=uart_get(drv,line);
if(IS_ERR(state)){
retval=PTR_ERR(state);
gotofail;
}
/*
*Oncewesettty->driver_datahere,weareguaranteedthat
*uart_close()willdecrementthedrivermoduleusecount.
*Anyfailuresfromhereonwardsshouldnottouchthecount.
*/
tty->driver_data=state;
tty->low_latency=(state->port->flags&UPF_LOW_LATENCY)?
1:
0;
tty->alt_speed=0;
state->info->tty=tty;
/*
*Iftheportisinthemiddleofclosing,bailoutnow.
*/
if(tty_hung_up_p(filp)){
retval=-EAGAIN;
state->count--;
mutex_unlock(&state->mutex);
gotofail;
}
/*
*MakesurethedeviceisinD0state.
*/
if(state->count==1)
uart_change_pm(state,0);
/*
*Startuptheserialport.
*/
retval=uart_startup(state,0);
/*
*Ifwesucceeded,waituntiltheportisready.
*/
if(retval==0)
retval=uart_block_til_ready(filp,state);
mutex_unlock(&state->mutex);
/*
*Ifthisisthefirstopentosucceed,adjustthingstosuit.
*/
if(retval==0&&!
(state->info->flags&UIF_NORMAL_ACTIVE)){
state->info->flags|=UIF_NORMAL_ACTIVE;
uart_update_termios(state);
}
fail:
returnretval;
}
在这里函数里,继续完成操作的设备文件所对应state初始化.现在用户空间open这个设备了.即要对这个文件进行操作了.那uart_port也要开始工作了.即调用uart_startup()使其进入工作状态.当然,也需要初始化uart_port所对应的环形缓冲区circ_buf.即state->info->xmit.
特别要注意,在这里将tty->driver_data=state;这是因为以后的操作只有port相关了,不需要去了解uart_driver的相关信息.
跟踪看一下里面调用的两个重要的子函数.uart_get()和uart_startup().先分析uart_get().代码如下:
staticstructuart_state*uart_get(structuart_driver*drv,intline)
{
structuart_state*state;
intret=0;
state=drv->stateline;
if(mutex_lock_interruptible(&state->mutex)){
ret=-ERESTARTSYS;
gotoerr;
}
state->count;
if(!
state->port||state->port->flags&UPF_DEAD){
ret=-ENXIO;
gotoerr_unlock;
}
if(!
state->info){
state->info=kzalloc(sizeof(structuart_info),GFP_KERNEL);
if(state->info){
init_waitqueue_head(&state->info->open_wait);
init_waitqueue_head(&state->info->delta_msr_wait);
/*
*Linktheinfointotheotherstructures.
*/
state->port->info=state->info;
tasklet_init(&state->info->tlet,uart_tasklet_action,
(unsignedlong)state);
}else{
ret=-ENOMEM;
gotoerr_unlock;
}
}
returnstate;
err_unlock:
state->count--;
mutex_unlock(&state->mutex);
err:
returnERR_PTR(ret);
}
从代码中可以看出.这里注要是操作是初始化state->info.注意port->info就是state->info的一个副本.即port直接通过port->info可以找到它要操作的缓存区.
uart_startup()代码如下:
staticintuart_startup(structuart_state*state,intinit_hw)
{
structuart_info*info=state->info;
structuart_port*port=state->port;
unsignedlongpage;
intretval=0;
if(info->flags&UIF_INITIALIZED)
return0;
/*
*SettheTTYIOerrormarker-wewillonlyclearthis
*oncewehavesuccessfullyopenedtheport.Alsoset
*upthetty->alt_speedkludge
*/
set_bit(TTY_IO_ERROR,&info->tty->flags);
if(port->type==PORT_UNKNOWN)
return0;
/*
*Initialiseandallocatethetransmitandtemporary
*buffer.
*/
if(!
info->xmit.buf){
page=get_zeroed_page(GFP_KERNEL);
if(!
page)
return-ENOMEM;
info->xmit.buf=(unsignedchar*)page;
uart_circ_clear(&info->xmit);
}
retval=port->ops->startup(port);
if(retval==0){
if(init_hw){
/*
*Initialisethehardwareportsettings.
*/
uart_change_speed(state,NULL);
/*
*SetuptheRTSandDTRsignalsoncethe
*portisopenandreadytorespond.
*/
if(info->tty->termios->c_cflag&CBAUD)
uart_set_mctrl(port,TIOCM_RTS|TIOCM_DTR);
}
if(info->flags&UIF_CTS_FLOW){
spin_lock_irq(&port->lock);
if(!
(port->ops->get_mctrl(port)&TIOCM_CTS))
info->tty->hw_stopped=1;
spin_unl