blob: 030332f0a91074cab2d43ae8523dc0cbb197766a [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 <os/mynewt.h>
#include <sysflash/sysflash.h>
#include <class/dfu/dfu_device.h>
#include <bsp/bsp.h>
#include <img_mgmt/img_mgmt.h>
#include <tinyusb/tinyusb.h>
#include <hal/hal_gpio.h>
#include <hal/hal_nvreg.h>
#include <tusb.h>
/*
* If DFU is activated from bootloader it writes to SLOT0.
* If application code handles DFU it writes to SLOT1 and marks slot as
* pending.
*/
#define FIRMWARE_SLOT MYNEWT_VAL(USBD_DFU_SLOT_ID)
struct os_callout delayed_reset_callout;
struct os_callout auto_confirm_callout;
void
delayed_reset_cb(struct os_event *event)
{
hal_system_reset();
}
/*
* DFU callbacks
* Note: alt is used as the partition number, in order to support multiple partitions like FLASH, EEPROM, etc.
*/
/*
* Invoked right before tud_dfu_download_cb() (state=DFU_DNBUSY) or tud_dfu_manifest_cb() (state=DFU_MANIFEST)
* Application return timeout in milliseconds (bwPollTimeout) for the next download/manifest operation.
* During this period, USB host won't try to communicate with us.
*/
uint32_t
tud_dfu_get_timeout_cb(uint8_t alt, uint8_t state)
{
if (state == DFU_DNBUSY) {
return MYNEWT_VAL(USBD_DFU_BLOCK_WRITE_TIME);
}
/* Since we don't buffer entire image and do any flashing in manifest stage no extra time is needed */
return 0;
}
static bool
flash_erased(const struct flash_area *fa, uint32_t off, uint32_t size)
{
uint8_t buf[64];
uint32_t limit = off + size;
uint32_t chunk;
while (off < limit) {
chunk = min(sizeof(buf), size);
if (flash_area_read_is_empty(fa, off, buf, chunk)) {
size -= chunk;
off += chunk;
} else {
break;
}
}
return off >= limit;
}
/*
* Invoked when received DFU_DNLOAD (wLength > 0) following by DFU_GETSTATUS (state=DFU_DNBUSY) requests
* This callback could be returned before flashing op is complete (async).
* Once finished flashing, application must call tud_dfu_finish_flashing()
*/
void
tud_dfu_download_cb(uint8_t alt, uint16_t block_num, const uint8_t *data, uint16_t length)
{
const struct flash_area *fa;
uint32_t status = DFU_STATUS_OK;
if (flash_area_open(FIRMWARE_SLOT, &fa)) {
status = DFU_STATUS_ERR_ADDRESS;
} else {
if (block_num == 0) {
USBD_DFU_LOG_INFO("Download started\n");
}
if (!flash_erased(fa, block_num * CFG_TUD_DFU_XFER_BUFSIZE, length)) {
USBD_DFU_LOG_DEBUG("Erasing flash 0x%X (0x%X bytes)\n", block_num * CFG_TUD_DFU_XFER_BUFSIZE, length);
if (flash_area_erase(fa, block_num * CFG_TUD_DFU_XFER_BUFSIZE, length)) {
USBD_DFU_LOG_ERROR("Flash erase failed\n");
status = DFU_STATUS_ERR_ERASE;
}
}
if (status == DFU_STATUS_OK) {
USBD_DFU_LOG_DEBUG("Writing flash 0x%X (0x%X bytes)\n", block_num * CFG_TUD_DFU_XFER_BUFSIZE, length);
if (flash_area_write(fa, block_num * CFG_TUD_DFU_XFER_BUFSIZE, data, length) < 0) {
USBD_DFU_LOG_ERROR("Flash write failed\n");
status = DFU_STATUS_ERR_PROG;
}
}
flash_area_close(fa);
}
tud_dfu_finish_flashing(status);
}
/*
* Invoked when download process is complete, received DFU_DNLOAD (wLength = 0) followed by DFU_GETSTATUS (state = Manifest)
* Application can do checksum, or actual flashing if buffered entire image previously.
* Once finished flashing, application must call tud_dfu_finish_flashing()
*/
void
tud_dfu_manifest_cb(uint8_t alt)
{
(void)alt;
#if !MYNEWT_VAL(BOOT_LOADER) && MYNEWT_VAL(USBD_DFU_MARK_AS_PENDING)
USBD_DFU_LOG_INFO("Download completed, enter manifestation. settings slot 1 as pending\n");
img_mgmt_state_set_pending(1, MYNEWT_VAL(USBD_DFU_MARK_AS_CONFIRMED));
#endif
/*
* Flashing op for manifest is complete without error.
* Application can perform checksum, should it fail,
* use appropriate status such as errVERIFY.
*/
tud_dfu_finish_flashing(DFU_STATUS_OK);
#if MYNEWT_VAL(USBD_DFU_RESET_AFTER_DOWNLOAD)
/*
* Device should reboot after download is complete.
* It should be done after final DFU_GETSTATUS. But this event is not
* propagated to application by TinyUSB stack.
* To satisfy DFU tools lets give USB stack some time to respond to finishing
* commands before reboot.
*/
os_callout_init(&delayed_reset_callout, os_eventq_dflt_get(), delayed_reset_cb, NULL);
os_callout_reset(&delayed_reset_callout, os_time_ms_to_ticks32(MYNEWT_VAL(USBD_DFU_RESET_TIMEOUT)));
#endif
}
void
tud_dfu_detach_cb(void)
{
/* TODO: implement detach if needed */
}
/**
* Function called by mcuboot before image verification/swap/and application execution.
* If GPIO pin is selected and in active state instead of performing normal startup
* TinyUSB with DFU interface is started allowing for application update.
*/
void
boot_preboot(void)
{
bool start_dfu = false;
if (MYNEWT_VAL_USBD_DFU_BOOT_PIN >= 0) {
hal_gpio_init_in(MYNEWT_VAL(USBD_DFU_BOOT_PIN),
MYNEWT_VAL(USBD_DFU_BOOT_PIN_PULL));
if (hal_gpio_read(MYNEWT_VAL(USBD_DFU_BOOT_PIN)) ==
MYNEWT_VAL(USBD_DFU_BOOT_PIN_VALUE)) {
hal_gpio_deinit(MYNEWT_VAL(USBD_DFU_BOOT_PIN));
start_dfu = true;
}
hal_gpio_deinit(MYNEWT_VAL(USBD_DFU_BOOT_PIN));
} else if (MYNEWT_VAL_USBD_DFU_RESET_COUNT_NVREG >= 0) {
uint32_t counter = hal_nvreg_read(MYNEWT_VAL_USBD_DFU_RESET_COUNT_NVREG);
uint32_t new_counter = 0;
if (counter == MYNEWT_VAL_USBD_DFU_RESET_COUNT) {
new_counter = counter;
} else if (hal_reset_cause() == HAL_RESET_PIN) {
/* Reset pin count increment */
new_counter = counter + 1;
}
if (new_counter == MYNEWT_VAL_USBD_DFU_RESET_COUNT) {
start_dfu = true;
new_counter = 0;
}
if (new_counter != counter) {
/* Write if value changed */
hal_nvreg_write(MYNEWT_VAL_USBD_DFU_RESET_COUNT_NVREG, new_counter);
}
} else if (MYNEWT_VAL_USBD_DFU_MAGIC_NVREG >= 0 &&
(hal_nvreg_read(MYNEWT_VAL_USBD_DFU_MAGIC_NVREG) ==
MYNEWT_VAL_USBD_DFU_MAGIC_VALUE)) {
/* Reset flag in NVReg */
hal_nvreg_write(MYNEWT_VAL_USBD_DFU_MAGIC_NVREG, 0);
start_dfu = true;
}
if (start_dfu) {
tinyusb_start();
}
}
void
auto_confirm_cb(struct os_event *event)
{
img_mgmt_state_confirm();
}
void
dfu_init(void)
{
os_callout_init(&auto_confirm_callout, os_eventq_dflt_get(),
auto_confirm_cb, NULL);
os_callout_reset(&auto_confirm_callout, OS_TICKS_PER_SEC * MYNEWT_VAL(USBD_DFU_AUTO_CONFIRM_TIME));
}