日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

uboot代码详细分析.pdf

發布時間:2024/3/12 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 uboot代码详细分析.pdf 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
目錄
u-boot-1.1.6 之cpu/arm920t/start.s 分析 ........................................................................................... 2
u-boot 中.lds 連接腳本文件的分析 ...................................................................................................12
分享一篇我總結的uboot 學習筆記(轉) .....................................................................................15
U-BOOT 內存布局及啟動過程淺析 ...................................................................................................22
u-boot 中的命令實現 .......................................................................................................................... 25
U-BOOT 環境變量實現 ........................................................................................................................28
1.相關文件 ...................................................................................................................................28
2.數據結構 ...................................................................................................................................28
3.ENV 的初始化...........................................................................................................................30
3.1env_init ............................................................................................................................30
3.2 env_relocate ...................................................................................................................30
3.3*env_relocate_spec ........................................................................................................31
4. ENV 的保存 ..............................................................................................................................31
U-Boot 環境變量 ..........................................................................................................................32
u-boot 代碼鏈接的問題 ......................................................................................................................35
ldr 和adr 在使用標號表達式作為操作數的區別 ............................................................................40
start_armboot 淺析 ..............................................................................................................................42
1.全局數據結構的初始化 ..........................................................................................................42
2.調用通用初始化函數...............................................................................................................43
3.初始化具體設備 .......................................................................................................................44
4.初始化環境變量 .......................................................................................................................44
5.進入主循環 ...............................................................................................................................44
u-boot 編譯過程 ..................................................................................................................................44
mkconfig 文件的分析 .......................................................................................................................... 47
從NAND 閃存中啟動U-BOOT 的設計 ..............................................................................................50
引言 ..............................................................................................................................................50
NAND 閃存工作原理 ................................................................................................................... 51
從NAND 閃存啟動U-BOOT 的設計思路.................................................................................. 51
具體設計 ...................................................................................................................................... 51
支持NAND 閃存的啟動程序設計 ..................................................................................... 51
支持U-BOOT 命令設計 ...................................................................................................... 52
結語 .............................................................................................................................................. 53
參考文獻 ...................................................................................................................................... 53
U-boot 給kernel 傳參數和kernel 讀取參數—struct tag (以及補充) ............................................ 53
1 、u-boot 給kernel 傳RAM 參數 ........................................................................................54
2 、Kernel 讀取U-boot 傳遞的相關參數 .............................................................................56
3 、關于U-boot 中的bd 和gd...............................................................................................59
U-BOOT 源碼分析及移植 ....................................................................................................................60
一、u-boot 工程的總體結構: ..................................................................................................61
1、源代碼組織 ....................................................................................................................61
2.makefile 簡要分析 ............................................................................................................61
3、u-boot 的通用目錄是怎么做到與平臺無關的?......................................................63
4、smkd2410 其余重要的文件 : ...................................................................................63
二、u-boot 的流程、主要的數據結構、內存分配 ................................................................64
1、u-boot 的啟動流程: ...................................................................................................64
2、u-boot 主要的數據結構 ...............................................................................................66
3、u-boot 重定位后的內存分布: ...................................................................................68
三、u-boot 的重要細節 。 ........................................................................................................68
關于U-boot 中命令相關的編程 : ................................................................................. 73
四、U-boot 在ST2410 的移植,基于NOR FLASH 和NAND FLASH 啟動。......................... 76
1、從smdk2410 到ST2410: .............................................................................................. 76
2、移植過程: .................................................................................................................... 76
3、移植要考慮的問題: ...................................................................................................77
4、SST39VF1601: .................................................................................................................77
5、我實現的flash.c 主要部分: ...................................................................................... 78
6、增加從Nand 啟動的代碼 : ..................................................................................... 82
7、添加網絡命令。 ............................................................................................................ 87
u-boot-1.1.6 之cpu/arm920t/start.s 分析
/*
* armboot - Startup Code for ARM920 CPU-core
*
* Copyright (c) 2001 Marius Gr 鰃er < mag@sysgo.de>
* Copyright (c) 2002 Alex Z 黳ke < azu@sysgo.de>
* Copyright (c) 2002 Gary Jennejohn < gj@denx.de>
*
* See file CREDITS for list of people who contributed to this
* project.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
#include <config.h>
#include <version.h>
/*
*************************************************************
************
*
* Jump vector table as in table 3.1 in [1]
*
*************************************************************
************
*/
//global 聲明一個符號可被其他文檔引用,相當于聲明了一個全局變量,.globl 和.global 相同。
//該部分為處理器的異常處理向量表。地址范圍為0x0000 0000 ~ 0x0000 0020,剛好8 條指令。
.globl _start //u-boot 啟動入口
_start: b reset //復位向量并且跳轉到reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq //中斷向量
ldr pc, _fiq //中斷向量
// .word 偽操作用于分配一段字內存單元(分配的單元都是字對齊的),并用偽操作中的expr 初始
化。.long 和.int 作用與之相同。
_undefined_instruction: .word undefined_instruction
_software_interrupt: .word software_interrupt
_prefetch_abort: .word prefetch_abort
_data_abort: .word data_abort
_not_used: .word not_used
_irq: .word irq
_fiq: .word fiq
// .align 偽操作用于表示對齊方式:通過添加填充字節使當前位置滿足一定的對齊方式。.balign 的作用
同.align。
// .align {alignment} {,fill} {,max}
// 其中:alignment 用于指定對齊方式,可能的取值為2 的次冪,缺省為4。fill 是填充內容,缺省用0
填充。max 是填充字節數最大值,假如填充字節數超過max,
// 就不進行對齊,例如:
// .align 4 /* 指定對齊方式為字對齊 */
.balignl 16,0xdeadbeef
/*
*************************************************************
************
*
* Startup Code (reset vector)
*
* do important init only if we don't start from memory!
* relocate armboot to ram
* setup stack
* jump to second stage
*
*************************************************************
************
*/
// TEXT_BASE 在研發板相關的目錄中的config.mk 文檔中定義, 他定義了
// 代碼在運行時所在的地址, 那么_TEXT_BASE 中保存了這個地址
_TEXT_BASE:
.word TEXT_BASE
// 聲明 _armboot_start 并用 _start 來進行初始化,在board/u-boot.lds 中定義。
.globl _armboot_start
_armboot_start:
.word _start
/*
* These are defined in the board-specific linker script.
*/
// 聲明_bss_start 并用__bss_start 來初始化,其中__bss_start 定義在和板相關的u-boot.lds 中。
// _bss_start 保存的是__bss_start 這個標號所在的地址, 這里涉及到當前代碼所在
// 的地址不是編譯時的地址的情況, 這里直接取得該標號對應的地址, 不受編譯時
// 地址的影響. _bss_end 也是同樣的道理.
.globl _bss_start
_bss_start:
.word __bss_start
// 同上
.globl _bss_end
_bss_end:
.word _end
#ifdef CONFIG_USE_IRQ
/* IRQ stack memory (calculated at run-time) */
.globl IRQ_STACK_START
IRQ_STACK_START:
.word 0x0badc0de
/* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_START
FIQ_STACK_START:
.word 0x0badc0de
#endif
/*
* the actual reset code
*/
// MRS {} Rd,CPSR|SPSR 將CPSR|SPSR 傳送到Rd
// 使用這兩條指令將狀態寄存器傳送到一般寄存器,只修改必要的位,再將結果傳送回狀態寄存器,這
樣能夠最好地完成對CRSP 或SPSR 的修改
// MSR {} CPSR_|SPSR_,Rm 或是 MSR {} CPSR_f|SPSR_f,#
// MRS 和MSR 配合使用,作為更新PSR 的“讀取--修改--寫回”序列的一部分
// bic r0,r1,r2 ;r0:=r1 and not r2
// orr ro,r1,r2 ;r0:=r1 or r2
// 這幾條指令執行完畢后,進入SVC32 模式,該模式主要用來處理軟件中斷(SWI)
reset:
/*
* set the cpu to SVC32 mode
*/
mrs r0,cpsr //將CPSR 狀態寄存器讀取,保存到R0 中
bic r0,r0,#0x1f
orr r0,r0,#0xd3
msr cpsr,r0 //將R0 寫入狀態寄存器中
/* turn off the watchdog */
//關閉看門狗
#if defined(CONFIG_S3C2400)
# define pWTCON 0x15300000
# define INTMSK 0x14400008 /* Interupt-Controller base addresses */
# define CLKDIVN 0x14800014 /* clock divisor register */
#elif defined(CONFIG_S3C2410)
# define pWTCON 0x53000000
# define INTMSK 0x4A000008 /* Interupt-Controller base addresses */
# define INTSUBMSK 0x4A00001C
# define CLKDIVN 0x4C000014 /* clock divisor register */
#endif
#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)
ldr r0, =pWTCON
mov r1, #0x0
str r1, [r0]
/*
* mask all IRQs by setting all bits in the INTMR - default
*/
//關閉所有中斷
mov r1, #0xffffffff
ldr r0, =INTMSK
str r1, [r0]
# if defined(CONFIG_S3C2410)
ldr r1, =0x3ff
ldr r0, =INTSUBMSK
str r1, [r0]
# endif
/* FCLK:HCLK:PCLK = 1:2:4 */
/* default FCLK is 120 MHz ! */
ldr r0, =CLKDIVN
mov r1, #3
str r1, [r0]
#endif /* CONFIG_S3C2400 || CONFIG_S3C2410 */
/*
* we do sys-critical inits only at reboot,
* not when booting from ram!
*/
// B 轉移指令,跳轉到指令中指定的目的地址
// BL 帶鏈接的轉移指令,像B 相同跳轉并把轉移后面緊接的一條指令地址保存到鏈接寄存器LR(R14)
中,以此來完成子程式的調用
// 該語句首先調用cpu_init_crit 進行CPU 的初始化,并把下一條指令的地址保存在LR 中,以使得執
行完后能夠正常返回。
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit
#endif
#ifndef CONFIG_SKIP_RELOCATE_UBOOT
//調試階段的代碼是直接在RAM 中運行的,而最后需要把這些代碼固化到Flash 中,因此U-Boot 需要
自己從Flash 轉移到
//RAM 中運行,這也是重定向的目的所在。
//通過adr 指令得到當前代碼的地址信息:假如U-boot 是從RAM 開始運行,則從adr,r0,_start 得到
的地址信息為
//r0=_start=_TEXT_BASE=TEXT_BASE=0xa3000000;假如U-boot 從Flash 開始運行,即從處
理器對應的地址運行,
//則r0=0x0000,這時將會執行copy_loop 標識的那段代碼了。
// _TEXT_BASE 定義在board/smdk2410/config.mk 中
relocate: /* relocate U-Boot to RAM */
adr r0, _start /* r0 <- current position of code */
ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
cmp r0, r1 /* don't reloc during debug */
beq stack_setup
//重新定位代碼
//聲明_bss_start 并用__bss_start 來初始化,其中__bss_start 定義在和板相關的u-boot.lds 中
ldr r2, _armboot_start
ldr r3, _bss_start
sub r2, r3, r2 /* r2 <- size of armboot */
add r2, r0, r2 /* r2 <- source end address */
copy_loop:
ldmia r0!, {r3-r10} /* copy from source address [r0] */
stmia r1!, {r3-r10} /* copy to target address [r1] */
cmp r0, r2 /* until source end addreee [r2] */
ble copy_loop
#endif /* CONFIG_SKIP_RELOCATE_UBOOT */
/* Set up the stack */
//初始化堆棧
stack_setup:
ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */
sub r0, r0, #CFG_MALLOC_LEN /* malloc area */
sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */
#ifdef CONFIG_USE_IRQ
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
sub sp, r0, #12 /* leave 3 words for abort-stack */
clear_bss:
ldr r0, _bss_start /* find start of bss segment */
ldr r1, _bss_end /* stop here */
mov r2, #0x00000000 /* clear */
clbss_l:str r2, [r0] /* clear loop... */
add r0, r0, #4
cmp r0, r1
ble clbss_l
#if 0
/* try doing this stuff after the relocation */
ldr r0, =pWTCON
mov r1, #0x0
str r1, [r0]
/*
* mask all IRQs by setting all bits in the INTMR - default
*/
mov r1, #0xffffffff
ldr r0, =INTMR
str r1, [r0]
/* FCLK:HCLK:PCLK = 1:2:4 */
/* default FCLK is 120 MHz ! */
ldr r0, =CLKDIVN
mov r1, #3
str r1, [r0]
/* END stuff after relocation */
#endif
//跳轉到start_armboot 函數入口,_start_armboot 字保存函數入口指針
//start_armboot 函數在lib_arm/board.c 中實現
ldr pc, _start_armboot
_start_armboot: .word start_armboot
/*
*************************************************************
************
*
* CPU_init_critical registers
*
* setup important registers
* setup memory timing
*
*************************************************************
************
*/
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
cpu_init_crit:
/*
* flush v4 I/D caches
*/
//初始化CACHE
mov r0, #0
mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */
mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */
/*
* disable MMU stuff and caches
*/
//關閉MMU 和CACHE
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS)
bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM)
orr r0, r0, #0x00000002 @ set bit 2 (A) Align
orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache
mcr p15, 0, r0, c1, c0, 0
/*
* before relocating, we have to setup RAM timing
* because memory timing is board-dependend, you will
* find a lowlevel_init.S in your board directory.
*/
//初始化RAM 時鐘。因為內存時鐘是依賴開發板硬件的,所以在board 的相應目錄下可以找到
memsetup.s 文件
mov ip, lr
bl lowlevel_init //lowlevel_init 子程序在board/smdk2410/memsetup.s 中實現
mov lr, ip
mov pc, lr
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */
/*
*************************************************************
************
*
* Interrupt handling
*
*************************************************************
************
*/
@
@ IRQ stack frame.
@
#define S_FRAME_SIZE 72
#define S_OLD_R0 68
#define S_PSR 64
#define S_PC 60
#define S_LR 56
#define S_SP 52
#define S_IP 48
#define S_FP 44
#define S_R10 40
#define S_R9 36
#define S_R8 32
#define S_R7 28
#define S_R6 24
#define S_R5 20
#define S_R4 16
#define S_R3 12
#define S_R2 8
#define S_R1 4
#define S_R0 0
#define MODE_SVC 0x13
#define I_BIT 0x80
/*
* use bad_save_user_regs for abort/prefetch/undef/swi ...
* use irq_save_user_regs / irq_restore_user_regs for IRQ/FIQ handling
*/
.macro bad_save_user_regs
sub sp, sp, #S_FRAME_SIZE
stmia sp, {r0 - r12} @ Calling r0-r12
ldr r2, _armboot_start
sub r2, r2, #(CONFIG_STACKSIZE+CFG_MALLOC_LEN)
sub r2, r2, #(CFG_GBL_DATA_SIZE+8) @ set base 2 words into abort stack
ldmia r2, {r2 - r3} @ get pc, cpsr
add r0, sp, #S_FRAME_SIZE @ restore sp_SVC
add r5, sp, #S_SP
mov r1, lr
stmia r5, {r0 - r3} @ save sp_SVC, lr_SVC, pc, cpsr
mov r0, sp
.endm
.macro irq_save_user_regs
sub sp, sp, #S_FRAME_SIZE
stmia sp, {r0 - r12} @ Calling r0-r12
add r8, sp, #S_PC
stmdb r8, {sp, lr}^ @ Calling SP, LR
str lr, [r8, #0] @ Save calling PC
mrs r6, spsr
str r6, [r8, #4] @ Save CPSR
str r0, [r8, #8] @ Save OLD_R0
mov r0, sp
.endm
.macro irq_restore_user_regs
ldmia sp, {r0 - lr}^ @ Calling r0 - lr
mov r0, r0
ldr lr, [sp, #S_PC] @ Get PC
add sp, sp, #S_FRAME_SIZE
subs pc, lr, #4 @ return & move spsr_svc into cpsr
.endm
.macro get_bad_stack
ldr r13, _armboot_start @ setup our mode stack
sub r13, r13, #(CONFIG_STACKSIZE+CFG_MALLOC_LEN)
sub r13, r13, #(CFG_GBL_DATA_SIZE+8) @ reserved a couple spots in abort
stack
str lr, [r13] @ save caller lr / spsr
mrs lr, spsr
str lr, [r13, #4]
mov r13, #MODE_SVC @ prepare SVC-Mode
@ msr spsr_c, r13
msr spsr, r13
mov lr, pc
movs pc, lr
.endm
.macro get_irq_stack @ setup IRQ stack
ldr sp, IRQ_STACK_START
.endm
.macro get_fiq_stack @ setup FIQ stack
ldr sp, FIQ_STACK_START
.endm
/*
* exception handlers
*/
//以下都是中斷處理函數,具體實現在lib_arm 目錄下interrupts.c
.align 5
undefined_instruction:
get_bad_stack
bad_save_user_regs
bl do_undefined_instruction
.align 5
software_interrupt:
get_bad_stack
bad_save_user_regs
bl do_software_interrupt
.align 5
prefetch_abort:
get_bad_stack
bad_save_user_regs
bl do_prefetch_abort
.align 5
data_abort:
get_bad_stack
bad_save_user_regs
bl do_data_abort
.align 5
not_used:
get_bad_stack
bad_save_user_regs
bl do_not_used
#ifdef CONFIG_USE_IRQ
.align 5
irq:
get_irq_stack
irq_save_user_regs
bl do_irq
irq_restore_user_regs
.align 5
fiq:
get_fiq_stack
/* someone ought to write a more effiction fiq_save_user_regs */
irq_save_user_regs
bl do_fiq
irq_restore_user_regs
#else
.align 5
irq:
get_bad_stack
bad_save_user_regs
bl do_irq
.align 5
fiq:
get_bad_stack
bad_save_user_regs
bl do_fiq
#endif
u-boot 中.lds 連接腳本文件的分析
對于.lds 文件,它定義了整個程序編譯之后的連接過程,決定了一個可執行程序的各個
段的存儲位置。雖然現在我還沒怎么用它,但感覺還是挺重要的,有必要了解一下。
先看一下GNU 官方網站上對.lds 文件形式的完整描述:
SECTIONS {
...
secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
{ contents } >region :phdr =fill
...
}
secname和contents是必須的,其他的都是可選的。下面挑幾個常用的看看:
1、secname:段名
2、contents:決定哪些內容放在本段,可以是整個目標文件,也可以是目標文件中的
某段(代碼段、數據段等)
3、start:本段連接(運行)的地址,如果沒有使用AT(ldadr),本段存儲的地址也
是start。GNU 網站上說start 可以用任意一種描述地址的符號來描述。
4、AT(ldadr):定義本段存儲(加載)的地址。
看一個簡單的例子:(摘自《2410 完全開發》)
/* nand.lds */
SECTIONS {
firtst 0x00000000 : { head.o init.o }
second 0x30000000 : AT(4096) { main.o }
}
以上,head.o 放在0x00000000 地址開始處,init.o 放在head.o 后面,他們的運行地址
也是0x00000000,即連接和存儲地址相同(沒有AT 指定);main.o 放在4096(0x1000,
是AT 指定的,存儲地址)開始處,但是它的運行地址在0x30000000,運行之前需要從0
x1000(加載處)復制到0x30000000(運行處),此過程也就用到了讀取Nand flash。
這就是存儲地址和連接(運行)地址的不同,稱為加載時域和運行時域,可以在.lds 連
接腳本文件中分別指定。
編寫好的.lds 文件,在用arm-linux-ld 連接命令時帶-Tfilename 來調用執行,如
arm-linux-ld –Tnand.lds x.o y.o –o xy.o。也用-Ttext 參數直接指定連接地址,如
arm-linux-ld –Ttext 0x30000000 x.o y.o –o xy.o。
既然程序有了兩種地址,就涉及到一些跳轉指令的區別,這里正好寫下來,以后萬一忘
記了也可查看,以前不少東西沒記下來現在忘得差不多了。。。
ARM匯編中,常有兩種跳轉方法:b 跳轉指令、ldr 指令向PC 賦值。
我自己經過歸納如下:
(1) b step1 :b 跳轉指令是相對跳轉,依賴當前PC 的值,偏移量是通過該指令本身的bit[2
3:0]算出來的,這使得使用b 指令的程序不依賴于要跳到的代碼的位置,只看指令本身。
(2) ldr pc, =step1 :該指令是從內存中的某個位置(step1)讀出數據并賦給PC,同樣依
賴當前PC 的值,但是偏移量是那個位置(step1)的連接地址(運行時的地址),所以可
以用它實現從Flash 到RAM 的程序跳轉。
(3) 此外,有必要回味一下adr 偽指令,U-boot 中那段relocate 代碼就是通過adr 實現當前
程序是在RAM 中還是flash 中。仍然用我當時的注釋:
relocate: /* 把U-Boot 重新定位到RAM */
adr r0, _start /* r0 是代碼的當前位置 */
/* adr 偽指令,匯編器自動通過當前PC 的值算出 如果執行到_start 時PC 的值,
放到r0 中:
當此段在flash 中執行時r0 = _start = 0;當此段在RAM中執行時_start = _TEX
T_BASE(在board/smdk2410/config.mk 中指定的值為0x33F80000,即u-boot 在
把代碼拷貝到RAM 中去執行的代碼段的開始) */
ldr r1, _TEXT_BASE /* 測試判斷是從Flash 啟動,還是RAM */
/* 此句執行的結果r1 始終是0x33FF80000,因為此值是又編譯器指定的(ads 中設
置,或-D 設置編譯器參數) */
cmp r0, r1 /* 比較r0 和r1,調試的時候不要執行重定位 */
下面,結合u-boot.lds 看看一個正式的連接腳本文件。這個文件的基本功能還能看明白,
雖然上面分析了好多,但其中那些GNU 風格的符號還是著實讓我感到迷惑,好菜啊,怪不
得連被3 家公司鄙視,自己鄙視自己。。。
OUTPUT_FORMAT("elf32&shy;littlearm", "elf32&shy;littlearm", "elf32&shy;littlea
rm")
;指定輸出可執行文件是elf 格式,32 位ARM 指令,小端
OUTPUT_ARCH(arm)
;指定輸出可執行文件的平臺為ARM
ENTRY(_start)
;指定輸出可執行文件的起始代碼段為_start.
SECTIONS
{
. = 0x00000000 ; 從0x0 位置開始
. = ALIGN(4) ; 代碼以4 字節對齊
.text : ;指定代碼段
{
cpu/arm920t/start.o (.text) ; 代碼的第一個代碼部分
*(.text) ;其它代碼部分
}
. = ALIGN(4)
.rodata : { *(.rodata) } ;指定只讀數據段
. = ALIGN(4);
.data : { *(.data) } ;指定讀/寫數據段
. = ALIGN(4);
.got : { *(.got) } ;指定got 段, got 段式是uboot 自定義的一個段, 非標準段
__u_boot_cmd_start = . ;把__u_boot_cmd_start 賦值為當前位置, 即起始位

.u_boot_cmd : { *(.u_boot_cmd) } ;指定u_boot_cmd 段, uboot 把所有的u
boot 命令放在該段.
__u_boot_cmd_end = .;把__u_boot_cmd_end 賦值為當前位置,即結束位置
. = ALIGN(4);
__bss_start = .; 把__bss_start 賦值為當前位置,即bss 段的開始位置
.bss : { *(.bss) }; 指定bss 段
_end = .; 把_end 賦值為當前位置,即bss 段的結束位置
}
分享一篇我總結的uboot 學習筆記(轉)
1. 下面代碼是系統啟動后U-boot 上電后運行的第一段代碼,他是什么意思?
.globl _start
_start: b reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
_undefined_instruction: .word undefined_instruction
_software_interrupt: .word software_interrupt
_prefetch_abort: .word prefetch_abort
_data_abort: .word data_abort
_not_used: .word not_used
_irq: .word irq
_fiq: .word fiq
.balignl 16,0xdeadbeef
他們是系統定義的異常,一上電程序跳轉到reset 異常處執行相應的匯編指令,下面定義出
的都是不同的異常,比如軟件發生軟中斷時,CPU 就會去執行軟中斷的指令,這些異常中斷
在CUP 中地址是從0 開始,每個異常占4 個字節。
reset:
/*
* set the cpu to SVC32 mode
*/
mrs r0,cpsr
bic r0,r0,#0x1f
orr r0,r0,#0xd3
msr cpsr,r0
操作系統先注冊一個總的中斷,然后去查是由哪個中斷源產生的中斷,再去查用戶注冊的中
斷表,查出來后就去執行用戶定義的用戶中斷處理函數。
ldr pc, _undefined_instruction 表示把_undefined_instruction 存放的數值存放到pc 指針上,
_undefined_instruction: .word undefined_instruction 表示未定義的這個異常是由.word 來定
義的,它表示定義一個字,一個32 位的數,.word 后面的數表示把該標識的編譯地址寫入
當前地址,標識是不占用任何指令的。把標識存放的數值copy 到指針pc 上面,那么標識上
存放的值是什么?是由.word undefined_instruction 來指定的,pc 就代表你運行代碼的地址,
她就實現了CPU 要做一次跳轉時的工作。
什么是編譯地址?什么是運行地址?
32 位的處理器,它的每一條指令是4 個字節,以4 個字節存儲順序,進行順序執行,CPU
是順序執行的,只要沒發生什么跳轉,它會順序進行執行,編譯器會對每一條指令分配一個
編譯地址,這是編譯器分配的,在編譯過程中分配的地址,我們稱之為編譯地址。
運行地址是指,程序指令真正運行的地址,是由用戶指定的,用戶將運行地址燒錄到哪里,
哪里就是運行的地址。比如有一個指令的編譯地址是0x5,實際運行的地址是0x200,如果
用戶將指令燒到0x200 上,那么這條指令的運行地址就是0x200,當編譯地址和運行地址不
同的時候會出現什么結果?結果是不能跳轉,編譯后會產生跳轉地址,如果實際地址和編譯
后產生的地址不相等,那么就不能跳轉。C 語言編譯地址都希望把編譯地址和實際運行地址
放在一起的,但是匯編代碼因為不需要做C 語言到匯編的轉換,可以人為的去寫地址,所以
直接寫的就是他的運行地址,這就是為什么任何bootloader 剛開始會有一段匯編代碼,因為
起始代碼編譯地址和實際地址不相等,這段代碼和匯編無關,跳轉用的運行地址。編譯地址
和運行地址如何來算呢?假如有兩個編譯地址a=0x10,b=0x7,b 的運行地址是0x300,那
么a 的運行地址就是b 的運行地址加上兩者編譯地址的差值,a-b=0x10-0x7=0x3,a 的運行
地址就是0x300+0x3=0x303。
假設uboot 上兩條指令的編譯地址為a=0x33000007 和b=0x33000001,這兩條指令都落在
bank6 上,現在要計算出他們對應的運行地址,要找出運行地址的始地址,這個是由用戶燒
錄進去的,假設運行地址的首地址是0x0,則a 的運行地址
為0x7,b 為0x1,就是這樣算出來的。
為什么要分配編譯地址?這樣做有什么好處,有什么作用?
比如在函數a 中定義了函數b,當執行到函數b 時要進行指令跳轉,要跳轉到b 函數所對應
的起始地址上去,編譯時,編譯器給每條指令都分配了編譯地址,如果編譯器已經給分配了
地址就可以直接進行跳轉,查找b 函數跳轉指令所對應的表,進行直接跳轉,因為有個編譯
地址和指令對應的一個表,如果沒有分配,編譯器就查找不到這個跳轉地址,要進行計算,
非常麻煩。
什么是相對地址?
以NOR Flash 為例,NOR Falsh 是映射到bank0 上面,SDRAM 是映射到bank6 上面,uboot
和內核最終是在SDRAM 上面運行,最開始我們是從Nor Flash 的零地址開始往后燒錄,uboot
中至少有一段代碼編譯地址和運行地址是不一樣的,編譯uboot 或內核時,都會將編譯地址
放入到SDRAM 中,他們最終都會在SDRAM 中執行,剛開始uboot 在Nor Flash 中運行,運
行地址是一個低端地址,是bank0 中的一個地址,但編譯地址是bank6 中的地址,這樣就會
導致絕對跳轉指令執行的失敗,所以就引出了相對地址的概念。那么什么是相對地址呢?至
少在bank0 中uboot 這段代碼要知道不能用b+編譯地址這樣的方法去跳轉指令,因為這段
代碼的編譯地址和運行地址不一樣,那如何去做呢?要去計算這個指令運行的真實地址,計
算出來后再做跳轉,應該是b+運行地址,不能出現b+編譯地址,而是b+運行地址,而運行
地址是算出來的。
_TEXT_BASE:
.word TEXT_BASE //0x33F80000,在board/config.mk 中
這段話表示,用戶告訴編譯器編譯地址的起始地址
Uboot 啟動流程
1. 設置CPU 的啟動模式
reset:
//設置CPU 進入管理模式 即設置相應的CPSR 程序狀態字
/* * set the cpu to SVC32 mode*/
mrs r0,cpsr
bic r0,r0,#0x1f
orr r0,r0,#0xd3
msr cpsr,r0
2. 關閉看門狗,關閉中斷,所謂的喂狗是每隔一段時間給某個寄存器置位而已,在實際
中會專門啟動一個線程或進程會專門喂狗,當上層軟件出現故障時就會停止喂狗,停止喂狗
之后,cpu 會自動復位,一般都在外部專門有一個看門狗,做一個外部的電路,不在cpu 內
部使用看門狗,cpu 內部的看門狗是復位的cpu,當開發板很復雜時,有好幾個cpu 時,就
不能完全讓板子復位,但我們通常都讓整個板子復位。看門狗每隔短時間就會喂狗,問題是
在兩次喂狗之間的時間間隔內,運行的代碼的時間是否夠用,兩次喂狗之間的代碼是否在兩
次喂狗的時間延遲之內,如果在延遲之外的話,代碼還沒改完就又進行喂狗,代碼永遠也改
不完。
//關閉看門狗的實際代碼
#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)
ldr r0, =pWTCON //將pwtcon 寄存器地址賦給R0
mov r1, #0x0 //r1 的內容為0
str r1, [r0] //將R1 的內容送到Ro 寄存器中去
3. 屏蔽所有中斷,為什么要關中斷?中斷處理中ldr pc 是將代碼的編譯地址放在了指針上,
而這段時間還沒有搬移代碼,所以編譯地址上面沒有這個代碼,如果進行跳轉就會跳轉到空
指針上面
/* * mask all IRQs by setting all bits in the INTMR - default*/
mov r1, #0xffffffff //寄存器中的值全為11111111111111111111111111111111
ldr r0, =INTMSK //將管理中斷的寄存器地址賦給ro
str r1, [r0] //將全1 的值賦給ro 地址中的內容
#if defined(CONFIG_S3C2410)
ldr r1, =0x3ff
ldr r0, =INTSUBMSK
str r1, [r0]
#endif
3. 設置時鐘分頻,為什么要設置時鐘?起始可以不設,系統能不能跑起來和頻率沒有任
何關系,頻率的設置是要讓外圍的設備能承受所設置的頻率,如果頻率過高則會導致cpu
操作外圍設備失敗
//設置CPU 的頻率
/* FCLK:HCLK:PCLK = 1:2:4 */
/* default FCLK is 120 MHz ! */
ldr r0, =CLKDIVN
mov r1, #3
str r1, [r0]
4. 做bank 的設置
cpu_init_crit:
/* flush v4 I/D caches,關閉catch*/
mov r0, #0
mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */
mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB *///協處理器
//禁止MMU
/** disable MMU stuff and caches*/
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS)
bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM)
orr r0, r0, #0x00000002 @ set bit 2 (A) Align
orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache
mcr p15, 0, r0, c1, c0, 0 //關閉
為什么要關閉catch 和MMU 呢?catch 和MMU 是做什么用的?
Catch 是cpu 內部的一個2 級緩存,她的作用是將常用的數據和指令放在cpu 內部,MMU
是用來做虛實地址轉換用的,我們的目的是設置控制的寄存器,寄存器都是實地址,如果既
要開啟MMU 又要做虛實地址轉換的話,中間還多一步,
先要把實地址轉換成虛地址,然后再做設置,但對uboot 而言就是起到一個簡單的初始化的
作用和引導操作系統,如果開啟MMU 的話,很麻煩,也沒必要,所以關閉MMU.
說道catch 就必須提到一個關鍵字Volatile,以后在設置寄存器時會經常遇到,他的本質
是告訴編譯器不要對我的代碼進行優化,優化的過程是將常用的代碼取出來放到catch 中,
它沒有從實際的物理地址去取,它直接從cpu 的緩存中去取,但常用的代碼就是為了感知一
些常用變量的變化,如果正在取數據的時候發生跳變,那么就感覺不到變量的變化了,所以
在這種情況下要用Volatile 關鍵字告訴編譯器不要做優化,每次從實際的物理地址中去取指
令,這就是為什么關閉catch 關閉MMU。但在C 語言中是不會關閉catch 和MMU 的,會打
開,如果編寫者要感知外界變化,或變化太快,從catch 中取數據會有誤差,就加一個關鍵
字Volatile。
5. bl lowlevel_init 下來初始化各個bank,把各個bank 設置必須搞清楚,對以后移植復雜
的uboot 有很大幫助
6.設置完畢后拷貝uboot 代碼到4k 空間,拷貝完畢后執行內存中的uboot 代碼
以上流程基本上適用于所有的bootloader,這就是step1 階段
7. 問題:如果換一塊開發板有可能改哪些東西?
首先,cpu 的運行模式,如果需要對cpu 進行設置那就設置,管看門狗,關中斷不用改,
時鐘有可能要改,如果能正常使用則不用改,關閉catch 和MMU 不用改,設置bank 有可能
要改。最后一步拷貝時看地址會不會變,如果變化也要改,執行內存中代碼,地址有可能要
改。
8. Nor Flash 和Nand Flash 本質區別就在于是否進行代碼拷貝,也就是下面代碼所表述:無論
是Nor Flash 還是Nand Flash,核心思想就是將uboot 代碼搬運到內存中去運行,但是沒有拷
貝bss 后面這段代碼,只拷貝bss 前面的代碼,bss 代碼是放置全局變量的。Bss 段代碼是為
了清零,拷貝過去再清零重復操作
//uboot 代碼搬運到RAM 中去
#ifndef CONFIG_SKIP_RELOCATE_UBOOT
relocate: /* relocate U-Boot to RAM */
adr r0, _start /* r0 <- current position of code */
ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
cmp r0, r1 /* don't reloc during debug */
beq stack_setup
ldr r2, _armboot_start //flash 中armboot_start 的起始地址
ldr r3, _bss_start //uboot_bss 的起始地址
sub r2, r3, r2 /* r2 <- size of armboot//uboot 實際程序代碼的大小 */
add r2, r0, r2 /* r2 <- source end address */
copy_loop:
ldmia r0!, {r3-r10} /* copy from source address [r0] */
stmia r1!, {r3-r10} /* copy to target address [r1] */
cmp r0, r2 /* until source end addreee [r2] */
ble copy_loop
#endif /* CONFIG_SKIP_RELOCATE_UBOOT */
9. 看一下uboot.lds文件,在board/smdk2410目錄下面,uboot.lds是告訴編譯器這些段
改怎么劃分,GUN編譯過的段,最基本的三個段是RO,RW,ZI,RO表示只讀,對應于具體
的指代碼段,RW是數據段,ZI是歸零段,就是全局變量的那段。Uboot代碼這么多,如何
保證start.s會第一個執行,編譯在最開始呢?就是通過uboot.lds鏈接文件進行
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
/*OUTPUT_FORMAT("elf32-arm", "elf32-arm", "elf32-arm")*/
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000; //起始地址
. = ALIGN(4); //4字節對齊
.text : //test指代碼段,上面3行標識是不占用任何空間的
{
cpu/arm920t/start.o (.text) //這里把start.o放在第一位就表示把start.s

譯時放到最開始,這就是為什么把uboot燒到起始地址上它肯定運行的是start.s
*(.text)
}
. = ALIGN(4); //前面的 “.” 代表當前值,是計算一個當前的值,是計算上
面占用的整個空間,再加一個單元就表示它現在的位置
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
.got : { *(.got) }
. = .;
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
. = ALIGN(4);
__bss_start = .; //bss表示歸零段
.bss : { *(.bss) }
_end = .;
回憶一下GUN在編譯代碼時的四個步驟,1.預處理,2.編譯,3.匯編,4.鏈接,鏈接就做的
是這個文件的動作,就是將這些文件重新map一下分配地址。
最后運行的是_start_armboot: .word start_armboot函數跳轉到step2的階段,這個
函數是uboot中第一個C代碼,也是第一個在內存中運行的代碼
U-BOOT 內存布局及啟動過程淺析
本文以ARC600平臺的某一實現為例,對U-BOOT的內存布局和啟動方式進行簡要的分析。
【內存布局】
在ARC600平臺,U-BOOT的內存布局圖1所示。
該布局由board/arc600/u-boot.lds文件定義,在鏈接的時候生成相應的二進制映像。首先,
定義起始地址為0x40800000,接下來是中斷向量表,大小為256字節,按每個中斷向量占
用4個字節的跳轉地址算,最多可以有64個中斷向量;第二部分是一些基礎性的代碼段,
它為下一步加載 boot或者kernel做準備,其大小為0x1700字節;第三部分是代碼段的后
半部分,代碼段的大部分代碼都在這里;第四部分只讀數據區;第五部分為可讀寫數據區;
第六部分為U-BOOT命令代碼區;最后一部分為未初始化數據段。
有一點比較疑惑的就是U-BOOT命令代碼區存放的分明是代碼,但它卻在數據段。內核中會
把一些初始化代碼放在數據區,因為這些代碼只運行一次,放在數據區可以在內核啟動后回
收該區域內存。但顯然U-BOOT命令不可能只運行一次,為何要把它放在數據段?不解!
【啟動過程】
眾所周知,U-BOOT是存放在FLASH上的。系統啟動時,CPU會映射FLASH到它的內存空間(映
射一部分、還是全部FLASH空間?),然后執行 FLASH上的代碼。首先,進入
cpu/arc600/start.S中的入口_start,進行內存初始化,接著把U-BOOT的前0x1800字節
從 FLASH復制到內存的0x40800000處,也就是鏈接時的地址;然后對bss段進行清零,設
置堆棧指針,為運行C函數做準備;下一步,運行C函數檢測在規定時間內是否有按鍵發生,
如有則加載boot的后半部分(0x40801800——DATA_END)并啟動boot,無則加載kernel并
啟動 kernel。U-BOOT啟動的前半部分流程如圖2所示。
U-BOOT啟動的后半部分,會進行heap、環境變量(env)的初始化,PHY驅動的加載等工作,
然后進入一個無限循環開始shell的運行,shell運行過程中的內存示意如圖3所示。其中,
heap和stack依次排列在bss段的后面,圖中所示的free area則為U-BOOT未用到的內存。
圖 3中,heap區域為malloc()提供內存。在uClib庫中,malloc()是通過sbrk()或者mmap()
實現的,而sbrk()和 mmap()是在內核中實現的。U-BOOT作為系統最早運行的程序,沒有內
核的支持。為了實現malloc(),它定義一個32K的heap區域,在此區域的基礎上實現了簡
化版的sbrk()。
圖3中,stack區域是在U-BOOT啟動的前半部分中第三步設置的。它首先根據BSS_END、heap
大小和stack大小算出stack_bottom的值,然后設置堆棧指針SP和幀指針FP為
stack_bottom - 4。
u-boot 中的命令實現
軟件平臺:u-boot-1.1.6,gcc for blackfin,visual dsp 5.0
我們知道,u-boot的運行過程是首先進行一些初始化化工作,然后在一個死
循環中不斷接收串口的命令并進行解釋執行,下面我們就看看執行部分代碼的實
現,見common/main.c中的run_command:
int run_command (const char *cmd, int flag)
{

while (*str) {

/* find macros in this token and replace them */
process_macros (token, finaltoken);
/* Extract arguments */
if ((argc = parse_line (finaltoken, argv)) == 0) {
rc = -1; /* no command at all */
continue;
}
/* Look up command in command table */
if ((cmdtp = find_cmd(argv[0])) == NULL) {
printf ("Unknown command '%s' - try 'help'\n", argv[0]);
rc = -1; /* give up after bad command */
continue;
}
/* found - check max args */
if (argc > cmdtp->maxargs) {
printf ("Usage:\n%s\n", cmdtp->usage);
rc = -1;
continue;
}

/* OK - call function to do the command */
if ((cmdtp->cmd) (cmdtp, flag, argc, argv) != 0) {
rc = -1;
}
repeatable &= cmdtp->repeatable;
/* Did the user stop this? */
if (had_ctrlc ())
return 0; /* if stopped then not repeatable */
}
return rc ? rc : repeatable;
}
很簡單的一個過程,擴展宏定義 -> 分析命令及其參數 -> 查找命令 -> 執
行命令,有意思的地方在查找命令上(common/command.c):
cmd_tbl_t *find_cmd (const char *cmd)
{
cmd_tbl_t *cmdtp;
cmd_tbl_t *cmdtp_temp = &__u_boot_cmd_start; /*Init value */
const char *p;
int len;
int n_found = 0;
/*
* Some commands allow length modifiers (like "cp.b");
* compare command name only until first dot.
*/
len = ((p = strchr(cmd, '.')) == NULL) ? strlen (cmd) : (p - cmd);
for (cmdtp = &__u_boot_cmd_start;
cmdtp != &__u_boot_cmd_end;
cmdtp++) {
if (strncmp (cmd, cmdtp->name, len) == 0) {
if (len == strlen (cmdtp->name))
return cmdtp; /* full match */
cmdtp_temp = cmdtp; /* abbreviated command ? */
n_found++;
}
}
if (n_found == 1) { /* exactly one match */
return cmdtp_temp;
}
return NULL; /* not found or ambiguous command */
}
看起來還是很簡單的一個過程,在一個命令數組中查找是否有指定名稱的命
令。問題是,在這里使用的兩個符號__u_boot_cmd_start和__u_boot_cmd_end,
在所有的C文件中都找不到它們的定義,那么它們的空間從哪里來呢?這些分散
在不同文件中的結構體又是如何能夠放在同一個數組中呢?
答案就在board/bf561-ezkit/u-boot.lds.s中,這個文件其實就是一個鏈
接文件,類似于VDSP中的LDF文件,see see:
___u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
___u_boot_cmd_end = .;
這幾句話的意思其實就是指示鏈接器將所有.u_boot_cmd數據段中的內容全
部放在一起,而且___u_boot_cmd_start和___u_boot_cmd_end是不會占用任何
存儲空間的,它們只是用來指示地址的兩個符號而已。那么數據段的定義在哪里
呢?看看U_BOOT_CMD的宏定義吧(include/command.h):
#define Struct_Section __attribute__ ((unused,section (".u_boot_cmd")))
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}
__attribute__ ((unused,section (".u_boot_cmd")))就指示編譯器將這些用U_BOOT_CMD定
義的結構體放在.u_boot_cmd這個數據段中。
如果要在VDSP中編譯u-boot,那么就需要在LDF文件中也定義這樣一個數
據段:
.u_boot_cmd
{
___u_boot_cmd_start = .;
INPUT_SECTIONS(common.dlb(.u_boot_cmd) common.dlb(__u_boot_cmd))
___u_boot_cmd_end = .;
} > MEM_SDRAM_U_BOOT
不過讓人郁悶的是:如果在一個定義命令的C文件中沒有一個函數被其它文
件引用,VDSP在鏈接時將認為這是一個多余的文件,從而不會將這個文件中的
函數鏈接進來,當然就無法使用其中定義的這些命令,如cmd_load.c。
解決的辦法可以是在這些文件中添加一個空函數,并在主函數中調用它們,
這樣VDSP就會把這個文件鏈接進來了。
U-BOOT 環境變量實現
(基于smdk2410)
1.相關文件
common/env_common.c
供u-boot 調用的通用函數接口,它們隱藏了env 的不同實現方式,比如dataflash, epprom, flash 等
common/env_dataflash.c
env 存儲在dataflash 中的實現
common/env_epprom.c
env 存儲在epprom 中的實現
common/env_flash.c
env 存儲在flash 中的實現
common/env_nand.c
env 存儲在nand 中的實現
common/env_nvedit.c
實現u-boot 對環境變量的操作命令
environment.c
環境變量以及一些宏定義
env 如果存儲在Flash 中還需要Flash 的支持。
2.數據結構
env 在 u-boot 中通常有兩種存在方式,在永久性存儲介質中( Flash NVRAM 等 )在SDRAM,可以
配置不使用 env 的永久存儲方式,但這不常用。u-boot 在啟動的時候會將存儲在永久性存儲介質中的
env 重新定位到 RAM 中,這樣可以快速訪問,同時可以通過saveenv 將 RAM 中的 env 保存到永久
性存儲介質中。
在include/environment.h 中定義了表示env 的數據結構
typedef struct environment_s
{
unsigned long crc; /* CRC32 over data bytes */
#ifdef CFG_REDUNDAND_ENVIRONMENT
unsigned char flags; /* active/obsolete flags */
#endif
unsigned char data[ENV_SIZE]; /* Environment data */
} env_t;
關于以上結構的說明:
crc 是u-boot 在保存env 的時候加上去的校驗頭,在第一次啟動時一般 crc 校驗會出錯,這很正常,因
為這時 Flash 中的數據無效。
data 字段保存實際的環境變量。u-boot 的 env 按 name=value”\0”的方式存儲,在所有env 的最后
以”\0\0”表示整個 env 的結束。新的name=value 對總是被添加到 env 數據塊的末尾,當刪除一個
name=value 對時,后面的環境變量將前移,對一個已經存在的環境變量的修改實際上先刪除再插入。
env 可以保存在 u-boot 的 TEXT 段中,這樣 env 就可以同 u-boot 一同加載入RAM 中,這種方法
沒有測試過。
上文提到u-boot 會將 env 從 flash 等存儲設備重定位到 RAM 中,在 env 的不同實現版本
( env_xxx.c )中定義了 env_ptr, 它指向 env 在RAM 中的位置。u-boot 在重定位 env 后對環境
變量的操作都是針對 env_ptr。
env_t 中除了數據之外還包含校驗頭,u-boot 把env_t 的數據指針有保存在了另外一個地方,這就
是 gd_t 結構( 不同平臺有不同的 gd_t 結構 ),這里以ARM 為例僅列出和 env 相關的部分
typedef struct global_data
{

unsigned long env_off; /* Relocation Offset */
unsigned long env_addr; /* Address of Environment struct ??? */
unsigned long env_valid /* Checksum of Environment valid */

} gd_t;
<include/asm-arm/Global_data.h>
gd_t.env_addr 即指向 env_ptr->data。
3.ENV 的初始化
start_armboot : ( lib_arm/board.c )
*env_init : env_xxx.c( xxx = nand | flash | epprom … )
env_relocate : env_common.c
*env_relocate_spec : env_xxx.c( xxx=nand | flash | eporom… )
3.1env_init
實現 env 的第一次初始化,對于nand env (非embedded 方式):
Env_nand.c : env_init
gd->env_addr = (ulong)&default_environment[0]; //先使gd->env_addr 指向默認的環境變量
gd->env_valid = 1;// env 有效位置1
3.2 env_relocate
#ifdefine ENV_IS_EMBEDDED
…(略)
#else
env_ptr = (env_t *)malloc (CFG_ENV_SIZE);
#endif
if( gd->env_valid == 0) // 在 Env_annd.c : env_init 中已經將 gd->env_valid 置1
{

}
else
env_relocate_spec ();// 調用具體的 env_relocate_spec 函數
gd->env_addr = (ulong)&(env_ptr->data);// 最終完成將環境變量搬移到內存
這里涉及到兩個和環境變量有關的宏
ENV_IS_EMBEDDED : env 是否存在于 u-boot TEXT 段中
CFG_ENV_SIZE : env 塊的大小
實際上還需要幾個宏來控制u-boot 對環境變量的處理
CFG_ENV_IS_IN_NAND : env 塊是否存在于Nand Flash 中
CFG_ENV_OFFSET : env 塊在 Flash 中偏移地址
3.3*env_relocate_spec
這里僅分析 Nand Flash 的 env_relocate_spec 實現
如果未設置 CFG_ENV_OFFSET_REDUND,env_relocate_spec 的實現如下 :
void env_relocate_spec (void)
{
#if !defined(ENV_IS_EMBEDDED)
ulong total;
int ret;
total = CFG_ENV_SIZE;
ret = nand_read(&nand_info[0], CFG_ENV_OFFSET, &total, (u_char*)env_ptr);
if (ret || total != CFG_ENV_SIZE)
return use_default();
if (crc32(0, env_ptr->data, ENV_SIZE) != env_ptr->crc)
return use_default();
#endif /* ! ENV_IS_EMBEDDED */
}
上面的代碼很清楚的表明了 env_relocate_spec 的意圖, 調用 nand_read 將環境變量從
CFG_ENV_OFFSET 處讀出,環境變量的大小為 CFG_ENV_SIZE 注意 CFG_ENV_OFFSET 和
CFG_ENV_SIZE 要和 Nand Flash 的塊/頁邊界對齊。讀出數據后再調用crc32 對env_ptr->data 進
行校驗并與保存在 env_ptr->crc 的校驗碼對比,看數據是否出錯,從這里也可以看出在系統第一次啟動
時,Nand Flash 里面沒有存儲任何環境變量,crc 校驗肯定回出錯,當我們保存環境變量后,接下來再啟
動板子u-boot 就不會再報crc32 出錯了。
4. ENV 的保存
由上問的論述得知, env 將從永久性存儲介質中搬到RAM 里面,以后對env 的操作,比如修改環境變量
的值,刪除環境變量的值都是對這個 env 在RAM 中的拷貝進行操作,由于RAM 的特性,下次啟動時所
做的修改將全部消失,u-boot 提供了將env 寫回 永久性存儲介質的命令支持 : saveenv,不同版本的
env ( nand flash, flash … ) 實現方式不同, 以Nand Flash 的實現( 未定義
CFG_ENV_OFFSET_REDUND)為例
Env_nand.c : saveenv
int saveenv(void)
{
ulong total;
int ret = 0;
puts ("Erasing Nand...");
if (nand_erase(&nand_info[0], CFG_ENV_OFFSET, CFG_ENV_SIZE))
return 1;
puts ("Writing to Nand... ");
total = CFG_ENV_SIZE;
ret = nand_write(&nand_info[0], CFG_ENV_OFFSET, &total, (u_char*)env_ptr);
if (ret || total != CFG_ENV_SIZE)
return 1;
puts ("done\n");
return ret;
}
Nand Flash 的 saveenv 命令實現很簡單,調用nand_erase 和nand_write 進行Nand Flash 的
erase, write。nand_write/erase 使用的是u-boot 的nand 驅動框架,我在做開發的過程中使用的是
nand_legacy 驅動, 所以可以把nand_erase 和nand_write 改成nand_legacy_erase 和
nand_legacy_rw 就可實現nand_legacy 驅動的保存環境變量版本。
========================================================
================
U-Boot 環境變量
U-Boot 通過環境變量(env)為用戶提供一定程度的可配置性,這些環境變量包括串口終端所使用的波特
率(baudrate)、啟動操作系統內核的參數(bootargs)、本地IP 地址(ipaddr)、網卡MAC 地址(ethaddr)
等等。環境變量可以固化到非易失性存儲介質中,使用printenv / saveenv 命令來查看和修改。本例中,
環境變量固化到Flash 中(AM29LV160DB,2MB)。
可配置性意味著環境變量中的項目是可以被添加、刪除和修改的,即環境變量的內容可能會頻繁變化。為
了不讓這種變化對U-Boot 的代碼和數據造成破壞,通常的選擇是在Flash 中準備一個專用的sector 來
存儲環境變量。簡化的ROM 分配模型如下圖所示,monitor 占用 Flash 前256KB,env 置于其后,Flash
的最后一部分用來存放壓縮的操作系統內核。
AM29LV160DB 分為35 個sector,地址范圍分配如下:
Sector Size ( KB ) Address Range ( Hex )
SA0 16 000000 ~ 003FFF
SA1 8 004000 ~ 005FFF
SA2 8 006000 ~ 007FFF
SA3 16 008000 ~ 00FFFF
SA4 32 010000 ~ 01FFFF
SA5 64 020000 ~ 02FFFF
... 64 ...
SA34 64 1F0000 ~ 1FFFFF
由于U-Boot 代碼通常達到100KB 左右,且必須從地址0 處開始,按照這樣的分配方式,我們將不得不
為env 分配一塊64KB 的sector,而實際中使用到的可能只是其中的幾百字節!U-Boot 還會為env 在
RAM 中保持一塊同樣大小的空間,這就造成ROM 和RAM 空間不必要的浪費。
為了盡可能地減少資源浪費,同時保證系統的健壯性,我們可以把env 放置在Flash 中容量最小的sector
里。這樣,env 嵌入(embed)到U-Boot 的代碼段。在common/environment.h 中會比較env 和
monitor 的范圍,如果確定env 位于monitor 內,則定義ENV_IS_EMBEDDED。
# if (CFG_ENV_ADDR >= CFG_MONITOR_BASE) && \
(CFG_ENV_ADDR+CFG_ENV_SIZE) <= (CFG_MONITOR_BASE + CFG_MONITOR_LEN)
# define ENV_IS_EMBEDDED 1
# endif
修改board/buf/EVB44B0/u-boot.lds:
/*------------------------------------------------------------
* Environment Variable setup
*/
#define CFG_ENV_IS_IN_FLASH 1 /* 使用Flash 存儲env */
#define CFG_ENV_SIZE 0x2000 /* 容量8KB (SA1) */
#define CFG_ENV_OFFSET 0x4000 /* 偏移地址 (SA1) */
$(LD)將一系列的obj 文件連接成elf 格式文件,其輸出文件的內存布局由linker script 決定。修改
board/buf/EVB44B0/u-boot.lds:
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
cpu/s3c44b0/start.o (.text)
board/buf/EVB44B0/lowlevel_init.o (.text)
lib_generic/string.o (.text)
lib_generic/zlib.o (.text)
. = env_offset;
common/environment.o (.text)
*(.text)
}
/* other sections ... */
}
從u-boot.map 選擇那些U-Boot 運行必須的,且不易受CFG_*宏影響的obj 文件,填充到start.o 后
面。可以參考board/trab/u-boot.lds。
env_offset 定義在common/environment.c 中:
#define GEN_SYMNAME(str) SYM_CHAR #str
#define GEN_VALUE(str) #str
#define GEN_ABS(name, value) \
asm (".globl " GEN_SYMNAME(name)); \
asm (GEN_SYMNAME(name) " = " GEN_VALUE(value))
GEN_ABS(env_offset, CFG_ENV_OFFSET);
u-boot 代碼鏈接的問題
環境和配置:u-boot-1.1.2, arm-linux-gcc(v3.2), redhat linux9.0,
cpu(s3c44b0), board(B2)
在/board/dave/B2/u-boot.lds有
. = 0x00000000;
. = ALIGN(4);
.text :
{
cpu/s3c44b0/start.o (.text)
*(.text)
}
而/board/dave/B2/config.mk中有
TEXT_BASE = 0x0C100000
最后編譯出來的代碼反匯編得到
0c100000 <_start>:
c100000: ea00000a b c100030 <reset>
c100004: e28ff303 add pc, pc, #201326592 ; 0xc000000
c100008: e28ff303 add pc, pc, #201326592 ; 0xc000000
c10000c: e28ff303 add pc, pc, #201326592 ; 0xc000000
c100010: e28ff303 add pc, pc, #201326592 ; 0xc000000
c100014: e28ff303 add pc, pc, #201326592 ; 0xc000000
c100018: e28ff303 add pc, pc, #201326592 ; 0xc000000
c10001c: e28ff303 add pc, pc, #201326592 ; 0xc000000
0c100020 <_TEXT_BASE>:
c100020: 0c100000 ldceq 0, cr0, [r0]
0c100024 <_armboot_start>:
c100024: 0c100000 ldceq 0, cr0, [r0]
0c100028 <_bss_start>:
c100028: 0c115694 ldceq 6, cr5, [r1], -#592
0c10002c <_bss_end>:
c10002c: 0c1198a4 ldceq 8, cr9, [r1], -#656
0c100030 <reset>:
c100030: e10f0000 mrs r0, CPSR
鏈接得到的起始地址為什么是TEXT_BASE,而不是0呢,所以現在只能夠下載到
ram中運行,但是無法燒寫道flash中跑,這是怎么回事呢?u-boot中應該是
start.S中的這段代碼在flash中運行吧,后面就把自身拷貝到ram中TEXT_BASE
地址處,為什么在鏈接文件中指定的_start的起始地址為0x00000000呢?
blob中把整個代碼分為兩部分,所以有兩個連接文件,前面1k在flash中運行,
鏈接起始地址為0x0,ram中運行的,然后通過工具把兩部分組合到一起,但是
u-boot是怎么做的呢?
迷惑中,請幫忙解答,謝先!
Re: u-boot代碼鏈接的問題
好象與這個地址沒關系的
u-boot既可以在SDRAM中,
也可在Flash中運行
Re: u-boot代碼鏈接的問題
兄弟,首先需要知道CPU的啟動方式,一般來說有BOOTROM,SPI,FLASH這三種
方式;一般來說也不會存在從SDRAM/DRAM上啟動機器,因為它們不可能作為永久
存儲。
然后就是需要知道,外設地址映射關系,對于CPU來說總是有統一的IO地址映射,
并且在不同的訪問模式下,地址映射關系不一定相同。
還有,代碼可以運行在不同的介質上,如:上述三種再包括RAM/ROM。
另外:還要知道代碼段的鏈接地址,這個你應該是知道的。
最后,就可以考慮你的FLASH啟動問題了。在Uboot里面的-text的參數,應該是
真對某個硬件系統的配置,這個就是FLASH的高端地址或者低端地址。
一般來說,bootloader的代碼的第一段總是運行在永久介質上,例如:FLASH,然
后才將代碼搬移到DRAM中,這個時候在dram中執行第二段BOOTLOADER的代碼段,
也就是你說下載到內存中的說法,實際上他都已經運行了一個階段了。在內存中
的運行入口地址,按照系統情況可以自行安排了。
因此,這個最開始的FLASH地址,肯定不是0,是什么看看你的單板的DATASHEET
和編程參考
Re: u-boot代碼鏈接的問題
謝謝,我的板子cpu是s3c44b0,不支持memory remap,flash為4M,從0x00000000
到0x00400000,
sdram為8M,地址從0x0c000000到0x0c800000,我不清楚的問題時鏈接腳本中指
定的_start的入口地址為0x00000000,為何編譯出來的代碼。鏈接地址是從
0x0c100000開始的,即把TEXT_BASE作為_start的入口地址了,如果我把
TEXT_BASE改為0x00000000,那么后面的rellocate代碼還有意義嗎?
Re: u-boot代碼鏈接的問題
我猜測,問題可能有可能,我說的僅僅是may be:
和我前面說的一樣,boot loader一般有兩段代碼段(獨立的),在連接的時候也
是分別連接,故此有兩個代碼連接文件,和編譯過程共產生的兩個MAP文件,可
以查一下你看的連接文件和編譯文件是不是對應的。
另外有兩個建議:
1:這個也是疑惑,我看了你的問題后,作了這樣的前提假設(個人的理解):在
dram里的loader stage起始地址是定義在sdram的最低端。從這個假設出來,我
覺得這個鏈接也是有問題的,因為一般來說sdram的最低端是做向量表、和模式
棧頂區、參數區來用的,這個肯定有問題
2:分析這個問題,首先要確定你的編譯過程,可以把編譯過程定向到一個文件中
仔細的分析,可以看到編譯鏈接各個細節。不妨發給我一份
hls780204cn@vip.sina.com
Re: u-boot代碼鏈接的問題
TEXTADDR 是內核的虛擬起始地址,并且在arch/<target>/ 下的Makefile 中指
定它的值。這個地址必須與引導裝載程序使用的地址相匹配。 一旦引導裝載程序
將內核復制到閃存或DRAM 中,內核就被重新定位到TEXTADDR — 它通常在DRAM
中,然后,引導裝載程序將控制轉給這個地址,以便內核能開始執行。
Re: u-boot代碼鏈接的問題
我已經理解了:
“鏈接得到的起始地址為什么是TEXT_BASE,而不是0呢,”
因為u_boot如果從flash運行的話,那么它會將自己的代碼拷貝到RAM中,然后
運行。u-boot開始部分代碼與編譯的入口沒有關系,而主要的代碼是在RAM中運
行,因此編譯的入口地址是TEXT_BASE.因此u-boot既可以flash運行,也可以
ram運行。
“為什么在鏈接文件中指定的_start的起始地址為0x00000000呢?”
lds文件中的起始地址為0x00000000是不起作用的,由-TTEXT_BASE參數替代的。
剛開始比較疑惑的原因是對:
126 relocate: /* relocate U-Boot to RAM */
127 adr r0, _start /* r0 <- current position of code */
adr這條指令沒有理解正確,因為把它想成mv r0,_start了,實際上adr這里的
_start是相對的,如果從flash運行的話,r0就是0, 如果從ram運行的話,r0
就是C100000。
我現在可以運行u-boot了,串口可以顯示內容并且可以使用命令。但網卡驅動和
flash驅動還有問題。慢慢搞就可以搞定,因為可以用printf調試的。
Re: u-boot代碼鏈接的問題
樓上是不是說 因為adr指令是小范圍地址讀取指令,所以在不同的運行環境下,
_start的值不同啊。
針對44box中的代碼
adr r0, real_vectors
add r2, r0, #1024
ldr r1, =0x0c000000
add r1, r1, #0x08
vector_copy_loop:
ldmia r0!, {r3-r10}
stmia r1!, {r3-r10}
cmp r0, r2
ble vector_copy_loop
復制向量中斷,為什么要復制1024個字節呢?
問得比較弱,見笑了。
Re: u-boot代碼鏈接的問題
多一點是沒有關系的
Re: u-boot代碼鏈接的問題
在FLASH中以相對地址運行,然后一個絕對跳轉完成從FLASH到RAM的切換,應
該制定為UBOOT在RAM中的起始地址!
看makefile 文件
$(obj)u-boot.img: $(obj)u-boot.bin
./tools/mkimage -A $(ARCH) -T firmware -C none \
-a $(TEXT_BASE) -e 0 \
-n $(shell sed -n -e 's/.*U_BOOT_VERSION//p' $(VERSION_FILE) | \
sed -e 's/"[ ]*$$/ for $(BOARD) board"/') \
-d $< $@
ldr 和adr 在使用標號表達式作為操作數的
區別
ARM匯編有ldr指令以及ldr、adr偽指令,他門都可以將標號表達式作為操作
數,下面通過分析一段代碼以及對應的反匯編結果來說明它們的區別。
ldr r0, _start
adr r0, _start
ldr r0, =_start
_start:
b _start
編譯的時候設置 RO 為 0x30000000,下面是反匯編的結果:
0x00000000: e59f0004 ldr r0, [pc, #4] ; 0xc
0x00000004: e28f0000 add r0, pc, #0 ; 0x0
0x00000008: e59f0000 ldr r0, [pc, #0] ; 0x10
0x0000000c: eafffffe b 0xc
0x00000010: 3000000c andcc r0, r0, ip
1.ldr r0, _start
這是一條指令,從內存地址 _start 的位置把值讀入。
在這里_start是一個標號(是一個相對程序的表達式),匯編程序計算相對于 PC 的
偏移量,并生成相對于 PC的前索引的指令:ldr r0, [pc, #4]。執行指令后,r0 =
0xeafffffe。
ldr r0, _start是根據_start對當前PC的相對位置讀取其所在地址的值,因此
可以在和_start標號的相對位置不變的情況下移動。
2.adr r0, _start
這是一條偽指令,總是會被匯編程序匯編為一個指令。匯編程序嘗試產生單個 ADD
或 SUB 指令來裝載該地址。如果不能在一個指令中構造該地址,則生成一個錯誤,并且匯
編失敗。
在這里是取得標號_start 的地址到 r0,因為地址是相對程序的,因此ADR產生
依賴于位置的代碼,在此例中被匯編成:add r0, pc, #0。因此該代碼可以在和標號相對位
置不變的情況下移動;
假如這段代碼在 0x30000000 運行,那么 adr r0, _start 得到 r0 = 0x3000000c;
如果在地址 0 運行,就是 0x0000000c 了。
通過這一點可以判斷程序在什么地方運行。U-boot中那段relocate代碼就是通過
adr實現當前程序是在RAM中還是flash中,下面進行簡要分析。
relocate: /* 把U-Boot重新定位到RAM */
adr r0, _start /* r0是代碼的當前位置 */
/* adr偽指令,匯編器自動通過當前PC的值算出 如果執行到_start時PC的值,放
到r0中:
當此段在flash中執行時r0 = _start = 0;當此段在RAM中執行時_start =
_TEXT_BASE(在board/smdk2410/config.mk中指定的值為0x30000000,即u-boot在把代碼
拷貝到RAM中去執行的代碼段的開始) */
ldr r1, _TEXT_BASE /* 測試判斷是從Flash啟動,還是RAM */
/* 此句執行的結果r1始終是0x30000000,因為此值是又編譯器指定的(ads中設置,
或-D設置編譯器參數) */
cmp r0, r1 /* 比較r0和r1,調試的時候不要執行重定位 */
3.ldr r0, =_start
這是一條偽指令,是一個相對程序的或外部的表達式。匯編程序將相對程序的標
號表達式 label-expr 的值放在一個文字池中,并生成一個相對程序的 LDR 指令來從文字
池中裝載該值,在此例中生成的指令為:ldr r0, [pc, #0],對應文字池中的地址以及值為:
0x00000010: 3000000c。如果 label-expr 是一個外部表達式,或者未包含于當前段內,則
匯編程序在目標文件中放置一個鏈接程序重定位命令。鏈接程序在鏈接時生成地址。
因此取得的是標號 _start 的絕對地址,這個絕對地址(運行地址)是在連接的
時候確定的。它要占用 2 個 32bit 的空間,一條是指令,另一條是文字池中存放_start 的
絕對地址。因此可以看出,不管這段代碼將來在什么地方運行,它的結果都是 r0 =
0x3000000c。由于ldr r0, =_start取得的是_start的絕對地址,這句代碼可以在_start
標號的絕對位置不變的情況下移動;如果使用寄存器pc在程序中可以實現絕對轉移。
參考資料:
1. ARM DUI 0204BSC,RealView 編譯工具 2.0 版 匯編程序指南,
http://infocenter.arm.com/help/index.jsp
2. GNU匯編使用經驗, http://blog.chinaunix.net/u1/37614/showart_390095.html
3. 對.lds連接腳本文件的分析,
http://blog.chinaunix.net/u1/58780/showart.php?id=462971
start_armboot 淺析
ARM920t 架構的CPU 在完成基本的初始化后(ARM 匯編代碼),就進入它的C 語言代
碼,而C 語言代碼的入口就是start_armboot, start_armboot 在lib_arm/board.c 中。start_armboot
將完成以下工作。
1.全局數據結構的初始化
比如gd_t 結構的初始化:
251 gd = (gd_t*)(_armboot_start – CFG_MALLOC_LEN – sizeof(gd_t));
_armboot_start 是u-boot 在RAM 中的開始地址(對于u-boot 最終搬移到RAM 中運行的情
況),CFG_MALLOC_LEN 在include/configs/<board name>.h 中定義。
bd_t 結構的初始化:
272 gd->bd = (bd_t*)((char*)gd-sizeof(bd_t));
u-boot 把bd_t 結構緊接著gd_t 結構存放。
內存分配的初始化
316 mem_malloc_init(_armboot_start-CFG_MALLOC_LEN);
經過以上的初始化后,u-boot 在內存中的布局為(在底端為低地址)
-----------------------------
BSS
-----------------------------
U-BOOT TEXT/DATA
-----------------------------
CFG_MALLOC_LEN
-----------------------------
gd_t
-----------------------------
bd_t
-----------------------------
STACK
-----------------------------
2.調用通用初始化函數
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
init_sequence[]是init_fnc_t 函數指針數組,這個數組包含了眾多初始化函數,比如cpu_init,
board_init 等。
//init_fnc_t *init_sequence[] = {
// cpu_init, /* basic cpu dependent setup */
// board_init, /* basic board dependent setup */
// interrupt_init, /* set up exceptions */
// env_init, /* initialize environment */
// init_baudrate, /* initialze baudrate settings */
// serial_init, /* serial communications setup */
// display_banner,
// dram_init, /* configure available RAM banks */
// display_dram_config,
// NULL,
// };
3.初始化具體設備
這一部分包括對Flash,LCD,網絡的初始化等,例如
318 #if (CONFIG_COMMANDS & CFG_CMD_NAND)
puts ("NAND: ");
nand_init(); /* go init the NAND */
#endif
367 devices_init();
386 #ifdef CONFIG_DRIVER_CS8900
cs8900_get_enetaddr (gd->bd->bi_enetaddr);
#endif
4.初始化環境變量
環境變量在通用初始化函數里面,已經初始化一次(env_init),這里調用env_relocate 對環
境變量進行重新定位。在我的另一篇文章”U-BOOT ENV 實現”中有對環境變量實現的討論。
5.進入主循環
當然start_armboot 除了以上工作外,還完成其它的初始化工作,具體參考lib_arm/board.c,
在一切準備就緒之后,就進入u-boot 的主循環:
416 for (;;) {
main_loop ();
}
main_loop 的代碼比較長,基本是就是執行用戶的輸入命令。
u-boot 編譯過程
現在介紹一下u-boot 的編譯過程,這里用的uboot 版本是U-Boot 2008.10,硬
件用smdk2410,這個板子用得比較普遍,uboot 已經有對其的支持。通過我們
對編譯過程和代碼的了解,我們也容易用uboot 支持我們自己需要的硬件。
編譯命令非常簡單:
make smdk2410_config (生成配置)
make all (生成最終文件)
當然,更好的做法是把編譯出的文件生成到另外一個目錄,并make clean 如:
export BUILD_DIR=../tmp
make distclean
make smdk2410_config
make all
現在,我們可以來看看Makefile,u-boot 的Makefile 文件非常大。但是,其結
構卻并不復雜。
u-boot 已經支持了很多硬件,前半部分是共用部分,編譯出最終的uboot 可執行
文件。
而后半部分,是為各種不同的硬件進行配置,每種硬件有一個目標,每個的做法
都非常類似,我們用到的是:
smdk2410_config : unconfig
@$(MKCONFIG) $(@:_config=) arm arm920t smdk2410 NULL s3c24x0
這里的 MKCONFIG := $(SRCTREE)/mkconfig
實際上是調用腳本mkconfig,而這個腳本做的工作簡單如下:
建立include/config.mk 文件
echo "ARCH = $2" > config.mk
echo "CPU = $3" >> config.mk
echo "BOARD = $4" >> config.mk
echo "VENDOR = $5" >> config.mk
echo "SOC = $6" >> config.mk
建立include/config.h
echo "#include <configs/$1.h>" >>config.h
在這里$1-$6 的值分別是:smdk2410 arm arm920t smdk2410 NULL s3c24x
0
而執行了 make smdk2410_config 之后,就生成了相應的config.mk,config.
h 兩個文件。
在config.mk 文件中,定義了相應硬件信息 : ARCH CPU BOARD VENDO
R SOC
在config.h 文件中,包含了相應硬件的頭文件smdk2410.h ,位于include\conf
igs 目錄下。
如果新建自己的硬件項目,那么也需要建立相應的頭文件在這個地方。
這樣,uboot 的配置已經生成,下一次介紹make all 的過程。
接著上次,這次介紹make all 的過程。
首先,介紹一下生成的config.mk 和 config.h 如何使用,得到正確配置的。
config.mk 直接被include 到Makefile 來,并使用其定義如下:
include $(obj)include/config.mk
export ARCH CPU BOARD VENDOR SOC
這樣可以直接選擇需要編譯的模塊,例如:
LIBS += cpu/$(CPU)/$(SOC)/lib$(SOC).a
LIBS += lib_$(ARCH)/lib$(ARCH).a
LIBBOARD = board/$(BOARDDIR)/lib$(BOARD).a
config.h 被include/common.h 所包含,而它有包含了相應硬件的頭文件。
common.h <--- config.h <--- smdk2410.h
除了在源程序中,使用這些頭文件的定義之外,uboot 還有一個腳本通過解析c
ommon.h 以及其包含的所有頭文件信息,來生成配置信息如下形式:
CONFIG_BAUDRATE=115200
CONFIG_NETMASK="255.255.255.0"
CONFIG_DRIVER_CS8900=y
CONFIG_ARM920T=y
CONFIG_RTC_S3C24X0=y
CONFIG_CMD_ELF=y
而頭文件的定義的形式如下,對比可以看出腳本的工作原理。
#define CONFIG_BAUDRATE 115200
#define CONFIG_NETMASK 255.255.255.0
#define CONFIG_DRIVER_CS8900 1 /* we have a CS8900 on-bo
ard */
#define CONFIG_ARM920T 1 /* This is an ARM920T Core */
#define CONFIG_RTC_S3C24X0 1
#define CONFIG_CMD_ELF
通過這樣,uboot 可以自動得到一個模塊選擇的配置功能。如果我們需要添加什
么定義或者功能,也可以在相應的頭文件中加入定義實現。
現在,配置已經得到,就看最后的編譯流程。
編譯分為五大部分,分別如下:
1. $(SUBDIRS) 工具,例子等,包括目錄:tools examples api_examples
2. $(OBJS) 啟動模塊 cpu/arm920t/start.o
3. $(LIBBOARD) 板子支持模塊 board/smdk2410/libsmdk2410.a
4. $(LIBS) 其他模塊,有諸如以下模塊:
cpu/arm920t/libarm920t.a
cpu/arm920t/s3c24x0/libs3c24x0.a
lib_arm/libarm.a
fs/jffs2/libjffs2.a
fs/yaffs2/libyaffs2.a
net/libnet.a
disk/libdisk.a
drivers/bios_emulator/libatibiosemu.a
drivers/mtd/libmtd.a
drivers/net/libnet.a
drivers/net/phy/libphy.a
drivers/net/sk98lin/libsk98lin.a
drivers/pci/libpci.a
common/libcommon.a /
5. $(LDSCRIPT) 鏈接腳本
編譯完成這五部分,鏈接成elf 格式的u-boot 文件,最后通過objcopy -O bina
ry 命令將elf 格式轉換成為raw binary 格式的文件u-boot.bin 就可以燒到板子上
使用了。
mkconfig 文件的分析
http://niutao.org/blog/?p=50
在編譯u-boot 之前都要執行”make XXX_config”命令,籠統的說是配置u-boot,使其編譯出
適合目標板的bootloader。那么該命令都做了那些工作,具體的執行過程是怎樣的?
我們首先從u-boot 的Makefile 文件看起,例如我們首先執行”make smd2410_config”
命令,則在Makefile 中會執行:
MKCONFIG := $(SRCTREE)/mkconfig
export MKCONFIG
smdk2410_config : unconfig
@$(MKCONFIG) $(@:_config=) arm arm920t smdk2410 NULL s3c24x0
也就是:
./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24×0
也就是說執行”make XXX_config”之后,實際上執行的是mkconfig 腳本,下面是對
mkconfig 文件的分析:
#!/bin/sh -e
APPEND=no # Default: Create new config file
BOARD_NAME="" # Name to print in make output
#如果命令行參數中有--,-a,-n 等參數,則執行以下循環。
#如果有-n XXX_config 或者-n XXX,則取出XXX 作為目標板的名字
while [ $# -gt 0 ] ; do
case "$1" in
--) shift ; break ;;
-a) shift ; APPEND=yes ;;
-n) shift ; BOARD_NAME="${1%%_config}" ; shift ;;
*) break ;;
esac
done
#如果傳遞給該腳本的參數小于4 個或者大于6 個,則退出
[ "${BOARD_NAME}" ] || BOARD_NAME="$1"
#如果BOARD_NAME 為空,則BOARD_NAME 等于傳遞給該腳本的第一個參數
[ $# -lt 4 ] && exit 1
[ $# -gt 6 ] && exit 1
echo "Configuring for ${BOARD_NAME} board..."
#OBJTREE 和SRCTREE 都是在Makefile 中導出的變量,分別為編譯目錄和源碼目錄
#如果編譯目錄和源碼目錄不為同一目錄,則執行一下命令,創建$(OBJTREE)/include 等目錄
if [ "$SRCTREE" != "$OBJTREE" ] ; then
mkdir -p ${OBJTREE}/include
mkdir -p ${OBJTREE}/include2
cd ${OBJTREE}/include2
rm -f asm
ln -s ${SRCTREE}/include/asm-$2 asm
LNPREFIX="../../include2/asm/"
cd ../include
rm -rf asm-$2
rm -f asm
mkdir asm-$2
ln -s asm-$2 asm
else
#如果編譯目錄和源碼目錄為同一個目錄,則進入include 目錄,刪除舊的asm 鏈接,創建目
#標板到asm 的鏈接,例如如果目標體系結構為arm,則創建asm 軟鏈接指向asm-arm。
cd ./include
rm -f asm
ln -s asm-$2 asm
fi
#刪除目標平臺下的舊的arch 鏈接
rm -f asm-$2/arch
#如果第6 個參數長度為0 或者其等于NULL,則創建arch-$3 到asm-$2 的鏈接,對于命令
#"./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0"
# $6=s3c24x0 不為空
# $3=arm920t $2=arm
#則最終執行的命令為"ln -s arch-s3c24x0 asm-arm/arch"
if [ -z "$6" -o "$6" = "NULL" ] ; then
ln -s ${LNPREFIX}arch-$3 asm-$2/arch
else
ln -s ${LNPREFIX}arch-$6 asm-$2/arch
fi
#如果目標平臺為ARM,則刪除建立的asm-arm 鏈接,重新建立從proc-armv 到asm-arm 的鏈接
if [ "$2" = "arm" ] ; then
rm -f asm-$2/proc
ln -s ${LNPREFIX}proc-armv asm-$2/proc
fi
#
# Create include file for Make
#
echo "ARCH = $2" > config.mk
echo "CPU = $3" >> config.mk
echo "BOARD = $4" >> config.mk
#如果參數5 存在并且不為NULL,則將VENDOR = $5 追加在config.mk 文件中
[ "$5" ] && [ "$5" != "NULL" ] && echo "VENDOR = $5" >> config.mk
#如果參數6 存在并且不為NULL,則將SOC = $6 追加在config.mk 文件中
[ "$6" ] && [ "$6" != "NULL" ] && echo "SOC = $6" >> config.mk
#創建config.h 文件
if [ "$APPEND" = "yes" ] # Append to existing config file
then
echo >> config.h
else
> config.h # Create new config file
fi
echo "/* Automatically generated - do not edit */" >>config.h
echo "#include <configs/$1.h>" >>config.h
exit 0
總結一下,”make XXX_config”總共做了一下工作:
(1)確定開發板名稱為BOARD_NAME = $1。
(2)創建一些鏈接文件,為編譯u-boot 做準備:
ln -s asm-$2 asm
ln -s arch-$6 asm-$2/arch
ln -s proc-armv asm-$2/proc #僅在目標平臺為arm 的時候才執行。
(3)創建頂層Makefile 包含的文件include/config.mk。
ARCH = $2
CPU = $3
BOARD = $4
VENDOR = $5
SOC = $6
(4)創建開發板相關的頭文件include/config.h。
/* Automatically generated - do not edit */
#include
從NAND 閃存中啟動U-BOOT 的設計
南昌大學信息工程學院 劉曄 汪燦 2007-02-12 21:29:20 電子設計應用/
引言
隨著嵌入式系統的日趨復雜,它對大容量數據存儲的需求越來越緊迫。而嵌入式設備低功耗、小體積以
及低成本的要求,使硬盤無法得到廣泛的應用。NAND閃存設備就是為了滿足這種需求而迅速發展起來的。
目前關于U-BOOT的移植解決方案主要面向的是微處理器中的NOR 閃存,如果能在微處理器上的NAND 閃存
中實現U-BOOT的啟動,則會給實際應用帶來極大的方便。
U-BOOT簡介
U-BOOT 支持ARM、 PowerPC等多種架構的處理器,也支持Linux、NetBSD和VxWorks等多種操作系統,
主要用來開發嵌入式系統初始化代碼bootloader。bootloader是芯片復位后進入操作系統之前執行的一段
代碼,完成由硬件啟動到操作系統啟動的過渡,為運行操作系統提供基本的運行環境,如初始化CPU、堆
棧、初始化存儲器系統等,其功能類似于PC機的BIOS。U-BOOT執行流程圖如圖1所示。
圖1 U-BOOT啟動流程圖
NAND 閃存工作原理
S3C2410開發板的NAND閃存由NAND閃存控制器(集成在S3C2410 CPU中)和NAND閃存芯片(K9F1208U0A)
兩大部分組成。當要訪問NAND閃存芯片中的數據時,必須通過NAND閃存控制器發送命令才能完成。所以,
NAND閃存相當于S3C2410的一個外設,而不位于它的內存地址區。
NAND閃存(K9F1208U0A)的數據存儲結構分層為:1設備(Device) = 4096 塊(Block);1塊= 32頁/行
(Page/row);1頁= 528B = 數據塊 (512B) + OOB塊 (16B)在每一頁中,最后16個字節(又稱OOB)在NAND
閃存命令執行完畢后設置狀態,剩余512個字節又分為前半部分和后半部分。可以通過NAND閃存命令
00h/01h/50h分別對前半部、后半部、OOB進行定位,通過NAND閃存內置的指針指向各自的首地址。
NAND閃存的操作特點為:擦除操作的最小單位是塊;NAND閃存芯片每一位只能從1變為0,而不能從
0變為1,所以在對其進行寫入操作之前一定要將相應塊擦除;OOB部分的第6字節為壞快標志,即如果不
是壞塊該值為FF,否則為壞塊;除OOB第6字節外,通常用OOB的前3個字節存放NAND閃存的硬件ECC(校
驗寄存器)碼;
從NAND 閃存啟動U-BOOT 的設計思路
如果S3C2410被配置成從NAND閃存啟動,上電后,S3C2410的NAND閃存控制器會自動把NAND閃存中
的前4K數據搬移到內部RAM中, 并把0x00000000設置為內部RAM的起始地址, CPU從內部RAM的
0x00000000位置開始啟動。因此要把最核心的啟動程序放在NAND閃存的前4K中。
由于NAND閃存控制器從NAND閃存中搬移到內部RAM的代碼是有限的,所以, 在啟動代碼的前4K里,
必須完成S3C2410的核心配置,并把啟動代碼的剩余部分搬到RAM中運行。在U-BOOT中, 前4K完成的主
要工作就是U-BOOT啟動的第一個階段(stage1)。
根據U-BOOT的執行流程圖,可知要實現從NAND閃存中啟動U-BOOT,首先需要初始化NAND閃存,并從
NAND閃存中把U-BOOT搬移到RAM中,最后需要讓U-BOOT支持NAND閃存的命令操作。 2開發環境
本設計中目標板硬件環境如下:CPU為S3C2410,SDRAM為HY57V561620,NAND閃存為64MB的K9F1208U0A。
主機軟件環境為Redhat9.0、 U-BOOT-1.1.3、gcc 2.95.3。修改U-BOOT的Makefile,加入:
wch2410_config : unconfig
@./mkconfig $(@:_config=) arm arm920t wch2410 NULL s3c24x0
即將開發板起名為wch2410,接下來依次進行如下操作:
mkdir board/wch2410
cp board/smdk2410 board/wch2410
mv smdk2410.c wch2410.c
cp include/configs/smdk2410.h include/configs/wch2410.h
export PATH="/usr/local/arm/2".95.3/bin:$PATH
最后執行:
make wch2410_config
make all ARCH="arm"
生成u-boot.bin,即通過了測試編譯。
具體設計
支持NAND 閃存的啟動程序設計
因為U-BOOT的入口程序是/cpu/arm920t/start.S,故需在該程序中添加NAND閃存的復位程序,以及實
現從NAND閃存中把U-BOOT搬移到RAM中的功能程序。
首先在/include/configs/wch2410.h中加入CONFIG_S3C2410_NAND_BOOT, 如下:
#define CONFIG_S3C2410_NAND_BOOT 1 @支持從NAND 閃存中啟動
然后在/cpu/arm920t/start.S中添加
#ifdef CONFIG_S3C2410_NAND_BOOT
copy_myself:
mov r10, lr
ldr sp, DW_STACK_START @安裝棧的起始地址
mov fp, #0 @初始化幀指針寄存器
bl nand_reset @跳到復位C函數去執行,執行NAND閃存復位
.......
/*從NAND閃存中把U-BOOT拷貝到RAM*/
ldr r0, =UBOOT_RAM_BASE@ 設置第1個參數: UBOOT在RAM中的起始地址
mov r1, #0x0 @ 設置第2個參數:NAND閃存的起始地址
mov r2, #0x20000 @ 設置第3個參數: U-BOOT的長度(128KB)
bl nand_read_whole @ 調用nand_read_whole(),把NAND閃存中的數據讀入到RAM中
tst r0, #0x0 @ 如果函數的返回值為0,表示執行成功
beq ok_nand_read @ 執行內存比較,把RAM中的前4K內容與NAND閃存中的前4K內容進行比較, 如
果完全相同, 則表示搬移成功
其中,nand_reset (),nand_read_whole()被加在/board/wch2410/wch2410.c中。
支持U-BOOT 命令設計
在U-BOOT下對nand閃存的支持主要是在命令行下實現對nand閃存的操作。對nand閃存實現的命令
為:nand info(打印nand Flash信息)、nand device(顯示某個nand閃存設備)、nand read(讀取nand閃
存)、nand write(寫nand閃存)、nand erease(擦除nand閃存)、nand bad(顯示壞塊)等。
用到的主要數據結構有:struct nand_flash_dev、struct nand_chip。前者包括主要的芯片型號、存
儲容量、設備ID、I/O總線寬度等信息;后者是具體對NAND閃存進行操作時用到的信息。
a. 設置配置選項
修改/include/configs/wch2410.h,主要是在CONFIG_COMMANDS中打開CFG_CMD_NAND選項。定義NAND
閃存控制器在SFR區中的起始寄存器地址、頁面大小,定義NAND閃存命令層的底層接口函數等。
b. 加入NAND閃存芯片型號
在/include/linux/mtd/ nand_ids.h中對如下結構體賦值進行修改:
static struct nand_flash_dev nand_flash_ids[] = {
......
{"Samsung K9F1208U0A", NAND_MFR_SAMSUNG, 0x76, 26, 0, 3, 0x4000, 0},
.......
}
這樣對于該款NAND閃存芯片的操作才能正確執行。
c. 編寫NAND閃存初始化函數
在/board/wch2410/wch2410.c中加入nand_init()函數。
void nand_init(void)
{
/* 初始化NAND閃存控制器, 以及NAND閃存芯片 */
nand_reset();
/* 調用nand_probe()來檢測芯片類型 */
printf ("%4lu MB\n", nand_probe(CFG_NAND_BASE) >> 20);
}
該函數在啟動時被start_armboot()調用。
最后重新編譯U-BOOT并將生成的u-boot.bin燒入NAND閃存中,目標板上電后從串口輸出如下信息:
U-Boot 1.1.3 (Nov 14 2006 - 11:29:50)
U-Boot code: 33F80000 -> 33F9C9E4 BSS: -> 33FA0B28
RAM Configuration:
Bank #0: 30000000 64 MB
## Unknown Flash on Bank 0: ID 0xffff, Size = 0x00000000 = 0 MB
Flash: 0 kB
NAND: 64 MB
In:serial
Out: serial
Err: serial
Hit any key to stop autoboot: 0
wch2410 #
結語
以往將U-BOOT移植到ARM9平臺中的解決方案主要針對的是ARM9中的NOR閃存,因為NOR閃存的結構
特點致使應用程序可以直接在其內部運行,不用把代碼讀到RAM中,移植過程相對簡單。從NAND閃存中啟
動U-BOOT的設計難點在于NAND閃存需要把U-BOOT的代碼搬移到RAM中,并要讓U-BOOT支持NAND閃存的
命令操作。本文介紹了實現這一設計的思路及具體程序。移植后,U-BOOT在嵌入式系統中運行良好。
參考文獻
1 杜春雷. ARM體系結構與編程[M]. 北京:清華大學出版社,2003
2 S3C2410 User’s Mannual[Z].Samsung
U-boot 給kernel 傳參數和kernel 讀取參數
—struct tag (以及補充)
U-boot 會給 Linux Kernel 傳遞很多參數,如:串口, RAM , videofb 等。而 Linu
x kernel 也會讀取和處理這些參數。兩者之間通過 struct tag 來傳遞參數。 U-boot 把
要傳遞給 kernel 的東西保存在 struct tag 數據結構中,啟動 kernel 時,把這個結構體
的物理地址傳給 kernel ; Linux kernel 通過這個地址,用 parse_tags 分析出傳遞過來
的參數。
本文主要以 U-boot 傳遞 RAM 和 Linux kernel 讀取 RAM 參數為例進行說明。
1 、u-boot 給kernel 傳RAM 參數
./common/cmd_bootm.c 文件中, bootm 命令對應的 do_bootm 函數,當分析 uI
mage 中信息發現 OS 是 Linux 時 , 調用 ./lib_arm/bootm.c 文件中的 do_bootm_lin
ux 函數來啟動 Linux kernel 。
在 do_bootm_linux 函數中:
void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
ulong addr, ulong *len_ptr, int verify)
{
......
#if defined (CONFIG_SETUP_MEMORY_TAGS) || \
defined (CONFIG_CMDLINE_TAG) || \
defined (CONFIG_INITRD_TAG) || \
defined (CONFIG_SERIAL_TAG) || \
defined (CONFIG_REVISION_TAG) || \
defined (CONFIG_LCD) || \
defined (CONFIG_VFD)
setup_start_tag (bd); // 初始化 tag 結構體開始
#ifdef CONFIG_SERIAL_TAG
setup_serial_tag (&params);
#endif
#ifdef CONFIG_REVISION_TAG
setup_revision_tag (&params);
#endif
#ifdef CONFIG_SETUP_MEMORY_TAGS
setup_memory_tags (bd); // 設置 RAM 參數
#endif
#ifdef CONFIG_CMDLINE_TAG
setup_commandline_tag (bd, commandline);
#endif
#ifdef CONFIG_INITRD_TAG
if (initrd_start && initrd_end)
setup_initrd_tag (bd, initrd_start, initrd_end);
#endif
#if defined (CONFIG_VFD) || defined (CONFIG_LCD)
setup_videolfb_tag ((gd_t *) gd);
#endif
setup_end_tag (bd); // 初始化 tag 結構體結束
#endif
......
......
theKernel (0, machid, bd->bi_boot_params);
// 傳給 Kernel 的參數= (struct tag *) 型的 bd->bi_boot_params
//bd->bi_boot_params 在 board_init 函數中初始化如對于 at91rm9200 ,初始化在 at91r
m9200dk.c 的 board_init 中進行: bd->bi_boot_params =PHYS_SDRAM + 0x100;
// 這個地址也是所有 taglist 的首地址,見下面的 setup_start_tag 函數
}
對于 setup_start_tag 和 setup_memory_tags 函數說明如下。
函數 setup_start_tag 也在此文件中定義,如下:
static void setup_start_tag (bd_t *bd)
{
params = (struct tag *) bd->bi_boot_params;
// 初始化 (struct tag *) 型的全局變量 params 為bd->bi_boot_params 的地址,之后的setup tags 相關
函數如下面的 setup_memory_tags 就把其它 tag 的數據放在此地址的偏移地址上。
params->hdr.tag = ATAG_CORE;
params->hdr.size = tag_size (tag_core);
params->u.core.flags = 0;
params->u.core.pagesize = 0;
params->u.core.rootdev = 0;
params = tag_next (params);
}
RAM 相關參數在 bootm.c 中的函數 setup_memory_tags 中初始化:
static void setup_memory_tags (bd_t *bd)
{
int i;
for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
params->hdr.tag = ATAG_MEM;
params->hdr.size = tag_size (tag_mem32);
params->u.mem.start = bd->bi_dram[i].start;
params->u.mem.size = bd->bi_dram[i].size;
params = tag_next (params);
} // 初始化內存相關 tag
}
2 、Kernel 讀取U-boot 傳遞的相關參數
對于 Linux Kernel , ARM 平臺啟動時,先執行 arch/arm/kernel/head.S ,此文件
會調用 arch/arm/kernel/head-common.S 中的函數,并最后調用 start_kernel :
......
b start_kernel
......
init/main.c 中的 start_kernel 函數中會調用 setup_arch 函數來處理各種平臺相關
的動作,包括了 u-boot 傳遞過來參數的分析和保存:
start_kernel()
{
......
setup_arch(&command_line);
......
}
其中, setup_arch 函數在 arch/arm/kernel/setup.c 文件中實現,如下:
void __init setup_arch(char **cmdline_p)
{
struct tag *tags = (struct tag *)&init_tags;
struct machine_desc *mdesc;
char *from = default_command_line;
setup_processor();
mdesc = setup_machine(machine_arch_type);
machine_name = mdesc->name;
if (mdesc->soft_reboot)
reboot_setup("s");
if (__atags_pointer)
// 指向各種 tag 起始位置的指針,定義如下:
//unsigned int __atags_pointer __initdata;
// 此指針指向 __initdata 段,各種 tag 的信息保存在這個段中。
tags = phys_to_virt(__atags_pointer);
else if (mdesc->boot_params)
tags = phys_to_virt(mdesc->boot_params);
if (tags->hdr.tag != ATAG_CORE)
convert_to_tag_list(tags);
if (tags->hdr.tag != ATAG_CORE)
tags = (struct tag *)&init_tags;
if (mdesc->fixup)
mdesc->fixup(mdesc, tags, &from, &meminfo);
if (tags->hdr.tag == ATAG_CORE) {
if (meminfo.nr_banks != 0)
squash_mem_tags(tags);
save_atags(tags);
parse_tags(tags);
// 處理各種 tags ,其中包括了 RAM 參數的處理。
// 這個函數處理如下 tags :
__tagtable(ATAG_MEM, parse_tag_mem32);
__tagtable(ATAG_VIDEOTEXT, parse_tag_videotext);
__tagtable(ATAG_RAMDISK, parse_tag_ramdisk);
__tagtable(ATAG_SERIAL, parse_tag_serialnr);
__tagtable(ATAG_REVISION, parse_tag_revision);
__tagtable(ATAG_CMDLINE, parse_tag_cmdline);
}
init_mm.start_code = (unsigned long) &_text;
init_mm.end_code = (unsigned long) &_etext;
init_mm.end_data = (unsigned long) &_edata;
init_mm.brk = (unsigned long) &_end;
memcpy(boot_command_line, from, COMMAND_LINE_SIZE);
boot_command_line[COMMAND_LINE_SIZE-1] = '\0';
parse_cmdline(cmdline_p, from); // 處理編譯內核時指定的 cmdline 或 u-boot 傳
遞的 cmdline
paging_init(&meminfo, mdesc);
request_standard_resources(&meminfo, mdesc);
#ifdef CONFIG_SMP
smp_init_cpus();
#endif
cpu_init();
init_arch_irq = mdesc->init_irq;
system_timer = mdesc->timer;
init_machine = mdesc->init_machine;
#ifdef CONFIG_VT
#if defined(CONFIG_VGA_CONSOLE)
conswitchp = &vga_con;
#elif defined(CONFIG_DUMMY_CONSOLE)
conswitchp = &dummy_con;
#endif
#endif
early_trap_init();
}
對于處理 RAM 的 tag ,調用了 parse_tag_mem32 函數:
static int __init parse_tag_mem32(const struct tag *tag)
{
......
arm_add_memory(tag->u.mem.start, tag->u.mem.size);
......
}
__tagtable(ATAG_MEM, parse_tag_mem32);
上述的 arm_add_memory 函數定義如下:
static void __init arm_add_memory(unsigned long start, unsigned long size)
{
struct membank *bank;
size -= start & ~PAGE_MASK;
bank = &meminfo.bank[meminfo.nr_banks++];
bank->start = PAGE_ALIGN(start);
bank->size = size & PAGE_MASK;
bank->node = PHYS_TO_NID(start);
}
如上可見, parse_tag_mem32 函數調用 arm_add_memory 函數把 RAM 的 start
和 size 等參數保存到了 meminfo 結構的 meminfo 結構體中。最后,在 setup_arch
中執行下面語句:
paging_init(&meminfo, mdesc);
對有 MMU 的平臺上調用 arch/arm/mm/nommu.c 中的 paging_init ,否則調用 ar
ch/arm/mm/mmu.c 中的 paging_init 函數。這里暫不分析 mmu.c 中的 paging_init 函
數。
3 、關于U-boot 中的bd 和gd
U-boot 中有一個用來保存很多有用信息的全局結構體-- gd_t ( global data 縮
寫),其中包括了 bd 變量,可以說 gd_t 結構體包括了 u-boot 中所有重要全局變量。
最后傳遞給內核的參數,都是從 gd 和 bd 中來的,如上述的 setup_memory_tags 函數
作用就是用 bd 中的值來初始化 RAM 相應的 tag 。
對于 ARM 平臺這個結構體的定義大致如下:
include/asm-arm/global_data.h
typedef struct global_data {
bd_t *bd;
unsigned long flags;
unsigned long baudrate;
unsigned long have_console; /* serial_init() was called */
unsigned long reloc_off; /* Relocation Offset */
unsigned long env_addr; /* Address of Environment struct */
unsigned long env_valid; /* Checksum of Environment valid? */
unsigned long fb_base; /* base address of frame buffer */
void **jt; /* jump table */
} gd_t;
在 U-boot 中使用 gd 結構之前要用先用宏 DECLARE_GLOBAL_DATA_PTR 來
聲明。這個宏的定義如下:
include/asm-arm/global_data.h
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
從這個宏的定義可以看出, gd 是一個保存在 ARM 的 r8 寄存器中的 gd_t 結構
體的指針。
說明:本文的版本為U-boot-1.3.4 、Linux-2.6.28 ,平臺是ARM 。
//補充一下:
來自:http://hi.baidu.com/armfans/blog/item/306cd5035f24ff084afb514b.html
bootloader巧妙地利用函數指針及傳參規范將R0:0x0,R1:機器號,R2:參數地址傳遞
給內核.由于R0,R1比較簡單,不需要再作說明.需要花點時間了解的是R2寄存器.
R2寄存器傳遞的是一個指針,這個指針指向一個TAG區域.UBOOT和Linux內核之
間正是通過這個擴展了的TAG區域來進行復雜參數的傳遞,如 command line,文件系
統信息等等,用戶也可以擴展這個TAG來進行更多參數的傳遞.TAG區域存放的地址,
也就是R2的值,是在/board /yourboard/youboard.c里的board_init函數中初始化的,
如在UB4020中初始化為:gd->bd->bi_boot_params = 0x30000100;,這是一個絕對地
址.
TAG區的結構比較簡單,可以視為一個一個TAG的排列(數組?),每一個TAG傳
遞一種特定類型的參數.各種系統TAG的定義可以參考./include/asm-arm/setup.h.
下面是一個TAG區的例子:
0x30000100 00000005 54410001 00000000 00000000
0x30000110 00000000 0000000F 54410009 746F6F72
0x30000120 65642F3D 61722F76 7220306D 6F632077
0x30000130 6C6F736E 74743D65 2C305379 30303639
0x30000140 696E6920 6C2F3D74 78756E69 EA006372
0x30000150 00000004 54420005 30300040 00200000
0x30000160 00000000 00000000
我們可以看到一共有三個TAG:
第一個TAG的長度是5個字,類型是ATAG_CORE(54410001),有三個元素,均為
全零.TAG區必須以這個TAG開頭.
第二個TAG的長度是F個字,類型是ATAG_CMDLINE(54410009),這是一個字符串,
是向內核傳遞的kernel command line
第三個TAG的長度是4個字,類型是ATAG_INITRD2(54410005),有兩個元素,第
一個是start:30300040(30300000+40),第二個是size:200000(2M)
如果說還有第四個TAG,那就是末尾的兩個全零,這是TAG結束的標志.
這些TAG是在./lib_arm/arm_linux.c中的do_bootm_linux函數中建立起來的.具
體建立哪些TAG,由相應的控制宏決定.具體可以參考相應代碼.例子中第一個TAG是
起始TAG,如果環境變量中有bootargs,則建立第二個TAG,如果bootm有兩個參數(引
導文件系統),則會讀取文件系統頭部的必要信息,建立第三個TAG.
內核啟動后,將根據R2寄存器的值找到這些TAG,并根據TAG類型,調用相應的處
理函數進行處理,從而獲取內核運行的必要信息.
U-BOOT 源碼分析及移植
本文從以下幾個方面粗淺地分析u-boot 并移植到FS2410 板上:
1、u-boot 工程的總體結構
2、u-boot 的流程、主要的數據結構、內存分配。
3、u-boot 的重要細節,主要分析流程中各函數的功能。
4、基于FS2410 板子的u-boot 移植。實現了NOR Flash 和NAND Flash 啟動,網絡功
能。
這些認識源于自己移植u-boot 過程中查找的資料和對源碼的簡單閱讀。下面主要以
smdk2410 為分析對象。
一、u-boot 工程的總體結構:
1、源代碼組織
對于ARM而言,主要的目錄如下:
board 平臺依賴 存放電路板相關的目錄文件,每一套板子對 應一個目
錄。如smdk2410(arm920t)
cpu 平臺依賴 存放CPU 相關的目錄文件,每一款CPU 對應一個目
錄,例如:arm920t、 xscale、i386 等目錄
lib_arm 平臺依賴 存放對ARM 體系結構通用的文件,主要用于實現
ARM 平臺通用的函數,如軟件浮點。
common 通用 通用的多功能函數實現,如環境,命令,控制臺相關的函數
實現。
include 通用 頭文件和開發板配置文件,所有開發板的配置文件都在
configs 目錄下
lib_generic 通用 通用庫函數的實現
net 通用 存放網絡協議的程序
drivers 通用 通用的設備驅動程序,主要有以太網接口的驅動,nand
驅動。
.......
2.makefile 簡要分析
所有這些目錄的編譯連接都是由頂層目錄的makefile 來確定的。
在執行make 之前,先要執行make $(board)_config 對工程進行配置,以確定特定于目
標板的各個子目錄和頭文件。
$(board)_config:是makefile 中的一個偽目標,它傳入指定的CPU,ARCH,BOARD,
SOC 參數去執行mkconfig 腳本。
這個腳本的主要功能在于連接目標板平臺相關的頭文件夾,生成config.h 文件包含板子的配
置頭文件。
使得makefile 能根據目標板的這些參數去編譯正確的平臺相關的子目錄。
以smdk2410 板為例,執行 make smdk2410_config,
主要完成三個功能:
@在include 文件夾下建立相應的文件(夾)軟連接,
#如果是ARM體系將執行以下操作:
#ln -s asm-arm asm
#ln -s arch-s3c24x0 asm-arm/arch
#ln -s proc-armv asm-arm/proc
@生成Makefile 包含文件include/config.mk,內容很簡單,定義了四個變量:
ARCH = arm
CPU = arm920t
BOARD = smdk2410
SOC = s3c24x0
@生成include/config.h 頭文件,只有一行:
/* Automatically generated - do not edit */
#include "config/smdk2410.h"
頂層makefile 先調用各子目錄的makefile,生成目標文件或者目標文件庫。
然后再連接所有目標文件(庫)生成最終的u-boot.bin。
連接的主要目標(庫)如下:
OBJS = cpu/$(CPU)/start.o
LIBS = lib_generic/libgeneric.a
LIBS += board/$(BOARDDIR)/lib$(BOARD).a
LIBS += cpu/$(CPU)/lib$(CPU).a
ifdef SOC
LIBS += cpu/$(CPU)/$(SOC)/lib$(SOC).a
endif
LIBS += lib_$(ARCH)/lib$(ARCH).a
LIBS += fs/cramfs/libcramfs.a fs/fat/libfat.a fs/fdos/libfdos.a
fs/jffs2/libjffs2.a \
fs/reiserfs/libreiserfs.a fs/ext2/libext2fs.a
LIBS += net/libnet.a
LIBS += disk/libdisk.a
LIBS += rtc/librtc.a
LIBS += dtt/libdtt.a
LIBS += drivers/libdrivers.a
LIBS += drivers/nand/libnand.a
LIBS += drivers/nand_legacy/libnand_legacy.a
LIBS += drivers/sk98lin/libsk98lin.a
LIBS += post/libpost.a post/cpu/libcpu.a
LIBS += common/libcommon.a
LIBS += $(BOARDLIBS)
顯然跟平臺相關的主要是:
cpu/$(CPU)/start.o
board/$(BOARDDIR)/lib$(BOARD).a
cpu/$(CPU)/lib$(CPU).a
cpu/$(CPU)/$(SOC)/lib$(SOC).a
lib_$(ARCH)/lib$(ARCH).a
這里面的四個變量定義在include/config.mk(見上述)。
其余的均與平臺無關。
所以考慮移植的時候也主要考慮這幾個目標文件(庫)對應的目錄。
關于u-boot 的makefile 更詳細的分析可以參照
http://blog.mcuol.com/User/lvembededsys/Article/4355_1.htm 。
3、u-boot 的通用目錄是怎么做到與平臺無關的?
include/config/smdk2410.h
這個頭文件中主要定義了兩類變量。
一類是選項,前綴是CONFIG_,用來選擇處理器、設備接口、命令、屬性等,主要用來 決
定是否編譯某些文件或者函數。
另一類是參數,前綴是CFG_,用來定義總線頻率、串口波特率、Flash 地址等參數。這些常
數參量主要用來支持通用目錄中的代碼,定義板子資源參數。
這兩類宏定義對u-boot 的移植性非常關鍵,比如drive/CS8900.c,對cs8900 而言,很
多操作都是通用的,但不是所有的板子上面都有這個芯片,即使有它在內存中映射的基地址也
是平臺相關的。所以對于smdk2410 板,在smdk2410.h 中定義了
#define CONFIG_DRIVER_CS8900 1 /* we have a CS8900
on-board */
#define CS8900_BASE 0x19000300 /*IO mode base address*/
CONFIG_DRIVER_CS8900 的定義使得cs8900.c 可以被編譯(當然還得定義
CFG_CMD_NET 才行),因為cs8900.c 中在函數定義的前面就有編譯條件判斷:#ifdef
CONFIG_DRIVER_CS8900 如果這個選項沒有定義,整個cs8900.c 就不會被編譯了。
而常數參量CS8900_BASE 則用在cs8900.h 頭文件中定義各個功能寄存器的地址。
u-boot 的CS8900 工作在IO 模式下,只要給定IO 寄存器在內存中映射的基地址,其余代
碼就與平臺無關了。
u-boot 的命令也是通過目標板的配置頭文件來配置的,比如要添加ping 命令,就必須添加
CFG_CMD_NET 和CFG_CMD_PING 才行。不然common/cmd_net.c 就不會被編譯
了。
從這里我可以這么認為,u-boot 工程可配置性和移植性可以分為兩層:
一是由makefile 來實現,配置工程要包含的文件和文件夾上,用什么編譯器。
二是由目標板的配置頭文件來實現源碼級的可配置性,通用性。主要使用的是#ifdef #else
#endif 之類來實現的。
4、smkd2410 其余重要的文件 :
include/s3c24x0.h 定義了s3x24x0 芯片的各個特殊功能寄存器(SFR)的地址。
cpu/arm920t/start.s 在flash 中執行的引導代碼,也就是bootloader 中的
stage1,負責初始化硬件環境,把u-boot 從flash 加載到RAM中去,然后跳到
lib_arm/board.c 中的start_armboot 中去執行。
lib_arm/board.c u-boot 的初始化流程,尤其是u-boot 用到的全局數據結構
gd,bd 的初始化,以及設備和控制臺的初始化。
board/smdk2410/flash.c 在board 目錄下代碼的都是嚴重依賴目標板,對于不同
的CPU,SOC,ARCH,u-boot 都有相對通用的代碼,但是板子構成卻是多樣的,主要是內
存地址,flash 型號,外圍芯片如網絡。對fs2410 來說,主要考慮從smdk2410 板來移植,
差別主要在nor flash 上面。
二、u-boot 的流程、主要的數據結構、內存分配
1、u-boot 的啟動流程:
從文件層面上看主要流程是在兩個文件中:cpu/arm920t/start.s,
lib_arm/board.c,
1)start.s
在flash 中執行的引導代碼,也就是bootloader 中的stage1,負責初始化硬件環境,把
u-boot 從flash 加載到RAM中去,然后跳到lib_arm/board.c 中的start_armboot
中去執行。
1.1.6 版本的start.s 流程:
硬件環境初始化 :
進入svc 模式;關閉watch dog;屏蔽所有IRQ 掩碼;設置時鐘頻率FCLK、HCLK、
PCLK;清I/D cache;禁止MMU 和CACHE;配置memory control;
重定位 :
如果當前代碼不在連接指定的地址上(對smdk2410 是0x3f000000)則需要把
u-boot 從當前位置拷貝到RAM指定位置中;
建立堆棧 ,堆棧是進入C 函數前必須初始化的。
清.bss 區 。
跳到start_armboot 函數中執行 。(lib_arm/board.c)
2)lib_arm/board.c:
start_armboot 是U-Boot 執行的第一個C 語言函數,完成系統初始化工作,進入主
循環,處理用戶輸入的命令。這里只簡要列出了主要執行的函數流程:
void start_armboot (void)
{
//全局數據變量指針gd 占用r8。
DECLARE_GLOBAL_DATA_PTR;
/* 給全局數據變量gd 安排空間*/
gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
memset ((void*)gd, 0, sizeof (gd_t));
/* 給板子數據變量gd->bd 安排空間*/
gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
memset (gd->bd, 0, sizeof (bd_t));
monitor_flash_len = _bss_start - _armboot_start;//取u-boot 的長度。
/* 順序執行init_sequence 數組中的初始化函數 */
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
/*配置可用的Flash */
size = flash_init ();
……
/* 初始化堆空間 */
mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);
/* 重新定位環境變量, */
env_relocate ();
/* 從環境變量中獲取IP 地址 */
gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
/* 以太網接口MAC 地址 */
……
devices_init (); /* 設備初始化 */
jumptable_init (); //跳轉表初始化
console_init_r (); /* 完整地初始化控制臺設備 */
enable_interrupts (); /* 使能中斷處理 */
/* 通過環境變量初始化 */
if ((s = getenv ("loadaddr")) != NULL) {
load_addr = simple_strtoul (s, NULL, 16);
}
/* main_loop()循環不斷執行 */
for (;;) {
main_loop (); /* 主循環函數處理執行用戶命令 --
common/main.c */
}
}
初始化函數序列init_sequence[]
init_sequence[]數組保存著基本的初始化函數指針。這些函數名稱和實現的程序文件在下
列注釋中。
init_fnc_t *init_sequence[] = {
cpu_init, /* 基本的處理器相關配置 -- cpu/arm920t/cpu.c */
board_init, /* 基本的板級相關配置 --
board/smdk2410/smdk2410.c */
interrupt_init, /* 初始化例外處理 --
cpu/arm920t/s3c24x0/interrupt.c */
env_init, /* 初始化環境變量 -- common/env_flash.c */
init_baudrate, /* 初始化波特率設置 -- lib_arm/board.c */
serial_init, /* 串口通訊設置 -- cpu/arm920t/s3c24x0/serial.c
*/
console_init_f, /* 控制臺初始化階段1 -- common/console.c */
display_banner, /* 打印u-boot 信息 -- lib_arm/board.c */
dram_init, /* 配置可用的RAM --
board/smdk2410/smdk2410.c */
display_dram_config, /* 顯示RAM的配置大小 -- lib_arm/board.c */
NULL,
};
整個u-boot 的執行就進入等待用戶輸入命令,解析并執行命令的死循環中。
2、u-boot 主要的數據結構
u- boot 的主要功能是用于引導OS 的,但是本身也提供許多強大的功能,可以通過輸入命令
行來完成許多操作。所以它本身也是一個很完備的系統。u-boot 的大部分操作都是圍繞它自
身的數據結構,這些數據結構是通用的,但是不同的板子初始化這些數據就不一樣了。所以
u-boot 的通用代碼是依賴于這些重要的數據結構的。這里說的數據結構其實就是一些全局變
量。
1)gd 全局數據變量指針,它保存了u-boot 運行需要的全局數據,類型定義:
typedef struct global_data {
bd_t *bd; //board data pointor 板子數據指針
unsigned long flags; //指示標志,如設備已經初始化標志等。
unsigned long baudrate; //串口波特率
unsigned long have_console; /* 串口初始化標志*/
unsigned long reloc_off; /* 重定位偏移,就是實際定向的位置與編譯連接時
指定的位置之差,一般為0 */
unsigned long env_addr; /* 環境參數地址*/
unsigned long env_valid; /* 環境參數CRC 檢驗有效標志 */
unsigned long fb_base; /* base address of frame buffer */
#ifdef CONFIG_VFD
unsigned char vfd_type; /* display type */
#endif
void **jt; /* 跳轉表,1.1.6 中用來函數調用地址登記 */
} gd_t;
2)bd 板子數據指針 。板子很多重要的參數。 類型定義如下:
typedef struct bd_info {
int bi_baudrate; /* 串口波特率 */
unsigned long bi_ip_addr; /* IP 地址 */
unsigned char bi_enetaddr[6]; /* MAC 地址*/
struct environment_s *bi_env;
ulong bi_arch_number; /* unique id for this board */
ulong bi_boot_params; /* 啟動參數 */
struct /* RAM 配置 */
{
ulong start;
ulong size;
}bi_dram[CONFIG_NR_DRAM_BANKS];
} bd_t;
3)環境變量指針 env_t *env_ptr = (env_t
*)(&environment[0]);(common/env_flash.c)
env_ptr 指向環境參數區,系統啟動時默認的環境參數environment[],定義在
common/environment.c 中。
參數解釋 :
bootdelay 定義執行自動啟動的等候秒數
baudrate 定義串口控制臺的波特率
netmask 定義以太網接口的掩碼
ethaddr 定義以太網接口的MAC 地址
bootfile 定義缺省的下載文件
bootargs 定義傳遞給Linux 內核的命令行參數
bootcmd 定義自動啟動時執行的幾條命令
serverip 定義tftp 服務器端的IP 地址
ipaddr 定義本地的IP 地址
stdin 定義標準輸入設備,一般是串口
stdout 定義標準輸出設備,一般是串口
stderr 定義標準出錯信息輸出設備,一般是串口
4)設備相關 :
標準IO設備數組

總結

以上是生活随笔為你收集整理的uboot代码详细分析.pdf的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

五月婷婷丁香在线观看 | 久久久久免费电影 | av资源免费看 | 国产999在线 | 国产精品久久久久四虎 | 国产精品成人一区二区三区吃奶 | 国产五月色婷婷六月丁香视频 | 婷婷丁香狠狠爱 | 人人澡人人添人人爽一区二区 | 国产偷国产偷亚洲清高 | 亚洲精品乱码白浆高清久久久久久 | 国产成人精品av在线 | 国产在线观看一 | 性日韩欧美在线视频 | 91久久国产精品 | 久久国产精品视频 | 日韩精品你懂的 | 日韩 国产| 91精品第一页 | 18女毛片| 超碰资源在线 | 青春草视频在线播放 | 日日夜夜操操操操 | 在线91精品 | 最近高清中文在线字幕在线观看 | 看片一区二区三区 | 久久精品视频国产 | 91私密视频| 超碰人人在线 | 开心激情久久 | 日本久久91 | 日韩成人看片 | 国产成人av网 | 18国产精品福利片久久婷 | 国产精品1024 | 高清一区二区三区av | 国产高清av在线播放 | 少妇精69xxtheporn | 日本精品久久久一区二区三区 | 日韩电影中文字幕在线 | 久久久精品免费观看 | 国产精品九九久久久久久久 | 91夫妻自拍 | 亚洲成人av在线播放 | 免费看av在线 | 91网址在线看 | 一个色综合网站 | 探花视频在线观看免费 | 国产一区二区三区免费视频 | bbbbb女女女女女bbbbb国产 | 久久国产福利 | 久久激情视频 久久 | 久久国产精品99久久久久久老狼 | 国产一级二级在线观看 | 久久久精品国产一区二区电影四季 | 综合色综合色 | 高清久久久久久 | 最近免费中文字幕大全高清10 | 色婷婷国产精品一区在线观看 | 日韩av中文字幕在线 | 天天干天天干天天干天天干天天干天天干 | 天天看天天干 | 国产亚洲精品久久久久秋 | 91久久爱热色涩涩 | 中文不卡视频在线 | 91亚洲精| 青春草视频在线播放 | 这里只有精彩视频 | 国产999精品久久久久久绿帽 | 色婷婷丁香 | 久久99视频免费 | 在线观看亚洲专区 | 欧美成年人在线观看 | 中文字幕免费一区二区 | 在线看v片成人 | 黄色资源在线观看 | 亚洲欧美日韩国产一区二区三区 | 国产精品一区二区av日韩在线 | 久久有精品 | 成人久久视频 | 欧美日韩中文国产一区发布 | 在线观看一区 | 日韩精品中文字幕在线 | 亚洲成人网在线 | 成人欧美一区二区三区在线观看 | 久草网在线视频 | av激情五月 | 久草在线免费看视频 | 国产成人精品福利 | 亚洲国产999| 欧美久久久一区二区三区 | 国产精品自产拍在线观看中文 | 国内精品久久久久久久久久久久 | 久久综合九色综合久久久精品综合 | 亚洲成年人av | 亚洲视频h| 成人福利在线观看 | 偷拍视频一区 | 欧美成人精品xxx | 狠狠躁夜夜av | 欧美视频日韩 | 国产精品一区二区三区电影 | 日韩av免费一区 | 另类老妇性bbwbbw高清 | 91久久爱热色涩涩 | 97超视频在线观看 | 亚洲国产视频a | 色窝资源 | 久久资源在线 | 日日夜日日干 | 国产第一页在线播放 | 久久国产日韩 | 日韩视频中文字幕在线观看 | 亚洲精品国内 | 欧美一级片免费在线观看 | 欧美精品一二三 | 亚洲一区日韩精品 | 不卡视频在线 | 国产亚洲综合精品 | 成人精品国产免费网站 | 爱情影院aqdy鲁丝片二区 | 亚洲va欧美va人人爽春色影视 | 91免费网| 人人添人人澡人人澡人人人爽 | 日韩欧美在线中文字幕 | 免费色视频网站 | 91九色蝌蚪视频网站 | 成人在线一区二区 | 激情综合六月 | 黄色在线观看免费网站 | 日韩国产欧美在线播放 | 操少妇视频 | 久久国产精品99久久人人澡 | 91丨精品丨蝌蚪丨白丝jk | 久久y| 国产裸体永久免费视频网站 | 成人在线视频在线观看 | 精品一区二区精品 | 久久久久蜜桃 | 亚洲欧美成人 | 国产a国产 | 91免费的视频在线播放 | 97日日碰人人模人人澡分享吧 | 国产午夜麻豆影院在线观看 | 丁香婷婷综合色啪 | 在线观看av网站 | 狠狠干中文字幕 | 日日躁天天躁 | 欧美日韩中字 | 久久不射电影院 | 青春草免费在线视频 | 午夜少妇 | 精品久久1 | 免费一级日韩欧美性大片 | 久久黄色免费 | 亚洲精品视频一二三 | 色婷婷a | 久久精品成人欧美大片古装 | 天天做日日爱夜夜爽 | 草久热 | 最近最新中文字幕 | 91九色蝌蚪在线 | 久久久免费播放 | 国产日韩精品一区二区三区在线 | 99久久99精品 | 中文在线√天堂 | 欧美国产精品一区二区 | 免费观看性生交大片3 | 久久字幕精品一区 | 久要激情网 | 欧美韩日精品 | 久久99热这里只有精品 | 91精品啪在线观看国产81旧版 | 综合色婷婷 | 天天做天天射 | 亚洲视频精品 | 免费婷婷| 精品国产自在精品国产精野外直播 | 日本一区二区不卡高清 | 午夜美女av | 国产中文字幕一区二区 | 成人网看片 | 99精品观看| 欧美日韩另类在线 | 久久久久久久看片 | 国产专区日韩专区 | 亚洲视频一级 | 六月色丁香 | 欧美精品免费在线 | 亚洲久草网 | 手机在线看永久av片免费 | 最新中文字幕视频 | 欧美极品一区二区三区 | 国产a级片免费观看 | 又色又爽又黄高潮的免费视频 | 日韩理论电影在线 | 天天综合成人网 | 欧美一级黄色视屏 | 蜜桃av人人夜夜澡人人爽 | 日韩大片免费观看 | 99热精品国产一区二区在线观看 | 免费成视频 | 国产美女精品视频免费观看 | 五月婷婷六月丁香在线观看 | 日韩av看片| 欧美成年人在线视频 | 免费十分钟 | 91视频最新网址 | 99这里只有久久精品视频 | 综合婷婷丁香 | 91色欧美 | 天天做天天爽 | 国产欧美在线一区二区三区 | 美女天天操 | 天天干天天搞天天射 | 在线免费黄| 国产手机av在线 | 最新久久免费视频 | 在线观看视频一区二区三区 | 久草视频在线新免费 | 久草免费在线视频观看 | 天天射天天射天天 | 国产麻豆视频免费观看 | 日韩精品免费在线观看视频 | 超碰在线中文字幕 | 色视频国产直接看 | 日韩欧美一区二区在线播放 | 亚洲精品久久久蜜臀下载官网 | 九月婷婷色 | 免费网站在线观看成人 | 亚洲午夜小视频 | 天天操天天射天天操 | 美女免费黄网站 | 四虎影视成人永久免费观看亚洲欧美 | 手机在线观看国产精品 | av.com在线| 婷婷在线观看视频 | 久久与婷婷 | 伊色综合久久之综合久久 | 欧美亚洲国产一卡 | 国产又粗又猛又色又黄网站 | 中文字幕 影院 | 91麻豆国产福利在线观看 | 国产一区在线视频播放 | 国产美腿白丝袜足在线av | 东方av在线免费观看 | 亚洲精品一区中文字幕乱码 | 在线视频成人 | 人人爽久久涩噜噜噜网站 | 一区电影| 成年人黄色大片在线 | 高清免费在线视频 | 日韩久久精品一区二区三区下载 | 日韩亚洲欧美中文字幕 | 国产精品18久久久 | 欧美韩国日本在线 | www日韩在线| 久久视频国产 | 激情综合电影网 | 91精品人成在线观看 | 日韩视频免费播放 | 91超国产 | 久草免费电影 | 国产精品久久久久久久久大全 | 高清中文字幕 | 成在人线av | 久草视频在 | av网在线观看 | 久99久在线视频 | 国产成人精品综合久久久久99 | 久草资源在线 | 99久久99久久精品免费 | 中文字幕一二 | 日韩免费一级电影 | 丝袜+亚洲+另类+欧美+变态 | 久久精品日韩 | 91亚洲国产成人久久精品网站 | 久久涩涩网站 | 久久国产精品二国产精品中国洋人 | 亚洲精品动漫在线 | 成人av电影免费在线播放 | 欧美久久99| 香蕉网在线| 中国一级特黄毛片大片久久 | 黄色小说在线观看视频 | 亚洲欧美经典 | 天天操网 | 91精品国产一区二区在线观看 | www.久久91 | 成人性生爱a∨ | 国产韩国精品一区二区三区 | 伊人天天综合 | 蜜臀久久99精品久久久无需会员 | 免费看v片| 免费在线视频一区二区 | 91精品国产麻豆 | 国产免费黄视频在线观看 | 中文字幕在线观看第二页 | 99久久精品国产欧美主题曲 | 99热精品久久 | 色视频网址 | 欧美精品xx | 九色自拍视频 | 9ⅰ精品久久久久久久久中文字幕 | 日韩精品不卡在线观看 | 欧美国产在线看 | 亚洲永久精品在线 | 色视频网站在线观看一=区 a视频免费在线观看 | 亚洲精品九九 | 精品一区二区三区四区在线 | 美女视频久久黄 | 国产高清在线a视频大全 | 一本一本久久a久久精品牛牛影视 | 黄在线 | www免费看| 亚洲天堂va | 久久国产精品第一页 | 81国产精品久久久久久久久久 | 日韩av手机在线看 | 一区二区视频免费在线观看 | 成人午夜在线观看 | 精品国产视频一区 | 国产精品99免费看 | 久久精品99北条麻妃 | 伊人狠狠色丁香婷婷综合 | 国产自在线| 天堂麻豆| 午夜在线资源 | 在线免费看片 | 正在播放一区二区 | 色视频在线观看 | 亚洲第一中文字幕 | h动漫中文字幕 | 中文字幕成人一区 | 91爱爱视频 | 天天操天天曰 | 91tv国产成人福利 | 亚洲天堂网站 | 久久www免费视频 | 99精品国产99久久久久久福利 | 九色精品免费永久在线 | 日日干激情五月 | 亚洲va欧美va人人爽 | 国产盗摄精品一区二区 | 51精品国自产在线 | 久久久久亚洲精品成人网小说 | 中文在线字幕免费观看 | 国产高清免费视频 | 欧美夫妻性生活电影 | 五月天婷婷视频 | 欧美一级在线 | 91九色精品 | 91av官网| 日本大片免费观看在线 | 91视频观看免费 | 日本黄色a级大片 | 在线观看国产高清视频 | 亚洲综合在线五月 | 91片黄在线观 | 亚洲波多野结衣 | 国产片免费在线观看视频 | 亚洲一级二级三级 | 九九免费观看全部免费视频 | av无限看| 二区三区在线观看 | 国内精品视频一区二区三区八戒 | 亚洲精品麻豆视频 | 最新黄色av网址 | 免费黄色网址大全 | 欧美小视频在线 | 精品久久亚洲 | 久久人网 | 国产一区二区三区 在线 | av 一区 二区 久久 | 国产成人精品一区二区三区福利 | 超碰97.com | 久久亚洲福利 | 天天透天天插 | 狠狠干天天操 | 久热免费 | 久草在线免费资源 | 亚洲在线国产 | 色天天久久 | 日本三级国产 | 中文字幕一区二区三区乱码在线 | 嫩草av影院 | 国产精品视频最多的网站 | 欧美男男tv网站 | 亚洲免费观看在线视频 | 成人一区在线观看 | 国产一级大片免费看 | 天堂v中文 | 黄色三级免费 | 五月婷婷久草 | 成人久久久久久久久久 | 亚洲国产综合在线 | 国产精品第三页 | 国产精品麻豆欧美日韩ww | 国产黄色高清 | 久久国产色 | 深爱激情综合 | 精品久久99 | 在线看国产一区 | 中文字幕一区二区三区四区在线视频 | 中国一 片免费观看 | 天天玩天天干天天操 | 久色小说| 免费高清av在线看 | zzijzzij日本成熟少妇 | 国产在线观看一 | 欧美激情精品久久久久久 | 国产精品区二区三区日本 | 99久久精品无码一区二区毛片 | 黄色视屏av | 欧美午夜视频在线 | 色婷婷久久久 | 一区免费在线 | 亚洲精品免费观看 | 九色91视频 | 永久免费精品视频网站 | 国产 日韩 欧美 自拍 | 亚洲色图美腿丝袜 | 亚洲激情在线视频 | 91av99| 久久久久久久久久久久久久av | 中文字幕在线观看不卡 | 国产成人福利在线观看 | 午夜精品一二区 | 91探花系列在线播放 | 人人爽人人乐 | 日韩精品在线视频免费观看 | 五月婷婷黄色网 | 国产一卡在线 | 欧美日本在线观看视频 | 福利一区在线 | 日韩大片在线免费观看 | 国产经典 欧美精品 | 一本一本久久a久久精品综合妖精 | 国产精品美女免费看 | 久热只有精品 | 91香蕉国产在线观看软件 | 天天干 夜夜操 | 国产精品一区二区在线免费观看 | 欧美日一级片 | 精品国产1区二区 | 中文字幕免费不卡视频 | 亚洲伊人av | a国产精品| 人人插人人做 | 成人全视频免费观看在线看 | 中文字幕第一 | 免费色视频网站 | 黄色av观看 | a'aaa级片在线观看 | 日韩有码专区 | 国产福利中文字幕 | 美女网站在线观看 | 色欲综合视频天天天 | 国产色小视频 | 国产生活一级片 | 黄色福利网站 | 69国产盗摄一区二区三区五区 | 中文字幕电影高清在线观看 | 久二影院| 国产成人黄色网址 | 二区三区毛片 | 99视频这里有精品 | 欧美日韩三级在线观看 | 欧美日韩一区二区在线 | 欧美 激情 国产 91 在线 | 成人国产精品免费观看 | 91桃花视频 | 国产在线观看,日本 | 国产视频日韩视频欧美视频 | 日本天天操 | 色先锋av资源中文字幕 | 九九精品毛片 | 日韩综合一区二区 | 欧美日韩中文字幕综合视频 | 人人搞人人爽 | 自拍超碰在线 | 人人干人人超 | 国产最顶级的黄色片在线免费观看 | 久久久久亚洲精品男人的天堂 | 国产免费又爽又刺激在线观看 | 亚洲欧美视频 | 亚洲精品婷婷 | 日日干av| 久久久久久电影 | 免费在线观看的av网站 | 亚洲做受高潮欧美裸体 | 中文字幕在线观看一区二区 | 91麻豆产精品久久久久久 | av在线色| 日日摸日日添夜夜爽97 | 日韩成人欧美 | 在线观看中文字幕一区二区 | www.久久久com| 特级黄色一级 | 日韩欧美一区二区不卡 | 国产高清免费av | 91亚洲精品国偷拍自产在线观看 | 亚洲免费一级 | 日韩一区二区三区观看 | 午夜av电影院 | 亚洲久草视频 | 国产精品麻豆果冻传媒在线播放 | 日韩精品视频一二三 | 亚洲欧美日韩国产一区二区三区 | 在线看一级片 | 一区二区三区在线电影 | 黄色免费高清视频 | 国偷自产视频一区二区久 | 欧美激情片在线观看 | 欧美影院久久 | 国产91亚洲 | 日韩国产高清在线 | 国产精品久久三 | 久久久久五月天 | 亚洲国产中文在线 | 亚洲成av人片在线观看 | 91精品久久久久久综合五月天 | a天堂中文在线 | 免费看一级特黄a大片 | 天天摸日日摸人人看 | 日日干干 | 久久情爱 | 美女国产精品 | 亚洲精品白浆高清久久久久久 | 91精品秘密在线观看 | 永久免费看av | 日韩在线精品 | 天天草综合 | 亚洲久草在线视频 | 亚洲精品字幕在线 | 中文字幕xxxx| 人人操日日干 | 99色免费| 亚洲va男人天堂 | 久久理论片 | 久久综合中文字幕 | 人人添人人澡人人澡人人人爽 | 亚洲一区二区三区91 | 日韩久久视频 | 久久免费视频精品 | 国产福利在线免费观看 | www夜夜操com | 日韩欧美一区二区在线播放 | 中文字幕精品一区二区精品 | 久久综合在线 | 亚洲国产av精品毛片鲁大师 | 黄色一级大片在线免费看产 | 91在线www| 毛片3 | 成人小视频免费在线观看 | 91视频免费网站 | 国产在线精品一区二区三区 | 久久精品欧美日韩精品 | 国产精品久久久av久久久 | 国产一区电影在线观看 | 国产亲近乱来精品 | 91精品人成在线观看 | 在线视频麻豆 | 中日韩免费视频 | 日本久久综合网 | 免费精品国产 | 国产福利在线 | 91视频中文字幕 | 精品亚洲视频在线观看 | 黄色影院在线免费观看 | 在线视频欧美日韩 | 激情网综合 | 伊人婷婷在线 | 日日夜夜天天 | 国产一区二区不卡视频 | 国产美腿白丝袜足在线av | 深爱五月激情网 | 国产精品亚洲片在线播放 | 欧美极品在线播放 | 中文字幕第一页在线 | 欧美成人猛片 | 免费福利在线播放 | 日韩综合第一页 | 久久公开免费视频 | 日韩色中色 | 久久久久久久99精品免费观看 | 麻豆果冻剧传媒在线播放 | 久草视频免费看 | 久久精品黄色 | 中文字幕高清免费日韩视频在线 | www.色综合.com | 国产在线精品一区二区不卡了 | 精品亚洲午夜久久久久91 | 欧美午夜久久久 | 日日操狠狠干 | 日韩理论在线观看 | 日韩网站免费观看 | 日韩在线看片 | 国产欧美日韩一区 | av亚洲产国偷v产偷v自拍小说 | 麻豆国产视频 | 美女视频黄的免费的 | 天天色天天射天天操 | 久久不卡视频 | 成年人免费av网站 | 日韩精品免费一区二区在线观看 | 国产视 | www.成人sex| 久久精品香蕉视频 | 久久99精品国产一区二区三区 | 国产永久免费高清在线观看视频 | 日韩a欧美 | 在线观看香蕉视频 | 午夜视频色 | 久久国产剧场电影 | 中文字幕.av.在线 | 91人人爽久久涩噜噜噜 | ,久久福利影视 | 国产精品麻豆99久久久久久 | 国产精品精品国产色婷婷 | 五月天av在线 | 亚洲国产中文字幕在线视频综合 | 最近最新中文字幕视频 | 在线视频精品播放 | 国产精品精品国产色婷婷 | 在线国产精品一区 | 九九亚洲精品 | 黄色精品久久 | 国产资源中文字幕 | 香蕉看片| 国产九九九九九 | 丁香久久婷婷 | 久久精品a | 日韩精品在线看 | 精品国产伦一区二区三区观看方式 | 国产视频精品免费 | 999久久国精品免费观看网站 | 狠狠操夜夜 | 特级西西www44高清大胆图片 | 久久久久人人 | 国产伦精品一区二区三区免费 | 精品国产福利在线 | 国产韩国精品一区二区三区 | 西西4444www大胆无视频 | 久久精品久久精品久久39 | 天天躁日日躁狠狠躁av麻豆 | 激情电影影院 | 久久成人在线视频 | 国产原创在线观看 | 蜜臀av麻豆| 久久精品美女视频网站 | 看片在线亚洲 | 精品久久久久久亚洲综合网 | av线上看 | 中文字幕日韩精品有码视频 | 日韩在线观看视频中文字幕 | 五月天综合| 韩日精品中文字幕 | 久久九九国产精品 | 在线涩涩 | 国产精品精品国产色婷婷 | 成人a大片| 亚洲国产视频a | 一区在线播放 | 亚洲激情五月 | av永久网址 | 黄色在线免费观看网站 | 久久久伊人网 | 亚洲区二区 | av不卡网站| 欧美性成人 | 91九色网址| 在线观看91精品视频 | 久久久午夜电影 | 91三级在线观看 | 久草视频在线资源站 | 夜夜操天天 | 国产精品在线看 | 久久这里只有精品首页 | 91精品资源 | 精品电影一区二区 | 天天玩天天干 | 热久久免费视频精品 | www黄色com | 中国成人一区 | 国产中文字幕一区二区三区 | 91视频 - 88av | 91在线中文字幕 | 999国内精品永久免费视频 | 麻豆一级视频 | 日韩欧美综合精品 | 亚洲精品国产综合99久久夜夜嗨 | 久久精品一二三 | 国产成人精品在线 | 国产精品一区二区美女视频免费看 | 国产视频丨精品|在线观看 国产精品久久久久久久久久久久午夜 | 亚洲自拍偷拍色图 | 成人在线网站观看 | 色婷婷久久一区二区 | 亚洲成人精品影院 | 日韩精品视频一二三 | 午夜精品久久久久久久久久久久久久 | 久久极品| 日韩精品你懂的 | 欧美黑人性猛交 | 麻豆视频免费入口 | 国产成人精品久久久久蜜臀 | 97视频在线免费播放 | 免费看国产一级片 | 久久精品视频99 | 九九av| 伊人久久精品久久亚洲一区 | 色黄www小说 | 中文字幕亚洲精品日韩 | 免费无遮挡动漫网站 | 日本免费一二三区 | 色狠狠一区二区 | 西西44人体做爰大胆视频 | 98涩涩国产露脸精品国产网 | 国产韩国精品一区二区三区 | www操操操 | 国产精品永久免费视频 | 少妇搡bbbb搡bbb搡忠贞 | 中文字幕一区三区 | 中文字幕在线观看三区 | 午夜精品福利一区二区三区蜜桃 | av日韩中文 | 久久免费视频6 | 成人一级片免费看 | 午夜精品一区二区三区免费 | h网站免费在线观看 | 一区二区三区久久 | 96久久欧美麻豆网站 | 国产一区二区在线免费 | 97超视频 | 亚洲精品视频在 | 九九在线视频免费观看 | 麻豆国产精品va在线观看不卡 | 久久午夜精品 | 欧美一级裸体视频 | 欧洲亚洲国产视频 | 婷婷丁香六月天 | 色播五月激情综合网 | 狠狠做深爱婷婷综合一区 | 欧美日韩另类在线 | 973理论片235影院9 | 午夜神马福利 | 国产精品va在线 | 亚洲一区视频免费观看 | 99久久精品免费看国产四区 | 天天摸日日摸人人看 | 亚洲欧美在线视频免费 | 伊人天天狠天天添日日拍 | 国产xx在线 | 日本精品久久 | 91热在线 | 亚洲影院一区 | 天天射天天干天天插 | 国产视频亚洲精品 | 天堂av一区二区 | 天堂成人在线 | 99精品视频在线观看视频 | 在线国产黄色 | 最新超碰| 日韩av片在线 | 免费视频三区 | 国产91区| 精品在线亚洲视频 | 日本成人黄色片 | 91激情视频在线播放 | 丁香婷婷激情网 | 人人添人人澡 | 国产一区在线观看视频 | 97在线播放视频 | 91视频在线观看免费 | 欧美肥妇free| 在线亚洲精品 | 日韩电影中文字幕在线 | 国产在线观看xxx | 在线观看免费黄色 | 国产精品黑丝在线观看 | 99人成在线观看视频 | 精品视频在线观看 | av高清网站在线观看 | 91理论片午午伦夜理片久久 | 99精品乱码国产在线观看 | 最近中文字幕大全中文字幕免费 | 日韩欧美在线视频一区二区三区 | 中文字幕在线观看视频一区二区三区 | 久久久 精品 | 激情欧美xxxx | 色视频国产直接看 | 一本一道久久a久久精品 | 中文字幕免费久久 | 国产成人综合在线观看 | 国产精品久久久久一区二区三区共 | 亚洲精品观看 | 狠狠的干狠狠的操 | 成人毛片100免费观看 | 欧美资源在线观看 | 四虎永久国产精品 | 超碰成人网 | 国产欧美精品xxxx另类 | www.狠狠干 | 欧美黄色特级片 | 亚洲欧美国产精品18p | 亚洲乱码精品久久久 | 一本一道波多野毛片中文在线 | 99riav1国产精品视频 | 中文字幕91视频 | av电影免费 | 国产精品久久久久久超碰 | 丁香五月亚洲综合在线 | 久久99在线 | 亚洲黄色在线看 | 国产美女免费观看 | 狠狠婷婷| 91夜夜夜 | 国产偷在线 | 91av亚洲 | 久久精品久久久久 | 丁香5月婷婷久久 | 激情综合五月婷婷 | 久久免费在线观看视频 | 91精品欧美 | 狠狠操狠狠干天天操 | 日本黄色一级电影 | 日韩精品一区在线播放 | 日韩精品一区二区三区第95 | 狠狠gao | 国产精品手机在线播放 | 国产一级视频在线免费观看 | 亚洲成av片人久久久 | 日韩精品一区二区在线视频 | 日韩一级黄色av | 免费av网站观看 | 久久五月网 | 国产美女无遮挡永久免费 | 精品不卡视频 | 久久免费精品视频 | 日日爱夜夜爱 | 依人成人综合网 | 免费91在线观看 | 激情喷水 | 国产高清区 | 色婷婷国产精品一区在线观看 | 日日夜夜网 | 成人av网站在线播放 | 黄色av一区二区三区 | 国产精品中文字幕在线 | 国内精品福利视频 | 久久久久免费视频 | 日韩最新在线 | 亚洲黄色在线观看 | 夜夜夜夜爽 | 亚洲国产综合在线 | 激情网站| 免费视频 三区 | 国产色女人 | 国产成人精品一区一区一区 | 国产精品一区免费观看 | 91大神精品视频在线观看 | 丁香激情婷婷 | 久久国产精品偷 | 九九九在线 | 亚洲h视频在线 | 91av网址| 人人爽人人爽人人片av免 | 天堂入口网站 | 日本久久精品视频 | 最近中文字幕在线中文高清版 | 国产夫妻自拍av | 五月婷婷丁香网 | 精品一区二三区 | 国产精品 亚洲精品 | 中文字幕在线观看三区 | 91x色| 国产精品情侣视频 | 亚洲激情一区二区三区 | 97视频在线观看网址 | 激情网站五月天 | 久久中文精品视频 | 久久综合一本 | h视频日本 | 精品久久久久久一区二区里番 | 特级黄色一级 | 在线日韩亚洲 | 日韩欧美精品一区二区 | 日日干夜夜干 | 在线看片日韩 | 欧美一区在线看 | 国产精品一区二区在线播放 | 亚洲精品网站 | 国产小视频你懂的 | 免费看片成人 | 久草视频在线资源 | 91污视频在线观看 | 欧美ⅹxxxxxx | 亚洲精品美女久久 | 狠狠88综合久久久久综合网 | 成人a视频片观看免费 | 日韩在线一级 | 66av99精品福利视频在线 | 欧美日韩国产一区二区在线观看 | 久久免费视频2 | 日b视频国产 | 808电影 | 久久精品官网 | 99久久超碰中文字幕伊人 | 国产精品美女 | 久久久精品免费观看 | 亚洲五月婷 | 精品黄色在线观看 | 亚洲aⅴ免费在线观看 | 亚洲精选久久 | 九草在线视频 | 在线91精品 | 99久久久久免费精品国产 | 91精品国产自产在线观看永久 | 久久久久国产a免费观看rela | 狠狠干激情 | 日韩在线观看第一页 | 久久97久久 | www一起操 | 国产精品久久久久永久免费观看 | 久草9视频 | 国产在线a | 免费看成人a | aaa毛片视频| 最新超碰 | 久久韩国免费视频 | 中文av网站 | av免费在线播放 | 国产精品久久久久av福利动漫 | 日韩欧美一区二区三区在线观看 | 精品日韩在线 | 欧美成人精品欧美一级乱黄 | 亚洲精品自在在线观看 | 一区二区不卡高清 | 丁香五香天综合情 | 西西大胆免费视频 | 国产亚洲精品美女 | 中文字幕色播 | 五月婷婷激情网 | 国产高清精 | 69av在线播放 | 欧美久久九九 | 伊人狠狠 | 成人av在线一区二区 | 亚洲综合网 | 人人澡人人添人人爽一区二区 | 一区二区激情视频 | 久久综合婷婷国产二区高清 | 久久久免费视频播放 | 久久99视频免费观看 | 97视频一区 | 97超级碰碰碰碰久久久久 | 精品视频免费久久久看 | 久草久草在线观看 | 成人国产一区 | 国产精品久久久久久久av电影 | 久久久久99精品成人片三人毛片 | 日本黄色免费在线观看 | 五月天综合婷婷 | 天堂av在线网站 | 亚洲精品国产精品国产 | 成人av在线网址 | free,性欧美 九九交易行官网 | 久久99久久99精品免费看小说 | 又爽又黄又刺激的视频 | 国产乱码精品一区二区蜜臀 | 日本中文字幕久久 | 久久免视频| 久久免视频 | www.亚洲视频 | 国产精品视频永久免费播放 | 欧美成人精品欧美一级乱黄 | 久久久久久久久毛片精品 | 免费a级观看 | av高清网站在线观看 | 免费裸体视频网 | 99久久精品电影 | 成人动图 | 蜜臀aⅴ精品一区二区三区 久久视屏网 | 亚洲精品av在线 | 麻豆视频大全 | 久久欧美精品 | 综合久久2023| 久久婷婷丁香 | 91精品国产麻豆国产自产影视 | 国产精品免费久久久久久久久久中文 | 一区二区三区在线观看 | 国产 一区二区三区 在线 | 亚洲免费精品视频 | 精品国产免费久久 | 成年人免费看的视频 | 欧美国产日韩激情 | 国内精品久久久久久久久久久 |