CS 528 - Project

Raspberry PI GPIO Kernel Programming

Purpose/Topic

To research how the Raspberry PI's GPIO pins are handled within the kernel and develop a kernel based system to be used by the programmer in user space

Code

code

Presentation

Link

Links

Updates

10/05/18

Got the custom kernel installed on the raspberry pi:

Got LED's/GPIO verified working and managed through sysfs (see log for 10/05)

10/08/18

Have basic system calls implemented for managing GPIO pins. The calls are stored in the file bj_gpio.c in the directory arch/arm/kernel:


#include <linux/gpio.h>

/* Initialize a pin to either input or output
 * inOrOut: 1: input, 0: output */
asmlinkage long sys_bj_initpin(const unsigned int pin, int inOrOut)
{
    int ret;
    char pinName[50]; // the label for the pin

    printk("BJ: Initializing pin %d as %s\n",
            pin, (inOrOut) ? "input" : "output");

    /* Set the pinname */
    sprintf(pinName, "gpio%d", pin);

    /* Request the pin */
    ret = gpio_request(pin, pinName);
    if (ret < 0) {
        printk("BJ: Bad GPIO request!\n");
        return -EINVAL;
        /* http://www-numi.fnal.gov/offline_software/srt_public_context/WebDocs$
         */
    }

    /* 1/not zero: input */
    if (inOrOut) {
        ret = gpio_direction_input(pin);
    }
   /* Otherwise output */
    else {
        /* 0 means default the pin to `off`, if its
         * 1 then the pin will default to `on` */
        ret = gpio_direction_output(pin, 0);
    }

    /* Test for failure setting direction */
    if (ret < 0) {
        printk("BJ: failed setting direction!\n");
        gpio_free(pin);
        return -EINVAL;
    }

    return 0;
}

asmlinkage long sys_bj_setpin(const unsigned int pin, const unsigned int val)
{
    printk("Somebody called bj setpin: %d, val: %d\n", pin, val);

    /* Set the value of the pin */
    gpio_set_value(pin, val);

    return 0;
}

asmlinkage long sys_bj_freepin(const unsigned int pin)
{
    printk("BJ: Somebody called freepin: %d\n", pin);

    // dummy to free mem
    gpio_free(pin);

    return 0;
}

In order to get the file to compile, had to add `bj_gpio.o` to obj-y in the Makefile in /arch/arm/kernel:


obj-y           := elf.o entry-common.o irq.o opcodes.o \
                   process.o ptrace.o reboot.o return_address.o \
                   setup.o signal.o sigreturn_codes.o \
                   stacktrace.o sys_arm.o time.o traps.o \
                   bj_gpio.o

This is the program used to test the calls on the raspberry pi:


#define _GNU_SOURCE
#include <unistd.h>
#include <sys/syscall.h>
#include <stdio.h>

// syscall numbers
#define SYS_bj_initpin 398
#define SYS_BJ_setpin  399
#define SYS_bj_freepin 400

int main(void)
{
    const unsigned int pin = 21;

    /* 0 for output */
    syscall(SYS_bj_initpin, pin, 0);

    /* Turn the pin on */
    syscall(SYS_bj_setpin, pin, 1);

    /* Sleep for 10 seconds */
    sleep(10);

    /* Turn the pin off */
    syscall(SYS_bj_setpin, pin, 0);

    /* Free the pin */
    syscall(SYS_bj_freepin, pin);

    return 0;
}

You can see the `printk`s from `dmesg | tail`:

10/09/18

Found a way to emulate the raspberry pi so I don't have to constantly move the kernel code back and forth between the pc and raspberry pi: Link.

Notes: it has to run on ethernet in order to create a bridge (for network access), you have to add some fields in visudo (read the link), and you have to edit qemu-pi.sh (just change a field to 1 instead of 0) to use network.

I also found out from another article that you can mount a .img file and whatever changes you make inside will stay there accordingly (same in qemu when you run based off the .img file): Link

Moved the system calls to their own file, `bj_gpio.c`, and added 3 system calls: bj_initpin, bj_setpin, and bj_freepin. See the project page for more details.

11/11/18

Created simple device driver on the raspberry pi, following this tutorial; just allows reading and writing short messages to/from a buffer by using /dev/bjrpi; going to expand to manipulate GPIO pins.

The code for the driver:


#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/mutex.h> // protect the device so it can only be used by
                         // 1 user at a time

#define DEVICE_NAME "bjrpi" // device will appear at /dev/bjrpi
#define CLASS_NAME  "bj"    // device class name (for /sys/class)

MODULE_LICENSE("GPL");
MODULE_AUTHOR("BJ Blair");
MODULE_DESCRIPTION("Hello World Driver for RPI");
MODULE_VERSION("0.1");

static DEFINE_MUTEX(bjrpi_mutex); // creates a new semaphore variable,
                                  // defaults to 1 (unlocked), to start
                                  // with locked (0) use DEFINE_MUTEX_LOCKED

static int major_number;        // device number, determined automatically
static char message[256] = {0}; // store message from userspace
static short message_size;      // how long the message is
static int   num_opens = 0;     // how many times the device has been opened
static struct class  *bjrpi_class  = NULL; // class pointer
static struct device *bjrpi_device = NULL; // device pointer

/* Function prototypes for IO operations */
static int bjrpi_open(struct inode *, struct file *);
static int bjrpi_release(struct inode *, struct file *);
static ssize_t bjrpi_read(struct file *, char *, size_t, loff_t *);
static ssize_t bjrpi_write(struct file *, const char *, size_t, loff_t *);

static int __init bjrpi_init(void);
static void __exit bjrpi_exit(void);

/* Structure defining functions for IO operations */
static struct file_operations fops = {
    .open = bjrpi_open,
    .read = bjrpi_read,
    .write = bjrpi_write,
    .release = bjrpi_release
};

static int __init bjrpi_init(void)
{
    printk(KERN_INFO "BJRPI: Initializing character LKM\n");

    /* Initialize the mutex lock dynamically at runtime */
    mutex_init(&bjrpi_mutex);

    /* Allocate a major driver number */
    major_number = register_chrdev(0, DEVICE_NAME, &fops);
    if (major_number < 0) {
        printk(KERN_ALERT "BJRPI: Failed to register major driver number\n");
        return major_number;
    }
    printk(KERN_INFO "BJRPI: Registered major number %d\n", major_number);

    /* Register the device class */
    bjrpi_class = class_create(THIS_MODULE, CLASS_NAME);
    if (IS_ERR(bjrpi_class)) {
        unregister_chrdev(major_number, DEVICE_NAME);
        printk(KERN_ALERT "BJRPI: Failed to register device class\n");
        return PTR_ERR(bjrpi_class);
    }
    printk(KERN_INFO "BJRPI: Registerd device class\n");


    /* Register the device driver */
    bjrpi_device = device_create(
        bjrpi_class,
        NULL,
        MKDEV(major_number, 0),
        NULL,
        DEVICE_NAME
    );
    if (IS_ERR(bjrpi_device)) {
        printk(KERN_ALERT "BJRPI: Failed to create device\n");
        class_destroy(bjrpi_class);
        unregister_chrdev(major_number, DEVICE_NAME);
        return PTR_ERR(bjrpi_device);
    }
    printk(KERN_INFO "BJRPI: Device class created correctly\n");

    return 0;
}

static void __exit bjrpi_exit(void)
{
    mutex_destroy(&bjrpi_mutex);
    device_destroy(bjrpi_class, MKDEV(major_number, 0));
    class_unregister(bjrpi_class);
    class_destroy(bjrpi_class);
    unregister_chrdev(major_number, DEVICE_NAME);
    printk(KERN_INFO "BJRPI: Goodbye from LKM Device Driver!\n");
}

static int bjrpi_open(struct inode *inodep, struct file *fp)
{
    /* see if the device is available, and if so lock it */
    if (!mutex_trylock(&bjrpi_mutex)) {
        printk(KERN_ALERT "BJRPI: Device busy (checked mutex)\n");
        return -EBUSY;
    }

    num_opens++;
    printk(KERN_INFO "BJRPI: Device has been opened %d times\n", num_opens);
    return 0;
}

static ssize_t bjrpi_read(struct file *fp, char *buffer, size_t len, loff_t *offset)
{
    int error_count = 0;

    /* Copy what we've read in so far to the user */
    error_count = copy_to_user(buffer, message, message_size);

    /* Sucess */
    if (error_count == 0) {
        printk(KERN_INFO "BJRPI: Sent %d characters to the user\n", message_size);
        return (message_size = 0); // set the message size to 0 to start overwriting the buffer
    }
    /* Failure */
    else {
        printk(KERN_INFO "BJRPI: Failed to send %d characters to the user\n", error_count);
        return -EFAULT; // failed - return a bad address message
    }
}

static ssize_t bjrpi_write(struct file *fp, const char *buffer, size_t len, loff_t *offset)
{
    sprintf(message, "%s(%zu letters)", buffer, len);
    message_size = strlen(message);
    printk(KERN_INFO "BJRPI: Receieved %zu characters from the user\n", len);
    return len;
}

static int bjrpi_release(struct inode *indoep, struct file *filep)
{
    /* make the device available */
    mutex_unlock(&bjrpi_mutex);
    printk(KERN_INFO "BJRPI: Device sucessfully closed\n");
    return 0;
}

/* Register the module */
module_init(bjrpi_init);
module_exit(bjrpi_exit);

And the test file code:


#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

#define BUFFER_LEN 256
static char buf[BUFFER_LEN];

int main(int argc, char *argv[])
{
    int ret, fd;
    char sendStr[BUFFER_LEN];

    /* Open the device */
    fd = open("/dev/bjrpi", O_RDWR); // open with read-write permissions
    if (fd < 0) {
        perror("Failed to open the device\n");
        return errno;
    }

    printf("Enter a string to send: ");
    scanf("%[^\n]%*c", sendStr); // read in a string with spaces

    /* Write to the device */
    ret = write(fd, sendStr, strlen(sendStr));
    if (ret < 0) {
        perror("Failed to write to device\n");
        return errno;
    }

    /* Read from the device */
    ret = read(fd, buf, BUFFER_LEN);
    if (ret < 0) {
        perror("Failed to read from device\n");
        return errno;
    }

    printf("Read message: %s\n", buf);

    return 0;
}

Cross compiled for the raspberry pi by putting driver code in linux/drivers/gpio/gpio-bj.c, and adding obj-m += gpio-bj.o to linux/drivers/gpio/Makefile, then running


make -j4 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules

Which means it will only need to compile YOUR module. The result is a file named `gpio-bj.ko`, which can be copied to the raspberry pi and then loaded running sudo insmod gpio-bj.ko. You can then compile and run the test code on the raspberry pi.

11/17/18

Added GPIO setting to the device driver; now you can write to /dev/bjrpi a number and it will output the appropriate pins in binary (e.g. echo 7 > /dev/bjrpi => pin output will be 00111! Code is now in the `code` link at the top of the page.