mirror of
https://github.com/torvalds/linux.git
synced 2024-12-04 18:13:04 +00:00
eb82fbcf82
The issue here is that in orangefs_bufmap_alloc() we do: bufmap->buffer_index_array = kzalloc(DIV_ROUND_UP(bufmap->desc_count, BITS_PER_LONG), GFP_KERNEL); If we choose a bufmap->desc_count like -31 then it means the DIV_ROUND_UP ends up having an integer overflow. The result is that kzalloc() returns the ZERO_SIZE_PTR and there is a static checker warning. But this bug is harmless because on the next lines we use ->desc_count to do a kcalloc(). That has integer overflow checking built in so the kcalloc() fails and we return an error code. Anyway, it doesn't make sense to talk about negative sizes and blocking them silences the static checker warning. Signed-off-by: Dan Carpenter <dan.carpenter@oracle.com> Signed-off-by: Mike Marshall <hubcap@omnibond.com>
562 lines
13 KiB
C
562 lines
13 KiB
C
/*
|
|
* (C) 2001 Clemson University and The University of Chicago
|
|
*
|
|
* See COPYING in top-level directory.
|
|
*/
|
|
#include "protocol.h"
|
|
#include "orangefs-kernel.h"
|
|
#include "orangefs-bufmap.h"
|
|
|
|
struct slot_map {
|
|
int c;
|
|
wait_queue_head_t q;
|
|
int count;
|
|
unsigned long *map;
|
|
};
|
|
|
|
static struct slot_map rw_map = {
|
|
.c = -1,
|
|
.q = __WAIT_QUEUE_HEAD_INITIALIZER(rw_map.q)
|
|
};
|
|
static struct slot_map readdir_map = {
|
|
.c = -1,
|
|
.q = __WAIT_QUEUE_HEAD_INITIALIZER(readdir_map.q)
|
|
};
|
|
|
|
|
|
static void install(struct slot_map *m, int count, unsigned long *map)
|
|
{
|
|
spin_lock(&m->q.lock);
|
|
m->c = m->count = count;
|
|
m->map = map;
|
|
wake_up_all_locked(&m->q);
|
|
spin_unlock(&m->q.lock);
|
|
}
|
|
|
|
static void mark_killed(struct slot_map *m)
|
|
{
|
|
spin_lock(&m->q.lock);
|
|
m->c -= m->count + 1;
|
|
spin_unlock(&m->q.lock);
|
|
}
|
|
|
|
static void run_down(struct slot_map *m)
|
|
{
|
|
DEFINE_WAIT(wait);
|
|
spin_lock(&m->q.lock);
|
|
if (m->c != -1) {
|
|
for (;;) {
|
|
if (likely(list_empty(&wait.task_list)))
|
|
__add_wait_queue_tail(&m->q, &wait);
|
|
set_current_state(TASK_UNINTERRUPTIBLE);
|
|
|
|
if (m->c == -1)
|
|
break;
|
|
|
|
spin_unlock(&m->q.lock);
|
|
schedule();
|
|
spin_lock(&m->q.lock);
|
|
}
|
|
__remove_wait_queue(&m->q, &wait);
|
|
__set_current_state(TASK_RUNNING);
|
|
}
|
|
m->map = NULL;
|
|
spin_unlock(&m->q.lock);
|
|
}
|
|
|
|
static void put(struct slot_map *m, int slot)
|
|
{
|
|
int v;
|
|
spin_lock(&m->q.lock);
|
|
__clear_bit(slot, m->map);
|
|
v = ++m->c;
|
|
if (unlikely(v == 1)) /* no free slots -> one free slot */
|
|
wake_up_locked(&m->q);
|
|
else if (unlikely(v == -1)) /* finished dying */
|
|
wake_up_all_locked(&m->q);
|
|
spin_unlock(&m->q.lock);
|
|
}
|
|
|
|
static int wait_for_free(struct slot_map *m)
|
|
{
|
|
long left = slot_timeout_secs * HZ;
|
|
DEFINE_WAIT(wait);
|
|
|
|
do {
|
|
long n = left, t;
|
|
if (likely(list_empty(&wait.task_list)))
|
|
__add_wait_queue_tail_exclusive(&m->q, &wait);
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
|
|
|
if (m->c > 0)
|
|
break;
|
|
|
|
if (m->c < 0) {
|
|
/* we are waiting for map to be installed */
|
|
/* it would better be there soon, or we go away */
|
|
if (n > ORANGEFS_BUFMAP_WAIT_TIMEOUT_SECS * HZ)
|
|
n = ORANGEFS_BUFMAP_WAIT_TIMEOUT_SECS * HZ;
|
|
}
|
|
spin_unlock(&m->q.lock);
|
|
t = schedule_timeout(n);
|
|
spin_lock(&m->q.lock);
|
|
if (unlikely(!t) && n != left && m->c < 0)
|
|
left = t;
|
|
else
|
|
left = t + (left - n);
|
|
if (unlikely(signal_pending(current)))
|
|
left = -EINTR;
|
|
} while (left > 0);
|
|
|
|
if (!list_empty(&wait.task_list))
|
|
list_del(&wait.task_list);
|
|
else if (left <= 0 && waitqueue_active(&m->q))
|
|
__wake_up_locked_key(&m->q, TASK_INTERRUPTIBLE, NULL);
|
|
__set_current_state(TASK_RUNNING);
|
|
|
|
if (likely(left > 0))
|
|
return 0;
|
|
|
|
return left < 0 ? -EINTR : -ETIMEDOUT;
|
|
}
|
|
|
|
static int get(struct slot_map *m)
|
|
{
|
|
int res = 0;
|
|
spin_lock(&m->q.lock);
|
|
if (unlikely(m->c <= 0))
|
|
res = wait_for_free(m);
|
|
if (likely(!res)) {
|
|
m->c--;
|
|
res = find_first_zero_bit(m->map, m->count);
|
|
__set_bit(res, m->map);
|
|
}
|
|
spin_unlock(&m->q.lock);
|
|
return res;
|
|
}
|
|
|
|
/* used to describe mapped buffers */
|
|
struct orangefs_bufmap_desc {
|
|
void *uaddr; /* user space address pointer */
|
|
struct page **page_array; /* array of mapped pages */
|
|
int array_count; /* size of above arrays */
|
|
struct list_head list_link;
|
|
};
|
|
|
|
static struct orangefs_bufmap {
|
|
int desc_size;
|
|
int desc_shift;
|
|
int desc_count;
|
|
int total_size;
|
|
int page_count;
|
|
|
|
struct page **page_array;
|
|
struct orangefs_bufmap_desc *desc_array;
|
|
|
|
/* array to track usage of buffer descriptors */
|
|
unsigned long *buffer_index_array;
|
|
|
|
/* array to track usage of buffer descriptors for readdir */
|
|
#define N DIV_ROUND_UP(ORANGEFS_READDIR_DEFAULT_DESC_COUNT, BITS_PER_LONG)
|
|
unsigned long readdir_index_array[N];
|
|
#undef N
|
|
} *__orangefs_bufmap;
|
|
|
|
static DEFINE_SPINLOCK(orangefs_bufmap_lock);
|
|
|
|
static void
|
|
orangefs_bufmap_unmap(struct orangefs_bufmap *bufmap)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < bufmap->page_count; i++)
|
|
put_page(bufmap->page_array[i]);
|
|
}
|
|
|
|
static void
|
|
orangefs_bufmap_free(struct orangefs_bufmap *bufmap)
|
|
{
|
|
kfree(bufmap->page_array);
|
|
kfree(bufmap->desc_array);
|
|
kfree(bufmap->buffer_index_array);
|
|
kfree(bufmap);
|
|
}
|
|
|
|
/*
|
|
* XXX: Can the size and shift change while the caller gives up the
|
|
* XXX: lock between calling this and doing something useful?
|
|
*/
|
|
|
|
int orangefs_bufmap_size_query(void)
|
|
{
|
|
struct orangefs_bufmap *bufmap;
|
|
int size = 0;
|
|
spin_lock(&orangefs_bufmap_lock);
|
|
bufmap = __orangefs_bufmap;
|
|
if (bufmap)
|
|
size = bufmap->desc_size;
|
|
spin_unlock(&orangefs_bufmap_lock);
|
|
return size;
|
|
}
|
|
|
|
int orangefs_bufmap_shift_query(void)
|
|
{
|
|
struct orangefs_bufmap *bufmap;
|
|
int shift = 0;
|
|
spin_lock(&orangefs_bufmap_lock);
|
|
bufmap = __orangefs_bufmap;
|
|
if (bufmap)
|
|
shift = bufmap->desc_shift;
|
|
spin_unlock(&orangefs_bufmap_lock);
|
|
return shift;
|
|
}
|
|
|
|
static DECLARE_WAIT_QUEUE_HEAD(bufmap_waitq);
|
|
static DECLARE_WAIT_QUEUE_HEAD(readdir_waitq);
|
|
|
|
/*
|
|
* orangefs_get_bufmap_init
|
|
*
|
|
* If bufmap_init is 1, then the shared memory system, including the
|
|
* buffer_index_array, is available. Otherwise, it is not.
|
|
*
|
|
* returns the value of bufmap_init
|
|
*/
|
|
int orangefs_get_bufmap_init(void)
|
|
{
|
|
return __orangefs_bufmap ? 1 : 0;
|
|
}
|
|
|
|
|
|
static struct orangefs_bufmap *
|
|
orangefs_bufmap_alloc(struct ORANGEFS_dev_map_desc *user_desc)
|
|
{
|
|
struct orangefs_bufmap *bufmap;
|
|
|
|
bufmap = kzalloc(sizeof(*bufmap), GFP_KERNEL);
|
|
if (!bufmap)
|
|
goto out;
|
|
|
|
bufmap->total_size = user_desc->total_size;
|
|
bufmap->desc_count = user_desc->count;
|
|
bufmap->desc_size = user_desc->size;
|
|
bufmap->desc_shift = ilog2(bufmap->desc_size);
|
|
|
|
bufmap->buffer_index_array =
|
|
kzalloc(DIV_ROUND_UP(bufmap->desc_count, BITS_PER_LONG), GFP_KERNEL);
|
|
if (!bufmap->buffer_index_array) {
|
|
gossip_err("orangefs: could not allocate %d buffer indices\n",
|
|
bufmap->desc_count);
|
|
goto out_free_bufmap;
|
|
}
|
|
|
|
bufmap->desc_array =
|
|
kcalloc(bufmap->desc_count, sizeof(struct orangefs_bufmap_desc),
|
|
GFP_KERNEL);
|
|
if (!bufmap->desc_array) {
|
|
gossip_err("orangefs: could not allocate %d descriptors\n",
|
|
bufmap->desc_count);
|
|
goto out_free_index_array;
|
|
}
|
|
|
|
bufmap->page_count = bufmap->total_size / PAGE_SIZE;
|
|
|
|
/* allocate storage to track our page mappings */
|
|
bufmap->page_array =
|
|
kcalloc(bufmap->page_count, sizeof(struct page *), GFP_KERNEL);
|
|
if (!bufmap->page_array)
|
|
goto out_free_desc_array;
|
|
|
|
return bufmap;
|
|
|
|
out_free_desc_array:
|
|
kfree(bufmap->desc_array);
|
|
out_free_index_array:
|
|
kfree(bufmap->buffer_index_array);
|
|
out_free_bufmap:
|
|
kfree(bufmap);
|
|
out:
|
|
return NULL;
|
|
}
|
|
|
|
static int
|
|
orangefs_bufmap_map(struct orangefs_bufmap *bufmap,
|
|
struct ORANGEFS_dev_map_desc *user_desc)
|
|
{
|
|
int pages_per_desc = bufmap->desc_size / PAGE_SIZE;
|
|
int offset = 0, ret, i;
|
|
|
|
/* map the pages */
|
|
ret = get_user_pages_fast((unsigned long)user_desc->ptr,
|
|
bufmap->page_count, 1, bufmap->page_array);
|
|
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (ret != bufmap->page_count) {
|
|
gossip_err("orangefs error: asked for %d pages, only got %d.\n",
|
|
bufmap->page_count, ret);
|
|
|
|
for (i = 0; i < ret; i++) {
|
|
SetPageError(bufmap->page_array[i]);
|
|
put_page(bufmap->page_array[i]);
|
|
}
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/*
|
|
* ideally we want to get kernel space pointers for each page, but
|
|
* we can't kmap that many pages at once if highmem is being used.
|
|
* so instead, we just kmap/kunmap the page address each time the
|
|
* kaddr is needed.
|
|
*/
|
|
for (i = 0; i < bufmap->page_count; i++)
|
|
flush_dcache_page(bufmap->page_array[i]);
|
|
|
|
/* build a list of available descriptors */
|
|
for (offset = 0, i = 0; i < bufmap->desc_count; i++) {
|
|
bufmap->desc_array[i].page_array = &bufmap->page_array[offset];
|
|
bufmap->desc_array[i].array_count = pages_per_desc;
|
|
bufmap->desc_array[i].uaddr =
|
|
(user_desc->ptr + (i * pages_per_desc * PAGE_SIZE));
|
|
offset += pages_per_desc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* orangefs_bufmap_initialize()
|
|
*
|
|
* initializes the mapped buffer interface
|
|
*
|
|
* returns 0 on success, -errno on failure
|
|
*/
|
|
int orangefs_bufmap_initialize(struct ORANGEFS_dev_map_desc *user_desc)
|
|
{
|
|
struct orangefs_bufmap *bufmap;
|
|
int ret = -EINVAL;
|
|
|
|
gossip_debug(GOSSIP_BUFMAP_DEBUG,
|
|
"orangefs_bufmap_initialize: called (ptr ("
|
|
"%p) sz (%d) cnt(%d).\n",
|
|
user_desc->ptr,
|
|
user_desc->size,
|
|
user_desc->count);
|
|
|
|
if (user_desc->total_size < 0 ||
|
|
user_desc->size < 0 ||
|
|
user_desc->count < 0)
|
|
goto out;
|
|
|
|
/*
|
|
* sanity check alignment and size of buffer that caller wants to
|
|
* work with
|
|
*/
|
|
if (PAGE_ALIGN((unsigned long)user_desc->ptr) !=
|
|
(unsigned long)user_desc->ptr) {
|
|
gossip_err("orangefs error: memory alignment (front). %p\n",
|
|
user_desc->ptr);
|
|
goto out;
|
|
}
|
|
|
|
if (PAGE_ALIGN(((unsigned long)user_desc->ptr + user_desc->total_size))
|
|
!= (unsigned long)(user_desc->ptr + user_desc->total_size)) {
|
|
gossip_err("orangefs error: memory alignment (back).(%p + %d)\n",
|
|
user_desc->ptr,
|
|
user_desc->total_size);
|
|
goto out;
|
|
}
|
|
|
|
if (user_desc->total_size != (user_desc->size * user_desc->count)) {
|
|
gossip_err("orangefs error: user provided an oddly sized buffer: (%d, %d, %d)\n",
|
|
user_desc->total_size,
|
|
user_desc->size,
|
|
user_desc->count);
|
|
goto out;
|
|
}
|
|
|
|
if ((user_desc->size % PAGE_SIZE) != 0) {
|
|
gossip_err("orangefs error: bufmap size not page size divisible (%d).\n",
|
|
user_desc->size);
|
|
goto out;
|
|
}
|
|
|
|
ret = -ENOMEM;
|
|
bufmap = orangefs_bufmap_alloc(user_desc);
|
|
if (!bufmap)
|
|
goto out;
|
|
|
|
ret = orangefs_bufmap_map(bufmap, user_desc);
|
|
if (ret)
|
|
goto out_free_bufmap;
|
|
|
|
|
|
spin_lock(&orangefs_bufmap_lock);
|
|
if (__orangefs_bufmap) {
|
|
spin_unlock(&orangefs_bufmap_lock);
|
|
gossip_err("orangefs: error: bufmap already initialized.\n");
|
|
ret = -EINVAL;
|
|
goto out_unmap_bufmap;
|
|
}
|
|
__orangefs_bufmap = bufmap;
|
|
install(&rw_map,
|
|
bufmap->desc_count,
|
|
bufmap->buffer_index_array);
|
|
install(&readdir_map,
|
|
ORANGEFS_READDIR_DEFAULT_DESC_COUNT,
|
|
bufmap->readdir_index_array);
|
|
spin_unlock(&orangefs_bufmap_lock);
|
|
|
|
gossip_debug(GOSSIP_BUFMAP_DEBUG,
|
|
"orangefs_bufmap_initialize: exiting normally\n");
|
|
return 0;
|
|
|
|
out_unmap_bufmap:
|
|
orangefs_bufmap_unmap(bufmap);
|
|
out_free_bufmap:
|
|
orangefs_bufmap_free(bufmap);
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* orangefs_bufmap_finalize()
|
|
*
|
|
* shuts down the mapped buffer interface and releases any resources
|
|
* associated with it
|
|
*
|
|
* no return value
|
|
*/
|
|
void orangefs_bufmap_finalize(void)
|
|
{
|
|
struct orangefs_bufmap *bufmap = __orangefs_bufmap;
|
|
if (!bufmap)
|
|
return;
|
|
gossip_debug(GOSSIP_BUFMAP_DEBUG, "orangefs_bufmap_finalize: called\n");
|
|
mark_killed(&rw_map);
|
|
mark_killed(&readdir_map);
|
|
gossip_debug(GOSSIP_BUFMAP_DEBUG,
|
|
"orangefs_bufmap_finalize: exiting normally\n");
|
|
}
|
|
|
|
void orangefs_bufmap_run_down(void)
|
|
{
|
|
struct orangefs_bufmap *bufmap = __orangefs_bufmap;
|
|
if (!bufmap)
|
|
return;
|
|
run_down(&rw_map);
|
|
run_down(&readdir_map);
|
|
spin_lock(&orangefs_bufmap_lock);
|
|
__orangefs_bufmap = NULL;
|
|
spin_unlock(&orangefs_bufmap_lock);
|
|
orangefs_bufmap_unmap(bufmap);
|
|
orangefs_bufmap_free(bufmap);
|
|
}
|
|
|
|
/*
|
|
* orangefs_bufmap_get()
|
|
*
|
|
* gets a free mapped buffer descriptor, will sleep until one becomes
|
|
* available if necessary
|
|
*
|
|
* returns slot on success, -errno on failure
|
|
*/
|
|
int orangefs_bufmap_get(void)
|
|
{
|
|
return get(&rw_map);
|
|
}
|
|
|
|
/*
|
|
* orangefs_bufmap_put()
|
|
*
|
|
* returns a mapped buffer descriptor to the collection
|
|
*
|
|
* no return value
|
|
*/
|
|
void orangefs_bufmap_put(int buffer_index)
|
|
{
|
|
put(&rw_map, buffer_index);
|
|
}
|
|
|
|
/*
|
|
* orangefs_readdir_index_get()
|
|
*
|
|
* gets a free descriptor, will sleep until one becomes
|
|
* available if necessary.
|
|
* Although the readdir buffers are not mapped into kernel space
|
|
* we could do that at a later point of time. Regardless, these
|
|
* indices are used by the client-core.
|
|
*
|
|
* returns slot on success, -errno on failure
|
|
*/
|
|
int orangefs_readdir_index_get(void)
|
|
{
|
|
return get(&readdir_map);
|
|
}
|
|
|
|
void orangefs_readdir_index_put(int buffer_index)
|
|
{
|
|
put(&readdir_map, buffer_index);
|
|
}
|
|
|
|
/*
|
|
* we've been handed an iovec, we need to copy it to
|
|
* the shared memory descriptor at "buffer_index".
|
|
*/
|
|
int orangefs_bufmap_copy_from_iovec(struct iov_iter *iter,
|
|
int buffer_index,
|
|
size_t size)
|
|
{
|
|
struct orangefs_bufmap_desc *to;
|
|
int i;
|
|
|
|
gossip_debug(GOSSIP_BUFMAP_DEBUG,
|
|
"%s: buffer_index:%d: size:%zu:\n",
|
|
__func__, buffer_index, size);
|
|
|
|
to = &__orangefs_bufmap->desc_array[buffer_index];
|
|
for (i = 0; size; i++) {
|
|
struct page *page = to->page_array[i];
|
|
size_t n = size;
|
|
if (n > PAGE_SIZE)
|
|
n = PAGE_SIZE;
|
|
n = copy_page_from_iter(page, 0, n, iter);
|
|
if (!n)
|
|
return -EFAULT;
|
|
size -= n;
|
|
}
|
|
return 0;
|
|
|
|
}
|
|
|
|
/*
|
|
* we've been handed an iovec, we need to fill it from
|
|
* the shared memory descriptor at "buffer_index".
|
|
*/
|
|
int orangefs_bufmap_copy_to_iovec(struct iov_iter *iter,
|
|
int buffer_index,
|
|
size_t size)
|
|
{
|
|
struct orangefs_bufmap_desc *from;
|
|
int i;
|
|
|
|
from = &__orangefs_bufmap->desc_array[buffer_index];
|
|
gossip_debug(GOSSIP_BUFMAP_DEBUG,
|
|
"%s: buffer_index:%d: size:%zu:\n",
|
|
__func__, buffer_index, size);
|
|
|
|
|
|
for (i = 0; size; i++) {
|
|
struct page *page = from->page_array[i];
|
|
size_t n = size;
|
|
if (n > PAGE_SIZE)
|
|
n = PAGE_SIZE;
|
|
n = copy_page_to_iter(page, 0, n, iter);
|
|
if (!n)
|
|
return -EFAULT;
|
|
size -= n;
|
|
}
|
|
return 0;
|
|
}
|