diff --git a/src/interrupts/gdt.rs b/src/interrupts/gdt.rs
index bf23aaf..50e73e5 100644
--- a/src/interrupts/gdt.rs
+++ b/src/interrupts/gdt.rs
@@ -1,3 +1,5 @@
+use x86_64::structures::tss::TaskStateSegment;
+
 pub struct Gdt {
     table: [u64; 8],
     next_free: usize,
@@ -11,3 +13,46 @@ impl Gdt {
         }
     }
 }
+
+pub enum Descriptor {
+    UserSegment(u64),
+    SystemSegment(u64, u64),
+}
+
+impl Descriptor {
+    pub fn kernel_code_segment() -> Descriptor {
+        let flags = USER_SEGMENT | PRESENT | EXECUTABLE | LONG_MODE;
+        Descriptor::UserSegment(flags.bits())
+    }
+
+    pub fn tss_segment(tss: &'static TaskStateSegment) -> Descriptor {
+        use core::mem::size_of;
+        use bit_field::BitField;
+
+        let ptr = tss as *const _ as u64;
+
+        let mut low = PRESENT.bits();
+        // base
+        low.set_bits(16..40, ptr.get_bits(0..24));
+        low.set_bits(56..64, ptr.get_bits(24..32));
+        // limit (the `-1` in needed since the bound is inclusive)
+        low.set_bits(0..16, (size_of::<TaskStateSegment>() - 1) as u64);
+        // type (0b1001 = available 64-bit tss)
+        low.set_bits(40..44, 0b1001);
+
+        let mut high = 0;
+        high.set_bits(0..32, ptr.get_bits(32..64));
+
+        Descriptor::SystemSegment(low, high)
+    }
+}
+
+bitflags! {
+    flags DescriptorFlags: u64 {
+        const CONFORMING        = 1 << 42,
+        const EXECUTABLE        = 1 << 43,
+        const USER_SEGMENT      = 1 << 44,
+        const PRESENT           = 1 << 47,
+        const LONG_MODE         = 1 << 53,
+    }
+}