| /* 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; |
| |
| 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); |
| |
| 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) |
| { |
| 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; |
| } |
| |
| if (!buffer) { |
| pr_err("ocmem: Invalid buffer\n"); |
| return -EINVAL; |
| } |
| |
| return __ocmem_free(client_id, buffer); |
| } |
| 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); |
| } |
| |
| /* 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; |
| } |