http://www.captain.at/howto-linux-device-driver-mmap.php

Linux Device Driver Template/Skeleton with memory mapping of kernel memory to user-space (mmap)


This is a modified version of the Linux Device Driver Template/Skeleton.

UPDATE 20-JAN-2006: THE EXAMPLE NOW ALSO WORKS WITH KERNEL 2.6!

NOTE: While the original Linux Device Driver Template/Skeleton works for kernel 2.4 and kernel 2.6, this mmap example works only with kernel 2.4 (the Makefile is the default makefile for both kernel branches).

The interrupt handler was removed - but it is possible to access kernel memory from userspace i.e. for high speed I/O operations. The driver creates a device entry in /dev/, which can be used to communicate with the kernel module (read, write, ioctl, mmap).

Compile everything with the "Makefile" and before you load the kernel module with
Kernel 2.4:
# insmod ./skeleton.o
-or-
Kernel 2.6:
# insmod ./skeleton.ko
you must create the device entry in /dev with
# mknod -m 666 /dev/skeleton c 240 0




After loading the module, start the userspace program "user". This program opens the device (/dev/skeleton), writes data to it and reads the data back (blocking read is disabled here).
# ./user
String 'Skeleton Kernel Module Test' written to /dev/skeleton
String 'Skeleton Kernel Module Test' read from /dev/skeleton
buffer[0]=0
buffer[4]=4
buffer[8]=8
buffer[12]=12
buffer[16]=16
buffer[20]=20
buffer[24]=24
buffer[28]=28
buffer[32]=32
buffer[36]=36
IOCTL test: written: '55555555' - received: '55555555'
#
After starting "user", you will see "String 'Skeleton Kernel Module Test' written to /dev/skeleton" and the string is read back. Furthermore the userspace program performs a mmap operation. When the kernel module is loaded, some buffer is allocated with kmalloc and some data is written to the buffer. The userspace application reads from the buffer and displays the values (the type of data in the buffer is controlled with "#define USEASCII").

The userspace program also does some basic IOCTL access - you can use this to send commands to the module.

Another way to test the driver:
Write some data to the module:
# cat > /dev/skeleton
this is a pen.
[press CTRL-D]
#
Read the data back:
# cat < /dev/skeleton
this is a pen.
#
Note about ClearPageReserved at the bottom


Also check the kernel log for messages:
# tail /var/log/kern.log
Jul 13 20:49:26 localhost kernel: initializing module
Jul 13 20:49:26 localhost kernel: kmalloc_area at 0xd5680000 (phys 0x81fa000)
Jul 13 20:49:26 localhost kernel: kmalloc_ptr[0]=0
Jul 13 20:49:26 localhost kernel: kmalloc_ptr[4]=4
Jul 13 20:49:26 localhost kernel: kmalloc_ptr[8]=8
Jul 13 20:49:26 localhost kernel: kmalloc_ptr[12]=12
Jul 13 20:49:26 localhost kernel: kmalloc_ptr[16]=16
Jul 13 20:49:26 localhost kernel: kmalloc_ptr[20]=20
Jul 13 20:49:26 localhost kernel: kmalloc_ptr[24]=24
Jul 13 20:49:26 localhost kernel: kmalloc_ptr[28]=28
Jul 13 20:49:26 localhost kernel: kmalloc_ptr[32]=32
Jul 13 20:49:26 localhost kernel: kmalloc_ptr[36]=36
Jul 13 20:49:28 localhost kernel: skeleton_open
Jul 13 20:49:28 localhost kernel: skeleton_release
Jul 13 20:49:32 localhost kernel: cleaning up module
The device driver "skeleton.c":
// Linux Device Driver Template/Skeleton with mmap
// Kernel Module

#include <linux/module.h>
#include <linux/init.h>
#include <linux/version.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/vmalloc.h>
#include <linux/mman.h>
#include <linux/slab.h>

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
#include <linux/wrapper.h>
#endif

#define SKELETON_MAJOR 240
#define SKELETON_NAME "skeleton"
#define CASE1 1
#define CASE2 2

static unsigned int counter = 0;
static char string [128];
static int data;

//#define USEASCII

#ifdef USEASCII
static char *kmalloc_area = NULL;
static char *kmalloc_ptr = NULL;
#else
static unsigned int *kmalloc_area = NULL;
static unsigned int *kmalloc_ptr = NULL;
#endif

#define LEN (64*1024)
unsigned long virt_addr;

DECLARE_WAIT_QUEUE_HEAD(skeleton_wait);
static int data_not_ready = 0;

// open function - called when the "file" /dev/skeleton is opened in userspace
static int skeleton_open (struct inode *inode, struct file *file) {
	printk("skeleton_open\n");
	// we could do some checking on the flags supplied by "open"
	// i.e. O_NONBLOCK
	// -> set some flag to disable interruptible_sleep_on in skeleton_read
	return 0;
}

// close function - called when the "file" /dev/skeleton is closed in userspace  
static int skeleton_release (struct inode *inode, struct file *file) {
	printk("skeleton_release\n");
	return 0;
}

// read function called when from /dev/skeleton is read
static ssize_t skeleton_read (struct file *file, char *buf,
		size_t count, loff_t *ppos) {
	int len, err;
	
	// check if we have data - if not, sleep
	// wake up in interrupt_handler
	while (data_not_ready) {
		interruptible_sleep_on(&skeleton_wait);
	}
	//data_not_ready = 1;
	
	if( counter <= 0 ) 
		return 0;
	err = copy_to_user(buf,string,counter);
	if (err != 0)
		return -EFAULT;
	len  = counter;
	counter = 0;
	return len;
}

// write function called when to /dev/skeleton is written
static ssize_t skeleton_write (struct file *file, const char *buf,
		size_t count, loff_t *ppos) {
	int err;
	err = copy_from_user(string,buf,count);
	if (err != 0)
		return -EFAULT;
	counter += count;
	return count;
}

// ioctl - I/O control
static int skeleton_ioctl(struct inode *inode, struct file *file,
		unsigned int cmd, unsigned long arg) {
	int retval = 0;
	switch ( cmd ) {
		case CASE1:/* for writing data to arg */
			if (copy_from_user(&data, (int *)arg, sizeof(int)))
			return -EFAULT;
			break;
		case CASE2:/* for reading data from arg */
			if (copy_to_user((int *)arg, &data, sizeof(int)))
			return -EFAULT;
			break;
		default:
			retval = -EINVAL;
	}
	return retval;
}

#ifndef VMALLOC_VMADDR
#define VMALLOC_VMADDR(x) ((unsigned long)(x))
#endif

// From: http://www.scs.ch/~frey/linux/memorymap.html
volatile void *virt_to_kseg(volatile void *address) {
	pgd_t *pgd; pmd_t *pmd; pte_t *ptep, pte;
	unsigned long va, ret = 0UL;
	va=VMALLOC_VMADDR((unsigned long)address);
	/* get the page directory. Use the kernel memory map. */
	pgd = pgd_offset_k(va);
	/* check whether we found an entry */
	if (!pgd_none(*pgd)) {
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
		/* get the page middle directory */
		pmd = pmd_offset(pgd, va);
#else
		// I'm not sure if we need this, or the line for 2.4
		//    above will work reliably too
		// If you know, please email me :-)
		pud_t *pud = pud_offset(pgd, va);		
		pmd = pmd_offset(pud, va);
#endif
		/* check whether we found an entry */
		if (!pmd_none(*pmd)) {
			/* get a pointer to the page table entry */
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
			ptep = pte_offset(pmd, va);
#else
			ptep = pte_offset_map(pmd, va);
#endif
			pte = *ptep;
			/* check for a valid page */
			if (pte_present(pte)) {
				/* get the address the page is refering to */
				ret = (unsigned long)page_address(pte_page(pte));
				/* add the offset within the page to the page address */
				ret |= (va & (PAGE_SIZE -1));
			}
		}
	}
	return((volatile void *)ret);
}

static int skeleton_mmap(struct file * filp, struct vm_area_struct * vma) {
	int ret;
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
	ret = remap_page_range(vma->vm_start,
			virt_to_phys((void*)((unsigned long)kmalloc_area)),
			vma->vm_end-vma->vm_start,
			PAGE_SHARED);
//			       vma->vm_page_prot);
#else
        ret = remap_pfn_range(vma,
               vma->vm_start,
               virt_to_phys((void*)((unsigned long)kmalloc_area)) >> PAGE_SHIFT,
               vma->vm_end-vma->vm_start,
               PAGE_SHARED);
//               vma->vm_page_prot); 
#endif
	if(ret != 0) {
		return -EAGAIN;
	}
	return 0;
}

// define which file operations are supported
struct file_operations skeleton_fops = {
	.owner	=	THIS_MODULE,
	.llseek	=	NULL,
	.read		=	skeleton_read,
	.write	=	skeleton_write,
	.readdir	=	NULL,
	.poll		=	NULL,
	.ioctl	=	skeleton_ioctl,
	.mmap		=	skeleton_mmap,
	.open		=	skeleton_open,
	.flush	=	NULL,
	.release	=	skeleton_release,
	.fsync	=	NULL,
	.fasync	=	NULL,
	.lock		=	NULL,
	.readv	=	NULL,
	.writev	=	NULL,
};

// initialize module
static int __init skeleton_init_module (void) {
	int i;
#ifndef USEASCII
	int tmp, tmp2;
#endif
	printk("initializing module\n");
	
	i = register_chrdev (SKELETON_MAJOR, SKELETON_NAME, &skeleton_fops);
	if (i != 0) return - EIO;
	
	// reserve memory with kmalloc - Allocating Memory in the Kernel
	kmalloc_ptr = kmalloc(LEN + 2 * PAGE_SIZE, GFP_KERNEL);
	if (!kmalloc_ptr) {
		printk("kmalloc failed\n");
		return 0;
	}
#ifdef USEASCII
	kmalloc_area = (char *)(((unsigned long)kmalloc_ptr + PAGE_SIZE -1) & PAGE_MASK);
#else
	kmalloc_area = (unsigned int *)(((unsigned long)kmalloc_ptr + PAGE_SIZE -1) & PAGE_MASK);
#endif
	for (virt_addr=(unsigned long)kmalloc_area; virt_addr < (unsigned long)kmalloc_area + LEN;
		virt_addr+=PAGE_SIZE) {
			// reserve all pages to make them remapable
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
			mem_map_reserve(virt_to_page(virt_addr));
#else
			SetPageReserved(virt_to_page(virt_addr));
#endif
	}
	printk("kmalloc_area at 0x%p (phys 0x%lx)\n", kmalloc_area,
		virt_to_phys((void *)virt_to_kseg(kmalloc_area)));

#ifdef USEASCII
	// fill allocated memory with 0123456789 ascii
	for( i = 48; i < 58; i++) {
		kmalloc_ptr[i-48] = (char)i;
	}
	i = 0;
	kmalloc_ptr[58-48] = (char)i;
#else
	// fill allocated memory with integers
	tmp = sizeof(int);
	for( i = 0; i < (10 * tmp); i = i + tmp) {
		kmalloc_ptr[i] = (unsigned int)i;
      
		tmp2 = (unsigned int)kmalloc_ptr[i];
		printk("kmalloc_ptr[%d]=%d\n", i, tmp2);
	}
#endif

	return 0;
}

// close and cleanup module
static void __exit skeleton_cleanup_module (void) {
	printk("cleaning up module\n");
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
	for (virt_addr=(unsigned long)kmalloc_area; virt_addr < (unsigned long)kmalloc_area + LEN;
		virt_addr+=PAGE_SIZE) {
			// clear all pages
			ClearPageReserved(virt_to_page(virt_addr));
	}
#endif
	kfree(kmalloc_ptr);
	unregister_chrdev (SKELETON_MAJOR, SKELETON_NAME);
}

module_init(skeleton_init_module);
module_exit(skeleton_cleanup_module);
MODULE_AUTHOR("www.captain.at");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Linux Device Driver Template with MMAP");


Userspace test program "user.c"
// Linux Device Driver Template/Skeleton with mmap
// Userspace test program

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/mman.h>

#define CASE1 1
#define CASE2 2
#define BUFSIZE 64*1024

//#define USEASCII

main() {
	int i, fd, len, wlen, tmp, tmp2;
	char string[] = "Skeleton Kernel Module Test";
	char receive[128];
	int data, rdata;

	char * mptr;
	size_t size = BUFSIZE;
#ifdef USEASCII	
	char buffer[BUFSIZE];
#else
	unsigned int buffer[BUFSIZE];
#endif
	fd = open("/dev/skeleton", O_RDWR | O_SYNC);
	if( fd == -1) {
		printf("open error...\n");
		exit(0);
	}

	// test device write function
	wlen = strlen(string) + 1;
	len = write(fd, string, wlen);
	if( len == -1 ) {
		printf("write error...\n");
		exit(1);
	}
	printf("String '%s' written to /dev/skeleton\n", string);
	
	// test device read function
	len = read(fd, receive, 128);
	if( len == -1 ) {
		printf("read error...\n");
		exit(1);
	}
	printf("String '%s' read from /dev/skeleton\n", receive);

	// test mmap
	mptr = mmap(0, size, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, fd, 0);
	if(mptr == MAP_FAILED) {
		printf("mmap() failed\n");
		exit(1);
	}
	// write something into the kernel device driver memory allocated by kmalloc
//	memcpy(mptr, "Hello World!", 13);
	// clear our local buffer
	memset(buffer, 0, size);
	// read from the kmalloc area in kernel space
	memcpy(buffer, mptr, size-1);

#ifdef USEASCII
	printf("mmap: '%s'\n", buffer);
#else
	tmp = sizeof(int);
	for( i = 0; i < (10 * tmp); i = i + tmp) {
		tmp2 = (unsigned int)buffer[i];
		printf("buffer[%d]=%d\n", i, tmp2);
	}
#endif

	// test ioctl
	data = ""
	ioctl(fd, CASE1, &data);
	ioctl(fd, CASE2, &rdata);
	printf("IOCTL test: written: '%x' - received: '%x'\n", data, rdata);

	munmap(mptr, size);
	close(fd);
}


Makefile
UNAME := $(shell uname -r)
KERNEL26 := 2.6
KERNELVERSION := $(findstring $(KERNEL26),$(UNAME))

all:: user

ifeq ($(KERNELVERSION),2.6)

obj-m	:= skeleton.o

INCLUDE	:= -I/usr/include/asm/mach-default/
KDIR	:= /lib/modules/$(shell uname -r)/build
PWD		:= $(shell pwd)

all::
	$(MAKE) -C $(KDIR) $(INCLUDE) SUBDIRS=$(PWD) modules
	
else

TARGET	:= skeleton
INCLUDE	:= -I/lib/modules/`uname -r`/build/include -I/usr/include/asm/mach-default/
CFLAGS	:= -O2 -Wall -DMODULE -D__KERNEL__ -DLINUX
CC	:= gcc

all:: ${TARGET}.o

${TARGET}.o: ${TARGET}.c
	$(CC) $(CFLAGS) ${INCLUDE} -c ${TARGET}.c
	
endif

user: user.c
	gcc -o $@ $<

clean::
	$(RM) .skeleton* *.cmd *.o *.ko *.mod.c
	$(RM) -R .tmp*


Don't forget to call ClearPageReserved on every page, other wise you'll see this:
Bad page state at __free_pages_ok (in process 'events/0', page c1035520)
flags:0x80000404 mapping:00000000 mapcount:0 count:0
Backtrace:
 [bad_page+129/192] bad_page+0x81/0xc0
 [__free_pages_ok+112/256] __free_pages_ok+0x70/0x100
 [free_hot_cold_page+272/400] free_hot_cold_page+0x110/0x190
 [__free_pages+76/96] __free_pages+0x4c/0x60
 [kmem_freepages+136/176] kmem_freepages+0x88/0xb0
 [slab_destroy+104/176] slab_destroy+0x68/0xb0
 [cache_reap+345/640] cache_reap+0x159/0x280
 [cache_reap+0/640] cache_reap+0x0/0x280
 [worker_thread+546/880] worker_thread+0x222/0x370
 [default_wake_function+0/32] default_wake_function+0x0/0x20
 [default_wake_function+0/32] default_wake_function+0x0/0x20
 [worker_thread+0/880] worker_thread+0x0/0x370
 [kthread+186/192] kthread+0xba/0xc0
 [kthread+0/192] kthread+0x0/0xc0
 [kernel_thread_helper+5/12] kernel_thread_helper+0x5/0xc
Trying to fix it up, but a reboot is needed


Reference:
http://www.scs.ch/~frey/linux/memorymap.html

Reply via email to