You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

200 lines
7.1 KiB
C++

/*! \file
* \brief The \ref GDT "Global Descriptor Table (GDT)".
*/
#pragma once
#include "../types.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 Segments {
SEGMENT_NULL = 0,
SEGMENT_KERNEL_CODE,
SEGMENT_KERNEL_DATA,
};
/*! \brief Unit of the segment limit
*/
enum Granularity {
GRANULARITY_BYTES = 0, ///< Segment limit in Bytes
GRANULARITY_4KBLOCK = 1 ///< Segment limit in blocks of 4 Kilobytes
};
/*! \brief Descriptor type */
enum DescriptorType {
DESCRIPTOR_SYSTEM = 0, ///< entry is a system segment
DESCRIPTOR_CODEDATA = 1, ///< entry is a code/data segment
};
/*! \brief Address width
*/
enum Size {
SIZE_16BIT = 0, ///< 16-bit (D/B = 0, L = 0)
SIZE_32BIT = 2, ///< 32-bit (D/B = 1, L = 0)
SIZE_64BIT = 1, ///< 64-bit (D/B = 0, L = 1)
};
/*! \brief Type flags for used descriptor types
*/
enum TypeFlags {
TYPE_DATA_RW = 0b0010ull, ///< Data rw, not expanding down
TYPE_CODE_RX = 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 ? TYPE_CODE_RX : TYPE_DATA_RW,
.descriptor_type = DESCRIPTOR_CODEDATA,
.privilege_level = ring,
.present = true,
.limit_high = (limit > 0xFFFFF ? (limit >> 28) : (limit >> 16)) & 0xF,
.available = false,
.custom = size,
.granularity =
limit > 0xFFFFF ? GRANULARITY_4KBLOCK : 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_64BIT);
}
} __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