How a character-mode Linux device driver can be useful in viewing a ‘net_device’ structure
• We wrote a Loadable Kernel Module that creates a pseudo-file allowing users to see some information about the kernel’s data struct net_device lo struct net_device eth0 struct net_device eth1
some header-files some global data netdevs.c
#include <linux/module.h>
#include <linux/proc_fs.h> char modname[ ] = “netdevs”;
… my_get_info() this module’s
‘payload’ function required module administration functions module_init() module_exit()
MODULE_LICENSE(“GPL”);
standard runtime library application program
user-space
(restricted privileges) kernel-space
(unrestricted privileges) operating system kernel
LINUX open read write
(etc)
‘cat’ netdevs.ko
installable module privilege barrier
• There is another kind of LKM, written to control the system’s hardware devices rather than merely to expose information
• Its code-structure will depend on the type of hardware device it is intended to control
– ‘character’ devices
– ‘block’ devices
– ‘network interface’ devices
• In order to write the software that controls a particular device, the programmer needs to know details about its capabilities and about its mechanisms for being controlled
• This information is found in programming manuals, produced by the manufacturer
• These manuals may or not be available to the general public (often are ‘proprietary’)
• If a particular device’s operations are very simple to understand, we may not need to consult the manufacturer’s documentation
(just use ‘common sense’ and guesswork)
• EXAMPLE: The computer system’s main memory offers us an easy-to-understand hardware component for which we can directly write a device-driver module
• Two benefits of having a device-driver for the computer’s physical memory are:
– We can directly look at kernel data-structures using ‘unprivileged’ application-programs
– We get to see the general code-structure for
Linux device-drivers in the simplest of cases
• Our previous ‘netdevs.c’ module tells us where the ‘struct net_device’ objects are located in our system’s physical memory
• So we can use ‘fileview’ to inspect these kernel datastructures once we’ve loaded our ‘dram.ko’ device-driver into the kernel
Timeout for an in-class demonstration
some header-files some global data required module administration functions dram.c
#include <linux/module.h>
#include <linux/highmem.h>
… char modname[ ] = “dram”; int my_major = 85;
… my_read() my_llseek() my_fops module_init() module_exit()
MODULE_LICENSE(“GPL”); this module’s
‘payload’
(its ‘method’ functions and its
‘file_operations’ structure)
• The Linux kernel provides quite a few aids to the authors of device-driver code:
– ‘register_chrdev()’ and ‘unregister_chrdev()’
– ‘copy_to_user()’ and ‘copy_from_user()’
– ‘kmap()’ and ‘kunmap()’
• The kernel also exports some of its ‘global variables’ (which drivers can reference):
– ‘num_physpages’ and ‘mem_map[ ]’
= persistent mapping
= transient mappings
HMA kernel space
896-MB physical RAM
There is more physical RAM in our classroom’s systems than can be ‘mapped’ into the available address-range for kernel virtual addresses user space
CPU’s virtual address-space
• The ‘ kmap ()’ helper-function allows your driver to create a temporary mapping for any one 4KB ‘page’ of physical memory to some unused virtual address in kernelspace, then later ‘ kunmap ()’ lets your driver discard that mapping when it’s no longer needed (so there will be available that kernel-address for later reuse)
• The kernel creates an array of structures, named ‘ mem_map [ ]’, whose entries hold detailed information about how each 4KB page of physical RAM is now being used
• The global variable named ‘ phys_mem ’ stores the total number of array-entries, and hence can be used by your driver to determine the amount of installed RAM
void * kmap ( struct page *page_ptr );
This function accepts a pointer to an entry of type ‘struct page’ in the kernel’s ‘mem_map[ ]’ array, and returns a kernel address where that page of physical RAM has been temporarily ‘mapped’ void kunmap ( void *virt_addr );
This function accepts an address where the kernel temporarily has mapped a page of physical RAM and it deletes that mapping, thus freeing the address for reuse later when the kernel is asked to setup a different temporary mapping of physical RAM into kernel-space
• It has to support the traditional stream-ofbytes paradigm, so a ‘sanity check’ will be needed for the caller’s argument-values ssize_t my_read( struct file *file, char *buf, size_t count, loff_t *pos ); number of bytes that caller wants to read the current position of the file-pointer
// There’s nothing to be ‘read’ beyond the end of physical RAM if ( *pos >= dram_size ) return 0;
Physical RAM dram_size
*pos
*pos
• Our driver has to accommodate the CPU’s
‘page-granular’ memory-architecture, and the ‘kmap()’ function’s ability to map onepage-at-a-time int page_number = *pos / PAGE_SIZE; int page_indent = *pos % PAGE_SIZE; if ( page_indent + count > PAGE_SIZE ) count = PAGE_SIZE – page_indent; struct page void int
*pp = &mem_map[ page_number ];
*from = kmap( pp ) + page_indent; more = copy_to_user( buf, from, count );
• It is possible that the caller did not supply a large-enough buffer for the amount of data that is supposed to be transferred
• That potential ‘buffer-overflow’ problem could be detected during execution of the
‘copy_to_user()’ helper-function, if fewer than ‘count’ bytes can be copied without triggering a ‘segmentation violation’
• The ‘copy_to_user()’ function return the number of bytes that remain to be copied
(normally this is zero: all copying got done)
• But if it’s NOT zero, the driver’s duty is to notify the user that a ‘segmentation fault’ error occurred – but AFTER ‘kunmap()’ int more = copy_to_user( buf, from, count );
// first unmap the page, then notify the user if necessary kunmap( pp ); if ( more ) return –EFAULT;
• Our ‘dram.c’ driver needs to implement its own ‘llseek()’ function, in order to allow an applicationprogram to ‘seek’ to the end of the device-file (so it will know what total amount of physical RAM is installed)
• This feature is used by our ‘fileview’ tool when a user hits the <END>-key, and to display the total size for the device-file
unsigned int dram_size; // equals PAGE_SIZE * num_physpages
}
{ loff_t my_llseek( struct file *file, loff_t offset, int whence ) loff_t newpos = -1; switch ( whence )
{ case 0: newpos = offset; break;
}
// SEEK_SET case 1: newpos = file->f_pos + offset; break; // SEEK_CUR case 2: newpos = dram_size + offset; break; // SEEK_END if (( newpos < 0 )||( newpos > dram_size )) return –EINVAL; file->f_pos = newpos; return newpos;
• This application makes use of information from the ‘/proc/netdevs’ pseudo-file, plus the information that can be read from the computer’s physical memory using the capabilities implemented by our ‘dram.c’ device-driver
• It lets a user view the ‘struct net_device’ object for a specified network-interface
• This module creates a pseudo-file that can help a user to interpret the hexadecimal output produced by ‘vwnetdev’
• It shows the locations within a ‘net_device’ structure for some structure-members of particular significance for network device drivers (which we shall explore next time)
• One of the ‘struct net_device’ fields that is significant in a Linux network device driver is the ‘ get_stats ’ function-pointer field
• Modify our ‘offsets.c’ module so that the pseudo-file this module creates will include the offset for the ‘get_stats’ member
• Turn in a printout of the enhanced output
(created using our ‘ ljpages ’ printing tool); be sure your name is handwritten on it
• Take a look at our kernel’s definition for a
‘struct net_device’ object, in header-file:
</usr/src/linux/include/linux/netdevice.h> and identify three additional member-fields that you would like to show the offsets for
• Then implement the display of those three offsets (by adding code to our ‘offsets.c’)