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
#![no_std]
#![cfg_attr(target_arch = "x86_64", feature(trait_alias))]

extern crate alloc;

use log::*;

#[cfg(target_arch = "x86_64")]
use {
    mpmc::Queue,
    event_types::Event,
    memory::MemoryManagementInfo,
    alloc::vec::Vec,
    io::{ByteReaderWriterWrapper, LockableIo, ReaderWriter},
    storage_manager::StorageDevice,
    memory::PhysicalAddress,
    serial_port::{SerialPortAddress, init_serial_port, take_serial_port_basic},
};

/// Performs early-stage initialization for simple devices needed during early boot.
///
/// This includes:
/// * [`acpi`] tables for system configuration info, including the IOAPIC.
#[cfg(target_arch = "x86_64")]
pub fn early_init(
    rsdp_address: Option<PhysicalAddress>,
    kernel_mmi: &mut MemoryManagementInfo
) -> Result<(), &'static str> {
    // Parse the ACPI tables to acquire system configuration info.
    acpi::init(rsdp_address, &mut kernel_mmi.page_table)?;

    Ok(())
}


/// Initializes all other devices not initialized during [`early_init()`]. 
///
/// Devices include:
/// * At least one [`serial_port`] (e.g., `COM1`) with full interrupt support,
/// * The fully-featured system [`logger`],
/// * The legacy PS2 controller and any connected devices: [`keyboard`] and [`mouse`],
/// * All other devices discovered on the [`pci`] bus.
pub fn init(
    #[cfg(target_arch = "x86_64")]
    key_producer: Queue<Event>,
    #[cfg(target_arch = "x86_64")]
    mouse_producer: Queue<Event>,
) -> Result<(), &'static str>  {

    let serial_ports = logger::take_early_log_writers();
    let logger_writers = IntoIterator::into_iter(serial_ports)
        .flatten()
        .filter_map(|sp| serial_port::init_serial_port(sp.base_port_address(), sp))
        .cloned();

    logger::init(None, logger_writers);
    info!("Initialized full logger.");

    // COM1 is the only UART on aarch64; it's used for logging as well as for the console.
    #[cfg(target_arch = "x86_64")] {
        // Ensure that both COM1 and COM2 are initialized, for logging and/or headless operation.
        // If a serial port was used for logging (as configured in [`logger::early_init()`]),
        // ignore its inputs for purposes of starting new console instances.
        let init_serial_port = |spa: SerialPortAddress| {
            if let Some(sp) = take_serial_port_basic(spa) {
                init_serial_port(spa, sp);
            } else {
                console::ignore_serial_port_input(spa as u16);
                info!("Ignoring input on {:?} because it is being used for logging.", spa);
            }
        };
        init_serial_port(SerialPortAddress::COM1);
        init_serial_port(SerialPortAddress::COM2);
    }

    // PS/2 is x86_64 only
    #[cfg(target_arch = "x86_64")] {
        let ps2_controller = ps2::init()?;
        if let Some(kb) = ps2_controller.keyboard_ref() {
            keyboard::init(kb, key_producer)?;
        }
        if let Some(m) = ps2_controller.mouse_ref() {
            mouse::init(m, mouse_producer)?;
        }
    }

    // Initialize/scan the PCI bus to discover PCI devices
    for dev in pci::pci_device_iter()? {
        debug!("Found PCI device: {:X?}", dev);
    }

    // store all the initialized ixgbe NICs here to be added to the network interface list
    // No NIC support on aarch64 at the moment
    #[cfg(target_arch = "x86_64")]
    let mut ixgbe_devs = Vec::new();

    // Iterate over all PCI devices and initialize the drivers for the devices we support.

    for dev in pci::pci_device_iter()? {
        // Currently we skip Bridge devices, since we have no use for them yet. 
        if dev.class == 0x06 {
            continue;
        }

        // If this is a storage device, initialize it as such.
        // No storage device support on aarch64 at the moment
        #[cfg(target_arch = "x86_64")]
        match storage_manager::init_device(dev) {
            // Successfully initialized this storage device.
            Ok(Some(_storage_controller)) => continue,

            // Not a storage device, so fall through and let another handler deal with it.
            Ok(None) => { }
            
            // Error initializing this device, so skip it.
            Err(e) => {
                error!("Failed to initialize storage device, it will be unavailable.\n{:?}\nError: {}", dev, e);
                continue;
            }
        }

        // If this is a network device, initialize it as such.
        // Look for networking controllers, specifically ethernet cards
        // No NIC support on aarch64 at the moment
        #[cfg(target_arch = "x86_64")]
        if dev.class == 0x02 && dev.subclass == 0x00 {
            if dev.vendor_id == e1000::INTEL_VEND && dev.device_id == e1000::E1000_DEV {
                info!("e1000 PCI device found at: {:?}", dev.location);
                let nic = e1000::E1000Nic::init(dev)?;
                let interface = net::register_device(nic);
                nic.lock().init_interrupts(interface)?;

                continue;
            }
            if dev.vendor_id == ixgbe::INTEL_VEND && dev.device_id == ixgbe::INTEL_82599 {
                info!("ixgbe PCI device found at: {:?}", dev.location);
                
                // Initialization parameters of the NIC.
                // These can be changed according to the requirements specified in the ixgbe init function.
                const VIRT_ENABLED: bool = true;
                const RSS_ENABLED: bool = false;
                const RX_DESCS: u16 = 8;
                const TX_DESCS: u16 = 8;
                
                let ixgbe_nic = ixgbe::IxgbeNic::init(
                    dev, 
                    dev.location,
                    VIRT_ENABLED, 
                    None, 
                    RSS_ENABLED, 
                    ixgbe::RxBufferSizeKiB::Buffer2KiB,
                    RX_DESCS,
                    TX_DESCS
                )?;

                ixgbe_devs.push(ixgbe_nic);
                continue;
            }
            if dev.vendor_id == mlx5::MLX_VEND && (dev.device_id == mlx5::CONNECTX5_DEV || dev.device_id == mlx5::CONNECTX5_EX_DEV) {
                info!("mlx5 PCI device found at: {:?}", dev.location);
                const RX_DESCS: usize = 512;
                const TX_DESCS: usize = 8192;
                const MAX_MTU:  u16 = 9000;

                mlx5::ConnectX5Nic::init(dev, TX_DESCS, RX_DESCS, MAX_MTU)?;
                continue;
            }

            // here: check for and initialize other ethernet cards
        }

        warn!("Ignoring PCI device with no handler. {:X?}", dev);
    }

    // Once all the NICs have been initialized, we can store them and add them to the list of network interfaces.
    // No NIC support on aarch64 at the moment
    #[cfg(target_arch = "x86_64")] {
        let ixgbe_nics = ixgbe::IXGBE_NICS.call_once(|| ixgbe_devs);
        for ixgbe_nic_ref in ixgbe_nics.iter() {
            net::register_device(ixgbe_nic_ref);
        }
    }

    // Convenience notification for developers to inform them of no networking devices
    // No NIC support on aarch64 at the moment
    #[cfg(target_arch = "x86_64")]
    if net::get_default_interface().is_none() {
        warn!("Note: no network devices found on this system.");
    }

    // Discover filesystems from each storage device on the storage controllers initialized above
    // and mount each filesystem to the root directory by default.
    // No storage device support on aarch64 at the moment
    #[cfg(target_arch = "x86_64")]
    if false {
        for storage_device in storage_manager::storage_devices() {
            let disk = fatfs_adapter::FatFsAdapter::new(
                ReaderWriter::new(
                    ByteReaderWriterWrapper::from(
                        LockableIo::<dyn StorageDevice + Send, spin::Mutex<_>, _>::from(storage_device)
                    )
                ),
            );

            if let Ok(filesystem) = fatfs::FileSystem::new(disk, fatfs::FsOptions::new()) {
                debug!("FATFS data:
                    fat_type: {:?},
                    volume_id: {:X?},
                    volume_label: {:?},
                    cluster_size: {:?},
                    status_flags: {:?},
                    stats: {:?}",
                    filesystem.fat_type(),
                    filesystem.volume_id(),
                    filesystem.volume_label(),
                    filesystem.cluster_size(),
                    filesystem.read_status_flags(),
                    filesystem.stats(),
                );

                let root = filesystem.root_dir();
                debug!("Root directory contents:");
                for f in root.iter() {
                    debug!("\t {:X?}", f.map(|entry| (entry.file_name(), entry.attributes(), entry.len())));
                }
            }
        }
    }

    Ok(())
}

#[cfg(target_arch = "x86_64")]
mod fatfs_adapter {
// TODO: move the following `FatFsAdapter` stuff into a separate crate. 

use derive_more::{From, Into};

/// An adapter (wrapper type) that implements traits required by the [`fatfs`] crate
/// for any I/O device that wants to be usable by [`fatfs`].
///
/// To meet [`fatfs`]'s requirements, the underlying I/O stream must be able to 
/// read, write, and seek while tracking its current offset. 
/// We use traits from the [`core2`] crate to meet these requirements, 
/// thus, the given `IO` parameter must implement those [`core2`] traits.
///
/// For example, this allows one to access a FAT filesystem 
/// by reading from or writing to a storage device.
pub struct FatFsAdapter<IO>(IO);
impl<IO> FatFsAdapter<IO> {
    pub fn new(io: IO) -> FatFsAdapter<IO> { FatFsAdapter(io) }
}
/// This tells the `fatfs` crate that our read/write/seek functions
/// may return errors of the type [`FatFsIoErrorAdapter`],
/// which is a simple wrapper around [`core2::io::Error`].
impl<IO> fatfs::IoBase for FatFsAdapter<IO> {
    type Error = FatFsIoErrorAdapter;
}
impl<IO> fatfs::Read for FatFsAdapter<IO> where IO: core2::io::Read {
    fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
        self.0.read(buf).map_err(Into::into)
    }
}
impl<IO> fatfs::Write for FatFsAdapter<IO> where IO: core2::io::Write {
    fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
        self.0.write(buf).map_err(Into::into)
    }
    fn flush(&mut self) -> Result<(), Self::Error> {
        self.0.flush().map_err(Into::into)
    }
}
impl<IO> fatfs::Seek for FatFsAdapter<IO> where IO: core2::io::Seek {
    fn seek(&mut self, pos: fatfs::SeekFrom) -> Result<u64, Self::Error> {
        let core2_pos = match pos {
            fatfs::SeekFrom::Start(s)   => core2::io::SeekFrom::Start(s),
            fatfs::SeekFrom::Current(c) => core2::io::SeekFrom::Current(c),
            fatfs::SeekFrom::End(e)     => core2::io::SeekFrom::End(e),
        };
        self.0.seek(core2_pos).map_err(Into::into)
    }
}

/// This struct exists to enable us to implement the [`fatfs::IoError`] trait
/// for the [`core2::io::Error`] trait.
/// 
/// This is required because Rust prevents implementing foreign traits for foreign types.
#[derive(Debug, From, Into)]
pub struct FatFsIoErrorAdapter(core2::io::Error);
impl fatfs::IoError for FatFsIoErrorAdapter {
    fn is_interrupted(&self) -> bool {
        self.0.kind() == core2::io::ErrorKind::Interrupted
    }
    fn new_unexpected_eof_error() -> Self {
        FatFsIoErrorAdapter(core2::io::ErrorKind::UnexpectedEof.into())
    }
    fn new_write_zero_error() -> Self {
        FatFsIoErrorAdapter(core2::io::ErrorKind::WriteZero.into())
    }
}
}