Subversion Repositories Kolibri OS

Rev

Rev 4619 | Rev 5351 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2288 clevermous 1
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2
;;                                                              ;;
4608 clevermous 3
;; Copyright (C) KolibriOS team 2004-2014. All rights reserved. ;;
2288 clevermous 4
;; Distributed under terms of the GNU General Public License    ;;
5
;;                                                              ;;
6
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
7
 
8
$Revision: 5130 $
9
 
4608 clevermous 10
; Initializes MTRRs.
11
proc init_mtrr
2288 clevermous 12
 
4608 clevermous 13
        cmp     [BOOT_VARS+BOOT_MTRR], byte 2
14
        je      .exit
2288 clevermous 15
 
4608 clevermous 16
        bt      [cpu_caps], CAPS_MTRR
17
        jnc     .exit
2288 clevermous 18
 
4608 clevermous 19
        call    mtrr_reconfigure
20
        stdcall set_mtrr, [LFBAddress], 0x1000000, MEM_WC
2288 clevermous 21
 
4608 clevermous 22
.exit:
2288 clevermous 23
        ret
24
endp
25
 
4608 clevermous 26
; Helper procedure for mtrr_reconfigure and set_mtrr,
27
; called before changes in MTRRs.
28
proc mtrr_begin_change
29
        mov     eax, cr0
30
        or      eax, 0x60000000 ;disable caching
31
        mov     cr0, eax
32
        wbinvd                  ;invalidate cache
2288 clevermous 33
        ret
34
endp
35
 
4608 clevermous 36
; Helper procedure for mtrr_reconfigure and set_mtrr,
37
; called after changes in MTRRs.
38
proc mtrr_end_change
39
        wbinvd                  ;again invalidate
40
        mov     eax, cr0
41
        and     eax, not 0x60000000
42
        mov     cr0, eax        ; enable caching
2288 clevermous 43
        ret
44
endp
45
 
4608 clevermous 46
; Some limits to number of structures located in the stack.
47
MAX_USEFUL_MTRRS = 16
48
MAX_RANGES = 16
2288 clevermous 49
 
4608 clevermous 50
; mtrr_reconfigure keeps a list of MEM_WB ranges.
51
; This structure describes one item in the list.
52
struct mtrr_range
53
next            dd      ?       ; next item
54
start           dq      ?       ; first byte
55
length          dq      ?       ; length in bytes
56
ends
2288 clevermous 57
 
4608 clevermous 58
uglobal
2288 clevermous 59
align 4
4608 clevermous 60
num_variable_mtrrs      dd      0       ; number of variable-range MTRRs
61
endg
2288 clevermous 62
 
4608 clevermous 63
; Helper procedure for MTRR initialization.
64
; Takes MTRR configured by BIOS and tries to recongifure them
65
; in order to allow non-UC data at top of 4G memory.
66
; Example: if low part of physical memory is 3.5G = 0xE0000000 bytes wide,
67
; BIOS can configure two MTRRs so that the first MTRR describes [0, 4G) as WB
68
; and the second MTRR describes [3.5G, 4G) as UC;
69
; WB+UC=UC, so the resulting memory map would be as needed,
70
; but in this configuration our attempts to map LFB at (say) 0xE8000000 as WC
71
; would be ignored, WB+UC+WC is still UC.
72
; So we must keep top of 4G memory not covered by MTRRs,
73
; using three WB MTRRs [0,2G) + [2G,3G) + [3G,3.5G),
74
; this gives the same memory map, but allows to add further entries.
75
; See mtrrtest.asm for detailed input/output from real hardware+BIOS.
76
proc mtrr_reconfigure
77
        push    ebp     ; we're called from init_LFB, and it feels hurt when ebp is destroyed
78
; 1. Prepare local variables.
79
; 1a. Create list of MAX_RANGES free (aka not yet allocated) ranges.
2288 clevermous 80
        xor     eax, eax
4608 clevermous 81
        lea     ecx, [eax+MAX_RANGES]
82
.init_ranges:
83
        sub     esp, sizeof.mtrr_range - 4
84
        push    eax
85
        mov     eax, esp
86
        dec     ecx
87
        jnz     .init_ranges
88
        mov     eax, esp
89
; 1b. Fill individual local variables.
90
        xor     edx, edx
91
        sub     esp, MAX_USEFUL_MTRRS * 16      ; .mtrrs
92
        push    edx             ; .mtrrs_end
93
        push    edx             ; .num_used_mtrrs
94
        push    eax             ; .first_free_range
95
        push    edx             ; .first_range: no ranges yet
96
        mov     cl, [cpu_phys_addr_width]
97
        or      eax, -1
98
        shl     eax, cl ; note: this uses cl&31 = cl-32, not the entire cl
99
        push    eax     ; .phys_reserved_mask
100
virtual at esp
101
.phys_reserved_mask     dd      ?
102
.first_range            dd      ?
103
.first_free_range       dd      ?
104
.num_used_mtrrs         dd      ?
105
.mtrrs_end              dd      ?
106
.mtrrs          rq      MAX_USEFUL_MTRRS * 2
107
.local_vars_size = $ - esp
108
end virtual
2288 clevermous 109
 
4608 clevermous 110
; 2. Get the number of variable-range MTRRs from MTRRCAP register.
111
; Abort if zero.
112
        mov     ecx, 0xFE
113
        rdmsr
114
        test    al, al
115
        jz      .abort
116
        mov     byte [num_variable_mtrrs], al
117
; 3. Validate MTRR_DEF_TYPE register.
118
        mov     ecx, 0x2FF
119
        rdmsr
120
; If BIOS has not initialized variable-range MTRRs, fallback to step 7.
121
        test    ah, 8
122
        jz      .fill_ranges_from_memory_map
123
; If the default memory type (not covered by MTRRs) is not UC,
124
; then probably BIOS did something strange, so it is better to exit immediately
125
; hoping for the best.
126
        cmp     al, MEM_UC
127
        jnz     .abort
128
; 4. Validate all variable-range MTRRs
129
; and copy configured MTRRs to the local array [.mtrrs].
130
; 4a. Prepare for the loop over existing variable-range MTRRs.
131
        mov     ecx, 0x200
132
        lea     edi, [.mtrrs]
133
.get_used_mtrrs_loop:
134
; 4b. For every MTRR, read PHYSBASEn and PHYSMASKn.
135
; In PHYSBASEn, clear upper bits and copy to ebp:ebx.
136
        rdmsr
137
        or      edx, [.phys_reserved_mask]
138
        xor     edx, [.phys_reserved_mask]
139
        mov     ebp, edx
2288 clevermous 140
        mov     ebx, eax
4608 clevermous 141
        inc     ecx
142
; If PHYSMASKn is not active, ignore this MTRR.
143
        rdmsr
144
        inc     ecx
145
        test    ah, 8
146
        jz      .get_used_mtrrs_next
147
; 4c. For every active MTRR, check that number of local entries is not too large.
148
        inc     [.num_used_mtrrs]
149
        cmp     [.num_used_mtrrs], MAX_USEFUL_MTRRS
150
        ja      .abort
151
; 4d. For every active MTRR, store PHYSBASEn with upper bits cleared.
152
; This contains the MTRR base and the memory type in low byte.
153
        mov     [edi], ebx
154
        mov     [edi+4], ebp
155
; 4e. For every active MTRR, check that the range is continuous:
156
; PHYSMASKn with upper bits set must be negated power of two, and
157
; low bits of PHYSBASEn must be zeroes:
158
; PHYSMASKn = 1...10...0,
159
; PHYSBASEn = x...x0...0,
160
; this defines a continuous range from x...x0...0 to x...x1...1,
161
; length = 10...0 = negated PHYSMASKn.
162
; Store length in the local array.
163
        and     eax, not 0xFFF
164
        or      edx, [.phys_reserved_mask]
165
        mov     dword [edi+8], 0
166
        mov     dword [edi+12], 0
167
        sub     [edi+8], eax
168
        sbb     [edi+12], edx
169
; (x and -x) is the maximum power of two that divides x.
170
; Condition for powers of two: (x and -x) equals x.
171
        and     eax, [edi+8]
172
        and     edx, [edi+12]
173
        cmp     eax, [edi+8]
174
        jnz     .abort
175
        cmp     edx, [edi+12]
176
        jnz     .abort
177
        sub     eax, 1
178
        sbb     edx, 0
179
        and     eax, not 0xFFF
180
        and     eax, ebx
181
        jnz     .abort
182
        and     edx, ebp
183
        jnz     .abort
184
; 4f. For every active MTRR, validate memory type: it must be either WB or UC.
185
        add     edi, 16
186
        cmp     bl, MEM_UC
187
        jz      .get_used_mtrrs_next
188
        cmp     bl, MEM_WB
189
        jnz     .abort
190
.get_used_mtrrs_next:
191
; 4g. Repeat the loop at 4b-4f for all [num_variable_mtrrs] entries.
192
        mov     eax, [num_variable_mtrrs]
193
        lea     eax, [0x200+eax*2]
194
        cmp     ecx, eax
195
        jb      .get_used_mtrrs_loop
196
; 4h. If no active MTRRs were detected, fallback to step 7.
197
        cmp     [.num_used_mtrrs], 0
198
        jz      .fill_ranges_from_memory_map
199
        mov     [.mtrrs_end], edi
200
; 5. Generate sorted list of ranges marked as WB.
201
; 5a. Prepare for the loop over configured MTRRs filled at step 4.
202
        lea     ecx, [.mtrrs]
203
.fill_wb_ranges:
204
; 5b. Ignore non-WB MTRRs.
205
        mov     ebx, [ecx]
206
        cmp     bl, MEM_WB
207
        jnz     .next_wb_range
208
        mov     ebp, [ecx+4]
209
        and     ebx, not 0xFFF  ; clear memory type and reserved bits
210
; ebp:ebx = start of the range described by the current MTRR.
211
; 5c. Find the first existing range containing a point greater than ebp:ebx.
212
        lea     esi, [.first_range]
213
.find_range_wb:
214
; If there is no next range or start of the next range is greater than ebp:ebx,
215
; exit the loop to 5d.
216
        mov     edi, [esi]
217
        test    edi, edi
218
        jz      .found_place_wb
219
        mov     eax, ebx
220
        mov     edx, ebp
221
        sub     eax, dword [edi+mtrr_range.start]
222
        sbb     edx, dword [edi+mtrr_range.start+4]
223
        jb      .found_place_wb
224
; Otherwise, if end of the next range is greater than or equal to ebp:ebx,
225
; exit the loop to 5e.
226
        mov     esi, edi
227
        sub     eax, dword [edi+mtrr_range.length]
228
        sbb     edx, dword [edi+mtrr_range.length+4]
229
        jb      .expand_wb
230
        or      eax, edx
231
        jnz     .find_range_wb
232
        jmp     .expand_wb
233
.found_place_wb:
234
; 5d. ebp:ebx is not within any existing range.
235
; Insert a new range between esi and edi.
236
; (Later, during 5e, it can be merged with the following ranges.)
237
        mov     eax, [.first_free_range]
238
        test    eax, eax
239
        jz      .abort
240
        mov     [esi], eax
241
        mov     edx, [eax+mtrr_range.next]
242
        mov     [.first_free_range], edx
243
        mov     dword [eax+mtrr_range.start], ebx
244
        mov     dword [eax+mtrr_range.start+4], ebp
245
; Don't fill [eax+mtrr_range.next] and [eax+mtrr_range.length] yet,
246
; they will be calculated including merges at step 5e.
247
        mov     esi, edi
2288 clevermous 248
        mov     edi, eax
4608 clevermous 249
.expand_wb:
250
; 5e. The range at edi contains ebp:ebx, and esi points to the first range
251
; to be checked for merge: esi=edi if ebp:ebx was found in an existing range,
252
; esi is next after edi if a new range with ebp:ebx was created.
253
; Merge it with following ranges while start of the next range is not greater
254
; than the end of the new range.
255
        add     ebx, [ecx+8]
256
        adc     ebp, [ecx+12]
257
; ebp:ebx = end of the range described by the current MTRR.
258
.expand_wb_loop:
259
; If there is no next range or start of the next range is greater than ebp:ebx,
260
; exit the loop to 5g.
261
        test    esi, esi
262
        jz      .expand_wb_done
263
        mov     eax, ebx
264
        mov     edx, ebp
265
        sub     eax, dword [esi+mtrr_range.start]
266
        sbb     edx, dword [esi+mtrr_range.start+4]
267
        jb      .expand_wb_done
268
; Otherwise, if end of the next range is greater than or equal to ebp:ebx,
269
; exit the loop to 5f.
270
        sub     eax, dword [esi+mtrr_range.length]
271
        sbb     edx, dword [esi+mtrr_range.length+4]
272
        jb      .expand_wb_last
273
; Otherwise, the current range is completely within the new range.
274
; Free it and continue the loop.
275
        mov     edx, [esi+mtrr_range.next]
276
        cmp     esi, edi
277
        jz      @f
278
        mov     eax, [.first_free_range]
279
        mov     [esi+mtrr_range.next], eax
280
        mov     [.first_free_range], esi
2288 clevermous 281
@@:
4608 clevermous 282
        mov     esi, edx
283
        jmp     .expand_wb_loop
284
.expand_wb_last:
285
; 5f. Start of the new range is inside range described by esi,
286
; end of the new range is inside range described by edi.
287
; If esi is equal to edi, the new range is completely within
288
; an existing range, so proceed to the next range.
289
        cmp     esi, edi
290
        jz      .next_wb_range
291
; Otherwise, set end of interval at esi to end of interval at edi
292
; and free range described by edi.
293
        mov     ebx, dword [esi+mtrr_range.start]
294
        mov     ebp, dword [esi+mtrr_range.start+4]
295
        add     ebx, dword [esi+mtrr_range.length]
296
        adc     ebp, dword [esi+mtrr_range.length+4]
297
        mov     edx, [esi+mtrr_range.next]
298
        mov     eax, [.first_free_range]
299
        mov     [esi+mtrr_range.next], eax
300
        mov     [.first_free_range], esi
301
        mov     esi, edx
302
.expand_wb_done:
303
; 5g. We have found the next range (maybe 0) after merging and
304
; the new end of range (maybe ebp:ebx from the new range
305
; or end of another existing interval calculated at step 5f).
306
; Write them to range at edi.
307
        mov     [edi+mtrr_range.next], esi
308
        sub     ebx, dword [edi+mtrr_range.start]
309
        sbb     ebp, dword [edi+mtrr_range.start+4]
310
        mov     dword [edi+mtrr_range.length], ebx
311
        mov     dword [edi+mtrr_range.length+4], ebp
312
.next_wb_range:
313
; 5h. Continue the loop 5b-5g over all configured MTRRs.
314
        add     ecx, 16
315
        cmp     ecx, [.mtrrs_end]
316
        jb      .fill_wb_ranges
317
; 6. Exclude all ranges marked as UC.
318
; 6a. Prepare for the loop over configured MTRRs filled at step 4.
319
        lea     ecx, [.mtrrs]
320
.fill_uc_ranges:
321
; 6b. Ignore non-UC MTRRs.
322
        mov     ebx, [ecx]
323
        cmp     bl, MEM_UC
324
        jnz     .next_uc_range
325
        mov     ebp, [ecx+4]
326
        and     ebx, not 0xFFF  ; clear memory type and reserved bits
327
; ebp:ebx = start of the range described by the current MTRR.
328
        lea     esi, [.first_range]
329
; 6c. Find the first existing range containing a point greater than ebp:ebx.
330
.find_range_uc:
331
; If there is no next range, ignore this MTRR,
332
; exit the loop and continue to next MTRR.
333
        mov     edi, [esi]
334
        test    edi, edi
335
        jz      .next_uc_range
336
; If start of the next range is greater than or equal to ebp:ebx,
337
; exit the loop to 6e.
338
        mov     eax, dword [edi+mtrr_range.start]
339
        mov     edx, dword [edi+mtrr_range.start+4]
340
        sub     eax, ebx
341
        sbb     edx, ebp
342
        jnb     .truncate_uc
343
; Otherwise, continue the loop if end of the next range is less than ebp:ebx,
344
; exit the loop to 6d otherwise.
345
        mov     esi, edi
346
        add     eax, dword [edi+mtrr_range.length]
347
        adc     edx, dword [edi+mtrr_range.length+4]
348
        jnb     .find_range_uc
349
; 6d. ebp:ebx is inside (or at end of) an existing range.
350
; Split the range. (The second range, maybe containing completely within UC-range,
351
; maybe of zero length, can be removed at step 6e, if needed.)
352
        mov     edi, [.first_free_range]
353
        test    edi, edi
354
        jz      .abort
355
        mov     dword [edi+mtrr_range.start], ebx
356
        mov     dword [edi+mtrr_range.start+4], ebp
357
        mov     dword [edi+mtrr_range.length], eax
358
        mov     dword [edi+mtrr_range.length+4], edx
359
        mov     eax, [edi+mtrr_range.next]
360
        mov     [.first_free_range], eax
361
        mov     eax, [esi+mtrr_range.next]
362
        mov     [edi+mtrr_range.next], eax
363
; don't change [esi+mtrr_range.next] yet, it will be filled at step 6e
364
        mov     eax, ebx
365
        mov     edx, ebp
366
        sub     eax, dword [esi+mtrr_range.start]
367
        sbb     edx, dword [esi+mtrr_range.start+4]
368
        mov     dword [esi+mtrr_range.length], eax
369
        mov     dword [esi+mtrr_range.length+4], edx
370
.truncate_uc:
371
; 6e. edi is the first range after ebp:ebx, check it and next ranges
372
; for intersection with the new range, truncate heads.
373
        add     ebx, [ecx+8]
374
        adc     ebp, [ecx+12]
375
; ebp:ebx = end of the range described by the current MTRR.
376
.truncate_uc_loop:
377
; If start of the next range is greater than ebp:ebx,
378
; exit the loop to 6g.
379
        mov     eax, ebx
380
        mov     edx, ebp
381
        sub     eax, dword [edi+mtrr_range.start]
382
        sbb     edx, dword [edi+mtrr_range.start+4]
383
        jb      .truncate_uc_done
384
; Otherwise, if end of the next range is greater than ebp:ebx,
385
; exit the loop to 6f.
386
        sub     eax, dword [edi+mtrr_range.length]
387
        sbb     edx, dword [edi+mtrr_range.length+4]
388
        jb      .truncate_uc_last
389
; Otherwise, the current range is completely within the new range.
390
; Free it and continue the loop if there is a next range.
391
; If that was a last range, exit the loop to 6g.
392
        mov     edx, [edi+mtrr_range.next]
393
        mov     eax, [.first_free_range]
394
        mov     [.first_free_range], edi
395
        mov     [edi+mtrr_range.next], eax
396
        mov     edi, edx
397
        test    edi, edi
398
        jnz     .truncate_uc_loop
399
        jmp     .truncate_uc_done
400
.truncate_uc_last:
401
; 6f. The range at edi partially intersects with the UC-range described by MTRR.
402
; Truncate it from the head.
403
        mov     dword [edi+mtrr_range.start], ebx
404
        mov     dword [edi+mtrr_range.start+4], ebp
405
        neg     eax
406
        adc     edx, 0
407
        neg     edx
408
        mov     dword [edi+mtrr_range.length], eax
409
        mov     dword [edi+mtrr_range.length+4], edx
410
.truncate_uc_done:
411
; 6g. We have found the next range (maybe 0) after intersection.
412
; Write it to [esi+mtrr_range.next].
413
        mov     [esi+mtrr_range.next], edi
414
.next_uc_range:
415
; 6h. Continue the loop 6b-6g over all configured MTRRs.
416
        add     ecx, 16
417
        cmp     ecx, [.mtrrs_end]
418
        jb      .fill_uc_ranges
419
; Sanity check: if there are no ranges after steps 5-6,
420
; fallback to step 7. Otherwise, go to 8.
421
        cmp     [.first_range], 0
422
        jnz     .ranges_ok
423
.fill_ranges_from_memory_map:
424
; 7. BIOS has not configured variable-range MTRRs.
425
; Create one range from 0 to [MEM_AMOUNT].
426
        mov     eax, [.first_free_range]
427
        mov     edx, [eax+mtrr_range.next]
428
        mov     [.first_free_range], edx
429
        mov     [.first_range], eax
430
        xor     edx, edx
431
        mov     [eax+mtrr_range.next], edx
432
        mov     dword [eax+mtrr_range.start], edx
433
        mov     dword [eax+mtrr_range.start+4], edx
434
        mov     ecx, [MEM_AMOUNT]
435
        mov     dword [eax+mtrr_range.length], ecx
436
        mov     dword [eax+mtrr_range.length+4], edx
437
.ranges_ok:
438
; 8. We have calculated list of WB-ranges.
439
; Now we should calculate a list of MTRRs so that
440
; * every MTRR describes a range with length = power of 2 and start that is aligned,
441
; * every MTRR can be WB or UC
442
; * (sum of all WB ranges) minus (sum of all UC ranges) equals the calculated list
443
; * top of 4G memory must not be covered by any ranges
444
; Example: range [0,0xBC000000) can be converted to
445
; [0,0x80000000)+[0x80000000,0xC0000000)-[0xBC000000,0xC0000000)
446
; WB            +WB                     -UC
447
; but not to [0,0x100000000)-[0xC0000000,0x100000000)-[0xBC000000,0xC0000000).
448
; 8a. Check that list of ranges is [0,something) plus, optionally, [4G,something).
449
; This holds in practice (see mtrrtest.asm for real-life examples)
450
; and significantly simplifies the code: ranges are independent, start of range
451
; is almost always aligned (the only exception >4G upper memory can be easily covered),
452
; there is no need to consider adding holes before start of range, only
453
; append them to end of range.
2288 clevermous 454
        xor     eax, eax
4608 clevermous 455
        mov     edi, [.first_range]
456
        cmp     dword [edi+mtrr_range.start], eax
457
        jnz     .abort
458
        cmp     dword [edi+mtrr_range.start+4], eax
459
        jnz     .abort
460
        cmp     dword [edi+mtrr_range.length+4], eax
461
        jnz     .abort
462
        mov     edx, [edi+mtrr_range.next]
463
        test    edx, edx
464
        jz      @f
465
        cmp     dword [edx+mtrr_range.start], eax
466
        jnz     .abort
467
        cmp     dword [edx+mtrr_range.start+4], 1
468
        jnz     .abort
469
        cmp     [edx+mtrr_range.next], eax
470
        jnz     .abort
3166 clevermous 471
@@:
4608 clevermous 472
; 8b. Initialize: no MTRRs filled.
473
        mov     [.num_used_mtrrs], eax
474
        lea     esi, [.mtrrs]
475
.range2mtrr_loop:
476
; 8c. If we are dealing with upper-memory range (after 4G)
477
; with length > start, create one WB MTRR with [start,2*start),
478
; reset start to 2*start and return to this step.
479
; Example: [4G,24G) -> [4G,8G) {returning} + [8G,16G) {returning}
480
; + [16G,24G) {advancing to ?}.
481
        mov     eax, dword [edi+mtrr_range.length+4]
2288 clevermous 482
        test    eax, eax
4608 clevermous 483
        jz      .less4G
484
        mov     edx, dword [edi+mtrr_range.start+4]
485
        cmp     eax, edx
486
        jb      .start_aligned
487
        inc     [.num_used_mtrrs]
488
        cmp     [.num_used_mtrrs], MAX_USEFUL_MTRRS
489
        ja      .abort
490
        mov     dword [esi], MEM_WB
491
        mov     dword [esi+4], edx
492
        mov     dword [esi+8], 0
493
        mov     dword [esi+12], edx
494
        add     esi, 16
495
        add     dword [edi+mtrr_range.start+4], edx
496
        sub     dword [edi+mtrr_range.length+4], edx
497
        jnz     .range2mtrr_loop
498
        cmp     dword [edi+mtrr_range.length], 0
499
        jz      .range2mtrr_next
500
.less4G:
501
; 8d. If we are dealing with low-memory range (before 4G)
502
; and appending a maximal-size hole would create a range covering top of 4G,
503
; create a maximal-size WB range and return to this step.
504
; Example: for [0,0xBC000000) the following steps would consider
505
; variants [0,0x80000000)+(another range to be splitted) and
506
; [0,0x100000000)-(another range to be splitted); we forbid the last variant,
507
; so the first variant must be used.
508
        bsr     ecx, dword [edi+mtrr_range.length]
509
        xor     edx, edx
510
        inc     edx
511
        shl     edx, cl
512
        lea     eax, [edx*2]
513
        add     eax, dword [edi+mtrr_range.start]
514
        jnz     .start_aligned
515
        inc     [.num_used_mtrrs]
516
        cmp     [.num_used_mtrrs], MAX_USEFUL_MTRRS
517
        ja      .abort
518
        mov     eax, dword [edi+mtrr_range.start]
519
        mov     dword [esi], eax
520
        or      dword [esi], MEM_WB
521
        mov     dword [esi+4], 0
522
        mov     dword [esi+8], edx
523
        mov     dword [esi+12], 0
524
        add     esi, 16
525
        add     dword [edi+mtrr_range.start], edx
526
        sub     dword [edi+mtrr_range.length], edx
527
        jnz     .less4G
528
        jmp     .range2mtrr_next
529
.start_aligned:
530
; Start is aligned for any allowed length, maximum-size hole is allowed.
531
; Select the best MTRR configuration for one range.
532
; length=...101101
533
; Without hole at the end, we need one WB MTRR for every 1-bit in length:
534
; length=...100000 + ...001000 + ...000100 + ...000001
535
; We can also append one hole at the end so that one 0-bit (selected by us)
536
; becomes 1 and all lower bits become 0 for WB-range:
537
; length=...110000 - (...00010 + ...00001)
538
; In this way, we need one WB MTRR for every 1-bit higher than the selected bit,
539
; one WB MTRR for the selected bit, one UC MTRR for every 0-bit between
540
; the selected bit and lowest 1-bit (they become 1-bits after negation)
541
; and one UC MTRR for lowest 1-bit.
542
; So we need to select 0-bit with the maximal difference
543
; (number of 0-bits) - (number of 1-bits) between selected and lowest 1-bit,
544
; this equals the gain from using a hole. If the difference is negative for
545
; all 0-bits, don't append hole.
546
; Note that lowest 1-bit is not included when counting, but selected 0-bit is.
547
; 8e. Find the optimal bit position for hole.
548
; eax = current difference, ebx = best difference,
549
; ecx = hole bit position, edx = current bit position.
2288 clevermous 550
        xor     eax, eax
4608 clevermous 551
        xor     ebx, ebx
552
        xor     ecx, ecx
553
        bsf     edx, dword [edi+mtrr_range.length]
554
        jnz     @f
555
        bsf     edx, dword [edi+mtrr_range.length+4]
556
        add     edx, 32
2288 clevermous 557
@@:
4608 clevermous 558
        push    edx     ; save position of lowest 1-bit for step 8f
559
.calc_stat:
2288 clevermous 560
        inc     edx
4608 clevermous 561
        cmp     edx, 64
562
        jae     .stat_done
563
        inc     eax     ; increment difference in hope for 1-bit
564
; Note: bt conveniently works with both .length and .length+4,
565
; depending on whether edx>=32.
566
        bt      dword [edi+mtrr_range.length], edx
567
        jc      .calc_stat
568
        dec     eax     ; hope was wrong, decrement difference to correct 'inc'
569
        dec     eax     ; and again, now getting the real difference
570
        cmp     eax, ebx
571
        jle     .calc_stat
572
        mov     ebx, eax
573
        mov     ecx, edx
574
        jmp     .calc_stat
575
.stat_done:
576
; 8f. If we decided to create a hole, flip all bits between lowest and selected.
577
        pop     edx     ; restore position of lowest 1-bit saved at step 8e
578
        test    ecx, ecx
579
        jz      .fill_hi_init
2288 clevermous 580
@@:
581
        inc     edx
4608 clevermous 582
        cmp     edx, ecx
583
        ja      .fill_hi_init
584
        btc     dword [edi+mtrr_range.length], edx
585
        jmp     @b
586
.fill_hi_init:
587
; 8g. Create MTRR ranges corresponding to upper 32 bits.
588
        sub     ecx, 32
589
.fill_hi_loop:
590
        bsr     edx, dword [edi+mtrr_range.length+4]
591
        jz      .fill_hi_done
592
        inc     [.num_used_mtrrs]
593
        cmp     [.num_used_mtrrs], MAX_USEFUL_MTRRS
594
        ja      .abort
595
        mov     eax, dword [edi+mtrr_range.start]
596
        mov     [esi], eax
597
        mov     eax, dword [edi+mtrr_range.start+4]
598
        mov     [esi+4], eax
599
        xor     eax, eax
600
        mov     [esi+8], eax
601
        bts     eax, edx
602
        mov     [esi+12], eax
603
        cmp     edx, ecx
604
        jl      .fill_hi_uc
605
        or      dword [esi], MEM_WB
606
        add     dword [edi+mtrr_range.start+4], eax
607
        jmp     @f
608
.fill_hi_uc:
609
        sub     dword [esi+4], eax
610
        sub     dword [edi+mtrr_range.start+4], eax
2288 clevermous 611
@@:
4608 clevermous 612
        add     esi, 16
613
        sub     dword [edi+mtrr_range.length], eax
614
        jmp     .fill_hi_loop
615
.fill_hi_done:
616
; 8h. Create MTRR ranges corresponding to lower 32 bits.
617
        add     ecx, 32
618
.fill_lo_loop:
619
        bsr     edx, dword [edi+mtrr_range.length]
620
        jz      .range2mtrr_next
621
        inc     [.num_used_mtrrs]
622
        cmp     [.num_used_mtrrs], MAX_USEFUL_MTRRS
623
        ja      .abort
624
        mov     eax, dword [edi+mtrr_range.start]
625
        mov     [esi], eax
626
        mov     eax, dword [edi+mtrr_range.start+4]
627
        mov     [esi+4], eax
628
        xor     eax, eax
629
        mov     [esi+12], eax
630
        bts     eax, edx
631
        mov     [esi+8], eax
632
        cmp     edx, ecx
633
        jl      .fill_lo_uc
634
        or      dword [esi], MEM_WB
635
        add     dword [edi+mtrr_range.start], eax
636
        jmp     @f
637
.fill_lo_uc:
638
        sub     dword [esi], eax
639
        sub     dword [edi+mtrr_range.start], eax
2288 clevermous 640
@@:
4608 clevermous 641
        add     esi, 16
642
        sub     dword [edi+mtrr_range.length], eax
643
        jmp     .fill_lo_loop
644
.range2mtrr_next:
645
; 8i. Repeat the loop at 8c-8h for all ranges.
646
        mov     edi, [edi+mtrr_range.next]
2288 clevermous 647
        test    edi, edi
4608 clevermous 648
        jnz     .range2mtrr_loop
649
; 9. We have calculated needed MTRRs, now setup them in the CPU.
650
; 9a. Abort if number of MTRRs is too large.
651
        mov     eax, [num_variable_mtrrs]
652
        cmp     [.num_used_mtrrs], eax
653
        ja      .abort
2288 clevermous 654
 
4608 clevermous 655
; 9b. Prepare for changes.
656
        call    mtrr_begin_change
2288 clevermous 657
 
4608 clevermous 658
; 9c. Prepare for loop over MTRRs.
659
        lea     esi, [.mtrrs]
660
        mov     ecx, 0x200
2288 clevermous 661
@@:
4608 clevermous 662
; 9d. For every MTRR, copy PHYSBASEn as is: step 8 has configured
663
; start value and type bits as needed.
664
        mov     eax, [esi]
665
        mov     edx, [esi+4]
666
        wrmsr
667
        inc     ecx
668
; 9e. For every MTRR, calculate PHYSMASKn = -(length) or 0x800
669
; with upper bits cleared, 0x800 = MTRR is valid.
2288 clevermous 670
        xor     eax, eax
671
        xor     edx, edx
4608 clevermous 672
        sub     eax, [esi+8]
673
        sbb     edx, [esi+12]
674
        or      eax, 0x800
675
        or      edx, [.phys_reserved_mask]
676
        xor     edx, [.phys_reserved_mask]
2288 clevermous 677
        wrmsr
678
        inc     ecx
4608 clevermous 679
; 9f. Continue steps 9d and 9e for all MTRRs calculated at step 8.
680
        add     esi, 16
681
        dec     [.num_used_mtrrs]
682
        jnz     @b
683
; 9g. Zero other MTRRs.
2288 clevermous 684
        xor     eax, eax
685
        xor     edx, edx
4608 clevermous 686
        mov     ebx, [num_variable_mtrrs]
687
        lea     ebx, [0x200+ebx*2]
2288 clevermous 688
@@:
4608 clevermous 689
        cmp     ecx, ebx
690
        jae     @f
691
        wrmsr
4418 clevermous 692
        inc     ecx
2288 clevermous 693
        wrmsr
4608 clevermous 694
        inc     ecx
695
        jmp     @b
696
@@:
697
 
698
; 9i. Configure MTRR_DEF_TYPE.
699
        mov     ecx, 0x2FF
700
        rdmsr
701
        or      ah, 8   ; enable variable-ranges MTRR
2288 clevermous 702
        and     al, 0xF0; default memtype = UC
703
        wrmsr
704
 
4608 clevermous 705
; 9j. Changes are done.
706
        call    mtrr_end_change
2288 clevermous 707
 
4608 clevermous 708
.abort:
709
        add     esp, .local_vars_size + MAX_RANGES * sizeof.mtrr_range
710
        pop     ebp
2288 clevermous 711
        ret
712
endp
713
 
4608 clevermous 714
; Allocate&set one MTRR for given range.
715
; size must be power of 2 that divides base.
2288 clevermous 716
proc set_mtrr stdcall, base:dword,size:dword,mem_type:dword
717
; find unused register
718
        mov     ecx, 0x201
4608 clevermous 719
.scan:
2288 clevermous 720
        rdmsr
721
        dec     ecx
722
        test    ah, 8
723
        jz      .found
724
        rdmsr
4608 clevermous 725
        test    edx, edx
726
        jnz     @f
727
        and     eax, not 0xFFF  ; clear reserved bits
2288 clevermous 728
        cmp     eax, [base]
729
        jz      .ret
4608 clevermous 730
@@:
2288 clevermous 731
        add     ecx, 3
4608 clevermous 732
        mov     eax, [num_variable_mtrrs]
733
        lea     eax, [0x200+eax*2]
734
        cmp     ecx, eax
735
        jb      .scan
2288 clevermous 736
; no free registers, ignore the call
737
.ret:
738
        ret
739
.found:
740
; found, write values
4608 clevermous 741
        call    mtrr_begin_change
2288 clevermous 742
        xor     edx, edx
743
        mov     eax, [base]
744
        or      eax, [mem_type]
745
        wrmsr
746
 
4608 clevermous 747
        mov     al, [cpu_phys_addr_width]
748
        xor     edx, edx
749
        bts     edx, eax
750
        xor     eax, eax
751
        sub     eax, [size]
2288 clevermous 752
        sbb     edx, 0
753
        or      eax, 0x800
754
        inc     ecx
755
        wrmsr
4608 clevermous 756
        call    mtrr_end_change
2288 clevermous 757
        ret
758
endp
759
 
4608 clevermous 760
; Helper procedure for mtrr_validate.
761
; Calculates memory type for given address according to variable-range MTRRs.
762
; Assumes that MTRRs are enabled.
763
; in: ebx = 32-bit physical address
764
; out: eax = memory type for ebx
765
proc mtrr_get_real_type
766
; 1. Initialize: we have not yet found any MTRRs covering ebx.
767
        push    0
768
        mov     ecx, 0x201
769
.mtrr_loop:
770
; 2. For every MTRR, check whether it is valid; if not, continue to the next MTRR.
771
        rdmsr
2288 clevermous 772
        dec     ecx
4608 clevermous 773
        test    ah, 8
774
        jz      .next
775
; 3. For every valid MTRR, check whether (ebx and PHYSMASKn) == PHYSBASEn,
776
; excluding low 12 bits.
777
        and     eax, ebx
778
        push    eax
779
        rdmsr
780
        test    edx, edx
781
        pop     edx
782
        jnz     .next
783
        xor     edx, eax
784
        and     edx, not 0xFFF
785
        jnz     .next
786
; 4. If so, set the bit corresponding to memory type defined by this MTRR.
787
        and     eax, 7
788
        bts     [esp], eax
789
.next:
790
; 5. Continue loop at 2-4 for all variable-range MTRRs.
791
        add     ecx, 3
792
        mov     eax, [num_variable_mtrrs]
793
        lea     eax, [0x200+eax*2]
794
        cmp     ecx, eax
795
        jb      .mtrr_loop
796
; 6. If no MTRRs cover address in ebx, use default MTRR type from MTRR_DEF_CAP.
797
        pop     edx
798
        test    edx, edx
799
        jz      .default
800
; 7. Find&clear 1-bit in edx.
801
        bsf     eax, edx
802
        btr     edx, eax
803
; 8. If there was only one 1-bit, then all MTRRs are consistent, return that bit.
804
        test    edx, edx
805
        jz      .nothing
806
; Otherwise, return MEM_UC (e.g. WB+UC is UC).
2288 clevermous 807
        xor     eax, eax
4608 clevermous 808
.nothing:
2288 clevermous 809
        ret
4608 clevermous 810
.default:
811
        mov     ecx, 0x2FF
812
        rdmsr
813
        movzx   eax, al
814
        ret
2288 clevermous 815
endp
2466 Serge 816
 
4608 clevermous 817
; If MTRRs are configured improperly, this is not obvious to the user;
818
; everything works, but the performance can be horrible.
819
; Try to detect this and let the user know that the low performance
820
; is caused by some problem and is not a global property of the system.
821
; Let's hope he would report it to developers...
822
proc mtrr_validate
823
; 1. If MTRRs are not supported, they cannot be configured improperly.
4619 clevermous 824
; Note: VirtualBox claims MTRR support in cpuid, but emulates MTRRCAP=0,
825
; which is efficiently equivalent to absent MTRRs.
826
; So check [num_variable_mtrrs] instead of CAPS_MTRR in [cpu_caps].
827
        cmp     [num_variable_mtrrs], 0
828
        jz      .exit
4608 clevermous 829
; 2. If variable-range MTRRs are not configured, this is a problem.
830
        mov     ecx, 0x2FF
831
        rdmsr
832
        test    ah, 8
833
        jz      .fail
834
; 3. Get the memory type for address somewhere inside working memory.
835
; It must be write-back.
836
        mov     ebx, 0x27FFFF
837
        call    mtrr_get_real_type
838
        cmp     al, MEM_WB
839
        jnz     .fail
840
; 4. If we're using a mode with LFB,
841
; get the memory type for last pixel of the framebuffer.
842
; It must be write-combined.
843
        test    word [SCR_MODE], 0x4000
844
        jz      .exit
845
        mov     eax, [_display.pitch]
846
        mul     [_display.height]
847
        dec     eax
848
; LFB is mapped to virtual address LFB_BASE,
849
; it uses global pages if supported by CPU.
5130 serge 850
        mov     ebx, [sys_proc+PROC.pdt_0+(LFB_BASE shr 20)]
4608 clevermous 851
        test    ebx, PG_LARGE
852
        jnz     @f
853
        mov     ebx, [page_tabs+(LFB_BASE shr 10)]
2466 Serge 854
@@:
4608 clevermous 855
        and     ebx, not 0xFFF
856
        add     ebx, eax
857
        call    mtrr_get_real_type
858
        cmp     al, MEM_WC
859
        jz      .exit
860
; 5. The check at step 4 fails on Bochs:
861
; Bochs BIOS configures MTRRs in a strange way not respecting [cpu_phys_addr_width],
862
; so mtrr_reconfigure avoids to touch anything.
863
; However, Bochs core ignores MTRRs (keeping them only for rdmsr/wrmsr),
864
; so we don't care about proper setting for Bochs.
865
; Use northbridge PCI id to detect Bochs: it emulates either i440fx or i430fx
866
; depending on configuration file.
867
        mov     eax, [pcidev_list.fd]
868
        cmp     eax, pcidev_list        ; sanity check: fail if no PCI devices
869
        jz      .fail
870
        cmp     [eax+PCIDEV.vendor_device_id], 0x12378086
871
        jz      .exit
872
        cmp     [eax+PCIDEV.vendor_device_id], 0x01228086
873
        jnz     .fail
874
.exit:
2466 Serge 875
        ret
4608 clevermous 876
.fail:
877
        mov     ebx, mtrr_user_message
878
        mov     ebp, notifyapp
879
        call    fs_execute_from_sysdir_param
880
        ret
2466 Serge 881
endp