3
votes

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;
}
2

2 Answers

0
votes

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;
0
votes

There are still several potential issues in your code although you found the root cause.

  1. "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.

  2. 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.