f9847ca5d5dc415fd39d65cc0112f323636eb73a
[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
28 /* types of alloc */
29 #define KM_TYPE 1               /* kmalloc */
30 #define VM_TYPE 2               /* vmalloc */
31
32 struct osi_linux_mem {
33     void *chunk;
34 };
35
36 /* These assume 32-bit pointers */
37 #define MEMTYPE(A) (((unsigned long)A) & 0x3)
38 #define MEMADDR(A) (void *)((unsigned long)(A) & (~0x3))
39
40 /* globals */
41 afs_atomlist *al_mem_pool;      /* pool of osi_linux_mem structures */
42 afs_lhash *lh_mem_htab;         /* mem hash table */
43 unsigned int allocator_init = 0;        /* has the allocator been initialized? */
44 unsigned int afs_linux_cur_allocs = 0;
45 unsigned int afs_linux_total_allocs = 0;
46 unsigned int afs_linux_hash_verify_count = 0;   /* used by hash_verify */
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
60 hash_equal(const void *a, const void *b)
61 {
62     return (MEMADDR(((struct osi_linux_mem *)a)->chunk) ==
63             MEMADDR(((struct osi_linux_mem *)b)->chunk));
64
65 }
66
67 /* linux_alloc : Allocates memory from the linux kernel. It uses 
68  *               kmalloc if possible. Otherwise, we use vmalloc. 
69  * Input:
70  *  asize - size of memory required in bytes
71  * Return Values:
72  *  returns NULL if we failed to allocate memory.
73  *  or pointer to memory if we succeeded.
74  */
75 static void *
76 linux_alloc(unsigned int asize, int drop_glock)
77 {
78     void *new = NULL;
79     int max_retry = 10;
80     int haveGlock = ISAFS_GLOCK();
81
82     /*  if we can use kmalloc use it to allocate the required memory. */
83     while (!new && max_retry) {
84         if (asize <= MAX_KMALLOC_SIZE) {
85             new = (void *)(unsigned long)kmalloc(asize,
86 #ifdef GFP_NOFS
87                                                  GFP_NOFS
88 #else
89                                                  GFP_KERNEL
90 #endif
91                 );
92             if (new)            /* piggy back alloc type */
93                 new = (void *)(KM_TYPE | (unsigned long)new);
94         } else {
95             osi_Assert(drop_glock || !haveGlock);
96             if (drop_glock && haveGlock)
97                 AFS_GUNLOCK();
98             new = (void *)vmalloc(asize);
99             if (drop_glock && haveGlock)
100                 AFS_GLOCK();
101             if (new)            /* piggy back alloc type */
102                 new = (void *)(VM_TYPE | (unsigned long)new);
103         }
104
105         if (!new) {
106 #ifdef set_current_state
107             set_current_state(TASK_INTERRUPTIBLE);
108 #else
109             current->state = TASK_INTERRUPTIBLE;
110 #endif
111             if (drop_glock && haveGlock)
112                 AFS_GUNLOCK();
113             schedule_timeout(HZ);
114             if (drop_glock && haveGlock)
115                 AFS_GLOCK();
116 #ifdef set_current_state
117             set_current_state(TASK_RUNNING);
118 #else
119             current->state = TASK_RUNNING;
120 #endif
121             --max_retry;
122         }
123     }
124     if (new)
125         memset(MEMADDR(new), 0, asize);
126
127     return new;
128 }
129
130 static void
131 linux_free(void *p)
132 {
133
134     /* mask out the type information from the pointer and
135      *  use the appropriate free routine to free the chunk.
136      */
137     switch (MEMTYPE(p)) {
138     case KM_TYPE:
139         kfree(MEMADDR(p));
140         break;
141     case VM_TYPE:
142         vfree(MEMADDR(p));
143         break;
144     default:
145         printf("afs_osi_Free: Asked to free unknown type %d at 0x%lx\n",
146                (int)MEMTYPE(p), (unsigned long)MEMADDR(p));
147         break;
148     }
149
150 }
151
152 /* hash_chunk() receives a pointer to a chunk and hashes it to produce a
153  *            key that the hashtable can use. The key is obtained by 
154  *            right shifting out the 2 LSBs and then multiplying the
155  *            result by a constant no. and dividing it with a large prime.
156  */
157 #define HASH_CONST   32786
158 #define HASH_PRIME   79367
159 static unsigned
160 hash_chunk(void *p)
161 {
162     unsigned int key;
163
164     key = (unsigned int)(long)p >> 2;
165     key = (key * HASH_CONST) % HASH_PRIME;
166
167     return key;
168 }
169
170 /* hash_free() : Invoked by osi_linux_free_afs_memory(), thru 
171  *          afs_lhash_iter(), this function is called by the lhash
172  *          module for every entry in the hash table. hash_free
173  *          frees the memory associated with the entry as well
174  *          as returning the osi_linux_mem struct to its pool.
175  */
176 static void
177 hash_free(size_t index, unsigned key, void *data)
178 {
179     linux_free(((struct osi_linux_mem *)data)->chunk);
180     afs_atomlist_put(al_mem_pool, data);
181 }
182
183 /* hash_verify() is invoked by osi_linux_verify_alloced_memory() thru
184  *   afs_lhash_iter() and is called by the lhash module for every element
185  *   in the hash table. 
186  *  hash_verify() verifies (within limits) that the memory passed to it is
187  *  valid.
188  */
189 static void
190 hash_verify(size_t index, unsigned key, void *data)
191 {
192     struct osi_linux_mem *lmp = (struct osi_linux_mem *)data;
193     int memtype;
194
195     memtype = MEMTYPE(lmp->chunk);
196     if (memtype != KM_TYPE && memtype != VM_TYPE) {
197         printf
198             ("osi_linux_verify_alloced_memory: unknown type %d at 0x%lx, index=%lu\n",
199              (int)memtype, (unsigned long)lmp->chunk, (unsigned long)index);
200     }
201     afs_linux_hash_verify_count++;
202 }
203
204
205 /* local_free() : wrapper for vfree(), to deal with incompatible protoypes */
206 static void
207 local_free(void *p, size_t n)
208 {
209     vfree(p);
210 }
211
212 /* linux_alloc_init(): Initializes the kernel memory allocator. As part
213  *    of this process, it also initializes a pool of osi_linux_mem
214  *    structures as well as the hash table itself.
215  *  Return values:
216  *    0 - failure
217  *    1 - success
218  */
219 static int
220 linux_alloc_init(void)
221 {
222     /* initiate our pool of osi_linux_mem structs */
223     al_mem_pool =
224         afs_atomlist_create(sizeof(struct osi_linux_mem), sizeof(long) * 1024,
225                             (void *)vmalloc, 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 /************** Linux memory allocator interface functions **********/
243
244 #if defined(AFS_LINUX24_ENV)
245 DECLARE_MUTEX(afs_linux_alloc_sem);
246 #else
247 struct semaphore afs_linux_alloc_sem = MUTEX;
248 #endif
249
250 void *
251 osi_linux_alloc(unsigned int asize, int drop_glock)
252 {
253     void *new = NULL;
254     struct osi_linux_mem *lmem;
255
256     new = linux_alloc(asize, drop_glock);       /* get a chunk of memory of size asize */
257
258     if (!new) {
259         printf("afs_osi_Alloc: Can't vmalloc %d bytes.\n", asize);
260         return new;
261     }
262
263     down(&afs_linux_alloc_sem);
264
265     /* allocator hasn't been initialized yet */
266     if (allocator_init == 0) {
267         if (linux_alloc_init() == 0) {
268             goto error;
269         }
270         allocator_init = 1;     /* initialization complete */
271     }
272
273     /* get an atom to store the pointer to the chunk */
274     lmem = (struct osi_linux_mem *)afs_atomlist_get(al_mem_pool);
275     if (!lmem) {
276         printf("afs_osi_Alloc: atomlist_get() failed.");
277         goto free_error;
278     }
279     /* store the chunk reference */
280     lmem->chunk = new;
281
282     /* hash in the chunk */
283     if (afs_lhash_enter(lh_mem_htab, hash_chunk(new), lmem) != 0) {
284         printf("afs_osi_Alloc: lhash_enter failed\n");
285         goto free_error;
286     }
287     afs_linux_cur_allocs++;     /* no. of current allocations */
288     afs_linux_total_allocs++;   /* total no. of allocations done so far */
289   error:
290     up(&afs_linux_alloc_sem);
291     return MEMADDR(new);
292
293   free_error:
294     if (new) {
295         linux_free(new);
296     }
297     new = NULL;
298     goto error;
299
300
301 }
302
303 /* osi_linux_free() - free chunk of memory passed to us.
304  */
305 void
306 osi_linux_free(void *addr)
307 {
308     struct osi_linux_mem lmem, *lmp;
309
310     down(&afs_linux_alloc_sem);
311
312     lmem.chunk = addr;
313     /* remove this chunk from our hash table */
314     if ((lmp =
315          (struct osi_linux_mem *)afs_lhash_remove(lh_mem_htab,
316                                                   hash_chunk(addr), &lmem))) {
317         linux_free(lmp->chunk); /* this contains the piggybacked type info */
318         afs_atomlist_put(al_mem_pool, lmp);     /* return osi_linux_mem struct to pool */
319         afs_linux_cur_allocs--;
320     } else {
321         printf("osi_linux_free: failed to remove chunk from hashtable\n");
322         BUG();
323     }
324
325     up(&afs_linux_alloc_sem);
326 }
327
328 /* osi_linux_free_afs_memory() - free all chunks of memory allocated.
329  */
330 void
331 osi_linux_free_afs_memory(void)
332 {
333     down(&afs_linux_alloc_sem);
334
335     if (allocator_init) {
336         /* iterate through all elements in the hash table and free both 
337          * the chunk and the atom associated with it.
338          */
339         afs_lhash_iter(lh_mem_htab, hash_free);
340
341         /*  free the atomlist. */
342         afs_atomlist_destroy(al_mem_pool);
343
344         /* free the hashlist. */
345         afs_lhash_destroy(lh_mem_htab);
346
347         /* change the state so that the allocator is now uninitialized. */
348         allocator_init = 0;
349     }
350     up(&afs_linux_alloc_sem);
351 }
352
353 /* osi_linux_verify_alloced_memory(): verify all chunks of alloced memory in
354  *          our hash table.
355  */
356 void
357 osi_linux_verify_alloced_memory()
358 {
359     down(&afs_linux_alloc_sem);
360
361     /* count of times hash_verify was called. reset it to 0 before iteration */
362     afs_linux_hash_verify_count = 0;
363
364     /* iterate thru elements in the hash table */
365     afs_lhash_iter(lh_mem_htab, hash_verify);
366
367     if (afs_linux_hash_verify_count != afs_linux_cur_allocs) {
368         /* hmm, some pieces of memory are missing. */
369         printf
370             ("osi_linux_verify_alloced_memory: %d chunks of memory are not accounted for during verify!\n",
371              afs_linux_hash_verify_count - afs_linux_cur_allocs);
372     }
373
374     up(&afs_linux_alloc_sem);
375     return;
376 }