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