Related
I am learning ioctl functionality in device drivers,
There is a function pointer .compat_ioctl in file_operations which allows 32-bit processes to use ioctls in 64-bit machines.
Following is my Driver Code:
#ifndef __IOCTL_CMD_H
#define __IOCTL_CMD_H
#define MSG_MAGIC_NUMBER 0x21
#define MSG_IOCTL_GET_LENGTH _IOR(MSG_MAGIC_NUMBER, 1, unsigned int)
#define MSG_IOCTL_CLEAR_BUFFER _IO(MSG_MAGIC_NUMBER, 2)
#define MSG_IOCTL_FILL_BUFFER _IOW(MSG_MAGIC_NUMBER, 3, unsigned char)
#define MSG_GET_ADDRESS _IOR(MSG_MAGIC_NUMBER, 4, unsigned long)
#define MSG_IOCTL_MAX_CMDS 4
#endif
long device_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
unsigned char ch;
int retval = 0;
long size = _IOC_SIZE(cmd);
pr_info("%s: Cmd:%u\t Arg:%lu Size:%lu add:%p\n", __func__, cmd, arg, size, &ch);
if (_IOC_TYPE(cmd) != MSG_MAGIC_NUMBER) return -ENOTTY;
if (_IOC_NR(cmd) > MSG_IOCTL_MAX_CMDS) return -ENOTTY;
//access_ok is kernel-oriented, so the concept of read and write is reversed
retval = access_ok((void __user *)arg, size);
pr_info("access_ok returned:%d\n", retval);
if (!retval)
return -EFAULT;
switch(cmd)
{
//Get Length of buffer
case MSG_IOCTL_GET_LENGTH:
pr_info("Get Buffer Length\n");
put_user(MAX_SIZE, (unsigned int *)arg);
break;
//clear buffer
case MSG_IOCTL_CLEAR_BUFFER:
pr_info("Clear buffer\n");
memset(kernel_buffer, 0, sizeof(kernel_buffer));
break;
//fill character
case MSG_IOCTL_FILL_BUFFER:
get_user(ch, (unsigned char *)arg);
pr_info("Fill Character:%c\n", ch);
memset(kernel_buffer, ch, sizeof(kernel_buffer));
buffer_index = sizeof(kernel_buffer);
break;
//address of kernel buffer
case MSG_GET_ADDRESS:
put_user(0x12345678, (unsigned long*)arg);
pr_info("MSG_GET_ADDRESS\n");
break;
default:
pr_info("Unknown Command:%u\n", cmd);
return -ENOTTY;
}
return 0;
}
long device_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
unsigned char ch;
int retval = 0;
long size = _IOC_SIZE(cmd);
pr_info("%s: Cmd:%u\t Arg:%lu Size:%lu add:%p\n", __func__, cmd, arg, size, &ch);
if (_IOC_TYPE(cmd) != MSG_MAGIC_NUMBER) return -ENOTTY;
if (_IOC_NR(cmd) > MSG_IOCTL_MAX_CMDS) return -ENOTTY;
//access_ok is kernel-oriented, so the concept of read and write is reversed
retval = access_ok((void __user *)arg, size);
pr_info("access_ok returned:%d\n", retval);
if (!retval)
return -EFAULT;
switch(cmd)
{
//Get Length of buffer
case MSG_IOCTL_GET_LENGTH:
pr_info("Get Buffer Length\n");
put_user(MAX_SIZE, (unsigned int *)arg);
break;
//clear buffer
case MSG_IOCTL_CLEAR_BUFFER:
pr_info("Clear buffer\n");
memset(kernel_buffer, 0, sizeof(kernel_buffer));
break;
//fill character
case MSG_IOCTL_FILL_BUFFER:
get_user(ch, (unsigned char *)arg);
pr_info("Fill Character:%c\n", ch);
memset(kernel_buffer, ch, sizeof(kernel_buffer));
buffer_index = sizeof(kernel_buffer);
break;
//address of kernel buffer
case MSG_GET_ADDRESS:
put_user(0x12345678, (unsigned long*)arg);
pr_info("MSG_GET_ADDRESS\n");
break;
default:
pr_info("Unknown Command:%u\n", cmd);
return -ENOTTY;
}
return 0;
}
struct file_operations device_fops = {
.read = device_read,
.write = device_write,
.open = device_open,
.release = device_release,
.llseek = device_lseek,
.unlocked_ioctl = device_ioctl,
.compat_ioctl = device_compat_ioctl
};
MSG_GET_ADDRESS ioctl accepts unsigned long long which is 4 bytes in 32 bit process and 8 bytes in 64 bit process. That is the reason i wrote compat_ioctl.
When i call the below code from user space (32-bit process), it fails with unknown ioctl in compat_ioctl definition.
int main(int argc, char *argv[])
{
char buffer[1024];
int fd;
unsigned int length;
int i = 0;
unsigned long addr;
fd = open("/dev/msg", O_RDWR);
if (fd < 0) {
perror("fd failed");
exit(2);
}
printf("Size:%d\n", _IOC_SIZE(MSG_GET_ADDRESS));
printf("cmd:%u\n", MSG_GET_ADDRESS);
ioctl(fd, MSG_GET_ADDRESS, &addr);
perror("ioctl");
getchar();
printf("address:%x\n", addr);
close(fd);
}
What is the mistake i am making here.
Your MSG_GET_ADDRESS ioctl request code is defined as:
#define MSG_GET_ADDRESS _IOR(MSG_MAGIC_NUMBER, 4, unsigned long)
The size of the third parameter is encoded into the ioctl request code. The size can be extracted from the request code using the _IOC_SIZE(req) macro.
The numeric value of MSG_GET_ADDRESS will be different in a 32-bit process/kernel compared to a 64-bit process/kernel. In particular, the encoded size will be different.
On a 32-bit process/kernel, _IOC_SIZE(MSG_GET_ADDRESS) will be 4. On a 64-bit process/kernel, _IOC_SIZE(MSG_GET_ADDRESS) will be 8. This is due to the different sizeof(unsigned long) values on 32-bit and 64-bit systems.
When running a 32-bit process on a 64-bit kernel with 32-bit compatibility support, the 32-bit process will be calling ioctl() with the 32-bit version of the MSG_GET_ADDRESS request code. However, your driver's device_compat_ioctl() is looking for the 64-bit version of the MSG_GET_ADDRESS request code.
A solution is to define a 32-bit version of the ioctl request code in the driver to mirror the "official" MSG_GET_ADDRESS request code:
#define MSG32_GET_ADDRESS _IOR(MSG_MAGIC_NUMBER, 4, compat_ulong_t)
Note that this request code does not need to be in the user-mode headers as it is only for use in kernel mode. However, if it is more convenient, you could include it in the user-mode headers but wrapped in a #ifdef __KERNEL__ / #endif pair:
#ifdef __KERNEL__
#define MSG32_GET_ADDRESS _IOR(MSG_MAGIC_NUMBER, 4, compat_ulong_t)
#endif
Now, your device_compat_ioctl function should be changed to handle a MSG32_GET_ADDRESS request code instead of a MSG_GET_ADDRESS request code:
//address of kernel buffer
case MSG32_GET_ADDRESS:
put_user(0x12345678, (compat_ulong_t*)arg);
pr_info("MSG_GET_ADDRESS\n");
break;
Note: According to the comments in your code, the MSG_GET_ADDRESS is actually supposed to get the address of a kernel buffer. I don't know what your user-space code intends to do with it, but be aware that a 64-bit kernel address will not fit in a 32-bit unsigned long (or the 32-bit compat_ulong_t type).
I have an exam question and I can't quite see how to solve it.
A driver that needs the ioctl method to be implemented and tested.
I have to write the ioctl() method, the associated test program as well as the common IOCTL definitions.
The ioctl() method should only handle one command. In this command, I need to transmit a data structure from user space to kernel space.
Below is the structure shown:
struct data
{
char label [10];
int value;
}
The driver must print the IOCTL command data, using printk();
Device name is "/dev/mydevice"
The test program must validate driver mode using an initialized data structure.
Hope there are some that can help
thanks in advance
My suggestion:
static int f_on_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
unsigned long arg)
{
int ret;
switch (cmd)
{
case PASS_STRUCT:
struct data pass_data;
ret = copy_from_user(&pass_data, arg, sizeof(*pass_data));
if(ret < 0)
{
printk("PASS_STRUCT\n");
return -1;
}
printk(KERN ALERT "Message PASS_STRUCT : %d and %c\n",pass_data.value, pass_data.label);
break;
default:
return ENOTTY;
}
return 0;
}
Definitions:
Common.h
#define SYSLED_IOC_MAGIC 'k'
#define PASS_STRUCT _IOW(SYSLED_IOC_MAGIC, 1, struct data)
The test program:
int main()
{
int fd = open("/dev/mydevice", O_RDWR);
data data_pass;
data_pass.value = 2;
data_pass.label = "hej";
ioctl(fd, PASS_STRUCT, &data_pass);
close(fd);
return 0;
}
Is this completely wrong??
I am writing code for implementing a simple i2c read/write function using the general linux i2c driver linux/i2c-dev.h
I am confused about the ioctl : I2C_SLAVE
The kernel documentation states as follows :
You can do plain i2c transactions by using read(2) and write(2) calls.
You do not need to pass the address byte; instead, set it through
ioctl I2C_SLAVE before you try to access the device
However I am using the ioctl I2C_RDWR where I again set the slave address using i2c_msg.addr.
The kernel documentation also mentions the following :
Some ioctl() calls are for administrative tasks and are handled by
i2c-dev directly. Examples include I2C_SLAVE
So is it must to use the ioctl I2C_SLAVE? If so do I need to set it just once or every time I perform a read and write?
If I had an i2c device I could have just tested the code on the device and would not have bothered you guys but unfortunately I don't have one right now.
Thanks for the help.
There are three major methods of communicating with i2c devices from userspace.
1. IOCTL I2C_RDWR
This method allows for simultaneous read/write and sending an uninterrupted sequence of message. Not all i2c devices support this method.
Before performing i/o with this method, you should check whether the device supports this method using an ioctl I2C_FUNCS operation.
Using this method, you do not need to perform an ioctl I2C_SLAVE operation -- it is done behind the scenes using the information embedded in the messages.
2. IOCTL SMBUS
This method of i/o is more powerful but the resulting code is more verbose. This method can be used if the device does not support the I2C_RDWR method.
Using this method, you do need to perform an ioctl I2C_SLAVE operation (or, if the device is busy, an I2C_SLAVE_FORCE operation).
3. SYSFS I/O
This method uses the basic file i/o system calls read() and write(). Uninterrupted sequential operations are not possible using this method. This method can be used if the device does not support the I2C_RDWR method.
Using this method, you do need to perform an ioctl I2C_SLAVE operation (or, if the device is busy, an I2C_SLAVE_FORCE operation).
I can't think of any situation when this method would be preferable to others, unless you need the chip to be treated like a file.
Full IOCTL Example
I haven't tested this example, but it shows the conceptual flow of writing to an i2c device.-- automatically detecting whether to use the ioctl I2C_RDWR or smbus technique.
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#define I2C_ADAPTER "/dev/i2c-0"
#define I2C_DEVICE 0x00
int i2c_ioctl_write (int fd, uint8_t dev, uint8_t regaddr, uint16_t *data, size_t size)
{
int i, j = 0;
int ret;
uint8_t *buf;
// the extra byte is for the regaddr
size_t buff_size = 1 + size;
buf = malloc(buff_size);
if (buf == NULL) {
return -ENOMEM;
}
buf[j ++] = regaddr;
for (i = 0; i < size / sizeof(uint16_t); i ++) {
buf[j ++] = (data[i] & 0xff00) >> 8;
buf[j ++] = data[i] & 0xff;
}
struct i2c_msg messages[] = {
{
.addr = dev,
.buf = buf,
.len = buff_size,
},
};
struct i2c_rdwr_ioctl_data payload = {
.msgs = messages,
.nmsgs = sizeof(messages) / sizeof(messages[0]),
};
ret = ioctl(fd, I2C_RDWR, &payload);
if (ret < 0) {
ret = -errno;
}
free (buf);
return ret;
}
int i2c_ioctl_smbus_write (int fd, uint8_t dev, uint8_t regaddr, uint16_t *data, size_t size)
{
int i, j = 0;
int ret;
uint8_t *buf;
buf = malloc(size);
if (buf == NULL) {
return -ENOMEM;
}
for (i = 0; i < size / sizeof(uint16_t); i ++) {
buf[j ++] = (data[i] & 0xff00) >> 8;
buf[j ++] = data[i] & 0xff;
}
struct i2c_smbus_ioctl_data payload = {
.read_write = I2C_SMBUS_WRITE,
.size = I2C_SMBUS_WORD_DATA,
.command = regaddr,
.data = (void *) buf,
};
ret = ioctl (fd, I2C_SLAVE_FORCE, dev);
if (ret < 0)
{
ret = -errno;
goto exit;
}
ret = ioctl (fd, I2C_SMBUS, &payload);
if (ret < 0)
{
ret = -errno;
goto exit;
}
exit:
free(buf);
return ret;
}
int i2c_write (int fd, uint8_t dev, uint8_t regaddr, uint16_t *data, size_t size)
{
unsigned long funcs;
if (ioctl(fd, I2C_FUNCS, &funcs) < 0) {
return -errno;
}
if (funcs & I2C_FUNC_I2C) {
return i2c_ioctl_write (fd, dev, regaddr, data, size);
} else if (funcs & I2C_FUNC_SMBUS_WORD_DATA) {
return i2c_ioctl_smbus_write (fd, dev, regaddr, data, size);
} else {
return -ENOSYS;
}
}
int parse_args (uint8_t *regaddr, uint16_t *data, size_t size, char *argv[])
{
char *endptr;
int i;
*regaddr = (uint8_t) strtol(argv[1], &endptr, 0);
if (errno || endptr == argv[1]) {
return -1;
}
for (i = 0; i < size / sizeof(uint16_t); i ++) {
data[i] = (uint16_t) strtol(argv[i + 2], &endptr, 0);
if (errno || endptr == argv[i + 2]) {
return -1;
}
}
return 0;
}
void usage (int argc, char *argv[])
{
fprintf(stderr, "Usage: %s regaddr data [data]*\n", argv[0]);
fprintf(stderr, " regaddr The 8-bit register address to write to.\n");
fprintf(stderr, " data The 16-bit data to be written.\n");
exit(-1);
}
int main (int argc, char *argv[])
{
uint8_t regaddr;
uint16_t *data;
size_t size;
int fd;
int ret = 0;
if (argc < 3) {
usage(argc, argv);
}
size = (argc - 2) * sizeof(uint16_t);
data = malloc(size);
if (data == NULL) {
fprintf (stderr, "%s.\n", strerror(ENOMEM));
return -ENOMEM;
}
if (parse_args(®addr, data, size, argv) != 0) {
free(data);
usage(argc, argv);
}
fd = open(I2C_ADAPTER, O_RDWR | O_NONBLOCK);
ret = i2c_write(fd, I2C_DEVICE, regaddr, data);
close(fd);
if (ret) {
fprintf (stderr, "%s.\n", strerror(-ret));
}
free(data);
return ret;
}
If you use the read() and write() methods, calling ioctl with I2C_SLAVE once is enough. You can also use I2C_SLAVE_FORCE if the device is already in use.
However I haven't yet found a consistent way to read specific registers for every device using the read()/write() methods.
I'm not too sure if this helps because I don't use ioctl I2C_RDWR but I've been using the following code with success:
int fd;
fd = open("/dev/i2c-5", O_RDWR);
ioctl(fd, I2C_SLAVE_FORCE, 0x20);
i2c_smbus_write_word_data(fd, ___, ___);
i2c_smbus_read_word_data(fd, ___);
All I do is set I2C_SLAVE_FORCE once at the beginning and I can read and write as much as I want to after that.
PS - This is just a code sample and obviously you should check the returns of all of these functions. I'm using this code to communicate with a digital I/O chip. The two i2c_* functions are just wrappers that call ioctl(fd, I2C_SMBUS, &args); where args is a struct i2c_smbus_ioctl_data type.
For the interested, SLAVE_FORCE is used when the device in question is already being managed by a kernel driver. (i2cdetect will show UU for that address)
I am new to driver development, and I am trying to write a simple char driver that has ioctl that allows user process to get the time(timespec) that my char driver took on last read and write.
long charmem_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
struct charmem_dev *dev = filp->private_data;
if (down_interruptible(&dev->sem)) {
printk(KERN_WARNING "I got booted!!\n");
return -ERESTARTSYS;
}
printk(KERN_WARNING "charmem: in ioctl; cmd = %d, arg = %d\n", (int)cmd, (int)arg);
switch(cmd) {
case IOCTL_GET_LAST_READ_TIME:
printk("charmem_ioctl: returning last read time delta, exiting...\n");
up(&dev->sem);
return dev->last_read_delta.tv_nsec;
break;
case IOCTL_GET_LAST_WRITE_TIME:
printk("charmem_ioctl: returning last write time delta, exiting...\n");
up(&dev->sem);
return dev->last_write_delta.tv_nsec;
break;
case IOCTL_RESET_READ: /*return read-pointer to the start of buffer*/
dev->rp = dev->buffer;
break;
case IOCTL_RESET_WRITE: /*return write-pointer to the start of buffer*/
dev->wp = dev->buffer;
break;
case IOCTL_LOAD_BUFFER_TO_CACHE:
load_buffer_to_cache(dev->buffer, dev->buffer_size);
break;
default:
printk("charmem_ioctl: invalid ioctl command, exiting...\n");
up(&dev->sem);
return -EFAULT;
}
up(&dev->sem);
return 0;
}
struct file_operations charmem_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = charmem_read,
.write = charmem_write,
.unlocked_ioctl = charmem_ioctl,
.open = charmem_open,
.release = charmem_release,
};
main.c -- user program that tests my char device:
int fd = 0, ret = 0;
fd = open("/dev/charmem0", O_RDWR);
if (fd < 0) {
printf("/dev/charmem0 unable to access (fd = %d)... EXITING\n", fd);
return -1;
}
ret = write(fd,msg1,10);
ret = read(fd,user_buffer,10);
read_delta = ioctl(fd, IOCTL_GET_LAST_READ_TIME);
printf("read_delta : %d\n ", read_delta);
write_delta = ioctl(fd, IOCTL_GET_LAST_WRITE_TIME);
printf("write_delta : %d\n ", write_delta);
main.c is the program that tests my char device; the program blocks after printing out read_delta value, and I am assuming that it blocks on ioctl. What am I doing wrong in my code?
I don't see any problems with the up/down of the semaphore in your code. The most likely place that your program is blocking is in the call to down_interruptible(). If you press control-c, that will force the down_interruptible to return, and you should see your printk of "I got booted" in dmesg or your console or syslog. Then the task is the figure out what other thing in your driver is holding that semaphore.
One other thought that occurs to me... printf is buffered. So it is possible that your GET_LAST_WRITE_TIME ioctl did return, and the output is in the stdout buffer, and your program is actually stuck on some code further down. Recommend adding a fflush(stdout) after the printf("write delta... to eliminate this possibility.
Michael
How to write char device drivers in Linux?
A very good example is the Linux "softdog", or software watchdog timer. When loaded, it will watch a special device for writes and take action depending on the frequency of those writes.
It also shows you how to implement a rudamentary ioctl interface, which is very useful.
The file to look at is drivers/watchdog/softdog.c
If you learn by example, that is a very good one to start with. The basic character devices (null, random, etc) as others suggest are also good, but do not adequately demonstrate how you need to implement an ioctl() interface.
A side note, I believe the driver was written by Alan Cox. If your going to learn from example, its never a bad idea to study the work of a top level maintainer. You can be pretty sure that the driver also illustrates adhering to proper Linux standards.
As far as drivers go (in Linux), character drivers are the easiest to write and also the most rewarding, as you can see your code working very quickly. Good luck and happy hacking.
Read this book: Linux Device Drivers published by O'Reilly.
Helped me a lot.
My favorite book for learning how the kernel works, BY FAR (and I've read most of them) is:
Linux Kernel Development (2nd Edition)
This book is fairly short, read it first, then read the O'Reilly book on drivers.
Read linux device driver 3rd edition. And the good thing is start coding. I am just pasting a simple char driver so that you can start coding.
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/fs.h> /*this is the file structure, file open read close */
#include<linux/cdev.h> /* this is for character device, makes cdev avilable*/
#include<linux/semaphore.h> /* this is for the semaphore*/
#include<linux/uaccess.h> /*this is for copy_user vice vers*/
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 *file, loff_t offset, int orig);
/*new code*/
#define BUFFER_SIZE 1024
static char device_buffer[BUFFER_SIZE];
struct semaphore sem;
struct cdev *mcdev; /*this is the name of my char driver that i will be registering*/
int major_number; /* will store the major number extracted by dev_t*/
int ret; /*used to return values*/
dev_t dev_num; /*will hold the major number that the kernel gives*/
#define DEVICENAME "megharajchard"
/*inode reffers to the actual file on disk*/
static int device_open(struct inode *inode, struct file *filp) {
if(down_interruptible(&sem) != 0) {
printk(KERN_ALERT "megharajchard : the device has been opened by some other device, unable to open lock\n");
return -1;
}
//buff_rptr = buff_wptr = device_buffer;
printk(KERN_INFO "megharajchard : device opened succesfully\n");
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 "megharajchard : Reached the end of the device\n");
bytes_read = bytes_to_read - copy_to_user(buff, device_buffer + *ppos, bytes_to_read);
printk(KERN_INFO "megharajchard : device has been read %d\n",bytes_read);
*ppos += bytes_read;
printk(KERN_INFO "megharajchard : device has been read\n");
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 read from ppos to BUFFER_SIZE*/
int bytes_to_write; /* gives the number of bytes to write*/
int bytes_writen;/*number of bytes actually writen*/
maxbytes = BUFFER_SIZE - *ppos;
if(maxbytes > length)
bytes_to_write = length;
else
bytes_to_write = maxbytes;
bytes_writen = bytes_to_write - copy_from_user(device_buffer + *ppos, buff, bytes_to_write);
printk(KERN_INFO "megharajchard : device has been written %d\n",bytes_writen);
*ppos += bytes_writen;
printk(KERN_INFO "megharajchard : device has been written\n");
return bytes_writen;
}
static loff_t device_lseek(struct file *file, loff_t offset, int orig) {
loff_t new_pos = 0;
printk(KERN_INFO "megharajchard : lseek function in work\n");
switch(orig) {
case 0 : /*seek set*/
new_pos = offset;
break;
case 1 : /*seek cur*/
new_pos = file->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;
file->f_pos = new_pos;
return new_pos;
}
static int device_close(struct inode *inode, struct file *filp) {
up(&sem);
printk(KERN_INFO "megharajchard : device has been closed\n");
return ret;
}
struct file_operations fops = { /* these are the file operations provided by our driver */
.owner = THIS_MODULE, /*prevents unloading when operations are in use*/
.open = device_open, /*to open the device*/
.write = device_write, /*to write to the device*/
.read = device_read, /*to read the device*/
.release = device_close, /*to close the device*/
.llseek = device_lseek
};
int chardev_init(void)
{
/* we will get the major number dynamically this is recommended please read ldd3*/
ret = alloc_chrdev_region(&dev_num,0,1,DEVICENAME);
if(ret < 0) {
printk(KERN_ALERT " megharajchard : failed to allocate major number\n");
return ret;
}
else
printk(KERN_INFO " megharajchard : mjor number allocated succesful\n");
major_number = MAJOR(dev_num);
printk(KERN_INFO "megharajchard : major number of our device is %d\n",major_number);
printk(KERN_INFO "megharajchard : to use mknod /dev/%s c %d 0\n",DEVICENAME,major_number);
mcdev = cdev_alloc(); /*create, allocate and initialize our cdev structure*/
mcdev->ops = &fops; /*fops stand for our file operations*/
mcdev->owner = THIS_MODULE;
/*we have created and initialized our cdev structure now we need to add it to the kernel*/
ret = cdev_add(mcdev,dev_num,1);
if(ret < 0) {
printk(KERN_ALERT "megharajchard : device adding to the kerknel failed\n");
return ret;
}
else
printk(KERN_INFO "megharajchard : device additin to the kernel succesful\n");
sema_init(&sem,1); /* initial value to one*/
return 0;
}
void chardev_exit(void)
{
cdev_del(mcdev); /*removing the structure that we added previously*/
printk(KERN_INFO " megharajchard : removed the mcdev from kernel\n");
unregister_chrdev_region(dev_num,1);
printk(KERN_INFO "megharajchard : unregistered the device numbers\n");
printk(KERN_ALERT " megharajchard : character driver is exiting\n");
}
MODULE_AUTHOR("A G MEGHARAJ(agmegharaj#gmail.com)");
MODULE_DESCRIPTION("A BASIC CHAR DRIVER");
//MODULE_LICENCE("GPL");
module_init(chardev_init);
module_exit(chardev_exit);
and this is the make file.
obj-m := megharajchard.o
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
$(MAKE) -C $(KERNELDIR) M=$(PWD)
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
load script. make sure the major number is 251 or else change it accordingly.
#!/bin/sh
sudo insmod megharajchard.ko
sudo mknod /dev/megharajchard c 251 0
sudo chmod 777 /dev/megharajchard
unload script,
#!/bin/sh
sudo rmmod megharajchard
sudo rm /dev/megharajchard
also a c program to test the operation of your device
#include<stdio.h>
#include<fcntl.h>
#include<string.h>
#include<malloc.h>
#define DEVICE "/dev/megharajchard"
//#define DEVICE "megharaj.txt"
int debug = 1, fd = 0;
int write_device() {
int write_length = 0;
ssize_t ret;
char *data = (char *)malloc(1024 * sizeof(char));
printf("please enter the data to write into device\n");
scanf(" %[^\n]",data); /* a space added after"so that it reads white space, %[^\n] is addeed so that it takes input until new line*/
write_length = strlen(data);
if(debug)printf("the length of dat written = %d\n",write_length);
ret = write(fd, data, write_length);
if(ret == -1)
printf("writting failed\n");
else
printf("writting success\n");
if(debug)fflush(stdout);/*not to miss any log*/
free(data);
return 0;
}
int read_device() {
int read_length = 0;
ssize_t ret;
char *data = (char *)malloc(1024 * sizeof(char));
printf("enter the length of the buffer to read\n");
scanf("%d",&read_length);
if(debug)printf("the read length selected is %d\n",read_length);
memset(data,0,sizeof(data));
data[0] = '0\';
ret = read(fd,data,read_length);
printf("DEVICE_READ : %s\n",data);
if(ret == -1)
printf("reading failed\n");
else
printf("reading success\n");
if(debug)fflush(stdout);/*not to miss any log*/
free(data);
return 0;
}
int lseek_device() {
int lseek_offset = 0,seek_value = 0;
int counter = 0; /* to check if function called multiple times or loop*/
counter++;
printf("counter value = %d\n",counter);
printf("enter the seek offset\n");
scanf("%d",&lseek_offset);
if(debug) printf("seek_offset selected is %d\n",lseek_offset);
printf("1 for SEEK_SET, 2 for SEEK_CUR and 3 for SEEK_END\n");
scanf("%d", &seek_value);
printf("seek value = %d\n", seek_value);
switch(seek_value) {
case 1: lseek(fd,lseek_offset,SEEK_SET);
return 0;
break;
case 2: lseek(fd,lseek_offset,SEEK_CUR);
return 0;
break;
case 3: lseek(fd,lseek_offset,SEEK_END);
return 0;
break;
default : printf("unknown option selected, please enter right one\n");
break;
}
/*if(seek_value == 1) {
printf("seek value = %d\n", seek_value);
lseek(fd,lseek_offset,SEEK_SET);
return 0;
}
if(seek_value == 2) {
lseek(fd,lseek_offset,SEEK_CUR);
return 0;
}
if(seek_value == 3) {
lseek(fd,lseek_offset,SEEK_END);
return 0;
}*/
if(debug)fflush(stdout);/*not to miss any log*/
return 0;
}
int lseek_write() {
lseek_device();
write_device();
return 0;
}
int lseek_read() {
lseek_device();
read_device();
return 0;
}
int main()
{
int value = 0;
if(access(DEVICE, F_OK) == -1) {
printf("module %s not loaded\n",DEVICE);
return 0;
}
else
printf("module %s loaded, will be used\n",DEVICE);
while(1) {
printf("\t\tplease enter 1 to write\n \
2 to read\n \
3 to lseek and write\
4 to lseek and read\n");
scanf("%d",&value);
switch(value) {
case 1 :printf("write option selected\n");
fd = open(DEVICE, O_RDWR);
write_device();
close(fd); /*closing the device*/
break;
case 2 :printf("read option selected\n");
/* dont know why but i am suppoesed to open it for writing and close it, i cant keep open and read.
its not working, need to sort out why is that so */
fd = open(DEVICE, O_RDWR);
read_device();
close(fd); /*closing the device*/
break;
case 3 :printf("lseek option selected\n");
fd = open(DEVICE, O_RDWR);
lseek_write();
close(fd); /*closing the device*/
break;
case 4 :printf("lseek option selected\n");
fd = open(DEVICE, O_RDWR);
lseek_read();
close(fd); /*closing the device*/
break;
default : printf("unknown option selected, please enter right one\n");
break;
}
}
return 0;
}
Have a look at some of the really simple standard ones - "null", "zero", "mem", "random", etc, in the standard kernel. They show the simple implementation.
Obviously if you're driving real hardware it's more complicated- you need to understand how to interface with the hardware as well as the subsystem APIs (PCI, USB etc) for your device. You might need to understand how to use interrupts, kernel timers etc as well.
Just check the character driver skeleton from here http://www.linuxforu.com/2011/02/linux-character-drivers/....Go ahead and read all the topics here, understand thoroughly.(this is kinda a tutorial-so play along as said).
See how "copy_to_user" and "copy_from_user" functions work, which you can use in read/write part of the driver function callbacks.
Once you are done with this, start reading a basic "tty" driver.
Focus, more on the driver registration architecture first, which means:-
See what structures are to be filled- ex:- struct file_operations f_ops = ....
Which are the function responsible to register a particular structure with core . ex:- _register_driver.
Once you are done with the above, see what functionality you want with the driver(policy), then think of way to implement that policy(called mechanism)- the policy and mechanism allows you to distinguish between various aspects of the driver.
write compilation makefiles(its hard if you have multiple files- but not tht hard).
Try to resolve the errors and warnings,
and you will be through.
When writing mechanism, never forget what it offers to the applications in user space.