Handout
This commit is contained in:
203
kernel/arch/gdt.h
Normal file
203
kernel/arch/gdt.h
Normal file
@@ -0,0 +1,203 @@
|
||||
/*! \file
|
||||
* \brief The \ref GDT "Global Descriptor Table (GDT)".
|
||||
* \defgroup memory Memory management
|
||||
*/
|
||||
|
||||
#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 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);
|
||||
}
|
||||
|
||||
} __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
|
||||
Reference in New Issue
Block a user