Table of Contents

Main steps when implementing a character driver:

  1. implement operations corresponding to system calls from an application
  2. define a file_operations structure associated to the implemented functions
  3. reserve a ser of major and minor numbers for the driver
  4. tell the kernel to associate the reserved major and minor to the defined file operations

Kernel APIs

These are some useful general-purpose kernel APIs:

Kernel memory allocation

Debugging features:

MMIO. I/O memory load/store instructions work with virtual addresses, therefore drivers need to have the proper virtual address. This is configured with ioremap(), iounmap(). MMIO devices are accessed with (read[bwl], write[bwl]) or without endianness conversion (raw_read[bwl], raw_write[bwl]).

An issue with I/O memory accesses is memory reordering, which may require memory barriers (rmb(), wmb(), mw()).

User space memory handling

User-space applications can access physical addresses directly through /dev/mem.

Modules

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

static int myparam = 1;
module_param(myparam, int, 0);

static int __init modname_init(void)
{
}

static void __exit modname_exit(void)
{
}

module_init(modname_init);
module_exit(modname_exit);
MODULE_LICENSE(¨GPL¨);
MODULE_DESCRIPTION(¨blah blah¨);
MODULE_AUTHOR(¨me¨);

Functions and variables have to be explicitly exported by the kernel to be visible from a module:

EXPORT_SYMBOL(symname);

A kernel module will only run in a kernel that matches the version of the kernel headers it was compiled against. Example of makefile for an out-of-tree module:

ifneq ($(KERNELRELEASE),)
obj-m := hello.o
else
KDIR := /path/kernel/sources     # if not full sources, at least headers directory
all:
    $(MAKE) -C $(KDIR) M=`pwd` modules
endif

Sleeping

/* dynamic declaration */
wait_queue_head_t queue;
init_waitqueue_head(&queue);
/* blocking */
wait_event_interruptible(queue, condition);
/* unblocking - if condition is still false, nothing happens;
   if the condition is true, the process in the wait queue is
   awakaned and its state set to TASK_RUNNING */
wake_up_interruptible(&queue);

Interrupts

Top half and botton half processing:

Interrupt handler constraints

Interrupt handler taks

Threaded interrupts

Threaded interrupts are executed inside a thread (allows to block inside the handler). There is support for interrupt handler execution priority.

UIO allows the handling from interrupt in user space.

Concurrency

The kernel lock validator is an useful tool to detect violations of locking rules during system life. Alternatives to locking are the use of lock-free algorithms (e.g. read copy update, RCU) or atomic operations (.e.g. atomic_set/atomic_read).

Semaphores

Mutexes

DEFINE_MUTEX(mname);
mutex_lock(&mname);
mutex_trylock(&mname);
mutex_is_locked(&mname);
mutex_unlock(&mname);

Spinlocks

Busy waiting locks, therefore usable inside interrupt handlers or critical sections in process context (that don´t want to sleep). Spinlocks cause kernel preemption to be disabled on the CPU executing them.

DEFINE_SPINLOCK(sname);
spin_lock(&sname);
... critical code ...
spin_unlock(&sname);

Debugging

Userspace:

Userspace drivers

http://2net.co.uk/slides/ew2016-userspace-drivers-slides.pdf