227 lines
7.6 KiB
C++
227 lines
7.6 KiB
C++
/*! \file
|
|
* \brief The \ref GDT "Global Descriptor Table (GDT)".
|
|
* \defgroup memory Memory management
|
|
*/
|
|
|
|
#pragma once
|
|
#include "../types.h"
|
|
#include "./tss.h"
|
|
|
|
/*! \brief Abstracts the GDT that, primarily, contains descriptors to memory
|
|
* segments.
|
|
* \ingroup memory
|
|
*
|
|
* The GDT is a table that primarily contains segment descriptors. Segment
|
|
* descriptors has a size of 8 Bytes and contains the size, position, access
|
|
* rights, and purpose of such a segment. Unlike the LDT, the GDT is shared
|
|
* between all processes and may contain TSS and LDT descriptors. For the
|
|
* kernel, the first entry is required to be a null descriptor and the code and
|
|
* data segments. To support user-mode processes, additional TSS, code, and data
|
|
* segments for ring 3 must be added.
|
|
*
|
|
* The base address and size of the GDT are written to the GDTR register during
|
|
* boot (via. `lgdt`).
|
|
*
|
|
* \see [ISDMv3, 2.4.1; Global Descriptor Table Register
|
|
* (GDTR)](intel_manual_vol3.pdf#page=72)
|
|
* \see [ISDMv3, 3.5.1; Segment
|
|
* Descriptor Tables](intel_manual_vol3.pdf#page=99)
|
|
*/
|
|
namespace GDT {
|
|
|
|
enum class Segments {
|
|
Null = 0,
|
|
KernelCode,
|
|
KernelData,
|
|
};
|
|
|
|
/*! \brief Unit of the segment limit
|
|
*/
|
|
enum class Granularity {
|
|
Bytes = 0, ///< Segment limit in Bytes
|
|
Pages = 1 ///< Segment limit in blocks of 4 Kilobytes
|
|
};
|
|
|
|
/*! \brief Descriptor type */
|
|
enum class DescriptorType {
|
|
System = 0, ///< entry is a system segment
|
|
CodeData = 1, ///< entry is a code/data segment
|
|
};
|
|
|
|
/*! \brief Address width
|
|
*/
|
|
enum class Size {
|
|
Bit16 = 0, ///< 16-bit (D/B = 0, L = 0)
|
|
Bit32 = 2, ///< 32-bit (D/B = 1, L = 0)
|
|
Bit64 = 1, ///< 64-bit (D/B = 0, L = 1)
|
|
};
|
|
|
|
/*! \brief Type flags for used descriptor types
|
|
*/
|
|
enum class TypeFlags : uint64_t {
|
|
DataRW = 0b0010ULL, ///< Data rw, not expanding down
|
|
CodeRX = 0b1010ULL, ///< Code rx, non-conforming
|
|
};
|
|
|
|
/*! \brief Describes the structure of segment descriptors
|
|
*
|
|
* A data structure that contains size, position, access rights, and purpose of
|
|
* any segment. Segment descriptors are used in both the GDT, as well as in
|
|
* LDTs.
|
|
*
|
|
* \see [ISDMv3, 3.4.5; Segment Descriptors](intel_manual_vol3.pdf#page=95)
|
|
* \see [AAPMv2, 4.7 Legacy Segment Descriptors](amd64_manual_vol2.pdf#page=132)
|
|
* \see [AAPMv2, 4.8 Long-Mode Segment
|
|
* Descriptors](amd64_manual_vol2.pdf#page=140)
|
|
*/
|
|
union SegmentDescriptor {
|
|
// Universally valid values (shared across all segment types)
|
|
struct {
|
|
uint64_t limit_low : 16; ///< Least-significant bits of segment size
|
|
///< (influenced by granularity!)
|
|
uint64_t base_low : 24; ///< Least-significant bits of base address
|
|
uint64_t type : 4; ///< Meaning of those 4 bits depends on
|
|
///< descriptor_type below
|
|
DescriptorType
|
|
descriptor_type : 1; ///< Descriptor type (influences the
|
|
///< meaning of the 3 bits above)
|
|
uint64_t privilege_level : 2; ///< Ring for this segment
|
|
bool present : 1; ///< Entry is valid iff set to `true`
|
|
uint64_t limit_high : 4; ///< Most-significant bits of segment size
|
|
bool available : 1; ///< Bit which can be used for other purposes (in
|
|
///< software)
|
|
uint64_t custom : 2; ///< Meaning of those 2 bits relate to
|
|
///< descriptor_type and type
|
|
Granularity granularity : 1; ///< Unit used as granularity for the
|
|
///< segment limit
|
|
uint64_t base_high : 8; ///< most-significant bits of base address
|
|
} __attribute__((packed));
|
|
|
|
uint64_t value; ///!< Merged value
|
|
|
|
/*! \brief Explicitly constructs a null descriptor.
|
|
*/
|
|
consteval static SegmentDescriptor Null() {
|
|
return SegmentDescriptor{
|
|
.value = 0,
|
|
};
|
|
}
|
|
|
|
/*! \brief Constructs a code/data segment descriptor.
|
|
* \param base Base Address of segment
|
|
* \param limit Size of segment
|
|
* \param code Code or data segment
|
|
* \param ring Privilege level
|
|
* \param size Address width
|
|
*/
|
|
consteval static SegmentDescriptor Segment(uintptr_t base, uint32_t limit,
|
|
bool code, uint64_t ring,
|
|
Size size) {
|
|
return SegmentDescriptor{
|
|
.limit_low = limit >> (limit > 0xFFFFF ? 12 : 0) & 0xFFFF,
|
|
.base_low = base & 0xFFFFFF,
|
|
.type = code ? uint64_t(TypeFlags::CodeRX)
|
|
: uint64_t(TypeFlags::DataRW),
|
|
.descriptor_type = DescriptorType::CodeData,
|
|
.privilege_level = ring,
|
|
.present = true,
|
|
.limit_high =
|
|
(limit > 0xFFFFF ? (limit >> 28) : (limit >> 16)) & 0xF,
|
|
.available = false,
|
|
.custom = uint64_t(size),
|
|
.granularity =
|
|
limit > 0xFFFFF ? Granularity::Pages : Granularity::Bytes,
|
|
.base_high = (base >> 24) & 0xFF,
|
|
};
|
|
}
|
|
|
|
/*! \brief Constructs a 64bit code/data segment descriptor.
|
|
* \param code Code or data segment
|
|
* \param ring Privilege level
|
|
*/
|
|
consteval static SegmentDescriptor Segment64(bool code, int ring) {
|
|
return SegmentDescriptor::Segment(0, 0, code, ring, Size::Bit64);
|
|
}
|
|
|
|
static SegmentDescriptor TSSLow (const TSS::Entry &tss){
|
|
return SegmentDescriptor{
|
|
.limit_low = sizeof(tss) & 0xFFFF,
|
|
.base_low=(uint64_t)(&tss) & 0xFFFFFF,
|
|
.type=0b1001, // dont set busy flag, is this even needed?
|
|
.descriptor_type = DescriptorType::System, // TSS descriptor needs this set to 0
|
|
.privilege_level = 0, // ring 0
|
|
.present = true,
|
|
.limit_high = 0, // should not be greater than 2^16 - 1 anyways
|
|
.available = false,
|
|
.custom=0,
|
|
.granularity = Granularity::Bytes,
|
|
.base_high = ((uint64_t)(&tss) >> 24)&0xFF,
|
|
};
|
|
}
|
|
|
|
static SegmentDescriptor TSSHigh (const TSS::Entry &tss){
|
|
return SegmentDescriptor{
|
|
.value = ((uint64_t)(&tss) >> 32) & 0xFFFFFFFF,
|
|
};;
|
|
}
|
|
|
|
} __attribute__((packed));
|
|
|
|
static_assert(sizeof(SegmentDescriptor) == 8,
|
|
"GDT::SegmentDescriptor has wrong size");
|
|
|
|
/*! \brief Structure that describes a GDT Pointer (aka GDT Descriptor)
|
|
*
|
|
* It contains both the length (in bytes) of the GDT (minus 1 byte) and the
|
|
* pointer to the GDT. The pointer to the GDT can be loaded using the
|
|
* instruction `lgdt`.
|
|
*
|
|
* \note As Intel uses little endian for representing multi-byte values, the
|
|
* GDT::Pointer structure can be used for 16, 32, and 64 bit descriptor tables:
|
|
* \verbatim
|
|
* | 16 bit | 16 bit | 16 bit | 16 bit | 16 bit |
|
|
* +--------+---------------------------------------+
|
|
* Pointer | limit | base (up to 64 bit) |
|
|
* +--------+---------+---------+---------+---------+
|
|
* | used for 16 bit | ignored... |
|
|
* | used for 32 bit | ignored... |
|
|
* | used for 64 bit |
|
|
* \endverbatim
|
|
*
|
|
* \see [ISDMv3, Figure 2-6; Memory Management
|
|
* Registers](intel_manual_vol3.pdf#page=72)
|
|
*/
|
|
struct Pointer {
|
|
uint16_t limit; //!< GDT size in bytes (minus 1 byte)
|
|
void* base; //!< GDT base address
|
|
|
|
/*! \brief Constructor (automatic length)
|
|
* \param desc Array of GDT segment descriptors -- must be defined in the
|
|
* same module!
|
|
*/
|
|
template <typename T, size_t LEN>
|
|
explicit constexpr Pointer(const T (&desc)[LEN])
|
|
: limit((LEN * sizeof(T)) - 1), base(const_cast<T*>(desc)) {}
|
|
|
|
/*! \brief Constructor
|
|
* \param desc Address of the GDT segment descriptors
|
|
* \param len Number of entries
|
|
*/
|
|
consteval Pointer(void* desc, size_t len)
|
|
: limit((len * sizeof(SegmentDescriptor)) - 1), base(desc) {}
|
|
|
|
/*! \brief Set an address
|
|
* \note On change, `lgdt` must be executed again
|
|
* \param desc Address of the GDT segment descriptors
|
|
* \param len Number of entries
|
|
*/
|
|
constexpr void set(void* desc, size_t len) {
|
|
limit = (len * sizeof(SegmentDescriptor)) - 1;
|
|
base = desc;
|
|
}
|
|
} __attribute__((packed));
|
|
|
|
static_assert(sizeof(Pointer) == 10, "GDT::Pointer has wrong size");
|
|
|
|
} // namespace GDT
|