Page 1 of 1

GNU-EFI how to get keyboard events ?

Posted: Thu Nov 26, 2020 7:39 am
by gomidas
Hello, I use uefi on vs2019 project. I based on sample code which found on osdev.org

I test the code on QEMU. My issue is whenever I press a key my program/system shutsdown immediately. Somehow I found a way to wait for a key and print a text but still I am doing it wrong. I want to re-wait for new keys unless that is "ESC" key. If key is esc program will exit. This is my first purpose. Second, if possible I want to print all characters when they are pressed.

Here is code:

Code: Select all

/*
 * UEFI:SIMPLE - UEFI development made easy
 * Copyright © 2014-2018 Pete Batard <[email protected]> - Public Domain
 * See COPYING for the full licensing terms.
 */
#include <efi.h>
#include <efilib.h>
#include <stdio.h>

// Application entrypoint (must be set to 'efi_main' for gnu-efi crt0 compatibility)
EFI_STATUS efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
{
	UINTN Event;

#if defined(_GNU_EFI)
	InitializeLib(ImageHandle, SystemTable);
#endif

	/*
	 * In addition to the standard %-based flags, Print() supports the following:
	 *   %N       Set output attribute to normal
	 *   %H       Set output attribute to highlight
	 *   %E       Set output attribute to error
	 *   %B       Set output attribute to blue color
	 *   %V       Set output attribute to green color
	 *   %r       Human readable version of a status code
	 */

	uefi_call_wrapper(SystemTable->ConOut->SetAttribute, 1, SystemTable->ConOut, EFI_BACKGROUND_MAGENTA); //SET BACK COLOR	
	Print(L"\n***DO NOT DISPLAY THIS***%N\n\n");
	uefi_call_wrapper(SystemTable->ConOut->ClearScreen, 1, SystemTable->ConOut); //CLEAR SCREEN

	uefi_call_wrapper(SystemTable->ConOut->SetAttribute, 1, SystemTable->ConOut, EFI_YELLOW); //SET TEXT COLOR

	Print(L"\n*** UEFI:HELLO MY FRIEND, bla bla and some other very necessary messages... ***%N\n\n");
	uefi_call_wrapper(SystemTable->ConOut->SetAttribute, 1, SystemTable->ConOut, EFI_RED); //SET TEXT COLOR
	Print(L"PRESS ESC KEY TO EXIT.%N\n");

	SystemTable->ConIn->Reset(SystemTable->ConIn, FALSE);

#if defined(_DEBUG)
	// If running in debug mode, use the EFI shut down call to close QEMU
	SystemTable->RuntimeServices->ResetSystem(EfiResetShutdown, EFI_SUCCESS, 0, NULL);
#endif

	return EFI_SUCCESS;
}

I found methods but I do not know how to use them, at least it does not work for debug mode but works on release mode:

Code: Select all

SystemTable->BootServices->WaitForEvent(1, &SystemTable->ConIn->WaitForKey, &Event);
SystemTable->BootServices->WaitForEvent(1, &SystemTable->ConIn->ReadKeyStroke, &Event);
uefi_call_wrapper(SystemTable->ConOut->OutputString,1, SystemTable->ConOut , L"test\n");
what should I do ?

Re: GNU-EFI how to get keyboard events ?

Posted: Thu Nov 26, 2020 5:29 pm
by Octocontrabass
Are you using GNU-EFI? If you are, you're using uefi_call_wrapper() incorrectly. If you're not, you shouldn't be using uefi_call_wrapper() at all.

Re: GNU-EFI how to get keyboard events ?

Posted: Thu Nov 26, 2020 6:00 pm
by zaval
first, if you use vs2019, then please, don't complicate yourself life and ditch that gnu-efi. use msvc. next, how you can get how to use key event functions? please, read the UEFI specification. it's the easiest way. here is how you could play with key pressing, in this example, the loader shuts down the system if "s" is pressed, you can change it for whatever you want, but you need to read the UEFI specification:

Code: Select all

PEFI_BOOT_SERVICES	pbs;    /* global pointers to the BS/RS, must be initialized */
PEFI_RUNTIME_SERVICES	prs;    /* from the System Table*/

PEFI_SIMPLE_TEXT_OUTPUT_PROTOCOL    pout;    /* global pointers to the I/O protocols, must be initialized */
PEFI_SIMPLE_TEXT_INPUT_PROTOCOL    pin;    /* from the System Table*/

EFI_STATUS EFIAPI SomeFunction(){
    UINTN index, Status;
    EFI_EVENT KeyEvent;
    EFI_INPUT_KEY InputKey;
/* ... */

    KeyEvent = pin->WaitForKey;
    pout->OutputString(pout, L"Press <s> to shutdown or anything else (exc. <Enter>) to exit this rough OSL.");
ZZZ: pbs->WaitForEvent(1, &KeyEvent, &index);
    Status = pin->ReadKeyStroke(pin, &InputKey);
    if(Status == EFI_NOT_READY){
        goto ZZZ;
    }else if(InputKey.UnicodeChar == 's'){
         /* clear screen here */
        pout->OutputString(L"Shutting down the machine...\r\n");
        prs->ResetSystem(EfiResetShutdown, EFI_SUCCESS, 0, NULL);
    }

    return pbs->Exit(ImageHandle, EFI_SUCCESS, 0, NULL);
}

Re: GNU-EFI how to get keyboard events ?

Posted: Fri Nov 27, 2020 10:36 am
by bzt
Octocontrabass wrote:Are you using GNU-EFI? If you are, you're using uefi_call_wrapper() incorrectly. If you're not, you shouldn't be using uefi_call_wrapper() at all.
Plus 1. Also there's no need for "#if defined(_GNU_EFI)". If you don't have that define and you don't initialize the GNU-EFI library, then all calls to "Print()" and "uefi_call_wrapper()" should be considered an UB.
zaval wrote:first, if you use vs2019, then please, don't complicate yourself life and ditch that gnu-efi. use msvc.
True, there's no need with MSVC. But if you want to use GNU-EFI, then use a GNU toolchain, like cygwin, mingw or WSL.

Finally, here's a GNU-EFI example that's known to work. It checks the keyboard buffer in a non-blocking manner, because I needed a half-sec timeout. I could have used a timer event, but that would be much more complex.

Code: Select all

        for(i=0;i<500;i++) {
            if(!uefi_call_wrapper(BS->CheckEvent, 1, CI->WaitForKey)) {           // see if there's a key waiting (non-blocking)
                uefi_call_wrapper(CI->ReadKeyStroke, 2, CI, &key);                // read the key (would be blocking, but since there's a key it returns immediately)
                // here you can do whatever you want with the "key"
                break;
            }
            // delay 1ms
            uefi_call_wrapper(BS->Stall, 1, 1000);
        }
Here ReadKeyStroke would block, so I only call it if there's already a key waiting in the input buffer. If you don't need a timeout, and your app doesn't need to do anything while waiting for the user to press a key, then there's no need for the "CheckEvent".

Code: Select all

                uefi_call_wrapper(CI->ReadKeyStroke, 2, CI, &key);                // this blocks until a key is pressed
                // here you can do whatever you want with the "key"
Cheers,
bzt

Re: GNU-EFI how to get keyboard events ?

Posted: Sat Nov 28, 2020 12:34 am
by gomidas
Alright, found correct way of doing it. Thank you for answers.

Code: Select all

#include ".vs/msvc/efi_reference.h"

//C
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h> //C cstdlib.h for C++

//LOCAL C/C++ myFiles
//#include ".vs/msvc/xxxx.h"

bool keyMap[255];
unsigned short ESC_KEY_DOWN_COUNT=2;

// Application entrypoint (must be set to 'efi_main' for gnu-efi crt0 compatibility)
EFI_STATUS  _cdecl efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE* SystemTable)
{

#if defined(_GNU_EFI)
	InitializeLib(ImageHandle, SystemTable);
#endif
	EFI_INPUT_KEY Key;
	EFI_STATUS Status;
	EFI_SIMPLE_POINTER_PROTOCOL* mouse;
	EFI_SIMPLE_POINTER_STATE     State;
	EFI_EVENT Events[2];

	Key.ScanCode = SCAN_NULL;

	gST->ConOut->SetAttribute(SystemTable->ConOut, EFI_BACKGROUND_MAGENTA);
	gST->ConOut->ClearScreen(SystemTable->ConOut, EFI_BACKGROUND_MAGENTA);

	gST->ConOut->EnableCursor(gST->ConOut, TRUE);

	gST->ConOut->SetCursorPosition(gST->ConOut,55,55);

	Status = gBS->LocateProtocol(&SimplePointerProtocol, NULL, (VOID**)&mouse);

	if (EFI_ERROR(Status))
	{
		Print(L"Error locating Simple Pointer Protocol.\n");
		return Status;
	}
	else
	{
		Print(L"Mouse Found\n");
	}

	Status = mouse->Reset(mouse, TRUE);
	if (EFI_ERROR(Status))
	{
		Print(L"Can not reset.\n");
		return Status;
	}
	else
	{
		Print(L"Mouse Reset\n");
	}

	//Keyboard
	Events[1] = gST->ConIn->WaitForKey;
	Events[0] = mouse->WaitForInput;

	while (1)
	{
		EFI_INPUT_KEY Key = {NULL};
		UINTN index;

		Status = gBS->WaitForEvent(2, Events, &index);

		// INDEX 0 IS KEYBOARD MESSAGE
		if (index == 1)
		{
			Status = gST->ConIn->ReadKeyStroke(gST->ConIn, &Key);

			if ((UINTN)Key.ScanCode == SCAN_ESC)
			{
				if(ESC_KEY_DOWN_COUNT == 0)
				{
					SystemTable->ConIn->Reset(SystemTable->ConIn, FALSE);
					SystemTable->RuntimeServices->ResetSystem(EfiResetShutdown, EFI_SUCCESS, 0, NULL);
					return EFI_SUCCESS;
				}
				ESC_KEY_DOWN_COUNT--;
			}
			else
			{
				ESC_KEY_DOWN_COUNT = 2;
				Print(L"%c", Key.UnicodeChar);
			}
		}
		else
		{
			Status = mouse->GetState(mouse, &State);
			Print(L"X:%d Y:%d Z:%d L:%d R:%d\n",
				State.RelativeMovementX,
				State.RelativeMovementY,
				State.RelativeMovementZ,
				State.LeftButton,
				State.RightButton
			);
		}
	}

	SystemTable->ConIn->Reset(SystemTable->ConIn, FALSE);
	SystemTable->RuntimeServices->ResetSystem(EfiResetShutdown, EFI_SUCCESS, 0, NULL);
	return EFI_SUCCESS;

#if defined(_DEBUG)
	// If running in debug mode, use the EFI shut down call to close QEMU
	SystemTable->RuntimeServices->ResetSystem(EfiResetShutdown, EFI_SUCCESS, 0, NULL);
#endif

	return EFI_SUCCESS;
}
I now get all keyboard events...
I also tested it on a real UEFI hardware that works.
When I first get the sample project, code was given like that I thought that should be like that.

Now I've got another issue I cant see mouse events for QEMU but on real hardware that captures all mouse events. What is the reason of that ?

When my program booted, if I do CTRL+ALT+DELETE that restarts. Is that possible I prevent it or should I prevent it.

Re: GNU-EFI how to get keyboard events ?

Posted: Sat Nov 28, 2020 12:11 pm
by austanss
gomidas wrote:Alright, found correct way of doing it. Thank you for answers.

Code: Select all

#include ".vs/msvc/efi_reference.h"

//C
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h> //C cstdlib.h for C++

//LOCAL C/C++ myFiles
//#include ".vs/msvc/xxxx.h"

bool keyMap[255];
unsigned short ESC_KEY_DOWN_COUNT=2;

// Application entrypoint (must be set to 'efi_main' for gnu-efi crt0 compatibility)
EFI_STATUS  _cdecl efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE* SystemTable)
{

#if defined(_GNU_EFI)
	InitializeLib(ImageHandle, SystemTable);
#endif
	EFI_INPUT_KEY Key;
	EFI_STATUS Status;
	EFI_SIMPLE_POINTER_PROTOCOL* mouse;
	EFI_SIMPLE_POINTER_STATE     State;
	EFI_EVENT Events[2];

	Key.ScanCode = SCAN_NULL;

	gST->ConOut->SetAttribute(SystemTable->ConOut, EFI_BACKGROUND_MAGENTA);
	gST->ConOut->ClearScreen(SystemTable->ConOut, EFI_BACKGROUND_MAGENTA);

	gST->ConOut->EnableCursor(gST->ConOut, TRUE);

	gST->ConOut->SetCursorPosition(gST->ConOut,55,55);

	Status = gBS->LocateProtocol(&SimplePointerProtocol, NULL, (VOID**)&mouse);

	if (EFI_ERROR(Status))
	{
		Print(L"Error locating Simple Pointer Protocol.\n");
		return Status;
	}
	else
	{
		Print(L"Mouse Found\n");
	}

	Status = mouse->Reset(mouse, TRUE);
	if (EFI_ERROR(Status))
	{
		Print(L"Can not reset.\n");
		return Status;
	}
	else
	{
		Print(L"Mouse Reset\n");
	}

	//Keyboard
	Events[1] = gST->ConIn->WaitForKey;
	Events[0] = mouse->WaitForInput;

	while (1)
	{
		EFI_INPUT_KEY Key = {NULL};
		UINTN index;

		Status = gBS->WaitForEvent(2, Events, &index);

		// INDEX 0 IS KEYBOARD MESSAGE
		if (index == 1)
		{
			Status = gST->ConIn->ReadKeyStroke(gST->ConIn, &Key);

			if ((UINTN)Key.ScanCode == SCAN_ESC)
			{
				if(ESC_KEY_DOWN_COUNT == 0)
				{
					SystemTable->ConIn->Reset(SystemTable->ConIn, FALSE);
					SystemTable->RuntimeServices->ResetSystem(EfiResetShutdown, EFI_SUCCESS, 0, NULL);
					return EFI_SUCCESS;
				}
				ESC_KEY_DOWN_COUNT--;
			}
			else
			{
				ESC_KEY_DOWN_COUNT = 2;
				Print(L"%c", Key.UnicodeChar);
			}
		}
		else
		{
			Status = mouse->GetState(mouse, &State);
			Print(L"X:%d Y:%d Z:%d L:%d R:%d\n",
				State.RelativeMovementX,
				State.RelativeMovementY,
				State.RelativeMovementZ,
				State.LeftButton,
				State.RightButton
			);
		}
	}

	SystemTable->ConIn->Reset(SystemTable->ConIn, FALSE);
	SystemTable->RuntimeServices->ResetSystem(EfiResetShutdown, EFI_SUCCESS, 0, NULL);
	return EFI_SUCCESS;

#if defined(_DEBUG)
	// If running in debug mode, use the EFI shut down call to close QEMU
	SystemTable->RuntimeServices->ResetSystem(EfiResetShutdown, EFI_SUCCESS, 0, NULL);
#endif

	return EFI_SUCCESS;
}
I now get all keyboard events...
I also tested it on a real UEFI hardware that works.
When I first get the sample project, code was given like that I thought that should be like that.

Now I've got another issue I cant see mouse events for QEMU but on real hardware that captures all mouse events. What is the reason of that ?

When my program booted, if I do CTRL+ALT+DELETE that restarts. Is that possible I prevent it or should I prevent it.
CTRL+ALT+DELETE is the universal keybind for resetting the computer. You should only block it if you intend to replace it with similar functionality. Also, did you configure a mouse in QEMU? It doesn't come by default.

Re: GNU-EFI how to get keyboard events ?

Posted: Sat Nov 28, 2020 1:24 pm
by gomidas
rizxt wrote: CTRL+ALT+DELETE is the universal keybind for resetting the computer. You should only block it if you intend to replace it with similar functionality. Also, did you configure a mouse in QEMU? It doesn't come by default.
On windows when I do ctrl+alt+delete I get some options like task-manager, reset, turn off, lock etc. I want to do something like that instead of default behaviour, I just confused when I got reset by default since I am very amateur.

For QEMU. The sample I based on uses it for debugging I do not know how to configure it at all. What is the way we configure that ?

Thank you for your help.

Re: GNU-EFI how to get keyboard events ?

Posted: Sat Nov 28, 2020 8:57 pm
by Minoto
rizxt wrote:CTRL+ALT+DELETE is the universal keybind for resetting the computer. You should only block it if you intend to replace it with similar functionality.
Really? I don't recall that being the case on Sun systems, SGI systems, VAXen, Macs, etc...

Re: GNU-EFI how to get keyboard events ?

Posted: Sun Nov 29, 2020 9:16 am
by austanss
Minoto wrote:
rizxt wrote:CTRL+ALT+DELETE is the universal keybind for resetting the computer. You should only block it if you intend to replace it with similar functionality.
Really? I don't recall that being the case on Sun systems, SGI systems, VAXen, Macs, etc...
Like I said, you can replace it. Most modern computers running on bare metal resets on CTRL+ALT+DELETE.

Re: GNU-EFI how to get keyboard events ?

Posted: Sun Nov 29, 2020 11:57 am
by nullplan
rizxt wrote:Like I said, you can replace it. Most modern computers running on bare metal resets on CTRL+ALT+DELETE.
I believe Minoto's point was more that this is specific to PCs, and does not work on systems that are not PCs. That said, there is nothing special about CTRL+ALT+DEL even on a PC until the button is handled in the OS. There is no special interrupt associated with that, no control line that only gets triggered in that case, or anything. If you can talk to the keyboard directly, it is just another key combination. The "bare metal reset" must be implemented in BIOS, and is no longer relevant once BIOS has given up control of the keyboard.