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!