root/src/add-ons/kernel/cpu/x86/generic_x86.cpp
/*
 * Copyright 2005-2009, Haiku, Inc.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Axel Dörfler, axeld@pinc-software.de
 *              Ingo Weinhold, ingo_weinhold@gmx.de
 */


#include "generic_x86.h"
#include "intel.h"
#include "amd.h"
#include "via.h"

#include <KernelExport.h>
#include <arch_system_info.h>
#include <arch/x86/arch_cpu.h>
#include <smp.h>


//#define TRACE_MTRR
#ifdef TRACE_MTRR
#       define TRACE(x...)      dprintf("mtrr: " x)
#else
#       define TRACE(x...)      /* nothing */
#endif


#define IA32_MTRR_ENABLE                                (1UL << 11)
#define IA32_MTRR_ENABLE_FIXED                  (1UL << 10)
#define IA32_MTRR_VALID_RANGE                   (1UL << 11)


struct mtrr_capabilities {
        mtrr_capabilities(uint64 value) { *(uint64 *)this = value; }

        uint64  variable_ranges : 8;
        uint64  supports_fixed : 1;
        uint64  _reserved0 : 1;
        uint64  supports_write_combined : 1;
        uint64  _reserved1 : 53;
};


uint64 gPhysicalMask = 0;


static const char *
mtrr_type_to_string(uint8 type)
{
        switch (type) {
                case IA32_MTR_UNCACHED:
                        return "uncacheable";
                case IA32_MTR_WRITE_COMBINING:
                        return "write combining";
                case IA32_MTR_WRITE_THROUGH:
                        return "write-through";
                case IA32_MTR_WRITE_PROTECTED:
                        return "write-protected";
                case IA32_MTR_WRITE_BACK:
                        return "write-back";
                default:
                        return "reserved";
        }
}


static void
set_mtrr(uint32 index, uint64 base, uint64 length, uint8 type)
{
        uint64 mask = length - 1;
        mask = ~mask & gPhysicalMask;

        TRACE("MTRR %" B_PRIu32 ": new mask %" B_PRIx64 "\n", index, mask);
        TRACE("  mask test base: %" B_PRIx64 "\n", mask & base);
        TRACE("  mask test middle: %" B_PRIx64 "\n", mask & (base + length / 2));
        TRACE("  mask test end: %" B_PRIx64 "\n", mask & (base + length));

        index *= 2;
                // there are two registers per slot

        // First, disable MTRR

        x86_write_msr(IA32_MSR_MTRR_PHYSICAL_MASK_0 + index, 0);

        if (base != 0 || mask != 0 || type != 0) {
                // then fill in the new values, and enable it again

                x86_write_msr(IA32_MSR_MTRR_PHYSICAL_BASE_0 + index,
                        (base & ~(B_PAGE_SIZE - 1)) | type);
                x86_write_msr(IA32_MSR_MTRR_PHYSICAL_MASK_0 + index,
                        mask | IA32_MTRR_VALID_RANGE);
        } else {
                // reset base as well
                x86_write_msr(IA32_MSR_MTRR_PHYSICAL_BASE_0 + index, 0);
        }
}


// #pragma mark -


uint32
generic_count_mtrrs(void)
{
        if (!x86_check_feature(IA32_FEATURE_MTRR, FEATURE_COMMON)
                || !x86_check_feature(IA32_FEATURE_MSR, FEATURE_COMMON))
                return 0;

        mtrr_capabilities capabilities(x86_read_msr(IA32_MSR_MTRR_CAPABILITIES));
        TRACE("CPU %" B_PRId32 " has %u variable range MTRRs.\n",
                smp_get_current_cpu(), (uint8)capabilities.variable_ranges);

        return capabilities.variable_ranges;
}


void
generic_init_mtrrs(uint32 count)
{
        if (count == 0)
                return;

        // If MTRRs are enabled, we leave everything as is (save for, possibly, the
        // default, which we set below), so that we can benefit from the BIOS's
        // setup until we've installed our own. If MTRRs are disabled, we clear
        // all registers and enable MTRRs.
        // (we leave the fixed MTRRs as is)
        // TODO: check if the fixed MTRRs are set on all CPUs identically?
        TRACE("generic_init_mtrrs(count = %" B_PRIu32 ")\n", count);

        uint64 defaultType = x86_read_msr(IA32_MSR_MTRR_DEFAULT_TYPE);
        if ((defaultType & IA32_MTRR_ENABLE) == 0) {
                for (uint32 i = 0; i < count; i++)
                        set_mtrr(i, 0, 0, 0);
        }

        // Turn on variable MTRR functionality.
        // We need to ensure that the default type is uncacheable, otherwise
        // clearing the mtrrs could result in ranges that aren't supposed to be
        // cacheable to become cacheable due to the default type.
        x86_write_msr(IA32_MSR_MTRR_DEFAULT_TYPE,
                (defaultType & ~0xff) | IA32_MTRR_ENABLE);
}


void
generic_set_mtrr(uint32 index, uint64 base, uint64 length, uint8 type)
{
        set_mtrr(index, base, length, type);
        TRACE("[cpu %" B_PRId32 "] mtrrs now:\n", smp_get_current_cpu());
#if TRACE_MTRR
        generic_dump_mtrrs(generic_count_mtrrs());
#endif
}


status_t
generic_get_mtrr(uint32 index, uint64 *_base, uint64 *_length, uint8 *_type)
{
        uint64 mask = x86_read_msr(IA32_MSR_MTRR_PHYSICAL_MASK_0 + index * 2);
        if ((mask & IA32_MTRR_VALID_RANGE) == 0)
                return B_ERROR;

        uint64 base = x86_read_msr(IA32_MSR_MTRR_PHYSICAL_BASE_0 + index * 2);

        *_base = base & ~(B_PAGE_SIZE - 1);
        *_length = (~mask & gPhysicalMask) + B_PAGE_SIZE;
        *_type = base & 0xff;

        return B_OK;
}


void
generic_set_mtrrs(uint8 newDefaultType, const x86_mtrr_info* infos,
        uint32 count, uint32 maxCount)
{
        // check count
        if (maxCount == 0)
                return;

        if (count > maxCount)
                count = maxCount;

        // disable MTTRs
        uint64 defaultType = x86_read_msr(IA32_MSR_MTRR_DEFAULT_TYPE)
                & ~IA32_MTRR_ENABLE;
        x86_write_msr(IA32_MSR_MTRR_DEFAULT_TYPE, defaultType);

        // set the given MTRRs
        for (uint32 i = 0; i < count; i++)
                set_mtrr(i, infos[i].base, infos[i].size, infos[i].type);

        // clear the other MTRRs
        for (uint32 i = count; i < maxCount; i++)
                set_mtrr(i, 0, 0, 0);

        // re-enable MTTRs and set the new default type
        defaultType = (defaultType & ~(uint64)0xff) | newDefaultType;
        x86_write_msr(IA32_MSR_MTRR_DEFAULT_TYPE, defaultType | IA32_MTRR_ENABLE);
}


status_t
generic_mtrr_compute_physical_mask(void)
{
        uint32 bits = 36;

        cpuid_info cpuInfo;
        if (get_current_cpuid(&cpuInfo, 0x80000000, 0) == B_OK
                && (cpuInfo.eax_0.max_eax & 0xff) >= 8) {
                get_current_cpuid(&cpuInfo, 0x80000008, 0);
                bits = cpuInfo.regs.eax & 0xff;

                // Obviously, the bits are not always reported correctly
                if (bits < 36)
                        bits = 36;
        }

        gPhysicalMask = ((1ULL << bits) - 1) & ~(B_PAGE_SIZE - 1);

        TRACE("CPU %" B_PRId32 " has %" B_PRIu32
                " physical address bits, physical mask is %016" B_PRIx64 "\n",
                smp_get_current_cpu(), bits, gPhysicalMask);

        return B_OK;
}


void
generic_dump_mtrrs(uint32 count)
{
        if (count == 0)
                return;

        int cpu = smp_get_current_cpu();
        uint64 defaultType = x86_read_msr(IA32_MSR_MTRR_DEFAULT_TYPE);
        dprintf("mtrr: [cpu %d] MTRRs are %sabled\n", cpu,
                (defaultType & IA32_MTRR_ENABLE) != 0 ? "en" : "dis");
        dprintf("mtrr: [cpu %d] default type is %u %s\n", cpu,
                (uint8)defaultType, mtrr_type_to_string(defaultType));
        dprintf("mtrr: [cpu %d] fixed range MTRRs are %sabled\n", cpu,
                (defaultType & IA32_MTRR_ENABLE_FIXED) != 0 ? "en" : "dis");

        for (uint32 i = 0; i < count; i++) {
                uint64 base;
                uint64 length;
                uint8 type;
                if (generic_get_mtrr(i, &base, &length, &type) == B_OK) {
                        dprintf("mtrr: [cpu %d] %" B_PRIu32 ": base: 0x%" B_PRIx64
                                "; length: 0x%" B_PRIx64 "; type: %u %s\n", cpu, i, base,
                                length, type, mtrr_type_to_string(type));
                }
        }
}


module_info *modules[] = {
        (module_info *)&gIntelModule,
        (module_info *)&gAMDModule,
        (module_info *)&gVIAModule,
        NULL
};