/* Copyright (c) 2012, 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/slab.h>
#include <linux/mm.h>
#include <linux/rbtree.h>
#include <linux/idr.h>
#include <linux/genalloc.h>
#include <linux/of.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <mach/ocmem_priv.h>

enum request_states {
	R_FREE = 0x0,	/* request is not allocated */
	R_PENDING,	/* request has a pending operation */
	R_ALLOCATED,	/* request has been allocated */
	R_MUST_GROW,	/* request must grow as a part of pending operation */
	R_MUST_SHRINK,	/* request must shrink as a part of pending operation */
	R_MUST_MAP,	/* request must be mapped before being used */
	R_MUST_UNMAP,	/* request must be unmapped when not being used */
	R_MAPPED,	/* request is mapped and actively used by client */
	R_UNMAPPED,	/* request is not mapped, so it's not in active use */
	R_EVICTED,	/* request is evicted and must be restored */
};

#define SET_STATE(x, val) (set_bit((val), &(x)->state))
#define CLEAR_STATE(x, val) (clear_bit((val), &(x)->state))
#define TEST_STATE(x, val) (test_bit((val), &(x)->state))

enum op_res {
	OP_COMPLETE = 0x0,
	OP_RESCHED,
	OP_PARTIAL,
	OP_FAIL = ~0x0,
};

/* Represents various client priorities */
/* Note: More than one client can share a priority level */
enum client_prio {
	MIN_PRIO = 0x0,
	NO_PRIO = MIN_PRIO,
	PRIO_SENSORS = 0x1,
	PRIO_OTHER_OS = 0x1,
	PRIO_LP_AUDIO = 0x1,
	PRIO_HP_AUDIO = 0x2,
	PRIO_VOICE = 0x3,
	PRIO_GFX_GROWTH = 0x4,
	PRIO_VIDEO = 0x5,
	PRIO_GFX = 0x6,
	PRIO_OCMEM = 0x7,
	MAX_OCMEM_PRIO = PRIO_OCMEM + 1,
};

static struct list_head sched_queue[MAX_OCMEM_PRIO];
static struct mutex sched_queue_mutex;

/* The duration in msecs before a pending operation is scheduled
 * This allows an idle window between use case boundaries where various
 * hardware state changes can occur. The value will be tweaked on actual
 * hardware.
*/
#define SCHED_DELAY 10

static struct list_head rdm_queue;
static struct mutex rdm_mutex;
static struct workqueue_struct *ocmem_rdm_wq;
static struct workqueue_struct *ocmem_eviction_wq;

static struct ocmem_eviction_data *evictions[OCMEM_CLIENT_MAX];

struct ocmem_rdm_work {
	int id;
	struct ocmem_map_list *list;
	struct ocmem_handle *handle;
	int direction;
	struct work_struct work;
};

/* OCMEM Operational modes */
enum ocmem_client_modes {
	OCMEM_PERFORMANCE = 1,
	OCMEM_PASSIVE,
	OCMEM_LOW_POWER,
	OCMEM_MODE_MAX = OCMEM_LOW_POWER
};

/* OCMEM Addressing modes */
enum ocmem_interconnects {
	OCMEM_BLOCKED = 0,
	OCMEM_PORT = 1,
	OCMEM_OCMEMNOC = 2,
	OCMEM_SYSNOC = 3,
};

/**
 * Primary OCMEM Arbitration Table
 **/
struct ocmem_table {
	int client_id;
	int priority;
	int mode;
	int hw_interconnect;
} ocmem_client_table[OCMEM_CLIENT_MAX] = {
	{OCMEM_GRAPHICS, PRIO_GFX, OCMEM_PERFORMANCE, OCMEM_PORT},
	{OCMEM_VIDEO, PRIO_VIDEO, OCMEM_PERFORMANCE, OCMEM_PORT},
	{OCMEM_CAMERA, NO_PRIO, OCMEM_PERFORMANCE, OCMEM_OCMEMNOC},
	{OCMEM_HP_AUDIO, PRIO_HP_AUDIO, OCMEM_PASSIVE, OCMEM_BLOCKED},
	{OCMEM_VOICE, PRIO_VOICE, OCMEM_PASSIVE, OCMEM_BLOCKED},
	{OCMEM_LP_AUDIO, PRIO_LP_AUDIO, OCMEM_LOW_POWER, OCMEM_SYSNOC},
	{OCMEM_SENSORS, PRIO_SENSORS, OCMEM_LOW_POWER, OCMEM_SYSNOC},
	{OCMEM_OTHER_OS, PRIO_OTHER_OS, OCMEM_LOW_POWER, OCMEM_SYSNOC},
};

static struct rb_root sched_tree;
static struct mutex sched_mutex;

/* A region represents a continuous interval in OCMEM address space */
struct ocmem_region {
	/* Chain in Interval Tree */
	struct rb_node region_rb;
	/* Hash map of requests */
	struct idr region_idr;
	/* Chain in eviction list */
	struct list_head eviction_list;
	unsigned long r_start;
	unsigned long r_end;
	unsigned long r_sz;
	/* Highest priority of all requests served by this region */
	int max_prio;
};

/* Is OCMEM tightly coupled to the client ?*/
static inline int is_tcm(int id)
{
	if (ocmem_client_table[id].hw_interconnect == OCMEM_PORT ||
		ocmem_client_table[id].hw_interconnect == OCMEM_OCMEMNOC)
		return 1;
	else
		return 0;
}

static inline int is_blocked(int id)
{
	return ocmem_client_table[id].hw_interconnect == OCMEM_BLOCKED ? 1 : 0;
}

inline struct ocmem_buf *handle_to_buffer(struct ocmem_handle *handle)
{
	if (handle)
		return &handle->buffer;
	else
		return NULL;
}

inline struct ocmem_handle *buffer_to_handle(struct ocmem_buf *buffer)
{
	if (buffer)
		return container_of(buffer, struct ocmem_handle, buffer);
	else
		return NULL;
}

inline struct ocmem_req *handle_to_req(struct ocmem_handle *handle)
{
	if (handle)
		return handle->req;
	else
		return NULL;
}

inline struct ocmem_handle *req_to_handle(struct ocmem_req *req)
{
	if (req && req->buffer)
		return container_of(req->buffer, struct ocmem_handle, buffer);
	else
		return NULL;
}

/* Simple wrappers which will have debug features added later */
inline int ocmem_read(void *at)
{
	return readl_relaxed(at);
}

inline int ocmem_write(unsigned long val, void *at)
{
	writel_relaxed(val, at);
	return 0;
}

inline int get_mode(int id)
{
	if (!check_id(id))
		return MODE_NOT_SET;
	else
		return ocmem_client_table[id].mode == OCMEM_PERFORMANCE ?
							WIDE_MODE : THIN_MODE;
}

/* Returns the address that can be used by a device core to access OCMEM */
static unsigned long device_address(int id, unsigned long addr)
{
	int hw_interconnect = ocmem_client_table[id].hw_interconnect;
	unsigned long ret_addr = 0x0;

	switch (hw_interconnect) {
	case OCMEM_PORT:
		ret_addr = phys_to_offset(addr);
		break;
	case OCMEM_OCMEMNOC:
	case OCMEM_SYSNOC:
		ret_addr = addr;
		break;
	case OCMEM_BLOCKED:
		ret_addr = 0x0;
		break;
	}
	return ret_addr;
}

/* Returns the address as viewed by the core */
static unsigned long core_address(int id, unsigned long addr)
{
	int hw_interconnect = ocmem_client_table[id].hw_interconnect;
	unsigned long ret_addr = 0x0;

	switch (hw_interconnect) {
	case OCMEM_PORT:
		ret_addr = offset_to_phys(addr);
		break;
	case OCMEM_OCMEMNOC:
	case OCMEM_SYSNOC:
		ret_addr = addr;
		break;
	case OCMEM_BLOCKED:
		ret_addr = 0x0;
		break;
	}
	return ret_addr;
}

static int insert_region(struct ocmem_region *region)
{

	struct rb_root *root = &sched_tree;
	struct rb_node **p = &root->rb_node;
	struct rb_node *parent = NULL;
	struct ocmem_region *tmp = NULL;
	unsigned long addr = region->r_start;

	while (*p) {
		parent = *p;
		tmp = rb_entry(parent, struct ocmem_region, region_rb);

		if (tmp->r_end > addr) {
			if (tmp->r_start <= addr)
				break;
			p =  &(*p)->rb_left;
		} else if (tmp->r_end <= addr)
			p = &(*p)->rb_right;
	}
	rb_link_node(&region->region_rb, parent, p);
	rb_insert_color(&region->region_rb, root);
	return 0;
}

static int remove_region(struct ocmem_region *region)
{
	struct rb_root *root = &sched_tree;
	rb_erase(&region->region_rb, root);
	return 0;
}

static struct ocmem_req *ocmem_create_req(void)
{
	struct ocmem_req *p = NULL;

	p =  kzalloc(sizeof(struct ocmem_req), GFP_KERNEL);
	if (!p)
		return NULL;

	INIT_LIST_HEAD(&p->zone_list);
	INIT_LIST_HEAD(&p->sched_list);
	init_rwsem(&p->rw_sem);
	SET_STATE(p, R_FREE);
	return p;
}

static int ocmem_destroy_req(struct ocmem_req *req)
{
	kfree(req);
	return 0;
}

static struct ocmem_region *create_region(void)
{
	struct ocmem_region *p = NULL;

	p =  kzalloc(sizeof(struct ocmem_region), GFP_KERNEL);
	if (!p)
		return NULL;
	idr_init(&p->region_idr);
	INIT_LIST_HEAD(&p->eviction_list);
	p->r_start = p->r_end = p->r_sz = 0x0;
	p->max_prio = NO_PRIO;
	return p;
}

static int destroy_region(struct ocmem_region *region)
{
	kfree(region);
	return 0;
}

static int attach_req(struct ocmem_region *region, struct ocmem_req *req)
{
	int ret, id;

	while (1) {
		if (idr_pre_get(&region->region_idr, GFP_KERNEL) == 0)
			return -ENOMEM;

		ret = idr_get_new_above(&region->region_idr, req, 1, &id);

		if (ret != -EAGAIN)
			break;
	}

	if (!ret) {
		req->req_id = id;
		pr_debug("ocmem: request %p(id:%d) attached to region %p\n",
				req, id, region);
		return 0;
	}
	return -EINVAL;
}

static int detach_req(struct ocmem_region *region, struct ocmem_req *req)
{
	idr_remove(&region->region_idr, req->req_id);
	return 0;
}

static int populate_region(struct ocmem_region *region, struct ocmem_req *req)
{
	region->r_start = req->req_start;
	region->r_end = req->req_end;
	region->r_sz =  req->req_end - req->req_start + 1;
	return 0;
}

static int region_req_count(int id, void *ptr, void *data)
{
	int *count = data;
	*count = *count + 1;
	return 0;
}

static int req_count(struct ocmem_region *region)
{
	int count = 0;
	idr_for_each(&region->region_idr, region_req_count, &count);
	return count;
}

static int compute_max_prio(int id, void *ptr, void *data)
{
	int *max = data;
	struct ocmem_req *req = ptr;

	if (req->prio > *max)
		*max = req->prio;
	return 0;
}

static int update_region_prio(struct ocmem_region *region)
{
	int max_prio;
	if (req_count(region) != 0) {
		idr_for_each(&region->region_idr, compute_max_prio, &max_prio);
		region->max_prio = max_prio;
	} else {
		region->max_prio = NO_PRIO;
	}
	pr_debug("ocmem: Updating prio of region %p as %d\n",
			region, max_prio);

	return 0;
}

static struct ocmem_region *find_region(unsigned long addr)
{
	struct ocmem_region *region = NULL;
	struct rb_node *rb_node = NULL;

	rb_node = sched_tree.rb_node;

	while (rb_node) {
		struct ocmem_region *tmp_region = NULL;
		tmp_region = rb_entry(rb_node, struct ocmem_region, region_rb);

		if (tmp_region->r_end > addr) {
			region = tmp_region;
			if (tmp_region->r_start <= addr)
				break;
			rb_node = rb_node->rb_left;
		} else {
			rb_node = rb_node->rb_right;
		}
	}
	return region;
}

static struct ocmem_region *find_region_intersection(unsigned long start,
					unsigned long end)
{

	struct ocmem_region *region = NULL;
	region = find_region(start);
	if (region && end <= region->r_start)
		region = NULL;
	return region;
}

static struct ocmem_region *find_region_match(unsigned long start,
					unsigned long end)
{

	struct ocmem_region *region = NULL;
	region = find_region(start);
	if (region && start == region->r_start && end == region->r_end)
		return region;
	return NULL;
}

static struct ocmem_req *find_req_match(int owner, struct ocmem_region *region)
{
	struct ocmem_req *req = NULL;

	if (!region)
		return NULL;

	req = idr_find(&region->region_idr, owner);

	return req;
}

/* Must be called with req->sem held */
static inline int is_mapped(struct ocmem_req *req)
{
	return TEST_STATE(req, R_MAPPED);
}

/* Must be called with sched_mutex held */
static int __sched_unmap(struct ocmem_req *req)
{
	struct ocmem_req *matched_req = NULL;
	struct ocmem_region *matched_region = NULL;

	matched_region = find_region_match(req->req_start, req->req_end);
	matched_req = find_req_match(req->req_id, matched_region);

	if (!matched_region || !matched_req) {
		pr_err("Could not find backing region for req");
		goto invalid_op_error;
	}

	if (matched_req != req) {
		pr_err("Request does not match backing req");
		goto invalid_op_error;
	}

	if (!is_mapped(req)) {
		pr_err("Request is not currently mapped");
		goto invalid_op_error;
	}

	/* Update the request state */
	CLEAR_STATE(req, R_MAPPED);
	SET_STATE(req, R_MUST_MAP);

	return OP_COMPLETE;

invalid_op_error:
	return OP_FAIL;
}

/* Must be called with sched_mutex held */
static int __sched_map(struct ocmem_req *req)
{
	struct ocmem_req *matched_req = NULL;
	struct ocmem_region *matched_region = NULL;

	matched_region = find_region_match(req->req_start, req->req_end);
	matched_req = find_req_match(req->req_id, matched_region);

	if (!matched_region || !matched_req) {
		pr_err("Could not find backing region for req");
		goto invalid_op_error;
	}

	if (matched_req != req) {
		pr_err("Request does not match backing req");
		goto invalid_op_error;
	}

	/* Update the request state */
	CLEAR_STATE(req, R_MUST_MAP);
	SET_STATE(req, R_MAPPED);

	return OP_COMPLETE;

invalid_op_error:
	return OP_FAIL;
}

static int do_map(struct ocmem_req *req)
{
	int rc = 0;

	down_write(&req->rw_sem);

	mutex_lock(&sched_mutex);
	rc = __sched_map(req);
	mutex_unlock(&sched_mutex);

	up_write(&req->rw_sem);

	if (rc == OP_FAIL)
		return -EINVAL;

	return 0;
}

static int do_unmap(struct ocmem_req *req)
{
	int rc = 0;

	down_write(&req->rw_sem);

	mutex_lock(&sched_mutex);
	rc = __sched_unmap(req);
	mutex_unlock(&sched_mutex);

	up_write(&req->rw_sem);

	if (rc == OP_FAIL)
		return -EINVAL;

	return 0;
}

static int process_map(struct ocmem_req *req, unsigned long start,
				unsigned long end)
{
	int rc = 0;

	rc = ocmem_enable_core_clock();

	if (rc < 0)
		goto core_clock_fail;

	rc = ocmem_enable_iface_clock();

	if (rc < 0)
		goto iface_clock_fail;

	rc = ocmem_enable_br_clock();

	if (rc < 0)
		goto br_clock_fail;

	rc = do_map(req);

	if (rc < 0) {
		pr_err("ocmem: Failed to map request %p for %d\n",
							req, req->owner);
		goto process_map_fail;

	}

	if (ocmem_lock(req->owner, phys_to_offset(req->req_start), req->req_sz,
							get_mode(req->owner))) {
		pr_err("ocmem: Failed to secure request %p for %d\n", req,
				req->owner);
		rc = -EINVAL;
		goto lock_failed;
	}

	return 0;
lock_failed:
	do_unmap(req);
process_map_fail:
	ocmem_disable_core_clock();
core_clock_fail:
	pr_err("ocmem: Failed to map ocmem request\n");
	return rc;
}

static int process_unmap(struct ocmem_req *req, unsigned long start,
				unsigned long end)
{
	int rc = 0;

	if (ocmem_unlock(req->owner, phys_to_offset(req->req_start),
							req->req_sz)) {
		pr_err("ocmem: Failed to un-secure request %p for %d\n", req,
				req->owner);
		rc = -EINVAL;
		goto unlock_failed;
	}

	rc = do_unmap(req);

	if (rc < 0)
		goto process_unmap_fail;

	ocmem_disable_iface_clock();
	ocmem_disable_core_clock();
	return 0;

unlock_failed:
process_unmap_fail:
	pr_err("ocmem: Failed to unmap ocmem request\n");
	return rc;
}

static int __sched_grow(struct ocmem_req *req, bool can_block)
{
	unsigned long min = req->req_min;
	unsigned long max = req->req_max;
	unsigned long step = req->req_step;
	int owner = req->owner;
	unsigned long curr_sz = 0;
	unsigned long growth_sz = 0;
	unsigned long curr_start = 0;
	enum client_prio prio = req->prio;
	unsigned long alloc_addr = 0x0;
	bool retry;
	struct ocmem_region *spanned_r = NULL;
	struct ocmem_region *overlap_r = NULL;

	struct ocmem_req *matched_req = NULL;
	struct ocmem_region *matched_region = NULL;

	struct ocmem_zone *zone = get_zone(owner);
	struct ocmem_region *region = NULL;

	matched_region = find_region_match(req->req_start, req->req_end);
	matched_req = find_req_match(req->req_id, matched_region);

	if (!matched_region || !matched_req) {
		pr_err("Could not find backing region for req");
		goto invalid_op_error;
	}

	if (matched_req != req) {
		pr_err("Request does not match backing req");
		goto invalid_op_error;
	}

	curr_sz = matched_req->req_sz;
	curr_start = matched_req->req_start;
	growth_sz = matched_req->req_max - matched_req->req_sz;

	pr_debug("Attempting to grow req %p from %lx to %lx\n",
			req, matched_req->req_sz, matched_req->req_max);

	retry = false;

	pr_debug("ocmem: GROW: growth size %lx\n", growth_sz);

retry_next_step:

	spanned_r = NULL;
	overlap_r = NULL;

	spanned_r = find_region(zone->z_head);
	overlap_r = find_region_intersection(zone->z_head,
				zone->z_head + growth_sz);

	if (overlap_r == NULL) {
		/* no conflicting regions, schedule this region */
		zone->z_ops->free(zone, curr_start, curr_sz);
		alloc_addr = zone->z_ops->allocate(zone, curr_sz + growth_sz);

		if (alloc_addr < 0) {
			pr_err("ocmem: zone allocation operation failed\n");
			goto internal_error;
		}

		curr_sz += growth_sz;
		/* Detach the region from the interval tree */
		/* This is to guarantee that any change in size
		 * causes the tree to be rebalanced if required */

		detach_req(matched_region, req);
		if (req_count(matched_region) == 0) {
			remove_region(matched_region);
			region = matched_region;
		} else {
			region = create_region();
			if (!region) {
				pr_err("ocmem: Unable to create region\n");
				goto region_error;
			}
		}

		/* update the request */
		req->req_start = alloc_addr;
		/* increment the size to reflect new length */
		req->req_sz = curr_sz;
		req->req_end = alloc_addr + req->req_sz - 1;

		/* update request state */
		CLEAR_STATE(req, R_MUST_GROW);
		SET_STATE(req, R_ALLOCATED);
		SET_STATE(req, R_MUST_MAP);
		req->op = SCHED_MAP;

		/* update the region with new req */
		attach_req(region, req);
		populate_region(region, req);
		update_region_prio(region);

		/* update the tree with new region */
		if (insert_region(region)) {
			pr_err("ocmem: Failed to insert the region\n");
			goto region_error;
		}

		if (retry) {
			SET_STATE(req, R_MUST_GROW);
			SET_STATE(req, R_PENDING);
			req->op = SCHED_GROW;
			return OP_PARTIAL;
		}
	} else if (spanned_r != NULL && overlap_r != NULL) {
		/* resolve conflicting regions based on priority */
		if (overlap_r->max_prio < prio) {
			/* Growth cannot be triggered unless a previous
			 * client of lower priority was evicted */
			pr_err("ocmem: Invalid growth scheduled\n");
			/* This is serious enough to fail */
			BUG();
			return OP_FAIL;
		} else if (overlap_r->max_prio > prio) {
			if (min == max) {
				/* Cannot grow at this time, try later */
				SET_STATE(req, R_PENDING);
				SET_STATE(req, R_MUST_GROW);
				return OP_RESCHED;
			} else {
			/* Try to grow in steps */
				growth_sz -= step;
				/* We are OOM at this point so need to retry */
				if (growth_sz <= curr_sz) {
					SET_STATE(req, R_PENDING);
					SET_STATE(req, R_MUST_GROW);
					return OP_RESCHED;
				}
				retry = true;
				pr_debug("ocmem: Attempting with reduced size %lx\n",
						growth_sz);
				goto retry_next_step;
			}
		} else {
			pr_err("ocmem: grow: New Region %p Existing %p\n",
				matched_region, overlap_r);
			pr_err("ocmem: Undetermined behavior\n");
			/* This is serious enough to fail */
			BUG();
		}
	} else if (spanned_r == NULL && overlap_r != NULL) {
		goto err_not_supported;
	}

	return OP_COMPLETE;

err_not_supported:
	pr_err("ocmem: Scheduled unsupported operation\n");
	return OP_FAIL;
region_error:
	zone->z_ops->free(zone, alloc_addr, curr_sz);
	detach_req(region, req);
	update_region_prio(region);
	/* req is going to be destroyed by the caller anyways */
internal_error:
	destroy_region(region);
invalid_op_error:
	return OP_FAIL;
}

/* Must be called with sched_mutex held */
static int __sched_free(struct ocmem_req *req)
{
	int owner = req->owner;
	int ret = 0;

	struct ocmem_req *matched_req = NULL;
	struct ocmem_region *matched_region = NULL;

	struct ocmem_zone *zone = get_zone(owner);

	BUG_ON(!zone);

	matched_region = find_region_match(req->req_start, req->req_end);
	matched_req = find_req_match(req->req_id, matched_region);

	if (!matched_region || !matched_req)
		goto invalid_op_error;
	if (matched_req != req)
		goto invalid_op_error;

	ret = zone->z_ops->free(zone,
		matched_req->req_start, matched_req->req_sz);

	if (ret < 0)
		goto err_op_fail;

	detach_req(matched_region, matched_req);
	update_region_prio(matched_region);
	if (req_count(matched_region) == 0) {
		remove_region(matched_region);
		destroy_region(matched_region);
	}

	/* Update the request */
	req->req_start = 0x0;
	req->req_sz = 0x0;
	req->req_end = 0x0;
	SET_STATE(req, R_FREE);
	return OP_COMPLETE;
invalid_op_error:
	pr_err("ocmem: free: Failed to find matching region\n");
err_op_fail:
	pr_err("ocmem: free: Failed\n");
	return OP_FAIL;
}

/* Must be called with sched_mutex held */
static int __sched_shrink(struct ocmem_req *req, unsigned long new_sz)
{
	int owner = req->owner;
	int ret = 0;

	struct ocmem_req *matched_req = NULL;
	struct ocmem_region *matched_region = NULL;
	struct ocmem_region *region = NULL;
	unsigned long alloc_addr = 0x0;

	struct ocmem_zone *zone = get_zone(owner);

	BUG_ON(!zone);

	/* The shrink should not be called for zero size */
	BUG_ON(new_sz == 0);

	matched_region = find_region_match(req->req_start, req->req_end);
	matched_req = find_req_match(req->req_id, matched_region);

	if (!matched_region || !matched_req)
		goto invalid_op_error;
	if (matched_req != req)
		goto invalid_op_error;


	ret = zone->z_ops->free(zone,
		matched_req->req_start, matched_req->req_sz);

	if (ret < 0) {
		pr_err("Zone Allocation operation failed\n");
		goto internal_error;
	}

	alloc_addr = zone->z_ops->allocate(zone, new_sz);

	if (alloc_addr < 0) {
		pr_err("Zone Allocation operation failed\n");
		goto internal_error;
	}

	/* Detach the region from the interval tree */
	/* This is to guarantee that the change in size
	 * causes the tree to be rebalanced if required */

	detach_req(matched_region, req);
	if (req_count(matched_region) == 0) {
		remove_region(matched_region);
		region = matched_region;
	} else {
		region = create_region();
		if (!region) {
			pr_err("ocmem: Unable to create region\n");
			goto internal_error;
		}
	}
	/* update the request */
	req->req_start = alloc_addr;
	req->req_sz = new_sz;
	req->req_end = alloc_addr + req->req_sz;

	if (req_count(region) == 0) {
		remove_region(matched_region);
		destroy_region(matched_region);
	}

	/* update request state */
	SET_STATE(req, R_MUST_GROW);
	SET_STATE(req, R_MUST_MAP);
	req->op = SCHED_MAP;

	/* attach the request to the region */
	attach_req(region, req);
	populate_region(region, req);
	update_region_prio(region);

	/* update the tree with new region */
	if (insert_region(region)) {
		pr_err("ocmem: Failed to insert the region\n");
		zone->z_ops->free(zone, alloc_addr, new_sz);
		detach_req(region, req);
		update_region_prio(region);
		/* req will be destroyed by the caller */
		goto region_error;
	}
	return OP_COMPLETE;

region_error:
	destroy_region(region);
internal_error:
	pr_err("ocmem: shrink: Failed\n");
	return OP_FAIL;
invalid_op_error:
	pr_err("ocmem: shrink: Failed to find matching region\n");
	return OP_FAIL;
}

/* Must be called with sched_mutex held */
static int __sched_allocate(struct ocmem_req *req, bool can_block,
				bool can_wait)
{
	unsigned long min = req->req_min;
	unsigned long max = req->req_max;
	unsigned long step = req->req_step;
	int owner = req->owner;
	unsigned long sz = max;
	enum client_prio prio = req->prio;
	unsigned long alloc_addr = 0x0;
	bool retry;

	struct ocmem_region *spanned_r = NULL;
	struct ocmem_region *overlap_r = NULL;

	struct ocmem_zone *zone = get_zone(owner);
	struct ocmem_region *region = NULL;

	BUG_ON(!zone);

	if (min > (zone->z_end - zone->z_start)) {
		pr_err("ocmem: requested minimum size exceeds quota\n");
		goto invalid_op_error;
	}

	if (max > (zone->z_end - zone->z_start)) {
		pr_err("ocmem: requested maximum size exceeds quota\n");
		goto invalid_op_error;
	}

	if (min > zone->z_free) {
			pr_err("ocmem: out of memory for zone %d\n", owner);
			goto invalid_op_error;
	}

	region = create_region();

	if (!region) {
		pr_err("ocmem: Unable to create region\n");
		goto invalid_op_error;
	}

	retry = false;

	pr_debug("ocmem: ALLOCATE: request size %lx\n", sz);

retry_next_step:

	spanned_r = NULL;
	overlap_r = NULL;

	spanned_r = find_region(zone->z_head);
	overlap_r = find_region_intersection(zone->z_head, zone->z_head + sz);

	if (overlap_r == NULL) {
		/* no conflicting regions, schedule this region */
		alloc_addr = zone->z_ops->allocate(zone, sz);

		if (alloc_addr < 0) {
			pr_err("Zone Allocation operation failed\n");
			goto internal_error;
		}

		/* update the request */
		req->req_start = alloc_addr;
		req->req_end = alloc_addr + sz - 1;
		req->req_sz = sz;
		req->zone = zone;

		/* update request state */
		CLEAR_STATE(req, R_FREE);
		SET_STATE(req, R_ALLOCATED);
		SET_STATE(req, R_MUST_MAP);
		req->op = SCHED_NOP;

		/* attach the request to the region */
		attach_req(region, req);
		populate_region(region, req);
		update_region_prio(region);

		/* update the tree with new region */
		if (insert_region(region)) {
			pr_err("ocmem: Failed to insert the region\n");
			zone->z_ops->free(zone, alloc_addr, sz);
			detach_req(region, req);
			update_region_prio(region);
			/* req will be destroyed by the caller */
			goto internal_error;
		}

		if (retry) {
			SET_STATE(req, R_MUST_GROW);
			SET_STATE(req, R_PENDING);
			req->op = SCHED_GROW;
			return OP_PARTIAL;
		}
	} else if (spanned_r != NULL && overlap_r != NULL) {
		/* resolve conflicting regions based on priority */
		if (overlap_r->max_prio < prio) {
			if (min == max) {
				pr_err("ocmem: Requires eviction support\n");
				goto err_not_supported;
			} else {
			/* Try to allocate atleast >= 'min' immediately */
				sz -= step;
				if (sz < min)
					goto err_out_of_mem;
				retry = true;
				pr_debug("ocmem: Attempting with reduced size %lx\n",
						sz);
				goto retry_next_step;
			}
		} else if (overlap_r->max_prio > prio) {
			if (can_block == true) {
				SET_STATE(req, R_PENDING);
				SET_STATE(req, R_MUST_GROW);
				return OP_RESCHED;
			} else {
				if (min == max) {
					pr_err("Cannot allocate %lx synchronously\n",
							sz);
					goto err_out_of_mem;
				} else {
					sz -= step;
					if (sz < min)
						goto err_out_of_mem;
					retry = true;
					pr_debug("ocmem: Attempting reduced size %lx\n",
							sz);
					goto retry_next_step;
				}
			}
		} else {
			pr_err("ocmem: Undetermined behavior\n");
			pr_err("ocmem: New Region %p Existing %p\n", region,
					overlap_r);
			/* This is serious enough to fail */
			BUG();
		}
	} else if (spanned_r == NULL && overlap_r != NULL)
		goto err_not_supported;

	return OP_COMPLETE;

err_not_supported:
	pr_err("ocmem: Scheduled unsupported operation\n");
	return OP_FAIL;

err_out_of_mem:
	pr_err("ocmem: Out of memory during allocation\n");
internal_error:
	destroy_region(region);
invalid_op_error:
	return OP_FAIL;
}

static int sched_enqueue(struct ocmem_req *priv)
{
	struct ocmem_req *next = NULL;
	mutex_lock(&sched_queue_mutex);
	list_add_tail(&priv->sched_list, &sched_queue[priv->owner]);
	pr_debug("enqueued req %p\n", priv);
	list_for_each_entry(next, &sched_queue[priv->owner], sched_list) {
		pr_debug("pending requests for client %p\n", next);
	}
	mutex_unlock(&sched_queue_mutex);
	return 0;
}

static struct ocmem_req *ocmem_fetch_req(void)
{
	int i;
	struct ocmem_req *req = NULL;
	struct ocmem_req *next = NULL;

	mutex_lock(&sched_queue_mutex);
	for (i = MIN_PRIO; i < MAX_OCMEM_PRIO; i++) {
		if (list_empty(&sched_queue[i]))
			continue;
		list_for_each_entry_safe(req, next, &sched_queue[i], sched_list)
		{
			if (req) {
				pr_debug("ocmem: Fetched pending request %p\n",
									req);
				list_del(&req->sched_list);
			break;
			}
		}
	}
	mutex_unlock(&sched_queue_mutex);
	return req;
}


unsigned long process_quota(int id)
{
	struct ocmem_zone *zone = NULL;

	if (is_blocked(id))
		return 0;

	zone = get_zone(id);

	if (zone && zone->z_pool)
		return zone->z_end - zone->z_start;
	else
		return 0;
}

static int do_grow(struct ocmem_req *req)
{
	struct ocmem_buf *buffer = NULL;
	bool can_block = true;
	int rc = 0;

	down_write(&req->rw_sem);
	buffer = req->buffer;

	/* Take the scheduler mutex */
	mutex_lock(&sched_mutex);
	rc = __sched_grow(req, can_block);
	mutex_unlock(&sched_mutex);

	if (rc == OP_FAIL)
		goto err_op_fail;

	if (rc == OP_RESCHED) {
		pr_debug("ocmem: Enqueue this allocation");
		sched_enqueue(req);
	}

	else if (rc == OP_COMPLETE || rc == OP_PARTIAL) {
		buffer->addr = device_address(req->owner, req->req_start);
		buffer->len = req->req_sz;
	}

	up_write(&req->rw_sem);
	return 0;
err_op_fail:
	up_write(&req->rw_sem);
	return -EINVAL;
}

static int process_grow(struct ocmem_req *req)
{
	int rc = 0;
	unsigned long offset = 0;

	/* Attempt to grow the region */
	rc = do_grow(req);

	if (rc < 0)
		return -EINVAL;

	/* Map the newly grown region */
	if (is_tcm(req->owner)) {
		rc = process_map(req, req->req_start, req->req_end);
		if (rc < 0)
			return -EINVAL;
	}

	offset = phys_to_offset(req->req_start);

	rc = ocmem_memory_on(req->owner, offset, req->req_sz);

	if (rc < 0) {
		pr_err("Failed to switch ON memory macros\n");
		goto power_ctl_error;
	}

	/* Notify the client about the buffer growth */
	rc = dispatch_notification(req->owner, OCMEM_ALLOC_GROW, req->buffer);
	if (rc < 0) {
		pr_err("No notifier callback to cater for req %p event: %d\n",
				req, OCMEM_ALLOC_GROW);
		BUG();
	}
	return 0;
power_ctl_error:
	return -EINVAL;
}

static int do_shrink(struct ocmem_req *req, unsigned long shrink_size)
{

	int rc = 0;
	struct ocmem_buf *buffer = NULL;

	down_write(&req->rw_sem);
	buffer = req->buffer;

	/* Take the scheduler mutex */
	mutex_lock(&sched_mutex);
	rc = __sched_shrink(req, shrink_size);
	mutex_unlock(&sched_mutex);

	if (rc == OP_FAIL)
		goto err_op_fail;

	else if (rc == OP_COMPLETE) {
		buffer->addr = device_address(req->owner, req->req_start);
		buffer->len = req->req_sz;
	}

	up_write(&req->rw_sem);
	return 0;
err_op_fail:
	up_write(&req->rw_sem);
	return -EINVAL;
}

static void ocmem_sched_wk_func(struct work_struct *work);
DECLARE_DELAYED_WORK(ocmem_sched_thread, ocmem_sched_wk_func);

static int ocmem_schedule_pending(void)
{
	schedule_delayed_work(&ocmem_sched_thread,
				msecs_to_jiffies(SCHED_DELAY));
	return 0;
}

static int do_free(struct ocmem_req *req)
{
	int rc = 0;
	struct ocmem_buf *buffer = req->buffer;

	down_write(&req->rw_sem);

	if (is_mapped(req)) {
		pr_err("ocmem: Buffer needs to be unmapped before free\n");
		goto err_free_fail;
	}

	/* Grab the sched mutex */
	mutex_lock(&sched_mutex);
	rc = __sched_free(req);
	mutex_unlock(&sched_mutex);

	switch (rc) {

	case OP_COMPLETE:
		buffer->addr = 0x0;
		buffer->len = 0x0;
		break;
	case OP_FAIL:
	default:
		goto err_free_fail;
		break;
	}

	up_write(&req->rw_sem);
	return 0;
err_free_fail:
	up_write(&req->rw_sem);
	pr_err("ocmem: freeing req %p failed\n", req);
	return -EINVAL;
}

int process_free(int id, struct ocmem_handle *handle)
{
	struct ocmem_req *req = NULL;
	struct ocmem_buf *buffer = NULL;
	unsigned long offset = 0;
	int rc = 0;

	if (is_blocked(id)) {
		pr_err("Client %d cannot request free\n", id);
		return -EINVAL;
	}

	req = handle_to_req(handle);
	buffer = handle_to_buffer(handle);

	if (!req)
		return -EINVAL;

	if (req->req_start != core_address(id, buffer->addr)) {
		pr_err("Invalid buffer handle passed for free\n");
		return -EINVAL;
	}

	if (is_tcm(req->owner)) {
		rc = process_unmap(req, req->req_start, req->req_end);
		if (rc < 0)
			return -EINVAL;
	}

	if (req->req_sz != 0) {

		offset = phys_to_offset(req->req_start);

		rc = ocmem_memory_off(req->owner, offset, req->req_sz);

		if (rc < 0) {
			pr_err("Failed to switch OFF memory macros\n");
			return -EINVAL;
		}

	}

	rc = do_free(req);
	if (rc < 0)
		return -EINVAL;

	ocmem_destroy_req(req);
	handle->req = NULL;

	ocmem_schedule_pending();
	return 0;
}

static void ocmem_rdm_worker(struct work_struct *work)
{
	int offset = 0;
	int rc = 0;
	int event;
	struct ocmem_rdm_work *work_data = container_of(work,
				struct ocmem_rdm_work, work);
	int id = work_data->id;
	struct ocmem_map_list *list = work_data->list;
	int direction = work_data->direction;
	struct ocmem_handle *handle = work_data->handle;
	struct ocmem_req *req = handle_to_req(handle);
	struct ocmem_buf *buffer = handle_to_buffer(handle);

	down_write(&req->rw_sem);
	offset = phys_to_offset(req->req_start);
	rc = ocmem_rdm_transfer(id, list, offset, direction);
	if (work_data->direction == TO_OCMEM)
		event = (rc == 0) ? OCMEM_MAP_DONE : OCMEM_MAP_FAIL;
	else
		event = (rc == 0) ? OCMEM_UNMAP_DONE : OCMEM_UNMAP_FAIL;
	up_write(&req->rw_sem);
	kfree(work_data);
	dispatch_notification(id, event, buffer);
}

int queue_transfer(struct ocmem_req *req, struct ocmem_handle *handle,
			struct ocmem_map_list *list, int direction)
{
	struct ocmem_rdm_work *work_data = NULL;

	down_write(&req->rw_sem);

	work_data = kzalloc(sizeof(struct ocmem_rdm_work), GFP_ATOMIC);
	if (!work_data)
		BUG();

	work_data->handle = handle;
	work_data->list = list;
	work_data->id = req->owner;
	work_data->direction = direction;
	INIT_WORK(&work_data->work, ocmem_rdm_worker);
	up_write(&req->rw_sem);
	queue_work(ocmem_rdm_wq, &work_data->work);
	return 0;
}

int process_xfer_out(int id, struct ocmem_handle *handle,
			struct ocmem_map_list *list)
{
	struct ocmem_req *req = NULL;
	int rc = 0;

	req = handle_to_req(handle);

	if (!req)
		return -EINVAL;

	if (!is_mapped(req)) {
		pr_err("Buffer is not already mapped\n");
		goto transfer_out_error;
	}

	rc = process_unmap(req, req->req_start, req->req_end);
	if (rc < 0) {
		pr_err("Unmapping the buffer failed\n");
		goto transfer_out_error;
	}

	rc = queue_transfer(req, handle, list, TO_DDR);

	if (rc < 0) {
		pr_err("Failed to queue rdm transfer to DDR\n");
		goto transfer_out_error;
	}


	return 0;

transfer_out_error:
	return -EINVAL;
}

int process_xfer_in(int id, struct ocmem_handle *handle,
			struct ocmem_map_list *list)
{
	struct ocmem_req *req = NULL;
	int rc = 0;

	req = handle_to_req(handle);

	if (!req)
		return -EINVAL;

	if (is_mapped(req)) {
		pr_err("Buffer is already mapped\n");
		goto transfer_in_error;
	}

	rc = process_map(req, req->req_start, req->req_end);
	if (rc < 0) {
		pr_err("Mapping the buffer failed\n");
		goto transfer_in_error;
	}

	rc = queue_transfer(req, handle, list, TO_OCMEM);

	if (rc < 0) {
		pr_err("Failed to queue rdm transfer to OCMEM\n");
		goto transfer_in_error;
	}

	return 0;
transfer_in_error:
	return -EINVAL;
}

int process_shrink(int id, struct ocmem_handle *handle, unsigned long size)
{
	struct ocmem_req *req = NULL;
	struct ocmem_buf *buffer = NULL;
	struct ocmem_eviction_data *edata = NULL;
	int rc = 0;

	if (is_blocked(id)) {
		pr_err("Client %d cannot request free\n", id);
		return -EINVAL;
	}

	req = handle_to_req(handle);
	buffer = handle_to_buffer(handle);

	if (!req)
		return -EINVAL;

	if (req->req_start != core_address(id, buffer->addr)) {
		pr_err("Invalid buffer handle passed for shrink\n");
		return -EINVAL;
	}

	edata = req->edata;

	if (is_tcm(req->owner))
		do_unmap(req);

	if (size == 0) {
		pr_info("req %p being shrunk to zero\n", req);
		rc = do_free(req);
		if (rc < 0)
			return -EINVAL;
	} else {
		rc = do_shrink(req, size);
		if (rc < 0)
			return -EINVAL;
	}

	edata->pending--;
	if (edata->pending == 0) {
		pr_debug("All regions evicted");
		complete(&edata->completion);
	}

	return 0;
}

int process_xfer(int id, struct ocmem_handle *handle,
		struct ocmem_map_list *list, int direction)
{
	int rc = 0;

	if (is_tcm(id)) {
		WARN(1, "Mapping operation is invalid for client\n");
		return -EINVAL;
	}

	if (direction == TO_DDR)
		rc = process_xfer_out(id, handle, list);
	else if (direction == TO_OCMEM)
		rc = process_xfer_in(id, handle, list);
	return rc;
}

int ocmem_eviction_thread(struct work_struct *work)
{
	return 0;
}

int process_evict(int id)
{
	struct ocmem_eviction_data *edata = NULL;
	int prio = ocmem_client_table[id].priority;
	struct rb_node *rb_node = NULL;
	struct ocmem_req *req = NULL;
	struct ocmem_buf buffer;
	int j = 0;

	edata = kzalloc(sizeof(struct ocmem_eviction_data), GFP_ATOMIC);

	INIT_LIST_HEAD(&edata->victim_list);
	INIT_LIST_HEAD(&edata->req_list);
	edata->prio = prio;
	edata->pending = 0;
	edata->passive = 1;
	evictions[id] = edata;

	mutex_lock(&sched_mutex);

	for (rb_node = rb_first(&sched_tree); rb_node;
				rb_node = rb_next(rb_node)) {
		struct ocmem_region *tmp_region = NULL;
		tmp_region = rb_entry(rb_node, struct ocmem_region, region_rb);
		if (tmp_region->max_prio < prio) {
			for (j = id - 1; j > NO_PRIO; j--) {
				req = find_req_match(j, tmp_region);
				if (req) {
					pr_info("adding %p to eviction list\n",
							tmp_region);
					list_add_tail(
						&tmp_region->eviction_list,
						&edata->victim_list);
					list_add_tail(
						&req->eviction_list,
						&edata->req_list);
					edata->pending++;
					req->edata = edata;
					buffer.addr = req->req_start;
					buffer.len = 0x0;
					dispatch_notification(req->owner,
						OCMEM_ALLOC_SHRINK, &buffer);
				}
			}
		} else {
			pr_info("skipping %p from eviction\n", tmp_region);
		}
	}
	mutex_unlock(&sched_mutex);
	pr_debug("Waiting for all regions to be shrunk\n");
	if (edata->pending > 0) {
		init_completion(&edata->completion);
		wait_for_completion(&edata->completion);
	}
	return 0;
}

static int do_allocate(struct ocmem_req *req, bool can_block, bool can_wait)
{
	int rc = 0;
	struct ocmem_buf *buffer = req->buffer;

	down_write(&req->rw_sem);

	/* Take the scheduler mutex */
	mutex_lock(&sched_mutex);
	rc = __sched_allocate(req, can_block, can_wait);
	mutex_unlock(&sched_mutex);

	if (rc == OP_FAIL)
		goto err_allocate_fail;

	if (rc == OP_RESCHED) {
		buffer->addr = 0x0;
		buffer->len = 0x0;
		pr_debug("ocmem: Enqueuing req %p\n", req);
		sched_enqueue(req);
	} else if (rc == OP_PARTIAL) {
		buffer->addr = device_address(req->owner, req->req_start);
		buffer->len = req->req_sz;
		pr_debug("ocmem: Enqueuing req %p\n", req);
		sched_enqueue(req);
	} else if (rc == OP_COMPLETE) {
		buffer->addr = device_address(req->owner, req->req_start);
		buffer->len = req->req_sz;
	}

	up_write(&req->rw_sem);
	return 0;
err_allocate_fail:
	up_write(&req->rw_sem);
	return -EINVAL;
}

int process_restore(int id)
{
	struct ocmem_req *req = NULL;
	struct ocmem_req *next = NULL;
	struct ocmem_eviction_data *edata = evictions[id];

	if (!edata)
		return 0;

	list_for_each_entry_safe(req, next, &edata->req_list, eviction_list)
	{
		if (req) {
			pr_debug("ocmem: Fetched evicted request %p\n",
								req);
			list_del(&req->sched_list);
			req->op = SCHED_ALLOCATE;
			sched_enqueue(req);
		}
	}
	kfree(edata);
	evictions[id] = NULL;
	pr_debug("Restore all evicted regions\n");
	ocmem_schedule_pending();
	return 0;
}

int process_allocate(int id, struct ocmem_handle *handle,
			unsigned long min, unsigned long max,
			unsigned long step, bool can_block, bool can_wait)
{

	struct ocmem_req *req = NULL;
	struct ocmem_buf *buffer = NULL;
	int rc = 0;
	unsigned long offset = 0;

	/* sanity checks */
	if (is_blocked(id)) {
		pr_err("Client %d cannot request allocation\n", id);
		return -EINVAL;
	}

	if (handle->req != NULL) {
		pr_err("Invalid handle passed in\n");
		return -EINVAL;
	}

	buffer = handle_to_buffer(handle);
	BUG_ON(buffer == NULL);

	/* prepare a request structure to represent this transaction */
	req = ocmem_create_req();
	if (!req)
		return -ENOMEM;

	req->owner = id;
	req->req_min = min;
	req->req_max = max;
	req->req_step = step;
	req->prio = ocmem_client_table[id].priority;
	req->op = SCHED_ALLOCATE;
	req->buffer = buffer;

	rc = do_allocate(req, can_block, can_wait);

	if (rc < 0)
		goto do_allocate_error;

	handle->req = req;

	if (is_tcm(id)) {
		rc = process_map(req, req->req_start, req->req_end);
		if (rc < 0)
			goto map_error;
	}

	if (req->req_sz != 0) {

		offset = phys_to_offset(req->req_start);

		rc = ocmem_memory_on(req->owner, offset, req->req_sz);

		if (rc < 0) {
			pr_err("Failed to switch ON memory macros\n");
			goto power_ctl_error;
		}
	}

	return 0;

power_ctl_error:
map_error:
	handle->req = NULL;
	do_free(req);
do_allocate_error:
	ocmem_destroy_req(req);
	return -EINVAL;
}

int process_delayed_allocate(struct ocmem_req *req)
{

	struct ocmem_handle *handle = NULL;
	int rc = 0;
	int id = req->owner;
	unsigned long offset = 0;

	handle = req_to_handle(req);
	BUG_ON(handle == NULL);

	rc = do_allocate(req, true, false);


	if (rc < 0)
		goto do_allocate_error;

	if (is_tcm(id)) {
		rc = process_map(req, req->req_start, req->req_end);
		if (rc < 0)
			goto map_error;
	}

	if (req->req_sz != 0) {

		offset = phys_to_offset(req->req_start);

		rc = ocmem_memory_on(req->owner, offset, req->req_sz);

		if (rc < 0) {
			pr_err("Failed to switch ON memory macros\n");
			goto power_ctl_error;
		}
	}

	/* Notify the client about the buffer growth */
	rc = dispatch_notification(id, OCMEM_ALLOC_GROW, req->buffer);
	if (rc < 0) {
		pr_err("No notifier callback to cater for req %p event: %d\n",
				req, OCMEM_ALLOC_GROW);
		BUG();
	}
	return 0;

power_ctl_error:
map_error:
	handle->req = NULL;
	do_free(req);
do_allocate_error:
	ocmem_destroy_req(req);
	return -EINVAL;
}

static void ocmem_sched_wk_func(struct work_struct *work)
{

	struct ocmem_buf *buffer = NULL;
	struct ocmem_handle *handle = NULL;
	struct ocmem_req *req = ocmem_fetch_req();

	if (!req) {
		pr_debug("No Pending Requests found\n");
		return;
	}

	pr_debug("ocmem: sched_wk pending req %p\n", req);
	handle = req_to_handle(req);
	buffer = handle_to_buffer(handle);
	BUG_ON(req->op == SCHED_NOP);

	switch (req->op) {
	case SCHED_GROW:
		process_grow(req);
		break;
	case SCHED_ALLOCATE:
		process_delayed_allocate(req);
		break;
	default:
		pr_err("ocmem: Unknown operation encountered\n");
		break;
	}
	return;
}

int ocmem_sched_init(void)
{
	int i = 0;
	sched_tree = RB_ROOT;
	mutex_init(&sched_mutex);
	mutex_init(&sched_queue_mutex);
	for (i = MIN_PRIO; i < MAX_OCMEM_PRIO; i++)
		INIT_LIST_HEAD(&sched_queue[i]);

	mutex_init(&rdm_mutex);
	INIT_LIST_HEAD(&rdm_queue);
	ocmem_rdm_wq = alloc_workqueue("ocmem_rdm_wq", 0, 0);
	if (!ocmem_rdm_wq)
		return -ENOMEM;
	ocmem_eviction_wq = alloc_workqueue("ocmem_eviction_wq", 0, 0);
	if (!ocmem_eviction_wq)
		return -ENOMEM;
	return 0;
}
