Skip to content

Commit

Permalink
Merge pull request #7 from blazern/main
Browse files Browse the repository at this point in the history
Add article - serial port exposure
  • Loading branch information
elektronenhirn authored Oct 2, 2024
2 parents 23a62bc + efba829 commit 5c13754
Show file tree
Hide file tree
Showing 2 changed files with 329 additions and 0 deletions.
197 changes: 197 additions & 0 deletions content/posts/expose-serial-port.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
---
title: "How to expose a serial port to a custom AOSP running in Trout / Cuttlefish"
date: 2024-09-20T16:34:36+02:00
draft: false
author: "Danil Garmanov"
---

Android running in a virtualized environment normally has no access to the hardware of the host machine, most of the hardware it "works" with is instead emulated.

But sometimes one wants a way to attach external hardware to their custom Android without working with an actual physical Android device with a USB port.
In our case we needed to attach a serial device.

In this article we will demonstrate how one can expose a *serial port* to Trout.

# crosvm
Trout is based on [crosvm](https://github.com/google/crosvm), and crosvm already supports serial devices exposure using the `--serial` parameter of the `crosvm run` command.

But we use crosvm indirectly, through Trout, when we start it using the `launch_cvd` command.
And `launch_cvd` does not provide a way to pass values to the `--serial` param of `crosvm run`.

So we will add it.

# Modifying the source code of your AOSP fork
*Disclaimer: the diffs are shown for Android 14, diffs for other versions of Android would be slightly different.*

First, `cd device/google/cuttlefish`.

Now, let's add new Cuttlefish config parameter, in the header file:
```
diff --git a/host/libs/config/cuttlefish_config.h b/host/libs/config/cuttlefish_config.h
index 9a2af86cd..7c9bb5725 100644
--- a/host/libs/config/cuttlefish_config.h
+++ b/host/libs/config/cuttlefish_config.h
@@ -125,6 +125,9 @@ class CuttlefishConfig {
void set_gem5_debug_flags(const std::string& gem5_debug_flags);
std::string gem5_debug_flags() const;
+ void set_attach_serial_device(const std::string& attach_serial_device);
+ std::string attach_serial_device() const;
+
void set_enable_host_uwb(bool enable_host_uwb);
bool enable_host_uwb() const;
```
...and in the implementation file:
```
diff --git a/host/libs/config/cuttlefish_config.cpp b/host/libs/config/cuttlefish_config.cpp
index 5c9e8dc36..2bdf2d6e4 100644
--- a/host/libs/config/cuttlefish_config.cpp
+++ b/host/libs/config/cuttlefish_config.cpp
@@ -188,6 +188,15 @@ void CuttlefishConfig::set_gem5_debug_flags(const std::string& gem5_debug_flags)
(*dictionary_)[kGem5DebugFlags] = gem5_debug_flags;
}
+static constexpr char kAttachSerialDevice[] = "attach_serial_device";
+void CuttlefishConfig::set_attach_serial_device(
+ const std::string& attach_serial_device) {
+ (*dictionary_)[kAttachSerialDevice] = attach_serial_device;
+}
+std::string CuttlefishConfig::attach_serial_device() const {
+ return (*dictionary_)[kAttachSerialDevice].asString();
+}
+
static constexpr char kWebRTCCertsDir[] = "webrtc_certs_dir";
void CuttlefishConfig::set_webrtc_certs_dir(const std::string& certs_dir) {
(*dictionary_)[kWebRTCCertsDir] = certs_dir;
```

Now let's add a new parameter to the `launch_cvd` command that would use this config change:
```
diff --git a/host/commands/assemble_cvd/flags.cc b/host/commands/assemble_cvd/flags.cc
index c5ef0344a..e10615d72 100644
--- a/host/commands/assemble_cvd/flags.cc
+++ b/host/commands/assemble_cvd/flags.cc
@@ -202,6 +202,16 @@ DEFINE_string(
seccomp_policy_dir, CF_DEFAULTS_SECCOMP_POLICY_DIR,
"With sandbox'ed crosvm, overrieds the security comp policy directory");
+DEFINE_string(
+ attach_serial_device, "",
+ "Path to a serial device that should be attached to crosvm "
+ "(/dev/<something>). "
+ "QEMU is not supported.\n"
+ "To see the attached device name inside the guest machine, look for a "
+ "corresponding log "
+ "message with the 'SERIAL_PORT' prefix during the VM start.\n"
+ "The device name is stable, but it may change on AOSP updates.");
+
DEFINE_vec(start_webrtc, cuttlefish::BoolToString(CF_DEFAULTS_START_WEBRTC),
"Whether to start the webrtc process.");
@@ -818,6 +828,8 @@ Result<CuttlefishConfig> InitializeCuttlefishConfiguration(
tmp_config_obj.set_gem5_debug_flags(FLAGS_gem5_debug_flags);
+ tmp_config_obj.set_attach_serial_device(FLAGS_attach_serial_device);
+
// streaming, webrtc setup
tmp_config_obj.set_webrtc_certs_dir(FLAGS_webrtc_certs_dir);
tmp_config_obj.set_sig_server_secure(FLAGS_webrtc_sig_server_secure);
```

Cuttlefish has already quite a few defined serial ports, let's increase their number by one:
```
diff --git a/host/libs/vm_manager/vm_manager.h b/host/libs/vm_manager/vm_manager.h
index 4b116bfd9..036a2d3eb 100644
--- a/host/libs/vm_manager/vm_manager.h
+++ b/host/libs/vm_manager/vm_manager.h
@@ -54,7 +54,8 @@ class VmManager {
// - /dev/hvc9 = uwb
// - /dev/hvc10 = oemlock
// - /dev/hvc11 = keymint
- static const int kDefaultNumHvcs = 12;
+ static const int kDefaultNumHvcs =
+ 13; // NOTE: on merge conflicts do +1 to the upstream's value
// This is the number of virtual disks (block devices) that should be
// configured by the VmManager. Related to the description above regarding
```

And finally let's actually forward the new parameter to `crosvm`:
```
diff --git a/host/libs/vm_manager/crosvm_manager.cpp b/host/libs/vm_manager/crosvm_manager.cpp
index 100b71e56..d81b5eb08 100644
--- a/host/libs/vm_manager/crosvm_manager.cpp
+++ b/host/libs/vm_manager/crosvm_manager.cpp
@@ -449,6 +449,18 @@ Result<std::vector<MonitorCommand>> CrosvmManager::StartCommands(
for (auto i = 0; i < VmManager::kMaxDisks - disk_num; i++) {
crosvm_cmd.AddHvcSink();
}
+
+ if (!config.attach_serial_device().empty()) {
+ crosvm_cmd.AddHvcReadWrite(config.attach_serial_device(),
+ config.attach_serial_device());
+ LOG(INFO) << "SERIAL_PORT: attaching serial port to crosvm: "
+ << config.attach_serial_device()
+ << ", most likely device name inside the guest machine: /dev/hvc"
+ << crosvm_cmd.HvcNum() - 1;
+ } else {
+ crosvm_cmd.AddHvcSink();
+ }
+
CF_EXPECT(crosvm_cmd.HvcNum() + disk_num ==
VmManager::kMaxDisks + VmManager::kDefaultNumHvcs,
"HVC count (" << crosvm_cmd.HvcNum() << ") + disk count ("
```

... but not to QEMU, which is mostly used as AVD for normal (phone) Android apps (it might actually work, but we haven't test it since our focus is Trout / crossvm):
```
diff --git a/host/libs/vm_manager/qemu_manager.cpp b/host/libs/vm_manager/qemu_manager.cpp
index a2744a35d..a0ae1286b 100644
--- a/host/libs/vm_manager/qemu_manager.cpp
+++ b/host/libs/vm_manager/qemu_manager.cpp
@@ -556,6 +556,8 @@ Result<std::vector<MonitorCommand>> QemuManager::StartCommands(
add_hvc_sink();
}
+ add_hvc_sink(); // attach_serial_device, not supported on QEMU
+
CF_EXPECT(
hvc_num + disk_num == VmManager::kMaxDisks + VmManager::kDefaultNumHvcs,
"HVC count (" << hvc_num << ") + disk count (" << disk_num << ") "
```

And the last thing we need to do is setting up proper access rights:
```
diff --git a/shared/config/ueventd.rc b/shared/config/ueventd.rc
index c604e3d23..dd7da4de3 100644
--- a/shared/config/ueventd.rc
+++ b/shared/config/ueventd.rc
@@ -44,5 +44,13 @@
# keymint / Rust
/dev/hvc11 0666 system system
+# Serial Port integration for the -attach_serial_device
+# param of the launch_cvd command.
+# Please update this name "/dev/hvcN" to "/dev/hvc{N+M}"
+# when kDefaultNumHvcs in 'host/libs/vm_manager/vm_manager.h' is updated.
+# The 'M' value here would be the delta between the new and the old values
+# of kDefaultNumHvcs.
+/dev/hvc12 0666 system system
+
# Factory Reset Protection
/dev/block/by-name/frp 0660 system system
```

You can find the full diff as one file [here](./full.diff).

# Conclusion
And that was basically it - now you can use the exposed port (`/dev/hvc12` in the case of the diff above) from inside of Android.
Don't forget to use the new parameter when running `launch_cvd`, e.g.:
```
launch_cvd ... -attach_serial_device /dev/ttyACM0
```
Replace `/dev/ttyACM0` with the actual path of the serial device on your host machine.

You can do a quick test by cross-compiling and adb-pushing a small binary to Android, which would attach to the serial port, and then executing the binary under `su`. Or just connect to the port from any system service.
132 changes: 132 additions & 0 deletions content/posts/expose-serial-port/full.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
diff --git a/host/commands/assemble_cvd/flags.cc b/host/commands/assemble_cvd/flags.cc
index c5ef0344a..e10615d72 100644
--- a/host/commands/assemble_cvd/flags.cc
+++ b/host/commands/assemble_cvd/flags.cc
@@ -202,6 +202,16 @@ DEFINE_string(
seccomp_policy_dir, CF_DEFAULTS_SECCOMP_POLICY_DIR,
"With sandbox'ed crosvm, overrieds the security comp policy directory");

+DEFINE_string(
+ attach_serial_device, "",
+ "Path to a serial device that should be attached to crosvm "
+ "(/dev/<something>). "
+ "QEMU is not supported.\n"
+ "To see the attached device name inside the guest machine, look for a "
+ "corresponding log "
+ "message with the 'SERIAL_PORT' prefix during the VM start.\n"
+ "The device name is stable, but it may change on AOSP updates.");
+
DEFINE_vec(start_webrtc, cuttlefish::BoolToString(CF_DEFAULTS_START_WEBRTC),
"Whether to start the webrtc process.");

@@ -818,6 +828,8 @@ Result<CuttlefishConfig> InitializeCuttlefishConfiguration(

tmp_config_obj.set_gem5_debug_flags(FLAGS_gem5_debug_flags);

+ tmp_config_obj.set_attach_serial_device(FLAGS_attach_serial_device);
+
// streaming, webrtc setup
tmp_config_obj.set_webrtc_certs_dir(FLAGS_webrtc_certs_dir);
tmp_config_obj.set_sig_server_secure(FLAGS_webrtc_sig_server_secure);
diff --git a/host/libs/config/cuttlefish_config.cpp b/host/libs/config/cuttlefish_config.cpp
index 5c9e8dc36..2bdf2d6e4 100644
--- a/host/libs/config/cuttlefish_config.cpp
+++ b/host/libs/config/cuttlefish_config.cpp
@@ -188,6 +188,15 @@ void CuttlefishConfig::set_gem5_debug_flags(const std::string& gem5_debug_flags)
(*dictionary_)[kGem5DebugFlags] = gem5_debug_flags;
}

+static constexpr char kAttachSerialDevice[] = "attach_serial_device";
+void CuttlefishConfig::set_attach_serial_device(
+ const std::string& attach_serial_device) {
+ (*dictionary_)[kAttachSerialDevice] = attach_serial_device;
+}
+std::string CuttlefishConfig::attach_serial_device() const {
+ return (*dictionary_)[kAttachSerialDevice].asString();
+}
+
static constexpr char kWebRTCCertsDir[] = "webrtc_certs_dir";
void CuttlefishConfig::set_webrtc_certs_dir(const std::string& certs_dir) {
(*dictionary_)[kWebRTCCertsDir] = certs_dir;
diff --git a/host/libs/config/cuttlefish_config.h b/host/libs/config/cuttlefish_config.h
index 9a2af86cd..7c9bb5725 100644
--- a/host/libs/config/cuttlefish_config.h
+++ b/host/libs/config/cuttlefish_config.h
@@ -125,6 +125,9 @@ class CuttlefishConfig {
void set_gem5_debug_flags(const std::string& gem5_debug_flags);
std::string gem5_debug_flags() const;

+ void set_attach_serial_device(const std::string& attach_serial_device);
+ std::string attach_serial_device() const;
+
void set_enable_host_uwb(bool enable_host_uwb);
bool enable_host_uwb() const;

diff --git a/host/libs/vm_manager/crosvm_manager.cpp b/host/libs/vm_manager/crosvm_manager.cpp
index 100b71e56..d81b5eb08 100644
--- a/host/libs/vm_manager/crosvm_manager.cpp
+++ b/host/libs/vm_manager/crosvm_manager.cpp
@@ -449,6 +449,18 @@ Result<std::vector<MonitorCommand>> CrosvmManager::StartCommands(
for (auto i = 0; i < VmManager::kMaxDisks - disk_num; i++) {
crosvm_cmd.AddHvcSink();
}
+
+ if (!config.attach_serial_device().empty()) {
+ crosvm_cmd.AddHvcReadWrite(config.attach_serial_device(),
+ config.attach_serial_device());
+ LOG(INFO) << "SERIAL_PORT: attaching serial port to crosvm: "
+ << config.attach_serial_device()
+ << ", most likely device name inside the guest machine: /dev/hvc"
+ << crosvm_cmd.HvcNum() - 1;
+ } else {
+ crosvm_cmd.AddHvcSink();
+ }
+
CF_EXPECT(crosvm_cmd.HvcNum() + disk_num ==
VmManager::kMaxDisks + VmManager::kDefaultNumHvcs,
"HVC count (" << crosvm_cmd.HvcNum() << ") + disk count ("
diff --git a/host/libs/vm_manager/qemu_manager.cpp b/host/libs/vm_manager/qemu_manager.cpp
index a2744a35d..a0ae1286b 100644
--- a/host/libs/vm_manager/qemu_manager.cpp
+++ b/host/libs/vm_manager/qemu_manager.cpp
@@ -556,6 +556,8 @@ Result<std::vector<MonitorCommand>> QemuManager::StartCommands(
add_hvc_sink();
}

+ add_hvc_sink(); // attach_serial_device, not supported on QEMU
+
CF_EXPECT(
hvc_num + disk_num == VmManager::kMaxDisks + VmManager::kDefaultNumHvcs,
"HVC count (" << hvc_num << ") + disk count (" << disk_num << ") "
diff --git a/host/libs/vm_manager/vm_manager.h b/host/libs/vm_manager/vm_manager.h
index 4b116bfd9..036a2d3eb 100644
--- a/host/libs/vm_manager/vm_manager.h
+++ b/host/libs/vm_manager/vm_manager.h
@@ -54,7 +54,8 @@ class VmManager {
// - /dev/hvc9 = uwb
// - /dev/hvc10 = oemlock
// - /dev/hvc11 = keymint
- static const int kDefaultNumHvcs = 12;
+ static const int kDefaultNumHvcs =
+ 13; // NOTE: on merge conflicts do +1 to the upstream's value

// This is the number of virtual disks (block devices) that should be
// configured by the VmManager. Related to the description above regarding
diff --git a/shared/config/ueventd.rc b/shared/config/ueventd.rc
index c604e3d23..dd7da4de3 100644
--- a/shared/config/ueventd.rc
+++ b/shared/config/ueventd.rc
@@ -44,5 +44,13 @@
# keymint / Rust
/dev/hvc11 0666 system system

+# Serial Port integration for the -attach_serial_device
+# param of the launch_cvd command.
+# Please update this name "/dev/hvcN" to "/dev/hvc{N+M}"
+# when kDefaultNumHvcs in 'host/libs/vm_manager/vm_manager.h' is updated.
+# The 'M' value here would be the delta between the new and the old values
+# of kDefaultNumHvcs.
+/dev/hvc12 0666 system system
+
# Factory Reset Protection
/dev/block/by-name/frp 0660 system system

0 comments on commit 5c13754

Please sign in to comment.