Skip to main content

guest_cli/
mem.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4use crate::platform::PlatformServices;
5use anyhow::{Error, anyhow};
6use fidl_fuchsia_virtualization::{
7    GuestMarker, GuestStatus, MemControllerMarker, MemControllerProxy,
8};
9use guest_cli_args as arguments;
10use std::fmt;
11use zx_status;
12
13#[derive(serde::Serialize, serde::Deserialize)]
14pub struct RequestSizeResult {
15    size: u64,
16}
17
18impl fmt::Display for RequestSizeResult {
19    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20        write!(f, "Resizing dynamically plugged memory to {} bytes!\n", self.size)
21    }
22}
23
24#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq)]
25pub struct VirtioMemStats {
26    block_size: u64,
27    region_size: u64,
28    usable_region_size: u64,
29    plugged_size: u64,
30    requested_size: u64,
31}
32
33impl fmt::Display for VirtioMemStats {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        write!(f, "Dynamically plugged memory stats:\n")?;
36        write!(f, "    block_size:          {}\n", self.block_size)?;
37        write!(f, "    region_size:         {}\n", self.region_size)?;
38        write!(f, "    usable_region_size:  {}\n", self.usable_region_size)?;
39        write!(f, "    plugged_size:        {}\n", self.plugged_size)?;
40        write!(f, "    requested_size:      {}\n", self.requested_size)
41    }
42}
43
44#[derive(serde::Serialize, serde::Deserialize)]
45pub enum GuestMemResult {
46    RequestSize(RequestSizeResult),
47    MemStats(VirtioMemStats),
48}
49
50impl fmt::Display for GuestMemResult {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        match self {
53            GuestMemResult::RequestSize(res) => write!(f, "{}", res),
54            GuestMemResult::MemStats(stats) => write!(f, "{}", stats),
55        }
56    }
57}
58
59async fn connect_to_mem_controller<P: PlatformServices>(
60    services: &P,
61    guest_type: arguments::GuestType,
62) -> Result<MemControllerProxy, Error> {
63    let manager = services.connect_to_manager(guest_type).await?;
64
65    let guest_info = manager.get_info().await?;
66    if guest_info.guest_status.expect("guest status should be set") == GuestStatus::Running {
67        let (guest_endpoint, guest_server_end) = fidl::endpoints::create_proxy::<GuestMarker>();
68        manager
69            .connect(guest_server_end)
70            .await
71            .map_err(|err| anyhow!("failed to get a connect response: {}", err))?
72            .map_err(|err| anyhow!("connect failed with: {:?}", err))?;
73
74        let (controller, server_end) = fidl::endpoints::create_proxy::<MemControllerMarker>();
75
76        guest_endpoint
77            .get_mem_controller(server_end)
78            .await?
79            .map_err(|err| anyhow!("failed to get MemController: {:?}", err))?;
80
81        Ok(controller)
82    } else {
83        Err(anyhow!(zx_status::Status::NOT_CONNECTED))
84    }
85}
86
87pub async fn handle_mem<P: PlatformServices>(
88    services: &P,
89    args: arguments::mem_args::MemArgs,
90) -> Result<GuestMemResult, Error> {
91    Ok(match args.mem_cmd {
92        arguments::mem_args::MemCommands::RequestPluggedMem(args) => {
93            GuestMemResult::RequestSize(do_request_size(
94                connect_to_mem_controller(services, args.guest_type).await?,
95                args.size,
96            )?)
97        }
98        arguments::mem_args::MemCommands::StatsMem(args) => GuestMemResult::MemStats(
99            do_stats(connect_to_mem_controller(services, args.guest_type).await?).await?,
100        ),
101    })
102}
103
104fn do_request_size(controller: MemControllerProxy, size: u64) -> Result<RequestSizeResult, Error> {
105    controller.request_size(size)?;
106    Ok(RequestSizeResult { size })
107}
108
109async fn do_stats(controller: MemControllerProxy) -> Result<VirtioMemStats, Error> {
110    let (block_size, region_size, usable_region_size, plugged_size, requested_size) =
111        controller.get_mem_size().await?;
112    Ok(VirtioMemStats { block_size, region_size, usable_region_size, plugged_size, requested_size })
113}
114
115#[cfg(test)]
116mod test {
117    use super::*;
118    use fidl::endpoints::create_proxy_and_stream;
119    use fuchsia_async as fasync;
120    use futures::StreamExt;
121
122    #[fuchsia::test(allow_stalls = false)]
123    async fn mem_valid_request_plugged_returns_ok() {
124        let (proxy, mut stream) = create_proxy_and_stream::<MemControllerMarker>();
125        let size = 12345;
126        let expected_string = format!("Resizing dynamically plugged memory to {} bytes!\n", size);
127
128        let res = do_request_size(proxy, size).unwrap();
129        let _ = stream
130            .next()
131            .await
132            .expect("Failed to read from stream")
133            .expect("Failed to parse request")
134            .into_request_size()
135            .expect("Unexpected call to Mem Controller");
136
137        assert_eq!(res.size, size);
138        assert_eq!(format!("{}", res), expected_string);
139    }
140
141    #[fuchsia::test(allow_stalls = false)]
142    async fn mem_valid_stats_returns_ok() {
143        let (proxy, mut stream) = create_proxy_and_stream::<MemControllerMarker>();
144        let (block_size, region_size, usable_region_size, plugged_size, requested_size) =
145            (1, 2, 3, 4, 5);
146
147        let _task = fasync::Task::spawn(async move {
148            let get_mem_size_responder = stream
149                .next()
150                .await
151                .expect("Failed to read from stream")
152                .expect("Failed to parse request")
153                .into_get_mem_size()
154                .expect("Unexpected call to Mem Controller");
155            get_mem_size_responder
156                .send(block_size, region_size, usable_region_size, plugged_size, requested_size)
157                .expect("Failed to send request to proxy");
158        });
159        let res = do_stats(proxy).await.unwrap();
160        assert_eq!(
161            res,
162            VirtioMemStats {
163                block_size,
164                region_size,
165                usable_region_size,
166                plugged_size,
167                requested_size
168            }
169        );
170
171        assert_eq!(
172            format!("{}", res),
173            concat!(
174                "Dynamically plugged memory stats:\n",
175                "    block_size:          1\n",
176                "    region_size:         2\n",
177                "    usable_region_size:  3\n",
178                "    plugged_size:        4\n",
179                "    requested_size:      5\n",
180            )
181        );
182    }
183}