You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by cc...@apache.org on 2016/10/08 19:52:56 UTC

[2/2] incubator-mynewt-newt git commit: Flash map config in bsp.yml.

Flash map config in bsp.yml.


Project: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/commit/a441cdf5
Tree: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/tree/a441cdf5
Diff: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/diff/a441cdf5

Branch: refs/heads/develop
Commit: a441cdf5f48b0b7a5dbf198813cbf9029fcd85a8
Parents: b6adacb
Author: Christopher Collins <cc...@apache.org>
Authored: Mon Oct 3 19:43:26 2016 -0700
Committer: Christopher Collins <cc...@apache.org>
Committed: Sat Oct 8 12:47:17 2016 -0700

----------------------------------------------------------------------
 newt/builder/build.go                           |   2 +-
 newt/builder/targetbuild.go                     |  40 +-
 newt/flash/flash.go                             | 401 +++++++++++++++++++
 newt/newtutil/newtutil.go                       |   5 +
 newt/pkg/bsp_package.go                         |  13 +
 newt/resolve/resolve.go                         |  17 +-
 newt/syscfg/restrict.go                         | 199 +++++++++
 newt/syscfg/syscfg.go                           | 345 ++++++++--------
 newt/sysinit/sysinit.go                         |   4 +-
 newt/vendor/mynewt.apache.org/newt/util/util.go |  15 +
 util/util.go                                    |  15 +
 11 files changed, 871 insertions(+), 185 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/a441cdf5/newt/builder/build.go
----------------------------------------------------------------------
diff --git a/newt/builder/build.go b/newt/builder/build.go
index 5d57153..445d9a2 100644
--- a/newt/builder/build.go
+++ b/newt/builder/build.go
@@ -390,7 +390,7 @@ func (b *Builder) PrepBuild() error {
 	}
 	baseCi.AddCompilerInfo(bspCi)
 
-	// All packages have access to the generated syscfg header directory.
+	// All packages have access to the generated code header directory.
 	baseCi.Cflags = append(baseCi.Cflags,
 		"-I"+GeneratedIncludeDir(b.targetPkg.Name()))
 

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/a441cdf5/newt/builder/targetbuild.go
----------------------------------------------------------------------
diff --git a/newt/builder/targetbuild.go b/newt/builder/targetbuild.go
index 288502d..ea8c0c8 100644
--- a/newt/builder/targetbuild.go
+++ b/newt/builder/targetbuild.go
@@ -130,7 +130,8 @@ func (t *TargetBuilder) ExportCfg() (resolve.CfgResolution, error) {
 		seeds = append(seeds, t.testPkg)
 	}
 
-	cfgResolution, err := resolve.ResolveCfg(seeds, t.injectedSettings)
+	cfgResolution, err := resolve.ResolveCfg(seeds, t.injectedSettings,
+		t.bspPkg.FlashMap)
 	if err != nil {
 		return cfgResolution, err
 	}
@@ -145,6 +146,11 @@ func (t *TargetBuilder) validateAndWriteCfg(
 		return util.NewNewtError(errText)
 	}
 
+	warningText := strings.TrimSpace(cfgResolution.WarningText())
+	for _, line := range strings.Split(warningText, "\n") {
+		log.Warn(line)
+	}
+
 	if err := syscfg.EnsureWritten(cfgResolution.Cfg,
 		GeneratedIncludeDir(t.target.Name())); err != nil {
 
@@ -172,7 +178,7 @@ func (t *TargetBuilder) resolvePkgs(cfgResolution resolve.CfgResolution) (
 		t.target.Package())
 }
 
-func (t *TargetBuilder) buildSysinit(
+func (t *TargetBuilder) generateSysinit(
 	cfgResolution resolve.CfgResolution) error {
 
 	loaderPkgs, appPkgs, err := t.resolvePkgs(cfgResolution)
@@ -193,12 +199,38 @@ func (t *TargetBuilder) buildSysinit(
 	return nil
 }
 
+func (t *TargetBuilder) generateFlashMap() error {
+	return t.bspPkg.FlashMap.EnsureWritten(
+		GeneratedSrcDir(t.target.Name()),
+		GeneratedIncludeDir(t.target.Name()),
+		pkg.ShortName(t.target.Package()))
+}
+
+func (t *TargetBuilder) generateCode(
+	cfgResolution resolve.CfgResolution) error {
+
+	if err := t.generateSysinit(cfgResolution); err != nil {
+		return err
+	}
+
+	if err := t.generateFlashMap(); err != nil {
+		return err
+	}
+
+	return nil
+}
+
 func (t *TargetBuilder) PrepBuild() error {
 	cfgResolution, err := t.ExportCfg()
 	if err != nil {
 		return err
 	}
 
+	flashErrText := t.bspPkg.FlashMap.ErrorText()
+	if flashErrText != "" {
+		return util.NewNewtError(flashErrText)
+	}
+
 	if err := t.validateAndWriteCfg(cfgResolution); err != nil {
 		return err
 	}
@@ -242,7 +274,7 @@ func (t *TargetBuilder) PrepBuild() error {
 
 	t.AppList = project.ResetDeps(nil)
 
-	if err := t.buildSysinit(cfgResolution); err != nil {
+	if err := t.generateCode(cfgResolution); err != nil {
 		return err
 	}
 
@@ -502,7 +534,7 @@ func (t *TargetBuilder) Test() error {
 		return err
 	}
 
-	if err := t.buildSysinit(cfgResolution); err != nil {
+	if err := t.generateCode(cfgResolution); err != nil {
 		return err
 	}
 

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/a441cdf5/newt/flash/flash.go
----------------------------------------------------------------------
diff --git a/newt/flash/flash.go b/newt/flash/flash.go
new file mode 100644
index 0000000..7538936
--- /dev/null
+++ b/newt/flash/flash.go
@@ -0,0 +1,401 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package flash
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"sort"
+	"strings"
+
+	log "github.com/Sirupsen/logrus"
+	"github.com/spf13/cast"
+
+	"mynewt.apache.org/newt/newt/newtutil"
+	"mynewt.apache.org/newt/util"
+)
+
+var SYSTEM_AREA_NAME_ID_MAP = map[string]int{
+	"FLASH_AREA_BOOTLOADER":    0,
+	"FLASH_AREA_IMAGE_0":       1,
+	"FLASH_AREA_IMAGE_1":       2,
+	"FLASH_AREA_IMAGE_SCRATCH": 3,
+	"FLASH_AREA_META":          4,
+}
+
+const AREA_USER_ID_MIN = 16
+
+const HEADER_PATH = "sysflash/sysflash.h"
+const C_VAR_NAME = "sysflash_map_dflt"
+const C_VAR_COMMENT = `/**
+ * This flash map definition is used for two purposes:
+ * 1. To locate the meta area, which contains the true flash map definition.
+ * 2. As a fallback in case the meta area cannot be read from flash.
+ */
+`
+
+type FlashArea struct {
+	Name   string
+	Id     int
+	Device int
+	Offset int
+	Size   int
+}
+
+type FlashMap struct {
+	Areas       map[string]FlashArea
+	Overlaps    [][]FlashArea
+	IdConflicts [][]FlashArea
+}
+
+func newFlashMap() FlashMap {
+	return FlashMap{
+		Areas:    map[string]FlashArea{},
+		Overlaps: [][]FlashArea{},
+	}
+}
+
+func flashAreaErr(areaName string, format string, args ...interface{}) error {
+	return util.NewNewtError(
+		"failure while parsing flash area \"" + areaName + "\": " +
+			fmt.Sprintf(format, args...))
+}
+
+func parseSize(val string) (int, error) {
+	lower := strings.ToLower(val)
+
+	multiplier := 1
+	if strings.HasSuffix(lower, "kb") {
+		multiplier = 1024
+		lower = strings.TrimSuffix(lower, "kb")
+	}
+
+	num, err := util.AtoiNoOct(lower)
+	if err != nil {
+		return 0, err
+	}
+
+	return num * multiplier, nil
+}
+
+func parseFlashArea(
+	name string, ymlFields map[string]interface{}) (FlashArea, error) {
+
+	area := FlashArea{
+		Name: name,
+	}
+
+	idPresent := false
+	devicePresent := false
+	offsetPresent := false
+	sizePresent := false
+
+	var isSystem bool
+	area.Id, isSystem = SYSTEM_AREA_NAME_ID_MAP[name]
+
+	var err error
+
+	fields := cast.ToStringMapString(ymlFields)
+	for k, v := range fields {
+		switch k {
+		case "user_id":
+			if isSystem {
+				return area, flashAreaErr(name,
+					"system areas cannot specify a user ID")
+			}
+			userId, err := util.AtoiNoOct(v)
+			if err != nil {
+				return area, flashAreaErr(name, "invalid id: %s", v)
+			}
+			area.Id = userId + AREA_USER_ID_MIN
+			idPresent = true
+
+		case "device":
+			area.Device, err = util.AtoiNoOct(v)
+			if err != nil {
+				return area, flashAreaErr(name, "invalid device: %s", v)
+			}
+			devicePresent = true
+
+		case "offset":
+			area.Offset, err = util.AtoiNoOct(v)
+			if err != nil {
+				return area, flashAreaErr(name, "invalid offset: %s", v)
+			}
+			offsetPresent = true
+
+		case "size":
+			area.Size, err = parseSize(v)
+			if err != nil {
+				return area, flashAreaErr(name, err.Error())
+			}
+			sizePresent = true
+
+		default:
+			util.StatusMessage(util.VERBOSITY_QUIET,
+				"Warning: flash area \"%s\" contains unrecognized field: %s",
+				name, k)
+		}
+	}
+
+	if !isSystem && !idPresent {
+		return area, flashAreaErr(name, "required field \"user_id\" missing")
+	}
+	if !devicePresent {
+		return area, flashAreaErr(name, "required field \"device\" missing")
+	}
+	if !offsetPresent {
+		return area, flashAreaErr(name, "required field \"offset\" missing")
+	}
+	if !sizePresent {
+		return area, flashAreaErr(name, "required field \"size\" missing")
+	}
+
+	return area, nil
+}
+
+func (flashMap FlashMap) unsortedAreas() []FlashArea {
+	areas := make([]FlashArea, 0, len(flashMap.Areas))
+	for _, area := range flashMap.Areas {
+		areas = append(areas, area)
+	}
+
+	return areas
+}
+
+func (flashMap FlashMap) sortedAreas() []FlashArea {
+	idMap := make(map[int]FlashArea, len(flashMap.Areas))
+	ids := make([]int, 0, len(flashMap.Areas))
+	for _, area := range flashMap.Areas {
+		idMap[area.Id] = area
+		ids = append(ids, area.Id)
+	}
+	sort.Ints(ids)
+
+	areas := make([]FlashArea, len(ids))
+	for i, id := range ids {
+		areas[i] = idMap[id]
+	}
+
+	return areas
+}
+
+func areasDistinct(a FlashArea, b FlashArea) bool {
+	var lo FlashArea
+	var hi FlashArea
+
+	if a.Offset < b.Offset {
+		lo = a
+		hi = b
+	} else {
+		lo = b
+		hi = a
+	}
+
+	return lo.Device != hi.Device || lo.Offset+lo.Size <= hi.Offset
+}
+
+func (flashMap *FlashMap) detectOverlaps() {
+	flashMap.Overlaps = [][]FlashArea{}
+
+	// Convert the map to a slice.
+	areas := flashMap.unsortedAreas()
+
+	for i := 0; i < len(areas)-1; i++ {
+		iarea := areas[i]
+		for j := i + 1; j < len(areas); j++ {
+			jarea := areas[j]
+
+			if !areasDistinct(iarea, jarea) {
+				flashMap.Overlaps = append(
+					flashMap.Overlaps, []FlashArea{iarea, jarea})
+			}
+
+			if iarea.Id == jarea.Id {
+				flashMap.IdConflicts = append(
+					flashMap.IdConflicts, []FlashArea{iarea, jarea})
+			}
+		}
+	}
+}
+
+func (flashMap FlashMap) ErrorText() string {
+	str := ""
+
+	if len(flashMap.IdConflicts) > 0 {
+		str += "Conflicting flash area IDs detected:\n"
+
+		for _, pair := range flashMap.IdConflicts {
+			str += fmt.Sprintf("    (%d) %s =/= %s\n",
+				pair[0].Id-AREA_USER_ID_MIN, pair[0].Name, pair[1].Name)
+		}
+	}
+
+	if len(flashMap.Overlaps) > 0 {
+		str += "Overlapping flash areas detected:\n"
+
+		for _, pair := range flashMap.Overlaps {
+			str += fmt.Sprintf("    %s =/= %s\n", pair[0].Name, pair[1].Name)
+		}
+	}
+
+	return str
+}
+
+func Read(ymlFlashMap map[string]interface{}) (FlashMap, error) {
+	flashMap := newFlashMap()
+
+	ymlAreas := ymlFlashMap["areas"]
+	if ymlAreas == nil {
+		return flashMap, util.NewNewtError(
+			"\"areas\" mapping missing from flash map definition")
+	}
+
+	areaMap := cast.ToStringMap(ymlAreas)
+	for k, v := range areaMap {
+		if _, ok := flashMap.Areas[k]; ok {
+			return flashMap, flashAreaErr(k, "name conflict")
+		}
+
+		ymlArea := cast.ToStringMap(v)
+		area, err := parseFlashArea(k, ymlArea)
+		if err != nil {
+			return flashMap, flashAreaErr(k, err.Error())
+		}
+
+		flashMap.Areas[k] = area
+	}
+
+	flashMap.detectOverlaps()
+
+	return flashMap, nil
+}
+
+func (flashMap FlashMap) varDecl() string {
+	return fmt.Sprintf("const struct flash_area %s[%d]", C_VAR_NAME,
+		len(flashMap.Areas))
+}
+
+func (area FlashArea) writeHeader(w io.Writer) {
+	fmt.Fprintf(w, "#define %-40s %d\n", area.Name, area.Id)
+}
+
+func (flashMap FlashMap) writeHeader(w io.Writer) {
+	fmt.Fprintf(w, newtutil.GeneratedPreamble())
+
+	fmt.Fprintf(w, "#ifndef H_MYNEWT_SYSFLASH_\n")
+	fmt.Fprintf(w, "#define H_MYNEWT_SYSFLASH_\n")
+	fmt.Fprintf(w, "\n")
+	fmt.Fprintf(w, "#include \"flash_map/flash_map.h\"\n")
+	fmt.Fprintf(w, "\n")
+	fmt.Fprintf(w, "%s", C_VAR_COMMENT)
+	fmt.Fprintf(w, "extern %s;\n", flashMap.varDecl())
+	fmt.Fprintf(w, "\n")
+
+	for _, area := range flashMap.sortedAreas() {
+		area.writeHeader(w)
+	}
+
+	fmt.Fprintf(w, "\n#endif\n")
+}
+
+func sizeComment(size int) string {
+	if size%1024 != 0 {
+		return ""
+	}
+
+	return fmt.Sprintf(" /* %d kB */", size/1024)
+}
+
+func (area FlashArea) writeSrc(w io.Writer) {
+	fmt.Fprintf(w, "    /* %s */\n", area.Name)
+	fmt.Fprintf(w, "    {\n")
+	fmt.Fprintf(w, "        .fa_id = %d,\n", area.Id)
+	fmt.Fprintf(w, "        .fa_device_id = %d,\n", area.Device)
+	fmt.Fprintf(w, "        .fa_off = 0x%08x,\n", area.Offset)
+	fmt.Fprintf(w, "        .fa_size = %d,%s\n", area.Size,
+		sizeComment(area.Size))
+	fmt.Fprintf(w, "    },\n")
+}
+
+func (flashMap FlashMap) writeSrc(w io.Writer) {
+	fmt.Fprintf(w, newtutil.GeneratedPreamble())
+
+	fmt.Fprintf(w, "#include \"%s\"\n", HEADER_PATH)
+	fmt.Fprintf(w, "\n")
+	fmt.Fprintf(w, "%s", C_VAR_COMMENT)
+	fmt.Fprintf(w, "%s = {", flashMap.varDecl())
+
+	for _, area := range flashMap.sortedAreas() {
+		fmt.Fprintf(w, "\n")
+		area.writeSrc(w)
+	}
+
+	fmt.Fprintf(w, "};\n")
+}
+
+func (flashMap FlashMap) ensureWrittenGen(path string, contents []byte) error {
+	writeReqd, err := util.FileContentsChanged(path, contents)
+	if err != nil {
+		return err
+	}
+	if !writeReqd {
+		log.Debugf("flash map unchanged; not writing file (%s).", path)
+		return nil
+	}
+
+	log.Debugf("flash map changed; writing file (%s).", path)
+
+	if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
+		return util.NewNewtError(err.Error())
+	}
+
+	if err := ioutil.WriteFile(path, contents, 0644); err != nil {
+		return util.NewNewtError(err.Error())
+	}
+
+	return nil
+}
+
+func (flashMap FlashMap) EnsureWritten(
+	srcDir string, includeDir string, targetName string) error {
+
+	buf := bytes.Buffer{}
+	flashMap.writeSrc(&buf)
+	if err := flashMap.ensureWrittenGen(
+		fmt.Sprintf("%s/%s-sysflash.c", srcDir, targetName),
+		buf.Bytes()); err != nil {
+
+		return err
+	}
+
+	buf = bytes.Buffer{}
+	flashMap.writeHeader(&buf)
+	if err := flashMap.ensureWrittenGen(
+		includeDir+"/"+HEADER_PATH, buf.Bytes()); err != nil {
+		return err
+	}
+
+	return nil
+}

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/a441cdf5/newt/newtutil/newtutil.go
----------------------------------------------------------------------
diff --git a/newt/newtutil/newtutil.go b/newt/newtutil/newtutil.go
index 0056201..2f52052 100644
--- a/newt/newtutil/newtutil.go
+++ b/newt/newtutil/newtutil.go
@@ -228,3 +228,8 @@ func CopyFile(dst string, src string) error {
 	}
 	return nil
 }
+
+func GeneratedPreamble() string {
+	return fmt.Sprintf("/**\n * This file was generated by %s\n */\n\n",
+		NewtVersionStr)
+}

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/a441cdf5/newt/pkg/bsp_package.go
----------------------------------------------------------------------
diff --git a/newt/pkg/bsp_package.go b/newt/pkg/bsp_package.go
index 51f6a86..89e9452 100644
--- a/newt/pkg/bsp_package.go
+++ b/newt/pkg/bsp_package.go
@@ -22,6 +22,7 @@ package pkg
 import (
 	"strings"
 
+	"mynewt.apache.org/newt/newt/flash"
 	"mynewt.apache.org/newt/newt/newtutil"
 	"mynewt.apache.org/newt/util"
 	"mynewt.apache.org/newt/viper"
@@ -37,6 +38,7 @@ type BspPackage struct {
 	Part2LinkerScript string /* script to link app to second partition */
 	DownloadScript    string
 	DebugScript       string
+	FlashMap          flash.FlashMap
 	BspV              *viper.Viper
 }
 
@@ -71,6 +73,17 @@ func (bsp *BspPackage) Reload(features map[string]bool) error {
 			"(bsp.arch)")
 	}
 
+	ymlFlashMap := newtutil.GetStringMapFeatures(bsp.BspV, features,
+		"bsp.flash_map")
+	if ymlFlashMap == nil {
+		return util.NewNewtError("BSP does not specify a flash map " +
+			"(bsp.flash_map)")
+	}
+	bsp.FlashMap, err = flash.Read(ymlFlashMap)
+	if err != nil {
+		return err
+	}
+
 	return nil
 }
 

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/a441cdf5/newt/resolve/resolve.go
----------------------------------------------------------------------
diff --git a/newt/resolve/resolve.go b/newt/resolve/resolve.go
index fc2f34c..b96e954 100644
--- a/newt/resolve/resolve.go
+++ b/newt/resolve/resolve.go
@@ -26,6 +26,7 @@ import (
 
 	log "github.com/Sirupsen/logrus"
 
+	"mynewt.apache.org/newt/newt/flash"
 	"mynewt.apache.org/newt/newt/newtutil"
 	"mynewt.apache.org/newt/newt/pkg"
 	"mynewt.apache.org/newt/newt/project"
@@ -37,6 +38,7 @@ type Resolver struct {
 	apis             map[string]*ResolvePackage
 	pkgMap           map[*pkg.LocalPackage]*ResolvePackage
 	injectedSettings map[string]string
+	flashMap         flash.FlashMap
 	cfg              syscfg.Cfg
 }
 
@@ -208,8 +210,8 @@ func (r *Resolver) loadDepsForPkg(rpkg *ResolvePackage) (bool, error) {
 		lpkg, ok := proj.ResolveDependency(newDep).(*pkg.LocalPackage)
 		if !ok {
 			return false,
-				util.NewNewtError("Could not resolve package dependency " +
-					newDep.String())
+				util.FmtNewtError("Could not resolve package dependency: "+
+					"%s; depender: %s", newDep.String(), rpkg.Name())
 		}
 
 		if r.pkgMap[lpkg] == nil {
@@ -277,7 +279,8 @@ func (r *Resolver) reloadCfg() (bool, error) {
 	// required for reloading syscfg, as features may unlock additional
 	// settings.
 	features := r.cfg.Features()
-	cfg, err := syscfg.Read(lpkgs, apis, r.injectedSettings, features)
+	cfg, err := syscfg.Read(lpkgs, apis, r.injectedSettings, features,
+		r.flashMap)
 	if err != nil {
 		return false, err
 	}
@@ -464,11 +467,13 @@ func ResolveSplitPkgs(cfgResolution CfgResolution,
 }
 
 func ResolveCfg(seedPkgs []*pkg.LocalPackage,
-	injectedSettings map[string]string) (CfgResolution, error) {
+	injectedSettings map[string]string,
+	flashMap flash.FlashMap) (CfgResolution, error) {
 
 	resolution := newCfgResolution()
 
 	r := newResolver()
+	r.flashMap = flashMap
 	if injectedSettings == nil {
 		injectedSettings = map[string]string{}
 	}
@@ -538,3 +543,7 @@ func (cfgResolution *CfgResolution) ErrorText() string {
 
 	return strings.TrimSpace(str)
 }
+
+func (cfgResolution *CfgResolution) WarningText() string {
+	return cfgResolution.Cfg.WarningText()
+}

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/a441cdf5/newt/syscfg/restrict.go
----------------------------------------------------------------------
diff --git a/newt/syscfg/restrict.go b/newt/syscfg/restrict.go
new file mode 100644
index 0000000..5f9c0f7
--- /dev/null
+++ b/newt/syscfg/restrict.go
@@ -0,0 +1,199 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package syscfg
+
+import (
+	"fmt"
+	"strings"
+
+	"mynewt.apache.org/newt/util"
+)
+
+type CfgRestrictionCode int
+
+const (
+	CFG_RESTRICTION_CODE_NOTNULL = iota
+	CFG_RESTRICTION_CODE_EXPR
+)
+
+var cfgRestrictionNameCodeMap = map[string]CfgRestrictionCode{
+	"$notnull": CFG_RESTRICTION_CODE_NOTNULL,
+}
+
+type CfgRestrictionExpr struct {
+	ReqSetting string
+	ReqVal     bool
+	BaseVal    bool
+}
+type CfgRestriction struct {
+	BaseSetting string
+	Code        CfgRestrictionCode
+
+	// Only used if Code is CFG_RESTRICTION_CODE_EXPR
+	Expr CfgRestrictionExpr
+}
+
+func parseRestrictionExprConsequent(field string) (string, bool) {
+	var val bool
+	var name string
+
+	if strings.HasPrefix(field, "!") {
+		val = false
+		name = strings.TrimPrefix(field, "!")
+	} else {
+		val = true
+		name = field
+	}
+
+	return name, val
+}
+
+// Parses a restriction value.
+//
+// Currently, two forms of restrictions are supported:
+// 1. "$notnull"
+// 2. expression
+//
+// The "$notnull" string indicates that the setting must be set to something
+// other than the empty string.
+//
+// An expression string indicates dependencies on other settings.  It would be
+// better to have a real expression parser.  For now, only very simple
+// expressions are supported.  A restriction expression must be of the
+// following form:
+//     [!]<req-setting> [if <base-val>]
+//
+// All setting values are interpreted as booleans.  If a setting is "0", "",
+// or undefined, it is false; otherwise it is true.
+//
+// Examples:
+//     # Can't enable this setting unless LOG_FCB is enabled.
+//	   pkg.restrictions:
+//         LOG_FCB
+//
+//     # Can't enable this setting unless LOG_FCB is disabled.
+//	   pkg.restrictions:
+//         !LOG_FCB
+//
+//     # Can't disable this setting unless LOG_FCB is enabled.
+//	   pkg.restrictions:
+//         LOG_FCB if 0
+func readRestrictionExpr(text string) (CfgRestrictionExpr, error) {
+	e := CfgRestrictionExpr{}
+
+	fields := strings.Fields(text)
+	switch len(fields) {
+	case 1:
+		e.ReqSetting, e.ReqVal = parseRestrictionExprConsequent(fields[0])
+		e.BaseVal = true
+
+	case 3:
+		if fields[1] != "if" {
+			return e, util.FmtNewtError("invalid restriction: %s", text)
+		}
+		e.ReqSetting, e.ReqVal = parseRestrictionExprConsequent(fields[0])
+		e.BaseVal = ValueIsTrue(fields[2])
+
+	default:
+		return e, util.FmtNewtError("invalid restriction: %s", text)
+	}
+
+	return e, nil
+}
+
+func readRestriction(baseSetting string, text string) (CfgRestriction, error) {
+	r := CfgRestriction{
+		BaseSetting: baseSetting,
+	}
+
+	var ok bool
+	if r.Code, ok = cfgRestrictionNameCodeMap[text]; !ok {
+		// If the restriction text isn't a defined string, parse it as an
+		// expression.
+		r.Code = CFG_RESTRICTION_CODE_EXPR
+
+		var err error
+		if r.Expr, err = readRestrictionExpr(text); err != nil {
+			return r, err
+		}
+	}
+
+	return r, nil
+}
+
+func (cfg *Cfg) violationText(entry CfgEntry, r CfgRestriction) string {
+	if r.Code == CFG_RESTRICTION_CODE_NOTNULL {
+		return entry.Name + " must not be null"
+	}
+
+	str := fmt.Sprintf("%s=%s ", entry.Name, entry.Value)
+	if r.Expr.ReqVal {
+		str += fmt.Sprintf("requires %s be set", r.Expr.ReqSetting)
+	} else {
+		str += fmt.Sprintf("requires %s not be set", r.Expr.ReqSetting)
+	}
+
+	str += fmt.Sprintf(", but %s", r.Expr.ReqSetting)
+	reqEntry, ok := cfg.Settings[r.Expr.ReqSetting]
+	if !ok {
+		str += "undefined"
+	} else {
+		str += fmt.Sprintf("=%s", reqEntry.Value)
+	}
+
+	return str
+}
+
+func (r *CfgRestriction) relevantSettingNames() []string {
+	switch r.Code {
+	case CFG_RESTRICTION_CODE_NOTNULL:
+		return []string{r.BaseSetting}
+
+	case CFG_RESTRICTION_CODE_EXPR:
+		return []string{r.BaseSetting, r.Expr.ReqSetting}
+
+	default:
+		panic("Invalid restriction code: " + string(r.Code))
+	}
+}
+
+func (cfg *Cfg) restrictionMet(r CfgRestriction) bool {
+	baseEntry := cfg.Settings[r.BaseSetting]
+	baseVal := baseEntry.IsTrue()
+
+	switch r.Code {
+	case CFG_RESTRICTION_CODE_NOTNULL:
+		return baseEntry.Value != ""
+
+	case CFG_RESTRICTION_CODE_EXPR:
+		if baseVal != r.Expr.BaseVal {
+			// Restriction does not apply.
+			return true
+		}
+
+		reqEntry, ok := cfg.Settings[r.Expr.ReqSetting]
+		reqVal := ok && reqEntry.IsTrue()
+
+		return reqVal == r.Expr.ReqVal
+
+	default:
+		panic("Invalid restriction code: " + string(r.Code))
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/a441cdf5/newt/syscfg/syscfg.go
----------------------------------------------------------------------
diff --git a/newt/syscfg/syscfg.go b/newt/syscfg/syscfg.go
index 14dbaf9..7b42c59 100644
--- a/newt/syscfg/syscfg.go
+++ b/newt/syscfg/syscfg.go
@@ -33,14 +33,14 @@ import (
 	log "github.com/Sirupsen/logrus"
 	"github.com/spf13/cast"
 
+	"mynewt.apache.org/newt/newt/flash"
 	"mynewt.apache.org/newt/newt/interfaces"
 	"mynewt.apache.org/newt/newt/newtutil"
 	"mynewt.apache.org/newt/newt/pkg"
 	"mynewt.apache.org/newt/util"
 )
 
-const SYSCFG_INCLUDE_SUBDIR = "syscfg"
-const SYSCFG_HEADER_FILENAME = "syscfg.h"
+const HEADER_PATH = "syscfg/syscfg.h"
 
 const SYSCFG_PREFIX_SETTING = "MYNEWT_VAL_"
 
@@ -50,6 +50,7 @@ const (
 	CFG_SETTING_TYPE_RAW CfgSettingType = iota
 	CFG_SETTING_TYPE_TASK_PRIO
 	CFG_SETTING_TYPE_INTERRUPT_PRIO
+	CFG_SETTING_TYPE_FLASH_OWNER
 )
 
 const SYSCFG_PRIO_ANY = "any"
@@ -65,6 +66,7 @@ var cfgSettingNameTypeMap = map[string]CfgSettingType{
 	"raw":                CFG_SETTING_TYPE_RAW,
 	"task_priority":      CFG_SETTING_TYPE_TASK_PRIO,
 	"interrupt_priority": CFG_SETTING_TYPE_INTERRUPT_PRIO,
+	"flash_owner":        CFG_SETTING_TYPE_FLASH_OWNER,
 }
 
 type CfgPoint struct {
@@ -72,13 +74,6 @@ type CfgPoint struct {
 	Source *pkg.LocalPackage
 }
 
-type CfgRestriction struct {
-	ReqSetting  string
-	ReqVal      bool
-	BaseSetting string
-	BaseVal     bool
-}
-
 type CfgEntry struct {
 	Name         string
 	Value        string
@@ -94,6 +89,18 @@ type CfgLateral struct {
 	SettingName string
 }
 
+type CfgFlashConflictCode int
+
+const (
+	CFG_FLASH_CONFLICT_CODE_BAD_NAME CfgFlashConflictCode = iota
+	CFG_FLASH_CONFLICT_CODE_NOT_UNIQUE
+)
+
+type CfgFlashConflict struct {
+	SettingNames []string
+	Code         CfgFlashConflictCode
+}
+
 type Cfg struct {
 	Settings map[string]CfgEntry
 
@@ -110,23 +117,21 @@ type Cfg struct {
 
 	// Attempted override by bottom-priority packages (libraries).
 	Laterals []CfgLateral
+
+	FlashConflicts []CfgFlashConflict
 }
 
 func NewCfg() Cfg {
 	return Cfg{
-		Settings:    map[string]CfgEntry{},
-		Orphans:     map[string][]CfgPoint{},
-		Ambiguities: map[string][]CfgPoint{},
-		Violations:  map[string][]CfgRestriction{},
-		Laterals:    []CfgLateral{},
+		Settings:       map[string]CfgEntry{},
+		Orphans:        map[string][]CfgPoint{},
+		Ambiguities:    map[string][]CfgPoint{},
+		Violations:     map[string][]CfgRestriction{},
+		Laterals:       []CfgLateral{},
+		FlashConflicts: []CfgFlashConflict{},
 	}
 }
 
-func WritePreamble(w io.Writer) {
-	fmt.Fprintf(w, "/**\n * This file was generated by %s\n */\n\n",
-		newtutil.NewtVersionStr)
-}
-
 func ValueIsTrue(val string) bool {
 	if val == "" {
 		return false
@@ -259,28 +264,6 @@ func (entry *CfgEntry) ambiguityText() string {
 	return str
 }
 
-func (cfg *Cfg) violationText(entry CfgEntry, r CfgRestriction) string {
-	str := fmt.Sprintf("%s=%s ", entry.Name, entry.Value)
-
-	reqVal := ""
-	if r.ReqVal {
-		reqVal = "1"
-	} else {
-		reqVal = "0"
-	}
-
-	str += fmt.Sprintf("requires %s=%s, but %s", r.ReqSetting, reqVal,
-		r.ReqSetting)
-	reqEntry, ok := cfg.Settings[r.ReqSetting]
-	if !ok {
-		str += "undefined"
-	} else {
-		str += fmt.Sprintf("=%s", reqEntry.Value)
-	}
-
-	return str
-}
-
 func FeatureToCflag(featureName string) string {
 	return fmt.Sprintf("-D%s=1", settingName(featureName))
 }
@@ -289,68 +272,6 @@ func stringValue(val interface{}) string {
 	return strings.TrimSpace(cast.ToString(val))
 }
 
-func parseRestrictionConsequent(field string) (string, bool) {
-	var val bool
-	var name string
-
-	if strings.HasPrefix(field, "!") {
-		val = false
-		name = strings.TrimPrefix(field, "!")
-	} else {
-		val = true
-		name = field
-	}
-
-	return name, val
-}
-
-// Parses a restriction expression.
-//
-// It would be better to have a real expression parser.  For now, only very
-// simple expressions are supported.  A restriction expression must be of the
-// following form:
-//     [!]<req-setting> [if <base-val>]
-//
-// All setting values are interpreted as booleans.  If a setting is "0", "",
-// or undefined, it is false; otherwise it is true.
-//
-// Examples:
-//     # Can't enable this setting unless LOG_FCB is enabled.
-//	   pkg.restrictions:
-//         LOG_FCB
-//
-//     # Can't enable this setting unless LOG_FCB is disabled.
-//	   pkg.restrictions:
-//         !LOG_FCB
-//
-//     # Can't disable this setting unless LOG_FCB is enabled.
-//	   pkg.restrictions:
-//         LOG_FCB if 0
-func readRestriction(baseSetting string, text string) (CfgRestriction, error) {
-	r := CfgRestriction{
-		BaseSetting: baseSetting,
-	}
-
-	fields := strings.Fields(text)
-	switch len(fields) {
-	case 1:
-		r.ReqSetting, r.ReqVal = parseRestrictionConsequent(fields[0])
-		r.BaseVal = true
-
-	case 3:
-		if fields[1] != "if" {
-			return r, util.FmtNewtError("invalid restriction: %s", text)
-		}
-		r.ReqSetting, r.ReqVal = parseRestrictionConsequent(fields[0])
-		r.BaseVal = ValueIsTrue(fields[2])
-
-	default:
-		return r, util.FmtNewtError("invalid restriction: %s", text)
-	}
-
-	return r, nil
-}
-
 func readSetting(name string, lpkg *pkg.LocalPackage,
 	vals map[interface{}]interface{}) (CfgEntry, error) {
 
@@ -469,42 +390,18 @@ func (cfg *Cfg) Log() {
 		log.Debugf("    %s=%s %s", k, entry.Value,
 			historyToString(entry.History))
 	}
-
-	keys = make([]string, len(cfg.Orphans))
-	i = 0
-	for k, _ := range cfg.Orphans {
-		keys[i] = k
-		i++
-	}
-	sort.Strings(keys)
-
-	for _, k := range keys {
-		str := fmt.Sprintf("ignoring override of undefined setting %s [", k)
-		for i, p := range cfg.Orphans[k] {
-			if i != 0 {
-				str += ", "
-			}
-			str += fmt.Sprintf("%s:%s", p.Name(), p.Value)
-		}
-		str += "]"
-
-		log.Warnf(str)
-	}
 }
 
-func (cfg *Cfg) restrictionMet(r CfgRestriction) bool {
-	baseEntry := cfg.Settings[r.BaseSetting]
-	baseVal := baseEntry.IsTrue()
+func (cfg *Cfg) settingsOfType(typ CfgSettingType) []CfgEntry {
+	entries := []CfgEntry{}
 
-	if baseVal != r.BaseVal {
-		// Restriction does not apply.
-		return true
+	for _, entry := range cfg.Settings {
+		if entry.SettingType == typ {
+			entries = append(entries, entry)
+		}
 	}
 
-	reqEntry, ok := cfg.Settings[r.ReqSetting]
-	reqVal := ok && reqEntry.IsTrue()
-
-	return reqVal == r.ReqVal
+	return entries
 }
 
 func (cfg *Cfg) detectViolations() {
@@ -522,19 +419,103 @@ func (cfg *Cfg) detectViolations() {
 	}
 }
 
+func (cfg *Cfg) detectFlashConflicts(flashMap flash.FlashMap) {
+	entries := cfg.settingsOfType(CFG_SETTING_TYPE_FLASH_OWNER)
+
+	areaEntryMap := map[string][]CfgEntry{}
+
+	for _, entry := range entries {
+		if entry.Value != "" {
+			area, ok := flashMap.Areas[entry.Value]
+			if !ok {
+				conflict := CfgFlashConflict{
+					SettingNames: []string{entry.Name},
+					Code:         CFG_FLASH_CONFLICT_CODE_BAD_NAME,
+				}
+				cfg.FlashConflicts = append(cfg.FlashConflicts, conflict)
+			} else {
+				areaEntryMap[area.Name] =
+					append(areaEntryMap[area.Name], entry)
+			}
+		}
+	}
+
+	// Settings with type flash_owner must have unique values.
+	for _, entries := range areaEntryMap {
+		if len(entries) > 1 {
+			conflict := CfgFlashConflict{
+				SettingNames: []string{},
+				Code:         CFG_FLASH_CONFLICT_CODE_NOT_UNIQUE,
+			}
+
+			for _, entry := range entries {
+				conflict.SettingNames =
+					append(conflict.SettingNames, entry.Name)
+			}
+
+			cfg.FlashConflicts = append(cfg.FlashConflicts, conflict)
+		}
+	}
+}
+
+func (cfg *Cfg) flashConflictErrorText(conflict CfgFlashConflict) string {
+	entry := cfg.Settings[conflict.SettingNames[0]]
+
+	switch conflict.Code {
+	case CFG_FLASH_CONFLICT_CODE_BAD_NAME:
+		return fmt.Sprintf("Setting %s specifies unknown flash area: %s\n",
+			entry.Name, entry.Value)
+
+	case CFG_FLASH_CONFLICT_CODE_NOT_UNIQUE:
+		return fmt.Sprintf(
+			"Multiple flash_owner settings specify the same flash area\n"+
+				"          settings: %s\n"+
+				"        flash area: %s\n",
+			strings.Join(conflict.SettingNames, ", "),
+			entry.Value)
+
+	default:
+		panic("Invalid flash conflict code: " + string(conflict.Code))
+	}
+}
+
+func historyTextOnce(settingName string, points []CfgPoint) string {
+	return fmt.Sprintf("    %s: %s\n", settingName, historyToString(points))
+}
+
+func historyText(historyMap map[string][]CfgPoint) string {
+	if len(historyMap) == 0 {
+		return ""
+	}
+
+	str := "Setting history (newest -> oldest):\n"
+	names := make([]string, 0, len(historyMap))
+	for name, _ := range historyMap {
+		names = append(names, name)
+	}
+	sort.Strings(names)
+
+	for _, name := range names {
+		points := historyMap[name]
+		str += historyTextOnce(name, points)
+	}
+
+	return str
+}
+
 func (cfg *Cfg) ErrorText() string {
 	str := ""
 
-	interestingNames := map[string]struct{}{}
+	historyMap := map[string][]CfgPoint{}
 
 	if len(cfg.Violations) > 0 {
 		str += "Syscfg restriction violations detected:\n"
 		for settingName, rslice := range cfg.Violations {
-			interestingNames[settingName] = struct{}{}
-
 			entry := cfg.Settings[settingName]
 			for _, r := range rslice {
-				interestingNames[r.ReqSetting] = struct{}{}
+				for _, name := range r.relevantSettingNames() {
+					historyMap[name] = entry.History
+				}
 				str += "    " + cfg.violationText(entry, r) + "\n"
 			}
 		}
@@ -551,7 +532,7 @@ func (cfg *Cfg) ErrorText() string {
 
 		for _, name := range settingNames {
 			entry := cfg.Settings[name]
-			interestingNames[entry.Name] = struct{}{}
+			historyMap[entry.Name] = entry.History
 			str += "    " + entry.ambiguityText()
 		}
 	}
@@ -560,31 +541,63 @@ func (cfg *Cfg) ErrorText() string {
 		str += "Lateral overrides detected (bottom-priority packages " +
 			"cannot override settings):\n"
 		for _, lateral := range cfg.Laterals {
-			interestingNames[lateral.SettingName] = struct{}{}
+			entry := cfg.Settings[lateral.SettingName]
+			historyMap[lateral.SettingName] = entry.History
 
 			str += fmt.Sprintf("    Package: %s, Setting: %s\n",
 				lateral.PkgName, lateral.SettingName)
 		}
 	}
 
+	if len(cfg.FlashConflicts) > 0 {
+		str += "Flash errors detected:\n"
+		for _, conflict := range cfg.FlashConflicts {
+			for _, name := range conflict.SettingNames {
+				entry := cfg.Settings[name]
+				historyMap[name] = entry.History
+			}
+
+			str += "    " + cfg.flashConflictErrorText(conflict)
+		}
+	}
+
 	if str == "" {
 		return ""
 	}
 
-	str += "\nSetting history:\n"
-	interestingSlice := make([]string, 0, len(interestingNames))
-	for name, _ := range interestingNames {
-		interestingSlice = append(interestingSlice, name)
+	str += "\n" + historyText(historyMap)
+
+	return strings.TrimSpace(str)
+}
+
+func (cfg *Cfg) WarningText() string {
+	str := ""
+
+	historyMap := map[string][]CfgPoint{}
+
+	if len(cfg.Orphans) > 0 {
+		settingNames := make([]string, len(cfg.Orphans))
+		i := 0
+		for k, _ := range cfg.Orphans {
+			settingNames[i] = k
+			i++
+		}
+		sort.Strings(settingNames)
+
+		str += "Ignoring override of undefined settings:"
+		for _, n := range settingNames {
+			historyMap[n] = cfg.Orphans[n]
+			str += fmt.Sprintf("\n    %s", n)
+		}
 	}
-	sort.Strings(interestingSlice)
 
-	for _, name := range interestingSlice {
-		entry := cfg.Settings[name]
-		str += fmt.Sprintf("    %s: %s\n", name,
-			historyToString(entry.History))
+	if str == "" {
+		return ""
 	}
 
-	return strings.TrimSpace(str)
+	str += "\n" + historyText(historyMap)
+
+	return str
 }
 
 func escapeStr(s string) string {
@@ -663,7 +676,8 @@ func (cfg *Cfg) detectAmbiguities() {
 }
 
 func Read(lpkgs []*pkg.LocalPackage, apis []string,
-	injectedSettings map[string]string, features map[string]bool) (Cfg, error) {
+	injectedSettings map[string]string, features map[string]bool,
+	flashMap flash.FlashMap) (Cfg, error) {
 
 	cfg := NewCfg()
 	for k, v := range injectedSettings {
@@ -707,6 +721,7 @@ func Read(lpkgs []*pkg.LocalPackage, apis []string,
 
 	cfg.detectAmbiguities()
 	cfg.detectViolations()
+	cfg.detectFlashConflicts(flashMap)
 
 	return cfg, nil
 }
@@ -883,7 +898,7 @@ func writeSettings(cfg Cfg, w io.Writer) {
 }
 
 func write(cfg Cfg, w io.Writer) {
-	WritePreamble(w)
+	fmt.Fprintf(w, newtutil.GeneratedPreamble())
 
 	fmt.Fprintf(w, "#ifndef H_MYNEWT_SYSCFG_\n")
 	fmt.Fprintf(w, "#define H_MYNEWT_SYSCFG_\n\n")
@@ -897,33 +912,15 @@ func write(cfg Cfg, w io.Writer) {
 	fmt.Fprintf(w, "#endif\n")
 }
 
-func writeRequired(contents []byte, path string) (bool, error) {
-	oldHeader, err := ioutil.ReadFile(path)
-	if err != nil {
-		if os.IsNotExist(err) {
-			// File doesn't exist; write required.
-			return true, nil
-		}
-
-		return true, util.NewNewtError(err.Error())
-	}
-
-	rc := bytes.Compare(oldHeader, contents)
-	return rc != 0, nil
-}
-
-func headerPath(includeDir string) string {
-	return fmt.Sprintf("%s/%s/%s", includeDir, SYSCFG_INCLUDE_SUBDIR,
-		SYSCFG_HEADER_FILENAME)
-}
-
 func EnsureWritten(cfg Cfg, includeDir string) error {
+	// XXX: Detect these problems at error text generation time.
 	if err := calcPriorities(cfg, CFG_SETTING_TYPE_TASK_PRIO,
 		SYSCFG_TASK_PRIO_MAX, false); err != nil {
 
 		return err
 	}
 
+	// XXX: Detect these problems at error text generation time.
 	if err := calcPriorities(cfg, CFG_SETTING_TYPE_INTERRUPT_PRIO,
 		SYSCFG_INTERRUPT_PRIO_MAX, true); err != nil {
 
@@ -933,9 +930,9 @@ func EnsureWritten(cfg Cfg, includeDir string) error {
 	buf := bytes.Buffer{}
 	write(cfg, &buf)
 
-	path := headerPath(includeDir)
+	path := includeDir + "/" + HEADER_PATH
 
-	writeReqd, err := writeRequired(buf.Bytes(), path)
+	writeReqd, err := util.FileContentsChanged(path, buf.Bytes())
 	if err != nil {
 		return err
 	}

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/a441cdf5/newt/sysinit/sysinit.go
----------------------------------------------------------------------
diff --git a/newt/sysinit/sysinit.go b/newt/sysinit/sysinit.go
index 5d6d392..9835a3a 100644
--- a/newt/sysinit/sysinit.go
+++ b/newt/sysinit/sysinit.go
@@ -30,8 +30,8 @@ import (
 
 	log "github.com/Sirupsen/logrus"
 
+	"mynewt.apache.org/newt/newt/newtutil"
 	"mynewt.apache.org/newt/newt/pkg"
-	"mynewt.apache.org/newt/newt/syscfg"
 	"mynewt.apache.org/newt/util"
 )
 
@@ -89,7 +89,7 @@ func write(pkgs []*pkg.LocalPackage, isLoader bool,
 	}
 	sort.Ints(stages)
 
-	syscfg.WritePreamble(w)
+	fmt.Fprintf(w, newtutil.GeneratedPreamble())
 
 	if isLoader {
 		fmt.Fprintf(w, "#if SPLIT_LOADER\n\n")

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/a441cdf5/newt/vendor/mynewt.apache.org/newt/util/util.go
----------------------------------------------------------------------
diff --git a/newt/vendor/mynewt.apache.org/newt/util/util.go b/newt/vendor/mynewt.apache.org/newt/util/util.go
index e90ddbc..2cda3e8 100644
--- a/newt/vendor/mynewt.apache.org/newt/util/util.go
+++ b/newt/vendor/mynewt.apache.org/newt/util/util.go
@@ -522,3 +522,18 @@ func IsNotExist(err error) bool {
 
 	return os.IsNotExist(err)
 }
+
+func FileContentsChanged(path string, newContents []byte) (bool, error) {
+	oldContents, err := ioutil.ReadFile(path)
+	if err != nil {
+		if os.IsNotExist(err) {
+			// File doesn't exist; write required.
+			return true, nil
+		}
+
+		return true, NewNewtError(err.Error())
+	}
+
+	rc := bytes.Compare(oldContents, newContents)
+	return rc != 0, nil
+}

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/a441cdf5/util/util.go
----------------------------------------------------------------------
diff --git a/util/util.go b/util/util.go
index e90ddbc..2cda3e8 100644
--- a/util/util.go
+++ b/util/util.go
@@ -522,3 +522,18 @@ func IsNotExist(err error) bool {
 
 	return os.IsNotExist(err)
 }
+
+func FileContentsChanged(path string, newContents []byte) (bool, error) {
+	oldContents, err := ioutil.ReadFile(path)
+	if err != nil {
+		if os.IsNotExist(err) {
+			// File doesn't exist; write required.
+			return true, nil
+		}
+
+		return true, NewNewtError(err.Error())
+	}
+
+	rc := bytes.Compare(oldContents, newContents)
+	return rc != 0, nil
+}