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