-
Notifications
You must be signed in to change notification settings - Fork 2
GSI Allocator: free GSIs when they are no longer used #74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: gardenlinux
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,14 +4,10 @@ | |
|
|
||
| #[cfg(target_arch = "x86_64")] | ||
| use std::collections::btree_map::BTreeMap; | ||
| use std::result; | ||
| use std::result::Result; | ||
| use std::sync::Mutex; | ||
|
|
||
| #[derive(Debug)] | ||
| pub enum Error { | ||
| Overflow, | ||
| } | ||
|
|
||
| pub type Result<T> = result::Result<T, Error>; | ||
| use thiserror::Error; | ||
|
|
||
| /// GsiApic | ||
| #[cfg(target_arch = "x86_64")] | ||
|
|
@@ -29,80 +25,191 @@ impl GsiApic { | |
| } | ||
| } | ||
|
|
||
| #[derive(Debug)] | ||
| struct InterruptAllocator { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| words: Vec<usize>, | ||
| size: u32, | ||
| offset: u32, | ||
| } | ||
|
|
||
| /// Errors that may happen while allocating or freeing an interrupt. | ||
| #[derive(Error, Debug)] | ||
| pub enum InterruptAllocError { | ||
| /// Interrupt allocator is exhausted, i.e. out of interrupt vectors. | ||
| #[error("Interrupt allocator is exhausted")] | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| ExhaustedError(), | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| /// Tried to free an interrupt that wasn't allocated. | ||
| #[error("Interrupt was not allocated: {0}")] | ||
| AlreadyFree(u32), | ||
|
|
||
| /// Tried to free an interrupt that is not in range of the interrupt allocator. | ||
| #[error("Interrupt vector is out of range: {0}")] | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| OutOfRange(u32), | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||
|
|
||
| // Maximum number of IRQ routes according to the kernel code. | ||
| const KVM_MAX_IRQ_ROUTES: u32 = 4096; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For upstreaming: This should be either made generic or gated behind |
||
|
|
||
| impl InterruptAllocator { | ||
| fn new(size: u32, offset: u32) -> Self { | ||
| assert_ne!(size, 0); | ||
| let bits_per_word = usize::BITS; | ||
| let num_words = (size + bits_per_word - 1).div_ceil(bits_per_word); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: this way it is even shorter:
|
||
|
|
||
| let mut words = vec![0usize; num_words as usize]; | ||
| words[(num_words - 1) as usize] = Self::last_word(size); | ||
|
|
||
| Self { | ||
| words, | ||
| size, | ||
| offset, | ||
| } | ||
| } | ||
|
|
||
| fn last_word(size: u32) -> usize { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not really sure about why you use |
||
| let rem = size % usize::BITS; | ||
| if rem == 0 { | ||
| 0usize | ||
| } else { | ||
| !((1usize << rem) - 1) | ||
| } | ||
| } | ||
|
|
||
| fn free(&mut self, vector: u32) -> Result<(), InterruptAllocError> { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| // At first we make sure that the vector is not out of range. | ||
| if !(self.offset..self.offset + self.size).contains(&vector) { | ||
| return Err(InterruptAllocError::OutOfRange(vector)); | ||
| } | ||
|
|
||
| let idx = vector.abs_diff(self.offset); | ||
| let (w, b) = Self::word_and_bit(idx); | ||
|
|
||
| let mask = 1usize << b; | ||
|
|
||
| // Let's first check whether the bit is set. | ||
| if self.words[w] & mask == 0usize { | ||
| return Err(InterruptAllocError::AlreadyFree(vector)); | ||
| } | ||
| // Clear the bit and we are done! | ||
| self.words[w] &= !mask; | ||
| Ok(()) | ||
| } | ||
|
|
||
| fn word_and_bit(vector: u32) -> (usize, usize) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. rename: |
||
| let idx = vector as usize; | ||
| let bits = usize::BITS as usize; | ||
| (idx / bits, idx & bits) | ||
| } | ||
|
|
||
| fn alloc(&mut self) -> Result<u32, InterruptAllocError> { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: |
||
| let bits = usize::BITS as usize; | ||
| for idx in 0..self.words.len() { | ||
| let word = self.words[idx]; | ||
|
|
||
| // If every bit is set, we can continue. | ||
| if word == !0usize { | ||
| continue; | ||
| } | ||
|
|
||
| // Find lowest free bit. | ||
| let bit = (!word).trailing_zeros() as usize; | ||
| // Set the bit. | ||
| self.words[idx] |= 1usize << bit; | ||
| // Calculate index, add offset and return. | ||
| let vector = idx * bits + bit; | ||
| return Ok(vector as u32 + self.offset); | ||
| } | ||
|
|
||
| Err(InterruptAllocError::ExhaustedError()) | ||
| } | ||
| } | ||
|
|
||
| /// GsiAllocator | ||
| pub struct GsiAllocator { | ||
| #[cfg(target_arch = "x86_64")] | ||
| apics: BTreeMap<u32, u32>, | ||
| next_irq: u32, | ||
| next_gsi: u32, | ||
| irqs: Mutex<InterruptAllocator>, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does this really need to be a mutex? I'd expect the whole |
||
| gsis: Mutex<InterruptAllocator>, | ||
| } | ||
|
|
||
| impl GsiAllocator { | ||
| #[cfg(target_arch = "x86_64")] | ||
| /// New GSI allocator | ||
| pub fn new(apics: &[GsiApic]) -> Self { | ||
| let mut allocator = GsiAllocator { | ||
| apics: BTreeMap::new(), | ||
| next_irq: 0xffff_ffff, | ||
| next_gsi: 0, | ||
| }; | ||
| let mut allocator_apics = BTreeMap::new(); | ||
| let mut next_irq = 0xffff_ffffu32; | ||
| let mut next_gsi = 0u32; | ||
|
|
||
| for apic in apics { | ||
| if apic.base < allocator.next_irq { | ||
| allocator.next_irq = apic.base; | ||
| if apic.base < next_irq { | ||
| next_irq = apic.base; | ||
| } | ||
|
|
||
| if apic.base + apic.irqs > allocator.next_gsi { | ||
| allocator.next_gsi = apic.base + apic.irqs; | ||
| if apic.base + apic.irqs > next_gsi { | ||
| next_gsi = apic.base + apic.irqs; | ||
| } | ||
|
|
||
| allocator.apics.insert(apic.base, apic.irqs); | ||
| allocator_apics.insert(apic.base, apic.irqs); | ||
| } | ||
|
|
||
| allocator | ||
| Self { | ||
| apics: allocator_apics, | ||
| irqs: Mutex::new(InterruptAllocator::new( | ||
| KVM_MAX_IRQ_ROUTES - next_irq, | ||
| next_irq, | ||
| )), | ||
| gsis: Mutex::new(InterruptAllocator::new( | ||
| KVM_MAX_IRQ_ROUTES - next_gsi, | ||
| next_gsi, | ||
| )), | ||
| } | ||
| } | ||
|
|
||
| #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] | ||
| /// New GSI allocator | ||
| pub fn new() -> Self { | ||
| GsiAllocator { | ||
| next_irq: arch::IRQ_BASE, | ||
| next_gsi: arch::IRQ_BASE, | ||
| irqs: Mutex::new(InterruptAllocator::new( | ||
| KVM_MAX_IRQ_ROUTES - arch::IRQ_BASE, | ||
| arch::IRQ_BASE, | ||
| )), | ||
| gsis: Mutex::new(InterruptAllocator::new( | ||
| KVM_MAX_IRQ_ROUTES - arch::IRQ_BASE, | ||
| arch::IRQ_BASE, | ||
| )), | ||
| } | ||
| } | ||
|
|
||
| /// Allocate a GSI | ||
| pub fn allocate_gsi(&mut self) -> Result<u32> { | ||
| let gsi = self.next_gsi; | ||
| self.next_gsi = self.next_gsi.checked_add(1).ok_or(Error::Overflow)?; | ||
| Ok(gsi) | ||
| pub fn allocate_gsi(&mut self) -> Result<u32, InterruptAllocError> { | ||
| self.gsis.lock().unwrap().alloc() | ||
| } | ||
|
|
||
| /// Frees a GSI | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit/idea: we could also totally over-engineer this: Each Gsi could be a smart object with a
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, we could do that. But I am unsure whether we should do that. Sounds totally over engineered, but on the other hand this would solve the question when to free the interrupts.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the current design is fine. We already have InterruptLine with drop semantics. |
||
| pub fn free_gsi(&mut self, vector: u32) -> Result<(), InterruptAllocError> { | ||
| self.gsis.lock().unwrap().free(vector) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. about commit
Be more confident ;) Replace shouldn't with won't |
||
| } | ||
|
|
||
| #[cfg(target_arch = "x86_64")] | ||
| /// Allocate an IRQ | ||
| pub fn allocate_irq(&mut self) -> Result<u32> { | ||
| let mut irq: u32 = 0; | ||
| pub fn allocate_irq(&mut self) -> Result<u32, InterruptAllocError> { | ||
| let next_irq = self.irqs.lock().unwrap().alloc()?; | ||
| for (base, irqs) in self.apics.iter() { | ||
| // HACKHACK - This only works with 1 single IOAPIC... | ||
| if self.next_irq >= *base && self.next_irq < *base + *irqs { | ||
| irq = self.next_irq; | ||
| self.next_irq += 1; | ||
| if next_irq >= *base && next_irq < *base + *irqs { | ||
| return Ok(next_irq); | ||
| } | ||
| } | ||
|
|
||
| if irq == 0 { | ||
| return Err(Error::Overflow); | ||
| } | ||
|
|
||
| Ok(irq) | ||
| self.irqs.lock().unwrap().free(next_irq)?; | ||
| Err(InterruptAllocError::ExhaustedError()) | ||
| } | ||
|
|
||
| #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] | ||
| /// Allocate an IRQ | ||
| pub fn allocate_irq(&mut self) -> Result<u32> { | ||
| let irq = self.next_irq; | ||
| self.next_irq = self.next_irq.checked_add(1).ok_or(Error::Overflow)?; | ||
| Ok(irq) | ||
| pub fn allocate_irq(&mut self) -> Result<u32, InterruptAllocError> { | ||
| self.irqs.lock().unwrap().alloc() | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,9 +10,9 @@ | |
| use vm_memory::{GuestAddress, GuestUsize}; | ||
|
|
||
| use crate::address::AddressAllocator; | ||
| use crate::gsi::GsiAllocator; | ||
| #[cfg(target_arch = "x86_64")] | ||
| use crate::gsi::GsiApic; | ||
| use crate::gsi::{GsiAllocator, InterruptAllocError}; | ||
| use crate::page_size::get_page_size; | ||
|
|
||
| /// Manages allocating system resources such as address space and interrupt numbers. | ||
|
|
@@ -31,17 +31,17 @@ use crate::page_size::get_page_size; | |
| /// GuestAddress(0x10000000), 0x10000000, | ||
| /// #[cfg(target_arch = "x86_64")] &[GsiApic::new(5, 19)]).unwrap(); | ||
| /// #[cfg(target_arch = "x86_64")] | ||
| /// assert_eq!(allocator.allocate_irq(), Some(5)); | ||
| /// assert_eq!(allocator.allocate_irq().unwrap(), 5); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please keep the old
provides much better error output if something is wrong |
||
| /// #[cfg(target_arch = "aarch64")] | ||
| /// assert_eq!(allocator.allocate_irq(), Some(32)); | ||
| /// assert_eq!(allocator.allocate_irq().unwrap(), 32); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same as above |
||
| /// #[cfg(target_arch = "riscv64")] | ||
| /// assert_eq!(allocator.allocate_irq(), Some(0)); | ||
| /// assert_eq!(allocator.allocate_irq().unwrap(), 0); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same as above |
||
| /// #[cfg(target_arch = "x86_64")] | ||
| /// assert_eq!(allocator.allocate_irq(), Some(6)); | ||
| /// assert_eq!(allocator.allocate_irq().unwrap(), 6); | ||
| /// #[cfg(target_arch = "aarch64")] | ||
| /// assert_eq!(allocator.allocate_irq(), Some(33)); | ||
| /// assert_eq!(allocator.allocate_irq().unwrap(), 33); | ||
| /// #[cfg(target_arch = "riscv64")] | ||
| /// assert_eq!(allocator.allocate_irq(), Some(1)); | ||
| /// assert_eq!(allocator.allocate_irq().unwrap(), 1); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same as above |
||
| /// assert_eq!(allocator.allocate_platform_mmio_addresses(None, 0x1000, Some(0x1000)), Some(GuestAddress(0x1fff_f000))); | ||
| /// | ||
| /// ``` | ||
|
|
@@ -82,13 +82,18 @@ impl SystemAllocator { | |
| } | ||
|
|
||
| /// Reserves the next available system irq number. | ||
| pub fn allocate_irq(&mut self) -> Option<u32> { | ||
| self.gsi_allocator.allocate_irq().ok() | ||
| pub fn allocate_irq(&mut self) -> Result<u32, InterruptAllocError> { | ||
| self.gsi_allocator.allocate_irq() | ||
| } | ||
|
|
||
| /// Reserves the next available GSI. | ||
| pub fn allocate_gsi(&mut self) -> Option<u32> { | ||
| self.gsi_allocator.allocate_gsi().ok() | ||
| pub fn allocate_gsi(&mut self) -> Result<u32, InterruptAllocError> { | ||
| self.gsi_allocator.allocate_gsi() | ||
| } | ||
|
|
||
| /// Frees the given GSI. | ||
| pub fn free_gsi(&mut self, vector: u32) -> Result<(), InterruptAllocError> { | ||
| self.gsi_allocator.free_gsi(vector) | ||
| } | ||
|
|
||
| /// Reserves a section of `size` bytes of IO address space. | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.