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