dc/hbj11: init with flasher
This brings in the flashing infrastructure for HBJ11 (bluepill&web).
Change-Id: I480855689f849c24712d58a0ccbce6e91c34f8bd
diff --git a/dc/hbj11/flasher/bluepill/.cargo/config b/dc/hbj11/flasher/bluepill/.cargo/config
new file mode 100644
index 0000000..128b9c4
--- /dev/null
+++ b/dc/hbj11/flasher/bluepill/.cargo/config
@@ -0,0 +1,3 @@
+[build]
+target = "thumbv7m-none-eabi"
+rustflags = [ "-C", "link-arg=-Tlink.x", "-C", "inline-threshold=255"]
diff --git a/dc/hbj11/flasher/bluepill/.gitignore b/dc/hbj11/flasher/bluepill/.gitignore
new file mode 100644
index 0000000..eb5a316
--- /dev/null
+++ b/dc/hbj11/flasher/bluepill/.gitignore
@@ -0,0 +1 @@
+target
diff --git a/dc/hbj11/flasher/bluepill/Cargo.lock b/dc/hbj11/flasher/bluepill/Cargo.lock
new file mode 100644
index 0000000..ff7488c
--- /dev/null
+++ b/dc/hbj11/flasher/bluepill/Cargo.lock
@@ -0,0 +1,460 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "aligned"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c19796bd8d477f1a9d4ac2465b464a8b1359474f06a96bb3cda650b4fca309bf"
+dependencies = [
+ "as-slice",
+]
+
+[[package]]
+name = "as-slice"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb4d1c23475b74e3672afa8c2be22040b8b7783ad9b461021144ed10a46bb0e6"
+dependencies = [
+ "generic-array 0.12.3",
+ "generic-array 0.13.2",
+ "generic-array 0.14.4",
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
+[[package]]
+name = "bare-metal"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3"
+dependencies = [
+ "rustc_version",
+]
+
+[[package]]
+name = "bitfield"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719"
+
+[[package]]
+name = "byteorder"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b"
+
+[[package]]
+name = "cast"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0"
+dependencies = [
+ "rustc_version",
+]
+
+[[package]]
+name = "cortex-m"
+version = "0.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9075300b07c6a56263b9b582c214d0ff037b00d45ec9fde1cc711490c56f1bb9"
+dependencies = [
+ "aligned",
+ "bare-metal",
+ "bitfield",
+ "cortex-m 0.7.1",
+ "volatile-register",
+]
+
+[[package]]
+name = "cortex-m"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0b756a8bffc56025de45218a48ff9b801180440c0ee49a722b32d49dcebc771"
+dependencies = [
+ "bare-metal",
+ "bitfield",
+ "embedded-hal",
+ "volatile-register",
+]
+
+[[package]]
+name = "cortex-m-rt"
+version = "0.6.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "980c9d0233a909f355ed297ef122f257942de5e0a2cb1c39f60684b65bcb90fb"
+dependencies = [
+ "cortex-m-rt-macros",
+ "r0",
+]
+
+[[package]]
+name = "cortex-m-rt-macros"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4717562afbba06e760d34451919f5c3bf3ac15c7bb897e8b04862a7428378647"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "cortex-m-rtic"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b30efcb6b7920d9016182c485687f0012487032a14c415d2fce6e9862ef8260e"
+dependencies = [
+ "cortex-m 0.6.7",
+ "cortex-m-rt",
+ "cortex-m-rtic-macros",
+ "heapless",
+ "rtic-core",
+ "version_check",
+]
+
+[[package]]
+name = "cortex-m-rtic-macros"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a1a6a4c9550373038c0e21a78d44d529bd697c25bbf6b8004bddc6e63b119c7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "rtic-syntax",
+ "syn",
+]
+
+[[package]]
+name = "cortex-m-semihosting"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6bffa6c1454368a6aa4811ae60964c38e6996d397ff8095a8b9211b1c1f749bc"
+dependencies = [
+ "cortex-m 0.7.1",
+]
+
+[[package]]
+name = "embedded-hal"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa998ce59ec9765d15216393af37a58961ddcefb14c753b4816ba2191d865fcb"
+dependencies = [
+ "nb 0.1.3",
+ "void",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec"
+dependencies = [
+ "typenum",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ed1e761351b56f54eb9dcd0cfaca9fd0daecf93918e1cfc01c8a3d26ee7adcd"
+dependencies = [
+ "typenum",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "hash32"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4041af86e63ac4298ce40e5cca669066e75b6f1aa3390fe2561ffa5e1d9f4cc"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
+
+[[package]]
+name = "hbj11-flasher-bluepill"
+version = "0.1.0"
+dependencies = [
+ "cortex-m 0.6.7",
+ "cortex-m-rt",
+ "cortex-m-rtic",
+ "cortex-m-semihosting",
+ "embedded-hal",
+ "nb 0.1.3",
+ "num-derive",
+ "num-traits",
+ "panic-halt",
+ "panic-semihosting",
+ "stm32f1xx-hal",
+ "usb-device",
+ "usbd-webusb",
+]
+
+[[package]]
+name = "heapless"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74911a68a1658cfcfb61bc0ccfbd536e3b6e906f8c2f7883ee50157e3e2184f1"
+dependencies = [
+ "as-slice",
+ "generic-array 0.13.2",
+ "hash32",
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fb1fa934250de4de8aef298d81c729a7d33d8c239daa3a7575e6b92bfc7313b"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "nb"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f"
+dependencies = [
+ "nb 1.0.0",
+]
+
+[[package]]
+name = "nb"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "546c37ac5d9e56f55e73b677106873d9d9f5190605e41a856503623648488cae"
+
+[[package]]
+name = "num-derive"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "panic-halt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de96540e0ebde571dc55c73d60ef407c653844e6f9a1e2fdbd40c07b9252d812"
+
+[[package]]
+name = "panic-semihosting"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d55dedd501dfd02514646e0af4d7016ce36bc12ae177ef52056989966a1eec"
+dependencies = [
+ "cortex-m 0.7.1",
+ "cortex-m-semihosting",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "r0"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2a38df5b15c8d5c7e8654189744d8e396bddc18ad48041a500ce52d6948941f"
+
+[[package]]
+name = "rtic-core"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8bd58a6949de8ff797a346a28d9f13f7b8f54fa61bb5e3cb0985a4efb497a5ef"
+
+[[package]]
+name = "rtic-syntax"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8152fcaa845720d61e6cc570548b89144c2c307f18a480bbd97e55e9f6eeff04"
+dependencies = [
+ "indexmap",
+ "proc-macro2",
+ "syn",
+]
+
+[[package]]
+name = "rustc_version"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "semver"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
+dependencies = [
+ "semver-parser",
+]
+
+[[package]]
+name = "semver-parser"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
+name = "stm32-usbd"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70d13eca735cae37df697f599777b000cc0ee924df8452f2b4bfaa6798ab0338"
+dependencies = [
+ "cortex-m 0.6.7",
+ "usb-device",
+ "vcell",
+]
+
+[[package]]
+name = "stm32f1"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "849b1e8d9bcfd792c9d9178cf86165d299a661c26e35d9322ae9382d3f3fe460"
+dependencies = [
+ "bare-metal",
+ "cortex-m 0.6.7",
+ "cortex-m-rt",
+ "vcell",
+]
+
+[[package]]
+name = "stm32f1xx-hal"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af9b9e5d7c2901ee39fc9527412327a1fe08f1d84e9d7f4b3497448e655e5098"
+dependencies = [
+ "as-slice",
+ "cast",
+ "cortex-m 0.6.7",
+ "cortex-m-rt",
+ "embedded-hal",
+ "nb 0.1.3",
+ "stm32-usbd",
+ "stm32f1",
+ "void",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "typenum"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
+
+[[package]]
+name = "usb-device"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "849eed9b4dc61a1f17ba1d7a5078ceb095b9410caa38a506eb281ed5eff12fbd"
+
+[[package]]
+name = "usbd-webusb"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed33ecaa7a26365f13059e753bfa23f0a4a557565499f46d255c51e737464bd8"
+dependencies = [
+ "usb-device",
+]
+
+[[package]]
+name = "vcell"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002"
+
+[[package]]
+name = "version_check"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
+
+[[package]]
+name = "void"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
+
+[[package]]
+name = "volatile-register"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d67cb4616d99b940db1d6bd28844ff97108b498a6ca850e5b6191a532063286"
+dependencies = [
+ "vcell",
+]
diff --git a/dc/hbj11/flasher/bluepill/Cargo.toml b/dc/hbj11/flasher/bluepill/Cargo.toml
new file mode 100644
index 0000000..efa7892
--- /dev/null
+++ b/dc/hbj11/flasher/bluepill/Cargo.toml
@@ -0,0 +1,34 @@
+[package]
+name = "hbj11-flasher-bluepill"
+version = "0.1.0"
+authors = ["Serge Bazanski <q3k@hackerspace.pl>"]
+edition = "2018"
+
+[profile.dev]
+opt-level = 3
+
+[profile.release]
+opt-level = 'z'
+lto = true
+
+[dependencies]
+cortex-m = "^0.6.3"
+cortex-m-rtic = "^0.5.5"
+cortex-m-rt = "^0.6.12"
+cortex-m-semihosting = "^0.3.7"
+embedded-hal = "^0.2.4"
+panic-halt = "^0.2.0"
+usb-device = "^0.2.7"
+usbd-webusb = "^1.0.2"
+panic-semihosting = "^0.5.0"
+nb = "^0.1.3"
+num-derive = "0.3"
+
+[dependencies.num-traits]
+default-features = false
+features = []
+version = "0.2"
+
+[dependencies.stm32f1xx-hal]
+features = ["stm32f103", "rt", "medium", "stm32-usbd"]
+version = "^0.6.1"
diff --git a/dc/hbj11/flasher/bluepill/README.md b/dc/hbj11/flasher/bluepill/README.md
new file mode 100644
index 0000000..67af491
--- /dev/null
+++ b/dc/hbj11/flasher/bluepill/README.md
@@ -0,0 +1,79 @@
+STM32 Bluepill-based I2C Flasher
+================================
+
+
+[TOC]
+
+![](../../doc/bluepill.jpg)
+
+This is a Rust project that runs on an STM32F103C8T6 on a common [bluepill](https://stm32-base.org/boards/STM32F103C8T6-Blue-Pill.html) board.
+
+It acts as a USB device, exposing an interface to perform arbitrary I2C operations.
+
+Hardware
+--------
+
+You will need a buepill with an STM32103C8T6 and a way to flash ELFs on it. An ST-Link or BlackMagicProbe (potentially running on another Bluepill) are good choices.
+
+For flashing the HBJ11 (or any other Dell M610 storage card) you will also need a PCIe x8 socket. The connections to make are as follows:
+
+| Bluepill/STM32 | Function | PCIe Slot |
+| -------------- | -------- | --------- |
+| G/GND | Ground | B7 |
+| 3.3/VCC | 3.3V | B10 |
+| B6 | SCL | B11 |
+| B7 | SDA | B12 |
+
+Note: the PCIe slot pin numbering follows the same convention as Dell parts (they have A1-A49/B1-B49 markers) and as [the Wikipedia article on PCIe](https://en.wikipedia.org/wiki/PCI_Express#Pinout).
+
+Note: you will need to add pull up resistors for SCL and SDA. 4k7 is a good value to start with. Use a scope to make sure the open drain/pullup behaviour looks sensible.
+
+Note: we run the I2C bus and EEPROM at 3.3V, even though it runs at 5V while in a server. This is fine for HBJ11 flashing, but might lead to issues when attempting to read/program Dell parts, like CERC6/i or the JM475.
+
+Firmware
+--------
+
+To build the firmware, you will need Rust with the thumbv7m-none-eabi target. We unfortunately don't have Bazel integration yet, as rules\_rust don't integrate fully with Bazel's toolchain/configurability system. This should be revisited at some point.
+
+To get Rust with the right target, rustup is recommended (Nix users: `nix-shell -p rustup`):
+
+ $ rustup update
+ $ rustup default stable
+ $ rustup target add thumbv7m-none-eabi
+
+Then, to build:
+
+ $ cargo build --release
+ $ file target/thumbv7m-none-eabi/release/hbj11-flasher-bluepill
+ target/thumbv7m-none-eabi/release/hbj11-flasher-bluepill: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, with debug_info, not stripped
+
+Debug builds are also available, but they require a semihosting debugger attached - otherwise, they will immediately get stuck trying to log debug messages to the host.
+
+Flashing Firmware
+-----------------
+
+If using a BlackMagicProbe:
+
+ $ arm-none-eabi-gdb -x flash.gdb target/thumbv7m-none-eabi/release/hbj11-flasher-bluepill
+ [...]
+ Loading section .vector_table, size 0x130 lma 0x8000000
+ Loading section .text, size 0x3820 lma 0x8000130
+ Loading section .rodata, size 0xd88 lma 0x8003950
+ Start address 0x08000130, load size 18136
+ Transfer rate: 15 KB/sec, 906 bytes/write.
+
+You can then C-c C-d and let the device run, or keep running it under the debugger. It should enumerate via USB:
+
+ $ lsusb -v | grep -A 4 0x16c0
+ idVendor 0x16c0 Van Ooijen Technische Informatica
+ idProduct 0x27d8 libusb-bound devices
+ bcdDevice 0.10
+ iManufacturer 1 Warsaw Hackerspace
+ iProduct 2 Web I2C Programmer
+
+We currently use an, uh, _community_ VID/PID. This will change in the future as we apply for a pair from pid.codes or elsewhere.
+
+Usage
+-----
+
+The flasher is controller via [WebI2C](../web/) (through WebUSB).
diff --git a/dc/hbj11/flasher/bluepill/flash.gdb b/dc/hbj11/flasher/bluepill/flash.gdb
new file mode 100644
index 0000000..e447bfc
--- /dev/null
+++ b/dc/hbj11/flasher/bluepill/flash.gdb
@@ -0,0 +1,7 @@
+# Flash script for BlackMagicProbe
+target extended-remote /dev/ttyACM0
+monitor swdp_scan
+attach 1
+load
+# Attach to the running process. C-c and C-d to detach.
+run
diff --git a/dc/hbj11/flasher/bluepill/memory.x b/dc/hbj11/flasher/bluepill/memory.x
new file mode 100644
index 0000000..d152927
--- /dev/null
+++ b/dc/hbj11/flasher/bluepill/memory.x
@@ -0,0 +1,7 @@
+MEMORY
+{
+ /* Flash memory begins at 0x80000000 and has a size of 64kB*/
+ FLASH : ORIGIN = 0x08000000, LENGTH = 64K
+ /* RAM begins at 0x20000000 and has a size of 20kB*/
+ RAM : ORIGIN = 0x20000000, LENGTH = 20K
+}
diff --git a/dc/hbj11/flasher/bluepill/src/i2c.rs b/dc/hbj11/flasher/bluepill/src/i2c.rs
new file mode 100644
index 0000000..35f52d6
--- /dev/null
+++ b/dc/hbj11/flasher/bluepill/src/i2c.rs
@@ -0,0 +1,372 @@
+/// USB Device Class for I2C transactions.
+//
+// It's not very good, and the API is weird. Someone with more USB device design experience could
+// easily come up with something better.
+//
+// Control OUT transactions are used to perform I2C transfers to/from an internal buffer.
+// Bulk IN/OUT transactions are used to transfer contents of the buffer to the host. It has not
+// been optimized for speed or pipelining.
+//
+// To perform an I2C read:
+// 1) Control OUT: ReadI2C(Address: 0xAA, Length: N)
+// (0xAA is the device address, N is the amount of bytes to read. Cannot be larger than
+// BUFFER_SIZE).
+// This performs an I2C read of N bytes into the inne buffer of the device, starting at
+// address 0.
+// 2) Control IN: GetStatus()
+// The host ensures that the transaction was either ACK or NACK by getting one byte of status
+// from the device.
+// 3) Control OUT: ReadBuffer(Address: X, Length: N)
+// (X is the address within the buffer, N is the amount of bytes to transfer to the host. N
+// cannot be larger than PACKET_SIZE).
+// 4) Bulk IN: Read PACKET_SIZE bytes.
+// Steps 3/4 can be skipped for scanning (the device won't mind the inner buffer not being read).
+//
+// To perform an I2C write:
+// 1) Control OUT: SetWritePointer(Addrss: X)
+// 2) Bulk OUT: Write at most PACKET_SIZE bytes.
+// Repeat steps 1/2 to fill buffer with an I2c transaction.
+// 3) Control OUT: WriteI2C(Address: 0x00, Length: N)
+// (0xAA is the device address, N is the amount of bytes to write. Cannot be larger than
+// BUFFER_SIZE).
+// 4) Control IN: GetStatus()
+// The host ensures that the transaction was either ACK or NACK by getting one byte of status
+// from the device.
+
+use embedded_hal::digital::v2::OutputPin;
+use usb_device::class_prelude::*;
+use nb::Error as NbError;
+use stm32f1xx_hal::{
+ gpio::{gpiob::*, Alternate, OpenDrain},
+ i2c::{BlockingI2c, Error as I2CError},
+ pac::I2C1,
+ prelude::*,
+};
+
+use num_derive::FromPrimitive;
+use num_traits::FromPrimitive;
+
+use crate::{hprint, hprintln};
+
+// Size of buffer within class, in bytes. Dictates maximum I2C transaction size.
+const BUFFER_SIZE: usize = 1024;
+// Size of bulk packets.
+const PACKET_SIZE: usize = 64;
+
+// All IN/OUT references bellow conform to typical USB naming, where IN: from device to host; OUT:
+// from host to device.
+
+/// Request number passed within Control IN requests to the I2C interface (ie. 'gets' from device).
+#[derive(FromPrimitive)]
+#[repr(u8)]
+enum ControlInRequest {
+ /// Write the current status as a single byte in response.
+ GetStatus = 1,
+}
+
+/// Request number passed within Control OUT requests to the I2C interface (ie. 'sets' from the
+/// host).
+#[derive(FromPrimitive)]
+#[repr(u8)]
+enum ControlOutRequest {
+ /// Set LED on or off (value == 0 -> off; on otherwise).
+ SetLED = 1,
+
+ /// Perform I2C bus read of a given length from a given I2C address.
+ /// I2C Address: lower 8 bits of value.
+ /// Read Length: upper 8 bits of value.
+ ReadI2C = 2,
+
+ /// Schedule a BULK IN transaction on the USB bus with the contents of the inner buffer.
+ /// Buffer start address: lower 8 bits of value
+ /// Read Length: upper 8 bits of value.
+ ReadBuffer = 3,
+
+ /// Perform I2C bus write of a given length to a given I2C address.
+ /// I2C Address: lower 8 bits of value.
+ /// Read Length: upper 8 bits of value.
+ WriteI2C = 4,
+
+ /// Set inner buffer write pointer. Any subsequent BULK OUT will write to the buffer at that
+ /// address (but will not auto advance the pointer).
+ SetWritePointer = 5,
+}
+
+/// Status of the I2C class. Combines information about requested transactions and I2C bus
+/// responses.
+#[derive(Copy, Clone)]
+#[repr(u8)]
+enum Status {
+ /// Last request okay.
+ OK = 0,
+ /// Last request contained an invalid argument.
+ InvalidArgument = 1,
+ /// Last request okay, resulted in a successful I2C transaction.
+ Ack = 2,
+ /// Last request okay, resulted in a NACKd I2C transaction.
+ Nack = 3,
+ /// Last request okay, resulted in a fully failed I2C transaction.
+ BusError = 4,
+}
+
+pub struct I2CClass<'a, B: UsbBus, LED> {
+ interface: InterfaceNumber,
+ /// Bulk IN endpoint for buffer transfers to host.
+ ep_in: EndpointIn<'a, B>,
+ /// Bulk OUT endpoint for buffer transfers from host.
+ ep_out: EndpointOut<'a, B>,
+
+ /// LED used for debugging.
+ led: LED,
+
+ /// The underlying I2C device.
+ i2c_dev: BlockingI2c<I2C1, (PB6<Alternate<OpenDrain>>, PB7<Alternate<OpenDrain>>)>,
+
+ /// Marker that is true when the host requested a BULK OUT via ReadBuffer.
+ expect_bulk_out: bool,
+
+ /// The underlying buffer and its write pointer.
+ buffer: [u8; BUFFER_SIZE],
+ write_pointer: usize,
+
+ /// The device's main status byte, used by host to check whether operations were succesful.
+ status: Status,
+}
+
+impl<B: UsbBus, LED: OutputPin> I2CClass<'_, B, LED> {
+ pub fn new(
+ alloc: &UsbBusAllocator<B>,
+ led: LED,
+ i2c_dev: BlockingI2c<I2C1, (PB6<Alternate<OpenDrain>>, PB7<Alternate<OpenDrain>>)>,
+ ) -> I2CClass<'_, B, LED> {
+ I2CClass {
+ interface: alloc.interface(),
+ ep_in: alloc.bulk(PACKET_SIZE as u16),
+ ep_out: alloc.bulk(PACKET_SIZE as u16),
+ led, i2c_dev,
+
+ expect_bulk_out: false,
+
+ buffer: [0; BUFFER_SIZE],
+ write_pointer: 0usize,
+ status: Status::OK,
+ }
+ }
+}
+
+impl<'a, B: UsbBus, LED: OutputPin> UsbClass<B> for I2CClass<'a, B, LED> {
+ fn reset(&mut self) {
+ self.expect_bulk_out = false;
+ self.status = Status::OK,
+ }
+
+ fn control_in(&mut self, xfer: ControlIn<B>) {
+ let req = xfer.request();
+
+ if req.request_type != control::RequestType::Vendor
+ || req.recipient != control::Recipient::Interface
+ || req.index != u8::from(self.interface) as u16 {
+ return
+ }
+
+ match FromPrimitive::from_u8(req.request) {
+ /// Serve GetStatus: return this.status.
+ Some(ControlInRequest::GetStatus) => {
+ let status = self.status.clone() as u8;
+ xfer.accept(|buf| {
+ buf[0] = status;
+ Ok(1usize)
+ }).ok();
+ },
+ _ => {
+ hprintln!("Unhandled control in on iface: {:?}", req).unwrap();
+ },
+ }
+ }
+
+ fn control_out(&mut self, xfer: ControlOut<B>) {
+ let req = xfer.request();
+
+ if req.request_type != control::RequestType::Vendor
+ || req.recipient != control::Recipient::Interface
+ || req.index != u8::from(self.interface) as u16 {
+ return
+ }
+
+ match FromPrimitive::from_u8(req.request) {
+ // Serve SetLED.
+ Some(ControlOutRequest::SetLED) => {
+ let on: bool = req.value > 0;
+ match on {
+ true => self.led.set_low(),
+ false => self.led.set_high(),
+ }.ok();
+ xfer.accept().ok();
+ },
+
+ // Serve ReadI2C: read len bytes from I2C addr into internal buffer.
+ Some(ControlOutRequest::ReadI2C) => {
+ let addr: u8 = (req.value & 0xff) as u8;
+ let len: u8 = (req.value >> 8) as u8;
+ if len as usize > BUFFER_SIZE || len < 1u8 {
+ self.status = Status::InvalidArgument;
+ xfer.accept().ok();
+ return
+ }
+ if addr > 127u8 {
+ self.status = Status::InvalidArgument;
+ xfer.accept().ok();
+ return
+ }
+ match self.i2c_dev.read(addr, &mut self.buffer[0usize..(len as usize)]) {
+ Ok(_) => {
+ self.status = Status::Ack;
+ },
+ Err(NbError::Other(I2CError::Acknowledge)) => {
+ self.status = Status::Nack;
+ },
+ Err(e) => {
+ hprintln!("When reading I2C (addr {}, {} bytes): {:?}", addr, len, e).ok();
+ self.status = Status::BusError;
+ },
+ }
+ xfer.accept().ok();
+ },
+
+ // Serve ReadBuffer: send BULK IN with slice of buffer.
+ Some(ControlOutRequest::ReadBuffer) => {
+ let addr: u8 = (req.value & 0xff) as u8;
+ let len: u8 = (req.value >> 8) as u8;
+
+ if len as usize > PACKET_SIZE || len < 1u8 {
+ self.status = Status::InvalidArgument;
+ xfer.accept().ok();
+ return
+ }
+
+ let start = addr as usize;
+ let end = (addr + len) as usize;
+ if end as usize > BUFFER_SIZE {
+ self.status = Status::InvalidArgument;
+ xfer.accept().ok();
+ return
+ }
+
+ hprintln!("READ BUFFER, addr: {}, len: {}", addr, len).ok();
+
+ self.status = Status::OK;
+ xfer.accept().ok();
+ match self.ep_in.write(&self.buffer[start..end]) {
+ Ok(count) => {
+ },
+ Err(UsbError::WouldBlock) => {},
+ Err(err) => {
+ hprintln!("bulk write failed: {:?}", err).ok();
+ },
+ }
+ },
+
+ // Serve WriteI2C: write len bytes to I2C bus at addr from internal buffer.
+ Some(ControlOutRequest::WriteI2C) => {
+ let addr: u8 = (req.value & 0xff) as u8;
+ let len: u8 = (req.value >> 8) as u8;
+ if len as usize > BUFFER_SIZE || len < 1u8 {
+ self.status = Status::InvalidArgument;
+ xfer.accept().ok();
+ return
+ }
+ if addr > 127u8 {
+ self.status = Status::InvalidArgument;
+ xfer.accept().ok();
+ return
+ }
+
+ hprintln!("WRITE I2C, addr: {}, len: {}", addr, len).ok();
+ match self.i2c_dev.write(addr, &self.buffer[0usize..(len as usize)]) {
+ Ok(_) => {
+ self.status = Status::Ack;
+ },
+ Err(NbError::Other(I2CError::Acknowledge)) => {
+ self.status = Status::Nack;
+ },
+ Err(e) => {
+ hprintln!("When writing I2C (addr {}, {} bytes): {:?}", addr, len, e).ok();
+ self.status = Status::BusError;
+ },
+ }
+ xfer.accept().ok();
+ },
+
+ // Serve SetWritePointer: set start address at which bytes from a BULK OUT will be
+ // written to. The write pointer does _not_ increment on every write, so will need to
+ // be manually controler after every BULK transfer.
+ Some(ControlOutRequest::SetWritePointer) => {
+ let pointer = req.value;
+ if (pointer as usize) >= BUFFER_SIZE {
+ self.status = Status::InvalidArgument;
+ xfer.accept().ok();
+ return
+ }
+ hprintln!("SET WRITE PTR, pointer: {}", pointer).ok();
+ self.write_pointer = pointer as usize;
+ self.status = Status::OK;
+ xfer.accept().ok();
+ },
+ _ => {
+ hprintln!("Unhandled control out on iface: {:?}", req).ok();
+ },
+ }
+ }
+
+ fn get_configuration_descriptors(
+ &self,
+ writer: &mut DescriptorWriter,
+ ) -> usb_device::Result<()> {
+ writer.interface(
+ self.interface,
+ 0xff,
+ 21, 37,
+ )?;
+ writer.endpoint(&self.ep_in)?;
+ writer.endpoint(&self.ep_out)?;
+
+ Ok(())
+ }
+
+ fn poll(&mut self) {
+ let mut temp_buf = [0; PACKET_SIZE];
+ // Serve BULK OUT writes - copy bytes into internal buffer.
+ match self.ep_out.read(&mut temp_buf) {
+ Ok(count) => {
+ if self.expect_bulk_out {
+ self.expect_bulk_out = false;
+ } else {
+ panic!("unexpectedly read data from bulk out endpoint");
+ }
+ hprintln!("SET BUFFER: ptr {}, {} bytes", self.write_pointer, count).ok();
+ for (i, c) in temp_buf.iter().enumerate() {
+ let ptr = self.write_pointer + i;
+ // Silently drop bytes that do not fit in buffer.
+ if ptr >= BUFFER_SIZE {
+ continue;
+ }
+ self.buffer[ptr] = c.clone();
+ }
+ },
+ Err(UsbError::WouldBlock) => {},
+ Err(err) => panic!("bulk read {:?}", err),
+ }
+ }
+
+ fn endpoint_out(&mut self, addr: EndpointAddress) {
+ if addr == self.ep_out.address() {
+ self.expect_bulk_out = true;
+ }
+ }
+
+ fn endpoint_in_complete(&mut self, addr: EndpointAddress) {
+ if addr == self.ep_in.address() {
+ // TODO(q3k): should we be doing something here?
+ }
+ }
+}
diff --git a/dc/hbj11/flasher/bluepill/src/main.rs b/dc/hbj11/flasher/bluepill/src/main.rs
new file mode 100644
index 0000000..da6cefd
--- /dev/null
+++ b/dc/hbj11/flasher/bluepill/src/main.rs
@@ -0,0 +1,136 @@
+#![no_main]
+#![no_std]
+
+extern crate panic_semihosting;
+
+use rtic::app;
+
+use cortex_m::asm::delay;
+use stm32f1xx_hal::{
+ gpio::{gpioc::*, Output, PushPull},
+ i2c::{BlockingI2c, Mode},
+ pac::{Peripherals},
+ prelude::*,
+ usb::{Peripheral, UsbBus, UsbBusType},
+};
+use embedded_hal::digital::v2::OutputPin;
+
+use usb_device::bus;
+use usb_device::prelude::*;
+
+use usbd_webusb::WebUsb;
+
+mod i2c;
+mod print;
+
+/// The main RTIC application object. See RTIC documentation for more information about how to read
+/// this.
+#[app(device = stm32f1xx_hal::stm32, peripherals = true)]
+const APP: () = {
+ struct Resources {
+ usb_dev: UsbDevice<'static, UsbBusType>,
+ webusb: WebUsb<UsbBusType>,
+ // The I2C USB device class that performs the main logic of accessing the I2C bus over USB
+ // for users of the device.
+ i2c: i2c::I2CClass<'static, UsbBusType, PC13<Output<PushPull>>>,
+ }
+
+ /// Idle loop to prevent WFI which in turn prevents debugging.
+ // TODO: make this only happen on debug builds?
+ #[idle]
+ fn idle(_: idle::Context) -> ! {
+ loop {}
+ }
+
+ #[init]
+ fn init(cx: init::Context) -> init::LateResources {
+ static mut USB_BUS: Option<bus::UsbBusAllocator<UsbBusType>> = None;
+
+ let mut flash = cx.device.FLASH.constrain();
+ let mut rcc = cx.device.RCC.constrain();
+
+ let clocks = rcc
+ .cfgr
+ .use_hse(8.mhz())
+ .sysclk(48.mhz())
+ .pclk1(24.mhz())
+ .freeze(&mut flash.acr);
+
+ assert!(clocks.usbclk_valid());
+
+ let mut gpioa = cx.device.GPIOA.split(&mut rcc.apb2);
+ let mut gpiob = cx.device.GPIOB.split(&mut rcc.apb2);
+ let mut gpioc = cx.device.GPIOC.split(&mut rcc.apb2);
+
+ // Active-low LED on bluepill board.
+ let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh);
+ led.set_high().ok();
+
+ let mut afio = cx.device.AFIO.constrain(&mut rcc.apb2);
+
+ // BluePill board has a pull-up resistor on the D+ line.
+ // Pull the D+ pin down to send a RESET condition to the USB bus.
+ // This forced reset is needed only for development, without it host
+ // will not reset your device when you upload new firmware.
+ let mut usb_dp = gpioa.pa12.into_push_pull_output(&mut gpioa.crh);
+ usb_dp.set_low().unwrap();
+ delay(clocks.sysclk().0 / 100);
+
+ let usb_dm = gpioa.pa11;
+ let usb_dp = usb_dp.into_floating_input(&mut gpioa.crh);
+
+ let usb = Peripheral {
+ usb: cx.device.USB,
+ pin_dm: usb_dm,
+ pin_dp: usb_dp,
+ };
+
+ *USB_BUS = Some(UsbBus::new(usb));
+
+ let i2c_pins = (
+ gpiob.pb6.into_alternate_open_drain(&mut gpiob.crl),
+ gpiob.pb7.into_alternate_open_drain(&mut gpiob.crl),
+ );
+
+ // Blocking I2C peripheral for use by the I2C app.
+ let i2c_dev = BlockingI2c::i2c1(
+ cx.device.I2C1,
+ i2c_pins,
+ &mut afio.mapr,
+ Mode::standard(100.khz()),
+ clocks,
+ &mut rcc.apb1,
+ 1000, 10, 1000, 1000,
+ );
+
+ // I2C app.
+ let i2c = i2c::I2CClass::new(
+ USB_BUS.as_ref().unwrap(),
+ led, i2c_dev,
+ );
+
+ let usb_dev = UsbDeviceBuilder::new(USB_BUS.as_ref().unwrap(), UsbVidPid(0x16c0, 0x27d8))
+ .manufacturer("Warsaw Hackerspace")
+ .product("Web I2C Programmer")
+ // TODO(q3k): generate serial at build time?
+ .serial_number("2137")
+ .build();
+
+ init::LateResources {
+ usb_dev, i2c,
+ webusb: WebUsb::new(
+ USB_BUS.as_ref().unwrap(),
+ usbd_webusb::url_scheme::HTTPS,
+ "hackdoc.hackerspace.pl/dc/hbj11/flasher",
+ ),
+ }
+ }
+
+ #[task(binds = USB_LP_CAN_RX0, resources = [usb_dev, webusb, i2c])]
+ fn usb_lp(cx: usb_lp::Context) {
+ cx.resources
+ .usb_dev
+ .poll(&mut [cx.resources.webusb, cx.resources.i2c]);
+ }
+};
+
diff --git a/dc/hbj11/flasher/bluepill/src/print.rs b/dc/hbj11/flasher/bluepill/src/print.rs
new file mode 100644
index 0000000..3693b00
--- /dev/null
+++ b/dc/hbj11/flasher/bluepill/src/print.rs
@@ -0,0 +1,40 @@
+// Wrappers around hprint(ln) that get disabled during release builds. This prevents us from
+// getting stuck in an hprint when a debugger is detached.
+
+#[cfg(debug_assertions)]
+#[macro_export]
+macro_rules! hprint {
+ ($s:expr) => {
+ cortex_m_semihosting::export::hstdout_str($s)
+ };
+ ($($tt:tt)*) => {
+ cortex_m_semihosting::export::hstdout_fmt(format_args!($($tt)*))
+ };
+}
+#[cfg(debug_assertions)]
+#[macro_export]
+macro_rules! hprintln {
+ () => {
+ cortex_m_semihosting::export::hstdout_str("\n")
+ };
+ ($s:expr) => {
+ cortex_m_semihosting::export::hstdout_str(concat!($s, "\n"))
+ };
+ ($s:expr, $($tt:tt)*) => {
+ cortex_m_semihosting::export::hstdout_fmt(format_args!(concat!($s, "\n"), $($tt)*))
+ };
+}
+
+#[cfg(not(debug_assertions))]
+#[macro_export]
+macro_rules! hprint {
+ () => { Result::<(), ()>::Ok(()) };
+ ($s:expr, $($tt:tt)*) => { Result::<(), ()>::Ok(()) };
+}
+#[cfg(not(debug_assertions))]
+#[macro_export]
+macro_rules! hprintln {
+ () => { Result::<(), ()>::Ok(()) };
+ ($s:expr) => { Result::<(), ()>::Ok(()) };
+ ($s:expr, $($tt:tt)*) => { Result::<(), ()>::Ok(()) };
+}