aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md114
1 files changed, 114 insertions, 0 deletions
diff --git a/README.md b/README.md
index 9f1088e..79efeda 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,117 @@
# CPU Internals Driver (cpui-drv)
A driver that allows users to interact with various internal components of the CPU. Implemented and tested with Linux kernel v6.2
+
+## CPU IDentification (CPUID)
+
+The software needs a way to find out if particular features are available (for example, 64-bit mode, Hardware Virtualization, HyperThreading, etc). The CPUID instruction is a mechanism that can be used to retrieve such information to understand whether or not CPU supports these features.
+
+CPUID does NOT have operands. Rather it takes input as a value preloaded into _eax_ (it's _eax_ even on 64-bit systems, not _rax_) and possibly _ecx_. After the instruction is executed, the outputs are stored to _eax_, _ebx_, _ecx_, and _edx_.
+
+### How to Check for CPUID Support
+
+CPUID didn't exist on the first systems so we actually have to check if the hardware supports CPUID (added in late i486 models):
+
+```
+The ID flag (bit 21) in the EFLAGS register indicates support for the CPUID instruction. If a software procedure can set and clear this flag, the processor executing the procedure supports the CPUID instruction.
+```
+
+To manipulate with the EFLAGS register values, we need `PUSHF` / `POPF` instructions, they push / pop lower 16 bits of EFLAGS to the stack.
+
+That's how it's done in the Linux kernel ([arch/x86/kernel/cpu/common.c](https://github.com/torvalds/linux/blob/c9c3395d5e3dcc6daee66c6908354d47bf98cb0c/arch/x86/kernel/cpu/common.c#L304)):
+
+``` c
+/* Standard macro to see if a specific flag is changeable */
+static inline int flag_is_changeable_p(u32 flag)
+{
+ u32 f1, f2;
+ asm volatile (
+ "pushfl \n\t" // Push the current value of the EFLAGS
+ "pushfl \n\t" // register onto the stack twice.
+ "popl %0 \n\t" // Pop the original EFLAGS value into the var `f1`
+ "movl %0, %1 \n\t" // Move the value of `f1` into `f2`
+ "xorl %2, %0 \n\t" // XOR op. between `flag` and `f1`
+ "pushl %0 \n\t" // Push the modified value of `f1` onto the stack and..
+ "popfl \n\t" // Pop it into the EFLAGS register
+ "pushfl \n\t" // Push the current value of the EFLAGS
+ // register onto the stack
+ "popl %0 \n\t" // Pop it into `f2`
+ "popfl \n\t" // Restore the original value of the EFLAGS
+
+ : "=&r" (f1), "=&r" (f2)
+ : "ir" (flag));
+
+ return ((f1^f2) & flag) != 0;
+}
+
+/* Probe for the CPUID instruction */
+int have_cpuid_p(void)
+{
+ return flag_is_changeable_p(X86_EFLAGS_ID);
+}
+```
+
+`X86_EFLAGS_ID` is defined in [arch/x86/include/uapi/asm/processor-flags.h](https://github.com/torvalds/linux/blob/c9c3395d5e3dcc6daee66c6908354d47bf98cb0c/arch/x86/include/uapi/asm/processor-flags.h#L46) :
+
+``` c
+#define X86_EFLAGS_ID_BIT 21 /* CPUID detection */
+#define X86_EFLAGS_ID _BITUL(X86_EFLAGS_ID_BIT)
+```
+
+To obtain the table with possible inputs and outputs, use _Intel 64 and IA-32 Architectures Software Developer's Manual, Volume 2, Chapter 3.3 Instructions, CPUID - CPU Identification_.
+
+## Model Specific Registers (MSRs)
+
+After the fact, when you find out that some features you want to use are supported, you want to have a mechanism for configuring them. Over time, this list of MSRs has grown so large that it has become a separate volume of the Intel manuals - _Volume 4: Model-Specific Registers_.
+
+Many MSRs have carried over from one generation of IA-32 processors to the next and to Intel 64 processors. A subset of MSRs and associated bit fields, which do **NOT** change on future processor generations, are now considered **architectural MSRs**. For historical reasons (beginning with the Pentium 4 processor), these “**architectural MSRs**” were given the prefix “**IA32_**” (it doesn't mean it's restricted to 32-bit execution).
+
+`RDMSR` / `WRMSR` are privileged instructions, so they cannot be used in user-space, only in kernel-mode.
+
+Take a look how the Linux kernel uses these instructions in C code ([arch/x86/include/asm/msr.h](https://github.com/torvalds/linux/blob/c9c3395d5e3dcc6daee66c6908354d47bf98cb0c/arch/x86/include/asm/msr.h)):
+
+``` c
+/*
+ * both i386 and x86_64 returns 64-bit value in edx:eax, but gcc's "A"
+ * constraint has different meanings. For i386, "A" means exactly
+ * edx:eax, while for x86_64 it doesn't mean rdx:rax or edx:eax. Instead,
+ * it means rax *or* rdx.
+ */
+#ifdef CONFIG_X86_64
+ /* Using 64-bit values saves one instruction clearing the high half of low */
+ #define DECLARE_ARGS(val, low, high) unsigned long low, high
+ #define EAX_EDX_VAL(val, low, high) ((low) | (high) << 32)
+ #define EAX_EDX_RET(val, low, high) "=a" (low), "=d" (high)
+#else
+ #define DECLARE_ARGS(val, low, high) unsigned long long val
+ #define EAX_EDX_VAL(val, low, high) (val)
+ #define EAX_EDX_RET(val, low, high) "=A" (val)
+#endif
+
+...
+
+static __always_inline unsigned long long __rdmsr(unsigned int msr)
+{
+ DECLARE_ARGS(val, low, high);
+
+ asm volatile("1: rdmsr\n"
+ "2:\n"
+ _ASM_EXTABLE_TYPE(1b, 2b, EX_TYPE_RDMSR)
+ : EAX_EDX_RET(val, low, high) : "c" (msr));
+
+ return EAX_EDX_VAL(val, low, high);
+}
+
+static __always_inline void __wrmsr(unsigned int msr, u32 low, u32 high)
+{
+ asm volatile("1: wrmsr\n"
+ "2:\n"
+ _ASM_EXTABLE_TYPE(1b, 2b, EX_TYPE_WRMSR)
+ : : "c" (msr), "a"(low), "d" (high) : "memory");
+}
+```
+
+## Resources
+
+* [Intel 64 and IA-32 Architectures Software Developer's Manual, Combined Volumes: 1, 2A, 2B, 2C, 2D, 3A, 3B, 3C, 3D and 4](https://cdrdv2.intel.com/v1/dl/getContent/671200)
+* [AMD CPUID Specification](https://www.amd.com/system/files/TechDocs/25481.pdf)