Jumping into ARM assembly

Ah, that's where the pink/rose comes from (rose is a perfectly valid colour in english, I guess I was mainly reviewing that code by the comments and hadn't spotted that deviation).
I imagined that the screen color change so fast, I should see pink.
I remember there was a grayscale trick with the on/off pixels of the HP-48.

Yes, that mvn will put 0xFFFF0FFF in r1. Is the standard Pandora framebuffer 16-bit? If so, and you actually want 0xFC1FFC1F you can't do that in one instruction, as ARM immediates can only be 8-bits wide (although those 8-bits can be anywhere within a 32-bit number).
Yup, Zaxxon is 16-bit, SL4P is 24/32 but I set it back to 16 for those scientific experiments,
[doublepost=1479258645,1479258242][/doublepost]Isn't there a way to write more pixels with the same instruction ? Extending registers or whatever ?
I just noticed in the TRM; unaligned data access is an ARM 'thing', and it's controlled by a status register bit on the cortex-A8. I guess Linux leaves that at default and allows it on this processor. It takes longer to write an unaligned address than an aligned one, but it still works.

You can write to multiple addresses using an STM flavour (store multiple), such as STMIA to write, starting from the address in the first register, upwards four bytes at a time. In that case, the address *must* be 4-byte aligned though. STM instructions take number of registers/words to write halved clock cycles to complete if you write at least 4 registers out each time. It also has the designed side effect that it updates your address register, so you just could have a series of STMIA operations and it would write out a whole row's worth of pixels if you wanted - you don't need any instructions to increment the address register.

Edit: And to get filckering pixels to work at all reliably, you'll pretty much need to wait for vsync after each run, so make sure each colour is shown for at least one frame.
I had unaligned exceptions sometimes when trying things ^^.

Indeed, I load white on r1-r8, and can execute lots of stmia. Nice !

mov r0, r11 @ load fb adress

stmia r0!, { r1-r8 }
stmia r0!, { r1-r8 }

Looks like there's some vsync-related code in tetet source.
[doublepost=1479265313,1479264574][/doublepost]This is no joke !

[/code]fbio_getvscreeninfo =0x4600
fbio_putvscreeninfo =0x4601
fbio_pandisplay =0x4606
vsync_data: .word 0
push {r0-r5,lr}

ldr r3,mmaptbl_ofs
@ wait 4 vsync
vsync_ioctl_command = ioc_write | (4 << 16) | ('F' << 8) | 0x20
@ ldr r1,vsync_ioctl_command
movw r1,#vsync_ioctl_command & 0xffff
adr r2,vsync_data
ldr r0,[r3,#(fhandle-mmaptbl)]
movt r1,#vsync_ioctl_command >> 16
invoke sys_ioctl
vsync_len: .word 0[/code]
I'm not sure, but I suspect those references to 'fb_flip' mean its also doing double buffering, which you won't need as you're able to refresh the screen within the vsync window just fine, I assume. But there only seems to be one sys_ioctl, so either that code's not actually there (maybe you snipped it) or that ioctl can do two things at once.
There's also a commented part :
@ push {r0-r2,lr}
@ ldr r0,fhandle
@ ldr r1,vsync_ioctl_command
@ adr r2,vsync_data
@ invoke sys_ioctl
@pop {r0-r2,pc}
FWIW, that's a subroutine, designed to be called from elsewhere. I can't see where that's called from so I dunno how it ties into the code above. But it could be the other ioctl designed to do whichever of vsync wait and frame switch it's not doing currently. You probably know more about the ioctl control codes than I do.
Okay, done a bit of digging. I've looked in /usr/include/linux/fb.h and is has this in it:

#define FBIO_WAITFORVSYNC _IOW('F', 0x20, __u32)

Now, I don't really know what it's doing there, but it looks to me rather like:

vsync_ioctl_command = ioc_write | (4 << 16) | ('F' << 8) | 0x20

So I think that variable points to FBIO_WAITFORVSYNC, which guessing by the name, waits until the next vsync event at which point you'll have a little time to do your drawing before the hypothetical CRT beam starts at the top of the screen again. Do on of those at the start of your painting routine inside the loop and see what you get.

Trouble is, looking at that code both the commented out code and the uncommented use that variable, albeit in different ways. I'd try the uncommented code first, even though it's longer. I guess you've snipped off the code that does the framebuffer switch, since I can see the label fb_flip: looks like the start of a callable, but you've cut off where it returns. It probably doesn't matter, just don't include that stack push, but do include everything after it.
Hello, I'm happy this thread is alive again. To help you I quickly put together an example that does the following:
- framebuffer setup with double buffering
- plots 2 pixels on the center of 2nd screen
- scrolls the virtual screen from the 480th line to 0

Error checking was removed to shorten the code.
If you cancel the operation with a CTRL-C then the screen remains scrolled. Put the pandora to sleep and wake it up to refresh the screen.

There's an option in the code to use vsync or not. The difference is clearly visible.
Here is the code. I hope you will enjoy it. If you have questions feel free to ask me. I will do my best to answer you.

.title openpandora vsync
.globl _start
.include "macro.mac"

with_vsync = 1    @ the option to use vsync or not
vsync_ioctl_cmd = ioc_write | (4<<16) | ('F'<<8) | 0X20

screensize=480*800*2*2  @ double buffer
fbio_getvscreeninfo    = 0x4600
fbio_putvscreeninfo    = 0x4601
fbio_pandisplay        = 0x4606

 bl fbinit

 cmp r0,#0
bne fbokay
 print "fb was not initialized\n"
 exit 255

@ plot two pixels pixel on the center of 2nd screen
 ldr r1,=800*2*720+400*2
 mvn r2,#0
@ r0 points to the doubled frame buffer
 str r2,[r0,r1]

@ scroll loop
 mov r8,#480    @ from virtual line 480, it's the 1st line of the 2nd screen
 ldr r6,=fbfd
.if with_vsync
 ldr r1,=vsync_ioctl_cmd
 ldr r2,=vsync_data    @ ED mentioned earlier that the OP vsync routine needs a zero as the 1st parameter
 ldr r0,[r6]     @ get fbfd
  invoke sys_ioctl

@ update yoffset
 str r8,[r6,#yoffset-fbfd]
 ldr r1,=fbio_pandisplay
 add r2,r6,#fb_vscreeninfo-fbfd    @ get address of the fb virtual screen info structure
 ldr r0,[r6]     @ fbfd
  invoke sys_ioctl

 subs r8,#1
bpl scroll_loop

 bl fbclose
exit 0

 push {r1-r5,lr}

 open "/dev/fb0", o_rdwr

 subs r4,r0,#0
 clr r0
bmi fbinitret

 mov r1,#fbio_getvscreeninfo
 ldr r2,=fb_vscreeninfo
 mov r0,r4            @ fbfd
  invoke sys_ioctl

@ double buffer init
 ldr r3,[r2,#yres-fb_vscreeninfo]
 inc r1       @ fbio_putvscreeninfo
 add r3,r3,r3
 mov r0,r4      @ fbfd
 str r3,[r2,#yres_virtual-fb_vscreeninfo]
  invoke sys_ioctl

@ r4=fbfd
@ r0 if 0 after a successful ioctl call. I assume everything was okay
 ldr r1,=screensize
 mov r2,#prot_read | prot_write
 mov r3,#map_shared
 mov r5,r0
  invoke sys_mmap2

 ldr r1,=fbp
 cmp r0,#0
beq fbinitret

 str r0,[r1]    @store fbp
 str r4,[r1,#4] @store fbfd

@ if r0 eq 0 then some error happened
pop {r1-r5,pc}

 push {r1,r2,lr}

 ldr r2,=fbp
 ldr r0,[r2]
 ldr r1,=screensize
  invoke sys_munmap

 ldr r0,[r2,#4]
  invoke sys_close

pop {r1,r2,pc}


vsync_data: .word 0


fbp:  .word 0
fbfd: .word 0

@ the fb vscreeninfo structure. copied from fb,inc
xres:         .word 0
yres:         .word 0
xres_virtual:  .word 0
yres_virtual:  .word 0
xoffset:     .word 0
yoffset:     .word 0
bpp:         .word 0
grayscale:     .word 0
@ fb_bitfield struc
@  offset:    .word 0
@  length:    .word 0
@  msb_right: .word 0    
@ ends
bitfield_red:   .word 0,0,0
bitfield_green: .word 0,0,0
bitfield_blue:  .word 0,0,0
bitfield_transp:.word 0,0,0
nonstd:     .word 0
activate:     .word 0
height:     .word 0
width:         .word 0
accel_flags:       .word 0
pixclock:     .word 0
left_margin:   .word 0
right_margin:  .word 0
upper_margin:  .word 0
lower_margin:  .word 0
hsync_len:     .word 0
vsync_len:     .word 0
sync:         .word 0
vmode:         .word 0
rotate:     .word 0
colorspace:     .word 0
.fill 4,4,0     @reserved

Hi again, and thanks for this piece of code !
You may regret coming back because I have TONS of questions !

vsync_ioctl_cmd = ioc_write | (4<<16) | ('F'<<8) | 0X20
Does it ask for a signal ? I don't understand how the program "gets" the vsync. Is it an interruption ?

screensize=480*800*2*2 @ double buffer
Why 2*2 ? I guess first *2 is because twice the memory for screen is reserved.

fbio_getvscreeninfo = 0x4600
fbio_putvscreeninfo = 0x4601
fbio_pandisplay = 0x4606
What is it ? Offsets ?
vsync_ioctl_cmd = ioc_write | (4<<16) | ('F'<<8) | 0X20
Does it ask for a signal ? I don't understand how the program "gets" the vsync. Is it an interruption ?
No, it's an ioctl call. It unwraps the pointer in R6 and puts it in R0 - that tells it what fb to look at I think. There's no return value as far as I know - it just hangs about until vsync happens.
screensize=480*800*2*2 @ double buffer
Why 2*2 ? I guess first *2 is because twice the memory for screen is reserved.
16-bit pixels = 2 bytes timed by 2 framebuffers I think.
levi, #311 - correct.

fbio_getvscreeninfo = 0x4600
fbio_putvscreeninfo = 0x4601
fbio_pandisplay = 0x4606
They're ioctl calls defined in fb.h and run by the kernel or the fb kernel module.

The screensize can be calculated by multiplying BPP and YRES_VIRTUAL parameters from the virtual screeninfo structure if we want to make the code Pyra-compatible.
Last edited:
A quick google suggests FBIOGET_VSCREENINFO (as it's named in /usr/include/linux/fb.h) gets variable screen info like bit depth and resolution. FBIOPUT_VSCREENINFO can be used to rotate the screen - perhaps it's also used to switch buffers, I've not really grokked the code yet. FBIOPAN_DISPLAY slightly confusingly actually waits for vsync.
16-bit pixels = 2 bytes, ok that's why when we write the whole 32bit register, we write 2 pixels.
So that screensize= includes both front and back buffer.

And what are the purposes of

I ran into https://people.freedesktop.org/~marcheu/linuxgraphicsdrivers.pdf , looks like a good document.

You can find better docs at linux-fbdev.org, like the HOWTO.

On the mentioned ioctls. In short: they're like INT 10, AH=4F / VESA BIOS calls on an x86 system.

There are two structures that hold information about the graphics hardware and its capabilities.

-- fix screeninfo. (fb_fix_screeninfo struc {} read-only) FBIOGET_FSCREENINFO 0x4602
You can find the MMIO, physical memory address and some other hardware -related things there.

-- variable screeninfo. fb_var_screeninfo struc {} can be read or write using FBIOGET_VSCREENINFO 0x4600 or FBIOPUT_VSCREENINFO 0x4601
This structure is what you can change if you want rotation or a larger virtual screen. You much check the capability bits or the kernel or fbdev module source first before using any of these functions. Not every feature is implemented. Resolutions, virtual screen and BPP settings usually work.
You can read out resolution information, like YRES and change the YRES_VIRTUAL to get double buffering or a larger virtual screen.

-- fbio_pandisplay
This function is used to pan the visible window on the virtual screen. XOFFSET and YOFFSET is used in fb_var_vscreeninfo structure to get the result.

edit. PANDISPLAY does not wait for vsync! check the source above and set "with_vsync" to zero then run the code to see the difference.
Last edited:
Finally found time to test the new dmarschal test. Lol, I was expecting two pixels !