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!

Friday, 10 January 2020

Unattended Linux Booting from SD Card

Foreword

In the previous post we managed to boot Linux on the Zybo board.

This was quite a manual orchestrated process, having to issue a number of XMD commands for loading the different parts for the Linux Kernel and then starting Linux.

In this post we will attempt to automate the Linux process by letting it boot unattended from a SD Card.

Booting U-Boot from SDCard

As the first step in the process, let us see what is involved in booting UBoot from the SD Card.

Firstly, you need to set the boot mode jumpers to SD, as follows:

With this setting the Zynq will look for a file called boot.bin on an active FAT32 partition, on an SDCard on bootup.

The file boot.bin should contain the First Stage Bootloader (FSBL) as well as the UBoot binary. This file is generated by using the bootgen utility which is present in the bin folder of the Xilinx SDK.

In order to the use the bootgen utility, one first need to create a boot.bif, which in our case will look as follows:

image : {
        [bootloader]fsbl_test.elf
        u-boot.elf
}

So, with the boot.bif file created, and both fsbl_test.elf and u-boot.elf present on the file path, we invoke bootgen as follows:

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

This will generate the file boot.bin. At this point I also would like to mention that for this exercise, using the FSBL and UBoot binary we created in the previous two posts will work just fine.

Next, we need to copy this boot.bin file to an SDCard. Preferably this file should be copied into an active FAT32 partition.

Now insert this SDCard into the SD Card slot on the Zybo board and power it up. As in the previous posts, we need to open a screen session to the Zybo board so we can see what is going on.

If all went well, you should see similar output within the screen session as I described in the previous two posts. If one missed the opportunity to abort the auto boot process, the UBoot will attempt to boot from other devices, but will eventually present you with the UBoot prompt.

Booting Linux from the SDCARD

In the previous post we booted in to Linux by issuing a couple of XMD commands for loading the Linux components into memory, followed by a bootm command in UBoot for launching Linux.

Let us see if we can follow a similar approach for booting Linux from the SD Card.

To load the related Linux files into memory, we can just include it within the boot.bin file. To create such a file with bootgen, we can use the following boot.bif file:

image : {
        [bootloader]fsbl_test.elf
        u-boot.elf
        [load=0x2a00000]zynq-zybo.dtb
        [load=0x2000000]uramdisk.image.gz
        [load=0x3000000]uImage.bin
}

Copy the the resulting boot.bin file again to the SD Card. When booting from SD Card, the FSBL will now load zynq-zybo.dtb, uramdisk.image.gz and uImage.bin into the requested addresses.

Once booted into the U-Boot prompt, you can start Linux again with the following command:

bootm 0x3000000 0x2000000 0x2a00000

Important that these steps will only work if you aborted the initial autoboot sequence. If you wait for the uboot prompt after the autoboot sequence, the above command will simply not work since the memory into which the Linux Binaries was loaded by the FSBL would have been overwritten.

At this point we are at a kind of a catch 22. The FSBL will happily load all the components for Linux into memory, but once we approach autoboot, where we want to be for unattended Linux Booting, these contents will have been overwritten.

It is clear that in order to autoboot Linux, we will need to let UBoot fetch the relevant Linux Components from the SDCard.

Autobooting Linux

As mentioned at the end of the previous section, one of the challenges for autobooting Linux from the SD Card, is to get the relevant Linux components into memory, since the FSBL loading attempt gets overwritten soon after the autoboot process started.

Luckily U-Boot provides a command called fatload that will load any file from a FAT32 partition into memory at an address of your choice. The following ssequence of commands will load all the Linux components into memory:

fatload mmc 0:1 0x2a00000 zynq-zybo.dtb
fatload mmc 0:1 0x2000000 uramdisk.image.gz 
fatload mmc 0:1 0x3000000 uImage.bin

Important here is that above mentioned files should be present as separate files on the same level as boot.bin. Although we already wrapped these files into boot.bin, fatload simply wouldn't be able to retrieve it from boot.bin.

After the three fatload commands above, you start booting linux with a bootm.

We just overcame another hurdle, but the question still remains: How do one autoboot Linux?

U-Boot provides an answer to this question by means of persistent environment variables. One of these environment variables is bootcmd.

With bootcmd you can basically specify a script that needs to be executed when autobooting.

At a U-Boot prompt enter the following:

setenv bootcmd "fatload mmc 0:1 0x2a00000 zynq-zybo.dtb; 
                fatload mmc 0:1 0x2000000 uramdisk.image.gz; 
                fatload mmc 0:1 0x3000000 uImage.bin; 
                bootm 0x3000000 0x2000000 0x2A00000"

saveenv



I have split the setenv command over multiple lines for better readability. However, this command should be entered in one line.

The saveenv command saves the environment variables to persistent storage, which by default on the Zybo Board will be the QSPI. In the next section we will change this storage area to a file on the SD Card.

If you now restart the Zybo Board, the autoboot sequence should take you into Linux.

Saving the environment on the SD Card

In the previous section we mentioned that we store the environment variables for UBoot on the QSPI flash located on the Zybo board.

There is probably nothing wrong storing the env variables on QPSI. However, it make sense to keep everything together on the SD Card, so if we have different images on different SD Cards, we can just swap out the SD Card with the need to worry about changing potential settings within the QSPI.

The path to the env persistent storage is hardcoded within the UBoot binary. To change it you will need to make some config changes to zynq_zybo_defconfig and rebuilt Uboot.

Looking for the correct settings within the config file can be quite a cumbersome task. To make the task easier, you can make use of make menuconfig, a similar utility available when you build the Linux Kernel. Please note, however, you will need to have Ncurses installed on your desktop Linux to run make menuconfig.

When you fire up make menuconfig, it is important that you load the config for the Zybo board. So, use the Right arrow key, move to the Load option and hit enter. When prompted to enter a file name enter configs/zynq_zybo_defconfig and hit <enter>.

Back in the Category menu, we are interested in the Environment section, so scroll down to this section and hit <enter> once again. We can clearly see i this section that SPI is selected for environment storage:


So, unselect SPI flash and select Fat filesystem:


We, are almost done. We need to scroll down and change a couple of more settings:


Here we specify that the FAT partition is present on a MMC device (e.g. SD Card), device number zero and partition one.

The file containing the settings will be called uboot.env.

Save the settings, rebuild uboot, repackage within boot.bin and copy again to the SD Card.

We haven't created the file uboot.env yet, so when we reboot the Zybo board with the SD Card we will eventually be presented with the Uboot prompt.

Follow the steps from the previous section again for setting bootcmd and save it.

When you now reboot, you will see that once again we automatically boot into Linux.

In Summary

In this post we managed to boot U-Boot and Linux from an SDCard.

The first boot attempt from the SD Card we performed a couple of manual steps, which were similar as described in the previous post.

We ended off the post by automating the boot process by defining a bootcmd environment variable, which is basically a script running the necessary steps to boot into Linux.

In the next post we will start playing with Linux Device Drivers. The eventual goal for this is to be able, eventually to display the Linux console on a VGA screen from the Zybo Board.

Till next time!

Saturday, 4 January 2020

Creating and Booting the Linux Kernel

Foreword

In the previous post we managed to build and run UBoot on the Zybo board.

In this post we will build and run Linux on the Zybo board.

Building the Linux Kernel

Before we can start building the Linux Kernel, we need to get the source from Github:

git clone https://github.com/Xilinx/linux-xlnx.git

Now, change into the cloned directory.

Before we start the building process, we need to set a couple of environment variables for our Terminal window:

export PATH=~/u-boot-xlnx/tools:/opt/cross/bin:$PATH

export CROSS_COMPILE=arm-linux-gnueabihf-

These set of environment variables will look familiar from the previous post. However, you will see as an extra, we are adding the tools folder of UBoot source.

We include this tools folder because it contains a utility called mkimage. The Linux Build process will use this utility to wrap the resulting Linux Image into something that the UBoot loader can understand.

Next let us configure the Linux Build process for a Zynq compatible device:

make ARCH=arm xilinx_zynq_defconfig

We are now ready to start off the building process:

make ARCH=arm UIMAGE_LOADADDR=0x8000 uImage

This will take a while to complete. Once finished you will see a file created called uImage located in arch/arm/boot. This is the image we will be using when booting Linux.

From the parameters we used to invoke the build, one can see that the start address of the image is at 0x8000.

I consulted the following resource regarding the building of the Linux Kernel: https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18842481/Build+kernel

Building The Device Tree Blob

When building Linux for any ARM based device, you will sooner or later be faced to create a Device Tree Blob.

The concept of device trees was introduced on ARM based devices, because you get ARM based Soc's in so many different configurations. Device trees just simplify the configuration of these devices.

The following resource explain how to create a Device Tree Blob for Xilinx devices: https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18842279/Build+Device+Tree+Blob

At first glance, this resource looks like quite intimidating information.However, things gets simpler quite drastically when you get to the section Alternative: For ARM only. It all boils down to the fact you only need a one-liner to create a Device Tree Blob for a Zynq:

make ARCH=arm zynq-zybo.dtb

This command will create the file zynq-zybo.dtb which you will find in the location arch/arm/boot/dts.

Getting hold of a RamDisk Image

We are almost ready to boot into Linux. However, what we still need is a RamDisk Image. The following resource explains how to create a RAMDisk:

https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18842473/Build+and+Modify+a+Rootfs

There is quite a number of steps involved to create a RAMDisk. Luckily the above mentioned resource also provides a couple of links to prebuild RAMDisk Images that you can download.

Booting Linux

It is time to boot our created Linux Kernel.

Once again we start in the XSDB console to load/run/stop the FSBL onto the Zybo board to initialise the DDR.

Next, we need to load the Device Tree Blob, RamDisk and uImage into RAM:

xmd% dow -data ~/linux-xlnx/arch/arm/boot/dts/zynq-zybo.dtb 0x2a00000
xmd% dow -data ~/Downloads/uramdisk.image.gz 0x2000000
xmd% dow -data ~/linux-xlnx/arch/arm/boot/uImage 0x3000000


Next, we should load Uboot into memory:

dow ~/u-boot-xlnx/u-boot.elf

Once again, we issue the con command on the XSDB console to start UBoot. Remember to hit a key on the screen session just before UBoot starts it attempts to boot from other devices.

At this point we have Uboot running, the Device Tree Blob loaded, the RamDisk loaded and uImage loaded. We just need to tell Uboot to boot linux:

bootm 0x3000000 0x2000000 0x2a00000

The parameters we supply to the bootm command is the addresses of the key elements we loaded into memory earlier.

This should boot Linux and the screen should look as follows:


At this point the RAM Disk is mounted as the Root filesystem. In other words, all changes you make in the filesystem will be lost once you switch off the Zybo board.

In Summary

In this post we managed to boot Linux on the Zybo Board.

In the next post we will attempt to boot Linux from an SDCard so that it is not necessary to follow all these manual steps explained in this post.

Till next time!

Friday, 27 December 2019

Creating and Running UBoot

Foreword

In the previous post we started our journey on getting Linux to run on the Zybo board.

We managed to create and run a First Stage Bootloader (FSBL).

In this post we will be building and running UBoot.

UBoot is an intermediate stage bootloader for booting Linux on ARM based devices.

Creating an ARM Cross Compiler

To compile UBoot/Linux for the Zynq, one needs a cross compiler for compiling source into ARM machine code.

In Linux distros like Ubuntu provides these cross compilers as packages that you can download and install. These packages, however, is sometimes a couple of versions behind and might not be sufficient for building ARM based packages.

In my own experience I have found it better to build the Cross Compiler toolchain yourself. Here is a very handy resource for creating your own toolchain:

https://preshing.com/20141119/how-to-build-a-gcc-cross-compiler/

These set of instructions explains how to build GCC version 4.9.2. This version of GCC is a bit outdated for our purposes. So, for some of the libraries we need to download, we need to get newer ones, which are as follows:

  • GCC version 7.5.0
  • Binutils version 2.25
  • isl version 0.18
When following the instructions from the above link, there are some instructions that need to be tweaked so we can compile for the ARM architecture. These are as follows:

  • --target=arm-linux-gnueabihf as well as --host=arm-linux-gnueabihf
  • ARCH=arm
  • The folder under /opt/cross should be arm-linux-gnueabihf
  • Also, in step 4 (e.g. Standard C Library Headers and Startup Files) the gcc cross compiler is invoked. The filename for this should be arm-linux-gnueabihf-gcc.
Following the steps from above Internet resource, together with the suggested amendments for the ARM architecture, will yield the Cross compiler within the folder /opt/cross.

If you want to use this Cross compiler, you need to ensure that it within the system path, which you can define as follows:

export PATH=/opt/cross/bin:$PATH

Building UBoot

to Build UBoot, we need to follow the instructions on this resource: https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18841973/Build+U-Boot

Let us follow these instructions step by step. Firstly, we need to get the source code from Github, via a terminal window:

git clone http://github.com/Xilinx/u-boot-xlnx.git

This is basically the uboot source with some Xilinx customisations.

Now, change into the cloned directly.

Next we need to set some environment variables within our terminal session:

export CROSS_COMPILE=arm-linux-gnueabihf-

export ARCH=arm

We can now build the u-boot image with the following:

make distclean
make zynq_zybo_defconfig
make

After the build process, there should be an executable called u-boot.elf.

Testing U-boot

Let us test the U-boot executable.

As in the previous post, with the Zybo board connected to the PC and powered up, start a screen session in a terminal window:

sudo screen /dev/ttyUSB1 115200

In another terminal window, start an XSDB session. Also, as in the previous post, issue XSDB commands for starting the FSBL and then stopping it. At this point, the DDR should have been initialised and we should be able to load the u-boot image into it:

dow ~/u-boot-xlnx/u-boot.elf

When you try these steps when your pc/laptop came out of hibernation, you might be presented with the following error:

Memory write error at 0x4000000. Cannot access DDR: the controller is held in reset

If this is the case, just switch the Zybo board off for a couple of seconds and then on again. After switching the Zybo board on, just re-establish a screen session and following the steps of downloading/starting/stopping the FSBL, and then downloading the U-Boot image.

Issuing the command con, will start the U-boot image. If everything went well, the output on your screen session should like the following:


If one just missed the opportunity to stop the autoboot, you will see some output indicating that it is trying to boot images from various devices.

In Summary

In this post we managed to build and run U-Boot. In the next post we will be building Linux and running it on the Zybo board.

Till next time!

Tuesday, 24 December 2019

The boot process of the ZYNQ

Foreword

In the previous post we have scaled up the video frames produced by the VIC-II module so that it can fill the whole screen.

At this point our C64 module has enough functionality implemented so that we can play a game on it. However, everytime we want to play a game with it, the Zybo board need to be attached to a PC, and we need to issue a couple of commands within the Xilinx SDK.

Wouldn't it be nice if the Zybo could just start up on its own and you don't need to connect it to a PC?

This will be the purpose of the next series of posts: To to be able to boot our C64 system on the Zybo board without any hand holding.

To approach this goal, we will work towards been able to run Linux on the Zybo Board. If we are within the Linux ecosystem on the Zybo board, we just have access to so many device drivers, making it easy for us, for instance, to load a tape image from a Micro SDCard.

In this post we will start by getting an overview on how the boot process works on the Zybo Board.

Also, in this in this post I will assume the Zybo board will be connected to a PC running Ubuntu OS or similar.

The Boot Process

When the Zynq is powered up and ready to start the process of booting into an OS, it is faced with a challenging initial condition: The DRAM is disabled.

To get past this hurdle, the Zynq contains onchip memory (OCM). The OCM contains 128KB BootROM code and 256KB SRAM.

The BootROM contains just enough code to load a boot image from a hand full of devices, like a SDCard and QSPI, into SRAM and start execution of it.

256KB of SRAM for loading a boot image is not a lot of memory, so on Zynq devices, the boot process is split into a number of stages:

  • Stage 0: The BootROM starts executing and loads the First stage loader into SRAM
  • Stage 1: The First stage bootloader starts to execute from SRAM. It is responsible for enabling DRAM. Amongst other things it also initialises a number of crucial on chip peripherals as well as a number of clocks on the Zynq. Also the bitstream is read into memory as well as the user application
  • Stage 2: At this stage the DRAM is fully operational and control is handed to the user application. In a Linux ecosystem, we will start with running UBoot, which will eventually load and start a Linux Image.
This is quite a cumbersome process. However, I can understand why this process is necessary. To initialise DRAM is quite a complicated process and one can easily introduce a bug when writing the software routine for this process. Should such a bug exist in BootROM, this could render the Zynq chip useless. Moving DRAM initialisation to software that one retrieves from a SDCard greatly reduces this risk.

Of course SRAM is quite an expensive resource. For this reason the Zynq only contains 256KB of it, and we need three stages of booting instead of just 2.

In this post we will focus on the first stage bootloader (FSBL) and in future posts the Stage 2 bootloader.

Creating a First Stage Bootloader

Let us start by having an overview of the process for creating an FSBL. There is a very nice block diagram of the process here:



The Xilinx SDK get a Hardware handoff file from Vivado and generate the FSBL. On the diagram the FSBL gets aggregated with other files like U-boot and uImage to produce a file called BOOT.BIN.

In this post, however, I will show a method where we will directly invoke the FSBL without having to generate a BOOT.BIN first.

Now, in Vivado let us create a Block Design containing only a Zynq block. Also, let us perform all the suggested wiring:


This block diagram looks very simplistic. However, when you open this block, you will see that it contains very important settings, like DDR settings:

It is these type of settings that Vivado will handoff to the Xilinx SDK and will be incorporated into the FSBL.

With this block design created, do a Synthesise, generate bitstream and an export.

Then, from Vivado launch the Xilinx SDK.

In the File menu, select new application project and provide a name for the application project.

Click next and in the left panel select Zynq FSBL:


When you click finish, a new project will be created and visible in the project panel.

A quick way to test our FSBL, would be to write a message to the console when it runs.

To do this, expand the src folder and open the file main.c. Scroll down to the main method. Just after the call to RegisterHandlers, add the following line:

 xil_printf("Hello World");

Click the down arrow next to the hammer icon and select the Release configuration. Now build the project.

Now, within the sdk folder, within your project folder and under the folder Release, there should be a *.elf file. This is the First stage Bootloader that we will use in the next section.

Testing the First Stage Bootloader

Let us test the FSBL we have created in the previous section.

Ensure the Zybo board is configured to boot into JTAG mode. Hook it up to a PC and power it up.

For his exercise we will need two terminal windows running on the Ubuntu PC. On the first terminal, issue the following command:

sudo screen /dev/ttyUSB1 115200

This is the terminal session where we expect our Hello World message to appear when our FSBL run. The device file might be different in your case or might even be /dev/ttyUSB2.

In the other terminal change to the bin folder of your Xilinx SDK installation and issue the following command:

./xsdb

You will be presented with the XSDB console. At this console, issue the following command:

connect

If you now issue the targets command, you will see a list of targets you can connect to:

xsdb% targets                                                                   
  1  APU
     2  ARM Cortex-A9 MPCore #0 (Running)
     3  ARM Cortex-A9 MPCore #1 (Running)
  4  xc7z010


Our goal is to run our FSBL on the first core, so let us select this core and stop it:

xsdb% target 2                                                                  
xsdb% stop                                                                      
Info: ARM Cortex-A9 MPCore #0 (target 2) Stopped at 0xffffff28 (Suspended)
xsdb% targets                                                                   
  1  APU
     2* ARM Cortex-A9 MPCore #0 (Suspended)
     3  ARM Cortex-A9 MPCore #1 (Running)
  4  xc7z010


We will now load the FSBL into the Zybo board from the location where it was generated. In my case the console output will look as follows:

xsdb% dow ~/fsbl-test/fsbl-test.sdk/fsbl_test/Release/fsbl_test.elf             
Downloading Program -- /home/johan/fsbl-test/fsbl-test.sdk/fsbl_test/Release/fsbl_test.elf
 section, .text: 0x00000000 - 0x0000d38b
 section, .handoff: 0x0000d38c - 0x0000d3d7
 section, .init: 0x0000d3d8 - 0x0000d3ef
 section, .fini: 0x0000d3f0 - 0x0000d407
 section, .rodata: 0x0000d408 - 0x0000d75f
 section, .data: 0x0000d760 - 0x0001054f
 section, .eh_frame: 0x00010550 - 0x00010553
 section, .mmu_tbl: 0x00014000 - 0x00017fff
 section, .init_array: 0x00018000 - 0x00018003
 section, .fini_array: 0x00018004 - 0x00018007
 section, .rsa_ac: 0x00018008 - 0x0001903f
 section, .bss: 0x00019040 - 0x0001ae6f
 section, .heap: 0x0001ae70 - 0x0001ce6f
 section, .stack: 0xffff0000 - 0xffffd3ff
100%    0MB   0.5MB/s  00:00                                                    
Setting PC to Program Start Address 0x00000000
Successfully downloaded /home/johan/fsbl-test/fsbl-test.sdk/fsbl_test/Release/fsbl_test.elf


The FSBL will be loaded at location starting at address 0, which is the area where SRAM resides of OCM.

We can now resume execution of core #0 with the command con. We can now see the output on the other Terminal Window:


Our first stage bootloader worked!

In Summary

In this post we have created and tested a First Stage Bootloader.

In the next post we will continue our journey in getting Linux to run on the Zybo Board.

In particular, we will be getting UBoot to run on the Zybo Board. UBoot is a component you will find in many ARM based systems that assist in booting Linux.

Till next time!