• 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 = 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
      ",          
                  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.
      ", 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
      ",                       
                   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
      ",                   
                   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;                                                             
      }                                                                               
    Read The Fucking Source Code
  • 相关阅读:
    持续集成
    Centos7配置安装及优化
    vi/vim如何添加或删除多行注释.
    [Ansible实战]-批量配置初始化主机环境.
    VW模板机准备
    一次CPU过载报警处理
    [Ansible实战]-免交互批量管理Zabbix
    [Ansible实战]-ansible初始化mysql数据库
    [Ansible实战]-ansible部署Redis-5.x集群
    远程管理服务(SSH).
  • 原文地址:https://www.cnblogs.com/wanghuaijun/p/7017131.html
Copyright © 2020-2023  润新知