[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_; }