Reputation: 51
I believe I have set up the GDT correctly like this:
# Start the CPU: switch to 32-bit protected mode, jump into C.
# The BIOS loads this code from the first sector of the hard disk into
# memory at physical address 0x7c00 and starts executing in real mode
# with %cs=0 %ip=7c00.
#define CYLS 0x0ff0
#define LEDS 0x0ff1
#define VMODE 0x0ff2
#define SCRNX 0x0ff4
#define SCRNY 0x0ff6
#define VRAM 0x0ff8
.set PROT_MODE_CSEG, 0x8 # kernel code segment selector
.set PROT_MODE_DSEG, 0x10 # kernel data segment selector
.set CR0_PE_ON, 0x1 # protected mode enable flag
.globl start
start:
.code16 # Assemble for 16-bit mode
# Set up the important data segment registers (DS, ES, SS).
xorw %ax,%ax # Segment number zero
movw %ax,%ds # -> Data Segment
movw %ax,%es # -> Extra Segment
movw %ax,%ss # -> Stack Segment
movb $0x13,%al # ;vga 320x200x8 位,color mode
movb $0x00,%ah
int $0x10
#save color mode in ram 0x0ff0
movb $8,(VMODE)
movw $320,(SCRNX)
movw $200,(SCRNY)
movl $0x000a0000,(VRAM)
#get keyboard led status
movb $0x02,%ah
int $0x16 #keyboard interrupts
movb %al,(LEDS)
#diplay something
movw $msg,%si
call puts
movw $try,%si
call puts
#jmp .
cli # Disable interrupts
cld # String operations increment
# Enable A20:
# For backwards compatibility with the earliest PCs, physical
# address line 20 is tied low, so that addresses higher than
# 1MB wrap around to zero by default. This code undoes this.
seta20.1:
inb $0x64,%al # Wait for not busy
testb $0x2,%al
jnz seta20.1
movb $0xd1,%al # 0xd1 -> port 0x64
outb %al,$0x64
seta20.2:
inb $0x64,%al # Wait for not busy
testb $02,%al
jnz seta20.2
movb $0xdf,%al # 0xdf -> port 0x60
outb %al,$0x60
# Switch from real to protected mode, using a bootstrap GDT this is vip ,but i don`t know it clearly now
# and segment translation that makes virtual addresses
# identical to their physical addresses, so that the
# effective memory map does not change during the switch.
lgdt gdtdesc
movl %cr0, %eax
orl $CR0_PE_ON, %eax
movl %eax, %cr0
# Jump to next instruction, but in 32-bit code segment.
# Switches processor into 32-bit mode.
ljmp $PROT_MODE_CSEG, $protcseg
msg:
.asciz "\r\n\n\rmy kernel is runing jos"
try:
.asciz "\r\n\n\rtry it again"
puts:
movb (%si),%al
add $1,%si
cmp $0,%al
je over
movb $0x0e,%ah
movw $15,%bx
int $0x10
jmp puts
over:
ret
.code32 # Assemble for 32-bit mode
protcseg:
# Set up the protected-mode data segment registers
movw $PROT_MODE_DSEG, %ax # Our data segment selector
movw %ax, %ds # -> DS: Data Segment
movw %ax, %es # -> ES: Extra Segment
movw %ax, %fs # -> FS
movw %ax, %gs # -> GS
movw %ax, %ss # -> SS: Stack Segment
# Set up the stack pointer and call into C.
movl $start, %esp
call bootmain
# If bootmain returns (it shouldn't), loop.
spin:
jmp spin
# Bootstrap GDT
.p2align 2 # force 4 byte alignment
gdt:
SEG_NULL # null seg
SEG(STA_X|STA_R, 0x0, 0xffffffff) # code seg
SEG(STA_W, 0x0, 0xffffffff) # data seg
gdtdesc:
.word 0x17 # sizeof(gdt) - 1
.long gdt # address gdt
#.fill 310
Jump to C part of code like this:
.code32 # Assemble for 32-bit mode
protcseg:
# Set up the protected-mode data segment registers
movw 0x10, %ax # Our data segment selector
movw %ax, %ds # -> DS: Data Segment
movw %ax, %es # -> ES: Extra Segment
movw %ax, %fs # -> FS
movw %ax, %gs # -> GS
movw %ax, %ss # -> SS: Stack Segment
# Set up the stack pointer and call into C.
movl $start, %esp
call bootmain
C code like following:
#define io_halt() asm("hlt")
#define write_mem8(addr,data8) (*(volatile char *)(addr))=(char)data8
void color_screen(char color) // 15:pure white
{
int i;
color = color;
for (i = 0xa0000; i < 0xaffff; i++)
{
write_mem8(i, i & 0x0f); // if we write 15 ,all pixels color will be white,15 mens pure white ,so the screen changes into white
}
}
void bootmain(void)
{
color_screen(3);
}
Screen was displaying strips different colors.
My question is, GDT first segment 8 bytes is null, second segment is code segment, third segment is data segment.
After I386 went into protect mode, why load DS with third segment value 0x10?
To display something write to address 0xa0000, how the address 0xa0000 relate to protect mode? Looks like 0xa0000 is a real address in display card, if I do not init protect mode, direct write this address, will the display still work?
Upvotes: 1
Views: 220
Reputation: 18503
how the address 0xa0000 relate to protect mode?
In protected mode without paging, the "linear" address of some memory access is calculated the following way:
Each segment in the GDT and the LDT has a "base address". In your example, both segments have a base address of 0.
The selector register (cs
, ds
, ...) describes an entry in the GDT or the LDT and the access rights:
0x08 is the first entry of the GDT (the first 8 bytes of the GDT not counted) with full access rights; 0x10 is the second entry and so on ...
The address is calculated as: Offset + "base address"
One example:
Let's say the ds
register contains the value 0x18; 0x18 means: The third entry of the GDT. Let's say your GDT contains one entry more and the "base address" of that additional entry is 0x10000.
In this case, write_mem8(0x90000, 123)
would access the graphic card memory at 0xa0000 because 0x90000 (so-called offset) plus 0x10000 (the "base address" of the GDT entry specified by ds
) is 0xa0000.
... if I do not init protect mode ...
In this case, you are in real mode.
In real mode, the address calculation works differently - that's why the registers cs
, ds
... are called "segment" registers and not "selector registers" in that mode:
The address is calculated by multiplying the value in the segment register by 16 and then adding the offset.
So if ds
has a value of 0x9800 and you perform a write_mem8(0x8000, 123)
, you write to address 0xa0000 because 0x9800*16+0x8000 = 0xa0000.
But ...
... in real mode and in 16-bit protected mode, the CPU is in .code16
mode. This means that it will not understand assembly instructions for .code32
mode.
... segments have a "limit".
If a segment has the limit 0x1234, write_mem8(0x1234 ...)
will work but write_mem8(0x1235 ...)
will not work because the offset (0x1235) is larger than the limit.
In your example, both GDT entries have a limit of 0xffffffff. In real mode, the limit of a segment is implicitly 0xffff. So you cannot simply set ds
to 0 and perform a write_mem8(0xa0000 ...)
because 0xa0000 > 0xffff.
There is a hack named "unreal mode" that allows setting the limit to 0xffffffff in real mode - however, you cannot activate that mode without entering (and therefore initializing) protected mode first.
For this reason, 16-bit code typically does not leave the ds
and es
registers unchanged but it modifies those registers whenever another memory area shall be accessed ...
Because of these two points, you will need a 16-bit C compiler if you want to run C code in real mode.
Upvotes: 1
Reputation: 1709
You are loading 0x10
to DS
because 0x10
is the start of the data segment in your GDT. Segmentation changes quite a bit in protected mode compared to real mode. You no longer resolve addresses based on segment:offset
logic. You rather load the a GDT and load the relevant offsets of the GDT entries into DS
or CS
. Since your code and data segments overlap and cover the entire 4 GiB it should have no effect on 0xA0000
whatsoever (as far as I know, someone please correct me if I am wrong). Your problem should not be with GDT.
You can find more info on protected mode segmentation here: https://wiki.osdev.org/Segmentation#Protected_Mode
write_mem8(i, i & 0x0f);
This somehow seems odd, why are you and
'ing the memory with the color value. Shouldn't it be write_mem8(i, 0x0f);
Edit:
As @MichaelPetch mentioned you should be doing
movw $0x10, %ax
to load the correct GDT data segment into DS
Also, yes the display would still work if you wrote to address 0xA0000 when you are in real mode if you are using a graphics video mode (like mode 13h)
Upvotes: 2