VOGONS


First post, by keenmaster486

User metadata
Rank l33t
Rank
l33t

This code works fine on my real 286 and real 486, and every emulated system I've tried it on - EXCEPT for original PC/XT/clones/8088 based systems, presumably all with XT keyboard controllers.

I know there's something different about the XT controller, but am I not reading the scancodes in the XT way here? The AT keyboard should be in the XT compatibility mode anyway, so it shouldn't matter... right?

In MartyPC the first key you press is the scancode that will come through for each subsequent key, that is, the same scancode comes through over and over no matter what key you press. In 86Box, no scancodes make it through at all. The interrupt never fires.

I know there's something wrong here but I have no idea what it is.

I attached the compiled program so you can see what I'm talking about.

#include <iostream>
#include <conio>
#include <dos>

using namespace std;

#define K_ANY 0
#define K_ESC 1
#define K_1 2
#define K_2 3
#define K_3 4
#define K_4 5
#define K_5 6
#define K_6 7
#define K_7 8
#define K_8 9
#define K_9 10
#define K_0 11
#define K_MINUS 12
#define K_EQUAL 13
#define K_BKSP 14
#define K_TAB 15
#define K_Q 16
#define K_W 17
#define K_E 18
#define K_R 19
#define K_T 20
#define K_Y 21
#define K_U 22
#define K_I 23
#define K_O 24
#define K_P 25
#define K_LBRK 26
#define K_RBRK 27
#define K_ENTER 28
#define K_CTRL 29
#define K_A 30
#define K_S 31
#define K_D 32
#define K_F 33
#define K_G 34
#define K_H 35
#define K_J 36
#define K_K 37
#define K_L 38
#define K_SEMI 39
#define K_APOS 40
#define K_TILDE 41
#define K_LSHFT 42
#define K_BSLSH 43
#define K_Z 44
#define K_X 45
#define K_C 46
#define K_V 47
#define K_B 48
#define K_N 49
#define K_M 50
#define K_COMMA 51
#define K_DOT 52
#define K_SLASH 53
Show last 93 lines
#define K_RSHFT	54
#define K_PSCRN 55
#define K_ALT 56
#define K_SPACE 57
#define K_CAPS 58
#define K_F1 59
#define K_F2 60
#define K_F3 61
#define K_F4 62
#define K_F5 63
#define K_F6 64
#define K_F7 65
#define K_F8 66
#define K_F9 67
#define K_F10 68
#define K_NUMLK 69
#define K_SCRL 70
#define K_HOME 71
#define K_UP 72
#define K_PGUP 73
#define K_NDASH 74
#define K_LEFT 75
#define K_CENT 76
#define K_RIGHT 77
#define K_NPLUS 78
#define K_END 79
#define K_DOWN 80
#define K_PGDN 81
#define K_INS 82
#define K_DEL 83
#define K_F11 87
#define K_F12 88


unsigned char keys[256];

void interrupt keyboardInterrupt() {
static unsigned char buffer;
unsigned char rawcode;
unsigned char make_break;
int scancode;

rawcode = inp(0x60);
cout<<"rawcode: "<<(unsigned int)rawcode<<endl;
make_break = !(rawcode & 0x80);
cout<<"make_break: "<<(unsigned int)make_break<<endl;
scancode = rawcode & 0x7F;
cout<<"scancode: "<<scancode<<endl;

if (buffer == 0xE0) { // second byte of an extended key
if (scancode < 0x60) {
keys[scancode] = make_break;
keys[K_ANY] = make_break;
}
buffer = 0;
} else if (buffer >= 0xE1 && buffer <= 0xE2) {
buffer = 0; // ingore these extended keys
} else if (rawcode >= 0xE0 && rawcode <= 0xE2) {
buffer = rawcode; // first byte of an extended key
} else if (scancode < 0x60) {
keys[scancode] = make_break;
keys[K_ANY] = make_break;
}

// Acknowledge interrupt:
outp(0x20, 0x20);
}

static void interrupt (*oldKeyboardInterrupt)();

void initKeyboard(void) {
oldKeyboardInterrupt = _dos_getvect(0x09);
_dos_setvect(0x09, keyboardInterrupt);
}

void unInitKeyboard(void) {
if (oldKeyboardInterrupt != NULL) {
_dos_setvect(0x09, oldKeyboardInterrupt);
oldKeyboardInterrupt = NULL;
}
}


int main () {
initKeyboard();
while (!keys[K_ESC]) {
__asm {
nop
}
}
unInitKeyboard();
}
The attachment TESTKEYS.EXE is no longer available

World's foremost 486 enjoyer.

Reply 1 of 15, by keenmaster486

User metadata
Rank l33t
Rank
l33t

I figured it out.

	rawcode = inp(0x60);

// The following two lines are copied from ID_IN.C from the Keen source code.
// I didn't know you had to do this. Couldn't find any info on it anywhere.
// But the keyboard code doesn't work on XT systems without it.

// Tell the XT keyboard controller to clear the key
outp(0x61, (temp = inp(0x61)) | 0x80);
outp(0x61, temp);

World's foremost 486 enjoyer.

Reply 2 of 15, by mkarcher

User metadata
Rank l33t
Rank
l33t
keenmaster486 wrote on 2025-04-15, 15:49:
I figured it out. […]
Show full quote

I figured it out.

	// Tell the XT keyboard controller to clear the key
outp(0x61, (temp = inp(0x61)) | 0x80);
outp(0x61, temp);

The code is correct. The comment is imprecise. The XT has no keyboard controller, but a synchronous serial receiver built from discrete components which is read through a 8255 parallel interface chip. The XT keyboard interface blocks the keyboard until you acknowledge you got the code, so no key codes get lost. The AT keyboard controller notices reads from port 60 and automatically unblocks itself when you read the key code, but on the XT, you need to manually pulse a bit that re-arms the receiver for the next code. Port 61 is acessing the same 8255 parallel interface chip as port 60: This chip has three 8-bit ports that are connected to the ISA bus: Port A is an 8-bit keyboard scancode input port. Port B is a miscelaneous output port (lowest two bits control the speaker, the top bit interacts with the keyboard interface. Port C (on ISA port 62) is an input port again, and used amongst other things to read DIP switches from the mainboard.

Reply 3 of 15, by keenmaster486

User metadata
Rank l33t
Rank
l33t

Thanks for the clarification! Makes sense now.

World's foremost 486 enjoyer.

Reply 5 of 15, by keenmaster486

User metadata
Rank l33t
Rank
l33t

Wow. I wish I had seen that before. I sympathize with his frustration that all of the resources online are for AT systems, since I found that to still be true today in 2025. Like him, I had to find the answer in someone else's already working code.

World's foremost 486 enjoyer.

Reply 6 of 15, by Jo22

User metadata
Rank l33t++
Rank
l33t++

I sympathize with his frustration that all of the resources online are for AT systems

Hi, that's understandable. The AT keyboard really became industry standard, though, across all industry.
(The second version, I mean. IBM Model M. The original AT keyboard, the Model F keyboard, became a footnote.)
The AT keyboard was also being used on IBM terminals, on Unix workstations etc.

That being said, the XT keyboard protocol was elegant through its simplicity.
Even though the original implementation without a dedicated microcontroller was very weak.
PC software can crash the "software" keyboard controller on IBM PC.
Newer DOS software written with PC/AT in mind can do that by accident.
In worst case, the keyboard handler will nolonger work until a reset.

I sympathize with his frustration that all of the resources online are for AT systems

On other hand, I'm often frustated that PC homebrew projects solely concentrate on IBM 5150. IBM PC here, IBM PC there..
A few dozen 100% accurate PC emulators (with none emulating V20).
There are no full 16-Bit ISA cards for EMS, UMB, XT-IDE/CF. There are no 16-Bit RAM cards for Extended Memory.
So far, I'm merely aware of one replica of an PC/AT motherboard.
I often think this is really unfair, considering how much more the PC/AT did for PC industry.
But then I'm thinking that I can be glad that there's retro PC development, at all, so I often don't say a thing. 🙁

PS: Btw, did I mention how much the C64 fandom does annoy me? sigh. 😮‍💨
It's not even the computer itself or the encouraged individuals, but the media hype. But that's another story. 🙁

Edit: This is quote is quite fitting, I think, because it says something about mentality.

This is an example of why I tell my co-workers at my Unix sysadmin job that my old DOS knowledge from 1983 helps me in all of my computer work: Because basic troubleshooting skills are basic troubleshooting skills. Computers are pretty much the same; if you train yourself to think a certain way, it will lend itself to other computer problems.

This can basically be read as high-level approach vs low-level approach.

Originally, in CP/M era (and Unix era), the programs had been written to be portable.
You didn't touch the hardware, that was part of the BIOS (lowest, hardware-dependent part of CP/M).
Programs rather interacted with the BDOS level.

In early days of MS-DOS, the CP/M model was still in use, that's how MS-DOS compatibles came to be.
WordStar and co had a set of files to support various terminal types, to compensate for missing OS level support.
Unfortunately, this all changed with the increasing importance of the IBM PC.
Suddenly, programmers tinkered with the hardware directly, like C64 users did.
This basically ended the platform-indepence of MS-DOS and its applications.

Last edited by Jo22 on 2025-04-17, 18:26. Edited 1 time in total.

"Time, it seems, doesn't flow. For some it's fast, for some it's slow.
In what to one race is no time at all, another race can rise and fall..." - The Minstrel

//My video channel//

Reply 7 of 15, by keenmaster486

User metadata
Rank l33t
Rank
l33t

It's because XT and 8-bit stuff is easier. It's difficult to build a hobbyist community from the ground up - it involves a lot of people doing this basically in their free time, producing open source projects that others can build off of. Essentially volunteer work. And it's hard work. This stuff is difficult, and few people know how to do it. You get a few people who are able to make some actual money, but they have to diversify significantly because they're not going to make a living only selling 8 bit expansion cards for 5150s.

So be patient, this will all come in time, and especially as the old hardware degrades and becomes more and more expensive, and the going rate for the parts you want to buy new gets high enough that it becomes worth someone's time to work on the project.

World's foremost 486 enjoyer.

Reply 8 of 15, by Jo22

User metadata
Rank l33t++
Rank
l33t++

I know, I know.. And you're right here, I guess. I just ment to say that I can feel the disappointment.
It's not just about IBM PC vs AT, but also C64 vs C128, for example.
I used to own a C128D (because of being curious about CP/M 3) and I thought it was a neat computer, one that fixes most of the flaws of C64 (like the broken floppy interface).
Yet, same time, it seems like the whole Commodore community is like "please don't ever mention the C128!", as if they try to pretend really hard it never happened. 😂🥲
For whatever reasons, the 128 is like a red flag to them or something.
Gratefully it's not like that with the PC community.

Edit: I forgot to mention, sorry for being a bit off-topic here. I don’t mean to de-rail your thread here.
I just want to add that I think that PC/XT vs PC/AT often reminds me of C64 vs C128.
And the C128, like the AT, was quite an interesting extension of original design.
The video output of C128 was EGA compatible, for example.
And the floppy drive was more advanced, comparable to how the PC/AT had high-speed floppy controller.

But there's even more similarity.
If the Z80 of the C128 had been replacedby a V20, it could have had run CP/M-86 or DOS.
In 8080 emulation mode, CP/M-80 could have been booted, too.
The V20 would have had made sense also because it was CMOS and could be tri-stated (good in a multi CPU config).
With 128KB of RAM, MS-DOS 1.25 might have booted up. But that's another story.

"Time, it seems, doesn't flow. For some it's fast, for some it's slow.
In what to one race is no time at all, another race can rise and fall..." - The Minstrel

//My video channel//

Reply 9 of 15, by GloriousCow

User metadata
Rank Member
Rank
Member
keenmaster486 wrote on 2025-04-15, 04:08:

In MartyPC the first key you press is the scancode that will come through for each subsequent key, that is, the same scancode comes through over and over no matter what key you press. In 86Box, no scancodes make it through at all. The interrupt never fires.

This bites a lot of folks. Although now you've got me wondering whether my IRQ behavior is correct here. I took another look at the schematic.

It looks like the IRQ1 line feeds into ~G of U24 which controls the parallel output data lines from the 74LS322 shift register. This means you can't read a scancode unless there's an active keyboard interrupt. A pending interrupt also ties up the keyboard clock and data lines via a pullup resistor which prevents any more data from being clocked in.

So no, MartyPC shouldn't be sending you any more keyboard interrupts until you clear the shift register, or more specifically the 74LS74 flip-flop U82 which allows the keyboard to send more data.

MartyPC: A cycle-accurate IBM PC/XT emulator | https://github.com/dbalsom/martypc

Reply 10 of 15, by keenmaster486

User metadata
Rank l33t
Rank
l33t

Interesting. Much more tightly controlled than on the AT.

World's foremost 486 enjoyer.

Reply 12 of 15, by keenmaster486

User metadata
Rank l33t
Rank
l33t

What's the story with the PCJr?

World's foremost 486 enjoyer.

Reply 13 of 15, by GloriousCow

User metadata
Rank Member
Rank
Member
keenmaster486 wrote on 2025-04-22, 23:11:

What's the story with the PCJr?

Well, essentially, they wanted a cordless keyboard. This means that the keyboard is battery powered. When you press a key, the IR transmitter on the keyboard sends the scancode in light pulses to the receiver.

Unlike the wired keyboard in the 5150/5160, there's no clock line to shift the serial input from the keyboard into a shift register and give you a nice convenient IRQ when it dings 'done' like a toaster making bagels.

We need a clock to know when to read the bits, and we don't have one - where can we get one? Well, we have an 8253 PIT. We can use that and it doesn't even cost us anything because it's already in the BOM. The CPU can then read the bits that are fed into one pin of the PPI, one bit at a time. Since there's no shift register or buffer now, the CPU has to read a scancode immediately or risk losing it, so the whole process of reading a key is triggered by the CPU's NMI line.

It's a bit batty, but the result of good intentions (a wireless keyboard should be more convenient for home use) facing budgetary constraints (generating a clock line without the CPU needing to be involved may have required a microcontroller or at least a bunch more circuitry).

MartyPC: A cycle-accurate IBM PC/XT emulator | https://github.com/dbalsom/martypc

Reply 14 of 15, by keenmaster486

User metadata
Rank l33t
Rank
l33t

Fascinating. I never knew that!

World's foremost 486 enjoyer.

Reply 15 of 15, by mkarcher

User metadata
Rank l33t
Rank
l33t
keenmaster486 wrote on 2025-04-15, 04:08:

In MartyPC the first key you press is the scancode that will come through for each subsequent key, that is, the same scancode comes through over and over no matter what key you press. In 86Box, no scancodes make it through at all. The interrupt never fires.

As GloriousCow already correctly deduced from the schematics, the 86Box behaviour is correct

GloriousCow wrote on 2025-04-22, 22:10:

It looks like the IRQ1 line feeds into ~G of U24 which controls the parallel output data lines from the 74LS322 shift register. This means you can't read a scancode unless there's an active keyboard interrupt.

I believe this detail is not reproduced on some XT compatible machines. I remember being able to see the scan code even after acknowledging IRQ1 on some XT class machines, though I might be mistaken. Anyway, if you stick to recommended behaviour, you only read Port 60h is IRQ1 is triggered (you may install an interrupt handler or poll the IRR of the PIC) and not yet acknowledged. In that case, it doesn't matter whether the 8255 receives the scan code only when a new code is present or always. The original IBM solution ensures that you don't get to see partially received codes, which might confuse software that just polls Port 60h (something you shouldn't do on XTs anyway because you need to clear the IRQ1).