Kernel Programming Using Raspberry pi

Implementation Of Device Driver On RaspBerry PI

Posted by Nivin Anton Alexis Lawrence on December 25, 2016

Recently by the same author:


Introduction to Functional Programming [Part 2]

Think Functional


Nivin Anton Alexis Lawrence

Human

Motivation

Kernel programming was always intimidating and hence I wanted to give it a shot. But I was wondering on what device driver to write? and that is when I found my lonely Raspberry PI lying in the corner. I was sketching out ideas on what I can build with available hardware.

Idea

I decided to go with writing Simple GPIO device driver. And my hardware consist of bread board, two led, toggle switch, T-Cobbler and Raspberry Pi.In order to build a device driver, it is important to know more about kernel, user spaces, and getting familiar with the hardware architecture.

Background Study

Here is the list of topics I revised:

  • Interrupt Handling.
    • In general, interrupts and their handlers are used to handle high-priority conditions that require the interruption of the current code the processor is executing.
    • Hardware interrupts are used by devices to communicate that they require attention from the operating system.
    • A software interrupt is caused either by an exceptional condition in the processor itself, or a special instruction in the instruction set which causes an interrupt when it is executed.
  • Different types of Device Drivers.
    • Drivers are hardware dependent and operating systems specific. They usually provide the interrupt handling required for any necessary asynchronous time-dependent hardware interface.
    • Block Device Drivers: Block device drivers manage devices with physically addressable storage media, such as disks.
    • Character Device Drivers: All other devices are considered character devices.
  • Loadable Kernel Module. HERE
    • A loadable kernel module (or LKM) is an object file that contains code to extend the running kernel, or so-called base kernel, of an operating system.
    • LKMs are typically used to add support for new hardware (as device drivers) and/or file systems, or for adding system calls. When the functionality provided by a LKM is no longer required, it can be unloaded in order to free memory and other resources.
  • Cross Compilation. HERE

Code

#include <linux/init.h>
#include <linux/module.h>
#include <asm/io.h>
#include <mach/platform.h>
#include <linux/timer.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>

MODULE_LICENSE("GPL");


//Instead of using native gpio library, we are writing from scratch.
struct GpioRegisters
{
    uint32_t GPFSEL[6]; // defines the function of the port.
    uint32_t Reserved1;
    uint32_t GPSET[2]; // sets the value for corresponding pin.
    uint32_t Reserved2;
    uint32_t GPCLR[2]; // clears the value 
};

struct GpioRegisters *s_pGpioRegisters;
static const int LedGpioPin1 = 21; // GPIO Output pin
static const int LedGpioPin2 = 20;
static const int gpioButton = 23; // Button that is used for toggling.
static bool on = false;
static unsigned int irqNumber;  // Irq Number to listens to. 

static irq_handler_t  ebbgpio_irq_handler(unsigned int irq, void *dev_id, struct pt_regs *regs); // defines the interrupt handler function

static void SetGPIOFunction(int GPIO, int functionCode)
{
    int registerIndex = GPIO / 10;
    int bit = (GPIO % 10) * 3;
    unsigned oldValue = s_pGpioRegisters-> GPFSEL[registerIndex];
    unsigned mask = 0b111 << bit;
    printk("Changing function of GPIO%d from %x to %x\n",
           GPIO,
           (oldValue >> bit) & 0b111,
           functionCode);
    s_pGpioRegisters-> GPFSEL[registerIndex] =
        (oldValue & ~mask) | ((functionCode << bit) & mask);
}

static void SetGPIOOutputValue(int GPIO, bool outputValue)
{
    if (outputValue)
        s_pGpioRegisters-> GPSET[GPIO / 32] = (1 << (GPIO % 32));
    else
        s_pGpioRegisters-> GPCLR[GPIO / 32] = (1 << (GPIO % 32));
}

static int __init LedBlinkModule_init(void)
{
   
    printk(KERN_INFO "GPIO_TEST Welcome");
    s_pGpioRegisters =
        (struct GpioRegisters *)__io_address(GPIO_BASE);
    SetGPIOFunction( LedGpioPin1, 0b001); //Output
    SetGPIOFunction( LedGpioPin2, 0b001); //Output
    SetGPIOFunction( gpioButton, 0); //Input
    SetGPIOOutputValue( LedGpioPin1, !on);
    SetGPIOOutputValue( LedGpioPin2, on);
    irqNumber = gpio_to_irq(gpioButton);
    printk(KERN_INFO "GPIO_TEST: The interrupt number  is: %d\n", irqNumber);
    //IRQ_HANDLED, request_irq, is a predefined function, that helps to register interupt lines. 

    int result = request_irq(irqNumber,
                        (irq_handler_t) ebbgpio_irq_handler, 
                        IRQF_TRIGGER_RISING,
                        "ebb_gpio_handler",
                        NULL);
 
   printk(KERN_INFO "GPIO_TEST: The interrupt request result is: %d\n", result);
   return result;	
}

static void __exit LedBlinkModule_exit(void)
{
    SetGPIOFunction( LedGpioPin1, 0); //Configure the pin as input
    SetGPIOFunction( LedGpioPin2, 0); //Output
    free_irq(irqNumber, NULL);
    gpio_free(gpioButton);
	
}
static irq_handler_t ebbgpio_irq_handler(unsigned int irq, void *dev_id, struct pt_regs *regs){
   on = !on;                          
   SetGPIOOutputValue( LedGpioPin1, !on);
   SetGPIOOutputValue( LedGpioPin2, on);         
   printk(KERN_INFO "GPIO_TEST: Interrupt! (button state is)");		
   return (irq_handler_t) IRQ_HANDLED;   
}



module_init(LedBlinkModule_init);
module_exit(LedBlinkModule_exit);

Make File

obj-m += oneled.o
CCPREFIX=/home/os/tools/arm-bcm2708/arm-bcm2708-linux-gnueabi/bin/arm-bcm2708-linux-gnueabi-
all:
	make ARCH=arm CROSS_COMPILE=$(CCPREFIX) -C /home/os/linux M=$(PWD) modules
clean:
	make -C /home/os/linux M=$(PWD) clean