Linux kernel 之 uart 驱动解析

  • uart 是一种非常之常见的总线,比如DEBUG信息输出,小数据量数据传输,485,以及蓝牙的控制,GPS,很多都是通过uart 进行数据传输并进行控制。


* #### 在Linux kernel 内部,uart 通常是作为 一个 tty 设备对其进行控制,也是就是一个字符设备文件,可对其进行读写操作。
* #### kernel version 4.4.12 * #### 首先先看一下基本的 结构体 和 API 操作。 ```c // include/linux/serial_core.h

// uart 驱动结构体
struct uart_driver {
struct module *owner;
const char *driver_name; // 驱动名字
const char *dev_name; // 设备名字
int major; // 主设备号
int minor; // 次设备号
int nr;
struct console *cons; // 看似控制台结构体

/*
* these are private; the low level driver should not
* touch these; they should be initialised to NULL
*/
struct uart_state *state; // 状态结构体
struct tty_driver *tty_driver; // tty 驱动结构体
};

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_mctrl)(struct uart_port , unsigned int);
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 );
int (
rs485_config)(struct uart_port *,
struct serial_rs485 rs485);
unsigned int irq; /
irq number /
unsigned long irqflags; /
irq flags /
unsigned int uartclk; /
base uart clock /
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 unused1;

define UPIO_PORT (SERIAL_IO_PORT) /* 8b I/O port access */

define UPIO_HUB6 (SERIAL_IO_HUB6) /* Hub6 ISA card */

define UPIO_MEM (SERIAL_IO_MEM) /* 8b MMIO access */

define UPIO_MEM32 (SERIAL_IO_MEM32) /* 32b little endian */

define UPIO_AU (SERIAL_IO_AU) /* Au1x00 and RT288x type IO */

define UPIO_TSI (SERIAL_IO_TSI) /* Tsi108/109 type IO */

define UPIO_MEM32BE (SERIAL_IO_MEM32BE) /* 32b big endian */

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;

/*
* These flags must be equivalent to the flags defined in
* include/uapi/linux/tty_flags.h which are the userspace definitions
* assigned from the serial_struct flags in uart_set_info()
* [for bit definitions in the UPF_CHANGE_MASK]
*
* Bits [0..UPF_LAST_USER] are userspace defined/visible/changeable
* except bit 15 (UPF_NO_TXEN_TEST) which is masked off.
* The remaining bits are serial-core specific and not modifiable by
* userspace.
/
#define UPF_FOURPORT ((__force upf_t) ASYNC_FOURPORT /
1 / )
#define UPF_SAK ((__force upf_t) ASYNC_SAK /
2 / )
#define UPF_SPD_HI ((__force upf_t) ASYNC_SPD_HI /
4 / )
#define UPF_SPD_VHI ((__force upf_t) ASYNC_SPD_VHI /
5 / )
#define UPF_SPD_CUST ((__force upf_t) ASYNC_SPD_CUST /
0x0030 / )
#define UPF_SPD_WARP ((__force upf_t) ASYNC_SPD_WARP /
0x1010 / )
#define UPF_SPD_MASK ((__force upf_t) ASYNC_SPD_MASK /
0x1030 / )
#define UPF_SKIP_TEST ((__force upf_t) ASYNC_SKIP_TEST /
6 / )
#define UPF_AUTO_IRQ ((__force upf_t) ASYNC_AUTO_IRQ /
7 / )
#define UPF_HARDPPS_CD ((__force upf_t) ASYNC_HARDPPS_CD /
11 / )
#define UPF_SPD_SHI ((__force upf_t) ASYNC_SPD_SHI /
12 / )
#define UPF_LOW_LATENCY ((__force upf_t) ASYNC_LOW_LATENCY /
13 / )
#define UPF_BUGGY_UART ((__force upf_t) ASYNC_BUGGY_UART /
14 / )
#define UPF_NO_TXEN_TEST ((__force upf_t) (1 << 15))
#define UPF_MAGIC_MULTIPLIER ((__force upf_t) ASYNC_MAGIC_MULTIPLIER /
16 */ )

/* Port has hardware-assisted h/w flow control /
#define UPF_AUTO_CTS ((__force upf_t) (1 << 20))
#define UPF_AUTO_RTS ((__force upf_t) (1 << 21))
#define UPF_HARD_FLOW ((__force upf_t) (UPF_AUTO_CTS | UPF_AUTO_RTS))
/
Port has hardware-assisted s/w flow control /
#define UPF_SOFT_FLOW ((__force upf_t) (1 << 22))
#define UPF_CONS_FLOW ((__force upf_t) (1 << 23))
#define UPF_SHARE_IRQ ((__force upf_t) (1 << 24))
#define UPF_EXAR_EFR ((__force upf_t) (1 << 25))
#define UPF_BUG_THRE ((__force upf_t) (1 << 26))
/
The exact UART type is known and should not be probed. */
#define UPF_FIXED_TYPE ((__force upf_t) (1 << 27))
#define UPF_BOOT_AUTOCONF ((__force upf_t) (1 << 28))
#define UPF_FIXED_PORT ((__force upf_t) (1 << 29))
#define UPF_DEAD ((__force upf_t) (1 << 30))
#define UPF_IOREMAP ((__force upf_t) (1 << 31))

define __UPF_CHANGE_MASK 0x17fff

define UPF_CHANGE_MASK ((__force upf_t) __UPF_CHANGE_MASK)

define UPF_USR_MASK ((__force upf_t) (UPF_SPD_MASK|UPF_LOW_LATENCY))

if __UPF_CHANGE_MASK > ASYNC_FLAGS

error Change mask not equivalent to userspace-visible bit defines

endif

/*
* Must hold termios_rwsem, port mutex and port lock to change;
* can hold any one lock to read.
*/
upstat_t status;

define UPSTAT_CTS_ENABLE ((__force upstat_t) (1 << 0))

define UPSTAT_DCD_ENABLE ((__force upstat_t) (1 << 1))

define UPSTAT_AUTORTS ((__force upstat_t) (1 << 2))

define UPSTAT_AUTOCTS ((__force upstat_t) (1 << 3))

define UPSTAT_AUTOXOFF ((__force upstat_t) (1 << 4))

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 irq_wake;
unsigned char unused[2];
struct attribute_group attr_group; / port specific attributes */
const struct attribute_group *tty_groups; / all attributes (serial core use only) */
struct serial_rs485 rs485;
void private_data; / generic platform data pointer */
};

// uart 驱动注册
int uart_register_driver(struct uart_driver *uart);
// uart 驱动注销
void uart_unregister_driver(struct uart_driver *uart);

int uart_add_one_port(struct uart_driver *reg, struct uart_port *port);
int uart_remove_one_port(struct uart_driver *reg, struct uart_port *port);
int uart_match_port(struct uart_port *port1, struct uart_port *port2);


* #### 通过实例看一下具体是怎么实现一个完整的 uart 驱动的
```c
    // drivers/tty/serial/omap-serial.c
    // 在文件最后,还是老套路
    module_init(serial_omap_init);                                                  
    module_exit(serial_omap_exit);                                                  
                                                                                
    MODULE_DESCRIPTION("OMAP High Speed UART driver");                              
    MODULE_LICENSE("GPL");                                                          
    MODULE_AUTHOR("Texas Instruments Inc");  

    // 让我们跟到 serial_omap_init
    static int __init serial_omap_init(void)                                        
    {                                                                               
        int ret;                                                                    
        //  uart 驱动注册                                                                        
        ret = uart_register_driver(&serial_omap_reg);                               
        if (ret != 0)                                                               
            return ret;  
        // uart 平台驱动注册                                                           
        ret = platform_driver_register(&serial_omap_driver);                        
        if (ret != 0)                                                               
            uart_unregister_driver(&serial_omap_reg);                               
        return ret;                                                                 
    } 

    // serial_omap_reg
    static struct uart_driver serial_omap_reg = {                                   
        .owner      = THIS_MODULE,                                                  
        .driver_name    = "OMAP-SERIAL",                                            
        .dev_name   = OMAP_SERIAL_NAME,                                             
        .nr     = OMAP_MAX_HSUART_PORTS,                                            
        .cons       = OMAP_CONSOLE,                                                 
    };   

    // serial_omap_driver
    static struct platform_driver serial_omap_driver = {                            
        .probe          = serial_omap_probe,   // probe 开始函数                                      
        .remove         = serial_omap_remove,                                       
        .driver     = {                                                             
            .name   = DRIVER_NAME,                                                  
            .pm = &serial_omap_dev_pm_ops,                                          
            .of_match_table = of_match_ptr(omap_serial_of_match), // of_match_table 匹配函数                   
        },                                                                          
    }; 

    // probe 函数
    static int serial_omap_probe(struct platform_device *pdev)                      
    {                                                                               
        struct omap_uart_port_info *omap_up_info = dev_get_platdata(&pdev->dev);    
        struct uart_omap_port *up;                                                  
        struct resource *mem;                                                       
        void __iomem *base;                                                         
        int uartirq = 0;                                                            
        int wakeirq = 0;                                                            
        int ret;                                                                    
         
        //  获取相关信息                                                                       
        /* The optional wakeirq may be specified in the board dts file */           
        if (pdev->dev.of_node) {                                                    
            uartirq = irq_of_parse_and_map(pdev->dev.of_node, 0);                   
            if (!uartirq)                                                           
                return -EPROBE_DEFER;                                               
            wakeirq = irq_of_parse_and_map(pdev->dev.of_node, 1);                   
            omap_up_info = of_get_uart_port_info(&pdev->dev);                       
            pdev->dev.platform_data = omap_up_info;                                 
        } else {                                                                    
            uartirq = platform_get_irq(pdev, 0);                                    
            if (uartirq < 0)                                                        
                return -EPROBE_DEFER;                                               
        }                                                                           
                                                                                
        up = devm_kzalloc(&pdev->dev, sizeof(*up), GFP_KERNEL);                     
        if (!up)                                                                    
            return -ENOMEM;                                                         
         
        // 内存,地址                                                                       
        mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);                       
        base = devm_ioremap_resource(&pdev->dev, mem);                              
        if (IS_ERR(base))                                                           
            return PTR_ERR(base);                                                   
                                                                                
        up->dev = &pdev->dev;                                                       
        up->port.dev = &pdev->dev;                                                  
        up->port.type = PORT_OMAP;                                                  
        up->port.iotype = UPIO_MEM;                                                 
        up->port.irq = uartirq;                                                     
        up->port.regshift = 2;                                                      
        up->port.fifosize = 64;                                                     
        up->port.ops = &serial_omap_pops;                                           
                                                                                
        if (pdev->dev.of_node)  // 获取id                                                    
            ret = of_alias_get_id(pdev->dev.of_node, "serial");                     
        else                                                                        
            ret = pdev->id;                                                         
                                                                                
        if (ret < 0) {                                                              
            dev_err(&pdev->dev, "failed to get alias/pdev id, errno %d\n",          
                ret);                                                               
            goto err_port_line;                                                     
        }                                                                           
            up->port.line = ret;                                                        
                                                                              
        if (up->port.line >= OMAP_MAX_HSUART_PORTS) {                               
            dev_err(&pdev->dev, "uart ID %d >  MAX %d.\n", up->port.line,           
                OMAP_MAX_HSUART_PORTS);                                             
            ret = -ENXIO;                                                           
            goto err_port_line;                                                     
        }                                                                           
                                                                                
        up->wakeirq = wakeirq;                                                      
        if (!up->wakeirq)                                                           
            dev_info(up->port.dev, "no wakeirq for uart%d\n",                       
                 up->port.line);                                                    
                                                                                
        ret = serial_omap_probe_rs485(up, pdev->dev.of_node);                       
        if (ret < 0)                                                                
            goto err_rs485;                                                         
        // 设置相关信息                                                                       
        sprintf(up->name, "OMAP UART%d", up->port.line);                            
        up->port.mapbase = mem->start;                                              
        up->port.membase = base;                                                    
        up->port.flags = omap_up_info->flags;                                       
        up->port.uartclk = omap_up_info->uartclk;                                   
        up->port.rs485_config = serial_omap_config_rs485;                           
        if (!up->port.uartclk) {                                                    
            up->port.uartclk = DEFAULT_CLK_SPEED;                                   
            dev_warn(&pdev->dev,                                                    
                 "No clock speed specified: using default: %d\n",                   
                 DEFAULT_CLK_SPEED);                                                
        }                                                                           
                                                                                
        up->latency = PM_QOS_CPU_DMA_LAT_DEFAULT_VALUE;                             
        up->calc_latency = PM_QOS_CPU_DMA_LAT_DEFAULT_VALUE;                        
        pm_qos_add_request(&up->pm_qos_request,                                     
            PM_QOS_CPU_DMA_LATENCY, up->latency);                                   
        INIT_WORK(&up->qos_work, serial_omap_uart_qos_work);                        
                                                                                
        platform_set_drvdata(pdev, up);                                             
        if (omap_up_info->autosuspend_timeout == 0)                                 
            omap_up_info->autosuspend_timeout = -1;                                 
                                                                                
        device_init_wakeup(up->dev, true);                                          
        pm_runtime_use_autosuspend(&pdev->dev);                                     
        pm_runtime_set_autosuspend_delay(&pdev->dev,                                
            omap_up_info->autosuspend_timeout);                                 
                                                                                
        pm_runtime_irq_safe(&pdev->dev);                                            
        pm_runtime_enable(&pdev->dev);                                              
                                                                                
        pm_runtime_get_sync(&pdev->dev);                                            
                                                                                
        omap_serial_fill_features_erratas(up);                                      
                                                                                
        ui[up->port.line] = up;                                                     
        serial_omap_add_console_port(up);                                           
                                                                                
        ret = uart_add_one_port(&serial_omap_reg, &up->port);                       
        if (ret != 0)                                                               
            goto err_add_port;                                                      
                                                                                
        pm_runtime_mark_last_busy(up->dev);                                         
        pm_runtime_put_autosuspend(up->dev);                                        
        // John add.    这个是另外的,不属于原生驱动                                                             
        #define GPIO_TO_PIN(bank, gpio)     (32 * (bank) + (gpio))                      
        devm_gpio_request(up->dev, GPIO_TO_PIN(0,22), "omap-serial");                   
        devm_gpio_request(up->dev, GPIO_TO_PIN(0,23), "omap-serial");                   
        devm_gpio_request(up->dev, GPIO_TO_PIN(0,19), "omap-serial");                   
        devm_gpio_request(up->dev, GPIO_TO_PIN(0,12), "omap-serial");                   
        gpio_direction_output(GPIO_TO_PIN(0,22),1); //COM0_MODE_0=1                 
        gpio_direction_output(GPIO_TO_PIN(0,23),0); //COM0_MODE_1=0                 
        gpio_direction_output(GPIO_TO_PIN(0,19),0); //COM0_TERM=0                   
        gpio_direction_output(GPIO_TO_PIN(0,12),1); //LVDS_BLKT_ON=1                
        return 0;                                                                   
                                                                                
    err_add_port:                                                                   
        pm_runtime_put(&pdev->dev);                                                 
        pm_runtime_disable(&pdev->dev);                                             
        pm_qos_remove_request(&up->pm_qos_request);                                 
        device_init_wakeup(up->dev, false);                                         
    err_rs485:                                                                      
    err_port_line:                                                                  
        return ret;                                                                 
    }                                                                                                                                                                                                                                                                                                                                                            
  • 看一下 uart_register_driver 内部是怎么实现的

    int uart_register_driver(struct uart_driver *drv)                               
    {                                                                               
        struct tty_driver *normal;   // 主要是对这个 tty 驱动结构体进行了初始化。                                                
        int i, retval;                                                              
                                                                                
        BUG_ON(drv->state);                                                         
                                                                                
        /*                                                                          
         * Maybe we should be using a slab cache for this, especially if            
         * we have a large number of ports to handle.                               
         */                                                                         
        drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);      
        if (!drv->state)                                                            
            goto out;                                                               
        // 申请了一个  tty 驱动结构体                                                                      
        normal = alloc_tty_driver(drv->nr);                                         
        if (!normal)                                                                
            goto out_kfree;                                                         
                                                                                
        drv->tty_driver = normal;                                                   
                                                                                
        normal->driver_name = drv->driver_name;                                     
        normal->name        = drv->dev_name;                                        
        normal->major       = drv->major;                                           
        normal->minor_start = drv->minor;                                           
        normal->type        = TTY_DRIVER_TYPE_SERIAL;                               
        normal->subtype     = SERIAL_TYPE_NORMAL;                                   
        normal->init_termios    = tty_std_termios;                                  
        normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;        
        normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;       
        normal->flags       = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;         
        normal->driver_state    = drv;                                              
        tty_set_operations(normal, &uart_ops);                                      
                                                                                
        /*                                                                          
         * Initialise the UART state(s).                                            
         */                                                                         
        for (i = 0; i < drv->nr; i++) {                                             
            struct uart_state *state = drv->state + i;                              
            struct tty_port *port = &state->port;                                   
                                                                                
            tty_port_init(port);                                                    
            port->ops = &uart_port_ops;                                             
        }                                                                           
        // tty  驱动的注册                                                                       
        retval = tty_register_driver(normal);                                       
        if (retval >= 0)                                                            
            return retval;                                                          
                                                                                
        for (i = 0; i < drv->nr; i++)                                               
            tty_port_destroy(&drv->state[i].port);                                  
        put_tty_driver(normal);                                                     
    out_kfree:                                                                      
        kfree(drv->state);                                                          
    out:                                                                            
        return -ENOMEM;                                                             
    }                                                                               
posted @ 2017-05-31 21:47  陈富林  阅读(4154)  评论(0)    收藏  举报