Added most things needed for interrupts
This commit is contained in:
parent
ba6f8a3498
commit
fc95fb5ff6
59
Makefile
59
Makefile
|
@ -1,23 +1,33 @@
|
||||||
ARCHDIR=arch/i686
|
ARCHDIR=arch/i686
|
||||||
|
|
||||||
CFLAGS?=
|
CFLAGS?=
|
||||||
|
ASFLAGS?=
|
||||||
|
|
||||||
INCLUDEDIR=include
|
INCLUDEDIR=include
|
||||||
|
|
||||||
CFLAGS:=-O2 \
|
CFLAGS:=\
|
||||||
|
-O2 \
|
||||||
-std=gnu99 \
|
-std=gnu99 \
|
||||||
-ffreestanding \
|
-ffreestanding \
|
||||||
-Wall -Wextra \
|
-Wall -Wextra \
|
||||||
-I$(INCLUDEDIR) \
|
-I$(INCLUDEDIR) \
|
||||||
$(CFLAGS)
|
$(CFLAGS)
|
||||||
|
|
||||||
|
ASFLAGS:=\
|
||||||
|
-I$(INCLUDEDIR) \
|
||||||
|
-I$(ARCHDIR) \
|
||||||
|
$(ASFLAGS)
|
||||||
|
|
||||||
|
KERNEL_OBJS=\
|
||||||
|
kernel/kernel.o \
|
||||||
|
kernel/debugging.o \
|
||||||
|
|
||||||
KERNEL_OBJS=kernel/kernel.o
|
|
||||||
LIB_OBJS=\
|
LIB_OBJS=\
|
||||||
$(ARCHDIR)/bootstrap.o \
|
$(ARCHDIR)/bootstrap.o \
|
||||||
$(ARCHDIR)/tty.o\
|
$(ARCHDIR)/tty.o \
|
||||||
$(ARCHDIR)/strlib.o\
|
$(ARCHDIR)/strlib.o \
|
||||||
$(ARCHDIR)/interrupt.o\
|
$(ARCHDIR)/interrupt.o \
|
||||||
|
$(ARCHDIR)/gdt.o \
|
||||||
|
|
||||||
OBJS=$(KERNEL_OBJS) $(LIB_OBJS)
|
OBJS=$(KERNEL_OBJS) $(LIB_OBJS)
|
||||||
|
|
||||||
|
@ -27,40 +37,37 @@ LDFLAGS=$(OBJS) \
|
||||||
-nostdlib \
|
-nostdlib \
|
||||||
-lgcc
|
-lgcc
|
||||||
|
|
||||||
|
.PHONY: all clean myos.iso
|
||||||
.SUFFIXES: .c .o .s
|
.SUFFIXES: .c .o .s
|
||||||
|
|
||||||
all:
|
all: myos.iso
|
||||||
$(MAKE) run-grub-qemu
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(OBJS)
|
rm -f $(OBJS)
|
||||||
rm myos.bin
|
rm -f myos.kernel
|
||||||
rm myos.iso
|
rm -f myos.iso
|
||||||
|
rm -f isodir/boot/myos.kernel
|
||||||
debug:
|
|
||||||
mkdir build || true
|
|
||||||
cp Makefile build
|
|
||||||
cp -r isodir build
|
|
||||||
CFLAGS=-g $(MAKE) -C build debug-grub-qemu
|
|
||||||
|
|
||||||
.c.o:
|
.c.o:
|
||||||
i686-elf-gcc -c $< -o $@ $(CFLAGS)
|
i686-elf-gcc -c $< -o $@ $(CFLAGS)
|
||||||
|
|
||||||
.s.o:
|
.s.o:
|
||||||
i686-elf-as $< -o $@
|
i686-elf-as $< -o $@ $(ASFLAGS)
|
||||||
|
|
||||||
myos.bin: $(OBJS)
|
myos.kernel: $(OBJS)
|
||||||
i686-elf-gcc -T $(ARCHDIR)/linker.ld -o myos.bin $(LDFLAGS)
|
i686-elf-gcc -T $(ARCHDIR)/linker.ld -o myos.kernel $(LDFLAGS)
|
||||||
grub-file --is-x86-multiboot myos.bin
|
grub-file --is-x86-multiboot myos.kernel
|
||||||
|
|
||||||
run-grub-qemu: myos.bin
|
myos.iso: myos.kernel
|
||||||
cp myos.bin isodir/boot/myos.bin
|
cp myos.kernel isodir/boot/myos.kernel
|
||||||
grub-mkrescue -o myos.iso isodir
|
grub-mkrescue -o myos.iso isodir
|
||||||
qemu-system-i386 myos.iso
|
|
||||||
|
|
||||||
debug-grub-qemu: myos.bin
|
debug: CFLAGS += -g -O0
|
||||||
cp myos.bin isodir/boot/myos.bin
|
debug: ASFLAGS += -g
|
||||||
grub-mkrescue -o myos.iso isodir
|
debug: all
|
||||||
qemu-system-i386 -S -gdb tcp::9000 myos.iso
|
|
||||||
|
|
||||||
|
run: myos.iso
|
||||||
|
qemu-system-i386 myos.iso -display curses
|
||||||
|
|
||||||
|
run-debug: debug
|
||||||
|
qemu-system-i386 -S -gdb tcp::9000 myos.iso -display curses
|
||||||
|
|
|
@ -65,6 +65,20 @@ _start:
|
||||||
*/
|
*/
|
||||||
mov $stack_top, %esp
|
mov $stack_top, %esp
|
||||||
|
|
||||||
|
cli
|
||||||
|
|
||||||
|
call gdt_init
|
||||||
|
|
||||||
|
movw %ax, 0x10
|
||||||
|
movw %ds, %ax
|
||||||
|
movw %es, %ax
|
||||||
|
movw %fs, %ax
|
||||||
|
movw %gs, %ax
|
||||||
|
movw %ss, %ax
|
||||||
|
# jmp kernel
|
||||||
|
ljmp $0x08, $kernel
|
||||||
|
|
||||||
|
kernel:
|
||||||
/*
|
/*
|
||||||
This is a good place to initialize crucial processor state before the
|
This is a good place to initialize crucial processor state before the
|
||||||
high-level kernel is entered. It's best to minimize the early
|
high-level kernel is entered. It's best to minimize the early
|
||||||
|
@ -84,8 +98,15 @@ _start:
|
||||||
stack since (pushed 0 bytes so far), so the alignment has thus been
|
stack since (pushed 0 bytes so far), so the alignment has thus been
|
||||||
preserved and the call is well defined.
|
preserved and the call is well defined.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
mov %esp, %edx
|
||||||
|
|
||||||
call kernel_main
|
call kernel_main
|
||||||
|
|
||||||
|
.global keyboard_test
|
||||||
|
keyboard_test:
|
||||||
|
hlt
|
||||||
|
|
||||||
/*
|
/*
|
||||||
If the system has nothing more to do, put the computer into an
|
If the system has nothing more to do, put the computer into an
|
||||||
infinite loop. To do that:
|
infinite loop. To do that:
|
||||||
|
@ -106,4 +127,5 @@ _start:
|
||||||
Set the size of the _start symbol to the current location '.' minus its start.
|
Set the size of the _start symbol to the current location '.' minus its start.
|
||||||
This is useful when debugging or when you implement call tracing.
|
This is useful when debugging or when you implement call tracing.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.size _start, . - _start
|
.size _start, . - _start
|
||||||
|
|
120
arch/i686/gdt.c
Normal file
120
arch/i686/gdt.c
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
#include <kernel/tty.h>
|
||||||
|
#include <debugging.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#define NUM_ENTRIES 3
|
||||||
|
|
||||||
|
struct SegmentDescriptor {
|
||||||
|
uint32_t limit;
|
||||||
|
uint32_t base;
|
||||||
|
uint8_t access_byte;
|
||||||
|
uint8_t flags;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GDT {
|
||||||
|
uint16_t size;
|
||||||
|
uint64_t* base;
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
static uint64_t entries[NUM_ENTRIES];
|
||||||
|
static size_t current_entry = 0;
|
||||||
|
struct GDT gdt;
|
||||||
|
|
||||||
|
static void encodeGdtEntry(uint8_t *target, struct SegmentDescriptor source)
|
||||||
|
{
|
||||||
|
// Check the limit to make sure that it can be encoded
|
||||||
|
if (source.limit > 0xFFFFF) {
|
||||||
|
terminal_writestring("WARNING: GDT entry has a limit (");
|
||||||
|
print_hex_bytes(&source.limit, 4);
|
||||||
|
terminal_writestring(") that is larger than accepted.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode the limit
|
||||||
|
target[0] = source.limit & 0xFF;
|
||||||
|
target[1] = (source.limit >> 8) & 0xFF;
|
||||||
|
target[6] = (source.limit >> 16) & 0x0F;
|
||||||
|
|
||||||
|
// Encode the base
|
||||||
|
target[2] = source.base & 0xFF;
|
||||||
|
target[3] = (source.base >> 8) & 0xFF;
|
||||||
|
target[4] = (source.base >> 16) & 0xFF;
|
||||||
|
target[7] = (source.base >> 24) & 0xFF;
|
||||||
|
|
||||||
|
// Encode the access byte
|
||||||
|
target[5] = source.access_byte;
|
||||||
|
|
||||||
|
// Encode the flags
|
||||||
|
target[6] |= (source.flags << 4);
|
||||||
|
|
||||||
|
print_hex_bytes(target, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_entry(struct SegmentDescriptor entry)
|
||||||
|
{
|
||||||
|
if (current_entry == NUM_ENTRIES) {
|
||||||
|
terminal_writestring("WARNING: More GDT entries than NUM_ENTRIES.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t descriptor = 0;
|
||||||
|
encodeGdtEntry((uint8_t*) &descriptor, entry);
|
||||||
|
|
||||||
|
entries[current_entry] = descriptor;
|
||||||
|
|
||||||
|
current_entry++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void load_gdt()
|
||||||
|
{
|
||||||
|
gdt.size = NUM_ENTRIES * 8 - 1;
|
||||||
|
gdt.base = entries;
|
||||||
|
|
||||||
|
for (int i = 0; i < NUM_ENTRIES; i++) {
|
||||||
|
print_hex_bytes(gdt.base + i, 8);
|
||||||
|
terminal_putchar('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
asm ( "lgdt %0" : : "m"(gdt) );
|
||||||
|
}
|
||||||
|
|
||||||
|
void gdt_init()
|
||||||
|
{
|
||||||
|
terminal_initialize();
|
||||||
|
struct SegmentDescriptor null;
|
||||||
|
null.limit = 0;
|
||||||
|
null.base = 0;
|
||||||
|
null.access_byte = 0;
|
||||||
|
null.flags = 0;
|
||||||
|
|
||||||
|
terminal_writestring("Kernel Null: ");
|
||||||
|
|
||||||
|
add_entry(null);
|
||||||
|
|
||||||
|
terminal_putchar('\n');
|
||||||
|
|
||||||
|
struct SegmentDescriptor kernel_code;
|
||||||
|
kernel_code.limit = 0x000003FF;
|
||||||
|
kernel_code.base = 0x00000000;
|
||||||
|
kernel_code.access_byte = 0b10011010;
|
||||||
|
kernel_code.flags = 0b1100;
|
||||||
|
|
||||||
|
terminal_writestring("Kernel Code: ");
|
||||||
|
|
||||||
|
add_entry(kernel_code);
|
||||||
|
terminal_putchar('\n');
|
||||||
|
|
||||||
|
struct SegmentDescriptor kernel_data;
|
||||||
|
kernel_code.limit = 0x000003FF;
|
||||||
|
kernel_data.base = 0x00400000;
|
||||||
|
kernel_data.access_byte = 0b10010010;
|
||||||
|
kernel_data.flags = 0b1100;
|
||||||
|
|
||||||
|
terminal_writestring("Kernel Data: ");
|
||||||
|
|
||||||
|
add_entry(kernel_data);
|
||||||
|
terminal_putchar('\n');
|
||||||
|
|
||||||
|
load_gdt();
|
||||||
|
}
|
|
@ -1,16 +1,88 @@
|
||||||
#include <kernel/tty.h>
|
#include <kernel/tty.h>
|
||||||
#include <kernel/interrupt.h>
|
#include <debugging.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
const uint16_t NUM_ID = 0;
|
#define NUM_ENTRIES 0
|
||||||
|
|
||||||
void load_idt(struct InterruptDescriptorTable *idt)
|
struct InterruptDescriptor {
|
||||||
|
uint32_t offset;
|
||||||
|
uint32_t selector;
|
||||||
|
uint8_t gate_type;
|
||||||
|
uint8_t ring;
|
||||||
|
bool present;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDT {
|
||||||
|
uint16_t size;
|
||||||
|
uint64_t* base;
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
static uint64_t entries[NUM_ENTRIES];
|
||||||
|
static size_t current_entry = 0;
|
||||||
|
struct IDT idt;
|
||||||
|
|
||||||
|
static void encodeIdtEntry(uint8_t *target, struct InterruptDescriptor source)
|
||||||
{
|
{
|
||||||
asm("lidt %0" : : "m"(idt));
|
// Encode the present bit
|
||||||
|
if (source.present) {
|
||||||
|
target[5] |= 0xF0;
|
||||||
|
} else {
|
||||||
|
// Not present, return
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode the offset
|
||||||
|
target[0] = (source.offset >> 0x00) & 0xFF;
|
||||||
|
target[1] = (source.offset >> 0x08) & 0xFF;
|
||||||
|
target[6] = (source.offset >> 0x10) & 0xFF;
|
||||||
|
target[7] = (source.offset >> 0x18) & 0xFF;
|
||||||
|
|
||||||
|
// Encode the base
|
||||||
|
target[2] = (source.selector >> 0x00) & 0xFF;
|
||||||
|
target[3] = (source.selector >> 0x08) & 0xFF;
|
||||||
|
|
||||||
|
// Encode the gate type
|
||||||
|
target[5] |= source.gate_type;
|
||||||
|
|
||||||
|
// Encode the DPL
|
||||||
|
target[5] |= (source.ring >> 5);
|
||||||
|
|
||||||
|
print_hex_bytes(target, 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
void interrupt_initialize(struct InterruptDescriptorTable IDT)
|
static void add_entry(struct InterruptDescriptor entry)
|
||||||
{
|
{
|
||||||
terminal_writestring("Loading IDT\n");
|
if (current_entry == NUM_ENTRIES) {
|
||||||
load_idt(&IDT);
|
terminal_writestring("WARNING: More IDT entries than NUM_ENTRIES.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t descriptor = 0;
|
||||||
|
encodeIdtEntry((uint8_t*) &descriptor, entry);
|
||||||
|
|
||||||
|
entries[current_entry] = descriptor;
|
||||||
|
|
||||||
|
current_entry++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void load_idt()
|
||||||
|
{
|
||||||
|
idt.size = NUM_ENTRIES * 8 - 1;
|
||||||
|
idt.base = entries;
|
||||||
|
|
||||||
|
for (int i = 0; i < NUM_ENTRIES; i++) {
|
||||||
|
print_hex_bytes(idt.base + i, 8);
|
||||||
|
terminal_putchar('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
asm ( "lidt %0" : : "m"(idt) );
|
||||||
|
}
|
||||||
|
|
||||||
|
void idt_init()
|
||||||
|
{
|
||||||
|
terminal_writestring("TODO: Add interrupts");
|
||||||
|
terminal_putchar('\n');
|
||||||
|
|
||||||
|
// load_idt();
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,14 +22,14 @@ SECTIONS
|
||||||
/* First put the multiboot header, as it is required to be put very early
|
/* First put the multiboot header, as it is required to be put very early
|
||||||
in the image or the bootloader won't recognize the file format.
|
in the image or the bootloader won't recognize the file format.
|
||||||
Next we'll put the .text section. */
|
Next we'll put the .text section. */
|
||||||
.text BLOCK(4K) : ALIGN(4K)
|
.text BLOCK (4K) : ALIGN(4K)
|
||||||
{
|
{
|
||||||
*(.multiboot)
|
*(.multiboot)
|
||||||
*(.text)
|
*(.text)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Read-only data. */
|
/* Read-only data. */
|
||||||
.rodata BLOCK(4K) : ALIGN(4K)
|
.rodata BLOCK(4K) : ALIGN(4M)
|
||||||
{
|
{
|
||||||
*(.rodata)
|
*(.rodata)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,6 @@
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <strlib.h>
|
#include <strlib.h>
|
||||||
|
|
||||||
/* Check if the compiler thinks you are targeting the wrong operating system. */
|
|
||||||
#if defined(__linux__)
|
|
||||||
#error "You are not using a cross-compiler, you will most certainly run into trouble"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* This tutorial will only work for the 32-bit ix86 targets. */
|
|
||||||
#if !defined(__i386__)
|
#if !defined(__i386__)
|
||||||
#error "This tutorial needs to be compiled with a ix86-elf compiler"
|
#error "This tutorial needs to be compiled with a ix86-elf compiler"
|
||||||
#endif
|
#endif
|
||||||
|
|
8
include/debugging.h
Normal file
8
include/debugging.h
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
char get_last_key_pressed();
|
||||||
|
void print_hex_digit(uint8_t digit);
|
||||||
|
void print_hex_byte(uint8_t byte);
|
||||||
|
void print_hex_bytes(void* bytes, size_t len);
|
|
@ -1,14 +1,4 @@
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
struct InterruptDescriptor
|
void idt_init();
|
||||||
{
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
struct InterruptDescriptorTable
|
|
||||||
{
|
|
||||||
uint16_t limit; /* Size of IDT array - 1 */
|
|
||||||
struct InterruptDescriptor* base; /* Pointer to IDT array */
|
|
||||||
} __attribute__((packed));
|
|
||||||
|
|
||||||
void interrupt_initialize(struct InterruptDescriptorTable IDT);
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
menuentry "myos" {
|
menuentry "myos" {
|
||||||
multiboot /boot/myos.bin
|
multiboot /boot/myos.kernel
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
|
@ -13,9 +13,10 @@ static inline uint8_t inb(uint16_t port)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_in_shift;
|
static bool is_in_shift;
|
||||||
|
|
||||||
static unsigned char keyboard_char(unsigned char scancode) {
|
static unsigned char keyboard_char(unsigned char scancode)
|
||||||
|
{
|
||||||
if (0x01 < scancode && scancode < 0x0B) {
|
if (0x01 < scancode && scancode < 0x0B) {
|
||||||
return scancode - 2 + '1';
|
return scancode - 2 + '1';
|
||||||
}
|
}
|
||||||
|
@ -118,7 +119,8 @@ static unsigned char keyboard_char(unsigned char scancode) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
char get_last_key_pressed() {
|
char get_last_key_pressed()
|
||||||
|
{
|
||||||
char scancode = inb(0x60);
|
char scancode = inb(0x60);
|
||||||
|
|
||||||
if (is_in_shift) {
|
if (is_in_shift) {
|
||||||
|
@ -128,7 +130,8 @@ char get_last_key_pressed() {
|
||||||
return to_lower_char(keyboard_char(scancode));
|
return to_lower_char(keyboard_char(scancode));
|
||||||
}
|
}
|
||||||
|
|
||||||
void print_hex_digit(uint8_t digit) {
|
void print_hex_digit(uint8_t digit)
|
||||||
|
{
|
||||||
digit = digit & 0xf;
|
digit = digit & 0xf;
|
||||||
if (digit < 0xA) {
|
if (digit < 0xA) {
|
||||||
terminal_putchar(digit + 0x30);
|
terminal_putchar(digit + 0x30);
|
||||||
|
@ -137,10 +140,19 @@ void print_hex_digit(uint8_t digit) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void print_hex_byte(uint8_t byte) {
|
void print_hex_byte(uint8_t byte)
|
||||||
|
{
|
||||||
int upper = byte >> 4;
|
int upper = byte >> 4;
|
||||||
int lower = byte & 0x0F;
|
int lower = byte & 0x0F;
|
||||||
|
|
||||||
print_hex_digit(upper);
|
print_hex_digit(upper);
|
||||||
print_hex_digit(lower);
|
print_hex_digit(lower);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void print_hex_bytes(void* bytes, size_t len)
|
||||||
|
{
|
||||||
|
uint8_t* value = bytes;
|
||||||
|
for (size_t i = len; i > 0; i--) {
|
||||||
|
print_hex_byte(value[i - 1]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,20 @@
|
||||||
#include <kernel/tty.h>
|
#include <kernel/tty.h>
|
||||||
#include <kernel/interrupt.h>
|
#include <kernel/interrupt.h>
|
||||||
#include <debugging.c>
|
#include <debugging.h>
|
||||||
|
|
||||||
|
/* Check if the compiler thinks you are targeting the wrong operating system. */
|
||||||
|
#if defined(__linux__)
|
||||||
|
#error "You are not using a cross-compiler, you will most certainly run into trouble"
|
||||||
|
#endif
|
||||||
|
|
||||||
void kernel_main(void)
|
void kernel_main(void)
|
||||||
{
|
{
|
||||||
/* Initialize terminal interface */
|
/* Initialize terminal interface */
|
||||||
terminal_initialize();
|
terminal_initialize();
|
||||||
|
|
||||||
|
// TODO: Add interrupts
|
||||||
|
// idt_init();
|
||||||
|
|
||||||
terminal_putchar(get_last_key_pressed());
|
terminal_putchar(get_last_key_pressed());
|
||||||
|
|
||||||
char current_character = get_last_key_pressed();
|
char current_character = get_last_key_pressed();
|
||||||
|
@ -17,11 +25,4 @@ void kernel_main(void)
|
||||||
}
|
}
|
||||||
current_character = tmp;
|
current_character = tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct InterruptDescriptorTable IDTR = {
|
|
||||||
|
|
||||||
};
|
|
||||||
interrupt_initialize(IDTR);
|
|
||||||
|
|
||||||
terminal_writestring("Hello World!");
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue