现在的位置: 首页 > 综合 > 正文

串口驱动程序分析—2.4内核

2017年08月24日 ⁄ 综合 ⁄ 共 12566字 ⁄ 字号 评论关闭

串口驱动程序分析---2.4内核

1--发送和接受

发送:循环buffer->发送fifo->发送移位寄存器

接收:接收移位寄存器->接收fifo->Flip_buf

绿色的为驱动做的


发送过程是:把数据写到发送fifo中,fifo把收到的数据传送给发送移位寄存器(自动的,非driver控制)然后每个时钟脉冲往串口线上写一bit数据。

接收的过程是:接收移位寄存器收到数据,发送给接收fifo,接收fifo事先设置好了触发门限,当里面的数据超过门限时,就会触发一个中断,调用驱动中的中断处理函数,把数据写到flip_buf中。

2.寄存器

UART Line Control Register:

 

Word Length :数据位长度

Number of Stop Bit :停止位数

Parity Mode :奇偶校验位类型

Infra-Red Mode :UART/红外模式选择(当以UART模式工作时,需设为“0”)


UART Control Register


Receive Mode:选择接收模式。如果是采用DMA模式的话,还需要指定说使用的DMA信道。

Transmit Mode :同上。

Send Break Signal :选择是否在传1帧资料中途发送Break信号。

Loopback Mode :选择是否将UART置于Loopback测试模式。

Rx Error Status Interrupt Enable :选择是否使能当发生接收异常时,是否产生接收错误中断。

Rx Time Out Enable :是否使能接收超时中断。

Rx Interrupt Type :选择接收中断类型。

选择0:Pulse(脉冲式/边沿式中断。非FIFO模式时,一旦接收缓冲区中有数据,即产生一个中断;为FIFO模式时,一旦当FIFO中的资料达到一定的触发水平后,即产生一个中断)

选择1:Level(电平模式中断。非FIFO模式时,只要接收缓冲区中有数据,即产生中断;为FIFO模式时,只有FIFO中的资料达到触发水平后,即产生中断)

Tx Interrupt Type :类同于Rx Interrupt Type


UART FIFO Conrtol Register


FIFO Enable :FIFO使能选择。
Rx FIFO Reset :选择当复位接收FIFO时是否自动清除FIFO中的内容。
Tx FIFO Reset :选择当复位发送FIFO时是否自动清除FIFO中的内容。
Rx FIFO Trigger Level :选择接收FIFO的触发水平。收到指定长度的数据时,产生接收中断。
Tx FIFO Trigger Level :选择发送FIFO的触发水平。当发送FIFO低于指定长度的数据时,产生发送中断。网卡是先发送完数据,然后产生中断。串口是先发送数据,然后产生中断,告诉可以再发送数据。

网卡接收---接收到一个完整的数据包产生接收中断。

网卡发送---发送完指定长度的数据时,产生发送中断。


UART TX/RX Status Register

Receive buffer data ready :当接收缓冲寄存器从UART接收端口接收到有效资料时将自动置“1”。反之为“0则表示缓冲器中没有资料。

Transmit buffer empty :当发送缓冲寄存器中为空,自动置“1”;反之表明缓冲器中正有资料等待发送。

Transmitter empty :当发送缓冲器中已经没有有效资料时,自动置“1”;反之表明尚有资料未发送。

UART FIFO Status Register

Rx FIFO Count :接收FIFO中当前存放的字节数。

Tx FIFO Count :发送FIFO中当前存放的字节数。

Rx FIFO Full :为“1“表明接收FIFO已满。

Tx FIFO Full :为“1“表明发送FIFO已满。

3.函数介绍

模块初始化函数:

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_int from_tty)

disable_irq(TX_IRQ(port)); 

停止发送的功能,其内部的函数disable_irq是停止中断的功能 ,发送数据是通过中断来完成的,关闭中断也就关闭了发送。

3.2 发送使能函数uart_start_tx

static void s3c2410uart_start_tx(struct uart_port *port, u_int nonempty, 

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和data carrier detect(详见Serial Programming Guide for POSIX Operating Systems) 

 

3.6 接收中断函数uart_rx_interrupt 

static void s3c2410uart_rx_interrupt(int irq, void *dev_id, struct pt_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_FLIP set\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(int irq, 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) &amp; UART_ERR_MASK确定了err的值,err的值是从是从UART Error Status Register读到的,该erstate只用了四位,所以用UART_ERR_MASK把高四位掩掉,然后测试低四位中哪个位被置1了,从而判断错误种类UERSTAT_BRK/FRAME/PARITY/OVERRUN 分别代表1000/0100/0010/0001 ,判断出错误种类再进行相应的中断计数,然后再根据不同的err给flg设上不同的值,有 

#define TTY_NORMAL 0 

#define TTY_BREAK 1 

#define TTY_FRAME 2 

#define TTY_PARITY 3 

#define TTY_OVERRUN 4 

3.9 初始化函数uart_startup

static int s3c2410uart_startup(struct uart_port *port, struct uart_info *info) 

int ret, flags; 

u_int ucon; 

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; 

#ifdef CONFIG_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); 

#if defined(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); 

return 0; 

#ifdef CONFIG_USE_ERR_IRQ 

err_failed: 

free_irq(TX_IRQ(port), info); 

#endif 

tx_failed: 

free_irq(RX_IRQ(port), info); 

rx_failed: 

return ret; 

如果使用了函数open(ttyS0),那么最后调用的实现open功能的就是这个函数,它打开ttyS0。

1:利用request_irq()申请发送,接收,错误三个中断,如果失败,就要释放调已经申请的全部资源  

 

2:设置UART Control Register

 

 

3.10 函数uart_change_speed

static void s3c2410uart_change_speed(struct uart_port *port, u_int cflag, u_int iflag, u_int quot) 

u_int ulcon, ufcon; 


int flags; 

ufcon = UART_UFCON(port); 

switch (cflag & CSIZE) { 

case CS5: ulcon = ULCON_WL5; break; 

case CS6: ulcon = ULCON_WL6; break; 

case CS7: 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 

static void s3c2410_console_write(struct console *co, const char *s, u_int count) 

int i; 

struct uart_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 

static int s3c2410_console_wait_key(struct console *co) 

int c; 

struct uart_port *port = s3c2410_ports + co->index; 

while (!(UART_UTRSTAT(port) & UTRSTAT_RX_RDY)); 

c = UART_URXH(port); 


return c; 

该函数在while循环中等待接收数据,一旦接收缓冲器中有有效数据,该函数立即返回,返回值为接收到的一个字符 

4.4 函数console_device 

static kdev_t s3c2410_console_device(struct console *co) 

return MKDEV(SERIAL_S3C2410_MAJOR, MINOR_START + co->index); 

通过主,次设备号返回kdev_t结构

4.5 设置函数console_setup 

static int __init s3c2410_console_setup(struct console *co, char *options) 

struct uart_port *port; 

int baud = 115200; 

int bits = 8; 

int parity = 'n'; 

int flow = 'n'; 

port = uart_get_console(s3c2410_ports, UART_NR, co); 

if (options) 

uart_parse_options(options, &baud, &parity, &bits, &flow); 

return uart_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_port struct的指针 

(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




【上篇】
【下篇】

抱歉!评论已关闭.