/* * Copyright (c) 2005 Topspin Communications. All rights reserved. * * This software is available to you under a choice of one of two * licenses. You may choose to be licensed under the terms of the GNU * General Public License (GPL) Version 2, available from the file * COPYING in the main directory of this source tree, or the * OpenIB.org BSD license below: * * Redistribution and use in source and binary forms, with or * without modification, are permitted provided that the following * conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the following * disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * $Id: $ */ #include #include #include #include #include #include #include #include #include #include #include #include MODULE_AUTHOR("Libor Michalek"); MODULE_DESCRIPTION("Get pages test"); MODULE_LICENSE("GPL"); enum { TEST_MAJOR = 232, TEST_MINOR = 255 }; #define TEST_DEV MKDEV(TEST_MAJOR, TEST_MINOR) enum { TEST_CMD_REGISTER = 1, TEST_CMD_UNREGISTER = 2, TEST_CMD_CHECK = 3 }; struct ioctl_arg { __u64 addr; __u64 size; }; struct region_root { struct semaphore mutex; struct list_head regions; /* list of pending events. */ struct file *filp; int nr_region; }; struct test_region { unsigned long user; unsigned long addr; unsigned long size; int nr_pages; struct page **pages; struct region_root *root; struct list_head region_list; /* member in root region list */ }; static void test_unlock(struct test_region *region) { long i; list_del(®ion->region_list); for (i = 0; i < region->nr_pages; i++) put_page(region->pages[i]); printk(KERN_ERR "TEST: Unlocked address <%016lx>\n", region->user); kfree(region->pages); kfree(region); } static struct test_region *test_lookup(struct region_root *root, unsigned long addr) { struct test_region *region; list_for_each_entry(region, &root->regions, region_list) if (region->user == addr) return region; return NULL; } static int test_lock(struct region_root *root, unsigned long uaddr, unsigned long size) { struct test_region *region; int nr_pages; int result; region = kmalloc(sizeof(*region), GFP_KERNEL); if (!region) return -ENOMEM; region->user = uaddr; region->addr = uaddr & PAGE_MASK; region->size = PAGE_ALIGN(size + (uaddr & ~PAGE_MASK)); region->root = root; nr_pages = (region->size + PAGE_SIZE-1) >> PAGE_SHIFT; region->pages = kmalloc(sizeof(struct page *) * nr_pages, GFP_KERNEL); if (!region->pages) { result = -ENOMEM; goto page_err; } region->nr_pages = get_user_pages(current, current->mm, region->addr, nr_pages, 1, 0, region->pages, NULL); if (region->nr_pages != nr_pages) { result = -EFAULT; goto get_err; } list_add_tail(®ion->region_list, &root->regions); printk(KERN_ERR "TEST: Locked address <%016lx>\n", region->user); return 0; get_err: kfree(region->pages); page_err: kfree(region); return result; } static int test_check(struct test_region *region) { struct page **pages; int nr_pages; int result = 0; int i; pages = kmalloc(sizeof(struct page *) * region->nr_pages, GFP_KERNEL); if (!pages) return -ENOMEM; nr_pages = get_user_pages(current, current->mm, region->addr, region->nr_pages, 1, 0, pages, NULL); if (region->nr_pages != nr_pages) { result = -EFAULT; goto get_err; } for (i = 0; i < nr_pages; i++) { if (region->pages[i] != pages[i]) printk(KERN_ERR "TEST: Check error <%p:%p> " "page <%u> of <%u>\n", pages[i], region->pages[i], i, nr_pages); put_page(pages[i]); } get_err: kfree(pages); return result; } static long test_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct region_root *root = filp->private_data; struct test_region *region; struct ioctl_arg ureq; int result = 0; if (!root) return -EINVAL; if (copy_from_user(&ureq, (void __user *)arg, sizeof(ureq))) return -EFAULT; down(&root->mutex); switch (cmd) { case TEST_CMD_REGISTER: result = test_lock(root, ureq.addr, ureq.size); break; case TEST_CMD_UNREGISTER: region = test_lookup(root, ureq.addr); if (!region) result = -ENOENT; else test_unlock(region); break; case TEST_CMD_CHECK: region = test_lookup(root, ureq.addr); if (!region) result = -ENOENT; else result = test_check(region); break; default: result = -ERANGE; break; } up(&root->mutex); return result; } static int test_open(struct inode *inode, struct file *filp) { struct region_root *root; root = kmalloc(sizeof(*root), GFP_KERNEL); if (!root) return -ENOMEM; memset(root, 0, sizeof(*root)); INIT_LIST_HEAD(&root->regions); init_MUTEX(&root->mutex); filp->private_data = root; root->filp = filp; printk(KERN_ERR "TEST: Created root struct\n"); return 0; } static int test_close(struct inode *inode, struct file *filp) { struct region_root *root = filp->private_data; struct test_region *region; down(&root->mutex); while (!list_empty(&root->regions)) { region = list_entry(root->regions.next, struct test_region, region_list); test_unlock(region); } up(&root->mutex); kfree(root); filp->private_data = NULL; printk(KERN_ERR "TEST: Deleted root struct\n"); return 0; } static struct file_operations test_fops = { .owner = THIS_MODULE, .open = test_open, .release = test_close, .compat_ioctl = test_ioctl, .unlocked_ioctl = test_ioctl, }; static struct cdev test_cdev; static int __init test_init(void) { int result; result = register_chrdev_region(TEST_DEV, 1, "mltest"); if (result) { printk(KERN_ERR "TEST: Error <%d> registering dev\n", result); goto err_chr; } cdev_init(&test_cdev, &test_fops); result = cdev_add(&test_cdev, TEST_DEV, 1); if (result) { printk(KERN_ERR "TEST: Error <%d> adding cdev\n", result); goto err_cdev; } return 0; err_cdev: unregister_chrdev_region(TEST_DEV, 1); err_chr: return result; } static void __exit test_cleanup(void) { cdev_del(&test_cdev); unregister_chrdev_region(TEST_DEV, 1); } module_init(test_init); module_exit(test_cleanup);