Linux Basics and Kernel Programming
Linux Basics and Kernel Programming
Linux Basics and Kernel Programming
src)
Ws of character drivers
If we write drivers for byte-oriented operations (or, in C lingo, character-oriented operations), then we refer to them as character drivers. Since the majority of devices are byte-oriented, the majority of device drivers are character device drivers.
Examples: serial drivers, audio drivers, video drivers, camera drivers, and basic I/O drivers.
In fact, all device drivers that are neither storage nor network device drivers are some type of a character driver.
for any user-space application to operate on a byte-oriented device (in hardware space), it should use the corresponding character device driver (in kernel space). Character driver usage is done through the corresponding character device file(s), linked to it through the virtual file system (VFS). What this means is that an application does the usual file operations on the character device file. Those operations are translated to the corresponding functions in the linked character device driver by the VFS.
Those functions then do the final low-level access to the actual device to achieve the desired results.
In this complete connection from the application to the device, there are four major entities involved: Application Character device file Character device driver Character device An application gets connected to a device file by invoking the open system call on the device file. Device file(s) are linked to the device driver by specific registrations done by the driver.
The driver is linked to a device by its device-specific low-level operations. Thus we form the complete connection. With this, note that the character device file is not the actual device, but just a place-holder for the actual device.
Major and minor numbers The connection between the application and the device file is based on the name of the device file.
However, the connection between the device file and the device driver is based on the number of the device file, not the name.
This allows a user-space application to have any name for the device file, and enables the kernel-space to have a trivial index-based linkage between the device file and the device driver. This device file number is more commonly referred to as the <major, minor> pair, or the major and minor numbers of the device file.
There could be multiple drivers under the same major number, but obviously, with different minor number ranges. However, this is more common with the non-reserved major numbers, and standard major numbers are typically preserved for single drivers. For example, 4 for serial interfaces, 13 for mice, 14 for audio devices, and so on. The following command would list the various character device files on your system:
WHATS A DRIVER: A driver drives, manages, controls, directs and monitors the entity under its command. What a bus driver does with a bus, a device driver does with a computer device (any piece of hardware connected to a computer) like a mouse, keyboard, monitor, hard disk, Web-camera, clock, and more. a specific piece of hardware could be controlled by a piece of software (a device driver), or could be controlled by another hardware device, which in turn could be managed by a software device driver. In the latter case, such a controlling device is commonly called a device controller. This, being a device itself, often also needs a driver, which is commonly referred to as a bus driver.
Hard disk controllers, display controllers, audio controllers , IDE controller, PCI controller, USB controller, SPI controller, I2C controller, etc.
Device controllers are typically connected to the CPU through their respectively named buses (collection of physical lines) for example, the PCI bus, the IDE bus
Parts of Driver:
Bus drivers provide hardware-specific interfaces for the corresponding hardware protocols, and are the bottom-most horizontal software layers of an operating system (OS). These operate on the underlying devices using the horizontal layer interfaces, and hence are device-specific. the whole idea of writing these drivers is to provide an abstraction to the user, and so, at the other end, these do provide an interface (which varies from OS to OS). In short, a device driver has two parts, which are: a) device-specific, b) OS-specific.
Verticals:
In Linux, a device driver provides a system call interface to the user; this is the boundary line between the so-called kernel space and user-space of Linux
Based on the OS-specific interface of a driver, in Linux, a driver is broadly classified into three verticals: Packet-oriented or the network vertical Block-oriented or the storage vertical Byte-oriented or the character vertical
The network vertical consists of two parts: a) the network protocol stack, and b)the network interface card (NIC) device drivers, or simply network device drivers, which could be for Ethernet, Wi-Fi, or any other network horizontals.
Modules:
#include <linux/init.h> #include <linux/module.h> MODULE_LICENSE("Dual BSD/GPL"); static int hello_init(void) { printk(KERN_ALERT "Hello, world\n"); return 0; } static void hello_exit(void) { printk(KERN_ALERT "Goodbye, cruel world\n"); } module_init(hello_init); module_exit(hello_exit);
What is Module Programming: The modules exit function (hello_exit in the example)gets invoked just before the module is unloaded. It should tell the kernel, Im not there anymore; dont ask me to do anything else. This kind of approach to programming is similar to event driven programming,
Another major difference between event-driven applications and kernel code is in the exit function: whereas an application that terminates can be lazy in releasing resources or avoids clean up altogether, the exit function of a module must carefully undo everything the init function built up, or the pieces remain around until the system is rebooted.
Module Vs Application:
A segmentation fault is harmless during application development and a debugger can always be used to trace the error to the problem in the source code, a kernel fault kills the current process at least, if not the whole system.
A module runs in kernel space, whereas applications run in user space. Because of operating system provide programs with a consistent of the computers hardware. operating system must account for independent operation of programs and protection against unauthorized access to resources.
Contd.....!
The chosen approach is to implement different operating modalities (or levels)in the CPU itself. The x86 family, have more levels; when several levels exist, the highest and lowest levels are used. Under Unix (or Linux) , the kernel executes in the highest level (also called
supervisor mode), where everything is allowed, whereas applications execute in the lowest level (the so-called user mode), where the processor regulates direct access to hardware and unauthorized access to
memory. We usually refer to the execution modes as kernel space and user space.
Contd.....!
each mode can have its own memory mappingits own address space.
Unix transfers execution from user space to kernel space whenever an application issues a system call or is suspended by a hardware interrupt. Kernel code executing a system call is working in the context of a processit operates on behalf of the calling process and is able to access data in the processs address space.
interrupts, on the other hand, is asynchronous with respect to processes and is not related to any particular process.
Contd.....!
each mode can have its own memory mappingits own address space.
Unix transfers execution from user space to kernel space whenever an application issues a system call or is suspended by a hardware interrupt. Kernel code executing a system call is working in the context of a processit operates on behalf of the calling process and is able to access data in the processs address space.
interrupts, on the other hand, is asynchronous with respect to processes and is not related to any particular process.
The other major difference from conventional program is kernel Concurrency Kernel code must be capable of Running more than one of which can be trying to use your driver at the same time. Running in more than one context at the same time.
Creating Makefile: Obj-m := hello.o The assignment above (which takes advantage of the extended syntax provided by GNU make)states that there is one module to be built from the object file hello.o. The resulting module is named hello.ko after being built from the object file. If, instead, you have a module called module.ko that is generated from two source files (called, say, file1.c and file2.c), the correct incantation would be: obj-m := module.o module-objs := file1.o file2.o
Typical Makefile :
# If KERNELRELEASE is defined, we've been invoked from the # kernel build system and can use its language. ifneq ($(KERNELRELEASE),) obj-m := hello.o # Otherwise we were called directly from the command # line; invoke the kernel build system. else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build PWD := (shell pwd)
static int __init initialization_function(void) { /* Initialization code here */ } module_init(initialization_function); Initialization functions should be declared static, since they are not meant to be visible outside the specific file; The __init token is a hint to the kernel that the given function is used only at initialization time. ``Y __initdata token is used to initialize data. __devinit These two translate to __init and __initdata __devinitdata respectively if kernel is not configured.
#include <linux/init.h> Is to specify your initialization and cleanup functions. MODULE_LICENSE("GPL") GPL (for any version of the GNU General Public License), GPL v2 (for GPL version two only), GPL and additionalrights, Dual BSD/GPL, Dual MPL/GPL, and Proprietary.
Contd....!
The Cleanup Function Is a function function, which unregisters interfaces and returns all resources to the system before the module is removed. This function is defined as: static void __exit cleanup_function(void) { /* Cleanup code here */ } module_exit(cleanup_function); The cleanup function has no value to return, so it is declared void. The __exit modifier(token) marks the code as being for module unload. For this reason, a function marked __exit can be called only at module unload or system shutdown time; any other use is an error. The module_exit declaration is necessary to enable to kernel to find your cleanup function. If your module does not define a cleanup function, the kernel does not allow it to be unloaded.
module code must always check return values, and be sure that the requested operations have actually succeeded. If any errors occur when you register utilities, the first order of business is to decide whether the module can continue initializing itself anyway. Whenever possible, your module should press forward and provide what capabilities it can after things fail. If you ever fail to unregister what you obtained, the kernel is left in an unstable state; it contains internal pointers to code that no longer exists. In such situations, the only recourse, usually, is to reboot the system
int __init my_init_function(void) { int err; /* registration takes a pointer and a name */ err = register_this(ptr1, "skull"); if (err) goto fail_this; err = register_that(ptr2, "skull"); if (err) goto fail_that; err = register_those(ptr3, "skull"); if (err) goto fail_those; return 0; /* success */ fail_those: unregister_that(ptr2, "skull"); fail_that: unregister_this(ptr1, "skull"); fail_this: return err; /* propagate the error */ }
Cond...!
Cond...!
as -ENODEV, -ENOMEM, and so on. Obviously, the module cleanup function must undo any registration performed by the initialization function, and it is customary (but not usually mandatory)to unregister facilities in the reverse order used to register them. void __exit my_cleanup_function(void) { unregister_those(ptr3, "skull"); unregister_that(ptr2, "skull"); unregister_this(ptr1, "skull"); return; }
Module Parameters:
parameter values can be assigned at load time by insmod or modprobe; both read parameter assignment from its configuration file (/etc/modprobe.conf).
Parameters are declared with the module_param macro, which is defined in <moduleparam.h.> insmod hellop.ko howmany=10 whom=I am static char *whom = "world"; static int howmany = 1; module_param(howmany, int, S_IRUGO); module_param(whom, charp, S_IRUGO);
Module Parameters:
Contd....!
bool A boolean (true or false)value Invbool The invbool type inverts the value, charp A char pointer value. Memory is allocated for user-provided strings, and the pointer is set accordingly.
Module Parameters:
Contd....!
The final module_param field is a permission value; you should use the definitions found in <linux/stat.h>.
This value controls who can access the representation of the module parameter in
sysfs. If perm is set to 0, there is no sysfs entry at all; otherwise, it appears under
Use S_IRUGO for a parameter that can be read by the world but cannot be
changed; S_IRUGO | S_IWUSR allows root to change the parameter.
The Internal Representation of Device Numbers within the kernel, the dev_t type (defined in <linux/types.h>) is used to hold device Numbers
The return value from register_chrdev_region will be 0 if the allocation was successfully performed. In case of error, a negative error code will be returned, and you will not have access to the requested region.
register_chrdev_region works well if you know ahead of time exactly which device
numbers you want.
To overcome this, use dynamicly-allocated device numbers. The kernel allocates a major number for you on the fly by using this function: int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name); dev is an output-only parameter that will, on successful completion, hold the first number in your allocated range. firstminor should be the requested first minor number to use; it is usually 0. The count and name parameters work like those given to request_chrdev_region.
mknod /dev/${device}0 c $major 0 mknod /dev/${device}1 c $major 1 mknod /dev/${device}2 c $major 2 mknod /dev/${device}3 c $major 3 group="staff" grep -q '^staff:' /etc/group || group="wheel" chgrp $group /dev/${device}[0-3] chmod $mode /dev/${device}[0-3] The script can be adapted for another driver by redefining the variables and adjusting the mknod lines.
The user can accept the default or choose a particular major number, either by modifying the macro before compiling the code we use in sculls 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; }
struct module *owner 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. Its a macro defined in <linux/module.h>. ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); Used to retrieve data from the device. 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 (*write) (struct file *, const char __user *, size_t, loff_t *);
Sends data to the device. The return value, if nonnegative, represents the number of bytes successfully written.
int (*open) (struct inode *, struct file *); 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.
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 read and write operations. Applications occasionally need to do a single read or write operation involving multiple memory areas;
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, };
mode_t f_mode; The file mode identifies the file as either readable or writable (or both), by means of the bits FMODE_READ and FMODE_WRITE. Y struct file_operations *f_op; The operations associated with the file. The kernel assigns the pointer as part of its implementation of open and then reads it when it needs to dispatch any
Contd....!
operations.
The value in filp->f_op is never saved by the kernel for later reference; unsigned int f_flags;
These are the file flags, such as O_RDONLY, O_NONBLOCK, and O_SYNC. A driver should check the O_NONBLOCK flag to see if nonblocking operation has been requested All the flags are defined in the header <linux/fcntl.h>.
The inode structure is used by the kernel internally to represent files. There can be numerous file structures representing multiple open descriptors on a single file, but they all point to a single inode structure. dev_t i_rdev; For inodes that represent device files, this field contains the actual device number. struct cdev *i_cdev; struct cdev is the kernels internal structure that represents char devices; this field contains a pointer to that structure when the inode refers to a char device file. To have more portable programming, the kernel developers have added two macros that can be used to obtain the major and minor numberfrom an inode: unsigned int iminor(struct inode *inode); unsigned int imajor(struct inode *inode);
The kernel which is using struct cdev to represent char devices internally must register with kerenel to get allocated a registration number for one or more of these structures.
<linux/cdev.h> where the structure and its associated helper functions are defined.
Two ways of getting & allocation fo registration 1. obtain a standalone cdev structure at runtime struct cdev *my_cdev = cdev_alloc( ); my_cdev->ops = &my_fops;
2. Initialization of the structure that you have already allocated with: void cdev_init(struct cdev *cdev, struct file_operations *fops);
Contd...!
Once the cdev structure is set up, the final step is to tell the kernel about it with a call to:
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
If it returns a negative error code, your device has not been added to the system. You should not call cdev_add until your driver is completely ready to handle operations on the device.
To remove a char device from the system, call: void cdev_del(struct cdev *dev); Clearly, you should not access the cdev structure after passing it to cdev_del.
Internally, scull represents each device with a structure of type struct scull_dev.
structure must be initialized and added to the system as described below.
This structure is defined as: 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 */ };
Contd....!
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); }
Contd....!
The classic way to register a char device driver is with: int register_chrdev(unsigned int major, const char *name, struct file_operations *fops); Here, major is the major number of interest, name is the name of the driver (it appears in /proc/devices), and fops is the default file_operations structure.
If you use register_chrdev, the proper function to remove your device(s) from the system is:
int unregister_chrdev(unsigned int major, const char *name); major and name must be the same as those passed to register_chrdev, or the call will fail.
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 prototype for the open method is: int (*open)(struct inode *inode, struct file *filp);
Contd...!
The simplified code for scull_open is: 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 */ }
ssize_t write(struct file *filp, const char __user *buff, size_t count, loff_t *offp);