SkyEye硬件模擬平臺,第三部分: 硬件仿真實現之三MMU/CACHE仿真 |
| |||||||||||
未顯示需要 JavaScript 的文檔選項 |
級別: 初級
陳渝 ([email protected])清華大學
2004 年 10 月 01 日
本系列文章的第三部分主要介紹了SkyEye硬件模擬平臺的實現細節。主要內容包括SkyEye的總體設計、SkyEye的可擴展框架、SkyEye的關鍵數據結構、SkyEye對各種CPU的模擬實現、SkyEye對各種外設的模擬實現、如何安裝使用SkyEye以及如何擴展SkyEye的仿真模塊等。對SkyEye的深入瞭解,有助於對嵌入式硬件系統有更深入的認識,特別是對操作系統、驅動程序如何與嵌入式硬件系統進行交互有更深刻的瞭解。
圖 0-1 ARM系統中MMU和Memory的系統結構
ARM系統中MMU和Memory的系統結構如圖 0 1所示。不過具體的CPU在實現MMU時差別較大,可能對其做簡化和擴展, SkyEye的MMU模擬實現基於此,在提供一個標準的接口基礎上,分成與具體CPU類型無關的MMU模擬子模塊和與具體CPU類型相關的MMU模擬子模塊兩個主要部分。
ARM CPU進行數據訪問的基本流程如圖 0 2所示。
圖 0-2 ARM CPU進行數據訪問的基本流程
數據結構
typedef struct mmu_state_t { ARMword control; //CP15 control register ARMword translation_table_base; //CP15 translation table base register ARMword domain_access_control; //CP15 domain access control register ARMword fault_status; //CP15 fault status register ARMword fault_address; //CP15 fault address register ARMword last_domain; //last access domain ARMword process_id; //CP15 process id register mmu_ops_t ops; union{ sa_mmu_t sa_mmu; arm7100_mmu_t arm7100_mmu; }u; } mmu_state_t; typedef struct mmu_ops_s{ int (*init)(ARMul_State *state);/*initilization*/ void (*exit)(ARMul_State *state);/*free on exit*/ fault_t (*read_byte)(ARMul_State *state, ARMword va, ARMword *data); fault_t (*write_byte)(ARMul_State *state, ARMword va, ARMword data); fault_t (*read_halfword)(ARMul_State *state, ARMword va, ARMword *data); fault_t (*write_halfword)(ARMul_State *state, ARMword va,ARMword data); fault_t (*read_word)(ARMul_State *state, ARMword va,ARMword *data); fault_t (*write_word)(ARMul_State *state, ARMword va,ARMword data); fault_t (*load_instr)(ARMul_State *state, ARMword va, ARMword *instr); void (*mcr)(ARMul_State *state, ARMword instr, ARMword val); ARMword (*mrc)(ARMul_State *state, ARMword instr); }mmu_ops_t; |
數據結構ARMul_State中類型爲mmu_state_t的mmu代表模擬中MMU的狀態。mmu_state_t中的ops提供具體mmu的接口函數,同時包括一個聯合結構u用於具體mmu實現的數據結構。
與具體CPU類型無關的MMU模擬子模塊是實現MMU模擬的基礎,它實現了TLB,TTW, 訪問控制,CACHE, Write Buffer,Read Buffer等MMU要素的模擬,並且提供了較統一的接口,可以較靈活地組合實現具體MMU模擬。
TLB的實現(mmu/tlb.[ch])
TLB是一個表,它的表項包含地址映射信息和訪問控制信息,地址轉換信息只有在該表中查詢不到時,才通過TTW在內存中查找。
數據結構
TLB 映射類型
typedef enum tlb_mapping_t { TLB_INVALID = 0, TLB_SMALLPAGE = 1, TLB_LARGEPAGE = 2, TLB_SECTION = 3 } tlb_mapping_t; |
TLB 表項
typedef struct tlb_entry_t { ARMword virt_addr; //virtual address ARMword phys_addr; //physical address ARMword perms; //access permission ARMword domain; //access domain tlb_mapping_t mapping; //tlb mapping type } tlb_entry_t; |
TLB 表
typedef struct tlb_s{ int num; /*num of tlb entry*/ int cycle; /*current tlb cycle,used for allocate tlb_entry*/ tlb_entry_t *entrys; }tlb_t; |
接口函數
int mmu_tlb_init(tlb_t *tlb_t, int num); 初始化TLB void mmu_tlb_exit(tlb_t *tlb_t); 釋放TLB void mmu_tlb_invalidate_all(ARMul_State *state, tlb_t *tlb_t); 無效所有的tlb_entry void mmu_tlb_invalidate_entry(ARMul_State *state, tlb_t *tlb_t, ARMword virt_addr); 無效包含指定virt_addr的tlb_entry. tlb_entry_t * mmu_tlb_search(ARMul_State *state, tlb_t *tlb_t, ARMword virt_addr); 根據virt_addr查找tlb_entry |
TTW的實現(mmu/tlb.[ch])
ARM系統中通過2級映射,實現了頁式地址轉換。
接口函數
fault_t translate(ARMul_State *state, ARMword virt_addr, tlb_t *tlb_t, tlb_entry_t **tlb) { /*首先查找TLB表,如果沒有找到,執行TTW並更新TLB表*/ *tlb = mmu_tlb_search(state, tlb_t, virt_addr);/*查找tlb*/ if (!*tlb) { /* 沒有找到,進行TTW*/ ARMword l1addr, l1desc; /*一級描述符地址、一級描述符*/ tlb_entry_t entry; l1addr = state->mmu.translation_table_base & 0xFFFFC000; l1addr = (l1addr | (virt_addr >> 18)) & ~3; /*計算一級描述符地址*/ l1desc = mem_read_word(state, l1addr); /*讀取一級描述符*/ switch (l1desc & 3) { case 0: case 3: return SECTION_TRANSLATION_FAULT; case 1: /*頁式變換*/ { ARMword l2addr, l2desc; /*二級描述符的地址,二級描述符*/ l2addr = l1desc & 0xFFFFFC00; /*計算二級描述符的地址*/ l2addr = (l2addr | ((virt_addr & 0x000FF000) >> 10)) & ~3; /*讀取二級描述符*/ l2desc = mem_read_word(state, l2addr); entry.virt_addr = virt_addr; entry.phys_addr = l2desc; entry.perms = l2desc & 0x00000FFC; entry.domain = (l1desc >> 5) & 0x0000000F; switch (l2desc & 3) { case 0: case 3: state->mmu.last_domain = entry.domain; return PAGE_TRANSLATION_FAULT; case 1: entry.mapping = TLB_LARGEPAGE; /*大頁*/ break; case 2: entry.mapping = TLB_SMALLPAGE; /*小頁*/ break; } } break; case 2: /*段變換*/ entry.virt_addr = virt_addr; entry.phys_addr = l1desc; entry.perms = l1desc & 0x00000C0C; entry.domain = (l1desc >> 5) & 0x0000000F; entry.mapping = TLB_SECTION; break; } entry.virt_addr &= tlb_masks[entry.mapping]; entry.phys_addr &= tlb_masks[entry.mapping]; /* 更新tlb*/ *tlb = &tlb_t->entrys[tlb_t->cycle]; tlb_t->cycle = (tlb_t->cycle + 1) % tlb_t->num; **tlb = entry; } state->mmu.last_domain = (*tlb)->domain; return NO_FAULT; } |
訪問控制的實現(mmu/tlb.[ch])
接口函數
fault_t check_access(ARMul_State *state, ARMword virt_addr, tlb_entry_t *tlb, int read); |
CACHE的實現(mmu/cache.[ch])
ARM系統中一般只實現組相聯CACHE。組相聯CACHE分成多組,一個組有包括多個CACHE line,一個32位地址可以由下圖表示。
Tag | Set | Word | 1 | 0 |
數據結構
Cache行
typedef struct cache_line_t{ ARMword tag; /* cache line align address | bit2: last half dirty bit1: first half dirty bit0: cache valid flag */ ARMword pa; /*physical address*/ ARMword *data; /*array of cached data*/ }cache_line_t; #define TAG_VALID_FLAG 0x00000001 #define TAG_FIRST_HALF_DIRTY 0x00000002 #define TAG_LAST_HALF_DIRTY 0x00000004 |
Cache 組
typedef struct cache_set_s{ cache_line_t *lines; int cycle; /*used for cache line allocation*/ }cache_set_t; |
Cache 結構
typedef struct cache_s{ int width; /*bytes in a line*/ int way; /*way of set asscociate*/ int set; /*num of set*/ int w_mode; /*write back or write through*/ //int a_mode; /*alloc mode: random or round-bin*/ cache_set_t *sets; }cache_t; |
接口函數
/*Cache的初始化和釋放*/ int mmu_cache_init(cache_t *cache_t, int width, int way, int set, int w_mode); void mmu_cache_exit(cache_t *cache_t); /*Cache 查找和分配*/ cache_line_t * mmu_cache_search(ARMul_State *state, cache_t *cache_t, ARMword va); cache_line_t * mmu_cache_alloc(ARMul_State *state, cache_t *cache_t, ARMword va, ARMword pa); /*Cache 操作*/ void mmu_cache_write_back(ARMul_State *state, cache_t *cache_t, cache_line_t *cache); void mmu_cache_clean(ARMul_State *state, cache_t *cache_t, ARMword va); void mmu_cache_invalidate(ARMul_State *state, cache_t *cache_t, ARMword va); void mmu_cache_invalidate_all(ARMul_State *state, cache_t *cache_t); void mmu_cache_soft_flush(ARMul_State *state, cache_t *cache_t, ARMword pa); |
Write Buffer的實現(mmu/wb.[hc])
用一個循環隊列模擬Write Buffer。
數據結構
typedef struct wb_entry_s{ ARMword pa; //phy_addr ARMbyte *data;//data int nb; //number byte to write }wb_entry_t; typedef struct wb_s{ int num; //wb_entry_t的總數 int nb; //wb_entry_t中最大字節數 int first; //循環隊列頭 int last; //循環隊列尾 int used; //循環隊列中已用wb_entry_t的個數 wb_entry_t *entrys; }wb_t; |
接口函數
/*初始化和釋放*/ int mmu_wb_init(wb_t *wb_t, int num, int nb); void mmu_wb_exit(wb_t *wb); /*數據入Write Buffer*/ void mmu_wb_write_bytess(ARMul_State *state, wb_t *wb_t, ARMword pa, ARMbyte *data, int n); /*將Write Buffer中數據全部寫入內存*/ void mmu_wb_drain_all(ARMul_State *state, wb_t *wb_t); |
Read Buffer的實現(mmu/rb.[hc])
數據結構
typedef struct rb_entry_s{ ARMword data[RB_WORD_NUM];//array to store data ARMword va; //first word va int type; //rb type fault_t fault; //fault set by rb alloc }rb_entry_t; typedef struct rb_s{ int num; rb_entry_t *entrys; }rb_t; |
接口函數
/*初始化和釋放*/ int mmu_rb_init(rb_t *rb_t, int num); void mmu_rb_exit(rb_t *rb_t); rb_entry_t *mmu_rb_search(rb_t *rb_t, ARMword va);/*查找*/ void mmu_rb_invalidate_entry(rb_t *rb_t, int i);/*無效*/ void mmu_rb_invalidate_all(rb_t *rb_t); void mmu_rb_load(ARMul_State *state, rb_t *rb_t, int i_rb, int type, ARMword va);/*裝入*/ |
與具體CPU類型相關的MMU模擬子模塊建立在與具體CPU無關的MMU模擬實現子模塊的基礎上,通過實現具體CPU的mmu_ops_t,完成具體CPU的MMU模擬。以下詳細介紹StrongARM的MMU實現。
StrongARM的MMU分成指令和數據兩部分。指令部分包括一個指令TLB和一個指令CACHE,數據部分包括一個數據TLB,兩個數據CACHE(main CACHE和 mini CACHE),一個Write Buffer和一個Read Buffer。如圖 0 3所示:
圖 0-3 StrongARM數據訪問功能部件圖
mmu_ops_t sa_mmu_ops = { sa_mmu_init, sa_mmu_exit, sa_mmu_read_byte, sa_mmu_write_byte, sa_mmu_read_halfword, sa_mmu_write_halfword, sa_mmu_read_word, sa_mmu_write_word, sa_mmu_load_instr, sa_mmu_mcr, sa_mmu_mrc, }; |
SA MMU的初始化
typedef struct sa_mmu_desc_s{ int i_tlb; cache_desc_t i_cache; int d_tlb; cache_desc_t main_d_cache; cache_desc_t mini_d_cache; int rb; wb_desc_t wb; }sa_mmu_desc_t; static sa_mmu_desc_t sa11xx_mmu_desc ={ 32, {32, 32, 16, CACHE_WRITE_BACK}, 32, {32, 32, 8, CACHE_WRITE_BACK}, {32, 2, 8, CACHE_WRITE_BACK}, 4, {8, 16} }; typedef struct sa_mmu_s{ /*指令部分*/ tlb_t i_tlb; /*指令tlb*/ cache_t i_cache; /*指令cache*/ /*數據部分*/ tlb_t d_tlb; /*數據tlb*/ cache_t main_d_cache;/*main cache*/ cache_t mini_d_cache;/*mini cache*/ rb_t rb_t; /*Read Buffer*/ wb_t wb_t; /*Write Buffer*/ }sa_mmu_t; |
int sa_mmu_init(ARMul_State *state);
sa_mmu_desc_t 是SA MMU參數描述的數據結構,sa_mmu_t實現SA MMU 的數據表示。sa_mmu_init根據sa11xx_mmu_desc,完成對state->u.sa_mmu的初始化。
SA MMU的數據讀操作
static fault_t sa_mmu_read(ARMul_State *state, ARMword va, ARMword *data, ARMword datatype) { fault_t fault; rb_entry_t *rb; tlb_entry_t *tlb; cache_line_t *cache; ARMword pa, real_va,temp,offset; /*根據CP15 process id register轉換地址*/ va = mmu_pid_va_map(va); real_va=va; if (MMU_Disabled){ /*如果MMU關閉,直接從內存讀取*/ if (datatype==ARM_BYTE_TYPE) *data = mem_read_byte(state, va); else if (datatype==ARM_HALFWORD_TYPE) *data = mem_read_halfword(state, va); else if (datatype==ARM_WORD_TYPE) *data = mem_read_word(state, va); else { printf("SKYEYE:1 sa_mmu_read error: unknown data type %d/n",datatype); exit(-1); } return 0; } /*align 異常檢查*/ if (((va & 3 ) && (datatype==ARM_WORD_TYPE) && MMU_Aligned) || / ((va & 1 ) && (datatype==ARM_HALFWORD_TYPE) && MMU_Aligned) ){ d_msg("align/n"); return ALIGNMENT_FAULT; } // else va &= ~(WORD_SIZE - 1); /*TTW得到tlb*/ fault = translate(state, va, D_TLB(), &tlb); if (fault){ d_msg("translate/n"); return fault; } /*訪問控制檢查*/ fault = check_access(state, va, tlb, 1); if (fault) return fault; /*首先在Read Buffer中查找數據*/ rb = mmu_rb_search(RB(), va); if (rb){ if (rb->fault) return rb->fault; *data = rb->data[(va & (rb_masks[rb->type]-1))>>WORD_SHT]; goto datatrans; //return 0; }; /*其次查找main cache*/ cache = mmu_cache_search(state, MAIN_D_CACHE(), va); if (cache){ *data = cache->data[va_cache_index(va, MAIN_D_CACHE())]; goto datatrans; //return 0; } /*最後查找mini cache*/ cache = mmu_cache_search(state, MINI_D_CACHE(), va); if (cache){ *data = cache->data[va_cache_index(va, MINI_D_CACHE())]; goto datatrans; //return 0; } /*計算物理地址*/ pa = tlb_va_to_pa(tlb, va); /*如果在[0xe0000000, 0xe8000000]區間,執行mmu_cache_soft_flush*/ if ((pa >= 0xe0000000) && (pa < 0xe8000000)){ if (tlb_c_flag(tlb)){ if (tlb_b_flag(tlb)){ mmu_cache_soft_flush(state, MAIN_D_CACHE(), pa); }else{ mmu_cache_soft_flush(state, MINI_D_CACHE(), pa); } } return 0; } /*如果B=1,必須先清空Write Buffer*/ if (tlb_b_flag(tlb)) mmu_wb_drain_all(state, WB()); if (tlb_c_flag(tlb) && MMU_DCacheEnabled){ /*如果C=1,並且Data Cache開啓,將數據讀入Cache*/ cache_t *cache_t; if (tlb_b_flag(tlb)) cache_t = MAIN_D_CACHE(); else cache_t = MINI_D_CACHE(); cache = mmu_cache_alloc(state, cache_t, va, pa); *data = cache->data[va_cache_index(va, cache_t)]; }else{ /*直接從內存讀取*/ if (datatype==ARM_BYTE_TYPE) *data = mem_read_byte(state, pa|(real_va&3)); else if (datatype==ARM_HALFWORD_TYPE) *data = mem_read_halfword(state, pa|(real_va&2)); else if (datatype==ARM_WORD_TYPE) *data = mem_read_word(state, pa); else { printf("SKYEYE:2 sa_mmu_read error: unknown data type %d/n", datatype); exit(-1); } return 0; } /*根據讀類型和Endian模式整理結果*/ datatrans: if (datatype==ARM_HALFWORD_TYPE){ temp = *data; offset = (((ARMword) state->bigendSig * 2) ^ (real_va & 2)) << 3; /* bit offset into the word */ *data= (temp>> offset) & 0xffff; } else if (datatype==ARM_BYTE_TYPE) { temp = *data; offset = (((ARMword) state->bigendSig * 3) ^ (real_va & 3)) << 3; /* bit offset into the word */ *data=(temp >> offset & 0xffL); } end: return 0; } |
SA MMU的mcr操作
static void sa_mmu_mcr(ARMul_State *state, ARMword instr, ARMword value) { mmu_regnum_t creg = BITS(16, 19) & 15; if (!strncmp(skyeye_config.cpu->cpu_arch_name, "armv4", 5) ) { switch (creg) { …………………. case MMU_CACHE_OPS: sa_mmu_cache_ops(state, instr, value); break; case MMU_TLB_OPS: sa_mmu_tlb_ops(state, instr, value); break; case MMU_SA_RB_OPS: sa_mmu_rb_ops(state, instr, value); break; case MMU_SA_DEBUG: break; case MMU_SA_CP15_R15: break; case MMU_PID: state->mmu.process_id = value & 0x8e000000; break; default: printf("mmu_mcr wrote UNKNOWN - reg %d/n", creg); break; } } } |
sa_mmu_mcr實現SA MMU特定的cache, tlb, read buffer, write buffer等操作。
SkyEye memory系統的模擬實現的基本思想是:
1. 地址劃分,每個劃分區域都提供對應的讀寫函數
2. 通過skyeye.conf配置文件配置memory地址區域
數據結構
typedef struct mem_bank_t { ARMword (*read_byte)(ARMul_State *state, ARMword addr); void (*write_byte)(ARMul_State *state, ARMword addr, ARMword data); ARMword (*read_halfword)(ARMul_State *state, ARMword addr); void (*write_halfword)(ARMul_State *state, ARMword addr, ARMword data); ARMword (*read_word)(ARMul_State *state, ARMword addr); void (*write_word)(ARMul_State *state, ARMword addr, ARMword data); unsigned long addr, len; char filename[MAX_STR]; } mem_bank_t; typedef struct { int bank_num; int current_num; /*current num of bank*/ mem_bank_t mem_banks[MAX_BANK]; } mem_config_t; |
配置函數
int do_mem_bank_option(skyeye_option_t *this_option, int num_params, const char *params[]); /*完成memory bank的配置*/
讀寫接口函數
ARMword mem_read_byte(ARMul_State *state, ARMword addr); ARMword mem_read_halfword(ARMul_State *state, ARMword addr); ARMword mem_read_word(ARMul_State *state, ARMword addr); void mem_write_byte(ARMul_State *state, ARMword addr, ARMword data); void mem_write_halfword(ARMul_State *state, ARMword addr, ARMword data); void mem_write_word(ARMul_State *state, ARMword addr, ARMword data); |
實現方式都是先根據地址在mem_config_t中找到對應的mem_bank,再調用mem_bank對應的讀寫函數。
內存區域的讀寫函數
ARMword real_read_byte(ARMul_State *state, ARMword addr); void real_write_byte(ARMul_State *state, ARMword addr, ARMword data); ARMword real_read_halfword(ARMul_State *state, ARMword addr); void real_write_halfword(ARMul_State *state, ARMword addr, ARMword data); ARMword real_read_word(ARMul_State *state, ARMword addr); void real_write_word(ARMul_State *state, ARMword addr, ARMword data); |
IO區域的讀寫函數
ARMword io_read_byte(ARMul_State *state, ARMword addr); void io_write_byte(ARMul_State *state, ARMword addr, ARMword data); ARMword io_read_halfword(ARMul_State *state, ARMword addr); void io_write_halfword(ARMul_State *state, ARMword addr, ARMword data); ARMword io_read_word(ARMul_State *state, ARMword addr); void io_write_word(ARMul_State *state, ARMword addr, ARMword data); |
這些函數將IO的讀寫轉換成模擬的CPU的IO讀寫
- 本文節自 《源碼開放的嵌入式系統軟件分析與實踐--基於SkyEye和ARM開發平臺》一書的第三章,對 SkyEye 開源項目感興趣的可以閱讀本書。
- SkyEye硬件模擬平臺, 第一部分: SkyEye 介紹
- SkyEye硬件模擬平臺,第二部分: 安裝與使用
- SkyEye硬件模擬平臺,第三部分: 硬件仿真實現之一
- SkyEye硬件模擬平臺,第三部分: 硬件仿真實現之二
- 在 developerWorks Linux 專區 可以找到更多爲 Linux 開發者準備的參考資料。
陳渝, 清華大學,通過 [email protected] 可以和他聯繫。 |