Bare metal OS sur Raspberry Pi

Systèmes et réseaux

29 mai 2015

Lucas Baudin et Lucas Verney

Sommaire

Raspberry Pi

  • Carte de développement bon marché
  • Broadcom BCM2835 - ARMv6

Implémentation

  • Amorçage
  • GPIOs
  • Écran
  • Interrupts / MMU

Amorçage

Particularité : Le boot se fait sur le GPU, qui charge un binaire depuis la carte SD en mémoire et passe la main au GPU.

// Enable FPU
mrc p15, 0, r0, c1, c0, 2
orr r0, r0, #0x300000 // single precision
orr r0, r0, #0xC00000 // double precision
mcr p15, 0, r0, c1, c0, 2
mov r0, #0x40000000
fmxr fpexc,r0

// Enable interrupts
mov r0, #(CPSR_MODE_IRQ | …)
msr cpsr_c, r0
mov sp, #0x07fffffc  // Set stack pointer
// Switch back to supervisor mode
mov r0, #(CPSR_MODE_SVR | …)
msr cpsr_c, r0

b _cstartup
void _cstartup(
        uint r0, uint r1, uint r2) {
    int* bss = &__bss_start__;
    int* bss_end = &__bss_end__;

    // Clear the BSS section.
    while (bss < bss_end) {
        *bss = 0;
        ++bss;
    }

    // Init MMU
    // Init screen
    // Init interrupts
    // Init timers
    // Init processes and scheduler
}

GPIOs

Pins d'entrée/sortie numérique. Il suffit d'écrire dans les bons emplacements pour les activer et les configurer.

// GPIO Register set
volatile rpi_gpio_t* gpio = (rpi_gpio_t*) GPIO_BASE;

void rpi_gpio_setup_led(void) {
    // Write 1 to the GPIO16 init nibble in the Function Select
    // 1 GPIO peripheral register to enable GPIO16 as an output
    rpi_get_gpio()[LED_GPFSEL] |= (1 << LED_GPFBIT);
}

void rpi_gpio_flip_led(void) {
	// Flip the LED
	static int lit = 1;
    if (lit) {
        rpi_get_gpio()[LED_GPCLR] = (1 << LED_GPIO_BIT);
        lit = 0;
    } else {
        rpi_get_gpio()[LED_GPSET] = (1 << LED_GPIO_BIT);
        lit = 1;
    }
}

Écran

Communication par mailbox, pour initialiser un framebuffer.

static rpi_mailbox_t* rpi_mailbox = (rpi_mailbox_t*)MAILBOX_BASE;

void rpi_mailbox_send_message(uint32_t to_send, uint32_t mailbox) {
	to_send = to_send + 0x40000000;
	while((rpi_mailbox->Status & 0x80000000) != 0) {
	}
	rpi_mailbox->Write = to_send + mailbox;
}

uint32_t rpi_mailbox_receive_message(uint32_t mailbox) {
    while((rpi_mailbox->Status & 0x40000000) != 0) { }
    if ((rpi_mailbox->Read & 0xf) == mailbox) {
        return (rpi_mailbox->Read >> 4);
    } else {
        return rpi_mailbox_receive_message(mailbox);
    }
}
void rpi_screen_clear(void) {
	uint32_t* frame = &FrameBufferInfo;
	uint16_t* buf = (uint16_t*)(frame[8]);
	for(int x = 0; x < 1024; x++) {
		memcpy(buf+x*768, screen_blank, sizeof(uint16_t)*768);
	}
}

Interrupts

void software_interrupt_helper(void) {
    rpi_screen_draw_line("software", 0xffcc, 4);
    uint32_t* sp = _get_stack_pointer() + 2 + 120/4;

    struct arm_registers registers;
    memcpy(&(registers), (void*)sp, sizeof(registers));

    char * b = "      ";
    rpi_screen_draw_line(itoa(registers.r0, b, 10), 0xcccc, 5);

    switch(registers.r0) {
    case 1:
        rpi_error((char*)registers.r1);
        break;
    …
    case 3:  // yield
        scheduler_schedule(sp);
        break;
    }
}
struct Process* interrupt_vector_helper(void) {
    uint32_t* sp = _get_stack_pointer() + 2 + 8/4;
    rpi_get_timer()->IRQClear = 1;
    rpi_gpio_flip_led();
    return scheduler_current();
}

MMU

Ordonnanceurs

  • Coopératif par FIFO
  • Préemptif « round-robin »
  • Préemptif avec priorité fixée

Coopératif par FIFO

  • C'est l'ordonnanceur le plus simple possible.
  • Chaque nouveau processus se signale à l'ordonnanceur et est ajouté dans une FIFO.
  • Pas de réordonnancement tant que le processus n'a pas rendu la main (yield) ⇨ coopératif.
  • Par conséquent, pas d'interrupts

Préemptif « round-robin »

  • Ordonnancement préemptif simple.
  • Chaque processus a un certain temps alloué pour s'exécuter. Il est préempté passé ce temps, et un autre processus prend la main.
  • Choix du processus fait par FIFO. On prend celui en tête et on empile le processus courant.
  • « round-robin » = FIFO + préemption.
  • Préemption par interrupt sur un timer.

Préemptif avec priorité fixée

Dans « round-robin », chaque processus a la même priorité et donc ils s'exécutent tous tour à tour. Comment introduire une notion de priorité ?

Il suffit de remplacer la FIFO par une file de priorité. Rien d'autre ne change.

Raffinements : Modifier les priorités en ligne.
⇨ Pénaliser / favoriser un processus qui ne rend pas la main à temps.

Démo

Émulateur

⇨ En utilisant une version modifiée de Qemu.

En vrai !