blob: 13c4c1af3e436e72ca0c37f064b2588429919c82 [file] [log] [blame]
/* Copyright (c) 2012-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/slab.h>
#include <mach/ocmem_priv.h>
static DEFINE_MUTEX(ocmem_eviction_lock);
static DECLARE_BITMAP(evicted, OCMEM_CLIENT_MAX);
static struct ocmem_handle *generate_handle(void)
{
struct ocmem_handle *handle = NULL;
handle = kzalloc(sizeof(struct ocmem_handle), GFP_KERNEL);
if (!handle) {
pr_err("ocmem: Unable to generate buffer handle\n");
return NULL;
}
mutex_init(&handle->handle_mutex);
return handle;
}
static int free_handle(struct ocmem_handle *handle)
{
if (!handle)
return -EINVAL;
mutex_destroy(&handle->handle_mutex);
kfree(handle);
handle = NULL;
return 0;
}
static int __ocmem_free(int id, struct ocmem_buf *buf)
{
int ret = 0;
struct ocmem_handle *handle = buffer_to_handle(buf);
if (!handle)
return -EINVAL;
mutex_lock(&handle->handle_mutex);
ret = process_free(id, handle);
mutex_unlock(&handle->handle_mutex);
if (ret) {
pr_err("ocmem: Free failed for client %s\n", get_name(id));
return ret;
}
free_handle(handle);
return 0;
}
static int __ocmem_shrink(int id, struct ocmem_buf *buf, unsigned long len)
{
int ret = 0;
struct ocmem_handle *handle = buffer_to_handle(buf);
if (!handle)
return -EINVAL;
mutex_lock(&handle->handle_mutex);
ret = process_shrink(id, handle, len);
mutex_unlock(&handle->handle_mutex);
if (ret)
return -EINVAL;
return 0;
}
static struct ocmem_buf *__ocmem_allocate_range(int id, unsigned long min,
unsigned long max, unsigned long step, bool block, bool wait)
{
struct ocmem_handle *handle = NULL;
int ret = 0;
handle = generate_handle();
if (!handle) {
pr_err("ocmem: Unable to generate handle\n");
return NULL;
}
mutex_lock(&handle->handle_mutex);
ret = process_allocate(id, handle, min, max, step, block, wait);
mutex_unlock(&handle->handle_mutex);
if (ret) {
pr_err("ocmem allocation failed\n");
free_handle(handle);
return NULL;
} else
return handle_to_buffer(handle);
}
struct ocmem_buf *ocmem_allocate(int client_id, unsigned long size)
{
bool can_block = false;
bool can_wait = true;
struct ocmem_buf *buffer;
struct timeval start_time;
struct timeval end_time;
unsigned int delay;
struct ocmem_zone *zone;
if (!check_id(client_id)) {
pr_err("ocmem: Invalid client id: %d\n", client_id);
return NULL;
}
if (!zone_active(client_id)) {
pr_err("ocmem: Client %s (id: %d) not allowed to use OCMEM\n",
get_name(client_id), client_id);
return NULL;
}
if (size < OCMEM_MIN_ALLOC) {
pr_err("ocmem: requested size %lx must be at least %x\n",
size, OCMEM_MIN_ALLOC);
return NULL;
}
if (!IS_ALIGNED(size, OCMEM_MIN_ALIGN)) {
pr_err("ocmem: Invalid alignment, size must be %x aligned\n",
OCMEM_MIN_ALIGN);
return NULL;
}
zone = get_zone(client_id);
if (!zone) {
pr_err("ocmem: Zone not found for client %d\n", client_id);
return NULL;
}
do_gettimeofday(&start_time);
buffer = __ocmem_allocate_range(client_id, size, size,
size, can_block, can_wait);
do_gettimeofday(&end_time);
if (!buffer)
return NULL;
delay = (end_time.tv_sec * USEC_PER_SEC + end_time.tv_usec)
- (start_time.tv_sec * USEC_PER_SEC + start_time.tv_usec);
if (delay > zone->max_alloc_time)
zone->max_alloc_time = delay;
if (delay < zone->min_alloc_time)
zone->min_alloc_time = delay;
zone->total_alloc_time += delay;
inc_ocmem_stat(zone, NR_SYNC_ALLOCATIONS);
return buffer;
}
EXPORT_SYMBOL(ocmem_allocate);
struct ocmem_buf *ocmem_allocate_nowait(int client_id, unsigned long size)
{
bool can_block = false;
bool can_wait = false;
if (!check_id(client_id)) {
pr_err("ocmem: Invalid client id: %d\n", client_id);
return NULL;
}
if (!zone_active(client_id)) {
pr_err("ocmem: Client %s (id: %d) not allowed to use OCMEM\n",
get_name(client_id), client_id);
return NULL;
}
if (size < OCMEM_MIN_ALLOC) {
pr_err("ocmem: requested size %lx must be at least %x\n",
size, OCMEM_MIN_ALLOC);
return NULL;
}
if (!IS_ALIGNED(size, OCMEM_MIN_ALIGN)) {
pr_err("ocmem: Invalid alignment, size must be %x aligned\n",
OCMEM_MIN_ALIGN);
return NULL;
}
return __ocmem_allocate_range(client_id, size, size,
size, can_block, can_wait);
}
EXPORT_SYMBOL(ocmem_allocate_nowait);
struct ocmem_buf *ocmem_allocate_range(int client_id, unsigned long min,
unsigned long goal, unsigned long step)
{
bool can_block = true;
bool can_wait = false;
if (!check_id(client_id)) {
pr_err("ocmem: Invalid client id: %d\n", client_id);
return NULL;
}
if (!zone_active(client_id)) {
pr_err("ocmem: Client %s (id: %d) not allowed to use OCMEM\n",
get_name(client_id), client_id);
return NULL;
}
/* Asynchronous API requires notifier registration */
if (!check_notifier(client_id)) {
pr_err("ocmem: No notifier registered for client %d\n",
client_id);
return NULL;
}
if (min < OCMEM_MIN_ALLOC) {
pr_err("ocmem: requested min size %lx must be at least %x\n",
min, OCMEM_MIN_ALLOC);
return NULL;
}
if (!IS_ALIGNED(min | goal | step, OCMEM_MIN_ALIGN)) {
pr_err("ocmem: Invalid alignment, args must be %x aligned\n",
OCMEM_MIN_ALIGN);
return NULL;
}
return __ocmem_allocate_range(client_id, min, goal,
step, can_block, can_wait);
}
EXPORT_SYMBOL(ocmem_allocate_range);
struct ocmem_buf *ocmem_allocate_nb(int client_id, unsigned long size)
{
bool can_block = true;
bool can_wait = false;
if (!check_id(client_id)) {
pr_err("ocmem: Invalid client id: %d\n", client_id);
return NULL;
}
/* Asynchronous API requires notifier registration */
if (!check_notifier(client_id)) {
pr_err("ocmem: No notifier registered for client %d\n",
client_id);
return NULL;
}
if (!zone_active(client_id)) {
pr_err("ocmem: Client %s (id: %d) not allowed to use OCMEM\n",
get_name(client_id), client_id);
return NULL;
}
if (size < OCMEM_MIN_ALLOC) {
pr_err("ocmem: requested size %lx must be at least %x\n",
size, OCMEM_MIN_ALLOC);
return NULL;
}
if (!IS_ALIGNED(size, OCMEM_MIN_ALIGN)) {
pr_err("ocmem: Invalid alignment, args must be %x aligned\n",
OCMEM_MIN_ALIGN);
return NULL;
}
return __ocmem_allocate_range(client_id, 0, size, size,
can_block, can_wait);
}
EXPORT_SYMBOL(ocmem_allocate_nb);
int ocmem_free(int client_id, struct ocmem_buf *buffer)
{
int rc;
struct timeval start_time;
struct timeval end_time;
unsigned int delay;
struct ocmem_zone *zone;
if (!check_id(client_id)) {
pr_err("ocmem: Invalid client id: %d\n", client_id);
return -EINVAL;
}
if (!zone_active(client_id)) {
pr_err("ocmem: Client %s (id: %d) not allowed to use OCMEM\n",
get_name(client_id), client_id);
return -EINVAL;
}
zone = get_zone(client_id);
if (!zone) {
pr_err("ocmem: Zone not found for client %d\n", client_id);
return -EINVAL;
}
if (!buffer) {
pr_err("ocmem: Invalid buffer\n");
return -EINVAL;
}
do_gettimeofday(&start_time);
rc = __ocmem_free(client_id, buffer);
do_gettimeofday(&end_time);
if (rc < 0)
return rc;
delay = (end_time.tv_sec * USEC_PER_SEC + end_time.tv_usec)
- (start_time.tv_sec * USEC_PER_SEC + start_time.tv_usec);
if (delay > zone->max_free_time)
zone->max_free_time = delay;
if (delay < zone->min_free_time)
zone->min_free_time = delay;
zone->total_free_time += delay;
inc_ocmem_stat(zone, NR_FREES);
return rc;
}
EXPORT_SYMBOL(ocmem_free);
int ocmem_shrink(int client_id, struct ocmem_buf *buffer, unsigned long len)
{
if (!buffer)
return -EINVAL;
if (len >= buffer->len)
return -EINVAL;
if (!zone_active(client_id)) {
pr_err("ocmem: Client id: %s (id: %d) not allowed to use OCMEM\n",
get_name(client_id), client_id);
return -EINVAL;
}
return __ocmem_shrink(client_id, buffer, len);
}
EXPORT_SYMBOL(ocmem_shrink);
int pre_validate_chunk_list(struct ocmem_map_list *list)
{
int i = 0;
struct ocmem_chunk *chunks;
if (!list)
return -EINVAL;
if (list->num_chunks > OCMEM_MAX_CHUNKS || list->num_chunks == 0)
return -EINVAL;
chunks = list->chunks;
if (!chunks)
return -EINVAL;
for (i = 0; i < list->num_chunks; i++) {
if (!chunks[i].ddr_paddr ||
!IS_ALIGNED(chunks[i].ddr_paddr, MIN_CHUNK_SIZE) ||
chunks[i].size < MIN_CHUNK_SIZE ||
!IS_ALIGNED(chunks[i].size, MIN_CHUNK_SIZE)) {
pr_err("Invalid ocmem chunk at index %d (p: %lx, size %lx)\n",
i, chunks[i].ddr_paddr, chunks[i].size);
return -EINVAL;
}
}
return 0;
}
int ocmem_map(int client_id, struct ocmem_buf *buffer,
struct ocmem_map_list *list)
{
int ret = 0;
struct ocmem_handle *handle = NULL;
if (!check_id(client_id)) {
pr_err("ocmem: Invalid client id: %d\n", client_id);
return -EINVAL;
}
if (!zone_active(client_id)) {
pr_err("ocmem: Client id: %s (id: %d) not allowed to use OCMEM\n",
get_name(client_id), client_id);
return -EINVAL;
}
/* Asynchronous API requires notifier registration */
if (!check_notifier(client_id)) {
pr_err("ocmem: No notifier registered for client %d\n",
client_id);
return -EINVAL;
}
if (!buffer) {
pr_err("ocmem: Invalid buffer\n");
return -EINVAL;
}
if (pre_validate_chunk_list(list) != 0)
return -EINVAL;
handle = buffer_to_handle(buffer);
if (!handle)
return -EINVAL;
mutex_lock(&handle->handle_mutex);
ret = process_xfer(client_id, handle, list, TO_OCMEM);
mutex_unlock(&handle->handle_mutex);
return ret;
}
EXPORT_SYMBOL(ocmem_map);
int ocmem_unmap(int client_id, struct ocmem_buf *buffer,
struct ocmem_map_list *list)
{
int ret = 0;
struct ocmem_handle *handle = NULL;
if (!check_id(client_id)) {
pr_err("ocmem: Invalid client id: %d\n", client_id);
return -EINVAL;
}
if (!zone_active(client_id)) {
pr_err("ocmem: Client id: %s (id: %d) not allowed to use OCMEM\n",
get_name(client_id), client_id);
return -EINVAL;
}
/* Asynchronous API requires notifier registration */
if (!check_notifier(client_id)) {
pr_err("ocmem: No notifier registered for client %d\n",
client_id);
return -EINVAL;
}
if (!buffer) {
pr_err("ocmem: Invalid buffer\n");
return -EINVAL;
}
if (pre_validate_chunk_list(list) != 0)
return -EINVAL;
handle = buffer_to_handle(buffer);
mutex_lock(&handle->handle_mutex);
ret = process_xfer(client_id, handle, list, TO_DDR);
mutex_unlock(&handle->handle_mutex);
return ret;
}
EXPORT_SYMBOL(ocmem_unmap);
int ocmem_drop(int client_id, struct ocmem_buf *buffer,
struct ocmem_map_list *list)
{
int ret = 0;
struct ocmem_handle *handle = NULL;
if (!check_id(client_id)) {
pr_err("ocmem: Invalid client id: %d\n", client_id);
return -EINVAL;
}
if (!zone_active(client_id)) {
pr_err("ocmem: Client id: %s (id: %d) not allowed to use OCMEM\n",
get_name(client_id), client_id);
return -EINVAL;
}
if (!buffer) {
pr_err("ocmem: Invalid buffer\n");
return -EINVAL;
}
if (pre_validate_chunk_list(list) != 0)
return -EINVAL;
handle = buffer_to_handle(buffer);
mutex_lock(&handle->handle_mutex);
ret = process_drop(client_id, handle, list);
mutex_unlock(&handle->handle_mutex);
return ret;
}
EXPORT_SYMBOL(ocmem_drop);
int ocmem_dump(int client_id, struct ocmem_buf *buffer,
unsigned long dst_phys_addr)
{
int ret = 0;
struct ocmem_handle *handle = NULL;
if (!check_id(client_id)) {
pr_err("ocmem: Invalid client id: %d\n", client_id);
return -EINVAL;
}
if (!zone_active(client_id)) {
pr_err("ocmem: Client id: %s (id: %d) not allowed to use OCMEM\n",
get_name(client_id), client_id);
return -EINVAL;
}
if (!buffer) {
pr_err("ocmem: Invalid buffer\n");
return -EINVAL;
}
handle = buffer_to_handle(buffer);
mutex_lock(&handle->handle_mutex);
ret = process_dump(client_id, handle, dst_phys_addr);
mutex_unlock(&handle->handle_mutex);
return ret;
}
EXPORT_SYMBOL(ocmem_dump);
unsigned long get_max_quota(int client_id)
{
if (!check_id(client_id)) {
pr_err("ocmem: Invalid client id: %d\n", client_id);
return 0x0;
}
return process_quota(client_id);
}
EXPORT_SYMBOL(get_max_quota);
/* Synchronous eviction/restore calls */
/* Only a single eviction or restoration is allowed */
/* Evictions/Restorations cannot be concurrent with other maps */
int ocmem_evict(int client_id)
{
int ret = 0;
if (!check_id(client_id)) {
pr_err("ocmem: Invalid client id: %d\n", client_id);
return -EINVAL;
}
mutex_lock(&ocmem_eviction_lock);
if (test_bit(client_id, evicted)) {
pr_err("ocmem: Previous eviction was not restored by %d\n",
client_id);
mutex_unlock(&ocmem_eviction_lock);
return -EINVAL;
}
ret = process_evict(client_id);
if (ret == 0)
set_bit(client_id, evicted);
mutex_unlock(&ocmem_eviction_lock);
return ret;
}
EXPORT_SYMBOL(ocmem_evict);
int ocmem_restore(int client_id)
{
int ret = 0;
if (!check_id(client_id)) {
pr_err("ocmem: Invalid client id: %d\n", client_id);
return -EINVAL;
}
mutex_lock(&ocmem_eviction_lock);
if (!test_bit(client_id, evicted)) {
pr_err("ocmem: No previous eviction by %d\n", client_id);
mutex_unlock(&ocmem_eviction_lock);
return -EINVAL;
}
ret = process_restore(client_id);
clear_bit(client_id, evicted);
mutex_unlock(&ocmem_eviction_lock);
return ret;
}
EXPORT_SYMBOL(ocmem_restore);
/* Wrappers until power control is transitioned to clients */
enum ocmem_power_state ocmem_get_power_state(int client_id,
struct ocmem_buf *buffer)
{
return 0;
}
int ocmem_set_power_state(int client_id, struct ocmem_buf *buffer,
enum ocmem_power_state new_state)
{
return 0;
}
struct ocmem_vectors *ocmem_get_vectors(int client_id,
struct ocmem_buf *buffer)
{
return NULL;
}