• 沒有找到結果。

3. 系統架構與實作

3.2. DSP System Kernel

3.2.1. Process

在DSP 執行的服務會以 Process 為單位在運行。一個 Process 在執行時,會 需要Process control block 來記錄其目前的資訊。如 Figure 24。有兩種 Stack 指標,

分別代表主要的Stack 和 System stack,因為 System stack 指標和 Stack 指標共用 高的7bit,所以只需記錄低的 16bit。Process 的狀態,有 Unregistered、Ready、

Busy、IDLE,其中 IDLE 是專門給代表目前沒有服務在運行的 Process 使用。

Process 的 ID,目前設計最多有 32 個 Process。以 function 這種型態來宣告 Entry point,可以很容易去用 C 語言呼叫 Initial function 和 Main function 的。Initialized 是記錄Initial function 是否被呼叫過的 flag。剩下的 pad 是為了使 PCB 大小是 16 個16bit,讓之後要用組語去撰寫存取各個 PCB 的 base address 計算,會比較簡 單而有效率。

typedef void(*function)();

typedef struct _PCB{

uint32 pc;

uint32 sp;

uint16 ssp;

enum STATE state;

int id;

function init_entry_point;

function entry_point;

int initialized;

uint16 pad[4];

}PCB;

Figure 24. Process Control Block

為了滿足動態服務的設計,Process 在運行服務時,其 State 切換會如 Figure 25,一開始建立新的 Process 後,會是在 Unregistered state。註冊一個服務後,被 分配的Process,會改成 Ready。 當 ARM 有需要 DSP 去運行服務時,會用 Invoke 命令,使得 Process 變成 Busy。運行完的 Process 會回到 Ready,可以再次被 Invoke。當不再需要 Invoke,就可以 Unregister 掉,回到初始的 Unregistered state。

init_service_

Ready

Busy Unregistered

invoke terminate

unregister register

Figure 25. Process State Transitions

如Figure 26,這是在 DSP 系統核心上運行的 Process 其建立過程的大略程式 碼,首先初始化IDLE Process,在 DSP 沒有任何服務在運行時,DSP 則會運行 這個Process,目前並沒針對功耗去做最佳化,所以 IDLE Process 其運行目的,

只是不停地從工作 Queue 中,找工作來執行。之後會用 init_service_slot 函式,

產生要運行實際服務的 Process,然後啟動 Timer,代表著 DSP 系統核心開始運 行。而process 和 IDLE_process 這兩個函式裡都是用無窮迴圈不停的執行其工作。

void main() {

/* System Initialization */

/* System Initialization */

idle_pcb = &pcb[MAX_PROCESS];

idle_pcb->id = MAX_PROCESS;

idle_pcb->state = IDLE;

current_pcb = idle_pcb;

for(int i=0; i<MAX_PROCESS; i++) if(init_service_slot() != idle_pcb->id) process();

DSP_CNTL_TIMER3 |= 1; // Start the timer IDLE_process();

}

Figure 26. Initialization of Unregistered Process

實際運行服務的Process 是經由 Figure 27 的 init_service_slot 函式所建立的,

在設定完新Process 的 PCB 的初始資訊後,會繼承原本正在執行中的 idle Process 的暫存器內容,並分裂出新的 Process,return 的 Process id 是為了給呼叫 init_service_slot 函式的呼叫者分辦目前是哪一個 Process 所使用的。

int init_service_slot() {

static int pid = 0;

uint16 *stack = (uint16 *)0x7C00 - 0x380*pid;

select_pcb = &pcb[pid];

select_pcb->sp = (uint32)stack;

select_pcb->ssp = ((uint32)stack - 0x300) & 0xFFFF;

select_pcb->state = UNREGISTERED;

select_pcb->id = pid++;

push_and_start();

return current_pcb->id;

}

Figure 27. Funtion init_service_slot()

Stack 指標的分配是從 0x7C00 到 0x0C00,屬於 DARAM 記憶體空間,會把 Stack 存放在 DARAM,是因為 C55x 的 DSP 指令集中有能夠一次進行兩筆 16bit 資料的stack operation,而 DARAM 能同時處理兩筆存取,所以把 stack 設定在 這邊可以加快context switch 的速度。因為 Stack 的使用是往低的 address 成長,

在 PCB 中 System stack 的指標是由 Stack 指標減去 0x300 得來的,就代表每個 Process 被分配的 Stack 大小為 0x300 個 16bit,而 System stack 的大小,因為每個 Process 的 Stack 指標相差 0x380,扣去 Stack 大小的 0x300,System stack 大小則 是0x80 個 16bit。

在push_and_start 函式中,會備份 IDLE Process 的暫存器內容,並切換目前 運行中的PCB 為新的 Process 的 PCB,這時從 push_and_start 函式離開後,邏輯 上就可以算可以分裂成了兩個 Process,一個是原本在執行的 IDLE Process,一 個是新建的Process,但實際上 DSP 核心正在執行的會是新建立的 Process,在系 統正式運行前,新建立的 Process 會先放棄 DSP 使用權,切換回 IDLE Process

DSP 系統核心在運行時,會有兩種 Process,一個是 IDLE Process,一個是 服務在運行的Process,各自會分別執行 IDLE_process 和 process 函式,會以 Figure 28 的方式來做切換,在 IDLE_process 函式會呼叫 yield 函式,會使目前的 Process 自願放棄DSP 使用權,並從工作 Queue 選取下一個工作,切換 PCB 並做 context switch,IDLE Process 會以這樣的方式不停的從工作 Queue,找工作來運行,如 果有工作可以運行,就會切換到process 函式來運行服務的工作實際內容(1 號箭 頭)。服務的工作運行結束,dequeue 函式會將目前工作,從工作 Queue 裡移除。

之後呼叫yield 函式,再選取工作 Queue 裡下一個工作來執行(2 號箭頭),如果工 作Queue 裡沒有工作可執行,就會回到 IDLE process(1 號箭頭)。

其中對工作Queue 進行修改的 yield 函式和 dequeue 函式,必需要有中斷的 mask 保護,確保修改工作 Queue 時,不會有中斷發生,修改後的工作運行的順 序邏輯上才會是正確。

Figure 28. Process Running Flowchart void IDLE_process () {

while(1) {

asm(" BSET ST1_INTM");

yield();

asm(" BCLR ST1_INTM");

} }

void process() {

int pid = current_pcb->id;

asm(" BSET ST1_INTM");

while(1) { yield();

asm(" BCLR ST1_INTM");

if(current_pcb->init_entry_point

&& !current_pcb->initialized) { current_pcb->init_entry_point(pid);

current_pcb->initialized = 1;

}

current_pcb->entry_point(pid);

asm(" BSET ST1_INTM");

dequeue(pid);

} }

1 2

服務實際會執行的內容,可分成兩個部分,如果有提供初始化函式,第一次 被執行的服務,會先執行初始化函式,另一部分則是服務要進行的演算法。因為 這兩種entry point 都是以 function 的型態來宣告,所以可以直接呼叫。

服務所運行的工作函式,都會傳入一個 pid,這是用來使 ARM 傳遞參數所 會用到的,隨著演算法不同,有些可能不需要參數,有些可能需要多個參數,甚 至有需要陣列當參數,這些參數就可以放在shared memory 上參數表,經由 pid 來讀取各個服務所需的參數。為了讀取這些參數,原本演算法的函式就需要一個 wrap 函式,用來讀取參數並傳給原本的函式。

如Figure 29,原本的演算法 dequant 函式,需要四個參數,而且前兩個還是 陣列。在ARM 下 Mailbox 命令,把這項工作加到工作 Queue 之前,要先把需要 的參數根據服務的pid,填到 shared memory 上的參數表,每個參數都以 32bit 的 長度寫入,實際傳入演算法的函式前,會藉由轉型而傳入正確的長度,而陣列的 參數,其起始位置,是由 ARM 動態決定,而其內容會根據陣列長度,看是由 ARM 或是 DMA 來搬。經由 wrap 的方式,dequant 函式就能正確執行。但因為 每個演算法需要參數的個數和型態,都不是固定的,像這樣的wrap 函式,目前 還是用人工撰寫的,未來如果有特製的compiler 配合,就可以自動化這個步驟。

#define PARAMETER_TABLE 0x600000

#define PARAMETER_SIZE 4 void dequant_wrap(int16 id) {

uint32 *ptr = (uint32*)PARAMETER_TABLE + PARAMETER_SIZE * id;

uint32 p0,p1,p2,p3;

p0 = *ptr++;

p1 = *ptr++;

p2 = *ptr++;

p3 = *ptr++;

dequant((int16 *)p0,(int16 *)p1, (uint32)p2, (uint32)p3);

}

相關文件