I am trying to learn device drivers and I started with char device driver. I implemented a small program which is able to read/write from/to kernel buffer. Further, I tried to implement memory mapping and this is not working properly. When I am trying to read through a simple process by mapping my kernel module, it is giving me garbage value. Can any one help with this?
#include<linux/init.h>
#include<linux/module.h>
#include<linux/kernel.h> //printk()
#include<linux/errno.h>
#include<linux/types.h>
#include<linux/proc_fs.h>
#include<asm/uaccess.h> //copy_from,to_user
#include<linux/mm.h> //remap_pfn_range
//#include<linux/mman.h> //private_mapping_ok
#define BUFF_SIZE 128
#define DEV_NAME "MyDevice"
MODULE_LICENSE("GPL");
//Method declarations
int mod_open(struct inode *,struct file *);
int mod_release(struct inode *,struct file *);
ssize_t mod_read(struct file *,char *,size_t ,loff_t *);
ssize_t mod_write(struct file *,char *,size_t ,loff_t *);
int mod_mmap(struct file *, struct vm_area_struct *);
void mod_exit(void);
int mod_init(void);
//Structure that declares the usual file access functions
struct file_operations mod_fops = {
read: mod_read,
write: mod_write,
open: mod_open,
release: mod_release,
mmap: mod_mmap
};
static const struct vm_operations_struct mod_mem_ops = {
};
module_init(mod_init);
module_exit(mod_exit);
char *read_buf;
char *write_buf;
static int Major;
//static int Device_Open = 0;
int buffsize = 0;
int mod_init(void)
{
Major = register_chrdev(0,DEV_NAME,&mod_fops);
if(Major < 0)
{
printk(KERN_ALERT "Can not register %s. No major number alloted",DEV_NAME);
return Major;
}
//allocate memory to buffers
read_buf = kmalloc(BUFF_SIZE, GFP_KERNEL);
write_buf = kmalloc(BUFF_SIZE, GFP_KERNEL);
if(!read_buf || !write_buf)
{
mod_exit();
return -ENOMEM;
}
//reset buffers
memset(read_buf,0, BUFF_SIZE);
memset(write_buf,0, BUFF_SIZE);
printk(KERN_INFO "I was assigned major number %d. To talk to\n", Major);
printk(KERN_INFO "the driver, create a dev file with\n");
printk(KERN_INFO "'mknod /dev/%s c %d 0'.\n",DEV_NAME, Major);
printk(KERN_INFO "Try various minor numbers. Try to cat and echo to\n");
printk(KERN_INFO "the device file.\n");
printk(KERN_INFO "Remove the device file and module when done.\n");
return 0;
}
void mod_exit(void)
{
unregister_chrdev(Major,"memory");
if(read_buf) kfree(read_buf);
if(write_buf) kfree(write_buf);
printk(KERN_INFO "removing module\n");
}
int mod_mmap(struct file *filp, struct vm_area_struct *vma)
{
size_t size = vma->vm_end - vma->vm_start;
vma->vm_ops = &mod_mem_ops;
/* Remap-pfn-range will mark the range VM_IO */
if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, size, vma->vm_page_prot)) {
return -EAGAIN;
}
printk(KERN_INFO "VMA Open. Virt_addr: %lx, phy_addr: %lx\n",vma->vm_start, vma->vm_pgoff<<PAGE_SHIFT);
return 0;
}
ssize_t mod_read(struct file *filp, char *buf, size_t len, loff_t *f_pos)
{
ssize_t bytes;
if(buffsize < len)
bytes = buffsize;
else
bytes = len;
printk(KERN_INFO "Buffer size availabe: %d\n", buffsize);
printk(KERN_INFO "VMA Open. read buffer initial: %lx\n",read_buf);
if(bytes == 0)
return 0;
int retval = copy_to_user(buf,read_buf,bytes);
if(retval)
{
printk(KERN_INFO "copy_to_user fail");
return -EFAULT;
}
else
{
printk(KERN_INFO "copy_to_user succeeded\n");
buffsize -= bytes;
return bytes;
}
}
ssize_t mod_write( struct file *filp,char *buf, size_t len, loff_t *f_pos)
{
memset(read_buf,0,BUFF_SIZE);
memset(write_buf,0,BUFF_SIZE);
if(len > BUFF_SIZE)
{
printk(KERN_ALERT "Buffer not available. Writing only %d bytes.\n",BUFF_SIZE);
len = BUFF_SIZE;
}
printk(KERN_INFO "User space msg size: %d\n",len);
int retval = copy_from_user(read_buf,buf,len);
printk(KERN_INFO "read %d bytes as: %s\n", retval,read_buf);
// memcpy(write_buf,read_buf,len);
// printk(KERN_INFO "written: %s\n", write_buf);
buffsize = len;
return len;
}
int mod_open(struct inode *inode, struct file *filp){return 0;}
int mod_release(struct inode *inode, struct file *filp) {return 0;}
The program which is trying to access this device driver:
#include<stdio.h>
#include<sys/fcntl.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<sys/mman.h>
int main(int argc,char *argv[])
{
int fd,n,len;
char *buff;
if(argc != 3)
{
printf("Too few arguments.\n");
exit(EXIT_FAILURE);
}
buff = (char *)malloc(128);
if(strcmp(argv[1],"read")==0)
{
if(-1 == (fd = open("/dev/MyDevice",O_RDONLY)))
{
printf("Device open fail. Error: %s",strerror(errno));
exit(EXIT_FAILURE);
}
memset(buff,0,128);
if(-1 == (buff = mmap(0,128,PROT_READ,MAP_SHARED | MAP_NORESERVE,fd,0)))
{
printf("Mapping failed. Error: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
/* if(-1 == (n = read(fd,buff,128)))
{
printf("Device read fail. Error: %s",strerror(errno));
exit(EXIT_FAILURE);
}
*/
printf("Read from device:\n%s\n",buff);
close(fd);
}
else if(strcmp(argv[1],"write")==0)
{
len = strlen(argv[2]);
if(-1 == (fd = open("/dev/MyDevice",O_WRONLY)))
{
printf("Device open fail. Error: %s",strerror(errno));
exit(EXIT_FAILURE);
}
if(-1 == (n = write(fd,argv[2],len)))
{
printf("Device write fail. Error: %s",strerror(errno));
exit(EXIT_FAILURE);
}
printf("Written %d bytes successfully.\n",n);
close(fd);
}
else
{
printf("Invalid argument..\n");
exit(EXIT_FAILURE);
}
return 0;
}
I got the error in my code. I was not mapping my buffer to vma->vm_pgoff. Just add following code before calling rmap_pfn_range, then this code will work fine
vma->vm_pgoff = virt_to_phys(read_buff)>>PAGE_SHIFT;
There are still several potential issues in your code although you found the root cause.
"vma->vm_pgoff = virt_to_phys(read_buff)>>PAGE_SHIFT"
It is not very good practice to program in this example, as basically you are overwriting a user file offset (in PAGE size unit). If your driver need to support mmap a memory offset, then obvious there is a issue. In this case, you can just pass virt_to_phys(read_buff)>>PAGE_SHIFT in place.
It is not recommended to use kmalloc to allocate the memory for remap purpose, as it is required to be page aligned, you can just use the kernel page APIs, like get_free_page to allocate the memory, further more, it is better to remap the memory in units of PAGE size, rather than 128 bytes here.
Related
I want to use serial port in kernel space, I have found some code which was in user space, I tried to convert the codes to work in kernel space...
This is my code
#include <linux/termios.h>
#include <linux/unistd.h>
#include <linux/signal.h>
#include <linux/fcntl.h>
#include <linux/types.h>
#include <linux/socket.h>
#include <linux/in.h>
struct file * fp;
...
struct termios termAttr;
struct sigaction saio;
oldfs = get_fs();
set_fs(KERNEL_DS);
fp = filp_open("/dev/ttymxc0", O_RDWR | O_NOCTTY | O_NDELAY,0);
if(fp == NULL)
printk(KERN_ALERT "Serial openning error!!.\n");
else{
saio.sa_handler = signal_handler_IO;
saio.sa_flags = 0;
saio.sa_restorer = NULL;
sigaction(SIGIO,&saio,NULL);
fcntl(fp, F_SETFL, O_NDELAY|FASYNC);
fcntl(fp, F_SETOWN, THIS_MODULE);
tcgetattr(fp,&termAttr);
cfsetispeed(&termAttr,B115200);
cfsetospeed(&termAttr,B115200);
termAttr.c_cflag &= ~PARENB;
termAttr.c_cflag &= ~CSTOPB;
termAttr.c_cflag &= ~CSIZE;
termAttr.c_cflag |= CS8;
termAttr.c_cflag |= (CLOCAL | CREAD);
termAttr.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
termAttr.c_iflag &= ~(IXON | IXOFF | IXANY);
termAttr.c_oflag &= ~OPOST;
tcsetattr(fp,TCSANOW,&termAttr);
printk(KERN_ALERT "Serial configured....\n");
vfs_write(fp, "HI",2, NULL);
filp_close(fp, NULL);
set_fs(oldfs);
}
while compiling, I got following errors:
note: each undeclared identifier is reported only once for each function it appears in
error: implicit declaration of function 'sigaction' [-Werror=implicit-function-declaration]
sigaction(SIGIO,&saio,NULL);
^
error: implicit declaration of function 'fcntl' [-Werror=implicit-function-declaration]
fcntl(fp, F_SETFL, O_NDELAY|FASYNC);
^
error: implicit declaration of function 'tcgetattr' [-Werror=implicit-function-declaration]
tcgetattr(fp,&termAttr);
^
error: implicit declaration of function 'cfsetispeed' [-Werror=implicit-function-declaration]
cfsetispeed(&termAttr,B115200);
^
error: implicit declaration of function 'cfsetospeed' [-Werror=implicit-function-declaration]
cfsetospeed(&termAttr,B115200);
^
error: implicit declaration of function 'tcsetattr' [-Werror=implicit-function-declaration]
I am cross compiling this driver and I already compiled Linux source, I have searched for this functions in my Linux source code but I did not find any of this functions! what should I use instead of this functions?
Edit 1:
I have changed my code into this:
//serial
struct ktermios termAttr;
struct sigaction saio;
loff_t pos =0;
struct tty_struct *tty;
serialfp = file_open("/dev/ttyS1", O_RDWR | O_NOCTTY | O_NDELAY,0);
if(serialfp == NULL)
printk(KERN_ALERT "ARIO RMG Serial openning error!!.\n");
else{
tty = (struct tty_struct *)serialfp->private_data;
tty_termios_encode_baud_rate(&tty->termios,B115200,B115200 );
printk(KERN_ALERT "ARIO RMG Serial configured....\n");
pos = serialfp->f_pos;
file_write(serialfp, "\n\n\n\n\nThis is first test of sending serial data from kernel module\n\n\n\n\n",70,&pos);
serialfp->f_pos=pos;
serial_thread_condition = 1;
mutex_init(&serial_mutex);
task1 = kthread_create(&thread_function, (void *)&pid1, "pradeep");
wake_up_process(task1);
printk(KERN_ALERT "data received:%s\n\n\n\n\n\n\n\n",rmg_drvstruct[0].RxSerial);
}
I am able to send data into serial port now, I also created a thread to read data from serial port. with following code:
static int thread_function(void *data){
loff_t pos;
while(serial_thread_condition){
mutex_lock(&serial_mutex);
if (IS_ERR(serialfp)) {
mutex_unlock(&serial_mutex);
serial_thread_condition=0;
return 0;
}
pos = serialfp->f_pos;
printk(KERN_INFO "try to read from serial\r\n");
if(file_read(serialfp, rmg_drvstruct[0].RxSerial, 100, &pos)>0)
{
printk(KERN_INFO "Data: %s\r\n", rmg_drvstruct[0].RxSerial);
serialfp->f_pos = pos;
serial_thread_condition = 0;
mutex_unlock(&serial_mutex);
break;
}
mutex_unlock(&serial_mutex);
}
}
int file_read(struct file *file, unsigned long long offset, unsigned char *data, unsigned int size)
{
mm_segment_t oldfs;
int ret;
oldfs = get_fs();
set_fs(get_ds());
ret = vfs_read(file, data, size, &offset);
set_fs(oldfs);
return ret;
}
But I got nothing in serial port in my thread, I wanted to use interrupts for new received bytes, but irq_request() function makes kernel panic and computer freezes out, so what should I do to properly receive data with interrupt or a thread?
So after a while searching and testing I used this code to communicate with serial port, But I have changed a bit of it.
Please read my edit before using this code
I used a thread to read from serial port, cause using interrupts (request_irq) makes my kernel to shoot himself in the head!
So this is my code for anyone who might want to use serial port in kernel module.
#include <linux/tty.h>
#include <linux/delay.h>
#include <linux/kthread.h>
#include<linux/mutex.h>
MODULE_LICENSE("GPL");
struct file * serialfp;
struct task_struct *task1;
int pid1 = 1;
....
static struct file *file_open(const char *path, int flags, int rights)
{
struct file *filp = NULL;
mm_segment_t oldfs;
int err = 0;
oldfs = get_fs();
set_fs(get_ds());
filp = filp_open(path, flags, rights);
set_fs(oldfs);
if (IS_ERR(filp)) {
err = PTR_ERR(filp);
return NULL;
}
return filp;
}
static void file_close(struct file *file)
{
filp_close(file, NULL);
}
static int file_read(struct file *file, unsigned long long *offset, unsigned char *data, unsigned int size)
{
mm_segment_t oldfs;
int ret;
oldfs = get_fs();
set_fs(get_ds());
ret = vfs_read(file, data, size, offset);
set_fs(oldfs);
return ret;
}
static int file_write(struct file *file, unsigned long long *offset, unsigned char *data, unsigned int size)
{
mm_segment_t oldfs;
int ret;
oldfs = get_fs();
set_fs(get_ds());
ret = vfs_write(file, data, size, offset);
set_fs(oldfs);
return ret;
}
int file_sync(struct file *file)
{
vfs_fsync(file, 0);
return 0;
}
static int thread_function(void *data){
loff_t pos;
int len;
while(serial_thread_condition){
mutex_lock(&serial_mutex);
if (IS_ERR(serialfp)|| serial_thread_condition==0) {
printk(KERN_INFO "serial reading thread has been terminated.\r\n");
mutex_unlock(&serial_mutex);
serial_thread_condition=0;
return 0;
}
pos = serialfp->f_pos;
if((len=file_read(serialfp,&pos, rmg_drvstruct[0].RxSerial, 100))>0){
printk(KERN_INFO "Received data : %s\r\n", rmg_drvstruct[0].RxSerial);
serialfp->f_pos = pos;
file_write(serialfp,&pos,"I have received:",16);
file_write(serialfp,&pos, rmg_drvstruct[0].RxSerial,len);
serialfp->f_pos = pos;
}
file_sync(serialfp);
mutex_unlock(&serial_mutex);
mdelay(5);
}
}
so my device opening function is like:
static int device_open(struct inode *inode, struct file *file){
loff_t pos =0;
struct tty_struct *tty;
...
serialfp = file_open("/dev/ttyS1", O_RDWR | O_NOCTTY | O_NDELAY ,0);
if(serialfp == NULL)
printk(KERN_ALERT "ARIO RMG Serial openning error!!.\n");
else{
tty = (struct tty_struct *)serialfp->private_data;
tty_termios_encode_baud_rate(&tty->termios,B115200,B115200 );
printk(KERN_ALERT "Serial configured....\n");
pos = serialfp->f_pos;
file_write(serialfp,&pos,"\n\n\n\n\nThis is first test of sending serial data from kernel module\n\n\n\n\n",70);
serialfp->f_pos=pos;
file_sync(serialfp);
serial_thread_condition = 1;
mutex_init(&serial_mutex);
task1 = kthread_create(&thread_function, (void *)&pid1, "pradeep");
wake_up_process(task1);
}
...
}
and closing module function:
static int device_release(struct inode *inode, struct file *file){
...
if (!IS_ERR(serialfp)) {
mutex_lock(&serial_mutex);
printk(KERN_INFO " Trying to realease serail thread");
if(serial_thread_condition==1){
int i=0;
while(i++<256)serial_thread_condition =0;
}
printk(KERN_INFO " serial thread released.");
file_close(serialfp);
mutex_unlock(&serial_mutex);
}
....
}
Thanks to 0andriy for helping and dmeister for his answer in that link.
EDIT:
So I did what I wanted in kernel, opening a file in kernel space regardless of any suggestions not to do so.
But I needed to open a file in kernel module...
So why every body said don't open user space files in kernel?
In these two articles there are some reasons to not using files in kernel , this link and this link.
there are some reasons like:
1- kernel module may lose CPU at any time and the file which is opened by kernel may close.
2- I am not sure really about this, But they said files needs to have a process to stay opened but kernel module itself is not a process(maybe I am wrong!).
3- If any error happens while working with files(open/close/read/write), kernel module can not handle it and causes kernel panic...
I have experienced a lot of kernel panics only while opening and closing the file I wanted to work with. These are some of reasons why you should not use files in kernel modules, So as every body said before "If you need to use files in kernel module you probably did something wrong in your code!"
I am trying to implement simple debugfs interface module. Code attached for reference. To write data I'm using echo 'string' > /sys/kernel/debug/debugexercise/text and its working as expected data being copied into kernel buffer.
But when I try to retrieve data back using cat command i.e. cat /sys/kernel/debug/debugexercise/text , its not printing any data on terminal.
I have also tried using simple_read_from_buffer instead of copy_to_user but got the same result.
Anybody have idea what is the problem with this code. 4.13.0-45-generic is the kernel version on my system.
#include <linux/module.h>
#include <linux/debugfs.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#define LEN 512
static struct dentry *test_dir;
static struct dentry *test_file;
static char ker_buf[LEN] ;
/* read file operation */
static ssize_t test_read(struct file *fp, char __user *user_buffer, size_t count, loff_t *position){
printk(KERN_NOTICE "debugfs_read called, count %d\n", count);
return copy_to_user(user_buffer, ker_buf, LEN);
}
static ssize_t test_write(struct file *fp, const char __user *user_buffer, size_t count, loff_t *position){
printk(KERN_NOTICE "debugfs_write called, count %d\n",count);
if(count > LEN )
return -EINVAL;
copy_from_user(ker_buf, user_buffer, count);
printk(KERN_NOTICE "write buffer complete: %s\n",ker_buf);
return count;
}
static struct file_operations fops_debug = {
.read = test_read,
.write = test_write,
};
static int __init init_debug(void)
{
test_dir = debugfs_create_dir("debugexercise", NULL);
if(NULL == test_dir){
printk(KERN_ERR "debugfs_create_dir() Failed\n");
return -1;
}
else
printk(KERN_NOTICE "debugexercise created\n");
test_file = debugfs_create_file("text", 0644, test_dir, NULL, &fops_debug);
if(NULL == test_file){
printk(KERN_ERR "debugfs_create_file() Failed\n");
debugfs_remove(test_dir);
return -1;
}
else
printk(KERN_NOTICE "text under debugexercise created\n");
return 0;
}
static void __exit exit_debug(void)
{
printk(KERN_NOTICE "removing module\n");
debugfs_remove(test_file);
debugfs_remove(test_dir);
}
module_init(init_debug)
module_exit(exit_debug)
MODULE_LICENSE("GPL");
copy_to_user returns the number of bytes that could not be copied. On success, this will be zero. Hence, the cat displays 0 characters. I believe you should do:
if (copy_to_user(user_buffer, ker_buf, LEN)){
printk(KERN_INFO "copy to user failed.\n");
return -EINVAL; /* For instance ... */
}
return LEN;
So I am trying to implement and see how does a module creates a virtual file in /proc and this is a very simple code I wrote:
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <asm/uaccess.h>
#define BUF_SIZE 48
struct proc_dir_entry *proc_entry,*root_dir=NULL;
char result_buffer[BUF_SIZE];
char before[BUF_SIZE];
int count,temp=1;
ssize_t write(struct file *f, const char __user *buf, size_t len, loff_t *off)
{
printk(KERN_ERR "my4: Somebody toyed here\n");
if (copy_from_user(result_buffer,buf,len))
return -EFAULT;
return len;
}
// this read implementation is just a fudge, i am just trying to get the grasp of basic concepts here
ssize_t read(struct file *f, char __user *buf, size_t c, loff_t *off)
{
int len = 0;
if (temp == 0) {
temp = 1;
return 0;
}
sprintf(before,"[%s]",result_buffer);
if (count <= 1)
sprintf(result_buffer,"my4 read %d\n",count++);
else
sprintf(result_buffer,"my4 read again!! count=%d\n", count++);
len = strlen(result_buffer);
if (copy_to_user(buf, result_buffer, len)) return -EFAULT;
printk(KERN_ERR "my4: page before=[%s]\n\t page after=[%s]\n", before, result_buffer);
temp = 0;
return len;
}
static const struct file_operations file_ops = {
.owner = THIS_MODULE,
.read = read,
.write = write
};
int init_my4(void) {
count = 1;
proc_entry = proc_create("my4", 438, NULL, &file_ops);
strcpy(result_buffer, "initialized\n");
if (proc_entry == NULL) {
printk(KERN_ERR "my4: could not create proc entry\n");
return -ENOMEM;
}
printk(KERN_INFO "my4: Module loaded successfully\n");
return 0;
}
void unload_my4(void) {
remove_proc_entry("my4",root_dir);
printk(KERN_INFO "my4: Module unloaded successfully\n");
}
module_init(init_my4);
module_exit(unload_my4);
MODULE_LICENSE("GPL");
What the problem is that BUF_SIZE being just 48 if I write more than that to the file like :
echo "Lets write more than 48 bytes to the file and see if it breaks or overflows or if the kernel kills my process" > /proc/my4
Now when I cat /proc/my4 I get:
my4: Somebody toyed here
[23482.029609] my4: page before=[[Lets write more than 48 bytes to the file and see if it breaks my4 read again!! count=3
]
page after=[my4 read again!! count=3
I fail to understand what is happening here that is how does the kernel simply ignores the input greater than 48 bytes and where does it go?
As I thought that either it will overflow with a segfault or the kernel will kill the process!
I want to write a virtual sound card driver that shall be used by the linux system for audio playback and capture. The driver shall use a buffer for audio data read/write. I have written the following basic driver:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sound.h>
#include <linux/sysctl.h>
#include <linux/device.h>
#include <linux/slab.h> /* kmalloc() */
#include <linux/gfp.h>
#include <asm/uaccess.h> /* copy_from/to_user */
#include <linux/major.h>
#include <linux/fs.h>
#include <linux/pci.h>
#include <linux/err.h>
#include <sound/core.h>
static char* mod_name = "prosip";
MODULE_LICENSE("GPL");
MODULE_VERSION("0.0.1111");
MODULE_AUTHOR("DD-DDD");
MODULE_DESCRIPTION("proSip Virtual Sound Card");
//
static int ver_major = 133;
static int ver_minor = 3;
//
static int buffer_size = 0;
static char* buffer;
static int read_count = 0;
/* Declaration of memory.c functions */
int prosip_open(struct inode *inode, struct file *filp);
int prosip_release(struct inode *inode, struct file *filp);
//
ssize_t prosip_read(struct file *filp, char *buf, size_t count, loff_t *f_pos);
ssize_t prosip_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos);
//
int prosip_ioctl(struct inode *inode,struct file *file,unsigned int ioctl_num,unsigned long ioctl_param);
//
static int __init prosip_init(void);
static void __exit prosip_exit(void);
/* Structure that declares the usual file access functions */
struct file_operations sound_fops =
{
owner:
THIS_MODULE,
read:
prosip_read,
write:
prosip_write,
open:
prosip_open,
release:
prosip_release,
ioctl:
prosip_ioctl
};
/* Declaration of the init and exit functions */
module_init(prosip_init);
module_exit(prosip_exit);
static int __init prosip_init(void)
{
int ret = -1;
buffer_size = 0;
printk("<1>[prosip] Init...!\n");
ret = register_sound_dsp(&sound_fops, ver_minor);
if(ret < 0)
{
printk("<1> [prosip] Registration failure\n");
//
return ret;
}
else
{
ver_minor = ret;
//
printk("<1> [prosip] DSP Registered succesfully with id %d\n", ret);
}
buffer = kmalloc(101, GFP_KERNEL);
if(buffer == 0)
{
printk("<1>[prosip] Failed to allocate buffer !!!\n");
//
return -ENOMEM;
}
//
return 0;
}
static void __exit prosip_exit(void)
{
printk("<1> [prosip] Sound Exit...\n");
unregister_sound_special(ver_minor);
if(buffer)
{
kfree(buffer);
}
}
/* Declaration of memory.c functions */
int prosip_open(struct inode *inode, struct file *filp)
{
printk("<1> [prosip] Sound Open... \n");
try_module_get(THIS_MODULE);
return 0;
}
//
int prosip_release(struct inode *inode, struct file *filp)
{
printk("<1> [MySound] Sound Release... \n");
module_put(THIS_MODULE);
return 0;
}
//
ssize_t prosip_read(struct file *filp, char *buf, size_t count, loff_t *f_pos)
{
printk("<1> [prosip] Sound read...\n");
printk("<1> [prosip] Writing Count: %d\n", count);
if(buffer == 0)
{
printk("<1> NULL buffer!!! Unable to read");
return 0;
}
//
count = buffer_size;
if(read_count == 0)
{
read_count = buffer_size;
}
else
{
read_count = 0;
}
copy_to_user(buf, buffer, buffer_size);
printk("<1> [prosip] Buffer: %s, buf: %s, Count: %d\n", buffer, buf, count);
return read_count;
}
//
ssize_t prosip_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos)
{
printk("<1> [prosip] Sound write...!\n");
printk("<1> [prosip] Writing Count: %d\n", count);
//
if(buffer == 0)
{
printk("<1> NULL buffer!!! Unable to write");
return 0;
}
copy_from_user(buffer, buf, count);
buffer[count] = 0;
buffer_size = count;
printk("<1> [MySound] Writing Buffer: %s, Count: %d\n", buffer, count);
return count;
}
/*
* This function is called whenever a process tries to do an ioctl on our
* device file.
*
*/
int prosip_ioctl(struct inode *inode,
struct file *file,
unsigned int ioctl_num,
unsigned long ioctl_param)
{
//
return 0;
}
insmoding this module creates a driver at /dev/dsp. Also it is found in /sys/devices/virtual/sound/dsp/, so it is recognised by the system as a virtual audio driver.
I am not yet able to choose this device for playback and capture from applications. What else do I need to do to make this driver enumerated by audio applications?
Well /dev/dsp was the device node used for a sound card under the old OSS sound system in linux but these days pretty much everything will default to looking for devices based on the newer ALSA sound system.
Some software still supports OSS but you may well need to give it special options, or change the configuration, to tell it to use OSS instead of ALSA.
ALSA devices are found under /dev/snd and aren't normally accessed directly, as they are more complicated than the old OSS devices. Instead libasound is normally used to interact with them.
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.