오후 4:45 2006-06-01
preemtive switch 완성
조경민 bro@shinbiro.com
http://neri.cafe24.com
============================================================

인터럽트 핸들러에서 어셈 짤때 주의 할점
-----------------------------------------
1. SVC모드 태스크를 위해 msr cpsr로 직접 인터럽트를 활성화 시키면
   바로 인터럽트가 발생될 수 있으므로, IRQ모드로 전환 후 msr spsr을 해야함
2. IRQ모드에서 r0-r12는 SVC모드와 공유된다. 즉, 사용하면 다시 복원해야한다.
   stmfd sp!, {r0}
   r0 사용
   ldmfd sp!, {r0}
   이렇게 사용 후 복원을 고민해야한다.
3. IRQ모드의 lr값을 SVC모드에게 전달하려면 방법이 현재 없다. 특정 메모리를
   사용하게 하였다.
        lr_irq                EQU        0x33000004

        ; IRQ 모드의 lr_irq를 특정 위치 =lr_irq로 저장
        stmfd        sp!, {r0}
        ; *MEM_lr_irq = lr_svc
        ldr        r0, =lr_irq
        str        lr, [r0]                ; MEM_lr_irq <= new_task.lr_svc
        ldmfd        sp!, {r0}   ; sp_irq임

        ; SVC모드에서 =lr_irq를 받아다가 lr_svc로 복원할때
        stmfd        sp!, {r0}
        ; lr_irq = MEM_lr_irq
        ldr        r0, =lr_irq
        ldr        lr, [r0]
        ldmfd        sp!, {r0}   ; sp_svc임
4. IRQ모드로 들어오면
        a. IRQ는 비활성화
        b. lr에는 복귀할 주소+4값 (마지막 실행한 주소+8)
        c. SVC모드의 psr은 spsr에 들어있음
   SVC모드로 돌아가기 위해서 아래와 같은 코드 중 하나를 실행하면 된다.
        movs        pc, lr
        subs        pc, lr, #4
        ldmfd        sp!, {r0-r3,pc}^


기존의 taskswitch 코드는 아래와 같다.
1. cpu context를 태스크 스택에 넣는다.
2. 스케줄링으로 새로운 태스크 스택을 얻는다.
3. 새로운 태스크 스택 정보로 cpu context를 채운다.

task_switch
        ; save cpu context of old task
        stmfd        sp!, {r0-r12,lr}
        mrs     r0, CPSR
        stmfd        sp!, {r0}

        ; 수동 스케줄링 중에 갑자기 interrupt가 들어와서
        ; r14를 0으로 바꾸는 일이 없도록
        mrs     r0, CPSR
        ORR         r0, r0, #0x80
        msr     CPSR_c, r0

        ; do schdule and get next task stack top pointer
        mov        r0, sp
        bl        schedule
        mov        sp, r0

        ; 다시 인터럽트 살려주고..
        mov        r0, #1
        MRS r1,CPSR    
        ORR r1,r1,#0x80    
        BIC r0,r1,r0,LSL #7    
        MSR CPSR_c,r0

        ; restore cpu context of new task
        ldmfd        sp!, {r0}
        msr        CPSR_cxsf, r0
        ldmfd        sp!, {r0-r12,pc}

그런데, 이 코드는 여전히 문제가 있다.

        ; restore cpu context of new task
        ldmfd        sp!, {r0}
        msr        CPSR_cxsf, r0      <--- A 부분
        ldmfd        sp!, {r0-r12,pc}

A부분에서 새로운 태스크의 cpsr이 들어가는데 0x53 즉, IRQ를 받도록 된 svc모드
인 경우, atomic하게 ldmfd        sp!, {r0-r12,pc} 도 실행되어야 하는데
그렇게 안되고 msr 명령 실행 한 후 바로 인터럽트가 걸려 태스크 스위치가
중복되어 실행될 문제가 있었다.;

이를 방지하기 위해서는 직접 태스크의 cpsr을 msr하기 보다는 IRQ 모드로 변환후
spsr에 값을 넣어 movs pc, lr을 시켜 arm이 IRQ모드에서 SVC모드로 전환 시
spsr의 값을 SVC모드의 새로운 태스크의 cpsr로 복원하게 만들어야 한다.
이렇게 하기 위해서 또 IRQ, SVC 모드간 공유할 수 있는 메모리인 cpsr_svc를
만들었다. -_-;

그리고 create_task시 func+4를 하는 불상사를 막기 위해서 boot.s의 IRQ핸들러에서
미리 lr -4를 하게 하였다. arm 책을 보니 IRQ 모드로 왔을때 lr_irq에는 마지막
실행된 주소+8이 되어 있다고 하는 걸 읽었다 -_-
즉, lr - 4가 진짜 실행할 주소라는 말이다. 이제 create_task에서 다시 원래대로
+ 4 안하고 바로 func를 넣을 수 있게 되었다.

irq
        ; IRQ shadow reg: r13, r14, spsr
        ; r14_svc
        ; save cpu context of old task
        ; 이미 cpsr의 IRQ는 꺼져있게 된다. irq 복원시 자동 온됨
        ; irq_lr에는 마지막 실행된 주소 + 8이 들어 있으므로 미리 -4를 함
        sub        lr, lr, #4
        stmfd        sp!, {r0-r3,lr}        ; APCS 규약에 맞게 r0 - r3, lr만 저장
        bl        irq_handler
        cmp        r0, #1                ; need schdule
        bne        irq_return
        ldmfd        sp!, {r0-r3,lr}
        b        need_schedule
irq_return
        ldmfd        sp!, {r0-r3, pc}^

어쨋든, task_switch에서도 아래처럼 바뀌었다. 엄청 길어졌다 -_-;;

cpsr_svc        EQU        0x33000010
lr_irq                EQU        0x33000004

이제 좀 길다;
task_switch
        stmfd        sp!, {r0}
        ; 오후 12:44 2006-06-01 이부분은 창우의 조언에 따르기로 한다.
        ; task_switch() 함수는 정책적으로 native function이다.
        ; 무조건 IRQ를 Disable 시킨다. 유저는 반드시 task_switch()호출후
        ; irq_enable()해야 한다.
        ; 수동 스케줄링 중에 갑자기 interrupt가 들어와서
        ; r14를 0으로 바꾸는 일이 없도록 irq disable한다.
        mrs        r0, CPSR
        ORR        r0, r0, #0x80
        msr        CPSR_c, r0
        ldmfd        sp!, {r0}

        ; save cpu context of old task
        stmfd        sp!, {r0-r12,lr}
        mrs     r0, CPSR
        stmfd        sp!, {r0}

        ; do schdule and get next task stack top pointer
        mov        r0, sp
        bl        schedule
        mov        sp, r0

        ; restore cpu context of new task
        ldmfd        sp!, {r0}
        ; msr        CPSR_cf, r0        ; 바로 psr_svc에 IRQ 활성화하면 안됨

        ; *cpsr_svc = r0 task's psr
        ldr        r1, =cpsr_svc
        str        r0, [r1]

        ldmfd        sp!, {r0-r12,lr}

        stmfd        sp!, {r0}
        ; *MEM_lr_irq = lr_svc
        ldr        r0, =lr_irq
        str        lr, [r0]                ; MEM_lr_irq <= new_task.lr_svc
        ldmfd        sp!, {r0}

        mov        lr, #IRQMODE | NOINT
        msr        cpsr_c,lr

        ; in irq: lr = lr_irq, spsr = cpsr_svc
        stmfd        sp!, {r0}
        ; lr_irq = MEM_lr_irq
        ldr        r0, =lr_irq
        ldr        lr, [r0]
        ; spsr = MEM_cpsr_svc
        ldr        r0, =cpsr_svc
        ldr        r0, [r0]
        msr        spsr_cf, r0
        ldmfd        sp!, {r0}

        movs        pc, lr

게다가 task_start 시키는 것 역시 msr을 쓰므로 길어졌다.;
task_start
        mov        sp, r0
        ; restore cpu context of new task
        ldmfd        sp!, {r0}
        ;msr        CPSR_cf, r0
        ;ldmfd        sp!, {r0-r12,pc}

        ; *cpsr_svc = r0 task's psr
        ldr        r1, =cpsr_svc
        str        r0, [r1]

        ldmfd        sp!, {r0-r12,lr}

        stmfd        sp!, {r0}
        ; *MEM_lr_irq = lr_svc
        ldr        r0, =lr_irq
        str        lr, [r0]                ; MEM_lr_irq <= new_task.lr_svc
        ldmfd        sp!, {r0}

        mov        lr, #IRQMODE | NOINT
        msr        cpsr_c,lr

        ; in irq: lr = lr_irq, spsr = cpsr_svc
        stmfd        sp!, {r0}
        ; lr_irq = MEM_lr_irq
        ldr        r0, =lr_irq
        ldr        lr, [r0]
        ; spsr = MEM_cpsr_svc
        ldr        r0, =cpsr_svc
        ldr        r0, [r0]
        msr        spsr_cf, r0
        ldmfd        sp!, {r0}

        movs        pc, lr


태스크 생성에서 func+4가 빠졌다. 이제 보기 좋아졌다 ㅠㅠ
그리고 바로 0xD3 (no irq, svc모드)이 아닌 0x53(irq on, svc모드)로 태스크를
활성화 할 수 있다!

void create_task( task* pTask, int func )
{
        memset( pTask->base, 0, TASK_STACK_SIZE );
        pTask->func = func;
        pTask->stack = (int*)(pTask->base + TASK_STACK_SIZE - 4); // t0

        pTask->stack -= 1;        // pc
        *pTask->stack = func;        // 시작할 pc값 넣음
        
        pTask->stack -= 13;        // r0-r12 적음

        pTask->stack -= 1;        // CPSR flag기록
        *pTask->stack = 0x53;        // irq enable, iF_svc mode    (t1)
        
        // full decending (start empty)
/*
                             empty
+--------+ (0x19C)        +--------+ TASK_STACK_SIZE (0x200)
|        | <- stack (t0)  | func   |
|        |                +--------+
|        |                | r12    |
|        |                +--------+
|        |                | ...    |
|        |                +--------+
|        |                | r0     |
|        |                +--------+
|        |                | 0xD3   |
|        |                +--------+ <- stack top (t1)
|        |                |        |
+--------+ <- base (0x0)  +--------+ <- base (0x0)

*/

}

이제 타이머를 이용해 선점형 태스크 스위치와 명시적 태스크 스위치를 할 수
있게 되었다.

void test1()
{
        int i = 0;

        while(1)
        {
                task_switch();
                irq_enable();
                i++;
        }

}

void test2()
{

        while(1)
        {
                //task_switch();
        }
        
}

:
void main()
{
        init_timer0();

        create_task( &g_Tasks[0], (int)test1 );
        create_task( &g_Tasks[1], (int)test2 );
        create_task( &g_Tasks[2], (int)test3 );
        create_task( &g_Tasks[3], (int)test4 );

        g_Tasks[0].next = &g_Tasks[1];
        g_Tasks[1].next = &g_Tasks[2];
        g_Tasks[2].next = &g_Tasks[3];
        g_Tasks[3].next = &g_Tasks[0];
        current_task = &g_Tasks[0];

        task_start((int)current_task->stack);
}

스터디 용이라 이해하기 쉬운 소스로 짜려다 보니, 많이 길어졌다;
물론 실무에서는 이렇게 길게 하면 안되고 optimization을 해야겠지..

+ Recent posts