What is a Linux Device Driver? Kevin Dankwardt, Ph.D. VP Technology Open Source Careers kdankwardt@oscareers.com
What does a driver do? Provides a more convenient interface to user-space for the hardware. Implements the appropriate metaphor - e.g., devices may appear to look like files implement the appropriate system calls: read, write, lseek,... Knows how to interact with the hardware knows about the hardware's registers knows about hardware timing knows about sizes, limitations, protocols etc of the device a protocol may involve writing or reading from/to particular registers accessing PCI areas doing DMA, inb/outb handling interrupts
System Calls We are covering device drivers in kernel space. System calls are functions that allow user space to communicate with kernel space. Drivers can overload system calls to provide the implementation for use with their device(s).
System Call Interface Kernel Space open method read method write method close method Character Driver User Space system call interface open read write close User Program
Current Process When a process calls a system call, we think of the process continuing to execute, but the process is now executing in kernel mode, in kernel space. A driver should be able to support multiple access to the device by different processes The macro, current, can be used for tracking processes current always points to the process running on the current processor
Device Types Character devices Block devices Network devices Device Drivers Hardware & Control Character Block Buffer Cache File Systems System Calls User Programs Kernel Space
Major/Minor Major = driver number Minor = device or options number The Linux kernel does allow drivers to share a major number. Traditionally in UNIX and in early Linux, each driver was assigned a major number and had all of the minor numbers to use however it wished. Now, commonly, a driver is assigned a sequence of minor numbers and another driver may use the same major number.
Creating Device Files Use mknod privileged The command takes: The name of the device file The device type The major and minor numbers An example to create a character device file: # mknod /dev/simplechar c 168 0
Registering Device Driver register_chrdev_region() // if you know the number you want alloc_chrdev_region() // to have kernel allocate the major number. register_chrdev() // old way register_blkdev()
Unregistering Device Driver Unregister interrupts handlers Free all memory Unregister a device driver: unregister_chrdev_region() unregister_chrdev() // old way unregister_blkdev()
File Operations Used by character drivers (and file systems). A structure of pointers to various functions (methods). The kernel will call the appropriate method when a user program issues a system call. Some structure entries can be left as NULL. The kernel will either not call any method, or it will call a default method. Generally the methods return zero (or positive) to indicate success and negative to indicate failure.
Initializing File Operations Linux drivers conventionaly use an uncommon construct to initialize the file operations structure. Example: struct file_operations fops = { };.open = my_open,.release = my_release,.read = my_read,.write = my_write,.owner = THIS_MODULE
open If NULL, open succeeds, but the driver is not notified The open method should never be NULL if the driver needs to preallocate resources or memory Should initialize any resources or structures used by the driver. Checks any additional permissions. With the owner field set to THIS_MODULE, the usage count will be incremented with each call to open().
Private data It is preferable to store device data in non global variables. Character devices can store private data in filp->private_data Network drivers can store data in dev->priv Minor device specific data can be stored in an array indexed by minor number
release Release all resources and memory used int release(inode, file) Should never be NULL if a resource or memory needs to be released. With the owner set to THIS_MODULE, usage count will be decremented.
read Retrieves data from the device ssize_t read(file, buffer, count, offset) Returns the number of bytes read Can be less than requested Should only be zero on EOF Get a negative number if read fails, e.g., get -EINVAL if making a reference to a null pointer
write Writes data to the device ssize_t write(file, buffer, count, offset) Returns the number of bytes written Can be less than requested Get a negative number if write fails, e.g., get -EINVAL if the device is missing
Network Drivers Kernel Space TCP Code Socket Code IP Code Network Driver Network Card socket connect write User Program User Space
From IGB Network Driver igb_main.c static struct pci_driver igb_driver = {.name = igb_driver_name,.id_table = igb_pci_tbl,.probe = igb_probe,.remove = devexit_p(igb_remove), #ifdef CONFIG_PM.driver.pm = &igb_pm_ops, #endif.shutdown = igb_shutdown,.err_handler = &igb_err_handler };
net_device_ops Structure Registered in igb_probe() static const struct net_device_ops igb_netdev_ops = {.ndo_open = igb_open,.ndo_stop = igb_close,.ndo_start_xmit = igb_xmit_frame,.ndo_get_stats = igb_get_stats,.ndo_set_rx_mode = igb_set_rx_mode,.ndo_set_mac_address = igb_set_mac,.ndo_change_mtu = igb_change_mtu,.ndo_do_ioctl = igb_ioctl,.ndo_tx_timeout = igb_tx_timeout,.ndo_validate_addr = eth_validate_addr,.ndo_vlan_rx_register = igb_vlan_rx_register,.ndo_vlan_rx_add_vid = igb_vlan_rx_add_vid,.ndo_vlan_rx_kill_vid = igb_vlan_rx_kill_vid,.ndo_set_vf_mac = igb_ndo_set_vf_mac,.ndo_set_vf_vlan = igb_ndo_set_vf_vlan,.ndo_set_vf_tx_rate = igb_ndo_set_vf_bw,.ndo_get_vf_config = igb_ndo_get_vf_config, #ifdef CONFIG_NET_POLL_CONTROLLER.ndo_poll_controller = igb_netpoll, #endif };
Naming Scheme The name should be: eth%d for ethernet drivers tr%d for token ring drivers The %d will be replaced by an unused identifying number: eth0 eth1...
open method Registers system resources Registers interrupt handler Activates hardware Call netif_tx_start_all_queues(netdev);
Interrupt Handler Packet Reception Passes packets up IP stack with netif_rx() Set protocol field in sk_buff skb->protocol = eth_type_trans(skb,dev); Update receive stats and dev->last_rx
get_stats method Returns network statistics number of received/transmitted packets number of received/transmitted bytes number of receive/transmit errors number of packets dropped during receive/transmit number of collisions number of multicast packets received
Block Device drivers Normally used for devices that can contain file systems Work with relatively large pieces at a time, say 4KB Provide random access
Block Drivers Kernel Space Buffer Cache File System Block Driver Disk Device open read write User Program User Space
Register Block Device Register block major number register_blkdev(unsigned int major, const char *name) unregister_blkdev(unsigned int major, const char *name) Needs corresponding device file under /dev struct gendisk representation of each disk struct gendisk *alloc_disk(int minors) void add_disk(struct gendisk *gd); Each block device has a request queue blk_init_queue(blk_default_queue(major_nr), simpleblock_request); release driver blk_cleanup_queue(blk_default_queue(major_nr))
struct block_device_operations #include <linux/fs.h> Methods: open release ioctl media_changed revalidate_disk owner
struct gendisk Allocate an instance with: gd = alloc_disk(num_of_minors_you_want) Free with del_gendisk(gd) Initialize gendisk, then call add_disk(gd) some gendisk fields: major - set to the major number first_minor minors - allows for partitions disk_name - shows up in /proc/partitions and sysfs, e.g., hda, /sys/block/hda block_device_operations struct request_queue - where kernel asks for reads and writes, initialization specifies request function to call.
request function void your_request(request_queue_t *q) Loops until no more requests call req = elv_next_request(q) to get next request check to see if it is a filesystem request, as opposed to operations like diagnostics, change modes, etc. Use blk_fs_request(req). When done call end_request()
Thanks!