From fdb26e4b906761571b32bf9a8630455331725fb1 Mon Sep 17 00:00:00 2001 From: jonlamb-gh Date: Mon, 1 Oct 2018 06:39:14 -0700 Subject: [PATCH] Add basic FOTA capable bootloader This commit introduces a local lightweight bootloader that enables FOTA firmware updates. It currently supports firmware updates over TCP via smoltcp IP stack, but stubs are in place to support updates over CAN as well. This is the new flash layout: - first 8 bytes are used to store a sticky word for the bootloader flagging mechanism - followed by the bootloader firmware at 0x2000_0008 - bootloader configurations are stored in sector 3, 0x0801_8000 - OxCC firmware stored in sector 4, 0x0802_0000 To enter the bootloader, reset the board with the user-button pressed or send the board a bootloader reset CAN message. A simple host-side `firmware-updater` Python script is provided as an example tool to interract with the bootloader over a TCP connection. --- Cargo.lock | 252 ++++++++- Cargo.toml | 4 + memory.x | 8 +- oxcc-bootloader/.gdbinit | 21 + oxcc-bootloader/.gitignore | 3 + oxcc-bootloader/Cargo.lock | 390 ++++++++++++++ oxcc-bootloader/Cargo.toml | 48 ++ oxcc-bootloader/README.md | 133 +++++ oxcc-bootloader/build.rs | 6 + oxcc-bootloader/firmware-updater | 282 ++++++++++ oxcc-bootloader/memory.x | 25 + oxcc-bootloader/scripts/deploy | 12 + oxcc-bootloader/src/bootload.rs | 78 +++ oxcc-bootloader/src/cache.rs | 9 + oxcc-bootloader/src/config.rs | 85 +++ oxcc-bootloader/src/ethernet.rs | 519 +++++++++++++++++++ oxcc-bootloader/src/flash.rs | 286 ++++++++++ oxcc-bootloader/src/gpio.rs | 85 +++ oxcc-bootloader/src/lib.rs | 29 ++ oxcc-bootloader/src/main.rs | 175 +++++++ oxcc-bootloader/src/network.rs | 213 ++++++++ oxcc-bootloader/src/rcc.rs | 88 ++++ oxcc-bootloader/src/systick.rs | 11 + src/can_protocols/bootloader_can_protocol.rs | 3 + src/config.rs | 4 +- src/fw_update.rs | 20 + src/main.rs | 8 + 27 files changed, 2793 insertions(+), 4 deletions(-) create mode 100644 oxcc-bootloader/.gdbinit create mode 100644 oxcc-bootloader/.gitignore create mode 100644 oxcc-bootloader/Cargo.lock create mode 100644 oxcc-bootloader/Cargo.toml create mode 100644 oxcc-bootloader/README.md create mode 100644 oxcc-bootloader/build.rs create mode 100755 oxcc-bootloader/firmware-updater create mode 100644 oxcc-bootloader/memory.x create mode 100755 oxcc-bootloader/scripts/deploy create mode 100644 oxcc-bootloader/src/bootload.rs create mode 100644 oxcc-bootloader/src/cache.rs create mode 100644 oxcc-bootloader/src/config.rs create mode 100644 oxcc-bootloader/src/ethernet.rs create mode 100644 oxcc-bootloader/src/flash.rs create mode 100644 oxcc-bootloader/src/gpio.rs create mode 100644 oxcc-bootloader/src/lib.rs create mode 100644 oxcc-bootloader/src/main.rs create mode 100644 oxcc-bootloader/src/network.rs create mode 100644 oxcc-bootloader/src/rcc.rs create mode 100644 oxcc-bootloader/src/systick.rs create mode 100644 src/can_protocols/bootloader_can_protocol.rs create mode 100644 src/fw_update.rs diff --git a/Cargo.lock b/Cargo.lock index 3487d39..a4dbe79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,11 +8,42 @@ name = "bare-metal" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "bitflags" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "built" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "git2 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)", + "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "toml 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "byteorder" +version = "1.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "cast" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "cc" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "cortex-m" version = "0.5.7" @@ -62,6 +93,73 @@ name = "embedded_types" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "git2" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "libgit2-sys 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "idna" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-normalization 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libc" +version = "0.2.43" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libgit2-sys" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "libz-sys 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libz-sys" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "log" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "managed" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "nb" version = "0.1.1" @@ -131,7 +229,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "oxcc" -version = "0.0.1" +version = "0.1.0" dependencies = [ "cortex-m 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", "cortex-m-rt 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -139,11 +237,26 @@ dependencies = [ "embedded-hal 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "nucleo-f767zi 0.0.1 (git+https://github.com/jonlamb-gh/nucleo-f767zi.git)", "num 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "oxcc-bootloader 0.1.0", "panic-abort 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "panic-semihosting 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "oxcc-bootloader" +version = "0.1.0" +dependencies = [ + "built 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "cortex-m 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", + "cortex-m-rt 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", + "cortex-m-semihosting 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "panic-abort 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "smoltcp 0.4.0 (git+https://github.com/m-labs/smoltcp?rev=21396867114d267da06f19cc54cc4a1883b900a5)", + "stm32f7 0.2.2 (git+https://github.com/jonlamb-gh/stm32-rs.git?branch=stm32f767zit6-patches)", +] + [[package]] name = "panic-abort" version = "0.3.1" @@ -158,6 +271,16 @@ dependencies = [ "cortex-m-semihosting 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "percent-encoding" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "pkg-config" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "proc-macro2" version = "0.4.19" @@ -192,6 +315,39 @@ name = "rand_core" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "redox_syscall" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "smoltcp" +version = "0.4.0" +source = "git+https://github.com/m-labs/smoltcp?rev=21396867114d267da06f19cc54cc4a1883b900a5#21396867114d267da06f19cc54cc4a1883b900a5" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "managed 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "stm32f7" version = "0.2.2" @@ -227,21 +383,67 @@ dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "time" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "toml" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "typenum" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unicode-bidi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "unicode-xid" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "url" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "vcell" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "vcpkg" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "void" version = "1.0.2" @@ -255,16 +457,48 @@ dependencies = [ "vcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "winapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [metadata] "checksum aligned 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d39da9b88ae1a81c03c9c082b8db83f1d0e93914126041962af61034ab44c4a5" "checksum bare-metal 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1bdcf9294ed648c7cd29b11db06ea244005aeef50ae8f605b1a3af2940bf8f92" +"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" +"checksum built 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "61f5aae2fa15b68fbcf0cbab64e659a55d10e9bacc55d3470ef77ae73030d755" +"checksum byteorder 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "90492c5858dd7d2e78691cfb89f90d273a2800fc11d98f60786e5d87e2f83781" "checksum cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "926013f2860c46252efceabb19f4a6b308197505082c609025aa6706c011d427" +"checksum cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "f159dfd43363c4d08055a07703eb7a3406b0dac4d0584d96965a3262db3c9d16" +"checksum cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4e7bb64a8ebb0d856483e1e682ea3422f883c5f5615a90d51a2c82fe87fdd3" "checksum cortex-m 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)" = "4573199c5b1e9b0eeae418b46f7c0af5fdf11b3057f83880810dfef68dd1dcb5" "checksum cortex-m-rt 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9dea4ad5f88b4ccfba2b738ebe42f9452b80481c44aae42c594cc66cf2c5f3c0" "checksum cortex-m-rt-macros 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f96e6af14f78ca987ba5487592a199878a7b17ee65b60e0b4aa563fc00965f4f" "checksum cortex-m-semihosting 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "54d46ec4730314a01de4504328ef4ed6b2c51b63815caac4847ac9e70f88c9e5" "checksum embedded-hal 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "26944677e4934eb5fb4025501dc0d6cdbcf6bfabd6200fcfee2e7e8eef8c0362" "checksum embedded_types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ea8dc5db8dae723ecf68d863ff0011500e29fbbede193fa8fb3ca032595d3f6a" +"checksum git2 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)" = "591f8be1674b421644b6c030969520bc3fa12114d2eb467471982ed3e9584e71" +"checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" +"checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d" +"checksum libgit2-sys 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4916b5addc78ec36cc309acfcdf0b9f9d97ab7b84083118b248709c5b7029356" +"checksum libz-sys 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)" = "c7bdca442aa002a930e6eb2a71916cabe46d91ffec8df66db0abfb1bc83469ab" +"checksum log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fcce5fa49cc693c312001daf1d13411c4a5283796bac1084299ea3e567113f" +"checksum managed 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fdcec5e97041c7f0f1c5b7d93f12e57293c831c646f4cc7a5db59460c7ea8de6" +"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" "checksum nb 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "69f380b5fe9fab8c0d7a6a99cda23e2cc0463bedb2cbc3aada0813b98496ecdc" "checksum nucleo-f767zi 0.0.1 (git+https://github.com/jonlamb-gh/nucleo-f767zi.git)" = "" "checksum num 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cf4825417e1e1406b3782a8ce92f4d53f26ec055e3622e1881ca8e9f5f9e08db" @@ -275,16 +509,32 @@ dependencies = [ "checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" "checksum panic-abort 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2c14a66511ed17b6a8b4256b868d7fd207836d891db15eea5195dbcaf87e630f" "checksum panic-semihosting 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1017854db1621a236488ac359b89b19a56dcb1cb45127376d04f23128cea7210" +"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" +"checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c" "checksum proc-macro2 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)" = "ffe022fb8c8bd254524b0b3305906c1921fa37a84a644e29079a9e62200c3901" "checksum quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "dd636425967c33af890042c483632d33fa7a18f19ad1d7ea72e8998c6ef8dea5" "checksum r0 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2a38df5b15c8d5c7e8654189744d8e396bddc18ad48041a500ce52d6948941f" "checksum rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e464cd887e869cddcae8792a4ee31d23c7edd516700695608f5b98c67ee0131c" "checksum rand_core 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "edecf0f94da5551fc9b492093e30b041a891657db7940ee221f9d2f66e82eef2" +"checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1" +"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +"checksum serde 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)" = "84257ccd054dc351472528c8587b4de2dbf0dc0fe2e634030c1a90bfdacebaa9" +"checksum smoltcp 0.4.0 (git+https://github.com/m-labs/smoltcp?rev=21396867114d267da06f19cc54cc4a1883b900a5)" = "" "checksum stm32f7 0.2.2 (git+https://github.com/jonlamb-gh/stm32-rs.git?branch=stm32f767zit6-patches)" = "" "checksum stm32f767-hal 0.0.1 (git+https://github.com/jonlamb-gh/stm32f767-hal.git)" = "" "checksum syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)" = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741" +"checksum time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "d825be0eb33fda1a7e68012d51e9c7f451dc1a69391e7fdc197060bb8c56667b" +"checksum toml 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "b7e7d59d55f36979a9dd86d71ae54657a5e9c7fdb4fa2212f4064e2d32f9dcda" "checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169" +"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +"checksum unicode-normalization 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6a0180bc61fc5a987082bfa111f4cc95c4caff7f9799f3e46df09163a937aa25" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum url 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2a321979c09843d272956e73700d12c4e7d3d92b2ee112b31548aef0d4efc5a6" "checksum vcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "45c297f0afb6928cd08ab1ff9d95e99392595ea25ae1b5ecf822ff8764e57a0d" +"checksum vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "def296d3eb3b12371b2c7d0e83bfe1403e4db2d7a0bba324a12b21c4ee13143d" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum volatile-register 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0d67cb4616d99b940db1d6bd28844ff97108b498a6ca850e5b6191a532063286" +"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index aa5a4cd..5e52263 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,10 @@ license = "MIT OR Apache-2.0" readme = "README.md" repository = "https://github.com/jonlamb-gh/oxcc" +[dependencies.oxcc-bootloader] +path = "oxcc-bootloader/" +version = "0.1.0" + [dependencies.panic-abort] version = "0.3.1" optional = true diff --git a/memory.x b/memory.x index bca9958..34e9331 100644 --- a/memory.x +++ b/memory.x @@ -3,8 +3,12 @@ MEMORY /* NOTE K = KiBi = 1024 bytes */ /* STM32F767ZI 2 MByte FLASH, 512 KByte RAM */ - FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K - RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 512K + /* FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K */ + + /* Using OxCC bootloader, user firmware starts at 0x0802_0000 */ + FLASH (rx) : ORIGIN = 0x08020000, LENGTH = (2048K - 128K) + /* First 8 bytes are reserved for the bootloader sticky flag word */ + RAM (xrw) : ORIGIN = 0x20000008, LENGTH = (512K - 8) } /* This is where the call stack will be allocated. */ diff --git a/oxcc-bootloader/.gdbinit b/oxcc-bootloader/.gdbinit new file mode 100644 index 0000000..f2bf090 --- /dev/null +++ b/oxcc-bootloader/.gdbinit @@ -0,0 +1,21 @@ +target remote :3333 + +# print demangled symbols by default +set print asm-demangle on + +monitor arm semihosting enable + +# # send captured ITM to the file itm.fifo +# # (the microcontroller SWO pin must be connected to the programmer SWO pin) +# # 8000000 must match the core clock frequency +# monitor tpiu config internal itm.fifo uart off 8000000 + +# # OR: make the microcontroller SWO pin output compatible with UART (8N1) +# # 2000000 is the frequency of the SWO pin +# monitor tpiu config external uart off 8000000 2000000 + +# # enable ITM port 0 +# monitor itm port 0 on + +load +step diff --git a/oxcc-bootloader/.gitignore b/oxcc-bootloader/.gitignore new file mode 100644 index 0000000..2bb8d25 --- /dev/null +++ b/oxcc-bootloader/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +.idea diff --git a/oxcc-bootloader/Cargo.lock b/oxcc-bootloader/Cargo.lock new file mode 100644 index 0000000..5541091 --- /dev/null +++ b/oxcc-bootloader/Cargo.lock @@ -0,0 +1,390 @@ +[[package]] +name = "aligned" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bare-metal" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bitflags" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "built" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "git2 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)", + "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "toml 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "byteorder" +version = "1.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cc" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cortex-m" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aligned 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bare-metal 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "volatile-register 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cortex-m-rt" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cortex-m-rt-macros 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "r0 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cortex-m-rt-macros" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cortex-m-semihosting" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "git2" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "libgit2-sys 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "idna" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-normalization 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libc" +version = "0.2.43" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libgit2-sys" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "libz-sys 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libz-sys" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "log" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "managed" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "oxcc-bootloader" +version = "0.1.0" +dependencies = [ + "built 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "cortex-m 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", + "cortex-m-rt 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cortex-m-semihosting 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "panic-abort 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "smoltcp 0.4.0 (git+https://github.com/m-labs/smoltcp?rev=21396867114d267da06f19cc54cc4a1883b900a5)", + "stm32f7 0.2.2 (git+https://github.com/jonlamb-gh/stm32-rs.git?branch=stm32f767zit6-patches)", +] + +[[package]] +name = "panic-abort" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "percent-encoding" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "pkg-config" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "proc-macro2" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quote" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "r0" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rand" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "redox_syscall" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "smoltcp" +version = "0.4.0" +source = "git+https://github.com/m-labs/smoltcp?rev=21396867114d267da06f19cc54cc4a1883b900a5#21396867114d267da06f19cc54cc4a1883b900a5" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "managed 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "stm32f7" +version = "0.2.2" +source = "git+https://github.com/jonlamb-gh/stm32-rs.git?branch=stm32f767zit6-patches#54dafd12e4741573ad10cc530ac7e242ef68e87b" +dependencies = [ + "bare-metal 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "cortex-m 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", + "cortex-m-rt 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", + "vcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "syn" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "time" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "toml" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "url" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "vcell" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "vcpkg" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "volatile-register" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "vcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum aligned 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d39da9b88ae1a81c03c9c082b8db83f1d0e93914126041962af61034ab44c4a5" +"checksum bare-metal 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1bdcf9294ed648c7cd29b11db06ea244005aeef50ae8f605b1a3af2940bf8f92" +"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" +"checksum built 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "61f5aae2fa15b68fbcf0cbab64e659a55d10e9bacc55d3470ef77ae73030d755" +"checksum byteorder 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "90492c5858dd7d2e78691cfb89f90d273a2800fc11d98f60786e5d87e2f83781" +"checksum cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "f159dfd43363c4d08055a07703eb7a3406b0dac4d0584d96965a3262db3c9d16" +"checksum cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4e7bb64a8ebb0d856483e1e682ea3422f883c5f5615a90d51a2c82fe87fdd3" +"checksum cortex-m 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)" = "4573199c5b1e9b0eeae418b46f7c0af5fdf11b3057f83880810dfef68dd1dcb5" +"checksum cortex-m-rt 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d86cfa89fa220d3cb7a41133693e2d302d18e9a632298ffb3738f175c8c12325" +"checksum cortex-m-rt-macros 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3139fdadccaa0db6fa96637678ced9b0b97e4f10047c9ab603d125048e107d1a" +"checksum cortex-m-semihosting 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "54d46ec4730314a01de4504328ef4ed6b2c51b63815caac4847ac9e70f88c9e5" +"checksum git2 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)" = "591f8be1674b421644b6c030969520bc3fa12114d2eb467471982ed3e9584e71" +"checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" +"checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d" +"checksum libgit2-sys 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)" = "93f2b22fce91fb820363cf88a849a8f8fdfd8be37774b6a9dd6cbda05cf940e6" +"checksum libz-sys 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)" = "c7bdca442aa002a930e6eb2a71916cabe46d91ffec8df66db0abfb1bc83469ab" +"checksum log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fcce5fa49cc693c312001daf1d13411c4a5283796bac1084299ea3e567113f" +"checksum managed 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fdcec5e97041c7f0f1c5b7d93f12e57293c831c646f4cc7a5db59460c7ea8de6" +"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +"checksum panic-abort 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2c14a66511ed17b6a8b4256b868d7fd207836d891db15eea5195dbcaf87e630f" +"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" +"checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c" +"checksum proc-macro2 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)" = "ffe022fb8c8bd254524b0b3305906c1921fa37a84a644e29079a9e62200c3901" +"checksum quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "dd636425967c33af890042c483632d33fa7a18f19ad1d7ea72e8998c6ef8dea5" +"checksum r0 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2a38df5b15c8d5c7e8654189744d8e396bddc18ad48041a500ce52d6948941f" +"checksum rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e464cd887e869cddcae8792a4ee31d23c7edd516700695608f5b98c67ee0131c" +"checksum rand_core 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "edecf0f94da5551fc9b492093e30b041a891657db7940ee221f9d2f66e82eef2" +"checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1" +"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +"checksum serde 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)" = "84257ccd054dc351472528c8587b4de2dbf0dc0fe2e634030c1a90bfdacebaa9" +"checksum smoltcp 0.4.0 (git+https://github.com/m-labs/smoltcp?rev=21396867114d267da06f19cc54cc4a1883b900a5)" = "" +"checksum stm32f7 0.2.2 (git+https://github.com/jonlamb-gh/stm32-rs.git?branch=stm32f767zit6-patches)" = "" +"checksum syn 0.15.6 (registry+https://github.com/rust-lang/crates.io-index)" = "854b08a640fc8f54728fb95321e3ec485b365a97fe47609797c671addd1dde69" +"checksum time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "d825be0eb33fda1a7e68012d51e9c7f451dc1a69391e7fdc197060bb8c56667b" +"checksum toml 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a0263c6c02c4db6c8f7681f9fd35e90de799ebd4cfdeab77a38f4ff6b3d8c0d9" +"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +"checksum unicode-normalization 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6a0180bc61fc5a987082bfa111f4cc95c4caff7f9799f3e46df09163a937aa25" +"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum url 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2a321979c09843d272956e73700d12c4e7d3d92b2ee112b31548aef0d4efc5a6" +"checksum vcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "45c297f0afb6928cd08ab1ff9d95e99392595ea25ae1b5ecf822ff8764e57a0d" +"checksum vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "def296d3eb3b12371b2c7d0e83bfe1403e4db2d7a0bba324a12b21c4ee13143d" +"checksum volatile-register 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0d67cb4616d99b940db1d6bd28844ff97108b498a6ca850e5b6191a532063286" +"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/oxcc-bootloader/Cargo.toml b/oxcc-bootloader/Cargo.toml new file mode 100644 index 0000000..4906648 --- /dev/null +++ b/oxcc-bootloader/Cargo.toml @@ -0,0 +1,48 @@ +# The main binary application is the bootloader firmware +[package] +name = "oxcc-bootloader" +version = "0.1.0" +authors = ["Jon Lamb"] + +# Provides an interface for user firmware to reset into the bootloader +[lib] +name = "oxcc_bootloader_lib" +path = "src/lib.rs" + +[[bin]] +name = "oxcc-bootloader" +path = "src/main.rs" + +[dependencies] +cortex-m = "0.5.7" +cortex-m-rt = "0.6.3" +# switching over to semihosting can be useful for debugging +#panic-semihosting = "0.4.0" +panic-abort = "0.3.1" +cortex-m-semihosting = "0.3.1" +byteorder = { version = "1.2.2", default-features = false } + +[dependencies.smoltcp] +git = "https://github.com/m-labs/smoltcp" +rev = "21396867114d267da06f19cc54cc4a1883b900a5" +default-features = false +features = ["proto-ipv4", "socket-tcp"] + +[dependencies.stm32f7] +git = "https://github.com/jonlamb-gh/stm32-rs.git" +branch = "stm32f767zit6-patches" +version = "0.2.2" +features = ["stm32f7x7", "rt"] + +[build-dependencies] +built = "0.3.0" + +[profile.dev] +codegen-units = 1 +incremental = false + +[profile.release] +debug = true +lto = true +codegen-units = 1 +incremental = false diff --git a/oxcc-bootloader/README.md b/oxcc-bootloader/README.md new file mode 100644 index 0000000..b274d12 --- /dev/null +++ b/oxcc-bootloader/README.md @@ -0,0 +1,133 @@ +# OxCC Bootloader + +An FOTA capable bootloader used to enable `OxCC` firmware +updates via TCP or CAN. + +Inspired by [blethrs](https://github.com/AirborneEngineering/blethrs). + +## Building + +**NOTE**: debug builds take up too much space, the bootloader needs to fit in a specific flash sector. + +Can this be detected at compile-time? + +```bash +cargo build --release +``` + +## Deploying + +```bash +./scripts/deploy +``` + +## Default Config + +Without a valid config in flash, `OxCC` defaults to: + +- IP address: `10.1.1.0/24` +- gateway: `10.1.1.1` +- MAC: `02:00:01:02:03:04` + +## Debugging + +```bash +cargo run --release +``` + +```text +|-=-=-=-=-=-=-= 0xCC Bootloader =-=-=-=-=-=-=- +| Version 0.1.0 7c0ed4b +| Platform thumbv7em-none-eabihf +| Built on Tue, 25 Sep 2018 13:20:52 GMT +| rustc 1.30.0-nightly (cb6d2dfa8 2018-09-16) +|-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + Initialising cache... OK + Initialising clocks... OK + Initialising GPIOs... OK + Reading configuration... OK +UserConfig: + MAC Address: 02:00:03:07:03:05 + IP Address: 10.1.1.10/24 + Gateway: 10.1.1.1 + Checksum: B3819D1F + + Initialising Ethernet... OK + Waiting for link... OK + Initialising network... OK + Ready. +``` + +## Using `firmware-updater` + +An example host-side updater Python [script](firmware-updater) is used to talk to the bootloader. + +```bash +./firmware-updator -h +``` + +### Info + +```bash +./firmware-updater 10.1.1.10 info + +Connecting to bootloader... +Received bootloader information: +Version: 0.1.0 c661da9 +Built: Tue, 25 Sep 2018 15:52:39 GMT +Compiler: rustc 1.30.0-nightly (cb6d2dfa8 2018-09-16) +MCU ID: 303138353436511600450038 +``` + +### Boot + +```bash +./firmware-updater 10.1.1.10 boot + +Connecting to bootloader... +Received bootloader information: +Version: 0.1.0 c661da9 +Built: Tue, 25 Sep 2018 15:52:39 GMT +Compiler: rustc 1.30.0-nightly (cb6d2dfa8 2018-09-16) +MCU ID: 303138353436511600450038 + +Sending reboot command... +``` + +### Deploying New Firmware + +#### Reset to Bootloader + +Instruct OxCC firmware to reset into the bootloader with a CAN frame: + +- CAN ID: `0xF0` +- DLC: `8` +- DATA: not used yet + +Or hold down the user-button and reset the board. + + +#### Program Flash + +```bash +# ELF to binary +arm-none-eabi-objcopy -O binary ../target/thumbv7em-none-eabihf/release/oxcc oxcc.bin + +./firmware-updater 10.1.1.10 program oxcc.bin + +Connecting to bootloader... +Received bootloader information: +Version: 0.1.0 c661da9 +Built: Tue, 25 Sep 2018 15:52:39 GMT +Compiler: rustc 1.30.0-nightly (cb6d2dfa8 2018-09-16) +MCU ID: 303138353436511600450038 + +Erasing (may take a few seconds)... +Writing 53.14kB in 54 segments... +100%|██████████████████████████████████████████████████████████████████| 54/54 [00:00<00:00, 59.56kB/s] +Writing completed successfully. Reading back... +100%|██████████████████████████████████████████████████████████████████| 54/54 [00:00<00:00, 76.50kB/s] +Readback successful. +Sending reboot command... +``` diff --git a/oxcc-bootloader/build.rs b/oxcc-bootloader/build.rs new file mode 100644 index 0000000..3bfdd5d --- /dev/null +++ b/oxcc-bootloader/build.rs @@ -0,0 +1,6 @@ +extern crate built; + +fn main() { + // Gather build information + built::write_built_file().expect("Failed to acquire build-time information"); +} diff --git a/oxcc-bootloader/firmware-updater b/oxcc-bootloader/firmware-updater new file mode 100755 index 0000000..818b107 --- /dev/null +++ b/oxcc-bootloader/firmware-updater @@ -0,0 +1,282 @@ +#!/usr/bin/env python3 +# +# NOTE: this is more or less a copy of `blethrs.py` from: +# https://github.com/AirborneEngineering/blethrs/blob/master/blethrs.py +# +# TODO: proper Rust CLI app? +# + +import time +import struct +import socket +import argparse +import crcmod + +try: + from tqdm import tqdm +except ImportError: + print("Notice: tqdm not installed, install for progress bars.") + + def tqdm(x, *args, **kwargs): + return x + + +commands = { + "info": 0, + "read": 1, + "erase": 2, + "write": 3, + "boot": 4, +} + + +errors = { + 0: "Success", + 1: "Invalid Address", + 2: "Length Not Multiple of 4", + 3: "Length Too Long", + 4: "Data Length Incorrect", + 5: "Erase Error", + 6: "Write Error", + 7: "Flash Error", + 8: "Network Error", + 9: "Internal Error", +} + + +class BootloaderError(Exception): + def __init__(self, errno): + self.errno = errno + + def __str__(self): + if self.errno in errors: + return "{}".format(errors[self.errno]) + else: + return "Unknown error {}".format(self.errno) + + +class MismatchError(Exception): + def __init__(self, addr, tx, rx): + self.addr = addr + self.tx = tx + self.rx = rx + + def __str__(self): + return "Mismatch at address {:08X}: {:02X}!={:02X}".format( + self.addr, self.tx, self.rx) + + +def boot_request(hostname, boot_req_port, bootloader_port, n_attempts=10): + print("Sending UDP boot request to port {}...".format(boot_req_port)) + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + cmd = struct.pack("5I", config_bytes) + raw = struct.pack("<5I", *u32) + crc = crc32(raw) + crc_bytes = struct.pack(" = None; +use config::{BOOTLOAD_FLAG_ADDRESS, BOOTLOAD_FLAG_VALUE}; + +/// Returns true if the most recent reset was due to a software request +/// +/// Clears the reset cause before returning, so this answer is only valid once. +pub fn was_software_reset(rcc: &mut stm32f7x7::RCC) -> bool { + let result = rcc.csr.read().sftrstf().bit_is_set(); + rcc.csr.modify(|_, w| w.rmvf().set_bit()); + result +} + +/// Returns true if the bootload flag is set: RAM 0x2000_0000 == 0xB00110AD +/// +/// Clears the flag before returning, so this answer is only valid once. +pub fn flag_set() -> bool { + cortex_m::interrupt::free(|_| unsafe { + let flag = core::ptr::read_volatile(BOOTLOAD_FLAG_ADDRESS as *const u32); + clear_flag(); + flag == BOOTLOAD_FLAG_VALUE + }) +} + +fn clear_flag() { + cortex_m::interrupt::free(|_| unsafe { + core::ptr::write_volatile(BOOTLOAD_FLAG_ADDRESS as *mut u32, 0); + }); +} + +/// Trigger a reset that will cause us to boot into the bootloader next go +/// around +#[allow(unused)] +pub fn reset_to_bootloader() -> ! { + set_flag(); + sw_reset(); + unreachable!(); +} + +fn set_flag() { + cortex_m::interrupt::free(|_| unsafe { + core::ptr::write_volatile(BOOTLOAD_FLAG_ADDRESS as *mut u32, BOOTLOAD_FLAG_VALUE); + }); +} + +/// Trigger a reset that will cause us to bootload the user application next go +/// around +pub fn reset_to_user_firmware() { + clear_flag(); + sw_reset(); +} + +fn sw_reset() { + // It's troublesome to require SCB be passed in here, and + // we're literally about to reset the whole microcontroller, + // so safety is not such a huge concern. + let aircr = 0xE000ED0C as *mut u32; + unsafe { *aircr = (0x5FA << 16) | (1 << 2) }; +} + +/// Jump to user code at the given address. +/// +/// Doesn't disable interrupts so only call this right at boot, +/// when no interrupt sources will be enabled. +pub fn bootload(scb: &mut cortex_m::peripheral::SCB, address: u32) { + unsafe { + let sp = *(address as *const u32); + let rv = *((address + 4) as *const u32); + + USER_RESET = Some(core::mem::transmute(rv)); + scb.vtor.write(address); + cortex_m::register::msp::write(sp); + (USER_RESET.unwrap())(); + } +} diff --git a/oxcc-bootloader/src/cache.rs b/oxcc-bootloader/src/cache.rs new file mode 100644 index 0000000..05f2aaf --- /dev/null +++ b/oxcc-bootloader/src/cache.rs @@ -0,0 +1,9 @@ +use stm32f7::stm32f7x7; + +/// Enable I and D cache +pub fn cache_enable(core_peripherals: &mut stm32f7x7::CorePeripherals) { + core_peripherals.SCB.enable_icache(); + core_peripherals + .SCB + .enable_dcache(&mut core_peripherals.CPUID); +} diff --git a/oxcc-bootloader/src/config.rs b/oxcc-bootloader/src/config.rs new file mode 100644 index 0000000..935642e --- /dev/null +++ b/oxcc-bootloader/src/config.rs @@ -0,0 +1,85 @@ +//! Chip and board specific configuration settings go here. +use bootload; +use stm32f7x7; + +/// TCP port to listen on +pub const TCP_PORT: u16 = 7776; + +/// PHY address +pub const ETH_PHY_ADDR: u8 = 0; + +/// Start address of each sector in flash +pub const FLASH_SECTOR_ADDRESSES: [u32; 12] = [ + 0x0800_0000, + 0x0800_8000, + 0x0801_0000, + 0x0801_8000, + 0x0802_0000, + 0x0804_0000, + 0x0808_0000, + 0x080C_0000, + 0x0810_0000, + 0x0814_0000, + 0x0818_0000, + 0x081C_0000, +]; + +/// Final valid address in flash +pub const FLASH_END: u32 = 0x081F_FFFF; + +/// Address of configuration sector. Must be one of the start addresses in +/// FLASH_SECTOR_ADDRESSES. +pub const FLASH_CONFIG: u32 = FLASH_SECTOR_ADDRESSES[3]; + +/// Address of user firmware sector. Must be one of the start addresses in +/// FLASH_SECTOR_ADDRESSES. +pub const FLASH_USER: u32 = FLASH_SECTOR_ADDRESSES[4]; + +/// Magic value used in this module to check if bootloader should start. +pub const BOOTLOAD_FLAG_VALUE: u32 = 0xB00110AD; +/// Address of magic value used in this module to check if bootloader should +/// start. +/// SRAM1 starts at 0x2002_0000 +/// DTCM RAM starts at 0x2000_0000 +pub const BOOTLOAD_FLAG_ADDRESS: u32 = 0x2000_0000; + +/// This function should return true if the bootloader should enter bootload +/// mode, or false to immediately chainload the user firmware. +/// +/// By default we check if there was a software reset and a magic value is set +/// in RAM, but you could also check GPIOs etc here. +/// +/// Ensure any state change to the peripherals is reset before returning from +/// this function. +pub fn should_enter_bootloader(peripherals: &mut stm32f7x7::Peripherals) -> bool { + // Our plan is: + // * If the reset was a software reset, and the magic flag is in the magic + // location, then the user firmware requested bootload, so enter bootload. + // + // * Otherwise we check if PC13 (user-button) is HIGH for at least a + // full byte period of the UART + let cond1 = bootload::was_software_reset(&mut peripherals.RCC) && bootload::flag_set(); + + // User button on PC13, pull-down/active-high + peripherals.RCC.ahb1enr.modify(|_, w| w.gpiocen().enabled()); + peripherals.GPIOC.moder.modify(|_, w| w.moder13().input()); + peripherals + .GPIOC + .pupdr + .modify(|_, w| w.pupdr13().pull_down()); + + let hsi_clk = 16_000_000; + let sync_baud = 1_000_000; + let bit_periods = 10; + let delay = (hsi_clk / sync_baud) * bit_periods; + let mut cond2 = true; + for _ in 0..delay { + cond2 &= peripherals.GPIOC.idr.read().idr13().bit_is_set(); + } + + peripherals + .RCC + .ahb1enr + .modify(|_, w| w.gpiocen().disabled()); + cond1 || cond2 +} diff --git a/oxcc-bootloader/src/ethernet.rs b/oxcc-bootloader/src/ethernet.rs new file mode 100644 index 0000000..a069ff0 --- /dev/null +++ b/oxcc-bootloader/src/ethernet.rs @@ -0,0 +1,519 @@ +use core; +use cortex_m; +use stm32f7x7; + +use smoltcp::{ + self, + phy::{self, DeviceCapabilities}, + time::Instant, + wire::EthernetAddress, +}; + +const ETH_BUF_SIZE: usize = 1536; +const ETH_NUM_TD: usize = 4; +const ETH_NUM_RD: usize = 4; + +use config::ETH_PHY_ADDR; + +/// Transmit Descriptor representation +/// +/// * tdes0: ownership bit and transmit settings +/// * tdes1: transmit buffer lengths +/// * tdes2: transmit buffer address +/// * tdes3: not used +/// +/// Note that Copy and Clone are derived to support initialising an array of +/// TDes, but you may not move a TDes after its address has been given to the +/// ETH_DMA engine. +#[derive(Copy, Clone)] +#[repr(C, packed)] +struct TDes { + tdes0: u32, + tdes1: u32, + tdes2: u32, + tdes3: u32, +} + +impl TDes { + /// Initialises this TDes to point at the given buffer. + pub fn init(&mut self, tdbuf: &[u32]) { + // Set FS and LS on each descriptor: each will hold a single full segment. + self.tdes0 = (1 << 29) | (1 << 28); + // Store pointer to associated buffer. + self.tdes2 = tdbuf.as_ptr() as u32; + // No second buffer. + self.tdes3 = 0; + } + + /// Mark this TDes as end-of-ring. + pub fn set_end_of_ring(&mut self) { + self.tdes0 |= 1 << 21; + } + + /// Return true if the RDes is not currently owned by the DMA + pub fn available(&self) -> bool { + self.tdes0 & (1 << 31) == 0 + } + + /// Release this RDes back to DMA engine for transmission + pub unsafe fn release(&mut self) { + self.tdes0 |= 1 << 31; + } + + /// Set the length of data in the buffer pointed to by this TDes + pub unsafe fn set_length(&mut self, length: usize) { + self.tdes1 = (length as u32) & 0x1FFF; + } + + /// Access the buffer pointed to by this descriptor + pub unsafe fn buf_as_slice_mut(&self) -> &mut [u8] { + core::slice::from_raw_parts_mut(self.tdes2 as *mut _, self.tdes1 as usize & 0x1FFF) + } +} + +/// Store a ring of TDes and associated buffers +struct TDesRing { + td: [TDes; ETH_NUM_TD], + tbuf: [[u32; ETH_BUF_SIZE / 4]; ETH_NUM_TD], + tdidx: usize, +} + +static mut TDESRING: TDesRing = TDesRing { + td: [TDes { + tdes0: 0, + tdes1: 0, + tdes2: 0, + tdes3: 0, + }; ETH_NUM_TD], + tbuf: [[0; ETH_BUF_SIZE / 4]; ETH_NUM_TD], + tdidx: 0, +}; + +impl TDesRing { + /// Initialise this TDesRing + /// + /// The current memory address of the buffers inside this TDesRing will be + /// stored in the descriptors, so ensure the TDesRing is not moved + /// after initialisation. + pub fn init(&mut self) { + for (td, tdbuf) in self.td.iter_mut().zip(self.tbuf.iter()) { + td.init(&tdbuf[..]); + } + self.td.last_mut().unwrap().set_end_of_ring(); + } + + /// Return the address of the start of the TDes ring + pub fn ptr(&self) -> *const TDes { + self.td.as_ptr() + } + + /// Return true if a TDes is available for use + pub fn available(&self) -> bool { + self.td[self.tdidx].available() + } + + /// Return the next available TDes if any are available, otherwise None + pub fn next(&mut self) -> Option<&mut TDes> { + if self.available() { + let rv = Some(&mut self.td[self.tdidx]); + self.tdidx = (self.tdidx + 1) % ETH_NUM_TD; + rv + } else { + None + } + } +} + +/// Receive Descriptor representation +/// +/// * rdes0: ownership bit and received packet metadata +/// * rdes1: receive buffer lengths and settings +/// * rdes2: receive buffer address +/// * rdes3: not used +/// +/// Note that Copy and Clone are derived to support initialising an array of +/// TDes, but you may not move a TDes after its address has been given to the +/// ETH_DMA engine. +#[derive(Copy, Clone)] +#[repr(C, packed)] +struct RDes { + rdes0: u32, + rdes1: u32, + rdes2: u32, + rdes3: u32, +} + +impl RDes { + /// Initialises this RDes to point at the given buffer. + pub fn init(&mut self, rdbuf: &[u32]) { + // Mark each RDes as owned by the DMA engine. + self.rdes0 = 1 << 31; + // Store length of and pointer to associated buffer. + self.rdes1 = rdbuf.len() as u32 * 4; + self.rdes2 = rdbuf.as_ptr() as u32; + // No second buffer. + self.rdes3 = 0; + } + + /// Mark this RDes as end-of-ring. + pub fn set_end_of_ring(&mut self) { + self.rdes1 |= 1 << 15; + } + + /// Return true if the RDes is not currently owned by the DMA + pub fn available(&self) -> bool { + self.rdes0 & (1 << 31) == 0 + } + + /// Release this RDes back to the DMA engine + pub unsafe fn release(&mut self) { + self.rdes0 |= 1 << 31; + } + + /// Access the buffer pointed to by this descriptor + pub unsafe fn buf_as_slice(&self) -> &[u8] { + core::slice::from_raw_parts(self.rdes2 as *const _, (self.rdes0 >> 16) as usize & 0x3FFF) + } +} + +/// Store a ring of RDes and associated buffers +struct RDesRing { + rd: [RDes; ETH_NUM_RD], + rbuf: [[u32; ETH_BUF_SIZE / 4]; ETH_NUM_RD], + rdidx: usize, +} + +static mut RDESRING: RDesRing = RDesRing { + rd: [RDes { + rdes0: 0, + rdes1: 0, + rdes2: 0, + rdes3: 0, + }; ETH_NUM_RD], + rbuf: [[0; ETH_BUF_SIZE / 4]; ETH_NUM_RD], + rdidx: 0, +}; + +impl RDesRing { + /// Initialise this RDesRing + /// + /// The current memory address of the buffers inside this TDesRing will be + /// stored in the descriptors, so ensure the TDesRing is not moved + /// after initialisation. + pub fn init(&mut self) { + for (rd, rdbuf) in self.rd.iter_mut().zip(self.rbuf.iter()) { + rd.init(&rdbuf[..]); + } + self.rd.last_mut().unwrap().set_end_of_ring(); + } + + /// Return the address of the start of the RDes ring + pub fn ptr(&self) -> *const RDes { + self.rd.as_ptr() + } + + /// Return true if a RDes is available for use + pub fn available(&self) -> bool { + self.rd[self.rdidx].available() + } + + /// Return the next available RDes if any are available, otherwise None + pub fn next(&mut self) -> Option<&mut RDes> { + if self.available() { + let rv = Some(&mut self.rd[self.rdidx]); + self.rdidx = (self.rdidx + 1) % ETH_NUM_RD; + rv + } else { + None + } + } +} + +/// Ethernet device driver +pub struct EthernetDevice { + rdring: &'static mut RDesRing, + tdring: &'static mut TDesRing, + eth_mac: stm32f7x7::ETHERNET_MAC, + eth_dma: stm32f7x7::ETHERNET_DMA, +} + +static mut BUFFERS_USED: bool = false; + +impl EthernetDevice { + /// Create a new uninitialised EthernetDevice. + /// + /// You must move in ETH_MAC, ETH_DMA, and they are then kept by the device. + /// + /// You may only call this function once; subsequent calls will panic. + pub fn new( + eth_mac: stm32f7x7::ETHERNET_MAC, + eth_dma: stm32f7x7::ETHERNET_DMA, + ) -> EthernetDevice { + cortex_m::interrupt::free(|_| unsafe { + if BUFFERS_USED { + panic!("EthernetDevice already created"); + } + BUFFERS_USED = true; + EthernetDevice { + rdring: &mut RDESRING, + tdring: &mut TDESRING, + eth_mac, + eth_dma, + } + }) + } + + /// Initialise the ethernet driver. + /// + /// Sets up the descriptor structures, sets up the peripheral clocks and + /// GPIO configuration, and configures the ETH MAC and DMA peripherals. + /// + /// Brings up the PHY and then blocks waiting for a network link. + pub fn init(&mut self, rcc: &mut stm32f7x7::RCC, addr: EthernetAddress) { + self.tdring.init(); + self.rdring.init(); + + self.init_peripherals(rcc, addr); + + self.phy_reset(); + self.phy_init(); + } + + pub fn link_established(&mut self) -> bool { + return self.phy_poll_link(); + } + + pub fn block_until_link(&mut self) { + while !self.link_established() {} + } + + /// Resume suspended TX DMA operation + pub fn resume_tx_dma(&mut self) { + if self.eth_dma.dmasr.read().tps().is_suspended() { + self.eth_dma.dmatpdr.write(|w| w.tpd().poll()); + } + } + + /// Resume suspended RX DMA operation + pub fn resume_rx_dma(&mut self) { + if self.eth_dma.dmasr.read().rps().is_suspended() { + self.eth_dma.dmarpdr.write(|w| w.rpd().poll()); + } + } + + /// Sets up the device peripherals. + fn init_peripherals(&mut self, rcc: &mut stm32f7x7::RCC, mac: EthernetAddress) { + // Reset ETH_MAC and ETH_DMA + rcc.ahb1rstr.modify(|_, w| w.ethmacrst().reset()); + rcc.ahb1rstr.modify(|_, w| w.ethmacrst().clear_bit()); + self.eth_dma.dmabmr.modify(|_, w| w.sr().reset()); + while self.eth_dma.dmabmr.read().sr().is_reset() {} + + // Set MAC address + let mac = mac.as_bytes(); + self.eth_mac.maca0lr.write(|w| { + w.maca0l().bits( + (mac[0] as u32) << 0 + | (mac[1] as u32) << 8 + | (mac[2] as u32) << 16 + | (mac[3] as u32) << 24, + ) + }); + self.eth_mac + .maca0hr + .write(|w| w.maca0h().bits((mac[4] as u16) << 0 | (mac[5] as u16) << 8)); + + // Enable RX and TX. We'll set link speed and duplex at link-up. + self.eth_mac + .maccr + .write(|w| w.re().enabled().te().enabled().cstf().enabled()); + + // Tell the ETH DMA the start of each ring + self.eth_dma + .dmatdlar + .write(|w| w.stl().bits(self.tdring.ptr() as u32)); + self.eth_dma + .dmardlar + .write(|w| w.srl().bits(self.rdring.ptr() as u32)); + + // Set DMA bus mode + self.eth_dma + .dmabmr + .modify(|_, w| w.aab().aligned().pbl().pbl1()); + + // Flush TX FIFO + self.eth_dma.dmaomr.write(|w| w.ftf().flush()); + while self.eth_dma.dmaomr.read().ftf().is_flush() {} + + // Set DMA operation mode to store-and-forward and start DMA + self.eth_dma.dmaomr.write(|w| { + w.rsf() + .store_forward() + .tsf() + .store_forward() + .st() + .started() + .sr() + .started() + }); + } + + /// Read a register over SMI. + fn smi_read(&mut self, reg: u8) -> u16 { + // Use PHY address 00000, set register address, set clock to HCLK/102, start + // read. + self.eth_mac.macmiiar.write(|w| { + w.mb() + .busy() + .pa() + .bits(ETH_PHY_ADDR) + .cr() + .cr_150_168() + .mr() + .bits(reg) + }); + + // Wait for read + while self.eth_mac.macmiiar.read().mb().is_busy() {} + + // Return result + self.eth_mac.macmiidr.read().td().bits() + } + + /// Write a register over SMI. + fn smi_write(&mut self, reg: u8, val: u16) { + // Use PHY address 00000, set write data, set register address, set clock to + // HCLK/102, start write operation. + self.eth_mac.macmiidr.write(|w| w.td().bits(val)); + self.eth_mac.macmiiar.write(|w| { + w.mb() + .busy() + .pa() + .bits(ETH_PHY_ADDR) + .mw() + .write() + .cr() + .cr_150_168() + .mr() + .bits(reg) + }); + + while self.eth_mac.macmiiar.read().mb().is_busy() {} + } + + /// Reset the connected PHY and wait for it to come out of reset. + fn phy_reset(&mut self) { + self.smi_write(0x00, 1 << 15); + while self.smi_read(0x00) & (1 << 15) == (1 << 15) {} + } + + /// Command connected PHY to initialise. + fn phy_init(&mut self) { + self.smi_write(0x00, 1 << 12); + } + + /// Poll PHY to determine link status. + fn phy_poll_link(&mut self) -> bool { + let bsr = self.smi_read(0x01); + let bcr = self.smi_read(0x00); + let lpa = self.smi_read(0x05); + + // No link without autonegotiate + if bcr & (1 << 12) == 0 { + return false; + } + // No link if link is down + if bsr & (1 << 2) == 0 { + return false; + } + // No link if remote fault + if bsr & (1 << 4) != 0 { + return false; + } + // No link if autonegotiate incomplete + if bsr & (1 << 5) == 0 { + return false; + } + // No link if other side can't do 100Mbps full duplex + if lpa & (1 << 8) == 0 { + return false; + } + + // Got link. Configure MAC to 100Mbit/s and full duplex. + self.eth_mac + .maccr + .modify(|_, w| w.fes().fes100().dm().full_duplex()); + + true + } +} + +pub struct TxToken(*mut EthernetDevice); +pub struct RxToken(*mut EthernetDevice); + +impl phy::TxToken for TxToken { + fn consume(self, _timestamp: Instant, len: usize, f: F) -> smoltcp::Result + where + F: FnOnce(&mut [u8]) -> smoltcp::Result, + { + // There can only be a single EthernetDevice and therefore all TxTokens are + // wrappers to a raw pointer to it. Unsafe required to dereference this + // pointer and call the various TDes methods. + assert!(len <= ETH_BUF_SIZE); + unsafe { + let tdes = (*self.0).tdring.next().unwrap(); + tdes.set_length(len); + let result = f(tdes.buf_as_slice_mut()); + tdes.release(); + (*self.0).resume_tx_dma(); + result + } + } +} + +impl phy::RxToken for RxToken { + fn consume(self, _timestamp: Instant, f: F) -> smoltcp::Result + where + F: FnOnce(&[u8]) -> smoltcp::Result, + { + // There can only be a single EthernetDevice and therefore all RxTokens are + // wrappers to a raw pointer to it. Unsafe required to dereference this + // pointer and call the various RDes methods. + unsafe { + let rdes = (*self.0).rdring.next().unwrap(); + let result = f(rdes.buf_as_slice()); + rdes.release(); + (*self.0).resume_rx_dma(); + result + } + } +} + +// Implement the smoltcp Device interface +impl<'a> phy::Device<'a> for EthernetDevice { + type RxToken = RxToken; + type TxToken = TxToken; + + fn capabilities(&self) -> DeviceCapabilities { + let mut caps = DeviceCapabilities::default(); + caps.max_transmission_unit = 1500; + caps.max_burst_size = Some(core::cmp::min(ETH_NUM_TD, ETH_NUM_RD)); + caps + } + + fn receive(&mut self) -> Option<(RxToken, TxToken)> { + if self.rdring.available() && self.tdring.available() { + Some((RxToken(self), TxToken(self))) + } else { + None + } + } + + fn transmit(&mut self) -> Option { + if self.tdring.available() { + Some(TxToken(self)) + } else { + None + } + } +} diff --git a/oxcc-bootloader/src/flash.rs b/oxcc-bootloader/src/flash.rs new file mode 100644 index 0000000..4b41f53 --- /dev/null +++ b/oxcc-bootloader/src/flash.rs @@ -0,0 +1,286 @@ +use core; +use stm32f7x7; + +use core::fmt; +use {Error, Result}; + +const CONFIG_MAGIC: u32 = 0x67797870; + +use config::{FLASH_CONFIG, FLASH_END, FLASH_SECTOR_ADDRESSES, FLASH_USER}; + +static mut FLASH: Option = None; + +/// Call to move the flash peripheral into this module +pub fn init(flash: stm32f7x7::FLASH) { + unsafe { FLASH = Some(flash) }; +} + +/// User configuration. Must live in flash at FLASH_CONFIG, 0x0800_C000. +/// `magic` must be set to 0x67797870. `checksum` must be the CRC32 of the +/// preceeding bytes. +#[derive(Copy, Clone)] +#[repr(C, packed)] +pub struct UserConfig { + magic: u32, + pub mac_address: [u8; 6], + pub ip_address: [u8; 4], + pub ip_gateway: [u8; 4], + pub ip_prefix: u8, + _padding: [u8; 1], + checksum: u32, +} + +impl fmt::Display for UserConfig { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!(f, "UserConfig:")?; + writeln!( + f, + " MAC Address: {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}", + self.mac_address[0], + self.mac_address[1], + self.mac_address[2], + self.mac_address[3], + self.mac_address[4], + self.mac_address[5] + )?; + writeln!( + f, + " IP Address: {}.{}.{}.{}/{}", + self.ip_address[0], + self.ip_address[1], + self.ip_address[2], + self.ip_address[3], + self.ip_prefix + )?; + writeln!( + f, + " Gateway: {}.{}.{}.{}", + self.ip_gateway[0], self.ip_gateway[1], self.ip_gateway[2], self.ip_gateway[3] + )?; + writeln!(f, " Checksum: {:08X}", self.checksum as u32) + } +} + +pub static DEFAULT_CONFIG: UserConfig = UserConfig { + // Locally administered MAC + magic: 0, + mac_address: [0x02, 0x00, 0x01, 0x02, 0x03, 0x04], + ip_address: [10, 1, 1, 10], + ip_gateway: [10, 1, 1, 1], + ip_prefix: 24, + _padding: [0u8; 1], + checksum: 0, +}; + +impl UserConfig { + /// Attempt to read the UserConfig from flash sector 3. + /// If a valid config cannot be read, the default one is returned instead. + pub fn get(crc: &mut stm32f7x7::CRC) -> Option { + // Read config from flash + let adr = FLASH_CONFIG as *const u32; + let cfg = unsafe { *(FLASH_CONFIG as *const UserConfig) }; + + // First check magic is correct + if cfg.magic != CONFIG_MAGIC { + return None; + } + + // Validate checksum + let len = core::mem::size_of::() / 4; + crc.cr.write(|w| w.reset().reset()); + for idx in 0..(len - 1) { + let val = unsafe { *(adr.offset(idx as isize)) }; + crc.dr.write(|w| w.dr().bits(val)); + } + let crc_computed = crc.dr.read().dr().bits(); + + if crc_computed == cfg.checksum { + Some(cfg.clone()) + } else { + None + } + } +} + +/// Try to determine if there is valid code in the user flash at 0x0801_0000. +/// Returns Some(u32) with the address to jump to if so, and None if not. +pub fn valid_user_code() -> Option { + let reset_vector: u32 = unsafe { *((FLASH_USER + 4) as *const u32) }; + if reset_vector >= FLASH_USER && reset_vector <= FLASH_END { + Some(FLASH_USER) + } else { + None + } +} + +/// Check if address+length is valid for read/write flash. +fn check_address_valid(address: u32, length: usize) -> Result<()> { + if address < FLASH_CONFIG { + Err(Error::InvalidAddress) + } else if address > (FLASH_END - length as u32 + 1) { + Err(Error::InvalidAddress) + } else { + Ok(()) + } +} + +/// Check length is a multiple of 4 and no greater than 1024 +fn check_length_valid(length: usize) -> Result<()> { + if length % 4 != 0 { + Err(Error::LengthNotMultiple4) + } else if length > 1024 { + Err(Error::LengthTooLong) + } else { + Ok(()) + } +} + +/// Check the specified length matches the amount of data available +fn check_length_correct(length: usize, data: &[u8]) -> Result<()> { + if length != data.len() { + Err(Error::DataLengthIncorrect) + } else { + Ok(()) + } +} + +/// Try to get the FLASH peripheral +fn get_flash_peripheral() -> Result<&'static mut stm32f7x7::FLASH> { + match unsafe { FLASH.as_mut() } { + Some(flash) => Ok(flash), + None => Err(Error::InternalError), + } +} + +/// Try to unlock flash +fn unlock(flash: &mut stm32f7x7::FLASH) -> Result<()> { + // Wait for any ongoing operations + while flash.sr.read().bsy().bit_is_set() {} + + // Attempt unlock + flash.keyr.write(|w| w.key().bits(0x45670123)); + flash.keyr.write(|w| w.key().bits(0xCDEF89AB)); + + // Verify success + match flash.cr.read().lock().is_unlocked() { + true => Ok(()), + false => Err(Error::FlashError), + } +} + +/// Lock flash +fn lock(flash: &mut stm32f7x7::FLASH) { + flash.cr.write(|w| w.lock().locked()); +} + +/// Erase flash sectors that cover the given address and length. +pub fn erase(address: u32, length: usize) -> Result<()> { + check_address_valid(address, length)?; + let address_start = address; + let address_end = address + length as u32; + for (idx, sector_start) in FLASH_SECTOR_ADDRESSES.iter().enumerate() { + let sector_start = *sector_start; + let sector_end = match FLASH_SECTOR_ADDRESSES.get(idx + 1) { + Some(adr) => *adr - 1, + None => FLASH_END, + }; + if (address_start >= sector_start && address_start <= sector_end) + || (address_end >= sector_start && address_end <= sector_end) + || (address_start <= sector_start && address_end >= sector_end) + { + erase_sector(idx as u8)?; + } + } + Ok(()) +} + +/// Erase specified sector +fn erase_sector(sector: u8) -> Result<()> { + if (sector as usize) >= FLASH_SECTOR_ADDRESSES.len() { + return Err(Error::InternalError); + } + let flash = get_flash_peripheral()?; + unlock(flash)?; + + // Erase. + // UNSAFE: We've verified that `sector` Result<&'static [u8]> { + check_address_valid(address, length)?; + check_length_valid(length)?; + let address = address as *const _; + unsafe { Ok(core::slice::from_raw_parts::<'static, u8>(address, length)) } +} + +/// Write to flash. +/// Returns () on success, None on failure. +/// length must be a multiple of 4. +pub fn write(address: u32, length: usize, data: &[u8]) -> Result<()> { + check_address_valid(address, length)?; + check_length_valid(length)?; + check_length_correct(length, data)?; + let flash = get_flash_peripheral()?; + unlock(flash)?; + + // Set parallelism to write in 32 bit chunks, and enable programming. + // Note reset value has 1 for lock so we need to explicitly clear it. + flash + .cr + .write(|w| w.lock().unlocked().psize().psize32().pg().program()); + + for idx in 0..(length / 4) { + let offset = idx * 4; + let word: u32 = (data[offset] as u32) + | (data[offset + 1] as u32) << 8 + | (data[offset + 2] as u32) << 16 + | (data[offset + 3] as u32) << 24; + let write_address = (address + offset as u32) as *mut u32; + unsafe { core::ptr::write_volatile(write_address, word) }; + + // Wait for write + while flash.sr.read().bsy().bit_is_set() {} + + // Check for errors + let sr = flash.sr.read(); + if sr.pgserr().bit_is_set() + || sr.pgperr().bit_is_set() + || sr.pgaerr().bit_is_set() + || sr.wrperr().bit_is_set() + { + lock(flash); + // TODO - getting flash write errors when writing configs? + //panic!("SR = 0x{:X} len {}", sr.bits(), length); + return Err(Error::WriteError); + } + } + + lock(flash); + + Ok(()) +} diff --git a/oxcc-bootloader/src/gpio.rs b/oxcc-bootloader/src/gpio.rs new file mode 100644 index 0000000..13b15dc --- /dev/null +++ b/oxcc-bootloader/src/gpio.rs @@ -0,0 +1,85 @@ +use stm32f7::stm32f7x7; + +/// Set up GPIOs for ethernet. +/// +/// All GPIO clocks are already enabled. +pub fn gpio_init(peripherals: &mut stm32f7x7::Peripherals) { + let gpioa = &peripherals.GPIOA; + let gpiob = &peripherals.GPIOB; + let gpioc = &peripherals.GPIOC; + let gpiog = &peripherals.GPIOG; + + // Status LED (red) on PB14 + gpiob.moder.modify(|_, w| w.moder14().output()); + gpiob.odr.modify(|_, w| w.odr14().set_bit()); + + // User button on PC13, pull-down/active-high + gpioc.moder.modify(|_, w| w.moder13().input()); + gpioc.pupdr.modify(|_, w| w.pupdr13().pull_down()); + + // Configure ethernet related GPIO: + // GPIOA 1, 2, 7 + // GPIOB 13 + // GPIOC 1, 4, 5 + // GPIOG 2, 11, 13 + // All set to AF11 and very high speed + gpioa.moder.modify(|_, w| { + w.moder1() + .alternate() + .moder2() + .alternate() + .moder7() + .alternate() + }); + gpiob.moder.modify(|_, w| w.moder13().alternate()); + gpioc.moder.modify(|_, w| { + w.moder1() + .alternate() + .moder4() + .alternate() + .moder5() + .alternate() + }); + gpiog.moder.modify(|_, w| { + w.moder2() + .alternate() + .moder11() + .alternate() + .moder13() + .alternate() + }); + gpioa.ospeedr.modify(|_, w| { + w.ospeedr1() + .very_high_speed() + .ospeedr2() + .very_high_speed() + .ospeedr7() + .very_high_speed() + }); + gpiob.ospeedr.modify(|_, w| w.ospeedr13().very_high_speed()); + gpioc.ospeedr.modify(|_, w| { + w.ospeedr1() + .very_high_speed() + .ospeedr4() + .very_high_speed() + .ospeedr5() + .very_high_speed() + }); + gpiog.ospeedr.modify(|_, w| { + w.ospeedr2() + .very_high_speed() + .ospeedr11() + .very_high_speed() + .ospeedr13() + .very_high_speed() + }); + gpioa + .afrl + .modify(|_, w| w.afrl1().af11().afrl2().af11().afrl7().af11()); + gpiob.afrh.modify(|_, w| w.afrh13().af11()); + gpioc + .afrl + .modify(|_, w| w.afrl1().af11().afrl4().af11().afrl5().af11()); + gpiog.afrl.modify(|_, w| w.afrl2().af11()); + gpiog.afrh.modify(|_, w| w.afrh11().af11().afrh13().af11()); +} diff --git a/oxcc-bootloader/src/lib.rs b/oxcc-bootloader/src/lib.rs new file mode 100644 index 0000000..4b70dd9 --- /dev/null +++ b/oxcc-bootloader/src/lib.rs @@ -0,0 +1,29 @@ +#![no_std] + +extern crate cortex_m; +extern crate stm32f7; + +#[allow(dead_code, unused_variables)] +mod bootload; +#[allow(dead_code, unused_variables)] +mod config; + +use stm32f7::stm32f7x7; + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum Error { + Success, + InvalidAddress, + LengthNotMultiple4, + LengthTooLong, + DataLengthIncorrect, + EraseError, + WriteError, + FlashError, + NetworkError, + InternalError, +} + +pub type Result = core::result::Result; + +pub use self::bootload::reset_to_bootloader; diff --git a/oxcc-bootloader/src/main.rs b/oxcc-bootloader/src/main.rs new file mode 100644 index 0000000..2616bdd --- /dev/null +++ b/oxcc-bootloader/src/main.rs @@ -0,0 +1,175 @@ +#![no_std] +#![no_main] + +extern crate byteorder; +extern crate cortex_m; +extern crate cortex_m_rt; +extern crate cortex_m_semihosting; +extern crate oxcc_bootloader_lib; +extern crate panic_abort; +// Can be useful for debugging +//extern crate panic_semihosting; +extern crate smoltcp; +extern crate stm32f7; + +use cortex_m_rt::{entry, exception, ExceptionFrame}; +use stm32f7::stm32f7x7; + +/// Try to print over semihosting if a debugger is available +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => ({ + use core::fmt::Write; + use cortex_m; + use cortex_m_semihosting; + if unsafe { (*cortex_m::peripheral::DCB::ptr()).dhcsr.read() & 1 == 1 } { + if let Ok(mut stdout) = cortex_m_semihosting::hio::hstdout() { + write!(stdout, $($arg)*).ok(); + } + } + }) +} + +/// Try to print a line over semihosting if a debugger is available +#[macro_export] +macro_rules! println { + ($fmt:expr) => (print!(concat!($fmt, "\n"))); + ($fmt:expr, $($arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*)); +} + +mod bootload; +mod cache; +mod config; +mod ethernet; +mod flash; +mod gpio; +mod network; +mod rcc; +mod systick; + +use cache::cache_enable; +use gpio::gpio_init; +use oxcc_bootloader_lib::{Error, Result}; +use rcc::rcc_init; +use systick::systick_init; + +// Pull in build information (from `built` crate) +mod build_info { + #![allow(dead_code)] + include!(concat!(env!("OUT_DIR"), "/built.rs")); +} + +#[entry] +fn main() -> ! { + let mut peripherals = stm32f7x7::Peripherals::take().unwrap(); + let mut core_peripherals = stm32f7x7::CorePeripherals::take().unwrap(); + + // Jump to user code if it exists and hasn't asked us to run + if let Some(address) = flash::valid_user_code() { + if !config::should_enter_bootloader(&mut peripherals) { + bootload::bootload(&mut core_peripherals.SCB, address); + } + } + + println!(""); + println!("|-=-=-=-=-=-=-= 0xCC Bootloader =-=-=-=-=-=-=-"); + println!( + "| Version {} {}", + build_info::PKG_VERSION, + build_info::GIT_VERSION.unwrap() + ); + println!("| Platform {}", build_info::TARGET); + println!("| Built on {}", build_info::BUILT_TIME_UTC); + println!("| {}", build_info::RUSTC_VERSION); + println!("|-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n"); + + print!(" Initialising cache... "); + cache_enable(&mut core_peripherals); + println!("OK"); + + print!(" Initialising clocks... "); + rcc_init(&mut peripherals); + println!("OK"); + + print!(" Initialising GPIOs... "); + gpio_init(&mut peripherals); + println!("OK"); + + print!(" Reading configuration... "); + let cfg = match flash::UserConfig::get(&mut peripherals.CRC) { + Some(cfg) => { + println!("OK"); + cfg + } + None => { + println!("Err\nCouldn't read configuration, using default."); + flash::DEFAULT_CONFIG + } + }; + println!("{}", cfg); + let mac_addr = smoltcp::wire::EthernetAddress::from_bytes(&cfg.mac_address); + + print!(" Initialising Ethernet... "); + let mut ethdev = + ethernet::EthernetDevice::new(peripherals.ETHERNET_MAC, peripherals.ETHERNET_DMA); + ethdev.init(&mut peripherals.RCC, mac_addr); + println!("OK"); + + print!(" Waiting for link... "); + ethdev.block_until_link(); + println!("OK"); + + print!(" Initialising network... "); + let ip_addr = smoltcp::wire::Ipv4Address::from_bytes(&cfg.ip_address); + let ip_cidr = smoltcp::wire::Ipv4Cidr::new(ip_addr, cfg.ip_prefix); + let cidr = smoltcp::wire::IpCidr::Ipv4(ip_cidr); + network::init(ethdev, mac_addr, cidr); + println!("OK"); + + // Move flash peripheral into flash module + flash::init(peripherals.FLASH); + + // TODO - Blink status LED? + println!(" Ready.\n"); + + // Begin periodic tasks via systick + systick_init(&mut core_peripherals.SYST); + + loop { + cortex_m::asm::wfi(); + } +} + +static mut SYSTICK_TICKS: u32 = 0; +static mut SYSTICK_RESET_AT: Option = None; + +#[exception] +fn SysTick() { + let ticks = unsafe { core::ptr::read_volatile(&SYSTICK_TICKS) + 1 }; + unsafe { core::ptr::write_volatile(&mut SYSTICK_TICKS, ticks) }; + network::poll(i64::from(ticks)); + if let Some(reset_time) = unsafe { core::ptr::read_volatile(&SYSTICK_RESET_AT) } { + if ticks >= reset_time { + println!("Performing scheduled reset"); + bootload::reset_to_user_firmware(); + } + } +} + +/// Reset after some ms delay. +pub fn schedule_reset(delay: u32) { + cortex_m::interrupt::free(|_| unsafe { + let ticks = core::ptr::read_volatile(&SYSTICK_TICKS) + delay; + core::ptr::write_volatile(&mut SYSTICK_RESET_AT, Some(ticks)); + }); +} + +#[exception] +fn HardFault(ef: &ExceptionFrame) -> ! { + panic!("HardFault at {:#?}", ef); +} + +#[exception] +fn DefaultHandler(irqn: i16) { + panic!("Unhandled exception (IRQn = {})", irqn); +} diff --git a/oxcc-bootloader/src/network.rs b/oxcc-bootloader/src/network.rs new file mode 100644 index 0000000..7925ec9 --- /dev/null +++ b/oxcc-bootloader/src/network.rs @@ -0,0 +1,213 @@ +use core::fmt::Write; + +use smoltcp; +use smoltcp::iface::{EthernetInterface, EthernetInterfaceBuilder, Neighbor, NeighborCache}; +use smoltcp::socket::{SocketHandle, SocketSet, SocketSetItem, TcpSocket, TcpSocketBuffer}; +use smoltcp::time::Instant; +use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr}; + +use cortex_m; + +use byteorder::{ByteOrder, LittleEndian}; + +use build_info; +use ethernet::EthernetDevice; +use flash; +use Error; + +const CMD_INFO: u32 = 0; +const CMD_READ: u32 = 1; +const CMD_ERASE: u32 = 2; +const CMD_WRITE: u32 = 3; +const CMD_BOOT: u32 = 4; + +use config::TCP_PORT; + +/// Read an address and length from the socket +fn read_adr_len(socket: &mut TcpSocket) -> (u32, usize) { + let mut adr = [0u8; 4]; + let mut len = [0u8; 4]; + socket.recv_slice(&mut adr[..]).ok(); + socket.recv_slice(&mut len[..]).ok(); + let adr = LittleEndian::read_u32(&adr); + let len = LittleEndian::read_u32(&len); + (adr, len as usize) +} + +/// Send a status word back at the start of a response +fn send_status(socket: &mut TcpSocket, status: ::Error) { + let mut resp = [0u8; 4]; + LittleEndian::write_u32(&mut resp, status as u32); + socket.send_slice(&resp).unwrap(); +} + +/// Respond to the information request command with our build information. +fn cmd_info(socket: &mut TcpSocket) { + // Read the device unique ID, see 45.6 + let id1: u32 = unsafe { *(0x1FF0_F420 as *const u32) }; + let id2: u32 = unsafe { *(0x1FF0_F424 as *const u32) }; + let id3: u32 = unsafe { *(0x1FF0_F428 as *const u32) }; + + send_status(socket, Error::Success); + write!( + socket, + "Version: {} {}\r\nBuilt: {}\r\nCompiler: {}\r\nMCU ID: {:08X}{:08X}{:08X}\r\n", + build_info::PKG_VERSION, + build_info::GIT_VERSION.unwrap(), + build_info::BUILT_TIME_UTC, + build_info::RUSTC_VERSION, + id3, + id2, + id1 + ).ok(); +} + +fn cmd_read(socket: &mut TcpSocket) { + let (adr, len) = read_adr_len(socket); + match flash::read(adr, len) { + Ok(data) => { + send_status(socket, Error::Success); + socket.send_slice(data).unwrap(); + } + Err(err) => send_status(socket, err), + }; +} + +fn cmd_erase(socket: &mut TcpSocket) { + let (adr, len) = read_adr_len(socket); + match flash::erase(adr, len) { + Ok(()) => send_status(socket, Error::Success), + Err(err) => send_status(socket, err), + } +} + +fn cmd_write(socket: &mut TcpSocket) { + let (adr, len) = read_adr_len(socket); + match socket.recv(|buf| (buf.len(), flash::write(adr, len, buf))) { + Ok(Ok(())) => send_status(socket, Error::Success), + Ok(Err(err)) => send_status(socket, err), + Err(_) => send_status(socket, Error::NetworkError), + } +} + +fn cmd_boot(socket: &mut TcpSocket) { + send_status(socket, Error::Success); + ::schedule_reset(50); +} + +// Stores the underlying data buffers. If these were included in Network, +// they couldn't live in BSS and therefore take up a load of flash space. +struct NetworkBuffers { + tcp_tx_buf: [u8; 1536], + tcp_rx_buf: [u8; 1536], +} + +static mut NETWORK_BUFFERS: NetworkBuffers = NetworkBuffers { + tcp_tx_buf: [0u8; 1536], + tcp_rx_buf: [0u8; 1536], +}; + +// Stores all the smoltcp required structs. +pub struct Network<'a> { + neighbor_cache_storage: [Option<(IpAddress, Neighbor)>; 16], + ip_addr: Option<[IpCidr; 1]>, + eth_iface: Option>, + sockets_storage: [Option>; 1], + sockets: Option>, + tcp_handle: Option, + initialised: bool, +} + +static mut NETWORK: Network = Network { + neighbor_cache_storage: [None; 16], + ip_addr: None, + eth_iface: None, + sockets_storage: [None], + sockets: None, + tcp_handle: None, + initialised: false, +}; + +/// Initialise the static NETWORK. +/// +/// Sets up the required EthernetInterface and sockets. +/// +/// Do not call more than once or this function will panic. +pub fn init<'a>(eth_dev: EthernetDevice, mac_addr: EthernetAddress, ip_addr: IpCidr) { + // Unsafe required for access to NETWORK. + // NETWORK.initialised guards against calling twice. + unsafe { + cortex_m::interrupt::free(|_| { + if NETWORK.initialised { + panic!("NETWORK already initialised"); + } + NETWORK.initialised = true; + }); + + let neighbor_cache = NeighborCache::new(&mut NETWORK.neighbor_cache_storage.as_mut()[..]); + + NETWORK.ip_addr = Some([ip_addr]); + NETWORK.eth_iface = Some( + EthernetInterfaceBuilder::new(eth_dev) + .ethernet_addr(mac_addr) + .neighbor_cache(neighbor_cache) + .ip_addrs(&mut NETWORK.ip_addr.as_mut().unwrap()[..]) + .finalize(), + ); + + NETWORK.sockets = Some(SocketSet::new(&mut NETWORK.sockets_storage.as_mut()[..])); + let tcp_rx_buf = TcpSocketBuffer::new(&mut NETWORK_BUFFERS.tcp_rx_buf.as_mut()[..]); + let tcp_tx_buf = TcpSocketBuffer::new(&mut NETWORK_BUFFERS.tcp_tx_buf.as_mut()[..]); + let tcp_socket = TcpSocket::new(tcp_rx_buf, tcp_tx_buf); + NETWORK.tcp_handle = Some(NETWORK.sockets.as_mut().unwrap().add(tcp_socket)); + } +} + +/// Poll network stack. +/// +/// Arrange for this function to be called frequently. +pub fn poll(time_ms: i64) { + // Unsafe required to access static mut NETWORK. + // Since the entire poll is run in an interrupt-free context no + // other access to NETWORK can occur. + cortex_m::interrupt::free(|_| unsafe { + // Bail out early if NETWORK is not initialised. + if !NETWORK.initialised { + return; + } + + let sockets = NETWORK.sockets.as_mut().unwrap(); + + // Handle TCP + { + let mut socket = sockets.get::(NETWORK.tcp_handle.unwrap()); + if !socket.is_open() { + socket.listen(TCP_PORT).unwrap(); + } + if !socket.may_recv() && socket.may_send() { + socket.close(); + } + if socket.can_recv() { + let mut cmd = [0u8; 4]; + socket.recv_slice(&mut cmd[..]).ok(); + let cmd = LittleEndian::read_u32(&cmd[..]); + match cmd { + CMD_INFO => cmd_info(&mut socket), + CMD_READ => cmd_read(&mut socket), + CMD_ERASE => cmd_erase(&mut socket), + CMD_WRITE => cmd_write(&mut socket), + CMD_BOOT => cmd_boot(&mut socket), + _ => (), + }; + socket.close(); + } + } + + // Poll smoltcp + let timestamp = Instant::from_millis(time_ms); + match NETWORK.eth_iface.as_mut().unwrap().poll(sockets, timestamp) { + Ok(_) | Err(smoltcp::Error::Exhausted) => (), + Err(_) => (), + } + }); +} diff --git a/oxcc-bootloader/src/rcc.rs b/oxcc-bootloader/src/rcc.rs new file mode 100644 index 0000000..2a59b07 --- /dev/null +++ b/oxcc-bootloader/src/rcc.rs @@ -0,0 +1,88 @@ +use stm32f7::stm32f7x7; + +/// Set up PLL to 168MHz from 16MHz HSI +pub fn rcc_init(peripherals: &mut stm32f7x7::Peripherals) { + let rcc = &peripherals.RCC; + let flash = &peripherals.FLASH; + let syscfg = &peripherals.SYSCFG; + + // Reset all peripherals + rcc.ahb1rstr.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); + rcc.ahb1rstr.write(|w| unsafe { w.bits(0) }); + rcc.ahb2rstr.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); + rcc.ahb2rstr.write(|w| unsafe { w.bits(0) }); + rcc.ahb3rstr.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); + rcc.ahb3rstr.write(|w| unsafe { w.bits(0) }); + rcc.apb1rstr.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); + rcc.apb1rstr.write(|w| unsafe { w.bits(0) }); + rcc.apb2rstr.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); + rcc.apb2rstr.write(|w| unsafe { w.bits(0) }); + + // Ensure HSI is on and stable + rcc.cr.modify(|_, w| w.hsion().set_bit()); + while rcc.cr.read().hsion().bit_is_clear() {} + + // Set system clock to HSI + rcc.cfgr.modify(|_, w| w.sw().hsi()); + while !rcc.cfgr.read().sws().is_hsi() {} + + // Clear registers to reset value + rcc.cr.write(|w| w.hsion().set_bit()); + rcc.cfgr.write(|w| unsafe { w.bits(0) }); + + // Configure PLL: 16MHz /8 *168 /2, source HSI + rcc.pllcfgr.write(|w| unsafe { + w.pllq() + .bits(4) + .pllsrc() + .hsi() + .pllp() + .div2() + .plln() + .bits(168) + .pllm() + .bits(8) + }); + // Activate PLL + rcc.cr.modify(|_, w| w.pllon().set_bit()); + + // Set other clock domains: PPRE2 to /2, PPRE1 to /4, HPRE to /1 + rcc.cfgr + .modify(|_, w| w.ppre2().div2().ppre1().div4().hpre().div1()); + + // Flash setup: prefetch enabled, 5 wait states (OK for 3.3V + // at 168MHz) + flash.acr.write(|w| w.prften().set_bit().latency().bits(5)); + + // Swap system clock to PLL + rcc.cfgr.modify(|_, w| w.sw().pll()); + while !rcc.cfgr.read().sws().is_pll() {} + + // Set SYSCFG early to RMII mode + rcc.apb2enr.modify(|_, w| w.syscfgen().enabled()); + syscfg.pmc.modify(|_, w| w.mii_rmii_sel().set_bit()); + + // Set up peripheral clocks + rcc.ahb1enr.modify(|_, w| { + w.gpioaen() + .enabled() + .gpioben() + .enabled() + .gpiocen() + .enabled() + .gpioden() + .enabled() + .gpioeen() + .enabled() + .gpiogen() + .enabled() + .crcen() + .enabled() + .ethmacrxen() + .enabled() + .ethmactxen() + .enabled() + .ethmacen() + .enabled() + }); +} diff --git a/oxcc-bootloader/src/systick.rs b/oxcc-bootloader/src/systick.rs new file mode 100644 index 0000000..51b3ffc --- /dev/null +++ b/oxcc-bootloader/src/systick.rs @@ -0,0 +1,11 @@ +use cortex_m; +use stm32f7::stm32f7x7; + +/// Set up the systick to provide a 1ms timebase +pub fn systick_init(syst: &mut stm32f7x7::SYST) { + syst.set_reload((168_000_000 / 8) / 1000); + syst.clear_current(); + syst.set_clock_source(cortex_m::peripheral::syst::SystClkSource::External); + syst.enable_interrupt(); + syst.enable_counter(); +} diff --git a/src/can_protocols/bootloader_can_protocol.rs b/src/can_protocols/bootloader_can_protocol.rs new file mode 100644 index 0000000..23f62a7 --- /dev/null +++ b/src/can_protocols/bootloader_can_protocol.rs @@ -0,0 +1,3 @@ +pub const OSCC_BOOTLOADER_RESET_CAN_ID: u16 = 0xF0; + +pub const OSCC_BOOTLOADER_RESET_CAN_DLC: u8 = 8; diff --git a/src/config.rs b/src/config.rs index 18e8657..d4805c8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,3 +1,4 @@ +use bootloader_can_protocol::*; use brake_can_protocol::*; use fault_can_protocol::*; use nucleo_f767zi::hal::can::{ @@ -77,6 +78,7 @@ pub fn gather_control_can_filters() -> [CanFilterConfig; 3] { f1.filter_id_high = 0; // filter 2 stores the enable control IDs for brake, throttle, and steering + // and the bootloader reset command ID // FIFO_1 let mut f2 = CanFilterConfig::default(); f2.filter_number = 2; @@ -87,7 +89,7 @@ pub fn gather_control_can_filters() -> [CanFilterConfig; 3] { f2.filter_mask_id_low = u32::from(OSCC_BRAKE_ENABLE_CAN_ID << 5); f2.filter_id_low = u32::from(OSCC_THROTTLE_ENABLE_CAN_ID << 5); f2.filter_mask_id_high = u32::from(OSCC_STEERING_ENABLE_CAN_ID << 5); - f2.filter_id_high = 0; + f2.filter_id_high = u32::from(OSCC_BOOTLOADER_RESET_CAN_ID << 5); [f0, f1, f2] } diff --git a/src/fw_update.rs b/src/fw_update.rs new file mode 100644 index 0000000..68be4be --- /dev/null +++ b/src/fw_update.rs @@ -0,0 +1,20 @@ +use bootloader_can_protocol::*; +use nucleo_f767zi::hal::can::CanFrame; +use oxcc_bootloader_lib::reset_to_bootloader; +use oxcc_error::OxccError; + +// TODO - cleanup CAN protocols and namespaces, use/validate data bytes? +pub fn process_rx_frame(can_frame: &CanFrame) -> Result<(), OxccError> { + if let CanFrame::DataFrame(ref frame) = can_frame { + let id: u32 = frame.id().into(); + let dlc = frame.data().len(); + + if id == u32::from(OSCC_BOOTLOADER_RESET_CAN_ID) { + if dlc == usize::from(OSCC_BOOTLOADER_RESET_CAN_DLC) { + reset_to_bootloader(); + } + } + } + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 1aa0696..7dd8b58 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,6 +15,7 @@ extern crate panic_abort; extern crate panic_semihosting; #[macro_use] extern crate typenum; +extern crate oxcc_bootloader_lib; mod board; mod can_gateway_module; @@ -23,12 +24,15 @@ mod dac_mcp4922; mod dtc; mod dual_signal; mod fault_condition; +mod fw_update; mod oxcc_error; mod ranges; mod steering_module; mod throttle_module; mod types; +#[path = "can_protocols/bootloader_can_protocol.rs"] +mod bootloader_can_protocol; #[path = "can_protocols/brake_can_protocol.rs"] mod brake_can_protocol; #[path = "can_protocols/fault_can_protocol.rs"] @@ -118,6 +122,7 @@ fn main() -> ! { writeln!(debug_console, "OxCC is running").unwrap(); // TODO - some of these are worthy of disabling controls? + // NOTE - these are currently cleared when using the fota-bootloader if board.reset_conditions.low_power { writeln!(debug_console, "WARNING: low-power reset detected") .expect(DEBUG_WRITE_FAILURE); @@ -262,6 +267,9 @@ fn process_control_can_frames( modules .steering .process_rx_frame(&rx_frame, debug_console)?; + + // process any firmware-update/bootloader frames + fw_update::process_rx_frame(&rx_frame)?; } Err(e) => { // report all but BufferExhausted (no data)