Question

I wonder if there is a way to write char drivers without using any of the functions provided in file_operations structure.

I am very new to Linux device drivers and as any novice would do, I started reading LDD3 book. And I was successful in writing a simple char driver.

include<linux/module.h>
#include<linux/kernel.h>    
#include<linux/fs.h>
#include<linux/cdev.h>
#include<linux/semaphore.h>
#include<linux/uaccess.h>

int chardev_init(void);
void chardev_exit(void);
static int device_open(struct inode *, struct file *);
static int device_close(struct inode *, struct file *);
static ssize_t device_read(struct file *, char *, size_t , loff_t *);
static ssize_t device_write(struct file *, const char *, size_t, loff_t *);
static loff_t device_lseek(struct file *filp, loff_t offset, int orig);

#define BUFFER_SIZE 1024
#define DEVICE_NAME "readWrite"

static char deviceBuffer[BUFFER_SIZE];
dev_t devNum; /* device number allocated by the kernel */
struct cdev *mcdev; /* name of the char driver that will be registered */
struct semaphore sem;
int majorNum;
int minorNum;
int ret;

static int device_open(struct inode *inode, struct file *filp)
{   
    if(down_interruptible(&sem) != 0)
    {
        printk(KERN_ALERT "%s: device has been opened by some other device, unable to open lock\n", DEVICE_NAME);
        return -1;
    }   
    printk(KERN_INFO "%s: device opened successfully\n", DEVICE_NAME);
    return 0;
}

static ssize_t device_read(struct file *fp, char *buff, size_t length, loff_t *ppos)
{
    int maxbytes; /* maximum bytes that can be read from ppos to BUFFER_SIZE */
    int bytes_to_read; /* gives the number of bytes to read */
    int bytes_read; /* number of bytes actually read */

    maxbytes = BUFFER_SIZE - *ppos;
    if(maxbytes > length)
        bytes_to_read = length;
    else
        bytes_to_read = maxbytes;

    if(bytes_to_read == 0)
        printk(KERN_INFO "%s: reached the end of the device\n", DEVICE_NAME);
    bytes_read = bytes_to_read - copy_to_user(buff, deviceBuffer + *ppos, bytes_to_read);
    printk(KERN_INFO "%s: device has been read %d bytes\n", DEVICE_NAME, bytes_read);
    *ppos += bytes_read;
    printk(KERN_INFO "%s: device has been read\n", DEVICE_NAME);
    return bytes_read;
}

static ssize_t device_write(struct file *fp, const char * buff, size_t length, loff_t *ppos)
{
    int maxbytes; /* maximum bytes that can be written */
    int bytes_to_write;
    int bytes_written;

    maxbytes = BUFFER_SIZE - *ppos;
    if(maxbytes < length)
        bytes_to_write = maxbytes;
    else
        bytes_to_write = length;    
    bytes_written = bytes_to_write - copy_from_user(deviceBuffer + *ppos, buff, bytes_to_write);
    printk(KERN_INFO "%s: device has been written %d bytes\n", DEVICE_NAME, bytes_written);
    *ppos += bytes_written;
    printk(KERN_INFO "%s: device has been written %d\n", DEVICE_NAME, bytes_written);
    return bytes_written;
}

static loff_t device_lseek(struct file *filp, loff_t offset, int orig)
{
    loff_t new_pos = 0;
    printk(KERN_INFO "%s: lseek function in work\n", DEVICE_NAME);

    switch(orig)
    {
        case 0: /* seek set */
            new_pos = offset;
            break;
        case 1: /* seek cur */
            new_pos = filp->f_pos + offset; 
            break;
        case 2: /* seek end */
            new_pos = BUFFER_SIZE - offset;
            break;
    }
    if(new_pos > BUFFER_SIZE)
        new_pos = BUFFER_SIZE;
    if(new_pos < 0)
        new_pos = 0;
    filp->f_pos = new_pos;
    return new_pos;
}

static int device_close(struct inode *inode, struct file *filp)
{
    up(&sem);
    printk(KERN_INFO "%s: device has been closed\n", DEVICE_NAME);
    return 0;
}

struct file_operations fops = 
{
    .owner = THIS_MODULE,
    .read  = device_read,
    .write = device_write,
    .llseek = device_lseek,
    .release = device_close,
    .open = device_open
};

int chardev_init(void)
{
    /* get the major number dynamically */
    ret = alloc_chrdev_region(&devNum, 0, 1, DEVICE_NAME);
    if(ret < 0)
    {
        printk(KERN_ALERT "%s: failed to allocate major number\n", DEVICE_NAME);
        return ret;
    }
    else
        printk(KERN_INFO "%s: major number allocation successful\n", DEVICE_NAME);
    majorNum = MAJOR(devNum);
    minorNum = MINOR(devNum);
    printk(KERN_INFO "%s: major number of our device is %d\n", DEVICE_NAME, majorNum);
    printk(KERN_INFO "%s: minor number of our device is %d\n", DEVICE_NAME, minorNum);
    printk(KERN_INFO "%s: to use mknod /dev/%s c %d 0\n", DEVICE_NAME, DEVICE_NAME, majorNum);

    mcdev = cdev_alloc(); /* create, allocate and initialize our cdev structure */
    mcdev->ops = &fops;
    mcdev->owner = THIS_MODULE;

    /* after creating and initializing our cdev structure, we need to add it to the kernel */
    ret = cdev_add(mcdev, devNum, 1);
    if(ret < 0)
    {
        printk(KERN_ALERT "%s: adding device to the kernel failed\n",DEVICE_NAME);
        return ret;
    }   
    else
        printk(KERN_INFO "%s: adding device to the kernel successful\n", DEVICE_NAME);
    sema_init(&sem, 1); /* initial value to 1 */
    return 0;
} 

void chardev_exit(void)
{
    cdev_del(mcdev); /* removing the mcdev structure */ 
    printk(KERN_INFO "%s: removed the mcdev from kernel\n", DEVICE_NAME);

    unregister_chrdev_region(devNum,1);
    printk(KERN_INFO "%s: unregistered the device numbers\n", DEVICE_NAME);
    printk(KERN_ALERT "%s: character driver is exiting\n", DEVICE_NAME);
}

MODULE_AUTHOR("SJ");
MODULE_DESCRIPTION("read write char driver");
MODULE_LICENSE("GPL");

module_init(chardev_init);
module_exit(chardev_exit);

Now I started to understand more complicated drivers like this one, but I am totally puzzled. Because there is no file_operations structure, no module_init() or module_exit() functions.

I have a lot of questions like,

Where the major number is allocated in the code?

Where is cdev structure?

How the read and write operations are handled?

Can somebody with Linux embedded device drivers writing experience, please help me answer my questions.

Thank you!

Was it helpful?

Solution

A character driver is a driver that create a character device, i.e., some device node in /dev/ that allows you to access the driver through system calls like open/read/write etc.

There are many other driver types for devices that are accessed in other ways. In the case of SPI drivers, the kernel SPI framework handles most of the device management; the spi-omap2-mcspi driver registers its own spi_master structure that is the equivalent of the generic cdev/fops structures.

OTHER TIPS

Please do not compare platform drivers with char drivers.They have altogether a different fitment in Linux Driver model.

Platform drivers are written for platform devices which assume to have a pseudo bus(platform bus).These drivers also have open(),write() functions etc. but their basic/main functions are probe() and remove() for platform device to bind and unbind respectively.

Char drivers can be written for char type of devices with use of major and minor number concepts.Platform drivers may not need these numbers as there binding is based on device name match mechanism.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top