Linux 3.13-rc1
[linux-2.6-microblaze.git] / drivers / staging / tidspbridge / pmgr / dmm.c
1 /*
2  * dmm.c
3  *
4  * DSP-BIOS Bridge driver support functions for TI OMAP processors.
5  *
6  * The Dynamic Memory Manager (DMM) module manages the DSP Virtual address
7  * space that can be directly mapped to any MPU buffer or memory region
8  *
9  * Notes:
10  *   Region: Generic memory entitiy having a start address and a size
11  *   Chunk:  Reserved region
12  *
13  * Copyright (C) 2005-2006 Texas Instruments, Inc.
14  *
15  * This package is free software; you can redistribute it and/or modify
16  * it under the terms of the GNU General Public License version 2 as
17  * published by the Free Software Foundation.
18  *
19  * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
20  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
21  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
22  */
23 #include <linux/types.h>
24
25 /*  ----------------------------------- Host OS */
26 #include <dspbridge/host_os.h>
27
28 /*  ----------------------------------- DSP/BIOS Bridge */
29 #include <dspbridge/dbdefs.h>
30
31 /*  ----------------------------------- OS Adaptation Layer */
32 #include <dspbridge/sync.h>
33
34 /*  ----------------------------------- Platform Manager */
35 #include <dspbridge/dev.h>
36 #include <dspbridge/proc.h>
37
38 /*  ----------------------------------- This */
39 #include <dspbridge/dmm.h>
40
41 /*  ----------------------------------- Defines, Data Structures, Typedefs */
42 #define DMM_ADDR_VIRTUAL(a) \
43         (((struct map_page *)(a) - virtual_mapping_table) * PG_SIZE4K +\
44         dyn_mem_map_beg)
45 #define DMM_ADDR_TO_INDEX(a) (((a) - dyn_mem_map_beg) / PG_SIZE4K)
46
47 /* DMM Mgr */
48 struct dmm_object {
49         /* Dmm Lock is used to serialize access mem manager for
50          * multi-threads. */
51         spinlock_t dmm_lock;    /* Lock to access dmm mgr */
52 };
53
54 struct map_page {
55         u32 region_size:15;
56         u32 mapped_size:15;
57         u32 reserved:1;
58         u32 mapped:1;
59 };
60
61 /*  Create the free list */
62 static struct map_page *virtual_mapping_table;
63 static u32 free_region;         /* The index of free region */
64 static u32 free_size;
65 static u32 dyn_mem_map_beg;     /* The Beginning of dynamic memory mapping */
66 static u32 table_size;          /* The size of virt and phys pages tables */
67
68 /*  ----------------------------------- Function Prototypes */
69 static struct map_page *get_region(u32 addr);
70 static struct map_page *get_free_region(u32 len);
71 static struct map_page *get_mapped_region(u32 addrs);
72
73 /*  ======== dmm_create_tables ========
74  *  Purpose:
75  *      Create table to hold the information of physical address
76  *      the buffer pages that is passed by the user, and the table
77  *      to hold the information of the virtual memory that is reserved
78  *      for DSP.
79  */
80 int dmm_create_tables(struct dmm_object *dmm_mgr, u32 addr, u32 size)
81 {
82         struct dmm_object *dmm_obj = (struct dmm_object *)dmm_mgr;
83         int status = 0;
84
85         status = dmm_delete_tables(dmm_obj);
86         if (!status) {
87                 dyn_mem_map_beg = addr;
88                 table_size = PG_ALIGN_HIGH(size, PG_SIZE4K) / PG_SIZE4K;
89                 /*  Create the free list */
90                 virtual_mapping_table = __vmalloc(table_size *
91                                 sizeof(struct map_page), GFP_KERNEL |
92                                 __GFP_HIGHMEM | __GFP_ZERO, PAGE_KERNEL);
93                 if (virtual_mapping_table == NULL)
94                         status = -ENOMEM;
95                 else {
96                         /* On successful allocation,
97                          * all entries are zero ('free') */
98                         free_region = 0;
99                         free_size = table_size * PG_SIZE4K;
100                         virtual_mapping_table[0].region_size = table_size;
101                 }
102         }
103
104         if (status)
105                 pr_err("%s: failure, status 0x%x\n", __func__, status);
106
107         return status;
108 }
109
110 /*
111  *  ======== dmm_create ========
112  *  Purpose:
113  *      Create a dynamic memory manager object.
114  */
115 int dmm_create(struct dmm_object **dmm_manager,
116                       struct dev_object *hdev_obj,
117                       const struct dmm_mgrattrs *mgr_attrts)
118 {
119         struct dmm_object *dmm_obj = NULL;
120         int status = 0;
121
122         *dmm_manager = NULL;
123         /* create, zero, and tag a cmm mgr object */
124         dmm_obj = kzalloc(sizeof(struct dmm_object), GFP_KERNEL);
125         if (dmm_obj != NULL) {
126                 spin_lock_init(&dmm_obj->dmm_lock);
127                 *dmm_manager = dmm_obj;
128         } else {
129                 status = -ENOMEM;
130         }
131
132         return status;
133 }
134
135 /*
136  *  ======== dmm_destroy ========
137  *  Purpose:
138  *      Release the communication memory manager resources.
139  */
140 int dmm_destroy(struct dmm_object *dmm_mgr)
141 {
142         struct dmm_object *dmm_obj = (struct dmm_object *)dmm_mgr;
143         int status = 0;
144
145         if (dmm_mgr) {
146                 status = dmm_delete_tables(dmm_obj);
147                 if (!status)
148                         kfree(dmm_obj);
149         } else
150                 status = -EFAULT;
151
152         return status;
153 }
154
155 /*
156  *  ======== dmm_delete_tables ========
157  *  Purpose:
158  *      Delete DMM Tables.
159  */
160 int dmm_delete_tables(struct dmm_object *dmm_mgr)
161 {
162         int status = 0;
163
164         /* Delete all DMM tables */
165         if (dmm_mgr)
166                 vfree(virtual_mapping_table);
167         else
168                 status = -EFAULT;
169         return status;
170 }
171
172 /*
173  *  ======== dmm_get_handle ========
174  *  Purpose:
175  *      Return the dynamic memory manager object for this device.
176  *      This is typically called from the client process.
177  */
178 int dmm_get_handle(void *hprocessor, struct dmm_object **dmm_manager)
179 {
180         int status = 0;
181         struct dev_object *hdev_obj;
182
183         if (hprocessor != NULL)
184                 status = proc_get_dev_object(hprocessor, &hdev_obj);
185         else
186                 hdev_obj = dev_get_first();     /* default */
187
188         if (!status)
189                 status = dev_get_dmm_mgr(hdev_obj, dmm_manager);
190
191         return status;
192 }
193
194 /*
195  *  ======== dmm_map_memory ========
196  *  Purpose:
197  *      Add a mapping block to the reserved chunk. DMM assumes that this block
198  *  will be mapped in the DSP/IVA's address space. DMM returns an error if a
199  *  mapping overlaps another one. This function stores the info that will be
200  *  required later while unmapping the block.
201  */
202 int dmm_map_memory(struct dmm_object *dmm_mgr, u32 addr, u32 size)
203 {
204         struct dmm_object *dmm_obj = (struct dmm_object *)dmm_mgr;
205         struct map_page *chunk;
206         int status = 0;
207
208         spin_lock(&dmm_obj->dmm_lock);
209         /* Find the Reserved memory chunk containing the DSP block to
210          * be mapped */
211         chunk = (struct map_page *)get_region(addr);
212         if (chunk != NULL) {
213                 /* Mark the region 'mapped', leave the 'reserved' info as-is */
214                 chunk->mapped = true;
215                 chunk->mapped_size = (size / PG_SIZE4K);
216         } else
217                 status = -ENOENT;
218         spin_unlock(&dmm_obj->dmm_lock);
219
220         dev_dbg(bridge, "%s dmm_mgr %p, addr %x, size %x\n\tstatus %x, "
221                 "chunk %p", __func__, dmm_mgr, addr, size, status, chunk);
222
223         return status;
224 }
225
226 /*
227  *  ======== dmm_reserve_memory ========
228  *  Purpose:
229  *      Reserve a chunk of virtually contiguous DSP/IVA address space.
230  */
231 int dmm_reserve_memory(struct dmm_object *dmm_mgr, u32 size,
232                               u32 *prsv_addr)
233 {
234         int status = 0;
235         struct dmm_object *dmm_obj = (struct dmm_object *)dmm_mgr;
236         struct map_page *node;
237         u32 rsv_addr = 0;
238         u32 rsv_size = 0;
239
240         spin_lock(&dmm_obj->dmm_lock);
241
242         /* Try to get a DSP chunk from the free list */
243         node = get_free_region(size);
244         if (node != NULL) {
245                 /*  DSP chunk of given size is available. */
246                 rsv_addr = DMM_ADDR_VIRTUAL(node);
247                 /* Calculate the number entries to use */
248                 rsv_size = size / PG_SIZE4K;
249                 if (rsv_size < node->region_size) {
250                         /* Mark remainder of free region */
251                         node[rsv_size].mapped = false;
252                         node[rsv_size].reserved = false;
253                         node[rsv_size].region_size =
254                             node->region_size - rsv_size;
255                         node[rsv_size].mapped_size = 0;
256                 }
257                 /*  get_region will return first fit chunk. But we only use what
258                    is requested. */
259                 node->mapped = false;
260                 node->reserved = true;
261                 node->region_size = rsv_size;
262                 node->mapped_size = 0;
263                 /* Return the chunk's starting address */
264                 *prsv_addr = rsv_addr;
265         } else
266                 /*dSP chunk of given size is not available */
267                 status = -ENOMEM;
268
269         spin_unlock(&dmm_obj->dmm_lock);
270
271         dev_dbg(bridge, "%s dmm_mgr %p, size %x, prsv_addr %p\n\tstatus %x, "
272                 "rsv_addr %x, rsv_size %x\n", __func__, dmm_mgr, size,
273                 prsv_addr, status, rsv_addr, rsv_size);
274
275         return status;
276 }
277
278 /*
279  *  ======== dmm_un_map_memory ========
280  *  Purpose:
281  *      Remove the mapped block from the reserved chunk.
282  */
283 int dmm_un_map_memory(struct dmm_object *dmm_mgr, u32 addr, u32 *psize)
284 {
285         struct dmm_object *dmm_obj = (struct dmm_object *)dmm_mgr;
286         struct map_page *chunk;
287         int status = 0;
288
289         spin_lock(&dmm_obj->dmm_lock);
290         chunk = get_mapped_region(addr);
291         if (chunk == NULL)
292                 status = -ENOENT;
293
294         if (!status) {
295                 /* Unmap the region */
296                 *psize = chunk->mapped_size * PG_SIZE4K;
297                 chunk->mapped = false;
298                 chunk->mapped_size = 0;
299         }
300         spin_unlock(&dmm_obj->dmm_lock);
301
302         dev_dbg(bridge, "%s: dmm_mgr %p, addr %x, psize %p\n\tstatus %x, "
303                 "chunk %p\n", __func__, dmm_mgr, addr, psize, status, chunk);
304
305         return status;
306 }
307
308 /*
309  *  ======== dmm_un_reserve_memory ========
310  *  Purpose:
311  *      Free a chunk of reserved DSP/IVA address space.
312  */
313 int dmm_un_reserve_memory(struct dmm_object *dmm_mgr, u32 rsv_addr)
314 {
315         struct dmm_object *dmm_obj = (struct dmm_object *)dmm_mgr;
316         struct map_page *chunk;
317         u32 i;
318         int status = 0;
319         u32 chunk_size;
320
321         spin_lock(&dmm_obj->dmm_lock);
322
323         /* Find the chunk containing the reserved address */
324         chunk = get_mapped_region(rsv_addr);
325         if (chunk == NULL)
326                 status = -ENOENT;
327
328         if (!status) {
329                 /* Free all the mapped pages for this reserved region */
330                 i = 0;
331                 while (i < chunk->region_size) {
332                         if (chunk[i].mapped) {
333                                 /* Remove mapping from the page tables. */
334                                 chunk_size = chunk[i].mapped_size;
335                                 /* Clear the mapping flags */
336                                 chunk[i].mapped = false;
337                                 chunk[i].mapped_size = 0;
338                                 i += chunk_size;
339                         } else
340                                 i++;
341                 }
342                 /* Clear the flags (mark the region 'free') */
343                 chunk->reserved = false;
344                 /* NOTE: We do NOT coalesce free regions here.
345                  * Free regions are coalesced in get_region(), as it traverses
346                  *the whole mapping table
347                  */
348         }
349         spin_unlock(&dmm_obj->dmm_lock);
350
351         dev_dbg(bridge, "%s: dmm_mgr %p, rsv_addr %x\n\tstatus %x chunk %p",
352                 __func__, dmm_mgr, rsv_addr, status, chunk);
353
354         return status;
355 }
356
357 /*
358  *  ======== get_region ========
359  *  Purpose:
360  *      Returns a region containing the specified memory region
361  */
362 static struct map_page *get_region(u32 addr)
363 {
364         struct map_page *curr_region = NULL;
365         u32 i = 0;
366
367         if (virtual_mapping_table != NULL) {
368                 /* find page mapped by this address */
369                 i = DMM_ADDR_TO_INDEX(addr);
370                 if (i < table_size)
371                         curr_region = virtual_mapping_table + i;
372         }
373
374         dev_dbg(bridge, "%s: curr_region %p, free_region %d, free_size %d\n",
375                 __func__, curr_region, free_region, free_size);
376         return curr_region;
377 }
378
379 /*
380  *  ======== get_free_region ========
381  *  Purpose:
382  *  Returns the requested free region
383  */
384 static struct map_page *get_free_region(u32 len)
385 {
386         struct map_page *curr_region = NULL;
387         u32 i = 0;
388         u32 region_size = 0;
389         u32 next_i = 0;
390
391         if (virtual_mapping_table == NULL)
392                 return curr_region;
393         if (len > free_size) {
394                 /* Find the largest free region
395                  * (coalesce during the traversal) */
396                 while (i < table_size) {
397                         region_size = virtual_mapping_table[i].region_size;
398                         next_i = i + region_size;
399                         if (virtual_mapping_table[i].reserved == false) {
400                                 /* Coalesce, if possible */
401                                 if (next_i < table_size &&
402                                     virtual_mapping_table[next_i].reserved
403                                     == false) {
404                                         virtual_mapping_table[i].region_size +=
405                                             virtual_mapping_table
406                                             [next_i].region_size;
407                                         continue;
408                                 }
409                                 region_size *= PG_SIZE4K;
410                                 if (region_size > free_size) {
411                                         free_region = i;
412                                         free_size = region_size;
413                                 }
414                         }
415                         i = next_i;
416                 }
417         }
418         if (len <= free_size) {
419                 curr_region = virtual_mapping_table + free_region;
420                 free_region += (len / PG_SIZE4K);
421                 free_size -= len;
422         }
423         return curr_region;
424 }
425
426 /*
427  *  ======== get_mapped_region ========
428  *  Purpose:
429  *  Returns the requestedmapped region
430  */
431 static struct map_page *get_mapped_region(u32 addrs)
432 {
433         u32 i = 0;
434         struct map_page *curr_region = NULL;
435
436         if (virtual_mapping_table == NULL)
437                 return curr_region;
438
439         i = DMM_ADDR_TO_INDEX(addrs);
440         if (i < table_size && (virtual_mapping_table[i].mapped ||
441                                virtual_mapping_table[i].reserved))
442                 curr_region = virtual_mapping_table + i;
443         return curr_region;
444 }
445
446 #ifdef DSP_DMM_DEBUG
447 u32 dmm_mem_map_dump(struct dmm_object *dmm_mgr)
448 {
449         struct map_page *curr_node = NULL;
450         u32 i;
451         u32 freemem = 0;
452         u32 bigsize = 0;
453
454         spin_lock(&dmm_mgr->dmm_lock);
455
456         if (virtual_mapping_table != NULL) {
457                 for (i = 0; i < table_size; i +=
458                      virtual_mapping_table[i].region_size) {
459                         curr_node = virtual_mapping_table + i;
460                         if (curr_node->reserved) {
461                                 /*printk("RESERVED size = 0x%x, "
462                                    "Map size = 0x%x\n",
463                                    (curr_node->region_size * PG_SIZE4K),
464                                    (curr_node->mapped == false) ? 0 :
465                                    (curr_node->mapped_size * PG_SIZE4K));
466                                  */
467                         } else {
468 /*                              printk("UNRESERVED size = 0x%x\n",
469                                         (curr_node->region_size * PG_SIZE4K));
470  */
471                                 freemem += (curr_node->region_size * PG_SIZE4K);
472                                 if (curr_node->region_size > bigsize)
473                                         bigsize = curr_node->region_size;
474                         }
475                 }
476         }
477         spin_unlock(&dmm_mgr->dmm_lock);
478         printk(KERN_INFO "Total DSP VA FREE memory = %d Mbytes\n",
479                freemem / (1024 * 1024));
480         printk(KERN_INFO "Total DSP VA USED memory= %d Mbytes \n",
481                (((table_size * PG_SIZE4K) - freemem)) / (1024 * 1024));
482         printk(KERN_INFO "DSP VA - Biggest FREE block = %d Mbytes \n\n",
483                (bigsize * PG_SIZE4K / (1024 * 1024)));
484
485         return 0;
486 }
487 #endif