1/*
2  This software is available to you under a choice of one of two
3  licenses.  You may choose to be licensed under the terms of the GNU
4  General Public License (GPL) Version 2, available at
5  <http://www.fsf.org/copyleft/gpl.html>, or the OpenIB.org BSD
6  license, available in the LICENSE.TXT file accompanying this
7  software.  These details are also available at
8  <http://openib.org/license.html>.
9
10  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
11  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
12  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
13  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
14  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
15  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
16  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
17  SOFTWARE.
18
19  Copyright (c) 2004 Mellanox Technologies Ltd.  All rights reserved.
20*/
21
22#define C_MEMTRACK_C
23
24#ifdef kmalloc
25        #undef kmalloc
26#endif
27#ifdef kfree
28        #undef kfree
29#endif
30#ifdef vmalloc
31        #undef vmalloc
32#endif
33#ifdef vfree
34        #undef vfree
35#endif
36#ifdef kmem_cache_alloc
37        #undef kmem_cache_alloc
38#endif
39#ifdef kmem_cache_free
40        #undef kmem_cache_free
41#endif
42
43#include <linux/module.h>
44#include <linux/kernel.h>
45#include <linux/slab.h>
46#include <linux/interrupt.h>
47#include <linux/vmalloc.h>
48#include <linux/version.h>
49#include <asm/uaccess.h>
50#include <linux/proc_fs.h>
51#include <memtrack.h>
52
53#include <linux/moduleparam.h>
54
55
56MODULE_AUTHOR("Mellanox Technologies LTD.");
57MODULE_DESCRIPTION("Memory allocations tracking");
58MODULE_LICENSE("GPL");
59
60#define MEMTRACK_HASH_SZ ((1<<15)-19)   /* prime: http://www.utm.edu/research/primes/lists/2small/0bit.html */
61#define MAX_FILENAME_LEN 31
62
63#define memtrack_spin_lock(spl, flags)     spin_lock_irqsave(spl, flags)
64#define memtrack_spin_unlock(spl, flags)   spin_unlock_irqrestore(spl, flags)
65
66/* if a bit is set then the corresponding allocation is tracked.
67   bit0 corresponds to MEMTRACK_KMALLOC, bit1 corresponds to MEMTRACK_VMALLOC etc. */
68static unsigned long track_mask = -1;   /* effectively everything */
69module_param(track_mask, ulong, 0444);
70MODULE_PARM_DESC(track_mask, "bitmask definenig what is tracked");
71
72/* if a bit is set then the corresponding allocation is strictly tracked.
73   That is, before inserting the whole range is checked to not overlap any
74   of the allocations already in the database */
75static unsigned long strict_track_mask = 0;     /* no strict tracking */
76module_param(strict_track_mask, ulong, 0444);
77MODULE_PARM_DESC(strict_track_mask, "bitmask which allocation requires strict tracking");
78
79typedef struct memtrack_meminfo_st {
80        unsigned long addr;
81        unsigned long size;
82        unsigned long line_num;
83        struct memtrack_meminfo_st *next;
84        struct list_head list;  /* used to link all items from a certain type together */
85        char filename[MAX_FILENAME_LEN + 1];    /* putting the char array last is better for struct. packing */
86} memtrack_meminfo_t;
87
88static struct kmem_cache *meminfo_cache;
89
90typedef struct {
91        memtrack_meminfo_t *mem_hash[MEMTRACK_HASH_SZ];
92        spinlock_t hash_lock;
93        unsigned long count; /* size of memory tracked (*malloc) or number of objects tracked */
94        struct list_head tracked_objs_head;     /* head of list of all objects */
95        int strict_track;       /* if 1 then for each object inserted check if it overlaps any of the objects already in the list */
96} tracked_obj_desc_t;
97
98static tracked_obj_desc_t *tracked_objs_arr[MEMTRACK_NUM_OF_MEMTYPES];
99
100static const char *rsc_names[MEMTRACK_NUM_OF_MEMTYPES] = {
101        "kmalloc",
102        "vmalloc",
103        "kmem_cache_alloc"
104};
105
106
107static const char *rsc_free_names[MEMTRACK_NUM_OF_MEMTYPES] = {
108        "kfree",
109        "vfree",
110        "kmem_cache_free"
111};
112
113
114static inline const char *memtype_alloc_str(memtrack_memtype_t memtype)
115{
116        switch (memtype) {
117                case MEMTRACK_KMALLOC:
118                case MEMTRACK_VMALLOC:
119                case MEMTRACK_KMEM_OBJ:
120                        return rsc_names[memtype];
121                default:
122                        return "(Unknown allocation type)";
123        }
124}
125
126static inline const char *memtype_free_str(memtrack_memtype_t memtype)
127{
128        switch (memtype) {
129                case MEMTRACK_KMALLOC:
130                case MEMTRACK_VMALLOC:
131                case MEMTRACK_KMEM_OBJ:
132                        return rsc_free_names[memtype];
133                default:
134                        return "(Unknown allocation type)";
135        }
136}
137
138/*
139 *  overlap_a_b
140 */
141static int overlap_a_b(unsigned long a_start, unsigned long a_end,
142                       unsigned long b_start, unsigned long b_end)
143{
144        if ((b_start > a_end) || (a_start > b_end)) {
145                return 0;
146        }
147        return 1;
148}
149
150/*
151 *  check_overlap
152 */
153static void check_overlap(memtrack_memtype_t memtype,
154                          memtrack_meminfo_t * mem_info_p,
155                          tracked_obj_desc_t * obj_desc_p)
156{
157        struct list_head *pos, *next;
158        memtrack_meminfo_t *cur;
159        unsigned long start_a, end_a, start_b, end_b;
160
161        list_for_each_safe(pos, next, &obj_desc_p->tracked_objs_head) {
162                cur = list_entry(pos, memtrack_meminfo_t, list);
163
164                start_a = mem_info_p->addr;
165                end_a = mem_info_p->addr + mem_info_p->size - 1;
166                start_b = cur->addr;
167                end_b = cur->addr + cur->size - 1;
168
169                if (overlap_a_b(start_a, end_a, start_b, end_b)) {
170                        printk
171                            ("%s overlaps! new_start=0x%lx, new_end=0x%lx, item_start=0x%lx, item_end=0x%lx\n",
172                             memtype_alloc_str(memtype), mem_info_p->addr,
173                             mem_info_p->addr + mem_info_p->size - 1, cur->addr,
174                             cur->addr + cur->size - 1);
175                }
176        }
177}
178
179/* Invoke on memory allocation */
180void memtrack_alloc(memtrack_memtype_t memtype, unsigned long addr,
181                    unsigned long size, const char *filename,
182                    const unsigned long line_num, int alloc_flags)
183{
184        unsigned long hash_val;
185        memtrack_meminfo_t *cur_mem_info_p, *new_mem_info_p;
186        tracked_obj_desc_t *obj_desc_p;
187        unsigned long flags;
188
189        if (memtype >= MEMTRACK_NUM_OF_MEMTYPES) {
190                printk("%s: Invalid memory type (%d)\n", __func__, memtype);
191                return;
192        }
193
194        if (!tracked_objs_arr[memtype]) {
195                /* object is not tracked */
196                return;
197        }
198        obj_desc_p = tracked_objs_arr[memtype];
199
200        hash_val = addr % MEMTRACK_HASH_SZ;
201
202        new_mem_info_p = (memtrack_meminfo_t *)
203            kmem_cache_alloc(meminfo_cache, alloc_flags);
204        if (new_mem_info_p == NULL) {
205                printk
206                    ("%s: Failed allocating kmem_cache item for new mem_info. "
207                     "Lost tracking on allocation at %s:%lu...\n", __func__,
208                     filename, line_num);
209                return;
210        }
211        /* save allocation properties */
212        new_mem_info_p->addr = addr;
213        new_mem_info_p->size = size;
214        new_mem_info_p->line_num = line_num;
215        /* Make sure that we will print out the path tail if the given filename is longer
216         * than MAX_FILENAME_LEN. (otherwise, we will not see the name of the actual file
217         * in the printout -- only the path head!
218         */
219        if (strlen(filename) > MAX_FILENAME_LEN) {
220          strncpy(new_mem_info_p->filename, filename + strlen(filename) - MAX_FILENAME_LEN, MAX_FILENAME_LEN);
221        } else {
222          strncpy(new_mem_info_p->filename, filename, MAX_FILENAME_LEN);
223        }
224        new_mem_info_p->filename[MAX_FILENAME_LEN] = 0; /* NULL terminate anyway */
225
226        memtrack_spin_lock(&obj_desc_p->hash_lock, flags);
227        /* make sure given memory location is not already allocated */
228        cur_mem_info_p = obj_desc_p->mem_hash[hash_val];
229        while (cur_mem_info_p != NULL) {
230                if (cur_mem_info_p->addr == addr) {
231                        /* Found given address in the database */
232                        printk
233                            ("mtl rsc inconsistency: %s: %s::%lu: %s @ addr=0x%lX which is already known from %s:%lu\n",
234                             __func__, filename, line_num,
235                             memtype_alloc_str(memtype), addr,
236                             cur_mem_info_p->filename,
237                             cur_mem_info_p->line_num);
238                        memtrack_spin_unlock(&obj_desc_p->hash_lock, flags);
239                        kmem_cache_free(meminfo_cache, new_mem_info_p);
240                        return;
241                }
242                cur_mem_info_p = cur_mem_info_p->next;
243        }
244        /* not found - we can put in the hash bucket */
245        /* link as first */
246        new_mem_info_p->next = obj_desc_p->mem_hash[hash_val];
247        obj_desc_p->mem_hash[hash_val] = new_mem_info_p;
248        if (obj_desc_p->strict_track) {
249                check_overlap(memtype, new_mem_info_p, obj_desc_p);
250        }
251        obj_desc_p->count += size;
252        list_add(&new_mem_info_p->list, &obj_desc_p->tracked_objs_head);
253
254        memtrack_spin_unlock(&obj_desc_p->hash_lock, flags);
255        return;
256}
257
258/* Invoke on memory free */
259void memtrack_free(memtrack_memtype_t memtype, unsigned long addr,
260                   const char *filename, const unsigned long line_num)
261{
262        unsigned long hash_val;
263        memtrack_meminfo_t *cur_mem_info_p, *prev_mem_info_p;
264        tracked_obj_desc_t *obj_desc_p;
265        unsigned long flags;
266
267        if (memtype >= MEMTRACK_NUM_OF_MEMTYPES) {
268                printk("%s: Invalid memory type (%d)\n", __func__, memtype);
269                return;
270        }
271
272        if (!tracked_objs_arr[memtype]) {
273                /* object is not tracked */
274                return;
275        }
276        obj_desc_p = tracked_objs_arr[memtype];
277
278        hash_val = addr % MEMTRACK_HASH_SZ;
279
280        memtrack_spin_lock(&obj_desc_p->hash_lock, flags);
281        /* find  mem_info of given memory location */
282        prev_mem_info_p = NULL;
283        cur_mem_info_p = obj_desc_p->mem_hash[hash_val];
284        while (cur_mem_info_p != NULL) {
285                if (cur_mem_info_p->addr == addr) {
286                        /* Found given address in the database - remove from the bucket/list */
287                        if (prev_mem_info_p == NULL) {
288                                obj_desc_p->mem_hash[hash_val] = cur_mem_info_p->next;  /* removing first */
289                        } else {
290                                prev_mem_info_p->next = cur_mem_info_p->next;   /* "crossover" */
291                        }
292                        list_del(&cur_mem_info_p->list);
293
294                        obj_desc_p->count -= cur_mem_info_p->size;
295                        memtrack_spin_unlock(&obj_desc_p->hash_lock, flags);
296                        kmem_cache_free(meminfo_cache, cur_mem_info_p);
297                        return;
298                }
299                prev_mem_info_p = cur_mem_info_p;
300                cur_mem_info_p = cur_mem_info_p->next;
301        }
302
303        /* not found */
304        printk
305            ("mtl rsc inconsistency: %s: %s::%lu: %s for unknown address=0x%lX\n",
306             __func__, filename, line_num, memtype_free_str(memtype), addr);
307        memtrack_spin_unlock(&obj_desc_p->hash_lock, flags);
308        return;
309}
310
311/* Report current allocations status (for all memory types) */
312static void memtrack_report(void)
313{
314        memtrack_memtype_t memtype;
315        unsigned long cur_bucket;
316        memtrack_meminfo_t *cur_mem_info_p;
317        int serial = 1;
318        tracked_obj_desc_t *obj_desc_p;
319        unsigned long flags;
320
321        printk("%s: Currently known allocations:\n", __func__);
322        for (memtype = 0; memtype < MEMTRACK_NUM_OF_MEMTYPES; memtype++) {
323                if (tracked_objs_arr[memtype]) {
324                        printk("%d) %s:\n", serial, memtype_alloc_str(memtype));
325                        obj_desc_p = tracked_objs_arr[memtype];
326                        /* Scan all buckets to find existing allocations */
327                        /* TBD: this may be optimized by holding a linked list of all hash items */
328                        for (cur_bucket = 0; cur_bucket < MEMTRACK_HASH_SZ;
329                             cur_bucket++) {
330                                memtrack_spin_lock(&obj_desc_p->hash_lock, flags);      /* protect per bucket/list */
331                                cur_mem_info_p =
332                                    obj_desc_p->mem_hash[cur_bucket];
333                                while (cur_mem_info_p != NULL) {        /* scan bucket */
334                                        printk("%s::%lu: %s(%lu)==%lX\n",
335                                               cur_mem_info_p->filename,
336                                               cur_mem_info_p->line_num,
337                                               memtype_alloc_str(memtype),
338                                               cur_mem_info_p->size,
339                                               cur_mem_info_p->addr);
340                                        cur_mem_info_p = cur_mem_info_p->next;
341                                }       /* while cur_mem_info_p */
342                                memtrack_spin_unlock(&obj_desc_p->hash_lock, flags);
343                        }       /* for cur_bucket */
344                        serial++;
345                }
346        }                       /* for memtype */
347}
348
349
350
351static struct proc_dir_entry *memtrack_tree;
352
353static memtrack_memtype_t get_rsc_by_name(const char *name)
354{
355        memtrack_memtype_t i;
356
357        for (i=0; i<MEMTRACK_NUM_OF_MEMTYPES; ++i) {
358                if (strcmp(name, rsc_names[i]) == 0) {
359                        return i;
360                }
361        }
362
363        return i;
364}
365
366
367static ssize_t memtrack_read(struct file *filp,
368                                                 char __user *buf,
369                                                         size_t size,
370                                                         loff_t *offset)
371{
372        unsigned long cur, flags;
373        loff_t pos = *offset;
374        static char kbuf[20];
375        static int file_len;
376        int _read, to_ret, left;
377        const char *fname;
378        memtrack_memtype_t memtype;
379
380        if (pos < 0)
381                return -EINVAL;
382
383        fname= filp->f_dentry->d_name.name;
384
385        memtype= get_rsc_by_name(fname);
386        if (memtype >= MEMTRACK_NUM_OF_MEMTYPES) {
387                printk("invalid file name\n");
388                return -EINVAL;
389        }
390
391        if ( pos == 0 ) {
392                memtrack_spin_lock(&tracked_objs_arr[memtype]->hash_lock, flags);
393                cur= tracked_objs_arr[memtype]->count;
394                memtrack_spin_unlock(&tracked_objs_arr[memtype]->hash_lock, flags);
395                _read = sprintf(kbuf, "%lu\n", cur);
396                if ( _read < 0 ) {
397                        return _read;
398                }
399                else {
400                        file_len = _read;
401                }
402        }
403
404        left = file_len - pos;
405        to_ret = (left < size) ? left : size;
406        if ( copy_to_user(buf, kbuf+pos, to_ret) ) {
407                return -EFAULT;
408        }
409        else {
410                *offset = pos + to_ret;
411                return to_ret;
412        }
413}
414
415static struct file_operations memtrack_proc_fops = {
416        .read = memtrack_read,
417};
418
419static const char *memtrack_proc_entry_name = "mt_memtrack";
420
421static int create_procfs_tree(void)
422{
423        struct proc_dir_entry *dir_ent;
424        struct proc_dir_entry *proc_ent;
425        int i, j;
426        unsigned long bit_mask;
427
428        dir_ent = proc_mkdir(memtrack_proc_entry_name, NULL);
429        if ( !dir_ent ) {
430                return -1;
431        }
432
433        memtrack_tree = dir_ent;
434
435        for (i=0, bit_mask=1; i<MEMTRACK_NUM_OF_MEMTYPES; ++i, bit_mask<<=1) {
436                if (bit_mask & track_mask) {
437                        proc_ent = create_proc_entry(rsc_names[i], S_IRUGO, memtrack_tree);
438                        if ( !proc_ent )
439                                goto undo_create_root;
440
441			proc_ent->proc_fops = &memtrack_proc_fops;
442                }
443        }
444
445        goto exit_ok;
446
447undo_create_root:
448        for (j=0, bit_mask=1; j<i; ++j, bit_mask<<=1) {
449                if (bit_mask & track_mask) {
450                        remove_proc_entry(rsc_names[j], memtrack_tree);
451                }
452        }
453        remove_proc_entry(memtrack_proc_entry_name, NULL);
454        return -1;
455
456exit_ok:
457        return 0;
458}
459
460
461static void destroy_procfs_tree(void)
462{
463        int i;
464        unsigned long bit_mask;
465
466        for (i=0, bit_mask=1; i<MEMTRACK_NUM_OF_MEMTYPES; ++i, bit_mask<<=1) {
467                if (bit_mask & track_mask) {
468                        remove_proc_entry(rsc_names[i], memtrack_tree);
469                }
470        }
471        remove_proc_entry(memtrack_proc_entry_name, NULL);
472}
473
474
475/* module entry points */
476
477int init_module(void)
478{
479        memtrack_memtype_t i;
480        int j;
481        unsigned long bit_mask;
482
483
484        /* create a cache for the memtrack_meminfo_t strcutures */
485        meminfo_cache = kmem_cache_create("memtrack_meminfo_t",
486                                          sizeof(memtrack_meminfo_t), 0,
487                                          SLAB_HWCACHE_ALIGN, NULL);
488        if (!meminfo_cache) {
489                printk("memtrack::%s: failed to allocate meminfo cache\n", __func__);
490                return -1;
491        }
492
493        /* initialize array of descriptors */
494        memset(tracked_objs_arr, 0, sizeof(tracked_objs_arr));
495
496        /* create a tracking object descriptor for all required objects */
497        for (i = 0, bit_mask = 1; i < MEMTRACK_NUM_OF_MEMTYPES;
498             ++i, bit_mask <<= 1) {
499                if (bit_mask & track_mask) {
500                        tracked_objs_arr[i] =
501                            vmalloc(sizeof(tracked_obj_desc_t));
502                        if (!tracked_objs_arr[i]) {
503                                printk("memtrack: failed to allocate tracking object\n");
504                                goto undo_cache_create;
505                        }
506
507                        memset(tracked_objs_arr[i], 0, sizeof(tracked_obj_desc_t));
508                        spin_lock_init(&tracked_objs_arr[i]->hash_lock);
509                        INIT_LIST_HEAD(&tracked_objs_arr[i]->tracked_objs_head);
510                        if (bit_mask & strict_track_mask) {
511                                tracked_objs_arr[i]->strict_track = 1;
512                        } else {
513                                tracked_objs_arr[i]->strict_track = 0;
514                        }
515                }
516        }
517
518
519        if ( create_procfs_tree() ) {
520                  printk("%s: create_procfs_tree() failed\n", __FILE__);
521                  goto undo_cache_create;
522        }
523
524
525        printk("memtrack::%s done.\n", __func__);
526
527        return 0;
528
529undo_cache_create:
530        for (j=0; j<i; ++j) {
531                if (tracked_objs_arr[j]) {
532                        vfree(tracked_objs_arr[j]);
533                }
534        }
535
536#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)
537        if (kmem_cache_destroy(meminfo_cache) != 0) {
538                printk("Failed on kmem_cache_destroy !\n");
539        }
540#else
541        kmem_cache_destroy(meminfo_cache);
542#endif
543        return -1;
544}
545
546
547void cleanup_module(void)
548{
549        memtrack_memtype_t memtype;
550        unsigned long cur_bucket;
551        memtrack_meminfo_t *cur_mem_info_p, *next_mem_info_p;
552        tracked_obj_desc_t *obj_desc_p;
553        unsigned long flags;
554
555
556        memtrack_report();
557
558
559        destroy_procfs_tree();
560
561        /* clean up any hash table left-overs */
562        for (memtype = 0; memtype < MEMTRACK_NUM_OF_MEMTYPES; memtype++) {
563                /* Scan all buckets to find existing allocations */
564                /* TBD: this may be optimized by holding a linked list of all hash items */
565                if (tracked_objs_arr[memtype]) {
566                        obj_desc_p = tracked_objs_arr[memtype];
567                        for (cur_bucket = 0; cur_bucket < MEMTRACK_HASH_SZ;
568                             cur_bucket++) {
569                                memtrack_spin_lock(&obj_desc_p->hash_lock, flags);      /* protect per bucket/list */
570                                cur_mem_info_p =
571                                    obj_desc_p->mem_hash[cur_bucket];
572                                while (cur_mem_info_p != NULL) {        /* scan bucket */
573                                        next_mem_info_p = cur_mem_info_p->next; /* save "next" pointer before the "free" */
574                                        kmem_cache_free(meminfo_cache,
575                                                        cur_mem_info_p);
576                                        cur_mem_info_p = next_mem_info_p;
577                                }       /* while cur_mem_info_p */
578                                memtrack_spin_unlock(&obj_desc_p->hash_lock, flags);
579                        }       /* for cur_bucket */
580                        vfree(obj_desc_p);
581                }
582        }                       /* for memtype */
583
584#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)
585        if (kmem_cache_destroy(meminfo_cache) != 0) {
586                printk
587                    ("memtrack::cleanup_module: Failed on kmem_cache_destroy !\n");
588        }
589#else
590        kmem_cache_destroy(meminfo_cache);
591#endif
592        printk("memtrack::cleanup_module done.\n");
593}
594
595EXPORT_SYMBOL(memtrack_alloc);
596EXPORT_SYMBOL(memtrack_free);
597
598//module_init(memtrack_init)
599//module_exit(memtrack_exit)
600
601