Add stm32f429i-disc1 target and pw_dumb_io

Adds both the stm32f429i-disc1 target and a pw_dumb_io uart-based
backend.

Change-Id: If24379118fc2877485653d61ca66ceeb59ef920a
diff --git a/modules.gni b/modules.gni
index 8fef82c..b0b6c6e 100644
--- a/modules.gni
+++ b/modules.gni
@@ -26,6 +26,8 @@
 dir_pw_build = "$dir_pigweed/pw_build"
 dir_pw_docgen = "$dir_pigweed/pw_docgen"
 dir_pw_dumb_io = "$dir_pigweed/pw_dumb_io"
+dir_pw_dumb_io_baremetal_stm32f429 =
+    "$dir_pigweed/pw_dumb_io_baremetal_stm32f429"
 dir_pw_dumb_io_stdio = "$dir_pigweed/pw_dumb_io_stdio"
 dir_pw_preprocessor = "$dir_pigweed/pw_preprocessor"
 dir_pw_span = "$dir_pigweed/pw_span"
diff --git a/pw_dumb_io_baremetal_stm32f429/BUILD.gn b/pw_dumb_io_baremetal_stm32f429/BUILD.gn
new file mode 100644
index 0000000..d467a41
--- /dev/null
+++ b/pw_dumb_io_baremetal_stm32f429/BUILD.gn
@@ -0,0 +1,40 @@
+# Copyright 2019 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.
+
+config("linker_script_config") {
+  _linker_script = "stm32f429.ld"
+  inputs = [
+    _linker_script,
+  ]
+  ldflags = [ "-T" + rebase_path("$_linker_script") ]
+}
+
+source_set("linker_script") {
+  public_configs = [ ":linker_script_config" ]
+}
+
+source_set("pw_dumb_io_baremetal_stm32f429") {
+  public_configs = [ "$dir_pw_build:pw_default_cpp" ]
+  public_deps = [
+    ":linker_script",
+  ]
+  deps = [
+    "$dir_pw_dumb_io:facade",
+    "$dir_pw_preprocessor",
+  ]
+  sources = [
+    "core_init.c",
+    "dumb_io_baremetal.cc",
+  ]
+}
diff --git a/pw_dumb_io_baremetal_stm32f429/bloaty_config.bloaty b/pw_dumb_io_baremetal_stm32f429/bloaty_config.bloaty
new file mode 100644
index 0000000..30e5cfa
--- /dev/null
+++ b/pw_dumb_io_baremetal_stm32f429/bloaty_config.bloaty
@@ -0,0 +1,29 @@
+# Copyright 2019 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.
+
+source_filter: "^SEG .+$"
+
+custom_data_source: {
+  name: "segment_names"
+  base_data_source: "segments"
+
+  rewrite: {
+    pattern: "LOAD #0"
+    replacement: "SEG FLASH"
+  }
+  rewrite: {
+    pattern: "LOAD #1"
+    replacement: "SEG RAM"
+  }
+}
diff --git a/pw_dumb_io_baremetal_stm32f429/core_init.c b/pw_dumb_io_baremetal_stm32f429/core_init.c
new file mode 100644
index 0000000..69bc16e
--- /dev/null
+++ b/pw_dumb_io_baremetal_stm32f429/core_init.c
@@ -0,0 +1,131 @@
+// Copyright 2019 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.
+
+//                               !!!WARNING!!!
+//
+// Some of the code in this file is run without static initialization expected
+// by C/C++. Any accesses to statically initialized objects/variables before
+// memory is initialized will result in undefined values and violates the C
+// specification. Only code run after memory initialization is complete will be
+// compliant and truly safe to run. In general, make early initialization code
+// run AFTER memory initialization has complete unless it is ABSOLUTELY
+// NECESSARY to modify the way memory is initialized.
+//
+// This file is similar to a traditional assembly startup file. It turns out
+// that everything typically done in ARMv7-M assembly startup can be done
+// straight from C code. This makes startup code easier to maintain, modify,
+// and read.
+//
+// Core initialization is comprised of two primary parts:
+//
+// 1. Initialize ARMv7-M Vector Table: The ARMv7-M vector table (See ARMv7-M
+//    Architecture Reference Manual DDI 0403E.b section B1.5) dictates the
+//    starting program counter (PC) and stack pointer (SP) when the SoC powers
+//    on. The vector table also contains a number of other vectors to handle
+//    different exceptions. This file omits many of the vectors and only
+//    configures the four most important ones.
+//
+// 2. Initialize Memory: When execution begins due to SoC power-on (or the
+//    device is reset), memory must be initialized to ensure it contains the
+//    expected values when code begins to run. The SoC doesn't inherently have a
+//    notion of how to do this, so before ANYTHING else the memory must be
+//    initialized. This is done at the beginning of pw_FirmwareInit().
+//
+//
+// The simple flow is as follows:
+//   Power on -> PC and SP set (from vector_table by SoC) -> pw_FirmwareInit()
+//
+// In pw_FirmwareInit():
+//   Initialize memory -> initialize board (pre-main init) -> main()
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "pw_preprocessor/compiler.h"
+
+// Extern symbols referenced in the vector table.
+extern const uint8_t _stack_end[];
+extern uint8_t _static_init_ram_start[];
+extern uint8_t _static_init_ram_end[];
+extern uint8_t _static_init_flash_start[];
+extern uint8_t _zero_init_ram_start[];
+extern uint8_t _zero_init_ram_end[];
+
+// Functions called as part of firmware initialization.
+void __libc_init_array(void);
+void pw_BoardInit(void);
+int main(void);
+
+void DefaultFaultHandler(void) {
+  while (true) {
+    // Wait for debugger to attach.
+  }
+}
+
+// WARNING: This code is run immediately upon boot, and performs initialization
+// of RAM. Note that code running before this function finishes memory
+// initialization will violate the C spec (Section 6.7.8, paragraph 10 for
+// example, which requires uninitialized static values to be zero-initialized).
+// Be EXTREMELY careful when running code before this function finishes RAM
+// initialization.
+//
+// This function runs immediately at boot because it is at index 1 of the
+// interrupt vector table.
+PW_NO_PROLOGUE void pw_FirmwareInit() {
+  // Begin memory initialization.
+  // Static-init RAM.
+  memcpy(_static_init_ram_start,
+         _static_init_flash_start,
+         _static_init_ram_end - _static_init_ram_start);
+
+  // Zero-init RAM.
+  memset(_zero_init_ram_start, 0, _zero_init_ram_end - _zero_init_ram_start);
+
+  // Call static constructors.
+  __libc_init_array();
+
+  // End memory initialization.
+
+  // Do any necessary board init.
+  pw_BoardInit();
+
+  // Run main.
+  main();
+
+  // In case main() returns, just sit here until the device is reset.
+  while (true) {
+  }
+}
+
+// This is the device's interrupt vector table. It's not referenced in any
+// code because the platform (STM32F4xx) expects this table to be present at the
+// beginning of flash.
+//
+// For more information, see ARMv7-M Architecture Reference Manual DDI 0403E.b
+// section B1.5.3.
+PW_KEEP_IN_SECTION(".vector_table")
+const uint32_t vector_table[] = {
+    // The starting location of the stack pointer.
+    [0] = (uint32_t)_stack_end,
+
+    // Reset handler, dictates how to handle reset interrupt. This is also run
+    // at boot.
+    [1] = (uint32_t)pw_FirmwareInit,
+
+    // NMI handler.
+    [2] = (uint32_t)DefaultFaultHandler,
+    // HardFault handler.
+    [3] = (uint32_t)DefaultFaultHandler,
+};
diff --git a/pw_dumb_io_baremetal_stm32f429/docs.rst b/pw_dumb_io_baremetal_stm32f429/docs.rst
new file mode 100644
index 0000000..5385e90
--- /dev/null
+++ b/pw_dumb_io_baremetal_stm32f429/docs.rst
@@ -0,0 +1,47 @@
+.. _chapter-dumb-io-baremetal-stm32f429:
+
+.. default-domain:: cpp
+
+.. highlight:: sh
+
+---------------------------
+STM32F429 baremetal dumb IO
+---------------------------
+The STM32F429 baremetal dumb IO backend provides device startup code and a UART
+driver to layer that allows applications built against the ``pw_dumb_io``
+interface to run on a STM32F429 chip and do simple input/output via UART.
+The code is optimized for the STM32F429I-DISC1, using USART1 (which is connected
+to the virtual COM port on the embedded ST-LINKv2 chip). However, this should
+work with all STM32F429 variations (and even some STM32F4xx chips).
+
+This backend has no configuration options. The point of it is to provide bare-
+minimum platform code needed to do UART reads/writes.
+
+Setup
+=====
+This module requires relatively minimal setup:
+
+  1. Write code against the ``pw_dumb_io`` facade.
+  2. Specify the ``dir_pw_dumb_io_backend`` GN gloabal variable to point to this
+     backend.
+  3. Build an executable with a main() function using a toolchain that
+     supports Cortex-M4.
+
+.. note::
+  This module provides early firmware init and a linker script, so it will
+  conflict with other modules that do any early device init or provide a linker
+  script.
+
+Module usage
+============
+After building a an executable that utilizes this backend, flash the
+produced .elf binary to the development board. Then, using a serial
+communication terminal (like minicom or screen), connect to the device at a
+baud rate of 115200 (8N1). If you're not using a STM32F429I-DISC1 development
+board, manually connect a USB TLL adapter to pins `A9` (TX) and
+`A10` (RX).
+
+Dependencies
+============
+  * pw_dumb_io facade
+  * pw_preprocessor module
diff --git a/pw_dumb_io_baremetal_stm32f429/dumb_io_baremetal.cc b/pw_dumb_io_baremetal_stm32f429/dumb_io_baremetal.cc
new file mode 100644
index 0000000..110e44c
--- /dev/null
+++ b/pw_dumb_io_baremetal_stm32f429/dumb_io_baremetal.cc
@@ -0,0 +1,193 @@
+// Copyright 2019 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_dumb_io/dumb_io.h"
+#include "pw_preprocessor/compiler.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 = 16000000;
+
+// Base address for everything peripheral-related on the STM32F4xx.
+constexpr uint32_t kPeripheralBaseAddr = 0x40000000u;
+// Base address for everything AHB1-related on the STM32F4xx.
+constexpr uint32_t kAhb1PeripheralBase = kPeripheralBaseAddr + 0x00020000U;
+// Base address for everything APB2-related on the STM32F4xx.
+constexpr uint32_t kApb2PeripheralBase = kPeripheralBaseAddr + 0x00010000U;
+
+// Reset/clock configuration block (RCC).
+// `reserved` fields are unimplemented features, and are present to ensure
+// proper alignment of registers that are in use.
+PW_PACKED(struct) RccBlock {
+  uint32_t reserved1[12];
+  uint32_t ahb1_config;
+  uint32_t reserved2[4];
+  uint32_t apb2_config;
+};
+
+// Mask for ahb1_config (AHB1ENR) to enable the "A" GPIO pins.
+constexpr uint32_t kGpioAEnable = 0x1u;
+
+// Mask for apb2_config (APB2ENR) to enable USART1.
+constexpr uint32_t kUsart1Enable = 0x1u << 4;
+
+// GPIO register block definition.
+PW_PACKED(struct) GpioBlock {
+  uint32_t modes;
+  uint32_t out_type;
+  uint32_t out_speed;
+  uint32_t pull_up_down;
+  uint32_t input_data;
+  uint32_t output_data;
+  uint32_t gpio_bit_set;
+  uint32_t port_config_lock;
+  uint32_t alt_low;
+  uint32_t alt_high;
+};
+
+// Constants related to GPIO mode register masks.
+constexpr uint32_t kGpioPortModeMask = 0x3u;
+constexpr uint32_t kGpio9PortModePos = 18;
+constexpr uint32_t kGpio10PortModePos = 20;
+constexpr uint32_t kGpioPortModeAlternate = 2;
+
+// Constants related to GPIO port speed register masks.
+constexpr uint32_t kGpioPortSpeedMask = 0x3u;
+constexpr uint32_t kGpio9PortSpeedPos = 18;
+constexpr uint32_t kGpio10PortSpeedPos = 20;
+constexpr uint32_t kGpioSpeedVeryHigh = 3;
+
+// Constants related to GPIO pull up/down resistor type masks.
+constexpr uint32_t kGpioPullTypeMask = 0x3u;
+constexpr uint32_t kGpio9PullTypePos = 18;
+constexpr uint32_t kGpio10PullTypePos = 20;
+constexpr uint32_t kPullTypePullUp = 1;
+
+// Constants related to GPIO port speed register masks.
+constexpr uint32_t kGpioAltModeMask = 0x3u;
+constexpr uint32_t kGpio9AltModeHighPos = 4;
+constexpr uint32_t kGpio10AltModeHighPos = 8;
+
+// Alternate function for pins A9 and A10 that enable USART1.
+constexpr uint8_t kGpioAlternateFunctionUsart1 = 0x07u;
+
+// USART status flags.
+constexpr uint32_t kTxRegisterEmpty = 0x1u << 7;
+
+// USART configuration flags for config1 register.
+// Note: a large number of configuration flags have been omitted as they default
+// to sane values and we don't need to change them.
+constexpr uint32_t kReceiveEnable = 0x1 << 2;
+constexpr uint32_t kTransmitEnable = 0x1 << 3;
+constexpr uint32_t kReadDataReady = 0x1u << 5;
+constexpr uint32_t kEnableUsart = 0x1 << 13;
+
+// Layout of memory mapped registers for USART blocks.
+PW_PACKED(struct) UsartBlock {
+  uint32_t status;
+  // Only the lower 8 bits are valid. Read for RX data, write to TX data.
+  uint32_t data_register;
+  uint32_t baud_rate;
+  uint32_t config1;
+  uint32_t config2;
+  uint32_t config3;
+  uint32_t config4;
+};
+
+// Sets the UART baud register using the peripheral clock and target baud rate.
+// These calculations are specific to the default oversample by 16 mode.
+// TODO(amontanez): Document magic calculations in full UART implementation.
+uint32_t CalcBaudRegister(uint32_t clock, uint32_t target_baud) {
+  uint32_t div_fac = (clock * 25) / (4 * target_baud);
+  uint32_t mantissa = div_fac / 100;
+  uint32_t fraction = ((div_fac - mantissa * 100) * 16 + 50) / 100;
+
+  return (mantissa << 4) + (fraction & 0xFFu);
+}
+
+// Declare a reference to the memory mapped RCC block.
+volatile RccBlock& platform_rcc =
+    *reinterpret_cast<volatile RccBlock*>(kAhb1PeripheralBase + 0x3800U);
+
+// Declare a reference to the 'A' GPIO memory mapped block.
+volatile GpioBlock& gpio_a =
+    *reinterpret_cast<volatile GpioBlock*>(kAhb1PeripheralBase + 0x0000U);
+
+// Declare a reference to the memory mapped block for USART1.
+volatile UsartBlock& usart1 =
+    *reinterpret_cast<volatile UsartBlock*>(kApb2PeripheralBase + 0x1000U);
+
+}  // namespace
+
+extern "C" void pw_BoardInit() {
+  // Enable 'A' GIPO clocks.
+  platform_rcc.ahb1_config |= kGpioAEnable;
+
+  // Enable Uart TX pin.
+  // Output type defaults to push-pull (rather than open/drain).
+  gpio_a.modes |= kGpioPortModeAlternate << kGpio9PortModePos;
+  gpio_a.out_speed |= kGpioSpeedVeryHigh << kGpio9PortSpeedPos;
+  gpio_a.pull_up_down |= kPullTypePullUp << kGpio9PullTypePos;
+  gpio_a.alt_high |= kGpioAlternateFunctionUsart1 << kGpio9AltModeHighPos;
+
+  // Enable Uart RX pin.
+  // Output type defaults to push-pull (rather than open/drain).
+  gpio_a.modes |= kGpioPortModeAlternate << kGpio10PortModePos;
+  gpio_a.out_speed |= kGpioSpeedVeryHigh << kGpio10PortSpeedPos;
+  gpio_a.pull_up_down |= kPullTypePullUp << kGpio10PullTypePos;
+  gpio_a.alt_high |= kGpioAlternateFunctionUsart1 << kGpio10AltModeHighPos;
+
+  // Initialize USART1. Initialized to 8N1 at the specified baud rate.
+  platform_rcc.apb2_config |= kUsart1Enable;
+
+  // Warning: Normally the baud rate register calculation is based off
+  // peripheral 2 clock. For this code, the peripheral clock defaults to
+  // the system core clock so it can be used directly.
+  usart1.baud_rate = CalcBaudRegister(kSystemCoreClock, /*target_baud=*/115200);
+
+  usart1.config1 = kEnableUsart | kReceiveEnable | kTransmitEnable;
+}
+
+namespace pw::dumb_io {
+
+// Wait for a byte to read on USART1. 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 GetByte(std::byte* dest) {
+  while (true) {
+    if (usart1.status & kReadDataReady) {
+      *dest = static_cast<std::byte>(usart1.data_register);
+    }
+  }
+  return Status::OK;
+}
+
+// Send a byte over USART1. 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 PutByte(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 (!(usart1.status & kTxRegisterEmpty)) {
+  }
+  usart1.data_register = static_cast<uint32_t>(b);
+  return Status::OK;
+}
+
+}  // namespace pw::dumb_io
diff --git a/pw_dumb_io_baremetal_stm32f429/stm32f429.ld b/pw_dumb_io_baremetal_stm32f429/stm32f429.ld
new file mode 100644
index 0000000..fbdbe77
--- /dev/null
+++ b/pw_dumb_io_baremetal_stm32f429/stm32f429.ld
@@ -0,0 +1,127 @@
+/**
+ * Copyright 2019 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.
+ */
+
+HIDDEN(_min_stack_size = 1K);
+
+/* Note: This technically doesn't set the firmware's entry point. Setting the
+ *       firmware entry point is done by setting vector_table[1] in core_init.c.
+ *       However, this DOES tell the compiler how to optimize when --gc-sections
+ *       is enabled.
+ */
+ENTRY(pw_FirmwareInit)
+
+MEMORY
+{
+  /* Internal Flash */
+  FLASH(rx) : ORIGIN = 0x08000000, LENGTH = 512K
+  /* Internal SRAM */
+  RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 192K
+}
+
+SECTIONS
+{
+
+  /* Main executable code. */
+  .code : ALIGN(8)
+  {
+    /* STM32F4xx expects the vector table to be at the beginning of flash. */
+    KEEP(*(.vector_table))
+
+    . = ALIGN(8);
+    /* Application code. */
+    *(.text)
+    *(.text*)
+    KEEP(*(.init))
+    KEEP(*(.fini))
+
+    . = ALIGN(8);
+    /* Constants.*/
+    *(.rodata)
+    *(.rodata*)
+
+    /* .preinit_array, .init_array, .fini_array are used by libc.
+     * Each section is a list of function pointers that are called pre-main and
+     * post-exit for object initialization and tear-down.
+     * Since the region isn't explicitly referenced, specify KEEP to prevent
+     * link-time garbage collection. SORT is used for sections that have strict
+     * init/de-init ordering requirements. */
+    . = ALIGN(8);
+    PROVIDE_HIDDEN (__preinit_array_start = .);
+    KEEP (*(.preinit_array*))
+    PROVIDE_HIDDEN (__preinit_array_end = .);
+
+    PROVIDE_HIDDEN (__init_array_start = .);
+    KEEP (*(SORT(.init_array.*)))
+    KEEP (*(.init_array*))
+    PROVIDE_HIDDEN (__init_array_end = .);
+
+    PROVIDE_HIDDEN (__fini_array_start = .);
+    KEEP (*(SORT(.fini_array.*)))
+    KEEP (*(.fini_array*))
+    PROVIDE_HIDDEN (__fini_array_end = .);
+  } >FLASH
+
+  /* Used by unwind-arm/ */
+  .ARM : ALIGN(8) {
+    __exidx_start = .;
+    *(.ARM.exidx*)
+    __exidx_end = .;
+  } >FLASH
+
+  /* Explicitly initialized global and static data. (.data)*/
+  .static_init_ram : ALIGN(8)
+  {
+    *(.data)
+    *(.data*)
+    . = ALIGN(8);
+  } >RAM AT> FLASH
+
+  /* Zero initialized global/static data. (.bss)
+   * This section is zero initialized in pw_FirmwareInit(). */
+  .zero_init_ram : ALIGN(8)
+  {
+    *(.bss)
+    *(.bss*)
+    *(COMMON)
+    . = ALIGN(8);
+  } >RAM
+
+  /* Link-time check for stack overlaps. */
+  .stack (NOLOAD) : ALIGN(8)
+  {
+    HIDDEN(_stack_size = ORIGIN(RAM) + LENGTH(RAM) - .);
+    ASSERT(_stack_size >= _min_stack_size, "Error: Not enough RAM for stack.");
+    . = . + _stack_size;
+  } >RAM
+}
+
+/* Symbols used by core_init.c: */
+/* Top of stack to set stack pointer. */
+_stack_end = ORIGIN(RAM) + LENGTH(RAM);
+
+/* Start of .static_init_ram in FLASH. */
+_static_init_flash_start = LOADADDR(.static_init_ram);
+
+/* Region of .static_init_ram in RAM. */
+_static_init_ram_start = ADDR(.static_init_ram);
+_static_init_ram_end = _static_init_ram_start + SIZEOF(.static_init_ram);
+
+/* Region of .zero_init_ram. */
+_zero_init_ram_start = ADDR(.zero_init_ram);
+_zero_init_ram_end = _zero_init_ram_start + SIZEOF(.zero_init_ram);
+
+/* arm-none-eabi expects `end` symbol to point to start of heap for sbrk. */
+PROVIDE (end = _zero_init_ram_end);
diff --git a/pw_vars_default.gni b/pw_vars_default.gni
index b2eed99..80a4a8a 100644
--- a/pw_vars_default.gni
+++ b/pw_vars_default.gni
@@ -24,7 +24,8 @@
   # The name of the GN target type used to build Pigweed executables.
   #
   # If this is a custom template, the .gni file containing the template must
-  # be imported at the top of this file to make it globally available.
+  # be imported at the top of the target configuration file to make it globally
+  # available.
   target_type = "executable"
 
   # Path to an instantiation of the "linker_script" GN template defining the
diff --git a/targets/stm32f429i-disc1/target_config.gni b/targets/stm32f429i-disc1/target_config.gni
new file mode 100644
index 0000000..e6e9d13
--- /dev/null
+++ b/targets/stm32f429i-disc1/target_config.gni
@@ -0,0 +1,49 @@
+# Copyright 2019 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 STM32f429I-DISC1 development board.
+#
+# TODO(amontanez): This target configuration treats
+# pw_dumb_io_baremetal_stm32f429 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_m4_og"
+}
+
+# Executable wrapper that includes some baremetal startup code.
+template("stm32f429i_executable") {
+  target("executable", target_name) {
+    forward_variables_from(invoker, "*")
+    deps += [ dir_pw_dumb_io_baremetal_stm32f429 ]
+  }
+}
+
+# Configuration options for Pigweed executable targets.
+pw_executable_config.target_type = "stm32f429i_executable"
+
+# Path to the bloaty config file for the output binaries.
+pw_executable_config.bloaty_config_file =
+    "$dir_pw_dumb_io_baremetal_stm32f429/bloaty_config.bloaty"
+
+# Path to a linker script target. This must be a target (e.g. source_set)
+# that provides a linker script (through a public config, for example).
+linker_script_target =
+    "$dir_pw_dumb_io_baremetal_stm32f429:linker_script_target"
+
+dir_pw_dumb_io_backend = "$dir_pw_dumb_io_baremetal_stm32f429"