转自:http://blog.chinaunix.net/uid-27717694-id-3493611.html

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()以注册UART驱动并添加端口,在模块卸载时调用uart_unregister_driver()和uart_remove_one_port()以注销UART驱动以移除端口。

(3)根据具体硬件的uart_ops中的成员函数,这些函数的实现成为UART驱动的主体工作。
1.串口结构体,他们之间的关系如图所示:


//(1)串口驱动结构体
struct uart_driver {  
    struct module   *owner; //模块所有者   
    const char  *driver_name;   //驱动名   
    const char  *dev_name;  //设备名   
    int  major; //主设备号   
    int  minor; //次设备号   
    int  nr;    //支持串口个数   
    struct console  *cons;//控制台设备   
    struct uart_state   *state; //串口状态   
    struct tty_driver   *tty_driver; //tty设备   
};

//(2)串口端口结构体
struct uart_port {  
    spinlock_t  lock;  
    unsigned long   iobase; //io端口基地址   
    unsigned char __iomem   *membase; //内存端口基地址   
    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    (*pm)(struct uart_port *, unsigned int state,unsigned int old);  
    unsigned int    irq;    //中断号   
    unsigned long   irqflags;   //中断标志   
    unsigned int    uartclk;   //串口时钟
    unsigned int    fifosize;   //fifo大小       
    unsigned char   x_char;  
    unsigned char   regshift;   //寄存器偏移值       
    unsigned char   iotype; //io访问类型   
    unsigned char   unused1;  
    unsigned int    read_status_mask;  
    unsigned int    ignore_status_mask;  
    struct uart_state   *state; //uart_state结构体    
    struct uart_icount  icount; //串口使用计数   
    struct console  *cons;  //console控制台   
#if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(SUPPORT_SYSRQ)   
    unsigned long   sysrq;  
#endif   
    upf_t   flags;  
    unsigned int    mctrl;  
    unsigned int    timeout;  
    unsigned int    type; //串口类型 
    const struct uart_ops   *ops;   //串口操作函数集   
    unsigned int    custom_divisor;  
    unsigned int    line;   //端口号   
    resource_size_t mapbase; //串口寄存器基地址(物理地址)
    struct device   *dev;   //设备文件   
    unsigned char   hub6;  
    unsigned char   suspended;  
    unsigned char   irq_wake;  
    unsigned char   unused[2];  
    void    *private_data;  
};

//(3)操作函数集
struct uart_ops {  
    unsigned int    (*tx_empty)(struct uart_port *);    //发送缓冲区为空   
    void    (*set_mctrl)(struct uart_port *, unsigned int mctrl);   //设置串口modem控制模式   
    unsigned int    (*get_mctrl)(struct uart_port *);   //获取串口modem控制模式   
    void    (*stop_tx)(struct uart_port *); //停止发送   
    void    (*start_tx)(struct uart_port *);    //开始发送   
    void    (*send_xchar)(struct uart_port *, char ch);  
    void    (*stop_rx)(struct uart_port *); //停止接收   
    void    (*enable_ms)(struct uart_port *);   //使能modem状态信息   
    void    (*break_ctl)(struct uart_port *, int ctl);  
    int (*startup)(struct uart_port *); //打开串口   
    void    (*shutdown)(struct uart_port *);    //关闭串口   
    void    (*flush_buffer)(struct uart_port *);  
    void    (*set_termios)(struct uart_port *, struct ktermios *new,struct ktermios *old);  //设置串口参数   
    void    (*set_ldisc)(struct uart_port *, int new);  
    void    (*pm)(struct uart_port *, unsigned int state,unsigned int oldstate);  
    int (*set_wake)(struct uart_port *, unsigned int state);  
    const char *(*type)(struct uart_port *);  
    void    (*release_port)(struct uart_port *);    //释放端口   
    int (*request_port)(struct uart_port *);    //请求端口   
    void    (*config_port)(struct uart_port *, int);    //配置端口   
    int (*verify_port)(struct uart_port *, struct serial_struct *); //校验端口   
    int (*ioctl)(struct uart_port *, unsigned int, unsigned long);  //控制   
#ifdef CONFIG_CONSOLE_POLL   
    void    (*poll_put_char)(struct uart_port *, unsigned char);  
    int (*poll_get_char)(struct uart_port *);  
#endif   
};

//(4)uart_state
struct uart_state {  
    struct tty_port port;  
    int     pm_state;  
    struct circ_buf xmit;  
    struct tasklet_struct   tlet;  
    struct uart_port    *uart_port;//指向对应的串口结构  
};

2.串口初始化
static int __init serial8250_init(void)
{
 int ret;

if (nr_uarts > UART_NR)//UART_NR =3
  nr_uarts = UART_NR;//串口数量最多设为3个

printk(KERN_INFO "Serial: 8250/16550 driver, ""%d ports, IRQ sharing %sabled\n", nr_uarts,share_irqs ? "en" : "dis");

serial8250_reg.nr = UART_NR;//串口数量
 /*
  static struct uart_driver serial8250_reg = {
  .owner   = THIS_MODULE,
  .driver_name  = "serial",
  .dev_name  = "ttyS",
  .major   = TTY_MAJOR,//主设备号是4
  .minor   = 64,
  .cons   = SERIAL8250_CONSOLE,//终端
 };
 
 #define SERIAL8250_CONSOLE &serial8250_console
 
 static struct console serial8250_console = {
  .name  = "ttyS",
  .write  = serial8250_console_write,
  .device  = uart_console_device,
  .setup  = serial8250_console_setup,//设置串口波特率,也就是设置串口。很重要,里面涉及到平台特性,波特率相关。
  .early_setup = serial8250_console_early_setup,
  .flags  = CON_PRINTBUFFER | CON_ANYTIME,
  .index  = -1,
  .data  = &serial8250_reg,
 };
 */
 //函数定义在serial_core.c中
 //注册uart串口驱动,完善uart_driver结构serial8250_reg的uart_state成员及tty_driver成员,并注册tty驱动
 ret = uart_register_driver(&serial8250_reg);
#endif
 if (ret)
  goto out;
 
 //创建一个platform_device结构:serial8250_isa_devs
 serial8250_isa_devs = platform_device_alloc("serial8250",PLAT8250_DEV_LEGACY);
 if (!serial8250_isa_devs) {
  ret = -ENOMEM;
  goto unreg_uart_drv;
 }
 
  //将该结构serial8250_isa_devs注册到总线上
 ret = platform_device_add(serial8250_isa_devs);
 if (ret)
  goto put_dev;
  
 //对uart_8250_port结构serial8250_reg[]初始化,即对3个串口的uart_port结构初始化,并添加端口
 serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);
 
 /*
 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,
  },
 };
 */
 //注册设备,会调用serial8250_probe()。怎样调用的serial8250_probe???????
 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:
#ifdef CONFIG_SPARC
 sunserial_unregister_minors(&serial8250_reg, UART_NR);
#else
 uart_unregister_driver(&serial8250_reg);
#endif
out:
 return ret;
}

//注册串口驱动
int uart_register_driver(struct uart_driver *drv)
{
 struct tty_driver *normal;
 int i, retval;

BUG_ON(drv->state);

//为串口的uart_driver结构分配要指向的uart_state结构的空间*串口数量
 drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);
 if (!drv->state)
  goto out;
 
 //为uart_driver结构分配要指向的tty_driver结构的空间
 /*struct tty_driver *alloc_tty_driver(int lines)
 {
  struct tty_driver *driver;
 
  driver = kzalloc(sizeof(struct tty_driver), GFP_KERNEL);//分配空间
  if (driver) {
   kref_init(&driver->kref);
   driver->magic = TTY_DRIVER_MAGIC;//tty_driver的魔数
   driver->num = lines;//串口数量3个
  }
  return driver;
 }*/
 normal = alloc_tty_driver(drv->nr);
 if (!normal)
  goto out_kfree;

drv->tty_driver = normal;//赋值给uart_driver结构的tty_driver成员
 //对tty_driver成员指向的结构初始化
 normal->owner  = drv->owner;//THIS_MODULE
 normal->driver_name = drv->driver_name;//"serial"
 normal->name  = drv->dev_name;//"ttyS"
 normal->major  = drv->major;//主设备号:TTY_MAJOR=4
 normal->minor_start = drv->minor;//次设备号:64
 normal->type  = TTY_DRIVER_TYPE_SERIAL;//#define TTY_DRIVER_TYPE_SERIAL 0x0003
 normal->subtype  = SERIAL_TYPE_NORMAL;//#define SERIAL_TYPE_NORMAL  1(定义在include/linux/tty_driver.h)
 normal->init_termios = tty_std_termios;//终端的标准配置(drivers/tty/tty_io.c)
 normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;//配置c_cflag
 normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;//波特率设为9600
 normal->flags  = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
 normal->driver_state    = drv;//指向相应的uart_driver
 tty_set_operations(normal, &uart_ops);//将tty_driver结构的normal操作指向uart_ops
 /*void tty_set_operations(struct tty_driver *driver,const struct tty_operations *op)
 {
  driver->ops = op;
 };
 static const struct tty_operations uart_ops = {
  .open  = uart_open,
  .close  = uart_close,
  .write  = uart_write,
  .put_char = uart_put_char,
  .flush_chars = uart_flush_chars,
  .write_room = uart_write_room,
  .chars_in_buffer= uart_chars_in_buffer,
  .flush_buffer = uart_flush_buffer,
  .ioctl  = uart_ioctl,
  .throttle = uart_throttle,
  .unthrottle = uart_unthrottle,
  .send_xchar = uart_send_xchar,
  .set_termios = uart_set_termios,
  .set_ldisc = uart_set_ldisc,
  .stop  = uart_stop,
  .start  = uart_start,
  .hangup  = uart_hangup,
  .break_ctl = uart_break_ctl,
  .wait_until_sent= uart_wait_until_sent,
 #ifdef CONFIG_PROC_FS
  .proc_fops = &uart_proc_fops,
 #endif
  .tiocmget = uart_tiocmget,
  .tiocmset = uart_tiocmset,
  .get_icount = uart_get_icount,
 #ifdef CONFIG_CONSOLE_POLL
  .poll_init = uart_poll_init,
  .poll_get_char = uart_poll_get_char,
  .poll_put_char = uart_poll_put_char,
 #endif
 };
 */

for (i = 0; i < drv->nr; i++) {//根据串口数量依次扫描drv->nr=3
  struct uart_state *state = drv->state + i;//找到每个串口的uart_state地址
  struct tty_port *port = &state->port;//每个串口有一个tty_port结构
  
  //初始化每个串口的tty_port结构
  tty_port_init(port);
  port->ops = &uart_port_ops;
  port->close_delay     = 500; /* .5 seconds */
  port->closing_wait    = 30000; /* 30 seconds */
  tasklet_init(&state->tlet, uart_tasklet_action,(unsigned long)state);
 }
 
 //注册tty驱动
 retval = tty_register_driver(normal);
 if (retval >= 0)
  return retval;

put_tty_driver(normal);
out_kfree:
 kfree(drv->state);
out:
 return -ENOMEM;
}

//初始化串口的uart_port结构
static void __init serial8250_register_ports(struct uart_driver *drv, struct device *dev)
{
 int i;
 
 //对uart_8250_port结构的serial8250_ports进行初始化
 for (i = 0; i < nr_uarts; i++) {//共3个串口
  struct uart_8250_port *up = &serial8250_ports[i];
  up->cur_iotype = 0xFF;//先初始化为0xFF,在set_io_from_upio()会赋值成uart_port->iotype(即UPIO_MEM=2)
 }
 
 //对uart_8250_port结构的serial8250_ports[]接着进行初始化,主要是设置uart_port字段ops的操作
 //由于此函数在初始化console时被调用过,所以现在调用的话会直接返回。即串口的uart_port的ops字段已经赋过值了
 serial8250_isa_init_ports();

for (i = 0; i < nr_uarts; i++) {//共3个串口
  struct uart_8250_port *up = &serial8250_ports[i];

up->port.dev = dev;//指向相应的struct device结构

if (up->port.flags & UPF_FIXED_TYPE)//因为未进行serial8250_probe(),所以此标志未设置,不进入下边函数
   serial8250_init_fixed_type_port(up, up->port.type);//设置串口type=PORT_AR7,即18
  
  //向设备添加端口,是在uart_driver增加一个port,在未进行serial8250_probe()之前,这个函数的串口配置操作会失败
  //进行过serial8250_probe()后,还会再调用此函数,那时就可以配置好串口。
  uart_add_one_port(drv, &up->port);
 }
}

static void __init serial8250_isa_init_ports(void)
{
 struct uart_8250_port *up;
 static int first = 1;
 int i, irqflag = 0;

if (!first)//静态变量,serial8250_console_init()第一次进入这个函数,之后serial8250_init()再进入这个函数就会直接返回
  return;
 first = 0;
 
 //对三个串口的uart_8250_port结构serial8250_ports结构体进行初始化
 for (i = 0; i < nr_uarts; i++) {
  struct uart_8250_port *up = &serial8250_ports[i];

up->port.line = i;//0代表串口0,1代表串口1
  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;
  
  //初始化uart_8250_port指向的uart_port字段port的操作
  up->port.ops = &serial8250_pops;
  /*
  static struct uart_ops serial8250_pops = {
   .tx_empty = serial8250_tx_empty,
   .set_mctrl = serial8250_set_mctrl,
   .get_mctrl = serial8250_get_mctrl,
   .stop_tx = serial8250_stop_tx,
   .start_tx = serial8250_start_tx,
   .stop_rx = serial8250_stop_rx,
   .enable_ms = serial8250_enable_ms,
   .break_ctl = serial8250_break_ctl,
   .startup = serial8250_startup,
   .shutdown = serial8250_shutdown,
   .set_termios = serial8250_set_termios,
   .set_ldisc = serial8250_set_ldisc,
   .pm  = serial8250_pm,
   .type  = serial8250_type,
   .release_port = serial8250_release_port,
   .request_port = serial8250_request_port,
   .config_port = serial8250_config_port,
   .verify_port = serial8250_verify_port,
  #ifdef CONFIG_CONSOLE_POLL
   .poll_get_char = serial8250_get_poll_char,
   .poll_put_char = serial8250_put_poll_char,
  #endif
  };
  */
 }

if (share_irqs)//中断是否共享(这里设置成不共享)
  irqflag = IRQF_SHARED;
 
 //条件不满足,不会进来初始化
 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.irqflags = old_serial_port[i].irqflags;
  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;
  set_io_from_upio(&up->port);
  up->port.irqflags |= irqflag;
  if (serial8250_isa_config != NULL)
   serial8250_isa_config(i, &up->port, &up->capabilities);
*/
 }
}

int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
{
 struct uart_state *state;
 struct tty_port *port;
 int ret = 0;
 struct device *tty_dev;

BUG_ON(in_interrupt());//不能在中断中调用
 
 //Uart_port->line就是对uart设备文件序号.它对应的也就是uart_driver->state数组中的uart_port->line项.
 if (uport->line >= drv->nr)//在serial8250_isa_init_ports()已经初始化过,代表端口号,0代表串口0。
  return -EINVAL;
 
 //根据串口号找到每个串口对应的uart_state结构
 state = drv->state + uport->line;
 port = &state->port;//通过uart_state结构找到每个串口的tty_port结构

mutex_lock(&port_mutex);
 mutex_lock(&port->mutex);
 if (state->uart_port) {
  ret = -EINVAL;
  goto out;
 }
 
 //将uart_state和uart_port结构相关联起来
 state->uart_port = uport; 
 state->pm_state = -1;

uport->cons = drv->cons;//将uart_driver的serial8250_console成员赋值给uart_port成员
 uport->state = state;//uart_port的state成员指向相应的uart_state

//If this port is a console, then the spinlock is already initialised.
  //检查这个串口是否就是终端,并且此终端是否已经注册完毕
 if (!(uart_console(uport) && (uport->cons->flags & CON_ENABLED))) {
  spin_lock_init(&uport->lock);
  lockdep_set_class(&uport->lock, &port_lock_key);
 }
 
 //进行port的自动配置,在未进行serial8250_probe(),串口的port->iobase、port->mapbase、port->membase都为空,所以函数进去会立即返回,即未配置成功。
 //当进行完serial8250_probe()函数时,还会调用uart_add_one_port(),再到这个函数配置时就会配置成功
 uart_configure_port(drv, state, uport);

//然后注册tty_device.如果用户空间运行了udev或者已经配置好了hotplug.就会在/dev下自动生成设备文件了.
 tty_dev = tty_register_device(drv->tty_driver, uport->line, uport->dev);
 if (likely(!IS_ERR(tty_dev))) {//设置设备的唤醒状态
  device_init_wakeup(tty_dev, 1);
  device_set_wakeup_enable(tty_dev, 0);
 } else
  printk(KERN_ERR "Cannot register tty device on line %d\n",uport->line);

//Ensure UPF_DEAD is not set.
 uport->flags &= ~UPF_DEAD;

out:
 mutex_unlock(&port->mutex);
 mutex_unlock(&port_mutex);

return ret;
}

static void uart_configure_port(struct uart_driver *drv, struct uart_state *state,struct uart_port *port)
{
 unsigned int flags;

if (!port->iobase && !port->mapbase && !port->membase)//未调用serial8250_probe()之前,会从这里直接返回。
  return;
 
 //调用serial8250_probe()之后,会接着往下进行
 flags = 0;
 if (port->flags & UPF_AUTO_IRQ)//未设置此标志
  flags |= UART_CONFIG_IRQ;
  
 if (port->flags & UPF_BOOT_AUTOCONF) {//经过probe()函数,此标志已配置
  if (!(port->flags & UPF_FIXED_TYPE)) {//已设置该标志,下边的不会进入
   port->type = PORT_UNKNOWN;//不会进入
   flags |= UART_CONFIG_TYPE;
  }
  port->ops->config_port(port, flags);//调用设备的自动配置函数,即serial8250_config_port()
 }

if (port->type != PORT_UNKNOWN) {
  unsigned long flags;
  
  //打印串口的信息
  uart_report_port(drv, port);

/* Power up port for set_mctrl() */
  uart_change_pm(state, 0);////改变端口的电源状态,上电

spin_lock_irqsave(&port->lock, flags);
  port->ops->set_mctrl(port, port->mctrl & TIOCM_DTR);////设置串口modem控制,调用serial8250_set_mctrl()
  spin_unlock_irqrestore(&port->lock, flags);

//注册终端,配置终端的信息,若此端口有cons字段,并且console的注册不成功。注册成功CON_ENABLED标志为1
  if (port->cons && !(port->cons->flags & CON_ENABLED))
  {
   /*serial8250_console_init()函数会比serial8250_probe()先调用,所以调用register_console的时候,port还没有初始化,所以当
    register_console调用serial8250_console_setup()设置buad,parity bits的时候,
    serial8250_console_setup()会检测port->iobase和port->membase是否是有效值,如果不是就返回,
    放弃初始化console,所以实际上,console不是在serial8250_console_init()里边初始化。
    
    当serial8250_probe()调用uart_add_one_port->uart_configure_port:又会调用register_console(),
    在这里会将真正的console注册掉。
    该函数会检查console有没有初始化,如果没有初始化,则调用register_console来初始化.
    所以console放在这里初始化也是比较好一些.
   */
   register_console(port->cons);//将该console注册到console_drivers链表上,最后调用release_console_sem,将printk缓冲的数据打印到ttyS2上
  }

//检查此串口是否是终端,除了我们使用作为console的串口,其余的进行断电
  if (!uart_console(port))//#define uart_console(port) ((port)->cons && (port)->cons->index == (port)->line)
   uart_change_pm(state, 3);//除了我们使用的console,其余的进行断电
 }
}

//注册设备serial8250_isa_driver时,会调用此函数
static int __devinit serial8250_probe(struct platform_device *dev)
{
 //传入的参数就是da8xx_serial_device
 /*struct platform_device da8xx_serial_device = {//platform_device就描述了设备对象。
  .name = "serial8250",
  .id = PLAT8250_DEV_PLATFORM,
  .dev = {
   .platform_data = da8xx_serial_pdata,//这个platform_device对象的私有数据指成员向一个plat_serial8250_port类型的数组。在这里该数组描述了三个串口接口的基本信息。
                     //当8250驱动检测到这个platform_device对象后,就分析该对象的私有数据成员指向的那个plat_serial8250_port类型的数组。
                     //然后根据该数组的每个成员描述的信息生成一个串口对象设备。
  },
 };
 
 static struct plat_serial8250_port da8xx_serial_pdata[] = {
  {
   .mapbase = DA8XX_UART0_BASE,//串口接口寄存器物理地址的基地址,#define DAVINCI_UART0_BASE (IO_PHYS + 0x20000)(见/arch/arm/mach-davinci/include/mach/serial.h)
   .irq  = IRQ_DA8XX_UARTINT0,//该串口接口使用的中断号, #define IRQ_DA8XX_UARTINT0  25,(在/arch/arm/mach-davinci/include/mach/irqs.h)
   .flags  = UPF_BOOT_AUTOCONF | UPF_SKIP_TEST | UPF_IOREMAP | UPF_FIXED_TYPE,
   .type  = PORT_AR7,//串口0类型
   .iotype  = UPIO_MEM,//成员表示该串口接口寄存器的地址类型,8位的内存地址 
   .regshift = 2,//在访问该串口接口的某个寄存器时,需把该寄存器的号左移多少位然后加基地址(不管是物理或虚拟地址)才能得能到这个寄存器的址址
  },
  {
   .mapbase = DA8XX_UART1_BASE, //DAVINCI_UART1_BASE (IO_PHYS + 0x20400)
   .irq  = IRQ_DA8XX_UARTINT1, //中断号是53
   .flags  = UPF_BOOT_AUTOCONF | UPF_SKIP_TEST |UPF_IOREMAP | UPF_FIXED_TYPE,
   .type  = PORT_AR7,
   .iotype  = UPIO_MEM,
   .regshift = 2,
  },
  {
   .mapbase = DA8XX_UART2_BASE, //#define DAVINCI_UART2_BASE (IO_PHYS + 0x20800)
   .irq  = IRQ_DA8XX_UARTINT2, //中断号是61
   .flags  = UPF_BOOT_AUTOCONF | UPF_SKIP_TEST |UPF_IOREMAP | UPF_FIXED_TYPE,
   .type  = PORT_AR7,
   .iotype  = UPIO_MEM,
   .regshift = 2,
  },
  {
   .flags = 0,
  },
 };*/
 struct plat_serial8250_port *p = dev->dev.platform_data;
 struct uart_port port;
 int ret, i, irqflag = 0;

memset(&port, 0, sizeof(struct uart_port));

if (share_irqs)
  irqflag = IRQF_SHARED;
  
 // 会 将 dev->dev.platform_data 所代表的 port 添加到 uart_driver 中
 for (i = 0; p && p->flags != 0; p++, i++) {//遍历三个串口
  port.iobase  = p->iobase;
  port.membase  = p->membase;
  port.irq  = p->irq;//中断号
  port.irqflags  = p->irqflags;
  port.uartclk  = p->uartclk;//时钟
  port.regshift  = p->regshift;//寄存器偏移量 2
  port.iotype  = p->iotype;//IO类型
  port.flags  = p->flags;//标志
  port.mapbase  = p->mapbase;//串口接口寄存器物理地址的基地址
  port.hub6  = p->hub6;
  port.private_data = p->private_data;
  port.type  = p->type;//PORT_AR7,串口类型
  port.serial_in  = p->serial_in;
  port.serial_out  = p->serial_out;
  port.set_termios = p->set_termios;
  port.pm   = p->pm;
  port.dev  = &dev->dev;
  port.irqflags  |= irqflag;
  if (p->clk)
   serial8250_ports[i].clk = p->clk;
  
  //再重新注册串口
  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;//到这里串口的初始化就结束!!!!
}

int serial8250_register_port(struct uart_port *port)
{
 struct uart_8250_port *uart;
 int ret = -ENOSPC;

if (port->uartclk == 0)
  return -EINVAL;

mutex_lock(&serial_mutex);
 
 //查找在serial8250_ports[]数组中是否已有记录
 uart = serial8250_find_match_or_unused(port);
 if (uart) {//都会查到有记录
  uart_remove_one_port(&serial8250_reg, &uart->port);//把原来的串口移除掉,再重新添加
  
  //串口的uart_port再赋值
  uart->port.iobase       = port->iobase;
  uart->port.membase      = port->membase;
  uart->port.irq          = port->irq;
  uart->port.irqflags     = port->irqflags;
  uart->port.uartclk      = port->uartclk;
  uart->port.fifosize     = port->fifosize;
  uart->port.regshift     = port->regshift;
  uart->port.iotype       = port->iotype;
  uart->port.flags        = port->flags | UPF_BOOT_AUTOCONF;
  uart->port.mapbase      = port->mapbase;
  uart->port.private_data = port->private_data;
  if (port->dev)
   uart->port.dev = port->dev;

if (port->flags & UPF_FIXED_TYPE)//probe()进行时,此标志已设置
   serial8250_init_fixed_type_port(uart, port->type);//设置串口type=PORT_AR7,即18

set_io_from_upio(&uart->port);//设置串口的读写函数
  
  //如果传进来的参数此成员有值,用原来的。实际时原来此成员变量为空,da8xx_serial_pdata变量中未赋值
  if (port->serial_in)
   uart->port.serial_in = port->serial_in;
  if (port->serial_out)
   uart->port.serial_out = port->serial_out;
  if (port->set_termios)
   uart->port.set_termios = port->set_termios;
  if (port->pm)
   uart->port.pm = port->pm;

if (serial8250_isa_config != NULL)
   serial8250_isa_config(0, &uart->port,&uart->capabilities);
  
  //再重新添加串口,这时配置串口就能成功
  ret = uart_add_one_port(&serial8250_reg, &uart->port);
  if (ret == 0)
   ret = uart->port.line;

ret = serial8250_cpufreq_register(uart);
  if (ret < 0)
   printk(KERN_ERR "Failed to add cpufreq notifier\n");
 }
 mutex_unlock(&serial_mutex);

return ret;
}

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;

if (cpu_is_davinci_da850())//是否为达芬奇平台
  up->bugs |= UART_BUG_NOMSR;

ret = serial8250_request_std_resource(up);//分配内存资源,IO资源
 if (ret < 0)
  return;

ret = serial8250_request_rsa_resource(up);//会返回失败
 if (ret < 0)
  probeflags &= ~PROBE_RSA;//清除PROBE_RSA标志
 
 //up->cur_iotype在serial8250_register_ports()设置成0xFF,
 //而up->port.iotype在serial8250_isa_init_ports()被设置为UPIO_MEM,即2
 if (up->port.iotype != up->cur_iotype)
  set_io_from_upio(port);//设置uart_port结构的serial_in函数和serial_out函数

if (flags & UART_CONFIG_TYPE)//此标志没有被设置
  autoconfig(up, probeflags);//不会调用

if (up->port.type == PORT_16550A && up->port.iotype == UPIO_AU)
  up->bugs |= UART_BUG_NOMSR;

if (up->port.type != PORT_UNKNOWN && flags & UART_CONFIG_IRQ)//此标志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);
}

static int serial8250_request_std_resource(struct uart_8250_port *up)
{
 unsigned int size = serial8250_port_size(up);
 int ret = 0;

switch (up->port.iotype) {//up->port.iotype == UPIO_MEM
 case UPIO_AU:
 case UPIO_TSI:
 case UPIO_MEM32:
 case UPIO_MEM:
 case UPIO_DWAPB:
  if (!up->port.mapbase)
   break;

if (!request_mem_region(up->port.mapbase, size, "serial")) {
   ret = -EBUSY;
   break;
  }

if (up->port.flags & UPF_IOREMAP) {//如果前边映射过了,UPF_IOREMAP被清零了,这里就不需再映射了
   up->port.membase = ioremap_nocache(up->port.mapbase,size);
   if (!up->port.membase) {
    release_mem_region(up->port.mapbase, size);
    ret = -ENOMEM;
   }
  }
  break;

case UPIO_HUB6:
 case UPIO_PORT:
  if (!request_region(up->port.iobase, size, "serial"))
   ret = -EBUSY;
  break;
 }
 return ret;
}

static void set_io_from_upio(struct uart_port *p)
{
 struct uart_8250_port *up = (struct uart_8250_port *)p;
 switch (p->iotype) {
 case UPIO_HUB6:
  p->serial_in = hub6_serial_in;
  p->serial_out = hub6_serial_out;
  break;

case UPIO_MEM://p->iotype=2,采用这里的读写函数
  p->serial_in = mem_serial_in;
  p->serial_out = mem_serial_out;
  break;

case UPIO_RM9000:
 case UPIO_MEM32:
  p->serial_in = mem32_serial_in;
  p->serial_out = mem32_serial_out;
  break;

case UPIO_AU:
  p->serial_in = au_serial_in;
  p->serial_out = au_serial_out;
  break;

case UPIO_TSI:
  p->serial_in = tsi_serial_in;
  p->serial_out = tsi_serial_out;
  break;

case UPIO_DWAPB:
  p->serial_in = mem_serial_in;
  p->serial_out = dwapb_serial_out;
  break;

default:
  p->serial_in = io_serial_in;
  p->serial_out = io_serial_out;
  break;
 }
 /* Remember loaded iotype */
 up->cur_iotype = p->iotype;//注意要赋值
}

static inline void uart_report_port(struct uart_driver *drv, struct uart_port *port)
{
 char address[64];

switch (port->iotype) {//port->iotype == UPIO_MEM
 case UPIO_PORT:
  snprintf(address, sizeof(address), "I/O 0x%lx", port->iobase);
  break;
 case UPIO_HUB6:
  snprintf(address, sizeof(address),"I/O 0x%lx offset 0x%x", port->iobase, port->hub6);
  break;
 case UPIO_MEM:
 case UPIO_MEM32:
 case UPIO_AU:
 case UPIO_TSI:
 case UPIO_DWAPB:
  snprintf(address, sizeof(address), "MMIO 0x%llx", (unsigned long long)port->mapbase);
  break;
 default:
  strlcpy(address, "*unknown*", sizeof(address));
  break;
 }
 /*打印出的信息如下:
 serial8250.0: ttyS0 at MMIO 0x1c42000 (irq = 25) is a AR7
 serial8250.0: ttyS1 at MMIO 0x1d0c000 (irq = 53) is a AR7
 serial8250.0: ttyS2 at MMIO 0x1d0d000 (irq = 61) is a AR7
 */
 printk(KERN_INFO "%s%s%s%d at %s (irq = %d) is a %s\n",
        port->dev ? dev_name(port->dev) : "",
        port->dev ? ": " : "",
        drv->dev_name,
        drv->tty_driver->name_base + port->line,
        address, port->irq, uart_type(port));
}

//下边再次调用register_console()注册serial8250_console真正的console终端
void register_console(struct console *newcon)
{
 int i;
 unsigned long flags;
 struct console *bcon = NULL;
 /*
 现在是注册一个serial8250_console,即
 static struct console serial8250_console = {
  .name  = "ttyS",
  .write  = serial8250_console_write,//写方法
  .device  = uart_console_device,//tty驱动
  .setup  = serial8250_console_setup,//设置串口波特率,也就是设置串口。很重要,里面涉及到平台特性,波特率相关。
  .early_setup = serial8250_console_early_setup,
  .flags  = CON_PRINTBUFFER | CON_ANYTIME,
  .index  = -1,
  .data  = &serial8250_reg,
 };
 */
 if (console_drivers && newcon->flags & CON_BOOT) {//注册的是serial8250_console,CON_BOOT没有置位,不是引导控制台。下边不会进去遍历
  for_each_console(bcon) {////遍历全局console_drivers数组   
   if (!(bcon->flags & CON_BOOT)) {//判断是否已经有引导控制台了,有了的话就直接退出
    printk(KERN_INFO "Too late to register bootconsole %s%d\n",newcon->name, newcon->index);
    return;
   }
  }
 }
 
 if (console_drivers && console_drivers->flags & CON_BOOT)//如果注册的是引导控制台,serial8250_console不是引导控制台
  bcon = console_drivers;//这里不执行

if (preferred_console < 0 || bcon || !console_drivers)
  preferred_console = selected_console;//设置preferred_console为uboot命令选择的selected_console(即在Uboot传入的参数“console=ttyS2,115200n8”在console_cmdline[]数组中的索引)   
                     //这里preferred_console =0
 if (newcon->early_setup)//serial8250_console初始化early_setup字段
  newcon->early_setup();//调用serial8250_console_early_setup()

if (preferred_console < 0) {//由于preferred_console =0,不会进入下边
  if (newcon->index < 0)
   newcon->index = 0;
  if (newcon->setup == NULL ||newcon->setup(newcon, NULL) == 0) {
   newcon->flags |= CON_ENABLED;
   if (newcon->device) {
    newcon->flags |= CON_CONSDEV;
    preferred_console = 0;
   }
  }
 }

//传给内核参数:
  //Kernel command line: console=ttyS2,115200n8 rw root=/dev/ram0 initrd=0xc2000000,20M mem=128M ip=192.168.1.220::192.168.1.1:255.255.255.0::eth0:off
  //所以这里将根据传参console=ttyS2,115200来配置作为console的ttyS2串口
 for (i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0];i++) {//遍历全局console_cmdline找到匹配的,i=0就是匹配的“ttyS2”
  if (strcmp(console_cmdline[i].name, newcon->name) != 0)//比较终端名称“ttyS”
   continue;
  if (newcon->index >= 0 &&newcon->index != console_cmdline[i].index)//console_cmdline[i].index=2。//比较次设备号  
   continue;
  if (newcon->index < 0)
   newcon->index = console_cmdline[i].index;//将终端号赋值给serial8250_console->index,这里是2
   
  //console_cmdline[i].options = "115200n8",对于serial8250_console而言setup字段已初始化
  if (newcon->setup && newcon->setup(newcon, console_cmdline[i].options) != 0)//调用serial8250_console_setup()对终端进行配置,未probe()前调用不成功,probe()后调用成功。
   break;
  //在这里注册serial8250_console时,调用serial8250_console_setup()由于port->iobase和port->membase不是有效值,
  //故返回错误,这样下边的操作不会执行,直接break跳出,从flag1出跳出函数。即在这里serial8250_console没有注册成功
  //由于内核在下边的操作队串口进行初始化时,还会调用register_console()来注册serial8250_console,在那时注册就会成功
  
  newcon->flags |= CON_ENABLED; //设置标志为CON_ENABLE,表示console使能(这个在printk调用中使用到) 
  newcon->index = console_cmdline[i].index;//设置索引号   
  if (i == selected_console) { //索引号和uboot指定的console的一样 
   newcon->flags |= CON_CONSDEV;//设置标志CON_CONSDEV(全局console_drivers链表中靠前) 
   preferred_console = selected_console;
  }
  break;
 }//for循环作用大致是查看注册的console是否是uboot知道的引导console,是则设置相关标志和preferred_console

//flag1:
 if (!(newcon->flags & CON_ENABLED))//若前边没有设置CON_ENABLED标志,就退出。若进行过probe(),CON_ENABLED置位,这里就往下接着注册console
  return;

if (bcon && ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV))//防止重复打印   
  newcon->flags &= ~CON_PRINTBUFFER;

acquire_console_sem();
 if ((newcon->flags & CON_CONSDEV) || console_drivers == NULL) {//如果是preferred控制台,此标志CON_CONSDEV在前边已设置过
  newcon->next = console_drivers;
  console_drivers = newcon;//添加进全局console_drivers链表前面位置(printk中会遍历该表调用合适的console的write方法打印信息)
  if (newcon->next)
   newcon->next->flags &= ~CON_CONSDEV;
 } else {//如果不是preferred控制台 
  newcon->next = console_drivers->next;
  console_drivers->next = newcon; //添加进全局console_drivers链表后面位置
 }
 
 //主册console主要是刷选preferred_console放置在全局console_drivers链表前面,剩下的console放置链表靠后的位置,并设置相应的flags,
 //console_drivers最终会在printk函数的层层调用中遍历到,并调用console的write方法将信息打印出来

if (newcon->flags & CON_PRINTBUFFER) {
  spin_lock_irqsave(&logbuf_lock, flags);
  con_start = log_start;
  spin_unlock_irqrestore(&logbuf_lock, flags);
 }
 release_console_sem();

if (bcon && ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV)) {
  printk(KERN_INFO "console [%s%d] enabled, bootconsole disabled\n",newcon->name, newcon->index);
  for_each_console(bcon)
   if (bcon->flags & CON_BOOT)
    unregister_console(bcon);
 } else {//调用这里
  printk(KERN_INFO "%sconsole [%s%d] enabled\n",(newcon->flags & CON_BOOT) ? "boot" : "" ,newcon->name, newcon->index);
 }
}

//serial8250_console_early_setup()-->serial8250_find_port_for_earlycon()
int serial8250_find_port_for_earlycon(void)
{
 struct early_serial8250_device *device = &early_device;//early console初始化时对early_device结构的初始化
 struct uart_port *port = &device->port;
 int line;
 int ret;

if (!device->port.membase && !device->port.iobase)//early_device结构初始化时已经配置好
  return -ENODEV;
 //early console注册时不会调用此函数。
 //当真正的console初始化时,会调用此函数。
 //真正的console初始化时,会查找early console注册时用的是哪一个串口号,从serial8250_ports[]中根据uart_port->mapbase地址来比对
 line = serial8250_find_port(port);//根据uart_port结构找到串口号,比对没有找到串口号,line返回负值
 if (line < 0)
  return -ENODEV;//从这里返回,下边的不再执行
 
 //若找到early console用的串口号,更新当初传入内核参数使用的console_cmdline[i],名称改成ttyS。。。。
 ret = update_console_cmdline("uart", 8250, "ttyS", line, device->options);
 if (ret < 0)
  ret = update_console_cmdline("uart", 0,"ttyS", line, device->options);

return ret;
}

static int __init serial8250_console_setup(struct console *co, char *options)
{
 struct uart_port *port;
 int baud = 9600;
 int bits = 8;
 int parity = 'n';
 int flow = 'n';

if (co->index >= nr_uarts)//console的索引,这里是2,即ttyS2
  co->index = 0;
 port = &serial8250_ports[co->index].port;//找到对应的ttyS2的uart_port结构
 
 //由于console_init在注册serial8250_console时调用的register_console()函数调用serial8250_console_setup()
 //进入这个函数时,由于ttyS2的uart_port结构没有初始化,port->iobase 和port->membase值都未设置,所以直接从下边返回
 //当进行串口初始化时,还会回来注册serial8250_console,再调用到这里,由于设置了ttyS2的uart_port结构,所以下边的配置就会成功
 if (!port->iobase && !port->membase)//第一次注册时,由于未设置,从这里直接返回
  return -ENODEV;

if (options)//如果options不为空,就将options里的数值写给baud, &parity, &bits, &flow
  uart_parse_options(options, &baud, &parity, &bits, &flow);
 //没有配置options,则使用缺省值,否则使用传下来的的参数options里的串口配置
 return uart_set_options(port, co, baud, parity, bits, flow);
}

void uart_parse_options(char *options, int *baud, int *parity, int *bits, int *flow)
{
 char *s = options;

*baud = simple_strtoul(s, NULL, 10);
 while (*s >= '0' && *s <= '9')
  s++;
 if (*s)
  *parity = *s++;
 if (*s)
  *bits = *s++ - '0';
 if (*s)
  *flow = *s;
}

int uart_set_options(struct uart_port *port, struct console *co,int baud, int parity, int bits, int flow)
{
 struct ktermios termios;
 static struct ktermios dummy;
 int i;

//Ensure that the serial console lock is initialised early.
 spin_lock_init(&port->lock);
 lockdep_set_class(&port->lock, &port_lock_key);

memset(&termios, 0, sizeof(struct ktermios));
 termios.c_cflag = CREAD | HUPCL | CLOCAL;

//Construct a cflag setting.
 for (i = 0; baud_rates[i].rate; i++)
  if (baud_rates[i].rate <= baud)
   break;

termios.c_cflag |= baud_rates[i].cflag;

if (bits == 7)
  termios.c_cflag |= CS7;
 else
  termios.c_cflag |= CS8;

switch (parity) {
 case 'o': case 'O':
  termios.c_cflag |= PARODD;
  /*fall through*/
 case 'e': case 'E':
  termios.c_cflag |= PARENB;
  break;
 }

if (flow == 'r')
  termios.c_cflag |= CRTSCTS;

/*
  * some uarts on other side don't support no flow control.
  * So we set * DTR in host uart to make them happy
  */
 port->mctrl |= TIOCM_DTR;

port->ops->set_termios(port, &termios, &dummy);//调用serial8250_set_termios()对串口进行配置

//Allow the setting of the UART parameters with a NULL console too:
 if (co)
  co->cflag = termios.c_cflag;

return 0;
}

struct device *tty_register_device(struct tty_driver *driver, unsigned index,struct device *device)
{
 char name[64];
 dev_t dev = MKDEV(driver->major, driver->minor_start) + index;//得到设备号

if (index >= driver->num) {
  printk(KERN_ERR "Attempt to register invalid tty line number "" (%d).\n", index);
  return ERR_PTR(-EINVAL);
 }

if (driver->type == TTY_DRIVER_TYPE_PTY)
  pty_line_name(driver, index, name);
 else
  tty_line_name(driver, index, name);//得到串口设备名称ttyS0,ttyS1,ttyS2
 
 return device_create(tty_class, device, dev, NULL, name);//在/dev下创建设备文件
}

linux 串口驱动(二)初始化 【转】的更多相关文章

  1. linux串口驱动分析

    linux串口驱动分析 硬件资源及描写叙述 s3c2440A 通用异步接收器和发送器(UART)提供了三个独立的异步串行 I/O(SIO)port,每一个port都能够在中断模式或 DMA 模式下操作 ...

  2. Smart210学习记录------linux串口驱动

    转自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=27025492&id=327609 一.核心数据结构 串口驱动有 ...

  3. linux串口驱动分析——发送数据

    一.应用程序中write函数到底层驱动历程 和前文提到的一样,首先先注册串口,使用uart_register_driver函数,依次分别为tty_register_driver,cdev_init函数 ...

  4. linux串口驱动分析——打开设备

    串口驱动是由tty_driver架构实现的.一个应用程序中的函数要操作硬件,首先会经过tty,级级调用之后才会到达驱动之中.本文先介绍应用程序中打开设备的open函数的整个历程. 首先在串口初始化中会 ...

  5. linux串口驱动分析【转】

    转自:http://blog.csdn.net/hanmengaidudu/article/details/11946591 硬件资源及描述 s3c2440A 通用异步接收器和发送器(UART)提供了 ...

  6. Linux串口驱动程序设计

    1. 在Linux系统中,终端是一类字符型设备,它包括多种类型,通常使用tty来简称各种类型的终端设备. (1)串口终端(/dev/ttyS*):串口终端是使用计算机串口连接的终端设备.Linux把每 ...

  7. linux 串口驱动(三) 【转】

    转自:http://blog.chinaunix.net/uid-27717694-id-3495825.html 三.串口的打开在用户空间执行open操作的时候,就会执行uart_ops->o ...

  8. Android 串口驱动和应用测试

    这篇博客主要是通过一个简单的例子来了解Android的串口驱动和应用,为方便后续对Android串口服务和USB虚拟串口服务的了解.这个例子中,参考了<Linux Device Drivers& ...

  9. Linux 串口、usb转串口驱动分析(2-2) 【转】

    转自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=26807463&id=4186852 Linux 串口.usb转 ...

随机推荐

  1. ns3 myfirst.cc 两个节点点对点通信

    首先在ns3.25/examples/tutorial/下找到 first.cc文件,将他拷贝到到scratch目录下. 然后为了方便将代码打出来 /* -*- Mode:C++; c-file-st ...

  2. 实现项目WC

    软件的需求分析 程序处理用户需求的模式为: wc.exe [parameter][filename] 在[parameter]中,用户通过输入参数与程序交互,需实现的功能如下: 1.基本功能 支持 - ...

  3. squid介绍和作用

    介绍 squid服务程序是一款在Unix系统中最为流行的高性能代理服务软件,通常会被当作网站的前置缓存服务,用于替代用户向网站服务器请求页面数据并进行缓存,通俗来讲,Squid服务程序会接收用户的请求 ...

  4. SpringBoot 1.快速搭建一个 SpringBoot Maven工程

    一.新建一个Maven工程 (1)选择创建简单MAVNE工程 (2)输入你自己的MAVEN工程的Group Id(必填).Artifact Id(必填).Version(必填).Packaging(必 ...

  5. shit aliyun

    shit aliyun 垃圾的 aliyun 什么鬼逻辑,怎么填写都不对,holy shit! 校验规则: // 对不起,昵称包含不可接受字符或被管理员屏蔽,请选择其它昵称 // 昵称的长度必须大于等 ...

  6. VC++ 常见问题及其解决方法

    1. 无法找到“XXX.exe”的调试信息,或者调试信息不匹配: 选择 配置属性->链接器->调试->生成调试信息 改为 是 选择 配置属性->C/C++ ->常规-&g ...

  7. 解决telnet不是内部命令

    1.telnet在win7下默认是不开启的,所以需要我们自己手动开启.那么首先我们点击开始菜单,找到控制面板项,点击进入: 2.进入程序和功能模块,我们在左边需要选择“打开或关闭windows功能”, ...

  8. 【ARC076D/F】Exhausted?

    Description ​ 题目链接 Solution ​ 场上尝试使用优化建图网络流实现,结果T到怀疑人生. ​ 鉴于这是个匹配问题,考虑用贪心做一下. ​ 先退一步,想一下如果每一个人只有\([1 ...

  9. [SDOI2017]硬币游戏

    考虑生成函数来做 g(x)函数就是0+0*x+...+1*x^s+...+|∑|^(n-s)x^n 就是最后s位必须填这个串,但是前面随便填的方案数 然后枚举之前出现了哪个串(包括自己),如果没有相交 ...

  10. Json对象和Json字符串的区别

    说白了,字符串都是带引号的. 尤其是在使用springmvc的时候,后台@RequestBody接受的是一个json格式的字符串,一定是一个字符串.  参考这个博客还可以: https://blog. ...