| /** |
| * 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 ( |
| "bytes" |
| "encoding/binary" |
| "fmt" |
| "io/ioutil" |
| "os" |
| "path/filepath" |
| "sort" |
| |
| "github.com/apache/mynewt-artifact/errors" |
| "github.com/apache/mynewt-artifact/flash" |
| "github.com/apache/mynewt-artifact/image" |
| "github.com/apache/mynewt-artifact/manifest" |
| "github.com/apache/mynewt-artifact/mfg" |
| "mynewt.apache.org/newt/newt/builder" |
| "mynewt.apache.org/newt/newt/flashmap" |
| "mynewt.apache.org/newt/newt/parse" |
| "mynewt.apache.org/newt/newt/pkg" |
| "mynewt.apache.org/newt/newt/project" |
| "mynewt.apache.org/newt/newt/target" |
| "mynewt.apache.org/newt/util" |
| ) |
| |
| type MfgBuildTarget struct { |
| Target *target.Target |
| Area flash.FlashArea |
| Offset int |
| Size int |
| IsBoot bool |
| BinPath string |
| ExtraManifest map[string]interface{} |
| } |
| |
| type MfgBuildRaw struct { |
| Filename string |
| Offset int |
| Size int |
| Area flash.FlashArea |
| ExtraFiles []string |
| ExtraManifest map[string]interface{} |
| } |
| |
| type MfgBuildMetaMmr struct { |
| Area flash.FlashArea |
| } |
| |
| type MfgBuildMeta struct { |
| Area flash.FlashArea |
| Hash bool |
| FlashMap bool |
| Mmrs []MfgBuildMetaMmr |
| } |
| |
| // Can be used to construct an Mfg object. |
| type MfgBuilder struct { |
| BasePkg *pkg.LocalPackage |
| Bsp *pkg.BspPackage |
| Targets []MfgBuildTarget |
| Raws []MfgBuildRaw |
| Meta *MfgBuildMeta |
| } |
| |
| // Searches the provided flash map for the named area. |
| func lookUpArea(fm flashmap.FlashMap, name string) (flash.FlashArea, error) { |
| area, ok := fm.Areas[name] |
| if !ok { |
| return flash.FlashArea{}, util.FmtNewtError( |
| "reference to undefined flash area \"%s\"", name) |
| } |
| |
| return area, nil |
| } |
| |
| // Searches the project for the target corresponding to the specified decoded |
| // entry (read from `mfg.yml`). |
| func lookUpTarget(dt DecodedTarget) (*target.Target, error) { |
| t := target.GetTargets()[dt.Name] |
| if t == nil { |
| return nil, util.FmtNewtError( |
| "target entry references undefined target \"%s\"", dt.Name) |
| } |
| |
| return t, nil |
| } |
| |
| func normalizeOffset(offset int, length int, |
| area flash.FlashArea) (int, error) { |
| |
| areaEnd := area.Offset + area.Size |
| if offset == OFFSET_END { |
| if length > area.Size { |
| return 0, util.FmtNewtError( |
| "segment is too large to fit in flash area \"%s\"; "+ |
| "segment=%d, area=%d", area.Name, length, area.Size) |
| } |
| return areaEnd - length, nil |
| } |
| |
| if offset+length > area.Size { |
| return 0, util.FmtNewtError( |
| "segment extends beyond end of flash area \"%s\"; "+ |
| "offset=%d len=%d area_len=%d", |
| area.Name, offset, length, area.Size) |
| } |
| |
| return area.Offset + offset, nil |
| } |
| |
| func calcBsp(dm DecodedMfg, |
| basePkg *pkg.LocalPackage) (*pkg.BspPackage, error) { |
| |
| var bspLpkg *pkg.LocalPackage |
| bspMap := map[*pkg.LocalPackage]struct{}{} |
| for _, dt := range dm.Targets { |
| t, err := lookUpTarget(dt) |
| if err != nil { |
| return nil, err |
| } |
| |
| bspLpkg = t.Bsp() |
| bspMap[bspLpkg] = struct{}{} |
| } |
| |
| if dm.Bsp != "" { |
| var err error |
| bspLpkg, err = project.GetProject().ResolvePackage( |
| basePkg.Repo(), dm.Bsp) |
| if err != nil { |
| return nil, util.FmtNewtError( |
| "failed to resolve BSP package: %s", err.Error()) |
| } |
| bspMap[bspLpkg] = struct{}{} |
| } |
| |
| if len(bspMap) == 0 { |
| return nil, util.FmtNewtError( |
| "failed to determine BSP: no targets and no \"bsp\" field") |
| } |
| |
| if len(bspMap) > 1 { |
| return nil, util.FmtNewtError("multiple BSPs detected") |
| } |
| |
| bsp, err := pkg.NewBspPackage(bspLpkg) |
| if err != nil { |
| return nil, util.FmtNewtError(err.Error()) |
| } |
| |
| return bsp, nil |
| } |
| |
| func (raw *MfgBuildRaw) ToPart(entryIdx int) (Part, error) { |
| data, err := ioutil.ReadFile(raw.Filename) |
| if err != nil { |
| return Part{}, util.ChildNewtError(err) |
| } |
| |
| off, err := normalizeOffset(raw.Offset, len(data), raw.Area) |
| if err != nil { |
| return Part{}, err |
| } |
| |
| return Part{ |
| Name: fmt.Sprintf("raw-%d (%s)", entryIdx, raw.Filename), |
| Offset: off, |
| Data: data, |
| }, nil |
| } |
| |
| func (mt *MfgBuildTarget) ToPart() (Part, error) { |
| data, err := ioutil.ReadFile(mt.BinPath) |
| if err != nil { |
| return Part{}, util.ChildNewtError(err) |
| } |
| |
| off, err := normalizeOffset(mt.Offset, len(data), mt.Area) |
| if err != nil { |
| return Part{}, err |
| } |
| |
| return Part{ |
| Name: fmt.Sprintf("%s (%s)", mt.Area.Name, filepath.Base(mt.BinPath)), |
| Offset: off, |
| Data: data, |
| }, nil |
| } |
| |
| func newMfgBuildTarget(dt DecodedTarget, |
| fm flashmap.FlashMap) (MfgBuildTarget, error) { |
| |
| t, err := lookUpTarget(dt) |
| if err != nil { |
| return MfgBuildTarget{}, err |
| } |
| |
| area, err := lookUpArea(fm, dt.Area) |
| if err != nil { |
| return MfgBuildTarget{}, err |
| } |
| |
| mpath := builder.ManifestPath(dt.Name, builder.BUILD_NAME_APP, |
| t.App().Name()) |
| man, err := manifest.ReadManifest(mpath) |
| if err != nil { |
| mpath = builder.ManifestPath(dt.Name, builder.BUILD_NAME_BOOT, |
| t.App().Name()) |
| man, err = manifest.ReadManifest(mpath) |
| if err != nil { |
| return MfgBuildTarget{}, util.FmtNewtError("%s", err.Error()) |
| } |
| } |
| |
| isBoot := parse.ValueIsTrue(man.Syscfg["BOOT_LOADER"]) |
| |
| binPath := targetSrcBinPath(t, isBoot) |
| |
| st, err := os.Stat(binPath) |
| if err != nil { |
| return MfgBuildTarget{}, errors.Wrapf(err, |
| "failed to determine size of file \"%s\"", binPath) |
| } |
| |
| return MfgBuildTarget{ |
| Target: t, |
| Area: area, |
| Offset: dt.Offset, |
| Size: int(st.Size()), |
| IsBoot: isBoot, |
| BinPath: binPath, |
| ExtraManifest: dt.ExtraManifest, |
| }, nil |
| } |
| |
| func newMfgBuildRaw(dr DecodedRaw, fm flashmap.FlashMap) (MfgBuildRaw, error) { |
| checkFile := func(filename string) (string, int, error) { |
| abs, err := filepath.Abs(filename) |
| if err != nil { |
| return "", 0, util.FmtNewtError( |
| "failed to determine absolute path of file: path=%s", filename) |
| } |
| |
| st, err := os.Stat(abs) |
| if err != nil { |
| return "", 0, errors.Wrapf(err, |
| "failed to determine size of file \"%s\"", abs) |
| } |
| |
| return abs, int(st.Size()), nil |
| } |
| |
| filename, filesize, err := checkFile(dr.Filename) |
| if err != nil { |
| return MfgBuildRaw{}, err |
| } |
| |
| var extraFiles []string |
| for _, ef := range dr.ExtraFiles { |
| ename, _, err := checkFile(ef) |
| if err != nil { |
| return MfgBuildRaw{}, err |
| } |
| extraFiles = append(extraFiles, ename) |
| } |
| |
| area, err := lookUpArea(fm, dr.Area) |
| if err != nil { |
| return MfgBuildRaw{}, err |
| } |
| |
| return MfgBuildRaw{ |
| Filename: filename, |
| Offset: dr.Offset, |
| Size: filesize, |
| Area: area, |
| ExtraFiles: extraFiles, |
| ExtraManifest: dr.ExtraManifest, |
| }, nil |
| } |
| |
| func newMfgBuildMeta(dm DecodedMeta, |
| fm flashmap.FlashMap) (MfgBuildMeta, error) { |
| |
| area, ok := fm.Areas[dm.Area] |
| if !ok { |
| return MfgBuildMeta{}, util.FmtNewtError( |
| "meta region specifies unrecognized flash area: \"%s\"", dm.Area) |
| } |
| |
| var mmrs []MfgBuildMetaMmr |
| for _, dmmr := range dm.Mmrs { |
| area, err := lookUpArea(fm, dmmr.Area) |
| if err != nil { |
| return MfgBuildMeta{}, err |
| } |
| mmr := MfgBuildMetaMmr{ |
| Area: area, |
| } |
| mmrs = append(mmrs, mmr) |
| } |
| |
| return MfgBuildMeta{ |
| Area: area, |
| Hash: dm.Hash, |
| FlashMap: dm.FlashMap, |
| Mmrs: mmrs, |
| }, nil |
| } |
| |
| func (mb *MfgBuilder) parts() ([]Part, error) { |
| parts := []Part{} |
| |
| // Create parts from the raw entries. |
| for i, raw := range mb.Raws { |
| part, err := raw.ToPart(i) |
| if err != nil { |
| return nil, err |
| } |
| parts = append(parts, part) |
| } |
| |
| // Create parts from the target entries. |
| for _, t := range mb.Targets { |
| part, err := t.ToPart() |
| if err != nil { |
| return nil, err |
| } |
| parts = append(parts, part) |
| } |
| |
| // Sort by offset. |
| return SortParts(parts), nil |
| } |
| |
| func (mb *MfgBuilder) detectOverlaps() error { |
| type overlap struct { |
| p1 Part |
| p2 Part |
| } |
| |
| overlaps := []overlap{} |
| |
| parts, err := mb.parts() |
| if err != nil { |
| return err |
| } |
| |
| for i, p1 := range parts[:len(parts)-1] { |
| p1end := p1.Offset + len(p1.Data) |
| for _, p2 := range parts[i+1:] { |
| // Parts are sorted by offset, so only one comparison is |
| // necessary to detect overlap. |
| if p2.Offset < p1end { |
| overlaps = append(overlaps, overlap{ |
| p1: p1, |
| p2: p2, |
| }) |
| } |
| } |
| } |
| |
| if len(overlaps) > 0 { |
| str := "flash overlaps detected:" |
| for _, overlap := range overlaps { |
| |
| p1end := overlap.p1.Offset + len(overlap.p1.Data) |
| p2end := overlap.p2.Offset + len(overlap.p2.Data) |
| str += fmt.Sprintf("\n * [%s] (%d - %d) <=> [%s] (%d - %d)", |
| overlap.p1.Name, overlap.p1.Offset, p1end, |
| overlap.p2.Name, overlap.p2.Offset, p2end) |
| } |
| |
| return util.NewNewtError(str) |
| } |
| |
| return nil |
| } |
| |
| // Determines which flash device the manufacturing image is intended for. It |
| // is an error if the mfg definition specifies 0 or >1 devices. |
| func (mb *MfgBuilder) calcDevice() (int, error) { |
| deviceMap := map[int]struct{}{} |
| for _, t := range mb.Targets { |
| deviceMap[t.Area.Device] = struct{}{} |
| } |
| for _, r := range mb.Raws { |
| deviceMap[r.Area.Device] = struct{}{} |
| } |
| |
| devices := make([]int, 0, len(deviceMap)) |
| for d, _ := range deviceMap { |
| devices = append(devices, d) |
| } |
| sort.Ints(devices) |
| |
| if len(devices) == 0 { |
| return 0, util.FmtNewtError( |
| "manufacturing image definition does not indicate flash device") |
| } |
| |
| if len(devices) > 1 { |
| return 0, util.FmtNewtError( |
| "multiple flash devices in use by single manufacturing image: %v", |
| devices) |
| } |
| |
| return devices[0], nil |
| } |
| |
| func newMfgBuilder(basePkg *pkg.LocalPackage, dm DecodedMfg, |
| ver image.ImageVersion) (MfgBuilder, error) { |
| |
| mb := MfgBuilder{ |
| BasePkg: basePkg, |
| } |
| |
| bsp, err := calcBsp(dm, basePkg) |
| if err != nil { |
| return mb, err |
| } |
| mb.Bsp = bsp |
| |
| for _, dt := range dm.Targets { |
| mbt, err := newMfgBuildTarget(dt, bsp.FlashMap) |
| if err != nil { |
| return mb, err |
| } |
| mb.Targets = append(mb.Targets, mbt) |
| } |
| |
| for _, dr := range dm.Raws { |
| mbr, err := newMfgBuildRaw(dr, bsp.FlashMap) |
| if err != nil { |
| return mb, err |
| } |
| mb.Raws = append(mb.Raws, mbr) |
| } |
| |
| if dm.Meta != nil { |
| meta, err := newMfgBuildMeta(*dm.Meta, mb.Bsp.FlashMap) |
| if err != nil { |
| return mb, err |
| } |
| mb.Meta = &meta |
| } |
| |
| if _, err := mb.calcDevice(); err != nil { |
| return mb, err |
| } |
| |
| if err := mb.detectOverlaps(); err != nil { |
| return mb, err |
| } |
| |
| return mb, nil |
| } |
| |
| // Creates a zeroed-out hash MMR TLV. The hash's original value must be zero |
| // for the actual hash to be calculated later. After the actual value is |
| // calculated, it replaces the zeros in the TLV. |
| func newZeroHashTlv() mfg.MetaTlv { |
| return mfg.MetaTlv{ |
| Header: mfg.MetaTlvHeader{ |
| Type: mfg.META_TLV_TYPE_HASH, |
| Size: mfg.META_TLV_HASH_SZ, |
| }, |
| Data: make([]byte, mfg.META_HASH_SZ), |
| } |
| } |
| |
| // Creates a flash area MMR TLV. |
| func newFlashAreaTlv(area flash.FlashArea) (mfg.MetaTlv, error) { |
| tlv := mfg.MetaTlv{ |
| Header: mfg.MetaTlvHeader{ |
| Type: mfg.META_TLV_TYPE_FLASH_AREA, |
| Size: mfg.META_TLV_FLASH_AREA_SZ, |
| }, |
| } |
| |
| body := mfg.MetaTlvBodyFlashArea{ |
| Area: uint8(area.Id), |
| Device: uint8(area.Device), |
| Offset: uint32(area.Offset), |
| Size: uint32(area.Size), |
| } |
| |
| b := &bytes.Buffer{} |
| if err := binary.Write(b, binary.LittleEndian, body); err != nil { |
| return tlv, util.ChildNewtError(err) |
| } |
| |
| tlv.Data = b.Bytes() |
| |
| return tlv, nil |
| } |
| |
| // Creates an MMR ref TLV. |
| func newMmrRefTlv(area flash.FlashArea) (mfg.MetaTlv, error) { |
| tlv := mfg.MetaTlv{ |
| Header: mfg.MetaTlvHeader{ |
| Type: mfg.META_TLV_TYPE_MMR_REF, |
| Size: mfg.META_TLV_MMR_REF_SZ, |
| }, |
| } |
| |
| body := mfg.MetaTlvBodyMmrRef{ |
| Area: uint8(area.Id), |
| } |
| |
| b := &bytes.Buffer{} |
| if err := binary.Write(b, binary.LittleEndian, body); err != nil { |
| return tlv, util.ChildNewtError(err) |
| } |
| |
| tlv.Data = b.Bytes() |
| |
| return tlv, nil |
| } |
| |
| // Builds a manufacturing meta region. |
| func (mb *MfgBuilder) buildMeta() (mfg.Meta, error) { |
| meta := mfg.Meta{ |
| Footer: mfg.MetaFooter{ |
| Size: 0, // Filled in later. |
| Version: mfg.META_VERSION, |
| Pad8: 0xff, |
| Magic: mfg.META_MAGIC, |
| }, |
| } |
| |
| // Hash TLV. |
| if mb.Meta.Hash { |
| meta.Tlvs = append(meta.Tlvs, newZeroHashTlv()) |
| } |
| |
| // Flash map TLVs. |
| if mb.Meta.FlashMap { |
| for _, area := range mb.Bsp.FlashMap.SortedAreas() { |
| tlv, err := newFlashAreaTlv(area) |
| if err != nil { |
| return meta, err |
| } |
| |
| meta.Tlvs = append(meta.Tlvs, tlv) |
| } |
| } |
| |
| // MMR ref TLVs. |
| for _, mmr := range mb.Meta.Mmrs { |
| tlv, err := newMmrRefTlv(mmr.Area) |
| if err != nil { |
| return meta, err |
| } |
| |
| meta.Tlvs = append(meta.Tlvs, tlv) |
| } |
| |
| // Fill in region size in footer now that we know the value. |
| meta.Footer.Size = uint16(meta.Size()) |
| |
| return meta, nil |
| } |
| |
| // Builds a manufacturing image. |
| func (mb *MfgBuilder) Build() (mfg.Mfg, error) { |
| parts, err := mb.parts() |
| if err != nil { |
| return mfg.Mfg{}, err |
| } |
| |
| bin, err := PartsBytes(parts) |
| if err != nil { |
| return mfg.Mfg{}, err |
| } |
| |
| var metaOff int |
| var metap *mfg.Meta |
| if mb.Meta != nil { |
| meta, err := mb.buildMeta() |
| if err != nil { |
| return mfg.Mfg{}, err |
| } |
| metap = &meta |
| metaOff = mb.Meta.Area.Offset + mb.Meta.Area.Size - meta.Size() |
| } |
| |
| return mfg.Mfg{ |
| Bin: bin, |
| Meta: metap, |
| MetaOff: metaOff, |
| }, nil |
| } |