Compare commits

...

11 Commits

Author SHA1 Message Date
Chris Sanden
a683c00fe7 preparing for PR 2026-04-20 13:06:45 +00:00
Chris Sanden
ac5e94cad5 Building final images and binaries 2026-04-20 12:36:07 +00:00
Chris
5f57b955aa finalising report 2026-04-20 00:24:59 +02:00
Teodor
d4d5665ddc Assignment 6 completed 2026-04-18 22:58:45 +00:00
Teodor
1840342765 Created a basic snake game (not using any OS features yet)v2 2026-04-16 22:41:23 +00:00
Teodor
b55d58bf76 Created a basic snake game (not using any OS features yet) 2026-04-16 22:40:21 +00:00
Chris
af1d383940 small notes edit 2026-04-16 13:43:37 +02:00
Teodor
21cc65beb1 Assignment 5 completed 2026-04-15 23:04:33 +00:00
Chris
1bef0f3bcb adding report fragment 2026-04-15 21:44:14 +02:00
Chris Sanden
9a213dcff2 finished PIT implementation 2026-04-15 15:49:11 +00:00
Chris Sanden
46a91312f4 updated gitignore 2026-04-15 12:53:38 +00:00
25 changed files with 901 additions and 418 deletions

6
.gitignore vendored
View File

@@ -19,4 +19,8 @@ src/group_name/src/kernel.c
devcontainer.json
.devcontainer/devcontainer.json
docs/
assignment_files/
assignment_files/
build/OSDev_18/Makefile
build/OSDev_18/compile_commands.json
build/OSDev_18/cmake_install.cmake
src/OSDev_18/notes/

View File

@@ -1,54 +0,0 @@
# Install script for directory: /workspaces/AdvOpSys/src/OSDev_18
# Set the install prefix
if(NOT DEFINED CMAKE_INSTALL_PREFIX)
set(CMAKE_INSTALL_PREFIX "/usr/local")
endif()
string(REGEX REPLACE "/$" "" CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}")
# Set the install configuration name.
if(NOT DEFINED CMAKE_INSTALL_CONFIG_NAME)
if(BUILD_TYPE)
string(REGEX REPLACE "^[^A-Za-z0-9_]+" ""
CMAKE_INSTALL_CONFIG_NAME "${BUILD_TYPE}")
else()
set(CMAKE_INSTALL_CONFIG_NAME "Debug")
endif()
message(STATUS "Install configuration: \"${CMAKE_INSTALL_CONFIG_NAME}\"")
endif()
# Set the component getting installed.
if(NOT CMAKE_INSTALL_COMPONENT)
if(COMPONENT)
message(STATUS "Install component: \"${COMPONENT}\"")
set(CMAKE_INSTALL_COMPONENT "${COMPONENT}")
else()
set(CMAKE_INSTALL_COMPONENT)
endif()
endif()
# Install shared libraries without execute permission?
if(NOT DEFINED CMAKE_INSTALL_SO_NO_EXE)
set(CMAKE_INSTALL_SO_NO_EXE "1")
endif()
# Is this installation the result of a crosscompile?
if(NOT DEFINED CMAKE_CROSSCOMPILING)
set(CMAKE_CROSSCOMPILING "FALSE")
endif()
# Set default install directory permissions.
if(NOT DEFINED CMAKE_OBJDUMP)
set(CMAKE_OBJDUMP "/usr/local/bin/i686-elf-objdump")
endif()
if(CMAKE_INSTALL_COMPONENT)
set(CMAKE_INSTALL_MANIFEST "install_manifest_${CMAKE_INSTALL_COMPONENT}.txt")
else()
set(CMAKE_INSTALL_MANIFEST "install_manifest.txt")
endif()
string(REPLACE ";" "\n" CMAKE_INSTALL_MANIFEST_CONTENT
"${CMAKE_INSTALL_MANIFEST_FILES}")
file(WRITE "/workspaces/AdvOpSys/build/OSDev_18/${CMAKE_INSTALL_MANIFEST}"
"${CMAKE_INSTALL_MANIFEST_CONTENT}")

View File

@@ -1,27 +0,0 @@
[
{
"directory": "/workspaces/AdvOpSys/build/OSDev_18",
"command": "/usr/bin/nasm -I/workspaces/AdvOpSys/src/OSDev_18/include -f elf32 -o CMakeFiles/uiaos-kernel.dir/src/multiboot2.asm.o /workspaces/AdvOpSys/src/OSDev_18/src/multiboot2.asm",
"file": "/workspaces/AdvOpSys/src/OSDev_18/src/multiboot2.asm"
},
{
"directory": "/workspaces/AdvOpSys/build/OSDev_18",
"command": "/usr/bin/nasm -I/workspaces/AdvOpSys/src/OSDev_18/include -f elf32 -o CMakeFiles/uiaos-kernel.dir/src/arch/i386/gdt_flush.asm.o /workspaces/AdvOpSys/src/OSDev_18/src/arch/i386/gdt_flush.asm",
"file": "/workspaces/AdvOpSys/src/OSDev_18/src/arch/i386/gdt_flush.asm"
},
{
"directory": "/workspaces/AdvOpSys/build/OSDev_18",
"command": "/usr/local/bin/i686-elf-gcc -I/workspaces/AdvOpSys/src/OSDev_18/include -g -Wall -Wextra -nostdinc -nostdlib -fno-builtin -fno-stack-protector -fno-stack-check -fno-lto -fPIE -m32 -march=i386 -mno-mmx -mno-sse -mno-sse2 -mno-red-zone -Wno-main -g -Wno-unused-variable -Wno-unused-parameter -std=gnu99 -o CMakeFiles/uiaos-kernel.dir/src/kernel.c.o -c /workspaces/AdvOpSys/src/OSDev_18/src/kernel.c",
"file": "/workspaces/AdvOpSys/src/OSDev_18/src/kernel.c"
},
{
"directory": "/workspaces/AdvOpSys/build/OSDev_18",
"command": "/usr/local/bin/i686-elf-gcc -I/workspaces/AdvOpSys/src/OSDev_18/include -g -Wall -Wextra -nostdinc -nostdlib -fno-builtin -fno-stack-protector -fno-stack-check -fno-lto -fPIE -m32 -march=i386 -mno-mmx -mno-sse -mno-sse2 -mno-red-zone -Wno-main -g -Wno-unused-variable -Wno-unused-parameter -std=gnu99 -o CMakeFiles/uiaos-kernel.dir/src/gdt.c.o -c /workspaces/AdvOpSys/src/OSDev_18/src/gdt.c",
"file": "/workspaces/AdvOpSys/src/OSDev_18/src/gdt.c"
},
{
"directory": "/workspaces/AdvOpSys/build/OSDev_18",
"command": "/usr/local/bin/i686-elf-gcc -I/workspaces/AdvOpSys/src/OSDev_18/include -g -Wall -Wextra -nostdinc -nostdlib -fno-builtin -fno-stack-protector -fno-stack-check -fno-lto -fPIE -m32 -march=i386 -mno-mmx -mno-sse -mno-sse2 -mno-red-zone -Wno-main -g -Wno-unused-variable -Wno-unused-parameter -std=gnu99 -o CMakeFiles/uiaos-kernel.dir/src/terminal.c.o -c /workspaces/AdvOpSys/src/OSDev_18/src/terminal.c",
"file": "/workspaces/AdvOpSys/src/OSDev_18/src/terminal.c"
}
]

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,97 +0,0 @@
# Summary of Work Done on April 10, 2026
## Overview
Today, work was done on the `OSDev_18` project to improve the kernel startup sequence and to make sure the kernel still boots correctly and prints output to the screen. The main focus was to add a minimal Global Descriptor Table (GDT) setup for the i386 version of the kernel and verify that it works as expected.
The project is set up to use a dev container, so the build and test steps were carried out in that environment. This was necessary because the container already includes the required cross-compilation and debugging tools.
## GDT Implementation
A minimal GDT was added for 32-bit protected mode. The implementation uses three entries:
1. A null descriptor
2. A kernel code segment descriptor
3. A kernel data segment descriptor
The code and data segments were configured with:
- base address `0x00000000`
- effective 4 GiB address space
- 4 KiB granularity
- ring 0 privilege level
The GDT setup code builds these descriptors in memory and prepares a GDT descriptor that can be loaded by the CPU.
After that, an assembly routine loads the new GDT with `lgdt`, performs a far jump to reload the code segment register, and then reloads the remaining segment registers for data access. This step is important because loading the GDT alone does not automatically update the segment registers already cached by the processor.
## Kernel Startup Changes
The kernel startup code was updated so that the GDT is initialized before the terminal is set up. After the GDT setup is finished, the terminal initialization still runs and the kernel prints `Hello, World!` to the VGA text buffer.
This means the boot flow now works roughly like this:
1. The bootloader transfers control to the kernel entry point.
2. The kernel starts execution in C code.
3. The GDT is initialized and loaded.
4. Segment registers are reloaded.
5. The VGA terminal is initialized.
6. `Hello, World!` is printed.
7. The kernel enters an infinite halt loop.
## Build System Changes
The build configuration was updated so the kernel now includes both the new C source for the GDT logic and the new assembly source for the reload routine. This ensures that the GDT implementation is compiled and linked into the kernel binary together with the rest of the startup code.
New build artifacts were generated today, including an updated kernel binary and bootable ISO image.
## Documentation Work
The project documentation was also improved today. Short explanations were added to describe:
- the general kernel boot flow
- where the GDT setup happens
- why `lgdt` is not enough on its own
- why a far jump and segment-register reload are required after loading the GDT
This was done to make the code easier to follow and to connect the implementation more clearly to the assignment requirements.
## Verification and Testing
The GDT implementation was tested in two ways.
### 1. Static inspection of the kernel binary
The built kernel binary was inspected to confirm that the expected GDT-related symbols were present and that the generated machine code actually contained:
- a call to load the GDT
- a far jump to selector `0x08`
- reloads of the data segment registers using selector `0x10`
This showed that the expected GDT setup sequence had been compiled into the kernel.
### 2. Runtime debugging in QEMU with GDB
The kernel was then started in QEMU and examined with `gdb-multiarch`. During this session, execution was stopped inside the GDT initialization code and inside the assembly reload routine.
The following results were observed:
- the GDT descriptor size was `0x17`, which matches three 8-byte entries minus 1
- the code segment entry was populated with the expected values
- after the far jump, the `cs` register changed to `0x08`
- after stepping through the segment reload instructions, `ds`, `es`, `fs`, `gs`, and `ss` all became `0x10`
These checks show that the processor did not just compile the code correctly, but also used the new GDT correctly at runtime.
## Current State at the End of the Day
At the end of todays work, the `OSDev_18` kernel:
- builds inside the dev container
- includes a minimal i386 GDT implementation
- loads the GDT during kernel startup
- reloads the code and data segment registers correctly
- still initializes the VGA terminal
- still prints `Hello, World!` after boot
Based on the testing that was done today, the GDT implementation appears to be working correctly for this minimal protected-mode kernel setup.

View File

@@ -1,43 +0,0 @@
# Summary of Work Done on Interrupt System
## Overview
The last 4 days, work was done on implementing interrupt support for the OSDev_18 project. The goal was to allow the kernel to handle both CPU exceptions and hardware interrupts.
This included setting up the Interrupt Descriptor Table (IDT), implementing basic Interrupt Service Routines (ISRs), adding support for hardware interrupts (IRQs), and handling keyboard input. After completing these steps, the system is now able to respond to interrupts and display output when they occur.
## IDT Implementation
The Interrupt Descriptor Table (IDT) was implemented to define how the CPU should respond to interrupts.
A structure for IDT entries was created, storing the handler address, segment selector, and flags. An IDT pointer structure was also defined to hold the base and size of the table.
The IDT is initialized in IdtInitialize() and loaded using the lidt instruction. A helper function is used to set up each descriptor.
## ISR Implementation
Interrupt Service Routines (ISRs) were implemented to handle CPU exceptions.
A Registers structure was created to store the CPU state during an interrupt. The IsrHandler() function uses this information to determine which interrupt occurred and prints a message to the terminal.
Interrupts can also be triggered manually for testing purposes.
## IRQ Implementation
Support for hardware interrupts (IRQs) was added for IRQ0 to IRQ15.
The Programmable Interrupt Controller (PIC) was remapped so that IRQs do not overlap with CPU exceptions. An IrqHandler() function was implemented to handle incoming hardware interrupts and dispatch them to registered handlers.
End-of-interrupt signals are sent to the PIC after handling each interrupt.
## Keyboard Implementation
Keyboard input was implemented using IRQ1.
A keyboard handler reads scancodes from port 0x60 and translates them into ASCII characters using a lookup table. The characters are stored in a buffer and printed to the screen.
This allows the system to respond to user input from the keyboard.
## Conclusion
The system now supports basic interrupt handling, including CPU exceptions and hardware interrupts. This is an important step towards building a more advanced and interactive operating system.

View File

@@ -1,164 +0,0 @@
# Assignment 4 Part 1: Memory Management
## Overview
In this part of the assignment, a basic memory management system was implemented for a simple OS kernel. The goal was to initialize kernel memory, enable paging, and implement dynamic memory allocation using `malloc()` and `free()`.
---
## Implemented Features
### Kernel Memory Initialization
- Used:
```c
extern uint32_t end;
```
to get the end address of the kernel from the linker.
- Implemented:
```c
init_kernel_memory(&end);
```
- Set up:
- `heap_begin`
- `heap_end`
- `pheap_begin` (page-aligned heap)
- `pheap_end`
---
### Paging
- Implemented:
```c
init_paging();
```
- Set up:
- Page directory at `0x400000`
- Page tables starting at `0x404000`
- Identity-mapped:
- `0x00000000 → 0x00000000`
- `0x400000 → 0x400000`
- Enabled paging by modifying `cr0` and `cr3`.
---
### Dynamic Memory Allocation
#### malloc()
- Implemented a simple heap allocator.
- Stores metadata using:
```c
typedef struct {
uint8_t status;
uint32_t size;
} alloc_t;
```
- Supports:
- Sequential allocation
- Reuse of freed blocks
- Zeroes allocated memory using `memset`.
---
#### free()
- Frees memory by marking the block as unused.
- Prevents errors by:
```c
if (!mem) return;
```
- Correctly updates `memory_used`.
---
#### Block Reuse
- When a block is freed, it can be reused by future `malloc()` calls.
- Verified by:
- Freeing a block
- Allocating a smaller block
- Observing same address reused
---
### Page-Aligned Allocation (pmalloc)
- Implemented `pmalloc()` and `pfree()`:
- Allocates memory in 4KB pages
- Uses a descriptor array (`pheap_desc`)
- Located in upper memory region near `0x400000`
---
### Memory Utility Functions
Implemented:
- `memcpy()`
- `memset()`
- `memset16()`
These replace standard library functions in kernel space.
---
### Debug Output
#### Memory Layout
- Implemented:
```c
print_memory_layout();
```
- Displays:
- Memory used
- Memory free
- Heap size
- Heap start/end
- Page heap start/end
#### Allocation Logs
- Outputs allocation details:
```
Allocated X bytes from 0x... to 0x...
Re-allocated X bytes from 0x... to 0x...
```
---
### Hex Output for Addresses
- Implemented:
```c
TerminalWriteHex(uint32_t num);
```
- Ensures correct formatting of memory addresses (base 16)
- Avoids confusion from decimal output
---
## Testing & Verification
### Allocation Test
```c
void* memory1 = malloc(48261);
void* memory2 = malloc(27261);
void* memory3 = malloc(12617);
```
### Free and Reuse Test
```c
free(memory2);
void* memory4 = malloc(1000);
```
### Result
- `memory4` reused the same address as `memory2`
- Confirms:
- `free()` works
- allocator reuses memory correctly
---
## Conclusion
- Successfully implemented a basic kernel memory manager
- Paging is enabled and functioning
- `malloc()` and `free()` work correctly
- Memory reuse is verified
- Debug output is clear and correctly formatted
---
## Next Step
Proceed to **Part 2: PIT and Sleep Functions**

View File

@@ -70,6 +70,9 @@ add_executable(uiaos-kernel
src/malloc.c
src/memutils.c
src/memory.c
src/pit.c
src/songPlayer.c
src/snake.c
)

View File

@@ -6,6 +6,8 @@
#define KEYBOARD_DATA_PORT 0x60
#define KEYBOARD_BUFFER_SIZE 256
char GetLastKeyPressed(void);
void KeyboardHandler(struct Registers* registers);
#endif

View File

@@ -8,9 +8,9 @@ typedef struct {
uint32_t size;
} alloc_t;
void init_kernel_memory(uint32_t* kernel_end);
void InitKernelMemory(uint32_t* kernel_end);
void init_paging(void);
void InitPaging(void);
void paging_map_virtual_to_phys(uint32_t virt, uint32_t phys);
char* pmalloc(size_t size);
@@ -21,6 +21,6 @@ void* memcpy(void* dest, const void* src, size_t num);
void* memset (void* ptr, int value, size_t num);
void* memset16 (void* ptr, uint16_t value, size_t num);
void print_memory_layout(void);
void PrintMemoryLayout(void);
#endif

View File

@@ -0,0 +1,27 @@
#ifndef KERNEL_PIT_H
#define KERNEL_PIT_H
#include <libc/stdint.h>
#include <libc/stdbool.h>
#define PIT_CMD_PORT 0x43
#define PIT_CHANNEL0_PORT 0x40
#define PIT_CHANNEL1_PORT 0x41
#define PIT_CHANNEL2_PORT 0x42
#define PC_SPEAKER_PORT 0x61
#define PIT_DEFAULT_DIVISOR 0x4E20 //20000, gets just shy of 60Hz
#define PIC1_CMD_PORT 0x20
#define PIC1_DATA_PORT 0x21
#define PIT_BASE_FREQ 1193180
#define TARGET_FREQ 1000
#define DIVIDER (PIT_BASE_FREQ / TARGET_FREQ)
#define TICKS_PER_MS (TARGET_FREQ / TARGET_FREQ) // = 1, needed for converting ms into ticks
uint32_t GetCurrentTick(void);
void PitInitialize();
void SleepInterrupt(uint32_t ticks);
void SleepBusy(uint32_t milliseconds);
void SleepTest();
#endif

View File

@@ -26,6 +26,7 @@ enum vga_colour{
uint8_t VgaEntryColour(enum vga_colour fg, enum vga_colour bg);
uint16_t VgaEntry(unsigned char uc, uint8_t color);
void TerminalClear(void);
void TerminalEntryAt(char c, uint8_t colour, size_t x, size_t y);
void TerminalInitialize(void);
void TerminalSetColour(uint8_t colour);
@@ -34,5 +35,7 @@ void TerminalPutChar(char c);
void TerminalWrite(const char* data, size_t size);
void TerminalWriteString(const char* data);
void TerminalWriteUInt(uint32_t num);
void TerminalWriteHex(uint32_t memory);
char TerminalGetChar(void);
#endif

View File

@@ -0,0 +1,72 @@
#ifndef SNAKEAPP_SNAKE_H
#define SNAKEAPP_SNAKE_H
#include <libc/stdint.h>
#define BOARD_SIZE 15
#define SNAKE_MAX_LENGTH (BOARD_SIZE * BOARD_SIZE)
#define GAME_SPEED_MS 500
enum Direction {
UP,
DOWN,
LEFT,
RIGHT
};
enum CollisionType {
NONE,
FOOD,
SELF,
WALL
};
struct SnakeSegment {
int x;
int y;
};
struct Snake {
uint32_t length;
enum Direction direction;
struct SnakeSegment body[SNAKE_MAX_LENGTH];
};
struct Food {
int x;
int y;
};
struct GameState {
char board[BOARD_SIZE][BOARD_SIZE][3];
struct Snake* snake;
struct Food* food;
uint32_t score;
uint32_t rngState;
};
struct GameState* CreateGame(void);
void DestroyGame(struct GameState* game);
void ResetGame(struct GameState* game);
void InitializeBoard(struct GameState* game);
void InitializeSnake(struct Snake* snake);
void InitializeFood(struct Food* food);
uint32_t Random(uint32_t* rngState);
void HandleInput(struct GameState* game, char input);
void PlayFoodSound(void);
void PlayDeathSound(void);
void PlayWinSound(void);
struct SnakeSegment MoveSnake(struct Snake* snake);
void SpawnFood(struct GameState* game);
void AddSegment(struct Snake* snake, int x, int y);
enum CollisionType CheckCollision(struct Snake* snake, struct Food* food);
void DrawBoard(struct GameState* game);
void PlayGame(void);
#endif

View File

@@ -0,0 +1,139 @@
#ifndef SONGAPP_FREQUENCIES_H
#define SONGAPP_FREQUENCIES_H
// Note frequencies (in Hz)
#define C0 16
#define Cs0 17
#define D0 18
#define Ds0 19
#define E0 21
#define F0 22
#define Fs0 23
#define G0 25
#define Gs0 26
#define A0 27
#define As0 29
#define B0 31
#define C1 33
#define Cs1 35
#define D1 37
#define Ds1 39
#define E1 41
#define F1 44
#define Fs1 46
#define G1 49
#define Gs1 52
#define A1 55
#define As1 58
#define B1 62
#define C2 65
#define Cs2 69
#define D2 73
#define Ds2 78
#define E2 82
#define F2 87
#define Fs2 92
#define G2 98
#define Gs2 104
#define A2 110
#define As2 117
#define B2 123
#define C3 131
#define Cs3 139
#define D3 147
#define Ds3 156
#define E3 165
#define F3 175
#define Fs3 185
#define G3 196
#define Gs3 208
#define A3 220
#define As3 233
#define B3 247
#define C4 262
#define Cs4 277
#define D4 294
#define Ds4 311
#define E4 330
#define F4 349
#define Fs4 370
#define G4 392
#define Gs4 415
#define A4 440
#define As4 466
#define B4 494
#define C5 523
#define Cs5 554
#define D5 587
#define Ds5 622
#define E5 659
#define F5 698
#define Fs5 740
#define G5 784
#define Gs5 831
#define A5 880
#define As5 932
#define B5 988
#define C6 1047
#define Cs6 1109
#define D6 1175
#define Ds6 1245
#define E6 1319
#define F6 1397
#define Fs6 1480
#define G6 1568
#define Gs6 1661
#define A6 1760
#define As6 1865
#define B6 1976
#define C7 2093
#define Cs7 2217
#define D7 2349
#define Ds7 2489
#define E7 2637
#define F7 2794
#define Fs7 2960
#define G7 3136
#define Gs7 3322
#define A7 3520
#define As7 3729
#define B7 3951
#define C8 4186
#define Cs8 4435
#define D8 4699
#define Ds8 4978
#define E8 5274
#define F8 5588
#define Fs8 5919
#define G8 6272
#define Gs8 6645
#define A8 7040
#define As8 7459
#define B8 7902
#define C9 8372
#define Cs9 8870
#define D9 9397
#define Ds9 9956
#define E9 10548
#define F9 11175
#define Fs9 11839
#define G9 12543
#define Gs9 13290
#define A9 14080
#define As9 14917
#define B9 15804
#define A_SHARP4 466
#define G_SHARP4 415
#define R 0 // R (no sound)
#endif //UIAOS_FREQUENCIES_H

View File

@@ -0,0 +1,89 @@
#ifndef SONGAPP_SONG_H
#define SONGAPP_SONG_H
#include <libc/stdint.h>
#include "frequencies.h"
// Define a struct to represent a single musical note
typedef struct {
uint32_t frequency; // The frequency of the note in Hz (e.g., A4 = 440 Hz)
uint32_t duration; // The duration of the note in milliseconds
} Note;
// Define a struct to represent a song
typedef struct {
Note* notes; // Pointer to an array of Note structs representing the song
uint32_t length; // The number of notes in the song
} Song;
// Define a struct to represent a song player
typedef struct {
void (*play_song)(Song* song); // Function pointer to a function that plays a song
} SongPlayer;
// Function prototype for creating a new SongPlayer instance
// Returns a pointer to a newly created SongPlayer object
SongPlayer* CreateSongPlayer(void);
static Note music_1[] = {
{E5, 250}, {R, 125}, {E5, 125}, {R, 125}, {E5, 125}, {R, 125},
{C5, 125}, {E5, 125}, {G5, 125}, {R, 125}, {G4, 125}, {R, 250},
{C5, 125}, {R, 250}, {G4, 125}, {R, 125}, {E4, 125}, {R, 125},
{A4, 125}, {B4, 125}, {R, 125}, {A_SHARP4, 125}, {A4, 125}, {R, 125},
{G4, 125}, {E5, 125}, {G5, 125}, {A5, 125}, {F5, 125}, {G5, 125},
{R, 125}, {E5, 125}, {C5, 125}, {D5, 125}, {B4, 125}, {R, 125},
{C5, 125}, {R, 250}, {G4, 125}, {R, 125}, {E4, 125}, {R, 125},
{A4, 125}, {B4, 125}, {R, 125}, {A_SHARP4, 125}, {A4, 125}, {R, 125},
{G4, 125}, {E5, 125}, {G5, 125}, {A5, 125}, {F5, 125}, {G5, 125},
{R, 125}, {E5, 125}, {C5, 125}, {D5, 125}, {B4, 125}, {R, 125},
};
static Note music_2[] = {
{A4, 200}, {E5, 200}, {A5, 200}, {R, 100}, {A5, 200}, {A5, 200}, {Gs5, 200}, {A5, 200},
{R, 100}, {E5, 200}, {R, 100}, {E5, 200}, {R, 100}, {E5, 200}, {R, 100}, {E5, 200},
{A4, 200}, {E5, 200}, {A5, 200}, {R, 100}, {A5, 200}, {A5, 200}, {Gs5, 200}, {A5, 200},
{R, 100}, {E5, 200}, {R, 100}, {E5, 200}, {R, 100}, {E5, 200}, {R, 100}, {E5, 200},
{A4, 200}, {E5, 200}, {A5, 200}, {R, 100}, {A5, 200}, {A5, 200}, {Gs5, 200}, {A5, 200},
{R, 100}, {E5, 200}, {R, 100}, {E5, 200}, {R, 100}, {E5, 200}, {R, 100}, {E5, 200}
};
static Note music_3[] = {
{E4, 200}, {E4, 200}, {F4, 200}, {G4, 200}, {G4, 200}, {F4, 200}, {E4, 200}, {D4, 200},
{C4, 200}, {C4, 200}, {D4, 200}, {E4, 200}, {E4, 400}, {R, 200},
{D4, 200}, {D4, 200}, {E4, 200}, {F4, 200}, {F4, 200}, {E4, 200}, {D4, 200}, {C4, 200},
{A4, 200}, {A4, 200}, {A4, 200}, {G4, 400}
};
static Note music_4[] = {
{C4, 500}, {D4, 500}, {E4, 500}, {C4, 500},
{C4, 500}, {D4, 500}, {E4, 500}, {C4, 500},
{E4, 500}, {F4, 500}, {G4, 1000},
{E4, 500}, {F4, 500}, {G4, 1000},
{G4, 250}, {A4, 250}, {G4, 250}, {F4, 250}, {E4, 500}, {C4, 500},
{G4, 250}, {A4, 250}, {G4, 250}, {F4, 250}, {E4, 500}, {C4, 500},
{C4, 500}, {G3, 500}, {C4, 1000},
{C4, 500}, {G3, 500}, {C4, 1000}
};
static Note music_5[] = {
{E4, 375}, {C4, 375}, {D4, 375}, {A3, 375}, {B3, 375}, {D4, 375}, {C4, 375}, {A3, 375},
{E4, 375}, {C4, 375}, {D4, 375}, {A3, 375}, {B3, 375}, {D4, 375}, {C4, 375}, {A3, 375},
};
static Note music_6[] = {
{F4, 250}, {F4, 250}, {F4, 250}, {C5, 250}, {A_SHARP4, 250}, {G_SHARP4, 250}, {F4, 500},
{F4, 250}, {F4, 250}, {F4, 250}, {C5, 250}, {A_SHARP4, 250}, {G_SHARP4, 250}, {F4, 500},
{A_SHARP4, 250}, {A_SHARP4, 250}, {A_SHARP4, 250}, {F5, 250}, {D5, 250}, {C5, 250}, {A_SHARP4, 500},
{A_SHARP4, 250}, {A_SHARP4, 250}, {A_SHARP4, 250}, {F5, 250}, {D5, 250}, {C5, 250}, {A_SHARP4, 500},
};
void EnableSpeaker(void);
void DisableSpeaker(void);
void PlaySound(uint32_t frequency);
void StopSound(void);
void PlaySongImpl(Song* song);
void PlayMusic(void);
#endif

View File

@@ -5,43 +5,67 @@
#include <kernel/interrupt.h>
#include <kernel/keyboard.h>
#include <kernel/memory.h>
#include <kernel/pit.h>
#include <songApp/song.h>
#include <songApp/frequencies.h>
#include <snakeApp/snake.h>
extern uint32_t end;
void PlayMusic(void) {
Song songs[] = {
{music_1, sizeof(music_1) / sizeof(Note)},
{music_2, sizeof(music_2) / sizeof(Note)},
{music_3, sizeof(music_3) / sizeof(Note)},
{music_4, sizeof(music_4) / sizeof(Note)},
{music_5, sizeof(music_5) / sizeof(Note)},
{music_6, sizeof(music_6) / sizeof(Note)}
};
uint32_t songCount = sizeof(songs) / sizeof(Song);
SongPlayer* player = CreateSongPlayer();
if (!player) {
TerminalWriteString("Failed to create SongPlayer.\n");
return;
}
for(uint32_t i = 0; i < songCount; i++) {
player->play_song(&songs[i]);
SleepInterrupt(1000);
}
}
void main(void) {
TerminalInitialize();
GdtInitialize();
IdtInitialize();
PitInitialize();
RegisterInterruptHandler(IRQ1, KeyboardHandler);
init_kernel_memory(&end);
init_paging();
print_memory_layout();
InitKernelMemory(&end);
InitPaging();
PrintMemoryLayout();
void* memory1 = malloc(48261);
void* memory2 = malloc(27261);
void* memory3 = malloc(12617);
while (1) {
TerminalClear();
TerminalWriteString("Enter application number (0 for music, 1 for snake): ");
char input = TerminalGetChar();
TerminalWriteString("memory1 = ");
TerminalWriteHex((uint32_t)memory1);
TerminalWriteString("\n");
TerminalWriteString("memory2 = ");
TerminalWriteHex((uint32_t)memory2);
TerminalWriteString("\n");
TerminalWriteString("memory3 = ");
TerminalWriteHex((uint32_t)memory3);
TerminalWriteString("\n");
free(memory2);
void* memory4 = malloc(1000);
TerminalWriteString("memory4 = ");
TerminalWriteHex((uint32_t)memory4);
TerminalWriteString("\n");
switch (input) {
case '0':
PlayMusic();
break;
case '1':
PlayGame();
break;
default:
TerminalWriteString("Invalid application number.\n");
break;
}
}
for (;;) {
__asm__ volatile("hlt");

View File

@@ -7,6 +7,8 @@
static uint32_t index = 0;
static uint8_t keyboardBuffer[KEYBOARD_BUFFER_SIZE];
static volatile char lastKeyPressed = 0;
static const char scancodeToAscii[128] = {
0, 27, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b',
'\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n',
@@ -19,6 +21,12 @@ static const char scancodeToAscii[128] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
char GetLastKeyPressed(void) {
char key = lastKeyPressed;
lastKeyPressed = 0;
return key;
}
void KeyboardHandler(struct Registers* registers) {
(void) registers;
@@ -37,7 +45,7 @@ void KeyboardHandler(struct Registers* registers) {
char ascii = scancodeToAscii[scancode];
if (ascii != 0) {
TerminalPutChar(ascii);
lastKeyPressed = ascii;
}
}
}

View File

@@ -12,7 +12,7 @@ static uint32_t pheap_end = 0;
static uint8_t *pheap_desc = 0;
static uint32_t memory_used = 0;
void init_kernel_memory(uint32_t* kernel_end) {
void InitKernelMemory(uint32_t* kernel_end) {
uint32_t kernelEndAddr = (uint32_t)kernel_end;
last_alloc = kernelEndAddr + 0x1000;
@@ -28,7 +28,7 @@ void init_kernel_memory(uint32_t* kernel_end) {
TerminalWriteString("\n");
}
void print_memory_layout(void) {
void PrintMemoryLayout(void) {
TerminalWriteString("Memory used:");
TerminalWriteUInt(memory_used);
TerminalWriteString(" bytes\n");

View File

@@ -25,7 +25,7 @@ void paging_enable() {
asm volatile("mov %eax, %cr0");
}
void init_paging(void) {
void InitPaging(void) {
TerminalWriteString("Setting up paging\n");
page_directory = (uint32_t*)0x400000;

59
src/OSDev_18/src/pit.c Normal file
View File

@@ -0,0 +1,59 @@
#include <kernel/pit.h>
#include <kernel/interrupt.h>
#include <kernel/io.h>
#include <kernel/terminal.h>
static volatile uint32_t pit_ticks = 0; // Encasulated to pit.c to avoid accidental overwrite
uint32_t GetCurrentTick(void){
return pit_ticks;
}
static void PitIrqHandler(struct Registers* regs){
(void)regs; // Required by interface, not needed in this implementation
pit_ticks++;
}
void PitInitialize(void){
uint16_t divisor = DIVIDER;
RegisterInterruptHandler(IRQ0, PitIrqHandler);
OutPortByte(PIT_CMD_PORT, 0x36);
OutPortByte(PIT_CHANNEL0_PORT, (uint8_t)(divisor & 0xFF));
OutPortByte(PIT_CHANNEL0_PORT, (uint8_t)((divisor >> 8) & 0xFF));
}
//More efficient way of sleeping compared to SleepBusy()
void SleepInterrupt(uint32_t ticks_to_wait){
uint32_t start_tick = GetCurrentTick();
uint32_t end_tick = start_tick + ticks_to_wait;
while(GetCurrentTick() < end_tick){
__asm__ volatile ("sti; hlt");
}
}
/* "Consumes" 100% CPU power to effectively put entire system into sleep
for (ticks_to_wait) amount of ticks / milliseconds */
void SleepBusy(uint32_t milliseconds){
uint32_t start_tick = GetCurrentTick();
uint32_t ticks_to_wait = milliseconds * TICKS_PER_MS;
while((GetCurrentTick() - start_tick) < ticks_to_wait){
/* Occupy the CPU by with handling a whole lot of nothing.
Also known as busy waiting */
}
}
void SleepTest(){
while (GetCurrentTick() < 15000) {
TerminalWriteString("Sleeping with busy-waiting (HIGH CPU).\n");
SleepBusy(1000);
TerminalWriteString("Slept using busy-waiting.\n");
TerminalWriteString("Sleeping with interrupts (LOW CPU).\n");
SleepInterrupt(1000);
TerminalWriteString("Slept using interrupts.\n");
}
}

357
src/OSDev_18/src/snake.c Normal file
View File

@@ -0,0 +1,357 @@
#include <snakeApp/snake.h>
#include <kernel/memory.h>
#include <kernel/pit.h>
#include <kernel/keyboard.h>
#include <kernel/terminal.h>
#include <songApp/song.h>
#include <songApp/frequencies.h>
struct GameState* CreateGame(void) {
struct GameState* game = (struct GameState*)malloc(sizeof(struct GameState));
if (!game) {
return 0;
}
game->snake = (struct Snake*)malloc(sizeof(struct Snake));
if (!game->snake) {
free(game);
return 0;
}
game->food = (struct Food*)malloc(sizeof(struct Food));
if (!game->food) {
free(game->snake);
free(game);
return 0;
}
game->score = 0;
game->rngState = GetCurrentTick();
InitializeBoard(game);
InitializeSnake(game->snake);
InitializeFood(game->food);
return game;
}
void DestroyGame(struct GameState* game) {
if (!game) return;
if (game->snake) {
free(game->snake);
}
if (game->food) {
free(game->food);
}
free(game);
}
void ResetGame(struct GameState* game) {
if (!game) return;
InitializeBoard(game);
InitializeSnake(game->snake);
InitializeFood(game->food);
game->score = 0;
game->rngState = GetCurrentTick();
}
void InitializeBoard(struct GameState* game) {
for (uint32_t i = 0; i < BOARD_SIZE; i++) {
for (uint32_t j = 0; j < BOARD_SIZE; j++) {
game->board[i][j][0] = ' ';
game->board[i][j][1] = ' ';
game->board[i][j][2] = '\0';
}
}
}
void InitializeSnake(struct Snake* snake) {
snake->length = 1;
snake->direction = LEFT;
snake->body[0].x = BOARD_SIZE / 2;
snake->body[0].y = BOARD_SIZE / 2;
}
void InitializeFood(struct Food* food) {
food->x = (BOARD_SIZE / 2) - 3;
food->y = (BOARD_SIZE / 2) - 3;
}
uint32_t Random(uint32_t* rngState) {
*rngState = (*rngState * 1103515245) + 12345;
return *rngState;
}
void HandleInput(struct GameState* game, char input) {
switch (input) {
case 'w': {
if (game->snake->direction != DOWN) {
game->snake->direction = UP;
}
break;
}
case 's': {
if (game->snake->direction != UP) {
game->snake->direction = DOWN;
}
break;
}
case 'a': {
if (game->snake->direction != RIGHT) {
game->snake->direction = LEFT;
}
break;
}
case 'd': {
if (game->snake->direction != LEFT) {
game->snake->direction = RIGHT;
}
break;
}
case 'r': {
if (game) {
ResetGame(game);
}
break;
}
case 'q': {
TerminalWriteString("Quitting game...\n");
DestroyGame(game);
}
default: {
break;
}
}
}
void PlayFoodSound(void) {
PlaySound(B5);
SleepInterrupt(50);
StopSound();
}
void PlayDeathSound(void) {
PlaySound(G3);
SleepInterrupt(100);
StopSound();
}
void PlayWinSound(void) {
PlaySound(C5);
SleepInterrupt(100);
StopSound();
PlaySound(E5);
SleepInterrupt(100);
StopSound();
PlaySound(G5);
SleepInterrupt(200);
StopSound();
}
struct SnakeSegment MoveSnake(struct Snake* snake) {
int x = snake->body[0].x;
int y = snake->body[0].y;
switch (snake->direction) {
case UP: {
snake->body[0].y--;
for (uint32_t i = 1; i < snake->length; i++) {
int tempX = snake->body[i].x;
int tempY = snake->body[i].y;
snake->body[i].x = x;
snake->body[i].y = y;
x = tempX;
y = tempY;
}
break;
}
case DOWN: {
snake->body[0].y++;
for (uint32_t i = 1; i < snake->length; i++) {
int tempX = snake->body[i].x;
int tempY = snake->body[i].y;
snake->body[i].x = x;
snake->body[i].y = y;
x = tempX;
y = tempY;
}
break;
}
case LEFT: {
snake->body[0].x--;
for (uint32_t i = 1; i < snake->length; i++) {
int tempX = snake->body[i].x;
int tempY = snake->body[i].y;
snake->body[i].x = x;
snake->body[i].y = y;
x = tempX;
y = tempY;
}
break;
}
case RIGHT: {
snake->body[0].x++;
for (uint32_t i = 1; i < snake->length; i++) {
int tempX = snake->body[i].x;
int tempY = snake->body[i].y;
snake->body[i].x = x;
snake->body[i].y = y;
x = tempX;
y = tempY;
}
break;
}
default: {
break;
}
}
return (struct SnakeSegment){ x, y };
}
void SpawnFood(struct GameState* game) {
int x, y;
uint32_t occupied;
if (game->snake->length == SNAKE_MAX_LENGTH) {
return;
}
do {
x = Random(&game->rngState) % BOARD_SIZE;
y = Random(&game->rngState) % BOARD_SIZE;
occupied = 0;
for (uint32_t i = 0; i < game->snake->length; i++) {
if (game->snake->body[i].x == x && game->snake->body[i].y == y) {
occupied = 1;
break;
}
}
} while (occupied);
game->food->x = x;
game->food->y = y;
}
void AddSegment(struct Snake* snake, int x, int y) {
if (snake->length < SNAKE_MAX_LENGTH) {
snake->body[snake->length].x = x;
snake->body[snake->length].y = y;
snake->length++;
}
}
enum CollisionType CheckCollision(struct Snake* snake, struct Food* food) {
if (snake->body[0].x == food->x && snake->body[0].y == food->y) {
return FOOD;
} else if (snake->body[0].x < 0 || snake->body[0].x >= BOARD_SIZE || snake->body[0].y < 0 || snake->body[0].y >= BOARD_SIZE) {
return WALL;
} else {
for (uint32_t i = 1; i < snake->length; i++) {
if (snake->body[0].x == snake->body[i].x && snake->body[0].y == snake->body[i].y) {
return SELF;
}
}
return NONE;
}
}
void DrawBoard(struct GameState* game) {
TerminalClear();
InitializeBoard(game);
if (game->snake->direction == UP) {
game->board[game->snake->body[0].y][game->snake->body[0].x][0] = '^';
game->board[game->snake->body[0].y][game->snake->body[0].x][1] = '^';
} else if (game->snake->direction == DOWN) {
game->board[game->snake->body[0].y][game->snake->body[0].x][0] = 'v';
game->board[game->snake->body[0].y][game->snake->body[0].x][1] = 'v';
} else if (game->snake->direction == LEFT) {
game->board[game->snake->body[0].y][game->snake->body[0].x][0] = '<';
game->board[game->snake->body[0].y][game->snake->body[0].x][1] = '<';
} else if (game->snake->direction == RIGHT) {
game->board[game->snake->body[0].y][game->snake->body[0].x][0] = '>';
game->board[game->snake->body[0].y][game->snake->body[0].x][1] = '>';
}
for (uint32_t i = 1; i < game->snake->length; i++) {
game->board[game->snake->body[i].y][game->snake->body[i].x][0] = '[';
game->board[game->snake->body[i].y][game->snake->body[i].x][1] = ']';
}
game->board[game->food->y][game->food->x][0] = '{';
game->board[game->food->y][game->food->x][1] = '}';
for (uint32_t i = 0; i < BOARD_SIZE + 2; i++) {
TerminalWriteString("##");
}
TerminalPutChar('\n');
for (uint32_t i = 0; i < BOARD_SIZE; i++) {
TerminalWriteString("##");
for (uint32_t j = 0; j < BOARD_SIZE; j++) {
TerminalWriteString(game->board[i][j]);
}
TerminalWriteString("##\n");
}
for (uint32_t i = 0; i < BOARD_SIZE + 2; i++) {
TerminalWriteString("##");
}
TerminalPutChar('\n');
TerminalWriteString("Score: ");
TerminalWriteUInt(game->score);
TerminalPutChar('\n');
}
void PlayGame(void) {
struct GameState* game = CreateGame();
if (!game) {
TerminalWriteString("Failed to create game.\n");
return;
}
char input = 0;
struct SnakeSegment tail;
enum CollisionType collisionType = NONE;
while(1) {
if (game->snake->length == SNAKE_MAX_LENGTH) {
break;
}
input = GetLastKeyPressed();
HandleInput(game, input);
if (input == 'q') return;
tail = MoveSnake(game->snake);
collisionType = CheckCollision(game->snake, game->food);
if (collisionType == FOOD) {
game->score++;
PlayFoodSound();
AddSegment(game->snake, tail.x, tail.y);
SpawnFood(game);
} else if (collisionType == WALL || collisionType == SELF) {
PlayDeathSound();
ResetGame(game);
}
collisionType = NONE;
DrawBoard(game);
SleepInterrupt(GAME_SPEED_MS);
}
TerminalWriteString("You win!\n");
PlayWinSound();
DestroyGame(game);
}

View File

@@ -0,0 +1,55 @@
#include <songApp/song.h>
#include <kernel/pit.h>
#include <kernel/io.h>
#include <kernel/memory.h>
#include <libc/stdint.h>
void EnableSpeaker(void) {
uint8_t speakerState = InPortByte(PC_SPEAKER_PORT);
OutPortByte(PC_SPEAKER_PORT, speakerState | 0x03);
}
void DisableSpeaker(void) {
uint8_t speakerState = InPortByte(PC_SPEAKER_PORT);
OutPortByte(PC_SPEAKER_PORT, speakerState & 0xFC);
}
void PlaySound(uint32_t frequency) {
if (!frequency) return;
uint32_t divisor = PIT_BASE_FREQ / frequency;
OutPortByte(PIT_CMD_PORT, 0xB6);
OutPortByte(PIT_CHANNEL2_PORT, divisor & 0xFF);
OutPortByte(PIT_CHANNEL2_PORT, (divisor >> 8) & 0xFF);
EnableSpeaker();
}
void StopSound(void) {
DisableSpeaker();
}
void PlaySongImpl(Song* song) {
for (uint32_t i = 0; i < song->length; i++) {
Note currentNote = song->notes[i];
if (currentNote.frequency == R) {
StopSound();
SleepInterrupt(currentNote.duration * 2); // *2 is a forced delay, added because QEMU struggles with frequencies switching fast
} else {
PlaySound(currentNote.frequency);
SleepInterrupt(currentNote.duration * 2); // *2 is a forced delay, added because QEMU struggles with frequencies switching fast
StopSound();
}
}
}
SongPlayer* CreateSongPlayer(void) {
SongPlayer* player = (SongPlayer*)malloc(sizeof(SongPlayer));
if (!player) return 0;
player->play_song = PlaySongImpl;
return player;
}

View File

@@ -1,6 +1,8 @@
#include <kernel/gdt.h>
#include <kernel/terminal.h>
#include <kernel/io.h>
#include <kernel/keyboard.h>
#include <kernel/pit.h>
const size_t VGA_HEIGHT = 25;
const size_t VGA_WIDTH = 80;
@@ -21,6 +23,18 @@ uint16_t VgaEntry(unsigned char uc, uint8_t color) {
return (uint16_t) uc | (uint16_t) color << 8;
}
void TerminalClear(void) {
for (size_t y = 0; y < VGA_HEIGHT; y++) {
for (size_t x = 0; x < VGA_WIDTH; x++) {
TerminalEntryAt(' ', terminal_colour, x, y);
}
}
terminal_row = 0;
terminal_column = 0;
TerminalUpdateCursor();
}
void TerminalEntryAt(char c, uint8_t colour, size_t x, size_t y){
size_t index = y * VGA_WIDTH + x;
terminal_buffer[index] = VgaEntry(c, colour);
@@ -125,4 +139,16 @@ void TerminalWriteHex(uint32_t num) {
uint8_t digit = (num >> i) & 0xF;
TerminalPutChar(hexChars[digit]);
}
}
char TerminalGetChar(void) {
while (1) {
char key = GetLastKeyPressed();
if (key != 0) {
return key;
}
SleepInterrupt(100);
}
}