Cogs and Levers A blog full of technical stuff

Framebuffer Drawing in Linux

Introduction

Some of my favourite graphics programming is done simply with a framebuffer pointer. The simplicity of accessing pixels directly can be alot of fun. In today’s article, I’ll walk through a couple of different ways that you can achieve this inside of Linux.

/dev/fb*

Probably the easiest way to get started with writing to the framebuffer is to start working directly with the /dev/fb0 device.

cat /dev/urandom > /dev/fb0

If your system is anything like mine, this results in zsh: permission denied: /dev/fb0. To get around this, add yourself to the “video” group.

sudo adduser $USER video

You can now fill your screen with garbage by sending all of those bytes from /dev/urandom.

This works, but it’s not the best way to get it done.

Xlib

Next up we’ll try again using Xlib. This isn’t exactly what we’re after, but I’ve included this one for completeness.

#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    Display *display;
    Window window;
    XEvent event;
    int screen;

    // Open connection to X server
    display = XOpenDisplay(NULL);
    if (display == NULL) {
        fprintf(stderr, "Unable to open X display\n");
        exit(1);
    }

    screen = DefaultScreen(display);

    // Create a window
    window = XCreateSimpleWindow(
      display, 
      RootWindow(display, screen), 
      10, 10, 
      800, 600, 
      1,
      BlackPixel(display, screen), 
      WhitePixel(display, screen)
    );

    // Select kind of events we are interested in
    XSelectInput(display, window, ExposureMask | KeyPressMask);

    // Map (show) the window
    XMapWindow(display, window);

    // Create a simple graphics context
    GC gc = XCreateGC(display, window, 0, NULL);
    XSetForeground(display, gc, BlackPixel(display, screen));

    // Allocate a buffer for drawing
    XImage *image = XCreateImage(
      display, 
      DefaultVisual(display, screen), DefaultDepth(display, screen), 
      ZPixmap, 
      0, 
      NULL, 
      800, 
      600, 
      32, 
      0
    );

    image->data = malloc(image->bytes_per_line * image->height);

    // Main event loop
    while (1) {
        XNextEvent(display, &event);
        if (event.type == Expose) {
            // Draw something to the buffer
            for (int y = 0; y < 600; y++) {
                for (int x = 0; x < 800; x++) {
                    unsigned long pixel = ((x ^ y) & 1) ? 0xFFFFFF : 0x000000; // Simple checker pattern
                    XPutPixel(image, x, y, pixel);
                }
            }

            // Copy buffer to window
            XPutImage(display, window, gc, image, 0, 0, 0, 0, 800, 600);
        }
        if (event.type == KeyPress)
            break;
    }

    // Cleanup
    XDestroyImage(image);
    XFreeGC(display, gc);
    XDestroyWindow(display, window);
    XCloseDisplay(display);

    return 0;
}

We are performing double-buffering here, but it’s only when the event type of Expose comes through. This can be useful, but not great if you want to do some animation.

In order to compile this particular example, you need to make sure that you have libx11-dev installed.

sudo apt-get install libx11-dev
gcc -o xlib_demo xlib_demo.c -lX11

SDL

For our last example here, we’ll use SDL to achieve pixel access to a backbuffer (or framebuffer) by creating an image. In this example we are continouosly flipping the back image onto video memory which allows for smooth animation.

#include <SDL2/SDL.h>
#include <stdio.h>

int main(int argc, char* argv[]) {
    if (SDL_Init(SDL_INIT_VIDEO) < 0) {
        fprintf(stderr, "Could not initialize SDL: %s\n", SDL_GetError());
        return 1;
    }

    SDL_Window *window = SDL_CreateWindow(
      "SDL Demo", 
      SDL_WINDOWPOS_UNDEFINED, 
      SDL_WINDOWPOS_UNDEFINED, 
      800, 
      600, 
      SDL_WINDOW_SHOWN
    );

    if (window == NULL) {
        fprintf(stderr, "Could not create window: %s\n", SDL_GetError());
        SDL_Quit();
        return 1;
    }

    SDL_Renderer *renderer = SDL_CreateRenderer(
      window, 
      -1, 
      SDL_RENDERER_ACCELERATED
    );

    SDL_Texture *texture = SDL_CreateTexture(
      renderer, 
      SDL_PIXELFORMAT_ARGB8888, 
      SDL_TEXTUREACCESS_STREAMING, 
      800, 
      600
    );

    Uint32 *pixels = malloc(800 * 600 * sizeof(Uint32));

    // Main loop
    int running = 1;
    while (running) {
        SDL_Event event;
        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT) {
                running = 0;
            }
        }

        // Draw something to the buffer
        for (int y = 0; y < 600; y++) {
            for (int x = 0; x < 800; x++) {
                pixels[y * 800 + x] = ((x ^ y) & 1) ? 0xFFFFFFFF : 0xFF000000; // Simple checker pattern
            }
        }

        SDL_UpdateTexture(texture, NULL, pixels, 800 * sizeof(Uint32));
        SDL_RenderClear(renderer);
        SDL_RenderCopy(renderer, texture, NULL, NULL);
        SDL_RenderPresent(renderer);

        SDL_Delay(16); // ~60 FPS
    }

    free(pixels);
    SDL_DestroyTexture(texture);
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();

    return 0;
}

Before being able to compile and run this, you need to make sure you have SDL installed on your system.

sudo apt-get install libsdl2-dev
gcc -o sdl_demo sdl_demo.c -lSDL2

That’s been a few different framebuffer options, all depending on your appetite for dependencies or ease of programming.

Getting Started with GNUstep

Introduction

GNUstep is a development framework for writing GUI applications. It aims to follow Apple’s Cocoa API but allows you to write applications for more platforms than just OSX.

In today’s article we’ll setup a local environment for writing GNUstep programs, and we’ll also write and compile a simple “Hello, world” application to make sure everything is setup.

Brief History

GNUstep is an open-source implementation of the OpenStep specification, which originated from NeXT, a company founded by Steve Jobs after he left Apple in 1985. NeXT developed the NeXTSTEP operating system, which introduced an advanced object-oriented framework for software development. In 1993, NeXT partnered with Sun Microsystems to create the OpenStep standard, which aimed to make NeXT’s frameworks available on other platforms.

When Apple acquired NeXT in 1996, the technology from NeXTSTEP and OpenStep formed the foundation of Apple’s new operating system, Mac OS X. Apple’s Cocoa framework, a core part of macOS, is directly derived from OpenStep. GNUstep, initiated in the early 1990s, aims to provide a free and portable version of the OpenStep API, allowing developers to create cross-platform applications with a foundation rooted in the same principles that underpin macOS development.

So, this still leaves us with GNUstep to get up and running.

Developer environment

First up, we need to install all of the dependencies in our developer environment. I’m using Debian so all of my package management will be specific to that distribution. All of these packages will be available on all distributions though.

sudo apt-get install build-essential gnustep gnustep-devel

Once this is installed, we can move on to writing some code.

“Hello World” alert

The following program is very basic. It’ll show an alert to screen, and then exit after the user has dismissed the alert.

// hello.m

#include <AppKit/AppKit.h>

@interface AppDelegate : NSObject<NSApplicationDelegate>

@end

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)notification {
  NSAlert *alert = [[[NSAlert alloc] init] autorelease];
  [alert setMessageText:@"Hello, World!"];
  [alert runModal];
  [NSApp terminate:nil];
}

@end

int main(int argc, char *argv[]) {
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  NSApplication *app = [NSApplication sharedApplication];

  AppDelegate *appDelegate = [[[AppDelegate alloc] init] autorelease];
  [app setDelegate:appDelegate];

  [app run];
  [pool drain];

  return 0;
}

Walkthrough

First of all, we include AppKit/AppKit.h so that we get access to the programming API. We then define our own AppDelegate so we capture the applicationDidFinishLaunching slot:

#include <AppKit/AppKit.h>

@interface AppDelegate : NSObject<NSApplicationDelegate>

@end

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)notification {
  NSAlert *alert = [[[NSAlert alloc] init] autorelease];
  [alert setMessageText:@"Hello, World!"];
  [alert runModal];
  [NSApp terminate:nil];
}

@end

The handler sets up the alert to show to screen, runs this modal as “the program” with runModal, and then we finish with `terminate’.

Next is the main program itself.

We start with an NSAutoreleasePool to give our program some automatic memory management. This is cleaned up at the end with a call to [pool drain].

The app variable is setup as an NSApplication which allows us to instantiate and attach our AppDelegate via the [app setDelegate:appDelegate]; call.

int main(int argc, char *argv[]) {
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  NSApplication *app = [NSApplication sharedApplication];

  AppDelegate *appDelegate = [[[AppDelegate alloc] init] autorelease];
  [app setDelegate:appDelegate];

  [app run];
  [pool drain];

  return 0;
}

Building

Now that we have our code written into hello.m, we can compile and link. There are some compile and link time libraries required to get this running. For this, we’ll use gnustep-config to do all of the heavy lifting for us.

gcc `gnustep-config --objc-flags` -o hello hello.m `gnustep-config --gui-libs`

If everything has gone to plan, you should be left with an executable called hello.

GNUstep Hello World

Happy GNUstep’ing!

Simple usages of rsync

Introduction

rsync is an excellent tool to have as your disposal for working with file data.

In today’s article, I’ll go through some usages.

Progress Copy

To securely and efficiently transfer data to (or from) a remote server while providing you progress updates you can use the following:

rsync -avzh --progress --stats user@server:/path/to/file output_name
  • -a puts rsync into archive mode, preserving your file and link structures
  • -v pumps up the logging output to give you verbose information
  • -z will compress the file data as it’s sent over the network
  • -h makes the output human readable
  • --progress provides a progress bar to be displayed, which tracks how far through we are
  • --stats provides a full statistical run down after the process has completed

Simple Backups with rsync

Introduction

Rsync is a powerful tool often utilized for its proficiency in file synchronization and transfer. Widely adopted in various computing environments, rsync excels in efficiently mirroring data across multiple platforms and minimizing data transfer by sending only changes in files. This utility is not only pivotal for maintaining backups but also ensures that the copies are consistent and up-to-date.

Today’s article is designed to guide you through the steps of setting up a basic yet effective backup system using rsync. By the end of this guide, you’ll have the knowledge to implement a reliable backup solution that can be customized to fit your specific needs.

Daily

The daily backup captures all of the changes for the day, giving you a backup with the fastest cadence.

#!/bin/bash

# daily-backup.sh
#
# Basic copy script to perform daily backups of the home dir

USER=$(whoami)
HOST=$(hostname)

rsync -aAX \
    --delete \
    --rsync-path="mkdir -p /path/to/backups/$HOST/daily/ && rsync" \
    --exclude-from=/home/$USER/.local/bin/rsync-homedir-excludes.txt \
    /home/$USER/ backup-user@backup-server:/path/to/backups/$HOST/daily/ 

Using whoami and hostname we can generalise this script so that you can reuse it between all of your machines.

The rsync-homedir-excludes.txt file allows you to define files that you’re not interested in backing up.

The switches that we’re sending into rsync are quite significant:

  • -a puts rsync into archive mode, preserving file structures, and links
  • -A preserves the ACLs so all of our permissions are preserved
  • -X any extra attributes that are stored by your file system will be preserved
  • --delete will delete files in the destination that are no longer present in the source, making the backup a true mirror

Weekly

The weekly backup is very similar to the daily backup. It’ll target different folders, and will have a different execution cadence.

#!/bin/bash

# weekly-backup.sh
#
# Basic copy script to perform weekly backups of the home dir

USER=$(whoami)
HOST=$(hostname)

rsync -aAX \
    --delete \
    --rsync-path="mkdir -p /path/to/backups/$HOST/weekly/ && rsync" \
    --exclude-from=/home/$USER/.local/bin/rsync-homedir-excludes.txt \
    /home/$USER/ backup-user@backup-server:/path/to/backups/$HOST/weekly/ 

There isn’t much difference here. Just writing to the /weekly folder.

Monthly

The longest cadence that we have is a monthly process which will archive the current state into an archive, and date the file for later use potentially.

#!/bin/bash

# monthly-backup.sh
#
# Monthly archive and copy

ARCHIVE=$(date +%Y-%m-%d).tar.gz
USER=$(whoami)
HOST=$(hostname)

tar --exclude-from=/home/$USER/.local/bin/rsync-homedir-excludes.txt \
    -zcvf \
    /tmp/$ARCHIVE \
    /home/$USER

scp /tmp/$ARCHIVE backup-user@backup-server:/path/to/backups/$HOST/monthly/

rm /tmp/$ARCHIVE

Using tar this script builds a full archive, and then sends that off to the backup server.

Scheduling

Finally, we need to setup these scripts to execute automatically for us. For this, we’ll use cron.

Here’s an example crontab scheduling these scripts:

# m h  dom mon dow   command
00 22 * * * /home/user/.local/bin/daily-backup.sh
00 21 * * 6 /home/user/.local/bin/weekly-backup.sh
00 22 1 * * /home/user/.local/bin/monthly-backup.sh

RAID array stability

Introduction

Recently, I’ve purchased a Terramaster D2-310 RAID array.

There were some important configurations that I needed to put together in order to get this unit to perform correctly.

Problems

After getting everything setup, I had two 4TB BarraCuda drives plugged in ready to go. I have this system running as a RAID 1 so that I have a mirror of my data.

After starting to transfer data onto the device, I noticed that copy jobs would grind to a halt; as well as the error log being full of the following:

[sda] tag#7 uas_zap_pending 0 uas-tag 13 inflight: CMD 
[sda] tag#7 CDB: Write(16) 8a 00 00 00 00 00 17 4f 50 10 00 00 04 00 00 00
[sda] tag#8 uas_zap_pending 0 uas-tag 14 inflight: CMD 
[sda] tag#8 CDB: Write(16) 8a 00 00 00 00 00 17 4f 50 00 00 00 00 10 00 00
[sda] tag#9 uas_zap_pending 0 uas-tag 15 inflight: CMD 
[sda] tag#9 CDB: Write(16) 8a 00 00 00 00 00 17 4f 4c 00 00 00 04 00 00 00
[sda] tag#10 uas_zap_pending 0 uas-tag 16 inflight: CMD 
[sda] tag#10 CDB: Write(16) 8a 00 00 00 00 00 17 4f 48 00 00 00 04 00 00 00
[sda] tag#11 uas_zap_pending 0 uas-tag 17 inflight: CMD 
[sda] tag#11 CDB: Write(16) 8a 00 00 00 00 00 17 4d 44 00 00 00 04 00 00 00
[sda] tag#13 uas_zap_pending 0 uas-tag 18 inflight: CMD 
[sda] tag#13 CDB: Synchronize Cache(10) 35 00 00 00 00 00 00 00 00 00

There’s also signs of crashes from the disk as well:

task:jbd2/sda1-8     state:D stack:0     pid:7414  tgi>
Call Trace:
 <TASK>
 __schedule+0x27c/0x6b0
 schedule+0x33/0x110
 io_schedule+0x46/0x80
 bit_wait_io+0x11/0x90
 __wait_on_bit+0x42/0x110
 ? __pfx_bit_wait_io+0x10/0x10
 out_of_line_wait_on_bit+0x8c/0xb0
 ? __pfx_wake_bit_function+0x10/0x10
 __wait_on_buffer+0x30/0x50
 jbd2_journal_commit_transaction+0x16da/0x1af0
 ? lock_timer_base+0x3b/0xe0
 kjournald2+0xab/0x280
 ? __pfx_autoremove_wake_function+0x10/0x10
 ? __pfx_kjournald2+0x10/0x10
 kthread+0xef/0x120
 ? __pfx_kthread+0x10/0x10
 ret_from_fork+0x44/0x70
 ? __pfx_kthread+0x10/0x10
 ret_from_fork_asm+0x1b/0x30
 </TASK>

Ugh …

Disable USB Auto Suspend

I thought there might have been a suspend issue with the USB device. In order to achieve this, I needed to send the kernel some parameters from GRUB.

Opening /etc/default/grub, we can start adding items to GRUB_CMDLINE_LINUX_DEFAULT:

GRUB_CMDLINE_LINUX_DEFAULT="usbcore.autosuspend=-1"

Failing back to usb_storage

Looking at some discussions I can see that a lot of people were having success with blacklisting the uas driver that the vendor provides, failing back to the standard driver from linux.

So, I did that too.

To do that, you need to pass the IDs as quirks into the blacklist request. To get those IDs, you need to look at the output from lsusb:

$ lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 003 Device 002: ID 174c:2074 ASMedia Technology Inc. ASM1074 High-Speed hub
Bus 003 Device 003: ID 046d:c548 Logitech, Inc. Logi Bolt Receiver
Bus 003 Device 004: ID 8087:0033 Intel Corp. AX211 Bluetooth
Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 004 Device 002: ID 174c:3074 ASMedia Technology Inc. ASM1074 SuperSpeed hub
Bus 004 Device 003: ID 0480:a006 Toshiba America Inc UAS Controller

The Toshiba, and ASMedia items were of interest to me.

Create a file /etc/modprobe.d/blacklist-uas.conf

I added this text:

options usb-storage quirks=174c:2074:u,174c:3074:u,0480:a006:u

Remember to specify the u at the end of the ID strings. It’s literal, and need to be there.

Conclusion

This thing works - and works really well now.