Add README explaining how to use vmbase.

Bug: 223166344
Test: Looked at rendered Markdown
Change-Id: I2ae943859e81e7f01af33cfc7847ad43c9fd05a7
diff --git a/vmbase/README.md b/vmbase/README.md
new file mode 100644
index 0000000..42b9d7b
--- /dev/null
+++ b/vmbase/README.md
@@ -0,0 +1,186 @@
+# vmbase
+
+This directory contains a Rust crate and static library which can be used to write `no_std` Rust
+binaries to run in an aarch64 VM under crosvm (via the VirtualizationService), such as for pVM
+firmware, a VM bootloader or kernel.
+
+In particular it provides:
+
+- An [entry point](entry.S) that initialises the MMU with a hard-coded identity mapping, enables the
+  cache, prepares the image and allocates a stack.
+- An [exception vector](exceptions.S) to call your exception handlers.
+- A UART driver and `println!` macro for early console logging.
+- Functions to shutdown or reboot the VM.
+
+Libraries are also available for heap allocation, page table manipulation and PSCI calls.
+
+## Usage
+
+The [example](example/) subdirectory contains an example of how to use it for a VM bootloader.
+
+### Build file
+
+Start by creating a `rust_ffi_static` rule containing your main module:
+
+```soong
+rust_ffi_static {
+    name: "libvmbase_example",
+    crate_name: "vmbase_example",
+    srcs: ["src/main.rs"],
+    edition: "2021",
+    no_stdlibs: true,
+    stdlibs: [
+        "libcompiler_builtins.rust_sysroot",
+        "libcore.rust_sysroot",
+    ],
+    rustlibs: [
+        "libvmbase",
+    ],
+    enabled: false,
+    target: {
+        android_arm64: {
+            enabled: true,
+        },
+    },
+}
+```
+
+Note that stdlibs must be explicitly specified, as we don't want the normal set of libraries used
+for a C++ binary intended to run in Android userspace.
+
+### Entry point
+
+Your main module needs to specify a couple of special attributes:
+
+```rust
+#![no_main]
+#![no_std]
+```
+
+This tells rustc that it doesn't depend on `std`, and won't have the usual `main` function as an
+entry point. Instead, `vmbase` provides a macro to specify your main function:
+
+```rust
+use vmbase::{main, println};
+
+main!(main);
+
+pub fn main(arg0: u64, arg1: u64, arg2: u64, arg3: u64) {
+    println!("Hello world");
+}
+```
+
+vmbase adds a wrapper around your main function to initialise the console driver first (with the
+UART at base address `0x3f8`, the first UART allocated by crosvm), and make a PSCI `SYSTEM_OFF` call
+to shutdown the VM if your main function ever returns.
+
+You can also shutdown the VM by calling `vmbase::power::shutdown` or 'reboot' by calling
+`vmbase::power::reboot`. Either will cause crosvm to terminate the VM, but by convention we use
+shutdown to indicate that the VM has finished cleanly, and reboot to indicate an error condition.
+
+### Exception handlers
+
+You must provide handlers for each of the 8 types of exceptions which can occur on aarch64. These
+must use the C ABI, and have the expected names. For example, to log sync exceptions and reboot:
+
+```rust
+use vmbase::{console::emergency_write_str, power::reboot};
+
+extern "C" fn sync_exception_current() {
+    emergency_write_str("sync_exception_current\n");
+
+    let mut esr: u64;
+    unsafe {
+        asm!("mrs {esr}, esr_el1", esr = out(reg) esr);
+    }
+    eprintln!("esr={:#08x}", esr);
+
+    reboot();
+}
+```
+
+The `println!` macro shouldn't be used in exception handlers, because it relies on a global instance
+of the UART driver which might be locked when the exception happens, which would result in deadlock.
+Instead you can use `emergency_write_str` and `eprintln!`, which will re-initialise the UART every
+time to ensure that it can be used. This should still be used with care, as it may interfere with
+whatever the rest of the program is doing with the UART.
+
+Note also that in some cases when the system is in a bad state resulting in the stack not working
+properly, `eprintln!` may hang. `emergency_write_str` may be more reliable as it seems to avoid
+any stack allocation. This is why the example above uses `emergency_write_str` first to ensure that
+at least something is logged, before trying `eprintln!` to print more details.
+
+See [example/src/exceptions.rs](examples/src/exceptions.rs) for a complete example.
+
+### Linker script and initial idmap
+
+The [entry point](entry.S) code expects to be provided a hardcoded identity-mapped page table to use
+initially. This must contain at least the region where the image itself is loaded, some writable
+DRAM to use for the `.bss` and `.data` sections and stack, and a device mapping for the UART MMIO
+region. See the [example/idmap.S](example/idmap.S) for an example of how this can be constructed.
+
+The addresses in the pagetable must map the addresses the image is linked at, as we don't support
+relocation. This can be achieved with a linker script, like the one in
+[example/image.ld](example/image.ld). The key part is the regions provided to be used for the image
+and writable data:
+
+```ld
+MEMORY
+{
+	image		    : ORIGIN = 0x80200000, LENGTH = 2M
+	writable_data	: ORIGIN = 0x80400000, LENGTH = 2M
+}
+```
+
+### Building a binary
+
+To link your Rust code together with the entry point code and idmap into a static binary, you need
+to use a `cc_binary` rule:
+
+```soong
+cc_binary {
+    name: "vmbase_example_elf",
+    stem: "vmbase_example",
+    srcs: [
+        "idmap.S",
+    ],
+    static_libs: [
+        "libvmbase_entry",
+        "libvmbase_example",
+    ],
+    static_executable: true,
+    nocrt: true,
+    system_shared_libs: ["libc"],
+    stl: "none",
+    linker_scripts: ["image.ld"],
+    installable: false,
+    enabled: false,
+    target: {
+        android_arm64: {
+            enabled: true,
+        },
+    },
+}
+```
+
+This takes your Rust library (`libvmbase_example`), the vmbase library entry point and exception
+vector (`libvmbase_entry`) and your initial idmap (`idmap.S`) and builds a static binary with your
+linker script (`image.ld`). This is an ELF binary, but to run it as a VM bootloader you need to
+`objcopy` it to a raw binary image instead, which you can do with a `raw_binary` rule:
+
+```soong
+raw_binary {
+    name: "vmbase_example",
+    src: ":vmbase_example_elf",
+    stem: "vmbase_example.bin",
+    enabled: false,
+    target: {
+        android_arm64: {
+            enabled: true,
+        },
+    },
+}
+```
+
+The resulting binary can then be used to start a VM by passing it as the bootloader in a
+`VirtualMachineRawConfig`.