├── .cargo └── config ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── blethrs.py ├── build.rs ├── memory.x └── src ├── bootload.rs ├── config.rs ├── ethernet.rs ├── flash.rs ├── main.rs └── network.rs /.cargo/config: -------------------------------------------------------------------------------- 1 | [target.thumbv7em-none-eabihf] 2 | rustflags = [ 3 | "-C", "link-arg=-Tlink.x", 4 | ] 5 | 6 | [build] 7 | target = "thumbv7em-none-eabihf" 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /target 3 | **/*.rs.bk 4 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "aligned" 5 | version = "0.3.2" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | dependencies = [ 8 | "as-slice 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 9 | ] 10 | 11 | [[package]] 12 | name = "as-slice" 13 | version = "0.1.3" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | dependencies = [ 16 | "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", 17 | "generic-array 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)", 18 | "stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 19 | ] 20 | 21 | [[package]] 22 | name = "autocfg" 23 | version = "1.0.0" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | 26 | [[package]] 27 | name = "bare-metal" 28 | version = "0.2.5" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | dependencies = [ 31 | "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 32 | ] 33 | 34 | [[package]] 35 | name = "bitflags" 36 | version = "1.2.1" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | 39 | [[package]] 40 | name = "blethrs" 41 | version = "0.1.3" 42 | dependencies = [ 43 | "built 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 44 | "cortex-m 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 45 | "cortex-m-rt 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", 46 | "cortex-m-semihosting 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 47 | "panic-halt 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 48 | "smoltcp 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 49 | "stm32f4 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", 50 | "ufmt 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 51 | ] 52 | 53 | [[package]] 54 | name = "built" 55 | version = "0.4.0" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | dependencies = [ 58 | "cargo-lock 4.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 59 | "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", 60 | "git2 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 61 | ] 62 | 63 | [[package]] 64 | name = "byteorder" 65 | version = "1.3.4" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | 68 | [[package]] 69 | name = "cargo-lock" 70 | version = "4.0.1" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | dependencies = [ 73 | "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 74 | "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", 75 | "toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", 76 | "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 77 | ] 78 | 79 | [[package]] 80 | name = "cc" 81 | version = "1.0.50" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | dependencies = [ 84 | "jobserver 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", 85 | ] 86 | 87 | [[package]] 88 | name = "cfg-if" 89 | version = "0.1.10" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | 92 | [[package]] 93 | name = "chrono" 94 | version = "0.4.11" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | dependencies = [ 97 | "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 98 | "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 99 | "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 100 | ] 101 | 102 | [[package]] 103 | name = "cortex-m" 104 | version = "0.6.2" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | dependencies = [ 107 | "aligned 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 108 | "bare-metal 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", 109 | "volatile-register 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 110 | ] 111 | 112 | [[package]] 113 | name = "cortex-m-rt" 114 | version = "0.6.12" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | dependencies = [ 117 | "cortex-m-rt-macros 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 118 | "r0 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 119 | ] 120 | 121 | [[package]] 122 | name = "cortex-m-rt-macros" 123 | version = "0.1.8" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | dependencies = [ 126 | "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", 127 | "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 128 | "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", 129 | ] 130 | 131 | [[package]] 132 | name = "cortex-m-semihosting" 133 | version = "0.3.5" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | dependencies = [ 136 | "cortex-m 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 137 | ] 138 | 139 | [[package]] 140 | name = "generic-array" 141 | version = "0.12.3" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | dependencies = [ 144 | "typenum 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)", 145 | ] 146 | 147 | [[package]] 148 | name = "generic-array" 149 | version = "0.13.2" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | dependencies = [ 152 | "typenum 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)", 153 | ] 154 | 155 | [[package]] 156 | name = "git2" 157 | version = "0.11.0" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | dependencies = [ 160 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 161 | "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", 162 | "libgit2-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", 163 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 164 | "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 165 | ] 166 | 167 | [[package]] 168 | name = "idna" 169 | version = "0.2.0" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | dependencies = [ 172 | "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 173 | "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 174 | "unicode-normalization 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 175 | ] 176 | 177 | [[package]] 178 | name = "jobserver" 179 | version = "0.1.21" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | dependencies = [ 182 | "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", 183 | ] 184 | 185 | [[package]] 186 | name = "libc" 187 | version = "0.2.69" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | 190 | [[package]] 191 | name = "libgit2-sys" 192 | version = "0.10.0" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | dependencies = [ 195 | "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", 196 | "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", 197 | "libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", 198 | "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", 199 | ] 200 | 201 | [[package]] 202 | name = "libz-sys" 203 | version = "1.0.25" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | dependencies = [ 206 | "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", 207 | "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", 208 | "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", 209 | "vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 210 | ] 211 | 212 | [[package]] 213 | name = "log" 214 | version = "0.4.8" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | dependencies = [ 217 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 218 | ] 219 | 220 | [[package]] 221 | name = "managed" 222 | version = "0.7.1" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | 225 | [[package]] 226 | name = "matches" 227 | version = "0.1.8" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | 230 | [[package]] 231 | name = "num-integer" 232 | version = "0.1.42" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | dependencies = [ 235 | "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 236 | "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 237 | ] 238 | 239 | [[package]] 240 | name = "num-traits" 241 | version = "0.2.11" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | dependencies = [ 244 | "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 245 | ] 246 | 247 | [[package]] 248 | name = "panic-halt" 249 | version = "0.2.0" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | 252 | [[package]] 253 | name = "percent-encoding" 254 | version = "2.1.0" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | 257 | [[package]] 258 | name = "pkg-config" 259 | version = "0.3.17" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | 262 | [[package]] 263 | name = "proc-macro-hack" 264 | version = "0.5.15" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | 267 | [[package]] 268 | name = "proc-macro2" 269 | version = "1.0.10" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | dependencies = [ 272 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 273 | ] 274 | 275 | [[package]] 276 | name = "quote" 277 | version = "1.0.3" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | dependencies = [ 280 | "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", 281 | ] 282 | 283 | [[package]] 284 | name = "r0" 285 | version = "0.2.2" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | 288 | [[package]] 289 | name = "redox_syscall" 290 | version = "0.1.56" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | 293 | [[package]] 294 | name = "rustc_version" 295 | version = "0.2.3" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | dependencies = [ 298 | "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 299 | ] 300 | 301 | [[package]] 302 | name = "semver" 303 | version = "0.9.0" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | dependencies = [ 306 | "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 307 | "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", 308 | ] 309 | 310 | [[package]] 311 | name = "semver-parser" 312 | version = "0.7.0" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | 315 | [[package]] 316 | name = "serde" 317 | version = "1.0.106" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | dependencies = [ 320 | "serde_derive 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", 321 | ] 322 | 323 | [[package]] 324 | name = "serde_derive" 325 | version = "1.0.106" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | dependencies = [ 328 | "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", 329 | "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 330 | "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", 331 | ] 332 | 333 | [[package]] 334 | name = "smallvec" 335 | version = "1.3.0" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | 338 | [[package]] 339 | name = "smoltcp" 340 | version = "0.6.0" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | dependencies = [ 343 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 344 | "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 345 | "managed 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", 346 | ] 347 | 348 | [[package]] 349 | name = "stable_deref_trait" 350 | version = "1.1.1" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | 353 | [[package]] 354 | name = "stm32f4" 355 | version = "0.10.0" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | dependencies = [ 358 | "bare-metal 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", 359 | "cortex-m 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 360 | "cortex-m-rt 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", 361 | "vcell 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 362 | ] 363 | 364 | [[package]] 365 | name = "syn" 366 | version = "1.0.17" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | dependencies = [ 369 | "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", 370 | "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 371 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 372 | ] 373 | 374 | [[package]] 375 | name = "time" 376 | version = "0.1.42" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | dependencies = [ 379 | "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", 380 | "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", 381 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 382 | ] 383 | 384 | [[package]] 385 | name = "toml" 386 | version = "0.5.6" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | dependencies = [ 389 | "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", 390 | ] 391 | 392 | [[package]] 393 | name = "typenum" 394 | version = "1.12.0" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | 397 | [[package]] 398 | name = "ufmt" 399 | version = "0.1.0" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | dependencies = [ 402 | "proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)", 403 | "ufmt-macros 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 404 | "ufmt-write 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 405 | ] 406 | 407 | [[package]] 408 | name = "ufmt-macros" 409 | version = "0.1.1" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | dependencies = [ 412 | "proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)", 413 | "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", 414 | "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 415 | "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", 416 | ] 417 | 418 | [[package]] 419 | name = "ufmt-write" 420 | version = "0.1.0" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | 423 | [[package]] 424 | name = "unicode-bidi" 425 | version = "0.3.4" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | dependencies = [ 428 | "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 429 | ] 430 | 431 | [[package]] 432 | name = "unicode-normalization" 433 | version = "0.1.12" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | dependencies = [ 436 | "smallvec 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 437 | ] 438 | 439 | [[package]] 440 | name = "unicode-xid" 441 | version = "0.2.0" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | 444 | [[package]] 445 | name = "url" 446 | version = "2.1.1" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | dependencies = [ 449 | "idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 450 | "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 451 | "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 452 | ] 453 | 454 | [[package]] 455 | name = "vcell" 456 | version = "0.1.2" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | 459 | [[package]] 460 | name = "vcpkg" 461 | version = "0.2.8" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | 464 | [[package]] 465 | name = "volatile-register" 466 | version = "0.2.0" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | dependencies = [ 469 | "vcell 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 470 | ] 471 | 472 | [[package]] 473 | name = "winapi" 474 | version = "0.3.8" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | dependencies = [ 477 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 478 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 479 | ] 480 | 481 | [[package]] 482 | name = "winapi-i686-pc-windows-gnu" 483 | version = "0.4.0" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | 486 | [[package]] 487 | name = "winapi-x86_64-pc-windows-gnu" 488 | version = "0.4.0" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | 491 | [metadata] 492 | "checksum aligned 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eb1ce8b3382016136ab1d31a1b5ce807144f8b7eb2d5f16b2108f0f07edceb94" 493 | "checksum as-slice 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "37dfb65bc03b2bc85ee827004f14a6817e04160e3b1a28931986a666a9290e70" 494 | "checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 495 | "checksum bare-metal 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" 496 | "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 497 | "checksum built 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "20c01a8375121157fc0a9643af2d045272d86916d9a3e2cb491096971bdaf1c1" 498 | "checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" 499 | "checksum cargo-lock 4.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8504b63dd1249fd1745b7b4ef9b6f7b107ddeb3c95370043c7dbcc38653a2679" 500 | "checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" 501 | "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 502 | "checksum chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" 503 | "checksum cortex-m 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2954942fbbdd49996704e6f048ce57567c3e1a4e2dc59b41ae9fde06a01fc763" 504 | "checksum cortex-m-rt 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "00d518da72bba39496024b62607c1d8e37bcece44b2536664f1132a73a499a28" 505 | "checksum cortex-m-rt-macros 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "4717562afbba06e760d34451919f5c3bf3ac15c7bb897e8b04862a7428378647" 506 | "checksum cortex-m-semihosting 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "113ef0ecffee2b62b58f9380f4469099b30e9f9cbee2804771b4203ba1762cfa" 507 | "checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" 508 | "checksum generic-array 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0ed1e761351b56f54eb9dcd0cfaca9fd0daecf93918e1cfc01c8a3d26ee7adcd" 509 | "checksum git2 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "77519ef7c5beee314d0804d4534f01e0f9e8d9acdee2b7a48627e590b27e0ec4" 510 | "checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" 511 | "checksum jobserver 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2" 512 | "checksum libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)" = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005" 513 | "checksum libgit2-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9ec6bca50549d34a392611dde775123086acbd994e3fff64954777ce2dc2e51" 514 | "checksum libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "2eb5e43362e38e2bca2fd5f5134c4d4564a23a5c28e9b95411652021a8675ebe" 515 | "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" 516 | "checksum managed 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fdcec5e97041c7f0f1c5b7d93f12e57293c831c646f4cc7a5db59460c7ea8de6" 517 | "checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" 518 | "checksum num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" 519 | "checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" 520 | "checksum panic-halt 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de96540e0ebde571dc55c73d60ef407c653844e6f9a1e2fdbd40c07b9252d812" 521 | "checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 522 | "checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" 523 | "checksum proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)" = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63" 524 | "checksum proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)" = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3" 525 | "checksum quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" 526 | "checksum r0 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2a38df5b15c8d5c7e8654189744d8e396bddc18ad48041a500ce52d6948941f" 527 | "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" 528 | "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 529 | "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 530 | "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 531 | "checksum serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)" = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399" 532 | "checksum serde_derive 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)" = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c" 533 | "checksum smallvec 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05720e22615919e4734f6a99ceae50d00226c3c5aca406e102ebc33298214e0a" 534 | "checksum smoltcp 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0fe46639fd2ec79eadf8fe719f237a7a0bd4dac5d957f1ca5bbdbc1c3c39e53a" 535 | "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" 536 | "checksum stm32f4 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44a3d6c58b14e63926273694e7dd644894513c5e35ce6928c4657ddb62cae976" 537 | "checksum syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)" = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03" 538 | "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" 539 | "checksum toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" 540 | "checksum typenum 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" 541 | "checksum ufmt 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2e7ecea7ef79d3f8f878eee614afdf5256475c63ad76139d4da6125617c784a0" 542 | "checksum ufmt-macros 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ed813e34a2bfa9dc58ee2ed8c8314d25e6d70c911486d64b8085cb695cfac069" 543 | "checksum ufmt-write 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e87a2ed6b42ec5e28cc3b94c09982969e9227600b2e3dcbc1db927a84c06bd69" 544 | "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" 545 | "checksum unicode-normalization 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" 546 | "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 547 | "checksum url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" 548 | "checksum vcell 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "876e32dcadfe563a4289e994f7cb391197f362b6315dc45e8ba4aa6f564a4b3c" 549 | "checksum vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" 550 | "checksum volatile-register 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0d67cb4616d99b940db1d6bd28844ff97108b498a6ca850e5b6191a532063286" 551 | "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 552 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 553 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 554 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "blethrs" 3 | version = "0.1.3" 4 | authors = ["Adam Greig "] 5 | 6 | [dependencies] 7 | cortex-m = "0.6.2" 8 | cortex-m-rt = "0.6.12" 9 | cortex-m-semihosting = "0.3.5" 10 | panic-halt = "0.2.0" 11 | ufmt = "0.1.0" 12 | 13 | [dependencies.smoltcp] 14 | version = "0.6.0" 15 | default-features = false 16 | features = ["proto-ipv4", "socket-tcp", "ethernet"] 17 | 18 | [dependencies.stm32f4] 19 | version = "0.10.0" 20 | features = ["stm32f407", "rt"] 21 | 22 | [build-dependencies] 23 | built = { version = "0.4.0", features = ["git2", "chrono"] } 24 | 25 | [profile.release] 26 | debug = true 27 | lto = true 28 | codegen-units = 1 29 | incremental = false 30 | opt-level = "s" 31 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Airborne Engineering Ltd 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # blethrs 2 | 3 | An ethernet bootloader for STM32s written in Rust, 4 | using the [smoltcp](https://github.com/m-labs/smoltcp) TCP/IP stack. 5 | 6 | ## Building 7 | 8 | 9 | cargo build --release 10 | 11 | The resulting executable is at `target/thumbv7em-none-eabihf/release/blethrs` 12 | 13 | 14 | ## Default Config 15 | 16 | Without a valid config in flash, blethers defaults to IP address `10.1.1.10`, 17 | gateway `10.1.1.1`, MAC address `02:00:01:02:03:04`. 18 | 19 | ## License 20 | 21 | Licensed under either of 22 | 23 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 24 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 25 | 26 | at your option. 27 | 28 | ## Contribution 29 | 30 | Unless you explicitly state otherwise, any contribution intentionally submitted 31 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 32 | dual licensed as above, without any additional terms or conditions. 33 | -------------------------------------------------------------------------------- /blethrs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import time 4 | import struct 5 | import socket 6 | import argparse 7 | import crcmod 8 | 9 | try: 10 | from tqdm import tqdm 11 | except ImportError: 12 | print("Notice: tqdm not installed, install for progress bars.") 13 | 14 | def tqdm(x, *args, **kwargs): 15 | return x 16 | 17 | 18 | commands = { 19 | "info": 0, 20 | "read": 1, 21 | "erase": 2, 22 | "write": 3, 23 | "boot": 4, 24 | } 25 | 26 | 27 | errors = { 28 | 0: "Success", 29 | 1: "Invalid Address", 30 | 2: "Length Not Multiple of 4", 31 | 3: "Length Too Long", 32 | 4: "Data Length Incorrect", 33 | 5: "Erase Error", 34 | 6: "Write Error", 35 | 7: "Flash Error", 36 | 8: "Network Error", 37 | 9: "Internal Error", 38 | } 39 | 40 | 41 | class BootloaderError(Exception): 42 | def __init__(self, errno): 43 | self.errno = errno 44 | 45 | def __str__(self): 46 | if self.errno in errors: 47 | return "{}".format(errors[self.errno]) 48 | else: 49 | return "Unknown error {}".format(self.errno) 50 | 51 | 52 | class MismatchError(Exception): 53 | def __init__(self, addr, tx, rx): 54 | self.addr = addr 55 | self.tx = tx 56 | self.rx = rx 57 | 58 | def __str__(self): 59 | return "Mismatch at address {:08X}: {:02X}!={:02X}".format( 60 | self.addr, self.tx, self.rx) 61 | 62 | 63 | def boot_request(hostname, boot_req_port, bootloader_port, n_attempts=10): 64 | print("Sending UDP boot request to port {}...".format(boot_req_port)) 65 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 66 | cmd = struct.pack("5I", config_bytes) 185 | raw = struct.pack("<5I", *u32) 186 | crc = crc32(raw) 187 | crc_bytes = struct.pack(" = None; 6 | use ::config::{BOOTLOAD_FLAG_VALUE, BOOTLOAD_FLAG_ADDRESS}; 7 | 8 | /// Returns true if the most recent reset was due to a software request 9 | /// 10 | /// Clears the reset cause before returning, so this answer is only valid once. 11 | pub fn was_software_reset(rcc: &mut stm32f407::RCC) -> bool { 12 | let result = rcc.csr.read().sftrstf().bit_is_set(); 13 | rcc.csr.modify(|_, w| w.rmvf().set_bit()); 14 | result 15 | } 16 | 17 | /// Returns true if the bootload flag is set: RAM 0x2000_0000 == 0xB00110AD 18 | /// 19 | /// Clears the flag before returning, so this answer is only valid once. 20 | pub fn flag_set() -> bool { 21 | cortex_m::interrupt::free(|_| unsafe { 22 | let flag = core::ptr::read_volatile(BOOTLOAD_FLAG_ADDRESS as *const u32); 23 | clear_flag(); 24 | flag == BOOTLOAD_FLAG_VALUE 25 | }) 26 | } 27 | 28 | fn clear_flag() { 29 | cortex_m::interrupt::free(|_| unsafe { 30 | core::ptr::write_volatile(BOOTLOAD_FLAG_ADDRESS as *mut u32, 0); 31 | }); 32 | } 33 | 34 | /// Trigger a reset that will cause us to bootload the user application next go around 35 | pub fn reset_bootload() { 36 | clear_flag(); 37 | // It's troublesome to require SCB be passed in here, and 38 | // we're literally about to reset the whole microcontroller, 39 | // so safety is not such a huge concern. 40 | let aircr = 0xE000ED0C as *mut u32; 41 | unsafe { *aircr = (0x5FA<<16) | (1<<2) }; 42 | } 43 | 44 | /// Jump to user code at the given address. 45 | /// 46 | /// Doesn't disable interrupts so only call this right at boot, 47 | /// when no interrupt sources will be enabled. 48 | pub fn bootload(scb: &mut cortex_m::peripheral::SCB, address: u32) { 49 | unsafe { 50 | let sp = *(address as *const u32); 51 | let rv = *((address + 4) as *const u32); 52 | 53 | USER_RESET = Some(core::mem::transmute(rv)); 54 | scb.vtor.write(address); 55 | cortex_m::register::msp::write(sp); 56 | (USER_RESET.unwrap())(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | //! Chip and board specific configuration settings go here. 2 | use stm32f407; 3 | use ::bootload; 4 | 5 | /// TCP port to listen on 6 | pub const TCP_PORT: u16 = 7777; 7 | 8 | /// PHY address 9 | pub const ETH_PHY_ADDR: u8 = 0; 10 | 11 | /// Start address of each sector in flash 12 | pub const FLASH_SECTOR_ADDRESSES: [u32; 12] = 13 | [0x0800_0000, 0x0800_4000, 0x0800_8000, 0x0800_C000, 14 | 0x0801_0000, 0x0802_0000, 0x0804_0000, 0x0806_0000, 15 | 0x0808_0000, 0x080A_0000, 0x080C_0000, 0x080E_0000]; 16 | /// Final valid address in flash 17 | pub const FLASH_END: u32 = 0x080F_FFFF; 18 | /// Address of configuration sector. Must be one of the start addresses in FLASH_SECTOR_ADDRESSES. 19 | pub const FLASH_CONFIG: u32 = FLASH_SECTOR_ADDRESSES[3]; 20 | /// Address of user firmware sector. Must be one of the start addresses in FLASH_SECTOR_ADDRESSES. 21 | pub const FLASH_USER: u32 = FLASH_SECTOR_ADDRESSES[4]; 22 | 23 | /// Magic value used in this module to check if bootloader should start. 24 | pub const BOOTLOAD_FLAG_VALUE: u32 = 0xB00110AD; 25 | /// Address of magic value used in this module to check if bootloader should start. 26 | pub const BOOTLOAD_FLAG_ADDRESS: u32 = 0x2000_0000; 27 | 28 | /// This function should return true if the bootloader should enter bootload mode, 29 | /// or false to immediately chainload the user firmware. 30 | /// 31 | /// By default we check if there was a software reset and a magic value is set in RAM, 32 | /// but you could also check GPIOs etc here. 33 | /// 34 | /// Ensure any state change to the peripherals is reset before returning from this function. 35 | pub fn should_enter_bootloader(peripherals: &mut stm32f407::Peripherals) -> bool { 36 | // Our plan is: 37 | // * If the reset was a software reset, and the magic flag is in the magic location, 38 | // then the user firmware requested bootload, so enter bootload. 39 | // * Otherwise we check if PD2 is LOW for at least a full byte period of the UART, 40 | // indicating someone has connected 3V to the external connector. 41 | let cond1 = bootload::was_software_reset(&mut peripherals.RCC) && bootload::flag_set(); 42 | 43 | peripherals.RCC.ahb1enr.modify(|_, w| w.gpioden().enabled()); 44 | peripherals.GPIOD.moder.modify(|_, w| w.moder2().input()); 45 | 46 | let hsi_clk = 16_000_000; 47 | let sync_baud = 1_000_000; 48 | let bit_periods = 10; 49 | let delay = (hsi_clk / sync_baud) * bit_periods; 50 | let mut cond2 = true; 51 | for _ in 0..delay { 52 | cond2 &= peripherals.GPIOD.idr.read().idr2().bit_is_clear(); 53 | } 54 | 55 | peripherals.RCC.ahb1enr.modify(|_, w| w.gpioden().disabled()); 56 | cond1 || cond2 57 | } 58 | 59 | /// Set up GPIOs for ethernet. 60 | /// 61 | /// You should enable 9 GPIOs used by the ethernet controller. All GPIO clocks are already enabled. 62 | /// This is also a sensible place to turn on an LED or similar to indicate bootloader mode. 63 | pub fn configure_gpio(peripherals: &mut stm32f407::Peripherals) { 64 | let gpioa = &peripherals.GPIOA; 65 | let gpiob = &peripherals.GPIOB; 66 | let gpioc = &peripherals.GPIOC; 67 | let gpioe = &peripherals.GPIOE; 68 | 69 | // Status LED 70 | gpioe.moder.modify(|_, w| w.moder7().output()); 71 | gpioe.odr.modify(|_, w| w.odr7().clear_bit()); 72 | 73 | // Configure ethernet related GPIO: 74 | // GPIOA 1, 2, 7 75 | // GPIOC 1, 4, 5 76 | // GPIOG 11, 13, 14 77 | // All set to AF11 and very high speed. 78 | gpioa.moder.modify(|_, w| 79 | w.moder1().alternate() 80 | .moder2().alternate() 81 | .moder7().alternate()); 82 | gpiob.moder.modify(|_, w| 83 | w.moder11().alternate() 84 | .moder12().alternate() 85 | .moder13().alternate()); 86 | gpioc.moder.modify(|_, w| 87 | w.moder1().alternate() 88 | .moder4().alternate() 89 | .moder5().alternate()); 90 | gpioa.ospeedr.modify(|_, w| 91 | w.ospeedr1().very_high_speed() 92 | .ospeedr2().very_high_speed() 93 | .ospeedr7().very_high_speed()); 94 | gpiob.ospeedr.modify(|_, w| 95 | w.ospeedr11().very_high_speed() 96 | .ospeedr12().very_high_speed() 97 | .ospeedr13().very_high_speed()); 98 | gpioc.ospeedr.modify(|_, w| 99 | w.ospeedr1().very_high_speed() 100 | .ospeedr4().very_high_speed() 101 | .ospeedr5().very_high_speed()); 102 | gpioa.afrl.modify(|_, w| 103 | w.afrl1().af11() 104 | .afrl2().af11() 105 | .afrl7().af11()); 106 | gpiob.afrh.modify(|_, w| 107 | w.afrh11().af11() 108 | .afrh12().af11() 109 | .afrh13().af11()); 110 | gpioc.afrl.modify(|_, w| 111 | w.afrl1().af11() 112 | .afrl4().af11() 113 | .afrl5().af11()); 114 | } 115 | -------------------------------------------------------------------------------- /src/ethernet.rs: -------------------------------------------------------------------------------- 1 | use core; 2 | use cortex_m; 3 | use stm32f407; 4 | 5 | use smoltcp::{self, phy::{self, DeviceCapabilities}, time::Instant, wire::EthernetAddress}; 6 | 7 | const ETH_BUF_SIZE: usize = 1536; 8 | const ETH_NUM_TD: usize = 4; 9 | const ETH_NUM_RD: usize = 4; 10 | 11 | use ::config::ETH_PHY_ADDR; 12 | 13 | /// Transmit Descriptor representation 14 | /// 15 | /// * tdes0: ownership bit and transmit settings 16 | /// * tdes1: transmit buffer lengths 17 | /// * tdes2: transmit buffer address 18 | /// * tdes3: not used 19 | /// 20 | /// Note that Copy and Clone are derived to support initialising an array of TDes, 21 | /// but you may not move a TDes after its address has been given to the ETH_DMA engine. 22 | #[derive(Copy,Clone)] 23 | #[repr(C,packed)] 24 | struct TDes { 25 | tdes0: u32, 26 | tdes1: u32, 27 | tdes2: u32, 28 | tdes3: u32, 29 | } 30 | 31 | impl TDes { 32 | /// Initialises this TDes to point at the given buffer. 33 | pub fn init(&mut self, tdbuf: &[u32]) { 34 | // Set FS and LS on each descriptor: each will hold a single full segment. 35 | self.tdes0 = (1<<29) | (1<<28); 36 | // Store pointer to associated buffer. 37 | self.tdes2 = tdbuf.as_ptr() as u32; 38 | // No second buffer. 39 | self.tdes3 = 0; 40 | } 41 | 42 | /// Mark this TDes as end-of-ring. 43 | pub fn set_end_of_ring(&mut self) { 44 | self.tdes0 |= 1<<21; 45 | } 46 | 47 | /// Return true if the RDes is not currently owned by the DMA 48 | pub fn available(&self) -> bool { 49 | self.tdes0 & (1<<31) == 0 50 | } 51 | 52 | /// Release this RDes back to DMA engine for transmission 53 | pub unsafe fn release(&mut self) { 54 | self.tdes0 |= 1<<31; 55 | } 56 | 57 | /// Set the length of data in the buffer pointed to by this TDes 58 | pub unsafe fn set_length(&mut self, length: usize) { 59 | self.tdes1 = (length as u32) & 0x1FFF; 60 | } 61 | 62 | /// Access the buffer pointed to by this descriptor 63 | pub unsafe fn buf_as_slice_mut(&self) -> &mut [u8] { 64 | core::slice::from_raw_parts_mut(self.tdes2 as *mut _, self.tdes1 as usize & 0x1FFF) 65 | } 66 | } 67 | 68 | /// Store a ring of TDes and associated buffers 69 | struct TDesRing { 70 | td: [TDes; ETH_NUM_TD], 71 | tbuf: [[u32; ETH_BUF_SIZE/4]; ETH_NUM_TD], 72 | tdidx: usize, 73 | } 74 | 75 | static mut TDESRING: TDesRing = TDesRing { 76 | td: [TDes { tdes0: 0, tdes1: 0, tdes2: 0, tdes3: 0 }; ETH_NUM_TD], 77 | tbuf: [[0; ETH_BUF_SIZE/4]; ETH_NUM_TD], 78 | tdidx: 0, 79 | }; 80 | 81 | impl TDesRing { 82 | /// Initialise this TDesRing 83 | /// 84 | /// The current memory address of the buffers inside this TDesRing will be stored in the 85 | /// descriptors, so ensure the TDesRing is not moved after initialisation. 86 | pub fn init(&mut self) { 87 | for (td, tdbuf) in self.td.iter_mut().zip(self.tbuf.iter()) { 88 | td.init(&tdbuf[..]); 89 | } 90 | self.td.last_mut().unwrap().set_end_of_ring(); 91 | } 92 | 93 | /// Return the address of the start of the TDes ring 94 | pub fn ptr(&self) -> *const TDes { 95 | self.td.as_ptr() 96 | } 97 | 98 | /// Return true if a TDes is available for use 99 | pub fn available(&self) -> bool { 100 | self.td[self.tdidx].available() 101 | } 102 | 103 | /// Return the next available TDes if any are available, otherwise None 104 | pub fn next(&mut self) -> Option<&mut TDes> { 105 | if self.available() { 106 | let rv = Some(&mut self.td[self.tdidx]); 107 | self.tdidx = (self.tdidx + 1) % ETH_NUM_TD; 108 | rv 109 | } else { 110 | None 111 | } 112 | } 113 | } 114 | 115 | /// Receive Descriptor representation 116 | /// 117 | /// * rdes0: ownership bit and received packet metadata 118 | /// * rdes1: receive buffer lengths and settings 119 | /// * rdes2: receive buffer address 120 | /// * rdes3: not used 121 | /// 122 | /// Note that Copy and Clone are derived to support initialising an array of TDes, 123 | /// but you may not move a TDes after its address has been given to the ETH_DMA engine. 124 | #[derive(Copy,Clone)] 125 | #[repr(C,packed)] 126 | struct RDes { 127 | rdes0: u32, 128 | rdes1: u32, 129 | rdes2: u32, 130 | rdes3: u32, 131 | } 132 | 133 | impl RDes { 134 | /// Initialises this RDes to point at the given buffer. 135 | pub fn init(&mut self, rdbuf: &[u32]) { 136 | // Mark each RDes as owned by the DMA engine. 137 | self.rdes0 = 1<<31; 138 | // Store length of and pointer to associated buffer. 139 | self.rdes1 = rdbuf.len() as u32 * 4; 140 | self.rdes2 = rdbuf.as_ptr() as u32; 141 | // No second buffer. 142 | self.rdes3 = 0; 143 | } 144 | 145 | /// Mark this RDes as end-of-ring. 146 | pub fn set_end_of_ring(&mut self) { 147 | self.rdes1 |= 1<<15; 148 | } 149 | 150 | /// Return true if the RDes is not currently owned by the DMA 151 | pub fn available(&self) -> bool { 152 | self.rdes0 & (1<<31) == 0 153 | } 154 | 155 | /// Release this RDes back to the DMA engine 156 | pub unsafe fn release(&mut self) { 157 | self.rdes0 |= 1<<31; 158 | } 159 | 160 | /// Access the buffer pointed to by this descriptor 161 | pub unsafe fn buf_as_slice_mut(&self) -> &mut [u8] { 162 | core::slice::from_raw_parts_mut(self.rdes2 as *mut _, (self.rdes0 >> 16) as usize & 0x3FFF) 163 | } 164 | } 165 | 166 | /// Store a ring of RDes and associated buffers 167 | struct RDesRing { 168 | rd: [RDes; ETH_NUM_RD], 169 | rbuf: [[u32; ETH_BUF_SIZE/4]; ETH_NUM_RD], 170 | rdidx: usize, 171 | } 172 | 173 | static mut RDESRING: RDesRing = RDesRing { 174 | rd: [RDes { rdes0: 0, rdes1: 0, rdes2: 0, rdes3: 0 }; ETH_NUM_RD], 175 | rbuf: [[0; ETH_BUF_SIZE/4]; ETH_NUM_RD], 176 | rdidx: 0, 177 | }; 178 | 179 | impl RDesRing { 180 | /// Initialise this RDesRing 181 | /// 182 | /// The current memory address of the buffers inside this TDesRing will be stored in the 183 | /// descriptors, so ensure the TDesRing is not moved after initialisation. 184 | pub fn init(&mut self) { 185 | for (rd, rdbuf) in self.rd.iter_mut().zip(self.rbuf.iter()) { 186 | rd.init(&rdbuf[..]); 187 | } 188 | self.rd.last_mut().unwrap().set_end_of_ring(); 189 | } 190 | 191 | /// Return the address of the start of the RDes ring 192 | pub fn ptr(&self) -> *const RDes { 193 | self.rd.as_ptr() 194 | } 195 | 196 | /// Return true if a RDes is available for use 197 | pub fn available(&self) -> bool { 198 | self.rd[self.rdidx].available() 199 | } 200 | 201 | /// Return the next available RDes if any are available, otherwise None 202 | pub fn next(&mut self) -> Option<&mut RDes> { 203 | if self.available() { 204 | let rv = Some(&mut self.rd[self.rdidx]); 205 | self.rdidx = (self.rdidx + 1) % ETH_NUM_RD; 206 | rv 207 | } else { 208 | None 209 | } 210 | } 211 | } 212 | 213 | /// Ethernet device driver 214 | pub struct EthernetDevice { 215 | rdring: &'static mut RDesRing, 216 | tdring: &'static mut TDesRing, 217 | eth_mac: stm32f407::ETHERNET_MAC, 218 | eth_dma: stm32f407::ETHERNET_DMA, 219 | } 220 | 221 | static mut BUFFERS_USED: bool = false; 222 | 223 | impl EthernetDevice { 224 | /// Create a new uninitialised EthernetDevice. 225 | /// 226 | /// You must move in ETH_MAC, ETH_DMA, and they are then kept by the device. 227 | /// 228 | /// You may only call this function once; subsequent calls will panic. 229 | pub fn new(eth_mac: stm32f407::ETHERNET_MAC, eth_dma: stm32f407::ETHERNET_DMA) 230 | -> EthernetDevice { 231 | cortex_m::interrupt::free(|_| unsafe { 232 | if BUFFERS_USED { 233 | panic!("EthernetDevice already created"); 234 | } 235 | BUFFERS_USED = true; 236 | EthernetDevice { rdring: &mut RDESRING, tdring: &mut TDESRING, eth_mac, eth_dma } 237 | }) 238 | } 239 | 240 | /// Initialise the ethernet driver. 241 | /// 242 | /// Sets up the descriptor structures, sets up the peripheral clocks and GPIO configuration, 243 | /// and configures the ETH MAC and DMA peripherals. 244 | /// 245 | /// Brings up the PHY and then blocks waiting for a network link. 246 | pub fn init(&mut self, rcc: &mut stm32f407::RCC, addr: EthernetAddress) { 247 | self.tdring.init(); 248 | self.rdring.init(); 249 | 250 | self.init_peripherals(rcc, addr); 251 | 252 | self.phy_reset(); 253 | self.phy_init(); 254 | } 255 | 256 | pub fn link_established(&mut self) -> bool { 257 | return self.phy_poll_link() 258 | } 259 | 260 | pub fn block_until_link(&mut self) { 261 | while !self.link_established() {} 262 | } 263 | 264 | /// Resume suspended TX DMA operation 265 | pub fn resume_tx_dma(&mut self) { 266 | if self.eth_dma.dmasr.read().tps().is_suspended() { 267 | self.eth_dma.dmatpdr.write(|w| w.tpd().poll()); 268 | } 269 | } 270 | 271 | /// Resume suspended RX DMA operation 272 | pub fn resume_rx_dma(&mut self) { 273 | if self.eth_dma.dmasr.read().rps().is_suspended() { 274 | self.eth_dma.dmarpdr.write(|w| w.rpd().poll()); 275 | } 276 | } 277 | 278 | /// Sets up the device peripherals. 279 | fn init_peripherals(&mut self, rcc: &mut stm32f407::RCC, mac: EthernetAddress) { 280 | // Reset ETH_MAC and ETH_DMA 281 | rcc.ahb1rstr.modify(|_, w| w.ethmacrst().reset()); 282 | rcc.ahb1rstr.modify(|_, w| w.ethmacrst().clear_bit()); 283 | self.eth_dma.dmabmr.modify(|_, w| w.sr().reset()); 284 | while self.eth_dma.dmabmr.read().sr().is_reset() {} 285 | 286 | // Set MAC address 287 | let mac = mac.as_bytes(); 288 | self.eth_mac.maca0lr.write(|w| w.maca0l().bits( 289 | (mac[0] as u32) << 0 | (mac[1] as u32) << 8 | 290 | (mac[2] as u32) <<16 | (mac[3] as u32) <<24)); 291 | self.eth_mac.maca0hr.write(|w| w.maca0h().bits( 292 | (mac[4] as u16) << 0 | (mac[5] as u16) << 8)); 293 | 294 | // Enable RX and TX. We'll set link speed and duplex at link-up. 295 | self.eth_mac.maccr.write(|w| 296 | w.re().enabled() 297 | .te().enabled() 298 | .cstf().enabled() 299 | ); 300 | 301 | // Tell the ETH DMA the start of each ring 302 | self.eth_dma.dmatdlar.write(|w| w.stl().bits(self.tdring.ptr() as u32)); 303 | self.eth_dma.dmardlar.write(|w| w.srl().bits(self.rdring.ptr() as u32)); 304 | 305 | // Set DMA bus mode 306 | self.eth_dma.dmabmr.modify(|_, w| 307 | w.aab().aligned() 308 | .pbl().pbl1() 309 | ); 310 | 311 | // Flush TX FIFO 312 | self.eth_dma.dmaomr.write(|w| w.ftf().flush()); 313 | while self.eth_dma.dmaomr.read().ftf().is_flush() {} 314 | 315 | // Set DMA operation mode to store-and-forward and start DMA 316 | self.eth_dma.dmaomr.write(|w| 317 | w.rsf().store_forward() 318 | .tsf().store_forward() 319 | .st().started() 320 | .sr().started() 321 | ); 322 | } 323 | 324 | /// Read a register over SMI. 325 | fn smi_read(&mut self, reg: u8) -> u16 { 326 | // Use PHY address 00000, set register address, set clock to HCLK/102, start read. 327 | self.eth_mac.macmiiar.write(|w| 328 | w.mb().busy() 329 | .pa().bits(ETH_PHY_ADDR) 330 | .cr().cr_150_168() 331 | .mr().bits(reg) 332 | ); 333 | 334 | // Wait for read 335 | while self.eth_mac.macmiiar.read().mb().is_busy() {} 336 | 337 | // Return result 338 | self.eth_mac.macmiidr.read().md().bits() 339 | } 340 | 341 | /// Write a register over SMI. 342 | fn smi_write(&mut self, reg: u8, val: u16) { 343 | // Use PHY address 00000, set write data, set register address, set clock to HCLK/102, 344 | // start write operation. 345 | self.eth_mac.macmiidr.write(|w| w.md().bits(val)); 346 | self.eth_mac.macmiiar.write(|w| 347 | w.mb().busy() 348 | .pa().bits(ETH_PHY_ADDR) 349 | .mw().write() 350 | .cr().cr_150_168() 351 | .mr().bits(reg) 352 | ); 353 | 354 | while self.eth_mac.macmiiar.read().mb().is_busy() {} 355 | } 356 | 357 | /// Reset the connected PHY and wait for it to come out of reset. 358 | fn phy_reset(&mut self) { 359 | self.smi_write(0x00, 1<<15); 360 | while self.smi_read(0x00) & (1<<15) == (1<<15) {} 361 | } 362 | 363 | /// Command connected PHY to initialise. 364 | fn phy_init(&mut self) { 365 | self.smi_write(0x00, 1<<12); 366 | } 367 | 368 | /// Poll PHY to determine link status. 369 | fn phy_poll_link(&mut self) -> bool { 370 | let bsr = self.smi_read(0x01); 371 | let bcr = self.smi_read(0x00); 372 | let lpa = self.smi_read(0x05); 373 | 374 | // No link without autonegotiate 375 | if bcr & (1<<12) == 0 { return false; } 376 | // No link if link is down 377 | if bsr & (1<< 2) == 0 { return false; } 378 | // No link if remote fault 379 | if bsr & (1<< 4) != 0 { return false; } 380 | // No link if autonegotiate incomplete 381 | if bsr & (1<< 5) == 0 { return false; } 382 | // No link if other side can't do 100Mbps full duplex 383 | if lpa & (1<< 8) == 0 { return false; } 384 | 385 | // Got link. Configure MAC to 100Mbit/s and full duplex. 386 | self.eth_mac.maccr.modify(|_, w| 387 | w.fes().fes100() 388 | .dm().full_duplex() 389 | ); 390 | 391 | true 392 | } 393 | } 394 | 395 | pub struct TxToken(*mut EthernetDevice); 396 | pub struct RxToken(*mut EthernetDevice); 397 | 398 | impl phy::TxToken for TxToken { 399 | fn consume(self, _timestamp: Instant, len: usize, f: F) -> smoltcp::Result 400 | where F: FnOnce(&mut [u8]) -> smoltcp::Result 401 | { 402 | // There can only be a single EthernetDevice and therefore all TxTokens are wrappers 403 | // to a raw pointer to it. Unsafe required to dereference this pointer and call 404 | // the various TDes methods. 405 | assert!(len <= ETH_BUF_SIZE); 406 | unsafe { 407 | let tdes = (*self.0).tdring.next().unwrap(); 408 | tdes.set_length(len); 409 | let result = f(tdes.buf_as_slice_mut()); 410 | tdes.release(); 411 | (*self.0).resume_tx_dma(); 412 | result 413 | } 414 | } 415 | } 416 | 417 | impl phy::RxToken for RxToken { 418 | fn consume(self, _timestamp: Instant, f: F) -> smoltcp::Result 419 | where F: FnOnce(&mut [u8]) -> smoltcp::Result 420 | { 421 | // There can only be a single EthernetDevice and therefore all RxTokens are wrappers 422 | // to a raw pointer to it. Unsafe required to dereference this pointer and call 423 | // the various RDes methods. 424 | unsafe { 425 | let rdes = (*self.0).rdring.next().unwrap(); 426 | let result = f(rdes.buf_as_slice_mut()); 427 | rdes.release(); 428 | (*self.0).resume_rx_dma(); 429 | result 430 | } 431 | } 432 | } 433 | 434 | // Implement the smoltcp Device interface 435 | impl<'a> phy::Device<'a> for EthernetDevice { 436 | type RxToken = RxToken; 437 | type TxToken = TxToken; 438 | 439 | fn capabilities(&self) -> DeviceCapabilities { 440 | let mut caps = DeviceCapabilities::default(); 441 | caps.max_transmission_unit = 1500; 442 | caps.max_burst_size = Some(core::cmp::min(ETH_NUM_TD, ETH_NUM_RD)); 443 | caps 444 | } 445 | 446 | fn receive(&mut self) -> Option<(RxToken, TxToken)> { 447 | if self.rdring.available() && self.tdring.available() { 448 | Some((RxToken(self), TxToken(self))) 449 | } else { 450 | None 451 | } 452 | } 453 | 454 | fn transmit(&mut self) -> Option { 455 | if self.tdring.available() { 456 | Some(TxToken(self)) 457 | } else { 458 | None 459 | } 460 | } 461 | } 462 | -------------------------------------------------------------------------------- /src/flash.rs: -------------------------------------------------------------------------------- 1 | use core; 2 | use stm32f407; 3 | 4 | use ufmt::uwrite; 5 | use ::{Error, Result}; 6 | 7 | 8 | const CONFIG_MAGIC: u32 = 0x67797870; 9 | 10 | use ::config::{FLASH_SECTOR_ADDRESSES, FLASH_END, FLASH_CONFIG, FLASH_USER}; 11 | 12 | 13 | static mut FLASH: Option = None; 14 | 15 | /// Call to move the flash peripheral into this module 16 | pub fn init(flash: stm32f407::FLASH) { 17 | unsafe { FLASH = Some(flash) }; 18 | } 19 | 20 | /// User configuration. Must live in flash at FLASH_CONFIG, 0x0800_C000. 21 | /// `magic` must be set to 0x67797870. `checksum` must be the CRC32 of the preceeding bytes. 22 | #[derive(Copy,Clone)] 23 | #[repr(C,packed)] 24 | pub struct UserConfig { 25 | magic: u32, 26 | pub mac_address: [u8; 6], 27 | pub ip_address: [u8; 4], 28 | pub ip_gateway: [u8; 4], 29 | pub ip_prefix: u8, 30 | _padding: [u8; 1], 31 | checksum: u32, 32 | } 33 | 34 | impl UserConfig { 35 | pub fn write_to_semihosting(&self) { 36 | if unsafe { (*cortex_m::peripheral::DCB::ptr()).dhcsr.read() & 1 == 0 } { return; } 37 | let mut stdout = match cortex_m_semihosting::hio::hstdout() { 38 | Ok(stdout) => stdout, 39 | Err(_) => { return; }, 40 | }; 41 | let mut stdout = WriteAdapter(&mut stdout); 42 | let mut hexbuf = [0u8; 2]; 43 | uwrite!(stdout, " MAC Address: ",).ok(); 44 | uwrite!(stdout, "{}:", u8_to_hex(self.mac_address[0], &mut hexbuf)).ok(); 45 | uwrite!(stdout, "{}:", u8_to_hex(self.mac_address[1], &mut hexbuf)).ok(); 46 | uwrite!(stdout, "{}:", u8_to_hex(self.mac_address[2], &mut hexbuf)).ok(); 47 | uwrite!(stdout, "{}:", u8_to_hex(self.mac_address[3], &mut hexbuf)).ok(); 48 | uwrite!(stdout, "{}:", u8_to_hex(self.mac_address[4], &mut hexbuf)).ok(); 49 | uwrite!(stdout, "{}\n", u8_to_hex(self.mac_address[5], &mut hexbuf)).ok(); 50 | uwrite!(stdout, " IP Address: {}.{}.{}.{}/{}\n", 51 | self.ip_address[0], self.ip_address[1], self.ip_address[2], self.ip_address[3], 52 | self.ip_prefix).ok(); 53 | uwrite!(stdout, " Gateway: {}.{}.{}.{}\n", 54 | self.ip_gateway[0], self.ip_gateway[1], self.ip_gateway[2], 55 | self.ip_gateway[3]).ok(); 56 | uwrite!(stdout, " Checksum: {}\n", self.checksum as u32).ok(); 57 | } 58 | } 59 | 60 | struct WriteAdapter(pub W) where W: core::fmt::Write; 61 | 62 | impl ufmt::uWrite for WriteAdapter where W: core::fmt::Write { 63 | type Error = core::fmt::Error; 64 | 65 | fn write_char(&mut self, c: char) -> core::result::Result<(), Self::Error> { 66 | self.0.write_char(c) 67 | } 68 | 69 | fn write_str(&mut self, s: &str) -> core::result::Result<(), Self::Error> { 70 | self.0.write_str(s) 71 | } 72 | } 73 | 74 | fn u8_to_hex(x: u8, buf: &mut [u8]) -> &str { 75 | static HEX_DIGITS: [u8; 16] = [ 76 | 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 77 | 65, 66, 67, 68, 69, 70, 78 | ]; 79 | let v1 = x & 0x0F; 80 | let v2 = (x & 0xF0) >> 4; 81 | buf[0] = HEX_DIGITS[v2 as usize]; 82 | buf[1] = HEX_DIGITS[v1 as usize]; 83 | unsafe { core::str::from_utf8_unchecked(buf) } 84 | } 85 | 86 | pub static DEFAULT_CONFIG: UserConfig = UserConfig { 87 | // Locally administered MAC 88 | magic: 0, 89 | mac_address: [0x02, 0x00, 0x01, 0x02, 0x03, 0x04], 90 | ip_address: [10, 1, 1, 10], 91 | ip_gateway: [10, 1, 1, 1], 92 | ip_prefix: 24, 93 | _padding: [0u8; 1], 94 | checksum: 0, 95 | }; 96 | 97 | impl UserConfig { 98 | /// Attempt to read the UserConfig from flash sector 3 at 0x0800_C000. 99 | /// If a valid config cannot be read, the default one is returned instead. 100 | pub fn get(crc: &mut stm32f407::CRC) -> Option { 101 | // Read config from flash 102 | let adr = FLASH_CONFIG as *const u32; 103 | let cfg = unsafe { *(FLASH_CONFIG as *const UserConfig) }; 104 | 105 | // First check magic is correct 106 | if cfg.magic != CONFIG_MAGIC { 107 | return None; 108 | } 109 | 110 | // Validate checksum 111 | let len = core::mem::size_of::() / 4; 112 | crc.cr.write(|w| w.reset().reset()); 113 | for idx in 0..(len - 1) { 114 | let val = unsafe { *(adr.offset(idx as isize)) }; 115 | crc.dr.write(|w| w.dr().bits(val)); 116 | } 117 | let crc_computed = crc.dr.read().dr().bits(); 118 | 119 | if crc_computed == cfg.checksum { 120 | Some(cfg.clone()) 121 | } else { 122 | None 123 | } 124 | } 125 | } 126 | 127 | /// Try to determine if there is valid code in the user flash at 0x0801_0000. 128 | /// Returns Some(u32) with the address to jump to if so, and None if not. 129 | pub fn valid_user_code() -> Option { 130 | let reset_vector: u32 = unsafe { *((FLASH_USER + 4) as *const u32) }; 131 | if reset_vector >= FLASH_USER && reset_vector <= FLASH_END { 132 | Some(FLASH_USER) 133 | } else { 134 | None 135 | } 136 | } 137 | 138 | /// Check if address+length is valid for read/write flash. 139 | fn check_address_valid(address: u32, length: usize) -> Result<()> { 140 | if address < FLASH_CONFIG { 141 | Err(Error::InvalidAddress) 142 | } else if address > (FLASH_END - length as u32 + 1) { 143 | Err(Error::InvalidAddress) 144 | } else{ 145 | Ok(()) 146 | } 147 | } 148 | 149 | /// Check length is a multiple of 4 and no greater than 1024 150 | fn check_length_valid(length: usize) -> Result<()> { 151 | if length % 4 != 0 { 152 | Err(Error::LengthNotMultiple4) 153 | } else if length > 1024 { 154 | Err(Error::LengthTooLong) 155 | } else { 156 | Ok(()) 157 | } 158 | } 159 | 160 | /// Check the specified length matches the amount of data available 161 | fn check_length_correct(length: usize, data: &[u8]) -> Result<()> { 162 | if length != data.len() { 163 | Err(Error::DataLengthIncorrect) 164 | } else { 165 | Ok(()) 166 | } 167 | } 168 | 169 | /// Try to get the FLASH peripheral 170 | fn get_flash_peripheral() -> Result<&'static mut stm32f407::FLASH> { 171 | match unsafe { FLASH.as_mut() } { 172 | Some(flash) => Ok(flash), 173 | None => Err(Error::InternalError), 174 | } 175 | } 176 | 177 | /// Try to unlock flash 178 | fn unlock(flash: &mut stm32f407::FLASH) -> Result<()> { 179 | // Wait for any ongoing operations 180 | while flash.sr.read().bsy().bit_is_set() {} 181 | 182 | // Attempt unlock 183 | flash.keyr.write(|w| w.key().bits(0x45670123)); 184 | flash.keyr.write(|w| w.key().bits(0xCDEF89AB)); 185 | 186 | // Verify success 187 | match flash.cr.read().lock().is_unlocked() { 188 | true => Ok(()), 189 | false => Err(Error::FlashError), 190 | } 191 | } 192 | 193 | /// Lock flash 194 | fn lock(flash: &mut stm32f407::FLASH) { 195 | flash.cr.write(|w| w.lock().locked()); 196 | } 197 | 198 | /// Erase flash sectors that cover the given address and length. 199 | pub fn erase(address: u32, length: usize) -> Result<()> { 200 | check_address_valid(address, length)?; 201 | let address_start = address; 202 | let address_end = address + length as u32; 203 | for (idx, sector_start) in FLASH_SECTOR_ADDRESSES.iter().enumerate() { 204 | let sector_start = *sector_start; 205 | let sector_end = match FLASH_SECTOR_ADDRESSES.get(idx + 1) { 206 | Some(adr) => *adr - 1, 207 | None => FLASH_END, 208 | }; 209 | if (address_start >= sector_start && address_start <= sector_end) || 210 | (address_end >= sector_start && address_end <= sector_end) || 211 | (address_start <= sector_start && address_end >= sector_end) { 212 | erase_sector(idx as u8)?; 213 | } 214 | } 215 | Ok(()) 216 | } 217 | 218 | /// Erase specified sector 219 | fn erase_sector(sector: u8) -> Result<()> { 220 | if (sector as usize) >= FLASH_SECTOR_ADDRESSES.len() { 221 | return Err(Error::InternalError); 222 | } 223 | let flash = get_flash_peripheral()?; 224 | unlock(flash)?; 225 | 226 | // Erase. 227 | // UNSAFE: We've verified that `sector` Result<&'static [u8]> { 256 | check_address_valid(address, length)?; 257 | check_length_valid(length)?; 258 | let address = address as *const _; 259 | unsafe { 260 | Ok(core::slice::from_raw_parts::<'static, u8>(address, length)) 261 | } 262 | } 263 | 264 | /// Write to flash. 265 | /// Returns () on success, None on failure. 266 | /// length must be a multiple of 4. 267 | pub fn write(address: u32, length: usize, data: &[u8]) -> Result<()> { 268 | check_address_valid(address, length)?; 269 | check_length_valid(length)?; 270 | check_length_correct(length, data)?; 271 | let flash = get_flash_peripheral()?; 272 | unlock(flash)?; 273 | 274 | // Set parallelism to write in 32 bit chunks, and enable programming. 275 | // Note reset value has 1 for lock so we need to explicitly clear it. 276 | flash.cr.write(|w| w.lock().unlocked() 277 | .psize().psize32() 278 | .pg().program()); 279 | 280 | for idx in 0..(length / 4) { 281 | let offset = idx * 4; 282 | let word: u32 = 283 | (data[offset] as u32) 284 | | (data[offset+1] as u32) << 8 285 | | (data[offset+2] as u32) << 16 286 | | (data[offset+3] as u32) << 24; 287 | let write_address = (address + offset as u32) as *mut u32; 288 | unsafe { core::ptr::write_volatile(write_address, word) }; 289 | 290 | // Wait for write 291 | while flash.sr.read().bsy().bit_is_set() {} 292 | 293 | // Check for errors 294 | let sr = flash.sr.read(); 295 | if sr.pgserr().bit_is_set() || sr.pgperr().bit_is_set() || 296 | sr.pgaerr().bit_is_set() || sr.wrperr().bit_is_set() { 297 | lock(flash); 298 | return Err(Error::WriteError); 299 | } 300 | } 301 | 302 | lock(flash); 303 | 304 | Ok(()) 305 | } 306 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | extern crate cortex_m; 5 | extern crate cortex_m_rt; 6 | extern crate cortex_m_semihosting; 7 | extern crate panic_halt; 8 | extern crate stm32f4; 9 | extern crate smoltcp; 10 | extern crate ufmt; 11 | 12 | use cortex_m_rt::{entry, exception}; 13 | use stm32f4::stm32f407; 14 | 15 | 16 | #[derive(Clone, Copy, PartialEq, Eq)] 17 | pub enum Error { 18 | Success, 19 | InvalidAddress, 20 | LengthNotMultiple4, 21 | LengthTooLong, 22 | DataLengthIncorrect, 23 | EraseError, 24 | WriteError, 25 | FlashError, 26 | NetworkError, 27 | InternalError, 28 | } 29 | 30 | pub type Result = core::result::Result; 31 | 32 | /// Try to print over semihosting if a debugger is available 33 | #[macro_export] 34 | macro_rules! print { 35 | ($($arg:expr),*) => ({ 36 | if unsafe { (*cortex_m::peripheral::DCB::ptr()).dhcsr.read() & 1 == 1 } { 37 | match cortex_m_semihosting::hio::hstdout() { 38 | Ok(mut stdout) => { 39 | $( 40 | stdout.write_all($arg.as_bytes()).ok(); 41 | )* 42 | }, 43 | Err(_) => () 44 | } 45 | } 46 | }) 47 | } 48 | 49 | mod config; 50 | mod ethernet; 51 | mod network; 52 | mod flash; 53 | mod bootload; 54 | 55 | // Pull in build information (from `built` crate) 56 | mod build_info { 57 | #![allow(dead_code)] 58 | include!(concat!(env!("OUT_DIR"), "/built.rs")); 59 | } 60 | 61 | /// Set up PLL to 168MHz from 16MHz HSI 62 | fn rcc_init(peripherals: &mut stm32f407::Peripherals) { 63 | let rcc = &peripherals.RCC; 64 | let flash = &peripherals.FLASH; 65 | let syscfg = &peripherals.SYSCFG; 66 | 67 | // Reset all peripherals 68 | rcc.ahb1rstr.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); 69 | rcc.ahb1rstr.write(|w| unsafe { w.bits(0)}); 70 | rcc.ahb2rstr.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); 71 | rcc.ahb2rstr.write(|w| unsafe { w.bits(0)}); 72 | rcc.ahb3rstr.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); 73 | rcc.ahb3rstr.write(|w| unsafe { w.bits(0)}); 74 | rcc.apb1rstr.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); 75 | rcc.apb1rstr.write(|w| unsafe { w.bits(0)}); 76 | rcc.apb2rstr.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); 77 | rcc.apb2rstr.write(|w| unsafe { w.bits(0)}); 78 | 79 | // Ensure HSI is on and stable 80 | rcc.cr.modify(|_, w| w.hsion().set_bit()); 81 | while rcc.cr.read().hsion().bit_is_clear() {} 82 | 83 | // Set system clock to HSI 84 | rcc.cfgr.modify(|_, w| w.sw().hsi()); 85 | while !rcc.cfgr.read().sws().is_hsi() {} 86 | 87 | // Clear registers to reset value 88 | rcc.cr.write(|w| w.hsion().set_bit()); 89 | rcc.cfgr.write(|w| unsafe { w.bits(0) }); 90 | 91 | // Configure PLL: 16MHz /8 *168 /2, source HSI 92 | rcc.pllcfgr.write(|w| unsafe { 93 | w.pllq().bits(4) 94 | .pllsrc().hsi() 95 | .pllp().div2() 96 | .plln().bits(168) 97 | .pllm().bits(8) 98 | }); 99 | // Activate PLL 100 | rcc.cr.modify(|_, w| w.pllon().set_bit()); 101 | 102 | // Set other clock domains: PPRE2 to /2, PPRE1 to /4, HPRE to /1 103 | rcc.cfgr.modify(|_, w| 104 | w.ppre2().div2() 105 | .ppre1().div4() 106 | .hpre().div1()); 107 | 108 | // Flash setup: I$ and D$ enabled, prefetch enabled, 5 wait states (OK for 3.3V at 168MHz) 109 | flash.acr.write(|w| unsafe { 110 | w.icen().set_bit() 111 | .dcen().set_bit() 112 | .prften().set_bit() 113 | .latency().bits(5) 114 | }); 115 | 116 | // Swap system clock to PLL 117 | rcc.cfgr.modify(|_, w| w.sw().pll()); 118 | while !rcc.cfgr.read().sws().is_pll() {} 119 | 120 | // Set SYSCFG early to RMII mode 121 | rcc.apb2enr.modify(|_, w| w.syscfgen().enabled()); 122 | syscfg.pmc.modify(|_, w| w.mii_rmii_sel().set_bit()); 123 | 124 | // Set up peripheral clocks 125 | rcc.ahb1enr.modify(|_, w| 126 | w.gpioaen().enabled() 127 | .gpioben().enabled() 128 | .gpiocen().enabled() 129 | .gpioden().enabled() 130 | .gpioeen().enabled() 131 | .gpiogen().enabled() 132 | .crcen().enabled() 133 | .ethmacrxen().enabled() 134 | .ethmactxen().enabled() 135 | .ethmacen().enabled() 136 | ); 137 | } 138 | 139 | /// Set up the systick to provide a 1ms timebase 140 | fn systick_init(syst: &mut stm32f407::SYST) { 141 | syst.set_reload((168_000_000 / 8) / 1000); 142 | syst.clear_current(); 143 | syst.set_clock_source(cortex_m::peripheral::syst::SystClkSource::External); 144 | syst.enable_interrupt(); 145 | syst.enable_counter(); 146 | } 147 | 148 | #[entry] 149 | fn main() -> ! { 150 | let mut peripherals = stm32f407::Peripherals::take().unwrap(); 151 | let mut core_peripherals = stm32f407::CorePeripherals::take().unwrap(); 152 | 153 | // Jump to user code if it exists and hasn't asked us to run 154 | match flash::valid_user_code() { 155 | Some(address) => if !config::should_enter_bootloader(&mut peripherals) { 156 | bootload::bootload(&mut core_peripherals.SCB, address); 157 | }, 158 | None => (), 159 | } 160 | 161 | print!("\n|-=-=-=-=-=-=-=-=-= blethrs =-=-=-=-=-=-=-=-=-\n"); 162 | print!("| Version ", build_info::PKG_VERSION, " ", build_info::GIT_VERSION.unwrap(), "\n"); 163 | print!("| Platform ", build_info::TARGET, "\n"); 164 | print!("| Built on ", build_info::BUILT_TIME_UTC, "\n"); 165 | print!("| ", build_info::RUSTC_VERSION, "\n"); 166 | print!("|-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n\n"); 167 | 168 | print!( " Initialising clocks... "); 169 | rcc_init(&mut peripherals); 170 | print!("OK\n"); 171 | 172 | print!( " Initialising GPIOs... "); 173 | config::configure_gpio(&mut peripherals); 174 | print!("OK\n"); 175 | 176 | print!( " Reading configuration... "); 177 | let cfg = match flash::UserConfig::get(&mut peripherals.CRC) { 178 | Some(cfg) => { print!("OK\n"); cfg }, 179 | None => { 180 | print!("Err\nCouldn't read configuration, using default.\n"); 181 | flash::DEFAULT_CONFIG 182 | }, 183 | }; 184 | 185 | cfg.write_to_semihosting(); 186 | let mac_addr = smoltcp::wire::EthernetAddress::from_bytes(&cfg.mac_address); 187 | 188 | print!( " Initialising Ethernet... "); 189 | let mut ethdev = ethernet::EthernetDevice::new( 190 | peripherals.ETHERNET_MAC, peripherals.ETHERNET_DMA); 191 | ethdev.init(&mut peripherals.RCC, mac_addr.clone()); 192 | print!("OK\n"); 193 | 194 | print!( " Waiting for link... "); 195 | ethdev.block_until_link(); 196 | print!("OK\n"); 197 | 198 | print!( " Initialising network... "); 199 | let ip_addr = smoltcp::wire::Ipv4Address::from_bytes(&cfg.ip_address); 200 | let ip_cidr = smoltcp::wire::Ipv4Cidr::new(ip_addr, cfg.ip_prefix); 201 | let cidr = smoltcp::wire::IpCidr::Ipv4(ip_cidr); 202 | network::init(ethdev, mac_addr.clone(), cidr); 203 | print!("OK\n"); 204 | 205 | // Move flash peripheral into flash module 206 | flash::init(peripherals.FLASH); 207 | 208 | // Turn on STATUS LED 209 | print!(" Ready.\n\n"); 210 | 211 | // Begin periodic tasks via systick 212 | systick_init(&mut core_peripherals.SYST); 213 | 214 | loop { 215 | cortex_m::asm::wfi(); 216 | } 217 | } 218 | 219 | static mut SYSTICK_TICKS: u32 = 0; 220 | static mut SYSTICK_RESET_AT: Option = None; 221 | 222 | #[exception] 223 | fn SysTick() { 224 | let ticks = unsafe { core::ptr::read_volatile(&SYSTICK_TICKS) + 1 }; 225 | unsafe { core::ptr::write_volatile(&mut SYSTICK_TICKS, ticks) }; 226 | network::poll(ticks as i64); 227 | match unsafe { core::ptr::read_volatile(&SYSTICK_RESET_AT) } { 228 | Some(reset_time) => if ticks >= reset_time { 229 | print!("Performing scheduled reset\n"); 230 | bootload::reset_bootload(); 231 | }, 232 | None => (), 233 | } 234 | } 235 | 236 | /// Reset after some ms delay. 237 | pub fn schedule_reset(delay: u32) { 238 | cortex_m::interrupt::free(|_| unsafe { 239 | let ticks = core::ptr::read_volatile(&SYSTICK_TICKS) + delay; 240 | core::ptr::write_volatile(&mut SYSTICK_RESET_AT, Some(ticks)); 241 | }); 242 | } 243 | 244 | #[exception] 245 | fn HardFault(ef: &cortex_m_rt::ExceptionFrame) -> ! { 246 | panic!("HardFault at {:#?}", ef); 247 | } 248 | 249 | #[exception] 250 | fn DefaultHandler(irqn: i16) { 251 | panic!("Unhandled exception (IRQn = {})", irqn); 252 | } 253 | -------------------------------------------------------------------------------- /src/network.rs: -------------------------------------------------------------------------------- 1 | use smoltcp; 2 | use smoltcp::time::Instant; 3 | use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr}; 4 | use smoltcp::iface::{Neighbor, NeighborCache, EthernetInterface, EthernetInterfaceBuilder}; 5 | use smoltcp::socket::{SocketSet, SocketSetItem, SocketHandle, TcpSocket, TcpSocketBuffer}; 6 | 7 | use cortex_m; 8 | 9 | use ::flash; 10 | use ::build_info; 11 | use ::Error; 12 | use ethernet::EthernetDevice; 13 | 14 | const CMD_INFO: u32 = 0; 15 | const CMD_READ: u32 = 1; 16 | const CMD_ERASE: u32 = 2; 17 | const CMD_WRITE: u32 = 3; 18 | const CMD_BOOT: u32 = 4; 19 | 20 | use ::config::TCP_PORT; 21 | 22 | /// Read an address and length from the socket 23 | fn read_adr_len(socket: &mut TcpSocket) -> (u32, usize) { 24 | let mut adr = [0u8; 4]; 25 | let mut len = [0u8; 4]; 26 | socket.recv_slice(&mut adr[..]).ok(); 27 | socket.recv_slice(&mut len[..]).ok(); 28 | let adr = u32::from_le_bytes(adr); 29 | let len = u32::from_le_bytes(len); 30 | (adr, len as usize) 31 | } 32 | 33 | /// Send a status word back at the start of a response 34 | fn send_status(socket: &mut TcpSocket, status: ::Error) { 35 | let resp = (status as u32).to_le_bytes(); 36 | socket.send_slice(&resp).unwrap(); 37 | } 38 | 39 | /// Read device unique ID, return as array of 24 ASCII hex digits 40 | pub fn get_hex_id() -> [u8; 24] { 41 | static HEX_DIGITS: [u8; 16] = [ 42 | 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 43 | 65, 66, 67, 68, 69, 70, 44 | ]; 45 | let id1: [u8; 4] = unsafe { *(0x1FFF_7A10 as *const u32) }.to_le_bytes(); 46 | let id2: [u8; 4] = unsafe { *(0x1FFF_7A14 as *const u32) }.to_le_bytes(); 47 | let id3: [u8; 4] = unsafe { *(0x1FFF_7A18 as *const u32) }.to_le_bytes(); 48 | let id = [ 49 | id3[3], id3[2], id3[1], id3[0], 50 | id2[3], id2[2], id2[1], id2[0], 51 | id1[3], id1[2], id1[1], id1[0], 52 | ]; 53 | let mut out = [0u8; 24]; 54 | for (idx, v) in id.iter().enumerate() { 55 | let v1 = v & 0x0F; 56 | let v2 = (v & 0xF0) >> 4; 57 | out[idx*2 ] = HEX_DIGITS[v2 as usize]; 58 | out[idx*2+1] = HEX_DIGITS[v1 as usize]; 59 | } 60 | out 61 | } 62 | 63 | /// Respond to the information request command with our build information. 64 | fn cmd_info(socket: &mut TcpSocket) { 65 | 66 | send_status(socket, Error::Success); 67 | 68 | socket.send_slice("blethrs ".as_bytes()).ok(); 69 | socket.send_slice(build_info::PKG_VERSION.as_bytes()).ok(); 70 | socket.send_slice(" ".as_bytes()).ok(); 71 | socket.send_slice(build_info::GIT_VERSION.unwrap().as_bytes()).ok(); 72 | socket.send_slice("\r\nBuilt: ".as_bytes()).ok(); 73 | socket.send_slice(build_info::BUILT_TIME_UTC.as_bytes()).ok(); 74 | socket.send_slice("\r\nCompiler: ".as_bytes()).ok(); 75 | socket.send_slice(build_info::RUSTC_VERSION.as_bytes()).ok(); 76 | socket.send_slice("\r\nMCU ID: ".as_bytes()).ok(); 77 | socket.send_slice(&get_hex_id()).ok(); 78 | socket.send_slice("\r\n".as_bytes()).ok(); 79 | } 80 | 81 | fn cmd_read(socket: &mut TcpSocket) { 82 | let (adr, len) = read_adr_len(socket); 83 | match flash::read(adr, len) { 84 | Ok(data) => { 85 | send_status(socket, Error::Success); 86 | socket.send_slice(data).unwrap(); 87 | }, 88 | Err(err) => send_status(socket, err), 89 | }; 90 | } 91 | 92 | fn cmd_erase(socket: &mut TcpSocket) { 93 | let (adr, len) = read_adr_len(socket); 94 | match flash::erase(adr, len) { 95 | Ok(()) => send_status(socket, Error::Success), 96 | Err(err) => send_status(socket, err), 97 | } 98 | } 99 | 100 | fn cmd_write(socket: &mut TcpSocket) { 101 | let (adr, len) = read_adr_len(socket); 102 | match socket.recv(|buf| (buf.len(), flash::write(adr, len, buf))) { 103 | Ok(Ok(())) => send_status(socket, Error::Success), 104 | Ok(Err(err)) => send_status(socket, err), 105 | Err(_) => send_status(socket, Error::NetworkError), 106 | } 107 | } 108 | 109 | fn cmd_boot(socket: &mut TcpSocket) { 110 | send_status(socket, Error::Success); 111 | ::schedule_reset(50); 112 | } 113 | 114 | // Stores the underlying data buffers. If these were included in Network, 115 | // they couldn't live in BSS and therefore take up a load of flash space. 116 | struct NetworkBuffers { 117 | tcp_tx_buf: [u8; 1536], 118 | tcp_rx_buf: [u8; 1536], 119 | } 120 | 121 | static mut NETWORK_BUFFERS: NetworkBuffers = NetworkBuffers { 122 | tcp_tx_buf: [0u8; 1536], 123 | tcp_rx_buf: [0u8; 1536], 124 | }; 125 | 126 | // Stores all the smoltcp required structs. 127 | pub struct Network<'a> { 128 | neighbor_cache_storage: [Option<(IpAddress, Neighbor)>; 16], 129 | ip_addr: Option<[IpCidr; 1]>, 130 | eth_iface: Option>, 131 | sockets_storage: [Option>; 1], 132 | sockets: Option>, 133 | tcp_handle: Option, 134 | initialised: bool, 135 | } 136 | 137 | static mut NETWORK: Network = Network { 138 | neighbor_cache_storage: [None; 16], 139 | ip_addr: None, 140 | eth_iface: None, 141 | sockets_storage: [None], 142 | sockets: None, 143 | tcp_handle: None, 144 | initialised: false, 145 | }; 146 | 147 | /// Initialise the static NETWORK. 148 | /// 149 | /// Sets up the required EthernetInterface and sockets. 150 | /// 151 | /// Do not call more than once or this function will panic. 152 | pub fn init<'a>(eth_dev: EthernetDevice, mac_addr: EthernetAddress, ip_addr: IpCidr) { 153 | // Unsafe required for access to NETWORK. 154 | // NETWORK.initialised guards against calling twice. 155 | unsafe { 156 | cortex_m::interrupt::free(|_| { 157 | if NETWORK.initialised { 158 | panic!("NETWORK already initialised"); 159 | } 160 | NETWORK.initialised = true; 161 | }); 162 | 163 | let neighbor_cache = NeighborCache::new(&mut NETWORK.neighbor_cache_storage.as_mut()[..]); 164 | 165 | NETWORK.ip_addr = Some([ip_addr]); 166 | NETWORK.eth_iface = Some(EthernetInterfaceBuilder::new(eth_dev) 167 | .ethernet_addr(mac_addr) 168 | .neighbor_cache(neighbor_cache) 169 | .ip_addrs(&mut NETWORK.ip_addr.as_mut().unwrap()[..]) 170 | .finalize()); 171 | 172 | NETWORK.sockets = Some(SocketSet::new(&mut NETWORK.sockets_storage.as_mut()[..])); 173 | let tcp_rx_buf = TcpSocketBuffer::new(&mut NETWORK_BUFFERS.tcp_rx_buf.as_mut()[..]); 174 | let tcp_tx_buf = TcpSocketBuffer::new(&mut NETWORK_BUFFERS.tcp_tx_buf.as_mut()[..]); 175 | let tcp_socket = TcpSocket::new(tcp_rx_buf, tcp_tx_buf); 176 | NETWORK.tcp_handle = Some(NETWORK.sockets.as_mut().unwrap().add(tcp_socket)); 177 | } 178 | } 179 | 180 | /// Poll network stack. 181 | /// 182 | /// Arrange for this function to be called frequently. 183 | pub fn poll(time_ms: i64) { 184 | // Unsafe required to access static mut NETWORK. 185 | // Since the entire poll is run in an interrupt-free context no 186 | // other access to NETWORK can occur. 187 | cortex_m::interrupt::free(|_| unsafe { 188 | // Bail out early if NETWORK is not initialised. 189 | if !NETWORK.initialised { 190 | return; 191 | } 192 | 193 | let sockets = NETWORK.sockets.as_mut().unwrap(); 194 | 195 | // Handle TCP 196 | { 197 | let mut socket = sockets.get::(NETWORK.tcp_handle.unwrap()); 198 | if !socket.is_open() { 199 | socket.listen(TCP_PORT).unwrap(); 200 | } 201 | if !socket.may_recv() && socket.may_send() { 202 | socket.close(); 203 | } 204 | if socket.can_recv() { 205 | let mut cmd = [0u8; 4]; 206 | socket.recv_slice(&mut cmd[..]).ok(); 207 | let cmd = u32::from_le_bytes(cmd); 208 | match cmd { 209 | CMD_INFO => cmd_info(&mut socket), 210 | CMD_READ => cmd_read(&mut socket), 211 | CMD_ERASE => cmd_erase(&mut socket), 212 | CMD_WRITE => cmd_write(&mut socket), 213 | CMD_BOOT => cmd_boot(&mut socket), 214 | _ => (), 215 | }; 216 | socket.close(); 217 | } 218 | } 219 | 220 | // Poll smoltcp 221 | let timestamp = Instant::from_millis(time_ms); 222 | match NETWORK.eth_iface.as_mut().unwrap().poll(sockets, timestamp) { 223 | Ok(_) | Err(smoltcp::Error::Exhausted) => (), 224 | Err(_) => (), 225 | } 226 | }); 227 | } 228 | --------------------------------------------------------------------------------