[Linux驱动篇]Uart驱动架构

Uart体系结构

    UART设备驱动可以使用tty驱动的框架来实现,但是因为串口之间有共性,所以Linux在tty接口上封装了一层(serial core)。后面我们再拿一篇文章来解释tty驱动,tty其实就是各种终端设备,串口其实也是终端设备。

    驱动工程师没必要关心上层的流程,只需注册一个uart_driver,并按硬件规范将对应接口函数完成就可以了。

上图我们只需要实现xxx_uart.c ,  而我们实现所需要的结构体和函数接口就是由serial_core.c提供。接下来我们来看一下对应的结构体和接口函数。

重要结构体

内核版本:4.20.12

  • uart_driver
1
2
3
4
5
6
7
8
9
10
11
12
13
struct uart_driver {
  struct module    *owner;
  const char    *driver_name;
  const char    *dev_name; //设备名,即dev下的节点名
  int       major;
  int       minor;
  int       nr;
  struct console    *cons;//console配置,串口作为console时才需要
 
    //私有的,底层驱动把它初始化为NULL即可
  struct uart_state  *state;
  struct tty_driver  *tty_driver;
};

串口设备也是字符设备,所以看到很多字符设备相关的,console就是控制台,我们平常所使用的debug口就是console。

  • uart_port
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
//描述一个UART端口
struct uart_port {
  spinlock_t    lock;      /* port lock */
  unsigned long    iobase;      /* in/out[bwl] */
  unsigned char __iomem  *membase;    /* read/write[bwl] */
  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      (*set_ldisc)(struct uart_port *,
               struct ktermios *);
  unsigned int    (*get_mctrl)(struct uart_port *);
  void      (*set_mctrl)(struct uart_port *, unsigned int);
  unsigned int    (*get_divisor)(struct uart_port *,
                 unsigned int baud,
                 unsigned int *frac);
  void      (*set_divisor)(struct uart_port *,
                 unsigned int baud,
                 unsigned int quot,
                 unsigned int quot_frac);
  int      (*startup)(struct uart_port *port);
  void      (*shutdown)(struct uart_port *port);
  void      (*throttle)(struct uart_port *port);
  void      (*unthrottle)(struct uart_port *port);
   //中断处理
  int      (*handle_irq)(struct uart_port *);
  void      (*pm)(struct uart_port *, unsigned int state,
              unsigned int old); //电源管理
  void      (*handle_break)(struct uart_port *);
    //485配置
  int      (*rs485_config)(struct uart_port *,
            struct serial_rs485 *rs485);
  int      (*iso7816_config)(struct uart_port *,
              struct serial_iso7816 *iso7816);
  unsigned int    irq;      /* 中断号 */
  unsigned long    irqflags;    /* 中断标志  */
  unsigned int    uartclk;    /* 串口时钟 */
  unsigned int    fifosize;    /* tx fifo size */
  unsigned char    x_char;      /* xon/xoff char */
  unsigned char    regshift;    /* reg offset shift */
  unsigned char    iotype;      /* io access style */
  unsigned char    quirks;      /* internal quirks */
 
 
//省略宏定义....
 
 
  unsigned int    read_status_mask;  /* driver specific */
  unsigned int    ignore_status_mask;  /* driver specific */
  struct uart_state  *state;      /* pointer to parent state */
  struct uart_icount  icount;      /* statistics */
 
 
  struct console    *cons;      /* struct console, if any */
#if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(SUPPORT_SYSRQ)
  unsigned long    sysrq;      /* sysrq timeout */
#endif
 
 
  /* flags must be updated while holding port mutex */
  upf_t      flags;
//省略宏定义....
 
 
  upstat_t    status;
//省略宏定义....
  int      hw_stopped;    /* sw-assisted CTS flow state */
  unsigned int    mctrl;      /* current modem ctrl settings */
  unsigned int    timeout;    /* character-based timeout */
  unsigned int    type;      /* port type */
  const struct uart_ops  *ops;     //串口操作函数
  unsigned int    custom_divisor;
  unsigned int    line;      /* port index */
  unsigned int    minor;
  resource_size_t    mapbase;    /* for ioremap */
  resource_size_t    mapsize;
  struct device    *dev;      /* parent device */
  unsigned char    hub6;      /* this should be in the 8250 driver */
  unsigned char    suspended;
  unsigned char    unused[2];
  const char    *name;      /* port name */
  struct attribute_group  *attr_group;    /* port specific attributes */
  const struct attribute_group **tty_groups;  /* all attributes (serial core use only) */
  struct serial_rs485     rs485;
  struct serial_iso7816   iso7816;
  void      *private_data;    /* generic platform data pointer */
};

uart_port用于描述一个UART端口的I/O端口或I/O内存地址、FIFO大小、端口类型等信息。这个结构体参数很多,还有很多对串口进行配置的函数。

  • uart_ops
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//物理硬件的所有操作
struct uart_ops {
    //一些操作函数
  unsigned int  (*tx_empty)(struct uart_port *);//判断发送FIFO是否为空
  void    (*set_mctrl)(struct uart_port *, unsigned int mctrl); //设置控制信息
  unsigned int  (*get_mctrl)(struct uart_port *); //获取当前控制信息
  void    (*stop_tx)(struct uart_port *); //停止tx
  void    (*start_tx)(struct uart_port *);//启动tx
  void    (*throttle)(struct uart_port *);//通知串口驱动,线路规程输入缓冲区接近满了
  void    (*unthrottle)(struct uart_port *);//通知串口驱动可以将字符发送到线路规程输入缓冲区
  void    (*send_xchar)(struct uart_port *, char ch); //传输高优先级字符,即使端口已停止。
  void    (*stop_rx)(struct uart_port *); //停止Rx
  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 *); //刷新写buffer,复位DMA
  void    (*set_termios)(struct uart_port *, struct ktermios *new,
               struct ktermios *old); //改变串口参数,包括字长,奇偶校验,停止位。
  void    (*set_ldisc)(struct uart_port *, struct ktermios *); //通知线路规程改变
  void    (*pm)(struct uart_port *, unsigned int state,
            unsigned int oldstate); //电源管理
 
 
   //返回一个描述串口类型的字符串
  const char  *(*type)(struct uart_port *);
    //释放IO和内存资源
  void    (*release_port)(struct uart_port *);
    //申请IO和内存资源
  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
  int    (*poll_init)(struct uart_port *);
  void    (*poll_put_char)(struct uart_port *, unsigned char);
  int    (*poll_get_char)(struct uart_port *);
#endif
};
  • uart_driver是对tty_driver的封装,uart_driver和platform_driver还是有区别的,因为它并没有probe回调函数。它主要是一些字符设备的信息
  • uart_port用来描述具体的串口,主要是一些串口参数
  • uart_ops就是一些串口的操作函数,和字符设备中的file_operations差不多。

API函数

1
2
3
4
5
6
7
8
9
//注册/注销uart_driver到串口核心层
int uart_register_driver(struct uart_driver *drv)
void uart_unregister_driver(struct uart_driver *drv)
 
 
//关联具体串口和驱动
int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
//移除串口和驱动的管理
int uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport)

我们使用到的接口函数很少,所以其实蛮简单的,Linux封装完之后就是填充结构体,然后调用接口注册一下。

 

总结

    首先我们要清楚,在底层,Uart驱动是为每个port都分配了缓存空间的。所以应用层读取的都是缓存空间中的。然后uart_driver不能和platform_driver混淆。后面我们分析实例时会发现Uart的驱动是由platform_driver来回调probe的。之前说过,控制器都是使用platform_driver, 串口对于芯片而言,也是一个控制器。

    分析一大堆代码是不是看着很累,所以千万别全部看,挑重点看,理清思路即可。