一. TTY
在Linux系统中,终端是一种字符设备,它有多种类型,通常使用tty来简称各种类型的终端设备。tty是Teletype的缩写,Teletype是最早出现的一种终端设备,很像电传打字机,是由Teletype公司产生的。Linux系统包含以下几类终端设备:
首先来看一下tty设备的数据流通图:
发送:循环buffer ->发送fifo->发送移位寄存器
接收:接收移位寄存器->接收fifo ->Flip_buf
发送的过程是:把数据写到发送fifo中,fifo把收到的数据传给发送移位寄存器(自动的,非driver控制),然后每个时钟脉冲往串口线上写一bit数据。
接收的过程是:接收移位寄存器收到数据,发送给接收fifo,接收fifo事先设置好了触发门限,当里面的数据量超过门限时就会触发一个中断,调用驱动中的中断处理函数,把数据写到flip_buf中。
/* 功能: uart_register_driver用于将串口驱动uart_driver注册到内核(串口核心层)中,通常在模块初始化函数调用该函数。 * 返回值: 成功,返回0;否则返回错误码 |
/* 功能: uart_unregister_driver用于注销我们已注册的uart_driver,通常在模块卸载函数调用该函数 * 返回值: 成功,返回0;否则返回错误码 |
/* 功能: uart_add_one_port用于为串口驱动添加一个串口端口,通常在探测到设备后(驱动的设备probe方法)调用该函数 * 返回值: 成功,返回0;否则返回错误码 |
/* 功能: uart_remove_one_port用于删除一个已添加到串口驱动中的串口端口,通常在驱动卸载时调用该函数 |
/* 功能: uart_write_wakeup唤醒上层因向串口端口写数据而阻塞的进程,通常在串口发送中断处理函数中调用该函数 |
/* 功能: uart_suspend_port用于挂起特定的串口端口 |
/* 功能: uart_resume_port用于恢复某一已挂起的串口 |
/* 功能: uart_get_baud_rate通过解码termios结构体来获取指定串口的波特率 |
/* 功能: uart_get_divisor用于计算某一波特率的串口时钟分频数(串口波特率除数) |
/* 功能: uart_update_timeout用于更新(设置)串口FIFO超时时间 |
/* 功能:uart_match_port用于判断两串口端口是否为同一端口 |
/* 功能: uart_console_write用于向串口端口写一控制台信息 * 参数 port: 要写信息的串口端口 |
//(2)串口端口结构体 描述串口端口的I/O端口或I/O内存地址、FIFO大小、端口类型、串口时钟等信息。实际上,一个uart_port实例对应一个串口设备
struct uart_port {
spinlock_t lock;
unsigned long iobase; //io端口基地址
unsigned char __iomem *membase; //内存端口基地址
unsigned int (*serial_in)(struct uart_port *, int); //串口读函数
void (*serial_out)(struct uart_port *, int, int); //串口写方法
void (*set_termios)(struct uart_port *,struct ktermios *new,struct ktermios *old); //串口配置方法函数
void (*pm)(struct uart_port *, unsigned int state,unsigned int old);
unsigned int irq; //中断号
unsigned long irqflags; //中断标志
unsigned int uartclk; //串口时钟
unsigned int fifosize; //fifo大小
unsigned char x_char;
unsigned char regshift; //寄存器偏移值
unsigned char iotype; //io访问类型
unsigned char unused1;
unsigned int read_status_mask;
unsigned int ignore_status_mask;
struct uart_state *state; //uart_state结构体
struct uart_icount icount; //串口使用计数
struct console *cons; //console控制台
#if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(SUPPORT_SYSRQ)
unsigned long sysrq;
#endif
upf_t flags;
unsigned int mctrl;
unsigned int timeout;
unsigned int type; //串口类型
const struct uart_ops *ops; //串口操作函数集
unsigned int custom_divisor;
unsigned int line; //端口号
resource_size_t mapbase; //串口寄存器基地址(物理地址)
struct device *dev; //设备文件
unsigned char hub6;
unsigned char suspended;
unsigned char irq_wake;
unsigned char unused[2];
void *private_data;
};
//(3)操作函数集 涵盖了串口驱动可对串口设备进行的所有操作。
struct uart_ops {
unsigned int (*tx_empty)(struct uart_port *); //发送缓冲区为空
void (*set_mctrl)(struct uart_port *, unsigned int mctrl); //设置串口modem控制模式
unsigned int (*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);
void (*stop_rx)(struct uart_port *); //停止接收
void (*enable_ms)(struct uart_port *); //使能modem状态信息
void (*break_ctl)(struct uart_port *, int ctl);
int (*startup)(struct uart_port *); //打开串口
void (*shutdown)(struct uart_port *); //关闭串口
void (*flush_buffer)(struct uart_port *);
void (*set_termios)(struct uart_port *, struct ktermios *new,struct ktermios *old); //设置串口参数
void (*set_ldisc)(struct uart_port *, int new);
void (*pm)(struct uart_port *, unsigned int state,unsigned int oldstate);
int (*set_wake)(struct uart_port *, unsigned int state);
const char *(*type)(struct uart_port *);
void (*release_port)(struct uart_port *); //释放端口
int (*request_port)(struct uart_port *); //请求端口
void (*config_port)(struct uart_port *, int); //配置端口
int (*verify_port)(struct uart_port *, struct serial_struct *); //校验端口
int (*ioctl)(struct uart_port *, unsigned int, unsigned long); //控制
#ifdef CONFIG_CONSOLE_POLL
void (*poll_put_char)(struct uart_port *, unsigned char);
int (*poll_get_char)(struct uart_port *);
#endif
};
//(4)uart_state
struct uart_state {
struct tty_port port;
int pm_state;
struct circ_buf xmit;
struct tasklet_struct tlet;
struct uart_port *uart_port;//指向对应的串口结构
};
/*串口初始化
完善uart_driver结构s3c2410_reg的uart_state成员及tty_driver成员,并注册tty驱动 */static int __init s3c2410uart_init(void)
{
return uart_register_driver(&s3c2410_reg);
}
使用uart_register_driver注册串口驱动。
static struct uart_driver s3c2410_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,
};
static struct uart_port s3c2410_ports[UART_NR] = {
{
iobase: (unsigned long)(UART0_CTL_BASE),
iotype: SERIAL_IO_PORT,
irq: IRQ_RXD0,
uartclk: 130252800,
fifosize: 16,
ops: &s3c2410_pops,
type: PORT_S3C2410,
flags: ASYNC_BOOT_AUTOCONF,
},
。。。。。。 。。。。。。。 。。。。。。。
};
static struct uart_ops s3c2410_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
static void s3c2410uart_stop_tx(struct uart_port *port, u_intfrom_tty)
{
disable_irq(TX_IRQ(port));
}
停止发送的功能,其内部的函数disable_irq是停止中断的功能 ,发送数据是通过中断来完成的,关闭中断也就关闭了发送。
3.2 发送使能函数uart_start_tx
static void s3c2410uart_start_tx(struct uart_port *port, u_intnonempty,
u_int from_tty)
{
enable_irq(TX_IRQ(port));
}
与上面的过程类似,就是一个相反的过程
3.3 阻止接收函数uart_stop_rx
static void s3c2410uart_stop_rx(struct uart_port *port)
{
disable_irq(RX_IRQ(port));
}
3.4 发送缓冲空判断函数uart_tx_empty
static u_int s3c2410uart_tx_empty(struct uart_port *port)
{
return (UART_UTRSTAT(port) &UTRSTAT_TR_EMP ? 0 : TIOCSER_TEMT);
}
如果发送缓冲为空则返回0,否则返回1。
3.5 获取控制信息函数uart_get_mctrl
static u_int s3c2410uart_get_mctrl(struct uart_port *port)
{
return (TIOCM_CTS | TIOCM_DSR | TIOCM_CAR);
}
获得控制信息,TIOCM_CTS ,TIOCM_DSR 和TIOCM_CAR,这几个宏代表串口的控制信息, 分别是clear to send,data set ready和datacarrier detect(详见Serial Programming Guide for POSIX Operating Systems)
3.6 接收中断函数uart_rx_interrupt
static void s3c2410uart_rx_interrupt(int irq, void *dev_id, structpt_regs *regs)
{
struct uart_info *info = dev_id;
struct tty_struct *tty = info->tty;
unsigned int status, ch, max_count = 256;
struct uart_port *port = info->port;
status = UART_UTRSTAT(port);
while ((status & UTRSTAT_RX_RDY)&& max_count--)
{
if (tty->flip.count >=TTY_FLIPBUF_SIZE)
{
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
static void s3c2410uart_tx_interrupt(int irq, void *dev_id,
struct pt_regs *reg) {
struct uart_info *info = dev_id;
struct uart_port *port = info->port;
int count;
if (port->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);
if (CIRC_CNT(info->xmit.head,info->xmit.tail,
UART_XMIT_SIZE) < WAKEUP_CHARS)
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指示tty设备是否停止,tty->hw_stop指示tty设备的硬件是否停止了,以上两个位都可以通过ttydriver来设定,否则的话说明有数据要发送。
(3) 如果以上条件都通过了,就利用一个while循环正式发送数据了,从环形缓冲尾巴上取一个数赋给UART_UTXH(port)(发送FIFO),UART_UTXH(port) = info->xmit.buf[info->xmit.tail],这条语句就是把数据送到发送FIFO中,然后计数++,循环一共进行fifosize/2次,也就是一次只能发送8 byte。
(4)循环传送完一次后,再查看缓冲器里还剩余多少数据,如果少于WAKEUP_CHARS(256)的话,就执行uart_event(info, 0),告诉TTY核心,可以接受更多数据了。这里可以看出,tty_driver和tty_core之间的层次,tty_driver可以知道缓冲空还是满,但是它没有权力让发送数据过来,它只能是通知tty_core,让它来处理。
(5) 最后再察看一下环形寄存器,如果serial core 没有发送来更多的数据,就关闭发送。
3.8 出错中断函数uart_err_interrupt
static void s3c2410uart_err_interrupt(intirq, void *dev_id,
struct pt_regs *reg) {
struct uart_info *info = dev_id;
struct uart_port *port = info->port;
struct tty_struct *tty = info->tty;
unsigned char err = UART_UERSTAT(port) & UART_ERR_MASK;
unsigned int ch, flg;
ch = UART_URXH(port);
if (!(err & (UERSTAT_BRK | UERSTAT_FRAME |
UERSTAT_PARITY | UERSTAT_OVERRUN)))
return;
if (err & UERSTAT_BRK)
port->icount.brk++;
if (err & UERSTAT_FRAME)
port->icount.frame++;
if (err & UERSTAT_PARITY)
port->icount.parity++;
if (err & UERSTAT_OVERRUN)
port->icount.overrun++;
err &= port->read_status_mask;
if (err & UERSTAT_PARITY)
flg = TTY_PARITY;
else if (err & UERSTAT_FRAME)
flg = TTY_FRAME;
else
flg = TTY_NORMAL;
if (err & UERSTAT_OVERRUN) {
*tty->flip.char_buf_ptr = ch;
*tty->flip.flag_buf_ptr = flg;
tty->flip.flag_buf_ptr++;
tty->flip.char_buf_ptr++;
tty->flip.count++;
if (tty->flip.count < TTY_FLIPBUF_SIZE) {
ch = 0;
flg = TTY_OVERRUN;
}
}
*tty->flip.flag_buf_ptr++ = flg;
*tty->flip.char_buf_ptr++ = ch;
tty->flip.count++;
}
#endif
首先err = UART_UERSTAT(port) & UART_ERR_MASK确定了err的值,err的值是从是从UARTError Status Register读到的,该erstate只用了四位,所以用UART_ERR_MASK把高四位掩掉,然后测试低四位中哪个位被置1了,从而判断错误种类UERSTAT_BRK/FRAME/PARITY/OVERRUN 分别代表1000/0100/0010/0001,判断出错误种类再进行相应的中断计数,然后再根据不同的err给flg设上不同的值,有
#defineTTY_NORMAL 0
#defineTTY_BREAK 1
#defineTTY_FRAME 2
#defineTTY_PARITY 3
#defineTTY_OVERRUN 4
3.9 初始化函数uart_startup
staticint s3c2410uart_startup(structuart_port *port, struct uart_info *info)
{
intret, flags;
u_intucon;
ret =request_irq(RX_IRQ(port), s3c2410uart_rx_interrupt,SA_INTERRUPT,
"serial_s3c2410_rx", info);
if(ret) goto rx_failed;
ret =request_irq(TX_IRQ(port), s3c2410uart_tx_interrupt,SA_INTERRUPT,
"serial_s3c2410_tx", info);
if(ret) goto tx_failed;
#ifdefCONFIG_USE_ERR_IRQ
ret =request_irq(ERR_IRQ(port), s3c2410uart_err_interrupt,SA_INTERRUPT,
"serial_s3c2410_err", info);
if(ret) goto err_failed;
#endif
ucon =(UCON_TX_INT_LVL | UCON_RX_INT_LVL |
UCON_TX_INT| UCON_RX_INT | UCON_RX_TIMEOUT);
#ifdefined(CONFIG_IRDA) || defined(CONFIG_IRDA_MODULE)
ULCON2|= ULCON_IR | ULCON_PAR_NONE | ULCON_WL8 | ULCON_ONE_STOP;
#endif
save_flags(flags);
cli();
UART_UCON(port)= ucon;
sti();
restore_flags(flags);
return0;
#ifdefCONFIG_USE_ERR_IRQ
err_failed:
free_irq(TX_IRQ(port),info);
#endif
tx_failed:
free_irq(RX_IRQ(port),info);
rx_failed:
returnret;
}
如果使用了函数open(ttyS0),那么最后调用的实现open功能的就是这个函数,它打开ttyS0。
1:利用request_irq()申请发送,接收,错误三个中断,如果失败,就要释放调已经申请的全部资源
2:设置UARTControl Register
3.10 函数uart_change_speed
staticvoid s3c2410uart_change_speed(structuart_port *port, u_int cflag, u_int iflag, u_int quot)
{
u_intulcon, ufcon;
intflags;
ufcon= UART_UFCON(port);
switch(cflag & CSIZE) {
caseCS5: ulcon = ULCON_WL5; break;
caseCS6: ulcon = ULCON_WL6; break;
caseCS7: ulcon = ULCON_WL7; break;
default:ulcon = ULCON_WL8; break;
}
if(cflag & CSTOPB)
ulcon|= ULCON_STOP;
if(cflag & PARENB) {
if(!(cflag & PARODD))
ulcon|= ULCON_PAR_EVEN;
}
if(port->fifosize > 1)
ufcon|= UFCON_FIFO_EN;
port->read_status_mask= UERSTAT_OVERRUN;
if(iflag & INPCK)
port->read_status_mask|= UERSTAT_PARITY | UERSTAT_FRAME;
port->ignore_status_mask= 0;
if(iflag & IGNPAR)
port->ignore_status_mask|= UERSTAT_FRAME | UERSTAT_PARITY;
if(iflag & IGNBRK) {
if(iflag & IGNPAR)
port->ignore_status_mask|= UERSTAT_OVERRUN;
}
quot-= 1;
save_flags(flags);
cli();
UART_UFCON(port)= ufcon;
UART_ULCON(port)= (UART_ULCON(port) & ~(ULCON_PAR | ULCON_WL)) | ulcon;
UART_UBRDIV(port)= quot;
sti();
restore_flags(flags);
}
1:
UBRDIVn=(int)(CLK/(bps*16))-1
quot=(CLK/ (baudrate x 16) ) (CLK为PCLK或UCLK,baudrate的单位是bps
(1):首先看一下cflag的cs位,同CS5/6/7比较,然后设置ulcon,接下来的几个if也是将ulcon根据cflag的设置进行一下设置,设置了停止位,校验位。
(2):如果port中设置了fifosize,就把UFCON(物理地址0x50000008)的第0位设为1。
4.控制台
4.1 注册控制台
void __init s3c2410_console_init(void)
{
register_console(&s3c2410_cons);
}
static struct console s3c2410_cons = {
name: "ttyS",
write: s3c2410_console_write,
device: s3c2410_console_device,
wait_key: s3c2410_console_wait_key,
setup: s3c2410_console_setup,
flags: CON_PRINTBUFFER,
index: -1,
};
4.2 函数console_write
staticvoid s3c2410_console_write(structconsole *co, const char *s, u_int count)
{
int i;
structuart_port *port = s3c2410_ports+ co->index;
for (i= 0; i < count; i++) {
while(!(UART_UTRSTAT(port) & UTRSTAT_TX_EMP));
UART_UTXH(port)= s[i];
if(s[i] == '\n') {
while(!(UART_UTRSTAT(port) & UTRSTAT_TX_EMP));
UART_UTXH(port)= '\r';
}
}
}
通过串口往外发送数据
for循环count次,每次发送一个字符,当发送缓冲寄存器为空时,就往里写一个字符,如果写的数据是回车加换行,就要再写一个换行符
4.3 函数console_waitkey
staticint s3c2410_console_wait_key(structconsole *co)
{
int c;
structuart_port *port = s3c2410_ports+ co->index;
while(!(UART_UTRSTAT(port) & UTRSTAT_RX_RDY));
c =UART_URXH(port);
returnc;
}
该函数在while循环中等待接收数据,一旦接收缓冲器中有有效数据,该函数立即返回,返回值为接收到的一个字符
4.4 函数console_device
statickdev_t s3c2410_console_device(structconsole *co)
{
returnMKDEV(SERIAL_S3C2410_MAJOR,MINOR_START + co->index);
}
通过主,次设备号返回kdev_t结构
4.5 设置函数console_setup
staticint __init s3c2410_console_setup(structconsole *co, char *options)
{
structuart_port *port;
intbaud = 115200;
intbits = 8;
intparity = 'n';
intflow = 'n';
port =uart_get_console(s3c2410_ports,UART_NR, co);
if(options)
uart_parse_options(options,&baud, &parity, &bits, &flow);
returnuart_set_options(port, co, baud, parity, bits, flow);
}
这个函数就是设置控制台(console)的状态,里面主要有三个函数
(1)uart_get_console (struct uart_port*ports, int nr, struct console *co)
该函数检查是否co->index是一个无效的index number,返回指向该index number 对应的uart_portstruct的指针
(2)uart_parse_options (options, &baud,&parity, &bits, &flow)
如果option被设置了,那么就要调用该函数,该函数解析options,options来自上一层函数的参数,它是一个字符串,应该包含baudrate,parity,bits,flow这些信息。
(3)uart_set_options(port, co, baud, parity, bits, flow)
针对以上给定的信息,对console的cflag进行设置.还要调用一下ops中的change_speed对baud rate进行实际的设置(物理),成功地话return 0