LINUX: fix array indexing issue in memory statistics
[openafs.git] / src / afs / LINUX / osi_alloc.c
index f076d7e..6fbaf00 100644 (file)
  * osi_alloc.c - Linux memory allocation routines.
  *
  */
-#include "../afs/param.h"
-#include "../afs/sysincludes.h"
-#include "../afs/afsincludes.h"
-#include "../h/mm.h"
+#include <afsconfig.h>
+#include "afs/param.h"
 
-#include "../afs/afs_atomlist.h"
-#include "../afs/afs_lhash.h"
 
-#define MAX_KMALLOC_SIZE (131072-16) /* Max we can alloc in physmem */
-#define MAX_BUCKET_LEN 30 /* max. no. of entries per buckets we expect to see */
-#define STAT_INTERVAL 8192 /* we collect stats once every STAT_INTERVAL allocs*/
+#include "afs/sysincludes.h"
+#include "afsincludes.h"
+#include <linux/mm.h>
+#include <linux/slab.h>
+
+#include "afs_atomlist.h"
+#include "afs_lhash.h"
+
+#define MAX_KMALLOC_SIZE PAGE_SIZE     /* Max we should alloc with kmalloc */
+#define MAX_BUCKET_LEN 30      /* max. no. of entries per buckets we expect to see */
+#define STAT_INTERVAL 8192     /* we collect stats once every STAT_INTERVAL allocs */
 
 /* types of alloc */
-#define KM_TYPE 1        /* kmalloc */
-#define VM_TYPE 2        /* vmalloc */
+#define KM_TYPE 1              /* kmalloc */
+#define VM_TYPE 2              /* vmalloc */
 
 struct osi_linux_mem {
     void *chunk;
@@ -36,30 +40,24 @@ struct osi_linux_mem {
 #define MEMADDR(A) (void *)((unsigned long)(A) & (~0x3))
 
 /* globals */
-afs_atomlist *al_mem_pool; /* pool of osi_linux_mem structures */
-afs_lhash    *lh_mem_htab; /* mem hash table */
-unsigned int allocator_init = 0; /* has the allocator been initialized? */
+afs_atomlist *al_mem_pool;     /* pool of osi_linux_mem structures */
+afs_lhash *lh_mem_htab;                /* mem hash table */
+unsigned int allocator_init = 0;       /* has the allocator been initialized? */
 unsigned int afs_linux_cur_allocs = 0;
 unsigned int afs_linux_total_allocs = 0;
-unsigned int afs_linux_hash_verify_count = 0; /* used by hash_verify */
-struct afs_lhash_stat afs_linux_lsb; /* hash table statistics */
-unsigned int afs_linux_hash_bucket_dist[MAX_BUCKET_LEN]; /* bucket population distribution in our hash table */
+unsigned int afs_linux_hash_verify_count = 0;  /* used by hash_verify */
+struct afs_lhash_stat afs_linux_lsb;   /* hash table statistics */
+unsigned int afs_linux_hash_bucket_dist[MAX_BUCKET_LEN];       /* bucket population distribution in our hash table */
 
-#if defined(AFS_LINUX24_ENV)
-#include "../h/vmalloc.h"
-#else
-/* externs : can we do this in a better way. Including vmalloc.h causes other
- * problems.*/
-extern void vfree(void * addr);
-extern void *vmalloc(unsigned long size);
-#endif
+#include <linux/vmalloc.h>
 
 /* Allocator support functions (static) */
 
-static int hash_equal(const void *a, const void *b) 
+static int
+hash_equal(const void *a, const void *b)
 {
-    return ( MEMADDR(((struct osi_linux_mem *)a)->chunk) == 
-           MEMADDR(((struct osi_linux_mem *)b)->chunk) );
+    return (MEMADDR(((struct osi_linux_mem *)a)->chunk) ==
+           MEMADDR(((struct osi_linux_mem *)b)->chunk));
 
 }
 
@@ -71,59 +69,73 @@ static int hash_equal(const void *a, const void *b)
  *  returns NULL if we failed to allocate memory.
  *  or pointer to memory if we succeeded.
  */
-static void *linux_alloc(unsigned int asize)
+static void *
+linux_alloc(unsigned int asize, int drop_glock)
 {
     void *new = NULL;
-    int has_afs_glock = ISAFS_GLOCK();
-
-    /* if global lock has been held save this info and unlock it. */
-    if (has_afs_glock)
-        AFS_GUNLOCK();
+    int max_retry = 10;
+    int haveGlock = ISAFS_GLOCK();
 
     /*  if we can use kmalloc use it to allocate the required memory. */
-    if (asize <  MAX_KMALLOC_SIZE) {
-        new = (void *)(long)kmalloc(asize, GFP_KERNEL);
-        if (new) /* piggy back alloc type */
-            (unsigned long)new |= KM_TYPE;
-    }
-    if (!new) { /* otherwise use vmalloc  */
-       int max_wait = 10;
-        while (!(new = (void *)vmalloc(asize))) {
-            if (--max_wait <=0) {
-               break;
-            }
-           schedule();
-        }
-       if (new) /* piggy back alloc type */
-           (unsigned long)new |= VM_TYPE;
+    while (!new && max_retry) {
+       if (asize <= MAX_KMALLOC_SIZE) {
+           new = (void *)(unsigned long)kmalloc(asize, GFP_NOFS);
+           if (new)            /* piggy back alloc type */
+               new = (void *)(KM_TYPE | (unsigned long)new);
+       } else {
+           osi_Assert(drop_glock || !haveGlock);
+           if (drop_glock && haveGlock)
+               AFS_GUNLOCK();
+           new = (void *)vmalloc(asize);
+           if (drop_glock && haveGlock)
+               AFS_GLOCK();
+           if (new)            /* piggy back alloc type */
+               new = (void *)(VM_TYPE | (unsigned long)new);
+       }
+
+       if (!new) {
+#ifdef set_current_state
+           set_current_state(TASK_INTERRUPTIBLE);
+#else
+           current->state = TASK_INTERRUPTIBLE;
+#endif
+           if (drop_glock && haveGlock)
+               AFS_GUNLOCK();
+           schedule_timeout(HZ);
+           if (drop_glock && haveGlock)
+               AFS_GLOCK();
+#ifdef set_current_state
+           set_current_state(TASK_RUNNING);
+#else
+           current->state = TASK_RUNNING;
+#endif
+           --max_retry;
+       }
     }
     if (new)
        memset(MEMADDR(new), 0, asize);
 
-    /* if the global lock had been held, lock it again. */
-    if (has_afs_glock)
-        AFS_GLOCK();
-
     return new;
 }
 
-static void linux_free(void *p)
+static void
+linux_free(void *p)
 {
-    
+
     /* mask out the type information from the pointer and
-     * use the appropriate free routine to free the chunk.
+     *  use the appropriate free routine to free the chunk.
      */
-    switch(MEMTYPE(p)) {
-      case KM_TYPE:
-        kfree(MEMADDR(p));
-        break;
-      case VM_TYPE:
-        vfree(MEMADDR(p));
-        break;
-      default:
-        printf("afs_osi_Free: Asked to free unknown type %d at 0x%x\n",
-               MEMTYPE(p), MEMADDR(p));
-        break;
+    switch (MEMTYPE(p)) {
+    case KM_TYPE:
+       kfree(MEMADDR(p));
+       break;
+    case VM_TYPE:
+       vfree(MEMADDR(p));
+       break;
+    default:
+       printf("afs_osi_Free: Asked to free unknown type %d at 0x%lx\n",
+              (int)MEMTYPE(p), (unsigned long)MEMADDR(p));
+       break;
     }
 
 }
@@ -135,12 +147,13 @@ static void linux_free(void *p)
  */
 #define HASH_CONST   32786
 #define HASH_PRIME   79367
-static unsigned hash_chunk(void *p)
+static unsigned
+hash_chunk(void *p)
 {
     unsigned int key;
 
     key = (unsigned int)(long)p >> 2;
-    key = (key * HASH_CONST)%HASH_PRIME;
+    key = (key * HASH_CONST) % HASH_PRIME;
 
     return key;
 }
@@ -171,19 +184,18 @@ hash_verify(size_t index, unsigned key, void *data)
     int memtype;
 
     memtype = MEMTYPE(lmp->chunk);
-    if ((memtype == KM_TYPE) && (AFS_LINUX_MAP_NR(lmp->chunk) > max_mapnr)) {
-       printf("osi_linux_verify_alloced_memory: address 0x%x outside range, index=%d, key=%d\n", lmp->chunk, index, key);
-    }
-    
     if (memtype != KM_TYPE && memtype != VM_TYPE) {
-       printf("osi_linux_verify_alloced_memory: unknown type %d at 0x%x, index=%d\n",    memtype, lmp->chunk, index);
+       printf
+           ("osi_linux_verify_alloced_memory: unknown type %d at 0x%lx, index=%lu\n",
+            (int)memtype, (unsigned long)lmp->chunk, (unsigned long)index);
     }
     afs_linux_hash_verify_count++;
 }
 
 
 /* local_free() : wrapper for vfree(), to deal with incompatible protoypes */
-static void local_free(void *p, size_t n)
+static void
+local_free(void *p, size_t n)
 {
     vfree(p);
 }
@@ -195,57 +207,63 @@ static void local_free(void *p, size_t n)
  *    0 - failure
  *    1 - success
  */
-static int linux_alloc_init()
+static int
+linux_alloc_init(void)
 {
     /* initiate our pool of osi_linux_mem structs */
-    al_mem_pool = afs_atomlist_create(sizeof(struct osi_linux_mem),
-                                     sizeof(long)*1024, (void *)vmalloc, 
-                                     local_free);
+    al_mem_pool =
+       afs_atomlist_create(sizeof(struct osi_linux_mem), sizeof(long) * 1024,
+                           (void *)vmalloc, local_free);
     if (!al_mem_pool) {
-        printf("afs_osi_Alloc: Error in initialization(atomlist_create)\n");
-        return 0;
+       printf("afs_osi_Alloc: Error in initialization(atomlist_create)\n");
+       return 0;
     }
 
     /* initialize the hash table to hold references to alloc'ed chunks */
     lh_mem_htab = afs_lhash_create(hash_equal, (void *)vmalloc, local_free);
     if (!lh_mem_htab) {
-        printf("afs_osi_Alloc: Error in initialization(lhash_create)\n");
-        return 0;
+       printf("afs_osi_Alloc: Error in initialization(lhash_create)\n");
+       return 0;
     }
-    
+
     return 1;
-    
+
 }
 
 /* hash_bucket_stat() : Counts the no. of elements in each bucket and
  *   stores results in our bucket stats vector.
  */
 static unsigned int cur_bucket, cur_bucket_len;
-static void hash_bucket_stat(size_t index, unsigned key, void *data)
+static void
+hash_bucket_stat(size_t index, unsigned key, void *data)
 {
-    if (index == cur_bucket) { 
+    if (index == cur_bucket) {
        /* while still on the same bucket, inc len & return */
        cur_bucket_len++;
        return;
-    }
-    else { /* if we're on the next bucket, store the distribution */
+    } else {                   /* if we're on the next bucket, store the distribution */
        if (cur_bucket_len < MAX_BUCKET_LEN)
            afs_linux_hash_bucket_dist[cur_bucket_len]++;
        else
-           printf("afs_get_hash_stats: Warning! exceeded max bucket len %d\n", cur_bucket_len);
+           printf
+               ("afs_get_hash_stats: Warning! exceeded max bucket len %d\n",
+                cur_bucket_len);
        cur_bucket = index;
        cur_bucket_len = 1;
     }
 }
+
 /* get_hash_stats() : get hash table statistics */
-static void get_hash_stats()
+static void
+get_hash_stats(void)
 {
     int i;
 
     afs_lhash_stat(lh_mem_htab, &afs_linux_lsb);
 
     /* clear out the bucket stat vector */
-    for(i=0;i<MAX_BUCKET_LEN;i++, afs_linux_hash_bucket_dist[i]=0);
+    for (i = 0; i < MAX_BUCKET_LEN; i++)
+       afs_linux_hash_bucket_dist[i] = 0;
     cur_bucket = cur_bucket_len = 00;
 
     /* populate the bucket stat vector */
@@ -254,32 +272,35 @@ static void get_hash_stats()
 
 /************** Linux memory allocator interface functions **********/
 
-#if defined(AFS_LINUX24_ENV)
-DECLARE_MUTEX(afs_linux_alloc_sem);
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16)
+DEFINE_MUTEX(afs_linux_alloc_sem);
 #else
-struct semaphore afs_linux_alloc_sem = MUTEX;
+DECLARE_MUTEX(afs_linux_alloc_sem);
 #endif
 
-void *osi_linux_alloc(unsigned int asize)
+void *
+osi_linux_alloc(unsigned int asize, int drop_glock)
 {
     void *new = NULL;
     struct osi_linux_mem *lmem;
 
-    down(&afs_linux_alloc_sem);
+    new = linux_alloc(asize, drop_glock);      /* get a chunk of memory of size asize */
+
+    if (!new) {
+       printf("afs_osi_Alloc: Can't vmalloc %d bytes.\n", asize);
+       return new;
+    }
+
+    mutex_lock(&afs_linux_alloc_sem);
 
-    if (allocator_init == 0) { /* allocator hasn't been initialized yet */
+    /* allocator hasn't been initialized yet */
+    if (allocator_init == 0) {
        if (linux_alloc_init() == 0) {
            goto error;
-        }
-       allocator_init = 1; /* initialization complete */
+       }
+       allocator_init = 1;     /* initialization complete */
     }
 
-    new = linux_alloc(asize); /* get a chunk of memory of size asize */
-    if (!new) {
-       printf("afs_osi_Alloc: Can't vmalloc %d bytes.\n", asize);
-       goto error;
-    }
-    
     /* get an atom to store the pointer to the chunk */
     lmem = (struct osi_linux_mem *)afs_atomlist_get(al_mem_pool);
     if (!lmem) {
@@ -294,51 +315,56 @@ void *osi_linux_alloc(unsigned int asize)
        printf("afs_osi_Alloc: lhash_enter failed\n");
        goto free_error;
     }
-    afs_linux_cur_allocs++;   /* no. of current allocations */
-    afs_linux_total_allocs++; /* total no. of allocations done so far */
+    afs_linux_cur_allocs++;    /* no. of current allocations */
+    afs_linux_total_allocs++;  /* total no. of allocations done so far */
     if ((afs_linux_cur_allocs % STAT_INTERVAL) == 0) {
        get_hash_stats();
     }
   error:
-    up(&afs_linux_alloc_sem);
+    mutex_unlock(&afs_linux_alloc_sem);
     return MEMADDR(new);
 
   free_error:
-    if (new)
-        linux_free(new);
+    if (new) {
+       linux_free(new);
+    }
     new = NULL;
     goto error;
 
-    
+
 }
 
 /* osi_linux_free() - free chunk of memory passed to us.
  */
-void osi_linux_free(void *addr)
+void
+osi_linux_free(void *addr)
 {
     struct osi_linux_mem lmem, *lmp;
 
-    down(&afs_linux_alloc_sem);
-    
+    mutex_lock(&afs_linux_alloc_sem);
+
     lmem.chunk = addr;
     /* remove this chunk from our hash table */
-    if ( lmp = (struct osi_linux_mem *)afs_lhash_remove(lh_mem_htab, hash_chunk(addr), &lmem)) {
-        linux_free(lmp->chunk); /* this contains the piggybacked type info*/
-        afs_atomlist_put(al_mem_pool, lmp); /* return osi_linux_mem struct to pool*/
-        afs_linux_cur_allocs--;
-    }
-    else   {
-        printf("osi_linux_free: failed to remove chunk from hashtable\n");
+    if ((lmp =
+        (struct osi_linux_mem *)afs_lhash_remove(lh_mem_htab,
+                                                 hash_chunk(addr), &lmem))) {
+       linux_free(lmp->chunk); /* this contains the piggybacked type info */
+       afs_atomlist_put(al_mem_pool, lmp);     /* return osi_linux_mem struct to pool */
+       afs_linux_cur_allocs--;
+    } else {
+       printf("osi_linux_free: failed to remove chunk from hashtable\n");
+       BUG();
     }
 
-    up(&afs_linux_alloc_sem);
+    mutex_unlock(&afs_linux_alloc_sem);
 }
 
 /* osi_linux_free_afs_memory() - free all chunks of memory allocated.
  */
-void osi_linux_free_afs_memory(void)
+void
+osi_linux_free_afs_memory(void)
 {
-    down(&afs_linux_alloc_sem);
+    mutex_lock(&afs_linux_alloc_sem);
 
     if (allocator_init) {
        /* iterate through all elements in the hash table and free both 
@@ -351,20 +377,21 @@ void osi_linux_free_afs_memory(void)
 
        /* free the hashlist. */
        afs_lhash_destroy(lh_mem_htab);
-       
+
        /* change the state so that the allocator is now uninitialized. */
        allocator_init = 0;
     }
-    up(&afs_linux_alloc_sem);    
+    mutex_unlock(&afs_linux_alloc_sem);
 }
 
 /* osi_linux_verify_alloced_memory(): verify all chunks of alloced memory in
  *          our hash table.
  */
-void osi_linux_verify_alloced_memory()
+void
+osi_linux_verify_alloced_memory()
 {
-    down(&afs_linux_alloc_sem);
-    
+    mutex_lock(&afs_linux_alloc_sem);
+
     /* count of times hash_verify was called. reset it to 0 before iteration */
     afs_linux_hash_verify_count = 0;
 
@@ -373,10 +400,42 @@ void osi_linux_verify_alloced_memory()
 
     if (afs_linux_hash_verify_count != afs_linux_cur_allocs) {
        /* hmm, some pieces of memory are missing. */
-       printf("osi_linux_verify_alloced_memory: %d chunks of memory are not accounted for during verify!\n", afs_linux_hash_verify_count - afs_linux_cur_allocs);
+       printf
+           ("osi_linux_verify_alloced_memory: %d chunks of memory are not accounted for during verify!\n",
+            afs_linux_hash_verify_count - afs_linux_cur_allocs);
     }
 
-    up(&afs_linux_alloc_sem);
+    mutex_unlock(&afs_linux_alloc_sem);
     return;
 }
 
+#ifdef AFS_PRIVATE_OSI_ALLOCSPACES
+
+void
+osi_FreeLargeSpace(void *p)
+{
+    kfree(p);
+}
+
+void
+osi_FreeSmallSpace(void *p)
+{
+    kfree(p);
+}
+
+void *
+osi_AllocLargeSpace(size_t size)
+{
+    if (size > AFS_LRALLOCSIZ)
+       osi_Panic("osi_AllocLargeSpace: size=%d\n", (int) size);
+    return kmalloc(AFS_LRALLOCSIZ, GFP_NOFS);
+}
+
+void *
+osi_AllocSmallSpace(size_t size)
+{
+    if (size > AFS_SMALLOCSIZ)
+       osi_Panic("osi_AllocSmallS: size=%d\n", (int)size);
+    return kmalloc(AFS_SMALLOCSIZ, GFP_NOFS);
+}
+#endif /* AFS_PRIVATE_OSI_ALLOCSPACES */