Monday, January 30, 2012

Linux Device Drivers : Kernel APIs used for Character Device Drivers

As i have planned, i went to 4-day workshop on Linux Device Drivers by Anil Kumar Pugalia. It was fine and made us to put some individual effort to understand more. Hence i will start posting them in my blog, not in order.

First to start with, i am keeping some info about Character Device Drivers. Here are Kernel APIs to be used for Character device drivers.

1. alloc_chrdev_region() : Allocates a range of char device numbers. The major number will be chosen dynamically, and returned in dev along with the first minor number.

alloc_chrdev_region (dev_t* dev, unsigned baseMinor, unsigned count, const char* name);

dev : output param for first assigned number
baseMinor: first of the requested range of minor numbers
count: number of minor numbers required
name: name of the associated device/driver

2. cdev_init() : Initialize the cdev structure, remembering fops, making it ready to add to the system with cdev_add

cdev_add(struct cdev* cdev, const struct file_operations* fops);

cdev : the structure to initialize
fops : the file operations for this device.

3. cdev_add() : Add a char device driver to the system, making it live immediately.

cdev_add(struct cdev *p, dev_t dev, unsigned count);

p : the cdev structure for this device
dev: the first device number for which this device is responsible
count: the number of consecutive minor numbers corresponding to this device

4. class_create() : creates a struct class structure (sysfs entry and generates uevents).

class_create(struct module * owner, const char *name);

owner: THIS MODULE
name: pointer to a string for the name of this class

5. device_create() : creates a device and register it with sysfs. This will be used by character device classes. A struct device will be created in sysfs, registered to the specified class.

device_create(struct class *class, struct device *parent, dev_t dev, const char* fmt, ...);
class: output of class_create()
parent: pointer to the parent struct device (NULL for the time being)
dev: dev-t for the device to be added (output of alloc_chrdev_region())
fmt: string for the device name
...

6. unregister_chrdev_region() :

unregister_chrdev_region(dev, minor_count);

7. cdev_del():

cdev_del(&c_dev);


/*** * * * * * * * * * * * ** * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
if ((ret = alloc_chrdev_region(&dev, 0, 1, "final_driver")) < 0)
{
return ret;
}
cdev_init(&c_dev, &driver_fops);
if ((ret = cdev_add(&c_dev, dev, 0)) < 0)
{
unregister_chrdev_region(dev, 0);
return ret;
}
if (IS_ERR(cl = class_create(THIS_MODULE, "char")))
{
cdev_del(&c_dev);
unregister_chrdev_region(dev, 0);
return PTR_ERR(cl);
}
if (IS_ERR(dev_ret = device_create(cl, NULL, dev, NULL, "mychar%d", 0)))
{
class_destroy(cl);
cdev_del(&c_dev);
unregister_chrdev_region(dev, 0);
return PTR_ERR(dev_ret);
}
/*** * * * * * * * * * * * ** * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */




Memory Allocation Related:

1. ioremap() : For mapping the physical address to virtual kernel space address.

void* ioremap(unsigned long phys_addr, unsigned long size);

phy_addr: begin of physical address range
size: size of the physical address range

ioremap builds new page tables. It doesn’t actually allocate memory. The return value of ioremap is a special address that can be used to access the specified physical address range. The return value form ioremap cannot be dereferenced directly on all platforms. Instead functions like readb, writeb etc will be used. ioremap allocates uncacheable pages.

2. ioread8(), ioread16(), ioread32(): The return address of ioremap cannot be dereferenced directly. Hence these functions are provided.

ioread8(void * address);

address: return value of opremap()

3. iowrite8(), iowrite16(), iowrite32(): To write and data into the given I/O memory returned by ioremap();

iowrite(U8 value , void *address);
iowrite16(U16 value , void *address);
iowrite32(U32 value , void *address);

4. copy_to_user() and copy_from_user() :To copy to or from user space to kernel space

copy_to_user(ubuf, kbuf, len);
copy_from_user(kbuf, ubuf, len);

5. iounmap() : Virtual address obtained by ioremap() should be released by calling iounmap()

iounmap(void * address);


About ioctl() :

This is to support unusual functionalities other than read, write, seek kind of operations with the device. Most devices can perform beyond the simple data transfers like for example eject the media, lock the door, change baud rate, change the page selection tray etc. To support these, ioctl() will be used to implement the handlers.

int (*ioctl)(struct inode *inode, struct file *filep, unsigned long cmd, unsigned long arg);


ioctl in user space:

int ioctl(int fd, unsigned cmd, . . .);

A system call can't actually have a variable number of arguments. The dots in the prototype represent not a variable number of arguments but a single optional argument. The dots are simply there to prevent type checking during compilation. The actual nature of the third argument depends on the specific control command being issued. Some commands take no arguments, some take an integer value, and some take a pointer.

ioctl() commands calculation:

The ioctl command numbers should be unique across the system in order to prevent errors caused by issuing the right command to the wrong drvice. To help programmers create unique ioctl command codes, these codes have been split up into several bitfields.