/***************************************** * VulcanOS Kernel * * Developed by Marco 'icebit' Cetica * * (c) 2019-2021 * * Released under GPLv3 * * https://github.com/ice-bit/vulcanos * *****************************************/ #ifndef _GDT_H_ #define _GDT_H_ #include /* * First a bit of theory: * GDT(Global Descriptor Table) is a complex data structure used in x86 systems * to define memory areas. * Technically speaking GDT is formed by an array of 8-bytes segment descriptors, * the first descriptor of the GDT is always a NULL one and CANNOT be used to allocate * memory; so we need at least two descriptors(plus the null descriptor) to successfully allocate * data on our memory. * In x86 architecture there're two methods to provide virtual memory: Segmentation and Paging: * With the first one every memory access is done within his own segment, so each address(of a process) * is added to the segment's base address and checked against the segment's length. * With paging, however, the address space is split into blocks(usually of 4KiB) called pages. * Each page can be mapped to physical memory or it can be unmapped(to create virtual memory). * Segmentation is a built-in functionality of x86 architecture, so to get around this we need to * define our own GDT. A cool thing segmentation can do for us is to set Ring Level: * a privilege level to allow our process to run in a 'unprivileged' mode(called user mode, ring 3) * and to allow drivers(or kernel related stuff) to run in a 'supervisor-mode'(called kernel mode, ring 0). * Usually bootloader(such as GRUB) sets up GDT for us, the problem is that we cannot known where it is or * if it is has been overwritten during some other tasks. So it's a good idea to implement * a new GDT ourself. */ /* Those values were taken from Intel's developer manual */ // GDT fields #define GDT_BASE 0x00000000 #define GDT_LIMIT 0xFFFFFFFF // GDT granularity #define GDT_SEGMENT_LENGTH 0xF #define GDT_OPERAND_SIZE_16 0 #define GDT_OPERAND_SIZE_32 1 #define GDT_GRANULARITY_1K 0 #define GDT_GRANULARITY_4K 1 // GDT access type fields #define GDT_DATA_TYPE_READ_ONLY 0x0 #define GDT_DATA_TYPE_READ_ONLY_ACCESSED 0x1 #define GDT_DATA_TYPE_READ_WRITE 0x2 #define GDT_DATA_TYPE_READ_WRITE_ACCESSED 0x3 #define GDT_DATA_TYPE_READ_ONLY_EXPAND_DOWN 0x4 #define GDT_DATA_TYPE_READ_ONLY_EXPAND_DOWN_ACCESSED 0x5 #define GDT_DATA_TYPE_READ_WRITE_EXPAND_DOWN 0x6 #define GDT_DATA_TYPE_READ_WRITE_EXPAND_DOWN_ACCESSED 0x7 #define GDT_DATA_TYPE_EXEC_ONLY 0x8 #define GDT_CODE_TYPE_EXEC_ONLY_ACCESSED 0x9 #define GDT_CODE_TYPE_EXEC_READ 0xA #define GDT_CODE_TYPE_EXEC_READ_ACCESSED 0xB #define GDT_CODE_TYPE_EXEC_CONFORMING 0xC #define GDT_CODE_TYPE_EXEC_CONFORMING_ACCESSED 0xD #define GDT_CODE_TYPE_EXEC_READ_CONFORMING 0xE #define GDT_CODE_TYPE_EXEC_READ_CONFORMING_ACCESSED 0xF // Descriptor type fields #define GDT_SYSTEM_DESCRIPTOR 0 #define GDT_CODE_AND_DATA_DESCRIPTOR 1 // GDT Ring number #define GDT_RING0 0 #define GDT_RING1 1 #define GDT_RING2 2 #define GDT_RING3 3 // 'Present' field #define GDT_SEGMENT_NOT_PRESENT 0 #define GDT_SEGMENT_PRESENT 1 /* Global Descriptor Table (GDT) implementation */ /* gdt_access is used to access portion of the GDT * | 0 - 3 | 4 | 3 - 6 | 7 | * | Type | DT | DPL | P | * Type: Which type * DT: descriptor type * DPL: Kernel ring(0-3) * P: is segment present? (bool) */ struct gdt_access { uint8_t type: 4; // 4 Bits uint8_t dt: 1; // 1 Bit uint8_t dpl: 2; // 2 Bits uint8_t p: 1; // 1 Bits }__attribute__((packed)); typedef struct gdt_access gdt_access_t; /* gdt_granularity is used to get portion of GDT entry * | 0 - 3 | 4 | 5 | 6 | 7 | * | seglen | 0 | D | G | * seglen: segment length * 0: Always zero * D: Operand size (0 = 16 bit, 1 = 32 bit) * G: granularity (0 = 1 Byte, 1 = 4KiB) */ struct gdt_granularity { uint8_t seglen: 4; uint8_t zero: 2; uint8_t d: 1; uint8_t g: 1; }__attribute__((packed)); typedef struct gdt_granularity gdt_gran_t; /* gdt_entry_struct contains the value of a single GDT entry * Each slice is 64 bits. * | 0 - 15 | 16 - 31 | 32 - 39 | 40 - 47 | 48 - 55 | 56 - 63 | * | lim low| base low|base mid | access | gran | base hg | * lim low: Lower 16 bits of the limit * base low: Lower 16 bits of the base * base mid: Next 8 bits of the base * access: access flag, e.g. which ring this segment can be used in. * gran. */ struct gdt_entry_struct { uint16_t limit_low; uint16_t base_low; uint8_t base_middle; gdt_access_t access; gdt_gran_t granularity; uint8_t base_high; }__attribute__((packed)); typedef struct gdt_entry_struct gdt_entry_t; /* Also we have to define a pointer to the data structure * This is needed to locate it later */ struct gdt_ptr { uint16_t limit; uint32_t base; }__attribute__((packed)); typedef struct gdt_ptr gdt_ptr_t; /* GDT Kernel API */ void gdt_setup(); #endif