Problems with getting scancode set

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
itsmevjnk
Member
Member
Posts: 32
Joined: Fri Apr 13, 2018 10:18 am
Location: Melbourne, VIC, Australia

Problems with getting scancode set

Post 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.
Just a procrastinating uni student doing stupid things (or not doing them at all)...

SysX: https://github.com/itsmevjnk/sysx.git
User avatar
BenLunt
Member
Member
Posts: 941
Joined: Sat Nov 22, 2014 6:33 pm
Location: USA
Contact:

Re: Problems with getting scancode set

Post 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
itsmevjnk
Member
Member
Posts: 32
Joined: Fri Apr 13, 2018 10:18 am
Location: Melbourne, VIC, Australia

Re: Problems with getting scancode set

Post 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.
Just a procrastinating uni student doing stupid things (or not doing them at all)...

SysX: https://github.com/itsmevjnk/sysx.git
davidv1992
Member
Member
Posts: 223
Joined: Thu Jul 05, 2007 8:58 am

Re: Problems with getting scancode set

Post 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?
itsmevjnk
Member
Member
Posts: 32
Joined: Fri Apr 13, 2018 10:18 am
Location: Melbourne, VIC, Australia

Re: Problems with getting scancode set

Post 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.
Just a procrastinating uni student doing stupid things (or not doing them at all)...

SysX: https://github.com/itsmevjnk/sysx.git
itsmevjnk
Member
Member
Posts: 32
Joined: Fri Apr 13, 2018 10:18 am
Location: Melbourne, VIC, Australia

Re: Problems with getting scancode set

Post 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!
Just a procrastinating uni student doing stupid things (or not doing them at all)...

SysX: https://github.com/itsmevjnk/sysx.git
User avatar
BenLunt
Member
Member
Posts: 941
Joined: Sat Nov 22, 2014 6:33 pm
Location: USA
Contact:

Re: Problems with getting scancode set

Post 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
Post Reply