Showing posts with label Angel SWI. Show all posts
Showing posts with label Angel SWI. Show all posts

Tuesday, July 11, 2006

Newlib Angel SWI handing in C

Previously, I have shown how to implement Angel SWI write command so that printf and friends would work on a UART. That implementation was rather simplistic because the UART writing loop will hold the caller from doing any other useful stuff. What I want to do now is to make use of FIFO in the UART and also interrupt it only when the FIFO is empty enough.

But first, I want to change the SWI handling from using ARM assembly code into C because the implementation now has become more sophisticated. Before you continue reading, I think you should also check the thread I started in the GNUARM mailing list on this subject where there were a lot of insights that can be gathered from the contributors to the thread.

It seems like GCC supports C function that can act as the handler to the various exceptions that ARM can throw through the __attribute__ modifier. Below is an example of a SWI handler that would accept an Angel SWI call from newlib and see if it is a write command and write the bytes in the string to a specific location.

static int *out = (int *) 0x08000000;

int __attribute__((interrupt("SWI")))
handle_swi(int reason, void *args) {

int i, n, *a;
char *s;
if (reason != 5) return -1;
a = (int*) args;
s = (char *) a[1];
n = a[2];
for (i=0; i<n; ++i) *out = s[i];
return 0;
}

Note that for Angel SWI write to work, we need to return the number of bytes yet to be written. So zero is returned above because the function has written out all of the bytes. Now, if we link this function to the ARM vector address for SWI, it looks like it may work. But look closely at the generated assembly code for the function:

00000000 <handle_swi>:
0: cmp r0, #5 ; 0x5
4: stmdb sp!, {r0, r1, r2, r3, ip}
8: mvnne r0, #0 ; 0x0
c: beq 18 <handle_swi+0x18>
10: ldmia sp!, {r0, r1, r2, r3, ip}
14: movs pc, lr
18: ldr r0, [r1, #8]
1c: cmp r0, #0 ; 0x0
20: ldr r1, [r1, #4]
24: ble 44 <handle_swi+0x44>
28: mov r2, #0 ; 0x0
2c: mov ip, #134217728 ; 0x8000000
30: ldrb r3, [r2, r1]
34: add r2, r2, #1 ; 0x1
38: cmp r0, r2
3c: str r3, [ip]
40: bne 30 <handle_swi+0x30>
44: mov r0, #0 ; 0x0
48: b 10 <handle_swi+0x10>

Upon entry, r0 is among a few registers that is saved on the stack. Upon return, at addresses 0x10 and 0x14, register r0 and the other registers are restored. As such the return value, which is set at address 0x44, has been clobbered.

After some more reading on GCC function attributes, it appeared that attribute naked could be used, but we need to insert inline assembly code for the prologue and epilogue ourselves. Somebody in the GNUARM mailing list thread also suggested something along this line. That is, we would do something like this instead:

static int *out = (int *) 0x08000000;

int __attribute__((naked))
handle_swi(int reason, void *args) {

asm("stmdb sp!,{r4-r11,ip,lr}");
int r, i, n, *a;
char *s;
r = 0;
if (reason != 5) r = -1;
else {
a = (int*) args;
s = (char *) a[1];
n = a[2];
for (i=0; i<n; ++i) *out = s[i];
}
asm("ldmia sp!,{r4-r11,ip,lr};movs pc,lr");
return r;
}

So much for trying to avoid assembly code in SWI handler. I choose not to save r0 to r3 because they are allowed to be clobbered in a function called. But the rest of the registers need to be saved and restored because we won't know how the C code would generally use them.

At this point, I came to the conclusion that avoiding assembly code from SWI handler is rather difficult. Actually, we need to do more than just the epilogue and prologue code. So instead of sprinkling inline assembly code in the C function, I came up with a consolidated assembly code wrapper that does the necessary checking and other preparation before calling a straight C function to handle the SWI call.

__swi_handler:
stmdb sp!,{r4,lr}

/* see if SWI argument is 0x123456 */
ldr r4,[lr,#-4]
bic r4,r4,#0xff000000
sub r4,r4,#0x00120000
sub r4,r4,#0x00003400
subs r4,r4,#0x00000056

/* save SPSR so that we have SWI reentrancy */
mrs r4,spsr
stmdb sp!,{r4}

/* only call handler if SWI argument is 0x123456 */
bleq swi_handler

/* restore SPSR */
ldmia sp!,{r4}
msr spsr,r4

ldmia sp!,{r4,pc}^

Now we just need to make sure SWI vector address calls __swi_handler. And remove the attribute modifier from swi_handler function definition.

Actually, there are more to SWI handling than just Angel SWI as can be gathered from the GNUARM thread mentioned earlier. For example, someone pointed out that divide-by-zero error will cause a SWI call. We may want to handle this properly too.

Monday, May 08, 2006

newlib printf through UART on GNU ARM toolchain

To make function like printf and puts to work on the GNUARM toolchain, the Angel SWI handler for the AngelSWI_Reason_Write command needs to be implemented. This command is indicated through value 5 in register r0 when the Angel SWI call is made. Register r1 will point to an array of 3 32-bit values. The first is a file pointer which I will ignore because I do not do any other file operation except writing to stdout. The second is a pointer to the array of char's to print. The third contains the length of the string. Below is the code snippet for the SWI handler:
__swi_handler:
cmp r0,#5
bne .Ldone

/* skip file pointer and store char pointer in r0
and length into r1 */

ldr r0,[r1,#4]!
ldr r1,[r1,#4]

add r1,r1,r0 /* now r1 points to end of chars to print */

mov r2,#UARTA_BASE

.Lnext:
ldr r3,[r2,#LSR]
ands r3,r3,#LSR_THRE
beq .Lnext

ldrb r3,[r0],#1
str r3,[r2,#THR]

/* append CR if we see a NL */

cmp r3,#0x0A /* NL? */
moveq r3,#0x0D /* CR */
streq r3,[r2,#THR]

cmp r0,r1
blt .Lnext
mov r0,#0
.Ldone:
movs pc,lr
I am being a little bit sloppy above by not checking that the SWI instruction that triggers the call does in fact have a 0x123456 as its argument which is required for Angel SWI. Of course, the ARM exception vectors needs to be configured accordingly:
__vec_start__:
LDR PC, Reset_Addr
LDR PC, Undef_Addr
LDR PC, SWI_Addr
LDR PC, PAbt_Addr
LDR PC, DAbt_Addr
NOP
LDR PC, IRQ_Addr
LDR PC, FIQ_Addr

Reset_Addr: .word start
Undef_Addr: .word Undef_Handler
SWI_Addr: .word __swi_handler
PAbt_Addr: .word PAbt_Handler
DAbt_Addr: .word DAbt_Handler
.word 0
IRQ_Addr: .word IRQ_Handler
FIQ_Addr: .word FIQ_Handler
And the UART needs to be configured properly before use. I have the code below in my crt0.S to initialize the UART. You need to program the DLL and DLH (Divisor Latch registers) according to your UART clock and the desired baud rate:

  mov    r0,#UARTA_BASE
mov r1,#0x07
str r1,[r0,#FCR] /* reset and enable FIFOs */
mov r1,#0x00
str r1,[r0,#IER] /* no interrupt */
mov r1,#0x80
str r1,[r0,#LCR] /* to program divisor */
mov r1,#0x41 /* change this if clock change! */
str r1,[r0,#DLL]
mov r1,#0x00
str r1,[r0,#DLH]
mov r1,#0x03
str r1,[r0,#LCR] /* 8 bit, 1 stop bit */