Hi!
This is is a RFC series of patches to add support for firmware upgrades on the OSTC3. The code have bin tested both against a "pcb" by HW and against my OSTC3 so it works and does the right things. JeanDo's ostc-companion[1] have bin the reference implementation I've looked at when I wrote this code.
There are still a couple of pain-points that I would like some feedback on:
How to start the device in service mode: To get access to the necessary commands for service mode, we can't INIT the device with INIT(0xBB), we need to INIT it with a special service mode sequence(0xAA, 0xAB, 0xCD, 0xEF). Now the INIT is sent in hw_ostc3_device_open, so how do you suggest that we refactor the code to allow another sequence after open?
The current quite crude hack is to just EXIT(0xFF) the device and re-init it after a 6s sleep. This won't work against the OSTC Sport.
The timing between command and data in write_block: The current hw_ostc3_device_write_block is a bit of copy-paste from hw_ostc3_transfer, to be able to send the data buffer to the device, without a additional copy.
Library triggered closed state: The device reboots when it gets the FLASH_FIRM-command so how can we "not fail" when the application then tries to talk to the device afterwards, or even call hw_ostc3_device_close?
There are also a couple of other FIXME's in the code, which I plan on cleaning up, but I thought that I should send out a first RFC patch series to get some additional feedback going.
Any other ideas about how the code looks?
//Anton
1. https://bitbucket.org/JeanDo/ostc-companion/
This imports Tiny AES128 from https://github.com/kokke/tiny-AES128-C for use in the decoding of OSTC3 firmwares.
This aes-code is released into the public domain.
Signed-off-by: Anton Lundin glance@acc.umu.se --- msvc/libdivecomputer.vcproj | 6 + src/Makefile.am | 1 + src/aes.c | 486 ++++++++++++++++++++++++++++++++++++++++++++ src/aes.h | 16 ++ 4 files changed, 509 insertions(+) create mode 100644 src/aes.c create mode 100644 src/aes.h
diff --git a/msvc/libdivecomputer.vcproj b/msvc/libdivecomputer.vcproj index 78a8190..10a4cf7 100644 --- a/msvc/libdivecomputer.vcproj +++ b/msvc/libdivecomputer.vcproj @@ -247,6 +247,9 @@ > </File> <File + RelativePath="..\src\aes.c" + > + <File RelativePath="..\src\hw_ostc3.c" > </File> @@ -537,6 +540,9 @@ > </File> <File + RelativePath="..\src\aes.h" + > + <File RelativePath="..\include\libdivecomputer\hw_ostc3.h" > </File> diff --git a/src/Makefile.am b/src/Makefile.am index 595f0c7..eb39a4b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -43,6 +43,7 @@ libdivecomputer_la_SOURCES = \ ihex.h ihex.c \ hw_ostc.c hw_ostc_parser.c \ hw_frog.c \ + aes.h aes.c \ hw_ostc3.c \ cressi_edy.c cressi_edy_parser.c \ cressi_leonardo.c cressi_leonardo_parser.c \ diff --git a/src/aes.c b/src/aes.c new file mode 100644 index 0000000..000a067 --- /dev/null +++ b/src/aes.c @@ -0,0 +1,486 @@ +/* + +This is an implementation of the AES128 algorithm, specifically ECB mode. + +The implementation is verified against the test vectors in: + National Institute of Standards and Technology Special Publication 800-38A 2001 ED + +ECB-AES128 +---------- + + plain-text: + 6bc1bee22e409f96e93d7e117393172a + ae2d8a571e03ac9c9eb76fac45af8e51 + 30c81c46a35ce411e5fbc1191a0a52ef + f69f2445df4f9b17ad2b417be66c3710 + + key: + 2b7e151628aed2a6abf7158809cf4f3c + + resulting cipher + 50fe67cc996d32b6da0937e99bafec60 + d9a4dada0892239f6b8b3d7680e15674 + a78819583f0308e7a6bf36b1386abf23 + c6d3416d29165c6fcb8e51a227ba994e + + +NOTE: String length must be evenly divisible by 16byte (str_len % 16 == 0) + You should pad the end of the string with zeros if this is not the case. + +*/ + +#ifndef _AES_C_ +#define _AES_C_ + + +/*****************************************************************************/ +/* Includes: */ +/*****************************************************************************/ +#include <stdint.h> +#include "aes.h" + + +/*****************************************************************************/ +/* Defines: */ +/*****************************************************************************/ +// The number of columns comprising a state in AES. This is a constant in AES. Value=4 +#define Nb 4 +// The number of 32 bit words in a key. +#define Nk 4 +// Key length in bytes [128 bit] +#define keyln 16 +// The number of rounds in AES Cipher. +#define Nr 10 + + +/*****************************************************************************/ +/* Private variables: */ +/*****************************************************************************/ +// in - pointer to the CipherText to be decrypted. +// out - pointer to buffer to hold output of the decryption. +// state - array holding the intermediate results during decryption. +static uint8_t* in, *out, state[4][4]; + +// The array that stores the round keys. +static uint8_t RoundKey[176]; + +// The Key input to the AES Program +static uint8_t* Key; + +// The lookup-tables are marked const so they can be placed in read-only storage instead of RAM +// The numbers below can be computed dynamically trading ROM for RAM - +// This can be useful in (embedded) bootloader applications, where ROM is often limited. +static const uint8_t sbox[256] = { + //0 1 2 3 4 5 6 7 8 9 A B C D E F + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, + 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, + 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, + 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, + 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, + 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, + 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, + 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, + 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, + 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, + 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 }; + +static const uint8_t rsbox[256] = +{ 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb +, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb +, 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e +, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25 +, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92 +, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84 +, 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06 +, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b +, 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73 +, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e +, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b +, 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4 +, 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f +, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef +, 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61 +, 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d }; + + +// The round constant word array, Rcon[i], contains the values given by +// x to th e power (i-1) being powers of x (x is denoted as {02}) in the field GF(2^8) +// Note that i starts at 1, not 0). +static const uint8_t Rcon[255] = { + 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, + 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, + 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, + 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, + 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, + 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, + 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, + 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, + 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, + 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, + 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, + 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, + 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, + 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, + 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, + 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb }; + + +/*****************************************************************************/ +/* Private functions: */ +/*****************************************************************************/ +static uint8_t getSBoxValue(uint8_t num) +{ + return sbox[num]; +} + +static uint8_t getSBoxInvert(uint8_t num) +{ + return rsbox[num]; +} + + +// This function produces Nb(Nr+1) round keys. The round keys are used in each round to decrypt the states. +static void KeyExpansion() +{ + uint32_t i, j, k; + uint8_t tempa[4]; // used for the column/row operations + + // The first round key is the key itself. + for(i = 0; i < Nk; ++i) + { + RoundKey[(i * 4) + 0] = Key[(i * 4) + 0]; + RoundKey[(i * 4) + 1] = Key[(i * 4) + 1]; + RoundKey[(i * 4) + 2] = Key[(i * 4) + 2]; + RoundKey[(i * 4) + 3] = Key[(i * 4) + 3]; + } + + // All other round keys are found from the previous round keys. + for(; (i < (Nb * (Nr + 1))); ++i) + { + for(j = 0; j < 4; ++j) + { + tempa[j]=RoundKey[(i-1) * 4 + j]; + } + if (i % Nk == 0) + { + // This function rotates the 4 bytes in a word to the left once. + // [a0,a1,a2,a3] becomes [a1,a2,a3,a0] + + // Function RotWord() + { + k = tempa[0]; + tempa[0] = tempa[1]; + tempa[1] = tempa[2]; + tempa[2] = tempa[3]; + tempa[3] = k; + } + + // SubWord() is a function that takes a four-byte input word and + // applies the S-box to each of the four bytes to produce an output word. + + // Function Subword() + { + tempa[0] = getSBoxValue(tempa[0]); + tempa[1] = getSBoxValue(tempa[1]); + tempa[2] = getSBoxValue(tempa[2]); + tempa[3] = getSBoxValue(tempa[3]); + } + + tempa[0] = tempa[0] ^ Rcon[i/Nk]; + } + else if (Nk > 6 && i % Nk == 4) + { + // Function Subword() + { + tempa[0] = getSBoxValue(tempa[0]); + tempa[1] = getSBoxValue(tempa[1]); + tempa[2] = getSBoxValue(tempa[2]); + tempa[3] = getSBoxValue(tempa[3]); + } + } + RoundKey[i * 4 + 0] = RoundKey[(i - Nk) * 4 + 0] ^ tempa[0]; + RoundKey[i * 4 + 1] = RoundKey[(i - Nk) * 4 + 1] ^ tempa[1]; + RoundKey[i * 4 + 2] = RoundKey[(i - Nk) * 4 + 2] ^ tempa[2]; + RoundKey[i * 4 + 3] = RoundKey[(i - Nk) * 4 + 3] ^ tempa[3]; + } +} + +// This function adds the round key to state. +// The round key is added to the state by an XOR function. +static void AddRoundKey(uint8_t round) +{ + uint8_t i,j; + for(i=0;i<4;i++) + { + for(j = 0; j < 4; ++j) + { + state[j][i] ^= RoundKey[round * Nb * 4 + i * Nb + j]; + } + } +} + +// The SubBytes Function Substitutes the values in the +// state matrix with values in an S-box. +static void SubBytes() +{ + uint8_t i, j; + for(i = 0; i < 4; ++i) + { + for(j = 0; j < 4; ++j) + { + state[i][j] = getSBoxValue(state[i][j]); + } + } +} + +// The ShiftRows() function shifts the rows in the state to the left. +// Each row is shifted with different offset. +// Offset = Row number. So the first row is not shifted. +static void ShiftRows() +{ + uint8_t temp; + + // Rotate first row 1 columns to left + temp = state[1][0]; + state[1][0] = state[1][1]; + state[1][1] = state[1][2]; + state[1][2] = state[1][3]; + state[1][3] = temp; + + // Rotate second row 2 columns to left + temp = state[2][0]; + state[2][0] = state[2][2]; + state[2][2] = temp; + + temp = state[2][1]; + state[2][1] = state[2][3]; + state[2][3] = temp; + + // Rotate third row 3 columns to left + temp = state[3][0]; + state[3][0] = state[3][3]; + state[3][3] = state[3][2]; + state[3][2] = state[3][1]; + state[3][1] = temp; +} + +static uint8_t xtime(uint8_t x) +{ + return ((x<<1) ^ (((x>>7) & 1) * 0x1b)); +} + +// MixColumns function mixes the columns of the state matrix +static void MixColumns() +{ + uint8_t i; + uint8_t Tmp,Tm,t; + for(i = 0; i < 4; ++i) + { + t = state[0][i]; + Tmp = state[0][i] ^ state[1][i] ^ state[2][i] ^ state[3][i] ; + Tm = state[0][i] ^ state[1][i] ; Tm = xtime(Tm); state[0][i] ^= Tm ^ Tmp ; + Tm = state[1][i] ^ state[2][i] ; Tm = xtime(Tm); state[1][i] ^= Tm ^ Tmp ; + Tm = state[2][i] ^ state[3][i] ; Tm = xtime(Tm); state[2][i] ^= Tm ^ Tmp ; + Tm = state[3][i] ^ t ; Tm = xtime(Tm); state[3][i] ^= Tm ^ Tmp ; + } +} + +// Multiplty is a macro used to multiply numbers in the field GF(2^8) +#define Multiply(x,y) (((y & 1) * x) ^ ((y>>1 & 1) * xtime(x)) ^ ((y>>2 & 1) * xtime(xtime(x))) ^ ((y>>3 & 1) * xtime(xtime(xtime(x)))) ^ ((y>>4 & 1) * xtime(xtime(xtime(xtime(x)))))) + + +// MixColumns function mixes the columns of the state matrix. +// The method used to multiply may be difficult to understand for the inexperienced. +// Please use the references to gain more information. +static void InvMixColumns() +{ + int i; + uint8_t a,b,c,d; + for(i=0;i<4;i++) + { + + a = state[0][i]; + b = state[1][i]; + c = state[2][i]; + d = state[3][i]; + + + state[0][i] = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^ Multiply(d, 0x09); + state[1][i] = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^ Multiply(d, 0x0d); + state[2][i] = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^ Multiply(d, 0x0b); + state[3][i] = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^ Multiply(d, 0x0e); + } +} + + +// The SubBytes Function Substitutes the values in the +// state matrix with values in an S-box. +static void InvSubBytes() +{ + uint8_t i,j; + for(i=0;i<4;i++) + { + for(j=0;j<4;j++) + { + state[i][j] = getSBoxInvert(state[i][j]); + } + } +} + + +static void InvShiftRows() +{ + uint8_t temp; + + // Rotate first row 1 columns to right + temp=state[1][3]; + state[1][3]=state[1][2]; + state[1][2]=state[1][1]; + state[1][1]=state[1][0]; + state[1][0]=temp; + + // Rotate second row 2 columns to right + temp=state[2][0]; + state[2][0]=state[2][2]; + state[2][2]=temp; + + temp=state[2][1]; + state[2][1]=state[2][3]; + state[2][3]=temp; + + // Rotate third row 3 columns to right + temp=state[3][0]; + state[3][0]=state[3][1]; + state[3][1]=state[3][2]; + state[3][2]=state[3][3]; + state[3][3]=temp; +} + + +// Cipher is the main function that encrypts the PlainText. +static void Cipher() +{ + uint8_t i, j, round = 0; + + //Copy the input PlainText to state array. + for(i = 0; i < 4; ++i) + { + for(j = 0; j < 4 ; ++j) + { + state[j][i] = in[(i * 4) + j]; + } + } + + // Add the First round key to the state before starting the rounds. + AddRoundKey(0); + + // There will be Nr rounds. + // The first Nr-1 rounds are identical. + // These Nr-1 rounds are executed in the loop below. + for(round = 1; round < Nr; ++round) + { + SubBytes(); + ShiftRows(); + MixColumns(); + AddRoundKey(round); + } + + // The last round is given below. + // The MixColumns function is not here in the last round. + SubBytes(); + ShiftRows(); + AddRoundKey(Nr); + + // The encryption process is over. + // Copy the state array to output array. + for(i = 0; i < 4; ++i) + { + for(j = 0; j < 4; ++j) + { + out[(i * 4) + j] = state[j][i]; + } + } +} + +static void InvCipher() +{ + uint8_t i,j,round=0; + + //Copy the input CipherText to state array. + for(i=0;i<4;i++) + { + for(j=0;j<4;j++) + { + state[j][i] = in[i*4 + j]; + } + } + + // Add the First round key to the state before starting the rounds. + AddRoundKey(Nr); + + // There will be Nr rounds. + // The first Nr-1 rounds are identical. + // These Nr-1 rounds are executed in the loop below. + for(round=Nr-1;round>0;round--) + { + InvShiftRows(); + InvSubBytes(); + AddRoundKey(round); + InvMixColumns(); + } + + // The last round is given below. + // The MixColumns function is not here in the last round. + InvShiftRows(); + InvSubBytes(); + AddRoundKey(0); + + // The decryption process is over. + // Copy the state array to output array. + for(i=0;i<4;i++) + { + for(j=0;j<4;j++) + { + out[i*4+j]=state[j][i]; + } + } +} + + +/*****************************************************************************/ +/* Public functions: */ +/*****************************************************************************/ + +void AES128_ECB_encrypt(uint8_t* input, uint8_t* key, uint8_t *output) +{ + // Copy the Key and CipherText + Key = key; + in = input; + out = output; + + // The KeyExpansion routine must be called before encryption. + KeyExpansion(); + + // The next function call encrypts the PlainText with the Key using AES algorithm. + Cipher(); +} + +void AES128_ECB_decrypt(uint8_t* input, uint8_t* key, uint8_t *output) +{ + Key = key; + in = input; + out = output; + + KeyExpansion(); + + InvCipher(); +} + +#endif //_AES_C_ + + diff --git a/src/aes.h b/src/aes.h new file mode 100644 index 0000000..5fb2176 --- /dev/null +++ b/src/aes.h @@ -0,0 +1,16 @@ +#ifndef _AES_H_ +#define _AES_H_ + +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void AES128_ECB_encrypt(uint8_t* input, uint8_t* key, uint8_t *output); +void AES128_ECB_decrypt(uint8_t* input, uint8_t* key, uint8_t *output); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif //_AES_H_
Dear Anton Lundin,
I'm used to program in C# Â and have zaro experience with C.
Are you aware of any c# code to link to the libdivecomputer ? I can't figure out from the example how to download and use data from a VEO2 computer. Thanks for help,  Loïc
Message du 14/11/14 18:02 De : "Anton Lundin" A : "Libdivecomputer Mailinglist" Copie à : Objet : [RFC PATCH 01/10] Import Tiny AES128
This imports Tiny AES128 from https://github.com/kokke/tiny-AES128-C for use in the decoding of OSTC3 firmwares.
This aes-code is released into the public domain.
Signed-off-by: Anton Lundin
msvc/libdivecomputer.vcproj | 6 + src/Makefile.am | 1 + src/aes.c | 486 ++++++++++++++++++++++++++++++++++++++++++++ src/aes.h | 16 ++ 4 files changed, 509 insertions(+) create mode 100644 src/aes.c create mode 100644 src/aes.h
diff --git a/msvc/libdivecomputer.vcproj b/msvc/libdivecomputer.vcproj index 78a8190..10a4cf7 100644 --- a/msvc/libdivecomputer.vcproj +++ b/msvc/libdivecomputer.vcproj @@ -247,6 +247,9 @@
- RelativePath="..\src\aes.c"
- RelativePath="..\src\hw_ostc3.c"
@@ -537,6 +540,9 @@
- RelativePath="..\src\aes.h"
- RelativePath="..\include\libdivecomputer\hw_ostc3.h"
diff --git a/src/Makefile.am b/src/Makefile.am index 595f0c7..eb39a4b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -43,6 +43,7 @@ libdivecomputer_la_SOURCES = \ ihex.h ihex.c \ hw_ostc.c hw_ostc_parser.c \ hw_frog.c \
- aes.h aes.c \
hw_ostc3.c \ cressi_edy.c cressi_edy_parser.c \ cressi_leonardo.c cressi_leonardo_parser.c \ diff --git a/src/aes.c b/src/aes.c new file mode 100644 index 0000000..000a067 --- /dev/null +++ b/src/aes.c @@ -0,0 +1,486 @@ +/*
+This is an implementation of the AES128 algorithm, specifically ECB mode.
+The implementation is verified against the test vectors in:
- National Institute of Standards and Technology Special Publication 800-38A 2001 ED
+ECB-AES128 +----------
- plain-text:
- 6bc1bee22e409f96e93d7e117393172a
- ae2d8a571e03ac9c9eb76fac45af8e51
- 30c81c46a35ce411e5fbc1191a0a52ef
- f69f2445df4f9b17ad2b417be66c3710
- key:
- 2b7e151628aed2a6abf7158809cf4f3c
- resulting cipher
- 50fe67cc996d32b6da0937e99bafec60
- d9a4dada0892239f6b8b3d7680e15674
- a78819583f0308e7a6bf36b1386abf23
- c6d3416d29165c6fcb8e51a227ba994e
+NOTE: String length must be evenly divisible by 16byte (str_len % 16 == 0)
- You should pad the end of the string with zeros if this is not the case.
+*/
+#ifndef _AES_C_ +#define _AES_C_
+/*****************************************************************************/ +/* Includes: */ +/*****************************************************************************/ +#include +#include "aes.h"
+/*****************************************************************************/ +/* Defines: */ +/*****************************************************************************/ +// The number of columns comprising a state in AES. This is a constant in AES. Value=4 +#define Nb 4 +// The number of 32 bit words in a key. +#define Nk 4 +// Key length in bytes [128 bit] +#define keyln 16 +// The number of rounds in AES Cipher. +#define Nr 10
+/*****************************************************************************/ +/* Private variables: */ +/*****************************************************************************/ +// in - pointer to the CipherText to be decrypted. +// out - pointer to buffer to hold output of the decryption. +// state - array holding the intermediate results during decryption. +static uint8_t* in, *out, state[4][4];
+// The array that stores the round keys. +static uint8_t RoundKey[176];
+// The Key input to the AES Program +static uint8_t* Key;
+// The lookup-tables are marked const so they can be placed in read-only storage instead of RAM +// The numbers below can be computed dynamically trading ROM for RAM - +// This can be useful in (embedded) bootloader applications, where ROM is often limited. +static const uint8_t sbox[256] = {
- //0 1 2 3 4 5 6 7 8 9 A B C D E F
- 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
- 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
- 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
- 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
- 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
- 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
- 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
- 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
- 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
- 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
- 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
- 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
- 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
- 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
- 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
- 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 };
+static const uint8_t rsbox[256] = +{ 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb +, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb +, 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e +, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25 +, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92 +, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84 +, 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06 +, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b +, 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73 +, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e +, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b +, 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4 +, 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f +, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef +, 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61 +, 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d };
+// The round constant word array, Rcon[i], contains the values given by +// x to th e power (i-1) being powers of x (x is denoted as {02}) in the field GF(2^8) +// Note that i starts at 1, not 0). +static const uint8_t Rcon[255] = {
- 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a,
- 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39,
- 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a,
- 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8,
- 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef,
- 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc,
- 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b,
- 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3,
- 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94,
- 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20,
- 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35,
- 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f,
- 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04,
- 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63,
- 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd,
- 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb };
+/*****************************************************************************/ +/* Private functions: */ +/*****************************************************************************/ +static uint8_t getSBoxValue(uint8_t num) +{
- return sbox[num];
+}
+static uint8_t getSBoxInvert(uint8_t num) +{
- return rsbox[num];
+}
+// This function produces Nb(Nr+1) round keys. The round keys are used in each round to decrypt the states. +static void KeyExpansion() +{
- uint32_t i, j, k;
- uint8_t tempa[4]; // used for the column/row operations
- // The first round key is the key itself.
- for(i = 0; i < Nk; ++i)
- {
- RoundKey[(i * 4) + 0] = Key[(i * 4) + 0];
- RoundKey[(i * 4) + 1] = Key[(i * 4) + 1];
- RoundKey[(i * 4) + 2] = Key[(i * 4) + 2];
- RoundKey[(i * 4) + 3] = Key[(i * 4) + 3];
- }
- // All other round keys are found from the previous round keys.
- for(; (i < (Nb * (Nr + 1))); ++i)
- {
- for(j = 0; j < 4; ++j)
- {
- tempa[j]=RoundKey[(i-1) * 4 + j];
- }
- if (i % Nk == 0)
- {
- // This function rotates the 4 bytes in a word to the left once.
- // [a0,a1,a2,a3] becomes [a1,a2,a3,a0]
- // Function RotWord()
- {
- k = tempa[0];
- tempa[0] = tempa[1];
- tempa[1] = tempa[2];
- tempa[2] = tempa[3];
- tempa[3] = k;
- }
- // SubWord() is a function that takes a four-byte input word and
- // applies the S-box to each of the four bytes to produce an output word.
- // Function Subword()
- {
- tempa[0] = getSBoxValue(tempa[0]);
- tempa[1] = getSBoxValue(tempa[1]);
- tempa[2] = getSBoxValue(tempa[2]);
- tempa[3] = getSBoxValue(tempa[3]);
- }
- tempa[0] = tempa[0] ^ Rcon[i/Nk];
- }
- else if (Nk > 6 && i % Nk == 4)
- {
- // Function Subword()
- {
- tempa[0] = getSBoxValue(tempa[0]);
- tempa[1] = getSBoxValue(tempa[1]);
- tempa[2] = getSBoxValue(tempa[2]);
- tempa[3] = getSBoxValue(tempa[3]);
- }
- }
- RoundKey[i * 4 + 0] = RoundKey[(i - Nk) * 4 + 0] ^ tempa[0];
- RoundKey[i * 4 + 1] = RoundKey[(i - Nk) * 4 + 1] ^ tempa[1];
- RoundKey[i * 4 + 2] = RoundKey[(i - Nk) * 4 + 2] ^ tempa[2];
- RoundKey[i * 4 + 3] = RoundKey[(i - Nk) * 4 + 3] ^ tempa[3];
- }
+}
+// This function adds the round key to state. +// The round key is added to the state by an XOR function. +static void AddRoundKey(uint8_t round) +{
- uint8_t i,j;
- for(i=0;i<4;i++)
- {
- for(j = 0; j < 4; ++j)
- {
- state[j][i] ^= RoundKey[round * Nb * 4 + i * Nb + j];
- }
- }
+}
+// The SubBytes Function Substitutes the values in the +// state matrix with values in an S-box. +static void SubBytes() +{
- uint8_t i, j;
- for(i = 0; i < 4; ++i)
- {
- for(j = 0; j < 4; ++j)
- {
- state[i][j] = getSBoxValue(state[i][j]);
- }
- }
+}
+// The ShiftRows() function shifts the rows in the state to the left. +// Each row is shifted with different offset. +// Offset = Row number. So the first row is not shifted. +static void ShiftRows() +{
- uint8_t temp;
- // Rotate first row 1 columns to left
- temp = state[1][0];
- state[1][0] = state[1][1];
- state[1][1] = state[1][2];
- state[1][2] = state[1][3];
- state[1][3] = temp;
- // Rotate second row 2 columns to left
- temp = state[2][0];
- state[2][0] = state[2][2];
- state[2][2] = temp;
- temp = state[2][1];
- state[2][1] = state[2][3];
- state[2][3] = temp;
- // Rotate third row 3 columns to left
- temp = state[3][0];
- state[3][0] = state[3][3];
- state[3][3] = state[3][2];
- state[3][2] = state[3][1];
- state[3][1] = temp;
+}
+static uint8_t xtime(uint8_t x) +{
- return ((x<<1) ^ (((x>>7) & 1) * 0x1b));
+}
+// MixColumns function mixes the columns of the state matrix +static void MixColumns() +{
- uint8_t i;
- uint8_t Tmp,Tm,t;
- for(i = 0; i < 4; ++i)
- {
- t = state[0][i];
- Tmp = state[0][i] ^ state[1][i] ^ state[2][i] ^ state[3][i] ;
- Tm = state[0][i] ^ state[1][i] ; Tm = xtime(Tm); state[0][i] ^= Tm ^ Tmp ;
- Tm = state[1][i] ^ state[2][i] ; Tm = xtime(Tm); state[1][i] ^= Tm ^ Tmp ;
- Tm = state[2][i] ^ state[3][i] ; Tm = xtime(Tm); state[2][i] ^= Tm ^ Tmp ;
- Tm = state[3][i] ^ t ; Tm = xtime(Tm); state[3][i] ^= Tm ^ Tmp ;
- }
+}
+// Multiplty is a macro used to multiply numbers in the field GF(2^8) +#define Multiply(x,y) (((y & 1) * x) ^ ((y>>1 & 1) * xtime(x)) ^ ((y>>2 & 1) * xtime(xtime(x))) ^ ((y>>3 & 1) * xtime(xtime(xtime(x)))) ^ ((y>>4 & 1) * xtime(xtime(xtime(xtime(x))))))
+// MixColumns function mixes the columns of the state matrix. +// The method used to multiply may be difficult to understand for the inexperienced. +// Please use the references to gain more information. +static void InvMixColumns() +{
- int i;
- uint8_t a,b,c,d;
- for(i=0;i<4;i++)
- {
- a = state[0][i];
- b = state[1][i];
- c = state[2][i];
- d = state[3][i];
- state[0][i] = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^ Multiply(d, 0x09);
- state[1][i] = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^ Multiply(d, 0x0d);
- state[2][i] = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^ Multiply(d, 0x0b);
- state[3][i] = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^ Multiply(d, 0x0e);
- }
+}
+// The SubBytes Function Substitutes the values in the +// state matrix with values in an S-box. +static void InvSubBytes() +{
- uint8_t i,j;
- for(i=0;i<4;i++)
- {
- for(j=0;j<4;j++)
- {
- state[i][j] = getSBoxInvert(state[i][j]);
- }
- }
+}
+static void InvShiftRows() +{
- uint8_t temp;
- // Rotate first row 1 columns to right
- temp=state[1][3];
- state[1][3]=state[1][2];
- state[1][2]=state[1][1];
- state[1][1]=state[1][0];
- state[1][0]=temp;
- // Rotate second row 2 columns to right
- temp=state[2][0];
- state[2][0]=state[2][2];
- state[2][2]=temp;
- temp=state[2][1];
- state[2][1]=state[2][3];
- state[2][3]=temp;
- // Rotate third row 3 columns to right
- temp=state[3][0];
- state[3][0]=state[3][1];
- state[3][1]=state[3][2];
- state[3][2]=state[3][3];
- state[3][3]=temp;
+}
+// Cipher is the main function that encrypts the PlainText. +static void Cipher() +{
- uint8_t i, j, round = 0;
- //Copy the input PlainText to state array.
- for(i = 0; i < 4; ++i)
- {
- for(j = 0; j < 4 ; ++j)
- {
- state[j][i] = in[(i * 4) + j];
- }
- }
- // Add the First round key to the state before starting the rounds.
- AddRoundKey(0);
- // There will be Nr rounds.
- // The first Nr-1 rounds are identical.
- // These Nr-1 rounds are executed in the loop below.
- for(round = 1; round < Nr; ++round)
- {
- SubBytes();
- ShiftRows();
- MixColumns();
- AddRoundKey(round);
- }
- // The last round is given below.
- // The MixColumns function is not here in the last round.
- SubBytes();
- ShiftRows();
- AddRoundKey(Nr);
- // The encryption process is over.
- // Copy the state array to output array.
- for(i = 0; i < 4; ++i)
- {
- for(j = 0; j < 4; ++j)
- {
- out[(i * 4) + j] = state[j][i];
- }
- }
+}
+static void InvCipher() +{
- uint8_t i,j,round=0;
- //Copy the input CipherText to state array.
- for(i=0;i<4;i++)
- {
- for(j=0;j<4;j++)
- {
- state[j][i] = in[i*4 + j];
- }
- }
- // Add the First round key to the state before starting the rounds.
- AddRoundKey(Nr);
- // There will be Nr rounds.
- // The first Nr-1 rounds are identical.
- // These Nr-1 rounds are executed in the loop below.
- for(round=Nr-1;round>0;round--)
- {
- InvShiftRows();
- InvSubBytes();
- AddRoundKey(round);
- InvMixColumns();
- }
- // The last round is given below.
- // The MixColumns function is not here in the last round.
- InvShiftRows();
- InvSubBytes();
- AddRoundKey(0);
- // The decryption process is over.
- // Copy the state array to output array.
- for(i=0;i<4;i++)
- {
- for(j=0;j<4;j++)
- {
- out[i*4+j]=state[j][i];
- }
- }
+}
+/*****************************************************************************/ +/* Public functions: */ +/*****************************************************************************/
+void AES128_ECB_encrypt(uint8_t* input, uint8_t* key, uint8_t *output) +{
- // Copy the Key and CipherText
- Key = key;
- in = input;
- out = output;
- // The KeyExpansion routine must be called before encryption.
- KeyExpansion();
- // The next function call encrypts the PlainText with the Key using AES algorithm.
- Cipher();
+}
+void AES128_ECB_decrypt(uint8_t* input, uint8_t* key, uint8_t *output) +{
- Key = key;
- in = input;
- out = output;
- KeyExpansion();
- InvCipher();
+}
+#endif //_AES_C_
diff --git a/src/aes.h b/src/aes.h new file mode 100644 index 0000000..5fb2176 --- /dev/null +++ b/src/aes.h @@ -0,0 +1,16 @@ +#ifndef _AES_H_ +#define _AES_H_
+#include
+#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */
+void AES128_ECB_encrypt(uint8_t* input, uint8_t* key, uint8_t *output); +void AES128_ECB_decrypt(uint8_t* input, uint8_t* key, uint8_t *output);
+#ifdef __cplusplus +} +#endif /* __cplusplus */
+#endif //_AES_H_
1.9.1
devel mailing list devel@libdivecomputer.org http://libdivecomputer.org/cgi-bin/mailman/listinfo/devel
This code is inspired by JeanDo ostc-companion.
Signed-off-by: Anton Lundin glance@acc.umu.se --- src/hw_ostc3.c | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+)
diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index b78702c..cc1deb3 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -21,6 +21,7 @@
#include <string.h> // memcmp, memcpy #include <stdlib.h> // malloc, free +#include <stdio.h> // FILE, fopen
#include <libdivecomputer/hw_ostc3.h>
@@ -30,6 +31,7 @@ #include "checksum.h" #include "ringbuffer.h" #include "array.h" +#include "aes.h"
#define ISINSTANCE(device) dc_device_isinstance((device), &hw_ostc3_device_vtable)
@@ -43,6 +45,7 @@ #define SZ_VERSION (SZ_CUSTOMTEXT + 4) #define SZ_MEMORY 0x200000 #define SZ_CONFIG 4 +#define SZ_FIRMWARE 0x01E000 // 120KB
#define RB_LOGBOOK_SIZE 256 #define RB_LOGBOOK_COUNT 256 @@ -622,3 +625,120 @@ hw_ostc3_device_config_reset (dc_device_t *abstract)
return DC_STATUS_SUCCESS; } + +typedef struct hw_ostc3_firmware_t { + unsigned char data[SZ_FIRMWARE]; + unsigned int checksum; +} hw_ostc3_firmware_t; + +// FIXME: This key is specific to the ostc3 +// The ostc sport has another key but the same protocoll +// How should we refactor the code for that? +static unsigned char ostc3_key[16] = { + 0xF1, 0xE9, 0xB0, 0x30, + 0x45, 0x6F, 0xBE, 0x55, + 0xFF, 0xE7, 0xF8, 0x31, + 0x13, 0x6C, 0xF2, 0xFE +}; + +static unsigned int hw_ostc3_firmware_checksum(hw_ostc3_firmware_t *firmware) +{ + unsigned short low = 0; + unsigned short high = 0; + for(unsigned int i = 0;i < SZ_FIRMWARE;i++) + { + low += firmware->data[i]; + high += low; + } + return (((unsigned int)high) << 16) + low; +} + +static dc_status_t +hw_ostc3_firmware_readfile (hw_ostc3_firmware_t *firmware, dc_context_t *context, const char *filename) +{ + dc_status_t rc = DC_STATUS_SUCCESS; + FILE *fp; + unsigned char iv[16] = {0}; + unsigned char tmpbuf[16] = {0}; + unsigned char encrypted[16] = {0}; + unsigned int bytes = 0, addr = 0, faddr = 0; + char checksum[4]; + + if (firmware == NULL) { + ERROR (context, "Invalid arguments."); + return DC_STATUS_INVALIDARGS; + } + + // Initialize the buffers. + memset (firmware->data, 0xFF, sizeof (firmware->data)); + firmware->checksum = 0; + + fp = fopen (filename, "rb"); + if (fp == NULL) { + ERROR (context, "Failed to open the file."); + return DC_STATUS_IO; + } + + // FIXME: replace with array_convert_hex2bin + if (fscanf(fp, ":000000" + "%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx" + "%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx" + "\n", + iv, iv + 1, iv + 2, iv + 3, iv + 4, iv + 5, iv + 6, iv + 7, + iv + 8, iv + 9, iv + 10, iv + 11, iv + 12, iv + 13, iv + 14, iv + 15) != 16) { + fclose(fp); + ERROR (context, "Failed to parse header."); + return DC_STATUS_IO; + } + bytes += 16; + + // Load the iv for AES-FCB-mode + AES128_ECB_encrypt(iv, ostc3_key, tmpbuf); + + for(addr = 0; addr < SZ_FIRMWARE; addr += 16) { + // FIXME: replace with array_convert_hex2bin + if (fscanf(fp, ":%6x" + "%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx" + "%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx" + "\n", + &faddr, + encrypted, encrypted + 1, encrypted + 2, encrypted + 3, encrypted + 4, encrypted + 5, encrypted + 6, encrypted + 7, + encrypted + 8, encrypted + 9, encrypted + 10, encrypted + 11, encrypted + 12, encrypted + 13, encrypted + 14, encrypted + 15) != 17) { + fclose(fp); + ERROR (context, "Failed to parse data."); + return DC_STATUS_IO; + } + + if (faddr != bytes) { + fclose(fp); + ERROR (context, "Failed to data address."); + return DC_STATUS_IO; + } + + // Decrypt AES-FCB data + for(int i=0;i < 16;i++) + firmware->data[addr + i] = encrypted[i] ^ tmpbuf[i]; + + // Run the next round of encryption + AES128_ECB_encrypt(encrypted, ostc3_key, tmpbuf); + + bytes += 16; + } + + // This file format contains a tail with the checksum in + if (fscanf(fp, ":%6x%2hhx%2hhx%2hhx%2hhx\n", &faddr, checksum, checksum + 1, checksum + 2, checksum + 3) != 5 || faddr != bytes) { + fclose(fp); + ERROR (context, "Failed to parse file tail."); + return DC_STATUS_IO; + } + fclose(fp); + + firmware->checksum = array_uint32_le(checksum); + + if (firmware->checksum != hw_ostc3_firmware_checksum(firmware)) { + ERROR (context, "Failed to verify file checksum."); + return DC_STATUS_IO; + } + + return DC_STATUS_SUCCESS; +}
This code is inspired by JeanDo ostc-companion.
Signed-off-by: Anton Lundin glance@acc.umu.se --- src/hw_ostc3.c | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-)
diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index cc1deb3..6bc50d5 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -50,6 +50,7 @@ #define RB_LOGBOOK_SIZE 256 #define RB_LOGBOOK_COUNT 256
+#define S_READY 0x4C #define READY 0x4D #define HEADER 0x61 #define CLOCK 0x62 @@ -191,7 +192,7 @@ hw_ostc3_transfer (hw_ostc3_device_t *device, }
// Verify the ready byte. - if (ready[0] != READY) { + if (!(ready[0] == READY || ready[0] == S_READY)) { ERROR (abstract->context, "Unexpected ready byte."); return DC_STATUS_PROTOCOL; } @@ -742,3 +743,53 @@ hw_ostc3_firmware_readfile (hw_ostc3_firmware_t *firmware, dc_context_t *context
return DC_STATUS_SUCCESS; } + +static dc_status_t +hw_ostc3_service_mode (dc_device_t *abstract) +{ + hw_ostc3_device_t *device = (hw_ostc3_device_t *) abstract; + unsigned char command[] = {0xAA, 0xAB, 0xCD, 0xEF}; + unsigned char output[5]; + dc_status_t rc; + + /* FIXME: this is ugly. + * But libdivecomputer enters download mode on connect, + * so we need to exit download mode and enter service mode. + */ + + // Exit download mode + rc = hw_ostc3_transfer (device, NULL, EXIT, NULL, 0, NULL, 0); + if (rc != DC_STATUS_SUCCESS) + return rc; + + // Wait for the device to reboot before + // sending command to enter service mode + serial_sleep(device->port, 6000); + + // We cant use hw_ostc3_transfer here, due to the different echos + int n = serial_write (device->port, command, sizeof (command)); + if (n != sizeof (command)) { + ERROR (abstract->context, "Failed to send the command."); + return EXITCODE (n); + } + + // Give the device some time to enter service mode + serial_sleep(device->port, 100); + + // Read the response + n = serial_read (device->port, output, sizeof (output)); + if (n != sizeof (output)) { + ERROR (abstract->context, "Failed to receive the echo."); + return EXITCODE (n); + } + + // Verify the response to service mode + if (output[0] != 0x4B || output[1] != 0xAB || + output[2] != 0xCD || output[3] != 0xEF || + output[4] != S_READY) { + ERROR (abstract->context, "Failed to verify echo."); + return DC_STATUS_IO; + } + + return DC_STATUS_SUCCESS; +}
Signed-off-by: Anton Lundin glance@acc.umu.se --- src/array.c | 19 +++++++++++++++++++ src/array.h | 6 ++++++ 2 files changed, 25 insertions(+)
diff --git a/src/array.c b/src/array.c index 243ec34..ee68b93 100644 --- a/src/array.c +++ b/src/array.c @@ -162,6 +162,16 @@ array_uint32_le (const unsigned char data[])
unsigned int +uint32_le_array (const unsigned int input, unsigned char data[]) +{ + data[0] = input & 0xFF; + data[1] = input >> 8 & 0xFF; + data[2] = input >> 16 & 0xFF; + data[3] = input >> 24 & 0xFF; +} + + +unsigned int array_uint24_be (const unsigned char data[]) { return (data[0] << 16) + (data[1] << 8) + data[2]; @@ -169,6 +179,15 @@ array_uint24_be (const unsigned char data[])
unsigned int +uint24_be_array (const unsigned int input, unsigned char data[]) +{ + data[0] = input >> 16 & 0xFF; + data[1] = input >> 8 & 0xFF; + data[2] = input & 0xFF; +} + + +unsigned int array_uint24_le (const unsigned char data[]) { return data[0] + (data[1] << 8) + (data[2] << 16); diff --git a/src/array.h b/src/array.h index b08ed2e..697b87e 100644 --- a/src/array.h +++ b/src/array.h @@ -56,9 +56,15 @@ unsigned int array_uint32_le (const unsigned char data[]);
unsigned int +uint32_le_array (const unsigned int input, unsigned char data[]); + +unsigned int array_uint24_be (const unsigned char data[]);
unsigned int +uint24_be_array (const unsigned int input, unsigned char data[]); + +unsigned int array_uint24_le (const unsigned char data[]);
unsigned short
This is the fist step in the firmware upgrade process.
This code is inspired by JeanDo ostc-companion.
Signed-off-by: Anton Lundin glance@acc.umu.se --- src/hw_ostc3.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+)
diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index 6bc50d5..a4f2141 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -46,10 +46,13 @@ #define SZ_MEMORY 0x200000 #define SZ_CONFIG 4 #define SZ_FIRMWARE 0x01E000 // 120KB +#define SZ_FIRMWARE_BLOCK 0x1000 // 4KB +#define FIRMWARE_AREA 0x3E0000
#define RB_LOGBOOK_SIZE 256 #define RB_LOGBOOK_COUNT 256
+#define ERASE_RANGE 0x42 #define S_READY 0x4C #define READY 0x4D #define HEADER 0x61 @@ -793,3 +796,28 @@ hw_ostc3_service_mode (dc_device_t *abstract)
return DC_STATUS_SUCCESS; } + +static dc_status_t +hw_ostc3_device_erase_range (hw_ostc3_device_t *device, unsigned int addr, unsigned int size) +{ + dc_status_t rc = DC_STATUS_SUCCESS; + // Convert size to number of pages, rounded up. + unsigned char blocks = ((size + SZ_FIRMWARE_BLOCK - 1) / SZ_FIRMWARE_BLOCK); + + // Erase just the needed pages. + unsigned char buffer[4]; + uint24_be_array(addr, buffer); + buffer[3] = blocks; + + // Make sure everything is in a sane state. + serial_sleep (device->port, 100); + serial_flush (device->port, SERIAL_QUEUE_BOTH); + + rc = hw_ostc3_transfer (device, NULL, ERASE_RANGE, buffer, sizeof(buffer), NULL, 0); + + // Good wait formula taken from ostc-companion + // Wait (120/4)ms by block of 4K, plus 3% VAT to be sure. + serial_sleep (device->port, 40 + blocks * 31); + + return rc; +}
This is necessary to verify that the memory written got transfered correctly.
This code is inspired by JeanDo ostc-companion.
Signed-off-by: Anton Lundin glance@acc.umu.se --- src/hw_ostc3.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+)
diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index a4f2141..117a4bb 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -52,6 +52,7 @@ #define RB_LOGBOOK_SIZE 256 #define RB_LOGBOOK_COUNT 256
+#define READ_BLOCK 0x20 #define ERASE_RANGE 0x42 #define S_READY 0x4C #define READY 0x4D @@ -821,3 +822,17 @@ hw_ostc3_device_erase_range (hw_ostc3_device_t *device, unsigned int addr, unsig
return rc; } + +static dc_status_t +hw_ostc3_device_read_block (hw_ostc3_device_t *device, unsigned int addr, unsigned char block[], unsigned int block_size) +{ + unsigned char buffer[6]; + uint24_be_array(addr, buffer); + uint24_be_array(block_size, buffer + 3); + + // Make sure everything is in a sane state. + serial_sleep (device->port, 100); + serial_flush (device->port, SERIAL_QUEUE_BOTH); + + return hw_ostc3_transfer (device, NULL, READ_BLOCK, buffer, sizeof(buffer), block, block_size); +}
This is now you transfer a new firmware to the OSTC3.
This code is inspired by JeanDo ostc-companion.
Signed-off-by: Anton Lundin glance@acc.umu.se --- src/hw_ostc3.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+)
diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index 117a4bb..ff65677 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -53,6 +53,7 @@ #define RB_LOGBOOK_COUNT 256
#define READ_BLOCK 0x20 +#define WRITE_BLOCK 0x30 #define ERASE_RANGE 0x42 #define S_READY 0x4C #define READY 0x4D @@ -836,3 +837,57 @@ hw_ostc3_device_read_block (hw_ostc3_device_t *device, unsigned int addr, unsign
return hw_ostc3_transfer (device, NULL, READ_BLOCK, buffer, sizeof(buffer), block, block_size); } + +static dc_status_t +hw_ostc3_device_write_block (dc_device_t *abstract, unsigned int addr, unsigned char block[], unsigned int block_size) +{ + dc_status_t rc = DC_STATUS_SUCCESS; + hw_ostc3_device_t *device = (hw_ostc3_device_t *) abstract; + dc_context_t *context = (abstract ? abstract->context : NULL); + int n; + unsigned char response; + unsigned char buffer[4]; + buffer[0] = WRITE_BLOCK; + uint24_be_array(addr, buffer + 1); + + // Make sure everything is in a sane state. + serial_sleep (device->port, 100); + serial_flush (device->port, SERIAL_QUEUE_BOTH); + + // Send initial write block command. + /* FIXME: we can't use hw_ostc3_transfer here, for some odd reason + * Here shit goes sideways. The device reboots probably due + * to that it thinks it doesn't have any data + rc = hw_ostc3_transfer(device, NULL, WRITE_BLOCK, buffer, sizeof(buffer), NULL, 0); + if (rc != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to send write block command"); + return rc; + }*/ + n = serial_write (device->port, buffer, sizeof(buffer)); + if (n != sizeof(buffer)) { + ERROR (context, "failed to send address to device"); + return EXITCODE (n); + } + + serial_flush (device->port, SERIAL_QUEUE_BOTH); + serial_sleep (device->port, 2); + + // Send the new block + n = serial_write (device->port, block, block_size); + if (n != block_size) { + ERROR (context, "failed to send block to device"); + return EXITCODE (n); + } + + serial_flush (device->port, SERIAL_QUEUE_BOTH); + + // A sleep lenght copied from ostc-companion + // Feels a bit slower than that what that does? + serial_sleep (device->port, 1100); + + n = serial_read (device->port, &response, sizeof (response)); + if (n != sizeof (response) || response != S_READY) + return EXITCODE (n); + + return DC_STATUS_SUCCESS; +}
This function triggers a reboot into the bootloader which flashes the new firmware to Prom.
This code is inspired by JeanDo ostc-companion.
Signed-off-by: Anton Lundin glance@acc.umu.se --- src/hw_ostc3.c | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+)
diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index ff65677..5cea5b9 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -57,6 +57,7 @@ #define ERASE_RANGE 0x42 #define S_READY 0x4C #define READY 0x4D +#define FLASH_FIRM 0x50 #define HEADER 0x61 #define CLOCK 0x62 #define CUSTOMTEXT 0x63 @@ -891,3 +892,41 @@ hw_ostc3_device_write_block (dc_device_t *abstract, unsigned int addr, unsigned
return DC_STATUS_SUCCESS; } + +static dc_status_t +hw_ostc3_device_upgrade_firmware (dc_device_t *abstract, unsigned int checksum) +{ + dc_status_t rc = DC_STATUS_SUCCESS; + hw_ostc3_device_t *device = (hw_ostc3_device_t *) abstract; + dc_context_t *context = (abstract ? abstract->context : NULL); + unsigned char buffer[5]; + uint32_le_array(checksum, buffer); + + // Compute a one byte checksum, so the device can validate the firmware image. + buffer[4] = 0x55; + buffer[4] ^= buffer[0]; + buffer[4] = (buffer[4]<<1 | buffer[4]>>7); + buffer[4] ^= buffer[1]; + buffer[4] = (buffer[4]<<1 | buffer[4]>>7); + buffer[4] ^= buffer[2]; + buffer[4] = (buffer[4]<<1 | buffer[4]>>7); + buffer[4] ^= buffer[3]; + buffer[4] = (buffer[4]<<1 | buffer[4]>>7); + + // Make sure everything is in a sane state. + serial_sleep (device->port, 100); + serial_flush (device->port, SERIAL_QUEUE_BOTH); + + rc = hw_ostc3_transfer(device, NULL, FLASH_FIRM, buffer, sizeof(buffer), NULL, 0); + if (rc != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to send flash firmware command"); + return rc; + } + + // Now the device resets, and if everything is well, it reprograms. + serial_sleep (device->port, 500); + + // FIXME: How should we force the application to close the device here? + + return DC_STATUS_SUCCESS; +}
This connects the bits and implements firmware upgrade for the OSTC3.
This code is inspired by JeanDo ostc-companion.
Signed-off-by: Anton Lundin glance@acc.umu.se --- include/libdivecomputer/hw_ostc3.h | 3 + src/hw_ostc3.c | 115 +++++++++++++++++++++++++++++++++++++ src/libdivecomputer.symbols | 1 + 3 files changed, 119 insertions(+)
diff --git a/include/libdivecomputer/hw_ostc3.h b/include/libdivecomputer/hw_ostc3.h index 267b7e3..bc56a9d 100644 --- a/include/libdivecomputer/hw_ostc3.h +++ b/include/libdivecomputer/hw_ostc3.h @@ -58,6 +58,9 @@ hw_ostc3_device_config_write (dc_device_t *abstract, unsigned int config, const dc_status_t hw_ostc3_device_config_reset (dc_device_t *abstract);
+dc_status_t +hw_ostc3_device_fwupdate (dc_device_t *abstract, const char *filename); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index 5cea5b9..e791277 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -930,3 +930,118 @@ hw_ostc3_device_upgrade_firmware (dc_device_t *abstract, unsigned int checksum)
return DC_STATUS_SUCCESS; } + +dc_status_t +hw_ostc3_device_fwupdate (dc_device_t *abstract, const char *filename) +{ + dc_status_t rc = DC_STATUS_SUCCESS; + hw_ostc3_device_t *device = (hw_ostc3_device_t *) abstract; + dc_context_t *context = (abstract ? abstract->context : NULL); + + // Enable progress notifications. + dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; + // load, servicemode, erase, upload FZ, verify FZ, reprogram + progress.maximum = 3 + SZ_FIRMWARE * 2 / SZ_FIRMWARE_BLOCK; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + if (!ISINSTANCE (abstract)) + return DC_STATUS_INVALIDARGS; + + hw_ostc3_device_display(abstract, " Loading FW..."); + + // Allocate memory for the firmware data. + hw_ostc3_firmware_t *firmware = (hw_ostc3_firmware_t *) malloc (sizeof (hw_ostc3_firmware_t)); + if (firmware == NULL) { + ERROR (context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + // Read the hex file. + rc = hw_ostc3_firmware_readfile (firmware, context, filename); + if (rc != DC_STATUS_SUCCESS) { + free (firmware); + return rc; + } + + // Firmware loaded + progress.current++; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + hw_ostc3_device_display(abstract, " Servicemode..."); + + // Set the device into servicemode + rc = hw_ostc3_service_mode(abstract); + if (rc != DC_STATUS_SUCCESS) { + free (firmware); + ERROR (context, "Failed to put device into servicemode"); + return rc; + } + + // Service mode done + progress.current++; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + hw_ostc3_device_display(abstract, " Erasing FW..."); + rc = hw_ostc3_device_erase_range(device, FIRMWARE_AREA, SZ_FIRMWARE); + if (rc != DC_STATUS_SUCCESS) { + free (firmware); + ERROR (context, "Failed to erase old firmware"); + return rc; + } + + // Memory erased + progress.current++; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + hw_ostc3_device_display(abstract, " Uploading..."); + + for(int len = 0; len < SZ_FIRMWARE; len += SZ_FIRMWARE_BLOCK) + { + char status[16]; // Status message on the display + snprintf (status, 16, " Uploading %2d%%", (100 * len) / SZ_FIRMWARE); + hw_ostc3_device_display (abstract, status); + + rc = hw_ostc3_device_write_block (abstract, FIRMWARE_AREA + len, firmware->data + len, SZ_FIRMWARE_BLOCK); + if (rc != DC_STATUS_SUCCESS) { + free(firmware); + ERROR (context, "Failed to write block to device"); + return rc; + } + // One block uploaded + progress.current++; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + } + + hw_ostc3_device_display (abstract, " Verifying..."); + + for(int len = 0; len < SZ_FIRMWARE; len += SZ_FIRMWARE_BLOCK) + { + unsigned char block[SZ_FIRMWARE_BLOCK]; + char status[16]; // Status message on the display + snprintf (status, 16, " Verifying %2d%%", (100 * len) / SZ_FIRMWARE); + hw_ostc3_device_display (abstract, status); + + rc = hw_ostc3_device_read_block (device, FIRMWARE_AREA + len, block, sizeof(block)); + if (rc != DC_STATUS_SUCCESS || memcmp (firmware->data + len, block, sizeof (block)) != 0) { + hw_ostc3_device_display (abstract, " Verify FAILED"); + free (firmware); + ERROR (context, "Failed verify."); + return DC_STATUS_IO; + } + // One block verified + progress.current++; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + } + + hw_ostc3_device_display(abstract, " Programming..."); + + rc = hw_ostc3_device_upgrade_firmware(abstract, firmware->checksum); + if (rc != DC_STATUS_SUCCESS) { + free (firmware); + ERROR (context, "Failed to start programing"); + return rc; + } + + // Finished! + return DC_STATUS_SUCCESS; +} diff --git a/src/libdivecomputer.symbols b/src/libdivecomputer.symbols index 14a96a8..4984942 100644 --- a/src/libdivecomputer.symbols +++ b/src/libdivecomputer.symbols @@ -157,6 +157,7 @@ hw_ostc3_device_customtext hw_ostc3_device_config_read hw_ostc3_device_config_write hw_ostc3_device_config_reset +hw_ostc3_device_fwupdate zeagle_n2ition3_device_open atomics_cobalt_device_open atomics_cobalt_device_version
This teaches the example firmware upgrader about how to upgrade the OSTC3's.
Signed-off-by: Anton Lundin glance@acc.umu.se --- examples/hw_ostc_fwupdate.c | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-)
diff --git a/examples/hw_ostc_fwupdate.c b/examples/hw_ostc_fwupdate.c index a9e7fde..7ce1347 100644 --- a/examples/hw_ostc_fwupdate.c +++ b/examples/hw_ostc_fwupdate.c @@ -19,10 +19,10 @@ * MA 02110-1301 USA */
-#include <stdio.h> // fopen, fwrite, fclose -#include <stdlib.h> +#include <string.h>
#include <libdivecomputer/hw_ostc.h> +#include <libdivecomputer/hw_ostc3.h>
#include "utils.h" #include "common.h" @@ -44,17 +44,23 @@ event_cb (dc_device_t *device, dc_event_type_t event, const void *data, void *us }
static dc_status_t -fwupdate (const char *name, const char *hexfile) +fwupdate (const char *name, const char *hexfile, int ostc3) { dc_context_t *context = NULL; dc_device_t *device = NULL; + dc_status_t rc = DC_EVENT_PROGRESS;
dc_context_new (&context); dc_context_set_loglevel (context, DC_LOGLEVEL_ALL); dc_context_set_logfunc (context, logfunc, NULL);
- message ("hw_ostc_device_open\n"); - dc_status_t rc = hw_ostc_device_open (&device, context, name); + if (ostc3) { + message ("hw_ostc3_device_open\n"); + rc = hw_ostc3_device_open (&device, context, name); + } else { + message ("hw_ostc_device_open\n"); + rc = hw_ostc_device_open (&device, context, name); + } if (rc != DC_STATUS_SUCCESS) { WARNING ("Error opening serial port."); dc_context_free (context); @@ -70,8 +76,13 @@ fwupdate (const char *name, const char *hexfile) return rc; }
- message ("hw_ostc_device_fwupdate\n"); - rc = hw_ostc_device_fwupdate (device, hexfile); + if (ostc3) { + message ("hw_ostc3_device_fwupdate\n"); + rc = hw_ostc3_device_fwupdate (device, hexfile); + } else { + message ("hw_ostc_device_fwupdate\n"); + rc = hw_ostc_device_fwupdate (device, hexfile); + } if (rc != DC_STATUS_SUCCESS) { WARNING ("Error flashing firmware."); dc_device_close (device); @@ -103,6 +114,7 @@ int main(int argc, char *argv[]) const char* name = "/dev/ttyUSB0"; #endif const char *hexfile = NULL; + int ostc3 = 0;
if (argc > 1) { name = argv[1]; @@ -110,11 +122,18 @@ int main(int argc, char *argv[]) if (argc > 2) { hexfile = argv[2]; } + if (argc > 3) { + if (strcmp(argv[3], "-3") == 0) { + ostc3 = 1; + } else { + ostc3 = 0; + } + }
message ("DEVICE=%s\n", name); message ("HEXFILE=%s\n", hexfile);
- dc_status_t a = fwupdate (name, hexfile); + dc_status_t a = fwupdate (name, hexfile, ostc3);
message ("SUMMARY\n"); message ("-------\n");
On 14-11-14 18:01, Anton Lundin wrote:
This is is a RFC series of patches to add support for firmware upgrades on the OSTC3. The code have bin tested both against a "pcb" by HW and against my OSTC3 so it works and does the right things. JeanDo's ostc-companion[1] have bin the reference implementation I've looked at when I wrote this code.
There are still a couple of pain-points that I would like some feedback on:
I still need to have a closer look at all the implementation details. But let's already start with the higher level questions.
How to start the device in service mode: To get access to the necessary commands for service mode, we can't INIT the device with INIT(0xBB), we need to INIT it with a special service mode sequence(0xAA, 0xAB, 0xCD, 0xEF). Now the INIT is sent in hw_ostc3_device_open, so how do you suggest that we refactor the code to allow another sequence after open?
The current quite crude hack is to just EXIT(0xFF) the device and re-init it after a 6s sleep. This won't work against the OSTC Sport.
I'm not really sure what would be the best solution here, so I'm just providing some ideas here:
One solution would be to remove the INIT from open, and maintain the current state (none, download or service) in the device handle. Initially the state would be "none". Then, in each of the public functions, we check the state and if it's "none", we automatically enter download or service mode. If we're in the wrong state, we fail an error.
Another alternative is to have a second variant of the open function that takes a parameter to indicate which mode you want. The tricky part here, is that the high-level dc_device_open() function won't know about this function, so it will always call the download variant. Thus to flash the firmware, an application would be forced to use the new variant. (Note that after v0.5 I intend to remove the vendor_product_device_open() functions from the public api and require applications to use the generic dc_device_open() function instead. But the service mode variant can stay as a vendor specific function, just like the hw_ostc3_device_fwupdate function itself.)
Yet another option is to remove the dc_device_t parameter from the hw_ostc3_device_fwupdate function, and let it open the device itself. Then we call the service mode variant of the open function internally.
On the other hand, I noticed some comment that the key is different for the ostc sport. So that means we somehow have to detect the right model. This is done with the version command, but I'm not sure if you can use this command in service mode. So we may need to identify the exact model in download mode first and then switch to service mode anyway. You say this won't work for the sport, so what's the problem there? Is that maybe because it exits bluetooth mode, and thus the serial port disappears? (I haven't used a sport myself, so I simply don't know.)
Right now, I have a preference towards the second option, with a separate open function for the service mode. It looks the least complex to me. But I'm not ruling out other options yet.
The timing between command and data in write_block: The current hw_ostc3_device_write_block is a bit of copy-paste from hw_ostc3_transfer, to be able to send the data buffer to the device, without a additional copy.
I wouldn't worry about an extra data copy. That's inexpensive compared to the actual I/O. But based on the comments, I guess the real problem is something else? Could it be that it seems there is no echo for the WRITE_BLOCK command?
Library triggered closed state: The device reboots when it gets the FLASH_FIRM-command so how can we "not fail" when the application then tries to talk to the device afterwards, or even call hw_ostc3_device_close?
I assume that the problem is that after the FLASH_FIRM command, we don't need to send the EXIT command in hw_ostc3_device_close? Or did you mean something else?
There are also a couple of other FIXME's in the code, which I plan on cleaning up, but I thought that I should send out a first RFC patch series to get some additional feedback going.
Any other ideas about how the code looks?
I noticed a large number of sleep and flush calls. Are those really necessary? I have the feeling some of them were just copy and pasted.
Jef
On 15 November, 2014 - Jef Driesen wrote:
On 14-11-14 18:01, Anton Lundin wrote:
This is is a RFC series of patches to add support for firmware upgrades on the OSTC3. The code have bin tested both against a "pcb" by HW and against my OSTC3 so it works and does the right things. JeanDo's ostc-companion[1] have bin the reference implementation I've looked at when I wrote this code.
There are still a couple of pain-points that I would like some feedback on:
I still need to have a closer look at all the implementation details. But let's already start with the higher level questions.
How to start the device in service mode: To get access to the necessary commands for service mode, we can't INIT the device with INIT(0xBB), we need to INIT it with a special service mode sequence(0xAA, 0xAB, 0xCD, 0xEF). Now the INIT is sent in hw_ostc3_device_open, so how do you suggest that we refactor the code to allow another sequence after open?
The current quite crude hack is to just EXIT(0xFF) the device and re-init it after a 6s sleep. This won't work against the OSTC Sport.
I'm not really sure what would be the best solution here, so I'm just providing some ideas here:
One solution would be to remove the INIT from open, and maintain the current state (none, download or service) in the device handle. Initially the state would be "none". Then, in each of the public functions, we check the state and if it's "none", we automatically enter download or service mode. If we're in the wrong state, we fail an error.
Another alternative is to have a second variant of the open function that takes a parameter to indicate which mode you want. The tricky part here, is that the high-level dc_device_open() function won't know about this function, so it will always call the download variant. Thus to flash the firmware, an application would be forced to use the new variant. (Note that after v0.5 I intend to remove the vendor_product_device_open() functions from the public api and require applications to use the generic dc_device_open() function instead. But the service mode variant can stay as a vendor specific function, just like the hw_ostc3_device_fwupdate function itself.)
Yet another option is to remove the dc_device_t parameter from the hw_ostc3_device_fwupdate function, and let it open the device itself. Then we call the service mode variant of the open function internally.
On the other hand, I noticed some comment that the key is different for the ostc sport. So that means we somehow have to detect the right model. This is done with the version command, but I'm not sure if you can use this command in service mode. So we may need to identify the exact model in download mode first and then switch to service mode anyway. You say this won't work for the sport, so what's the problem there? Is that maybe because it exits bluetooth mode, and thus the serial port disappears? (I haven't used a sport myself, so I simply don't know.)
Right now, I have a preference towards the second option, with a separate open function for the service mode. It looks the least complex to me. But I'm not ruling out other options yet.
I was more looking at how the api for hw_ostc_device_fwupdate looks and just created a equivalent api.
For me it really doesn't matter, but it makes sense that the both hw_ostc_device_fwupdate and hw_ostc3_device_fwupdate works the same way.
The timing between command and data in write_block: The current hw_ostc3_device_write_block is a bit of copy-paste from hw_ostc3_transfer, to be able to send the data buffer to the device, without a additional copy.
I wouldn't worry about an extra data copy. That's inexpensive compared to the actual I/O. But based on the comments, I guess the real problem is something else? Could it be that it seems there is no echo for the WRITE_BLOCK command?
The echo comes after the data block. I've bin playing with sending it as one write too, but for some reason the device doesn't like that and programs nothing.
I think the magic is the serial_sleep (device->port, 2) between the WRITE_BLOCK + address and the data.
Maybe its not that bad, its only one function that has some special properties that can't use hw_ostc3_transfer.
Library triggered closed state: The device reboots when it gets the FLASH_FIRM-command so how can we "not fail" when the application then tries to talk to the device afterwards, or even call hw_ostc3_device_close?
I assume that the problem is that after the FLASH_FIRM command, we don't need to send the EXIT command in hw_ostc3_device_close? Or did you mean something else?
Exactly that. This is just the cousin to the open/service-mode problem.
There are also a couple of other FIXME's in the code, which I plan on cleaning up, but I thought that I should send out a first RFC patch series to get some additional feedback going.
Any other ideas about how the code looks?
I noticed a large number of sleep and flush calls. Are those really necessary? I have the feeling some of them were just copy and pasted.
Not really copy-pasted, but re-implemented the same ones as where in ostc-companion. I've removed most of them by now, and the ones left there kinda needs to be there.
//Anton
On 2014-11-15 15:45, Anton Lundin wrote:
On 15 November, 2014 - Jef Driesen wrote:
On 14-11-14 18:01, Anton Lundin wrote:
This is is a RFC series of patches to add support for firmware upgrades on the OSTC3. The code have bin tested both against a "pcb" by HW and against my OSTC3 so it works and does the right things. JeanDo's ostc-companion[1] have bin the reference implementation I've looked at when I wrote this code.
There are still a couple of pain-points that I would like some feedback on:
I still need to have a closer look at all the implementation details. But let's already start with the higher level questions.
How to start the device in service mode: To get access to the necessary commands for service mode, we can't INIT the device with INIT(0xBB), we need to INIT it with a special service mode sequence(0xAA, 0xAB, 0xCD, 0xEF). Now the INIT is sent in hw_ostc3_device_open, so how do you suggest that we refactor the code to allow another sequence after open?
The current quite crude hack is to just EXIT(0xFF) the device and re-init it after a 6s sleep. This won't work against the OSTC Sport.
I'm not really sure what would be the best solution here, so I'm just providing some ideas here:
One solution would be to remove the INIT from open, and maintain the current state (none, download or service) in the device handle. Initially the state would be "none". Then, in each of the public functions, we check the state and if it's "none", we automatically enter download or service mode. If we're in the wrong state, we fail an error.
Another alternative is to have a second variant of the open function that takes a parameter to indicate which mode you want. The tricky part here, is that the high-level dc_device_open() function won't know about this function, so it will always call the download variant. Thus to flash the firmware, an application would be forced to use the new variant. (Note that after v0.5 I intend to remove the vendor_product_device_open() functions from the public api and require applications to use the generic dc_device_open() function instead. But the service mode variant can stay as a vendor specific function, just like the hw_ostc3_device_fwupdate function itself.)
Yet another option is to remove the dc_device_t parameter from the hw_ostc3_device_fwupdate function, and let it open the device itself. Then we call the service mode variant of the open function internally.
On the other hand, I noticed some comment that the key is different for the ostc sport. So that means we somehow have to detect the right model. This is done with the version command, but I'm not sure if you can use this command in service mode. So we may need to identify the exact model in download mode first and then switch to service mode anyway. You say this won't work for the sport, so what's the problem there? Is that maybe because it exits bluetooth mode, and thus the serial port disappears? (I haven't used a sport myself, so I simply don't know.)
Right now, I have a preference towards the second option, with a separate open function for the service mode. It looks the least complex to me. But I'm not ruling out other options yet.
I was more looking at how the api for hw_ostc_device_fwupdate looks and just created a equivalent api.
For me it really doesn't matter, but it makes sense that the both hw_ostc_device_fwupdate and hw_ostc3_device_fwupdate works the same way.
The main difference between option #1 (e.g. remove init from open) and option #2 (e.g. separate open function for service mode) will not be the hw_ostc_device_fwupdate() function itself.
For option #1, the application would do:
dc_device_open(&device, ...); hw_ostc3_device_fwupdate(device, filename); dc_device_close(device);
This is exactly the same sequence as for downloading dives. It's also identical for the firmware upgrade for the ostc2. Except that you should call the corresponding ostc2 function of course.
So the main advantage is that for the application this is all very straightforward. The disadvantage lies in the backend implementation. In the open() function we don't know yet whether we'll need download or service mode, so we'll have to postpone that decision and not initialize any mode at all. Thus this will require some extra complexity in the backend, because in every function we'll need to check whether the correct mode has already been activated or not. Certainly not impossible, but not very elegant either.
For option #2, the application will need to do:
hw_ostc3_device_open_servicemode(&device, ...); hw_ostc3_device_fwupdate(device, filename); dc_device_close(device);
The main advantage here, is that we can immediately enter the correct mode in the open function. The default hw_ostc3_device_open() will start the download mode, and the hw_ostc3_device_open_servicemode() function will start service mode. This keeps things simple in the backend implementation.
The downside is now that the application can no longer use the generic dc_device_open() function. Internally that one will always enter the normal download mode, because that's of course the normal use-case. But on the other hand, is that really a problem? For firmware updating, you need to use a device specific function anyway (hw_ostc3_device_fwupdate). So if you need some device specific code anyway, I assume calling a different open function is not such a big deal either?
Now you basically do:
dc_device_open(&device, context, descriptor, serialport); switch (dc_device_get_type(device)) { case DC_FAMILY_HW_OSTC: hw_ostc_device_fwupdate(device, fileName); break; case DC_FAMILY_HW_OSTC3: hw_ostc3_device_fwupdate(device, fileName); break; default: /* Not supported. */ break; } dc_device_open(device);
And you just need to change the first line too something like this:
if (dc_descriptor_get_type(descriptor) == DC_FAMILY_HW_OSTC3) { hw_ostc3_device_open_servicemode(&device, context, serialport); } else { dc_device_open(&device, context, descriptor, serialport); }
(Or move the open call into the switch. That might even be better, because then you only open a connection for devices that actually support firmware updating!)
That's in a nutshell why I have a preference towards option #2.
The timing between command and data in write_block: The current hw_ostc3_device_write_block is a bit of copy-paste from hw_ostc3_transfer, to be able to send the data buffer to the device, without a additional copy.
I wouldn't worry about an extra data copy. That's inexpensive compared to the actual I/O. But based on the comments, I guess the real problem is something else? Could it be that it seems there is no echo for the WRITE_BLOCK command?
The echo comes after the data block. I've bin playing with sending it as one write too, but for some reason the device doesn't like that and programs nothing.
I think the magic is the serial_sleep (device->port, 2) between the WRITE_BLOCK + address and the data.
Maybe its not that bad, its only one function that has some special properties that can't use hw_ostc3_transfer.
I was surprised to notice that this was different from the other commands, and I was just wondering whether that was on purpose or by accident. If it doesn't work, then there's probably a good reason why it's different :-) We can check with Matthias.
Library triggered closed state: The device reboots when it gets the FLASH_FIRM-command so how can we "not fail" when the application then tries to talk to the device afterwards, or even call hw_ostc3_device_close?
I assume that the problem is that after the FLASH_FIRM command, we don't need to send the EXIT command in hw_ostc3_device_close? Or did you mean something else?
Exactly that. This is just the cousin to the open/service-mode problem.
Indeed. But that's easy to address once we have decided how we're going to implement the open function. If we are in download mode, we send the EXIT command, and in service mode we don't do anything.
BTW, that reminds me of something else. If the firmware update fails halfway for some reason, is there some command we need to send to exit service mode? (I haven't tried the firmware update yet, so I've no idea what happens in such a case.)
There are also a couple of other FIXME's in the code, which I plan on cleaning up, but I thought that I should send out a first RFC patch series to get some additional feedback going.
Any other ideas about how the code looks?
I noticed a large number of sleep and flush calls. Are those really necessary? I have the feeling some of them were just copy and pasted.
Not really copy-pasted, but re-implemented the same ones as where in ostc-companion. I've removed most of them by now, and the ones left there kinda needs to be there.
If they are necessary to get the firmware update to work, then of course they need to stay. But if not, they are only slowing things down unnecessary. Hence my question.
Jef
On 21 November, 2014 - Jef Driesen wrote:
On 2014-11-15 15:45, Anton Lundin wrote:
On 15 November, 2014 - Jef Driesen wrote:
On 14-11-14 18:01, Anton Lundin wrote:
This is is a RFC series of patches to add support for firmware upgrades on the OSTC3. The code have bin tested both against a "pcb" by HW and against my OSTC3 so it works and does the right things. JeanDo's ostc-companion[1] have bin the reference implementation I've looked at when I wrote this code.
There are still a couple of pain-points that I would like some feedback on:
I still need to have a closer look at all the implementation details. But let's already start with the higher level questions.
How to start the device in service mode: To get access to the necessary commands for service mode, we can't INIT the device with INIT(0xBB), we need to INIT it with a special service mode sequence(0xAA, 0xAB, 0xCD, 0xEF). Now the INIT is sent in hw_ostc3_device_open, so how do you suggest that we refactor the code to allow another sequence after open?
The current quite crude hack is to just EXIT(0xFF) the device and re-init it after a 6s sleep. This won't work against the OSTC Sport.
I'm not really sure what would be the best solution here, so I'm just providing some ideas here:
One solution would be to remove the INIT from open, and maintain the current state (none, download or service) in the device handle. Initially the state would be "none". Then, in each of the public functions, we check the state and if it's "none", we automatically enter download or service mode. If we're in the wrong state, we fail an error.
Another alternative is to have a second variant of the open function that takes a parameter to indicate which mode you want. The tricky part here, is that the high-level dc_device_open() function won't know about this function, so it will always call the download variant. Thus to flash the firmware, an application would be forced to use the new variant. (Note that after v0.5 I intend to remove the vendor_product_device_open() functions from the public api and require applications to use the generic dc_device_open() function instead. But the service mode variant can stay as a vendor specific function, just like the hw_ostc3_device_fwupdate function itself.)
Yet another option is to remove the dc_device_t parameter from the hw_ostc3_device_fwupdate function, and let it open the device itself. Then we call the service mode variant of the open function internally.
On the other hand, I noticed some comment that the key is different for the ostc sport. So that means we somehow have to detect the right model. This is done with the version command, but I'm not sure if you can use this command in service mode. So we may need to identify the exact model in download mode first and then switch to service mode anyway. You say this won't work for the sport, so what's the problem there? Is that maybe because it exits bluetooth mode, and thus the serial port disappears? (I haven't used a sport myself, so I simply don't know.)
Right now, I have a preference towards the second option, with a separate open function for the service mode. It looks the least complex to me. But I'm not ruling out other options yet.
I was more looking at how the api for hw_ostc_device_fwupdate looks and just created a equivalent api.
For me it really doesn't matter, but it makes sense that the both hw_ostc_device_fwupdate and hw_ostc3_device_fwupdate works the same way.
The main difference between option #1 (e.g. remove init from open) and option #2 (e.g. separate open function for service mode) will not be the hw_ostc_device_fwupdate() function itself.
For option #1, the application would do:
dc_device_open(&device, ...); hw_ostc3_device_fwupdate(device, filename); dc_device_close(device);
This is exactly the same sequence as for downloading dives. It's also identical for the firmware upgrade for the ostc2. Except that you should call the corresponding ostc2 function of course.
So the main advantage is that for the application this is all very straightforward. The disadvantage lies in the backend implementation. In the open() function we don't know yet whether we'll need download or service mode, so we'll have to postpone that decision and not initialize any mode at all. Thus this will require some extra complexity in the backend, because in every function we'll need to check whether the correct mode has already been activated or not. Certainly not impossible, but not very elegant either.
For option #2, the application will need to do:
hw_ostc3_device_open_servicemode(&device, ...); hw_ostc3_device_fwupdate(device, filename); dc_device_close(device);
The main advantage here, is that we can immediately enter the correct mode in the open function. The default hw_ostc3_device_open() will start the download mode, and the hw_ostc3_device_open_servicemode() function will start service mode. This keeps things simple in the backend implementation.
The downside is now that the application can no longer use the generic dc_device_open() function. Internally that one will always enter the normal download mode, because that's of course the normal use-case. But on the other hand, is that really a problem? For firmware updating, you need to use a device specific function anyway (hw_ostc3_device_fwupdate). So if you need some device specific code anyway, I assume calling a different open function is not such a big deal either?
Now you basically do:
dc_device_open(&device, context, descriptor, serialport); switch (dc_device_get_type(device)) { case DC_FAMILY_HW_OSTC: hw_ostc_device_fwupdate(device, fileName); break; case DC_FAMILY_HW_OSTC3: hw_ostc3_device_fwupdate(device, fileName); break; default: /* Not supported. */ break; } dc_device_open(device);
And you just need to change the first line too something like this:
if (dc_descriptor_get_type(descriptor) == DC_FAMILY_HW_OSTC3) { hw_ostc3_device_open_servicemode(&device, context, serialport); } else { dc_device_open(&device, context, descriptor, serialport); }
(Or move the open call into the switch. That might even be better, because then you only open a connection for devices that actually support firmware updating!)
That's in a nutshell why I have a preference towards option #2.
I did some hacking earlier this week and implemented #1, because that was the variant I felt for when i thought about it. I can send those patches out a bit later if you would like to see.
If you feel strongly for the #2 option, it wouldn't be brain surgery to rewire it into a #2 variant.
The timing between command and data in write_block: The current hw_ostc3_device_write_block is a bit of copy-paste from hw_ostc3_transfer, to be able to send the data buffer to the device, without a additional copy.
I wouldn't worry about an extra data copy. That's inexpensive compared to the actual I/O. But based on the comments, I guess the real problem is something else? Could it be that it seems there is no echo for the WRITE_BLOCK command?
The echo comes after the data block. I've bin playing with sending it as one write too, but for some reason the device doesn't like that and programs nothing.
I think the magic is the serial_sleep (device->port, 2) between the WRITE_BLOCK + address and the data.
Maybe its not that bad, its only one function that has some special properties that can't use hw_ostc3_transfer.
I was surprised to notice that this was different from the other commands, and I was just wondering whether that was on purpose or by accident. If it doesn't work, then there's probably a good reason why it's different :-) We can check with Matthias.
I think there is some magic timing there, and it probably have $reasons.
You can later the results of my experiments in a #if 0 in a patch I'm going to send later.
Library triggered closed state: The device reboots when it gets the FLASH_FIRM-command so how can we "not fail" when the application then tries to talk to the device afterwards, or even call hw_ostc3_device_close?
I assume that the problem is that after the FLASH_FIRM command, we don't need to send the EXIT command in hw_ostc3_device_close? Or did you mean something else?
Exactly that. This is just the cousin to the open/service-mode problem.
Indeed. But that's easy to address once we have decided how we're going to implement the open function. If we are in download mode, we send the EXIT command, and in service mode we don't do anything.
BTW, that reminds me of something else. If the firmware update fails halfway for some reason, is there some command we need to send to exit service mode? (I haven't tried the firmware update yet, so I've no idea what happens in such a case.)
In the solution i wrote it will actually enter service mode and only if it sent FLASH_FIRM (0x50) it will skip the EXIT command, othervise it will EXIT from service mode the same way as it exits download mode.
There are also a couple of other FIXME's in the code, which I plan on cleaning up, but I thought that I should send out a first RFC patch series to get some additional feedback going.
Any other ideas about how the code looks?
I noticed a large number of sleep and flush calls. Are those really necessary? I have the feeling some of them were just copy and pasted.
Not really copy-pasted, but re-implemented the same ones as where in ostc-companion. I've removed most of them by now, and the ones left there kinda needs to be there.
If they are necessary to get the firmware update to work, then of course they need to stay. But if not, they are only slowing things down unnecessary. Hence my question.
Yea, I've cleaned up a bit of those. My first iteration i sent out was pretty much a re-implementation of how ostc-companion did it. Now I've looked at it and how the blocking on read is done in hw_ostc3_transfer solves most of those problems.
Yesterday I got a Ostc Sport to develop against (Thanks HW!) so i plan to verify and write some support for the Ostc Sport to.
Would you like to get some "merge-ready" patches for Ostc3 or would you like to wait before the Ostc Sport patches are ready too?
I would say that it would be good to merge the Ostc3 patches, and then iterate on-top of that for the Ostc Sport support.
//Anton