vmm/devices/pci/
pci_segment.rs

1// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2//
3// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
4// Use of this source code is governed by a BSD-style license that can be
5// found in the LICENSE-BSD-3-Clause file.
6//
7// Copyright © 2019 - 2021 Intel Corporation
8//
9// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
10//
11
12use std::sync::{Arc, Mutex};
13
14#[cfg(target_arch = "x86_64")]
15use acpi_tables::{Aml, aml};
16use log::info;
17use pci::PciBdf;
18#[cfg(target_arch = "x86_64")]
19use uuid::Uuid;
20use vm_allocator::AddressAllocator;
21
22use crate::arch::{ArchVm as Vm, PCI_MMCONFIG_START, PCI_MMIO_CONFIG_SIZE_PER_SEGMENT};
23#[cfg(target_arch = "x86_64")]
24use crate::pci::bus::{PCI_CONFIG_IO_PORT, PCI_CONFIG_IO_PORT_SIZE};
25use crate::pci::bus::{PciBus, PciConfigIo, PciConfigMmio, PciRoot, PciRootError};
26use crate::vstate::bus::{BusDeviceSync, BusError};
27use crate::vstate::resources::ResourceAllocator;
28
29pub struct PciSegment {
30    pub(crate) id: u16,
31    pub(crate) pci_bus: Arc<Mutex<PciBus>>,
32    pub(crate) pci_config_mmio: Arc<Mutex<PciConfigMmio>>,
33    pub(crate) mmio_config_address: u64,
34    pub(crate) proximity_domain: u32,
35
36    #[cfg(target_arch = "x86_64")]
37    pub(crate) pci_config_io: Option<Arc<Mutex<PciConfigIo>>>,
38
39    // Bitmap of PCI devices to hotplug.
40    pub(crate) pci_devices_up: u32,
41    // Bitmap of PCI devices to hotunplug.
42    pub(crate) pci_devices_down: u32,
43    // List of allocated IRQs for each PCI slot.
44    pub(crate) pci_irq_slots: [u8; 32],
45
46    // Device memory covered by this segment
47    pub(crate) start_of_mem32_area: u64,
48    pub(crate) end_of_mem32_area: u64,
49
50    pub(crate) start_of_mem64_area: u64,
51    pub(crate) end_of_mem64_area: u64,
52}
53
54impl std::fmt::Debug for PciSegment {
55    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56        f.debug_struct("PciSegment")
57            .field("id", &self.id)
58            .field("mmio_config_address", &self.mmio_config_address)
59            .field("proximity_domain", &self.proximity_domain)
60            .field("pci_devices_up", &self.pci_devices_up)
61            .field("pci_devices_down", &self.pci_devices_down)
62            .field("pci_irq_slots", &self.pci_irq_slots)
63            .field("start_of_mem32_area", &self.start_of_mem32_area)
64            .field("end_of_mem32_area", &self.end_of_mem32_area)
65            .field("start_of_mem64_area", &self.start_of_mem64_area)
66            .field("end_of_mem64_area", &self.end_of_mem64_area)
67            .finish()
68    }
69}
70
71impl PciSegment {
72    fn build(id: u16, vm: &Arc<Vm>, pci_irq_slots: &[u8; 32]) -> Result<PciSegment, BusError> {
73        let pci_root = PciRoot::new(None);
74        let pci_bus = Arc::new(Mutex::new(PciBus::new(pci_root, vm.clone())));
75
76        let pci_config_mmio = Arc::new(Mutex::new(PciConfigMmio::new(Arc::clone(&pci_bus))));
77        let mmio_config_address = PCI_MMCONFIG_START + PCI_MMIO_CONFIG_SIZE_PER_SEGMENT * id as u64;
78
79        vm.common.mmio_bus.insert(
80            Arc::clone(&pci_config_mmio) as Arc<dyn BusDeviceSync>,
81            mmio_config_address,
82            PCI_MMIO_CONFIG_SIZE_PER_SEGMENT,
83        )?;
84
85        let resource_allocator = vm.resource_allocator();
86
87        let start_of_mem32_area = resource_allocator.mmio32_memory.base();
88        let end_of_mem32_area = resource_allocator.mmio32_memory.end();
89
90        let start_of_mem64_area = resource_allocator.mmio64_memory.base();
91        let end_of_mem64_area = resource_allocator.mmio64_memory.end();
92
93        let segment = PciSegment {
94            id,
95            pci_bus,
96            pci_config_mmio,
97            mmio_config_address,
98            proximity_domain: 0,
99            pci_devices_up: 0,
100            pci_devices_down: 0,
101            #[cfg(target_arch = "x86_64")]
102            pci_config_io: None,
103            start_of_mem32_area,
104            end_of_mem32_area,
105            start_of_mem64_area,
106            end_of_mem64_area,
107            pci_irq_slots: *pci_irq_slots,
108        };
109
110        Ok(segment)
111    }
112
113    #[cfg(target_arch = "x86_64")]
114    pub(crate) fn new(
115        id: u16,
116        vm: &Arc<Vm>,
117        pci_irq_slots: &[u8; 32],
118    ) -> Result<PciSegment, BusError> {
119        use crate::Vm;
120
121        let mut segment = Self::build(id, vm, pci_irq_slots)?;
122        let pci_config_io = Arc::new(Mutex::new(PciConfigIo::new(Arc::clone(&segment.pci_bus))));
123
124        vm.pio_bus.insert(
125            pci_config_io.clone(),
126            PCI_CONFIG_IO_PORT,
127            PCI_CONFIG_IO_PORT_SIZE,
128        )?;
129
130        segment.pci_config_io = Some(pci_config_io);
131
132        info!(
133            "pci: adding PCI segment: id={:#x}, PCI MMIO config address: {:#x}, mem32 area: \
134             [{:#x}-{:#x}], mem64 area: [{:#x}-{:#x}] IO area: [{PCI_CONFIG_IO_PORT:#x}-{:#x}]",
135            segment.id,
136            segment.mmio_config_address,
137            segment.start_of_mem32_area,
138            segment.end_of_mem32_area,
139            segment.start_of_mem64_area,
140            segment.end_of_mem64_area,
141            PCI_CONFIG_IO_PORT + PCI_CONFIG_IO_PORT_SIZE - 1
142        );
143
144        Ok(segment)
145    }
146
147    #[cfg(target_arch = "aarch64")]
148    pub(crate) fn new(
149        id: u16,
150        vm: &Arc<Vm>,
151        pci_irq_slots: &[u8; 32],
152    ) -> Result<PciSegment, BusError> {
153        let segment = Self::build(id, vm, pci_irq_slots)?;
154        info!(
155            "pci: adding PCI segment: id={:#x}, PCI MMIO config address: {:#x}, mem32 area: \
156             [{:#x}-{:#x}], mem64 area: [{:#x}-{:#x}]",
157            segment.id,
158            segment.mmio_config_address,
159            segment.start_of_mem32_area,
160            segment.end_of_mem32_area,
161            segment.start_of_mem64_area,
162            segment.end_of_mem64_area,
163        );
164
165        Ok(segment)
166    }
167
168    pub(crate) fn next_device_bdf(&self) -> Result<PciBdf, PciRootError> {
169        Ok(PciBdf::new(
170            self.id,
171            0,
172            self.pci_bus
173                .lock()
174                .unwrap()
175                .next_device_id()?
176                .try_into()
177                .unwrap(),
178            0,
179        ))
180    }
181}
182
183#[cfg(target_arch = "x86_64")]
184struct PciDevSlot {
185    device_id: u8,
186}
187
188#[cfg(target_arch = "x86_64")]
189impl Aml for PciDevSlot {
190    fn append_aml_bytes(&self, v: &mut Vec<u8>) -> Result<(), aml::AmlError> {
191        let sun = self.device_id;
192        let adr: u32 = (self.device_id as u32) << 16;
193        aml::Device::new(
194            format!("S{:03}", self.device_id).as_str().try_into()?,
195            vec![
196                &aml::Name::new("_SUN".try_into()?, &sun)?,
197                &aml::Name::new("_ADR".try_into()?, &adr)?,
198                &aml::Method::new(
199                    "_EJ0".try_into()?,
200                    1,
201                    true,
202                    vec![&aml::MethodCall::new(
203                        "\\_SB_.PHPR.PCEJ".try_into()?,
204                        vec![&aml::Path::new("_SUN")?, &aml::Path::new("_SEG")?],
205                    )],
206                ),
207            ],
208        )
209        .append_aml_bytes(v)
210    }
211}
212
213#[cfg(target_arch = "x86_64")]
214struct PciDevSlotNotify {
215    device_id: u8,
216}
217
218#[cfg(target_arch = "x86_64")]
219impl Aml for PciDevSlotNotify {
220    fn append_aml_bytes(&self, v: &mut Vec<u8>) -> Result<(), aml::AmlError> {
221        let device_id_mask: u32 = 1 << self.device_id;
222        let object = aml::Path::new(&format!("S{:03}", self.device_id))?;
223        aml::And::new(&aml::Local(0), &aml::Arg(0), &device_id_mask).append_aml_bytes(v)?;
224        aml::If::new(
225            &aml::Equal::new(&aml::Local(0), &device_id_mask),
226            vec![&aml::Notify::new(&object, &aml::Arg(1))],
227        )
228        .append_aml_bytes(v)
229    }
230}
231
232#[cfg(target_arch = "x86_64")]
233struct PciDevSlotMethods {}
234
235#[cfg(target_arch = "x86_64")]
236impl Aml for PciDevSlotMethods {
237    fn append_aml_bytes(&self, v: &mut Vec<u8>) -> Result<(), aml::AmlError> {
238        let mut device_notifies = Vec::new();
239        for device_id in 0..32 {
240            device_notifies.push(PciDevSlotNotify { device_id });
241        }
242
243        let mut device_notifies_refs: Vec<&dyn Aml> = Vec::new();
244        for device_notify in device_notifies.iter() {
245            device_notifies_refs.push(device_notify);
246        }
247
248        aml::Method::new("DVNT".try_into()?, 2, true, device_notifies_refs).append_aml_bytes(v)?;
249        aml::Method::new(
250            "PCNT".try_into()?,
251            0,
252            true,
253            vec![
254                &aml::Acquire::new("\\_SB_.PHPR.BLCK".try_into()?, 0xffff),
255                &aml::Store::new(
256                    &aml::Path::new("\\_SB_.PHPR.PSEG")?,
257                    &aml::Path::new("_SEG")?,
258                ),
259                &aml::MethodCall::new(
260                    "DVNT".try_into()?,
261                    vec![&aml::Path::new("\\_SB_.PHPR.PCIU")?, &aml::ONE],
262                ),
263                &aml::MethodCall::new(
264                    "DVNT".try_into()?,
265                    vec![&aml::Path::new("\\_SB_.PHPR.PCID")?, &3usize],
266                ),
267                &aml::Release::new("\\_SB_.PHPR.BLCK".try_into()?),
268            ],
269        )
270        .append_aml_bytes(v)
271    }
272}
273
274#[cfg(target_arch = "x86_64")]
275struct PciDsmMethod {}
276
277#[cfg(target_arch = "x86_64")]
278impl Aml for PciDsmMethod {
279    fn append_aml_bytes(&self, v: &mut Vec<u8>) -> Result<(), aml::AmlError> {
280        // Refer to ACPI spec v6.3 Ch 9.1.1 and PCI Firmware spec v3.3 Ch 4.6.1
281        // _DSM (Device Specific Method), the following is the implementation in ASL.
282
283        // Method (_DSM, 4, NotSerialized)  // _DSM: Device-Specific Method
284        // {
285        //      If ((Arg0 == ToUUID ("e5c937d0-3553-4d7a-9117-ea4d19c3434d") /* Device Labeling
286        // Interface */))      {
287        //          If ((Arg2 == Zero))
288        //          {
289        //              Return (Buffer (One) { 0x21 })
290        //          }
291        //          If ((Arg2 == 0x05))
292        //          {
293        //              Return (Zero)
294        //          }
295        //      }
296        //
297        //      Return (Buffer (One) { 0x00 })
298        // }
299        //
300        // As per ACPI v6.3 Ch 19.6.142, the UUID is required to be in mixed endian:
301        // Among the fields of a UUID:
302        //   {d1 (8 digits)} - {d2 (4 digits)} - {d3 (4 digits)} - {d4 (16 digits)}
303        // d1 ~ d3 need to be little endian, d4 be big endian.
304        // See https://en.wikipedia.org/wiki/Universally_unique_identifier#Encoding .
305        let uuid = Uuid::parse_str("E5C937D0-3553-4D7A-9117-EA4D19C3434D").unwrap();
306        let (uuid_d1, uuid_d2, uuid_d3, uuid_d4) = uuid.as_fields();
307        let mut uuid_buf = vec![];
308        uuid_buf.extend(uuid_d1.to_le_bytes());
309        uuid_buf.extend(uuid_d2.to_le_bytes());
310        uuid_buf.extend(uuid_d3.to_le_bytes());
311        uuid_buf.extend(uuid_d4);
312        aml::Method::new(
313            "_DSM".try_into()?,
314            4,
315            false,
316            vec![
317                &aml::If::new(
318                    &aml::Equal::new(&aml::Arg(0), &aml::Buffer::new(uuid_buf)),
319                    vec![
320                        &aml::If::new(
321                            &aml::Equal::new(&aml::Arg(2), &aml::ZERO),
322                            vec![&aml::Return::new(&aml::Buffer::new(vec![0x21]))],
323                        ),
324                        &aml::If::new(
325                            &aml::Equal::new(&aml::Arg(2), &0x05u8),
326                            vec![&aml::Return::new(&aml::ZERO)],
327                        ),
328                    ],
329                ),
330                &aml::Return::new(&aml::Buffer::new(vec![0])),
331            ],
332        )
333        .append_aml_bytes(v)
334    }
335}
336
337#[cfg(target_arch = "x86_64")]
338impl Aml for PciSegment {
339    fn append_aml_bytes(&self, v: &mut Vec<u8>) -> Result<(), aml::AmlError> {
340        let mut pci_dsdt_inner_data: Vec<&dyn Aml> = Vec::new();
341        let hid = aml::Name::new("_HID".try_into()?, &aml::EisaName::new("PNP0A08")?)?;
342        pci_dsdt_inner_data.push(&hid);
343        let cid = aml::Name::new("_CID".try_into()?, &aml::EisaName::new("PNP0A03")?)?;
344        pci_dsdt_inner_data.push(&cid);
345        let adr = aml::Name::new("_ADR".try_into()?, &aml::ZERO)?;
346        pci_dsdt_inner_data.push(&adr);
347        let seg = aml::Name::new("_SEG".try_into()?, &self.id)?;
348        pci_dsdt_inner_data.push(&seg);
349        let uid = aml::Name::new("_UID".try_into()?, &aml::ZERO)?;
350        pci_dsdt_inner_data.push(&uid);
351        let cca = aml::Name::new("_CCA".try_into()?, &aml::ONE)?;
352        pci_dsdt_inner_data.push(&cca);
353        let supp = aml::Name::new("SUPP".try_into()?, &aml::ZERO)?;
354        pci_dsdt_inner_data.push(&supp);
355
356        let proximity_domain = self.proximity_domain;
357        let pxm_return = aml::Return::new(&proximity_domain);
358        let pxm = aml::Method::new("_PXM".try_into()?, 0, false, vec![&pxm_return]);
359        pci_dsdt_inner_data.push(&pxm);
360
361        let pci_dsm = PciDsmMethod {};
362        pci_dsdt_inner_data.push(&pci_dsm);
363
364        #[allow(clippy::if_same_then_else)]
365        let crs = if self.id == 0 {
366            aml::Name::new(
367                "_CRS".try_into()?,
368                &aml::ResourceTemplate::new(vec![
369                    &aml::AddressSpace::new_bus_number(0x0u16, 0x0u16)?,
370                    &aml::Io::new(0xcf8, 0xcf8, 1, 0x8),
371                    &aml::Memory32Fixed::new(
372                        true,
373                        self.mmio_config_address.try_into().unwrap(),
374                        PCI_MMIO_CONFIG_SIZE_PER_SEGMENT.try_into().unwrap(),
375                    ),
376                    &aml::AddressSpace::new_memory(
377                        aml::AddressSpaceCacheable::NotCacheable,
378                        true,
379                        self.start_of_mem32_area,
380                        self.end_of_mem32_area,
381                    )?,
382                    &aml::AddressSpace::new_memory(
383                        aml::AddressSpaceCacheable::NotCacheable,
384                        true,
385                        self.start_of_mem64_area,
386                        self.end_of_mem64_area,
387                    )?,
388                    &aml::AddressSpace::new_io(0u16, 0x0cf7u16)?,
389                    &aml::AddressSpace::new_io(0x0d00u16, 0xffffu16)?,
390                ]),
391            )?
392        } else {
393            aml::Name::new(
394                "_CRS".try_into()?,
395                &aml::ResourceTemplate::new(vec![
396                    &aml::AddressSpace::new_bus_number(0x0u16, 0x0u16)?,
397                    &aml::Memory32Fixed::new(
398                        true,
399                        self.mmio_config_address.try_into().unwrap(),
400                        PCI_MMIO_CONFIG_SIZE_PER_SEGMENT.try_into().unwrap(),
401                    ),
402                    &aml::AddressSpace::new_memory(
403                        aml::AddressSpaceCacheable::NotCacheable,
404                        true,
405                        self.start_of_mem32_area,
406                        self.end_of_mem32_area,
407                    )?,
408                    &aml::AddressSpace::new_memory(
409                        aml::AddressSpaceCacheable::NotCacheable,
410                        true,
411                        self.start_of_mem64_area,
412                        self.end_of_mem64_area,
413                    )?,
414                ]),
415            )?
416        };
417        pci_dsdt_inner_data.push(&crs);
418
419        let mut pci_devices = Vec::new();
420        for device_id in 0..32 {
421            let pci_device = PciDevSlot { device_id };
422            pci_devices.push(pci_device);
423        }
424        for pci_device in pci_devices.iter() {
425            pci_dsdt_inner_data.push(pci_device);
426        }
427
428        let pci_device_methods = PciDevSlotMethods {};
429        pci_dsdt_inner_data.push(&pci_device_methods);
430
431        // Build PCI routing table, listing IRQs assigned to PCI devices.
432        let prt_package_list: Vec<(u32, u32)> = self
433            .pci_irq_slots
434            .iter()
435            .enumerate()
436            .map(|(i, irq)| {
437                (
438                    ((((u32::try_from(i).unwrap()) & 0x1fu32) << 16) | 0xffffu32),
439                    *irq as u32,
440                )
441            })
442            .collect();
443        let prt_package_list: Vec<aml::Package> = prt_package_list
444            .iter()
445            .map(|(bdf, irq)| aml::Package::new(vec![bdf, &0u8, &0u8, irq]))
446            .collect();
447        let prt_package_list: Vec<&dyn Aml> = prt_package_list
448            .iter()
449            .map(|item| item as &dyn Aml)
450            .collect();
451        let prt = aml::Name::new("_PRT".try_into()?, &aml::Package::new(prt_package_list))?;
452        pci_dsdt_inner_data.push(&prt);
453
454        aml::Device::new(
455            format!("_SB_.PC{:02X}", self.id).as_str().try_into()?,
456            pci_dsdt_inner_data,
457        )
458        .append_aml_bytes(v)
459    }
460}
461
462#[cfg(test)]
463mod tests {
464
465    use super::*;
466    use crate::arch;
467    use crate::builder::tests::default_vmm;
468    use crate::utils::u64_to_usize;
469
470    #[test]
471    fn test_pci_segment_build() {
472        let vmm = default_vmm();
473        let pci_irq_slots = &[0u8; 32];
474        let pci_segment = PciSegment::new(0, &vmm.vm, pci_irq_slots).unwrap();
475
476        assert_eq!(pci_segment.id, 0);
477        assert_eq!(
478            pci_segment.start_of_mem32_area,
479            arch::MEM_32BIT_DEVICES_START
480        );
481        assert_eq!(
482            pci_segment.end_of_mem32_area,
483            arch::MEM_32BIT_DEVICES_START + arch::MEM_32BIT_DEVICES_SIZE - 1
484        );
485        assert_eq!(
486            pci_segment.start_of_mem64_area,
487            arch::MEM_64BIT_DEVICES_START
488        );
489        assert_eq!(
490            pci_segment.end_of_mem64_area,
491            arch::MEM_64BIT_DEVICES_START + arch::MEM_64BIT_DEVICES_SIZE - 1
492        );
493        assert_eq!(pci_segment.mmio_config_address, arch::PCI_MMCONFIG_START);
494        assert_eq!(pci_segment.proximity_domain, 0);
495        assert_eq!(pci_segment.pci_devices_up, 0);
496        assert_eq!(pci_segment.pci_devices_down, 0);
497        assert_eq!(pci_segment.pci_irq_slots, [0u8; 32]);
498    }
499
500    #[cfg(target_arch = "x86_64")]
501    #[test]
502    fn test_io_bus() {
503        let vmm = default_vmm();
504        let pci_irq_slots = &[0u8; 32];
505        let pci_segment = PciSegment::new(0, &vmm.vm, pci_irq_slots).unwrap();
506
507        let mut data = [0u8; u64_to_usize(PCI_CONFIG_IO_PORT_SIZE)];
508        vmm.vm.pio_bus.read(PCI_CONFIG_IO_PORT, &mut data).unwrap();
509
510        vmm.vm
511            .pio_bus
512            .read(PCI_CONFIG_IO_PORT + PCI_CONFIG_IO_PORT_SIZE, &mut data)
513            .unwrap_err();
514    }
515
516    #[test]
517    fn test_mmio_bus() {
518        let vmm = default_vmm();
519        let pci_irq_slots = &[0u8; 32];
520        let pci_segment = PciSegment::new(0, &vmm.vm, pci_irq_slots).unwrap();
521
522        let mut data = [0u8; u64_to_usize(PCI_MMIO_CONFIG_SIZE_PER_SEGMENT)];
523
524        vmm.vm
525            .common
526            .mmio_bus
527            .read(pci_segment.mmio_config_address, &mut data)
528            .unwrap();
529        vmm.vm
530            .common
531            .mmio_bus
532            .read(
533                pci_segment.mmio_config_address + PCI_MMIO_CONFIG_SIZE_PER_SEGMENT,
534                &mut data,
535            )
536            .unwrap_err();
537    }
538
539    #[test]
540    fn test_next_device_bdf() {
541        let vmm = default_vmm();
542        let pci_irq_slots = &[0u8; 32];
543        let pci_segment = PciSegment::new(0, &vmm.vm, pci_irq_slots).unwrap();
544
545        // Start checking from device id 1, since 0 is allocated to the Root port.
546        for dev_id in 1..32 {
547            let bdf = pci_segment.next_device_bdf().unwrap();
548            // In our case we have a single Segment with id 0, which has
549            // a single bus with id 0. Also, each device of ours has a
550            // single function.
551            assert_eq!(bdf, PciBdf::new(0, 0, dev_id, 0));
552        }
553
554        // We can only have 32 devices on a segment
555        pci_segment.next_device_bdf().unwrap_err();
556    }
557}