uC/OS-II Porting to Intel X86 Platform 2010/04/29 Yufeng Lin Outline uC/OS-II Porting Multicore Boot Demo SMP uC/OS-II 2/32 uC/OS-II 3/32 uC/OS-II Porting Limit Hardware limit 處理器的 C 編譯器能產生可重入程式碼。 用 C 語言就可以打開和關閉中斷。 處理器支援中斷,並且能產生定時中斷 (通常在 10 至 100Hz 之間)。 處理器支援能夠容納一定數量的資料的硬體堆疊 (可能是幾千位元 組)。 處理器有將堆疊指標和其他 CPU 暫存器讀出和儲存到堆疊或記憶 體中的指令。 4/32 uC/OS-II Porting 1. 裝置啟動後,執行完硬體的初始設定或是硬體狀態檢查,就 main OSInit OSStartHigeReady 直接跳轉到 AP 執行的位置 2. uC/OS-II 一般啟動流程 OSStart Task create Do something Task 1 OSStatrtTimeDly Context switch Other Tasks 5/32 uC/OS-II Porting Porting = bootloader + OS Bootloader There are two ways Initialize hardware, vectors, memory, stack, register value 分開成兩個檔案(bin),bootloader裡面要設定OS Image存放的位址 (OS入口),需要兩者一致方可成功啟動OS 合成一個bin檔,在bootloader執行完以後,透過跳轉__main進入 OS入口 … 6/32 uC/OS-II Porting --- Bootloader Reset EntryPoint32 Real mode 47 Set PE in CR0 Clear instr. queue Protected mode NEAR ; Initialize allDescriptor segment registersTable) to 10h (entry #2 16 GDTR bit LDT(Local is GDT = Global Descriptor Table in the GDT) _Gdt: the same 16 15 mov ax,10h ; entry #2 in GDT DD DD 32 Build GDT PROC 0 ; GDT[0]: Null entry, never used. 16 bit Table ds,ax ; ds = 10h 0 mov bit Linear Base Address mov es,ax ; es = 10h Limit mov read-only fs,axcode, base ; fs address = 10h of 0, ; GDT[1]: Executable, mov gs,ax ; gs = 10h ;limit of FFFFFh, granularity bit (G) set (making the limit 4GB) mov ss,ax ; ss = 10h DW 0FFFFh ; Limit[15..0] DW DB DB DB DB mov eax,cr0 GDT or addresseax,1 mov cr0,eax GDT Size 0000h ; Base[15..0] ; Set the top of stack to allow stack operations. 00h ; Base[23..16] 10011010b ; P(1) DPL(00) S(1) 1 C(0) R(1) A(0) mov esp,8000h ; arbitrary top of stack 11001111b ; G(1) D(1) 0 0 Limit[19..16] 00h ; Base[31..24] ; Call main(), which is not expected to return. calldata segment _main ; GDT[2]: Writable 32 bit 16 clear bit Protected Jump.386p could mode LGDTqueue GDT DW instruction 0FFFFh ; Limit[15..0] DW DB DB DB DB ; Base[15..0] Jmp 0000h EntryPoint32 00h ; Base[23..16] 10010010b ; P(1) DPL(00) S(1) 0 E(0) W(1) A(0) uC/OS-II Main Function 11001111b ; G(1) B(1) 0 0 Limit[19..16] 00h ; Base[31..24] 7/32 0 uC/OS-II Porting --- Hardware Init. (1/4) The rest of the hardware initialization is performed in the application. Once in main(), a call is done to OsCpuInit(), in order to perform the following: Enable the address line 20 Relocate the IRQ interrupts Initialize the interrupt table The clock handler is installed as the interrupt 20h handler The uC/OS-II context switch handler is installed as the interrupt 30h handler 8/32 uC/OS-II Porting --- Hardware Init. (2/4) void OSCpuInit() { int IntNo; InitA20(); // Enable address line 20 InitPIC(); // Relocate IRQs to 0x20-0x2F // Install a default handler for all supported interrupts: // a) 0x00-0x1F: Intel-reserved // b) 0x20-0x2F: IRQ (relocated) // c) 0x30-0x3F: Available for uCOS and application. for (IntNo = 0; IntNo < 0x40; IntNo++) SetIntVector(IntNo, DefIntHandler); SetIntVector(0x20, OSTickISR); SetIntVector(0x30, OSCtxSw); } // Install the tick handler // Install uCOS-II's context switch handler 9/32 uC/OS-II Porting --- Hardware Init. (3/4) The A20 line is enabled by sending a few commands to the Intel 8042 keyboard controller. InitA20 do {status = inportb(0x64);} while (status & 2); // Wait until i8042 can receive the command. outportb(0x64, 0xd1); // Send the 'write' command to the i8042. do { status = inportb(0x64);} while (status & 2); // Wait until i8042 can receive the command. outportb(0x60, 0xdf); // Send the new set-up. do { status = inportb(0x64);} while (status & 2); // Wait until i8042 has received the command. 10/32 uC/OS-II Porting --- Hardware Init. (4/4) Relocate the IRQ lanes from 0x00 to 0x20 to prevent conflicts between IRQ and CPU's exceptions InitPIC // Reprogram the master 8259. outportb(I8259_A0, 0x11); outportb(I8259_A1, 0x20); outportb(I8259_A1, 0x04); outportb(I8259_A1, 0x01); outportb(I8259_A1, 0x00); // Reprogram the slave 8259. outportb(I8259_B0, 0x11); outportb(I8259_B1, 0x28); outportb(I8259_B1, 0x02); outportb(I8259_B1, 0x01); outportb(I8259_B1, 0x00); 11/32 uC/OS-II Porting OS_CPU.h Define Data type & register value depend on hardware specification & compiler OS_CPU_A.asm OSStartHighRdy() OSCtwSw() OSTickISR() OSIntCtxSw() 12/32 uC/OS-II Porting OS_CPU_C.c OSTaskStkInit() OSTaskCreateHook() OSTaskDelHook() OSTaskSwHook() OSTaskStatHook() OSTimeTickHook() 13/32 OS_CPU.h (1/2) Compiler dependent(請查看compiler手冊) typedef unsigned char typedef unsigned char typedef char typedef unsigned short typedef short typedef unsigned long typedef long typedef float typedef double typedef INT32U BOOLEAN; INT8U; INT8S; INT16U; INT16S; INT32U; INT32S; FP32; FP64; OS_STK; 14/32 OS_CPU.h (2/2) Processor dependent #define #define #define #define OS_ENTER_CRITICAL() OS_EXIT_CRITICAL() OS_STK_GROWTH OS_TASK_SW() __asm PUSHFD __asm CLI __asm POPFD 1 __asm INT 0x30 15/32 OS_CPU_A.asm 一般寫完bootloader後,常要觀察其與OS搭配後,是否可以順 利進入OS 為了往後可以測試task context switch 是否成功,因此建立2個 task,不同的priority,分別進行不同動作,例如輸出不同字元 taskA priority 5 並在while迴圈中加入OSTimeDly taskB priority 10並在while迴圈中加入OSTimeDly 16/32 OS_CPU_A.asm --- OSStartHighRdy(1/3) OSStartHighRdy() 由於OSstart()後,會去執行schedule,並且挑出最高priority的task執行 Pseudo code void OSStartHighRdy (void) { Call user definable OSTaskSwHook(); Get the stack pointer of the task to resume: Stack pointer = OSTCBHighRdy->OSTCBStkPtr; OSRunning = TRUE; Restore all processor registers from the new task's stack; Execute a return from interrupt instruction; } 17/32 OS_CPU_A.asm --- OSStartHighRdy(2/3) OSStartHighRdy OSRunning = TRUE mov eax,[_OSTCBHighRdy] mov esp,[eax] Pop all the processor registers from the stack mov eax, 1h mov [ _OSRunning ], eax Load the processor stack pointer with OSTCBHighRdy->OSTCBStkPtr call _OSTaskSwHook ; Call OSTaskSwHook(); popad Execute a Return from interrupt intruction iretd 18/32 OS_CPU_A.asm --- OSStartHighRdy(3/3) 若是此函式完成後 可以觀察到taskA的動作 若是沒有預期動作,則表示先前的準備工作尚未全部完成 Ex.bootloader、OS_CPU.h…… 19/32 OS_CPU_A.asm --- OSCtxSw(1/4) OSCtxSw() 若是目前task ready queue中有更高priority的task,則使用此函式將目 前正在執行的task與之交換 Pseudo code void OSCtxSw(void) { 保存處理器暫存器; 將當前 task 的堆疊指標保存到當前 task 的 OS_TCB 中: OSTCBCur->OSTCBStkPtr = Stack pointer; 呼叫使用者定義的 OSTaskSwHook(); OSTCBCur = OSTCBHighRdy; OSPrioCur = OSPrioHighRdy; 得到需要恢復的 task 的堆疊指標: Stack pointer = OSTCBHighRdy->OSTCBStkPtr; 將所有處理器暫存器從新 task 的堆疊中恢復出來; 20/32 執行中斷返回指令; OS_CPU_A.asm --- OSCtxSw(2/4) OSCtxSw 保存處理器暫存器 pushad OSTCBCur->OSTCBStkPtr = Stack pointer mov eax,[_OSTCBCur] mov [eax],esp ; Stack pointer is ESP 21/32 OS_CPU_A.asm --- OSCtxSw(3/4) 呼叫使用者定義的 OSTaskSwHook() call _OSTaskSwHook OSTCBCur = OSTCBHighRdy mov al,[_OSPrioHighRdy] ; AL is OSPrioHighRdy mov [_OSPrioCur],al OSPrioCur = OSTCBHighRdy mov eax,[_OSTCBHighRdy] ; EAX is OSTCBHighRdy mov [_OSTCBCur],eax 22/32 OS_CPU_A.asm --- OSCtxSw(4/4) Stack pointer = OSTCBHighRdy->OSTCBStkPtr mov esp,[eax] ; ESP = OSTCBHighRdy->OSTCBStkPtr 將所有處理器暫存器從新 task 的堆疊中恢復出來 執行中斷返回指令 popad iretd 此函式成功,則在taskA動作結束後,taskB會隨之動作 23/32 OS_CPU_A.asm --- OSTickISR OSTickISR 保存處理器暫存器 Send an end-of-interrupt to the i8259 _OSIntEnter call _OSTimeTick call _OSIntExit 恢復處理器暫存器 call 呼叫 OSIntExit() al,20h 20h,al 呼叫 OSTimeTick() mov out 呼叫 OSIntEnter() 或者直接將 OSIntNesting 加 1 pushad popad 執行中斷返回指令 iretd 此函式完成後,可以在此函式中加入輸出, 若是此輸出可以依照interval持續到來,表示成功 24/32 Multicore Boot Multicore BSP (Bootstrap Processor) AP (Application Processor) How to boot AP (Application Processor) ? Disable 8259 Initialize Local APIC Initialize I/O APIC 25/32 IMC R BSP PIC Mode CPU 1 AP CPU 2 AP CPU 3 Local APIC 0 Local APIC 1 Local APIC 2 NMI LINTIN1 LINTIN0 RESET ICC BUS 8259 I/O Interrupt INT APIC 26/32 Interrupt Mode IMC R APIC Mode NMI BSP CPU 1 AP CPU 2 AP CPU 3 Local APIC 0 Local APIC 1 Local APIC 2 LINTIN1 LINTIN0 RESET ICC BUS 8259 I/O Interrupt INT` APIC 27/32 Multicore Boot Disable 8259 It means leave PIC mode IMCR ( Interrupt Mode Control Register) Write 0x70 on 0x22 (choose IMCR) Write 0x01 on 0x23 (not use PIC mode) Disable(mask) 8259 all IRQ pins 28/32 Multicore Boot BSP初始化過程 Intel spec AP初始化 1. 初始化memory 2. Load microcode到處理器 1. 獲取信號量,開始初始化 3. 初始化內存範圍寄存器(MTRRs) 2. load microcode到處理器 4. enable cache 3. 初始化內存範圍寄存器(MTRRs) 5. 確定BSP是否是"GenuineIntel" 4. enable cache 6. 執行CPUID,保存CPU信息為將來使用 5. 檢查AP是否是"GenuineIntel" 7. load AP的啟動代碼到低1M的一個4K的頁裡 6. 保存CPUID信息,為將來使用 8. 切換到保護模式 7. 切換到保護模式 9. 轉換4K的頁基址為一個8位的向量. 例如 8. 配置AP的共存內存接口執行環境 0x000BD000H --> 0xBDH 9. 將處理器個數加1 10.設置APIC的SVR的bit8來enable local APIC 10.釋放信號量 11.建立錯誤處理handler 11. 執行CLI並且進入halt狀態 12.初始化鎖信號量 12.等待INIT IPI 13.探測系統中的AP, 方法如下: - 設置處理器COUNT為1 - 啟動一個timer,BSP開始等待 - 此時AP開始初始化,並將COUNT加1 - timer到期,BSP檢查COUNT,如果沒有增加,就表示系統中沒有AP. 29/32 14. 等timer中斷,檢查COUNT並建立處理器數目 Multicore Boot Broadcasts an INIT-SIPI-SIPI IPI sequence to the APs to wake them up and initialize send twice IPI (Inter-Processor Interrupt) (INIT) 1. make AP initialize 2. set AP Arb ID register broadcast SIPI (start-up IPI) 1. Number of times depend on CPU 2. Tell AP the address where it start execute 30/32 Demo 31/32 Questions? 32/32