码迷,mamicode.com
首页 > 其他好文 > 详细

u-boot移植(三)---修改前工作:代码流程分析2

时间:2017-06-03 17:24:46      阅读:331      评论:0      收藏:0      [点我收藏+]

标签:blog   立即数   返回   ase   before   ant   mmu   stat   sub   

一、vectors.S

1.1 代码地址

  vectors.S (arch\arm\lib) 

1.2 流程跳转

  技术分享  技术分享

  跳转符号 B 为 start.S 中的 reset 执行代码,暂且先不看,先看看 vector.S 中的执行。

1.3 代码分析

  ldr{条件}   目的寄存器     <存储器地址>
 1 _start:
 2 
 3 #ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
 4     .word    CONFIG_SYS_DV_NOR_BOOT_CFG
 5 #endif
 6     /* LDR{条件}   目的寄存器     <存储器地址> */
 7     /* 当异常发生的时候,由硬件机制处理器自动的跳到一个固定地址去执行相关异常处理程序,而这个固定地址就是所谓的异常向量。 */
 8     b    reset                        /* 跳转到reset执行 0x00000000 复位异常*/
 9     ldr    pc, _undefined_instruction    /* 0x00000004 未定义指令异常 */
10     ldr    pc, _software_interrupt        /* 0x00000008 软中断异常 */
11     ldr    pc, _prefetch_abort            /* 0x0000000c 预取异常 */
12     ldr    pc, _data_abort                /* 0x00000010 数据异常 */
13     ldr    pc, _not_used                /* 0x00000014 未使用异常,多余的指令 */
14     ldr    pc, _irq                    /* 0x00000018 外部中断异常 */
15     ldr    pc, _fiq                    /* 0x0000001c 快速中断异常 */

  这里的代码全部都是异常向量表的定义,第一段代码中,执行 b   reset ,跳转到reset执行。

  reset 我们的 start.S 代码中。

二、start.S

2.1 跳转流程

  技术分享  

2.2 代码流程

  技术分享

 

2.3 代码分析 

  start.S (arch\arm\cpu\arm920t) 

2.3.1 指令介绍

  • .global symbol .global 使得链接程序(ld)能够识别 symbol,声明symbol是全局可见的
  • ARM模式切换指令:
    • mrs(Move to Register from State register):指令可以对状态寄存器CPSR和SPSR进行读操作。通过读CPSR可以获得当前处理器的工作状态。读SPSR寄存器可以获得进入异常前的处理器状态(因为只有异常模式下有SPSR寄存器)。
    • msr:MSR指令可以对状态寄存器CPSR和SPSR进行写操作。
  • bic:(位清除)指令对 Rn 中的值 和 Operand2 值的反码按位进行逻辑“与”运算。BIC 是 逻辑”与非” 指令, 实现的 Bit Clear的功能。
  • orr:用于在两个操作数上进行逻辑或运算,并把结果放置到目的寄存器中。
    • ORR{条件}{S} 目的寄存器,操作数 1,操作数 2
  • 数据传输指令:LDR,STR。LDR和STR的第一操作数是目标寄存器,第二操作数是内存地址。
    • LDR  内存--->寄存器
    • STR 寄存器--->内存
    • 内存的表示方式有:立即数,寄存器,或寄存器加偏移
      • 立即数:内存的物理地址,前面加个#
      • 寄存器:加个[],如[r1]
      • 寄存器加偏移:[r1,r2]  [r2, #4] , [r2, LSL, #4]等,把寄存器里的数当成地址  
    • mvn指令:与mov指令相似,唯一的区别是赋值的时候先按位取反 
  • 协处理器指令:MCR,MRC
    • MCR:将ARM处理器的寄存器中的数据传送到协处理器的寄存器中。如果协处理器不能成功地执行该操作,将产生未定义的指令异常中断。指令的语法格式:
      • MCR{<cond>} p15, 0, <Rd>, <CRn>, <CRm>{,<opcode_2>}
      • MCR2 p15, 0, <Rd>, <CRn>, <CRm>{,<opcode_2>}
      • <cond>为指令执行的条件码。当<cond>忽略时指令为无条件执行。MCR2中,<cond>为Ob1111,指令为无条件执行指令。 
    • MRC:将协处理器的寄存器中数值传送到ARM处理器的寄存器中。如果协处理器不能成功地执行该操作,将产生未定义的指令异常中断。        

2.3.2 代码分析

2.3.2.1 u-boot.dis分析

  u-boot.dis分析

  代码跳转到reset,reset的地址为 2e8:

  技术分享

  在2e8处执行reset的代码,此段代码是对CPSR寄存器进行操作

  技术分享

  CPSR寄存器主要是用来CPU切换用户模式和系统模式,寄存器的定义如下:

  技术分享

  • 中断禁止位I、F:置1时,禁止IRQ中断和FIQ中断。
  • T标志位:该位反映处理器的运行状态。当该位为1时,程序运行于THUMB状态,否则运行于ARM状态。该信号反映在外部引脚TBIT上。在程序中不得修改CPSR中的TBIT位,否则处理器工作状态不能确定。
M[4:0] 处理器模式 ARM模式可访问的寄存器 THUMB模式可访问的寄存器
0b10000 用户模式 PC,CPSR,R0~R14 PC,CPSR,R0~R7,LR,SP
0b10001 FIQ模式 PC,CPSR,SPSR_fiq,R14_fiq~R8_fiq,R0~R7 PC,CPSR,SPSR_fiq,LR_fiq,SP_fiq,R0~R7
0b10010 IRQ模式 PC,CPSR,SPSR_irq,R14_irq~R13_irq,R0~R12 PC,CPSR,SPSR_irq,LR_irq,SP_irq,R0~R7
0b10011 管理模式 PC,CPSR,SPSR_svc,R14_svc~R13_svc,R0~R12 PC,CPSR,SPSR_svc,LR_svc,SP_svc,R0~R7
0b10111 中止模式 PC,CPSR,SPSR_abt,R14_abt~R13_abt,R0~R12 PC,CPSR,SPSR_abt,LR_abt,SP_abt,R0~R7
0b11011 未定义模式 PC,CPSR,SPSR_und,R14_und~R13_und,R0~R12 PC,CPSR,SPSR_und,LR_und,SP_und,R0~R7
0b11111 系统模式 PC,CPSR,R0~R14 PC,CPSR,LR,SP,R0~R74

  

 

 

 

 

 

  CPSR寄存器的M4~M0位为模式位,所以将CPSR的值放入R0寄存器中后,就是将R0的值的低5位进行清零,然后与 1101 0011进行相互,则进入管理模式。

  关闭看门狗:

  技术分享

  看门狗寄存器

  技术分享

 

  屏蔽中断,代码中并没有234行和236行的执行,这里是跳转到 地址位 35c 去执行打印,暂且先不管这两行,只要知道都是中断屏蔽的代码,留待后续分析:

  技术分享

  技术分享

  技术分享

  中断屏蔽寄存器:

  技术分享

 

  设置启动参数:时钟

   技术分享

   时钟分频寄存器

  技术分享

 

  紧接着跳转到地址 328 去执行 cpu_init_crit,执行DRAM的初始化:

  技术分享

  技术分享

  这里涉及到CP15寄存器,CP15是用于系统存储管理的协处理器,操作指令为: 

  MCR{cond}     coproc,opcode1,Rd,CRn,CRm,opcode2
  MRC {cond}    coproc,opcode1,Rd,CRn,CRm,opcode2 
  • coproc         指令操作的协处理器名.标准名为pn,n,为0~15 
  • opcode1      协处理器的特定操作码. 对于CP15寄存器来说,opcode1永远为0,不为0时,操作结果不可预知
  • CRd             作为源寄存器的ARM寄存器,其值被传送到协处理器寄存器中。CRd不能为PC,当其为PC时,指令操作结果不可预知。
  • CRn             存放第1个操作数的协处理器寄存器。作为目标寄存器的协处理器寄存器,其编号可能为C0,C1....C15。
  • CRm            存放第2个操作数的协处理器寄存器. (用来区分同一个编号的不同物理寄存器,当不需要提供附加信息时,指定为C0)
  • opcode2       提供附加信息,可选的协处理器特定操作码. (用来区分同一个编号的不同物理寄存器,当不需要提供附加信息时,指定为0)

   cpu_init_crit 涉及到的协处理器的寄存器有C0,C1,C7,C8:

  【1】C0寄存器

  技术分享

  技术分享

  技术分享

  技术分享

  技术分享

  技术分享

  【2】C1寄存器

  技术分享

  技术分享

  技术分享

C1中的控制位 含义
M(bit[0])

0 :禁止 MMU 或者 PU 

1 :使能 MMU 或者 PU

如果系统中没有MMU及PU,读取时该位返回0,写入时忽略该位

A(bit[1])

0 :禁止地址对齐检查

1 :使能地址对齐检查

C(bit[2])

当数据cache和指令cache分开时,本控制位禁止/使能数据cache。当数据cache和指令cache统一时,该控制位禁止/使能整个cache。

0 :禁止数据 / 整个 cache 

1 :使能数据 / 整个 cache

如果系统中不含cache,读取时该位返回0.写入时忽略

当系统中不能禁止cache 时,读取时返回1.写入时忽略

W(bit[3])

0 :禁止写缓冲

1 :使能写缓冲

如果系统中不含写缓冲时,读取时该位返回0.写入时忽略

当系统中不能禁止写缓冲时,读取时返回1.写入时忽略

P(bit[4])

对于向前兼容26位地址的ARM处理器,本控制位控制PROG32控制信号

0 :异常中断处理程序进入 32 位地址模式

1 :异常中断处理程序进入26 位地址模式

如果本系统中不支持向前兼容26位地址,读取该位时返回1,写入时忽略

D(bit[5]

对于向前兼容26位地址的ARM处理器,本控制位控制DATA32控制信号

0 :禁止 26 位地址异常检查

1 :使能 26 位地址异常检查

如果本系统中不支持向前兼容26位地址,读取该位时返回1,写入时忽略

L(bit[6]

对于ARMv3及以前的版本,本控制位可以控制处理器的中止模型

0 :选择早期中止模型

1 :选择后期中止模型

B(bit[7])

对于存储系统同时支持big-endian和little-endian的ARM系统,本控制位配置系统的存储模式

0 : little endian  

1 : big endian

对于只支持little-endian的系统,读取时该位返回0,写入时忽略

对于只支持big-endian的系统,读取时该位返回1,写入时忽略

S(bit[8]) 在基于 MMU 的存储系统中,本位用作系统保护
R(bit[9]) 在基于 MMU 的存储系统中,本位用作 ROM 保护
F(bit[10]) 由生产商定义
Z(bit[11])

对于支持跳转预测的ARM系统,本控制位禁止/使能跳转预测功能

0 :禁止跳转预测功能 

1 :使能跳转预测功能

对于不支持跳转预测的ARM系统,读取该位时返回0,写入时忽略

I(bit[12])

当数据cache和指令cache是分开的,本控制位禁止/使能指令cache

0 :禁止指令 cache  

1 :使能指令 cache

如果系统中使用统一的指令cache和数据cache或者系统中不含cache,读取该位时返回0,写入时忽略。当系统中的指令cache不能禁止时,读取时该位返回1,写入时忽略

V(bit[13]

对于支持高端异常向量表的系统,本控制位控制向量表的位置

0 :选择低端异常中断向量 0x0~0x1c 

1 :选择高端异常中断向量0xffff0000~ 0xffff001c

对于不支持高端异常向量表的系统,读取时该位返回0,写入时忽略

PR(bit[14])

如果系统中的cache的淘汰算法可以选择的话,本控制位选择淘汰算法

0 :常规的 cache 淘汰算法,如随机淘汰 

1 :预测性淘汰算法,如round-robin 淘汰算法

如果系统中cache的淘汰算法不可选择,写入该位时忽略。读取该位时,根据其淘汰算法是否可以比较简单地预测最坏情况返回0或者1

L4(bit[15])

对于ARM版本5及以上的版本,本控制位可以提供兼容以前的ARM版本的功能

0 :保持 ARMv5 以上版本的正常功能

1 :将 ARMv5 以上版本与以前版本处理器 兼容,不根据跳转地址的 bit[0] 进行 ARM 指令和 Thumb 状态切换: bit[0] 等于 0 表示 ARM 指令,等于 1 表示 Thumb 指令

Bits[31:16]) 这些位保留将来使用,应为UNP/SBZP

 

 

 

 

 

 

 

  

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  【3】C7寄存器

  技术分享

  技术分享

  【4】C8寄存器

  技术分享

  技术分享

  执行完cache和MMU的设置后,则跳转到lowlevel_init(lowlevel_init.S (arch\arm\cpu\arm920t\ep93xx) )中执行。

   技术分享

  执行完内存初始化后,则执行_main()函数。

2.3.2.2 代码执行

  start.S

  

  1     .globl    reset
  2 
  3 reset:
  4     /*
  5      * set the cpu to SVC32 mode
  6      * 设置 CPU 为管理员模式
  7      */
  8     mrs    r0, cpsr            /* 将CPSR状态寄存器读取保存在r0中 */
  9     bic    r0, r0, #0x1f        /* 清除r0中数据的低5位,然后存放在r0中 */
 10     orr    r0, r0, #0xd3        /* r0的数据与 1101 0011 相或,然后存放在r0中 */
 11     msr    cpsr, r0            /* 将r0中的数据存放在cpsr寄存器中 */
 12 
 13 
 14     /* 定义控制寄存器地址 */
 15 #  define pWTCON    0x53000000    /* 看门狗地寄存器地址 */
 16 #  define INTMSK    0x4A000008    /* Interrupt-Controller base addresses */
 17 #  define INTSUBMSK    0x4A00001C    /* 中断掩码 */
 18 #  define CLKDIVN    0x4C000014    /* clock divisor register */
 19 
 20     /* 关闭看门狗 */
 21     ldr    r0, =pWTCON                /* r0中存放看门狗寄存器地址 */
 22     mov    r1, #0x0                /* 将立即数0存放到r1中,r1 = 0x0 */
 23     str    r1, [r0]                /* 将r1中的值存放到以r0中的值为地址的存储单元中,即 pwTCON = 0 */
 24 
 25     /*
 26      * mask all IRQs by setting all bits in the INTMR - default
 27      * 屏蔽中断
 28      */
 29     mov    r1, #0xffffffff            /* 将立即数 0xffffffff 存放到r1中*/
 30     ldr    r0, =INTMSK             /* r0中存放中断控制寄存器基地址 */
 31     str    r1, [r0]                /* 将r1中的值存放到以r0中的值为地址的存储单元中,即 INTMSK = 0xffffffff */
 32 
 33     /* FCLK:HCLK:PCLK = 1:2:4 */
 34     /* default FCLK is 120 MHz ! */
 35     /* 设置启动参数:时钟 */
 36     ldr    r0, =CLKDIVN            /* r0中存放时钟寄存器地址 */
 37     mov    r1, #3                    /* 将立即数0存放到r1中,r1 = 0x3 */
 38     str    r1, [r0]                /* 将r1中的值存放到以r0中的值为地址的存储单元中,即 CLKDIVN = 0 */
 39 
 40     /*
 41      * we do sys-critical inits only at reboot,
 42      * not when booting from ram!
 43      */
 44 #ifndef CONFIG_SKIP_LOWLEVEL_INIT
 45     /*  执行CPU初始化,完成DRAM初始化操作,跳转到 cpu_init_crit 执行 */
 46     bl    cpu_init_crit
 47 #endif
 48 
 49     bl    _main
 50 
 51 
 52 /*------------------------------------------------------------------------------*/
 53 
 54     .globl    c_runtime_cpu_setup
 55 c_runtime_cpu_setup:
 56 
 57     mov    pc, lr
 58 
 59 /*
 60  *************************************************************************
 61  *
 62  * CPU_init_critical registers
 63  *
 64  * setup important registers
 65  * setup memory timing
 66  *
 67  *************************************************************************
 68  */
 69 
 70 
 71 #ifndef CONFIG_SKIP_LOWLEVEL_INIT
 72 cpu_init_crit:
 73     /*
 74      * flush v4 I/D caches
 75      * CP15 的 C7 寄存器用来控制 cache 和写缓存
 76      * CP15的寄存器C8就是清除TLB内容的相关操作。
 77      */
 78     mov    r0, #0                    /* r0 = 0x0 */
 79     /* 对应表格可知道:使整个数据cache和指令cache无效,即cache清零 */
 80     mcr    p15, 0, r0, c7, c7, 0    /* flush v3/v4 cache */
 81     /* 对应表格可知道:使整个数据和指令TLB无效,即TLB清零*/
 82     mcr    p15, 0, r0, c8, c7, 0    /* flush v4 TLB */
 83     
 84 
 85     /*
 86      * disable MMU stuff and caches
 87      */
 88     mrc    p15, 0, r0, c1, c0, 0        /* 将 CP15 的寄存器 C1 的值读到 r0 中 */
 89     /* C1寄存器的8,9,13位被清除
 90      * S(bit[8])在基于 MMU 的存储系统中,本位用作系统保护
 91      * R(bit[9])在基于 MMU 的存储系统中,本位用作 ROM 保护
 92      * V(bit[13])
 93      *        对于支持高端异常向量表的系统,本控制位控制向量表的位置
 94      *        0 :选择低端异常中断向量 0x0~0x1c 
 95      *        1 :选择高端异常中断向量0xffff0000~ 0xffff001c
 96      *        对于不支持高端异常向量表的系统,读取时该位返回0,写入时忽略
 97      */
 98     bic    r0, r0, #0x00002300    @ clear bits 13, 9:8 (--V- --RS)
 99     /*     C1寄存器的0,1,2,7位被清除
100      *    M(bit[0])
101      *                0 :禁止 MMU 或者 PU 
102      *                1 :使能 MMU 或者 PU
103      *                如果系统中没有MMU及PU,读取时该位返回0,写入时忽略该位
104      *  A(bit[1])
105      *                0 :禁止地址对齐检查
106      *                1 :使能地址对齐检查
107      *  C(bit[2])
108      *                当数据cache和指令cache分开时,本控制位禁止/使能数据cache。
109      *                当数据cache和指令cache统一时,该控制位禁止/使能整个cache。
110      *                0 :禁止数据 / 整个 cache 
111      *                1 :使能数据 / 整个 cache
112      *                如果系统中不含cache,读取时该位返回0.写入时忽略
113      *                当系统中不能禁止cache 时,读取时返回1.写入时忽略
114      *  B(bit[7])
115      *                对于存储系统同时支持big-endian和little-endian的ARM系统,本控制位配置系统的存储模式
116      *                0 : little endian  
117      *                1 : big endian
118      *                对于只支持little-endian的系统,读取时该位返回0,写入时忽略
119      *                对于只支持big-endian的系统,读取时该位返回1,写入时忽略
120      */
121     bic    r0, r0, #0x00000087    @ clear bits 7, 2:0 (B--- -CAM)
122     /* C1寄存器的bit1置1,即使能地址对齐 */
123     orr    r0, r0, #0x00000002    @ set bit 1 (A) Align
124     /* C1寄存器的bit12置1,即使能指令 cache */
125     /* I(bit[12])
126      *                当数据cache和指令cache是分开的,本控制位禁止/使能指令cache
127      *                0 :禁止指令 cache  
128      *                1 :使能指令 cache
129      *                    如果系统中使用统一的指令cache和数据cache或者系统中不含cache,读取该位时返回0,写入时忽略。
130      *                    当系统中的指令cache不能禁止时,读取时该位返回1,写入时忽略
131      */
132     orr    r0, r0, #0x00001000    @ set bit 12 (I) I-Cache
133     mcr    p15, 0, r0, c1, c0, 0        /* 将 r0 的值写到 CP15 的寄存器 C1 中 */
134 
135     /*
136      * before relocating, we have to setup RAM timing
137      * because memory timing is board-dependend, you will
138      * find a lowlevel_init.S in your board directory.
139      */
140     mov    ip, lr                    /* 保存函数地址,用于返回 */
141 
142     bl    lowlevel_init            /* 跳转到lowlevel_init 进行DRAM设置 */
143 
144     /* 返回 */
145     mov    lr, ip
146     mov    pc, lr
147 #endif /* CONFIG_SKIP_LOWLEVEL_INIT */

 

  lowlevel_init.S

 1 .globl lowlevel_init
 2 lowlevel_init:
 3     /* memory control configuration */
 4     /* make r0 relative the current location so that it */
 5     /* reads SMRDATA out of FLASH rather than memory ! */
 6     /* 初始化内存 */
 7     ldr     r0, =SMRDATA            /* 将SMRDATA的首地址(第一个.word)内存单元数据放置到r0寄存器中 r0=eac */
 8     ldr    r1, =CONFIG_SYS_TEXT_BASE    /* CONFIG_SYS_TEXT_BASE=0x0(include/configs/jz2440中定义) */
 9     sub    r0, r0, r1                    /* r0 = r0 -r1 */
10     ldr    r1, =BWSCON    /* Bus Width Status Controller,BWSCON=0x48000000,此文件中定义 */
11     add     r2, r0, #13*4            /* r2 = r0 + 13*4 = 0xeac + 0x34 = 0xee0*/
12 0:
13     ldr     r3, [r0], #4            /* 将r0的值代表的内存单元放入r3中,之后r0的值偏移4位 */
14     str     r3, [r1], #4            /* 将r3的值放入r1的值代表的地址中,r1的值代表的地址偏移4位 */
15     cmp     r2, r0                    /* 比较r2 和 r0 ,若不相等则执行下一句*/
16     bne     0b                        /* 向后跳转到标签0处*/
17 
18     /* everything is fine now */
19     mov    pc, lr                        /* 返回 */
20 
21     .ltorg
22 /* the literal pools origin */
23 
24 /* 
25  * 初始化存储控制器,经过此初始化之后,内存才可以使用
26  */
27  /* 地址为 0x00000eb0 */
28 SMRDATA:
29     /* 地址为 0x00000eb0 */
30     .word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
31     /* 地址为 0x00000eb4 */
32     .word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))
33     /* 地址为 0x00000eb8 */
34     .word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))
35     /* 地址为 0x00000ebc */
36     .word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))
37     /* 地址为 0x00000ec0 */
38     .word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))
39     /* 地址为 0x00000ec4 */
40     .word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))
41     /* 地址为 0x00000ec8 */
42     .word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))
43     /* 地址为 0x00000ecc */
44     .word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))
45     /* 地址为 0x00000ed0 */
46     .word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))
47     /* 地址为 0x00000ed4 */
48     .word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)
49     /* 地址为 0x00000ed8 */
50     .word 0x32
51     /* 地址为 0x00000edc */
52     .word 0x30
53     /* 地址为 0x00000ee0 */
54     .word 0x30

 

 

  

 

u-boot移植(三)---修改前工作:代码流程分析2

标签:blog   立即数   返回   ase   before   ant   mmu   stat   sub   

原文地址:http://www.cnblogs.com/kele-dad/p/6935287.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!