Sunteți pe pagina 1din 44

Character Device Drivers

CSIE, NCKU
The information on the slides are from Linux Device Drivers, Third Edition, by Jonathan Corbet, Alessandro Rubini, and Greg Kroah-Hartman. Copyright 2005 OReilly Media, Inc., 0-596-00590-3.

The Scull Char Driver


We present code fragments of a char device driver: scull
acts on a memory area, not traditional IO devices
Hardware independent Portable
Can be a template

But, only shows the kernel interface


The device interface is not shown

The Scull Char Driver


Four devices implemented by scull
scull0~3 each device
Contains a global and persistent memory area Global
Data of an area can be shared by multiple users

Persistent
Data remains even when the device is closed

Major and Minor Numbers


Char devices are accessed through device files in the filesystem
Device files, Special files, or Nodes Located in the /dev directory

Char devices

Major Minor

Device name

Major and Minor Numbers


Why we need major & minor numbers?
For identifying a device
Major: the device type Minor: the device number in that type

Traditionally, each major number is managed by a driver


kernel seldom handles minor numbers

But Linux allows multiple drivers share the same major number

Internal Representation of Device Numbers


dev_t is used to hold a device number
Major (12bits) , minor (20bits) for kernel 2.6.0 The real representation can be changed.So,
Use the following macros
MAJOR(dev_t dev); MINOR(dev_t dev); MKDEV(int major, int minor);

Allocating and Freeing Device Numbers


Allocating a range of device numbers to manage
If you know the major number
register_chrdev_region Traditionally, major numbers are statically assigned You can pick an unused number in Documentation/devices.txt
Major number conflict may happen when you deploy your driver

If you want the kernel to give you a major number dynamically


alloc_chrdev_region

Allocating and Freeing Device Numbers


int register_chrdev_region(dev_t first, unsigned int count, char *name);
first: the first dev_t
Include major + first minor

count: total number of contiguous device numbers you are requesting name: the name of the device

Allocating and Freeing Device Numbers


int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
dev: output parameter
The dev_t indicating the (major, first minor) is setup when the function successfully returns
Check /proc/devices or sysfs after the invocation of the above two functions

An Example of /proc/devices
Character devices: 1 mem 2 pty 3 ttyp 4 ttyS 6 lp 7 vcs 10 misc 13 input 14 sound 21 sg 180 usb Block devices: 2 fd 8 sd 11 sr 65 sd 66 sd

Allocating and Freeing Device Numbers


Free the device numbers
void unregister_chrdev_region(dev_t first, unsigned int count);

Dynamic Allocation of Major Numbers


More flexible, no conflict However, the device files can not be created in advance
Create the device files after insmod
Creating ${device}0-3 after you got the $major

Where Are We?


Now, the driver have some device numbers And, we have device files
mknod Users can access these files

But, how these accesses finally go to the driver ?

Some Important Data Structures


file_operations file inode

File Operations
Let the accesses go to the driver
Why? .. because device FILE

A collection of function pointers


read, write, Each driver should implement this set of functions (some of them may be NULL)

Each open file is associated with its own set of functions


File: object, file operation: method

File Operations: An Example


ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
corresponds to the read() system call __user
user-space address cannot be directly dereferenced

File Operations of the scull Driver


struct file_operations scull_fops = {
.owner = THIS_MODULE, .llseek = scull_llseek, .read = scull_read, .write = scull_write, .ioctl = scull_ioctl, .open = scull_open, .release = scull_release,

The syntax is more portable across changes in the definitions of the structures

};
Others functions are not implemented by the scull driver Handled by the kernel default handler

The file Structure


Represents an open instance of a file
not specific to device drivers created by the kernel on open released when the last user close the file

Important fields
mode_t f_mode
read/write permission

The file Structure


Important fields
loff_t f_pos
current reading or writing position

unsigned int f_flags


E.g., O_RDONLY, O_NONBLOCK, O_SYNC A driver should check the O_NONBLOCK flag to see if nonblocking operation has been requested

The file Structure


Important fields
struct file_operations *f_op
operations associated with the file assign the operations during open you can change the file operations
E.g., you can assign a different set of file operations for each minor number during the open method

void *private_data
For your private use Usually be used to preserve state information across system calls

The file Structure


Important fields
struct dentry *f_dentry
The directory entry (dentry) structure A directory is a file that records a set of directory entries Usually be used to access inode
filp->f_dentry->d_inode Dentry format: i_no 100 2037 80
some fields (name_len,)

filp is a ptr to struct file

file/sudir name file1 subdir1 firefox

some fields (5,) some fields (7,) some fields (7,)

The inode Structure


Each file has an inode
Record file information
Data blocks, timing information, size.

If a file is opened by two users


Two file structures and one inode structure

How to find the file /usr/local/bin/foo ? Important fields


dev_t i_rdev
the actual device number ( for a device file )

struct cdev *i_cdev


pointer to the cdev, which represents a char device

Char Device Registration


Allocating & Initializing cdev
Method 1
struct cdev *my_cdev = cdev_alloc( ); my_cdev->ops = &my_fops;

Method 2
void cdev_init(struct cdev *cdev, struct file_operations *fops);

In either way,
my_cdev->owner = THIS_MODULE;

Char Device Registration


Registering to the kernel
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
num : first device number count: number of device numbers for the device
Usually, 1

The registration may fail should check it Once the registration succeeds, kernel will call your operations prepare for that Remove a char device
void cdev_del(struct cdev *dev);

Device Registration in scull


scull represents each device with a structure of type struct scull_dev
struct scull_dev { struct scull_qset *data; int quantum; int qset; unsigned long size; unsigned int access_key; struct semaphore sem; struct cdev cdev; }; /* Pointer to first quantum set */ /* the current quantum size */ /* the current array size */ /* amount of data stored here */ /* used by sculluid and scullpriv */ /* mutual exclusion semaphore */ /* Char device structure */

Device Registration in scull


static void scull_setup_cdev(struct scull_dev *dev, int index) { int err, devno = MKDEV(scull_major, scull_minor + index); cdev_init(&dev->cdev, &scull_fops); //init the cdev dev->cdev.owner = THIS_MODULE; dev->cdev.ops = &scull_fops; err = cdev_add (&dev->cdev, devno, 1); // register to kernel /* Fail gracefully if need be */ if (err) printk(KERN_NOTICE "Error %d adding scull%d", err, index); }
The caller : for (i = 0; i < scull_nr_devs; i++) { .. scull_setup_cdev(&scull_devices[i], i); }

The Older Way


Registration
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
registers minor numbers 0255 for the given major number
sets up a default cdev structure for each minor number

Unregistration
int unregister_chrdev(unsigned int major, const char *name);

Open Method
Initialization and preparation for later operations Usually perform the following Jobs
Check for device-specific errors Initialize the device Update the f_op pointer, if necessary Allocate and fill any data structure to be put in filp->private_data

Open Method
Which scull device is being opened?
int (*open)(struct inode *inode, struct file *filp);
Get the cdev
inode->i_cdev

Get the scull_dev


dev = container_of( inode->i_cdev, struct scull_dev, cdev);

Set the private area to point to the scull dev


filp->private_data = dev;

scull_open()

Introduced later

The release Method


Usually perform the following tasks
Deallocate anything that open allocated in filp-> private_data Shut down the device on last close
int scull_release(struct inode *inode, struct file *filp) { return 0; } Scull has no hardware to shut down

Memory Usage of the scull Driver


In scull, the device is memory
Variable sized Dynamically expanded

Use kmalloc() and kfree()


void *kmalloc(size_t size, int flags);
Flags = = GFP_KERNEL

void kfree(void *ptr);

Memory Usage of the scull Driver


Each device is a linked list of pointers
Each pointer points to a scull_qset structure

scull_dev.qset

scull_dev.quantum

Scull_trim()
int scull_trim(struct scull_dev *dev) { struct scull_qset *next, *dptr; int qset = dev->qset; /* "dev" is not-null */ int i; for (dptr = dev->data; dptr; dptr = next) { /* all the list items */ if (dptr->data) { for (i = 0; i < qset; i++) kfree(dptr->data[i]); // free the quantums in a qset kfree(dptr->data); // free qsets data dptr->data = NULL; } next = dptr->next; kfree(dptr); // free the qset data structure } dev->size = 0; dev->quantum = scull_quantum; dev->qset = scull_qset; dev->data = NULL; return 0;}

The read and write Methods


ssize_t read(struct file *filp, char __user *buff, size_t count, loff_t *offp); ssize_t write(struct file *filp, const char __user *buff, size_t count, loff_t *offp);

The buff argument is user-space pointer


May not be directly accessible by kernel May be paged out
Oops on directly access

May be a wrong/invalid pointer


Kernel does not trust the users

Accessing User Space Data


Transferring data with user space
unsigned long copy_to_user(void __user *to, const void *from, unsigned long count); unsigned long copy_from_user(void *to, const void __user *from, unsigned long count);

The above functions


check whether the user space pointer is valid
Returns number of bytes that could NOT be copied

perform data transfer may sleep


For paging in user pages

Accessing User Space Data


If you are sure the user addresses are fine
Use __copy_to_user()/ __copy_from_user()
No checks, faster

Be careful
Kernel crashes Security holes

Coming Back to The read and write Methods

The Read Method


The return value R of read
R = count ( > 0 )
The specified amount of data is read

0 < R < count


Partial of the specified amount of data is read

R=0
EOF

R<0
Error

Scull_read()

// EOF, return 0

Scull_read()

// f_pos should reflect the current RW head

Scull_write()
ssize_t scull_write (struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { struct scull_dev *dev = filp->private_data; struct scull_qset *dptr; int quantum = dev->quantum, qset = dev->qset; int itemsize = quantum * qset; int item, s_pos, q_pos, rest; ssize_t retval = -ENOMEM; /* value used in "goto out" statements */ find listitem, qset index and offset in the quantum skipped dptr = scull_follow(dev, item); if (dptr = = NULL) goto out; if (!dptr->data) { dptr->data = kmalloc (qset * sizeof(char *), GFP_KERNEL); if (!dptr->data) goto out; // no free memory!!! memset (dptr->data, 0, qset * sizeof(char *)); }

Scull_write()
if (!dptr->data[s_pos]) { dptr->data[s_pos] = kmalloc (quantum, GFP_KERNEL); //allocate the quantum if (!dptr->data[s_pos]) goto out; } /* write only up to the end of this quantum */ if (count > quantum - q_pos) count = quantum - q_pos; if (copy_from_user (dptr->data[s_pos]+q_pos, buf, count)) { retval = -EFAULT; goto out; } *f_pos += count; retval = count; // f_pos should reflect the current RW head /* update the size */ if (dev->size < *f_pos) dev->size = *f_pos; out: up(&dev->sem); return retval; }

Scatter-Gather Read/Write
Read/write data from/to multiple buffers in a single operation
readv and writev

iovec structure
Used to specify the address range of a buffer

An array of iovec structures is passed to readv() or writev() A driver can implement readv() and writev() methods if it benefits from scatter-gather IO

S-ar putea să vă placă și