Trying to get interrupts to work

If you have any questions on programming, this is the place to ask them, whether you're a newbie or an experienced programmer. Discussion on programming in general is also welcome. We will help you with programming homework, but we will not do your work for you! Any porting requests must be made in Developmental Ideas.
Post Reply
User avatar
bogglez
Moderator
Moderator
Posts: 578
https://www.artistsworkshop.eu/meble-kuchenne-na-wymiar-warszawa-gdzie-zamowic/
Joined: Sun Apr 20, 2014 9:45 am
Has thanked: 0
Been thanked: 0

Trying to get interrupts to work

Post by bogglez »

Hi,

I've been reading the SH4 manual and tried to get into assembly a bit. I'm having issues with understanding how interrupts are set up.
I can draw to the screen, but I do not receive any interrupts whatsoever.
Curiously, if I replace the nop after the jump to main with "ldc r1,sr", "1" is spammed by my interrupt handler, but I cannot see any graphics on screen anymore and my print statements from main() also don't show up.

Does anyone have any idea what I might be doing wrong?

start.s:
Spoiler!

Code: Select all

.globl start

.extern main
.extern ak_interrupt_handler

.text
.align 2
start: ! Entry point. Sets up the cache then jumps to main.
	! Make the program counter a P2 memory area address to enter privileged mode.
	mov.l setup_cache_addr,r0
	mov.l p2_mask,r1
	or r1,r0
	jmp @r0
	nop

setup_cache: ! Enables the cache. Must be called in priviledged mode.
	! Now that we are in P2, it's safe to enable the cache
	mov.l ccr_addr,r0
	mov.w ccr_data,r1
	mov.l r1,@r0      ! After changing CCR, eight instructions must be executed before it's safe to enter a cached area such as P1
	mov.l vbr_addr,r0 ! 1 Set up interrupt table
	ldc r0,vbr        ! 2
	mov.l initaddr,r0 ! 3
	mov.l stkaddr,r15 ! 4 Set up stack
	nop               ! 5
	nop               ! 6
	nop               ! 7
	nop               ! 8
	jmp @r0
	nop

init:
	mov.l init_sr,r1  ! Enable interrupts
	ldc r1,sr
	mov.l mainaddr,r0 ! Jump to main in C code
	jsr @r0
	nop

interrupt_handler:
	mov.l inthandleraddr,r0
	jsr @r0
	nop
	rte
	nop

.align 4
vbr_addr:         .long vma_table
p2_mask:          .long 0xa0000000
setup_cache_addr: .long setup_cache
initaddr:         .long init
mainaddr:         .long _main
inthandleraddr:   .long _ak_interrupt_handler
ccr_addr:         .long 0xff00001c
init_sr:          .long 0x60000000 ! MD,RB
stkaddr:          .long stack
ccr_data:         .word 0x0909


.align 2
vma_table:
	.space 0x100
vma_table_100: ! General exceptions
	mov #1,r4
	bra interrupt_handler
	.space 0x300 - 4
vma_table_400: ! MMU exceptions
	mov #2,r4
	bra interrupt_handler
	.space 0x200 - 4
vma_table_600: ! IRQs
	mov #3,r4
	bra interrupt_handler
	nop

.align 4
.space 4096 ! 4kb stack
stack:

.end
main.c
Spoiler!

Code: Select all

void ak_interrupt_handler(u32 code) {
	switch(code) {
		case 1: puts("1"); break;
		case 2: puts("2"); break;
		case 3: puts("3"); break;
		default: puts("default");
	}
}

int main() {
	puts("main");

	for(;;) {
		ak_gfx_clear(bg_color);
		ak_gfx_draw_box(10, 20, 50, 100, 0x3f0);
	}
}
Wiki & tutorials: http://dcemulation.org/?title=Development
Wiki feedback: viewtopic.php?f=29&t=103940
My libgl playground (not for production): https://bitbucket.org/bogglez/libgl15
My lxdream fork (with small fixes): https://bitbucket.org/bogglez/lxdream
nymus
DC Developer
DC Developer
Posts: 968
Joined: Tue Feb 11, 2003 4:12 pm
Location: In a Dream
Has thanked: 5 times
Been thanked: 6 times

Re: Trying to get interrupts to work

Post by nymus »

Hmm, I haven't done any interrupt handling myself, but at first glance, I see you are making considerations according to Section 5.8 (Restrictions) of the Programming manual...

It looks like you should be okay but you don't "nop" in the delay slot after the first bra instruction. You put .space which, I guess just puts zeros there.

For the math, I'd probably prefer something like .space 0x400 - (. - vma_table), assuming this works as expected.

Again, no experience here, just throwing out a few suggestions :)
behold the mind
inspired by Dreamcast
User avatar
bogglez
Moderator
Moderator
Posts: 578
Joined: Sun Apr 20, 2014 9:45 am
Has thanked: 0
Been thanked: 0

Re: Trying to get interrupts to work

Post by bogglez »

Thanks for your reply, nymus.
nymus wrote:Hmm, I haven't done any interrupt handling myself, but at first glance, I see you are making considerations according to Section 5.8 (Restrictions) of the Programming manual...
My manual doesn't have a "Section 5.8 Restrictions". I'm using the official manual by Renesas.
nymus wrote: It looks like you should be okay but you don't "nop" in the delay slot after the first bra instruction. You put .space which, I guess just puts zeros there.
Good catch. Somehow I assumed nop is 0, but it's not.. Fixed, but didn't solve the problem.
Wiki & tutorials: http://dcemulation.org/?title=Development
Wiki feedback: viewtopic.php?f=29&t=103940
My libgl playground (not for production): https://bitbucket.org/bogglez/libgl15
My lxdream fork (with small fixes): https://bitbucket.org/bogglez/lxdream
nymus
DC Developer
DC Developer
Posts: 968
Joined: Tue Feb 11, 2003 4:12 pm
Location: In a Dream
Has thanked: 5 times
Been thanked: 6 times

Re: Trying to get interrupts to work

Post by nymus »

I've tried following the program flow and have a few observations:

Handling procedure (INT is interrupt_handler in assembly):

Code: Select all

f() > INT > ak_interrupt() > RTS > INT > RTE > f()
- INT calls ak_interrupt using JSR so we are inside a "new" exception-handling context (overwritten PR, saved R15).
- We are not taking interrupts now. When ak_interrupt returns to us, PR and R15 have been clobbered by further function calls e.g. puts(). Their last value is probably at or just after the RTE instruction… I believe
- we "did" use PR during exception handling so a call to RTS without a proper JSR will come here
- e.g. gfx_clear() > INT > gfx_clear() > RTS > ???
- should we save PR during exception handling?
- Should we restore R15? (STC SGR, R15). Probably not important here.
- RTE unblocks exceptions.
- back to where we were

Now following the actual program...

startup

Code: Select all

init() > unblock > INT > init() > ???
main() might never be reached and the program would be infinitely stuck here. Suggest moving enable_intr inside main()...

main loop

Code: Select all

main() > INT > main()
This looks okay. I'd just check the handler to make sure we can come back to main().

Another issue could be that you are setting RN_BANK1 for normal code instead of RN_BANK0 when you unblock.
SR bits: 31 30 29 28 > __ MD RB BL

4 > 0 1 0 0 > Privileged+Bank0+unblock
6 > 0 1 1 0 > Privileged+Bank1+unblock

When an interrupt happens, you overwrite everything because you are already using Bank1.

At startup, KOS loads 0x500000F0 ( 0 1 0 1 ) which sets Privileged+Bank0+Block. I see KOS does an AND of 0xEFFFFF0F ( 1 1 1 0 ) in the kernel to enable interrupts which has a 1 for the Bank bit but I think this is just to preserve whatever the coder is using. This AND would still restore the CPU to Bank0 if that's what it was before the interrupt.

I don't know how IMASK works.
behold the mind
inspired by Dreamcast
Chilly Willy
DC Developer
DC Developer
Posts: 414
Joined: Thu Aug 20, 2009 11:00 am
Has thanked: 0
Been thanked: 2 times

Re: Trying to get interrupts to work

Post by Chilly Willy »

Interrupt handlers MUST save and restore ANY register that is used. In fact, your handler itself is walking on r0 and PR without saving and restoring them. In general, if you plan to use C/C++ code from interrupts, save and restore ALL registers, period. Well, you don't have to save the FP regs... because you should never use floating point inside an exception/interrupt unless that exception is fatal and you don't plan on returning to the regular program.

Second, certain instructions cannot be placed in delay slots. See the hardware manual for details. Doing so results in an illegal delay slot instruction exception.
User avatar
bogglez
Moderator
Moderator
Posts: 578
Joined: Sun Apr 20, 2014 9:45 am
Has thanked: 0
Been thanked: 0

Re: Trying to get interrupts to work

Post by bogglez »

Chilly Willy wrote: Second, certain instructions cannot be placed in delay slots. See the hardware manual for details. Doing so results in an illegal delay slot instruction exception.
Something that had me confused is why ldc *,sr immediately after the jump leads to the interrupt handler being called, while before it won't. It seems like exception handling is on by default without changing sr (this seems to contradict the documentation?) and jmp followed by ldc *,sr is invalid, so the exception handler is called. Is that possible?
I need to get a better emulator, probably need to install Windows..

And also, do any interrupts like vblank fire by default? Maybe my code was correct with ldc *,sr before jmp and I just didn't get any exceptions or interrupts. That would explain things..
Wiki & tutorials: http://dcemulation.org/?title=Development
Wiki feedback: viewtopic.php?f=29&t=103940
My libgl playground (not for production): https://bitbucket.org/bogglez/libgl15
My lxdream fork (with small fixes): https://bitbucket.org/bogglez/lxdream
Chilly Willy
DC Developer
DC Developer
Posts: 414
Joined: Thu Aug 20, 2009 11:00 am
Has thanked: 0
Been thanked: 2 times

Re: Trying to get interrupts to work

Post by Chilly Willy »

KOS sets up a lot of stuff. Look at kos/kernel/arch/dreamcast/kernel/init.c - the standard kos start calls arch_main, which clears the bss, calls arch_auto_init, calls all the ctors, and then calls main(). The arch_auto_init does a lot of hardware setup.

And in general, exceptions ALWAYS occur. Interrupts can be turned on or off, but exceptions generally can't unless specifically mentioned in the hardware manual. You have gotten the SH4 hardware and software manuals, right? They can be easily found online. If you're going to do any assembly level work, you need both.

The way people "disable" exceptions is to put in an exception handler that ignores the exception. I think the default kos exception handlers do a kernel panic. See kos/kernel/arch/dreamcast/kernel/entry.s for exception and irq handlers. Oh, one thing I forgot about the SH4 - it gives you a bank of registers covering r0 to r7 that switch in during irqs and exceptions so that simple exception handlers don't have to save and restore those regs. All others need to be saved and restored, and the banked r0 to r7 need to be saved/restored when calling C code for handlers. Again, see that entry.s file to see how kos handles it. Looking at the kos code for these things will be very instructive.
User avatar
bogglez
Moderator
Moderator
Posts: 578
Joined: Sun Apr 20, 2014 9:45 am
Has thanked: 0
Been thanked: 0

Re: Trying to get interrupts to work

Post by bogglez »

Thanks for helping, Chilly!
I'm aware of most what you said, except the hardware init stuff, I'll look into that, thanks!

The manual says "BL=1: Interrupt requests are masked. If a general exception other than a user break occurs while BL = 1, the processor switches to the reset state."
So if I get 2 exceptions before I handled the first the Dreamcast resets, right? I've been reading various stuff so I'm a bit overwhelmed right now, need to get this knowledge sorted out, sorry :D

I guess I'll try to init a timer interrupt or something to cause interrupt handling.
Wiki & tutorials: http://dcemulation.org/?title=Development
Wiki feedback: viewtopic.php?f=29&t=103940
My libgl playground (not for production): https://bitbucket.org/bogglez/libgl15
My lxdream fork (with small fixes): https://bitbucket.org/bogglez/lxdream
Chilly Willy
DC Developer
DC Developer
Posts: 414
Joined: Thu Aug 20, 2009 11:00 am
Has thanked: 0
Been thanked: 2 times

Re: Trying to get interrupts to work

Post by Chilly Willy »

Yes, most processors do something like that because an exception during an exception is usually fatal. BTW, interrupts are also exceptions, just a specific type (asynchronously externally generated and possibly masked).

When testing exception handlers, a trap command is very handy as you can use inline assembly to trigger it where and when you want.
User avatar
bogglez
Moderator
Moderator
Posts: 578
Joined: Sun Apr 20, 2014 9:45 am
Has thanked: 0
Been thanked: 0

Re: Trying to get interrupts to work

Post by bogglez »

I got everything working now, apart from the switch to user mode. As I feared there was something else going on as well.

I wanted to do hardware initialization etc before I jump to main and then disallow access to privileged instructions, so I tried to switch to user mode before the jump.
However, because mainaddr is not within U0, I get a "data address error (write)" when I perform "ldc r0,sr" (I found out by actually printing the exception code).

So I think I have to create a linker script that puts the code which changes SR to user mode then jumps to main in a different memory area.
However, I'm not sure where I'm allowed to put it and how to do that.. any advice?
In my Makefile I tried " -Wl,--section-start=usercode=0x0,-Ttext=0x8c010000" (text for the start function) and put ".section usercode" in front of my SR changing function, but that wouldn't work. :-/
Wiki & tutorials: http://dcemulation.org/?title=Development
Wiki feedback: viewtopic.php?f=29&t=103940
My libgl playground (not for production): https://bitbucket.org/bogglez/libgl15
My lxdream fork (with small fixes): https://bitbucket.org/bogglez/lxdream
Chilly Willy
DC Developer
DC Developer
Posts: 414
Joined: Thu Aug 20, 2009 11:00 am
Has thanked: 0
Been thanked: 2 times

Re: Trying to get interrupts to work

Post by Chilly Willy »

Most OSes setup the user level task at kernel level, then use a return from exception to run the task. One of the main things a return from exception does is restore the level the task had before the exception occurred. So it can switch you from kernel to user mode while simultaneously jumping to the code address. On the SH4, the instruction is RTE, like many other ISAs. You set the ssr with the value you want the sr to be in the code, set spc to the value you want the pc to be, then do RTE. It's a delay slot instruction, so be sure to follow it with a NOP.
User avatar
bogglez
Moderator
Moderator
Posts: 578
Joined: Sun Apr 20, 2014 9:45 am
Has thanked: 0
Been thanked: 0

Re: Trying to get interrupts to work

Post by bogglez »

Chilly Willy wrote:Most OSes setup the user level task at kernel level, then use a return from exception to run the task. One of the main things a return from exception does is restore the level the task had before the exception occurred. So it can switch you from kernel to user mode while simultaneously jumping to the code address. On the SH4, the instruction is RTE, like many other ISAs. You set the ssr with the value you want the sr to be in the code, set spc to the value you want the pc to be, then do RTE. It's a delay slot instruction, so be sure to follow it with a NOP.
That's a neat trick!
I replaced the jsr to main with the following:

Code: Select all

	mov.l mainaddr,r0
!	jsr @r0
	ldc r0,spc
	mov.l user_sr,r0 ! user_sr: .long 0x00000000
	ldc r0,ssr
	rte
	nop
If I use the privileged SR, everything's fine. But this way I get the "data access error (write)" again. So I will need to put main into a different memory area somehow, right?
Wiki & tutorials: http://dcemulation.org/?title=Development
Wiki feedback: viewtopic.php?f=29&t=103940
My libgl playground (not for production): https://bitbucket.org/bogglez/libgl15
My lxdream fork (with small fixes): https://bitbucket.org/bogglez/lxdream
Chilly Willy
DC Developer
DC Developer
Posts: 414
Joined: Thu Aug 20, 2009 11:00 am
Has thanked: 0
Been thanked: 2 times

Re: Trying to get interrupts to work

Post by Chilly Willy »

Oh, you need to edit the dreamcast environment script. Look for this line

Code: Select all

export KOS_LDFLAGS="${KOS_LDFLAGS} -ml -m4-single-only -Wl,-Ttext=0x8c010000 -Wl,--gc-sections"
That sets the address that everything links to. Hmm - that does lead to a tricky situation - you need some of the code in kernel space to start things off, but then you need the rest in user space. You may need to compile the user code completely separate from the standard KOS code, link the user code to the proper user address, then load that code by hand inside the KOS code. Look at libelf in dctool as an example of using libelf. I don't see being able to link parts of the code to one address and parts to another as a single object. Not without compiling/linking it to binary, then embedding it in the other code. For your small tests, that may be the easiest thing. Compile the test function, link it with the proper user address, use objcopy to convert it to binary, then embed that in your KOS test code.
Post Reply