QNX startup程序分析

QNX相關(guān)歷史文章:

這篇文章主要描述QNX的startup程序功能及組成,分析了system page結(jié)構(gòu),以及該結(jié)構(gòu)中跟硬件相關(guān)性較大的hwinfo段與callout段。

1. startup介紹

在一個可啟動的QNX鏡像中,startup是第一個啟動程序,startup程序的作用包括:

  • 初始化硬件
    完成基礎的硬件初始化,具體需要做多少初始化工作,取決于IPL loader中做了多少。有時只需要做很少的初始化:比如MMU、定時器、中斷控制器。
  • 初始化系統(tǒng)頁system page
    關(guān)于系統(tǒng)的信息存放在一個叫做系統(tǒng)頁(system page)的數(shù)據(jù)結(jié)構(gòu)中,包括處理器類型、總線類型,可用的系統(tǒng)RAM位置和大小、硬件配置、緩存、內(nèi)存映射和地址空間、定時器參數(shù)等,存放該結(jié)構(gòu)的頁面區(qū)域是內(nèi)存的專用區(qū)域。內(nèi)核和應用軟件都可以以只讀的形式來訪問這些信息。
  • 初始化callout
    系統(tǒng)頁system page結(jié)構(gòu)中還包含了內(nèi)核callouts字段,內(nèi)核callout用于提供一些板級相關(guān)的代碼,比如內(nèi)核調(diào)試、系統(tǒng)定時器、緩存控制、中斷處理、系統(tǒng)重啟等,最終被QNX內(nèi)核調(diào)用。這些代碼都由startup程序來提供,由內(nèi)核來負責回調(diào)進而操作硬件。這樣的實現(xiàn)可以讓QNX系統(tǒng)與硬件進行解耦合,具備更好的移植性。
  • 加載并將控制權(quán)轉(zhuǎn)交給鏡像中的下一個程序
    在這個階段所做的工作包括初始化MMU,創(chuàng)建處理分頁、進程和異常的結(jié)構(gòu),使能中斷等,之后就可以跳轉(zhuǎn)到內(nèi)核運行了。

2. startup程序結(jié)構(gòu)

代碼位于{BSP_ROOT_DIR}/src/hardware/startup目錄中,以R-Car為例:


從圖中可以看出,boards目錄下放置的rcar_gens,表明是瑞薩R-Car的第三代SoC,在該目錄下的rcar_h3和rcar_m3分別對應兩個不同的系列,黃色箭頭所指的_start.S為程序的總體入口。

從_start.S進去,會涉及到ARM V8處理器的一系列初始化和設置,這部分是通用的,最終會調(diào)到main()函數(shù),而這個main()函數(shù),正是R-Car的startup的一部分。main()函數(shù)位于上圖中的boards/rcar_gen3/目錄下。

每個startup程序,都會包含一個main()函數(shù),main()函數(shù)的偽代碼如下:

Global variables
 
main()
{
    Call add_callout_array (note 1)
 
    Argument parsing (note 2)
 
    Call init_raminfo (note 3)
 
    Remove ram used by modules in the image
 
    if (virtual)
        Call init_mmu (note 4)
 
    Call init_intrinfo (note 5)
 
    Call init_qtime (note 6)
 
    Call init_cacheattr (note 7)
 
    Call init_cpuinfo (note 8)
 
    Set hardware machine name
 
    Call init_system_private (note 9)
 
    Call print_syspage (note 10)
}

上述代碼中對應的注釋note如下:

  • note 1
    將callout添加到system page結(jié)構(gòu)中,上文中提到過callout本質(zhì)上是回調(diào)函數(shù),這個過程相當于把信息注冊進系統(tǒng)頁這個結(jié)構(gòu)中;
  • note 2
    參數(shù)解析,對傳入的ASCII字符進行處理,通過'switch case'來選擇對應的項,操作包括:Reboot、輸出通道的選擇(kprintf或stdout)、CPU頻率/時鐘頻率/定時器頻率、Reserve內(nèi)存、確定在SMP系統(tǒng)中CPU個數(shù)等;
  • note 3
    確定可用系統(tǒng)RAM的位置和大小,并在system page結(jié)構(gòu)中初始化asinfo結(jié)構(gòu),如果已經(jīng)知道了RAM的確切數(shù)量和位置,可以使用一個自定義的函數(shù)(在這個函數(shù)中可以使用add_ram來進行硬編碼)來代替這個函數(shù);
  • note 4
    設置MMU,通過設置頁表,來完成物理地址到虛擬地址的轉(zhuǎn)換;
  • note 5
    設置中斷系統(tǒng)的相關(guān)信息,比如中斷向量表相關(guān);
  • note 6
    初始化system page中的qtime結(jié)構(gòu),qtime結(jié)構(gòu)包含關(guān)于系統(tǒng)上的基準時間信息,以及其他與時間相關(guān)的信息;
  • note 7
    初始化緩存相關(guān)內(nèi)容,包含片內(nèi)和片外的緩存氣筒,對所有平臺,這部分都只是一個占位符,沒有實現(xiàn);
  • note 8
    初始化CPU相關(guān)信息,比如CPU類型、速度、功能、性能和緩存大小等;
  • note 9
    這個模塊在所有平臺上,都不需要修改,完成的工作包括:找到所有需要啟動的引導鏡像,并用這些信息填充結(jié)構(gòu);告訴內(nèi)核鏡像文件系統(tǒng)的位置;為system page結(jié)構(gòu)分配實際的存儲空間等;
  • note 10
    打印system page結(jié)構(gòu)中的所有成員內(nèi)容,其中全局變量debug_level用于確定輸出的內(nèi)容,debug_level至少為2才能打印任何內(nèi)容,debug_level為3將打印子結(jié)構(gòu)中的信息,system page對應的數(shù)據(jù)結(jié)構(gòu)如下,這個結(jié)構(gòu)中的字段,有些可通過startup庫來初始化,有些則需要自己去實現(xiàn)代碼來填充;

3. system page

從startup的代碼中可以看出,在main函數(shù)中的所有處理,基本都是圍繞這個system page結(jié)構(gòu)來展開,完成相應段的初始化。最終在write_syspage_memory()之后,通知system page已經(jīng)ready了,再調(diào)用startnext()進入下一個階段的運行,也就是啟動QNX內(nèi)核。QNX內(nèi)核讀取這個內(nèi)存區(qū)域來獲取系統(tǒng)信息。

system page的結(jié)構(gòu),由不同的section組成,具體如下:

/*
 * contains at least the following:
 */
struct syspage_entry {
    uint16_t            size;
    uint16_t            total_size;
    uint16_t            type;
    uint16_t            num_cpu;
    syspage_entry_info  system_private;
    syspage_entry_info  asinfo;  /* address space information 結(jié)構(gòu)數(shù)組,用于描述不同部分的內(nèi)存映射,比如RAM、SRAM、Flash、I/O范圍等,當procnto為進程地址空間管理虛擬地址時,會使用asinfo中的信息來獲取可以從RAM中的何處分配內(nèi)存。內(nèi)存映射采用樹狀格式,地址范圍可以有父節(jié)點,比如/memory/io/memclass/...,其中memclass可以是ram、rom、flash等 */
    syspage_entry_info  hwinfo;
    syspage_entry_info  cpuinfo;
    syspage_entry_info  cacheattr;  /* 關(guān)于片內(nèi)和片外緩存系統(tǒng)配置的信息,該區(qū)域還包含了用于內(nèi)核控制緩存操作的Callout,cacheattr結(jié)構(gòu)由init_cpuinfo()和init_cacheattr()來填充,cacheattr條目組織在一個鏈表中,結(jié)構(gòu)體中的next成員表示下一級緩存條目的索引 */
    syspage_entry_info  qtime;  /* 關(guān)于系統(tǒng)上顯示時間的基準信息,以及其他與時間相關(guān)的信息 */
    syspage_entry_info  callout;
    syspage_entry_info  callin;
    syspage_entry_info  typed_strings;
    syspage_entry_info  strings;
    syspage_entry_info  intrinfo;   /* 中斷系統(tǒng)信息,還包含了用于操作中斷控制器硬件的內(nèi)核Callout */
    syspage_entry_info  smp;
    syspage_entry_info  pminfo;
     
    union {
        struct x86_syspage_entry    x86;
        struct ppc_syspage_entry    ppc;
        struct mips_syspage_entry   mips;
        struct arm_syspage_entry    arm;
        struct sh_syspage_entry     sh;
    } un;
};

4. hwinfo

注意到上文中提到system page結(jié)構(gòu)中,有一個syspage_entry_info hwinfo成員,這個結(jié)構(gòu)包含了硬件平臺的信息,包括總線類型、設備、中斷等。

hwinfo段不是由一個單獨結(jié)構(gòu)或相同類型的數(shù)組組成,而是由一些標簽化的結(jié)構(gòu)組成,這些結(jié)構(gòu)作為一個整體來描述電路板上的硬件。在hwinfo段中,有兩個概念,一個是Tag,一個是Item。

Tag:
Tag結(jié)構(gòu)用于描述硬件組件的特定方面的信息,Tag都是以下邊這個結(jié)構(gòu)開頭

struct hwi_prefix {
    uint16_t        size;
    uint16_t        name;
};

目前提供了幾個預定義的Tag,如下所示:

/* 它給出了寄存器的位置(不管是在I/O空間還是在內(nèi)存空間),如果有多個寄存器組,則Item中可能會有多個這樣的Tag */
#define HWI_TAG_NAME_location   "location"
#define HWI_TAG_ALIGN_location  (sizeof(uint64))
struct hwi_location {
    struct hwi_prefix   prefix;
    uint32_t            len;      /* 寄存器范圍的長度 */
    uint64_t            base;     /* 寄存器的物理基地址 */
    uint16_t            regshift; /* Indicates the shift for each register access. */
    uint16_t            addrspace; /* 從asinfo部分開始的偏移量,以字節(jié)為單位,這個成員用于標識寄存器是內(nèi)存映射還是在單獨的IO地址空間 */
};
 
/* 給出了設備的中斷號 */
#define HWI_TAG_NAME_irq        "irq"
#define HWI_TAG_ALIGN_irq       (sizeof(uint32))
struct hwi_irq {
    struct hwi_prefix   prefix;
    uint32_t            vector;   /* 邏輯向量中斷號 */
};
 
/* 這個Tag,用于填充,保證字節(jié)能對齊 */
#define HWI_TAG_NAME_pad        "pad"
#define HWI_TAG_ALIGN_pad       (sizeof(uint32))
struct hwi_pad {
    struct hwi_prefix   prefix;
};

Item:
Item是Tag的集合,用于描述一個硬件組件的完整信息。每個Item中的第一個Tag都是以struct hwi_item開始,結(jié)構(gòu)如下所示:

struct hwi_item {
    struct hwi_prefix   prefix;
    uint16_t            itemsize;    /* 到下一項Item開始的距離,以4字節(jié)為單位 */
    uint16_t            itemname;    /* 這個字段是整型,存放的是system page中strings段中對應的偏移量,用于描述Item的名稱 */
    uint16_t            owner;       /* 這個字段用于將Item組織成樹狀結(jié)構(gòu),類似于文件系統(tǒng)的層次結(jié)構(gòu),存放的值是hwinfo段中對應的偏移量 */
    uint16_t            kids;        /* 當前項的子項數(shù)目 */
};

通過將Item組織,可以在hwinfo中構(gòu)造一個設備樹,比如通常設備層次結(jié)構(gòu)為:/hw/bus/devclass/device

  • hw,硬件樹的根;
  • bus,硬件所在的總線,比如pci、eisa等,對應Bus Item;
  • devclass,設備的分類,比如serial、rtc等,對應Group Item;
  • device,實際的設備,比如8250、mc146818等,對應Device Item;
    目前提供了部分的預定義的Item,如下:
/* Group Item, Item組,可以將多個Item組織在一起,它與文件系統(tǒng)中的目錄具有相同的用途,比如/hw樹中的devclass層就使用Group Item  */
#define HWI_TAG_NAME_group  "Group"
#define HWI_TAG_ALIGN_group (sizeof(uint32_t))
struct hwi_group {
    struct hwi_item     item;
};
 
/* Bus Item, 總線Item用于告訴系統(tǒng)硬件總線的信息 */
#define HWI_TAG_NAME_bus    "Bus"
#define HWI_TAG_ALIGN_bus   (sizeof(uint32))
struct hwi_bus {
    struct hwi_item     item;
};
/* Bus Item的名字可以是(不限于) */
#define HWI_ITEM_BUS_PCI        "pci"
#define HWI_ITEM_BUS_ISA        "isa"
#define HWI_ITEM_BUS_EISA       "eisa"
#define HWI_ITEM_BUS_MCA        "mca"
#define HWI_ITEM_BUS_PCMCIA     "pcmcia"
#define HWI_ITEM_BUS_UNKNOWN    "unknown"
 
/* Device Item, 設備Item用于告訴系統(tǒng)單個設備的信息 */
#define HWI_TAG_NAME_device     "Device"
#define HWI_TAG_ALIGN_device    (sizeof(uint32))
struct hwi_device {
    struct hwi_item     item;
    uint32_t            pnpid;    /* 微軟分配的即插即用設備標識符,只適用于播放媒體的設備,已經(jīng)棄用 */
};

上述的Item和Tag只是預定義的,用戶可以創(chuàng)建自己需要的Item。

構(gòu)建hwinfo段的接口:
針對hwinfo的Tag和Item,提供了一下相關(guān)的操作接口,如下:

/* 分配一個Tag */
void *hwi_alloc_tag(const char *name, unsigned size, unsigned align);
 
/* 分配一個Item */
void *hwi_alloc_item(const char *name, unsigned size,
                     unsigned align, const char *itemname,
                     unsigned owner);
 
/* 查到Item中的信息 */
unsigned hwi_find_item(unsigned start, ...);
 
/* 根據(jù)Tag的指針,得到在hwinfo段中的offset */
unsigned hwi_tag2off(void *);
 
/* 根據(jù)offset, 來得到Tag */
unsigned hwi_tag2off(void *);
 
/* 根據(jù)tagname,得到Tag */
unsigned hwi_find_tag(unsigned start, int curr_item, const char *tagname);
 
/* 獲取給定偏移量對應的Item的下一個Item在hwinfo段中的偏移量 */
unsigned hwi_next_item( unsigned off);
 
/* 獲取給定偏移量對應的Tag的下一個Tag在hwinfo段中的偏移量 */
unsigned hwi_next_tag( unsigned off, int curr_item );

要構(gòu)建一個Item,有以下步驟:

  • 調(diào)用hwi_alloc_item()接口來構(gòu)建一個頂層的Item,它的owner字段被設置成HWI_NULL_OFF;
  • 調(diào)用hwi_alloc_tag()接口來添加任何想要的Tag結(jié)構(gòu);
  • 調(diào)用hwi_alloc_item()來創(chuàng)建一個新的Item,這個Item可以是剛創(chuàng)建Item的第一個子項,也可以是另一個頂層的Item。

5. 內(nèi)核Callout

什么是Callout?先來看幾個數(shù)據(jù)結(jié)構(gòu):

struct callout_rtn {
    unsigned    *rw_size;
    void        (*patcher)(PADDR_T paddr, uintptr_t vaddr, unsigned rtn_offset, unsigned rw_offset, void *data, const struct callout_rtn *src);
    unsigned    rtn_size;
    uint8_t        rtn_code[1];
};
 
struct callout_slot {
    unsigned                    offset;
    const struct callout_rtn    *callout;
};

Callout是獨立的代碼片段,可以認為是一些由startup來提供的回調(diào)函數(shù),在QNX內(nèi)核中綁定調(diào)用,用于執(zhí)行特定于硬件的操作。不需要靜態(tài)地將這些代碼鏈接到QNX內(nèi)核中,這樣做也就能將QNX內(nèi)核與硬件相關(guān)的操作分離。

Callout例程通常以匯編的形式給出,Callout例程作為startup程序的一部分,在內(nèi)核啟動時它將被覆蓋,為了避免這種情況,startup程序會將這些Callouts例程從加載的位置拷貝到一個安全的位置,所以Callouts的代碼需要是位置無關(guān)(position-indepentent)的。

內(nèi)核使用SoC的Application Binary Interface(ABI)來向Callout傳遞數(shù)據(jù)或者從Callout獲取數(shù)據(jù)。當嘗試為開發(fā)板編寫Callout時,首先需要去熟悉板子的ABI接口文檔。

5.1 內(nèi)核Callout類別

startup庫為內(nèi)核提供了內(nèi)核Callout,用于處理不同類別的任務,可以使用這些Callout當做模板來編寫自己的Callout。

Kernel Debug

內(nèi)核在需要打印一些內(nèi)部調(diào)試信息或遇到錯誤時,需要用到Debug Callout,以輸出調(diào)試或檢測信息。

包括:

  • display_char(),從內(nèi)核接收到字符,并將它輸出給UART或其他設備;
  • poll_key(),傳遞一個字符給內(nèi)核,如果字符不可用,則返回-1;
  • break_detect(),檢測是否有中斷;

當內(nèi)核希望與串口、控制臺或其他設備交互時,比如打印一些內(nèi)部調(diào)試信息,會調(diào)用這些接口,其中poll_key()和break_detect()是可選的。

Clock/timer

內(nèi)核使用這部分的Callout與硬件定時器交互(定時器/計數(shù)器芯片),在很多情況下,一個開發(fā)板上可能有多個計時器,可以在啟動代碼中選定一個Callout來使用。內(nèi)核使用硬件定時器來產(chǎn)生周期性中斷,用于軟件定時器、調(diào)度、更新系統(tǒng)時間或其他軟件時間等。

Callout包括:

  • timer_load(),負責將內(nèi)核傳遞的值填充到硬件中,Callout會將寫入硬件計時器的值寫入到qtime_entry中的timer_load字段,這樣內(nèi)核就可以看到實際使用了哪個值;
  • timer_reload(),內(nèi)核在中斷開始時調(diào)用timer_reload(),如果timer_reload()返回1,則將中斷視為時鐘tick,當有多個中斷源可以產(chǎn)生相同的中斷時,timer_reload()返回值可以用于將時鐘tick中斷源與其他中斷源區(qū)分開來;
  • timer_value(),返回定時器芯片內(nèi)部的計數(shù)值,內(nèi)核可以調(diào)用timer_value()獲取下一個中斷到來的時間;

內(nèi)核使用這些Callout來與硬件定時器芯片交互。

Interrupt controller

中斷控制器接口包括內(nèi)核Callout和Stubs兩部分

Callout包括:

  • mask(),mask某個中斷向量;
  • unmask(),unmask某個中斷向量;
  • config(),發(fā)現(xiàn)指定中斷級別的配置;

Stubs包括:

  • interrupt_id_*(),負責將中斷級別配置進CPU寄存器中,并Mask處理。Mask在處理邊緣觸發(fā)中斷的情況下是必要的,可以防止在完成響應之前再次中斷。
    需要做的工作包括:1)從某種中斷狀態(tài)寄存器中讀取信息;2)執(zhí)行一些位操作來確定中斷級別;3)將中斷級別值寫入通用的CPU寄存器中,以便內(nèi)核使用;4)在出現(xiàn)故障或錯誤斷言的情況下,在GPR中寫入-1,表明沒有中斷內(nèi)核;5)操作enable和mask寄存器來屏蔽中斷;

  • interrupt_eoi_*(),End of Interrupt(EOI)
    需要做的工作包括:1)告訴中斷控制器,中斷已經(jīng)被處理;2)打開中斷級別的掩碼,解除屏蔽;3)在某些情況下,內(nèi)核Callout會操作寄存器中的其他位,以提示中斷控制器重新計算它接收到的輸入。

stubs這部分代碼直接集成到了內(nèi)核代碼中,它們的調(diào)用方式與其他的Callout調(diào)用方式不一樣,不能從這兩個Callout中間返回,必須執(zhí)行到最后。

Cache controller

根據(jù)系統(tǒng)中的緩存控制器電路,可能也需要為內(nèi)核提供與緩存控制器相關(guān)的Callout,用于在內(nèi)核中執(zhí)行某些特定功能時使部分緩存失效。

內(nèi)核Callout的原型如下:

  • control(),需要傳給這個Callout一些Flags標記、地址(虛擬地址或物理地址,取決于system page結(jié)構(gòu)中cacheattr數(shù)組中的Flag值),需要影響的cache line數(shù)量;
    這個接口返回所影響的cache lines,返回0表明所有的cache都被invalidate了。在有的處理器架構(gòu)中,緩存控制器與CPU緊密耦合,這也意味著內(nèi)核不必與緩存控制器通信。

一般不太可能需要自己實現(xiàn)這個Callout,大多數(shù)情況下startup標準庫中都提供了接口,能正常使用。

System reset

每當內(nèi)核需要重新啟動機器是,會調(diào)用Callout中的reboot()接口,這個可以讓開發(fā)人員做一些定制化操作,比如在某些事情發(fā)生時進行重啟操作。sysmgr_reboot()最終會調(diào)用到reboot()。

Power management

每當需要激活電源管理時,會去調(diào)用power(),而這個Callout是特定于CPU的。通常CPU的電源模式有以下幾種:

  • Active or Running,系統(tǒng)正在運行應用程序,一些外圍設備可能處于空閑或關(guān)閉狀態(tài);
  • Idle,系統(tǒng)沒有運行應用程序,CPU停止,代碼全部或部分駐留在內(nèi)存中;
  • Standby,系統(tǒng)沒有運行應用程序,CPU停止,代碼沒有駐留在內(nèi)存中;
  • Shutdown,最小或零功率狀態(tài),CPU、內(nèi)存和設備都關(guān)閉了電源;

5.2 Callout編寫

如果startup庫中的內(nèi)核Callout不支持目標硬件平臺,或者任何可用的特定于硬件的內(nèi)核Callout也不支持目標硬件平臺,那就需要自己去實現(xiàn)Callout了。

Callout都以匯編的形式給出來,文件的命名約定為callout_category_device.S,其中category有:cache、debug、interrupt、timer、reboot等幾種,device指的是特定的設備,比如在R-Car中使用了串口,命名為callout_debug_scif.S。

在編寫Callout之前,需要先查看硬件文檔,以便了解內(nèi)核Callout需要做什么,才能在目標硬件上完成它的任務。一般可以拷貝功能相近的Callout文件,然后在它的基礎上進行修改。

編寫內(nèi)核Callout有幾點注意的:

  • 開始和結(jié)束宏
/* 包含頭文件 */
#include "callout.ah"
/* 或者如下 */
//.include "callout.ah"
 
CALLOUT_START(timer_load_8254, 0, 0)
CALLOUT_END(timer_load_8254)

CALLOUT_START宏,表示Callout的起始,有三個參數(shù),分別代表例程名字、四字節(jié)變量的地址(該地址包含了Callout需要的讀寫存儲量)、patcher例程的地址(0表示不需要patching)

CALLOUT_END宏,表示Callout的結(jié)束,參數(shù)與CALLOUT_START宏中的第一個參數(shù)一樣。

當這個Callout被內(nèi)核選中的話,CALLOUT_START和CALLOUT_END之間的代碼會被拷貝到一個安全的內(nèi)存區(qū)域,方便內(nèi)核使用。

  • patcher例程
    如果為其編寫內(nèi)核Callout的設備可以出現(xiàn)在不同的開發(fā)板中的不同位置,則需要一個patch例程來將寄存器地址添加到內(nèi)核Callout代碼中。

內(nèi)核Callout是startup庫中的一部分,因此設計的很靈活,不會硬編碼寄存器地址,而是假設寄存器地址是通過patch進來的,寄存器地址都是來自板級代碼中,在板級目錄的代碼中可以找到。如果內(nèi)核Callout只訪問CPU寄存器,則不需要這個patch操作。

patcher例程的函數(shù)原型如下:

/*
 * paddr, system page開始的物理地址
 * vaddr,允許讀寫訪問system page的虛擬地址(僅供內(nèi)核使用)
 * rtn_offset,從system page開始到內(nèi)核Callout代碼開始的偏移量
 * rw_offset,從system page開始到讀寫位置的偏移量,可以由所有在CALLOUT_START宏的第二個參數(shù)中具有相同值的內(nèi)核Callout共享
 * data,指向callout_register_data()注冊的任意數(shù)據(jù)的指針
 * src,指向callout_rtn結(jié)構(gòu)的指針,被復制到適當?shù)奈恢? */
void patcher( paddr_t paddr,
    paddr_t vaddr,
    unsigned rtn_offset,
    unsigned rw_offset,
    void *data,
    struct callout_rtn *src );

這個例程會在內(nèi)核Callout被拷貝到最終位置時立馬被調(diào)用。patcher例程不必使用匯編實現(xiàn),但通常都是通過匯編實現(xiàn),因此可以將其保存在它patching的源文件中,與CALLOUT_START/CALLOUT_END組織的代碼放在一塊。

  • 分配讀寫空間
    在某些情況下,內(nèi)核Callout需要訪問一些靜態(tài)讀寫存儲,特別是為了能夠與其他內(nèi)核Callout共享信息時。由于內(nèi)核Callout代碼是位置無關(guān)的,因此它不能有靜態(tài)讀寫存儲,可以在system page的末尾將少量的內(nèi)存分配給內(nèi)核Callout作為讀寫存儲。使用CALLOUT_START宏的第二個參數(shù)來指定一個四字節(jié)變量的地址,該變量包含內(nèi)核Callout所需的讀寫存儲量。

Callout 示例
Callout的編寫如下,以R-Car的callout_debug_scif.S為例:

/*
 * Patch interrupt callouts to access rw data.
 * The first call will also map the uart.
 *
 * Patcher routine takes the following arguments:
 *    x0 - syspage paddr
 *    x1 - vaddr of callout
 *    x2 - offset from start of syspage to start of callout routine
 *    x3 - offset from start of syspage to rw storage
 *    x4 - patch data
 *    x5 - callout_rtn
 */
 
patch_debug:
    sub        sp, sp, #16
    stp        x19, x30, [sp]
 
    add        x19, x0, x2                // x19 = address of callout routine
 
    /*
     * Map UART using patch_data parameter
     */
    mov        x0, #0x1000
    ldr        x1, [x4]
    bl        callout_io_map
 
    /*
     * Patch callout with mapped virtual address in x0
     */
    CALLOUT_PATCH    x19, w6, w7
 
    ldp        x19, x30, [sp]
    add        sp, sp, #16
    ret
 
/*
 * -----------------------------------------------------------------------
 * void    display_char_scif(struct sypage_entry *, char)
 *
 * x0: syspage pointer
 * x1: character
 * -----------------------------------------------------------------------
 */
CALLOUT_START(display_char_scif, 0, patch_debug)
    mov        x7, #0xabcd                // UART base address (patched)
    movk    x7, #0xabcd, lsl #16
    movk    x7, #0xabcd, lsl #32
    movk    x7, #0xabcd, lsl #48
 
0:    ldr        w2, [x7, #SCIF_SCFSR_OFF]
    tst        w2, #SCIF_SCSSR_TDFE
    b.eq    0b
 
    and        w0, w1, #0xff
    strb    w0, [x7, #SCIF_SCFTDR_OFF]
 
1:    ldr        w2, [x7, #SCIF_SCFSR_OFF]
    tst        w2, #SCIF_SCSSR_TEND
    b.eq    1b
 
    mov        w2, #0
    strh    w2, [x7, #SCIF_SCFSR_OFF]
 
    ret
CALLOUT_END(display_char_scif)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、溫故而知新 1. 內(nèi)存不夠怎么辦 內(nèi)存簡單分配策略的問題地址空間不隔離內(nèi)存使用效率低程序運行的地址不確定 關(guān)于...
    SeanCST閱讀 8,107評論 0 27
  • 進程 創(chuàng)建 創(chuàng)建進程用fork()函數(shù)。fork()為子進程創(chuàng)建新的地址空間并且拷貝頁表。子進程的虛擬地址空間...
    梅花怒閱讀 2,071評論 0 7
  • QNX相關(guān)歷史文章:QNX簡介QNX Neutrino微內(nèi)核QNX IPC機制QNX進程管理器QNX資源管理器QN...
    Loyen閱讀 8,480評論 2 5
  • 思維導圖https://mubu.com/doc/1y91Dl_sPF 嵌入式系統(tǒng)概述 嵌入式系統(tǒng)(Embedde...
    DecadeHeart閱讀 1,618評論 0 6
  • 一次去朋友單位辦事,發(fā)現(xiàn)一個有趣的事情:朋友單位中層領(lǐng)導多數(shù)是80后,唯有他所在的辦公室,領(lǐng)導是一個70后。 朋友...
    木南開閱讀 626評論 0 0

友情鏈接更多精彩內(nèi)容