Skip to main content

strat9_kernel/arch/x86_64/
smp.rs

1//! SMP (Symmetric Multi-Processing) boot for x86_64.
2//!
3//! Boots Application Processors (APs) using the legacy INIT+SIPI sequence.
4//! Inspired by Redox-OS's approach: a minimal trampoline does the 16→64 bit
5//! mode switch, then jumps directly to `smp_main` in Rust.
6//!
7//! Data layout after the trampoline code (written by BSP, read by AP):
8//!   offset +0: PML4 physical address (CR3)
9//!   offset +8: kernel stack top virtual address (RSP)
10//!
11//! Synchronization: AP increments `BOOTED_CORES` after finishing per-CPU init.
12//! BSP spins until all expected APs are online.
13
14use core::{
15    arch::global_asm,
16    sync::atomic::{AtomicBool, AtomicUsize, Ordering},
17};
18
19use alloc::{vec, vec::Vec};
20use x86_64::{
21    structures::paging::{Page, PageTableFlags, PhysFrame, Size4KiB},
22    PhysAddr, VirtAddr,
23};
24
25use crate::{
26    acpi::madt,
27    arch::x86_64::{apic, idt, io::io_wait, percpu, timer},
28    memory,
29};
30
31/// Physical address where the SMP trampoline is copied.
32pub const TRAMPOLINE_PHYS_ADDR: u64 = 0x8000;
33
34/// Number of booted cores (starts at 1 for BSP).
35static BOOTED_CORES: AtomicUsize = AtomicUsize::new(1);
36/// Counter for synchronization barriers.
37static SYNC_BARRIER: AtomicUsize = AtomicUsize::new(0);
38/// Target count for the rendezvous barrier (set by BSP before barrier).
39static BARRIER_TARGET: AtomicUsize = AtomicUsize::new(0);
40/// Gate used by BSP to release APs into scheduler/timer start.
41static AP_SCHED_GATE_OPEN: AtomicBool = AtomicBool::new(false);
42
43// ---------------------------------------------------------------------------
44// Trampoline: 16-bit → 32-bit → 64-bit mode switch.
45//
46// The AP starts in real mode at the SIPI vector (0x8000).  This stub:
47//   1. Loads a GDT embedded at known physical offsets.
48//   2. Enables protected mode, then PAE + long mode + paging.
49//   3. Loads the kernel PML4 (CR3) and kernel stack (RSP) from the data area.
50//   4. Jumps to smp_main (64-bit Rust code).
51//
52// LAYOUT (physical addresses, copied to 0x8000):
53//   0x8000:  cli ; cld ; ljmp 0, 0x8040         (16-bit)
54//   0x8010:  _gdt_table  (32 bytes: null, code64, data, code32)
55//   0x8030:  _gdt        (GDTR: limit=31, base=0x8010)
56//   0x8040:  real-mode setup (xor ax,ax; lgdt; enter PM)
57//   0x8060:  32-bit code (enable PAE + LME + paging)
58//   0x80C0:  64-bit code (load stack, jump to smp_main)
59//
60// CRITICAL: The GDT table and descriptor MUST occupy these exact offsets.
61// The `lgdt [0x8030]` instruction reads physical 0x8030 which contains the
62// GDTR. Without this embedded GDT data, the AP loads garbage and triple-faults.
63//
64// Data area (at smp_trampoline_end, written by BSP via HHDM):
65//   +0 u64: CR3 (PML4 physical address)
66//   +8 u64: RSP (kernel stack top virtual address)
67// ---------------------------------------------------------------------------
68#[cfg(target_arch = "x86_64")]
69global_asm!(
70    r#"
71.section .text
72.code16
73
74.global smp_trampoline
75.global smp_trampoline_end
76
77.set SMP_VAR_ADDR, 0x8000 + (smp_trampoline_end - smp_trampoline)
78
79smp_trampoline:
80    cli
81    cld
82    # Jump over the GDT data : code continues at 0x8040.
83    ljmp 0, 0x8040
84
85# -------------------------------------------------------------------
86# GDT : must be at physical offset 0x10 so that:
87#   _gdt_table starts at 0x8010, _gdt (GDTR) is at 0x8030.
88# -------------------------------------------------------------------
89.align 16
90_gdt_table:
91    .long 0, 0                       # null  (selector 0)
92    .long 0x0000ffff, 0x00af9a00     # code64 (selector 8):  64-bit ring-0
93    .long 0x0000ffff, 0x00cf9200     # data   (selector 16): ring-0 rw
94    .long 0x0000ffff, 0x00cf9a00     # code32 (selector 24): 32-bit ring-0
95_gdt:
96    .word _gdt - _gdt_table - 1      # limit = 31 (4 entries × 8 - 1)
97    .long 0x8010                     # base  = 0x8010
98    .long 0, 0                       # padding
99.align 64
100
101# -------------------------------------------------------------------
102# Real-mode setup continues at 0x8040.
103# -------------------------------------------------------------------
104    xor ax, ax
105    mov ds, ax
106    lgdt [0x8030]                    # loads GDTR from 0x8030
107
108    # Enter protected mode
109    mov eax, cr0
110    or eax, 1
111    mov cr0, eax
112    ljmp 24, 0x8060                  # → code32 segment
113
114.align 32
115.code32
116    mov ax, 16
117    mov ds, ax
118    mov ss, ax
119
120    # Enable PAE + PSE + OSFXSR + OSXMMEXCPT
121    # OSXSAVE is NOT set here : enabled conditionally in Rust
122    mov eax, cr4
123    or eax, 0x630
124    mov cr4, eax
125
126    # Enable Long Mode (EFER.LME)
127    mov ecx, 0xc0000080
128    xor edx, edx
129    rdmsr
130    or eax, 0x901                    # LME + SCE
131    wrmsr
132
133    # Load kernel PML4 from data area
134    mov eax, [SMP_VAR_ADDR]
135    mov cr3, eax
136
137    # Enable paging (activates long mode)
138    mov eax, cr0
139    and eax, 0xFFFFFFFB              # Clear EM
140    or eax, 0x80010002               # PG + WP + MP
141    mov cr0, eax
142
143    ljmp 8, 0x80c0                   # → code64 segment
144
145.align 32
146.code64
147    # Load kernel stack pointer
148    mov rsp, [SMP_VAR_ADDR + 8]
149
150    # Clear RFLAGS
151    push 0
152    popfq
153
154    # Jump to smp_main (Rust)
155    # NOTE: must use movabs (64-bit absolute), NOT RIP-relative lea.
156    # The trampoline is copied to 0x8000, so RIP-relative would compute
157    # an offset based on the wrong base address (the linker's original
158    # placement in the kernel .text section, not 0x8000).
159    movabs rax, offset smp_main
160    jmp rax
161
162.align 8
163smp_trampoline_end:
164"#
165);
166
167unsafe extern "C" {
168    fn smp_trampoline();
169    fn smp_trampoline_end();
170}
171
172/// Busy-wait for the given number of microseconds (very rough).
173fn udelay(us: u32) {
174    for _ in 0..us {
175        io_wait();
176    }
177}
178
179/// Identity-map the trampoline physical pages so the AP can execute the
180/// trampoline code in real mode / protected mode before paging is enabled.
181fn ensure_identity_mapping(phys_start: u64, length: usize) {
182    let start = phys_start & !0xFFFu64;
183    let end = (phys_start + length as u64 + 0xFFF) & !0xFFFu64;
184    let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE;
185
186    let mut addr = start;
187    while addr < end {
188        let virt = VirtAddr::new(addr);
189        if let Some(mapped) = crate::memory::paging::translate(virt) {
190            if mapped.as_u64() != addr {
191                log::warn!(
192                    "SMP: identity map collision at {:#x} -> {:#x}",
193                    addr,
194                    mapped.as_u64()
195                );
196            }
197        } else {
198            let page = Page::<Size4KiB>::containing_address(virt);
199            let frame = PhysFrame::<Size4KiB>::containing_address(PhysAddr::new(addr));
200            if let Err(e) = crate::memory::paging::map_page(page, frame, flags) {
201                log::error!("SMP: failed to identity map {:#x}: {}", addr, e);
202            }
203        }
204        addr += 0x1000;
205    }
206}
207
208/// Copy the trampoline to physical address 0x8000 and write the data area.
209///
210/// Data area layout (at smp_trampoline_end):
211///   +0: CR3 (PML4 physical address)
212///   +8: RSP (kernel stack top virtual address)
213///
214/// After writing, performs WBINVD to flush the cache hierarchy to RAM.
215/// This is essential on real hardware: the BSP writes the trampoline via
216/// HHDM (WB cacheable), but the AP boots in real mode where the effective
217/// memory type is determined by MTRRs. If MTRRs mark the region as UC, or
218/// if platform firmware does not guarantee cache coherency, the AP would
219/// read stale data from RAM without this flush.
220fn copy_trampoline(cr3_phys: u64, stack_top_virt: u64) {
221    let tramp_len = (smp_trampoline_end as *const u8 as usize)
222        .saturating_sub(smp_trampoline as *const u8 as usize);
223
224    ensure_identity_mapping(TRAMPOLINE_PHYS_ADDR, tramp_len + 16);
225
226    let tramp_virt = memory::phys_to_virt(TRAMPOLINE_PHYS_ADDR) as *mut u8;
227
228    // SAFETY: trampoline destination is mapped and writable in HHDM.
229    unsafe {
230        core::ptr::copy_nonoverlapping(smp_trampoline as *const u8, tramp_virt, tramp_len);
231        let data = tramp_virt.add(tramp_len) as *mut u64;
232        // +0: CR3
233        core::ptr::write_volatile(data, cr3_phys);
234        // +8: RSP (stack top virtual address)
235        core::ptr::write_volatile(data.add(1), stack_top_virt);
236
237        // SFENCE + WBINVD: ensure all stores reach RAM before the AP starts.
238        core::arch::asm!("sfence");
239        core::arch::asm!("wbinvd");
240    }
241}
242
243/// Wait for ICR delivery to complete.
244///
245/// In x2APIC mode, the ICR MSR write is synchronous : the CPU blocks until
246/// the IPI is dispatched, so there is nothing to wait for.  In xAPIC mode,
247/// we poll the delivery-pending bit (ICR_LOW bit 12) until it clears.
248fn wait_delivery() {
249    // x2APIC: MSR write is synchronous, no pending bit to poll.
250    if apic::is_x2apic_enabled() {
251        return;
252    }
253
254    const DELIVERY_PENDING: u32 = 1 << 12;
255    for i in 0..1_000_000 {
256        let val = unsafe { apic::read_reg(apic::REG_ICR_LOW) };
257        if val & DELIVERY_PENDING == 0 {
258            return;
259        }
260        if i > 0 && i % 200_000 == 0 {
261            crate::serial_println!(
262                "[smp] wait_delivery: still pending (iter={}, icr={:#x})",
263                i,
264                val,
265            );
266        }
267        core::hint::spin_loop();
268    }
269    let final_val = unsafe { apic::read_reg(apic::REG_ICR_LOW) };
270    crate::serial_println!("[smp] wait_delivery: TIMEOUT, final icr={:#x}", final_val,);
271    log::warn!("SMP: IPI delivery timeout");
272}
273
274/// Send an IPI and wait for delivery.
275fn send_ipi(apic_id: u32, value: u32) {
276    apic::send_ipi_raw(apic_id, value);
277    wait_delivery();
278}
279
280/// Send INIT + SIPI×2 to an AP per Intel SDM Volume 3, Section 10.6.7.1.
281///
282/// The Intel SDM recommends sending SIPI twice (200 µs apart) so that if
283/// the first SIPI is missed, the second one still catches the AP. Some
284/// platforms (Redox-OS) succeed with a single SIPI, but sending two is
285/// more robust and matches Linux/FreeBSD behaviour.
286///
287///   Step 1: INIT level-assert (0xC500) : wait ≥10 ms
288///   Step 2: INIT level-de-assert (0x8500) : wait ≥200 µs
289///   Step 3: SIPI (0x4608, vector=0x08 → trampoline at 0x8000) : 200 µs
290///   Step 4: SIPI again : 200 µs
291///
292/// ICR values (xAPIC format, delivery-mode bit-fields):
293///   INIT:  delivery=INIT(101), level=1(assert), trigger=1(level)
294///   Deassert: delivery=INIT(101), level=0(de-assert), trigger=1(level)
295///   SIPI:  delivery=STARTUP(110), level=1, vector=0x8
296fn send_init_sipi(apic_id: u32) {
297    crate::serial_println!("[smp] send_init_sipi: apic_id={}", apic_id);
298
299    // Step 1: INIT level-assert
300    crate::serial_println!("[smp]   INIT assert -> {}", apic_id);
301    send_ipi(apic_id, 0xC500);
302    udelay(10_000);
303
304    // Step 2: INIT level-de-assert
305    crate::serial_println!("[smp]   INIT de-assert -> {}", apic_id);
306    send_ipi(apic_id, 0x8500);
307    udelay(200);
308
309    // Step 3: SIPI #1
310    crate::serial_println!("[smp]   SIPI (1/2) -> {} (vector=0x8)", apic_id);
311    send_ipi(apic_id, 0x0608);
312    udelay(200);
313
314    // Step 4: SIPI #2  (SDM recommends two SIPIs)
315    crate::serial_println!("[smp]   SIPI (2/2) -> {} (vector=0x8)", apic_id);
316    send_ipi(apic_id, 0x0608);
317    udelay(200);
318
319    crate::serial_println!("[smp]   INIT+SIPI complete for {}", apic_id);
320}
321
322/// Broadcast a halt command to all other CPUs.
323///
324/// Used during panic to stop the system and prevent log corruption.
325pub fn broadcast_panic_halt() {
326    if !apic::is_initialized() {
327        return;
328    }
329    let icr_low = (0b11 << 18) | (0b100 << 8) | (1 << 14);
330    apic::send_ipi_raw(0, icr_low);
331}
332
333/// Wait at a synchronization barrier until all expected CPUs arrive.
334fn rendezvous_barrier() {
335    let expected = BARRIER_TARGET.load(Ordering::Acquire);
336    SYNC_BARRIER.fetch_add(1, Ordering::AcqRel);
337    while SYNC_BARRIER.load(Ordering::Acquire) < expected {
338        core::hint::spin_loop();
339    }
340}
341
342/// Boot Application Processors.
343///
344/// AP kernel stack frames are allocated from the buddy allocator during this
345/// function and intentionally leaked (never freed). The allocation happens
346/// once at boot while only the BSP is online, so there is no lock contention
347/// and no risk of heap exhaustion under concurrent pressure.
348///
349/// If CPU hotplug is added in the future, stack allocation must be moved to
350/// a sleepable context (e.g. `Mutex`-protected) to avoid heap allocation
351/// under any spinlock that could be held during hot-add.
352pub fn init() -> Result<usize, &'static str> {
353    crate::serial_println!("[smp] init: entering SMP initialization");
354    if !apic::is_initialized() {
355        crate::serial_println!("[smp] init: ERROR - APIC not initialized");
356        return Err("APIC not initialized");
357    }
358
359    BOOTED_CORES.store(1, Ordering::Release);
360    SYNC_BARRIER.store(0, Ordering::Release);
361    BARRIER_TARGET.store(0, Ordering::Release);
362
363    let madt_info = madt::parse_madt().ok_or("MADT not available")?;
364    let bsp_apic_id = apic::lapic_id();
365
366    crate::serial_println!(
367        "[smp] init: MADT parsed, {} local APICs, BSP apic_id={}",
368        madt_info.local_apic_count,
369        bsp_apic_id,
370    );
371
372    if madt_info.local_apic_count <= 1 {
373        crate::serial_println!("[smp] init: single CPU system, returning");
374        log::info!("SMP: single CPU system");
375        return Ok(1);
376    }
377
378    let mut max_apic_id: usize = 0;
379    for i in 0..madt_info.local_apic_count {
380        if let Some(ref entry) = madt_info.local_apics[i] {
381            crate::serial_println!(
382                "[smp]   APIC[{}]: id={} proc={} flags={:#x} {}",
383                i,
384                entry.apic_id,
385                entry.processor,
386                entry.flags,
387                if entry.flags & 1 == 0 {
388                    "(DISABLED)"
389                } else {
390                    ""
391                },
392            );
393            max_apic_id = max_apic_id.max(entry.apic_id as usize);
394        }
395    }
396
397    let mut stack_tops: Vec<u64> = vec![0; max_apic_id + 1];
398    let cr3_phys = crate::memory::paging::kernel_l4_phys().as_u64();
399    let mut targets: Vec<u32> = Vec::new();
400    let mut expected: usize = 1;
401
402    for i in 0..madt_info.local_apic_count {
403        let Some(ref entry) = madt_info.local_apics[i] else {
404            continue;
405        };
406
407        let apic_id = entry.apic_id as u32;
408        if apic_id == bsp_apic_id {
409            continue;
410        }
411
412        // Allocate AP kernel stack from the buddy allocator.
413        let stack_size = crate::process::task::Task::DEFAULT_STACK_SIZE;
414        let pages = (stack_size + 4095) / 4096;
415        let order = pages.next_power_of_two().trailing_zeros() as u8;
416        let frame = crate::sync::with_irqs_disabled(|token| {
417            crate::memory::allocate_phys_contiguous(token, order)
418        })
419        .map_err(|_| "SMP: failed to allocate AP stack from buddy")?;
420        let stack_phys = frame.start_address.as_u64();
421        let stack_virt = crate::memory::phys_to_virt(stack_phys);
422
423        // Zero the stack.
424        unsafe { core::ptr::write_bytes(stack_virt as *mut u8, 0, stack_size) };
425
426        // Stack grows downward: top = base_virt + size.
427        let stack_top = stack_virt.saturating_add(stack_size as u64);
428
429        if apic_id as usize >= stack_tops.len() {
430            log::warn!("SMP: APIC id {} out of stack array range", apic_id);
431            continue;
432        }
433
434        stack_tops[apic_id as usize] = stack_top;
435
436        let cpu_index =
437            percpu::register_cpu(apic_id).ok_or("SMP: exceeded MAX_CPUS for per-CPU data")?;
438        percpu::set_kernel_stack_top(cpu_index, stack_top);
439
440        targets.push(apic_id);
441        expected += 1;
442    }
443
444    // Copy trampoline and write data area (CR3 + first AP's stack top).
445    // All APs share the same CR3; each gets its own stack from stack_tops.
446    // We write the first target's stack top; subsequent APs will use their
447    // own stack (set up in smp_main via percpu::kernel_stack_top).
448    let first_stack_top = targets
449        .first()
450        .and_then(|id| stack_tops.get(*id as usize))
451        .copied()
452        .unwrap_or(0);
453    copy_trampoline(cr3_phys, first_stack_top);
454    crate::serial_println!(
455        "[smp] init: trampoline at {:#x}, cr3={:#x}, stack={:#x}",
456        TRAMPOLINE_PHYS_ADDR,
457        cr3_phys,
458        first_stack_top,
459    );
460
461    // Send INIT + single SIPI to each AP (Redox-OS style, no broadcast INIT).
462    crate::serial_println!("[smp] init: sending INIT+SIPI to {} APs", targets.len(),);
463    for apic_id in &targets {
464        // Each AP needs its own stack. Re-write the data area stack pointer
465        // before each SIPI so the AP picks up the correct stack.
466        if let Some(stack_top) = stack_tops.get(*apic_id as usize) {
467            let tramp_len = (smp_trampoline_end as *const u8 as usize)
468                .saturating_sub(smp_trampoline as *const u8 as usize);
469            let data = memory::phys_to_virt(TRAMPOLINE_PHYS_ADDR) as *mut u64;
470            unsafe {
471                // +8: RSP
472                core::ptr::write_volatile(data.add(tramp_len / 8 + 1), *stack_top);
473            }
474        }
475        send_init_sipi(*apic_id);
476    }
477
478    // Wait for APs to come online (they increment BOOTED_CORES in smp_main).
479    let mut spins: u64 = 0;
480    const MAX_SPINS: u64 = 200_000_000;
481    crate::serial_println!("[smp] init: waiting for APs (expected={})...", expected,);
482    while BOOTED_CORES.load(Ordering::Acquire) < expected && spins < MAX_SPINS {
483        if spins > 0 && spins % 50_000_000 == 0 {
484            crate::serial_println!(
485                "[smp] init: waiting... online={} expected={} spins={}",
486                BOOTED_CORES.load(Ordering::Acquire),
487                expected,
488                spins,
489            );
490        }
491        core::hint::spin_loop();
492        spins = spins.saturating_add(1);
493    }
494    let online = BOOTED_CORES.load(Ordering::Acquire);
495    crate::serial_println!(
496        "[smp] init: AP wait done: online={} expected={} spins={}",
497        online,
498        expected,
499        spins,
500    );
501    if online < expected {
502        crate::serial_println!(
503            "[smp] init: WARNING only {}/{} APs online",
504            online,
505            expected,
506        );
507        log::warn!(
508            "SMP: timeout waiting APs (online={} expected={}), continuing",
509            online,
510            expected
511        );
512    }
513
514    log::info!("SMP: {} cores online (expected {})", online, expected);
515
516    // Publish barrier target so APs can proceed.
517    BARRIER_TARGET.store(online, Ordering::Release);
518    rendezvous_barrier();
519
520    Ok(online)
521}
522
523/// First Rust function executed on APs after the trampoline.
524///
525/// The trampoline enables paging with the kernel's PML4, sets up the stack,
526/// and jumps here. All virtual addresses are valid at this point.
527#[unsafe(no_mangle)]
528pub extern "C" fn smp_main() -> ! {
529    // Early serial output : use raw port 0x3F8 directly since the AP
530    // hasn't initialized the serial mutex yet.
531    {
532        use core::fmt::Write;
533        let mut port = unsafe { uart_16550::SerialPort::new(0x3F8) };
534        port.init();
535        let _ = port.write_fmt(format_args!("[smp][ap] entered smp_main\n"));
536    }
537
538    idt::load();
539
540    // Re-initialize Local APIC for this core.
541    apic::init_ap();
542
543    let apic_id = apic::lapic_id();
544    {
545        use core::fmt::Write;
546        let mut port = unsafe { uart_16550::SerialPort::new(0x3F8) };
547        let _ = port.write_fmt(format_args!("[smp][ap] apic_id={}\n", apic_id));
548    }
549
550    let cpu_index = match percpu::cpu_index_by_apic(apic_id) {
551        Some(idx) => idx,
552        None => {
553            {
554                use core::fmt::Write;
555                let mut port = unsafe { uart_16550::SerialPort::new(0x3F8) };
556                let _ = port.write_fmt(format_args!(
557                    "[smp][ap] ERROR: apic_id={} not registered, halting\n",
558                    apic_id
559                ));
560            }
561            loop {
562                core::hint::spin_loop();
563            }
564        }
565    };
566
567    // Initialize per-CPU GS base.
568    crate::arch::x86_64::percpu::init_gs_base(cpu_index);
569
570    // Initialize per-CPU TSS/GDT.
571    crate::arch::x86_64::tss::init_cpu(cpu_index);
572    crate::arch::x86_64::gdt::init_cpu(cpu_index);
573
574    crate::arch::x86_64::syscall::init();
575    crate::arch::x86_64::init_cpu_extensions();
576
577    if let Some(stack_top) = percpu::kernel_stack_top(cpu_index) {
578        crate::arch::x86_64::tss::set_kernel_stack_for(cpu_index, x86_64::VirtAddr::new(stack_top));
579    }
580
581    let _ = percpu::mark_online_by_apic(apic_id);
582    BOOTED_CORES.fetch_add(1, Ordering::Release);
583
584    {
585        use core::fmt::Write;
586        let mut port = unsafe { uart_16550::SerialPort::new(0x3F8) };
587        let _ = port.write_fmt(format_args!(
588            "[smp][ap] cpu_index={} online, waiting for barrier\n",
589            cpu_index
590        ));
591    }
592
593    // AP spins until BSP publishes the barrier target.
594    while BARRIER_TARGET.load(Ordering::Acquire) == 0 {
595        core::hint::spin_loop();
596    }
597    rendezvous_barrier();
598
599    crate::serial_println!("[trace][ap] cpu_index={} entering scheduler", cpu_index);
600
601    // Wait until BSP has finished scheduler initialization.
602    while !AP_SCHED_GATE_OPEN.load(Ordering::Acquire) {
603        core::hint::spin_loop();
604    }
605
606    // Start APIC timer on this CPU.
607    timer::start_apic_timer_cached();
608
609    // Start per-CPU scheduler (never returns).
610    crate::process::scheduler::schedule_on_cpu(cpu_index)
611}
612
613/// Return the number of online CPUs.
614pub fn cpu_count() -> usize {
615    BOOTED_CORES.load(Ordering::Acquire)
616}
617
618/// Allow APs to start their local timer and enter the scheduler.
619pub fn open_ap_scheduler_gate() {
620    AP_SCHED_GATE_OPEN.store(true, Ordering::Release);
621}