[guest] Allow more flexible memory configuration
We can now map memory at different addresses, and with different cache
policies, all from the command line. This allows us to more easily
configure a guest for device pass-through.
MAC-180
Test: Ran vmm_unittests.
Change-Id: I52ca3ebab9ceb72a67a23135a1c7cc50ebf19e75
diff --git a/bin/guest/vmm/guest_config.cc b/bin/guest/vmm/guest_config.cc
index c1dded1..c2664d7 100644
--- a/bin/guest/vmm/guest_config.cc
+++ b/bin/guest/vmm/guest_config.cc
@@ -119,53 +119,10 @@
};
}
-constexpr size_t kMinMemorySize = 1 << 20;
-
-static GuestConfigParser::OptionHandler parse_mem_size(size_t* out) {
- return [out](const std::string& key, const std::string& value) {
- if (value.empty()) {
- FXL_LOG(ERROR) << "Option: '" << key << "' expects a value (--" << key
- << "=<value>)";
- return ZX_ERR_INVALID_ARGS;
- }
- char modifier = 'b';
- size_t size;
- int ret = sscanf(value.c_str(), "%zd%c", &size, &modifier);
- if (ret < 1) {
- FXL_LOG(ERROR) << "Value is not a size string: " << value;
- return ZX_ERR_INVALID_ARGS;
- }
- switch (modifier) {
- case 'b':
- break;
- case 'k':
- size *= (1 << 10);
- break;
- case 'M':
- size *= (1 << 20);
- break;
- case 'G':
- size *= (1 << 30);
- break;
- default:
- FXL_LOG(ERROR) << "Invalid size modifier " << modifier;
- return ZX_ERR_INVALID_ARGS;
- }
-
- if (size < kMinMemorySize) {
- FXL_LOG(ERROR) << "Requested memory " << size
- << " is less than the minimum supported size "
- << kMinMemorySize;
- return ZX_ERR_INVALID_ARGS;
- }
- *out = size;
- return ZX_OK;
- };
-}
-
template <typename NumberType>
-static zx_status_t transform_number(const std::string& value, NumberType* out) {
- if (!fxl::StringToNumberWithError(value, out)) {
+static zx_status_t parse_number(const std::string& value, NumberType* out,
+ fxl::Base base) {
+ if (!fxl::StringToNumberWithError(value, out, base)) {
FXL_LOG(ERROR) << "Unable to convert '" << value << "' into a number";
return ZX_ERR_INVALID_ARGS;
}
@@ -173,14 +130,14 @@
}
template <typename NumberType>
-static GuestConfigParser::OptionHandler parse_number(NumberType* out) {
+static GuestConfigParser::OptionHandler set_number(NumberType* out) {
return [out](const std::string& key, const std::string& value) {
if (value.empty()) {
FXL_LOG(ERROR) << "Option: '" << key << "' expects a value (--" << key
<< "=<value>)";
return ZX_ERR_INVALID_ARGS;
}
- return transform_number(value, out);
+ return parse_number(value, out, fxl::Base::k10);
};
}
@@ -210,10 +167,9 @@
};
}
-static zx_status_t transform_block_spec(const std::string& spec,
- BlockSpec* out) {
- std::string token;
+static zx_status_t parse_block_spec(const std::string& spec, BlockSpec* out) {
std::istringstream token_stream(spec);
+ std::string token;
while (std::getline(token_stream, token, ',')) {
if (token == "fdio") {
out->format = fuchsia::guest::BlockFormat::RAW;
@@ -235,17 +191,94 @@
return ZX_OK;
}
-static zx_status_t transform_interrupt_spec(const std::string& spec,
- InterruptSpec* out) {
- auto pos = spec.find(',');
- if (pos == std::string::npos) {
+static std::vector<std::string> split(const std::string& spec, char delim) {
+ std::istringstream token_stream(spec);
+ std::string token;
+ std::vector<std::string> tokens;
+ while (std::getline(token_stream, token, delim)) {
+ tokens.push_back(token);
+ }
+ return tokens;
+}
+
+static zx_status_t parse_interrupt_spec(const std::string& spec,
+ InterruptSpec* out) {
+ std::vector<std::string> tokens = split(spec, ',');
+ if (tokens.size() != 2) {
return ZX_ERR_INVALID_ARGS;
}
- zx_status_t status = transform_number(spec.substr(0, pos), &out->vector);
+ zx_status_t status = parse_number(tokens[0], &out->vector, fxl::Base::k10);
if (status != ZX_OK) {
return status;
}
- return transform_number(spec.substr(pos + 1), &out->options);
+ return parse_number(tokens[1], &out->options, fxl::Base::k10);
+}
+
+constexpr size_t kMinMemorySize = 1 << 20;
+
+static zx_status_t parse_memory(const std::string& value, size_t* out) {
+ char modifier = 'b';
+ size_t size;
+ int ret = sscanf(value.c_str(), "%zd%c", &size, &modifier);
+ if (ret < 1) {
+ FXL_LOG(ERROR) << "Value is not a size string: " << value;
+ return ZX_ERR_INVALID_ARGS;
+ }
+ switch (modifier) {
+ case 'b':
+ break;
+ case 'k':
+ size *= (1 << 10);
+ break;
+ case 'M':
+ size *= (1 << 20);
+ break;
+ case 'G':
+ size *= (1 << 30);
+ break;
+ default:
+ FXL_LOG(ERROR) << "Invalid size modifier " << modifier;
+ return ZX_ERR_INVALID_ARGS;
+ }
+
+ if (size < kMinMemorySize) {
+ FXL_LOG(ERROR) << "Requested memory " << size
+ << " is less than the minimum supported size "
+ << kMinMemorySize;
+ return ZX_ERR_INVALID_ARGS;
+ }
+ *out = size;
+ return ZX_OK;
+}
+
+static zx_status_t parse_memory_spec(const std::string& spec, MemorySpec* out) {
+ out->addr = 0;
+ out->policy = MemoryPolicy::GUEST_CACHED;
+ std::vector<std::string> tokens = split(spec, ',');
+ if (tokens.size() == 1) {
+ return parse_memory(tokens[0], &out->len);
+ }
+ if (tokens.size() > 3) {
+ return ZX_ERR_INVALID_ARGS;
+ }
+ zx_status_t status = parse_number(tokens[0], &out->addr, fxl::Base::k16);
+ if (status != ZX_OK) {
+ return status;
+ }
+ status = parse_memory(tokens[1], &out->len);
+ if (status != ZX_OK) {
+ return status;
+ }
+ if (tokens.size() != 3) {
+ return ZX_OK;
+ } else if (tokens[2] == "cached") {
+ out->policy = MemoryPolicy::HOST_CACHED;
+ } else if (tokens[2] == "device") {
+ out->policy = MemoryPolicy::HOST_DEVICE;
+ } else {
+ return ZX_ERR_INVALID_ARGS;
+ }
+ return ZX_OK;
}
static GuestConfigParser::OptionHandler save_kernel(std::string* out,
@@ -265,18 +298,17 @@
: cfg_(cfg),
opts_{
{"block",
- add_option<BlockSpec>(&cfg_->block_specs_, transform_block_spec)},
+ add_option<BlockSpec>(&cfg_->block_devices_, parse_block_spec)},
{"cmdline-add", append_string(&cfg_->cmdline_, " ")},
{"cmdline", save_option(&cfg_->cmdline_)},
- {"cpus", parse_number(&cfg_->cpus_)},
+ {"cpus", set_number(&cfg_->cpus_)},
{"dtb-overlay", save_option(&cfg_->dtb_overlay_path_)},
- {"host-memory", set_flag(&cfg_->host_memory_, true)},
- {"interrupt", add_option<InterruptSpec>(&cfg_->interrupts_,
- transform_interrupt_spec)},
+ {"interrupt",
+ add_option<InterruptSpec>(&cfg_->interrupts_, parse_interrupt_spec)},
{"legacy-net", set_flag(&cfg_->legacy_net_, true)},
{"linux",
save_kernel(&cfg_->kernel_path_, &cfg_->kernel_, Kernel::LINUX)},
- {"memory", parse_mem_size(&cfg_->memory_)},
+ {"memory", add_option<MemorySpec>(&cfg_->memory_, parse_memory_spec)},
{"ramdisk", save_option(&cfg_->ramdisk_path_)},
{"virtio-balloon", set_flag(&cfg_->virtio_balloon_, true)},
{"virtio-console", set_flag(&cfg_->virtio_console_, true)},
@@ -288,7 +320,11 @@
save_kernel(&cfg_->kernel_path_, &cfg_->kernel_, Kernel::ZIRCON)},
} {}
-GuestConfigParser::~GuestConfigParser() = default;
+void GuestConfigParser::SetDefaults() {
+ if (cfg_->memory_.empty()) {
+ cfg_->memory_.push_back({.len = 1ul << 30});
+ }
+}
zx_status_t GuestConfigParser::ParseArgcArgv(int argc, char** argv) {
fxl::CommandLine cl = fxl::CommandLineFromArgcArgv(argc, argv);
@@ -313,6 +349,7 @@
}
}
+ SetDefaults();
return ZX_OK;
}
@@ -362,5 +399,6 @@
return ZX_ERR_INVALID_ARGS;
}
+ SetDefaults();
return ZX_OK;
}
diff --git a/bin/guest/vmm/guest_config.h b/bin/guest/vmm/guest_config.h
index f020a58..b5ee709 100644
--- a/bin/guest/vmm/guest_config.h
+++ b/bin/guest/vmm/guest_config.h
@@ -14,6 +14,7 @@
#include <zircon/syscalls.h>
#include <zircon/types.h>
+#include "garnet/lib/machina/guest.h"
#include "garnet/lib/machina/interrupt_controller.h"
struct BlockSpec {
@@ -32,13 +33,12 @@
Kernel kernel() const { return kernel_; }
const std::string& kernel_path() const { return kernel_path_; }
const std::string& ramdisk_path() const { return ramdisk_path_; }
- const std::string& cmdline() const { return cmdline_; }
const std::string& dtb_overlay_path() const { return dtb_overlay_path_; }
- const std::vector<BlockSpec>& block_devices() const { return block_specs_; }
+ const std::string& cmdline() const { return cmdline_; }
+ const std::vector<BlockSpec>& block_devices() const { return block_devices_; }
+ const std::vector<MemorySpec>& memory() const { return memory_; }
const std::vector<InterruptSpec>& interrupts() const { return interrupts_; }
uint8_t cpus() const { return cpus_; }
- size_t memory() const { return memory_; }
- bool host_memory() const { return host_memory_; }
bool legacy_net() const { return legacy_net_; }
bool virtio_balloon() const { return virtio_balloon_; }
bool virtio_console() const { return virtio_console_; }
@@ -52,13 +52,12 @@
Kernel kernel_ = Kernel::ZIRCON;
std::string kernel_path_;
std::string ramdisk_path_;
- std::string cmdline_;
std::string dtb_overlay_path_;
- std::vector<BlockSpec> block_specs_;
+ std::string cmdline_;
+ std::vector<BlockSpec> block_devices_;
+ std::vector<MemorySpec> memory_;
std::vector<InterruptSpec> interrupts_;
uint8_t cpus_ = zx_system_get_num_cpus();
- size_t memory_ = 1 << 30;
- bool host_memory_ = false;
bool legacy_net_ = true;
bool virtio_balloon_ = true;
bool virtio_console_ = true;
@@ -73,16 +72,15 @@
using OptionHandler = std::function<zx_status_t(const std::string& name,
const std::string& value)>;
GuestConfigParser(GuestConfig* config);
- ~GuestConfigParser();
zx_status_t ParseArgcArgv(int argc, char** argv);
zx_status_t ParseConfig(const std::string& data);
private:
GuestConfig* cfg_;
+ std::unordered_map<std::string, OptionHandler> opts_;
- using OptionMap = std::unordered_map<std::string, OptionHandler>;
- OptionMap opts_;
+ void SetDefaults();
};
#endif // GARNET_BIN_GUEST_VMM_GUEST_CONFIG_H_
diff --git a/bin/guest/vmm/guest_config_unittest.cc b/bin/guest/vmm/guest_config_unittest.cc
index 4d9606b..1336810 100644
--- a/bin/guest/vmm/guest_config_unittest.cc
+++ b/bin/guest/vmm/guest_config_unittest.cc
@@ -10,27 +10,29 @@
#include "gtest/gtest.h"
#include "lib/fxl/arraysize.h"
-namespace guest {
-namespace {
+class GuestConfigParserTest : public ::testing::Test {
+ protected:
+ GuestConfig config_;
+ GuestConfigParser parser_{&config_};
-TEST(GuestConfigParserTest, DefaultValues) {
- GuestConfig config;
- GuestConfigParser parser(&config);
- parser.ParseConfig("{}");
+ zx_status_t ParseArg(const char* arg) {
+ const char* argv[] = {"exe_name", arg};
+ return parser_.ParseArgcArgv(arraysize(argv), const_cast<char**>(argv));
+ }
+};
- ASSERT_EQ(Kernel::ZIRCON, config.kernel());
- ASSERT_TRUE(config.kernel_path().empty());
- ASSERT_TRUE(config.ramdisk_path().empty());
- ASSERT_EQ(zx_system_get_num_cpus(), config.cpus());
- ASSERT_TRUE(config.block_devices().empty());
- ASSERT_TRUE(config.cmdline().empty());
+TEST_F(GuestConfigParserTest, DefaultValues) {
+ ASSERT_EQ(ZX_OK, parser_.ParseConfig("{}"));
+ ASSERT_EQ(Kernel::ZIRCON, config_.kernel());
+ ASSERT_TRUE(config_.kernel_path().empty());
+ ASSERT_TRUE(config_.ramdisk_path().empty());
+ ASSERT_EQ(zx_system_get_num_cpus(), config_.cpus());
+ ASSERT_TRUE(config_.block_devices().empty());
+ ASSERT_TRUE(config_.cmdline().empty());
}
-TEST(GuestConfigParserTest, ParseConfig) {
- GuestConfig config;
- GuestConfigParser parser(&config);
-
- ASSERT_EQ(ZX_OK, parser.ParseConfig(
+TEST_F(GuestConfigParserTest, ParseConfig) {
+ ASSERT_EQ(ZX_OK, parser_.ParseConfig(
R"JSON({
"zircon": "zircon_path",
"ramdisk": "ramdisk_path",
@@ -38,198 +40,222 @@
"block": "/pkg/data/block_path",
"cmdline": "kernel cmdline"
})JSON"));
- ASSERT_EQ(Kernel::ZIRCON, config.kernel());
- ASSERT_EQ("zircon_path", config.kernel_path());
- ASSERT_EQ("ramdisk_path", config.ramdisk_path());
- ASSERT_EQ(4, config.cpus());
- ASSERT_EQ(1, config.block_devices().size());
- ASSERT_EQ("/pkg/data/block_path", config.block_devices()[0].path);
- ASSERT_EQ("kernel cmdline", config.cmdline());
+ ASSERT_EQ(Kernel::ZIRCON, config_.kernel());
+ ASSERT_EQ("zircon_path", config_.kernel_path());
+ ASSERT_EQ("ramdisk_path", config_.ramdisk_path());
+ ASSERT_EQ(4, config_.cpus());
+ ASSERT_EQ(1, config_.block_devices().size());
+ ASSERT_EQ("/pkg/data/block_path", config_.block_devices()[0].path);
+ ASSERT_EQ("kernel cmdline", config_.cmdline());
}
-TEST(GuestConfigParserTest, ParseArgs) {
- GuestConfig config;
- GuestConfigParser parser(&config);
-
+TEST_F(GuestConfigParserTest, ParseArgs) {
const char* argv[] = {
"exe_name", "--linux=linux_path", "--ramdisk=ramdisk_path",
"--cpus=4", "--block=/pkg/data/block_path", "--cmdline=kernel_cmdline"};
ASSERT_EQ(ZX_OK,
- parser.ParseArgcArgv(arraysize(argv), const_cast<char**>(argv)));
- ASSERT_EQ(Kernel::LINUX, config.kernel());
- ASSERT_EQ("linux_path", config.kernel_path());
- ASSERT_EQ("ramdisk_path", config.ramdisk_path());
- ASSERT_EQ(4, config.cpus());
- ASSERT_EQ(1, config.block_devices().size());
- ASSERT_EQ("/pkg/data/block_path", config.block_devices()[0].path);
- ASSERT_EQ("kernel_cmdline", config.cmdline());
+ parser_.ParseArgcArgv(arraysize(argv), const_cast<char**>(argv)));
+ ASSERT_EQ(Kernel::LINUX, config_.kernel());
+ ASSERT_EQ("linux_path", config_.kernel_path());
+ ASSERT_EQ("ramdisk_path", config_.ramdisk_path());
+ ASSERT_EQ(4, config_.cpus());
+ ASSERT_EQ(1, config_.block_devices().size());
+ ASSERT_EQ("/pkg/data/block_path", config_.block_devices()[0].path);
+ ASSERT_EQ("kernel_cmdline", config_.cmdline());
}
-TEST(GuestConfigParserTest, UnknownArgument) {
- GuestConfig config;
- GuestConfigParser parser(&config);
-
+TEST_F(GuestConfigParserTest, UnknownArgument) {
const char* argv[] = {"exe_name", "--invalid-arg"};
ASSERT_EQ(ZX_ERR_INVALID_ARGS,
- parser.ParseArgcArgv(arraysize(argv), const_cast<char**>(argv)));
+ parser_.ParseArgcArgv(arraysize(argv), const_cast<char**>(argv)));
}
-TEST(GuestConfigParserTest, BooleanFlag) {
- GuestConfig config;
- GuestConfigParser parser(&config);
-
+TEST_F(GuestConfigParserTest, BooleanFlag) {
const char* argv_false[] = {"exe_name", "--virtio-net=false"};
- ASSERT_EQ(ZX_OK, parser.ParseArgcArgv(arraysize(argv_false),
- const_cast<char**>(argv_false)));
- ASSERT_FALSE(config.virtio_net());
+ ASSERT_EQ(ZX_OK, parser_.ParseArgcArgv(arraysize(argv_false),
+ const_cast<char**>(argv_false)));
+ ASSERT_FALSE(config_.virtio_net());
const char* argv_true[] = {"exe_name", "--virtio-net=true"};
- ASSERT_EQ(ZX_OK, parser.ParseArgcArgv(arraysize(argv_true),
- const_cast<char**>(argv_true)));
- ASSERT_TRUE(config.virtio_net());
+ ASSERT_EQ(ZX_OK, parser_.ParseArgcArgv(arraysize(argv_true),
+ const_cast<char**>(argv_true)));
+ ASSERT_TRUE(config_.virtio_net());
}
-TEST(GuestConfigParserTest, CommandLineAppend) {
- GuestConfig config;
- GuestConfigParser parser(&config);
-
+TEST_F(GuestConfigParserTest, CommandLineAppend) {
const char* argv[] = {"exe_name", "--cmdline=foo bar", "--cmdline-add=baz"};
ASSERT_EQ(ZX_OK,
- parser.ParseArgcArgv(arraysize(argv), const_cast<char**>(argv)));
- ASSERT_EQ("foo bar baz", config.cmdline());
+ parser_.ParseArgcArgv(arraysize(argv), const_cast<char**>(argv)));
+ ASSERT_EQ("foo bar baz", config_.cmdline());
}
-TEST(GuestConfigParserTest, BlockSpecArg) {
- GuestConfig config;
- GuestConfigParser parser(&config);
-
+TEST_F(GuestConfigParserTest, BlockSpecArg) {
const char* argv[] = {"exe_name", "--block=/pkg/data/foo,ro,fdio",
"--block=/dev/class/block/001,rw,fdio"};
ASSERT_EQ(ZX_OK,
- parser.ParseArgcArgv(arraysize(argv), const_cast<char**>(argv)));
- ASSERT_EQ(2, config.block_devices().size());
+ parser_.ParseArgcArgv(arraysize(argv), const_cast<char**>(argv)));
+ ASSERT_EQ(2, config_.block_devices().size());
- const BlockSpec& spec0 = config.block_devices()[0];
+ const BlockSpec& spec0 = config_.block_devices()[0];
ASSERT_EQ(fuchsia::guest::BlockMode::READ_ONLY, spec0.mode);
ASSERT_EQ(fuchsia::guest::BlockFormat::RAW, spec0.format);
ASSERT_EQ("/pkg/data/foo", spec0.path);
- const BlockSpec& spec1 = config.block_devices()[1];
+ const BlockSpec& spec1 = config_.block_devices()[1];
ASSERT_EQ(fuchsia::guest::BlockMode::READ_WRITE, spec1.mode);
ASSERT_EQ(fuchsia::guest::BlockFormat::RAW, spec1.format);
ASSERT_EQ("/dev/class/block/001", spec1.path);
}
-TEST(GuestConfigParserTest, BlockSpecJson) {
- GuestConfig config;
- GuestConfigParser parser(&config);
-
- ASSERT_EQ(ZX_OK, parser.ParseConfig(
+TEST_F(GuestConfigParserTest, BlockSpecJson) {
+ ASSERT_EQ(ZX_OK, parser_.ParseConfig(
R"JSON({
"block": [
"/pkg/data/foo,ro,fdio",
"/dev/class/block/001,rw,fdio"
]
})JSON"));
- ASSERT_EQ(2, config.block_devices().size());
+ ASSERT_EQ(2, config_.block_devices().size());
- const BlockSpec& spec0 = config.block_devices()[0];
+ const BlockSpec& spec0 = config_.block_devices()[0];
ASSERT_EQ(fuchsia::guest::BlockMode::READ_ONLY, spec0.mode);
ASSERT_EQ(fuchsia::guest::BlockFormat::RAW, spec0.format);
ASSERT_EQ("/pkg/data/foo", spec0.path);
- const BlockSpec& spec1 = config.block_devices()[1];
+ const BlockSpec& spec1 = config_.block_devices()[1];
ASSERT_EQ(fuchsia::guest::BlockMode::READ_WRITE, spec1.mode);
ASSERT_EQ(fuchsia::guest::BlockFormat::RAW, spec1.format);
ASSERT_EQ("/dev/class/block/001", spec1.path);
}
-TEST(GuestConfigParserTest, InterruptSpecArg) {
- GuestConfig config;
- GuestConfigParser parser(&config);
-
+TEST_F(GuestConfigParserTest, InterruptSpecArg) {
const char* argv[] = {"exe_name", "--interrupt=32,2", "--interrupt=33,4"};
ASSERT_EQ(ZX_OK,
- parser.ParseArgcArgv(arraysize(argv), const_cast<char**>(argv)));
- ASSERT_EQ(2, config.interrupts().size());
+ parser_.ParseArgcArgv(arraysize(argv), const_cast<char**>(argv)));
+ ASSERT_EQ(2, config_.interrupts().size());
- const InterruptSpec& spec0 = config.interrupts()[0];
+ const InterruptSpec& spec0 = config_.interrupts()[0];
ASSERT_EQ(32, spec0.vector);
ASSERT_EQ(ZX_INTERRUPT_MODE_EDGE_LOW, spec0.options);
- const InterruptSpec& spec1 = config.interrupts()[1];
+ const InterruptSpec& spec1 = config_.interrupts()[1];
ASSERT_EQ(33, spec1.vector);
ASSERT_EQ(ZX_INTERRUPT_MODE_EDGE_HIGH, spec1.options);
}
-TEST(GuestConfigParserTest, InterruptSpecJson) {
- GuestConfig config;
- GuestConfigParser parser(&config);
-
- ASSERT_EQ(ZX_OK, parser.ParseConfig(
+TEST_F(GuestConfigParserTest, InterruptSpecJson) {
+ ASSERT_EQ(ZX_OK, parser_.ParseConfig(
R"JSON({
"interrupt": [
"32,2",
"33,4"
]
})JSON"));
- ASSERT_EQ(2, config.interrupts().size());
+ ASSERT_EQ(2, config_.interrupts().size());
- const InterruptSpec& spec0 = config.interrupts()[0];
+ const InterruptSpec& spec0 = config_.interrupts()[0];
ASSERT_EQ(32, spec0.vector);
ASSERT_EQ(ZX_INTERRUPT_MODE_EDGE_LOW, spec0.options);
- const InterruptSpec& spec1 = config.interrupts()[1];
+ const InterruptSpec& spec1 = config_.interrupts()[1];
ASSERT_EQ(33, spec1.vector);
ASSERT_EQ(ZX_INTERRUPT_MODE_EDGE_HIGH, spec1.options);
}
-#define TEST_PARSE_MEM_SIZE(string, result) \
- TEST(GuestConfigParserTest, MemSizeTest_##string) { \
- GuestConfig config; \
- GuestConfigParser parser(&config); \
- \
- const char* argv[] = {"exe_name", "--memory=" #string}; \
- ASSERT_EQ(ZX_OK, parser.ParseArgcArgv(arraysize(argv), \
- const_cast<char**>(argv))); \
- ASSERT_EQ((result), config.memory()); \
- }
+TEST_F(GuestConfigParserTest, Memory_1024k) {
+ ASSERT_EQ(ZX_OK, ParseArg("--memory=1024k"));
+ const auto& memory = config_.memory();
+ EXPECT_EQ(1, memory.size());
+ EXPECT_EQ(0, memory[0].addr);
+ EXPECT_EQ(1ul << 20, memory[0].len);
+ EXPECT_EQ(MemoryPolicy::GUEST_CACHED, memory[0].policy);
+}
-TEST_PARSE_MEM_SIZE(1024k, 1u << 20);
-TEST_PARSE_MEM_SIZE(2M, 2ul << 20);
-TEST_PARSE_MEM_SIZE(4G, 4ul << 30);
+TEST_F(GuestConfigParserTest, Memory_2M) {
+ ASSERT_EQ(ZX_OK, ParseArg("--memory=2M"));
+ const auto& memory = config_.memory();
+ EXPECT_EQ(1, memory.size());
+ EXPECT_EQ(0, memory[0].addr);
+ EXPECT_EQ(2ul << 20, memory[0].len);
+ EXPECT_EQ(MemoryPolicy::GUEST_CACHED, memory[0].policy);
+}
-#define TEST_PARSE_MEM_SIZE_ERROR(name, string) \
- TEST(GuestConfigParserTest, MemSizeTest_##name) { \
- GuestConfig config; \
- GuestConfigParser parser(&config); \
- \
- const char* argv[] = {"exe_name", "--memory=" #string}; \
- ASSERT_EQ( \
- ZX_ERR_INVALID_ARGS, \
- parser.ParseArgcArgv(arraysize(argv), const_cast<char**>(argv))); \
- }
+TEST_F(GuestConfigParserTest, Memory_4G) {
+ ASSERT_EQ(ZX_OK, ParseArg("--memory=4G"));
+ const auto& memory = config_.memory();
+ EXPECT_EQ(1, memory.size());
+ EXPECT_EQ(0, memory[0].addr);
+ EXPECT_EQ(4ul << 30, memory[0].len);
+ EXPECT_EQ(MemoryPolicy::GUEST_CACHED, memory[0].policy);
+}
-TEST_PARSE_MEM_SIZE_ERROR(TooSmall, 1024);
-TEST_PARSE_MEM_SIZE_ERROR(IllegalModifier, 5l);
-TEST_PARSE_MEM_SIZE_ERROR(NonNumber, abc);
+TEST_F(GuestConfigParserTest, Memory_AddressAndSize) {
+ ASSERT_EQ(ZX_OK, ParseArg("--memory=ffff,4G"));
+ const auto& memory = config_.memory();
+ EXPECT_EQ(1, memory.size());
+ EXPECT_EQ(0xffff, memory[0].addr);
+ EXPECT_EQ(4ul << 30, memory[0].len);
+ EXPECT_EQ(MemoryPolicy::GUEST_CACHED, memory[0].policy);
+}
-TEST(GuestConfigParserTest, VirtioGpu) {
+TEST_F(GuestConfigParserTest, Memory_HostCached) {
+ ASSERT_EQ(ZX_OK, ParseArg("--memory=eeee,2G,cached"));
+ const auto& memory = config_.memory();
+ EXPECT_EQ(1, memory.size());
+ EXPECT_EQ(0xeeee, memory[0].addr);
+ EXPECT_EQ(2ul << 30, memory[0].len);
+ EXPECT_EQ(MemoryPolicy::HOST_CACHED, memory[0].policy);
+}
+
+TEST_F(GuestConfigParserTest, Memory_HostDevice) {
+ ASSERT_EQ(ZX_OK, ParseArg("--memory=dddd,1G,device"));
+ const auto& memory = config_.memory();
+ EXPECT_EQ(1, memory.size());
+ EXPECT_EQ(0xdddd, memory[0].addr);
+ EXPECT_EQ(1ul << 30, memory[0].len);
+ EXPECT_EQ(MemoryPolicy::HOST_DEVICE, memory[0].policy);
+}
+
+TEST_F(GuestConfigParserTest, Memory_MultipleEntries) {
+ const char* argv[] = {"exe_name", "--memory=f0000000,1M",
+ "--memory=ffffffff,2M"};
+ ASSERT_EQ(ZX_OK,
+ parser_.ParseArgcArgv(arraysize(argv), const_cast<char**>(argv)));
+ const auto& memory = config_.memory();
+ EXPECT_EQ(2, memory.size());
+ EXPECT_EQ(0xf0000000, memory[0].addr);
+ EXPECT_EQ(1ul << 20, memory[0].len);
+ EXPECT_EQ(MemoryPolicy::GUEST_CACHED, memory[0].policy);
+ EXPECT_EQ(0xffffffff, memory[1].addr);
+ EXPECT_EQ(2ul << 20, memory[1].len);
+ EXPECT_EQ(MemoryPolicy::GUEST_CACHED, memory[1].policy);
+}
+
+TEST_F(GuestConfigParserTest, Memory_TooSmall) {
+ ASSERT_EQ(ZX_ERR_INVALID_ARGS, ParseArg("--memory=1k"));
+}
+
+TEST_F(GuestConfigParserTest, Memory_IllegalModifier) {
+ ASSERT_EQ(ZX_ERR_INVALID_ARGS, ParseArg("--memory=5l"));
+}
+
+TEST_F(GuestConfigParserTest, Memory_NonNumber) {
+ ASSERT_EQ(ZX_ERR_INVALID_ARGS, ParseArg("--memory=abc"));
+}
+
+TEST_F(GuestConfigParserTest, VirtioGpu) {
GuestConfig config;
GuestConfigParser parser(&config);
const char* virtio_gpu_true_argv[] = {"exe_name", "--virtio-gpu=true"};
ASSERT_EQ(ZX_OK,
- parser.ParseArgcArgv(arraysize(virtio_gpu_true_argv),
- const_cast<char**>(virtio_gpu_true_argv)));
- ASSERT_TRUE(config.virtio_gpu());
+ parser_.ParseArgcArgv(arraysize(virtio_gpu_true_argv),
+ const_cast<char**>(virtio_gpu_true_argv)));
+ ASSERT_TRUE(config_.virtio_gpu());
const char* virtio_gpu_false_argv[] = {"exe_name", "--virtio-gpu=false"};
ASSERT_EQ(ZX_OK,
- parser.ParseArgcArgv(arraysize(virtio_gpu_false_argv),
- const_cast<char**>(virtio_gpu_false_argv)));
- ASSERT_FALSE(config.virtio_gpu());
+ parser_.ParseArgcArgv(arraysize(virtio_gpu_false_argv),
+ const_cast<char**>(virtio_gpu_false_argv)));
+ ASSERT_FALSE(config_.virtio_gpu());
}
-
-} // namespace
-} // namespace guest
diff --git a/bin/guest/vmm/linux.cc b/bin/guest/vmm/linux.cc
index ca9578b..4cfba0c 100644
--- a/bin/guest/vmm/linux.cc
+++ b/bin/guest/vmm/linux.cc
@@ -349,11 +349,11 @@
}
static zx_status_t load_device_tree(
- const int dtb_fd, const machina::PhysMem& phys_mem,
+ const int dtb_fd, const GuestConfig& cfg, const machina::PhysMem& phys_mem,
const machina::DevMem& dev_mem,
const std::vector<machina::PlatformDevice*>& devices,
const std::string& cmdline, const int dtb_overlay_fd,
- const size_t initrd_size, const GuestConfig& cfg) {
+ const size_t initrd_size) {
void* dtb;
size_t dtb_size;
zx_status_t status = read_device_tree(dtb_fd, phys_mem, kDtbOffset,
@@ -467,9 +467,11 @@
}
status = add_memory_entry(dtb, memory_off, range.addr, range.size);
};
- dev_mem.YieldInverseRange(0, phys_mem.size(), yield);
- if (status != ZX_OK) {
- return status;
+ for (const MemorySpec& spec : cfg.memory()) {
+ dev_mem.YieldInverseRange(spec.addr, spec.len, yield);
+ if (status != ZX_OK) {
+ return status;
+ }
}
// Add all platform devices to device tree.
@@ -552,8 +554,8 @@
FXL_LOG(ERROR) << "Failed to open device tree " << kDtbPath;
return ZX_ERR_IO;
}
- status = load_device_tree(dtb_fd.get(), phys_mem, dev_mem, devices, cmdline,
- dtb_overlay_fd.get(), initrd_size, cfg);
+ status = load_device_tree(dtb_fd.get(), cfg, phys_mem, dev_mem, devices,
+ cmdline, dtb_overlay_fd.get(), initrd_size);
if (status != ZX_OK) {
return status;
}
diff --git a/bin/guest/vmm/main.cc b/bin/guest/vmm/main.cc
index f8193e6..08f2b1a 100644
--- a/bin/guest/vmm/main.cc
+++ b/bin/guest/vmm/main.cc
@@ -114,17 +114,17 @@
return status;
}
- // Having memory overlap with dynamic device assignment will work, as any
- // devices will get subtracted from the RAM list later. But it will probably
- // result in much less RAM than expected and so we shall consider it an error.
- if (cfg.memory() >= kFirstDynamicDeviceAddr) {
- FXL_LOG(ERROR) << "Requested memory should be less than "
- << kFirstDynamicDeviceAddr;
- return ZX_ERR_INVALID_ARGS;
+ // We want to avoid a collision between static and dynamic address assignment.
+ for (const MemorySpec& spec : cfg.memory()) {
+ if (spec.addr + spec.len > kFirstDynamicDeviceAddr) {
+ FXL_LOG(ERROR) << "Requested memory should be less than "
+ << kFirstDynamicDeviceAddr;
+ return ZX_ERR_INVALID_ARGS;
+ }
}
machina::Guest guest;
- status = guest.Init(cfg.memory(), cfg.host_memory());
+ status = guest.Init(cfg.memory());
if (status != ZX_OK) {
return status;
}
diff --git a/bin/guest/vmm/zircon.cc b/bin/guest/vmm/zircon.cc
index 91d6167..b8324c0 100644
--- a/bin/guest/vmm/zircon.cc
+++ b/bin/guest/vmm/zircon.cc
@@ -205,14 +205,16 @@
}
// Memory config.
std::vector<zbi_mem_range_t> mem_config;
-
- dev_mem.YieldInverseRange(0, cfg.memory(), [&mem_config](auto range) {
+ auto yield = [&mem_config](auto range) {
mem_config.emplace_back(zbi_mem_range_t{
.paddr = range.addr,
.length = range.size,
.type = ZBI_MEM_RANGE_RAM,
});
- });
+ };
+ for (const MemorySpec& spec : cfg.memory()) {
+ dev_mem.YieldInverseRange(spec.addr, spec.len, yield);
+ }
// Zircon only supports a limited number of peripheral ranges so for any
// dev_mem ranges that are not in the RAM range we will build a single
@@ -220,7 +222,7 @@
zbi_mem_range_t periph_range = {
.paddr = 0, .length = 0, .type = ZBI_MEM_RANGE_PERIPHERAL};
for (const auto& range : dev_mem) {
- if (range.addr < cfg.memory()) {
+ if (range.addr < phys_mem.size()) {
mem_config.emplace_back(zbi_mem_range_t{
.paddr = range.addr,
.length = range.size,
diff --git a/lib/machina/guest.cc b/lib/machina/guest.cc
index 3a62dd9..ab22b10 100644
--- a/lib/machina/guest.cc
+++ b/lib/machina/guest.cc
@@ -40,67 +40,95 @@
}
}
-namespace machina {
-
-zx_status_t Guest::Init(size_t mem_size, bool host_memory) {
- auto sysinfo = get_sysinfo();
- zx::vmo vmo;
- if (host_memory) {
- zx_status_t fidl_status;
- zx::resource resource;
- zx_status_t status = sysinfo->GetRootResource(&fidl_status, &resource);
- if (status != ZX_OK || fidl_status != ZX_OK) {
- FXL_LOG(ERROR) << "Failed to get root resource " << status << " "
- << fidl_status;
- return status != ZX_OK ? status : fidl_status;
- }
- status = zx::vmo::create_physical(resource, 0, mem_size, &vmo);
- if (status != ZX_OK) {
- FXL_LOG(ERROR) << "Failed to create physical VMO " << status;
- return status;
- }
- status = vmo.set_cache_policy(ZX_CACHE_POLICY_CACHED);
- if (status != ZX_OK) {
- FXL_LOG(ERROR) << "Failed to set cache policy on VMO " << status;
- return status;
- }
- } else {
- zx_status_t status = zx::vmo::create(mem_size, ZX_VMO_NON_RESIZABLE, &vmo);
- if (status != ZX_OK) {
- FXL_LOG(ERROR) << "Failed to create VMO " << status;
- return status;
- }
- }
-
- zx_status_t status = phys_mem_.Init(std::move(vmo));
+static zx_status_t get_hypervisor_resource(
+ const fuchsia::sysinfo::DeviceSyncPtr& sysinfo, zx::resource* resource) {
+ zx_status_t fidl_status;
+ zx_status_t status = sysinfo->GetHypervisorResource(&fidl_status, resource);
if (status != ZX_OK) {
- FXL_LOG(ERROR) << "Failed to initialize guest physical memory " << status;
return status;
}
+ return fidl_status;
+}
- zx_status_t fidl_status;
- zx::resource resource;
- status = sysinfo->GetHypervisorResource(&fidl_status, &resource);
- if (status != ZX_OK || fidl_status != ZX_OK) {
- FXL_LOG(ERROR) << "Failed to get hypervisor resource " << status << " "
- << fidl_status;
- return status != ZX_OK ? status : fidl_status;
+static constexpr uint32_t cache_policy(MemoryPolicy policy) {
+ switch (policy) {
+ case MemoryPolicy::HOST_DEVICE:
+ return ZX_CACHE_POLICY_UNCACHED_DEVICE;
+ default:
+ return ZX_CACHE_POLICY_CACHED;
}
+}
- status = zx::guest::create(resource, 0, &guest_, &vmar_);
+namespace machina {
+
+zx_status_t Guest::Init(const std::vector<MemorySpec>& memory) {
+ fuchsia::sysinfo::DeviceSyncPtr sysinfo = get_sysinfo();
+ zx::resource hypervisor_resource;
+ zx_status_t status = get_hypervisor_resource(sysinfo, &hypervisor_resource);
+ if (status != ZX_OK) {
+ FXL_LOG(ERROR) << "Failed to get hypervisor resource " << status;
+ return status;
+ }
+ status = zx::guest::create(hypervisor_resource, 0, &guest_, &vmar_);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to create guest " << status;
return status;
}
- zx_gpaddr_t addr;
- status = vmar_.map(0, phys_mem_.vmo(), 0, mem_size,
- ZX_VM_PERM_READ | ZX_VM_PERM_WRITE | ZX_VM_PERM_EXECUTE |
- ZX_VM_SPECIFIC | ZX_VM_FLAG_REQUIRE_NON_RESIZABLE,
- &addr);
- if (status != ZX_OK) {
- FXL_LOG(ERROR) << "Failed to map guest physical memory " << status;
- return status;
+ zx::resource root_resource;
+ for (const MemorySpec& spec : memory) {
+ zx::vmo vmo;
+ switch (spec.policy) {
+ case MemoryPolicy::GUEST_CACHED:
+ status = zx::vmo::create(spec.len, ZX_VMO_NON_RESIZABLE, &vmo);
+ if (status != ZX_OK) {
+ FXL_LOG(ERROR) << "Failed to create VMO " << status;
+ return status;
+ }
+ break;
+ case MemoryPolicy::HOST_CACHED:
+ case MemoryPolicy::HOST_DEVICE:
+ if (!root_resource) {
+ status = get_root_resource(sysinfo, &root_resource);
+ if (status != ZX_OK) {
+ FXL_LOG(ERROR) << "Failed to get root resource " << status;
+ return status;
+ }
+ }
+ status = zx::vmo::create_physical(root_resource, 0, spec.len, &vmo);
+ if (status != ZX_OK) {
+ FXL_LOG(ERROR) << "Failed to create physical VMO " << status;
+ return status;
+ }
+ status = vmo.set_cache_policy(cache_policy(spec.policy));
+ if (status != ZX_OK) {
+ FXL_LOG(ERROR) << "Failed to set cache policy on VMO " << status;
+ return status;
+ }
+ break;
+ default:
+ FXL_LOG(ERROR) << "Unknown memory policy "
+ << static_cast<uint32_t>(spec.policy);
+ return ZX_ERR_INVALID_ARGS;
+ }
+
+ zx_gpaddr_t addr;
+ status = vmar_.map(spec.addr, vmo, 0, spec.len,
+ ZX_VM_PERM_READ | ZX_VM_PERM_WRITE | ZX_VM_PERM_EXECUTE |
+ ZX_VM_SPECIFIC | ZX_VM_FLAG_REQUIRE_NON_RESIZABLE,
+ &addr);
+ if (status != ZX_OK) {
+ FXL_LOG(ERROR) << "Failed to map guest physical memory " << status;
+ return status;
+ }
+ if (!phys_mem_.vmo()) {
+ status = phys_mem_.Init(std::move(vmo));
+ if (status != ZX_OK) {
+ FXL_LOG(ERROR) << "Failed to initialize guest physical memory "
+ << status;
+ return status;
+ }
+ }
}
for (size_t i = 0; i < kNumAsyncWorkers; ++i) {
diff --git a/lib/machina/guest.h b/lib/machina/guest.h
index 97711ea..a9d5f38 100644
--- a/lib/machina/guest.h
+++ b/lib/machina/guest.h
@@ -17,6 +17,23 @@
#include "garnet/lib/machina/io.h"
#include "garnet/lib/machina/vcpu.h"
+// NOTE(abdulla): Ideally, these should be in guest_config.h
+enum class MemoryPolicy {
+ // Map a VMO as cached memory into the guest physical address space.
+ GUEST_CACHED = 0,
+ // Map a VMO with 1:1 correspondence with host memory as cached memory into
+ // the guest physical address space.
+ HOST_CACHED = 1,
+ // Map a VMO with 1:1 correspondence with host memory as device memory into
+ // the guest physical address space.
+ HOST_DEVICE = 2,
+};
+struct MemorySpec {
+ zx_gpaddr_t addr;
+ size_t len;
+ MemoryPolicy policy;
+};
+
namespace machina {
enum class TrapType {
@@ -31,7 +48,7 @@
using VcpuArray = std::array<std::unique_ptr<Vcpu>, kMaxVcpus>;
using IoMappingList = std::forward_list<IoMapping>;
- zx_status_t Init(size_t mem_size, bool host_memory);
+ zx_status_t Init(const std::vector<MemorySpec>& memory);
const PhysMem& phys_mem() const { return phys_mem_; }
const zx::guest& object() { return guest_; }