Revert "chromeos-config: Update branch to ToT"

This reverts commit a23e599607a928e89caedae770ad0ffd66891c1c.

Reason for revert:
More changes are needed elsewhere before everything will build correctly.

Original change's description:
> chromeos-config: Update branch to ToT
> 
> This brings the following patches into the grunt firmware branch so
> that the yaml file can be used unchanged.
> 
> b28f6bd6c chromeos-config: Remove unused create_bios_rw_image
> f82770f99 chromeos-config: Change '{main,ec,pd}-image' to '{main,ec,pd}-ro-image'
> e89e072c8 chromeos-config: Remove unused 'extra' and 'tools' properties
> 40f41a430 chromeos-config: Fixing a link that currently 404s.
> d555031f8 chromeos-config: add bread crumbs to cros_config
> 6de63e1f2 chromeos-config: Migrate package to GN
> d9e59e28a chromeos-config: Add runtime integer property support
> bf10ad773 chromeos-config: Add camera count to schema
> 17249c80d chromeos-config: Output generated EC code
> 1c8476370 chromeos-config: Fix issue with dptf-dv param
> 1989ddffe chromeos-config: Support config partials for dump
> e224241fe chromeos-config: Add cras config back to dump
> a69fe2799 chromeos-config: Add bluetooth support
> dc305d03d chromeos-config: Remove unsupported abspath
> 915698e30 chromeos-config: Don't store empty properties
> 1d8fa17db chromeos-config: updating documentation for changing a model.yaml file
> 4c72f9905 chromeos-config: Check build target exists before reading
> 124b652b3 chromeos-config: Better unique error reporting
> 45bae3de7 chromeos-config: Remove device-tree config support
> bb69f09f9 chromeos-config: Remove validate_config support
> 8f7e3b7c6 chromeos-config: Remove device-tree config support
> 2e1d8b5dc chromeos-config: Delete unused v1 readme
> 89a6cafe1 chromeos-config: Fix test schema json format
> 3e368f9fe chromeos-config: Clarify EC code generation
> 5333ccdb9 chromeos-config: Migration mosys bindings compat
> dbcb7a22d chromeos-config: Fix YAML whitelabel logic
> e97bd6bdc chromeos-config: Add brand-code to FirmwareInfo
> aa1a67d8b chromeos-config: Drop RLZ constraint
> eb9ab50bf chromeos-config: Better migration test format
> 832f91b88 chromeos-config: Fix migration test script
> 780385620 chromeos-config: Correct endianness when getting/setting SKU ID
> bdde39845 chromeos-config: Make YAML/DT properties diffable
> 8fdd631ef chromeos-config: FDT backwards compatibility
> ad81a40a1 chromeos-config: FirmwareInfo DT/YAML parity
> 4e8a6f989 chromeos-config: Make error messages easier
> 6d189ce2c chromeos-config: Add runtime bool property support
> 305a41e92 chromeos-config: Add support for named fw payload
> 41fd3fd94 chromeos-config: Add OWNERS file
> da4e6d382 chromeos-config: Delete old test file
> 08a5044b9 chromeos-config: Revert to gyp build
> c8a39de78 chromeos-config: Add firmware name field
> e24e5b5ff chromeos-config: Fix a memory leak
> 99ec4d099 chromeos-config: Add cros_config_test_schema proc
> f14587dd1 chromeos-config: Add ec_config autogen code
> cafdbb1bf chromeos-config: add has-fingerprint-sensor field under hwprops
> acd7566d0 chromeos-config: Add trailing newlines to tests
> 37b802414 chromeos-config: Support sku-id as an identifier for ARM
> d0b2511d5 chromeos-config: Add hwprops field to schema
> 22c4c888f chromeos-config: Support dump of valid properties
> a9f5035f5 chromeos-config: Add merge documentation
> 940021c5f chromeos-config: migrate the package to GN
> bc9c2774e chromeos-config: remove firmware updater script from dtsi config.
> def3eff4a Revert "chromeos-config: Change to regex for ARM DT match"
> 9641b0419 chromeos-config: remove all references to firmware-updater-script, and make firmware/script optional in dtsi script.
> 043978019 chromeos-config: add property for specifying modem firmware variant
> d214b74d8 chromeos-config: Add file equality helper to tests
> af6942c54 chromeos-config: Default --model to CROS_CONFIG_MODEL env variable
> b00b3e753 chromeos-config: Make --model apply to all sub-commands
> f5cdbcbbe chromeos-config: clarify identity docs
> 9504dbe4e chromeos-config: Adjust Fdt implementation for upstream
> 25dbccc7e chromeos-config: Gen all schema properties
> 2fa67a751 chromeos-config: Complete dump fn coverage
> c246cd954 chromeos-config: Remove unused --model flag in test
> ffeeaf004 chromeos-config: Change to regex for ARM DT match
> 359a7aa14 chromeos-config: Remove legacy models reference
> c2448fca8 chromeos-config: Add fw updater script config
> c8b7cf42e chromeos-config: add ec_extras target
> 0134c22a3 chromeos-config: rename unittest to xxx_test.cc
> 11469c112 chromeos-config: parent scope definitions win
> 
> TEST=Build Firmware
> BUG=None
> 
> Change-Id: Ibd729a1e7e8cc28df5b2a5a0a9d0d8ceba1a1395
> Signed-off-by: Martin Roth <martinroth@google.com>
> Reviewed-on: https://chromium-review.googlesource.com/c/1410067
> Reviewed-by: Martin Roth <martinroth@chromium.org>
> Tested-by: Martin Roth <martinroth@chromium.org>

Bug: None
Change-Id: Iceadafa60de35f0c1b7d4efd46551a780147d641
Reviewed-on: https://chromium-review.googlesource.com/c/1409855
Reviewed-by: Martin Roth <martinroth@chromium.org>
Commit-Queue: Martin Roth <martinroth@chromium.org>
Tested-by: Martin Roth <martinroth@chromium.org>
diff --git a/chromeos-config/.gitignore b/chromeos-config/.gitignore
index 9ecedb7..fb483ac 100644
--- a/chromeos-config/.gitignore
+++ b/chromeos-config/.gitignore
@@ -1,3 +1,4 @@
 /dev_mem
 /test*.dtb
+/test_config.json
 /vpd
diff --git a/chromeos-config/BUILD.gn b/chromeos-config/BUILD.gn
deleted file mode 100644
index f1e1416..0000000
--- a/chromeos-config/BUILD.gn
+++ /dev/null
@@ -1,104 +0,0 @@
-# Copyright 2018 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//common-mk/pkg_config.gni")
-
-group("all") {
-  deps = [
-    ":cros_config"
-  ]
-  if (use.test) {
-    deps += [
-      ":cros_config_test",
-      ":cros_config_main_test",
-      ":fake_cros_config_test",
-    ]
-  }
-}
-
-pkg_config("target_defaults") {
-  pkg_deps = [
-    "libchrome-${libbase_ver}",
-  ]
-}
-
-shared_library("libcros_config") {
-  configs += [ ":target_defaults" ]
-  sources = [
-    "libcros_config/cros_config.cc",
-    "libcros_config/cros_config_impl.cc",
-    "libcros_config/cros_config_json.cc",
-    "libcros_config/fake_cros_config.cc",
-    "libcros_config/identity.cc",
-    "libcros_config/identity_arm.cc",
-    "libcros_config/identity_x86.cc",
-  ]
-  libs = [
-    "fdt",
-  ]
-}
-
-pkg_config("cros_config_config") {
-  pkg_deps = [
-    "libbrillo-${libbase_ver}",
-  ]
-}
-
-executable("cros_config") {
-  configs += [
-    ":cros_config_config",
-    ":target_defaults",
-  ]
-  deps = [":libcros_config"]
-  sources = [
-    "cros_config_main.cc",
-  ]
-}
-
-if (use.test) {
-  executable("cros_config_test") {
-    configs += [
-      "//common-mk:test",
-      ":target_defaults",
-    ]
-    include_dirs = [
-      "libcros_config",
-    ]
-    deps = [
-      ":libcros_config",
-    ]
-    sources = [
-      "libcros_config/cros_config_test.cc",
-    ]
-  }
-
-  executable("cros_config_main_test") {
-    configs += [
-      "//common-mk:test",
-      ":target_defaults",
-    ]
-    deps = [
-      ":cros_config",
-    ]
-    sources = [
-      "cros_config_main_test.cc",
-    ]
-  }
-
-  executable("fake_cros_config_test") {
-    configs += [
-      "//common-mk:test",
-      ":target_defaults",
-    ]
-    include_dirs = [
-      "libcros_config",
-    ]
-    deps = [
-      ":libcros_config",
-    ]
-    sources = [
-      "libcros_config/fake_cros_config_test.cc",
-    ]
-  }
-}
diff --git a/chromeos-config/OWNERS b/chromeos-config/OWNERS
deleted file mode 100644
index e6e31b1..0000000
--- a/chromeos-config/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-set noparent
-shapiroc@chromium.org
-gmeinke@chromium.org
diff --git a/chromeos-config/README.md b/chromeos-config/README.md
index c95af09..9acfd7f 100644
--- a/chromeos-config/README.md
+++ b/chromeos-config/README.md
@@ -15,8 +15,8 @@
 
 See [CrosConfig](./libcros_config/cros_config.h) for the class to use to access
 configuration strings on a target. See
-[cros_config_host.py](./cros_config_host/cros_config_host.py) for access to the
-config on a host or during a build.
+[cros_config_host.py](./cros_config_host.py) for access to the config on a host
+or during a build.
 
 ## CLI Usage
 
@@ -92,7 +92,7 @@
         known until the device/product/sku variables come into scope (e.g.
         $sku-id).
 
-3.  File Imports - File imports allow common snippets of YAML to be shared
+2.  File Imports - File imports allow common snippets of YAML to be shared
     across multiple different implementations.  File importing works the same as
     if the YAML files were cat'd together and then evaluated.  File importing is
     recursive also, so it will support importing files that import other files.
@@ -159,43 +159,6 @@
         signature-id: "SomeDevice"
 ```
 
-### YAML Merging (multiple source YAML files)
-
-There are various cases where it makes sense to manage YAML config across
-multiple different repos in separate YAML files.
-
-E.g.
-
-* Permissions based control via git repo access to specific config
-* Extending overlays for device customization (e.g. Moblab)
-
-This is supported through cros_config_schema tool and is invoked as part of the
-chromeos-config ebuild.
-
-Using normal portage ebuilds/config, users can install as many YAML files as
-they wish to be merged together into: /usr/share/chromeos-config/yaml
-
-E.g.
-
-* models.yaml
-* models-private.yaml (private config overlaid on the public config)
-
-These files are then merged together based on their lexicographic name order.
-
-Merging of YAML files applies the following characteristics:
-
-1. Order is important.  If two files supply the same config, the last file wins.
-2. Identity is important.  Config is merged based on ONE OF the following:
-
-   1. name - If the name attribute matches, the config is merged
-   2. identity - Identity can be matched on all or some of the attributes.
-
-For a detailed example of how merging works, see the following test files:
-
-1. [test_merge_base.yaml](https://chromium.googlesource.com/chromiumos/platform2/+/HEAD/chromeos-config/libcros_config/test_merge_base.yaml) - First file passed to the merge routine.
-2. [test_merge_overlay.yaml](https://chromium.googlesource.com/chromiumos/platform2/+/HEAD/chromeos-config/libcros_config/test_merge_overlay.yaml) - Second file passed (winner of any conflicts).
-3. [test_merge.json](https://chromium.googlesource.com/chromiumos/platform2/+/HEAD/chromeos-config/libcros_config/test_merge.json) - Result generated from the merge.
-
 ### YAML Transform (to JSON)
 
 In addition to the templating evaluation discussed above, the YAML is converted
@@ -228,20 +191,6 @@
 * 'product' generally defines identity/branding information only. The main
   reason multiple products are supported is for the whitelabel case.
 
-### Making changes to a YAML model file
-
-When modifying a `model.yaml` file there are few steps that need to be taken to
- manifest the change in a board target. Since the actual work to combine and
- process the YAML files is done in the chromeos-config ebuild, it needs to be
- remerged after the input YAML has been modified.
-
-
-1. Start cros_workon on the ebuild where your source model.yaml lives: `cros_workon start chromeos-base/chrome-config-bsp-{BOARD}`
-1. Make and install your incremental changes: `cros_workon_make chromeos-base/chrome-config-bsp-{BOARD} --install`
-1. Remerge the chromeos-config ebuild: `emerge-$BORAD chromeos-config`
-
-Note: The config-bsp overlay path may be slightly different depending on the board and if it is public or private.
-
 ### Schema Validation
 
 The config is evaluated against a http://json-schema.org/ schema located at:
@@ -266,14 +215,10 @@
 | --------- | ------ | --------- | -------- | ----------- |  ----------- |
 | arc | [arc](#arc) |  | False |  |  |
 | audio | [audio](#audio) |  | False |  |  |
-| bluetooth | [bluetooth](#bluetooth) |  | False |  |  |
-| brand-code | string |  | False |  | Brand code of the model (also called RLZ code). |
-| camera | [camera](#camera) |  | False |  |  |
+| brand-code | string | ```^[A-Z]{4}$``` | False |  | Brand code of the model (also called RLZ code). |
 | firmware | [firmware](#firmware) |  | True |  |  |
 | firmware-signing | [firmware-signing](#firmware-signing) |  | False |  |  |
-| hardware-properties | [hardware-properties](#hardware-properties) |  | False |  | Contains boolean flags for hardware properties of this board, for example if it's convertible, has a touchscreen, has a camera, etc. This information is used to auto-generate C code that is consumed by the EC build process in order to do run-time configuration. If a value is defined within a config file, but not for a specific model, that value will be assumed to be false for that model. All properties must be booleans. If non-boolean properties are desired, the generation code in cros_config_schema.py must be updated to support them. |
 | identity | [identity](#identity) |  | False |  | Defines attributes that are used by cros_config to detect the identity of the platform and which corresponding config should be used. This tuple must either contain x86 properties only or ARM properties only. |
-| modem | [modem](#modem) |  | False |  |  |
 | name | string | ```^[_a-zA-Z0-9]{3,}``` | True |  | Unique name for the given model. |
 | oem-id | string | ```[0-9]+``` | False |  | Some projects store SKU ID, OEM ID and Board Revision in an EEPROM and only SKU ID can be updated in the factory and RMA flow but others should be pre-flashed in the chip level. In this case, we would like to validate whether oem-id here from the updated SKU ID matches the one in the EEPROM so we can prevent this device from being updated to another OEM's devices.  |
 | power | [power](#power) |  | False |  | WARNING -- This config contains unvalidated settings, which is not a correct usage pattern, but this will be used in the interim until a longer term solution can be put in place where the overall schema can be single sourced (for the YAML and C++ that uses it); likely though some type of code generation. SUMMARY -- Contains power_manager device settings.  This is the new mechanism used in lieu of the previous file based implementation (via powerd-prefs). Power manager will first check for a property in this config, else it will revert to the file based mechanism (via the powerd-prefs setting). This provides more flexibility in sharing power settings across different devices that share the same build overlay. Any property can be overridden from - src/platform2/power_manager/default_prefs or src/platform2/power_manager/optional_prefs For details about each setting property, see - src/platform2/power_manager/common/power_constants.h For examples on setting these properties (including multiline examples), see the power config example in libcros_config/test.yaml |
@@ -325,37 +270,19 @@
 | destination | string |  | False |  | Installation path for the file on the system image. |
 | source | string |  | False |  | Source of the file relative to the build system. |
 
-### bluetooth
-| Attribute | Type   | RegEx     | Required | Oneof Group |  Description |
-| --------- | ------ | --------- | -------- | ----------- |  ----------- |
-| config | [config](#config) |  | True |  |  |
-
-### config
-| Attribute | Type   | RegEx     | Required | Oneof Group |  Description |
-| --------- | ------ | --------- | -------- | ----------- |  ----------- |
-| build-path | string |  | True |  | Source of the file relative to the build system. |
-| system-path | string |  | True |  | Installation path for the file on the system image. |
-
-### camera
-| Attribute | Type   | RegEx     | Required | Oneof Group |  Description |
-| --------- | ------ | --------- | -------- | ----------- |  ----------- |
-| count | integer |  | False |  | Specified the number of cameras on the model. |
-
 ### firmware
 | Attribute | Type   | RegEx     | Required | Oneof Group |  Description |
 | --------- | ------ | --------- | -------- | ----------- |  ----------- |
 | bcs-overlay | string |  | False |  | BCS overlay path used to determine BCS file path for binary firmware downloads. |
 | build-targets | [build-targets](#build-targets) |  | False |  |  |
-| ec-image | string |  | False |  | A deprecated string property to be removed after migration. |
-| ec-ro-image | string |  | False |  | Name of the file located in BCS under the respective bcs-overlay. |
+| ec-image | string |  | False |  | Name of the file located in BCS under the respective bcs-overlay. |
+| extra | array - string |  | False |  | A list of extra files or directories needed to update firmware, each being a string filename. Any filename is supported. If it starts with `bcs://` then it is read from BCS as with main-image above. But normally it is a path. A typical example is `${FILESDIR}/extra` which means that the `extra` diectory is copied from the firmware ebuild's `files/extra` directory. Full paths can be provided, e.g. `${SYSROOT}/usr/bin/ectool`. If a directory is provided, its contents are copied (subdirectories are not supported). This mirrors the functionality of `CROS_FIRMWARE_EXTRA_LIST`. |
 | key-id | string |  | False |  | Key ID from the signer key set that is used to sign the given firmware image. |
-| main-image | string |  | False |  | A deprecated string property to be removed after migration. |
-| main-ro-image | string |  | False |  | Name of the file located in BCS under the respective bcs-overlay. |
+| main-image | string |  | False |  | Name of the file located in BCS under the respective bcs-overlay. |
 | main-rw-image | string |  | False |  | Name of the file located in BCS under the respective bcs-overlay. |
-| name | string |  | False |  | This is a human-recognizable name used to refer to the firmware. It will be used when generating the shellball via firmware packer. Mainly, this is only for compatibility testing with device tree (since DT allowed firmwares to be named). |
 | no-firmware | boolean |  | False |  | If present this indicates that this model has no firmware at present. This means that it will be omitted from the firmware updater (chromeos-firmware- ebuild) and it will not be included in the signer instructions file sent to the signer. This option is often useful when a model is first added, since it may not have firmware at that point. |
-| pd-image | string |  | False |  | A deprecated string property to be removed after migration. |
-| pd-ro-image | string |  | False |  | Name of the file located in BCS under the respective bcs-overlay. |
+| pd-image | string |  | False |  | Name of the file located in BCS under the respective bcs-overlay. |
+| tools | array - string |  | False |  | A list of additional tools which should be packed into the firmware update shellball. This is only needed if this model needs to run a special tool to do the firmware update. |
 
 ### build-targets
 | Attribute | Type   | RegEx     | Required | Oneof Group |  Description |
@@ -365,7 +292,6 @@
 | cr50 | string |  | False |  | Build target that will be considered dirty when building/testing locally. |
 | depthcharge | string |  | False |  | Build target that will be considered dirty when building/testing locally. |
 | ec | string |  | False |  | Build target that will be considered dirty when building/testing locally. |
-| ec_extras | array - string |  | False |  | Extra EC build targets to build within chromeos-ec. |
 | libpayload | string |  | False |  | Build target that will be considered dirty when building/testing locally. |
 | u-boot | string |  | False |  | Build target that will be considered dirty when building/testing locally. |
 
@@ -376,33 +302,18 @@
 | sig-id-in-customization-id | boolean |  | False |  | Indicates that this model cannot be decoded by the mapping table. Instead the model is stored in the VPD (Vital Product Data) region in the customization_id property. This allows us to determine the model to use in the factory during the finalization stage. Note that if the VPD is wiped then the model will be lost. This may mean that the device will revert back to a generic model, or may not work. It is not possible in general to test whether the model in the VPD is correct at run-time. We simply assume that it is. The advantage of using this property is that no hardware changes are needed to change one model into another. For example we can create 20 different whitelabel boards, all with the same hardware, just by changing the customization_id that is written into SPI flash. |
 | signature-id | string |  | True |  | ID used to generate keys/keyblocks in the firmware signing output.  This is also the value provided to mosys platform signature for the updater4.sh script. |
 
-### hardware-properties
-| Attribute | Type   | RegEx     | Required | Oneof Group |  Description |
-| --------- | ------ | --------- | -------- | ----------- |  ----------- |
-| has-base-accelerometer | boolean |  | False |  | Is there an accelerometer in the base of the device. |
-| has-base-gyroscope | boolean |  | False |  | Is there a gyroscope in the base of the device. |
-| has-fingerprint-sensor | boolean |  | False |  | Is there a fingerprint sensor on the device. |
-| has-lid-accelerometer | boolean |  | False |  | Is there an accelerometer in the lid of the device. |
-| is-lid-convertible | boolean |  | False |  | Can the lid be rotated 360 degrees. |
-
 ### identity
 | Attribute | Type   | RegEx     | Required | Oneof Group |  Description |
 | --------- | ------ | --------- | -------- | ----------- |  ----------- |
-| customization-id | string |  | False | x86 | 'customization_id' value set in the VPD for non-unibuild Zergs and Whitelabels. Deprecated for use in new products since 2017/07/26. |
+| customization-id | string |  | False | x86 | 'customization-id' value set in the VPD for Zergs and older Whitelabels. |
 | platform-name | string |  | False | x86 | Defines the name that is reported by 'mosys platform name' This is typically the reference design name with the first letter capitalized |
-| sku-id | integer |  | False | x86 | SKU/Board strapping pins configured during board manufacturing. |
+| sku-id | integer |  | False | x86 | [x86] SKU/Board strapping pins configured during board manufacturing. |
 | smbios-name-match | string |  | False | x86 | [x86] Firmware name built into the firmware and reflected back out in the SMBIOS tables. |
-| whitelabel-tag | string |  | False | x86 | 'whitelabel_tag' value set in the VPD, to add Whitelabel branding over an unbranded base model. |
-| customization-id | string |  | False | ARM | 'customization_id' value set in the VPD for non-unibuild Zergs and Whitelabels. Deprecated for use in new products since 2017/07/26. |
+| whitelabel-tag | string |  | False | x86 | 'whitelabel-tag' value set in the VPD for Whitelabels. |
+| customization-id | string |  | False | ARM | 'customization-id' value set in the VPD for Zergs and older Whitelabels. |
 | device-tree-compatible-match | string |  | False | ARM | [ARM] String pattern (partial) that is matched against the contents of /proc/device-tree/compatible on ARM devices. |
 | platform-name | string |  | False | ARM | Defines the name that is reported by 'mosys platform name' This is typically the reference design name with the first letter capitalized |
-| sku-id | integer |  | False | ARM | SKU/Board strapping pins configured during board manufacturing. |
-| whitelabel-tag | string |  | False | ARM | 'whitelabel_tag' value set in the VPD, to add Whitelabel branding over an unbranded base model. |
-
-### modem
-| Attribute | Type   | RegEx     | Required | Oneof Group |  Description |
-| --------- | ------ | --------- | -------- | ----------- |  ----------- |
-| firmware-variant | string |  | False |  | Variant of the modem firmware to be used. This value is read by modemfwd to match against the variant field of a firmware entry in a firmware manifest. In most cases, we simply use the model name as the value. |
+| whitelabel-tag | string |  | False | ARM | 'whitelabel-tag' value set in the VPD for Whitelabels. |
 
 ### power
 | Attribute | Type   | RegEx     | Required | Oneof Group |  Description |
@@ -495,9 +406,446 @@
 *   cros_config_host -c /build/coral/usr/share/chromeos-config/yaml/config.yaml
     get-models
 
-## Device Testing
+## Device Tree - Schema (v1)
 
-To test configuration changes on actual devices use the platform.CrosConfig
-Tast test. This will run cros_config tests for unibuilds and mosys
-for all devices.
-See [HOWTO](https://chromium.googlesource.com/chromiumos/platform/tast-tests/+/HEAD/src/chromiumos/tast/local/bundles/cros/platform/cros_config_testing.md).
+The 2017 unibuild devices were launched under version 1 for chromeos-config. For
+these cases, the v1 schema (below) is still used; however, all new devices
+should be using the v2 schema (YAML based).
+
+We plan to migrate all overlays to v2 as soon as possible. Please do not create
+any new device tree based configs or update the device tree implementation with
+features for overlays that have already been migrated.
+
+*   `family`: Provides family-level configuration settings, which apply to all
+    models in the family.
+
+    *   `audio` (optional): Contains information about audio devices used by
+        this family. Each subnode is defined as a phandle that can be referenced
+        from the model-specific configuration using the `audio-type` property.
+        *   `<audio-type>`: Node containing the audio config for one device
+            type. All filenames referenced are in relation to the ${FILERDIR}
+            directory of the ebuild containing them.
+            *   `cras-config-dir`: Directory to pass to cras for the location of
+                its config files
+            *   `ucm-suffix`: Internal UCM suffix to pass to cras
+            *   `topology-name` (optional): Name of the topology firmware to use
+            *   `card`: Name of the audio 'card'
+            *   `volume`: Template filename of volume curve file
+            *   `dsp-ini`: Template filename of dsp.ini file
+            *   `hifi-conf`: Template filename of the HiFi.conf file
+            *   `alsa-conf`: Template filename of the card configuration file
+            *   `topology-bin` (optional): Template filename of the topology
+                firmware file Template filenames may include the following
+                fields, enclosed in `{...}` defined by the audio node: `card`,
+                `cras-config-dir`, `topology-name`, `ucm-suffix` as well as
+                `model` for the model name. The expansion / interpretation
+                happens in cros_config_host. Other users should not attempt to
+                implement this. The purpose is to avoid having to repeat the
+                filename in each model that uses a particular manufacturer's
+                card, since the naming convention is typically consistent for
+                that manufacturer.
+    *   `arc` (optional): Contains information for the Android container used by
+        this family.
+
+        *   `build-properties` (optional): Contains information that will be set
+            into the Android property system inside the container. Each subnode
+            is defined as a phandle that can be referenced from the
+            model-specific configuration using the `arc-properties-type`
+            property. The information here is not to be used for Chrome OS
+            itself. It is purely for the container environment.
+
+            The Android build fingerprint is generated from these properties.
+            Currently the fingerprint is:
+
+            `google/{product}/{device}:{version_rel}/{id}/{version_inc}:{type}/{tags}`
+
+            Of these, the first three fields come from the properties here. The
+            rest are defined by the build system.
+
+            *   `<arc-properties-type>`: Node containing the base cheets
+                configuration for use by models.
+                *   `product`: Product name to report in `ro.product.name`. This
+                    may be the model name, or it can be something else, to allow
+                    several models to be grouped into one product.
+                *   `device`: Device name to report in `ro.product.device`. This
+                    is often `{product}_cheets` but it can be something else if
+                    desired.
+                *   `oem`: Original Equipment Manufacturer for this model. This
+                    generally means the OEM name printed on the device.
+                *   `marketing-name`: Name of this model as it is called in the
+                    market, reported in `ro.product.model`. This often starts
+                    with `{oem}`.
+                *   `metrics-tag`: Tag to use to track metrics for this model.
+                    The tag can be shared across many models if desired, but
+                    this will result in larger granularity for metrics
+                    reporting. Ideally the metrics system should support
+                    collation of metrics with different tags into groups, but if
+                    this is not supported, this tag can be used to achieve the
+                    same end. This is reported in `ro.product.metrics.tag`.
+                *   `first-api-level`: The first [Android API
+                    level](https://source.android.com/setup/build-numbers) that
+                    this model shipped with.
+
+    *   `power` (optional): Contains information about power devices used by
+        this family. Each subnode is defined as a phandle that can be referenced
+        from the model-specific configuration using the `power-type` property.
+
+        *   `<power-type>`: Node containing the power config for one device
+            type. Each property corresponds to a power_manager preference, more
+            completely documented in
+            [power_manager](https://cs.corp.google.com/chromeos_public/src/platform2/power_manager/common/power_constants.h).
+            *   `charging-ports`: String describing charging port positions.
+            *   `keyboard-backlight-no-als-brightness`: Initial brightness for
+                the keyboard backlight for systems without ambient light
+                sensors, in the range [0.0, 100.0].
+            *   `low-battery-shutdown-percent`: Battery percentage threshold at
+                which the system should shut down automatically, in the range
+                [0.0, 100.0].
+            *   `power-supply-full-factor`: Fraction of the battery's total
+                charge at which it should be reported as full in the UI, in the
+                range (0.0, 1.0].
+            *   `set-wifi-transmit-power-for-tablet-mode`: If true (1), update
+                wifi transmit power when in tablet vs. clamshell mode.
+            *   `suspend-to-idle`: If true (1), suspend to idle by writing
+                freeze to /sys/power/state.
+
+    *   `bcs` (optional): Provides a set of BCS (Binary Cloud Storage) sources
+        which can be used to download files needed by the build system. Each
+        subnode is defined as a phandle that can be referenced from a node which
+        needs access to BCS.
+
+        *   `<bcs-type>`: Node containing information about one BCS tarfile:
+            *   `overlay`: Name of overlay to download from
+            *   `package`: Package subdirectory to download from
+            *   `ebuild-version`: Tarfile version to download. This corresponds
+                to the ebuild version prior to unibuild, but can be any suitable
+                string.
+            *   `tarball`: Template for tarball to download. This can include
+                `{package}` and `{version}`.
+
+    *   `firmware` (optional) : Contains information about firmware versions and
+        files
+
+        *   `script`: Updater script to use. See [the pack_dist
+            directory](https://cs.corp.google.com/chromeos_public/src/platform/firmware/pack_dist)
+            for the scripts. The options are:
+            *   `updater1s.sh`: Only used by mario. Do not use for new boards.
+            *   `updater2.sh`: Only used by x86-alex and x86-zgb. Do not use for
+                new boards.
+            *   `updater3.sh`: Used for various devices shipped around 2012.
+            *   `updater4.sh`: In current use. Supports software sync for the
+                EC.
+            *   `updater5.sh`: In current use. Supports firmware v4
+                (chromeos-ec, vboot2)
+        *   `shared`: Contains information intended to be shared across all
+            models (see firmware discussion under models below)
+            *   `bcs-overlay`: Overlay name containing the firmware binaries.
+                This is used to generate the full path. For example a value of
+                `overlay-reef-private` in the `reef` model means that all files
+                will be of the form
+                `gs://chromeos-binaries/HOME/bcs-reef-private/overlay-reef-private/chromeos-base/chromeos-firmware-reef/<filename>`.
+            *   `build-targets`: Sub-nodes of this define the name of the build
+                artifact produced by a particular software project in the
+                Portage tree.
+                *   `coreboot`: Defines the Kconfig/target used for coreboot and
+                    chromeos-bootimage ebuilds.
+                *   `ec`: Defines the "board" used to generate the ec firmware
+                    blob within the chromeos-ec ebuild.
+                *   `depthcharge`: Defines the model target passed to the
+                    compile phase within the depthcharge ebuild.
+                *   `libpayload`: Not currently used as the libpayload ebuild is
+                    not yet unibuild-aware.
+                *   `cr50` (optional): Defines the model target to build cr50,
+                    if this is used on the platform. This is actually a build
+                    target for the EC but is specified separately to make it
+                    clear that it is a build for a separate device.
+            *   `main-image`: Main image location. This must start with `bcs://`
+                . It refers to a file available in BCS. The file will be
+                unpacked to produce a firmware binary image.
+            *   `main-rw-image` (optional): Main RW (Read/Write) image location.
+                This must start with `bcs://`. It refers to a file available in
+                BCS. The file will be unpacked to produce a firmware binary
+                image.
+            *   `ec-image` (optional): EC (Embedded Controller) image location.
+                This must start with `bcs://` . It refers to a file available in
+                BCS. The file will be unpacked to produce a firmware binary
+                image.
+            *   `pd-image` (optional): PD (Power Delivery controller) image
+                location. This must start with `bcs://` . It refers to a file
+                available in BCS. The file will be unpacked to produce a
+                firmware binary image.
+            *   `stable-main-version` (optional): Version of the stable
+                firmware. On dogfood devices where RO firmware can be updated,
+                we perform a full firmware update if the existing firmware on
+                the device is older than this version. *Deprecation in progress.
+                See crbug.com/70541.*
+            *   `stable-ec-version` (optional): Version of the stable EC
+                firmware. On dogfood devices where RO EC firmware can be
+                updated, we perform a full firmware update if the existing EC
+                firmware on the device is older than this version. *Deprecation
+                in progress. See crbug.com/70541.*
+            *   `stable-pd-version` (optional): Version of the stable PD
+                firmware. On dogfood devices where RO PD firmware can be
+                updated, we perform a full firmware update if the existing PD
+                firmware on the device is older than this version. *Deprecation
+                in progress. See crbug.com/70541.*
+            *   `extra` (optional): A list of extra files or directories needed
+                to update firmware, each being a string filename. Any filename
+                is supported. If it starts with `bcs://` then it is read from
+                BCS as with main-image above. But normally it is a path. A
+                typical example is `${FILESDIR}/extra` which means that the
+                `extra` diectory is copied from the firmware ebuild's
+                `files/extra` directory. Full paths can be provided, e.g.
+                `${SYSROOT}/usr/bin/ectool`. If a directory is provided, its
+                contents are copied (subdirectories are not supported). This
+                mirrors the functionality of `CROS_FIRMWARE_EXTRA_LIST`. But
+                note that multiple files or directories should use a normal
+                device-tree list format, not be separated by semicolon.
+            *   `tools` (optional): A list of additional tools which should be
+                packed into the firmware update shellball. This is only needed
+                if this model needs to run a special tool to do the firmware
+                update.
+            *   `create-bios-rw-image` (optional): If present this indicates
+                that we should re-sign and generate a read-write firmware image.
+                This replaces the `CROS_FIRMWARE_BUILD_MAIN_RW_IMAGE` ebuild
+                variable.
+            *   `no-firmware` (optional): If present this indicates that this
+                model has no firmware at present. This means that it will be
+                omitted from the firmware updater (chromeos-firmware-<board>
+                ebuild) and it will not be included in the signer instructions
+                file sent to the signer. This option is often useful when a
+                model is first added, since it may not have firmware at that
+                point.
+
+    *   `mapping`: (optional): Used to determine the model/sub-model for a
+        particular device. There can be any number of mappings. At present only
+        a `sku-map` is allowed.
+
+        *   `sku-map`: Provides a mapping from SKU ID to model/sub-model. One of
+            `simple-sku-map` or `single-sku` must be provided.
+            `smbios-name-match` is needed only if the family supports models
+            which have SKU ID conflicts and needs the SMBIOS name to
+            disambiguate them. This is common when migrating legacy boards to
+            unified builds, but may also occur if the SKU ID mapping is not used
+            for some reason.
+            *   `platform-name`: Indicates the platform name for this platform.
+                This is reported by 'mosys platform name'. It is typically the
+                family name with the first letter capitalized.
+            *   `smbios-name-match` (optional) Indicates the smbios name that
+                this table mapping relates to. This map will be ignored on
+                models which don't have a matching smbios name.
+            *   `simple-sku-map` (optional): Provides a simple mapping from SKU
+                (an integer value) to model / sub-model. Each entry consists of
+                a sku value (typically 0-255) and a phandle pointing to the
+                model or sub-model.
+            *   `single-sku` (optional): Used in cases where only a single model
+                is supported by this mapping. In other words, if the SMBIOS name
+                matches, this is the model to use. The value is a phandle
+                pointing to the model (it cannot point to a sub-model).
+
+    *   `touch` (optional): Contains information about touch devices used by
+        this family. Each node is defined as a Phandle that can be referenced
+        from the model-specific configuration using the `touch-type` property.
+
+        *   `vendor`: Name of vendor.
+        *   `firmware-bin`: Template filename to use for vendor firmware binary.
+            The file is installed into `/opt/google/touch`.
+        *   `firmware-symlink`: Template filename to use for the /lib/firmware
+            symlink to the firmware file in `/opt/google/touch`. The
+            `/lib/firmware` part is assumed.
+        *   `bcs-type` (optional): phandle pointing to the BCS node to use to
+            obtain a tarfile containing the firmware.
+
+        Template filenames may include the following fields, enclosed in `{...}`
+        defined by the touch node: `vendor`, `pid`, `version` as well as `model`
+        for the model name. The expansion / interpretation happens in
+        cros_config. Other users should not attempt to implement this. The
+        purpose is to avoid having to repeat the filename in each model that
+        uses a particular manufacturer's touchscreen, since the naming
+        convention is typically consistent for that manufacturer.
+
+*   `models`: Sub-nodes of this define models supported by this board.
+
+    *   `<model name>`: actual name of the model being defined, e.g. `reef` or
+        `pyro`
+
+        *   `arc` (optional): Contains arc++ configuration information
+            *   `hw-features`: Script filename that configures the Arc++
+                hardware features (by probing or hard-coding) for a model.
+            *   `build-properties` (optional): Contains information for the
+                Android container system properties used by this model.
+                Properties here are the same as in the family node above, with
+                one addition to provide common values:
+                *   `arc-properties-type`: Phandle pointing to a subnode of the
+                    family arc build-properties configuration.
+        *   `audio` (optional): Contains information about audio devices used by
+            this model.
+
+            *   `<audio_system>`: Contains information about a particular audio
+                device used by this model. Valid values for the package name
+                are:
+
+                *   `main`: The main audio system
+
+                For each of these:
+
+                *   `audio-type`: Phandle pointing to a subnode of the family
+                    audio configuration.
+
+                All properties defined by the family subnode can be used here.
+                Typically it is enough to define only `cras-config-dir`,
+                `ucm-suffix` and `topology-name`. The rest are generally defined
+                in terms of these, within the family configuration nodes.
+
+        *   `brand-code`: (optional): Brand code of the model (also called RLZ
+            code). See [list](go/chromeos-rlz) and
+            [one-pager](gi/chromeos-rlz-onepager).
+
+        *   `default` (optional): Indicates that all of the nodes and properties
+            of this model should default to the same as another model. The value
+            is a phandle pointing to the model. It is not possible to 'remove'
+            nodes / properties defined by the other model. It is only possible
+            to change properties or add new ones. Note: This is an experimental
+            feature which will be evaluated in December 2017 to determine its
+            usefulness versus the potential confusion it can cause.
+
+        *   `thermal`(optional): Contains information about thermal properties
+            and settings.
+
+            *   `dptf-dv`: Filename of the .dv file containing DPTF (Dynamic
+                Platform and Thermal Framework) settings, relative to the
+                ebuild's FILESDIR.
+
+        *   `touch` (optional): Contains information about touch devices such as
+            touchscreens, touchpads, stylus.
+
+            *   `present` (optional): Indicates whether this model has a
+                touchscreen. This is used by the ARC++ system to pass
+                information to Android, for example. Valid values are:
+                *   `no`: This model does not have a touchscreen (default)
+                *   `yes`: This model has a touchscreen
+                *   `probe`: This model might have a touchscreen but we need to
+                    probe at run-time to find out. This should ideally only be
+                    needed on legacy devices which were not shipped with
+                    unibuild.
+            *   `probe-regex` (optional): Indicates the regular expression that
+                should be used to match again device names in
+                `sys/class/input/input*/name`. If the expression matches any
+                device then the touchscreen is assumed to exist.
+            *   `<device_type>` (optional): Contains information about touch
+                firmware packages. Valid values for package_name are:
+
+                *   `stylus` - a pen-like device with a sensor on or behind the
+                    display which together provide absolute positions with
+                    respect to the display
+                *   `touchpad` - a touch surface separate from the display
+                *   `touchscreen` - a transparent touch surface on a display
+                    which provides absolute positions with respect to the
+                    display
+
+                You can use unit values (`touchscreen@0`, `touchscreen@1`) to
+                allow multiple devices of the same type on a model.
+
+                For each of these:
+
+                *   `touch-type`: Phandle pointing to the `touch` node in the
+                    Family configuration. This allows the vendor name and
+                    default firmware file template to be defined.
+                *   `pid`: Product ID string, as defined by the vendor.
+                *   `version`: Version string, as defined by the vendor.
+                *   `firmware-bin` (optional): Filename of firmware file. See
+                    the Family `touch` node above for the format. If not
+                    specified then the firmware-bin property from touch-type is
+                    used.
+                *   `firmware-symlink`: Filename of firmware file within
+                    /lib/firmware on the device. See the Family `touch` node
+                    above for the format.
+
+        *   `wallpaper` (optional): base filename of the default wallpaper to
+            show on this device. The base filename points `session_manager` to
+            two files in the `/usr/share/chromeos-assets/wallpaper/<wallpaper>`
+            directory: `/[filename]_[small|large].jpg`. If these files are
+            missing or the property does not exist, "default" is used.
+
+        *   `whitelabel` (optional): Sometimes models are so similar that we do
+            not want to have separate settings. This happens in particular with
+            'white-label' devices, where the same device is shipped by several
+            OEMs under difference brands. This is a phandle pointing to another
+            model whose configuration is shared. All settings (except for a very
+            few exceptions) will then come from the shares node. Currently if
+            this properly is used, then only the `firmware { key-id }`,
+            `brand-code` and `wallpaper` propertles can be provided. All other
+            properties will come from the shared model.
+
+        *   `firmware` (optional) : Contains information about firmware versions
+            and files. The properties and nodes inside this node are exactly the
+            same as family/firmware/shared. By convention, tools looking for
+            firmware properties for a model will fallback to the family-level
+            firmware/shared configuration if the node or property is not found
+            at the model level.
+
+            *   `shares`(optional): Phandle pointing to the firmware to use for
+                this model. This is a list with a single phandle, pointing to
+                the firmware node of another model. The presence of this
+                property indicates that this model does not have separate
+                firmware although it may have its own keyset. This property is
+                used to share firmware across multiple models where hardware
+                differences are small and we can detect the model from board ID
+                pins. At this time, only a phandle reference to a subnode of
+                family/firmware is supported. There are no restrictions on the
+                phandle target node naming. Note that this property cannot be
+                provided if the model configuration is shared at the model level
+                (the `whitelabel` property under `<model_name>`).
+            *   `key-id` (optional): Unique ID that matches which key will be
+                used in for firmware signing as part of vboot. For context, see
+                go/cros-unibuild-signing
+            *   `sig-id-in-customization-id` (optional): Indicates that this
+                model cannot be decoded by the mapping table. Instead the model
+                is stored in the VPD (Vital Product Data) region in the
+                customization_id property. This allows us to determine the model
+                to use in the factory during the finalization stage. Note that
+                if the VPD is wiped then the model will be lost. This may mean
+                that the device will revert back to a generic model, or may not
+                work. It is not possible in general to test whether the model in
+                the VPD is correct at run-time. We simply assume that it is. The
+                advantage of using this property is that no hardware changes are
+                needed to change one model into another. For example we can
+                create 20 different whitelabel boards, all with the same
+                hardware, just by changing the customization_id that is written
+                into SPI flash.
+
+        *   `powerd-prefs` (optional): Name of a subdirectory under the powerd
+            model_specific prefs directory where model-specific prefs files are
+            stored.
+
+        *   `test-label` (optional): Test label applied to DUTs in the lab. In
+            Autotest, this will be the model label. By allowing an alternate
+            label, different models can be shared for testing purposes.
+
+        *   `ui` (optional): Config related to operation of the UI on this
+            model.
+
+            *   `power-button` (optional): Defines the position on the screen
+                where the power-button menu appears after a long press of the
+                power button on a tablet device. The position is defined
+                according to the 'landscape-primary' orientation, so that if the
+                device is rotated, the button position on the screen will follow
+                the rotation.
+
+                *   `edge`: Indicates which edge the power button is anchored
+                    to. Can be "left", "right", "top", "bottom". For example,
+                    "left" means that the menu will appear on the left side of
+                    the screen, with the distance along that edge (top to
+                    bottom) defined by the next property.
+                *   `position': Indicates the position of the menu along that
+                    edge, as a fraction, measured from the top or left of the
+                    screen. For example, "0.3" means that the menu will be 30%
+                    of the way from the origin (which is the left or top of the
+                    screen).
+
+        *   `oem-id` (optional): Some projects store SKU ID, OEM ID and Board
+            Revision in an EEPROM and only SKU ID can be updated in the factory
+            and RMA flow but others should be pre-flashed in the chip level. In
+            this case, we would like to validate whether oem-id here from the
+            updated SKU ID matches the one in the EEPROM so we can prevent this
+            device from being updated to another OEM's models.
diff --git a/chromeos-config/chromeos-config-test-setup.sh b/chromeos-config/chromeos-config-test-setup.sh
index d68e070..38ddf0a 100755
--- a/chromeos-config/chromeos-config-test-setup.sh
+++ b/chromeos-config/chromeos-config-test-setup.sh
@@ -6,6 +6,12 @@
 
 set -e
 
+# Compile the test .dts files.
+args="-I dts -O dtb -Wno-unit_address_vs_reg "
+dtc ${args} -o test.dtb libcros_config/test.dts
+dtc ${args} -o test_bad_struct.dtb libcros_config/test_bad_struct.dts
+dtc ${args} -o test_bad_default.dtb libcros_config/test_bad_default.dts
+
 # Copy the json test config locally
 cp libcros_config/test.json test.json
 cp libcros_config/test_arm.json test_arm.json
diff --git a/chromeos-config/chromeos-config.gyp b/chromeos-config/chromeos-config.gyp
new file mode 100644
index 0000000..7347f1a
--- /dev/null
+++ b/chromeos-config/chromeos-config.gyp
@@ -0,0 +1,122 @@
+# TODO: Rename these files to pass this check.
+# gyplint: disable=GypLintSourceFileNames
+
+{
+  'target_defaults': {
+    'variables': {
+      'deps': [
+        'libchrome-<(libbase_ver)',
+      ],
+    },
+  },
+  'targets' : [
+    {
+      'target_name': 'libcros_config',
+      'type': 'shared_library',
+      'sources': [
+        'libcros_config/cros_config.cc',
+        'libcros_config/cros_config_fdt.cc',
+        'libcros_config/cros_config_impl.cc',
+        'libcros_config/cros_config_json.cc',
+        'libcros_config/fake_cros_config.cc',
+        'libcros_config/identity.cc',
+        'libcros_config/identity_arm.cc',
+        'libcros_config/identity_x86.cc',
+      ],
+      'link_settings': {
+        'libraries': [
+          '-lfdt',
+        ],
+      },
+    },
+    {
+      'target_name': 'cros_config',
+      'type': 'executable',
+      'variables': {
+        'deps': [
+          'libbrillo-<(libbase_ver)',
+        ],
+      },
+      'dependencies': ['libcros_config'],
+      'sources': [
+        'cros_config_main.cc',
+      ],
+    },
+  ],
+  'conditions': [
+    ['USE_test == 1', {
+      'targets': [
+        {
+          'target_name': 'cros_config_unittest',
+          'type': 'executable',
+          'includes': ['../common-mk/common_test.gypi'],
+          'include_dirs': [
+            'libcros_config',
+          ],
+          'dependencies': [
+            'libcros_config',
+          ],
+          'sources': [
+            'libcros_config/cros_config_unittest.cc',
+          ],
+        },
+        {
+          'target_name': 'cros_config_json_unittest',
+          'type': 'executable',
+          'defines': [
+            'USE_JSON',
+          ],
+          'includes': ['../common-mk/common_test.gypi'],
+          'include_dirs': [
+            'libcros_config',
+          ],
+          'dependencies': [
+            'libcros_config',
+          ],
+          'sources': [
+            'libcros_config/cros_config_unittest.cc',
+          ],
+        },
+        {
+          'target_name': 'cros_config_main_unittest',
+          'type': 'executable',
+          'includes': ['../common-mk/common_test.gypi'],
+          'dependencies': [
+            'cros_config',
+          ],
+          'sources': [
+            'cros_config_main_unittest.cc',
+          ],
+        },
+        {
+          'target_name': 'cros_config_main_json_unittest',
+          'type': 'executable',
+          'defines': [
+            'USE_JSON',
+          ],
+          'includes': ['../common-mk/common_test.gypi'],
+          'dependencies': [
+            'cros_config',
+          ],
+          'sources': [
+            'cros_config_main_unittest.cc',
+          ],
+        },
+        {
+          'target_name': 'fake_cros_config_unittest',
+          'type': 'executable',
+          'includes': ['../common-mk/common_test.gypi'],
+          'include_dirs': [
+            'libcros_config',
+          ],
+          'dependencies': [
+            'libcros_config',
+          ],
+          'sources': [
+            'libcros_config/fake_cros_config_unittest.cc',
+          ],
+        },
+      ],
+    }],
+  ],
+}
diff --git a/chromeos-config/cros_config_host/cros_config_host.py b/chromeos-config/cros_config_host/cros_config_host.py
index 556c9fa..e49fa0b 100755
--- a/chromeos-config/cros_config_host/cros_config_host.py
+++ b/chromeos-config/cros_config_host/cros_config_host.py
@@ -14,10 +14,11 @@
 
 import argparse
 import json
-import os
 import sys
 
 from libcros_config_host import CrosConfig
+from libcros_config_host_fdt import CrosConfigFdt
+from libcros_config_host import FORMAT_YAML
 
 def DumpConfig(config):
   """Dumps all of the config to stdout
@@ -103,21 +104,6 @@
     print(files.source)
     print(files.dest)
 
-def GetBluetoothFiles(config):
-  """Print a list of bluetooth files across all devices
-
-  The output is one line for the source file and one line for the install file,
-  e.g.:
-     bluetooth/main.conf
-     /etc/bluetooth/main.conf
-
-  Args:
-    config: A CrosConfig instance
-  """
-  for files in config.GetBluetoothFiles():
-    print(files.source)
-    print(files.dest)
-
 def GetFirmwareBuildTargets(config, target_type):
   """Lists all firmware build-targets of the given type, for all models.
 
@@ -169,6 +155,47 @@
     for value in target_values:
       print(value)
 
+def WriteTargetDirectories():
+  """Writes out a file containing the directory target info"""
+  target_dirs = CrosConfigFdt.GetTargetDirectories()
+  print('''/*
+ * This is a generated file, DO NOT EDIT!'
+ *
+ * This provides a map from property name to target directory for all PropFile
+ * objects defined in the schema.
+ */
+
+/ {
+\tchromeos {
+\t\tschema {
+\t\t\ttarget-dirs {''')
+  for name in sorted(target_dirs.keys()):
+    print('\t\t\t\t%s = "%s";' % (name, target_dirs[name]))
+  print('''\t\t\t};
+\t\t};
+\t};
+};
+''')
+
+def WritePhandleProperties():
+  """Writes out a file containing the directory target info"""
+  phandle_props = CrosConfigFdt.GetPhandleProperties()
+  quoted = ['"%s"' % prop for prop in sorted(phandle_props)]
+  print('''/*
+ * This is a generated file, DO NOT EDIT!'
+ *
+ * This provides a list of property names which are used as phandles in the
+ * schema.
+ */
+
+/ {
+\tchromeos {
+\t\tschema {
+\t\t\tphandle-properties = %s;
+\t\t};
+\t};
+};
+''' % (', '.join(quoted)))
 
 def GetWallpaperFiles(config):
   """Get the wallpaper files needed for installation
@@ -197,8 +224,7 @@
                       help='Override the master config file path. Use - for '
                            'stdin.')
   parser.add_argument('-m', '--model', type=str,
-                      help='Which model to run the subcommand on. Defaults to '
-                           'CROS_CONFIG_MODEL environment variable.')
+                      help='Which model to run the subcommand on.')
   subparsers = parser.add_subparsers(dest='subcommand')
   subparsers.add_parser(
       'dump-config',
@@ -237,11 +263,6 @@
       'get-audio-files',
       help='Lists pairs of audio files in sequence: first line is ' +
       'the source file, second line is the full install pathname')
-  # Parser: get-bluetooth-files
-  subparsers.add_parser(
-      'get-bluetooth-files',
-      help='Lists pairs of bluetooth files in sequence: first line is ' +
-           'the source file, second line is the full install pathname')
   # Parser: get-firmware-build-targets
   build_target_parser = subparsers.add_parser(
       'get-firmware-build-targets',
@@ -298,28 +319,32 @@
   if argv is None:
     argv = sys.argv[1:]
   opts = parser.parse_args(argv)
-
-  if not opts.model and 'CROS_CONFIG_MODEL' in os.environ:
-    opts.model = os.environ['CROS_CONFIG_MODEL']
-
-  config = CrosConfig(opts.config, model_filter_regex=opts.model)
-  # Get all models we are invoking on (if any).
-  if opts.model and not config.GetDeviceConfigs():
-    print("Unknown model '%s'" % opts.model, file=sys.stderr)
+  if opts.subcommand == 'write-target-dirs':
+    WriteTargetDirectories()
     return
+  elif opts.subcommand == 'write-phandle-properties':
+    WritePhandleProperties()
+    return
+  config = CrosConfig(opts.config)
+  # Get all models we are invoking on (if any).
+  model = None
+  if opts.model:
+    for device in config.GetDeviceConfigs():
+      if device.GetName() == opts.model:
+        model = device
+    if not model:
+      print("Unknown model '%s'" % opts.model, file=sys.stderr)
+      return
   # Main command branch
   if opts.subcommand == 'list-models':
     ListModels(config)
   elif opts.subcommand == 'dump-config':
     DumpConfig(config)
   elif opts.subcommand == 'get':
-    if not opts.model:
+    if not model:
       print('You must specify --model for this command. See --help for more '
             'info.', file=sys.stderr)
       return
-    # There are multiple configs per model. Not sure how correct it is to pick
-    # just the first one.
-    model = config.GetDeviceConfigs()[0]
     GetProperty(model, opts.path, opts.prop)
   elif opts.subcommand == 'get-touch-firmware-files':
     GetTouchFirmwareFiles(config)
@@ -329,8 +354,6 @@
     GetArcFiles(config)
   elif opts.subcommand == 'get-audio-files':
     GetAudioFiles(config)
-  elif opts.subcommand == 'get-bluetooth-files':
-    GetBluetoothFiles(config)
   elif opts.subcommand == 'get-firmware-build-targets':
     GetFirmwareBuildTargets(config, opts.type)
   elif opts.subcommand == 'get-thermal-files':
diff --git a/chromeos-config/cros_config_host/cros_config_host_unittest.py b/chromeos-config/cros_config_host/cros_config_host_unittest.py
index f81d1bd..d14f333 100755
--- a/chromeos-config/cros_config_host/cros_config_host_unittest.py
+++ b/chromeos-config/cros_config_host/cros_config_host_unittest.py
@@ -12,15 +12,14 @@
 import subprocess
 import unittest
 
+import fdt_util
+
 CLI_FILE = 'python -m cros_config_host.cros_config_host'
+DTS_FILE = '../libcros_config/test.dts'
 YAML_FILE = '../libcros_config/test.yaml'
 
 
-class CrosConfigHostTest(unittest.TestCase):
-  """Tests for master configuration in yaml format"""
-  def setUp(self):
-    self.conf_file = os.path.join(os.path.dirname(__file__), YAML_FILE)
-
+class CommonTests(object):
   """Common tests shared between the YAML and FDT implementations."""
   def CheckManyLinesWithoutSpaces(self, output, lines=3):
     # Expect there to be a few lines
@@ -47,20 +46,6 @@
     output = subprocess.check_output(call_args)
     self.CheckManyLinesWithoutSpaces(output, lines=2)
 
-  def testListModelsWithFilter(self):
-    call_args = '{} -c {} --model=another list-models'.format(
-        CLI_FILE, self.conf_file).split()
-    output = subprocess.check_output(call_args)
-    self.assertEqual("another\n", output)
-
-  def testListModelsWithEnvFilter(self):
-    call_args = '{} -c {} list-models'.format(
-        CLI_FILE, self.conf_file).split()
-    os.environ['CROS_CONFIG_MODEL'] = 'another'
-    output = subprocess.check_output(call_args)
-    del os.environ['CROS_CONFIG_MODEL']
-    self.assertEqual("another\n", output)
-
   def testGetPropSingle(self):
     call_args = '{} -c {} --model=another get / wallpaper'.format(
         CLI_FILE, self.conf_file).split()
@@ -87,7 +72,7 @@
     self.assertEqual(output, os.linesep)
 
   def testGetFirmwareUris(self):
-    call_args = '{} -c {} get-firmware-uris'.format(
+    call_args = '{} -c {} --model=another get-firmware-uris'.format(
         CLI_FILE, self.conf_file).split()
     output = subprocess.check_output(call_args)
     self.CheckManyLines(output)
@@ -104,12 +89,6 @@
     output = subprocess.check_output(call_args)
     self.CheckManyLines(output, 10)
 
-  def testGetBluetoothFiles(self):
-    call_args = '{} -c {} get-bluetooth-files'.format(
-        CLI_FILE, self.conf_file).split()
-    output = subprocess.check_output(call_args)
-    self.CheckManyLines(output, 1)
-
   def testGetFirmwareBuildTargets(self):
     call_args = '{} -c {} get-firmware-build-targets coreboot'.format(
         CLI_FILE, self.conf_file).split()
@@ -123,6 +102,60 @@
     self.CheckManyLines(output, 1)
 
 
+class CrosConfigHostTestFdt(unittest.TestCase, CommonTests):
+  """Tests for master configuration in device-tree format"""
+  def setUp(self):
+    path = os.path.join(os.path.dirname(__file__), DTS_FILE)
+    (self.conf_file, self.temp_file) = fdt_util.EnsureCompiled(path)
+
+  def tearDown(self):
+    os.remove(self.temp_file.name)
+
+  def testReadStdin(self):
+    call_args = '{} -c - list-models < {}'.format(CLI_FILE, self.conf_file)
+    output = subprocess.check_output(call_args, shell=True)
+    self.CheckManyLinesWithoutSpaces(output, lines=2)
+
+  def testListModelsInvalid(self):
+    call_args = '{} -c invalid.dtb list-models'.format(CLI_FILE).split()
+    with open(os.devnull, 'w') as devnull:
+      with self.assertRaises(subprocess.CalledProcessError):
+        subprocess.check_call(call_args, stdout=devnull, stderr=devnull)
+
+  def testWriteTargetDirectories(self):
+    """Test that we can write out a list of file paths"""
+    call_args = '{} write-target-dirs'.format(CLI_FILE).split()
+    output = subprocess.check_output(call_args)
+    lines = [line for line in output.splitlines()]
+    # Just check for one line, of the form:
+    #   alsa-conf = "/usr/share/alsa/ucm";
+    alsa_lines = [line for line in lines if 'alsa-conf' in line]
+    self.assertEqual(len(alsa_lines), 1)
+    m = re.match(r'\s+([-a-z]+) = "(.*)";', alsa_lines[0])
+    name, value = m.groups()
+    self.assertEqual(name, 'alsa-conf')
+    self.assertEqual(value, '/usr/share/alsa/ucm')
+
+  def testWritePhandleProperties(self):
+    """Test that we can write out a list of phandle properties"""
+    call_args = '{} write-phandle-properties'.format(CLI_FILE).split()
+    output = subprocess.check_output(call_args)
+    lines = [line for line in output.splitlines()]
+    # Find the line of the form:
+    #   phandle-properties = ...;
+    phandle_props = [line for line in lines if 'phandle-properties' in line]
+    self.assertEqual(len(phandle_props), 1)
+    m = re.match(r'.* = "(.*)";', phandle_props[0])
+    self.assertTrue(m != None)
+    props = m.group(0).split('", "')
+    self.assertTrue(len(props) > 5)
+
+
+class CrosConfigHostTestYaml(unittest.TestCase, CommonTests):
+  """Tests for master configuration in yaml format"""
+  def setUp(self):
+    self.conf_file = os.path.join(os.path.dirname(__file__), YAML_FILE)
+
 
 if __name__ == '__main__':
   unittest.main()
diff --git a/chromeos-config/cros_config_host/cros_config_schema.py b/chromeos-config/cros_config_host/cros_config_schema.py
index 560cf9c..aa1dff3 100755
--- a/chromeos-config/cros_config_host/cros_config_schema.py
+++ b/chromeos-config/cros_config_host/cros_config_schema.py
@@ -1,5 +1,4 @@
 #!/usr/bin/env python2
-# -*- coding: utf-8 -*-
 # Copyright 2017 The Chromium OS Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
@@ -10,15 +9,13 @@
 import argparse
 import collections
 import copy
-from jinja2 import Template
 import json
+from jsonschema import validate
 import os
 import re
 import sys
 import yaml
 
-import  libcros_schema
-
 this_dir = os.path.dirname(__file__)
 
 CHROMEOS = 'chromeos'
@@ -28,21 +25,34 @@
 SKUS = 'skus'
 CONFIG = 'config'
 BUILD_ONLY_ELEMENTS = [
-  '/arc/files',
-  '/audio/main/files',
-  '/bluetooth/config/build-path',
-  '/firmware',
-  '/firmware-signing',
-  '/thermal/files',
-  '/touch/files',
+    '/firmware', '/firmware-signing', '/audio/main/files', '/touch/files',
+    '/arc/files', '/thermal/files'
 ]
 BRAND_ELEMENTS = ['brand-code', 'firmware-signing', 'wallpaper']
 TEMPLATE_PATTERN = re.compile('{{([^}]*)}}')
 
-EC_OUTPUT_NAME = 'ec_config'
-MOSYS_OUTPUT_NAME = 'config.c'
-TEMPLATE_DIR = 'templates'
-TEMPLATE_SUFFIX = '.jinja2'
+
+def GetNamedTuple(mapping):
+  """Converts a mapping into Named Tuple recursively.
+
+  Args:
+    mapping: A mapping object to be converted.
+
+  Returns:
+    A named tuple generated from mapping
+  """
+  if not isinstance(mapping, collections.Mapping):
+    return mapping
+  new_mapping = {}
+  for k, v in mapping.iteritems():
+    if type(v) is list:
+      new_list = []
+      for val in v:
+        new_list.append(GetNamedTuple(val))
+      new_mapping[k.replace('-', '_').replace('@', '_')] = new_list
+    else:
+      new_mapping[k.replace('-', '_').replace('@', '_')] = GetNamedTuple(v)
+  return collections.namedtuple('Config', new_mapping.iterkeys())(**new_mapping)
 
 
 def MergeDictionaries(primary, overlay):
@@ -62,7 +72,7 @@
       primary[overlay_key] = overlay_value
     elif isinstance(overlay_value, collections.Mapping):
       MergeDictionaries(primary[overlay_key], overlay_value)
-    elif isinstance(overlay_value, list):
+    elif type(overlay_value) is list:
       primary[overlay_key].extend(overlay_value)
     else:
       primary[overlay_key] = overlay_value
@@ -104,9 +114,9 @@
       help='Output file that will be generated by the transform (system file)')
   parser.add_argument(
       '-g',
-      '--generated_c_output_directory',
+      '--generated_c_output',
       type=str,
-      help='Directory where generated C config code should be placed')
+      help='Output file that will contain generated c bindings of the config.')
   parser.add_argument(
       '-f',
       '--filter',
@@ -123,15 +133,16 @@
     template_input: A mapping object to be walked.
     template_vars: A mapping object built up while walking the template_input.
   """
-  to_add = {}
+  to_walk = []
   for key, val in template_input.iteritems():
     if isinstance(val, collections.Mapping):
-      _SetTemplateVars(val, template_vars)
-    elif not isinstance(val, list):
-      to_add[key] = val
+      to_walk.append(val)
+    elif type(val) is not list:
+      template_vars[key] = val
 
-  # Do this last so all variables from the parent scope win.
-  template_vars.update(to_add)
+  # Do this last so all variables from the parent are in scope first.
+  for val in to_walk:
+    _SetTemplateVars(val, template_vars)
 
 
 def _GetVarTemplateValue(val, template_input, template_vars):
@@ -227,7 +238,7 @@
     val = template_input[key]
     if isinstance(val, collections.Mapping):
       _DeleteTemplateOnlyVars(val)
-    elif isinstance(val, list):
+    elif type(val) is list:
       for v in val:
         if isinstance(v, collections.Mapping):
           _DeleteTemplateOnlyVars(v)
@@ -252,7 +263,7 @@
       return True
 
 
-def TransformConfig(config, model_filter_regex=None):
+def TransformConfig(config):
   """Transforms the source config (YAML) to the target system format (JSON)
 
   Applies consistent transforms to covert a source YAML configuration into
@@ -260,7 +271,6 @@
 
   Args:
     config: Config that will be transformed.
-    model_filter_regex: Only returns configs that match the filter
 
   Returns:
     Resulting JSON output from the transform.
@@ -290,32 +300,15 @@
   else:
     configs = json_config[CHROMEOS][CONFIGS]
 
-  if model_filter_regex:
-    matcher = re.compile(model_filter_regex)
-    configs = [
-        config for config in configs if matcher.match(config['name'])
-    ]
-
   # Drop everything except for configs since they were just used as shared
   # config in the source yaml.
-  json_config = {
-      CHROMEOS: {
-          CONFIGS: configs,
-      },
-  }
+  json_config = {CHROMEOS: {CONFIGS: configs}}
 
-  return libcros_schema.FormatJson(json_config)
+  return _FormatJson(json_config)
 
-def _IsLegacyMigration(platform_name):
-  """Determines if the platform was migrated from FDT impl.
 
-  Args:
-    platform_name: Platform name to be checked
-  """
-  return platform_name in ['Coral', 'Fizz']
-
-def GenerateMosysCBindings(config):
-  """Generates Mosys C struct bindings
+def GenerateCBindings(config):
+  """Generates C struct bindings
 
   Generates C struct bindings that can be used by mosys.
 
@@ -335,7 +328,6 @@
   struct_format_arm = '''
     {.platform_name = "%s",
      .device_tree_compatible_match = "%s",
-     .sku_id = %s,
      .customization_id = "%s",
      .whitelabel_tag = "%s",
      .info = {.brand = "%s",
@@ -354,22 +346,12 @@
     signature_id = signature_id or name
     brand_code = config.get('brand-code', '')
     platform_name = identity.get('platform-name', '')
-    sku_id = identity.get('sku-id', -1)
     device_tree_compatible_match = identity.get(
         'device-tree-compatible-match', '')
-    if _IsLegacyMigration(platform_name):
-      # The mosys device-tree impl hard-coded this logic.
-      # Since we've already launched without this on new platforms,
-      # we have to keep to special backwards compatibility.
-      if whitelabel_tag:
-        customization = ('%s-%s' % (name, customization))
-      customization =  customization.upper()
-
     if device_tree_compatible_match:
       structs.append(
           struct_format_arm % (platform_name,
                                device_tree_compatible_match,
-                               sku_id,
                                customization_id,
                                whitelabel_tag,
                                brand_code,
@@ -380,7 +362,7 @@
       structs.append(
           struct_format_x86 % (platform_name,
                                identity.get('smbios-name-match', ''),
-                               sku_id,
+                               identity.get('sku-id', -1),
                                customization_id,
                                whitelabel_tag,
                                brand_code,
@@ -396,79 +378,23 @@
 const struct config_map *cros_config_get_config_map(int *num_entries) {
   *num_entries = %s;
   return &all_configs[0];
-}'''
+}
+'''
 
   return file_format % (',\n'.join(structs), len(structs))
 
 
-def GenerateEcCBindings(config):
-  """Generates EC C struct bindings
-
-  Generates .h and .c file containing C struct bindings that can be used by ec.
+def _FormatJson(config):
+  """Formats JSON for output or printing.
 
   Args:
-    config: Config (transformed) that is the transform basis.
+    config: Dictionary to be output
   """
-
-  json_config = json.loads(config)
-  device_properties = collections.defaultdict(dict)
-  flag_set = set()
-  for config in json_config[CHROMEOS][CONFIGS]:
-    firmware = config['firmware']
-
-    if 'build-targets' not in firmware:
-      # Do not consider it an error if a config explicitly specifies no
-      # firmware.
-      if 'no-firmware' not in firmware:
-        print("WARNING: config missing 'firmware.build-targets', skipping",
-              file=sys.stderr)
-    elif 'ec' not in firmware['build-targets']:
-      print("WARNING: config missing 'firmware.build-targets.ec', skipping",
-            file=sys.stderr)
-    elif 'identity' not in config:
-      print("WARNING: config missing 'identity', skipping",
-            file=sys.stderr)
-    elif 'sku-id' not in config['identity']:
-      print("WARNING: config missing 'identity.sku-id', skipping",
-            file=sys.stderr)
-    else:
-      sku = config['identity']['sku-id']
-      ec_build_target = firmware['build-targets']['ec'].upper()
-
-      # Default flag value will be false.
-      flag_values = collections.defaultdict(bool)
-
-      hwprops = config.get('hardware-properties', None)
-      if hwprops:
-        # |flag| is a user specified property of the hardware, for example
-        # 'is-lid-convertible', which means that the device can rotate 360.
-        for flag, value in hwprops.iteritems():
-          # Convert the name of the flag to a valid C identifier.
-          clean_flag = flag.replace('-', '_')
-          flag_set.add(clean_flag)
-          flag_values[clean_flag] = value
-
-      # Duplicate skus take the last value in the config file.
-      device_properties[ec_build_target][sku] = flag_values
-
-  flags = list(flag_set)
-  flags.sort()
-  for ec_build_target in device_properties.iterkeys():
-    # Order struct definitions by sku.
-    device_properties[ec_build_target] = \
-        sorted(device_properties[ec_build_target].items())
-
-  h_template_path = os.path.join(
-      this_dir, TEMPLATE_DIR, (EC_OUTPUT_NAME + '.h' + TEMPLATE_SUFFIX))
-  h_template = Template(open(h_template_path).read())
-
-  c_template_path = os.path.join(
-      this_dir, TEMPLATE_DIR, (EC_OUTPUT_NAME + '.c' + TEMPLATE_SUFFIX))
-  c_template = Template(open(c_template_path).read())
-
-  h_output = h_template.render(flags=flags)
-  c_output = c_template.render(device_properties=device_properties, flags=flags)
-  return (h_output, c_output)
+  # Work around bug in json dumps that adds trailing spaces with indent set.
+  return re.sub(
+      ', $',
+      ',',
+      json.dumps(config, sort_keys=True, indent=2), flags=re.MULTILINE)
 
 
 def FilterBuildElements(config):
@@ -483,7 +409,7 @@
   for config in json_config[CHROMEOS][CONFIGS]:
     _FilterBuildElements(config, '')
 
-  return libcros_schema.FormatJson(json_config)
+  return _FormatJson(json_config)
 
 
 def _FilterBuildElements(config, path):
@@ -504,50 +430,20 @@
     config.pop(key)
 
 
-def GetValidSchemaProperties(
-    schema=os.path.join(this_dir, 'cros_config_schema.yaml')):
-  """Returns all valid properties from the given schema
+def ValidateConfigSchema(schema, config):
+  """Validates a transformed cros config against the schema specified
 
-  Iterates over the config payload for devices and returns the list of
-  valid properties that could potentially be returned from
-  cros_config_host or cros_config
+  Verifies that the config complies with the schema supplied.
 
   Args:
-    schema: Source schema that contains the properties.
+    schema: Source schema used to verify the config.
+    config: Config (transformed) that will be verified.
   """
-  with open(schema, 'r') as schema_stream:
-    schema_yaml = yaml.load(schema_stream.read())
-  root_path = 'properties/chromeos/properties/configs/items/properties'
-  schema_node = schema_yaml
-  for element in root_path.split('/'):
-    schema_node = schema_node[element]
-
-  result = {}
-  _GetValidSchemaProperties(schema_node, [], result)
-  return result
-
-
-def _GetValidSchemaProperties(schema_node, path, result):
-  """Recursively finds the valid properties for a given node
-
-  Args:
-    schema_node: Single node from the schema
-    path: Running path that a given node maps to
-    result: Running collection of results
-  """
-  full_path = '/%s' % '/'.join(path)
-  for key in schema_node:
-    new_path = path + [key]
-    node_type = schema_node[key]['type']
-
-    if node_type == 'object':
-      if 'properties' in schema_node[key]:
-        _GetValidSchemaProperties(
-            schema_node[key]['properties'], new_path, result)
-    elif node_type == 'string':
-      all_props = result.get(full_path, [])
-      all_props.append(key)
-      result[full_path] = all_props
+  json_config = json.loads(config)
+  schema_yaml = yaml.load(schema)
+  schema_json_from_yaml = json.dumps(schema_yaml, sort_keys=True, indent=2)
+  schema_json = json.loads(schema_json_from_yaml)
+  validate(json_config, schema_json)
 
 
 class ValidationError(Exception):
@@ -561,23 +457,10 @@
   Args:
     json_config: JSON config dictionary
   """
-  identities = set()
-  duplicate_identities = set()
-  for config in json_config['chromeos']['configs']:
-    if 'identity' not in config and 'name' not in config:
-      raise ValidationError(
-          'Missing identity for config: %s' % str(config))
-    identity_str = "%s-%s" % (
-        config.get('name', ''), str(config.get('identity', {})))
-    if identity_str in identities:
-      duplicate_identities.add(identity_str)
-    else:
-      identities.add(identity_str)
-
-  if duplicate_identities:
-    raise ValidationError(
-        'Identities are not unique: %s' % duplicate_identities)
-
+  identities = [str(config['identity'])
+                for config in json_config['chromeos']['configs']]
+  if len(identities) != len(set(identities)):
+    raise ValidationError('Identities are not unique: %s' % identities)
 
 def _ValidateWhitelabelBrandChangesOnly(json_config):
   """Verifies that whitelabel changes are contained to branding information.
@@ -587,7 +470,7 @@
   """
   whitelabels = {}
   for config in json_config['chromeos']['configs']:
-    whitelabel_tag = config.get('identity', {}).get('whitelabel-tag', None)
+    whitelabel_tag = config['identity'].get('whitelabel-tag', None)
     if whitelabel_tag:
       name = '%s - %s' % (config['name'], config['identity'].get('sku-id', 0))
       config_list = whitelabels.get(name, [])
@@ -621,27 +504,6 @@
                                  base_str,
                                  compare_str))
 
-
-def _ValidateHardwarePropertiesAreBoolean(json_config):
-  """Checks that all fields under hardware-properties are boolean
-
-     Ensures that no key is added to hardware-properties that has a non-boolean
-     value, because non-boolean values are unsupported by the
-     hardware-properties codegen.
-
-  Args:
-    json_config: JSON config dictionary
-  """
-  for config in json_config['chromeos']['configs']:
-    hardware_properties = config.get('hardware-properties', None)
-    if hardware_properties:
-      for key, value in hardware_properties.iteritems():
-        if not isinstance(value, bool):
-          raise ValidationError(
-              ('All configs under hardware-properties must be boolean flags\n'
-               'However, key \'{}\' has value \'{}\'.').format(key, value))
-
-
 def ValidateConfig(config):
   """Validates a transformed cros config for general business rules.
 
@@ -654,8 +516,58 @@
   json_config = json.loads(config)
   _ValidateUniqueIdentities(json_config)
   _ValidateWhitelabelBrandChangesOnly(json_config)
-  _ValidateHardwarePropertiesAreBoolean(json_config)
 
+def _FindImports(config_file, includes):
+  """Recursively looks up and finds files to include for yaml.
+
+  Args:
+    config_file: Path to the config file for which to apply imports.
+    includes: List that is built up through processing the files.
+  """
+  working_dir = os.path.dirname(config_file)
+  with open(config_file, 'r') as config_stream:
+    config_lines = config_stream.readlines()
+    yaml_import_lines = []
+    found_imports = False
+    # Parsing out just the imports snippet is required because the YAML
+    # isn't valid until the imports are eval'd
+    for line in config_lines:
+      if re.match("^imports", line):
+        found_imports = True
+        yaml_import_lines.append(line)
+      elif found_imports:
+        match = re.match(" *- (.*)", line)
+        if match:
+          yaml_import_lines.append(line)
+        else:
+          break
+
+    if yaml_import_lines:
+      yaml_import = yaml.load("\n".join(yaml_import_lines))
+
+      for import_file in yaml_import.get("imports", []):
+        full_path = os.path.join(working_dir, import_file)
+        _FindImports(full_path, includes)
+    includes.append(config_file)
+
+def ApplyImports(config_file):
+  """Parses the imports statements and applies them to a result config.
+
+  Args:
+    config_file: Path to the config file for which to apply imports.
+
+  Returns:
+    Raw config with the imports applied.
+  """
+  import_files = []
+  _FindImports(config_file, import_files)
+
+  all_yaml_files = []
+  for import_file in import_files:
+    with open(import_file, 'r') as yaml_stream:
+      all_yaml_files.append(yaml_stream.read())
+
+  return '\n'.join(all_yaml_files)
 
 def MergeConfigs(configs):
   """Evaluates and merges all config files into a single configuration.
@@ -668,7 +580,7 @@
   """
   json_files = []
   for yaml_file in configs:
-    yaml_with_imports = libcros_schema.ApplyImports(yaml_file)
+    yaml_with_imports = ApplyImports(yaml_file)
     json_transformed_file = TransformConfig(yaml_with_imports)
     json_files.append(json.loads(json_transformed_file))
 
@@ -701,14 +613,13 @@
       if not matched:
         result_json['chromeos']['configs'].append(to_merge_config)
 
-  return libcros_schema.FormatJson(result_json)
-
+  return _FormatJson(result_json)
 
 def Main(schema,
          config,
          output,
          filter_build_details=False,
-         gen_c_output_dir=None,
+         gen_c_bindings_output=None,
          configs=None):
   """Transforms and validates a cros config file for use on the system
 
@@ -723,7 +634,7 @@
     config: Source config file that will be transformed/verified.
     output: Output file that will be generated by the transform.
     filter_build_details: Whether build only details should be filtered or not.
-    gen_c_output_dir: Output directory for generated C config files.
+    gen_c_bindings_output: Output file with generated c bindings.
     configs: List of source config files that will be transformed/verified.
   """
   if not schema:
@@ -738,41 +649,31 @@
   json_transform = full_json_transform
 
   with open(schema, 'r') as schema_stream:
-    libcros_schema.ValidateConfigSchema(schema_stream.read(), json_transform)
+    ValidateConfigSchema(schema_stream.read(), json_transform)
     ValidateConfig(json_transform)
     if filter_build_details:
       json_transform = FilterBuildElements(json_transform)
   if output:
     with open(output, 'w') as output_stream:
-      # Using print function adds proper trailing newline.
-      print(json_transform, file=output_stream)
+      output_stream.write(json_transform)
   else:
     print(json_transform)
-  if gen_c_output_dir:
-    with open(os.path.join(gen_c_output_dir, MOSYS_OUTPUT_NAME), 'w') \
-    as output_stream:
-      # Using print function adds proper trailing newline.
-      print(GenerateMosysCBindings(full_json_transform), file=output_stream)
-    h_output, c_output = GenerateEcCBindings(full_json_transform)
-    with open(os.path.join(gen_c_output_dir, EC_OUTPUT_NAME + ".h"), 'w') \
-    as output_stream:
-      print(h_output, file=output_stream)
-    with open(os.path.join(gen_c_output_dir, EC_OUTPUT_NAME + ".c"), 'w') \
-    as output_stream:
-      print(c_output, file=output_stream)
+  if gen_c_bindings_output:
+    with open(gen_c_bindings_output, 'w') as output_stream:
+      output_stream.write(GenerateCBindings(full_json_transform))
 
-# The distutils generated command line wrappers will not pass us argv.
-def main(argv=None):
+
+def main(_argv=None):
   """Main program which parses args and runs
 
   Args:
-    argv: List of command line arguments, if None uses sys.argv.
+    _argv: Intended to be the list of arguments to the program, or None to use
+        sys.argv (but at present this is unused)
   """
-  if argv is None:
-    argv = sys.argv[1:]
-  opts = ParseArgs(argv)
-  Main(opts.schema, opts.config, opts.output, opts.filter,
-       opts.generated_c_output_directory, opts.configs)
+  args = ParseArgs(sys.argv[1:])
+  Main(args.schema, args.config, args.output, args.filter,
+       args.generated_c_output, args.configs)
+
 
 if __name__ == '__main__':
-  sys.exit(main(sys.argv[1:]))
+  main()
diff --git a/chromeos-config/cros_config_host/cros_config_schema.yaml b/chromeos-config/cros_config_host/cros_config_schema.yaml
index b66f4a0..90b69e1 100644
--- a/chromeos-config/cros_config_host/cros_config_schema.yaml
+++ b/chromeos-config/cros_config_host/cros_config_schema.yaml
@@ -6,14 +6,10 @@
   bcs-file-name: &bcs_file_name
     type: string
     description: Name of the file located in BCS under the respective bcs-overlay.
-  deprecated_string: &deprecated_string
-    type: string
-    description: A deprecated string property to be removed after migration.
   firmware-build-target: &firmware_build_target
     type: string
     description: Build target that will be considered dirty when building/testing
       locally.
-  # TODO(shapiroc): Migrate to use system-file-v2 instead
   system-file: &system_file
     type: object
     properties:
@@ -24,22 +20,6 @@
         description: Installation path for the file on the system image.
         type: string
     additionalProperties: false
-  # V2 exists because system-file targets were poorly named, so they couldn't
-  # be logically shared for build-time and run-time usage.
-  # E.g. bluetooth config we want to both install and get the path at runtime
-  system-file-v2: &system_file_v2
-    type: object
-    properties:
-      build-path:
-        description: Source of the file relative to the build system.
-        type: string
-      system-path:
-        description: Installation path for the file on the system image.
-        type: string
-    additionalProperties: false
-    required:
-      - build-path
-      - system-path
   firmware-file: &firmware_file
     type: object
     properties:
@@ -53,14 +33,11 @@
         description: Symlink file that will be installed pointing to the destination.
         type: string
     additionalProperties: false
-  sku-id: &sku_id
-    description: "SKU/Board strapping pins configured during board manufacturing."
-    type: integer
   whitelabel-tag: &whitelabel_tag
-    description: "'whitelabel_tag' value set in the VPD, to add Whitelabel branding over an unbranded base model."
+    description: "'whitelabel-tag' value set in the VPD for Whitelabels."
     type: string
   customization-id: &customization_id
-    description: "'customization_id' value set in the VPD for non-unibuild Zergs and Whitelabels. Deprecated for use in new products since 2017/07/26."
+    description: "'customization-id' value set in the VPD for Zergs and older Whitelabels."
     type: string
   platform-name: &platform_name
     description: "Defines the name that is reported by 'mosys platform name'
@@ -144,23 +121,10 @@
               additionalProperties: false
               required:
               - main
-            bluetooth:
-              type: object
-              properties:
-                config: *system_file_v2
-              additionalProperties: false
-              required:
-              - config
             brand-code:
               description: Brand code of the model (also called RLZ code).
               type: string
-            camera:
-              type: object
-              properties:
-                count:
-                  type: integer
-                  description: Specified the number of cameras on the model.
-              additionalProperties: false
+              pattern: "^[A-Z]{4}$"
             firmware:
               type: object
               properties:
@@ -175,11 +139,6 @@
                       description: Build target of the base EC firmware for a detachable device,
                         that will be considered dirty when building/testing
                       type: string
-                    ec_extras:
-                      type: array
-                      items:
-                        type: string
-                        description: Extra EC build targets to build within chromeos-ec.
                     coreboot: *firmware_build_target
                     cr50: *firmware_build_target
                     depthcharge: *firmware_build_target
@@ -187,23 +146,14 @@
                     libpayload: *firmware_build_target
                     u-boot: *firmware_build_target
                   additionalProperties: false
-                ec-image: *deprecated_string
-                ec-ro-image: *bcs_file_name
-                pd-image: *deprecated_string
-                pd-ro-image: *bcs_file_name
+                ec-image: *bcs_file_name
+                pd-image: *bcs_file_name
                 key-id:
                   description: Key ID from the signer key set that is used to sign the
                     given firmware image.
                   type: string
-                main-image: *deprecated_string
-                main-ro-image: *bcs_file_name
+                main-image: *bcs_file_name
                 main-rw-image: *bcs_file_name
-                name:
-                  description: This is a human-recognizable name used to refer to the firmware.
-                    It will be used when generating the shellball via firmware packer.
-                    Mainly, this is only for compatibility testing with device tree (since DT
-                    allowed firmwares to be named).
-                  type: string
                 no-firmware:
                   description: If present this indicates that this model has no firmware at present.
                     This means that it will be omitted from the firmware updater
@@ -212,6 +162,28 @@
                     This option is often useful when a model is first added,
                     since it may not have firmware at that point.
                   type: boolean
+                extra:
+                  type: array
+                  items:
+                    type: string
+                    description: A list of extra files or directories needed
+                      to update firmware, each being a string filename. Any filename
+                      is supported. If it starts with `bcs://` then it is read from
+                      BCS as with main-image above. But normally it is a path. A
+                      typical example is `${FILESDIR}/extra` which means that the
+                      `extra` diectory is copied from the firmware ebuild's
+                      `files/extra` directory. Full paths can be provided, e.g.
+                      `${SYSROOT}/usr/bin/ectool`. If a directory is provided, its
+                      contents are copied (subdirectories are not supported). This
+                      mirrors the functionality of `CROS_FIRMWARE_EXTRA_LIST`.
+                tools:
+                  type: array
+                  items:
+                    type: string
+                    description: A list of additional tools which should be
+                      packed into the firmware update shellball. This is only needed
+                      if this model needs to run a special tool to do the firmware
+                      update.
               additionalProperties: false
             firmware-signing:
               type: object
@@ -251,11 +223,14 @@
                 This tuple must either contain x86 properties only or ARM properties only.
               oneOf:
                 - properties:
+                    sku-id:
+                      description: "[x86] SKU/Board strapping pins configured during board
+                        manufacturing."
+                      type: integer
                     smbios-name-match:
                       description: "[x86] Firmware name built into the firmware and reflected back
                         out in the SMBIOS tables."
                       type: string
-                    sku-id: *sku_id
                     platform-name: *platform_name
                     customization-id: *customization_id
                     whitelabel-tag: *whitelabel_tag
@@ -265,7 +240,6 @@
                       description: "[ARM] String pattern (partial) that is matched against the
                         contents of /proc/device-tree/compatible on ARM devices."
                       type: string
-                    sku-id: *sku_id
                     platform-name: *platform_name
                     customization-id: *customization_id
                     whitelabel-tag: *whitelabel_tag
@@ -366,47 +340,6 @@
                 prevent this device from being updated to another OEM's devices.
               type: string
               pattern: "[0-9]+"
-            modem:
-              type: object
-              properties:
-                firmware-variant:
-                  description: Variant of the modem firmware to be used. This
-                    value is read by modemfwd to match against the variant field
-                    of a firmware entry in a firmware manifest. In most cases,
-                    we simply use the model name as the value.
-                  type: string
-              additionalProperties: false
-            hardware-properties:
-              type: object
-              description: Contains boolean flags for hardware properties of
-                this board, for example if it's convertible, has a touchscreen,
-                has a camera, etc. This information is used to auto-generate C
-                code that is consumed by the EC build process in order to do
-                run-time configuration. If a value is defined within a config
-                file, but not for a specific model, that value will be assumed
-                to be false for that model.
-                All properties must be booleans. If non-boolean
-                properties are desired, the generation code in
-                cros_config_schema.py must be updated to support them.
-              properties:
-                is-lid-convertible:
-                  description: Can the lid be rotated 360 degrees.
-                  type: boolean
-                has-lid-accelerometer:
-                  description: Is there an accelerometer in the lid of the
-                    device.
-                  type: boolean
-                has-base-accelerometer:
-                  description: Is there an accelerometer in the base of the
-                    device.
-                  type: boolean
-                has-base-gyroscope:
-                  description: Is there a gyroscope in the base of the device.
-                  type: boolean
-                has-fingerprint-sensor:
-                  description: Is there a fingerprint sensor on the device.
-                  type: boolean
-              additionalProperties: false
           additionalProperties: false
           required:
           - firmware
diff --git a/chromeos-config/cros_config_host/cros_config_schema_unittest.py b/chromeos-config/cros_config_host/cros_config_schema_unittest.py
index 5c6d870..51a61bb 100755
--- a/chromeos-config/cros_config_host/cros_config_schema_unittest.py
+++ b/chromeos-config/cros_config_host/cros_config_schema_unittest.py
@@ -1,5 +1,4 @@
 #!/usr/bin/env python2
-# -*- coding: utf-8 -*-
 # Copyright 2017 The Chromium OS Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
@@ -8,24 +7,20 @@
 
 from __future__ import print_function
 
-from itertools import izip_longest
 import json
 import jsonschema
 import os
 import unittest
 import re
+import tempfile
 
 import cros_config_schema
-import libcros_schema
-
-from chromite.lib import cros_test_lib
-
 
 BASIC_CONFIG = """
 reef-9042-fw: &reef-9042-fw
   bcs-overlay: 'overlay-reef-private'
-  ec-ro-image: 'Reef_EC.9042.87.1.tbz2'
-  main-ro-image: 'Reef.9042.87.1.tbz2'
+  ec-image: 'Reef_EC.9042.87.1.tbz2'
+  main-image: 'Reef.9042.87.1.tbz2'
   main-rw-image: 'Reef.9042.110.0.tbz2'
   build-targets:
     coreboot: 'reef'
@@ -65,7 +60,26 @@
 this_dir = os.path.dirname(__file__)
 
 
-class MergeDictionaries(cros_test_lib.TestCase):
+class GetNamedTupleTests(unittest.TestCase):
+
+  def testRecursiveDicts(self):
+    val = {'a': {'b': 1, 'c': 2}}
+    val_tuple = cros_config_schema.GetNamedTuple(val)
+    self.assertEqual(val['a']['b'], val_tuple.a.b)
+    self.assertEqual(val['a']['c'], val_tuple.a.c)
+
+  def testListInRecursiveDicts(self):
+    val = {'a': {'b': [{'c': 2}]}}
+    val_tuple = cros_config_schema.GetNamedTuple(val)
+    self.assertEqual(val['a']['b'][0]['c'], val_tuple.a.b[0].c)
+
+  def testDashesReplacedWithUnderscores(self):
+    val = {'a-b': 1}
+    val_tuple = cros_config_schema.GetNamedTuple(val)
+    self.assertEqual(val['a-b'], val_tuple.a_b)
+
+
+class MergeDictionaries(unittest.TestCase):
 
   def testBaseKeyMerge(self):
     primary = {'a': {'b': 1, 'c': 2}}
@@ -80,7 +94,7 @@
     self.assertEqual({'a': {'b': 1, 'c': [1, 2, 3, 4]}}, primary)
 
 
-class ParseArgsTests(cros_test_lib.TestCase):
+class ParseArgsTests(unittest.TestCase):
 
   def testParseArgs(self):
     argv = ['-s', 'schema', '-c', 'config', '-o', 'output', '-f' 'True']
@@ -97,13 +111,13 @@
     self.assertEqual(args.configs, ['m1' 'm2' 'm3'])
 
 
-class TransformConfigTests(cros_test_lib.TestCase):
+class TransformConfigTests(unittest.TestCase):
 
   def testBasicTransform(self):
     result = cros_config_schema.TransformConfig(BASIC_CONFIG)
     json_dict = json.loads(result)
     self.assertEqual(len(json_dict), 1)
-    json_obj = libcros_schema.GetNamedTuple(json_dict)
+    json_obj = cros_config_schema.GetNamedTuple(json_dict)
     self.assertEqual(1, len(json_obj.chromeos.configs))
     model = json_obj.chromeos.configs[0]
     self.assertEqual('basking', model.name)
@@ -112,66 +126,6 @@
     self.assertEqual('/etc/cras/basking/dsp.ini',
                      model.audio.main.files[0].destination)
 
-  def testTransformConfig_NoMatch(self):
-    result = cros_config_schema.TransformConfig(
-        BASIC_CONFIG, model_filter_regex='abc123')
-    json_dict = json.loads(result)
-    json_obj = libcros_schema.GetNamedTuple(json_dict)
-    self.assertEqual(0, len(json_obj.chromeos.configs))
-
-  def testTransformConfig_FilterMatch(self):
-    scoped_config = """
-reef-9042-fw: &reef-9042-fw
-  bcs-overlay: 'overlay-reef-private'
-  ec-ro-image: 'Reef_EC.9042.87.1.tbz2'
-  main-ro-image: 'Reef.9042.87.1.tbz2'
-  main-rw-image: 'Reef.9042.110.0.tbz2'
-  build-targets:
-    coreboot: 'reef'
-chromeos:
-  devices:
-    - $name: 'foo'
-      products:
-        - $key-id: 'OEM2'
-      skus:
-        - config:
-            identity:
-              sku-id: 0
-            audio:
-              main:
-                cras-config-dir: '{{$name}}'
-                ucm-suffix: '{{$name}}'
-            name: '{{$name}}'
-            firmware: *reef-9042-fw
-            firmware-signing:
-              key-id: '{{$key-id}}'
-              signature-id: '{{$name}}'
-    - $name: 'bar'
-      products:
-        - $key-id: 'OEM2'
-      skus:
-        - config:
-            identity:
-              sku-id: 0
-            audio:
-              main:
-                cras-config-dir: '{{$name}}'
-                ucm-suffix: '{{$name}}'
-            name: '{{$name}}'
-            firmware: *reef-9042-fw
-            firmware-signing:
-              key-id: '{{$key-id}}'
-              signature-id: '{{$name}}'
-"""
-
-    result = cros_config_schema.TransformConfig(
-        scoped_config, model_filter_regex='bar')
-    json_dict = json.loads(result)
-    json_obj = libcros_schema.GetNamedTuple(json_dict)
-    self.assertEqual(1, len(json_obj.chromeos.configs))
-    model = json_obj.chromeos.configs[0]
-    self.assertEqual('bar', model.name)
-
   def testTemplateVariableScope(self):
     scoped_config = """
 audio_common: &audio_common
@@ -202,7 +156,7 @@
 """
     result = cros_config_schema.TransformConfig(scoped_config)
     json_dict = json.loads(result)
-    json_obj = libcros_schema.GetNamedTuple(json_dict)
+    json_obj = cros_config_schema.GetNamedTuple(json_dict)
     config = json_obj.chromeos.configs[0]
     self.assertEqual(
         'overridden-by-product-scope', config.audio.main.cras_config_dir)
@@ -210,7 +164,7 @@
         'overridden-by-device-scope', config.audio.main.ucm_suffix)
 
 
-class ValidateConfigSchemaTests(cros_test_lib.TestCase):
+class ValidateConfigSchemaTests(unittest.TestCase):
 
   def setUp(self):
     with open(os.path.join(this_dir,
@@ -218,14 +172,14 @@
       self._schema = schema_stream.read()
 
   def testBasicSchemaValidation(self):
-    libcros_schema.ValidateConfigSchema(
+    cros_config_schema.ValidateConfigSchema(
         self._schema, cros_config_schema.TransformConfig(BASIC_CONFIG))
 
   def testMissingRequiredElement(self):
     config = re.sub(r' *cras-config-dir: .*', '', BASIC_CONFIG)
     config = re.sub(r' *volume: .*', '', BASIC_CONFIG)
     try:
-      libcros_schema.ValidateConfigSchema(
+      cros_config_schema.ValidateConfigSchema(
           self._schema, cros_config_schema.TransformConfig(config))
     except jsonschema.ValidationError as err:
       self.assertIn('required', err.__str__())
@@ -234,14 +188,14 @@
   def testReferencedNonExistentTemplateVariable(self):
     config = re.sub(r' *$card: .*', '', BASIC_CONFIG)
     try:
-      libcros_schema.ValidateConfigSchema(
+      cros_config_schema.ValidateConfigSchema(
           self._schema, cros_config_schema.TransformConfig(config))
     except cros_config_schema.ValidationError as err:
       self.assertIn('Referenced template variable', err.__str__())
       self.assertIn('cras-config-dir', err.__str__())
 
 
-class ValidateConfigTests(cros_test_lib.TestCase):
+class ValidateConfigTests(unittest.TestCase):
 
   def testBasicValidation(self):
     cros_config_schema.ValidateConfig(
@@ -251,8 +205,8 @@
     config = """
 reef-9042-fw: &reef-9042-fw
   bcs-overlay: 'overlay-reef-private'
-  ec-ro-image: 'Reef_EC.9042.87.1.tbz2'
-  main-ro-image: 'Reef.9042.87.1.tbz2'
+  ec-image: 'Reef_EC.9042.87.1.tbz2'
+  main-image: 'Reef.9042.87.1.tbz2'
   main-rw-image: 'Reef.9042.110.0.tbz2'
   build-targets:
     coreboot: 'reef'
@@ -330,54 +284,8 @@
     except cros_config_schema.ValidationError as err:
       self.assertIn('Whitelabel configs can only', err.__str__())
 
-  def testHardwarePropertiesNonBoolean(self):
-    config = \
-"""
-chromeos:
-  devices:
-    - $name: 'bad_device'
-      skus:
-        - config:
-            identity:
-              sku-id: 0
-            # THIS WILL CAUSE THE FAILURE
-            hardware-properties:
-              has-base-accelerometer: true
-              has-base-gyroscope: 7
-              has-lid-accelerometer: false
-              has-fingerprint-sensor: false
-              is-lid-convertible: false
-"""
-    try:
-      cros_config_schema.ValidateConfig(
-          cros_config_schema.TransformConfig(config))
-    except cros_config_schema.ValidationError as err:
-      self.assertIn('must be boolean', err.__str__())
-    else:
-      self.fail('ValidationError not raised')
 
-  def testHardwarePropertiesBoolean(self):
-    config = \
-"""
-chromeos:
-  devices:
-    - $name: 'good_device'
-      skus:
-        - config:
-            identity:
-              sku-id: 0
-            hardware-properties:
-              has-base-accelerometer: true
-              has-base-gyroscope: true
-              has-lid-accelerometer: true
-              has-fingerprint-sensor: true
-              is-lid-convertible: false
-"""
-    cros_config_schema.ValidateConfig(
-        cros_config_schema.TransformConfig(config))
-
-
-class FilterBuildElements(cros_test_lib.TestCase):
+class FilterBuildElements(unittest.TestCase):
 
   def testBasicFilterBuildElements(self):
     json_dict = json.loads(
@@ -386,110 +294,82 @@
     self.assertNotIn('firmware', json_dict['chromeos']['configs'][0])
 
 
-class GetValidSchemaProperties(cros_test_lib.TestCase):
+class MainTests(unittest.TestCase):
 
-  def testGetValidSchemaProperties(self):
-    schema_props = cros_config_schema.GetValidSchemaProperties()
-    self.assertIn('cras-config-dir', schema_props['/audio/main'])
-    self.assertIn('key-id', schema_props['/firmware-signing'])
-
-
-class MainTests(cros_test_lib.TempDirTestCase):
-  def assertFileEqual(self, file_expected, file_actual, regen_cmd=''):
-    self.assertTrue(os.path.isfile(file_expected),
-                    "Expected file does not exist at path: {}" \
-                    .format(file_expected))
-
-    self.assertTrue(os.path.isfile(file_actual),
-                    "Actual file does not exist at path: {}" \
-                    .format(file_actual))
-
-    with open(file_expected, 'r') as expected, open(file_actual, 'r') as actual:
-      for line_num, (line_expected, line_actual) in \
-          enumerate(izip_longest(expected, actual)):
-        self.assertEqual(line_expected, line_actual, \
-           ('Files differ at line {0}\n'
-            'Expected: {1}\n'
-            'Actual  : {2}\n'
-            'Path of expected output file: {3}\n'
-            'Path of actual output file: {4}\n'
-            '{5}').format(line_num, repr(line_expected), repr(line_actual),
-                          file_expected, file_actual, regen_cmd))
-
-  def assertMultilineStringEqual(self, str_expected, str_actual):
-    expected = str_expected.strip().split("\n")
-    actual = str_actual.strip().split("\n")
-    for line_num, (line_expected, line_actual) in \
-        enumerate(izip_longest(expected, actual)):
-      self.assertEqual(line_expected, line_actual, \
-         ('Strings differ at line {0}\n'
-          'Expected: {1}\n'
-          'Actual  : {2}\n').format(line_num, repr(line_expected),
-                                    repr(line_actual)))
-
-  def testMainWithExampleWithBuildAndMosysCBindings(self):
-    json_output = os.path.join(self.tempdir, 'output.json')
-    c_output = os.path.join(self.tempdir, 'config.c')
+  def testMainWithExampleWithBuildAndCBindings(self):
+    output = tempfile.mktemp()
+    c_output = tempfile.mktemp()
     cros_config_schema.Main(
         None,
         os.path.join(this_dir, '../libcros_config/test.yaml'),
-        json_output,
-        gen_c_output_dir=self.tempdir)
+        output,
+        gen_c_bindings_output=c_output)
     regen_cmd = ('To regenerate the expected output, run:\n'
                  '\tpython -m cros_config_host.cros_config_schema '
                  '-c libcros_config/test.yaml '
                  '-o libcros_config/test_build.json '
-                 '-g libcros_config')
-
-    expected_json_file = \
-            os.path.join(this_dir, '../libcros_config/test_build.json')
-    self.assertFileEqual(expected_json_file, json_output, regen_cmd)
-
-    expected_c_file = os.path.join(this_dir, '../libcros_config/test.c')
-    self.assertFileEqual(expected_c_file, c_output, regen_cmd)
+                 '-g libcros_config/test.c')
+    with open(output, 'r') as output_stream:
+      with open(os.path.join(
+          this_dir, '../libcros_config/test_build.json')) as expected_stream:
+        self.assertEqual(expected_stream.read(), output_stream.read(),
+                         regen_cmd)
+    with open(c_output, 'r') as output_stream:
+      with open(os.path.join(this_dir,
+                             '../libcros_config/test.c')) as expected_stream:
+        self.assertEqual(expected_stream.read(), output_stream.read(),
+                         regen_cmd)
 
   def testMainWithExampleWithoutBuild(self):
-    output = os.path.join(self.tempdir, 'output')
+    output = tempfile.mktemp()
     cros_config_schema.Main(
         None,
         os.path.join(this_dir, '../libcros_config/test.yaml'),
         output,
         filter_build_details=True)
+    with open(output, 'r') as output_stream:
+      with open(os.path.join(this_dir,
+                             '../libcros_config/test.json')) as expected_stream:
+        self.assertEqual(expected_stream.read(), output_stream.read(),
+                         ('To regenerate the expected output, run:\n'
+                          '\tpython -m cros_config_host.cros_config_schema '
+                          '-f True '
+                          '-c libcros_config/test.yaml '
+                          '-o libcros_config/test.json'))
 
-    regen_cmd = ('To regenerate the expected output, run:\n'
-                 '\tpython -m cros_config_host.cros_config_schema '
-                 '-f True '
-                 '-c libcros_config/test.yaml '
-                 '-o libcros_config/test.json')
-
-    expected_file = os.path.join(this_dir, '../libcros_config/test.json')
-    self.assertFileEqual(expected_file, output, regen_cmd)
+    os.remove(output)
 
   def testMainArmExample(self):
-    json_output = os.path.join(self.tempdir, 'output.json')
-    c_output = os.path.join(self.tempdir, 'config.c')
+    output = tempfile.mktemp()
+    c_output = tempfile.mktemp()
     cros_config_schema.Main(
         None,
         os.path.join(this_dir, '../libcros_config/test_arm.yaml'),
-        json_output,
+        output,
         filter_build_details=True,
-        gen_c_output_dir=self.tempdir)
+        gen_c_bindings_output=c_output)
     regen_cmd = ('To regenerate the expected output, run:\n'
                  '\tpython -m cros_config_host.cros_config_schema '
                  '-f True '
                  '-c libcros_config/test_arm.yaml '
                  '-o libcros_config/test_arm.json '
-                 '-g libcros_config')
+                 '-g libcros_config/test_arm.c')
+    with open(output, 'r') as output_stream:
+      with open(os.path.join(
+          this_dir,
+          '../libcros_config/test_arm.json')) as expected_stream:
+        self.assertEqual(
+            expected_stream.read(), output_stream.read(), regen_cmd)
+    with open(c_output, 'r') as output_stream:
+      expected_file = os.path.join(this_dir, '../libcros_config/test_arm.c')
+      with open(expected_file) as expected_stream:
+        self.assertEqual(expected_stream.read(), output_stream.read(),
+                         regen_cmd)
 
-    expected_json_file = \
-            os.path.join(this_dir, '../libcros_config/test_arm.json')
-    self.assertFileEqual(expected_json_file, json_output, regen_cmd)
-
-    expected_c_file = os.path.join(this_dir, '../libcros_config/test_arm.c')
-    self.assertFileEqual(expected_c_file, c_output, regen_cmd)
+    os.remove(output)
 
   def testMainImportExample(self):
-    output = os.path.join(self.tempdir, 'output')
+    output = tempfile.mktemp()
     cros_config_schema.Main(
         None,
         os.path.join(this_dir, '../libcros_config/test_import.yaml'),
@@ -498,11 +378,17 @@
                  '\tpython -m cros_config_host.cros_config_schema '
                  '-c libcros_config/test_import.yaml '
                  '-o libcros_config/test_import.json')
-    expected_file = os.path.join(this_dir, '../libcros_config/test_import.json')
-    self.assertFileEqual(expected_file, output, regen_cmd)
+    with open(output, 'r') as output_stream:
+      with open(os.path.join(
+          this_dir,
+          '../libcros_config/test_import.json')) as expected_stream:
+        self.assertEqual(
+            expected_stream.read(), output_stream.read(), regen_cmd)
+
+    os.remove(output)
 
   def testMainMergeExample(self):
-    output = os.path.join(self.tempdir, 'output')
+    output = tempfile.mktemp()
     base_path = os.path.join(this_dir, '../libcros_config')
     cros_config_schema.Main(
         None,
@@ -515,118 +401,15 @@
                  '-o libcros_config/test_merge.json '
                  '-m libcros_config/test_merge_base.yaml '
                  'libcros_config/test_merge_overlay.yaml')
-    expected_file = os.path.join(this_dir, '../libcros_config/test_merge.json')
-    self.assertFileEqual(expected_file, output, regen_cmd)
+    with open(output, 'r') as output_stream:
+      with open(os.path.join(
+          this_dir,
+          '../libcros_config/test_merge.json')) as expected_stream:
+        self.assertEqual(
+            expected_stream.read(), output_stream.read(), regen_cmd)
 
-  def testEcCodegenManyBoards(self):
-    input_json = """
-      {
-        "chromeos": {
-          "configs": [
-            {
-              "firmware": {
-                "build-targets": {
-                  "ec": "Another"
-                }
-              },
-              "hardware-properties": {
-                "is-lid-convertible": false,
-                "has-base-accelerometer": true,
-                "has-lid-accelerometer": true
-              },
-              "identity": {
-                "sku-id": 40
-              }
-            },
-            {
-              "firmware": {
-                "build-targets": {
-                  "ec": "Some"
-                }
-              },
-              "hardware-properties": {
-                "is-lid-convertible": false,
-                "has-base-accelerometer": true,
-                "has-lid-accelerometer": true
-              },
-              "identity": {
-                "sku-id": 9
-              }
-            },
-            {
-              "firmware": {
-                "build-targets": {
-                  "ec": "Some"
-                }
-              },
-              "hardware-properties": {
-                "is-lid-convertible": true,
-                "has-lid-accelerometer": true
-              },
-              "identity": {
-                "sku-id": 99
-              }
-            }
-          ]
-        }
-      }
-    """
-    h_expected_path = os.path.join(this_dir, '../libcros_config/ec_test_many.h')
-    c_expected_path = os.path.join(this_dir, '../libcros_config/ec_test_many.c')
-    h_expected = open(h_expected_path).read()
-    c_expected = open(c_expected_path).read()
+    os.remove(output)
 
-    h_actual, c_actual = cros_config_schema.GenerateEcCBindings(input_json)
-    self.assertMultilineStringEqual(h_expected, h_actual)
-    self.assertMultilineStringEqual(c_expected, c_actual)
-
-  def testEcCodegenWithOneBoard(self):
-    input_json_path = os.path.join(this_dir,
-                                   '../libcros_config/test_build.json')
-    input_json = open(input_json_path).read()
-
-    h_expected_path = os.path.join(this_dir, '../libcros_config/ec_test_one.h')
-    c_expected_path = os.path.join(this_dir, '../libcros_config/ec_test_one.c')
-    h_expected = open(h_expected_path).read()
-    c_expected = open(c_expected_path).read()
-
-    h_actual, c_actual = cros_config_schema.GenerateEcCBindings(input_json)
-    self.assertMultilineStringEqual(h_expected, h_actual)
-    self.assertMultilineStringEqual(c_expected, c_actual)
-
-  def testEcCodegenWithNoBoards(self):
-    input_json = """
-    {
-      "chromeos": {
-        "configs": []
-      }
-    }
-    """
-    h_expected_path = os.path.join(this_dir, '../libcros_config/ec_test_none.h')
-    c_expected_path = os.path.join(this_dir, '../libcros_config/ec_test_none.c')
-    h_expected = open(h_expected_path).read()
-    c_expected = open(c_expected_path).read()
-
-    h_actual, c_actual = cros_config_schema.GenerateEcCBindings(input_json)
-    self.assertMultilineStringEqual(h_expected, h_actual)
-    self.assertMultilineStringEqual(c_expected, c_actual)
-
-  def testEcCodegenMain(self):
-    output = os.path.join(self.tempdir, 'output')
-    cros_config_schema.Main(
-        None,
-        os.path.join(this_dir, '../libcros_config/test.yaml'),
-        output,
-        gen_c_output_dir=self.tempdir)
-
-    h_expected = os.path.join(this_dir, '../libcros_config/ec_test_one.h')
-    c_expected = os.path.join(this_dir, '../libcros_config/ec_test_one.c')
-
-    h_actual = os.path.join(self.tempdir, "ec_config.h")
-    c_actual = os.path.join(self.tempdir, "ec_config.c")
-
-    self.assertFileEqual(h_expected, h_actual)
-    self.assertFileEqual(c_expected, c_actual)
 
 if __name__ == '__main__':
   unittest.main()
diff --git a/chromeos-config/cros_config_host/cros_config_test_schema.py b/chromeos-config/cros_config_host/cros_config_test_schema.py
deleted file mode 100755
index 56bfaf3..0000000
--- a/chromeos-config/cros_config_host/cros_config_test_schema.py
+++ /dev/null
@@ -1,161 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-# Copyright 2018 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""Transforms and validates cros config test from source YAML to target JSON"""
-
-from __future__ import print_function
-
-import argparse
-import json
-import os
-import sys
-import yaml
-
-import  libcros_schema
-
-this_dir = os.path.dirname(__file__)
-default_test_schema = os.path.join(this_dir, 'cros_config_test_schema.yaml')
-
-CHROMEOS = 'chromeos'
-DEVICES = 'devices'
-ROOT_PATH = 'properties/chromeos/properties/devices/items/properties'
-
-
-def ParseArgs(argv):
-  """Parse the available arguments.
-
-  Invalid arguments or -h cause this function to print a message and exit.
-
-  Args:
-    argv: List of string arguments (excluding program name / argv[0])
-
-  Returns:
-    argparse.Namespace object containing the attributes.
-  """
-  parser = argparse.ArgumentParser(description=__doc__)
-  parser.add_argument(
-      '-s',
-      '--schema',
-      type=str,
-      help='Path to the schema file used to validate the config')
-  parser.add_argument(
-      '-c',
-      '--config',
-      type=str,
-      help='Path to the YAML config file that will be validated/transformed',
-      required=True)
-  parser.add_argument(
-      '-f',
-      '--filter',
-      type=str,
-      help='Filter device by name')
-  parser.add_argument(
-      '-o',
-      '--output',
-      type=str,
-      help='Output file that will be generated by the transform (system file)',
-      required=True)
-  return parser.parse_args(argv)
-
-
-def TransformConfig(config, device_filter=None):
-  """Transforms the source config (YAML) to the target system format (JSON)
-
-  Applies consistent transforms to covert a source YAML configuration into
-  JSON output that will be used by the tast test program.
-
-  Args:
-    config: Config that will be transformed.
-    device_filter: Only returns configs that match the filter.
-
-  Returns:
-    Resulting JSON output from the transform.
-  """
-  config_yaml = yaml.load(config)
-  json_from_yaml = json.dumps(config_yaml, sort_keys=True, indent=2)
-  json_config = json.loads(json_from_yaml)
-  configs = []
-  if DEVICES in json_config[CHROMEOS]:
-    for device in json_config[CHROMEOS][DEVICES]:
-      configs.append(device)
-
-  if device_filter:
-    configs = [
-        config for config in configs if device_filter == config['device-name']
-    ]
-
-  # Drop everything except for devices since they were just used as shared
-  # config in the source yaml.
-  json_config = {
-      CHROMEOS: {
-          DEVICES: configs,
-      },
-  }
-
-  return libcros_schema.FormatJson(json_config)
-
-
-def MergeConfig(yaml_file, filter_name):
-  """Evaluates and merges all config files into a single configuration.
-
-  Args:
-    yaml_file: List of source config files that will be transformed/merged.
-    filter_name: Name of device to filter on.
-
-  Returns:
-    Final merged JSON result.
-  """
-  yaml_with_imports = libcros_schema.ApplyImports(yaml_file)
-  json_transformed_file = TransformConfig(yaml_with_imports, filter_name)
-  return json_transformed_file
-
-
-def Start(config, filter_name, output, schema):
-  """Transforms and validates a cros config test file for use on the system
-
-  Applies consistent transforms to covert a source YAML configuration into
-  a JSON file that will be used on the system by cros_config tast tests.
-
-  Verifies that the file complies with the schema verification rules and
-  performs additional verification checks for config consistency.
-
-  Args:
-    config: Source config file that will be transformed/verified.
-    filter_name: Device name to filter on.
-    output: Output file that will be generated by the transform.
-    schema: Schema file used to verify the config.
-  """
-  json_transform = MergeConfig(config, filter_name)
-
-  if schema is None:
-    schema = default_test_schema
-  with open(schema, 'r') as schema_stream:
-    libcros_schema.ValidateConfigSchema(
-        schema_stream.read(), libcros_schema.FormatJson(json_transform))
-
-  if output:
-    with open(output, 'w') as output_stream:
-      # Using print function adds proper trailing newline.
-      print(json_transform, file=output_stream)
-  else:
-    print(json_transform)
-
-
-# The distutils generated command line wrappers will not pass us argv.
-def main(argv=None):
-  """Main program which parses sys.argv and runs
-
-  Args:
-    argv: List of command line arguments, if None uses sys.argv.
-  """
-  if argv is None:
-    argv = sys.argv[1:]
-  opts = ParseArgs(argv)
-  Start(opts.config, opts.filter, opts.output, opts.schema)
-
-
-if __name__ == '__main__':
-  sys.exit(main(sys.argv[1:]))
diff --git a/chromeos-config/cros_config_host/cros_config_test_schema.yaml b/chromeos-config/cros_config_host/cros_config_test_schema.yaml
deleted file mode 100644
index 7ab90d9..0000000
--- a/chromeos-config/cros_config_host/cros_config_test_schema.yaml
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright 2018 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-"$schema": http://json-schema.org/draft-04/schema#
-typeDefs:
-  command-group: &command_group
-    type: object
-    properties:
-      name:
-        description: Name of command to run on the device.
-        type: string
-      args:
-        description: Arguments to pass to multiple command invocations and compare against golden database.
-        type: array
-        items:
-          type: string
-          description: Arguments to run and compare against golden database.
-properties:
-  chromeos:
-    type: object
-    properties:
-      devices:
-        type: array
-        items:
-          type: object
-          properties:
-            device-name:
-              type: string
-              description: Device name.
-              pattern: "^[A-Za-z]$"
-            command-groups:
-              type: array
-              items: *command_group
diff --git a/chromeos-config/cros_config_host/cros_config_test_schema_unittest.py b/chromeos-config/cros_config_host/cros_config_test_schema_unittest.py
deleted file mode 100755
index c0ec330..0000000
--- a/chromeos-config/cros_config_host/cros_config_test_schema_unittest.py
+++ /dev/null
@@ -1,153 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-# Copyright 2018 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# pylint: disable=module-missing-docstring,class-missing-docstring
-
-from __future__ import print_function
-
-import cros_config_test_schema
-import json
-import os
-
-import libcros_schema
-
-from chromite.lib import cros_test_lib
-from chromite.lib import osutils
-
-
-BASIC_CONFIG = """
-mosys-base: &mosys_base_cmds
-    name: 'mosys'
-    args:
-      - 'platform id'
-      - 'platform name'
-nautilus-mosys-base: &nautilus_mosys_cmds
-    name: 'mosys'
-    args:
-      - 'platform version'
-cros-config-base: &cros_config_base_cmds
-    name: 'cros-config'
-    args:
-      - '/ brand-name'
-cros-config-lte: &cros_config_lte_cmds
-    name: 'cros-config'
-    args:
-      - '/arc/build-properties device'
-chromeos:
-  devices:
-    - device-name: 'nautilus'
-      command-groups:
-        - *mosys_base_cmds
-        - *nautilus_mosys_cmds
-        - *cros_config_base_cmds
-    - device-name: 'nautiluslte'
-      command-groups:
-        - *mosys_base_cmds
-        - *nautilus_mosys_cmds
-        - *cros_config_base_cmds
-        - *cros_config_lte_cmds
-"""
-
-this_dir = os.path.dirname(__file__)
-
-
-class ParseArgsTests(cros_test_lib.TestCase):
-
-  def testParseArgs(self):
-    argv = ['-s', 'schema', '-c', 'config', '-f', 'nautilus', '-o', 'output']
-    opts = cros_config_test_schema.ParseArgs(argv)
-    self.assertEqual(opts.schema, 'schema')
-    self.assertEqual(opts.config, 'config')
-    self.assertEqual(opts.filter, 'nautilus')
-    self.assertEqual(opts.output, 'output')
-
-
-class TransformConfigTests(cros_test_lib.TestCase):
-
-  def testBasicTransform(self):
-    result = cros_config_test_schema.TransformConfig(BASIC_CONFIG)
-    json_dict = json.loads(result)
-    self.assertEqual(1, len(json_dict))
-
-    json_obj = libcros_schema.GetNamedTuple(json_dict)
-    self.assertEqual(2, len(json_obj.chromeos.devices))
-
-    device = json_obj.chromeos.devices[0]
-    self.assertEqual('nautilus', device.device_name)
-    self.assertEqual(3, len(device.command_groups))
-
-    device = json_obj.chromeos.devices[1]
-    self.assertEqual('nautiluslte', device.device_name)
-    self.assertEqual(4, len(device.command_groups))
-
-  def testTransformConfig_NoMatch(self):
-    result = cros_config_test_schema.TransformConfig(
-        BASIC_CONFIG, device_filter='abc123')
-    json_dict = json.loads(result)
-    json_obj = libcros_schema.GetNamedTuple(json_dict)
-    self.assertEqual(0, len(json_obj.chromeos.devices))
-
-  def testTransformConfig_FilterMatch(self):
-    result = cros_config_test_schema.TransformConfig(
-        BASIC_CONFIG, device_filter='nautilus')
-    json_dict = json.loads(result)
-    json_obj = libcros_schema.GetNamedTuple(json_dict)
-    self.assertEqual(1, len(json_obj.chromeos.devices))
-    device = json_obj.chromeos.devices[0]
-    self.assertEqual('nautilus', device.device_name)
-    self.assertEqual(3, len(device.command_groups))
-
-
-class MainTests(cros_test_lib.TempDirTestCase):
-
-  def testMainImportNoFilter(self):
-    output = os.path.join(self.tempdir, 'output.json')
-    cros_config_test_schema.Start(
-        os.path.join(this_dir, 'test_data/cros_config_test_device.yaml'),
-        None,
-        output,
-        None)
-    json_dict = json.loads(osutils.ReadFile(output))
-    json_obj = libcros_schema.GetNamedTuple(json_dict)
-    self.assertEqual(2, len(json_obj.chromeos.devices))
-
-    device = json_obj.chromeos.devices[0]
-    self.assertEqual('nautilus', device.device_name)
-    self.assertEqual(4, len(device.command_groups))
-
-    device = json_obj.chromeos.devices[1]
-    self.assertEqual('nautiluslte', device.device_name)
-    self.assertEqual(5, len(device.command_groups))
-
-  def testMainImportFilterNautilus(self):
-    output = os.path.join(self.tempdir, 'output.json')
-    cros_config_test_schema.Start(
-        os.path.join(this_dir, 'test_data/cros_config_test_device.yaml'),
-        'nautilus',
-        output,
-        None)
-    json_dict = json.loads(osutils.ReadFile(output))
-    json_obj = libcros_schema.GetNamedTuple(json_dict)
-    self.assertEqual(1, len(json_obj.chromeos.devices))
-
-    device = json_obj.chromeos.devices[0]
-    self.assertEqual('nautilus', device.device_name)
-    self.assertEqual(4, len(device.command_groups))
-
-  def testMainImportFilterNautilusLte(self):
-    output = os.path.join(self.tempdir, 'output.json')
-    cros_config_test_schema.Start(
-        os.path.join(this_dir, 'test_data/cros_config_test_device.yaml'),
-        'nautiluslte',
-        output,
-        None)
-    json_dict = json.loads(osutils.ReadFile(output))
-    json_obj = libcros_schema.GetNamedTuple(json_dict)
-    self.assertEqual(1, len(json_obj.chromeos.devices))
-
-    device = json_obj.chromeos.devices[0]
-    self.assertEqual('nautiluslte', device.device_name)
-    self.assertEqual(5, len(device.command_groups))
diff --git a/chromeos-config/cros_config_host/fdt.py b/chromeos-config/cros_config_host/fdt.py
new file mode 100644
index 0000000..4dabd02
--- /dev/null
+++ b/chromeos-config/cros_config_host/fdt.py
@@ -0,0 +1,403 @@
+# Copyright 2017 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+# Taken from U-Boot v2017.07 (tools/dtoc)
+
+"""The higher level FDT library for parsing and interfacing with a dtb."""
+
+from __future__ import print_function
+
+from collections import OrderedDict
+import struct
+
+import libfdt
+
+import fdt_util
+
+# This deals with a device tree, presenting it as an assortment of Node and
+# Prop objects, representing nodes and properties, respectively. This file
+# contains the base classes and defines the high-level API. You can use
+# FdtScan() as a convenience function to create and scan an Fdt.
+
+# This implementation uses a libfdt Python library to access the device tree,
+# so it is fairly efficient.
+
+# A list of types we support
+(TYPE_BYTE, TYPE_INT, TYPE_STRING, TYPE_BOOL, TYPE_INT64) = range(5)
+
+
+def CheckErr(errnum, msg):
+  """Checks for a lib fdt error and prints it out if one exists.
+
+  Args:
+    errnum: The error number returned by lib fdt
+    msg: The message to bundle with the error print
+  """
+  if errnum:
+    raise ValueError('Error %d: %s: %s' %
+                     (errnum, libfdt.fdt_strerror(errnum), msg))
+
+
+class Prop(object):
+  """A device tree property
+
+  Properties:
+    fdt: Device tree object
+    name: Property name (as per the device tree)
+    value: Property value as a string of bytes, or a list of strings of
+      bytes
+    type: Value type
+    data: The string data
+  """
+  def __init__(self, fdt, node, offset, name, data):
+    self.fdt = fdt
+    self.node = node
+    self._offset = offset
+    self.name = name
+    self.value = None
+    self.data = str(data)
+    if not data:
+      self.type = TYPE_BOOL
+      self.value = True
+      return
+    self.type, self.value = self.BytesToValue(data)
+
+  def GetPhandle(self):
+    """Get a (single) phandle value from a property
+
+    Gets the phandle value from a property and returns it as an integer
+    """
+    return fdt_util.fdt32_to_cpu(self.value[:4])
+
+  def LookupPhandle(self):
+    """Look up a node by its phandle (treating this property as a phandle)
+
+    Returns:
+      Node object, or None if not found
+    """
+    return self.fdt.LookupPhandle(self.GetPhandle())
+
+  def BytesToValue(self, data):
+    """Converts a string of bytes into a type and value
+
+    Args:
+      data: A string containing bytes
+
+    Returns:
+      A tuple:
+        Type of data
+        Data, either a single element or a list of elements. Each
+        element is one of:
+          TYPE_STRING: string value from the property
+          TYPE_INT: a byte-swapped integer stored as a 4-byte string
+          TYPE_BYTE: a byte stored as a single-byte string
+    """
+    data = str(data)
+    size = len(data)
+    strings = data.split('\0')
+    is_string = True
+    count = len(strings) - 1
+    if count > 0 and not strings[-1]:
+      for string in strings[:-1]:
+        if not string:
+          is_string = False
+          break
+        for ch in string:
+          if ch < ' ' or ch > '~':
+            is_string = False
+            break
+    else:
+      is_string = False
+    if is_string:
+      if count == 1:
+        return TYPE_STRING, strings[0]
+      else:
+        return TYPE_STRING, strings[:-1]
+    if size % 4:
+      if size == 1:
+        return TYPE_BYTE, data[0]
+      else:
+        return TYPE_BYTE, list(data)
+    val = []
+    for i in range(0, size, 4):
+      val.append(data[i:i + 4])
+    if size == 4:
+      return TYPE_INT, val[0]
+    else:
+      return TYPE_INT, val
+
+  def GetEmpty(self, value_type):
+    """Get an empty / zero value of the given type
+
+    Returns:
+      A single value of the given type
+    """
+    if value_type == TYPE_BYTE:
+      return chr(0)
+    elif value_type == TYPE_INT:
+      return struct.pack('<I', 0)
+    elif value_type == TYPE_STRING:
+      return ''
+    else:
+      return True
+
+
+class Node(object):
+  """A device tree node
+
+  Properties:
+    offset: Integer offset in the device tree
+    name: Device tree node tname
+    path: Full path to node, along with the node name itself
+    fdt: Device tree object
+    subnodes: A list of subnodes for this node, each a Node object
+    props: A dict of properties for this node, each a Prop object.
+      Keyed by property name
+  """
+  def __init__(self, fdt, parent, offset, name, path):
+    self.fdt = fdt
+    self.parent = parent
+    self.offset = offset
+    self.name = name
+    self.path = path
+    self.subnodes = OrderedDict()
+    self.props = OrderedDict()
+
+  def FindNode(self, name):
+    """Find a node given its name
+
+    Args:
+      name: Node name to look for
+
+    Returns:
+      Node object if found, else None
+    """
+    for subnode in self.subnodes.values():
+      if subnode.name == name:
+        return subnode
+    return None
+
+  def Offset(self):
+    """Returns the offset of a node, after checking the cache
+
+    This should be used instead of self.offset directly, to ensure that
+    the cache does not contain invalid offsets.
+    """
+    self.fdt.CheckCache()
+    return self.offset
+
+  def Scan(self):
+    """Scan a node's properties and subnodes
+
+    This fills in the props and subnodes properties, recursively
+    searching into subnodes so that the entire tree is built.
+    """
+    self.props = self.fdt.GetProps(self)
+    phandle = self.props.get('phandle')
+    if phandle:
+      val = fdt_util.fdt32_to_cpu(phandle.value)
+      self.fdt.phandle_to_node[val] = self
+
+    offset = libfdt.fdt_first_subnode(self.fdt.GetFdt(), self.Offset())
+    while offset >= 0:
+      sep = '' if self.path[-1] == '/' else '/'
+      name = self.fdt.fdt_obj.get_name(offset)
+      path = self.path + sep + name
+      node = Node(self.fdt, self, offset, name, path)
+      self.subnodes[name] = node
+
+      node.Scan()
+      offset = libfdt.fdt_next_subnode(self.fdt.GetFdt(), offset)
+
+  def Refresh(self, my_offset):
+    """Fix up the offset for each node, recursively
+
+    Note: This does not take account of property offsets - these will not
+    be updated.
+    """
+    if self.offset != my_offset:
+      #print '%s: %d -> %d\n' % (self.path, self._offset, my_offset)
+      self.offset = my_offset
+    offset = libfdt.fdt_first_subnode(self.fdt.GetFdt(), self.offset)
+    for subnode in self.subnodes.values():
+      subnode.Refresh(offset)
+      offset = libfdt.fdt_next_subnode(self.fdt.GetFdt(), offset)
+
+  def DeleteProp(self, prop_name):
+    """Delete a property of a node
+
+    The property is deleted and the offset cache is invalidated.
+
+    Args:
+      prop_name: Name of the property to delete
+
+    Raises:
+      ValueError if the property does not exist
+    """
+    CheckErr(libfdt.fdt_delprop(self.fdt.GetFdt(), self.Offset(), prop_name),
+             "Node '%s': delete property: '%s'" % (self.path, prop_name))
+    del self.props[prop_name]
+    self.fdt.Invalidate()
+
+
+class Fdt(object):
+  """Provides simple access to a flat device tree blob using libfdts.
+
+  Properties:
+    infile: The File to read the dtb from
+    _root: Root of device tree (a Node object)
+  """
+  def __init__(self, infile):
+    self._root = None
+    self._cached_offsets = False
+    self.phandle_to_node = OrderedDict()
+    self._fdt = bytearray(infile.read())
+    self.fdt_obj = libfdt.Fdt(self._fdt)
+
+  def LookupPhandle(self, phandle):
+    """Look up a node by its phandle
+
+    Args:
+      phandle: Phandle to look up (integer > 0)
+
+    Returns:
+      Node object, or None if not found
+    """
+    return self.phandle_to_node.get(phandle)
+
+  def Scan(self):
+    """Scan a device tree, building up a tree of Node objects
+
+    This fills in the self._root property
+
+    Args:
+      root: Ignored
+
+    TODO(sjg@chromium.org): Implement the 'root' parameter
+    """
+    self._root = self.Node(self, None, 0, '/', '/')
+    self._root.Scan()
+
+  def GetRoot(self):
+    """Get the root Node of the device tree
+
+    Returns:
+      The root Node object
+    """
+    return self._root
+
+  def GetNode(self, path):
+    """Look up a node from its path
+
+    Args:
+      path: Path to look up, e.g. '/microcode/update@0'
+
+    Returns:
+      Node object, or None if not found
+    """
+    node = self._root
+    for part in path.split('/')[1:]:
+      node = node.FindNode(part)
+      if not node:
+        return None
+    return node
+
+  def Flush(self, outfile):
+    """Flush device tree changes to the given file
+
+    Args:
+      outfile: The file to write the device tree out to
+    """
+    outfile.write(self._fdt)
+
+  def Pack(self):
+    """Pack the device tree down to its minimum size
+
+    When nodes and properties shrink or are deleted, wasted space can
+    build up in the device tree binary.
+    """
+    CheckErr(libfdt.fdt_pack(self._fdt), 'pack')
+    fdt_len = libfdt.fdt_totalsize(self._fdt)
+    del self._fdt[fdt_len:]
+
+  def GetFdt(self):
+    """Get the contents of the FDT
+
+    Returns:
+      The FDT contents as a string of bytes
+    """
+    return self._fdt
+
+
+  def GetProps(self, node):
+    """Get all properties from a node.
+
+    Args:
+      node: A Node object to get the properties for.
+
+    Returns:
+      A dictionary containing all the properties, indexed by node name.
+      The entries are Prop objects.
+
+    Raises:
+      ValueError: if the node does not exist.
+    """
+    props_dict = OrderedDict()
+    poffset = libfdt.fdt_first_property_offset(self._fdt, node.offset)
+    while poffset >= 0:
+      p = self.fdt_obj.get_property_by_offset(poffset)
+      prop = Prop(node.fdt, node, poffset, p.name, p.value)
+      props_dict[prop.name] = prop
+
+      poffset = libfdt.fdt_next_property_offset(self._fdt, poffset)
+    return props_dict
+
+  def Invalidate(self):
+    """Mark our offset cache as invalid"""
+    self._cached_offsets = False
+
+  def CheckCache(self):
+    """Refresh the offset cache if needed"""
+    if self._cached_offsets:
+      return
+    self.Refresh()
+    self._cached_offsets = True
+
+  def Refresh(self):
+    """Refresh the offset cache"""
+    self._root.Refresh(0)
+
+  def GetStructOffset(self, offset):
+    """Get the file offset of a given struct offset
+
+    Args:
+      offset: Offset within the 'struct' region of the device tree
+
+    Returns:
+      Position of @offset within the device tree binary
+    """
+    return libfdt.fdt_off_dt_struct(self._fdt) + offset
+
+  @classmethod
+  def Node(cls, fdt, parent, offset, name, path):
+    """Create a new node
+
+    This is used by Fdt.Scan() to create a new node using the correct
+    class.
+
+    Args:
+      fdt: Fdt object
+      parent: Parent node, or None if this is the root node
+      offset: Offset of node
+      name: Node name
+      path: Full path to node
+    """
+    node = Node(fdt, parent, offset, name, path)
+    return node
+
+def FdtScan(fname):
+  """Returns a new Fdt object from the implementation we are using"""
+  with open(fname) as fd:
+    dtb = Fdt(fd)
+  dtb.Scan()
+  return dtb
diff --git a/chromeos-config/cros_config_host/fdt_unittest.py b/chromeos-config/cros_config_host/fdt_unittest.py
new file mode 100755
index 0000000..192f77e
--- /dev/null
+++ b/chromeos-config/cros_config_host/fdt_unittest.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python2
+# Copyright 2017 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""The unit test suite for the fdt.py library"""
+
+from __future__ import print_function
+
+import os
+import unittest
+
+from . import fdt, fdt_util
+
+DTS_FILE = '../libcros_config/test.dts'
+ANOTHER_FIRMWARE_NAMES = ['bcs-overlay', 'ec-image', 'main-image',
+                          'main-rw-image', 'key-id', 'extra', 'tools']
+
+
+class FdtLibTest(unittest.TestCase):
+  """The unit test suite for the fdt.py library"""
+  def setUp(self):
+    path = os.path.join(os.path.dirname(__file__), DTS_FILE)
+    temp_file = None
+    try:
+      (fname, temp_file) = fdt_util.EnsureCompiled(path)
+      with open(fname) as fdt_file:
+        self.test_fdt = fdt.Fdt(fdt_file)
+    finally:
+      if temp_file is not None:
+        os.remove(temp_file.name)
+    self.test_fdt.Scan()
+
+  def testFdtScan(self):
+    self.assertIsNotNone(self.test_fdt.GetRoot())
+
+  def testGetModels(self):
+    models_node = self.test_fdt.GetNode('/chromeos/models')
+    models = [m.name for m in models_node.subnodes.values()]
+    self.assertSequenceEqual(models, ['some', 'another', 'whitelabel'])
+
+  def testPropertyOrder(self):
+    firmware = self.test_fdt.GetNode('/chromeos/models/another/firmware')
+
+    self.assertSequenceEqual(firmware.props.keys(), ANOTHER_FIRMWARE_NAMES)
+
+  def testGetStringProperty(self):
+    firmware = self.test_fdt.GetNode('/chromeos/models/another/firmware')
+    bcs_overlay = firmware.props['bcs-overlay'].value
+    self.assertEqual(bcs_overlay, 'overlay-another-private')
+    firmware = self.test_fdt.GetNode('/chromeos/models/another')
+    value = firmware.props['wallpaper'].value
+    self.assertEqual(value, 'default')
+
+  def testLookupPhandle(self):
+    firmware = self.test_fdt.GetNode('/chromeos/models/some/firmware')
+    shared = self.test_fdt.GetNode('/chromeos/family/firmware/some')
+    self.assertEqual(shared, firmware.props['shares'].LookupPhandle())
+
+    # Phandles are sequentially allocated integers > 0, so 0 is invalid
+    self.assertEqual(None, self.test_fdt.LookupPhandle(0))
+
+  def testGetStringListProperty(self):
+    firmware = self.test_fdt.GetNode('/chromeos/models/another')
+    str_list = firmware.props['string-list'].value
+    self.assertEqual(str_list, ['default', 'more'])
+
+  def testGetBoolProperty(self):
+    firmware = self.test_fdt.GetNode('/chromeos/models/another')
+    present = firmware.props['bool-prop'].value
+    self.assertEqual(present, True)
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/chromeos-config/cros_config_host/fdt_util.py b/chromeos-config/cros_config_host/fdt_util.py
new file mode 100644
index 0000000..3c5e46e
--- /dev/null
+++ b/chromeos-config/cros_config_host/fdt_util.py
@@ -0,0 +1,154 @@
+# Copyright 2017 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+# Taken from U-Boot v2017.07 (tools/dtoc)
+
+"""Utility functions for fdt."""
+
+from __future__ import print_function
+
+import os
+import struct
+import sys
+import tempfile
+
+from chromite.lib import cros_build_lib
+
+
+def fdt32_to_cpu(val):
+  """Convert a device tree cell to an integer
+
+  Args:
+    val: Value to convert (4-character string representing the cell value)
+
+  Returns:
+    A native-endian integer value
+  """
+  if sys.version_info > (3, 0):
+    if isinstance(val, bytes):
+      val = val.decode('utf-8')
+    val = val.encode('raw_unicode_escape')
+  return struct.unpack('>I', val)[0]
+
+
+def fdt_cells_to_cpu(val, cells):
+  """Convert one or two cells to a long integer
+
+  Args:
+    val: Value to convert (array of one or more 4-character strings)
+    cells: Cell number
+
+  Returns:
+    A native-endian long value
+  """
+  if not cells:
+    return 0
+  out = long(fdt32_to_cpu(val[0]))
+  if cells == 2:
+    out = out << 32 | fdt32_to_cpu(val[1])
+  return out
+
+def GetInt(node, propname, default=None):
+  prop = node.props.get(propname)
+  if not prop:
+    return default
+  value = fdt32_to_cpu(prop.value)
+  if type(value) == type(list):
+    raise ValueError("Node '%s' property '%s' has list value: expecting"
+                     "a single integer" % (node.name, propname))
+  return value
+
+
+def GetString(node, propname, default=None):
+  prop = node.props.get(propname)
+  if not prop:
+    return default
+  value = prop.value
+  if type(value) == type(list):
+    raise ValueError("Node '%s' property '%s' has list value: expecting"
+                     "a single string" % (node.name, propname))
+  return value
+
+
+def GetBool(node, propname, default=False):
+  if propname in node.props:
+    return True
+  return default
+
+
+def CompileDts(dts_input):
+  """Compiles a single .dts file
+
+  Args:
+    dts_input: Input filename
+
+  Returns:
+    Tuple:
+      Filename of resulting .dtb file
+  """
+  dtb_output = tempfile.NamedTemporaryFile(suffix='.dtb', delete=False)
+  args = ['-I', 'dts', '-o', dtb_output.name, '-O', 'dtb']
+  args.append(dts_input)
+  cros_build_lib.RunCommand(['dtc'] + args, quiet=True)
+  return dtb_output.name, dtb_output
+
+
+def EnsureCompiled(fname):
+  """Compile an fdt .dts source file into a .dtb binary blob if needed.
+
+  Args:
+    fname: Filename (if .dts it will be compiled). It not it will be
+      left alone
+
+  Returns:
+    Tuple:
+      Filename of resulting .dtb file
+      tempfile object to unlink after the caller is finished
+  """
+  out = None
+  _, ext = os.path.splitext(fname)
+  if ext == '.dtb':
+    return fname, None
+  elif ext == '.md':
+    out = tempfile.NamedTemporaryFile(suffix='.dts', delete=False)
+    out.write('/dts-v1/;\n/ {\n')
+    with open(fname) as fd:
+      adding = False
+      for line in fd:
+        if line == '```\n':
+          adding = not adding
+          continue
+        if adding:
+          out.write(line)
+    out.write('};\n')
+    out.close()
+    dts_input = out.name
+  else:
+    dts_input = fname
+  result = CompileDts(dts_input)
+  if out:
+    os.unlink(out.name)
+  return result
+
+
+def CompileAll(fnames):
+  """Compile a selection of .dtsi files
+
+  This inserts the Chrome OS header and then includes the files one by one to
+  ensure that error messages quote the correct file/line number.
+
+  Args:
+    fnames: List of .dtsi files to compile
+  """
+  out = tempfile.NamedTemporaryFile(suffix='.dts', delete=False)
+  out.write('/dts-v1/;\n')
+  out.write('/ { chromeos { family: family { }; models: models { };')
+  out.write('schema { target-dirs { }; }; }; };\n')
+  for fname in fnames:
+    out.write('/include/ "%s"\n' % fname)
+  out.close()
+  dts_input = out.name
+  result = CompileDts(dts_input)
+  if out:
+    os.unlink(out.name)
+  return result
diff --git a/chromeos-config/cros_config_host/libcros_config_host.py b/chromeos-config/cros_config_host/libcros_config_host.py
index 7117b02..1b16528 100644
--- a/chromeos-config/cros_config_host/libcros_config_host.py
+++ b/chromeos-config/cros_config_host/libcros_config_host.py
@@ -13,11 +13,15 @@
 import os
 import sys
 
+import libcros_config_host_fdt
 import libcros_config_host_json
 
 UNIBOARD_CONFIG_INSTALL_DIR = 'usr/share/chromeos-config'
 
-def CrosConfig(fname=None, model_filter_regex=None):
+# We support two configuration file format
+(FORMAT_FDT, FORMAT_YAML) = range(2)
+
+def CrosConfig(fname=None):
   """Create a new CrosConfigBaseImpl object
 
   This is in a separate function to allow us to (in the future) support YAML,
@@ -25,8 +29,11 @@
 
   Args:
     fname: Filename of config file
-    model_filter_regex: Only returns configs that match the filter
   """
+  if fname and ('.yaml' in fname or '.json' in fname):
+    config_format = FORMAT_YAML
+  else:
+    config_format = FORMAT_FDT
   if not fname:
     if 'SYSROOT' not in os.environ:
       raise ValueError('No master configuration is available outside the '
@@ -36,10 +43,20 @@
         UNIBOARD_CONFIG_INSTALL_DIR,
         'yaml',
         'config.yaml')
+    if os.path.exists(fname):
+      config_format = FORMAT_YAML
+    else:
+      fname = os.path.join(
+          os.environ['SYSROOT'], UNIBOARD_CONFIG_INSTALL_DIR, 'config.dtb')
+      config_format = FORMAT_FDT
   if fname == '-':
     infile = sys.stdin
   else:
     infile = open(fname)
 
-  return libcros_config_host_json.CrosConfigJson(
-      infile, model_filter_regex=model_filter_regex)
+  if config_format == FORMAT_FDT:
+    return libcros_config_host_fdt.CrosConfigFdt(infile)
+  elif config_format == FORMAT_YAML:
+    return libcros_config_host_json.CrosConfigJson(infile)
+  else:
+    raise ValueError("Invalid config format '%s' requested" % config_format)
diff --git a/chromeos-config/cros_config_host/libcros_config_host_base.py b/chromeos-config/cros_config_host/libcros_config_host_base.py
index db49765..daceae3 100644
--- a/chromeos-config/cros_config_host/libcros_config_host_base.py
+++ b/chromeos-config/cros_config_host/libcros_config_host_base.py
@@ -6,29 +6,8 @@
 from __future__ import print_function
 
 from collections import namedtuple, OrderedDict
-
 import os
 
-from cros_config_schema import GetValidSchemaProperties
-
-# Defines a list of undiffable properties between the device-tree impl and
-# the YAML based impl.  These properties are accounted for in other API level
-# diffs, but they changed between the schemas and therefore can't be diffed.
-INCOMPATIBLE_PROPERTIES = {
-  '/' : [
-    'brand-code',         # Moved due to Whitelabel/Product changes.
-    'name',               # Only in YAML
-  ],
-  '/firmware' : [
-    'key-id',             # Moved to firmware-signing in YAML,
-    'name',               # Doesn't exist in DT impl
-  ],
-  '/firmware-signing' : [
-    'key-id',             # Moved from firmware in DT
-    'signature-id',       # Only in YAML
-  ],
-}
-
 # Represents a single touch firmware file which needs to be installed:
 #   source: source filename of firmware file. This is installed in a
 #       directory in the root filesystem
@@ -61,15 +40,17 @@
 #       image
 #   pd_image_uri: URI to use to obtain the PD (Power Delivery controller)
 #       firmware image
+#   extra: List of extra files to include in the firmware update, each a string
+#   create_bios_rw_image: True to create a RW BIOS image
+#   tools: List of tools to include in the firmware update
 #   sig_id: Signature ID to put in the setvars.sh file. This is normally the
 #       same as the model, since that is what we use for signature ID. But for
 #       zero-touch whitelabel this is 'sig-id-in-customization-id' since we do
 #       not know the signature ID until we look up in VPD.
-#   brand-code: Uniquely identifies a given brand (see go/chromeos-rlz)
 FirmwareInfo = namedtuple('FirmwareInfo', [
     'model', 'shared_model', 'key_id', 'have_image', 'bios_build_target',
     'ec_build_target', 'main_image_uri', 'main_rw_image_uri', 'ec_image_uri',
-    'pd_image_uri', 'sig_id', 'brand_code'
+    'pd_image_uri', 'extra', 'create_bios_rw_image', 'tools', 'sig_id'
 ])
 
 class PathComponent(object):
@@ -221,15 +202,6 @@
     """
     pass
 
-  def GetBluetoothFiles(self):
-    """Get a list of bluetooth config files
-
-    Returns:
-      List of BaseFile objects representing the bluetooth files referenced
-      by this device.
-    """
-    pass
-
   def GetThermalFiles(self):
     """Get a list of thermal files
 
@@ -281,37 +253,13 @@
       Dictionary that maps method call onto return config.
     """
     result = {}
-    result['ListModels'] = self.GetModelList()
     result['GetFirmwareUris'] = self.GetFirmwareUris()
     result['GetTouchFirmwareFiles'] = self.GetTouchFirmwareFiles()
     result['GetArcFiles'] = self.GetArcFiles()
     result['GetAudioFiles'] = self.GetAudioFiles()
-    bluetooth_files = self.GetBluetoothFiles()
-    if bluetooth_files:
-      result['GetBluetoothFiles'] = bluetooth_files
     result['GetThermalFiles'] = self.GetThermalFiles()
     result['GetFirmwareInfo'] = self.GetFirmwareInfo()
-    for target in ['coreboot', 'ec']:
-      result['GetFirmwareBuildTargets_%s' % target] = \
-        self.GetFirmwareBuildTargets(target)
-    result['GetFirmwareBuildCombinations'] = \
-      self.GetFirmwareBuildCombinations(['coreboot', 'ec'])
     result['GetWallpaperFiles'] = self.GetWallpaperFiles()
-
-    schema_properties = GetValidSchemaProperties()
-    for device in self.GetDeviceConfigs():
-      value_map = {}
-      for path in schema_properties:
-        for schema_property in schema_properties[path]:
-          # Exclude incompatible properties for now until the migration
-          # is done and we aren't trying to diff properties any longer.
-          if schema_property not in INCOMPATIBLE_PROPERTIES.get(path, []):
-            prop_value = device.GetProperty(path, schema_property)
-            # Only dump populated values; this makes it so the config dumps
-            # don't need to be updated when new schema attributes are added.
-            if prop_value:
-              value_map['%s::%s' % (path, schema_property)] = prop_value
-      result['GetProperty_%s' % device.GetName()] = value_map
     return result
 
 
@@ -394,20 +342,6 @@
 
     return sorted(file_set, key=lambda files: files.source)
 
-  def GetBluetoothFiles(self):
-    """Get a list of unique bluetooth files for all devices
-
-    Returns:
-      List of BaseFile objects representing all the bluetooth files referenced
-      by all devices
-    """
-    file_set = set()
-    for device in self.GetDeviceConfigs():
-      for files in device.GetBluetoothFiles():
-        file_set.add(files)
-
-    return sorted(file_set, key=lambda files: files.source)
-
   def GetFirmwareBuildTargets(self, target_type):
     """Returns a list of all firmware build-targets of the given target type.
 
@@ -435,9 +369,6 @@
         for ec_extra in ('base', 'cr50'):
           if ec_extra in device_targets:
             build_targets.append(device_targets[ec_extra])
-        if 'ec_extras' in device_targets:
-          for extra_target in device_targets['ec_extras']:
-            build_targets.append(extra_target)
     return sorted(set(build_targets))
 
   def GetFirmwareBuildCombinations(self, components):
@@ -462,7 +393,7 @@
       # Skip device_targetss with no build targets
       if not device_targets:
         continue
-      targets = [device_targets[c] for c in components if c in device_targets]
+      targets = [device_targets[c] for c in components]
 
       # Always name firmware combinations after the 'coreboot' name.
       # TODO(teravest): Add a 'name' field.
@@ -524,6 +455,14 @@
     """
     return sorted(set([device.GetName() for device in self.GetDeviceConfigs()]))
 
+  def GetFirmwareScript(self):
+    """Obtain the packer script to use. Always updater4.sh
+
+    Returns:
+      Filename of packer script to use (e.g. 'updater4.sh')
+    """
+    return 'updater4.sh'
+
   def GetFirmwareInfo(self):
     firmware_info = OrderedDict()
     for name in self.GetModelList():
diff --git a/chromeos-config/cros_config_host/libcros_config_host_fdt.py b/chromeos-config/cros_config_host/libcros_config_host_fdt.py
new file mode 100644
index 0000000..255daf5
--- /dev/null
+++ b/chromeos-config/cros_config_host/libcros_config_host_fdt.py
@@ -0,0 +1,723 @@
+# Copyright 2017 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Chrome OS Configuration access library (FDT).
+
+Chrome OS Configuration access library for a master configuration using flat
+device tree.
+"""
+
+from __future__ import print_function
+
+from collections import OrderedDict
+
+import os
+import fdt
+import validate_config
+
+from libcros_config_host_base import BaseFile, CrosConfigBaseImpl, DeviceConfig
+from libcros_config_host_base import FirmwareInfo, TouchFile
+
+def GetFilename(node_path, props, fname_template):
+  """Create a filename based on the given template and properties
+
+  Args:
+    node_path: Path of the node generating this filename (for error
+        reporting only)
+    props: Dict of properties which can be used in the template:
+        key: Variable name
+        value: Value of that variable
+    fname_template: Filename template
+  """
+  template = fname_template.replace('$', '')
+  try:
+    return template.format(props, **props)
+  except KeyError as e:
+    raise ValueError(("node '%s': Format string '%s' has properties '%s' "
+                      "but lacks '%s'")
+                     % (node_path, template, props.keys(), e.message))
+
+
+def GetPropFilename(node_path, props, fname_prop):
+  """Create a filename based on the given template and properties
+
+  Args:
+    node_path: Path of the node generating this filename (for error
+        reporting only)
+    props: Dict of properties which can be used in the template:
+        key: Variable name
+        value: Value of that variable
+    fname_prop: Name of property containing the template
+  """
+  template = props[fname_prop]
+  return GetFilename(node_path, props, template)
+
+
+class CrosConfigFdt(CrosConfigBaseImpl):
+  """Flat Device Tree implementation of CrosConfig.
+
+  This uses a device-tree file to hold this config. This provides efficient
+  run-time access since there is no need to parse the whole file. It also
+  supports links from one node to another, reducing redundancy in the config.
+
+  Properties:
+    phandle_to_node:
+        Map of phandles to the assocated CrosConfigFdt.Node:
+        key: Integer phandle value (>= 1)
+        value: Associated CrosConfigFdt.Node object
+    family: Family node (CrosConfigFdt.Node object)
+    models: All models in the CrosConfigFdt tree, in the form of a
+            dictionary:
+            <model name: string, model: CrosConfigFdt.Node>
+    phandle_props: Set of properties which can be phandles (i.e. point to
+        another part of the config)
+    root: Root node (CrosConigImpl.Node object)
+    validator: Validator for the config (CrosConfigValidator object)
+  """
+  def __init__(self, infile):
+    self.infile = infile
+    self.models = OrderedDict()
+    self.validator = validate_config.GetValidator()
+    self.phandle_props = self.validator.GetPhandleProps()
+    self._fdt = fdt.Fdt(self.infile)
+    self._fdt.Scan()
+    self.phandle_to_node = {}
+    self.root = CrosConfigFdt.MakeNode(self, self._fdt.GetRoot())
+    self.family = self.root.subnodes['chromeos'].subnodes['family']
+
+  def GetDeviceConfigs(self):
+    return self.models.values()
+
+  def _GetProperty(self, absolute_path, property_name):
+    """Internal function to read a property from anywhere in the tree
+
+    Args:
+      absolute_path: within the root node (e.g. '/chromeos/family/firmware')
+      property_name: Name of property to get
+
+    Returns:
+      Property object, or None if not found
+    """
+    return self.root.PathProperty(absolute_path, property_name)
+
+  def GetNode(self, absolute_path):
+    """Internal function to get a node from anywhere in the tree
+
+    Args:
+      absolute_path: within the root node (e.g. '/chromeos/family/firmware')
+
+    Returns:
+      Node object, or None if not found
+    """
+    return self.root.PathNode(absolute_path)
+
+  def GetFamilyNode(self, relative_path):
+    return self.family.PathNode(relative_path)
+
+  def GetFamilyProperty(self, relative_path, property_name):
+    """Read a property from a family node
+
+    Args:
+      relative_path: Relative path within the family (e.g. '/firmware')
+      property_name: Name of property to get
+
+    Returns:
+      Property object, or None if not found
+    """
+    return self.family.PathProperty(relative_path, property_name)
+
+  @staticmethod
+  def GetTargetDirectories():
+    """Gets a dict of directory targets for each PropFile property
+
+    Returns:
+      Dict:
+        key: Property name
+        value: Ansolute path for this property
+    """
+    validator = validate_config.GetValidator()
+    return validator.GetTargetDirectories()
+
+  @staticmethod
+  def GetPhandleProperties():
+    """Gets a dict of properties which contain phandles
+
+    Returns:
+      Dict:
+        key: Property name
+        value: Ansolute path for this property
+    """
+    validator = validate_config.GetValidator()
+    return validator.GetPhandleProps()
+
+  class Node(DeviceConfig):
+    """Represents a single node in the CrosConfig tree, including Model.
+
+    A node can have several subnodes nodes, as well as several properties. Both
+    can be accessed via .subnodes and .properties respectively. A few helpers
+    are also provided to make node traversal a little easier.
+
+    Properties:
+      name: The name of the this node.
+      subnodes: Child nodes, in the form of a dictionary:
+                <node name: string, child node: CrosConfigFdt.Node>
+      properties: All properties attached to this node in the form of a
+                  dictionary: <name: string,
+                  property: CrosConfigFdt.Property>
+    """
+
+    def __init__(self, cros_config, fdt_node):
+      self.cros_config = cros_config
+      self.subnodes = OrderedDict()
+      self.properties = OrderedDict()
+      self.default = None
+      self.submodels = {}
+      self._fdt_node = fdt_node
+      self.name = fdt_node.name
+      # Subnodes are set up in Model.ScanSubnodes()
+      self.properties = OrderedDict((n, CrosConfigFdt.Property(p))
+                                    for n, p in fdt_node.props.iteritems())
+
+    def GetPath(self):
+      """Get the full path to a node.
+
+      Returns:
+        path to node as a string
+      """
+      return self._fdt_node.path
+
+    def FollowPhandle(self, prop_name):
+      """Follow a property's phandle
+
+      Args:
+        prop_name: Property name to check
+
+      Returns:
+        Node that the property's phandle points to, or None if none
+      """
+      prop = self.properties.get(prop_name)
+      if not prop:
+        return None
+      return self.cros_config.phandle_to_node[prop.GetPhandle()]
+
+    def GetName(self):
+      return self.name
+
+    def GetProperties(self, path):
+      result = {}
+      node = self.PathNode(path)
+      if node and node.properties:
+        for prop in node.properties.values():
+          result[prop.name] = prop.value
+      return result
+
+    def FollowShare(self):
+      """Follow a node's shares property
+
+      Some nodes support sharing the properties of another node, e.g. firmware
+      and whitelabel. This follows that share if it can find it. We don't need
+      to be too careful to ignore invalid properties (e.g. whitelabel can only
+      be in a model node) since validation takes care of that.
+
+      Returns:
+        Node that the share points to, or None if none
+      """
+      # TODO(sjg@chromium.org):
+      # Note that the 'or i in self.subnodes' part is for yaml, where we use a
+      # json file within libcros_config_host and this does not support links
+      # between nodes (instead every use of a node blows it out fully at that
+      # point in the tree). For now this is modelled as a subnode with the
+      # name of the phandle, but at some point this will move to merging the
+      # target node's properties with this node, so this whole function will
+      # become unnecessary.
+      share_prop = [i for i in self.cros_config.phandle_props
+                    if i in self.properties or i in self.subnodes]
+      if share_prop:
+        return self.FollowPhandle(share_prop[0])
+      return None
+
+    def PathNode(self, relative_path):
+      """Returns the CrosConfigFdt.Node at the relative path.
+
+      This method is useful for accessing a nested child object at a relative
+      path from a Node (or Model). The path must be separated with '/'
+      delimiters. Return None if the path is invalid.
+
+      Args:
+        relative_path: A relative path string separated by '/', '/thermal'
+
+      Returns:
+        A CrosConfigFdt.Node at the path, or None if it doesn't exist
+      """
+      if not relative_path:
+        return self
+      path_parts = [path for path in relative_path.split('/') if path]
+      if not path_parts:
+        return self
+      part = path_parts[0]
+      if part in self.subnodes:
+        sub_node = self.subnodes[part]
+
+      # Handle a 'shares' property, which means we can grab nodes / properties
+      # from the associated node.
+      else:
+        shared = self.FollowShare()
+        if shared and part in shared.subnodes:
+          sub_node = shared.subnodes[part]
+        else:
+          return None
+      return sub_node.PathNode('/'.join(path_parts[1:]))
+
+    def Property(self, property_name):
+      """Get a property from a node
+
+      This is a trivial function but it does provide some insulation against our
+      internal data structures.
+
+      Args:
+        property_name: Name of property to find
+
+      Returns:
+        CrosConfigFdt.Property object that was found, or None if none
+      """
+      return self.properties.get(property_name)
+
+    def GetStr(self, property_name):
+      """Get a string value from a property
+
+      Args:
+        property_name: Name of property to access
+
+      Returns:
+        String value of property, or '' if not present
+      """
+      prop = self.Property(property_name)
+      return prop.value if prop else ''
+
+    def GetStrList(self, property_name):
+      """Get a string-list value from a property
+
+      Args:
+        property_name: Name of property to access
+
+      Returns:
+        List of strings representing the value of the property, or [] if not
+        present
+      """
+      prop = self.Property(property_name)
+      if not prop:
+        return []
+      if not isinstance(prop.value, list):
+        return [prop.value]
+      return prop.value
+
+    def GetBool(self, property_name):
+      """Get a boolean value from a property
+
+      Args:
+        property_name: Name of property to access
+
+      Returns:
+        True if the property is present, False if not
+      """
+      return property_name in self.properties
+
+    def GetProperty(self, path, name):
+      result = self.PathProperty(path, name)
+      return result.value if result else ''
+
+    def PathProperty(self, relative_path, property_name):
+      """Returns the value of a property relatative to this node
+
+      This function honours the 'shared' property, by following the phandle and
+      searching there, at any component of the path. It also honours the
+      'default' property which is defined for nodes.
+
+      Args:
+        relative_path: A relative path string separated by '/', e.g. '/thermal'
+        property_name: Name of property to look up, e.g 'dptf-dv'
+
+      Returns:
+        String value of property, or None if not found
+      """
+      child_node = self.PathNode(relative_path)
+      if not child_node:
+        shared = self.FollowShare()
+        if shared:
+          child_node = shared.PathNode(relative_path)
+      if child_node:
+        prop = child_node.properties.get(property_name)
+        if not prop:
+          shared = child_node.FollowShare()
+          if shared:
+            prop = shared.properties.get(property_name)
+        if prop:
+          return prop
+      if self.default:
+        return self.default.PathProperty(relative_path, property_name)
+      return None
+
+    @staticmethod
+    def MergeProperties(props, node, ignore=''):
+      if node:
+        for name, prop in node.properties.iteritems():
+          if (name not in props and not name.endswith('phandle') and
+              name != ignore):
+            props[name] = prop.value
+
+    def GetMergedProperties(self, node, phandle_prop):
+      """Obtain properties in two nodes linked by a phandle
+
+      This is used to create a dict of the properties in a main node along with
+      those found in a linked node. The link is provided by a property in the
+      main node containing a single phandle pointing to the linked node.
+
+      The result is a dict that combines both nodes' properties, with the
+      linked node filling in anything missing. The main node's properties take
+      precedence.
+
+      Phandle properties and 'reg' properties are not included. The 'default'
+      node is checked as well.
+
+      Args:
+        node: Main node to obtain properties from
+        phandle_prop: Name of the phandle property to follow to get more
+            properties
+
+      Returns:
+        dict containing property names and values from both nodes:
+          key: property name
+          value: property value
+      """
+      # First get all the property keys/values from the main node
+      props = OrderedDict((prop.name, prop.value)
+                          for prop in node.properties.values()
+                          if prop.name not in [phandle_prop, 'bcs-type', 'reg'])
+
+      # Follow the phandle and add any new ones we find
+      self.MergeProperties(props, node.FollowPhandle(phandle_prop), 'bcs-type')
+      if self.default:
+        # Get the path of this node relative to its model. For example:
+        # '/chromeos/models/pyro/thermal' will return '/thermal' in subpath.
+        # Once crbug.com/775229 is completed, we will be able to do this in a
+        # nicer way.
+        _, _, _, _, subpath = node.GetPath().split('/', 4)
+        default_node = self.default.PathNode(subpath)
+        if default_node:
+          self.MergeProperties(props, default_node, phandle_prop)
+          self.MergeProperties(props, default_node.FollowPhandle(phandle_prop))
+      return OrderedDict(sorted(props.iteritems()))
+
+    def ScanSubnodes(self):
+      """Collect a list of submodels"""
+      if (self.name in self.cros_config.models and
+          'submodels' in self.subnodes.keys()):
+        for name, subnode in self.subnodes['submodels'].subnodes.iteritems():
+          self.submodels[name] = subnode
+
+    def SubmodelPathProperty(self, submodel_name, relative_path, property_name):
+      """Reads a property from a submodel.
+
+      Args:
+        submodel_name: Submodel to read from
+        relative_path: A relative path string separated by '/'.
+        property_name: Name of property to read
+
+      Returns:
+        Value of property as a string, or None if not found
+      """
+      submodel = self.submodels.get(submodel_name)
+      if not submodel:
+        return None
+      return submodel.PathProperty(relative_path, property_name)
+
+    def GetFirmwareConfig(self):
+      """Returns a map hierarchy of the firmware config."""
+      firmware = self.PathNode('/firmware')
+      if not firmware or firmware.GetBool('no-firmware'):
+        return {}
+      return self.GetMergedProperties(firmware, 'shares')
+
+    def SetupModelProps(self, props):
+      props['model'] = self.name
+      props['MODEL'] = self.name.upper()
+
+    def GetTouchFirmwareFiles(self):
+      files = {}
+      for device_name, props in self.GetTouchBspInfo():
+        # Add a special property for the capitalised model name
+        self.SetupModelProps(props)
+        fw_prop_name = 'firmware-bin'
+        fw_target_dir = self.cros_config.validator.GetModelTargetDir(
+            '/touch/ANY', fw_prop_name)
+        if not fw_target_dir:
+          raise ValueError(("node '%s': Property '%s' does not have a " +
+                            'target directory (internal error)') %
+                           (device_name, fw_prop_name))
+        sym_prop_name = 'firmware-sym'
+        sym_target_dir = self.cros_config.validator.GetModelTargetDir(
+            '/touch/ANY', 'firmware-symlink')
+        if not sym_target_dir:
+          raise ValueError(("node '%s': Property '%s' does not have a " +
+                            'target directory (internal error)') %
+                           (device_name, sym_prop_name))
+        src = GetPropFilename(self.GetPath(), props, fw_prop_name)
+        dest = src
+        sym_fname = GetPropFilename(self.GetPath(), props, 'firmware-symlink')
+        files[device_name] = TouchFile(src, os.path.join(fw_target_dir, dest),
+                                       os.path.join(sym_target_dir, sym_fname))
+
+      return files.values()
+
+    def GetTouchBspInfo(self):
+      touch = self.PathNode('/touch')
+      if touch:
+        for device in touch.subnodes.values():
+          props = self.GetMergedProperties(device, 'touch-type')
+          yield [device.name, props]
+
+    def AllPathNodes(self, relative_path, whitelabel=False):
+      """List all path nodes which match the relative path (including submodels)
+
+      This looks in the model and all its submodels for this relative path.
+
+      Args:
+        relative_path: A relative path string separated by '/', '/thermal'
+        whitelabel: True to look in the whitelabels subnode as well (for
+            whiltelabel alternative schema)
+
+      Returns:
+        Dict of:
+          key: Name of this model/submodel
+          value: Node object for this model/submodel
+      """
+      path_nodes = {}
+      node = self.PathNode(relative_path)
+      if node:
+        path_nodes[self.name] = node
+      for submodel_node in self.submodels.values():
+        node = submodel_node.PathNode(relative_path)
+        if node:
+          path_nodes[submodel_node.name] = node
+      if whitelabel:
+        whitelabels = self.PathNode('/whitelabels')
+        if whitelabels:
+          for node in whitelabels.subnodes.values():
+            path_nodes[node.name] = node
+      return path_nodes
+
+    def GetArcFiles(self):
+      files = {}
+      prop = 'hw-features'
+      arc = self.PathNode('/arc')
+      target_dir = self.cros_config.validator.GetModelTargetDir('/arc', prop)
+      if arc and prop in arc.properties:
+        files['base'] = BaseFile(
+            arc.properties[prop].value,
+            os.path.join(target_dir, arc.properties[prop].value))
+      return files.values()
+
+    def GetAudioFiles(self):
+      card = None  # To keep pylint happy since we use it in this function:
+      name = ''
+
+      def _AddAudioFile(prop_name, dest_template, dirname=''):
+        """Helper to add a single audio file
+
+        If present in the configuration, this adds an audio file containing the
+        source and destination file.
+        """
+        if prop_name in props:
+          target_dir = self.cros_config.validator.GetModelTargetDir(
+              '/audio/ANY', prop_name)
+          if not target_dir:
+            raise ValueError(
+                ("node '%s': Property '%s' does not have a " +
+                 'target directory (internal error)') % (card.name, prop_name))
+          files[name, prop_name] = BaseFile(
+              GetPropFilename(self.GetPath(), props, prop_name),
+              os.path.join(target_dir, dirname,
+                           GetFilename(self.GetPath(), props, dest_template)))
+
+      files = {}
+      audio_nodes = self.AllPathNodes('/audio')
+      for name, audio in audio_nodes.iteritems():
+        for card in audio.subnodes.values():
+          # First get all the property keys/values from the current node
+          props = self.GetMergedProperties(card, 'audio-type')
+          self.SetupModelProps(props)
+
+          cras_dir = props.get('cras-config-dir')
+          if not cras_dir:
+            raise ValueError(
+                ("node '%s': Should have a cras-config-dir") % (card.GetPath()))
+          _AddAudioFile('volume', '{card}', cras_dir)
+          _AddAudioFile('dsp-ini', 'dsp.ini', cras_dir)
+
+          # Allow renaming this file to something other than HiFi.conf
+          _AddAudioFile(
+              'hifi-conf',
+              os.path.join('{card}.{ucm-suffix}',
+                           os.path.basename(props['hifi-conf'])))
+          _AddAudioFile('alsa-conf',
+                        '{card}.{ucm-suffix}/{card}.{ucm-suffix}.conf')
+
+          # Non-Intel platforms don't use topology-bin
+          topology = props.get('topology-bin')
+          if topology:
+            _AddAudioFile('topology-bin',
+                          os.path.basename(props.get('topology-bin')))
+
+      return files.values()
+
+    def GetThermalFiles(self):
+      files = {}
+      prop = 'dptf-dv'
+      for name, thermal in self.AllPathNodes('/thermal').iteritems():
+        target_dir = self.cros_config.validator.GetModelTargetDir(
+            '/thermal', prop)
+        if prop in thermal.properties:
+          files[name] = BaseFile(
+              thermal.properties[prop].value,
+              os.path.join(target_dir, thermal.properties[prop].value))
+      return files.values()
+
+    def GetFirmwareInfo(self):
+      whitelabel = self.FollowPhandle('whitelabel')
+      base_model = whitelabel if whitelabel else self
+      firmware_node = self.PathNode('/firmware')
+      base_firmware_node = base_model.PathNode('/firmware')
+
+      # If this model shares firmware with another model, get our
+      # images from there.
+      image_node = base_firmware_node.FollowPhandle('shares')
+      if image_node:
+        # Override the node - use the shared firmware instead.
+        node = image_node
+        shared_model = image_node.name
+      else:
+        node = base_firmware_node
+        shared_model = None
+      key_id = firmware_node.GetStr('key-id')
+      if firmware_node.GetBool('no-firmware'):
+        return {}
+
+      have_image = True
+      if (whitelabel and base_firmware_node and
+          base_firmware_node.Property('sig-id-in-customization-id')):
+        # For zero-touch whitelabel devices, we don't need to generate anything
+        # since the device will never report this model at runtime. The
+        # signature ID will come from customization_id.
+        have_image = False
+
+      # Firmware configuration supports both sharing the same fw image across
+      # multiple models and pinning specific models to different fw revisions.
+      # For context, see:
+      # https://chromium.googlesource.com/chromiumos/platform2/+/master/chromeos-config/README.md
+      #
+      # In order to support this, the firmware build target needs to be
+      # decoupled from models (since it can be shared).  This was supported
+      # in the config with 'build-targets', which drives the actual firmware
+      # build/targets.
+      #
+      # This takes advantage of that same config to determine what the target
+      # FW image will be named in the build output.  This allows a many to
+      # many mapping between models and firmware images.
+      build_node = node.PathNode('build-targets')
+      if build_node:
+        bios_build_target = build_node.GetStr('coreboot')
+        ec_build_target = build_node.GetStr('ec')
+      else:
+        bios_build_target, ec_build_target = None, None
+
+      main_image_uri = node.GetStr('main-image')
+      main_rw_image_uri = node.GetStr('main-rw-image')
+      ec_image_uri = node.GetStr('ec-image')
+      pd_image_uri = node.GetStr('pd-image')
+      create_bios_rw_image = node.GetBool('create-bios-rw-image')
+      extra = node.GetStrList('extra')
+
+      tools = node.GetStrList('tools')
+
+      whitelabels = self.PathNode('/whitelabels')
+      if whitelabels or firmware_node.GetBool('sig-id-in-customization-id'):
+        sig_id = 'sig-id-in-customization-id'
+      else:
+        sig_id = self.name
+
+      info = FirmwareInfo(self.name, shared_model, key_id, have_image,
+                          bios_build_target, ec_build_target, main_image_uri,
+                          main_rw_image_uri, ec_image_uri, pd_image_uri, extra,
+                          create_bios_rw_image, tools, sig_id)
+
+      # Handle the alternative schema, where whitelabels are in a single model
+      # and have whitelabel tags to distinguish them.
+      result = OrderedDict()
+      result[self.name] = info
+      if whitelabels:
+        # Sort these to get a deterministic ordering with yaml (for tests).
+        for name in sorted(whitelabels.subnodes):
+          whitelabel = whitelabels.subnodes[name]
+          key_id = whitelabel.GetStr('key-id')
+          whitelabel_name = '%s-%s' % (base_model.name, whitelabel.name)
+          result[whitelabel_name] = info._replace(
+              model=whitelabel_name,
+              key_id=key_id,
+              have_image=False,
+              sig_id=whitelabel_name)
+      return result
+
+    def GetWallpaperFiles(self):
+      """Get a set of wallpaper files used for this model"""
+      wallpapers = set()
+      for node in self.AllPathNodes('/', whitelabel=True).values():
+        wallpaper = node.properties.get('wallpaper')
+        if wallpaper:
+          wallpapers.add(wallpaper.value)
+      return wallpapers
+
+  @staticmethod
+  def MakeNode(cros_config, fdt_node):
+    """Make a new Node in the tree
+
+    This create a new Node or Model object in the tree and recursively adds all
+    subnodes to it. Any phandles found update the phandle_to_node map.
+
+    Args:
+      cros_config: CrosConfig object
+      fdt_node: fdt.Node object containing the device-tree node
+    """
+    node = CrosConfigFdt.Node(cros_config, fdt_node)
+    if fdt_node.parent and fdt_node.parent.name == 'models':
+      cros_config.models[node.name] = node
+    if 'phandle' in node.properties:
+      phandle = fdt_node.props['phandle'].GetPhandle()
+      cros_config.phandle_to_node[phandle] = node
+    node.default = node.FollowPhandle('default')
+    for subnode in fdt_node.subnodes.values():
+      node.subnodes[subnode.name] = CrosConfigFdt.MakeNode(cros_config, subnode)
+    node.ScanSubnodes()
+    return node
+
+  class Property(object):
+    """FDT implementation of a property
+
+    Properties:
+      name: The name of the property.
+      value: The value of the property.
+      type: The Python type of the property.
+    """
+    def __init__(self, fdt_prop):
+      self._fdt_prop = fdt_prop
+      self.name = fdt_prop.name
+      self.value = fdt_prop.value
+      self.type = fdt_prop.type
+
+    def GetPhandle(self):
+      """Get the value of a property as a phandle
+
+      Returns:
+        Property's phandle as an integer (> 0)
+      """
+      return self._fdt_prop.GetPhandle()
diff --git a/chromeos-config/cros_config_host/libcros_config_host_json.py b/chromeos-config/cros_config_host/libcros_config_host_json.py
index 25e5729..5fb785f 100644
--- a/chromeos-config/cros_config_host/libcros_config_host_json.py
+++ b/chromeos-config/cros_config_host/libcros_config_host_json.py
@@ -11,7 +11,6 @@
 from __future__ import print_function
 
 from collections import OrderedDict
-import copy
 import json
 
 from cros_config_schema import TransformConfig
@@ -64,10 +63,7 @@
     file_region = self.GetProperties(path)
     if file_region and 'files' in file_region:
       for item in file_region['files']:
-        if 'build-path' in item:
-          result.append(BaseFile(item['build-path'], item['system-path']))
-        else:
-          result.append(BaseFile(item['source'], item['destination']))
+        result.append(BaseFile(item['source'], item['destination']))
     return result
 
   def GetFirmwareConfig(self):
@@ -95,13 +91,6 @@
   def GetAudioFiles(self):
     return self._GetFiles('/audio/main')
 
-  def GetBluetoothFiles(self):
-    result = []
-    config = self.GetProperties('/bluetooth/config')
-    if config:
-      result.append(BaseFile(config['build-path'], config['system-path']))
-    return result
-
   def GetThermalFiles(self):
     return self._GetFiles('/thermal')
 
@@ -118,16 +107,11 @@
 
   Properties:
     _json: Root json for the entire config.
-    _configs: List of DeviceConfigJson instances
+    _configs: List of DeviceConfigJson instances.
   """
 
-  def __init__(self, infile, model_filter_regex=None):
-    """
-    Args:
-      model_filter_regex: Only returns configs that match the filter.
-    """
-    self._json = json.loads(
-        TransformConfig(infile.read(), model_filter_regex=model_filter_regex))
+  def __init__(self, infile):
+    self._json = json.loads(TransformConfig(infile.read()))
     self._configs = []
     for config in self._json['chromeos']['configs']:
       self._configs.append(DeviceConfigJson(config))
@@ -141,20 +125,14 @@
     processed = set()
     for config in self._configs:
       fw = config.GetFirmwareConfig()
-      # For partial configs (public vs private), we need to support the name
-      # for cases where identity isn't specified.
-      identity = config.GetName() + str(config.GetProperties('/identity'))
-      brand_code = config.GetProperty('/', 'brand-code')
+      identity = str(config.GetProperties('/identity'))
       if fw and identity not in processed:
         fw_str = str(fw)
         shared_model = None
-        if fw_str not in fw_by_model:
-          # Use the explict name of the firmware, else use the device name
-          # This supports equivalence testing with DT since it allowed
-          # naming firmware images.
-          fw_by_model[fw_str] = fw.get('name', config.GetName())
-
-        shared_model = fw_by_model[fw_str]
+        if fw_str in fw_by_model:
+          shared_model = fw_by_model[fw_str]
+        else:
+          fw_by_model[fw_str] = config.GetName()
 
         build_config = config.GetProperties('/firmware/build-targets')
         if build_config:
@@ -162,14 +140,14 @@
           ec_build_target = config.GetValue(build_config, 'ec')
         else:
           bios_build_target, ec_build_target = None, None
+        create_bios_rw_image = False
 
-        main_image_uri = (config.GetValue(fw, 'main-ro-image') or
-                          config.GetValue(fw, 'main-image') or '')
+        main_image_uri = config.GetValue(fw, 'main-image') or ''
         main_rw_image_uri = config.GetValue(fw, 'main-rw-image') or ''
-        ec_image_uri = (config.GetValue(fw, 'ec-ro-image') or
-                        config.GetValue(fw, 'ec-image') or '')
-        pd_image_uri = (config.GetValue(fw, 'pd-ro-image') or
-                        config.GetValue(fw, 'pd-image') or '')
+        ec_image_uri = config.GetValue(fw, 'ec-image') or ''
+        pd_image_uri = config.GetValue(fw, 'pd-image') or ''
+        extra = config.GetValue(fw, 'extra') or []
+        tools = config.GetValue(fw, 'tools') or []
 
         fw_signer_config = config.GetProperties('/firmware-signing')
         key_id = config.GetValue(fw_signer_config, 'key-id')
@@ -180,8 +158,8 @@
         name = config.GetName()
 
         if sig_in_customization_id:
+          key_id = ''
           sig_id = 'sig-id-in-customization-id'
-          brand_code = ''
         else:
           sig_id = config.GetValue(fw_signer_config, 'signature-id')
           processed.add(identity)
@@ -189,26 +167,19 @@
         info = FirmwareInfo(name, shared_model, key_id, have_image,
                             bios_build_target, ec_build_target, main_image_uri,
                             main_rw_image_uri, ec_image_uri, pd_image_uri,
-                            sig_id, brand_code)
+                            extra, create_bios_rw_image, tools, sig_id)
         config.firmware_info[name] = info
 
         if sig_in_customization_id:
-          for wl_config in self._configs:
-            if wl_config.GetName() == name:
-              wl_brand_code = wl_config.GetProperty('/', 'brand-code')
-              wl_identity_str = str(wl_config.GetProperties('/identity'))
-              wl_identity = wl_config.GetName() + wl_identity_str
-              processed.add(wl_identity)
-              fw_signer_config = wl_config.GetProperties('/firmware-signing')
-              wl_key_id = wl_config.GetValue(fw_signer_config, 'key-id')
-              wl_sig_id = wl_config.GetValue(fw_signer_config, 'signature-id')
-              wl_fw_info = copy.deepcopy(info)
-              wl_config.firmware_info[wl_sig_id] = wl_fw_info._replace(
-                  model=wl_sig_id,
-                  key_id=wl_key_id,
-                  have_image=False,
-                  sig_id=wl_sig_id,
-                  brand_code=wl_brand_code)
+          for config in self._configs:
+            if config.GetName() == name:
+              identity = str(config.GetProperties('/identity'))
+              processed.add(identity)
+              fw_signer_config = config.GetProperties('/firmware-signing')
+              key_id = config.GetValue(fw_signer_config, 'key-id')
+              sig_id = config.GetValue(fw_signer_config, 'signature-id')
+              config.firmware_info[sig_id] = info._replace(
+                  model=sig_id, key_id=key_id, have_image=False, sig_id=sig_id)
 
   def GetDeviceConfigs(self):
     return self._configs
diff --git a/chromeos-config/cros_config_host/libcros_config_host_unittest.py b/chromeos-config/cros_config_host/libcros_config_host_unittest.py
index 697b8a7..df59827 100755
--- a/chromeos-config/cros_config_host/libcros_config_host_unittest.py
+++ b/chromeos-config/cros_config_host/libcros_config_host_unittest.py
@@ -10,16 +10,16 @@
 from collections import OrderedDict
 from contextlib import contextmanager
 from io import BytesIO
-import copy
-import json
 import os
 import sys
 import unittest
 
-from libcros_config_host import CrosConfig
+import fdt_util
+from libcros_config_host import CrosConfig, FORMAT_FDT
 from libcros_config_host_base import BaseFile, TouchFile, FirmwareInfo
 
 
+DTS_FILE = '../libcros_config/test.dts'
 YAML_FILE = '../libcros_config/test.yaml'
 MODELS = sorted(['some', 'another', 'whitelabel'])
 ANOTHER_BUCKET = ('gs://chromeos-binaries/HOME/bcs-another-private/overlay-'
@@ -50,21 +50,9 @@
   finally:
     sys.stdout, sys.stderr = old_out, old_err
 
-def _FormatNamedTuplesDict(value):
-   result = copy.deepcopy(value)
-   for key, value in result.iteritems():
-     result[key] = value._asdict()
 
-   return json.dumps(result, indent=2)
-
-
-class CrosConfigHostTest(unittest.TestCase):
-  def setUp(self):
-    self.filepath = os.path.join(os.path.dirname(__file__), YAML_FILE)
-
-  def _assertEqualsNamedTuplesDict(self, expected, result):
-    self.assertEqual(
-        _FormatNamedTuplesDict(expected), _FormatNamedTuplesDict(result))
+class CommonTests(object):
+  """Shared tests between the YAML and FDT implementations."""
 
   def testGetProperty(self):
     config = CrosConfig(self.filepath)
@@ -102,15 +90,6 @@
             source='some/hardware_features',
             dest='/usr/share/chromeos-config/sbin/some/hardware_features')])
 
-  def testGetBluetoothFiles(self):
-    config = CrosConfig(self.filepath)
-    bluetooth_files = config.GetBluetoothFiles()
-    self.assertEqual(
-        bluetooth_files,
-        [BaseFile(
-            source='some/main.conf',
-            dest='/etc/bluetooth/some/main.conf')])
-
   def testGetThermalFiles(self):
     config = CrosConfig(self.filepath)
     thermal_files = config.GetThermalFiles()
@@ -128,8 +107,7 @@
     self.assertSequenceEqual(config.GetFirmwareBuildTargets('coreboot'),
                              ['another'])
     self.assertSequenceEqual(config.GetFirmwareBuildTargets('ec'),
-                             ['another', 'another_base', 'another_cr50',
-                              'extra1', 'extra2'])
+                             ['another', 'another_base', 'another_cr50'])
     del os.environ['FW_NAME']
 
   def testFileTree(self):
@@ -161,7 +139,7 @@
     self.assertEqual(lines[3].split(), ['missing', 'cras/'])
 
 
-  def testFirmwareBuildCombinations(self):
+  def testFimwareBuildCombinations(self):
     """Test generating a dict of firmware build combinations."""
     config = CrosConfig(self.filepath)
     expected = OrderedDict(
@@ -170,13 +148,6 @@
     result = config.GetFirmwareBuildCombinations(['coreboot', 'depthcharge'])
     self.assertEqual(result, expected)
 
-    # Should not explode when devices do not specify requested target.
-    expected = OrderedDict(
-        [('another', ['another_base']),
-         ('some', [])])
-    result = config.GetFirmwareBuildCombinations(['base'])
-    self.assertEqual(result, expected)
-
     os.environ['FW_NAME'] = 'another'
     expected = OrderedDict([('another', ['another', 'another'])])
     result = config.GetFirmwareBuildCombinations(['coreboot', 'depthcharge'])
@@ -273,10 +244,13 @@
 
   def testFirmware(self):
     """Test access to firmware information"""
+    config = CrosConfig(self.filepath)
+    self.assertEqual('updater4.sh', config.GetFirmwareScript())
+
     expected = OrderedDict(
         [('another',
           FirmwareInfo(model='another',
-                       shared_model='another',
+                       shared_model=None,
                        key_id='ANOTHER',
                        have_image=True,
                        bios_build_target='another',
@@ -285,8 +259,10 @@
                        main_rw_image_uri='bcs://Another_RW.1111.11.1.tbz2',
                        ec_image_uri='bcs://Another_EC.1111.11.1.tbz2',
                        pd_image_uri='',
-                       sig_id='another',
-                       brand_code='')),
+                       extra=['${FILESDIR}/extra'],
+                       create_bios_rw_image=False,
+                       tools=['${FILESDIR}/tools1', '${FILESDIR}/tools2'],
+                       sig_id='another')),
          ('some',
           FirmwareInfo(model='some',
                        shared_model='some',
@@ -298,12 +274,14 @@
                        main_rw_image_uri='bcs://Some_RW.1111.11.1.tbz2',
                        ec_image_uri='bcs://Some_EC.1111.11.1.tbz2',
                        pd_image_uri='',
-                       sig_id='some',
-                       brand_code='')),
+                       extra=[],
+                       create_bios_rw_image=False,
+                       tools=[],
+                       sig_id='some')),
          ('whitelabel',
           FirmwareInfo(model='whitelabel',
                        shared_model='some',
-                       key_id='WHITELABEL1',
+                       key_id='',
                        have_image=True,
                        bios_build_target='some',
                        ec_build_target='some',
@@ -311,8 +289,10 @@
                        main_rw_image_uri='bcs://Some_RW.1111.11.1.tbz2',
                        ec_image_uri='bcs://Some_EC.1111.11.1.tbz2',
                        pd_image_uri='',
-                       sig_id='sig-id-in-customization-id',
-                       brand_code='')),
+                       extra=[],
+                       create_bios_rw_image=False,
+                       tools=[],
+                       sig_id='sig-id-in-customization-id')),
          ('whitelabel-whitelabel1',
           FirmwareInfo(model='whitelabel-whitelabel1',
                        shared_model='some',
@@ -324,8 +304,10 @@
                        main_rw_image_uri='bcs://Some_RW.1111.11.1.tbz2',
                        ec_image_uri='bcs://Some_EC.1111.11.1.tbz2',
                        pd_image_uri='',
-                       sig_id='whitelabel-whitelabel1',
-                       brand_code='WLBA')),
+                       extra=[],
+                       create_bios_rw_image=False,
+                       tools=[],
+                       sig_id='whitelabel-whitelabel1')),
          ('whitelabel-whitelabel2',
           FirmwareInfo(model='whitelabel-whitelabel2',
                        shared_model='some',
@@ -337,10 +319,160 @@
                        main_rw_image_uri='bcs://Some_RW.1111.11.1.tbz2',
                        ec_image_uri='bcs://Some_EC.1111.11.1.tbz2',
                        pd_image_uri='',
-                       sig_id='whitelabel-whitelabel2',
-                       brand_code='WLBB'))])
-    result = CrosConfig(self.filepath).GetFirmwareInfo()
-    self._assertEqualsNamedTuplesDict(result, expected)
+                       extra=[],
+                       create_bios_rw_image=False,
+                       tools=[],
+                       sig_id='whitelabel-whitelabel2'))])
+    result = config.GetFirmwareInfo()
+    self.assertEqual(result, expected)
+
+
+class CrosConfigHostTestFdt(unittest.TestCase, CommonTests):
+  """Tests for master configuration in device tree format"""
+  def setUp(self):
+    path = os.path.join(os.path.dirname(__file__), DTS_FILE)
+    (self.filepath, self.temp_file) = fdt_util.EnsureCompiled(path)
+
+  def tearDown(self):
+    os.remove(self.temp_file.name)
+
+  def testGoodDtbFile(self):
+    self.assertIsNotNone(CrosConfig(self.filepath))
+
+  def testNodeSubnames(self):
+    config = CrosConfig(self.filepath)
+    for name, model in config.models.iteritems():
+      self.assertEqual(name, model.name)
+
+  def testPathNode(self):
+    config = CrosConfig(self.filepath)
+    self.assertIsNotNone(config.models['another'].PathNode('/firmware'))
+
+  def testBadPathNode(self):
+    config = CrosConfig(self.filepath)
+    self.assertIsNone(config.models['another'].PathNode('/dne'))
+
+  def testPathProperty(self):
+    config = CrosConfig(self.filepath)
+    another = config.models['another']
+    ec_image = another.PathProperty('/firmware', 'ec-image')
+    self.assertEqual(ec_image.value, 'bcs://Another_EC.1111.11.1.tbz2')
+
+  def testBadPathProperty(self):
+    config = CrosConfig(self.filepath)
+    another = config.models['another']
+    self.assertIsNone(another.PathProperty('/firmware', 'dne'))
+    self.assertIsNone(another.PathProperty('/dne', 'ec-image'))
+
+  def testSinglePhandleFollowProperty(self):
+    config = CrosConfig(self.filepath)
+    another = config.models['another']
+    bcs_overlay = another.PathProperty('/firmware', 'bcs-overlay')
+    self.assertEqual(bcs_overlay.value, 'overlay-another-private')
+
+  def testSinglePhandleFollowNode(self):
+    config = CrosConfig(self.filepath)
+    another = config.models['another']
+    target = another.PathProperty('/firmware/build-targets', 'coreboot')
+    self.assertEqual(target.value, 'another')
+
+  def testGetMergedPropertiesAnother(self):
+    config = CrosConfig(self.filepath)
+    another = config.models['another']
+    stylus = another.PathNode('touch/stylus')
+    props = another.GetMergedProperties(stylus, 'touch-type')
+    self.assertSequenceEqual(
+        props,
+        {'version': 'another-version',
+         'vendor': 'some_stylus_vendor',
+         'firmware-bin': '{vendor}/{version}.hex',
+         'firmware-symlink': '{vendor}_firmware_{MODEL}.bin'})
+
+  def testGetMergedPropertiesSome(self):
+    config = CrosConfig(self.filepath)
+    some = config.models['some']
+    touchscreen = some.PathNode('touch/touchscreen@0')
+    props = some.GetMergedProperties(touchscreen, 'touch-type')
+    self.assertEqual(
+        props,
+        {'pid': 'some-other-pid',
+         'version': 'some-other-version',
+         'vendor': 'some_touch_vendor',
+         'firmware-bin': '{vendor}/{pid}_{version}.bin',
+         'firmware-symlink': '{vendor}ts_i2c_{pid}.bin'})
+
+  def testGetMergedPropertiesDefault(self):
+    """Test that the 'default' property is used when collecting properties"""
+    config = CrosConfig(self.filepath)
+    another = config.models['another']
+    audio = another.PathNode('/audio/main')
+    props = another.GetMergedProperties(audio, 'audio-type')
+    self.assertSequenceEqual(
+        props,
+        {'cras-config-dir': 'another',
+         'ucm-suffix': 'another',
+         'topology-name': 'another',
+         'card': 'a-card',
+         'volume': 'cras-config/{cras-config-dir}/{card}',
+         'dsp-ini': 'cras-config/{cras-config-dir}/dsp.ini',
+         'hifi-conf': 'ucm-config/{card}.{ucm-suffix}/HiFi.conf',
+         'alsa-conf': 'ucm-config/{card}.{ucm-suffix}/{card}.' +
+                      '{ucm-suffix}.conf',
+         'topology-bin': 'topology/{topology-name}-tplg.bin'})
+
+  def testWriteTargetDirectories(self):
+    """Test that we can write out a list of file paths"""
+    config = CrosConfig(self.filepath)
+    target_dirs = config.GetTargetDirectories()
+    self.assertEqual(target_dirs['dptf-dv'], '/etc/dptf')
+    self.assertEqual(target_dirs['hifi-conf'], '/usr/share/alsa/ucm')
+    self.assertEqual(target_dirs['alsa-conf'], '/usr/share/alsa/ucm')
+    self.assertEqual(target_dirs['volume'], '/etc/cras')
+    self.assertEqual(target_dirs['dsp-ini'], '/etc/cras')
+    self.assertEqual(target_dirs['cras-config-dir'], '/etc/cras')
+
+  def testSubmodel(self):
+    """Test that we can read properties from the submodel"""
+    config = CrosConfig(self.filepath)
+    some = config.models['some']
+    self.assertEqual(
+        some.SubmodelPathProperty('touch', '/audio/main', 'ucm-suffix').value,
+        'some')
+    self.assertEqual(
+        some.SubmodelPathProperty('touch', '/touch', 'present').value, 'yes')
+    self.assertEqual(
+        some.SubmodelPathProperty('notouch', '/touch', 'present').value, 'no')
+
+  def testGetProperty(self):
+    """Test that we can read properties from non-model nodes"""
+    config = CrosConfig(self.filepath)
+    # pylint: disable=protected-access
+    self.assertEqual(config._GetProperty('/chromeos/family/firmware',
+                                         'script').value, 'updater4.sh')
+    firmware = config.GetFamilyNode('/firmware')
+    self.assertEqual(firmware.name, 'firmware')
+    self.assertEqual(firmware.Property('script').value, 'updater4.sh')
+    self.assertEqual(
+        config.GetFamilyProperty('/firmware', 'script').value, 'updater4.sh')
+
+  def testDefaultConfig(self):
+    if 'SYSROOT' in os.environ:
+      del os.environ['SYSROOT']
+    with self.assertRaisesRegexp(ValueError,
+                                 'No master configuration is available'):
+      CrosConfig()
+
+    os.environ['SYSROOT'] = 'fred'
+    with self.assertRaises(IOError) as e:
+      CrosConfig()
+    self.assertIn('fred/usr/share/chromeos-config/config.dtb',
+                  str(e.exception))
+
+
+class CrosConfigHostTestYaml(unittest.TestCase, CommonTests):
+  """Tests for master configuration in yaml format"""
+  def setUp(self):
+    self.filepath = os.path.join(os.path.dirname(__file__), YAML_FILE)
 
 
 if __name__ == '__main__':
diff --git a/chromeos-config/cros_config_host/libcros_schema.py b/chromeos-config/cros_config_host/libcros_schema.py
deleted file mode 100644
index b034aaf..0000000
--- a/chromeos-config/cros_config_host/libcros_schema.py
+++ /dev/null
@@ -1,142 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright 2018 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-"""Chrome OS Configuration Schema library.
-
-Provides common cros_config and cros_config_test schema functions.
-"""
-
-from __future__ import print_function
-
-import collections
-import json
-import os
-import re
-import yaml
-
-from jsonschema import validate
-
-def GetNamedTuple(mapping):
-  """Converts a mapping into Named Tuple recursively.
-
-  Args:
-    mapping: A mapping object to be converted.
-
-  Returns:
-    A named tuple generated from mapping
-  """
-  if not isinstance(mapping, collections.Mapping):
-    return mapping
-  new_mapping = {}
-  for k, v in mapping.iteritems():
-    if isinstance(v, list):
-      new_list = []
-      for val in v:
-        new_list.append(GetNamedTuple(val))
-      new_mapping[k.replace('-', '_').replace('@', '_')] = new_list
-    else:
-      new_mapping[k.replace('-', '_').replace('@', '_')] = GetNamedTuple(v)
-  return collections.namedtuple('Config', new_mapping.iterkeys())(**new_mapping)
-
-
-def FormatJson(config):
-  """Formats JSON for output or printing.
-
-  Args:
-    config: Dictionary to be output
-  """
-  return json.dumps(config, sort_keys=True, indent=2, separators=(',', ': '))
-
-
-def GetValidSchemaProperties(schema_node, path, result):
-  """Recursively finds the valid properties for a given node
-
-  Args:
-    schema_node: Single node from the schema
-    path: Running path that a given node maps to
-    result: Running collection of results
-  """
-  full_path = '/%s' % '/'.join(path)
-  for key in schema_node:
-    new_path = path + [key]
-    schema_type = schema_node[key]['type']
-
-    if schema_type == 'object':
-      if 'properties' in schema_node[key]:
-        GetValidSchemaProperties(
-            schema_node[key]['properties'], new_path, result)
-    elif schema_type == 'string':
-      all_props = result.get(full_path, [])
-      all_props.append(key)
-      result[full_path] = all_props
-
-
-def ValidateConfigSchema(schema, config):
-  """Validates a transformed config against the schema specified.
-
-  Verifies that the config complies with the schema supplied.
-
-  Args:
-    schema: Source schema used to verify the config.
-    config: Config (transformed) that will be verified.
-  """
-  json_config = json.loads(config)
-  schema_yaml = yaml.load(schema)
-  schema_json_from_yaml = json.dumps(schema_yaml, sort_keys=True, indent=2)
-  schema_json = json.loads(schema_json_from_yaml)
-  validate(json_config, schema_json)
-
-
-def FindImports(config_file, includes):
-  """Recursively looks up and finds files to include for yaml.
-
-  Args:
-    config_file: Path to the config file for which to apply imports.
-    includes: List that is built up through processing the files.
-  """
-  working_dir = os.path.dirname(config_file)
-  with open(config_file, 'r') as config_stream:
-    config_lines = config_stream.readlines()
-    yaml_import_lines = []
-    found_imports = False
-    # Parsing out just the imports snippet is required because the YAML
-    # isn't valid until the imports are eval'd.
-    for line in config_lines:
-      if re.match(r'^imports', line):
-        found_imports = True
-        yaml_import_lines.append(line)
-      elif found_imports:
-        match = re.match(r' *- (.*)', line)
-        if match:
-          yaml_import_lines.append(line)
-        else:
-          break
-
-    if yaml_import_lines:
-      yaml_import = yaml.load('\n'.join(yaml_import_lines))
-
-      for import_file in yaml_import.get('imports', []):
-        full_path = os.path.join(working_dir, import_file)
-        FindImports(full_path, includes)
-    includes.append(config_file)
-
-
-def ApplyImports(config_file):
-  """Parses the imports statements and applies them to a result config.
-
-  Args:
-    config_file: Path to the config file for which to apply imports.
-
-  Returns:
-    Raw config with the imports applied.
-  """
-  import_files = []
-  FindImports(config_file, import_files)
-
-  all_yaml_files = []
-  for import_file in import_files:
-    with open(import_file, 'r') as yaml_stream:
-      all_yaml_files.append(yaml_stream.read())
-
-  return '\n'.join(all_yaml_files)
diff --git a/chromeos-config/cros_config_host/libcros_schema_unittest.py b/chromeos-config/cros_config_host/libcros_schema_unittest.py
deleted file mode 100755
index 4abe016..0000000
--- a/chromeos-config/cros_config_host/libcros_schema_unittest.py
+++ /dev/null
@@ -1,36 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright 2018 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# pylint: disable=module-missing-docstring,class-missing-docstring
-
-from __future__ import print_function
-
-import libcros_schema
-
-from chromite.lib import cros_test_lib
-
-
-class GetNamedTupleTests(cros_test_lib.TestCase):
-
-  def testRecursiveDicts(self):
-    val = {'a': {'b': 1, 'c': 2}}
-    val_tuple = libcros_schema.GetNamedTuple(val)
-    self.assertEqual(val['a']['b'], val_tuple.a.b)
-    self.assertEqual(val['a']['c'], val_tuple.a.c)
-
-  def testListInRecursiveDicts(self):
-    val = {'a': {'b': [{'c': 2}]}}
-    val_tuple = libcros_schema.GetNamedTuple(val)
-    self.assertEqual(val['a']['b'][0]['c'], val_tuple.a.b[0].c)
-
-  def testDashesReplacedWithUnderscores(self):
-    val = {'a-b': 1}
-    val_tuple = libcros_schema.GetNamedTuple(val)
-    self.assertEqual(val['a-b'], val_tuple.a_b)
-
-
-# TODO(crbug.com/897753): Add explicit tests for FindImports and ApplyIports.
-# This functionality is already tested in larger, more specific tests in
-# cros_config_schema_unittest and cros_config_test_schema_unittest.
diff --git a/chromeos-config/cros_config_host/templates/ec_config.c.jinja2 b/chromeos-config/cros_config_host/templates/ec_config.c.jinja2
deleted file mode 100644
index 75dab37..0000000
--- a/chromeos-config/cros_config_host/templates/ec_config.c.jinja2
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2018 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ec_config.h"
-
-#include "compile_time_macros.h"
-
-{% for ec_build_target, skus in device_properties.iteritems() -%}
-  {%- if device_properties | length > 1 -%}
-    {% if loop.first %}
-#if defined(BOARD_{{ ec_build_target | upper }})
-    {%- else %}
-#elif defined(BOARD_{{ ec_build_target | upper }})
-    {%- endif -%}
-  {%- endif %}
-
-const struct sku_info ALL_SKUS[] = {
-  {%- for sku, flag_values in skus %}
-  {.sku = {{ sku }}
-    {%- for flag in flags %},
-   .{{ flag }} = {{ flag_values[flag] | int }}
-    {%- endfor -%}
-  }
-    {%- if not loop.last -%} , {%- endif -%}
-  {% endfor %}
-};
-  {%- if device_properties | length > 1 and loop.last %}
-
-#endif
-  {%- endif %}
-{% else -%}
-  {#- Ensures we get an empty struct def even if no ec targets are defined. -#}
-  const struct sku_info ALL_SKUS[] = {};
-{%- endfor %}
-
-const size_t NUM_SKUS = ARRAY_SIZE(ALL_SKUS);
diff --git a/chromeos-config/cros_config_host/templates/ec_config.h.jinja2 b/chromeos-config/cros_config_host/templates/ec_config.h.jinja2
deleted file mode 100644
index 3062963..0000000
--- a/chromeos-config/cros_config_host/templates/ec_config.h.jinja2
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright 2018 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROMEOS_CONFIG_EC_CONFIG_H_
-#define CHROMEOS_CONFIG_EC_CONFIG_H_
-
-#include <stdint.h>
-#include <stdlib.h>
-
-struct sku_info {
-  const uint8_t sku;
-  {%- for flag in flags %}
-  const uint8_t {{ flag }} :1;
-  {%- endfor %}
-};
-
-extern const size_t NUM_SKUS;
-extern const struct sku_info ALL_SKUS[];
-
-#endif  // CHROMEOS_CONFIG_EC_CONFIG_H_
diff --git a/chromeos-config/cros_config_host/test_data/cros_config_test_common.yaml b/chromeos-config/cros_config_host/test_data/cros_config_test_common.yaml
deleted file mode 100644
index c6e747b..0000000
--- a/chromeos-config/cros_config_host/test_data/cros_config_test_common.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
-mosys-base: &mosys_base_cmds
-  name: "mosys"
-  args:
-    - "platform id"
-    - "platform name"
-
-mosys-unibuild: &mosys_unibuild_cmds
-  name: "mosys"
-  args:
-    - "platform vendor"
-
-mosys-non-unibuild: &mosys_non_unibuild_cmds
-  name: "mosys"
-  args:
-    - "platform version"
-
-cros-config-unibuild: &cros_config_unibuild_cmds
-  name: "cros_config"
-  args:
-    - "/ brand-name"
diff --git a/chromeos-config/cros_config_host/test_data/cros_config_test_device.yaml b/chromeos-config/cros_config_host/test_data/cros_config_test_device.yaml
deleted file mode 100644
index 4eff93a..0000000
--- a/chromeos-config/cros_config_host/test_data/cros_config_test_device.yaml
+++ /dev/null
@@ -1,29 +0,0 @@
-imports:
-  - "cros_config_test_common.yaml"
-
-mosys-nautilus: &mosys_nautilus_cmds
-  name: "mosys"
-  args:
-    - "platform model"
-
-cros-config-lte: &cros_config_lte_cmds
-  name: "cros_config"
-  args:
-    - "/arc/build-properties device"
-
-chromeos:
-  devices:
-    - device-name: "nautilus"
-      command-groups:
-        - *mosys_base_cmds
-        - *mosys_unibuild_cmds
-        - *mosys_nautilus_cmds
-        - *cros_config_unibuild_cmds
-
-    - device-name: "nautiluslte"
-      command-groups:
-        - *mosys_base_cmds
-        - *mosys_unibuild_cmds
-        - *mosys_nautilus_cmds
-        - *cros_config_unibuild_cmds
-        - *cros_config_lte_cmds
diff --git a/chromeos-config/cros_config_host/validate_config.py b/chromeos-config/cros_config_host/validate_config.py
new file mode 100755
index 0000000..899959e
--- /dev/null
+++ b/chromeos-config/cros_config_host/validate_config.py
@@ -0,0 +1,732 @@
+#!/usr/bin/env python2
+# Copyright 2017 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Validates a given master configuration
+
+This enforces various rules defined by the master configuration. Some of these
+are fairly simple (the valid properties and subnodes for each node, the
+allowable values for properties) and some are more complex (where phandles
+are allowed to point).
+
+The schema is defined by Python objects containing variable SchemaElement
+subclasses. Each subclass defines how the device tree property is validated.
+For strings this is via a regex. Phandles properties are validated by the
+target they are expected to point to.
+
+Schema elements can be optional or required. Optional elements will not cause
+a failure if the node does not include them.
+
+The presence or absense of a particular schema element can also be controlled
+by a 'conditional_props' option. This lists elements that must (or must not)
+be present in the node for this element to be present. This provides some
+flexibility where the schema for a node has two options, for example, where
+the presence of one element conflicts with the presence of others.
+
+Usage:
+  The validator can be run like this (set PYTHONPATH to your chromium dir):
+
+  PYTHONPATH=~/cosarm ./validate_config \
+      ~/cosarm/chroot/build/coral/usr/share/chromeos-config/config.dtb \
+      ~/cosarm/chroot/build/reef-uni/usr/share/chromeos-config/config.dtb \
+      README.md
+
+  The output format for each input file is the name of the file followed by a
+  list of validation problems. If there are no problems, the filename is not
+  shown.
+
+  Unit tests can be run like this:
+
+  PYTHONPATH=~/cosarm python validate_config_unittest.py
+"""
+
+from __future__ import print_function
+
+import argparse
+import copy
+import itertools
+import os
+import re
+import sys
+
+from chromite.lib import cros_build_lib
+
+import fdt, fdt_util
+from validate_schema import NodeAny, NodeDesc, NodeModel, NodeSubmodel
+from validate_schema import PropCustom, PropDesc, PropString, PropStringList
+from validate_schema import PropPhandleTarget, PropPhandle, CheckPhandleTarget
+from validate_schema import PropAny, PropBool, PropFile, PropFloat
+
+
+def ParseArgv(argv):
+  """Parse the available arguments.
+
+  Invalid arguments or -h cause this function to print a message and exit.
+
+  Args:
+    argv: List of string arguments (excluding program name / argv[0])
+
+  Returns:
+    argparse.Namespace object containing the attributes.
+  """
+  parser = argparse.ArgumentParser(description=__doc__)
+  parser.add_argument('-d', '--debug', action='store_true',
+                      help='Run in debug mode (full exception traceback)')
+  parser.add_argument('-p', '--partial', action='store_true',
+                      help='Validate a list partial files (.dtsi) individually')
+  parser.add_argument('-r', '--raise-on-error', action='store_true',
+                      help='Causes the validator to raise on the first ' +
+                      'error it finds. This is useful for debugging.')
+  parser.add_argument('config', type=str, nargs='+',
+                      help='Paths to the config files (.dtb) to validated')
+  return parser.parse_args(argv)
+
+
+class CrosConfigValidator(object):
+  """Validator for the master configuration"""
+  def __init__(self, schema, raise_on_error):
+    """Master configuration validator.
+
+    Properties:
+      _errors: List of validation errors detected (each a string)
+      _fdt: fdt.Fdt object containing device tree to validate
+      _raise_on_error: True if the validator should raise on the first error
+          (useful for debugging)
+      model_list: List of model names found in the config
+      submodel_list: Dict of submodel names found in the config:
+          key: Model name
+          value: List of submodel names
+    """
+    self._errors = []
+    self._fdt = None
+    self._raise_on_error = raise_on_error
+    self._schema = schema
+
+    # This iniital value matches the standard schema object. This is
+    # overwritten by the real model list by Start().
+    self.model_list = ['MODEL']
+    self.submodel_list = {}
+
+  def Fail(self, location, msg):
+    """Record a validation failure
+
+    Args:
+      location: fdt.Node object where the error occurred
+      msg: Message to record for this failure
+    """
+    self._errors.append('%s: %s' % (location, msg))
+    if self._raise_on_error:
+      raise ValueError(self._errors[-1])
+
+  def ElementPresent(self, schema, parent_node):
+    """Check whether a schema element should be present
+
+    This handles the conditional_props feature. The list of names of sibling
+    nodes/properties that are actually present is checked to see if any of them
+    conflict with the conditional properties for this node. If there is a
+    conflict, then this element is considered to be absent.
+
+    Args:
+      schema: Schema element to check
+      parent_node: Parent fdt.Node containing this schema element (or None if
+          this is not known)
+
+    Returns:
+      True if this element is present, False if absent
+    """
+    if schema.conditional_props and parent_node:
+      for rel_name, value in schema.conditional_props.iteritems():
+        name = rel_name
+        schema_target = schema.parent
+        node_target = parent_node
+        while name.startswith('../'):
+          schema_target = schema_target.parent
+          node_target = node_target.parent
+          name = name[3:]
+        parent_props = [e.name for e in schema_target.elements]
+        sibling_names = node_target.props.keys()
+        sibling_names += [n.name for n in node_target.subnodes.values()]
+        if name in parent_props and value != (name in sibling_names):
+          return False
+    return True
+
+  def GetElement(self, schema, name, node, expected=None):
+    """Get an element from the schema by name
+
+    Args:
+      schema: Schema element to check
+      name: Name of element to find (string)
+      node: Node containing the property (or for nodes, the parent node
+          containing the subnode) we are looking up. None if none available
+      expected: The SchemaElement object that is expected. This can be NodeDesc
+          if a node is expected, PropDesc if a property is expected, or None
+          if either is fine.
+
+    Returns:
+      Tuple:
+        Schema for the node, or None if none found
+        True if the node should have schema, False if it can be ignored
+            (because it is internal to the device-tree format)
+    """
+    for element in schema.elements:
+      if not self.ElementPresent(element, node):
+        continue
+      if element.name == name:
+        return element, True
+      elif (self.model_list and isinstance(element, NodeModel) and
+            name in self.model_list):
+        return element, True
+      elif self.submodel_list and isinstance(element, NodeSubmodel) and node:
+        m = re.match('/chromeos/models/([a-z0-9]+)/submodels', node.path)
+        if m and name in self.submodel_list[m.group(1)]:
+          return element, True
+      elif ((expected is None or expected == NodeDesc) and
+            isinstance(element, NodeAny)):
+        return element, True
+      elif ((expected is None or expected == PropDesc) and
+            isinstance(element, PropAny)):
+        return element, True
+    if expected == PropDesc:
+      if name == 'linux,phandle':
+        return None, False
+    return None, True
+
+  def GetElementByPath(self, path):
+    """Find a schema element given its full path
+
+    Args:
+      path: Full path to look up (e.g. '/chromeos/models/MODEL/thermal/dptf-dv')
+
+    Returns:
+      SchemaElement object for that path
+
+    Raises:
+      AttributeError if not found
+    """
+    parts = path.split('/')[1:]
+    schema = self._schema
+    for part in parts:
+      element, _ = self.GetElement(schema, part, None)
+      schema = element
+    return schema
+
+  def _ValidateSchema(self, node, schema):
+    """Simple validation of properties.
+
+    This only handles simple mistakes like getting the name wrong. It
+    cannot handle relationships between different properties.
+
+    Args:
+      node: fdt.Node where the property appears
+      schema: NodeDesc containing schema for this node
+    """
+    schema.Validate(self, node)
+    schema_props = [e.name for e in schema.elements
+                    if isinstance(e, PropDesc) and
+                    self.ElementPresent(e, node)]
+
+    # Validate each property and check that there are no extra properties not
+    # mentioned in the schema.
+    for prop_name in node.props.keys():
+      if prop_name == 'linux,phandle':  # Ignore this (use 'phandle' instead)
+        continue
+      element, _ = self.GetElement(schema, prop_name, node, PropDesc)
+      if not element or not isinstance(element, PropDesc):
+        if prop_name == 'phandle':
+          self.Fail(node.path, 'phandle target not valid for this node')
+        else:
+          self.Fail(node.path, "Unexpected property '%s', valid list is (%s)" %
+                    (prop_name, ', '.join(schema_props)))
+        continue
+      element.Validate(self, node.props[prop_name])
+
+    # Check that there are no required properties which we don't have
+    for element in schema.elements:
+      if (not isinstance(element, PropDesc) or
+          not self.ElementPresent(element, node)):
+        continue
+      if element.required and element.name not in node.props.keys():
+        self.Fail(node.path, "Required property '%s' missing" % element.name)
+
+    # Check that any required subnodes are present
+    subnode_names = [n.name for n in node.subnodes.values()]
+    for element in schema.elements:
+      if (not isinstance(element, NodeDesc) or not element.required
+          or not self.ElementPresent(element, node)):
+        continue
+      if element.name not in subnode_names:
+        msg = "Missing subnode '%s'" % element.name
+        if subnode_names:
+          msg += ' in %s' % ', '.join(subnode_names)
+        self.Fail(node.path, msg)
+
+  def GetSchema(self, node, parent_schema):
+    """Obtain the schema for a subnode
+
+    This finds the schema for a subnode, by scanning for a matching element.
+
+    Args:
+      node: fdt.Node whose schema we are searching for
+      parent_schema: Schema for the parent node, which contains that schema
+
+    Returns:
+      Schema for the node, or None if none found
+    """
+    schema, needed = self.GetElement(parent_schema, node.name, node.parent,
+                                     NodeDesc)
+    if not schema and needed:
+      elements = [e.name for e in parent_schema.GetNodes()
+                  if self.ElementPresent(e, node.parent)]
+      self.Fail(os.path.dirname(node.path),
+                "Unexpected subnode '%s', valid list is (%s)" %
+                (node.name, ', '.join(elements)))
+    return schema
+
+  def _ValidateTree(self, node, parent_schema):
+    """Validate a node and all its subnodes recursively
+
+    Args:
+      node: name of fdt.Node to search for
+      parent_schema: Schema for the parent node
+    """
+    if node.name == '/':
+      schema = parent_schema
+    else:
+      schema = self.GetSchema(node, parent_schema)
+      if schema is None:
+        return
+
+    self._ValidateSchema(node, schema)
+    for subnode in node.subnodes.values():
+      self._ValidateTree(subnode, schema)
+
+  @staticmethod
+  def ValidateSkuMap(val, prop):
+    it = iter(prop.value)
+    sku_set = set()
+    for sku, phandle in itertools.izip(it, it):
+      sku_id = fdt_util.fdt32_to_cpu(sku)
+      # Allow a SKU ID of -1 as a valid match.
+      if sku_id > 0xffff and sku_id != 0xffffffff:
+        val.Fail(prop.node.path, 'sku_id %d out of range' % sku_id)
+      if sku_id in sku_set:
+        val.Fail(prop.node.path, 'Duplicate sku_id %d' % sku_id)
+      sku_set.add(sku_id)
+      phandle_val = fdt_util.fdt32_to_cpu(phandle)
+      target = prop.fdt.LookupPhandle(phandle_val)
+      if (not CheckPhandleTarget(val, target, '/chromeos/models/MODEL') and
+          not CheckPhandleTarget(val, target,
+                                 '/chromeos/models/MODEL/submodels/SUBMODEL')):
+        val.Fail(prop.node.path,
+                 "Phandle '%s' sku-id %d must target a model or submodel'" %
+                 (prop.name, sku_id))
+
+  def GetModelTargetDir(self, path, prop_name):
+    """Get the target directory for a given path and property
+
+    This looks up the model schema for a given path and property, and locates
+    the target directory for that property.
+
+    Args:
+      path: Path within model schema to examine (e.g. /thermal)
+      prop_name: Property name to examine (e.g. 'dptf-dv')
+
+    Returns:
+      target directory for that property (e.g. '/etc/dptf')
+    """
+    element = self.GetElementByPath(
+        '/chromeos/models/MODEL%s/%s' % (path, prop_name))
+    return element.target_dir
+
+  def Prepare(self, _fdt):
+    """Locate all the models and submodels before we start"""
+    self._fdt = _fdt
+    models = self._fdt.GetNode('/chromeos/models')
+    for model in models.subnodes.values():
+      self.model_list.append(model.name)
+      sub_models = model.FindNode('submodels')
+      if sub_models:
+        self.submodel_list[model.name] = (
+            [sm.name for sm in sub_models.subnodes.values()])
+      else:
+        self.submodel_list[model.name] = []
+
+
+  def Start(self, fnames, partial=False):
+    """Start validating a master configuration file
+
+    Args:
+      fnames: List of filenames containing the configuration to validate.
+          Supports compiled .dtb files, source .dts files and README.md (which
+          has configuration source between ``` markers). If partial is False
+          then there can be only one filename in the list.
+      partial: True to process a list of partial config files (.dtsi)
+    """
+    tmpfile = None
+    self.model_list = []
+    self.submodel_list = {}
+    self._errors = []
+    try:
+      if partial:
+        dtb, tmpfile = fdt_util.CompileAll(fnames)
+      else:
+        dtb, tmpfile = fdt_util.EnsureCompiled(fnames[0])
+      self.Prepare(fdt.FdtScan(dtb))
+
+      # Validate the entire master configuration
+      self._ValidateTree(self._fdt.GetRoot(), self._schema)
+    finally:
+      if tmpfile:
+        os.unlink(tmpfile.name)
+    return self._errors
+
+  @classmethod
+  def AddElementTargetDirectories(cls, target_dirs, parent):
+    if isinstance(parent, PropFile):
+      if parent.name in target_dirs:
+        if target_dirs[parent.name] != parent.target_dir:
+          raise ValueError(
+              "Path for element '%s' is inconsistent with previous path '%s'" %
+              (parent.target_dir, target_dirs[parent.name]))
+      else:
+        target_dirs[parent.name] = parent.target_dir
+    if isinstance(parent, NodeDesc):
+      for element in parent.elements:
+        cls.AddElementTargetDirectories(target_dirs, element)
+
+  def GetTargetDirectories(self):
+    """Gets a dict of directory targets for each PropFile property
+
+    Returns:
+      Dict:
+        key: Property name
+        value: Ansolute path for this property
+    """
+    target_dirs = {}
+    self.AddElementTargetDirectories(target_dirs, self._schema)
+    return target_dirs
+
+  @classmethod
+  def AddElementPhandleProps(cls, phandle_props, parent):
+    if isinstance(parent, PropPhandle):
+      phandle_props.add(parent.name)
+    elif isinstance(parent, NodeDesc):
+      for element in parent.elements:
+        cls.AddElementPhandleProps(phandle_props, element)
+
+  def GetPhandleProps(self):
+    """Gets a set of properties which are used as phandles
+
+    Some properties are used as phandles to link to shared config. This returns
+    a set of such properties. Note that 'default' is a special case here
+    because it is not a simple phandle link. It locates notes and properties
+    anywhere in the linked model. So we need to exclude it from this list so
+    that the 'default' handling works correctly.
+
+    Returns:
+      set of property names, each a string
+    """
+    phandle_props = set()
+    self.AddElementPhandleProps(phandle_props, self._schema)
+    phandle_props.discard('default')
+    return phandle_props
+
+
+# Known directories for installation
+CRAS_CONFIG_DIR = '/etc/cras'
+UCM_CONFIG_DIR = '/usr/share/alsa/ucm'
+LIB_FIRMWARE = '/lib/firmware'
+TOUCH_FIRMWARE = '/opt/google/touch/firmware'
+# In order not to pollute the regular /usr/sbin with model specific files
+# putting the files underneath chromeos-config
+ARC_SBIN_DIR = '/usr/share/chromeos-config/sbin'
+
+# Basic firmware schema, which is augmented depending on the situation.
+FW_COND = {'shares': False, '../whitelabel': False}
+
+BASE_FIRMWARE_SCHEMA = [
+    PropString('bcs-overlay', True, 'overlay-.*', FW_COND),
+    PropString('ec-image', False, r'bcs://.*\.tbz2', FW_COND),
+    PropString('main-image', False, r'bcs://.*\.tbz2', FW_COND),
+    PropString('main-rw-image', False, r'bcs://.*\.tbz2', FW_COND),
+    PropString('pd-image', False, r'bcs://.*\.tbz2', FW_COND),
+    PropStringList('extra', False,
+                   r'(\${(FILESDIR|SYSROOT)}/[a-z/]+)|' +
+                   r'(bcs://[A-Za-z0-9\.]+\.tbz2)', FW_COND),
+    ]
+
+# Firmware build targets schema, defined here since it is used in a few places.
+BUILD_TARGETS_SCHEMA = NodeDesc('build-targets', True, elements=[
+    PropString('coreboot', True),
+    PropString('ec', True),
+    PropString('depthcharge', True),
+    PropString('libpayload', True),
+    PropString('u-boot'),
+    PropString('cr50'),
+    PropString('base'),
+], conditional_props={'shares': False, '../whitelabel': False})
+
+BASE_AUDIO_SCHEMA = [
+    PropString('card', True, '', {'audio-type': False}),
+    PropFile('volume', True, '', {'audio-type': False}, CRAS_CONFIG_DIR),
+    PropFile('dsp-ini', True, '', {'audio-type': False}, CRAS_CONFIG_DIR),
+    PropFile('hifi-conf', True, '', {'audio-type': False}, UCM_CONFIG_DIR),
+    PropFile('alsa-conf', True, '', {'audio-type': False}, UCM_CONFIG_DIR),
+    PropString('topology-name', False, r'\w+'),
+    PropFile('topology-bin', False, '', {'audio-type': False}, LIB_FIRMWARE),
+
+    # TODO(sjg@chromium.org): There is no validation that we have these two.
+    # They must both exist either in the model's audio node or here.
+    PropFile('cras-config-dir', False, r'[\w${}]+', target_dir=CRAS_CONFIG_DIR),
+    PropString('ucm-suffix', False, r'[\w${}]+'),
+]
+
+ARC_PROPERTIES_SCHEMA = [
+    PropString('product', False, '[a-z0-9]+', {'../whitelabel': False}),
+    PropString('device', False, '[{}a-z0-9_]+', {'../whitelabel': False}),
+    PropString('oem', False),
+    PropString('marketing-name', False),
+    PropString('metrics-tag', False),
+    PropString('first-api-level', False, '[0-9]+'),
+]
+
+BASE_AUDIO_NODE = [
+    NodeAny(r'main', [
+        PropPhandle('audio-type', '/chromeos/family/audio/ANY',
+                    False),
+    ] + BASE_AUDIO_SCHEMA)
+]
+
+BASE_POWER_SCHEMA = [
+    PropString('charging-ports'),
+    PropFloat('keyboard-backlight-no-als-brightness', False, (0, 100)),
+    PropFloat('low-battery-shutdown-percent', False, (0, 100)),
+    PropFloat('power-supply-full-factor', False, (0.001, 1.0)),
+    PropString('set-wifi-transmit-power-for-tablet-mode', False, '0|1'),
+    PropString('suspend-to-idle', False, '0|1'),
+    PropString('touchpad-wakeup', False, '0|1'),
+]
+
+NOT_WL = {'whitelabel': False}
+
+"""This is the schema. It is a hierarchical set of nodes and properties, just
+like the device tree. If an object subclasses NodeDesc then it is a node,
+possibly with properties and subnodes.
+
+In this way it is possible to describe the schema in a fairly natural,
+hierarchical way.
+"""
+SCHEMA = NodeDesc('/', True, [
+    NodeDesc('chromeos', True, [
+        NodeDesc('family', True, [
+            NodeDesc('audio', elements=[
+                NodeAny('', [PropPhandleTarget()] +
+                        copy.deepcopy(BASE_AUDIO_SCHEMA)),
+            ]),
+            NodeDesc('bcs', False, [
+                NodeAny('', [
+                    PropPhandleTarget(),
+                    PropString('overlay', False, 'overlay-.*'),
+                    PropString('package'),
+                    PropString('tarball'),
+                    PropString('ebuild-version', False, '[-.0-9r]+'),
+                ]),
+            ]),
+            NodeDesc('power', elements=[
+                NodeAny('', [PropPhandleTarget()] +
+                        copy.deepcopy(BASE_POWER_SCHEMA)),
+            ]),
+            NodeDesc('arc', elements=[
+                NodeDesc('build-properties', elements=[
+                    NodeAny('', [PropPhandleTarget()] +
+                            copy.deepcopy(ARC_PROPERTIES_SCHEMA)),
+                ]),
+            ]),
+            NodeDesc('firmware', elements=[
+                PropString('script', True, r'updater4\.sh'),
+                NodeAny('', [
+                    PropPhandleTarget(),
+                    copy.deepcopy(BUILD_TARGETS_SCHEMA),
+                    ] + copy.deepcopy(BASE_FIRMWARE_SCHEMA))
+            ]),
+            NodeDesc('touch', False, [
+                NodeAny('', [
+                    PropPhandleTarget(),
+                    PropPhandle('bcs-type', '/chromeos/family/bcs/ANY'),
+                    PropFile('firmware-bin', True, target_dir=TOUCH_FIRMWARE),
+                    PropFile('firmware-symlink', True, target_dir=LIB_FIRMWARE),
+                    PropString('vendor', True, ''),
+                ]),
+            ]),
+            NodeDesc('mapping', False, [
+                NodeAny(r'sku-map(@[0-9])?', [
+                    PropString('platform-name', False, ''),
+                    PropString('smbios-name-match', False, ''),
+                    PropPhandle('single-sku', '/chromeos/models/MODEL', False),
+                    PropCustom('simple-sku-map',
+                               CrosConfigValidator.ValidateSkuMap, False),
+                ]),
+            ]),
+        ]),
+        NodeDesc('models', True, [
+            NodeModel([
+                PropPhandleTarget(),
+                PropPhandle('default', '/chromeos/models/MODEL', False),
+                PropPhandle('whitelabel', '/chromeos/models/MODEL', False),
+                NodeDesc('firmware', False, [
+                    PropPhandle('shares', '/chromeos/family/firmware/ANY',
+                                False, {'../whitelabel': False}),
+                    PropString(('sig-id-in-customization-id'),
+                               conditional_props={'../whitelabel': False}),
+                    PropString('key-id', False, '[A-Z][A-Z0-9]+'),
+                    PropBool('no-firmware'),
+                    copy.deepcopy(BUILD_TARGETS_SCHEMA)
+                    ] + copy.deepcopy(BASE_FIRMWARE_SCHEMA)),
+                PropString('brand-code', False, '[A-Z]{4}'),
+                PropString('powerd-prefs', conditional_props=NOT_WL),
+                PropString('test-label', False, '[a-z0-9_]+'),
+                PropString('wallpaper', False, '[a-z_]+'),
+                PropString('oem-id', False, '[0-9]+'),
+                NodeDesc('audio', False, copy.deepcopy(BASE_AUDIO_NODE),
+                         conditional_props=NOT_WL),
+                NodeDesc('arc', False, [
+                    PropFile('hw-features', False, '',
+                             target_dir=ARC_SBIN_DIR),
+                    NodeDesc('build-properties', False, [
+                        PropPhandle('arc-properties-type',
+                                    '/chromeos/family/arc/build-properties/ANY',
+                                    False, {'../whitelabel': False})
+                    ] + copy.deepcopy(ARC_PROPERTIES_SCHEMA)),
+                ], conditional_props=NOT_WL),
+                NodeDesc('power', False, [
+                    PropPhandle('power-type', '/chromeos/family/power/ANY',
+                                False),
+                ] + copy.deepcopy(BASE_POWER_SCHEMA), conditional_props=NOT_WL),
+                NodeDesc('submodels', False, [
+                    NodeSubmodel([
+                        PropPhandleTarget(),
+                        NodeDesc('audio', False, copy.deepcopy(BASE_AUDIO_NODE),
+                                 conditional_props={'../../audio': False}),
+                        NodeDesc('touch', False, [
+                            PropString('present', False, r'yes|no|probe'),
+                            PropString('probe-regex', False, ''),
+                        ]),
+                    ])
+                ], conditional_props=NOT_WL),
+                NodeDesc('thermal', False, [
+                    PropFile('dptf-dv', False, r'\w+/dptf.dv',
+                             target_dir='/etc/dptf'),
+                ], conditional_props=NOT_WL),
+                NodeDesc('touch', False, [
+                    PropString('present', False, r'yes|no|probe'),
+                    # We want to validate that probe-regex is only present when
+                    # 'present' = 'probe', but have no way of doing this
+                    # currently.
+                    PropString('probe-regex', False, ''),
+                    NodeAny(r'(stylus|touchpad|touchscreen)(@[0-9])?', [
+                        PropPhandle('bcs-type', '/chromeos/family/bcs/ANY'),
+                        PropString('pid', False),
+                        PropString('version', True),
+                        PropPhandle('touch-type', '/chromeos/family/touch/ANY',
+                                    False),
+                        PropFile('firmware-bin', True, '',
+                                 {'touch-type': False},
+                                 target_dir=TOUCH_FIRMWARE),
+                        PropFile('firmware-symlink', True, '',
+                                 {'touch-type': False},
+                                 target_dir=LIB_FIRMWARE),
+                        PropString('date-code', False),
+                    ]),
+                ], conditional_props=NOT_WL),
+                NodeDesc('whitelabels', False, [
+                    NodeAny('', [
+                        PropString('brand-code', False, '[A-Z]{4}'),
+                        PropString('wallpaper', False, '[a-z_]+'),
+                        PropString('key-id', False, '[A-Z][A-Z0-9]+'),
+                    ]),
+                ], conditional_props=NOT_WL),
+                NodeDesc('ui', False, [
+                    NodeDesc('power-button', False, [
+                        PropString('edge', True, 'left|right|top|bottom'),
+                        PropFloat('position', True, (0, 1)),
+                    ]),
+                ], conditional_props=NOT_WL),
+            ])
+        ]),
+        NodeDesc('schema', False, [
+            NodeDesc('target-dirs', False, [
+                PropAny(),
+            ]),
+            PropStringList('phandle-properties'),
+        ])
+    ])
+])
+
+
+def GetValidator():
+  """Get a schema validator for use by another module
+
+  Returns:
+    CrosConfigValidator object
+  """
+  return CrosConfigValidator(SCHEMA, raise_on_error=True)
+
+
+def ShowErrors(fname, errors):
+  """Show validation errors
+
+  Args:
+    fname: Filename containng the errors
+    errors: List of errors, each a string
+  """
+  print('%s:' % fname, file=sys.stderr)
+  for error in errors:
+    print(error, file=sys.stderr)
+  print(file=sys.stderr)
+
+
+def Main(argv=None):
+  """Main program for validator
+
+  This validates each of the provided files and prints the errors for each, if
+  any.
+
+  Args:
+    argv: Arguments to the problem (excluding argv[0]); if None, uses sys.argv
+  """
+  if argv is None:
+    argv = sys.argv[1:]
+  args = ParseArgv(argv)
+  validator = CrosConfigValidator(SCHEMA, args.raise_on_error)
+  found_errors = False
+  try:
+    # If we are given partial files (.dtsi) then we compile them all into one
+    # .dtb and validate that.
+    if args.partial:
+      errors = validator.Start(args.config, partial=True)
+      fname = args.config[0]
+      if errors:
+        ShowErrors(fname, errors)
+        found_errors = True
+
+    # Otherwise process each file individually
+    else:
+      for fname in args.config:
+        errors = validator.Start([fname])
+        if errors:
+          found_errors = True
+          if errors:
+            ShowErrors(fname, errors)
+            found_errors = True
+  except cros_build_lib.RunCommandError as e:
+    if args.debug:
+      raise
+    print('Failed: %s' % e, file=sys.stderr)
+    found_errors = True
+  if found_errors:
+    sys.exit(1)
+
+
+if __name__ == "__main__":
+  Main()
diff --git a/chromeos-config/cros_config_host/validate_config_unittest.py b/chromeos-config/cros_config_host/validate_config_unittest.py
new file mode 100755
index 0000000..f1c6b83
--- /dev/null
+++ b/chromeos-config/cros_config_host/validate_config_unittest.py
@@ -0,0 +1,704 @@
+#!/usr/bin/env python2
+# Copyright 2017 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Unit tests for the config validator"""
+
+from __future__ import print_function
+
+import os
+import subprocess
+import tempfile
+
+from chromite.lib import cros_test_lib
+
+from . import validate_config
+
+
+HEADER = '''/dts-v1/;
+
+/ {
+  chromeos {
+    family: family {
+    };
+
+    models: models {
+    };
+    schema {
+      target-dirs {
+        dptf-dv = "/etc/dptf";
+      };
+    };
+  };
+};
+'''
+
+MODELS = '''
+&models {
+  reef: reef {
+  };
+  pyro: pyro {
+  };
+  snappy: snappy {
+  };
+};
+'''
+
+FAMILY_FIRMWARE_MISSING = '''
+&family {
+  firmware {
+    script = "updater4.sh";
+    shared: reef {
+      ec-image = "bcs://Reef_EC.9984.0.0.tbz2";
+      main-image = "bcs://Reef.9984.0.0.tbz2";
+    };
+  };
+};
+'''
+
+FAMILY_FIRMWARE_SCRIPT_MISSING = '''
+&family {
+  firmware {
+  };
+};
+'''
+
+FAMILY_FIRMWARE_BAD_SHARES = '''
+&family {
+  firmware {
+    shared: reef {
+      shares = <&shared>;
+    };
+  };
+};
+'''
+
+FAMILY_FIRMWARE = FAMILY_FIRMWARE_MISSING + '''
+&family {
+  firmware {
+    script = "updater4.sh";
+    shared: reef {
+      bcs-overlay = "overlay-reef-private";
+      build-targets {
+        coreboot = "reef";
+        ec = "reef";
+        depthcharge = "reef";
+        libpayload = "reef";
+      };
+    };
+  };
+};
+'''
+
+MODEL_FIRMWARE_SHARED_WRONG_TARGET = '''
+&family {
+  firmware {
+    shared: reef {
+      build_targets: build-targets {
+        coreboot = "reef";
+        ec = "reef";
+        depthcharge = "reef";
+        libpayload = "reef";
+      };
+    };
+  };
+};
+
+&models {
+  reef {
+    firmware {
+      shares = < &build_targets >;
+      key-id = "REEF";
+    };
+  };
+};
+'''
+
+MODEL_FIRMWARE_SHARED = '''
+&models {
+  reef {
+    firmware {
+      shares = < &shared >;
+      key-id = "REEF";
+    };
+  };
+};
+'''
+
+MODEL_FIRMWARE_SHARE_EXTRA_PROPS = '''
+&models {
+  reef {
+    firmware {
+      shares = < &shared >;
+      key-id = "REEF";
+      bcs-overlay = "overlay-reef-private";
+    };
+  };
+};
+'''
+
+MODEL_FIRMWARE_SHARE_EXTRA_NODES = '''
+&models {
+  reef {
+    firmware {
+      shares = < &shared >;
+      key-id = "REEF";
+      build-targets {
+        coreboot = "reef";
+      };
+    };
+  };
+};
+'''
+
+MODEL_BRAND_CODE = '''
+&models {
+  reef {
+    brand-code = "ABCD";
+  };
+  pyro{
+    brand-code = "abcd";
+  };
+  snappy{
+    brand-code = "AB1";
+  };
+};
+'''
+
+MODEL_THERMAL = '''
+&models {
+  reef {
+    thermal {
+      dptf-dv = "reef/dptf.dv";
+    };
+  };
+  pyro{
+    thermal {
+      dptf-dv = "dptf.dv";
+    };
+  };
+  snappy{
+    thermal {
+      dptf-dv = "reef/bad.dv";
+    };
+  };
+};
+'''
+
+TOUCH = r'''
+&family {
+  touch {
+    elan_touchscreen: elan-touchscreen {
+      vendor = "elan";
+      firmware-bin = "{vendor}/{pid}_{version}.bin";
+      firmware-symlink = "{vendor}ts_i2c_{pid}.bin";
+    };
+    weida_touchscreen: weida-touchscreen {
+        vendor = "weida";
+        firmware-bin = "weida/{pid}_{version}_{date-code}.bin";
+        firmware-symlink = "wdt87xx.bin";
+    };
+  };
+};
+&models {
+  reef {
+    touch {
+      present = "probe";
+      probe-regex = "[Tt]ouchscreen|WCOMNTN2";
+      touchscreen@0 {
+        touch-type = <&elan_touchscreen>;
+        pid = "0a97";
+        version = "1012";
+      };
+      touchscreen@1 {
+        touch-type = <&shared>;
+        pid = "0b10";
+        version = "002n";
+      };
+      touchscreen@2 {
+        touch-type = <&weida_touchscreen>;
+        pid = "01017401";
+        version = "2082";
+        date-code = "0133c65b";
+      };
+      bad {
+      };
+    };
+  };
+};
+'''
+
+AUDIO = r'''
+&family {
+  audio {
+      audio_type: audio-type {
+        card = "bxtda7219max";
+        volume = "cras-config/{cras-config-dir}/{card}";
+        dsp-ini = "cras-config/{cras-config-dir}/dsp.ini";
+        hifi-conf = "ucm-config/{card}.{ucm-suffix}/HiFi.conf";
+        alsa-conf = "ucm-config/{card}.{ucm-suffix}/{card}.{ucm-suffix}.conf";
+        topology-bin = "topology/5a98-reef-{topology-name}-8-tplg.bin";
+      };
+      bad_audio_type: bad-audio-type {
+        volume = "cras-config/{cras-config-dir}/{card}";
+        dsp-ini = "cras-config/{cras-config-dir}/dsp.ini";
+        hifi-conf = "ucm-config/{card}.{ucm-suffix}/HiFi.conf";
+        alsa-conf = "ucm-config/{card}.{ucm-suffix}/{card}.{ucm-suffix}.conf";
+        topology-bin = "topology/5a98-reef-{topology-name}-8-tplg.bin";
+      };
+  };
+};
+&models {
+  pyro: pyro {
+    powerd-prefs = "pyro_snappy";
+    wallpaper = "alien_invasion";
+    brand-code = "ABCE";
+    audio {
+      main {
+        audio-type = <&audio_type>;
+        cras-config-dir = "pyro";
+        ucm-suffix = "pyro";
+        topology-name = "pyro";
+      };
+    };
+  };
+  pyro: pyro {
+    powerd-prefs = "pyro_snappy";
+    wallpaper = "alien_invasion";
+    brand-code = "ABCE";
+    audio {
+      main {
+        audio-type = <&audio_type>;
+        ucm-suffix = "pyro";
+        topology-name = "pyro";
+      };
+    };
+  };
+  snappy: snappy {
+    powerd-prefs = "pyro_snappy";
+    wallpaper = "chocolate";
+    brand-code = "ABCF";
+    audio {
+      main {
+        cras-config-dir = "snappy";
+        ucm-suffix = "snappy";
+        topology-name = "snappy";
+      };
+    };
+  };
+};
+'''
+
+POWER = r'''
+&family {
+  power {
+    power_type: power_type {
+      power-supply-full-factor = "0.10";
+      suspend-to-idle = "1";
+      touchpad-wakeup = "1";
+    };
+    bad_power_type: bad-power-type {
+      low-battery-shutdown-percent = "110";
+      power-supply-full-factor = "ten point one";
+      suspend-to-idle = "2";
+      touchpad-wakeup = "2";
+    };
+  };
+};
+&models {
+  pyro: pyro {
+    power {
+      power-type = <&power_type>;
+      charging-ports = "CROS_USB_PD_CHARGER0 LEFT\nCROS_USB_PD_CHARGER1 RIGHT";
+      power-supply-full-factor = "0.12";
+    };
+  };
+};
+'''
+
+MAPPING = '''
+&family {
+  mapping {
+    sku-map@0 {
+      platform-name = "Reef";
+      smbios-name-match = "reef";
+      /* This is an example! It does not match any real family */
+      simple-sku-map = <
+        (-1) &pyro
+        0 &reef
+        4 &reef_4
+        4 &snappy    /* duplicate */
+        5 &reef_5
+        8 &shared
+        65536 &reef_5>;
+    };
+    sku-map@1 {
+      platform-name = "Pyro";
+      smbios-name-match = "pyro";
+      single-sku = <&pyro>;
+    };
+    sku-map@2 {
+      platform-name = "Snappy";
+      smbios-name-match = "snappy";
+      single-sku = <&snappy>;
+    };
+  };
+};
+&models {
+  reef {
+    /*
+     * We don't have submodel validation, but add this in here so that mapping
+     * validation is complete.
+     */
+    submodels {
+      reef_4: reef-touchscreen {
+      };
+      reef_5: reef-notouch {
+      };
+    };
+  };
+};
+'''
+
+WHITELABEL = '''
+&models {
+  /* Whitelabel model */
+  whitetip: whitetip {
+    firmware {
+      shares = <&shared>;
+    };
+  };
+
+  whitetip1 {
+    whitelabel = <&whitetip>;
+    wallpaper = "shark";
+    brand-code = "SHAR";
+    firmware {
+      key-id = "WHITELABEL1";
+    };
+  };
+
+  whitetip2 {
+    whitelabel = <&whitetip>;
+    wallpaper = "more_shark";
+    brand-code = "SHAQ";
+    firmware {
+      key-id = "WHITELABEL2";
+    };
+  };
+
+  bad {
+    whitelabel = <&whitetip>;
+    firmware {
+      shares = <&shared>;
+      key-id = "WHITELABEL2";
+    };
+    thermal {
+      dptf-dv = "bad/dptf.dv";
+    };
+  };
+};
+'''
+
+DEFAULT_MODEL = '''
+&models {
+  /* Model which gets most of its settings from another */
+  other {
+    default = <&reef>;
+  };
+};
+'''
+
+class UnitTests(cros_test_lib.TestCase):
+  """Unit tests for CrosConfigValidator
+
+  Properties:
+    val: Validator to use
+    returncode: Holds the return code for the case where the validator is called
+        through its command-line interface
+  """
+  def setUp(self):
+    self.val = validate_config.CrosConfigValidator(validate_config.SCHEMA,
+                                                   False)
+    self.returncode = 0
+
+  def Run(self, dts_source, use_command_line=False, extra_options=None):
+    """Run the validator with a single source file
+
+    Args:
+      dts_source: String containing the device-tree source to process
+      use_command_line: True to run through the command-line interface.
+          Otherwise the imported validator class is used directly. When using
+          the command-line interface, the return code is available in
+          self.returncode, since only one test needs it.
+      extra_options: Extra command-line arguments to pass
+    """
+    dts = tempfile.NamedTemporaryFile(suffix='.dts', delete=False)
+    dts.write(dts_source)
+    dts.close()
+    self.returncode = 0
+    if use_command_line:
+      call_args = ['python', '-m', 'cros_config_host.validate_config',
+                   '-d', dts.name]
+      if extra_options:
+        call_args += extra_options
+      try:
+        output = subprocess.check_output(call_args, stderr=subprocess.STDOUT)
+      except subprocess.CalledProcessError as e:
+        output = e.output
+        self.returncode = e.returncode
+      errors = output.strip().splitlines()
+    else:
+      errors = self.val.Start([dts.name])
+    if errors:
+      return errors
+    if dts:
+      os.unlink(dts.name)
+    return []
+
+  def RunMultiple(self, dts_source_list, use_command_line=False):
+    """Run the validator with a list of .dtsi fragments
+
+    Args:
+      dts_source_list: List of strings, containing the device-tree source to
+          process for each fragment
+      use_command_line: True to run through the command-line interface.
+          Otherwise the imported validator class is used directly. When using
+          the command-line interface, the return code is available in
+          self.returncode, since only one test needs it.
+    """
+    dts_list = []
+    for source in dts_source_list:
+      dts = tempfile.NamedTemporaryFile(suffix='.dtsi', delete=False)
+      dts_list.append(dts)
+      dts.write(source)
+      dts.close()
+    fnames = [dts.name for dts in dts_list]
+    self.returncode = 0
+    if use_command_line:
+      call_args = ['python', '-m', 'cros_config_host.validate_config',
+                   '-d', '-p'] + fnames
+      try:
+        output = subprocess.check_output(call_args, stderr=subprocess.STDOUT)
+      except subprocess.CalledProcessError as e:
+        output = e.output
+        self.returncode = e.returncode
+      errors = output.strip().splitlines()
+    else:
+      errors = self.val.Start(fnames, partial=True)
+    if errors:
+      return errors
+    if dts:
+      os.unlink(dts.name)
+    return []
+
+  def _CheckAllIn(self, err_msg_list, result_lines):
+    """Check that the given messages appear in the validation result
+
+    All messages must appear, and all lines must be matches.
+
+    Args:
+      result_lines: List of validation results to check, each a string
+      err_msg_list: List of error messages to check for
+    """
+    err_msg_set = set(err_msg_list)
+    for line in result_lines:
+      found = False
+      for err_msg in err_msg_set:
+        if err_msg in line:
+          err_msg_set.remove(err_msg)
+          found = True
+          break
+      if not found:
+        self.fail("Found unexpected result: %s" % line)
+    if err_msg_set:
+      self.fail("Expected '%s'\n but not found in result: %s" %
+                (err_msg_set.pop(), '\n'.join(result_lines)))
+
+  def testBase(self):
+    """Test a skeleton file"""
+    self.assertEqual([], self.Run(HEADER))
+
+  def testModels(self):
+    """Test a skeleton file with models"""
+    self.assertEqual([], self.Run(HEADER + MODELS))
+
+  def testFamilyFirmwareMissing(self):
+    """Test family firmware with missing pieces"""
+    self.assertEqual([
+        "/chromeos/family/firmware/reef: Required property 'bcs-overlay' " +
+        "missing",
+        "/chromeos/family/firmware/reef: Missing subnode 'build-targets'",
+        ], self.Run(HEADER + MODELS + FAMILY_FIRMWARE_MISSING))
+
+  def testFamilyFirmwareScriptMissing(self):
+    """Test a family firmware without a script"""
+    self.assertIn(
+        "/chromeos/family/firmware: Required property 'script' missing",
+        self.Run(HEADER + MODELS + FAMILY_FIRMWARE_SCRIPT_MISSING))
+
+  def testFamilyFirmware(self):
+    """Test valid family firmware"""
+    self.assertEqual([], self.Run(HEADER + MODELS + FAMILY_FIRMWARE))
+
+  def testFamilyFirmwareSharedWrongTarget(self):
+    """Test a model trying to share an invalid firmware target"""
+    result = self.Run(HEADER + MODELS + FAMILY_FIRMWARE +
+                      MODEL_FIRMWARE_SHARED_WRONG_TARGET)
+    self.assertEqual(
+        ['/chromeos/family/firmware/reef/build-targets: phandle target not '
+         'valid for this node',
+         "/chromeos/models/reef/firmware: Phandle 'shares' " +
+         "targets node '/chromeos/family/firmware/reef/build-targets' "
+         "which does not match pattern '/chromeos/family/firmware/ANY'",
+        ], result)
+
+  def testFamilyFirmwareBadShares(self):
+    """Test a model trying to share an invalid firmware target"""
+    result = self.Run(HEADER + MODELS + FAMILY_FIRMWARE +
+                      FAMILY_FIRMWARE_BAD_SHARES)
+    self.assertEqual(
+        ["/chromeos/family/firmware/reef: Unexpected property 'shares', " +
+         "valid list is (phandle, bcs-overlay, ec-image, main-image, " +
+         "main-rw-image, pd-image, extra)"], result)
+
+  def testFamilyFirmwareShared(self):
+    """Test valid shared firmware"""
+    result = self.Run(HEADER + MODELS + FAMILY_FIRMWARE +
+                      MODEL_FIRMWARE_SHARED)
+    self.assertEqual([], result)
+
+  def testFamilyFirmwareSharedExtraProps(self):
+    """Test the model trying to specify properties that should be shared"""
+    result = self.Run(HEADER + MODELS + FAMILY_FIRMWARE +
+                      MODEL_FIRMWARE_SHARE_EXTRA_PROPS)
+    self.assertEqual(
+        ["/chromeos/models/reef/firmware: Unexpected property 'bcs-overlay', "
+         'valid list is (shares, sig-id-in-customization-id, key-id, '
+         'no-firmware)'], result)
+
+  def testFamilyFirmwareSharedExtraNodes(self):
+    """Test the model trying to specify nodes that should be shared"""
+    result = self.Run(HEADER + MODELS + FAMILY_FIRMWARE +
+                      MODEL_FIRMWARE_SHARE_EXTRA_NODES)
+    self.assertEqual(
+        ["/chromeos/models/reef/firmware: Unexpected subnode 'build-targets', "
+         "valid list is ()"], result)
+
+  def testBrandCode(self):
+    """Test validation of brand codes"""
+    self.assertEqual([
+        "/chromeos/models/pyro: 'brand-code' value 'abcd' does not match " +
+        "pattern '^[A-Z]{4}$'",
+        "/chromeos/models/snappy: 'brand-code' value 'AB1' does not match " +
+        "pattern '^[A-Z]{4}$'"], self.Run(HEADER + MODELS + MODEL_BRAND_CODE))
+
+  def testThermal(self):
+    """Test validation of the thermal node"""
+    self.assertEqual([
+        "/chromeos/models/pyro/thermal: 'dptf-dv' value 'dptf.dv' does not "
+        "match pattern '^\\w+/dptf.dv$'",
+        "/chromeos/models/snappy/thermal: 'dptf-dv' value 'reef/bad.dv' does " +
+        "not match pattern '^\\w+/dptf.dv$'"
+        ], self.Run(HEADER + MODELS + MODEL_THERMAL))
+
+  def testTouch(self):
+    """Test validation of the thermal node"""
+    result = self.Run(HEADER + MODELS + FAMILY_FIRMWARE + TOUCH)
+    self._CheckAllIn([
+        "Node name 'bad' does not match pattern",
+        "/bad: Required property 'version' missing",
+        "/bad: Required property 'firmware-bin' missing",
+        "/bad: Required property 'firmware-symlink' missing",
+        "/touchscreen@1: Phandle 'touch-type' targets node",
+        ], result)
+
+  def testAudio(self):
+    """Test validation of the audio nodes"""
+    result = self.Run(HEADER + MODELS + AUDIO)
+    self._CheckAllIn([
+        "snappy/audio/main: Required property 'card' missing",
+        "snappy/audio/main: Required property 'volume' missing",
+        "snappy/audio/main: Required property 'dsp-ini' missing",
+        "snappy/audio/main: Required property 'hifi-conf' missing",
+        "snappy/audio/main: Required property 'alsa-conf' missing",
+        "bad-audio-type: Required property 'card' missing",
+        ], result)
+
+  def testPower(self):
+    """Test validation of the power nodes"""
+    result = self.Run(HEADER + MODELS + POWER)
+    self._CheckAllIn([
+        "/chromeos/family/power/bad-power-type: 'power-supply-full-factor' " +
+        "value 'ten point one' is not a float",
+        "/chromeos/family/power/bad-power-type: 'suspend-to-idle' value '2' " +
+        "does not match pattern '^0|1$'",
+        "/chromeos/family/power/bad-power-type: 'touchpad-wakeup' value '2' " +
+        "does not match pattern '^0|1$'",
+        "/chromeos/family/power/bad-power-type: " +
+        "'low-battery-shutdown-percent' value '110' is out of range [0..100]",
+        ], result)
+
+  def testMapping(self):
+    """Test validation of the mapping node"""
+    result = self.Run(HEADER + MODELS + FAMILY_FIRMWARE + MAPPING)
+    self._CheckAllIn([
+        'mapping/sku-map@0: Duplicate sku_id 4',
+        'mapping/sku-map@0: sku_id 65536 out of range',
+        "mapping/sku-map@0: Phandle 'simple-sku-map' sku-id 8 must target a " +
+        'model or submodel',
+        ], result)
+
+  def testWhiteLabel(self):
+    result = self.Run(HEADER + MODELS + FAMILY_FIRMWARE + WHITELABEL)
+    self._CheckAllIn([
+        "/bad: Unexpected subnode 'thermal', valid list is (",
+        "bad/firmware: Unexpected property 'shares', valid list is "
+        '(key-id, no-firmware)',
+        ], result)
+
+  def testPartial(self):
+    """Test that we can validate config coming from multiple .dtsi files"""
+    result = self.RunMultiple([MODELS, FAMILY_FIRMWARE, WHITELABEL])
+    self._CheckAllIn([
+        "/bad: Unexpected subnode 'thermal', valid list is (",
+        "bad/firmware: Unexpected property 'shares', valid list is "
+        '(key-id, no-firmware)',
+        ], result)
+
+  def testComanndLine(self):
+    """Test that the command-line interface works correctly"""
+    self.assertEqual([], self.Run(HEADER, True))
+
+    output = self.Run(HEADER, True, ['--invalid-option'])
+    self.assertEqual(self.returncode, 2)
+    self._CheckAllIn([
+        'usage: validate_config.py',
+        'error: unrecognized arguments: --invalid-option'
+        ], output)
+
+    result = self.Run(HEADER + MODELS + FAMILY_FIRMWARE + WHITELABEL, True)
+    self.assertEqual(self.returncode, 1)
+    self._CheckAllIn([
+        '/tmp/',
+        "/bad: Unexpected subnode 'thermal', valid list is (",
+        "bad/firmware: Unexpected property 'shares', valid list is "
+        '(key-id, no-firmware)',
+        ], result)
+
+    result = self.RunMultiple([MODELS, FAMILY_FIRMWARE, WHITELABEL], True)
+    self.assertEqual(self.returncode, 1)
+    self._CheckAllIn([
+        '/tmp/',
+        "/bad: Unexpected subnode 'thermal', valid list is (",
+        "bad/firmware: Unexpected property 'shares', valid list is "
+        '(key-id, no-firmware)',
+        ], result)
+
+  def testDefault(self):
+    result = self.Run(HEADER + MODELS + FAMILY_FIRMWARE + DEFAULT_MODEL)
+    self.assertEqual([], result)
+
+if __name__ == '__main__':
+  cros_test_lib.main(module=__name__)
diff --git a/chromeos-config/cros_config_host/validate_schema.py b/chromeos-config/cros_config_host/validate_schema.py
new file mode 100644
index 0000000..e9e9c4f
--- /dev/null
+++ b/chromeos-config/cros_config_host/validate_schema.py
@@ -0,0 +1,325 @@
+# Copyright 2017 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Schema elements.
+
+This module provides schema elements that can be used to build up a schema for
+validation of the master configuration
+"""
+
+from __future__ import print_function
+
+import re
+
+
+def CheckPhandleTarget(val, target, target_path_match):
+  """Check that the target of a phandle matches a pattern
+
+  Args:
+    val: Validator (used for model list, etc.)
+    target: Target node path (string)
+    target_path_match: Match string. This is the full path to the node that the
+        target must point to. Some 'wildcard' nodes are supported in the path:
+
+           MODEL - matches any model node
+           SUBMODEL - matches any submodel when MODEL is earlier in the path
+           ANY - matches any node
+
+  Returns:
+    True if the target matches, False if not
+  """
+  model = None
+  parts = target_path_match.split('/')
+  target_parts = target.path.split('/')
+  ok = len(parts) == len(target_parts)
+  if ok:
+    for i, part in enumerate(parts):
+      if part == 'MODEL':
+        if target_parts[i] in val.model_list:
+          model = target_parts[i]
+          continue
+      if part == 'SUBMODEL' and model:
+        if target_parts[i] in val.submodel_list[model]:
+          continue
+      elif part == 'ANY':
+        continue
+      if part != target_parts[i]:
+        ok = False
+  return ok
+
+
+class SchemaElement(object):
+  """A schema element, either a property or a subnode
+
+  Args:
+    name: Name of schema eleent
+    prop_type: String describing this property type
+    required: True if this element is mandatory, False if optional
+    conditional_props: Properties which control whether this element is present.
+       Dict:
+         key: name of controlling property
+         value: True if the property must be present, False if it must be absent
+  """
+  def __init__(self, name, prop_type, required=False, conditional_props=None):
+    self.name = name
+    self.prop_type = prop_type
+    self.required = required
+    self.conditional_props = conditional_props
+    self.parent = None
+
+  def Validate(self, val, prop):
+    """Validate the schema element against the given property.
+
+    This method is overridden by subclasses. It should call val.Fail() if there
+    is a problem during validation.
+
+    Args:
+      val: CrosConfigValidator object
+      prop: Prop object of the property
+    """
+    pass
+
+
+class PropDesc(SchemaElement):
+  """A generic property schema element (base class for properties)"""
+  def __init__(self, name, prop_type, required=False, conditional_props=None):
+    super(PropDesc, self).__init__(name, prop_type, required, conditional_props)
+
+
+class PropString(PropDesc):
+  """A string-property
+
+  Args:
+    str_pattern: Regex to use to validate the string
+  """
+  def __init__(self, name, required=False, str_pattern='',
+               conditional_props=None):
+    super(PropString, self).__init__(name, 'string', required,
+                                     conditional_props)
+    self.str_pattern = str_pattern
+
+  def Validate(self, val, prop):
+    """Check the string with a regex"""
+    if not self.str_pattern:
+      return
+    pattern = '^' + self.str_pattern + '$'
+    m = re.match(pattern, prop.value)
+    if not m:
+      val.Fail(prop.node.path, "'%s' value '%s' does not match pattern '%s'" %
+               (prop.name, prop.value, pattern))
+
+
+class PropFloat(PropDesc):
+  """A floating-point property"""
+  def __init__(self, name, required=False, float_range=None,
+               conditional_props=None):
+    super(PropFloat, self).__init__(name, 'float', required, conditional_props)
+    self.float_range = float_range
+
+  def Validate(self, val, prop):
+    """Check that the value is a float"""
+    try:
+      float_val = float(prop.value)
+      if self.float_range is not None:
+        # pylint: disable=unpacking-non-sequence
+        min_val, max_val = self.float_range
+        if float_val < min_val or float_val > max_val:
+          val.Fail(prop.node.path, "'%s' value '%s' is out of range [%g..%g]" %
+                   (prop.name, prop.value, min_val, max_val))
+
+    except ValueError:
+      val.Fail(prop.node.path, "'%s' value '%s' is not a float" %
+               (prop.name, prop.value))
+
+
+class PropBool(PropDesc):
+  """A boolean property"""
+  def __init__(self, name, conditional_props=None):
+    super(PropBool, self).__init__(name, 'bool', False, conditional_props)
+
+
+class PropFile(PropDesc):
+  """A file property
+
+  This represents a file to be installed on the filesystem.
+
+  Properties:
+    target_dir: Target directory in the filesystem for files from this
+        property (e.g. '/etc/cras'). This is used to set the install directory
+        and keep it consistent across ebuilds (which use cros_config_host) and
+        init scripts (which use cros_config). The actual file written will be
+        relative to this.
+  """
+  def __init__(self, name, required=False, str_pattern='',
+               conditional_props=None, target_dir=None):
+    super(PropFile, self).__init__(name, 'file', required, conditional_props)
+    self.str_pattern = str_pattern
+    self.target_dir = target_dir
+
+  def Validate(self, val, prop):
+    """Check the filename with a regex"""
+    if not self.str_pattern:
+      return
+    pattern = '^' + self.str_pattern + '$'
+    m = re.match(pattern, prop.value)
+    if not m:
+      val.Fail(prop.node.path, "'%s' value '%s' does not match pattern '%s'" %
+               (prop.name, prop.value, pattern))
+
+
+class PropStringList(PropDesc):
+  """A string-list property schema element
+
+  Note that the list may be empty in which case no validation is performed.
+
+  Args:
+    str_pattern: Regex to use to validate the string
+  """
+  def __init__(self, name, required=False, str_pattern='',
+               conditional_props=None):
+    super(PropStringList, self).__init__(name, 'stringlist', required,
+                                         conditional_props)
+    self.str_pattern = str_pattern
+
+  def Validate(self, val, prop):
+    """Check each item of the list with a regex"""
+    if not self.str_pattern:
+      return
+    pattern = '^' + self.str_pattern + '$'
+    for item in prop.value:
+      m = re.match(pattern, item)
+      if not m:
+        val.Fail(prop.node.path, "'%s' value '%s' does not match pattern '%s'" %
+                 (prop.name, item, pattern))
+
+
+class PropPhandleTarget(PropDesc):
+  """A phandle-target property schema element
+
+  A phandle target can be pointed to by another node using a phandle property.
+  """
+  def __init__(self, required=False, conditional_props=None):
+    super(PropPhandleTarget, self).__init__('phandle', 'phandle-target',
+                                            required, conditional_props)
+
+
+class PropPhandle(PropDesc):
+  """A phandle property schema element
+
+  Phandle properties point to other nodes, and allow linking from one node to
+  another.
+
+  Properties:
+    target_path_match: String to use to validate the target of this phandle.
+        It is the full path to the node that it must point to. See
+        CheckPhandleTarget for details.
+  """
+  def __init__(self, name, target_path_match, required=False,
+               conditional_props=None):
+    super(PropPhandle, self).__init__(name, 'phandle', required,
+                                      conditional_props)
+    self.target_path_match = target_path_match
+
+  def Validate(self, val, prop):
+    """Check that this phandle points to the correct place"""
+    phandle = prop.GetPhandle()
+    target = prop.fdt.LookupPhandle(phandle)
+    if not CheckPhandleTarget(val, target, self.target_path_match):
+      val.Fail(prop.node.path, "Phandle '%s' targets node '%s' which does not "
+               "match pattern '%s'" % (prop.name, target.path,
+                                       self.target_path_match))
+
+
+class PropCustom(PropDesc):
+  """A custom property with its own validator
+
+  Properties:
+    validator: Function to call to validate this property
+  """
+  def __init__(self, name, validator, required=False, conditional_props=None):
+    super(PropCustom, self).__init__(name, 'custom', required,
+                                     conditional_props)
+    self.validator = validator
+
+  def Validate(self, val, prop):
+    """Validator for this property
+
+    This should be a static method in CrosConfigValidator.
+
+    Args:
+      val: CrosConfigValidator object
+      prop: Prop object of the property
+    """
+    self.validator(val, prop)
+
+
+class PropAny(PropDesc):
+  """A placeholder for any property name
+
+  Properties:
+    validator: Function to call to validate this property
+  """
+  def __init__(self, validator=None):
+    super(PropAny, self).__init__('ANY', 'any')
+    self.validator = validator
+
+  def Validate(self, val, prop):
+    """Validator for this property
+
+    This should be a static method in CrosConfigValidator.
+
+    Args:
+      val: CrosConfigValidator object
+      prop: Prop object of the property
+    """
+    if self.validator:
+      self.validator(val, prop)
+
+
+class NodeDesc(SchemaElement):
+  """A generic node schema element (base class for nodes)"""
+  def __init__(self, name, required=False, elements=None,
+               conditional_props=None):
+    super(NodeDesc, self).__init__(name, 'node', required,
+                                   conditional_props)
+    self.elements = elements
+    for element in elements:
+      element.parent = self
+
+  def GetNodes(self):
+    """Get a list of schema elements which are nodes
+
+    Returns:
+      List of objects, each of which has NodeDesc as a base class
+    """
+    return [n for n in self.elements if isinstance(n, NodeDesc)]
+
+
+class NodeModel(NodeDesc):
+  """A generic node schema element (base class for nodes)"""
+  def __init__(self, elements):
+    super(NodeModel, self).__init__('MODEL', elements=elements)
+
+
+class NodeSubmodel(NodeDesc):
+  """A generic node schema element (base class for nodes)"""
+  def __init__(self, elements):
+    super(NodeSubmodel, self).__init__('SUBMODEL', elements=elements)
+
+
+class NodeAny(NodeDesc):
+  """A generic node schema element (base class for nodes)"""
+  def __init__(self, name_pattern, elements):
+    super(NodeAny, self).__init__('ANY', elements=elements)
+    self.name_pattern = name_pattern
+
+  def Validate(self, val, node):
+    """Check the name with a regex"""
+    if not self.name_pattern:
+      return
+    pattern = '^' + self.name_pattern + '$'
+    m = re.match(pattern, node.name)
+    if not m:
+      val.Fail(node.path, "Node name '%s' does not match pattern '%s'" %
+               (node.name, pattern))
diff --git a/chromeos-config/cros_config_main.cc b/chromeos-config/cros_config_main.cc
index 301b174..f743962 100644
--- a/chromeos-config/cros_config_main.cc
+++ b/chromeos-config/cros_config_main.cc
@@ -15,7 +15,8 @@
 #include "chromeos-config/libcros_config/cros_config.h"
 
 int main(int argc, char* argv[]) {
-  DEFINE_string(test_file, "",
+  DEFINE_bool(abspath, false, "Show the property value as an absolute path.");
+  DEFINE_string(test_database, "",
                 "Override path to system config database for testing.");
   DEFINE_string(test_name, "", "Override platform name for testing.");
   DEFINE_int32(test_sku_id, brillo::kDefaultSkuId,
@@ -28,8 +29,8 @@
                       "debug logging messages.\n";
   brillo::FlagHelper::Init(argc, argv, usage);
 
-  CHECK_EQ(FLAGS_test_file.empty(), FLAGS_test_name.empty())
-      << "You must pass both --test_file and --test_name or neither.";
+  CHECK_EQ(FLAGS_test_database.empty(), FLAGS_test_name.empty())
+      << "You must pass both --test_database and --test_name or neither.";
 
   logging::LoggingSettings settings;
   settings.logging_dest = logging::LOG_TO_FILE;
@@ -40,12 +41,12 @@
   logging::SetMinLogLevel(-3);
 
   brillo::CrosConfig cros_config;
-  if (FLAGS_test_file.empty()) {
+  if (FLAGS_test_database.empty()) {
     if (!cros_config.Init(FLAGS_test_sku_id)) {
       return 1;
     }
   } else {
-    if (!cros_config.InitForTestX86(base::FilePath(FLAGS_test_file),
+    if (!cros_config.InitForTestX86(base::FilePath(FLAGS_test_database),
                                  FLAGS_test_name, FLAGS_test_sku_id,
                                  FLAGS_whitelabel_tag)) {
       return 1;
@@ -62,7 +63,12 @@
   std::string property = args[1];
 
   std::string value;
-  bool result = cros_config.GetString(path, property, &value);
+  bool result;
+  if (FLAGS_abspath) {
+    result = cros_config.GetAbsPath(path, property, &value);
+  } else {
+    result = cros_config.GetString(path, property, &value);
+  }
   if (!result) {
     return 1;
   }
diff --git a/chromeos-config/cros_config_main_test.cc b/chromeos-config/cros_config_main_unittest.cc
similarity index 71%
rename from chromeos-config/cros_config_main_test.cc
rename to chromeos-config/cros_config_main_unittest.cc
index aee41d5..41500ad 100644
--- a/chromeos-config/cros_config_main_test.cc
+++ b/chromeos-config/cros_config_main_unittest.cc
@@ -28,7 +28,11 @@
     const std::vector<std::string>& params) {
   std::vector<std::string> cmd = {
       base::StringPrintf("%s/cros_config", installed_dir),
-      "--test_file=test.json",
+#ifndef USE_JSON
+      "--test_database=test.dtb",
+#else
+      "--test_database=test.json",
+#endif
       "--test_name=Another"};
   cmd.insert(cmd.end(), params.begin(), params.end());
   return cmd;
@@ -56,6 +60,28 @@
   EXPECT_EQ("probe", val);
 }
 
+TEST(CrosConfigTest, GetAbsPath) {
+  std::string val;
+
+  bool success = base::GetAppOutput(
+      GetCrosConfigCommand({"/audio/main", "cras-config-dir"}), &val);
+  EXPECT_TRUE(success);
+  EXPECT_EQ("another", val);
+
+  success = base::GetAppOutput(
+      GetCrosConfigCommand({"--abspath", "/audio/main", "cras-config-dir"}),
+      &val);
+  EXPECT_TRUE(success);
+  EXPECT_EQ("/etc/cras/another", val);
+
+  // We are not allowed to request an absolute path on something that is not
+  // a PropFile.
+  success = base::GetAppOutput(
+      GetCrosConfigCommand({"--abspath", "/", "wallpaper"}), &val);
+  EXPECT_FALSE(success);
+  EXPECT_EQ("", val);
+}
+
 int main(int argc, char** argv) {
   int status = system("exec ./chromeos-config-test-setup.sh");
   if (status != 0)
diff --git a/chromeos-config/cros_config_migration_test.sh b/chromeos-config/cros_config_migration_test.sh
index f857e11..421d781 100755
--- a/chromeos-config/cros_config_migration_test.sh
+++ b/chromeos-config/cros_config_migration_test.sh
@@ -1,20 +1,20 @@
 #!/bin/sh
-echo "cras-config-dir: $(cros_config --abspath /audio/main cras-config-dir)"
-echo "ucm-suffix: $(cros_config /audio/main ucm-suffix)"
-echo "arc product: $(cros_config /arc/build-properties product)"
-echo "arc oem: $(cros_config /arc/build-properties oem)"
-echo "arc device: $(cros_config /arc/build-properties device)"
-echo "arc marketing-name: $(cros_config /arc/build-properties marketing-name)"
-echo "arc metrics-tag: $(cros_config /arc/build-properties metrics-tag)"
-echo "power charging-ports: $(cros_config /power charging-ports)"
-echo "power keyboard-backlight-no-als-brightness: $(cros_config /power keyboard-backlight-no-als-brightness)"
-echo "power low-battery-shutdown-percent: $(cros_config /power low-battery-shutdown-percent)"
-echo "power power-supply-full-factor: $(cros_config /power power-supply-full-factor)"
-echo "power set-wifi-transmit-power-for-tablet-mode: $(cros_config /power set-wifi-transmit-power-for-tablet-mode)"
-echo "power suspend-to-idle: $(cros_config /power suspend-to-idle)"
-echo "brand-code: $(cros_config / brand-code)"
-echo "touch present: $(cros_config /touch present)"
-echo "touch probe-regex: $(cros_config /touch probe-regex)"
-echo "wallpaper: $(cros_config / wallpaper)"
-echo "powerd-prefs: $(cros_config / powerd-prefs)"
-echo "test-label: $(cros_config / test-label)"
+echo -e "cras-config-dir: $(cros_config --abspath /audio/main cras-config-dir)\n"
+echo -e "ucm-suffix: $(cros_config /audio/main ucm-suffix)\n"
+echo -e "arc product: $(cros_config /arc/build-properties product)\n"
+echo -e "arc oem: $(cros_config /arc/build-properties oem)\n"
+echo -e "arc device: $(cros_config /arc/build-properties device)\n"
+echo -e "arc marketing-name: $(cros_config /arc/build-properties marketing-name)\n"
+echo -e "arc metrics-tag: $(cros_config /arc/build-properties metrics-tag)\n"
+echo -e "power charging-ports: $(cros_config /power charging-ports)\n"
+echo -e "power keyboard-backlight-no-als-brightness: $(cros_config /power keyboard-backlight-no-als-brightness)\n"
+echo -e "power low-battery-shutdown-percent: $(cros_config /power low-battery-shutdown-percent)\n"
+echo -e "power power-supply-full-factor: $(cros_config /power power-supply-full-factor)\n"
+echo -e "power set-wifi-transmit-power-for-tablet-mode: $(cros_config /power set-wifi-transmit-power-for-tablet-mode)\n"
+echo -e "power suspend-to-idle: $(cros_config /power suspend-to-idle)\n"
+echo -e "brand-code: $(cros_config / brand-code)\n"
+echo -e "touch present: $(cros_config /touch present)\n"
+echo -e "touch probe-regex: $(cros_config /touch probe-regex)\n"
+echo -e "wallpaper: $(cros_config / wallpaper)\n"
+echo -e "powerd-prefs: $(cros_config / powerd-prefs)\n"
+echo -e "test-label: $(cros_config / test-label)\n"
diff --git a/chromeos-config/libcros_config/cros_config.cc b/chromeos-config/libcros_config/cros_config.cc
index d2aee4b..052abe5 100644
--- a/chromeos-config/libcros_config/cros_config.cc
+++ b/chromeos-config/libcros_config/cros_config.cc
@@ -5,6 +5,7 @@
 // Library to provide access to the Chrome OS master configuration
 
 #include "chromeos-config/libcros_config/cros_config.h"
+#include "chromeos-config/libcros_config/cros_config_fdt.h"
 #include "chromeos-config/libcros_config/cros_config_json.h"
 #include "chromeos-config/libcros_config/identity_x86.h"
 
@@ -24,9 +25,10 @@
 const char kWhitelabelTag[] = "/sys/firmware/vpd/ro/whitelabel_tag";
 const char kProductName[] = "/sys/devices/virtual/dmi/id/product_name";
 const char kProductSku[] = "/sys/devices/virtual/dmi/id/product_sku";
-const char kArmSkuId[] = "/proc/device-tree/firmware/coreboot/sku-id";
 const char kDeviceTreeCompatiblePath[] = "/proc/device-tree/compatible";
+const char kConfigDtbPath[] = "/usr/share/chromeos-config/config.dtb";
 const char kConfigJsonPath[] = "/usr/share/chromeos-config/config.json";
+const char kDtbExtension[] = ".dtb";
 }  // namespace
 
 namespace brillo {
@@ -73,22 +75,19 @@
 
 bool CrosConfig::InitForTestArm(const base::FilePath& filepath,
                                 const std::string& dt_compatible_name,
-                                int sku_id,
                                 const std::string& customization_id) {
-  base::FilePath dt_compatible_file, sku_id_file, vpd_file;
+  base::FilePath dt_compatible_file, vpd_file;
   CrosConfigIdentityArm identity;
   if (!identity.FakeVpd(customization_id, &vpd_file)) {
     CROS_CONFIG_LOG(ERROR) << "FakeVpd() failed";
     return false;
   }
-  if (!identity.Fake(dt_compatible_name, sku_id, &dt_compatible_file,
-                     &sku_id_file)) {
+  if (!identity.FakeDtCompatible(dt_compatible_name, &dt_compatible_file)) {
     CROS_CONFIG_LOG(ERROR) << "FakeDtCompatible() failed";
     return false;
   }
   return InitCrosConfig(filepath) &&
-         SelectConfigByIdentityArm(dt_compatible_file,
-                                   sku_id_file, vpd_file);
+         SelectConfigByIdentityArm(dt_compatible_file, vpd_file);
 }
 
 bool CrosConfig::InitModel() {
@@ -100,6 +99,9 @@
   bool init_config = false;
   if (base::PathExists(json_path)) {
     init_config = InitCrosConfig(json_path);
+  } else {
+    base::FilePath dtb_path(kConfigDtbPath);
+    init_config = InitCrosConfig(dtb_path);
   }
   if (!init_config) {
     return false;
@@ -115,10 +117,13 @@
     return SelectConfigByIdentityX86(product_name_file, product_sku_file,
                                      vpd_file, sku_id);
   } else {
+    if (sku_id != kDefaultSkuId) {
+      CROS_CONFIG_LOG(ERROR) << "Argument - test_sku_id is only for x86 or "
+                             << "x86_64 system.";
+      return false;
+    }
     base::FilePath dt_compatible_file(kDeviceTreeCompatiblePath);
-    base::FilePath sku_id_file(kArmSkuId);
-    return SelectConfigByIdentityArm(dt_compatible_file, sku_id_file,
-                                     vpd_file, sku_id);
+    return SelectConfigByIdentityArm(dt_compatible_file, vpd_file);
   }
 }
 
@@ -141,6 +146,15 @@
   return cros_config_->GetString(path, prop, val_out);
 }
 
+bool CrosConfig::GetAbsPath(const std::string& path,
+                            const std::string& prop,
+                            std::string* val_out) {
+  if (!InitCheck()) {
+    return false;
+  }
+  return cros_config_->GetAbsPath(path, prop, val_out);
+}
+
 bool CrosConfig::SelectConfigByIdentityX86(
     const base::FilePath& product_name_file,
     const base::FilePath& product_sku_file,
@@ -177,31 +191,21 @@
 
 bool CrosConfig::SelectConfigByIdentityArm(
     const base::FilePath& dt_compatible_file,
-    const base::FilePath& sku_id_file,
-    const base::FilePath& vpd_file,
-    const int sku_id) {
+    const base::FilePath& vpd_file) {
   CROS_CONFIG_LOG(INFO) << ">>>>> Starting to read ARM identity";
   CrosConfigIdentityArm identity;
   if (!identity.ReadVpd(vpd_file)) {
     CROS_CONFIG_LOG(ERROR) << "Cannot read VPD identity";
     return false;
   }
-  if (!identity.ReadInfo(dt_compatible_file, sku_id_file)) {
-    CROS_CONFIG_LOG(ERROR) << "Cannot read device-tree compatible and "
-                           << "sku-id identities";
+  if (!identity.ReadDtCompatible(dt_compatible_file)) {
+    CROS_CONFIG_LOG(ERROR) << "Cannot read device-tree compatible identity";
     return false;
   }
-  if (sku_id != kDefaultSkuId) {
-    identity.SetSkuId(sku_id);
-    CROS_CONFIG_LOG(INFO) << "Set sku_id to explicitly assigned value "
-                          << sku_id;
-  }
   if (!cros_config_->SelectConfigByIdentityArm(identity)) {
-    CROS_CONFIG_LOG(ERROR) << "Cannot find config for "
-                           << "device-tree compatible string: "
-                           << identity.GetCompatibleDeviceString()
-                           << " SKU ID: " << identity.GetSkuId()
-                           << " VPD ID from " << vpd_file.MaybeAsASCII()
+    CROS_CONFIG_LOG(ERROR) << "Cannot find config for device-tree compatible "
+                           << "string " << identity.GetCompatibleDeviceString()
+                           << " with VPD ID from " << vpd_file.MaybeAsASCII()
                            << ": " << identity.GetVpdId();
     return false;
   }
@@ -220,7 +224,11 @@
     return false;
   }
 
-  cros_config_ = std::make_unique<CrosConfigJson>();
+  if (filepath.MatchesExtension(kDtbExtension)) {
+    cros_config_ = std::make_unique<CrosConfigFdt>();
+  } else {
+    cros_config_ = std::make_unique<CrosConfigJson>();
+  }
 
   cros_config_->ReadConfigFile(filepath);
 
diff --git a/chromeos-config/libcros_config/cros_config.h b/chromeos-config/libcros_config/cros_config.h
index 0e010e1..c52f733 100644
--- a/chromeos-config/libcros_config/cros_config.h
+++ b/chromeos-config/libcros_config/cros_config.h
@@ -69,12 +69,10 @@
   // based on the supplied ARM identifiers.
   // @filepath: Path to configuration .dtb|.json file.
   // @dt_compatible_name: ARM device-tree compatible name string.
-  // @sku_id: SKU ID as returned by 'mosys platform sku'.
   // @customization_id: VPD customization ID
   // @return true if OK, false on error.
   bool InitForTestArm(const base::FilePath& filepath,
                       const std::string& dt_compatible_name,
-                      int sku_id,
                       const std::string& customization_id);
 
   // Internal function to obtain a property value and return a list of log
@@ -94,6 +92,11 @@
                  const std::string& prop,
                  std::string* val_out) override;
 
+  // CrosConfigInterface:
+  bool GetAbsPath(const std::string& path,
+                  const std::string& prop,
+                  std::string* val_out) override;
+
  private:
   // Common initialization function based on x86 identity.
   // @product_name_file: File containing the product name.
@@ -112,17 +115,11 @@
 
   // Common initialization function based on ARM identity.
   // @dt_compatible_file: ARM based device-tree compatible file
-  // @sku_id_file: File containing the SKU interger.
   // @vpd_file: File containing the customization_id from VPD. Typically this
   //     is '/sys/firmware/vpd/ro/customization_id'.
-  // @sku_id:If value is kDefaultSkuId then identity will get sku_id from
-  //     FDT which is the same with `mosys platform id`. Or this value
-  //     will be set into identity to replace the default one from FDT.
   //   results for every query
   bool SelectConfigByIdentityArm(const base::FilePath& dt_compatible_file,
-                                 const base::FilePath& sku_id_file,
-                                 const base::FilePath& vpd_file,
-                                 const int sku_id = kDefaultSkuId);
+                                 const base::FilePath& vpd_file);
 
   // Creates the appropriate instance of CrosConfigImpl based on the underlying
   // file type (.dtb|.json).
diff --git a/chromeos-config/libcros_config/cros_config_fdt.cc b/chromeos-config/libcros_config/cros_config_fdt.cc
new file mode 100644
index 0000000..c0956e8
--- /dev/null
+++ b/chromeos-config/libcros_config/cros_config_fdt.cc
@@ -0,0 +1,537 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Provide access to the Chrome OS master configuration from device tree
+
+#include "chromeos-config/libcros_config/cros_config_fdt.h"
+
+// TODO(sjg@chromium.org): See if this can be accepted upstream.
+extern "C" {
+#include <libfdt.h>
+};
+
+#include <stdlib.h>
+
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/strings/string_split.h>
+#include <base/strings/string_util.h>
+
+namespace {
+const char kTargetDirsPath[] = "/chromeos/schema/target-dirs";
+const char kSchemaPath[] = "/chromeos/schema";
+const char kPhandleProperties[] = "phandle-properties";
+}  // namespace
+
+namespace brillo {
+
+ConfigNode::ConfigNode() : valid_(false), node_offset_(-1) {}
+
+ConfigNode::ConfigNode(int offset) {
+  valid_ = true;
+  node_offset_ = offset;
+}
+
+int ConfigNode::GetOffset() const {
+  if (!valid_) {
+    return -1;
+  }
+  return node_offset_;
+}
+
+bool ConfigNode::operator==(const ConfigNode& other) const {
+  if (valid_ != other.valid_) {
+    return false;
+  }
+  return node_offset_ == other.node_offset_;
+}
+
+CrosConfigFdt::CrosConfigFdt() {}
+
+CrosConfigFdt::~CrosConfigFdt() {}
+
+std::string CrosConfigFdt::GetFullPath(const ConfigNode& node) {
+  const void* blob = blob_.c_str();
+  char buf[256];
+  int err;
+
+  err = fdt_get_path(blob, node.GetOffset(), buf, sizeof(buf));
+  if (err) {
+    CROS_CONFIG_LOG(WARNING) << "Cannot get full path: " << fdt_strerror(err);
+    return "unknown";
+  }
+
+  return std::string(buf);
+}
+
+ConfigNode CrosConfigFdt::GetPathNode(const ConfigNode& base_node,
+                                      const std::string& path) {
+  const void* blob = blob_.c_str();
+  auto parts = base::SplitString(path.substr(1), "/", base::KEEP_WHITESPACE,
+                                 base::SPLIT_WANT_ALL);
+  int node = base_node.GetOffset();
+  for (auto part : parts) {
+    node = fdt_subnode_offset(blob, node, part.c_str());
+    if (node < 0) {
+      break;
+    }
+  }
+  return ConfigNode(node);
+}
+
+bool CrosConfigFdt::LookupPhandle(const ConfigNode& node,
+                                  const std::string& prop_name,
+                                  ConfigNode* node_out) {
+  const void* blob = blob_.c_str();
+  int len;
+  const fdt32_t* ptr = static_cast<const fdt32_t*>(
+      fdt_getprop(blob, node.GetOffset(), prop_name.c_str(), &len));
+
+  // We probably don't need all these checks since validation will ensure that
+  // the config is correct. But this is a critical tool and we want to avoid
+  // crashes in any situation.
+  *node_out = ConfigNode();
+  if (!ptr) {
+    return false;
+  }
+  if (len != sizeof(fdt32_t)) {
+    CROS_CONFIG_LOG(ERROR) << prop_name << " phandle for model " << model_name_
+                           << " is of size " << len << " but should be "
+                           << sizeof(fdt32_t);
+    return false;
+  }
+  int phandle = fdt32_to_cpu(*ptr);
+  int target_node = fdt_node_offset_by_phandle(blob, phandle);
+  if (target_node < 0) {
+    CROS_CONFIG_LOG(ERROR) << prop_name << "lookup for model " << model_name_
+                           << " failed: " << fdt_strerror(target_node);
+    return false;
+  }
+  *node_out = ConfigNode(target_node);
+  return true;
+}
+
+int CrosConfigFdt::FindIDsInMap(int node,
+                                const std::string& find_name,
+                                int find_sku_id,
+                                std::string* platform_name_out) {
+  const void* blob = blob_.c_str();
+  VLOG(1) << "Trying " << fdt_get_name(blob, node, NULL);
+  const char* smbios_name = static_cast<const char*>(
+      fdt_getprop(blob, node, "smbios-name-match", NULL));
+  if (smbios_name &&
+      (find_name.empty() || strcmp(smbios_name, find_name.c_str()))) {
+    CROS_CONFIG_LOG(INFO) << "SMBIOS name " << smbios_name << " does not match "
+                          << find_name;
+    return 0;
+  }
+
+  // If we have a single SKU, deal with that first
+  int len = 0;
+  const fdt32_t* data =
+      (const fdt32_t*)fdt_getprop(blob, node, "single-sku", &len);
+  int found_phandle = 0;
+  if (data) {
+    if (len != sizeof(fdt32_t)) {
+      CROS_CONFIG_LOG(ERROR) << "single-sku: Invalid length " << len;
+      return -1;
+    }
+    found_phandle = fdt32_to_cpu(*data);
+    CROS_CONFIG_LOG(INFO) << "Single SKU match";
+  } else {
+    // Locate the map and make sure it is a multiple of 2 cells (first is SKU
+    // ID, second is phandle).
+    const fdt32_t *data, *end, *ptr;
+    data = static_cast<const fdt32_t*>(
+        fdt_getprop(blob, node, "simple-sku-map", &len));
+    if (!data) {
+      CROS_CONFIG_LOG(ERROR)
+          << "Cannot find simple-sku-map: " << fdt_strerror(node);
+      return -1;
+    }
+    if (len % (sizeof(fdt32_t) * 2)) {
+      // Validation of configuration should catch this, so this should never
+      // happen. But we don't want to crash.
+      CROS_CONFIG_LOG(ERROR)
+          << "single-sku-map: " << fdt_get_name(blob, node, NULL)
+          << " invalid length " << len;
+      return -1;
+    }
+
+    // Search for the SKU ID in the list.
+    for (end = data + len / sizeof(fdt32_t), ptr = data; ptr < end; ptr += 2) {
+      int sku_id = fdt32_to_cpu(ptr[0]);
+      int phandle = fdt32_to_cpu(ptr[1]);
+
+      if (sku_id == find_sku_id) {
+        found_phandle = phandle;
+        break;
+      }
+    }
+    if (!found_phandle) {
+      VLOG(1) << "SKU ID " << find_sku_id << " not found in mapping";
+      return 0;
+    }
+    CROS_CONFIG_LOG(INFO) << "Simple SKU map match ";
+  }
+
+  const char* pname =
+      static_cast<const char*>(fdt_getprop(blob, node, "platform-name", NULL));
+  if (pname)
+    *platform_name_out = pname;
+  else
+    *platform_name_out = "unknown";
+  CROS_CONFIG_LOG(INFO) << "Platform name " << *platform_name_out;
+
+  return found_phandle;
+}
+
+int CrosConfigFdt::FindIDsInAllMaps(int mapping_node,
+                                    const std::string& find_name,
+                                    int find_sku_id,
+                                    std::string* platform_name_out) {
+  const void* blob = blob_.c_str();
+  int subnode;
+
+  fdt_for_each_subnode(subnode, blob, mapping_node) {
+    int phandle =
+        FindIDsInMap(subnode, find_name, find_sku_id, platform_name_out);
+    if (phandle < 0) {
+      return -1;
+    } else if (phandle > 0) {
+      return phandle;
+    }
+  }
+  return 0;
+}
+
+int CrosConfigFdt::FollowPhandle(int phandle, int* target_out) {
+  const void* blob = blob_.c_str();
+
+  // Follow the phandle to the target
+  int node = fdt_node_offset_by_phandle(blob, phandle);
+  if (node < 0) {
+    CROS_CONFIG_LOG(ERROR) << "Cannot find phandle for sku ID: "
+                           << fdt_strerror(node);
+    return -1;
+  }
+
+  // Figure out whether the target is a model or a sub-model
+  int parent = fdt_parent_offset(blob, node);
+  if (parent < 0) {
+    CROS_CONFIG_LOG(ERROR) << "Cannot find parent of phandle target: "
+                           << fdt_strerror(node);
+    return -1;
+  }
+  const char* parent_name = fdt_get_name(blob, parent, NULL);
+  int model_node;
+  if (!strcmp(parent_name, "submodels")) {
+    model_node = fdt_parent_offset(blob, parent);
+    if (model_node < 0) {
+      CROS_CONFIG_LOG(ERROR)
+          << "Cannot find sub-model parent: " << fdt_strerror(node);
+      return -1;
+    }
+  } else if (!strcmp(parent_name, "models")) {
+    model_node = node;
+  } else {
+    CROS_CONFIG_LOG(ERROR) << "Phandle target parent " << parent_name
+                           << " is invalid";
+    return -1;
+  }
+  *target_out = node;
+
+  return model_node;
+}
+
+bool CrosConfigFdt::SelectConfigByIdentityArm(
+    const CrosConfigIdentityArm& identity) {
+  CROS_CONFIG_LOG(ERROR) << "ARM is not supported for the FDT impl.";
+  return false;
+}
+
+bool CrosConfigFdt::SelectConfigByIdentityX86(
+    const CrosConfigIdentityX86& identity) {
+  const std::string& find_name = identity.GetName();
+  int find_sku_id = identity.GetSkuId();
+  const std::string& find_whitelabel_name = identity.GetVpdId();
+  const void* blob = blob_.c_str();
+  CROS_CONFIG_LOG(INFO) << "Looking up name " << find_name << ", SKU ID "
+                        << find_sku_id;
+
+  int mapping_node = fdt_path_offset(blob, "/chromeos/family/mapping");
+  if (mapping_node < 0) {
+    CROS_CONFIG_LOG(ERROR) << "Cannot find mapping node: "
+                           << fdt_strerror(mapping_node);
+    return false;
+  }
+
+  std::string platform_name;
+  int phandle =
+      FindIDsInAllMaps(mapping_node, find_name, find_sku_id, &platform_name);
+  if (phandle <= 0) {
+    return false;
+  }
+  int target;
+  int model_offset = FollowPhandle(phandle, &target);
+  if (model_offset < 0) {
+    return false;
+  }
+
+  //  We found the model node, so set up the data
+  platform_name_ = platform_name;
+  model_node_ = ConfigNode(model_offset);
+  model_name_ = fdt_get_name(blob, model_offset, NULL);
+  if (target != model_offset) {
+    submodel_node_ = ConfigNode(target);
+    submodel_name_ = fdt_get_name(blob, target, NULL);
+  } else {
+    submodel_node_ = ConfigNode();
+    submodel_name_ = "";
+  }
+
+  // If this is a whitelabel model, use the tag.
+  int firmware_node = fdt_subnode_offset(blob, model_offset, "firmware");
+  if (firmware_node >= 0) {
+    if (fdt_getprop(blob, firmware_node, "sig-id-in-customization-id", NULL)) {
+      int models_node = fdt_path_offset(blob, "/chromeos/models");
+      int wl_model =
+          fdt_subnode_offset(blob, models_node, find_whitelabel_name.c_str());
+      if (wl_model >= 0) {
+        whitelabel_node_ = model_node_;
+        model_node_ = ConfigNode(wl_model);
+      } else {
+        CROS_CONFIG_LOG(ERROR)
+            << "Cannot find whitelabel model " << find_whitelabel_name
+            << ": using " << model_name_ << ": " << fdt_strerror(wl_model);
+      }
+    }
+  }
+  int wl_tags_node = fdt_subnode_offset(blob, model_offset, "whitelabels");
+  if (wl_tags_node >= 0) {
+    int wl_tag =
+        fdt_subnode_offset(blob, wl_tags_node, find_whitelabel_name.c_str());
+    if (wl_tag >= 0) {
+      whitelabel_tag_node_ = ConfigNode(wl_tag);
+    } else {
+      CROS_CONFIG_LOG(ERROR)
+          << "Cannot find whitelabel tag " << find_whitelabel_name << ": using "
+          << model_name_ << ": " << fdt_strerror(wl_tag);
+    }
+  }
+
+  // See if there is a whitelabel config for this model.
+  if (!whitelabel_node_.IsValid()) {
+    LookupPhandle(model_node_, "whitelabel", &whitelabel_node_);
+  }
+  ConfigNode next_node;
+  default_nodes_.clear();
+  for (ConfigNode node = model_node_;
+       LookupPhandle(node, "default", &next_node); node = next_node) {
+    if (std::find(default_nodes_.begin(), default_nodes_.end(), next_node) !=
+        default_nodes_.end()) {
+      CROS_CONFIG_LOG(ERROR) << "Circular default at " << GetFullPath(node);
+      return false;
+    }
+    default_nodes_.push_back(next_node);
+  }
+
+  CROS_CONFIG_LOG(INFO) << "Using master configuration for model "
+                        << model_name_ << ", submodel "
+                        << (submodel_name_.empty() ? "(none)" : submodel_name_);
+  if (whitelabel_node_.IsValid()) {
+    CROS_CONFIG_LOG(INFO) << "Whitelabel of  " << GetFullPath(whitelabel_node_);
+  } else if (whitelabel_tag_node_.IsValid()) {
+    CROS_CONFIG_LOG(INFO) << "Whitelabel tag "
+                          << GetFullPath(whitelabel_tag_node_);
+  }
+  inited_ = true;
+
+  return true;
+}
+
+int CrosConfigFdt::GetProp(const ConfigNode& node,
+                           std::string name,
+                           std::string* value_out) {
+  const void* blob = blob_.c_str();
+  int len;
+  const char* value = static_cast<const char*>(
+      fdt_getprop(blob, node.GetOffset(), name.c_str(), &len));
+  if (value) {
+    *value_out = value;
+    len--;
+  }
+  return len;
+}
+
+bool CrosConfigFdt::ReadConfigFile(const base::FilePath& filepath) {
+  if (!base::ReadFileToString(filepath, &blob_)) {
+    CROS_CONFIG_LOG(ERROR) << "Could not read file " << filepath.MaybeAsASCII();
+    return false;
+  }
+  const void* blob = blob_.c_str();
+  int ret = fdt_check_header(blob);
+  if (ret) {
+    CROS_CONFIG_LOG(ERROR) << "Config file " << filepath.MaybeAsASCII()
+                           << " is invalid: " << fdt_strerror(ret);
+    return false;
+  }
+
+  int target_dirs_offset = fdt_path_offset(blob, kTargetDirsPath);
+  if (target_dirs_offset >= 0) {
+    for (int poffset = fdt_first_property_offset(blob, target_dirs_offset);
+         poffset >= 0; poffset = fdt_next_property_offset(blob, poffset)) {
+      int len;
+      const struct fdt_property* prop =
+          fdt_get_property_by_offset(blob, poffset, &len);
+      const char* name = fdt_string(blob, fdt32_to_cpu(prop->nameoff));
+      target_dirs_[name] = prop->data;
+    }
+  } else if (target_dirs_offset < 0) {
+    CROS_CONFIG_LOG(WARNING) << "Cannot find " << kTargetDirsPath
+                             << " node: " << fdt_strerror(target_dirs_offset);
+  }
+  int schema_offset = fdt_path_offset(blob, kSchemaPath);
+  if (schema_offset >= 0) {
+    int len;
+    const char* prop = static_cast<const char*>(
+        fdt_getprop(blob, schema_offset, kPhandleProperties, &len));
+    if (prop) {
+      const char* end = prop + len;
+      for (const char* ptr = prop; ptr < end; ptr += strlen(ptr) + 1) {
+        phandle_props_.push_back(ptr);
+      }
+    } else {
+      CROS_CONFIG_LOG(WARNING) << "Cannot find property " << kPhandleProperties
+                               << " node: " << fdt_strerror(len);
+    }
+  } else if (schema_offset < 0) {
+    CROS_CONFIG_LOG(WARNING) << "Cannot find " << kSchemaPath
+                             << " node: " << fdt_strerror(schema_offset);
+  }
+  return true;
+}
+
+bool CrosConfigFdt::GetStringByNode(const ConfigNode& base_node,
+                                    const std::string& path,
+                                    const std::string& prop,
+                                    std::string* val_out,
+                                    std::vector<std::string>* log_msgs_out) {
+  ConfigNode subnode = GetPathNode(base_node, path);
+  ConfigNode wl_subnode;
+  if (whitelabel_node_.IsValid()) {
+    wl_subnode = GetPathNode(whitelabel_node_, path);
+    if (!subnode.IsValid() && wl_subnode.IsValid()) {
+      CROS_CONFIG_LOG(INFO)
+          << "The path " << GetFullPath(base_node) << path
+          << " does not exist. Falling back to whitelabel path";
+      subnode = wl_subnode;
+    }
+  }
+  if (!subnode.IsValid()) {
+    log_msgs_out->push_back("The path " + GetFullPath(base_node) + path +
+                            " does not exist.");
+    return false;
+  }
+
+  std::string value;
+  int len = GetProp(subnode, prop, &value);
+  if (len < 0 && wl_subnode.IsValid()) {
+    len = GetProp(wl_subnode, prop, &value);
+    CROS_CONFIG_LOG(INFO) << "The property " << prop
+                          << " does not exist. Falling back to "
+                          << "whitelabel property";
+  }
+  if (len < 0) {
+    ConfigNode target_node;
+    for (int i = 0; i < phandle_props_.size(); i++) {
+      LookupPhandle(subnode, phandle_props_[i], &target_node);
+      if (target_node.IsValid()) {
+        len = GetProp(target_node, prop, &value);
+        if (len < 0) {
+          CROS_CONFIG_LOG(INFO)
+              << "Followed " << phandle_props_[i] << " phandle";
+          break;
+        }
+      }
+    }
+  }
+
+  if (len < 0) {
+    log_msgs_out->push_back("Cannot get path " + path + " property " + prop +
+                            ": " + "full path " + GetFullPath(subnode) + ": " +
+                            fdt_strerror(len));
+    return false;
+  }
+
+  // We must have a normally terminated string. This guards against a string
+  // list being used, or perhaps a property that does not contain a valid
+  // string at all.
+  if (!len || value.size() != len) {
+    log_msgs_out->push_back("String at path " + path + " property " + prop +
+                            " is invalid");
+    return false;
+  }
+
+  val_out->assign(value);
+
+  return true;
+}
+
+bool CrosConfigFdt::GetString(const std::string& path,
+                              const std::string& prop,
+                              std::string* val_out,
+                              std::vector<std::string>* log_msgs_out) {
+  if (!InitCheck()) {
+    return false;
+  }
+
+  if (!model_node_.IsValid()) {
+    log_msgs_out->push_back("Please specify the model to access.");
+    return false;
+  }
+
+  if (path.empty()) {
+    log_msgs_out->push_back("Path must be specified");
+    return false;
+  }
+
+  if (path[0] != '/') {
+    log_msgs_out->push_back("Path must start with / specifying the root node");
+    return false;
+  }
+
+  if (whitelabel_tag_node_.IsValid()) {
+    if (path == "/" && GetStringByNode(whitelabel_tag_node_, "/", prop, val_out,
+                                       log_msgs_out)) {
+      return true;
+    }
+    // TODO(sjg@chromium.org): We are considering moving the key-id to the root
+    // of the model schema. If we do, we can drop this special case.
+    if (path == "/firmware" && prop == "key-id" &&
+        GetStringByNode(whitelabel_tag_node_, "/", prop, val_out,
+                        log_msgs_out)) {
+      return true;
+    }
+  }
+  if (!GetStringByNode(model_node_, path, prop, val_out, log_msgs_out)) {
+    if (submodel_node_.IsValid() &&
+        GetStringByNode(submodel_node_, path, prop, val_out, log_msgs_out)) {
+      return true;
+    }
+    for (ConfigNode node : default_nodes_) {
+      if (GetStringByNode(node, path, prop, val_out, log_msgs_out))
+        return true;
+    }
+    return false;
+  }
+  return true;
+}
+
+}  // namespace brillo
diff --git a/chromeos-config/libcros_config/cros_config_fdt.h b/chromeos-config/libcros_config/cros_config_fdt.h
new file mode 100644
index 0000000..03c0c7c
--- /dev/null
+++ b/chromeos-config/libcros_config/cros_config_fdt.h
@@ -0,0 +1,165 @@
+// Copyright 2016 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Library to provide access to the Chrome OS master configuration
+
+#ifndef CHROMEOS_CONFIG_LIBCROS_CONFIG_CROS_CONFIG_FDT_H_
+#define CHROMEOS_CONFIG_LIBCROS_CONFIG_CROS_CONFIG_FDT_H_
+
+#include "chromeos-config/libcros_config/cros_config_impl.h"
+
+#include <string>
+#include <vector>
+
+namespace brillo {
+
+// References a node in the configuration.
+// This allows us to reference a node whether it is device tree or JSON.
+class ConfigNode {
+ public:
+  ConfigNode();
+  // Constructor which uses a device-tree offset
+  explicit ConfigNode(int offset);
+
+  // @return true if this node reference is valid (points to an actual node)
+  bool IsValid() const { return valid_; }
+
+  // @return offset of the device-tree node, or -1 if not valid
+  int GetOffset() const;
+
+  // Test equality for two ConfigNode objectcs
+  bool operator==(const ConfigNode& other) const;
+
+ private:
+  bool valid_;       // true if we have a valid node reference
+  int node_offset_;  // Device-tree node offset
+};
+
+class CrosConfigFdt : public CrosConfigImpl {
+ public:
+  CrosConfigFdt();
+  ~CrosConfigFdt() override;
+
+  // CrosConfig:
+  bool GetString(const std::string& path,
+                 const std::string& prop,
+                 std::string* val_out,
+                 std::vector<std::string>* log_msgs_out) override;
+
+  // CrosConfigImpl:
+  bool ReadConfigFile(const base::FilePath& filepath) override;
+  bool SelectConfigByIdentityX86(
+      const CrosConfigIdentityX86& identity) override;
+  bool SelectConfigByIdentityArm(
+      const CrosConfigIdentityArm& identity) override;
+
+ private:
+  // Internal function to obtain a property value based on a node
+  // This looks up a property for a path, relative to a given base node.
+  // @base_node: base node for the search.
+  // @path: Path to locate (relative to @base). Must start with "/".
+  // @prop: Property name to look up
+  // @val_out: returns the string value found, if any
+  // @log_msgs_out: returns a list of error messages if this function fails
+  // @return true if found, false if not found
+  bool GetStringByNode(const ConfigNode& base_node,
+                       const std::string& path,
+                       const std::string& prop,
+                       std::string* val_out,
+                       std::vector<std::string>* log_msgs_out);
+
+  // Read a property from a node
+  // @node: Node to read from
+  // @name: Property name to reset
+  // @value_out: Returns value read from property, if no error
+  // @return length of property value, or -ve on error
+  int GetProp(const ConfigNode& node, std::string name, std::string* value_out);
+
+  // Look up a phandle in a node.
+  // Looks up a phandle with the given property name in the given node.
+  // @node: Node to look in.
+  // @prop_name: Name of property to look up.
+  // @node_out: Returns the node the phandle points to, if found.
+  // @return true if found, false if not.
+  bool LookupPhandle(const ConfigNode& node,
+                     const std::string& prop_name,
+                     ConfigNode* node_out);
+
+  // Obtain the full path for the node at a given node.
+  // @node: Node to check.
+  // @return path to node, or "unknown" if it's 256 characters or more (due to
+  // limited buffer space). This is much longer than any expected length so
+  // should not happen.
+  std::string GetFullPath(const ConfigNode& node);
+
+  // Obtain node of a given path, relative to the base node.
+  // @base_node: base node.
+  // @path: Path to locate (relative to @base). Must start with "/".
+  // @return node found, or negative value on error.
+  ConfigNode GetPathNode(const ConfigNode& base_node, const std::string& path);
+
+  // Check a single sku-map node for a match
+  // This searches the given sku-map node to see if it is a match for the given
+  // SKU ID.
+  // @node: 'sku-map' node to examine
+  // @find_sku_id: SKU ID to search for. This is not required (can be -1) for
+  // single-sku matching
+  // @find_name: Platform name to search for. Can be empty if the name does
+  // not need to be checked (no smbios-name-match property). This only works
+  // on x86 devices at present although it should be easily extensible to ARM.
+  // @platform_name_out: Returns platform name for this SKU, if found
+  // @return the phandle to a model or submodel node that was found (> 0), or 0
+  // if not found, or negative on error
+  int FindIDsInMap(int node,
+                   const std::string& find_name,
+                   int find_sku_id,
+                   std::string* platform_name_out);
+
+  // Check all sku-map nodes for a match
+  // This searches all the sku-map subnodes to see if one is a match for the
+  // given SKU ID.
+  // @mapping_node: 'mapping' node to examine
+  // @find_name: platform name to search for
+  // @find_sku_id: SKU ID to search for
+  // @platform_name_out: Returns platform name for this SKU, if found
+  // @return the phandle to a model or submodel node that was found (> 0), or 0
+  // if not found, or negative on error
+  int FindIDsInAllMaps(int mapping_node,
+                       const std::string& find_name,
+                       int find_sku_id,
+                       std::string* platform_name_out);
+
+  // Find the model node pointed to by a phandle
+  // Note that a SKU map can point to either a model node or a submodel node.
+  // In the latter case, this function still returns the model node, but the
+  // submodel node is available in @target_out.
+  // @phandle: Phandle to look up
+  // @target_out: Returns the target node of the phandle, which may be a model
+  // node or a submodel node
+  // @return model node for this phandle, or negative on error
+  int FollowPhandle(int phandle, int* target_out);
+
+  ConfigNode model_node_;       // Model's node
+  ConfigNode submodel_node_;    // Submodel's node
+  std::string model_name_;      // Name of current model
+  std::string submodel_name_;   // Name of current submodel
+  std::string platform_name_;   // Platform name associated with the SKU map
+  ConfigNode whitelabel_node_;  // Whitelabel model
+
+  // We support a special-case 'whitelabel' node which is inside a model. We
+  // check this first on any property reads, since it overrides the model
+  // itself.
+  ConfigNode whitelabel_tag_node_;
+  std::vector<std::string> phandle_props_;  // List of phandle properties
+  // Default modes to check when we cannot find the requested node or property
+  std::vector<ConfigNode> default_nodes_;
+
+  std::string blob_;  // Device tree binary blob
+
+  DISALLOW_COPY_AND_ASSIGN(CrosConfigFdt);
+};
+
+}  // namespace brillo
+
+#endif  // CHROMEOS_CONFIG_LIBCROS_CONFIG_CROS_CONFIG_FDT_H_
diff --git a/chromeos-config/libcros_config/cros_config_impl.cc b/chromeos-config/libcros_config/cros_config_impl.cc
index 994ac69..645fd35 100644
--- a/chromeos-config/libcros_config/cros_config_impl.cc
+++ b/chromeos-config/libcros_config/cros_config_impl.cc
@@ -41,6 +41,24 @@
   return true;
 }
 
+bool CrosConfigImpl::GetAbsPath(const std::string& path,
+                                const std::string& prop,
+                                std::string* val_out) {
+  std::string val;
+  if (!GetString(path, prop, &val)) {
+    return false;
+  }
+
+  if (target_dirs_.find(prop) == target_dirs_.end()) {
+    CROS_CONFIG_LOG(ERROR) << "Absolute path requested at path " << path
+                           << " property " << prop << ": not found";
+    return false;
+  }
+  *val_out = target_dirs_[prop] + "/" + val;
+
+  return true;
+}
+
 bool CrosConfigImpl::GetString(const std::string& path,
                                const std::string& prop,
                                std::string* val_out) {
diff --git a/chromeos-config/libcros_config/cros_config_impl.h b/chromeos-config/libcros_config/cros_config_impl.h
index 854613f..20f3b2b 100644
--- a/chromeos-config/libcros_config/cros_config_impl.h
+++ b/chromeos-config/libcros_config/cros_config_impl.h
@@ -36,6 +36,9 @@
   bool GetString(const std::string& path,
                  const std::string& prop,
                  std::string* val_out) override;
+  bool GetAbsPath(const std::string& path,
+                  const std::string& prop,
+                  std::string* val_out) override;
 
   // Read the config into our internal structures
   // @filepath: path to configuration file (e.g. .dtb).
@@ -62,6 +65,9 @@
 
   bool inited_;  // true if the class is ready for use (Init*ed)
 
+  // List of target directories used to obtain absolute paths
+  std::map<std::string, std::string> target_dirs_;
+
  private:
   DISALLOW_COPY_AND_ASSIGN(CrosConfigImpl);
 };
diff --git a/chromeos-config/libcros_config/cros_config_interface.h b/chromeos-config/libcros_config/cros_config_interface.h
index 456dfdb..21a7c2f 100644
--- a/chromeos-config/libcros_config/cros_config_interface.h
+++ b/chromeos-config/libcros_config/cros_config_interface.h
@@ -36,6 +36,14 @@
                          const std::string& prop,
                          std::string* val_out) = 0;
 
+  // Obtain a config property as an absolute path.
+  // This is similar to GetString except that the schema element referred to
+  // must be a PropFile element. It prepends the target directory to the value
+  // returned, producing an absolute path for use at run-time.
+  virtual bool GetAbsPath(const std::string& path,
+                          const std::string& prop,
+                          std::string* val_out) = 0;
+
   // Return true iff library debug logging is enabled.
   // Currently this checks for a non-empty CROS_CONFIG_DEBUG environment
   // variable.
diff --git a/chromeos-config/libcros_config/cros_config_json.cc b/chromeos-config/libcros_config/cros_config_json.cc
index 5a65f19..70f8d07 100644
--- a/chromeos-config/libcros_config/cros_config_json.cc
+++ b/chromeos-config/libcros_config/cros_config_json.cc
@@ -25,7 +25,7 @@
 
 namespace brillo {
 
-CrosConfigJson::CrosConfigJson() : config_dict_(nullptr) {}
+CrosConfigJson::CrosConfigJson() : model_dict_(nullptr) {}
 
 CrosConfigJson::~CrosConfigJson() {}
 
@@ -44,53 +44,59 @@
   if (json_config_->GetAsDictionary(&root_dict)) {
     const base::DictionaryValue* chromeos = nullptr;
     if (root_dict->GetDictionary("chromeos", &chromeos)) {
-      const base::ListValue* configs_list = nullptr;
-      if (chromeos->GetList("configs", &configs_list)) {
-        size_t num_configs = configs_list->GetSize();
-        for (size_t i = 0; i < num_configs; ++i) {
-          const base::DictionaryValue* config_dict = nullptr;
-          if (configs_list->GetDictionary(i, &config_dict)) {
+      const base::ListValue* models_list = nullptr;
+      if (chromeos->GetList("configs", &models_list) ||
+          chromeos->GetList("models", &models_list)) {
+        size_t num_models = models_list->GetSize();
+        for (size_t i = 0; i < num_models; ++i) {
+          const base::DictionaryValue* model_dict = nullptr;
+          if (models_list->GetDictionary(i, &model_dict)) {
             const base::DictionaryValue* identity_dict = nullptr;
-            if (config_dict->GetDictionary("identity", &identity_dict)) {
-              int find_sku_id = -1;
+            if (model_dict->GetDictionary("identity", &identity_dict)) {
               bool platform_specific_match = false;
               if (identity_x86) {
-                find_sku_id = identity_x86->GetSkuId();
-
                 const std::string& find_name = identity_x86->GetName();
+                int find_sku_id = identity_x86->GetSkuId();
+                int current_sku_id;
+                bool sku_match = true;
+                if (find_sku_id > -1 &&
+                    identity_dict->GetInteger("sku-id", &current_sku_id)) {
+                  sku_match = current_sku_id == find_sku_id;
+                }
+
+                bool name_match = true;
                 std::string current_name;
                 if (identity_dict->GetString("smbios-name-match",
-                                             &current_name)) {
-                  platform_specific_match = current_name == find_name;
+                                             &current_name) &&
+                    !find_name.empty()) {
+                  name_match = current_name == find_name;
                 }
+                platform_specific_match = sku_match && name_match;
               } else if (identity_arm) {
-                find_sku_id = identity_arm->GetSkuId();
-
+                bool dt_compatible_match = false;
                 std::string current_dt_compatible_match;
                 if (identity_dict->GetString("device-tree-compatible-match",
                                              &current_dt_compatible_match)) {
-                  platform_specific_match =
+                  dt_compatible_match =
                       identity_arm->IsCompatible(current_dt_compatible_match);
                 }
-              }
-              bool sku_match = true;
-              int current_sku_id;
-              if (find_sku_id > -1 &&
-                  identity_dict->GetInteger("sku-id", &current_sku_id)) {
-                sku_match = current_sku_id == find_sku_id;
+                platform_specific_match = dt_compatible_match;
               }
 
-              std::string current_vpd_tag = "";
-              identity_dict->GetString("whitelabel-tag", &current_vpd_tag);
-              if (current_vpd_tag.empty()) {
-                identity_dict->GetString("customization-id", &current_vpd_tag);
+              bool vpd_tag_match = true;
+              std::string current_vpd_tag;
+              if ((identity_dict->GetString("whitelabel-tag",
+                                           &current_vpd_tag) ||
+                  identity_dict->GetString("customization-id",
+                                           &current_vpd_tag)) &&
+                  !current_vpd_tag.empty()) {
+                // Currently, the find_whitelabel_name can be either the
+                // whitelabel-tag or the customization-id.
+                vpd_tag_match = current_vpd_tag == find_whitelabel_name;
               }
-              // Currently, the find_whitelabel_name can be either the
-              // whitelabel-tag or the customization-id.
-              bool vpd_tag_match = current_vpd_tag == find_whitelabel_name;
 
-              if (platform_specific_match && sku_match && vpd_tag_match) {
-                config_dict_ = config_dict;
+              if (platform_specific_match && vpd_tag_match) {
+                model_dict_ = model_dict;
                 break;
               }
             }
@@ -99,7 +105,7 @@
       }
     }
   }
-  if (!config_dict_) {
+  if (!model_dict_) {
     if (identity_arm) {
       CROS_CONFIG_LOG(ERROR)
           << "Failed to find config for device-tree compatible string: "
@@ -144,7 +150,7 @@
   }
 
   bool valid_path = true;
-  const base::DictionaryValue* attr_dict = config_dict_;
+  const base::DictionaryValue* attr_dict = model_dict_;
 
   if (path.length() > 1) {
     std::vector<std::string> path_tokens = base::SplitString(
@@ -158,28 +164,10 @@
     }
   }
 
-  if (valid_path) {
-    std::string value;
-    if (attr_dict->GetString(prop, &value)) {
-      val_out->assign(value);
-      return true;
-    }
-
-    int int_value;
-    if (attr_dict->GetInteger(prop, &int_value)) {
-      val_out->assign(std::to_string(int_value));
-      return true;
-    }
-
-    bool bool_value;
-    if (attr_dict->GetBoolean(prop, &bool_value)) {
-      if (bool_value) {
-        val_out->assign("true");
-      } else {
-        val_out->assign("false");
-      }
-      return true;
-    }
+  std::string value;
+  if (valid_path && attr_dict->GetString(prop, &value)) {
+    val_out->assign(value);
+    return true;
   }
   return false;
 }
@@ -199,6 +187,16 @@
     return false;
   }
 
+  // TODO(crbug.com/xxx): Figure out a way to represent this. For now it is
+  // hard-coded
+  target_dirs_["alsa-conf"] = "/usr/share/alsa/ucm";
+  target_dirs_["cras-config-dir"] = "/etc/cras";
+  target_dirs_["dptf-dv"] = "/etc/dptf";
+  target_dirs_["dsp-ini"] = "/etc/cras";
+  target_dirs_["hifi-conf"] = "/usr/share/alsa/ucm";
+  target_dirs_["topology-bin"] = "/lib/firmware";
+  target_dirs_["volume"] = "/etc/cras";
+
   return true;
 }
 
diff --git a/chromeos-config/libcros_config/cros_config_json.h b/chromeos-config/libcros_config/cros_config_json.h
index 7e5e72c..0384b4c 100644
--- a/chromeos-config/libcros_config/cros_config_json.h
+++ b/chromeos-config/libcros_config/cros_config_json.h
@@ -39,7 +39,7 @@
                               const CrosConfigIdentityX86* identity_x86);
   std::unique_ptr<const base::Value> json_config_;
   // Owned by json_config_
-  const base::DictionaryValue* config_dict_;  // Root of configs
+  const base::DictionaryValue* model_dict_;  // Model root
 
   DISALLOW_COPY_AND_ASSIGN(CrosConfigJson);
 };
diff --git a/chromeos-config/libcros_config/cros_config_test.cc b/chromeos-config/libcros_config/cros_config_test.cc
deleted file mode 100644
index 292e100..0000000
--- a/chromeos-config/libcros_config/cros_config_test.cc
+++ /dev/null
@@ -1,129 +0,0 @@
-// Copyright 2016 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/*
- * Tests for the CrosConfig library, which provides access to the Chrome OS
- * master configuration.
- */
-
-#include <stdlib.h>
-
-#include <base/files/file_path.h>
-#include <base/logging.h>
-#include <chromeos-config/libcros_config/cros_config.h>
-#include <gtest/gtest.h>
-
-#define TEST_FILE "test.json"
-#define TEST_FILE_ARM "test_arm.json"
-
-class CrosConfigTest : public testing::Test {
- protected:
-  void InitConfig(const std::string name = "Another",
-                  int sku_id = -1,
-                  std::string whitelabel_name = "") {
-    base::FilePath filepath(TEST_FILE);
-    ASSERT_TRUE(
-        cros_config_.InitForTestX86(filepath, name, sku_id, whitelabel_name));
-  }
-
-  void InitConfigArm(const std::string device_name = "google,some",
-                     int sku_id = -1,
-                     std::string whitelabel_name = "") {
-    base::FilePath filepath(TEST_FILE_ARM);
-    ASSERT_TRUE(
-        cros_config_.InitForTestArm(filepath, device_name,
-                                    sku_id, whitelabel_name));
-  }
-
-  brillo::CrosConfig cros_config_;
-};
-
-TEST_F(CrosConfigTest, CheckMissingFile) {
-  base::FilePath filepath("invalid-file");
-  ASSERT_FALSE(cros_config_.InitForTestX86(filepath, "Another", -1, ""));
-}
-
-TEST_F(CrosConfigTest, CheckUnknownModel) {
-  base::FilePath filepath(TEST_FILE);
-  ASSERT_FALSE(cros_config_.InitForTestX86(filepath, "no-model", -1, ""));
-}
-
-TEST_F(CrosConfigTest, Check111NoInit) {
-  std::string val;
-  ASSERT_FALSE(cros_config_.GetString("/", "wallpaper", &val));
-}
-
-TEST_F(CrosConfigTest, CheckWrongPath) {
-  InitConfig();
-  std::string val;
-  ASSERT_FALSE(cros_config_.GetString("/wibble", "wallpaper", &val));
-}
-
-TEST_F(CrosConfigTest, CheckBadString) {
-  InitConfig();
-  std::string val;
-  ASSERT_FALSE(cros_config_.GetString("/", "string-list", &val));
-}
-
-TEST_F(CrosConfigTest, CheckGoodStringRoot) {
-  InitConfig();
-  std::string val;
-  ASSERT_TRUE(cros_config_.GetString("/", "wallpaper", &val));
-  ASSERT_EQ("default", val);
-}
-
-TEST_F(CrosConfigTest, CheckGoodStringNonRoot) {
-  InitConfig();
-  std::string val;
-  ASSERT_TRUE(cros_config_.GetString("/touch", "present", &val));
-  ASSERT_EQ("probe", val);
-}
-
-TEST_F(CrosConfigTest, CheckEmptyPathError) {
-  InitConfig();
-  std::string val;
-  ASSERT_FALSE(cros_config_.GetString("", "wallpaper", &val));
-  ASSERT_EQ("", val);
-}
-
-TEST_F(CrosConfigTest, CheckPathWithoutSlashError) {
-  InitConfig();
-  std::string val;
-  ASSERT_FALSE(cros_config_.GetString("noslash", "wallpaper", &val));
-  ASSERT_EQ("", val);
-}
-
-TEST_F(CrosConfigTest, CheckUiPowerPosition) {
-  InitConfig("Some", 1);
-  std::string val;
-  ASSERT_TRUE(cros_config_.GetString("/ui/power-button", "edge", &val));
-  ASSERT_EQ("left", val);
-  ASSERT_TRUE(cros_config_.GetString("/ui/power-button", "position", &val));
-  ASSERT_EQ("0.3", val);
-}
-
-TEST_F(CrosConfigTest, CheckCameraCount) {
-  InitConfig("Some", 0);
-  std::string val;
-  ASSERT_TRUE(cros_config_.GetString("/camera", "count", &val));
-  ASSERT_EQ("1", val);
-}
-
-int main(int argc, char** argv) {
-  int status = system("exec ./chromeos-config-test-setup.sh");
-  if (status != 0)
-    return EXIT_FAILURE;
-
-  logging::LoggingSettings settings;
-  settings.logging_dest = logging::LOG_TO_FILE;
-  settings.log_file = "log.test";
-  settings.lock_log = logging::DONT_LOCK_LOG_FILE;
-  settings.delete_old = logging::DELETE_OLD_LOG_FILE;
-  logging::InitLogging(settings);
-  logging::SetMinLogLevel(-3);
-
-  testing::InitGoogleTest(&argc, argv);
-
-  return RUN_ALL_TESTS();
-}
diff --git a/chromeos-config/libcros_config/cros_config_unittest.cc b/chromeos-config/libcros_config/cros_config_unittest.cc
new file mode 100644
index 0000000..c94195c
--- /dev/null
+++ b/chromeos-config/libcros_config/cros_config_unittest.cc
@@ -0,0 +1,258 @@
+// Copyright 2016 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/*
+ * Tests for the CrosConfig library, which provides access to the Chrome OS
+ * master configuration.
+ */
+
+#include <stdlib.h>
+
+#include <base/files/file_path.h>
+#include <base/logging.h>
+#include <chromeos-config/libcros_config/cros_config.h>
+#include <gtest/gtest.h>
+
+#ifndef USE_JSON
+#define TEST_FILE "test.dtb"
+#define TEST_FILE_ARM ""  // No FDT support for ARM
+#else
+#define TEST_FILE "test.json"
+#define TEST_FILE_ARM "test_arm.json"
+#endif
+
+class CrosConfigTest : public testing::Test {
+ protected:
+  void InitConfig(const std::string name = "Another",
+                  int sku_id = -1,
+                  std::string whitelabel_name = "") {
+    base::FilePath filepath(TEST_FILE);
+    ASSERT_TRUE(
+        cros_config_.InitForTestX86(filepath, name, sku_id, whitelabel_name));
+  }
+
+  void InitConfigArm(const std::string device_name = "google,some",
+                     std::string whitelabel_name = "") {
+    base::FilePath filepath(TEST_FILE_ARM);
+    ASSERT_TRUE(
+        cros_config_.InitForTestArm(filepath, device_name, whitelabel_name));
+  }
+
+  brillo::CrosConfig cros_config_;
+};
+
+TEST_F(CrosConfigTest, CheckMissingFile) {
+  base::FilePath filepath("invalid-file");
+  ASSERT_FALSE(cros_config_.InitForTestX86(filepath, "Another", -1, ""));
+}
+
+TEST_F(CrosConfigTest, CheckUnknownModel) {
+  base::FilePath filepath(TEST_FILE);
+  ASSERT_FALSE(cros_config_.InitForTestX86(filepath, "no-model", -1, ""));
+}
+
+TEST_F(CrosConfigTest, Check111NoInit) {
+  std::string val;
+  ASSERT_FALSE(cros_config_.GetString("/", "wallpaper", &val));
+}
+
+TEST_F(CrosConfigTest, CheckWrongPath) {
+  InitConfig();
+  std::string val;
+  ASSERT_FALSE(cros_config_.GetString("/wibble", "wallpaper", &val));
+}
+
+TEST_F(CrosConfigTest, CheckBadString) {
+  InitConfig();
+  std::string val;
+  ASSERT_FALSE(cros_config_.GetString("/", "string-list", &val));
+}
+
+TEST_F(CrosConfigTest, CheckGoodStringRoot) {
+  InitConfig();
+  std::string val;
+  ASSERT_TRUE(cros_config_.GetString("/", "wallpaper", &val));
+  ASSERT_EQ("default", val);
+}
+
+TEST_F(CrosConfigTest, CheckGoodStringNonRoot) {
+  InitConfig();
+  std::string val;
+  ASSERT_TRUE(cros_config_.GetString("/touch", "present", &val));
+  ASSERT_EQ("probe", val);
+}
+
+TEST_F(CrosConfigTest, CheckEmptyPathError) {
+  InitConfig();
+  std::string val;
+  ASSERT_FALSE(cros_config_.GetString("", "wallpaper", &val));
+  ASSERT_EQ("", val);
+}
+
+TEST_F(CrosConfigTest, CheckPathWithoutSlashError) {
+  InitConfig();
+  std::string val;
+  ASSERT_FALSE(cros_config_.GetString("noslash", "wallpaper", &val));
+  ASSERT_EQ("", val);
+}
+
+TEST_F(CrosConfigTest, CheckAbsPath) {
+  InitConfig("Another");
+  std::string val;
+
+  ASSERT_TRUE(cros_config_.GetAbsPath("/audio/main", "cras-config-dir", &val));
+  ASSERT_EQ("/etc/cras/another", val);
+}
+
+#ifndef USE_JSON
+TEST_F(CrosConfigTest, CheckBadFile) {
+  base::FilePath filepath("test.dts");
+  ASSERT_FALSE(cros_config_.InitForTestX86(filepath, "Another", -1, ""));
+}
+
+TEST_F(CrosConfigTest, CheckBadStruct) {
+  base::FilePath filepath("test_bad_struct.dtb");
+  ASSERT_FALSE(cros_config_.InitForTestX86(filepath, "not_another", -1, ""));
+}
+
+TEST_F(CrosConfigTest, CheckSubmodel) {
+  InitConfig("Some", 0, "");
+  std::string val;
+  ASSERT_TRUE(cros_config_.GetString("/touch", "present", &val));
+  ASSERT_EQ("yes", val);
+
+  InitConfig("Some", 1, "");
+  ASSERT_TRUE(cros_config_.GetString("/touch", "present", &val));
+  ASSERT_EQ("no", val);
+
+  std::vector<std::string> log_msgs;
+  ASSERT_FALSE(cros_config_.GetString("/touch", "presents", &val, &log_msgs));
+  ASSERT_EQ(2, log_msgs.size());
+  ASSERT_EQ(
+      "Cannot get path /touch property presents: full path "
+      "/chromeos/models/some/touch: FDT_ERR_NOTFOUND",
+      log_msgs[0]);
+  ASSERT_EQ(
+      "Cannot get path /touch property presents: full path "
+      "/chromeos/models/some/submodels/notouch/touch: FDT_ERR_NOTFOUND",
+      log_msgs[1]);
+}
+
+TEST_F(CrosConfigTest, CheckFollowPhandle) {
+  InitConfig("Another");
+  std::string val;
+  ASSERT_TRUE(cros_config_.GetString("/audio/main", "card", &val));
+  ASSERT_EQ("a-card", val);
+}
+
+TEST_F(CrosConfigTest, CheckWhiteLabel) {
+  // Check values defined by whitelabel1.
+  InitConfig("Some", 8, "whitelabel1");
+  std::string val;
+  ASSERT_TRUE(cros_config_.GetString("/", "wallpaper", &val));
+  ASSERT_EQ("wallpaper-wl1", val);
+  ASSERT_TRUE(cros_config_.GetString("/firmware", "key-id", &val));
+  ASSERT_EQ("WHITELABEL1", val);
+  ASSERT_TRUE(cros_config_.GetString("/", "brand-code", &val));
+  ASSERT_EQ("WLBA", val);
+
+  // Check values defined by whitelabel2.
+  InitConfig("Some", 9, "whitelabel2");
+  ASSERT_TRUE(cros_config_.GetString("/", "wallpaper", &val));
+  ASSERT_EQ("wallpaper-wl2", val);
+  ASSERT_TRUE(cros_config_.GetString("/firmware", "key-id", &val));
+  ASSERT_EQ("WHITELABEL2", val);
+  ASSERT_TRUE(cros_config_.GetString("/", "brand-code", &val));
+  ASSERT_EQ("WLBB", val);
+}
+#else
+TEST_F(CrosConfigTest, CheckMultilineString) {
+  InitConfig("Some");
+  std::string val;
+  ASSERT_TRUE(cros_config_.GetString("/power", "charging-ports", &val));
+  ASSERT_EQ("CROS_USB_PD_CHARGER0 LEFT\nCROS_USB_PD_CHARGER1 RIGHT\n", val);
+}
+
+TEST_F(CrosConfigTest, CheckCustomizationId) {
+  // Assert a model can be looked up based on the customization-id value.
+  InitConfig("SomeCustomization", -1, "SomeCustomization");
+  std::string val;
+  ASSERT_TRUE(cros_config_.GetString("/", "name", &val));
+  ASSERT_EQ("some_customization", val);
+}
+
+TEST_F(CrosConfigTest, CheckEmptySkuCase) {
+  InitConfig("Another", 0);
+  std::string val;
+  ASSERT_TRUE(cros_config_.GetString("/", "name", &val));
+  ASSERT_EQ("another", val);
+}
+
+TEST_F(CrosConfigTest, CheckArmIdentityByDeviceName) {
+  InitConfigArm();
+  std::string val;
+  ASSERT_TRUE(cros_config_.GetString("/", "wallpaper", &val));
+  ASSERT_EQ("some-wallpaper", val);
+}
+
+TEST_F(CrosConfigTest, CheckArmIdentityByWhitelabel) {
+  InitConfigArm("google,whitelabel", "whitelabel1");
+  std::string val;
+  ASSERT_TRUE(cros_config_.GetString("/", "wallpaper", &val));
+  ASSERT_EQ("whitelabel1-wallpaper", val);
+}
+
+TEST_F(CrosConfigTest, CheckWhitelabelEmptyMatchesDefault) {
+  InitConfigArm("google,whitelabel", "");
+  std::string val;
+  ASSERT_TRUE(cros_config_.GetString("/", "wallpaper", &val));
+  ASSERT_EQ("whitelabel-default-wallpaper", val);
+}
+
+TEST_F(CrosConfigTest, CheckWhitelabelUnknownMatchesDefault) {
+  InitConfigArm("google,whitelabel", "unknown");
+  std::string val;
+  ASSERT_TRUE(cros_config_.GetString("/", "wallpaper", &val));
+  ASSERT_EQ("whitelabel-default-wallpaper", val);
+}
+
+TEST_F(CrosConfigTest, CheckWhitelabelTagIgnoredForNonWhitelabel) {
+  InitConfigArm("google,some", "whitelabel-tag-to-ignore");
+  std::string val;
+  ASSERT_TRUE(cros_config_.GetString("/", "wallpaper", &val));
+  ASSERT_EQ("some-wallpaper", val);
+}
+
+TEST_F(CrosConfigTest, CheckArmNoIdentityMatch) {
+  base::FilePath filepath(TEST_FILE_ARM);
+  ASSERT_FALSE(cros_config_.InitForTestArm(filepath, "invalid", ""));
+}
+#endif /* !USE_JSON */
+
+TEST_F(CrosConfigTest, CheckUiPowerPosition) {
+  InitConfig("Some", 1);
+  std::string val;
+  ASSERT_TRUE(cros_config_.GetString("/ui/power-button", "edge", &val));
+  ASSERT_EQ("left", val);
+  ASSERT_TRUE(cros_config_.GetString("/ui/power-button", "position", &val));
+  ASSERT_EQ("0.3", val);
+}
+
+int main(int argc, char** argv) {
+  int status = system("exec ./chromeos-config-test-setup.sh");
+  if (status != 0)
+    return EXIT_FAILURE;
+
+  logging::LoggingSettings settings;
+  settings.logging_dest = logging::LOG_TO_FILE;
+  settings.log_file = "log.test";
+  settings.lock_log = logging::DONT_LOCK_LOG_FILE;
+  settings.delete_old = logging::DELETE_OLD_LOG_FILE;
+  logging::InitLogging(settings);
+  logging::SetMinLogLevel(-3);
+
+  testing::InitGoogleTest(&argc, argv);
+
+  return RUN_ALL_TESTS();
+}
diff --git a/chromeos-config/libcros_config/ec_test_many.c b/chromeos-config/libcros_config/ec_test_many.c
deleted file mode 100644
index 100d47b..0000000
--- a/chromeos-config/libcros_config/ec_test_many.c
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2018 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ec_config.h"
-
-#include "compile_time_macros.h"
-
-
-#if defined(BOARD_SOME)
-
-const struct sku_info ALL_SKUS[] = {
-  {.sku = 9,
-   .has_base_accelerometer = 1,
-   .has_lid_accelerometer = 1,
-   .is_lid_convertible = 0},
-  {.sku = 99,
-   .has_base_accelerometer = 0,
-   .has_lid_accelerometer = 1,
-   .is_lid_convertible = 1}
-};
-
-#elif defined(BOARD_ANOTHER)
-
-const struct sku_info ALL_SKUS[] = {
-  {.sku = 40,
-   .has_base_accelerometer = 1,
-   .has_lid_accelerometer = 1,
-   .is_lid_convertible = 0}
-};
-
-#endif
-
-
-const size_t NUM_SKUS = ARRAY_SIZE(ALL_SKUS);
diff --git a/chromeos-config/libcros_config/ec_test_many.h b/chromeos-config/libcros_config/ec_test_many.h
deleted file mode 100644
index ecf3bb3..0000000
--- a/chromeos-config/libcros_config/ec_test_many.h
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright 2018 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROMEOS_CONFIG_EC_CONFIG_H_
-#define CHROMEOS_CONFIG_EC_CONFIG_H_
-
-#include <stdint.h>
-#include <stdlib.h>
-
-struct sku_info {
-  const uint8_t sku;
-  const uint8_t has_base_accelerometer :1;
-  const uint8_t has_lid_accelerometer :1;
-  const uint8_t is_lid_convertible :1;
-};
-
-extern const size_t NUM_SKUS;
-extern const struct sku_info ALL_SKUS[];
-
-#endif  // CHROMEOS_CONFIG_EC_CONFIG_H_
diff --git a/chromeos-config/libcros_config/ec_test_none.c b/chromeos-config/libcros_config/ec_test_none.c
deleted file mode 100644
index 98d8509..0000000
--- a/chromeos-config/libcros_config/ec_test_none.c
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright 2018 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ec_config.h"
-
-#include "compile_time_macros.h"
-
-const struct sku_info ALL_SKUS[] = {};
-
-const size_t NUM_SKUS = ARRAY_SIZE(ALL_SKUS);
diff --git a/chromeos-config/libcros_config/ec_test_none.h b/chromeos-config/libcros_config/ec_test_none.h
deleted file mode 100644
index 7079a2b..0000000
--- a/chromeos-config/libcros_config/ec_test_none.h
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright 2018 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROMEOS_CONFIG_EC_CONFIG_H_
-#define CHROMEOS_CONFIG_EC_CONFIG_H_
-
-#include <stdint.h>
-#include <stdlib.h>
-
-struct sku_info {
-  const uint8_t sku;
-};
-
-extern const size_t NUM_SKUS;
-extern const struct sku_info ALL_SKUS[];
-
-#endif  // CHROMEOS_CONFIG_EC_CONFIG_H_
diff --git a/chromeos-config/libcros_config/ec_test_one.c b/chromeos-config/libcros_config/ec_test_one.c
deleted file mode 100644
index 7172130..0000000
--- a/chromeos-config/libcros_config/ec_test_one.c
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2018 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ec_config.h"
-
-#include "compile_time_macros.h"
-
-
-
-const struct sku_info ALL_SKUS[] = {
-  {.sku = 0,
-   .has_base_accelerometer = 1,
-   .has_base_gyroscope = 1,
-   .has_lid_accelerometer = 1,
-   .is_lid_convertible = 1},
-  {.sku = 1,
-   .has_base_accelerometer = 0,
-   .has_base_gyroscope = 0,
-   .has_lid_accelerometer = 0,
-   .is_lid_convertible = 0},
-  {.sku = 8,
-   .has_base_accelerometer = 0,
-   .has_base_gyroscope = 0,
-   .has_lid_accelerometer = 0,
-   .is_lid_convertible = 0},
-  {.sku = 9,
-   .has_base_accelerometer = 0,
-   .has_base_gyroscope = 0,
-   .has_lid_accelerometer = 0,
-   .is_lid_convertible = 0}
-};
-
-
-const size_t NUM_SKUS = ARRAY_SIZE(ALL_SKUS);
diff --git a/chromeos-config/libcros_config/ec_test_one.h b/chromeos-config/libcros_config/ec_test_one.h
deleted file mode 100644
index 385d104..0000000
--- a/chromeos-config/libcros_config/ec_test_one.h
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright 2018 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROMEOS_CONFIG_EC_CONFIG_H_
-#define CHROMEOS_CONFIG_EC_CONFIG_H_
-
-#include <stdint.h>
-#include <stdlib.h>
-
-struct sku_info {
-  const uint8_t sku;
-  const uint8_t has_base_accelerometer :1;
-  const uint8_t has_base_gyroscope :1;
-  const uint8_t has_lid_accelerometer :1;
-  const uint8_t is_lid_convertible :1;
-};
-
-extern const size_t NUM_SKUS;
-extern const struct sku_info ALL_SKUS[];
-
-#endif  // CHROMEOS_CONFIG_EC_CONFIG_H_
diff --git a/chromeos-config/libcros_config/fake_cros_config.cc b/chromeos-config/libcros_config/fake_cros_config.cc
index 399ee4e..716323b 100644
--- a/chromeos-config/libcros_config/fake_cros_config.cc
+++ b/chromeos-config/libcros_config/fake_cros_config.cc
@@ -32,4 +32,28 @@
   return true;
 }
 
+void FakeCrosConfig::SetTargetDir(const std::string& prop,
+                                  const std::string& dirname) {
+  target_dirs_[prop] = dirname;
+}
+
+bool FakeCrosConfig::GetAbsPath(const std::string& path,
+                                const std::string& prop,
+                                std::string* val_out) {
+  std::string val;
+  if (!GetString(path, prop, &val)) {
+    return false;
+  }
+
+  auto match = target_dirs_.find(prop);
+  if (match == target_dirs_.end()) {
+    CROS_CONFIG_LOG(ERROR) << "Absolute path requested at path " << path
+                           << " property " << prop << " but none is available";
+    return false;
+  }
+  *val_out = match->second + "/" + val;
+
+  return true;
+}
+
 }  // namespace brillo
diff --git a/chromeos-config/libcros_config/fake_cros_config.h b/chromeos-config/libcros_config/fake_cros_config.h
index ebb1d2e..8fdfe6d 100644
--- a/chromeos-config/libcros_config/fake_cros_config.h
+++ b/chromeos-config/libcros_config/fake_cros_config.h
@@ -38,12 +38,26 @@
                  const std::string& prop,
                  std::string* val_out) override;
 
+  // Sets up an entry in the target directory map. This will be used in future
+  // calls to GetAbsPath(). The Fake does not read the schema file, relying
+  // instead on its caller to provide test data. This provides more flexibility
+  // for callers to implement tests as they wish.
+  // @prop: Name of property to set up
+  // @dirname: Directory name to use for that property
+  void SetTargetDir(const std::string& prop, const std::string& dirname);
+
+  // CrosConfigInterface:
+  bool GetAbsPath(const std::string& path,
+                  const std::string& prop,
+                  std::string* val_out) override;
+
  private:
   using PathProp = std::pair<std::string, std::string>;
 
   // This stores the string values, keyed by a pair consisting of the path
   // and the property.
   std::map<PathProp, std::string> values_;
+  std::map<std::string, std::string> target_dirs_;
 
   DISALLOW_COPY_AND_ASSIGN(FakeCrosConfig);
 };
diff --git a/chromeos-config/libcros_config/fake_cros_config_test.cc b/chromeos-config/libcros_config/fake_cros_config_unittest.cc
similarity index 74%
rename from chromeos-config/libcros_config/fake_cros_config_test.cc
rename to chromeos-config/libcros_config/fake_cros_config_unittest.cc
index a8ac096..2a0206a 100644
--- a/chromeos-config/libcros_config/fake_cros_config_test.cc
+++ b/chromeos-config/libcros_config/fake_cros_config_unittest.cc
@@ -27,6 +27,18 @@
   ASSERT_TRUE(cros_config_.GetString("/thermal", "dptf-dv", &val));
 }
 
+TEST_F(FakeCrosConfigTest, CheckGetAbsPath) {
+  std::string val;
+
+  cros_config_.SetString("/thermal", "dptf-dv", "testing.dv");
+  ASSERT_FALSE(cros_config_.GetAbsPath("/thermal", "dptf-dv", &val));
+
+  // Add to the map and try again. This should work.
+  cros_config_.SetTargetDir("dptf-dv", "/etc/dptf");
+  ASSERT_TRUE(cros_config_.GetAbsPath("/thermal", "dptf-dv", &val));
+  ASSERT_EQ("/etc/dptf/testing.dv", val);
+}
+
 int main(int argc, char** argv) {
   logging::LoggingSettings settings;
   settings.logging_dest = logging::LOG_TO_FILE;
diff --git a/chromeos-config/libcros_config/identity_arm.cc b/chromeos-config/libcros_config/identity_arm.cc
index 83f1072..299d2bf 100644
--- a/chromeos-config/libcros_config/identity_arm.cc
+++ b/chromeos-config/libcros_config/identity_arm.cc
@@ -6,64 +6,37 @@
 #include "chromeos-config/libcros_config/identity_arm.h"
 
 #include <string>
-#include <arpa/inet.h>
 
 #include <base/logging.h>
 #include <base/files/file_util.h>
 
 namespace brillo {
 
-static const int SKU_ID_LEN = 4;
-
 CrosConfigIdentityArm::CrosConfigIdentityArm() {}
 
 CrosConfigIdentityArm::~CrosConfigIdentityArm() {}
 
-bool CrosConfigIdentityArm::Fake(
-    const std::string& device_name,
-    int sku_id,
-    base::FilePath* dt_compatible_file_out,
-    base::FilePath* sku_id_file_out) {
+bool CrosConfigIdentityArm::FakeDtCompatible(
+    const std::string& device_name, base::FilePath* dt_compatible_file_out) {
   *dt_compatible_file_out = base::FilePath("dt_compatible");
   if (base::WriteFile(*dt_compatible_file_out, device_name.c_str(),
                       device_name.length()) != device_name.length()) {
     CROS_CONFIG_LOG(ERROR) << "Failed to write device-tree compatible file";
     return false;
   }
-  char sku_id_char[SKU_ID_LEN];
-  uint32_t sku_id_fdt = htonl(sku_id);
-  *sku_id_file_out = base::FilePath("sku-id");
-
-  std::memcpy(sku_id_char, &sku_id_fdt, SKU_ID_LEN);
-
-  if (base::WriteFile(*sku_id_file_out, sku_id_char,
-                      SKU_ID_LEN) != SKU_ID_LEN) {
-    CROS_CONFIG_LOG(ERROR) << "Failed to write sku-id file";
-    return false;
-  }
 
   return true;
 }
 
-bool CrosConfigIdentityArm::ReadInfo(const base::FilePath& dt_compatible_file,
-                                     const base::FilePath& sku_id_file) {
+bool CrosConfigIdentityArm::ReadDtCompatible(
+    const base::FilePath& dt_compatible_file) {
   if (!base::ReadFileToString(dt_compatible_file, &compatible_devices_)) {
     CROS_CONFIG_LOG(ERROR) << "Failed to read device-tree compatible file: "
                            << dt_compatible_file.MaybeAsASCII();
     return false;
   }
-
-  char sku_id_char[SKU_ID_LEN];
-  if (base::ReadFile(sku_id_file, sku_id_char, SKU_ID_LEN) != SKU_ID_LEN) {
-    CROS_CONFIG_LOG(WARNING) << "Cannot read product_sku file ";
-    return false;
-  }
-  std::memcpy(&sku_id_, sku_id_char, SKU_ID_LEN);
-  sku_id_ = ntohl(sku_id_);
-
   CROS_CONFIG_LOG(INFO) << "Read device-tree compatible list: "
-                        << compatible_devices_
-                        << ", sku_id: " << sku_id_;
+                        << compatible_devices_;
   return true;
 }
 
diff --git a/chromeos-config/libcros_config/identity_arm.h b/chromeos-config/libcros_config/identity_arm.h
index 273c756..cc1985e 100644
--- a/chromeos-config/libcros_config/identity_arm.h
+++ b/chromeos-config/libcros_config/identity_arm.h
@@ -20,30 +20,17 @@
   CrosConfigIdentityArm();
   ~CrosConfigIdentityArm();
 
-  // @return SKU ID value read via FDT
-  int GetSkuId() const { return sku_id_; }
-
-  // Initially, the SKU ID will be read from FDT but if user would like to
-  // have the identify with different SKU ID then you can overwrite it here.
-  void SetSkuId(const int sku_id) { sku_id_ = sku_id; }
-
   // Read the compatible devices list from the device-tree compatible file.
   //
   // @dt_compatible_file: File to read - typically /proc/device-tree/compatible
-  // @sku_id_file: File containing SKU ID integer
-  bool ReadInfo(const base::FilePath& dt_compatible_file,
-                const base::FilePath& sku_id_file);
+  bool ReadDtCompatible(const base::FilePath& dt_compatible_file);
 
   // Write out fake device-tree compatible file for testing purposes.
   // @device_name: Device name to write to the compatible file
-  // @sku_id_file: File containing SKU ID integer
   // @dt_compatible_file_out: Returns the file that was written
-  // @sku_id_file_out: File that the SKU ID integer was written into
   // @return true if OK, false on error
-  bool Fake(const std::string& device_name,
-            int sku_id,
-            base::FilePath* dt_compatible_file_out,
-            base::FilePath* sku_id_file_out);
+  bool FakeDtCompatible(const std::string& device_name,
+                        base::FilePath* dt_compatible_file_out);
 
   // Checks if the device_name exists in the compatible devices string.
   // @return true if device is compatible
@@ -56,7 +43,6 @@
 
  private:
   std::string compatible_devices_;
-  int sku_id_;
 
   DISALLOW_COPY_AND_ASSIGN(CrosConfigIdentityArm);
 };
diff --git a/chromeos-config/libcros_config/target_dirs.dtsi b/chromeos-config/libcros_config/target_dirs.dtsi
new file mode 100644
index 0000000..d5e05db
--- /dev/null
+++ b/chromeos-config/libcros_config/target_dirs.dtsi
@@ -0,0 +1,27 @@
+/*
+ * This is a generated file, DO NOT EDIT!'
+ *
+ * This provides a map from property name to target directory for all PropFile
+ * objects defined in the schema.
+ */
+
+/ {
+	chromeos {
+		schema {
+			phandle-properties = "arc-properties-type",
+				"audio-type", "bcs-type", "power-type",
+				"shares", "single-sku", "touch-type",
+				"whitelabel";
+			target-dirs {
+				alsa-conf = "/usr/share/alsa/ucm";
+				cras-config-dir = "/etc/cras";
+				dptf-dv = "/etc/dptf";
+				dsp-ini = "/etc/cras";
+				hifi-conf = "/usr/share/alsa/ucm";
+				topology-bin = "/lib/firmware";
+				volume = "/etc/cras";
+			};
+		};
+	};
+};
+
diff --git a/chromeos-config/libcros_config/test.c b/chromeos-config/libcros_config/test.c
index 233266f..5a23179 100644
--- a/chromeos-config/libcros_config/test.c
+++ b/chromeos-config/libcros_config/test.c
@@ -46,7 +46,7 @@
      .sku_id = 8,
      .customization_id = "",
      .whitelabel_tag = "whitelabel1",
-     .info = {.brand = "WLBA",
+     .info = {.brand = "",
               .model = "whitelabel",
               .customization = "whitelabel1",
               .signature_id = "whitelabel-whitelabel1"}},
@@ -56,7 +56,7 @@
      .sku_id = 9,
      .customization_id = "",
      .whitelabel_tag = "whitelabel1",
-     .info = {.brand = "WLBA",
+     .info = {.brand = "",
               .model = "whitelabel",
               .customization = "whitelabel1",
               .signature_id = "whitelabel-whitelabel1"}},
@@ -66,7 +66,7 @@
      .sku_id = 8,
      .customization_id = "",
      .whitelabel_tag = "whitelabel2",
-     .info = {.brand = "WLBB",
+     .info = {.brand = "",
               .model = "whitelabel",
               .customization = "whitelabel2",
               .signature_id = "whitelabel-whitelabel2"}},
@@ -76,7 +76,7 @@
      .sku_id = 9,
      .customization_id = "",
      .whitelabel_tag = "whitelabel2",
-     .info = {.brand = "WLBB",
+     .info = {.brand = "",
               .model = "whitelabel",
               .customization = "whitelabel2",
               .signature_id = "whitelabel-whitelabel2"}}
diff --git a/chromeos-config/libcros_config/test.dts b/chromeos-config/libcros_config/test.dts
new file mode 100644
index 0000000..97a1d3f
--- /dev/null
+++ b/chromeos-config/libcros_config/test.dts
@@ -0,0 +1,227 @@
+/*
+ * Copyright 2016 The Chromium OS Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/dts-v1/;
+
+/ {
+	chromeos {
+		family: family {
+		};
+		models: models {
+		};
+	};
+};
+
+&family {
+	audio {
+		audio_type: audio-type {
+			card = "a-card";
+			volume = "cras-config/{cras-config-dir}/{card}";
+			dsp-ini = "cras-config/{cras-config-dir}/dsp.ini";
+			hifi-conf = "ucm-config/{card}.{ucm-suffix}/HiFi.conf";
+			alsa-conf = "ucm-config/{card}.{ucm-suffix}/{card}.{ucm-suffix}.conf";
+			topology-bin = "topology/{topology-name}-tplg.bin";
+		};
+	};
+	firmware {
+		script = "updater4.sh";
+		shared: some {
+			bcs-overlay = "overlay-some-private";
+			ec-image = "bcs://Some_EC.1111.11.1.tbz2";
+			main-image = "bcs://Some.1111.11.1.tbz2";
+			main-rw-image = "bcs://Some_RW.1111.11.1.tbz2";
+			build-targets {
+				coreboot = "some";
+				ec = "some";
+				depthcharge = "some";
+				libpayload = "some";
+			};
+		};
+	};
+	mapping {
+		sku-map@0 {
+			platform-name = "Some";
+			smbios-name-match = "Some";
+			/*
+			 * This is an example! It does not match any real
+			 * family.
+			 */
+			simple-sku-map = <
+				0 &some_touch
+				1 &some_notouch
+				8 &some_whitelabel_touch
+				9 &some_whitelabel_notouch>;
+		};
+		sku-map@1 {
+			platform-name = "Another";
+			smbios-name-match = "Another";
+			single-sku = <&another>;
+		};
+	};
+	touch {
+		/* Example of how to put firmware in BCS */
+		some_touchscreen: some-touchscreen {
+			vendor = "some_touch_vendor";
+			firmware-bin = "{vendor}/{pid}_{version}.bin";
+			firmware-symlink = "{vendor}ts_i2c_{pid}.bin";
+		};
+		some_stylus: some-stylus {
+			vendor = "some_stylus_vendor";
+			firmware-bin = "{vendor}/{version}.hex";
+			firmware-symlink = "{vendor}_firmware_{MODEL}.bin";
+		};
+	};
+};
+
+&models {
+	some {
+		wallpaper = "some";
+		oem-id = "0";
+		arc {
+			hw-features = "some/hardware_features";
+		};
+		firmware {
+			key-id = "SOME";
+			shares = <&shared>;
+		};
+		submodels {
+			some_touch: touch {
+				touch {
+					present = "yes";
+				};
+				audio {
+					main {
+						audio-type = <&audio_type>;
+						cras-config-dir = "some";
+						ucm-suffix = "some";
+						topology-name = "some";
+					};
+				};
+				thermal {
+					dptf-dv = "some_touch/dptf.dv";
+				};
+			};
+			some_notouch: notouch {
+				touch {
+					present = "no";
+				};
+				audio {
+					main {
+						audio-type = <&audio_type>;
+						cras-config-dir = "some";
+						ucm-suffix = "some";
+						topology-name = "some";
+					};
+				};
+				thermal {
+					dptf-dv = "some_notouch/dptf.dv";
+				};
+			};
+		};
+		touch {
+			stylus {
+				touch-type = <&some_stylus>;
+				version = "some-version";
+			};
+			touchpad {
+				touch-type = <&some_touchscreen>;
+				pid = "some-pid";
+				version = "some-version";
+			};
+			touchscreen@0 {
+				touch-type = <&some_touchscreen>;
+				pid = "some-other-pid";
+				version = "some-other-version";
+			};
+		};
+		ui {
+			power-button {
+				edge = "left";
+				position = "0.3";
+			};
+		};
+	};
+	another: another {
+		string-list = "default", "more";
+		bool-prop;
+		wallpaper = "default";
+		audio {
+			main {
+				audio-type = <&audio_type>;
+				cras-config-dir = "another";
+				ucm-suffix = "another";
+				topology-name = "another";
+			};
+		};
+		firmware {
+			bcs-overlay = "overlay-another-private";
+			ec-image = "bcs://Another_EC.1111.11.1.tbz2";
+			main-image = "bcs://Another.1111.11.1.tbz2";
+			main-rw-image = "bcs://Another_RW.1111.11.1.tbz2";
+			key-id = "ANOTHER";
+			extra = "${FILESDIR}/extra";
+			tools = "${FILESDIR}/tools1", "${FILESDIR}/tools2";
+			build-targets {
+				base = "another_base";
+				coreboot = "another";
+				cr50 = "another_cr50";
+				ec = "another";
+				depthcharge = "another";
+				libpayload = "another";
+			};
+		};
+		thermal {
+			dptf-dv = "another/dptf.dv";
+		};
+		touch {
+			present = "probe";
+			probe-regex = "another-probe-regex";
+			touchscreen {
+				touch-type = <&some_touchscreen>;
+				pid = "some-pid";
+				version = "some-version";
+			};
+			stylus {
+				touch-type = <&some_stylus>;
+				version = "another-version";
+			};
+		};
+	};
+
+	whitelabel: whitelabel {
+		oem-id = "1";
+		firmware {
+			shares = <&shared>;
+		};
+		submodels {
+			some_whitelabel_touch: touch {
+				touch {
+					present = "yes";
+				};
+			};
+			some_whitelabel_notouch: notouch {
+				touch {
+					present = "no";
+				};
+			};
+		};
+		whitelabels {
+			whitelabel1 {
+				wallpaper = "wallpaper-wl1";
+				brand-code = "WLBA";
+				key-id = "WHITELABEL1";
+			};
+			whitelabel2 {
+				wallpaper = "wallpaper-wl2";
+				brand-code = "WLBB";
+				key-id = "WHITELABEL2";
+			};
+		};
+	};
+};
+
+/include/ "target_dirs.dtsi"
diff --git a/chromeos-config/libcros_config/test.json b/chromeos-config/libcros_config/test.json
index 713bd34..c20c270 100644
--- a/chromeos-config/libcros_config/test.json
+++ b/chromeos-config/libcros_config/test.json
@@ -13,28 +13,11 @@
             "ucm-suffix": "some"
           }
         },
-        "bluetooth": {
-          "config": {
-            "system-path": "/etc/bluetooth/some/main.conf"
-          }
-        },
-        "camera": {
-          "count": 1
-        },
-        "hardware-properties": {
-          "has-base-accelerometer": true,
-          "has-base-gyroscope": true,
-          "has-lid-accelerometer": true,
-          "is-lid-convertible": true
-        },
         "identity": {
           "platform-name": "Some",
           "sku-id": 0,
           "smbios-name-match": "Some"
         },
-        "modem": {
-          "firmware-variant": "some"
-        },
         "name": "some",
         "oem-id": "0",
         "power": {
@@ -73,25 +56,11 @@
             "ucm-suffix": "some"
           }
         },
-        "bluetooth": {
-          "config": {
-            "system-path": "/etc/bluetooth/some/main.conf"
-          }
-        },
-        "camera": {
-          "count": 1
-        },
-        "hardware-properties": {
-          "is-lid-convertible": false
-        },
         "identity": {
           "platform-name": "Some",
           "sku-id": 1,
           "smbios-name-match": "Some"
         },
-        "modem": {
-          "firmware-variant": "some"
-        },
         "name": "some",
         "oem-id": "0",
         "power": {
@@ -122,9 +91,6 @@
             "ucm-suffix": "another"
           }
         },
-        "hardware-properties": {
-          "is-lid-convertible": true
-        },
         "identity": {
           "platform-name": "Another",
           "smbios-name-match": "Another"
@@ -151,18 +117,6 @@
             "first-api-level": "27"
           }
         },
-        "bluetooth": {
-          "config": {
-            "system-path": "/etc/bluetooth/some/main.conf"
-          }
-        },
-        "brand-code": "WLBA",
-        "camera": {
-          "count": 1
-        },
-        "hardware-properties": {
-          "is-lid-convertible": false
-        },
         "identity": {
           "platform-name": "Some",
           "sku-id": 8,
@@ -198,18 +152,6 @@
             "first-api-level": "27"
           }
         },
-        "bluetooth": {
-          "config": {
-            "system-path": "/etc/bluetooth/some/main.conf"
-          }
-        },
-        "brand-code": "WLBA",
-        "camera": {
-          "count": 1
-        },
-        "hardware-properties": {
-          "is-lid-convertible": false
-        },
         "identity": {
           "platform-name": "Some",
           "sku-id": 9,
@@ -244,18 +186,6 @@
             "first-api-level": "27"
           }
         },
-        "bluetooth": {
-          "config": {
-            "system-path": "/etc/bluetooth/some/main.conf"
-          }
-        },
-        "brand-code": "WLBB",
-        "camera": {
-          "count": 1
-        },
-        "hardware-properties": {
-          "is-lid-convertible": false
-        },
         "identity": {
           "platform-name": "Some",
           "sku-id": 8,
@@ -291,18 +221,6 @@
             "first-api-level": "27"
           }
         },
-        "bluetooth": {
-          "config": {
-            "system-path": "/etc/bluetooth/some/main.conf"
-          }
-        },
-        "brand-code": "WLBB",
-        "camera": {
-          "count": 1
-        },
-        "hardware-properties": {
-          "is-lid-convertible": false
-        },
         "identity": {
           "platform-name": "Some",
           "sku-id": 9,
@@ -333,4 +251,4 @@
       }
     ]
   }
-}
+}
\ No newline at end of file
diff --git a/chromeos-config/libcros_config/test.yaml b/chromeos-config/libcros_config/test.yaml
index b5e792b..ad46a51 100644
--- a/chromeos-config/libcros_config/test.yaml
+++ b/chromeos-config/libcros_config/test.yaml
@@ -44,12 +44,6 @@
         destination: "/usr/share/chromeos-config/sbin/some/hardware_features"
     build-properties:
       first-api-level: "27"
-  bluetooth:
-    config:
-      build-path: "some/main.conf"
-      system-path: "/etc/bluetooth/some/main.conf"
-  camera:
-    count: 1
   firmware:
     bcs-overlay: "overlay-some-private"
     build-targets:
@@ -59,8 +53,8 @@
       libpayload: "some"
       u-boot: "some"
 
-    ec-ro-image: "bcs://Some_EC.1111.11.1.tbz2"
-    main-ro-image: "bcs://Some.1111.11.1.tbz2"
+    ec-image: "bcs://Some_EC.1111.11.1.tbz2"
+    main-image: "bcs://Some.1111.11.1.tbz2"
     main-rw-image: "bcs://Some_RW.1111.11.1.tbz2"
   power:
     charging-ports: |
@@ -86,20 +80,15 @@
   firmware-signing:
     key-id: "{{$key-id}}"
     signature-id: "{{$name}}"
-  hardware-properties:
-    is-lid-convertible: true
   identity:
     platform-name: "Some"
     smbios-name-match: "Some"
     sku-id: "{{$sku-id}}"
-  modem:
-    firmware-variant: "some"
   wallpaper: "some"
   $oem-id: "0"
 
 some_whitelabel_config: &some_whitelabel_config
   <<: *some_base_config
-  brand-code: "{{$brand-code}}"
   identity:
     platform-name: "Some"
     smbios-name-match: "Some"
@@ -109,8 +98,6 @@
     key-id: "{{$key-id}}"
     signature-id: "{{$name}}-{{$whitelabel-tag}}"
     sig-id-in-customization-id: True
-  hardware-properties:
-    is-lid-convertible: false
   $oem-id: "1"
 
 some_touch_config: &some_touch_config
@@ -139,11 +126,6 @@
         - $sku-id: 0
           config:
             <<: *some_config
-            hardware-properties:
-              is-lid-convertible: true
-              has-base-accelerometer: true
-              has-base-gyroscope: true
-              has-lid-accelerometer: true
             touch:
               <<: *some_touch_config
               present: "probe"
@@ -156,8 +138,6 @@
         - $sku-id: 1
           config:
             <<: *some_config
-            hardware-properties:
-              is-lid-convertible: false
             touch:
               <<: *some_touch_config
               present: "no"
@@ -174,22 +154,22 @@
             audio:
               <<: *audio
               $topology-name: "another"
-            hardware-properties:
-              is-lid-convertible: true
             identity:
               platform-name: "Another"
               smbios-name-match: "Another"
             firmware:
               bcs-overlay: "overlay-another-private"
 
-              ec-ro-image: "bcs://Another_EC.1111.11.1.tbz2"
-              main-ro-image: "bcs://Another.1111.11.1.tbz2"
+              ec-image: "bcs://Another_EC.1111.11.1.tbz2"
+              main-image: "bcs://Another.1111.11.1.tbz2"
               main-rw-image: "bcs://Another_RW.1111.11.1.tbz2"
+              extra:
+                - "${FILESDIR}/extra"
+              tools:
+                - "${FILESDIR}/tools1"
+                - "${FILESDIR}/tools2"
               build-targets:
                 base: "another_base"
-                ec_extras:
-                  - "extra1"
-                  - "extra2"
                 coreboot: "another"
                 cr50: "another_cr50"
                 ec: "another"
diff --git a/chromeos-config/libcros_config/test_arm.c b/chromeos-config/libcros_config/test_arm.c
index e75471e..a733118 100644
--- a/chromeos-config/libcros_config/test_arm.c
+++ b/chromeos-config/libcros_config/test_arm.c
@@ -3,7 +3,6 @@
 static struct config_map all_configs[] = {
     {.platform_name = "",
      .device_tree_compatible_match = "google,some",
-     .sku_id = -1,
      .customization_id = "",
      .whitelabel_tag = "",
      .info = {.brand = "",
@@ -13,7 +12,6 @@
 
     {.platform_name = "",
      .device_tree_compatible_match = "google,whitelabel",
-     .sku_id = -1,
      .customization_id = "",
      .whitelabel_tag = "whitelabel1",
      .info = {.brand = "",
@@ -23,7 +21,6 @@
 
     {.platform_name = "",
      .device_tree_compatible_match = "google,whitelabel",
-     .sku_id = -1,
      .customization_id = "",
      .whitelabel_tag = "whitelabel2",
      .info = {.brand = "",
@@ -33,36 +30,15 @@
 
     {.platform_name = "",
      .device_tree_compatible_match = "google,whitelabel",
-     .sku_id = -1,
      .customization_id = "",
      .whitelabel_tag = "",
      .info = {.brand = "",
               .model = "whitelabel",
               .customization = "whitelabel",
-              .signature_id = "whitelabel"}},
-
-    {.platform_name = "Another",
-     .device_tree_compatible_match = "google,another",
-     .sku_id = 8,
-     .customization_id = "",
-     .whitelabel_tag = "",
-     .info = {.brand = "",
-              .model = "another1",
-              .customization = "another1",
-              .signature_id = "another1"}},
-
-    {.platform_name = "Another",
-     .device_tree_compatible_match = "google,another",
-     .sku_id = 9,
-     .customization_id = "",
-     .whitelabel_tag = "",
-     .info = {.brand = "",
-              .model = "another2",
-              .customization = "another2",
-              .signature_id = "another2"}}
+              .signature_id = "whitelabel"}}
 };
 
 const struct config_map *cros_config_get_config_map(int *num_entries) {
-  *num_entries = 6;
+  *num_entries = 4;
   return &all_configs[0];
 }
diff --git a/chromeos-config/libcros_config/test_arm.json b/chromeos-config/libcros_config/test_arm.json
index 4e323aa..989eda3 100644
--- a/chromeos-config/libcros_config/test_arm.json
+++ b/chromeos-config/libcros_config/test_arm.json
@@ -31,23 +31,7 @@
         },
         "name": "whitelabel",
         "wallpaper": "whitelabel-default-wallpaper"
-      },
-      {
-        "identity": {
-          "device-tree-compatible-match": "google,another",
-          "platform-name": "Another",
-          "sku-id": 8
-        },
-        "name": "another1"
-      },
-      {
-        "identity": {
-          "device-tree-compatible-match": "google,another",
-          "platform-name": "Another",
-          "sku-id": 9
-        },
-        "name": "another2"
       }
     ]
   }
-}
+}
\ No newline at end of file
diff --git a/chromeos-config/libcros_config/test_arm.yaml b/chromeos-config/libcros_config/test_arm.yaml
index ac85ee4..6550098 100644
--- a/chromeos-config/libcros_config/test_arm.yaml
+++ b/chromeos-config/libcros_config/test_arm.yaml
@@ -4,15 +4,6 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-another_config: &another_config
-  name: "{{$name}}"
-  identity:
-    platform-name: "Another"
-    device-tree-compatible-match: "google,another"
-    sku-id: "{{$sku-id}}"
-  firmware:
-    no-firmware: true
-
 chromeos:
   devices:
     - $name: "some"
@@ -32,8 +23,8 @@
           $wallpaper: "whitelabel1-wallpaper"
         - $whitelabel-tag: "whitelabel2"
           $wallpaper: "whitelabel2-wallpaper"
-        - $whitelabel-tag: "" # Default when no VPD value is set.
-          $wallpaper: "whitelabel-default-wallpaper"
+        - $wallpaper: "whitelabel-default-wallpaper"
+          $whitelabel-tag: "" # Default when no VPD value is set.
       skus:
         - config:
             name: "{{$name}}"
@@ -43,15 +34,3 @@
             firmware:
               no-firmware: true
             wallpaper: "{{$wallpaper}}"
-    - $name: "another1"
-      products:
-        - $key-id: "ANOTHER1"
-      skus:
-        - $sku-id: 8
-          config: *another_config
-    - $name: "another2"
-      products:
-        - $key-id: "ANOTHER2"
-      skus:
-        - $sku-id: 9
-          config: *another_config
diff --git a/chromeos-config/libcros_config/test_bad_audio.dts b/chromeos-config/libcros_config/test_bad_audio.dts
new file mode 100644
index 0000000..702e3a9
--- /dev/null
+++ b/chromeos-config/libcros_config/test_bad_audio.dts
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2018 The Chromium OS Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/dts-v1/;
+
+/* This file has a missing audio property */
+/ {
+	chromeos {
+		family {
+		};
+
+		models: models {
+		};
+	};
+};
+
+&models {
+	pyro {
+		audio {
+			main {
+			};
+		};
+	};
+};
diff --git a/chromeos-config/libcros_config/test_bad_default.dts b/chromeos-config/libcros_config/test_bad_default.dts
new file mode 100644
index 0000000..d36318f
--- /dev/null
+++ b/chromeos-config/libcros_config/test_bad_default.dts
@@ -0,0 +1,43 @@
+/dts-v1/;
+
+/ {
+	chromeos {
+		family: family {
+		};
+		models: models {
+		};
+	};
+};
+
+&family {
+	mapping {
+		#address-cells = <1>;
+		#size-cells = <0>;
+		sku-map@0 {
+			reg = <0>;
+			platform-name = "Reef";
+			smbios-name-match = "Reef";
+			/*
+			* This is an example! It does not match any real
+			* family.
+			*/
+			simple-sku-map = <21 &circular_default>;
+		};
+	};
+};
+
+&models {
+	circular_default: circular-default {
+		default = <&circular_default_1>;
+		firmware {
+			no-firmware;
+		};
+	};
+
+	circular_default_1: circular-default-1 {
+		default = <&circular_default>;
+		firmware {
+			no-firmware;
+		};
+	};
+};
diff --git a/chromeos-config/libcros_config/test_bad_struct.dts b/chromeos-config/libcros_config/test_bad_struct.dts
new file mode 100644
index 0000000..1b68b52
--- /dev/null
+++ b/chromeos-config/libcros_config/test_bad_struct.dts
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2016 The Chromium OS Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/dts-v1/;
+
+/* This file has an incorrect structure (models should be within chromeos) */
+/ {
+	chromeos {
+	};
+
+	models: models {
+	};
+};
+
+&models {
+	pyro {
+		wallpaper = "default";
+		string-list = "default", "more";
+	};
+};
diff --git a/chromeos-config/libcros_config/test_build.json b/chromeos-config/libcros_config/test_build.json
index 7ce3cf6..61f8183 100644
--- a/chromeos-config/libcros_config/test_build.json
+++ b/chromeos-config/libcros_config/test_build.json
@@ -41,15 +41,6 @@
             "ucm-suffix": "some"
           }
         },
-        "bluetooth": {
-          "config": {
-            "build-path": "some/main.conf",
-            "system-path": "/etc/bluetooth/some/main.conf"
-          }
-        },
-        "camera": {
-          "count": 1
-        },
         "firmware": {
           "bcs-overlay": "overlay-some-private",
           "build-targets": {
@@ -59,28 +50,19 @@
             "libpayload": "some",
             "u-boot": "some"
           },
-          "ec-ro-image": "bcs://Some_EC.1111.11.1.tbz2",
-          "main-ro-image": "bcs://Some.1111.11.1.tbz2",
+          "ec-image": "bcs://Some_EC.1111.11.1.tbz2",
+          "main-image": "bcs://Some.1111.11.1.tbz2",
           "main-rw-image": "bcs://Some_RW.1111.11.1.tbz2"
         },
         "firmware-signing": {
           "key-id": "SOME",
           "signature-id": "some"
         },
-        "hardware-properties": {
-          "has-base-accelerometer": true,
-          "has-base-gyroscope": true,
-          "has-lid-accelerometer": true,
-          "is-lid-convertible": true
-        },
         "identity": {
           "platform-name": "Some",
           "sku-id": 0,
           "smbios-name-match": "Some"
         },
-        "modem": {
-          "firmware-variant": "some"
-        },
         "name": "some",
         "oem-id": "0",
         "power": {
@@ -170,15 +152,6 @@
             "ucm-suffix": "some"
           }
         },
-        "bluetooth": {
-          "config": {
-            "build-path": "some/main.conf",
-            "system-path": "/etc/bluetooth/some/main.conf"
-          }
-        },
-        "camera": {
-          "count": 1
-        },
         "firmware": {
           "bcs-overlay": "overlay-some-private",
           "build-targets": {
@@ -188,25 +161,19 @@
             "libpayload": "some",
             "u-boot": "some"
           },
-          "ec-ro-image": "bcs://Some_EC.1111.11.1.tbz2",
-          "main-ro-image": "bcs://Some.1111.11.1.tbz2",
+          "ec-image": "bcs://Some_EC.1111.11.1.tbz2",
+          "main-image": "bcs://Some.1111.11.1.tbz2",
           "main-rw-image": "bcs://Some_RW.1111.11.1.tbz2"
         },
         "firmware-signing": {
           "key-id": "SOME",
           "signature-id": "some"
         },
-        "hardware-properties": {
-          "is-lid-convertible": false
-        },
         "identity": {
           "platform-name": "Some",
           "sku-id": 1,
           "smbios-name-match": "Some"
         },
-        "modem": {
-          "firmware-variant": "some"
-        },
         "name": "some",
         "oem-id": "0",
         "power": {
@@ -291,23 +258,23 @@
             "cr50": "another_cr50",
             "depthcharge": "another",
             "ec": "another",
-            "ec_extras": [
-              "extra1",
-              "extra2"
-            ],
             "libpayload": "another"
           },
-          "ec-ro-image": "bcs://Another_EC.1111.11.1.tbz2",
-          "main-ro-image": "bcs://Another.1111.11.1.tbz2",
-          "main-rw-image": "bcs://Another_RW.1111.11.1.tbz2"
+          "ec-image": "bcs://Another_EC.1111.11.1.tbz2",
+          "extra": [
+            "${FILESDIR}/extra"
+          ],
+          "main-image": "bcs://Another.1111.11.1.tbz2",
+          "main-rw-image": "bcs://Another_RW.1111.11.1.tbz2",
+          "tools": [
+            "${FILESDIR}/tools1",
+            "${FILESDIR}/tools2"
+          ]
         },
         "firmware-signing": {
           "key-id": "ANOTHER",
           "signature-id": "another"
         },
-        "hardware-properties": {
-          "is-lid-convertible": true
-        },
         "identity": {
           "platform-name": "Another",
           "smbios-name-match": "Another"
@@ -366,16 +333,6 @@
             }
           ]
         },
-        "bluetooth": {
-          "config": {
-            "build-path": "some/main.conf",
-            "system-path": "/etc/bluetooth/some/main.conf"
-          }
-        },
-        "brand-code": "WLBA",
-        "camera": {
-          "count": 1
-        },
         "firmware": {
           "bcs-overlay": "overlay-some-private",
           "build-targets": {
@@ -385,8 +342,8 @@
             "libpayload": "some",
             "u-boot": "some"
           },
-          "ec-ro-image": "bcs://Some_EC.1111.11.1.tbz2",
-          "main-ro-image": "bcs://Some.1111.11.1.tbz2",
+          "ec-image": "bcs://Some_EC.1111.11.1.tbz2",
+          "main-image": "bcs://Some.1111.11.1.tbz2",
           "main-rw-image": "bcs://Some_RW.1111.11.1.tbz2"
         },
         "firmware-signing": {
@@ -394,9 +351,6 @@
           "sig-id-in-customization-id": true,
           "signature-id": "whitelabel-whitelabel1"
         },
-        "hardware-properties": {
-          "is-lid-convertible": false
-        },
         "identity": {
           "platform-name": "Some",
           "sku-id": 8,
@@ -455,16 +409,6 @@
             }
           ]
         },
-        "bluetooth": {
-          "config": {
-            "build-path": "some/main.conf",
-            "system-path": "/etc/bluetooth/some/main.conf"
-          }
-        },
-        "brand-code": "WLBA",
-        "camera": {
-          "count": 1
-        },
         "firmware": {
           "bcs-overlay": "overlay-some-private",
           "build-targets": {
@@ -474,8 +418,8 @@
             "libpayload": "some",
             "u-boot": "some"
           },
-          "ec-ro-image": "bcs://Some_EC.1111.11.1.tbz2",
-          "main-ro-image": "bcs://Some.1111.11.1.tbz2",
+          "ec-image": "bcs://Some_EC.1111.11.1.tbz2",
+          "main-image": "bcs://Some.1111.11.1.tbz2",
           "main-rw-image": "bcs://Some_RW.1111.11.1.tbz2"
         },
         "firmware-signing": {
@@ -483,9 +427,6 @@
           "sig-id-in-customization-id": true,
           "signature-id": "whitelabel-whitelabel1"
         },
-        "hardware-properties": {
-          "is-lid-convertible": false
-        },
         "identity": {
           "platform-name": "Some",
           "sku-id": 9,
@@ -543,16 +484,6 @@
             }
           ]
         },
-        "bluetooth": {
-          "config": {
-            "build-path": "some/main.conf",
-            "system-path": "/etc/bluetooth/some/main.conf"
-          }
-        },
-        "brand-code": "WLBB",
-        "camera": {
-          "count": 1
-        },
         "firmware": {
           "bcs-overlay": "overlay-some-private",
           "build-targets": {
@@ -562,8 +493,8 @@
             "libpayload": "some",
             "u-boot": "some"
           },
-          "ec-ro-image": "bcs://Some_EC.1111.11.1.tbz2",
-          "main-ro-image": "bcs://Some.1111.11.1.tbz2",
+          "ec-image": "bcs://Some_EC.1111.11.1.tbz2",
+          "main-image": "bcs://Some.1111.11.1.tbz2",
           "main-rw-image": "bcs://Some_RW.1111.11.1.tbz2"
         },
         "firmware-signing": {
@@ -571,9 +502,6 @@
           "sig-id-in-customization-id": true,
           "signature-id": "whitelabel-whitelabel2"
         },
-        "hardware-properties": {
-          "is-lid-convertible": false
-        },
         "identity": {
           "platform-name": "Some",
           "sku-id": 8,
@@ -632,16 +560,6 @@
             }
           ]
         },
-        "bluetooth": {
-          "config": {
-            "build-path": "some/main.conf",
-            "system-path": "/etc/bluetooth/some/main.conf"
-          }
-        },
-        "brand-code": "WLBB",
-        "camera": {
-          "count": 1
-        },
         "firmware": {
           "bcs-overlay": "overlay-some-private",
           "build-targets": {
@@ -651,8 +569,8 @@
             "libpayload": "some",
             "u-boot": "some"
           },
-          "ec-ro-image": "bcs://Some_EC.1111.11.1.tbz2",
-          "main-ro-image": "bcs://Some.1111.11.1.tbz2",
+          "ec-image": "bcs://Some_EC.1111.11.1.tbz2",
+          "main-image": "bcs://Some.1111.11.1.tbz2",
           "main-rw-image": "bcs://Some_RW.1111.11.1.tbz2"
         },
         "firmware-signing": {
@@ -660,9 +578,6 @@
           "sig-id-in-customization-id": true,
           "signature-id": "whitelabel-whitelabel2"
         },
-        "hardware-properties": {
-          "is-lid-convertible": false
-        },
         "identity": {
           "platform-name": "Some",
           "sku-id": 9,
@@ -710,4 +625,4 @@
       }
     ]
   }
-}
+}
\ No newline at end of file
diff --git a/chromeos-config/libcros_config/test_config.json b/chromeos-config/libcros_config/test_config.json
new file mode 100644
index 0000000..56ec54b
--- /dev/null
+++ b/chromeos-config/libcros_config/test_config.json
@@ -0,0 +1,52 @@
+{
+  "models": [
+    {
+      "audio": {
+        "cras-config-dir": "/etc/cras/pyro",
+        "disable-profile": "",
+        "ucm-suffix": "pyro"
+      },
+      "brand-code": "PYRO",
+      "identity": {
+        "sku-id": 0,
+        "smbios-name-match": "Pyro"
+      },
+      "name": "pyro",
+      "powerd-prefs": "pyro",
+      "wallpaper": "default",
+      "touch": {
+        "present": "probe"
+      }
+    },
+    {
+      "audio": {
+        "cras-config-dir": "/etc/cras/basking",
+        "disable-profile": "",
+        "ucm-suffix": "basking"
+      },
+      "brand-code": "ASUN",
+      "identity": {
+        "customization-id": "BASKING",
+        "sku-id": 0,
+        "smbios-name-match": "Reef"
+      },
+      "name": "basking",
+      "powerd-prefs": "reef"
+    },
+    {
+      "audio": {
+        "cras-config-dir": "/etc/cras/electro",
+        "disable-profile": "",
+        "ucm-suffix": "electro"
+      },
+      "brand-code": "ACBB",
+      "identity": {
+        "customization-id": "ELECTRO",
+        "sku-id": 8,
+        "smbios-name-match": "Reef"
+      },
+      "name": "electro",
+      "powerd-prefs": "reef"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/chromeos-config/libcros_config/test_import.json b/chromeos-config/libcros_config/test_import.json
index bd9e3ed..f5625e1 100644
--- a/chromeos-config/libcros_config/test_import.json
+++ b/chromeos-config/libcros_config/test_import.json
@@ -20,4 +20,4 @@
       }
     ]
   }
-}
+}
\ No newline at end of file
diff --git a/chromeos-config/libcros_config/test_merge.json b/chromeos-config/libcros_config/test_merge.json
index 6b2dc13..afbbcaf 100644
--- a/chromeos-config/libcros_config/test_merge.json
+++ b/chromeos-config/libcros_config/test_merge.json
@@ -50,4 +50,4 @@
       }
     ]
   }
-}
+}
\ No newline at end of file
diff --git a/chromeos-config/mosys_migration_test.sh b/chromeos-config/mosys_migration_test.sh
index fd8fab6..df2fde6 100755
--- a/chromeos-config/mosys_migration_test.sh
+++ b/chromeos-config/mosys_migration_test.sh
@@ -1,11 +1,11 @@
 #!/bin/sh
-echo "vendor": $(mosys platform vendor)
-echo "name": $(mosys platform name)
-echo "model": $(mosys platform model)
-echo "chassis": $(mosys platform chassis)
-echo "sku": $(mosys platform sku)
-echo "brand": $(mosys platform brand)
-echo "customization": $(mosys platform customization)
-echo "signature": $(mosys platform signature)
-echo "version": $(mosys platform version)
-echo "family": $(mosys platform family)
+mosys platform vendor
+mosys platform name
+mosys platform model
+mosys platform chassis
+mosys platform sku
+mosys platform brand
+mosys platform customization
+mosys platform signature
+mosys platform version
+mosys platform family
diff --git a/chromeos-config/setup.py b/chromeos-config/setup.py
index df47a79..22f9276 100644
--- a/chromeos-config/setup.py
+++ b/chromeos-config/setup.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 # Copyright 2017 The Chromium OS Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
@@ -15,16 +14,12 @@
     author_email='sjg@chromium.org',
     url='README.md',
     packages=['cros_config_host'],
-    package_data={'cros_config_host':
-                  ['cros_config_schema.yaml', 'cros_config_test_schema.yaml',
-                   'templates/ec_config.c.jinja2',
-                   'templates/ec_config.h.jinja2']},
+    package_data={'cros_config_host': ['cros_config_schema.yaml']},
     entry_points={
         'console_scripts': [
             'cros_config_host = cros_config_host.cros_config_host:main',
             'cros_config_schema = cros_config_host.cros_config_schema:main',
-            'cros_config_test_schema = \
-                cros_config_host.cros_config_test_schema:main',
+            'validate_config = cros_config_host.validate_config:Main',
         ],
     },
     description='Access to the master configuration from the host',