mirror of
https://git.stationery.faith/corn/corn.git
synced 2024-11-21 23:42:21 +00:00
better mboot
This commit is contained in:
parent
c9d9f1a9af
commit
9cc8dfcf0e
7 changed files with 156 additions and 131 deletions
|
@ -1,13 +1,5 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
enum acpi_status {
|
|
||||||
ACPI_SUCCESS = 0,
|
|
||||||
ACPI_FAILURE = -1,
|
|
||||||
ACPI_MALFORMED_TABLE = -2,
|
|
||||||
ACPI_OLD_VERSION = -4,
|
|
||||||
ACPI_S5_PARSE_ERROR = -5,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the ACPI tables
|
* Loads the ACPI tables
|
||||||
* https://en.wikipedia.org/wiki/ACPI
|
* https://en.wikipedia.org/wiki/ACPI
|
||||||
|
|
|
@ -2,6 +2,10 @@
|
||||||
|
|
||||||
#include <memory.h>
|
#include <memory.h>
|
||||||
|
|
||||||
|
#define CMDLINE_MAX 32
|
||||||
|
|
||||||
struct boot_info {
|
struct boot_info {
|
||||||
struct memory_map *map;
|
struct memory_map *map;
|
||||||
|
void *acpi_table;
|
||||||
|
char cmdline[CMDLINE_MAX];
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
#include "bindings.h"
|
#include "bindings.h"
|
||||||
|
#include "serial.h"
|
||||||
|
|
||||||
/* global state, idk a better way rn */
|
/* global state, idk a better way rn */
|
||||||
struct acpi_state state;
|
struct acpi_state state;
|
||||||
|
@ -21,7 +22,18 @@ struct acpi_header {
|
||||||
uint32_t creator_revision;
|
uint32_t creator_revision;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// root system descriptor pointer
|
||||||
|
// ACPI 1.0
|
||||||
|
struct rsdp {
|
||||||
|
char signature[8];
|
||||||
|
uint8_t checksum;
|
||||||
|
char oemid[6];
|
||||||
|
uint8_t revision;
|
||||||
|
uint32_t rsdt_addr;
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
// eXtended system descriptor pointer
|
// eXtended system descriptor pointer
|
||||||
|
// ACPI 2.0
|
||||||
struct xsdp {
|
struct xsdp {
|
||||||
char signature[8];
|
char signature[8];
|
||||||
uint8_t checksum;
|
uint8_t checksum;
|
||||||
|
@ -35,13 +47,21 @@ struct xsdp {
|
||||||
uint8_t reserved[3];
|
uint8_t reserved[3];
|
||||||
} __attribute__((packed));
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
// root system descriptor table
|
||||||
|
// ACPI 1.0
|
||||||
|
struct rsdt {
|
||||||
|
struct acpi_header h;
|
||||||
|
uint64_t sdt_pointers[];
|
||||||
|
};
|
||||||
|
|
||||||
// eXtended system descriptor table
|
// eXtended system descriptor table
|
||||||
|
// ACPI 2.0
|
||||||
struct xsdt {
|
struct xsdt {
|
||||||
struct acpi_header h;
|
struct acpi_header h;
|
||||||
uint64_t sdt_pointers[];
|
uint64_t sdt_pointers[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// generic address structure
|
// generic address structure
|
||||||
struct gas {
|
struct gas {
|
||||||
uint8_t address_space;
|
uint8_t address_space;
|
||||||
|
@ -56,10 +76,10 @@ struct fadt {
|
||||||
struct acpi_header h;
|
struct acpi_header h;
|
||||||
uint32_t firmware_ctrl;
|
uint32_t firmware_ctrl;
|
||||||
uint32_t dsdt;
|
uint32_t dsdt;
|
||||||
|
|
||||||
// field used in ACPI 1.0; no longer in use, for compatibility only
|
// field used in ACPI 1.0; no longer in use, for compatibility only
|
||||||
uint8_t reserved;
|
uint8_t reserved;
|
||||||
|
|
||||||
uint8_t preferred_power_management_profile;
|
uint8_t preferred_power_management_profile;
|
||||||
uint16_t sci_interrupt;
|
uint16_t sci_interrupt;
|
||||||
uint32_t smi_command_port;
|
uint32_t smi_command_port;
|
||||||
|
@ -92,23 +112,23 @@ struct fadt {
|
||||||
uint8_t day_alarm;
|
uint8_t day_alarm;
|
||||||
uint8_t month_alarm;
|
uint8_t month_alarm;
|
||||||
uint8_t century;
|
uint8_t century;
|
||||||
|
|
||||||
// reserved in ACPI 1.0; used since ACPI 2.0+
|
// reserved in ACPI 1.0; used since ACPI 2.0+
|
||||||
uint16_t boot_architecture_flags;
|
uint16_t boot_architecture_flags;
|
||||||
|
|
||||||
uint8_t reserved_2;
|
uint8_t reserved_2;
|
||||||
uint32_t flags;
|
uint32_t flags;
|
||||||
|
|
||||||
// 12 byte structure; see below for details
|
// 12 byte structure; see below for details
|
||||||
struct gas reset_reg;
|
struct gas reset_reg;
|
||||||
|
|
||||||
uint8_t reset_value;
|
uint8_t reset_value;
|
||||||
uint8_t reserved_3[3];
|
uint8_t reserved_3[3];
|
||||||
|
|
||||||
// 64bit pointers - Available on ACPI 2.0+
|
// 64bit pointers - Available on ACPI 2.0+
|
||||||
uint64_t x_firmware_control;
|
uint64_t x_firmware_control;
|
||||||
uint64_t x_dsdt;
|
uint64_t x_dsdt;
|
||||||
|
|
||||||
struct gas x_pm1_a_event_block;
|
struct gas x_pm1_a_event_block;
|
||||||
struct gas x_pm1_b_event_block;
|
struct gas x_pm1_b_event_block;
|
||||||
struct gas x_pm1_a_control_block;
|
struct gas x_pm1_a_control_block;
|
||||||
|
@ -135,9 +155,11 @@ static bool checksum(uint8_t *data, size_t len) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static int read_s5_addr(struct acpi_state *state) {
|
static int read_s5_addr(struct acpi_state *state) {
|
||||||
|
serial_out_str("a");
|
||||||
uintptr_t ptr = state->fadt.dsdt;
|
uintptr_t ptr = state->fadt.dsdt;
|
||||||
char *s5_addr = (void*) (ptr + 36);
|
char *s5_addr = (void*) (ptr + 36);
|
||||||
|
serial_out_str("a");
|
||||||
|
|
||||||
int dsdt_len = *((int*) (ptr+1)) - 36;
|
int dsdt_len = *((int*) (ptr+1)) - 36;
|
||||||
while (0 < dsdt_len--) {
|
while (0 < dsdt_len--) {
|
||||||
if ( memcmp(s5_addr, "_S5_", 4) == 0)
|
if ( memcmp(s5_addr, "_S5_", 4) == 0)
|
||||||
|
@ -164,17 +186,29 @@ static int read_s5_addr(struct acpi_state *state) {
|
||||||
state->SCI_EN = 1;
|
state->SCI_EN = 1;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return ACPI_S5_PARSE_ERROR;
|
return -1;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return ACPI_S5_PARSE_ERROR;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ACPI_SUCCESS;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void *acpi_find_table_rsdt(struct rsdt *rsdt, const char *identifier, int ident_len) {
|
||||||
|
int entries = (rsdt->h.length - sizeof(rsdt->h)) / 8;
|
||||||
|
|
||||||
static void *acpi_find_table(struct xsdt *xsdt, const char *identifier, int ident_len) {
|
for (int i = 0; i < entries; i++) {
|
||||||
|
struct acpi_header *h = (struct acpi_header *) (uintptr_t) rsdt->sdt_pointers[i];
|
||||||
|
if (!strncmp(h->signature, identifier, ident_len))
|
||||||
|
return (void *)h;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TABLE NOT FOUND
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *acpi_find_table_xsdt(struct xsdt *xsdt, const char *identifier, int ident_len) {
|
||||||
int entries = (xsdt->h.length - sizeof(xsdt->h)) / 8;
|
int entries = (xsdt->h.length - sizeof(xsdt->h)) / 8;
|
||||||
|
|
||||||
for (int i = 0; i < entries; i++) {
|
for (int i = 0; i < entries; i++) {
|
||||||
|
@ -187,38 +221,72 @@ static void *acpi_find_table(struct xsdt *xsdt, const char *identifier, int iden
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
int acpi_init(void *rootsdp) {
|
int acpi_init_rsdt(struct rsdt *rsdt) {
|
||||||
struct xsdp *xsdp = (struct xsdp *) rootsdp;
|
if (!checksum((uint8_t *) &rsdt->h, rsdt->h.length))
|
||||||
|
return -1;
|
||||||
|
|
||||||
if (!checksum((uint8_t *)xsdp, sizeof(struct xsdp)))
|
struct fadt *fadt = acpi_find_table_rsdt(rsdt, "FACP", 4);
|
||||||
return ACPI_MALFORMED_TABLE;
|
|
||||||
|
|
||||||
if (xsdp->revision != 2)
|
|
||||||
return ACPI_OLD_VERSION;
|
|
||||||
|
|
||||||
struct xsdt *xsdt = (struct xsdt *) (uintptr_t) xsdp->xsdt_addr;
|
|
||||||
if (!checksum((uint8_t *) &xsdt->h, xsdt->h.length))
|
|
||||||
return ACPI_MALFORMED_TABLE;
|
|
||||||
|
|
||||||
struct fadt *fadt = acpi_find_table(xsdt, "FACP", 4);
|
|
||||||
if (!fadt)
|
if (!fadt)
|
||||||
return ACPI_MALFORMED_TABLE;
|
return -1;
|
||||||
|
|
||||||
if (!checksum((uint8_t *) &fadt->h, fadt->h.length))
|
if (!checksum((uint8_t *) &fadt->h, fadt->h.length))
|
||||||
return ACPI_MALFORMED_TABLE;
|
return -1;
|
||||||
|
|
||||||
state.fadt = *fadt;
|
state.fadt = *fadt;
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int acpi_init_xsdt(struct xsdt *xsdt) {
|
||||||
|
if (!checksum((uint8_t *) &xsdt->h, xsdt->h.length))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
struct fadt *fadt = acpi_find_table_xsdt(xsdt, "FACP", 4);
|
||||||
|
if (!fadt)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (!checksum((uint8_t *) &fadt->h, fadt->h.length))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
state.fadt = *fadt;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int acpi_init(void *rootsdp) {
|
||||||
|
struct rsdp *rsdp = (struct rsdp *) rootsdp;
|
||||||
|
|
||||||
|
if (!checksum((uint8_t *)rsdp, sizeof(struct xsdp)))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
int res;
|
||||||
|
|
||||||
|
if (rsdp->revision == 0) {
|
||||||
|
res = acpi_init_rsdt(
|
||||||
|
(struct rsdt *) (uintptr_t) rsdp->rsdt_addr
|
||||||
|
);
|
||||||
|
} else if (rsdp->revision == 2) {
|
||||||
|
struct xsdp *xsdp = (struct xsdp *) rsdp;
|
||||||
|
res = acpi_init_xsdt(
|
||||||
|
(struct xsdt *) (uintptr_t) xsdp->xsdt_addr
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res)
|
||||||
|
return res;
|
||||||
|
|
||||||
int ret = read_s5_addr(&state);
|
int ret = read_s5_addr(&state);
|
||||||
if (!ret)
|
if (!ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
outb(state.fadt.smi_command_port,state.fadt.acpi_enable);
|
outb(state.fadt.smi_command_port,state.fadt.acpi_enable);
|
||||||
|
|
||||||
return ACPI_SUCCESS;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int acpi_shutdown(void) {
|
int acpi_shutdown(void) {
|
||||||
outw((unsigned int) state.fadt.pm1_a_control_block, state.SLP_TYPb | state.SLP_EN);
|
outw((unsigned int) state.fadt.pm1_a_control_block, state.SLP_TYPb | state.SLP_EN);
|
||||||
return ACPI_FAILURE;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,80 +1,72 @@
|
||||||
#include "mboot.h"
|
#include "mboot.h"
|
||||||
#include "serial.h"
|
#include "serial.h"
|
||||||
|
#include "shim.h"
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <lib.h>
|
#include <lib.h>
|
||||||
|
|
||||||
static void read_cmdline(struct mboot_tag *tag, char *data, uint8_t len) {
|
enum mboot_tag_type {
|
||||||
|
MBOOT_CMDLINE = 0,
|
||||||
|
MBOOT_MEMORYMAP = 6,
|
||||||
|
MBOOT_SYMBOLS = 9,
|
||||||
|
MBOOT_XSDP = 14
|
||||||
|
};
|
||||||
|
|
||||||
|
static void read_cmdline(struct boot_info *shim_info, char *data, uint8_t len) {
|
||||||
if (len >= CMDLINE_MAX)
|
if (len >= CMDLINE_MAX)
|
||||||
len = CMDLINE_MAX; // truncate :(
|
len = CMDLINE_MAX; // truncate :(
|
||||||
memcpy(tag->data.cmdline, data, len);
|
memcpy(shim_info->cmdline, data, len);
|
||||||
tag->data.cmdline[len] = '\0';
|
shim_info->cmdline[len] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
static void read_memorymap(struct mboot_tag *tag, uint64_t size, uint32_t *data) {
|
static void read_memorymap(struct boot_info *shim_info, uint64_t size, uint32_t *data) {
|
||||||
tag->data.memory_map = (struct memory_map *) data;
|
shim_info->map = (struct memory_map *) data;
|
||||||
tag->data.memory_map->size = size;
|
shim_info->map->size = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void read_xsdp(struct mboot_tag *tag, char *data) {
|
static void read_xsdp(struct boot_info *shim_info, char *data) {
|
||||||
tag->data.rootsdp = (void *) data;
|
shim_info->acpi_table = (void *) data;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint32_t *read_tag(struct mboot_info *info, uint32_t *data) {
|
static uint32_t *read_tag(uint32_t *data, struct boot_info *shim_info) {
|
||||||
struct mboot_tag tag;
|
|
||||||
tag.type = *((uint16_t *)data);
|
|
||||||
tag.size = data[1];
|
|
||||||
tag.valid = 1;
|
|
||||||
|
|
||||||
uint8_t data_len = tag.size - 2 * sizeof(uint32_t);
|
uint16_t type = *((uint16_t *)data);
|
||||||
|
uint32_t size = data[1];
|
||||||
|
|
||||||
switch (tag.type) {
|
uint8_t data_len = size - 2 * sizeof(uint32_t);
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
case MBOOT_CMDLINE:
|
case MBOOT_CMDLINE:
|
||||||
read_cmdline(&tag, (char *)(data + 2), data_len);
|
read_cmdline(shim_info, (char *)(data + 2), data_len);
|
||||||
break;
|
break;
|
||||||
case MBOOT_MEMORYMAP:
|
case MBOOT_MEMORYMAP:
|
||||||
read_memorymap(&tag, tag.size, data + 2);
|
read_memorymap(shim_info, size, data + 2);
|
||||||
break;
|
break;
|
||||||
case MBOOT_SYMBOLS:
|
case MBOOT_SYMBOLS:
|
||||||
// TODO:
|
// TODO:
|
||||||
goto done;
|
break;
|
||||||
case MBOOT_XSDP:
|
case MBOOT_XSDP:
|
||||||
read_xsdp(&tag, (char *) (data + 2));
|
read_xsdp(shim_info, (char *) (data + 2));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
goto done;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
done:
|
if(size % 8 != 0) {
|
||||||
|
size += 8 - (size % 8);
|
||||||
info->tags[tag.type] = tag;
|
|
||||||
|
|
||||||
if(tag.size % 8 != 0) {
|
|
||||||
tag.size += 8 - (tag.size % 8);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return data + tag.size / sizeof(uint32_t);
|
return data + size / sizeof(uint32_t);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct mboot_info mboot_load_info(const void *mboot_info) {
|
void mboot_load_info(
|
||||||
struct mboot_info info = {0};
|
const void *mboot_info,
|
||||||
|
struct boot_info *shim_info
|
||||||
|
) {
|
||||||
uint32_t* data = (uint32_t*) mboot_info;
|
uint32_t* data = (uint32_t*) mboot_info;
|
||||||
info.total_size = *data++;
|
uint32_t total_size = *data++;
|
||||||
info.reserved = *data++;
|
data++; //reserved
|
||||||
|
|
||||||
|
|
||||||
while((uint8_t*) data < (uint8_t*) mboot_info + info.total_size) {
|
while((uint8_t*) data < (uint8_t*) mboot_info + total_size) {
|
||||||
data = read_tag(&info, data);
|
data = read_tag(data, shim_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct mboot_tag *mboot_get_tag(struct mboot_info *info, enum mboot_tag_type type) {
|
|
||||||
if (info->tags[type].valid) {
|
|
||||||
return &info->tags[type];
|
|
||||||
} else {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,44 +1,11 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "memory.h"
|
#include <shim.h>
|
||||||
#include <stdint.h>
|
#include <memory.h>
|
||||||
|
|
||||||
#define CMDLINE_MAX 32
|
|
||||||
|
|
||||||
struct mboot_tag {
|
|
||||||
uint8_t valid; // if the tag at this location is set
|
|
||||||
uint32_t type;
|
|
||||||
uint32_t size;
|
|
||||||
union {
|
|
||||||
char cmdline[CMDLINE_MAX + 1];
|
|
||||||
struct memory_map *memory_map;
|
|
||||||
void *rootsdp;
|
|
||||||
} data;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum mboot_tag_type {
|
|
||||||
MBOOT_CMDLINE = 0,
|
|
||||||
MBOOT_MEMORYMAP = 6,
|
|
||||||
MBOOT_SYMBOLS = 9,
|
|
||||||
MBOOT_XSDP = 14
|
|
||||||
};
|
|
||||||
|
|
||||||
struct mboot_info {
|
|
||||||
uint32_t total_size;
|
|
||||||
uint32_t reserved;
|
|
||||||
struct mboot_tag tags[22];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the multi boot information
|
* Loads the multi boot information
|
||||||
* @param mboot_info - the pointer passed from multiboot2
|
* @param mboot_info - the pointer passed from multiboot2
|
||||||
|
* @param shim_info - the info to be collected by shim
|
||||||
*/
|
*/
|
||||||
struct mboot_info mboot_load_info(const void *mboot_info);
|
void mboot_load_info(const void *mboot_info, struct boot_info *shim_info);
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a tag from multiboot
|
|
||||||
* @param type - the tag type
|
|
||||||
* @returns NULL - tag not loaded
|
|
||||||
* @returns tag - tag was loaded
|
|
||||||
*/
|
|
||||||
struct mboot_tag *mboot_get_tag(struct mboot_info *info, enum mboot_tag_type type);
|
|
||||||
|
|
|
@ -12,22 +12,14 @@
|
||||||
static struct boot_info boot_info;
|
static struct boot_info boot_info;
|
||||||
|
|
||||||
void* amd64_shim(void *mboot_data_ptr) {
|
void* amd64_shim(void *mboot_data_ptr) {
|
||||||
|
|
||||||
serial_init();
|
serial_init();
|
||||||
paging_init();
|
paging_init();
|
||||||
pic_remap();
|
pic_remap();
|
||||||
idt_init();
|
idt_init();
|
||||||
|
|
||||||
//kmap_page(mboot_data_ptr, mboot_data_ptr, F_WRITEABLE);
|
mboot_load_info(mboot_data_ptr, &boot_info);
|
||||||
|
|
||||||
struct mboot_info mboot_info;
|
|
||||||
mboot_info = mboot_load_info(mboot_data_ptr);
|
|
||||||
|
|
||||||
struct mboot_tag *map_tag;
|
|
||||||
map_tag = mboot_get_tag(&mboot_info, MBOOT_MEMORYMAP);
|
|
||||||
|
|
||||||
boot_info.map = map_tag->data.memory_map;
|
|
||||||
|
|
||||||
return &boot_info;
|
return &boot_info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
10
src/kmain.c
10
src/kmain.c
|
@ -1,3 +1,4 @@
|
||||||
|
#include "acpi.h"
|
||||||
#include <memory.h>
|
#include <memory.h>
|
||||||
#include <lib.h>
|
#include <lib.h>
|
||||||
#include <serial.h>
|
#include <serial.h>
|
||||||
|
@ -30,10 +31,19 @@ void print_memory() {
|
||||||
|
|
||||||
void kmain(struct boot_info *info) {
|
void kmain(struct boot_info *info) {
|
||||||
memory_init(info->map);
|
memory_init(info->map);
|
||||||
|
|
||||||
|
int i = acpi_init(info->acpi_table);
|
||||||
|
char buf[20];
|
||||||
|
ltoa(i, buf, 10);
|
||||||
|
serial_out_str("acpi: ");
|
||||||
|
serial_out_str(buf);
|
||||||
|
serial_out_str("\n");
|
||||||
|
|
||||||
serial_out_str("entered kmain\n");
|
serial_out_str("entered kmain\n");
|
||||||
*(char*)(0xB8000 + 0x144) = 'h';
|
*(char*)(0xB8000 + 0x144) = 'h';
|
||||||
*(char*)(0xB8000 + 0x146) = 'i';
|
*(char*)(0xB8000 + 0x146) = 'i';
|
||||||
//fb_init(1024, 768);
|
//fb_init(1024, 768);
|
||||||
|
acpi_shutdown();
|
||||||
while (1) {
|
while (1) {
|
||||||
// loop so we dont halt
|
// loop so we dont halt
|
||||||
// this allows interrupts to fire
|
// this allows interrupts to fire
|
||||||
|
|
Loading…
Reference in a new issue