오후 4:39 2006-05-30
timer 상에서 task switch하기 on smdk2440 board
조경민 bro@shinbiro.com
http://neri.cafe24.com
============================================================
IRQ 를 위한 스택을 잡아주었다.
; IRQ 스택 초기화
mrs r0, CPSR
bic r0, r0, #0x1f
orr r0, r0, #IRQMODE | NOINT
msr cpsr_cxsf,r0
mov sp, #0x32000000
; SVC 스택 초기화
mrs r0, CPSR
bic r0, r0, #0x1f
orr r0, r0, #SVCMODE | NOINT
msr cpsr_cxsf,r0
mov sp, #0x31000000
irq 가 오면 irq_handler c코드로 뜨게 하고, irq_handler에서 리턴이 1이면
스케줄링을 필요로 한다는 개념으로 접근했다.
irq
; IRQ shadow reg: r13, r14, spsr
; r14_svc
; save cpu context of old task
stmfd sp!, {r0-r12,lr}
; 수동 스케줄링 중에 갑자기 interrupt가 들어와서
; r14를 0으로 바꾸는 일이 없도록
mrs r0, CPSR
orr r0, r0, #0x80
msr CPSR_c, r0
bl irq_handler
cmp r0, #1 ; need schdule
ldmfd sp!, {r0-r12,lr}
bne irq_return
b need_schedule
irq_return
subs pc, lr, #4
타이머나 irq 처리 루틴을 위한 파일이 생겨서 memory.map도 추가되었다.
MEMORY_MAP 0x0
{
BOOT 0x0
{
boot.o (boot, +First)
* (+RO, +RW, +ZI)
}
KERNEL 0x30003000
{
main.o (+RO, +RW, +ZI)
int.o (+RO, +RW, +ZI)
timer.o (+RO, +RW, +ZI)
interrupt.o (+RO, +RW, +ZI)
}
}
소스가 좀 많이 늘어났다. 이제 ADS 1.2의 프로젝트 관리 툴을 사용해야
할 때가 온것 같다 ㅡ,.ㅡ;
;=====================================================
; boot test
; by bro, bro@shinbiro.com 오전 10:23 2006-04-26
;
; armasm main.s -o main.o
; armlink main.o --ro-base 0x0 -o main.axf
;=====================================================
GET option.inc
GET memcfg.inc
GET 2440addr.inc
; main.c의 커널 main 함수 심볼 쓰기 위해서
IMPORT main
IMPORT ||Image$$KERNEL$$ZI$$Limit||
EXPORT __user_initial_stackheap
EXPORT vector
EXPORT irq_return
; interrupt.c의 함수
IMPORT irq_handler
IMPORT need_schedule
; KERNEL region 복사를 위해 심볼 사용
IMPORT ||Image$$KERNEL$$Base||
IMPORT ||Image$$KERNEL$$Length||
IMPORT ||Load$$KERNEL$$Base||
;Pre-defined constants
USERMODE EQU 0x10
FIQMODE EQU 0x11
IRQMODE EQU 0x12
SVCMODE EQU 0x13
ABORTMODE EQU 0x17
UNDEFMODE EQU 0x1b
MODEMASK EQU 0x1f
NOINT EQU 0xc0
PRESERVE8
AREA boot, CODE, READONLY
ENTRY
vector ; 인터럽트 벡터 테이블
b reset ; 주소 0번지에 reset 인터럽트가 오면 바로
reset레이블로 점프
b . ; undefined_instruction
b . ; software_interrupt
b . ; prefetch_abort
b . ; data_abort
b . ; not_used
b irq ; IRQ
b . ; FIQ
irq
; IRQ shadow reg: r13, r14, spsr
; r14_svc
; save cpu context of old task
stmfd sp!, {r0-r12,lr}
; 수동 스케줄링 중에 갑자기 interrupt가 들어와서
; r14를 0으로 바꾸는 일이 없도록
mrs r0, CPSR
orr r0, r0, #0x80
msr CPSR_c, r0
bl irq_handler
cmp r0, #1 ; need schdule
ldmfd sp!, {r0-r12,lr}
bne irq_return
b need_schedule
irq_return
;stmfd sp!, {r0}
;; 다시 인터럽트 살려주고..
;mrs r0, cpsr
;bic r0, r0, #0x80
;msr cpsr_c, r0
;ldmfd sp!, {r0}
subs pc, lr, #4
reset
; 현재 모드에서 IRQ|FIQ를 끈다.
mrs r0, CPSR
bic r0, r0, #0x1f
orr r0, r0, #(0x13 | 0xc0) ; 0x13(0001 0011) | 0xC0 (1101 0000)
= 0xD3(1101 0011)
msr CPSR_cxsf, r0
; IRQ 스택 초기화
mrs r0, CPSR
bic r0, r0, #0x1f
orr r0, r0, #IRQMODE | NOINT
msr cpsr_cxsf,r0
mov sp, #0x32000000
; SVC 스택 초기화
mrs r0, CPSR
bic r0, r0, #0x1f
orr r0, r0, #SVCMODE | NOINT
msr cpsr_cxsf,r0
mov sp, #0x31000000
; 메모리 초기화
bl SDRAM_init
; 커널 복사 (memory.map에서 커널영역은 0x10000에서 하겠다고 했으니
복사해야함;)
bl copy_kernel
ldr pc, =main
b end
__user_initial_stackheap
LDR r0, =||Image$$KERNEL$$ZI$$Limit||
MOV pc, lr
copy_kernel
ldr r0, =||Load$$KERNEL$$Base|| ; source (FLASH ROM같은 영역
)
ldr r1, =||Image$$KERNEL$$Base|| ; destination (SDRAM 같은 영
역)
ldr r2, =||Image$$KERNEL$$Length|| ; kernel length
mov r3, #0 ; counter
loop
ldr r4, [r0], #4
str r4, [r1], #4
add r3, r3, #4
cmp r3, r2
bne loop
mov pc, lr
Clock_Init ; 버스 클럭 초기화
;To reduce PLL lock time, adjust the LOCKTIME register.
ldr r0,=LOCKTIME
ldr r1,=0xffffff
str r1,[r0]
[ PLL_ON_START
; Added for confirm clock divide. for 2440.
; Setting value Fclk:Hclk:Pclk
ldr r0,=CLKDIVN
ldr r1,=CLKDIV_VAL ; 0=1:1:1, 1=1:1:2, 2=1:2:2,
3=1:2:4, 4=1:4:4, 5=1:4:8, 6=1:3:3, 7=1:3:6.
str r1,[r0]
;Configure UPLL
ldr r0,=UPLLCON
ldr r1,=((U_MDIV<<12)+(U_PDIV<<4)+U_SDIV)
str r1,[r0]
nop ; Caution: After UPLL setting, at least 7-clocks delay must
be inserted for setting hardware be completed.
nop
nop
nop
nop
nop
nop
;Configure MPLL
ldr r0,=MPLLCON
ldr r1,=((M_MDIV<<12)+(M_PDIV<<4)+M_SDIV) ;Fin=16.9344MHz
str r1,[r0]
]
SDRAM_init ; 0x30000000 SDRAM 메모리 초기화
;Set memory control registers
ldr r0,=SMRDATA
ldr r1,=BWSCON ;BWSCON Address
add r2, r0, #52 ;End address of SMRDATA
0
ldr r3, [r0], #4
str r3, [r1], #4
cmp r2, r0
bne %B0
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;; When EINT0 is pressed, Clear SDRAM
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;
; check if EIN0 button is pressed
ldr r0,=GPFCON
ldr r1,=0x0
str r1,[r0]
ldr r0,=GPFUP
ldr r1,=0xff
str r1,[r0]
ldr r1,=GPFDAT
ldr r0,[r1]
bic r0,r0,#(0x1e<<1) ; bit clear
tst r0,#0x1
bne %F1
; Clear SDRAM Start
ldr r0,=GPFCON
ldr r1,=0x55aa
str r1,[r0]
; ldr r0,=GPFUP
; ldr r1,=0xff
; str r1,[r0]
ldr r0,=GPFDAT
ldr r1,=0x0
str r1,[r0] ;LED=****
mov r1,#0
mov r2,#0
mov r3,#0
mov r4,#0
mov r5,#0
mov r6,#0
mov r7,#0
mov r8,#0
ldr r9,=0x4000000 ;64MB
ldr r0,=0x30000000
0
stmia r0!,{r1-r8}
subs r9,r9,#32
bne %B0
;Clear SDRAM End
1
mov pc, lr
SMRDATA DATA
; Memory configuration should be optimized for best performance
; The following parameter is not optimized.
; Memory access cycle parameter strategy
; 1) The memory settings is safe parameters even at HCLK=75Mhz.
; 2) SDRAM refresh period is for HCLK<=75Mhz.
DCD (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+
(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
DCD ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+
(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC)) ;GCS0
DCD ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+
(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC)) ;GCS1
DCD ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+
(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC)) ;GCS2
DCD ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+
(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC)) ;GCS3
DCD ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+
(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC)) ;GCS4
DCD ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+
(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC)) ;GCS5
DCD ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN)) ;GCS6
DCD ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN)) ;GCS7
DCD ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Tsrc<<18)+(Tchr<<16)
+REFCNT)
DCD 0x32 ;SCLK power saving mode, BANKSIZE 128M/128M
DCD 0x30 ;MRSR6 CL=3clk
DCD 0x30 ;MRSR7 CL=3clk
ALIGN
end
b .
END
---------------------------------------------------------------------------
--
main.c
#define TASK_STACK_SIZE 0x200
#define TASK_NUMBER 4
typedef struct _task
{
char base[TASK_STACK_SIZE];
int* stack;
int func;
struct _task* next;
}task;
task g_Tasks[TASK_NUMBER];
task* current_task;
void task_switch(void); // in int.s
void task_start(int task_sp);
void memset( char* src, int c, int size )
{
int i;
for( i = 0; i < size; i ++)
src[i] = c;
}
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+4; // 시작할 pc값 넣음
/*
오후 3:42 2006-05-30 현재 func+4로 초기값을 넣어야
IRQ 타이머에서 subs pc, lr, #4로 -4 한 것에 대응되어 태스크의 첫 시작이
잘된다. +4 안하면 태스크 첫 주소 -4인 test2()라면 test1()의 마지막 라인이
실행되는 불상사가 생긴다 -_-
*/
pTask->stack -= 13; // r0-r12 적음
pTask->stack -= 1; // CPSR flag기록
*pTask->stack = 0xD3; // 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* schedule(void* current_sp)
{
// 만일 task_start()없이 바로 task_switch()한다면
// if문이 필요하다. current_task없이 커널 main() 코드의 sp가 넘어오
기 때문.
// if(current_task)
current_task->stack = (int*)current_sp;
//pTask = select_task();
current_task = current_task->next;
return current_task->stack;
}
void init_timer0(void);
void irq_enable(void);
void test1()
{
int i = 0;
//task_switch();
//irq_enable();
irq_enable();
while(1)
{
//task_switch();
i++;
}
}
void test2()
{
while(1)
{
//task_switch();
}
}
void test3()
{
int c = 0;
while(1)
{
c +=2;
//task_switch();
}
}
void test4()
{
int c = 0;
while(1)
{
c +=2;
//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);
}
---------------------------------------------------------------------------
--
int.s
EXPORT task_switch
EXPORT task_start
EXPORT need_schedule
IMPORT schedule
IMPORT irq_return
;Pre-defined constants
USERMODE EQU 0x10
FIQMODE EQU 0x11
IRQMODE EQU 0x12
SVCMODE EQU 0x13
ABORTMODE EQU 0x17
UNDEFMODE EQU 0x1b
MODEMASK EQU 0x1f
NOINT EQU 0xc0
;sharing stack
sp_irq EQU 0x33000000
lr_irq EQU 0x33000004
r0_tmp EQU 0x33000008
r1_tmp EQU 0x3300000C
PRESERVE8
AREA intr, CODE, READONLY
ENTRY
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}
task_start
mov sp, r0
; restore cpu context of new task
ldmfd sp!, {r0}
msr CPSR_cxsf, r0
ldmfd sp!, {r0-r12,pc}
need_schedule
; IRQ 모드에서 스케줄이 필요한 상황이다.
; lr에는 IRQ오기 전의 SVC모드의 pc가 들어 있다.
; 스케줄을 한 후 새로운 리턴 주소를
; subs pc, lr, #4 해서 SVC모드로 복원해야한다.
; 복원할 pc_svc를 =lr_irq메모리에 적어놓는다.
stmfd sp!, {r0}
; save lr_irq
ldr r0, =lr_irq
str lr, [r0]
ldmfd sp!, {r0}
; change to svc mode lr_irq는 저장해놨기때문에 마구쓴다.
mrs lr, CPSR
bic lr, lr, #0x1f
orr lr, lr, #SVCMODE
msr cpsr_cxsf,lr
; SVC모드 먼저 현재 task의 lr에 임시로 =lr_irq값을 넣는다.
; save cpu context of old task
stmfd sp!, {r0}
ldr r0, =lr_irq
ldr lr, [r0] ; old_task.lr <= MEM_lr_irq
ldmfd sp!, {r0}
; 현재 task를 스택에 저장 이때 task.lr에 다음 시작 pc들어있음
stmfd sp!, {lr} ; save old task pc
stmfd sp!, {r0-r12}
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_cxsf, r0
ldmfd sp!, {r0-r12,lr}
; 이번에 선택된 task.lr 복원 주소를 =lr_irq메모리에 씀
; save new task return address
stmfd sp!, {r0}
; save lr_irq
ldr r0, =lr_irq
str lr, [r0] ; MEM_lr_irq <= new_task.lr_svc
ldmfd sp!, {r0}
; 인터럽트 복원을 위해 IRQ 모드로 돌아가자.(lr_svc 저장되었음마구씀)
; change to irq mode
mrs lr, CPSR
bic lr, lr, #0x1f
orr lr, lr, #IRQMODE
msr cpsr_cxsf,lr
; 복원 lr_irq 값을 새로운 태스크 pc인 =lr_irq값으로 바꾼다.
; get new task return address
stmfd sp!, {r0}
; load lr_irq
ldr r0, =lr_irq
ldr lr, [r0] ; lr_irq <= MEM_lr_irq
ldmfd sp!, {r0}
subs pc, lr, #4
END
---------------------------------------------------------------------------
--
MEMORY_MAP 0x0
{
BOOT 0x0
{
boot.o (boot, +First)
* (+RO, +RW, +ZI)
}
KERNEL 0x30003000
{
main.o (+RO, +RW, +ZI)
int.o (+RO, +RW, +ZI)
timer.o (+RO, +RW, +ZI)
interrupt.o (+RO, +RW, +ZI)
}
}
---------------------------------------------------------------------------
--
interrupt.c
#include "smdk2440.h"
#define IRQMODE 0x12
#define SVCMODE 0x13
unsigned int g_timeslice = 0;
int isr_timer0()
{
if( g_timeslice > 10)
{
g_timeslice = 0;
return 1;
}
g_timeslice++;
return 0;
}
int irq_handler() // return true is 'need scheduling'
{
int need_schedule = 0;
if( SRCPND & INT_TIMER0 )
{
need_schedule = isr_timer0();
}
SRCPND = INT_TIMER0; //Clear pending bit
INTPND = INT_TIMER0;
INTPND; //Prevent an double interrupt pending
return need_schedule;
}
void irq_enable()
{
int my_cpsr;
g_timeslice = 0;
__asm
{
mrs my_cpsr, CPSR
ORR my_cpsr, my_cpsr, #0x80 /* set IRQ disable bit flag
*/
BIC my_cpsr, my_cpsr, 1, LSL #7
msr CPSR_c, my_cpsr
}
}
-------------------------------------------------------------------------
timer.c
/*
by KyungMin Cho
bro( bro@shinbiro.com )
http://neri.cafe24.com
s3c2440 PWM Timer
*/
#include "smdk2440.h"
void init_timer0()
{
//(1/(PCLK/(Prescaler+1)/divider) * count(Max 65535) = Timer clock
(Frequency)
//(1/(50.8MHz/1/2)) * 2000 = 0.0787 msec ( 12.7 KHz)
//(1/(50.8MHz/1/2)) * 4000 = 0.1575 msec ( 6.35 KHz)
//(1/(50.8MHz/1/2)) * 5000 = 0.1969 msec ( 5.080 KHz)
//(1/(50.8MHz/1/2)) * 10000 = 0.3937 msec ( 2.54 KHz)
//(1/(50.8MHz/8/16)) * 2000 = 5.039 msec ( 198.45 Hz)
//(1/(50.8MHz/8/16)) * 4000 = 10.079 msec ( 99.216 KHz)
//(1/(50.8MHz/8/16)) * 5000 = 12.598 msec ( 79.38 Hz)
//(1/(50.8MHz/8/16)) * 10000 = 25.197 msec ( 39.687 Hz)
// TCFG0 = 7; // timer0 Prescaler 8
TCFG0 = 100; // timer0 Prescaler 8
TCFG1 = 3; // 1/16 divide
TCNTB0 = 1000; // down-counter init value, = 0 irq!
TCMPB0 = 500; // for Pulse Width Modulation(PWM)
// TCNTB0 = 10000; // down-counter init value, = 0 irq!
// TCMPB0 = 5000; // for Pulse Width Modulation(PWM)
//(1/(50.8MHz/8/16)) * 2000 = 5.039 msec ( 198.45 Hz)
TCON = 1<<1; // manual update
TCON = 0x09; // interval, start
INTMSK = ~INT_TIMER0; // Timer0 interrupt bitmask clear
}
----------------------------------------------------------------------
smdk2440.h
#ifndef __SMDK2440_H__
#define __SMDK2440_H__
////////////////////////////////////////////////////////////////
// interrupt
////////////////////////////////////////////////////////////////
#define INT_BASE (unsigned long*)0x4A000000
// source pending register 1=requsted
#define SRCPND *(INT_BASE+0)
#define INT_TIMER0 (0x1<<10)
// 0=irq 1=fiq
#define INTMOD *(INT_BASE+0x4/4)
// 0=available 1=masked
#define INTMSK *(INT_BASE+0x8/4)
// 0=interrupt has not been requsted 1=opposite
#define INTPND *(INT_BASE+0x10/4)
// 어떤 INTPND의 IRQ 인터럽트가 발생되었는지 체크
#define INTOFFSET *(INT_BASE+0x14/4)
#define INTOFFSET_TIMER0 10
///////////////////////////////////////////////////////////////
// Timer
///////////////////////////////////////////////////////////////
#define TIMER_BASE ((unsigned long*)0x51000000)
// Prescaler0 [7:0] for Timer 0, 1
// Prescaler1 [15:8] for Timer 2,3,4
// Timer input clock Frequency = PCLK / {prescaler value+1} / {divider
value}
// {prescaler value} = 0~255
// {divider value} = 2, 4, 8, 16
#define TCFG0 *(TIMER_BASE+0)
// DMA mode [23:20] 0000=all interrupt 0001=timer0 ~ 0101=timer4
// MUX0 [3:0] 0000=1/2 0001=1/4 0010=1/8 0011=1/16 01xx=External_TCLK0 for
timer0
#define TCFG1 *(TIMER_BASE+1)
// TCON Timer Control
// Timer0 start/stop [0] 0=stop 1=start, manual update[1] 0=no 1=update,
reload[3] 0=oneshot 1=interval
// Timer1 start/stop [8] ~ ...
#define TCON *(TIMER_BASE+2)
// timer0 count buffer and compare [15:0]
#define TCNTB0 *(TIMER_BASE+0xC/4)
#define TCMPB0 *(TIMER_BASE+0x10/4)
// timer0 count observation register [15:0] ReadOnly
#define TCNTO0 *(TIMER_BASE+0x14/4)
#endif //__SMDK2440_H__
-------------------------------------------------------------------------
flash.cmm
// Simple & Fast Flash program macro file
// FLASH Programming Script file for Lauterbach JTAG/ICD TRACE32.
SYSTEM.RESET
SYSTEM.CPU ARM920T
SYSTEM.UP
DO C:\T32\Init.cmm
FLASH.RESET
FLASH.CREATE 1. 0x00000000--0xffff 0x2000 target WORD
FLASH.CREATE 1. 0x10000--0x3effff 0x10000 target WORD
FLASH.CREATE 1. 0x3f0000--0x3fffff 0x2000 target WORD
FLASH.TARGET 0x30000000++0xfff 0x30001000++0xfff C:\T32
\demo\arm\FLASH\WORD\AM29F100.BIN
FLASH.ERASE ALL
FLASH.PROGRAM ALL
DATA.LOAD.ELF kernel.axf /WORD
FLASH.PROGRAM OFF
ENDDO
------------------------------------------------------------------
빌드 과정
armasm boot.s -G -o boot.o
armasm int.s -G -o int.o
armcc -c main.c -debug -g -o main.o
armcc -c timer.c -debug -g -o timer.o
armcc -c interrupt.c -debug -g -o interrupt.o
armlink boot.o main.o int.o timer.o interrupt.o -map -noremove -scatter
memory.map -entry vector -o kernel.axf
pause
이번에는 타이머를 잡아서 타이머 인터럽트 발생시 스케줄링을 일으켜서
선점형 라운드 로빈 스케줄러를 만들었다.
지금 내가 짠 코드와 암 아키텍처 상의 사소한 문제가 있는지, create_task시
첫 함수 시작 주소 + 4의 코드로 처음에 태스크 스택을 세팅하게 바꾸었다.
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+4; // 시작할 pc값 넣음
/*
오후 3:42 2006-05-30 현재 func+4로 초기값을 넣어야
IRQ 타이머에서 subs pc, lr, #4로 -4 한 것에 대응되어 태스크의 첫 시작이
잘된다. +4 안하면 태스크 첫 주소 -4인 test2()라면 test1()의 마지막 라인이
실행되는 불상사가 생긴다 -_-
*/
이렇게 시작 함수+4 위치를 기억하게 한 후, IRQ 타이머 스케줄링 발생시
int.s의 need_schedule가 호출되게 되는데,
이 부분에서 irq 모드에서 svc 모드 태스크로 복원해야하는데, 스케줄링을
시켜서 원래 복원시키려는 태스크가 아닌 다른 태스크로 복원하게 하는
트릭을 작성했다. 그러기 위해서 irq 모드의 lr을(복원하려는 주소값이 담김)
lr_irq을 기억하기 위한 메모리를 따로 정적으로 두었다...;
lr_irq EQU 0x33000004
그런 후, irq모드의 lr을 이 lr_irq 메모리에 넣고, svc모드로 변환후,
현재 태스크를 저장하고 (이 때 현재 태스크의 마지막 실행 주소를 lr_irq메모리
에서
얻어와서 세팅함), 스케줄링 후 다음 태스크를 선택 후, 다음 태스크의
시작 주소를 lr_irq 메모리에 저장하고, IRQ모드로 다시 돌아와, lr_irq메모리를
읽어와서 irq 모드의 lr에 재설정하여 다음 태스크로 복원하도록 하였다.
need_schedule
; IRQ 모드에서 스케줄이 필요한 상황이다.
; lr에는 IRQ오기 전의 SVC모드의 pc가 들어 있다.
; 스케줄을 한 후 새로운 리턴 주소를
; subs pc, lr, #4 해서 SVC모드로 복원해야한다.
; 복원할 pc_svc를 =lr_irq메모리에 적어놓는다.
stmfd sp!, {r0}
; save lr_irq
ldr r0, =lr_irq
str lr, [r0]
ldmfd sp!, {r0}
; change to svc mode lr_irq는 저장해놨기때문에 마구쓴다.
mrs lr, CPSR
bic lr, lr, #0x1f
orr lr, lr, #SVCMODE
msr cpsr_cxsf,lr
; SVC모드 먼저 현재 task의 lr에 임시로 =lr_irq값을 넣는다.
; save cpu context of old task
stmfd sp!, {r0}
ldr r0, =lr_irq
ldr lr, [r0] ; old_task.lr <= MEM_lr_irq
ldmfd sp!, {r0}
; 현재 task를 스택에 저장 이때 task.lr에 다음 시작 pc들어있음
stmfd sp!, {lr} ; save old task pc
stmfd sp!, {r0-r12}
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_cxsf, r0
ldmfd sp!, {r0-r12,lr}
; 이번에 선택된 task.lr 복원 주소를 =lr_irq메모리에 씀
; save new task return address
stmfd sp!, {r0}
; save lr_irq
ldr r0, =lr_irq
str lr, [r0] ; MEM_lr_irq <= new_task.lr_svc
ldmfd sp!, {r0}
; 인터럽트 복원을 위해 IRQ 모드로 돌아가자.(lr_svc 저장되었음마구씀)
; change to irq mode
mrs lr, CPSR
bic lr, lr, #0x1f
orr lr, lr, #IRQMODE
msr cpsr_cxsf,lr
; 복원 lr_irq 값을 새로운 태스크 pc인 =lr_irq값으로 바꾼다.
; get new task return address
stmfd sp!, {r0}
; load lr_irq
ldr r0, =lr_irq
ldr lr, [r0] ; lr_irq <= MEM_lr_irq
ldmfd sp!, {r0}
subs pc, lr, #4
END
timer 상에서 task switch하기 on smdk2440 board
조경민 bro@shinbiro.com
http://neri.cafe24.com
============================================================
IRQ 를 위한 스택을 잡아주었다.
; IRQ 스택 초기화
mrs r0, CPSR
bic r0, r0, #0x1f
orr r0, r0, #IRQMODE | NOINT
msr cpsr_cxsf,r0
mov sp, #0x32000000
; SVC 스택 초기화
mrs r0, CPSR
bic r0, r0, #0x1f
orr r0, r0, #SVCMODE | NOINT
msr cpsr_cxsf,r0
mov sp, #0x31000000
irq 가 오면 irq_handler c코드로 뜨게 하고, irq_handler에서 리턴이 1이면
스케줄링을 필요로 한다는 개념으로 접근했다.
irq
; IRQ shadow reg: r13, r14, spsr
; r14_svc
; save cpu context of old task
stmfd sp!, {r0-r12,lr}
; 수동 스케줄링 중에 갑자기 interrupt가 들어와서
; r14를 0으로 바꾸는 일이 없도록
mrs r0, CPSR
orr r0, r0, #0x80
msr CPSR_c, r0
bl irq_handler
cmp r0, #1 ; need schdule
ldmfd sp!, {r0-r12,lr}
bne irq_return
b need_schedule
irq_return
subs pc, lr, #4
타이머나 irq 처리 루틴을 위한 파일이 생겨서 memory.map도 추가되었다.
MEMORY_MAP 0x0
{
BOOT 0x0
{
boot.o (boot, +First)
* (+RO, +RW, +ZI)
}
KERNEL 0x30003000
{
main.o (+RO, +RW, +ZI)
int.o (+RO, +RW, +ZI)
timer.o (+RO, +RW, +ZI)
interrupt.o (+RO, +RW, +ZI)
}
}
소스가 좀 많이 늘어났다. 이제 ADS 1.2의 프로젝트 관리 툴을 사용해야
할 때가 온것 같다 ㅡ,.ㅡ;
;=====================================================
; boot test
; by bro, bro@shinbiro.com 오전 10:23 2006-04-26
;
; armasm main.s -o main.o
; armlink main.o --ro-base 0x0 -o main.axf
;=====================================================
GET option.inc
GET memcfg.inc
GET 2440addr.inc
; main.c의 커널 main 함수 심볼 쓰기 위해서
IMPORT main
IMPORT ||Image$$KERNEL$$ZI$$Limit||
EXPORT __user_initial_stackheap
EXPORT vector
EXPORT irq_return
; interrupt.c의 함수
IMPORT irq_handler
IMPORT need_schedule
; KERNEL region 복사를 위해 심볼 사용
IMPORT ||Image$$KERNEL$$Base||
IMPORT ||Image$$KERNEL$$Length||
IMPORT ||Load$$KERNEL$$Base||
;Pre-defined constants
USERMODE EQU 0x10
FIQMODE EQU 0x11
IRQMODE EQU 0x12
SVCMODE EQU 0x13
ABORTMODE EQU 0x17
UNDEFMODE EQU 0x1b
MODEMASK EQU 0x1f
NOINT EQU 0xc0
PRESERVE8
AREA boot, CODE, READONLY
ENTRY
vector ; 인터럽트 벡터 테이블
b reset ; 주소 0번지에 reset 인터럽트가 오면 바로
reset레이블로 점프
b . ; undefined_instruction
b . ; software_interrupt
b . ; prefetch_abort
b . ; data_abort
b . ; not_used
b irq ; IRQ
b . ; FIQ
irq
; IRQ shadow reg: r13, r14, spsr
; r14_svc
; save cpu context of old task
stmfd sp!, {r0-r12,lr}
; 수동 스케줄링 중에 갑자기 interrupt가 들어와서
; r14를 0으로 바꾸는 일이 없도록
mrs r0, CPSR
orr r0, r0, #0x80
msr CPSR_c, r0
bl irq_handler
cmp r0, #1 ; need schdule
ldmfd sp!, {r0-r12,lr}
bne irq_return
b need_schedule
irq_return
;stmfd sp!, {r0}
;; 다시 인터럽트 살려주고..
;mrs r0, cpsr
;bic r0, r0, #0x80
;msr cpsr_c, r0
;ldmfd sp!, {r0}
subs pc, lr, #4
reset
; 현재 모드에서 IRQ|FIQ를 끈다.
mrs r0, CPSR
bic r0, r0, #0x1f
orr r0, r0, #(0x13 | 0xc0) ; 0x13(0001 0011) | 0xC0 (1101 0000)
= 0xD3(1101 0011)
msr CPSR_cxsf, r0
; IRQ 스택 초기화
mrs r0, CPSR
bic r0, r0, #0x1f
orr r0, r0, #IRQMODE | NOINT
msr cpsr_cxsf,r0
mov sp, #0x32000000
; SVC 스택 초기화
mrs r0, CPSR
bic r0, r0, #0x1f
orr r0, r0, #SVCMODE | NOINT
msr cpsr_cxsf,r0
mov sp, #0x31000000
; 메모리 초기화
bl SDRAM_init
; 커널 복사 (memory.map에서 커널영역은 0x10000에서 하겠다고 했으니
복사해야함;)
bl copy_kernel
ldr pc, =main
b end
__user_initial_stackheap
LDR r0, =||Image$$KERNEL$$ZI$$Limit||
MOV pc, lr
copy_kernel
ldr r0, =||Load$$KERNEL$$Base|| ; source (FLASH ROM같은 영역
)
ldr r1, =||Image$$KERNEL$$Base|| ; destination (SDRAM 같은 영
역)
ldr r2, =||Image$$KERNEL$$Length|| ; kernel length
mov r3, #0 ; counter
loop
ldr r4, [r0], #4
str r4, [r1], #4
add r3, r3, #4
cmp r3, r2
bne loop
mov pc, lr
Clock_Init ; 버스 클럭 초기화
;To reduce PLL lock time, adjust the LOCKTIME register.
ldr r0,=LOCKTIME
ldr r1,=0xffffff
str r1,[r0]
[ PLL_ON_START
; Added for confirm clock divide. for 2440.
; Setting value Fclk:Hclk:Pclk
ldr r0,=CLKDIVN
ldr r1,=CLKDIV_VAL ; 0=1:1:1, 1=1:1:2, 2=1:2:2,
3=1:2:4, 4=1:4:4, 5=1:4:8, 6=1:3:3, 7=1:3:6.
str r1,[r0]
;Configure UPLL
ldr r0,=UPLLCON
ldr r1,=((U_MDIV<<12)+(U_PDIV<<4)+U_SDIV)
str r1,[r0]
nop ; Caution: After UPLL setting, at least 7-clocks delay must
be inserted for setting hardware be completed.
nop
nop
nop
nop
nop
nop
;Configure MPLL
ldr r0,=MPLLCON
ldr r1,=((M_MDIV<<12)+(M_PDIV<<4)+M_SDIV) ;Fin=16.9344MHz
str r1,[r0]
]
SDRAM_init ; 0x30000000 SDRAM 메모리 초기화
;Set memory control registers
ldr r0,=SMRDATA
ldr r1,=BWSCON ;BWSCON Address
add r2, r0, #52 ;End address of SMRDATA
0
ldr r3, [r0], #4
str r3, [r1], #4
cmp r2, r0
bne %B0
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;; When EINT0 is pressed, Clear SDRAM
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;
; check if EIN0 button is pressed
ldr r0,=GPFCON
ldr r1,=0x0
str r1,[r0]
ldr r0,=GPFUP
ldr r1,=0xff
str r1,[r0]
ldr r1,=GPFDAT
ldr r0,[r1]
bic r0,r0,#(0x1e<<1) ; bit clear
tst r0,#0x1
bne %F1
; Clear SDRAM Start
ldr r0,=GPFCON
ldr r1,=0x55aa
str r1,[r0]
; ldr r0,=GPFUP
; ldr r1,=0xff
; str r1,[r0]
ldr r0,=GPFDAT
ldr r1,=0x0
str r1,[r0] ;LED=****
mov r1,#0
mov r2,#0
mov r3,#0
mov r4,#0
mov r5,#0
mov r6,#0
mov r7,#0
mov r8,#0
ldr r9,=0x4000000 ;64MB
ldr r0,=0x30000000
0
stmia r0!,{r1-r8}
subs r9,r9,#32
bne %B0
;Clear SDRAM End
1
mov pc, lr
SMRDATA DATA
; Memory configuration should be optimized for best performance
; The following parameter is not optimized.
; Memory access cycle parameter strategy
; 1) The memory settings is safe parameters even at HCLK=75Mhz.
; 2) SDRAM refresh period is for HCLK<=75Mhz.
DCD (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+
(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
DCD ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+
(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC)) ;GCS0
DCD ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+
(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC)) ;GCS1
DCD ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+
(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC)) ;GCS2
DCD ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+
(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC)) ;GCS3
DCD ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+
(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC)) ;GCS4
DCD ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+
(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC)) ;GCS5
DCD ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN)) ;GCS6
DCD ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN)) ;GCS7
DCD ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Tsrc<<18)+(Tchr<<16)
+REFCNT)
DCD 0x32 ;SCLK power saving mode, BANKSIZE 128M/128M
DCD 0x30 ;MRSR6 CL=3clk
DCD 0x30 ;MRSR7 CL=3clk
ALIGN
end
b .
END
---------------------------------------------------------------------------
--
main.c
#define TASK_STACK_SIZE 0x200
#define TASK_NUMBER 4
typedef struct _task
{
char base[TASK_STACK_SIZE];
int* stack;
int func;
struct _task* next;
}task;
task g_Tasks[TASK_NUMBER];
task* current_task;
void task_switch(void); // in int.s
void task_start(int task_sp);
void memset( char* src, int c, int size )
{
int i;
for( i = 0; i < size; i ++)
src[i] = c;
}
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+4; // 시작할 pc값 넣음
/*
오후 3:42 2006-05-30 현재 func+4로 초기값을 넣어야
IRQ 타이머에서 subs pc, lr, #4로 -4 한 것에 대응되어 태스크의 첫 시작이
잘된다. +4 안하면 태스크 첫 주소 -4인 test2()라면 test1()의 마지막 라인이
실행되는 불상사가 생긴다 -_-
*/
pTask->stack -= 13; // r0-r12 적음
pTask->stack -= 1; // CPSR flag기록
*pTask->stack = 0xD3; // 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* schedule(void* current_sp)
{
// 만일 task_start()없이 바로 task_switch()한다면
// if문이 필요하다. current_task없이 커널 main() 코드의 sp가 넘어오
기 때문.
// if(current_task)
current_task->stack = (int*)current_sp;
//pTask = select_task();
current_task = current_task->next;
return current_task->stack;
}
void init_timer0(void);
void irq_enable(void);
void test1()
{
int i = 0;
//task_switch();
//irq_enable();
irq_enable();
while(1)
{
//task_switch();
i++;
}
}
void test2()
{
while(1)
{
//task_switch();
}
}
void test3()
{
int c = 0;
while(1)
{
c +=2;
//task_switch();
}
}
void test4()
{
int c = 0;
while(1)
{
c +=2;
//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);
}
---------------------------------------------------------------------------
--
int.s
EXPORT task_switch
EXPORT task_start
EXPORT need_schedule
IMPORT schedule
IMPORT irq_return
;Pre-defined constants
USERMODE EQU 0x10
FIQMODE EQU 0x11
IRQMODE EQU 0x12
SVCMODE EQU 0x13
ABORTMODE EQU 0x17
UNDEFMODE EQU 0x1b
MODEMASK EQU 0x1f
NOINT EQU 0xc0
;sharing stack
sp_irq EQU 0x33000000
lr_irq EQU 0x33000004
r0_tmp EQU 0x33000008
r1_tmp EQU 0x3300000C
PRESERVE8
AREA intr, CODE, READONLY
ENTRY
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}
task_start
mov sp, r0
; restore cpu context of new task
ldmfd sp!, {r0}
msr CPSR_cxsf, r0
ldmfd sp!, {r0-r12,pc}
need_schedule
; IRQ 모드에서 스케줄이 필요한 상황이다.
; lr에는 IRQ오기 전의 SVC모드의 pc가 들어 있다.
; 스케줄을 한 후 새로운 리턴 주소를
; subs pc, lr, #4 해서 SVC모드로 복원해야한다.
; 복원할 pc_svc를 =lr_irq메모리에 적어놓는다.
stmfd sp!, {r0}
; save lr_irq
ldr r0, =lr_irq
str lr, [r0]
ldmfd sp!, {r0}
; change to svc mode lr_irq는 저장해놨기때문에 마구쓴다.
mrs lr, CPSR
bic lr, lr, #0x1f
orr lr, lr, #SVCMODE
msr cpsr_cxsf,lr
; SVC모드 먼저 현재 task의 lr에 임시로 =lr_irq값을 넣는다.
; save cpu context of old task
stmfd sp!, {r0}
ldr r0, =lr_irq
ldr lr, [r0] ; old_task.lr <= MEM_lr_irq
ldmfd sp!, {r0}
; 현재 task를 스택에 저장 이때 task.lr에 다음 시작 pc들어있음
stmfd sp!, {lr} ; save old task pc
stmfd sp!, {r0-r12}
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_cxsf, r0
ldmfd sp!, {r0-r12,lr}
; 이번에 선택된 task.lr 복원 주소를 =lr_irq메모리에 씀
; save new task return address
stmfd sp!, {r0}
; save lr_irq
ldr r0, =lr_irq
str lr, [r0] ; MEM_lr_irq <= new_task.lr_svc
ldmfd sp!, {r0}
; 인터럽트 복원을 위해 IRQ 모드로 돌아가자.(lr_svc 저장되었음마구씀)
; change to irq mode
mrs lr, CPSR
bic lr, lr, #0x1f
orr lr, lr, #IRQMODE
msr cpsr_cxsf,lr
; 복원 lr_irq 값을 새로운 태스크 pc인 =lr_irq값으로 바꾼다.
; get new task return address
stmfd sp!, {r0}
; load lr_irq
ldr r0, =lr_irq
ldr lr, [r0] ; lr_irq <= MEM_lr_irq
ldmfd sp!, {r0}
subs pc, lr, #4
END
---------------------------------------------------------------------------
--
MEMORY_MAP 0x0
{
BOOT 0x0
{
boot.o (boot, +First)
* (+RO, +RW, +ZI)
}
KERNEL 0x30003000
{
main.o (+RO, +RW, +ZI)
int.o (+RO, +RW, +ZI)
timer.o (+RO, +RW, +ZI)
interrupt.o (+RO, +RW, +ZI)
}
}
---------------------------------------------------------------------------
--
interrupt.c
#include "smdk2440.h"
#define IRQMODE 0x12
#define SVCMODE 0x13
unsigned int g_timeslice = 0;
int isr_timer0()
{
if( g_timeslice > 10)
{
g_timeslice = 0;
return 1;
}
g_timeslice++;
return 0;
}
int irq_handler() // return true is 'need scheduling'
{
int need_schedule = 0;
if( SRCPND & INT_TIMER0 )
{
need_schedule = isr_timer0();
}
SRCPND = INT_TIMER0; //Clear pending bit
INTPND = INT_TIMER0;
INTPND; //Prevent an double interrupt pending
return need_schedule;
}
void irq_enable()
{
int my_cpsr;
g_timeslice = 0;
__asm
{
mrs my_cpsr, CPSR
ORR my_cpsr, my_cpsr, #0x80 /* set IRQ disable bit flag
*/
BIC my_cpsr, my_cpsr, 1, LSL #7
msr CPSR_c, my_cpsr
}
}
-------------------------------------------------------------------------
timer.c
/*
by KyungMin Cho
bro( bro@shinbiro.com )
http://neri.cafe24.com
s3c2440 PWM Timer
*/
#include "smdk2440.h"
void init_timer0()
{
//(1/(PCLK/(Prescaler+1)/divider) * count(Max 65535) = Timer clock
(Frequency)
//(1/(50.8MHz/1/2)) * 2000 = 0.0787 msec ( 12.7 KHz)
//(1/(50.8MHz/1/2)) * 4000 = 0.1575 msec ( 6.35 KHz)
//(1/(50.8MHz/1/2)) * 5000 = 0.1969 msec ( 5.080 KHz)
//(1/(50.8MHz/1/2)) * 10000 = 0.3937 msec ( 2.54 KHz)
//(1/(50.8MHz/8/16)) * 2000 = 5.039 msec ( 198.45 Hz)
//(1/(50.8MHz/8/16)) * 4000 = 10.079 msec ( 99.216 KHz)
//(1/(50.8MHz/8/16)) * 5000 = 12.598 msec ( 79.38 Hz)
//(1/(50.8MHz/8/16)) * 10000 = 25.197 msec ( 39.687 Hz)
// TCFG0 = 7; // timer0 Prescaler 8
TCFG0 = 100; // timer0 Prescaler 8
TCFG1 = 3; // 1/16 divide
TCNTB0 = 1000; // down-counter init value, = 0 irq!
TCMPB0 = 500; // for Pulse Width Modulation(PWM)
// TCNTB0 = 10000; // down-counter init value, = 0 irq!
// TCMPB0 = 5000; // for Pulse Width Modulation(PWM)
//(1/(50.8MHz/8/16)) * 2000 = 5.039 msec ( 198.45 Hz)
TCON = 1<<1; // manual update
TCON = 0x09; // interval, start
INTMSK = ~INT_TIMER0; // Timer0 interrupt bitmask clear
}
----------------------------------------------------------------------
smdk2440.h
#ifndef __SMDK2440_H__
#define __SMDK2440_H__
////////////////////////////////////////////////////////////////
// interrupt
////////////////////////////////////////////////////////////////
#define INT_BASE (unsigned long*)0x4A000000
// source pending register 1=requsted
#define SRCPND *(INT_BASE+0)
#define INT_TIMER0 (0x1<<10)
// 0=irq 1=fiq
#define INTMOD *(INT_BASE+0x4/4)
// 0=available 1=masked
#define INTMSK *(INT_BASE+0x8/4)
// 0=interrupt has not been requsted 1=opposite
#define INTPND *(INT_BASE+0x10/4)
// 어떤 INTPND의 IRQ 인터럽트가 발생되었는지 체크
#define INTOFFSET *(INT_BASE+0x14/4)
#define INTOFFSET_TIMER0 10
///////////////////////////////////////////////////////////////
// Timer
///////////////////////////////////////////////////////////////
#define TIMER_BASE ((unsigned long*)0x51000000)
// Prescaler0 [7:0] for Timer 0, 1
// Prescaler1 [15:8] for Timer 2,3,4
// Timer input clock Frequency = PCLK / {prescaler value+1} / {divider
value}
// {prescaler value} = 0~255
// {divider value} = 2, 4, 8, 16
#define TCFG0 *(TIMER_BASE+0)
// DMA mode [23:20] 0000=all interrupt 0001=timer0 ~ 0101=timer4
// MUX0 [3:0] 0000=1/2 0001=1/4 0010=1/8 0011=1/16 01xx=External_TCLK0 for
timer0
#define TCFG1 *(TIMER_BASE+1)
// TCON Timer Control
// Timer0 start/stop [0] 0=stop 1=start, manual update[1] 0=no 1=update,
reload[3] 0=oneshot 1=interval
// Timer1 start/stop [8] ~ ...
#define TCON *(TIMER_BASE+2)
// timer0 count buffer and compare [15:0]
#define TCNTB0 *(TIMER_BASE+0xC/4)
#define TCMPB0 *(TIMER_BASE+0x10/4)
// timer0 count observation register [15:0] ReadOnly
#define TCNTO0 *(TIMER_BASE+0x14/4)
#endif //__SMDK2440_H__
-------------------------------------------------------------------------
flash.cmm
// Simple & Fast Flash program macro file
// FLASH Programming Script file for Lauterbach JTAG/ICD TRACE32.
SYSTEM.RESET
SYSTEM.CPU ARM920T
SYSTEM.UP
DO C:\T32\Init.cmm
FLASH.RESET
FLASH.CREATE 1. 0x00000000--0xffff 0x2000 target WORD
FLASH.CREATE 1. 0x10000--0x3effff 0x10000 target WORD
FLASH.CREATE 1. 0x3f0000--0x3fffff 0x2000 target WORD
FLASH.TARGET 0x30000000++0xfff 0x30001000++0xfff C:\T32
\demo\arm\FLASH\WORD\AM29F100.BIN
FLASH.ERASE ALL
FLASH.PROGRAM ALL
DATA.LOAD.ELF kernel.axf /WORD
FLASH.PROGRAM OFF
ENDDO
------------------------------------------------------------------
빌드 과정
armasm boot.s -G -o boot.o
armasm int.s -G -o int.o
armcc -c main.c -debug -g -o main.o
armcc -c timer.c -debug -g -o timer.o
armcc -c interrupt.c -debug -g -o interrupt.o
armlink boot.o main.o int.o timer.o interrupt.o -map -noremove -scatter
memory.map -entry vector -o kernel.axf
pause
이번에는 타이머를 잡아서 타이머 인터럽트 발생시 스케줄링을 일으켜서
선점형 라운드 로빈 스케줄러를 만들었다.
지금 내가 짠 코드와 암 아키텍처 상의 사소한 문제가 있는지, create_task시
첫 함수 시작 주소 + 4의 코드로 처음에 태스크 스택을 세팅하게 바꾸었다.
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+4; // 시작할 pc값 넣음
/*
오후 3:42 2006-05-30 현재 func+4로 초기값을 넣어야
IRQ 타이머에서 subs pc, lr, #4로 -4 한 것에 대응되어 태스크의 첫 시작이
잘된다. +4 안하면 태스크 첫 주소 -4인 test2()라면 test1()의 마지막 라인이
실행되는 불상사가 생긴다 -_-
*/
이렇게 시작 함수+4 위치를 기억하게 한 후, IRQ 타이머 스케줄링 발생시
int.s의 need_schedule가 호출되게 되는데,
이 부분에서 irq 모드에서 svc 모드 태스크로 복원해야하는데, 스케줄링을
시켜서 원래 복원시키려는 태스크가 아닌 다른 태스크로 복원하게 하는
트릭을 작성했다. 그러기 위해서 irq 모드의 lr을(복원하려는 주소값이 담김)
lr_irq을 기억하기 위한 메모리를 따로 정적으로 두었다...;
lr_irq EQU 0x33000004
그런 후, irq모드의 lr을 이 lr_irq 메모리에 넣고, svc모드로 변환후,
현재 태스크를 저장하고 (이 때 현재 태스크의 마지막 실행 주소를 lr_irq메모리
에서
얻어와서 세팅함), 스케줄링 후 다음 태스크를 선택 후, 다음 태스크의
시작 주소를 lr_irq 메모리에 저장하고, IRQ모드로 다시 돌아와, lr_irq메모리를
읽어와서 irq 모드의 lr에 재설정하여 다음 태스크로 복원하도록 하였다.
need_schedule
; IRQ 모드에서 스케줄이 필요한 상황이다.
; lr에는 IRQ오기 전의 SVC모드의 pc가 들어 있다.
; 스케줄을 한 후 새로운 리턴 주소를
; subs pc, lr, #4 해서 SVC모드로 복원해야한다.
; 복원할 pc_svc를 =lr_irq메모리에 적어놓는다.
stmfd sp!, {r0}
; save lr_irq
ldr r0, =lr_irq
str lr, [r0]
ldmfd sp!, {r0}
; change to svc mode lr_irq는 저장해놨기때문에 마구쓴다.
mrs lr, CPSR
bic lr, lr, #0x1f
orr lr, lr, #SVCMODE
msr cpsr_cxsf,lr
; SVC모드 먼저 현재 task의 lr에 임시로 =lr_irq값을 넣는다.
; save cpu context of old task
stmfd sp!, {r0}
ldr r0, =lr_irq
ldr lr, [r0] ; old_task.lr <= MEM_lr_irq
ldmfd sp!, {r0}
; 현재 task를 스택에 저장 이때 task.lr에 다음 시작 pc들어있음
stmfd sp!, {lr} ; save old task pc
stmfd sp!, {r0-r12}
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_cxsf, r0
ldmfd sp!, {r0-r12,lr}
; 이번에 선택된 task.lr 복원 주소를 =lr_irq메모리에 씀
; save new task return address
stmfd sp!, {r0}
; save lr_irq
ldr r0, =lr_irq
str lr, [r0] ; MEM_lr_irq <= new_task.lr_svc
ldmfd sp!, {r0}
; 인터럽트 복원을 위해 IRQ 모드로 돌아가자.(lr_svc 저장되었음마구씀)
; change to irq mode
mrs lr, CPSR
bic lr, lr, #0x1f
orr lr, lr, #IRQMODE
msr cpsr_cxsf,lr
; 복원 lr_irq 값을 새로운 태스크 pc인 =lr_irq값으로 바꾼다.
; get new task return address
stmfd sp!, {r0}
; load lr_irq
ldr r0, =lr_irq
ldr lr, [r0] ; lr_irq <= MEM_lr_irq
ldmfd sp!, {r0}
subs pc, lr, #4
END
'KB > embbeded sw' 카테고리의 다른 글
arm-9: preemtive switch 완성 (0) | 2006.06.01 |
---|---|
arm-8: boot.s에서 사용한 smdk2440을 위한 코드 (0) | 2006.05.30 |
Reconfigurable Architecture (0) | 2006.05.26 |
Multi-Processors.... amdahl's law (0) | 2006.05.26 |
[idea] starcore dsp 상의 os 커널 서비스 스레드의 VLES 활용 동시 처리 (0) | 2006.05.08 |