首页 > 代码库 > Linux kernel 之 uart 驱动解析

Linux kernel 之 uart 驱动解析

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

  • 在Linux kernel 内部,uart 通常是作为 一个 tty 设备对其进行控制,也是就是一个字符设备文件,可对其进行读写操作。

  • kernel version 4.4.12

  • 首先先看一下基本的 结构体 和 API 操作。

    // 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 驱动注册
    intuart_register_driver(struct uart_driver *uart);
    // uart 驱动注销                             
    voiduart_unregister_driver(struct uart_driver *uart); 
    
    intuart_add_one_port(struct uart_driver *reg, struct uart_port *port);         
    intuart_remove_one_port(struct uart_driver *reg, struct uart_port *port);         
    intuart_match_port(struct uart_port *port1, struct uart_port *port2);          
  • 通过实例看一下具体是怎么实现一个完整的 uart 驱动的

    // 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 = http://www.mamicode.com/omap_up_info;                                 "kw">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;                                                             
    }                                                                               

Linux kernel 之 uart 驱动解析