Linux终端控制台体系及串口驱动分析.docx
《Linux终端控制台体系及串口驱动分析.docx》由会员分享,可在线阅读,更多相关《Linux终端控制台体系及串口驱动分析.docx(22页珍藏版)》请在冰豆网上搜索。
Linux终端控制台体系及串口驱动分析
Linux终端控制台体系及串口驱动分析
数据通信的基本方式可分为并行通信与串行通信两种:
并行通信:
利用多条数据线路将数据的各位同时传送。
它的特点是传输速度快,适用于短距离通信。
串行通信:
利用一条数据线将数据一位位顺序传送。
特点是通信线路简单,利用简单的线缆就可实现通信,低成本,适用于远距离通信。
异步通信以一个字符为传输单位,通信中的两个字符间的时间间隔是不固定的,然而同一个字符中的两个相邻位之间的时间间隔是固定的。
通信协议:
是指双方约定的一些规则。
在使用异步串口传送一个字符信息时,
对数据格式有如下规定:
规定有空闲位、起始位、资料位、奇偶校验位、停止位。
起始位:
先发一个逻辑“0”信号,表示传输字符的开始
数据位:
紧接在起始位之后。
数据位的个数可以是4、5、6、7、8,从最低位开始传送,靠时钟定位。
奇偶校验位:
数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以此校验数据传送的正确性。
停止位:
它是一个字符数据的结束标志。
空闲位:
处于逻辑“1”状态,表示当前线路上没有数据传送。
波特率:
是衡量数据传送速率的指针。
表述每秒钟传送的二进制位数。
注:
异步通信是按字符传输的,接收设备在收到起始信号之后在一个字符的传输时间内能和发送设备保持同步就能正确接收。
传送方式:
单工方式、半双工方式、全双工方式
终端概述:
在Linux中,TTY(终端)是一类字符设备的统称,它包括了3种类型:
控制台、串口和伪终端。
控制台:
供内核使用的终端为控制台。
控制台在Linux启动时,通过命令console=…指定,如果没有指定控制台,系统把第一个注册的终端(tty)作为控制台。
1、控制台是一个虚拟的终端,它必须映射到真正的终端上。
2、控制台可以简单的理解为printk输出的地方
3、控制台是个只输出的设备,功能很简单,只能在内核中访问。
伪终端:
伪终端设备是一种特殊的终端设备,由主-从两个成对的设备构成,当打开主设备时,对应的从设备随之打开,形成连接状态。
输入到主设备的数据成为从设备的输出,输入到从设备的数据成为主设备的输出,形成双向管道。
伪终端设备通常用于远程登录服务器来建立网络和终端的关联。
当通过telnet远程登录到另一台主机时,telnet进程与远程主机的telnet服务器相连接。
telnet服务器使用某个主设备并通过对应的从设备与telnet进程相互通信。
终端体系:
从上往下:
TTYcore—>TTYlinediscipline(可以没有)—>ttydriver
在Linux中,TTY体系分为:
TTY核心、TTY线路规程、TTY驱动3部分。
TTY核心从用户获取要发送给TTY设备的数据,然后把数据传送给TTY线路规程(协议),它对数据进行处理后,负责把数据传递到TTY驱动程序,TTY驱动程序负责格式化数据,并通过硬件发送出去。
从硬件收到的数据向上通过TTY驱动,进入TTY线路规程,再进入TTY核心,最后被用户获取。
TTY驱动可以直接和TTY核心通讯,但通常TTY线路规程会修改在两者之间的传送的数据。
TTY驱动不能直接和线路规程通信,甚至不知道它的存在,线路规程的工作是格式化从用户或者硬件收到的数据。
这种格式常常实现为一个协议,如PPP或Bluetooth
终端体系—串口(★)
往串口发送数据时的数据流向:
/dev/ttys0(串口的设备文件)—>tty_io.c(TTYcore)—>n_tty.c(tty线路规程,必须向core注册)—>处理完返回ttycore—>再由ttycore交给驱动程序serial.c—>驱动程序控制硬件
数据流
读操作:
TTY驱动从硬件接收到数据后,负责把数据传送到TTY核心,TTY核心将从TTY驱动收到的数据缓存到一个tty_flip_buffer类型的结构中。
该结构包含两个数据数组(一个用于读、一个用于写)。
从TTY设备接收到的数据被存储到第一个数组,当这个数组满,等待数据的用户将被通知。
当用户从这个数组读数据时,任何从TTY驱动新来的数据被存储在第2个数组。
当第二个数组存满后,数据再次提交给用户,并且驱动又开始填充第1个数组,以此交替。
驱动描述:
Linux内核使用uart_driver描述串口驱动,它包含串口设备的驱动名、设备名、设备号等信息。
structuart_driver{
structmodule*owner;
constchar*driver_name;//驱动名
constchar*dev_name;//设备名
intmajor;//主设备号
intminor;//从设备号
intnr;//设备数
structconsole*cons;
structuart_state*state;
structtty_driver*tty_driver;
};
注册驱动:
Linux为串口驱动注册了如下接口:
intuart_register_driver(structuart_driver*drv)
端口描述:
uart_port用于描述一个UART端口(一个串口)的地址、FIFO大小、端口类型等信息。
structuart_port
{
spinlock_tlock;/*端口锁*/
unsignedintiobase;/*IO端口基地址*/
unsignedchar__iomem*membase;/*IO内存基地址*/
unsignedintirq;/*中断号*/
unsignedcharfifosize;/*传输fifo大小*/
conststructuart_ops*ops;
……………………..
}
操作串口:
uart_ops定义了针对串口的一系列操作,包括发送、接收及线路设置等。
structuart_ops
{
unsignedint(*tx_empty)(structuart_port*);
void(*set_mctrl)(structuart_port*,unsignedintmctrl);
unsignedint(*get_mctrl)(structuart_port);
void(*stop_tx)(structuart_poart*);//停止发送
void(*start_tx)(structuart_poart*);//开始发送
void(*send_xchar)(structuart_poart*,charch);//发送xchar
void(*stop_rx)(structuart_port*);//停止接收
………………………………..
}
添加串口:
串口核心层提供如下函数添加1个端口:
intuart_add_one_port(structuart_driver*drv,structuart_port*port)
★串口驱动设计流程:
1、定义一个uart_driver的变量,并初始化
2、使用uart_regisetr_driver来注册这个驱动
3、初始化uart_port和ops函数表
4、调用uart_add_one_poart()添加初始化好的uart_port
Ø实例分析—mini2440串口驱动程序分析
1.发送和接收
发送:
循环buffer发送fifo发送移位寄存器
//循环buffer(驱动实现)发送fifo(串口芯片)过程由驱动程序完成;发送fifo发送移位寄存器过程由硬件完成
接收:
接收移位寄存器接收fifoFlip_buf
发送的过程是:
把数据写到发送fifo中,fifo把收到的数据传给发送移位寄存器(自动的,非driver控制),然后每个时钟脉冲往串口线上写一bit数据。
接收的过程是:
接收移位寄存器收到数据,发送给接收fifo,接收fifo事先设置好了触发门限,当里面的数据量超过门限时就会触发一个中断,调用驱动中的中断处理函数,把数据写到flip_buf中。
2.寄存器
UARTLineControlRegister:
WordLength:
数据位长度
NumberofStopBit:
停止位数
ParityMode:
奇偶校验位类型
Infra-RedMode:
UART/红外模式选择(当以UART模式工作时,需设为“0”)
UARTControlRegister
ReceiveMode:
选择接收模式。
如果是采用DMA模式的话,还需要指定说使用的DMA信道。
TransmitMode:
同上。
SendBreakSignal:
选择是否在传1帧资料中途发送Break信号。
LoopbackMode:
选择是否将UART置于Loopback测试模式。
RxErrorStatusInterruptEnable:
选择是否使能当发生接收异常时,是否产生接收错误中断。
RxTimeOutEnable:
是否使能接收超时中断。
RxInterruptType:
选择接收中断类型。
选择0:
Pulse(脉冲式/边沿式中断。
非FIFO模式时,一旦接收缓冲区中有数据,即产生一个中断;为FIFO模式时,一旦当FIFO中的资料达到一定的触发水平后,即产生一个中断)
选择1:
Level(电平模式中断。
非FIFO模式时,只要接收缓冲区中有数据,即产生中断;为FIFO模式时,只有FIFO中的资料达到触发水平后,即产生中断)
TxInterruptType:
类同于RxInterruptType
UARTFIFOConrtolRegister
FIFOEnable:
FIFO使能选择。
RxFIFOReset:
选择当复位接收FIFO时是否自动清除FIFO中的内容。
TxFIFOReset:
选择当复位发送FIFO时是否自动清除FIFO中的内容。
RxFIFOTriggerLevel:
选择接收FIFO的触发水平。
TxFIFOTriggerLevel:
选择发送FIFO的触发水平。
UARTTX/RXStatusRegister
Receivebufferdataready:
当接收缓冲寄存器从UART接收端口接收到有效资料时将自动置“1”。
反之为“0则表示缓冲器中没有资料。
Transmitbufferempty:
当发送缓冲寄存器中为空,自动置“1”;反之表明缓冲器中正有资料等待发送。
Transmitterempty:
当发送缓冲器中已经没有有效资料时,自动置“1”;反之表明尚有资料未发送。
UARTFIFOStatusRegister
RxFIFOCount:
接收FIFO中当前存放的字节数。
TxFIFOCount:
发送FIFO中当前存放的字节数。
RxFIFOFull:
为“1“表明接收FIFO已满。
TxFIFOFull:
为“1“表明发送FIFO已满。
3.函数介绍
模块初始化函数:
staticint__inits3c2410uart_init(void)
{
returnuart_register_driver(&s3c2410_reg);
}
使用uart_register_driver注册串口驱动。
staticstructuart_drivers3c2410_reg={
owner:
THIS_MODULE,
normal_major:
SERIAL_S3C2410_MAJOR,
normal_name:
"ttyS%d",
callout_name:
"cua%d",
normal_driver:
&normal,
callout_major:
CALLOUT_S3C2410_MAJOR,
callout_driver:
&callout,
table:
s3c2410_table,
termios:
s3c2410_termios,
termios_locked:
s3c2410_termios_locked,
minor:
MINOR_START,
nr:
UART_NR,
port:
s3c2410_ports,
cons:
S3C2410_CONSOLE,
};
staticstructuart_ports3c2410_ports[UART_NR]={
{
iobase:
(unsignedlong)(UART0_CTL_BASE),
iotype:
SERIAL_IO_PORT,
irq:
IRQ_RXD0,
uartclk:
130252800,
fifosize:
16,
ops:
&s3c2410_pops,
type:
PORT_S3C2410,
flags:
ASYNC_BOOT_AUTOCONF,
},
。
。
。
。
。
。
。
。
。
。
。
。
。
。
。
。
。
。
。
。
};
staticstructuart_opss3c2410_pops={
tx_empty:
s3c2410uart_tx_empty,
set_mctrl:
s3c2410uart_set_mctrl,
get_mctrl:
s3c2410uart_get_mctrl,
stop_tx:
s3c2410uart_stop_tx,
start_tx:
s3c2410uart_start_tx,
stop_rx:
s3c2410uart_stop_rx,
enable_ms:
s3c2410uart_enable_ms,
break_ctl:
s3c2410uart_break_ctl,
startup:
s3c2410uart_startup,
shutdown:
s3c2410uart_shutdown,
change_speed:
s3c2410uart_change_speed,
type:
s3c2410uart_type,
config_port:
s3c2410uart_config_port,
release_port:
s3c2410uart_release_port,
request_port:
s3c2410uart_request_port,
};
3.1阻止发送函数uart_stop_tx
staticvoids3c2410uart_stop_tx(structuart_port*port,u_intfrom_tty)
{
disable_irq(TX_IRQ(port));
}
停止发送的功能,其内部的函数disable_irq是停止中断的功能,发送数据是通过中断来完成的,关闭中断也就关闭了发送。
3.2发送使能函数uart_start_tx
staticvoids3c2410uart_start_tx(structuart_port*port,u_intnonempty,
u_intfrom_tty)
{
enable_irq(TX_IRQ(port));
}
与上面的过程类似,就是一个相反的过程
3.3阻止接收函数uart_stop_rx
staticvoids3c2410uart_stop_rx(structuart_port*port)
{
disable_irq(RX_IRQ(port));
}
3.4发送缓冲空判断函数uart_tx_empty
staticu_ints3c2410uart_tx_empty(structuart_port*port)
{
return(UART_UTRSTAT(port)&UTRSTAT_TR_EMP?
0:
TIOCSER_TEMT);
}
如果发送缓冲为空则返回0,否则返回1。
3.5获取控制信息函数uart_get_mctrl
staticu_ints3c2410uart_get_mctrl(structuart_port*port)
{
return(TIOCM_CTS|TIOCM_DSR|TIOCM_CAR);
}
获得控制信息,TIOCM_CTS,TIOCM_DSR和TIOCM_CAR,这几个宏代表串口的控制信息,分别是cleartosend,datasetready和datacarrierdetect(详见SerialProgrammingGuideforPOSIXOperatingSystems)
3.6接收中断函数uart_rx_interrupt
staticvoids3c2410uart_rx_interrupt(intirq,void*dev_id,structpt_regs*regs)
{
structuart_info*info=dev_id;
structtty_struct*tty=info->tty;
unsignedintstatus,ch,max_count=256;
structuart_port*port=info->port;
status=UART_UTRSTAT(port);
while((status&UTRSTAT_RX_RDY)&&max_count--)
{//status&UTRSTAT_RX_RDY判断有数据
if(tty->flip.count>=TTY_FLIPBUF_SIZE)
{//flipbuf已满
tty->flip.tqueue.routine((void*)tty);
//交换到另一个缓冲数组
if(tty->flip.count>=TTY_FLIPBUF_SIZE){//再进行判断
printk(KERN_WARNING"TTY_DONT_FLIPset\n");
return;
}
}
ch=UART_URXH(port);//从寄存器中取数据
*tty->flip.char_buf_ptr=ch;
*tty->flip.flag_buf_ptr=TTY_NORMAL;
port->icount.rx++;
tty->flip.flag_buf_ptr++;
tty->flip.char_buf_ptr++;
tty->flip.count++;//处理一个字节
status=UART_UTRSTAT(port);
}
tty_flip_buffer_push(tty);
return;
}
功能:
主要是是while大循环,首先看循环判断条件status&UTRSTAT_RX_RDY,前面有status=UART_UTRSTAT(port),查2410的datasheet,status&UTRSTAT_RX_RDY这个位是判断接收buffer内是否还有有效数据?
按道理一次中断只是把接收的fifobuffer中的数据放到flipbuffer中去,接收的fifo的中断门限是4-12字节,进行一次接收往往要中断好多次,这样中断开销比较大,所以在while的循环条件中判断一下是否还有接收的有效数据,如果有,就继续在中断程序中继续接收,当然,永远都在接收中断中(如果一直有数据要接收)也不合适,所以while循环还有计数,最多循环256次。
在循环中,首先是要判断一下接收数据用的flip-buffer是不是已经满了,if(tty->flip.count>=TTY_FLIPBUF_SIZE)如果满了,就要跳到另一个buffer上去,tty->flip.tqueue.routine((void*)tty)是用来实现跳到另一个buffer上的功能,然后把收到的数据写到flip-buffer中,相应的状态,统计数据都要改,接着再来while循环,循环结束后就要调用tty_flip_buffer_push(tty)来让用户把存在缓冲里的数据取走,接收一次都要把缓存清空。
3.7发送中断函数uart_tx_interrupt
staticvoids3c2410uart_tx_interrupt(intirq,void*dev_id,
structpt_regs*reg){
structuart_info*info=dev_id;
structuart_port*port=info->port;
intcount;
if(port->x_char){//x_char为停止位
UART_UTXH(port)=port->x_char;
port->icount.tx++;
port->x_char=0;
return;
}
if(info->xmit.head==info->xmit.tail//无数据发送
||info->tty->stopped||info->tty->hw_stopped){
s3c2410uart_stop_tx(info->port,0);//关闭发送中断
return;
}
count=port->fifosize>>1;
do{//有数据发送
UART_UTXH(port)=info->xmit.buf[info->xmit.tail];
info->xmit.tail=(info->xmit.tail+1)&(UART_XMIT_SIZE-1);
port->icount.tx++;
if(info->xmit.head==info->xmit.tail)
break;
}while(--count>0);//count=fifpsize(16字节)>>1即8字节
if(CIRC_CNT(info->xmit.head,info->xmit.tail,
UART_XMIT_SIZE)uart_event(info,EVT_WRITE_WAKEUP);//通知上层
if(info->xmit.head==info->xmit.tail)
s3c2410uart_stop_tx(info->port,0);
}
(1)首先查看port中的x_char是不是为0,不为0则把x_char发送出去。
x_char是xon/xoff的意思,每发一个字节时在开始前先发xon信号,在结束时发xoff。
(2)如果x_char没有被设置,再看环形缓冲区是否为空,或者info->tty->stopped和info->tty->hw_stopped两个位是不是为1,如果这些条件成立的话,就停止发送。
Tty->stop指示