Keyboard Driver Implementation Tutorial

This topic describes how to implement an interrupt driven keyboard driver.

The steps are:

  • Implement the driver entry point function and initialisation code. This function is called when the extension is loaded.

    The initialisation code binds the hardware interrupt to the Interrupt Service Routine (ISR) and enables the interrupt.

  • Implement an ISR to handle key events. The ISR queues a keyboard DFC.

  • Implement a keyboard DFC. This function interrogates the keyboard, converts the scancode to a keypress event, and places it onto the kernel's event queue.

Set Up

In the template reference board port, the .mmp file for the keyboard driver is ...\template_variant\exkey_inttemplate.mmp. This is one of the PRJ_MMPFILES referenced in the template variant's bld.inf file in the ...\template_variant\... directory, and means that the keyboard driver is built as part of the Variant.

The source for the driver is contained entirely within ...\template_variant\specific\keyboard_interrupt.cpp.

The driver is defined as a kernel extension and is loaded early in the boot sequence.

Entry point implementation

The driver functionality is encapsulated by the DKeyboardTemplate class, and an instance of this is created when the extension is loaded.

As the driver is a kernel extension, it must have a DECLARE_STANDARD_EXTENSION() statement. In the template port, this is implemented:

DECLARE_STANDARD_EXTENSION()
    {
    __KTRACE_OPT(KEXTENSION,Kern::Printf("Starting keyboard driver"));

    // create keyboard driver
    TInt r=KErrNoMemory;
    DKeyboardTemplate* pK=new DKeyboardTemplate;
    if (pK)
        r=pK->Create();

    __KTRACE_OPT(KEXTENSION,Kern::Printf("Returns %d",r));
    return r;
    }

It simply creates an instance of the DKeyboardTemplate class and then performs a second-phase initialisation, which is a common pattern in Symbian platform and third party applications.

Initialisation on construction

The constructor of the DKeyboardTemplate class has the following signature:

DKeyboardTemplate::DKeyboardTemplate()
    :    DPowerHandler(KLitKeyboard), 
        iMsgQ(rxMsg,this,NULL,1),
        iPowerUpDfc(PowerUpDfcFn,this,6),
        iPowerDownDfc(PowerDownDfcFn,this,7),
        iEventDfc(EventDfcFn,this,1)
    {
    }

See also Interrupt Dispatcher Tutorial.

Interrupt Service Routine (ISR) implementation

The ISR (Interrupt Service Routine) just schedules the DFC that handles the keypress. It can also optionally contribute the timing of key interrupts as a source of random data for the Random Number Generator (see CSPRNG Implementation in Kernel). On the template reference board, this is implemented as:

void DKeyboardTemplate::Isr(TAny* aPtr)
	{
	Interrupt::Disable(KIntIdKeyboard);
	
	// Add the timing of key interrupts as entropy data for the RNG
	Interrupt::AddTimingEntropy();
	
	k.iEventDfc.Add();
	}

The ISR disables the keyboard interrupt, as repeated ISRs are not allowed to queue further DFC routines.

DFC function implementation

The DFC is the function DKeyboardTemplate::EventDfcFn which is implemented as a call to DKeyboardTemplate::EventDfc().

void DKeyboardTemplate::EventDfcFn(TAny* aPtr)
    {
    ((DKeyboardTemplate*)aPtr)->EventDfc();
    }

void DKeyboardTemplate::EventDfc()
    {
    __KTRACE_OPT(KHARDWARE,Kern::Printf("DKeyboardTemplate::EventDfc"));

    TInt irq=NKern::DisableAllInterrupts();
    while (IsKeyReady())                        // while there are keys in the controller's output buffer
        {
        NKern::RestoreInterrupts(irq);
        TRawEvent e;
        TUint keyCode=GetKeyCode();                // Read keycodes from controller
        __KTRACE_OPT(KHARDWARE,Kern::Printf("#%02x",keyCode));

        //
        // TO DO: (mandatory)
        //
        // Convert from hardware scancode to EPOC scancode and send the scancode as an event (key pressed or released)
        // as per below EXAMPLE ONLY:
        //
        TUint bareCode=keyCode&~KFlagKeyPressed;
        TUint8 stdKey=convertCode[bareCode];
        if (keyCode&KFlagKeyPressed)
            e.Set(TRawEvent::EKeyUp,stdKey,0);
        else
            e.Set(TRawEvent::EKeyDown,stdKey,0);
        Kern::AddEvent(e);
        NKern::Sleep(1);                        // pause before reading more keycodes
        irq=NKern::DisableAllInterrupts();
        }
    Interrupt::Enable(KIntIdKeyboard);
    NKern::RestoreInterrupts(irq);
    }

This:

  • reads the scan code data by calling DKeyboardTemplate::GetKeyCode()

  • re-enables the keyboard interrupt, now that the DFC has freed up the input data register

  • translates the keypress event into a Symbian scancode

  • puts the translated event on to the event queue.

See also

Concepts