@@ -85,13 +85,19 @@ fn default_ack_on_stop() -> bool {
8585}
8686
8787/// Command recieved from the API to start a hinting run
88- #[ derive( Copy , Clone , Debug , Eq , PartialEq , Default , Deserialize ) ]
88+ #[ derive( Copy , Clone , Debug , Eq , PartialEq , Deserialize ) ]
8989pub struct StartHintingCmd {
9090 /// If we should automatically acknowledge end of the run after stop.
9191 #[ serde( default = "default_ack_on_stop" ) ]
9292 pub acknowledge_on_stop : bool ,
9393}
9494
95+ impl Default for StartHintingCmd {
96+ fn default ( ) -> Self {
97+ Self { acknowledge_on_stop : true }
98+ }
99+ }
100+
95101/// Returned to the API for get hinting status
96102#[ derive( Copy , Clone , Debug , Eq , PartialEq , Default , Serialize ) ]
97103pub struct HintingStatus {
@@ -936,18 +942,51 @@ impl VirtioDevice for Balloon {
936942
937943#[ cfg( test) ]
938944pub ( crate ) mod tests {
945+ use itertools:: iproduct;
946+
939947 use super :: super :: BALLOON_CONFIG_SPACE_SIZE ;
940948 use super :: * ;
949+ use crate :: arch:: host_page_size;
941950 use crate :: check_metric_after_block;
942951 use crate :: devices:: virtio:: balloon:: report_balloon_event_fail;
943952 use crate :: devices:: virtio:: balloon:: test_utils:: {
944953 check_request_completion, invoke_handler_for_queue_event, set_request,
945954 } ;
946955 use crate :: devices:: virtio:: queue:: { VIRTQ_DESC_F_NEXT , VIRTQ_DESC_F_WRITE } ;
947- use crate :: devices:: virtio:: test_utils:: { VirtQueue , default_interrupt, default_mem} ;
956+ use crate :: devices:: virtio:: test_utils:: {
957+ VirtQueue , default_interrupt, default_mem
958+ } ;
959+ use crate :: devices:: virtio:: test_utils:: test:: {
960+ VirtioTestDevice , VirtioTestHelper , create_virtio_mem,
961+ } ;
948962 use crate :: test_utils:: single_region_mem;
963+ use crate :: utils:: align_up;
949964 use crate :: vstate:: memory:: GuestAddress ;
950965
966+ impl VirtioTestDevice for Balloon {
967+ fn set_queues ( & mut self , queues : Vec < Queue > ) {
968+ self . queues = queues;
969+ }
970+
971+ fn num_queues ( & self ) -> usize {
972+ let mut idx = STATS_INDEX ;
973+
974+ if self . stats_polling_interval_s > 0 {
975+ idx += 1 ;
976+ }
977+
978+ if self . free_page_hinting ( ) {
979+ idx += 1 ;
980+ }
981+
982+ if self . free_page_reporting ( ) {
983+ idx += 1 ;
984+ }
985+
986+ idx
987+ }
988+ }
989+
951990 impl Balloon {
952991 pub ( crate ) fn set_queue ( & mut self , idx : usize , q : Queue ) {
953992 self . queues [ idx] = q;
@@ -1030,31 +1069,37 @@ pub(crate) mod tests {
10301069 #[ test]
10311070 fn test_virtio_features ( ) {
10321071 // Test all feature combinations.
1033- for deflate_on_oom in [ true , false ] . iter ( ) {
1034- for stats_interval in [ 0 , 1 ] . iter ( ) {
1035- let mut balloon =
1036- Balloon :: new ( 0 , * deflate_on_oom, * stats_interval, false , false ) . unwrap ( ) ;
1037- assert_eq ! ( balloon. device_type( ) , VIRTIO_ID_BALLOON ) ;
1038-
1039- let features: u64 = ( 1u64 << VIRTIO_F_VERSION_1 )
1040- | ( u64:: from ( * deflate_on_oom) << VIRTIO_BALLOON_F_DEFLATE_ON_OOM )
1041- | ( ( u64:: from ( * stats_interval) ) << VIRTIO_BALLOON_F_STATS_VQ ) ;
1042-
1043- assert_eq ! (
1044- balloon. avail_features_by_page( 0 ) ,
1045- ( features & 0xFFFFFFFF ) as u32
1046- ) ;
1047- assert_eq ! ( balloon. avail_features_by_page( 1 ) , ( features >> 32 ) as u32 ) ;
1048- for i in 2 ..10 {
1049- assert_eq ! ( balloon. avail_features_by_page( i) , 0u32 ) ;
1050- }
1072+ let combinations = iproduct ! (
1073+ & [ true , false ] , // Reporitng
1074+ & [ true , false ] , // Hinting
1075+ & [ true , false ] , // Deflate
1076+ & [ 0 , 1 ] // Interval
1077+ ) ;
10511078
1052- for i in 0 ..10 {
1053- balloon. ack_features_by_page ( i, u32:: MAX ) ;
1054- }
1055- // Only present features should be acknowledged.
1056- assert_eq ! ( balloon. acked_features, features) ;
1079+ for ( reporting, hinting, deflate_on_oom, stats_interval) in combinations {
1080+ let mut balloon = Balloon :: new ( 0 , * deflate_on_oom, * stats_interval, * hinting, * reporting) . unwrap ( ) ;
1081+ assert_eq ! ( balloon. device_type( ) , VIRTIO_ID_BALLOON ) ;
1082+
1083+ let features: u64 = ( 1u64 << VIRTIO_F_VERSION_1 )
1084+ | ( u64:: from ( * deflate_on_oom) << VIRTIO_BALLOON_F_DEFLATE_ON_OOM )
1085+ | ( ( u64:: from ( * reporting) ) << VIRTIO_BALLOON_F_FREE_PAGE_REPORTING )
1086+ | ( ( u64:: from ( * hinting) ) << VIRTIO_BALLOON_F_FREE_PAGE_HINTING )
1087+ | ( ( u64:: from ( * stats_interval) ) << VIRTIO_BALLOON_F_STATS_VQ ) ;
1088+
1089+ assert_eq ! (
1090+ balloon. avail_features_by_page( 0 ) ,
1091+ ( features & 0xFFFFFFFF ) as u32
1092+ ) ;
1093+ assert_eq ! ( balloon. avail_features_by_page( 1 ) , ( features >> 32 ) as u32 ) ;
1094+ for i in 2 ..10 {
1095+ assert_eq ! ( balloon. avail_features_by_page( i) , 0u32 ) ;
1096+ }
1097+
1098+ for i in 0 ..10 {
1099+ balloon. ack_features_by_page ( i, u32:: MAX ) ;
10571100 }
1101+ // Only present features should be acknowledged.
1102+ assert_eq ! ( balloon. acked_features, features) ;
10581103 }
10591104 }
10601105
@@ -1125,6 +1170,58 @@ pub(crate) mod tests {
11251170 assert_eq ! ( actual_config_space, expected_config_space) ;
11261171 }
11271172
1173+ #[ test]
1174+ fn test_free_page_hinting_config ( ) {
1175+ let mut balloon = Balloon :: new ( 0 , true , 0 , true , false ) . unwrap ( ) ;
1176+ let mem = default_mem ( ) ;
1177+ let interrupt = default_interrupt ( ) ;
1178+ let infq = VirtQueue :: new ( GuestAddress ( 0 ) , & mem, 16 ) ;
1179+ balloon. set_queue ( INFLATE_INDEX , infq. create_queue ( ) ) ;
1180+ balloon. set_queue ( DEFLATE_INDEX , infq. create_queue ( ) ) ;
1181+ balloon. set_queue ( balloon. free_page_hinting_idx ( ) , infq. create_queue ( ) ) ;
1182+ balloon. activate ( mem. clone ( ) , interrupt) . unwrap ( ) ;
1183+
1184+ let expected_config_space: [ u8 ; BALLOON_CONFIG_SPACE_SIZE ] = [
1185+ 0x00 , 0x50 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 ,
1186+ ] ;
1187+ balloon. write_config ( 0 , & expected_config_space) ;
1188+
1189+ let mut actual_config_space = [ 0u8 ; BALLOON_CONFIG_SPACE_SIZE ] ;
1190+ balloon. read_config ( 0 , & mut actual_config_space) ;
1191+ assert_eq ! ( actual_config_space, expected_config_space) ;
1192+
1193+ // We expect the cmd_id to be set to 2 now
1194+ balloon. start_hinting ( Default :: default ( ) ) . unwrap ( ) ;
1195+
1196+ let expected_config_space: [ u8 ; BALLOON_CONFIG_SPACE_SIZE ] = [
1197+ 0x00 , 0x50 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x02 , 0x00 , 0x00 , 0x00 ,
1198+ ] ;
1199+ let mut actual_config_space = [ 0u8 ; BALLOON_CONFIG_SPACE_SIZE ] ;
1200+ balloon. read_config ( 0 , & mut actual_config_space) ;
1201+ assert_eq ! ( actual_config_space, expected_config_space) ;
1202+
1203+ // We expect the cmd_id to be set to 1
1204+ balloon. stop_hinting ( ) . unwrap ( ) ;
1205+
1206+ let expected_config_space: [ u8 ; BALLOON_CONFIG_SPACE_SIZE ] = [
1207+ 0x00 , 0x50 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x01 , 0x00 , 0x00 , 0x00 ,
1208+ ] ;
1209+ let mut actual_config_space = [ 0u8 ; BALLOON_CONFIG_SPACE_SIZE ] ;
1210+ balloon. read_config ( 0 , & mut actual_config_space) ;
1211+ assert_eq ! ( actual_config_space, expected_config_space) ;
1212+
1213+ // We expect the cmd_id to be bumped up to 3 now
1214+ balloon. start_hinting ( Default :: default ( ) ) . unwrap ( ) ;
1215+
1216+ let expected_config_space: [ u8 ; BALLOON_CONFIG_SPACE_SIZE ] = [
1217+ 0x00 , 0x50 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x03 , 0x00 , 0x00 , 0x00 ,
1218+ ] ;
1219+ let mut actual_config_space = [ 0u8 ; BALLOON_CONFIG_SPACE_SIZE ] ;
1220+ balloon. read_config ( 0 , & mut actual_config_space) ;
1221+ assert_eq ! ( actual_config_space, expected_config_space) ;
1222+
1223+ }
1224+
11281225 #[ test]
11291226 fn test_invalid_request ( ) {
11301227 let mut balloon = Balloon :: new ( 0 , true , 0 , false , false ) . unwrap ( ) ;
@@ -1400,16 +1497,174 @@ pub(crate) mod tests {
14001497 }
14011498 }
14021499
1500+ #[ test]
1501+ fn test_process_reporting ( ) {
1502+ let mem = create_virtio_mem ( ) ;
1503+ let mut th = VirtioTestHelper :: < Balloon > :: new ( & mem,
1504+ Balloon :: new ( 0 , true , 0 , false , true ) . unwrap ( ) ) ;
1505+
1506+ th. activate_device ( & mem) ;
1507+
1508+ let page_size = host_page_size ( ) as u64 ;
1509+
1510+ // This has to be u32 for the scatter gather
1511+ #[ allow( clippy:: cast_possible_truncation) ]
1512+ let page_size_chain = page_size as u32 ;
1513+ let reporting_idx = th. device ( ) . free_page_reporting_idx ( ) ;
1514+
1515+ let safe_addr = align_up ( th. data_address ( ) , page_size) ;
1516+
1517+ th. add_scatter_gather ( reporting_idx, 0 , & [ ( 0 , safe_addr, page_size_chain, 0 ) ] ) ;
1518+ check_metric_after_block ! (
1519+ METRICS . free_page_report_freed,
1520+ page_size,
1521+ th. device( ) . process_free_page_reporting_queue( ) . unwrap( )
1522+ ) ;
1523+
1524+ // Test with multiple items
1525+ th. add_scatter_gather ( reporting_idx, 0 , & [
1526+ ( 0 , safe_addr, page_size_chain, 0 ) ,
1527+ ( 1 , safe_addr + page_size, page_size_chain, 0 ) ,
1528+ ( 2 , safe_addr + ( page_size * 2 ) , page_size_chain, 0 ) ,
1529+ ] ) ;
1530+
1531+ check_metric_after_block ! (
1532+ METRICS . free_page_report_freed,
1533+ page_size * 3 ,
1534+ th. device( ) . process_free_page_reporting_queue( ) . unwrap( )
1535+ ) ;
1536+
1537+ // Test with unaligned length
1538+ th. add_scatter_gather ( reporting_idx, 0 , & [
1539+ ( 1 , safe_addr + 1 , page_size_chain, 0 ) ,
1540+ ] ) ;
1541+
1542+ check_metric_after_block ! (
1543+ METRICS . free_page_report_fails,
1544+ 1 ,
1545+ th. device( ) . process_free_page_reporting_queue( ) . unwrap( )
1546+ ) ;
1547+ }
1548+
1549+ #[ test]
1550+ fn test_process_hinting ( ) {
1551+ let mem = create_virtio_mem ( ) ;
1552+ let mut th = VirtioTestHelper :: < Balloon > :: new ( & mem,
1553+ Balloon :: new ( 0 , true , 0 , true , false ) . unwrap ( ) ) ;
1554+
1555+ th. activate_device ( & mem) ;
1556+
1557+ let page_size = host_page_size ( ) as u64 ;
1558+ let hinting_idx = th. device ( ) . free_page_hinting_idx ( ) ;
1559+ // This has to be u32 for the scatter gather
1560+ #[ allow( clippy:: cast_possible_truncation) ]
1561+ let page_size_chain = page_size as u32 ;
1562+ let safe_addr = align_up ( th. data_address ( ) , page_size) ;
1563+
1564+ // Test the good case
1565+ th. device ( ) . start_hinting ( Default :: default ( ) ) . unwrap ( ) ;
1566+
1567+ let mut host_cmd = th. device ( ) . get_hinting_status ( ) . unwrap ( ) . host_cmd ;
1568+ println ! ( "We got host cmd {host_cmd}" ) ;
1569+
1570+ macro_rules! test_hinting {
1571+ ( $cmd: expr, $expected: expr) => {
1572+ mem. write_obj( $cmd as u32 , GuestAddress :: new( safe_addr) ) . unwrap( ) ;
1573+ th. add_scatter_gather( hinting_idx, 0 , & [
1574+ ( 0 , safe_addr, 4 , 0 ) ,
1575+ ( 1 , safe_addr + page_size, page_size_chain, 0 ) ,
1576+ ] ) ;
1577+
1578+ check_metric_after_block!(
1579+ METRICS . free_page_hint_freed,
1580+ $expected,
1581+ th. device( ) . process_free_page_hinting_queue( ) . unwrap( )
1582+ ) ;
1583+ } ;
1584+
1585+ ( $expected: expr) => {
1586+ th. add_scatter_gather( hinting_idx, 0 , & [
1587+ ( 0 , safe_addr + page_size, page_size_chain, 0 ) ,
1588+ ] ) ;
1589+
1590+ check_metric_after_block!(
1591+ METRICS . free_page_hint_freed,
1592+ $expected,
1593+ th. device( ) . process_free_page_hinting_queue( ) . unwrap( )
1594+ ) ;
1595+ } ;
1596+ }
1597+
1598+ // Ack the start of the hinting run and send a single page
1599+ test_hinting ! ( host_cmd, page_size) ;
1600+
1601+ // Report pages for an invalid cmd
1602+ test_hinting ! ( host_cmd + 1 , 0 ) ;
1603+
1604+ // If correct cmd is again used continue again
1605+ test_hinting ! ( host_cmd, page_size) ;
1606+
1607+ // Trigger another hinting run this will bump the cmd id
1608+ // so we should ignore any inflight requests
1609+ th. device ( ) . start_hinting ( Default :: default ( ) ) . unwrap ( ) ;
1610+ test_hinting ! ( 0 ) ;
1611+
1612+ // Update to our new host cmd and check this now works
1613+ host_cmd = th. device ( ) . get_hinting_status ( ) . unwrap ( ) . host_cmd ;
1614+ test_hinting ! ( host_cmd, page_size) ;
1615+ test_hinting ! ( page_size) ;
1616+
1617+ // Simulate the driver finishing a run. Any reported values after
1618+ // should be ignored
1619+ test_hinting ! ( FREE_PAGE_HINT_STOP , 0 ) ;
1620+ test_hinting ! ( 0 ) ;
1621+
1622+ // As we had auto ack on finish the host cmd should be set to done
1623+ host_cmd = th. device ( ) . get_hinting_status ( ) . unwrap ( ) . host_cmd ;
1624+ assert_eq ! ( host_cmd, FREE_PAGE_HINT_DONE ) ;
1625+
1626+ // Test no ack on stop behaviour
1627+ th. device ( ) . start_hinting ( StartHintingCmd { acknowledge_on_stop : false } ) . unwrap ( ) ;
1628+ host_cmd = th. device ( ) . get_hinting_status ( ) . unwrap ( ) . host_cmd ;
1629+ test_hinting ! ( host_cmd, page_size) ;
1630+ test_hinting ! ( page_size) ;
1631+ test_hinting ! ( FREE_PAGE_HINT_STOP , 0 ) ;
1632+ let new_host_cmd = th. device ( ) . get_hinting_status ( ) . unwrap ( ) . host_cmd ;
1633+ assert_eq ! ( host_cmd, new_host_cmd) ;
1634+
1635+ // Test misaligned writes report as an error
1636+ th. device ( ) . start_hinting ( Default :: default ( ) ) . unwrap ( ) ;
1637+ host_cmd = th. device ( ) . get_hinting_status ( ) . unwrap ( ) . host_cmd ;
1638+ test_hinting ! ( host_cmd, page_size) ;
1639+ test_hinting ! ( page_size) ;
1640+
1641+ th. add_scatter_gather ( hinting_idx, 0 , & [
1642+ ( 0 , safe_addr + page_size + 1 , page_size_chain, 0 ) ,
1643+ ] ) ;
1644+
1645+ check_metric_after_block ! (
1646+ METRICS . free_page_hint_fails,
1647+ 1 ,
1648+ th. device( ) . process_free_page_hinting_queue( ) . unwrap( )
1649+ ) ;
1650+ }
1651+
14031652 #[ test]
14041653 fn test_process_balloon_queues ( ) {
1405- let mut balloon = Balloon :: new ( 0x10 , true , 0 , false , false ) . unwrap ( ) ;
1654+ let mut balloon = Balloon :: new ( 0x10 , true , 0 , true , true ) . unwrap ( ) ;
14061655 let mem = default_mem ( ) ;
14071656 let interrupt = default_interrupt ( ) ;
14081657 let infq = VirtQueue :: new ( GuestAddress ( 0 ) , & mem, 16 ) ;
14091658 let defq = VirtQueue :: new ( GuestAddress ( 0 ) , & mem, 16 ) ;
1659+ let hintq = VirtQueue :: new ( GuestAddress ( 0 ) , & mem, 16 ) ;
1660+ let reportq = VirtQueue :: new ( GuestAddress ( 0 ) , & mem, 16 ) ;
14101661
14111662 balloon. set_queue ( INFLATE_INDEX , infq. create_queue ( ) ) ;
14121663 balloon. set_queue ( DEFLATE_INDEX , defq. create_queue ( ) ) ;
1664+ balloon. set_queue ( balloon. free_page_hinting_idx ( ) ,
1665+ hintq. create_queue ( ) ) ;
1666+ balloon. set_queue ( balloon. free_page_reporting_idx ( ) ,
1667+ reportq. create_queue ( ) ) ;
14131668
14141669 balloon. activate ( mem, interrupt) . unwrap ( ) ;
14151670 balloon. process_virtio_queues ( ) . unwrap ( ) ;
@@ -1451,6 +1706,9 @@ pub(crate) mod tests {
14511706 let mut balloon = Balloon :: new ( 0 , true , 0 , false , false ) . unwrap ( ) ;
14521707 // Assert that we can't update an inactive device.
14531708 balloon. update_size ( 1 ) . unwrap_err ( ) ;
1709+ balloon. start_hinting ( Default :: default ( ) ) . unwrap_err ( ) ;
1710+ balloon. get_hinting_status ( ) . unwrap_err ( ) ;
1711+ balloon. stop_hinting ( ) . unwrap_err ( ) ;
14541712 }
14551713
14561714 #[ test]
0 commit comments