lm3s6965evb-qemu: Introduce Stellaris QEMU target

Introduces the lm3s6965evb target designed to run inside QEMU. Not all
tests have been verified on this target, but the target builds and
successfully runs some of the tests.

Change-Id: Ic0c309c691096aa18b03bd85457195d6a2b49a0b
diff --git a/docs/BUILD.gn b/docs/BUILD.gn
index 9edef7f..527d20b 100644
--- a/docs/BUILD.gn
+++ b/docs/BUILD.gn
@@ -37,6 +37,7 @@
   deps = [
     "$dir_pigweed/targets/docs:target_docs",
     "$dir_pigweed/targets/host:target_docs",
+    "$dir_pigweed/targets/lm3s6965evb-qemu:target_docs",
     "$dir_pigweed/targets/stm32f429i-disc1:target_docs",
   ]
 }
diff --git a/modules.gni b/modules.gni
index 824ce7a..0eeeedc 100644
--- a/modules.gni
+++ b/modules.gni
@@ -45,6 +45,8 @@
 dir_pw_status = "$dir_pigweed/pw_status"
 dir_pw_string = "$dir_pigweed/pw_string"
 dir_pw_sys_io = "$dir_pigweed/pw_sys_io"
+dir_pw_sys_io_baremetal_lm3s6965evb =
+    "$dir_pigweed/pw_sys_io_baremetal_lm3s6965evb"
 dir_pw_sys_io_baremetal_stm32f429 = "$dir_pigweed/pw_sys_io_baremetal_stm32f429"
 dir_pw_sys_io_stdio = "$dir_pigweed/pw_sys_io_stdio"
 dir_pw_target_runner = "$dir_pigweed/pw_target_runner"
diff --git a/pw_boot_armv7m/BUILD.gn b/pw_boot_armv7m/BUILD.gn
index f72ed77..6a5b745 100644
--- a/pw_boot_armv7m/BUILD.gn
+++ b/pw_boot_armv7m/BUILD.gn
@@ -34,6 +34,9 @@
       ":armv7m_linker_script",
       "$dir_pw_preprocessor",
     ]
+    if (defined(pw_boot_armv7m_qemu_shutdown) && pw_boot_armv7m_qemu_shutdown) {
+      defines = [ "PW_BOOT_ARMV7M_QEMU_SHUTDOWN=1" ]
+    }
     public = [ "public/pw_boot_armv7m/boot.h" ]
     sources = [ "core_init.c" ] + public
   }
diff --git a/pw_boot_armv7m/core_init.c b/pw_boot_armv7m/core_init.c
index 209cac4..b8b4223 100644
--- a/pw_boot_armv7m/core_init.c
+++ b/pw_boot_armv7m/core_init.c
@@ -103,6 +103,12 @@
   // Run main.
   main();
 
+#if PW_BOOT_ARMV7M_QEMU_SHUTDOWN
+  // QEMU requires a special command to tell the VM to shut down.
+  volatile uint32_t* aircr = (uint32_t*)(0xE000ED0CU);
+  *aircr = 0x5fa0004;
+#endif  // PW_BOOT_ARMV7M_QEMU_SHUTDOWN
+
   // In case main() returns, just sit here until the device is reset.
   while (true) {
   }
diff --git a/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py b/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py
index 069747a..454496f 100755
--- a/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py
+++ b/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py
@@ -155,6 +155,9 @@
     ninja('docs:docs', ctx=ctx)
 
 
+_QEMU_GEN_ARGS = gn_args(
+    pw_target_config='"//targets/lm3s6965evb-qemu/target_config.gni"')
+
 GN: Tuple[Callable, ...] = (
     gn_clang_build,
     gn_arm_build,
@@ -404,6 +407,11 @@
     build_gn.update(
         _get_paths_from_command('gn', 'desc', docs_dir, '*', ctx=ctx))
 
+    qemu_dir = ctx.output_directory.joinpath('qemu')
+    gn_gen(_QEMU_GEN_ARGS, ctx=ctx, path=qemu_dir)
+    build_gn.update(
+        _get_paths_from_command('gn', 'desc', qemu_dir, '*', ctx=ctx))
+
     missing_bazel = []
     missing_gn = []
 
diff --git a/pw_sys_io_baremetal_lm3s6965evb/BUILD b/pw_sys_io_baremetal_lm3s6965evb/BUILD
new file mode 100644
index 0000000..e3f3d38
--- /dev/null
+++ b/pw_sys_io_baremetal_lm3s6965evb/BUILD
@@ -0,0 +1,24 @@
+# Copyright 2020 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+filegroup(
+    name = "pw_sys_io_baremetal_lm3s6965evb",
+    srcs = [
+        "sys_io_baremetal.cc",
+    ],
+)
diff --git a/pw_sys_io_baremetal_lm3s6965evb/BUILD.gn b/pw_sys_io_baremetal_lm3s6965evb/BUILD.gn
new file mode 100644
index 0000000..60828b5
--- /dev/null
+++ b/pw_sys_io_baremetal_lm3s6965evb/BUILD.gn
@@ -0,0 +1,29 @@
+# Copyright 2020 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+import("$dir_pw_docgen/docs.gni")
+
+# This if statement allows docs to always build even if the target isn't
+# compatible with this backend.
+if (dir_pw_sys_io_backend == dir_pw_sys_io_baremetal_lm3s6965evb) {
+  source_set("pw_sys_io_baremetal_lm3s6965evb") {
+    public_deps = [ "$dir_pw_boot_armv7m" ]
+    deps = [
+      "$dir_pw_preprocessor",
+      "$dir_pw_sys_io:default_putget_bytes",
+      "$dir_pw_sys_io:facade",
+    ]
+    sources = [ "sys_io_baremetal.cc" ]
+  }
+}
diff --git a/pw_sys_io_baremetal_lm3s6965evb/sys_io_baremetal.cc b/pw_sys_io_baremetal_lm3s6965evb/sys_io_baremetal.cc
new file mode 100644
index 0000000..7930439
--- /dev/null
+++ b/pw_sys_io_baremetal_lm3s6965evb/sys_io_baremetal.cc
@@ -0,0 +1,185 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include <cinttypes>
+
+#include "pw_boot_armv7m/boot.h"
+#include "pw_preprocessor/compiler.h"
+#include "pw_sys_io/sys_io.h"
+
+namespace {
+
+// Default core clock. This is technically not a constant, but since this app
+// doesn't change the system clock a constant will suffice.
+constexpr uint32_t kSystemCoreClock = 12000000;
+
+// UART status flags.
+constexpr uint32_t kTxFifoEmptyMask = 0b10000000;
+constexpr uint32_t kTxFifoFullMask = 0b1000000;
+constexpr uint32_t kRxFifoFullMask = 0b100000;
+constexpr uint32_t kRxFifoEmptyMask = 0b10000;
+constexpr uint32_t kTxBusyMask = 0b1000;
+
+// UART line control flags.
+// Default: 8n1
+constexpr uint32_t kDefaultLineControl = 0x60;
+
+// UART control flags.
+constexpr uint32_t kUartEnableMask = 0x1;
+
+PW_PACKED(struct) UartBlock {
+  uint32_t data_register;
+  uint32_t receive_error;
+  uint32_t reserved1[4];
+  uint32_t status_flags;
+  uint32_t reserved2;
+  uint32_t low_power;
+  uint32_t integer_baud;
+  uint32_t fractional_baud;
+  uint32_t line_control;
+  uint32_t control;
+  uint32_t interrupt_fifo_level;
+  uint32_t interrupt_mask;
+  uint32_t raw_interrupt;
+  uint32_t masked_interrupt;
+  uint32_t interrupt_clear;
+};
+
+// Declare a reference to the memory mapped block for UART0.
+volatile UartBlock& uart0 = *reinterpret_cast<volatile UartBlock*>(0x4000C000U);
+
+constexpr uint32_t kRcgcUart0EnableMask = 0x1;
+volatile uint32_t& rcgc1 = *reinterpret_cast<volatile uint32_t*>(0x400FE104U);
+
+constexpr uint32_t kRccDefault = 0x078E3AD1U;
+volatile uint32_t& rcc = *reinterpret_cast<volatile uint32_t*>(0x400FE070U);
+
+constexpr uint32_t kRcc2Default = 0x07802810U;
+volatile uint32_t& rcc2 = *reinterpret_cast<volatile uint32_t*>(0x400FE070U);
+
+// Calculate a baud rate multiplier such that we have 16 bits of precision for
+// the integer portion and 6 bits for the fractional portion.
+void SetBaudRate(uint32_t clock, uint32_t target_baud) {
+  uint32_t divisor = target_baud * 16;
+  uint32_t remainder = clock % divisor;
+  uart0.integer_baud = (clock % divisor) & 0xffff;
+  uart0.fractional_baud = (((remainder << 7) / divisor + 1) >> 1) & 0x3f;
+}
+
+// Default handler to insert into the ARMv7-M vector table (below).
+// This function exists for convenience. If a device isn't doing what you
+// expect, it might have hit a fault and ended up here.
+void DefaultFaultHandler(void) {
+  while (true) {
+    // Wait for debugger to attach.
+  }
+}
+
+// This is the device's interrupt vector table. It's not referenced in any code
+// because the platform expects this table to be present at the beginning of
+// flash. The exact address is specified in the pw_boot_armv7m configuration as
+// part of the target config.
+//
+// For more information, see ARMv7-M Architecture Reference Manual DDI 0403E.b
+// section B1.5.3.
+
+// This typedef is for convenience when building the vector table. With the
+// exception of SP_main (0th entry in the vector table), all the entries of the
+// vector table are function pointers.
+typedef void (*InterruptHandler)();
+
+PW_KEEP_IN_SECTION(".vector_table")
+const InterruptHandler vector_table[] = {
+    // The starting location of the stack pointer.
+    // This address is NOT an interrupt handler/function pointer, it is simply
+    // the address that the main stack pointer should be initialized to. The
+    // value is reinterpret casted because it needs to be in the vector table.
+    [0] = reinterpret_cast<InterruptHandler>(&pw_stack_high_addr),
+
+    // Reset handler, dictates how to handle reset interrupt. This is the
+    // address that the Program Counter (PC) is initialized to at boot.
+    [1] = pw_BootEntry,
+
+    // NMI handler.
+    [2] = DefaultFaultHandler,
+    // HardFault handler.
+    [3] = DefaultFaultHandler,
+};
+
+}  // namespace
+
+extern "C" void pw_PreMainInit() {
+  // Force RCC to be at default at boot.
+  rcc = kRccDefault;
+  rcc2 = kRcc2Default;
+
+  rcgc1 |= kRcgcUart0EnableMask;
+  for (volatile int i = 0; i < 3; ++i) {
+    // We must wait after enabling uart.
+  }
+  // Set baud rate.
+  SetBaudRate(kSystemCoreClock, /*target_baud=*/115200);
+  uart0.line_control = kDefaultLineControl;
+  uart0.control |= kUartEnableMask;
+}
+
+namespace pw::sys_io {
+
+// Wait for a byte to read on UART0. This blocks until a byte is read. This is
+// extremely inefficient as it requires the target to burn CPU cycles polling to
+// see if a byte is ready yet.
+Status ReadByte(std::byte* dest) {
+  while (true) {
+    if (uart0.receive_error) {
+      // Writing anything to this register clears all errors.
+      uart0.receive_error = 0xff;
+    }
+    if (uart0.status_flags & kRxFifoFullMask) {
+      *dest = static_cast<std::byte>(uart0.data_register);
+      break;
+    }
+  }
+  return Status::OK;
+}
+
+// Send a byte over UART0. Since this blocks on every byte, it's rather
+// inefficient. At the default baud rate of 115200, one byte blocks the CPU for
+// ~87 micro seconds. This means it takes only 10 bytes to block the CPU for
+// 1ms!
+Status WriteByte(std::byte b) {
+  // Wait for TX buffer to be empty. When the buffer is empty, we can write
+  // a value to be dumped out of UART.
+  while (!(uart0.status_flags & kTxFifoEmptyMask)) {
+  }
+  uart0.data_register = static_cast<uint32_t>(b);
+  return Status::OK;
+}
+
+// Writes a string using pw::sys_io, and add newline characters at the end.
+StatusWithSize WriteLine(const std::string_view& s) {
+  size_t chars_written = 0;
+  StatusWithSize result = WriteBytes(as_bytes(span(s)));
+  if (!result.ok()) {
+    return result;
+  }
+  chars_written += result.size();
+
+  // Write trailing newline ("\n\r").
+  result = WriteBytes(as_bytes(span("\n\r", 2)));
+  chars_written += result.size();
+
+  return StatusWithSize(result.status(), chars_written);
+}
+
+}  // namespace pw::sys_io
diff --git a/pw_toolchain/BUILD.gn b/pw_toolchain/BUILD.gn
index d4837be..6309c8f 100644
--- a/pw_toolchain/BUILD.gn
+++ b/pw_toolchain/BUILD.gn
@@ -72,6 +72,44 @@
   }
 }
 
+generate_toolchains("cortex_m3") {
+  toolchain_template = "arm_gcc_toolchain"
+
+  common_toolchain_cflags = [
+    "-mabi=aapcs",
+    "-mcpu=cortex-m3",
+    "-mfloat-abi=soft",
+    "-mthumb",
+    "-specs=nano.specs",
+    "-specs=nosys.specs",
+  ]
+
+  common_toolchain_ldflags = [
+    "-lnosys",
+    "-lc",
+  ]
+
+  toolchains = [
+    # All Cortex-M3 toolchains use software-emulated floating point.
+    {
+      toolchain_name = "arm_gcc_cortex_m3_og"
+      additional_cflags = [ "-Og" ]
+    },
+    {
+      toolchain_name = "arm_gcc_cortex_m3_o1"
+      additional_cflags = [ "-O1" ]
+    },
+    {
+      toolchain_name = "arm_gcc_cortex_m3_o2"
+      additional_cflags = [ "-O2" ]
+    },
+    {
+      toolchain_name = "arm_gcc_cortex_m3_os"
+      additional_cflags = [ "-Os" ]
+    },
+  ]
+}
+
 generate_toolchains("cortex_m4") {
   toolchain_template = "arm_gcc_toolchain"
 
diff --git a/targets/lm3s6965evb-qemu/BUILD.gn b/targets/lm3s6965evb-qemu/BUILD.gn
new file mode 100644
index 0000000..1c9c16e
--- /dev/null
+++ b/targets/lm3s6965evb-qemu/BUILD.gn
@@ -0,0 +1,19 @@
+# Copyright 2020 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+import("$dir_pw_docgen/docs.gni")
+
+pw_doc_group("target_docs") {
+  sources = [ "target_docs.rst" ]
+}
diff --git a/targets/lm3s6965evb-qemu/target_config.gni b/targets/lm3s6965evb-qemu/target_config.gni
new file mode 100644
index 0000000..3616b60
--- /dev/null
+++ b/targets/lm3s6965evb-qemu/target_config.gni
@@ -0,0 +1,74 @@
+# Copyright 2020 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+# Target configuration for the lm3s6965evb development board, run using QEMU.
+#
+# TODO(amontanez): This target configuration treats
+# pw_sys_io_baremetal_lm3s6965evb as if it were a platform. This is for
+# testing/development and should eventually point to something more
+# sophisticated.
+
+import("$dir_pigweed/pw_vars_default.gni")
+
+declare_args() {
+  # Specifies the toolchain to use for this build.
+  pw_target_toolchain = "$dir_pw_toolchain:arm_gcc_cortex_m3_og"
+}
+
+# Use the logging main.
+pw_unit_test_main = "$dir_pw_unit_test:logging_main"
+
+# Expose the tool to use for preprocessing linker scripts.
+# TODO(pwbug/53): Temporary, will be removed when proper linker script support
+# is added to GN.
+pw_cc_command = "arm-none-eabi-gcc"
+
+# Executable wrapper that includes some baremetal startup code.
+template("lm3s6965evb_executable") {
+  target("executable", target_name) {
+    forward_variables_from(invoker, "*")
+    if (!defined(deps)) {
+      deps = []
+    }
+    deps += [ dir_pw_sys_io_baremetal_lm3s6965evb ]
+  }
+}
+
+# Configuration options for Pigweed executable targets.
+pw_executable_config.target_type = "lm3s6965evb_executable"
+
+# Path to the bloaty config file for the output binaries.
+pw_executable_config.bloaty_config_file =
+    "$dir_pw_boot_armv7m/bloaty_config.bloaty"
+
+# Facade backends
+dir_pw_assert_backend = dir_pw_assert_basic
+dir_pw_boot_backend = dir_pw_boot_armv7m
+dir_pw_cpu_exception_backend = dir_pw_cpu_exception_armv7m
+dir_pw_log_backend = dir_pw_log_basic
+dir_pw_sys_io_backend = dir_pw_sys_io_baremetal_lm3s6965evb
+
+# Tell QEMU to shut down after running a binary.
+pw_boot_armv7m_qemu_shutdown = true
+
+pw_boot_armv7m_config.defines += [
+  "PW_BOOT_FLASH_BEGIN=0x00000200",
+  "PW_BOOT_FLASH_SIZE=255K",
+  "PW_BOOT_HEAP_SIZE=0",
+  "PW_BOOT_MIN_STACK_SIZE=1K",
+  "PW_BOOT_RAM_BEGIN=0x20000000",
+  "PW_BOOT_RAM_SIZE=64K",
+  "PW_BOOT_VECTOR_TABLE_BEGIN=0x00000000",
+  "PW_BOOT_VECTOR_TABLE_SIZE=512",
+]
diff --git a/targets/lm3s6965evb-qemu/target_docs.rst b/targets/lm3s6965evb-qemu/target_docs.rst
new file mode 100644
index 0000000..1ab20e5
--- /dev/null
+++ b/targets/lm3s6965evb-qemu/target_docs.rst
@@ -0,0 +1,82 @@
+.. _chapter-lm3s6965evb-qemu:
+
+.. default-domain:: cpp
+
+.. highlight:: sh
+
+----------------
+lm3s6965evb-qemu
+----------------
+This target is specifically for emulation of the Texas Instruments Stellaris
+LM3S lm3s6965evb using QEMU. This may be useful for testing ARMv7-M code without
+physical hardware.
+
+This target configuration has **not** been tested on the physical Stellaris
+development board.
+
+Building
+========
+To build for this target, change the ``pw_target_config`` GN build arg to point
+to this target's configuration file.
+
+.. code:: sh
+
+  $ gn gen --args='pw_target_config = "//targets/lm3s6965evb-qemu/target_config.gni"' out/qemu
+  $ ninja -C out/qemu
+
+or
+
+.. code:: sh
+
+  $ gn gen out/qemu
+  $ gn args
+  # Modify and save the args file to update the pw_target_config.
+  pw_target_config = "//targets/lm3s6965evb-qemu/target_config.gni"
+  $ ninja -C out/qemu
+
+Testing
+=======
+
+This target does not yet support automatic test running (though it would be
+relatively easy to do so). To run a QEMU binary, see the instructions below.
+
+Executing Binaries
+==================
+When running a QEMU binary, you may chose to run it interactively with GDB, or
+allow the binary to run in a hands-off manner.
+
+Note: qemu-system-arm is not currently provided by the Pigweed environment.
+
+Running Without GDB
+-------------------
+When running without GDB, the firmware will execute normally without requiring
+further interaction.
+
+.. code:: sh
+
+  $ qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb \
+    -nographic -no-reboot \
+    -kernel path/to/firmware.elf
+
+Run With GDB
+------------------
+When running with GDB, execution of the binary will begin in a halted state. To
+begin running the code, you must connect using GDB, set any breakpoints you
+wish, and then continue execution.
+
+.. code:: sh
+
+  # Start the VM and GDB server.
+  $ qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb \
+    -gdb tcp::3333 -S
+    -nographic -no-reboot \
+    -kernel path/to/firmware.elf
+
+In another window
+
+.. code:: sh
+
+  $ arm-none-eabi-gdb path/to/firmare.elf
+  (gdb) target remote :3333
+  (gdb) break SomeFunction()
+  (gdb) continue