New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Assets OTA #2668
Assets OTA #2668
Conversation
998da42
to
d73c9de
Compare
8f3b366
to
d067fc1
Compare
@@ -49,6 +49,11 @@ ASFLAGS += -I$(COMMON_BUILD)/arm/startup | |||
ASFLAGS += -Wa,--defsym -Wa,SPARK_INIT_STARTUP=0 | |||
ASFLAGS += -D__STACKSIZE__=$(MAIN_STACK_SIZE) -D__STACK_SIZE=$(MAIN_STACK_SIZE) | |||
|
|||
# Tracker | |||
ifeq ($(PLATFORM_ID),26) | |||
USE_PRINTF_FLOAT ?= n |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a breaking change for customer applications using printf?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but I have a workaround for this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you elaborate on the workaround? I tried Serial.printlnf("pi=%f", M_PI);
on a Tracker with 5.4.1 and I got the expected pi=3.141593
but with this branch, I get pi=
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the workaround because Tracker Edge/Monitor Edge uses a lot of floats and doubles
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly in the jsmn JSON writer but also logging of various sensor data and GPS
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to re-iterate: this is a temporary change and will be resolved (restored and some other stuff thrown away) before this PR is merged. This is failing a lot of our automated tests, so won't be unnoticed.
user/tests/app/assets/assets.cpp
Outdated
} else if (c == 'h') { | ||
LOG(INFO, "Hook executed=%d", hookExecuted); | ||
if (hookExecuted) { | ||
LOG(INFO, "%u ssets captured in hook:", hookAssets.size()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'A'
int read() override; | ||
int peek() override; | ||
// Not present in Stream | ||
virtual int read(char* buffer, size_t size); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For convenience, can we add an uint8_t
overload virtual int read(uint8_t* buffer, size_t size);
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great job 👍 ! This is a huge PR and I just had a brief review. The main problem I found is we use memory allocation in many places without checking the result. This is a potential risk. I'll go though the asset OTA process as well.
namespace particle { | ||
|
||
class ApplicationAsset: public Stream { | ||
public: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
doxygen comments please for public methods here
wiring/inc/spark_wiring_system.h
Outdated
static spark::Vector<ApplicationAsset> assetsAvailable(); | ||
static int assetsHandled(bool state = true); | ||
|
||
static int onAssetsOta(OnAssetsOtaCallback cb, void* context = nullptr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
doxygen comments for public methods please
@@ -48,9 +48,9 @@ const module_bounds_t module_km0_part1 = { | |||
|
|||
// OTA region, to be updated. | |||
module_bounds_t module_ota = { | |||
.maximum_size = 0x200000, //2M | |||
.maximum_size = 0x180000, // 1.5MB |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we get these values from the linker script?
struct asset_manager_stream; | ||
typedef struct asset_manager_stream asset_manager_stream; | ||
|
||
typedef enum asset_manager_consumer_state { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please comment these cases about what the do or represent
|
||
LOG(INFO, "prefix func=%d flags=%x start=%x end=%x compressed=%d", prefix.module_function, prefix.flags, prefix.module_start_address, prefix.module_end_address, compressed); | ||
|
||
// Comperssion header |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Compression"
42e7dad
to
5f86de1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Works great!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Posting the comments I have so far. Will continue looking into this tomorrow.
(*ctx)->buf_size = bufSize; | ||
(*ctx)->output = output; | ||
(*ctx)->user_data = user_data; | ||
inflate_reset(*ctx); | ||
CHECK(inflate_reset(*ctx)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The context will be leaked if inflate_reset
fails.
} | ||
|
||
int inflate_reset_impl(inflate_ctx* ctx) { | ||
#if HAL_PLATFORM_INFLATE_USE_FILESYSTEM |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The case when HAL_PLATFORM_INFLATE_USE_FILESYSTEM
is 1 but the entire buffer is allocated in RAM doesn't seem to be handled by this function.
uintptr_t productDataStart = (uintptr_t)desc.info.module_end_address - sizeof(module_info_suffix_base_t) - sizeof(module_info_product_data_ext_t); | ||
CHECK(hal_storage_read(storageId, productDataStart, (uint8_t*)&product, sizeof(product))); | ||
|
||
return product.version; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
@@ -81,28 +81,41 @@ extern "C" { | |||
# define EXTERNAL_FLASH_RESERVED1_XIP_ADDRESS (EXTERNAL_FLASH_RESERVED1_ADDRESS + EXTERNAL_FLASH_XIP_BASE) | |||
# define EXTERNAL_FLASH_RESERVED1_LENGTH (2048*1024) | |||
# define EXTERNAL_FLASH_FAC_ADDRESS (EXTERNAL_FLASH_RESERVED1_ADDRESS + EXTERNAL_FLASH_RESERVED1_LENGTH) | |||
# define EXTERNAL_FLASH_ASSET_STORAGE_FIRST_PAGE ((EXTERNAL_FLASH_RESERVED1_XIP_ADDRESS) / sFLASH_PAGESIZE) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is EXTERNAL_FLASH_RESERVED1_XIP_ADDRESS
and not EXTERNAL_FLASH_RESERVED1_ADDRESS
used here?
services/inc/buffer.h
Outdated
|
||
class Buffer { | ||
public: | ||
Buffer(size_t size = 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider making the constructor explicit and removing implicit conversion operators to avoid unintended conversions.
return !memcmp(buffer_.get(), other.buffer_.get(), size()); | ||
} | ||
return false; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The way it's implemented now, the operator will return false
if both buffers are empty.
services/inc/storage_streams.h
Outdated
#pragma once | ||
|
||
#undef LOG_COMPILE_TIME_LEVEL | ||
#define LOG_COMPILE_TIME_LEVEL LOG_LEVEL_NONE |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We shouldn't be overriding the logging settings in header files.
isOpen_(false) { | ||
fs_ = filesystem_get_instance(instance, nullptr); | ||
open(filename); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider only providing a default constructor and exposing the open()
method so that the possible error when opening the file can be properly handled by the calling code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will leave this one as-is just for simplicity of not having to carry around UGH Fixed 😄filename
(see AssetReader
usage as well).
services/inc/storage_streams.h
Outdated
CHECK_TRUE(data, SYSTEM_ERROR_INVALID_ARGUMENT); | ||
size = std::min(size, remaining_); | ||
fs::FsLock lock(fs_); | ||
CHECK(lfs_file_seek(&fs_->instance, &file_, offset_, LFS_SEEK_SET)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make sure CHECK_FS
is used with LittleFS functions here and in other places in this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good job. I don't think I found anything major, although I must admit I don't fully follow the logic where a file is used to store the decompression buffer. Hopefully that is well covered by tests.
services/inc/storage_streams.h
Outdated
offset_(0), | ||
error_(0) { | ||
|
||
error_ = inflate_create(&inflate_, nullptr, [](const char* data, size_t size, void* ctx) -> int { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This approach to error handling is tedious and prone to bugs. In this class specifically, error_
is only checked in rewind()
so the calling code will highly likely miss the error occurred when creating a decompressor context in the constructor. Consider separating the initialization logic in a checkable method such as init()
.
} | ||
} | ||
return availForRead(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's pretty cool that this class doesn't require an intermediate buffer.
@@ -0,0 +1,158 @@ | |||
/* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This header file seems to be internal to the system. If so, consider moving it to system/src
as e.g. asset_manager_internal.h
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also required by hal unfortunately (ota_flash_hal.cpp)
system/inc/asset_manager_api.h
Outdated
Buffer buf(hash_.size() * 2 + 1); | ||
if (buf.size()) { | ||
memset(buf.data(), 0, buf.size()); | ||
bytes2hexbuf_lower_case((const uint8_t*)hash_.data(), hash_.size(), buf.data()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just in case, there's a safer alternative that always null-terminates the output buffer.
system/inc/asset_manager_api.h
Outdated
* @param reserved Reserved (NULL) | ||
* @return 0 on success or `system_error_t` error code | ||
*/ | ||
int asset_manager_free_info(asset_manager_info* info, void* reserved); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's use void
as the return type of destructor-like functions where possible as it's often not straightforward to handle errors during deinitialization in the calling code. If it does make sense to return an error from such a function, it should be documented what happens to the instance being deinitialized when an error occurs.
Same here.
system/src/asset_manager.cpp
Outdated
CHECK_TRUE(prefix.module_function == MODULE_FUNCTION_ASSET, SYSTEM_ERROR_BAD_DATA); | ||
CHECK_TRUE(prefix.flags & MODULE_INFO_FLAG_DROP_MODULE_INFO, SYSTEM_ERROR_BAD_DATA); | ||
CHECK_FALSE(prefix.flags & MODULE_INFO_FLAG_PREFIX_EXTENSIONS, SYSTEM_ERROR_BAD_DATA); | ||
CHECK_TRUE(moduleSize == ((uintptr_t)prefix.module_end_address - (uintptr_t)prefix.module_start_address + sizeof(uint32_t)), SYSTEM_ERROR_BAD_DATA); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add a comment that sizeof(uint32_t)
is the CRC.
system/src/asset_manager_api.cpp
Outdated
info->internal = (void*)internal; | ||
|
||
bool ok = false; | ||
SCOPE_GUARD({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We also have NAMED_SCOPE_GUARD
that can be dismissed.
system/src/asset_manager_api.cpp
Outdated
delete internal; | ||
} | ||
if (info->required) { | ||
free(info->required); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
free()
is used to free the memory allocated with new
.
Same here.
internal->required = AssetManager::instance().requiredAssets(); | ||
internal->available = AssetManager::instance().availableAssets(); | ||
|
||
info->internal = (void*)internal; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This internal info doesn't seem to be used by any of the API calls. If so, let's remove it and save on memory.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's the storage for at the very least hash and name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, didn't notice that 👍
} | ||
|
||
inline bool AssetHash::operator==(const AssetHash& other) const { | ||
return (type_ == other.type_ && hash_ == other.hash_); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it intentional that the asset name is not taken into account here? It's worth adding a comment if so. Oops, this class is just an asset's hash.
This reverts commit 7691f36.
…alidate open files
da322aa
to
2858d78
Compare
Description
Assets OTA
TODO
Steps to Test
P2 / Photon 2
1.1. The easiest is
device-os-flash --all-devices 5.4.1
2.2.
(bootloader) $ make PLATFORM=p2 COMPILE_LTO=n clean all
2.3. Flash bootloader
device-os-flash --all-devices $DEVICE_OS_DIR/build/target/bootloader/platform-32-m/bootloader.bin
deviceOS@source
)Gen 3 (nRF52840 platforms)
1.1. The easiest is
device-os-flash --all-devices 5.4.1
2.2.
(bootloader) $ make PLATFORM=boron COMPILE_LTO=n clean all
2.3. Flash bootloader
device-os-flash --all-devices $DEVICE_OS_DIR/build/target/bootloader/platform-13-m/bootloader.bin
deviceOS@source
)Example App
See
app/assets
.References
N/A
Completeness