1 ;;;;------------------------------------------------------------------
3 ;;;; Copyright (C) 2001,2000, 2002-2005,
4 ;;;; Department of Computer Science, University of Tromso, Norway
6 ;;;; Filename: bootblock.lisp
7 ;;;; Description: A simple, single-stage, floppy bootloader.
8 ;;;; Author: Frode Vatvedt Fjeld <frodef@acm.org>
9 ;;;; Created at: Mon Oct 9 20:47:19 2000
10 ;;;; Distribution: See the accompanying file COPYING.
12 ;;;; $Id: bootblock.lisp,v 1.16 2008/03/03 22:40:55 ffjeld Exp $
14 ;;;;------------------------------------------------------------------
18 (defvar *bootblock-build-file
* #p
"bootblock-id.txt")
19 (defvar *bootblock-build
*
20 ;; make this variable persistent.
22 (with-open-file (s *bootblock-build-file
* :direction
:input
)
23 (with-standard-io-syntax (read s
))))
24 (warn "Unable to read ~S from ~A, resetting to zero."
26 *bootblock-build-file
*)
29 (defvar *floppy-size
* (* 512 18 2 80))
31 (defun make-segment-descriptor-byte (&rest descriptor-args
)
32 (list (complex (binary-types::bitfield-compute-numeric-value
33 (find-binary-type 'segment-descriptor
)
34 (apply #'make-segment-descriptor descriptor-args
))
37 (defun mkasm16-bios-print ()
38 "Print something to the terminal. [es:si] points to the text"
49 (defun mkasm16-format-hex ()
50 "Format a 16-bit word (in DX) into hex string (in DI)"
58 (:movb
('hex-table bx
) :al
)
65 hex-table
(% format nil
"0123456789abcdef")))
67 (defconstant +SECTOR-SIZE
+ 512)
68 (defconstant +HEAD
+ 0)
69 (defconstant +TRACK
+ 1)
70 (defconstant +NOSEC
+ 2)
71 (defconstant +DADDR
+ 4)
72 (defconstant +DADDRSEG
+ 6)
73 (defconstant +STARTSEC
+ 8)
75 (defconstant +linear-sector
+ -
4)
76 (defconstant +destination
+ -
8)
77 (defconstant +sectors-per-track
+ -
12)
78 (defconstant +stack-frame-size
+ 16)
80 (defconstant +read-buffer
+ #x10000
)
82 (defun mkasm16-bios-bootloader (image-size load-address
&optional
(skip-sectors 0))
83 (let* ((first-sector (1+ skip-sectors
))
84 (last-sector (+ first-sector
(ceiling image-size
+sector-size
+)))
85 (read-buffer-segment (floor +read-buffer
+ #x10
)))
86 `((:jmp
(:pc
+ 0)) ; some BIOSes might check for this.
88 ;; We are running at address #x7c00.
97 (:leaw
(:bp
,(- +stack-frame-size
+)) :sp
)
98 (:movw
'welcome
:si
) ; Print welcome message)
102 ;; Enable the A20 gate
113 ;; Poll the floppy's sectors per track
115 (:movw
5 (:bp
,+sectors-per-track
+))
117 (:incb
(:bp
,+sectors-per-track
+))
119 (:movw
(:bp
,+sectors-per-track
+) :cx
)
122 (:movw
,read-buffer-segment
:bx
)
125 (:int
#x13
) ; Call BIOS routine
127 (:jz
'check-geometry
)
128 (:decb
(:bp
,+sectors-per-track
+))
131 ;; Read sectors into memory
134 (:movw
,first-sector
(:bp
,+linear-sector
+))
135 (:movl
,load-address
(:bp
,+destination
+))
139 (:cmpw
,last-sector
(:bp
,+linear-sector
+))
142 (:movw
'track-start-msg
:si
) ; Print '(' to screen for each track
145 (:movw
(:bp
,+linear-sector
+) :ax
)
146 (:movb
(:bp
,+sectors-per-track
+) :cl
)
147 (:divb
:cl
:ax
) ; al=quotient, ah=remainder of :ax/:cl
149 (:movb
:ah
:cl
) ; sector - 1
153 (:shrb
1 :ch
) ; track
154 (:xorb
:dl
:dl
) ; drive = 0
155 (:movw
(:bp
,+sectors-per-track
+) :ax
)
156 (:subb
:cl
:al
) ; number of sectors (rest of track)
158 (:addw
:ax
(:bp
,+linear-sector
+)) ; update read pointer
159 (:movw
(:bp
,+linear-sector
+) :bx
) ; subtract some if it's the last track.
160 (:subw
,last-sector
:bx
)
161 (:jc
'subtract-zero-sectors
)
164 subtract-zero-sectors
167 (:movw
,read-buffer-segment
:bx
)
170 (:int
#x13
) ; Call BIOS routine
176 ;; Install GS as 4GB segment
177 ;; http://www.faqs.org/faqs/assembly-language/x86/general/part2/
180 (:lgdt
('gdt-addr
)) ; load gdt
191 ;; Completed install GS as 4GB segment.
193 ;; Copy data to destination
194 (:shll
,(+ 9 -
2) :ecx
) ; 512/4 = sector-size/word-size
195 (:movl
,+read-buffer
+ :ebx
)
196 (:movl
(:bp
,+destination
+) :esi
)
197 (:leal
(:esi
(:ecx
4)) :edx
)
199 (:movl
:edx
(:bp
,+destination
+))
203 ((:gs-override
) :movl
(:ebx
(:ecx
4)) :edx
)
204 ((:gs-override
) :movl
:edx
(:esi
(:ecx
4)))
207 (:movw
'track-end-msg
:si
) ; Print ')' to screen after each track
214 motor-loop
; Wait for floppy motor
218 (:movw
'entering
:si
) ; Print welcome message
221 ;; Read the cursor position into DH (row) and DL (column).
226 (:cli
) ; Disable interrupts
227 (:lgdt
('gdt-addr
)) ; load gdt
230 (:movw
:ax
:es
) ; reset es
233 ;; Turn off the cursor
237 ;;; (movw #x0100 :cx)
242 ;; Load machine status word. This will enable
243 ;; protected mode. The subsequent instruction MUST
244 ;; reload the code segment register with a selector for
245 ;; the protected mode code segment descriptor (see
246 ;; GDT specification).
249 (:lmsw
:ax
) ; load word 0 of cr0
252 ;; Do a longjump to new-world. This will cause the CS to
253 ;; be loaded with the correct descriptor, and the processor
254 ;; will now run in 32 bit mode.
257 (:jmp
8 ('new-world
))
260 ;; Display error message and hang
263 (:movw
'error
:si
) ; Print error message
267 (:jmp
'halt-cpu
) ; Infinite loop
270 ;; Empty the 8042 Keyboard controller
274 (:inb
#x64
:al
) ; 8042 status port
275 (:testb
1 :al
) ; if ( no information available )
276 (:jz
'no-output
) ; goto no_output
278 (:inb
#x60
:al
) ; read it
281 (:testb
2 :al
) ; if ( input buffer is full )
282 (:jnz
'empty-8042
) ; goto empty_8042
291 print
,@(mkasm16-bios-print)
294 welcome
(:%
:format
8 "Loading Movitz ~D..~%
"
295 ,(incf *bootblock-build
*))
296 entering
(:%
:format
8 "~%
Enter..")
297 error
(:%
:format
8 "Failed!)")
298 track-start-msg
(:%
:format
8 "(")
299 track-end-msg
(:%
:format
8 ")")
300 sector-msg
(:%
:format
8 "-")
306 (:%
:bytes
16 ,(1- (* 3 8)))
307 (:%
:bytes
32 'gdt
) ; both the null and pointer to gdt
308 ;; (% fun (make-segment-descriptor-byte)) ; dummy null descriptor
309 (:%
:fun
(make-segment-descriptor-byte :base
0 :limit
#xfffff
; 1: code segment
312 (:%
:fun
(make-segment-descriptor-byte :base
0 :limit
#xfffff
; 2: data segment
317 ;; ..must be concatenated onto here.
321 (defconstant +screen-base
+ #xb8000
)
322 (defparameter +message
+ "Ok.")
323 (defparameter +halt-message
+ "Halt!")
325 (defun make-vga-string (string)
326 (loop for char across string
327 collect
(char-code char
)
330 (defun mkasm-loader (image-size load-address call-address
)
331 "Make the 32-bit loader."
332 (assert (<= load-address call-address
(+ load-address image-size
)) ()
333 "Call-address #x~X is not in range #x~X to #x~X."
334 call-address load-address
(+ load-address image-size
))
335 `((:movw
,(* 2 8) :ax
) ; Load DS, ES and SS with the correct data segment descriptors
343 ;;; (pushl -1) ; stack-end-marker
345 ;; If we are not on a 386, perform WBINVD to flush caches.
346 ;; (:testl :edi :edi) ; clear ZF
347 (:pushfl
) ; push original EFLAGS
348 (:popl
:eax
) ; get original EFLAGS
349 (:movl
:eax
:ecx
) ; save original EFLAGS
350 (:xorl
#x40000
:eax
) ; flip AC bit in EFLAGS
351 (:pushl
:eax
) ; save new EFLAGS value on stack
352 (:popfl
) ; replace current EFLAGS value
353 (:pushfl
) ; get new EFLAGS
354 (:popl
:eax
) ; store new EFLAGS in EAX
355 (:xorl
:ecx
:eax
) ; can't toggle AC bit, processor=80386, ZF=1
356 (:jz
'skip-wbinvd
) ; jump if 80386 processor
360 (:movzxb
:dl
:eax
) ; cursor column
361 (:movzxb
:dh
:ebx
) ; cursor row
363 (:imull
160 :ebx
:ebx
)
364 (:movl
'i-am-32
:esi
)
367 (:leal
((:eax
2) :ebx
,+screen-base
+) :edi
)
369 (:movb
,(length +message
+) :cl
)
370 ((:repz
) :movsw
) ; print i-am-32
372 (:movl
,call-address
:eax
)
373 (:jmp
:eax
) ; call OS
375 ;;; (:movl ,(length +halt-message+) :ecx)
376 ;;; (:movl 'halt-msg :esi)
377 ;;; (:movl ,(+ +screen-base+ (* 2 80 11) (* 2 35)) :edi)
380 ;;; (:movw #x7400 (:edi))
384 ;;; (:jmp 'eternal) ; OS returned?
386 i-am-32
(:%
:bytes
8 ,@(make-vga-string +message
+))
387 ;;; halt-msg (% fun ((lambda ()
388 ;;; (loop for char across ,+halt-message+
389 ;;; collect (complex (logior #x4700 (char-code char)) 2)))))
392 (defun make-bootblock (image-size load-address call-address
393 &key
(skip-sectors 0) (include-records))
395 (let ((floppy-room (- *floppy-size
* 512))) ; Size of floppy minus the bootloader.
396 (if (> image-size floppy-room
)
397 (warn "The image is ~D bytes too big to fit on a ~,2F MB floppy."
398 (- image-size floppy-room
)
399 (/ *floppy-size
* (* 1024 1000)))
400 (format t
"~&;; Bootloader has room for ~,1F KB more."
401 (/ (- floppy-room image-size
) 1024)))))
402 (multiple-value-bind (bios-loader bb-symtab
)
403 (let ((asm-x86:*position-independent-p
* nil
)
404 (asm-x86:*cpu-mode
* :16-bit
))
405 (asm:assemble-proglist
(mkasm16-bios-bootloader image-size load-address skip-sectors
)
407 (multiple-value-bind (protected-loader protected-symtab
)
408 (let ((asm-x86:*position-independent-p
* nil
)
409 (asm-x86:*cpu-mode
* :32-bit
))
410 (asm:assemble-proglist
(mkasm-loader image-size load-address call-address
)
411 :start-pc
(cdr (or (assoc 'new-world bb-symtab
)
412 (error "No new-world defined in bios-loader.")))))
413 (let* ((loader-length (+ (length bios-loader
)
414 (length protected-loader
)))
416 (assert (<= loader-length
510) ()
417 "Bootblock size of ~D octets is too big, max is 510!" loader-length
)
418 (make-array 512 :element-type
'(unsigned-byte 8)
419 :fill-pointer loader-length
))))
420 (setf (subseq bootblock
0) bios-loader
421 (subseq bootblock
(length bios-loader
)) protected-loader
)
422 (loop until
(zerop (mod (fill-pointer bootblock
) 4))
423 do
(vector-push 0 bootblock
))
424 (dolist (record include-records
)
425 (let ((*endian
* :little-endian
))
426 (with-binary-output-to-vector (stream bootblock
)
427 (write-binary-record record stream
))))
428 (setf (fill-pointer bootblock
) 512
429 (subseq bootblock
510) #(#x55
#xaa
)) ; bootblock signature
430 (format t
"~&;; Bootblock size is ~D octets.~%" loader-length
)
431 (format t
"~&;; Bootblock build ID: ~D.~%" *bootblock-build
*)
432 (with-open-file (s #p
"bootblock-id.txt"
434 :if-exists
:supersede
)
435 (with-standard-io-syntax
436 (write *bootblock-build
* :stream s
)))
437 (values bootblock
(append bb-symtab protected-symtab
))))))