Skip to content

Commit e9d0d6c

Browse files
committed
lesson 21, keyboard input and shell
1 parent 5aaabf1 commit e9d0d6c

File tree

25 files changed

+1217
-3
lines changed

25 files changed

+1217
-3
lines changed

20-interrupts-timer/Makefile

Lines changed: 0 additions & 1 deletion
This file was deleted.

20-interrupts-timer/Makefile

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
C_SOURCES = $(wildcard kernel/*.c drivers/*.c cpu/*.c libc/*.c)
2+
HEADERS = $(wildcard kernel/*.h drivers/*.h cpu/*.h libc/*.h)
3+
# Nice syntax for file extension replacement
4+
OBJ = ${C_SOURCES:.c=.o cpu/interrupt.o}
5+
6+
# Change this if your cross-compiler is somewhere else
7+
CC = /usr/local/i386elfgcc/bin/i386-elf-gcc
8+
GDB = /usr/local/i386elfgcc/bin/i386-elf-gdb
9+
# -g: Use debugging symbols in gcc
10+
CFLAGS = -g
11+
12+
# First rule is run by default
13+
os-image.bin: boot/bootsect.bin kernel.bin
14+
cat $^ > os-image.bin
15+
16+
# '--oformat binary' deletes all symbols as a collateral, so we don't need
17+
# to 'strip' them manually on this case
18+
kernel.bin: boot/kernel_entry.o ${OBJ}
19+
i386-elf-ld -o $@ -Ttext 0x1000 $^ --oformat binary
20+
21+
# Used for debugging purposes
22+
kernel.elf: boot/kernel_entry.o ${OBJ}
23+
i386-elf-ld -o $@ -Ttext 0x1000 $^
24+
25+
run: os-image.bin
26+
qemu-system-i386 -fda os-image.bin
27+
28+
# Open the connection to qemu and load our kernel-object file with symbols
29+
debug: os-image.bin kernel.elf
30+
qemu-system-i386 -s -fda os-image.bin -d guest_errors,int &
31+
${GDB} -ex "target remote localhost:1234" -ex "symbol-file kernel.elf"
32+
33+
# Generic rules for wildcards
34+
# To make an object, always compile from its .c
35+
%.o: %.c ${HEADERS}
36+
${CC} ${CFLAGS} -ffreestanding -c $< -o $@
37+
38+
%.o: %.asm
39+
nasm $< -f elf -o $@
40+
41+
%.bin: %.asm
42+
nasm $< -f bin -o $@
43+
44+
clean:
45+
rm -rf *.bin *.dis *.o os-image.bin *.elf
46+
rm -rf kernel/*.o boot/*.bin drivers/*.o boot/*.o cpu/*.o

20-interrupts-timer/drivers/screen.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
#include "screen.h"
2-
#include "ports.h"
3-
#include "../kernel/util.h"
2+
#include "../drivers/ports.h"
43

54
/* Declaration of private functions */
65
int get_cursor_offset();

21-shell/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../20-interrupts-timer/Makefile

21-shell/README.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
2+
**Goal: Clean the code a bit and parse user input**
3+
4+
In this lesson we will do tho things. First, we will clean up the code a bit, so it is ready
5+
for further lessons. During the previous ones I tried to put things in the most predictable places,
6+
but it is also a good exercise to know when the code base is growing and adapt it to current
7+
and further needs.
8+
9+
10+
Code cleaning
11+
-------------
12+
13+
First of all, we will quickly start to need more utility functions
14+
for handling strings and so on. In a regular OS, this is called the C library,
15+
or libc for short.
16+
17+
Right now we have a `utils.c` which we will split into `mem.c` and `string.c`, with their respective headers.
18+
19+
Second, we will create a new function `irq_install()` so that the kernel
20+
only needs to perform one call to initialize all the IRQs. That function
21+
is akin to `isr_install()` and placed on the same `irq.c`.
22+
While we're here, we will disable the `kprint()` on `timer_callback()`
23+
to avoid filling the screen with junk, now that we know that it works
24+
properly.
25+
26+
There is not a clear distinction between `cpu/` and `drivers/`.
27+
Keep in mind that I'm
28+
creating this tutorial while following many others, and each of them
29+
has a distinct folder structure. The only change we will do for now is to
30+
move `drivers/ports.*` into `cpu/` since it is clearly cpu-dependent code.
31+
`boot/` is also CPU-dependent code, but we will not mess with it until
32+
we implement the boot sequence for a different machine.
33+
34+
35+
Keyboard characters
36+
-------------------
37+
38+
How to access the typed characters, then?
39+
40+
- When a key is pressed, the callback gets the ASCII code via a new
41+
arrays which are defined at the beginning of `keyboard.c`
42+
- The callback then appends that character to a buffer, `key_buffer`
43+
- It is also printed on the screen
44+
- When the OS wants to read user input, it calls `libc/io.c:readline()`
45+
46+
`keyboard.c` also parses backspace, by removing the last element
47+
of the key buffer, and deleting it from the screen, by calling
48+
`screen.c:kprint_backspace()`. For this we needed to modify a bit
49+
`print_char()` to not advance the offset when printing a backspace
50+
51+
52+
Responding to user input
53+
------------------------
54+
55+
The keyboard callback checks for a newline, and then calls the kernel,
56+
telling it that the user has input something. Out final libc function
57+
is `strcmp()`, which compares two strings and returns 0 if they
58+
are equal. If the user inputs "END", we halt the CPU.
59+
60+
This is the most basic shell ever, but you should be proud, because
61+
we implemented it from scratch. Do you realize how cool this is?
62+
63+
If you want to, expand `kernel.c` to parse more stuff. In the future,
64+
when we have a filesystem, we will allow the user to run some basic commands.

21-shell/boot

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../20-interrupts-timer/boot

21-shell/cpu/idt.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#include "idt.h"
2+
3+
void set_idt_gate(int n, u32 handler) {
4+
idt[n].low_offset = low_16(handler);
5+
idt[n].sel = KERNEL_CS;
6+
idt[n].always0 = 0;
7+
idt[n].flags = 0x8E;
8+
idt[n].high_offset = high_16(handler);
9+
}
10+
11+
void set_idt() {
12+
idt_reg.base = (u32) &idt;
13+
idt_reg.limit = IDT_ENTRIES * sizeof(idt_gate_t) - 1;
14+
/* Don't make the mistake of loading &idt -- always load &idt_reg */
15+
__asm__ __volatile__("lidtl (%0)" : : "r" (&idt_reg));
16+
}

21-shell/cpu/idt.h

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#ifndef IDT_H
2+
#define IDT_H
3+
4+
#include "types.h"
5+
6+
/* Segment selectors */
7+
#define KERNEL_CS 0x08
8+
9+
/* How every interrupt gate (handler) is defined */
10+
typedef struct {
11+
u16 low_offset; /* Lower 16 bits of handler function address */
12+
u16 sel; /* Kernel segment selector */
13+
u8 always0;
14+
/* First byte
15+
* Bit 7: "Interrupt is present"
16+
* Bits 6-5: Privilege level of caller (0=kernel..3=user)
17+
* Bit 4: Set to 0 for interrupt gates
18+
* Bits 3-0: bits 1110 = decimal 14 = "32 bit interrupt gate" */
19+
u8 flags;
20+
u16 high_offset; /* Higher 16 bits of handler function address */
21+
} __attribute__((packed)) idt_gate_t ;
22+
23+
/* A pointer to the array of interrupt handlers.
24+
* Assembly instruction 'lidt' will read it */
25+
typedef struct {
26+
u16 limit;
27+
u32 base;
28+
} __attribute__((packed)) idt_register_t;
29+
30+
#define IDT_ENTRIES 256
31+
idt_gate_t idt[IDT_ENTRIES];
32+
idt_register_t idt_reg;
33+
34+
35+
/* Functions implemented in idt.c */
36+
void set_idt_gate(int n, u32 handler);
37+
void set_idt();
38+
39+
#endif

0 commit comments

Comments
 (0)