lewis
lewis

Reputation: 51

The relation between I386 GDT and display memory address 0xa0000?

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

Answers (2)

Martin Rosenau
Martin Rosenau

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 ...

  1. ... 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.

  2. ... 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

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

Related Questions