Page 1 of 1

Problems with getting scancode set

Posted: Wed Apr 18, 2018 8:28 am
by itsmevjnk
Hi guys,
I am having quite a bit of problem dealing with getting/setting the scancode set for my PS/2 keyboard (and mouse?) driver. The code will work on emulators such as Bochs or Qemu, but wants me to press a key and then display garble on real hardware. Can someone help me with that?
This is the driver's code by the way:

Code: Select all

#include <kernel/i8042.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <kernel/io.h>
#include <kernel/tty.h>
/*
 * Initialize the Intel 8042 PS/2 Controller.
 * Input: nothing
 * Return: error code:
 *  - 0: No error
 *  - 1: PS/2 controller doesn't exist (will happen on Apple machines)
 *  - 2: Self test failed
 *  - 3: No error, dual channel PS/2 available
 *  - 4: First PS/2 port test failed because clock line stuck low
 *  - 5: First PS/2 port test failed because clock line stuck high
 *  - 6: First PS/2 port test failed because data line stuck low
 *  - 7: First PS/2 port test failed because data line stuck high
 *  - 8: Second PS/2 port test failed because clock line stuck low
 *  - 9: Second PS/2 port test failed because clock line stuck high
 *  - 10: Second PS/2 port test failed because data line stuck low
 *  - 11: Second PS/2 port test failed because data line stuck high
 *  - 12: Cannot set keyboard scan code set to 1
 *  - 13: Cannot initialize keyboard
 *  - 14: Keyboard failed to respond to echo
 */
uint8_t i8042Init(void) {
	uint8_t returnCode = 0; // return code in case everything goes well
	// disable all PS/2 ports
	outb(0x64, 0xad);
	outb(0x64, 0xa7);
	inb(0x60); // flush output buffer
	outb(0x64, 0x20); // read CCB
	while(!(inb(0x64) & 0x01));
	uint8_t ccb = inb(0x60);
	ccb &= 0x34;
	outb(0x64, 0x60); // write CCB
	while(inb(0x64) & 0x01);
	outb(0x60, ccb);
	outb(0x64, 0xaa); // perform controller self test
	while(!(inb(0x64) & 0x01));
	if(inb(0x60) != 0x55) return 2; // self test failed
	outb(0x64, 0x60); // rewrite CCB, just in case that self test reset the controller
	while(inb(0x64) & 0x01);
	outb(0x60, ccb);
	if(ccb & 0x20) returnCode = 0; // single channel controller detected
	else {
		outb(0x64, 0xa8); // enable second PS/2 port
		outb(0x64, 0x20);
		while(!(inb(0x64) & 0x01));
		if(!(inb(0x60) & 0x20)) {
			returnCode = 3;
			outb(0x64, 0xa7); //disable second PS/2 port again
		}
	}
	uint8_t testResult;
	outb(0x64, 0xab); // test first port
	while(!(inb(0x64) & 0x01));
	testResult = inb(0x60);
	if(testResult != 0) return (testResult + 3);
	if(returnCode == 3) {
		// test second port
		outb(0x64, 0xa9);
		while(!(inb(0x64) & 0x01));
		testResult = inb(0x60);
		if(testResult != 0) return (testResult + 7);
	}
	outb(0x64, 0xae); // re-enable the first port
	if(returnCode == 3) outb(0x64, 0xa8); // re-enable the second port if possible
	
	/* initialize keyboard */
	while(1) {
		i8042SendData(0, 0xFF);
		uint8_t tmp = i8042GetScancode();
		if(tmp == 0xAA) break;
		else if((tmp == 0xFC) || (tmp == 0xFD)) return 13;
	}
	
	i8042DisableSecondPort(); // disable the second port
	
	/* send echo */
	uint8_t echoResponse;
	for(uint8_t i = 0; i < 3; i++) {
		i8042SendData(0, 0xEE);
		echoResponse = i8042GetScancode();
		if(echoResponse == 0xEE) break;
	}
	if(echoResponse != 0xEE) return 14;
	
	return returnCode;
}

uint8_t i8042GetScancode(void) {
	while(!(inb(0x64) & 1));
	return inb(0x60);
}

void i8042SendData(uint8_t port, uint8_t data) {
	if(port != 0) outb(0x64, 0xd4);
	while(inb(0x64) & 2);
	outb(0x60, data);
}

void i8042SetScancodeSet(uint8_t set) {
	uint8_t response;
	while(1) {
		i8042SendData(0, 0xf0);
		while(1) {
			response = inb(0x60);
			if((response == 0xFE) || (response == 0xFA)) break;
		}
		if(response != 0xFE) break;
	}
	while(1) {
		i8042SendData(0, set);
		while(1) {
			response = inb(0x60);
			if((response != 0xAA) && (response != 0xFA) && (response != 0x00)) break;
		}
		if(response != 0xFE) break;
	}
}

uint8_t i8042GetScancodeSet(void) {
	uint8_t response;
	while(1) {
		i8042SendData(0, 0xf0);
		while(1) {
			response = inb(0x60);
			if((response == 0xFE) || (response == 0xFA)) break;
		}
		if(response != 0xFE) break;
	}
	while(1) {
		i8042SendData(0, 0);
		while(1) {
			response = inb(0x60);
			if((response != 0xAA) && (response != 0xFA) && (response != 0x00)) break;
		}
		if(response != 0xFE) break;
	}
	return response;
}

/* Based on knusbaum's work */

#define ESC 27
#define BS 8
#define EOT 4

static const uint8_t scSet1Lower[256] = {
    0x00,  ESC,  '1',  '2',     /* 0x00 */
     '3',  '4',  '5',  '6',     /* 0x04 */
     '7',  '8',  '9',  '0',     /* 0x08 */
     '-',  '=',   BS, '\t',     /* 0x0C */
     'q',  'w',  'e',  'r',     /* 0x10 */
     't',  'y',  'u',  'i',     /* 0x14 */
     'o',  'p',  '[',  ']',     /* 0x18 */
    '\n', 0x00,  'a',  's',     /* 0x1C */
     'd',  'f',  'g',  'h',     /* 0x20 */
     'j',  'k',  'l',  ';',     /* 0x24 */
    '\'',  '`', 0x00, '\\',     /* 0x28 */
     'z',  'x',  'c',  'v',     /* 0x2C */
     'b',  'n',  'm',  ',',     /* 0x30 */
     '.',  '/', 0x00,  '*',     /* 0x34 */
    0x00,  ' ', 0x00, 0x00,     /* 0x38 */
    0x00, 0x00, 0x00, 0x00,     /* 0x3C */
    0x00, 0x00, 0x00, 0x00,     /* 0x40 */
    0x00, 0x00, 0x00,  '7',     /* 0x44 */
     '8',  '9',  '-',  '4',     /* 0x48 */
     '5',  '6',  '+',  '1',     /* 0x4C */
     '2',  '3',  '0',  '.',     /* 0x50 */
    0x00, 0x00, 0x00, 0x00,     /* 0x54 */
    0x00, 0x00, 0x00, 0x00      /* 0x58 */
};

static const uint8_t scSet1Upper[256] = {
    0x00,  ESC,  '!',  '@',     /* 0x00 */
     '#',  '$',  '%',  '^',     /* 0x04 */
     '&',  '*',  '(',  ')',     /* 0x08 */
     '_',  '+',   BS, '\t',     /* 0x0C */
     'Q',  'W',  'E',  'R',     /* 0x10 */
     'T',  'Y',  'U',  'I',     /* 0x14 */
     'O',  'P',  '{',  '}',     /* 0x18 */
    '\n', 0x00,  'A',  'S',     /* 0x1C */
     'D',  'F',  'G',  'H',     /* 0x20 */
     'J',  'K',  'L',  ':',     /* 0x24 */
     '"',  '~', 0x00,  '|',     /* 0x28 */
     'Z',  'X',  'C',  'V',     /* 0x2C */
     'B',  'N',  'M',  '<',     /* 0x30 */
     '>',  '?', 0x00,  '*',     /* 0x34 */
    0x00,  ' ', 0x00, 0x00,     /* 0x38 */
    0x00, 0x00, 0x00, 0x00,     /* 0x3C */
    0x00, 0x00, 0x00, 0x00,     /* 0x40 */
    0x00, 0x00, 0x00,  '7',     /* 0x44 */
     '8',  '9',  '-',  '4',     /* 0x48 */
     '5',  '6',  '+',  '1',     /* 0x4C */
     '2',  '3',  '0',  '.',     /* 0x50 */
    0x00, 0x00, 0x00, 0x00,     /* 0x54 */
    0x00, 0x00, 0x00, 0x00      /* 0x58 */
};

# define KBBUFFLEN 128

static uint8_t shift, ctrl, keypresses[256], caps = 0;
static uint8_t kbBuff[KBBUFFLEN], kbBuffHd, kbBuffTl;

static void pollKbInput() {
	uint8_t nextHd = (kbBuffHd + 1) % KBBUFFLEN;
	if(nextHd == kbBuffTl) return;
	
	if(!(inb(0x64) & 1)) return;
	uint8_t byte = inb(0x60);
	if(byte == 0) return;
	
	if(byte & 0x80) {
		uint8_t pressedByte = byte & 0x7f;
		if(pressedByte == 0x2a) shift &= 0x02;
		else if(pressedByte == 0x36) shift &= 0x01;
		else if(pressedByte == 0x1d) ctrl = 0;
		
		keypresses[pressedByte] = 0;
		return;
	}
	
	if(keypresses[byte] < 10 && keypresses[byte] > 0) {
		keypresses[byte]++;
		return;
	}
	keypresses[byte]++;
	
	if(byte == 0x2a) {
		shift |= 0x01;
		return;
	} else if(byte == 0x36) {
		shift |= 0x02;
		return;
	} else if(byte == 0x1d) {
		ctrl = 1;
		return;
	} else if(byte == 0x3a) {
		caps ^= 1;
		return;
	}
	
	const uint8_t *codes;
	if(ctrl) {
		if(scSet1Lower[byte] == 'd') {
			// ctrl+d = EOT
			kbBuff[kbBuffHd] = EOT;
			kbBuffHd = nextHd;
			return;
		}
	} else if((shift != 0) && (caps == 0)) codes = scSet1Upper;
	else if((shift == 0) && (caps != 0)) codes = scSet1Upper;
	else codes = scSet1Lower;
	
	uint8_t ascii = codes[byte];
	if(ascii != 0) {
		kbBuff[kbBuffHd] = ascii;
		kbBuffHd = nextHd;
		return;
	}
}

char i8042GetChar(void) {
	while(1) {
		pollKbInput();
		if(kbBuffHd != kbBuffTl) {
			char c = kbBuff[kbBuffTl];
			kbBuffTl = (kbBuffTl + 1) % KBBUFFLEN;
			pollKbInput();
			return c;
		}
	}
}

void i8042EnableFirstPort(void) {
	outb(0x64, 0xae);
}

void i8042DisableFirstPort(void) {
	outb(0x64, 0xad);
}

void i8042EnableSecondPort(void) {
	outb(0x64, 0xa8);
}

void i8042DisableSecondPort(void) {
	outb(0x64, 0xa7);
}
EDIT: Bochs will hang during PS/2 keyboard initialization because of the initialize keyboard and/or echo portion.

Re: Problems with getting scancode set

Posted: Wed Apr 18, 2018 6:27 pm
by BenLunt
I only have two comments.

The first, when someone posts a question of "why won't it work" and then posts 100+ lines of code, usually the first thing most people do is to hit the back button to move on to the next question. No one likes to see 100+ lines of code when the question is "why won't it work". Sorry.

Second, you have to wait for the ps2 controller to be ready to read or write a value to the command and/or data ports. You can't just simply read a byte from the port and expect it to be valid.

https://wiki.osdev.org/PS/2_Keyboard might be a good place to start.

Write a few (inline) routines that wait for the PS/2 to be ready for a command/data write before writing a command/data as well as a read routine. Then replace the lines you have with calls to these routines and see what you find out.

Be sure to use timeouts or you might be waiting a while on occasion.

Ben
- http://www.fysnet.net/input_and_output_devices.htm

Re: Problems with getting scancode set

Posted: Thu Apr 19, 2018 1:03 am
by itsmevjnk
This is the exact problem I am getting: When I send 0xF0 (the command to get scan code set) to port 0x60, the keyboard sends back 0xFA, which is the ACK byte. After that, when I send the parameter (in this case is 0), the keyboard gives back 0xFE. Any subsequent attempts to send that byte will result in the keyboard sending 0xFE back. This doesn't happen on emulators: it only happens on real hardware.
EDIT: Any subsequent attempts to send that command again still causes the same thing to happen.

Re: Problems with getting scancode set

Posted: Thu Apr 19, 2018 3:03 am
by davidv1992
Few questions:

- Do you wait until the PS/2 controller indicates it is ready for the next byte (by checking input buffer status bit)?
- Does the hardware you are testing on have a physical PS/2 controller and keyboard, or is emulation used?

Re: Problems with getting scancode set

Posted: Thu Apr 19, 2018 4:13 am
by itsmevjnk
davidv1992 wrote:Few questions:

- Do you wait until the PS/2 controller indicates it is ready for the next byte (by checking input buffer status bit)?
- Does the hardware you are testing on have a physical PS/2 controller and keyboard, or is emulation used?
1. I waited until the PS/2 controller's input buffer status bit to clear and then write the data.
2. The hardware I am testing on has a physical PS/2 keyboard.

Re: Problems with getting scancode set

Posted: Thu Apr 19, 2018 4:36 am
by itsmevjnk
Nevermind, I got it to work. Apparently I accidentally set the USB Keyboard Support to Enabled in the BIOS settings, which messes up with the PS/2 controller. Thanks for pointing that out!

Re: Problems with getting scancode set

Posted: Thu Apr 19, 2018 12:22 pm
by BenLunt
weedboi6969 wrote:Nevermind, I got it to work. Apparently I accidentally set the USB Keyboard Support to Enabled in the BIOS settings, which messes up with the PS/2 controller. Thanks for pointing that out!
Yes it does. :-) Eventually, after you have become comfortable programming hardware, you can skip the BIOS and disable it yourself before hand, making sure that this doesn't happen again.

Ben

- http://www.fysnet.net/the_universal_serial_bus.htm