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 > SWI

#102920 - Kirby - Mon Sep 18, 2006 12:11 am

Hi. It's me again. I am writing a function that handles all BIOS functions, but I still need to know more. Here's what it looks like so far:
Code:
#include "gba.h"

#define THUMB_MODE 0

#define SWI_SoftReset 0x000000
#define SWI_RegisterRamReset 0x010000

void swi(u16 func, u16 paras, u16 para1, u16 para2, u16 para3, u16 para4,u8 ret, u16 value);

void swi(u16 func, u16 paras, u16 para1, u16 para2, u16 para3, u16 para4,u8 ret, u16 value)
{
   if (paras >= 1) {
      *(volatile char *)0x03007FFA = para1;
   }
   if (THUMB_MODE == 1) {
      func /= 0x10000;
   }
   asm volatile ("swi " & func);
}

func is one of the defines above, paras is the # of paramenters, para1-4 is obvious, ret is wheather or not there is a return value, and value should store the value.
What addresses are there for the other 4 r's I need? And I also need to know how to get a return value, for something like 0x060000 Div. And one last question. Would it be easier to change it to a int instead of void, and have value be returned? Like this:
Code:
#include "gba.h"

#define THUMB_MODE 0

#define SWI_SoftReset 0x000000
#define SWI_RegisterRamReset 0x010000

u16 swi(u16 func, u16 paras, u16 para1, u16 para2, u16 para3, u16 para4,u8 ret);

u16 swi(u16 func, u16 paras, u16 para1, u16 para2, u16 para3, u16 para4,u8 ret)
{
   if (paras >= 1) {
      *(volatile char *)0x03007FFA = para1;
   }
   if (THUMB_MODE == 1) {
      func /= 0x10000;
   }
   asm volatile ("swi " & func);
}

_________________
Knock-knock.
Whose there?
Kirby.
Kirby-who?
I told you! Kirby!

#102924 - Kirby - Mon Sep 18, 2006 12:30 am

Actually, never mind about most of that, I've got it fixed, I just need to know what addresses r1, r2, and r3 are at.
_________________
Knock-knock.
Whose there?
Kirby.
Kirby-who?
I told you! Kirby!

#102925 - tepples - Mon Sep 18, 2006 1:14 am

For most BIOS functions, the arguments and the return value should be 32-bit, not 16-bit.

And no, the arguments aren't going to be at the same address. They're stored on a stack, and the addresses will vary depending on whether you call it in main() or in a function.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.

#102929 - Kirby - Mon Sep 18, 2006 1:55 am

tepples wrote:
And no, the arguments aren't going to be at the same address. They're stored on a stack, and the addresses will vary depending on whether you call it in main() or in a function.

How do I know where r0,r1,r2, and r3 are then?
_________________
Knock-knock.
Whose there?
Kirby.
Kirby-who?
I told you! Kirby!

#102932 - Kirby - Mon Sep 18, 2006 2:01 am

How's this for so far?
Code:
#include "gba.h"

#define THUMB_MODE 0

#define SWI_SoftReset 0x000000
#define SWI_RegisterRamReset 0x010000
#define SWI_Halt 0x020000
#define SWI_Stop 0x030000
#define SWI_IntrWait 0x0400000
#define SWI_VBlankIntrWait 0x050000
#define SWI_Div 0x0600000
#define SWI_DivArm 0x070000
#define SWI_Sqrt 0x080000
#define SWI_ArcTan 0x090000
#define SWI_ArcTan2 0x0A0000
#define SWI_CpuSet 0x0B0000
#define SWI_CpuFastSet 0x0C0000
#define SWI_GetBiosChecksum 0x0D0000 //Undocumented
#define SWI_BgAffineSet 0x0E0000 //Don't use in swi(....)
#define SWI_ObjAffineSet 0x0F0000 //Don't use in swu(....)
#define SWI_BitUnPack 0x100000
#define SWI_LZ77UnCompWram 0x110000
#define SWI_LZ77UnCompVram 0x120000
#define SWI_HuffUnComp 0x130000
#define SWI_RLUnCompWram 0x140000
#define SWI_RLUnCompVram 0x150000
#define SWI_Diff8bitUnFilterWram 0x160000
#define SWI_Diff8bitUnFilterVram 0x170000
#define SWI_Diff16bitUnFilter 0x180000
#define SWI_SoundBias 0x190000
#define SWI_SoundDriverInit 0x1A0000
#define SWI_SoundDriverMode 0x1B0000
#define SWI_SoundDriverMain 0x1C0000
#define SWI_SoundDriverVSync 0x1D0000
#define SWI_SoundChannelClear 0x1E0000
#define SWI_MidiKey2Freq 0x1F0000
#define SWI_MultiBoot 0x250000
#define SWI_HardReset 0x260000 //Undocumented
#define SWI_CustomHalt 0x270000 //Undocumented
#define SWI_SoundDriverVSyncOff 0x280000
#define SWI_SoundDriverVSyncOn 0x290000
#define SWI_GetJumpList 0x2A0000 //Undocumented

#define r0 (*((u16 volatile *) 0x3007FFA))

u16 blank;

typedef struct BgAfflineSrc
{
   s32 origCenterX;
   s32 origCenterY;
   s16 dispCenterX;
   s16 dispCenterY;
   s16 scalRatioXdir;
   s16 scalRatioYdir;
   u16 rotAngle;
}BgAfflineSrc;

typedef struct BgAfflineDest
{
   s16 diffXsame;
   s16 diffXnext;
   s16 diffYsame;
   s16 diffYnext;
   s32 startX;
   s32 startY;
}BgAfflineDest;

typedef struct ObjAfflineSrc
{
   s16 scalRatioXdir;
   s16 scalRatioYdir;
   u16 rotAngle;
}ObjAfflineSrc;

typedef struct ObjAfflineDest
{
   s16 diffXsame;
   s16 diffXnext;
   s16 diffYsame;
   s16 diffYnext;
}ObjAfflineDest;

void swi(u16 func, u16 paras, u32 para1, u32 para2, u32 para3, u32 para4,u16 ret, u32 *ret1, u32 *ret2, u32 *ret3);
void BgAfflineSet(BgAfflineSrc *src, BgAfflineDest *dest, u16 num);
void ObjAfflineSet(ObjAfflineSrc *src, ObjAfflineDest *dest, u16 num, u16 offset);

void swi(u16 func, u16 paras, u16 para1, u16 para2, u16 para3, u16 para4,u6 ret, u16 *ret1, u16 *ret2, u16 *ret3)
{
   //Get Input
   if (paras >= 1) {
      r0 = para1;
   }

   //Fix SWI Value
   if (THUMB_MODE == 1) {
      func /= 0x10000;
   }

   //Run BIOS Function
   asm volatile ("swi " & func);

   //Return Output
   if (ret >= 1) {
      ret1 = r0;
   }
}

void BgAfflineSet(BgAfflineSrc *src, BgAfflineDest *dest, u16 num)
{
   r0 = src;
   r1 = dest;
   r2 = num;
   if (THUMB_MODE == 1) {
      asm volatile ("swi 0x0E");
   } else {
      asm volatile ("swi 0x0E0000");
   }
}

void ObjAfflineSet(ObjAfflineSrc *src, ObjAfflineDest *dest, u16 num, u16 offset)[
{
   r0 = src;
   r1 = dest;
   r2 = num;
   r3 = offset;
   if (THUMB_MODE == 1) {
      asm volatile ("swi 0x0F");
   } else {
      asm volatile ("swi 0x0F0000");
   }
}

_________________
Knock-knock.
Whose there?
Kirby.
Kirby-who?
I told you! Kirby!

#102933 - tepples - Mon Sep 18, 2006 2:11 am

If a function has only four arguments, they will already be in r0, r1, r2, and r3. If it has more, see the ARM Procedure Call Standard.

I'd suggest making a separate .s file with an SWI for each BIOS call. They would look like the following (untested):

swi.s (to be compiled using something like arm-eabi-gcc -mthumb -mthumb-interwork -c swi.s -o swi.o):
Code:
@ void RegisterRamReset(unsigned int regions)
@ Clears each memory or register region where the bit is set.

.THUMB
.THUMB_FUNC
.ALIGN
.GLOBL  RegisterRamReset
RegisterRamReset:
  swi 0x01
  bx lr

@ int Div(int num, int den)
@ Divides two signed integers.  Division by 0 returns INT_MAX.

.THUMB
.THUMB_FUNC
.ALIGN
.GLOBL  Div

Div:
  cmp r1, #0
  beq 0f
  swi 0x06
  bx lr
0:
  ldr r0, =0x7fffffff
  bx lr


@ void LZ77UnCompWRAM(const void *src, void *dst)
@ Unpack GB LZSS format data.

.THUMB
.THUMB_FUNC
.ALIGN
.GLOBL  LZ77UnCompWRAM
LZ77UnCompWRAM:
  swi 0x11
  bx lr


swi.h (to be #included from C files):
Code:
#ifndef INCLUDE_SWI_H
#define INCLUDE_SWI_H
#ifdef __cplusplus
extern "C" {
#endif

void RegisterRamReset(unsigned int regions);
int Div(int num, int den);
LZ77UnCompWRAM(const void *src, void *dst);

#ifdef __cplusplus
}
#endif
#endif

_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.

#102958 - Kirby - Mon Sep 18, 2006 6:51 am

Is there a way to retrieve to current address of r0, r1, r2, and r3 using ASM? That way I could have r0 be retrieved from ASM every VSync. Like this:
Code:
extern volatile Getr0;
extern volatile Getr1;
extern volatile Getr2;
extern volatile Getr3;

...

Getr0 = para1;

_________________
Knock-knock.
Whose there?
Kirby.
Kirby-who?
I told you! Kirby!

#102966 - Cearn - Mon Sep 18, 2006 8:39 am

Both libgba and tonc's codebase provide a full list of bios calls, it's probably easier to just use those. More information on how to get them running can be found at tonc:swi, and the AAPCS can be found here. It'll help you understand how function calling operates on ARM systems. The basic gist of it is that the first 4 arguments are put in r0-r3, the rest goes on the stack (pointed to by r13), and r0 holds the return value of the function.

Other points:
  • The registers don't have addresses, they're CPU registers (not IO registers like REG_DISPCNT and such). If you're working in C, you shouldn't care about which registers are used where ... with the exception of bios calls I guess. If you do need to care, I'd suggest using actual assembly, where you have the necessary control over what's going on.
  • Use word (int or unsigned int) for basic datatypes unless you have a very good reason not to.
  • The word is affine, not affline :P
  • GCC already creates a macro called __thumb__ for thumb compiled code, so you don't have to do that yourself. Again, see libgba or tonc for more.
  • You can't convert numbers to strings using '&'. This is not VB.
  • ObjAffineSet does not necessarily take an ObjAffineDest as its second parameter. Technically it's a pointer to the pa element of the matrix, with offset being the offset between the elements. That way you can use it for backgrounds (offset=2) or objects (offset=8). Also, if you want num to work correctly you need to watch your data alignment. The struct needs to be word-aligned in order for ObjAffineSet to work with num>1.

#103303 - ProblemBaby - Wed Sep 20, 2006 9:00 pm

Doesnt SWI require a constant parameter?
I think you should write this kind of functions in pure asm, that gives you much more control.

#103946 - hello world - Mon Sep 25, 2006 4:30 pm

Correct me if I'm wrong but would Get/Set rX work like this: (The code is untested)
Code:

Getr1:
   @ Move r1 into r0 (return value)
   mov r0, r1
   bx lr

Code:

Setr1
    @ Move r0 into r1 (r0 = first parameter)
    mov r1, r0
    bx lr

And then you'd write
Code:

extern int Getr1 (int x);
extern int Setr1 (int x);

Or something like that.

#103952 - Cearn - Mon Sep 25, 2006 5:15 pm

Yes, but there's no point in doing something like that because you have no control over what the registers will be in the C code surrounding those calls. Example of using functions like that:
Code:
void BgAfflineSet(BgAfflineSrc *src, BgAfflineDest *dest, u16 num)
{
   SetR0((u32)src);
   SetR1((u32)dest);
   SetR2((u32)num);
   
   swi_call(0x0E);
}

The calls to SetR1 and SetR2 would also set r0 again because that's how parameter passing works. Reversing the calling order would work ... this time. But any code inbetween calls would just mess everything up again. It's just too unstable; if you want/need to know exact registers (which is a rare situation anyway), just use assembly for the whole thing.