blob: e17635b159686ca4378425b592b06b77518fac40 [file] [log] [blame]
/**
* 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 mfg
import (
"fmt"
"io/ioutil"
"sort"
"strconv"
"strings"
"github.com/spf13/cast"
"mynewt.apache.org/newt/newt/newtutil"
"mynewt.apache.org/newt/newt/pkg"
"mynewt.apache.org/newt/newt/project"
"mynewt.apache.org/newt/newt/target"
"mynewt.apache.org/newt/newt/toolchain"
"mynewt.apache.org/newt/util"
)
const MFG_YAML_FILENAME string = "mfg.yml"
type partSorter struct {
parts []mfgPart
}
func (s partSorter) Len() int {
return len(s.parts)
}
func (s partSorter) Swap(i, j int) {
s.parts[i], s.parts[j] = s.parts[j], s.parts[i]
}
func (s partSorter) Less(i, j int) bool {
return s.parts[i].offset < s.parts[j].offset
}
func sortParts(parts []mfgPart) []mfgPart {
sorter := partSorter{
parts: parts,
}
sort.Sort(sorter)
return sorter.parts
}
func (mi *MfgImage) loadError(
msg string, args ...interface{}) *util.NewtError {
return util.FmtNewtError("Error in %s mfg: %s", mi.basePkg.Name(),
fmt.Sprintf(msg, args...))
}
func (mi *MfgImage) loadTarget(targetName string) (
*target.Target, error) {
tgt := target.GetTargets()[targetName]
if tgt == nil {
return nil, mi.loadError("cannot resolve referenced target \"%s\"",
targetName)
}
return tgt, nil
}
func (mi *MfgImage) loadRawEntry(
entryIdx int, rawEntry map[string]string) (MfgRawEntry, error) {
raw := MfgRawEntry{}
var err error
deviceStr := rawEntry["device"]
if deviceStr == "" {
return raw, mi.loadError(
"raw entry %d missing required \"device\" field", entryIdx)
}
raw.device, err = util.AtoiNoOct(deviceStr)
if err != nil {
return raw, mi.loadError(
"raw entry %d contains invalid offset: %s", entryIdx, deviceStr)
}
offsetStr := rawEntry["offset"]
if offsetStr == "" {
return raw, mi.loadError(
"raw entry %d missing required \"offset\" field", entryIdx)
}
raw.offset, err = util.AtoiNoOct(offsetStr)
if err != nil {
return raw, mi.loadError(
"raw entry %d contains invalid offset: %s", entryIdx, offsetStr)
}
raw.filename = rawEntry["file"]
if raw.filename == "" {
return raw, mi.loadError(
"raw entry %d missing required \"file\" field", entryIdx)
}
if !strings.HasPrefix(raw.filename, "/") {
raw.filename = mi.basePkg.BasePath() + "/" + raw.filename
}
raw.data, err = ioutil.ReadFile(raw.filename)
if err != nil {
return raw, mi.loadError(
"error loading file for raw entry %d; filename=%s: %s",
entryIdx, raw.filename, err.Error())
}
return raw, nil
}
func (mi *MfgImage) detectInvalidDevices() error {
sectionIds := mi.sectionIds()
deviceIds := mi.bsp.FlashMap.DeviceIds()
deviceMap := map[int]struct{}{}
for _, device := range deviceIds {
deviceMap[device] = struct{}{}
}
invalidIds := []int{}
for _, sectionId := range sectionIds {
if _, ok := deviceMap[sectionId]; !ok {
invalidIds = append(invalidIds, sectionId)
}
}
if len(invalidIds) == 0 {
return nil
}
listStr := ""
for i, id := range invalidIds {
if i != 0 {
listStr += ", "
}
listStr += strconv.Itoa(id)
}
return util.FmtNewtError(
"image specifies flash devices that are not present in the BSP's "+
"flash map: %s", listStr)
}
func (mi *MfgImage) detectOverlaps() error {
type overlap struct {
part0 mfgPart
part1 mfgPart
}
overlaps := []overlap{}
dpMap, err := mi.devicePartMap()
if err != nil {
return err
}
// Iterate flash devices in order.
devices := make([]int, 0, len(dpMap))
for device, _ := range dpMap {
devices = append(devices, device)
}
sort.Ints(devices)
for _, device := range devices {
parts := dpMap[device]
for i, part0 := range parts[:len(parts)-1] {
part0End := part0.offset + len(part0.data)
for _, part1 := range parts[i+1:] {
// Parts are sorted by offset, so only one comparison is
// necessary to detect overlap.
if part1.offset < part0End {
overlaps = append(overlaps, overlap{
part0: part0,
part1: part1,
})
}
}
}
}
if len(overlaps) > 0 {
str := "flash overlaps detected:"
for _, overlap := range overlaps {
part0End := overlap.part0.offset + len(overlap.part0.data)
part1End := overlap.part1.offset + len(overlap.part1.data)
str += fmt.Sprintf("\n * s%d [%s] (%d - %d) <=> [%s] (%d - %d)",
overlap.part0.device,
overlap.part0.name, overlap.part0.offset, part0End,
overlap.part1.name, overlap.part1.offset, part1End)
}
return util.NewNewtError(str)
}
return nil
}
func Load(basePkg *pkg.LocalPackage) (*MfgImage, error) {
v, err := newtutil.ReadConfig(basePkg.BasePath(),
strings.TrimSuffix(MFG_YAML_FILENAME, ".yml"))
if err != nil {
return nil, err
}
mi := &MfgImage{
basePkg: basePkg,
}
bootName := v.GetValString("mfg.bootloader", nil)
if bootName == "" {
return nil, mi.loadError("mfg.bootloader field required")
}
mi.boot, err = mi.loadTarget(bootName)
if err != nil {
return nil, err
}
imgNames := v.GetValStringSlice("mfg.images", nil)
if imgNames != nil {
for _, imgName := range imgNames {
imgTarget, err := mi.loadTarget(imgName)
if err != nil {
return nil, err
}
mi.images = append(mi.images, imgTarget)
}
}
if len(mi.images) > 2 {
return nil, mi.loadError("too many images (%d); maximum is 2",
len(mi.images))
}
itf := v.GetFirstVal("mfg.raw", nil)
slice := cast.ToSlice(itf)
if slice != nil {
for i, entryItf := range slice {
yamlEntry := cast.ToStringMapString(entryItf)
entry, err := mi.loadRawEntry(i, yamlEntry)
if err != nil {
return nil, err
}
mi.rawEntries = append(mi.rawEntries, entry)
}
}
proj := project.GetProject()
bspLpkg, err := proj.ResolvePackage(mi.basePkg.Repo(),
mi.boot.BspName)
if err != nil {
return nil, mi.loadError(
"could not resolve boot loader BSP package: %s",
mi.boot.BspName)
}
mi.bsp, err = pkg.NewBspPackage(bspLpkg)
if err != nil {
return nil, mi.loadError(err.Error())
}
compilerPkg, err := proj.ResolvePackage(mi.bsp.Repo(), mi.bsp.CompilerName)
if err != nil {
return nil, mi.loadError(err.Error())
}
mi.compiler, err = toolchain.NewCompiler(compilerPkg.BasePath(), "",
target.DEFAULT_BUILD_PROFILE)
if err != nil {
return nil, mi.loadError(err.Error())
}
for _, imgTarget := range mi.images {
if len(mi.images) > 1 && imgTarget.LoaderName != "" {
return nil, mi.loadError("only one image allowed in "+
"split image mode (%s is a split build)", imgTarget.Name())
}
if imgTarget.Bsp() != mi.bsp.LocalPackage {
return nil, mi.loadError(
"image target \"%s\" specified conflicting BSP; "+
"boot loader uses %s, image uses %s",
imgTarget.Name(), mi.bsp.Name(), imgTarget.BspName)
}
}
if err := mi.detectInvalidDevices(); err != nil {
return nil, err
}
return mi, nil
}