Part 1 - Internet connected user-space device drivers using CGo

In this 2-part post, I am going to show how to use Golang with standard device driver libraries using CGo as a bridge to connect the two together.

Table of contents

  1. Why Go?
  2. User-space drivers
  3. Avoiding wrapper libraries
  4. Enter CGo
  5. 2x Hello World
  6. Linking pre-built libraries
  7. Part 2
  8. Epilogue

Why Go?

Golang and Rust are the 2 new kids on the block that most threaten to displace C/C++ for the throne of preferred embedded language. Now, something like this will take decades to happen because embedded systems have a pretty long shelf life, and most don't have OTA updates yet. So what you pick is something that you have to stick to for quite some time.

Golang is something that I have seen being used time and again for CLI tools. Rust on the other hand has good support for embedded systems, especially Cortex microcontrollers. I am picking Golang purely because I am more familiar with it than Rust.


User-space drivers

For example, let's say you want to build a custom printing station. In that case you will be building what we'll call a "frontend" for your application - it could be a local web app or a native app, and it will have a backend that will talk to hardware. Linux has support for something known as the CUPS which is a printer server  which can accept jobs and has the ability to talk to different printers over USB or the network.

Now this program has a prebuilt library called libcups which makes it possible for you to build apps around this. Unfortunately this only supports C by default, and you have to

  • either find a wrapper library in your language of choice
  • or write some C code, and make it talk to your program.

I always prefer the latter approach.

Avoiding wrapper libraries

Typically wrapper libraries are an abstraction of some API, and more often than not there will be some functionality that's missing. The folks that write the API won't always be aware of the limitations of your favourite programming language, so their design considerations might not be suitable for you. Plus finding support for your wrapper library will always be more difficult than finding support for the language that the API was intended for.

Note - this means that you can end up rewriting code that someone else is already using on the Internet. That can bother some people, but I think it's more important to write code that is more readable to other folks working on similar tech than try and get everything to work with a one single programming language. YMMV.

Enter CGo

CGo enables you to use C for the interaction with the hardware - things like USB devices, printers, cameras, and more - but still use Golang for the more Internet-y things like routing client requests, ReST API calls, serving content like images and more.

In this post, I am going to show how to use Golang with standard device driver libraries using CGo to invoke a C function.


2x Hello World

Let's start with the the following 3 files, in one directory

  • my_driver.h
  • my_driver.c
  • my_app.go
#pragma once
int my_driver_init();
my_driver.h

The #pragma once isn't required here, but it ensures that the function definition is included only once. This is important if there are multiple files including this file, and we want to avoid a circular dependency.

#include "my_driver.h"
#include <stdio.h>

int my_driver_init() 
{
    printf("Hello from C\n");
    return 0;
}
my_driver.c
package main

// #include "my_driver.h"
import "C"
import "fmt"

func main() {
	fmt.Println("Hello from Golang")
	C.my_driver_init()
}
my_app.go

Once you have the above in a folder called my_app, you can run the go build command, and you should have a binary called my_app.

$ ./my_app
Hello from Golang                                                                              Hello from C

Linking pre-built libraries

Now let's introduce a standard library to make things more interesting. I am going to use the libusb library as you don't need any additional driver to try this (unlike printers, cameras, serial devices, etc.)

Note: This code works on a MacOS machine as well. I haven't tested it on a Windows machine.

  • The go file looks pretty much the same as from the previous section except for 2 new things - CFLAGS and LDFLAGS. These tell the go compiler to look at the directory in CFLAGS for the header file that's not part of this directory i.e. libusb.h. The LDFLAGS tells go to use the precompiled library usb-1.0 which will have the function definitions of anything that we find from libusb.h.
package main

// #cgo CFLAGS: -I/usr/local/include/libusb-1.0
// #cgo LDFLAGS: -lusb-1.0
// #include "my_driver.h"
import "C"
import "fmt"

func main() {
	fmt.Println("Hello from Golang")
	C.my_driver_init()
}
my_app.go
  • my_driver.h has no changes as compared to the previous section.
  • As I said, using C for driver interfacing comes handy when it comes to support. We can see some good examples available for us to get started in the libusb repo. I am going to pick the listdevs.c example from here and make some changes to it.
  • Most of my changes are about formatting, but I have added some code to show the manufacturer string, and not just the vendor ID. This requires 3 or so additional lines as compared to the standard example from libusb.
#include "my_driver.h"
#include <stdio.h>
#include <libusb.h>

static void print_devs(libusb_device **devs)
{
  libusb_device *dev;
  int i = 0, j = 0;
  uint8_t path[8];
  libusb_device_handle* dev_handle;
  unsigned char manufacturer[200];
  printf("Manufacturer\tvendor ID\tProduct ID\tBus number,Device address\n\n");
  while ((dev = devs[i++]) != NULL) {
    struct libusb_device_descriptor desc;
    int r = libusb_get_device_descriptor(dev, &desc);
    if (r < 0) {
      fprintf(stderr, "failed to get device descriptor");
      return;
    }
    libusb_open(dev, &dev_handle);
    libusb_get_string_descriptor_ascii(dev_handle, desc.iManufacturer,
				       manufacturer,200);
    printf("%s \n\t\t %04x\t\t%04x \t\t (%d, %d)", manufacturer,
	   desc.idVendor, desc.idProduct,
	   libusb_get_bus_number(dev), libusb_get_device_address(dev));
    printf("\n-------------------------------------------------------------------");
    printf("\n");
  }
}

int my_driver_init()
{
  libusb_device **devs;
  int r;
  ssize_t cnt;

  r = libusb_init(NULL);
  if (r < 0)
    return r;

  cnt = libusb_get_device_list(NULL, &devs);
  if (cnt < 0){
    libusb_exit(NULL);
    return (int) cnt;
  }
  print_devs(devs);
  libusb_free_device_list(devs, 1);
  libusb_exit(NULL);
  return 0;
}
my_driver.c
  • The steps for building don't change - run go build and then run the program using ./my_app. Your output should be something like the following -
Hello from Golang
Manufacturer	vendor ID	Product ID	Bus number,Device address


		 04d9		1203 		 (20, 7)
-------------------------------------------------------------------
PixArt
		 04ca		0061 		 (20, 6)
-------------------------------------------------------------------
Apple Inc.
		 05ac		8509 		 (26, 2)
-------------------------------------------------------------------
Apple Inc.
		 05ac		0252 		 (29, 5)
-------------------------------------------------------------------
Apple Computer, Inc.
		 05ac		8242 		 (29, 4)
-------------------------------------------------------------------
Apple Inc.
		 0a5c		4500 		 (29, 3)
-------------------------------------------------------------------
Apple Inc.
		 0424		2513 		 (29, 2)
-------------------------------------------------------------------
Apple Inc.
		 8087		0024 		 (26, 1)
-------------------------------------------------------------------
Apple Inc.
		 8087		0024 		 (29, 1)
-------------------------------------------------------------------
Output from my MacOS machine

So right now we have a Go program with some flags for compiling C programs with linked libraries, and a simple call to a C function. The C program simply prints the output on the console.


Part 2

In the next blog, I will be talking about

  1. Passing Go data structures to C
  2. Passing C data structures back to Golang
  3. Showing the connected USB devices on a webpage

Cheers!

Show Comments