Template Information

Trang

Important things in Char Driver Programming

Thứ Bảy, 4 tháng 6, 2011 / 20:57

The Internal Representation of Device Numbers:
Within the kernel, the dev_t type (defined in <linux/types.h>) is used to hold device
numbers—both the major and minor parts. As of Version 2.6.0 of the kernel, dev_t is
a 32-bit quantity with 12 bits set aside for the major number and 20 for the minor
number. Your code should, of course, never make any assumptions about the internal
organization of device numbers; it should, instead, make use of a set of macros
found in <linux/kdev_t.h>. To obtain the major or minor parts of a dev_t, use:
MAJOR(dev_t dev);
MINOR(dev_t dev);
If, instead, you have the major and minor numbers and need to turn them into a dev_t,
use:
MKDEV(int major, int minor);

Allocating and Freeing Device Numbers:
int register_chrdev_region(dev_t first, unsigned int count,
char *name);
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor,
unsigned int count, char *name);

void unregister_chrdev_region(dev_t first, unsigned int count);
The usual place to call unregister_chrdev_region would be in your module’s cleanup
function.

Here’s the code we use in scull’s source to get a major number:
if (scull_major) {
dev = MKDEV(scull_major, scull_minor);
result = register_chrdev_region(dev, scull_nr_devs, "scull");
} else {
result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs,
"scull");
scull_major = MAJOR(dev);
}
if (result < 0) {
printk(KERN_WARNING "scull: can't get major %d\n", scull_major);
return result;
}

Some Important Data Structures
File Operations:
struct module *owner
The first file_operations field is not an operation at all; it is a pointer to the
module that “owns” the structure. This field is used to prevent the module from
being unloaded while its operations are in use. Almost all the time, it is simply
initialized to THIS_MODULE, a macro defined in <linux/module.h>.
loff_t (*llseek) (struct file *, loff_t, int);
The llseek method is used to change the current read/write position in a file, and
the new position is returned as a (positive) return value. The loff_t parameter is
a “long offset” and is at least 64 bits wide even on 32-bit platforms. Errors are
signaled by a negative return value. If this function pointer is NULL, seek calls will
modify the position counter in the file structure (described in the section “The
file Structure”) in potentially unpredictable ways.
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
Used to retrieve data from the device. A null pointer in this position causes the
read system call to fail with -EINVAL (“Invalid argument”). A nonnegative return
value represents the number of bytes successfully read (the return value is a
“signed size” type, usually the native integer type for the target platform).
ssize_t (*aio_read)(struct kiocb *, char __user *, size_t, loff_t);
Initiates an asynchronous read—a read operation that might not complete
before the function returns. If this method is NULL, all operations will be processed
(synchronously) by read instead.
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
Sends data to the device. If NULL, -EINVAL is returned to the program calling the
write system call. The return value, if nonnegative, represents the number of
bytes successfully written.
ssize_t (*aio_write)(struct kiocb *, const char __user *, size_t, loff_t *);
Initiates an asynchronous write operation on the device.
int (*readdir) (struct file *, void *, filldir_t);
This field should be NULL for device files; it is used for reading directories and is
useful only for filesystems.
unsigned int (*poll) (struct file *, struct poll_table_struct *);
The poll method is the back end of three system calls: poll, epoll, and select, all of
which are used to query whether a read or write to one or more file descriptors
would block. The poll method should return a bit mask indicating whether nonblocking
reads or writes are possible, and, possibly, provide the kernel with
information that can be used to put the calling process to sleep until I/O
becomes possible. If a driver leaves its poll method NULL, the device is assumed to
be both readable and writable without blocking.
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
The ioctl system call offers a way to issue device-specific commands (such as formatting
a track of a floppy disk, which is neither reading nor writing). Additionally,
a few ioctl commands are recognized by the kernel without referring to the
fops table. If the device doesn’t provide an ioctl method, the system call returns
an error for any request that isn’t predefined (-ENOTTY, “No such ioctl for
device”).
int (*mmap) (struct file *, struct vm_area_struct *);
mmap is used to request a mapping of device memory to a process’s address
space. If this method is NULL, the mmap system call returns -ENODEV.
int (*open) (struct inode *, struct file *);
Though this is always the first operation performed on the device file, the driver
is not required to declare a corresponding method. If this entry is NULL, opening
the device always succeeds, but your driver isn’t notified.
int (*flush) (struct file *);
The flush operation is invoked when a process closes its copy of a file descriptor
for a device; it should execute (and wait for) any outstanding operations on the
device. This must not be confused with the fsync operation requested by user
programs. Currently, flush is used in very few drivers; the SCSI tape driver uses
it, for example, to ensure that all data written makes it to the tape before the
device is closed. If flush is NULL, the kernel simply ignores the user application
request.
int (*release) (struct inode *, struct file *);
This operation is invoked when the file structure is being released. Like open,
release can be NULL.*
int (*fsync) (struct file *, struct dentry *, int);
This method is the back end of the fsync system call, which a user calls to flush
any pending data. If this pointer is NULL, the system call returns -EINVAL.
int (*aio_fsync)(struct kiocb *, int);
This is the asynchronous version of the fsync method.
int (*fasync) (int, struct file *, int);
This operation is used to notify the device of a change in its FASYNC flag. Asynchronous
notification is an advanced topic and is described in Chapter 6. The
field can be NULL if the driver doesn’t support asynchronous notification.
int (*lock) (struct file *, int, struct file_lock *);
The lock method is used to implement file locking; locking is an indispensable
feature for regular files but is almost never implemented by device drivers.
ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
These methods implement scatter/gather read and write operations. Applications
occasionally need to do a single read or write operation involving multiple
memory areas; these system calls allow them to do so without forcing extra copy
operations on the data. If these function pointers are left NULL, the read and write
methods are called (perhaps more than once) instead.
ssize_t (*sendfile)(struct file *, loff_t *, size_t, read_actor_t, void *);
This method implements the read side of the sendfile system call, which moves
the data from one file descriptor to another with a minimum of copying. It is
used, for example, by a web server that needs to send the contents of a file out a
network connection. Device drivers usually leave sendfile NULL.
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *,
int);
sendpage is the other half of sendfile; it is called by the kernel to send data, one
page at a time, to the corresponding file. Device drivers do not usually implement
sendpage.
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned
long, unsigned long, unsigned long);
The purpose of this method is to find a suitable location in the process’s address
space to map in a memory segment on the underlying device. This task is normally
performed by the memory management code; this method exists to allow
drivers to enforce any alignment requirements a particular device may have.
Most drivers can leave this method NULL.
int (*check_flags)(int)
This method allows a module to check the flags passed to an fcntl(F_SETFL...)
call.
int (*dir_notify)(struct file *, unsigned long);
This method is invoked when an application uses fcntl to request directory
change notifications. It is useful only to filesystems; drivers need not implement
dir_notify.
The scull device driver implements only the most important device methods. Its
file_operations structure is initialized as follows:
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 inode Structure:
unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);

Char Device Registration:
struct cdev *my_cdev = cdev_alloc( );
my_cdev->ops = &my_fops;
void cdev_init(struct cdev *cdev, struct file_operations *fops);
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
To remove a char device from the system, call:
void cdev_del(struct cdev *dev);

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

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);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &scull_fops;
err = cdev_add (&dev->cdev, devno, 1);
/* Fail gracefully if need be */
if (err)
printk(KERN_NOTICE "Error %d adding scull%d", err, index);
}

The open Method:
The open method is provided for a driver to do any initialization in preparation for
later operations. In most drivers, open should perform the following tasks:
• Check for device-specific errors (such as device-not-ready or similar hardware
problems)
• Initialize the device if it is being opened for the first time
• Update the f_op pointer, if necessary
• Allocate and fill any data structure to be put in filp->private_data
The first order of business, however, is usually to identify which device is being
opened. Remember that the prototype for the open method is:
int (*open)(struct inode *inode, struct file *filp);

int scull_open(struct inode *inode, struct file *filp)
{
struct scull_dev *dev; /* device information */
dev = container_of(inode->i_cdev, struct scull_dev, cdev);
filp->private_data = dev; /* for other methods */
/* now trim to 0 the length of the device if open was write-only */
if ( (filp->f_flags & O_ACCMODE) = = O_WRONLY) {
scull_trim(dev); /* ignore errors */
}
return 0; /* success */
}

The release Method:
The role of the release method is the reverse of open. Sometimes you’ll find that the
method implementation is called device_close instead of device_release. Either
way, the device method should perform the following tasks:
• Deallocate anything that open allocated in filp->private_data
• Shut down the device on last close
The basic form of scull has no hardware to shut down, so the code required is
minimal:*
int scull_release(struct inode *inode, struct file *filp)
{
return 0;
}

read and write:
The read and write methods both perform a similar task, that is, copying data from
and to application code. Therefore, their prototypes are pretty similar, and it’s worth
introducing them at the same time:
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);
For both methods, filp is the file pointer and count is the size of the requested data
transfer. The buff argument points to the user buffer holding the data to be written or
the empty buffer where the newly read data should be placed. Finally, offp is a pointer
to a “long offset type” object that indicates the file position the user is accessing. The
return value is a “signed size type”; its use is discussed later.

0 nhận xét:

Đăng nhận xét