Fully synchronize sys/boot from FreeBSD-5.x, but add / to the module path
[dragonfly.git] / sys / boot / i386 / cdboot / cdboot.s
1 #
2 # Copyright (c) 2001 John Baldwin
3 # All rights reserved.
4 #
5 # Redistribution and use in source and binary forms are freely
6 # permitted provided that the above copyright notice and this
7 # paragraph and the following disclaimer are duplicated in all
8 # such forms.
9 #
10 # This software is provided "AS IS" and without any express or
11 # implied warranties, including, without limitation, the implied
12 # warranties of merchantability and fitness for a particular
13 # purpose.
14 #
15
16 # $FreeBSD: src/sys/boot/i386/cdboot/cdboot.s,v 1.9 2001/11/07 01:20:33 jhb Exp $
17 # $DragonFly: src/sys/boot/i386/cdboot/Attic/cdboot.s,v 1.3 2003/11/10 06:08:35 dillon Exp $
18
19 #
20 # This program is a freestanding boot program to load an a.out binary
21 # from a CD-ROM booted with no emulation mode as described by the El
22 # Torito standard.  Due to broken BIOSen that do not load the desired
23 # number of sectors, we try to fit this in as small a space as possible.
24 #
25 # Basically, we first create a set of boot arguments to pass to the loaded
26 # binary.  Then we attempt to load /boot/loader from the CD we were booted
27 # off of. 
28 #
29
30 #
31 # Memory locations.
32 #
33                 .set MEM_PAGE_SIZE,0x1000       # memory page size, 4k
34                 .set MEM_ARG,0x900              # Arguments at start
35                 .set MEM_ARG_BTX,0xa100         # Where we move them to so the
36                                                 #  BTX client can see them
37                 .set MEM_ARG_SIZE,0x18          # Size of the arguments
38                 .set MEM_BTX_ADDRESS,0x9000     # where BTX lives
39                 .set MEM_BTX_ENTRY,0x9010       # where BTX starts to execute
40                 .set MEM_BTX_OFFSET,MEM_PAGE_SIZE # offset of BTX in the loader
41                 .set MEM_BTX_CLIENT,0xa000      # where BTX clients live
42 #
43 # a.out header fields
44 #
45                 .set AOUT_TEXT,0x04             # text segment size
46                 .set AOUT_DATA,0x08             # data segment size
47                 .set AOUT_BSS,0x0c              # zero'd BSS size
48                 .set AOUT_SYMBOLS,0x10          # symbol table
49                 .set AOUT_ENTRY,0x14            # entry point
50                 .set AOUT_HEADER,MEM_PAGE_SIZE  # size of the a.out header
51 #
52 # Flags for kargs->bootflags
53 #
54                 .set KARGS_FLAGS_CD,0x1         # flag to indicate booting from
55                                                 #  CD loader
56 #
57 # Segment selectors.
58 #
59                 .set SEL_SDATA,0x8              # Supervisor data
60                 .set SEL_RDATA,0x10             # Real mode data
61                 .set SEL_SCODE,0x18             # PM-32 code
62                 .set SEL_SCODE16,0x20           # PM-16 code
63 #
64 # BTX constants
65 #
66                 .set INT_SYS,0x30               # BTX syscall interrupt
67 #
68 # Constants for reading from the CD.
69 #
70                 .set ERROR_TIMEOUT,0x80         # BIOS timeout on read
71                 .set NUM_RETRIES,3              # Num times to retry
72                 .set SECTOR_SIZE,0x800          # size of a sector
73                 .set SECTOR_SHIFT,11            # number of place to shift
74                 .set BUFFER_LEN,0x100           # number of sectors in buffer
75                 .set MAX_READ,0x10000           # max we can read at a time
76                 .set MAX_READ_SEC,MAX_READ >> SECTOR_SHIFT
77                 .set MEM_READ_BUFFER,0x9000     # buffer to read from CD
78                 .set MEM_VOLDESC,MEM_READ_BUFFER # volume descriptor
79                 .set MEM_DIR,MEM_VOLDESC+SECTOR_SIZE # Lookup buffer
80                 .set VOLDESC_LBA,0x10           # LBA of vol descriptor
81                 .set VD_PRIMARY,1               # Primary VD
82                 .set VD_END,255                 # VD Terminator
83                 .set VD_ROOTDIR,156             # Offset of Root Dir Record
84                 .set DIR_LEN,0                  # Offset of Dir Record length
85                 .set DIR_EA_LEN,1               # Offset of EA length
86                 .set DIR_EXTENT,2               # Offset of 64-bit LBA
87                 .set DIR_SIZE,10                # Offset of 64-bit length
88                 .set DIR_NAMELEN,32             # Offset of 8-bit name len
89                 .set DIR_NAME,33                # Offset of dir name
90 #
91 # We expect to be loaded by the BIOS at 0x7c00 (standard boot loader entry
92 # point)
93 #
94                 .code16
95                 .globl start
96                 .org 0x0, 0x0
97 #
98 # Program start.
99 #
100 start:          cld                             # string ops inc
101                 xor %ax,%ax                     # zero %ax
102                 mov %ax,%ss                     # setup the
103                 mov $start,%sp                  #  stack
104                 mov %ax,%ds                     # setup the
105                 mov %ax,%es                     #  data segments
106                 mov %dl,drive                   # Save BIOS boot device
107                 mov $msg_welcome,%si            # %ds:(%si) -> welcome message
108                 call putstr                     # display the welcome message
109 #
110 # Setup the arguments that the loader is expecting from boot[12]
111 #
112                 mov $msg_bootinfo,%si           # %ds:(%si) -> boot args message
113                 call putstr                     # display the message
114                 mov $MEM_ARG,%bx                # %ds:(%bx) -> boot args
115                 mov %bx,%di                     # %es:(%di) -> boot args
116                 xor %eax,%eax                   # zero %eax
117                 mov $(MEM_ARG_SIZE/4),%cx       # Size of arguments in 32-bit
118                                                 #  dwords
119                 rep                             # Clear the arguments
120                 stosl                           #  to zero
121                 mov drive,%dl                   # Store BIOS boot device
122                 mov %dl,0x4(%bx)                #  in kargs->bootdev
123                 or $KARGS_FLAGS_CD,0x8(%bx)     # kargs->bootflags |=
124                                                 #  KARGS_FLAGS_CD
125 #
126 # Load Volume Descriptor
127 #
128                 mov $VOLDESC_LBA,%eax           # Set LBA of first VD
129 load_vd:        push %eax                       # Save %eax
130                 mov $1,%dh                      # One sector
131                 mov $MEM_VOLDESC,%ebx           # Destination
132                 call read                       # Read it in
133                 cmpb $VD_PRIMARY,(%bx)          # Primary VD?
134                 je have_vd                      # Yes
135                 pop %eax                        # Prepare to
136                 inc %eax                        #  try next
137                 cmpb $VD_END,(%bx)              # Last VD?
138                 jne load_vd                     # No, read next
139                 mov $msg_novd,%si               # No VD
140                 jmp error                       # Halt
141 have_vd:                                        # Have Primary VD
142 #
143 # Lookup the loader binary.
144 #
145                 mov $loader_path,%si            # File to lookup
146                 call lookup                     # Try to find it
147 #
148 # Load the binary into the buffer.  Due to real mode addressing limitations
149 # we have to read it in in 64k chunks.
150 #
151                 mov DIR_SIZE(%bx),%eax          # Read file length
152                 add $SECTOR_SIZE-1,%eax         # Convert length to sectors
153                 shr $11,%eax
154                 cmp $BUFFER_LEN,%eax
155                 jbe load_sizeok
156                 mov $msg_load2big,%si           # Error message
157                 call error
158 load_sizeok:    movzbw %al,%cx                  # Num sectors to read
159                 mov DIR_EXTENT(%bx),%eax        # Load extent
160                 xor %edx,%edx
161                 mov DIR_EA_LEN(%bx),%dl
162                 add %edx,%eax                   # Skip extended
163                 mov $MEM_READ_BUFFER,%ebx       # Read into the buffer
164 load_loop:      mov %cl,%dh
165                 cmp $MAX_READ_SEC,%cl           # Truncate to max read size
166                 jbe load_notrunc
167                 mov $MAX_READ_SEC,%dh
168 load_notrunc:   sub %dh,%cl                     # Update count
169                 push %eax                       # Save
170                 call read                       # Read it in
171                 pop %eax                        # Restore
172                 add $MAX_READ_SEC,%eax          # Update LBA
173                 add $MAX_READ,%ebx              # Update dest addr
174                 jcxz load_done                  # Done?
175                 jmp load_loop                   # Keep going
176 load_done:
177 #
178 # Turn on the A20 address line
179 #
180                 call seta20                     # Turn A20 on
181 #
182 # Relocate the loader and BTX using a very lazy protected mode
183 #
184                 mov $msg_relocate,%si           # Display the
185                 call putstr                     #  relocation message
186                 mov MEM_READ_BUFFER+AOUT_ENTRY,%edi # %edi is the destination
187                 mov $(MEM_READ_BUFFER+AOUT_HEADER),%esi # %esi is
188                                                 #  the start of the text
189                                                 #  segment
190                 mov MEM_READ_BUFFER+AOUT_TEXT,%ecx # %ecx = length of the text
191                                                 #  segment
192                 push %edi                       # Save entry point for later
193                 lgdt gdtdesc                    # setup our own gdt
194                 cli                             # turn off interrupts
195                 mov %cr0,%eax                   # Turn on
196                 or $0x1,%al                     #  protected
197                 mov %eax,%cr0                   #  mode
198                 ljmp $SEL_SCODE,$pm_start       # long jump to clear the
199                                                 #  instruction pre-fetch queue
200                 .code32
201 pm_start:       mov $SEL_SDATA,%ax              # Initialize
202                 mov %ax,%ds                     #  %ds and
203                 mov %ax,%es                     #  %es to a flat selector
204                 rep                             # Relocate the
205                 movsb                           #  text segment
206                 add $(MEM_PAGE_SIZE - 1),%edi   # pad %edi out to a new page
207                 and $~(MEM_PAGE_SIZE - 1),%edi #  for the data segment
208                 mov MEM_READ_BUFFER+AOUT_DATA,%ecx # size of the data segment
209                 rep                             # Relocate the
210                 movsb                           #  data segment
211                 mov MEM_READ_BUFFER+AOUT_BSS,%ecx # size of the bss
212                 xor %eax,%eax                   # zero %eax
213                 add $3,%cl                      # round %ecx up to
214                 shr $2,%ecx                     #  a multiple of 4
215                 rep                             # zero the
216                 stosl                           #  bss
217                 mov MEM_READ_BUFFER+AOUT_ENTRY,%esi # %esi -> relocated loader
218                 add $MEM_BTX_OFFSET,%esi        # %esi -> BTX in the loader
219                 mov $MEM_BTX_ADDRESS,%edi       # %edi -> where BTX needs to go
220                 movzwl 0xa(%esi),%ecx           # %ecx -> length of BTX
221                 rep                             # Relocate
222                 movsb                           #  BTX
223                 ljmp $SEL_SCODE16,$pm_16        # Jump to 16-bit PM
224                 .code16
225 pm_16:          mov $SEL_RDATA,%ax              # Initialize
226                 mov %ax,%ds                     #  %ds and
227                 mov %ax,%es                     #  %es to a real mode selector
228                 mov %cr0,%eax                   # Turn off
229                 and $~0x1,%al                   #  protected
230                 mov %eax,%cr0                   #  mode
231                 ljmp $0,$pm_end                 # Long jump to clear the
232                                                 #  instruction pre-fetch queue
233 pm_end:         sti                             # Turn interrupts back on now
234 #
235 # Copy the BTX client to MEM_BTX_CLIENT
236 #
237                 xor %ax,%ax                     # zero %ax and set
238                 mov %ax,%ds                     #  %ds and %es
239                 mov %ax,%es                     #  to segment 0
240                 mov $MEM_BTX_CLIENT,%di         # Prepare to relocate
241                 mov $btx_client,%si             #  the simple btx client
242                 mov $(btx_client_end-btx_client),%cx # length of btx client
243                 rep                             # Relocate the
244                 movsb                           #  simple BTX client
245 #
246 # Copy the boot[12] args to where the BTX client can see them
247 #
248                 mov $MEM_ARG,%si                # where the args are at now
249                 mov $MEM_ARG_BTX,%di            # where the args are moving to
250                 mov $(MEM_ARG_SIZE/4),%cx       # size of the arguments in longs
251                 rep                             # Relocate
252                 movsl                           #  the words
253 #
254 # Save the entry point so the client can get to it later on
255 #
256                 pop %eax                        # Restore saved entry point
257                 stosl                           #  and add it to the end of
258                                                 #  the arguments
259 #
260 # Now we just start up BTX and let it do the rest
261 #
262                 mov $msg_jump,%si               # Display the
263                 call putstr                     #  jump message
264                 ljmp $0,$MEM_BTX_ENTRY          # Jump to the BTX entry point
265
266 #
267 # Lookup the file in the path at [SI] from the root directory.
268 #
269 # Trashes: All but BX
270 # Returns: BX = pointer to record
271 #
272 lookup:         mov $VD_ROOTDIR+MEM_VOLDESC,%bx # Root directory record
273                 push %si
274                 mov $msg_lookup,%si             # Display lookup message
275                 call putstr
276                 pop %si
277                 push %si
278                 call putstr
279                 mov $msg_lookup2,%si
280                 call putstr
281                 pop %si
282 lookup_dir:     lodsb                           # Get first char of path
283                 cmp $0,%al                      # Are we done?
284                 je lookup_done                  # Yes
285                 cmp $'/',%al                    # Skip path separator.
286                 je lookup_dir
287                 dec %si                         # Undo lodsb side effect
288                 call find_file                  # Lookup first path item
289                 jnc lookup_dir                  # Try next component
290                 mov $msg_lookupfail,%si         # Not found.
291                 jmp error
292 lookup_done:    mov $msg_lookupok,%si           # Success message
293                 call putstr
294                 ret
295
296 #
297 # Lookup file at [SI] in directory whose record is at [BX].
298 #
299 # Trashes: All but returns
300 # Returns: CF = 0 (success), BX = pointer to record, SX = next path item
301 #          CF = 1 (not found), SI = preserved
302 #
303 find_file:      mov DIR_EXTENT(%bx),%eax        # Load extent
304                 xor %edx,%edx
305                 mov DIR_EA_LEN(%bx),%dl
306                 add %edx,%eax                   # Skip extended attributes
307                 mov %eax,rec_lba                # Save LBA
308                 mov DIR_SIZE(%bx),%eax          # Save size
309                 mov %eax,rec_size
310                 xor %cl,%cl                     # Zero length
311                 push %si                        # Save
312 ff.namelen:     inc %cl                         # Update length
313                 lodsb                           # Read char
314                 cmp $0,%al                      # Nul?
315                 je ff.namedone                  # Yes
316                 cmp $'/',%al                    # Path separator?
317                 jnz ff.namelen                  # No, keep going
318 ff.namedone:    dec %cl                         # Adjust length and save
319                 mov %cl,name_len
320                 pop %si                         # Restore
321 ff.load:        mov rec_lba,%eax                # Load LBA
322                 mov $MEM_DIR,%ebx               # Address buffer
323                 mov $1,%dh                      # One sector
324                 call read                       # Read directory block
325                 incl rec_lba                    # Update LBA to next block
326 ff.scan:        mov %ebx,%edx                   # Check for EOF
327                 sub $MEM_DIR,%edx
328                 cmp %edx,rec_size
329                 ja ff.scan.1
330                 stc                             # EOF reached
331                 ret
332 ff.scan.1:      cmpb $0,DIR_LEN(%bx)            # Last record in block?
333                 je ff.nextblock
334                 push %si                        # Save
335                 movzbw DIR_NAMELEN(%bx),%si     # Find end of string
336 ff.checkver:    cmpb $'0',DIR_NAME-1(%bx,%si)   # Less than '0'?
337                 jb ff.checkver.1
338                 cmpb $'9',DIR_NAME-1(%bx,%si)   # Greater than '9'?
339                 ja ff.checkver.1
340                 dec %si                         # Next char
341                 jnz ff.checkver
342                 jmp ff.checklen                 # All numbers in name, so
343                                                 #  no version
344 ff.checkver.1:  movzbw DIR_NAMELEN(%bx),%cx
345                 cmp %cx,%si                     # Did we find any digits?
346                 je ff.checkdot                  # No
347                 cmpb $';',DIR_NAME-1(%bx,%si)   # Check for semicolon
348                 jne ff.checkver.2
349                 dec %si                         # Skip semicolon
350                 mov %si,%cx
351                 mov %cl,DIR_NAMELEN(%bx)        # Adjust length
352                 jmp ff.checkdot
353 ff.checkver.2:  mov %cx,%si                     # Restore %si to end of string
354 ff.checkdot:    cmpb $'.',DIR_NAME-1(%bx,%si)   # Trailing dot?
355                 jne ff.checklen                 # No
356                 decb DIR_NAMELEN(%bx)           # Adjust length
357 ff.checklen:    pop %si                         # Restore
358                 movzbw name_len,%cx             # Load length of name
359                 cmp %cl,DIR_NAMELEN(%bx)        # Does length match?
360                 je ff.checkname                 # Yes, check name
361 ff.nextrec:     add DIR_LEN(%bx),%bl            # Next record
362                 adc $0,%bh
363                 jmp ff.scan
364 ff.nextblock:   subl $SECTOR_SIZE,rec_size      # Adjust size
365                 jnc ff.load                     # If subtract ok, keep going
366                 ret                             # End of file, so not found
367 ff.checkname:   lea DIR_NAME(%bx),%di           # Address name in record
368                 push %si                        # Save
369                 repe cmpsb                      # Compare name
370                 jcxz ff.match                   # We have a winner!
371                 pop %si                         # Restore
372                 jmp ff.nextrec                  # Keep looking.
373 ff.match:       add $2,%sp                      # Discard saved %si
374                 clc                             # Clear carry
375                 ret
376
377 #
378 # Load DH sectors starting at LBA EAX into [EBX].
379 #
380 # Trashes: EAX
381 #
382 read:           push %si                        # Save
383                 mov %eax,edd_lba                # LBA to read from
384                 mov %ebx,%eax                   # Convert address
385                 shr $4,%eax                     #  to segment
386                 mov %ax,edd_addr+0x2            #  and store
387 read.retry:     call twiddle                    # Entertain the user
388                 push %dx                        # Save
389                 mov $edd_packet,%si             # Address Packet
390                 mov %dh,edd_len                 # Set length
391                 mov drive,%dl                   # BIOS Device
392                 mov $0x42,%ah                   # BIOS: Extended Read
393                 int $0x13                       # Call BIOS
394                 pop %dx                         # Restore
395                 jc read.fail                    # Worked?
396                 pop %si                         # Restore
397                 ret                             # Return
398 read.fail:      cmp $ERROR_TIMEOUT,%ah          # Timeout?
399                 je read.retry                   # Yes, Retry.
400 read.error:     mov %ah,%al                     # Save error
401                 mov $hex_error,%di              # Format it
402                 call hex8                       #  as hex
403                 mov $msg_badread,%si            # Display Read error message
404
405 #
406 # Display error message at [SI] and halt.
407 #
408 error:          call putstr                     # Display message
409 halt:           hlt
410                 jmp halt                        # Spin
411
412 #
413 # Display a null-terminated string.
414 #
415 # Trashes: AX, SI
416 #
417 putstr:         push %bx                        # Save
418 putstr.load:    lodsb                           # load %al from %ds:(%si)
419                 test %al,%al                    # stop at null
420                 jnz putstr.putc                 # if the char != null, output it
421                 pop %bx                         # Restore
422                 ret                             # return when null is hit
423 putstr.putc:    call putc                       # output char
424                 jmp putstr.load                 # next char
425
426 #
427 # Display a single char.
428 #
429 putc:           mov $0x7,%bx                    # attribute for output
430                 mov $0xe,%ah                    # BIOS: put_char
431                 int $0x10                       # call BIOS, print char in %al
432                 ret                             # Return to caller
433
434 #
435 # Output the "twiddle"
436 #
437 twiddle:        push %ax                        # Save
438                 push %bx                        # Save
439                 mov twiddle_index,%al           # Load index
440                 mov twiddle_chars,%bx           # Address table
441                 inc %al                         # Next
442                 and $3,%al                      #  char
443                 xlat                            # Get char
444                 call putc                       # Output it
445                 mov $8,%al                      # Backspace
446                 call putc                       # Output it
447                 pop %bx                         # Restore
448                 pop %ax                         # Restore
449                 ret
450
451 #
452 # Enable A20
453 #
454 seta20:         cli                             # Disable interrupts
455 seta20.1:       in $0x64,%al                    # Get status
456                 test $0x2,%al                   # Busy?
457                 jnz seta20.1                    # Yes
458                 mov $0xd1,%al                   # Command: Write
459                 out %al,$0x64                   #  output port
460 seta20.2:       in $0x64,%al                    # Get status
461                 test $0x2,%al                   # Busy?
462                 jnz seta20.2                    # Yes
463                 mov $0xdf,%al                   # Enable
464                 out %al,$0x60                   #  A20
465                 sti                             # Enable interrupts
466                 ret                             # To caller
467
468 #
469 # Convert AL to hex, saving the result to [EDI].
470 #
471 hex8:           pushl %eax                      # Save
472                 shrb $0x4,%al                   # Do upper
473                 call hex8.1                     #  4
474                 popl %eax                       # Restore
475 hex8.1:         andb $0xf,%al                   # Get lower 4
476                 cmpb $0xa,%al                   # Convert
477                 sbbb $0x69,%al                  #  to hex
478                 das                             #  digit
479                 orb $0x20,%al                   # To lower case
480                 stosb                           # Save char
481                 ret                             # (Recursive)
482
483 #
484 # BTX client to start btxldr
485 #
486                 .code32
487 btx_client:     mov $(MEM_ARG_BTX-MEM_BTX_CLIENT+MEM_ARG_SIZE-4), %esi
488                                                 # %ds:(%esi) -> end
489                                                 #  of boot[12] args
490                 mov $(MEM_ARG_SIZE/4),%ecx      # Number of words to push
491                 std                             # Go backwards
492 push_arg:       lodsl                           # Read argument
493                 push %eax                       # Push it onto the stack
494                 loop push_arg                   # Push all of the arguments
495                 cld                             # In case anyone depends on this
496                 pushl MEM_ARG_BTX-MEM_BTX_CLIENT+MEM_ARG_SIZE # Entry point of
497                                                 #  the loader
498                 push %eax                       # Emulate a near call
499                 mov $0x1,%eax                   # 'exec' system call
500                 int $INT_SYS                    # BTX system call
501 btx_client_end:
502                 .code16
503
504                 .p2align 4
505 #
506 # Global descriptor table.
507 #
508 gdt:            .word 0x0,0x0,0x0,0x0           # Null entry
509                 .word 0xffff,0x0,0x9200,0xcf    # SEL_SDATA
510                 .word 0xffff,0x0,0x9200,0x0     # SEL_RDATA
511                 .word 0xffff,0x0,0x9a00,0xcf    # SEL_SCODE (32-bit)
512                 .word 0xffff,0x0,0x9a00,0x8f    # SEL_SCODE16 (16-bit)
513 gdt.1:
514 #
515 # Pseudo-descriptors.
516 #
517 gdtdesc:        .word gdt.1-gdt-1               # Limit
518                 .long gdt                       # Base
519 #
520 # EDD Packet
521 #
522 edd_packet:     .byte 0x10                      # Length
523                 .byte 0                         # Reserved
524 edd_len:        .byte 0x0                       # Num to read
525                 .byte 0                         # Reserved
526 edd_addr:       .word 0x0,0x0                   # Seg:Off
527 edd_lba:        .quad 0x0                       # LBA
528
529 drive:          .byte 0
530
531 #
532 # State for searching dir
533 #
534 rec_lba:        .long 0x0                       # LBA (adjusted for EA)
535 rec_size:       .long 0x0                       # File size
536 name_len:       .byte 0x0                       # Length of current name
537
538 twiddle_index:  .byte 0x0
539
540 msg_welcome:    .asciz  "CD Loader 1.01\r\n\n"
541 msg_bootinfo:   .asciz  "Building the boot loader arguments\r\n"
542 msg_relocate:   .asciz  "Relocating the loader and the BTX\r\n"
543 msg_jump:       .asciz  "Starting the BTX loader\r\n"
544 msg_badread:    .ascii  "Read Error: 0x"
545 hex_error:      .ascii  "00\r\n"
546 msg_novd:       .asciz  "Could not find Primary Volume Descriptor\r\n"
547 msg_lookup:     .asciz  "Looking up "
548 msg_lookup2:    .asciz  "... "
549 msg_lookupok:   .asciz  "Found\r\n"
550 msg_lookupfail: .asciz  "File not found\r\n"
551 msg_load2big:   .asciz  "File too big\r\n"
552 loader_path:    .asciz  "/BOOT/LOADER"
553 twiddle_chars:  .ascii  "|/-\\"
554