gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

ASM > Calling ARM-IWRAM code from THUMB/ROM

#24013 - pan69 - Mon Jul 26, 2004 5:27 pm

Hi guys,

Have been messing all day, programming and searching this forum to figure out where I go wrong. I'm trying to call an ARM function located in IWRAM from my THUMB (ROM) code but the function doesn't seem to return.

My main THUMB code looks like this:

Code:

@
@ file: main.s
@

   .thumb_func
   .align
   .section .text

   .include "gba.inc"

   .global   AgbMain

AgbMain:
      bl      memCopy    @ <-- Where it goes wrong (read on)

   @ Switch to 240x160x15bpp mode.

      ldr   r0, =MODE_3
      ldr   r1, =BG2_ENABLE
      bl      setModeT

   @ Plot a green pixel in the center of the screen.

      ldr   r0, =0
      ldr   r1, =255
      ldr   r2, =0
      bl      rgb15T

      ldr   r1, =(240 / 2)
      ldr   r2, =(160 / 2)
      ldr   r3, =VRAM
      bl      setPixel3T

main:
      b      main

   .end


The first function called is the ARM-IWRAM memCopy function which is defined like this:

Code:

@
@ file: memCopy.s
@

   .arm
   .align
   .section .iwram, "ax", %progbits

   .include "gba.inc"

   .global memCopy
   .type memCopy, function

memCopy:
   @ Return to caller...

      bx      lr

   .end


Everything compiles/links fine (GAS from GNUARM 3.4.4). Now, when running this program it crashes. But, when I remove that first BL to memCopy the program runs just fine.

Also when I change the:

Code:

.section .iwram, "ax", %progbits


in the memCopy.s file to:

Code:

.section .text


The program runs fine. Meaning the BL to memCopy returns. I'm using crt0.S v1.28 and Linker Script v1.3 by Jeff Frohwein.

This is how I compile and link:

Code:

arm-elf-as -mcpu=arm7tdmi -mthumb-interwork main.s -o main.o
arm-elf-as -mcpu=arm7tdmi -mthumb-interwork setModeT.s -o setMode.o
arm-elf-as -mcpu=arm7tdmi -mthumb-interwork setPixel3T.s -o setPixel.o
arm-elf-as -mcpu=arm7tdmi -mthumb-interwork rgb15T.s -o rgb15.o
arm-elf-as -mcpu=arm7tdmi -mthumb-interwork memCopy.s -o memCopy.o

arm-elf-ld -Tc:\development\gba\lnkscript.ld -o rom.elf crt0.o main.o setMode.o setPixel.o rgb15.o memCopy.o
arm-elf-objcopy -O binary rom.elf rom.gba


I would really appriciate any help!

Thanks...

- Pan

#24015 - poslundc - Mon Jul 26, 2004 5:38 pm

Well, unless you're building it as a multiboot program, it shouldn't be linking at all, because the distance from code in the ROM to code in IWRAM is further than what can be processed in a BL instruction.

Furthermore, you are switching from Thumb to ARM instructions without using a BX instruction, which won't work.

You need to load the address of the other function into memory, and then use the BX instruction to branch to it. eg.

Code:
     ldr     r0, L_MYFUNC
     mov     lr, pc
     bx      r0
          ...
L_MYFUNC:
     .word     myFunctionName     @ function you want to branch to


Dan.

#24017 - pan69 - Mon Jul 26, 2004 6:24 pm

Aha, I understand...

...but that leaves me with even more questions. First, how come that it does link? When my memCopy function is supposed to be in IWRAM and unreachable with a BL instruction, I should get a link error but I don't... Any ideas why?

And I don't quite understand your code. You load the address of a label and at the address of that label is a word variable...? I don't understand.... Could you whip me up a working example? That would be great... :)

Thanks!

- Pan

#24018 - poslundc - Mon Jul 26, 2004 7:18 pm

pan69 wrote:
...but that leaves me with even more questions. First, how come that it does link? When my memCopy function is supposed to be in IWRAM and unreachable with a BL instruction, I should get a link error but I don't... Any ideas why?


Three possibilities:

1. Your code is compiled as multiboot.
2. Your linker/link script is erroneous.
3. I made a mistake reading/interpreting your code, or am missing some other information.

Quote:
And I don't quite understand your code. You load the address of a label and at the address of that label is a word variable...? I don't understand.... Could you whip me up a working example? That would be great... :)


It is the same as loading a global variable from C into a register, except instead of the name of the variable it is the name of the function. I don't have time to write a working example for you, but you should be able to find plenty by searching the forums or the net.

Dan.

#24029 - pan69 - Mon Jul 26, 2004 8:35 pm

Thanks for your time and putting me into the right direction. The hunt is on! To bad there are so many half-truths and incomplete code snippets out there...

- Pan

#24066 - pan69 - Tue Jul 27, 2004 10:02 am

Oke guys,

It's always the best reward if you find things out for your self. This was the problem:

First of all the call to the memCopy function should have been done like poslundc suggested. Becoming like this:

Code:

      ldr   r0, =memCopy
      mov   lr, pc
      bx      r0


After changing that, the function never returned. This was due to the fact that 'lr' did contain the correct address to return to but when the 'bx' instruction was executed the cpu never got back to thumb mode and started executing ARM instructions at the address where 'lr' pointing to.
So before executing 'bx lr' the lower bit of the 'lr' register needed to be set to '1' (causing a switch to thumb on bx). the code became this:

Code:

memCopy:
   @ Return to caller...

     orr     lr, lr, #1          @ <-- going back to thumb...
     bx      lr


So, there you have it... I hope this may help people in the future :)

- Pan

#24069 - torne - Tue Jul 27, 2004 12:00 pm

I would recommend setting the low bit before the call, not in the return, otherwise if you call memCopy from ARM code it will fail to return correctly. Something like this:
Code:

      ldr   r0, =memCopy
      mov   lr, pc
      add   lr, #3
      bx      r0

You have to add three because the addition of the extra instruction means you need to return one halfword later.

Personally, I'd do this with a farcall helper function, but if you're only calling it in a few places it probably doesn't matter.

#24070 - Kay - Tue Jul 27, 2004 12:03 pm

In order to return correctly from subrout, you must save LR (R14) in stack before calling cascaded branch and link/exchange code from another subrout (exemple in ARM).


Code:

   ....
   stmed   sp!,{r14}
   bl   SubRoutToGo
   ldmed   sp!,{r14}
   ...

SubRoutToGo:
   ...
   mov   pc,lr



Try to verify that point too ... in many cases, this will cause infinite loop or random failure when returning from subrout when LR is not saved.




-- Kay

#24072 - pan69 - Tue Jul 27, 2004 12:47 pm

torne wrote:
I would recommend setting the low bit before the call, not in the return, otherwise if you call memCopy from ARM code it will fail to return correctly. Something like this:
Code:

      ldr   r0, =memCopy
      mov   lr, pc
      add   lr, #3
      bx      r0

You have to add three because the addition of the extra instruction means you need to return one halfword later.

Personally, I'd do this with a farcall helper function, but if you're only calling it in a few places it probably doesn't matter.


Good point! But, instead of using a helper function, which can cause a little overhead, I fabricated this little GAS macro:

Code:

.macro farCallT funcptr
      ldr   r7, funcptr
      mov   r6, #3
      mov   lr, pc
      add   lr, r6
      bx      r7
.endm


This macro is only usable in thumb code (I use the T at the end of all my functions/macros in order to indicate where they are to be used). Now I can call memCopy like this:

Code:

      farCallT =memCopy


- Pan

#24078 - torne - Tue Jul 27, 2004 5:35 pm

Why put 3 into r6 first? You can add it directly.

#24091 - pan69 - Tue Jul 27, 2004 8:19 pm

torne wrote:
Why put 3 into r6 first? You can add it directly.


GAS doesn't like me adding immediate values to a register higher then r7 (lr equals r14) in thumb mode...

- Pan