vmm/device_manager/
legacy.rs

1// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3//
4// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
5// Use of this source code is governed by a BSD-style license that can be
6// found in the THIRD-PARTY file.
7#![cfg(target_arch = "x86_64")]
8
9use std::fmt::Debug;
10use std::sync::{Arc, Mutex};
11
12use acpi_tables::aml::AmlError;
13use acpi_tables::{Aml, aml};
14use libc::EFD_NONBLOCK;
15use vm_superio::Serial;
16use vmm_sys_util::eventfd::EventFd;
17
18use crate::Vm;
19use crate::devices::legacy::serial::SerialOut;
20use crate::devices::legacy::{EventFdTrigger, I8042Device, SerialDevice, SerialEventsWrapper};
21use crate::vstate::bus::BusError;
22
23/// Errors corresponding to the `PortIODeviceManager`.
24#[derive(Debug, derive_more::From, thiserror::Error, displaydoc::Display)]
25pub enum LegacyDeviceError {
26    /// Failed to add legacy device to Bus: {0}
27    BusError(BusError),
28    /// Failed to create EventFd: {0}
29    EventFd(std::io::Error),
30}
31
32/// The `PortIODeviceManager` is a wrapper that is used for registering legacy devices
33/// on an I/O Bus. It currently manages the uart and i8042 devices.
34/// The `LegacyDeviceManger` should be initialized only by using the constructor.
35#[derive(Debug)]
36pub struct PortIODeviceManager {
37    // BusDevice::Serial
38    pub stdio_serial: Arc<Mutex<SerialDevice>>,
39    // BusDevice::I8042Device
40    pub i8042: Arc<Mutex<I8042Device>>,
41
42    // Communication event on ports 1 & 3.
43    pub com_evt_1_3: EventFdTrigger,
44    // Communication event on ports 2 & 4.
45    pub com_evt_2_4: EventFdTrigger,
46    // Keyboard event.
47    pub kbd_evt: EventFd,
48}
49
50impl PortIODeviceManager {
51    /// x86 global system interrupt for communication events on serial ports 1
52    /// & 3. See
53    /// <https://en.wikipedia.org/wiki/Interrupt_request_(PC_architecture)>.
54    const COM_EVT_1_3_GSI: u32 = 4;
55    /// x86 global system interrupt for communication events on serial ports 2
56    /// & 4. See
57    /// <https://en.wikipedia.org/wiki/Interrupt_request_(PC_architecture)>.
58    const COM_EVT_2_4_GSI: u32 = 3;
59    /// x86 global system interrupt for keyboard port.
60    /// See <https://en.wikipedia.org/wiki/Interrupt_request_(PC_architecture)>.
61    const KBD_EVT_GSI: u32 = 1;
62    /// Legacy serial port device addresses. See
63    /// <https://tldp.org/HOWTO/Serial-HOWTO-10.html#ss10.1>.
64    const SERIAL_PORT_ADDRESSES: [u64; 4] = [0x3f8, 0x2f8, 0x3e8, 0x2e8];
65    /// Size of legacy serial ports.
66    const SERIAL_PORT_SIZE: u64 = 0x8;
67    /// i8042 keyboard data register address. See
68    /// <https://elixir.bootlin.com/linux/latest/source/drivers/input/serio/i8042-io.h#L41>.
69    const I8042_KDB_DATA_REGISTER_ADDRESS: u64 = 0x060;
70    /// i8042 keyboard data register size.
71    const I8042_KDB_DATA_REGISTER_SIZE: u64 = 0x5;
72
73    /// Create a new DeviceManager handling legacy devices (uart, i8042).
74    pub fn new(
75        stdio_serial: Arc<Mutex<SerialDevice>>,
76        i8042: Arc<Mutex<I8042Device>>,
77    ) -> Result<Self, LegacyDeviceError> {
78        let com_evt_1_3 = stdio_serial
79            .lock()
80            .expect("Poisoned lock")
81            .serial
82            .interrupt_evt()
83            .try_clone()?;
84        let com_evt_2_4 = EventFdTrigger::new(EventFd::new(EFD_NONBLOCK)?);
85        let kbd_evt = i8042
86            .lock()
87            .expect("Poisoned lock")
88            .kbd_interrupt_evt
89            .try_clone()?;
90
91        Ok(PortIODeviceManager {
92            stdio_serial,
93            i8042,
94            com_evt_1_3,
95            com_evt_2_4,
96            kbd_evt,
97        })
98    }
99
100    /// Register supported legacy devices.
101    pub fn register_devices(&mut self, vm: &Vm) -> Result<(), LegacyDeviceError> {
102        let serial_2_4 = Arc::new(Mutex::new(SerialDevice {
103            serial: Serial::with_events(
104                self.com_evt_2_4.try_clone()?.try_clone()?,
105                SerialEventsWrapper {
106                    buffer_ready_event_fd: None,
107                },
108                SerialOut::Sink,
109            ),
110            input: None,
111        }));
112        let serial_1_3 = Arc::new(Mutex::new(SerialDevice {
113            serial: Serial::with_events(
114                self.com_evt_1_3.try_clone()?.try_clone()?,
115                SerialEventsWrapper {
116                    buffer_ready_event_fd: None,
117                },
118                SerialOut::Sink,
119            ),
120            input: None,
121        }));
122
123        let io_bus = &vm.pio_bus;
124        io_bus.insert(
125            self.stdio_serial.clone(),
126            Self::SERIAL_PORT_ADDRESSES[0],
127            Self::SERIAL_PORT_SIZE,
128        )?;
129        io_bus.insert(
130            serial_2_4.clone(),
131            Self::SERIAL_PORT_ADDRESSES[1],
132            Self::SERIAL_PORT_SIZE,
133        )?;
134        io_bus.insert(
135            serial_1_3,
136            Self::SERIAL_PORT_ADDRESSES[2],
137            Self::SERIAL_PORT_SIZE,
138        )?;
139        io_bus.insert(
140            serial_2_4,
141            Self::SERIAL_PORT_ADDRESSES[3],
142            Self::SERIAL_PORT_SIZE,
143        )?;
144        io_bus.insert(
145            self.i8042.clone(),
146            Self::I8042_KDB_DATA_REGISTER_ADDRESS,
147            Self::I8042_KDB_DATA_REGISTER_SIZE,
148        )?;
149
150        vm.register_irq(&self.com_evt_1_3, Self::COM_EVT_1_3_GSI)
151            .map_err(|e| {
152                LegacyDeviceError::EventFd(std::io::Error::from_raw_os_error(e.errno()))
153            })?;
154        vm.register_irq(&self.com_evt_2_4, Self::COM_EVT_2_4_GSI)
155            .map_err(|e| {
156                LegacyDeviceError::EventFd(std::io::Error::from_raw_os_error(e.errno()))
157            })?;
158        vm.register_irq(&self.kbd_evt, Self::KBD_EVT_GSI)
159            .map_err(|e| {
160                LegacyDeviceError::EventFd(std::io::Error::from_raw_os_error(e.errno()))
161            })?;
162
163        Ok(())
164    }
165
166    pub(crate) fn append_aml_bytes(bytes: &mut Vec<u8>) -> Result<(), AmlError> {
167        // Set up COM devices
168        let gsi = [
169            Self::COM_EVT_1_3_GSI,
170            Self::COM_EVT_2_4_GSI,
171            Self::COM_EVT_1_3_GSI,
172            Self::COM_EVT_2_4_GSI,
173        ];
174        for com in 0u8..4 {
175            // COM1
176            aml::Device::new(
177                format!("_SB_.COM{}", com + 1).as_str().try_into()?,
178                vec![
179                    &aml::Name::new("_HID".try_into()?, &aml::EisaName::new("PNP0501")?)?,
180                    &aml::Name::new("_UID".try_into()?, &com)?,
181                    &aml::Name::new("_DDN".try_into()?, &format!("COM{}", com + 1))?,
182                    &aml::Name::new(
183                        "_CRS".try_into().unwrap(),
184                        &aml::ResourceTemplate::new(vec![
185                            &aml::Interrupt::new(true, true, false, false, gsi[com as usize]),
186                            &aml::Io::new(
187                                PortIODeviceManager::SERIAL_PORT_ADDRESSES[com as usize]
188                                    .try_into()
189                                    .unwrap(),
190                                PortIODeviceManager::SERIAL_PORT_ADDRESSES[com as usize]
191                                    .try_into()
192                                    .unwrap(),
193                                1,
194                                PortIODeviceManager::SERIAL_PORT_SIZE.try_into().unwrap(),
195                            ),
196                        ]),
197                    )?,
198                ],
199            )
200            .append_aml_bytes(bytes)?;
201        }
202        // Setup i8042
203        aml::Device::new(
204            "_SB_.PS2_".try_into()?,
205            vec![
206                &aml::Name::new("_HID".try_into()?, &aml::EisaName::new("PNP0303")?)?,
207                &aml::Method::new(
208                    "_STA".try_into()?,
209                    0,
210                    false,
211                    vec![&aml::Return::new(&0x0fu8)],
212                ),
213                &aml::Name::new(
214                    "_CRS".try_into()?,
215                    &aml::ResourceTemplate::new(vec![
216                        &aml::Io::new(
217                            PortIODeviceManager::I8042_KDB_DATA_REGISTER_ADDRESS
218                                .try_into()
219                                .unwrap(),
220                            PortIODeviceManager::I8042_KDB_DATA_REGISTER_ADDRESS
221                                .try_into()
222                                .unwrap(),
223                            1u8,
224                            1u8,
225                        ),
226                        // Fake a command port so Linux stops complaining
227                        &aml::Io::new(0x0064, 0x0064, 1u8, 1u8),
228                        &aml::Interrupt::new(true, true, false, false, Self::KBD_EVT_GSI),
229                    ]),
230                )?,
231            ],
232        )
233        .append_aml_bytes(bytes)
234    }
235}
236
237#[cfg(test)]
238mod tests {
239    use super::*;
240    use crate::vstate::vm::tests::setup_vm_with_memory;
241
242    #[test]
243    fn test_register_legacy_devices() {
244        let (_, vm) = setup_vm_with_memory(0x1000);
245        vm.setup_irqchip().unwrap();
246        let mut ldm = PortIODeviceManager::new(
247            Arc::new(Mutex::new(SerialDevice {
248                serial: Serial::with_events(
249                    EventFdTrigger::new(EventFd::new(EFD_NONBLOCK).unwrap()),
250                    SerialEventsWrapper {
251                        buffer_ready_event_fd: None,
252                    },
253                    SerialOut::Sink,
254                ),
255                input: None,
256            })),
257            Arc::new(Mutex::new(
258                I8042Device::new(EventFd::new(libc::EFD_NONBLOCK).unwrap()).unwrap(),
259            )),
260        )
261        .unwrap();
262        ldm.register_devices(&vm).unwrap();
263    }
264}