ts-7000
[Top] [All Lists]

[ts-7000] Cannot mmap kmalloc'd memory in kernel module with userland =/

To:
Subject: [ts-7000] Cannot mmap kmalloc'd memory in kernel module with userland =/
From: "explosivedonut" <>
Date: Mon, 01 Mar 2010 01:37:17 -0000


I'm trying to write a small kernel module for TS-Linux ts-9 (Linux 2.4.26) on the TS-7200. All it should do is kmalloc a bit of memory and share it via mmap with a userland process. I've been trying lots of variations of code I've found on across the internet, but to no avail. I'm using remap_page_range in my kernel module's mmap function, which completes without error. However, trying to read/write to the mmap'ed buffer in userland does not work (always reads 0x0000 0000). I've also tried using get_free_page() instead of kmalloc and using nopage instead of remap_page_range to do the page table mapping.

I've exhausted every attempt to fix this that I can think of. If you could take a look at my code and let me know if you see something wrong, please let me know. All of the source/Makefile/terminal spew is available at http://drop.io/woddjcp/asset/kmod-tar-gz. I'm also copying the terminal spew/code below.

Thanks!
-Mike


Terminal Spew

$ insmod accel_kmod.o 
Using accel_kmod.o
Warning: loading accel_kmod will taint the kernel: no license
  See http://www.tux.org/lkml/#export-tainted for information about tainted modules
init_module()
Registration is a success. The major device number is 100.
If you want to talk to the device driver,
you'll have to create a device file.
We suggest you use mknod accel_kmod c 100 0
The device file name is important, because
the ioctl program assumes that's the
file you'll use.

virt_to_page(0xc4104000): 0xc00b2cb0
patterning memory
dma buffer: kernel=0xc4104000, phys=0x04104000
$ ./accel_client 
device_open(c51411a0)
device_mmap(0xc51411a0, 0xc1194b00)
remap_page_range(0x2aac1000,0x04104000,4096,0x0000003F)
dma_buf virt addr: 0x2aac1000

*** kernel mem stuff ***
0: 0x00000000 0x00000000
8: 0x00000000 0x00000000
16: 0x00000000 0x00000000
...
dma_buf[0]: 0x       0

*** writing 0xbeefbabe  to kernel mem ***

*** reading from kernel mem ***
dma_buf[0]: 0xBEEFBABE
device_release(c067da00,c51411a0)


accel_kmod.c

#define MODULE
#define LINUX
#define __KERNEL__

// We're doing kernel module work here.
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/mm.h>

// Deal with CONFIG_MODVERSIONS
#if CONFIG_MODVERSIONS==1
#define MODVERSIONS
#include <linux/modversions.h>
#endif

// Character device definitions are here
#include <linux/fs.h>

// A wrapper which does next to nothing
// at the present, but may help for compatibility
// with future versions of Linux.
#include <linux/wrapper.h>

// Our own IOCTL numbers
#include "chardev.h"

// For get_user and put_user
#include <asm/uaccess.h>

#define SUCCESS 0

// Pointer to unalgined data
static int *dma_buf_ptr = NULL;

// Pointer to page aligned area
static int *dma_buf_area = NULL;

// *** DEVICE DECLARATIONS ****************************************************

// The name for our device (as it will appear in /proc/devices)
#define DEVICE_NAME "accel_kmod"

// Is the device open right now? (used to prevent concurrent access into the 
// same device)
static int Device_Open = 0;

// This function is called whenever a process attempts to open the device file
static int device_open(struct inode *inode, struct file *file)
{
#ifdef DEBUG
  printk("device_open(%p)\n", file);
#endif // DEBUG
  
  // We don't want to talk to two processes at the same time
  if (Device_Open)
    {
      return -EBUSY;
    }

  // If this was a process, we would have to be more careful here, because one
  // process might have checked Device_Open right before the other one tried
  // to increment it. However, we're in the kernel, so we're protected against 
  // context switches.
  // 
  // This is NOT the right attidue to take, because we might be running on an
  // SMP box, but we'll deal with SMP later if need be.

  Device_Open++;

  MOD_INC_USE_COUNT;

  return SUCCESS;
}

// This function is called when a process closes the device file.
static int device_release(struct inode *inode, struct file *file)
{
#ifdef DEBUG
  printk("device_release(%p,%p)\n", inode, file);
#endif // DEBUG

  // We're now ready for our next caller
  Device_Open--;

  MOD_DEC_USE_COUNT;

  return 0;
}

// *** Device memory map method ***
// 2.4.x: this method is called from do_mmap_pgoff, from do_mmap, from the
//        syscall. The caller of do_mmap grabs the mm semaphore. So we are
//        protected from races here.

int device_mmap(struct file *file, struct vm_area_struct *vma)
{
  unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
  unsigned long size = vma->vm_end - vma->vm_start;

  printk("device_mmap(0x%p, 0x%p)\n", file, vma);

  if (offset & ~PAGE_MASK) {
    printk("offset not aligned: %ld\n", offset);
    return -ENXIO;
  }

  if (size > DMA_BUF_SIZE) {
    printk("size too big\n");
    return -ENXIO;
  }

  // We only support shared mappings. Copy on write mappings are rejected here.
  // A shared mapping that is writeable must have the shared flag set.
  if ((vma->vm_flags & VM_WRITE) && !(vma->vm_flags & VM_SHARED)) {
    printk("writeable mappings must be shared, rejecting\n");
    return -EINVAL;
  }

  // We do not want to have this area swapped out, lock it!
  vma->vm_flags |= VM_LOCKED | VM_RESERVED;

  // We create a mapping between the physical pages and the
  // virtual addresses of teh application with remap_page_range
  if (offset == 0) {
    printk("remap_page_range(0x%p,0x%p,%u,0x%08X)\n",
  vma->vm_start,
  (unsigned int)virt_to_phys(dma_buf_area),
  size, 
  vma->vm_page_prot);

    if (remap_page_range(vma->vm_start,
(unsigned int)virt_to_phys(dma_buf_area),
size,
vma->vm_page_prot)) {
      printk("remap page range failed\n");
      return -ENXIO;
    }
  } else {
    printk("offset out of range\n");
    return -ENXIO;
  }

  return SUCCESS;
}

// *** MODULE DECLARATIONS ****************************************************

// This structure will hold the functions to be called when a process does
// something to the device we created. Since a pointer to this structure is
// kept in the devices table, it can't be local to init_module. NULL is for 
// unimplemented functions.
struct file_operations Fops = {
 open:    device_open,
 release: device_release,
 mmap:    device_mmap
};

// Initialize the module - register the character device
int init_module()
{
  int ret_val;
  unsigned int i;
  unsigned long dmaBuf_va;
  struct page *page;

#ifdef DEBUG
  printk("init_module()\n");
#endif // DEBUG
  
  // Register the character device (at least try)
  ret_val = register_chrdev(MAJOR_NUM, DEVICE_NAME, &Fops);
  
  // Negative values signify an error
  if (ret_val < 0) {
    printk("Sorry, registering the character device failed with %d\n", 
  ret_val);
  }

  printk("Registration is a success. The major device number is %d.\n",
MAJOR_NUM);
  printk("If you want to talk to the device driver,\n"
"you'll have to create a device file.\n"
"We suggest you use mknod %s c %d 0\n", DEVICE_FILE_NAME, MAJOR_NUM);
  printk("The device file name is important, because\n"
"the ioctl program assumes that's the\n"
"file you'll use.\n\n");

  // allocated DMA buffer in kernel memory
  // Align memory to a page - it will be physically contiguous
  dma_buf_ptr = 
    (int *)kmalloc(DMA_BUF_SIZE+2*PAGE_SIZE, GFP_KERNEL | GFP_DMA);
  dma_buf_area = 
    (int *)(((unsigned long)dma_buf_ptr + PAGE_SIZE - 1) & PAGE_MASK);

  for (dmaBuf_va = (unsigned long)dma_buf_area;
       dmaBuf_va < (unsigned long)dma_buf_area + DMA_BUF_SIZE;
       dmaBuf_va += PAGE_SIZE) {
    
    // reserve all pages to make them remapable
    printk("virt_to_page(0x%p): 0x%p\n", dmaBuf_va, virt_to_page(dmaBuf_va));

    page = virt_to_page(dmaBuf_va);
    mem_map_reserve(page);
  }

  // Pattern the memory with some magic numbers (for testing!)
  printk("patterning memory\n");
  for (i = 0; i < (DMA_BUF_SIZE/4); i += 2) {
    dma_buf_area[i] = (0xdead << 16) + i;
    dma_buf_area[i+1] = (0xbeef << 16) + i;
  }

  // Report alloc memory info!
  printk("dma buffer: kernel=0x%p, phys=0x%p\n", 
dma_buf_area, virt_to_phys(dma_buf_area)); 

  return 0;
}

// Cleanup - unregister the appropriate file from /proc
void cleanup_module()
{
#ifdef DEBUG
  printk("cleanup_module()\n");
#endif // DEBUG

  int ret;
  unsigned long va;


  // Unreserve all kmalloc'd pages
  for (va = (unsigned long)dma_buf_area;
       va < (unsigned long)dma_buf_area + DMA_BUF_SIZE;
       va += PAGE_SIZE) {
    mem_map_unreserve(virt_to_page(va));
  }

  // Free kmallc'd pages
  if (NULL != dma_buf_area) {
    kfree((unsigned long)dma_buf_area);
  }

  // Unregister the device
  ret = unregister_chrdev(MAJOR_NUM, DEVICE_NAME);
  
  // If there's an error, report it
  if (ret < 0) {
    printk("Error in unregister_chrdev: %d\n", ret);
  }

}

chardev.h
#ifndef CHARDEV_H
#define CHARDEV_H

#include <linux/ioctl.h>

// The major device number. We can't rely on dynamic registration
// because ioctls need to know it.
#define MAJOR_NUM 100

// The name of the device file
#define DEVICE_FILE_NAME "accel_kmod"

#define DMA_BUF_SIZE 4096

#endif // CHARDEV_H


accel_client.c

#define MODULE
#define LINUX
#define __KERNEL__

// We're doing kernel module work here.
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/mm.h>

// Deal with CONFIG_MODVERSIONS
#if CONFIG_MODVERSIONS==1
#define MODVERSIONS
#include <linux/modversions.h>
#endif

// Character device definitions are here
#include <linux/fs.h>

// A wrapper which does next to nothing
// at the present, but may help for compatibility
// with future versions of Linux.
#include <linux/wrapper.h>

// Our own IOCTL numbers
#include "chardev.h"

// For get_user and put_user
#include <asm/uaccess.h>

#define SUCCESS 0

// Pointer to unalgined data
static int *dma_buf_ptr = NULL;

// Pointer to page aligned area
static int *dma_buf_area = NULL;

// *** DEVICE DECLARATIONS ****************************************************

// The name for our device (as it will appear in /proc/devices)
#define DEVICE_NAME "accel_kmod"

// Is the device open right now? (used to prevent concurrent access into the 
// same device)
static int Device_Open = 0;

// This function is called whenever a process attempts to open the device file
static int device_open(struct inode *inode, struct file *file)
{
#ifdef DEBUG
  printk("device_open(%p)\n", file);
#endif // DEBUG
  
  // We don't want to talk to two processes at the same time
  if (Device_Open)
    {
      return -EBUSY;
    }

  // If this was a process, we would have to be more careful here, because one
  // process might have checked Device_Open right before the other one tried
  // to increment it. However, we're in the kernel, so we're protected against 
  // context switches.
  // 
  // This is NOT the right attidue to take, because we might be running on an
  // SMP box, but we'll deal with SMP later if need be.

  Device_Open++;

  MOD_INC_USE_COUNT;

  return SUCCESS;
}

// This function is called when a process closes the device file.
static int device_release(struct inode *inode, struct file *file)
{
#ifdef DEBUG
  printk("device_release(%p,%p)\n", inode, file);
#endif // DEBUG

  // We're now ready for our next caller
  Device_Open--;

  MOD_DEC_USE_COUNT;

  return 0;
}

// *** Device memory map method ***
// 2.4.x: this method is called from do_mmap_pgoff, from do_mmap, from the
//        syscall. The caller of do_mmap grabs the mm semaphore. So we are
//        protected from races here.

int device_mmap(struct file *file, struct vm_area_struct *vma)
{
  unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
  unsigned long size = vma->vm_end - vma->vm_start;

  printk("device_mmap(0x%p, 0x%p)\n", file, vma);

  if (offset & ~PAGE_MASK) {
    printk("offset not aligned: %ld\n", offset);
    return -ENXIO;
  }

  if (size > DMA_BUF_SIZE) {
    printk("size too big\n");
    return -ENXIO;
  }

  // We only support shared mappings. Copy on write mappings are rejected here.
  // A shared mapping that is writeable must have the shared flag set.
  if ((vma->vm_flags & VM_WRITE) && !(vma->vm_flags & VM_SHARED)) {
    printk("writeable mappings must be shared, rejecting\n");
    return -EINVAL;
  }

  // We do not want to have this area swapped out, lock it!
  vma->vm_flags |= VM_LOCKED | VM_RESERVED;

  // We create a mapping between the physical pages and the
  // virtual addresses of teh application with remap_page_range
  if (offset == 0) {
    printk("remap_page_range(0x%p,0x%p,%u,0x%08X)\n",
  vma->vm_start,
  (unsigned int)virt_to_phys(dma_buf_area),
  size, 
  vma->vm_page_prot);

    if (remap_page_range(vma->vm_start,
(unsigned int)virt_to_phys(dma_buf_area),
size,
vma->vm_page_prot)) {
      printk("remap page range failed\n");
      return -ENXIO;
    }
  } else {
    printk("offset out of range\n");
    return -ENXIO;
  }

  return SUCCESS;
}

// *** MODULE DECLARATIONS ****************************************************

// This structure will hold the functions to be called when a process does
// something to the device we created. Since a pointer to this structure is
// kept in the devices table, it can't be local to init_module. NULL is for 
// unimplemented functions.
struct file_operations Fops = {
 open:    device_open,
 release: device_release,
 mmap:    device_mmap
};

// Initialize the module - register the character device
int init_module()
{
  int ret_val;
  unsigned int i;
  unsigned long dmaBuf_va;
  struct page *page;

#ifdef DEBUG
  printk("init_module()\n");
#endif // DEBUG
  
  // Register the character device (at least try)
  ret_val = register_chrdev(MAJOR_NUM, DEVICE_NAME, &Fops);
  
  // Negative values signify an error
  if (ret_val < 0) {
    printk("Sorry, registering the character device failed with %d\n", 
  ret_val);
  }

  printk("Registration is a success. The major device number is %d.\n",
MAJOR_NUM);
  printk("If you want to talk to the device driver,\n"
"you'll have to create a device file.\n"
"We suggest you use mknod %s c %d 0\n", DEVICE_FILE_NAME, MAJOR_NUM);
  printk("The device file name is important, because\n"
"the ioctl program assumes that's the\n"
"file you'll use.\n\n");

  // allocated DMA buffer in kernel memory
  // Align memory to a page - it will be physically contiguous
  dma_buf_ptr = 
    (int *)kmalloc(DMA_BUF_SIZE+2*PAGE_SIZE, GFP_KERNEL | GFP_DMA);
  dma_buf_area = 
    (int *)(((unsigned long)dma_buf_ptr + PAGE_SIZE - 1) & PAGE_MASK);

  for (dmaBuf_va = (unsigned long)dma_buf_area;
       dmaBuf_va < (unsigned long)dma_buf_area + DMA_BUF_SIZE;
       dmaBuf_va += PAGE_SIZE) {
    
    // reserve all pages to make them remapable
    printk("virt_to_page(0x%p): 0x%p\n", dmaBuf_va, virt_to_page(dmaBuf_va));

    page = virt_to_page(dmaBuf_va);
    mem_map_reserve(page);
  }

  // Pattern the memory with some magic numbers (for testing!)
  printk("patterning memory\n");
  for (i = 0; i < (DMA_BUF_SIZE/4); i += 2) {
    dma_buf_area[i] = (0xdead << 16) + i;
    dma_buf_area[i+1] = (0xbeef << 16) + i;
  }

  // Report alloc memory info!
  printk("dma buffer: kernel=0x%p, phys=0x%p\n", 
dma_buf_area, virt_to_phys(dma_buf_area)); 

  return 0;
}

// Cleanup - unregister the appropriate file from /proc
void cleanup_module()
{
#ifdef DEBUG
  printk("cleanup_module()\n");
#endif // DEBUG

  int ret;
  unsigned long va;


  // Unreserve all kmalloc'd pages
  for (va = (unsigned long)dma_buf_area;
       va < (unsigned long)dma_buf_area + DMA_BUF_SIZE;
       va += PAGE_SIZE) {
    mem_map_unreserve(virt_to_page(va));
  }

  // Free kmallc'd pages
  if (NULL != dma_buf_area) {
    kfree((unsigned long)dma_buf_area);
  }

  // Unregister the device
  ret = unregister_chrdev(MAJOR_NUM, DEVICE_NAME);
  
  // If there's an error, report it
  if (ret < 0) {
    printk("Error in unregister_chrdev: %d\n", ret);
  }

}


Makefile

KMOD_TARGET   := accel_kmod
KMOD_CLIENT   := accel_client

INCLUDE := -isystem $(TS7200_KERNEL)/include
WARN    := -W
CFLAGS  := -O2 -DDEBUG $(WARN) $(INCLUDE)
CC      := arm-linux-gcc

all: $(KMOD_TARGET).o $(KMOD_CLIENT)

$(KMOD_TARGET).o: $(KMOD_TARGET).c

$(KMOD_CLIENT): $(KMOD_CLIENT).c
$(CC) -g -DDEBUG $(KMOD_CLIENT).c -o $(KMOD_CLIENT)

.PHONY: clean

clean:
rm -rf $(KMOD_TARGET).o $(KMOD_CLIENT)

deploy-kmod:
scp -P 5022 accel_kmod.o :/kmod/

deploy-client:
scp -P 5022 accel_client :/kmod/

deploy: all deploy-kmod deploy-client



__._,_.___


Your email settings: Individual Email|Traditional
Change settings via the Web (Yahoo! ID required)
Change settings via email: =Email Delivery: Digest | m("yahoogroups.com?subject","ts-7000-fullfeatured");=Change Delivery Format: Fully Featured">Switch to Fully Featured
Visit Your Group | Yahoo! Groups Terms of Use | =Unsubscribe

__,_._,___
<Prev in Thread] Current Thread [Next in Thread>
Admin

Disclaimer: Neither Andrew Taylor nor the University of NSW School of Computer and Engineering take any responsibility for the contents of this archive. It is purely a compilation of material sent by many people to the birding-aus mailing list. It has not been checked for accuracy nor its content verified in any way. If you wish to get material removed from the archive or have other queries about the archive e-mail Andrew Taylor at this address: andrewt@cse.unsw.EDU.AU