| /** |
| * 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 |
| } |