/* * Copyright (c) 2016 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #ifndef ZEPHYR_INCLUDE_ARCH_X86_IA32_SEGMENTATION_H_ #define ZEPHYR_INCLUDE_ARCH_X86_IA32_SEGMENTATION_H_ #include /* Host gen_idt uses this header as well, don't depend on toolchain.h */ #ifndef __packed #define __packed __attribute__((packed)) #endif #ifdef __cplusplus extern "C" { #endif /* NOTE: We currently do not have definitions for 16-bit segment, currently * assume everything we are working with is 32-bit */ #define SEG_TYPE_LDT 0x2 #define SEG_TYPE_TASK_GATE 0x5 #define SEG_TYPE_TSS 0x9 #define SEG_TYPE_TSS_BUSY 0xB #define SEG_TYPE_CALL_GATE 0xC #define SEG_TYPE_IRQ_GATE 0xE #define SEG_TYPE_TRAP_GATE 0xF #define DT_GRAN_BYTE 0 #define DT_GRAN_PAGE 1 #define DT_READABLE 1 #define DT_NON_READABLE 0 #define DT_WRITABLE 1 #define DT_NON_WRITABLE 0 #define DT_EXPAND_DOWN 1 #define DT_EXPAND_UP 0 #define DT_CONFORM 1 #define DT_NONCONFORM 0 #define DT_TYPE_SYSTEM 0 #define DT_TYPE_CODEDATA 1 #ifndef _ASMLANGUAGE /* Section 7.2.1 of IA architecture SW developer manual, Vol 3. */ struct __packed task_state_segment { uint16_t backlink; uint16_t reserved_1; uint32_t esp0; uint16_t ss0; uint16_t reserved_2; uint32_t esp1; uint16_t ss1; uint16_t reserved_3; uint32_t esp2; uint16_t ss2; uint16_t reserved_4; uint32_t cr3; uint32_t eip; uint32_t eflags; uint32_t eax; uint32_t ecx; uint32_t edx; uint32_t ebx; uint32_t esp; uint32_t ebp; uint32_t esi; uint32_t edi; uint16_t es; uint16_t reserved_5; uint16_t cs; uint16_t reserved_6; uint16_t ss; uint16_t reserved_7; uint16_t ds; uint16_t reserved_8; uint16_t fs; uint16_t reserved_9; uint16_t gs; uint16_t reserved_10; uint16_t ldt_ss; uint16_t reserved_11; uint8_t t:1; /* Trap bit */ uint16_t reserved_12:15; uint16_t iomap; }; #define SEG_SELECTOR(index, table, dpl) (index << 3 | table << 2 | dpl) /* References * * Section 5.8.3 (Call gates) * Section 7.2.2 (TSS Descriptor) * Section 3.4.5 (Segment descriptors) * Section 6.11 (IDT Descriptors) * * IA architecture SW developer manual, Vol 3. */ struct __packed segment_descriptor { /* First DWORD: 0-15 */ union { /* IRQ, call, trap gates */ uint16_t limit_low; /* Task gates */ uint16_t reserved_task_gate_0; /* Everything else */ uint16_t offset_low; }; /* First DWORD: 16-31 */ union { /* Call/Task/Interrupt/Trap gates */ uint16_t segment_selector; /* TSS/LDT/Segments */ uint16_t base_low; /* Bits 0-15 */ }; /* Second DWORD: 0-7 */ union { /* TSS/LDT/Segments */ uint8_t base_mid; /* Bits 16-23 */ /* Task gates */ uint8_t reserved_task_gate_1; /* IRQ/Trap/Call Gates */ struct { /* Reserved except in case of call gates */ uint8_t reserved_or_param:5; /* Bits 5-7 0 0 0 per CPU manual */ uint8_t always_0_0:3; }; }; /* Second DWORD: 8-15 */ union { /* Code or data Segments */ struct { /* Set by the processor, init to 0 */ uint8_t accessed:1; /* executable ? readable : writable */ uint8_t rw:1; /* executable ? conforming : direction */ uint8_t cd:1; /* 1=code 0=data */ uint8_t executable:1; /* Next 3 fields actually common to all */ /* 1=code or data, 0=system type */ uint8_t descriptor_type:1; uint8_t dpl:2; uint8_t present:1; }; /* System types */ struct { /* One of the SEG_TYPE_* macros above */ uint8_t type:4; /* Alas, C doesn't let you do a union of the first * 4 bits of a bitfield and put the rest outside of it, * it ends up getting padded. */ uint8_t use_other_union:4; }; }; /* Second DWORD: 16-31 */ union { /* Call/IRQ/trap gates */ uint16_t offset_hi; /* Task Gates */ uint16_t reserved_task_gate_2; /* segment/LDT/TSS */ struct { uint8_t limit_hi:4; /* flags */ uint8_t avl:1; /* CPU ignores this */ /* 1=Indicates 64-bit code segment in IA-32e mode */ uint8_t flags_l:1; /* L field */ uint8_t db:1; /* D/B field 1=32-bit 0=16-bit*/ uint8_t granularity:1; uint8_t base_hi; /* Bits 24-31 */ }; }; }; /* Address of this passed to lidt/lgdt. * IA manual calls this a 'pseudo descriptor'. */ struct __packed pseudo_descriptor { uint16_t size; struct segment_descriptor *entries; }; /* * Full linear address (segment selector+offset), for far jumps/calls */ struct __packed far_ptr { /** Far pointer offset, unused when invoking a task. */ void *offset; /** Far pointer segment/gate selector. */ uint16_t sel; }; #define DT_ZERO_ENTRY { { 0 } } /* NOTE: the below macros only work for fixed addresses provided at build time. * Base addresses or offsets cannot be &some_variable, as pointer values are not * known until link time and the compiler has to split the address into various * fields in the segment selector well before that. * * If you really need to put &some_variable as the base address in some * segment descriptor, you will either need to do the assignment at runtime * or implement some tool to populate values post-link like gen_idt does. */ #define _LIMIT_AND_BASE(base_p, limit_p, granularity_p) \ .base_low = (((uint32_t)base_p) & 0xFFFF), \ .base_mid = (((base_p) >> 16) & 0xFF), \ .base_hi = (((base_p) >> 24) & 0xFF), \ .limit_low = ((limit_p) & 0xFFFF), \ .limit_hi = (((limit_p) >> 16) & 0xF), \ .granularity = (granularity_p), \ .flags_l = 0, \ .db = 1, \ .avl = 0 #define _SEGMENT_AND_OFFSET(segment_p, offset_p) \ .segment_selector = (segment_p), \ .offset_low = ((offset_p) & 0xFFFF), \ .offset_hi = ((offset_p) >> 16) #define _DESC_COMMON(dpl_p) \ .dpl = (dpl_p), \ .present = 1 #define _SYS_DESC(type_p) \ .type = type_p, \ .descriptor_type = 0 #define DT_CODE_SEG_ENTRY(base_p, limit_p, granularity_p, dpl_p, readable_p, \ conforming_p) \ { \ _DESC_COMMON(dpl_p), \ _LIMIT_AND_BASE(base_p, limit_p, granularity_p), \ .accessed = 0, \ .rw = (readable_p), \ .cd = (conforming_p), \ .executable = 1, \ .descriptor_type = 1 \ } #define DT_DATA_SEG_ENTRY(base_p, limit_p, granularity_p, dpl_p, writable_p, \ direction_p) \ { \ _DESC_COMMON(dpl_p), \ _LIMIT_AND_BASE(base_p, limit_p, granularity_p), \ .accessed = 0, \ .rw = (writable_p), \ .cd = (direction_p), \ .executable = 0, \ .descriptor_type = 1 \ } #define DT_LDT_ENTRY(base_p, limit_p, granularity_p, dpl_p) \ { \ _DESC_COMMON(dpl_p), \ _LIMIT_AND_BASE(base_p, limit_p, granularity_p), \ _SYS_DESC(SEG_TYPE_LDT) \ } #define DT_TSS_ENTRY(base_p, limit_p, granularity_p, dpl_p) \ { \ _DESC_COMMON(dpl_p), \ _LIMIT_AND_BASE(base_p, limit_p, granularity_p), \ _SYS_DESC(SEG_TYPE_TSS) \ } /* "standard" TSS segments that don't stuff extra data past the end of the * TSS struct */ #define DT_TSS_STD_ENTRY(base_p, dpl_p) \ DT_TSS_ENTRY(base_p, sizeof(struct task_state_segment), DT_GRAN_BYTE, \ dpl_p) #define DT_TASK_GATE_ENTRY(segment_p, dpl_p) \ { \ _DESC_COMMON(dpl_p), \ _SYS_DESC(SEG_TYPE_TASK_GATE), \ .segment_selector = (segment_p) \ } #define DT_IRQ_GATE_ENTRY(segment_p, offset_p, dpl_p) \ { \ _DESC_COMMON(dpl_p), \ _SEGMENT_AND_OFFSET(segment_p, offset_p), \ _SYS_DESC(SEG_TYPE_IRQ_GATE), \ .always_0_0 = 0 \ } #define DT_TRAP_GATE_ENTRY(segment_p, offset_p, dpl_p) \ { \ _DESC_COMMON(dpl_p), \ _SEGMENT_AND_OFFSET(segment_p, offset_p), \ _SYS_DESC(SEG_TYPE_TRAP_GATE), \ .always_0_0 = 0 \ } #define DT_CALL_GATE_ENTRY(segment_p, offset_p, dpl_p, param_count_p) \ { \ _DESC_COMMON(dpl_p), \ _SEGMENT_AND_OFFSET(segment_p, offset_p), \ _SYS_DESC(SEG_TYPE_TRAP_GATE), \ .reserved_or_param = (param_count_p), \ .always_0_0 = 0 \ } #define DTE_BASE(dt_entry) ((dt_entry)->base_low | \ ((dt_entry)->base_mid << 16) | \ ((dt_entry)->base_hi << 24)) #define DTE_LIMIT(dt_entry) ((dt_entry)->limit_low | \ ((dt_entry)->limit_hi << 16)) #define DTE_OFFSET(dt_entry) ((dt_entry)->offset_low | \ ((dt_entry)->offset_hi << 16)) #define DT_INIT(entries) { sizeof(entries) - 1, &entries[0] } #ifdef CONFIG_SET_GDT /* This is either the ROM-based GDT in crt0.S or generated by gen_gdt.py, * depending on CONFIG_GDT_DYNAMIC */ extern struct pseudo_descriptor _gdt; #endif extern const struct pseudo_descriptor z_idt; /** * Properly set the segment descriptor segment and offset * * Used for call/interrupt/trap gates * * @param sd Segment descriptor * @param offset Offset within segment * @param segment_selector Segment selector */ static inline void z_sd_set_seg_offset(struct segment_descriptor *sd, uint16_t segment_selector, uint32_t offset) { sd->offset_low = offset & 0xFFFFU; sd->offset_hi = offset >> 16U; sd->segment_selector = segment_selector; sd->always_0_0 = 0U; } /** * Initialize an segment descriptor to be a 32-bit IRQ gate * * @param sd Segment descriptor memory * @param seg_selector Segment selector of handler * @param offset offset of handler * @param dpl descriptor privilege level */ static inline void z_init_irq_gate(struct segment_descriptor *sd, uint16_t seg_selector, uint32_t offset, uint32_t dpl) { z_sd_set_seg_offset(sd, seg_selector, offset); sd->dpl = dpl; sd->descriptor_type = DT_TYPE_SYSTEM; sd->present = 1U; sd->type = SEG_TYPE_IRQ_GATE; } /** * Set current IA task TSS * * @param sel Segment selector in GDT for desired TSS */ static inline void _set_tss(uint16_t sel) { __asm__ __volatile__ ("ltr %0" :: "r" (sel)); } /** * Get the TSS segment selector in the GDT for the current IA task * * @return Segment selector for current IA task */ static inline uint16_t _get_tss(void) { uint16_t sel; __asm__ __volatile__ ("str %0" : "=r" (sel)); return sel; } /** * Get the current global descriptor table * * @param gdt Pointer to memory to receive GDT pseudo descriptor information */ static inline void _get_gdt(struct pseudo_descriptor *gdt) { __asm__ __volatile__ ("sgdt %0" : "=m" (*gdt)); } /** * Get the current interrupt descriptor table * * @param idt Pointer to memory to receive IDT pseudo descriptor information */ static inline void _get_idt(struct pseudo_descriptor *idt) { __asm__ __volatile__ ("sidt %0" : "=m" (*idt)); } /** * Get the current local descriptor table (LDT) * * @return Segment selector in the GDT for the current LDT */ static inline uint16_t _get_ldt(void) { uint16_t ret; __asm__ __volatile__ ("sldt %0" : "=m" (ret)); return ret; } /** * Set the local descriptor table for the current IA Task * * @param ldt Segment selector in the GDT for an LDT */ static inline void _set_ldt(uint16_t ldt) { __asm__ __volatile__ ("lldt %0" :: "m" (ldt)); } /** * Set the global descriptor table * * You will most likely need to update all the data segment registers * and do a far call to the code segment. * * @param gdt Pointer to GDT pseudo descriptor. */ static inline void _set_gdt(const struct pseudo_descriptor *gdt) { __asm__ __volatile__ ("lgdt %0" :: "m" (*gdt)); } /** * Set the interrupt descriptor table * * @param idt Pointer to IDT pseudo descriptor. */ static inline void z_set_idt(const struct pseudo_descriptor *idt) { __asm__ __volatile__ ("lidt %0" :: "m" (*idt)); } /** * Get the segment selector for the current code segment * * @return Segment selector */ static inline uint16_t _get_cs(void) { uint16_t cs = 0U; __asm__ __volatile__ ("mov %%cs, %0" : "=r" (cs)); return cs; } /** * Get the segment selector for the current data segment * * @return Segment selector */ static inline uint16_t _get_ds(void) { uint16_t ds = 0U; __asm__ __volatile__ ("mov %%ds, %0" : "=r" (ds)); return ds; } #endif /* _ASMLANGUAGE */ #ifdef __cplusplus } #endif #endif /* ZEPHYR_INCLUDE_ARCH_X86_IA32_SEGMENTATION_H_ */