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