Arm Cortex-M System Call Part 1

Discuss svc handler

show syscall.h

#ifndef __SYSCALL_H
#define __SYSCALL_H

#include <stdint.h>

#define SYSCALL_SLEEP 0
#define SYSCALL_SERIAL_WRITE 1

int syscall(void *arg);

/* called by syscall handler */
int do_sleep(int val);
int do_serial_write(char *msg);

/* Called by user process */
int sleep(int delay);
int serial_write(char *msg);


#endif /* __SYSCALL_H */

Show syscall.c

#include <stdlib.h>
#include <syscall.h>
#include <task.h>

__attribute__((interrupt)) void svc_handler(void)
{
        /* We want access to the process stack */
        /* And set it as the argument to the syscall func */
        asm volatile("mrs r0, psp");

        /* Call the syscall func */
        asm volatile("bl syscall");
        
        /* We now want to push the return val onto the process stack */
        asm volatile("mrs r1, psp");
        /* Now store the new r0 */
        asm volatile("str r0, [r1]");   

        /* Return from exception */
        asm volatile("mov lr, #0xfffffffd");
}

int syscall(void *arg)
{
        int ret = -1;
        uint32_t *args = (uint32_t *) arg;
        
        /*      
         * To get the SVC number we need examine the svc
         * instruction that was called, this is at saved $pc-2
         */

        uint8_t svc_num = 0;
        uint8_t *svc_pc = (uint8_t *) args[6];

        svc_pc -= 2;
        svc_num = *svc_pc;

        switch(svc_num) {
                case SYSCALL_SLEEP:
                        ret = do_sleep(args[0]);
                break;
                case SYSCALL_SERIAL_WRITE:
                        ret = do_serial_write((char *) args[0]);
        }

        return ret;
}

/* called by syscall handler */
int do_sleep(int delay)
{
        puts("syscall:sleep\n");
        return 0;
}

int do_serial_write(char *msg)
{
        puts("MSG: ");
        puts(msg);
        puts("\n");
        return 0;
}

/* Called by user task */
int sleep(int delay)
{
        int ret = 0;
        asm volatile("mov r0, %[d]" :: [d] "r" (delay));
        asm volatile("svc #0");
        /* Preserve the return value */
        asm volatile("mov %[r], r0" : [r] "=r" (ret));

        return ret;
}

int serial_write(char *msg)
{
        int ret = 0;
        asm volatile("mov r0, %[d]" :: [d] "r" (msg));
        asm volatile("svc #1");
        /* Preserve the return value */
        asm volatile("mov %[r], r0" : [r] "=r" (ret));

        return ret;
}

show vector_table.s

.syntax unified

.cpu cortex-m3
.thumb

/* Vector Table */

.word 0x20001000        /* Stack Pointer */
.word reset_handler    /* 1 Reset Handler */
.word default_handler   /* 2 NMI */
.word default_handler   /* 3 Hard Fault */
.word default_handler   /* 4 Mem Management Fault */
.word default_handler   /* 5 Bus Fault */
.word usage_handler     /* 6 Usage Fault */
.word default_handler   /* 7 RESERVED */
.word default_handler   /* 8 RESERVED */
.word default_handler   /* 9 RESERVED*/
.word default_handler   /* 10 RESERVED */
.word svc_handler       /* 11 SVCall */
.word default_handler   /* 12 Debug Monitor */
.word default_handler   /* 13 RESERVED */
.word task_switch       /* 14 PendSV */
.word timer_func        /* 15 SysTick */

.global reset_handler
.type reset_handler, STT_FUNC
.thumb_func
reset_handler:
    bl main
halt:
    bl halt

.global default_handler
.type default_handler, STT_FUNC
.thumb_func
default_handler:
    bl default_handler

.global usage_handler
.type usage_handler, STT_FUNC
.thumb_func
usage_handler:
    bl usage_handler

show main.c

#include <stdint.h>
#include <stdio.h>
#include <task.h>


/* QEMU uses a 12Mhz system clock */
/* So 0x00b71b00 in theory would be used */
/* However the following value is more accurate on my system YMMV */

#define SYSTICKS_PER_SECOND ((uint32_t) 0x00c00000)
 
#define TIMER_RELOAD (SYSTICKS_PER_SECOND / 100)

void task_1(void)
{
    serial_write("Hello from task_1");
    while(1) { ; }
}

void task_2(void)
{
    serial_write("Hello from task_2");
    while(1) { ; }
}

int main() 
{
    task_init();
    task_create(task_1, 0);
    task_create(task_2, 1);
    
    timer_reload(TIMER_RELOAD);
    timer_start();

    while(1) { ; }

    return 0;    
}

Change Makefile

TARGET=startup.elf
OBJ := vector_table.o main.o serial.o timer.o task.o syscall.o
LDSCRIPT=startup.ld
CROSS=arm-none-eabi-
CFLAGS := -I. -nostdlib -g -mcpu=cortex-m3 -mthumb
LDFLAGS := -g -o
ASMFLAGS := -g -o

TTY := $(shell tty)
MACH := lm3s6965evb
CDEV := tty,id=any,path=$(TTY)

QEMU_FLAGS := -S -gdb tcp::1234 -nographic -kernel

all: $(TARGET)

%.o: %.s
    $(CROSS)as $(ASMFLAGS) $@ $<

%.o: %.c
    $(CROSS)gcc $(CFLAGS) -c $<

$(TARGET): $(OBJ) $(LDSCRIPT)
    $(CROSS)ld $(LDFLAGS) $@ -T $(LDSCRIPT) $(OBJ)

clean:
    $(RM) *.o *.elf

run: $(TARGET)
    qemu-system-arm -machine $(MACH) $(QEMU_FLAGS) $< -chardev $(CDEV)    

debug: $(TARGET)
    $(CROSS)gdb -x gdbinit

use make run and make debug to show output

 

 

Advertisements