Hi,
digo_rp wrote:example in the intel manual it says:
This mask is chosen so
that when any address in the 200000H to 3FFFFFH range is AND’d with the mask
value, it will return the same value as when the base address is AND’d with the mask
value (which is 200000H).
You could convert that paragraph directly into C code:
Code: Select all
if( (address & MTTR_MASK) == MTTR_BASE) { /* MTRR effects this access */ }
It's worth noting here that it is impossible to cover some address ranges with a single variable MTTR. To be able to cover an address range with one variable MTTR, the base address of that range must be on a "size of the range" boundary and the size of the range must be a power of 2. For example, a 2 MiB address range would have to be on a 2 MiB boundary, a 4 KiB address range would have to be on a 4 KiB boundary, etc.
If the size of the range is a power of 2 and the base address of the range is on a "size of the range" boundary; then the calculation is simple:
If the size of the range is not a power of 2, or if the base address of the range is not on a "size of the range" boundary; then you have to split it up into multiple ranges that are. This can be tricky.
For an example, imagine you've got 56 KiB area at 0xA8642000. To start with the size of the area isn't a power of 2, so you need to split it up into smaller areas that are a power of 2. The largest area would be 32 KiB (which leaves you with 24 KiB that still needs splitting up), the next area would be 16 KiB and the last area would be 8 KiB. The 32 KiB area can't use "base address 0xA8642000" because it's not aligned, and neither can the 16 KiB area. The 8 KiB area can though. If the 16 KiB area placed directly above that, and the 32 KiB area is directly above that, then it all works. You end up with:
1st: 8 KiB from 0xA8642000 to 0xA8643FFF (MTRR_base = 0xA8642000, MTTR_mask = 0xFFFFE000)
2nd: 16 KiB from 0xA8644000 to 0xA8647FFF (MTRR_base = 0xA8644000, MTTR_mask = 0xFFFFC000)
3rd: 32 KiB from 0xA8648000 to 0xA864FFFF (MTRR_base = 0xA8648000, MTTR_mask = 0xFFFF8000)
What if there's a 56 KiB area at 0xE0000000? In that case you'd still need to split it into 3 areas, but the largest area can go first, and you'd end up with:
1st: 32 KiB from 0xE0000000 to 0xE0007FFF (MTRR_base = 0xE0000000, MTTR_mask = 0xFFFF8000)
2nd: 16 KiB from 0xE0008000 to 0xE000BFFF (MTRR_base = 0xE0008000, MTTR_mask = 0xFFFFC000)
3rd: 8 KiB from 0xE000C000 to 0xA8643FFF (MTRR_base = 0xE000C000, MTTR_mask = 0xFFFFE000)
However, this is a special case; where you may be able to do it more efficiently by having one area that is "too large" and another area to correct the wrong part. For the area that is too large, you'd round up to the nearest power of 2 and get a 64 KiB area. This is 8 KiB larger than it should be, so you can create a second area that says "ignore the first MTTR, this 8 KiB is uncachable". The result might look like this:
1st: 64 KiB from 0xE0000000 to 0xE000FFFF (MTRR_base = 0xE0000000, MTTR_mask = 0xFFFF0000) - "write through"
2nd: 8 KiB from 0xE000E000 to 0xE000FFFF (MTRR_base = 0xE000E000, MTTR_mask = 0xFFFFE000) - "uncachable"
This only works in some cases though - that second area must be "uncachable", and (if I remember right) the first area can't be "write combining". It'd be best to look up the details in Intel's manual if you're considering this..
Fortunately, the PCI specifications have restrictions that device manufacturers have to follow for memory mapped IO areas; and these restrictions happen to be virtually the same as the MTRR restrictions. This means that for memory mapped PCI areas you'll only need one MTTR; and the only case where you need to use more than one is for RAM (which the firmware would've already done for you).
Cheers,
Brendan