First stab at OTA-update

Introduce new testing-app example.
Add a new coap error code for blockwise transfer.
Add include-file for bootloader callbacks (jumptable).
Note that only the bootloader for osd-merkur-256 will support
OTA-update, the -128 simply has not enough flash memory, so only
in the -256 we have the bootloader functions in the jump-table
of the bootloader and in the bootloader-if.h include-file.
This commit is contained in:
Ralf Schlatterbeck 2017-07-31 13:26:29 +02:00
parent 1a57b55d8f
commit c35be7c066
13 changed files with 455 additions and 4 deletions

View file

@ -94,6 +94,7 @@ typedef enum {
NOT_FOUND_4_04 = 132, /* NOT_FOUND */
METHOD_NOT_ALLOWED_4_05 = 133, /* METHOD_NOT_ALLOWED */
NOT_ACCEPTABLE_4_06 = 134, /* NOT_ACCEPTABLE */
REQUEST_ENTITY_INCOMPLETE_4_08 = 136, /* REQUEST_ENTITY_INCOMPLETE */
PRECONDITION_FAILED_4_12 = 140, /* BAD_REQUEST */
REQUEST_ENTITY_TOO_LARGE_4_13 = 141, /* REQUEST_ENTITY_TOO_LARGE */
UNSUPPORTED_MEDIA_TYPE_4_15 = 143, /* UNSUPPORTED_MEDIA_TYPE */

View file

@ -482,6 +482,7 @@ const struct rest_implementation coap_rest_implementation = {
NOT_FOUND_4_04,
METHOD_NOT_ALLOWED_4_05,
NOT_ACCEPTABLE_4_06,
REQUEST_ENTITY_INCOMPLETE_4_08,
REQUEST_ENTITY_TOO_LARGE_4_13,
UNSUPPORTED_MEDIA_TYPE_4_15,
INTERNAL_SERVER_ERROR_5_00,

View file

@ -56,6 +56,7 @@ struct rest_implementation_status {
const unsigned int NOT_FOUND; /* NOT_FOUND_4_04, NOT_FOUND_404 */
const unsigned int METHOD_NOT_ALLOWED; /* METHOD_NOT_ALLOWED_4_05, METHOD_NOT_ALLOWED_405 */
const unsigned int NOT_ACCEPTABLE; /* NOT_ACCEPTABLE_4_06, NOT_ACCEPTABLE_406 */
const unsigned int REQUEST_ENTITY_INCOMPLETE; /* REQUEST_ENTITY_INCOMPLETE_4_08, REQUEST_ENTITY_INCOMPLETE_408 */
const unsigned int REQUEST_ENTITY_TOO_LARGE; /* REQUEST_ENTITY_TOO_LARGE_4_13, REQUEST_ENTITY_TOO_LARGE_413 */
const unsigned int UNSUPPORTED_MEDIA_TYPE; /* UNSUPPORTED_MEDIA_TYPE_4_15, UNSUPPORTED_MEDIA_TYPE_415 */

View file

@ -0,0 +1,49 @@
# Set this to the name of your sketch (without extension .pde)
SKETCH=sketch
EXE=ota
all: $(EXE)
CONTIKI=../../..
# Contiki IPv6 configuration
CONTIKI_WITH_IPV6 = 1
CFLAGS += -DPROJECT_CONF_H=\"project-conf.h\"
PROJECT_SOURCEFILES += res_upload_image.c ${SKETCH}.cpp
# variable for Makefile.include
ifneq ($(TARGET), minimal-net)
CFLAGS += -DUIP_CONF_IPV6_RPL=1
else
# minimal-net does not support RPL under Linux and is mostly used to test CoAP only
${info INFO: compiling without RPL}
CFLAGS += -DUIP_CONF_IPV6_RPL=0
CFLAGS += -DHARD_CODED_ADDRESS=\"fdfd::10\"
${info INFO: compiling with large buffers}
CFLAGS += -DUIP_CONF_BUFFER_SIZE=2048
CFLAGS += -DREST_MAX_CHUNK_SIZE=1024
CFLAGS += -DCOAP_MAX_HEADER_SIZE=640
endif
# linker optimizations
SMALL=1
# REST Engine shall use Erbium CoAP implementation
APPS += er-coap
APPS += rest-engine
APPS += arduino #json-resource #time json arduino
include $(CONTIKI)/Makefile.include
include $(CONTIKI)/apps/arduino/Makefile.include
avr-size: $(EXE).$(TARGET).sz
flash: $(EXE).$(TARGET).u # $(EXE).$(TARGET).eu
.PHONY: flash avr-size
.PRECIOUS: $(EXE).$(TARGET).hex $(EXE).$(TARGET).eep
AVRDUDE_PORT=/dev/ttyUSB1

View file

@ -0,0 +1,81 @@
==========
OTA Update
==========
OTA stands for "Over the Air". OTA update is used to flash a new
firmware to a device over the air. This document (some of) the
requirements for OTA.
Security
========
Position Independent Code
=========================
The new contiki-osd-merkur-256 target should have enough memory for two
independent application images. An application image should include the
code for over-the-air update to ensure it can be upgraded in the field.
Upgrading an image means writing a new image into the other half of the
flash memory (the part which does not run the current image). Since an
image has internal addresses and is usually linked to fixed addresses we
have two options:
- Find a way to generate position independent code for the Atmel
microcontrollers so that an image can be used in the lower- or upper
half of flash memory
- Generate two images, one linked for the lower, one linked for the
upper half of flash memory.
It seems the GCC Atmel compiler cannot generate position independent
code. So we have to modify the first option to make it work: We can use
some magic during loading of an image to link it to the correct half of
flash-memory during loading. This can either mean full relocation of the
image (the same job that is normally done before runtime by the linker)
or offline-generation of a jump-table for all objects (functions) that
are accessed and the bootloader then only relocates the addresses in the
jump-table.
The first implementation will use two images (one for upper-, one for
lower half).
Memory Layout
=============
+--------------------------------------+
| 3E000-3FFFF Bootloader |
+--------------------------------------+
| 3DE00-3DFFF Flash image directory |
+--------------------------------------+
| 3DC00-3DDFF IRQVec copy upper image |
+--------------------------------------+
| 1F100-3DBFF |
| Upper Image (w/o first two pages) |
| |
| |
+--------------------------------------+
| 1EF00-1F0FF IRQVec upper image |
+--------------------------------------+
| 1ED00-1EEFF IRQVec copy lower image |
+--------------------------------------+
| 00200-1ECFF |
| Lower Image (w/o first two pages) |
| |
| |
+--------------------------------------+
| 00000-001FF IRQVec running image |
+--------------------------------------+
We have two identical images. Each image contains the IRQ vectors (and
some code after the vector table) in the lower two pages. A copy of
these two pages is kept in two pages after the image. The reason is that
the IRQ vectors are fixed at address 00000 in this processor
architecture. So for running an image we need to copy the irq-vectors to
the fixed location (and therefore we keep a backup to be able to restore
the original image at that location).
Note that in the table above an image as generated by the compiler
consists of the IRQ vectors in the first two pages (00000-001FF for the
first and 1EF00-1F0FF for the second image) plus the rest of the code
for that image.

View file

@ -0,0 +1,2 @@
#include <arduino-process.h>
AUTOSTART_PROCESSES(&arduino_sketch);

View file

@ -0,0 +1,101 @@
/*
* Copyright (c) 2010, Swedish Institute of Computer Science.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the Institute nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
*
*/
#ifndef PROJECT_RPL_WEB_CONF_H_
#define PROJECT_RPL_WEB_CONF_H_
#define PLATFORM_HAS_LEDS 1
//#define PLATFORM_HAS_BUTTON 1
#define PLATFORM_HAS_BATTERY 1
#define SICSLOWPAN_CONF_FRAG 1
/* For Debug: Dont allow MCU sleeping between channel checks */
#undef RDC_CONF_MCU_SLEEP
#define RDC_CONF_MCU_SLEEP 0
/* Disabling RDC for demo purposes. Core updates often require more memory. */
/* For projects, optimize memory and enable RDC again. */
// #undef NETSTACK_CONF_RDC
//#define NETSTACK_CONF_RDC nullrdc_driver
/* Increase rpl-border-router IP-buffer when using more than 64. */
#undef REST_MAX_CHUNK_SIZE
#define REST_MAX_CHUNK_SIZE 64
/* Estimate your header size, especially when using Proxy-Uri. */
/*
#undef COAP_MAX_HEADER_SIZE
#define COAP_MAX_HEADER_SIZE 70
*/
/* The IP buffer size must fit all other hops, in particular the border router. */
#undef UIP_CONF_BUFFER_SIZE
#define UIP_CONF_BUFFER_SIZE 256
/* Multiplies with chunk size, be aware of memory constraints. */
#undef COAP_MAX_OPEN_TRANSACTIONS
#define COAP_MAX_OPEN_TRANSACTIONS 4
/* Must be <= open transaction number, default is COAP_MAX_OPEN_TRANSACTIONS-1. */
/*
#undef COAP_MAX_OBSERVERS
#define COAP_MAX_OBSERVERS 2
*/
/* Filtering .well-known/core per query can be disabled to save space. */
/*
#undef COAP_LINK_FORMAT_FILTERING
#define COAP_LINK_FORMAT_FILTERING 0
*/
/* Save some memory for the sky platform. */
/*
#undef NBR_TABLE_CONF_MAX_NEIGHBORS
#define NBR_TABLE_CONF_MAX_NEIGHBORS 10
#undef UIP_CONF_MAX_ROUTES
#define UIP_CONF_MAX_ROUTES 10
*/
/* Reduce 802.15.4 frame queue to save RAM. */
/*
#undef QUEUEBUF_CONF_NUM
#define QUEUEBUF_CONF_NUM 4
*/
/*
#undef SICSLOWPAN_CONF_FRAG
#define SICSLOWPAN_CONF_FRAG 1
*/
#endif /* PROJECT_RPL_WEB_CONF_H_ */

View file

@ -0,0 +1,175 @@
/*
* Copyright (c) 2017, Marcus Priesch, Ralf Schlatterbeck
* with code from the res-plugtest-large-update.c by
* Copyright (c) 2013, Institute for Pervasive Computing, ETH Zurich
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the Institute nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* This file is part of the Contiki operating system.
*/
/**
* \file
* Over-the-air update using blockwise transfer
* \author
* Marcus Priesch <marcus@priesch.co.at>
* Ralf Schlatterbeck <rsc@runtux.com>
*/
#include <string.h>
#include "sys/cc.h"
#include "rest-engine.h"
#include "er-coap.h"
#include "contiki.h"
#include "contiki-net.h"
#include "er-coap.h"
#include "Arduino.h"
#include <avr/interrupt.h>
#include "bootloader_if.h"
#if 1
#include <stdio.h>
#define PRINTF(x) printf x
#else
#define PRINTF(x)
#endif
static const uint32_t partition_start = 0x1ef00; //bootloader_get_part_start ();
static const uint32_t partition_size = 5000; //bootloader_get_part_size ();
// We allocate this statically, otherwise we cannot flash a new image
// when ram is exhausted!
static uint8_t current_page [256];
static size_t current_offset = 0;
#define PAGESIZE (sizeof (current_page))
static void
res_put_handler(void *request, void *response, uint8_t *buffer, uint16_t preferred_size, int32_t *offset)
{
coap_packet_t *const packet = (coap_packet_t *)request;
uint8_t *in_data = NULL;
size_t len = 0;
uint8_t sreg = SREG;
unsigned int ct = -1;
REST.get_header_content_type(request, &ct);
/* Require content_type APPLICATION_OCTET_STREAM */
if (ct != REST.type.APPLICATION_OCTET_STREAM) {
REST.set_response_status(response, REST.status.BAD_REQUEST);
const char *error_msg = "ContentType";
REST.set_response_payload(response, error_msg, strlen(error_msg));
return;
}
len = REST.get_request_payload (request, (const uint8_t **)&in_data);
PRINTF (("cur: %lu len: %lu, offset: %lu\n",
(uint32_t)current_offset, (uint32_t)len, (uint32_t)*offset));
PRINTF (("b1-offs: %lu, b1-size: %u, b1-num: %lu b1-more: %d b1-size1: %lu\n",
packet->block1_offset, packet->block1_size, packet->block1_num,
packet->block1_more, packet->size1));
if (len == 0 || NULL == in_data) {
REST.set_response_status(response, REST.status.BAD_REQUEST);
const char *error_msg = "NoPayload";
REST.set_response_payload(response, error_msg, strlen(error_msg));
return;
}
if (packet->block1_offset > current_offset) {
REST.set_response_status(response, REST.status.REQUEST_ENTITY_INCOMPLETE);
const char *error_msg = "OutOfSequence";
REST.set_response_payload(response, error_msg, strlen(error_msg));
return;
}
/* Old packet or retransmission, immediately confirm */
if (packet->block1_offset + len <= current_offset) {
REST.set_response_status(response, REST.status.CHANGED);
coap_set_header_block1
(response, packet->block1_num, 0, packet->block1_size);
return;
}
// FIXME: blocksize may be larger than our flash page size
if (len > PAGESIZE) {
REST.set_response_status(response, REST.status.INTERNAL_SERVER_ERROR);
const char *error_msg = "GRMPF: PageSize";
REST.set_response_payload(response, error_msg, strlen(error_msg));
return;
}
// FIXME: blocksize may be larger than our flash page size
// So we should handle this case and repeatedly flash a block until the
// received data is written.
if (current_offset % PAGESIZE + len > PAGESIZE) {
REST.set_response_status(response, REST.status.INTERNAL_SERVER_ERROR);
const char *error_msg = "GRMPF: blocksize";
REST.set_response_payload(response, error_msg, strlen(error_msg));
return;
}
// Should never happen, we test for < and > earlier.
if (packet->block1_offset != current_offset) {
REST.set_response_status(response, REST.status.INTERNAL_SERVER_ERROR);
const char *error_msg = "GRMPF: Offset";
REST.set_response_payload(response, error_msg, strlen(error_msg));
return;
}
if(packet->block1_offset + len > partition_size) {
REST.set_response_status(response,
REST.status.REQUEST_ENTITY_TOO_LARGE);
REST.set_response_payload(
response,
buffer,
sprintf((char *)buffer, "%luB max.", partition_size));
return;
}
memcpy (current_page + current_offset % PAGESIZE, in_data, len);
if (current_offset % PAGESIZE == 0 || !packet->block1_more) {
// WRITE Flash here
PRINTF (("Flashing: %lu\n", (uint32_t)len));
sreg = SREG;
cli ();
bootloader_write_page_to_flash
(partition_start + current_offset, len, current_page);
SREG = sreg;
}
current_offset += len;
REST.set_response_status(response, REST.status.CHANGED);
coap_set_header_block1(response, packet->block1_num, 0, packet->block1_size);
}
RESOURCE(
res_upload_image
, "title=\"Flash memory upgrade\";rt=\"block\""
, NULL
, NULL
, res_put_handler
, NULL
);

View file

@ -0,0 +1,28 @@
/*
* Gardena 9V Magnet-Valve
* We have a CoAP Resource for the Valve, it can be in state 1 (on) and
* 0 (off).
* Transition on-off outputs a negative pulse
* Transition off-on outputs a positive pulse
*/
extern "C" {
#include <stdio.h>
#include "contiki.h"
#include "contiki-net.h"
#include "er-coap.h"
extern resource_t res_upload_image;
char resname[] = "update";
}
void setup (void)
{
rest_init_engine ();
rest_activate_resource (&res_upload_image, resname);
}
void loop (void)
{
//printf ("Huhu\n");
}

View file

@ -0,0 +1,6 @@
#ifndef BOOTLOADER_IF_H_
#define BOOTLOADER_IF_H_
extern uint8_t bootloader_get_mac(uint8_t);
#endif /* BOOTLOADER_IF_H_ */

View file

@ -39,6 +39,7 @@
#endif
#include "contiki.h"
#include "bootloader_if.h"
#include <avr/pgmspace.h>
#include <avr/eeprom.h>
#include <stdio.h>
@ -85,8 +86,6 @@ const uint8_t default_domain_name[] PROGMEM = PARAMS_DOMAINNAME;
#if PARAMETER_STORAGE==0
/* 0 Hard coded, minmal program and eeprom usage. */
extern uint8_t bootloader_get_mac(uint8_t);
uint8_t
params_get_eui64(uint8_t *eui64) {
#if CONTIKI_CONF_RANDOM_MAC

View file

@ -0,0 +1,8 @@
#ifndef BOOTLOADER_IF_H_
#define BOOTLOADER_IF_H_
extern uint8_t bootloader_get_mac(uint8_t);
extern int bootloader_write_page_to_flash
(uint32_t address, unsigned int size, unsigned char *p);
#endif /* BOOTLOADER_IF_H_ */

View file

@ -39,6 +39,7 @@
#endif
#include "contiki.h"
#include "bootloader_if.h"
#include <avr/pgmspace.h>
#include <avr/eeprom.h>
#include <stdio.h>
@ -85,8 +86,6 @@ const uint8_t default_domain_name[] PROGMEM = PARAMS_DOMAINNAME;
#if PARAMETER_STORAGE==0
/* 0 Hard coded, minmal program and eeprom usage. */
extern uint8_t bootloader_get_mac(uint8_t);
uint8_t
params_get_eui64(uint8_t *eui64) {
#if CONTIKI_CONF_RANDOM_MAC