ftp://ftp.kernel.org/pub/linux/kernel/v2.6/linux-2.6.6.tar.bz2
[linux-2.6.git] / arch / ppc64 / kernel / lmb.c
1 /*
2  * Procedures for interfacing to Open Firmware.
3  *
4  * Peter Bergner, IBM Corp.     June 2001.
5  * Copyright (C) 2001 Peter Bergner.
6  * 
7  *      This program is free software; you can redistribute it and/or
8  *      modify it under the terms of the GNU General Public License
9  *      as published by the Free Software Foundation; either version
10  *      2 of the License, or (at your option) any later version.
11  */
12
13 #include <linux/config.h>
14 #include <linux/kernel.h>
15 #include <linux/init.h>
16 #include <asm/types.h>
17 #include <asm/page.h>
18 #include <asm/prom.h>
19 #include <asm/lmb.h>
20 #include <asm/abs_addr.h>
21 #include <asm/bitops.h>
22
23 struct lmb lmb __initdata;
24
25 static unsigned long __init
26 lmb_addrs_overlap(unsigned long base1, unsigned long size1,
27                   unsigned long base2, unsigned long size2)
28 {
29         return ((base1 < (base2+size2)) && (base2 < (base1+size1)));
30 }
31
32 static long __init
33 lmb_addrs_adjacent(unsigned long base1, unsigned long size1,
34                    unsigned long base2, unsigned long size2)
35 {
36         if (base2 == base1 + size1)
37                 return 1;
38         else if (base1 == base2 + size2)
39                 return -1;
40
41         return 0;
42 }
43
44 static long __init
45 lmb_regions_adjacent(struct lmb_region *rgn, unsigned long r1, unsigned long r2)
46 {
47         unsigned long base1 = rgn->region[r1].base;
48         unsigned long size1 = rgn->region[r1].size;
49         unsigned long base2 = rgn->region[r2].base;
50         unsigned long size2 = rgn->region[r2].size;
51
52         return lmb_addrs_adjacent(base1, size1, base2, size2);
53 }
54
55 /* Assumption: base addr of region 1 < base addr of region 2 */
56 static void __init
57 lmb_coalesce_regions(struct lmb_region *rgn, unsigned long r1, unsigned long r2)
58 {
59         unsigned long i;
60
61         rgn->region[r1].size += rgn->region[r2].size;
62         for (i=r2; i < rgn->cnt-1; i++) {
63                 rgn->region[i].base = rgn->region[i+1].base;
64                 rgn->region[i].physbase = rgn->region[i+1].physbase;
65                 rgn->region[i].size = rgn->region[i+1].size;
66         }
67         rgn->cnt--;
68 }
69
70 /* This routine called with relocation disabled. */
71 void __init
72 lmb_init(void)
73 {
74         unsigned long offset = reloc_offset();
75         struct lmb *_lmb = PTRRELOC(&lmb);
76
77         /* Create a dummy zero size LMB which will get coalesced away later.
78          * This simplifies the lmb_add() code below...
79          */
80         _lmb->memory.region[0].base = 0;
81         _lmb->memory.region[0].size = 0;
82         _lmb->memory.cnt = 1;
83
84         /* Ditto. */
85         _lmb->reserved.region[0].base = 0;
86         _lmb->reserved.region[0].size = 0;
87         _lmb->reserved.cnt = 1;
88 }
89
90 /* This routine called with relocation disabled. */
91 void __init
92 lmb_analyze(void)
93 {
94         unsigned long i;
95         unsigned long mem_size = 0;
96         unsigned long size_mask = 0;
97         unsigned long offset = reloc_offset();
98         struct lmb *_lmb = PTRRELOC(&lmb);
99 #ifdef CONFIG_MSCHUNKS
100         unsigned long physbase = 0;
101 #endif
102
103         for (i=0; i < _lmb->memory.cnt; i++) {
104                 unsigned long lmb_size;
105
106                 lmb_size = _lmb->memory.region[i].size;
107
108 #ifdef CONFIG_MSCHUNKS
109                 _lmb->memory.region[i].physbase = physbase;
110                 physbase += lmb_size;
111 #else
112                 _lmb->memory.region[i].physbase = _lmb->memory.region[i].base;
113 #endif
114                 mem_size += lmb_size;
115                 size_mask |= lmb_size;
116         }
117
118         _lmb->memory.size = mem_size;
119 }
120
121 /* This routine called with relocation disabled. */
122 static long __init
123 lmb_add_region(struct lmb_region *rgn, unsigned long base, unsigned long size)
124 {
125         unsigned long i, coalesced = 0;
126         long adjacent;
127
128         /* First try and coalesce this LMB with another. */
129         for (i=0; i < rgn->cnt; i++) {
130                 unsigned long rgnbase = rgn->region[i].base;
131                 unsigned long rgnsize = rgn->region[i].size;
132
133                 adjacent = lmb_addrs_adjacent(base,size,rgnbase,rgnsize);
134                 if ( adjacent > 0 ) {
135                         rgn->region[i].base -= size;
136                         rgn->region[i].physbase -= size;
137                         rgn->region[i].size += size;
138                         coalesced++;
139                         break;
140                 }
141                 else if ( adjacent < 0 ) {
142                         rgn->region[i].size += size;
143                         coalesced++;
144                         break;
145                 }
146         }
147
148         if ((i < rgn->cnt-1) && lmb_regions_adjacent(rgn, i, i+1) ) {
149                 lmb_coalesce_regions(rgn, i, i+1);
150                 coalesced++;
151         }
152
153         if ( coalesced ) {
154                 return coalesced;
155         } else if ( rgn->cnt >= MAX_LMB_REGIONS ) {
156                 return -1;
157         }
158
159         /* Couldn't coalesce the LMB, so add it to the sorted table. */
160         for (i=rgn->cnt-1; i >= 0; i--) {
161                 if (base < rgn->region[i].base) {
162                         rgn->region[i+1].base = rgn->region[i].base;
163                         rgn->region[i+1].physbase = rgn->region[i].physbase;
164                         rgn->region[i+1].size = rgn->region[i].size;
165                 }  else {
166                         rgn->region[i+1].base = base;
167                         rgn->region[i+1].physbase = lmb_abs_to_phys(base);
168                         rgn->region[i+1].size = size;
169                         break;
170                 }
171         }
172         rgn->cnt++;
173
174         return 0;
175 }
176
177 /* This routine called with relocation disabled. */
178 long __init
179 lmb_add(unsigned long base, unsigned long size)
180 {
181         unsigned long offset = reloc_offset();
182         struct lmb *_lmb = PTRRELOC(&lmb);
183         struct lmb_region *_rgn = &(_lmb->memory);
184
185         /* On pSeries LPAR systems, the first LMB is our RMO region. */
186         if ( base == 0 )
187                 _lmb->rmo_size = size;
188
189         return lmb_add_region(_rgn, base, size);
190
191 }
192
193 long __init
194 lmb_reserve(unsigned long base, unsigned long size)
195 {
196         unsigned long offset = reloc_offset();
197         struct lmb *_lmb = PTRRELOC(&lmb);
198         struct lmb_region *_rgn = &(_lmb->reserved);
199
200         return lmb_add_region(_rgn, base, size);
201 }
202
203 long __init
204 lmb_overlaps_region(struct lmb_region *rgn, unsigned long base, unsigned long size)
205 {
206         unsigned long i;
207
208         for (i=0; i < rgn->cnt; i++) {
209                 unsigned long rgnbase = rgn->region[i].base;
210                 unsigned long rgnsize = rgn->region[i].size;
211                 if ( lmb_addrs_overlap(base,size,rgnbase,rgnsize) ) {
212                         break;
213                 }
214         }
215
216         return (i < rgn->cnt) ? i : -1;
217 }
218
219 unsigned long __init
220 lmb_alloc(unsigned long size, unsigned long align)
221 {
222         return lmb_alloc_base(size, align, LMB_ALLOC_ANYWHERE);
223 }
224
225 unsigned long __init
226 lmb_alloc_base(unsigned long size, unsigned long align, unsigned long max_addr)
227 {
228         long i, j;
229         unsigned long base = 0;
230         unsigned long offset = reloc_offset();
231         struct lmb *_lmb = PTRRELOC(&lmb);
232         struct lmb_region *_mem = &(_lmb->memory);
233         struct lmb_region *_rsv = &(_lmb->reserved);
234
235         for (i=_mem->cnt-1; i >= 0; i--) {
236                 unsigned long lmbbase = _mem->region[i].base;
237                 unsigned long lmbsize = _mem->region[i].size;
238
239                 if ( max_addr == LMB_ALLOC_ANYWHERE )
240                         base = _ALIGN_DOWN(lmbbase+lmbsize-size, align);
241                 else if ( lmbbase < max_addr )
242                         base = _ALIGN_DOWN(min(lmbbase+lmbsize,max_addr)-size, align);
243                 else
244                         continue;
245
246                 while ( (lmbbase <= base) &&
247                         ((j = lmb_overlaps_region(_rsv,base,size)) >= 0) ) {
248                         base = _ALIGN_DOWN(_rsv->region[j].base-size, align);
249                 }
250
251                 if ( (base != 0) && (lmbbase <= base) )
252                         break;
253         }
254
255         if ( i < 0 )
256                 return 0;
257
258         lmb_add_region(_rsv, base, size);
259
260         return base;
261 }
262
263 unsigned long __init
264 lmb_phys_mem_size(void)
265 {
266         unsigned long offset = reloc_offset();
267         struct lmb *_lmb = PTRRELOC(&lmb);
268 #ifdef CONFIG_MSCHUNKS
269         return _lmb->memory.size;
270 #else
271         struct lmb_region *_mem = &(_lmb->memory);
272         unsigned long total = 0;
273         int i;
274
275         /* add all physical memory to the bootmem map */
276         for (i=0; i < _mem->cnt; i++)
277                 total += _mem->region[i].size;
278         return total;
279 #endif /* CONFIG_MSCHUNKS */
280 }
281
282 unsigned long __init
283 lmb_end_of_DRAM(void)
284 {
285         unsigned long offset = reloc_offset();
286         struct lmb *_lmb = PTRRELOC(&lmb);
287         struct lmb_region *_mem = &(_lmb->memory);
288         int idx = _mem->cnt - 1;
289
290 #ifdef CONFIG_MSCHUNKS
291         return (_mem->region[idx].physbase + _mem->region[idx].size);
292 #else
293         return (_mem->region[idx].base + _mem->region[idx].size);
294 #endif /* CONFIG_MSCHUNKS */
295
296         return 0;
297 }
298
299 unsigned long __init
300 lmb_abs_to_phys(unsigned long aa)
301 {
302         unsigned long i, pa = aa;
303         unsigned long offset = reloc_offset();
304         struct lmb *_lmb = PTRRELOC(&lmb);
305         struct lmb_region *_mem = &(_lmb->memory);
306
307         for (i=0; i < _mem->cnt; i++) {
308                 unsigned long lmbbase = _mem->region[i].base;
309                 unsigned long lmbsize = _mem->region[i].size;
310                 if ( lmb_addrs_overlap(aa,1,lmbbase,lmbsize) ) {
311                         pa = _mem->region[i].physbase + (aa - lmbbase);
312                         break;
313                 }
314         }
315
316         return pa;
317 }