mirror of
https://gerrit.hackerspace.pl/hscloud
synced 2025-03-18 15:54:52 +00:00
Merge "dc/hbj11: init with flasher"
This commit is contained in:
commit
62ba93eae1
20 changed files with 2250 additions and 0 deletions
16
dc/hbj11/README.md
Normal file
16
dc/hbj11/README.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
hbj11 - the Hackerspace Blade JBOD adapter
|
||||
===
|
||||
|
||||
Replacement cards for M610 blades at the hackerspace.
|
||||
|
||||

|
||||
|
||||
Hardware
|
||||
--------
|
||||
|
||||
To be published.
|
||||
|
||||
EEPROM & flasher
|
||||
----------------
|
||||
|
||||
See [flasher](flasher) for a WebUSB/STM32 based flasher for the cards.
|
BIN
dc/hbj11/doc/bluepill.jpg
Normal file
BIN
dc/hbj11/doc/bluepill.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 376 KiB |
BIN
dc/hbj11/doc/hbj11-a0-photo.jpg
Normal file
BIN
dc/hbj11/doc/hbj11-a0-photo.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 317 KiB |
BIN
dc/hbj11/doc/webi2c.png
Normal file
BIN
dc/hbj11/doc/webi2c.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 113 KiB |
20
dc/hbj11/flasher/README.md
Normal file
20
dc/hbj11/flasher/README.md
Normal file
|
@ -0,0 +1,20 @@
|
|||
HBJ11 Flasher
|
||||
=============
|
||||
|
||||
This is a EEPROM flashing tool for the HBJ11 cards designed at the Warsaw Hackerspace.
|
||||
|
||||
It's made up of two parts:
|
||||
|
||||
The Device (Bluepill)
|
||||
---------------------
|
||||
|
||||
An USB/I2C adapter based on an STM32 Bluepill devboard. See [bluepill](bluepill/) for more information. You will need one physically plugged into your machine and wired up to a PCIe socket to insert the HBJ11s into.
|
||||
|
||||

|
||||
|
||||
The Web Interface (WebI2C)
|
||||
---------------------------
|
||||
|
||||
A WebUSB-based flashing tool that will run under any Chromium-based browser (eg. Chrome, Edge. See [web](web/) for more information and a link to a publicly available instance.
|
||||
|
||||

|
3
dc/hbj11/flasher/bluepill/.cargo/config
Normal file
3
dc/hbj11/flasher/bluepill/.cargo/config
Normal file
|
@ -0,0 +1,3 @@
|
|||
[build]
|
||||
target = "thumbv7m-none-eabi"
|
||||
rustflags = [ "-C", "link-arg=-Tlink.x", "-C", "inline-threshold=255"]
|
1
dc/hbj11/flasher/bluepill/.gitignore
vendored
Normal file
1
dc/hbj11/flasher/bluepill/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
target
|
460
dc/hbj11/flasher/bluepill/Cargo.lock
generated
Normal file
460
dc/hbj11/flasher/bluepill/Cargo.lock
generated
Normal file
|
@ -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",
|
||||
]
|
34
dc/hbj11/flasher/bluepill/Cargo.toml
Normal file
34
dc/hbj11/flasher/bluepill/Cargo.toml
Normal file
|
@ -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"
|
79
dc/hbj11/flasher/bluepill/README.md
Normal file
79
dc/hbj11/flasher/bluepill/README.md
Normal file
|
@ -0,0 +1,79 @@
|
|||
STM32 Bluepill-based I2C Flasher
|
||||
================================
|
||||
|
||||
|
||||
[TOC]
|
||||
|
||||

|
||||
|
||||
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).
|
7
dc/hbj11/flasher/bluepill/flash.gdb
Normal file
7
dc/hbj11/flasher/bluepill/flash.gdb
Normal file
|
@ -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
|
7
dc/hbj11/flasher/bluepill/memory.x
Normal file
7
dc/hbj11/flasher/bluepill/memory.x
Normal file
|
@ -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
|
||||
}
|
372
dc/hbj11/flasher/bluepill/src/i2c.rs
Normal file
372
dc/hbj11/flasher/bluepill/src/i2c.rs
Normal file
|
@ -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?
|
||||
}
|
||||
}
|
||||
}
|
136
dc/hbj11/flasher/bluepill/src/main.rs
Normal file
136
dc/hbj11/flasher/bluepill/src/main.rs
Normal file
|
@ -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]);
|
||||
}
|
||||
};
|
||||
|
40
dc/hbj11/flasher/bluepill/src/print.rs
Normal file
40
dc/hbj11/flasher/bluepill/src/print.rs
Normal file
|
@ -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(()) };
|
||||
}
|
18
dc/hbj11/flasher/web/README.md
Normal file
18
dc/hbj11/flasher/web/README.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
WebI2C
|
||||
======
|
||||
|
||||
A WebUSB interface for flashing I2C EEPROMs, notably the HJB11 FRU EEPROM.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
TODO(q3k): host public instance
|
||||
|
||||
Development
|
||||
-----------
|
||||
|
||||
Plain javascript, bring a static file server, eg. Python's builtin one:
|
||||
|
||||
$ cd hscloud/dc/hjb11/flasher/web/
|
||||
$ python3 -m http.server
|
||||
|
284
dc/hbj11/flasher/web/fru.js
Normal file
284
dc/hbj11/flasher/web/fru.js
Normal file
|
@ -0,0 +1,284 @@
|
|||
/*
|
||||
* Platform Management FRU Information Storage Definition v1.0
|
||||
* Document Revision 1.3, March 24, 2015
|
||||
*
|
||||
* From: https://www.intel.com/content/www/us/en/servers/ipmi/ipmi-platform-mgt-fru-infostorage-def-v1-0-rev-1-3-spec-update.html
|
||||
*/
|
||||
export class FRUParser {
|
||||
constructor(data) {
|
||||
this.data = data
|
||||
}
|
||||
|
||||
parseCommon(data) {
|
||||
// 8. Common Header Format
|
||||
let version = data[0];
|
||||
if ((version >> 4) !== 0) throw new Error("Invalid Common Header version");
|
||||
if ((version & 0b1111) !== 1) throw new Error("Invalid Common Header version");
|
||||
|
||||
let res = {};
|
||||
res.version = version;
|
||||
res.internalUseStart = data[1] * 8;
|
||||
res.chassisInfoStart = data[2] * 8;
|
||||
res.boardInfoStart = data[3] * 8;
|
||||
res.productInfoStart = data[4] * 8;
|
||||
res.multiRecordInfoStart = data[5] * 8;
|
||||
|
||||
let sum = data.reduce((a, b) => a + b, 0) & 0xff;
|
||||
if (sum !== 0) throw new Error("Common area checksum error");
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
parseBoardInfo(data) {
|
||||
// 11. Board Info Area Format
|
||||
let res = {};
|
||||
|
||||
let version = data[0];
|
||||
if ((version >> 4) !== 0) throw new Error("Invalid Board Info version");
|
||||
if ((version & 0b1111) !== 1) throw new Error("Invalid Board Info version");
|
||||
res.version = version;
|
||||
|
||||
let areaLength = data[1] * 8;
|
||||
if (areaLength > data.length) throw new Error("Invalid Board Info length");
|
||||
data = data.slice(0, areaLength);
|
||||
|
||||
let sum = data.reduce((a, b) => a + b, 0) & 0xff;
|
||||
if (sum !== 0) throw new Error("Board Info Area checksum error");
|
||||
|
||||
let r = new Reader(data);
|
||||
r.skip(2);
|
||||
|
||||
res.language = r.readLanguageCode();
|
||||
res.manufacturingDate = r.readDateTime();
|
||||
res.manufacturerName = r.readTypeLength(res.language);
|
||||
res.productName = r.readTypeLength(res.language);
|
||||
res.serialNumber = r.readTypeLength(res.language);
|
||||
res.partNumber = r.readTypeLength(res.language);
|
||||
res.fruFileID = r.readTypeLength(res.language);
|
||||
res.custom = r.readTypeLength(res.language);
|
||||
// Not sure if this is up to standard - the standard seems to say that
|
||||
// C1 must always appear, but the Dell storage cards I've looked at
|
||||
// skip it. There's an earlier C1, but that's part of the FRU File ID.
|
||||
if (res.length > 0) {
|
||||
if (r.readByte() !== 0xc1) throw new Error("Custom area must end with C1");
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
parseInternalUseDell(data) {
|
||||
let version = data[0];
|
||||
if ((version >> 4) !== 0) throw new Error("Invalid Internal Use version");
|
||||
if ((version & 0b1111) !== 1) throw new Error("Invalid Internal Use version");
|
||||
|
||||
if ((new TextDecoder().decode(data.slice(1,5))) !== "DELL") {
|
||||
throw new Error("Invalid 'DELL' magic in internal area");
|
||||
}
|
||||
|
||||
let sum = data.reduce((a, b) => a + b, 0) & 0xff;
|
||||
if (sum !== 0) throw new Error("Dell Internal Area checksum error");
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
parse() {
|
||||
this.common = this.parseCommon(this.data.slice(0, 8))
|
||||
|
||||
if (this.common.boardInfoStart !== 0) {
|
||||
let data = this.data.slice(this.common.boardInfoStart, this.data.length);
|
||||
this.boardInfo = this.parseBoardInfo(data);
|
||||
} else {
|
||||
this.boardInfo = {};
|
||||
}
|
||||
this.internalUse = {};
|
||||
if (this.common.internalUseStart !== 0) {
|
||||
let data = this.data.slice(this.common.internalUseStart, this.data.length);
|
||||
this.internalUse.dell = this.parseInternalUseDell(data);
|
||||
}
|
||||
}
|
||||
|
||||
stringify() {
|
||||
let res = [];
|
||||
res.push(`Version: ${this.common.version}`)
|
||||
res.push(`Board Info:`)
|
||||
let bi = this.boardInfo;
|
||||
res.push(` Language: ${bi.language}`)
|
||||
if (bi.manufacturingDate !== undefined)
|
||||
res.push(` Manufacturing Date: ${bi.manufacturingDate}`);
|
||||
res.push(` Manufacturer Name: ${bi.manufacturerName}`)
|
||||
res.push(` Product Name: ${bi.productName}`)
|
||||
res.push(` Serial Number: ${bi.serialNumber}`)
|
||||
res.push(` PartNumber: ${bi.partNumber}`)
|
||||
res.push(` FRU File ID: ${bi.fruFileID}`)
|
||||
|
||||
if (this.internalUse.dell !== undefined) {
|
||||
res.push("Internal Use: DELL-specific")
|
||||
}
|
||||
return res.join("\n");
|
||||
}
|
||||
}
|
||||
|
||||
class Reader {
|
||||
constructor(data) {
|
||||
this.data = data;
|
||||
}
|
||||
skip(n) {
|
||||
this.data = this.data.slice(n);
|
||||
}
|
||||
readByte() {
|
||||
let num = this.data[0];
|
||||
this.data = this.data.slice(1);
|
||||
return num;
|
||||
}
|
||||
readLanguageCode() {
|
||||
let num = this.readByte();
|
||||
let encoding = num >> 6;
|
||||
let language = [
|
||||
"en", "aa", "ab", "af", "am", "ar", "as", "ay", "az", "ba", "be",
|
||||
"bg", "bh", "bi", "bn", "bo", "br", "ca", "co", "cs", "cy", "da",
|
||||
"de", "dz", "el", "en", "eo", "es", "et", "eu", "fa", "fi", "fj",
|
||||
"fo", "fr", "fy", "ga", "gd", "gl", "gn", "gu", "ha", "hi", "hr",
|
||||
"hu", "hy", "ia", "ie", "ik", "in", "is", "it", "iw", "ja", "ji",
|
||||
"jw", "ka", "kk", "kl", "km", "kn", "ko", "ks", "ku", "ky", "la",
|
||||
"ln", "lo", "lt", "lv", "mg", "mi", "mk", "ml", "mn", "mo", "mr",
|
||||
"ms", "mt", "my", "na", "ne", "nl", "no", "oc", "om", "or", "pa",
|
||||
"pl", "ps", "pt", "qu", "rm", "rn", "ro", "ru", "rw", "sa", "sd",
|
||||
"sg", "sh", "di", "sk", "dl", "sm", "sn", "so", "sq", "sr", "ss",
|
||||
"st", "su", "sv", "sw", "ta", "te", "tg", "th", "ti", "tk", "tl",
|
||||
"tn", "to", "tr", "ts", "tt", "tw", "uk", "ur", "uz", "vi", "vo",
|
||||
"wo", "xh", "yo", "zh", "zu",
|
||||
][num & 0b111111];
|
||||
return language;
|
||||
}
|
||||
readTypeLength(language) {
|
||||
let tag = this.readByte();
|
||||
let type = tag >> 6;
|
||||
let len = tag & 0b111111;
|
||||
switch (type) {
|
||||
case 0:
|
||||
return this.readTLBinary(len);
|
||||
case 1:
|
||||
return this.readTLBCDPlus(len);
|
||||
case 2:
|
||||
return this.readTL6BASCII(len);
|
||||
case 3:
|
||||
return this.readTLString(len, language);
|
||||
}
|
||||
}
|
||||
readTLBinary(len) {
|
||||
let data = this.data.slice(0, len);
|
||||
this.data = this.data.slice(len);
|
||||
return data;
|
||||
}
|
||||
readTLBCDPlus(len) {
|
||||
let data = this.data.slice(0, len);
|
||||
this.data = this.data.slice(len);
|
||||
const lookup = "012345689 -.???";
|
||||
let res = [];
|
||||
for (const c of data) {
|
||||
let upper = lookup[c >> 4];
|
||||
let lower = lookup[c & 0b1111];
|
||||
if ((upper === "?") || (lower === "?")) {
|
||||
throw new Error("Invalid BCD Plus data");
|
||||
}
|
||||
res.push(upper);
|
||||
res.push(lower);
|
||||
}
|
||||
return res.join("");
|
||||
}
|
||||
readTL6BASCII(len) {
|
||||
let data = this.data.slice(0, len);
|
||||
this.data = this.data.slice(len);
|
||||
const lookup =
|
||||
" !\"#$%&'()*+,-./" +
|
||||
"0123456789:;<=>?" +
|
||||
"@ABCDEFGHIJKLMNO" +
|
||||
"PQRSTUVWXYZ[\\]^_";
|
||||
|
||||
let res = [];
|
||||
let availbits = 0;
|
||||
let bits = 0;
|
||||
while ((data.length > 0) || (availbits >= 6)) {
|
||||
if (availbits < 6) {
|
||||
bits |= (data[0] << availbits);
|
||||
availbits += 8;
|
||||
data = data.slice(1);
|
||||
}
|
||||
let n = bits & 0b111111;
|
||||
availbits -= 6;
|
||||
bits >>= 6;
|
||||
res.push(lookup[n]);
|
||||
}
|
||||
return res.join("");
|
||||
}
|
||||
readTLString(len, language) {
|
||||
let data = this.data.slice(0, len);
|
||||
this.data = this.data.slice(len);
|
||||
// 13. Type/Length Byte Format
|
||||
// Yikes, Intel.
|
||||
if (language !== "en") {
|
||||
throw new Error("Unicode unimplemented");
|
||||
}
|
||||
// This should be 'ASCII + Latin 1', but this is a good enough approximation.
|
||||
return new TextDecoder().decode(data);
|
||||
}
|
||||
readDateTime() {
|
||||
let minutes = this.readByte() | (this.readByte() << 8) | (this.readByte() << 16);
|
||||
if (minutes !== 0) throw new Error("Datetime parsing not implemented");
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export class HBJ11FRUAssembler {
|
||||
constructor(serial) {
|
||||
this.serial = serial;
|
||||
}
|
||||
|
||||
assemble() {
|
||||
// Strings can be longer in FRU spec, but let's keep it conservative.
|
||||
if (this.serial.length > 8) {
|
||||
throw new Error("Serial too long");
|
||||
}
|
||||
// Same layout as DELL FRUs, board specific after common, internal use after board specific.
|
||||
let common = [0x01, 0x0a, 0x00, 0x01, 0x00, 0x00, 0x00, 0xf4];
|
||||
|
||||
// Similar layout to DELL FRUs, 72 bytes.
|
||||
let board = [
|
||||
0x01, // Version 1
|
||||
0x09, // Length (9*8 == 72 bytes)
|
||||
0x00, 0x00, 0x00, 0x00, // Manufacturing time (unspecified)
|
||||
0xC7, 98, 103, 112, 46, 119, 116, 102, // Manufacturer: bgp.wtf
|
||||
0xDE, 83, 65, 84, 65, 32, 82, 101, 112, 101, 97, 116, 101, 114, // Product name: SATA Repeater
|
||||
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, // ... pad above to 30 chars.
|
||||
];
|
||||
// Serial number tag/length.
|
||||
board.push(0xC0 | (this.serial.length));
|
||||
// Serial number.
|
||||
for (const c of this.serial) {
|
||||
board.push(c.charCodeAt());
|
||||
}
|
||||
board = board.concat([
|
||||
0xC7, 72, 66, 74, 49, 49, 65, 48, // Part number: HBJ11A0
|
||||
0xC1, 0x02, 0xC1, 0x00, // FRU File ID 2, one-byte custom area/end? Weird shit.
|
||||
]);
|
||||
if (board.length > 71) {
|
||||
throw new Error("Board Area too long!");
|
||||
}
|
||||
// Pad with zeroes.
|
||||
board = board.concat(Array(71 - board.length).fill(0));
|
||||
// Calculate checksum.
|
||||
let sum = (0xff ^ (board.reduce((a, b) => a + b, 0) & 0xff));
|
||||
board.push((sum + 1) & 0xff);
|
||||
|
||||
// Dell internal use.
|
||||
let dell = [
|
||||
0x01, 0x44, 0x45, 0x4c, 0x4c, 0xf7, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x01, 0x13, 0x58, 0x01,
|
||||
0x0f, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0d, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x01,
|
||||
0x00, 0x01, 0x01, 0x00,
|
||||
];
|
||||
|
||||
let eeprom = common.concat(board).concat(dell);
|
||||
// Pad to 256 bytes.
|
||||
eeprom = eeprom.concat(Array(256 - eeprom.length).fill(0));
|
||||
return new Uint8Array(eeprom);
|
||||
}
|
||||
}
|
159
dc/hbj11/flasher/web/i2c.js
Normal file
159
dc/hbj11/flasher/web/i2c.js
Normal file
|
@ -0,0 +1,159 @@
|
|||
/**
|
||||
* Low-level interface to programmer's I2C USB class.
|
||||
*
|
||||
* See //dc/hbj11/flasher/bluepill/src/i2c.rs for more information.
|
||||
*/
|
||||
|
||||
const ControlInRequest = Object.freeze({
|
||||
'GetStatus': 1,
|
||||
});
|
||||
|
||||
const ControlOutRequest = Object.freeze({
|
||||
'SetLED': 1,
|
||||
'ReadI2C': 2,
|
||||
'ReadBuffer': 3,
|
||||
'WriteI2C': 4,
|
||||
'SetWritePointer': 5,
|
||||
});
|
||||
|
||||
export const Status = Object.freeze({
|
||||
'Idle': 0,
|
||||
'InvalidArgument': 1,
|
||||
'Ack': 2,
|
||||
'Nack': 3,
|
||||
'BusError': 4,
|
||||
});
|
||||
|
||||
export const StatusFromU8 = function(u8) {
|
||||
for (const label of Object.keys(Status)) {
|
||||
let val = Status[label];
|
||||
if (val === u8) {
|
||||
return label;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Low-level interface to programmer's I2C USB class. Thinly wraps available
|
||||
* USB transfers.
|
||||
*/
|
||||
export class USBI2CClassInterface {
|
||||
/**
|
||||
* @param {USBDevice} usb - The WebUSB device that backs this USB class.
|
||||
*/
|
||||
constructor(device) {
|
||||
this.usb = device;
|
||||
this.BUFFER_SIZE = 1024;
|
||||
this.PACKET_SIZE = 64;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open this programmer via WebUSB and finds all required endpoints.
|
||||
*/
|
||||
async open() {
|
||||
await this.usb.open();
|
||||
await this.usb.selectConfiguration(1);
|
||||
await this.usb.claimInterface(0);
|
||||
|
||||
let eps = this.usb.configuration.interfaces[0].alternate.endpoints;
|
||||
this.bulk_out = null;
|
||||
this.bulk_in = null;
|
||||
for (const ep of eps) {
|
||||
if (ep.direction == "out" && ep.type == "bulk") {
|
||||
this.bulk_out = ep;
|
||||
}
|
||||
if (ep.direction == "in" && ep.type == "bulk") {
|
||||
this.bulk_in = ep;
|
||||
}
|
||||
}
|
||||
if (this.bulk_out === null) {
|
||||
throw new Error("Could not find bulk out endpoint");
|
||||
}
|
||||
if (this.bulk_in === null) {
|
||||
throw new Error("Could not find bulk in endpoint");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a USB Control OUT request to the I2C class.
|
||||
* @param {number} request - Request number for transfer (0-255).
|
||||
* @param {number} value - Value for transfer (0-65535).
|
||||
* @returns {Promise<USBOutTransferResult>} The underlying WebUSB transfer result.
|
||||
*/
|
||||
async controlOut(request, value) {
|
||||
return await this.usb.controlTransferOut({
|
||||
requestType: "vendor",
|
||||
recipient: "interface",
|
||||
request: request,
|
||||
value: value,
|
||||
index: 0,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Read status from programmer.
|
||||
* @returns {Status} The status of the programmer.
|
||||
*/
|
||||
async getStatus() {
|
||||
let res = await this.usb.controlTransferIn({
|
||||
requestType: "vendor",
|
||||
recipient: "interface",
|
||||
request: ControlInRequest.GetStatus,
|
||||
value: 0,
|
||||
index: 0
|
||||
}, 1);
|
||||
if (res.data.byteLength < 1) {
|
||||
throw new Error('returned data too short')
|
||||
}
|
||||
return res.data.getInt8(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends SetLED control OUT request.
|
||||
*/
|
||||
async setLED(on) {
|
||||
return await this.controlOut(ControlOutRequest.SetLED, on ? 1 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends ReadI2C control OUT request.
|
||||
*/
|
||||
async readI2C(addr, length) {
|
||||
return await this.controlOut(ControlOutRequest.ReadI2C, (length << 8) | addr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends ReadBuffer control OUT request.
|
||||
*/
|
||||
async readBuffer(addr, length) {
|
||||
return await this.controlOut(ControlOutRequest.ReadBuffer, (length << 8) | addr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends WriteI2C control OUT request.
|
||||
*/
|
||||
async writeI2C(addr, length) {
|
||||
return await this.controlOut(ControlOutRequest.WriteI2C, (length << 8) | addr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends SetWritePointer control OUT request.
|
||||
*/
|
||||
async setWritePointer(addr) {
|
||||
return await this.controlOut(ControlOutRequest.SetWritePointer, addr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Requets bulk IN data.
|
||||
*/
|
||||
async bulkIn(length) {
|
||||
return await this.usb.transferIn(this.bulk_in.endpointNumber, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends bulk OUT data.
|
||||
*/
|
||||
async bulkOut(data) {
|
||||
return await this.usb.transferOut(this.bulk_out.endpointNumber, data);
|
||||
}
|
||||
}
|
142
dc/hbj11/flasher/web/index.html
Normal file
142
dc/hbj11/flasher/web/index.html
Normal file
|
@ -0,0 +1,142 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Web I2C Flasher</title>
|
||||
<style>
|
||||
html {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
header {
|
||||
padding: 1rem;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
|
||||
}
|
||||
#document {
|
||||
margin: 40px auto;
|
||||
max-width: 750px;
|
||||
}
|
||||
button {
|
||||
display: inline-block;
|
||||
border: 0;
|
||||
background-color: #f8f8f8;
|
||||
color: #333;
|
||||
border: 1px solid #dedede;
|
||||
padding: 0.2rem 0.8rem 0.2rem 0.8rem;
|
||||
font-family: Verdana, Sans-Serif;
|
||||
font-size: 0.65rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
outline: 0;
|
||||
}
|
||||
button + button {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #fefefe;
|
||||
}
|
||||
button:active {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
button:disabled {
|
||||
color: #888;
|
||||
}
|
||||
button:disabled:hover {
|
||||
background-color: #f8f8f8;
|
||||
border: 1px solid #dedede;
|
||||
}
|
||||
button.btn-red {
|
||||
padding: 0.4rem 1rem 0.4rem 1rem;
|
||||
background-color: #ff4949;
|
||||
color: #fff;
|
||||
border: 1px solid #8a1c05;
|
||||
}
|
||||
button.btn-red:hover {
|
||||
background-color: #ff5959;
|
||||
border: 1px solid #8a1c05;
|
||||
}
|
||||
button.btn-red:active {
|
||||
background-color: #ff3939;
|
||||
border: 1px solid #8a1c05;
|
||||
}
|
||||
h1,h2,h3 {
|
||||
font-family: Helvetica, Sans-Serif;
|
||||
margin: 0;
|
||||
color: #111;
|
||||
}
|
||||
h1 {
|
||||
font-size: 1.5rem;
|
||||
color: #333;
|
||||
}
|
||||
h2 {
|
||||
margin-bottom: 0.3rem;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
h3 {
|
||||
font-size: 0.8rem;
|
||||
margin: 0.3rem;
|
||||
}
|
||||
#programmers {
|
||||
}
|
||||
.programmer {
|
||||
width: 100%;
|
||||
margin-top: 1rem;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
|
||||
}
|
||||
.programmerName {
|
||||
font-family: Verdana, sans-serif;
|
||||
font-size: 0.9rem;
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid #ddd;
|
||||
background-color: #fff;
|
||||
}
|
||||
.programmerOptions {
|
||||
float: right;
|
||||
}
|
||||
.devices {
|
||||
padding: 2rem;
|
||||
}
|
||||
.device {
|
||||
clear: both;
|
||||
background-color: #fafafa;
|
||||
padding: 1rem;
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
.device + .device {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.deviceName {
|
||||
font-family: Verdana, sans-serif;
|
||||
}
|
||||
.deviceOptions {
|
||||
float: right;
|
||||
}
|
||||
.deviceDump {
|
||||
color: #fff;
|
||||
background-color: #1c1c1c;
|
||||
display: block;
|
||||
clear: both;
|
||||
padding: 1rem;
|
||||
font-family: monospace;
|
||||
margin-top: 1rem;
|
||||
border-radius: 0.3rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Web I2C Flash</h1>
|
||||
</header>
|
||||
<div id="document">
|
||||
<button id="connect" class="btn-red">Add...</button>
|
||||
<div id="programmers">
|
||||
</div>
|
||||
</div>
|
||||
<script src="main.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
472
dc/hbj11/flasher/web/main.js
Normal file
472
dc/hbj11/flasher/web/main.js
Normal file
|
@ -0,0 +1,472 @@
|
|||
/*
|
||||
* WebI2C, a web interface for flashing I2C EEPROMS, notably FRU EEPROMs for
|
||||
* the HBJ11.
|
||||
*/
|
||||
|
||||
import { FRUParser, HBJ11FRUAssembler } from './fru.js';
|
||||
import { Status, StatusFromU8, USBI2CClassInterface } from './i2c.js';
|
||||
|
||||
/**
|
||||
* I2CDevice is an I2C device (eg. EEPROM) on the I2C bus, attached via a
|
||||
* Programmer.
|
||||
*/
|
||||
class I2CDevice {
|
||||
constructor(programmer, addr) {
|
||||
this.programmer = programmer;
|
||||
this.addr = addr;
|
||||
this.dump = "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Treat this device as an I2C EEPROM (eg. 24C02) and read its content.
|
||||
* @param {number} addr - The address in the EEPROM to start reading at.
|
||||
* @param {number} length - Count of bytes to read starting at address.
|
||||
* @returns {Promise<Uint8Array>} Contents of the EEPROM.
|
||||
*/
|
||||
async readFlash(addr, length) {
|
||||
// Always send a non-zero seek, otherwise 24C02 sometimes NACKs?
|
||||
await this.programmer.writeI2C(this.addr, new Uint8Array([1]));
|
||||
await this.programmer.readI2C(this.addr, 1);
|
||||
|
||||
// Chunk up reads into 128 bytes.
|
||||
let i = 0;
|
||||
const max_chunk_size = 128;
|
||||
let flash = new Uint8Array(length);
|
||||
while (i < length) {
|
||||
await this.programmer.writeI2C(this.addr, new Uint8Array([i]));
|
||||
let chunk_size = length - i;
|
||||
if (chunk_size > max_chunk_size) {
|
||||
chunk_size = max_chunk_size;
|
||||
}
|
||||
let res = await this.programmer.readI2C(this.addr, chunk_size);
|
||||
flash.set(new Uint8Array(res.buffer), i);
|
||||
i += chunk_size;
|
||||
}
|
||||
return flash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Threat this devices as an I2C EEPROM on a HBJ11 and flash it with a given
|
||||
* serial nyumber.
|
||||
* @param {string} serial - The serial number of the HJB11 to brand it with.
|
||||
* @param {HTMLButtonelement} button - Button used to trigger this action,
|
||||
* will be disabled while the flashing is
|
||||
* performed.
|
||||
*/
|
||||
async writeHBJ11(serial, button) {
|
||||
// Always send a non-zero seek, otherwise 24C02 sometimes NACKs?
|
||||
await this.programmer.writeI2C(this.addr, new Uint8Array([1]));
|
||||
await this.programmer.readI2C(this.addr, 1);
|
||||
|
||||
let text = button.innerText;
|
||||
button.disabled = true;
|
||||
button.innerText = "Flashing...";
|
||||
|
||||
let data = new HBJ11FRUAssembler(serial).assemble();
|
||||
|
||||
// Chunk up writes into 16 bytes.
|
||||
let chunks = [];
|
||||
for (let i = 0; i < data.length; i+= 16) {
|
||||
chunks.push([i].concat(Array.from(data.slice(i, i+16))));
|
||||
}
|
||||
|
||||
for (const chunk of chunks) {
|
||||
await this.programmer.writeI2C(this.addr, new Uint8Array(chunk));
|
||||
}
|
||||
|
||||
button.disabled = false;
|
||||
button.innerText = text;
|
||||
}
|
||||
|
||||
render(div) {
|
||||
div.innerHTML = "";
|
||||
let deviceName = document.createElement("div");
|
||||
deviceName.className = "deviceName";
|
||||
deviceName.appendChild(document.createTextNode(`Device 0x${this.addr.toString(16)}`));
|
||||
|
||||
div.appendChild(deviceName);
|
||||
|
||||
let deviceOptions = document.createElement("div");
|
||||
deviceOptions.className = "deviceOptions";
|
||||
|
||||
let readButton = document.createElement("button");
|
||||
readButton.appendChild(document.createTextNode("Read flash"));
|
||||
readButton.onclick = async () => {
|
||||
let res = await this.readFlash(this.addr, 256);
|
||||
|
||||
this.dump = "";
|
||||
const hex = "0123456789ABCDEF";
|
||||
for (let i = 0; i < res.length; i += 16) {
|
||||
let block = res.slice(i, Math.min(i+16, res.length));
|
||||
let addr = ("0000" + i.toString(16)).slice(-4);
|
||||
let codes = Array.from(block.values()).map((code) => {
|
||||
return " " + hex[(0xF0 & code) >> 4] + hex[0x0f & code];
|
||||
}).join("");
|
||||
codes += " ".repeat(16 - block.length);
|
||||
let chars = Array.from(block.values()).map((code) => {
|
||||
if (code < 0x20 || code > 0x7e) {
|
||||
return ".";
|
||||
}
|
||||
return String.fromCharCode(code);
|
||||
}).join("");
|
||||
codes += " ".repeat(16 - block.length);
|
||||
this.dump += (addr + " " + codes + " " + chars + "\n");
|
||||
}
|
||||
|
||||
let p = new FRUParser(res);
|
||||
try {
|
||||
p.parse();
|
||||
this.dump += "\nFRU EEPROM:\n";
|
||||
this.dump += p.stringify();
|
||||
} catch(err) {
|
||||
this.dump += "\nNot an FRU EEPROM: " + err;
|
||||
}
|
||||
|
||||
console.log(this.dump);
|
||||
|
||||
this.render(div);
|
||||
};
|
||||
deviceOptions.appendChild(readButton);
|
||||
|
||||
let makeButton = document.createElement("button");
|
||||
makeButton.appendChild(document.createTextNode("Make HBJ11"));
|
||||
makeButton.onclick = async () => {
|
||||
await this.writeHBJ11(window.prompt("Enter HBJ11 Serial", "A0000"), makeButton);
|
||||
};
|
||||
deviceOptions.appendChild(makeButton);
|
||||
|
||||
deviceName.appendChild(deviceOptions);
|
||||
|
||||
if (this.dump.length > 0) {
|
||||
let deviceDump = document.createElement("pre");
|
||||
|
||||
deviceDump.className = "deviceDump";
|
||||
deviceDump.innerText = this.dump;
|
||||
div.appendChild(deviceDump);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of I2CDevices, eg. EEPROMs. Used for DOM rendering.
|
||||
*/
|
||||
class I2CDeviceList {
|
||||
constructor() {
|
||||
this.list = [];
|
||||
}
|
||||
|
||||
set(devices) {
|
||||
this.list = devices;
|
||||
}
|
||||
|
||||
render(div) {
|
||||
if (this.list.length === 0) {
|
||||
div.innerHTML = "<i>No devices...</i>";
|
||||
return;
|
||||
}
|
||||
|
||||
for (const device of this.list) {
|
||||
let deviceDiv = document.createElement("div");
|
||||
deviceDiv.className = "device";
|
||||
device.render(deviceDiv);
|
||||
div.appendChild(deviceDiv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A WebI2C compatible programmer accessed over USB.
|
||||
*/
|
||||
class Programmer {
|
||||
/**
|
||||
* @param {USBDevice} usb - The WebUSB device that backs this programmer.
|
||||
*/
|
||||
constructor(usb) {
|
||||
this.usb = usb;
|
||||
this.i2c = new USBI2CClassInterface(usb);
|
||||
this.devices = new I2CDeviceList();
|
||||
}
|
||||
/**
|
||||
* Get programmer manufacturer name.
|
||||
* @returns {string} The name.
|
||||
*/
|
||||
get manufacturerName() {
|
||||
return this.usb.manufacturerName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get programmer product name.
|
||||
* @returns {string} The name.
|
||||
*/
|
||||
get productName() {
|
||||
return this.usb.productName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get programmer serial number.
|
||||
* @returns {string} The name.
|
||||
*/
|
||||
get serialNumber() {
|
||||
return this.usb.serialNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two Programmers and checks if they're using the same WebUSB
|
||||
* device underneath. This is used for housekeeping of the ProgrammerList.
|
||||
*/
|
||||
equal(other) {
|
||||
let one = this.usb;
|
||||
let two = other.usb;
|
||||
return (one.vendorId == two.vendorId)
|
||||
&& (one.productId == two.productId)
|
||||
&& (one.serialNumber == two.serialNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs an I2C read on the bus of the programmer and reads the resulting
|
||||
* data from the buffer. The readout is performed in chunks over multiple
|
||||
* Bulk transfer.
|
||||
* @param {number} addr - Address of the I2C device to read from.
|
||||
* @param {number} length - Number of bytes to read from I2C (not larger than
|
||||
* BUFFER_SIZE).
|
||||
* @returns {object} Object with status and bufer keys. TODO(q3k): declare type.
|
||||
*/
|
||||
async readI2C(addr, length) {
|
||||
await this.i2c.readI2C(addr, length);
|
||||
let status = await this.i2c.getStatus();
|
||||
if (status !== Status.Ack) {
|
||||
return {status: status, buffer: null};
|
||||
}
|
||||
let buffer = new Uint8Array(length);
|
||||
let i = 0;
|
||||
while (i < length) {
|
||||
let chunkSize = length - i;
|
||||
if (chunkSize > this.i2c.PACKET_SIZE) {
|
||||
chunkSize = this.i2c.PACKET_SIZE;
|
||||
}
|
||||
let chunk = await this.readBuffer(i, chunkSize);
|
||||
buffer.set(new Uint8Array(chunk.buffer), i);
|
||||
i += chunkSize;
|
||||
}
|
||||
return {status: status, buffer: buffer};
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfers data to internal buffer of programmers and performs an I2C write
|
||||
* with the given data.
|
||||
* @param {number} addr - Address of the I2C to write data to.
|
||||
* @param {ArrayBuffer} data - Data to write to device.
|
||||
*/
|
||||
async writeI2C(addr, data) {
|
||||
let i = 0;
|
||||
while (i < data.length) {
|
||||
let end = i + this.i2c.PACKET_SIZE;
|
||||
if (end > data.length) {
|
||||
end = data.length;
|
||||
}
|
||||
let chunk = data.slice(i, end);
|
||||
await this.writeBuffer(i, chunk);
|
||||
i = end;
|
||||
}
|
||||
await this.i2c.writeI2C(addr, data.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a scan of the I2C bus for all connected devices and upgrades the
|
||||
* internal I2CDeviceList with found I2CDevices.
|
||||
* @param {HTMLButtonElement} button - Button that will be disabled when the
|
||||
* Scan is performed.
|
||||
*/
|
||||
async scan(button) {
|
||||
let text = button.innerText;
|
||||
button.innerText = "Scanning...";
|
||||
button.disabled = true;
|
||||
|
||||
let present = [];
|
||||
for (let i = 0; i < 127; i++) {
|
||||
let res = await this.readI2C(i, 1);
|
||||
switch (res.status) {
|
||||
case Status.Ack:
|
||||
present.push(new I2CDevice(this, i));
|
||||
break;
|
||||
case Status.Nack:
|
||||
break;
|
||||
default:
|
||||
throw new Error(`When scanning ${i}: ${StatusFromU8(res.status)}`);
|
||||
}
|
||||
}
|
||||
this.devices.set(present);
|
||||
|
||||
button.disabled = false;
|
||||
button.innerText = text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Blinks the programmer's LED.
|
||||
* @param {HTMLButtonElement} button - Button that will be disabled when the
|
||||
* LED blinks.
|
||||
*/
|
||||
async blink(button) {
|
||||
let on = true;
|
||||
button.disabled = true;
|
||||
let text = button.innerText;
|
||||
button.innerText = "Blinking...";
|
||||
for (let i = 0; i < 20; i++) {
|
||||
await this.i2c.setLED(on);
|
||||
await new Promise(r => setTimeout(r, 100));
|
||||
on = !on;
|
||||
}
|
||||
button.disabled = false;
|
||||
button.innerText = text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests buffer readout from device via ReadBuffer control transfer and
|
||||
* then performs a single read via the Bulk IN endpoint.
|
||||
* @param {number} addr - Address within the buffer to start read at.
|
||||
* @param {number} length - Number of bytes to read (not larger than
|
||||
* PACKET_SIZE).
|
||||
* @returns {ArrayBuffer} Data read from buffer.
|
||||
*/
|
||||
async readBuffer(addr, length) {
|
||||
await this.i2c.readBuffer(addr, length);
|
||||
let status = await this.i2c.getStatus();
|
||||
if (status !== Status.Idle) {
|
||||
throw new Error(`When requesting buffer: ${StatusFromU8(res.status)}`);
|
||||
}
|
||||
let res = await this.i2c.bulkIn(length);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes bytes to internal buffer.
|
||||
* @param {number} addr - Address within the buffer to start write at.
|
||||
* @param {ArrayBuffer} data - Data to write to buffer (must not be longer
|
||||
* than PACKET_SIZE).
|
||||
*/
|
||||
async writeBuffer(addr, data) {
|
||||
await this.i2c.setWritePointer(addr);
|
||||
let status = await this.i2c.getStatus();
|
||||
if (status !== Status.Idle) {
|
||||
throw new Error(`When setting pointer: ${StatusFromU8(res.status)}`);
|
||||
}
|
||||
await this.i2c.bulkOut(data);
|
||||
}
|
||||
|
||||
|
||||
render(div) {
|
||||
let programmer = document.createElement("div");
|
||||
programmer.className = "programmer";
|
||||
let programmerName = document.createElement("div");
|
||||
programmerName.className = "programmerName";
|
||||
programmerName.appendChild(document.createTextNode(this.manufacturerName));
|
||||
programmerName.appendChild(document.createTextNode(" "));
|
||||
let b = document.createElement("b");
|
||||
b.textContent = this.productName;
|
||||
programmerName.appendChild(b);
|
||||
|
||||
let programmerOptions = document.createElement("div");
|
||||
programmerOptions.className = "programmerOptions";
|
||||
|
||||
let blinkButton = document.createElement("button");
|
||||
blinkButton.appendChild(document.createTextNode("Blink LED"));
|
||||
blinkButton.onclick = async () => {
|
||||
await this.blink(blinkButton);
|
||||
};
|
||||
programmerOptions.appendChild(blinkButton);
|
||||
|
||||
let devices = document.createElement("div");
|
||||
devices.className = "devices";
|
||||
|
||||
let scanButton = document.createElement("button");
|
||||
scanButton.appendChild(document.createTextNode("Scan I2C Bus"));
|
||||
scanButton.onclick = async () => {
|
||||
await this.scan(scanButton);
|
||||
devices.innerText = "";
|
||||
this.devices.render(devices)
|
||||
};
|
||||
programmerOptions.appendChild(scanButton);
|
||||
|
||||
programmerName.appendChild(programmerOptions);
|
||||
programmer.appendChild(programmerName);
|
||||
|
||||
this.devices.render(devices)
|
||||
programmer.append(devices);
|
||||
|
||||
div.appendChild(programmer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List of Programmers, used for rendering to DOM.
|
||||
*/
|
||||
class ProgrammerList {
|
||||
constructor(list) {
|
||||
this.list = [];
|
||||
for (const l of list) {
|
||||
this.list.push(l);
|
||||
}
|
||||
this.status = {};
|
||||
}
|
||||
async addProgrammer(programmer) {
|
||||
let existing = this.list.filter(d => d.equal(programmer));
|
||||
if (existing.length == 0) {
|
||||
this.list.push(programmer);
|
||||
await programmer.i2c.open();
|
||||
}
|
||||
}
|
||||
removeProgrammer(programmer) {
|
||||
this.list = this.list.filter(d => !d.equal(programmer));
|
||||
}
|
||||
render() {
|
||||
let div = document.querySelector("#programmers");
|
||||
div.innerText = "";
|
||||
for (const programmer of this.list) {
|
||||
programmer.render(div);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (navigator.usb === undefined || navigator.usb.requestDevice === undefined) {
|
||||
alert("No WebUSB support! Please use a Chromium-based browser.");
|
||||
}
|
||||
|
||||
// 'global' ProgrammerList, modified by document/USB events.
|
||||
let list = null;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
let programmers = (await navigator.usb.getDevices()).map(d => new Programmer(d));
|
||||
for (const programmer of programmers) {
|
||||
await programmer.i2c.open();
|
||||
}
|
||||
list = new ProgrammerList(programmers);
|
||||
list.render();
|
||||
});
|
||||
|
||||
navigator.usb.addEventListener('connect', async event => {
|
||||
await list.addProgrammer(new Programmer(event.device));
|
||||
list.render();
|
||||
});
|
||||
|
||||
navigator.usb.addEventListener('disconnect', event => {
|
||||
list.removeProgrammer(new Programmer(event.device));
|
||||
list.render();
|
||||
});
|
||||
|
||||
document.getElementById("connect").onclick = async () => {
|
||||
let device;
|
||||
try {
|
||||
device = await navigator.usb.requestDevice({
|
||||
filters: [{
|
||||
vendorId: 0x16c0,
|
||||
productId: 0x27d8,
|
||||
}]
|
||||
});
|
||||
} catch (err) {
|
||||
return;
|
||||
};
|
||||
if (device !== undefined) {
|
||||
await list.addProgrammer(new Programmer(device));
|
||||
list.render();
|
||||
}
|
||||
};
|
Loading…
Add table
Reference in a new issue