blob: a9b5f39d77a06876e4129fd0e57878a59773ba12 [file] [log] [blame] [raw]
/*
* dosstart.S -- DOS EXE and Linux kernel startup code for GNU GRUB
* Copyright (C) 2003-2007 Tinybit(tinybit@tom.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/* Note for License: Since we have copied the Linux kernel code and the
* Linux kernel follows GPL2, so only GPL2 is adoptable and it rules here.
*/
/*
* This program is used to generate the grub.exe file, which can boot off DOS
* directly or, in another way, boot off Linux through KEXEC.
*
* Use the following shell command to generate the grub.exe file:
*
* cat dosstart pre_stage2 > grub.exe
*
*/
#define ASM_FILE
#include <shared.h>
#include <stage2_size.h>
//#define ABS_PSP(x) (x - _start - 0x100)
//#define ABS_PSP(x) (x - _start + 0x0E0)
#define ABS_PSP(x) (x - _start - 0x200)
#define ABS_START(x) (x - _start - 0x200)
//#define ABS_START(x) (x - _start - 0x020)
#define ABS_FINAL(x) (x - pre_stage2_start + 0x200)
/* HMA_backup immediately follows the main body of stage2, para aligned */
//#define HMA_backup ((STAGE2_SIZE + pre_stage2_start - _start - 0x100 + 15) / 16)
//#define HMA_backup ((STAGE2_SIZE + pre_stage2_start - _start + 0x0E0 + 15) / 16)
#define HMA_backup (ABS_PSP(STAGE2_SIZE + pre_stage2_start + 15) / 16)
.file "dosstart.S"
.text
/* Tell GAS to generate 16-bit instructions so that this code works
in real mode. */
.code16
.globl start, _start
//start:
_start:
/* 1 sector of EXE header is used by DOS. Actually only the leading
* 32 bytes are used for EXE header. At offset 0x20(=32) begins the
* DOS device driver structure. And at offset 0x1f1(=497) begins
* the Linux kernel structure.
*/
/*
* The dos startup code counts N=(pre_stage2_start-_start)/512 sectors:
*
* 1 sector for DOS EXE header, followed by (N-1) sectors of code.
* Of the (N-1) code sectors, the first (N-2) sectors are preparational
* sectors, and the last sector is called the FINAL sector. The
* FINAL sector contains code that finally launches the stage2 code.
* So pay special attention to the FINAL sector. Do not destroy
* the FINAL sector upon memory move operations.
*/
.byte 0x4d, 0x5a /* 'MZ', the EXE magic number. */
/* EXE file last sector in bytes */
.word ((STAGE2_SIZE + pre_stage2_start - _start) % 512)
#define exe_sectors ((STAGE2_SIZE + pre_stage2_start - _start + 511) / 512 )
.word exe_sectors /* total sectors the EXE file occupies */
.word 0 /* relocation table entries */
.word 0x0002 /* header size in 16-byte paragraphs.
0x0002 paragraphs == 32 bytes */
.word 0x1000 + 0x0300 + 0x0040 #; MinAlloc paragraphs:
#; 64KB for HMA_backup
#; 12KB for VCPI page_info
#; 1KB for restoring EBDA
.word 0x1000 + 0x0300 + 0x0040 #; MaxAlloc paragraphs:
#; no more memory needed, so
#; MaxAlloc == MinAlloc
.word 0x001E /* initial SS, relative to the
* so-called START segment */
.word ABS_START(dos_stack) /* initial SP, rich room for stack */
.word 0 /* checksum, ignored by dos */
.word ABS_START(_dos_start) /* initial IP */
.word 0x001E /* initial CS, relative to START segment */
.word 0x001C /* displacement of the relocation table */
.word 0 /* overlay serial number. 0 for main program */
/* DOS program begins at _dos_start. CS==SS==START, DS==ES==PSP */
/* For DOS device driver, _START + 0x20 locates CS:0000 */
#-----------------------------------------------------------------------------
# DOS device driver structure begins here
#-----------------------------------------------------------------------------
. = _start + 0x20
#if 0
# SKELETON.ASM - A SKELETON DEVICE DRIVER V1.0
# (C) COPYRIGHT 1994 G.KLEIN
#
#This file contains structures, sample code and information intended
# to aid in building a device driver. It is set up as a skeleton, i.e.
# hardly any working code is provided. Only the Initialisation command
# is implemented - it displays a signon message and the commandline
# it was loaded from (excluding the "device[high]=" part). Sample code
# is provided both to let the driver load as to let it unload itself
# from memory after the initialisation command is processed. There is
# also a bit of code showing how to implement the driver as an EXE file,
# with different code depending on weather it is loaded from config.sys
# or from the command prompt.
#Although the major part of this file contains structures and other info,
# is assembles to a "working" device driver. It can be loaded from
# config.sys, and displays a signon message. When run from the commandline,
# it also displays a (different) message. It has been tested with and
# assembles "out of the box" under TASM V2.0 and MASM V5.0.
#The contents of this file is copyrighted. It may be used freely for
# whatever purpose you see it fit. Although a big effort has been made
# to ensure all information in this file is correct, no guarantees can
# be given. Any comments you may have on this file or the (correctness of)
# the information therein are welcome. Here is how you can reach me:
#
# Fidonet: Gertjan Klein @ 2:280/901.14
# Internet: gklein@hacktic.nl (Gertjan Klein)
# as of Sept. 1994: gklein@xs4all.nl (Gertjan Klein)
#####################################################################
#Some structures describing the request headers for the various commands.
# In the comment field, a "<" means this is input to the command, and ">"
# means this is output from the command. It is indicated weather the
# command applies to block (B) or character (C) device drivers, or both
# (B/C). If a command is defined later then version 2.0, the DOS version
# in which it first appears is placed in parenthesis. This also applies
# to the fields in the request headers.
rh0 struc #0 - Initialisation (B/C)
rh0_len db ? # < Length of packet
rh0_unit db ? #Unused
rh0_cmd db 0 # < Device driver command
rh0_stat dw ? # > Status returned by device driver
rh0_rsvd db 8 dup (?) #Reserved
rh0_units db ? # > Number of units (B)
rh0_break dd ? # < End of driver memory (5.0+)
# > Break address (new end drvr mem)
rh0_bpb dd ? # < Pointer to cmdline (inc. name)
# > Pointer to BPB pointers array (B)
rh0_drv_ltr db ? # < First available drive (B, 3.0+)
rh0_msgflag dw ? # > 1 To make DOS show errors (5.0+)
rh0 ends
rh1 struc #1 - Media Check (B)
rh1_len db ? # < Length of packet
rh1_unit db ? # < Unit code
rh1_cmd db 1 # < Device driver command
rh1_stat dw ? # > Status returned by device driver
rh1_rsvd db 8 dup (?) #Reserved
rh1_media db ? # < Current media descriptor
rh1_md_stat db ? # > Media status (changed/not/?)
rh1_volID dd ? # > Ptr to Volume ID (3.0+)
rh1 ends
rh2 struc #2 - Get BPB (B)
rh2_len db ? # < Length of packet
rh2_unit db ? # < Unit code
rh2_cmd db 2 # < Device driver command
rh2_stat dw ? # > Status returned by device driver
rh2_rsvd db 8 dup (?) #Reserved
rh2_media db ? # < Current media descriptor
rh2_buff dd ? # < Pointer to buffer
rh2_bpb dd ? # > Pointer to BPB
rh2 ends
rh3 struc #3 - IOCTL input (B/C)
rh3_len db ? # < Length of packet
rh3_unit db ? # < Unit code (B)
rh3_cmd db 3 # < Device driver command
rh3_stat dw ? # > Status returned by device driver
rh3_rsvd db 8 dup (?) #Reserved
rh3_media db ? #Unused
rh3_buff dd ? # < Pointer to buffer
rh3_count dw ? # < Transfer count (bytes)
# > Nr of bytes read
rh3 ends
rh4 struc #4 - Input (B/C)
rh4_len db ? # < Length of packet
rh4_unit db ? # < Unit code (B)
rh4_cmd db 4 # < Device driver command
rh4_stat dw ? # > Status returned by device driver
rh4_rsvd db 8 dup (?) #Reserved
rh4_media db ? # < Media descriptor byte (B)
rh4_buff dd ? # < Pointer to data transfer area
rh4_count dw ? # < Transfer count (B:sectors,C:bytes)
# > Nr of sectors/bytes read
rh4_start dw ? # < Start sector number (B)
rh4_volID dd ? # > Pointer to volume ID (B, 5.0+)
rh4_HugeStart dd ? # < 32-bits start sector (B, 5.0+)
# (if rh4_count = 0FFFFh)
rh4 ends
rh5 struc #5 - Non-destructive input (C)
rh5_len db ? # < Length of packet
rh5_unit db ? #Unused
rh5_cmd db 5 # < Device driver command
rh5_stat dw ? # > Status returned by device driver
rh5_rsvd db 8 dup (?) #Reserved
rh5_char db ? # > Character returned
rh5 ends
rh6 struc #6 - Input status (C)
rh6_len db ? # < Length of packet
rh6_unit db ? #Unused
rh6_cmd db 6 # < Device driver command
rh6_stat dw ? # > Status returned by device driver
rh6_rsvd db 8 dup (?) #Reserved
rh6 ends
rh7 struc #7 - Input flush (C)
rh7_len db ? # < Length of packet
rh7_unit db ? #Unused
rh7_cmd db 7 # < Device driver command
rh7_stat dw ? # > Status returned by device driver
rh7_rsvd db 8 dup (?) #Reserved
rh7 ends
rh8 struc #8 - Output (B/C)
rh8_len db ? # < Length of packet
rh8_unit db ? # < Unit code (B)
rh8_cmd db 8 # < Device driver command
rh8_stat dw ? # > Status returned by device driver
rh8_rsvd db 8 dup (?) #Reserved
rh8_media db ? # < Media descriptor byte (B)
rh8_buff dd ? # < Pointer to data transfer area
rh8_count dw ? # < Transfer count (B:sectors,C:bytes)
# > Nr of sectors/bytes read
rh8_start dw ? # < Start sector number (B)
rh8_volID dd ? # > Pointer to volume ID (B, 5.0+)
rh8_HugeStart dd ? # < 32-bits start sector (B, 5.0+)
# (if rh8_count = 0FFFFh)
rh8 ends
rh9 struc #9 - Output with verify (B/C)
rh9_len db ? # < Length of packet
rh9_unit db ? # < Unit code (B)
rh9_cmd db 9 # < Device driver command
rh9_stat dw ? # > Status returned by device driver
rh9_rsvd db 8 dup (?) #Reserved
rh9_media db ? # < Media descriptor byte (B)
rh9_buff dd ? # < Pointer to data transfer area
rh9_count dw ? # < Transfer count (B:sectors,C:bytes)
# > Nr of sectors/bytes read
rh9_start dw ? # < Start sector number (B)
rh9_volID dd ? # > Pointer to volume ID (B, 5.0+)
rh9_HugeStart dd ? # < 32-bits start sector (B, 5.0+)
# (if rh9_count = 0FFFFh)
rh9 ends
rh0A struc #0Ah - Output status (C)
rh0A_len db ? # < Length of packet
rh0A_unit db ? #Unused
rh0A_cmd db 0ah # < Device driver command
rh0A_stat dw ? # > Status returned by device driver
rh0A_rsvd db 8 dup (?) #Reserved
rh0A ends
rh0B struc #0Bh - Output flush (C)
rh0B_len db ? # < Length of packet
rh0B_unit db ? #Unused
rh0B_cmd db 0bh # < Device driver command
rh0B_stat dw ? # > Status returned by device driver
rh0B_rsvd db 8 dup (?) #Reserved
rh0B ends
rh0C struc #0Ch - IOCTL output (B/C)
rh0C_len db ? # < Length of packet
rh0C_unit db ? # < Unit code (B)
rh0C_cmd db 0ch # < Device driver command
rh0C_stat dw ? # > Status returned by device driver
rh0C_rsvd db 8 dup (?) #Reserved
rh0C_media db ? #Unused
rh0C_buff dd ? # < Pointer to buffer
rh0C_count dw ? # < Transfer count (bytes)
# > Nr of bytes written
rh0C ends
rh0D struc #0Dh - Open (B/C) (3.0+)
rh0D_len db ? # < Length of packet
rh0D_unit db ? # < Unit code (B)
rh0D_cmd db 0dh # < Device driver command
rh0D_stat dw ? # > Status returned by device driver
rh0D_rsvd db 8 dup (?) #Reserved
rh0D ends
rh0E struc #0Eh - Close (B/C) (3.0+)
rh0E_len db ? # < Length of packet
rh0E_unit db ? # < Unit code (B)
rh0E_cmd db 0eh # < Device driver command
rh0E_stat dw ? # > Status returned by device driver
rh0E_rsvd db 8 dup (?) #Reserved
rh0E ends
rh0F struc #0Fh - Removable media (B) (3.0+)
rh0F_len db ? # < Length of packet
rh0F_unit db ? # < Unit code
rh0F_cmd db 0fh # < Device driver command
rh0F_stat dw ? # > Status returned by device driver
rh0F_rsvd db 8 dup (?) #Reserved
rh0F ends
rh10 struc #10h - Output until busy (C) (3.0+)
rh10_len db ? # < Length of packet
rh10_unit db ? #Unused
rh10_cmd db 10h # < Device driver command
rh10_stat dw ? # > Status returned by device driver
rh10_rsvd db 8 dup (?) #Reserved
rh10_media db ? #Unused
rh10_buff dd ? # < Pointer to buffer
rh10_count dw ? # < Transfer count (B:sectors,C:bytes)
# > Nr of sectors/bytes written
rh10 ends
#Commands 11h and 12h are undefined
rh13 struc #13h - Generic IOCTL (3.2+:B, 3.3+:B/C)
rh13_len db ? # < Length of packet
rh13_unit db ? # < Unit code (B)
rh13_cmd db 13h # < Device driver command
rh13_stat dw ? # > Status returned by device driver
rh13_rsvd db 8 dup (?) #Reserved
rh13_major db ? # < Category
rh13_minor db ? # < Minor function
rh13_SI dw ? # < Contents of SI register
rh13_DI dw ? # < Contents of DI register
rh13_pkt dd ? # < Pointer to generic IOCTL req.
rh13 ends
#Commands 14 to 16 are undefined
rh17 struc #17h - Get device (B) (3.2+)
rh17_len db ? # < Length of packet
rh17_unit db ? # < Unit number to check
# > Last active drive (or 0)
rh17_cmd db 17h # < Device driver command
rh17_stat dw ? # > Status returned by device driver
rh17_rsvd db 8 dup (?) #Reserved
rh17 ends
rh18 struc #18h - Set device (B) (3.2+)
rh18_len db ? # < Length of packet
rh18_unit db ? # < Unit to make active
rh18_cmd db 18h # < Device driver command
rh18_stat dw ? # > Status returned by device driver
rh18_rsvd db 8 dup (?) #Reserved
rh18 ends
rh19 struc #19h - IOCTL query (B/C) (5.0+)
rh19_len db ? # < Length of packet
rh19_unit db ? # < Unit code (B)
rh19_cmd db 19h # < Device driver command
rh19_stat dw ? # > Status returned by device driver
rh19_rsvd db 8 dup (?) #Reserved
rh19_major db ? # < Category
rh19_minor db ? # < Minor function
rh19_SI dw ? # < Contents of SI register
rh19_DI dw ? # < Contents of DI register
rh19_pkt dd ? # < Pointer to IOCTL query req.
rh19 ends
#endif
nxtdev: .long -1 #Pointer to next device driver in chain
attr: .word 0x8000 #Attribute: character device
strat: .word strategy - nxtdev #Address of strategy routine
intr: .word interrupt - nxtdev #Address of interrupt routine
dname: .ascii "GRUB4DOS" #Name of the device driver (C), or
# (1 byte:) number of devices (B)
#The attribute word is defined as follows:
# Bit 0: (C): 1 if driver is stdin
# 1: (C): 1 if driver is stdout
# (B): 1 if driver supports 32-bit sector addressing
# (commands 4, 8 and 9)
# 2: (C): 1 if driver is NUL device (only allowed for DOS's own driver)
# 3: (C): 1 if driver is clock device
# 4: (C): 1 if driver supports fast character I/O (int 29h)
# 5: undefined
# 6: (C/B): 0 if driver supports commands 17h and 18h and/or 13h
# (get/set logical drive and generic IOCTL)
# 7: (C/B): 1 if driver supports IOCTL query (command 19h)
# 8: undefined
# 9: undefined
# 10: undefined
# 11: (C/B): 1 if driver supports commands 0dh, 0eh and 0fh
# (Open/close device and removable media)
# 12: (C): 1 if driver supports output until busy (command 10h)
# (B): 1 if driver requires first FAT sector for build BPB
# (command 2)
# 13: undefined
# 14: (C/B): 1 if driver supports IOCTL read and write (commands 3
# and 0ch)
# 15: (C): 1 if driver supports a character device
# (B): 0 if driver supports a block device
ReqHeader:
RhOffs: .word 0 #Saved offset of request header
RhSeg: .word 0 #Saved segment of request header
strategy:
movw %es, %cs:(RhSeg - nxtdev) #Save request header segment
movw %bx, %cs:(RhOffs - nxtdev) #Save request header offset
lret #Return to caller
#The following command table is initialised to the exit routines to
# use when the command is not implemented. In this skeleton, only Init
# is implemented. Change the pointers as you add routines.
#Normally, at least the following routines are required:
# For character device drivers: 0,4,5,6,7,8,9,0ah,0bh
# For block devices : 0,1,2,4,8,9
//cmdtab: .word Init - nxtdev # 0 Initialisation command
// .word Exit_Done - nxtdev # 1 Media_check
// .word Exit_Done - nxtdev # 2 Build BPB
// .word Exit_UC - nxtdev # 3 IOCtl in
// .word Exit_Done - nxtdev # 4 Input
// .word Exit_Busy - nxtdev # 5 Non-destructive input
// .word Exit_Done - nxtdev # 6 Input status
// .word Exit_Done - nxtdev # 7 Input flush
// .word Exit_Done - nxtdev # 8 Output
// .word Exit_Done - nxtdev # 9 Output with verify
// .word Exit_Done - nxtdev #0Ah Output status
// .word Exit_Done - nxtdev #0Bh Output flush
// .word Exit_UC - nxtdev #0Ch IOCtl out
// .word Exit_Done - nxtdev #0Dh Device open (3.0+)
// .word Exit_Done - nxtdev #0Eh Device close (3.0+)
// .word Exit_UC - nxtdev #0Fh Removable media (3.0+)
// .word Exit_UC - nxtdev #10h Output till busy (3.0+)
// .word Exit_UC - nxtdev #11h (Unused)
// .word Exit_UC - nxtdev #12h (Unused)
// .word Exit_UC - nxtdev #13h Generic IOCTL (3.2:B, 3.3+:B/C)
// .word Exit_UC - nxtdev #14h (Unused)
// .word Exit_UC - nxtdev #15h (Unused)
// .word Exit_UC - nxtdev #16h (Unused)
// .word Exit_UC - nxtdev #17h Get logical device (3.2+)
// .word Exit_UC - nxtdev #18h Set logical device (3.2+)
// .word Exit_UC - nxtdev #19h IOCTL query (5.0+)
interrupt:
pushfw
cli
cld
pushaw
pushw %ds #Save segment registers
pushw %es
movw %cs, %ax #Make DS point to us
movw %ax, %ds
lesw ReqHeader - nxtdev, %bx #Get ptr to request header in ES:BX
movb %es:2(%bx), %al #Get command
// cmpb $0x19, %al #Check if within range
// ja Exit_UC # no: set unknown command bit and exit
// shlb $1, %al # yes: make word index
// xorb %ah, %ah
// movw %ax, %di #Place it in index register
// jmp *(cmdtab - nxtdev)(%di) # and jump to apropriate routine
testb %al, %al
jz Init
//Exit_Busy:
// orw $0x0200, %es:3(%bx)
// jmp Exit_Done
//Exit_UC:
orw $0x8103, %es:3(%bx) #Set ERROR, DONE and UNKNOWN COMMAND
exit:
popw %es #Restore segment registers
popw %ds
popaw
popfw
lret #Return to caller
Init:
#if 0
/* Display CS and the original value at break address. */
movw %cs, %bp
call display_word
movw $0x0e0d, %ax
int $0x10
movw $0x0e0a, %ax
int $0x10
lesw ReqHeader - nxtdev, %bx #Get ptr to request header in ES:BX
movl %es:14(%bx), %esi #Set zero break address
movl %esi, %ebp
shrl $16, %ebp
call display_word
movl %esi, %ebp
call display_word
movw $0x0e0d, %ax
int $0x10
movw $0x0e0a, %ax
int $0x10
#endif
#if 0
/* Show commandline (including driver name) */
movb $3, %ah #Read cursor position
xorb %bh, %bh # for video page 0
int $0x10 #(BIOS) (Returned in dx)
lesw ReqHeader - nxtdev, %bx #Get ptr to request header in ES:BX
lesw %es:18(%bx), %bp #Get pointer to commandline in ES:BP
movw %bp, %di #Get pointer in ES:DI too
movb $0x0A, %al #Terminating character of commandline
movw $2048, %cx #Search max 2048 bytes
cld
repnz scasb #Search for it
movw %di, %cx #Get pointer to end of string
subw %bp, %cx #Calculate length
movw $0x1301, %ax #Write string in teletype mode
movw $2, %bx # with attribute green
int $0x10 #(BIOS)
#endif
#if 1
/* DS=CS */
lesw ReqHeader - nxtdev, %bx #Get ptr to request header in ES:BX
ldsw %es:18(%bx), %si #Get pointer to commandline in DS:SI
movw $0x0080, %di #Command line will be moved to here
#as if it is in a PSP
movw %cs, %ax
addw $((start - nxtdev) >> 4), %ax
movw %ax, %es
cld
xorw %ax, %ax
stosb #store 0 at offset 0x0080, this is an
#indicator for device driver than EXE
/* DS:SI points to driver name */
movw $0xfff, %cx /* move at most 4095 bytes */
/* first, skip all blank characters. */
1:
lodsb
cmpb $0x20, %al /* 0x20 is space bar */
je 1b
cmpb $0x09, %al /* 0x09 is TAB */
je 1b
decw %si
/* secondly, skip the name of the driver. */
1:
lodsb
cmpb $0x20, %al /* 0x20 is space bar */
ja 1b
/* finally, copy the rest of the command line. */
1:
cmpb $0x0A, %al /* 0x0A is LF */
jne 2f
movb $0x0D, %al /* 0x0D is CR */
2:
cmpb $0x0D, %al /* 0x0D is CR */
je 3f
cmpb $0x09, %al /* 0x09 is the TAB key. */
jne 2f
movb $0x20, %al
2:
stosb
lodsb
loop 1b
3:
movb $0x00, %al /* Ends in a NULL */
stosb /* the last char stored is NULL */
#endif
movw %cs, %ax
movw %ax, %ds
lesw ReqHeader - nxtdev, %bx #Get ptr to request header back
orw $0x810C, %es:3(%bx) #Set ERROR and DONE bits
# and OTHER ERROR code
movb $0, %es:13(%bx) #Clear UNITS byte for CHAR device
andw $0x7FFF, attr - nxtdev #Clear CHAR bit in device attrib eord
xorw %ax, %ax
xchgw %ax, %es:14(%bx) #Set zero break address
/* MS-DOS will initialize the break address as XXXX:0000. */
testw %ax, %ax #Is it MS-DOS 5.0+ ?
movw %cs, %ax
xchgw %ax, %es:16(%bx) #Set break address segment
jnz 1f #no, do not check the end address.
/* yes, check the memory available for this device driver. */
movw %cs, %bx #the driver start segment
cmpw %bx, %ax #Is the end address too low?
jbe 1f #yes, do not check the end address.
cmpw $0xe000, %ax #Is the end address too high?
jae 1f #yes, do not check the end address.
/* check if we have 64K room available at the end of the driver image.
* This 64K space will be used for backup the HMA. Exit here if no
* enough memory. And 12K room for VCPI page_info, and additional
* 1K room for restoring EBDA from (nearly)bottom to (nearly)top of
* the conventional memory.
*/
movw %cs, %bx #the driver start segment
addw $((exe_sectors * 0x20) + 0x1000 + 0x0300 + 0x0040), %bx
#0x1000 paragraphs = 64K (HMA_backup)
#0x0300 paragraphs = 12K (page_info)
#0x0040 paragraphs = 1K (EBDA)
jc 2f #load too high, failure
cmpw %bx, %ax
jnb 1f #check passed, continue
2:
/* print error message. */
movb $3, %ah #Read cursor position
xorb %bh, %bh # for video page 0
int $0x10 #(BIOS) (Returned in dx)
movw $0x1301, %ax #Write string in teletype mode
movb $3, %bl # with attribute cyan
movw $(nomem_end - nomem), %cx # length of string
movw $(nomem - nxtdev), %bp # start of string
pushw %cs # in es:bp
popw %es
int $0x10 #(BIOS)
jmp exit
1:
//jmp 2b
#if 1
/* Because our stack will be changed, we must backup all registers. */
movw %cs, %ax
//movw %ax, %ds # DS=CS
// /* DS=CS=device driver start. Adjust the exit address. */
// movw %ax, (device_driver_exit_cs - nxtdev)
addw $((start - nxtdev) >> 4), %ax
movw %ax, %es /* ES:0000 now points to START */
cld
xorw %di, %di
stosl /* EAX */
movl %ebx, %eax
stosl /* EBX */
movl %ecx, %eax
stosl /* ECX */
movl %edx, %eax
stosl /* EDX */
movl %edi, %eax
stosl /* EDI */
movl %esp, %eax
stosl /* ESP */
movl %ebp, %eax
stosl /* EBP */
movw %ss, %ax
stosw /* SS */
movw %fs, %ax
stosw /* FS */
movw %gs, %ax
stosw /* GS */
pushfl
popl %eax
stosl /* EFLAGS */
movl %esi, %eax
stosl /* ESI */
/* modify the stack */
cli
movw %es, %ax /* ES:0000 now points to START */
movw %ax, %ds
movw %ax, %ss
movw $ABS_START(dos_stack), %sp
/* far jump to _dos_start1. */
/* Note: DS=ES=CS tells _dos_start we are from device driver. */
pushw %es
pushw $ABS_START(_dos_start1)
lret
/* if need be, we could come back here from _dos_start1. */
device_driver_exit:
// /* far jump to load CS */
//
// .byte 0xEA /* opcode for ljmp */
// .word (device_driver_exit1 - nxtdev)
// /* offset for destination */
//device_driver_exit_cs:
// .word 0 /* filled with driver CS */
//
//device_driver_exit1:
cli
cld
movw %cs, %ax
movw %ax, %ds
movw $(start - nxtdev), %si
lodsl /* EAX */
lodsl /* EBX */
xchgl %eax, %ebx
lodsl /* ECX */
xchgl %eax, %ecx
lodsl /* EDX */
xchgl %eax, %edx
lodsl /* EDI */
xchgl %eax, %edi
lodsl /* ESP */
xchgl %eax, %esp
lodsl /* EBP */
xchgl %eax, %ebp
lodsw /* SS */
movw %ax, %ss
lodsw /* FS */
movw %ax, %fs
lodsw /* GS */
movw %ax, %gs
/* SS:SP has been restored, so it is safe to use stack. */
lodsl /* EFLAGS */
pushl %eax
popfl
/* direction flag here is UPWARD as we saved it before. */
/* Another note: the direction flag does not matter, because
* this is the last LODS instruction executed before we exit.
*/
lodsl /* ESI */
xchgl %eax, %esi
jmp exit
#endif
#if 0
display_word:
/* routine for display hex value of BP */
xorw %bx, %bx
movw %bp, %ax
shrw $12, %ax
cmpb $9, %al
jbe 1f
addb $7, %al
1:
addb $0x30, %al
movb $0x0e, %ah
int $0x10
movw %bp, %ax
shrw $8, %ax
andb $0x0f, %al
cmpb $9, %al
jbe 1f
addb $7, %al
1:
addb $0x30, %al
movb $0x0e, %ah
int $0x10
movw %bp, %ax
shrw $4, %ax
andb $0x0f, %al
cmpb $9, %al
jbe 1f
addb $7, %al
1:
addb $0x30, %al
movb $0x0e, %ah
int $0x10
movw %bp, %ax
andb $0x0f, %al
cmpb $9, %al
jbe 1f
addb $7, %al
1:
addb $0x30, %al
movb $0x0e, %ah
int $0x10
ret
#endif
nomem:
.ascii "\r\nError: No enough memory to run GRUB.EXE with the DEVICE command in CONFIG.SYS\r\n"
nomem_end:
#-----------------------------------------------------------------------------
# DOS device driver structure ends here
#-----------------------------------------------------------------------------
#undef ENTRY
#define __ASSEMBLY__
#define __KERNEL__
#define __BIG_KERNEL__
#define SVGA_MODE NORMAL_VGA
/* stolen from linux-2.6.13.1/arch/i386/boot/bootsect.S */
/*
* bootsect.S Copyright (C) 1991, 1992 Linus Torvalds
*
* modified by Drew Eckhardt
* modified by Bruce Evans (bde)
* modified by Chris Noe (May 1999) (as86 -> gas)
* gutted by H. Peter Anvin (Jan 2003)
*
* BIG FAT NOTE: We're in real mode using 64k segments. Therefore segment
* addresses must be multiplied by 16 to obtain their respective linear
* addresses. To avoid confusion, linear addresses are written using leading
* hex while segment addresses are written as segment:offset.
*
*/
#include <asm/boot.h>
SETUPSECTS = 4 /* default nr of setup-sectors */
BOOTSEG = 0x07C0 /* original address of boot-sector */
INITSEG = DEF_INITSEG /* we move boot here - out of the way */
SETUPSEG = DEF_SETUPSEG /* setup starts here */
SYSSEG = DEF_SYSSEG /* system loaded at 0x10000 (65536) */
SYSSIZE = DEF_SYSSIZE /* system size: # of 16-byte clicks */
/* to be loaded */
ROOT_DEV = 0 /* ROOT_DEV is now written by "build" */
SWAP_DEV = 0 /* SWAP_DEV is now written by "build" */
#ifndef SVGA_MODE
#define SVGA_MODE ASK_VGA
#endif
#ifndef RAMDISK
#define RAMDISK 0
#endif
#ifndef ROOT_RDONLY
#define ROOT_RDONLY 1
#endif
//.code16
//.text
//.global _start
//_start:
#if 0
# Normalize the start address
jmpl $BOOTSEG, $(start2 - _start)
start2:
movw %cs, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %ss
movw $0x7c00, %sp
sti
cld
movw $(bugger_off_msg - _start), %si
msg_loop:
lodsb
andb %al, %al
jz die
movb $0xe, %ah
movw $7, %bx
int $0x10
jmp msg_loop
die:
# Allow the user to press a key, then reboot
xorw %ax, %ax
int $0x16
int $0x19
# int 0x19 should never return. In case it does anyway,
# invoke the BIOS reset code...
ljmp $0xf000,$0xfff0
bugger_off_msg:
.ascii "Direct booting from floppy is no longer supported.\r\n"
.ascii "Please use a boot loader program instead.\r\n"
.ascii "\n"
.ascii "Remove disk and press any key to reboot . . .\r\n"
.byte 0
#endif
#-----------------------------------------------------------------------------
# Linux kernel structure begins here
#-----------------------------------------------------------------------------
# Kernel attributes; used by setup
#if 0
.org 497
setup_sects: .byte SETUPSECTS
#else
. = _start + 0x1f1
setup_sects: .byte ((startup_32 - _start) >> 9) - 1
#endif
root_flags: .word ROOT_RDONLY
syssize: .word SYSSIZE
swap_dev: .word SWAP_DEV
ram_size: .word RAMDISK
vid_mode: .word SVGA_MODE
root_dev: .word ROOT_DEV
#if 1
boot_flag: .word 0xAA55
#else
/* the sector is not bootable since it begins in a EXE header. */
boot_flag: .word 0
#endif
. = _start + 0x200
/* For Linux kernel and DOS EXE, START locates CS:0000 */
/* For DOS EXE, START also locates CS:0000 */
#-----------------------------------------------------------------------------
# DOS device driver and EXE use 9 sectors here for storing registers and
# the 4096-byte command line. The command line is at offset 0x0081.
#-----------------------------------------------------------------------------
/* stolen from linux-2.6.13.1/arch/i386/boot/setup.S */
/*
* setup.S Copyright (C) 1991, 1992 Linus Torvalds
*
* setup.s is responsible for getting the system data from the BIOS,
* and putting them into the appropriate places in system memory.
* both setup.s and system has been loaded by the bootblock.
*
* This code asks the bios for memory/disk/other parameters, and
* puts them in a "safe" place: 0x90000-0x901FF, ie where the
* boot-block used to be. It is then up to the protected mode
* system to read them from there before the area is overwritten
* for buffer-blocks.
*
* Move PS/2 aux init code to psaux.c
* (troyer@saifr00.cfsat.Honeywell.COM) 03Oct92
*
* some changes and additional features by Christoph Niemann,
* March 1993/June 1994 (Christoph.Niemann@linux.org)
*
* add APM BIOS checking by Stephen Rothwell, May 1994
* (sfr@canb.auug.org.au)
*
* High load stuff, initrd support and position independency
* by Hans Lermen & Werner Almesberger, February 1996
* <lermen@elserv.ffm.fgan.de>, <almesber@lrc.epfl.ch>
*
* Video handling moved to video.S by Martin Mares, March 1996
* <mj@k332.feld.cvut.cz>
*
* Extended memory detection scheme retwiddled by orc@pell.chi.il.us (david
* parsons) to avoid loadlin confusion, July 1997
*
* Transcribed from Intel (as86) -> AT&T (gas) by Chris Noe, May 1999.
* <stiker@northlink.com>
*
* Fix to work around buggy BIOSes which don't use carry bit correctly
* and/or report extended memory in CX/DX for e801h memory size detection
* call. As a result the kernel got wrong figures. The int15/e801h docs
* from Ralf Brown interrupt list seem to indicate AX/BX should be used
* anyway. So to avoid breaking many machines (presumably there was a reason
* to orginally use CX/DX instead of AX/BX), we do a kludge to see
* if CX/DX have been changed in the e801 call and if so use AX/BX .
* Michael Miller, April 2001 <michaelm@mjmm.org>
*
* New A20 code ported from SYSLINUX by H. Peter Anvin. AMD Elan bugfixes
* by Robert Schwebel, December 2001 <robert@schwebel.de>
*/
#include <linux/config.h>
#include <asm/segment.h>
#include <linux/version.h>
#include <linux/compile.h>
#include <asm/boot.h>
#include <asm/e820.h>
#include <asm/page.h>
/* Signature words to ensure LILO loaded us right */
#define SIG1 0xAA55
#define SIG2 0x5A5A
INITSEG = DEF_INITSEG # 0x9000, we move boot here, out of the way
SYSSEG = DEF_SYSSEG # 0x1000, system loaded at 0x10000 (65536).
SETUPSEG = DEF_SETUPSEG # 0x9020, this is the current segment
# ... and the former contents of CS
DELTA_INITSEG = SETUPSEG - INITSEG # 0x0020
.code16
.globl begtext, begdata, begbss, endtext, enddata, endbss
.text
begtext:
.data
begdata:
.bss
begbss:
.text
start:
jmp trampoline
# This is the setup header, and it must start at %cs:2 (old 0x9020:2)
.ascii "HdrS" # header signature
.word 0x0203 # header version number (>= 0x0105)
# or else old loadlin-1.5 will fail)
realmode_swtch: .word 0, 0 # default_switch, SETUPSEG
start_sys_seg: .word SYSSEG
.word kernel_version - start
# pointing to kernel version string
# above section of header is compatible
# with loadlin-1.5 (header v1.5). Don't
# change it.
type_of_loader: .byte 0 # = 0, old one (LILO, Loadlin,
# Bootlin, SYSLX, bootsect...)
# See Documentation/i386/boot.txt for
# assigned ids
# flags, unused bits must be zero (RFU) bit within loadflags
loadflags:
LOADED_HIGH = 1 # If set, the kernel is loaded high
CAN_USE_HEAP = 0x80 # If set, the loader also has set
# heap_end_ptr to tell how much
# space behind setup.S can be used for
# heap purposes.
# Only the loader knows what is free
#ifndef __BIG_KERNEL__
.byte 0
#else
.byte LOADED_HIGH
#endif
setup_move_size: .word 0x8000 # size to move, when setup is not
# loaded at 0x90000. We will move setup
# to 0x90000 then just before jumping
# into the kernel. However, only the
# loader knows how much data behind
# us also needs to be loaded.
code32_start: # here loaders can put a different
# start address for 32-bit code.
#ifndef __BIG_KERNEL__
.long 0x1000 # 0x1000 = default for zImage
#else
.long 0x100000 # 0x100000 = default for big kernel
#endif
ramdisk_image: .long 0 # address of loaded ramdisk image
# Here the loader puts the 32-bit
# address where it loaded the image.
# This only will be read by the kernel.
ramdisk_size: .long 0 # its size in bytes
bootsect_kludge:
.long 0 # obsolete
heap_end_ptr: .word modelist-start+1024
# (Header version 0x0201 or later)
# space from here (exclusive) down to
# end of setup code can be used by setup
# for local heap purposes.
pad1: .word 0
cmd_line_ptr: .long 0 # (Header version 0x0202 or later)
# If nonzero, a 32-bit pointer
# to the kernel command line.
# The command line should be
# located between the start of
# setup and the end of low
# memory (0xa0000), or it may
# get overwritten before it
# gets read. If this field is
# used, there is no longer
# anything magical about the
# 0x90000 segment; the setup
# can be located anywhere in
# low memory 0x10000 or higher.
ramdisk_max: .long (-__PAGE_OFFSET-(512 << 20)-1) & 0x7fffffff
# (Header version 0x0203 or later)
# The highest safe address for
# the contents of an initrd
trampoline: call start_of_setup
.align 16
# The offset at this point is 0x240
.space (0xeff-0x240+1) # E820 & EDD space (ending at 0xeff)
# End of setup header #####################################################
start_of_setup:
# Bootlin depends on this being done early
movw $0x01500, %ax
movb $0x81, %dl
int $0x13
#ifdef SAFE_RESET_DISK_CONTROLLER
# Reset the disk controller.
movw $0x0000, %ax
movb $0x80, %dl
int $0x13
#endif
# Set %ds = %cs, we know that SETUPSEG = %cs at this point
movw %cs, %ax # aka SETUPSEG
movw %ax, %ds
# Check signature at end of setup
cmpw $SIG1, setup_sig1 - start
jne bad_sig
cmpw $SIG2, setup_sig2 - start
jne bad_sig
jmp good_sig1
no_sig_mess: .string "No setup signature found ..."
good_sig1:
jmp good_sig
# We now have to find the rest of the setup code/data
bad_sig:
movw %cs, %ax # SETUPSEG
subw $DELTA_INITSEG, %ax # INITSEG
movw %ax, %ds
xorb %bh, %bh
movb (497), %bl # get setup sect from bootsect
subw $4, %bx # LILO loads 4 sectors of setup
shlw $8, %bx # convert to words (1sect=2^8 words)
movw %bx, %cx
shrw $3, %bx # convert to segment
addw $SYSSEG, %bx
movw %bx, %cs:(start_sys_seg - start)
# Move rest of setup code/data to here
movw $2048, %di # four sectors loaded by LILO
subw %si, %si
pushw %cs
popw %es
movw $SYSSEG, %ax
movw %ax, %ds
rep
movsw
movw %cs, %ax # aka SETUPSEG
movw %ax, %ds
cmpw $SIG1, setup_sig1 - start
jne no_sig
cmpw $SIG2, setup_sig2 - start
jne no_sig
jmp good_sig
no_sig:
lea no_sig_mess - start, %si
call prtstr
no_sig_loop:
hlt
jmp no_sig_loop
good_sig:
movw %cs, %ax # aka SETUPSEG
subw $DELTA_INITSEG, %ax # aka INITSEG
movw %ax, %ds
# Check if an old loader tries to load a big-kernel
testb $LOADED_HIGH, %cs:loadflags - start # Do we have a big kernel?
jz loader_ok # No, no danger for old loaders.
cmpb $0, %cs:type_of_loader - start # Do we have a loader that
# can deal with us?
jnz loader_ok # Yes, continue.
pushw %cs # No, we have an old loader,
popw %ds # die.
lea loader_panic_mess - start, %si
call prtstr
jmp no_sig_loop
loader_panic_mess: .string "Wrong loader, giving up..."
loader_ok:
#; GRUB4DOS needn't get memory size
jmp get_mem_size_end
# Get memory size (extended mem, kB)
xorl %eax, %eax
movl %eax, (0x1e0)
#ifndef STANDARD_MEMORY_BIOS_CALL
movb %al, (E820NR)
# Try three different memory detection schemes. First, try
# e820h, which lets us assemble a memory map, then try e801h,
# which returns a 32-bit memory size, and finally 88h, which
# returns 0-64m
# method E820H:
# the memory map from hell. e820h returns memory classified into
# a whole bunch of different types, and allows memory holes and
# everything. We scan through this memory map and build a list
# of the first 32 memory areas, which we return at [E820MAP].
# This is documented at http://www.acpi.info/, in the ACPI 2.0 specification.
#define SMAP 0x534d4150
meme820:
xorl %ebx, %ebx # continuation counter
movw $E820MAP, %di # point into the whitelist
# so we can have the bios
# directly write into it.
jmpe820:
movl $0x0000e820, %eax # e820, upper word zeroed
movl $SMAP, %edx # ascii 'SMAP'
movl $20, %ecx # size of the e820rec
pushw %ds # data record.
popw %es
int $0x15 # make the call
jc bail820 # fall to e801 if it fails
cmpl $SMAP, %eax # check the return is `SMAP'
jne bail820 # fall to e801 if it fails
# cmpl $1, 16(%di) # is this usable memory?
# jne again820
# If this is usable memory, we save it by simply advancing %di by
# sizeof(e820rec).
#
good820:
movb (E820NR), %al # up to 128 entries
cmpb $E820MAX, %al
jae bail820
incb (E820NR)
movw %di, %ax
addw $20, %ax
movw %ax, %di
again820:
cmpl $0, %ebx # check to see if
jne jmpe820 # %ebx is set to EOF
bail820:
# method E801H:
# memory size is in 1k chunksizes, to avoid confusing loadlin.
# we store the 0xe801 memory size in a completely different place,
# because it will most likely be longer than 16 bits.
# (use 1e0 because that's what Larry Augustine uses in his
# alternative new memory detection scheme, and it's sensible
# to write everything into the same place.)
meme801:
stc # fix to work around buggy
xorw %cx,%cx # BIOSes which don't clear/set
xorw %dx,%dx # carry on pass/error of
# e801h memory size call
# or merely pass cx,dx though
# without changing them.
movw $0xe801, %ax
int $0x15
jc mem88
cmpw $0x0, %cx # Kludge to handle BIOSes
jne e801usecxdx # which report their extended
cmpw $0x0, %dx # memory in AX/BX rather than
jne e801usecxdx # CX/DX. The spec I have read
movw %ax, %cx # seems to indicate AX/BX
movw %bx, %dx # are more reasonable anyway...
e801usecxdx:
andl $0xffff, %edx # clear sign extend
shll $6, %edx # and go from 64k to 1k chunks
movl %edx, (0x1e0) # store extended memory size
andl $0xffff, %ecx # clear sign extend
addl %ecx, (0x1e0) # and add lower memory into
# total size.
# Ye Olde Traditional Methode. Returns the memory size (up to 16mb or
# 64mb, depending on the bios) in ax.
mem88:
#endif
movb $0x88, %ah
int $0x15
movw %ax, (2)
get_mem_size_end:
# Set the keyboard repeat rate to the max
movw $0x0305, %ax
xorw %bx, %bx
int $0x16
#; grub4dos needn't get video, hd, mouse, APM, edd
#; jmp get_misc_info_end
#;
#;# Check for video adapter and its parameters and allow the
#;# user to browse video modes.
#; call video # NOTE: we need %ds pointing
#; # to bootsector
#;
#;# Get hd0 data...
#; xorw %ax, %ax
#; movw %ax, %ds
#; ldsw (4 * 0x41), %si
#; movw %cs, %ax # aka SETUPSEG
#; subw $DELTA_INITSEG, %ax # aka INITSEG
#; pushw %ax
#; movw %ax, %es
#; movw $0x0080, %di
#; movw $0x10, %cx
#; pushw %cx
#; cld
#; rep
#; movsb
#;# Get hd1 data...
#; xorw %ax, %ax
#; movw %ax, %ds
#; ldsw (4 * 0x46), %si
#; popw %cx
#; popw %es
#; movw $0x0090, %di
#; rep
#; movsb
#;# Check that there IS a hd1 :-)
#; movw $0x01500, %ax
#; movb $0x81, %dl
#; int $0x13
#; jc no_disk1
#;
#; cmpb $3, %ah
#; je is_disk1
#;
#;no_disk1:
#; movw %cs, %ax # aka SETUPSEG
#; subw $DELTA_INITSEG, %ax # aka INITSEG
#; movw %ax, %es
#; movw $0x0090, %di
#; movw $0x10, %cx
#; xorw %ax, %ax
#; cld
#; rep
#; stosb
#;is_disk1:
#;# check for Micro Channel (MCA) bus
#; movw %cs, %ax # aka SETUPSEG
#; subw $DELTA_INITSEG, %ax # aka INITSEG
#; movw %ax, %ds
#; xorw %ax, %ax
#; movw %ax, (0xa0) # set table length to 0
#; movb $0xc0, %ah
#; stc
#; int $0x15 # moves feature table to es:bx
#; jc no_mca
#;
#; pushw %ds
#; movw %es, %ax
#; movw %ax, %ds
#; movw %cs, %ax # aka SETUPSEG
#; subw $DELTA_INITSEG, %ax # aka INITSEG
#; movw %ax, %es
#; movw %bx, %si
#; movw $0xa0, %di
#; movw (%si), %cx
#; addw $2, %cx # table length is a short
#; cmpw $0x10, %cx
#; jc sysdesc_ok
#;
#; movw $0x10, %cx # we keep only first 16 bytes
#;sysdesc_ok:
#; rep
#; movsb
#; popw %ds
#;no_mca:
#;#ifdef CONFIG_X86_VOYAGER
#; movb $0xff, 0x40 # flag on config found
#; movb $0xc0, %al
#; mov $0xff, %ah
#; int $0x15 # put voyager config info at es:di
#; jc no_voyager
#; movw $0x40, %si # place voyager info in apm table
#; cld
#; movw $7, %cx
#;voyager_rep:
#; movb %es:(%di), %al
#; movb %al,(%si)
#; incw %di
#; incw %si
#; decw %cx
#; jnz voyager_rep
#;no_voyager:
#;#endif
#;# Check for PS/2 pointing device
#; movw %cs, %ax # aka SETUPSEG
#; subw $DELTA_INITSEG, %ax # aka INITSEG
#; movw %ax, %ds
#; movw $0, (0x1ff) # default is no pointing device
#; int $0x11 # int 0x11: equipment list
#; testb $0x04, %al # check if mouse installed
#; jz no_psmouse
#;
#; movw $0xAA, (0x1ff) # device present
#;no_psmouse:
#;
#;#if defined(CONFIG_X86_SPEEDSTEP_SMI) || defined(CONFIG_X86_SPEEDSTEP_SMI_MODULE)
#; movl $0x0000E980, %eax # IST Support
#; movl $0x47534943, %edx # Request value
#; int $0x15
#;
#; movl %eax, (96)
#; movl %ebx, (100)
#; movl %ecx, (104)
#; movl %edx, (108)
#;#endif
#;
#;#if defined(CONFIG_APM) || defined(CONFIG_APM_MODULE)
#;# Then check for an APM BIOS...
#; # %ds points to the bootsector
#; movw $0, 0x40 # version = 0 means no APM BIOS
#; movw $0x05300, %ax # APM BIOS installation check
#; xorw %bx, %bx
#; int $0x15
#; jc done_apm_bios # Nope, no APM BIOS
#;
#; cmpw $0x0504d, %bx # Check for "PM" signature
#; jne done_apm_bios # No signature, no APM BIOS
#;
#; andw $0x02, %cx # Is 32 bit supported?
#; je done_apm_bios # No 32-bit, no (good) APM BIOS
#;
#; movw $0x05304, %ax # Disconnect first just in case
#; xorw %bx, %bx
#; int $0x15 # ignore return code
#; movw $0x05303, %ax # 32 bit connect
#; xorl %ebx, %ebx
#; xorw %cx, %cx # paranoia :-)
#; xorw %dx, %dx # ...
#; xorl %esi, %esi # ...
#; xorw %di, %di # ...
#; int $0x15
#; jc no_32_apm_bios # Ack, error.
#;
#; movw %ax, (66) # BIOS code segment
#; movl %ebx, (68) # BIOS entry point offset
#; movw %cx, (72) # BIOS 16 bit code segment
#; movw %dx, (74) # BIOS data segment
#; movl %esi, (78) # BIOS code segment lengths
#; movw %di, (82) # BIOS data segment length
#;# Redo the installation check as the 32 bit connect
#;# modifies the flags returned on some BIOSs
#; movw $0x05300, %ax # APM BIOS installation check
#; xorw %bx, %bx
#; xorw %cx, %cx # paranoia
#; int $0x15
#; jc apm_disconnect # error -> shouldn't happen
#;
#; cmpw $0x0504d, %bx # check for "PM" signature
#; jne apm_disconnect # no sig -> shouldn't happen
#;
#; movw %ax, (64) # record the APM BIOS version
#; movw %cx, (76) # and flags
#; jmp done_apm_bios
#;
#;apm_disconnect: # Tidy up
#; movw $0x05304, %ax # Disconnect
#; xorw %bx, %bx
#; int $0x15 # ignore return code
#;
#; jmp done_apm_bios
#;
#;no_32_apm_bios:
#; andw $0xfffd, (76) # remove 32 bit support bit
#;done_apm_bios:
#;#endif
#;
#;#include "edd.S"
#;
#;get_misc_info_end:
# Now we want to move to protected mode ...
cmpw $0, %cs:realmode_swtch - start
jz rmodeswtch_normal
lcall *%cs:realmode_swtch - start
jmp rmodeswtch_end
rmodeswtch_normal:
pushw %cs
call default_switch
rmodeswtch_end:
# we get the code32 start address and modify the below 'jmpi'
# (loader may have changed it)
movl %cs:code32_start - start, %eax
movl %eax, %cs:code32 - start
# Now we move the system to its rightful place ... but we check if we have a
# big-kernel. In that case we *must* not move it ...
testb $LOADED_HIGH, %cs:loadflags - start
jz do_move0 # .. then we have a normal low
# loaded zImage
# .. or else we have a high
# loaded bzImage
jmp end_move # ... and we skip moving
do_move0:
movw $0x100, %ax # start of destination segment
movw %cs, %bp # aka SETUPSEG
subw $DELTA_INITSEG, %bp # aka INITSEG
movw %cs:start_sys_seg - start, %bx # start of source segment
cld
do_move:
movw %ax, %es # destination segment
incb %ah # instead of add ax,#0x100
movw %bx, %ds # source segment
addw $0x100, %bx
subw %di, %di
subw %si, %si
movw $0x800, %cx
rep
movsw
cmpw %bp, %bx # assume start_sys_seg > 0x200,
# so we will perhaps read one
# page more than needed, but
# never overwrite INITSEG
# because destination is a
# minimum one page below source
jb do_move
end_move:
# then we load the segment descriptors
movw %cs, %ax # aka SETUPSEG
movw %ax, %ds
# Check whether we need to be downward compatible with version <=201
cmpl $0, cmd_line_ptr - start
jne end_move_self # loader uses version >=202 features
cmpb $0x20, type_of_loader - start
je end_move_self # bootsect loader, we know of it
# Boot loader doesnt support boot protocol version 2.02.
# If we have our code not at 0x90000, we need to move it there now.
# We also then need to move the params behind it (commandline)
# Because we would overwrite the code on the current IP, we move
# it in two steps, jumping high after the first one.
movw %cs, %ax
cmpw $SETUPSEG, %ax
je end_move_self
cli # make sure we really have
# interrupts disabled !
# because after this the stack
# should not be used
subw $DELTA_INITSEG, %ax # aka INITSEG
movw %ss, %dx
cmpw %ax, %dx
jb move_self_1
addw $INITSEG, %dx
subw %ax, %dx # this will go into %ss after
# the move
move_self_1:
movw %ax, %ds
movw $INITSEG, %ax # real INITSEG
movw %ax, %es
movw %cs:setup_move_size - start, %cx
std # we have to move up, so we use
# direction down because the
# areas may overlap
movw %cx, %di
decw %di
movw %di, %si
subw $(move_self_here - start+0x200), %cx
rep
movsb
ljmp $SETUPSEG, $(move_self_here - start)
move_self_here:
movw $(move_self_here - start+0x200), %cx
rep
movsb
movw $SETUPSEG, %ax
movw %ax, %ds
movw %dx, %ss
end_move_self: # now we are at the right place
#
# Enable A20. This is at the very best an annoying procedure.
# A20 code ported from SYSLINUX 1.52-1.63 by H. Peter Anvin.
# AMD Elan bug fix by Robert Schwebel.
#
movw $(a20_control_begin_string - start), %si
call prtstr
movw $0x00ff, %cx # try so many times on failure
movw $0x0101, %dx # non-zero means `enable'
call enable_disable_a20
jz 3f
movw $(a20_control_fail_string - start), %si
call prtstr
1:
hlt
jmp 1b
3:
movw $(a20_control_ok_string - start), %si
call prtstr
# set up gdt and idt
lidt idt_48 - start # load idt with 0,0
xorl %eax, %eax # Compute gdt_base
movw %ds, %ax # (Convert %ds:gdt to a linear ptr)
shll $4, %eax
addl $gdt - start, %eax
movl %eax, (gdt_48 - start+2)
lgdt gdt_48 - start # load gdt with whatever is
# appropriate
# make sure any possible coprocessor is properly reset..
xorw %ax, %ax
outb %al, $0xf0
call delay
outb %al, $0xf1
call delay
# well, that went ok, I hope. Now we mask all interrupts - the rest
# is done in init_IRQ().
movb $0xFF, %al # mask all interrupts for now
outb %al, $0xA1
call delay
movb $0xFB, %al # mask all irq's but irq2 which
outb %al, $0x21 # is cascaded
# Well, that certainly wasn't fun :-(. Hopefully it works, and we don't
# need no steenking BIOS anyway (except for the initial loading :-).
# The BIOS-routine wants lots of unnecessary data, and it's less
# "interesting" anyway. This is how REAL programmers do it.
#
# Well, now's the time to actually move into protected mode. To make
# things as simple as possible, we do no register set-up or anything,
# we let the gnu-compiled 32-bit programs do that. We just jump to
# absolute address 0x1000 (or the loader supplied one),
# in 32-bit protected mode.
#
# Note that the short jump isn't strictly needed, although there are
# reasons why it might be a good idea. It won't hurt in any case.
movw $1, %ax # protected mode (PE) bit
lmsw %ax # This is it!
jmp flush_instr
flush_instr:
xorw %bx, %bx # Flag to indicate a boot
xorl %esi, %esi # Pointer to real-mode code
movw %cs, %si
subw $DELTA_INITSEG, %si
shll $4, %esi # Convert to 32-bit pointer
# jump to startup_32 in arch/i386/boot/compressed/head.S
#
# NOTE: For high loaded big kernels we need a
# jmpi 0x100000,__BOOT_CS
#
# but we yet haven't reloaded the CS register, so the default size
# of the target offset still is 16 bit.
# However, using an operand prefix (0x66), the CPU will properly
# take our 48 bit far pointer. (INTeL 80386 Programmer's Reference
# Manual, Mixing 16-bit and 32-bit code, page 16-6)
.byte 0x66, 0xea # prefix + jmpi-opcode
code32: .long 0x1000 # will be set to 0x100000
# for big kernels
.word __BOOT_CS
# Here's a bunch of information about your current kernel..
kernel_version: .ascii UTS_RELEASE
.ascii " ("
.ascii LINUX_COMPILE_BY
.ascii "@"
.ascii LINUX_COMPILE_HOST
.ascii ") "
.ascii UTS_VERSION
.byte 0
# This is the default real mode switch routine.
# to be called just before protected mode transition
default_switch:
cli # no interrupts allowed !
movb $0x80, %al # disable NMI for bootup
# sequence
outb %al, $0x70
lret
#-----------------------------------------------------------------------------
# DOS device driver and EXE will use this code to enable/disable A20
#-----------------------------------------------------------------------------
// to asure the command-line menu will not overwrite our code here
. = start + 0x1000 + 0x82 + (. - (start + 0x1000 + 0x82)) * (1 & (. > (start + 0x1000 + 0x82)))
. = . - ((0x1000 + 0x81) / (. - start))
/* print ASCIZ string DS:SI (modifies AX BX SI) */
1:
xorw %bx, %bx /* video page 0 */
movb $0x0e, %ah /* print char in AL */
int $0x10 /* via TTY mode */
prtstr:
lodsb /* get token */
cmpb $0, %al /* end of string? */
jne 1b
ret
//# Routine to print asciiz string at ds:si
//prtstr:
//2:
// lodsb
// andb %al, %al
// jz 2f
//
// call 1f //prtchr
// jmp 2b
//
//2:
// ret
//
//# Space printing
//prtsp2: call prtspc # Print double space
//prtspc: movb $0x20, %al # Print single space (note: fall-thru)
//
//# Part of above routine, this one just prints ascii al
//prtchr:
//1:
// pushw %ax
// pushw %cx
// movw $7,%bx
// movw $0x01, %cx
// movb $0x0e, %ah
// int $0x10
// popw %cx
// popw %ax
// ret
//
//beep:
// movb $0x07, %al
// jmp 1b //prtchr
a20_control_begin_string:
.ascii "\r\nkernel grub.exe: Trying to turn on A20 gate ...\r\n\0$"
a20_control_ok_string:
.ascii "\r\nkernel grub.exe: A20 gate turned on OK!\r\n\0$"
a20_control_fail_string:
.ascii "\r\nkernel grub.exe: A20 gate not responding!\r\n\0$"
#include "a20.inc"
# Read the cmos clock. Return the seconds in al
gettime:
pushw %cx
movb $0x02, %ah
int $0x1a
movb %dh, %al # %dh contains the seconds
andb $0x0f, %al
movb %dh, %ah
movb $0x04, %cl
shrb %cl, %ah
aad
popw %cx
ret
# Descriptor tables
#
# NOTE: The intel manual says gdt should be sixteen bytes aligned for
# efficiency reasons. However, there are machines which are known not
# to boot with misaligned GDTs, so alter this at your peril! If you alter
# GDT_ENTRY_BOOT_CS (in asm/segment.h) remember to leave at least two
# empty GDT entries (one for NULL and one reserved).
#
# NOTE: On some CPUs, the GDT must be 8 byte aligned. This is
# true for the Voyager Quad CPU card which will not boot without
# This directive. 16 byte aligment is recommended by intel.
#
.align 16
gdt:
.fill GDT_ENTRY_BOOT_CS,8,0
.word 0xFFFF # 4Gb - (0x100000*0x1000 = 4Gb)
.word 0 # base address = 0
.word 0x9A00 # code read/exec
.word 0x00CF # granularity = 4096, 386
# (+5th nibble of limit)
.word 0xFFFF # 4Gb - (0x100000*0x1000 = 4Gb)
.word 0 # base address = 0
.word 0x9200 # data read/write
.word 0x00CF # granularity = 4096, 386
# (+5th nibble of limit)
gdt_end:
.align 4
.word 0 # alignment byte
idt_48:
.word 0 # idt limit = 0
.word 0, 0 # idt base = 0L
.word 0 # alignment byte
gdt_48:
.word gdt_end - gdt - 1 # gdt limit
.word 0, 0 # gdt base (filled in later)
# Include video setup & detection code
#;#include "video.S"
# Setup signature -- must be last
setup_sig1: .word SIG1
setup_sig2: .word SIG2
# After this point, there is some free space which is used by the video mode
# handling code to store the temporary mode table (not used by the kernel).
modelist:
.text
endtext:
.data
enddata:
.bss
endbss:
/* stolen from linux-2.6.13.1/arch/i386/boot/compressed/head.S */
/*
* linux/boot/head.S
*
* Copyright (C) 1991, 1992, 1993 Linus Torvalds
*/
/*
* head.S contains the 32-bit startup code.
*
* NOTE!!! Startup happens at absolute address 0x00001000, which is also where
* the page directory will exist. The startup code will be overwritten by
* the page directory. [According to comments etc elsewhere on a compressed
* kernel it will end up at 0x1000 + 1Mb I hope so as I assume this. - AC]
*
* Page 0 is deliberately kept safe, since System Management Mode code in
* laptops may need to access the BIOS data stored there. This is also
* useful for future device drivers that either access the BIOS via VM86
* mode.
*/
/*
* High loaded stuff by Hans Lermen & Werner Almesberger, Feb. 1996
*/
.text
#include <linux/linkage.h>
#include <asm/segment.h>
. = _start + 0x2000
.code32
.align 0x200
.globl startup_32
//#define debug_putchar(x) movw $(x | ((x << 12) & 0xF000) | (((x << 8) & 0x0F00) ^ 0xC00)), (0xB8140 + (((x - 0x30) % 80) * 2))
#define debug_putchar(x)
startup_32:
cld
cli
movl $(__BOOT_DS), %eax
movl %eax, %ds
movl %eax, %es
movl %eax, %fs
movl %eax, %gs
movl %eax, %ss
debug_putchar('0')
// movw $0x0C30, 0xB80A0
/* we use space near the end of real mode IDT for temp stack */
movl $0x400, %esp
xorl %eax,%eax
/*
* Initialize eflags. Some BIOS's leave bits like NT set. This would
* confuse the debugger if this code is traced.
* XXX - best to initialize before switching to protected mode.
*/
//pushl $0
pushl %eax # EAX=0
popfl # cli, cld by the way
1: incl %eax # check that A20 really IS enabled
movl %eax,0x000000 # loop forever if it isn't
cmpl %eax,0x100000
je 1b
debug_putchar('1')
// movw $0x1D31, (0xB80A0 + (1 * 2))
call 1f # just calculate the EIP register
1:
popl %ebp # EBP=linear address of 1b, i.e., current EIP
subl $(1b - startup_32), %ebp # EBP=linear address of startup_32
#if 0
# debug, move conventional mem to 0x1000000
pushw %cs
pushw %ds
pushw %es
pushw %ss
pushw %fs
pushw %gs
pushal
movl $0x1000000, %edi
xorl %esi, %esi
movl $0x400000, %ecx
cld
rep movsl
popal
popw %gs
popw %fs
popw %ss
popw %es
popw %ds
popw %cx
#endif
/* memory layout:
*
* 0x0600-0x07FF this sector is not used
* 0x0800-0x09FF this sector stores command line
* 0x0A00-0x0DFF 2 sectors for real mode variables
* 0x0E00-0x0FFF the current sector will be copied here
* 0x1000-0x8000 the rest sectors
*
* Note: The first page(4KB) is not used by Linux, so it is a safe
* place to store our code and data.
*/
cld # can be omitted(already done by popfl)
pushl %esi # points to boot_params
/* move 1 sector of commandline onto 0x00000800-0x000009FF */
movl $0x00000800, %edi
movl 0x228(%esi), %esi # 0x228 is offset for cmd_line_ptr
movl $0x00000080, %ecx # move 1 sector
repz movsl
popl %esi # points to boot_params
debug_putchar('2')
/* move 2-sector real mode variables onto 0x00000A00-0x00000DFF */
movl $0x00000A00, %edi
movl $0x00000100, %ecx # move 2 sectors
repz movsl
debug_putchar('3')
/* move 1 sector of the init code here to a safe place 0x00000E00 */
movl $0x00000E00, %edi
movl %ebp, %esi # EBP=linear address of startup_32
movl $0x00000080, %ecx # move 1 sector
repz movsl
debug_putchar('4')
/* jump to new code in the sector starting at 0x00000E00 */
/* just to learn various jumps */
#if 1
# this way we needn't touch CS register
#if 1
# and this way we needn't touch the stack
#if 0
# and this even won't touch any registers
# but unfortunately we don't know the
# relative displacement
// jmp (1f - 0x????????)
#else
# but this way we need to touch a register
movl $(1f - startup_32 + 0x00000E00), %eax
jmp *%eax # Oh, don't use `jmp *(%eax)' by mistake!
# Should use register indirect addressing,
# not memory indirect addressing.
#endif
#else
# and, this way we need the stack
movl $(1f - startup_32 + 0x00000E00), %eax
pushl %eax
ret
#endif
#else
# jump to new code. __BOOT_CS is this segment, and still 32 bit
ljmp $(__BOOT_CS), $(1f - startup_32 + 0x00000E00)
#endif
1:
debug_putchar('5')
/* We are executed in low memory rang 0x0E00 to 0x0FFF, and now
* it is safe to copy the rest code to 0x00001000
*/
movl $((_dos_start - startup_32 - 0x200) / 4), %ecx
repz movsl
debug_putchar('6')
/* default boot device: DH=partition number, DL=drive number */
movl $0x80, %edx
/* At last, move pre_stage2 to 0x00008200 */
movl $0x00008200, %edi
movl $0x00020000, %ecx # move 0x80000 bytes(512KB)
movl %ebp, %esi # EBP=linear address of startup_32
addl $(pre_stage2_start - startup_32), %esi
# ESI points to pre_stage2_start
cmpl $0x00008200, %esi
jb 1f
cld
repz movsl
jmp 2f
1:
addl $0x7fffc, %edi
addl $0x7fffc, %esi
std # downward
# remember to clear it later!
rep movsl # EDI=0x00008200
cld # now clear it so we are safe
2:
debug_putchar('7')
//----------------------------------------------------------------------------
# resolve the commandline arguments and move preset_menu to 0x00000800
# commandline has been copied to the sector starting at 0x00000800
/* ECX = 0 */
movl $0x00000200, %ecx # find the option in 1 sector
//movb $2, %ch # ECX=0x200
movl $0x00000800, %esi # starting at physical address 0x800
/* look for a non-blank char */
cld
3:
lodsb
cmpb $0, %al
je 4f # end string, failure
cmpb $0x20, %al
je 2f
cmpb $0x09, %al
jne 1f
2:
loop 3b # load next byte, ECX decreased
jmp 4f #; all are blanks, failure
1:
decl %esi # found, let ESI point to it
// incl %ecx
/* This is a leading non-blank char. Check if it matches the option.
* option_config_file points to constant string "--config-file="
*/
movl $(option_config_file - startup_32 + 0xE00), %edi
pushl %ecx
movl $(option_config_file_end - option_config_file), %ecx
movl %esi, %eax # save ESI
cld
repz cmpsb #; ESI, EDI both changed
popl %ecx
je 1f # success
/* No match. Then look for a blank char. */
movl %eax, %esi # restore ESI
incl %esi # let ESI points to the char next to
# the leading non-blank char.
decl %ecx
cld
3:
lodsb
cmpb $0, %al
je 4f # end string, failure
cmpb $0x20, %al
je 2b # Blank found, try again
cmpb $0x09, %al
je 2b # Blank found, try again
loop 3b
4:
/* failure: the option "--config-file" was not found. */
debug_putchar('8')
stc
jmp normal_config_file
1:
debug_putchar('9')
/* success: the option "--config-file" was found. */
xorl %ebx, %ebx
cmpb $0x22, (%esi) /* 0x22 is the double quote char */
jne 1f
incl %esi
incl %ebx /* EBX=1 means a leading double quote exists */
1:
cmpb $0x28, (%esi) /* 0x28 is "(" */
je normal_config_file # CF=0
cmpb $0x2F, (%esi) /* 0x2F is "/" */
je normal_config_file # CF=0
# locate the end of the commandline preset_menu
movl $0x000009ff, %ecx # end of the sector
subl %esi, %ecx # length of menu
movl $0x00000800, %edi
testl %ebx, %ebx # quoted?
jz 1f # no
/* change the double quote to NULL */
pushl %edi
pushl %ecx
movl %esi, %edi # EDI points to source menu string
movb $0x22, %al # locate the double quote
cld
repnz scasb
jnz 4f # not found, continue
decl %edi # EDI points to the double quote
movb $0, (%edi) # change to 0 in source string
4:
popl %ecx
popl %edi
/* Move the command-line menu to 0x800, and change semi-colon
* to LF by the way.
*/
1:
cld
lodsb
cmpb $0x3B, %al /* semi-colon */
jne 3f
movb $0x0A, %al /* change to LF */
3:
stosb
cmpb $0, %al
je 4f
loop 1b
4:
# the config_file should be disabled
movb $0, (default_config_file - startup_32 + 0xE00)
jmp 4f
normal_config_file:
# clear preset_menu value at 0x820C to 0
movl $0, 0x0000820C
jc 4f /* no `--config-file=' option specified. */
/* ESI points to the first byte of the filename. if it is not `(',
* then don't touch the boot device.
*/
xorl %eax, %eax
movl %esi, %edi /* save ESI */
cmpb $0x28, (%esi) /* 0x28 is "(" */
jne 1f
lodsb
lodsb
cmpb $0x30, %al /* 0x30 is "0" */
jb 4f /* invalid config file */
cmpb $0x39, %al /* 0x39 is "9" */
jbe 2f /* BIOS drive number */
cmpb $0x66, %al /* 0x66 is "f" */
je 3f
movb $0x80, %ah /* for hard drive */
cmpb $0x68, %al /* 0x68 is "h" */
je 3f
lodsb
cmpb $0x64, %al /* 0x64 is "d" */
je 1f
jmp 4f /* invalid config file */
3:
/* hard drive or floppy */
lodsb
cmpb $0x64, %al /* 0x64 is "d" */
jne 4f /* invalid config file */
lodsb
cmpb $0x30, %al /* 0x30 is "0" */
jb 4f /* invalid config file */
cmpb $0x39, %al /* 0x39 is "9" */
ja 4f /* invalid config file */
2:
movl $0x7f, %ebx /* Max drive number */
call parse_number /* parse drive number */
jc 4f
xchgl %eax, %ebx /* save drive number to EAX */
/* BH=0 for floppy, and 0x80 for hard drive */
orb %bh, %al /* adjust hard drive number */
movb $0xff, %bl /* partition number for floppy */
testb %bh, %bh /* floppy, skip the partition number parsing. */
jz 5f
movl $0x7f, %ebx /* Max partition number */
call parse_number /* parse partition number */
jc 4f
5:
/* partition number is in BL */
xchgl %eax, %edx /* save drive number to DL */
movb %bl, %dh /* save partition number to DH */
/* From now on, remember not to change DX until launching GRUB */
1:
# move the specified config-file to default_config_file
movl %edi, %esi /* restore ESI */
movl $0x4d, %ecx
movl $(default_config_file - startup_32 + 0xE00), %edi
//cld
repz movsb
4:
debug_putchar('A')
//----------------------------------------------------------------------------
//cld
cli
movl $(head_idt_48 - startup_32 + 0xE00), %esi
# ESI=linear address of idt_48
lidt (%esi) # load idt at idt_48 with limit=0x3ff, base=0
movl $(head_gdt - startup_32 + 0xE00), %eax
# EAX=linear address of gdt
movl $(head_gdt_48 - startup_32 + 0xE00), %esi
# ESI=linear address of gdt_48
movl %eax, 2(%esi) # let gdt_base = linear address of gdt
lgdt (%esi) # load gdt at gdt_48
# switch to 16bit protected mode code segment
ljmp $0x10 /*(__BOOT_CS)*/ , $(to_realmode - startup_32 + 0xE00)
//----------------------------------------------------------------------------
parse_number:
/* input: ESI points to the number
* EBX upper limit of the number
* output: EBX the value of the number
* ESI points to the next byte after the delimiter
* EBP changed
* CF=1 failure
* CF=0 success
*/
pushl %edx
pushl %eax
pushl %ebx /* upper limit */
cld
movl $16, %ebp /* initialize EBP for hex */
//movl %edi, %esi
movl $0xffffffff, %edx
xorl %ebx, %ebx
lodsw
cmpw $0x5830, %ax /* 0X */
je 2f
cmpw $0x7830, %ax /* 0x */
je 2f
/* decimal */
decl %esi
decl %esi
movl $10, %ebp
2:
xorl %eax, %eax
lodsb
cmpb $0, %al
je 4f /* CF=0 */
cmpb $0x2C, %al /* 0x2C= `,' ends the drive number. */
je 4f /* CF=0 */
cmpb $0x29, %al /* 0x29= `)' ends partition number. */
je 4f /* CF=0 */
cmpb $0x30, %al
jb 3f
cmpb $0x39, %al
jbe 1f
cmpl $16, %ebp /* Is hex? */
jb 3f /* no, failure */
orb $0x20, %al
cmpb $0x61, %al
jb 3f
cmpb $0x66, %al
ja 3f
subb $0x27, %al
1:
subb $0x30, %al
pushl %eax
movl %ebx, %eax
mull %ebp
movl %eax, %ebx
popl %eax
jc 3f
addl %eax, %ebx
jc 3f
popl %eax
pushl %eax /* upper limit */
cmpl %eax, %ebx
ja 3f
jmp 2b
4:
testl %edx, %edx /* CF=0 */
jz 1f /* return success */
3:
/* return failure */
stc
1:
popl %eax /* upper limit */
popl %eax
popl %edx
ret
//----------------------------------------------------------------------------
option_config_file:
.ascii "--config-file="
option_config_file_end:
# Descriptor tables
#
# NOTE: The intel manual says gdt should be sixteen bytes aligned for
# efficiency reasons. However, there are machines which are known not
# to boot with misaligned GDTs, so alter this at your peril! If you alter
# GDT_ENTRY_BOOT_CS (in asm/segment.h) remember to leave at least two
# empty GDT entries (one for NULL and one reserved).
#
# NOTE: On some CPUs, the GDT must be 8 byte aligned. This is
# true for the Voyager Quad CPU card which will not boot without
# This directive. 16 byte aligment is recommended by intel.
#
.align 16
head_gdt:
// .fill GDT_ENTRY_BOOT_CS,8,0
# GDT entry 0(NULL entry)
.word 0 # alignment byte
head_gdt_48:
.word head_gdt_end - head_gdt - 1 # gdt limit
.word 0, 0 # gdt base (filled in later)
# GDT entry 1(not used)
.word 0 # alignment byte
head_idt_48:
.word 0x3ff # idt limit = 0x3ff
.word 0, 0 # idt base = 0L
# GDT entry 2(__BOOT_CS)
.word 0xFFFF # limit 64Kb - (0x10000 = 64Kb)
.word 0 # base address = 0
.word 0x9A00 # code read/exec
.word 0x0000 # granularity = 1, 386
# (+5th nibble of limit)
# GDT entry 3(__BOOT_DS)
.word 0xFFFF # limit 64Kb - (0x10000 = 64Kb)
.word 0 # base address = 0
.word 0x9200 # data read/write
.word 0x0000 # granularity = 1, 386
# (+5th nibble of limit)
head_gdt_end:
/* Here comes the initial stack pointer of DOS executable.
* Note that grub as a dos EXE would call init_pic below, so the
* stack should be here to avoid overwriting the code in init_pic.
*/
/* Also note that the stack pointer should be dword aligned */
.align 4
dos_stack:
.code16
# real mode startup code for pre_stage2
# load all segment registers
to_realmode:
cli
movw $(__BOOT_DS), %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
movw %ax, %ss
movl %cr0, %eax
andl $0x00000011, %eax /* turn off paging */
orl $0x60000000, %eax
movl %eax, %cr0
movl %eax, %cr3 /* reload page dir to asure paging off */
movl %cr0, %ebx
andl $0x60000000, %ebx /* test if wbinvd is supported */
jz 1f
wbinvd
1:
andb $0x10, %al /* mask off the PE bit */
movl %eax, %cr0
/* far jump to load the real mode CS */
ljmp $0, $(1f - startup_32 + 0xE00)
1:
/* Now we are in real mode */
xorw %ax, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
movw %ax, %ss
movl $0x400, %esp /* clear high word of ESP */
xorl %ebp, %ebp
pushw %dx /* boot drive and partition number */
call init_pic
popw %dx /* boot drive and partition number */
# old code, should be deleted later
// /* ensure this is in the sector starting at 0000:0A00 */
//
// . = . - ((0xA00 - 1)/ (. - startup_32 + 0x800))
//
// /* It is right time to move the menu sector at 0x600 onto 0x800 */
// movw $0x600, %si
// movw $0x800, %di
// movw $0x080, %cx /* move 1 sector */
// cld
// repz movsl
/* put the config file name */
movw $0x0010, %cx /* set max length of grub version string */
movw $0x8212, %di /* version string */
xorw %ax, %ax /* AL=0. Locate the end of version string */
/* pre_stage2_start+0xA holds the boot partition number. The default
* partition number is 0(and default drive number is 0x80 in DL)
*/
movb %dh, -8(%di) /* the boot partition number */
cld
repnz scasb /* find the location of the default config file name */
jcxz 1f /* failed, will not use the default config file name */
movw $0x4e, %cx /* max length of config file name */
movw $(default_config_file - startup_32 + 0xE00), %si
cld
repz movsb /* move file name to the config-file field of stage2 */
1:
//sti
movw $0x0003, %ax /* set display mode: 80*25 color text */
int $0x10
movw $(launch_pre_stage2 - startup_32 + 0xE00), %si
call print_message
xorw %ax, %ax
movw %ax, %ss
movw $0x2000, %sp
//movb $0x80, %dl /* default boot_drive is (hd0) */
//sti
ljmp $0, $0x8200 //jmp pre_stage2_start
init_pic:
#if 1
/* this is needed for booting via win98 */
xorw %ax, %ax
/* init DMA, stolen from bochs(rombios.c) */
#; first reset the DMA controllers
outb %al, $0x0D #; disable DMA-1
outb %al, $0xDA #; disable DMA-2
movl $2, %ecx # delay at least 2 microseconds
call iodelay
#; then initialize the DMA controllers
movb $0xC0, %al #; cascade mode of channel 4 enabled
outb %al, $0xD6 #; DMA-2 mode register
movb $0x00, %al #; unmask channel 4
outb %al, $0xD4 #; DMA-2 mask register
movl $2, %ecx # delay at least 2 microseconds
call iodelay
#endif
/* initialize PIC. */
//outb(0xff, PIC_MASTER_IMR); /* mask all of 8259A-1 */
//outb(0xff, PIC_SLAVE_IMR); /* mask all of 8259A-2 */
/* send clear_mask command to slave and master */
movb $0x48, %al
call send_command_to_slave_and_master
/* send EOI command to slave and master */
movb $0x20, %al
call send_command_to_slave_and_master
/* set IRQ15 and IRQ7 to least privilege */
movb $0xC7, %al
call send_command_to_slave_and_master
# make sure any possible coprocessor is properly reset..
xorw %ax, %ax
movw $0xF0, %dx # write a byte of 0 to port F0 will clear the busy bit of math coprocessor
call outb_and_delay
movw $0xF1, %dx # write a byte of 0 to port F1 will reset coprocessor and switch it to real mode
call outb_and_delay
# Now we mask all interrupts
movb $0xFF, %al # mask all interrupts for now
movw $0xA1, %dx
call outb_and_delay
movb $0xFB, %al # mask all irq's but irq2 which is cascaded
movw $0x21, %dx
call outb_and_delay
/*
* outb_p - this has to work on a wide range of PC hardware.
*/
cli
//outb_p(0x11, PIC_MASTER_CMD); /* ICW1: select 8259A-1 init */
movb $0x11, %al
movw $0x20, %dx
call outb_and_delay
//outb_p(0x20 + 0, PIC_MASTER_IMR); /* ICW2: 8259A-1 IR0-7 mapped to 0x20-0x27 */
movb $0x08, %al
movw $0x21, %dx
call outb_and_delay
//outb_p(1U << PIC_CASCADE_IR, PIC_MASTER_IMR); /* 8259A-1 (the master) has a slave on IR2 */
movb $0x04, %al
movw $0x21, %dx
call outb_and_delay
/* Yes, the normal EOI should be used for real mode OS. */
#if 0 //if (auto_eoi) /* master does Auto EOI */
//outb_p(MASTER_ICW4_DEFAULT | PIC_ICW4_AEOI, PIC_MASTER_IMR);
movb $0x03, %al
movw $0x21, %dx
call outb_and_delay
#else //else /* master expects normal EOI */
//outb_p(MASTER_ICW4_DEFAULT, PIC_MASTER_IMR);
movb $0x01, %al
movw $0x21, %dx
call outb_and_delay
#endif
//outb_p(0x11, PIC_SLAVE_CMD); /* ICW1: select 8259A-2 init */
movb $0x11, %al
movw $0xA0, %dx
call outb_and_delay
//outb_p(0x20 + 8, PIC_SLAVE_IMR); /* ICW2: 8259A-2 IR0-7 mapped to 0x28-0x2f */
movb $0x70, %al
movw $0xA1, %dx
call outb_and_delay
//outb_p(PIC_CASCADE_IR, PIC_SLAVE_IMR); /* 8259A-2 is a slave on master's IR2 */
movb $0x02, %al
movw $0xA1, %dx
call outb_and_delay
//outb_p(SLAVE_ICW4_DEFAULT, PIC_SLAVE_IMR); /* (slave's support for AEOI in flat mode is to be investigated) */
movb $0x01, %al
movw $0xA1, %dx
call outb_and_delay
#if 0 //if (auto_eoi)
/*
* in AEOI mode we just have to mask the interrupt
* when acking.
*/
//i8259A_irq_type.ack = disable_8259A_irq;
#else //else
//i8259A_irq_type.ack = mask_and_ack_8259A;
#endif
//udelay(100); /* wait for 8259A to initialize */
movl $100, %ecx # delay at least 100 microseconds
call iodelay
//outb(cached_master_mask, PIC_MASTER_IMR); /* restore master IRQ mask */
//outb(cached_slave_mask, PIC_SLAVE_IMR); /* restore slave IRQ mask */
cli
movb $0x00, %al # enable all IRQs
movw $0x21, %dx
call outb_and_delay
movb $0x00, %al # enable all the cascaded IRQs
movw $0xA1, %dx
call outb_and_delay
//udelay(100); /* wait for 8259A to initialize */
movl $100, %ecx # delay at least 100 microseconds
call iodelay
/* end initialize PIC. */
/* FIXME: init edge-level-control-registers(IO ports 0x4D0, 0x4D1) for EISA */
#if 1
/* this is needed for booting via win98 */
/* EISA 8259-1 */
/* init system timer 0 (stolen from bochs rombios.c) */
#if 0
movb $0x34, %al # system timer 0, 16-bit binary count, mode 2
#else
/* system timer 0 should be in mode 3 */
movb $0x36, %al # system timer 0, 16-bit binary count, mode 3
#endif
movw $0x43, %dx
call outb_and_delay
movb $0x00, %al # maximum count of 0000H = 18.2Hz
movw $0x40, %dx
call outb_and_delay # write lo byte 00
call outb_and_delay # write hi byte 00
/* disable NMI */
orb $0x80, %al
outb %al, $0x70
movl $2, %ecx # delay at least 2 microseconds
call iodelay
#endif
/* init PCI (stolen from bochs rombios.c) */
#if 0
call pcibios_init_iomem_bases
call pcibios_init_irqs
#else
#if 1
/* this is needed for booting via win98 */
/* EISA 8259-1 */
movw $0x4D0, %dx
inb %dx, %al
movw $0x21, %dx # mask off level-triggerred IRQs
call outb_and_delay
/* EISA 8259-2 */
movw $0x4D1, %dx
inb %dx, %al
movw $0xA1, %dx # mask off level-triggerred IRQs
call outb_and_delay
#endif
#endif
movb $0x20, %al #; send EOI to 8259A-2
movw $0xA0, %dx
call outb_and_delay
movb $0x20, %al #; send EOI to 8259A-1
movw $0x20, %dx
call outb_and_delay
#if 0
movw $0x461, %dx
inb %dx, %al
andb $0xF0, %al
call outb_and_delay
#endif
#if 0
movb $0x0C, %al
outb %al, $0xA1
xorw %cx, %cx
1: loop 1b
movb $0x08, %al
outb %al, $0x21
xorw %cx, %cx
1: loop 1b
#endif
#if 0
/* reset mouse */
movw $0xC201, %ax
int $0x15
/* disable mouse */
movw $0xC200, %ax
xorw %bx, %bx /* BH=0 means disable */
int $0x15
/* set mouse handler address */
movw $0xC207, %ax
xorw %bx, %bx /* ES:BX=0000:0000 to cancel the handler */
int $0x15
/* disable monitor clock (Watch-Dog) */
movw $0xC300, %ax
int $0x15
#endif
ret
#if 0
.align 16
pci_routing_table_structure:
.byte 0x24, 0x50, 0x49, 0x52 #; "$PIR" signature
.byte 0, 1 #; version
.word 32 + (6 * 16) #; table size
.byte 0 #; PCI interrupt router bus
.byte 0x08 #; PCI interrupt router DevFunc
.word 0x0000 #; PCI exclusive IRQs
.word 0x8086 #; compatible PCI interrupt router vendor ID
.word 0x7000 #; compatible PCI interrupt router device ID
.word 0,0 #; Miniport data
.byte 0,0,0,0,0,0,0,0,0,0,0 #; reserved
.byte 0x07 #; checksum
#; first slot entry PCI-to-ISA (embedded)
.byte 0 #; pci bus number
.byte 0x08 #; pci device number (bit 7-3)
.byte 0x60 #; link value INTA#: pointer into PCI2ISA config space
.word 0xdef8 #; IRQ bitmap INTA#
.byte 0x61 #; link value INTB#
.word 0xdef8 #; IRQ bitmap INTB#
.byte 0x62 #; link value INTC#
.word 0xdef8 #; IRQ bitmap INTC#
.byte 0x63 #; link value INTD#
.word 0xdef8 #; IRQ bitmap INTD#
.byte 0 #; physical slot (0 = embedded)
.byte 0 #; reserved
#; second slot entry: 1st PCI slot
.byte 0 #; pci bus number
.byte 0x10 #; pci device number (bit 7-3)
.byte 0x61 #; link value INTA#
.word 0xdef8 #; IRQ bitmap INTA#
.byte 0x62 #; link value INTB#
.word 0xdef8 #; IRQ bitmap INTB#
.byte 0x63 #; link value INTC#
.word 0xdef8 #; IRQ bitmap INTC#
.byte 0x60 #; link value INTD#
.word 0xdef8 #; IRQ bitmap INTD#
.byte 1 #; physical slot (0 = embedded)
.byte 0 #; reserved
#; third slot entry: 2nd PCI slot
.byte 0 #; pci bus number
.byte 0x18 #; pci device number (bit 7-3)
.byte 0x62 #; link value INTA#
.word 0xdef8 #; IRQ bitmap INTA#
.byte 0x63 #; link value INTB#
.word 0xdef8 #; IRQ bitmap INTB#
.byte 0x60 #; link value INTC#
.word 0xdef8 #; IRQ bitmap INTC#
.byte 0x61 #; link value INTD#
.word 0xdef8 #; IRQ bitmap INTD#
.byte 2 #; physical slot (0 = embedded)
.byte 0 #; reserved
#; 4th slot entry: 3rd PCI slot
.byte 0 #; pci bus number
.byte 0x20 #; pci device number (bit 7-3)
.byte 0x63 #; link value INTA#
.word 0xdef8 #; IRQ bitmap INTA#
.byte 0x60 #; link value INTB#
.word 0xdef8 #; IRQ bitmap INTB#
.byte 0x61 #; link value INTC#
.word 0xdef8 #; IRQ bitmap INTC#
.byte 0x62 #; link value INTD#
.word 0xdef8 #; IRQ bitmap INTD#
.byte 3 #; physical slot (0 = embedded)
.byte 0 #; reserved
#; 5th slot entry: 4rd PCI slot
.byte 0 #; pci bus number
.byte 0x28 #; pci device number (bit 7-3)
.byte 0x60 #; link value INTA#
.word 0xdef8 #; IRQ bitmap INTA#
.byte 0x61 #; link value INTB#
.word 0xdef8 #; IRQ bitmap INTB#
.byte 0x62 #; link value INTC#
.word 0xdef8 #; IRQ bitmap INTC#
.byte 0x63 #; link value INTD#
.word 0xdef8 #; IRQ bitmap INTD#
.byte 4 #; physical slot (0 = embedded)
.byte 0 #; reserved
#; 6th slot entry: 5rd PCI slot
.byte 0 #; pci bus number
.byte 0x30 #; pci device number (bit 7-3)
.byte 0x61 #; link value INTA#
.word 0xdef8 #; IRQ bitmap INTA#
.byte 0x62 #; link value INTB#
.word 0xdef8 #; IRQ bitmap INTB#
.byte 0x63 #; link value INTC#
.word 0xdef8 #; IRQ bitmap INTC#
.byte 0x60 #; link value INTD#
.word 0xdef8 #; IRQ bitmap INTD#
.byte 5 #; physical slot (0 = embedded)
.byte 0 #; reserved
#endif
pci_irq_list:
.byte 11, 10, 9, 5
pcibios_init_sel_reg:
# input:
# BX hi 16 bit of address
# DL lo 8 bit of address
# output:
# register was selected
# DX 0x0cf8
pushl %eax
movl $0x800000, %eax
movw %bx, %ax
shll $8, %eax
andb $0xfc, %dl
orb %dl, %al
movw $0x0cf8, %dx
outl %eax, %dx
popl %eax
ret
pcibios_init_iomem_bases:
pushw %bp
movw %sp, %bp
movl $0xe0000000, %eax #; base for memory init
pushl %eax
movw $0xc000, %ax #; base for i/o init
pushw %ax
movw $0x0010, %ax #; start at base address #0
pushw %ax
movw $0x0008, %bx #; hi 16-bit
pci_init_io_loop1:
movb $0x00, %dl #; lo 8-bit
call pcibios_init_sel_reg
movw $0x0cfc, %dx
inw %dx, %ax
cmpw $0xffff, %ax
jz next_pci_dev
movb $0x04, %dl #; disable i/o and memory space access
call pcibios_init_sel_reg
movw $0x0cfc, %dx
inb %dx, %al
andb $0xfc, %al
outb %al, %dx
pci_init_io_loop2:
movb -8(%bp), %dl
call pcibios_init_sel_reg
movw $0x0cfc, %dx
inl %dx, %eax
testb $0x01, %al
jnz init_io_base
movl %eax, %ecx
movl $0xffffffff, %eax
outl %eax, %dx
inl %dx, %eax
cmpl %ecx, %eax
je next_pci_base
xorl $0xffffffff, %eax
movl %eax, %ecx
movl -4(%bp), %eax
outl %eax, %dx
addl %ecx, %eax #; calculate next free mem base
addl $0x01000000, %eax
andl $0xff000000, %eax
movl %eax, -4(%bp)
jmp next_pci_base
init_io_base:
movw %ax, %cx
movw $0xffff, %ax
outw %ax, %dx
inw %dx, %ax
cmpw %cx, %ax
je next_pci_base
xorw $0xfffe, %ax
movw %ax, %cx
movw -6(%bp), %ax
outw %ax, %dx
addw %cx, %ax #; calculate next free i/o base
addw $0x0100, %ax
andw $0xff00, %ax
movw %ax, -6(%bp)
next_pci_base:
movb -8(%bp), %al
addb $0x04, %al
cmpb $0x28, %al
je enable_iomem_space
movb %al, -8(%bp)
jmp pci_init_io_loop2
enable_iomem_space:
movb $0x04, %dl #; enable i/o and memory space access if available
call pcibios_init_sel_reg
movw $0x0cfc, %dx
inb %dx, %al
orb $0x07, %al
outb %al, %dx
next_pci_dev:
movb $0x10, -8(%bp)
incw %bx
cmpw $0x0100, %bx
jne pci_init_io_loop1
movw %bp, %sp
popw %bp
ret
pcibios_init_set_elcr:
pushw %ax
pushw %cx
movw $0x04d0, %dx
testb $0x08, %al
jz is_master_pic
incw %dx
andb $0x07, %al
is_master_pic:
movb %al, %cl
movb $0x01, %bl
shlb %cl, %bl
inb %dx, %al
orb %bl, %al
outb %al, %dx
popw %cx
popw %ax
ret
pcibios_init_irqs:
pushw %ds
pushw %bp
movw $0xf000, %ax
movw %ax, %ds # DS=0xF000
movw $0x04d0, %dx #; reset ELCR1 + ELCR2
movb $0x00, %al
outb %al, %dx
incw %dx
outb %al, %dx
/* find pci_routing_table_structure in ROM range F0000-FFFF0 */
cld
xorw %si, %si
1:
lodsl
addw $12, %si #; points to next paragraph
jc pci_init_end #; PCI IRQ Routing Table not found
cmpl $0x52495024, %eax #; "$PIR" signature
jnz 1b #; try next paragraph
/* table size must be larger than 32 and must be a multiple of 16 */
movw -10(%si), %ax
cmpw $32, %ax
jb 1b #; try next paragraph
testb $0x0F, %al
jnz 1b #; try next paragraph
/* sum of all bytes in the PCI IRQ Routing Table should be 0 */
pushw %si
movw %ax, %cx #; table size
subw $16, %si #; points to table
xorw %ax, %ax
2:
lodsb
addb %al, %ah
loop 2b
testb %ah, %ah
popw %si
jnz 1b #; try next paragraph
subw $16, %si #; points to table
movb 8(%si), %bh
movb 9(%si), %bl
movb $0x00, %dl
call pcibios_init_sel_reg
movw $0x0cfc, %dx
inl %dx, %eax
cmpl 12(%si), %eax #; check irq router
jne pci_init_end
movb 34(%si), %dl
call pcibios_init_sel_reg
pushw %bx #; save irq router bus + devfunc
movw $0x0cfc, %dx
movw $0x8080, %ax
outw %ax, %dx #; reset PIRQ route control
incw %dx
incw %dx
outw %ax, %dx
movw 6(%si), %ax
subw $0x20, %ax
shrw $4, %ax
movw %ax, %cx
addw $0x20, %si #; set pointer to 1st entry
movw %sp, %bp
/* calculate pointer value pci_irq_list relative to CS segment */
call 1f
1:
popw %ax /* AX=instruction pointer of 1b */
subw $(1b - pci_irq_list), %ax /* AX=instruction pointer of pci_irq_list */
pushw %ax #; save pointer to stack
xorw %ax, %ax
pushw %ax #; push 0
pci_init_irq_loop1:
movb (%si), %bh
movb 1(%si), %bl
pci_init_irq_loop2:
movb $0x00, %dl
call pcibios_init_sel_reg
movw $0x0cfc, %dx
inw %dx, %ax
cmpw $0xffff, %ax
jnz pci_test_int_pin
testb $0x07, %bl
jz next_pir_entry
jmp next_pci_func
pci_test_int_pin:
movb $0x3c, %dl
call pcibios_init_sel_reg
movw $0x0cfd, %dx
inb %dx, %al
andb $0x07, %al
jz next_pci_func
decb %al #; determine pirq reg
movb $0x03, %dl
//mulb %dl, %al
mulb %dl
addb $0x02, %al
xorb %ah, %ah
movw %ax, %bx
movb (%bx, %si), %al
movb %al, %dl
movw (%bp), %bx
call pcibios_init_sel_reg
movw $0x0cfc, %dx
andb $0x03, %al
addb %al, %dl
inb %dx, %al
cmpb $0x80, %al
jb pirq_found
movw -2(%bp), %bx #; pci_irq_list pointer
movb %cs:(%bx), %al
outb %al, %dx
incw %bx
movw %bx, -2(%bp) #; points to next irq
call pcibios_init_set_elcr
pirq_found:
movb (%si), %bh
movb 1(%si), %bl
addb -3(%bp), %bl #; pci function number
movb $0x3c, %dl
call pcibios_init_sel_reg
movw $0x0cfc, %dx
outb %al, %dx
next_pci_func:
incb -3(%bp)
incb %bl
testb $0x07, %bl
jnz pci_init_irq_loop2
next_pir_entry:
addw $0x10, %si
movb $0x00, -3(%bp)
loop pci_init_irq_loop1
movw %bp, %sp
popw %bx
pci_init_end:
popw %bp
popw %ds
ret
send_command_to_slave_and_master:
movw $0xA0, %dx
call outb_and_delay
movw $0x20, %dx
call outb_and_delay
ret
outb_and_delay:
outb %al, %dx
movl $2, %ecx # delay at least 2 microseconds
call iodelay
ret
iodelay:
/* input: ECX - microseconds to delay */
/* On a 4GHz CPU, we needs roughly 4G/1000000=4000 clocks
* for a microsecond.
*/
shll $14, %ecx /* 2**14=4096*4, 4 times of a microsecond */
1:
addr32 loop 1b
ret
/* prints string DS:SI (modifies AX BX SI) */
3:
//xorw %bx, %bx /* video page 0 */
movb $0x0e, %ah /* print char in AL */
int $0x10 /* via TTY mode */
print_message:
lodsb (%si), %al /* get token */
cmpb $0, %al /* end of string? */
jne 3b
ret
launch_pre_stage2:
.ascii "\r\n\r\nLaunching GRUB...\r\n"
.byte 0 /* mark the end of ascii zero string */
default_config_file:
.ascii "/menu.lst"
default_config_file_end:
.byte 0 /* mark the end of ascii zero string */
. = default_config_file + 0x4d
.byte 0 /* mark the end of ascii zero string */
// . = _start + 0x2400
.align 0x200
#-----------------------------------------------------------------------------
# Linux kernel structure ends here
#-----------------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////////
#-----------------------------------------------------------------------------
# DOS EXE code begins here
#
# Segment base of CS and SS is START: CS:0000=SS:0000=START=(PSP+0x2E0)
# Segment base of DS and ES is PSP : DS:0000=ES:0000=PSP=(START-0x2E0)
#
#-----------------------------------------------------------------------------
//dos_stack:
_dos_start:
/* Code from startup_32 to _dos_start will be copied to 0x0E00-0x8000
* when grub.exe is used as a linux kernel. So check the length here
*/
. = . - ((_dos_start - startup_32) / (0x8000 - 0x0E00 + 1))
/* first, move command line to CS:0081 */
cli
cld /* move upward */
movw $0x80, %si
movw %cs, %ax
movw %ax, %es
movw %si, %di
movw %si, %cx /* move 0x80 bytes */
repz movsb
movw %ax, %ds
/* non-zero indicates EXE rather than device driver */
movb $1, 0x80
movb $0x00, 0xff /* end it with NULL */
_dos_start1:
/* At here DS=ES=CS=SS */
//sti
cli /* disable interrupt for usb keyboard hack */
/* XXX: Maybe we should better mask off all IRQs
* and disable NMI here
*/
//----------------------------------------------------------------------------
/* change all TABs to spaces, and the first CR/LF char to NULL */
cld /* load upward */
movw $0x0081, %si /* get command line */
movw $0x1000, %cx /* max length of command line */
1:
lodsb
cmpb $0x00, %al /* NULL */
je 1f
cmpb $0x0D, %al /* CR */
je 1f
cmpb $0x0A, %al /* LF */
je 1f
cmpb $0x09, %al /* TAB */
jne 2f
movb $0x20, -1(%si) /* change TAB to SPACE */
jmp 3f
2:
cmpb $0x20, %al /* another non-printable char below SPACE? */
jb invalid_option /* yes, invalid */
3:
loop 1b
1:
decw %si /* SI points to the ending NULL */
movb $0x00, (%si) /* change CR/LF to NULL */
//----------------------------------------------------------------------------
/* change the leading slash and chars upto "--" to spaces */
cld /* scan upward */
movw $0x0081, %di /* get command line */
movw %si, %cx
subw %di, %cx /* length of command line */
movb $0x20, %al /* the space bar */
repz scasb /* skip spaces */
jz 3f /* the line contains only spaces */
/* scas always increment/decrement DI, so DI > 0x81 */
decw %di /* points to the first non-space char */
incw %cx
cmpb $0x2f, (%di) /* check if the leading char is a slash */
je 2f
cmpb $0x00, (%di) /* check if the leading char is NULL */
je 3f
cmpw $0x2d2d, (%di) /* check if the leading pair is "--" */
je 1f /* yes, continue */
jmp invalid_option
2:
/* yes, it is a possible switch inserted by Microsoft for the
* SHELL=GRUB.EXE line in CONFIG.SYS, so we wipe it out...
*/
stosb /* change the leading slash to space... */
/* ... and change all the rest chars to spaces, up to the pair "--" */
2:
cmpb $0x20, (%di) /* check if the next char is printable */
jb 3f /* this is NULL, and command line is empty */
cmpw $0x2d2d, (%di) /* check if the next pair is "--" */
je 1f /* yes, continue */
stosb /* change the char to space bar */
loop 2b
/* no "--" is found */
3:
use_default_config_file:
/* no option specified */
/* first, try .\menu.lst */
/* SI points to filename */
movw $ABS_PSP(default_config_file_dos1), %si
call open_dos_file
jnc 3f /* success */
/* second, try \menu.lst */
/* SI points to filename */
movw $ABS_PSP(default_config_file_dos2), %si
call open_dos_file
jnc 3f /* success */
/* last, use default config file */
movw $ABS_PSP(default_config_file_dos), %si
movw $(default_config_file_dos_end - default_config_file_dos), %cx
jmp put_config_file_name
3:
jmp raw_commands_embedded
///* setup saved_entry_no, etc. */
//jmp done_options
1:
//----------------------------------------------------------------------------
/* when we get here, DI points to the leading "--" */
pushw %si
pushw %di
pushw %si
pushw %di
/* find options --bypass, --time-out=T, --hot-key=K */
4:
popw %di
popw %si
pushw %si
pushw %di
cld
movw %si, %cx
subw %di, %cx /* length of command line */
movb $0x2d, %al /* "-" */
1:
repnz scasb
testw %cx, %cx
jnz 2f
/* not found */
movb ABS_PSP(options_doing), %al
orb %al, ABS_PSP(options_done)
popw %ax /* discard old DI */
popw %ax /* discard old SI */
popw %di /* restore original DI */
popw %si /* restore original SI */
pushw %si /* push SI back to stack */
pushw %di /* push DI back to stack */
pushw %si /* push SI again */
pushw %di /* push DI again */
jmp 3f
2:
cmpb $0x2d, (%di) /* the second "-" */
jne 1b
decw %di
incw %cx
popw %dx
pushw %dx
cmpw %dx, %di
jbe 3f
incw %di
decw %cx
incw %di
decw %cx
cmpb $0x20, -3(%di)
jne 1b
decw %di
incw %cx
decw %di
incw %cx
//pushw %di
3:
testb $1, ABS_PSP(options_done)
jne 5f
movl $1, ABS_PSP(options_doing)
/* compare with "--bypass" */
cld /* compare upward */
movw $ABS_PSP(option_bypass_dos), %si
movw %di, %dx /* DI is our option string, save to DX */
movw $(option_bypass_dos_end - option_bypass_dos), %cx
/* length of the string */
repz cmpsb
jz 2f /* yes, exactly */
movw $ABS_PSP(option_bypass1_dos), %si
movw %dx, %di /* restore DI from DX */
movw $(option_bypass1_dos_end - option_bypass1_dos), %cx
/* length of the string */
repz cmpsb
jnz 3f
2:
/* found "--bypass" */
cmpb $0x20, (%di)
je 2f
cmpb $0, (%di)
jne 3f
//orb $1, ABS_PSP(options_done)
2:
cmpl $0, ABS_PSP(bypass)
jne invalid_option /* specify --bypass twice */
movl $1, ABS_PSP(bypass)
/* wipe off "--bypass" */
movw $(option_bypass_dos_end - option_bypass_dos), %cx
subw %cx, %di
movb $0x22, %al /* double-quote */
stosb
decw %cx
movb $0x20, %al /* SPACE */
repz stosb
3:
popw %ax /* discard old DI */
pushw %di /* push new DI */
//testb $1, ABS_PSP(options_done)
//jz 4b
jmp 4b
//popw %di
//pushw %di
5:
testb $2, ABS_PSP(options_done)
jne 5f
movl $2, ABS_PSP(options_doing)
/* compare with "--time-out=" */
cld /* compare upward */
movw $ABS_PSP(option_time_out_dos), %si
movw %di, %dx /* DI is our option string, save to DX */
movw $(option_time_out_dos_end - option_time_out_dos), %cx
/* length of the string */
repz cmpsb
jz 2f /* yes, exactly */
movw $ABS_PSP(option_time_out1_dos), %si
movw %dx, %di /* restore DI from DX */
movw $(option_time_out1_dos_end - option_time_out1_dos), %cx
/* length of the string */
repz cmpsb
jnz 3f
2:
/* found "--time-out=" */
movl $0x000000ff, %ebx /* upper limit of time-out */
movw %di, %si
call parse_number_dos
jc invalid_option /* invalid time-out value */
decw %si
movw %si, %cx
subw %di, %cx
movw %si, %di
lodsb
cmpb $0x20, %al
je 2f
cmpb $0, %al
jne invalid_option /* invalid time-out value */
//orb $2, ABS_PSP(options_done)
2:
cmpl $0x80000000, ABS_PSP(time_out)
jne invalid_option /* specify time-out twice */
movl %ebx, ABS_PSP(time_out)
/* wipe off "--time-out=T" */
addw $(option_time_out_dos_end - option_time_out_dos), %cx
subw %cx, %di
movb $0x22, %al /* double-quote */
stosb
decw %cx
movb $0x20, %al /* SPACE */
repz stosb
3:
popw %ax /* discard old DI */
pushw %di /* push new DI */
//testb $2, ABS_PSP(options_done)
//jz 4b
jmp 4b
//popw %di
//pushw %di
5:
testb $4, ABS_PSP(options_done)
jne 5f
movl $4, ABS_PSP(options_doing)
/* compare with "--hot-key=" */
cld /* compare upward */
movw $ABS_PSP(option_hot_key_dos), %si
movw %di, %dx /* DI is our option string, save to DX */
movw $(option_hot_key_dos_end - option_hot_key_dos), %cx
/* length of the string */
repz cmpsb
jz 2f /* yes, exactly */
movw $ABS_PSP(option_hot_key1_dos), %si
movw %dx, %di /* restore DI from DX */
movw $(option_hot_key1_dos_end - option_hot_key1_dos), %cx
/* length of the string */
repz cmpsb
jnz 3f
2:
/* found "--hot-key=" */
movl $0x0000ffff, %ebx /* upper limit of hot-key */
movw %di, %si
call parse_number_dos
jc invalid_option /* invalid hot-key value */
decw %si
movw %si, %cx
subw %di, %cx
movw %si, %di
lodsb
cmpb $0x20, %al
je 2f
cmpb $0, %al
jne invalid_option /* invalid hot-key value */
//orb $4, ABS_PSP(options_done)
2:
cmpl $0xffff3920, ABS_PSP(hot_key)
jne invalid_option /* specify hot-key twice */
movl %ebx, ABS_PSP(hot_key)
/* wipe off "--hot-key=K" */
addw $(option_hot_key_dos_end - option_hot_key_dos), %cx
subw %cx, %di
movb $0x22, %al /* double-quote */
stosb
decw %cx
movb $0x20, %al /* SPACE */
repz stosb
3:
popw %ax /* discard old DI */
pushw %di /* push new DI */
//testb $4, ABS_PSP(options_done)
//jz 4b
jmp 4b
//popw %di
5:
// testb $2, ABS_PSP(options_done)
// jne 5f
//
// jmp 4b
//
//1:
popw %di
popw %si
popw %di
popw %si
//---------------------------------------------------------------------------
/* if specified --bypass, then time-out default to 5 */
movl ABS_PSP(bypass), %ecx /* --bypass specified? */
jecxz 1f /* no, continue */
cmpl $0x80000000, ABS_PSP(time_out)
jne 1f
movl $5, ABS_PSP(time_out)
1:
//---------------------------------------------------------------------------
/* if time-out is not 0, print message */
movw ABS_PSP(time_out), %cx
jcxz 1f
movw $ABS_PSP(press_space_bar_string), %dx
cmpw $0x3920, ABS_PSP(hot_key)
je 2f
movw $ABS_PSP(press_hot_key_string), %dx
2:
movb $0x09, %ah /* display a dollar terminated string */
int $0x21 /* call DOS */
movw $ABS_PSP(press_any_key_string), %dx
movb $0x09, %ah /* display a dollar terminated string */
int $0x21 /* call DOS */
1:
//---------------------------------------------------------------------------
/* find option "--config-file=" */
cld
movw %di, %si /* SI points to the leading "--" */
/* but may be changed to double-quote */
1:
lodsb
cmpb $0x22, %al /* the double quote char */
//je 1b
jne 2f
movb $0x20, -1(%si)
jmp 1b
2:
cmpb $0x20, %al /* SPACE */
je 1b
cmpb $0, %al
je use_default_config_file
decw %si
cmpw $0x2d2d, (%si) /* "--" */
jne invalid_option
movw %si, %di /* DI points to the leading "--" */
/* compare with "--config-file=" */
cld /* compare upward */
movw $ABS_PSP(option_config_file_dos), %si
movw %si, %bx /* SI is const string "--config-file=" */
movw %di, %dx /* DI is our option string, save to DX */
movw $(option_config_file_dos_end - option_config_file_dos), %cx
/* length of the string */
repz cmpsb
jz 1f /* yes, exactly */
/* translate all upper case to lower case, and try again */
movw $0x1000, %cx
movw %dx, %si /* about to translate our option string */
movw %dx, %di
2:
lodsb
cmpb $0x00, %al
je 2f
cmpb $0x41, %al /* below 'A'? */
jb 3f /* yes, no change. */
cmpb $0x5a, %al /* above 'Z'? */
ja 3f /* yes, no change. */
orb $0x20, %al /* change to lower */
3:
stosb
loop 2b
2:
/* compare again */
cld /* compare upward */
movw $(option_config_file_dos_end - option_config_file_dos), %cx
movw %bx, %si /* SI is const string "--config-file=" */
movw %dx, %di /* DI is our option string, restore from DX */
repz cmpsb /* compare for the second time */
jnz invalid_option
1:
/* the option "--config-file=" exists for sure */
cld /* scan upward */
movw %di, %si /* points to the config file name */
movw $0x1000, %cx
movb $0x00, %al /* find the end of command line */
repnz scasb
jnz invalid_option /* no ending NULL */
movw %di, %cx /* points to end of config file name */
subw %si, %cx
decw %cx /* length of config file name */
jz invalid_option /* FILENAME is empty */
/* SI points to filename string with length CX */
/* the leading "(" or "/" indicates a normal config filename */
movb (%si), %al
cmpb $0x28, %al /* "(" */
jz 2f
cmpb $0x2F, %al /* "/" */
jnz 1f
2:
/* we change the ending space-slash to NULL only for normal config
* filename.
*/
pushw %cx /* save CX(=length) important! */
/* find the space-slash pair and change the space to NULL */
cld /* scan upward */
xorw %dx, %dx /* 0 for outside the double-quotes */
movw %si, %di /* scan the filename */
//movw $0x1000, %cx /* max length of command line */
movb $0x20, %al /* the space bar character */
2:
repnz scasb /* find the space char */
jnz 2f /* not found, continue */
cmpb $0x2f, (%di) /* is it the " /" pair */
jnz 2b /* no, search the rest of the command line */
decw %di
movb $0x00, %al /* mark the end of command line */
stosb /* change the space to NULL */
2:
popw %cx /* restore CX, which is non-zero. */
jmp put_config_file_name
1:
/* The filename specifies an embedded command-line menu. */
movw %cx, %bx
incw %cx
cld
//movw $0x1000, %cx
movw $0x81, %di
xorw %ax, %ax
/* change semi-colon to LF. change double-quote to 0 if it is followed
* by SPACE or NULL, otherwise delete the double-quote char and
* continue.
*/
3:
lodsb
4:
cmpb $0x3B, %al /* semi-colon */
jnz 2f
movb $0x0A, %al /* change to LF */
2:
cmpb $0x22, %al /* the double quote */
jnz 2f
jcxz 4f
lodsb
decw %cx
cmpw %bx, %cx /* is the double quote leading? */
jae 4b /* yes, discard the double quote */
cmpb $0x20, %al /* is the next char a space? */
jz 4f /* yes, end */
cmpb $0x00, %al /* is the next char a NULL */
jnz 4b /* no, discard the double quote */
4:
movb $0x00, %al /* change to NULL */
2:
stosb
cmpb $0x00, %al
jz 1f
jcxz 4b
loop 3b
1:
/* DI points to the char after the ending NULL */
movw $0x81, %si /* SI points to filename */
movw %di, %cx /* points to end of filename */
subw %si, %cx
decw %cx /* CX=length of the filename */
jz invalid_option /* FILENAME is empty */
/* the leading "(" or "/" indicates a normal config filename */
movb (%si), %al
cmpb $0x28, %al /* "(" */
jz put_config_file_name
cmpb $0x2F, %al /* "/" */
jz put_config_file_name
/* invalidate the config file in pre_stage2 */
raw_commands_embedded:
/* CX=0 means raw commands are embedded. */
xorw %cx, %cx /* copy 0 bytes to config-file field */
//jmp 6f
put_config_file_name:
cld
movl %esi, %edx /* save ESI to EDX */
movw $ABS_PSP(pre_stage2_start+0x12), %di /* version string */
xorl %eax, %eax /* 0 means the end of the string */
/* pre_stage2_start+0xA holds the boot partition number. We use
* default partition number 0(and default drive number 0x80 in DL)
*/
movb %al, -8(%di) /* the boot partition number */
/* pre_stage2_start+0xB holds our temp boot_drive */
movb $0x80, -7(%di) /* temp storage for boot_drive */
jcxz 6f /* CX=0 means raw commands are embedded. */
/* pre_stage2_start+0xC holds the address of preset_menu.
* we are using the config-file, and we do not change the default
* preset_menu, so let it be 0. */
movl %eax, -6(%di)
/* ESI points to the first byte of the filename. if it is not `(',
* then don't touch the boot device.
*/
cmpb $0x28, (%si) /* 0x28 is "(" */
jne 6f
xorw %ax, %ax /* clear AH */
lodsb /* AL="(" */
lodsb
/* the drive number in hex or decimal form */
cmpb $0x30, %al /* 0x30 is "0" */
jb invalid_option /* invalid config file */
cmpb $0x39, %al /* 0x39 is "9" */
jbe 2f /* BIOS drive number */
/* the drive number in (fd?) or (hd?) form */
cmpb $0x66, %al /* 0x66 is "f" */
je 3f
movb $0x80, %ah /* for hard drive */
cmpb $0x68, %al /* 0x68 is "h" */
je 3f
lodsb
cmpb $0x64, %al /* 0x64 is "d" */
je 6f /* might be cd/nd/md/rd, do nothing */
jmp invalid_option /* invalid config file */
3:
/* hard drive or floppy */
lodsb
cmpb $0x64, %al /* 0x64 is "d" */
jne invalid_option /* invalid config file */
lodsb
cmpb $0x30, %al /* 0x30 is "0" */
jb invalid_option /* invalid config file */
cmpb $0x39, %al /* 0x39 is "9" */
ja invalid_option /* invalid config file */
2:
decw %si /* SI points to hexa or decimal number */
movl $0x7f, %ebx /* Max drive number */
call parse_number_dos /* parse drive number */
jc invalid_option /* invalid config file */
cld
decw %si /* SI points to delimiter "," or ")" */
lodsb
cmpb $0x2C, %al /* "," */
je 2f
cmpb $0x29, %al /* ")" */
jne invalid_option /* invalid config file */
2:
xchgl %eax, %ebx /* save drive number to EAX */
/* BH=0 for floppy, and 0x80 for hard drive */
orb %bh, %al /* adjust hard drive number */
movb $0xff, %bl /* partition number for floppy */
testb %bh, %bh /* floppy, skip the partition number parsing */
jz 2f
movl $0x7f, %ebx /* Max partition number */
call parse_number_dos /* parse partition number */
jc invalid_option /* invalid config file */
2:
/* BL=boot partition number, AL=boot_drive number */
movb %al, %bh /* BH=boot_drive number */
decw %si /* SI should point to ")" */
lodsb
cmpb $0x29, %al /* ")" */
jne invalid_option /* invalid config file */
movw %bx, -8(%di) /* setup boot_drive and boot_partition number */
//----------------------------------------------------------------------------
6:
movl %edx, %esi /* restore ESI from EDX */
xorb %al, %al
pushw %cx /* CX=0 means raw commands are embedded. */
movw $0x0010, %cx /* set max length of grub version string */
cld
repnz scasb /* DI changed */
popw %cx /* CX=0 means raw commands are embedded. */
movw %cx, %ax
addw %di, %ax
cmpw $ABS_PSP(pre_stage2_start+0x6f), %ax
/* check for possible buffer overflow */
jnb file_name_too_long
pushw %cx /* CX=0 for embedded menu, and ... */
cld
repz movsb /* now it is safe, perform the move */
popw %cx /* ... CX=non-zero for normal file */
xorb %al, %al /* write an end-of-string mark ... */
movb %al, (%di) /* ... to the new config file name */
testw %cx, %cx /* CX=0 means raw commands are embedded. */
jnz 1f /* normal grub-file, continue */
/* CX=0, and SI points to beginning of the string. */
/* If "#@" is leading, it leads a DOS filename for menu.
* Based on John Cobb (Queen Mary, University of London):
* http://sysdocs.stu.qmul.ac.uk/sysdocs/Comment/Code_Snippets/Grub_For_DOS_Extra/paramfile.s
*/
cmpw $0x4023, (%si) /* "#@" */
jne 2f /* normal embedded menu, continue */
lodsw /* addw $2, %si; see cld above */
dos_filename:
/* SI points to filename */
call open_dos_file
jc invalid_option /* failure */
jmp 1f /* done, continue. */
2:
/* check if it is DOS-style */
cld
movw %si, %di
movw $0x1000, %cx
movb $0x20, %al
repz scasb
//jcxz 1f /* don't check a long menu for dos filename */
testw %cx, %cx
jz 1f /* don't check a long menu for dos filename */
decw %di /* DI points to the first non-space char */
movw %di, %si
movw $70, %cx
xorb %al, %al
repnz scasb
//jcxz 1f /* don't check a long menu for dos filename */
testw %cx, %cx
jz 1f /* don't check a long menu for dos filename */
movw %di, %bx
subw %si, %bx
decw %bx /* length */
movw (%si), %ax
cmpb $0x3A, %ah /* : */
jne 2f
cmpb $0x41, %al /* A */
jb 2f
cmpb $0x7A, %al /* z */
ja 2f
cmpb $0x5A, %al /* Z */
jbe dos_filename //invalid_option
cmpb $0x61, %al /* a */
jae dos_filename //invalid_option
2:
cmpb $0x5C, %al /* \ */
je dos_filename //invalid_option
//cmpb $0x2F, %al /* / */
//je invalid_option
/* if there is a LF, this is not of a DOS filename. */
movw %si, %di
//incw %di
movw %bx, %cx
//decw %cx
movb $0x0A, %al
repnz scasb
jz 1f
/* if there is a back slash, this is of a DOS filename. */
movw %si, %di
//incw %di
movw %bx, %cx
//decw %cx
movb $0x5C, %al
repnz scasb
jz dos_filename //invalid_option
/* now no LFs, no back slahses. So this can only be a filename without
* leading dir part. If there is a space in the file name, then it is
* not treated as a DOS filename.
*/
movw %si, %di
//incw %di
movw %bx, %cx
//decw %cx
movb $0x20, %al
repnz scasb
jz 1f
//movw %si, %di
//incw %di
//movw %bx, %cx
//decw %cx
//movb $0x2F, %al
//repnz scasb
//jz invalid_option
/* now no spaces. If there is a dot in the file name, then it is
* treated as a DOS filename.
*/
movw %si, %di
movw %bx, %cx
movb $0x2E, %al /* . */
repnz scasb
jz dos_filename //invalid_option
movw %si, %di
movw %bx, %cx
movb $0x2D, %al /* - */
repnz scasb
jz dos_filename //invalid_option
//movw %si, %di
//movw %bx, %cx
//movb $0x5F, %al /* _ */
//repnz scasb
//jz invalid_option
movw %si, %di
movw %bx, %cx
movb $0x7E, %al /* ~ */
repnz scasb
jz dos_filename //invalid_option
movw %si, %di
movw %bx, %cx
movb $0x21, %al /* ! */
repnz scasb
jz dos_filename //invalid_option
movw %si, %di
movw %bx, %cx
movb $0x2A, %al /* * */
repnz scasb
jz dos_filename //invalid_option
movw %si, %di
movw %bx, %cx
movb $0x3F, %al /* ? */
repnz scasb
jz dos_filename //invalid_option
1:
done_options:
smsw %ax /* the old 286 code, save MSW to AX */
testb $0x01, %al /* is it in protected mode? */
jnz protected_mode /* continue to check vm86 mode */
jmp backup_low_memory_and_HMA /* in real mode, continue */
invalid_option:
movw $ABS_PSP(usage_string), %dx
movb $0x09, %ah /* display a dollar terminated string */
int $0x21 /* call DOS */
/* hexdump the PSP commandline area */
movb $0x0e, %ah /* display char */
movw $7, %bx /* white */
movb $8, %dh /* 8 sections (1 section = 16 bytes) */
movw $0x0080, %si /* offset in PSP */
4:
movw $0x10, %cx /* display 16 bytes per line */
/* display leading string like "0080: " */
movb $0x30, %al /* display "0" */
int $0x10
int $0x10 /* twice */
movw %si, %ax /* AH modified */
shrb $4, %al /* higher 4 bits of SI */
cmpb $9, %al
jbe 2f
addb $7, %al
2:
addb $0x30, %al /* 0x30 is '0' */
movb $0x0e, %ah /* display char */
int $0x10
movb $0x30, %al /* display "0" */
int $0x10
movb $0x3a, %al /* display ":" */
int $0x10
movb $0x20, %al /* display 3 space chars */
int $0x10
int $0x10
int $0x10
/* display hex values */
1:
lodsb
movb %al, %dl /* save lower 4 bits of AL to DL */
shrb $4, %al /* higher 4 bits */
cmpb $9, %al
jbe 2f
addb $7, %al
2:
addb $0x30, %al /* 0x30 is '0' */
movb $0x0e, %ah /* display char */
//movw $7, %bx /* white */
int $0x10
movb %dl, %al /* restore AL from DL */
andb $0x0f, %al
cmpb $9, %al
jbe 2f
addb $7, %al
2:
addb $0x30, %al /* 0x30 is '0' */
//movb $0x0e, %ah /* display char */
//movw $7, %bx /* white */
int $0x10
movb $0x20, %al /* display a space char */
int $0x10
loop 1b
int $0x10 /* display 2 space chars */
int $0x10
/* display ascii values */
movw $0x10, %cx
subw %cx, %si
1:
lodsb
//cmpb $0x80, %al /* is it big char? */
//jb 2f /* no, continue */
cmpb $7, %al /* is it BELL char? */
je 2f /* no, continue */
cmpb $8, %al /* is it BACKSPACE char? */
je 2f /* no, continue */
cmpb $0x0a, %al /* is it LF char? */
je 2f /* no, continue */
cmpb $0x0d, %al /* is it CR char? */
jne 3f /* no, continue */
2:
movb $0x2e, %al /* display a dot */
3:
int $0x10
loop 1b
movb $0x0d, %al /* display CR char */
int $0x10
movb $0x0a, %al /* display LF char */
int $0x10
decb %dh
jnz 4b
jmp exit_no_message
open_dos_file:
/* SI points to filename */
/* skip any possible leading blanks in the filename. */
movw %si, %di
movw $0x1000, %cx
movb $0x20, %al /* space */
cld
repz scasb
jz 4f //invalid_option /* filename cannot be only spaces */
decw %di /* DI points to the first non-space char */
//AH = 3Dh //OPEN EXISTING FILE
//AL = access and sharing modes
//DS:DX -> ASCIZ filename
//CL = attribute mask of files to look for (server call only)
//Return:
//CF clear if successful
//AX = file handle
//CF set on error
//AX = error code (01h,02h,03h,04h,05h,0Ch,56h)
movzwl %di, %edx
movw $0x3d00, %ax /* open file for reading */
int $0x21 /* call DOS */
jnc 3f
/* open failure */
pushl %edx
movl $ABS_PSP(failed_open_file_string), %edx
movb $0x09, %ah
int $0x21
popl %edx
/* locate the end of filename */
movw %dx, %di
movw $0x1000, %cx
movb $0, %al /* NULL */
cld
repnz scasb
decw %di /* DI points to the ending NULL */
pushw %di
movb $0x24, %al /* '$' */
stosb /* change the NULL to '$' */
movb $0x09, %ah /* display a dollar terminated string */
int $0x21 /* call DOS */
popw %di
movb $0, %al
stosb /* change `$' back to NULL */
jmp 4f //invalid_option /* jump if failed to open */
failed_open_file_string:
.ascii "\r\nUnable to open DOS file: $"
3:
//READ FROM FILE OR DEVICE
//AH = 3Fh
//BX = file handle
//CX = number of bytes to read
//DS:DX -> buffer for data
//Return:
//CF clear if successful
//AX = number of bytes actually read (0 if at EOF before call)
//CF set on error
//AX = error code (05h,06h)
movw %ax, %bx
pushw %bx
movb $0x3f, %ah
movw $0x1000, %cx
movl $0x81, %edx
int $0x21
popw %bx
jnc 3f
/* read failure */
movl $ABS_PSP(failed_read_file_string), %edx
movb $0x09, %ah /* display a dollar terminated string */
int $0x21 /* call DOS */
jmp 4f //invalid_option /* jump if failed to read */
failed_read_file_string:
.ascii "\r\nUnable to read DOS file.$"
3:
//CLOSE FILE
//AH = 3Eh
//BX = file handle
//
//Return:
//CF clear if successful
// AX destroyed
//CF set on error
// AX = error code (06h)
pushw %ax //number of bytes actually read (0 if at EOF before call)
movb $0x3e, %ah // close file (BX = file handle)
int $0x21
popw %bx //number of bytes actually read (0 if at EOF before call)
movb $0, 0x81(%bx) /* end in NULL */
clc
ret
4:
stc
ret
message_exit:
movb $0x09, %ah /* display a dollar terminated string */
int $0x21 /* call DOS */
exit_no_message:
movw $0x4C01, %ax /* exit EXE with error number 01 */
terminate_program:
cmpb $0, %cs:0x80 /* Are we running as EXE or device driver? */
jne 1f /* It is EXE. So we call DOS to exit. */
/* We are running as a device driver. */
/* Calculate the CS */
movw %cs, %bx /* points to START. */
subw $((start -nxtdev) >> 4), %bx
pushw %bx /* points to device driver start */
movw $(device_driver_exit - nxtdev), %bx
pushw %bx
lret
1:
int $0x21 /* call DOS */
program_hangs: /* should not get here, just in case int21/AH=4Ch would fail */
jmp program_hangs
//----------------------------------------------------------------------------
file_name_too_long:
.code16
movw $ABS_PSP(fatal_string), %dx
jmp message_exit
//----------------------------------------------------------------------------
parse_number_dos:
.code16
/* input: ESI points to the number
* EBX upper limit of the number
* output: EBX the value of the number
* ESI points to the next byte after the delimiter
* EBP changed
* CF=1 failure
* CF=0 success
*/
pushl %edx
pushl %eax
pushl %ebx /* upper limit */
cld
movl $16, %ebp /* initialize EBP for hex */
//movl %edi, %esi
movl $0xffffffff, %edx
xorl %ebx, %ebx
lodsw
cmpw $0x5830, %ax /* 0X */
je 2f
cmpw $0x7830, %ax /* 0x */
je 2f
/* decimal */
decl %esi
decl %esi
movl $10, %ebp
2:
xorl %eax, %eax
lodsb
cmpb $0, %al
je 4f /* CF=0 */
cmpb $0x2C, %al /* 0x2C= `,' ends the drive number. */
je 4f /* CF=0 */
cmpb $0x29, %al /* 0x29= `)' ends partition number. */
je 4f /* CF=0 */
cmpb $0x20, %al /* 0x20= ` ' */
je 4f /* CF=0 */
cmpb $0x30, %al
jb 3f
cmpb $0x39, %al
jbe 1f
cmpl $16, %ebp /* Is hex? */
jb 3f /* no, failure */
orb $0x20, %al
cmpb $0x61, %al
jb 3f
cmpb $0x66, %al
ja 3f
subb $0x27, %al
1:
subb $0x30, %al
pushl %eax
movl %ebx, %eax
mull %ebp
movl %eax, %ebx
popl %eax
jc 3f
addl %eax, %ebx
jc 3f
popl %eax
pushl %eax /* upper limit */
cmpl %eax, %ebx
ja 3f
jmp 2b
4:
testl %edx, %edx /* CF=0 */
jz 1f /* return success */
3:
/* return failure */
stc
1:
popl %eax /* upper limit */
popl %eax
popl %edx
ret
protected_mode:
pushfw
popw %ax
testb $0x30, %ah /* IOPL */
jz 1f
/* IOPL is not 0 */
andb $0xcf, %ah /* let IOPL = 0 */
pushw %ax
popfw
pushfw
popw %ax
testb $0x30, %ah /* is IOPL still 0? */
jz 2f /* CPL is 0 */
jmp 3f
1:
/* IOPL is 0 */
orb $0x30, %ah /* let IOPL != 0 */
pushw %ax
popfw
pushfw
popw %ax
testb $0x30, %ah /* is IOPL still 0? */
jnz 2f /* CPL is 0 */
jmp 3f
2:
/* ring 0 of 16-bit protected mode will get here */
andb $0xcf, %ah /* let IOPL = 0 */
pushw %ax
popfw
jmp backup_low_memory_and_HMA /* CPL = 0, Continue */
3:
/* vm86 mode(emm386/Windows DOS Box/LINUX DOSEMU) will get here */
/* CPL != 0 */
/* Note for people who are tracing/debugging this program:
*
* PROBE_INT will take over int01, so probably you don't want
* to trace into PROBE_INT.
*/
call probe_int
//jc failed_probe_int
/* Note for people who are tracing/debugging this program:
*
* The following code will switch from VM86 mode to protected mode,
* and then to real mode. Your debugger will hang in a mode switch.
* So stop tracing now.
*/
/* Use VCPI to switch to protected mode, and then switch to real mode
* and run grub. After returning from grub, switch to protected mode, and
* finally use VCPI again to switch back to vm86 mode.
*/
/* stolen from http://my.execpc.com/~geezer/os/slfb.asm */
#;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
#; Simulated Linear Frame Buffer
#; Christopher Giese <geezer@execpc.com>
#; http://www.execpc.com/~geezer/os/
#;
#; Updated March 6, 2001: can now return to V86 mode when done
#;
#; Some stuff (actually, pmode VESA BIOS bank switching) fixed by
#; Alexei A. Frounze <alexfru@chat.ru>
#; http://alexfru.chat.ru
#;
#; You can do anything with this code but blame us for it.
#;
#; To run this code, you need a PC with a 32-bit processor (386SX or better)
#; and DOS. This code has been tested with the following SVGA video boards:
#;
#; bank-switch function
#; buss chipset used by this code
#; ---- ------- --------------------
#; 16-bit ISA Cirrus 5422 Cirrus
#; 32-bit PCI S3 86c765 (Trio 64V+) S3
#; 32-bit PCI (?) STB Nitro (?) S3
#;
#; To assemble this code, you need NASM (http://www.web-sites.co.uk/nasm/)
#; nasm -f bin -o slfb.com slfb.asm
#;
#; bugs/to do:
#; - test with other systems
#;
#; Here is an interesting link:
#; http://marc.theaimsgroup.com/?m=88102879813311&w=2
#; Also, look at the "vflat" files in the source code to the
#; SciTech MGL graphics library
#;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
movl $1, ABS_PSP(emm386_running)
#; save CS value for return to real mode
movw %cs, ABS_PSP(real_mode_cs)
#; our pmode code will use the stack DOS gave us, but we have
#; to zero the top 16 bits of the stack pointer (ESP)
movzwl %sp, %esp
#; get the virtual-to-physical conversion value. Though we turn on paging,
#; this code runs in memory that is identity-mapped (i.e. no page-based
#; address translation). We do, however, use segment-based address
#; translation, to give things the same addresses in both real and pmode.
xorl %ebx, %ebx
movw %cs, %bx
shll $4, %ebx
#; set base addresses of the 16- and 32-bit code segment descriptors
movl %ebx, %eax
movw %ax, ABS_PSP(gdt2 + 2) #; 32-bit code segment
movw %ax, ABS_PSP(gdt4 + 2) #; 16-bit code segment
shrl $16, %eax
movb %al, ABS_PSP(gdt2 + 4) #; 32-bit code segment
movb %al, ABS_PSP(gdt4 + 4) #; 16-bit code segment
movb %ah, ABS_PSP(gdt2 + 7) #; 32-bit code segment; AH=0
#; movb %ah, ABS_PSP(gdt4 + 7) #; 16-bit code segment
#; now do the same with the data/stack segment descriptors
movl %ebx, %eax
movw %ax, ABS_PSP(gdt3 + 2) #; 32-bit data segment
movw %ax, ABS_PSP(gdt5 + 2) #; 16-bit data segment
shrl $16, %eax
movb %al, ABS_PSP(gdt3 + 4) #; 32-bit data segment
movb %al, ABS_PSP(gdt5 + 4) #; 16-bit data segment
movb %ah, ABS_PSP(gdt3 + 7) #; 32-bit data segment; AH=0
#; movb %ah, ABS_PSP(gdt5 + 7) #; 16-bit data segment
#; point the TSS descriptor to the LINEAR/PHYSICAL address of 'tss'
movl $ABS_PSP(tss), %eax
addl %ebx, %eax
movw %ax, ABS_PSP(gdt6 + 2) #; 32-bit TSS selector
shrl $16, %eax
movb %al, ABS_PSP(gdt6 + 4) #; 32-bit TSS selector
movb %ah, ABS_PSP(gdt6 + 7) #; 32-bit TSS selector
# we don't use protected mode idt during the mode switch
#; point 'gdt_ptr' to LINEAR/PHYSICAL address of the GDT;
#; 'idt_ptr' to LINEAR/PHYSICAL addr of the IDT
addl %ebx, ABS_PSP(gdt_ptr + 2)
addl %ebx, ABS_PSP(idt_ptr + 2)
#; DOS doesn't load .COM or .EXE files on a page (4K) boundary, so
#; we must now find a page boundary for the page directory/tables.
movw %es, %ax
addw $(HMA_backup), %ax
movw %ax, %es
#; page_info immediately follows 64K space at HMA_backup
xorl %esi, %esi
#if 1
movw %es, %si
addw $(HMA_backup+0x1000), %si #; SI=segment of page_info
shll $4, %esi #; ESI = LINEAR/PHYSICAL addr of page_info
#else
movw $ABS_PSP(page_info), %si #; SI = offset of page_info
addl %ebx, %esi #; ESI = LINEAR/PHYSICAL addr of page_info
#endif
addl $4095, %esi #; round to 4K boundary
andl $0xFFFFF000, %esi #; ESI = LINEAR/PHYSICAL addr of page dir
movl %esi, %edi
#if 0
subl %ebx, %edi #; DI = offset of page dir; EDI <= 0xffff
#else
shrl $4, %edi #; DI=segment of page dir
movw %di, %es #; ES:0000 points to page dir
xorw %di, %di
#endif
#; save page dir address for later use by VCPI
movl %esi, ABS_PSP(vcpi_cr3)
#; EAX = LINEAR/PHYSICAL address of "kernel" page table
#; (4K above the page directory)
movl %esi, %eax
addl $4096, %eax
#; create entry in page dir for "kernel" page table
orb $7, %al #; Ring 3, writable, present
#if 0
movl %eax, (%di)
#else
movl %eax, %es:(%di) #; EDI=0
#endif
#; advance DI from page directory to "kernel" page table
addl $4096, %edi #; EDI=4096
#; using the "kernel" page table, identity-map
#; (virtual == physical) the bottom 4M of RAM
#; With VCPI, we can skip this step, and simply take over EMM386's
#; conventional memory page table using INT 67h AX=DE01h
cld
movw $1024, %cx
movl $7, %eax #; Ring 3, writable, present
1:
stosl
addl $4096, %eax #; next page
loop 1b
#; move DI back to "kernel" page table, in case we want to change it
subl $4096, %edi #; EDI=4096
# Windows is running?
pushl %ebx
pushw %es
pushl %edi
movw $0x1600, %ax
int $0x2F
popl %edi
popw %es
popl %ebx
//cmpb $0, %al
//je check_vcpi # not Windows DOS Box
//cmpb $0x80, %al
//je check_vcpi # not Windows DOS Box
shlb $1, %al
jz check_vcpi # not Windows DOS Box
movl $1, ABS_PSP(windows_running)
check_dpmi:
/* print the warning */
movw $ABS_PSP(warning_win_msg), %dx
movb $0x09, %ah /* display a dollar terminated string */
int $0x21 /* call DOS */
movw $ABS_PSP(press_space_bar_string), %dx
cmpw $0x3920, ABS_PSP(hot_key)
je 2f
movw $ABS_PSP(press_hot_key_string), %dx
2:
movb $0x09, %ah /* display a dollar terminated string */
int $0x21 /* call DOS */
movw $ABS_PSP(press_any_key_string), %dx
movb $0x09, %ah /* display a dollar terminated string */
int $0x21 /* call DOS */
movw $5, ABS_PSP(time_out) /* non-zero timeout hangs Windows */
sti /* for sleep_5_seconds to work. */
call sleep_5_seconds
//xorw %ax, %ax
cli /* the clock is not used any more */
movw $ABS_PSP(cancel_msg), %dx
jne message_exit /* not the hot-key, exit to DOS */
/* hot-key pressed, continue */
//// movw $0, ABS_PSP(time_out) /* clear time_out before we continue to run under Windows. */
cmpl $0, ABS_PSP(bypass) /* default is bypass GRUB? */
jne message_exit /* yes, exit to DOS now. */
/* no, continue to run grub under windows. */
/* now bypass is 0. */
#if 0
movw $ABS_PSP(win_msg), %dx #; yup, it's 'Doze
jmp message_exit #; Cant_Enter_PMode
#endif
#; check dpmi
#;
#; Get the entry point address and save it
#;
movw $0x1687, %ax #; DOS Protected-Mode Interface - INSTALLATION CHECK
int $0x2F #; Return:
#; AX = 0000h if installed BX = flags
#;
#; bit 0: 32-bit programs supported
#; CL = processor type (02h=80286, 03h=80386, 04h=80486)
#; DH = DPMI major version
#; DL = two-digit DPMI minor version (binary)
#; SI = number of paragraphs of DOS extender private data
#; ES:DI -> DPMI mode-switch entry point.
#; AX nonzero if not installed
testw %ax, %ax
movw $ABS_PSP(win_msg), %dx #; yup, it's 'Doze
jnz message_exit #; Cant_Enter_PMode
movw %es, ABS_PSP(PMode_Entry_Seg)
movw %di, ABS_PSP(PMode_Entry_Off)
#;
#; Allocate memory for use by DOS extender if necessary
#; NOTE: This code assumes that the program has already
#; shrunk its memory block so that the DOS
#; memory allocation call will work
#;
testw %si, %si
jz Enter_PMode_Now
movw %si, %bx
movb $0x48, %ah
int $0x21
jc message_exit #; Cant_Enter_PMode
movw %ax, %es
#;
#; Enter protected mode as a 16-bit program
#;
Enter_PMode_Now:
#; After using Int 2Fh function 1687h, to obtain the protected mode
#; entry point, the DPMI client must call the entry point address as
#; described in this section.
#;To Call
#; AX = Flags
#; Bit 0 = 1 if program is a 32-bit application
#; ES = Real mode segment of DPMI host data area. This
#; must be the size of the data area returned in SI
#; from the previous function. ES will be ignored if
#; the required data size is zero.
#; Call the address returned in ES:DI by the previous
#; function
#;Returns
#; If function was successful:
#; Carry flag is clear.
#; Program is now executing in protected mode.
#; CS = 16-bit selector with base of real mode CS and a
#; 64K limit
#; SS = Selector with base of real mode SS and a 64K limit
#; DS = Selector with base of real mode DS and a 64K limit
#; ES = Selector to program's PSP with a 100h byte limit
#; FS and GS = 0 (if running on an 80386 or 80486)
#; If the program is a 32-bit application the high word of
#; ESP will be 0
#; All other registers are preserved
#; If function was not successful:
#; Carry flag is set.
#; Program is executing in real mode
xorw %ax, %ax
//movw %ax, %ds #; DS=0
lcall %cs:*ABS_PSP(PMode_Entry_Off)
jc message_exit #; Cant_Enter_PMode
#;
#; The program is running in protected mode now!
ring_check:
#; 5. clear the PE bit
movl %cr0, %eax
andb $0xFE, %al
movl %eax, %cr0
pushfw
popw %ax
testb $0x30, %ah /* IOPL */
jz 4f
/* IOPL is not 0 */
andb $0xcf, %ah /* let IOPL = 0 */
pushw %ax
popfw
pushfw
popw %ax
testb $0x30, %ah /* is IOPL still 0? */
jz 2f /* CPL is 0 */
jmp 3f
4:
/* IOPL is 0 */
orb $0x30, %ah /* let IOPL != 0 */
pushw %ax
popfw
pushfw
popw %ax
testb $0x30, %ah /* is IOPL still 0? */
jnz 2f /* CPL is 0 */
jmp 3f
2:
/* ring 0 of 16-bit protected mode will get here */
andb $0xcf, %ah /* let IOPL = 0 */
pushw %ax
popfw
jmp real2 /* CPL = 0, Continue */
3:
/* CPL != 0 */
#; we must switch to ring 0
#; Allocate Descriptors
movw $0x0000, %ax #; function number
movw $1, %cx #; CX = Number of descriptors to allocate
int $0x31 #; call DPMI
#; return AX = Base selector
movw $ABS_PSP(win_msg), %dx #; yup, it's 'Doze
jc message_exit #; Cant_Enter_PMode
movw %ax, ABS_PSP(selector_alloc)
#; Get Descriptor
movw $0x000B, %ax #; function number
movw %ds, %bx #; selector number
#; ES:(E)DI = Pointer to an 8 byte buffer to receive copy of descriptor
movl $ABS_PSP(descriptor_buf), %edi
pushw %es
pushw %ds
popw %es
int $0x31 #; call DPMI
popw %es
#; DS:DI points to buffer
movw $ABS_PSP(win_msg), %dx #; yup, it's 'Doze
jc message_exit #; Cant_Enter_PMode
#; Set Descriptor
movw $0x000C, %ax #; function number
movw ABS_PSP(selector_alloc), %bx #; selector number
#; ES:(E)DI = Pointer to an 8 byte buffer that contains descriptor.
#; The type byte (byte 5) follows the same format and restrictions
#; as the access rights/type parameter (in CL) to Set Descriptor
#; Access Rights. The extended type byte (byte 6) follows the same
#; format and restrictions as the extended access rights/type parameter
#; (in CH) to Set Descriptor Access Rights, except the limit field may
#; have any value, except the low order 4 bits (marked "reserved") are
#; used to set the upper 4 bits of the descriptor's limit.
movl $ABS_PSP(descriptor_buf), %edi
pushw %es
pushw %ds
popw %es
int $0x31 #; call DPMI
popw %es
#; DS:DI points to buffer
movw $ABS_PSP(win_msg), %dx #; yup, it's 'Doze
jc message_exit #; Cant_Enter_PMode
movw ABS_PSP(selector_alloc), %cx
incw %cx
testw $7, %cx
jnz message_exit
// . = . - (ABS_PSP(.) - 0x28F3)
movw ABS_PSP(selector_alloc), %cx
movw %cx, %fs #; FS is the new selector
#; get LDTR(it is a selector that points into GDT)
// pushw %ds
// popw %fs #; fs base = 0, limit=64K
// pushw %ss
// popw %ds
#; Set Segment Base Address of fs to 0
#; Sets the 32-bit linear base address field in the LDT descriptor
#; for the specified segment.
movw $0x0007, %ax #; function number
movw %fs, %bx #; selector number
#; CX:DX = 32-bit linear base address of segment
xorw %cx, %cx
xorw %dx, %dx
int $0x31 #; call DPMI
movw $ABS_PSP(win_msg), %dx #; yup, it's 'Doze
jc message_exit #; Cant_Enter_PMode
#; check if fs base == 0
#; Get Segment Base Address
movw $0x0006, %ax #; function number
movw %fs, %bx #; selector number
int $0x31 #; call DPMI
#; return CX:DX = 32-bit linear base address of segment
movw %dx, %ax
movw $ABS_PSP(win_msg), %dx #; yup, it's 'Doze
jc message_exit #; Cant_Enter_PMode
testw %ax, %ax
jnz message_exit
testw %cx, %cx
jnz message_exit
#; adjust fs limit to 4G
#; AX = 0008h
#; BX = Selector
#; CX:DX = 32-bit segment limit
movw $0x0008, %ax #; function number
movw %fs, %bx #; selector number
movw $0xffff, %cx
movw $0xffff, %dx
int $0x31 #; call DPMI
movw $ABS_PSP(win_msg), %dx #; yup, it's 'Doze
jc message_exit #; Cant_Enter_PMode
#if 1
xorl %ecx, %ecx
movl %fs, %ecx
lsll %ecx, %eax
jnz message_exit
movw $ABS_PSP(err_msg), %dx #; yup, it's 'Doze
incl %eax
jnz message_exit
sgdt ABS_PSP(gdtr)
// movl ABS_PSP(gdtr + 2), %ebx #; GDT linear base address
//
// fs testl $0xFFFFFFFF, (%ebx)
// movw $ABS_PSP(win_msg), %dx #; yup, it's 'Doze
// jnz message_exit #; Cant_Enter_PMode
#endif
#if 0
#; Get CS Base Address
movw $0x0006, %ax #; function number
movw %cs, %bx #; selector number
int $0x31 #; call DPMI
#; return CX:DX = 32-bit linear base address of segment
movw %dx, %ax
movw $ABS_PSP(win_msg), %dx #; yup, it's 'Doze
jc message_exit #; Cant_Enter_PMode
shll $16, %ecx
movw %ax, %cx #; ECX=linear base address of CS
xorl %edx, %edx
movw ABS_PSP(real_mode_cs), %dx
shll $4, %edx
cmpl %ecx, %edx
movw $ABS_PSP(win_msg), %dx #; yup, it's 'Doze
jnz message_exit #; Cant_Enter_PMode
#endif
#; Set Protected Mode Interrupt Vector
#;
movw $0x0205, %ax #; function number
movb $0x00, %bl #; Interrupt number
#; CX:(E)DX = Selector:Offset of exception handler
xorl %edx, %edx
xorl %ecx, %ecx
#if 0
movw ABS_PSP(real_mode_cs), %cx
movw $ABS_PSP(int3_handler), %dx
shll $4, %ecx
addl %ecx, %edx #; EDX=linear address
movl $0x28, %ecx #; win9x selector with ring 0
#else
movw %cs, %cx
movw $ABS_PSP(int3_handler), %dx
#endif
int $0x31 #; call DPMI
movw $ABS_PSP(win_msg), %dx #; yup, it's 'Doze
jc message_exit #; Cant_Enter_PMode
// jnc message_exit #; Cant_Enter_PMode
movw %cs, %cx
incw %cx
testw $7, %cx
jnz message_exit
#; Allocate Descriptors
movw $0x0000, %ax #; function number
movw $1, %cx #; CX = Number of descriptors to allocate
int $0x31 #; call DPMI
#; return AX = Base selector
movw $ABS_PSP(win_msg), %dx #; yup, it's 'Doze
jc message_exit #; Cant_Enter_PMode
movw %ax, ABS_PSP(selector_alloc)
#; Get Descriptor
movw $0x000B, %ax #; function number
movw %cs, %bx #; selector number
#; ES:(E)DI = Pointer to an 8 byte buffer to receive copy of descriptor
movl $ABS_PSP(descriptor_buf), %edi
pushw %es
pushw %ds
popw %es
int $0x31 #; call DPMI
popw %es
#; DS:DI points to buffer
movw $ABS_PSP(win_msg), %dx #; yup, it's 'Doze
jc message_exit #; Cant_Enter_PMode
// andb $0x9F, 5(%di) #; Let DPL=0
#; Set Descriptor
movw $0x000C, %ax #; function number
movw ABS_PSP(selector_alloc), %bx #; selector number
#; ES:(E)DI = Pointer to an 8 byte buffer that contains descriptor.
#; The type byte (byte 5) follows the same format and restrictions
#; as the access rights/type parameter (in CL) to Set Descriptor
#; Access Rights. The extended type byte (byte 6) follows the same
#; format and restrictions as the extended access rights/type parameter
#; (in CH) to Set Descriptor Access Rights, except the limit field may
#; have any value, except the low order 4 bits (marked "reserved") are
#; used to set the upper 4 bits of the descriptor's limit.
movl $ABS_PSP(descriptor_buf), %edi
pushw %es
pushw %ds
popw %es
int $0x31 #; call DPMI
popw %es
#; DS:DI points to buffer
movw $ABS_PSP(win_msg), %dx #; yup, it's 'Doze
jc message_exit #; Cant_Enter_PMode
movw ABS_PSP(selector_alloc), %cx
incw %cx
testw $7, %cx
jnz message_exit
#; get LDTR(it is a selector that points into GDT)
sldt %bx
#if 0
/* cannot get ldt base in this way because the function works only
* for BX=(a selector that points into LDT)
*/
#; Get LDT Base Address
movw $0x0006, %ax #; function number
// movw %fs, %bx #; selector number
int $0x31 #; call DPMI
#; return CX:DX = 32-bit linear base address of LDT
// movw $ABS_PSP(err_msg), %dx #; DEBUG DPMI
// cmpw $0x8022, %ax
// jz message_exit #; Cant_Enter_PMode
// movw $ABS_PSP(win_msg), %dx #; yup, it's 'Doze
// jmp message_exit
movw %dx, %ax
movw $ABS_PSP(win_msg), %dx #; yup, it's 'Doze
jc message_exit
#else
movl ABS_PSP(gdtr + 2), %eax #; GDT linear base address
andl $0xFFF8, %ebx
addl %eax, %ebx #; EBX points to LDT descriptor
addr32 fs movl 2(%ebx), %eax #; lower 3 byte of LDT base
roll $8, %eax
andb $0x1F, %al
cmpb $0x02, %al
jne message_exit
addr32 fs movb 7(%ebx), %al #; higher byte of LDT base
rorl $8, %eax #; EAX=linear address of LDT base
movl %eax, ABS_PSP(ldt_base)
#endif
// movw %cx, %bx
// shll $16, %ebx
// movw %ax, %bx #; EBX=LDT linear base address
movl ABS_PSP(ldt_base), %ebx
xorl %ecx, %ecx
movw ABS_PSP(selector_alloc), %cx
andw $0xFFF8, %cx #; offset in LDT
addl %ecx, %ebx #; points to descriptor
addr32 fs movb 5(%ebx), %al
xorb $0x60, %al
andb $0x60, %al
jnz message_exit
addr32 fs xorb $0x60, 5(%ebx) #; Let DPL=0
// movw $ABS_PSP(win_msg), %dx #; yup, it's 'Doze
// jz message_exit
#; Set Protected Mode Interrupt Vector
#;
movw $0x0205, %ax #; function number
movb $0x00, %bl #; Interrupt number
#; CX:(E)DX = Selector:Offset of exception handler
xorl %edx, %edx
xorl %ecx, %ecx
movw ABS_PSP(selector_alloc), %cx
andw $0xFFFC, %cx
movw $ABS_PSP(int3_handler), %dx
int $0x31 #; call DPMI
movw $ABS_PSP(win_msg), %dx #; yup, it's 'Doze
jc message_exit #; Cant_Enter_PMode
#; Physical Address Mapping
#;
#; To Call
#; AX = 0800h
#; BX:CX = Physical address of memory
#; SI:DI = Size of region to map in bytes
#; Returns
#; If function was successful:
#; Carry flag is clear.
#; BX:CX = Linear address that can be used to access the physical memory
#; If function was not successful:
#; Carry flag is set.
movw $0x0800, %ax
movw $0, %bx
movw $0, %cx
movw $0x000A, %si
movw $0, %di
int $0x31
movw $ABS_PSP(win_msg), %dx #; yup, it's 'Doze
jc message_exit
movw %cx, ABS_PSP(conventional_linear)
movw %bx, ABS_PSP(conventional_linear + 2)
#; Allocate Memory Block
#; Allocates and commits a block of linear memory.
#; Call With
#; AX = 0501H
#; BX:CX = size of block (bytes, must be nonzero)
#; Returns
#; if function successful
#; Carry flag = clear
#; BX:CX = linear address of allocated memory block
#; SI:DI = memory block handle (used to resize and free block)
#; if function unsuccessful
#; Carry flag = set
#; AX = error code
#; 8012H linear memory unavailable
#; 8013H physical memory unavailable
#; 8014H backing store unavailable
#; 8016H handle unavailable
#; 8021H invalid value (BX:CX = 0)
movw $0x0501, %ax
movw $0x000A, %bx
movw $0, %cx
int $0x31
movw $ABS_PSP(win_msg), %dx #; yup, it's 'Doze
jc message_exit
movw %cx, ABS_PSP(conventional_backup)
movw %bx, ABS_PSP(conventional_backup + 2)
#; Allocate Descriptors
movw $0x0000, %ax #; function number
movw $1, %cx #; CX = Number of descriptors to allocate
int $0x31 #; call DPMI
#; return AX = Base selector
movw $ABS_PSP(win_msg), %dx #; yup, it's 'Doze
jc message_exit #; Cant_Enter_PMode
movw %ax, ABS_PSP(selector_alloc1)
#; Get Descriptor
movw $0x000B, %ax #; function number
movw %cs, %bx #; selector number
#; ES:(E)DI = Pointer to an 8 byte buffer to receive copy of descriptor
movl $ABS_PSP(descriptor_buf), %edi
pushw %es
pushw %ds
popw %es
int $0x31 #; call DPMI
// . = . - (ABS_PSP(.) - 0x2AE6)
popw %es
#; DS:DI points to buffer
movw $ABS_PSP(win_msg), %dx #; yup, it's 'Doze
jc message_exit #; Cant_Enter_PMode
#; Set Descriptor
movw $0x000C, %ax #; function number
movw ABS_PSP(selector_alloc1), %bx #; selector number
#; ES:(E)DI = Pointer to an 8 byte buffer that contains descriptor.
#; The type byte (byte 5) follows the same format and restrictions
#; as the access rights/type parameter (in CL) to Set Descriptor
#; Access Rights. The extended type byte (byte 6) follows the same
#; format and restrictions as the extended access rights/type parameter
#; (in CH) to Set Descriptor Access Rights, except the limit field may
#; have any value, except the low order 4 bits (marked "reserved") are
#; used to set the upper 4 bits of the descriptor's limit.
movl $ABS_PSP(descriptor_buf), %edi
pushw %es
pushw %ds
popw %es
int $0x31 #; call DPMI
popw %es
#; DS:DI points to buffer
movw $ABS_PSP(win_msg), %dx #; yup, it's 'Doze
jc message_exit #; Cant_Enter_PMode
movw ABS_PSP(selector_alloc1), %cx
incw %cx
testw $7, %cx
jnz message_exit
movw ABS_PSP(selector_alloc1), %cx
movw %cx, %gs #; GS is the new selector
#; Set Segment Base Address of gs
#; Sets the 32-bit linear base address field in the LDT descriptor
#; for the specified segment.
movw $0x0007, %ax #; function number
movw %gs, %bx #; selector number
#; CX:DX = 32-bit linear base address of segment
//movw ABS_PSP(conventional_linear), %dx
//movw ABS_PSP(conventional_linear + 2), %cx
//xorw %cx, %cx
//xorw %dx, %dx
xorl %ecx, %ecx
xorl %edx, %edx
movw ABS_PSP(real_mode_cs), %cx
//shll $4, %ecx
//movw %cx, %dx
//shrl $16, %ecx
movw %cx, %dx
shrw $12, %cx
shlw $4, %dx
int $0x31 #; call DPMI
movw $ABS_PSP(win_msg), %dx #; yup, it's 'Doze
jc message_exit #; Cant_Enter_PMode
#; adjust gs limit to 64K
#; AX = 0008h
#; BX = Selector
#; CX:DX = 32-bit segment limit
movw $0x0008, %ax #; function number
movw %gs, %bx #; selector number
movw $0x0000, %cx
movw $0xFFFF, %dx
int $0x31 #; call DPMI
movw $ABS_PSP(win_msg), %dx #; yup, it's 'Doze
jc message_exit #; Cant_Enter_PMode
xorl %ecx, %ecx
movl %gs, %ecx
lsll %ecx, %eax
jnz message_exit
cmpl $0x0000FFFF, %eax
jnz message_exit
movl ABS_PSP(ldt_base), %ebx
xorl %ecx, %ecx
movw ABS_PSP(selector_alloc1), %cx
andw $0xFFF8, %cx #; offset in LDT
addl %ecx, %ebx #; points to descriptor
addr32 fs movb 5(%ebx), %al
xorb $0x60, %al
andb $0x60, %al
jnz message_exit
addr32 fs xorb $0x60, 5(%ebx) #; Let DPL=0
#if 0
#; Set Protected Mode Interrupt Vector
#;
movw $0x0205, %ax #; function number
movb $0x00, %bl #; Interrupt number
#; CX:(E)DX = Selector:Offset of exception handler
xorl %edx, %edx
xorl %ecx, %ecx
movw ABS_PSP(selector_alloc1), %cx
andw $0xFFFC, %cx
movw ABS_PSP(real_mode_cs), %dx
shll $4, %edx
addl $ABS_PSP(int3_handler), %edx
int $0x31 #; call DPMI
movw $ABS_PSP(win_msg), %dx #; yup, it's 'Doze
jc message_exit #; Cant_Enter_PMode
#endif
// movw %cs, %cx
// incw %cx
// testw $7, %cx
// jnz message_exit
#if 0
//pushl %edx
sidt ABS_PSP(idtr)
//popl %edx #; EDX=IDT base address
movl ABS_PSP(idtr + 2), %edx
addl $(0x00 * 8), %edx
pushw %ds
movw $0x30, %cx
movw %cx, %ds
addr32 orw $0xE000, 4(%edx)
popw %ds
#; EBX=original offset of int3 service routine
#endif
/* we can use int00 instead of int03 */
xorw %bx, %bx
xorw %dx, %dx
movw $1, %ax
#if 0
divw %bx
#else
int $0x00
#endif
movw $ABS_PSP(win_msg), %dx #; yup, it's 'Doze
jmp message_exit #; Cant_Enter_PMode
/* DPMI DATA */
dpmi_old_cr0:
.long 0
dpmi_old_cr3:
.long 0
ldt_base:
.long 0
conventional_linear:
.long 0
conventional_backup:
.long 0
descriptor_buf:
.long 0
.long 0
selector_alloc:
.word 0
selector_alloc1:
.word 0
gdtr:
.word 0
.long 0
idtr:
.word 0
.long 0
/* VCPI DATA */
#; Even if you don't use TSS-based task-switching, you need one
#; TSS to hold the user stack pointer.
tss:
.word 0, 0 #; back link
.long 0 #; ESP0
.word gdt3 - gdt0, 0 #; SS0, reserved
.long 0 #; ESP1
.word 0, 0 #; SS1, reserved
.long 0 #; ESP2
.word 0, 0 #; SS2, reserved
.long 0 #; CR3
.long 0, 0 #; EIP, EFLAGS
.long 0, 0, 0, 0 #; EAX, ECX, EDX, EBX
.long 0, 0, 0, 0 #; ESP, EBP, ESI, EDI
.word 0, 0 #; ES, reserved
.word 0, 0 #; CS, reserved
.word 0, 0 #; SS, reserved
.word 0, 0 #; DS, reserved
.word 0, 0 #; FS, reserved
.word 0, 0 #; GS, reserved
.word 0, 0 #; LDT, reserved
.word 0, 0 #; debug, IO perm. bitmap
.align 16
#; null descriptor
gdt0:
.word 0 #; limit 15:0
.word 0 #; base 15:0
.byte 0 #; base 23:16
.byte 0 #; type
.byte 0 #; limit 19:16, flags
.byte 0 #; base 31:24
#; LINEAR_SEL equ $-gdt
gdt1:
.word 0xFFFF
.word 0
.byte 0
.byte 0x92 #; present, ring 0, data, expand-up, writable
.byte 0xCF #; page-granular, 32-bit
.byte 0
#; CODE_SEL equ $-gdt
gdt2:
.word 0xFFFF
.word 0
.byte 0
.byte 0x9A #; present, ring 0, code, non-conforming, readable
.byte 0xCF #; page-granular, 32-bit
.byte 0
#; DATA_SEL equ $-gdt
gdt3:
.word 0xFFFF
.word 0
.byte 0
.byte 0x92 #; present, ring 0, data, expand-up, writable
.byte 0xCF #; page-granular, 32-bit
.byte 0
#; CODE_SEL16 equ $-gdt
gdt4:
.word 0xFFFF
.word 0
.byte 0
.byte 0x9A #; present, ring 0, code, non-conforming, readable
.byte 0 #; byte-granular, 16-bit
.byte 0
#; DATA_SEL16 equ $-gdt
gdt5:
.word 0xFFFF
.word 0
.byte 0
.byte 0x92 #; present, ring 0, data, expand-up, writable
.byte 0 #; byte-granular, 16-bit
.byte 0
#; TSS_SEL equ $-gdt
gdt6:
.word 103
.word 0
.byte 0
.byte 0x89 #; Ring 0 available 32-bit TSS
.byte 0
.byte 0
#; VCPI_SEL equ $-gdt
gdt7:
.long 0, 0 #; dummy descriptors used by VCPI
.long 0, 0
.long 0, 0
gdt10_end:
idt:
.rept 32
.word 0 #; low 16 bits of ISR address
.word gdt2 - gdt0 #; 32-bit code segment selector
.byte 0 #; word count
.byte 0x8E #; access byte: Present, Ring 0, '386 intr gate
.word 0 #; high 16 bits of ISR
.endr
idt_end:
gdt_ptr:
.word gdt10_end - gdt0 - 1 #; GDT limit
.long ABS_PSP(gdt0) #; linear, physical address of GDT
idt_ptr:
.word idt_end - idt - 1 #; IDT limit
.long ABS_PSP(idt) #; linear, physical address of IDT
real_idt_ptr:
.word 0x3FF #; limit 1023
.long 0 #; IDT (IVT, actually) at address 0
#;krnl_page_table:
#; dw 0
vcpi_entry:
.long 0
.word gdt7 - gdt0 #; dummy descriptors used by VCPI
vcpi_cr0:
.long 0
vcpi_control_block:
vcpi_cr3:
.long 0
vcpi_gdtr:
.long ABS_PSP(gdt_ptr)
vcpi_idtr:
.long ABS_PSP(idt_ptr)
vcpi_ldtr:
.word 0
vcpi_tr:
.word gdt6 - gdt0 #; 32-bit TSS selector
vcpi_eip:
.long ABS_PSP(from_vcpi)
vcpi_cs:
.word gdt2 - gdt0 #; 32-bit code segment selector
//page_info:
// .space 4096 #; padding to 4K boundary
// .space 4096 #; page dir somewhere in here
// .space 4096 #; "kernel" page table for bottom 4 meg
#;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
#; DATA
#;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PMode_Entry_Off:
.word 0
PMode_Entry_Seg:
.word 0
windows_running:
.long 0
emm386_running:
.long 0
error_message:
.long 0
err_msg:
.ascii "\r\nIt is in VM86 mode but without VCPI. Enable VCPI with EMM386 and try again.\r\n"
.ascii "\r\nThis problem will occur when GRUB.EXE runs at the Command Prompt of Windows\r\n"
.ascii "NT/2K/XP. If so, restart the computer with a bootable DOS floppy and try again.\r\n$"
win_msg:
.ascii "\r\nDue to a DPMI failure, GRUB.EXE cannot run in a Windows DOS box.\r\n"
.ascii "\r\nRestart the computer in MS-DOS mode and try again.\r\n$"
warning_win_msg:
.ascii "\r\nRunning GRUB.EXE from a Windows DOS box could hang the machine!\r\n"
.ascii "\r\nYou should better restart the computer in MS-DOS mode and run GRUB.EXE there.\r\n"
.ascii "\r\n !!Save your work before you choose to continue!!\r\n$"
cancel_msg:
.ascii "\r\n\r\nCancelled.\r\n$"
vcpi_err_msg:
.ascii "\r\nError getting VCPI server entry point.\r\n$"
int3_handler:
/* it is our handler, int3 or int0 */
/* it seems our handler is in 16-bit protected mode */
.code16
cli
// jmp 0f
//
// #; simply return if we are in real mode
// incl %ebx
// xorw %dx, %dx
// xorw %ax, %ax
//
// iret
//
//0:
#;jmp ring_check
#; 5. clear the PE bit
movl %cr0, %eax
andb $0xFE, %al
movl %eax, %cr0
pushfw
popw %ax
testb $0x30, %ah /* IOPL */
jz 4f
/* IOPL is not 0 */
andb $0xcf, %ah /* let IOPL = 0 */
pushw %ax
popfw
pushfw
popw %ax
testb $0x30, %ah /* is IOPL still 0? */
jz 2f /* CPL is 0 */
jmp 3f
4:
/* IOPL is 0 */
orb $0x30, %ah /* let IOPL != 0 */
pushw %ax
popfw
pushfw
popw %ax
testb $0x30, %ah /* is IOPL still 0? */
jnz 2f /* CPL is 0 */
jmp 3f
3:
// . = . - (ABS_PSP(.) - 0x2A36)
/* CPL != 0 */
#if 0
movw $1, %bx
movw $0, %ax
movw $0, %dx
iret
#endif
movw $ABS_PSP(win_msg), %dx #; yup, it's 'Doze
jmp message_exit #; Cant_Enter_PMode
2:
/* ring 0 of 16-bit protected mode will get here */
andb $0xcf, %ah /* let IOPL = 0 */
pushw %ax
popfw
/* we are in 16-bit protected mode with CPL=0 */
#if 0
/* for debug only */
movl $ABS_PSP(int3_handler), %eax
xorl %ecx, %ecx
movw ABS_PSP(real_mode_cs), %cx
shll $4, %ecx
addl %ecx, %eax
addr32 fs cmpw $0xEBFA, (%eax)
jne 0f
// jmp real2 /* CPL = 0, Continue */
movw $ABS_PSP(win_msg), %dx #; yup, it's 'Doze
0:
jmp message_exit #; Cant_Enter_PMode
#endif
/* now switch from privileged protected mode to real mode */
#; save old dpmi cr0 and cr3
movl $ABS_PSP(dpmi_old_cr0), %edi
movl %cr0, %eax
movl %eax, (%di)
xorl %esi, %esi
movw ABS_PSP(real_mode_cs), %si
shll $4, %esi
addl %esi, %edi
// . = . - (ABS_PSP(.) - 0x2BBA)
addr32 fs movl %eax, (%edi)
movl $ABS_PSP(dpmi_old_cr3), %edi
movl %cr3, %eax
movl %eax, (%di)
xorl %esi, %esi
movw ABS_PSP(real_mode_cs), %si
shll $4, %esi
addl %esi, %edi
addr32 fs movl %eax, (%edi)
#; get physical base address of page dir
andl $0xFFFFF000, %eax
movw %ax, %cx
shrl $16, %eax
movw %ax, %bx
#; Physical Address Mapping
#;
#; To Call
#; AX = 0800h
#; BX:CX = Physical address of memory
#; SI:DI = Size of region to map in bytes
#; Returns
#; If function was successful:
#; Carry flag is clear.
#; BX:CX = Linear address that can be used to access the physical memory
#; If function was not successful:
#; Carry flag is set.
movw $0x0800, %ax
movw $0x0000, %si
movw $0x1000, %di
int $0x31
movw $ABS_PSP(win_msg), %dx #; yup, it's 'Doze
jc message_exit
movw %bx, %si
shll $16, %esi
movw %cx, %si #; ESI=linear base address of page dir
#; load the first entry
cld
// . = . - (ABS_PSP(.) - 0x2BE8)
addr32 fs lodsl
pushl %eax
#; copy all page dir entries except the first one
movw %es, %ax
movw %fs, %bx
movw %bx, %es
movl $(1024 - 1), %ecx
movl ABS_PSP(vcpi_cr3), %edi
addl $4, %edi
cld
repz addr32 fs movsl
movw %ax, %es
popl %eax
#; get physical base address of the first page table
andl $0xFFFFF000, %eax
movw %ax, %cx
shrl $16, %eax
movw %ax, %bx
#; Physical Address Mapping
#;
#; To Call
#; AX = 0800h
#; BX:CX = Physical address of memory
#; SI:DI = Size of region to map in bytes
#; Returns
#; If function was successful:
#; Carry flag is clear.
#; BX:CX = Linear address that can be used to access the physical memory
#; If function was not successful:
#; Carry flag is set.
movw $0x0800, %ax
movw $0x0000, %si
movw $0x1000, %di
int $0x31
movw $ABS_PSP(win_msg), %dx #; yup, it's 'Doze
jc message_exit
movw %bx, %si
shll $16, %esi
movw %cx, %si #; ESI=linear base address of the first page table
#; skip the leading 160 page table entries
addl $(160 * 4), %esi #; 160 = 640K / 4K
#; copy the rest of the page table entries
movw %es, %ax
movw %fs, %bx
movw %bx, %es
movl $(1024 - 160), %ecx
movl ABS_PSP(vcpi_cr3), %edi
addl $(4096 + (160 * 4)), %edi
cld
repz addr32 fs movsl
movw %ax, %es
#if 1
#; move 640K linear conventional memory to physical 0
cld
movw %es, %ax
movw %fs, %bx
movw %bx, %es
movl ABS_PSP(conventional_backup), %edi
xorl %esi, %esi
movl $(0xA0000 / 4), %ecx
repz addr32 fs movsl
movl ABS_PSP(conventional_linear), %edi
movl ABS_PSP(conventional_backup), %esi
movl $(0xA0000 / 4), %ecx
repz addr32 fs movsl
movw %ax, %es
#else
#; move only the grub.exe image from linear to physical
movw %es, %ax
movw %fs, %bx
movw %bx, %es
movl $((ABS_PSP(STAGE2_SIZE + pre_stage2_start + 15 + 0x10000 + 0x03000) / 16) * 4), %ecx
#; 0x10000 = 64KB for HMA_backup
#; 0x03000 = 12KB for VCPI page_info
movl ABS_PSP(conventional_linear), %edi
xorl %esi, %esi
movw ABS_PSP(real_mode_cs), %si
shll $4, %esi
addl %esi, %edi
cld
repz addr32 fs movsl
movw %ax, %es
#endif
#; load cr3
movl ABS_PSP(vcpi_cr3), %eax
movl %eax, %cr3
#; now the low 640K is identity-mapped
#; back-to-real-mode sequence from 14.5 of 386INTEL.TXT
#; 1. TURN OFF PAGING
#; 1a. jump to code memory which is identity-mapped
/* Load new GDT. All memory references should be limited in
* conventional memory, i.e., lower than physical address 640K.
*/
lgdt ABS_PSP(gdt_ptr)
ljmp $(gdt4 - gdt0), $ABS_PSP(2f) #; 16-bit code segment
2:
#; 1b. clear the PG bit
movl %cr0, %eax
andl $0x7FFFFFFF, %eax
movl %eax, %cr0
#; 1c. "Move zeros to CR3 to clear out the paging cache."
xorl %eax, %eax
movl %eax, %cr3
#; 2. jump to 16-bit code segment
//ljmp $(gdt4 - gdt0), $ABS_PSP(real2)
// #; (gdt4 - gdt0) is 16-bit code segment selector
#; we are already in 16-bit code segment
jmp real2
.code16
#; Protected mode initialization code would go here.
#; Mark program's real mode memory as pageable, etc.
#;
#; .
#; .
#; .
#;
#; Quit the program and return to real mode DOS
#;
#; mov ax, 4C00h
#; int 21h
check_vcpi:
#; if it's EMM386, we can use VCPI to switch to protected mode
#; Testing for VCPI like this makes Win95 pop up a dialog suggesting
#; "MS-DOS mode". This may or may not be a good thing.
movw $0xDE00, %ax
pushl %ebx #; save EBX; not interested in VCPI version
pushw %es
pushl %edi
int $0x67
popl %edi
popw %es
popl %ebx
cmpb $0, %ah
movw $ABS_PSP(err_msg), %dx
#if 1
jne message_exit # no VCPI; Windows NT/2K/XP
#else
jne check_dpmi # no VCPI; Windows NT/2K/XP
# DPMI for NT/2K/XP does not work :-(
#endif
#; get "kernel" page table and partial GDT from the VCPI server.
#; If we set up our own identity-mapped page table, this step is optional.
#; (we still need to do INT 67h AX=DE01h to get the pmode entry
#; point to the VCPI server -- so we can return to V86 mode later)
#; ES:DI points to buffer for "kernel" page table
#; DS:SI points to buffer for 3 entries in GDT
movw $ABS_PSP(gdt7), %si #; dummy descriptors used by VCPI
movw $0xDE01, %ax
pushl %ebx
//pushl %edi
int $0x67
movl %ebx, ABS_PSP(vcpi_entry)
//popl %edi
popl %ebx
cmpb $0, %ah
movw $ABS_PSP(vcpi_err_msg), %dx
jne message_exit
#; set up the VCPI control block for the switch to pmode
addl %ebx, ABS_PSP(vcpi_gdtr)
addl %ebx, ABS_PSP(vcpi_idtr)
#; disable interrupts
cli
#; OK, let's do it
movl $ABS_PSP(vcpi_control_block), %esi
addl %ebx, %esi
movw $0xDE0C, %ax
#; if all goes well, the interrupt will return at 'from_vcpi'
int $0x67
#;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
#; MAIN 32-BIT PMODE CODE, here from VCPI
#;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
from_vcpi:
/* This is in 32-bit protected mode. CS=(gdt2 - gdt0) */
.code32
//1: jmp 1b
movw $(gdt3 - gdt0), %ax #; 32-bit data segment
movw %ax, %ss
movw %ax, %ds
movw $(gdt1 - gdt0), %ax #; 4GB, 32-bit data segment, base 0
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
#; call draw_lines
#; save pmode registers
movl %cr0, %eax
movl %eax, ABS_PSP(vcpi_cr0)
#; movl %cr3, %eax
#; movl %eax, ABS_PSP(pmode_cr3)
#; save GDTR LDTR IDTR
#; (needn't do)
#; back-to-real-mode sequence from 14.5 of 386INTEL.TXT
#; 1. TURN OFF PAGING
#; 1a. jump to code memory which is identity-mapped
#; (already done)
#; 1b. clear the PG bit
movl %cr0, %eax
andl $0x7FFFFFFF, %eax
movl %eax, %cr0
#; 1c. "Move zeros to CR3 to clear out the paging cache."
xorl %eax, %eax
movl %eax, %cr3
#; 2. jump to 16-bit code segment (real2 is above, in the BITS 16 section)
ljmp $(gdt4 - gdt0), $ABS_PSP(real2) #; 16-bit code segment
#; enter real mode
#; mov eax, cr0
#; and eax, 7ffffffeh ; turn off paging
#; mov cr0, eax ; enter real mode
#; xor eax, eax
#; mov cr3, eax ; flush the TLB
#; return to protected mode
#; restore pmode registers
#; we jump here (in 16-bit pmode) when returning to real mode
#; Back-to-real-mode sequence from 14.5 of 386INTEL.TXT:
#; 3. load segment registers with values "appropriate to real mode"
real2:
/* This is in 16-bit protected mode */
.code16
// . = . - (ABS_PSP(.) - 0x2A84)
movw $(gdt5 - gdt0), %ax #; 16-bit data segment selector
movw %ax, %ss
movw %ax, %ds
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
#; 4. disable interrupts
#; (already done)
#; 5. clear the PE bit
movl %cr0, %eax
andb $0xFE, %al
movl %eax, %cr0
#; 6. far jump to true real mode
#if 0
#; movw $ABS_PSP(real3), ABS_PSP(real_mode_ip)
ljmp *ABS_PSP(real_mode_ip)
#else
.byte 0xEA #; ljmp $CURR_CS, $ABS_PSP(real3)
#endif
real_mode_ip:
.word ABS_PSP(real3)
real_mode_cs:
.word 0 #; filled earlier with the current CS value
#; 7. load an IDT that is compatible with real-mode IVT
real3:
/* This is in real mode */
.code16
/* Note: DS is still of protected mode but working */
lidt ABS_PSP(real_idt_ptr)
#; 9. restore real-mode segment registers
movw %cs, %ax
movw %ax, %ss
movw %ax, %ds
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
#; we are in real mode, run grub as normal :-)
#; remember to restore our stack pointer for quit_address to work well
movw $ABS_START(dos_stack), %sp
#; initialize EFLAGS
pushl $0
popfl
#; initialize PIC here
#; Note that the code for init_pic is shared. It can be called when
#; grub.exe is running both from dos and from kexec on linux.
call init_pic
#if 0
#; disable DMA
movb $0, %al
outb %al, $0x0D #; disable DMA-1
outb %al, $0xDA #; disable DMA-2
movl $2, %ecx # delay at least 2 microseconds
call iodelay
#; system clock 0 set to mode 3
movb $0x16, %al #; 8-bit command, mode 3, binary data
outb %al, $0x43 #; sent to mode control port
movl $2, %ecx # delay at least 2 microseconds
call iodelay
movb $0, %al #; data 0 for 18.2Hz
outb %al, $0x40 #; save to clock 0
movl $2, %ecx # delay at least 2 microseconds
call iodelay
#endif
#if 0
/* debug check for a working emm386 VCPI */
jmp message_exit1
#endif
cmpl $0, ABS_START(windows_running)
jne restore_BIOS_environment # no backup 64K DOS memory
jmp backup_low_memory_and_HMA
//----------------------------------------------------------------------------
.align 4
options_doing:
.long 0
options_done:
.long 0
.align 4
option_bypass_dos:
.ascii "--bypass"
option_bypass_dos_end:
bypass:
.long 0
option_bypass1_dos:
.ascii "--BYPASS"
option_bypass1_dos_end:
time_out:
.long 0x80000000
option_time_out_dos:
.ascii "--time-out="
option_time_out_dos_end:
.align 4
option_time_out1_dos:
.ascii "--TIME-OUT="
option_time_out1_dos_end:
.align 4
hot_key:
.long 0xffff3920
option_hot_key_dos:
.ascii "--hot-key="
option_hot_key_dos_end:
.align 4
option_hot_key1_dos:
.ascii "--HOT-KEY="
option_hot_key1_dos_end:
.align 4
option_config_file_dos:
.ascii "--config-file="
option_config_file_dos_end:
default_config_file_dos:
.ascii "/menu.lst"
default_config_file_dos_end:
.byte 0 /* mark the end of ascii zero string */
/* bad DOS pathname delimiter! Don't use single back slash by mistake! */
default_config_file_dos2:
.ascii "\\menu.lst\0" /* use double back slashes */
default_config_file_dos1:
.ascii ".\\menu.lst\0" /* use double back slashes */
usage_string: /* DOS string terminator is dollar($), not the null char */
.ascii "\r\nGRUB: Unrecognized or failed command line parameters. Usage:\r\n"
.ascii "\tGRUB [--bypass] [--time-out=T] [--hot-key=K] [--config-file=\"FILE\"]\r\n"
.ascii "The options are case-sensitive, you must use lower-case letters. Each\r\n"
.ascii "option can be specified only once at most. FILE defaults to (hd0,0)/menu.lst:\r\n"
.ascii "\tGRUB --config-file=(hd0,0)/menu.lst\r\n"
.ascii "FILE should be specified in GRUB style, but if FILE is led by the pair '#@',\r\n"
.ascii "then a DOS filename follows.\r\n"
.ascii "FILE can also be some GRUB commands delimited by semi-colon, or the whole\r\n"
.ascii "content of a menu file(again semi-colon delimits each line of the menu file).\r\n"
.ascii "FILE can be double-quoted(this is recommended, especially for use as embedded\r\n"
.ascii "commands or menu). Hexdump of command-line buffer in PSP:\r\n$"
fatal_string:
.ascii "\r\nFatal error: filename too long!\r\n$"
dos_unsupport_string:
.ascii "\r\nFailure restore ROM INT 0xHH vector. Unsupported DOS, device driver, or TSR.\r\n$"
#if 0
A20_crash_string:
.ascii "\r\nGRUB.EXE: About to turn on A20. Report bug if crashing. CR0=0x$"
#endif
//check_bootable_cdrom_string:
// .ascii "\r\nAbout to check bootable CDROM disk emulation...\r\n$"
//terminate_cdrom_emulation_string:
// .ascii "\r\nAbout to terminate CDROM disk emulation...\r\n$"
//check_bootable_cdrom_done_string:
// .ascii "\r\nBootable CDROM check is done\r\n$"
press_space_bar_string:
.ascii "\r\nPress space bar$"
press_hot_key_string:
.ascii "\r\nPress your hot-key$"
press_any_key_string:
.ascii " to start GRUB, any other key to bypass GRUB ...$"
.align 4
//mapped_int13_vector_BIOS:
// .word 0 /* hooked int13 segment */
// .word 0 /* low mem in K before int13 hook */
// .long 0 /* original BIOS int13 vector if non-zero */
stage2_64K_pages:
.long (STAGE2_SIZE + 0x200 + 0xffff) >> 16
//boot_drive:
// .long 0x80 /* default boot drive is (hd0) */
a20_status:
.long 0
sleep_5_seconds:
.code16
/* sleep 5 seconds */
/* sleep forever if ABS_PSP(time_out) is 0xff */
/* calculate the timeout ticks */
pushw %es
pushw %ds
pushl %edx
pushw %ds
popw %es
movl $0xffffffff, %edx
movzbl ABS_PSP(time_out), %eax
cmpb $0xff, %al
je 1f
movl $18, %edx /* 18.2 ticks per second. We simply use 18. */
mulw %dx /* EDX=0, EAX=ticks */
xchgw %ax, %dx /* EAX=0, EDX=ticks */
1:
xorw %ax, %ax
movw %ax, %ds /* DS=0 */
movl 0x46c, %eax /* initial tick */
movl %eax, %ecx /* ECX=initial tick */
testl %edx, %edx
js 1f
addl %edx, %eax /* EAX=timeout tick */
jmp 3f
1:
movl %edx, %eax /* EAX=0xffffffff */
3:
movl 0x46c, %ebx /* EBX=current tick */
cmpl %ecx, %ebx
jnb 2f
/* current tick is less than initial tick, this means the ticks have
* overflowed to the next day, and EBX is rather small. */
xorl %ecx, %ecx
movl %edx, %eax
2:
/* check if there is any key press. */
pushl %eax
movb $1, %ah
int $0x16
pushw %ax
pushfw
movb $0x11, %ah
int $0x16
jnz 1f
popfw
jnz 2f
/* no, there is no key press. */
popw %ax
popl %eax
cmpl %eax, %ebx /* timeout? */
jbe 3b /* no, continue to wait */
/* timeout reached, CF=0, no key pressed. */
/* `above' for no key */
popl %edx
popw %ds
popw %es
ret
1:
popfw
2:
/* yes, there is a key press. */
#if 0
/* clear the keyboard buffer */
movb $1, %ah
int $0x16
jz 1f /* no keys, end */
movb $0, %ah
int $0x16 /* discard the key */
jmp 1b
1:
#endif
/* check if it is the desired key. */
/* DS=0, but ES=old DS on stack */
xorw %es:ABS_PSP(hot_key), %ax /* CF=0 */
popw %ax
je 1f
xorw %es:ABS_PSP(hot_key), %ax /* CF=0 */
jne 2f /* not desired, return CF=0 */
/* remove the desired key from the keyboard buffer. */
pushfw
movb $0, %ah
int $0x16 /* discard the key */
popfw
jmp 3f
1:
/* remove the desired key from the keyboard buffer. */
pushfw
movb $0x10, %ah
int $0x16 /* discard the key */
popfw
3:
stc /* CF=1, the desired key pressed */
2:
cmc
/* `equal' for hot key, `below' for other key */
popl %eax
popl %edx
popw %ds
popw %es
ret
//check_a_range_of_ROM_vectors:
// cld
// lodsl
// cmpl $0xC0000000, %eax /* ROM vectors assumed to be above C000:0 */
// jb 1f /* check failed, with carry */
// loop check_a_range_of_ROM_vectors
// clc /* check passed, with no carry */
//1: ret
# Descriptor tables
#
# NOTE: The intel manual says gdt should be sixteen bytes aligned for
# efficiency reasons. However, there are machines which are known not
# to boot with misaligned GDTs, so alter this at your peril! If you alter
# GDT_ENTRY_BOOT_CS (in asm/segment.h) remember to leave at least two
# empty GDT entries (one for NULL and one reserved).
#
# NOTE: On some CPUs, the GDT must be 8 byte aligned. This is
# true for the Voyager Quad CPU card which will not boot without
# This directive. 16 byte aligment is recommended by intel.
#
.align 16
dos_gdt:
/* this is the default null entry in GDT */
.word dos_gdt_end - dos_gdt - 1 # gdt limit
.long 0 # linear address of dos_gdt
.word 0 # pad 2 bytes
.word 0xFFFF # 4Gb - (0x100000*0x1000 = 4Gb)
.word 0 # base address = 0
.word 0x9200 # data read/write
.word 0x00CF # granularity = 4096, 386
# (+5th nibble of limit)
// /* real mode data segment base=0x200000 */
// .word 0xFFFF, 0
// .byte 0x20, 0x92, 0, 0
/* real mode data segment base=0 */
.word 0xFFFF, 0
.byte 0, 0x92, 0, 0
dos_gdt_end:
quit_address:
.code16
cli
cld
movw %cs, %ax
movw %ax, %ss
movw $(ABS_START(dos_stack) - 36), %sp
popal
popw %es
popw %ds
/* restore High Memory Area */
pushw %ds
pushw %es
movw %ds, %ax
addw $(HMA_backup + 1), %ax
movw %ax, %ds /* DS:0 is (CS + HMA_backup):0010 */
xorw %si, %si
movw $0xFFFF, %di
movw %di, %es /* ES=0xFFFF */
movw $0x0010, %di
movw $(0x4000-4), %cx /* move 64KB -16Bytes */
repz movsl
popw %es
popw %ds
message_exit1:
.code16
#; if we had escaped from emm386, we should go back to pmode
cmpl $0, ABS_PSP(emm386_running)
jne 1f
/* No emm386, normal exit to real-mode dos. */
movw $0x0003, %ax /* set display mode: 80*25 color text */
int $0x10
movl ABS_PSP(error_message), %edx
testl %edx, %edx
jnz message_exit /* error */
movw $0x4c00, %ax /* exit with error number 00 */
//int $0x21 /* call DOS */
jmp terminate_program
1:
/* emm386 was running and we need to restore its VM86 environment. */
/* print a debug message */
movw $0x0e58, %ax /* print an 'X' */
int $0x10
#; set vital registers: page directory base register(CR3)
movl ABS_PSP(vcpi_cr3), %eax
movl %eax, %cr3
#; ...GDTR and IDTR
cli /* in case interrupt was enabled. */
lgdt ABS_PSP(gdt_ptr)
lidt ABS_PSP(idt_ptr)
#; ...enable paging and pmode
movl ABS_PSP(vcpi_cr0), %eax
movl %eax, %cr0
#; far jmp to 32-bit pmode code segment to return to vm86-mode DOS
ljmp $(gdt2 - gdt0), $ABS_PSP(1f) #; 32-bit code segment
1:
##############################################################################
/* This is in 32-bit protected mode. CS=(gdt2 - gdt0) */
/* Will switch to VM86 mode and go back to DOS in VM86 mode */
.code32
movw $(gdt3 - gdt0), %ax #; 32-bit data segment
movw %ax, %ds
movw %ax, %ss
movl $ABS_START(dos_stack), %esp
movw $(gdt1 - gdt0), %ax #; 4GB, 32-bit data segment, base 0
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
#; movl ABS_PSP(vcpi_cr3), %eax
#; movl %eax, %cr3
#; movl ABS_PSP(vcpi_cr0), %eax
#; movl %eax, %cr0
#if 0
/* FreeDOS has not a working VCPI with EMM386 */
jmp 1f
#endif
#; return to V86 mode
movl $0xDE0C, %eax
#; from E-67DE0C of Ralf Brown's list:
#; DS = segment selector mapping entire linear
#; address space obtained via AX=DE01h
#; I'm not sure what that means, but I peeked inside EMM386.EXE,
#; and it sets DS for me, so I don't have to.
movzwl ABS_PSP(real_mode_cs), %ebx
movl %esp, %ebp
pushl %ebx #; GS
pushl %ebx #; FS
pushl %ebx #; DS
pushl %ebx #; ES
pushl %ebx #; SS
pushl %ebp #; ESP
pushl $0x00023002 #; EFLAGS
pushl %ebx #; CS
pushl $ABS_PSP(1f) #; EIP
movw $(gdt7 - gdt0), %bx #; dummy descriptors used by VCPI
movw %bx, %ds
lcall %cs:*ABS_PSP(vcpi_entry)
/* will return to VM86 mode below */
#; should not reach this
// in case VCPI returns failure, go to real mode and print a message.
//jmp from_vcpi
/* end of 32-bit code segment of switch to vm86 mode */
##############################################################################
1:
/* This is in VM86 mode */
/* Will return to DOS in VM86 mode(terminate normally). */
.code16
#if 0
#undef debug_putchar
#define debug_putchar(x) movw $(x | ((x << 12) & 0xF000) | (((x << 8) & 0x0F00) ^ 0xC00)), (0x140 + (((x - 0x30) % 80) * 2))
movw $0xB800, %ax
movw %ax, %ds
debug_putchar ('2')
debug_putchar ('3')
debug_putchar ('4')
debug_putchar ('5')
#endif
movw %cs, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
movw %ax, %ss
movw $ABS_START(dos_stack), %sp
movw $0x0003, %ax /* set display mode: 80*25 color text */
int $0x10
#if 0
/* print a debug message */
movw $0x0e59, %ax /* print a 'Y' */
int $0x10
#endif
movl ABS_PSP(error_message), %edx
testl %edx, %edx
jnz message_exit /* error */
movw $0x4c00, %ax /* exit with error number 00 */
//int $0x21 /* call DOS */
jmp terminate_program
# The mode switch series completed
##############################################################################
#if 0
display_word:
.code16
/* routine for display hex value of BP */
pushaw
xorw %bx, %bx
movw %bp, %ax
shrw $12, %ax
cmpb $9, %al
jbe 1f
addb $7, %al
1:
addb $0x30, %al
movb $0x0e, %ah
int $0x10
movw %bp, %ax
shrw $8, %ax
andb $0x0f, %al
cmpb $9, %al
jbe 1f
addb $7, %al
1:
addb $0x30, %al
movb $0x0e, %ah
int $0x10
movw %bp, %ax
shrw $4, %ax
andb $0x0f, %al
cmpb $9, %al
jbe 1f
addb $7, %al
1:
addb $0x30, %al
movb $0x0e, %ah
int $0x10
movw %bp, %ax
andb $0x0f, %al
cmpb $9, %al
jbe 1f
addb $7, %al
1:
addb $0x30, %al
movb $0x0e, %ah
int $0x10
popaw
ret
#endif
backup_low_memory_and_HMA:
.code16
#if 0
movw $ABS_PSP(A20_crash_string), %dx
movb $0x09, %ah /* display a dollar terminated string */
int $0x21 /* call DOS */
movl %cr0, %eax
movw $0x4D1, %dx
inb %dx, %al
movb %al, %ah
movw $0x4D0, %dx
inb %dx, %al
movl %eax, %ebp
shrl $16, %ebp
call display_word
movl %eax, %ebp
call display_word
#endif
//---------------------------------------------------------------------------
#if 1
/* if time-out is not 0, sleep */
movw ABS_PSP(time_out), %cx
jcxz 2f
sti /* for sleep_5_seconds to work. */
call sleep_5_seconds
cli /* the clock is not used any more */
je 1f /* desired hot-key pressed */
jb message_exit1 /* another key pressed */
/* timeout reached */
2:
cmpl $0, ABS_PSP(bypass) /* default is bypass GRUB? */
jne message_exit1 /* yes, exit to DOS */
/* no, continue to run grub */
1:
#endif
//---------------------------------------------------------------------------
#if 0
movl $0x04000000, %ecx
1:
addr32 loop 1b
#endif
/* enable A20! */
/* oh! it seems we MUST cli, because DOS might enable/disable A20
* when entering/leaving DOS interrupt service routine which is
* loaded in the High Memory Area.
*
* We should asure A20 keeps ON when we backup HMA!
*/
cli
movw $0x00ff, %cx # try so many times on failure
movw $0x0001, %dx # non-zero means `enable'
/* Note for people who are tracing/debugging this program:
*
* ENABLE_DISABLE_A20 is not likely what you want to trace into.
*/
#if 1
call enable_disable_a20
movw $ABS_PSP(a20_control_fail_string), %dx
jnz message_exit /* enable a20 failed */
//jz exit_no_message /* A20 disabled */
#endif
#if 0
/* now A20 is on */
decl ABS_PSP(a20_status) # let it be 0xFFFFFFFF
#else
/* save the current gateA20 status */
xorl %eax, %eax
movl %eax, ABS_PSP(a20_status)
/* check A20 status again to confirm */
call a20_test /* ZF=0 for A20 enabled, ZF=1 for disabled */
movw $ABS_PSP(a20_control_fail_string), %dx
// jz 1f /* A20 disabled */
jz message_exit /* enable a20 failed */
/* now A20 is on */
decl %eax /* EAX=0xFFFFFFFF */
movl %eax, ABS_PSP(a20_status)
1:
#endif
/* backup High Memory Area to HMA_backup */
pushw %ds
pushw %es
movw $0xFFFF, %ax
movw %ax, %ds /* DS=0xFFFF(i.e., HMA segment) */
/* HMA_backup is the destination address */
movw %es, %ax
addw $(HMA_backup), %ax
movw %ax, %es
movw $0x4000, %cx /* 0x4000 dwords= 64KB */
xorw %si, %si
xorw %di, %di
cld
repz movsl
popw %es
popw %ds
/* HMA backup is done, so it is safe now to sti. But this could
* cause A20 off, even keyboard off. So we don't sti until DOS
* has totally gone.
*/
//sti
/* save 640KB conventional memory(surely, including 64K at HMA_backup)
* to physical address 0x200000=2MB, so we could restore our DOS
* memory later.
*/
/* fill gdt base first thing! */
xorl %eax, %eax
movw %ds, %ax
shll $4, %eax
addl $ABS_PSP(dos_gdt), %eax
movl %eax, ABS_PSP(dos_gdt) + 2
/* DS=ES */
/* Save all registers on stack, which quit_address will use to
* restore registers after pre_stage2 calls quit.
*/
pushw %ds
pushw %es
pushal
//movw %es, %bx # save old ES to BX
movl ABS_PSP(a20_status), %edx
cli
lgdt ABS_PSP(dos_gdt)
/* Note for people who are tracing/debugging this program:
*
* This will switch to protected mode! Don't single step it!
*
* Set break point at restore_BIOS_environment, and go!
*/
movl %cr0, %eax
orb $1, %al
movl %eax, %cr0
/* Here the CPU is in protected mode. The real mode interrupt
* vector table won't work now.
*
* Note that the CS segment is still 16-bit because we have not
* reloaded CS with a 32-bit segment selector.
*/
jmp 1f
1:
movw $8, %si # 4GB limit
movw %si, %es
movw %si, %ds
xorl %ebp, %ebp
xorl %esi, %esi
movl $0x200000, %edi
movl $(0xA0000 / 4), %ecx # move 640K
cld
1:
addr32 lodsl (%esi)
addl %eax, %ebp
addr32 stosl (%edi)
addr32 loop 1b
//decl %ecx
//jnz 1b
movl $0x50554B42, %eax # "BKUP"
addl %eax, %ebp
addr32 stosl (%edi)
xchgl %eax, %edx
addl %eax, %ebp
addr32 stosl (%edi)
movw %cs, %ax
shll $16, %eax
movw $ABS_START(quit_address), %ax
addl %eax, %ebp
addr32 stosl (%edi)
xchgl %eax, %ebp
negl %eax
addr32 stosl (%edi)
movw $16, %si # get real mode 64K segment limit
movw %si, %es
movw %si, %ds
movl %cr0, %eax
andb $0xfe, %al
movl %eax, %cr0
jmp 1f
1:
/* Here the CPU turns back to real mode. The real mode interrupt
* vector table should work now.
*
* Note that the CS segment is still 16-bit because we have not
* reloaded CS with a 32-bit segment selector.
*/
//jmp 1b
//movw %bx, %es # restore ES from BX
//movw %bx, %ds # restore DS from BX
popal
popw %es
popw %ds
restore_BIOS_environment:
/* Note for people who are tracing/debugging this program:
*
* PROBE_INT will take over int01, so probably you don't want
* to trace into PROBE_INT.
*/
call probe_int
jc failed_probe_int
/* Note for people who are tracing/debugging this program:
*
* restore_BDA_EBDA will destroy the DOS memory structure,
* i.e., the MCB (Memory Control Block).
*
* If you trace into restore_BDA_EBDA now, DOS could hang
* when you quit the debugger.
*/
call restore_BDA_EBDA
/* Note for people who are tracing/debugging this program:
*
* Oh! Don't continue. The interrupt vector table will be changed!
* The DOS(especially with your debugger!) will disappear!
* It will be overwritten by the GRUB code and data of pre_stage2.
* After pre_stage2 gains control, the processor will switch to
* protected mode.
*/
/* overwrite 1 sector of IVT with the new bios_interrupt_vector_table */
cli
movw %cs, %ax
movw %ax, %ds
movw $ABS_START(bios_interrupt_vector_table), %si
xorw %di, %di /* ES:DI=0000:0000 */
movw %di, %es
movw $0x80, %cx
cld
repz movsl
#if 1
/* restore int 13 and int 15 for disk emulation */
movl ABS_START(tmp_int13_vector), %eax
cmpl $0x9A000000, %eax
jb 1f
movw $0x4C, %di
stosl
movl ABS_START(tmp_int15_vector), %eax
cmpl $0x9A000000, %eax
jb 1f
movw $0x54, %di
stosl
1:
#endif
jmp move_stage2_image
failed_probe_int:
/* probe failure */
movw %cs, %ax
movw %ax, %ds
movw %ax, %es
xchgw %ax, %cx # move CL to AL
xorb %ah, %ah
movb $16, %cl
divb %cl # quo=AL, rem=AH
orw $0x3030, %ax
cmpb $0x39, %ah
jbe 1f
addb $7, %ah
1:
cmpb $0x39, %al
jbe 1f
addb $7, %al
1:
movw %ax, ABS_START(dos_unsupport_string) + 28
movl $ABS_START(dos_unsupport_string), ABS_PSP(error_message)
jmp message_exit1
probe_int:
/* Note for people who are tracing/debugging this program:
* PROBE_INT will take over int01, so probably you don't want
* to trace into PROBE_INT.
*
* The int01 will be replaced and restored in RESTORE_VECTOR.
*/
/* probe critical interrupt vectors established by ROM BIOS. */
/* find a dummy default vector that simply do an `iret' */
/* first, search the Interrupt Vector Table for a dummy vector */
xorw %ax, %ax
movw %ax, %ds /* DS=0 */
movw %ax, %si
movw $256, %cx
1:
cld
lodsl
cmpl $0xC0000000, %eax
jb 2f /* failure, try next vector */
movw %ax, %di
shrl $16, %eax
movw %ax, %es
cmpb $0xCF, %es:(%di) /* 0xCF=IRET */
je 3f
2:
loop 1b
/* not found, try a further probe in ROM segment F000:0000 */
movw $0xF000, %ax
movw %ax, %es /* ES=0xF000 */
movw $0xFFFF, %cx
movb $0xCF, %al /* 0xCF=IRET */
movw $0xFFFF, %di /* top-down */
std
repnz scasb
cld
movw $0xFFFE, %cx
stc
jnz 9f
incw %di
3:
/* found, ES:DI points to IRET */
movw %di, %cs:ABS_START(dummy_iret_vector)
movw %es, %cs:ABS_START(dummy_iret_vector + 2)
/* copy 1 sector of IVT to bios_interrupt_vector_table */
movw %cs, %ax
movw %ax, %es /* ES=CS */
movw $ABS_START(bios_interrupt_vector_table), %di
xorw %si, %si /* DS:SI=0000:0000 */
movw $0x80, %cx
2:
cld
lodsl
cmpl $0, %es:(%di)
jne 1f
stosl
subw $4, %di
1:
addw $4, %di
loop 2b
/* save the current int1 vector */
movl 0x0004, %eax /* DS=0 */
movl %eax, %cs:ABS_START(orig_int1_vector)
movw %cs, %ax
movw %ax, %ds /* DS=ES=CS */
/* Print a message before it could hang. */
movw $ABS_START(probe_int_hang_string), %si
call prtstr
/* check the int 13 vector high word */
cmpw $0xC000, ABS_START(bios_interrupt_vector_table) + 0x4E
jb 2f
cmpw $0xF000, ABS_START(bios_interrupt_vector_table) + 0x4E
jna 3f /* int 13 points to ROM */
2:
/* int 13 service routine was not in ROM */
/* clear tmp_int13_vector and tmp_int15_vector */
xorl %eax, %eax
movl %eax, ABS_START(tmp_int13_vector)
movl %eax, ABS_START(tmp_int15_vector)
3:
/* check the int 15 vector high word */
cmpw $0xC000, ABS_START(bios_interrupt_vector_table) + 0x56
jb 2f
cmpw $0xF000, ABS_START(bios_interrupt_vector_table) + 0x56
jna 3f /* int 15 points to ROM */
2:
/* int 15 service routine was not in ROM */
/* clear tmp_int15_vector */
xorl %eax, %eax
movl %eax, ABS_START(tmp_int15_vector)
3:
movw $ABS_START(bios_interrupt_vector_table), %si
movw %si, %di
xorw %cx, %cx
1:
//cmpw $0x15, %cx
//je 2f
call print_cl
cld
lodsl
testl %eax, %eax /* zero vectors? */
jz 2f /* yes. won't touch */
cmpl $0xF000FFFF, %eax /* modified by protected-mode OS? */
ja 8f /* yes. */
cmpl $0xC0000000, %eax /* ROM vectors? */
jb 8f /* no. */
/* it is in the ROM range. */
/* if in real mode, then it is ROM vector. */
pushw %ax
smsw %ax
testb $1, %al
popw %ax
jz 2f /* ROM vector, won't touch. */
/* now in VM86 mode. check if the ROM routine is writable. */
pushl %eax
pushw %ds
pushw %si
movw %ax, %si
shrl $16, %eax
movw %ax, %ds
movw (%si), %ax /* save original word at CS:IP to AX. */
notw (%si) /* toggle each bit. */
cmpw (%si), %ax /* is it changed? */
notw (%si) /* toggle back. The NOT won't change flags. */
popw %si
popw %ds
popl %eax
je 2f /* equal, so it is in ROM and won't touch. */
8:
/* if it points to an IRET, set it to dummy iret */
pushw %ds
pushw %si
ldsw -4(%si), %si
cmpb $0xCF, (%si)
popw %si
popw %ds
je 3f
/* set int 00, 01, 03, 04, 07 to dummy */
cmpw $ABS_START(bios_interrupt_vector_table) + 0x08, %di
jb 3f /* int 00, 01 */
ja 8f
/* int 02 External hardware - NON-MASKABLE INTERRUPT */
/* Desc: Generated by the CPU when the input to the NMI pin is asserted. */
/* Notes: Return address points to start of interrupted instruction on
* 80286+. On the 80286+, further NMIs are disabled until the next IRET
* instruction, but one additional NMI is remembered by the hardware
* and will be serviced after the IRET instruction reenables NMIs.
* Maskable interrupts may interrupt the NMI handler if interrupts are
* enabled. Although the Intel documentation states that this interrupt
* is typically used for power-failure procedures, it has many other
* uses on IBM-compatible machines: Memory parity error, Coprocessor
* interrupt, Keyboard interrupt, I/O channel check, Disk-controller
* power-on request, System suspend, Real-time clock, System watch-dog
* timer time-out interrupt, DMA timer time-out interrupt, Low battery,
* Module pulled.
*/
#; jmp 4f /* qemu sets this to dummy. */
call restore_vector
jc 9f
jmp 2f
8:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x0C, %di /* int 03 */
je 3f
cmpw $ABS_START(bios_interrupt_vector_table) + 0x10, %di /* int 04 */
je 3f
cmpw $ABS_START(bios_interrupt_vector_table) + 0x14, %di /* int 05 */
jne 8f
#; jmp 5f /* qemu sets this to dummy. */
call restore_vector
jc 9f
jmp 2f
8:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x18, %di /* int 06 */
jne 8f
#; jmp 6f /* qemu sets this to dummy. */
call restore_vector
jc 9f
jmp 2f
8:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x1C, %di /* int 07 */
je 3f
cmpw $ABS_START(bios_interrupt_vector_table) + 0x20, %di /* int 08-0f hardware IRQs */
jb 7f
cmpw $ABS_START(bios_interrupt_vector_table) + 0x40, %di
jnb 7f
call restore_vector
jc 9f
jmp 2f
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x1C0, %di /* int 70-77 hardware IRQs */
jb 7f
cmpw $ABS_START(bios_interrupt_vector_table) + 0x1E0, %di
jnb 7f
call restore_vector
jc 9f
jmp 2f
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x40, %di /* int 10, VIDEO */
jne 7f
/* teletype output a space char */
movw $0x0E20, %ax
xorw %bx, %bx
call restore_vector
jc 9f
jmp 2f
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x44, %di /* int 11, GET EQUIPMENT LIST */
jne 7f
call restore_vector
jc 9f
jmp 2f
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x48, %di /* int 12, GET MEMORY SIZE */
jne 7f
call restore_vector
jc 9f
jmp 2f
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x4C, %di /* int 13, DISK */
jne 7f
/* If we track it by reading a sector, we could track into protected
* mode or into int15/ah=87h for a RAM disk. So we only check the
* status of the drives.
*/
/* GET STATUS OF LAST OPERATION */
movb $0, %dl /* try each drive 00 - FF */
8:
movb $1, %ah
call restore_vector
jnc 2f
/* try next floppy */
incb %dl /* INC won't touch CF */
jnz 8b
jmp 9f /* CF=1 */
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x50, %di /* int 14, SERIAL */
jne 7f
/* GET PORT STATUS */
movw $0x0300, %ax
movw $0, %dx /* port number */
call restore_vector
jc 9f
jmp 2f
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x54, %di /* int 15, SYSTEM service */
jne 7f
/* later PS/2s - GET A20 GATE STATUS */
// call print_cl
movw $0x2402, %ax
call restore_vector
jc 9f
jmp 2f
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x58, %di /* int 16, KEYBOARD */
jne 7f
/* GET KEYBOARD FUNCTIONALITY */
movw $0x0900, %ax
call restore_vector
jnc 2f
/* GET KEYBOARD ID */
movw $0x0A00, %ax
call restore_vector
jnc 2f
jmp 9f /* CF=1 */
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x5C, %di /* int 17, PRINTER */
jne 7f
/* GET STATUS */
movw $0x0200, %ax
movw $0, %dx /* port number */
call restore_vector
jnc 2f
jmp 9f /* CF=1 */
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x60, %di /* int 18, BOOT FAILURE */
jne 7f
/* We should not try to call this interrupt! */
jmp 3f /* simply set to dummy */
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x64, %di /* int 19, SYSTEM - BOOTSTRAP LOADER */
jne 7f
/* We should not try to call this interrupt! */
jmp 3f /* simply set to dummy */
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x68, %di /* int 1A, TIME and PCI BIOS */
jne 7f
/* Tracking into TIMER could hang the system when accessing CMOS! */
/* PCI BIOS needs too much stack, so do not call it. */
/* Call it with unknown function. */
movw $0xFFFF, %ax
call restore_vector
jnc 2f
jmp 9f /* CF=1 */
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x6C, %di /* int 1B, KEYBOARD - CONTROL-BREAK HANDLER */
jne 7f
/* Tracking into int 1B could break GRUB.EXE and return to DOS! */
/* BIOS sets this to dummy iret */
jmp 3f /* simply set to dummy */
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x70, %di /* int 1C, TIME - SYSTEM TIMER TICK */
jne 7f
/* BIOS sets this to dummy iret */
jmp 3f /* simply set to dummy */
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x74, %di /* int 1D, SYSTEM DATA - VIDEO PARAMETER TABLES */
jne 7f
/* Notes: The default parameter table is located at F000h:F0A4h
* for 100% compatible BIOSes. Under PhysTechSoft's PTS ROM-DOS
* this table is fictitious. ---- Ralf Brown's Interrupt List
*/
/* QEMU sets this to dummy iret. It is not proper. */
/* DOS should not change this vector. */
stc
jmp 9f /* CF=1 */
//movl $0xF000F0A4, %eax
//jmp 2f
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x78, %di /* int 1E, SYSTEM DATA - DISKETTE PARAMETERS */
jne 7f
/* Notes: The default parameter table is located at F000h:EFC7h
* for 100% compatible BIOSes.
*/
/* QEMU sets this to F000h:EFDFh. It is not proper. */
/* We can get this vector by calling int13/ah=8h (GET DRIVE PARAMETERS).
*/
/* original floppy parameter table */
#; Do not call int13 if it has not been restored.
cmpl $0xC000, ABS_START(bios_interrupt_vector_table) + 0x4E
jb 2f
cmpl $0xF000, ABS_START(bios_interrupt_vector_table) + 0x4E
ja 2f
pushw %cx
pushw %ds
pushw %si
pushw %es
pushw %di
pushl %eax
movb $8, %ah
movb $0, %dl
stc
#; int $0x13
pushfw
lcall *ABS_START(bios_interrupt_vector_table) + 0x4C
jc 8f
orw %bx, %ax
orw %cx, %ax
jz 8f
movw %es, %ax
testw %ax, %ax
jz 8f
popl %eax
pushw %es
pushw %di
8:
popl %eax
popw %di
popw %es
popw %si
popw %ds
popw %cx
jmp 2f
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x7C, %di /* int 1F, SYSTEM DATA - 8 x 8 GRAPHICS FONT */
jne 7f
/* This vector points at 1024 bytes of graphics data, 8 bytes for
* each character 80h-FFh.
*/
/* Notes: Graphics data for characters 00h-7Fh stored at F000h:FA6Eh
* in 100% compatible BIOSes. Under PhysTechSoft's PTS ROM-DOS this
* table is fictitious. ---- Ralf Brown's Interrupt List
*/
/* System VGA BIOS sets to 0:0, Video Card usually sets to C000:xxxx */
/* We can get this vector by calling int10/ax=1130h/bh=04h
* (GET FONT INFORMATION).
*/
/* get original font */
#; Do not call int10 if it has not been restored.
cmpl $0xC000, ABS_START(bios_interrupt_vector_table) + 0x42
jb 2f
cmpl $0xF000, ABS_START(bios_interrupt_vector_table) + 0x42
ja 2f
pushw %cx
pushw %ds
pushw %si
pushw %es
pushw %di
pushl %eax
movw $0x1130, %ax
movb $4, %bh
#; int $0x10
pushfw
lcall *ABS_START(bios_interrupt_vector_table) + 0x40
movw %es, %ax
cmpw $0xC000, %ax
jb 8f
popl %eax
pushw %es
pushw %bp
8:
popl %eax
popw %di
popw %es
popw %si
popw %ds
popw %cx
jmp 2f
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x100, %di /* int 40, original ROM BIOS DISKETTE HANDLER */
jne 7f
/* This is ROM BIOS diskette handler relocated by hard disk BIOS. */
/* GET STATUS OF LAST OPERATION */
movb $0, %dl
8:
movb $1, %ah
call restore_vector
jnc 2f
incb %dl
cmpb $2, %dl
jnz 8b
stc
jmp 9f
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0xC0, %di /* int 30 */
jne 7f
/* It seems BIOS set this interrupt to dummy iret. */
jmp 3f
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x104, %di /* int 41, SYSTEM DATA - HARD DISK 0 */
jne 7f
/* SYSTEM DATA(NOT A VECTOR!) - HARD DISK 0 PARAMETER TABLE ADDRESS */
/* DOS should not change this vector. */
/* It is hard to retrieve this vector. Hopefully int13/ah=09h never be called. */
jmp 2f
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x118, %di /* int 46, SYSTEM DATA - HARD DISK 1 */
jne 7f
/* SYSTEM DATA(NOT A VECTOR!) - HARD DISK 1 PARAMETER TABLE ADDRESS */
/* DOS should not change this vector. */
/* It is hard to retrieve this vector. Hopefully int13/ah=09h never be called. */
jmp 2f
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x108, %di /* int 42, RELOCATED INT 10 VIDEO SERVICES */
jne 7f
/* Desc: Contains the address of the original INT 10 handler which
* an EGA+ video adapter replaces with its own on-board BIOS code.
*/
/* DOS should not change this vector. */
stc
jmp 9f
/* The vector is generally not used any longer. */
//jmp 2f
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x10C, %di /* int 43, VIDEO DATA - CHARACTER TABLE */
jne 7f
/* VIDEO DATA(NOT A VECTOR!) - Points at graphics data for characters
* 00h-7Fh of the current font in 8 x 8 dot modes, graphics data for
* all characters in 8 x 14 and 8 x 16 modes.
*/
/* We can get this vector by calling int10/ax=1130h/bh=03h
* (GET FONT INFORMATION).
*/
/* get original font */
#; Do not call int10 if it has not been restored.
cmpl $0xC000, ABS_START(bios_interrupt_vector_table) + 0x42
jb 2f
cmpl $0xF000, ABS_START(bios_interrupt_vector_table) + 0x42
ja 2f
pushw %cx
pushw %ds
pushw %si
pushw %es
pushw %di
pushl %eax
movw $0x1130, %ax
movb $3, %bh
#; int $0x10
pushfw
lcall *ABS_START(bios_interrupt_vector_table) + 0x40
movw %es, %ax
cmpw $0xC000, %ax
jb 8f
popl %eax
pushw %es
pushw %bp
8:
popl %eax
popw %di
popw %es
popw %si
popw %ds
popw %cx
jmp 2f
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x110, %di /* int 44, undocumented */
jne 7f
/* VIDEO DATA - ROM BIOS CHARACTER FONT, CHARACTERS 00h-7Fh (PCjr)
* This vector points at graphics data for current character font.
* ---- Ralf Brown's Interrupt List
*/
/* Ralf Brown's Interrupt List seems wrong for this interrupt.
* And it seems BIOS set this interrupt to dummy iret.
*/
/* DOS should not change this vector. */
stc
jmp 9f
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x114, %di /* int 45, undocumented */
jne 7f
/* It seems BIOS set this interrupt to dummy iret. */
/* DOS should not change this vector. */
stc
jmp 9f
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x11C, %di /* int 47, undocumented */
jne 7f
/* It seems BIOS set this interrupt to dummy iret. */
/* DOS should not change this vector. */
stc
jmp 9f
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x120, %di /* int 48, undocumented keyboard */
jne 7f
/* It seems BIOS set this interrupt to dummy iret. */
/* DOS should not change this vector. */
stc
jmp 9f
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x124, %di /* int 49, undocumented */
jne 7f
/* SYSTEM DATA - NON-KEYBOARD SCAN-CODE TRANSLATION TABLE (PCjr)
* ---- Ralf Brown's Interrupt List
*/
/* Ralf Brown's Interrupt List seems wrong for this interrupt.
* And it seems BIOS set this interrupt to dummy iret.
*/
/* DOS should not change this vector. */
stc
jmp 9f
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x128, %di /* int 4A, SYSTEM - USER ALARM HANDLER */
jne 7f
/* Desc: This interrupt is invoked by the BIOS when a real-time
* clock alarm occurs. An application may use it to perform an
* action at a predetermined time.
*
* Note: This interrupt is called from within a hardware
* interrupt handler, so all usual precautions against
* reentering DOS must be taken.
* ---- Ralf Brown's Interrupt List
*/
/* It seems BIOS set this interrupt to dummy iret. */
/* DOS should not change this vector. */
stc
jmp 9f
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x12C, %di /* int 4B, Virtual DMA Specification (VDS) */
jne 7f
/* It seems BIOS set this interrupt to dummy iret. */
jmp 3f
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x130, %di /* int 4C, undocumented */
jne 7f
/* It seems BIOS set this interrupt to dummy iret. */
jmp 3f
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x134, %di /* int 4D, undocumented */
jne 7f
/* It seems BIOS set this interrupt to dummy iret. */
jmp 3f
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x138, %di /* int 4E, undocumented */
jne 7f
/* It seems BIOS set this interrupt to dummy iret. */
jmp 3f
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x13C, %di /* int 4F, undocumented */
jne 7f
/* It seems BIOS set this interrupt to dummy iret. */
jmp 3f
7:
#if 0
cmpw $ABS_START(bios_interrupt_vector_table) + 0x170, %di /* int 5C, NetBIOS INTERFACE */
jne 7f
/* Windows revectored this interrupt. */
/* It seems BIOS set this interrupt to dummy iret. */
jmp 3f
7:
#endif
cmpw $ABS_START(bios_interrupt_vector_table) + 0x140, %di /* int 50 - 5F */
jb 7f
cmpw $ABS_START(bios_interrupt_vector_table) + 0x180, %di
jnb 7f
/* It seems BIOS set this interrupt to dummy iret. */
jmp 3f
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x180, %di /* int 60 - 67 */
jb 7f
cmpw $ABS_START(bios_interrupt_vector_table) + 0x1A0, %di
jnb 7f
/* It seems BIOS set this interrupt to 0:0. */
xorl %eax, %eax
jmp 2f
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x1B4, %di /* int 6D, VIDEO BIOS ENTRY POINT */
jne 7f
/* DOS should not change this vector. */
stc
jmp 9f
7:
cmpw $ABS_START(bios_interrupt_vector_table) + 0x1A0, %di /* int 68 - 6F */
jb 7f
cmpw $ABS_START(bios_interrupt_vector_table) + 0x1C0, %di
jnb 7f
/* It seems BIOS set this interrupt to dummy iret. */
jmp 3f
7:
/* other vectors won't touch */
jmp 2f
#; 6:
#; /* int 06, bad code interrupt. */
#; /* qemu sets this to dummy, so can we. */
#; 5:
#; /* int 05 */
#; /* qemu sets this to dummy, so can we. */
#; 4:
#; /* int 02 */
#; /* qemu sets this to dummy, so can we. */
3:
/* dummy iret vector */
movl ABS_START(dummy_iret_vector), %eax
2:
stosl
jmp 2f
9:
testb %ch, %ch /* save the first failed vector ... */
jnz 3f
movb %cl, %ch /* ... in CH */
3:
addw $4, %di
/* print an "!" for failure */
pushw %ds
pushw %es
pushal
xorw %bx, %bx
movw $0x0e21, %ax /* 0x21 = "!" */
int $0x10
popal
popw %es
popw %ds
2:
incb %cl
cmpb $0x80, %cl
jb 1b
movb %ch, %cl
negb %ch /* CF=1 if CH is non-zero */
ret
probe_int_hang_string:
.ascii "\r\nProbing ROM INT vectors. If hang, unload a device driver or TSR and try again.\r\n\0"
restore_vector:
#; input:
#; DI points to vector in bios_interrupt_vector_table
#; output:
#; CF=0 success, EAX=the restored ROM vector
#; CF=1 failure, EAX destroyed.
/* Note for people who are tracing/debugging this program:
* This is where int01 will be replaced and restored.
* Take care!
*/
/* replace int1 handler */
pushw %ax
pushw %es
pushw %di
xorw %ax, %ax
movw %ax, ABS_START(tmp_vector + 2)
movw %ax, %es
movw $4, %di
movw $ABS_START(int1_handler), %ax
cld
stosw
movw %cs, %ax
stosw
popw %di
popw %es
popw %ax
///* clear tmp_vector */
//movl $0, ABS_START(tmp_vector) #; DS=CS
movw %di, ABS_START(tmp_vector)
pushw %ds
pushw %es
pushaw
/* additional PUSHF/POPF are required because some BIOS INT service
* routines will end with `ret 02' instead of `iret'.
* The `ret 02' does not clear TF, while `iret' does.
* With additional PUSHF/POPF, the TF can be safely cleared.
*/
pushfw /* additional PUSHF */
pushfw
/* set the TF flag */
pushw %ax
pushfw
popw %ax
orw $0x100, %ax
pushw %ax
popfw
popw %ax
lcall *(%di)
popfw /* additional POPF */
popaw
popw %es
popw %ds
cli
/* TF is cleared here automatically */
/* restore the original int1 handler */
pushw %es
pushw %di
xorw %ax, %ax
movw %ax, %es
movw $4, %di
movl ABS_START(orig_int1_vector), %eax
stosl
popw %di
popw %es
/* failure? */
movl ABS_START(tmp_vector), %eax
cmpl $0xC0000000, %eax
/* CF=1 for failure. EAX below 0xC0000000 is not a ROM vector.
* CF=0 for success. EAX is a ROM vector.
*/
ret
/*
* Check if the next instruction is in ROM, and if this is true, store the
* entry point to tmp_vector and terminate the tracking.
*
*/
int1_handler:
cli /* so that we won't take up too much stack. */
pushw %bp
movw %sp, %bp
pushl %eax
movl 2(%bp), %eax /* CS:IP of the next instruction */
cmpl $0xC0000000, %eax
jnb 2f
/* if int 13 or int 15 */
cmpl $ABS_START(bios_interrupt_vector_table + 0x4C), %cs:ABS_START(tmp_vector) // int 13
jne 3f
cmpl $0, %cs:ABS_START(tmp_int13_vector)
jne 1f
cmpl $0x9A000000, %eax
jb 1f
movl $1, %cs:ABS_START(tmp_int13_vector) /* invalidate it if the first ip is not 0x100 */
cmpw $0x100, %ax
jne 1f
testl $0x3F0000, %eax
jnz 1f
pushw %ds
pushw %bx
xorw %ax, %ax
movw %ax, %ds /* DS=0 */
movw 0x413, %ax
shlw $6, %ax
movw %ax, %bx
rorl $16, %eax
cmpw %ax, %bx
popw %bx
popw %ds
jne 1f
/* this was a disk emulation made by an old grub4dos. */
movw $0x100, %ax
movl %eax, %cs:ABS_START(tmp_int13_vector)
jmp 1f
3:
cmpl $ABS_START(bios_interrupt_vector_table + 0x54), %cs:ABS_START(tmp_vector) // int 15
jne 1f
cmpl $0, %cs:ABS_START(tmp_int15_vector)
jne 1f
cmpl $1, %cs:ABS_START(tmp_int13_vector)
jbe 1f
rorl $16, %eax
cmpw %ax, %cs:ABS_START(tmp_int13_vector + 2)
jne 1f
rorl $16, %eax
movl %eax, %cs:ABS_START(tmp_int15_vector)
jmp 1f
2:
/* calculate linear address of CS:IP in EAX */
pushl %eax
pushl %ebx
movl %eax, %ebx
xorw %bx, %bx
shrl $12, %ebx
movzwl %ax, %eax
addl %eax, %ebx /* EBX=linear address */
cmpl $0x100000, %ebx /* is it below 1M? */
popl %ebx
popl %eax
jnb 1f /* not below, so it is not in ROM. */
/* check if we can write-access the instruction at CS:IP */
pushl %eax
pushw %ds
pushw %si
movw %ax, %si
shrl $16, %eax
movw %ax, %ds
movw (%si), %ax /* save original word at CS:IP to AX. */
notw (%si) /* toggle each bit. */
cmpw (%si), %ax /* is it changed? */
notw (%si) /* toggle back. The NOT won't change flags. */
popw %si
popw %ds
popl %eax
jne 1f /* not equal, so it is not in ROM. */
/* This is the entry point in ROM */
movl %eax, %cs:ABS_START(tmp_vector)
/* clear the TF flag */
andw $0xFEFF, 6(%bp)
1:
/* restore registers */
popl %eax
popw %bp
iret
.align 4
tmp_int13_vector:
.word 0
.word 0
tmp_int15_vector:
.word 0
.word 0
tmp_vector:
.word 0
.word 0
dummy_iret_vector:
.word 0
.word 0
orig_int1_vector:
.word 0
.word 0
/* 1 sector for the first half of IVT */
bios_interrupt_vector_table:
.space 0x200
print_cl:
pushw %ds
pushw %es
pushal
xchgw %ax, %cx # move CL to AL
xorb %ah, %ah
movb $16, %cl
divb %cl # quo=AL, rem=AH
orw $0x3030, %ax
cmpb $0x39, %ah
jbe 1f
addb $7, %ah
1:
cmpb $0x39, %al
jbe 1f
addb $7, %al
1:
movb %ah, %cl
xorw %bx, %bx
pushw %ax
movw $0x0e20, %ax
int $0x10
popw %ax
movb $0x0e, %ah
int $0x10
movb $0x0e, %ah
movb %cl, %al
int $0x10
popal
popw %es
popw %ds
ret
restore_BDA_EBDA:
/* MS-DOS 7+ moved the EBDA segment from higher address to lower
* address, so we need to move it back to the top of conventional
* memory.
*/
cli
cld
xorw %ax, %ax
movw %ax, %ds
movw $0x040e, %si /* points to EBDA segment */
lodsw
orw %ax, %ax /* is EBDA segment? */
#; jz invalid_EBDA /* no EBDA exists */
jnz 1f
/* no EBDA, let EBDA be at A000:0000 */
movw $0xA000, %ax
1:
movw %ax, %cx /* CX=EBDA segment */
movw $0x0413, %si /* points to conventional memory size in KB */
lodsw
cmpw $0x0280, %ax /* 0x280 == 640 */
ja invalid_EBDA /* too much conventional memory */
shlw $0x06, %ax
movw %ax, %dx /* conventional memory size in paragraphs */
xorb %al, %al
cmpw $0xA000, %cx
jz 1f #; no EBDA exists
movw %cx, %ds /* DOS currently used EBDA segment */
xorw %si, %si /* the first byte of the EBDA ... */
lodsb /* ...is the EBDA size in KB */
cmpb $0x80, %al /* EBDA size greater than 128K ? */
ja invalid_EBDA /* EBDA too large */
orb %al, %al /* EBDA size is 0 ? */
jz invalid_EBDA /* EBDA should not be 0 in length */
1:
movw $0x0280, %bx
subb %al, %bl /* BX is the calculated low mem in K */
shlw $0x06, %bx /* BX is the calculated EBDA segment */
cmpw %bx, %cx /* CX is currently used EBDA segment */
jb 1f /* MSDOS had moved EBDA to low, jump */
/* BX should be equal to CX, so EBDA needn't be restored.
*
* DX holds the segment at low mem end, so DX should be less
* than or equal to BX.
*/
#if 0
/* restore vectors in the range from DX:0 to BX:0 */
movw %cs, %ax
movw %ax, %es
movw $ABS_START(mapped_int13_vector_BIOS), %di
movw %dx, %ax /* AX=currently used low mem in paragraghs */
stosw /* the possible hooked int13 segment */
movw %bx, %ax /* AX is the calculated EBDA segment */
shrw $6, %ax /* AX=calculated low mem in K */
stosw /* the low mem in K before int13 hook */
#; after int13 is unhooked, the low mem will
#; end at EBDA. so any int13 service routine
#; installed by another software(eg, memdisk)
#; will be forcibly dismissed!!!!!!
movw %di, %bp
movw %dx, %es /* currently used low mem in paragraghs */
xorw %di, %di
/* suppose ES:DI is our int13 handler, we check it. */
/* the original int13 vector must be stored in the first kilo bytes */
movw $0x0400, %cx
movb $0x9a, %al /* 0x9a == lcall */
3:
repnz scasb
jcxz 2f /* our int13 code was not found. */
cmpl $0x9a006e8b, %es:-4(%di) /* movw (%bp), %bp; lcall */
jnz 3b
/* our int13 found. */
movw %di, %si
movw %bp, %di
movw %es, %ax
movw %ax, %ds /* DS=code segment of int13 service routine */
#; DS:SI points to original BIOS int13 vector
movw %cs, %ax
movw %ax, %es #; ES:DI points to the location to save
lodsl
cmpl $0x80000000, %eax
jb 2f
stosl /* save the original BIOS int13 vector */
2:
#endif
ret
1:
/* MS-DOS 7+ moved 1K EBDA from 9FC0:0000 to lower mem. In this case,
* there shouldn't be any int13 routine at the top of conventional
* memory. so we simply copy 1K of EBDA back to 9FC0:0000 and
* of course, adjust data at 0000:040e and 0000:0413
*/
/* Restore vectors in the range from DX:0 to A000:0
* No vectors need to be restored because DX seems always be 0xA000
*/
movw %bx, %es /* ES is the calculated EBDA segment */
shlw $9, %ax /* AX *= 512 */
movw %ax, %cx /* EBDA size in words */
decw %ax
shlw $1, %ax /* points to the last word in EBDA */
movw %ax, %si /* DS holds the currently used EBDA segment */
movw %ax, %di /* ES holds the calculated EBDA segment */
std /* move higher word first */
repz movsw /* move to BIOS EBDA */
cld
xorw %ax, %ax
movw %ax, %es /* DI string operations use ES == 0 */
movw %bx, %ax
movw $0x040e, %di
stosw /* restore the BDA pointer of EBDA segment */
movw $0x0413, %di
shrw $6, %ax
stosw /* restore the BDA field of low mem size */
ret
invalid_EBDA:
xorw %ax, %ax /* AX == 0 means invalid EBDA segment */
movw %ax, %es /* DI string operations use ES == 0 */
movw $0x040e, %di /* let EBDA segment be invalid */
stosw /* store 0 to 0000:040e */
movw $0x0413, %di /* let lower memory size at 0x413 ... */
movw $0x0280, %ax /* ... be 640K (0x280 == 640) */
stosw /* store 640 to 0000:0413 */
ret
move_stage2_image:
#if 0
/* open video */
movw $0x1200, %ax
movb $0x32, %bl
int $0x10
movb $0x0f, %ah /* get current display mode */
int $0x10
xorb %ah, %ah
andb $0x7f, %al
#endif
#if 1
/* The Chinese DOS display systems are in graphics mode and cause
* display problem after we enter GRUB environment, so we need to
* enter text mode.
*/
movw $0x0003, %ax /* set display mode: 80*25 color text */
int $0x10
#endif
movw %cs, %ax
movw %ax, %ds
#if 0
/* restore mapped int13 */
movw $ABS_START(mapped_int13_vector_BIOS), %si
xorw %ax, %ax
movw %ax, %es #; ES=0
lodsl
movl %eax, %ecx
lodsl
testl %eax, %eax
jz 1f
movw $0x004c, %di /* int13 */
movl %es:(%di), %ebx
testw %bx, %bx
jnz 1f
shrl $16, %ebx
cmpw %cx, %bx
jnz 1f
stosl /* write to 0000:004c */
shrl $16, %ecx
movw $0x0413, %di
movw %cx, %ax
stosw /* write to 0000:0413 */
/* restore int15 */
movw $0x0054, %di /* int15 */
cmpw %es:2(%di), %bx #; Is int15 routine in the same segment?
jne 1f #; no, needn't restore int15
movw %es:(%di), %di
// cmpw $0x0c00, %di /* 3K = 0x0c00 */
// jnb 1f
movw %di, %ax
shrw $4, %ax
addw %bx, %ax #; AX=linear address by paragraphs of int15 service routine
cmpw $0xA000, %ax #; exceeds 640K ?
jnb 1f #; yes, fail to restore int15
movw %bx, %es #; ES=segment of int13 service routine
movl %es:-4(%di), %eax /* ROM_int15 */
cmpl $0xc0000000, %eax #; a ROM vector?
jb 1f #; no, fail to restore int15
movw %es:-6(%di), %cx #; CH=harddrives byte, CL=floppies byte
xorw %bx, %bx
movw %bx, %es #; ES=0
movw $0x0054, %di /* int15 */
stosl
movb %cl, %es:0x0410 /* floppies byte */
movb %ch, %es:0x0475 /* harddrives byte */
1:
/* original floppy parameter table */
/* it is safe to call int13 here, since int13 vector has been
* restored.
*/
movb $8, %ah
movb $0, %dl
stc
int $0x13
jc 1f
orw %bx, %ax
orw %cx, %ax
jz 1f
movw %es, %ax
testw %ax, %ax
jz 1f
movw %es, %bx
xorw %ax, %ax
movw %ax, %es
movw %es:0x007a, %ax /* int1e segment */
cmpw $0xc000, %ax
jnb 1f
movw %bx, %es:0x007a
movw %di, %es:0x0078
1:
#endif
movw %cs, %ax
movw %ax, %ds /* DS=CS */
xorw %ax, %ax
movw %ax, %es /* ES=0 */
#if 1
/* move command-line menu from CS:0081 to 0000:0800 */
movw $0x0081, %si /* DS=CS */
movw $0x0800, %di /* ES=0 */
movw $0x0400, %cx /* move 4KB */
cld
repz movsl
stosb /* end in NULL */
#endif
movw $ABS_START(stage2_64K_pages), %si
movw (%si), %cx
movw %cs, %ax
addw $(ABS_START(pre_stage2_start-0x200)/0x10), %ax
movw %ax, %bx /* save the final stage2 segment to BX */
cmpw $0x0800, %ax
ja 2f /* move from higher at BX:0000 to lower at 0800:0000 */
/* move from lower at BX:0000 to higher at 0800:0000 */
1:
xorw %ax, %ax
movb %cl, %ah
decb %ah
shlb $0x04, %ah
movw %bx, %dx
addw %ax, %dx
movw %dx, %ds
addb $0x08, %ah /* addw $0x0800, %ax */
movw %ax, %es
movw $0xfffe, %si
movw %si, %di
movw %cx, %dx /* save %cx to %dx */
movw $0x8000, %cx /* 0x8000 words == 64K bytes */
/* Can safely move the final sector, because this code is just before
* the final sector */
#if 0
cmpw $0x0001, %dx /* do not move the sector containing this
code, to ensure no overlap */
ja 3f
decb %ch /* %cx == 0x7f00 */
3:
#endif
//cli
std /* move begins at higher end */
repz movsw
movw %dx, %cx /* restore %cx from %dx */
loop 1b
// launch_stage2_code is in the final sector, so it may be overwritten
//jmp launch_stage2_code
//
ljmp $0x800, $ABS_FINAL(launch_stage2_code)
//. = (DOSSTART_SIZE - 0x0200)
. = . + (0x400 - (. % 0x200)) % 0x200
//////////////////////////////////////////////////////////////////////////////
/* The final sector begins here! */
#if 1
/* update: the address of the commandline embedded preset menu has
* changed to physical address 0x800.
*/
#else
/* room for the new preset menu (only 0x80 byte needed) */
. = . + 0x80
#endif
2:
/* move from higher at BX:0000 to lower at 0800:0000.
* Note: this could overwrite our code and stack, so we shouldn't
* use stack(remember to cli), and we should copy the final sector
* to a safe place(because the code will continue to execute after
* the moving).
*/
/* first, copy the final sector to 07e0:0000. Use 07e0 other than 0800
* to ensure no overlap.
*/
/* Now we are at the final sector, and we are above 0800:0000.
* So it is safe to move ourselves onto the lower sector at 07e0:0000.
*/
xorw %si, %si
xorw %di, %di
movw $0x07e0, %ax
movw %ax, %es
movw %bx, %ds
movw %cx, %dx /* save %cx to %dx */
movw $0x0100, %cx /* move the sector containing this code */
cli
// xorw %ax, %ax
// movw %ax, %ss
// movw $0x2000, %sp
cld
repz movsw
movw %dx, %cx /* restore %cx from %dx */
/* Now we should long jump to the new safe code that has just moved,
* because the further moving could overwrite the code here at the
* final sector.
*/
ljmp $0x07e0, $ABS_FINAL(1f)
#if 1
/* update: our stack has changed to dos_stack. */
#else
. = 2b + 0x80
/* room for stack, 0x80 bytes */
. = . + 0x80
#endif
//just_here:
1:
/* from here on, code must be in the FINAL sector */
movw $0x0800, %ax
movw %ax, %es
movw %bx, %ds
1:
xorw %si, %si
xorw %di, %di
movw %cx, %dx /* save %cx to %dx */
movw $0x8000, %cx /* 0x8000 words == 64K bytes */
cld
repz movsw
movw %dx, %cx /* restore %cx from %dx */
addw $0x1000, %ax /* move the next 64K-byte page */
movw %ax, %es
addw $0x1000, %bx
movw %bx, %ds
loop 1b
launch_stage2_code:
cli
cld
xorw %ax, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %ss
movw $0x2000, %sp
xchgb %al, 0x820B # AL=boot_drive, and clear the byte at 0x820B
xchgw %ax, %dx # DL=boot_drive
xorl %ebp, %ebp
xorl %esi, %esi
/* for usb keyborad hack here, disable interrupt */
//sti /* safe now, just before transferring control */
ljmp $0, $0x8200
#if 0
/* print ASCIZ string CS:SI (modifies AX BX SI) */
3:
xorw %bx, %bx /* video page 0 */
movb $0x0e, %ah /* print char in AL */
int $0x10 /* via TTY mode */
print_message_dos:
lodsb %cs:(%si), %al /* get token */
cmpb $0, %al /* end of string? */
jne 3b
ret
#endif
. = 2b + 0x200
#-----------------------------------------------------------------------------
# EXE code ends here
#-----------------------------------------------------------------------------
// if not aligned 0x200, issue error
. = . - (. % 0x200)
pre_stage2_start:
/* Main program of GNU GRUB(i.e., pre_stage2) immediately follows. */