linux-dont-d-drop-in-revalidate-20010416
[openafs.git] / src / afs / LINUX / osi_alloc.c
1 /*
2  * Copyright 2000, International Business Machines Corporation and others.
3  * All Rights Reserved.
4  * 
5  * This software has been released under the terms of the IBM Public
6  * License.  For details, see the LICENSE file in the top-level source
7  * directory or online at http://www.openafs.org/dl/license10.html
8  */
9
10 /*
11  * osi_alloc.c - Linux memory allocation routines.
12  *
13  */
14 #include "../afs/param.h"
15 #include "../afs/sysincludes.h"
16 #include "../afs/afsincludes.h"
17 #include "../h/mm.h"
18
19 #include "../afs/afs_atomlist.h"
20 #include "../afs/afs_lhash.h"
21
22 #define MAX_KMALLOC_SIZE (131072-16) /* Max we can alloc in physmem */
23 #define MAX_BUCKET_LEN 30 /* max. no. of entries per buckets we expect to see */
24 #define STAT_INTERVAL 8192 /* we collect stats once every STAT_INTERVAL allocs*/
25
26 /* types of alloc */
27 #define KM_TYPE 1        /* kmalloc */
28 #define VM_TYPE 2        /* vmalloc */
29
30 struct osi_linux_mem {
31     void *chunk;
32 };
33
34 /* These assume 32-bit pointers */
35 #define MEMTYPE(A) (((unsigned long)A) & 0x3)
36 #define MEMADDR(A) (void *)((unsigned long)(A) & (~0x3))
37
38 /* globals */
39 afs_atomlist *al_mem_pool; /* pool of osi_linux_mem structures */
40 afs_lhash    *lh_mem_htab; /* mem hash table */
41 unsigned int allocator_init = 0; /* has the allocator been initialized? */
42 unsigned int afs_linux_cur_allocs = 0;
43 unsigned int afs_linux_total_allocs = 0;
44 unsigned int afs_linux_hash_verify_count = 0; /* used by hash_verify */
45 struct afs_lhash_stat afs_linux_lsb; /* hash table statistics */
46 unsigned int afs_linux_hash_bucket_dist[MAX_BUCKET_LEN]; /* bucket population distribution in our hash table */
47
48 #if defined(AFS_LINUX24_ENV)
49 #include "../h/vmalloc.h"
50 #else
51 /* externs : can we do this in a better way. Including vmalloc.h causes other
52  * problems.*/
53 extern void vfree(void * addr);
54 extern void *vmalloc(unsigned long size);
55 #endif
56
57 /* Allocator support functions (static) */
58
59 static int hash_equal(const void *a, const void *b) 
60 {
61     return ( MEMADDR(((struct osi_linux_mem *)a)->chunk) == 
62             MEMADDR(((struct osi_linux_mem *)b)->chunk) );
63
64 }
65
66 /* linux_alloc : Allocates memory from the linux kernel. It uses 
67  *               kmalloc if possible. Otherwise, we use vmalloc. 
68  * Input:
69  *  asize - size of memory required in bytes
70  * Return Values:
71  *  returns NULL if we failed to allocate memory.
72  *  or pointer to memory if we succeeded.
73  */
74 static void *linux_alloc(unsigned int asize)
75 {
76     void *new = NULL;
77     int has_afs_glock = ISAFS_GLOCK();
78
79     /* if global lock has been held save this info and unlock it. */
80     if (has_afs_glock)
81         AFS_GUNLOCK();
82
83     /*  if we can use kmalloc use it to allocate the required memory. */
84     if (asize <  MAX_KMALLOC_SIZE) {
85         new = (void *)(long)kmalloc(asize, GFP_KERNEL);
86         if (new) /* piggy back alloc type */
87             (unsigned long)new |= KM_TYPE;
88     }
89     if (!new) { /* otherwise use vmalloc  */
90         int max_wait = 10;
91         while (!(new = (void *)vmalloc(asize))) {
92             if (--max_wait <=0) {
93                 break;
94             }
95             schedule();
96         }
97         if (new) /* piggy back alloc type */
98             (unsigned long)new |= VM_TYPE;
99     }
100     if (new)
101         memset(MEMADDR(new), 0, asize);
102
103     /* if the global lock had been held, lock it again. */
104     if (has_afs_glock)
105         AFS_GLOCK();
106
107     return new;
108 }
109
110 static void linux_free(void *p)
111 {
112     
113     /* mask out the type information from the pointer and
114      *  use the appropriate free routine to free the chunk.
115      */
116     switch(MEMTYPE(p)) {
117       case KM_TYPE:
118         kfree(MEMADDR(p));
119         break;
120       case VM_TYPE:
121         vfree(MEMADDR(p));
122         break;
123       default:
124         printf("afs_osi_Free: Asked to free unknown type %d at 0x%x\n",
125                MEMTYPE(p), MEMADDR(p));
126         break;
127     }
128
129 }
130
131 /* hash_chunk() receives a pointer to a chunk and hashes it to produce a
132  *            key that the hashtable can use. The key is obtained by 
133  *            right shifting out the 2 LSBs and then multiplying the
134  *            result by a constant no. and dividing it with a large prime.
135  */
136 #define HASH_CONST   32786
137 #define HASH_PRIME   79367
138 static unsigned hash_chunk(void *p)
139 {
140     unsigned int key;
141
142     key = (unsigned int)(long)p >> 2;
143     key = (key * HASH_CONST)%HASH_PRIME;
144
145     return key;
146 }
147
148 /* hash_free() : Invoked by osi_linux_free_afs_memory(), thru 
149  *          afs_lhash_iter(), this function is called by the lhash
150  *          module for every entry in the hash table. hash_free
151  *          frees the memory associated with the entry as well
152  *          as returning the osi_linux_mem struct to its pool.
153  */
154 static void
155 hash_free(size_t index, unsigned key, void *data)
156 {
157     linux_free(((struct osi_linux_mem *)data)->chunk);
158     afs_atomlist_put(al_mem_pool, data);
159 }
160
161 /* hash_verify() is invoked by osi_linux_verify_alloced_memory() thru
162  *   afs_lhash_iter() and is called by the lhash module for every element
163  *   in the hash table. 
164  *  hash_verify() verifies (within limits) that the memory passed to it is
165  *  valid.
166  */
167 static void
168 hash_verify(size_t index, unsigned key, void *data)
169 {
170     struct osi_linux_mem *lmp = (struct osi_linux_mem *)data;
171     int memtype;
172
173     memtype = MEMTYPE(lmp->chunk);
174     if ((memtype == KM_TYPE) && (AFS_LINUX_MAP_NR(lmp->chunk) > max_mapnr)) {
175         printf("osi_linux_verify_alloced_memory: address 0x%x outside range, index=%d, key=%d\n", lmp->chunk, index, key);
176     }
177     
178     if (memtype != KM_TYPE && memtype != VM_TYPE) {
179         printf("osi_linux_verify_alloced_memory: unknown type %d at 0x%x, index=%d\n",    memtype, lmp->chunk, index);
180     }
181     afs_linux_hash_verify_count++;
182 }
183
184
185 /* local_free() : wrapper for vfree(), to deal with incompatible protoypes */
186 static void local_free(void *p, size_t n)
187 {
188     vfree(p);
189 }
190
191 /* linux_alloc_init(): Initializes the kernel memory allocator. As part
192  *    of this process, it also initializes a pool of osi_linux_mem
193  *    structures as well as the hash table itself.
194  *  Return values:
195  *    0 - failure
196  *    1 - success
197  */
198 static int linux_alloc_init()
199 {
200     /* initiate our pool of osi_linux_mem structs */
201     al_mem_pool = afs_atomlist_create(sizeof(struct osi_linux_mem),
202                                       sizeof(long)*1024, (void *)vmalloc, 
203                                       local_free);
204     if (!al_mem_pool) {
205         printf("afs_osi_Alloc: Error in initialization(atomlist_create)\n");
206         return 0;
207     }
208
209     /* initialize the hash table to hold references to alloc'ed chunks */
210     lh_mem_htab = afs_lhash_create(hash_equal, (void *)vmalloc, local_free);
211     if (!lh_mem_htab) {
212         printf("afs_osi_Alloc: Error in initialization(lhash_create)\n");
213         return 0;
214     }
215     
216     return 1;
217     
218 }
219
220 /* hash_bucket_stat() : Counts the no. of elements in each bucket and
221  *   stores results in our bucket stats vector.
222  */
223 static unsigned int cur_bucket, cur_bucket_len;
224 static void hash_bucket_stat(size_t index, unsigned key, void *data)
225 {
226     if (index == cur_bucket) { 
227         /* while still on the same bucket, inc len & return */
228         cur_bucket_len++;
229         return;
230     }
231     else { /* if we're on the next bucket, store the distribution */
232         if (cur_bucket_len < MAX_BUCKET_LEN)
233             afs_linux_hash_bucket_dist[cur_bucket_len]++;
234         else
235             printf("afs_get_hash_stats: Warning! exceeded max bucket len %d\n", cur_bucket_len);
236         cur_bucket = index;
237         cur_bucket_len = 1;
238     }
239 }
240 /* get_hash_stats() : get hash table statistics */
241 static void get_hash_stats()
242 {
243     int i;
244
245     afs_lhash_stat(lh_mem_htab, &afs_linux_lsb);
246
247     /* clear out the bucket stat vector */
248     for(i=0;i<MAX_BUCKET_LEN;i++, afs_linux_hash_bucket_dist[i]=0);
249     cur_bucket = cur_bucket_len = 00;
250
251     /* populate the bucket stat vector */
252     afs_lhash_iter(lh_mem_htab, hash_bucket_stat);
253 }
254
255 /************** Linux memory allocator interface functions **********/
256
257 #if defined(AFS_LINUX24_ENV)
258 DECLARE_MUTEX(afs_linux_alloc_sem);
259 #else
260 struct semaphore afs_linux_alloc_sem = MUTEX;
261 #endif
262
263 void *osi_linux_alloc(unsigned int asize)
264 {
265     void *new = NULL;
266     struct osi_linux_mem *lmem;
267
268     down(&afs_linux_alloc_sem);
269
270     if (allocator_init == 0) { /* allocator hasn't been initialized yet */
271         if (linux_alloc_init() == 0) {
272             goto error;
273          }
274         allocator_init = 1; /* initialization complete */
275     }
276
277     new = linux_alloc(asize); /* get a chunk of memory of size asize */
278     if (!new) {
279         printf("afs_osi_Alloc: Can't vmalloc %d bytes.\n", asize);
280         goto error;
281     }
282     
283     /* get an atom to store the pointer to the chunk */
284     lmem = (struct osi_linux_mem *)afs_atomlist_get(al_mem_pool);
285     if (!lmem) {
286         printf("afs_osi_Alloc: atomlist_get() failed.");
287         goto free_error;
288     }
289     /* store the chunk reference */
290     lmem->chunk = new;
291
292     /* hash in the chunk */
293     if (afs_lhash_enter(lh_mem_htab, hash_chunk(new), lmem) != 0) {
294         printf("afs_osi_Alloc: lhash_enter failed\n");
295         goto free_error;
296     }
297     afs_linux_cur_allocs++;   /* no. of current allocations */
298     afs_linux_total_allocs++; /* total no. of allocations done so far */
299     if ((afs_linux_cur_allocs % STAT_INTERVAL) == 0) {
300         get_hash_stats();
301     }
302   error:
303     up(&afs_linux_alloc_sem);
304     return MEMADDR(new);
305
306   free_error:
307     if (new)
308         linux_free(new);
309     new = NULL;
310     goto error;
311
312     
313 }
314
315 /* osi_linux_free() - free chunk of memory passed to us.
316  */
317 void osi_linux_free(void *addr)
318 {
319     struct osi_linux_mem lmem, *lmp;
320
321     down(&afs_linux_alloc_sem);
322     
323     lmem.chunk = addr;
324     /* remove this chunk from our hash table */
325     if ( lmp = (struct osi_linux_mem *)afs_lhash_remove(lh_mem_htab, hash_chunk(addr), &lmem)) {
326         linux_free(lmp->chunk); /* this contains the piggybacked type info*/
327         afs_atomlist_put(al_mem_pool, lmp); /* return osi_linux_mem struct to pool*/
328         afs_linux_cur_allocs--;
329     }
330     else   {
331         printf("osi_linux_free: failed to remove chunk from hashtable\n");
332     }
333
334     up(&afs_linux_alloc_sem);
335 }
336
337 /* osi_linux_free_afs_memory() - free all chunks of memory allocated.
338  */
339 void osi_linux_free_afs_memory(void)
340 {
341     down(&afs_linux_alloc_sem);
342
343     if (allocator_init) {
344         /* iterate through all elements in the hash table and free both 
345          * the chunk and the atom associated with it.
346          */
347         afs_lhash_iter(lh_mem_htab, hash_free);
348
349         /*  free the atomlist. */
350         afs_atomlist_destroy(al_mem_pool);
351
352         /* free the hashlist. */
353         afs_lhash_destroy(lh_mem_htab);
354         
355         /* change the state so that the allocator is now uninitialized. */
356         allocator_init = 0;
357     }
358     up(&afs_linux_alloc_sem);    
359 }
360
361 /* osi_linux_verify_alloced_memory(): verify all chunks of alloced memory in
362  *          our hash table.
363  */
364 void osi_linux_verify_alloced_memory()
365 {
366     down(&afs_linux_alloc_sem);
367     
368     /* count of times hash_verify was called. reset it to 0 before iteration */
369     afs_linux_hash_verify_count = 0;
370
371     /* iterate thru elements in the hash table */
372     afs_lhash_iter(lh_mem_htab, hash_verify);
373
374     if (afs_linux_hash_verify_count != afs_linux_cur_allocs) {
375         /* hmm, some pieces of memory are missing. */
376         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);
377     }
378
379     up(&afs_linux_alloc_sem);
380     return;
381 }
382