1. Introduction
  2. The NetBSD driver model
  3. Example driver from scratch
  4. Interacting with userspace
  5. Using ioctls
  6. A few tips
  7. Summary

Introduction

Why was this tutorial created?

What won't be covered here?

We don't have much time, so several advanced topics were omitted:

However, once you finish this tutorial, you should be able to pursue this knowledge yourself.

What is a driver anyway?

What do you need to write a driver?

Why is writing the device drivers considered difficult?

The NetBSD driver model

The NetBSD kernel basics

The NetBSD source directory structure

Kernel autoconfiguration framework - autoconf(9)

Autoconfiguration as seen in the dmesg

NetBSD 6.99.12 (GENERIC) #7: Fri Oct  5 18:43:21 CEST 2012
        rkujawa@saiko.local:/Users/rkujawa/netbsd-eurobsdcon2012/src/sys/arch/cobalt/compile/obj/GENERIC
Cobalt Qube 2
total memory = 32768 KB
avail memory = 27380 KB
mainbus0 (root)
com0 at mainbus0 addr 0x1c800000 level 3: ns16550a, working fifo
com0: console
cpu0 at mainbus0: QED RM5200 CPU (0x28a0) Rev. 10.0 with built-in FPU Rev. 1.0
cpu0: 48 TLB entries, 256MB max page size
cpu0: 32KB/32B 2-way set-associative L1 instruction cache
cpu0: 32KB/32B 2-way set-associative write-back L1 data cache
mcclock0 at mainbus0 addr 0x10000070: mc146818 compatible time-of-day clock
panel0 at mainbus0 addr 0x1f000000
gt0 at mainbus0 addr 0x14000000
pci0 at gt0
pchb0 at pci0 dev 0 function 0: Galileo GT-64011 System Controller, rev 1
pcib0 at pci0 dev 9 function 0
pcib0: VIA Technologies VT82C586 PCI-ISA Bridge, rev 57
viaide0 at pci0 dev 9 function 1
viaide0: VIA Technologies VT82C586 (Apollo VP) ATA33 controller
viaide0: primary channel interrupting at irq 14
atabus0 at viaide0 channel 0
viaide0: secondary channel interrupting at irq 15
atabus1 at viaide0 channel 1
wd0 at atabus0 drive 0
wd0: <netbsd-cobalt.img>
wd0: 750 MB, 1524 cyl, 16 head, 63 sec, 512 bytes/sect x 1536192 sectors

The bus_space(9) framework

Machine independent drivers

Example driver from scratch

Development environment

Quick introduction to GXemul

Our hardware - functional description

Our hardware - technical details

Our hardware - technical details (memory mapped register set)

Register Name Offset Description
COMMAND 0x4 Register used to issue commands to the engine
DATA 0x8 Register used to load data to internal engine registers
RESULT 0xC Register used to store the result of arithmetic operation
Bit R/W Description
0 W Execute ADD operation on values loaded into internal register A and B
1 R/W Select internal register A for access through DATA register
2 R/W Select internal register B for access through DATA register

Our hardware - technical details (memory mapped register set)

Bit R/W Description
0:31 R/W Read/write the value in internal engine register
Bit R/W Description
0:31 R Holds the result of last ADD operation

Our hardware - technical details (operation algorithm)

Adding a new driver to the NetBSD kernel

Modifying the PCI device database

unmatched vendor 0xfabc product 0x0001 (Co-processor 
processor, revision 0x01) at pci0 dev 12 function 0 
not configured

Modifying the PCI device database - example

--- pcidevs 29 Sep 2012 10:26:14 -0000  1.1139
+++ pcidevs 5 Oct 2012 08:52:59 -0000
@@ -669,6 +669,7 @@
 vendor CHRYSALIS   0xcafe  Chrysalis-ITS
 vendor MIDDLE_DIGITAL  0xdeaf  Middle Digital
 vendor ARC     0xedd8  ARC Logic
+vendor FAKECARDS   0xfabc  Fake Cards
 vendor INVALID     0xffff  INVALID VENDOR ID

 /*
@@ -2120,6 +2121,9 @@
 /* Eumitcom products */
 product EUMITCOM WL11000P  0x1100  WL11000P PCI WaveLAN/IEEE 802.11

+/* FakeCards products */
+product FAKECARDS AAA      0x0001  Advanced Addition Accelerator
+
 /* O2 Micro */
 product O2MICRO 00F7       0x00f7  Integrated OHCI IEEE 1394 Host Controller
 product O2MICRO OZ6729     0x6729  OZ6729 PCI-PCMCIA Bridge

Modifying the PCI device database - example

Fake Cards Advanced Addition Accelerator (Co-processor 
processor, revision 0x01) at pci0 dev 12 function 0 
not configured

Adding the new PCI driver

Adding the new PCI driver - main driver

Adding the new PCI driver - main driver

Adding the new PCI driver - main driver cont'd

Adding the new PCI driver - main driver example

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: bus_space_tutorial.mdwn,v 1.16 2020/09/09 14:28:56 kim Exp $");
#include <sys/param.h>
#include <sys/device.h>
#include <dev/pci/pcivar.h>
#include <dev/pci/pcidevs.h>
#include <dev/pci/faareg.h>
#include <dev/pci/faavar.h>

static int      faa_match(device_t, cfdata_t, void *);
static void     faa_attach(device_t, device_t, void *);

CFATTACH_DECL_NEW(faa, sizeof(struct faa_softc),
    faa_match, faa_attach, NULL, NULL);

static int
faa_match(device_t parent, cfdata_t match, void *aux)
{
        return 0;
}

static void
faa_attach(device_t parent, device_t self, void *aux)
{ 
}

Adding the new PCI driver - auxiliary includes

#ifndef FAAREG_H
#define FAAREG_H
/* 
 * Registers are defined using preprocessor:
 * #define FAA_REGNAME  0x0
 * We'll add them later, let's leave it empty for now.
 */
#endif /* FAAREG_H */
#ifndef FAAVAR_H
#define FAAVAR_H

/* sc_dev is an absolute minimum, we'll add more later */
struct faa_softc {
        device_t sc_dev;
};
#endif /* FAAVAR_H */

Adding the new PCI driver - registering the driver (courtesy)

--- DEVNAMES    1 Sep 2012 11:19:58 -0000   1.279
+++ DEVNAMES    6 Oct 2012 19:59:06 -0000
@@ -436,6 +436,7 @@
 ex         MI
 exphy          MI
 ezload         MI      Attribute
+faa            MI
 fb         luna68k
 fb         news68k
 fb         newsmips

Adding the new PCI driver - registering the driver

--- pci/files.pci   2 Aug 2012 00:17:44 -0000   1.360
+++ pci/files.pci   6 Oct 2012 19:59:10 -0000
@@ -1122,3 +1122,9 @@
 device tdvfb: wsemuldisplaydev, rasops8, vcons, videomode
 attach tdvfb at pci
 file   dev/pci/tdvfb.c     tdvfb   
+
+# FakeCards Advanced Addition Accelerator
+device faa
+attach faa at pci
+file   dev/pci/faa.c       faa 
+

Adding the new PCI driver to the kernel configuration

--- GENERIC 10 Mar 2012 21:51:50 -0000  1.134
+++ GENERIC 6 Oct 2012 20:12:37 -0000
@@ -302,6 +302,9 @@
 #fms*      at pci? dev ? function ?    # Forte Media FM801
 #sv*       at pci? dev ? function ?    # S3 SonicVibes

+# Fake Cards Advanced Addition Accelerator
+faa*       at pci? dev ? function ?
+
 # Audio support
 #audio*        at audiobus?

Adding the new PCI driver - example

Matching the PCI device

static int
faa_match(device_t parent, cfdata_t match, void *aux)
{
        const struct pci_attach_args *pa = (const struct pci_attach_args *)aux;

        if ((PCI_VENDOR(pa->pa_id) == PCI_VENDOR_FAKECARDS) 
            && (PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_FAKECARDS_AAA))
                return 1;

        return 0;
}

Attaching to the PCI device

faa0 at pci0 dev 12 function 0

Variable types used with bus_space

Why do we need to "map" the resources?

Mapping the hardware resources

bus_space_map(bus_space_tag_t space, bus_addr_t address, 
bus_size_t size, int flags, bus_space_handle_t  *handlep);

Mapping the hardware resources

pci_mapreg_map(const struct pci_attach_args *pa, int reg, pcireg_t type, 
int busflags, bus_space_tag_t *tagp, bus_space_handle_t *handlep, 
bus_addr_t *basep, bus_size_t *sizep);

Mapping the registers using BAR - adding auxiliary includes

#define FAA_MMREG_BAR   0x10
struct faa_softc {
        device_t sc_dev;

        bus_space_tag_t sc_regt;
        bus_space_handle_t sc_regh;
        bus_addr_t sc_reg_pa;

};

Mapping the registers using BAR - main driver code

static void
faa_attach(device_t parent, device_t self, void *aux)
{
        struct faa_softc *sc = device_private(self);
        const struct pci_attach_args *pa = aux;

        sc->sc_dev = self;

        pci_aprint_devinfo(pa, NULL);

        if (pci_mapreg_map(pa, FAA_MMREG_BAR, PCI_MAPREG_TYPE_MEM, 0, 
            &sc->sc_regt, &sc->sc_regh, &sc->sc_reg_pa, 0) != 0 ) {
            aprint_error_dev(sc->sc_dev, "can't map the BAR\n");
            return;
        }

        aprint_normal_dev(sc->sc_dev, "regs at 0x%08x\n", (uint32_t) sc->sc_reg_pa);
}

Accessing the hardware registers

Variants of bus_space_read and bus_space_write

Data Read function Write function
8-bit bus_space_read_1 bus_space_write_1
16-bit bus_space_read_2 bus_space_write_2
32-bit bus_space_read_4 bus_space_write_4
64-bit bus_space_read_8 bus_space_write_8

Accessing the hardware registers - example

#define FAA_DATA                0x8
#define FAA_COMMAND             0x4
#define FAA_COMMAND_STORE_A         __BIT(1)

Accessing the hardware registers - example

static void
faa_attach(device_t parent, device_t self, void *aux)
{
   /* ... */
   if (!faa_check(sc)) {
        aprint_error_dev(sc->sc_dev, "hardware not responding\n");
        return;
   }
}

static bool
faa_check(struct faa_softc *sc)
{
        uint32_t testval = 0xff11ee22; 
        bus_space_write_4(sc->sc_regt, sc->sc_regh, FAA_COMMAND, FAA_COMMAND_STORE_A);
        bus_space_write_4(sc->sc_regt, sc->sc_regh, FAA_DATA, testval);
        if (bus_space_read_4(sc->sc_regt, sc->sc_regh, FAA_DATA) == testval)
                return true;

        return false;
}

Accessing the hardware registers - running the example

[ faa: COMMAND register (0x4) WRITE value 0x2 ]
[ faa: DATA register (0x8) WRITE value 0xff11ee22 ]
[ faa: DATA register (0x8) READ value 0xff11ee22 ]
faa0 at pci0 dev 12 function 0: Fake Cards Advanced Addition Accelerator (rev. 0x01)
faa0: registers at 0x10110000

Implementing addition using the hardware

Implementing addition using the hardware

#define FAA_STATUS              0x0
#define FAA_COMMAND             0x4
#define FAA_COMMAND_ADD             __BIT(0)        
#define FAA_COMMAND_STORE_A         __BIT(1)
#define FAA_COMMAND_STORE_B         __BIT(2)
#define FAA_DATA                0x8
#define FAA_RESULT              0xC

Implementing addition using the hardware

static void
faa_attach(device_t parent, device_t self, void *aux)
{
        /* ... */
        aprint_normal_dev(sc->sc_dev, "just checking: 1 + 2 = %d\n", faa_add(sc, 1, 2));
}

static uint32_t
faa_add(struct faa_softc *sc, uint32_t a, uint32_t b)
{
        bus_space_write_4(sc->sc_regt, sc->sc_regh, FAA_COMMAND, FAA_COMMAND_STORE_A);
        bus_space_write_4(sc->sc_regt, sc->sc_regh, FAA_DATA, a);
        bus_space_write_4(sc->sc_regt, sc->sc_regh, FAA_COMMAND, FAA_COMMAND_STORE_B);
        bus_space_write_4(sc->sc_regt, sc->sc_regh, FAA_DATA, b);
        bus_space_write_4(sc->sc_regt, sc->sc_regh, FAA_COMMAND, FAA_COMMAND_ADD);
        return bus_space_read_4(sc->sc_regt, sc->sc_regh, FAA_RESULT);
}

Implementing addition using the hardware - running the example

[ faa: COMMAND register (0x4) WRITE value 0x2 ]
[ faa: DATA register (0x8) WRITE value 0x1 ]
[ faa: COMMAND register (0x4) WRITE value 0x4 ]
[ faa: DATA register (0x8) WRITE value 0x2 ]
[ faa: COMMAND register (0x4) WRITE value 0x1 ]
[ faa: RESULT register (0xC) READ value 0x3 ]
faa0 at pci0 dev 12 function 0: Fake Cards Advanced Addition Accelerator (rev. 0x01)
faa0: registers at 0x10110000
faa0: just checking: 1 + 2 = 3

Interacting with userspace

The kernel-user space interface

Device files

Operations on device files

Adding cdevsw

dev_type_open(faaopen);
dev_type_close(faaclose);
dev_type_ioctl(faaioctl);

const struct cdevsw faa_cdevsw = {
        faaopen, faaclose, noread, nowrite, faaioctl,
        nostop, notty, nopoll, nommap, nokqfilter, D_OTHER
};

Prototyping the cdevsw operations

Implemeting the cdevsw operations - open / close

int
faaopen(dev_t dev, int flags, int mode, struct lwp *l)
{
        struct faa_softc *sc;
        sc = device_lookup_private(&faa_cd, minor(dev));

        if (sc == NULL)
                return ENXIO;
        if (sc->sc_flags & FAA_OPEN)
                return EBUSY;

        sc->sc_flags |= FAA_OPEN;
        return 0;
}
int
faaclose(dev_t dev, int flag, int mode, struct lwp *l)
{
        struct faa_softc *sc;
        sc = device_lookup_private(&faa_cd, minor(dev));

        if (sc->sc_flags & FAA_OPEN)
                sc->sc_flags =~ FAA_OPEN;

        return 0;
}

Defining the ioctls

Using ioctls

Defining the ioctls

#include <sys/ioccom.h>

#define FAAIO_ADD   _IOWR(0, 1, struct faaio_add)

struct faaio_add {
    uint32_t a;
    uint32_t b;
    uint32_t *result;
};

Implemeting the cdevsw operations - ioctl

int
faaioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l)
{
        struct faa_softc *sc = device_lookup_private(&faa_cd, minor(dev));
        int err;

        switch (cmd) {
        case FAAIO_ADD:
                err = faaioctl_add(sc, (struct faaio_add *) data);
                break;
        default:
                err = EINVAL;
                break;
        }
        return(err);
}
static int
faaioctl_add(struct faa_softc *sc, struct faaio_add *data)
{
        uint32_t result; int err;

        aprint_normal_dev(sc->sc_dev, "got ioctl with a %d, b %d\n",
            data->a, data->b);

        result = faa_add(sc, data->a, data->b);
        err = copyout(&result, data->result, sizeof(uint32_t));
        return err;
}

Using copyout to pass data to userspace

Defining device major number

Creating the device node

An example user space program

An example user space program - source

void add(int, uint32_t, uint32_t);

static const char* faa_device = "/dev/faa0";

int
main(int argc, char *argv[])
{
        int devfd;

        if (argc != 3) {
                printf("usage: %s a b\n", argv[0]);
                return 1;
        }
        if ( (devfd = open(faa_device, O_RDWR)) == -1) {
                perror("can't open device file");
                return 1;
        }

        add(devfd, atoi(argv[1]), atoi(argv[2]));

        close(devfd);
        return 0;
}

An example user space program - source

void
add(int devfd, uint32_t a, uint32_t b)
{
        struct faaio_add faaio;
        uint32_t result = 0;

        faaio.result = &result;
        faaio.a = a;
        faaio.b = b;

        if (ioctl(devfd, FAAIO_ADD, &faaio) == -1) {
                perror("ioctl failed");
        }
        printf("%d\n", result);
}

An example user space program - running it

# make
cc -o aaa_add aaa_add.c
# ./aaa_add 3 7
faa0: got ioctl with a 3, b 7
10

A few tips

Avoiding common pitfalls

Basic driver debugging

Summary

Further reading

Get the source code