hal_x86_64/interrupt/apic/
local.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
//! Local APIC
pub use self::register::{ErrorStatus, Version};
use self::register::{LvtTimer, TimerMode};
use super::{PinPolarity, TriggerMode};
use crate::{
    cpu::{local, FeatureNotSupported, Msr},
    mm::{self, page, size::Size4Kb, PhysPage, VirtPage},
    time::{Duration, InvalidDuration},
};
use core::{
    cell::{Cell, RefCell},
    convert::TryInto,
    marker::PhantomData,
    num::NonZeroU32,
};
use hal_core::{PAddr, VAddr};
use mycelium_util::fmt;
use raw_cpuid::CpuId;
use register::TimerDivisor;
use volatile::{access, Volatile};

/// Local APIC state.
///
///# Notes on Mutability
///
/// In a multi-core x86_64 system, each CPU core has its own local APIC.
/// Therefore, the Mycelium HAL's [`interrupt::Controller`] type uses
/// [core-local storage] to store a reference to each CPU core's local APIC that
/// can be accessed only by that core. The value in core-local storage is a
/// [`RefCell`]`<`[`Option`]`<`[`LocalApic`]`>>`, as the local APIC must be
/// initialized on a per-core basis before it can be used.
///
/// Note that the [`RefCell`] must *only* be [borrowed
/// mutably](RefCell::borrow_mut) in order to *initialize* the `LocalApic`
/// value. Once the local APIC has been initialized, it may receive interrupts,
/// and may also be accessed by interrupt *handlers* in order to send an
/// end-of-interrupt. This means that mutably borrowing the `RefCell` once
/// interrupts are enabled may result in a panic if an ISR attempts to borrow
/// the local APIC immutably to send an EOI while the local APIC is borrowed
/// mutably.
///
/// Therefore, all local APIC operations that mutate state, such as setting the
/// timer calibration, must use interior mutability, so that an interrupt
/// handler may access the local APIC's state. Interior mutability need not be
/// thread-safe, however, as the local APIC state is only accessed from the core
/// that owns it.
///
/// [`interrupt::Controller`]: crate::interrupt::Controller
/// [core-local storage]: crate::cpu::local
#[derive(Debug)]
pub struct LocalApic {
    msr: Msr,
    base: VAddr,
    timer_calibration: Cell<Option<TimerCalibration>>,
}

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
struct TimerCalibration {
    frequency_hz: u32,
    divisor: register::TimerDivisor,
}

/// Represents a register in the local APIC's configuration area.
#[derive(Debug)]
pub struct LocalApicRegister<T = u32, A = access::ReadWrite> {
    offset: usize,
    name: &'static str,
    _ty: PhantomData<fn(T, A)>,
}

#[derive(Debug)]
pub(in crate::interrupt) struct Handle(local::LocalKey<RefCell<Option<LocalApic>>>);

pub trait RegisterAccess {
    type Access;
    type Target;
    fn volatile(
        ptr: &'static mut Self::Target,
    ) -> Volatile<&'static mut Self::Target, Self::Access>;
}

#[derive(Debug, Eq, PartialEq, thiserror::Error)]
pub enum LocalApicError {
    /// The system is configured to use the PIC interrupt model rather than the
    /// APIC interrupt model.
    #[error("interrupt model is PIC, not APIC")]
    NoApic,

    /// The local APIC is uninitialized.
    #[error("the local APIC has not been initialized on this core")]
    Uninitialized,
}

#[derive(Debug, Eq, PartialEq, thiserror::Error)]
pub enum TimerError {
    #[error(transparent)]
    InvalidDuration(#[from] InvalidDuration),
    #[error("local APIC timer has not been calibrated on this core")]
    NotCalibrated,
}

impl LocalApic {
    const BASE_PADDR_MASK: u64 = 0xffff_ffff_f000;

    /// Try to construct a `LocalApic`.
    ///
    /// # Arguments
    ///
    /// - `pagectrl`: a [page mapper](page::Map) used to ensure that the local
    ///   APIC's memory-mapped register page is mapped and writable.
    /// - `frame_alloc`: a [frame allocator](page::Alloc) used to allocate page
    ///   frame(s) while mapping the MMIO register page.
    ///
    /// # Returns
    ///
    /// - [`Ok`]`(LocalApic)` if this CPU supports the APIC interrupt model.
    /// - [`Err`]`(`[`FeatureNotSupported`]`)` if this CPU does not support APIC
    ///   interrupt handling.
    pub fn try_new<A>(
        pagectrl: &mut impl page::Map<Size4Kb, A>,
        frame_alloc: &A,
    ) -> Result<Self, FeatureNotSupported>
    where
        A: page::Alloc<Size4Kb>,
    {
        if !super::is_supported() {
            return Err(FeatureNotSupported::new("APIC interrupt model"));
        }

        let msr = Msr::ia32_apic_base();
        let base_paddr = PAddr::from_u64(msr.read() & Self::BASE_PADDR_MASK);
        let base = mm::kernel_vaddr_of(base_paddr);
        tracing::debug!(?base, "found local APIC base address");
        assert_ne!(base, VAddr::from_u64(0));

        unsafe {
            // ensure the local APIC's MMIO page is mapped and writable.
            let virt = VirtPage::<Size4Kb>::containing_fixed(base);
            let phys = PhysPage::<Size4Kb>::containing_fixed(base_paddr);
            tracing::debug!(?virt, ?phys, "mapping local APIC MMIO page...");
            pagectrl
                .map_page(virt, phys, frame_alloc)
                .set_writable(true)
                .commit();
            tracing::debug!("mapped local APIC MMIO page");
        }

        Ok(Self {
            msr,
            base,
            timer_calibration: Cell::new(None),
        })
    }

    #[must_use]
    #[inline]
    pub fn new<A>(pagectrl: &mut impl page::Map<Size4Kb, A>, frame_alloc: &A) -> Self
    where
        A: page::Alloc<Size4Kb>,
    {
        Self::try_new(pagectrl, frame_alloc).unwrap()
    }

    pub fn enable(&self, spurious_vector: u8) {
        use register::Version;
        /// Writing this to the IA32_APIC_BASE MSR enables the local APIC.
        const MSR_ENABLE: u64 = 0x800;
        /// Bit 8 in the spurious interrupt vector register enables the APIC.
        const SPURIOUS_VECTOR_ENABLE_BIT: u32 = 1 << 8;

        // Write the enable bit to the MSR
        unsafe {
            self.msr.update(|base| base | MSR_ENABLE);
        }

        // Enable the APIC by writing the spurious vector to the APIC's
        // SPURIOUS_VECTOR register.
        let value = spurious_vector as u32 | SPURIOUS_VECTOR_ENABLE_BIT;
        unsafe { self.register(register::SPURIOUS_VECTOR).write(value) }

        let version = self.version();
        tracing::info!(
            base = ?self.base,
            spurious_vector,
            version = ?version.get(Version::VERSION),
            max_lvt_entry = ?version.get(Version::MAX_LVT),
            supports_eoi_suppression = ?version.get(Version::EOI_SUPPRESSION),
            "local APIC enabled",
        );
    }

    pub fn calibrate_timer(&self, divisor: TimerDivisor) {
        if let Some(prev) = self.timer_calibration.get() {
            if prev.divisor == divisor {
                tracing::info!(
                    ?divisor,
                    "local APIC timer already calibrated with this divisor"
                );
                return;
            }
        }
        self.timer_calibration.set(Some(TimerCalibration {
            frequency_hz: self.calibrate_frequency_hz(divisor),
            divisor,
        }));
    }

    /// Reads the local APIC's version register.
    ///
    /// The returned [`Version`] struct indicates the version of the local APIC,
    /// the maximum LVT entry index, and whether or not EOI suppression is
    /// supported.
    pub fn version(&self) -> register::Version {
        unsafe { self.register(register::VERSION) }.read()
    }

    fn calibrate_frequency_hz(&self, divisor: TimerDivisor) -> u32 {
        // How sloppy do we expect the PIT frequency calibration to be?
        // If the delta between the CPUID frequency and the frequency we
        // determined using PIT calibration is > this value, we'll yell about
        // it.
        const PIT_SLOPPINESS: u32 = 1000;

        // Start out by calibrating the APIC frequency using the PIT.
        let pit_frequency_hz = self.calibrate_frequency_hz_pit(divisor);

        // See if we can get something from CPUID.
        let Some(cpuid_frequency_hz) = Self::calibrate_frequency_hz_cpuid(divisor) else {
            tracing::info!(
                pit.frequency_hz = pit_frequency_hz,
                "CPUID does not indicate APIC timer frequency; using PIT \
                 calibration only"
            );
            return pit_frequency_hz;
        };

        // Cross-check the PIT calibration result and CPUID value.
        let distance = if pit_frequency_hz > cpuid_frequency_hz {
            pit_frequency_hz - cpuid_frequency_hz
        } else {
            cpuid_frequency_hz - pit_frequency_hz
        };
        if distance > PIT_SLOPPINESS {
            tracing::warn!(
                pit.frequency_hz = pit_frequency_hz,
                cpuid.frequency_hz = cpuid_frequency_hz,
                distance,
                ?divisor,
                "APIC timer frequency from PIT calibration differs substantially \
                 from CPUID!"
            );
        }

        cpuid_frequency_hz
    }

    /// Calibrate the timer frequency using the PIT.
    #[inline(always)]
    fn calibrate_frequency_hz_pit(&self, divisor: TimerDivisor) -> u32 {
        tracing::debug!(?divisor, "calibrating APIC timer frequency using PIT...");
        const ATTEMPTS: usize = 5;

        // lock the PIT now, before actually starting the timer IRQ, so that we
        // don't include any time spent waiting for the PIT lock.
        //
        // since we only run this code on startup, before any other cores have
        // been started, this probably never actually waits for a lock. but...we
        // should do it the right way, anyway.
        let mut pit = crate::time::PIT.lock();

        let mut sum = 0;
        let mut min = u32::MAX;
        let mut max = 0;
        for attempt in 1..ATTEMPTS + 1 {
            unsafe {
                // start the timer

                // set timer divisor to 16
                self.write_register(register::TIMER_DIVISOR, divisor);
                self.register(register::LVT_TIMER).update(|lvt_timer| {
                    lvt_timer
                        .set(LvtTimer::MODE, TimerMode::OneShot)
                        .set(LvtTimer::MASKED, false);
                });
                // set initial count to -1
                self.write_register(register::TIMER_INITIAL_COUNT, -1i32 as u32);
            }

            // use the PIT to sleep for 10ms
            pit.sleep_blocking(Duration::from_millis(10))
                .expect("the PIT should be able to send a 10ms interrupt...");

            unsafe {
                // stop the timer
                self.register(register::LVT_TIMER).update(|lvt_timer| {
                    lvt_timer.set(LvtTimer::MASKED, true);
                });
            }

            let elapsed_ticks = unsafe { self.register(register::TIMER_CURRENT_COUNT).read() };
            // since we slept for ten milliseconds, each tick is 10 kHz. we don't
            // need to account for the divisor since we ran the timer at that
            // divisor already.
            let ticks_per_10ms = (-1i32 as u32).wrapping_sub(elapsed_ticks);
            // convert the frequency to Hz.
            let frequency_hz = ticks_per_10ms * 100;
            // TODO(eliza): throw out attempts that differ too substantially
            // from the min/max?
            sum += frequency_hz;
            min = core::cmp::min(min, frequency_hz);
            max = core::cmp::max(max, frequency_hz);
            tracing::trace!(attempt, frequency_hz, sum, min, max);
        }
        let frequency_hz = sum / ATTEMPTS as u32;
        tracing::debug!(
            ?divisor,
            frequency_hz,
            min_hz = min,
            max_hz = max,
            "calibrated local APIC timer using PIT"
        );
        frequency_hz
    }

    #[inline(always)]
    fn calibrate_frequency_hz_cpuid(divisor: TimerDivisor) -> Option<u32> {
        let cpuid = CpuId::new();
        if let Some(freq_khz) = cpuid.get_hypervisor_info().and_then(|hypervisor| {
            tracing::trace!("CPUID contains hypervisor info");
            let freq = hypervisor.apic_frequency();
            tracing::trace!(hypervisor.apic_frequency_khz = ?freq);
            NonZeroU32::new(freq?)
        }) {
            // the hypervisor info CPUID leaf expresses the frequency in kHz,
            // and the frequency is not divided by the target timer divisor.
            let frequency_hz = (freq_khz.get() * 1000) / divisor.into_divisor();
            tracing::debug!(
                ?divisor,
                frequency_hz,
                "determined APIC timer frequency from CPUID hypervisor info"
            );
            return Some(frequency_hz);
        }

        // XXX ELIZA THIS IS TSC FREQUENCY, SO IDK IF THAT'S RIGHT?
        /*
        if let Some(undivided_freq_hz) = cpuid.get_tsc_info().and_then(|tsc| {
            tracing::trace!("CPUID contains TSC info");
            let freq = tsc.nominal_frequency();
            NonZeroU32::new(freq)
        }) {
            // divide by the target timer divisor.
            let frequency_hz = undivided_freq_hz.get() / Self::TIMER_DIVISOR;
            tracing::debug!(
                frequency_hz,
                "determined APIC frequency from CPUID TSC info"
            );
            return frequency_hz;
        }
        */

        // CPUID didn't help, so fall back to calibrating the APIC frequency
        // using the PIT.
        None
    }

    pub fn interrupt_in(&self, duration: Duration, vector: u8) -> Result<(), TimerError> {
        let TimerCalibration {
            frequency_hz,
            divisor,
        } = self
            .timer_calibration
            .get()
            .ok_or(TimerError::NotCalibrated)?;
        let duration_ms: u32 = duration.as_millis().try_into().map_err(|_| {
            InvalidDuration::new(
                duration,
                "local APIC oneshot timer duration exceeds a `u32`",
            )
        })?;
        let ticks = duration_ms
            .checked_mul(frequency_hz / 1000)
            .ok_or_else(|| {
                InvalidDuration::new(
                duration,
                "local APIC oneshot timer duration requires a number of ticks that exceed a `u32`",
            )
            })?;
        unsafe {
            self.configure_timer(divisor, TimerMode::OneShot, vector, ticks);
        }

        Ok(())
    }

    #[tracing::instrument(
        level = tracing::Level::DEBUG,
        name = "LocalApic::start_periodic_timer",
        skip(self, interval),
        fields(?interval, vector),
        err
    )]
    pub fn start_periodic_timer(&self, interval: Duration, vector: u8) -> Result<(), TimerError> {
        let TimerCalibration {
            frequency_hz,
            divisor,
        } = self
            .timer_calibration
            .get()
            .ok_or(TimerError::NotCalibrated)?;
        let ticks_per_ms = frequency_hz / 1000;
        tracing::trace!(
            frequency_hz,
            ticks_per_ms,
            ?divisor,
            "starting local APIC timer"
        );
        let interval_ms: u32 = interval.as_millis().try_into().map_err(|_| {
            InvalidDuration::new(
                interval,
                "local APIC periodic timer interval exceeds a `u32`",
            )
        })?;
        let ticks_per_interval = interval_ms.checked_mul(ticks_per_ms).ok_or_else(|| {
            InvalidDuration::new(
                interval,
                "local APIC periodic timer interval requires a number of ticks that exceed a `u32`",
            )
        })?;

        unsafe {
            self.configure_timer(divisor, TimerMode::Periodic, vector, ticks_per_interval);
        }

        tracing::info!(
            ?interval,
            frequency_hz,
            ticks_per_ms,
            vector,
            "started local APIC timer"
        );

        Ok(())
    }

    #[inline(always)]
    unsafe fn configure_timer(
        &self,
        divisor: TimerDivisor,
        mode: TimerMode,
        vector: u8,
        initial_count: u32,
    ) {
        // Set the divisor configuration, update the LVT entry, and set the
        // initial count. Per the OSDev Wiki, we must do this in this specific
        // order (divisor, then unmask the LVT entry, then set the initial
        // count), or else we may miss IRQs or have other issues. I'm not sure
        // why this is, but see:
        // https://wiki.osdev.org/APIC_Timer#Enabling_APIC_Timer
        self.register(register::TIMER_DIVISOR).write(divisor);
        self.register(register::LVT_TIMER).update(|lvt_timer| {
            lvt_timer
                .set(LvtTimer::VECTOR, vector)
                .set(LvtTimer::MODE, mode)
                .set(LvtTimer::MASKED, false);
        });
        self.register(register::TIMER_INITIAL_COUNT)
            .write(initial_count)
    }

    /// Sends an End of Interrupt (EOI) to the local APIC.
    ///
    /// This should be called by an interrupt handler after handling a local
    /// APIC interrupt.
    ///
    /// # Safety
    ///
    /// This should only be called when an interrupt has been triggered by this
    /// local APIC.
    pub unsafe fn end_interrupt(&self) {
        // Write a 0 to the EOI register.
        self.register(register::END_OF_INTERRUPT).write(0);
    }

    /// Reads the error stauts register (`ESR`) of the local APIC.
    ///
    /// Calling this method resets the value of the error status register. Any
    /// currently set error bits are cleared. If the same error bits are present
    /// in a subsequent call to `LocalApic::check_error`, they represent *new*
    /// instances of the same error.
    ///
    /// # Returns
    ///
    /// If any error bits are set, this method returns
    /// [`Err`]`(`[`ErrorStatus`]`)`. Otherwise, if no error bits are present,
    /// this method returns [`Ok`]`()`.
    pub fn check_error(&self) -> Result<(), register::ErrorStatus> {
        let esr = unsafe {
            let mut reg = self.register(register::ERROR_STATUS);

            // Per the Intel SDM, Vol 3A, Ch 7, Section 12.5.3, "Error
            // Handling":
            //
            // > The ESR is a write/read register. Before attempt to read from
            // > the ESR, software should first write to it. (The value written
            // > does not affect the values read subsequently; only zero may be
            // > written in x2APIC mode.) This write clears  any previously
            // > logged errors and updates the ESR with any errors detected
            // > since the last write to the ESR. This write also rearms the
            // > APIC error interrupt triggering mechanism.
            //
            // So, first write a zero.
            reg.write(register::ErrorStatus::default());
            reg.read()
        };

        // Return the ESR value if error bits are set.
        if esr.is_error() {
            Err(esr)
        } else {
            Ok(())
        }
    }

    unsafe fn write_register<T, A>(&self, register: LocalApicRegister<T, A>, value: T)
    where
        LocalApicRegister<T, A>: RegisterAccess<Target = T, Access = A>,
        A: access::Writable,
        T: Copy + fmt::Debug + 'static,
    {
        tracing::trace!(%register, write = ?value);
        self.register(register).write(value);
    }

    #[must_use]
    unsafe fn register<T, A>(
        &self,
        register: LocalApicRegister<T, A>,
    ) -> Volatile<&'static mut T, A>
    where
        LocalApicRegister<T, A>: RegisterAccess<Target = T, Access = A>,
    {
        let addr = self.base + register.offset;
        assert!(
            addr.is_aligned(16usize),
            "Local APIC memory-mapped registers must be 16-byte aligned!"
        );
        let reference = &mut *addr.as_mut_ptr::<T>();
        LocalApicRegister::<T, A>::volatile(reference)
    }
}

impl Handle {
    pub(in crate::interrupt) const fn new() -> Self {
        Self(local::LocalKey::new(|| RefCell::new(None)))
    }

    pub(in crate::interrupt) fn with<T>(
        &self,
        f: impl FnOnce(&LocalApic) -> T,
    ) -> Result<T, LocalApicError> {
        self.0.with(|apic| {
            Ok(f(apic
                .borrow()
                .as_ref()
                .ok_or(LocalApicError::Uninitialized)?))
        })
    }

    pub(in crate::interrupt) unsafe fn initialize<A>(
        &self,
        frame_alloc: &A,
        pagectrl: &mut impl page::Map<Size4Kb, A>,
        spurious_vector: u8,
    ) where
        A: page::Alloc<Size4Kb>,
    {
        self.0.with(|slot| {
            let mut slot = slot.borrow_mut();
            if slot.is_some() {
                // already initialized, bail.
                return;
            }
            let apic = LocalApic::new(pagectrl, frame_alloc);
            apic.enable(spurious_vector);
            *slot = Some(apic);
        })
    }
}

pub mod register {
    use super::*;
    use mycelium_util::bits::{bitfield, enum_from_bits};
    use volatile::access::*;

    impl<T> RegisterAccess for LocalApicRegister<T, ReadOnly> {
        type Access = ReadOnly;
        type Target = T;
        fn volatile(
            ptr: &'static mut Self::Target,
        ) -> Volatile<&'static mut Self::Target, Self::Access> {
            Volatile::new_read_only(ptr)
        }
    }

    impl<T> RegisterAccess for LocalApicRegister<T, ReadWrite> {
        type Access = ReadWrite;
        type Target = T;
        fn volatile(
            ptr: &'static mut Self::Target,
        ) -> Volatile<&'static mut Self::Target, Self::Access> {
            Volatile::new(ptr)
        }
    }

    impl<T> RegisterAccess for LocalApicRegister<T, WriteOnly> {
        type Access = WriteOnly;
        type Target = T;
        fn volatile(
            ptr: &'static mut Self::Target,
        ) -> Volatile<&'static mut Self::Target, Self::Access> {
            Volatile::new_write_only(ptr)
        }
    }

    impl<T, A> fmt::Display for LocalApicRegister<T, A> {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            let Self { name, offset, _ty } = self;
            write!(f, "{name} ({offset:#x})")
        }
    }

    macro_rules! registers {
        ( $( $(#[$m:meta])* $NAME:ident$(<$T:ty>)? = $offset:literal, $access:ident);+ $(;)? ) => {
            $(
                registers! {@ $(#[$m])* $NAME$(<$T>)? = $offset, $access }
            )+
        };
        (@ $(#[$m:meta])* $NAME:ident = $offset:literal, $access:ident) => {
            registers!{@ $(#[$m])* $NAME<u32> = $offset, $access }
        };
        (@ $(#[$m:meta])* $NAME:ident<$T:ty> = $offset:literal, $access:ident )=> {
            $(#[$m])*
            pub const $NAME: LocalApicRegister<$T, $access> = LocalApicRegister {
                offset: $offset,
                name: stringify!($NAME),
                _ty: PhantomData,
            };
        };
    }

    registers! {

        /// Local APIC ID
        ///
        /// **Access**: read/write
        ID = 0x020, ReadWrite;

        /// Local APIC version
        ///
        /// **Access**: read-only
        VERSION<Version> = 0x030, ReadOnly;

        /// Task Priority Register (TPR)
        ///
        /// **Access**: read/write
        TASK_PRIORITY = 0x080, ReadWrite;

        /// Arbitration Priority Register (APR)
        ///
        /// **Access**: read-only
        ARBITRATION_PRIORITY = 0x090, ReadOnly;

        /// Processor Priority Register (APR)
        ///
        /// **Access**: read-only
        PROCESSOR_PRIORITY = 0x0a0, ReadOnly;

        /// End of Interrupt (EOI) Register
        ///
        /// **Access**: write-only
        END_OF_INTERRUPT = 0x0b0, WriteOnly;

        /// Remote Read Register (RRD)
        ///
        /// **Access**: read-only
        REMOTE_READ = 0x0c0, ReadOnly;

        /// Logical Destination Register
        ///
        /// **Access**: read/write
        LOGICAL_DEST = 0x0d0, ReadWrite;

        /// Destination Format Register
        ///
        /// **Access**: read/write
        DEST_FORMAT = 0x0e0, ReadWrite;

        /// Spurious Interrupt Vector Register
        ///
        /// **Access**: read/write
        SPURIOUS_VECTOR = 0x0f0, ReadWrite;

        /// In-Service Register (ISR) 0
        ///
        /// **Access**: read-only
        IN_SERVICE_0 = 0x100, ReadOnly;

        /// Error Status Register (ESR)
        ///
        /// **Access**: read/write
        ERROR_STATUS<ErrorStatus> = 0x280, ReadWrite;

        /// LVT Corrected Machine Check Interrupt (CMCI) Register
        ///
        /// *Access**: read/write
        LVT_CMCI = 0x2f0, ReadWrite;

        ICR_LOW = 0x300, ReadWrite;
        ICR_HIGH = 0x310, ReadWrite;

        LVT_TIMER<LvtTimer> = 0x320, ReadWrite;
        LVT_THERMAL<LvtEntry> = 0x330, ReadWrite;
        LVT_PERF<LvtEntry> = 0x340, ReadWrite;
        LVT_LINT0<LvtEntry> = 0x350, ReadWrite;
        LVT_LINT1<LvtEntry> = 0x360, ReadWrite;
        LVT_ERROR<LvtEntry> = 0x370, ReadWrite;

        TIMER_INITIAL_COUNT = 0x380, ReadWrite;
        TIMER_CURRENT_COUNT = 0x390, ReadOnly;
        TIMER_DIVISOR<TimerDivisor> = 0x3e0, ReadWrite;
    }

    bitfield! {
        /// Value of the `VERSION` register in the local APIC.
        pub struct Version<u32> {
            /// The version numbers of the local APIC:
            /// - 0XH: 82489DX discrete APIC.
            /// -10H - 15H: Integrated APIC.
            /// Other values reserved.
            pub const VERSION: u8;
            const _RESERVED_0 = 8;
            /// The maximum number of LVT entries in the local APIC, minus one.
            ///
            /// For the Pentium 4 and Intel Xeon processors (which
            /// have 6 LVT entries), the value returned in the Max LVT field is
            /// 5; for the P6 family processors (which have 5 LVT entries), the
            /// value returned is 4; for the Pentium processor (which has 4 LVT
            /// entries), the value returned is 3. For processors based on the
            /// Nehalem microarchitecture (which has 7 LVT entries) and onward,
            /// the value returned is 6.
            pub const MAX_LVT: u8;
            /// Indicates whether software can inhibit the broadcast of EOI
            /// message by setting bit 12 of the  Spurious Interrupt Vector
            /// Register; see Section 12.8.5 and Section 12.9. in Ch. 7 of Vol.
            /// 3A of the Intel SDM for details.
            pub const EOI_SUPPRESSION: bool;
        }
    }

    bitfield! {
        pub struct LvtTimer<u32> {
            pub const VECTOR: u8;
            const _RESERVED_0 = 4;
            pub const SEND_PENDING: bool;
            const _RESERVED_1 = 3;
            pub const MASKED: bool;
            pub const MODE: TimerMode;
        }
    }

    bitfield! {
        pub struct LvtEntry<u32> {
            pub const VECTOR: u8;
            const _RESERVED_0 = 2;
            pub const NMI: bool;
            pub const SEND_PENDING: bool;
            pub const POLARITY: PinPolarity;
            pub const REMOTE_IRR: bool;
            pub const TRIGGER: TriggerMode;
            pub const MASKED: bool;
        }
    }

    enum_from_bits! {
        #[derive(Debug, Eq, PartialEq)]
        pub enum TimerMode<u8> {
            /// One-shot mode, program count-down value in an initial-count register.
            OneShot = 0b00,
            /// Periodic mode, program interval value in an initial-count register.
            Periodic = 0b01,
            /// TSC-Deadline mode, program target value in IA32_TSC_DEADLINE MSR.
            TscDeadline = 0b10,
        }
    }

    enum_from_bits! {
        /// Configures the LVT time divisor.
        #[derive(Debug, Eq, PartialEq)]
        pub enum TimerDivisor<u32> {
            By1 = 0b1011,
            By2 = 0b0000,
            By4 = 0b0001,
            By8 = 0b0010,
            By16 = 0b0011,
            By32 = 0b1000,
            By64 = 0b1001,
            By128 = 0b1010,
        }
    }
    impl TimerDivisor {
        /// Returns the numeric value of the divisor configuration.
        pub(super) fn into_divisor(self) -> u32 {
            match self {
                TimerDivisor::By1 => 1,
                TimerDivisor::By2 => 2,
                TimerDivisor::By4 => 4,
                TimerDivisor::By8 => 8,
                TimerDivisor::By16 => 16,
                TimerDivisor::By32 => 32,
                TimerDivisor::By64 => 64,
                TimerDivisor::By128 => 128,
            }
        }
    }

    bitfield! {
        /// Value of the Error Status Register (ESR).
        ///
        /// See Intel SDM Vol. 3A, Ch. 7, Section 12.5.3, "Error Handling".
        #[derive(Default)]
        pub struct ErrorStatus<u32> {
            /// Set when the local APIC detects a checksum error for a message
            /// that it sent on the APIC bus.
            ///
            /// Used only on P6 family and Pentium processors.
            pub const SEND_CHECKSUM_ERROR: bool;

            /// Set when the local APIC detects a checksum error for a message
            /// that it received on the APIC bus.
            ///
            /// Used only on P6 family and Pentium processors.
            pub const RECV_CHECKSUM_ERROR: bool;

            /// Set when the local APIC detects that a message it sent was not
            /// accepted by any APIC on the APIC bus
            ///
            /// Used only on P6 family and Pentium processors.
            pub const SEND_ACCEPT_ERROR: bool;

            /// Set when the local APIC detects that a message it received was
            /// not accepted by any APIC on the APIC bus.
            ///
            /// Used only on P6 family and Pentium processors.
            pub const RECV_ACCEPT_ERROR: bool;

            /// Set when the local APIC detects an attempt to send an IPI with
            /// the lowest-priority delivery mode and the local APIC does not
            /// support the sending of such IPIs. This bit is used on some Intel
            /// Core and Intel Xeon processors.
            pub const REDIRECTABLE_IPI: bool;

            /// Set when the local APIC detects an illegal vector (one in the
            /// range 0 to 15) in the message that it is sending. This occurs as
            /// the result of a write to the ICR (in both xAPIC and x2APIC
            /// modes) or to SELF IPI register (x2APIC mode only) with an
            /// illegal vector.
            ///
            /// If the local APIC does not support the sending of
            /// lowest-priority IPIs and software writes the ICR to send a
            /// lowest-priority IPI with an illegal vector, the local APIC sets
            /// only the “redirectable IPI” error bit. The interrupt is not
            /// processed and hence the “Send Illegal Vector” bit is not set in
            /// the ESR.
            pub const SEND_ILLEGAL_VECTOR: bool;

            /// Set when the local APIC detects an illegal vector (one in the
            /// range 0 to 15) in an interrupt message it receives or in an
            /// interrupt generated locally from the local vector table or via a
            /// self IPI. Such interrupts are not delivered to the processor;
            /// the local APIC will never set an IRR bit in the range 0 to 15.
            pub const RECV_ILLEGAL_VECTOR: bool;

            /// Set when the local APIC is in xAPIC mode and software attempts
            /// to access a register that is reserved in the processor's
            /// local-APIC register-address space; see Table 10-1. (The
            /// local-APIC register-address spacemprises the 4 KBytes at the
            /// physical address specified in the `IA32_APIC_BASE` MSR.) Used only
            /// on Intel Core, Intel Atom, Pentium 4, Intel Xeon, and P6 family
            /// processors.
            ///
            /// In x2APIC mode, software accesses the APIC registers using the
            /// `RDMSR` and `WRMSR` instructions. Use of one of these
            /// instructions to access a reserved register cause a
            /// general-protection exception (see Section 10.12.1.3). They do
            /// not set the “Illegal Register Access” bit in the ESR.
            pub const ILLEGAL_REGISTER_ACCESS: bool;
        }

    }

    impl ErrorStatus {
        /// Returns `true` if an error is present, or `false` if no error
        /// bits are set.
        pub fn is_error(&self) -> bool {
            // Mask out the reserved bits, just in case they have values in the,
            // (they shouldn't, per the SDM, but...who knows!)
            self.bits() & 0b1111_1111 != 0
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn lvt_entry_is_valid() {
        register::LvtEntry::assert_valid();
    }

    #[test]
    fn lvt_timer_is_valid() {
        register::LvtTimer::assert_valid();
    }

    #[test]
    fn lvt_timer_offsets() {
        assert_eq!(
            register::LvtTimer::VECTOR.least_significant_index(),
            0,
            "vector LSB"
        );
        assert_eq!(
            register::LvtTimer::VECTOR.most_significant_index(),
            8,
            "vector MSB"
        );
        assert_eq!(
            register::LvtTimer::SEND_PENDING.least_significant_index(),
            12,
            "send pending"
        );
        assert_eq!(
            register::LvtTimer::MASKED.least_significant_index(),
            16,
            "masked MSB"
        );
        assert_eq!(
            register::LvtTimer::MODE.least_significant_index(),
            17,
            "mode LSB"
        );
    }
}