Do not use LD A,I to read interrupt enabled state

Страница 2/4
1 | | 3 | 4

By Grauw

Ascended (9071)

Аватар пользователя Grauw

27-12-2015, 16:54

Haa kanima, that’s a nice one that will work without patching the ISR in a complicated way Smile. It’s a bit slow but elegant, and it’ll do the trick as a drop-in replacement. Thanks a lot!

(Only when NMI interrupts are present it will not work, but that’s not the case on MSX luckily.)

By Prodatron

Paragon (1789)

Аватар пользователя Prodatron

27-12-2015, 16:07

Very nice solution! And sorry @RetroTechie, I mixed the fact, that the P/V flag is only destroyed during the LD A,I instruction. So your solution is indeed working fine when the VDP is the only IRQ source.

By Grauw

Ascended (9071)

Аватар пользователя Grauw

27-12-2015, 16:44

Prodatron wrote:

And sorry @RetroTechie, I mixed the fact, that the P/V flag is only destroyed during the LD A,I instruction. So your solution is indeed working fine when the VDP is the only IRQ source.

As RetroTechie said it’s still not 100% airtight. Even with a single source of interrupts, if the code on the ISR takes very long, there’s still an edge case where it can trigger a new ISR in the middle of the second ld a,i. Though he’s right that the probability of a faulty return value decreases (but I’d like it to be 0).

By Prodatron

Paragon (1789)

Аватар пользователя Prodatron

27-12-2015, 16:52

Sure, that's true if the code takes more than one VBLANC. Though usual ISRs shouldn't take that long.

By Grauw

Ascended (9071)

Аватар пользователя Grauw

27-12-2015, 17:00

My previous code was also working in 99.99% of the cases, until I expanded my code Smile. I’d prefer that situation to not re-occur Wink. Also considering that someone else may modify the code without being aware of this assumption on the ISR duration (or that I forget about it in 6 months time, or there’s a TSR running, etc). And that if you break the assumption, it is so difficult to detect.

By Grauw

Ascended (9071)

Аватар пользователя Grauw

27-12-2015, 18:11

Kanima’s solution in subroutine form so that it can be used as a drop-in replacement for ld a,i:

    IF $ < 100H
        ERROR "Must be at address >= 100H"
    ENDIF

; Workaround for LD A,I / LD A,R lying to us if an interrupt occurs during the
; instruction. We detect this by examining if (sp - 1) was overwritten.
; f: pe <- interrupts enabled
; Modifies: af
System_CheckEIState:
    xor a
    push af  ; set (sp - 1) to 0
    pop af
    ld a,i
    ret pe  ; interrupts enabled? return with pe
    dec sp
    dec sp  ; check whether the Z80 lied about ints being disabled
    pop af  ; (sp - 1) is overwritten w/ MSB of ret address if an ISR occurred
    sub 1
    sbc a,a
    and 1  ; (sp - 1) is not 0? return with pe, otherwise po
    ret

By Grauw

Ascended (9071)

Аватар пользователя Grauw

27-12-2015, 18:11

Edited the snippet above to replace the conditional jump with some math.

By kanima

Master (193)

Аватар пользователя kanima

27-12-2015, 20:03

Just to make it clear: the basic principle and implementation of that solution (that is, writing a zero byte below the stack and checking it afterwards to see if an interrupt occurred) all came from somebody at that discussion I linked too. They had several versions of the code that forces PE/PO at the end (after reading back the value from below the stack) and I'm not sure any of them really worked right. All I did was create a version where that last bit of code was working correct and slightly changed the comments.

Well done @Grauw for optimizing that last bit and putting it all in a neat reusable subroutine.

By hit9918

Prophet (2891)

Аватар пользователя hit9918

27-12-2015, 21:28

but, what is the underlying problem that makes you want ld a,i?

An example of "what is the underlying problem":
the typical bios function doing DI : do something : EI.
how to call such functions in an interrupt handler.
with the style "things always run EI".
the interrupt handler runs under EI. and with a blocking variable against the double interrupt.
now one can use the bios function for writing the PSG in the interrupt.
it does EI, but one just no more does bother.

in case you still got a problem, use master disable interrupt Big smile
the IM2 server goes "if masterdi != 0 then return with disabled interrupts".
it is a nested counter that you increment and decrement.

		ld hl,masterdi
		inc (hl)
		
		... EI has no effect ...
		
		ld hl,masterdi
		dec (hl)

but likely its all about one of your interrupt handlers. which you want to exclude.
so instead using the global masterdi, have a variable to block that one.

		ld hl,handlerdi
		inc (hl)
		
		... may call bios functions who do EI, la la la ...
		... other interrupts might still get served, things are more preemptive multitasking, la la la...
		
		ld hl,handlerdi
		dec (hl)
		EI			;in case someone is waiting
handler:	push af
		ld a,(handlerdi)
		and a
		jr z,handlerdo
		pop af
		ret			;return without EI, the deal!
		
handlerdo:	;usual device handling
		...

The thing has same effect as a nested DI.
Except that it can be more preemptive multitasking.

Fast loops can go as usual DI : do something : EI with just 2 NOPs time spent on the whole issue.
this ended up being faster as-if push af would save the DI flag.
that z80 bug is great Big smile

By Grauw

Ascended (9071)

Аватар пользователя Grauw

27-12-2015, 21:40

The problem I tried to solve is that some things need to disable the interrupts temporarily (e.g. setting a VDP register), but I do not want to manually enable interrupts after every call to such function, nor do I want to have two implementations, one which enables it and one which doesn’t (you can end up with a lot of duplicated code if it is called by functions which also need to keep it disabled, etc).

Essentially, I want to deal with di/ei state as little as possible. It’s difficult to keep track of, and can cause annoying bugs if you make a mistake, so I prefer to just avoid the topic entirely. If something disables the interrupts, I want it to do it transparently.

Of course in practice I keep interrupts enabled at nearly all times and the few places where I wouldn’t (e.g. ISR) I probably would use specialised more efficient code, so maybe I’m solving a non-problem Smile and should just happily enable interrupts and deal with the cases where I need to keep it disabled individually.

Страница 2/4
1 | | 3 | 4