use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use lazy_static::lazy_static;
use spin::Mutex;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum CxlTier {
LocalDram = 0,
CxlNear = 1,
CxlFar = 2,
Storage = 3,
}
impl CxlTier {
pub fn latency_ns(&self) -> u64 {
match self {
CxlTier::LocalDram => 100, CxlTier::CxlNear => 300, CxlTier::CxlFar => 1000, CxlTier::Storage => 100_000, }
}
pub fn bandwidth_gbps(&self) -> u64 {
match self {
CxlTier::LocalDram => 200, CxlTier::CxlNear => 64, CxlTier::CxlFar => 32, CxlTier::Storage => 7, }
}
pub fn cost_per_gb(&self) -> u64 {
match self {
CxlTier::LocalDram => 100,
CxlTier::CxlNear => 40,
CxlTier::CxlFar => 20,
CxlTier::Storage => 1,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BlockTemperature {
VeryHot,
Hot,
Warm,
Cold,
Frozen,
}
impl BlockTemperature {
pub fn from_access_pattern(access_count: u64, elapsed_seconds: u64) -> Self {
if elapsed_seconds == 0 {
return BlockTemperature::VeryHot;
}
let accesses_per_second = access_count / elapsed_seconds.max(1);
match accesses_per_second {
100.. => BlockTemperature::VeryHot,
10..=99 => BlockTemperature::Hot,
1..=9 => BlockTemperature::Warm,
0 if access_count > 0 => BlockTemperature::Cold,
_ => BlockTemperature::Frozen,
}
}
pub fn recommended_tier(&self) -> CxlTier {
match self {
BlockTemperature::VeryHot => CxlTier::LocalDram,
BlockTemperature::Hot => CxlTier::CxlNear,
BlockTemperature::Warm => CxlTier::CxlFar,
BlockTemperature::Cold | BlockTemperature::Frozen => CxlTier::Storage,
}
}
}
#[derive(Debug, Clone)]
pub struct CxlBlockMeta {
pub block_id: u64,
pub current_tier: CxlTier,
pub access_count: u64,
pub last_access: u64,
pub first_access: u64,
pub temperature: BlockTemperature,
}
impl CxlBlockMeta {
pub fn new(block_id: u64, tier: CxlTier, timestamp: u64) -> Self {
Self {
block_id,
current_tier: tier,
access_count: 0,
last_access: timestamp,
first_access: timestamp,
temperature: BlockTemperature::Frozen,
}
}
pub fn record_access(&mut self, timestamp: u64) {
self.access_count += 1;
self.last_access = timestamp;
let elapsed = timestamp.saturating_sub(self.first_access);
self.temperature = BlockTemperature::from_access_pattern(self.access_count, elapsed);
}
pub fn should_promote(&self) -> bool {
let recommended = self.temperature.recommended_tier();
recommended < self.current_tier
}
pub fn should_demote(&self, current_time: u64) -> bool {
const DEMOTE_THRESHOLD_SECONDS: u64 = 60;
let time_since_access = current_time.saturating_sub(self.last_access);
if time_since_access > DEMOTE_THRESHOLD_SECONDS {
let recommended = self.temperature.recommended_tier();
return recommended > self.current_tier;
}
false
}
}
#[derive(Debug, Clone, Default)]
pub struct CxlTierStats {
pub capacity: u64,
pub used: u64,
pub block_count: usize,
pub promotions: u64,
pub demotions: u64,
}
lazy_static! {
static ref CXL_MANAGER: Mutex<CxlMemoryManager> = Mutex::new(CxlMemoryManager::new());
}
pub struct CxlMemoryManager {
blocks: BTreeMap<u64, CxlBlockMeta>,
tier_stats: BTreeMap<CxlTier, CxlTierStats>,
timestamp: u64,
total_promotions: u64,
total_demotions: u64,
}
impl Default for CxlMemoryManager {
fn default() -> Self {
Self::new()
}
}
impl CxlMemoryManager {
pub fn new() -> Self {
let mut tier_stats = BTreeMap::new();
tier_stats.insert(
CxlTier::LocalDram,
CxlTierStats {
capacity: 32 * 1024 * 1024 * 1024, ..Default::default()
},
);
tier_stats.insert(
CxlTier::CxlNear,
CxlTierStats {
capacity: 128 * 1024 * 1024 * 1024, ..Default::default()
},
);
tier_stats.insert(
CxlTier::CxlFar,
CxlTierStats {
capacity: 256 * 1024 * 1024 * 1024, ..Default::default()
},
);
tier_stats.insert(
CxlTier::Storage,
CxlTierStats {
capacity: 2 * 1024 * 1024 * 1024 * 1024, ..Default::default()
},
);
Self {
blocks: BTreeMap::new(),
tier_stats,
timestamp: 0,
total_promotions: 0,
total_demotions: 0,
}
}
pub fn init_tiers(
&mut self,
local_dram_gb: u64,
cxl_near_gb: u64,
cxl_far_gb: u64,
storage_tb: u64,
) -> Result<(), &'static str> {
let local_stats = self
.tier_stats
.get_mut(&CxlTier::LocalDram)
.ok_or("LocalDram tier not initialized")?;
local_stats.capacity = local_dram_gb * 1024 * 1024 * 1024;
let near_stats = self
.tier_stats
.get_mut(&CxlTier::CxlNear)
.ok_or("CxlNear tier not initialized")?;
near_stats.capacity = cxl_near_gb * 1024 * 1024 * 1024;
let far_stats = self
.tier_stats
.get_mut(&CxlTier::CxlFar)
.ok_or("CxlFar tier not initialized")?;
far_stats.capacity = cxl_far_gb * 1024 * 1024 * 1024;
let storage_stats = self
.tier_stats
.get_mut(&CxlTier::Storage)
.ok_or("Storage tier not initialized")?;
storage_stats.capacity = storage_tb * 1024 * 1024 * 1024 * 1024;
Ok(())
}
pub fn allocate_block(
&mut self,
block_id: u64,
size: u64,
tier: CxlTier,
) -> Result<(), &'static str> {
let stats = self.tier_stats.get_mut(&tier).ok_or("Invalid tier")?;
if stats.used + size > stats.capacity {
return Err("Tier full");
}
self.timestamp += 1;
let block = CxlBlockMeta::new(block_id, tier, self.timestamp);
self.blocks.insert(block_id, block);
stats.used += size;
stats.block_count += 1;
Ok(())
}
pub fn access_block(&mut self, block_id: u64) {
self.timestamp += 1;
if let Some(block) = self.blocks.get_mut(&block_id) {
block.record_access(self.timestamp);
}
}
pub fn promote_block(
&mut self,
block_id: u64,
block_size: u64,
) -> Result<CxlTier, &'static str> {
let block = self.blocks.get_mut(&block_id).ok_or("Block not found")?;
let new_tier = match block.current_tier {
CxlTier::Storage => CxlTier::CxlFar,
CxlTier::CxlFar => CxlTier::CxlNear,
CxlTier::CxlNear => CxlTier::LocalDram,
CxlTier::LocalDram => return Err("Already at highest tier"),
};
let target_stats = self.tier_stats.get(&new_tier).ok_or("Invalid tier")?;
if target_stats.used + block_size > target_stats.capacity {
return Err("Target tier full");
}
let old_tier = block.current_tier;
let old_stats = self
.tier_stats
.get_mut(&old_tier)
.ok_or("Source tier not found")?;
old_stats.used -= block_size;
old_stats.block_count -= 1;
old_stats.promotions += 1;
let new_stats = self
.tier_stats
.get_mut(&new_tier)
.ok_or("Target tier not found")?;
new_stats.used += block_size;
new_stats.block_count += 1;
block.current_tier = new_tier;
self.total_promotions += 1;
Ok(new_tier)
}
pub fn demote_block(
&mut self,
block_id: u64,
block_size: u64,
) -> Result<CxlTier, &'static str> {
let block = self.blocks.get_mut(&block_id).ok_or("Block not found")?;
let new_tier = match block.current_tier {
CxlTier::LocalDram => CxlTier::CxlNear,
CxlTier::CxlNear => CxlTier::CxlFar,
CxlTier::CxlFar => CxlTier::Storage,
CxlTier::Storage => return Err("Already at lowest tier"),
};
let old_tier = block.current_tier;
let old_stats = self
.tier_stats
.get_mut(&old_tier)
.ok_or("Source tier not found")?;
old_stats.used -= block_size;
old_stats.block_count -= 1;
old_stats.demotions += 1;
let new_stats = self
.tier_stats
.get_mut(&new_tier)
.ok_or("Target tier not found")?;
new_stats.used += block_size;
new_stats.block_count += 1;
block.current_tier = new_tier;
self.total_demotions += 1;
Ok(new_tier)
}
pub fn auto_tier(&mut self, block_size: u64) -> (u64, u64) {
let mut promotions = 0;
let mut demotions = 0;
let block_ids: Vec<u64> = self.blocks.keys().copied().collect();
for block_id in block_ids {
let Some(block) = self.blocks.get(&block_id) else {
continue;
};
let should_promote = block.should_promote();
let should_demote = block.should_demote(self.timestamp);
if should_promote {
if self.promote_block(block_id, block_size).is_ok() {
promotions += 1;
}
} else if should_demote && self.demote_block(block_id, block_size).is_ok() {
demotions += 1;
}
}
(promotions, demotions)
}
pub fn get_tier_stats(&self, tier: CxlTier) -> Option<CxlTierStats> {
self.tier_stats.get(&tier).cloned()
}
pub fn get_global_stats(&self) -> (usize, u64, u64) {
(
self.blocks.len(),
self.total_promotions,
self.total_demotions,
)
}
}
pub struct CxlEngine;
impl CxlEngine {
pub fn init(
local_dram_gb: u64,
cxl_near_gb: u64,
cxl_far_gb: u64,
storage_tb: u64,
) -> Result<(), &'static str> {
let mut mgr = CXL_MANAGER.lock();
mgr.init_tiers(local_dram_gb, cxl_near_gb, cxl_far_gb, storage_tb)
}
pub fn allocate(block_id: u64, size: u64, tier: CxlTier) -> Result<(), &'static str> {
let mut mgr = CXL_MANAGER.lock();
mgr.allocate_block(block_id, size, tier)
}
pub fn access(block_id: u64) {
let mut mgr = CXL_MANAGER.lock();
mgr.access_block(block_id);
}
pub fn promote(block_id: u64, block_size: u64) -> Result<CxlTier, &'static str> {
let mut mgr = CXL_MANAGER.lock();
mgr.promote_block(block_id, block_size)
}
pub fn demote(block_id: u64, block_size: u64) -> Result<CxlTier, &'static str> {
let mut mgr = CXL_MANAGER.lock();
mgr.demote_block(block_id, block_size)
}
pub fn auto_tier(block_size: u64) -> (u64, u64) {
let mut mgr = CXL_MANAGER.lock();
mgr.auto_tier(block_size)
}
pub fn tier_stats(tier: CxlTier) -> Option<CxlTierStats> {
let mgr = CXL_MANAGER.lock();
mgr.get_tier_stats(tier)
}
pub fn global_stats() -> (usize, u64, u64) {
let mgr = CXL_MANAGER.lock();
mgr.get_global_stats()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tier_properties() {
assert_eq!(CxlTier::LocalDram.latency_ns(), 100);
assert_eq!(CxlTier::CxlNear.latency_ns(), 300);
assert!(CxlTier::LocalDram.bandwidth_gbps() > CxlTier::Storage.bandwidth_gbps());
}
#[test]
fn test_block_temperature() {
let temp = BlockTemperature::from_access_pattern(1000, 5);
assert_eq!(temp, BlockTemperature::VeryHot);
let temp = BlockTemperature::from_access_pattern(50, 5);
assert_eq!(temp, BlockTemperature::Hot);
let temp = BlockTemperature::from_access_pattern(5, 5);
assert_eq!(temp, BlockTemperature::Warm);
let temp = BlockTemperature::from_access_pattern(1, 10);
assert_eq!(temp, BlockTemperature::Cold);
}
#[test]
fn test_recommended_tier() {
assert_eq!(
BlockTemperature::VeryHot.recommended_tier(),
CxlTier::LocalDram
);
assert_eq!(BlockTemperature::Hot.recommended_tier(), CxlTier::CxlNear);
assert_eq!(BlockTemperature::Warm.recommended_tier(), CxlTier::CxlFar);
assert_eq!(BlockTemperature::Cold.recommended_tier(), CxlTier::Storage);
}
#[test]
fn test_allocate_block() {
let mut mgr = CxlMemoryManager::new();
mgr.init_tiers(1, 4, 8, 1)
.expect("test: operation should succeed");
assert!(mgr.allocate_block(100, 4096, CxlTier::Storage).is_ok());
assert_eq!(mgr.blocks.len(), 1);
let stats = mgr
.get_tier_stats(CxlTier::Storage)
.expect("test: operation should succeed");
assert_eq!(stats.used, 4096);
assert_eq!(stats.block_count, 1);
}
#[test]
fn test_access_tracking() {
let mut mgr = CxlMemoryManager::new();
mgr.allocate_block(100, 4096, CxlTier::Storage)
.expect("test: operation should succeed");
for _ in 0..150 {
mgr.access_block(100);
}
let block = mgr
.blocks
.get(&100)
.expect("test: operation should succeed");
assert_eq!(block.access_count, 150);
assert!(matches!(block.temperature, BlockTemperature::Warm));
}
#[test]
fn test_promotion() {
let mut mgr = CxlMemoryManager::new();
mgr.init_tiers(1, 4, 8, 1)
.expect("test: operation should succeed");
mgr.allocate_block(100, 4096, CxlTier::Storage)
.expect("test: operation should succeed");
let new_tier = mgr
.promote_block(100, 4096)
.expect("test: operation should succeed");
assert_eq!(new_tier, CxlTier::CxlFar);
let block = mgr
.blocks
.get(&100)
.expect("test: operation should succeed");
assert_eq!(block.current_tier, CxlTier::CxlFar);
}
#[test]
fn test_demotion() {
let mut mgr = CxlMemoryManager::new();
mgr.init_tiers(1, 4, 8, 1)
.expect("test: operation should succeed");
mgr.allocate_block(100, 4096, CxlTier::LocalDram)
.expect("test: operation should succeed");
let new_tier = mgr
.demote_block(100, 4096)
.expect("test: operation should succeed");
assert_eq!(new_tier, CxlTier::CxlNear);
let block = mgr
.blocks
.get(&100)
.expect("test: operation should succeed");
assert_eq!(block.current_tier, CxlTier::CxlNear);
}
#[test]
fn test_auto_tiering() {
let mut mgr = CxlMemoryManager::new();
mgr.init_tiers(1, 4, 8, 1)
.expect("test: operation should succeed");
mgr.allocate_block(100, 4096, CxlTier::LocalDram)
.expect("test: operation should succeed");
mgr.timestamp += 100;
let (promotions, demotions) = mgr.auto_tier(4096);
assert_eq!(promotions, 0);
assert!(demotions > 0);
}
#[test]
fn test_tier_full() {
let mut mgr = CxlMemoryManager::new();
mgr.init_tiers(0, 0, 0, 1)
.expect("test: operation should succeed");
mgr.tier_stats
.get_mut(&CxlTier::LocalDram)
.expect("test: operation should succeed")
.capacity = 4096;
assert!(mgr.allocate_block(100, 4096, CxlTier::LocalDram).is_ok());
assert!(mgr.allocate_block(101, 4096, CxlTier::LocalDram).is_err());
}
}