Transcript PPTX Slides

Dr A Sahu
Dept of Comp Sc & Engg.
IIT Guwahati
• Writing/Registering to /proc
• Character Device Driver
– Characteristics and functionality
– Basic IO functions
– Examples (ADC, DAC, Printer, Test data generator)
• Create 30S Delay SR using CMOS Data
– Real Time CMOS Clock
– 30S Delay
Linux allows us to write our own
installable kernel modules
and add them to a running system
application
module
call
call
ret
standard
“runtime”
libraries
user space
syscall
ret
Operating System
kernel
sysret
kernel space
• We can write code to implement our own
‘pseudo’ files, located in ‘/proc’ directory
• We do this by adding a ‘payload’ function to a
Linux Kernel Module, and by including calls to
special kernel-functions within our moduleinit and our module-exit routines
• These special kernel-functions serve to
‘register’, and ‘unregister’, our payload
• Your module-initialization function should
‘register’ the module’s ‘get_info()’ function:
create_proc_info_entry( modname, 0, NULL, get_info );
the name for your proc file
the file-access attributes (0=default)
directory where file will reside (NULL=default)
function-pointer to your module’s ‘callback’ routine
• Your cleanup should do an ‘unregister’:
remove_proc_entry( modname, NULL );
file’s name
directory
• A device driver is a kernel module responsible for
managing low-level I/O operations for a particular
hardware device.
• VFS
• Block drivers: Physically addressable media (disks)
• All other devices are considered character devices
• Line printer, ADC, DAC, …
System Call Interface
VFS
Socket
File System
Network
Protocol
Buffer
Cache
Block
Character
Device Driver Device Driver
Hardware
Network
Device Driver
• All drivers are required to implement the
loadable module entry points
– init ()
– finalize ()
– info ()
// (load)
//unload
// Gather information of device
• Drivers should allocate and initialize any global
resources in init() and release their resources
in finalize().
• Device Properties
– can’t be randomly accessed
– can’t be buffered
– usually are slow devices
• Export Interface
– file_operations
• Data Flow in read/write
Device-driver LKM layout
module’s ‘payload’
is a collection of
callback-functions
having prescribed
prototypes
function
function
function
fops
...
the usual pair of
module-administration
functions
AND
a ‘package’ of
function-pointers
init
registers the ‘fops’
exit
unregisters the ‘fops’
• Character device drivers normally perform I/O
in a byte stream.
• Examples of devices using character drivers
include tape drives and serial ports.
• Character device drivers can also provide
additional interfaces not present in block
drivers,
– I/O control (ioctl) commands
– memory mapping
– device polling.
Function
Meanings
Lseek
to change the current read/write position in a file
Read
to retrieve data from the device
Write
Sends data to the devic
Readdir
NULL for device files; reading dirs & only useful to FS.
Poll
back end of two system calls, poll and select, used to inquire a
device is readable or writable or in some special state
Ioctl
to issue device-specific commands
Mmap
to request a mapping of device mem to a process's address space
Open
first operation performed on the device file
Flush
..
Lock
..
Release
..
Fsync
..
Fasync
..
• Block drivers are required to support strategy,
while character drivers can choose to
implement whatever mix of
– read, write, ioctl, mmap, or devmap
– These entry points as appropriate for the type of
device.
• Character drivers can also support a polling
interface through
– ch_poll
– as well as asynchronous I/O through aread and
awrite.
• To appreciate the considerations that have
motivated the over-all Linux driver’s design
requires an understanding of how normal
application-programs get their access to
services that the operating system offers
• This access is indirect – through specially
protected interfaces (i.e., system calls) –
usually implemented as ‘library’ functions
int open( char *pathname, int flags, … );
int read( int fd, void *buf, size_t count );
int write( int fd, void *buf, size_t count );
int lseek( int fd, loff_t offset, int whence );
int close( int fd );
(and other less-often-used file-I/O functions)
• UNIX systems treat hardware-devices as
special files, so that familiar functions can be
used by application programmers to access
devices (e.g., open, read, close)
• But a System Administrator has to create
these device-files (in the ‘/dev’ directory)
# mknod /dev/cmos c 70 0
• Or alternatively (as we’ve seen), an LKM could
create these necessary device-files
#include <unistd.h>
int close( int fd );
• Breaks link between file and file-descriptor
• Returns 0 on success, or -1 if an error
#include <unistd.h>
int write( int fd,
void *buf,
size_t count);
•
•
•
•
•
Attempts to write up to ‘count’ bytes
Bytes are taken from ‘buf’ memory-buffer
Returns the number of bytes written
Or returns -1 if some error occurred
Return-value 0 means no data was written
#include <unistd.h>
int read( int fd,
void *buf,
size_t count );
•
•
•
•
•
Attempts to read up to ‘count’ bytes
Bytes are placed in ‘buf’ memory-buffer
Returns the number of bytes read
Or returns -1 if some error occurred
Return-value 0 means ‘end-of-file’
• These functions have (as a “side-effect”) the
advancement of a file-pointer variable
• They return a negative function-value of -1 if
an error occurs, indicating that no actual data
could be transferred; otherwise, they return
the number of bytes read or written
• The ‘read()’ function normally does not return
0, unless ‘end-of-file’ is reached
#include <unistd.h>
off_t lseek(int fd,
off_t offset,
int whence );
enum { SEEK_SET, SEEK_CUR, SEEK_END };
• Modifies the file-pointer variable, based on
the value of whence
• Returns the new value of the file-pointer (or
returns -1 if any error occurred)
• For normal files, your application can find out
how many bytes belong to a file using the
‘lseek()’ function:
int filesize=lseek(fd,0,SEEK_END);
• But afterward you need to ‘rewind’ the file if you
want to read its data:
lseek( fd, 0, SEEK_SET );
• LKM implements a simple character-mode
device-driver
• For RT Clock's non-volatile memory(128 bytes).
• It should fully supports read() and lseek() access,
• Its support restricted access to
– write(), only clock-and-calendar locations
– The non-configuration data.
root# mknod /dev/cmos c 70 0
• We implement three callback functions:
– llseek:
– read:
– write:
// sets file-pointer’s position
// inputs a byte from CMOS
// outputs a byte to CMOS
• We omit other callback functions, such as:
– open:
– release:
// we leave this function-pointer NULL
// we leave this function-pointer NULL
• The kernel has its own ‘default’ implementation
for any function with NULL as its pointer-value
Device-driver LKM layout
module’s ‘payload’
is a collection of
callback-functions
having prescribed
prototypes
function
function
function
fops
...
the usual pair of
module-administration
functions
AND
a ‘package’ of
function-pointers
init
registers the ‘fops’
exit
unregisters the ‘fops’
• The GNU C-compiler supports a syntax for
‘struct’ field-initializations that lets you give
your field-values in any convenient order:
struct file_operations my_fops
llseek:
write:
read:
};
={
my_llseek,
my_write,
my_read,
#include <linux/module.h>
#include <linux/fs.h>
register_chrdev()
#include <asm/uaccess.h>
get_user()
#include <asm/io.h>
// for printk()
// for
// for put_user(),
// for inb(), outb()
char modname[] = "cmosram";
//
module
char devname[] = "cmos";
//
device's file
int
my_major = 70;
//
driver
int
cmos_size = 128; // total
memory
name of this kernel
name for the
major ID-number for
bytes of cmos
ssize_t my_read( struct file *file, char *buf, size_t
len, loff_t *pos ) {
unsigned char datum;
if ( *pos >= cmos_size ) return 0;
outb( *pos, 0x70 ); datum = inb( 0x71 );
if ( put_user( datum, buf ) ) return –EFAULT;
*pos += 1;
return 1;
}
ssize_t my_write( struct file *file, const char *buf,
size_t len, loff_t *pos ) {
unsigned char
datum;
if ( *pos >= cmos_size ) return 0;
if ( *pos > write_max ) return –EPERM;
if ( get_user( datum, buf ) ) return –EFAULT;
outb( *pos, 0x70 ); outb( datum, 0x71 );
*pos += 1; return 1;
}
loff_t my_llseek( struct file *file, loff_t pos, int
whence )
{
loff_t
newpos = -1;
switch ( whence ) {
case 0:
newpos = pos; break;
// SEEK_SET
case 1: newpos = file->f_pos + pos; break;
// SEEK_CUR
case 2: newpos = cmos_size + pos; break; //
SEEK_END
}
if (( newpos < 0 )||( newpos > cmos_size )) return
–EINVAL;
file->f_pos = newpos;
return
newpos;
struct file_operations my_fops = {
owner:
THIS_MODULE,
llseek:
my_llseek,
write:
my_write,
read:
my_read,
};
static int __init my_init( void ) {
printk( "<1>\nInstalling \'%s\' module ", devname
);
printk( "(major=%d) \n", my_major );
return
register_chrdev( my_major, devname,
&my_fops );
}
static void __exit my_exit(void ) {
unregister_chrdev( my_major, devname );
printk( "<1>Removing \'%s\' module\n", devname );
}
#include <stdio.h>
// for printf(), perror()
#include <fcntl.h>
// for open()
#include <unistd.h>
// for read()
int main( int argc, char **argv ) {
int status = 0; int fd = open( "/dev/cmos", O_RDONLY );
if ( fd < 0 ) { perror( "/dev/cmos" ); return -1; }
// Repeatedly reads Status_Reg until its bit has 'flipped‘ 30 times
for (int i = 0; i < 30; i++) {
do { // do busy-wait until UpdateInProgress is 'true'
lseek( fd, 10, SEEK_SET ); read( fd, &status, 1 );
} while ( ( status & 0x80 ) == 0x00 );
do{ // do busy-wait until UpdateInProgress is 'false’
lseek( fd, 10, SEEK_SET );
read( fd, &status, 1 );
} while ( ( status & 0x80 ) == 0x80 );
printf( " %d Second Elapsed\n", i+1 );
}
}
•
•
•
•
•
•
•
Download mmake.cpp and cmosram.c
Course Website, tested on Fedora 12
Compile mmake.cpp using ‘g++’
Then compile cmosram.c using ‘make’
Install ‘cmos.ko’ (and see printk-message)
See $cat /proc/cmos
Compile ThirtySec.cpp and execute..