blob: d5a84047a9e69a264f0ba30a163adaeba3c9d1eb [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.
*/
#include <string.h>
#include "os/mynewt.h"
#include "modlog/modlog.h"
#include "mfg/mfg.h"
/**
* The "manufacturing meta region" is located at the end of the boot loader
* flash area. This region has the following structure.
*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | TLV type | TLV size | TLV data ("TLV size" bytes) ~
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ~
* ~ ~
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | TLV type | TLV size | TLV data ("TLV size" bytes) ~
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ~
* ~ ~
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Region size | Version | 0xff padding |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Magic (0x3bb2a269) |
* +-+-+-+-+-+--+-+-+-+-end of boot loader area+-+-+-+-+-+-+-+-+-+-+
*
* The number of TLVs is variable; two are shown above for illustrative
* purposes.
*
* Fields:
* <TLVs>
* 1. TLV type: Indicates the type of data to follow.
* 2. TLV size: The number of bytes of data to follow.
* 3. TLV data: "TLV size" bytes of data.
*
* <Footer>
* 4. Region size: The size, in bytes, of the entire manufacturing meta region;
* includes TLVs and footer.
* 5. Version: Manufacturing meta version number; always 0x02.
* 6. Magic: Indicates the presence of the manufacturing meta region.
*/
#define MFG_META_MAGIC 0x3bb2a269
#define MFG_META_VERSION 2
#define MFG_META_FOOTER_SZ (sizeof (struct mfg_meta_footer))
#define MFG_META_TLV_SZ (sizeof (struct mfg_meta_tlv))
struct mfg_meta_footer {
uint16_t size;
uint8_t version;
uint8_t pad8;
uint32_t magic;
};
/** Represents an MMR after it has been read from flash. */
struct mfg_mmr {
/* Flash area containing MMR. */
uint8_t area_id;
/* Offset within flash area of start of MMR. */
uint32_t offset;
/* Total size of MMR (TLVs + footer). */
uint32_t size;
};
/** The full set of MMRs comprised by all installed mfgimages. */
static struct mfg_mmr mfg_mmrs[MYNEWT_VAL(MFG_MAX_MMRS)];
static int mfg_num_mmrs;
/** True if MMR detection has occurred. */
static bool mfg_initialized;
void
mfg_open(struct mfg_reader *out_reader)
{
/* Ensure MMRs have been detected. */
mfg_init();
/* Start at MMR index 0. */
*out_reader = (struct mfg_reader) { 0 };
}
/**
* Seeks to the next mfg TLV.
*
* @param reader The reader to seek with.
*
* @return 0 if the next TLV was successfully seeked to.
* SYS_EDONE if there are no additional TLVs
* available.
* SYS_EAGAIN if the end of the current MMR is
* reached, but additional MMRs are available
* for reading.
* Other MFG error code on failure.
*/
static int
mfg_seek_next_aux(struct mfg_reader *reader)
{
const struct flash_area *fap;
const struct mfg_mmr *mmr;
int rc;
if (reader->mmr_idx >= mfg_num_mmrs) {
/* The reader is expired. */
return SYS_EINVAL;
}
mmr = &mfg_mmrs[reader->mmr_idx];
rc = flash_area_open(mmr->area_id, &fap);
if (rc != 0) {
return SYS_EIO;
}
if (reader->offset == 0) {
/* First seek; advance to the start of the MMR. */
reader->offset = mmr->offset;
} else {
/* Follow-up seek; skip the current TLV. */
reader->offset += MFG_META_TLV_SZ + reader->cur_tlv.size;
}
if (reader->offset >= fap->fa_size - MFG_META_FOOTER_SZ) {
/* Reached end of the MMR; advance to the next MMR if one exists. */
if (reader->mmr_idx + 1 >= mfg_num_mmrs) {
rc = SYS_EDONE;
} else {
reader->offset = 0;
reader->mmr_idx++;
rc = SYS_EAGAIN;
}
goto done;
}
/* Read current TLV header. */
rc = flash_area_read(fap, reader->offset, &reader->cur_tlv,
MFG_META_TLV_SZ);
if (rc != 0) {
rc = SYS_EIO;
goto done;
}
done:
flash_area_close(fap);
return rc;
}
int
mfg_seek_next(struct mfg_reader *reader)
{
int rc;
do {
rc = mfg_seek_next_aux(reader);
} while (rc == SYS_EAGAIN);
return rc;
}
int
mfg_seek_next_with_type(struct mfg_reader *reader, uint8_t type)
{
int rc;
while (1) {
rc = mfg_seek_next(reader);
if (rc != 0) {
break;
}
if (reader->cur_tlv.type == type) {
break;
}
/* Proceed to next TLV. */
}
return rc;
}
/**
* Opens the flash area pointed to by the provided reader.
*/
static int
mfg_open_flash_area(const struct mfg_reader *reader,
const struct flash_area **fap)
{
const struct mfg_mmr *mmr;
int rc;
assert(reader->mmr_idx < mfg_num_mmrs);
mmr = &mfg_mmrs[reader->mmr_idx];
rc = flash_area_open(mmr->area_id, fap);
if (rc != 0) {
return SYS_EIO;
}
return 0;
}
static int
mfg_read_tlv_body(const struct mfg_reader *reader, void *dst, int max_size)
{
const struct flash_area *fap;
int read_sz;
int rc;
rc = mfg_open_flash_area(reader, &fap);
if (rc != 0) {
return rc;
}
memset(dst, 0, max_size);
read_sz = min(max_size, reader->cur_tlv.size);
rc = flash_area_read(fap, reader->offset + MFG_META_TLV_SZ, dst, read_sz);
flash_area_close(fap);
if (rc != 0) {
return SYS_EIO;
}
return 0;
}
int
mfg_read_tlv_flash_area(const struct mfg_reader *reader,
struct mfg_meta_flash_area *out_mfa)
{
return mfg_read_tlv_body(reader, out_mfa, sizeof *out_mfa);
}
int
mfg_read_tlv_mmr_ref(const struct mfg_reader *reader,
struct mfg_meta_mmr_ref *out_mr)
{
return mfg_read_tlv_body(reader, out_mr, sizeof *out_mr);
}
int
mfg_read_tlv_hash(const struct mfg_reader *reader, void *out_hash)
{
return mfg_read_tlv_body(reader, out_hash, MFG_HASH_SZ);
}
/**
* Reads an MMR from the end of the specified flash area.
*/
static int
mfg_read_mmr(uint8_t area_id, struct mfg_mmr *out_mmr)
{
const struct flash_area *fap;
struct mfg_meta_footer ftr;
int rc;
rc = flash_area_open(area_id, &fap);
if (rc != 0) {
return SYS_EIO;
}
/* Read the MMR footer. */
rc = flash_area_read(fap, fap->fa_size - sizeof ftr, &ftr, sizeof ftr);
flash_area_close(fap);
if (rc != 0) {
return SYS_EIO;
}
if (ftr.magic != MFG_META_MAGIC) {
return SYS_ENODEV;
}
if (ftr.version != MFG_META_VERSION) {
return SYS_ENOTSUP;
}
if (ftr.size > fap->fa_size) {
return SYS_ENODEV;
}
*out_mmr = (struct mfg_mmr) {
.area_id = area_id,
.offset = fap->fa_size - ftr.size,
.size = ftr.size,
};
return 0;
}
/**
* Reads an MMR from the end of the specified flash area. On success, the
* global MMR list is populated with the result for subsequent reading.
*/
static int
mfg_read_next_mmr(uint8_t area_id)
{
int rc;
int i;
/* Detect if this MMR has already been read. */
for (i = 0; i < mfg_num_mmrs; i++) {
if (mfg_mmrs[i].area_id == area_id) {
return SYS_EALREADY;
}
}
if (mfg_num_mmrs >= MYNEWT_VAL(MFG_MAX_MMRS)) {
return SYS_ENOMEM;
}
rc = mfg_read_mmr(area_id, &mfg_mmrs[mfg_num_mmrs]);
if (rc != 0) {
return rc;
}
mfg_num_mmrs++;
return 0;
}
/**
* Reads all MMR ref TLVs in the specified MMR. The global MMR list is
* populated with the results for subsequent reading.
*/
static int
mfg_read_mmr_refs(void)
{
struct mfg_meta_mmr_ref mmr_ref;
struct mfg_reader reader;
int rc;
mfg_open(&reader);
/* Repeatedly find and read the next MMR ref TLV. As new MMRs are read,
* they are added to the global list and become available in this loop.
*/
while (true) {
rc = mfg_seek_next_with_type(&reader, MFG_META_TLV_TYPE_MMR_REF);
switch (rc) {
case 0:
/* Found an MMR ref TLV. Read it below. */
break;
case SYS_EDONE:
/* No more MMR ref TLVs. */
return 0;
default:
return rc;
}
rc = mfg_read_tlv_mmr_ref(&reader, &mmr_ref);
if (rc != 0) {
return rc;
}
rc = mfg_read_next_mmr(mmr_ref.area_id);
if (rc != 0 && rc != SYS_EALREADY) {
return rc;
}
}
return 0;
}
/**
* Locates the manufacturing meta region in flash. This function must be
* called before any TLVs can be read. No-op if this function has already
* executed successfully.
*/
void
mfg_init(void)
{
int rc;
if (mfg_initialized) {
return;
}
mfg_initialized = true;
/* Ensure this function only gets called by sysinit. */
SYSINIT_ASSERT_ACTIVE();
/* Read the first MMR from the boot loader area. */
rc = mfg_read_next_mmr(FLASH_AREA_BOOTLOADER);
if (rc != 0) {
goto err;
}
/* Read all MMR references. */
rc = mfg_read_mmr_refs();
if (rc != 0) {
goto err;
}
return;
err:
MFG_LOG_ERROR("failed to read MMRs: rc=%d", rc);
}