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)