Merge "Add NATIVE_COVERAGE_PATHS"
diff --git a/host/commands/assemble_cvd/flags.cc b/host/commands/assemble_cvd/flags.cc
index af839c1..b0a24ec 100644
--- a/host/commands/assemble_cvd/flags.cc
+++ b/host/commands/assemble_cvd/flags.cc
@@ -249,7 +249,7 @@
 DEFINE_string(ril_dns, "8.8.8.8", "DNS address of mobile network (RIL)");
 DEFINE_bool(kgdb, false, "Configure the virtual device for debugging the kernel "
                          "with kgdb/kdb. The kernel must have been built with "
-                         "kgdb support.");
+                         "kgdb support, and serial console must be enabled.");
 
 DEFINE_bool(start_gnss_proxy, false, "Whether to start the gnss proxy.");
 
@@ -262,6 +262,8 @@
 DEFINE_int32(modem_simulator_sim_type, 1,
              "Sim type: 1 for normal, 2 for CtsCarrierApiTestCases");
 
+DEFINE_bool(console, false, "Enable the serial console");
+
 namespace {
 
 const std::string kKernelDefaultPath = "kernel";
@@ -475,6 +477,11 @@
       vm_manager_cmdline += " earlycon=uart8250,io,0x3f8";
 
       if (FLAGS_vm_manager == QemuManager::name()) {
+        // crosvm doesn't support ACPI PNP, but QEMU does. We need to disable
+        // it on QEMU so that the ISA serial ports aren't claimed by ACPI, so
+        // we can use serdev with platform devices instead
+        vm_manager_cmdline += " pnpacpi=off";
+
         // crosvm sets up the ramoops.xx= flags for us, but QEMU does not.
         // See external/crosvm/x86_64/src/lib.rs
         // this feature is not supported on aarch64
@@ -490,26 +497,47 @@
     }
   }
 
-  std::string console_dev;
-  auto can_use_virtio_console = !FLAGS_kgdb && !FLAGS_use_bootloader;
-  if (can_use_virtio_console) {
-    // If kgdb and the bootloader are disabled, the Android serial console spawns on a
-    // virtio-console port. If the bootloader is enabled, virtio console can't be used
-    // since uboot doesn't support it.
-    console_dev = "hvc1";
-  } else {
-    // crosvm ARM does not support ttyAMA. ttyAMA is a part of ARM arch.
-    if (cuttlefish::HostArch() == "aarch64" && FLAGS_vm_manager != CrosvmManager::name()) {
-      console_dev = "ttyAMA0";
+  if (FLAGS_console) {
+    std::string console_dev;
+    auto can_use_virtio_console = !FLAGS_kgdb && !FLAGS_use_bootloader;
+    if (can_use_virtio_console) {
+      // If kgdb and the bootloader are disabled, the Android serial console spawns on a
+      // virtio-console port. If the bootloader is enabled, virtio console can't be used
+      // since uboot doesn't support it.
+      console_dev = "hvc1";
     } else {
-      console_dev = "ttyS0";
+      // crosvm ARM does not support ttyAMA. ttyAMA is a part of ARM arch.
+      if (cuttlefish::HostArch() == "aarch64" && FLAGS_vm_manager != CrosvmManager::name()) {
+        console_dev = "ttyAMA0";
+      } else {
+        console_dev = "ttyS0";
+      }
     }
+
+    vm_manager_cmdline += " androidboot.console=" + console_dev;
+    if (FLAGS_kgdb) {
+      vm_manager_cmdline += " kgdboc_earlycon kgdbcon kgdboc=" + console_dev;
+    }
+
+    tmp_config_obj.set_kgdb(FLAGS_kgdb);
+  } else {
+    // Specify an invalid path under /dev, so the init process will disable the
+    // console service due to the console not being found. On physical devices,
+    // it is enough to not specify androidboot.console= *and* not specify the
+    // console= kernel command line parameter, because the console and kernel
+    // dmesg are muxed. However, on cuttlefish, we don't need to mux, and would
+    // prefer to retain the kernel dmesg logging, so we must work around init
+    // falling back to the check for /dev/console (which we'll always have).
+    vm_manager_cmdline += " androidboot.console=invalid";
+
+    // Right now 'kdb' is the only way to interact with kgdb. Until we move the
+    // kgdb feature to its own serial port, it doesn't make much to enable kgdb
+    // unless serial console is also enabled. The 'kdb' feature cannot be used
+    // over adb.
+    tmp_config_obj.set_kgdb(false);
   }
 
-  vm_manager_cmdline += " androidboot.console=" + console_dev;
-  if (FLAGS_kgdb) {
-    vm_manager_cmdline += " kgdboc_earlycon kgdbcon kgdboc=" + console_dev;
-  }
+  tmp_config_obj.set_console(FLAGS_console);
 
   tmp_config_obj.set_vm_manager_kernel_cmdline(vm_manager_cmdline);
 
@@ -591,7 +619,6 @@
 
   tmp_config_obj.set_ril_dns(FLAGS_ril_dns);
 
-  tmp_config_obj.set_kgdb(FLAGS_kgdb);
   tmp_config_obj.set_enable_minimal_mode(FLAGS_enable_minimal_mode);
 
   std::vector<int> instance_nums;
diff --git a/host/commands/run_cvd/launch.cc b/host/commands/run_cvd/launch.cc
index 909e831..acc2717 100644
--- a/host/commands/run_cvd/launch.cc
+++ b/host/commands/run_cvd/launch.cc
@@ -449,17 +449,16 @@
 }
 
 void LaunchGnssGrpcProxyServerIfEnabled(const cuttlefish::CuttlefishConfig& config,
-                                      cuttlefish::ProcessMonitor* process_monitor) {
+                                        cuttlefish::ProcessMonitor* process_monitor) {
     if (!config.enable_gnss_grpc_proxy() ||
         !cuttlefish::FileExists(cuttlefish::GnssGrpcProxyBinary())) {
         return;
     }
+
     cuttlefish::Command gnss_grpc_proxy_cmd(cuttlefish::GnssGrpcProxyBinary());
     auto instance = config.ForDefaultInstance();
+
     auto gnss_in_pipe_name = instance.gnss_in_pipe_name();
-
-    auto gnss_out_pipe_name = instance.gnss_out_pipe_name();
-
     if (mkfifo(gnss_in_pipe_name.c_str(), 0600) != 0) {
       auto error = errno;
       LOG(ERROR) << "Failed to create gnss input fifo for crosvm: "
@@ -467,12 +466,14 @@
       return;
     }
 
+    auto gnss_out_pipe_name = instance.gnss_out_pipe_name();
     if (mkfifo(gnss_out_pipe_name.c_str(), 0660) != 0) {
       auto error = errno;
       LOG(ERROR) << "Failed to create gnss output fifo for crosvm: "
                 << strerror(error);
       return;
     }
+
     // These fds will only be read from or written to, but open them with
     // read and write access to keep them open in case the subprocesses exit
     cuttlefish::SharedFD gnss_grpc_proxy_in_wr =
@@ -482,6 +483,7 @@
                 << gnss_grpc_proxy_in_wr->StrError();
       return;
     }
+
     cuttlefish::SharedFD gnss_grpc_proxy_out_rd =
         cuttlefish::SharedFD::Open(gnss_out_pipe_name.c_str(), O_RDWR);
     if (!gnss_grpc_proxy_out_rd->IsOpen()) {
@@ -496,7 +498,6 @@
     gnss_grpc_proxy_cmd.AddParameter("--gnss_grpc_port=", gnss_grpc_proxy_server_port);
     process_monitor->StartSubprocess(std::move(gnss_grpc_proxy_cmd),
                                      GetOnSubprocessExitCallback(config));
-
 }
 
 void LaunchSecureEnvironment(cuttlefish::ProcessMonitor* process_monitor,
@@ -538,3 +539,54 @@
     process_monitor->StartSubprocess(std::move(grpc_server),
                                      GetOnSubprocessExitCallback(config));
 }
+
+void LaunchConsoleForwarderIfEnabled(const cuttlefish::CuttlefishConfig& config,
+                                     cuttlefish::ProcessMonitor* process_monitor)
+{
+    if (!config.console()) {
+        return;
+    }
+
+    cuttlefish::Command console_forwarder_cmd(cuttlefish::ConsoleForwarderBinary());
+    auto instance = config.ForDefaultInstance();
+
+    auto console_in_pipe_name = instance.console_in_pipe_name();
+    if (mkfifo(console_in_pipe_name.c_str(), 0600) != 0) {
+      auto error = errno;
+      LOG(ERROR) << "Failed to create console input fifo for crosvm: "
+                 << strerror(error);
+      return;
+    }
+
+    auto console_out_pipe_name = instance.console_out_pipe_name();
+    if (mkfifo(console_out_pipe_name.c_str(), 0660) != 0) {
+      auto error = errno;
+      LOG(ERROR) << "Failed to create console output fifo for crosvm: "
+                 << strerror(error);
+      return;
+    }
+
+    // These fds will only be read from or written to, but open them with
+    // read and write access to keep them open in case the subprocesses exit
+    cuttlefish::SharedFD console_forwarder_in_wr =
+        cuttlefish::SharedFD::Open(console_in_pipe_name.c_str(), O_RDWR);
+    if (!console_forwarder_in_wr->IsOpen()) {
+      LOG(ERROR) << "Failed to open console_forwarder input fifo for writes: "
+                 << console_forwarder_in_wr->StrError();
+      return;
+    }
+
+    cuttlefish::SharedFD console_forwarder_out_rd =
+        cuttlefish::SharedFD::Open(console_out_pipe_name.c_str(), O_RDWR);
+    if (!console_forwarder_out_rd->IsOpen()) {
+      LOG(ERROR) << "Failed to open console_forwarder output fifo for reads: "
+                 << console_forwarder_out_rd->StrError();
+      return;
+    }
+
+    console_forwarder_cmd.AddParameter("--console_in_fd=", console_forwarder_in_wr);
+    console_forwarder_cmd.AddParameter("--console_out_fd=", console_forwarder_out_rd);
+    process_monitor->StartSubprocess(std::move(console_forwarder_cmd),
+                                     GetOnSubprocessExitCallback(config));
+
+}
diff --git a/host/commands/run_cvd/launch.h b/host/commands/run_cvd/launch.h
index 9679bcf..974bba9 100644
--- a/host/commands/run_cvd/launch.h
+++ b/host/commands/run_cvd/launch.h
@@ -50,3 +50,6 @@
 
 void LaunchVerhicleHalServerIfEnabled(const cuttlefish::CuttlefishConfig& config,
                                       cuttlefish::ProcessMonitor* process_monitor);
+
+void LaunchConsoleForwarderIfEnabled(const cuttlefish::CuttlefishConfig& config,
+                                     cuttlefish::ProcessMonitor* process_monitor);
diff --git a/host/commands/run_cvd/main.cc b/host/commands/run_cvd/main.cc
index e57d20c..02ed6eb 100644
--- a/host/commands/run_cvd/main.cc
+++ b/host/commands/run_cvd/main.cc
@@ -501,17 +501,21 @@
 
   PrintStreamingInformation(*config);
 
-  LOG(INFO) << kGreenColor << "To access the console run: screen "
-            << instance.console_path() << kResetColor;
+  if (config->console()) {
+    LOG(INFO) << kGreenColor << "To access the console run: screen "
+              << instance.console_path() << kResetColor;
+  } else {
+    LOG(INFO) << kGreenColor
+              << "Serial console is disabled; use -console=true to enable it"
+              << kResetColor;
+  }
 
   LOG(INFO) << kGreenColor
             << "The following files contain useful debugging information:"
             << kResetColor;
-  if (config->run_as_daemon()) {
-    LOG(INFO) << kGreenColor
-              << "  Launcher log: " << instance.launcher_log_path()
-              << kResetColor;
-  }
+  LOG(INFO) << kGreenColor
+            << "  Launcher log: " << instance.launcher_log_path()
+            << kResetColor;
   LOG(INFO) << kGreenColor
             << "  Android's logcat output: " << instance.logcat_path()
             << kResetColor;
@@ -577,6 +581,7 @@
   LaunchGnssGrpcProxyServerIfEnabled(*config, &process_monitor);
   LaunchSecureEnvironment(&process_monitor, *config);
   LaunchVerhicleHalServerIfEnabled(*config, &process_monitor);
+  LaunchConsoleForwarderIfEnabled(*config, &process_monitor);
 
   // The streamer needs to launch before the VMM because it serves on several
   // sockets (input devices, vsock frame server) when using crosvm.
diff --git a/host/frontend/webrtc/lib/client_handler.cpp b/host/frontend/webrtc/lib/client_handler.cpp
index 7c5c785..d11c32a 100644
--- a/host/frontend/webrtc/lib/client_handler.cpp
+++ b/host/frontend/webrtc/lib/client_handler.cpp
@@ -381,15 +381,18 @@
   reply["type"] = "offer";
   reply["sdp"] = offer_str;
 
+  state_ = State::kAwaitingAnswer;
   send_to_client_(reply);
 }
 
 void ClientHandler::OnCreateSDPFailure(webrtc::RTCError error) {
+  state_ = State::kFailed;
   LogAndReplyError(error.message());
   Close();
 }
 
 void ClientHandler::OnSetSDPFailure(webrtc::RTCError error) {
+  state_ = State::kFailed;
   LogAndReplyError(error.message());
   LOG(ERROR) << "Error setting local description: Either there is a bug in "
                 "libwebrtc or the local description was (incorrectly) modified "
@@ -408,6 +411,14 @@
   }
   auto type = message["type"].asString();
   if (type == "request-offer") {
+    // Can't check for state being different that kNew because renegotiation can
+    // start in any state after the answer is returned.
+    if (state_ == State::kCreatingOffer) {
+      // An offer has been requested already
+      LogAndReplyError("Multiple requests for offer received from single client");
+      return;
+    }
+    state_ = State::kCreatingOffer;
     peer_connection_->CreateOffer(
         // No memory leak here because this is a ref counted objects and the
         // peer connection immediately wraps it with a scoped_refptr
@@ -417,6 +428,10 @@
     // The created offer wil be sent to the client on
     // OnSuccess(webrtc::SessionDescriptionInterface* desc)
   } else if (type == "answer") {
+    if (state_ != State::kAwaitingAnswer) {
+      LogAndReplyError("Received unexpected SDP answer");
+      return;
+    }
     auto result = ValidationResult::ValidateJsonObject(message, type,
                                      {{"sdp", Json::ValueType::stringValue}});
     if (!result.ok()) {
@@ -441,6 +456,7 @@
               }
             }));
     peer_connection_->SetRemoteDescription(std::move(remote_desc), observer);
+    state_ = State::kConnecting;
 
   } else if (type == "ice-candidate") {
     {
@@ -508,6 +524,7 @@
       break;
     case webrtc::PeerConnectionInterface::PeerConnectionState::kConnected:
       LOG(VERBOSE) << "Client " << client_id_ << ": WebRTC connected";
+      state_ = State::kConnected;
       observer_->OnConnected(
           [this](const uint8_t *msg, size_t size, bool binary) {
             control_handler_->Send(msg, size, binary);
@@ -559,6 +576,7 @@
 }
 
 void ClientHandler::OnRenegotiationNeeded() {
+  state_ = State::kNew;
   LOG(VERBOSE) << "Client " << client_id_ << " needs renegotiation";
 }
 
@@ -620,6 +638,7 @@
       LOG(DEBUG) << "ICE connection state: Completed";
       break;
     case webrtc::PeerConnectionInterface::kIceConnectionFailed:
+      state_ = State::kFailed;
       LOG(DEBUG) << "ICE connection state: Failed";
       break;
     case webrtc::PeerConnectionInterface::kIceConnectionDisconnected:
diff --git a/host/frontend/webrtc/lib/client_handler.h b/host/frontend/webrtc/lib/client_handler.h
index adaec3e..8e1488d 100644
--- a/host/frontend/webrtc/lib/client_handler.h
+++ b/host/frontend/webrtc/lib/client_handler.h
@@ -93,6 +93,14 @@
       rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver) override;
 
  private:
+  enum class State {
+      kNew,
+      kCreatingOffer,
+      kAwaitingAnswer,
+      kConnecting,
+      kConnected,
+      kFailed,
+  };
   ClientHandler(int client_id, std::shared_ptr<ConnectionObserver> observer,
                 std::function<void(const Json::Value&)> send_client_cb,
                 std::function<void()> on_connection_closed_cb);
@@ -103,6 +111,7 @@
   void LogAndReplyError(const std::string& error_msg) const;
 
   int client_id_;
+  State state_ = State::kNew;
   std::shared_ptr<ConnectionObserver> observer_;
   std::function<void(const Json::Value&)> send_to_client_;
   std::function<void()> on_connection_closed_cb_;
diff --git a/host/libs/config/cuttlefish_config.cpp b/host/libs/config/cuttlefish_config.cpp
index 3ac9fb1..31ec07c 100644
--- a/host/libs/config/cuttlefish_config.cpp
+++ b/host/libs/config/cuttlefish_config.cpp
@@ -160,6 +160,8 @@
 
 const char* kEnableMinimalMode = "enable_minimal_mode";
 
+const char* kConsole = "console";
+
 }  // namespace
 
 namespace cuttlefish {
@@ -743,6 +745,13 @@
   (*dictionary_)[kEnableMinimalMode] = enable_minimal_mode;
 }
 
+void CuttlefishConfig::set_console(bool console) {
+  (*dictionary_)[kConsole] = console;
+}
+bool CuttlefishConfig::console() const {
+  return (*dictionary_)[kConsole].asBool();
+}
+
 // Creates the (initially empty) config object and populates it with values from
 // the config file if the CUTTLEFISH_CONFIG_FILE env variable is present.
 // Returns nullptr if there was an error loading from file
diff --git a/host/libs/config/cuttlefish_config.h b/host/libs/config/cuttlefish_config.h
index d48cd1b..0f7a6b8 100644
--- a/host/libs/config/cuttlefish_config.h
+++ b/host/libs/config/cuttlefish_config.h
@@ -286,6 +286,10 @@
   void set_kgdb(bool kgdb);
   bool kgdb() const;
 
+  // Serial console
+  void set_console(bool console);
+  bool console() const;
+
   // Configuration flags for a minimal device
   bool enable_minimal_mode() const;
   void set_enable_minimal_mode(bool enable_minimal_mode);
@@ -389,9 +393,11 @@
 
     std::string kernel_log_pipe_name() const;
 
+    std::string console_pipe_prefix() const;
     std::string console_in_pipe_name() const;
     std::string console_out_pipe_name() const;
 
+    std::string gnss_pipe_prefix() const;
     std::string gnss_in_pipe_name() const;
     std::string gnss_out_pipe_name() const;
 
diff --git a/host/libs/config/cuttlefish_config_instance.cpp b/host/libs/config/cuttlefish_config_instance.cpp
index 556ac52..d1bca71 100644
--- a/host/libs/config/cuttlefish_config_instance.cpp
+++ b/host/libs/config/cuttlefish_config_instance.cpp
@@ -115,20 +115,28 @@
   return cuttlefish::AbsolutePath(PerInstanceInternalPath("kernel-log-pipe"));
 }
 
+std::string CuttlefishConfig::InstanceSpecific::console_pipe_prefix() const {
+  return cuttlefish::AbsolutePath(PerInstanceInternalPath("console"));
+}
+
 std::string CuttlefishConfig::InstanceSpecific::console_in_pipe_name() const {
-  return cuttlefish::AbsolutePath(PerInstanceInternalPath("console-in-pipe"));
+  return console_pipe_prefix() + ".in";
 }
 
 std::string CuttlefishConfig::InstanceSpecific::console_out_pipe_name() const {
-  return cuttlefish::AbsolutePath(PerInstanceInternalPath("console-out-pipe"));
+  return console_pipe_prefix() + ".out";
+}
+
+std::string CuttlefishConfig::InstanceSpecific::gnss_pipe_prefix() const {
+  return cuttlefish::AbsolutePath(PerInstanceInternalPath("gnss"));
 }
 
 std::string CuttlefishConfig::InstanceSpecific::gnss_in_pipe_name() const {
-  return cuttlefish::AbsolutePath(PerInstanceInternalPath("gnss-in-pipe"));
+  return gnss_pipe_prefix() + ".in";
 }
 
 std::string CuttlefishConfig::InstanceSpecific::gnss_out_pipe_name() const {
-  return cuttlefish::AbsolutePath(PerInstanceInternalPath("gnss-out-pipe"));
+  return gnss_pipe_prefix() + ".out";
 }
 
 int CuttlefishConfig::InstanceSpecific::gnss_grpc_proxy_server_port() const {
diff --git a/host/libs/config/kernel_args.cpp b/host/libs/config/kernel_args.cpp
index 1db316e..40f527d 100644
--- a/host/libs/config/kernel_args.cpp
+++ b/host/libs/config/kernel_args.cpp
@@ -61,7 +61,7 @@
   if (config.enable_gnss_grpc_proxy()) {
     kernel_cmdline.push_back("gnss_cmdline.serdev=serial8250/serial0/serial0-0");
     kernel_cmdline.push_back("gnss_cmdline.type=0");
-    kernel_cmdline.push_back("serdev_ttyport.pdev_tty_port=ttyS3");
+    kernel_cmdline.push_back("serdev_ttyport.pdev_tty_port=ttyS1");
   }
 
   kernel_cmdline.push_back(concat("androidboot.serialno=", instance.serial_number()));
diff --git a/host/libs/vm_manager/crosvm_manager.cpp b/host/libs/vm_manager/crosvm_manager.cpp
index aa0b3f1..96239a4 100644
--- a/host/libs/vm_manager/crosvm_manager.cpp
+++ b/host/libs/vm_manager/crosvm_manager.cpp
@@ -220,7 +220,7 @@
   // virtio-console driver may not be available for early messages
   // In kgdb mode, earlycon is an interactive console, and so early
   // dmesg will go there instead of the kernel.log
-  if (!(config_->use_bootloader() || config_->kgdb())) {
+  if (!(config_->console() && (config_->use_bootloader() || config_->kgdb()))) {
     crosvm_cmd.AddParameter("--serial=hardware=serial,num=1,type=file,path=",
                             instance.kernel_log_pipe_name(), ",earlycon=true");
   }
@@ -231,71 +231,37 @@
   crosvm_cmd.AddParameter("--serial=hardware=virtio-console,num=1,type=file,path=",
                           instance.kernel_log_pipe_name(), ",console=true");
 
-  auto console_in_pipe_name = instance.console_in_pipe_name();
-  if (mkfifo(console_in_pipe_name.c_str(), 0600) != 0) {
-    auto error = errno;
-    LOG(ERROR) << "Failed to create console input fifo for crosvm: "
-               << strerror(error);
-    return {};
-  }
-  auto console_out_pipe_name = instance.console_out_pipe_name();
-  if (mkfifo(console_out_pipe_name.c_str(), 0660) != 0) {
-    auto error = errno;
-    LOG(ERROR) << "Failed to create console output fifo for crosvm: "
-               << strerror(error);
-    return {};
-  }
-
-  // These fds will only be read from or written to, but open them with
-  // read and write access to keep them open in case the subprocesses exit
-  cuttlefish::SharedFD console_in_wr =
-      cuttlefish::SharedFD::Open(console_in_pipe_name.c_str(), O_RDWR);
-  if (!console_in_wr->IsOpen()) {
-    LOG(ERROR) << "Failed to open console input fifo for writes: "
-               << console_in_wr->StrError();
-    return {};
-  }
-  cuttlefish::SharedFD console_out_rd =
-      cuttlefish::SharedFD::Open(console_out_pipe_name.c_str(), O_RDWR);
-  if (!console_out_rd->IsOpen()) {
-    LOG(ERROR) << "Failed to open console output fifo for reads: "
-               << console_out_rd->StrError();
-    return {};
-  }
-
-  // stdin is the only currently supported way to write data to a serial port in
-  // crosvm. A file (named pipe) is used here instead of stdout to ensure only
-  // the serial port output is received by the console forwarder as crosvm may
-  // print other messages to stdout.
-  if (config_->kgdb() || config_->use_bootloader()) {
-    crosvm_cmd.AddParameter("--serial=hardware=serial,num=1,type=file,path=",
-                            console_out_pipe_name, ",input=", console_in_pipe_name,
-                            ",earlycon=true");
-    // In kgdb mode, we have the interactive console on ttyS0 (both Android's
-    // console and kdb), so we can disable the virtio-console port usually
-    // allocated to Android's serial console, and redirect it to a sink. This
-    // ensures that that the PCI device assignments (and thus sepolicy) don't
-    // have to change
-    crosvm_cmd.AddParameter("--serial=hardware=virtio-console,num=2,type=sink");
+  if (config_->console()) {
+    // stdin is the only currently supported way to write data to a serial port in
+    // crosvm. A file (named pipe) is used here instead of stdout to ensure only
+    // the serial port output is received by the console forwarder as crosvm may
+    // print other messages to stdout.
+    if (config_->kgdb() || config_->use_bootloader()) {
+      crosvm_cmd.AddParameter("--serial=hardware=serial,num=1,type=file,path=",
+                              instance.console_out_pipe_name(), ",input=",
+                              instance.console_in_pipe_name(), ",earlycon=true");
+      // In kgdb mode, we have the interactive console on ttyS0 (both Android's
+      // console and kdb), so we can disable the virtio-console port usually
+      // allocated to Android's serial console, and redirect it to a sink. This
+      // ensures that that the PCI device assignments (and thus sepolicy) don't
+      // have to change
+      crosvm_cmd.AddParameter("--serial=hardware=virtio-console,num=2,type=sink");
+    } else {
+      crosvm_cmd.AddParameter("--serial=hardware=virtio-console,num=2,type=file,path=",
+                              instance.console_out_pipe_name(), ",input=",
+                              instance.console_in_pipe_name());
+    }
   } else {
-    crosvm_cmd.AddParameter("--serial=hardware=virtio-console,num=2,type=file,path=",
-                            console_out_pipe_name, ",input=", console_in_pipe_name);
+    // as above, create a fake virtio-console 'sink' port when the serial
+    // console is disabled, so the PCI device ID assignments don't move
+    // around
+    crosvm_cmd.AddParameter("--serial=hardware=virtio-console,num=2,type=sink");
   }
 
-  cuttlefish::Command console_cmd(ConsoleForwarderBinary());
-  console_cmd.AddParameter("--console_in_fd=", console_in_wr);
-  console_cmd.AddParameter("--console_out_fd=", console_out_rd);
-
-
   if (config_->enable_gnss_grpc_proxy()) {
-    auto gnss_in_pipe_name = instance.gnss_in_pipe_name();
-    auto gnss_out_pipe_name = instance.gnss_out_pipe_name();
-
-   
- 
-    crosvm_cmd.AddParameter("--serial=hardware=serial,num=4,type=file,path=",
-                        gnss_out_pipe_name,
-                        ",input=", gnss_in_pipe_name);
+    crosvm_cmd.AddParameter("--serial=hardware=serial,num=2,type=file,path=",
+                            instance.gnss_out_pipe_name(), ",input=",
+                            instance.gnss_in_pipe_name());
   }
 
   cuttlefish::SharedFD log_out_rd, log_out_wr;
@@ -348,7 +314,6 @@
 
   std::vector<cuttlefish::Command> ret;
   ret.push_back(std::move(crosvm_cmd));
-  ret.push_back(std::move(console_cmd));
   ret.push_back(std::move(log_tee_cmd));
   return ret;
 }
diff --git a/host/libs/vm_manager/qemu_manager.cpp b/host/libs/vm_manager/qemu_manager.cpp
index bd5f57c..d9bbfa5 100644
--- a/host/libs/vm_manager/qemu_manager.cpp
+++ b/host/libs/vm_manager/qemu_manager.cpp
@@ -38,6 +38,7 @@
 #include "common/libs/utils/subprocess.h"
 #include "common/libs/utils/users.h"
 #include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/known_paths.h"
 
 namespace cuttlefish {
 namespace vm_manager {
@@ -200,21 +201,17 @@
 
   // In kgdb mode, earlycon is an interactive console, and so early
   // dmesg will go there instead of the kernel.log
-  if (config_->kgdb() || config_->use_bootloader()) {
-    qemu_cmd.AddParameter("-chardev");
-    qemu_cmd.AddParameter("socket,id=earlycon,path=",
-                          instance.console_path(), ",server,nowait");
-  } else {
+  if (!(config_->console() && (config_->kgdb() || config_->use_bootloader()))) {
     qemu_cmd.AddParameter("-chardev");
     qemu_cmd.AddParameter("file,id=earlycon,path=",
                           instance.kernel_log_pipe_name(), ",append=on");
-  }
 
-  // On ARM, -serial will imply an AMBA pl011 serial port. On x86, -serial
-  // will imply an ISA serial port. We have set up earlycon for each of these
-  // port types, so the setting here should match
-  qemu_cmd.AddParameter("-serial");
-  qemu_cmd.AddParameter("chardev:earlycon");
+    // On ARM, -serial will imply an AMBA pl011 serial port. On x86, -serial
+    // will imply an ISA serial port. We have set up earlycon for each of these
+    // port types, so the setting here should match
+    qemu_cmd.AddParameter("-serial");
+    qemu_cmd.AddParameter("chardev:earlycon");
+  }
 
   // This sets up the HVC (virtio-serial / virtio-console) port for the kernel
   // logging. This will take over the earlycon logging when the module is
@@ -231,18 +228,34 @@
 
   // This handles the Android interactive serial console - /dev/hvc1
 
-  // In kgdb mode, we have the interactive console on ttyS0 (both Android's
-  // console and kdb), so we can disable the virtio-console port usually
-  // allocated to Android's serial console, and redirect it to a sink. This
-  // ensures that that the PCI device assignments (and thus sepolicy) don't
-  // have to change
-  if (config_->kgdb() || config_->use_bootloader()) {
+  if (config_->console()) {
+    if (config_->kgdb() || config_->use_bootloader()) {
+      qemu_cmd.AddParameter("-chardev");
+      qemu_cmd.AddParameter("pipe,id=earlycon,path=", instance.console_pipe_prefix());
+
+      // On ARM, -serial will imply an AMBA pl011 serial port. On x86, -serial
+      // will imply an ISA serial port. We have set up earlycon for each of these
+      // port types, so the setting here should match
+      qemu_cmd.AddParameter("-serial");
+      qemu_cmd.AddParameter("chardev:earlycon");
+
+      // In kgdb mode, we have the interactive console on ttyS0 (both Android's
+      // console and kdb), so we can disable the virtio-console port usually
+      // allocated to Android's serial console, and redirect it to a sink. This
+      // ensures that that the PCI device assignments (and thus sepolicy) don't
+      // have to change
+      qemu_cmd.AddParameter("-chardev");
+      qemu_cmd.AddParameter("null,id=hvc1");
+    } else {
+      qemu_cmd.AddParameter("-chardev");
+      qemu_cmd.AddParameter("pipe,id=hvc1,path=", instance.console_pipe_prefix());
+    }
+  } else {
+    // as above, create a fake virtio-console 'sink' port when the serial
+    // console is disabled, so the PCI device ID assignments don't move
+    // around
     qemu_cmd.AddParameter("-chardev");
     qemu_cmd.AddParameter("null,id=hvc1");
-  } else {
-    qemu_cmd.AddParameter("-chardev");
-    qemu_cmd.AddParameter("socket,id=hvc1,path=", instance.console_path(),
-                          ",server,nowait");
   }
 
   qemu_cmd.AddParameter("-device");
@@ -251,6 +264,14 @@
   qemu_cmd.AddParameter("-device");
   qemu_cmd.AddParameter("virtconsole,bus=virtio-serial1.0,chardev=hvc1");
 
+  if (config_->enable_gnss_grpc_proxy()) {
+      qemu_cmd.AddParameter("-chardev");
+      qemu_cmd.AddParameter("pipe,id=gnss,path=", instance.gnss_pipe_prefix());
+
+      qemu_cmd.AddParameter("-serial");
+      qemu_cmd.AddParameter("chardev:gnss");
+  }
+
   // If configured, this handles logcat forwarding to the host via serial
   // (instead of vsocket) - /dev/hvc2