Interrupt Mode 1 on RC2014 with SIO/2 Board

715 views
Skip to first unread message

Michael Kamprath

unread,
Aug 20, 2018, 1:21:18 AM8/20/18
to RC2014-Z80
So I have been working on a high level library for programming J.B. Langston's TMS9918A board, and I have gotten to the point where I wanted to move things like sprites and smooth scrolling to operate out of an interrupt driven by the VDP scan refresh interrupt. Since the TMS 9918A can't put data in the bus in conjunction with it's interrupt to identify the interrupt source, I am forced to use the interrupt mode 1 on the Z80. My first go at writing an mode 1 interrupt handler didn't end well, and I struggled to figure out why. After much trial and error, I realized that the printf() calls I was making in my C code while my assembly level interrupt was running was somehow conflicting. Further research has given me this theory: since the printf() calls eventually send data through my SIO/2 card to reach terminal, and since the SIO/2 card operates on interrupt mode 2, there is some sort of conflict. When I remove the printf() calls while my IM1 interrupt is active, everything works perfectly. I've done plenty of internet searches looking for some documentation to help me understand the issues here, but it is all either too terse to be educational or more about either MSX or Sinclair ZX platforms, rather than CP/M running on the RC2014. 

Since this is certainly at the edge of my knowledge, I thought I'd tap this group pointers, insights, or resources I can use for learning how to best manage interrupts on a typical RC2014 setup. Hopefully my learnings will result in a high level library all can use for the TMS9918A board. 

Thanks!

Michael

Jay Cotton

unread,
Aug 20, 2018, 9:41:33 AM8/20/18
to RC2014-Z80
CP/M on rc2014 runs mode 2.  You really can't mix them.  So, learn about mode 2 interrupts.  You may need to use the CTC board as an interrupt generator for the TMS8818, since it surely does not now how to
to a mode 2 vector.  

jc

Mark T

unread,
Aug 20, 2018, 9:46:49 AM8/20/18
to RC2014-Z80
 I think the SIO would be using IM2 and I don't think its possible to use two different interupt modes at the same time.

For systems using the SIO, you could possibly rewrite the BIOS to change to IM 1, and poll the status of the TMS9918A or SIO to decide which routine to execute.

As you are setting out to write support for the TMS9918A it might be easier to use a CTC as the interupt handler to support IM2. This is the method used by the Sord M5, while the later MSX machines used IM1 but did not use other IM2 peripheral devices.

If you are going to use the CTC to handle interupts you may as well copy the Sord M5 and use TRG3 for the INT and TRG2 for GROMCLK. I'm not sure but I think GROMCLK is used to time the frame blanking period where access to the VDP ram does not require an 8us delay between reads or writes.

MSX was probably the most popular system using the TMS9918A, and would probably be worth trying to change the RC2014 bios to use IM1 for the SIO, but I'm not sure how difficult that might be.

Alan Cox

unread,
Aug 20, 2018, 9:52:10 AM8/20/18
to rc201...@googlegroups.com
> MSX was probably the most popular system using the TMS9918A, and would probably be worth trying to change the RC2014 bios to use IM1 for the SIO, but I'm not sure how difficult that might be.

The SIO is designed to be usable without Z80 IM2 modes. It has the
needed interface bits to be driven either polled or in other interrupt
modes / with other processors. The CTC is actually the hardest one to
deal with in IM1.

Mark T

unread,
Aug 20, 2018, 10:29:35 AM8/20/18
to RC2014-Z80
To clarify my earlier post, use the CTC with TMS9918A only if you plan to use it in IM2, otherwise change bios to use IM1 for SIO and TMS9918A.

It should still be possibly in IM1 to use CTC as baud rate generator as it would not be generating interupts. 

Alan Cox

unread,
Aug 20, 2018, 10:55:18 AM8/20/18
to rc201...@googlegroups.com
> To clarify my earlier post, use the CTC with TMS9918A only if you plan to use it in IM2, otherwise change bios to use IM1 for SIO and TMS9918A.
>
> It should still be possibly in IM1 to use CTC as baud rate generator as it would not be generating interupts.

You can use the CTC to generate interrupts, but it's fiddly because
the CTC doesn't have any good way except via IM2 to tell you that it
interrupted. If you are counting it's ok as you can read it and see if
it counted to zero. In free running mode you pretty much have to chain
it to another counter and check that in order to work in IM1. The reti
will still do the job if it's set up properly.

In a lot of cases it's a good idea to chain timers anyway because not
only does it give you a way to check in IM1 that the timer event
occurred, the chained counter also tells you how many you missed if
you had interrupts off for a bit.

With the 9918A you shouldn't need the CTC for time interrupts anyway
as it generates a vertical blank interrupt if asked to do so.


Alan

Mark T

unread,
Aug 20, 2018, 12:08:56 PM8/20/18
to RC2014-Z80
TMS9918A interupts only at the end of each frame, the start of the blanking period. Its also usefull to know the end of the blanking period, as the timing of reads and writes to vram is different during the active frame and blanking period. I think this is what the Sord M5 is doing with GROMCLK to the channel 2 trigger, although I haven't gone far enough into the disassembled bios to find that yet. I think I was getting 29 interupts from channel 2 for each end of frame interupt from channel 3, using 256 (0) for channel 2 counter initialisation. This seems correct for 60Hz frame rate. I don't think the Sord is using a chained counter as it doesn't show in the schematics, although that would be usefull to time the end of the blanking period. I suspect the Sord is counting the GROMCLK interupts in the interupt service routine.

Mark

Michael Kamprath

unread,
Aug 20, 2018, 2:36:21 PM8/20/18
to RC2014-Z80
Yeah, to be clear, I am talking about how to use the TMS9918A's generated vertical blank interrupt to that I can do the fast data transfers during this time.  Right now, given the design the the J.B. Langston rc9918 card, when the TMS9918A fire's it interrupt signal, it is wired to the Z80's /INT line. Since the TMS9918A does nothing else other than to trigger /INT in this case, IM2 cannot be used for the rc9918 card's interrupts.

Based on various discussions here, I have some follow on questions:
  • Would the proper hardware design for using the CTC is to have the TMS9918A vertical refresh signal to be wired to an input on the CTC rather than directly to the Z80 /INT line? In this case, I do want to get every vertical refresh interrupt. Can the CTC be configure to pass the vertical refresh signal through as an interrupt AND additionally put the data on the data bus needed to make the IM2 interrupts work here? If so, are there any example circuits to look at?
  • Ignoring hardware change options (that is, the TMS9918A's vertical refresh signal fires /INT on the Z80), if I launch an app in CP/M and within that app turn on IM1 but I don't use any CP/M service (such as printf), am I good? Should I reenable IM2 before the app exits? From the best I can tell, this approach does work, but I don't know if it is ideal.
Thanks!

Michael

Alan Cox

unread,
Aug 20, 2018, 5:12:13 PM8/20/18
to rc201...@googlegroups.com
The way using the CTC works is that you wire the interrupt line of the
TMS9918A to a trigger on the CTC. The CTC then counts it. If the CTC
is set to 1 and to count down on the trigger then the CTC hits 0 and
raises an interrupt itself using IM2 and with a vector that tells you
which CTC counter triggered it. You can do similar tricks using a Z80
PIO as an interrupt controller.

If you do anything involving interrupts in CP/M then your behaviour is
undefined. So really this isn't a CP/M question but a ROMWBW or
whatever CP/M you are using question. Most CP/M (ROMWBW included)
doesn't use interrupts at all. If you have a CP/M that does and you
steal all the interrupts but don't make any CP/M calls then *probably*
you'll be okay, but there are systems out there which will break or do
really bad things like leave the floppy motor running forever. If your
code messes with interrupts and runs under MP/M it will break
horribly.

The other advantage of using a CTC is as mentioned earlier in the
thread is that you can trigger two timers from the CTC, one to
immediately cause a vblank and the second to count down and tell you
when the vblank period is over.

Alan

Mark T

unread,
Aug 20, 2018, 5:15:15 PM8/20/18
to RC2014-Z80
I haven't gone into much depth with CP/M yet, but I think is you exit with a cold restart it should re-enable IM2 interupts.

The TMS9918A will still keep generating interupts, unless you disable those prior to exit from your app, otherwise they will be treated as IM2 interupts.

One thing you could try on the hardware side to use TMS9918A in IM2 is to put pullup resistors on the data bus, during the Z80 interupt acknowledge to the interupt from the TMS9918A none of the IM2 devices would respond so the interupt vector should be 0xFF. I think 10K pull ups should be OK.

If you didn't want to use pull ups, then tri-state buffer to place a vector on the data bus when /INT is low, IEO of the last IM2 device is high, /M1 is low and /IORQ is low.

Attached schematic for Sord M5 could be used as example circuit using CTC.

Also attached simple test code that I used to check interupts from TMS9918A via CTC. This would need modifying to send I or g to the SIO in your system, and probably also to locate it to 0x100 for CP/M.
Sord_M5_TMS9918A_schematic.pdf
ctcvdp.asm

Steve Cousins

unread,
Aug 20, 2018, 5:20:45 PM8/20/18
to RC2014-Z80
Hi Michael

I believe a sensible way to get a Z80 mode 2 interrupt from the vertical refresh signal is indeed to connect it to a CTC input rather than the Z80 /INT line. You would then program the CTC to trigger an interrupt on the required input edge. You do this by setting the CTC reload counter register to 1. On the appropriate input edge the count decrements to zero, triggers a mode 2 interrupt (generated by the CTC) and reloaded to counter to 1 ready for the next appropriate input edge. So I think the answer to your first question is YES.

If you change the processor's interrupt mode to 1 in a CP/M app I believe you should put it back to 2 before exiting. You should also set the SIO to not generate any interrupts during the time the interrupt mode is set to 1. I think this is necessary as an incoming character could cause the SIO to generate an interrupt and without a mode 1 interrupt handler for the SIO interrupt you could get problems with the interrupt condition not being cleared. I'm not entirely sure what would happen here without studying the data sheets.

I designed my CTC board to provide a range of functions, including general timing, periodic interrupt generation, baud rate generation and last, but not least, to provide mode 2 interrupts from non-mode 2 devices like the TMS9918A. I have not yet checked out all these features with appropriate software, but I believe the design is sound. I've attached the schematic.

As you can see from the schematic the design provides jumpers to allow selection of the input signal to all 4 of the CTC channels. One of the options is to connect the input to a USER pin on the RC2014 bus.  Alternatively you could use a flying wire from the video card to the CTC input jumper header pin.

Sorry, I don't have a convenient example to post of a tried and tested mode 1 to mode 2 conversion using a CTC.

Steve
SC102 - Schematic - v1.1.pdf

Mark T

unread,
Aug 20, 2018, 5:41:23 PM8/20/18
to RC2014-Z80
It looks to me like JB's TMS9918A card connects the INT output of the TMS9918A directly to the /INT signal of the RC2014 bus. I didn't find schematic, just traced the layout screenshot on his latest version. If that is the case then don't connect  INT output of the TMS9918A to /INT on the RC2014 bus if any other devices are also connected to /INT.

Steve Cousins

unread,
Aug 20, 2018, 5:48:28 PM8/20/18
to RC2014-Z80
I think this mode 2 interrupt issue highlights a broader problem than is becoming an issue for the RC2014.

As more modules are produced this issue of interrupts starts to become a serious concern. I can see we will soon have multiple highly desirable modules that need interrupts to perform to their full potential. To run these at the same time we need a 'proper' solution to handling more than one interrupt.

This is a problem for the RC2014 for a number of reasons:
  • As with any expandable modular system there can be a variety of hardware combinations to support
  • The RC2014 bus has no standard mechanism to handle a mode 2 interrupt daisy chain
  • The RC2014 bus has only one pin allocated for interrupt signals

In a system without hardware options you would know what hardware is available and can write efficient interrupt code to support it. Things get more complex when the hardware is less clearly defined. You can devise software to manage this sort of thing but it is likely quite complex and will probably have significant overheads. It would also require support software to conform to a common standard. I think RomWBW has such a mechanism. 

Without a mode 2 interrupt daisy chain you can't add multiple mode 2 devices to your system. The cards I have built have headers to create a daisy chain with flying wires - rather messy. I also have jumpers to use pins 38 and 39 as a daisy chain, but that requires special backplanes and also requires other modules not to use these backplane pins. I have build some backplanes to support this approach but a 'proper' solution requires a standard approach from module designers.

Without more RC2014 bus lines dedicated to interrupt signals you can't easily build a module to manage non-mode 2 interrupts from multiple devices.

I think mode 2 interrupts are the way to go with the RC2014, but there needs to be agreement on a common solution to these issues. Otherwise we won't be able to run multiple interrupt generating modules at the same time. We will be stuck with 'solutions' like turning off the SIO interrupts when using video interrupts.

Steve









Steve Cousins

unread,
Aug 20, 2018, 5:53:42 PM8/20/18
to RC2014-Z80
Must be getting tired, lots of typos in last post. Soory!

Mark T

unread,
Aug 20, 2018, 6:00:52 PM8/20/18
to RC2014-Z80
Note the reason not to connect INT from TMS9918A directly to /INT is that the output from the TMS9918A is not open collector or open drain, so any other device generating an interupt will be putting a short circuit on the TMS9918A which doesn't have very good drive capabilities at the best of times.

Alan Cox

unread,
Aug 20, 2018, 7:19:55 PM8/20/18
to rc201...@googlegroups.com
IMHO history suggests IM2 is a pain in the backside. Not only due to
the limited number of devices in the chain but also the difficulty
both of handling non Zilog hardware, and of dealing with all the other
interesting fallout like debugging spurious vectors and a leap into
oblivion - real fun when it's caused by a hardware glitch 8)

Only one major vendor with a highly expandable system used IM2 on a
configurable system and that was Cromemco. The Cromemco systems had
little connectors on the boards and you built the IM2 chain with
flying leads according to the priority order you wanted. They were
considered some of the finest Z80 machines of their time, so I
wouldn't feel bad about flying leads 8)

Alan

Alan Cox

unread,
Aug 20, 2018, 7:30:18 PM8/20/18
to rc201...@googlegroups.com
On Mon, 20 Aug 2018 at 22:48, Steve Cousins <steve...@gmail.com> wrote:
> I think RomWBW has such a mechanism.

Last time I looked RomWBW couldn't handle interrupts at all.

> I think mode 2 interrupts are the way to go with the RC2014, but there needs to be agreement on a common solution to these issues. Otherwise we won't be able to run multiple interrupt generating modules at the same time. We will be stuck with 'solutions' like turning off the SIO interrupts when using video interrupts.

Or the cards need to agree not to just drive the IRQ line as if they
own it. The SIO and CTC are open drain. The rest you can do in
software even if it's a bit less elegant.

Alan

Wayne Warthen

unread,
Aug 21, 2018, 10:26:27 AM8/21/18
to RC2014-Z80
On Monday, August 20, 2018 at 4:30:18 PM UTC-7, Alan Cox wrote:
On Mon, 20 Aug 2018 at 22:48, Steve Cousins wrote:
> I think RomWBW has such a mechanism.

Last time I looked RomWBW couldn't handle interrupts at all.

I just want to clarify that RomWBW does support both IM1 and IM2 style interrupts.  It has a fairly complete framework for this.  However, use of interrupts is dependent on the hardware drivers being used.  In the default build of RC2014, IM1 is active for both SIO and ACIA serial ports because interrupts are mandatory to implement inbound flow control.  IM1 is used because ACIA does not support IM2.  If you build a custom ROM and omit ACIA, you can use IM2 instead.  All of this is controlled by configuration settings at build time.

On non-RC2014 platforms, it is common to use 16C550 derived UARTs which, in many cases, have built-in hardware flow control.  This is optimal and requires no interrupts, so that is why many variations of RomWBW leave interrupts disabled.

For platforms that have generic access to timer hardware, a timer interrupt is included.  This is primarily used with Z180 hardware because the CPU has a built-in timer.  So, for the RC2014 build for the Z180 CPU, an IM2 timer interrupt is enabled.

-Wayne
 

Michael Kamprath

unread,
Aug 21, 2018, 10:53:02 AM8/21/18
to RC2014-Z80
Wayne,

This is good to know. Given that RomWBW (which I am using) needs IM1 to manage SIO (which is consistent with my original problems), if I need to use IM1 too for the TMS9918A, would the right was to doings be to patch the RST $38  handler? That is, I need to write three bytes at $38, being a JP opcode and then my handler's address. But before doing so, I save the three bytes at $38-$3A, then in my handler, if I determine that the interrupt does not pertain to the TMS9918A (by checking its status register), I execute the original 3 bytes at $38 and then JP to $3B if the original 3 bytes didn't already JP me somewhere. The one issue I see with this is what if there was a collision of interrupts between the (for example) SIO and TMS9918A. The scheme I layout will not handle one of the interrupts, possibly causing one of the devices to not let of the /INT line, though I might be misunderstanding this ... am I?

Also, would it be correct to interpret if the default configuration of RomWBW uses IM1 for handling SIO, if my app were to implement IM2 that would cause another class of conflicts?

Michael

Wayne Warthen

unread,
Aug 21, 2018, 11:18:32 AM8/21/18
to RC2014-Z80
On Tuesday, August 21, 2018 at 7:53:02 AM UTC-7, Michael Kamprath wrote:
Wayne,

This is good to know. Given that RomWBW (which I am using) needs IM1 to manage SIO (which is consistent with my original problems), if I need to use IM1 too for the TMS9918A, would the right was to doings be to patch the RST $38  handler? That is, I need to write three bytes at $38, being a JP opcode and then my handler's address. But before doing so, I save the three bytes at $38-$3A, then in my handler, if I determine that the interrupt does not pertain to the TMS9918A (by checking its status register), I execute the original 3 bytes at $38 and then JP to $3B if the original 3 bytes didn't already JP me somewhere. The one issue I see with this is what if there was a collision of interrupts between the (for example) SIO and TMS9918A. The scheme I layout will not handle one of the interrupts, possibly causing one of the devices to not let of the /INT line, though I might be misunderstanding this ... am I?

You are on the right track, but the RomWBW IM1 framework takes care of some of this for you.  When RomWBW is built for IM1, it implements a call list that is processed upon every interrupt.  There can be 1 or more interrupt handlers setup in the call list.  Each handler returns with an indication whether it handled the interrupt or not.  If a handler indicates it handled the interrupt, the remainder of the call list is not processed for that interrupt.  At startup, drivers can call HB_ADDIM1 to add a new vector to the IM1 call chain.  Obviously, each interrupt handler must be able to determine if the interrupt was for it or not.  I know that SIO can do this, not sure about TMS9918A.  If TMS9918A is the last vector in the call chain, I suppose it could just assume the interrupt was for it.

Just to reinforce this.  DO NOT patch the JP instruction at $38 yourself.  RomWBW sets up this JP to go to some code in high memory that bank switches the BIOS code into CPU memory space and then switches it back after the interrupt processing is complete.
 
Also, would it be correct to interpret if the default configuration of RomWBW uses IM1 for handling SIO, if my app were to implement IM2 that would cause another class of conflicts?

Yes, the default build of RomWBW for RC2014 uses IM1 for interrupt processing.  IM1 and IM2 are mutually exclusive -- only one mode can be active and you cannot switch between them on the fly.  It is very easy to configure RomWBW to use IM2, but some devices are incapable of running with IM2 (such as ACIA and TMS9918A) which is why IM1 is the default.  So, you CAN implement IM2 in your app if you want to as long as you disable anything that requires IM1 in the build config.

Hope this helps!

Wayne

Alan Cox

unread,
Aug 21, 2018, 12:52:14 PM8/21/18
to rc201...@googlegroups.com
Sorry I should have been clearer

My understanding from the code and docs is that ROMWBW doesn't provide
any way for end user code (not compiled in ROM firmware) to use
interrupts. At least there is no documented API I can find for hooking
the HBIOS interrupt vector - correct me if I am wrong. It's one of the
the things I hit when I poked at using ROMWBW to do all the low level
work for Fuzix or MP/M on N8VEM V2.

And yes I'd really like to be able to use ROMWBW for such things
because I'm all for lazy programming and doing the job once but
setting 257 bytes to 0xFE and stealing the vector with IM2 struck me
as a bit ugly 8)

Alan

Wayne Warthen

unread,
Aug 21, 2018, 2:57:42 PM8/21/18
to RC2014-Z80
On Tuesday, August 21, 2018 at 9:52:14 AM UTC-7, Alan Cox wrote:
My understanding from the code and docs is that ROMWBW doesn't provide
any way for end user code (not compiled in ROM firmware) to use
interrupts. At least there is no documented API I can find for hooking
the HBIOS interrupt vector - correct me if I am wrong. It's one of the
the things I hit when I poked at using ROMWBW to do all the low level
work for Fuzix or MP/M on N8VEM V2.

Ah, got it.  Yes, you are right.  Interrupt management is entirely internal to RomWBW.

And yes I'd really like to be able to use ROMWBW for such things
because I'm all for lazy programming and doing the job once but
setting 257 bytes to 0xFE and stealing the vector with IM2 struck me
as a bit ugly 8)

I would love to get some input on what API functions would make it possible to expose required interrupt functionality outside of the ROM itself.  I did look at MP/M a while back and understand that it wants to hook a timer interrupt.  Is there anything more required?  If anyone has thoughts on this, please speak up!

Thanks,

Wayne 

Alan Cox

unread,
Aug 21, 2018, 5:11:43 PM8/21/18
to rc201...@googlegroups.com
> I would love to get some input on what API functions would make it possible to expose required interrupt functionality outside of the ROM itself. I did look at MP/M a while back and understand that it wants to hook a timer interrupt. Is there anything more required? If anyone has thoughts on this, please speak up!

Brain dump... not entirely thought through so caveat emptor etc.

The important document is The MPM II Operating System System
Implementor's Guide, which is around but not so easy to find and I
think historically only available to licensees.

http://www.retroarchive.org/cpm/archive/unofficial/drilib.html

Most BIOS support for MP/M is there I think including support for
multiple consoles and the ability to ask if a console write would
block.

MP/M wants an interrupt, but it also needs to control the exit from
that interrupt as it doesn't finish interrupt handling with EI RET but
your BIOS jumps to an address supplied by MP/M which does magic. The
reason for this is that you are quite likely to go into an interrupt
from one task and come out in another. Fuzix has similar needs for the
same reason.

So for the interrupt I'd think something like

CHAIN_INTERRUPT
HL = address in common area to jump to at the end of BIOS interrupt processing
or 0 to unchain

Return OK or BUSY (already chained)

Which would just replace the final EI RETI with JP xxxx, or put back
the EI RET(I)

The called routine is then responsible for saving any register it
uses, doing any RETI, switching banks back if it moves them etc. It
also needs to be responsible for RETI because in many cases you have
code doing

LD HL,#foo
PUSH HL
RETI
foo: DI

as it needs to generate the RETI cycle before task switching but
whilst still in the interrupt handler with interrupts blocked.

MP/M does also want a timer interrupt, and it needs to know if the
interrupt is a timer event or not so I guess there would need to be a
variable/method to report the interrupt causes or similar. If you've
got a real RTC it likes to turn the interrupt off when it doesn't need
it but that's an optimzation detal. It likes 10Hz but obviously a
7.3MHz Z80 is a bit different to the 1MHZ 8080 it anticipated.

So I'd guess

SET_TIMER A = 1 / 0
Start or stop timer. Returns the timer frequency in HZ. Errors
NO_TIMER, NO_TIMER_STOP

Other than that it wants to be able to disk I/O into the currently
selected bank or common space.

What I don't know enough about is how the memory management would
work. In the conventional model MP/M has multiple banks (usually 48K)
and a 16K fixed common. That bit is easy but the 'what happens if
ROMWBW is paged and you take an interrupt' question is trickier. I
guess the MP/M BIOS knows if it's calling ROMWBW so could just defer
the event until after ROMWBW returns (and count timer ticks if need
be). For N8VEM Mark 2 you are stuck with 32/32 so you'd need to
generate different MP/M or I guess just recognize MP/M isn't that
useful on it.

I guess you'd need a way to query/set the banking arrangement but that
also changes the bank allocation logic because presumably all your
bank numbers change if you do it. That to me seems the hairy bit for
MP/M. Fuzix wants to control the 16K banks independently if they are
present although to be honest with 512K of RAM a 48K/16K faked banking
setup wouldn't be much worse.

SET_BANK_BOUNDARY 32K | 48K | ?? (I guess with Z180 anywhere

OK | NOT SUPPORTED

but I have no idea how the allocation API would then behave.

The re-entrancy question is a more tricky one. MP/M needs to be able
to test if there is character I/O pending or room for writes from
within an interrupt. Fuzix is slightly more antisocial and also wants
to be able to read/write characters in one (blame the designers of
Unix not me). In both cases it's not quite as bad as it could be.
Re-entrancy to ROMWBW could be avoided, and the cases where it calls
ROMWBW from an interrupt will be after the chained interrupt so I
guess to ROMWBW after an interrupt. If need be the caller could just
defer any action on an interrupt during a ROMWBW call.

Beyond that MP/M is pretty isolated from real interrupt structure. It
provides methods to have your code called regularly to poll for
events, or you can wait for and clear a flag (from 1-32) and provides
an interrupt callable routine that sets the flag. It's otherwise
oblivious to real interrupt goings on in the BIOS and below.

Disk I/O is serialized. MP/M has a single threaded disk I/O system so
while it can do clever stuff like run another task during a disk DMA
it doesn't re-enter the disk I/O code, and I don't think that matters
because AFAIK no ROMWBW system can actually do asynchronous disk I/O
anyway can it ?

Alan

Wayne Warthen

unread,
Aug 21, 2018, 7:35:17 PM8/21/18
to RC2014-Z80
This is extremely useful.  Thank you Alan!

I will take some time thinking about this.

-Wayne

Michael Kamprath

unread,
Aug 21, 2018, 8:07:17 PM8/21/18
to RC2014-Z80


On Tuesday, August 21, 2018 at 11:57:42 AM UTC-7, Wayne Warthen wrote:

I would love to get some input on what API functions would make it possible to expose required interrupt functionality outside of the ROM itself.  I did look at MP/M a while back and understand that it wants to hook a timer interrupt.  Is there anything more required?  If anyone has thoughts on this, please speak up!


My use case is simple: I would like to run some code with the TMS9918A vertical refresh fires. In the context of a shared IM1 interrupt, I think some specific requirements would be (other than the obvious "have a way to add an interrupt handler to the chain"):

  • I would want to be able to set the priority of my handler over the priority of others. The reason for this is when the TMS9918A refresh fires, I have a set amount of time to do what I need to do. If my handler was last in the chain of interrupt handlers, there is a good chance the handlers before mine would urn that time window for me. I would like to guarantee my handler is always first  one called. Maybe be able to request that my handler be the first called (and do tell me if it can't be). 
  • I would like to not have to worry about restoring the original the register state when my handler is done.
  • I would like to be able to pause, uninstall, or even change my handler, even from within the handler itself. 
  • There probably should be a design expectation that a handler's first task is to determine if the interrupt pertains to it, and if not, immediately return. Even if a handler doesn't need to be first, it probably doesn't want handlers ahead of it stealing unneeded cycles.
  • I would like to know if IM1 or IM2 interrupts are expected or support (I suspect this is just a documentation requirement).

Michael

Wayne Warthen

unread,
Aug 21, 2018, 9:52:27 PM8/21/18
to RC2014-Z80


On Tuesday, August 21, 2018 at 5:07:17 PM UTC-7, Michael Kamprath wrote:
My use case is simple: I would like to run some code with the TMS9918A vertical refresh fires. In the context of a shared IM1 interrupt, I think some specific requirements would be (other than the obvious "have a way to add an interrupt handler to the chain"):

  • I would want to be able to set the priority of my handler over the priority of others. The reason for this is when the TMS9918A refresh fires, I have a set amount of time to do what I need to do. If my handler was last in the chain of interrupt handlers, there is a good chance the handlers before mine would urn that time window for me. I would like to guarantee my handler is always first  one called. Maybe be able to request that my handler be the first called (and do tell me if it can't be). 
Installing your custom interrupt vector dynamically with a desired priority is probably ok.  I can envision a couple ways to handle this.
  • I would like to not have to worry about restoring the original the register state when my handler is done.
No problem.  As long as you return from your routine via a "RET" instruction, all registers will be restored.  Actually, to be clear, the AF, BC, DE, HL, and IY registers would be restored.  IX and the alternate register set would be your responsibility.
  • I would like to be able to pause, uninstall, or even change my handler, even from within the handler itself. 
Uninstall is fine.  However, I'm not sure what pause means.  If a hardware interrupt fires, it needs to be handled.  I think pausing would require disabling the interrupt temporarily in the hardware registers of the TMS9918A.  Also note that if you uninstall the interrupt vector, then it would be essential that the hardware be instructed to stop generating interrupts.
  • There probably should be a design expectation that a handler's first task is to determine if the interrupt pertains to it, and if not, immediately return. Even if a handler doesn't need to be first, it probably doesn't want handlers ahead of it stealing unneeded cycles.
Definitely.  Impossible to enforce pro grammatically, but that is a best practice for any IM1 interrupt handler. 
  • I would like to know if IM1 or IM2 interrupts are expected or support (I suspect this is just a documentation requirement).
I need to think about IM2 a bit.  Installing an IM2 interrupt vector is a little different because each possible interrupt lives in a slot in a table (there is no list of interrupt handlers).  In the case of an IM2 environment, you would need to supply the slot for the vector because the slot number is a function of the hardware programming.

---

Some additional things to consider:

Your code will need to locate itself at or above $8000 (in the top 32K).  The interrupt management system assumes that the BIOS bank must be activated as soon as an interrupt occurs.  When that happens, the lower 32K of TPA is swapped out and the RomWBW HBIOS code is swapped in.  It would be very painful to do nested swapping.  So, your interrupt processing would need to reside entirely above $8000 and not make any calls into memory below that.

Normally, interrupt processing is the domain of the BIOS and OS.  Nothing impossible about end user code handling interrupts, but many ways that the system could go haywire.  You would need to be VERY careful to not enable the TMS9918A hardware interrupts until after your vector is installed.  You will need to be sure to disable them prior to exiting your code and be sure to remove your vector from the call list.  CP/M programs have a habit of just "bailing out" via a soft reset at any time.  Leaving your program without the required clean up would be an immediate disaster.

Making CP/M BDOS/BIOS or RomWBW HBIOS calls within your interrupt handler is likely to be problematic.  Some things may work, but I suspect most things will not.

Thanks,

Wayne

Michael Kamprath

unread,
Aug 22, 2018, 4:23:41 AM8/22/18
to RC2014-Z80


On Tuesday, August 21, 2018 at 6:52:27 PM UTC-7, Wayne Warthen wrote:

On Tuesday, August 21, 2018 at 5:07:17 PM UTC-7, Michael Kamprath wrote:
  • I would like to be able to pause, uninstall, or even change my handler, even from within the handler itself. 
Uninstall is fine.  However, I'm not sure what pause means.  If a hardware interrupt fires, it needs to be handled.  I think pausing would require disabling the interrupt temporarily in the hardware registers of the TMS9918A.  Also note that if you uninstall the interrupt vector, then it would be essential that the hardware be instructed to stop generating interrupts.

Sorry, got a little prolific with my words here. Uninstalling the interrupt handler would be an adequate way of handling all use cases.
 
  • I would like to know if IM1 or IM2 interrupts are expected or support (I suspect this is just a documentation requirement).
I need to think about IM2 a bit.  Installing an IM2 interrupt vector is a little different because each possible interrupt lives in a slot in a table (there is no list of interrupt handlers).  In the case of an IM2 environment, you would need to supply the slot for the vector because the slot number is a function of the hardware programming.


Maybe IM2 is for version 2? ;-)


Your code will need to locate itself at or above $8000 (in the top 32K).  The interrupt management system assumes that the BIOS bank must be activated as soon as an interrupt occurs.  When that happens, the lower 32K of TPA is swapped out and the RomWBW HBIOS code is swapped in.  It would be very painful to do nested swapping.  So, your interrupt processing would need to reside entirely above $8000 and not make any calls into memory below that.

Seems reasonable ... though I would need to figure out how to do this in general using the mix of C and assembly that I am doing in z88dk. Since the interrupt I would be writing would be used for managing sprite movement and background scrolling, that means my pattern data must be above $8000 too (just thinking aloud). 
 
Normally, interrupt processing is the domain of the BIOS and OS.  Nothing impossible about end user code handling interrupts, but many ways that the system could go haywire. 

Totally understand. I am trying to write my code to be generic, but I want it to run on RomWBW because I like the system. Also, I note that RomWBW has simple support for the TMS9918A already ... may I presume pull requests are welcome (though my efforts are certainly going to be "slow going"). 
 
You would need to be VERY careful to not enable the TMS9918A hardware interrupts until after your vector is installed.  You will need to be sure to disable them prior to exiting your code and be sure to remove your vector from the call list. 

Again, a very reasonable expectation. 
 
Michael

Alan Cox

unread,
Aug 22, 2018, 7:01:26 AM8/22/18
to rc201...@googlegroups.com
IM2 also needs an allocator, and if you are routing IM1 devices via a
CTC you have the whole 'interrupt controller' to describe somehow.

At minimum though for IM2 you'd also need to be able to allocate some
number of IM2 vectors on a given power of two boundary so you don't
clash with whatever the firmware has assigned to devices it manages.

The CP/M world never really addressed portable graphics code for
anything but basic graph drawing (GSX). To be fair MSDOS never did
either nor Windows for a long time.

Wayne Warthen

unread,
Aug 28, 2018, 10:02:07 PM8/28/18
to RC2014-Z80
Well, I just checked in some changes that should be what you need.  The changes are in the RomWBW master branch in GitHub -- you will need to do your own build to pick them up.  Added API functions to get current interrupt info (mode, etc.), get and set interrupt vectors.  New API calls are basically documented in the API.txt file in the Source/HBIOS subdirectory.  There is a test application called INTTEST in the Source/Apps directory.  I have tested this under IM1 and IM2 successfully.

Note that the current API changes are not sufficient for MP/M, but should be fine for you Michael.

I'm sure there will be some questions, so don't hesitate to ask.

Good luck and have fun!

Wayne

Michael Kamprath

unread,
Aug 29, 2018, 8:01:40 PM8/29/18
to RC2014-Z80
Oh wow, cool. I will need to figure out how to generate a build using the Windows-based toolset. I am on a Mac. Is there a process for doing a Mac-hosted build? Once I get a build together, I will test it.

Thanks!

Michael

Wayne Warthen

unread,
Aug 29, 2018, 8:50:17 PM8/29/18
to RC2014-Z80
Sorry, but there is not currently a Mac-based build script.  In theory, it could be done since all the required tools can run there, but would be some effort to cobble it all together.

I will go ahead and create a full release of RomWBW and publish it.  You can work from that I think.  Later tonight, you should find RomWBW v2.9.1-pre.7 available on GitHub in the "releases" tab.

-Wayne

Michael Kamprath

unread,
Aug 30, 2018, 12:50:34 AM8/30/18
to RC2014-Z80
I did find the Linux build instructions in the Contrib directory. I think I can adapt those for the Mac given the unit toolset.

Michael

Wayne Warthen

unread,
Aug 30, 2018, 8:14:32 PM8/30/18
to RC2014-Z80
Sounds good Michael.  I do think those instructions are a bit dated.  Would be nice if you want to contribute any revisions as you work through the process.  :-)

-Wayne

Michael Kamprath

unread,
Dec 10, 2018, 12:44:48 AM12/10/18
to RC2014-Z80
Wayne,

Just to follow up, I haven't forgotten about doing this. My day job just has gotten pretty busy lately. With that said, I have been able to get portions of the RomWBW to successfully build on a Mac using Wine to emulate Window (to run TASM.EXE) and Makefiles. I suspect my approach will work on Linux too, though I will let someone else test that when I have the complete build created. I should have some time over the holidays, maybe I can get it done then.

Michael

Wayne Warthen

unread,
Dec 10, 2018, 7:59:09 PM12/10/18
to RC2014-Z80
Certainly no rush Michael.  I understand how life gets in the way.  Good luck!
Reply all
Reply to author
Forward
0 new messages