8250 driver

RK3399之8250串口驱动 一、驱动整体框架二、驱动结构体对象三、驱动代码流程四、串口读写流程。 阅读详情

一:前言

前一段时间自己实践了一下8250 芯片串口驱动的编写。今天就在此基础上分析一下 linux kernel  自带的串口驱动。毕竟只有对比专业的驱动代码才能更好的进步 , 同以往一样,基于 linux kernel2.6.25. 相应驱动代码位于: linux- 2.6.25/drivers/serial/8250.c 。 
二:8250 串口驱动初始化 
相应的初始化函数为 serial8250_init(). 代码如下 : 
static int __init serial8250_init(void) 
{ 
     int ret, i;   
     if (nr_uarts > UART_NR) 

         nr_uarts = UART_NR;   
     printk(KERN_INFO "Serial: 8250/16550 driver " 
         "%d ports, IRQ sharing %sabled/n", nr_uarts, 
         share_irqs ? "en" : "dis");   
     for (i = 0; i < NR_IRQS; i++) 
         spin_lock_init(&irq_lists[i].lock);   
     ret = uart_register_driver(&serial8250_reg); 
     if (ret) 
         goto out;   
     serial8250_isa_devs = platform_device_alloc("serial8250", 
                PLAT8250_DEV_LEGACY); 
     if (!serial8250_isa_devs) { 
         ret = -ENOMEM; 
         goto unreg_uart_drv; 
     }   
     ret = platform_device_add(serial8250_isa_devs); 
     if (ret) 
         goto put_dev;   
     serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);   
     ret = platform_driver_register(&serial8250_isa_driver);      

 if (ret == 0) 
         goto out;   
     platform_device_del(serial8250_isa_devs); 
 put_dev: 
     platform_device_put(serial8250_isa_devs); 
 unreg_uart_drv: 
     uart_unregister_driver(&serial8250_reg); 
 out: 
     return ret; 
} 
这段代码涉及到的知识要求,如platform ,uart 等我们在之前都已经做过详细的分析。这里不再重复。在代码中 UART_NR: 表示串口的个数。这个参数在编译内核的时候可以自己配置,默认为 32 。 
我们按照代码中的流程一步一步进行研究。 
1: 注册 uart_driver.  
对应uart-driver 的结构为 serial8250_reg. 定义如下: 
static struct uart_driver serial8250_reg = { 
     .owner             = THIS_MODULE, 
     .driver_name       = "serial", 
     .dev_name     = "ttyS", 
     .major             = TTY_MAJOR, 
     .minor             = 64, 
     .nr           = UART_NR, 
     .cons              = SERIAL8250_CONSOLE, 
}; 

TTY_MAJOR定义如下: 
#define TTY_MAJOR      4 
从上面可以看出。串口对应的设备节点为/dev/ ttyS0 ~ /dev/ ttyS(UART_NR). 设备节点号为( 4,64 )起始的 UART_NR 个节点。  
2:初始化并注册  platform_device 
相关代码如下: 
serial8250_isa_devs = platform_device_alloc("serial8250", PAT8250_DEV_LEGACY); 
platform_device_add(serial8250_isa_devs); 
可以看出。serial8250_isa_devs->name 为 serial8250 。这个参数是在匹配 platform_device 和
platform_driver使用的。  
3:为 uart-driver 添加  port. 
相关代码如下: 
serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev) 
跟进这个函数看一下: 
static void __init 
serial8250_register_ports(struct uart_driver *drv, struct device *dev) 
{     

 int i;   
     serial8250_isa_init_ports();   
     for (i = 0; i < nr_uarts; i++) { 
         struct uart_8250_port *up = &serial8250_ports[i];   
         up->port.dev = dev; 
         uart_add_one_port(drv, &up->port); 
     } 
}

在这里函数里,初始化了port. 然后将它添加到 uart-driver 中。我们还注意到。生成的 deivce 节点,在 sysfs 中是位于  platform_deivce 对应目录的下面 . 
serial8250_isa_init_ports()代码如下所示: 
static void __init serial8250_isa_init_ports(void) 
{ 
     struct uart_8250_port *up; 
     static int first = 1; 
     int i;   
     if (!first) 
         return; 
     first = 0;   
     for (i = 0; i < nr_uarts; i++) { 
         struct uart_8250_port *up = &serial8250_ports[i];   
         up->port.line = i; 
         spin_lock_init(&up->port.lock);   
         init_timer(&up->timer); 
         up->timer.function = serial8250_timeout;   
         /* 
          * ALPHA_KLUDGE_MCR needs to be killed. 
          */ 
         up->mcr_mask = ~ALPHA_KLUDGE_MCR; 
         up->mcr_force = ALPHA_KLUDGE_MCR;   
         up->port.ops = &serial8250_pops; 
     } 

     for (i = 0, up = serial8250_ports; 
          i < ARRAY_SIZE(old_serial_port) && i < nr_uarts;i++, up++) { 
         up->port.iobase = old_serial_port[i].port; 
         up->port.irq = irq_canonicalize(old_serial_port[i].irq); 
         up->port.uartclk  = old_serial_port[i].baud_base * 16; 
         up->port.flags    = old_serial_port[i].flags; 
         up->port.hub6 = old_serial_port[i].hub6; 
         up->port.membase = old_serial_port[i].iomem_base; 
         up->port.iotype = old_serial_port[i].io_type; 
         up->port.regshift  = old_serial_port[i].iomem_reg_shift; 
         if (share_irqs) 

              up->port.flags |= UPF_SHARE_IRQ; 

     } 

}

在这里, 我们关注一下注要成员的初始化。 Uart_port 的各项操作位于 serial8250_pops 中。 Iobase 、 irq 等成员是从 old_serial_por 这个结构中得来的,这个结构如下所示: 
static const struct old_serial_port old_serial_port[] = { 
     SERIAL_PORT_DFNS /* defined in asm/serial.h */ 
} 

#define SERIAL_PORT_DFNS             / 
     /* UART CLK   PORT IRQ     FLAGS   */              / 
     { 0, BASE_BAUD, 0x3F8, 4, STD_COM_FLAGS },     /* ttyS0 */   / 
     { 0, BASE_BAUD, 0x2F8, 3, STD_COM_FLAGS },     /* ttyS1 */   / 
     { 0, BASE_BAUD, 0x3E8, 4, STD_COM_FLAGS },     /* ttyS2 */   / 
     { 0, BASE_BAUD, 0x2E8, 3, STD_COM4_FLAGS },    /* ttyS3 */ 
从上面看到。前两项对应了com1 、 com2 的各项参数。如寄存器首始地址, Irq 号等。后面两项不太清楚。在上面的代码中,我们看到了 uart_port  各项成员的初始化。在后面很多操作中需要用到这个成员。我们等分析相关部份的时候,再到这个地方来看相关成员的值。 
 

4:注册 platform_driver 
相关代码如下: 
platform_driver_register(&serial8250_isa_driver); 
serial8250_isa_driver定义如下 : 
static struct platform_driver serial8250_isa_driver = { 
     .probe =  serial8250_probe, 
     .remove =  __devexit_p(serial8250_remove), 
     .suspend =  serial8250_suspend, 
     .resume =  serial8250_resume, 
     .driver  =  { 
       .name = "serial8250", 
      .owner = THIS_MODULE, 
     }, 
} 

  为了以后把分析集中到具体的驱动部份. 我们先把这个 platform_driver 引出的问题讲述完 . 
经过前面有关platform 的分析我们知道,这个 platform 的 name 为 ” serial8250” 。刚好跟前面注册的 platform_device 相匹配 . 会调用  platform_driver-> probe. 在这里 , 对应的接口为 : 
serial8250_probe().代码如下 : 
static int __devinit serial8250_probe(struct platform_device *dev) 
{ 
     struct plat_serial8250_port  *p =  dev->dev.platform_data; 
     struct uart_port port; 
     int ret, i;   
     memset(&port, 0, sizeof(struct uart_port));   
     for (i = 0; p && p->flags != 0; p++, i++) { 
         port.iobase = p->iobase; 
         port.membase = p->membase; 
         port.irq = p->irq; 
         port.uartclk = p->uartclk; 
         port.regshift = p->regshift; 
         port.iotype = p->iotype; 
         port.flags = p->flags; 
         port.mapbase = p->mapbase; 
         port.hub6 = p->hub6; 
         port.private_data = p->private_data; 
         port.dev = &dev->dev; 
         if (share_irqs) 
              port.flags |= UPF_SHARE_IRQ; 
         ret  =  serial8250_register_port(&port); 
         if (ret < 0) { 
              dev_err(&dev->dev, "unable to register port at index %d " 
                   "(IO%lx MEM%llx IRQ%d): %d/n", i, 
                   p->iobase, (unsigned long long)p->mapbase, 
                   p->irq, ret); 
         } 
     } 
     return 0; 
} 
从上述代码可以看出. 会 将 dev->dev.platform_data 所代表的 port 添加到 uart_driver 中。这个
dev->dev.platform_data 究竟代表什么 . 我们在看到的时候再来研究它 . 
现在, 我们把精力集中到 uart_port 的操作上。

三:config_port  过程 
在初始化uart_port 的过程中 , 在以下代码片段 : 
serial8250_isa_init_ports(void) 
{     ......

for (i = 0, up = serial8250_ports; 
          i < ARRAY_SIZE(old_serial_port) && i < nr_uarts; 
          i++, up++) { 
         up->port.iobase    =  old_serial_port[i].port; 
         up->port.irq       =  irq_canonicalize(old_serial_port[i].irq); 
         up->port.uartclk   =  old_serial_port[i].baud_base * 16; 
         up->port.flags     =  old_serial_port[i].flags; 
         up->port.hub6      =  old_serial_port[i].hub6; 
         up->port.membase   =  old_serial_port[i].iomem_base; 
         up->port.iotype    =  old_serial_port[i].io_type; 
         up->port.regshift  =  old_serial_port[i].iomem_reg_shift; 
         if (share_irqs) 
              up->port.flags |= UPF_SHARE_IRQ; 
     } 
} 
而old_serial_port 又定义如下 : 
static const struct old_serial_port old_serial_port[] = { 
     SERIAL_PORT_DFNS /* defined in asm/serial.h */ 
}; 
#define SERIAL_PORT_DFNS             / 
     /* UART CLK   PORT IRQ     FLAGS        */              / 
     { 0, BASE_BAUD, 0x3F8, 4, STD_COM_FLAGS },     /* ttyS0 */   / 
     { 0, BASE_BAUD, 0x2F8, 3, STD_COM_FLAGS },     /* ttyS1 */   / 
     { 0, BASE_BAUD, 0x3E8, 4, STD_COM_FLAGS },     /* ttyS2 */   / 
     { 0, BASE_BAUD, 0x2E8, 3, STD_COM4_FLAGS },    /* ttyS3 */   
由此可见,port->flags 被定义成了 STD_COM_FLAGS, 定义如下: 
#ifdef CONFIG_SERIAL_DETECT_IRQ 
#define STD_COM_FLAGS (ASYNC_BOOT_AUTOCONF | ASYNC_SKIP_TEST | 
ASYNC_AUTO_IRQ) 
#define STD_COM4_FLAGS (ASYNC_BOOT_AUTOCONF | ASYNC_AUTO_IRQ) 
#else 
#define STD_COM_FLAGS (ASYNC_BOOT_AUTOCONF | ASY NC_SKIP_TEST) 
#define STD_COM4_FLAGS ASYNC_BOOT_AUTOCONF 
#endif 
从这里看到, 不管是否自己探测 IRQ, 都会定义 ASYNC_BOOT_AUTOCONF 。这样 , 在
uart_add_one_port()的时候。就会进入到 port->config_port 来配置端口,在 8250 中 , 对应的接口为 : 
serial8250_config_port(). 代码如下 : 
static void serial8250_config_port(struct uart_port *port, int flags) 
{ 
     struct uart_8250_port *up = (struct uart_8250_port *)port; 
     int probeflags =  PROBE_ANY;      

 int ret;   
     /* 
      * Find the region that we can probe for.  This in turn 
      * tells us whether we can probe for the type of port. 
      */ 
     ret = serial8250_request_std_resource(up); 
     if (ret < 0) 
         return;   
     ret = serial8250_request_rsa_resource(up); 
     if (ret < 0) 
         probeflags &= ~PROBE_RSA;   
     if (flags & UART_CONFIG_TYPE) 
         autoconfig(up, probeflags); 
     if (up->port.type != PORT_UNKNOWN && flags & UART_CONFIG_IRQ) 
         autoconfig_irq(up);   
     if (up->port.type != PORT_RSA && probeflags & PROBE_RSA) 
         serial8250_release_rsa_resource(up); 
     if (up->port.type == PORT_UNKNOWN) 
         serial8250_release_std_resource(up); 
}
serial8250_request_std_resource 和 serial8250_request_rsa_resource 都是分配操作的端口,回
顾在前面的分析中,port 的相关参数会从  old_serial_port 中取得 . 而 old_serial_port 中又没有定义 port->iotype  和 port-> regshift. 也就是说对应这两项全为 0. 而 
#define UPIO_PORT      (0) 
即表示是要操作I/O 端口。自己阅读这两个函数,会发现在 serial8250_request_rsa_resource() 中是会返回失败的。另外 , 在 uart_add_one_port() 在进行端口匹配时 , 会先置 flags 为 UART_CONFIG_TYPE 。这样 , 在本次操作中 , if (flags & UART_CONFIG_TYPE) 是会满足的,相应的就会进入 autoconfig() 。
代码如下, 这段代码比较长 , 分段分析如下 : 
static void autoconfig(struct uart_8250_port *up, unsigned int probeflags) 
{ 
     unsigned char status1, scratch, scratch2, scratch3; 
     unsigned char save_lcr, save_mcr; 
     unsigned long flags;   
     if (!up->port.iobase && !up->port.mapbase && !up->port.membase) 
         return;   
     DEBUG_AUTOCONF("ttyS%d: autoconf (0x%04x, 0x%p): ", 
              up->port.line, up->port.iobase, up->port.membase);   
     /* 
      * We really do need global IRQs disabled here - we're going to 
      * be frobbing the chips IRQ enable register to see if it exists. 
      */ 
     spin_lock_irqsave(&up->port.lock, flags);   
     up->capabilities = 0; 
     up->bugs = 0;   
  if (!(up->port.flags & UPF_BUGGY_UART)) {   
          /* 
          * Do a simple existence test first; if we fail this, 
          * there's no point trying anything else. 
          * 
          * 0x80 is used as a nonsense port to prevent against 
          * false positives due to ISA bus float.  The 
          * assumption is that 0x80 is a non-existent port; 
          * which should be safe since include/asm/io.h also 
          * makes this assumption. 
          * 
          * Note: this is safe as long as MCR bit 4 is clear 
          * and the device is in "PC" mode. 
          */   
          scratch = serial_inp(up, UART_IER); 
         serial_outp(up, UART_IER, 0); 
#ifdef __i386__ 
         outb(0xff, 0x080); 
#endif 
         /* 
          * Mask out IER[7:4] bits for test as some UARTs (e.g. TL 
          * 16C754B) allow only to modify them if an EFR bit is set. 
          */ 
         scratch2 = serial_inp(up, UART_IER) & 0x0f; 
         serial_outp(up, UART_IER, 0x0F); 
#ifdef __i386__ 
         outb(0, 0x080); 
#endif 
         scratch3 = serial_inp(up, UART_IER) & 0x0f; 
         serial_outp(up, UART_IER, scratch); 
         if (scratch2 != 0 || scratch3 != 0x0F) { 
              /*                * We failed; there's nothing here 
               */ 
              DEBUG_AUTOCONF("IER test failed (%02x, %02x) ", 
                         scratch2, scratch3); 
              goto out; 
         } 
     } 
在这里, 先对 8250 是否存在做一个简单的判断 , 先将IER 中的值取得 , 这样可以在测试之后恢复 IER 中的值 , 然后往IER 中写放 0 , 再将IER 中的值取出 , 又往IER 中写入 0xOF. 然后再将 IER 中的值取出 . 最后将 IER 中的值恢复到原值 。 这样就可以根据写入的值和读出的值是否相等来判断该寄存器是否存在.   
     save_mcr = serial_in(up, UART_MCR); 
     save_lcr = serial_in(up, UART_LCR); 
在这里, 先将 MCR 和  LCR 中的值取出 , 因为在后面的操作中会使用这两个寄存器 , 方便使用完了恢复 
     /* 
      * Check to see if a UART is really there.  Certain broken 
      * internal modems based on the Rockwell chipset fail this 
      * test, because they apparently don't implement the loopback 
      * test mode.  So this test is skipped on the COM 1 through 
      * COM 4 ports.  This *should* be safe, since no board 
      * manufacturer would be stupid enough to design a board 
      * that conflicts with COM 1-4 --- we hope! 
      */   
     if (!(up->port.flags & UPF_SKIP_TEST)) { 
         serial_outp(up, UART_MCR, UART_MCR_LOOP | 0x0A); 
         status1 = serial_inp(up, UART_MSR) & 0xF0; 
         serial_outp(up, UART_MCR, save_mcr); 
         if (status1 != 0x90) { 
              DEBUG_AUTOCONF("LOOP test failed (%02x) ", 
                          status1); 
              goto out; 
         } 
     } 
在这里, 将 MCR 的自检位置位 , 并允许向中断控制器产生中断 , 而且产生RTS 信号 , 这样MSR 寄存器应该可以检测到这个信号 . 如果没有检测到 . 自测失 败 !MCR 寄存器已经操作完了 , 恢复 MCR 寄存器的原值 。
     /* 
      * We're pretty sure there's a port here.  Lets find out what 
      * type of port it is.  The IIR top two bits allows us to find 
      * out if it's 8250 or 16450, 16550, 16550A or later.  This 
      * determines what we test for next. 
      * 
      * We also initialise the EFR (if any) to zero for later.  The 
      * EFR occupies the same register location as the FCR and IIR.       */ 
     serial_outp(up, UART_LCR, 0xBF); 
     serial_outp(up, UART_EFR, 0); 
     serial_outp(up, UART_LCR, 0);   
     serial_outp(up, UART_FCR, UART_FCR_ENABLE_FIFO); 
     scratch = serial_in(up, UART_IIR) >> 6;   
     DEBUG_AUTOCONF("iir=%d ", scratch);   
     switch (scratch) { 
     case 0: 
         autoconfig_8250(up); 
         break; 
     case 1: 
         up->port.type = PORT_UNKNOWN; 
         break; 
     case 2: 
         up->port.type = PORT_16550; 
         break; 
     case 3: 
         autoconfig_16550a(up); 
         break; 
     } 
在这里, 先允许使用 FIFO 寄存器 , 然后通过 IIR 寄存的高二位来判断芯片的类型  
#ifdef CONFIG_SERIAL_8250_RSA 
     /* 
      * Only probe for RSA ports if we got the region. 
      */ 
     if (up->port.type == PORT_16550A && probeflags & PROBE_RSA) { 
         int i; 
  
         for (i = 0 ; i < probe_rsa_count; ++i) { 
              if (probe_rsa[i] == up->port.iobase && 
                  __enable_rsa(up)) { 
                   up->port.type = PORT_RSA; 
                   break; 
              } 
         } 
     } 
#endif   
#ifdef CONFIG_SERIAL_8250_AU1X00      /* if access method is AU, it is a 16550 with a quirk */ 
     if (up->port.type == PORT_16550A && up->port.iotype == UPIO_AU) 
         up->bugs |= UART_BUG_NOMSR; 
#endif   
     serial_outp(up, UART_LCR, save_lcr);   
     if (up->capabilities != uart_config[up->port.type].flags) { 
         printk(KERN_WARNING 
                "ttyS%d: detected caps %08x should be %08x/n", 
              up->port.line, up->capabilities, 
              uart_config[up->port.type].flags); 
     }   
     up->port.fifosize = uart_config[up->port.type].fifo_size; 
     up->capabilities = uart_config[up->port.type].flags; 
     up->tx_loadsz = uart_config[up->port.type].tx_loadsz;   
     if (up->port.type == PORT_UNKNOWN) 
         goto out;   
     /* 
      * Reset the UART. 
      */ 
#ifdef CONFIG_SERIAL_8250_RSA 
     if (up->port.type == PORT_RSA) 
         serial_outp(up, UART_RSA_FRR, 0); 
#endif 
     serial_outp(up, UART_MCR, save_mcr); 
     serial8250_clear_fifos(up); 
     serial_in(up, UART_RX); 
     if (up->capabilities & UART_CAP_UUE) 
         serial_outp(up, UART_IER, UART_IER_UUE); 
     else 
         serial_outp(up, UART_IER, 0); 
  
 out: 
     spin_unlock_irqrestore(&up->port.lock, flags); 
     DEBUG_AUTOCONF("type=%s/n", uart_config[up->port.type].name); 
} 
最后, 复位串口控制器,我们假设使用的是 8250 串口芯片 , 在芯片类型判断的时候就会进入autoconfig_8250(). 代码如 下 : 
static void autoconfig_8250(struct uart_8250_port *up) { 
     unsigned char scratch, status1, status2;   
     up->port.type = PORT_8250;   
     scratch = serial_in(up, UART_SCR); 
     serial_outp(up, UART_SCR, 0xa5); 
     status1 = serial_in(up, UART_SCR); 
     serial_outp(up, UART_SCR, 0x5a); 
     status2 = serial_in(up, UART_SCR); 
     serial_outp(up, UART_SCR, scratch);   
     if (status1 == 0xa5 && status2 == 0x5a) 
         up->port.type = PORT_16450; 
} 
如果存在SCR 寄存器 , 则芯片是 16450 类型的 , 这不是我们需要研究的芯片 。   
回到serial8250_config_port() 中 , 代码片 段如下所示 : 
static void serial8250_config_port(struct uart_port *port, int flags) 
{ 
     …… 
     if (flags & UART_CONFIG_TYPE) 
         autoconfig(up, probeflags); 
     if (up->port.type != PORT_UNKNOWN && flags & UART_CONFIG_IRQ) 
         autoconfig_irq(up); 
  
     if (up->port.type != PORT_RSA && probeflags & PROBE_RSA) 
        serial8250_release_rsa_resource(up); 
     if (up->port.type == PORT_UNKNOWN) 
         serial8250_release_std_resource(up); 
} 
如果定义了自己控测IRQ 号 (CONFIG_SERIAL_8250_DETECT_IRQ) , 一般情况下, 编译内核的时候一般都将其赋值为 CONFIG_SERIAL_8250_DETECT_IRQ = y , 此时就会进入autoconfig_irq(). 代码如下 : 
static void autoconfig_irq(struct uart_8250_port *up) 
{ 
     unsigned char save_mcr, save_ier; 
     unsigned char save_ICP = 0; 
     unsigned int ICP = 0; 
     unsigned long irqs; 
     int irq;   
     if (up->port.flags & UPF_FOURPORT) {         

       ICP = (up->port.iobase & 0xfe0) | 0x1f; 
         save_ICP = inb_p(ICP); 
         outb_p(0x80, ICP); 
         (void) inb_p(ICP); 
     }   
     /* forget possible initially masked and pending IRQ */ 
     probe_irq_off(probe_irq_on()); 
     save_mcr = serial_inp(up, UART_MCR); 
     save_ier = serial_inp(up, UART_IER); 
     serial_outp(up, UART_MCR, UART_MCR_OUT1 | UART_MCR_OUT2);   
     irqs = probe_irq_on(); 
     serial_outp(up, UART_MCR, 0); 
     udelay(10); 
     if (up->port.flags & UPF_FOURPORT) { 
         serial_outp(up, UART_MCR, 
                  UART_MCR_DTR | UART_MCR_RTS); 
     } else { 
         serial_outp(up, UART_MCR, 
                  UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2); 
     } 
     serial_outp(up, UART_IER, 0x0f); /* enable all intrs */ 
     (void)serial_inp(up, UART_LSR); 
     (void)serial_inp(up, UART_RX); 
     (void)serial_inp(up, UART_IIR); 
     (void)serial_inp(up, UART_MSR); 
     serial_outp(up, UART_TX, 0xFF); 
     udelay(20); 
     irq = probe_irq_off(irqs);   
     serial_outp(up, UART_MCR, save_mcr); 
     serial_outp(up, UART_IER, save_ier);   
     if (up->port.flags & UPF_FOURPORT) 
         outb_p(save_ICP, ICP);   
     up->port.irq = (irq > 0) ? irq : 0; 
} 
在上述代码的操作中, 先将 8250 相关中断允许寄存器全打开 , 然后调用驱动使用的函数,  当它不得不探测来决定哪个中断线被设备在使用 , probe_irq_on()将中断暂时关掉 , 然后配置 MCR 寄存器使之发送 DTR 和 RTS. 之后再用  probe_irq_off() 来检测 IRQ 号 , 如果检测成功, 则值赋值给 port->irq , 进行到这里,conifg_port 动作就完成了 。 经过这个config_port 过程后 , 我们发现 , 并没有对 serial8250_isa_devs->dev-> platform_data 赋值 , 也就是说 platform_driver->probe 函数并无实质性的处理 , 在第一次for 循环的时 , 就会因条件不符而退出 . 
四: startup 操作 
在前面分析uart 驱动架构的时候 , 曾说过 , 在 open 的时候 , 会调用 port->startup() , 在本次分析的驱动中 , 对应接口为serial8250_startup() 分段分析如下 : 
static int serial8250_startup(struct uart_port *port) 
{ 
     struct uart_8250_port *up = (struct uart_8250_port *)port; 
     unsigned long flags; 
     unsigned char lsr, iir; 
     int retval;   
     up->capabilities = uart_config[up->port.type].flags; 
     up->mcr = 0;   
     if (up->port.type == PORT_16C950) { 
         /* Wake up and initialize UART */ 
         up->acr = 0; 
         serial_outp(up, UART_LCR, 0xBF); 
         serial_outp(up, UART_EFR, UART_EFR_ECB); 
         serial_outp(up, UART_IER, 0); 
         serial_outp(up, UART_LCR, 0); 
         serial_icr_write(up, UART_CSR, 0); /* Reset the UART */ 
         serial_outp(up, UART_LCR, 0xBF); 
         serial_outp(up, UART_EFR, UART_EFR_ECB); 
         serial_outp(up, UART_LCR, 0); 
     }   
#ifdef CONFIG_SERIAL_8250_RSA 
     /* 
      * If this is an RSA port, see if we can kick it up to the 
      * higher speed clock. 
      */ 
     enable_rsa(up); 
#endif   
     /* 
      * Clear the FIFO buffers and disable them. 
      * (they will be reenabled in set_termios()) 
      */ 
     serial8250_clear_fifos(up); 
上面的代码都不是对应8250 芯片的情况 
     /* 
      * Clear the interrupt registers.       */ 
     (void) serial_inp(up, UART_LSR); 
     (void) serial_inp(up, UART_RX); 
     (void) serial_inp(up, UART_IIR); 
     (void) serial_inp(up, UART_MSR); 
复位LSR,RX,IIR,MSR 寄存器   
     /* 
      * At this point, there's no way the LSR could still be 0xff; 
      * if it is, then bail out, because there's likely no UART 
      * here. 
      */ 
     if (!(up->port.flags & UPF_BUGGY_UART) && 
         (serial_inp(up, UART_LSR) == 0xff)) { 
         printk("ttyS%d: LSR safety check engaged!/n", up->port.line); 
         return -ENODEV; 
     } 
若LSR 寄存器中的值为 0xFF. 异常 
     /* 
      * For a XR16C850, we need to set the trigger levels 
      */ 
     if (up->port.type == PORT_16850) { 
         unsigned char fctr;   
         serial_outp(up, UART_LCR, 0xbf);   
         fctr = serial_inp(up, UART_FCTR) & ~(UART_FCTR_RX|UART_FCTR_TX); 
         serial_outp(up, UART_FCTR, fctr | UART_FCTR_TRGD | UART_FCTR_RX); 
         serial_outp(up, UART_TRG, UART_TRG_96); 
         serial_outp(up, UART_FCTR, fctr | UART_FCTR_TRGD | UART_FCTR_TX); 
         serial_outp(up, UART_TRG, UART_TRG_96);   
         serial_outp(up, UART_LCR, 0); 
     } 
16850系列芯片的处理 , 忽略   
     if (is_real_interrupt(up->port.irq)) { /* 
          * Test for UARTs that do not reassert THRE when the 
          * transmitter is idle and the interrupt has already 
          * been cleared.  Real 16550s should always reassert 
          * this interrupt whenever the transmitter is idle and 
          * the interrupt is enabled.  Delays are necessary to 
          * allow register changes to become visible.           */ 
         spin_lock_irqsave(&up->port.lock, flags);   
         wait_for_xmitr(up, UART_LSR_THRE); 
         serial_out_sync(up, UART_IER, UART_IER_THRI); 
          udelay(1); /* allow THRE to set */ 
         serial_in(up, UART_IIR); 
         serial_out(up, UART_IER, 0); 
         serial_out_sync(up, UART_IER, UART_IER_THRI); 
         udelay(1); /* allow a working UART time to re-assert THRE */ 
         iir = serial_in(up, UART_IIR); 
         serial_out(up, UART_IER, 0);   
         spin_unlock_irqrestore(&up->port.lock, flags);   
         /* 
          * If the interrupt is not reasserted, setup a timer to 
          * kick the UART on a regular basis. 
          */ 
         if (iir & UART_IIR_NO_INT) { 
              pr_debug("ttyS%d - using backup timer/n", port->line); 
              up->timer.function = serial8250_backup_timeout; 
              up->timer.data = (unsigned long)up; 
              mod_timer(&up->timer, jiffies + 
                   poll_timeout(up->port.timeout) + HZ / 5); 
         } 
     } 
如果中断号有效, 还要进一步判断这个中断号是否有效 . 具体操作为 , 先等待 8250 发送寄存器空 . 然后允许发送中断空的中断 . 然后判断 IIR 寄存器是 否收到中断 . 如果有没有收到中断 , 则说明这根中断线无效 。 只能采用轮询的方式. 关于轮询方式 , 我们在之后再以独立章节的形式给出分析   
     /* 
      * If the "interrupt" for this port doesn't correspond with any 
      * hardware interrupt, we use a timer-based system.  The original 
      * driver used to do this with IRQ0. 
      */ 
     if (!is_real_interrupt(up->port.irq)) { 
         up->timer.data = (unsigned long)up; 
         mod_timer(&up->timer, jiffies + poll_timeout(up->port.timeout)); 
     } else { 
         retval = serial_link_irq_chain(up); 
         if (retval) 
              return retval; 
     } 如果没有设置中断号 , 则采用轮询方式 . 如果中断后有效 . 流程转入 serial_link_irq_chain(). 在这个里面 .
会注册中断处理函 数.  
     /* 
      * Now, initialize the UART 
      */ 
     serial_outp(up, UART_LCR, UART_LCR_WLEN8);   
     spin_lock_irqsave(&up->port.lock, flags); 
     if (up->port.flags & UPF_FOURPORT) { 
         if (!is_real_interrupt(up->port.irq)) 
              up->port.mctrl |= TIOCM_OUT1; 
     } else 
         /* 
          * Most PC uarts need OUT2 raised to enable interrupts. 
          */ 
         if (is_real_interrupt(up->port.irq)) 
              up->port.mctrl |= TIOCM_OUT2;   
     serial8250_set_mctrl(&up->port, up->port.mctrl);   
     /* 
      * Do a quick test to see if we receive an 
      * interrupt when we enable the TX irq. 
      */ 
     serial_outp(up, UART_IER, UART_IER_THRI); 
     lsr = serial_in(up, UART_LSR); 
     iir = serial_in(up, UART_IIR); 
     serial_outp(up, UART_IER, 0);   
     if (lsr & UART_LSR_TEMT && iir & UART_IIR_NO_INT) { 
         if (!(up->bugs & UART_BUG_TXEN)) { 
              up->bugs |= UART_BUG_TXEN; 
              pr_debug("ttyS%d - enabling bad tx status workarounds/n", 
                    port->line); 
         } 
     } else { 
         up->bugs &= ~UART_BUG_TXEN; 
     }   
     spin_unlock_irqrestore(&up->port.lock, flags);   
     /* 
      * Clear the interrupt registers again for luck, and clear the 
      * saved flags to avoid getting false values from polling       * routines or the previous session. 
      */ 
     serial_inp(up, UART_LSR); 
     serial_inp(up, UART_RX); 
     serial_inp(up, UART_IIR); 
     serial_inp(up, UART_MSR); 
     up->lsr_saved_flags = 0; 
     up->msr_saved_flags = 0; 
  
     /* 
      * Finally, enable interrupts.  Note: Modem status interrupts 
      * are set via set_termios(), which will be occurring imminently 
      * anyway, so we don't enable them here. 
      */ 
     up->ier = UART_IER_RLSI | UART_IER_RDI; 
     serial_outp(up, UART_IER, up->ier);   
     if (up->port.flags & UPF_FOURPORT) { 
  &nbs p;      unsigned int icp; 
         /* 
          * Enable interrupts on the AST Fourport board 
          */ 
         icp = (up->port.iobase & 0xfe0) | 0x01f; 
         outb_p(0x80, icp); 
         (void) inb_p(icp); 
     }   
     return 0; 
} 
最后, 就是对 8250 芯片的初始化了 , 包括: 在 LCR 中设置数据格式 , 在 MCR 中设置允许中断到 8259. 在 IER 中设置相关允许位 。 另外在open 的时候 , 还会调用 port-> enable_ms () 接口 , 在本例中对应为 : 
serial8250_enable_ms(). 代码如下 : 
static void serial8250_enable_ms(struct uart_port *port) 
{ 
     struct uart_8250_port *up = (struct uart_8250_port *)port;   
     /* no MSR capabilities */ 
     if (up->bugs & UART_BUG_NOMSR) 
         return;   
     up->ier |= UART_IER_MSI; 
     serial_out(up, UART_IER, up->ier); 
} 

即允许moden 中断 
  
五: 数据发送的操作 
在uart 驱动架构中分析过 , 在发送数据的时候 ,uart 层先会将数据放入  circ_buffer 。 最后再调用port->start_tx(). 
在这里, 这个接口对应为  serial8250_start_tx(). 代码如下 : 
static void serial8250_start_tx(struct uart_port *port) 
{ 
     struct uart_8250_port *up = (struct uart_8250_port *)port;   
     if (!(up->ier & UART_IER_THRI)) { 
         up->ier |= UART_IER_THRI; 
         serial_out(up, UART_IER, up->ier);   
         if (up->bugs & UART_BUG_TXEN) { 
              unsigned char lsr, iir; 
              lsr = serial_in(up, UART_LSR); 
              up->lsr_saved_flags |= lsr & LSR_SAVE_FLAGS; 
              iir = serial_in(up, UART_IIR) & 0x0f; 
              if ((up->port.type == PORT_RM9000) ? 
                   (lsr & UART_LSR_THRE && 
                   (iir == UART_IIR_NO_INT || iir == UART_IIR_THRI)) : 
                   (lsr & UART_LSR_TEMT && iir & UART_IIR_NO_INT)) 
                   transmit_chars(up); 
         } 
     }   
     /* 
      * Re-enable the transmitter if we disabled it. 
      */ 
     if (up->port.type == PORT_16C950 && up->acr & UART_ACR_TXDIS) { 
         up->acr &= ~UART_ACR_TXDIS; 
         serial_icr_write(up, UART_ACR, up->acr); 
     } 
} 
这个函数非常 简单. 如果没有定义发送空中断 . 则在 IER 中打开这个中断 , 关于TXEN 上的 bug 修复和 16C950 类型的芯片不是我们所关注的部份 . 。 那,  这里只是打开了这个中断 , 写数据到芯片的这个过程是在什么地方完成的呢?  是在中断处理中 , 如果是发送空的中断, 就将 circ buffer 中的数据写出发送寄存器 , 跟踪一下代码 , 中断处理函数为serial8250_interrupt(). 
static irqreturn_t serial8250_interrupt(int irq, void *dev_id) 
{ 
     struct irq_info *i = dev_id; 
     struct list_head *l, *end = NULL;     

 int pass_counter = 0, handled = 0;   
     DEBUG_INTR("serial8250_interrupt(%d)...", irq);   
     spin_lock(&i->lock);   
     l = i->head; 
     do { 
         struct uart_8250_port *up; 
         unsigned int iir;   
         up = list_entry(l, struct uart_8250_port, list);   
         iir = serial_in(up, UART_IIR); 
         if (!(iir & UART_IIR_NO_INT)) { 
              serial8250_handle_port(up);   
              handled = 1;   
              end = NULL; 
         } else if (up->port.iotype == UPIO_DWAPB && 
                (iir & UART_IIR_BUSY) == UART_IIR_BUSY) { 
              /* The DesignWare APB UART has an Busy Detect (0x07) 
               * interrupt meaning an LCR write attempt occured while the 
               * UART was busy. The interrupt must be cleared by reading 
               * the UART status register (USR) and the LCR re-written. */ 
              unsigned int status; 
              status = *(volatile u32 *)up->port.private_data; 
              serial_out(up, UART_LCR, up->lcr);   
              handled = 1;   
              end = NULL; 
         } else if (end == NULL) 
              end = l;   
         l = l->next;   
         if (l == i->head && pass_counter++ > PASS_LIMIT) { 
              /* If we hit this, we're dead. */ 
              printk(KERN_ERR "serial8250: too much work for " 
                   "irq%d/n", irq); 
              break; 
         }      

} while (l != end);   
     spin_unlock(&i->lock);   
     DEBUG_INTR("end./n");   
     return IRQ_RETVAL(handled); 
} 
这里可能有个疑问的地方, 挂在这个链表上的到底是什么 , 这我们要从serial_link_irq_chain() 来说起 。 该函数代码如下: 
static int serial_link_irq_chain(struct uart_8250_port *up) 
{ 
     struct irq_info *i = irq_lists + up->port.irq; 
     int ret, irq_flags = up->port.flags & UPF_SHARE_IRQ ? IRQF_SHARED : 0;   
     spin_lock_irq(&i->lock);   
     if (i->head) { 
         list_add(&up->list, i->head); 
         spin_unlock_irq(&i->lock);   
         ret = 0; 
     } else { 
         INIT_LIST_HEAD(&up->list); 
         i->head = &up->list; 
         spin_unlock_irq(&i->lock);   
         ret = request_irq(up->port.irq, serial8250_interrupt, 
                     irq_flags, "serial", i); 
         if (ret < 0) 
              serial_do_unlink(i, up); 
     }   
     return ret; 
} 
从这里看到, 注册中断处理函数的参数 i  就是对应 irq_lists + up->port.irq. 即对应在 irq_lists 数组中的 port->irq  项 . 随后 , 将注册的 uart_8250_port 添加到了这个链表 。 奇怪了, 为什么要这么做了 ? 我们返回 old_serial_port 的定义 看看 : 
static const struct old_serial_port old_serial_port[] = { 
     SERIAL_PORT_DFNS /* defined in asm/serial.h */ 
}; 
#define SERIAL_PORT_DFNS             / 
     /* UART CLK   PORT IRQ     FLAGS        */              / 
     { 0, BASE_BAUD, 0x3F8, 4, STD_COM_FLAGS },     /* ttyS0 */   /     

 { 0, BASE_BAUD, 0x2F8, 3, STD_COM_FLAGS },     /* ttyS1 */   / 
     { 0, BASE_BAUD, 0x3E8, 4, STD_COM_FLAGS },     /* ttyS2 */   / 
     { 0, BASE_BAUD, 0x2E8, 3, STD_COM4_FLAGS },    /* ttyS3 */ 
在这里, 注意到同一个 IRQ 号会对应两个 port , IRQ中发生中断的时候 , 怎么去判断是哪一个 port 所引起的 , 当然方法有多种多样 。 在这里,8250 驱动的作者是将不同的 port 链入到 IRQ 对应的链表来完成的 , 这样, 如果 IRQ 产生了中断了 , 就判断挂在该链表中的 port, 看中断是否由它产生 。   
经过这个分析之 后, 我们应该很清楚 serial8250_interrupt() 中的处理流程了 。 对应产生IRQ 的 port, 流程会转入  serial8250_handle_port() 中 . 代码如下 : 
static inline void 
serial8250_handle_port(struct uart_8250_port *up) 
{ 
     unsigned int status; 
     unsigned long flags;   
     spin_lock_irqsave(&up->port.lock, flags);   
     status = serial_inp(up, UART_LSR);   
     DEBUG_INTR("status = %x...", status);   
     if (status & UART_LSR_DR) 
         receive_chars(up, &status); 
     check_modem_status(up); 
     if (status & UART_LSR_THRE) 
         transmit_chars(up);   
     spin_unlock_irqrestore(&up->port.lock, flags); 
} 
对于产生中断的情况下, 判断发送缓存区是否为空 , 如果为空 , 就可以发送数据了 , 对应的处理在transmit_chars(up) , 如果接收缓存区满, 就那接收 数据 , 这是在 receive_chars() 中处理的 , 对于接收数据, 我们在下一节再分析 。
transmit_chars()代码如 下 : 
static void transmit_chars(struct uart_8250_port *up) 
{ 
     struct circ_buf *xmit = &up->port.info->xmit; 
     int count;   
     if (up->port.x_char) { 
         serial_outp(up, UART_TX, up->port.x_char); 
         up->port.icount.tx++; 
         up->port.x_char = 0; 
         return; 
     }     

 if (uart_tx_stopped(&up->port)) { 
         serial8250_stop_tx(&up->port); 
         return; 
     } 
     if (uart_circ_empty(xmit)) { 
         __stop_tx(up); 
         return; 
     }   
     count = up->tx_loadsz; 
     do { 
         serial_out(up, UART_TX, xmit->buf[xmit->tail]); 
         xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); 
         up->port.icount.tx++; 
         if (uart_circ_empty(xmit)) 
              break; 
     } while (--count > 0); 
  
     if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) 
         uart_write_wakeup(&up->port);   
     DEBUG_INTR("THRE...");   
     if (uart_circ_empty(xmit)) 
         __stop_tx(up); 
} 
从上面的代码看出 , 会从xmit 中取出数据 , 然后将其写入到发送寄存器中 , 特别的, 在 8250 芯片的情况下 , up->tx_loadsz  等于 1. 也就是说 , 一次只能传送 1 个字节 。 如果缓存区的数据传输 完 了之后, 就会调用  __stop_tx() , 代码如下: 
static inline void __stop_tx(struct uart_8250_port *p) 
{ 
     if (p->ier & UART_IER_THRI) { 
         p->ier &= ~UART_IER_THRI; 
         serial_out(p, UART_IER, p->ier); 
     } 
} 
对应的, 在 IER  中 , 将发送缓存区空的中断关掉 . 
  
六: 数据 读取操作 
在前面的tty  驱动架构分析中 , 曾说过 , 在 tty_driver 中并末提供 read 接口 , 上层的read 操作是直接到 ldsic  的缓存区中读数据的 , 那ldsic 的数据是怎么送入进去的呢 ? 继续看中断处理中的数据接收流程 . 即为 : 
receive_chars().代码片 段如下 : 
static void 
receive_chars(struct uart_8250_port *up, unsigned int *status) { 
     …… 
     …… 
     uart_insert_char(&up->port, lsr, UART_LSR_OE, ch, flag); 
} 
最后流据会转入uart_inset_char() , 这个函数是uart 层提供的一个接口 , 代码如下 : 
static inline void 
uart_insert_char(struct uart_port *port, unsigned int status, 
          unsigned int overrun, unsigned int ch, unsigned int flag) 
{ 
     struct tty_struct *tty = port->info->tty; 
     if ((status & port->ignore_status_mask & ~overrun) == 0) 
         tty_insert_flip_char(tty, ch, flag);   
     /* 
      * Overrun is special.  Since it's reported immediately, 
      * it doesn't affect the current character. 
      */ 
     if (status & ~port->ignore_status_mask & overrun) 
         tty_insert_flip_char(tty, 0, TTY_OVERRUN); 
} 
Tty_insert_filp()函数的代码我们在之前已经分析过 , 这里不再赘述 , 就这样, 数据就直接交给了 ldisc. 
  
七: 轮询操作 
在前面已经分析到, 如果没有定义 irq 或者没有控测到 irq 号 , 就会采用轮询 。 在代码 中 采用定时器的方式去判断是否有数据到来, 或者将数据写入 8250. 定时器对应的运行函数为 serial8250_backup_timeout() , 代码如下: 
static void serial8250_backup_timeout(unsigned long data)
{
     struct uart_8250_port *up = (struct uart_8250_port *)data;
     unsigned int iir, ier = 0, lsr;
     unsigned long flags; 
     /*
      * Must disable interrupts or else we risk racing with the interrupt
      * based handler.
      */
     if (is_real_interrupt(up->port.irq)) {
         ier = serial_in(up, UART_IER);
         serial_out(up, UART_IER, 0);
     } 
     iir = serial_in(up, UART_IIR);  
     /*
      * This should be a safe test for anyone who doesn't trust the
      * IIR bits on their UART, but it's specifically designed for
      * the "Diva" UART used on the management processor on many HP
      * ia64 and parisc boxes.
      */
     spin_lock_irqsave(&up->port.lock, flags);
     lsr = serial_in(up, UART_LSR);
     up->lsr_saved_flags |= lsr & LSR_SAVE_FLAGS;
     spin_unlock_irqrestore(&up->port.lock, flags);
     if ((iir & UART_IIR_NO_INT) && (up->ier & UART_IER_THRI) &&
         (!uart_circ_empty(&up->port.info->xmit) || up->port.x_char) &&
         (lsr & UART_LSR_THRE)) {
         iir &= ~(UART_IIR_ID | UART_IIR_NO_INT);
         iir |= UART_IIR_THRI;
     } 
     if (!(iir & UART_IIR_NO_INT))
         serial8250_handle_port(up); 
     if (is_real_interrupt(up->port.irq))
         serial_out(up, UART_IER, ier); 
     /* Standard timer interval plus 0.2s to keep the port running */
     mod_timer(&up->timer,
         jiffies + poll_timeout(up->port.timeout) + HZ / 5);
}
如果IRQ 线有效 , 先在 IER 中禁用全部中断 , 等定时器处理函数处理完后, 再恢复 IER 中的内容 . 这样主要是为了防止会产生发送缓存区空的中断 , 流程最后还是会转入到serial8250_handle_port() 中 . 这个函数我们在上面已经分析过了 。
 
八: 小结
分析完了这个驱动, 我们可以看到 , 专业的开发人员思维是多么的缜密 , 真是滴水不漏 , 该代码里有很多非常精彩的处理, 需要细细揣摩。

8250驱动使用workqueue加入RS485控制逻辑 项目中使用了RS485,主控是PX30 查看资料,有些前辈的帖子写了用了tasklet 中断服务来做,占资源比较多,而且无法在内部执行一段udelay (看有人这么操作,但是我的系统源码编译出来后会产生互斥锁递归导致系统崩溃)。 于是使用了workqueue来进行延迟检测操作,主要是为了检查是否数据发送完毕,数据发送完后就拉低IO口。 在结构体中新增定义: diff --git a/kernel/include/linux/serial_8250.h b/kernel/include/linu. 阅读详情

相关推荐

RK3568 启动失败

后面测试各路电压,发现VDD_CPU电压不正常,没有输出,后面仔细检查,发现PMIC_SLEEP这个GPIO被拉高了,导致VDD_CPU这个DCDC休眠,卡在usb加载过程,起初以为是EMMC,USB等模块导致,在dts disabled相应模块,依旧没有效果。但是在没有烧入固件的时候,这个VDD_CPU是OK正常的,估计是这个GPIO内部默认下拉,硬件上有个上拉,逻辑设计不合理。

layuetian2011的博客 3828

8250串口控制台的回显处理

static void echo_char(unsigned char c, struct tty_struct *tty) {     mutex_lock(&tty->echo_lock);     if (c == ECHO_OP_START) {         add_echo_byte(ECHO_OP_START, tty);         add_echo_byte(

qwaszx523的博客 1237

linux 8250驱动笔记

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 priva

junior1108的专栏 6113

linux serial framework (2) - serial 8250 driver

uart 1.UART 框架图 2.8250/16550芯片   该类芯片的驱动文件为8250.c,位于内核源码的/drivers/serial/目录下面。serial8250_init为该驱动的入口函数,nr_uarts和UART_NR在内核配置中定义,跟具体的平台相关,通常定义为4。由于CONFIG_SPARC没有被定义,所以做的第一件事情就是注册一个名为serial_8250_reg的UART驱动。 重点分析: serial8250_init()—>uart_register_driv.

weixin_41028621的博客 4722

Linux tty驱动学习 - UART驱动注册

8250/16550系列芯片为常见的UART芯片,该类芯片的驱动文件为8250.c,位于内核源码的/drivers/serial/目录下面。serial8250_init为该驱动的入口函数,nr_uarts和UART_NR在内核配置中定义,跟具体的平台相关,通常定义为4。由于CONFIG_SPARC没有被定义,所以做的第一件事情就是注册一个名为serial_8250_reg的UART驱动。stat

sbctsp的博客 1788

RK3568平台(UART篇)uart_driver 注册流程

串口子系统框架是 Linux 内核中专门用于处理串口设备的模块化框架:在上图中,包含了多个层级,每个层级负责处理不同的功能和任务,从而实现串口设备的 完整驱动和管理。接下来依次介绍每个层级的作用。应用层:位于最顶层,是串口子系统中用户空间应用程序与内核空间之间的接口,应用层 包括了用户空间的串口应用程序,如串口通信工具 minicom 等。字符设备层:位于应用层的下方,负责将用户空间的串口读写请求传递给内核空间的 tty_core 层。

weixin_49303682的博客 583

8250 driver Linux多串口驱动解析 xr16v554

一:前言 前一段时间自己实践了一下8250芯片串口驱动的编写。今天就在此基础上分析一下 linux kernel  自带的串口驱动。毕竟只有对比专业的驱动代码才能更好的进步, 同以往一样,基于linux kernel2.6.25. 相应驱动代码位于: linux-2.6.25/drivers/serial/8250.c。  二:8250串口驱动初始化   相应的初始化函数为

liuhuahan的专栏 2590

ENVI 5.3与IDL 8.5安装指南:从下载到激活的完整流程

本文提供了一份详细的ENVI 5.3与IDL 8.5安装教程,涵盖从获取安装包、运行主程序、安装许可证到关键文件替换的完整流程。指南重点解决了激活过程中的常见问题,并分享了个人实践经验,旨在帮助用户,特别是遥感图像处理初学者,一次性顺利完成软件部署,为后续的科研与开发工作奠定基础。

weixin_29213827的博客 545

linux8250驱动支持5路串口,linux 设备驱动之8250串口驱动分析

linux 设备驱动之 8250 串口驱动------------------------------------------本文系本站原创,欢迎转载!转载请注明出处:http://ericxiao.cublog.cn/------------------------------------------一:前言前一段时间自己实践了一下8250芯片串口驱动的编写。今天就在此基础上分析一下linux ...

weixin_42365422的博客 787

LTspice基础教程-028.Timestep too small

Q: “Timestep too small” error message A: If the LTspice program can’t find a solution for the guessed time step, then the program decreases the time step and tries again. This can’t work forever. If...

gaoyong_wang的博客 2万+

CCS TMSC6748 报错Error connecting to the target: (Error -600 @ 0x0)

CCS TMSC6748 报错Error connecting to the target:(Error -600@ 0x0)

AS_the_deer1717的博客 6634

linux8250驱动支持5路串口,linux设备驱动之8250串口驱动.doc

linux设备驱动之8250串口驱动一:前言前一段时间自己实践了一下8250芯片串口驱动的编写。今天就在此基础上分析一下linux kernel自带的串口驱动。毕竟只有对比专业的驱动代码才能更好的进步,同以往一样,基于linix kernel2.6.25.相应驱动代码位于:linux-2.6.25/drivers/serial/8250.c。二:8250串口驱动初始化相应的初始化函数为serial...

weixin_29967445的博客 445

LFM调制信号分类与检测识别

支持识别LFM信号、间歇采样干扰(ISRJ)、灵巧噪声干扰(SNJ)、扫频干扰(SJ)、瞄准干扰(AJ)、阻塞干扰(BJ)、密集假目标干扰(DFJ)、密集假目标加灵巧噪声干扰(DFWSNJ)、距离欺骗干扰(DDJ)、距离欺骗加灵巧噪声干扰(DDWSNJ)。Faster-RCNN支持检测识别LFM信号、间歇采样干扰(ISRJ)、灵巧噪声干扰(SNJ)、扫频干扰(SJ)、瞄准干扰(AJ)、密集假目标干扰(DFJ)、密集假目标加灵巧噪声干扰(DFWSNJ)、距离欺骗加灵巧噪声干扰(DDWSNJ)等8种信号。

菜yuan~的博客 1029

Linux 8250串口驱动学习

注册uart_drivers,在8250串口驱动中此步骤为第二步在串口驱动框架中,tty层,串口核心层,具体的串口驱动层硬件相关。内核中对应的源码位置为:tty层:drivers/tty/tty_io.c串口核心层:drivers/tty/serial/serial_core.c驱动硬件相关:drivers/tty/serial/imx.c(imx6ull)、drivers/tty/serial/8250/ (8250)

xingchenhx的博客 2906

实现边缘实时动物检测:YOLOv5模型优化与树莓派4B部署全攻略

本文介绍了基于高性能计算机训练YOLOv5目标检测模型,并部署到树莓派4B实现实时动物检测的全流程。主要内容包括:1)在高性能计算机上通过Anaconda创建虚拟环境,安装YOLOv5项目并进行模型训练;2)将训练好的best.pt模型迁移到树莓派,配置轻量级Miniconda环境;3)连接树莓派摄像头,运行实时检测程序。该系统兼顾了训练效率和边缘设备部署的实用性,可广泛应用于智慧农业、生态监测等场景。文中详细说明了数据准备、环境配置和部署步骤,为开发轻量级目标检测系统提供了完整参考方案。

m0_64661770的博客 201

面试提问:数据开发中如何通过指标拆解来指导SQL编写?(附拆解模板)

摘要:指标拆解是一种高效的数据分析方法,通过将复杂业务指标分解为可执行的原子指标,指导SQL查询的编写。核心步骤包括:明确指标定义、拆解原子指标、定位数据源、确定分组维度、构建中间表。文章提供了指标拆解模板和实际案例(如用户留存率计算),并总结了拆解带来的5大好处:逻辑清晰、易于调试、可复用、降低错误率、便于协作。最后强调"一定义、二拆解、三定位、四分步、五合并"的口诀,适用于数仓开发、BI报表等场景。

石榴姐yyds 266

linux 串口 lsr 0xc9,linux设备驱动之8250串口驱动209.doc

linux设备驱动之8250串口驱动209linux设备驱动之8250串口驱动一:前言前一段时间自己实践了一下8250芯片串口驱动的编写。今天就在此基础上分析一下linux kernel自带的串口驱动。毕竟只有对比专业的驱动代码才能更好的进步,同以往一样,基于linix kernel2.6.25.相应驱动代码位于:linux-2.6.25/drivers/serial/8250.c。二:8250串...

weixin_31362539的博客 293

用 OP-TEE 给 AI 模型“上锁”:密文存储、TEE 解密放行、推理后销毁(实战可落地)

本文提出一种基于OP-TEE的AI模型保护方案,通过两级密钥体系确保模型静态存储为密文,运行时仅在TEE内解密。方案采用KEK+DEK架构,DEK永不出TEE,支持分块解密与可验证擦除。该方案能有效防御静态分析、应用层逆向等威胁,但需承认其物理级攻击的局限性。文章详细描述了端到端架构、文件格式设计及TA实现要点,包括密钥管理、分块解密策略和销毁机制,为设备侧AI部署提供了工程可行的安全解决方案。

Interview_TC的博客 288

rk3399启动过程中Serial: 8250/16550 driver, 6 ports, IRQ sharing disabled

内核启动过程中遇到的IRQ共享问题

weixin_45591821的博客 1224

SAP Single Sign-On 全景指南:从传统 SAP SSO 到 SAP Secure Login Service for SAP GUI 的架构、场景与迁移实践

本文系统梳理了SAP生态下的单点登录技术方案,重点分析了SAP GUI的云化替代方案SAP Secure Login Service(SLS)。内容涵盖SNC、Kerberos/SPNEGO、SAML 2.0等核心协议的技术实现,对比了传统SAP SSO与新型SLS for SAP GUI的架构差异,并给出AS ABAP系统的具体实施路径。针对SAP GUI访问、浏览器访问Fiori/移动端等不同场景,提供了详细的协议选型建议和配置指南,帮助团队构建安全可靠且适应云时代的统一认证体系。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 273

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

Linux 驱动UART子系统源码分析

所以 uart 其实就是 16550 芯片, 它是早期 UART 芯片(如 8250)的增强版. 因为 uart 本质是通信协议, 16550 是uart的一种体现uart 就是一份汽车蓝图, 16550 就是一台做好的汽车一个标准的16550芯片只能驱动一个独立的UART串口, 一个“8250驱动”可以驱动无数个16550芯片((例如/dev/ttyS0/dev/ttyS1/dev/ttyS2/dev/ttyS3每个实例对应一个16550芯片)现在很少会看到一颗独立的“16550”芯片。

u011895157的博客 223

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

Linux中tty与8250-uart的虐恋(包括双中断发送接收机制)

Linux中tty与8250-uart的虐恋(包括双中断发送接收机制)

william_myq的博客 1341

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

Linux驱动中platfrom-driver/device注册及probe执行过程分析

1、platform系统 platfrom系统在linux内核中包含三个部分 ①platfrom-bus虚拟总线,是一个内核抽象出来的虚拟的总线,在linux内核中以链表的形式表示,platform-driver/device通过挂载在platform虚拟总线上,实现设备和驱动之间的匹配,在内核表现形式为一系列platform-driver/device结构体组成的链表形式; ②platform-driver模型,内核中抽象出来的driver模型,表现形式为platfrom-driver的结构体,它的

QQ135102692的博客 2485

linux内核串口初始化,linux 串口驱动(二)初始化

8250串口的初始化:(1)定义uart_driver、uart_ops、uart_port等结构体的实例并在适当的地方更具具体的硬件驱动情况初始化他们,当然具体设备xxx的驱动可以将这些结构体套在新定义的xxx_uart_driver、xxx_uart_ops、xxx_uart_port之内。(2)在模块初始化调用uart_register()和uart_add_one_port()以注册UAR...

weixin_39725873的博客 1090
上一篇: 在单机上安装、配置MPI 并行环境(Linux版)
下一篇: 在linux下用rsh实现两个节点的无密码互通
control_tech
博客等级 码龄17年 53粉丝 32原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值