Thursday, 2 April 2020

Loading tape images from Linux file system

Foreword

In the previous post we wrote some code for mapping PC scancodes to C64 scancodes, and sending the result to our C64 FPGA core.

In this post we will be writing some code for reading tape images from the Linux file system and sending them to our C64 core.

Overview

Let us start this post by familiarising ourselves again on how tape image loading  currently works in our C64 core.

Our C64 core uses an AXI master port to read tape image data from the SDRAM on the Zybo from a predefined location.

So, when triggering a tape load in our C64 core by typing LOAD<ENTER>, it is up to you to ensure that this predefined memory area is populated with a valid tape image.

Apart from the memory area that should contain the tape image, our C64 core also have a peripheral register mapped into the Zynq memory space that controls tape operation. This peripheral register is located at address 0x43c0_0008.

In this register there is two bits of importance for controlling tape loading:


  • Bit 2: Tape Button: This bit corresponds to the Play Button of an original Datasette unit. 
  • Bit 1: Reset tape data pointer: When you have populated the tape image area in memory with a new image, you should briefly set this bit to 1 and then back to zero. This ensure that when you trigger a LOAD from the C64 core, the reading will start at the beginning of the memory area.
So, our application running in Linux should perform a couple of things to cater for tape loading.

Firstly when the application starts, it should the requested tape image into the tape area in memory. The tape image to load should be supplied as a command line parameter .

From previous posts, you might remember that with user applications Linux, you work in Virtual Memory space, and don't have access to physical memory addresses. In our case we will need access to physical memory addresses in order populate the area in memory with the required tape image so that our C64 core can access it.

Our Linux Kernel driver in this regard will also need to act as mediator for moving the tape image to required memory area.

Using IOCTL's

In one the previous two posts we have developed Kernel driver that served as an interface between a user application and our C64 FPGA core.

Currently when opening this driver as a file, you can only write C64 scan codes to it.

Can you utilise this driver to control more aspects of the C64 core? For instance, if you not only want to send C64 scancodes to this driver, but also tape images?

One can indeed, with the help of IOCTL's. According to Wikipedia:
ioctl (an abbreviation of input/output control) is a system call for device-specific input/output operations and other operations which cannot be expressed by regular system calls.
This sounds exactly what we want to achieve with our device driver. IOCTL provides you a way of serving multiple with a single file handle.

We encountered ioctl's in a previous post where I explained how to capture keyup/keydown events in Linux within the setupKeyboard event:

    /* save old keyboard mode */
    if (ioctl(0, KDGKBMODE, &old_keyboard_mode) < 0) {
 return 0;
    }

Here we used an ioctl to get the current keyboard mode from stdin (e.g. file handle zero). By simply doing a read() call from stdin, you will just receive key events from the keyboard and you simply wouldn't be able to get the keyboard mode at all.

IOCTL gives us some helping out for this need. IOCTL is almost like a read() function call, but it provides you with an additional important parameter: A command parameter. In the previous example we provided the command parameter KDGKBMODE.

So, how does one define an ioctl call within your device driver? For starters, you need to define a function in your driver having the following signature:

int (*ioctl) (struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg);

In our case, we won't worry about the first two parameters. Our key parameters will only be the last two parameters, cmd and arg.

The arg parameter in our case will be a pointer, and we will need to cast it as such in our ioctl method.

Let us end this section, by defining a skeleton ioctl method, on which we will expand in coming sections:

...
static struct file_operations fops =
{
   .open = dev_open,
   .read = dev_read,
   .write = dev_write,
   .unlocked_ioctl = c64_ioctl,
   .release = dev_release,
};
...
static long c64_ioctl (struct file *filp,
                   unsigned int cmd, unsigned long arg) {

}
...

I have also added an extra member to out fops struct. This method instructs Linux on which method to call when we do an ioctl on our C64 driver.

Changes to our driver

Let us tart with the necessary changes to our C64 kernel driver.

The first thing that comes to mind is that currently, when we open the driver, we switch to the C64 screen right away.

So, in effect the copying of the tape data will take place while the C64 screen is already active. Somehow, this scenario doesn't seem so clean. We would want to only switch to the C64 screen once all background initialisation has been completed.

So let us do the following modifiction to the open method:

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;
}

No switching to C64 screen on open anymore!!

Next, let us work on the actual tape loading mechanism. Let us start with by defining a memory mapping for a physical address range storing the tape image that our C64 core will retrieve:

 ...
static void __iomem *tape_mem_area;
...
static int __init ebbchar_init(void){
...
  tape_mem_area =  ioremap(0x1f500000,
           2000000);
...
   return 0;
}

I have used start address 0x1f50_0000, which is also above the 500MB mark, out of available Kernel space.

Next, let us define an IOCTL call for copying tape data from userspace to out tape area:

...
static long c64_ioctl (struct file *filp,
                   unsigned int cmd, unsigned long arg) {
...
  if (cmd == 0) {
    unsigned char * user = (unsigned char *) arg;
    copy_from_user(tape_data, user, 8192);
    for (i = 0; i < 8192; i++) {
      iowrite8(tape_data[i], i + tape_mem_area + tape_pointer);
    }
    tape_pointer = tape_pointer + 8192;
  } 
...
  return 0;
}
...

So, zero is the command code to send chunks of data from our userspace program to the Kernel.

This call assumes a chunk size of 8KB per call. So, you need to always pass a pointer to an array of char with 8192 elements. This simplify code somewhat. If we are about to send the last portion of the tape file, the chunk size might be less than 8192 bytes. The remaining garbage in the array is not an issue since our C64 core only reads what it needs.

The tape_pointer variable keeps track of where we are currently with the copying of the tape image.

Once we are finished copying the tape image, we need a IOCTL call to actually switch to C64 and to inform the C64 core to start reading the tape image from the beginning:

static long c64_ioctl (struct file *filp,
                   unsigned int cmd, unsigned long arg) {
  int i;
  if (cmd == 0) {
    unsigned char * user = (unsigned char *) arg;
    printk("after cast\n");
    copy_from_user(tape_data, user, 8192);
    for (i = 0; i < 8192; i++) {
      iowrite8(tape_data[i], i + tape_mem_area + tape_pointer);
    }
    tape_pointer = tape_pointer + 8192;
  } else if (cmd == 1) {
    iowrite32(0x206, c64_reg_screen_mode);
    msleep(1000);
    iowrite32(0x204, c64_reg_screen_mode);
  } 

  return 0;
}

The reset function currently only resets the C64core's pointer to the beginning of the tape area. However, the FPGA can be altered to also reset the 6502 CPU.

We finally need one last IOCTL call to simulate the press of the Play button:

static long c64_ioctl (struct file *filp,
                   unsigned int cmd, unsigned long arg) {
  int i;
  if (cmd == 0) {
    unsigned char * user = (unsigned char *) arg;
    printk("after cast\n");
    copy_from_user(tape_data, user, 8192);
    for (i = 0; i < 8192; i++) {
      iowrite8(tape_data[i], i + tape_mem_area + tape_pointer);
    }
    tape_pointer = tape_pointer + 8192;
  } else if (cmd == 1) {
    iowrite32(0x206, c64_reg_screen_mode);
    msleep(1000);
    iowrite32(0x204, c64_reg_screen_mode);
  } else if (cmd == 3) {
    iowrite32(0x200, c64_reg_screen_mode);
  }

  return 0;
}

Changes to our userspace program

Let us see what changes we need for our userspace program.

We start with the following changes:

int main(int argc, char *argv[]){
...
   unsigned char data[8192];
   tapefile = fopen(argv[1],"r");
   int num_read;
   do {
     num_read = fread(data, 1, 8192, tapefile);
     ioctl(fd,0,&data);
   } while (num_read == 8192);

   ioctl(fd,1);
...
}


We receive the filename of the required as a parameter on the commandline. We then open this file and send chunks of 8KB to the driver.

Finally we call command #1 on the driver, which enables the C64 screen and reset the C64core so that it reads tape data from the beginning.

We finally need to cater for allowing the user to enable the Play, when required to do so. I have decided to allocate the Function key F11 for this purpose.

I will checking for this key in the loop that process 'ASCII'-scancodes:

    for (int i = 0; i < num_keys_to_process; i++) {
      int offset = shifted << 1;
      if (keys_to_process[i] == 0x18) {
        ioctl(fd,3);
        continue;
      }
      int c64_scan_code = key_map[keys_to_process[i] & 0xff][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] & 0xff][offset+1]) {
        keyToProcess.word1 = keyToProcess.word1 | (1<<15);
      }
    }

In my mapping file where I map Linux scan codes to ASCII-scancodes I have mapped the resulting code for F11 to 0x18. When we encounter this key, we make IOCTL call to enable the play button, and skip further processing of this key.

In Summary

In this post we have developed some functionality for loading tape images stored on a Linux File system, and copying it to an area of memory which our C64 core can access to trigger a tape load.

In the next post we will be mapping keys for a joystick, so we can play the game we have load from the tape image on the Linux File System!

Till next time!

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!

Saturday, 15 February 2020

Toggling between C64 video output and the Linux console

Foreword

In the previous pot we wrote a small program running under Linux that captures the Scan codes that gets pressed and released on a keyboard.

This paved way for a future post where we will be delegating key presses in Linux to the C64 module.

In this post we will start with some Linux/C64 module integration.

In particular, we will be developing some functionality that allows us to toggle video output to the VGA screen between the Linux console and the video output from the C64 FPGA module.

Overview

Let us start by having an overview of what we want to achieve.

The last time we work on our C64 CPGA module was in this post, where we have added a scaler for scaling up the video frames our C64 module produces, so that it fills the screen.

Obviously, when we toggle from our C64 video output to the Linux console, we will need to bypass this scaler. This bypass is depicted by the illustration below:


The red arrow indicates when we want to display the Linux console on the VGA screen, we need to bypass the video scaler, as well as the succeeding buffer. So, in this scenario we effectively outputting pixels directly from  the first pixel buffer to the VGA screen.

Apart from bypassing the video scaler related components, we will also need to adjust the visible portion of the screen when toggling between the C64 video output and the Linux console. The Linux console will fill the whole screen, wheras the C64 module wont fit the complete width, e.g. a black band on the left and the right.

To trigger a change of video output source I will be utilising one of the push buttons on the Zybo board. When this button isn't pressed, we will show the Linux console on the VGA screen. If you push this button, the C64 screen will be shown. As soon as you release the button, the Linux console will be shown again.

In a future post we will be writing a program running under Linux that will automatically switch to the C64 screen when we run it, and switching back to the Linux console when we terminate this program.

Changing Framebuffer Addresses

Having to cater for two possible video sources (e.g. Linux console and C64), we need to assign a separate framebuffer area in memory for each.

From a previously post we have seen that it is better to host a framebuffer area at the top of the Zybo RAM and permit Linux to only use memory below this region. This just avoid headaches of trying to get Linux allocating large chunks of physical contiguous memory.

To accommodate the Linux framebuffer console, we have decided to limit allowable Linux memory to 500MB and put the framebuffer at address 507MB, resolving to physical address 0x1fb00000.

For the C64 video output we can reserve a framebuffer at address 503MB. This in turn resolves to physcial address 0x1f700000.

In previous posts C64 video output was always written to address 0x200000, so we need to change this in the burst write unit of our C64 module

always @(negedge clk)
if (!reset | (next_frame & count_in_buf == 0))
begin
  axi_start_address <= /*32'h200000*/32'h1f700000;
  axi_data_inc <= 0;
end
else if (state == INIT_CMD)
begin
  axi_start_address <= axi_start_address + axi_data_inc;
  axi_data_inc <= {BURST_THRES,2'b0};
end    


We have covered the core surrounding this code in a post a year or so ago. Basically this snippet keeps track of the address to use for each AXI burst. This gets reset at the beginning of each frame to display.

For our C64 video output we can always just use the same address. In case we change video source to Linux console, our C64 can continue writing its value output to its memory area without causing any harm.

So, our C64 module doesn't need much changes to accommodate two video sources. Our VGA module, however, needs to be able to read framebuffer data from two different areas.

For starters, we need to tell our VGA module which display mode we are in. We do this with an input port:

module vga(
  input wire clk,
  input wire clk_axi,
  input wire reset,
  output wire vert_sync,
  output wire horiz_sync,
  output wire [4:0] red,
  output wire [5:0] green,
  output wire [4:0] blue,  
  output wire [31:0] ip2bus_mst_addr,
  output wire [11:0] ip2bus_mst_length,
  input wire [31:0] ip2bus_mstrd_d,
  output wire [4:0] ip2bus_inputs,
  input wire [5:0] ip2bus_otputs,
  input wire c64_mode_in,
...
    );
...
reg c64_mode;
...
always @(posedge clk_axi)
  c64_mode <= c64_mode_in;
...


Later in this post we will be connecting c64_mode_in to a toggle button present on the Zybo board.

I am sampling this input port to a flip-flop just to cater for potential button bounces. This is probably an overkill, but I am just doing this in case...

Next, we need to make the following modifications to our AXI burst read block within our VGA module:

burst_read_block my_read_block(
          .clk(clk_axi),
          .reset(reset),
          .restart(trigger_restart_state == RESTART_STATE_RESTART),
          .count_in_buf(),
          .ip2bus_mst_addr(ip2bus_mst_addr),
          .ip2bus_mst_length(ip2bus_mst_length),
          .ip2bus_mstrd_d(ip2bus_mstrd_d),
          .ip2bus_inputs(ip2bus_inputs),
          .ip2bus_otputs(ip2bus_otputs),
          .axi_d_out(axi_read_data),
          .empty(axi_buffer_empty_temp),
          .read(read_from_axi),
          .start_address(c64_mode ? 32'h1f700000 : 32'h1fb00000)          
            );


We have added the start_address port in a previous post. Now we just take it one step further and provide a different adddress depending on whether c64_mode is enabled or not.

Different visible regions

Another difference between C64 video output and the Linux console we should address, is the difference between visible regions. The Linux console fills the screen whereas the C64 video output doesn't fill the whole width of the screen.

Let us start by defining a computational logic output for indicating when we are in a visible portion of the screen:

...
assign visible_region_vga = (vert_pos_next > 0)  & (vert_pos_next < 766) &
                           (horiz_pos_next > 0) & (horiz_pos_next < 1361);
                           
assign visible_region_c64 = (vert_pos > 20)  & (vert_pos < 760) &                               
                            (horiz_pos > 100) & (horiz_pos < 1175); 

assign visible_region = c64_mode ? visible_region_c64 : visible_region_vga;
...

Together with the different visible regions, we also need to able to select pixel display data from different sources:

assign pixel_display_data = c64_mode ? fifo_data_read : out_pixel_buffer;

So, in C64 mode we output the FIFO pixel buffered data from the scalar unit. With the Linux console mode, we output directly from the 16-bit asynchronous FIFO buffer, which buffer data from the AXI burst unit.

All this we can combine into the following, which will give us a black border for the portion of the screen which the applicable video output doesn't fill:

 assign out_pixel_buffer_final = visible_region ? pixel_display_data : 0;


The Final touches

We have developed the majority of functionality so that our FGPA design can display video from different sources.

There is a couple of remaining things that needs to be implemented.

First thing we need to have a look at is controlling the reading of data from the Asynchronous FIFO.

In C64 mode the Scalar unit will control the reading from the Asynchronous FIFO, whereas in Linux Console mode we will trigger reads from this FIFO when we are in the visible portions of the screen.

This translates to the following change:

aFifo
  #(.DATA_WIDTH(16))
  my_fifo
    (.Data_out(out_pixel_buffer), 
     .Empty_out(async_empty),
           .ReadEn_in(c64_mode ? (nextDIn & data_valid_in) : visible_region_vga),

     .RClk(clk),        
     .Data_in(shift_reg_16_bit[31:16]),  
     .Full_out(buffer_full),
     .WriteEn_in((state_shift_reg == STATE_16_SHIFT_STORED | state_shift_reg == STATE_16_SHIFT_SHIFTED) & !buffer_full),
     .WClk(clk_axi),
  
     .Clear_in(trigger_restart_state == RESTART_STATE_RESTART)
     

     );


The final thing we should address is the different orders pixels are stored for the two video sources for each 32-bit word received from the AXI bus. For instance, on the C64 module each 32-bit AXI word has the first pixel in line in the higher 16 bits, and the next pixel in line as the lower 16-bits.

On the Linux console video source has the first pixel in line in the lowest 16 bits of the 32-bit word, whereas the next pixel in line as the higher order 16-bits.

We need to account for this in our pixel splitter:

always @(posedge clk_axi)
  if (read_from_axi)
    shift_reg_16_bit <= c64_mode ? {axi_read_data[31:16], axi_read_data[15:0]} : 
        { axi_read_data[15:0], axi_read_data[31:16]};
  else if (state_shift_reg == STATE_16_SHIFT_STORED & !buffer_full)
    shift_reg_16_bit <= {shift_reg_16_bit[15:0], 16'b0};


Testing the setup

Let us test the setup.

We still need to link up the C64_mode pin on our VGA module to one of the push buttons.

Usually this involves adding an extra constraint to our constraint file (e.g. the XDC file).

Luckily we don't need to figure out these constraints from scratch, since the Home website of the Zybo board provides us with a template XDC file.

Scrolling down this template XDC, we eventually find the set of lines we are looking for:

##Buttons
#set_property -dict { PACKAGE_PIN R18   IOSTANDARD LVCMOS33 } [get_ports { btn[0] }]; #IO_L20N_T3_34 Sch=BTN0
#set_property -dict { PACKAGE_PIN P16   IOSTANDARD LVCMOS33 } [get_ports { btn[1] }]; #IO_L24N_T3_34 Sch=BTN1
#set_property -dict { PACKAGE_PIN V16   IOSTANDARD LVCMOS33 } [get_ports { btn[2] }]; #IO_L18P_T2_34 Sch=BTN2
#set_property -dict { PACKAGE_PIN Y16   IOSTANDARD LVCMOS33 } [get_ports { btn[3] }]; #IO_L7P_T1_34 Sch=BTN3


This declares a potential input port we can add to our design called btn.

This port, however, is declared as a 4 bit vector. From this 4 bit vector we only need one bit, meaning that we would need to write some extra Verilog code to split this vector into individual bits.

We could, however, simplify our life by not declaring these set of buttons as a vector constraint, but as individual ports:

##Buttons
#set_property -dict { PACKAGE_PIN R18   IOSTANDARD LVCMOS33 } [get_ports { btn_0 }]; #IO_L20N_T3_34 Sch=BTN0
#set_property -dict { PACKAGE_PIN P16   IOSTANDARD LVCMOS33 } [get_ports { btn_1 }]; #IO_L24N_T3_34 Sch=BTN1
#set_property -dict { PACKAGE_PIN V16   IOSTANDARD LVCMOS33 } [get_ports { btn_2 }]; #IO_L18P_T2_34 Sch=BTN2
#set_property -dict { PACKAGE_PIN Y16   IOSTANDARD LVCMOS33 } [get_ports { btn_3 }]; #IO_L7P_T1_34 Sch=BTN3


We can now just choose the applicable button we want and add it to our block design.

After synthesis, creating the bitstream, and creating a new boot.bin, we are now ready to run a test.

The following video shows the outcome:


We start off with the Linux console. As we press and release one of the push buttons, the C64 screen pops up and go back to the Linux console.

In Summary

In this post we have implemented functionality within our FPGA design in which we can switch between C64 video output and the Linux console.

For now this switching can only be done via a push button on the Zybo board.

In the next post we will be redirecting keystrokes from Linux to our C64 FPGA module.

Till next time!


Thursday, 6 February 2020

Capturing KeyUp/KeyDown events in Linux

Foreword

In the previous post we finally got to a point where we can view a Linux console from a Zybo board on a VGA screen, as well as interacting with it via a USB keyboard.

For all practical reasons, Linux can now now run completely on its own a Zybo board, without the need of been connected to a PC.

Having manged to run Linux on the Zybo board, the next step would be to able for this running instance of Linux to be able to interact with our C64 FPGA module. This interaction will involve the following:


  • Redirecting keystrokes to the C64 FPGA module
  • Switching between C64 video output and the Linux Console
  • Loading .TAP images from the Linux file system and transferring it to the C64 FPGA module
This is quite a lot of functionality and we will definitely not be able tackle it all in one post, but as usual, we will tackle it like eating an elephant: bit by bit, or in our case, post by post :-)

In this particular post will start to tackle the redirection of keystrokes to the C64 FPGA module. This by itself needs two pieces of functionality:

  • Writing a user program in C running under Linux, carefully monitoring when the user pressed a key or released a key.
  • Writing a Kernel Driver driver that receives the key events from the user program mentioned in the previous point, and forwarding it to our C64 FPGA module.
One might wonder why it would be necessary to write a Kernel driver for forwarding the keystrokes to our C64 FPGA module. Why couldn't the mentioned user program take care of this as well?

The big reason for this is is because because interaction between our C64 FPGA module and Linux will happen via specific physical locations in memory space. When working with physical locations in memory it is always better to delegate these actions to a Kernel Driver.

In this post we will only be dealing with creating the user program for capturing the KeyUp/KeyDown events and in a later post we will be developing the associated Kernel Driver.

Watching the keyboard like a Hawk


When one writes an emulator for the Commodore 64, one of the first things one realise is that at any instant in time, you need to exactly know the state of the keyboard: Which keys are currently been held down and the moment one of them is released.

Knowing this kind if information just makes your emulator appear more in sync with your keyboard.

If one will be getting these keyboard changes from the Linux console, as we will be doing with our Zybo board, one will be facing a couple of frustrations. One of these frustrations is that by default you will not receive any key release events, no matter what you try.

This issue has to do with the default mode the Linux console is in, which is called cooked mode.

Let us analyse this problem by first looking at what cooked mode is.

Cooked mode actually provides a lot of functionality in the background when requesting a line of text. In cooked all edits from the user will be performed in the background and the final result will be send when you hit enter.

However, when we want to catch both keyup and keydown events, we need to switch the console from cooked mode to raw mode.

To set the right options to switch to raw mode takes quite a bit of fiddling.

Luckily someone on the Internet did a nice write-up on how to switch to raw mode: 

http://www.gcat.org.uk/tech/?p=70

In this blog post, the following method takes care of setting the console into raw mode:

#include "unistd.h"
#include "linux/kd.h"
#include "termios.h"
#include "fcntl.h"
#include "sys/ioctl.h"

static struct termios tty_attr_old;
static int old_keyboard_mode;

int setupKeyboard()
{
    struct termios tty_attr;
    int flags;

    /* make stdin non-blocking */
    flags = fcntl(0, F_GETFL);
    flags |= O_NONBLOCK;
    fcntl(0, F_SETFL, flags);

    /* save old keyboard mode */
    if (ioctl(0, KDGKBMODE, &old_keyboard_mode) < 0) {
 return 0;
    }

    tcgetattr(0, &tty_attr_old);

    /* turn off buffering, echo and key processing */
    tty_attr = tty_attr_old;
    tty_attr.c_lflag &= ~(ICANON | ECHO | ISIG);
    tty_attr.c_iflag &= ~(ISTRIP | INLCR | ICRNL | IGNCR | IXON | IXOFF);
    tcsetattr(0, TCSANOW, &tty_attr);

    ioctl(0, KDSKBMODE, K_RAW);
    return 1;
}

If one wants to set the console back to cooked mode, the following method will do the trick:

void restoreKeyboard()
{
    tcsetattr(0, TCSAFLUSH, &tty_attr_old);
    ioctl(0, KDSKBMODE, old_keyboard_mode);
}

This is quite a bit of code to get into raw mode and it might be easier just to use the SDL library that makes it much easier to capture keyup and keydown events.

However, to get the SDL library on the Zybo board, one would need to jump through a couple of cross compile hoops.

So, for now the setupKeyboard method will do it for me.

The previous mentioned post also provides a method for reading the keystrokes when we are in raw mode:

void readKeyboard()
{
    char buf[1];
    int res;

    /* read scan code from stdin */
    res = read(0, &buf[0], 1);
    /* keep reading til there's no more*/
    while (res >= 0) {
 switch (buf[0]) {
 case 0x01:
            /* escape was pressed */
            break;
        case 0x81:
            /* escape was released */
            break;
        /* process more scan code possibilities here! */
 }
 res = read(0, &buf[0], 1);
    }
}

Putting everything together

The mentioned Blog post in the previous section didn't provide a main method, so let us quickly create one:

int main(int argc, char * argv[])
{
   //code
  setupKeyboard();
  while(1) {
    usleep(20000);
    readKeyboard();
  }

}

Here we are in an endless loop, waiting 20milliseconds at each cycle before we read the keyboard.

Currently the readKeyboard method doesn't give any visual feedback on the keystrokes it gets. For this one could probably do a printf after each read. This would, however, clutter the display with repeated scancodes at a rate of whatever the keyboard repeat rate is been set at.

It would be so much nicer if we could just print output if the state of a key changes.

Let us achieve this by keeping at hand an array of keys that is currently been held down together with an applicable utility method:

...
int keys[6];
...
int getCodeInList(int code) {
  if (code == 0xff) {
    return -1;
  }
  code = code & 0x7f;

  for (int i = 0; i < 6; i++) {
    if (keys[i] == code)
      return i;
  }
  
  return -1;
}
...

With this code we can have up to 6 keys that are simultaneously been held down. You will also realise that we are masking off the most siginicant bit of the scancode to test. This is because both a press and release will yield the same value in lower 7 bits, but a release will have bit 7 set to one.

Next, let us write a method for when the key pressed, we insert the code into the array:

void doKeyDown(int scanCode) {
  int index = getCodeInList(scanCode);
  if (index != -1)
    return;
  for (int i = 0; i < 6; i++) {
    if (keys[i] == 0) {
      keys[i] = scanCode;
      if (scanCode == 0x1e) {
        printf("A pressed\n");
      } else if (scanCode == 0x1f) {
        printf("S pressed\n");
      }
      break;
    }
  }
}


Here we only print something to the console if the scan code wasn't in the key array.

Similarly, the following method will remove an element from the array:

void doKeyUp(int scanCode) {
  for (int i = 0; i < 6; i++) {
    if (keys[i] == scanCode) {
      keys[i] = 0;
      if (scanCode == 0x1e) {
        printf("A released\n");
      } else if (scanCode == 0x1f) {
        printf("S released\n");
      }
      break;
    }
  }
}


Once again, we only print something if this code were previously in the array.

Next, let us write another method that will call either doKeyDown or doKeyUp, depending on the scanCode given:

void processKey(int scanCode) {
  if (scanCode == 0xff) {
    return;
  }

  if ((scanCode & 0x80)) {
    //do key release
    doKeyUp(scanCode & 0x7f);
  } else {
    //do key down
    doKeyDown(scanCode);
  }
}


Finally, we need to modify the readKeyboard method as follows:

void readKeyboard()
{
    char buf[1];
    int res;

    /* read scan code from stdin */
    res = read(0, &buf[0], 1);
    if(buf[0] == 1) {
      restoreKeyboard();
      exit(0);
    }
    processKey(buf[0]);
    /* keep reading til there's no more*/
    while (res >= 0) {
 res = read(0, &buf[0], 1);
        if (buf[0] == 1) {
          restoreKeyboard();
          exit(0);
        }
        processKey(buf[0]);
    }
}

You can now test the whole program. This program, however, will only work if you use a virtual console. If you do it over an SSH channel or terminal window in an X-Windows session, it will not work.

As you keep pressing and releasing the A ans S keys, you will see statements  like A pressed and A released.

In Summary

In this post we have implemented functionality to capture KeyUp and KeyDown events.

In the next post we will be implementing functionality within our C64 FPGA module for switching between the video output of the C64 module and the Linux console.

Till next time!

Saturday, 1 February 2020

Rendering Linux Console to VGA screen: Part 2

Foreword

In the previous post we managed to compile the Simple Frambuffer driver and got it load on Linux startup on a Zybo board.

We also found that once this framebuffer  driver has loaded, Linux automatically utilises this framebuffer as part of a new console. We confirmed this by inspecting the contents of the framebuffer in memory, finding that Linux rendered the contents of the startup console into it.

In this post we will be developing the FPGA side of the solution that will render the console to a VGA screen.

FPGA code tweaks

If we have a look at our current C64 FPGA module, we see that there already exist code that will render the contents of a framebuffer in memory to a VGA screen.

Throughout the course of this Blog-series I haven't really provided full source code listings for the C64 module as I went along. One can, however, get a big chunk of the code on the following github repo: https://github.com/ovalcode/c64fpga.

The code in previous mentioned Github Repo, doesn't contain the code explained in recent posts, but is enough to render contents from a framebuffer in memory and rendering it to a VGA screen.

In addition, this code will only render on a small portion of a screen big enough to fit the contents of a C64 screen. Preferably we would want to render on the full screen when rendering a Linux console, so we need to make some minor tweaks to the code on this Github Repo.

Let us start by having a look at the files in this Github Repo that are relevant to VGA rendering. All of these files are located in ip/vga_block_c64 and are as follows:

  • GrayCounter.v
  • aFifo.v
  • burst_read_block.v
  • fifo.v
  • sync_dual_port_ram.v
  • vga.v

From this list of files it is only the file vga.v that we need to make changes to. Let us have a look at the extend of the changes we need to make to this file.

First change that we should do is to make the image to display fill the whole screen, which ,in my case, is a screen with resolution 1360x768:

...
aFifo
  #(.DATA_WIDTH(16))
  my_fifo
     //Reading port
    (.Data_out(out_pixel_buffer), 
           .ReadEn_in((vert_pos_next > 0)  & (vert_pos_next < 766) &
                           (horiz_pos_next > 0) & (horiz_pos_next < 1361)),

     .RClk(clk),        
     //Writing port.  
     .Data_in(/*out_pixel*/shift_reg_16_bit[31:16]),  
     .Full_out(buffer_full),
     .WriteEn_in((state_shift_reg == STATE_16_SHIFT_STORED | state_shift_reg == STATE_16_SHIFT_SHIFTED) & !buffer_full),
     .WClk(clk_axi),
  
     .Clear_in(trigger_restart_state == RESTART_STATE_RESTART)
     
     );
...
 assign out_pixel_buffer_final = (vert_pos > 0)  & (vert_pos < 766) &
                                (horiz_pos > 0) & (horiz_pos < 1361)
                                ? out_pixel_buffer : 0;
...

These changes will read and display image data when we are in the full visible area of the screen and will render images correctly with resolution similar to that of my screen.

One problem I had when displaying a picture full screen, was that the first pixel always showd a garbage pixel value, causing the whole image to be displayed incorrectly on the screen.

This garbled pixel was caused by reading from the AXI buffer during the period when this buffer transitions from been empty to been filled with one item.

We can remedy the situation by waiting for a couple of clock cycles when the AXI buffer have been filled with the first item:

...
always @(posedge clk_axi)
begin
  axi_buffer_empty3 <= axi_buffer_empty_temp;
  axi_buffer_empty2 <= axi_buffer_empty3;
  axi_buffer_empty1 <= axi_buffer_empty2;
  axi_buffer_empty <= axi_buffer_empty1;
end
...
burst_read_block my_read_block(
          .clk(clk_axi),
          .reset(reset),
          .restart(trigger_restart_state == RESTART_STATE_RESTART),
          //.write_data(ram[current_address]), 
          .count_in_buf(),
          //output src ready
          //-----------------------------------------
          .ip2bus_mst_addr(ip2bus_mst_addr),
          .ip2bus_mst_length(ip2bus_mst_length),
          .ip2bus_mstrd_d(ip2bus_mstrd_d),
          .ip2bus_inputs(ip2bus_inputs),
          .ip2bus_otputs(ip2bus_otputs),
          .axi_d_out(axi_read_data),
          .empty(axi_buffer_empty_temp),
          .read(read_from_axi),
            );
...
assign read_from_axi = !axi_buffer_empty & !buffer_full & (state_shift_reg == STATE_16_SHIFT_IDLE | state_shift_reg == STATE_16_SHIFT_SHIFTED) ? 1 : 0;
...

Here we see that axi_buffer_empty forms part of the decision when to trigger a read action on the AXI_BUFFER. By delaying this signal with the help of a couple of cascaded flip-flops, we effectively delaying reading from this buffer when it transitions to non-empty.

One final thing we should change is the start address of the frame we read from SDRAM for display on screen.

The read addreses to SDRAM is also generated within burst_read_block.v. If you have a look within this module you will see that it is currently been hardcoded to 0x200000.

From the previous post, you will remember that the start address of the framebuffer we use is at address 0x1fb00000.

So, we can either change this hardcoded value in burst_read_block.v, or you can add a port to this module in which you you provide this address:

burst_read_block my_read_block(
          .clk(clk_axi),
          .reset(reset),          
          .restart(trigger_restart_state == RESTART_STATE_RESTART),           
          .count_in_buf(),
          //output src ready
          //-----------------------------------------
          .ip2bus_mst_addr(ip2bus_mst_addr),
          .ip2bus_mst_length(ip2bus_mst_length),
          .ip2bus_mstrd_d(ip2bus_mstrd_d),
          .ip2bus_inputs(ip2bus_inputs),
          .ip2bus_otputs(ip2bus_otputs),
          .axi_d_out(axi_read_data),
          .empty(axi_buffer_empty_temp),
          .read(read_from_axi),
          .start_address(32'h1fb00000)
            );

One could go one step further where you service this port out of the C64 module, and then add the necessary logic where we could set the address from Linux. We will however not do it in this post.

Loading the FPGA design on Zybo board at startup

With the FPGA design created in the previous section, let us see if we can get it loaded when we boot the Zybo board into Linux.

To start, a bit bitstream will need to be created for the FPGA design and exported to an SDK project.

Next we need to create an FSBL as I explain in a previous post.

With the FSBL created, we need to encapsulate it together with our FPGA bistream, as well the ubootloader into a single file.

We have done something similar in a previous post, as explained here. The resulting boot.bin file, however, didn't contain an FPGA bistream file.

To create a boot.bin file containing an FPGA bitstream, we need to use a boot.bif file that looks as follow:

image : {
        [bootloader]fsbl_vga.elf
        design_1_wrapper.bit
        u-boot.elf
}

Of course, design_1_wrapper.bit is your FPGA bitstream.

As previously, we create the boot.bin file with the following command:

bootgen -image boot.bif -o i boot.bin

Copy the resulting boot.bin back to the SDCard from which your Zybo boards boots from.

The End Result

Let us have a look at the end result.

I have inserted the SDCard back into the Zybo board, hooked it up to a VGA screen and attached a USB keyboard.

I got hold of a 5V power adaptor which attached to the Zybo board via its barrel connector. I have also set the appropriate jumper setting so that the Zybo board draws power from this device.

The following video shows the end result:


In this video I start off by switching on the Zybo board and a couple of seconds later some boot messages appear, after which we are presented with the Linux command prompt.

At the command prompt I clear the screen with the clear command, after which I show directory contents of the /etc directory.

In Summary

In this post we managed to boot the Zybo board completely standalone and watch the console output on an attached VGA monitor.

In the next post we will investigate how to capture key-up and key-down events in Linux from a keyboard.

We will use this information in coming posts so we can interface the Linux running on the Zybo board with our C64 FPGA module, so that Linux can delegate keystrokes to it.

Till next time!

Saturday, 25 January 2020

Rendering Linux Console to VGA screen: Part 1

Foreword

In the previous post we managed to boot Linux from an SDCARD on the Zybo board without any manual intervention.

This is one step closer for our Zybo board in running standalone, but our Zybo board still needs to be attached to a PC in order for us to view the output of the Linux console.

In this post and in the next one, we will see if we can display the Linux console output on a VGA screen that is attached to the Zybo board. Once we have achieved this goal we would be able to say that our Zybo board can truly operate on its own without the help of a PC.

Overview

The following diagram gives an overview of what we want to achieve in this post and the next one:


So, in the end we want to have a module implemented in FPGA that continously read pixel data from SDRAM and output it to a VGA screen.

Such an area in SDRAM is referred to a as a framebuffer in the Linux world and is accompanied by a Framebuffer driver.

We ultimately want the output of the Linux console to be rendered as bitmap images to the framebuffer so that it can be displayed on the VGA screen.

In this post we will be dealing with the Linux part of the equation, which will be developing the Framebuffer driver and see how it can be integrated with the Linux console.

In the next post we will deal with the FPGA part of the equation.

Linux Console interaction with Framebuffer Subsystem

We start off by investigating how the Linux Console interact with the Framebuffer subsystem.

Firstly, let us have a closer look at a very simple framebuffer driver. For this purpose we will be using the simple framebuffer which is available within the Linux source code, located in the path drivers/video/fbdev/simplefb.c.

When a framebuffer device has loaded successfully in Linux, an entry under /dev will be created of the form fbX, where X is 0 for the first framebuffer device.

However, if you look through the source of the file simplefb.c, you will not really find any evidence where the node fbX is created under the /dev node.

A clue to this question is found within the method simplefb_probe at the following method call:

...
ret = register_framebuffer(info);
...

The declaration of this function can be traced to the file drivers/video/fbdev/core/fbmem.c. This function eventually calls the function do_register_framebuffer and this where the entry gets created under /dev:

...
 fb_info->dev = device_create(fb_class, fb_info->device,
         MKDEV(FB_MAJOR, i), NULL, "fb%d", i);
...

So far so good, but how do you get a Linux cosole to use this framebuffer device to render its contents?

To accomplish this, there is some glue logic happening within the file drivers/video/console/fbcon.c. To explain what is going on in this file, there is a very nice text file Documentation/fb/fbcon.txt. The key in this document is load scenario #3:

3. Driver is compiled as a module, fbcon is compiled statically
You get your standard console.  Once the driver is loaded with
'modprobe xxxfb', fbcon automatically takes over the console with
the possible exception of using the fbcon=map:n option.
So, some nice magic is happening in the background. Provided fbcon is compiled statically into the kernel, as soon as you load a framebuffer driver as a module, everything should just work!

Building and Installing the Simple Framebuffer as a module

The Simple Framebuffer driver mentioned in the previous section is a perfect match for as framebuffer driver for the purposes of this post.

So, in this section we will be building this driver as a module, so we can use it later on.

Firstly, please ensure that your environment is setup, as described in this post.

Create an empty directory and copy the following from the Linux source tree to it: drivers/video/fbdev/simplefb.c

Within this new directory, you also need to create file called Makefile, with the following content:

obj-m += simplefb.c

all:
 make -C <path to linux source> M=<path to folder containing simplefb.c> modules

clean:
 make -C <path to linux source> M=<path to folder containing simplefb.c> clean

Substitute the square brackets with the paths that are applicable in your situation.

You can now build the module with the following command:

make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-

After the build process is completed, a new file should have been created called simplefb.ko, in the same folder as the source file.

You will need to copy this file to ramdisk that you are using to boot Linux on the Zybo (Within any home directory will do). There is a number of steps involved for changing the contents of a RAMDisk, and is covered in this resource: https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18842473/Build+and+Modify+a+Rootfs

In order to load the simplefb module, we also need to make some modifications to the device tree. This involves adding the following node to zynq-7000.dtsi:

 framebuffer0: framebuffer@1d385000 {
  compatible = "simple-framebuffer";
  reg = <0x1d385000 (1600 * 1200 * 2)>;
  width = <1600>;
  height = <1200>;
  stride = <(1600 * 2)>;
  format = "r5g6b5";
 };


This node corresponds more or less as describe in the document Documentation/devicetree/bindings/video/simple-framebuffer.txt

With this change you will need to recompile the device tree and copy it to the SD Card that you use to boot the Zybo board into Linux.

Initial attempt to load new Module

With our modified device tree and ramdisk copied to the SDCard, let us boot the Zybo board into Linux.

To load the module, issue the following command:

insmod /home/default/simplefb.ko

This assumes that you have copied this kernel module file to /home/default.

In my initial attempt my kernel module failed to load successfully. I was present with error code -12, which is an out of memory error.

To troubleshoot this issue, I added a number of printk statements (e.g. the kernel's version of printf) to get an idea of where the module actually falls over. Eventually I found the snippet that was causing the issue:

 info->screen_base = ioremap_wc(info->fix.smem_start,
           info->fix.smem_len);

The problem appear to be related to memory management.

In the next section we will have a closer look into memory management with this driver.

Memory management within Simplefb

Let us start by finding out what the function ioremap_wc does.

In effect ioremap_wc is a variant of the ioremap function, which is described here. The following sentence quoted from this resource, summarise the purpose of this function:

A successful call to ioremap() returns a kernel virtual address corresponding to start of the requested physical address range.
This makes kind of sense within the context of a framebuffer driver. When display hardware access SDRAM, it can only work with physical memory addresses. Virtual addresses is simply meaningless to display hardware.

The Linux kernel and User programs, however, only deals with Virtual address space. In the majority of cases the kernel/user program can't easily know what the physical address is for a given virtual address.

For this purpose we need the ioremap_wc function. We have a reserved area in RAM for the Framebuffer, and this function just translate this physical address range into a virtual one so that the Linux kernel can work with it.

Next, let us have a look at how the paramteres to the function ioremap_wc are derived:

...
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
...
info->fix.smem_start = mem->start;
info->fix.smem_len = resource_size(mem);
...
info->screen_base = ioremap_wc(info->fix.smem_start,
           info->fix.smem_len);
...

The values for the resource IORESOURCE_MEM is retrieved from the framebuffer node in the device tree in the following attribute:

 framebuffer0: framebuffer@1d385000 {
...
  reg = <0x1d385000 (1600 * 1200 * 2)>;
...
 };


The first number is the starting address for the framebuffer, followed by the size of the framebuffer.

If we convert these numbers to MegaBytes, the framebuffer starts at around 467MB and is about 3MB in size.

As the Zybo board contains 512MB with the Linux Kernel using most of this, one can clearly see that the memory range of the framebuffer conflict with the memory range of the Linux Kernel.

One can solve this issue by moving the framebuffer to the top of memory and lowering the upper limit of memory that Linux can reserve for its own use.

To move the framebuffer to the top of RAM, we need to change the framebuffer node in the device tree as follows:

 framebuffer0: framebuffer@1d385000 {
...
  reg = <0x1fb00000 (1600 * 1200 * 2)>;
...
 };


This will effectively move the framebuffer to address 507MB.

To set the top of usable Linux memory, we need to supply a parameter to the Linux Kernel when it boots up. We set this as a persistable variable within UBoot, with the following commands at the uboot console:

setenv bootargs "mem=500M"
saveenv

If you now restart the Zybo board, you will find that the same insmod command will load the frambuffer successfully.

Loading simplefb automatically at startup

At this point the loading of the framebuffer is a manual process. It would be nice if we can automatically load this module at startup.

If I do an Internet search on how to autoload a module on startup in Linux, I basically get two suggestions:


  • Adding the module to the file /etc/modules (for use with SysVinit init system)
  • Adding the module to a conf file under /etc/modules-load.d (for use with systemd init system.)
In short, neither of the above methods methods work on the Linux booting on my Zybo board.

To get some insight into the matter, let us have a closer look into how the above two methods are implemented on different Linux distributions.

It turns out that each Linux distribution can only implement one of the methods and is based on the init system it support. An init system is basically the system that Linux invoke after it has started up.

After Linux has started up, it will look for executable called init in the /sbin directory.

In Ubuntu 15 and subsequent versions, for instance, /sbin/init will point to the executable /lib/systemd/systemd. These versions clearly uses the systemd init system, which will invoke scripts under etc/modules-load.d.

Ubuntu versions before Ubuntu 15 utilises the SysVinit system, which will automatically load modules within the file /etc/modules.

It is kind of easier to figure out how systemd invoke scripts under etc/modules-load.d than it is to figure out how SystemVinit loads the modules specified in /etc/modules.

To get an idea how SytemVinit does this, we have to boot into a pre-Ubuntu 15 version. On my PC I am currently running Ubuntu 16.04, so I need to jump through a couple of extra hoops to test this.

I downloaded a Ubuntu 13 iso, after which I used Virtualbox to boot this image. It is just quicker this way, than to first write this image to a USB stick or a CD and then booting from it.

With this ISO bootup within Virtualbox, we are prompted whether we want to install or just try out ubuntu. I opted for the quick option, try Ubuntu.

With UBuntu 13.04 booted and having opened up a Terminal Window, we ask ourselves: what should we be looking for? Doing some further reading on the Internet, I found that we should look out for a file called /etc/init/kmod.conf.

This file indeed exists on a Ubuntu 13.04 installation and opening it up reveals something very interesting:


We have found where the module autoloading from /etc/modules happen!

Let us now check if we can find something similar in our Linux installation on the Zybo board.

Firstly, let us check to what /sbin/init is mapped to. This is actually something a bit different than we expected:

lrwxrwxrwx    1 root     root            14 Nov 27  2012 init -> ../bin/busybox

On the other hand, however, this link to init is something you will find on most embedded systems.

Busybox is very light, but sacrifice some functionality.

The Busybox init utility read the /etc/inittab file that explains what to do at various events.

The Linux installation on the Zybo board, have a inittab file that looks as follows:

::sysinit:/etc/init.d/rcS

# /bin/ash
#
# Start an askfirst shell on the serial ports
# and the tty0 for when the video is being used

ttyPS0::respawn:-/bin/ash
tty0::respawn:-/bin/ash

# What to do when restarting the init process

::restart:/sbin/init

# What to do before rebooting

::shutdown:/bin/umount -a -r


From this snippet we see that when Linux starts up the script /etc/init.d/rcS will get executed.

The quickest way get our module to load at startup, is to add it to this rcS script.  The resulting rcS script will something like the following:

#!/bin/sh

echo "Starting rcS..."
insmod /lib/modules/3.6.0/simplefb.ko
echo "++ Mounting filesystem"
mkdir -p /dev/pts
mkdir -p /dev/i2c
mount -a
...

This is enough configuration for our kernel module to load at startup.

Testing results

With our module that loads automatically at startup, it would be interesting to see if the Linux console does in fact write to the framebuffer.

A quick way to check this is to use the XDB console to dump memory from the framebuffer region to a file.

I gave that a try and got something sensible back:


In this exercise a have also attached a USB keyboard and blindly started typing, which in this case was to change into the directory /etc and listing contents.

So, we know our Framebuffer works and a USB keyboard also works out of the box!

In Summary

In this post we integerated the simpleframebuffer into the Linux distrubition running on the Zybo board.

We confirmed that the Linux console indeed writes to the Framebuffer.

In the next post we will be developing the FPGA part of the design that will render the contents of the framebuffer to the VGA screen.

Till next time!