Sunday 22 March 2020

Redirecting keystrokes from Linux to the C64 module: Part 2

Foreword

In the previous post we have started the process of capturing keystrokes in Linux and rediecting them to out C64 module.

We ended with a Kernel driver and a very simple user program and left the PC scancode mapping to C64 scancode mapping for this post.

For the scancode mapping we could have probably hack together something quickly, with a long case statement just doing a mapping of the essentials keys.

I did indeed followed a similar minimalistic approach with previous emulators that I wrote, and it worked just fine.

However, when entering programs with such a minimal mapping, it becomes a guessing game when you need to enter a shift key sequence. On a PC keyboard, for instance, hitting Shift and '2', gives you the @-sign. On a C64 keyboard, the same key sequence gives you double quotes (").

In this regard the Vice Commodore emulator do a very convenient key mapping. If you enter a Shift+2 sequence on a PC keyboard, a @ will be displayed on the C64 screen.  Thus, no need to have a photo of a C64 handy when typing Shift sequences!

In this post we will look into how keymapping works in the Vice Commodore emulator, and see how we can apply a similar keymapping within our C64 implementation.

An Overview of Vice key mapping

Let us do a quick overview on how key mapping works in the Vice emulator.

The easiest way to approach this exercise is to have a look at a key mapping file within the Vice source.

Start by downloading the source of Vice as a tarball, untarring it, and opening the file data/C64/sdl_sym.vkm.

This is a text file and apart from configuration information, it contains lots of comments.

One of the useful comments is the format of every line:

# - normal line has 'keysym/scancode row column shiftflag'

Each line start with a scan code for a particular key on a source keyboard.

Quickly looking at these scan codes, it appears to be the ASCII representation of applicable key. The key 'A', for instance will have the scancode 97 in the file (e.g. lowecase a). Similarly, the '1' key will have the code 49.

The next two values, row and column, represents the row and column values from the C64 keyboard for the associated key.

The final value on a row, shiftflag, tells whether a shiftkey is applicable for this key mapping. This will become clear in a moment.

Further on in the file we have a couple of comments giving more information about shiftflag:

# Shiftflag can have the values:
# 0      key is not shifted for this keysym/scancode
# 1      key is shifted for this keysym/scancode
# 2      left shift
# 4      right shift
# 8      key can be shifted or not with this keysym/scancode
# 16     deshift key for this keysym/scancode
# 32     another definition for this keysym/scancode follows
# 64     shift lock
# 256    key is used for an alternative keyboard mapping

Let us try to understand these shiftflags by looking at some examples.

We start with a simple example:

49 7 0 8               /*            1 -> 1            */

Here the shiftflag is 8, which, according to the table, can be either shifted or not. In short, this means for that particular key, we can blindly pass shift key presses on the PC keyboard to the C64 core. This is because on both a PC keyboard and C64 keyboard a Shift+1 corresponds to an exclamation mark (!).

Let us look at a more complex example:

50 7 3 32              /*            2 -> 2            */
50 5 6 16              /*            @ -> @            */

The first row have a shiftcode of 32, which, according to the table: another definition for this keysym/scancode follows.

This means that when you use this key with a shift, don't simply pass on the shift key to the C64 core. In such a case you need to consider the definition in the next row.

In this, the definition in the next row give us a new C64 scan code for the PC Shift+2 combination. Also, the shift code for this row is 16, meaning that we should not send a shift key to the C64 core for this key combination. So, on a PC Shift+2 corresponds to @. On a C64 you can access the @-key without a shift.

Let us look at another example:

55 3 0 32              /*            7 -> 7            */
55 2 3 1               /*            & -> &            */

Here the Shift+7 key has the shiftcode 1. This means that not only does Shift+7 maps to a different C64 scancode, but in addition we need to send a shift key to the C64 core.

Let us end off this section with another interesting example:

39 3 0 33              /*            ' -> '            */
39 7 3 1               /*            " -> "            */

In the first row we have two shift set simultaneously. This firstly means that for Shift+', use the definition in the  next row.

It also means that if we use (') without the shift, we need to also pass a shift to the C64 core. This is because to type a (') on the C64 we need to use the key combination Shift+7.

ASCII like scan codes

I mentioned previously that the source scan codes in sdl_sym.vkm corresponds more or less to the ASCII code of the relevant keys.

However, the scan codes we get from Linux, doesn't have any relation to a equivalent ASCII code whatsoever.

So, we need a conversion table between Linux scan codes to the scan code we require. This table will look more or less like the following:

struct keyboard {
           char code;
           char desc[20];
};
struct keyboard temp[256] ={
  {},
  {'\x1b',"ESCAPE"}, //1
  {'1',"Key1"}, //2
  {'2',"Key2"}, //3
  {'3',"Key3"}, //4
  {'4',"Key4"}, //5
  {'5',"Key5"}, //6
  {'6',"Key6"}, //7
  {'7',"Key7"}, //8
  {'8',"Key8"}, //9
  {'9',"Key9"}, //a
  {'0',"Key0"}, //b
...
  {'q',"KeyQ"}, //10
  {'w',"KeyW"}, //11
  {'e',"KeyE"}, //12
  {'r',"KeyR"}, //13
  {'t',"KeyT"}, //14
  {'y',"KeyY"}, //15
  {'u',"KeyU"}, //16
  {'i',"KeyI"}, //17
  {'o',"KeyO"}, //18
  {'p',"KeyP"}, //19
  {'[',"Key["}, //1a
...
};

So, for instance if someone press the key '1', we will get the scan code 2 from Linux. If we look at position 2 in this lookup table, we will find char value '1'.

The question might arise what code we can use as a scan code for modifier keys (e.g. shift, control) that doesn't really map to any ASCII code.

For these keys we could either use Capitals or ASCII codes after 128.

Parsing sdl_sym.vkm

Earlier on we discussed the structure of a vkm file.

The question at his point is: How do we parse such a file and store in a structure that we can easily transform a PC scancode to a C64 scancode?

For this purpose we can create a lookup table where we use the PC scan code as an index to retrieve the appropriate row that contains the resulting C64 scan code.

That is easy enough, but how do we cater for the shifted version of a scancode?

We can cater the shifted version by having two elements in each row of the lookup table, with the second element in a row been the shifted version of a scancode.

You might also remember from our discussion on the vkm file structure, that the resulting C64 scancode might optionally have an shift key assosiated with it. To cater for this, our lookup table need to have 4 elements per row.

Let us do some coding and start by defining this lookup table and initialising it:

...
int key_map[256][4];
...
void init_table() {
  for (int i = 0; i < 256; i++) {
    key_map[i][0] = -1;
    key_map[i][1] = -1;
    key_map[i][2] = -1;
    key_map[i][3] = -1;
  }
...
}

We want to keep the parsing of the vkm file simple, so we will need to modify this file a bit. We will be removing all comment lines at the beginning. We will also remove all the negative scancodes towards the end of the file.

Speaking of removing comments. You will see that each mapping line ends with a comment. It is not necessary for a vigorous exercise to remove these comments as well. Instead, when reading each line, we will read the first four items on every line that we need and skip straight to the next line.

Let us see if we can implement this functionality in code:

void init_table() {
...
  FILE *fp;
  fp = fopen("sym.txt", "r");
  char input[80];
  int num1, num2, num3, num4;
  while (1) {
    int status = fscanf(fp,"%d", &num1);
    fscanf(fp,"%d", &num2);
    fscanf(fp,"%d", &num3);
    fscanf(fp,"%d", &num4);
    fscanf(fp,"%[^\n]",input);
    if (status == EOF)
      break;
  }
...
}

sym.txt is our modified vkm as described earlier.

For every line we read our four values, and skip to the next line with [^\n].

We can now use num1, num2, num3 and num4 on each line to populate the lookup table.

Let us start with the simple case for rows having the shiftcode 8:

void init_table() {
  for (int i = 0; i < 256; i++) {
    key_map[i][0] = -1;
    key_map[i][1] = -1;
    key_map[i][2] = -1;
    key_map[i][3] = -1;
  }
  FILE *fp;
  fp = fopen("sym.txt", "r");
  char input[80];
  int num1, num2, num3, num4;
  while (1) {
    int status = fscanf(fp,"%d", &num1);
    fscanf(fp,"%d", &num2);
    fscanf(fp,"%d", &num3);
    fscanf(fp,"%d", &num4);
    fscanf(fp,"%[^\n]",input);
    if (status == EOF)
      break;
    if (num4 == 8) {
      key_map[num1][0] = (num2 << 3) | num3;
      key_map[num1][1] = 0;
      key_map[num1][2] = (num2 << 3) | num3;
      key_map[num1][3] = 1;
    }
  }
}


Here is number of things going on here, so let us unpack it a bit.

First, we are combining the row and column value into a single number, by putting row value at bits 5-3 and the column value into bits 2-0.

Also element 0 and 1 on the row, is for the unshifted version of the PC scancode, and element 2 and 3 for the shifted version.

Element 1 and 3 indicates whether we should pass a shift key to our C64 core with the resolved scancode.

Next, let us cater for rows having shiftcode as 1:

void init_table() {
  for (int i = 0; i < 256; i++) {
    key_map[i][0] = -1;
    key_map[i][1] = -1;
    key_map[i][2] = -1;
    key_map[i][3] = -1;
  }
  FILE *fp;
  fp = fopen("sym.txt", "r");
  char input[80];
  int num1, num2, num3, num4;
  while (1) {
    int status = fscanf(fp,"%d", &num1);
    fscanf(fp,"%d", &num2);
    fscanf(fp,"%d", &num3);
    fscanf(fp,"%d", &num4);
    fscanf(fp,"%[^\n]",input);
    if (status == EOF)
      break;
    if (num4 == 8) {
      key_map[num1][0] = (num2 << 3) | num3;
      key_map[num1][1] = 0;
      key_map[num1][2] = (num2 << 3) | num3;
      key_map[num1][3] = 1;
    } else if (num4 == 1) {
      key_map[num1][0] = (num2 << 3) | num3;
      key_map[num1][1] = 1;
    } 
  }
}

For rows having this shift code we don't have a shifted version for these scancodes.

Finally let us cater for rows with shiftcode 32. As seen in a previous section we can have cases where shiftcode 32 and 1 can be enabled simultaneously. So, when checking for flag 32, we need to mask off all other bits:

void init_table() {
  for (int i = 0; i < 256; i++) {
    key_map[i][0] = -1;
    key_map[i][1] = -1;
    key_map[i][2] = -1;
    key_map[i][3] = -1;
  }
  FILE *fp;
  fp = fopen("sym.txt", "r");
  char input[80];
  int num1, num2, num3, num4;
  while (1) {
    int status = fscanf(fp,"%d", &num1);
    fscanf(fp,"%d", &num2);
    fscanf(fp,"%d", &num3);
    fscanf(fp,"%d", &num4);
    fscanf(fp,"%[^\n]",input);
    if (status == EOF)
      break;
    if (num4 == 8) {
      key_map[num1][0] = (num2 << 3) | num3;
      key_map[num1][1] = 0;
      key_map[num1][2] = (num2 << 3) | num3;
      key_map[num1][3] = 1;
    } else if (num4 == 1) {
      key_map[num1][0] = (num2 << 3) | num3;
      key_map[num1][1] = 1;
    } else if (num4 & 32) {
      key_map[num1][0] = (num2 << 3) | num3;
      key_map[num1][1] = num4 & 1;
      fscanf(fp,"%d",&num1);
      fscanf(fp,"%d",&num2);
      fscanf(fp,"%d",&num3);
      fscanf(fp,"%d",&num4);
      fscanf(fp,"%[^\n]",input);
      key_map[num1][2] = (num2 << 3) | num3;
      key_map[num1][3] = num4 & 1;
    }
  }
}


When we reach a row with shiftflag 32, we also need to read the next line to get details about the shifted version of the scancode.

Transforming to C64 scancodes

Let us bring all code we have developed in this post together. The following outline shows the changes we need to make to our main method so that we convert PC scancodes to C64 scancodes:

int main(){
...
  init_table();
...
  while(1) {
...
    char keys_to_process[4];
...
    readKeyboard();

    for (int i = 0; i < 6; i++) {

      char ps_2_code = temp[keys[i]].code;
      if ((ps_2_code == 'C') || (ps_2_code == 'B')) {
        shifted = 1;
        continue;
      }
      keys_to_process[num_keys_to_process] = ps_2_code;
      num_keys_to_process++;
    }

    keyToProcess.word1 = 0;
    keyToProcess.word2 = 0;

    for (int i = 0; i < num_keys_to_process; i++) {
      int offset = shifted << 1;
      int c64_scan_code = key_map[keys_to_process[i]][offset];
      if (c64_scan_code == -1)
        continue;
      if (c64_scan_code < 32) {
        keyToProcess.word1 = keyToProcess.word1 | (1 << c64_scan_code);
      } else {
        c64_scan_code = c64_scan_code - 32;        
        keyToProcess.word2 = keyToProcess.word2 | (1 << c64_scan_code);
      }
      if (key_map[keys_to_process[i]][offset+1]) {
        keyToProcess.word1 = keyToProcess.word1 | (1<<15);
      }
    }
    ret = write(fd, &keyToProcess, 8); // Send the string to the LKM

  }
   return 0;
}


We start by calling init_table, which we developed earlier on.

We then take the captured keys produced by readKeyboard(), and convert them to ASCII like scancodes as they appear in the VKM file. The translated keys are stored in the array keys_to_process.

During this loop we also determine if any shift key is been held done, and set the variable shifted as such. I would also like to mention here that in this transaltion process I have decided to assign the left- and right-shiftkey to the ASCII values 'C' and 'B' respectively.

Once we have translated all keys, we loop though them and translate it to C64 scancodes. Here we make use of the shifted variable to decide if we are going to either use elements 0/1, or elements 2/3.

We finally enable the shiftKey in the C64 keyboard matrix if either element 1 or 3 are set.

Mapping the cursor keys

Up to this point the mapping of PC keys to C64 scancodes has been fairly straightforward. Our mapping model, however, falls a bit on its face when we try to map the cursor keys.

When we press one of the cursor keys, Linux returns us a break code preceding the actual scan code.

For instance, if we press cursor up, we will receive the following bytes:

E0 48

This is a bit problematic for our process that maps to ASCII like scancodes, which only works with single byte values.

We could condense these two bytes into a single one with the fact that these scan codes usually only make use of values 0 to 127. For scancodes preceded by E0, we can just set bit 7, and then set the lower 7 bits with our actual scan codes.

On thing we should aware of, though, is that when we release a key, we get a scan code of which bit 7 is also set.

With all this in mind, let us write some code for condensing a scan code with a break code, into a single byte.

We start with our readKeyboard() method. Previously this method read a single byte from stdin and acted accordingly.

Here we already have problems if we get a scancode with a breakcode. At this point we can already reduce our problems by reducing a scancode with a breakcode into a single number.

I will, however, reduce this number to a 9-bit number instead of a 8-number, so we can preserve information about whether the scancode is for a key down or a key up:

int getKeyCode(int *code) {
  char buf[1];
  int res;
  res = read(0, &buf[0], 1);
  if (res == -1)
    return -1;
  if ((buf[0] & 0xff) != 0xe0) {
    *code = buf[0] & 0xff;
    return res;
  }
  res = read(0, &buf[0], 1);
  if (buf[0] == 1) {
    restoreKeyboard();
    exit(0);
  }
  *code = (buf[0] & 0xff) | 0x100;
  return res;
}

void readKeyboard()
{
    int res;
    int code;
    res = getKeyCode(&code);
    processKey(code);
    while (res >= 0) {
        res = getKeyCode(&code);
        processKey(code);
    }
}


So, if we encounter a scancode with a breakcode, we just set bit 8 to a 1.

Next, we should change processKey so that it can interpret bit 8 accordingly:

void processKey(int scanCode) {
  if ((scanCode & 0x80)) {
    //do key release
    scanCode = (scanCode & 0x100) ? ((scanCode & 0x7f) | 0x80) : (scanCode & 0x7f);
    doKeyUp(scanCode);
  } else {
    //do key down
    scanCode = (scanCode & 0x100) ? ((scanCode & 0x7f) | 0x80) : scanCode;
    doKeyDown(scanCode);
  }
}


Once we have determined whether the scancode is a keydown or a keyup, we can reduce the scancode to 8-bits.

In Summary

In this post we have implemented scan code mapping from PC scancodes to C64 scancodes. For this purpose we have used a key mapping file from the Vice Commodore emulator.

In the next post we will be modifying our Test program so that it can accept a tape image filename as a parameter. The test program will then transfer the Tape Image from the Linux file system to the C64 core.

Till next time!

Sunday 1 March 2020

Redirecting keystrokes from Linux to the C64 module: Part 1

Foreword

In the previous post we implemented functionality within our C64 design allowing us to toggle between Linux console output and C64 video output.

We performed this toggling between the two video outputs with the help of a toggle button present on the Zybo board. Later on we will be moving the control of this video mode toggling to software.

In this post we will be focusing on redirecting keystrokes from Linux to our C64 module.

In order to do this we need to develop a Kernel Driver and a user program in userspace.

This is quite a lot to cover in one post, so in this post we will not be developing a complex PC key to C64 mapping mechanism. Instead, as a proof of concept, we will just be capturing two keystrokes from a keyboard to display on the C64 screen.

For this reason I have decided to split the whole keystroke redirection functionality into two posts. In the next post we will be tackling advanced PC key -> C64 key mapping.

Surfacing Screen mode to software

As mentioned in the forward, we need to work towards the goal of switching screen mode in software.

In order to achieve this, we need to surface this mode bit within a register in our Slave AXI block.

For this purpose we can just use Slave register 2, since we don't utilise all the bits of this register. Bits 8 to 4 gets utilised by the joystick bits, so we can use bit 9 for screen mode. For this we make the following changes to our user logic:

 // Add user logic here
    assign slave_reg_0 = slv_reg0;
    assign slave_reg_1 = slv_reg1;
    assign restart = slv_reg2[1];
    assign tape_button = slv_reg2[2];
    assign joybits = slv_reg2[8:4];
    assign c64_mode = slv_reg2[9];
 // User logic ends


We need to ensure that this c64_mode gets surfaced in our Slave AXI block:
This port we will connect to our VGA block, effectively replacing the connection from the push button on the Zybo board.

We will now be able to control the screen mode in software by just writing to bit 9 of address 0x43c0_0008.

As mentioned in a previous post, it is not so easy to access a physical address in Linux, especially in Userspace.

So, this is one of the reasons we will be developing a Kernel driver in this post.

Into Kernel drivers

Let us get our fingers dirty with writing a Kernel driver. For beginners there is a nice resource, Linux Device Drivers (third edition), available here.

Before we start writing a Kernel device driver, let us first focus on what we want to achieve.

To send one or more keystrokes to our C64 module, we need to set one or more bits in the registers located at addresses 0x43c0_0000 and 0x43c0_0004. Together these two registers contains 64 bits, which corresponds to the 64 keys you find on a C64 keyboard.

The idea is that we open this Kernel device driver as a file and we write two 32-bits at a time to this 'file'. Our kernel driver in turn will write these values to address 0x43c0_0000 and 0x43c0_0004 respectively.

To send both register values as a unit, we can make use of a struct with the following definition:
struct keyboard 
{
           u32 word1;
           u32 word2;
};


The details will become clear later.

To help us to get started quickly, it will help if we can find a minimalistic example on the Internet that is similar to what we want to achieve. Derek Molloy's comes to the rescue here: http://derekmolloy.ie/writing-a-linux-kernel-module-part-2-a-character-device/

Derek gives the source of this tutorial on his Github site. In particular, we are interested in the following two files:


In ebbchar.c there is all the necessary code for a fully fletched character driver. The example provided open the device driver as a file, write a string to it and then reads it back.

When I tried out this example, it crashed when i tried writing to the driver. At first I could figure out why this was happening. However, when I had a look at the read and write method together, I discovered something:

static ssize_t dev_read(struct file *filep, char *buffer, size_t len, loff_t *offset){
   int error_count = 0;
   // copy_to_user has the format ( * to, *from, size) and returns 0 on success
   error_count = copy_to_user(buffer, message, size_of_message);

   if (error_count==0){            // if true then have success
      printk(KERN_INFO "EBBChar: Sent %d characters to the user\n", size_of_message);
      return (size_of_message=0);  // clear the position to the start and return 0
   }
   else {
      printk(KERN_INFO "EBBChar: Failed to send %d characters to the user\n", error_count);
      return -EFAULT;              // Failed -- return a bad address message (i.e. -14)
   }
}

static ssize_t dev_write(struct file *filep, const char *buffer, size_t len, loff_t *offset){
   sprintf(message, "%s(%zu letters)", buffer, len);   // appending received string with its length
   size_of_message = strlen(message);                 // store the length of the stored message
   printk(KERN_INFO "EBBChar: Received %zu characters from the user\n", len);
   return len;
}

In dev_read there is a call to copy_to_user, but not a similar call within dev_write. When passing a pointer from user space to kernel space, functions like copy_to_user and copy_from_user is necessary to move the information  between the two spaces.

Writing the C64 Keyboard driver

In the previous section we had a look at Derek Molloy's example Kernel driver. With the minimum amount of tweaks to this example driver, we can easily create our C64 Keyboard driver.

We start off by mapping our Slave AXI registers into Kernel Space:

...
static void __iomem *c64_reg_base;
static void __iomem *c64_reg_screen_mode;
static void __iomem *c64_reg_keyboard_0;
static void __iomem *c64_reg_keyboard_1;
...
static int __init ebbchar_init(void){
...
   c64_reg_base = ioremap(0x43c00000, 16384);
   c64_reg_screen_mode = c64_reg + 8;
   c64_reg_keyboard_0 = c64_reg;
   c64_reg_keyboard_1 = c64_reg + 4;
...
   return 0;
}
...

The key here is the call to ioremap, which maps maps a 16KB region, starting at the first address of our Slave AXI regsiters, into virtual memory.

We then define some more pointers in which we can access the keyboard bits and C64 screen mode directly.

I was thinking for some time what kind of interface we could use for switching between two screen modes. This ended off not to be a problem at all. We can just switch to C64 screen moe when we open the driver, and switching back to Linux Console mode when we close the driver again:

...
static int dev_open(struct inode *inodep, struct file *filep){
   numberOpens++;
   printk(KERN_INFO "EBBChar: Device has been opened %d time(s)\n", numberOpens);
   iowrite32(0x200, c64_reg_screen_mode);
   return 0;
}
...
static int dev_release(struct inode *inodep, struct file *filep){
   printk(KERN_INFO "EBBChar: Device successfully closed\n");
   iowrite32(0x0, c64_reg_screen_mode);
   return 0;
}
...

What we still need to do is to take writes to our kernel driver and sending this information to the physical registers:

static ssize_t dev_write(struct file *filep, const char * keys, size_t len, loff_t *offset){
   struct keyboard temp[1];
   copy_from_user(temp, keys, 8);
   iowrite32(temp[0].word1, c64_reg_keyboard_0);
   iowrite32(temp[0].word2, c64_reg_keyboard_1);
   return 8;
}


Our dev_write accepts the keys as a pointer of char. This is to conform to the interface when creating a character file driver. Here we cheat a bit, however. The actual data we will be sending will not be a an array of char, but a struct of keyboard.

Internally we will copy this data an actual keyboard structure. Lastly we will write the actual data to the actual registers.

With Linux running on our Zybo board, to load this driver is a two step process.

Firstly, similarly as we done with our Linux Framebuffer driver, we need to issue a insmod command, for loading the kernel driver so it can be used by the Linux Kernel driver.

When this particular driver loads, it will output the major number it is registered as. Make a note of this number, as you will need it to in order to add it as a node under /dev.

To add a node under /dev for this device, issue the following command:

mknod /dev/ebbchar c 244 0

In my case the major device number was 244. Also, the c indicates that we are about to add a character driver.

Writing the user program

Our test program is kind of a merge, where we take a take program in a previous post where captured keystrokes in Linux, together with Derek Molloy's test program.

Let us start the discussion by looking at the final main() method:

int main(){
   int ret, fd;
   char stringToSend[BUFFER_LENGTH];
   printf("Starting device test code example...\n");
   fd = open("/dev/ebbchar", O_RDWR);             // Open the device with read/write access
   if (fd < 0){
      perror("Failed to open the device...");
      return errno;
   }

  setupKeyboard();
  struct keyboard keyToProcess;

  while(1) {
    usleep(20000);
    readKeyboard();
    keyToProcess.word1 = 0;
    keyToProcess.word2 = 0;
    for (int i = 0; i < 6; i++) {
      if (keys[i] == 0)
        continue;
      int translated = getC64ScanCode(keys[i]);
      keyToProcess.word1 = (translated < 32 ) ? (keyToProcess.word1 | (1 << translated)) : keyToProcess.word1;
      if (translated > 31) {
        translated = translated - 32;
        keyToProcess.word2 = keyToProcess.word2 | (1 << translated);
      } 
    }
    ret = write(fd, &keyToProcess, 8); // Send the string to the LKM
    if (ret < 0){
       perror("Failed to write the message to the device.");
       return errno;
    }

  }
   return 0;
}

We start by opening a file handle to our device driver. In a main loop we capture key up/down events from the keyboard, convert it to a C64 scancode and send to our device driver as a keyboard struct.

You will remember from a previous post that we maintain a global keys array, which indicates which keys are currently been held down. This array caters for up to 6 elements and if an element is not in use, it will simply hold the value zero.

We will be implenting the method getC64ScanCode in the next post.

In Summary

In this post we have created a Linux Kernel device driver that will accept C64 scan codes from a user program and forward it to our C64 module.

In the next post we will be functionality where we will map PC scancodes to C64 scancodes. In this process I will try to utilise the keyboard mapping functionality present in the Vice C64 emulator.

Till next time!