// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2025 Google LLC. use kernel::{ page::{PAGE_MASK, PAGE_SIZE}, prelude::*, seq_file::SeqFile, seq_print, task::Pid, }; use crate::range_alloc::{DescriptorState, FreedRange, Range}; /// Keeps track of allocations in a process' mmap. /// /// Each process has an mmap where the data for incoming transactions will be placed. This struct /// keeps track of allocations made in the mmap. For each allocation, we store a descriptor that /// has metadata related to the allocation. We also keep track of available free space. pub(super) struct ArrayRangeAllocator { /// This stores all ranges that are allocated. Unlike the tree based allocator, we do *not* /// store the free ranges. /// /// Sorted by offset. pub(super) ranges: KVec>, size: usize, free_oneway_space: usize, } struct FindEmptyRes { /// Which index in `ranges` should we insert the new range at? /// /// Inserting the new range at this index keeps `ranges` sorted. insert_at_idx: usize, /// Which offset should we insert the new range at? insert_at_offset: usize, } impl ArrayRangeAllocator { pub(crate) fn new(size: usize, alloc: EmptyArrayAlloc) -> Self { Self { ranges: alloc.ranges, size, free_oneway_space: size / 2, } } pub(crate) fn free_oneway_space(&self) -> usize { self.free_oneway_space } pub(crate) fn count_buffers(&self) -> usize { self.ranges.len() } pub(crate) fn total_size(&self) -> usize { self.size } pub(crate) fn is_full(&self) -> bool { self.ranges.len() == self.ranges.capacity() } pub(crate) fn debug_print(&self, m: &SeqFile) -> Result<()> { for range in &self.ranges { seq_print!( m, " buffer {}: {} size {} pid {} oneway {}", 0, range.offset, range.size, range.state.pid(), range.state.is_oneway(), ); if let DescriptorState::Reserved(_) = range.state { seq_print!(m, " reserved\n"); } else { seq_print!(m, " allocated\n"); } } Ok(()) } /// Find somewhere to put a new range. /// /// Unlike the tree implementation, we do not bother to find the smallest gap. The idea is that /// fragmentation isn't a big issue when we don't have many ranges. /// /// Returns the index that the new range should have in `self.ranges` after insertion. fn find_empty_range(&self, size: usize) -> Option { let after_last_range = self.ranges.last().map(Range::endpoint).unwrap_or(0); if size <= self.total_size() - after_last_range { // We can put the range at the end, so just do that. Some(FindEmptyRes { insert_at_idx: self.ranges.len(), insert_at_offset: after_last_range, }) } else { let mut end_of_prev = 0; for (i, range) in self.ranges.iter().enumerate() { // Does it fit before the i'th range? if size <= range.offset - end_of_prev { return Some(FindEmptyRes { insert_at_idx: i, insert_at_offset: end_of_prev, }); } end_of_prev = range.endpoint(); } None } } pub(crate) fn reserve_new( &mut self, debug_id: usize, size: usize, is_oneway: bool, pid: Pid, ) -> Result { // Compute new value of free_oneway_space, which is set only on success. let new_oneway_space = if is_oneway { match self.free_oneway_space.checked_sub(size) { Some(new_oneway_space) => new_oneway_space, None => return Err(ENOSPC), } } else { self.free_oneway_space }; let FindEmptyRes { insert_at_idx, insert_at_offset, } = self.find_empty_range(size).ok_or(ENOSPC)?; self.free_oneway_space = new_oneway_space; let new_range = Range { offset: insert_at_offset, size, state: DescriptorState::new(is_oneway, debug_id, pid), }; // Insert the value at the given index to keep the array sorted. self.ranges .insert_within_capacity(insert_at_idx, new_range) .ok() .unwrap(); Ok(insert_at_offset) } pub(crate) fn reservation_abort(&mut self, offset: usize) -> Result { // This could use a binary search, but linear scans are usually faster for small arrays. let i = self .ranges .iter() .position(|range| range.offset == offset) .ok_or(EINVAL)?; let range = &self.ranges[i]; if let DescriptorState::Allocated(_) = range.state { return Err(EPERM); } let size = range.size; let offset = range.offset; if range.state.is_oneway() { self.free_oneway_space += size; } // This computes the range of pages that are no longer used by *any* allocated range. The // caller will mark them as unused, which means that they can be freed if the system comes // under memory pressure. let mut freed_range = FreedRange::interior_pages(offset, size); #[expect(clippy::collapsible_if)] // reads better like this if offset % PAGE_SIZE != 0 { if i == 0 || self.ranges[i - 1].endpoint() <= (offset & PAGE_MASK) { freed_range.start_page_idx -= 1; } } if range.endpoint() % PAGE_SIZE != 0 { let page_after = (range.endpoint() & PAGE_MASK) + PAGE_SIZE; if i + 1 == self.ranges.len() || page_after <= self.ranges[i + 1].offset { freed_range.end_page_idx += 1; } } self.ranges.remove(i)?; Ok(freed_range) } pub(crate) fn reservation_commit(&mut self, offset: usize, data: &mut Option) -> Result { // This could use a binary search, but linear scans are usually faster for small arrays. let range = self .ranges .iter_mut() .find(|range| range.offset == offset) .ok_or(ENOENT)?; let DescriptorState::Reserved(reservation) = &range.state else { return Err(ENOENT); }; range.state = DescriptorState::Allocated(reservation.clone().allocate(data.take())); Ok(()) } pub(crate) fn reserve_existing(&mut self, offset: usize) -> Result<(usize, usize, Option)> { // This could use a binary search, but linear scans are usually faster for small arrays. let range = self .ranges .iter_mut() .find(|range| range.offset == offset) .ok_or(ENOENT)?; let DescriptorState::Allocated(allocation) = &mut range.state else { return Err(ENOENT); }; let data = allocation.take(); let debug_id = allocation.reservation.debug_id; range.state = DescriptorState::Reserved(allocation.reservation.clone()); Ok((range.size, debug_id, data)) } pub(crate) fn take_for_each)>(&mut self, callback: F) { for range in self.ranges.iter_mut() { if let DescriptorState::Allocated(allocation) = &mut range.state { callback( range.offset, range.size, allocation.reservation.debug_id, allocation.data.take(), ); } } } } pub(crate) struct EmptyArrayAlloc { ranges: KVec>, } impl EmptyArrayAlloc { pub(crate) fn try_new(capacity: usize) -> Result { Ok(Self { ranges: KVec::with_capacity(capacity, GFP_KERNEL)?, }) } }