Dica: Obtendo a lista do mapeamento da RAM pela BIOS

No caso do desenvolvimento de um kernel, antes de pularmos para o modo protegido é prudente obter os espaços da memória de RAM usável. Ao contrário do que você pode imaginar, a RAM não é aquele grande array de memória contígua, onde temos um byte atrás do outro. Ela tem buracos!

Nos manuais de desenvolvimento de Kernels e BIOS, tanto da Intel quanto da AMD, recomenda-se o uso de um serviço estendido da BIOS, via INT 0x15/EAX=0xe820. Esse serviço preenche uma estrutura, cujo endereço é fornecido de antemão, com o endereço inicial, o tamanho do bloco e o “tipo” de RAM, que pode ser entendido como “usável” e “não usável”. Esse serviço precisa ser chamado várias vezes até que obtenhamos a lista completa.

As listagens à seguir, escritas usando o NASM 2.11 e o Borland C++ 3.1 (para DOS), ilustram o uso deste serviço. Aliás, se vocẽ não possuir o Borland C++ 3.1, pode usar o Turbo C 2.0, substitua a chamada ao BCC.EXE pelo TCC.EXE (Eu só não sei se o tip ‘long’, no TCC, tem 32 bits!):

;*** E820.ASM ***

;
; Registradores de 32 bits de entrada e saída
; da função e820call().
;
struc e820regs
  ._eax   resd  1
  ._ebx   resd  1
  ._ecx   resd  1
  ._edx   resd  1
endstruc

; Stack frame da função e820call().
struc stkfrm
  .oldbp      resw  1
  .retaddr    resw  1
  .bufferptr  resd  1
  .inregs     resd  1
  .outregs    resd  1
endstruc

; Sintaxe estranha, mas funciona com o Borland C++ 3.1
; e com o Turbo C 2.0.
;
; Compilar com `nasm -f obj e820.asm -o e820.obj`
;
segment _TEXT public class=CODE use16
cpu 386

;
; int e820call(void far *bufferptr,
;              struct e820regs far *inregs,
;              struct e820regs far *outregs);
;
global _e820call
_e820call:
  ; Estabelece o stackframe.
  push  bp
  mov   bp,sp

  ; A convenção de chamada pede que esses registadores
  ; sejam preservados.
  push  ds
  push  es
  push  bx
  push  si
  push  di

  les   di,[bp+stkfrm.bufferptr]
  movzx edi,di   ; int 0x15/ax=0xe820 requer ES:EDI.

  ; Ajusta os valores dos registradores a partir da
  ; estrutura de inregs.
  lds   si,[bp+stkfrm.inregs]
  mov   ebx,[si+e820regs._ebx]
  mov   ecx,[si+e820regs._ecx]
  mov   edx,[si+e820regs._edx]
  mov   eax,0x0e820

  int   0x15

  ; Salva os registradores retornados na estrutura
  ; de outregs.
  lds   si,[bp+stkfrm.outregs]
  mov   [si+e820regs._eax],eax
  mov   [si+e820regs._ebx],ebx
  mov   [si+e820regs._ecx],ecx
  mov   [si+e820regs._edx],edx

  ; 0 = OK. Qualquer outra coisa, erro!
  mov   ax,0
  setc  al

  pop   di
  pop   si
  pop   bx
  pop   es
  pop   ds

  leave         ; Limpa o stack frame.
  ret

Com base nessa pequena função, podemos usá-la, em C:

/*** TEST_E82.C ***/
#include <stdio.h>

/* Estrutura que abriga os registradores que
   serão usados/alterados por e820call().
   
   Atenção: "long" precisa ter 32 bits de
   tamanho! */
struct e820regs { long eax, ebx, ecx, edx; };

/* A estrutura tem 24 bytes de tamanho! */
struct e820struc {
  /* Note que 'long' tem 32 bits,
     mas o endereço incial e o tamanho
     têm 64! */
  long addr_lo, addr_hi;
  long len_lo, len_hi;

  long type;
};

/* Essa é nossa rotina em assembly. */
extern int e820call(void far *, 
                    struct e820regs far *, 
                    struct e820regs far *);

static const char * get_type_name(int type);

void main(void)
{
  int err;
  struct e820regs inregs, outregs;
  struct e820struc e820s,  /* Resolvi alocar o buffer 
                              na pilha! */

  /* Na primeira chamada EAX=0xe820, EBX=0, 
     ECX=24 e EDX='SMAP' */
  inregs.ebx = 0;
  inregs.ecx = sizeof(struct e820struc);
  inregs.edx = 0x534d4150;

  /* Enquanto não falhar... */
  if (!!(err = e820call(&e820s, &inregs, &outregs)))
  {
    fputs("Error calling INT 0x15/AX=0xe820 service!\n",
          stderr);
    return;
  }

  while (!err)
  {
    /* Imprime os valores na estrtutura. */ 
    printf("%08lX%08lX (%08lX%08lX bytes): (%s)\n",
      e820s.addr_hi, e820s.addr_lo,
      e820s.len_hi, e820s.len_lo,
      get_type_name(e820s.type));

    /* EBX == 0 marca o fim da lista. */
    if (!outregs.ebx)
      break;

    /* O EBX retornado indica o próximo item da lista. */
    inregs.ebx = outregs.ebx;

    err = e820call(&e820s, &inregs, &outregs);
  }
}

/* Rotininha simples só para retornar o significado 
   de "type". */
static const char *get_type_name(int type)
{
  if (type == 1) return "RAM";
  else return "Reserved";
}

Para compilar, nada mais simples:

C:\WORK\E820H\> nasm -f obj e820.asm -o e820.obj
C:\WORK\E820H\> bcc -c test_e82.c
C:\WORK\E820H\> tlink /Lc:\bcc\lib c0s.obj+test_e82.obj+e820.obj,test_e82.exe,nul,cs.lib

AVISO: O código acima só funciona no MS-DOS!!!

Ao executar o TEST_E82.EXE, obtemos isso:

e820

Isso foi obtido a partir de uma máquina virtual com 256 MB de RAM. Note que, da mamória RAM usável, apenas dois blocos estão disponíveis: A RAM de 0x00000000 até 0x0009fbff, já que, a partir de 0x0a0000 temos a memória de vídeo e 267321344 bytes de RAM acima do primeiro megabyte, lógo após a cópia da ROM (que está localizada entre 0xf0000 até 0xfffff).

Aparecem ainda dois blocos “reservados”. O primeiro começa em 0xfff0000 e o segundo em 0xfffc0000. Ambos são dedicados à I/O mapeado em memória e à ROM real do sistema.

Observação: A rotina e820call() toma 3 ponteiros “far” porque eu quis fazê-la independente de modelo.

Anúncios

Deixe um comentário

Faça o login usando um destes métodos para comentar:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s