Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 1 | /* |
| 2 | * allocator.c -- allocate after high_memory, if available |
| 3 | * |
| 4 | * NOTE: this is different from my previous allocator, the one that |
| 5 | * assembles pages, which revealed itself both slow and unreliable. |
| 6 | * |
| 7 | * Copyright (C) 1998 rubini@linux.it (Alessandro Rubini) |
| 8 | * |
| 9 | * This program is free software; you can redistribute it and/or modify |
| 10 | * it under the terms of the GNU General Public License as published by |
| 11 | * the Free Software Foundation; either version 2 of the License, or |
| 12 | * (at your option) any later version. |
| 13 | * |
| 14 | * This program is distributed in the hope that it will be useful, |
| 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 17 | * GNU General Public License for more details. |
| 18 | * |
| 19 | * You should have received a copy of the GNU General Public License |
| 20 | * along with this program; if not, write to the Free Software |
| 21 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
| 22 | * |
| 23 | |
| 24 | -- Changes -- |
| 25 | |
| 26 | Date Programmer Description of changes made |
| 27 | ------------------------------------------------------------------- |
| 28 | 02-Aug-2002 NJC allocator now steps in 1MB increments, rather |
Greg Kroah-Hartman | 1769fd8 | 2009-12-22 15:18:28 -0800 | [diff] [blame] | 29 | than doubling its size each time. |
Greg Kroah-Hartman | dcff74c | 2010-02-09 12:41:38 -0800 | [diff] [blame] | 30 | Also, allocator_init(u32 *) now returns |
Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 31 | (in the first arg) the size of the free |
| 32 | space. This is no longer consistent with |
| 33 | using the allocator as a module, and some changes |
| 34 | may be necessary for that purpose. This was |
| 35 | designed to work with the DT3155 driver, in |
| 36 | stand alone mode only!!! |
| 37 | 26-Oct-2009 SS Port to 2.6.30 kernel. |
| 38 | */ |
| 39 | |
| 40 | |
| 41 | #ifndef __KERNEL__ |
| 42 | # define __KERNEL__ |
| 43 | #endif |
| 44 | #ifndef MODULE |
| 45 | # define MODULE |
| 46 | #endif |
| 47 | |
| 48 | #include <linux/version.h> |
| 49 | |
| 50 | #include <linux/sched.h> |
| 51 | #include <linux/kernel.h> |
| 52 | #include <linux/fs.h> |
| 53 | #include <linux/proc_fs.h> |
| 54 | #include <linux/errno.h> |
| 55 | #include <linux/types.h> |
| 56 | #include <linux/mm.h> /* PAGE_ALIGN() */ |
Simon Horman | 2141ec6 | 2009-12-24 22:42:31 +1100 | [diff] [blame] | 57 | #include <linux/io.h> |
Tejun Heo | 5a0e3ad | 2010-03-24 17:04:11 +0900 | [diff] [blame^] | 58 | #include <linux/slab.h> |
Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 59 | |
| 60 | #include <asm/page.h> |
| 61 | |
Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 62 | /*#define ALL_DEBUG*/ |
| 63 | #define ALL_MSG "allocator: " |
| 64 | |
| 65 | #undef PDEBUG /* undef it, just in case */ |
| 66 | #ifdef ALL_DEBUG |
| 67 | # define __static |
| 68 | # define DUMP_LIST() dump_list() |
| 69 | # ifdef __KERNEL__ |
| 70 | /* This one if debugging is on, and kernel space */ |
Greg Kroah-Hartman | 1769fd8 | 2009-12-22 15:18:28 -0800 | [diff] [blame] | 71 | # define PDEBUG(fmt, args...) printk(KERN_DEBUG ALL_MSG fmt, ## args) |
Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 72 | # else |
| 73 | /* This one for user space */ |
| 74 | # define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args) |
| 75 | # endif |
| 76 | #else |
| 77 | # define PDEBUG(fmt, args...) /* not debugging: nothing */ |
| 78 | # define DUMP_LIST() |
| 79 | # define __static static |
| 80 | #endif |
| 81 | |
| 82 | #undef PDEBUGG |
| 83 | #define PDEBUGG(fmt, args...) |
| 84 | /*#define PDEBUGG(fmt, args...) printk( KERN_DEBUG ALL_MSG fmt, ## args)*/ |
| 85 | |
| 86 | |
| 87 | int allocator_himem = 1; /* 0 = probe, pos. = megs, neg. = disable */ |
| 88 | int allocator_step = 1; /* This is the step size in MB */ |
| 89 | int allocator_probe = 1; /* This is a flag -- 1=probe, 0=don't probe */ |
| 90 | |
Greg Kroah-Hartman | 1769fd8 | 2009-12-22 15:18:28 -0800 | [diff] [blame] | 91 | static unsigned long allocator_buffer; /* physical address */ |
| 92 | static unsigned long allocator_buffer_size; /* kilobytes */ |
Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 93 | |
| 94 | /* |
| 95 | * The allocator keeps a list of DMA areas, so multiple devices |
| 96 | * can coexist. The list is kept sorted by address |
| 97 | */ |
| 98 | |
| 99 | struct allocator_struct { |
Greg Kroah-Hartman | 1769fd8 | 2009-12-22 15:18:28 -0800 | [diff] [blame] | 100 | unsigned long address; |
| 101 | unsigned long size; |
| 102 | struct allocator_struct *next; |
Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 103 | }; |
| 104 | |
Greg Kroah-Hartman | 1769fd8 | 2009-12-22 15:18:28 -0800 | [diff] [blame] | 105 | struct allocator_struct *allocator_list; |
Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 106 | |
| 107 | |
| 108 | #ifdef ALL_DEBUG |
| 109 | static int dump_list(void) |
| 110 | { |
Greg Kroah-Hartman | 1769fd8 | 2009-12-22 15:18:28 -0800 | [diff] [blame] | 111 | struct allocator_struct *ptr; |
Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 112 | |
Greg Kroah-Hartman | 1769fd8 | 2009-12-22 15:18:28 -0800 | [diff] [blame] | 113 | PDEBUG("Current list:\n"); |
| 114 | for (ptr = allocator_list; ptr; ptr = ptr->next) |
| 115 | PDEBUG("0x%08lx (size %likB)\n", ptr->address, ptr->size>>10); |
| 116 | return 0; |
Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 117 | } |
| 118 | #endif |
| 119 | |
| 120 | /* ======================================================================== |
| 121 | * This function is the actual allocator. |
| 122 | * |
| 123 | * If space is available in high memory (as detected at load time), that |
| 124 | * one is returned. The return value is a physical address (i.e., it can |
| 125 | * be used straight ahead for DMA, but needs remapping for program use). |
| 126 | */ |
| 127 | |
Greg Kroah-Hartman | 1769fd8 | 2009-12-22 15:18:28 -0800 | [diff] [blame] | 128 | unsigned long allocator_allocate_dma(unsigned long kilobytes, int prio) |
Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 129 | { |
Greg Kroah-Hartman | 1769fd8 | 2009-12-22 15:18:28 -0800 | [diff] [blame] | 130 | struct allocator_struct *ptr = allocator_list, *newptr; |
| 131 | unsigned long bytes = kilobytes << 10; |
Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 132 | |
Greg Kroah-Hartman | 1769fd8 | 2009-12-22 15:18:28 -0800 | [diff] [blame] | 133 | /* check if high memory is available */ |
| 134 | if (!allocator_buffer) |
| 135 | return 0; |
Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 136 | |
Greg Kroah-Hartman | 1769fd8 | 2009-12-22 15:18:28 -0800 | [diff] [blame] | 137 | /* Round it to a multiple of the pagesize */ |
| 138 | bytes = PAGE_ALIGN(bytes); |
| 139 | PDEBUG("request for %li bytes\n", bytes); |
Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 140 | |
Greg Kroah-Hartman | 1769fd8 | 2009-12-22 15:18:28 -0800 | [diff] [blame] | 141 | while (ptr && ptr->next) { |
| 142 | if (ptr->next->address - (ptr->address + ptr->size) >= bytes) |
| 143 | break; /* enough space */ |
| 144 | ptr = ptr->next; |
| 145 | } |
| 146 | if (!ptr->next) { |
| 147 | DUMP_LIST(); |
| 148 | PDEBUG("alloc failed\n"); |
| 149 | return 0; /* end of list */ |
| 150 | } |
| 151 | newptr = kmalloc(sizeof(struct allocator_struct), prio); |
| 152 | if (!newptr) |
| 153 | return 0; |
Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 154 | |
Greg Kroah-Hartman | 1769fd8 | 2009-12-22 15:18:28 -0800 | [diff] [blame] | 155 | /* ok, now stick it after ptr */ |
| 156 | newptr->address = ptr->address + ptr->size; |
| 157 | newptr->size = bytes; |
| 158 | newptr->next = ptr->next; |
| 159 | ptr->next = newptr; |
Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 160 | |
Greg Kroah-Hartman | 1769fd8 | 2009-12-22 15:18:28 -0800 | [diff] [blame] | 161 | DUMP_LIST(); |
| 162 | PDEBUG("returning 0x%08lx\n", newptr->address); |
| 163 | return newptr->address; |
Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 164 | } |
| 165 | |
Greg Kroah-Hartman | 1769fd8 | 2009-12-22 15:18:28 -0800 | [diff] [blame] | 166 | int allocator_free_dma(unsigned long address) |
Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 167 | { |
Greg Kroah-Hartman | 1769fd8 | 2009-12-22 15:18:28 -0800 | [diff] [blame] | 168 | struct allocator_struct *ptr = allocator_list, *prev; |
Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 169 | |
Greg Kroah-Hartman | 1769fd8 | 2009-12-22 15:18:28 -0800 | [diff] [blame] | 170 | while (ptr && ptr->next) { |
| 171 | if (ptr->next->address == address) |
| 172 | break; |
| 173 | ptr = ptr->next; |
Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 174 | } |
Greg Kroah-Hartman | 1769fd8 | 2009-12-22 15:18:28 -0800 | [diff] [blame] | 175 | /* the one being freed is ptr->next */ |
| 176 | prev = ptr; ptr = ptr->next; |
Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 177 | |
Greg Kroah-Hartman | 1769fd8 | 2009-12-22 15:18:28 -0800 | [diff] [blame] | 178 | if (!ptr) { |
| 179 | printk(KERN_ERR ALL_MSG |
| 180 | "free_dma(0x%08lx) but add. not allocated\n", |
| 181 | ptr->address); |
| 182 | return -EINVAL; |
| 183 | } |
| 184 | PDEBUGG("freeing: %08lx (%li) next %08lx\n", ptr->address, ptr->size, |
| 185 | ptr->next->address); |
| 186 | prev->next = ptr->next; |
| 187 | kfree(ptr); |
Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 188 | |
Greg Kroah-Hartman | 1769fd8 | 2009-12-22 15:18:28 -0800 | [diff] [blame] | 189 | /* dump_list(); */ |
| 190 | return 0; |
Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 191 | } |
| 192 | |
| 193 | /* ======================================================================== |
| 194 | * Init and cleanup |
| 195 | * |
| 196 | * On cleanup everything is released. If the list is not empty, that a |
| 197 | * problem of our clients |
| 198 | */ |
H Hartley Sweeten | 3a8954e | 2010-02-26 17:58:07 -0700 | [diff] [blame] | 199 | int allocator_init(u32 *allocator_max) |
Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 200 | { |
Greg Kroah-Hartman | 1769fd8 | 2009-12-22 15:18:28 -0800 | [diff] [blame] | 201 | /* check how much free memory is there */ |
| 202 | void *remapped; |
| 203 | unsigned long max; |
| 204 | unsigned long trial_size = allocator_himem<<20; |
| 205 | unsigned long last_trial = 0; |
| 206 | unsigned long step = allocator_step<<20; |
| 207 | unsigned long i = 0; |
| 208 | struct allocator_struct *head, *tail; |
| 209 | char test_string[] = "0123456789abcde"; /* 16 bytes */ |
Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 210 | |
Greg Kroah-Hartman | 1769fd8 | 2009-12-22 15:18:28 -0800 | [diff] [blame] | 211 | PDEBUGG("himem = %i\n", allocator_himem); |
| 212 | if (allocator_himem < 0) /* don't even try */ |
| 213 | return -EINVAL; |
Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 214 | |
Greg Kroah-Hartman | 1769fd8 | 2009-12-22 15:18:28 -0800 | [diff] [blame] | 215 | if (!trial_size) |
| 216 | trial_size = 1<<20; /* not specified: try one meg */ |
Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 217 | |
Greg Kroah-Hartman | 1769fd8 | 2009-12-22 15:18:28 -0800 | [diff] [blame] | 218 | while (1) { |
| 219 | remapped = ioremap(__pa(high_memory), trial_size); |
| 220 | if (!remapped) { |
| 221 | PDEBUGG("%li megs failed!\n", trial_size>>20); |
| 222 | break; |
| 223 | } |
| 224 | PDEBUGG("Trying %li megs (at %p, %p)\n", trial_size>>20, |
| 225 | (void *)__pa(high_memory), remapped); |
| 226 | for (i = last_trial; i < trial_size; i += 16) { |
| 227 | strcpy((char *)(remapped)+i, test_string); |
| 228 | if (strcmp((char *)(remapped)+i, test_string)) |
| 229 | break; |
| 230 | } |
| 231 | iounmap((void *)remapped); |
| 232 | schedule(); |
| 233 | last_trial = trial_size; |
| 234 | if (i == trial_size) |
| 235 | trial_size += step; /* increment, if all went well */ |
| 236 | else { |
| 237 | PDEBUGG("%li megs copy test failed!\n", trial_size>>20); |
| 238 | break; |
| 239 | } |
| 240 | if (!allocator_probe) |
| 241 | break; |
| 242 | } |
| 243 | PDEBUG("%li megs (%li k, %li b)\n", i>>20, i>>10, i); |
| 244 | allocator_buffer_size = i>>10; /* kilobytes */ |
| 245 | allocator_buffer = __pa(high_memory); |
| 246 | if (!allocator_buffer_size) { |
| 247 | printk(KERN_WARNING ALL_MSG "no free high memory to use\n"); |
| 248 | return -ENOMEM; |
| 249 | } |
Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 250 | |
Greg Kroah-Hartman | 1769fd8 | 2009-12-22 15:18:28 -0800 | [diff] [blame] | 251 | /* |
| 252 | * to simplify things, always have two cells in the list: |
| 253 | * the first and the last. This avoids some conditionals and |
| 254 | * extra code when allocating and deallocating: we only play |
| 255 | * in the middle of the list |
| 256 | */ |
| 257 | head = kmalloc(sizeof(struct allocator_struct), GFP_KERNEL); |
| 258 | if (!head) |
| 259 | return -ENOMEM; |
| 260 | tail = kmalloc(sizeof(struct allocator_struct), GFP_KERNEL); |
| 261 | if (!tail) { |
| 262 | kfree(head); |
| 263 | return -ENOMEM; |
| 264 | } |
Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 265 | |
Greg Kroah-Hartman | 1769fd8 | 2009-12-22 15:18:28 -0800 | [diff] [blame] | 266 | max = allocator_buffer_size<<10; |
Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 267 | |
Greg Kroah-Hartman | 1769fd8 | 2009-12-22 15:18:28 -0800 | [diff] [blame] | 268 | head->size = tail->size = 0; |
| 269 | head->address = allocator_buffer; |
| 270 | tail->address = allocator_buffer + max; |
| 271 | head->next = tail; |
| 272 | tail->next = NULL; |
| 273 | allocator_list = head; |
Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 274 | |
Greg Kroah-Hartman | 1769fd8 | 2009-12-22 15:18:28 -0800 | [diff] [blame] | 275 | /* Back to the user code, in KB */ |
| 276 | *allocator_max = allocator_buffer_size; |
Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 277 | |
Greg Kroah-Hartman | 1769fd8 | 2009-12-22 15:18:28 -0800 | [diff] [blame] | 278 | return 0; /* ok, ready */ |
Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 279 | } |
| 280 | |
| 281 | void allocator_cleanup(void) |
| 282 | { |
Greg Kroah-Hartman | 1769fd8 | 2009-12-22 15:18:28 -0800 | [diff] [blame] | 283 | struct allocator_struct *ptr, *next; |
Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 284 | |
Greg Kroah-Hartman | 1769fd8 | 2009-12-22 15:18:28 -0800 | [diff] [blame] | 285 | for (ptr = allocator_list; ptr; ptr = next) { |
| 286 | next = ptr->next; |
| 287 | PDEBUG("freeing list: 0x%08lx\n", ptr->address); |
| 288 | kfree(ptr); |
| 289 | } |
Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 290 | |
Greg Kroah-Hartman | 1769fd8 | 2009-12-22 15:18:28 -0800 | [diff] [blame] | 291 | allocator_buffer = 0; |
| 292 | allocator_buffer_size = 0; |
| 293 | allocator_list = NULL; |
Scott Smedley | aa337ef | 2009-12-18 10:54:26 -0800 | [diff] [blame] | 294 | } |
| 295 | |
| 296 | |