What is a Driver?

Device Abstraction

  • OS objective: Abstract away the differences in the hardware from the applications
  • Each device has a software called a driver, that knows about the details of the device hardware
  • These drivers provide a consistent interface towards the application

Device Abstraction in Linux

  • Similarity between accessing I/O devices and acessing files
  • Data from I/O devices can be treated as a stream of bytes, just like files.
  • In Unix, everything is a file!

Device Files

  • Files can be broadly classified as plain files, directories and special or device file
  • Physical devices are accessed through device files
  • Device files are located in /dev

Example Device Files

/dev/hda, /dev/hdb - IDE Hardisk, CDROM
/dev/sda, /dev/sdb - SCSCI or SATA Hardisk
/dev/fd0           - Floppy Drive
/dev/audio         - Sound Card
/dev/input/mice    - Mouse
/dev/mem           - RAM

Working with Device Files

  • Reading and writing to the device files, will result in reading and writing to the devices
    # cat /dev/input/mice
  • Moving the mouse, causes wierd characters getting dumped on the screen
  • Correlation between the mouse movements and the data from the device file

Mouse Event Format

  • For each mouse event 3 bytes are produced.
  • First byte contains bits to indicate which of the 3 mouse buttons was pressed.
    Bit 0 - Left Button
    Bit 1 - Right Button
    Bit 2 - Middle Button

Python Code

fp = open("/dev/input/mice", "rb");

while True:
    buf = fp.read(3)
    if buf[0] & 0x01:
        print("Left Button Pressed")
    if buf[0] & 0x02:
        print("Right Button Pressed")
    if buf[0] & 0x04:
        print("Middle Button Pressed")

fp.close()

How Device Files Work

  • How does the operating system know that mouse events should be reported when /dev/input/mice is read?
  • ls on device files and ordinary files.
    [~]$ ls -alh /dev/input/mice /dev/audio
    crw-rw---- 1 root audio 14,  4 2007-08-21 10:02 /dev/audio
    crw-rw---- 1 root root  13, 63 2007-08-21 10:01 /dev/input/mice
    
    [~]$ ls -alh *.pdf
    -rw-r--r-- 1 vijay vijay  323K 2007-07-28 21:40 lcd-datasheet.pdf
    -rw-r--r-- 1 vijay vijay 1023K 2007-08-19 18:05 radmind-solaris.pdf
    -rw-r--r-- 1 vijay vijay  3.2M 2007-08-11 09:40 thinkpad.pdf

Kernel Driver Model

figures/device-file-driver.png

More fun with device files

Accessing the Audio Device

  • Device file: /dev/audio
  • Format: 1-channel, 8-bit, mu-law, sampling rate of 8kHz
    [~]$ cat mysound.au > /dev/audio
  • Creating the file from .mp3 file
[~]$ sox ~/song.mp3 -b 8 -e mu-law -c 1 -r 8000 song.au
  • -b 8 specifies 8-bit, -u specifies mu-law, -c specifies mono (single-channel), -r 8000 specifies 8kHz sampling rate.

Accessing the Hard Disk

  • Hard disk is usually accessed through the file system
  • Raw access the hard disk through the device files /dev/hda, /dev/hdb, etc
  • Sectors in the hard disk appear to be layed out sequentially when accessed through /dev/hda

Printing the Partition Table

  • Each sector is of 512 bytes.
  • The first sector of the hard disk is called the MBR.
  • The MBR contains a part of the boot loader, and the partition table.
  • The first 446 bytes contains the boot loader code.
  • The next 64 bytes contains the partition table.
  • The last 2 bytes contain magic nos. 55, AA.

Printing the Partition Table (Contd.)

  • The partition table has four records, each of size 16 bytes.
  • Each record has the following format.
figures/part-table.png

Code

import struct
from math import ceil

KB = 1000
MB = KB * 1000
GB = MB * 1000

def human_readable(size):
    if size > GB:
        return "{0:.1f} GB".format(ceil(size / GB))
    elif size > MB:
        return "{0:.1f} MB".format(ceil(size / MB))
    elif size > KB:
        return "{0:.1f} KB".format(ceil(size / KB))
    else:
        return "{0} bytes".format(size)

def print_parts(device):
    fp = open(device, "rb")
    fp.read(446)

    for i in range(4):
        pt_entry = fp.read(16)
        active, ptype, start, size = struct.unpack("BxxxBxxxII", pt_entry)

        if size == 0:
            continue

        start = human_readable(start * 512)
        size = human_readable(size * 512)
        print("{0} {1:10} {2:10}".format(i+1, start, size))

if __name__ == "__main__":
    print_parts("/dev/sda")

Controlling Devices

Introduction

  • Reading and writing to device files translate really well to device I/O
  • Limits to the abstraction
  • Not possible to model all device operations as reading and writing to files
  • Example: Control and configuration of the device

IOCTL

  • Linux has a separate system call called ioctl() to deal with this.
    ioctl(fd, request, [arg])

CDROM Tray Eject

  • Device File: /dev/sr0
  • Header file: linux/cdrom.h
  • IOCTL: CDROMEJECT

Code

import fcntl
import os
import ioctl

fd = os.open("/dev/sr0", os.O_RDONLY | os.O_NONBLOCK)
fp = os.fdopen(fd)
fcntl.ioctl(fp, ioctl.CDROMEJECT)
fp.close()
  • If O_NONBLOCK is not specified then opening the device file will fail, if there is no CD-ROM in the drive
  • Programmatic closing of the CD-ROM drive, using CDROMCLOSETRAY

Reading RTC

  • RTC (Real Time Clock) maintains the system time, even when the system is powered down
  • The RTC is backed up by a battery, and when the system powers up it reads the current time from the RTC
  • Date/time is read from the RTC using ioctls

Reading RTC (Contd.)

  • Device file: /dev/rtc0
  • Header file: linux/rtc.h
  • IOCTL: RTC_RD_TIME
  • Argument is a buffer where driver fills in the current time

Code

import struct
import fcntl
import ioctl

# struct rtc_time {
#       int tm_sec;
#       int tm_min;
#       int tm_hour;
#       int tm_mday;
#       int tm_mon;
#       int tm_year;
#       int tm_wday;
#       int tm_yday;
#       int tm_isdst;
# };

fmt = "iiiiiiiii"
time = bytearray(struct.calcsize(fmt))

fp = open("/dev/rtc0")
fcntl.ioctl(fp, ioctl.RTC_RD_TIME, time)
sec, min, hour, mday, mon, year, wday, yday, isdst = struct.unpack(fmt, time)
fp.close()

print("Time: {0:02}:{1:02}:{2:02}".format(hour, min, sec))
print("Date: {0}/{1}/{2}".format(mday, mon+1, year+1900))

Volume Control

  • Device file: /dev/mixer
  • Header file: linux/soundcard.h.
  • IOCTL: SOUND_MIXER_READ_VOLUME, SOUND_MIXER_WRITE_VOLUME
  • Argument is a buffer containing an integer
    • byte 0 represents the left volume
    • byte 1 represents the right volume

Code

import struct
import fcntl
import ioctl

def split_lr(volume):
    left = volume & 0xff;
    right = (volume & 0xff00) >> 8;
    return (left, right)

def join_lr(left, right):
    return left | (right << 8);


volume_bytes = bytearray(struct.calcsize("i"))
fp = open("/dev/mixer")

fcntl.ioctl(fp, ioctl.SOUND_MIXER_READ_VOLUME, volume_bytes);
volume, = struct.unpack("i", volume_bytes)

left, right = split_lr(volume)
left += 20;
right += 20;
volume = join_lr(left, right)

volume_bytes = struct.pack("i", volume)
fcntl.ioctl(fp, ioctl.SOUND_MIXER_WRITE_VOLUME, volume_bytes);

fp.close()

Resource

File: ioctl.py

import ioctl_util

CDROMEJECT = 0x5309

RTC_RD_TIME = ioctl_util._IOR(ord("p"), 0x09, "iiiiiiiii")

SOUND_MIXER_READ_VOLUME = ioctl_util._IOR(ord("M"), 0, "i")
SOUND_MIXER_WRITE_VOLUME = ioctl_util._IOWR(ord("M"), 0, "i")

File: ioctl_util.py

"""
Source: http://code.activestate.com/recipes/578225-linux-ioctl-numbers-in-python/

Linux ioctl numbers made easy

size can be an integer or format string compatible with struct module

for example include/linux/watchdog.h:

#define WATCHDOG_IOCTL_BASE     'W'

struct watchdog_info {
        __u32 options;          /* Options the card/driver supports */
        __u32 firmware_version; /* Firmware version of the card */
        __u8  identity[32];     /* Identity of the board */
};

#define WDIOC_GETSUPPORT  _IOR(WATCHDOG_IOCTL_BASE, 0, struct watchdog_info)

becomes:

WDIOC_GETSUPPORT = _IOR(ord('W'), 0, "=II32s")


"""
import struct
# constant for linux portability
_IOC_NRBITS = 8
_IOC_TYPEBITS = 8

# architecture specific
_IOC_SIZEBITS = 14
_IOC_DIRBITS = 2

_IOC_NRSHIFT = 0
_IOC_TYPESHIFT = _IOC_NRSHIFT + _IOC_NRBITS
_IOC_SIZESHIFT = _IOC_TYPESHIFT + _IOC_TYPEBITS
_IOC_DIRSHIFT = _IOC_SIZESHIFT + _IOC_SIZEBITS

_IOC_NONE = 0
_IOC_WRITE = 1
_IOC_READ = 2


def _IOC(dir, type, nr, size):
    if isinstance(size, str):
        size = struct.calcsize(size)
    return dir  << _IOC_DIRSHIFT  | \
           type << _IOC_TYPESHIFT | \
           nr   << _IOC_NRSHIFT   | \
           size << _IOC_SIZESHIFT


def _IO(type, nr): return _IOC(_IOC_NONE, type, nr, 0)
def _IOR(type, nr, size): return _IOC(_IOC_READ, type, nr, size)
def _IOW(type, nr, size): return _IOC(_IOC_WRITE, type, nr, size)
def _IOWR(type, nr, size): return _IOC(_IOC_READ | _IOC_WRITE, type, nr, size)

Further Reading