⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/virt/
hotplug.rs

1//! Hot-plug support for CPUs, memory, and PCI devices
2//!
3//! Implements ACPI Generic Event Device (GED), CPU hot-plug with lifecycle
4//! management, memory DIMM hot-add/remove, PCI SHPC and PCIe native hot-plug.
5//!
6//! Sprints W5-S9 (CPU + memory hot-plug), W5-S10 (PCI hot-plug).
7
8#![allow(dead_code)]
9
10#[cfg(feature = "alloc")]
11extern crate alloc;
12
13#[cfg(feature = "alloc")]
14use alloc::{collections::VecDeque, vec, vec::Vec};
15
16use super::VmError;
17
18// ---------------------------------------------------------------------------
19// Constants
20// ---------------------------------------------------------------------------
21
22/// Maximum CPUs for hot-plug
23const MAX_HOTPLUG_CPUS: usize = 256;
24
25/// Maximum memory DIMMs
26const MAX_DIMMS: usize = 64;
27
28/// Maximum PCI hot-plug slots
29const MAX_PCI_SLOTS: usize = 32;
30
31/// Maximum ACPI GED events in queue
32const MAX_GED_EVENTS: usize = 64;
33
34/// Bits per u64 in CPU bitmap
35const BITS_PER_WORD: usize = 64;
36
37// ---------------------------------------------------------------------------
38// Hot-plug Type
39// ---------------------------------------------------------------------------
40
41/// Type of hot-plug event
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum HotplugType {
44    /// CPU hot-plug (add or remove)
45    Cpu,
46    /// Memory hot-plug (add or remove DIMM)
47    Memory,
48    /// PCI device hot-plug
49    PciDevice,
50}
51
52// ---------------------------------------------------------------------------
53// Hot-plug Event
54// ---------------------------------------------------------------------------
55
56/// A hot-plug event notification
57#[derive(Debug, Clone)]
58pub struct HotplugEvent {
59    /// Type of device being hot-plugged
60    pub event_type: HotplugType,
61    /// Device-specific info (slot/cpu_id/dimm_id encoded as u64)
62    pub device_info: u64,
63    /// Timestamp (monotonic counter)
64    pub timestamp: u64,
65    /// Whether this is an add (true) or remove (false) event
66    pub is_add: bool,
67}
68
69impl HotplugEvent {
70    /// Create a new hot-plug event
71    pub fn new(event_type: HotplugType, device_info: u64, timestamp: u64, is_add: bool) -> Self {
72        Self {
73            event_type,
74            device_info,
75            timestamp,
76            is_add,
77        }
78    }
79
80    /// Create a CPU add event
81    pub fn cpu_add(cpu_id: u32, timestamp: u64) -> Self {
82        Self::new(HotplugType::Cpu, cpu_id as u64, timestamp, true)
83    }
84
85    /// Create a CPU remove event
86    pub fn cpu_remove(cpu_id: u32, timestamp: u64) -> Self {
87        Self::new(HotplugType::Cpu, cpu_id as u64, timestamp, false)
88    }
89
90    /// Create a memory add event
91    pub fn memory_add(dimm_slot: u32, timestamp: u64) -> Self {
92        Self::new(HotplugType::Memory, dimm_slot as u64, timestamp, true)
93    }
94
95    /// Create a memory remove event
96    pub fn memory_remove(dimm_slot: u32, timestamp: u64) -> Self {
97        Self::new(HotplugType::Memory, dimm_slot as u64, timestamp, false)
98    }
99
100    /// Create a PCI device add event
101    pub fn pci_add(slot_id: u32, timestamp: u64) -> Self {
102        Self::new(HotplugType::PciDevice, slot_id as u64, timestamp, true)
103    }
104
105    /// Create a PCI device remove event
106    pub fn pci_remove(slot_id: u32, timestamp: u64) -> Self {
107        Self::new(HotplugType::PciDevice, slot_id as u64, timestamp, false)
108    }
109}
110
111// ---------------------------------------------------------------------------
112// ACPI Generic Event Device (GED)
113// ---------------------------------------------------------------------------
114
115/// ACPI Generic Event Device for hot-plug notifications
116#[cfg(feature = "alloc")]
117pub struct AcpiGed {
118    /// Pending events queue
119    events: VecDeque<HotplugEvent>,
120    /// Event counter for timestamps
121    event_counter: u64,
122    /// Whether the GED is enabled
123    enabled: bool,
124}
125
126#[cfg(feature = "alloc")]
127impl Default for AcpiGed {
128    fn default() -> Self {
129        Self::new()
130    }
131}
132
133#[cfg(feature = "alloc")]
134impl AcpiGed {
135    /// Create a new ACPI GED
136    pub fn new() -> Self {
137        Self {
138            events: VecDeque::new(),
139            event_counter: 0,
140            enabled: true,
141        }
142    }
143
144    /// Inject a hot-plug event
145    pub(crate) fn inject_event(&mut self, mut event: HotplugEvent) -> Result<(), VmError> {
146        if !self.enabled {
147            return Err(VmError::DeviceError);
148        }
149        if self.events.len() >= MAX_GED_EVENTS {
150            return Err(VmError::DeviceError);
151        }
152        self.event_counter = self.event_counter.saturating_add(1);
153        event.timestamp = self.event_counter;
154        self.events.push_back(event);
155        Ok(())
156    }
157
158    /// Poll the next pending event
159    pub(crate) fn poll_event(&mut self) -> Option<HotplugEvent> {
160        self.events.pop_front()
161    }
162
163    /// Check if there are pending events
164    pub(crate) fn has_pending_events(&self) -> bool {
165        !self.events.is_empty()
166    }
167
168    /// Get number of pending events
169    pub(crate) fn pending_count(&self) -> usize {
170        self.events.len()
171    }
172
173    /// Clear all pending events
174    pub(crate) fn clear(&mut self) {
175        self.events.clear();
176    }
177
178    /// Enable or disable the GED
179    pub(crate) fn set_enabled(&mut self, enabled: bool) {
180        self.enabled = enabled;
181    }
182
183    /// Check if enabled
184    pub(crate) fn is_enabled(&self) -> bool {
185        self.enabled
186    }
187}
188
189// ---------------------------------------------------------------------------
190// CPU Lifecycle State
191// ---------------------------------------------------------------------------
192
193/// CPU hot-plug lifecycle state
194#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
195pub enum CpuLifecycleState {
196    /// CPU slot is empty (not allocated)
197    #[default]
198    Empty,
199    /// CPU has been allocated (resources reserved)
200    Allocated,
201    /// CPU is being initialized (firmware/microcode)
202    Initializing,
203    /// CPU is online and running
204    Online,
205    /// CPU is being taken offline
206    Removing,
207    /// CPU has been removed
208    Removed,
209}
210
211// ---------------------------------------------------------------------------
212// CPU Hot-plug
213// ---------------------------------------------------------------------------
214
215/// CPU hot-plug manager
216#[cfg(feature = "alloc")]
217pub struct CpuHotplug {
218    /// Maximum CPUs supported
219    pub max_cpus: u32,
220    /// Online CPU bitmap
221    online_cpus: Vec<u64>,
222    /// Per-CPU lifecycle state
223    cpu_states: Vec<CpuLifecycleState>,
224    /// Number of currently online CPUs
225    online_count: u32,
226    /// Boot CPU (cannot be removed)
227    boot_cpu: u32,
228}
229
230#[cfg(feature = "alloc")]
231impl CpuHotplug {
232    /// Create a new CPU hot-plug manager
233    pub fn new(max_cpus: u32, boot_cpu: u32) -> Self {
234        let max = max_cpus.min(MAX_HOTPLUG_CPUS as u32);
235        let bitmap_words = (max as usize).div_ceil(BITS_PER_WORD);
236        let mut cpu_states = vec![CpuLifecycleState::Empty; max as usize];
237
238        // Boot CPU is always online
239        let mut online_cpus = vec![0u64; bitmap_words];
240        if (boot_cpu as usize) < cpu_states.len() {
241            cpu_states[boot_cpu as usize] = CpuLifecycleState::Online;
242            let word = boot_cpu as usize / BITS_PER_WORD;
243            let bit = boot_cpu as usize % BITS_PER_WORD;
244            if word < online_cpus.len() {
245                online_cpus[word] |= 1u64 << bit;
246            }
247        }
248
249        Self {
250            max_cpus: max,
251            online_cpus,
252            cpu_states,
253            online_count: 1,
254            boot_cpu,
255        }
256    }
257
258    /// Add (hot-plug) a CPU
259    pub(crate) fn add_cpu(&mut self, cpu_id: u32) -> Result<(), VmError> {
260        if cpu_id >= self.max_cpus {
261            return Err(VmError::InvalidVmState);
262        }
263        let idx = cpu_id as usize;
264        if self.cpu_states[idx] != CpuLifecycleState::Empty
265            && self.cpu_states[idx] != CpuLifecycleState::Removed
266        {
267            return Err(VmError::InvalidVmState);
268        }
269
270        // Transition: Empty/Removed -> Allocated -> Initializing -> Online
271        self.cpu_states[idx] = CpuLifecycleState::Allocated;
272        self.cpu_states[idx] = CpuLifecycleState::Initializing;
273        // In a real implementation, firmware init happens here
274        self.cpu_states[idx] = CpuLifecycleState::Online;
275
276        let word = idx / BITS_PER_WORD;
277        let bit = idx % BITS_PER_WORD;
278        if word < self.online_cpus.len() {
279            self.online_cpus[word] |= 1u64 << bit;
280        }
281        self.online_count = self.online_count.saturating_add(1);
282        Ok(())
283    }
284
285    /// Remove (hot-unplug) a CPU
286    pub(crate) fn remove_cpu(&mut self, cpu_id: u32) -> Result<(), VmError> {
287        if cpu_id >= self.max_cpus {
288            return Err(VmError::InvalidVmState);
289        }
290        if cpu_id == self.boot_cpu {
291            return Err(VmError::InvalidVmState); // Cannot remove boot CPU
292        }
293        let idx = cpu_id as usize;
294        if self.cpu_states[idx] != CpuLifecycleState::Online {
295            return Err(VmError::InvalidVmState);
296        }
297
298        // Transition: Online -> Removing -> Removed
299        self.cpu_states[idx] = CpuLifecycleState::Removing;
300        // In a real implementation, drain tasks, send IPI, etc.
301        self.cpu_states[idx] = CpuLifecycleState::Removed;
302
303        let word = idx / BITS_PER_WORD;
304        let bit = idx % BITS_PER_WORD;
305        if word < self.online_cpus.len() {
306            self.online_cpus[word] &= !(1u64 << bit);
307        }
308        self.online_count = self.online_count.saturating_sub(1);
309        Ok(())
310    }
311
312    /// Check if a CPU is online
313    pub(crate) fn is_online(&self, cpu_id: u32) -> bool {
314        if cpu_id >= self.max_cpus {
315            return false;
316        }
317        let word = cpu_id as usize / BITS_PER_WORD;
318        let bit = cpu_id as usize % BITS_PER_WORD;
319        if word < self.online_cpus.len() {
320            self.online_cpus[word] & (1u64 << bit) != 0
321        } else {
322            false
323        }
324    }
325
326    /// Get CPU lifecycle state
327    pub(crate) fn cpu_state(&self, cpu_id: u32) -> CpuLifecycleState {
328        if (cpu_id as usize) < self.cpu_states.len() {
329            self.cpu_states[cpu_id as usize]
330        } else {
331            CpuLifecycleState::Empty
332        }
333    }
334
335    /// Get number of online CPUs
336    pub(crate) fn online_count(&self) -> u32 {
337        self.online_count
338    }
339
340    /// Get maximum CPUs
341    pub(crate) fn max_cpus(&self) -> u32 {
342        self.max_cpus
343    }
344
345    /// Get list of online CPU IDs
346    pub(crate) fn online_cpu_ids(&self) -> Vec<u32> {
347        let mut ids = Vec::new();
348        for (word_idx, &word) in self.online_cpus.iter().enumerate() {
349            if word == 0 {
350                continue;
351            }
352            for bit in 0..BITS_PER_WORD {
353                if word & (1u64 << bit) != 0 {
354                    let cpu_id = (word_idx * BITS_PER_WORD + bit) as u32;
355                    if cpu_id < self.max_cpus {
356                        ids.push(cpu_id);
357                    }
358                }
359            }
360        }
361        ids
362    }
363}
364
365// ---------------------------------------------------------------------------
366// Memory DIMM
367// ---------------------------------------------------------------------------
368
369/// A memory DIMM slot for hot-plug
370#[derive(Debug, Clone, Copy)]
371pub struct MemoryDimm {
372    /// Slot number
373    pub slot: u32,
374    /// DIMM size in MB
375    pub size_mb: u32,
376    /// Base physical address when online
377    pub base_addr: u64,
378    /// Whether this DIMM is online
379    pub online: bool,
380}
381
382impl MemoryDimm {
383    /// Create a new DIMM
384    pub fn new(slot: u32, size_mb: u32, base_addr: u64) -> Self {
385        Self {
386            slot,
387            size_mb,
388            base_addr,
389            online: false,
390        }
391    }
392
393    /// Get DIMM size in bytes
394    pub(crate) fn size_bytes(&self) -> u64 {
395        self.size_mb as u64 * 1024 * 1024
396    }
397
398    /// Get end address (exclusive)
399    pub(crate) fn end_addr(&self) -> u64 {
400        self.base_addr + self.size_bytes()
401    }
402}
403
404// ---------------------------------------------------------------------------
405// Memory Hot-plug
406// ---------------------------------------------------------------------------
407
408/// Memory hot-plug manager
409#[cfg(feature = "alloc")]
410pub struct MemoryHotplug {
411    /// Maximum DIMM slots
412    pub max_dimms: u32,
413    /// Installed DIMMs
414    pub dimms: Vec<MemoryDimm>,
415    /// Next available base address for hot-added memory
416    next_base_addr: u64,
417    /// Total online memory in MB
418    total_online_mb: u64,
419}
420
421#[cfg(feature = "alloc")]
422impl MemoryHotplug {
423    /// Create a new memory hot-plug manager
424    ///
425    /// `initial_base` is the address above which hot-added DIMMs are placed.
426    pub fn new(max_dimms: u32, initial_base: u64) -> Self {
427        Self {
428            max_dimms: max_dimms.min(MAX_DIMMS as u32),
429            dimms: Vec::new(),
430            next_base_addr: initial_base,
431            total_online_mb: 0,
432        }
433    }
434
435    /// Add (hot-plug) a memory DIMM
436    pub(crate) fn add_dimm(&mut self, size_mb: u32) -> Result<u32, VmError> {
437        if self.dimms.len() >= self.max_dimms as usize {
438            return Err(VmError::GuestMemoryError);
439        }
440        if size_mb == 0 {
441            return Err(VmError::GuestMemoryError);
442        }
443
444        let slot = self.dimms.len() as u32;
445        let base_addr = self.next_base_addr;
446        let size_bytes = size_mb as u64 * 1024 * 1024;
447
448        let mut dimm = MemoryDimm::new(slot, size_mb, base_addr);
449        dimm.online = true;
450        self.dimms.push(dimm);
451
452        self.next_base_addr = self.next_base_addr.saturating_add(size_bytes);
453        self.total_online_mb = self.total_online_mb.saturating_add(size_mb as u64);
454
455        // In a real implementation, this would:
456        // 1. Map the new memory region in EPT
457        // 2. Notify the guest via ACPI GED
458        // 3. Guest OS then onlines the memory
459
460        Ok(slot)
461    }
462
463    /// Remove (hot-unplug) a memory DIMM
464    pub(crate) fn remove_dimm(&mut self, slot: u32) -> Result<(), VmError> {
465        let dimm = self
466            .dimms
467            .iter_mut()
468            .find(|d| d.slot == slot)
469            .ok_or(VmError::GuestMemoryError)?;
470
471        if !dimm.online {
472            return Err(VmError::GuestMemoryError);
473        }
474
475        // In a real implementation:
476        // 1. Notify guest to offline the memory
477        // 2. Wait for guest acknowledgment
478        // 3. Unmap from EPT
479
480        self.total_online_mb = self.total_online_mb.saturating_sub(dimm.size_mb as u64);
481        dimm.online = false;
482        Ok(())
483    }
484
485    /// Get DIMM by slot
486    pub(crate) fn dimm(&self, slot: u32) -> Option<&MemoryDimm> {
487        self.dimms.iter().find(|d| d.slot == slot)
488    }
489
490    /// Get total online memory in MB
491    pub(crate) fn total_online_mb(&self) -> u64 {
492        self.total_online_mb
493    }
494
495    /// Get number of installed DIMMs
496    pub(crate) fn dimm_count(&self) -> usize {
497        self.dimms.len()
498    }
499
500    /// Get number of online DIMMs
501    pub(crate) fn online_dimm_count(&self) -> usize {
502        self.dimms.iter().filter(|d| d.online).count()
503    }
504}
505
506// ---------------------------------------------------------------------------
507// PCI Hot-plug Indicators
508// ---------------------------------------------------------------------------
509
510/// Power indicator state
511#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
512pub enum PowerIndicator {
513    /// Indicator off
514    #[default]
515    Off,
516    /// Indicator on (steady)
517    On,
518    /// Indicator blinking
519    Blink,
520}
521
522/// Attention indicator state
523#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
524pub enum AttentionIndicator {
525    /// Indicator off
526    #[default]
527    Off,
528    /// Indicator on (steady)
529    On,
530    /// Indicator blinking
531    Blink,
532}
533
534// ---------------------------------------------------------------------------
535// PCI Hot-plug Slot
536// ---------------------------------------------------------------------------
537
538/// A PCI hot-plug slot
539#[derive(Debug, Clone, Copy, Default)]
540pub struct PciHotplugSlot {
541    /// Slot identifier
542    pub slot_id: u32,
543    /// Whether a device is present in the slot
544    pub occupied: bool,
545    /// PCI address of the device (if present)
546    pub device_addr: u32, // BDF encoded
547    /// Power indicator state
548    pub power_indicator: PowerIndicator,
549    /// Attention indicator state
550    pub attention_indicator: AttentionIndicator,
551    /// Whether the slot has power
552    pub powered: bool,
553    /// Whether surprise removal is supported
554    pub surprise_removal_supported: bool,
555}
556
557impl PciHotplugSlot {
558    /// Create a new empty slot
559    pub fn new(slot_id: u32) -> Self {
560        Self {
561            slot_id,
562            surprise_removal_supported: false,
563            ..Default::default()
564        }
565    }
566
567    /// Create a slot with surprise removal support
568    pub fn with_surprise_removal(slot_id: u32) -> Self {
569        Self {
570            slot_id,
571            surprise_removal_supported: true,
572            ..Default::default()
573        }
574    }
575}
576
577// ---------------------------------------------------------------------------
578// SHPC (Standard Hot-Plug Controller)
579// ---------------------------------------------------------------------------
580
581/// Standard Hot-Plug Controller for conventional PCI hot-plug
582#[cfg(feature = "alloc")]
583pub struct ShpcController {
584    /// Hot-plug slots
585    pub slots: Vec<PciHotplugSlot>,
586    /// Controller base address (MMIO)
587    pub base_addr: u64,
588    /// Whether the controller is enabled
589    pub enabled: bool,
590}
591
592#[cfg(feature = "alloc")]
593impl ShpcController {
594    /// Create a new SHPC controller
595    pub fn new(num_slots: u32, base_addr: u64) -> Self {
596        let count = num_slots.min(MAX_PCI_SLOTS as u32);
597        let mut slots = Vec::with_capacity(count as usize);
598        for i in 0..count {
599            slots.push(PciHotplugSlot::new(i));
600        }
601        Self {
602            slots,
603            base_addr,
604            enabled: true,
605        }
606    }
607
608    /// Power on a slot (insert device)
609    pub(crate) fn slot_power_on(&mut self, slot_id: u32, device_bdf: u32) -> Result<(), VmError> {
610        let slot = self
611            .slots
612            .iter_mut()
613            .find(|s| s.slot_id == slot_id)
614            .ok_or(VmError::DeviceError)?;
615
616        if slot.occupied {
617            return Err(VmError::DeviceError);
618        }
619
620        slot.occupied = true;
621        slot.device_addr = device_bdf;
622        slot.powered = true;
623        slot.power_indicator = PowerIndicator::On;
624        Ok(())
625    }
626
627    /// Power off a slot (eject device)
628    pub(crate) fn slot_power_off(&mut self, slot_id: u32) -> Result<(), VmError> {
629        let slot = self
630            .slots
631            .iter_mut()
632            .find(|s| s.slot_id == slot_id)
633            .ok_or(VmError::DeviceError)?;
634
635        if !slot.occupied {
636            return Err(VmError::DeviceError);
637        }
638
639        slot.powered = false;
640        slot.power_indicator = PowerIndicator::Off;
641        slot.occupied = false;
642        slot.device_addr = 0;
643        Ok(())
644    }
645
646    /// Handle surprise removal of a device
647    pub(crate) fn surprise_removal(&mut self, slot_id: u32) -> Result<HotplugEvent, VmError> {
648        let slot = self
649            .slots
650            .iter_mut()
651            .find(|s| s.slot_id == slot_id)
652            .ok_or(VmError::DeviceError)?;
653
654        if !slot.occupied {
655            return Err(VmError::DeviceError);
656        }
657        if !slot.surprise_removal_supported {
658            return Err(VmError::DeviceError);
659        }
660
661        let event = HotplugEvent::pci_remove(slot_id, 0);
662        slot.occupied = false;
663        slot.powered = false;
664        slot.power_indicator = PowerIndicator::Off;
665        slot.attention_indicator = AttentionIndicator::Blink;
666        slot.device_addr = 0;
667
668        Ok(event)
669    }
670
671    /// Get slot state
672    pub(crate) fn slot(&self, slot_id: u32) -> Option<&PciHotplugSlot> {
673        self.slots.iter().find(|s| s.slot_id == slot_id)
674    }
675
676    /// Get number of occupied slots
677    pub(crate) fn occupied_count(&self) -> usize {
678        self.slots.iter().filter(|s| s.occupied).count()
679    }
680
681    /// Get number of total slots
682    pub(crate) fn slot_count(&self) -> usize {
683        self.slots.len()
684    }
685}
686
687// ---------------------------------------------------------------------------
688// PCIe Native Hot-plug
689// ---------------------------------------------------------------------------
690
691/// Slot event type for PCIe native hot-plug
692#[derive(Debug, Clone, Copy, PartialEq, Eq)]
693pub enum SlotEvent {
694    /// Attention button pressed
695    AttentionButton,
696    /// Power fault detected
697    PowerFault,
698    /// Presence detect changed
699    PresenceChanged,
700    /// Command completed
701    CommandCompleted,
702    /// Data link layer state changed
703    DllStateChanged,
704}
705
706/// PCIe native hot-plug controller (per-slot)
707#[derive(Debug, Clone, Copy, Default)]
708pub struct PcieNativeHotplug {
709    /// Slot ID
710    pub slot_id: u32,
711    /// Presence detect state
712    pub presence_detect: bool,
713    /// Power fault detected
714    pub power_fault: bool,
715    /// Attention button pressed
716    pub attention_button: bool,
717    /// Power controller enabled
718    pub power_enabled: bool,
719    /// Power indicator
720    pub power_indicator: PowerIndicator,
721    /// Attention indicator
722    pub attention_indicator: AttentionIndicator,
723    /// Slot capabilities
724    pub surprise_supported: bool,
725    /// Hot-plug capable
726    pub hotplug_capable: bool,
727    /// Data link layer active
728    pub dll_active: bool,
729}
730
731impl PcieNativeHotplug {
732    /// Create a new PCIe hot-plug controller for a slot
733    pub fn new(slot_id: u32) -> Self {
734        Self {
735            slot_id,
736            hotplug_capable: true,
737            surprise_supported: true,
738            ..Default::default()
739        }
740    }
741
742    /// Handle a slot event
743    pub(crate) fn handle_slot_event(&mut self, event: SlotEvent) -> Option<HotplugEvent> {
744        match event {
745            SlotEvent::AttentionButton => {
746                self.attention_button = true;
747                self.attention_indicator = AttentionIndicator::Blink;
748                // Start 5-second attention button timer (simplified)
749                if self.presence_detect {
750                    // Button pressed on occupied slot -> request eject
751                    Some(HotplugEvent::pci_remove(self.slot_id, 0))
752                } else {
753                    None
754                }
755            }
756            SlotEvent::PowerFault => {
757                self.power_fault = true;
758                self.power_enabled = false;
759                self.power_indicator = PowerIndicator::Blink;
760                self.attention_indicator = AttentionIndicator::On;
761                None
762            }
763            SlotEvent::PresenceChanged => {
764                self.presence_detect = !self.presence_detect;
765                if self.presence_detect {
766                    // Device inserted
767                    self.power_indicator = PowerIndicator::Blink;
768                    Some(HotplugEvent::pci_add(self.slot_id, 0))
769                } else {
770                    // Device removed
771                    self.power_indicator = PowerIndicator::Off;
772                    self.power_enabled = false;
773                    Some(HotplugEvent::pci_remove(self.slot_id, 0))
774                }
775            }
776            SlotEvent::CommandCompleted => {
777                // Command completed, no further action needed
778                None
779            }
780            SlotEvent::DllStateChanged => {
781                self.dll_active = !self.dll_active;
782                None
783            }
784        }
785    }
786
787    /// Enable power to the slot
788    pub(crate) fn power_on(&mut self) {
789        self.power_enabled = true;
790        self.power_indicator = PowerIndicator::On;
791        self.power_fault = false;
792    }
793
794    /// Disable power to the slot
795    pub(crate) fn power_off(&mut self) {
796        self.power_enabled = false;
797        self.power_indicator = PowerIndicator::Off;
798    }
799
800    /// Check if a device is present
801    pub(crate) fn is_present(&self) -> bool {
802        self.presence_detect
803    }
804
805    /// Check if power is on
806    pub(crate) fn is_powered(&self) -> bool {
807        self.power_enabled
808    }
809}
810
811// ---------------------------------------------------------------------------
812// Tests
813// ---------------------------------------------------------------------------
814
815#[cfg(test)]
816mod tests {
817    use super::*;
818
819    #[test]
820    fn test_hotplug_event_cpu() {
821        let event = HotplugEvent::cpu_add(4, 100);
822        assert_eq!(event.event_type, HotplugType::Cpu);
823        assert_eq!(event.device_info, 4);
824        assert!(event.is_add);
825    }
826
827    #[test]
828    fn test_hotplug_event_memory() {
829        let event = HotplugEvent::memory_remove(2, 200);
830        assert_eq!(event.event_type, HotplugType::Memory);
831        assert!(!event.is_add);
832    }
833
834    #[test]
835    fn test_hotplug_event_pci() {
836        let event = HotplugEvent::pci_add(1, 300);
837        assert_eq!(event.event_type, HotplugType::PciDevice);
838        assert!(event.is_add);
839    }
840
841    #[test]
842    fn test_acpi_ged_inject_poll() {
843        let mut ged = AcpiGed::new();
844        assert!(!ged.has_pending_events());
845
846        ged.inject_event(HotplugEvent::cpu_add(1, 0)).unwrap();
847        ged.inject_event(HotplugEvent::memory_add(0, 0)).unwrap();
848        assert_eq!(ged.pending_count(), 2);
849
850        let ev1 = ged.poll_event().unwrap();
851        assert_eq!(ev1.event_type, HotplugType::Cpu);
852
853        let ev2 = ged.poll_event().unwrap();
854        assert_eq!(ev2.event_type, HotplugType::Memory);
855
856        assert!(ged.poll_event().is_none());
857    }
858
859    #[test]
860    fn test_acpi_ged_disabled() {
861        let mut ged = AcpiGed::new();
862        ged.set_enabled(false);
863        assert!(ged.inject_event(HotplugEvent::cpu_add(0, 0)).is_err());
864    }
865
866    #[test]
867    fn test_acpi_ged_clear() {
868        let mut ged = AcpiGed::new();
869        ged.inject_event(HotplugEvent::cpu_add(0, 0)).unwrap();
870        ged.inject_event(HotplugEvent::cpu_add(1, 0)).unwrap();
871        ged.clear();
872        assert_eq!(ged.pending_count(), 0);
873    }
874
875    #[test]
876    fn test_cpu_hotplug_new() {
877        let hp = CpuHotplug::new(8, 0);
878        assert_eq!(hp.online_count(), 1); // Boot CPU
879        assert!(hp.is_online(0));
880    }
881
882    #[test]
883    fn test_cpu_hotplug_add() {
884        let mut hp = CpuHotplug::new(8, 0);
885        hp.add_cpu(1).unwrap();
886        hp.add_cpu(2).unwrap();
887        assert_eq!(hp.online_count(), 3);
888        assert!(hp.is_online(1));
889        assert!(hp.is_online(2));
890        assert_eq!(hp.cpu_state(1), CpuLifecycleState::Online);
891    }
892
893    #[test]
894    fn test_cpu_hotplug_remove() {
895        let mut hp = CpuHotplug::new(8, 0);
896        hp.add_cpu(1).unwrap();
897        hp.remove_cpu(1).unwrap();
898        assert_eq!(hp.online_count(), 1);
899        assert!(!hp.is_online(1));
900        assert_eq!(hp.cpu_state(1), CpuLifecycleState::Removed);
901    }
902
903    #[test]
904    fn test_cpu_hotplug_cannot_remove_boot() {
905        let mut hp = CpuHotplug::new(8, 0);
906        assert!(hp.remove_cpu(0).is_err());
907    }
908
909    #[test]
910    fn test_cpu_hotplug_re_add() {
911        let mut hp = CpuHotplug::new(8, 0);
912        hp.add_cpu(3).unwrap();
913        hp.remove_cpu(3).unwrap();
914        hp.add_cpu(3).unwrap(); // Re-add after removal
915        assert!(hp.is_online(3));
916    }
917
918    #[test]
919    fn test_cpu_hotplug_online_ids() {
920        let mut hp = CpuHotplug::new(8, 0);
921        hp.add_cpu(2).unwrap();
922        hp.add_cpu(5).unwrap();
923        let ids = hp.online_cpu_ids();
924        assert!(ids.contains(&0));
925        assert!(ids.contains(&2));
926        assert!(ids.contains(&5));
927        assert_eq!(ids.len(), 3);
928    }
929
930    #[test]
931    fn test_memory_dimm() {
932        let dimm = MemoryDimm::new(0, 1024, 0x1_0000_0000);
933        assert_eq!(dimm.size_bytes(), 1024 * 1024 * 1024);
934        assert_eq!(dimm.end_addr(), 0x1_0000_0000 + 1024 * 1024 * 1024);
935    }
936
937    #[test]
938    fn test_memory_hotplug_add() {
939        let mut hp = MemoryHotplug::new(8, 0x1_0000_0000);
940        let slot = hp.add_dimm(512).unwrap();
941        assert_eq!(slot, 0);
942        assert_eq!(hp.total_online_mb(), 512);
943        assert_eq!(hp.dimm_count(), 1);
944    }
945
946    #[test]
947    fn test_memory_hotplug_remove() {
948        let mut hp = MemoryHotplug::new(8, 0x1_0000_0000);
949        hp.add_dimm(512).unwrap();
950        hp.remove_dimm(0).unwrap();
951        assert_eq!(hp.total_online_mb(), 0);
952        assert_eq!(hp.online_dimm_count(), 0);
953    }
954
955    #[test]
956    fn test_memory_hotplug_remove_offline() {
957        let mut hp = MemoryHotplug::new(8, 0x1_0000_0000);
958        hp.add_dimm(256).unwrap();
959        hp.remove_dimm(0).unwrap();
960        assert!(hp.remove_dimm(0).is_err()); // Already offline
961    }
962
963    #[test]
964    fn test_shpc_controller_power_on() {
965        let mut shpc = ShpcController::new(4, 0xFE00_0000);
966        assert_eq!(shpc.slot_count(), 4);
967        shpc.slot_power_on(0, 0x0018).unwrap();
968        assert_eq!(shpc.occupied_count(), 1);
969        let slot = shpc.slot(0).unwrap();
970        assert!(slot.occupied);
971        assert!(slot.powered);
972    }
973
974    #[test]
975    fn test_shpc_controller_power_off() {
976        let mut shpc = ShpcController::new(4, 0);
977        shpc.slot_power_on(0, 0x0018).unwrap();
978        shpc.slot_power_off(0).unwrap();
979        assert_eq!(shpc.occupied_count(), 0);
980    }
981
982    #[test]
983    fn test_shpc_surprise_removal() {
984        let mut shpc = ShpcController::new(4, 0);
985        shpc.slots[0].surprise_removal_supported = true;
986        shpc.slot_power_on(0, 0x0018).unwrap();
987        let event = shpc.surprise_removal(0).unwrap();
988        assert_eq!(event.event_type, HotplugType::PciDevice);
989        assert!(!event.is_add);
990        assert_eq!(shpc.occupied_count(), 0);
991    }
992
993    #[test]
994    fn test_shpc_surprise_not_supported() {
995        let mut shpc = ShpcController::new(4, 0);
996        shpc.slot_power_on(0, 0x0018).unwrap();
997        assert!(shpc.surprise_removal(0).is_err());
998    }
999
1000    #[test]
1001    fn test_pcie_native_presence_change_insert() {
1002        let mut hp = PcieNativeHotplug::new(0);
1003        assert!(!hp.is_present());
1004        let event = hp.handle_slot_event(SlotEvent::PresenceChanged).unwrap();
1005        assert_eq!(event.event_type, HotplugType::PciDevice);
1006        assert!(event.is_add);
1007        assert!(hp.is_present());
1008    }
1009
1010    #[test]
1011    fn test_pcie_native_presence_change_remove() {
1012        let mut hp = PcieNativeHotplug::new(0);
1013        // Insert first
1014        hp.handle_slot_event(SlotEvent::PresenceChanged);
1015        hp.power_on();
1016        // Then remove
1017        let event = hp.handle_slot_event(SlotEvent::PresenceChanged).unwrap();
1018        assert!(!event.is_add);
1019        assert!(!hp.is_present());
1020        assert!(!hp.is_powered());
1021    }
1022
1023    #[test]
1024    fn test_pcie_native_power_fault() {
1025        let mut hp = PcieNativeHotplug::new(0);
1026        hp.power_on();
1027        assert!(hp.is_powered());
1028        let event = hp.handle_slot_event(SlotEvent::PowerFault);
1029        assert!(event.is_none()); // Power fault doesn't generate hot-plug event
1030        assert!(!hp.is_powered());
1031        assert!(hp.power_fault);
1032    }
1033
1034    #[test]
1035    fn test_pcie_native_attention_button_empty() {
1036        let mut hp = PcieNativeHotplug::new(0);
1037        let event = hp.handle_slot_event(SlotEvent::AttentionButton);
1038        assert!(event.is_none()); // No device, no eject
1039    }
1040
1041    #[test]
1042    fn test_pcie_native_attention_button_occupied() {
1043        let mut hp = PcieNativeHotplug::new(0);
1044        hp.handle_slot_event(SlotEvent::PresenceChanged); // Insert
1045        let event = hp.handle_slot_event(SlotEvent::AttentionButton);
1046        assert!(event.is_some());
1047        assert!(!event.unwrap().is_add); // Request eject
1048    }
1049}