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