1/* @title: IRQLs */
2
3/* @idea:big IRQLs */
4/* # Big Idea: IRQLs (STABLE)
5 *
6 * ## Credits: gummi
7 *
8 * ## Audience:
9 * Everyone
10 *
11 * > IRQLs play a major role in the preemption and interrupt
12 * control mechanisms of this kernel. Thus, this document regarding
13 * the details of the IRQLs and usages is quite important.
14 *
15 * ## Overview:
16 * IRQLs provide a centralized preemption and interrupt control mechanism.
17 * IRQLs are software only, meaning they are strictly enforced only by
18 * software and used as an abstraction in software.
19 *
20 * ## Background:
21 * IRQLs are a feature of quite a few other kernels. Windows NT is the
22 * most prominent of the many kernels that have IRQLs. Similar concepts exist
23 * in other kernels, however.
24 *
25 * ## Summary:
26 * Each logical processor (or CPU) on the machine has its own IRQL.
27 *
28 * At each IRQL, the effects of lower IRQLs are also applied.
29 * There are 5 IRQLs, ordered from the least restrictive to most restrictive:
30 *
31 * - PASSIVE (standard code execution, nothing blocked)
32 * - APC (APCs[^1] blocked, preemption blocked)
33 * - DISPATCH (DPCs[^2] blocked)
34 * - DEVICE (same as DISPATCH, currently reserved for future use)
35 * - HIGH (hardware interrupts blocked)
36 *
37 * To change the IRQL, a function call to raise or lower it must be made.
38 * This is because the raise/lower operation must invoke otherfunctions
39 * to block other events (such as preemption or interrupts). This means
40 * that you cannot change the current IRQL willy-nilly and expect everything
41 * to work out just fine.
42 *
43 * ### Bootstage exception:
44 *
45 * There is also another "pseudo-IRQL" that is used as a placeholder so that
46 * IRQL functions can be called at early bootstages[^3] (making the IRQL
47 * functions effectively no-ops), which is NONE.
48 *
49 * ## API:
50 * There are three primary IRQL related functions:
51 *
52 * `irql_raise()`, `irql_lower()`, and `irql_get()`.
53 *
54 * `irql_raise()` raises the IRQL to a new IRQL, returning
55 * the previous IRQL. If the machine has not reached the LATE_DEVICES
56 * bootstage, it will not do anything and will return the NONE IRQL.
57 *
58 * If this new IRQL is equal to the current IRQL, nothing is done.
59 * However, if the new IRQL is *lower* than the current IRQL, the function
60 * will panic.
61 *
62 * `irql_lower()` lowers the IRQL to a new IRQL. If the bootstage has
63 * not reached the LATE_DEVICES stage, *or* if the NONE IRQL is passed in,
64 * the function will not do anything.
65 *
66 * If the new IRQL is equal to the current IRQL, nothing is done.
67 * However, if the new IRQL is *higher* than the current IRQL, the function
68 * will panic.
69 *
70 * `irql_lower()` will also check if any APCs and DPCs have came in, and
71 * execute them if the new IRQL permits the execution of these procedures.
72 *
73 * `irql_lower()` will also check if a reschedule is needed, and appropriately
74 * call `scheduler_yield()` if preemption is enabled.
75 *
76 * An example of a common IRQL usage pattern for disabling preemption:
77 *
78 * ```c
79 * enum irql old_irql = irql_raise(IRQL_DISPATCH_LEVEL); // disable preemption
80 *
81 * // do critical work
82 *
83 * irql_lower(old_irql);
84 * ```
85 *
86 * `irql_get()` will return the current IRQL of the caller's logical
87 * processor.
88 *
89 * ## Errors:
90 * No errors besides the possible panics described above are possible.
91 *
92 * ## Context:
93 * IRQL usage interacts with many subsystems, but primarily interacts with
94 * synchronization, interrupt handlers, and scheduling.
95 *
96 * ## Constraints:
97 * The `irql_raise()` and `irql_lower()` functions cannot call themselves.
98 * Thus, inside the functions, whenever interrupts need to be disabled,
99 * they use the raw `disable_interrupts()` and `enable_interrupts()`
100 * assembly routines.
101 *
102 * ## Internals:
103 * None
104 *
105 * ## Strategy:
106 * To raise the IRQL, the necessary operations are performed depending on
107 * the IRQL being raised to. (disable preemption, interrupts, etc.).
108 *
109 * To lower the IRQL, we simply re-enable the blocked event types, and
110 * attempt DPC and APC execution if we are currently running in the
111 * context of a thread.
112 *
113 * ## Rationale:
114 * IRQLs were introduced as the preemption/interrupt control mechanism
115 * of this kernel because of the structure and strict rules they
116 * provide and enforce. DPCs and APCs also require IRQLs.
117 *
118 * ## Changelog:
119 * 11/22/2025 - gummi: created file
120 * 11/23/2025 - gummi: move references
121 *
122 * ## Notes:
123 * I primarily decided to bring IRQLs to this kernel because I had read
124 * code from kernels without IRQLs (with the constant
125 * enable/disable_interrupts, preempt_disable_enable sprinkled about),
126 * and thought "This could really be simplified with a state machine!",
127 * but then realized that VMS and NT basically did that already.
128 * So, I chose to introduce IRQLs.
129 *
130 * [^1]: "APCs"
131 * [^2]: "DPCs"
132 * [^3]: "Bootstages"
133 *
134 */
135
136#pragma once
137
138enum irql {
139 IRQL_PASSIVE_LEVEL = 0, /* Normal execution */
140 IRQL_APC_LEVEL = 1, /* No preemption */
141 IRQL_DISPATCH_LEVEL = 2, /* No DPCs */
142 IRQL_DEVICE_LEVEL = 3, /* Device interrupts */
143 IRQL_HIGH_LEVEL = 4, /* All interrupts masked */
144 IRQL_NONE = -1,
145};
146
147static inline const char *irql_to_str(enum irql level) {
148 switch (level) {
149 case IRQL_PASSIVE_LEVEL: return "PASSIVE LEVEL";
150 case IRQL_APC_LEVEL: return "APC LEVEL";
151 case IRQL_DISPATCH_LEVEL: return "DISPATCH LEVEL";
152 case IRQL_DEVICE_LEVEL: return "DEVICE LEVEL";
153 case IRQL_HIGH_LEVEL: return "HIGH LEVEL";
154 case IRQL_NONE: return "NONE";
155 }
156 return "UNKNOWN";
157}
158
159enum irql irql_raise(enum irql new_level);
160void irql_lower(enum irql old_level);
161enum irql irql_get();
162