blob: 61ecc3decee6befc352ad20941b316b8eb53adc7 [file] [log] [blame]
/* Copyright (c) 2013, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/mm.h>
#include <asm/cacheflush.h>
#include <asm-generic/sizes.h>
#include <mach/iommu_domains.h>
#include <linux/msm_ion.h>
#include <asm/uaccess.h>
#include "memory_prof_module.h"
#include "timing_debug.h"
#define MEMORY_PROF_DEV_NAME "memory_prof"
struct memory_prof_module_data {
struct ion_client *client;
};
static struct class *memory_prof_class;
static int memory_prof_major;
static struct device *memory_prof_dev;
static void zero_the_pages_plz(struct page **pages, int npages,
unsigned int page_size)
{
void *ptr;
int len = npages * page_size;
ptr = vmap(pages, npages, VM_IOREMAP, pgprot_writecombine(PAGE_KERNEL));
if (ptr == NULL) {
WARN(1, "BOGUS vmap ERROR!!!\n");
} else {
memset(ptr, 0, len);
dmac_flush_range(ptr, ptr + len);
vunmap(ptr);
}
}
static void test_kernel_alloc(const char *tag, gfp_t thegfp,
unsigned int page_size,
void (*transform_page)(struct page *),
void (*transform_pages)(struct page **, int, unsigned int))
{
char st1[200];
char st2[200];
int i, j;
int order = get_order(page_size);
int size = (SZ_1M * 20);
int npages = PAGE_ALIGN(size) / page_size;
struct page **pages;
pages = kmalloc(npages * sizeof(struct page *),
GFP_KERNEL);
snprintf(st1, 200, "before %s%s", tag,
transform_page ? " (flush each)" : "");
snprintf(st2, 200, "after %s%s", tag,
transform_page ? " (flush each)" : "");
timing_debug_tick(st1);
for (i = 0; i < npages; ++i) {
pages[i] = alloc_pages(thegfp, order);
if (!pages[i]) {
WARN(1, "BOGUS alloc_pages ERROR!!!\n");
npages = i;
goto out;
}
if (transform_page)
for (j = 0; j < order; j++)
transform_page(nth_page(pages[i], j));
}
timing_debug_tock(st2);
if (transform_pages) {
timing_debug_tick("before transform_pages");
transform_pages(pages, npages, page_size);
timing_debug_tock("after transform_pages");
}
out:
for (i = 0; i < npages; ++i)
__free_pages(pages[i], order);
kfree(pages);
}
#define DO_TEST_KERNEL_ALLOC(thegfp, sz, transform_page, transform_pages) \
test_kernel_alloc(#thegfp " " #sz, thegfp, sz, \
transform_page, transform_pages)
static void test_kernel_allocs(void)
{
int i;
timing_debug_init();
timing_debug_start_new_timings();
pr_err("Testing small pages without flushing individual pages\n");
for (i = 0; i < 7; ++i)
DO_TEST_KERNEL_ALLOC(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO,
PAGE_SIZE, NULL, NULL);
for (i = 0; i < 7; ++i)
DO_TEST_KERNEL_ALLOC(GFP_KERNEL | __GFP_HIGHMEM,
PAGE_SIZE, NULL, NULL);
for (i = 0; i < 7; ++i)
DO_TEST_KERNEL_ALLOC(GFP_KERNEL | __GFP_HIGHMEM,
PAGE_SIZE, NULL, zero_the_pages_plz);
for (i = 0; i < 7; ++i)
DO_TEST_KERNEL_ALLOC(GFP_KERNEL | __GFP_ZERO,
PAGE_SIZE, NULL, NULL);
for (i = 0; i < 7; ++i)
DO_TEST_KERNEL_ALLOC(GFP_KERNEL,
PAGE_SIZE, NULL, NULL);
for (i = 0; i < 7; ++i)
DO_TEST_KERNEL_ALLOC(GFP_KERNEL,
PAGE_SIZE, NULL, zero_the_pages_plz);
pr_err("Testing small pages with flushing individual pages\n");
for (i = 0; i < 7; ++i)
DO_TEST_KERNEL_ALLOC(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO,
PAGE_SIZE, flush_dcache_page, NULL);
for (i = 0; i < 7; ++i)
DO_TEST_KERNEL_ALLOC(GFP_KERNEL | __GFP_HIGHMEM,
PAGE_SIZE, flush_dcache_page, NULL);
for (i = 0; i < 7; ++i)
DO_TEST_KERNEL_ALLOC(GFP_KERNEL | __GFP_HIGHMEM,
PAGE_SIZE, flush_dcache_page,
zero_the_pages_plz);
for (i = 0; i < 7; ++i)
DO_TEST_KERNEL_ALLOC(GFP_KERNEL | __GFP_ZERO,
PAGE_SIZE, flush_dcache_page, NULL);
for (i = 0; i < 7; ++i)
DO_TEST_KERNEL_ALLOC(GFP_KERNEL,
PAGE_SIZE, flush_dcache_page, NULL);
for (i = 0; i < 7; ++i)
DO_TEST_KERNEL_ALLOC(GFP_KERNEL,
PAGE_SIZE, flush_dcache_page,
zero_the_pages_plz);
pr_err("Testing with large page sizes without flushing individual pages\n");
for (i = 0; i < 7; ++i)
DO_TEST_KERNEL_ALLOC(
GFP_KERNEL | __GFP_HIGHMEM | __GFP_COMP | __GFP_ZERO,
SZ_64K, NULL, NULL);
for (i = 0; i < 7; ++i)
DO_TEST_KERNEL_ALLOC(
GFP_KERNEL | __GFP_HIGHMEM | __GFP_COMP,
SZ_64K, NULL, NULL);
for (i = 0; i < 7; ++i)
DO_TEST_KERNEL_ALLOC(
GFP_KERNEL | __GFP_COMP | __GFP_ZERO,
SZ_64K, NULL, NULL);
for (i = 0; i < 7; ++i)
DO_TEST_KERNEL_ALLOC(
GFP_KERNEL | __GFP_COMP,
SZ_64K, NULL, NULL);
pr_err("Testing with large page sizes with flushing individual pages\n");
for (i = 0; i < 7; ++i)
DO_TEST_KERNEL_ALLOC(
GFP_KERNEL | __GFP_HIGHMEM | __GFP_COMP | __GFP_ZERO,
SZ_64K, flush_dcache_page, NULL);
for (i = 0; i < 7; ++i)
DO_TEST_KERNEL_ALLOC(
GFP_KERNEL | __GFP_HIGHMEM | __GFP_COMP,
SZ_64K, flush_dcache_page, NULL);
for (i = 0; i < 7; ++i)
DO_TEST_KERNEL_ALLOC(
GFP_KERNEL | __GFP_COMP | __GFP_ZERO,
SZ_64K, flush_dcache_page, NULL);
for (i = 0; i < 7; ++i)
DO_TEST_KERNEL_ALLOC(
GFP_KERNEL | __GFP_COMP,
SZ_64K, flush_dcache_page, NULL);
timing_debug_dump_results();
}
static long memory_prof_test_ioctl(struct file *file, unsigned cmd, unsigned long arg)
{
int ret = 0;
struct memory_prof_module_data *module_data = file->private_data;
switch (cmd) {
case MEMORY_PROF_IOC_CLIENT_CREATE:
{
module_data->client = msm_ion_client_create(~0, "memory_prof_module");
if (IS_ERR_OR_NULL(module_data->client))
return -EIO;
break;
}
case MEMORY_PROF_IOC_CLIENT_DESTROY:
{
ion_client_destroy(module_data->client);
break;
}
case MEMORY_PROF_IOC_TEST_MAP_EXTRA:
{
struct memory_prof_map_extra_args args;
struct ion_handle *handle;
struct ion_client *client = module_data->client;
unsigned long iova, buffer_size;
if (copy_from_user(&args, (void __user *)arg,
sizeof(struct memory_prof_map_extra_args)))
return -EFAULT;
handle = ion_import_dma_buf(client, args.ionfd);
if (IS_ERR_OR_NULL(handle)) {
pr_err("Couldn't do ion_import_dma_buf in "
"MEMORY_PROF_IOC_TEST_MAP_EXTRA\n");
return -EINVAL;
}
ret = ion_map_iommu(client, handle, VIDEO_DOMAIN,
VIDEO_FIRMWARE_POOL, SZ_8K,
args.iommu_map_len, &iova, &buffer_size,
0, 0);
if (ret) {
pr_err("Couldn't ion_map_iommu in "
"MEMORY_PROF_IOC_TEST_MAP_EXTRA\n");
return ret;
}
break;
}
case MEMORY_PROF_IOC_TEST_KERNEL_ALLOCS:
{
test_kernel_allocs();
break;
}
default:
pr_info("command not supproted\n");
ret = -EINVAL;
}
return ret;
}
static int memory_prof_test_open(struct inode *inode, struct file *file)
{
struct memory_prof_module_data *module_data = kzalloc(
sizeof(struct memory_prof_module_data), GFP_KERNEL);
if (!module_data)
return -ENOMEM;
file->private_data = module_data;
pr_info("memory_prof test device opened\n");
return 0;
}
static int memory_prof_test_release(struct inode *inode, struct file *file)
{
struct memory_prof_module_data *module_data = file->private_data;
kfree(module_data);
pr_info("memory_prof test device closed\n");
return 0;
}
static const struct file_operations memory_prof_test_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = memory_prof_test_ioctl,
.open = memory_prof_test_open,
.release = memory_prof_test_release,
};
static int memory_prof_device_create(void)
{
int rc = 0;
memory_prof_major = register_chrdev(0, MEMORY_PROF_DEV_NAME,
&memory_prof_test_fops);
if (memory_prof_major < 0) {
rc = memory_prof_major;
pr_err("Unable to register chrdev: %d\n", memory_prof_major);
goto out;
}
memory_prof_class = class_create(THIS_MODULE, MEMORY_PROF_DEV_NAME);
if (IS_ERR(memory_prof_class)) {
rc = PTR_ERR(memory_prof_class);
pr_err("Unable to create class: %d\n", rc);
goto err_create_class;
}
memory_prof_dev = device_create(memory_prof_class, NULL,
MKDEV(memory_prof_major, 0),
NULL, MEMORY_PROF_DEV_NAME);
if (IS_ERR(memory_prof_dev)) {
rc = PTR_ERR(memory_prof_dev);
pr_err("Unable to create device: %d\n", rc);
goto err_create_device;
}
return rc;
err_create_device:
class_destroy(memory_prof_class);
err_create_class:
unregister_chrdev(memory_prof_major, MEMORY_PROF_DEV_NAME);
out:
return rc;
}
static void memory_prof_device_destroy(void)
{
device_destroy(memory_prof_class, MKDEV(memory_prof_major, 0));
class_destroy(memory_prof_class);
unregister_chrdev(memory_prof_major, MEMORY_PROF_DEV_NAME);
}
static int memory_prof_test_init(void)
{
return memory_prof_device_create();
}
static void memory_prof_test_exit(void)
{
return memory_prof_device_destroy();
}
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Profile memory stuff");
module_init(memory_prof_test_init);
module_exit(memory_prof_test_exit);