Author: wmb Date: 2009-02-11 00:59:41 +0100 (Wed, 11 Feb 2009) New Revision: 1101
Added: cpu/x86/pc/olpc/keyject.bth cpu/x86/pc/olpc/keyject.fth cpu/x86/pc/olpc/kjversions.fth Modified: cpu/x86/pc/olpc/fw.bth cpu/x86/pc/olpc/mfgdata.fth cpu/x86/pc/olpc/security.fth cpu/x86/pc/olpc/setwp.fth dev/olpc/spiflash/spiui.fth Log: OLPC - keyjector support.
Modified: cpu/x86/pc/olpc/fw.bth =================================================================== --- cpu/x86/pc/olpc/fw.bth 2009-02-02 08:47:00 UTC (rev 1100) +++ cpu/x86/pc/olpc/fw.bth 2009-02-10 23:59:41 UTC (rev 1101) @@ -529,7 +529,6 @@ install-dual-console
?gui-banner - " probe-" do-drop-in \ ." nvramrc" cr \ use-nvramrc? if nvramrc safe-evaluate then ; @@ -603,12 +602,17 @@
read-game-keys stdout off \ + + " probe-" do-drop-in + probe-pci sound open-keyboard ?games ['] false to interrupt-auto-boot? probe-usb + " probe+" do-drop-in + interpreter-init ?scan-nand ?diags
Added: cpu/x86/pc/olpc/keyject.bth =================================================================== --- cpu/x86/pc/olpc/keyject.bth (rev 0) +++ cpu/x86/pc/olpc/keyject.bth 2009-02-10 23:59:41 UTC (rev 1101) @@ -0,0 +1,121 @@ +purpose: Construct the Open Firmware module collection + +command: &builder &this +in: ${BP}/cpu/x86/pc/olpc/build/ec.img +in: ${BP}/cpu/x86/pc/olpc/build/romreset.di +\ in: ${BP}/cpu/x86/pc/olpc/build/romstart.di +in: ${BP}/cpu/x86/pc/olpc/build/resume.di +in: ${BP}/cpu/x86/pc/olpc/build/rmstart.img +in: ${BP}/cpu/x86/pc/olpc/build/paging.di +in: ${BP}/cpu/x86/pc/olpc/build/fw.dic +in: ${BP}/dev/mmc/sdhci/build/sdhci.fc +in: ${BP}/dev/mmc/sdhci/build/sdmmc.fc +in: ${BP}/dev/geode/ac97/build/ac97.fc +in: ${BP}/dev/usb2/hcd/ohci/build/ohci.fc +in: ${BP}/dev/usb2/hcd/ehci/build/ehci.fc +in: ${BP}/dev/usb2/device/hub/build/hub.fc +in: ${BP}/dev/usb2/device/net/build/usbnet.fc +in: ${BP}/dev/usb2/device/serial/build/usbserial.fc +in: ${BP}/dev/usb2/device/storage/build/usbstorage.fc +in: ${BP}/dev/usb2/device/keyboard/build/usbkbd.fc +in: ${BP}/dev/usb2/device/wlan/build/usb8388.fc +in: ${BP}/dev/olpc/cafenand/build/cafenand.fc +in: ${BP}/dev/olpc/cafecamera/build/cafecamera.fc +in: ${BP}/clients/memtest86/memtest +in: ${BP}/clients/emacs/x86/emacs +in: ${BP}/cpu/x86/pc/olpc/build/verify.img +in: ${BP}/cpu/x86/pc/olpc/build/usb8388.bin +in: ${BP}/dev/pci/build/pcibridg.fc +in: ${BP}/cpu/x86/pc/olpc/build/dsdt.aml +in: ${BP}/cpu/x86/pc/olpc/build/mcastnand.bin + +build-now + +fload ${BP}/cpu/x86/pc/olpc/kjversions.fth + +" macro: FW_VERSION Q2${FW_MAJOR}${FW_MINOR}" expand$ eval + + +fload ${BP}/cpu/x86/pc/olpc/config.fth + +\ Always re-create the builton.fth file when we make a new rom.img +fload ${BP}/cpu/x86/pc/builton.bth +fload ${BP}/cpu/x86/pc/olpc/sourceurl.fth + +fload ${BP}/forth/lib/crc32.fth + +hex + +: pad-file ( location -- ) + ofd @ fsize + 2dup u< abort" The ROM image is too large" + ?do h# ff ofd @ fputc loop +; + +.( --- Saving as ) +" ${FW_VERSION}.rom" expand$ 2dup lower ( adr len ) +2dup type cr ( adr len ) +$new-file + " ec.img" $add-file + + dropin-base rom-pa - pad-file + + " romreset.di" $add-file +\ " romstart.di" $add-file + " resume.di" $add-file + + fload ${BP}/cpu/x86/pc/olpc/loaddropins.fth + " sourceurl" " sourceurl" $add-dropin + + " ${BP}/clients/memtest86/memtest" " memtest" $add-deflated-dropin +\ " ${BP}/cpu/x86/pc/olpc/images/olpc16EQima.wav" " splash" $add-deflated-dropin +\ " ${BP}/cpu/x86/pc/olpc/images/Edge1-16k-EQ-Comp-Amp-ima.wav" " splash" $add-deflated-dropin + " ${BP}/cpu/x86/pc/olpc/images/Edge1-8k-EQ-Comp-Amp-Short.wav" " splash" $add-deflated-dropin + " ${BP}/cpu/x86/pc/olpc/build/dsdt.aml" " dsdt" $add-deflated-dropin +\ " ${BP}/cpu/x86/pc/olpc/build/ssdt.aml" " ssdt" $add-deflated-dropin + + " ${BP}/cpu/x86/pc/olpc/build/nandblaster_rx.bin" " nb_rx" $add-deflated-dropin + " ${BP}/cpu/x86/pc/olpc/build/nandblaster_tx.bin" " nb_tx" $add-deflated-dropin +\ " ${BP}/cpu/x86/pc/olpc/build/multicast-nand/mcastnand.elf" " mcastnand" $add-deflated-dropin +\ " ${BP}/cpu/x86/pc/olpc/build/blaster.bin" " blaster" $add-deflated-dropin +\ " ${BP}/cpu/x86/pc/olpc/build/cloner.bin" " cloner" $add-deflated-dropin + + " ${BP}/cpu/x86/pc/olpc/keyject.fth" " probe+" $add-deflated-dropin + " /space/bios-crypto/build/k2.public" " o1" $add-dropin \ Incompressible + " /space/bios-crypto/build/k2.public" " s1" $add-dropin + " /space/bios-crypto/build/k2.public" " d1" $add-dropin + " /space/bios-crypto/build/k2.public" " w1" $add-dropin + " /space/bios-crypto/build/k2.public" " a1" $add-dropin + + /rom h# 400 - pad-file \ rmstart image must start 0x400 from end + " rmstart.img" $add-file + +\ Insert the revision signature +/rom h# 40 - ofd @ fseek +h# 10 buffer: signature +signature h# 10 blank +" CL1" signature swap move +" ${FW_VERSION}" expand$ signature 6 + swap move +" Q2${FW_MAJOR}" expand$ signature d# 13 + swap move +signature h# 10 ofd @ fputs + +/l buffer: crcbuf +/rom buffer: filebuf +0 ofd @ fseek + +\ Read the entire image, compute the CRC, and store it h# 30 from the end +filebuf /rom ofd @ fgets /rom <> abort" Can't read back image" +0 crctab filebuf /rom ($crc) crcbuf ! + +/rom h# 30 - ofd @ fseek +crcbuf /l ofd @ fputs + +ofd @ fclose + +\ Creating olpc.version serves two purposes: +\ a) It reports the firmware revision for use by external scripts +\ b) It provides an olpc.* artifact for the builder dependency management. + +writing keyject.version +" ${FW_VERSION}" expand$ 2dup lower ofd @ fputs +ofd @ fclose
Added: cpu/x86/pc/olpc/keyject.fth =================================================================== --- cpu/x86/pc/olpc/keyject.fth (rev 0) +++ cpu/x86/pc/olpc/keyject.fth 2009-02-10 23:59:41 UTC (rev 1101) @@ -0,0 +1,243 @@ +purpose: Inject additional keys into manufacturing data +\ See license at end of file + +\ Search for !!! for things that may need to change for different deployments + +\ See HowItWorks near end of file for a description of the overall procedure + +create test-me +[ifdef] test-me +.( WARNING WARNING - keyjector test code installed) cr +[else] +.( Warning - keyjector version) cr +[then] + +\ !!! Re-implement this for each different deployment +: wrong-sku? ( -- flag ) + " P#" find-tag 0= if true exit then ( pn$ ) + + -null ( pn$' ) + 2dup " 1CL11ZP0KD6" $= if 2drop false exit then ( pn$ ) \ UY BYD LiFePO4 + 2dup " 1CL11ZP0KD7" $= if 2drop false exit then ( pn$ ) \ UY GP NiMH + 2dup " 1CL11ZP0KD9" $= if 2drop false exit then ( pn$ ) \ UY GP LiFePO4 +[ifdef] test-me + 2dup " 1CL11ZU0KDB" $= if 2drop false exit then ( pn$ ) \ US for testing +[then] + 2drop + + true +; + +\ !!! Change the date for each different deployment +: keyject-expired? ( -- flag ) " 20090401T000000Z" expired? ; + +\ !!! Change the key list for each different deployment +: new-key-list$ ( -- ) " o1 s1 d1 w1 a1" ; + +\ True if the all the requested tags are already present. +\ This prevents endless looping. +: already-injected? ( -- flag ) + new-key-list$ begin dup while ( $ ) + bl left-parse-string ( $' name$ ) + find-tag if ( $ value$ ) + 2drop ( $ ) + else ( $ ) + 2drop false exit + then ( $ ) + repeat ( $ ) + 2drop true +; + +: inject-key ( keyname$ -- ) + 2dup find-drop-in if ( keyname$ value$ ) + 2over ram-find-tag if ( keyname$ value$ oldvalue$ ) + 2 pick <> if ( keyname$ value$ oldvalue$ ) + 3drop ( keyname$ ) + ." Warning: inconsistent old tag length for " type cr ( ) + exit + then ( keyname$ value$ oldvalue-adr ) + >r 2tuck r> swap move ( valu$ keyname$ ) + green-letters + ." Replaced " type cr ( value$ ) + black-letters + else ( keyname$ value$ ) + 2swap ( value$ keyname$ ) + 2over 2over ( value$ keyname$ value$ keyname$ ) + ($add-tag) ( value$ keyname$ ) + green-letters + ." Added " type cr ( value$ ) + black-letters + then ( value$ ) + free-mem ( ) + else ( keyname$ ) + ." Warning: Can't find a dropin module for " type cr ( ) + then ( ) +; + +: inject-keys ( -- ) + get-mfg-data + new-key-list$ begin dup while ( $ ) + bl left-parse-string ( $' name$ ) + inject-key ( $ ) + repeat ( $ ) + 2drop ( ) + (put-mfg-data) ( ) +; + +: keyject-error ( msg$ -- ) + cr + red-letters ." Not injecting because: " type cr black-letters + cr + ." Will update firmware in 20 seconds" cr + d# 20,000 ms +; + +: do-keyject? ( -- flag ) + wrong-sku? if + " Wrong SKU" keyject-error + false exit + then + keyject-expired? if + " Date Expired" keyject-error + false exit + then + already-injected? if + " Keys Already Present" keyject-error + false exit + then + true +; + +false value new-firmware? +: got-firmware? ( dev$ -- flag ) + 2dup ." Looking for new bootfw2.zip on " type cr ( dev$ ) + dn-buf place ( ) + " \boot" pn-buf place ( ) + filesystem-present? 0= if false exit then ( ) + null$ cn-buf place ( ) + " bootfw2" bundle-present? 0= if false exit then ( ) + ." Found" cr ( ) + secure? if ( ) + load-crypto if ( ) + ." Crypto load failed" cr false exit ( ) + then ( ) + fwkey$ to pubkey$ ( ) + img$ sig$ fw-valid? 0= if ( ) + ." Bad signature" cr ( ) + false exit + then ( ) + then ( ) + img$ tuck flash-buf swap /flash min move ( len ) + ['] ?image-valid catch if ( x ) + ." Bad firmware image" cr ( x ) + drop false exit + then ( ) + ." Good image" cr ( ) + + true to new-firmware? ( ) + true ( true ) +; + +: get-new-firmware ( -- ) + all-devices$ begin dup while ( $ ) + bl left-parse-string ( $' dev$ ) + got-firmware? if 2drop exit then ( $ ) + repeat ( $ ) + 2drop +; + +\ Firmware is in flash-buf +: update-firmware ( -- ) + write-firmware + + ['] verify-firmware catch if + ." Verify failed. Retrying once" cr + spi-identify + write-firmware + verify-firmware + then +; + +: ?keyject ( -- ) + visible + green-letters cr ." Security Key Injector" cr cr black-letters + \ Get the new firmware first, so any security checks use the old keys + get-new-firmware + do-keyject? if + flash-write-enable + inject-keys + new-firmware? if update-firmware then + flash-write-disable \ Should reboot + else + \ If we can't update the firmware, don't touch the SPI FLASH, lest + \ we get into an infinite reboot cycle. + new-firmware? if + ." Updating firmware ..." cr + flash-write-enable + update-firmware + flash-write-disable \ Should reboot + then + then +; + +?keyject + +[ifdef] HowItWorks +OLPC signs bootfw.zip containing OFW image A and bootfw2.zip containing OFW image B. +* A is an OFW with additional keyjector functionality +* B is an ordinary OFW +Version number B > version number A. + +bootfw.zip and bootfw2.zip are presented to a deployment machine in the usual manner, +either on a USB key or as part of a signed OS image. + +On a deployment machine with firmware X (version X < version A): + +The deployment machine (with old firmware version X < A) +auto-reflashes itself with firmware A via the existing secure +reflash mechanism. + +1) Firmware A starts and chacks that: + + The SKU is for the intended deployment + + the date is before the keyjector expiration date + + The override keys are not already present +so it + ! Injects the new keys +then it + ! Reads bootfw2.zip, checks its signature, and reflashes with firmware B (version > A) + ! Reboots + +2) Firmware B starts, performs the normal fw update attempt step, +noticing that data.img (firmware A) is downrev, and proceeds to +boot normally. + +In step (1), on a non-UY SKU, or after the expiration date, +firmware A skips the keyjection step and instead goes straight +to the "reflash firmware B" step. + +In either case, the machine ends up with (normal) firmware B. +[then] + +\ LICENSE_BEGIN +\ Copyright (c) 2007 FirmWorks +\ +\ Permission is hereby granted, free of charge, to any person obtaining +\ a copy of this software and associated documentation files (the +\ "Software"), to deal in the Software without restriction, including +\ without limitation the rights to use, copy, modify, merge, publish, +\ distribute, sublicense, and/or sell copies of the Software, and to +\ permit persons to whom the Software is furnished to do so, subject to +\ the following conditions: +\ +\ The above copyright notice and this permission notice shall be +\ included in all copies or substantial portions of the Software. +\ +\ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +\ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +\ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +\ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +\ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +\ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +\ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +\ +\ LICENSE_END
Added: cpu/x86/pc/olpc/kjversions.fth =================================================================== --- cpu/x86/pc/olpc/kjversions.fth (rev 0) +++ cpu/x86/pc/olpc/kjversions.fth 2009-02-10 23:59:41 UTC (rev 1101) @@ -0,0 +1,31 @@ +\ Version numbers of items included in the OLPC firmware image + +\ The overall firmware revision +macro: FW_MAJOR E +macro: FW_MINOR 30b + +\ The EC microcode +macro: EC_VERSION e21 + +\ Alternate command for getting EC microcode, for testing new versions. +\ Temporarily uncomment the line and modify the path as necessary +\ macro: GET_EC cp pq2e18c.img ec.img + +macro: KEYS mpkeys +\ macro: KEYS testkeys + +\ The wireless LAN module firmware +macro: WLAN_RPM ${WLAN_VERSION}.olpc1 +macro: WLAN_VERSION 5.110.22.p23 + +\ The bios_verify image +macro: CRYPTO_VERSION 0.2 + +\ The multicast NAND updater code version +\ Use a specific git commit ID for a formal release or "test" for development. +\ With a specific ID, mcastnand.bth will download a tarball without .git stuff. +\ With "test", mcastnand.bth will clone the git head if build/multicast-nand/ +\ is not already present, then you can modify the git subtree as needed. +macro: MCNAND_VERSION 0c73b4a084a27f0687b152dd0395c67fdf54b10f +\ macro: MCNAND_VERSION test +\ macro: MCNAND_VERSION HEAD
Modified: cpu/x86/pc/olpc/mfgdata.fth =================================================================== --- cpu/x86/pc/olpc/mfgdata.fth 2009-02-02 08:47:00 UTC (rev 1100) +++ cpu/x86/pc/olpc/mfgdata.fth 2009-02-10 23:59:41 UTC (rev 1101) @@ -51,8 +51,8 @@ nip if drop flash-base h# f.0000 + then ;
-: find-tag ( name$ -- false | data$ true ) - drop >r mfg-data-top ( adr r: name-adr ) +: (find-tag) ( name$ top-adr -- false | data$ true ) + -rot drop >r ( adr r: name-adr ) begin another-tag? while ( adr' data$ tname-adr r: name-adr ) r@ 2 comp 0= if ( adr' data$ r: name-adr ) r> drop rot drop ( data$ ) @@ -62,6 +62,9 @@ repeat ( adr' r: name-adr ) r> 2drop false ; +: find-tag ( name$ -- false | data$ true ) + mfg-data-top (find-tag) +;
\ Remove bogus null characters from the end of mfg data tags (old machines \ have malformed tags)
Modified: cpu/x86/pc/olpc/security.fth =================================================================== --- cpu/x86/pc/olpc/security.fth 2009-02-02 08:47:00 UTC (rev 1100) +++ cpu/x86/pc/olpc/security.fth 2009-02-10 23:59:41 UTC (rev 1101) @@ -20,15 +20,15 @@ debug-security? if red-letters type black-letters cr else 2drop then ;
+code halt hlt c; \ To save power + : fail-load ( -- ) + show-sad text-on ." OS Load Failed" cr - quit - begin again + begin halt again ;
-code halt hlt c; \ To save power - 0 value security-off?
: security-failure ( -- ) @@ -943,7 +943,7 @@ banner then
- load-crypto if " Crytpo load failed" .security-failure then ( ) + load-crypto if " Crypto load failed" .security-failure then ( )
alternate? if " \boot-alt" else " \boot" then pn-buf place
Modified: cpu/x86/pc/olpc/setwp.fth =================================================================== --- cpu/x86/pc/olpc/setwp.fth 2009-02-02 08:47:00 UTC (rev 1100) +++ cpu/x86/pc/olpc/setwp.fth 2009-02-10 23:59:41 UTC (rev 1101) @@ -7,9 +7,16 @@ \ require a full erase. That is faster and safer than copying out the \ data, erasing the block and rewriting it.
-: set-wp ( -- ) +: enable-security ( "serialnumber" -- ) + board-revision h# b48 < abort" Only supported on B4 and later" h# fffefffe 2 " wp" $= if ." wp is already set" cr exit then - " SN" find-tag 0= abort" No serial number (SN tag); enabling security would brick me." 2drop + " SN" find-tag 0= abort" No serial number (SN tag); enabling security would brick me." ( sn$ ) + safe-parse-word ( sn$ confirmation$ ) + $= 0= abort" Confirmation code mismatch" + + " Enabling security is dangerous. Are you sure you want to do it?" confirmedn? + 0= abort" Canceled" + " U#" find-tag 0= abort" No U# tag; enabling security would brick me." 2drop h# fffefffe 2 " ww" $= 0= abort" No ww tag" spi-start spi-identify @@ -25,10 +32,18 @@ rom-pa mfg-data-offset + mfg-data-buf /flash-block lmove ;
+: (put-mfg-data) + mfg-data-buf mfg-data-end-offset mfg-data-offset write-flash-range +; + +: ram-find-tag ( name$ -- false | data$ true ) + mfg-data-buf /flash-block + (find-tag) +; + \ Write mfg data from RAM to FLASH : put-mfg-data ( -- ) spi-start spi-identify - mfg-data-buf mfg-data-end-offset mfg-data-offset write-flash-range + (put-mfg-data) spi-reprogrammed ;
@@ -52,12 +67,14 @@ r> ;
+[ifdef] notdef \ Change the "ww" tag to "wp" : hard-set-wp ( -- ) " ww" mfg-data-setup ( ram-adr ) [char] p swap 1+ c! ( ) put-mfg-data ; +[then]
\ Change the "wp" tag to "ww" : clear-wp ( -- ) @@ -68,14 +85,12 @@
alias disable-security clear-wp
-: enable-security ( -- ) - board-revision h# b48 < abort" Only supported on B4 and later" - set-wp -; - : ?tagname-valid ( tagname$ -- tagname$ ) dup 2 <> abort" Tag name must be 2 characters long" ; +: ?wp-tag ( tagname$ -- tagname$ ) + 2dup " wp" $= abort" Use enable-security for the wp tag" +; : tag-setup ( tagname$ -- ram-value$ ) ?tagname-valid get-mfg-data @@ -100,6 +115,7 @@ ;
: $change-tag ( value$ tagname$ -- ) + ?wp-tag tag-setup ( new-value$ old-value$ ) 2over 2over value-mismatch? abort" New value and old value have different lengths" drop swap move ( ) @@ -116,11 +132,7 @@ mfg-data-buf /flash-block + last-mfg-data ;
-: $add-tag ( value$ name$ -- ) - ?tagname-valid ( value$ name$ ) - 2dup find-tag abort" Tagname already exists" ( value$ name$ ) - - get-mfg-data +: ($add-tag) ( value$ name$ -- ) ram-last-mfg-data >r ( value$ name$ r: adr )
\ Check for enough space for the new tag @@ -152,7 +164,14 @@
\ Copy the value data over - swap move ( ) +; +: $add-tag ( value$ name$ -- ) + ?wp-tag + ?tagname-valid ( value$ name$ ) + 2dup find-tag abort" Tagname already exists" ( value$ name$ )
+ get-mfg-data ( value$ name$ ) + ($add-tag) ( ) put-mfg-data ( ) ;
Modified: dev/olpc/spiflash/spiui.fth =================================================================== --- dev/olpc/spiflash/spiui.fth 2009-02-02 08:47:00 UTC (rev 1100) +++ dev/olpc/spiflash/spiui.fth 2009-02-10 23:59:41 UTC (rev 1101) @@ -40,8 +40,9 @@ ." Verifying" cr ?do ( adr ) i .x (cr - dup i + /flash-block i flash-verify - /flash-block +loop ( adr ) + dup /flash-block i flash-verify + /flash-block + ( adr' ) + /flash-block +loop ( adr ) cr drop ( ) ;
@@ -226,8 +227,19 @@ then ;
-: write-firmware ( -- ) +: verify-firmware ( -- ) [ifdef] use-flash-nvram + flash-buf nvram-offset 0 verify-flash-range \ Verify first part +[else] + flash-buf mfg-data-offset 0 verify-flash-range \ Verify first part +[then] + + \ Don't verify the block containing the manufacturing data + + flash-buf mfg-data-end-offset + /flash mfg-data-end-offset verify-flash-range \ Verify last part +; +: write-firmware ( -- ) +[ifdef] use-flash-nvram flash-buf nvram-offset 0 write-flash-range \ Write first part [else] flash-buf mfg-data-offset 0 write-flash-range \ Write first part @@ -235,8 +247,7 @@
\ Don't write the block containing the manufacturing data
- flash-buf mfg-data-end-offset + ( adr ) - /flash mfg-data-end-offset write-flash-range \ Write last part + flash-buf mfg-data-end-offset + /flash mfg-data-end-offset write-flash-range \ Write last part ;
: .verify-msg ( -- ) @@ -334,11 +345,11 @@ ?file
\ Don't overwrite the EC code - flash-buf /flash /ec write-flash-range + flash-buf /ec + /flash /ec write-flash-range
.verify-msg ; -: verify-bios ( -- ) flash-buf /flash /ec verify-flash-range ; +: verify-bios ( -- ) flash-buf /ec + /flash /ec verify-flash-range ;
: flash-ec ( -- ) ?file