1\ $NetBSD: bootblk.fth,v 1.3 2001/08/15 20:10:24 eeh Exp $ 2\ 3\ IEEE 1275 Open Firmware Boot Block 4\ 5\ Parses disklabel and UFS and loads the file called `ofwboot' 6\ 7\ 8\ Copyright (c) 1998 Eduardo Horvath. 9\ All rights reserved. 10\ 11\ Redistribution and use in source and binary forms, with or without 12\ modification, are permitted provided that the following conditions 13\ are met: 14\ 1. Redistributions of source code must retain the above copyright 15\ notice, this list of conditions and the following disclaimer. 16\ 2. Redistributions in binary form must reproduce the above copyright 17\ notice, this list of conditions and the following disclaimer in the 18\ documentation and/or other materials provided with the distribution. 19\ 3. All advertising materials mentioning features or use of this software 20\ must display the following acknowledgement: 21\ This product includes software developed by Eduardo Horvath. 22\ 4. The name of the author may not be used to endorse or promote products 23\ derived from this software without specific prior written permission 24\ 25\ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 26\ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 27\ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 28\ IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 29\ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 30\ NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 31\ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 32\ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 33\ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 34\ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 35\ 36 37offset16 38hex 39headers 40 41false value boot-debug? 42 43\ 44\ First some housekeeping: Open /chosen and set up vectors into 45\ client-services 46 47" /chosen" find-package 0= if ." Cannot find /chosen" 0 then 48constant chosen-phandle 49 50" /openprom/client-services" find-package 0= if 51 ." Cannot find client-services" cr abort 52then constant cif-phandle 53 54defer cif-claim ( align size virt -- base ) 55defer cif-release ( size virt -- ) 56defer cif-open ( cstr -- ihandle|0 ) 57defer cif-close ( ihandle -- ) 58defer cif-read ( len adr ihandle -- #read ) 59defer cif-seek ( low high ihandle -- -1|0|1 ) 60\ defer cif-peer ( phandle -- phandle ) 61\ defer cif-getprop ( len adr cstr phandle -- ) 62 63: find-cif-method ( method,len -- xf ) 64 cif-phandle find-method drop 65; 66 67" claim" find-cif-method to cif-claim 68" open" find-cif-method to cif-open 69" close" find-cif-method to cif-close 70" read" find-cif-method to cif-read 71" seek" find-cif-method to cif-seek 72 73: twiddle ( -- ) ." ." ; \ Need to do this right. Just spit out periods for now. 74 75\ 76\ Support routines 77\ 78 79: strcmp ( s1 l1 s2 l2 -- true:false ) 80 rot tuck <> if 3drop false exit then 81 comp 0= 82; 83 84\ Move string into buffer 85 86: strmov ( s1 l1 d -- d l1 ) 87 dup 2over swap -rot ( s1 l1 d s1 d l1 ) 88 move ( s1 l1 d ) 89 rot drop swap 90; 91 92\ Move s1 on the end of s2 and return the result 93 94: strcat ( s1 l1 s2 l2 -- d tot ) 95 2over swap ( s1 l1 s2 l2 l1 s1 ) 96 2over + rot ( s1 l1 s2 l2 s1 d l1 ) 97 move rot + ( s1 s2 len ) 98 rot drop ( s2 len ) 99; 100 101: strchr ( s1 l1 c -- s2 l2 ) 102 begin 103 dup 2over 0= if ( s1 l1 c c s1 ) 104 2drop drop exit then 105 c@ = if ( s1 l1 c ) 106 drop exit then 107 -rot /c - swap ca1+ ( c l2 s2 ) 108 swap rot 109 again 110; 111 112 113: cstr ( ptr -- str len ) 114 dup 115 begin dup c@ 0<> while + repeat 116 over - 117; 118 119\ 120\ BSD FFS parameters 121\ 122 123fload assym.fth.h 124 125sbsize buffer: sb-buf 126-1 value boot-ihandle 127dev_bsize value bsize 1280 value raid-offset \ Offset if it's a raid-frame partition 129 130: strategy ( addr size start -- nread ) 131 raid-offset + bsize * 0 " seek" boot-ihandle $call-method 132 -1 = if 133 ." strategy: Seek failed" cr 134 abort 135 then 136 " read" boot-ihandle $call-method 137; 138 139\ 140\ Cylinder group macros 141\ 142 143: cgbase ( cg fs -- cgbase ) fs_fpg l@ * ; 144: cgstart ( cg fs -- cgstart ) 145 2dup fs_cgmask l@ not and ( cg fs stuff -- ) 146 over fs_cgoffset l@ * -rot ( stuffcg fs -- ) 147 cgbase + 148; 149: cgdmin ( cg fs -- 1st-data-block ) dup fs_dblkno l@ -rot cgstart + ; 150: cgimin ( cg fs -- inode-block ) dup fs_iblkno l@ -rot cgstart + ; 151: cgsblock ( cg fs -- super-block ) dup fs_sblkno l@ -rot cgstart + ; 152: cgstod ( cg fs -- cg-block ) dup fs_cblkno l@ -rot cgstart + ; 153 154\ 155\ Block and frag position macros 156\ 157 158: blkoff ( pos fs -- off ) fs_qbmask x@ and ; 159: fragoff ( pos fs -- off ) fs_qfmask x@ and ; 160: lblktosize ( blk fs -- off ) fs_bshift l@ << ; 161: lblkno ( pos fs -- off ) fs_bshift l@ >> ; 162: numfrags ( pos fs -- off ) fs_fshift l@ >> ; 163: blkroundup ( pos fs -- off ) dup fs_bmask l@ -rot fs_qbmask x@ + and ; 164: fragroundup ( pos fs -- off ) dup fs_fmask l@ -rot fs_qfmask x@ + and ; 165\ : fragroundup ( pos fs -- off ) tuck fs_qfmask x@ + swap fs_fmask l@ and ; 166: fragstoblks ( pos fs -- off ) fs_fragshift l@ >> ; 167: blkstofrags ( blk fs -- frag ) fs_fragshift l@ << ; 168: fragnum ( fsb fs -- off ) fs_frag l@ 1- and ; 169: blknum ( fsb fs -- off ) fs_frag l@ 1- not and ; 170: dblksize ( lbn dino fs -- size ) 171 -rot ( fs lbn dino ) 172 di_size x@ ( fs lbn di_size ) 173 -rot dup 1+ ( di_size fs lbn lbn+1 ) 174 2over fs_bshift l@ ( di_size fs lbn lbn+1 di_size b_shift ) 175 rot swap << >= ( di_size fs lbn res1 ) 176 swap ndaddr >= or if ( di_size fs ) 177 swap drop fs_bsize l@ exit ( size ) 178 then tuck blkoff swap fragroundup ( size ) 179; 180 181 182: ino-to-cg ( ino fs -- cg ) fs_ipg l@ / ; 183: ino-to-fsbo ( ino fs -- fsb0 ) fs_inopb l@ mod ; 184: ino-to-fsba ( ino fs -- ba ) \ Need to remove the stupid stack diags someday 185 2dup ( ino fs ino fs ) 186 ino-to-cg ( ino fs cg ) 187 over ( ino fs cg fs ) 188 cgimin ( ino fs inode-blk ) 189 -rot ( inode-blk ino fs ) 190 tuck ( inode-blk fs ino fs ) 191 fs_ipg l@ ( inode-blk fs ino ipg ) 192 mod ( inode-blk fs mod ) 193 swap ( inode-blk mod fs ) 194 dup ( inode-blk mod fs fs ) 195 fs_inopb l@ ( inode-blk mod fs inopb ) 196 rot ( inode-blk fs inopb mod ) 197 swap ( inode-blk fs mod inopb ) 198 / ( inode-blk fs div ) 199 swap ( inode-blk div fs ) 200 blkstofrags ( inode-blk frag ) 201 + 202; 203: fsbtodb ( fsb fs -- db ) fs_fsbtodb l@ << ; 204 205\ 206\ File stuff 207\ 208 209niaddr /w* constant narraysize 210 211struct 212 8 field >f_ihandle \ device handle 213 8 field >f_seekp \ seek pointer 214 8 field >f_fs \ pointer to super block 215 dinode_SIZEOF field >f_di \ copy of on-disk inode 216 8 field >f_buf \ buffer for data block 217 4 field >f_buf_size \ size of data block 218 4 field >f_buf_blkno \ block number of data block 219constant file_SIZEOF 220 221file_SIZEOF buffer: the-file 222sb-buf the-file >f_fs x! 223 224dinode_SIZEOF buffer: cur-inode 225h# 2000 buffer: indir-block 226-1 value indir-addr 227 228\ 229\ Translate a fileblock to a disk block 230\ 231\ We only allow single indirection 232\ 233 234: block-map ( fileblock -- diskblock ) 235 \ Direct block? 236 dup ndaddr < if ( fileblock ) 237 cur-inode di_db ( arr-indx arr-start ) 238 swap la+ l@ exit ( diskblock ) 239 then ( fileblock ) 240 ndaddr - ( fileblock' ) 241 \ Now we need to check the indirect block 242 dup sb-buf fs_nindir l@ < if ( fileblock' ) 243 cur-inode di_ib l@ dup ( fileblock' indir-block indir-block ) 244 indir-addr <> if ( fileblock' indir-block ) 245 to indir-addr ( fileblock' ) 246 indir-block ( fileblock' indir-block ) 247 sb-buf dup fs_bsize l@ ( fileblock' indir-block fs fs_bsize ) 248 swap indir-addr swap ( fileblock' indir-block fs_bsize indiraddr fs ) 249 fsbtodb ( fileblock' indir-block fs_bsize db ) 250 strategy ( fileblock' nread ) 251 then ( fileblock' nread|indir-block ) 252 drop \ Really should check return value 253 indir-block swap la+ l@ exit 254 then 255 dup sb-buf fs_nindir - ( fileblock'' ) 256 \ Now try 2nd level indirect block -- just read twice 257 dup sb-buf fs_nindir l@ dup * < if ( fileblock'' ) 258 cur-inode di_ib 1 la+ l@ ( fileblock'' indir2-block ) 259 to indir-addr ( fileblock'' ) 260 \ load 1st level indir block 261 indir-block ( fileblock'' indir-block ) 262 sb-buf dup fs_bsize l@ ( fileblock'' indir-block fs fs_bsize ) 263 swap indir-addr swap ( fileblock'' indir-block fs_bsize indiraddr fs ) 264 fsbtodb ( fileblock'' indir-block fs_bsize db ) 265 strategy ( fileblock'' nread ) 266 drop ( fileblock'' ) 267 dup sb-buf fs_nindir / ( fileblock'' indir-offset ) 268 indir-block swap la+ l@ ( fileblock'' indirblock ) 269 to indir-addr ( fileblock'' ) 270 \ load 2nd level indir block 271 indir-block ( fileblock'' indir-block ) 272 sb-buf dup fs_bsize l@ ( fileblock'' indir-block fs fs_bsize ) 273 swap indir-addr swap ( fileblock'' indir-block fs_bsize indiraddr fs ) 274 fsbtodb ( fileblock'' indir-block fs_bsize db ) 275 strategy ( fileblock'' nread ) 276 drop ( fileblock'' ) 277 sb-buf fs_nindir l@ mod indir-block swap la+ l@ exit 278 then 279 ." block-map: exceeded max file size" cr 280 abort 281; 282 283\ 284\ Read file into internal buffer and return pointer and len 285\ 286 2872000 buffer: cur-block \ Why do dynamic allocation? 288-1 value cur-blockno 2890 value cur-offset 290 291: buf-read-file ( fs -- len buf ) 292 cur-offset swap ( seekp fs ) 293 2dup blkoff ( seekp fs off ) 294 -rot 2dup lblkno ( off seekp fs block ) 295 swap 2dup cur-inode ( off seekp block fs block fs inop ) 296 swap dblksize ( off seekp block fs size ) 297 rot dup cur-blockno ( off seekp fs size block block cur ) 298 <> if ( off seekp fs size block ) 299 block-map ( off seekp fs size diskblock ) 300 dup 0= if ( off seekp fs size diskblock ) 301 over cur-block swap 0 fill ( off seekp fs size diskblock ) 302 boot-debug? if ." buf-read-file fell off end of file" cr then 303 else 304 2dup sb-buf fsbtodb cur-block -rot strategy ( off seekp fs size diskblock nread ) 305 rot 2dup <> if " buf-read-file: short read." cr abort then 306 then ( off seekp fs diskblock nread size ) 307 nip nip ( off seekp fs size ) 308 else ( off seekp fs size block block cur ) 309 2drop ( off seekp fs size ) 310 then 311\ dup cur-offset + to cur-offset \ Set up next xfer -- not done 312 nip nip swap - ( len ) 313 cur-block 314; 315 316\ 317\ Read inode into cur-inode -- uses cur-block 318\ 319 320: read-inode ( inode fs -- ) 321 twiddle ( inode fs -- inode fs ) 322 323 cur-block ( inode fs -- inode fs buffer ) 324 325 over ( inode fs buffer -- inode fs buffer fs ) 326 fs_bsize l@ ( inode fs buffer -- inode fs buffer size ) 327 328 2over ( inode fs buffer size -- inode fs buffer size inode fs ) 329 2over ( inode fs buffer size inode fs -- inode fs buffer size inode fs buffer size ) 330 2swap tuck ( inode fs buffer size inode fs buffer size -- inode fs buffer size buffer size fs inode fs ) 331 332 ino-to-fsba ( inode fs buffer size buffer size fs inode fs -- inode fs buffer size buffer size fs fsba ) 333 swap ( inode fs buffer size buffer size fs fsba -- inode fs buffer size buffer size fsba fs ) 334 fsbtodb ( inode fs buffer size buffer size fsba fs -- inode fs buffer size buffer size db ) 335 336 dup to cur-blockno ( inode fs buffer size buffer size dstart -- inode fs buffer size buffer size dstart ) 337 strategy ( inode fs buffer size buffer size dstart -- inode fs buffer size nread ) 338 <> if ." read-inode - residual" cr abort then 339 dup 2over ( inode fs buffer -- inode fs buffer buffer inode fs ) 340 ino-to-fsbo ( inode fs buffer -- inode fs buffer buffer fsbo ) 341 dinode_SIZEOF * + ( inode fs buffer buffer fsbo -- inode fs buffer dinop ) 342 cur-inode dinode_SIZEOF move ( inode fs buffer dinop -- inode fs buffer ) 343 \ clear out the old buffers 344 drop ( inode fs buffer -- inode fs ) 345 2drop 346; 347 348\ Identify inode type 349 350: is-dir? ( dinode -- true:false ) di_mode w@ ifmt and ifdir = ; 351: is-symlink? ( dinode -- true:false ) di_mode w@ ifmt and iflnk = ; 352 353 354 355\ 356\ Hunt for directory entry: 357\ 358\ repeat 359\ load a buffer 360\ while entries do 361\ if entry == name return 362\ next entry 363\ until no buffers 364\ 365 366: search-directory ( str len -- ino|0 ) 367 0 to cur-offset 368 begin cur-offset cur-inode di_size x@ < while ( str len ) 369 sb-buf buf-read-file ( str len len buf ) 370 over 0= if ." search-directory: buf-read-file zero len" cr abort then 371 swap dup cur-offset + to cur-offset ( str len buf len ) 372 2dup + nip ( str len buf bufend ) 373 swap 2swap rot ( bufend str len buf ) 374 begin dup 4 pick < while ( bufend str len buf ) 375 dup d_ino l@ 0<> if ( bufend str len buf ) 376 boot-debug? if dup dup d_name swap d_namlen c@ type cr then 377 2dup d_namlen c@ = if ( bufend str len buf ) 378 dup d_name 2over ( bufend str len buf dname str len ) 379 comp 0= if ( bufend str len buf ) 380 \ Found it -- return inode 381 d_ino l@ nip nip nip ( dino ) 382 boot-debug? if ." Found it" cr then 383 exit ( dino ) 384 then 385 then ( bufend str len buf ) 386 then ( bufend str len buf ) 387 dup d_reclen w@ + ( bufend str len nextbuf ) 388 repeat 389 drop rot drop ( str len ) 390 repeat 391 2drop 2drop 0 ( 0 ) 392; 393 394: ffs_oldcompat ( -- ) 395\ Make sure old ffs values in sb-buf are sane 396 sb-buf fs_npsect dup l@ sb-buf fs_nsect l@ max swap l! 397 sb-buf fs_interleave dup l@ 1 max swap l! 398 sb-buf fs_postblformat l@ fs_42postblfmt = if 399 8 sb-buf fs_nrpos l! 400 then 401 sb-buf fs_inodefmt l@ fs_44inodefmt < if 402 sb-buf fs_bsize l@ 403 dup ndaddr * 1- sb-buf fs_maxfilesize x! 404 niaddr 0 ?do 405 sb-buf fs_nindir l@ * dup ( sizebp sizebp -- ) 406 sb-buf fs_maxfilesize dup x@ ( sizebp sizebp *fs_maxfilesize fs_maxfilesize -- ) 407 rot ( sizebp *fs_maxfilesize fs_maxfilesize sizebp -- ) 408 + ( sizebp *fs_maxfilesize new_fs_maxfilesize -- ) swap x! ( sizebp -- ) 409 loop drop ( -- ) 410 sb-buf dup fs_bmask l@ not swap fs_qbmask x! 411 sb-buf dup fs_fmask l@ not swap fs_qfmask x! 412 then 413; 414 415: read-super ( sector -- ) 4160 " seek" boot-ihandle $call-method 417 -1 = if 418 ." Seek failed" cr 419 abort 420 then 421 sb-buf sbsize " read" boot-ihandle $call-method 422 dup sbsize <> if 423 ." Read of superblock failed" cr 424 ." requested" space sbsize . 425 ." actual" space . cr 426 abort 427 else 428 drop 429 then 430; 431 432: ufs-open ( bootpath,len -- ) 433 boot-ihandle -1 = if 434 over cif-open dup 0= if ( boot-path len ihandle? ) 435 ." Could not open device" space type cr 436 abort 437 then ( boot-path len ihandle ) 438 to boot-ihandle \ Save ihandle to boot device 439 then 2drop 440 sboff read-super 441 sb-buf fs_magic l@ fs_magic_value <> if 442 64 dup to raid-offset 443 dev_bsize * sboff + read-super 444 sb-buf fs_magic l@ fs_magic_value <> if 445 ." Invalid superblock magic" cr 446 abort 447 then 448 then 449 sb-buf fs_bsize l@ dup maxbsize > if 450 ." Superblock bsize" space . ." too large" cr 451 abort 452 then 453 fs_SIZEOF < if 454 ." Superblock bsize < size of superblock" cr 455 abort 456 then 457 ffs_oldcompat 458 boot-debug? if ." ufs-open complete" cr then 459; 460 461: ufs-close ( -- ) 462 boot-ihandle dup -1 <> if 463 cif-close -1 to boot-ihandle 464 then 465; 466 467: boot-path ( -- boot-path ) 468 " bootpath" chosen-phandle get-package-property if 469 ." Could not find bootpath in /chosen" cr 470 abort 471 else 472 decode-string 2swap 2drop 473 then 474; 475 476: boot-args ( -- boot-args ) 477 " bootargs" chosen-phandle get-package-property if 478 ." Could not find bootargs in /chosen" cr 479 abort 480 else 481 decode-string 2swap 2drop 482 then 483; 484 4852000 buffer: boot-path-str 4862000 buffer: boot-path-tmp 487 488: split-path ( path len -- right len left len ) 489\ Split a string at the `/' 490 begin 491 dup -rot ( oldlen right len left ) 492 ascii / left-parse-string ( oldlen right len left len ) 493 dup 0<> if 4 roll drop exit then 494 2drop ( oldlen right len ) 495 rot over = ( right len diff ) 496 until 497; 498 499: find-file ( load-file len -- ) 500 rootino dup sb-buf read-inode ( load-file len -- load-file len ino ) 501 -rot ( load-file len ino -- pino load-file len ) 502 \ 503 \ For each path component 504 \ 505 begin split-path dup 0<> while ( pino right len left len -- ) 506 cur-inode is-dir? not if ." Inode not directory" cr abort then 507 boot-debug? if ." Looking for" space 2dup type space ." in directory..." cr then 508 search-directory ( pino right len left len -- pino right len ino|false ) 509 dup 0= if ." Bad path" cr abort then ( pino right len cino ) 510 sb-buf read-inode ( pino right len ) 511 cur-inode is-symlink? if \ Symlink -- follow the damn thing 512 \ Save path in boot-path-tmp 513 boot-path-tmp strmov ( pino new-right len ) 514 515 \ Now deal with symlink 516 cur-inode di_size x@ ( pino right len linklen ) 517 dup sb-buf fs_maxsymlinklen l@ ( pino right len linklen linklen maxlinklen ) 518 < if \ Now join the link to the path 519 cur-inode di_shortlink l@ ( pino right len linklen linkp ) 520 swap boot-path-str strmov ( pino right len new-linkp linklen ) 521 else \ Read file for symlink -- Ugh 522 \ Read link into boot-path-str 523 boot-path-str dup sb-buf fs_bsize l@ 524 0 block-map ( pino right len linklen boot-path-str bsize blockno ) 525 strategy drop swap ( pino right len boot-path-str linklen ) 526 then ( pino right len linkp linklen ) 527 \ Concatenate the two paths 528 strcat ( pino new-right newlen ) 529 swap dup c@ ascii / = if \ go to root inode? 530 rot drop rootino -rot ( rino len right ) 531 then 532 rot dup sb-buf read-inode ( len right pino ) 533 -rot swap ( pino right len ) 534 then ( pino right len ) 535 repeat 536 2drop drop 537; 538 539: read-file ( size addr -- ) 540 \ Read x bytes from a file to buffer 541 begin over 0> while 542 cur-offset cur-inode di_size x@ > if ." read-file EOF exceeded" cr abort then 543 sb-buf buf-read-file ( size addr len buf ) 544 over 2over drop swap ( size addr len buf addr len ) 545 move ( size addr len ) 546 dup cur-offset + to cur-offset ( size len newaddr ) 547 tuck + ( size len newaddr ) 548 -rot - swap ( newaddr newsize ) 549 repeat 550 2drop 551; 552 553h# 5000 constant loader-base 554 555\ 556\ Elf support -- find the load addr 557\ 558 559: is-elf? ( hdr -- res? ) h# 7f454c46 = ; 560 561\ 562\ Finally we finish it all off 563\ 564 565: load-file-signon ( load-file len boot-path len -- load-file len boot-path len ) 566 ." Loading file" space 2over type cr ." from device" space 2dup type cr 567; 568 569: load-file-print-size ( size -- size ) 570 ." Loading" space dup . space ." bytes of file..." cr 571; 572 573: load-file ( load-file len boot-path len -- load-base ) 574 boot-debug? if load-file-signon then 575 the-file file_SIZEOF 0 fill \ Clear out file structure 576 ufs-open ( load-file len ) 577 find-file ( ) 578 579 \ 580 \ Now we've found the file we should read it in in one big hunk 581 \ 582 583 cur-inode di_size x@ ( file-len ) 584 dup " to file-size" evaluate ( file-len ) 585 boot-debug? if load-file-print-size then 586 0 to cur-offset 587 loader-base ( buf-len addr ) 588 2dup read-file ( buf-len addr ) 589 ufs-close ( buf-len addr ) 590 dup is-elf? if ." load-file: not an elf executable" cr abort then 591 592 \ Luckily the prom should be able to handle ELF executables by itself 593 594 nip ( addr ) 595; 596 597: do-boot ( bootfile -- ) 598 ." NetBSD IEEE 1275 Bootblock" cr 599 boot-path load-file ( -- load-base ) 600 dup 0<> if " to load-base init-program" evaluate then 601; 602 603 604boot-args ascii V strchr 0<> swap drop if 605 true to boot-debug? 606then 607 608boot-args ascii D strchr 0= swap drop if 609 " /ofwboot" do-boot 610then exit 611 612 613