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