views:

3630

answers:

6

OK, I feel very very stupid for getting hung up on such a basic concept, but I can't seem to find a difinitive answer for this question anywhere.

I'm in the process of trying to hack together the first bits of a kernel. I currently have the entire kernel compiled down as C code, and I've managed to get it displaying text in the console window and all of that fine goodness. Now, I want to start accepting keyboard input so I can actually make some use of the thing and get going on process management.

I'm using DJGPP to compile, and loading with GRUB. I'm also using a small bit of assembly which basically jumps directly into my compiled C code and I'm happy from there. (A friend on the project got this bit working.) Now, fair warning, I don't pretend to know much about x86 assembly, although I'm familiar with other types of assembly, like z80 for example.

All the research I've done seems to point to an ISR at $0x16 to read in the next character from the keyboard buffer. From what I can tell, this is supposed to store the ASCII value in ah, and the keycode in al, or something to that effect. I'm attempting to code this using the following routine in inline assembly:

char getc(void) 
{
int output = 0;

//CRAZY VOODOO CODE
asm("xor %%ah, %%ah\n\t"
 "int $0x16"
 : "=a" (output)
 : "a" (output)
 : 

 );

return (char)output;
}

When this code is called, the core immediately crashes. (I'm running it on VirtualBox, I didn't feel the need to try something this basic on real hardware.)

Now I have actually a couple of questions. No one has been able to tell me if (since my code was launched from GRUB) I'm running in real mode or protected mode at the moment. I haven't made the jump one way or another, I was planning on running in real mode until I got a process handler set up.

So, assuming that I'm running in real mode, what am I doing wrong, and how do I fix it? I just need a basic getc routine, preferably non-blocking, but I'll be darned if google is helping on this one at all. Once I can do that, I can do the rest from there.

I guess what I'm asking here is, am I anywhere near the right track? How does one generally go about getting keyboard input on this level?

Thanks a bunch.

EDIT: OOhh... so I'm running in protected mode. This certainly explains the crash trying to access real mode functions then.

So then I guess I'm looking for how to access the keyboard IO from protected mode. I might be able to find that on my own, but if anyone happens to know feel free. Thanks again.

+1  A: 

I've a piece of GeekOS that seems to do

In_Byte(KB_CMD);

and then

In_Byte(KB_DATA);

to fetch a scancode. I put it up: keyboard.c and keyboard.h. KB_CMD and KB_DATA being 0x64 and 0x60 respectively. I could perhaps also point out that this is done in an interrupt handler for intr:1.

Anders Eurenius
+3  A: 

The code you've got there is trying to access a real mode BIOS service. If you're running in protected mode, which is likely considering that you're writing a kernel, then the interrupt won't work. You will need to do one of the following:

  • Thunk the CPU into real mode, making sure the interrupt vector table is correct, and use the real mode code you have or
  • Write your own protected mode keyboard handler (i.e. use the in/out intructions).

The first solution is going to involve a runtime performance overhead whist the second will require some information about keyboard IO.

Skizz

Skizz
A: 

Just an idea: looking at GRUB for DOS source (asm.s), the console_checkkey function is using BIOS INT 16H Function 01, and not function 00, as you are trying to do. Maybe you'd want to check if a key is waiting to be input.

The console_checkkey code is setting the CPU to real mode in order to use the BIOS, as @skizz suggested.

You can also try using GRUB functions directly (if still mapped in real mode).

A note on reading assembly source: in this version

movb    $0x1, %ah

means move constant byte (0x1) to register %ah

The console_checkkey from GRUB asm.s:

/*
 * int console_checkkey (void)
 *  if there is a character pending, return it; otherwise return -1
 * BIOS call "INT 16H Function 01H" to check whether a character is pending
 *  Call with %ah = 0x1
 *  Return:
 *   If key waiting to be input:
 *    %ah = keyboard scan code
 *    %al = ASCII character
 *    Zero flag = clear
 *   else
 *    Zero flag = set
 */
 ENTRY(console_checkkey)
  push %ebp
  xorl %edx, %edx

  call EXT_C(prot_to_real) /* enter real mode */

  .code16

  sti  /* checkkey needs interrupt on */

  movb $0x1, %ah
  int $0x16

  DATA32 jz notpending

  movw %ax, %dx
  //call translate_keycode
  call remap_ascii_char
  DATA32 jmp pending

notpending:
  movl $0xFFFFFFFF, %edx

pending:
  DATA32 call EXT_C(real_to_prot)
  .code32

  mov %edx, %eax

  pop %ebp
  ret
gimel
+3  A: 

If you are compiling with gcc, unless you are using the crazy ".code16gcc" trick the linux kernel uses (which I very much doubt), you cannot be in real mode. If you are using the GRUB multiboot specification, GRUB itself is switching to protected mode for you. So, as others pointed out, you will have to talk to the 8042-compatible keyboard/mouse controller directly. Unless it's a USB keyboard/mouse and 8042 emulation is disabled, where you would need a USB stack (but you can use the "boot" protocol for the keyboard/mouse, which is simpler).

Nobody said writing an OS kernel was simple.

CesarB
Note that as long as you stay in protected mode and not long mode, you should be able to use a virtual 8086 task to execute BIOS interrupts. (Raw keyboard IO is relatively simple, though, and it's probably more worthwhile to just interface with the hardware directly, as @CesarB suggests.)
bcat
+1  A: 

You're doing the right thing, but I seem to recall that djgpp only generates protected mode output, which you can't call interrupts from. Can you drop to real mode like others have suggested, or would you prefer to address the hardware directly?

Brian Knoblauch
+1  A: 

For the purposes of explanation, let's suppose you were writing everything in assembly language yourself, boot loader and kernel (*cough* I've done this).

In real mode, you can make use of the interrupt routines that come from the BIOS. You can also replace the interrupt vectors with your own. However all code is 16-bit code, which is not binary compatible with 32-bit code.

When you jump through a few burning hoops to get to protected mode (including reprogramming the interrupt controller, to get around the fact that IBM used Intel-reserved interrupts in the PC), you have the opportunity to set up 16- and 32-bit code segments. This can be used to run 16-bit code. So you can use this to access the getchar interrupt!

... not quite. For this interrupt to work, you actually need data in a keyboard buffer that was put there by a different ISR - the one that is triggered by the keyboard when a key is pressed. There are various issues which pretty much prevent you using BIOS ISRs as actual hardware ISRs in protected mode. So, the BIOS keyboard routines are useless.

BIOS video calls, on the other hand, are fine, because there's no hardware-triggered component. You do have to prepare a 16-bit code segment but if that's under control then you can switch video modes and that sort of thing by using BIOS interrupts.

Back to the keyboard: what you need (again assuming that YOU'RE writing all the code) is to write a keyboard driver. Unless you're a masochist (I'm one) then don't go there.

A suggestion: try writing a multitasking kernel in Real mode. (That's 16-bit mode.) You can use all the BIOS interrupts! You don't get memory protection but you can still get pre-emptive multitasking by hooking the timer interrupt.

Artelius