. Login Cadastro

Comunidade para programadores


Você não está conectado. Conecte-se ou registre-se

Desenvolvendo sistema operacional do zero

Ver o tópico anterior Ver o tópico seguinte Ir para baixo Mensagem [Página 1 de 1]

Post: #119/11/2020, 13:21

Morzan

Morzan

Administrador
Código:
/*
 virt.lds
 Linker script for outputting to RISC-V QEMU "virt" machine.
 Stephen Marz
 6 October 2019
*/

/*
  riscv is the name of the architecture that the linker understands
  for any RISC-V target (64-bit or 32-bit).

  We will further refine this by using -mabi=lp64 and -march=rv64gc
*/
OUTPUT_ARCH( "riscv" )

/*
We're setting our entry point to a symbol
called _start which is inside of boot.S. This
essentially stores the address of _start as the
"entry point", or where CPU instructions should start
executing.

In the rest of this script, we are going to place _start
right at the beginning of 0x8000_0000 because this is where
the virtual machine and many RISC-V boards will start executing.
*/
ENTRY( _start )

/*
The MEMORY section will explain that we have "ram" that contains
a section that is 'w' (writeable), 'x' (executable), and 'a' (allocatable).
We use '!' to invert 'r' (read-only) and 'i' (initialized). We don't want
our memory to be read-only, and we're stating that it is NOT initialized
at the beginning.

The ORIGIN is the memory address 0x8000_0000. If we look at the virt
spec or the specification for the RISC-V HiFive Unleashed, this is the
starting memory address for our code.

Side note: There might be other boot ROMs at different addresses, but
their job is to get to this point.

Finally LENGTH = 128M tells the linker that we have 128 megabyte of RAM.
The linker will double check this to make sure everything can fit.

The HiFive Unleashed has a lot more RAM than this, but for the virtual
machine, I went with 128M since I think that's enough RAM for now.

We can provide other pieces of memory, such as QSPI, or ROM, but we're
telling the linker script here that we have one pool of RAM.
*/
MEMORY
{
  ram  (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M
}

/*
PHDRS is short for "program headers", which we specify three here:
text - CPU instructions (executable sections)
data - Global, initialized variables
bss  - Global, uninitialized variables (all will be set to 0 by boot.S)

The command PT_LOAD tells the linker that these sections will be loaded
from the file into memory.

We can actually stuff all of these into a single program header, but by
splitting it up into three, we can actually use the other PT_* commands
such as PT_DYNAMIC, PT_INTERP, PT_NULL to tell the linker where to find
additional information.

However, for our purposes, every section will be loaded from the program
headers.
*/
PHDRS
{
  text PT_LOAD;
  data PT_LOAD;
  bss PT_LOAD;
}

/*
We are now going to organize the memory based on which
section it is in. In assembly, we can change the section
with the ".section" directive. However, in C++ and Rust,
CPU instructions go into text, global constants go into
rodata, global initialized variables go into data, and
global uninitialized variables go into bss.
*/
SECTIONS
{
  /*
    The first part of our RAM layout will be the text section.
   Since our CPU instructions are here, and our memory starts at
   0x8000_0000, we need our entry point to line up here.
  */
  .text : {
     /*
       PROVIDE allows me to access a symbol called _text_start so
      I know where the text section starts in the operating system.
      This should not move, but it is here for convenience.
      The period '.' tells the linker to set _text_start to the
      CURRENT location ('.' = current memory location). This current
      memory location moves as we add things.
     */

    PROVIDE(_text_start = .);
   /*
     We are going to layout all text sections here, starting with
     .text.init. The asterisk in front of the parentheses means to match
     the .text.init section of ANY object file. Otherwise, we can specify
     which object file should contain the .text.init section, for example,
     boot.o(.text.init) would specifically put the .text.init section of
     our bootloader here.

     Because we might want to change the name of our files, we'll leave it
     with a *.

     Inside the parentheses is the name of the section. I created my own
     called .text.init to make 100% sure that the _start is put right at the
     beginning. The linker will lay this out in the order it receives it:

     .text.init first
     all .text sections next
     any .text.* sections last

     .text.* means to match anything after .text. If we didn't already specify
     .text.init, this would've matched here. The assembler and linker can place
     things in "special" text sections, so we match any we might come across here.
   */
    *(.text.init) *(.text .text.*)

   /*
     Again, with PROVIDE, we're providing a readable symbol called _text_end, which is
     set to the memory address AFTER .text.init, .text, and .text.*'s have been added.
   */
    PROVIDE(_text_end = .);
   /*
     The portion after the right brace is in an odd format. However, this is telling the
     linker what memory portion to put it in. We labeled our RAM, ram, with the constraints
     that it is writeable, allocatable, and executable. The linker will make sure with this
     that we can do all of those things.

     >ram - This just tells the linker script to put this entire section (.text) into the
           ram region of memory. To my knowledge, the '>' does not mean "greater than". Instead,
          it is a symbol to let the linker know we want to put this in ram.

     AT>ram - This sets the LMA (load memory address) region to the same thing. LMA is the final
             translation of a VMA (virtual memory address). With this linker script, we're loading
           everything into its physical location. We'll let the kernel copy and sort out the
           virtual memory. That's why >ram and AT>ram are continually the same thing.

     :text  - This tells the linker script to put this into the :text program header. We've only
             defined three: text, data, and bss. In this case, we're telling the linker script
           to go into the text section.
   */
  } >ram AT>ram :text
  /*
    The global pointer allows the linker to position global variables and constants into
    independent positions relative to the gp (global pointer) register. The globals start
    after the text sections and are only relevant to the rodata, data, and bss sections.
  */
  PROVIDE(_global_pointer = .);
  /*
    Most compilers create a rodata (read only data) section for global constants. However,
    we're going to place ours in the text section. We can actually put this in :data, but
    since the .text section is read-only, we can place it there.

    NOTE: This doesn't actually do anything, yet. The actual "protection" cannot be done
    at link time. Instead, when we program the memory management unit (MMU), we will be
    able to choose which bits (R=read, W=write, X=execute) we want each memory segment
    to be able to do.
  */
  .rodata : {
    PROVIDE(_rodata_start = .);
    *(.rodata .rodata.*)
    PROVIDE(_rodata_end = .);
   /*
     Again, we're placing the rodata section in the memory segment "ram" and we're putting
     it in the :text program header. We don't have one for rodata anyway.
   */
  } >ram AT>ram :text

  .data : {
   /*
     . = ALIGN(4096) tells the linker to align the current memory location (which is
     0x8000_0000 + text section + rodata section) to 4096 bytes. This is because our paging
     system's resolution is 4,096 bytes or 4 KiB.
   */
    . = ALIGN(4096);
    PROVIDE(_data_start = .);
   /*
     sdata and data are essentially the same thing. However, compilers usually use the
     sdata sections for shorter, quicker loading sections. So, usually critical data
     is loaded there. However, we're loading all of this in one fell swoop.
     So, we're looking to put all of the following sections under the umbrella .data:
     .sdata
     .sdata.[anything]
     .data
     .data.[anything]

     ...in that order.
   */
    *(.sdata .sdata.*) *(.data .data.*)
    PROVIDE(_data_end = .);
  } >ram AT>ram :data

  .bss : {
    PROVIDE(_bss_start = .);
    *(.sbss .sbss.*) *(.bss .bss.*)
    PROVIDE(_bss_end = .);
  } >ram AT>ram :bss

  /*
    The following will be helpful when we allocate the kernel stack (_stack) and
    determine where the heap begnis and ends (_heap_start and _heap_start + _heap_size)/
    When we do memory allocation, we can use these symbols.

    We use the symbols instead of hard-coding an address because this is a floating target.
    As we add code, the heap moves farther down the memory and gets shorter.

    _memory_start will be set to 0x8000_0000 here. We use ORIGIN(ram) so that it will take
    whatever we set the origin of ram to. Otherwise, we'd have to change it more than once
    if we ever stray away from 0x8000_0000 as our entry point.
  */
  PROVIDE(_memory_start = ORIGIN(ram));
  /*
    Our kernel stack starts at the end of the bss segment (_bss_end). However, we're allocating
    0x80000 bytes (524 KiB) to our kernel stack. This should be PLENTY of space. The reason
    we add the memory is because the stack grows from higher memory to lower memory (bottom to top).
    Therefore we set the stack at the very bottom of its allocated slot.
    When we go to allocate from the stack, we'll subtract the number of bytes we need.
  */
  PROVIDE(_stack_start = _bss_end);
  PROVIDE(_stack_end = _stack_start + 0x8000);
  PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram));

  /*
    Finally, our heap starts right after the kernel stack. This heap will be used mainly
    to dole out memory for user-space applications. However, in some circumstances, it will
    be used for kernel memory as well.

    We don't align here because we let the kernel determine how it wants to do this.
  */
  PROVIDE(_heap_start = _stack_end);
  PROVIDE(_heap_size = _memory_end - _heap_start);
}


Arquivo do Linker script, configuracao padrao do QEMU que usaremos para desenvolver o Kernel basico.


Código:
# boot.S
# bootloader for SoS
# Stephen Marz
# 8 February 2019

# Disable generation of compressed instructions.
.option norvc

# Define a .data section.
.section .data

# Define a .text.init section.
.section .text.init

# Execution starts here.
.global _start
_start:
   # Any hardware threads (hart) that are not bootstrapping
   # need to wait for an IPI
   csrr   t0, mhartid
   bnez   t0, 3f
   # SATP should be zero, but let's make sure
   csrw   satp, zero
   
   # Disable linker instruction relaxation for the `la` instruction below.
   # This disallows the assembler from assuming that `gp` is already initialized.
   # This causes the value stored in `gp` to be calculated from `pc`.
.option push
.option norelax
   la      gp, _global_pointer
.option pop
   # Set all bytes in the BSS section to zero.
   la       a0, _bss_start
   la      a1, _bss_end
   bgeu   a0, a1, 2f
1:
   sd      zero, (a0)
   addi   a0, a0, 8
   bltu   a0, a1, 1b
2:
   # Control registers, set the stack, mstatus, mepc,
   # and mtvec to return to the main function.
   # li      t5, 0xffff;
   # csrw   medeleg, t5
   # csrw   mideleg, t5
   # The stack grows from bottom to top, so we put the stack pointer
   # to the very end of the stack range.
   la      sp, _stack_end
   # Setting `mstatus` register:
   # 0b11 << 11: Machine's previous protection mode is 3 (MPP=3).
   li      t0, 0b11 << 11
   csrw   mstatus, t0
   # Machine's exception program counter (MEPC) is set to `kinit`.
   la      t1, kmain
   csrw   mepc, t1
   # Machine's trap vector base address is set to `asm_trap_vector`.
   la      t2, asm_trap_vector
   csrw   mtvec, t2
   # Set the return address to get us into supervisor mode
   la      ra, 2f
   # We use mret here so that the mstatus register is properly updated.
   mret
2:
   # We set the return address (ra above) to this label. When kinit() is finished
   # in Rust, it will return here.

   # Setting `sstatus` (supervisor status) register:
   # 1 << 8    : Supervisor's previous protection mode is 1 (SPP=1 [Supervisor]).
   # 1 << 5    : Supervisor's previous interrupt-enable bit is 1 (SPIE=1 [Enabled]).
   # 1 << 1    : Supervisor's interrupt-enable bit will be set to 1 after sret.
   # We set the "previous" bits because the sret will write the current bits
   # with the previous bits.
   li      t0, (1 << 8) | (1 << 5)
   csrw   sstatus, t0
   la      t1, kmain
   csrw   sepc, t1
   # Setting `mideleg` (machine interrupt delegate) register:
   # 1 << 1  : Software interrupt delegated to supervisor mode
   # 1 << 5  : Timer interrupt delegated to supervisor mode
   # 1 << 9  : External interrupt delegated to supervisor mode
   # By default all traps (interrupts or exceptions) automatically
   # cause an elevation to the machine privilege mode (mode 3).
   # When we delegate, we're telling the CPU to only elevate to
   # the supervisor privilege mode (mode 1)
   li      t2, (1 << 1) | (1 << 5) | (1 << 9)
   csrw   mideleg, t2
   # Setting `sie` (supervisor interrupt enable) register:
   # This register takes the same bits as mideleg
   # 1 << 1    : Supervisor software interrupt enable (SSIE=1 [Enabled])
   # 1 << 5    : Supervisor timer interrupt enable (STIE=1 [Enabled])
   # 1 << 9    : Supervisor external interrupt enable (SEIE=1 [Enabled])
   csrw   sie, t2
   # Setting `stvec` (supervisor trap vector) register:
   # Essentially this is a function pointer, but the last two bits can be 00 or 01
   # 00        : All exceptions set pc to BASE
   # 01        : Asynchronous interrupts set pc to BASE + 4 x scause
   la      t3, asm_trap_vector
   csrw   stvec, t3
   # kinit() is required to return back the SATP value (including MODE) via a0
   csrw   satp, a0
   # Force the CPU to take our SATP register.
   # To be efficient, if the address space identifier (ASID) portion of SATP is already
   # in cache, it will just grab whatever's in cache. However, that means if we've updated
   # it in memory, it will be the old table. So, sfence.vma will ensure that the MMU always
   # grabs a fresh copy of the SATP register and associated tables.
   sfence.vma
   # sret will put us in supervisor mode and re-enable interrupts
   sret
3:

   # Parked harts go here. We need to set these
   # to only awaken if it receives a software interrupt,
   # which we're going to call the SIPI (Software Intra-Processor Interrupt).
   # We call the SIPI by writing the software interrupt into the Core Local Interruptor (CLINT)
   # Which is calculated by: base_address + hart * 4
   # where base address is 0x0200_0000 (MMIO CLINT base address)
   # We only use additional harts to run user-space programs, although this may
   # change.
4:
   wfi
   j      4b


Arquivo de boot , que iniciará o sistema e carregará nosso código em C++

Código:
typedef unsigned long long uint64;
typedef char uint8;

#include "uart.h"
#include "mem.h"

const bool DISABLE_AFTER_FIRST_INTERRUPT = false;

static void print(char* string) {
   UART uart(0x10000000);
   uart.write(string);
}

struct trap_frame{
   uint64 regs[32];       // 0 - 255 bytes
   uint64 fregs[32];       // 256 - 511 bytes
   uint64 satp;          // 512 - 519 bytes   
   uint64* trap_stack;      // 520 byte
   uint64 hartid;         // 528 byte
};


void disable_interrupts() {
   asm("addi sp, sp, -8");
   asm("sd t0, 0(sp)");

   asm("csrr t0, mie");
   asm("andi t0, t0, 0xffffffffffffff7f");
   asm("csrw mie, t0");

   asm("ld t0, 0(sp)");
   asm("addi sp, sp, 8");
}

void add_timer(int seconds) {
   volatile uint64* mtimecmp= reinterpret_cast<uint64*>(0x02004000);
   volatile uint64* mtime =  reinterpret_cast<uint64*>(0x0200bff8);

   *mtimecmp = *mtime + (seconds * 10000000);
}

extern "C" int kmain()
{

   Memory::init();
   Memory::alloc(3);   
   
   return 0;
}

extern "C" void m_trap(uint64 epc, uint64 tval,uint64 cause,uint64 hart, uint64 status, trap_frame* trap_frame)
{
   // o bit mais significante da causa diz se é uma interrupcao sincrona ou assincrona
   bool async = false;
   if ((cause >> 63 & 1) == 1){
      async = true;
   } else {
      async = false;
   }

   // os 4 bits menos significantes dizem qual foi o tipo de interrupcao
   unsigned int cause_num = cause & 0xf;
   volatile uint64* mtimecmp= reinterpret_cast<uint64*>(0x02004000);
   volatile uint64* mtime =  reinterpret_cast<uint64*>(0x0200bff8);
   uint64 time = *mtime;
   uint64 timecmp = *mtimecmp;

   if(async) {
      // o tipo 7 é uma interrupcao de timer
      if (cause_num == 7) {
         if (DISABLE_AFTER_FIRST_INTERRUPT) {
            disable_interrupts();
         } else {
            add_timer(2);
         }
      }
   }

   timecmp = *mtimecmp;
}


Nosso arquivo main.cc

https://doublefree.forumeiros.com

Ver o tópico anterior Ver o tópico seguinte Ir para o topo Mensagem [Página 1 de 1]

Permissões neste sub-fórum
Não podes responder a tópicos