串口驱动及架构

一、重点概述:
在Linux中,TTY(终端)是一类字符设备的统称,包括了3种类型:控制台,串口和伪终端。
1)控制台:供内核使用的终端为控制台。控制台在Linux启动时,通过命令
console=…指定,如果没有指定控制台,系统把第一个注册的终端(tty)作为控制台。 如printk打印到哪里哪里就是控制台,如lcd屏幕,终端软件的那个等

1. 控制台是一个虚拟的终端,它必须映射到真正的终端上。
2. 控制台可以简单的理解为printk输出的地方。
3. 控制台是个只输出的设备,功能很简单,只能在内核中访问。

2)伪终端:
伪终端设备是一种特殊的终端设备, 由主-从两个成对的设备构成, 当打开主设备时, 对应的从设备随之打开, 形
成连接状态。输入到主设备的数据成为从设备的输出, 输入到从设备的数据成为主设备的输出, 形成双向管
道。伪终端设备常用于远程登录服务器来建立网络和终端的关联。当通过telnet远程登录到另一台主机时, 
telnet进程与远程主机的telnet服务器相连接. telnet服务器使用某个主设备并通过对应的从设备与telnet进程相互通信

3)终端体系:
 图:


 
  在Linux中,TTY体系分为:TTY核心,TTY线路规程,TTY驱动3部分。TTY核心从用户
获取要发送给TTY设备的数据,然后把数据传递给TTY线路规程, 它对数据进行处理后,
负责把数据传递到TTY驱动程序,TTY驱动程序负责格式化数据,并通过硬件发送出去。

  从硬件收到的数据向上通过TTY驱动, 进入TTY线路规程, 再进入TTY核心, 最后被用户获取。TTY驱动
可以直接和TTY核心通讯, 但是通常TTY线路规程会修改在两者之间传送的数据。TTY驱动不能直接和
线路规程通信,甚至不知道它的存在,线路规程的工作是格式化从用户或者硬件收到的数据. 这种格
式化常常实现为一个协议, 如PPP或Bluetooth

形象图:

 

4)数据流:
如图:


   读操作:TTY驱动从硬件收到数据后,负责把数据传递到TTY 核
心,TTY核心将从TTY驱动收到的数据缓存到一个tty_flip_buffer 类型的结构中。该结构包含两个数据数
组。从TTY设备接收到的数据被存储于第一个数组,当这个数组满, 等待数据的用户将被通知。当用户从这个
数组读数据时, 任何从TTY驱动新来的数据将被存储在第2个数组。当第二个数组存满后,数据再次提交给用
户, 并且驱动又开始填充第1个数组,以此交替

5)驱动描述:
Linux内核使用uart_driver描述串口驱动,它包含串口设备
的驱动名、设备名、设备号等信息。
struct uart_driver
{
  struct module *owner;
  const char *driver_name;  //驱动名
  const char *dev_name;   //设备名
  int major;   //主设备号
  int minor;   //起始次设备号
  int nr;       //设备数
  struct console *cons;
  struct uart_state *state;
  struct tty_driver *tty_driver;
}

6)注册驱动:
Linux为串口驱动注册提供了如下接口:
int uart_register_driver(struct uart_driver *drv)

7)端口描述:
uart_port用于描述一个UART端口(一个串口)的地址、FIFO大
小、端口类型等信息
struct uart_port
{
  spinlock_t lock; /* 端口锁*/
  unsigned int iobase; /* IO端口基地址*/
  unsigned char __iomem *membase; /* IO内存基地址*/
  unsigned int irq; /* 中断号*/
  unsigned char fifosize; /* 传输fifo大小*/
  const structuart_ops *ops;
  …………………………………………………………
}

8)操作串口:
uart_ops定义了针对串口的一系列操作,包括发
送、接收及线路设置等。
struct uart_ops
{
  unsigned int(*tx_empty)(struct uart_port*);
  void(*set_mctrl)(struct uart_port *, unsigned int mctrl);
  unsigned int(*get_mctrl)(struct uart_port*);
  void(*stop_tx)(struct uart_port*);    //停止发送
  void(*start_tx)(struct uart_port*);   //开始发送
  void(*send_xchar)(struct uart_port *, char ch); //发送xchar
  void(*stop_rx)(struct uart_port*);   //停止接收
  .......
}

9)添加端口:
串口核心层提供如下函数来添加1个端口:
int uart_add_one_port(struct uart_driver *drv, struct uart_port *port)


10)串口驱动程序操作流程:
定义一个uart_driver的变量,并初始化;
2. 使用uart_register_driver来注册这个驱动;
3. 初始化uart_port和ops函数表;
4. 调用uart_add_one_port()添加初始化好的uart_port。


二、串口驱动程序分析
1)发送和接收:
发送: 循环buffer -(驱动做)-> 发送 fifi -(硬件自己做)-> 发送移位寄存器

       把数据写到发送fifo中。fifo把收到的数据传给发送移位寄存器(自动的,非driver控制),然后每个时钟脉冲往串口线上写一个bit数据
       所以是一直是中断告知还可以发送几个。当不足时一直引发中断。
       Tx FIFO trigger level选择fifo触发水平,选择什么时候引发中断,如fifo 低于 到4个或8 16 个字节等时中断 
 

       
接收: 接收移位寄存器 -(硬件自己做)-> 接收fifo -(驱动做)-> flip_buf
 
       接收移位寄存器收到数据后,发送给接收fifo,接收fifo事先设置好触发门限,当里面的数据量超过门限时,就会触发一个中断,调用驱动里的中断处理函数,把数据写到flip_buf中。
       Rx FIFO trigger level选择fifo触发水平,选择什么时候引发中断,如收到4个或8 16 个字节等时中断 
 

2)模块初始化函数:
    static int __int s3c2410uart_init(void)
    {
        return  uart_register_driver(&s3c2410_reg);
    }
    
    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)阻止发送函数uart_stop_tx 
static void s3c2410uart_stop_tx(struct uart_port *port, u_int from_tty)
{  
 disable_irq(TX_IRQ(port)); //因为是中断产生说能再发送才能发送的,所以关闭中断就阻止了发送
}

4) 发送使能函数uart_start_tx 
static void s3c2410uart_start_tx(struct uart_port *port, u_int nonempty,  u_int from_tty)
{  
 enable_irq(TX_IRQ(port)); 
}

5)阻止接收函数uart_stop_rx 
static void s3c2410uart_stop_rx(struct uart_port *port)

  disable_irq(RX_IRQ(port)); 
}

6)发送缓冲空判断函数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。
  
7) 获取控制信息函数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) 
  
8)接收中断函数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)来让用户把存在缓冲里的数据取走,接收一次都要把缓存清空。
 
9)发送中断函数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); //CIRC_CNT是kernel自动计算环形队列还剩多少空间。需传入头,尾,长度
 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 没有发送来更多的数据,就关闭发送

猜你喜欢

转载自blog.csdn.net/zjy900507/article/details/81283548