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> |
---|---|---|
|
Previous by Date: | [ts-7000] Re: Why: Unable to interrupt RedBoot, Fahad C |
---|---|
Next by Date: | [ts-7000] [ts-7200] loading kernel from Compact flash, Marwa Mohamed |
Previous by Thread: | RE: [ts-7000] Help - confused by platform driver code., Sandesh Ghimire |
Next by Thread: | [ts-7000] FIXED: Cannot mmap kmalloc'd memory in kernel module with userland =/, explosivedonut |
Indexes: | [Date] [Thread] [Top] [All Lists] |
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