ubootのstart_Armboot分析2

25867 ワード

注:本文は朱先生の課程整理を学ぶノートで、uboot-1.3.4とs 5 pc 11 x分析に基づいています.
前の分析はubootのstartを参照してください.Armboot分析1
  • interrupt_init
  • int interrupt_init(void)
    {
    
        S5PC11X_TIMERS *const timers = S5PC11X_GetBase_TIMERS();
    
        /* use PWM Timer 4 because it has no output */
        /* prescaler for Timer 4 is 16 */
        timers->TCFG0 = 0x0f00;
        if (timer_load_val == 0) {
            /*
             * for 10 ms clock period @ PCLK with 4 bit divider = 1/2
             * (default) and prescaler = 16. Should be 10390
             * @33.25MHz and  @ 66 MHz
             */
            timer_load_val = get_PCLK() / (16 * 100);
        }
    
        /* load value for 10 ms timeout */
        lastdec = timers->TCNTB4 = timer_load_val;
        /* auto load, manual update of Timer 4 */
        timers->TCON = (timers->TCON & ~0x00700000) | TCON_4_AUTO | TCON_4_UPDATE;
        /* auto load, start Timer 4 */
        timers->TCON = (timers->TCON & ~0x00700000) | TCON_4_AUTO | COUNT_4_ON;
        timestamp = 0;
    
    
        return (0);
    }
    

    名前関数は割り込み初期化に関係していますが、実際にはそうではありません.実際にはこの関数はタイマを初期化するために使用されています(実際にはTimer 4を使用しています).
    s 5 pv 210には5つのPWMタイマがある.ここで、Timer 0-timer 3は、対応するPWM信号出力のピンを有する.一方、Timer 4はピンがなく、PWM波形を出力できない.Timer 4は、設計時にPWM波形を出力するためのものではなく(ピンがなく、TCMPBレジスタがない)、このタイマーはタイミングを計るために設計されている.Timer 4は、2つのレジスタ:TCNTB 4、TCNTO 4を計時に使用します.TCNTBには1つの数が格納されており、この数がタイミング回数(時間ごとにクロックで決定され、実は2段クロック分周器で決定される)である.私たちはタイミングを決める時、タイミング/基準時間=数をTCNTBに入れるだけです.TCNTOレジスタで読み取り時間を0に減らすことができ、0に読み込んだ後、決まった時間になったことがわかります.
    interrupt_Init関数はtimer 4をタイミング10 msに設定します.肝心な部分はget_PCLK関数取得システム設定PCLK_PSYSクロック周波数は、TCFG 0とTCFG 1を分周して10 msに設定したときにTCNTBに書き込む必要がある値を算出してTCNTBに書き込み、auto reloadモードに設定し、タイマーをつけて計時を開始するとなくなります.
    S5PC11X_TIMERSは以下のように定義される.
    
    typedef struct {
        S5PC11X_REG32   TCFG0;
        S5PC11X_REG32   TCFG1;
        S5PC11X_REG32   TCON;
        S5PC11X_TIMER   ch[4];
        S5PC11X_REG32   TCNTB4;
        S5PC11X_REG32   TCNTO4;
    } S5PC11X_TIMERS;

    S5PC11X_GetBase_TIMERS()は以下のように定義される.
    static inline S5PC11X_TIMERS * S5PC11X_GetBase_TIMERS(void)
    {
        return (S5PC11X_TIMERS *)ELFIN_TIMER_BASE;
    }

    構造体を定義することでレジスタにアクセスし,関数で設定値を自動的に計算してタイマを設定することを学ぶ.
  • env_init
  • int env_init(void)
    {
    #if defined(ENV_IS_EMBEDDED)
        ulong total;
        int crc1_ok = 0, crc2_ok = 0;
        env_t *tmp_env1, *tmp_env2;
    
        total = CFG_ENV_SIZE;
    
        tmp_env1 = env_ptr;
        tmp_env2 = (env_t *)((ulong)env_ptr + CFG_ENV_SIZE);
    
        crc1_ok = (crc32(0, tmp_env1->data, ENV_SIZE) == tmp_env1->crc);
        crc2_ok = (crc32(0, tmp_env2->data, ENV_SIZE) == tmp_env2->crc);
    
        if (!crc1_ok && !crc2_ok)
            gd->env_valid = 0;
        else if(crc1_ok && !crc2_ok)
            gd->env_valid = 1;
        else if(!crc1_ok && crc2_ok)
            gd->env_valid = 2;
        else {
            /* both ok - check serial */
            if(tmp_env1->flags == 255 && tmp_env2->flags == 0)
                gd->env_valid = 2;
            else if(tmp_env2->flags == 255 && tmp_env1->flags == 0)
                gd->env_valid = 1;
            else if(tmp_env1->flags > tmp_env2->flags)
                gd->env_valid = 1;
            else if(tmp_env2->flags > tmp_env1->flags)
                gd->env_valid = 2;
            else /* flags are equal - almost impossible */
                gd->env_valid = 1;
        }
    
        if (gd->env_valid == 1)
            env_ptr = tmp_env1;
        else if (gd->env_valid == 2)
            env_ptr = tmp_env2;
    #else /* ENV_IS_EMBEDDED */
        gd->env_addr  = (ulong)&default_environment[0];
        gd->env_valid = 1;
    #endif /* ENV_IS_EMBEDDED */
    
        return (0);
    }

    env_Initは,名前を見ると環境変数に関する初期化であることがわかる.
    なぜenvがたくさんあるのかInit関数は、ubootが様々な起動媒体(例えばnorflash、nandflash、inand、sdカード・・)をサポートしているため、私たちは一般的にどこから起動するかで環境変数envをどこに置く.様々なメディアアクセス操作envの方法は異なる.従ってubootは、様々な異なる媒体におけるenvの動作方法をサポートする.だからenvがたくさんありますxx先頭のcファイル.実際に使用しているのは、どれが自分の開発ボードで使用している記憶媒体によって決まるのか(これらenv_xx.cは同時に1つしか機能しませんが、他は入れません.x 210_sd.hに配置されたマクロで誰が含まれているかを決めます)、x 210にとってenv_movi.cの関数.
    基本的な分析を経て、この関数はメモリの中でメンテナンスしているubootのenvに対して基本的な初期化あるいは判定をしただけです(中に使える環境変数があるかどうかを判定します).現在、SDRからDDRへの環境変数のrelocateはまだ行われていないため、現在の環境変数は使用できません.
    start_armboot関数でenv_を呼び出すrelocateは、SDカードからDDRへの環境変数の再配置を行います.再配置後に環境変数が必要な場合はDDRから取り出すことができ、再配置前に環境変数を使用する場合はSDカードからのみ読み込むことができます.
  • init_baudrate
  • static int init_baudrate (void)
    {
        char tmp[64];   /* long enough for environment variables */
        int i = getenv_r ("baudrate", tmp, sizeof (tmp));
        gd->bd->bi_baudrate = gd->baudrate = (i > 0)
                ? (int) simple_strtoul (tmp, NULL, 10)
                : CONFIG_BAUDRATE;
    
        return (0);
    }

    init_baudrateは名前を見るとシリアル通信のボーレートを初期化します.
    getenv_r関数は、環境変数の値を読み出すために使用されます.getenv関数で環境変数のbaudrateの値を読み込み(int型ではなく文字列タイプを読み込むことに注意)、simple_strtoul関数は文字列を数値フォーマットのボーレートに変換します.
    baudrate初期化のルールは、環境変数から「baudrate」という環境変数の値を読み出すことです.読み取りに成功した場合、この値を環境変数として使用し、gd->baudrateとgd->bd->bi_に記録します.baudrateで;読み込みに失敗した場合はx 210_を使用sd.hの中のCONFIG_BAUDRATEの値をボーレートとして使用します.これから,環境変数の優先度が高いことが分かる.
  • serial_init serial_Initは名前を見てシリアルポートを初期化します.serial_Init関数はstart.Sで呼び出されたlowlevel_init.Sではアセンブリを用いてシリアルポートを初期化しているが,ここではハードウェアレジスタの初期化は行わない.
  • console_init_f
  • int console_init_f (void)
    {
        gd->have_console = 1;
        return (0);
    }

    console_init_f uboot/common/console.cではconsole(コンソール)の第1段階初期化である.gd->have_だけコンソールが1に設定されているだけで、他のことはしていません.
    _fは第1段階初期化であることを示し、rは第2段階の初期化を表す.
    初期化関数は一度に一括して完了できない場合があり、中間にいくつかのコードが混在しなければならないため、完全なモジュールの初期化を2つの段階に分けます.(我々のubootではstart_armbootの下にconsole_init_rの初期化が行われています)
  • display_banner
  • static int display_banner (void)
    {
        printf ("

    %s

    "
    , version_string); debug ("U-Boot code: %08lX -> %08lX BSS: -> %08lX
    "
    , _armboot_start, _bss_start, _bss_end); #ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */ debug("\t\bMalloc and Stack is above the U-Boot Code.
    "
    ); #else debug("\t\bMalloc and Stack is below the U-Boot Code.
    "
    ); #endif open_backlight();//lqm. //open_gprs(); return (0); }

    display_bannerはubootを表示する情報をシリアル出力するために使用される.
    display_bannerでprintf関数を使用してversion_をシリアルポートに出力しましたstringという文字列.では、上記の分析はconsole_を表しています.init_fはconsoleを初期化していないのにどうしてprintfできるのですか?
    printfの実装を追跡すると、printfでputsが呼び出され、puts関数では現在のubootでconsoleが初期化されているかどうかを判断します.
    void puts (const char *s)
    {
        if (gbl_silent) return;
        if (gd->flags & GD_FLG_DEVINIT) {
            /* Send to the standard output */
            fputs (stdout, s);
        } else {
            /* Send directly to the handler */
            serial_puts (s);
        }
    }

    コンソールが初期化された場合、fputsを呼び出してシリアルポート送信を完了します(この線こそコンソールです).コンソールが初期化されていない場合はserial_が呼び出されます.puts(serial_putcを呼び出してシリアルポートレジスタを直接操作してコンテンツ送信する).
    コンソールもシリアルポートで出力され、非コンソールもシリアルポートで出力されます.コンソールとは何ですか?コンソールを使わないのと違いますか?実際にコードを分析すると、コンソールはソフトウェアで仮想化されたデバイスであり、このデバイスには専用の通信関数(送信、受信・・)があり、コンソールの通信関数は最終的にハードウェアの通信関数にマッピングされて実現されることがわかります.ubootでは実際にコンソールの通信関数はハードウェアシリアルポートの通信関数に直接マッピングされている.すなわちubootではコントローラを使わないが本質的な違いはない.しかし、他のシステムでは、コンソールの通信関数がハードウェア通信関数にマッピングされる場合、ソフトウェアを使用して、バッファメカニズムなどの中間最適化を行うことができます.(オペレーティングシステムのコンソールではバッファリングメカニズムが使用されているため、printfではコンテンツが表示されていない場合がありますが、画面に出力情報が表示されないのはバッファリングされているためです.出力された情報はconsoleのbufferにすぎず、bufferはハードウェア出力装置にリフレッシュされていません.特に出力装置がLCD画面の場合).
    U_BOOT_VERSIONはubootソースコードで定義が見つかりません.この変数は実際にmakefileで定義され、コンパイル時に生成されたinclude/version_autogenerated.hでは1つのマクロ定義で実現される.
  • print_cpuinfo
  • int print_cpuinfo(void)
    {
        uint set_speed;
        uint tmp;
        uchar result_set;
    
    #if defined(CONFIG_CLK_533_133_100_100)
        set_speed = 53300;
    ……
    #elif defined(CONFIG_CLK_1000_200_166_133)
        set_speed = 100000;
    
        printf("Any CONFIG_CLK_XXX is not enabled
    "
    ); #endif tmp = (set_speed / (get_ARMCLK()/1000000)); if((tmp < 105) && (tmp > 95)){ result_set = 1; } else { result_set = 0; } printf("
    CPU: S5PV210@%ldMHz(%s)
    "
    , get_ARMCLK()/1000000, ((result_set == 1) ? "OK" : "FAIL")); printf(" APLL = %ldMHz, HclkMsys = %ldMHz, PclkMsys = %ldMHz
    "
    , get_FCLK()/1000000, get_HCLK()/1000000, get_PCLK()/1000000); printf(" MPLL = %ldMHz, EPLL = %ldMHz
    "
    , get_MPLL_CLK()/1000000, get_PLLCLK(EPLL)/1000000); printf(" HclkDsys = %ldMHz, PclkDsys = %ldMHz
    "
    , get_HCLKD()/1000000, get_PCLKD()/1000000); printf(" HclkPsys = %ldMHz, PclkPsys = %ldMHz
    "
    , get_HCLKP()/1000000, get_PCLKP()/1000000); printf(" SCLKA2M = %ldMHz
    "
    , get_SCLKA2M()/1000000); puts("Serial = CLKUART "); return 0; }

    出力情報は次のとおりです.
    CPU:  S5PV210@1000MHz(OK)
            APLL = 1000MHz, HclkMsys = 200MHz, PclkMsys = 100MHz
            MPLL = 667MHz, EPLL = 96MHz
                           HclkDsys = 166MHz, PclkDsys = 83MHz
                           HclkPsys = 133MHz, PclkPsys = 66MHz
                           SCLKA2M  = 200MHz
    Serial = CLKUART 
  • checkboard
  • int checkboard(void)
    {
    #ifdef CONFIG_MCP_SINGLE
    #if defined(CONFIG_VOGUES)
        printf("
    Board: VOGUESV210
    "
    ); #else printf("
    Board: X210
    "
    ); #endif //CONFIG_VOGUES #else printf("
    Board: X210
    "
    ); #endif return (0); }

    checkboard名前を見るのは開発板をチェック、確認するという意味です.この関数の役割は,現在の開発ボードがどの開発ボードであるかをチェックし,開発ボードの名前を印刷することである.
  • init_func_i2c
  • #if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
    static int init_func_i2c (void)
    {
        puts ("I2C:   ");
        i2c_init (CFG_I2C_SPEED, CFG_I2C_SLAVE);
        puts ("ready
    "
    ); return (0); } #endif
    #undef CONFIG_S3C64XX_I2C       /* this board has H/W I2C */
    #ifdef CONFIG_S3C64XX_I2C
    #define CONFIG_HARD_I2C     1

    だってCONFIG_S3C64XX_I 2 C定義がキャンセルされたのでCONFIG_HARD_I 2 Cは定義されず、この関数は実行されず、X 210のubootではI 2 Cは使用されていない.将来、当社の開発ボードがI 2 Cを拡張して外付けハードウェアに接続する場合は、x 210_sd.hで対応するマクロを構成するとオンになります.
  • dram_init
  • int dram_init(void)
    {
        DECLARE_GLOBAL_DATA_PTR;
    
        gd->bd->bi_dram[0].start = PHYS_SDRAM_1;
        gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;
    
    #if defined(PHYS_SDRAM_2)
        gd->bd->bi_dram[1].start = PHYS_SDRAM_2;
        gd->bd->bi_dram[1].size = PHYS_SDRAM_2_SIZE;
    #endif
        return 0;
    }

    dram_initは名前を見てDDRの初期化についてです.
    アセンブリフェーズでDDRが初期化されています.そうしないと、relocateを第2の部分に実行できません.ここでDDRを初期化するにはどうすればいいですか.dram_initは実はgd->bd->bi_を初期化しますdramという構造体配列はDDRを初期化していない.
  • display_dram_config
  • static int display_dram_config (void)
    {
        int i;
    
    #ifdef DEBUG
        puts ("RAM Configuration:
    "
    ); for(i=0; i"Bank #%d: %08lx ", i, gd->bd->bi_dram[i].start); print_size (gd->bd->bi_dram[i].size, "
    "
    ); } #else ulong size = 0; for (i=0; ibd->bi_dram[i].size; } puts("DRAM: "); print_size(size, "
    "
    ); #endif return (0); }

    名前を見るとdramの構成情報を印刷表示するという意味です.
    起動情報の:(DRAM:512 MB)はこの関数に印刷されます.
    print_size()は次のとおりです.
    /*
     * print sizes as "xxx kB", "xxx.y kB", "xxx MB", "xxx.y MB",
     * xxx GB, or xxx.y GB as needed; allow for optional trailing string
     * (like "
    "
    ) */ void print_size (phys_size_t size, const char *s) { ulong m = 0, n; phys_size_t d = 1 << 30; /* 1 GB */ char c = 'G'; if (size < d) { /* try MB */ c = 'M'; d = 1 << 20; if (size < d) { /* print in kB */ c = 'k'; d = 1 << 10; } } n = size / d; /* If there's a remainder, deal with it */ if(size % d) { m = (10 * (size - (n * d)) + (d / 2) ) / d; if (m >= 10) { m -= 10; n += 1; } } printf ("%2ld", n); if (m) { printf (".%ld", m); } printf (" %cB%s", c, s); }

    思考:ubootの実行中にubootのDDR構成情報を知るにはどうすればいいですか?ubootにはbdinfoというコマンドがあり、gd->bdに記録されているすべてのハードウェアに関するグローバル変数の値を印刷できるので、DDRの構成情報がわかります.
    DRAM bank   = 0x00000000
    -> start    = 0x30000000
    -> size     = 0x10000000
    DRAM bank   = 0x00000001
    -> start    = 0x40000000
    -> size     = 0x10000000

    これでinit_sequenceはもう終わりました.
    その後の分析ではubootのstart_Armboot分析3