pci/
lib.rs

1/* Copyright (c) 2015 The Robigalia Project Developers
2 * Licensed under the Apache License, Version 2.0
3 * <LICENSE-APACHE or
4 * http://www.apache.org/licenses/LICENSE-2.0> or the MIT
5 * license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
6 * at your option. All files in the project carrying such
7 * notice may not be copied, modified, or distributed except
8 * according to those terms.
9 */
10
11#![feature(no_std)]
12#![no_std]
13
14//! PCI bus management
15//!
16//! This crate defines various traits, functions, and types for working with the PCI local bus.
17//!
18//!
19//! It is assumed that PCI(e) is already configured - that is, that each device has been allocated
20//! the memory it requests and the BARs are already configured correctly. The firmware (BIOS, UEFI)
21//! usually does this on PC platforms.
22//!
23//! This crate is not yet suitable for multicore use - nothing is synchronized.
24//!
25//! This crate does not yet contain any hardware-specific workarounds for buggy or broken hardware.
26//!
27//! This crate cannot yet exploit PCIe memory-mapped configuration spaces.
28//!
29//! This crate only supports x86, currently.
30
31/// A trait defining port I/O operations.
32///
33/// All port I/O operations are parametric over this trait. This allows operating systems to use
34/// this crate without modifications, by suitably instantiating this trait with their own
35/// primitives.
36pub trait PortOps {
37    fn read8(&self, port: u16) -> u8;
38    fn read16(&self, port: u16) -> u16;
39    fn read32(&self, port: u16) -> u32;
40
41    fn write8(&self, port: u16, val: u8);
42    fn write16(&self, port: u16, val: u16);
43    fn write32(&self, port: u16, val: u32);
44}
45
46const CONFIG_ADDRESS: u16 = 0x0CF8;
47const CONFIG_DATA: u16 = 0x0CFC;
48
49#[derive(Copy, Clone, PartialEq, Eq)]
50pub enum CSpaceAccessMethod {
51    // The legacy, deprecated (as of PCI 2.0) IO-range method.
52    // Until/unless there is a relevant platform that requires this, leave it out.
53    // IO_Mechanism_2
54    /// The legacy (pre-PCIe) 2-IO port method as specified on page 50 of PCI Local Bus
55    /// Specification 3.0.
56    IO,
57    // PCIe memory-mapped configuration space access
58    //MemoryMapped(*mut u8),
59}
60
61// All IO-bus ops are 32-bit, we mask and shift to get the values we want.
62
63impl CSpaceAccessMethod {
64    pub fn read8<T: PortOps>(self, ops: &T, loc: Location, offset: u16) -> u8 {
65        let val = self.read32(ops, loc, offset & 0b11111100);
66        ((val >> ((offset as usize & 0b11) << 3)) & 0xFF) as u8
67    }
68
69    /// Returns a value in native endian.
70    pub fn read16<T: PortOps>(self, ops: &T, loc: Location, offset: u16) -> u16 {
71        let val = self.read32(ops, loc, offset & 0b11111100);
72        ((val >> ((offset as usize & 0b10) << 3)) & 0xFFFF) as u16
73    }
74
75    /// Returns a value in native endian.
76    pub fn read32<T: PortOps>(self, ops: &T, loc: Location, offset: u16) -> u32 {
77        debug_assert!((offset & 0b11) == 0, "misaligned PCI configuration dword u32 read");
78        match self {
79            CSpaceAccessMethod::IO => {
80                ops.write32(CONFIG_ADDRESS, loc.encode() | ((offset as u32) & 0b11111100));
81                ops.read32(CONFIG_DATA).to_le()
82            },
83            //MemoryMapped(ptr) => {
84            //    // FIXME: Clarify whether the rules for GEP/GEPi forbid using regular .offset() here.
85            //    ::core::intrinsics::volatile_load(::core::intrinsics::arith_offset(ptr, offset as usize))
86            //}
87        }
88    }
89
90    pub fn write8<T: PortOps>(self, ops: &T, loc: Location, offset: u16, val: u8) {
91        let old = self.read32(ops, loc, offset);
92        let dest = offset as usize & 0b11 << 3;
93        let mask = (0xFF << dest) as u32;
94        self.write32(ops, loc, offset, ((val as u32) << dest | (old & !mask)).to_le());
95    }
96
97    /// Converts val to little endian before writing.
98    pub fn write16<T: PortOps>(self, ops: &T, loc: Location, offset: u16, val: u16) {
99        let old = self.read32(ops, loc, offset);
100        let dest = offset as usize & 0b10 << 3;
101        let mask = (0xFFFF << dest) as u32;
102        self.write32(ops, loc, offset, ((val as u32) << dest | (old & !mask)).to_le());
103    }
104
105    /// Takes a value in native endian, converts it to little-endian, and writes it to the PCI
106    /// device configuration space at register `offset`.
107    pub fn write32<T: PortOps>(self, ops: &T, loc: Location, offset: u16, val: u32) {
108        debug_assert!((offset & 0b11) == 0, "misaligned PCI configuration dword u32 read");
109        match self {
110            CSpaceAccessMethod::IO => {
111                ops.write32(CONFIG_ADDRESS, loc.encode() | (offset as u32 & 0b11111100));
112                ops.write32(CONFIG_DATA, val.to_le())
113            },
114            //MemoryMapped(ptr) => {
115            //    // FIXME: Clarify whether the rules for GEP/GEPi forbid using regular .offset() here.
116            //    ::core::intrinsics::volatile_load(::core::intrinsics::arith_offset(ptr, offset as usize))
117            //}
118        }
119    }
120}
121
122/// Physical location of a device on the bus
123#[derive(Copy, Clone, PartialEq, Eq)]
124pub struct Location {
125    pub bus: u8,
126    pub device: u8,
127    pub function: u8,
128}
129
130impl Location {
131    #[inline(always)]
132    fn encode(self) -> u32 {
133        (1 << 31) | ((self.bus as u32) << 16) | (((self.device as u32) & 0b11111) << 11) | (((self.function as u32) & 0b111) << 8)
134    }
135}
136
137#[derive(Copy, Clone, PartialEq, Eq)]
138pub struct Identifier {
139    vendor_id: u16,
140    device_id: u16,
141    revision_id: u8,
142    class: u8,
143    subclass: u8,
144}
145
146/// A device on the PCI bus.
147///
148/// Although accessing configuration space may be expensive, it is not cached.
149#[derive(Copy, Clone, PartialEq, Eq)]
150pub struct PCIDevice {
151    pub loc: Location,
152    pub id: Identifier,
153    pub bars: [Option<BAR>; 6],
154    pub cspace_access_method: CSpaceAccessMethod,
155}
156
157pub enum PCIScanError {
158
159}
160
161#[derive(Copy, Clone, PartialEq, Eq)]
162pub enum Prefetchable {
163    Yes,
164    No
165}
166
167#[derive(Copy, Clone, PartialEq, Eq)]
168pub enum Type {
169    Bits32,
170    Bits64
171}
172
173#[derive(Copy, Clone, PartialEq, Eq)]
174pub enum BAR {
175    Memory(u64, u32, Prefetchable, Type),
176    IO(u32),
177}
178
179impl BAR {
180    pub fn decode<T: PortOps>(ops: &T, loc: Location, am: CSpaceAccessMethod, idx: u16) -> (Option<BAR>, usize) {
181        let raw = am.read32(ops, loc, 16 + (idx << 2));
182        if raw & 1 == 0 {
183            let mut bits64 = false;
184            let base: u64 =
185            match (raw & 0b110) >> 1 {
186                0 => { bits64 = true; ((raw & !0xF) as u64) | ((am.read32(ops, loc, 16 + ((idx + 1) << 2)) as u64) << 32) }
187                2 => (raw & !0xF) as u64,
188                _ => { debug_assert!(false, "bad type in memory BAR"); return (None, idx as usize + 1) },
189            };
190            am.write32(ops, loc, 16 + (idx << 2), !0);
191            let len = !am.read32(ops, loc, 16 + (idx << 12)) + 1;
192            am.write32(ops, loc, 16 + (idx << 2), raw);
193            (Some(BAR::Memory(base, len, if raw & 0b1000 == 0 { Prefetchable::No } else { Prefetchable::Yes },
194                        if bits64 { Type::Bits64 } else { Type::Bits32 })),
195             if bits64 { idx + 2 } else { idx + 1 } as usize)
196        } else {
197            (Some(BAR::IO(raw & !0x3)), idx as usize + 1)
198        }
199    }
200}
201
202pub struct BusScan<'a, T: PortOps+'a> {
203    loc: Location,
204    ops: &'a T,
205    am: CSpaceAccessMethod,
206}
207
208impl<'a, T: PortOps> BusScan<'a, T> {
209    fn done(&self) -> bool {
210        if self.loc.bus == 255 && self.loc.device == 31 && self.loc.function == 7 {
211            true
212        } else {
213            false
214        }
215    }
216
217    fn increment(&mut self) {
218        // TODO: Decide whether this is actually nicer than taking a u16 and incrementing until it
219        // wraps.
220        if self.loc.function < 7 {
221            self.loc.function += 1;
222            return
223        } else {
224            self.loc.function = 0;
225            if self.loc.device < 31 {
226                self.loc.device += 1;
227                return;
228            } else {
229                self.loc.device = 0;
230                if self.loc.bus == 255 {
231                    self.loc.device = 31;
232                    self.loc.device = 7;
233                } else {
234                    self.loc.bus += 1;
235                    return;
236                }
237            }
238        }
239    }
240}
241
242impl<'a, T: PortOps> ::core::iter::Iterator for BusScan<'a, T> {
243    type Item = PCIDevice;
244    #[inline]
245    fn next(&mut self) -> Option<PCIDevice> {
246        // FIXME: very naive atm, could be smarter and waste much less time by only scanning used
247        // busses.
248        let mut ret = None;
249        loop {
250            if self.done() {
251                return ret;
252            }
253            ret = probe_function(self.ops, self.loc, self.am);
254            self.increment();
255            if ret.is_some() {
256                return ret;
257            }
258        }
259    }
260}
261
262pub fn probe_function<T: PortOps>(ops: &T, loc: Location, am: CSpaceAccessMethod) -> Option<PCIDevice> {
263    // FIXME: it'd be more efficient to use read32 and decode separately.
264    let vid = am.read16(ops, loc, 0);
265    if vid == 0xFFFF {
266        return None;
267    }
268    let did = am.read16(ops, loc, 2);
269    let rid = am.read8(ops, loc, 8);
270    let subclass = am.read8(ops, loc, 10);
271    let class = am.read8(ops, loc, 11);
272    let id = Identifier {
273        vendor_id: vid,
274        device_id: did,
275        revision_id: rid,
276        class: class,
277        subclass: subclass,
278    };
279    let hdrty = am.read8(ops, loc, 14);
280    let mut bars = [None, None, None, None, None, None];
281    let max = match hdrty {
282        0 => 6,
283        1 => 2,
284        _ => 0,
285    };
286    let mut i = 0;
287    while i < max {
288        let (bar, next) = BAR::decode(ops, loc, am, i as u16);
289        bars[i] = bar;
290        i = next;
291    }
292    Some(PCIDevice {
293        loc: loc,
294        id: id,
295        bars: bars,
296        cspace_access_method: am,
297    })
298}
299
300pub fn scan_bus<'a, T: PortOps>(ops: &'a T, am: CSpaceAccessMethod) -> BusScan<'a, T> {
301    BusScan { loc: Location { bus: 0, device: 0, function: 0 }, ops: ops, am: am }
302}