Repository: firecracker-microvm/firecracker
File: src/vmm/src/vstate/vm/aarch64.rs (Rust)

Code before:
        // with many VMs. It might be a kernel bug but apparently has not been fixed.
        //
        // To mitigate it, QEMU does an inifinite retry on EINTR:
        // - https://github.com/qemu/qemu/commit/94ccff133820552a859c0fb95e33a539e0b90a75
        // - https://github.com/qemu/qemu/commit/bbde13cd14ad4eec18529ce0bf5876058464e124
        //
        // Similarly, we do retries up to 5 times until the bug is fixed.
        const MAX_ATTEMPTS: u8 = 5;
        let mut attempts = 0;
        let fd = loop {
            match kvm.fd.create_vm() {
                Ok(fd) => break fd,
                Err(err) if err.errno() == libc::EINTR => {
                    attempts += 1;
                    info!("KVM_CREATE_VM returned EINTR: {attempts} of {MAX_ATTEMPTS} attempts");
                    if attempts == MAX_ATTEMPTS {
                        return Err(VmError::CreateVm(err));
                    }
                    // Exponential backoff (1us, 2us, 4us, 8us => 15ms in total)
                    std::thread::sleep(std::time::Duration::from_micros(
                        2u64.pow(attempts as u32 - 1),
                    ));
                }
                Err(err) => return Err(VmError::CreateVm(err)),
            }
        };

        Ok(ArchVm {
            fd,
            irqchip_handle: None,
        })
    }

    pub(super) fn arch_pre_create_vcpus(&mut self, _: u8) -> Result<(), ArchVmError> {
        Ok(())
    }

    pub(super) fn arch_post_create_vcpus(&mut self, nr_vcpus: u8) -> Result<(), ArchVmError> {
        // On aarch64, the vCPUs need to be created (i.e call KVM_CREATE_VCPU) before setting up the
        // IRQ chip because the `KVM_CREATE_VCPU` ioctl will return error if the IRQCHIP
        // was already initialized.
        // Search for `kvm_arch_vcpu_create` in arch/arm/kvm/arm.c.
        self.setup_irqchip(nr_vcpus)
    }

    /// Creates the GIC (Global Interrupt Controller).
    pub fn setup_irqchip(&mut self, vcpu_count: u8) -> Result<(), ArchVmError> {
        self.irqchip_handle = Some(
            crate::arch::aarch64::gic::create_gic(&self.fd, vcpu_count.into(), None)
                .map_err(ArchVmError::VmCreateGIC)?,
        );

Review comment (refactor):
Mhh, can we extract this into its own function and move it into architecture independent code? Either by going back to something like a `Vm::create`/`Vm::arch_create` split, where `Vm::create` allocates the FD and passes it to `Vm::arch_create` as a parameter, or by moving the loop into some `Kvm::create_fd()` wrapper that get's called from these two arch functions?

Code after:
            fd,
            irqchip_handle: None,
        })
    }

    pub(super) fn arch_pre_create_vcpus(&mut self, _: u8) -> Result<(), ArchVmError> {
        Ok(())
    }

    pub(super) fn arch_post_create_vcpus(&mut self, nr_vcpus: u8) -> Result<(), ArchVmError> {
        // On aarch64, the vCPUs need to be created (i.e call KVM_CREATE_VCPU) before setting up the
        // IRQ chip because the `KVM_CREATE_VCPU` ioctl will return error if the IRQCHIP
        // was already initialized.
        // Search for `kvm_arch_vcpu_create` in arch/arm/kvm/arm.c.
        self.setup_irqchip(nr_vcpus)
    }

    /// Creates the GIC (Global Interrupt Controller).
    pub fn setup_irqchip(&mut self, vcpu_count: u8) -> Result<(), ArchVmError> {
        self.irqchip_handle = Some(
            crate::arch::aarch64::gic::create_gic(&self.fd, vcpu_count.into(), None)
                .map_err(ArchVmError::VmCreateGIC)?,
        );
        Ok(())
    }

    /// Gets a reference to the irqchip of the VM.
    pub fn get_irqchip(&self) -> &crate::arch::aarch64::gic::GICDevice {
        self.irqchip_handle.as_ref().expect("IRQ chip not set")
    }

    /// Saves and returns the Kvm Vm state.
    pub fn save_state(&self, mpidrs: &[u64]) -> Result<VmState, ArchVmError> {
        Ok(VmState {
            gic: self
                .get_irqchip()
                .save_device(mpidrs)
                .map_err(ArchVmError::SaveGic)?,
        })
    }

    /// Restore the KVM VM state
    ///
    /// # Errors
    ///
    /// When [`crate::arch::aarch64::gic::GICDevice::restore_device`] errors.
    pub fn restore_state(&mut self, mpidrs: &[u64], state: &VmState) -> Result<(), ArchVmError> {
        self.get_irqchip()
            .restore_device(mpidrs, &state.gic)
            .map_err(ArchVmError::RestoreGic)?;
        Ok(())