Character Device Driver

Function
cdev_init Init char device
cdev_add Add char device to system
cdev_del Remove char device from system
alloc_chrdev_region Register a range of char device numbers
unregister_chrdev_region Undo alloc_chrdev_region
Command
mknod Make device files
Struct
inode Identity of file on disk
file Open instance of file
file_operations Tells kernel how to handle IO requests
Term
Major number Kernel driver ID, who responsible for devices
Minor number ID of devices within a driver
IOCTL API for users to query, configure a driver
VMA Virtual Memory Area
erDiagram
    Driver ||--o{ Device : handle

    Driver {
        int majorNumber
    }

    Device {
        int minorNumber
    }


Lab 1: Scull Basic

Run

$ git clone https://github.com/d0u9/Linux-Device-Driver.git
$ cd Linux-Device-Driver/eg_03_scull_basic
$ make
$ insmod scull.ko
# find major number.
$ cat /proc/devices
237 scull
# create device files.
$ mknod /dev/scull0 c 237 0
$ mknod /dev/scull1 c 237 1
$ mknod /dev/scull2 c 237 2
$ chmod 666 /dev/scull[0-2]
$ echo "hello" > /dev/scull0

$ dmesg
scull_open() is invoked
scull_trim() is invoked
scull_write() is invoked
WR pos = 6, block = 0, offset = 0, write 6 bytes
scull_release() is invoked
$ cat /dev/scull0
hello

$ dmesg
scull_open() is invoked
scull_read() is invoked
RD pos = 6, block = 0, offset = 0, read 6 bytes
scull_read() is invoked
RD pos = 6, block = 0, offset = 6, read 131072 bytes
scull_release() is invoked

Code

Setup file_operations.

struct file_operations scull_fops = {
    .owner   = _,
    .open    = scull_open,
    .read    = scull_read,
    .write   = scull_write,
    .release = scull_release,
};

int scull_open(struct inode *inode, struct file *filp) {}
int scull_release(struct inode *inode, struct file *filp) {}
ssize_t scull_read(struct file *filp, char __user *buff, _) {}
ssize_t scull_write(struct file *filp, const char __user *buff, _ ) {}

Setup char devices.

// register number of devices that driver can handle
alloc_chrdev_region(_, _, dev_count=3, name="scull");

// create devices with associated file_operations, 
// major_number, minor_number
for(int i = 0; i < dev_count; i++)
{
    // init a char device
    cdev_init(cdev, &scull_fops);
    
    // make device id from major, minor numbers
    devno = MKDEV(major_number, minor_number);

    // add device to system
    cdev_add(cdev, devno, _);
}

Clean on exit.

for(int i = 0; i < dev_count; i++)
{
    cdev_del(cdev);
}

devno = MKDEV(major_number, minor_number);
unregister_chrdev_region(from=devno, count=3);

Illustrate.

┌─────────────┐      ┌─────────────┐
│   cdev      │      │   cdev      │
├─────────────┤      ├─────────────┤
/dev/scull0 │      │ /dev/scull1 │
│ major=237   │      │ major=237
│ minor=0     │      │ minor=1
└─────┬───────┘      └──────┬──────┘
      │cdev_init            │cdev_init
      │                     │
      │  ┌───────────────┐  │  ┌────────────────┐
      └─►│file_operations│◄─┘  │ kernel module  │
         ├───────────────┤     │────────────────│
.open       │     │ scull_open()
.read       ├────►│ scull_read()
.write      │     │ scull_write()
.release    │     │ scull_release()
         └───────────────┘     └────────────────┘


Lab 2: IOCTL

Run

$ cd Linux-Device-Driver/eg_07_ioctl
$ make
$ insmod ioctl.ko
# find major number.
$ cat /proc/devices
237 ioctl
# create device files.
$ mknod /dev/ioctl c 237 0
$ chmod 666 /dev/ioctl
$ cd Linux-Device-Driver/eg_07_ioctl/test
$ make
$ ./ioctl_test.out
retval = 0

$ dmesg
ioctl_open() is invoked
ioctl_ioctl() is invoked
ioctl -> cmd: set print message

Code

Define IOCTL commands.

#include <ioctl.h>

#define IOCTL_DEV_NR        1
#define IOCTL_IOC_MAGIC     'd'

#define IOCTL_RESET         _IO(IOCTL_IOC_MAGIC, 0)
#define IOCTL_HOWMANY       _IOWR(IOCTL_IOC_MAGIC, 1, int)
#define IOCTL_MESSAGE       _IOW(IOCTL_IOC_MAGIC, 2, int)

Setup file_operations.

static struct file_operations fops = {
    .unlocked_ioctl = ioctl_ioctl,
};

long ioctl_ioctl(struct file *filp, unsigned int cmd,
            unsigned long arg)
{
    switch (cmd) {
    case IOCTL_RESET:
        pr_debug("ioctl -> cmd: reset\n");      
        break;
    case IOCTL_HOWMANY:
        pr_debug("ioctl -> cmd: set howmany\n");        
        break;
    case IOCTL_MESSAGE:
        pr_debug("ioctl -> cmd: set print message\n");
        break;  
    }
}

Test: Users call ioctl.

int fd = open("/dev/ioctl", O_RDONLY);
ioctl(fd, IOCTL_HOWMANY, _);
ioctl(fd, IOCTL_MESSAGE, _);
ioctl(fd, IOCTL_RESET, _);

Illustrate.

  ┌────────────┐                        
  │ cdev       │                        
  ├────────────┤                        
/dev/ioctl │                        
  │ major=237
  │ minor=0
  └─────┬──────┘                        
        │cdev_init                      


┌─────────────────┐    ┌───────────────┐
│ file_operations │    │ kernel module │
│─────────────────│    │───────────────│
.unlocked_ioctl ├───►│ ioctl_ioctl()
└─────────────────┘    └───────────────┘


Lab 3: MMAP

Run

$ Linux-Device-Driver/eg_23_simple
$ make
$ insmod simple.ko
# find major number
$ cat /proc/devices
237 simple
# create device files
$ mknod /dev/simple0 c 237 0
$ mknod /dev/simple1 c 237 1
$ chmod 666 /dev/simple*
$ Linux-Device-Driver/eg_23_simple/test
$ make
$ ./remap.out
$ dmesg
vm_start: 0x7fd01b9c5000, vm_end: 0x7fd01b9cb000
, len: 0x6000, vm_pgoff: 0x11e150(0x11e150000), ops=000000009da6972b
vma_open -- vm_start: 0x7fd01b9c5000, vm_end: 0x7fd01b9cb000
, len: 0x6000, vm_pgoff: 0x11e150(0x11e150000), ops=0000000023e1a403
pfn = 0, last page refc when close = 1
vma_close -- vm_start: 0x7fd01b9c5000, vm_end: 0x7fd01b9cb000
, len: 0x6000, vm_pgoff: 0x11e150(0x11e150000), ops=0000000023e1a403
$ ./fault.out
$ dmesg
vma_open -- vm_start: 0x7fc1b8c56000, vm_end: 0x7fc1b8c5c000
, len: 0x6000, vm_pgoff: 0x1(0x1000), ops=0000000035d509e0
pfn = 0, last page refc when close = 1
vma_close -- vm_start: 0x7fc1b8c56000, vm_end: 0x7fc1b8c5c000
, len: 0x6000, vm_pgoff: 0x1(0x1000), ops=0000000035d509e0

Code

Setup file_operations.

struct file_operations simple_remap_ops = {
    .open    = simple_open,
    .mmap    = simple_remap_mmap,
};

int simple_remap_mmap(struct file *filp,
            struct vm_area_struct *vma)
{
    pr_debug("vm_start:_",
         vma->vm_start, vma->vm_end, len,
         vma->vm_pgoff, vma->vm_pgoff << PAGE_SHIFT,
         vma->vm_ops);

    vma->vm_ops = &simple_remap_vm_ops;
    simple_vma_open(vma);
}

Setup virtual memory operations.

struct vm_operations_struct simple_remap_vm_ops = {
    .open =  simple_vma_open,
    .close = simple_vma_close,
};

void simple_vma_open(struct vm_area_struct *vma)
{
    size_t len = vma->vm_end - vma->vm_start;
    pr_debug("vma_open_",
         vma->vm_start, vma->vm_end, len,
         vma->vm_pgoff, vma->vm_pgoff << PAGE_SHIFT,
         vma->vm_ops);
}

void simple_vma_close(struct vm_area_struct *vma)
{
    pr_debug("vma_close_",
         vma->vm_start, vma->vm_end, len,
         vma->vm_pgoff, vma->vm_pgoff << PAGE_SHIFT,
         vma->vm_ops);
}

Test: Users call mmap.

fd = open("/dev/simple0", _);
mmap(NULL, _, fd, _);
     ┌──────────────┐                                  
     │     cdev     │                                  
     ├──────────────┤                                  
/dev/simple0 │                                  
     │ major=237
     │ minor=0
     └─────┬────────┘                                  
           │cdev_init                                  


   ┌─────────────────┐                                 
   │ file_operations │                                 
   ├─────────────────│                                 
┌──┼──── .mmap       │                                 
│  └─────────────────┘                                 
│                             ┌────────────────────┐   
│                             │    kernel module   │   
│ ┌─────────────────────┐     │────────────────────│   
│ │    kernel module    │     │ simple_vma_open()
│ │─────────────────────┤     │                    │◄─┐
└─► simple_remap_mmap() │     │ simple_vma_close() │  │
  └─────────┬───────────┘     └────────────────────┘  │
            │                                         │
            ▼                                         │
    ┌────────────────┐      ┌──────────────────────┐  │
    │ vm_area_struct │      │ vm_operations_struct │  │
    │────────────────│      │──────────────────────│  │
.vm_ops ─────┼─────►│       .open          ┼──┘
    └────────────────┘      │       .close
                            └──────────────────────┘   

References