3、u-boot-2016 - board_init_f
i.MX6Q - u-boot_2016
飛凌嵌入式開發板 OKMX6Q_C 1G-DDR, 8G-EMMC版本。
board_init_f
原型:void board_init_f(ulong boot_flags) 路徑:common/board_f.c void board_init_f(ulong boot_flags) { #ifdef CONFIG_SYS_GENERIC_GLOBAL_DATA/** For some archtectures, global data is initialized and used before* calling this function. The data should be preserved. For others,* CONFIG_SYS_GENERIC_GLOBAL_DATA should be defined and use the stack* here to host global data until relocation.*/gd_t data;gd = &data;/** Clear global data before it is accessed at debug print* in initcall_run_list. Otherwise the debug print probably* get the wrong vaule of gd->have_console.*/zero_global_data(); #endifgd->flags = boot_flags;gd->have_console = 0;if (initcall_run_list(init_sequence_f))hang();#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \!defined(CONFIG_EFI_APP)/* NOTREACHED - jump_to_copy() does not return */hang(); #endif }這里未定義宏CONFIG_SYS_GENERIC_GLOBAL_DATA,所以 board_init_f 函數可以簡化為如下:
void board_init_f(ulong boot_flags) {gd->flags = boot_flags;gd->have_console = 0;if (initcall_run_list(init_sequence_f))hang(); }在調用board_init_f函數前,執行了mov r0, #0。所以 boot_flags 為0。
所以,board_init_f 函數的作用:
- 1、對global_data 結構體的flags 和have_console 賦值,且都是0。
flags的含義暫不祥,have_console == 0說明此時還沒有控制臺可用來打印輸出。 - 2、init_sequence_f 是一個函數指針數組,initcall_run_list 的作用就是執行這些函數指針。
一、initcall_run_list
// lib/initcall.c DECLARE_GLOBAL_DATA_PTR;int initcall_run_list(const init_fnc_t init_sequence[]) {const init_fnc_t *init_fnc_ptr;for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {unsigned long reloc_ofs = 0;int ret;if (gd->flags & GD_FLG_RELOC)reloc_ofs = gd->reloc_off; #ifdef CONFIG_EFI_APPreloc_ofs = (unsigned long)image_base; #endifdebug("initcall: %p", (char *)*init_fnc_ptr - reloc_ofs);if (gd->flags & GD_FLG_RELOC)debug(" (relocated to %p)\n", (char *)*init_fnc_ptr);elsedebug("\n");ret = (*init_fnc_ptr)();if (ret) {printf("initcall sequence %p failed at call %p (err=%d)\n",init_sequence,(char *)*init_fnc_ptr - reloc_ofs, ret);return -1;}}return 0; }先不看debug信息,函數可簡化為:
// lib/initcall.c DECLARE_GLOBAL_DATA_PTR;int initcall_run_list(const init_fnc_t init_sequence[]) {const init_fnc_t *init_fnc_ptr;for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {unsigned long reloc_ofs = 0;int ret;if (gd->flags & GD_FLG_RELOC)reloc_ofs = gd->reloc_off;ret = (*init_fnc_ptr)();if (ret) {printf("initcall sequence %p failed at call %p (err=%d)\n",init_sequence,(char *)*init_fnc_ptr - reloc_ofs, ret);return -1;}}return 0; }可以看出 initcall_run_list 內部就是用一個for循環執行函數指針數組。
二、init_sequence_f
函數數組init_sequence_f,定義如下:
static init_fnc_t init_sequence_f[] = { #ifdef CONFIG_SANDBOXsetup_ram_buf, #endifsetup_mon_len, #ifdef CONFIG_OF_CONTROLfdtdec_setup, #endif #ifdef CONFIG_TRACEtrace_early_init, #endifinitf_malloc,initf_console_record, #if defined(CONFIG_MPC85xx) || defined(CONFIG_MPC86xx)/* TODO: can this go into arch_cpu_init()? */probecpu, #endif #if defined(CONFIG_X86) && defined(CONFIG_HAVE_FSP)x86_fsp_init, #endifarch_cpu_init, /* basic arch cpu dependent setup */initf_dm,arch_cpu_init_dm,mark_bootstage, /* need timer, go after init dm */ #if defined(CONFIG_BOARD_EARLY_INIT_F)board_early_init_f, #endif/* TODO: can any of this go into arch_cpu_init()? */ #if defined(CONFIG_PPC) && !defined(CONFIG_8xx_CPUCLK_DEFAULT)get_clocks, /* get CPU and bus clocks (etc.) */ #if defined(CONFIG_TQM8xxL) && !defined(CONFIG_TQM866M) \&& !defined(CONFIG_TQM885D)adjust_sdram_tbs_8xx, #endif/* TODO: can we rename this to timer_init()? */init_timebase, #endif #if defined(CONFIG_ARM) || defined(CONFIG_MIPS) || \defined(CONFIG_BLACKFIN) || defined(CONFIG_NDS32) || \defined(CONFIG_SPARC)timer_init, /* initialize timer */ #endif #ifdef CONFIG_SYS_ALLOC_DPRAM #if !defined(CONFIG_CPM2)dpram_init, #endif #endif #if defined(CONFIG_BOARD_POSTCLK_INIT)board_postclk_init, #endif #if defined(CONFIG_SYS_FSL_CLK) || defined(CONFIG_M68K)get_clocks, #endifenv_init, /* initialize environment */ #if defined(CONFIG_8xx_CPUCLK_DEFAULT)/* get CPU and bus clocks according to the environment variable */get_clocks_866,/* adjust sdram refresh rate according to the new clock */sdram_adjust_866,init_timebase, #endifinit_baud_rate, /* initialze baudrate settings */serial_init, /* serial communications setup */console_init_f, /* stage 1 init of console */ #ifdef CONFIG_SANDBOXsandbox_early_getopt_check, #endif #ifdef CONFIG_OF_CONTROLfdtdec_prepare_fdt, #endifdisplay_options, /* say that we are here */display_text_info, /* show debugging info if required */ #if defined(CONFIG_MPC8260)prt_8260_rsr,prt_8260_clks, #endif /* CONFIG_MPC8260 */ #if defined(CONFIG_MPC83xx)prt_83xx_rsr, #endif #if defined(CONFIG_PPC) || defined(CONFIG_M68K)checkcpu, #endifprint_cpuinfo, /* display cpu info (and speed) */ #if defined(CONFIG_MPC5xxx)prt_mpc5xxx_clks, #endif /* CONFIG_MPC5xxx */ #if defined(CONFIG_DISPLAY_BOARDINFO)show_board_info, #endifINIT_FUNC_WATCHDOG_INIT #if defined(CONFIG_MISC_INIT_F)misc_init_f, #endifINIT_FUNC_WATCHDOG_RESET #if defined(CONFIG_HARD_I2C) || defined(CONFIG_SYS_I2C)init_func_i2c, #endif #if defined(CONFIG_HARD_SPI)init_func_spi, #endifannounce_dram_init,/* TODO: unify all these dram functions? */ #if defined(CONFIG_ARM) || defined(CONFIG_X86) || defined(CONFIG_NDS32) || \defined(CONFIG_MICROBLAZE) || defined(CONFIG_AVR32)dram_init, /* configure available RAM banks */ #endif #if defined(CONFIG_MIPS) || defined(CONFIG_PPC) || defined(CONFIG_M68K)init_func_ram, #endif #ifdef CONFIG_POSTpost_init_f, #endifINIT_FUNC_WATCHDOG_RESET #if defined(CONFIG_SYS_DRAM_TEST)testdram, #endif /* CONFIG_SYS_DRAM_TEST */INIT_FUNC_WATCHDOG_RESET#ifdef CONFIG_POSTinit_post, #endifINIT_FUNC_WATCHDOG_RESET/** Now that we have DRAM mapped and working, we can* relocate the code and continue running from DRAM.** Reserve memory at end of RAM for (top down in that order):* - area that won't get touched by U-Boot and Linux (optional)* - kernel log buffer* - protected RAM* - LCD framebuffer* - monitor code* - board info struct*/setup_dest_addr, #if defined(CONFIG_BLACKFIN)/* Blackfin u-boot monitor should be on top of the ram */reserve_uboot, #endif #if defined(CONFIG_SPARC)reserve_prom, #endif #if defined(CONFIG_LOGBUFFER) && !defined(CONFIG_ALT_LB_ADDR)reserve_logbuffer, #endif #ifdef CONFIG_PRAMreserve_pram, #endifreserve_round_4k, #if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF)) && \defined(CONFIG_ARM)reserve_mmu, #endif #ifdef CONFIG_DM_VIDEOreserve_video, #else # ifdef CONFIG_LCDreserve_lcd, # endif/* TODO: Why the dependency on CONFIG_8xx? */ # if defined(CONFIG_VIDEO) && (!defined(CONFIG_PPC) || defined(CONFIG_8xx)) && \!defined(CONFIG_ARM) && !defined(CONFIG_X86) && \!defined(CONFIG_BLACKFIN) && !defined(CONFIG_M68K)reserve_legacy_video, # endif #endif /* CONFIG_DM_VIDEO */reserve_trace, #if !defined(CONFIG_BLACKFIN)reserve_uboot, #endif #ifndef CONFIG_SPL_BUILDreserve_malloc,reserve_board, #endifsetup_machine,reserve_global_data,reserve_fdt,reserve_arch,reserve_stacks,setup_dram_config,show_dram_config, #if defined(CONFIG_PPC) || defined(CONFIG_M68K) || defined(CONFIG_MIPS)setup_board_part1, #endif #if defined(CONFIG_PPC) || defined(CONFIG_M68K)INIT_FUNC_WATCHDOG_RESETsetup_board_part2, #endifdisplay_new_sp, #ifdef CONFIG_SYS_EXTBDINFOsetup_board_extra, #endifINIT_FUNC_WATCHDOG_RESETreloc_fdt,setup_reloc, #if defined(CONFIG_X86) || defined(CONFIG_ARC)copy_uboot_to_ram,clear_bss,do_elf_reloc_fixups, #endif #if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX)jump_to_copy, #endifNULL, };針對imx6q平臺對數組預處理:
static init_fnc_t init_sequence_f[] = {setup_mon_len,initf_malloc,initf_console_record,arch_cpu_init, /* basic arch cpu dependent setup */initf_dm,arch_cpu_init_dm,mark_bootstage, /* need timer, go after init dm */board_early_init_f,timer_init, /* initialize timer */board_postclk_init,get_clocks,env_init, /* initialize environment */init_baud_rate, /* initialze baudrate settings */serial_init, /* serial communications setup */console_init_f, /* stage 1 init of console */display_options, /* say that we are here */display_text_info, /* show debugging info if required */print_cpuinfo, /* display cpu info (and speed) */show_board_info,init_func_i2c,announce_dram_init,dram_init, /* configure available RAM banks */setup_dest_addr,reserve_round_4k,reserve_mmu,reserve_trace,reserve_uboot,reserve_malloc,reserve_board,setup_machine,reserve_global_data,reserve_fdt,reserve_arch,reserve_stacks,setup_dram_config,show_dram_config,display_new_sp,reloc_fdt,setup_reloc,NULL, };-
1、setup_mon_len
static int setup_mon_len(void) { #if defined(__ARM__) || defined(__MICROBLAZE__)gd->mon_len = (ulong)&__bss_end - (ulong)_start; #elif defined(CONFIG_SANDBOX) || defined(CONFIG_EFI_APP)gd->mon_len = (ulong)&_end - (ulong)_init; #elif defined(CONFIG_BLACKFIN) || defined(CONFIG_NIOS2)gd->mon_len = CONFIG_SYS_MONITOR_LEN; #elif defined(CONFIG_NDS32)gd->mon_len = (ulong)(&__bss_end) - (ulong)(&_start); #else/* TODO: use (ulong)&__bss_end - (ulong)&__text_start; ? */gd->mon_len = (ulong)&__bss_end - CONFIG_SYS_MONITOR_BASE; #endifreturn 0; }預處理后代碼如下:
static int setup_mon_len(void) {gd->mon_len = (ulong)&__bss_end - (ulong)_start;return 0; }初始化了全局變量gd的mon_len值,結構體定義的注釋 /* monitor len */,個人理解就是整個程序空間的大小。
-
2、initf_malloc
// common/dlmalloc.c int initf_malloc(void) { #ifdef CONFIG_SYS_MALLOC_F_LENassert(gd->malloc_base); /* Set up by crt0.S */gd->malloc_limit = CONFIG_SYS_MALLOC_F_LEN;gd->malloc_ptr = 0; #endifreturn 0; }初始化了全局變量gd的malloc_limit(limit address)就是堆的長度 和malloc_ptr(current address)。
-
3、initf_console_record
static int initf_console_record(void) { #if defined(CONFIG_CONSOLE_RECORD) && defined(CONFIG_SYS_MALLOC_F_LEN)return console_record_init(); #elsereturn 0; #endif }CONFIG_CONSOLE_RECORD 未定義,所以函數initf_console_record為空。
-
4、arch_cpu_init
// arch/arm/cpu/armv7/mx6/soc.c int arch_cpu_init(void) {if (!is_cpu_type(MXC_CPU_MX6SL) && !is_cpu_type(MXC_CPU_MX6SX)&& !is_cpu_type(MXC_CPU_MX6UL) && !is_cpu_type(MXC_CPU_MX6ULL)&& !is_cpu_type(MXC_CPU_MX6SLL)) {/** imx6sl doesn't have pcie at all.* this bit is not used by imx6sx anymore*/u32 val;/** There are about 0.02% percentage, random pcie link down* when warm-reset is used.* clear the ref_ssp_en bit16 of gpr1 to workaround it.* then warm-reset imx6q/dl/solo again.*/val = readl(IOMUXC_BASE_ADDR + 0x4);if (val & (0x1 << 16)) {val &= ~(0x1 << 16);writel(val, IOMUXC_BASE_ADDR + 0x4);reset_cpu(0);}}init_aips();/* Need to clear MMDC_CHx_MASK to make warm reset work. */clear_mmdc_ch_mask();/** Disable self-bias circuit in the analog bandap.* The self-bias circuit is used by the bandgap during startup.* This bit should be set after the bandgap has initialized.*/init_bandgap();if (!is_cpu_type(MXC_CPU_MX6UL) && !is_cpu_type(MXC_CPU_MX6ULL)) {/** When low freq boot is enabled, ROM will not set AHB* freq, so we need to ensure AHB freq is 132MHz in such* scenario.*/if (mxc_get_clock(MXC_ARM_CLK) == 396000000)set_ahb_rate(132000000);}if (is_cpu_type(MXC_CPU_MX6UL)) {if (is_soc_rev(CHIP_REV_1_0)) {/** According to the design team's requirement on i.MX6UL,* the PMIC_STBY_REQ PAD should be configured as open* drain 100K (0x0000b8a0).*/writel(0x0000b8a0, IOMUXC_BASE_ADDR + 0x29c);} else {/** From TO1.1, SNVS adds internal pull up control for POR_B,* the register filed is GPBIT[1:0], after system boot up,* it can be set to 2b'01 to disable internal pull up.* It can save about 30uA power in SNVS mode.*/writel((readl(MX6UL_SNVS_LP_BASE_ADDR + 0x10) & (~0x1400)) | 0x400,MX6UL_SNVS_LP_BASE_ADDR + 0x10);}}if (is_cpu_type(MXC_CPU_MX6ULL)) {/** GPBIT[1:0] is suggested to set to 2'b11:* 2'b00 : always PUP100K* 2'b01 : PUP100K when PMIC_ON_REQ or SOC_NOT_FAIL* 2'b10 : always disable PUP100K* 2'b11 : PDN100K when SOC_FAIL, PUP100K when SOC_NOT_FAIL* register offset is different from i.MX6UL, since* i.MX6UL is fixed by ECO.*/writel(readl(MX6UL_SNVS_LP_BASE_ADDR) |0x3, MX6UL_SNVS_LP_BASE_ADDR);}/* Set perclk to source from OSC 24MHz */ #if defined(CONFIG_MX6SL)set_preclk_from_osc(); #endifif (is_cpu_type(MXC_CPU_MX6SX))set_uart_from_osc();imx_set_wdog_powerdown(false); /* Disable PDE bit of WMCR register */if (!is_cpu_type(MXC_CPU_MX6SL) && !is_cpu_type(MXC_CPU_MX6UL) &&!is_cpu_type(MXC_CPU_MX6ULL) && !is_cpu_type(MXC_CPU_MX6SLL))imx_set_pcie_phy_power_down();if (!is_mx6dqp() && !is_cpu_type(MXC_CPU_MX6UL) &&!is_cpu_type(MXC_CPU_MX6ULL) && !is_cpu_type(MXC_CPU_MX6SLL))imx_set_vddpu_power_down();#ifdef CONFIG_APBH_DMA/* Start APBH DMA */mxs_dma_init(); #endifinit_src();if (is_mx6dqp())writel(0x80000201, 0xbb0608);return 0; }arch_cpu_init是跟CPU架構嚴格相關的函數,一般是由芯片官方提供。 不同的芯片,這塊內容不一樣。
這里只是記錄過程,不去追究細節。-
4.1、init_aips
void init_aips(void) {struct aipstz_regs *aips1, *aips2, *aips3;aips1 = (struct aipstz_regs *)AIPS1_BASE_ADDR;aips2 = (struct aipstz_regs *)AIPS2_BASE_ADDR;aips3 = (struct aipstz_regs *)AIPS3_BASE_ADDR;/** Set all MPROTx to be non-bufferable, trusted for R/W,* not forced to user-mode.*/writel(0x77777777, &aips1->mprot0);writel(0x77777777, &aips1->mprot1);writel(0x77777777, &aips2->mprot0);writel(0x77777777, &aips2->mprot1);/** Set all OPACRx to be non-bufferable, not require* supervisor privilege level for access,allow for* write access and untrusted master access.*/writel(0x00000000, &aips1->opacr0);writel(0x00000000, &aips1->opacr1);writel(0x00000000, &aips1->opacr2);writel(0x00000000, &aips1->opacr3);writel(0x00000000, &aips1->opacr4);writel(0x00000000, &aips2->opacr0);writel(0x00000000, &aips2->opacr1);writel(0x00000000, &aips2->opacr2);writel(0x00000000, &aips2->opacr3);writel(0x00000000, &aips2->opacr4);if (is_cpu_type(MXC_CPU_MX6ULL) || is_cpu_type(MXC_CPU_MX6SX) ||is_soc_type(MXC_SOC_MX7)) {/** Set all MPROTx to be non-bufferable, trusted for R/W,* not forced to user-mode.*/writel(0x77777777, &aips3->mprot0);writel(0x77777777, &aips3->mprot1);/** Set all OPACRx to be non-bufferable, not require* supervisor privilege level for access,allow for* write access and untrusted master access.*/writel(0x00000000, &aips3->opacr0);writel(0x00000000, &aips3->opacr1);writel(0x00000000, &aips3->opacr2);writel(0x00000000, &aips3->opacr3);writel(0x00000000, &aips3->opacr4);} } -
4.2、clear_mmdc_ch_mask
static void clear_mmdc_ch_mask(void) {struct mxc_ccm_reg *mxc_ccm = (struct mxc_ccm_reg *)CCM_BASE_ADDR;u32 reg;reg = readl(&mxc_ccm->ccdr);/* Clear MMDC channel mask */if (is_cpu_type(MXC_CPU_MX6SX) || is_cpu_type(MXC_CPU_MX6UL) ||is_cpu_type(MXC_CPU_MX6SL) || is_cpu_type(MXC_CPU_MX6ULL) ||is_cpu_type(MXC_CPU_MX6SLL))reg &= ~(MXC_CCM_CCDR_MMDC_CH1_HS_MASK);elsereg &= ~(MXC_CCM_CCDR_MMDC_CH1_HS_MASK | MXC_CCM_CCDR_MMDC_CH0_HS_MASK);writel(reg, &mxc_ccm->ccdr); } -
4.3、init_bandgap
static void init_bandgap(void) {struct anatop_regs *anatop = (struct anatop_regs *)ANATOP_BASE_ADDR;struct ocotp_regs *ocotp = (struct ocotp_regs *)OCOTP_BASE_ADDR;struct fuse_bank *bank = &ocotp->bank[1];struct fuse_bank1_regs *fuse =(struct fuse_bank1_regs *)bank->fuse_regs;uint32_t val;/** Ensure the bandgap has stabilized.*/while (!(readl(&anatop->ana_misc0) & 0x80));/** For best noise performance of the analog blocks using the* outputs of the bandgap, the reftop_selfbiasoff bit should* be set.*/writel(BM_ANADIG_ANA_MISC0_REFTOP_SELBIASOFF, &anatop->ana_misc0_set);/** On i.MX6ULL,we need to set VBGADJ bits according to the* REFTOP_TRIM[3:0] in fuse table* 000 - set REFTOP_VBGADJ[2:0] to 3b'110,* 110 - set REFTOP_VBGADJ[2:0] to 3b'000,* 001 - set REFTOP_VBGADJ[2:0] to 3b'001,* 010 - set REFTOP_VBGADJ[2:0] to 3b'010,* 011 - set REFTOP_VBGADJ[2:0] to 3b'011,* 100 - set REFTOP_VBGADJ[2:0] to 3b'100,* 101 - set REFTOP_VBGADJ[2:0] to 3b'101,* 111 - set REFTOP_VBGADJ[2:0] to 3b'111,*/if (is_cpu_type(MXC_CPU_MX6ULL)) {val = readl(&fuse->mem0);val >>= OCOTP_MEM0_REFTOP_TRIM_SHIFT;val &= 0x7;writel(val << BM_ANADIG_ANA_MISC0_REFTOP_VBGADJ_SHIFT,&anatop->ana_misc0_set);} } -
4.4、mxs_dma_init
void mxs_dma_init(void) {struct mxs_apbh_regs *apbh_regs =(struct mxs_apbh_regs *)MXS_APBH_BASE;#ifdef CONFIG_MX6if (check_module_fused(MX6_MODULE_APBHDMA)) {printf("NAND APBH-DMA@0x%x is fused, disable it\n",MXS_APBH_BASE);return;} #endifmxs_reset_block(&apbh_regs->hw_apbh_ctrl0_reg);#ifdef CONFIG_APBH_DMA_BURST8writel(APBH_CTRL0_AHB_BURST8_EN,&apbh_regs->hw_apbh_ctrl0_set); #elsewritel(APBH_CTRL0_AHB_BURST8_EN,&apbh_regs->hw_apbh_ctrl0_clr); #endif#ifdef CONFIG_APBH_DMA_BURSTwritel(APBH_CTRL0_APB_BURST_EN,&apbh_regs->hw_apbh_ctrl0_set); #elsewritel(APBH_CTRL0_APB_BURST_EN,&apbh_regs->hw_apbh_ctrl0_clr); #endif } -
4.5、init_src
void init_src(void) {struct src *src_regs = (struct src *)SRC_BASE_ADDR;u32 val;/** force warm reset sources to generate cold reset* for a more reliable restart*/val = readl(&src_regs->scr);val &= ~(1 << SRC_SCR_WARM_RESET_ENABLE);writel(val, &src_regs->scr); }
-
-
5、initf_dm
static int initf_dm(void) { #if defined(CONFIG_DM) && defined(CONFIG_SYS_MALLOC_F_LEN)int ret;ret = dm_init_and_scan(true);if (ret)return ret; #endif #ifdef CONFIG_TIMER_EARLYret = dm_timer_init();if (ret)return ret; #endifreturn 0; }這里定義了CONFIG_DM和CONFIG_SYS_MALLOC_F_LEN,未定義CONFIG_TIMER_EARLY。
- dm_init_and_scan
-
6、arch_cpu_init_dm
__weak int arch_cpu_init_dm(void) {return 0; }該函數為空。
-
7、mark_bootstage
/* Record the board_init_f() bootstage (after arch_cpu_init()) */ static int mark_bootstage(void) {bootstage_mark_name(BOOTSTAGE_ID_START_UBOOT_F, "board_init_f");return 0; }該函數記錄了啟動階段。
-
8、board_early_init_f
// board/freescale/mx6sabresd/mx6sabresd.c int board_early_init_f(void) {setup_iomux_uart(); #if defined(CONFIG_VIDEO_IPUV3)setup_display(); #endifreturn 0; } -
9、timer_init
// arch/arm/imx-common/timer.c int timer_init(void) {int i;/* setup GP Timer 1 */__raw_writel(GPTCR_SWR, &cur_gpt->control);/* We have no udelay by now */for (i = 0; i < 100; i++)__raw_writel(0, &cur_gpt->control);i = __raw_readl(&cur_gpt->control);i &= ~GPTCR_CLKSOURCE_MASK;#ifdef CONFIG_MXC_GPT_HCLKif (gpt_has_clk_source_osc()) {i |= GPTCR_CLKSOURCE_OSC | GPTCR_TEN;/* For DL/S, SX, UL, ULL set 24Mhz OSC Enable bit and prescaler */if (is_cpu_type(MXC_CPU_MX6DL) ||is_cpu_type(MXC_CPU_MX6SOLO) ||is_cpu_type(MXC_CPU_MX6SX) ||is_cpu_type(MXC_CPU_MX7D) ||is_cpu_type(MXC_CPU_MX6UL) ||is_cpu_type(MXC_CPU_MX6ULL) ||is_cpu_type(MXC_CPU_MX6SLL)) {i |= GPTCR_24MEN;/* Produce 3Mhz clock */__raw_writel((7 << GPTPR_PRESCALER24M_SHIFT),&cur_gpt->prescaler);}} else {i |= GPTCR_CLKSOURCE_PRE | GPTCR_TEN;} #else__raw_writel(0, &cur_gpt->prescaler); /* 32Khz */i |= GPTCR_CLKSOURCE_32 | GPTCR_TEN; #endif__raw_writel(i, &cur_gpt->control);gd->arch.tbl = __raw_readl(&cur_gpt->counter);gd->arch.tbu = 0;return 0; } -
10、board_postclk_init
// arch/arm/cpu/armv7/mx6/soc.c int board_postclk_init(void) {/* NO LDO SOC on i.MX6SLL */if (is_cpu_type(MXC_CPU_MX6SLL))return 0;set_ldo_voltage(LDO_SOC, 1175); /* Set VDDSOC to 1.175V */return 0; } -
11、get_clocks
// arch/arm/imx-common/speed.c int get_clocks(void) { #ifdef CONFIG_FSL_ESDHC #ifdef CONFIG_FSL_USDHC #if CONFIG_SYS_FSL_ESDHC_ADDR == USDHC2_BASE_ADDRgd->arch.sdhc_clk = mxc_get_clock(MXC_ESDHC2_CLK); #elif CONFIG_SYS_FSL_ESDHC_ADDR == USDHC3_BASE_ADDRgd->arch.sdhc_clk = mxc_get_clock(MXC_ESDHC3_CLK); #elif CONFIG_SYS_FSL_ESDHC_ADDR == USDHC4_BASE_ADDRgd->arch.sdhc_clk = mxc_get_clock(MXC_ESDHC4_CLK); #elsegd->arch.sdhc_clk = mxc_get_clock(MXC_ESDHC_CLK); #endif #else #if CONFIG_SYS_FSL_ESDHC_ADDR == MMC_SDHC2_BASE_ADDRgd->arch.sdhc_clk = mxc_get_clock(MXC_ESDHC2_CLK); #elif CONFIG_SYS_FSL_ESDHC_ADDR == MMC_SDHC3_BASE_ADDRgd->arch.sdhc_clk = mxc_get_clock(MXC_ESDHC3_CLK); #elif CONFIG_SYS_FSL_ESDHC_ADDR == MMC_SDHC4_BASE_ADDRgd->arch.sdhc_clk = mxc_get_clock(MXC_ESDHC4_CLK); #elsegd->arch.sdhc_clk = mxc_get_clock(MXC_ESDHC_CLK); #endif #endif #endifreturn 0; }獲取 sdhc 時鐘,eMMC/SD 接口的控制器。
-
12、env_init
// 因為配置emmc啟動,所以在common/env_mmc.c int env_init(void) {/* use default */gd->env_addr = (ulong)&default_enviornment[0];gd->env_valid = 1;return 0; }default_enviornment 是默認的環境變量,其實就是一個字符串數組, 存儲著基本的一些變量。
定義在 include/env_default.h 中。 -
13、init_baud_rate
static int init_baud_rate(void) {gd->baudrate = getenv_ulong("baudrate", 10, CONFIG_BAUDRATE);return 0; } /*** Decode the integer value of an environment variable and return it.** @param name Name of environemnt variable* @param base Number base to use (normally 10, or 16 for hex)* @param default_val Default value to return if the variable is not* found* @return the decoded value, or default_val if not found*/ ulong getenv_ulong(const char *name, int base, ulong default_val) {/** We can use getenv() here, even before relocation, since the* environment variable value is an integer and thus short.*/const char *str = getenv(name);return str ? simple_strtoul(str, NULL, base) : default_val; } -
14、serial_init
// drivers/serial/serial.c int serial_init(void) {gd->flags |= GD_FLG_SERIAL_READY;return get_current()->start(); } static struct serial_device *get_current(void) {struct serial_device *dev;if (!(gd->flags & GD_FLG_RELOC))dev = default_serial_console();else if (!serial_current)dev = default_serial_console();elsedev = serial_current;/* We must have a console device */if (!dev) { #ifdef CONFIG_SPL_BUILDputs("Cannot find console\n");hang(); #elsepanic("Cannot find console\n"); #endif}return dev; }default_serial_console 定義在 drivers/serial/serial_mxc.c
static struct serial_device mxc_serial_drv = {.name = "mxc_serial",.start = mxc_serial_init,.stop = NULL,.setbrg = mxc_serial_setbrg,.putc = mxc_serial_putc,.puts = default_serial_puts,.getc = mxc_serial_getc,.tstc = mxc_serial_tstc, };__weak struct serial_device *default_serial_console(void) {return &mxc_serial_drv; } -
15、console_init_f
// common/console.c /* Called before relocation - use serial functions */ int console_init_f(void) {gd->have_console = 1; #ifdef CONFIG_SILENT_CONSOLEif (getenv("silent") != NULL)gd->flags |= GD_FLG_SILENT; #endifprint_pre_console_buffer(PRE_CONSOLE_FLUSHPOINT1_SERIAL);return 0; }
這里gd->have_console 賦值為1,從這里開始串口就可以輸出數據了。
-
16、display_options
// lib/display_options.c int display_options (void) { #if defined(BUILD_TAG)printf ("\n\n%s, Build: %s\n\n", version_string, BUILD_TAG); #elseprintf ("\n\n%s\n\n", version_string); #endif } -
17、display_text_info
static int display_text_info(void) { #if !defined(CONFIG_SANDBOX) && !defined(CONFIG_EFI_APP)ulong bss_start, bss_end, text_base;bss_start = (ulong)&__bss_start;bss_end = (ulong)&__bss_end;#ifdef CONFIG_SYS_TEXT_BASEtext_base = CONFIG_SYS_TEXT_BASE; #elsetext_base = CONFIG_SYS_MONITOR_BASE; #endifdebug("U-Boot code: %08lX -> %08lX BSS: -> %08lX\n",text_base, bss_start, bss_end); #endif#ifdef CONFIG_USE_IRQdebug("IRQ Stack: %08lx\n", IRQ_STACK_START);debug("FIQ Stack: %08lx\n", FIQ_STACK_START); #endifreturn 0; } -
18、print_cpuinfo
// arch/arm/imx-common/cpu.c int print_cpuinfo(void) {u32 cpurev;__maybe_unused u32 max_freq; #if defined(CONFIG_DBG_MONITOR)struct dbg_monitor_regs *dbg =(struct dbg_monitor_regs *)DEBUG_MONITOR_BASE_ADDR; #endifcpurev = get_cpu_rev();#if defined(CONFIG_IMX_THERMAL)struct udevice *thermal_dev;int cpu_tmp, minc, maxc, ret;printf("CPU: Freescale i.MX%s rev%d.%d",get_imx_type((cpurev & 0xFF000) >> 12),(cpurev & 0x000F0) >> 4,(cpurev & 0x0000F) >> 0);max_freq = get_cpu_speed_grade_hz();if (!max_freq || max_freq == mxc_get_clock(MXC_ARM_CLK)) {printf(" at %dMHz\n", mxc_get_clock(MXC_ARM_CLK) / 1000000);} else {printf(" %d MHz (running at %d MHz)\n", max_freq / 1000000,mxc_get_clock(MXC_ARM_CLK) / 1000000);} #elseprintf("CPU: Freescale i.MX%s rev%d.%d at %d MHz\n",get_imx_type((cpurev & 0xFF000) >> 12),(cpurev & 0x000F0) >> 4,(cpurev & 0x0000F) >> 0,mxc_get_clock(MXC_ARM_CLK) / 1000000); #endif#if defined(CONFIG_IMX_THERMAL)puts("CPU: ");switch (get_cpu_temp_grade(&minc, &maxc)) {case TEMP_AUTOMOTIVE:puts("Automotive temperature grade ");break;case TEMP_INDUSTRIAL:puts("Industrial temperature grade ");break;case TEMP_EXTCOMMERCIAL:puts("Extended Commercial temperature grade ");break;default:puts("Commercial temperature grade ");break;}printf("(%dC to %dC)", minc, maxc);ret = uclass_get_device(UCLASS_THERMAL, 0, &thermal_dev);if (!ret) {ret = thermal_get_temp(thermal_dev, &cpu_tmp);if (!ret)printf(" at %dC\n", cpu_tmp);elsedebug(" - invalid sensor data\n");} else {debug(" - invalid sensor device\n");} #endif#if defined(CONFIG_DBG_MONITOR)if (readl(&dbg->snvs_addr))printf("DBG snvs regs addr 0x%x, data 0x%x, info 0x%x\n",readl(&dbg->snvs_addr),readl(&dbg->snvs_data),readl(&dbg->snvs_info)); #endifprintf("Reset cause: %s\n", get_reset_cause());return 0; } #endif -
19、show_board_info
// common/board_info.c /* * If the root node of the DTB has a "model" property, show it. * Then call checkboard(). */ int show_board_info(void) { #if defined(CONFIG_OF_CONTROL) && !defined(CONFIG_CUSTOM_BOARDINFO)DECLARE_GLOBAL_DATA_PTR;const char *model;model = fdt_getprop(gd->fdt_blob, 0, "model", NULL);if (model)printf("Model: %s\n", model); #endifreturn checkboard(); } // CONFIG_OF_CONTROL 未定義 // board/freescale/mx6sabresd/mx6sabresd.c int checkboard() {puts("Board: MX6-SabreSD\n");return 0; } -
20、init_func_i2c
static int init_func_i2c(void) {puts("I2C: "); #ifdef CONFIG_SYS_I2Ci2c_init_all(); #elsei2c_init(CONFIG_SYS_I2C_SPEED, CONFIG_SYS_I2C_SLAVE); #endifputs("ready\n"); } -
21、announce_dram_init
static int announce_dram_init(void) {puts("DRAM: ");return 0; } -
22、dram_init, /* configure available RAM banks */
// board/freescale/mx6sabresd/mx6sabresd.c int dram_init(void) {gd_ram_size = imx_ddr_size();return 0; }- imx_ddr_size
這里通過讀取寄存器的值來計算出ddr的大小,單位字節。
-
23、setup_dest_addr
static int setup_dest_addr(void) {debug("Monitor len: %08lX\n", gd->mon_len);/** Ram is setup, size stored in gd !!*/debug("Ram size: %08lX\n", (ulong)gd->ram_size); #ifdef CONFIG_SYS_MEM_RESERVE_SECURE/* Reserve memory for secure MMU tables, and/or security monitor */gd->ram_size -= CONFIG_SYS_MEM_RESERVE_SECURE;/** Record secure memory location. Need recalcuate if memory splits* into banks, or the ram base is not zero.*/gd->secure_ram = gd->ram_size; #endif/** Subtract specified amount of memory to hide so that it won't* get "touched" at all by U-Boot. By fixing up gd->ram_size* the Linux kernel should now get passed the now "corrected"* memory size and won't touch it either. This has been used* by arch/powerpc exclusively. Now ARMv8 takes advantage of* thie mechanism. If memory is split into banks, addresses* need to be calculated.*/gd->ram_size = board_reserve_ram_top(gd->ram_size);#ifdef CONFIG_SYS_SDRAM_BASEgd->ram_top = CONFIG_SYS_SDRAM_BASE; #endifgd->ram_top += get_effective_memsize();gd->ram_top = board_get_usable_ram_top(gd->mon_len);gd->relocaddr = gd->ram_top;debug("Ram top: %08lX\n", (ulong)gd->ram_top); #if defined(CONFIG_MP) && (defined(CONFIG_MPC86xx) || defined(CONFIG_E500))/** We need to make sure the location we intend to put secondary core* boot code is reserved and not used by any part of u-boot*/if (gd->relocaddr > determine_mp_bootpg(NULL)) {gd->relocaddr = determine_mp_bootpg(NULL);debug("Reserving MP boot page to %08lx\n", gd->relocaddr);} #endifreturn 0; }- 23.1、board_reserve_ram_top
這里未定義CONFIG_SYS_MEM_TOP_HIDE。
- 23.2、CONFIG_SYS_SDRAM_BASE 定義在 include/configs/mx6sabre_common.h
MMDC0_ARB_BASE_ADDR 定義在 arch/arm/include/asm/arch-mx6/imx-regs.h
#define MMDC0_ARB_BASE_ADDR 0x10000000- 23.3、get_effective_memsize
- 23.4、board_get_usable_ram_top
這里設置重定位相關數據:
- gd->ram_size = 0x40000000 = 1G
- gd->relocaddr = gd->ram_top = 0x50000000
-
24、reserve_round_4k
/* Round memory pointer down to next 4 kB limit */ static int reserve_round_4k(void) {gd->relocaddr &= ~(4096 - 1);// 對 gd->relocaddr 做4K對齊。// 這里 relocaddr = 0x50000000return 0; } -
25、reserve_mmu
static int reserve_mmu(void) {/* reserve TLB table */gd->arch.tlb_size = PGTABLE_SIZE;// 這里回 gd->arch.tlb_size 賦值,PGTABLE_SIZE = 16KB,這段作為MMU table用。gd->relocaddr -= gd->arch.tlb_size;// relocaddr 減掉 mmu table的長度。這里relocaddr = 0x50000000 - 0x4000 = 0x4fffc000/* round down to next 64 kB limit */gd->relocaddr &= ~(0x10000 - 1);// 這里的relocaddr 是 mmu table 的基地址,這個操作的是對 mmu table 基地址做64kB對齊。// 這時 relocaddr = 0x4fffc000 & (~0xffff) = 0x4fff0000gd->arch.tlb_addr = gd->relocaddr;// 把對齊后的mmu table 基地址賦值給 gd->arch.tlb_addr, 這里relocaddr = 4fff0000 debug("TLB table from %08lx to %08lx\n", gd->arch.tlb_addr,gd->arch.tlb_addr + gd->arch.tlb_size);return 0; }PGTABLE_SIZE 定義在 arch/arm/include/asm/system.h, 因為imx6q是32為的arm,所以定義應是如下:
#ifndef PGTABLE_SIZE #define PGTABLE_SIZE (4096 * 4) #endif -
26、reserve_trace
static int reserve_trace(void) { #ifdef CONFIG_TRACEgd->relocaddr -= CONFIG_TRACE_BUFFER_SIZE;gd->trace_buff = map_sysmem(gd->relocaddr, CONFIG_TRACE_BUFFER_SIZE);debug("Reserving %dk for trace data at: %08lx\n",CONFIG_TRACE_BUFFER_SIZE >> 10, gd->relocaddr); #endifreturn 0; }未定義 CONFIG_TRACE,所以這里是空函數。
-
27、reserve_uboot
static int reserve_uboot(void) {/** reserve memory for U-Boot code, data & bss* round down to next 4 kB limit*/gd->relocaddr -= gd->mon_len;// mon_len 的長度在setup_mon_len 里已設置好,這里放u-boot的代碼。gd->relocaddr &= ~(4096 - 1);// 對 relocaddr 再次做 4kB 對齊 #ifdef CONFIG_E500/* round down to next 64 kB limit so that IVPR stays aligned */gd->relocaddr &= ~(65536 - 1); #endifdebug("Reserving %ldk for U-Boot at: %08lx\n", gd->mon_len >> 10,gd->relocaddr);gd->start_addr_sp = gd->relocaddr;// 4kB 對齊后的 relocaddr 作為新的棧指針。return 0; }這里保留的內存是為了后面代碼重定位做準備,就是把u-boot代碼拷貝到這里。
-
28、reserve_malloc
static int reserve_malloc(void) {gd->start_addr_sp = gd->start_addr_sp - TOTAL_MALLOC_LEN;// 新的棧指針減去 TOTAL_MALLOC_LEN,// 這個內存區域作為堆使用debug("Reserving %dk for malloc() at: %08lx\n",TOTAL_MALLOC_LEN >> 10, gd->start_addr_sp);return 0; }這里保留的內存是堆的位置。
#if defined(CONFIG_ENV_IS_EMBEDDED) #define TOTAL_MALLOC_LEN CONFIG_SYS_MALLOC_LEN #elif ( ((CONFIG_ENV_ADDR+CONFIG_ENV_SIZE) < CONFIG_SYS_MONITOR_BASE) || \(CONFIG_ENV_ADDR >= (CONFIG_SYS_MONITOR_BASE + CONFIG_SYS_MONITOR_LEN)) ) || \defined(CONFIG_ENV_IS_IN_NVRAM) #define TOTAL_MALLOC_LEN (CONFIG_SYS_MALLOC_LEN + CONFIG_ENV_SIZE) #else #define TOTAL_MALLOC_LEN CONFIG_SYS_MALLOC_LEN #endif // include/configs/mx6sabre_common.h #define CONFIG_SYS_MALLOC_LEN (16 * SZ_1M) #define CONFIG_ENV_SIZE (8 * 1024)
TOTAL_MALLOC_LEN 定義在 include/common.h 中。根據debug打印,TOTAL_MALLOC_LEN 為 16392kB,所以這里 TOTAL_MALLOC_LEN = (CONFIG_SYS_MALLOC_LEN + CONFIG_ENV_SIZE)
-
29、reserve_board
/* (permanently) allocate a Board Info struct */ static int reserve_board(void) {if (!gd->bd) {gd->start_addr_sp -= sizeof(bd_t);// 堆的及地址減去 bd_t 結構體的大小,這個區域存放 bd_t 結構體。gd->bd = (bd_t *)map_sysmem(gd->start_addr_sp, sizeof(bd_t));// map_sysmem 返回的還是 start_addr_sp,memset(gd->bd, '\0', sizeof(bd_t));debug("Reserving %zu Bytes for Board Info at: %08lx\n",sizeof(bd_t), gd->start_addr_sp);}return 0; }這里保留了一個 bd_t 結構體的長度,存放板級信息的結構體,u-boot里面兩個重要的全局變量一個是 global_data, 一個就是這個 bd_t 。
- map_sysmem
這里未定義 CONFIG_ARCH_MAP_SYSMEM。
-
30、setup_machine
static int setup_machine(void) { #ifdef CONFIG_MACH_TYPEgd->bd->bi_arch_number = CONFIG_MACH_TYPE; /* board id for Linux */ #endifreturn 0; } // CONFIG_MACH_TYPE 定義在 include/configs/mx6sabresd.h 中, // #define CONFIG_MACH_TYPE 3980 -
31、reserve_global_data
static int reserve_global_data(void) {gd->start_addr_sp -= sizeof(gd_t);// 這里在bd_t的基地址在減去 gd_t 的大小。// 這里的區域存放新的 gd_t 結構體。 gd->new_gd = (gd_t *)map_sysmem(gd->start_addr_sp, sizeof(gd_t));// 這里 map_sysmem 和上面的一樣,返回 start_addr_spdebug("Reserving %zu Bytes for Global Data at: %08lx\n",sizeof(gd_t), gd->start_addr_sp);return 0; }這里為新的global_data結構體保留了內存,之前的global_data結構體是存放在 OCRAM上的,這里是DDRAM上。
-
32、reserve_fdt
static int reserve_fdt(void) { #ifndef CONFIG_OF_EMBED/** If the device tree is sitting immediately above our image then we* must relocate it. If it is embedded in the data section, then it* will be relocated with other data.*/if (gd->fdt_blob) {gd->fdt_size = ALIGN(fdt_totalsize(gd->fdt_blob) + 0x1000, 32);gd->start_addr_sp -= gd->fdt_size;gd->new_fdt = map_sysmem(gd->start_addr_sp, gd->fdt_size);debug("Reserving %lu Bytes for FDT at: %08lx\n",gd->fdt_size, gd->start_addr_sp);} #endifreturn 0; }這里未定義 CONFIG_OF_EMBED,所以這里為空函數。
-
33、reserve_arch
/* Architecture-specific memory reservation */ __weak int reserve_arch(void) {return 0; } -
34、reserve_stacks
static int reserve_stacks(void) {/* make stack pointer 16-byte aligned */gd->start_addr_sp -= 16;// 在新的 gd_t 的基地址在減去16字節。gd->start_addr_sp &= ~0xf;// 對新的start_addr_sp 做16字節對齊/** let the architecture-specific code tailor gd->start_addr_sp and* gd->irq_sp*/return arch_reserve_stacks(); } int arch_reserve_stacks(void) {return 0; } -
35、setup_dram_config
static int setup_dram_config(void) {/* Ram is board specific, so move it to board code ... */dram_init_banksize();return 0; }__weak void dram_init_banksize(void) { #if defined(CONFIG_NR_DRAM_BANKS) && defined(CONFIG_SYS_SDRAM_BASE)gd->bd->bi_dram[0].start = CONFIG_SYS_SDRAM_BASE;gd->bd->bi_dram[0].size = get_effective_memsize(); #endif } // include/configs/mx6sabre_common.h #define CONFIG_NR_DRAM_BANKS 1 #define PHYS_SDRAM MMDC0_ARB_BASE_ADDR #define CONFIG_SYS_SDRAM_BASE PHYS_SDRAM// arch/arm/include/asm/arch-mx6/imx-regs.h #define MMDC0_ARB_BASE_ADDR 0x10000000這里是填充bd_t結構體,關于dram信息的域。
-
36、show_dram_config
static int show_dram_config(void) {unsigned long long size;#ifdef CONFIG_NR_DRAM_BANKSint i;debug("\nRAM Configuration:\n");for (i = size = 0; i < CONFIG_NR_DRAM_BANKS; i++) {size += gd->bd->bi_dram[i].size;debug("Bank #%d: %llx ", i,(unsigned long long)(gd->bd->bi_dram[i].start)); #ifdef DEBUGprint_size(gd->bd->bi_dram[i].size, "\n"); #endif}debug("\nDRAM: "); #elsesize = gd->ram_size; #endifprint_size(size, "");board_add_ram_info(0);putc('\n');return 0; } -
37、display_new_sp
static int display_new_sp(void) {debug("New Stack Pointer is: %08lx\n", gd->start_addr_sp);return 0; } -
38、reloc_fdt
static int reloc_fdt(void) { #ifndef CONFIG_OF_EMBEDif (gd->flags & GD_FLG_SKIP_RELOC)return 0;if (gd->new_fdt) {memcpy(gd->new_fdt, gd->fdt_blob, gd->fdt_size);gd->fdt_blob = gd->new_fdt;} #endifreturn 0; }這里未定義 CONFIG_OF_EMBED,所以這里為空函數。
-
39、setup_reloc
static int setup_reloc(void) {if (gd->flags & GD_FLG_SKIP_RELOC) {debug("Skipping relocation due to flag\n");return 0;}#ifdef CONFIG_SYS_TEXT_BASEgd->reloc_off = gd->relocaddr - CONFIG_SYS_TEXT_BASE;// 對 reloc_off 賦值,reloc_off 是重定位后地址到源地址的偏移量// relocaddr 存放的是重定位后的u-boot代碼的起始地址// CONFIG_SYS_TEXT_BASE 是最開始 u-boot代碼的起始地址 #ifdef CONFIG_M68K/** On all ColdFire arch cpu, monitor code starts always* just after the default vector table location, so at 0x400*/gd->reloc_off = gd->relocaddr - (CONFIG_SYS_TEXT_BASE + 0x400); #endif #endifmemcpy(gd->new_gd, (char *)gd, sizeof(gd_t));// 把現在的 gd_t 結構體內容拷貝到新的 gd_t 結構體中。debug("Relocation Offset is: %08lx\n", gd->reloc_off);debug("Relocating to %08lx, new gd at %08lx, sp at %08lx\n",gd->relocaddr, (ulong)map_to_sysmem(gd->new_gd),gd->start_addr_sp);return 0; }這里未定義 CONFIG_M68K 。
到這里重定位的準備都做好了,下一步就是重定位 u-boot 代碼。
總結:
board_init_f 函數在這里主要功能是硬件上初始化了芯片的時鐘,定時器等。 外設主要初始化了UART(串口debug), I2C(PMIC相關)。這里沒有初始化ddr的代碼,是因為這里ddr是由Bootrom初始化的。另外就是軟件上主要設置了全局變量 global_data結構體,為后面代碼重定位做準備。總結
以上是生活随笔為你收集整理的3、u-boot-2016 - board_init_f的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python之简介
- 下一篇: 投诉百度快照对排名的影响