Preface: I have a bit of a headache with this, as it feels a bit more like a personal rant topped with a question. At the same time he's diving right into the real beauty of the II.
One of the weirdest misfeatures of the Apple II - and perhaps the dumbest thing Woz ever designed - is the vestigial priority system for IRQ and DMA on the Apple II expansion bus.
Vestigial might be quite misleading as a description. It is offering the ability to implement a priority chain with minimal effort. For sure this is better than no provision at all, don't you think so?
And accusing Woz of being dumb is usually only possible if one hasn't looked at the whole picture.
Each slot has four pins devoted to this system: an input and output for each priority signal. Expansion cards are supposed to check these signals and refrain from signalling IRQ or DMA when a higher priority card is doing so.
Right. It follows the basic implementation decision of Woz to use geographic addressing. That is, each slot has fixed resources assigned. This goes for I/O address area and ROM addresses as well as IRQ and DMA priority. Unlike a random (parallel) design like the ISA Bus or S100, where every card is equal - and has to burden to decode everything they need in full. Circuitry to decode are needed on each and every card again. Also, each card needs to be configured before usage to answer the right addresses.
Geographical decoding in contrast needs no hardware configuration. This is done the moment a card is plugged. It's not wrong to call the Apple II a Plug and Play system before the term was coined (*1). Since Woz moved decoding to the main board, building an expansion card became easy. A simple Port could be added without any glue logic at all. On the PC in contrast it did need several TTL, or a PAL to add even the most simple device.
It's worth noting, that Apple cards could of course use different address schemes by spending more hardware. In fact, unlike with the PC, a card could even disable the whole board selection and do its own thing.
And the same principle is added to interrupts and DMA. To allow more than one card issuing interrupts or taking over the buss a simple priority chain was added. There is no difference to what other systems did, except for the simplicity of design. Where the PC used a 8259 to prioritize and handle multiple interrupt lines, plus the need to spend many slot lines to carry them, the Apple only needed two contacts.
To me it's a great example about squeezing out the most versatile concept with the least effort.
It might be interesting to notice that next to every other bus system evolutionary switched to some form of geographic addressing at some point. Motorola did it with VME64, IBM with MCA while compatibles added it due PCI.
(There are several other strategies, all of them do need more hardware/software to implement)
For IRQ, this system is completely useless. Software interrupt handlers check all interrupt sources in sequence anyway,
And usually break as soon as one handler assumes responsibility for a detected source, not handing over to the next.
and the IRQ signal to the CPU is open-collector and level-triggered (thus multiple simultaneous IRQs are safe and will work correctly).
No, they are not. It's depending on software structure and the way certain hardware offers and handles interrupts. Especially in a modular system there are endless ways to screw this up - not least lazy programmers not expecting other interactions.
So there is no need to avoid contention, and no particular reason a card should pay any attention to this signal.
It sure is a great way to make sure that there is only one interrupt at a given time, and more importantly, that no lower priority card is issuing an interrupt when a higher one is still being handled. From a certain point of view (and when handled accordingly) the INT-Chain works much like the priority decoder and locking like within an Intel 8259. Interrupts are served in order of priority to the CPU and new interrupts are only issued when the last one has been acknowledged.
Of course, all of this can also be handled by sophisticated software - at the expense of slower handling and way more complexity. Therefore the INT chain can be seen as optional if the goal does not include optimal performance.
For DMA, though, it's potentially more important. Only one card can use DMA at a time, so you need some way to avoid contention. Unfortunately it still doesn't work very well. One major problem is that the system only works if all the cards using DMA are in adjacent slots, because an empty slot breaks the chain.
Right, that's one of the requirements. The basic idea of the slots is to fill them left to right.
This, combined with the use of separate in and out signals in the first place, implies that you are supposed to only signal downstream if you want to perform DMA. But on the other hand, if you only signal downstream, a potential upstream card doesn't know that you've started DMA, and might interrupt your DMA transaction with its own. Apparently, the intention was that a higher priority card could actually do this and you were supposed to watch for it and get off the bus.
Exactly that is the premise of a simple priority chain. Higher priority card overwrites lower priority. All is handles on a single byte level synchronized by the system clock. This decision is to be done for each cycle. There is no such thing as a multi byte transaction - well, except for the card in slot 1 that is :)
Unfortunately, there's no timing information provided in any documentation I've found. A compliant card could simply signal at the same time it begins a transaction, giving the downstream card no time to respond and leading to a potential bus fight.
There is plenty of time, as all it needs to do in the first place is disable the drivers of a lower priority card.
This would also leave memory in an inconsistent state, with the downstream card's transaction half done.
There is no such thing as a half done transaction. Either the byte is read or written, or not.
The reference manuals usually mumble something about "cards should usually connect the IN and OUT lines together" without saying anything about how to actually use them to initiate DMA.
I thought of it as pretty self explanatory.
The whole Phi1 high phase is available for arbitration, so even if you take away a few hundred nanoseconds for the address generation etc, there is still plenty of time to disable the address output buffers before the active access starts if a higher prioritized card signals that it will use the bus.
It would be easy to read that to mean you should signal both ways. Making matters worse, the schematic shows lower numbered slots having higher priority, but at least one source (IIe tech reference) says HIGHER numbered slots have priority!
Would you mind supplying a page and wording? I just checked my IIe Ref and couldn't find it. My version got the section about the chains on page 170.
Unsurprisingly, DMA cards in the Apple II were frequently incompatible with each other.
As so often with many cards on many systems. If you think the Apple II is badly documented, never try to debug a complex S100 Bus system :)
Beside cards which simply ignore DMA priority, the most common problem are cards using DMA but not taking into account that others may need the bus. A typical carelessly acting card is Microsoft's Softcard (Z80 board) in default setting (S1-2 off). When activated it grabs the bus full time, only releasing it for one cycle during Opcode decoding (M1), so 6502 refresh gets a chance. If there's another, well behaving DMA card down the line, it will grab the bus during that cycle and the 6502 will soon starve to death.
Now, if switched to obey DMA_IN (S2-1 on), the Soft-Card is pretty handsome, and can give way for an unlimited time, as the Z80 is still clocked, just halted. Of course, if the higher priority card relinquishes the bus for only one clock cycle, the Softcard will take over again and do at least two cycles (one 6502 cycle). As Z80 instructions may have several cycles (up to 14?), the 6502 may have to wait for a cycle until one Z80 instruction is finished that way, which may again result in starvation (see #1 in additional reading).
The whole DMA protocol is designed for cards that sometimes need fast access. Cards which taking over the system are an anomaly. But thanks to the Softcard default setting, many other CPU cards (see #2 in additional reading) didn't much care about higher priority DMA.
In a perfect world (or at least with well behaving cards), a CPU card should be installed in slot#7 (*2) and obey DMA_IN. Now it would really behave as a CPU replacement and being able to use other DMA devices. Sure, it might need a bit more logic to keep the 6502 alive.
In fact, if we want to point something out where Woz went short it's keeping the 6502 alive during lengthy DMA cycles. A provision on the main board might have been a good idea. Then again, it would have not only made the protocol way more complex (now a back channel for a lower priority used to request the bus against priority would be needed and managed), also, at least one additional counter would have been needed on the main board. Considering that other computers of the same time (and even way later) had no provision for prioritizing DMA - or even DMA at all, I think he found a great way to enable this functionality without spending much hardware for an exotic case. I say it was impossible to imagine that Z80 cards later became defacto standard in most II's.
An equally common cause for incompatibility of DMA cards have been subtle differences between the II, IIe and IIgs. The buffer circuitry has changed between these machines, resulting in a slightly different DMA timing (see #3 in additional reading).
Did cards just ignore the DMA priority lines, did they signal one way or both ways,
Some did, some don't. After all, the number of active cards are rather rare, and multiple of them even more.
- Most non-DMA cards followed Apple's recommendations and loped DMA_IN/DMA_OUT.
- Some didn't and will produce problems in complex combinations
- Many simple CPU cards do just grab DMA and go on without obeying DMA_IN
- Some cards can do, but won't always, like the Softcard which must be switched (S1-2 on) to do so.
- Some always do.
So pick your combination :)
or could they even potentially be damaged (bus fight) if someone tried to drive signals upstream on the "IN" line?
That depends on the individual card design, but I wouldn't recommend it, as it doesn't make any sense.
Did higher-priority cards watch the open-collector DMA line to avoid interrupting existing transactions?
Not really useful, as they could as well trust the protocol. A case like that would be hard to resolve anyway.
Did cards observe some informal software protocol to deal with the problem?
No, at least not in the beginning. With the Apple Mouse card a software protocol for interrupt sharing was established using so called screen holes.
Or was it more of a "pray, and tell the user to turn off DMA if there's a problem" sort of thing?
I'd say it was something restricted to high class use cases that usually did require more thinking and experiments anyway.
The grandfather of all DMA cards is no doubt Microsoft's Softcard. Typically for back then a rather good documentation was available, including a thorough hardware description and schematics. Since scans are somewhat bad to read, people even redid the schematics.
There is a real well made description by the German magazine mc issue Sept.1985 of a 68008 card. The article starts with a thorough description of DMA access. The Bus handling is like most (simple) CPU cards do. 1 in 8 cycles are given to the 6502 (or more correct lower priority cards). It does handle DMA_OUT while ignoring DMA_IN. Wonders of the internet, there is an acceptable English translation available.
Apple also added some information in 1983 via the Apple II Technical Note called Apple IIe #2: Hardware Protocol for Doing DMA. Sadly it focuses on DMA timing itself rather than cooperation of cards.
*1 - I'd even say the major reason to come up with such a phrase was due the complicated procedures needed to be handled on the PC before the work arounds where added.
*2 - Microsoft recommends slot#4 and #7 only if #4 is occupied. Nonetheless it worked fine in any slot.