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
- Why Go?
- User-space drivers
- Avoiding wrapper libraries
- Enter CGo
- 2x Hello World
- Linking pre-built libraries
- Part 2
- 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
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.
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
andLDFLAGS
. These tell the go compiler to look at the directory inCFLAGS
for the header file that's not part of this directory i.e.libusb.h
. TheLDFLAGS
tells go to use the precompiled libraryusb-1.0
which will have the function definitions of anything that we find fromlibusb.h
.
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.
- 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 -
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
- Passing Go data structures to C
- Passing C data structures back to Golang
- Showing the connected USB devices on a webpage
Cheers!