ostd/bus/pci/
cfg_space.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
// SPDX-License-Identifier: MPL-2.0

//! The PCI configuration space.
//!
//! Reference: <https://wiki.osdev.org/PCI>

use alloc::sync::Arc;

use bitflags::bitflags;

use super::PciDeviceLocation;
use crate::{
    arch::device::io_port::{PortRead, PortWrite},
    io::IoMem,
    mm::{
        page_prop::{CachePolicy, PageFlags},
        PodOnce, VmIoOnce,
    },
    Error, Result,
};

/// Offset in PCI device's common configuration space(Not the PCI bridge).
#[repr(u16)]
pub enum PciDeviceCommonCfgOffset {
    /// Vendor ID
    VendorId = 0x00,
    /// Device ID
    DeviceId = 0x02,
    /// PCI Command
    Command = 0x04,
    /// PCI Status
    Status = 0x06,
    /// Revision ID
    RevisionId = 0x08,
    /// Class code
    ClassCode = 0x09,
    /// Cache Line Size
    CacheLineSize = 0x0C,
    /// Latency Timer
    LatencyTimer = 0x0D,
    /// Header Type: Identifies the layout of the header.
    HeaderType = 0x0E,
    /// BIST: Represents the status and allows control of a devices BIST(built-in self test).
    Bist = 0x0F,
    /// Base Address Register #0
    Bar0 = 0x10,
    /// Base Address Register #1
    Bar1 = 0x14,
    /// Base Address Register #2
    Bar2 = 0x18,
    /// Base Address Register #3
    Bar3 = 0x1C,
    /// Base Address Register #4
    Bar4 = 0x20,
    /// Base Address Register #5
    Bar5 = 0x24,
    /// Cardbus CIS Pointer
    CardbusCisPtr = 0x28,
    /// Subsystem Vendor ID
    SubsystemVendorId = 0x2C,
    /// Subsystem ID
    SubsystemId = 0x2E,
    /// Expansion ROM base address
    XromBar = 0x30,
    /// Capabilities pointer
    CapabilitiesPointer = 0x34,
    /// Interrupt Line
    InterruptLine = 0x3C,
    /// INterrupt PIN
    InterruptPin = 0x3D,
    /// Min Grant
    MinGrant = 0x3E,
    /// Max latency
    MaxLatency = 0x3F,
}

bitflags! {
    /// PCI device common config space command register.
    pub struct Command: u16 {
        /// Sets to 1 if the device can respond to I/O Space accesses.
        const IO_SPACE                  =  1 << 0;
        /// Sets to 1 if the device can respond to Memory SPace accesses.
        const MEMORY_SPACE              =  1 << 1;
        /// Sets to 1 if the device can behave as a bus master.
        const BUS_MASTER                =  1 << 2;
        /// Sets to 1 if the device can monitor Special Cycle operations.
        const SPECIAL_CYCLES            =  1 << 3;
        /// Memory Write and Invalidate Enable. Set to 1 if the device can
        /// generate the Memory Write and Invalidate command.
        const MWI_ENABLE                =  1 << 4;
        /// Sets to 1 if the device does not respond to palette register writes
        /// and will snoop the data.
        const VGA_PALETTE_SNOOP         =  1 << 5;
        /// Sets to 1 if the device will takes its normal action when a parity
        /// error is detected.
        const PARITY_ERROR_RESPONSE     =  1 << 6;
        /// Sets to 1 if the SERR# driver is enabled.
        const SERR_ENABLE               =  1 << 8;
        /// Sets to 1 if the device is allowed to generate fast back-to-back
        /// transactions
        const FAST_BACK_TO_BACK_ENABLE  =  1 << 9;
        /// Sets to 1 if the assertion of the devices INTx# signal is disabled.
        const INTERRUPT_DISABLE         =  1 << 10;
    }
}

bitflags! {
    /// PCI device common config space status register.
    pub struct Status: u16 {
        /// The status of the device's INTx# signal.
        const INTERRUPT_STATUS          = 1 << 3;
        /// Sets to 1 if the device support capabilities.
        const CAPABILITIES_LIST         = 1 << 4;
        /// Sets to 1 if the device is capable of running at 66 MHz.
        const MHZ66_CAPABLE             = 1 << 5;
        /// Sets to 1 if the device can accept fast back-to-back transactions
        /// that are not from the same agent.
        const FAST_BACK_TO_BACK_CAPABLE = 1 << 7;
        /// This bit is only set when the following conditions are met:
        /// 1. The bus agent asserted PERR# on a read or observed an assertion
        /// of PERR# on a write
        /// 2. The agent setting the bit acted as the bus master for the
        /// operation in which the error occurred
        /// 3. Bit 6 of the Command register (Parity Error Response bit) is set
        ///  to 1.
        const MASTER_DATA_PARITY_ERROR  = 1 << 8;
        /// The read-only bit that represent the slowest time that a device will
        /// assert DEVSEL# for any bus command except Configuration Space read
        /// and writes.
        ///
        /// If both `DEVSEL_MEDIUM_TIMING` and `DEVSEL_SLOW_TIMING` are not set,
        /// then it represents fast timing
        const DEVSEL_MEDIUM_TIMING      = 1 << 9;
        /// Check `DEVSEL_MEDIUM_TIMING`
        const DEVSEL_SLOW_TIMING        = 1 << 10;
        /// Sets to 1 when a target device terminates a transaction with Target-
        /// Abort.
        const SIGNALED_TARGET_ABORT     = 1 << 11;
        /// Sets to 1 by a master device when its transaction is terminated with
        /// Target-Abort
        const RECEIVED_TARGET_ABORT     = 1 << 12;
        /// Sets to 1 by a master device when its transaction (except for Special
        /// Cycle transactions) is terminated with Master-Abort.
        const RECEIVED_MASTER_ABORT     = 1 << 13;
        /// Sets to 1 when the device asserts SERR#
        const SIGNALED_SYSTEM_ERROR     = 1 << 14;
        /// Sets to 1 when the device detects a parity error, even if parity error
        /// handling is disabled.
        const DETECTED_PARITY_ERROR     = 1 << 15;
    }
}

/// BAR space in PCI common config space.
#[derive(Debug, Clone)]
pub enum Bar {
    /// Memory BAR
    Memory(Arc<MemoryBar>),
    /// I/O BAR
    Io(Arc<IoBar>),
}

impl Bar {
    pub(super) fn new(location: PciDeviceLocation, index: u8) -> Result<Self> {
        if index >= 6 {
            return Err(Error::InvalidArgs);
        }

        let offset = index as u16 * 4 + PciDeviceCommonCfgOffset::Bar0 as u16;
        let raw = location.read32(offset);

        // Check the "Space Indicator" bit.
        let result = if raw & 1 == 0 {
            // Memory BAR
            Self::Memory(Arc::new(MemoryBar::new(&location, index, raw)?))
        } else {
            // I/O port BAR
            Self::Io(Arc::new(IoBar::new(&location, index, raw)?))
        };
        Ok(result)
    }

    /// Reads a value of a specified type at a specified offset.
    pub fn read_once<T: PodOnce + PortRead>(&self, offset: usize) -> Result<T> {
        match self {
            Bar::Memory(mem_bar) => mem_bar.io_mem().read_once(offset),
            Bar::Io(io_bar) => io_bar.read(offset as u32),
        }
    }

    /// Writes a value of a specified type at a specified offset.
    pub fn write_once<T: PodOnce + PortWrite>(&self, offset: usize, value: T) -> Result<()> {
        match self {
            Bar::Memory(mem_bar) => mem_bar.io_mem().write_once(offset, &value),
            Bar::Io(io_bar) => io_bar.write(offset as u32, value),
        }
    }
}

/// Memory BAR
#[derive(Debug, Clone)]
pub struct MemoryBar {
    base: u64,
    size: u64,
    prefetchable: bool,
    address_length: AddrLen,
    io_memory: IoMem,
}

impl MemoryBar {
    /// Memory BAR bits type
    pub fn address_length(&self) -> AddrLen {
        self.address_length
    }

    /// Whether this bar is prefetchable, allowing the CPU to get the data
    /// in advance.
    pub fn prefetchable(&self) -> bool {
        self.prefetchable
    }

    /// Base address
    pub fn base(&self) -> u64 {
        self.base
    }

    /// Size of the memory
    pub fn size(&self) -> u64 {
        self.size
    }

    /// Grants I/O memory access
    pub fn io_mem(&self) -> &IoMem {
        &self.io_memory
    }

    /// Creates a memory BAR structure.
    fn new(location: &PciDeviceLocation, index: u8, raw: u32) -> Result<Self> {
        debug_assert_eq!(raw & 1, 0);

        // Quoted sentences below are from "7.5.1.2.1 Base Address Registers (Offset 10h - 24h)" in
        // "PCI Express(R) Base Specification Revision 5.0 Version 1.0".

        // Check the "Memory Type" bitfield.
        let address_length = match (raw >> 1) & 3 {
            // "Base register is 32 bits wide and can be mapped anywhere in the 32 address bit
            // Memory Space."
            0b00 => AddrLen::Bits32,
            // "Base register is 64 bits wide and can be mapped anywhere in the 64 address bit
            // Memory Space."
            0b10 => AddrLen::Bits64,
            // "Reserved."
            _ => return Err(Error::InvalidArgs),
        };

        let offset = index as u16 * 4 + PciDeviceCommonCfgOffset::Bar0 as u16;

        // "Software saves the original value of the Base Address register, writes a value of all
        // 1's to the register, then reads it back."
        location.write32(offset, !0);

        let size_encoded = location.read32(offset);

        // "Unimplemented Base Address registers are hardwired to zero."
        if size_encoded == 0 {
            return Err(Error::InvalidArgs);
        }

        // "64-bit (memory) Base Address registers can be handled the same, except that the second
        // 32 bit register is considered an extension of the first (i.e., bits 63:32). Software
        // writes a value of all 1's to both registers, reads them back, and combines the result
        // into a 64-bit value."
        #[cfg_attr(target_arch = "loongarch64", expect(unused_variables))]
        let (raw64, size_encoded64) = match address_length {
            AddrLen::Bits32 => (raw as u64, size_encoded as u64 | ((u32::MAX as u64) << 32)),
            AddrLen::Bits64 => {
                let raw64 = raw as u64 | ((location.read32(offset + 4) as u64) << 32);
                location.write32(offset + 4, !0);
                let size_encoded64 =
                    size_encoded as u64 | ((location.read32(offset + 4) as u64) << 32);
                (raw64, size_encoded64)
            }
        };

        // "Size calculation can be done from the 32 bit value read by first clearing encoding
        // information bits (bits 1:0 for I/O, bits 3:0 for memory), inverting all 32 bits (logical
        // NOT), then incrementing by 1."
        let size = !(size_encoded64 & !0xF) + 1;

        // Restore the original base address.
        #[cfg(not(target_arch = "loongarch64"))]
        let base = raw64 & !0xF;
        // In LoongArch, the BAR base address needs to be allocated manually.
        #[cfg(target_arch = "loongarch64")]
        let base = {
            use core::alloc::Layout;
            crate::arch::pci::alloc_mmio(
                Layout::from_size_align(size as usize, size as usize).unwrap(),
            )
            .unwrap() as u64
        };
        match address_length {
            AddrLen::Bits32 => location.write32(offset, base as u32),
            AddrLen::Bits64 => {
                location.write32(offset, base as u32);
                location.write32(offset + 4, (base >> 32) as u32);
            }
        }

        // FIXME: At least on some x86 laptops, it has been found that the BIOS does not properly
        // initialize all PCI devices. Consequently, the base address reported by uninitialized PCI
        // devices is zero. To address this, we may need to add the ability to manually allocate
        // the base address.
        #[cfg(not(target_arch = "loongarch64"))]
        if base == 0 {
            log::info!(
                "presumably uninitialized BAR {} (Memory {:?}, size={}) of PCI device {:?}",
                index,
                address_length,
                size,
                location,
            );
            return Err(Error::InvalidArgs);
        }

        // Check the "Prefetchable" bit.
        let prefetchable = raw & 0b1000 != 0;

        Ok(MemoryBar {
            base,
            size,
            prefetchable,
            address_length,
            // SAFETY: The address range is initialized by the BIOS or allocated by us. It is
            // guaranteed to be I/O memory.
            io_memory: unsafe {
                IoMem::new(
                    (base as usize)..((base + size) as usize),
                    PageFlags::RW,
                    CachePolicy::Uncacheable,
                )
            },
        })
    }
}

/// Whether this BAR is 64bit address or 32bit address
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum AddrLen {
    /// 32 bits
    Bits32,
    /// 64 bits
    Bits64,
}

/// I/O port BAR.
#[derive(Debug, Clone, Copy)]
pub struct IoBar {
    base: u32,
    size: u32,
}

impl IoBar {
    /// Base port
    pub fn base(&self) -> u32 {
        self.base
    }

    /// Size of the port
    pub fn size(&self) -> u32 {
        self.size
    }

    /// Reads from port
    pub fn read<T: PortRead>(&self, offset: u32) -> Result<T> {
        // Check alignment
        if (self.base + offset) % size_of::<T>() as u32 != 0 {
            return Err(Error::InvalidArgs);
        }
        // Check overflow
        if self.size < size_of::<T>() as u32 || offset > self.size - size_of::<T>() as u32 {
            return Err(Error::InvalidArgs);
        }
        // SAFETY: The range of ports accessed is within the scope managed by the IoBar and
        // an out-of-bounds check is performed.
        unsafe { Ok(T::read_from_port((self.base + offset) as u16)) }
    }

    /// Writes to port
    pub fn write<T: PortWrite>(&self, offset: u32, value: T) -> Result<()> {
        // Check alignment
        if (self.base + offset) % size_of::<T>() as u32 != 0 {
            return Err(Error::InvalidArgs);
        }
        // Check overflow
        if size_of::<T>() as u32 > self.size || offset > self.size - size_of::<T>() as u32 {
            return Err(Error::InvalidArgs);
        }
        // SAFETY: The range of ports accessed is within the scope managed by the IoBar and
        // an out-of-bounds check is performed.
        unsafe { T::write_to_port((self.base + offset) as u16, value) }
        Ok(())
    }

    /// Creates an I/O port BAR structure.
    fn new(location: &PciDeviceLocation, index: u8, raw: u32) -> Result<Self> {
        debug_assert_eq!(raw & 1, 1);

        let offset = index as u16 * 4 + PciDeviceCommonCfgOffset::Bar0 as u16;

        // "Software saves the original value of the Base Address register, writes a value of all
        // 1's to the register, then reads it back."
        location.write32(offset, !0);
        let size_encoded = location.read32(offset);

        // "Size calculation can be done from the 32 bit value read by first clearing encoding
        // information bits (bits 1:0 for I/O, bits 3:0 for memory), inverting all 32 bits (logical
        // NOT), then incrementing by 1."
        let size = !(size_encoded & !0x3) + 1;

        // Restore the original base address.
        let base = raw & 0x3;
        location.write32(offset, base);

        // FIXME: As with the memory BAR check, we assume that a zero base address means that the
        // BAR has not been initialized. In the future, we may need to add the ability to manually
        // allocate the base address.
        if base == 0 {
            log::info!(
                "presumably uninitialized BAR {} (I/O, size={}) of PCI device {:?}",
                index,
                size,
                location,
            );
            return Err(Error::InvalidArgs);
        }

        Ok(Self { base, size })
    }
}