From 0112e7ddb87fb9756aa835bc9012c21b52b45188 Mon Sep 17 00:00:00 2001 From: maxtyson123 <98maxt98@gmail.com> Date: Fri, 18 Apr 2025 18:37:18 +1200 Subject: [PATCH 01/38] Use IDE Interface to find disks --- kernel/CMakeLists.txt | 4 +- kernel/include/drivers/disk/ata.h | 3 +- kernel/include/drivers/disk/ide.h | 48 ++++++++++++++ kernel/include/drivers/driver.h | 10 +-- .../include/drivers/ethernet/amd_am79c973.h | 3 +- kernel/include/drivers/ethernet/ethernet.h | 2 +- kernel/src/common/logger.cpp | 2 +- kernel/src/drivers/clock/clock.cpp | 3 +- kernel/src/drivers/console/textmode.cpp | 7 +-- kernel/src/drivers/disk/{disk => }/ata.cpp | 27 ++++---- kernel/src/drivers/disk/ide.cpp | 48 ++++++++++++++ kernel/src/drivers/driver.cpp | 62 +------------------ kernel/src/drivers/ethernet/amd_am79c973.cpp | 13 ++-- kernel/src/drivers/ethernet/ethernet.cpp | 32 +--------- kernel/src/drivers/ethernet/intel_i217.cpp | 24 +++---- kernel/src/drivers/video/vesa.cpp | 3 +- kernel/src/drivers/video/video.cpp | 7 +-- kernel/src/hardwarecommunication/pci.cpp | 26 ++++---- 18 files changed, 155 insertions(+), 169 deletions(-) create mode 100644 kernel/include/drivers/disk/ide.h rename kernel/src/drivers/disk/{disk => }/ata.cpp (93%) create mode 100644 kernel/src/drivers/disk/ide.cpp diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index cb05c1f0..7f048111 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -14,7 +14,9 @@ SET_SOURCE_FILES_PROPERTIES(${ASM_SRCS} PROPERTIES LANGUAGE ASM) FILE(GLOB_RECURSE KERNEL_SRCS src/*.cpp src/*.s) # Create the kernel -ADD_EXECUTABLE(MaxOSk64 ${KERNEL_SRCS}) +ADD_EXECUTABLE(MaxOSk64 ${KERNEL_SRCS} + include/drivers/disk/ide.h + src/drivers/disk/ide.cpp) TARGET_COMPILE_DEFINITIONS(MaxOSk64 PUBLIC MAXOS_KERNEL) # Update the version before building diff --git a/kernel/include/drivers/disk/ata.h b/kernel/include/drivers/disk/ata.h index 521bdc5a..217b0b21 100644 --- a/kernel/include/drivers/disk/ata.h +++ b/kernel/include/drivers/disk/ata.h @@ -36,9 +36,8 @@ namespace MaxOS{ bool m_is_master; uint16_t m_bytes_per_sector { 512 }; - common::OutputStream* ata_message_stream; public: - AdvancedTechnologyAttachment(uint16_t port_base, bool master, common::OutputStream* output_stream); + AdvancedTechnologyAttachment(uint16_t port_base, bool master); ~AdvancedTechnologyAttachment(); void identify(); diff --git a/kernel/include/drivers/disk/ide.h b/kernel/include/drivers/disk/ide.h new file mode 100644 index 00000000..d53ebf92 --- /dev/null +++ b/kernel/include/drivers/disk/ide.h @@ -0,0 +1,48 @@ +// +// Created by Max Tyson on 18/04/2025. +// + +#ifndef MAXOS_DRIVERS_IDE_H +#define MAXOS_DRIVERS_IDE_H + +#include +#include +#include + + +namespace MaxOS{ + + namespace drivers{ + + namespace disk{ + + /** + * * @class IntegratedDriveElectronicsController + * @brief Driver for the IDE controller, handles the creation and management of the IDE devices + */ + class IntegratedDriveElectronicsController : public Driver{ + + AdvancedTechnologyAttachment primary_maser; + AdvancedTechnologyAttachment primary_slave; + + AdvancedTechnologyAttachment secondary_maser; + AdvancedTechnologyAttachment secondary_slave; + + + public: + IntegratedDriveElectronicsController(hardwarecommunication::PeripheralComponentInterconnectDeviceDescriptor* device_descriptor); + ~IntegratedDriveElectronicsController(); + + void initialise() override; + + string vendor_name() final; + string device_name() final; + + + }; + } + } +} + + +#endif //MAXOS_DRIVERS_IDE_H diff --git a/kernel/include/drivers/driver.h b/kernel/include/drivers/driver.h index bd77ce12..9fb64b02 100644 --- a/kernel/include/drivers/driver.h +++ b/kernel/include/drivers/driver.h @@ -22,19 +22,11 @@ namespace MaxOS * @brief base class for all drivers, handles the activation, deactivation, initialisation and reset of the driver as well as error messages and identifying the device */ class Driver { - protected: public: - common::OutputStream* m_driver_message_stream; - - Driver(common::OutputStream* driverMessageStream = nullptr); + Driver(); ~Driver(); - void error_message(const string& message) const; - void error_message(char char_to_write) const; - void error_message(int int_to_write) const; - void error_message(uint32_t hex_to_write) const; - virtual void activate(); virtual void deactivate(); virtual void initialise(); diff --git a/kernel/include/drivers/ethernet/amd_am79c973.h b/kernel/include/drivers/ethernet/amd_am79c973.h index 442b1e40..8bdb41a3 100644 --- a/kernel/include/drivers/ethernet/amd_am79c973.h +++ b/kernel/include/drivers/ethernet/amd_am79c973.h @@ -8,7 +8,6 @@ #include #include #include -#include #include #include @@ -86,7 +85,7 @@ namespace MaxOS{ void FetchDataSent(); //Fetches the data from the buffer public: - AMD_AM79C973(hardwarecommunication::PeripheralComponentInterconnectDeviceDescriptor* deviceDescriptor, common::OutputStream* amdNetMessageStream = nullptr); + AMD_AM79C973(hardwarecommunication::PeripheralComponentInterconnectDeviceDescriptor* device_descriptor); ~AMD_AM79C973(); //Override driver default methods diff --git a/kernel/include/drivers/ethernet/ethernet.h b/kernel/include/drivers/ethernet/ethernet.h index 36b9363d..e300eda0 100644 --- a/kernel/include/drivers/ethernet/ethernet.h +++ b/kernel/include/drivers/ethernet/ethernet.h @@ -90,7 +90,7 @@ namespace MaxOS{ void FireDataSent(uint8_t* buffer, uint32_t size); public: - EthernetDriver(common::OutputStream* ethernetMessageStream); + EthernetDriver(); ~EthernetDriver(); static MediaAccessControlAddress CreateMediaAccessControlAddress(uint8_t digit1, uint8_t digit2, uint8_t digit3, uint8_t digit4, uint8_t digit5, uint8_t digit6); diff --git a/kernel/src/common/logger.cpp b/kernel/src/common/logger.cpp index 52f1c421..6d13246b 100644 --- a/kernel/src/common/logger.cpp +++ b/kernel/src/common/logger.cpp @@ -96,7 +96,7 @@ void Logger::set_log_level(LogLevel log_level) { break; case LogLevel::WARNING: - *this << ANSI_COLOURS[ANSIColour::FG_Yellow] << ANSI_COLOURS[FG_White] << "[ WARNING ]" << ANSI_COLOURS[ANSIColour::Reset] << " "; + *this << ANSI_COLOURS[ANSIColour::BG_Yellow] << ANSI_COLOURS[FG_White] << "[ WARNING ]" << ANSI_COLOURS[ANSIColour::Reset] << " "; break; case LogLevel::ERROR: diff --git a/kernel/src/drivers/clock/clock.cpp b/kernel/src/drivers/clock/clock.cpp index 9aa8acaa..a9c72eba 100644 --- a/kernel/src/drivers/clock/clock.cpp +++ b/kernel/src/drivers/clock/clock.cpp @@ -55,8 +55,7 @@ Event* ClockEventHandler::on_event(Event* event) { * @param time_between_events The time between events in 10ths of a second */ Clock::Clock(AdvancedProgrammableInterruptController* apic, uint16_t time_between_events) -: Driver(), - InterruptHandler(0x20), +: InterruptHandler(0x20), m_apic(apic), m_ticks_between_events(time_between_events) { diff --git a/kernel/src/drivers/console/textmode.cpp b/kernel/src/drivers/console/textmode.cpp index 0855e1a8..8dd4769a 100644 --- a/kernel/src/drivers/console/textmode.cpp +++ b/kernel/src/drivers/console/textmode.cpp @@ -9,12 +9,7 @@ using namespace MaxOS::common; using namespace MaxOS::drivers; using namespace MaxOS::drivers::console; -TextModeConsole::TextModeConsole() -: Driver(), - Console() -{ - -} +TextModeConsole::TextModeConsole() = default; TextModeConsole::~TextModeConsole() = default; diff --git a/kernel/src/drivers/disk/disk/ata.cpp b/kernel/src/drivers/disk/ata.cpp similarity index 93% rename from kernel/src/drivers/disk/disk/ata.cpp rename to kernel/src/drivers/disk/ata.cpp index 768dadc0..c2ed1d0a 100644 --- a/kernel/src/drivers/disk/disk/ata.cpp +++ b/kernel/src/drivers/disk/ata.cpp @@ -10,9 +10,8 @@ using namespace MaxOS::hardwarecommunication; using namespace MaxOS::drivers; using namespace MaxOS::drivers::disk; -AdvancedTechnologyAttachment::AdvancedTechnologyAttachment(uint16_t port_base, bool master, OutputStream*output_stream) -: Driver(output_stream), - m_data_port(port_base), +AdvancedTechnologyAttachment::AdvancedTechnologyAttachment(uint16_t port_base, bool master) +: m_data_port(port_base), m_error_port(port_base + 1), m_sector_count_port(port_base + 2), m_LBA_low_port(port_base + 3), @@ -21,8 +20,7 @@ AdvancedTechnologyAttachment::AdvancedTechnologyAttachment(uint16_t port_base, b m_device_port(port_base + 6), m_command_port(port_base + 7), m_control_port(port_base + 0x206), - m_is_master(master), - ata_message_stream(output_stream) + m_is_master(master) { } @@ -44,7 +42,7 @@ void AdvancedTechnologyAttachment::identify() { m_device_port.write(0xA0); uint8_t status = m_command_port.read(); if(status == 0xFF){ - ata_message_stream-> write("Invalid Status"); + Logger::WARNING() << "ATA Device: Invalid status"; return; } @@ -71,16 +69,22 @@ void AdvancedTechnologyAttachment::identify() { //Check for any errors if(status & 0x01){ - ata_message_stream-> write("ERROR"); + Logger::WARNING() << "ATA Device: Error reading status\n"; return; } - // read the data and print it + // Print the data + Logger::DEBUG() << "ATA: "; for (uint16_t i = 0; i < 256; ++i) { - uint16_t data = m_data_port.read(); - ata_message_stream-> write(" 0x"); - ata_message_stream-> write_hex(data); + + uint16_t data = m_data_port.read(); + char *text = " \0"; + text[0] = (data >> 8) & 0xFF; + text[1] = data & 0xFF; + Logger::Out() << text; + } + Logger::Out() << "\n"; } /** @@ -232,6 +236,5 @@ string AdvancedTechnologyAttachment::device_name() { * @return The name of the vendor */ string AdvancedTechnologyAttachment::vendor_name() { - return "IDE"; } diff --git a/kernel/src/drivers/disk/ide.cpp b/kernel/src/drivers/disk/ide.cpp new file mode 100644 index 00000000..66462fbd --- /dev/null +++ b/kernel/src/drivers/disk/ide.cpp @@ -0,0 +1,48 @@ +// +// Created by Max Tyson on 18/04/2025. +// +#include + +using namespace MaxOS; +using namespace MaxOS::hardwarecommunication; +using namespace MaxOS::drivers; +using namespace MaxOS::drivers::disk; + +IntegratedDriveElectronicsController::IntegratedDriveElectronicsController(PeripheralComponentInterconnectDeviceDescriptor* device_descriptor) +: primary_maser(0x1F0, true), + primary_slave(0x1F0, false), + secondary_maser(0x170, true), + secondary_slave(0x170, false) +{ + +} + +IntegratedDriveElectronicsController::~IntegratedDriveElectronicsController() = default; + +/** + * @brief Initialise the IDE controller by identifying the devices + */ +void IntegratedDriveElectronicsController::initialise() +{ + primary_maser.identify(); + primary_slave.identify(); + secondary_maser.identify(); + secondary_slave.identify(); +} + + +/** + * @brief Get the vendor name + */ +string IntegratedDriveElectronicsController::vendor_name() +{ + return "Intel"; +} + +/** + * @brief Get the device name + */ +string IntegratedDriveElectronicsController::device_name() +{ + return "PIIX4"; +} \ No newline at end of file diff --git a/kernel/src/drivers/driver.cpp b/kernel/src/drivers/driver.cpp index 0a9c2261..6e653e12 100644 --- a/kernel/src/drivers/driver.cpp +++ b/kernel/src/drivers/driver.cpp @@ -10,14 +10,8 @@ using namespace MaxOS::drivers; using namespace MaxOS::memory; using namespace MaxOS::hardwarecommunication; -Driver::Driver(OutputStream* driverMessageStream) -: m_driver_message_stream(driverMessageStream) { - -} - -Driver::~Driver(){ - this ->m_driver_message_stream = nullptr; -} +Driver::Driver() = default; +Driver::~Driver() = default; /** * @brief activate the driver @@ -49,58 +43,6 @@ uint32_t Driver::reset(){ return 0; } -/** - * @brief write a message to the driver message stream if it is not null - * - * @param message The message to write - */ -void Driver::error_message(const string& message) const { - - // If there is a driver message stream write the message to it - if(m_driver_message_stream != nullptr) - m_driver_message_stream-> write(message); - -} - -/** - * @brief write a character to the driver message stream if it is not null - * - * @param char_to_write The character to write - */ -void Driver::error_message(char char_to_write) const { - - // If there is a driver message stream write the character to it - if(m_driver_message_stream != nullptr) - m_driver_message_stream-> write_char(char_to_write); - -} - - -/** - * @brief write an integer to the driver message stream if it is not null - * - * @param int_to_write The integer to write - */ -void Driver::error_message(int int_to_write) const { - - // If there is a driver message stream write the integer to it - if(m_driver_message_stream != nullptr) - m_driver_message_stream-> write_int(int_to_write); -} - -/** - * @brief write a hex to the driver message stream if it is not null - * - * @param hex_to_write The hex to write - */ -void Driver::error_message(uint32_t hex_to_write) const { - - // If there is a driver message stream write the hex to it - if(m_driver_message_stream != nullptr) - m_driver_message_stream->write_hex(hex_to_write); - -} - /** * @brief Get the vendor name of the driver * diff --git a/kernel/src/drivers/ethernet/amd_am79c973.cpp b/kernel/src/drivers/ethernet/amd_am79c973.cpp index 578b2327..e20f7ec1 100644 --- a/kernel/src/drivers/ethernet/amd_am79c973.cpp +++ b/kernel/src/drivers/ethernet/amd_am79c973.cpp @@ -11,9 +11,8 @@ using namespace MaxOS::drivers; using namespace MaxOS::drivers::ethernet; using namespace MaxOS::hardwarecommunication; -AMD_AM79C973::AMD_AM79C973(PeripheralComponentInterconnectDeviceDescriptor *dev, OutputStream *amdNetMessageStream) -: EthernetDriver(amdNetMessageStream), - InterruptHandler(0x20 + dev -> interrupt), +AMD_AM79C973::AMD_AM79C973(PeripheralComponentInterconnectDeviceDescriptor *dev) +: InterruptHandler(0x20 + dev -> interrupt), MACAddress0Port(dev ->port_base), MACAddress2Port(dev ->port_base + 0x02), MACAddress4Port(dev ->port_base + 0x04), @@ -169,13 +168,13 @@ void AMD_AM79C973::handle_interrupt() { // Errors if((temp & 0x8000) == 0x8000) - error_message("AMD am79c973 ERROR: "); + Logger::WARNING() << "AMD am79c973 ERROR: "; if((temp & 0x2000) == 0x2000) - error_message("COLLISION ERROR\n"); + Logger::WARNING() << "COLLISION ERROR\n"; if((temp & 0x1000) == 0x1000) - error_message("MISSED FRAME\n"); + Logger::WARNING() << "MISSED FRAME\n"; if((temp & 0x0800) == 0x0800) - error_message("MEMORY ERROR\n"); + Logger::WARNING() << "MEMORY ERROR\n"; // Responses diff --git a/kernel/src/drivers/ethernet/ethernet.cpp b/kernel/src/drivers/ethernet/ethernet.cpp index 0773fcff..7cfb102d 100644 --- a/kernel/src/drivers/ethernet/ethernet.cpp +++ b/kernel/src/drivers/ethernet/ethernet.cpp @@ -59,13 +59,8 @@ Event* EthernetDriverEventHandler::on_event(Event write("Sending: "); - - int displayType = 34; //What header to hide (Ethernet Header = 14, IP Header = 34, UDP = 42, TCP Header = 54, ARP = 42) - for(uint32_t i = displayType; i < size; i++) - { - m_driver_message_stream->write_hex(buffer[i]); - m_driver_message_stream-> write(" "); - } - m_driver_message_stream-> write("\n"); // Raise the event raise_event(new BeforeSendEvent(buffer, size)); @@ -116,15 +102,6 @@ void EthernetDriver::DoSend(uint8_t*, uint32_t) */ void EthernetDriver::FireDataReceived(uint8_t* buffer, uint32_t size) { - m_driver_message_stream-> write("Receiving: "); - //size = 64; - int displayType = 34; //What header to hide (Ethernet Header = 14, IP Header = 34, UDP = 42, TCP Header = 54, ARP = 42) - for(uint32_t i = displayType; i < size; i++) - { - m_driver_message_stream->write_hex(buffer[i]); - m_driver_message_stream-> write(" "); - } - m_driver_message_stream-> write("\n"); // Raise the event Vector*> values = @@ -134,17 +111,14 @@ void EthernetDriver::FireDataReceived(uint8_t* buffer, uint32_t size) for(auto & value : values) { switch (value->type) { case EthernetDriverEvents::DATA_RECEIVED: - if(value->return_value.boolValue){ - m_driver_message_stream-> write("Sending back... \n"); + if(value->return_value.boolValue) Send(buffer, size); - } break; default: break; } } - m_driver_message_stream-> write("DATA HANDLED\n"); } /** diff --git a/kernel/src/drivers/ethernet/intel_i217.cpp b/kernel/src/drivers/ethernet/intel_i217.cpp index b295a26f..9baaed06 100644 --- a/kernel/src/drivers/ethernet/intel_i217.cpp +++ b/kernel/src/drivers/ethernet/intel_i217.cpp @@ -23,8 +23,7 @@ using namespace memory; ///__DRIVER___ intel_i217::intel_i217(PeripheralComponentInterconnectDeviceDescriptor *deviceDescriptor) -: EthernetDriver(nullptr), - InterruptHandler(0x20 + deviceDescriptor->interrupt) +: InterruptHandler(0x20 + deviceDescriptor->interrupt) { //Set the registers @@ -63,13 +62,10 @@ intel_i217::intel_i217(PeripheralComponentInterconnectDeviceDescriptor *deviceDe detectEEProm (); if (readMACAddress()){ - ownMAC = CreateMediaAccessControlAddress(macAddress[0], macAddress[1], macAddress[2], macAddress[3], macAddress[4], macAddress[5]); }else{ - - error_message("ERROR, INIT FAILED, MAC ADDRESS NOT FOUND"); - while (true); + ASSERT(false, "ERROR, INIT FAILED, MAC ADDRESS NOT FOUND"); } for(int i = 0; i < 0x80; i++) //Loop through all the registers @@ -286,7 +282,6 @@ void intel_i217::sendInit() { void intel_i217::activate() { - m_driver_message_stream-> write("Activating Intel i217\n"); //Enable interrupts Write(interruptMaskRegister ,0x1F6DC); //Enable all interrupts @@ -300,7 +295,6 @@ void intel_i217::activate() { sendInit(); active = true; // Set active to true - m_driver_message_stream-> write("Intel i217 INIT DONE\n"); } @@ -309,18 +303,17 @@ void intel_i217::handle_interrupt() { Write(interruptMaskRegister, 0x1); //Clear the interrupt or it will hang uint32_t temp = Read(0xc0); //read the interrupt status register - m_driver_message_stream-> write("Interrupt from INTEL i217"); + // if(temp & 0x04) + // m_driver_message_stream-> write("INTEL i217 START LINK");//initDone = true; + // + // if(temp & 0x10) + // m_driver_message_stream-> write("INTEL i217 GOOD THRESHOLD"); - if(temp & 0x04) - m_driver_message_stream-> write("INTEL i217 START LINK");//initDone = true; - if(temp & 0x10) - m_driver_message_stream-> write("INTEL i217 GOOD THRESHOLD"); if(temp & 0x80) FetchDataReceived(); } void intel_i217::FetchDataReceived() { - m_driver_message_stream-> write("Fetching data... "); uint16_t old_cur; @@ -348,7 +341,6 @@ void intel_i217::FetchDataReceived() { void intel_i217::DoSend(uint8_t* buffer, uint32_t size) { - m_driver_message_stream-> write("Sending package... "); while(!active); //Put params into send buffer @@ -369,12 +361,10 @@ void intel_i217::DoSend(uint8_t* buffer, uint32_t size) { //Wait for the packet to be sent while(!(sendDsrctrs[old_cur]->status & 0xff)); - m_driver_message_stream-> write(" Done\n"); } uint64_t intel_i217::GetMediaAccessControlAddress() { - m_driver_message_stream-> write("Getting MAC address... "); while(ownMAC == 0); return ownMAC; diff --git a/kernel/src/drivers/video/vesa.cpp b/kernel/src/drivers/video/vesa.cpp index f8b98c0d..ec57b3ee 100644 --- a/kernel/src/drivers/video/vesa.cpp +++ b/kernel/src/drivers/video/vesa.cpp @@ -13,8 +13,7 @@ using namespace MaxOS::system; using namespace MaxOS::common; VideoElectronicsStandardsAssociation::VideoElectronicsStandardsAssociation(multiboot_tag_framebuffer* framebuffer_info) -: VideoDriver(), - m_framebuffer_info(framebuffer_info) +: m_framebuffer_info(framebuffer_info) { // Get the framebuffer info Logger::INFO() << "Setting up VESA driver\n"; diff --git a/kernel/src/drivers/video/video.cpp b/kernel/src/drivers/video/video.cpp index e9dece67..70bae311 100644 --- a/kernel/src/drivers/video/video.cpp +++ b/kernel/src/drivers/video/video.cpp @@ -8,12 +8,7 @@ using namespace MaxOS::drivers::video; using namespace MaxOS::common; -VideoDriver::VideoDriver() -: Driver(), - GraphicsContext() -{ - -} +VideoDriver::VideoDriver() = default; VideoDriver::~VideoDriver() = default; diff --git a/kernel/src/hardwarecommunication/pci.cpp b/kernel/src/hardwarecommunication/pci.cpp index ae15f1fd..8c4c4070 100644 --- a/kernel/src/hardwarecommunication/pci.cpp +++ b/kernel/src/hardwarecommunication/pci.cpp @@ -2,18 +2,21 @@ // Created by 98max on 12/10/2022. // #include -#include -#include #include +// Divers that need PCI descriptors +#include +#include +#include using namespace MaxOS; using namespace MaxOS::common; using namespace MaxOS::hardwarecommunication; +using namespace MaxOS::memory; using namespace MaxOS::drivers; using namespace MaxOS::drivers::ethernet; using namespace MaxOS::drivers::video; -using namespace MaxOS::memory; +using namespace MaxOS::drivers::disk; ///__DESCRIPTOR___ @@ -165,8 +168,7 @@ void PeripheralComponentInterconnectController::select_drivers(DriverSelectorEve if(deviceDescriptor.vendor_id == 0x0000 || deviceDescriptor.vendor_id == 0x0001 || deviceDescriptor.vendor_id == 0xFFFF) continue; - - // Get port number + // Get the earliest port number for(int barNum = 5; barNum >= 0; barNum--){ BaseAddressRegister bar = get_base_address_register(bus, device, function, barNum); if(bar.address && (bar.type == BaseAddressRegisterType::InputOutput)) @@ -249,10 +251,17 @@ Driver* PeripheralComponentInterconnectController::get_driver(PeripheralComponen { switch (dev.device_id) { + case 0x100E: //i217 (Ethernet Controller) { return new intel_i217(&dev); } + + case 0x7010: // PIIX4 (IDE Controller) + { + return new IntegratedDriveElectronicsController(&dev); + } + default: break; } @@ -366,13 +375,6 @@ void PeripheralComponentInterconnectController::list_known_device(const Peripher } - case 0x7010: - { - Logger::Out() << "PIIX4"; - break; - - } - case 0x7111: { Logger::Out() << "PIIX3 ACPI"; From 553f30d8f20d4b18b485c8e4bb0b789195544be5 Mon Sep 17 00:00:00 2001 From: maxtyson123 <98maxt98@gmail.com> Date: Sat, 19 Apr 2025 18:15:53 +1200 Subject: [PATCH 02/38] Read MBR Partitions from disk --- README.md | 5 +- kernel/CMakeLists.txt | 6 +- kernel/include/common/map.h | 14 ++++ kernel/include/drivers/disk/ata.h | 18 +++--- kernel/include/drivers/disk/disk.h | 42 ++++++++++++ kernel/include/drivers/disk/ide.h | 11 ++-- kernel/include/filesystem/msdospart.h | 67 ------------------- kernel/include/filesystem/partition/msdos.h | 71 ++++++++++++++++++++ kernel/src/drivers/disk/ata.cpp | 59 ++++++++--------- kernel/src/drivers/disk/disk.cpp | 72 +++++++++++++++++++++ kernel/src/drivers/disk/ide.cpp | 71 +++++++++++++++++--- kernel/src/filesystem/msdospart.cpp | 36 ----------- kernel/src/filesystem/partition/msdos.cpp | 43 ++++++++++++ 13 files changed, 355 insertions(+), 160 deletions(-) create mode 100644 kernel/include/drivers/disk/disk.h delete mode 100644 kernel/include/filesystem/msdospart.h create mode 100644 kernel/include/filesystem/partition/msdos.h create mode 100644 kernel/src/drivers/disk/disk.cpp delete mode 100644 kernel/src/filesystem/msdospart.cpp create mode 100644 kernel/src/filesystem/partition/msdos.cpp diff --git a/README.md b/README.md index 236716ba..9eed2c3e 100644 --- a/README.md +++ b/README.md @@ -213,14 +213,15 @@ No user usage so far (userland will be added in the future) - [ ] VFS - [x] Loading ELF - [ ] Multiple Cores Support (SMP & Scheduler) +- [ ] Move drivers to userspace +- [ ] Move VFS to userspace - [ ] Userland GUI - [ ] CLI - [ ] Porting & Dynamically Linking Libc - [ ] Self-hosted os - [ ] App Framework & System Apps - [ ] DOOM Port -- [ ] UserSpace Drivers -- [ ] Userspace Networking +- [ ] Move networking to userspace (& rewrite, fix) - [ ] Connect to Clion with SMB for files and GDB for debugging in userspace - [ ] Auto Updater & Image Builder (ISO Release) - [ ] Store diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index 7f048111..86dcc77d 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -16,7 +16,11 @@ FILE(GLOB_RECURSE KERNEL_SRCS src/*.cpp src/*.s) # Create the kernel ADD_EXECUTABLE(MaxOSk64 ${KERNEL_SRCS} include/drivers/disk/ide.h - src/drivers/disk/ide.cpp) + src/drivers/disk/ide.cpp + include/filesystem/partition/msdos.h + src/filesystem/partition/msdos.cpp + include/drivers/disk/disk.h + src/drivers/disk/disk.cpp) TARGET_COMPILE_DEFINITIONS(MaxOSk64 PUBLIC MAXOS_KERNEL) # Update the version before building diff --git a/kernel/include/common/map.h b/kernel/include/common/map.h index f309e939..85d569d0 100644 --- a/kernel/include/common/map.h +++ b/kernel/include/common/map.h @@ -53,6 +53,8 @@ namespace MaxOS{ iterator find(Key); bool empty(); + int size(); + void clear(); void insert(Key, Value); void erase(Key); @@ -163,6 +165,18 @@ namespace MaxOS{ return m_elements.empty(); } + /** + * @brief The number of elements in the map + * + * @return The number of elements in the Map + */ + template int Map::size() + { + // Return the size of the vector + return m_elements.size(); + + } + /** * @brief Removes all elements from the map * diff --git a/kernel/include/drivers/disk/ata.h b/kernel/include/drivers/disk/ata.h index 217b0b21..9f0e49e4 100644 --- a/kernel/include/drivers/disk/ata.h +++ b/kernel/include/drivers/disk/ata.h @@ -2,12 +2,12 @@ // Created by 98max on 24/10/2022. // -#ifndef MAXOS_DRIVERS_ATA_H -#define MAXOS_DRIVERS_ATA_H +#ifndef MAXOS_DRIVERS_DISK_ATA_H +#define MAXOS_DRIVERS_DISK_ATA_H #include #include -#include +#include #include @@ -21,7 +21,7 @@ namespace MaxOS{ * @class AdvancedTechnologyAttachment * @brief Driver for the ATA controller, handles the reading and writing of data to the hard drive */ - class AdvancedTechnologyAttachment : public Driver { + class AdvancedTechnologyAttachment : public Disk { protected: hardwarecommunication::Port16Bit m_data_port; @@ -40,10 +40,10 @@ namespace MaxOS{ AdvancedTechnologyAttachment(uint16_t port_base, bool master); ~AdvancedTechnologyAttachment(); - void identify(); - void read_28(uint32_t sector, uint8_t* data, int count); - void write_28(uint32_t sector, const uint8_t* data, int count); - void flush(); + bool identify(); + void read(uint32_t sector, uint8_t* data_buffer, size_t amount) final; + void write(uint32_t sector, const uint8_t* data, size_t count) final; + void flush() final; void activate() final; @@ -54,4 +54,4 @@ namespace MaxOS{ } } -#endif //MAXOS_DRIVERS_ATA_H +#endif //MAXOS_DRIVERS_DISK_ATA_H diff --git a/kernel/include/drivers/disk/disk.h b/kernel/include/drivers/disk/disk.h new file mode 100644 index 00000000..ae0848a5 --- /dev/null +++ b/kernel/include/drivers/disk/disk.h @@ -0,0 +1,42 @@ +// +// Created by 98max on 18/04/2025. +// + +#ifndef MAXOS_DRIVERS_DISK_H +#define MAXOS_DRIVERS_DISK_H + +#include +#include +#include + + +namespace MaxOS{ + + namespace drivers{ + + namespace disk{ + + /** + * @class Disk + * @brief Generic Disk, handles the reading and writing of data to the hard drive + */ + class Disk : public Driver { + + public: + Disk(); + ~Disk(); + + virtual void read(uint32_t sector, uint8_t* data_buffer, size_t amount); + virtual void write(uint32_t sector, const uint8_t* data, size_t count); + virtual void flush(); + + void activate() override; + + string device_name() override; + string vendor_name() override; + }; + } + } +} + +#endif //MAXOS_DRIVERS_DISK_H diff --git a/kernel/include/drivers/disk/ide.h b/kernel/include/drivers/disk/ide.h index d53ebf92..2bcbd693 100644 --- a/kernel/include/drivers/disk/ide.h +++ b/kernel/include/drivers/disk/ide.h @@ -7,7 +7,9 @@ #include #include +#include #include +#include namespace MaxOS{ @@ -22,18 +24,15 @@ namespace MaxOS{ */ class IntegratedDriveElectronicsController : public Driver{ - AdvancedTechnologyAttachment primary_maser; - AdvancedTechnologyAttachment primary_slave; - - AdvancedTechnologyAttachment secondary_maser; - AdvancedTechnologyAttachment secondary_slave; + common::Map devices; public: IntegratedDriveElectronicsController(hardwarecommunication::PeripheralComponentInterconnectDeviceDescriptor* device_descriptor); ~IntegratedDriveElectronicsController(); - void initialise() override; + void initialise() final; + void activate() final; string vendor_name() final; string device_name() final; diff --git a/kernel/include/filesystem/msdospart.h b/kernel/include/filesystem/msdospart.h deleted file mode 100644 index fcd9c682..00000000 --- a/kernel/include/filesystem/msdospart.h +++ /dev/null @@ -1,67 +0,0 @@ -// -// Created by 98max on 12/28/2022. -// - -#ifndef MAXOS_FILESYSTEM_MSDOSPART_H -#define MAXOS_FILESYSTEM_MSDOSPART_H - -#include "drivers/disk/ata.h" -#include -#include - -namespace MaxOS{ - - namespace filesystem{ - - /** - * @struct PartitionTableEntry - * @brief Stores information about a partition - */ - struct PartitionTableEntry{ - - uint8_t bootable; // 0x80 = bootable, 0x00 = not bootable - - uint8_t startHead; - uint8_t startSector : 6; - uint16_t startCylinder : 10; - - uint8_t partitionId; - - uint8_t endHead; - uint8_t endSector : 6; - uint16_t endCylinder : 10; - - uint32_t startLBA; - uint32_t length; - - } __attribute__((packed)); - - /** - * @struct MasterBootRecord - * @brief Stores information about the master boot record - */ - struct MasterBootRecord{ - - uint8_t bootloader[440]; - uint32_t diskSignature; - uint16_t unused; - - PartitionTableEntry primaryPartition[4]; - - uint16_t magicNumber; - - } __attribute__((packed)); - - /** - * @class MSDOSPartitionTable - * @brief Reads the partition table of the hard drive - */ - class MSDOSPartitionTable{ - public: - static void read_partitions(drivers::disk::AdvancedTechnologyAttachment *hd); - - }; - } -} - -#endif //MAXOS_FILESYSTEM_MSDOSPART_H diff --git a/kernel/include/filesystem/partition/msdos.h b/kernel/include/filesystem/partition/msdos.h new file mode 100644 index 00000000..ea9c04b3 --- /dev/null +++ b/kernel/include/filesystem/partition/msdos.h @@ -0,0 +1,71 @@ +// +// Created by Max Tyson on 18/04/2025. +// + +#ifndef MAXOS_FILESYSTEM_PARTITION_MSDOS_H +#define MAXOS_FILESYSTEM_PARTITION_MSDOS_H + +#include +#include + +namespace MaxOS{ + + namespace filesystem{ + + namespace partition{ + /** + * @struct PartitionTableEntry + * @brief Stores information about a partition + */ + struct PartitionTableEntry{ + + uint8_t bootable; // 0x80 = bootable, 0x00 = not bootable + + uint8_t start_head; + uint8_t start_sector : 6; + uint16_t start_cylinder : 10; + + uint8_t type; + + uint8_t end_head; + uint8_t end_sector : 6; + uint16_t end_cylinder : 10; + + uint32_t start_LBA; + uint32_t length; + + } __attribute__((packed)); + + /** + * @struct MasterBootRecord + * @brief Stores information about the master boot record + */ + struct MasterBootRecord{ + + uint8_t bootloader[440]; + uint32_t disk_signature; + uint16_t unused; + + PartitionTableEntry primary_partition[4]; + + uint16_t magic; + + } __attribute__((packed)); + + + class MSDOSPartition + { + public: + static void mount_partitions(drivers::disk::Disk* disk); + }; + + + // TODO: Abstract some of this into a base class and use it for GPT and other partition tables + + + + } + } +} + +#endif //MSDOS_H diff --git a/kernel/src/drivers/disk/ata.cpp b/kernel/src/drivers/disk/ata.cpp index c2ed1d0a..2d54299d 100644 --- a/kernel/src/drivers/disk/ata.cpp +++ b/kernel/src/drivers/disk/ata.cpp @@ -29,8 +29,10 @@ AdvancedTechnologyAttachment::~AdvancedTechnologyAttachment() = default; /** * @brief Identify the ATA device + * + * @return True if the device is present, false otherwise */ -void AdvancedTechnologyAttachment::identify() { +bool AdvancedTechnologyAttachment::identify() { // Select the device (master or slave) m_device_port.write(m_is_master ? 0xA0 : 0xB0); @@ -43,7 +45,7 @@ void AdvancedTechnologyAttachment::identify() { uint8_t status = m_command_port.read(); if(status == 0xFF){ Logger::WARNING() << "ATA Device: Invalid status"; - return; + return false; } // Select the device (master or slave) @@ -61,7 +63,7 @@ void AdvancedTechnologyAttachment::identify() { // Check if the device is present status = m_command_port.read(); if(status == 0x00) - return; + return false; // Wait for the device to be ready or for an error to occur while (((status & 0x80) == 0x80) && ((status & 0x01) != 0x01)) @@ -70,39 +72,32 @@ void AdvancedTechnologyAttachment::identify() { //Check for any errors if(status & 0x01){ Logger::WARNING() << "ATA Device: Error reading status\n"; - return; + return false; } - // Print the data - Logger::DEBUG() << "ATA: "; - for (uint16_t i = 0; i < 256; ++i) { - + // Read the rest of the data + for (uint16_t i = 0; i < 256; ++i) uint16_t data = m_data_port.read(); - char *text = " \0"; - text[0] = (data >> 8) & 0xFF; - text[1] = data & 0xFF; - Logger::Out() << text; - } - Logger::Out() << "\n"; + // Device is present and ready + return true; } /** - * @brief read a sector from the ATA device + * @brief Read a sector from the ATA device * * @param sector The sector to read - * @param data The data to read into - * @param count The amount of data to read from that sector + * @param data_buffer The data to read into + * @param amount The amount of data to read from that sector */ -void AdvancedTechnologyAttachment::read_28(uint32_t sector, uint8_t* data, int count) +void AdvancedTechnologyAttachment::read(uint32_t sector, uint8_t* data_buffer, size_t amount) { // Don't allow reading more than a sector - if(sector & 0xF0000000 || count > m_bytes_per_sector) + if(sector & 0xF0000000 || amount > m_bytes_per_sector) return; // Select the device (master or slave) and reset it - m_device_port.write((m_is_master ? 0xE0 : 0xF0) | - ((sector & 0x0F000000) >> 24)); + m_device_port.write((m_is_master ? 0xE0 : 0xF0) | ((sector & 0x0F000000) >> 24)); m_error_port.write(0); m_sector_count_port.write(1); @@ -128,19 +123,19 @@ void AdvancedTechnologyAttachment::read_28(uint32_t sector, uint8_t* data, int c return; // read the data and store it in the array - for(int i = 0; i < count; i+= 2) + for(int i = 0; i < amount; i+= 2) { uint16_t read_data = m_data_port.read(); - data[i] = read_data & 0x00FF; + data_buffer[i] = read_data & 0x00FF; // Place the next byte in the array if there is one - if(i+1 < count) - data[i+1] = (read_data >> 8) & 0x00FF; + if(i+1 < amount) + data_buffer[i+1] = (read_data >> 8) & 0x00FF; } // read the remaining bytes - for(uint16_t i = count + (count % 2); i < m_bytes_per_sector; i+= 2) + for(uint16_t i = amount + (amount % 2); i < m_bytes_per_sector; i+= 2) m_data_port.read(); } @@ -148,17 +143,17 @@ void AdvancedTechnologyAttachment::read_28(uint32_t sector, uint8_t* data, int c * @brief write to a sector on the ATA device * * @param sector The sector to write to + * @param data The data to write * @param count The amount of data to write to that sector */ -void AdvancedTechnologyAttachment::write_28(uint32_t sector, const uint8_t* data, int count){ +void AdvancedTechnologyAttachment::write(uint32_t sector, const uint8_t* data, size_t count){ // Don't allow writing more han a sector if(sector > 0x0FFFFFFF || count > m_bytes_per_sector) return; // Select the device (master or slave) and reset it - m_device_port.write(m_is_master ? 0xE0 - : 0xF0 | ((sector & 0x0F000000) >> 24)); + m_device_port.write(m_is_master ? 0xE0 : 0xF0 | ((sector & 0x0F000000) >> 24)); m_error_port.write(0); m_sector_count_port.write(1); @@ -213,10 +208,12 @@ void AdvancedTechnologyAttachment::flush() { } /** - * @brief Activate the ATA device + * @brief Activate the ATA device by mounting it to the virtual file system */ void AdvancedTechnologyAttachment::activate() { - Driver::activate(); + + + } /** diff --git a/kernel/src/drivers/disk/disk.cpp b/kernel/src/drivers/disk/disk.cpp new file mode 100644 index 00000000..48e36781 --- /dev/null +++ b/kernel/src/drivers/disk/disk.cpp @@ -0,0 +1,72 @@ +// +// Created by Max Tyson on 18/04/2025. +// + +#include + +using namespace MaxOS; +using namespace MaxOS::drivers; +using namespace MaxOS::drivers::disk; + +Disk::Disk() = default; +Disk::~Disk() = default; + + +/** + * @brief Read data from the disk + * + * @param sector The sector to read from + * @param data_buffer The buffer to read the data into + * @param amount The amount of data to read + */ +void Disk::read(uint32_t sector, uint8_t* data_buffer, size_t amount) +{ +} + +/** + * @brief Write data to the disk + * + * @param sector The sector to write to + * @param data_buffer The buffer to write the data into + * @param amount The amount of data to write + */ +void Disk::write(uint32_t sector, const uint8_t* data, size_t count) +{ +} + +/** + * @brief Flush the disk cache + * + * This function is used to flush the disk cache to ensure that all data is written to the disk. + */ +void Disk::flush() +{ +} + +/** + * @brief Activate the disk driver + */ +void Disk::activate() +{ + Driver::activate(); +} + +/** + * @brief Get the device name + * + * @return The name of the device + */ +string Disk::device_name() +{ + return "Disk"; +} + +/** + * @brief Get the vendor name + * + * @return The name of the vendor + */ +string Disk::vendor_name() +{ + return "Generic"; +} diff --git a/kernel/src/drivers/disk/ide.cpp b/kernel/src/drivers/disk/ide.cpp index 66462fbd..05f6b38b 100644 --- a/kernel/src/drivers/disk/ide.cpp +++ b/kernel/src/drivers/disk/ide.cpp @@ -3,17 +3,29 @@ // #include + using namespace MaxOS; using namespace MaxOS::hardwarecommunication; using namespace MaxOS::drivers; using namespace MaxOS::drivers::disk; +using namespace MaxOS::filesystem; +using namespace MaxOS::filesystem::partition; IntegratedDriveElectronicsController::IntegratedDriveElectronicsController(PeripheralComponentInterconnectDeviceDescriptor* device_descriptor) -: primary_maser(0x1F0, true), - primary_slave(0x1F0, false), - secondary_maser(0x170, true), - secondary_slave(0x170, false) { + // TODO: Use the device descriptor to get the port base and add the devices dynamically + + // Primary + auto primary_maser = new AdvancedTechnologyAttachment(0x1F0, true); + auto primary_slave = new AdvancedTechnologyAttachment(0x1F0, false); + devices.insert(primary_maser, true); + devices.insert(primary_slave, false); + + // Secondary + auto secondary_maser = new AdvancedTechnologyAttachment(0x170, true); + auto secondary_slave = new AdvancedTechnologyAttachment(0x170, false); + devices.insert(secondary_maser, true); + devices.insert(secondary_slave, false); } @@ -24,10 +36,53 @@ IntegratedDriveElectronicsController::~IntegratedDriveElectronicsController() = */ void IntegratedDriveElectronicsController::initialise() { - primary_maser.identify(); - primary_slave.identify(); - secondary_maser.identify(); - secondary_slave.identify(); + + // Loop through the devices and identify them + for(auto& device : devices) + { + + // Get the ata device + auto ata_device = device.first; + + // Check if the device is present + if(ata_device == nullptr) + continue; + + // Identify the device + bool exists = ata_device->identify(); + + // Remove the device if it does not exist + if(!exists) + { + devices.erase(ata_device); + delete ata_device; + continue; + } + } + + // Log the init done + Logger::DEBUG() << "IDE Controller: Initialised " << devices.size() << " devices\n"; +} + +/** + * @brief Activate the IDE controller by mounting the devices to the virtual file system + */ +void IntegratedDriveElectronicsController::activate() +{ + + + // Loop through the devices and load the partitions + for(auto& device : devices) + { + + // Ensure there is a device and that it is the master + if(device.first == nullptr || !device.second) + continue; + + // Mount the device + MSDOSPartition::mount_partitions(device.first); + + } } diff --git a/kernel/src/filesystem/msdospart.cpp b/kernel/src/filesystem/msdospart.cpp deleted file mode 100644 index 32876599..00000000 --- a/kernel/src/filesystem/msdospart.cpp +++ /dev/null @@ -1,36 +0,0 @@ -// -// Created by 98max on 12/28/2022. -// - -#include - -using namespace MaxOS; -using namespace MaxOS::drivers::disk; -using namespace MaxOS::common; -using namespace MaxOS::filesystem; - -/** - * @brief read the partition table of a given hard disk - * - * @param hd The hard disk to read the partition table from - */ -void MSDOSPartitionTable::read_partitions(AdvancedTechnologyAttachment *hd) { - - // read the MBR from the hard disk - MasterBootRecord masterBootRecord = {}; - hd -> read_28(0, (uint8_t *)&masterBootRecord, sizeof(MasterBootRecord)); - - // Check if the magic number is correct - if(masterBootRecord.magicNumber != 0xAA55) - return; - - - // Loop through the primary partitions - for(auto& entry : masterBootRecord.primaryPartition){ - - if(entry.partitionId == 0) continue; // If the partition id is 0, skip it - - //TODO: Create a new FAT32 object - - } -} diff --git a/kernel/src/filesystem/partition/msdos.cpp b/kernel/src/filesystem/partition/msdos.cpp new file mode 100644 index 00000000..08d98d24 --- /dev/null +++ b/kernel/src/filesystem/partition/msdos.cpp @@ -0,0 +1,43 @@ +// +// Created by 98max on 12/28/2022. +// + +#include + +using namespace MaxOS; +using namespace MaxOS::filesystem; +using namespace MaxOS::filesystem::partition; +using namespace MaxOS::drivers::disk; + +/** + * @brief read the partition table of a given hard disk + * + * @param hd The hard disk to read the partition table from + */ +void MSDOSPartition::mount_partitions(Disk* hd) { + + // read the MBR from the hard disk + MasterBootRecord mbr = {}; + hd -> read(0, (uint8_t *)&mbr, sizeof(MasterBootRecord)); + + // Check if the magic number is correct + if(mbr.magic != 0xAA55) + { + Logger::WARNING() << "Could not find valid MBR on disk 0x" << (uint64_t)hd << "\n"; + return; + } + + + // Loop through the primary partitions + for(auto& entry : mbr.primary_partition){ + + // Empty entry + if(entry.type == 0) + continue; + + Logger::DEBUG() << "Partition 0x" << (uint64_t)entry.type << " at 0x" << (uint64_t)entry.start_LBA << "\n"; + + //TODO: Create a new FAT32 object + + } +} From 13f7d317adff1c312ededf4ca1b75d3e8dae701c Mon Sep 17 00:00:00 2001 From: maxtyson123 <98maxt98@gmail.com> Date: Sun, 20 Apr 2025 16:59:40 +1200 Subject: [PATCH 03/38] MS Dos FS Types --- kernel/include/filesystem/partition/msdos.h | 279 ++++++++++++++++++++ kernel/src/filesystem/partition/msdos.cpp | 17 +- kernel/src/kernel.cpp | 7 +- 3 files changed, 300 insertions(+), 3 deletions(-) diff --git a/kernel/include/filesystem/partition/msdos.h b/kernel/include/filesystem/partition/msdos.h index ea9c04b3..5af35bd0 100644 --- a/kernel/include/filesystem/partition/msdos.h +++ b/kernel/include/filesystem/partition/msdos.h @@ -13,6 +13,285 @@ namespace MaxOS{ namespace filesystem{ namespace partition{ + + + /// Credit: http://www.osdever.net/documents/pdf/partitiontypes.pdf + enum class PartitionType { + EMPTY, // 0x00 + FAT12, // 0x01 + XENIX_ROOT, // 0x02 + XENIX_USR, // 0x03 + FAT16, // 0x04 + EXTENDED, // 0x05 + FAT16_32MB, // 0x06 + NTFS, // 0x07 + QNX_QNY, // 0x08 + AIX_DATA, // 0x09 + OS2_BOOT_MANAGER, // 0x0A + FAT32, // 0x0B + FAT32_LBA, // 0x0C + UNUSED_1, // 0x0D + FAT16_LBA, // 0x0E + EXTENDED_LBA, // 0x0F + OPUS, // 0x10 + FAT12_HIDDEN, // 0x11 + COMPAQ, // 0x12 + UNUSED_2, // 0x13 + FAT16_HIDDEN, // 0x14 + UNUSED_3, // 0x15 + FAT16_32MB_HIDDEN, // 0x16 + IFS_HIDDEN, // 0x17 + AST_SMART_SLEEP, // 0x18 + UNUSED_4, // 0x19 + UNUSED_5, // 0x1A + FAT32_HIDDEN, // 0x1B + FAT32_LBA_HIDDEN, // 0x1C + UNUSED_6, // 0x1D + FAT16_LBA_HIDDEN, // 0x1E + + UNUSED_7, // 0x1F + UNUSED_8, // 0x20 + RESERVED_1, // 0x21 + UNUSED_10, // 0x22 + RESERVED_2, // 0x23 + NEC_DOS, // 0x24 + UNUSED_11, // 0x25 + RESERVED_3, // 0x26 + + // 0x27 - 0x30: unassigned + UNUSED_12, // 0x27 + UNUSED_13, // 0x28 + UNUSED_14, // 0x29 + UNUSED_15, // 0x2A + UNUSED_16, // 0x2B + UNUSED_17, // 0x2C + UNUSED_18, // 0x2D + UNUSED_19, // 0x2E + UNUSED_20, // 0x2F + UNUSED_21, // 0x30 + + RESERVED_4, // 0x31 + NOS, // 0x32 + RESERVED_5, // 0x33 + RESERVED_6, // 0x34 + JFS, // 0x35 + RESERVED_7, // 0x36 + UNUSED_22, // 0x37 + THEOS_3_2, // 0x38 + PLAN9, // 0x39 + THEOS_4_4GB, // 0x3A + THEOS_4_EXTENDED, // 0x3B + PARTITIONMAGIC_RECOVERY, // 0x3C + HIDDEN_NETWARE, // 0x3D + UNUSED_23, // 0x3E + UNUSED_24, // 0x3F + + VENIX, // 0x40 + LINUX_MINIX, // 0x41 + LINUX_SWAP, // 0x42 + LINUX_NATIVE, // 0x43 + GOBACK, // 0x44 + BOOT_US, // 0x45 + EUMEL_ELAN_46, // 0x46 + EUMEL_ELAN_47, // 0x47 + EUMEL_ELAN_48, // 0x48 + UNUSED_25, // 0x49 + ADAOS, // 0x4A + UNUSED_26, // 0x4B + OBERON, // 0x4C + QNX4, // 0x4D + QNX4_SECOND, // 0x4E + QNX4_THIRD, // 0x4F + + ONTRACK_DM, // 0x50 + ONTRACK_DM_RW, // 0x51 + CPM, // 0x52 + DISK_MANAGER_AUX3, // 0x53 + DISK_MANAGER_DDO, // 0x54 + EZ_DRIVE, // 0x55 + GOLDEN_BOW, // 0x56 + DRIVE_PRO, // 0x57 + + UNUSED_27, // 0x58 + UNUSED_28, // 0x59 + UNUSED_29, // 0x5A + UNUSED_30, // 0x5B + PRIAM_EDISK, // 0x5C + UNUSED_31, // 0x5D + UNUSED_32, // 0x5E + UNUSED_33, // 0x5F + + UNUSED_34, // 0x60 + SPEEDSTOR, // 0x61 + UNUSED_35, // 0x62 + UNIX, // 0x63 + PC_ARMOUR, // 0x64 + NOVELL_NETWARE386, // 0x65 + NOVELL_SMS, // 0x66 + NOVELL, // 0x67 + NOVELL_OLD, // 0x68 + NOVELL_NETWARE_NSS, // 0x69 + + UNUSED_36, // 0x6A + UNUSED_37, // 0x6B + UNUSED_38, // 0x6C + UNUSED_39, // 0x6D + UNUSED_40, // 0x6E + UNUSED_41, // 0x6F + + DISKSECURE, // 0x70 + RESERVED_8, // 0x71 + UNUSED_42, // 0x72 + RESERVED_9, // 0x73 + SCRAMDISK, // 0x74 + IBM_PCIX, // 0x75 + RESERVED_10, // 0x76 + M2FS, // 0x77 + XOSL_FS, // 0x78 + UNUSED_43, // 0x79 + UNUSED_44, // 0x7A + UNUSED_45, // 0x7B + UNUSED_46, // 0x7C + UNUSED_47, // 0x7D + UNUSED_48, // 0x7E + UNUSED_49, // 0x7F + + MINIX, // 0x80 + MINIX2, // 0x81 + LINUX_SWAP_ALT, // 0x82 + LINUX_EXT2, // 0x83 + HIBERNATION, // 0x84 + LINUX_EXTENDED, // 0x85 + LINUX_RAID, // 0x86 + NTFS_VOLUME_SET, // 0x87 + UNUSED_50, // 0x88 + UNUSED_51, // 0x89 + LINUX_KERNEL, // 0x8A + FAULT_TOLERANT_FAT32, // 0x8B + FT_FAT32_LBA, // 0x8C + FREEFDISK_FAT12, // 0x8D + LINUX_LVM, // 0x8E + UNUSED_52, // 0x8F + FREEFDISK_FAT16, // 0x90 + FREEFDISK_EXTENDED, // 0x91 + FREEFDISK_FAT16_LARGE, // 0x92 + HIDDEN_LINUX_NATIVE, // 0x93 + AMOEBA_BAD_BLOCK, // 0x94 + MIT_EXOPC, // 0x95 + UNUSED_53, // 0x96 + FREEFDISK_FAT32, // 0x97 + FREEFDISK_FAT32_LBA, // 0x98 + DCE376, // 0x99 + FREEFDISK_FAT16_LBA, // 0x9A + FREEFDISK_EXTENDED_LBA,// 0x9B + + UNUSED_54, // 0x9C + UNUSED_55, // 0x9D + UNUSED_56, // 0x9E + + BSD_OS, // 0x9F + LAPTOP_HIBERNATION, // 0xA0 + HP_VOLUME_EXPANSION, // 0xA1 + UNUSED_57, // 0xA2 + RESERVED_11, // 0xA3 + RESERVED_12, // 0xA4 + BSD_386, // 0xA5 + OPENBSD, // 0xA6 + NEXTSTEP, // 0xA7 + MAC_OS_X, // 0xA8 + NETBSD, // 0xA9 + OLIVETTI_SERVICE, // 0xAA + MAC_OS_X_BOOT, // 0xAB + UNUSED_58, // 0xAC + UNUSED_59, // 0xAD + SHAGOS, // 0xAE + SHAGOS_SWAP, // 0xAF + BOOTSTAR, // 0xB0 + + RESERVED_13, // 0xB1 + UNUSED_60, // 0xB2 + RESERVED_14, // 0xB3 + RESERVED_15, // 0xB4 + UNUSED_61, // 0xB5 + RESERVED_16, // 0xB6 + + BSDI_BSD386_FS, // 0xB7 + BSDI_BSD386_SWAP, // 0xB8 + UNUSED_62, // 0xB9 + UNUSED_63, // 0xBA + BOOT_WIZARD_HIDDEN, // 0xBB + UNUSED_64, // 0xBC + UNUSED_65, // 0xBD + SOLARIS8_BOOT, // 0xBE + UNUSED_66, // 0xBF + CTOS, // 0xC0 + DRDOS_FAT12, // 0xC1 + RESERVED_17, // 0xC2 + HIDDEN_LINUX_SWAP, // 0xC3 + DRDOS_FAT16, // 0xC4 + DRDOS_EXTENDED, // 0xC5 + DRDOS_FAT16_LARGE, // 0xC6 + NTFS_CORRUPT, // 0xC7 + + UNUSED_67, // 0xC8 + UNUSED_68, // 0xC9 + UNUSED_69, // 0xCA + + DRDOS_FAT32, // 0xCB + DRDOS_FAT32_LBA, // 0xCC + CTOS_MEMDUMP, // 0xCD + DRDOS_FAT16_LBA, // 0xCE + UNUSED_70, // 0xCF + REAL32_SECURE_BIG, // 0xD0 + OLD_MULTIUSER_DOS_FAT12,// 0xD1 + UNUSED_71, // 0xD2 + UNUSED_72, // 0xD3 + OLD_MULTIUSER_DOS_FAT16,// 0xD4 + OLD_MULTIUSER_DOS_EXTENDED,// 0xD5 + OLD_MULTIUSER_DOS_FAT16_LARGE,// 0xD6 + UNUSED_73, // 0xD7 + CPM86, // 0xD8 + UNUSED_74, // 0xD9 + NON_FS_DATA, // 0xDA + DIGITAL_RESEARCH_CPM, // 0xDB + UNUSED_75, // 0xDC + HIDDEN_CTOS_MEMDUMP, // 0xDD + DELL_POWEREDGE, // 0xDE + DGUX, // 0xDF + STM_AVFS, // 0xE0 + SPEEDSTOR_FAT_EXTENDED,// 0xE1 + UNUSED_76, // 0xE2 + SPEEDSTOR_RO, // 0xE3 + SPEEDSTOR_EXTENDED, // 0xE4 + TANDY_DOS, // 0xE5 + + RESERVED_18, // 0xE6 + UNUSED_77, // 0xE7 + UNUSED_78, // 0xE8 + UNUSED_79, // 0xE9 + + BEFS, // 0xEA + SPRYTIX, // 0xEB + EFI_PROTECTIVE, // 0xEE + EFI_SYSTEM, // 0xEF + LINUX_PA_RISC, // 0xF0 + SPEEDSTOR_2, // 0xF1 + DOS_3_3_SECONDARY, // 0xF2 + RESERVED_19, // 0xF3 + SPEEDSTOR_LARGE, // 0xF4 + PROLOGUE_MULTI, // 0xF5 + RESERVED_20, // 0xF6 + UNUSED_80, // 0xF7 + UNUSED_81, // 0xF8 + UNUSED_82, // 0xF9 + BOCHS, // 0xFA + VMFS, // 0xFB + VMWARE_SWAP, // 0xFC + LINUX_RAID_AUTO, // 0xFD + NT_HIDDEN, // 0xFE + XENIX_BAD_BLOCK // 0xFF + }; + /** * @struct PartitionTableEntry * @brief Stores information about a partition diff --git a/kernel/src/filesystem/partition/msdos.cpp b/kernel/src/filesystem/partition/msdos.cpp index 08d98d24..f486781c 100644 --- a/kernel/src/filesystem/partition/msdos.cpp +++ b/kernel/src/filesystem/partition/msdos.cpp @@ -35,9 +35,22 @@ void MSDOSPartition::mount_partitions(Disk* hd) { if(entry.type == 0) continue; - Logger::DEBUG() << "Partition 0x" << (uint64_t)entry.type << " at 0x" << (uint64_t)entry.start_LBA << "\n"; + Logger::DEBUG() << "Partition 0x" << (uint64_t)entry.type << " at 0x" << (uint64_t)entry.start_LBA << ": "; - //TODO: Create a new FAT32 object + // Create a file system for the partition + switch ((PartitionType)entry.type) + { + case PartitionType::EMPTY: + Logger::Out() << "Empty partition\n"; + break; + case PartitionType::FAT32: + Logger::Out() << "FAT32 partition\n"; + break; + + default: + Logger::Out() << "Unknown or unimplemented partition type: " << entry.type << "\n"; + + } } } diff --git a/kernel/src/kernel.cpp b/kernel/src/kernel.cpp index 03ab1523..79b3eb4b 100644 --- a/kernel/src/kernel.cpp +++ b/kernel/src/kernel.cpp @@ -92,4 +92,9 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic // Idle loop (read Idle.md) while (true) asm("nop"); -} \ No newline at end of file +} + +// TODO: +// - Define a vfs structure & filesystem structure +// - Implement FAT32 +// - Implement ext2 \ No newline at end of file From 8d6052061240789b70d55981bb594e47d97de418 Mon Sep 17 00:00:00 2001 From: maxtyson123 <98maxt98@gmail.com> Date: Sun, 20 Apr 2025 22:19:45 +1200 Subject: [PATCH 04/38] Virtual File System --- kernel/CMakeLists.txt | 8 +- kernel/include/common/string.h | 9 +- kernel/include/filesystem/filesystem.h | 90 +++++- kernel/include/filesystem/vfs.h | 51 +++ kernel/src/common/string.cpp | 88 ++++++ kernel/src/filesystem/filesystem.cpp | 342 ++++++++++++++++++++ kernel/src/filesystem/vfs.cpp | 414 +++++++++++++++++++++++++ kernel/src/kernel.cpp | 20 +- kernel/src/system/multiboot.cpp | 2 +- 9 files changed, 998 insertions(+), 26 deletions(-) create mode 100644 kernel/include/filesystem/vfs.h create mode 100644 kernel/src/filesystem/vfs.cpp diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index 86dcc77d..cb05c1f0 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -14,13 +14,7 @@ SET_SOURCE_FILES_PROPERTIES(${ASM_SRCS} PROPERTIES LANGUAGE ASM) FILE(GLOB_RECURSE KERNEL_SRCS src/*.cpp src/*.s) # Create the kernel -ADD_EXECUTABLE(MaxOSk64 ${KERNEL_SRCS} - include/drivers/disk/ide.h - src/drivers/disk/ide.cpp - include/filesystem/partition/msdos.h - src/filesystem/partition/msdos.cpp - include/drivers/disk/disk.h - src/drivers/disk/disk.cpp) +ADD_EXECUTABLE(MaxOSk64 ${KERNEL_SRCS}) TARGET_COMPILE_DEFINITIONS(MaxOSk64 PUBLIC MAXOS_KERNEL) # Update the version before building diff --git a/kernel/include/common/string.h b/kernel/include/common/string.h index f898ab53..ac5c7c38 100644 --- a/kernel/include/common/string.h +++ b/kernel/include/common/string.h @@ -7,6 +7,8 @@ #include +#include + namespace MaxOS { /** @@ -34,7 +36,12 @@ namespace MaxOS { [[nodiscard]] int length(bool count_ansi = true) const; char* c_str(); - [[nodiscard]] const char* c_str() const; + const char* c_str() const; + + bool starts_with(String const &other); + String substring(int start, int length) const; + common::Vector split(String const &delimiter) const; + [[nodiscard]] String center(int width, char fill = ' ') const; diff --git a/kernel/include/filesystem/filesystem.h b/kernel/include/filesystem/filesystem.h index f6009cdb..c13dd098 100644 --- a/kernel/include/filesystem/filesystem.h +++ b/kernel/include/filesystem/filesystem.h @@ -6,7 +6,9 @@ #define MAXOS_FILESYSTEM_FILESYSTEM_H #include +#include #include +#include namespace MaxOS{ @@ -15,12 +17,96 @@ namespace MaxOS{ enum class SeekType{ SET, - CUR, + CURRENT, END }; + /** + * @class Path + * @brief Handles file & directory paths + */ + class Path + { + public: + static bool vaild(string path); + static string file_name(string path); + static string file_extension(string path); + static string file_path(string path); - // TODO: Redo this class + }; + + /** + * @class File + * @brief Handles file operations and information + */ + class File + { + + private: + uint32_t m_offset; + string m_name; + + public: + File(); + ~File(); + + virtual void write(const uint8_t* data, size_t size); + virtual void read(uint8_t* data, size_t size); + virtual void flush(); + + virtual void seek(SeekType seek_type, size_t offset); + virtual uint32_t position(); + + virtual size_t size(); + virtual string name(); + }; + + /** + * @class Directory + * @brief Handles a group of files (directory) + */ + class Directory + { + private: + common::Vector m_files; + common::Vector m_subdirectories; + + string m_name; + + public: + Directory(); + ~Directory(); + + virtual File* create_file(const string& name); + virtual File* open_file(const string& name); + virtual void remove_file(const string& name); + common::Vector files(); + + virtual Directory* create_subdirectory(const string& name); + virtual Directory* open_subdirectory(const string& name); + virtual void remove_subdirectory(const string& name); + common::Vector subdirectories(); + + virtual string name(); + virtual size_t size(); + }; + + /** + * @class FileSystem + * @brief Handles the disk operations and file system information + */ + class FileSystem + { + + public: + FileSystem(); + ~FileSystem(); + + virtual Directory* root_directory(); + Directory* get_directory(const string& path); + + bool exists(const string& path); + }; } diff --git a/kernel/include/filesystem/vfs.h b/kernel/include/filesystem/vfs.h new file mode 100644 index 00000000..9cf69401 --- /dev/null +++ b/kernel/include/filesystem/vfs.h @@ -0,0 +1,51 @@ +// +// Created by Max Tyson on 20/04/2025. +// + +#ifndef MAXOS_FILESYSTEM_VFS_H +#define MAXOS_FILESYSTEM_VFS_H + +#include +#include + +namespace MaxOS{ + + namespace filesystem{ + + class VirtualFileSystem{ + + private: + common::Map filesystems; + inline static VirtualFileSystem* s_current_file_system = nullptr; + + public: + VirtualFileSystem(); + ~VirtualFileSystem(); + + VirtualFileSystem* current_file_system(); + + void mount_filesystem(FileSystem* filesystem); + void mount_filesystem(FileSystem* filesystem, string mount_point); + void unmount_filesystem(FileSystem* filesystem); + void unmount_filesystem(string mount_point); + void unmount_all(); + + FileSystem* root_filesystem(); + FileSystem* get_filesystem(string mount_point); + FileSystem* find_filesystem(string path); + string get_relative_path(FileSystem* filesystem, string path); + + Directory* root_directory(); + Directory* open_directory(string path); + Directory* create_directory(string path); + void delete_directory(string path); + + File* create_file(string path); + File* open_file(string path); + File* open_file(string path, size_t offset); + void delete_file(string path); + }; + } +} + +#endif //MAXOS_FILESYSTEM_VFS_H diff --git a/kernel/src/common/string.cpp b/kernel/src/common/string.cpp index b3f5428b..95a38767 100644 --- a/kernel/src/common/string.cpp +++ b/kernel/src/common/string.cpp @@ -163,6 +163,94 @@ const char* String::c_str() const { } +/** + * @brief Checks if the string starts with the other string (must contain the same characters in the same order) + * + * @param other The other string + * @return True if the string starts with the other string, false otherwise + */ +bool String::starts_with(String const& other) +{ + + // Must at least be able to fit the other string + if (m_length < other.length()) + return false; + + // Check if the string starts with the other string + for (int i = 0; i < other.length(); i++) + if (m_string[i] != other[i]) + return false; + + // No string left over to check so it must contain other + return true; +} + +/** + * @brief Get a section of the string + * + * @param start The start of the substring + * @param length The length of the substring + * @return The substring or empty string if out of bounds + */ +String String::substring(int start, int length) const +{ + + // Ensure the start is within bounds + if (start < 0 || start >= m_length) + return {}; + + // Ensure the length is within bounds + if (length < 0 || start + length > m_length) + return {}; + + // Allocate memory for the substring (and null terminator) + String substring; + substring.m_length = length; + substring.m_string = new char[length + 1]; + + // Copy the substring + for (int i = 0; i < length; i++) + substring.m_string[i] = m_string[start + i]; + + // Write the null terminator + substring.m_string[length] = '\0'; + + return substring; +} + +/** + * @brief Splits the string by the delimiter + * + * @param delimiter What to split the string by + * @return A vector of strings that were split by the delimiter + */ +common::Vector String::split(String const& delimiter) const +{ + // The vector of strings + common::Vector strings; + + // Go through the string and split it by the delimiter + int start = 0; + int end = 0; + while (end < m_length) { + + // If the character is the delimiter, add the string to the vector + if (m_string[end] == delimiter[0]) { + strings.push_back(substring(start, end - start)); + start = end + 1; + } + + // Increment the end + end++; + } + + // Add the last string to the vector + strings.push_back(substring(start, end - start)); + + // Return the vector of strings + return strings; +} + /** * @brief Returns the length of the string * diff --git a/kernel/src/filesystem/filesystem.cpp b/kernel/src/filesystem/filesystem.cpp index 44defcc4..35822121 100644 --- a/kernel/src/filesystem/filesystem.cpp +++ b/kernel/src/filesystem/filesystem.cpp @@ -7,3 +7,345 @@ using namespace MaxOS; using namespace MaxOS::filesystem; +/** + * @brief Check if a path is valid + * + * @param path The path to check + * @return True if the path is valid, false otherwise + */ +bool Path::vaild(string path) +{ + // Must not be empty + if (path.length() == 0) + return false; + + // Must start with a / + if (path[0] != '/') + return false; + + // Valid + return true; + +} + +/** + * @brief Get the file name component of a path if it exists or an empty string + * + * @param path The path to get the file name from + * @return The file name or the original path if it does not exist + */ +string Path::file_name(string path) +{ + + // Find the last / + int last_slash = -1; + for (int i = 0; i < path.length(); i++) + if (path[i] == '/') + last_slash = i; + + // Make sure there was a slash to split + if (last_slash == -1) + return path; + + // Get the file name + string file_name = path.substring(last_slash + 1, path.length() - last_slash - 1); + return file_name; +} + +/** + * @brief Try to get the file extension from a path (assumes split by ".") + * + * @param path The path to get the file extension from + * @return The file extension or the original path if it does not exist + */ +string Path::file_extension(string path) +{ + + // Find the last . + int last_dot = -1; + for (int i = 0; i < path.length(); i++) + if (path[i] == '.') + last_dot = i; + + // Make sure there was a dot to split + if (last_dot == -1) + return path; + + // Get the file extension (what is after the ".") + string file_extension = path.substring(last_dot + 1, path.length() - last_dot - 1); + return file_extension; + +} + +/** + * @brief Finds the path to the directory the file is in + * + * @param path The path to get the file path from + * @return The file path or the original path if it does not exist + */ +string Path::file_path(string path) +{ + // Try to find the last / + int last_slash = -1; + for (int i = 0; i < path.length(); i++) + if (path[i] == '/') + last_slash = i; + + // Make sure there was a slash to split + if (last_slash == -1) + return path; + + // Get the file path + string file_path = path.substring(0, last_slash); + return file_path; + +} + +File::File() = default; + +File::~File() = default; + +/** + * @brief Write data to the file + * + * @param data The byte buffer to write + * @param size The amount of data to write + */ +void File::write(const uint8_t* data, size_t size) +{ +} + +/** + * @brief Read data from the file + * + * @param data The byte buffer to read into + * @param size The amount of data to read + */ +void File::read(uint8_t* data, size_t size) +{ +} + +/** + * @brief Flush the file to the disk + */ +void File::flush() +{ +} + +/** + * @brief Seek to a position in the file + * + * @param seek_type The type of seek to perform (where to seek to) + * @param offset The amount to seek + */ +void File::seek(SeekType seek_type, size_t offset) +{ +} + +/** + * @brief Get where the file is currently at (amount read/write/seeked) + * + * @return The current position in the file + */ +uint32_t File::position() +{ + return 0; +} + +/** + * @brief Get the size of the file + * + * @return The size of the file (in bytes) + */ +size_t File::size() +{ + return 0; +} + +/** + * @brief Get the name of the file + * + * @return The name of the file + */ +string File::name() +{ + return ""; +} + +Directory::Directory() = default; + +Directory::~Directory() = default; + +/** + * @brief Create a file in the directory + * + * @param name The name of the file to create + * @return A new file object or null if it could not be created + */ +File* Directory::create_file(const string& name) +{ + return nullptr; +} + +/** + * @brief Open a file in the directory + * + * @param name The name of the file to open + * @return + */ +File* Directory::open_file(const string& name) +{ + return nullptr; +} + +/** + * @brief Delete a file in the directory + * + * @param name The name of the file to remove + */ +void Directory::remove_file(const string& name) +{ +} + +/** + * @brief Get the files in the directory + * + * @return A list of all the files in the directory + */ +common::Vector Directory::files() +{ + return m_files; +} + +/** + * @brief Create a directory in the directory + * + * @param name The name of the directory to create + * @return The new directory object or null if it could not be created + */ +Directory* Directory::create_subdirectory(const string& name) +{ + return nullptr; +} + +/** + * @brief Open a directory in the directory + * + * @param name The name of the directory to open + * @return The directory object or null if it could not be opened + */ +Directory* Directory::open_subdirectory(const string& name) +{ + return nullptr; +} + +/** + * @brief Try to remove a directory in the directory + * @param name The name of the directory to remove + */ +void Directory::remove_subdirectory(const string& name) +{ +} + +/** + * @brief Get the subdirectories in the directory + * + * @return The subdirectories in the directory + */ +common::Vector Directory::subdirectories() +{ + return m_subdirectories; +} + +/** + * @brief Get the name of the directory + * + * @return The name of the directory + */ +string Directory::name() +{ + return m_name; +} + +/** + * @brief Get the size of the directory + * + * @return The size of the directory (sum of all the files in bytes) + */ +size_t Directory::size() +{ + return 0; +} + +FileSystem::FileSystem() = default; + +FileSystem::~FileSystem() = default; + +/** + * @brief Get the directory at "/" + * + * @return The root directory of the filesystem + */ +Directory* FileSystem::root_directory() +{ + return nullptr; +} + +/** + * @brief Get the directory at a given path + * + * @param path The path to the directory + * @return The directory object or null if it could not be opened + */ +Directory* FileSystem::get_directory(const string& path) +{ + + // Check if the path is the root + if (path == "/") + return root_directory(); + + // Recursively open the directory + Directory* directory = root_directory(); + string directory_path = path; + while (directory_path.length() > 0) + { + // Get the name of the directory + string directory_name = Path::file_name(directory_path); + + // Open the directory + Directory* subdirectory = directory->open_subdirectory(directory_name); + if (!subdirectory) + return nullptr; + + // Set the new directory + directory = subdirectory; + + // Get the path to the next directory + directory_path = Path::file_path(directory_path); + } + + // Return the directory + return directory; +} + +/** + * @brief Check if a path exists on the filesystem + * + * @param path The path to check + * @return True if the path exists, false otherwise + */ +bool FileSystem::exists(const string& path) +{ + + // Get the directory at the path + string directory_path = Path::file_path(path); + Directory* directory = get_directory(directory_path); + + // Check if the directory exists + if (!directory) + return false; + + // Check if the file exists + const string file_name = Path::file_name(path); + return directory->open_file(file_name) != nullptr; +} \ No newline at end of file diff --git a/kernel/src/filesystem/vfs.cpp b/kernel/src/filesystem/vfs.cpp new file mode 100644 index 00000000..87ee1c5f --- /dev/null +++ b/kernel/src/filesystem/vfs.cpp @@ -0,0 +1,414 @@ +// +// Created by Max Tyson on 20/04/2025. +// +#include +#include + +using namespace MaxOS; +using namespace MaxOS::filesystem; + +VirtualFileSystem::VirtualFileSystem() +{ + + // Set the current file system to this instance + s_current_file_system = this; + +} + +VirtualFileSystem::~VirtualFileSystem() +{ + // Remove all mounted filesystems + unmount_all(); + + // Set the current file system to null + s_current_file_system = nullptr; + +} + +/** + * @brief Get the active virtual file system + * + * @return The current virtual file system or null if none is set + */ +VirtualFileSystem* VirtualFileSystem::current_file_system() +{ + return s_current_file_system; +} + +/** + * @brief Add a filesystem to the virtual file system + * + * @param filesystem The filesystem to add + */ +void VirtualFileSystem::mount_filesystem(FileSystem* filesystem) +{ + + // Check if the filesystem is already mounted + if (filesystems.find(filesystem) != filesystems.end()) + { + Logger::WARNING() << "Filesystem already mounted\n"; + return; + } + + // Get the mount point for the filesystem + string mount_point = "/filesystem_" + filesystems.size(); + + // If this is the first filesystem to be mounted, set the root filesystem + if (filesystems.size() == 0) + mount_point = "/"; + + // Add the filesystem to the map + filesystems.insert(filesystem, mount_point); +} + +/** + * @brief Add a filesystem to the virtual file system at a given mount point + * + * @param filesystem The filesystem to add + * @param mount_point The mount point for the filesystem + */ +void VirtualFileSystem::mount_filesystem(FileSystem* filesystem, string mount_point) +{ + + // Check if the filesystem is already mounted + if (filesystems.find(filesystem) != filesystems.end()) + { + Logger::WARNING() << "Filesystem already mounted at " << mount_point << "\n"; + return; + } + + // Add the filesystem to the map + filesystems.insert(filesystem, mount_point); +} + +/** + * @brief Remove a filesystem from the virtual file system & delete it + * + * @param filesystem The filesystem to remove + */ +void VirtualFileSystem::unmount_filesystem(FileSystem* filesystem) +{ + + // Check if the filesystem is mounted + if (filesystems.find(filesystem) == filesystems.end()) + return; + + // Remove the filesystem from the map + filesystems.erase(filesystem); + + // Delete the filesystem + delete filesystem; +} + +/** + * @brief Remove a filesystem from the virtual file system + * + * @param mount_point Where the filesystem is mounted + */ +void VirtualFileSystem::unmount_filesystem(string mount_point) +{ + // Check if the filesystem is mounted + auto it = filesystems.begin(); + while (it != filesystems.end()) + { + if (it->second == mount_point) + { + // Remove the filesystem from the map + return unmount_filesystem(it->first); + } + ++it; + } + + // Filesystem not found + Logger::WARNING() << "Filesystem not found at " << mount_point << "\n"; +} + +/** + * @brief Remove all mounted filesystems + */ +void VirtualFileSystem::unmount_all() +{ + + // Loop through the filesystems and unmount them + for (auto it = filesystems.begin(); it != filesystems.end();) + { + unmount_filesystem(it->first); + it = filesystems.begin(); + } +} + +/** + * @brief Get the filesystem mounted at the root + * + * @return The file system mounted "/" or null if none is mounted + */ +FileSystem* VirtualFileSystem::root_filesystem() +{ + + // Ensure there is at least one filesystem mounted + if (filesystems.size() == 0) + return nullptr; + + // It is always the first filesystem mounted + return filesystems.begin()->first; +} + +/** + * @brief Get a specific filesystem mounted at a given mount point + * + * @param mount_point The mount point to search for + * @return The filesystem mounted at the given mount point or null if none is found + */ +FileSystem* VirtualFileSystem::get_filesystem(string mount_point) +{ + + // Check if the filesystem is mounted + auto it = filesystems.begin(); + while (it != filesystems.end()) + { + if (it->second == mount_point) + return it->first; + ++it; + } + + // Filesystem not found + return nullptr; +} + +/** + * @brief Find the filesystem that is responsible for a given path + * + * @param path The path to search for + * @return The filesystem that contains the path or null if none is found + */ +FileSystem* VirtualFileSystem::find_filesystem(string path) +{ + + // Longest matching path will be where the filesystem is mounted + string longest_match = ""; + FileSystem* longest_match_fs = nullptr; + + // Search through the filesystems + for (auto it = filesystems.begin(); it != filesystems.end(); ++it) + { + // Get the filesystem and mount point + FileSystem* fs = it->first; + string mount_point = it->second; + + // Check if the path starts with the mount point + if (path.starts_with(mount_point) && mount_point.length() > longest_match.length()) + { + longest_match = mount_point; + longest_match_fs = fs; + } + } + + return longest_match_fs; +} + +/** + * @brief Get the relative path on a filesystem for a given VFS path (ie remove the mount point) + * + * @param filesystem The filesystem to get the path for + * @param path The path to get the relative path for + * @return The relative path on the filesystem or an empty string if none is found + */ +string VirtualFileSystem::get_relative_path(FileSystem* filesystem, string path) +{ + // Find the mount point for the filesystem + auto it = filesystems.find(filesystem); + if (it == filesystems.end()) + return ""; + + // Get the mount point + string mount_point = it->second; + + // Make sure that the path points to the filesystem + if (!path.starts_with(mount_point)) + return ""; + + // Get the relative path + string relative_path = path.substring(0, mount_point.length()); + return relative_path; +} + +/** + * @brief Get the root directory of the virtual file system + * + * @return The root directory of the virtual file system + */ +Directory* VirtualFileSystem::root_directory() +{ + // Get the root filesystem + FileSystem* fs = root_filesystem(); + + // Check if the filesystem is mounted + if (!fs) + return nullptr; + + // Get the root directory + return fs->root_directory(); +} + +/** + * @brief Try to create a directory on the virtual file system + * + * @param path The path to the directory + * @return The directory object or null if it could not be opened + */ +Directory* VirtualFileSystem::open_directory(string path) +{ + // Ensure a valid path is given + if (!Path::vaild(path)) + return nullptr; + + // Try to find the filesystem that is responsible for the path + FileSystem* fs = find_filesystem(path); + if (!fs) + return nullptr; + + // Get where to open the directory + string relative_path = get_relative_path(fs, path); + string directory_path = Path::file_path(relative_path); + + // Open the directory + return fs -> get_directory(directory_path); + +} + +/** + * @brief Try to create a directory on the virtual file system + * + * @param path The path to the directory + * @return The directory object or null if it could not be opened + */ +Directory* VirtualFileSystem::create_directory(string path) +{ + // Ensure a valid path is given + if (!Path::vaild(path)) + return nullptr; + + // Try to find the filesystem that is responsible for the path + FileSystem* fs = find_filesystem(path); + if (!fs) + return nullptr; + + // Open the parent directory + Directory* parent_directory = open_directory(path); + if (!parent_directory) + return nullptr; + + // Create the directory + string directory_name = Path::file_name(path); + return parent_directory -> create_subdirectory(directory_name); +} + +/** + * @brief Delete a directory on the virtual file system + * + * @param path The path to the directory + */ +void VirtualFileSystem::delete_directory(string path) +{ + // Ensure a valid path is given + if (!Path::vaild(path)) + return; + + // Open the directory + Directory* directory = open_directory(path); + if (!directory) + return; + + // Delete the directory + string directory_name = Path::file_name(path); + directory -> remove_subdirectory(directory_name); +} + + +/** + * @brief Try to create a file on the virtual file system + * + * @param path The path to the file (including the extension) + * @return The file object or null if it could not be created + */ +File* VirtualFileSystem::create_file(string path) +{ + // Ensure a valid path is given + if (!Path::vaild(path)) + return nullptr; + + // Open the directory + Directory* directory = open_directory(path); + if (!directory) + return nullptr; + + // Create the file + string file_name = Path::file_name(path); + return directory -> create_file(file_name); +} + +/** + * @brief Try to open a file on the virtual file system + * + * @param path The path to the file (including the extension) + * @return The file object or null if it could not be opened + */ +File* VirtualFileSystem::open_file(string path) +{ + + // Open the file at the start + return open_file(path, 0); + +} + + +/** + * @brief Try to open a file on the virtual file system with a given offset + * + * @param path The path to the file (including the extension) + * @param offset The offset to seek to + * @return + */ +File* VirtualFileSystem::open_file(string path, size_t offset) +{ + // Ensure a valid path is given + if (!Path::vaild(path)) + return nullptr; + + // Open the directory + Directory* directory = open_directory(path); + if (!directory) + return nullptr; + + // Open the file + File* opened_file = directory -> open_file(Path::file_name(path)); + if (!opened_file) + return nullptr; + + // Seek to the offset + opened_file -> seek(SeekType::SET, offset); + + // File opened successfully + return opened_file; +} + +/** + * @brief Delete a file on the virtual file system + * + * @param path The path to the file (including the extension) + */ +void VirtualFileSystem::delete_file(string path) +{ + // Ensure a valid path is given + if (!Path::vaild(path)) + return; + + // Open the directory + Directory* directory = open_directory(path); + if (!directory) + return; + + // Delete the file + string file_name = Path::file_name(path); + directory -> remove_file(file_name); +} \ No newline at end of file diff --git a/kernel/src/kernel.cpp b/kernel/src/kernel.cpp index 79b3eb4b..57684a4f 100644 --- a/kernel/src/kernel.cpp +++ b/kernel/src/kernel.cpp @@ -1,34 +1,21 @@ //Common #include #include - -//Hardware com #include -#include -#include - -//Drivers #include #include #include #include #include #include - -//GUI #include - -//PROCESS #include - -//SYSTEM #include #include #include - -//MEMORY #include #include +#include using namespace MaxOS; @@ -43,6 +30,7 @@ using namespace MaxOS::gui; using namespace MaxOS::processes; using namespace MaxOS::system; using namespace MaxOS::memory; +using namespace MaxOS::filesystem; extern "C" void call_constructors(); extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic) @@ -69,9 +57,10 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic VESABootConsole console(&vesa); Logger::HEADER() << "Stage {2}: Hardware Initialisation\n"; - DriverManager driver_manager; + VirtualFileSystem vfs; CPU cpu(&gdt, &multiboot); Clock kernel_clock(cpu.apic, 1); + DriverManager driver_manager; driver_manager.add_driver(&kernel_clock); driver_manager.find_drivers(); uint32_t reset_wait_time = driver_manager.reset_devices(); @@ -97,4 +86,5 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic // TODO: // - Define a vfs structure & filesystem structure // - Implement FAT32 +// - Userspace Files // - Implement ext2 \ No newline at end of file diff --git a/kernel/src/system/multiboot.cpp b/kernel/src/system/multiboot.cpp index 65597c4a..e2801639 100644 --- a/kernel/src/system/multiboot.cpp +++ b/kernel/src/system/multiboot.cpp @@ -50,7 +50,7 @@ Multiboot::Multiboot(unsigned long address, unsigned long magic) case MULTIBOOT_TAG_TYPE_BOOTDEV: multiboot_tag_bootdev *bootdev; bootdev = (multiboot_tag_bootdev *)tag; - Logger::DEBUG() << "Boot device: 0x" << (uint64_t) bootdev->biosdev << ", 0x" << (uint64_t) bootdev->slice << ", 0x" << (uint64_t) bootdev->part << " of type 0x" << (uint64_t) bootdev->type << "\n"; + Logger::DEBUG() << "Boot device: drive=0x" << (uint64_t)bootdev->biosdev << ", partition=0x" << (uint64_t)bootdev->part << "\n"; break; case MULTIBOOT_TAG_TYPE_MMAP: From 742ebe2acb4e29be0beeb7b9891c23f6f8c3989d Mon Sep 17 00:00:00 2001 From: maxtyson123 <98maxt98@gmail.com> Date: Wed, 23 Apr 2025 17:20:33 +1200 Subject: [PATCH 05/38] Fat 32 Theory Code, Untested --- kernel/include/common/string.h | 1 + kernel/include/filesystem/fat32.h | 164 ++++- kernel/include/filesystem/filesystem.h | 35 +- kernel/include/filesystem/partition/msdos.h | 2 + kernel/include/filesystem/vfs.h | 2 +- kernel/src/common/string.cpp | 12 + kernel/src/filesystem/fat32.cpp | 694 ++++++++++++++++++++ kernel/src/filesystem/filesystem.cpp | 113 ++-- kernel/src/filesystem/partition/msdos.cpp | 4 + kernel/src/kernel.cpp | 21 +- toolchain/copy_filesystem.sh | 5 + 11 files changed, 988 insertions(+), 65 deletions(-) diff --git a/kernel/include/common/string.h b/kernel/include/common/string.h index ac5c7c38..f7384f8b 100644 --- a/kernel/include/common/string.h +++ b/kernel/include/common/string.h @@ -26,6 +26,7 @@ namespace MaxOS { String(); String(char const* string); + String(uint8_t const* string, int length); String(String const &other); String(int value); String(uint64_t value); diff --git a/kernel/include/filesystem/fat32.h b/kernel/include/filesystem/fat32.h index 41fc8d01..3b69acb5 100644 --- a/kernel/include/filesystem/fat32.h +++ b/kernel/include/filesystem/fat32.h @@ -5,11 +5,8 @@ #ifndef MAXOS_FILESYSTEM_FAT32_H #define MAXOS_FILESYSTEM_FAT32_H -#include "drivers/disk/ata.h" -#include -#include +#include #include -#include #include namespace MaxOS{ @@ -20,7 +17,7 @@ namespace MaxOS{ * @struct BiosParameterBlock * @brief Stores information about the FAT32 filesystem */ - struct BiosParameterBlock32{ + typedef struct BiosParameterBlock32{ uint8_t jump[3]; uint8_t OEM_name[8]; @@ -51,13 +48,28 @@ namespace MaxOS{ uint8_t volume_label[11]; uint8_t file_system_type[8]; - } __attribute__((packed)); + } __attribute__((packed)) bpb32_t; + + /** + * @struct FileSystemInfo + * @brief Stores extra information about the FAT32 filesystem + */ + typedef struct FSInfo + { + uint32_t lead_signature; + uint8_t reserved1[480]; + uint32_t structure_signature; + uint32_t free_cluster_count; + uint32_t next_free_cluster; + uint8_t reserved2[12]; + uint32_t trail_signature; + } __attribute__((packed)) fs_info_t; /** * @struct DirectoryEntry * @brief Stores information about a file or directory */ - struct DirectoryEntry{ + typedef struct DirectoryEntry{ uint8_t name[8]; uint8_t extension[3]; @@ -78,10 +90,144 @@ namespace MaxOS{ uint32_t size; - } __attribute__((packed)); + } __attribute__((packed)) dir_entry_t; + + typedef struct LongFileNameEntry + { + uint8_t order; + uint16_t name1[5]; + uint8_t attributes; + uint8_t type; + uint8_t checksum; + uint16_t name2[6]; + uint16_t zero; + uint16_t name3[2]; + + } __attribute__((packed)) long_file_name_entry_t; + + typedef uint32_t lba_t; + + enum class ClusterState + { + FREE = 0x00000000, + BAD = 0x0FFFFFF7, + END_OF_CHAIN = 0x0FFFFFF8, + }; + + /** + * @class Fat32Volume + * @brief Handles the FAT table that stores the information about the files on the disk and operations on the disk + */ + class Fat32Volume + { + public: + Fat32Volume(drivers::disk::Disk* disk, lba_t partition_offset); + ~Fat32Volume(); + + bpb32_t bpb; + fs_info_t fsinfo; + + uint8_t sectors_per_cluster; + uint16_t bytes_per_sector; + + size_t fat_size; + size_t fat_total_clusters; + lba_t fat_first_sector; + lba_t fat_lba; + lba_t fat_info_lba; + + lba_t data_lba; + lba_t root_lba; + + drivers::disk::Disk* disk; + + lba_t next_cluster(lba_t cluster); + lba_t set_next_cluster(lba_t cluster, lba_t next_cluster); + lba_t find_free_cluster(); + + lba_t allocate_cluster(lba_t cluster); + lba_t allocate_cluster(lba_t cluster, size_t amount); + + void free_cluster(lba_t cluster); + void free_cluster(lba_t cluster, size_t amount); + }; + + /** + * @class Fat32File + * @brief Handles the file operations on the FAT32 filesystem + */ + class Fat32File : public File + { + + private: + Fat32Volume* m_volume; + + lba_t m_first_cluster; + + public: + Fat32File(Fat32Volume* volume, lba_t cluster, size_t size, const string& name); + ~Fat32File() final; + + void write(const uint8_t* data, size_t size) final; + void read(uint8_t* data, size_t size) final; + void flush() final; + + lba_t first_cluster() const { return m_first_cluster; } + }; + + enum class DirectoryEntryType + { + EMPTY = 0x00, + DIRECTORY = 0x10, + FILE = 0x20, + DELETED = 0xE5, + }; + + /** + * @class Fat32Directory + * @brief Handles the directory operations on the FAT32 filesystem + */ + class Fat32Directory : public Directory + { + + private: + Fat32Volume* m_volume; + lba_t m_first_cluster; + + common::Vector m_entries; + + lba_t create_entry(const string& name, bool is_directory); + void remove_entry(lba_t cluster, const string& name); + + public: + Fat32Directory(Fat32Volume* volume, lba_t cluster, const string& name); + ~Fat32Directory() final; + + static const size_t MAX_NAME_LENGTH = 255; + + File* create_file(const string& name) final; + void remove_file(const string& name) final; + + Directory* create_subdirectory(const string& name) final; + void remove_subdirectory(const string& name) final; + + lba_t first_cluster() const { return m_first_cluster; } + }; + + /** + * @class Fat32FileSystem + * @brief Handles the FAT32 filesystem operations + */ + class Fat32FileSystem : public FileSystem + { + private: + Fat32Volume m_volume; + public: + Fat32FileSystem(drivers::disk::Disk* disk, uint32_t partition_offset); + ~Fat32FileSystem() final; + }; - // TODO: Redo FAT32 } } diff --git a/kernel/include/filesystem/filesystem.h b/kernel/include/filesystem/filesystem.h index c13dd098..eae0ed6d 100644 --- a/kernel/include/filesystem/filesystem.h +++ b/kernel/include/filesystem/filesystem.h @@ -42,23 +42,24 @@ namespace MaxOS{ class File { - private: + protected: uint32_t m_offset; string m_name; + size_t m_size; public: File(); - ~File(); + virtual ~File(); virtual void write(const uint8_t* data, size_t size); virtual void read(uint8_t* data, size_t size); virtual void flush(); - virtual void seek(SeekType seek_type, size_t offset); - virtual uint32_t position(); + void seek(SeekType seek_type, size_t offset); + uint32_t position(); - virtual size_t size(); - virtual string name(); + size_t size(); + string name(); }; /** @@ -67,7 +68,7 @@ namespace MaxOS{ */ class Directory { - private: + protected: common::Vector m_files; common::Vector m_subdirectories; @@ -75,20 +76,20 @@ namespace MaxOS{ public: Directory(); - ~Directory(); + virtual ~Directory(); + common::Vector files(); + File* open_file(const string& name); virtual File* create_file(const string& name); - virtual File* open_file(const string& name); virtual void remove_file(const string& name); - common::Vector files(); + common::Vector subdirectories(); + Directory* open_subdirectory(const string& name); virtual Directory* create_subdirectory(const string& name); - virtual Directory* open_subdirectory(const string& name); virtual void remove_subdirectory(const string& name); - common::Vector subdirectories(); - virtual string name(); - virtual size_t size(); + string name(); + size_t size(); }; /** @@ -97,12 +98,14 @@ namespace MaxOS{ */ class FileSystem { + protected: + Directory* m_root_directory; public: FileSystem(); - ~FileSystem(); + virtual ~FileSystem(); - virtual Directory* root_directory(); + Directory* root_directory(); Directory* get_directory(const string& path); bool exists(const string& path); diff --git a/kernel/include/filesystem/partition/msdos.h b/kernel/include/filesystem/partition/msdos.h index 5af35bd0..b0ca908c 100644 --- a/kernel/include/filesystem/partition/msdos.h +++ b/kernel/include/filesystem/partition/msdos.h @@ -7,6 +7,8 @@ #include #include +#include +#include namespace MaxOS{ diff --git a/kernel/include/filesystem/vfs.h b/kernel/include/filesystem/vfs.h index 9cf69401..9c5e48c8 100644 --- a/kernel/include/filesystem/vfs.h +++ b/kernel/include/filesystem/vfs.h @@ -22,7 +22,7 @@ namespace MaxOS{ VirtualFileSystem(); ~VirtualFileSystem(); - VirtualFileSystem* current_file_system(); + static VirtualFileSystem* current_file_system(); void mount_filesystem(FileSystem* filesystem); void mount_filesystem(FileSystem* filesystem, string mount_point); diff --git a/kernel/src/common/string.cpp b/kernel/src/common/string.cpp index 95a38767..b2b374dd 100644 --- a/kernel/src/common/string.cpp +++ b/kernel/src/common/string.cpp @@ -42,6 +42,18 @@ String::String(char const *string) m_string[m_length] = '\0'; } +String::String(uint8_t const* string, int length) +{ + // Allocate memory for the string (and null terminator) + m_string = new char[length + 1]; + + // Copy the string + for (int i = 0; i < length; i++) + m_string[i] = string[i]; + + // Write the null terminator + m_string[length] = '\0'; +} String::String(int value) { diff --git a/kernel/src/filesystem/fat32.cpp b/kernel/src/filesystem/fat32.cpp index 40d4dc97..072af209 100644 --- a/kernel/src/filesystem/fat32.cpp +++ b/kernel/src/filesystem/fat32.cpp @@ -3,9 +3,703 @@ // #include +#include using namespace MaxOS; using namespace MaxOS::common; using namespace MaxOS::drivers; +using namespace MaxOS::drivers::disk; using namespace MaxOS::filesystem; using namespace MaxOS::memory; + +Fat32Volume::Fat32Volume(Disk* hd, uint32_t partition_offset) +: disk(hd) +{ + + // TODO: Table copies? + + // Read the BIOS parameter block + disk -> read(partition_offset, (uint8_t *)&bpb, sizeof(bpb32_t)); + + // Set the volume information + bytes_per_sector = bpb.bytes_per_sector; + sectors_per_cluster = bpb.sectors_per_cluster; + + fat_size = bpb.table_size_32; + fat_total_clusters = bpb.total_sectors_32 - (bpb.reserved_sectors + (bpb.table_copies * fat_size)); + fat_first_sector = bpb.reserved_sectors; + fat_lba = partition_offset + fat_first_sector; + fat_info_lba = partition_offset + bpb.fat_info; + disk -> read(fat_info_lba, (uint8_t *)&fsinfo, sizeof(fs_info_t)); + + + data_lba = fat_lba + (bpb.table_copies * fat_size); + root_lba = data_lba + sectors_per_cluster * (bpb.root_cluster - 2); + + // Validate the fat information + if (fsinfo.lead_signature != 0x41615252 || fsinfo.structure_signature != 0x61417272 || fsinfo.trail_signature != 0xAA550000) + { + Logger::ERROR() << "Invalid FAT32 filesystem information TODO: Handle this\n"; + return; + } + +} + +Fat32Volume::~Fat32Volume() = default; + + +/** + * @brief Take the cluster and gets the next cluster in the chain + * + * @param cluster The base cluster to start from + * @return The next cluster in the chain + */ +lba_t Fat32Volume::next_cluster(lba_t cluster) +{ + + // Get the location in the FAT table + uint32_t offset = cluster * sizeof(uint32_t); + uint32_t sector = fat_first_sector + (offset / bytes_per_sector); + uint32_t entry = offset % bytes_per_sector; + + // Read the FAT entry + uint8_t fat[bytes_per_sector]; + disk -> read(sector, fat, bytes_per_sector); + + // Get the next cluster info (mask the upper 4 bits) + uint32_t next_cluster = *(uint32_t *)&fat[entry]; + return next_cluster & 0x0FFFFFFF; +} + +/** + * @brief Sets the next cluster in the chain (where the base cluster should point) + * + * @param cluster The base cluster to start from + * @param next_cluster The next cluster in the chain + * @return The next cluster in the chain + */ +lba_t Fat32Volume::set_next_cluster(lba_t cluster, lba_t next_cluster) +{ + + // Get the location in the FAT table + uint32_t offset = cluster * sizeof(uint32_t); + uint32_t sector = fat_first_sector + (offset / bytes_per_sector); + uint32_t entry = offset % bytes_per_sector; + + // Read the FAT entry + uint8_t fat[bytes_per_sector]; + disk -> read(sector, fat, bytes_per_sector); + + // Set the next cluster info (mask the upper 4 bits) + *(uint32_t *)&fat[entry] = next_cluster & 0x0FFFFFFF; + disk -> write(sector, fat, bytes_per_sector); + + // Return the next cluster + return next_cluster; + +} + +/** + * @brief Searches the fat table for a free cluster starting from the first free cluster in the fsinfo, will then wrap around + * + * @return The first free cluster in the FAT table + */ +lba_t Fat32Volume::find_free_cluster() +{ + + // Get the first free cluster + for (lba_t start = fsinfo.next_free_cluster; start < fat_total_clusters + 1; start++) + if (next_cluster(start) == 0) + return start; + + // Check any clusters before the first free cluster + for (lba_t start = 0; start < fsinfo.next_free_cluster; start++) + if (next_cluster(start) == 0) + return start; + + + // No free clusters found + ASSERT(false, "No free clusters found in the FAT table"); + return 0; +} + +/** + * @brief Allocate a cluster in the FAT table + * + * @param cluster The base cluster to start from or 0 if this is a new chain + * @return The next cluster in the chain + */ +lba_t Fat32Volume::allocate_cluster(lba_t cluster) +{ + + // Allocate 1 cluster + allocate_cluster(cluster, 1); + + +} + +/** + * @brief Allocate a number of clusters in the FAT table + * + * @param cluster The base cluster to start from or 0 if this is a new chain + * @param amount The number of clusters to allocate + * @return The next cluster in the chain + */ +lba_t Fat32Volume::allocate_cluster(lba_t cluster, size_t amount) +{ + + // Make sure within bounds + if (cluster > fat_total_clusters || cluster + amount > fat_total_clusters) + return 0; + + // Go through allocating the clusters + for (size_t i = 0; i < amount; i++) + { + // Get the next cluster + lba_t next_cluster = find_free_cluster(); + + // Update the fsinfo + fsinfo.next_free_cluster = next_cluster + 1; + fsinfo.free_cluster_count -= 1; + + // Update the chain (if this is an existing chain) + if (cluster != 0) + set_next_cluster(cluster, next_cluster); + + // Move to the next cluster + cluster = next_cluster; + } + + // Save the fsinfo + disk -> write(fat_info_lba, (uint8_t *)&fsinfo, sizeof(fs_info_t)); + + // Mark the end of the chain + set_next_cluster(cluster, (lba_t)ClusterState::END_OF_CHAIN); + + // Return the cluster + return cluster; +} + +/** + * @brief Free a cluster in the FAT table + * + * @param cluster The base cluster to start from + * @param full Weather the chain's length is 1 or not + */ +void Fat32Volume::free_cluster(lba_t cluster) +{ + + // Free 1 cluster + free_cluster(cluster, 1); + +} + +/** + * @brief Free a number of clusters in the FAT table + * + * @param cluster The base cluster to start from + * @param amount The number of clusters to free + */ +void Fat32Volume::free_cluster(lba_t cluster, size_t amount) +{ + + // Make sure within bounds + if (cluster < 2 || cluster > fat_total_clusters || cluster + amount > fat_total_clusters) + return; + + // Go through freeing the clusters + for (size_t i = 0; i < amount; i++) + { + + // Get the next cluster + lba_t next_in_chain = next_cluster(cluster); + + // Update the fsinfo + fsinfo.next_free_cluster = cluster; + fsinfo.free_cluster_count += 1; + + // Update the chain + set_next_cluster(cluster, (lba_t)ClusterState::FREE); + cluster = next_in_chain; + } + + // Save the fsinfo + disk -> write(fat_info_lba, (uint8_t *)&fsinfo, sizeof(fs_info_t)); + + // Mark the end of the chain + set_next_cluster(cluster, (lba_t)ClusterState::END_OF_CHAIN); +} + + +Fat32File::Fat32File(Fat32Volume* volume, lba_t cluster, size_t size, const string& name) +: m_volume(volume), + m_first_cluster(cluster) +{ + + // Set the base file information + m_name = name; + m_size = size; + m_offset = 0; +} + +Fat32File::~Fat32File() = default; + +/** + * @brief Write data to the file + * + * @param data The byte buffer to write + * @param size The amount of data to write + */ +void Fat32File::write(const uint8_t* data, size_t size) +{ + File::write(data, size); +} + +/** + * @brief Read data from the file + * + * @param data The byte buffer to read into + * @param size The amount of data to read + */ +void Fat32File::read(uint8_t* data, size_t size) +{ + File::read(data, size); +} + +/** + * @brief Flush the file to the disk + */ +void Fat32File::flush() +{ + File::flush(); +} + +Fat32Directory::Fat32Directory(Fat32Volume* volume, lba_t cluster, const string& name) +: m_volume(volume), + m_first_cluster(cluster) +{ + + // Set the name + m_name = name; + + // Read the directory + while (cluster >= 2 && cluster < (lba_t)ClusterState::BAD) + { + + // Get the buffer and address to read + uint8_t buffer[volume -> bytes_per_sector * volume -> sectors_per_cluster]; + lba_t lba = volume -> data_lba + (cluster - 2) * volume -> sectors_per_cluster; + + // Read each sector in the cluster + for (size_t sector = 0; sector < volume -> sectors_per_cluster; sector++) + volume -> disk -> read(lba + sector, buffer + sector * volume -> bytes_per_sector, volume -> bytes_per_sector); + + string long_name = ""; + + // Parse the directory entries (each entry is 32 bytes) + for (size_t entry_offset = 0; entry_offset < sizeof(buffer); entry_offset += 32) + { + + // Get the entry + auto entry = (dir_entry_t *)&buffer[entry_offset]; + + // Skip the MacOS specific entries + if (strncmp(string(entry -> extension, 3), "Mac", 3)) + continue; + + // Store the entry + m_entries.push_back(*entry); + + // Check if the entry is the end of the directory + if (entry -> name[0] == (uint8_t)DirectoryEntryType::EMPTY) + return; + + // Check if the entry is free + if (entry -> name[0] == (uint8_t)DirectoryEntryType::DELETED) + continue; + + // Parse the long name entry + if ((entry -> attributes & 0x0F) == 0x0F) + { + + // Get the long name entry + auto* long_name_entry = (long_file_name_entry_t*)entry; + + // Extract the long name from each part (in reverse order) + for (int i = 0; i < 13; i++) { + + // Get the character (in utf8 encoding) + char c; + if (i < 5) + c = long_name_entry->name1[i] & 0xFF; + else if (i < 11) + c = long_name_entry->name2[i - 5] & 0xFF; + else + c = long_name_entry->name3[i - 11] & 0xFF; + + // Check if the character is valid + if (c == '\0' || c == (char)0xFF) + break; + + // Add the character to the long name + long_name = string(c) + long_name; + } + } + + // Get the name of the entry + string name = long_name == "" ? string(entry->name, 8) : long_name; + + // Reset the long name for the next entry + long_name = ""; + + // Get the starting cluster + lba_t start_cluster = (entry -> first_cluster_high << 16) | entry -> first_cluster_low; + + // Store the file or directory + if (entry -> attributes & 0x10) + m_subdirectories.push_back(new Fat32Directory(volume, start_cluster, name)); + else + m_files.push_back(new Fat32File(volume, start_cluster, entry -> size, name)); + } + + // Get the next cluster in the chain + cluster = m_volume -> next_cluster(cluster); + } +} + +Fat32Directory::~Fat32Directory() +{ + + // Free the files + for (auto & file : m_files) + delete file; + + // Free the subdirectories + for (auto & subdirectory : m_subdirectories) + delete subdirectory; +} + +/** + * @brief Create a new entry in the directory + * + * @param name The name of the file or directory to create + * @param is_directory True if the entry is a directory, false if it is a file + * @return The cluster of the new entry + */ +lba_t Fat32Directory::create_entry(const string& name, bool is_directory) +{ + + // Allocate a cluster for the new entry + lba_t cluster = m_volume -> allocate_cluster(0); + if (cluster == 0) + return 0; + + // Store the short name and extension + char short_name[8]; + char short_extension[3]; + for (int i = 0; i < 8; i++) + short_name[i] = (i < name.length()) ? name[i] : ' '; + for (int i = 0; i < 3; i++) + short_extension[i] = (8 + i < name.length()) ? name[8 + i] : ' '; + + // Information for the new long file name entry + size_t lfn_count = (name.length() + 12) / 13; + Vector lfn_entries; + + // Create the long file name entries (in reverse order) + for (int i = lfn_count - 1; i >= 0; i--) + { + // Create the long file name entry + long_file_name_entry_t lfn_entry; + lfn_entry.order = i + 1; + lfn_entry.attributes = 0x0F; + lfn_entry.type = 0; + lfn_entry.checksum = 0; + + // If it is the last entry, set the last bit + if (i == lfn_entries.size() - 1) + lfn_entry.order |= 0x40; + + // Set the name + for (int j = 0; j < 13; j++) + { + + // Get the character info (0xFFFF if out of bounds) + size_t char_index = i * 13 + j; + char c = (char_index < name.length()) ? name[char_index] : 0xFFFF; + + // Set the character in the entry + if (j < 5) + lfn_entry.name1[j] = c; + else if (j < 11) + lfn_entry.name2[j - 5] = c; + else + lfn_entry.name3[j - 11] = c; + } + + // Add the entry to the list + lfn_entries.push_back(lfn_entry); + } + + // Create the directory entry + dir_entry_t entry; + memcpy(entry.name, short_name, sizeof(short_name)); + memcpy(entry.extension, short_extension, sizeof(short_extension)); + entry.attributes = is_directory ? 0x10 : 0x20; + entry.first_cluster_high = (cluster >> 16) & 0xFFFF; + entry.first_cluster_low = cluster & 0xFFFF; + + // Find free space for the new entry and the name + size_t entries_needed = 1 + lfn_entries.size(); + size_t entry_index = 0; + for (size_t i = 0; i < m_entries.size(); i++) + { + // Check if there are enough free entries in a row + bool found = true; + for (size_t j = 0; j < entries_needed; j++) + if (m_entries[i + j].name[0] != 0x00 && m_entries[i + j].name[0] != 0xE5) + found = false; + + // If free space is found, set the entry index + if (found) + { + entry_index = i; + break; + } + } + + // Store the entries in the cache + for (size_t i = 0; i < entries_needed; i++) + { + if (i == 0) + m_entries[entry_index + i] = entry; + else + m_entries[entry_index + i] = *(dir_entry_t *)&lfn_entries[i - 1]; + } + + // Get where to write the entry + lba_t lba = m_volume -> data_lba + (m_first_cluster - 2) * m_volume -> sectors_per_cluster; + uint32_t offset = entry_index * sizeof(dir_entry_t); + + // Write the long file name entries + for (auto& lfn_entry : lfn_entries) + { + // Write the entry to the disk + m_volume -> disk -> write(lba + offset, (uint8_t *)&lfn_entry, sizeof(long_file_name_entry_t)); + offset += sizeof(long_file_name_entry_t); + } + + // Write the directory entry + m_volume -> disk -> write(lba + offset, (uint8_t *)&entry, sizeof(dir_entry_t)); + + // Creating the file is done + if (!is_directory) + return cluster; + + // Create the "." in the directory + dir_entry_t current_dir_entry; + memcpy(current_dir_entry.name, ".", 1); + current_dir_entry.attributes = 0x10; + current_dir_entry.first_cluster_high = (cluster >> 16) & 0xFFFF; + current_dir_entry.first_cluster_low = cluster & 0xFFFF; + + // Create the ".." in the directory + dir_entry_t parent_dir_entry; + memcpy(parent_dir_entry.name, "..", 2); + parent_dir_entry.attributes = 0x10; + parent_dir_entry.first_cluster_high = (m_first_cluster >> 16) & 0xFFFF; + parent_dir_entry.first_cluster_low = m_first_cluster & 0xFFFF; + + // Write the entries to the disk + lba_t child_lba = m_volume -> data_lba + (cluster - 2) * m_volume -> sectors_per_cluster; + m_volume -> disk -> write(child_lba, (uint8_t *)¤t_dir_entry, sizeof(dir_entry_t)); + m_volume -> disk -> write(child_lba + sizeof(dir_entry_t), (uint8_t *)&parent_dir_entry, sizeof(dir_entry_t)); + + // Directory created + return cluster; +} + +/** + * @brief Remove an entry from the directory + * + * @param cluster The cluster of the entry to remove + * @param name The name of the entry to remove + */ +void Fat32Directory::remove_entry(lba_t cluster, const string& name) +{ + + // Find the entry in the directory + size_t entry_index = 0; + for (size_t i = 0; i < m_entries.size(); i++) + { + auto& entry = m_entries[i]; + + // End of directory means no more entries + if (entry.name[0] == (uint8_t)DirectoryEntryType::EMPTY) + return; + + // Skip deleted entries + if (entry.name[0] == (uint8_t)DirectoryEntryType::DELETED) + continue; + + // Check if the entry is the one to remove + lba_t start_cluster = (entry.first_cluster_high << 16) | entry.first_cluster_low; + if (start_cluster == cluster) + { + entry_index = i; + break; + } + } + + // Make sure the entry is valid + if (entry_index >= m_entries.size()) + return; + + // Find any lfn entries that belong to this entry + size_t delete_entry_index = entry_index; + while (delete_entry_index > 0 && (m_entries[delete_entry_index - 1].attributes & 0x0F) == 0x0F) + delete_entry_index--; + + // Mark the entries as deleted + for (size_t i = delete_entry_index; i < entry_index; i++) + m_entries[i].name[0] = (uint8_t)DirectoryEntryType::DELETED; + + // Update the entries on the disk + lba_t first_directory_entry_lba = m_volume -> data_lba + (m_first_cluster - 2) * m_volume -> sectors_per_cluster; + for (size_t i = delete_entry_index; i <= entry_index; i++) + { + // Get the offset to write the entry + uint32_t offset = (i * sizeof(dir_entry_t)) / m_volume -> bytes_per_sector; + m_volume -> disk -> write(first_directory_entry_lba + offset, (uint8_t *)&m_entries[i], sizeof(dir_entry_t)); + } + + // Count the number of clusters in the chain + size_t cluster_count = 0; + lba_t next_cluster = cluster; + while (next_cluster >= 2 && next_cluster < (lba_t)ClusterState::BAD) + { + cluster_count++; + next_cluster = m_volume -> next_cluster(next_cluster); + } + + // Free the clusters in the chain + m_volume -> free_cluster(cluster, cluster_count); + +} + +/** + * @brief Create a new file in the directory + * + * @param name The name of the file to create + * @return The new file object or null if it could not be created + */ +File* Fat32Directory::create_file(const string& name) +{ + + // Check if the file already exists + for (auto & file : m_files) + if (file -> name() == name) + return nullptr; + + // Check if the name is too long + if (name.length() > MAX_NAME_LENGTH) + return nullptr; + + // Create the file + auto file = new Fat32File(m_volume, create_entry(name, false), 0, name); + m_files.push_back(file); + return file; +} + + +void Fat32Directory::remove_file(const string& name) +{ + // Find the file if it exists + for (auto& file : m_files) + if (file -> name() == name) + { + // Remove the file from the directory + m_files.erase(file); + + // Remove the entry + remove_entry(((Fat32File*)file) -> first_cluster(), name); + + // Delete the file + delete file; + return; + } +} + +/** + * @brief Create a new directory in the directory + * + * @param name The name of the directory to create + * @return The new directory object or null if it could not be created + */ +Directory* Fat32Directory::create_subdirectory(const string& name) +{ + + // Check if the directory already exists + for (auto & subdirectory : m_subdirectories) + if (subdirectory -> name() == name) + return nullptr; + + // Check if the name is too long + if (name.length() > MAX_NAME_LENGTH) + return nullptr; + + // Create the directory + auto directory = new Fat32Directory(m_volume, create_entry(name, true), name); + m_subdirectories.push_back(directory); + return directory; + +} + +/** + * @brief Remove a directory entry from the directory + * + * @param name The name of the entry to remove + */ +void Fat32Directory::remove_subdirectory(const string& name) +{ + // Find the directory if it exists + for (auto& subdirectory : m_subdirectories) + if (subdirectory -> name() == name) + { + + // Remove all the files in the directory + for (auto& file : subdirectory -> files()) + subdirectory -> remove_file(file -> name()); + + // Remove all the subdirectories in the directory + for (auto& subdirectory : subdirectory -> subdirectories()) + subdirectory -> remove_subdirectory(subdirectory -> name()); + + // Remove the directory from this directory + m_subdirectories.erase(subdirectory); + + // Remove the entry + remove_entry(((Fat32Directory*)subdirectory) -> first_cluster(), name); + + // Delete the directory + delete subdirectory; + return; + } +} + + +Fat32FileSystem::Fat32FileSystem(Disk* disk, uint32_t partition_offset) +: m_volume(disk, partition_offset) +{ + + // Create the root directory + m_root_directory = new Fat32Directory(&m_volume, m_volume.root_lba, "/"); + +} + +Fat32FileSystem::~Fat32FileSystem(){ + + // Free the root directory + delete m_root_directory; + +} \ No newline at end of file diff --git a/kernel/src/filesystem/filesystem.cpp b/kernel/src/filesystem/filesystem.cpp index 35822121..386dee08 100644 --- a/kernel/src/filesystem/filesystem.cpp +++ b/kernel/src/filesystem/filesystem.cpp @@ -140,6 +140,23 @@ void File::flush() */ void File::seek(SeekType seek_type, size_t offset) { + + // Seek based on the type + switch (seek_type) + { + case SeekType::SET: + m_offset = offset; + break; + + case SeekType::CURRENT: + m_offset += offset; + break; + + case SeekType::END: + m_offset = size() - offset; + break; + } + } /** @@ -149,27 +166,27 @@ void File::seek(SeekType seek_type, size_t offset) */ uint32_t File::position() { - return 0; + return m_offset; } /** - * @brief Get the size of the file + * @brief Get the name of the file * - * @return The size of the file (in bytes) + * @return The name of the file */ -size_t File::size() +string File::name() { - return 0; + return m_name; } /** - * @brief Get the name of the file + * @brief Get the size of the file * - * @return The name of the file + * @return The size of the file (in bytes) */ -string File::name() +size_t File::size() { - return ""; + return m_size; } Directory::Directory() = default; @@ -177,55 +194,61 @@ Directory::Directory() = default; Directory::~Directory() = default; /** - * @brief Create a file in the directory + * @brief Get the files in the directory * - * @param name The name of the file to create - * @return A new file object or null if it could not be created + * @return A list of all the files in the directory */ -File* Directory::create_file(const string& name) +common::Vector Directory::files() { - return nullptr; + return m_files; } /** * @brief Open a file in the directory * * @param name The name of the file to open - * @return + * @return */ File* Directory::open_file(const string& name) { + + // Try to find the file + for (auto& file : m_files) + if (file->name() == name) + return file; + + // File not found return nullptr; } /** - * @brief Delete a file in the directory + * @brief Create a file in the directory * - * @param name The name of the file to remove + * @param name The name of the file to create + * @return A new file object or null if it could not be created */ -void Directory::remove_file(const string& name) +File* Directory::create_file(const string& name) { + return nullptr; } /** - * @brief Get the files in the directory + * @brief Delete a file in the directory * - * @return A list of all the files in the directory + * @param name The name of the file to remove */ -common::Vector Directory::files() +void Directory::remove_file(const string& name) { - return m_files; } /** - * @brief Create a directory in the directory + * @brief Get the subdirectories in the directory * - * @param name The name of the directory to create - * @return The new directory object or null if it could not be created + * @return The subdirectories in the directory */ -Directory* Directory::create_subdirectory(const string& name) +common::Vector Directory::subdirectories() { - return nullptr; + return m_subdirectories; } /** @@ -236,27 +259,36 @@ Directory* Directory::create_subdirectory(const string& name) */ Directory* Directory::open_subdirectory(const string& name) { + + // Try to find the directory + for (auto& subdirectory : m_subdirectories) + if (subdirectory->name() == name) + return subdirectory; + + // Directory not found return nullptr; } /** - * @brief Try to remove a directory in the directory - * @param name The name of the directory to remove + * @brief Create a directory in the directory + * + * @param name The name of the directory to create + * @return The new directory object or null if it could not be created */ -void Directory::remove_subdirectory(const string& name) +Directory* Directory::create_subdirectory(const string& name) { + return nullptr; } /** - * @brief Get the subdirectories in the directory - * - * @return The subdirectories in the directory + * @brief Try to remove a directory in the directory + * @param name The name of the directory to remove */ -common::Vector Directory::subdirectories() +void Directory::remove_subdirectory(const string& name) { - return m_subdirectories; } + /** * @brief Get the name of the directory * @@ -274,7 +306,14 @@ string Directory::name() */ size_t Directory::size() { - return 0; + + // Sum the size of all the files + size_t size = 0; + for (auto& file : m_files) + size += file->size(); + + return size; + } FileSystem::FileSystem() = default; @@ -288,7 +327,7 @@ FileSystem::~FileSystem() = default; */ Directory* FileSystem::root_directory() { - return nullptr; + return m_root_directory; } /** diff --git a/kernel/src/filesystem/partition/msdos.cpp b/kernel/src/filesystem/partition/msdos.cpp index f486781c..0a1afa3e 100644 --- a/kernel/src/filesystem/partition/msdos.cpp +++ b/kernel/src/filesystem/partition/msdos.cpp @@ -27,6 +27,9 @@ void MSDOSPartition::mount_partitions(Disk* hd) { return; } + // Get the VFS + VirtualFileSystem* vfs = VirtualFileSystem::current_file_system(); + // Loop through the primary partitions for(auto& entry : mbr.primary_partition){ @@ -46,6 +49,7 @@ void MSDOSPartition::mount_partitions(Disk* hd) { case PartitionType::FAT32: Logger::Out() << "FAT32 partition\n"; + vfs -> mount_filesystem(new Fat32FileSystem(hd, entry.start_LBA)); break; default: diff --git a/kernel/src/kernel.cpp b/kernel/src/kernel.cpp index 57684a4f..557cfabd 100644 --- a/kernel/src/kernel.cpp +++ b/kernel/src/kernel.cpp @@ -72,6 +72,21 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic driver_manager.initialise_drivers(); driver_manager.activate_drivers(); + + // FS Tests + Directory* root = vfs.root_directory(); + ASSERT(root != nullptr, "Root directory is null\n"); + for (auto& file : root->files()) + { + Logger::DEBUG() << "File: " << file->name() << "\n"; + Logger::DEBUG() << "Size: " << file->size() << "\n"; + } + for (auto& directory : root->subdirectories()) + { + Logger::DEBUG() << "Directory: " << directory->name() << "\n"; + Logger::DEBUG() << "Size: " << directory->size() << "\n"; + } + Logger::HEADER() << "Stage {4}: System Finalisation\n"; Scheduler scheduler(multiboot); SyscallManager syscalls; @@ -84,7 +99,9 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic } // TODO: -// - Define a vfs structure & filesystem structure // - Implement FAT32 +// - Test FAT32, When created I think it recursively reads all the subdirectories so it will be slow - need to open on demand instead // - Userspace Files -// - Implement ext2 \ No newline at end of file +// - Implement ext2 +// - Class & Struct docstrings +// - Logo on fail in center \ No newline at end of file diff --git a/toolchain/copy_filesystem.sh b/toolchain/copy_filesystem.sh index ab477aee..e8a38c2c 100755 --- a/toolchain/copy_filesystem.sh +++ b/toolchain/copy_filesystem.sh @@ -27,6 +27,11 @@ sudo rm -rf "$DESTINATION/boot" && sudo cp -r $SCRIPTDIR/../filesystem/boot sudo rm -rf "$DESTINATION/os" && sudo cp -r $SCRIPTDIR/../filesystem/os $DESTINATION sudo rm -rf "$DESTINATION/user" && sudo cp -r $SCRIPTDIR/../filesystem/user $DESTINATION +# If MacOS is used remove the MacOS folder +if [ "$USE_ISO" -eq 1 ]; then + sudo rm -rf "$DESTINATION/MacOS" +fi + # Sync filesystem msg "Syncing filesystem" sudo sync From bbf5c37f2a08c60628e0005dd93b99fff5364f8d Mon Sep 17 00:00:00 2001 From: maxtyson123 <98maxt98@gmail.com> Date: Thu, 1 May 2025 22:23:13 +1200 Subject: [PATCH 06/38] First half code and comment clean up --- kernel/include/common/eventHandler.h | 5 + kernel/include/common/logger.h | 1 - kernel/include/common/string.h | 1 + kernel/include/drivers/disk/ata.h | 2 - kernel/include/drivers/peripherals/keyboard.h | 6 + kernel/include/drivers/peripherals/mouse.h | 6 +- kernel/include/drivers/video/vesa.h | 3 - kernel/include/filesystem/fat32.h | 2 +- kernel/include/system/syscalls.h | 34 +- kernel/src/common/colour.cpp | 8 +- kernel/src/common/graphicsContext.cpp | 19 +- kernel/src/common/logger.cpp | 38 +-- kernel/src/common/spinlock.cpp | 10 - kernel/src/common/string.cpp | 130 +++----- kernel/src/drivers/clock/clock.cpp | 37 +-- kernel/src/drivers/console/console.cpp | 63 ++-- kernel/src/drivers/console/serial.cpp | 2 - kernel/src/drivers/console/textmode.cpp | 22 +- kernel/src/drivers/console/vesaboot.cpp | 35 +-- kernel/src/drivers/disk/ata.cpp | 30 +- kernel/src/drivers/disk/ide.cpp | 5 +- kernel/src/drivers/driver.cpp | 2 +- kernel/src/drivers/ethernet/amd_am79c973.cpp | 4 + kernel/src/drivers/ethernet/intel_i217.cpp | 6 + kernel/src/drivers/peripherals/keyboard.cpp | 28 +- kernel/src/drivers/peripherals/mouse.cpp | 42 ++- kernel/src/drivers/video/vesa.cpp | 74 ++--- kernel/src/drivers/video/vga.cpp | 24 +- kernel/src/drivers/video/video.cpp | 20 +- kernel/src/filesystem/fat32.cpp | 87 ++---- kernel/src/filesystem/filesystem.cpp | 1 - kernel/src/filesystem/partition/msdos.cpp | 3 +- kernel/src/gui/widgets/button.cpp | 3 - kernel/src/hardwarecommunication/apic.cpp | 153 +++++---- .../src/hardwarecommunication/interrupts.cpp | 63 ++-- kernel/src/hardwarecommunication/pci.cpp | 12 +- kernel/src/memory/memorymanagement.cpp | 91 +++--- kernel/src/memory/physical.cpp | 292 +++++++----------- kernel/src/processes/ipc.cpp | 3 +- kernel/src/system/syscalls.cpp | 8 +- 40 files changed, 570 insertions(+), 805 deletions(-) diff --git a/kernel/include/common/eventHandler.h b/kernel/include/common/eventHandler.h index 4a82b0f0..57b3cb8b 100644 --- a/kernel/include/common/eventHandler.h +++ b/kernel/include/common/eventHandler.h @@ -8,6 +8,11 @@ #include #include + + +// TODO: - Event doesnt need to be a class should be a struct +// - With moving to micro kernel this should be moved to a external lib as not needed in the kernel + namespace MaxOS{ namespace common{ diff --git a/kernel/include/common/logger.h b/kernel/include/common/logger.h index d9f822dd..18dbcaa1 100644 --- a/kernel/include/common/logger.h +++ b/kernel/include/common/logger.h @@ -58,7 +58,6 @@ void set_log_level(LogLevel log_level); void write_char(char c) final; - void lineFeed() final; void printf(const char* format, ...); static void ASSERT(bool condition, const char* message, ...); diff --git a/kernel/include/common/string.h b/kernel/include/common/string.h index f7384f8b..e668baaf 100644 --- a/kernel/include/common/string.h +++ b/kernel/include/common/string.h @@ -21,6 +21,7 @@ namespace MaxOS { int m_length = 0; [[nodiscard]] static int lex_value(String const &other) ; + void allocate_self(); public: diff --git a/kernel/include/drivers/disk/ata.h b/kernel/include/drivers/disk/ata.h index 9f0e49e4..b9bfc5ef 100644 --- a/kernel/include/drivers/disk/ata.h +++ b/kernel/include/drivers/disk/ata.h @@ -45,8 +45,6 @@ namespace MaxOS{ void write(uint32_t sector, const uint8_t* data, size_t count) final; void flush() final; - void activate() final; - string device_name() final; string vendor_name() final; }; diff --git a/kernel/include/drivers/peripherals/keyboard.h b/kernel/include/drivers/peripherals/keyboard.h index 0e6f95e9..db0712b9 100644 --- a/kernel/include/drivers/peripherals/keyboard.h +++ b/kernel/include/drivers/peripherals/keyboard.h @@ -22,6 +22,12 @@ namespace MaxOS namespace peripherals{ + enum class ScanCodeType : int{ + REGULAR, + EXTENDED, + EXTENDED_BUFFER + }; + enum class KeyCode : uint16_t{ // Alphabet diff --git a/kernel/include/drivers/peripherals/mouse.h b/kernel/include/drivers/peripherals/mouse.h index f07daa4b..b0abd178 100644 --- a/kernel/include/drivers/peripherals/mouse.h +++ b/kernel/include/drivers/peripherals/mouse.h @@ -86,9 +86,9 @@ namespace MaxOS { void handle_interrupt() final; - uint8_t buffer[3] = {}; - uint8_t offset = 0; - uint8_t buttons = 0; + uint8_t m_buffer[3] = {}; + uint8_t m_offset = 0; + uint8_t m_buttons = 0; public: MouseDriver(); diff --git a/kernel/include/drivers/video/vesa.h b/kernel/include/drivers/video/vesa.h index 7b5ac4b4..3fb56234 100644 --- a/kernel/include/drivers/video/vesa.h +++ b/kernel/include/drivers/video/vesa.h @@ -25,9 +25,6 @@ namespace MaxOS { */ class VideoElectronicsStandardsAssociation : public VideoDriver { - private: - static bool init(); - protected: bool internal_set_mode(uint32_t width, uint32_t height, uint32_t) final; diff --git a/kernel/include/filesystem/fat32.h b/kernel/include/filesystem/fat32.h index 3b69acb5..65bf17c5 100644 --- a/kernel/include/filesystem/fat32.h +++ b/kernel/include/filesystem/fat32.h @@ -177,7 +177,7 @@ namespace MaxOS{ enum class DirectoryEntryType { - EMPTY = 0x00, + LAST = 0x00, DIRECTORY = 0x10, FILE = 0x20, DELETED = 0xE5, diff --git a/kernel/include/system/syscalls.h b/kernel/include/system/syscalls.h index 8b712009..a2415f62 100644 --- a/kernel/include/system/syscalls.h +++ b/kernel/include/system/syscalls.h @@ -21,7 +21,7 @@ namespace MaxOS{ class SyscallManager; /// DO NOT REARRANGE ONLY APPEND TO - enum SyscallType{ + enum class SyscallType{ CLOSE_PROCESS, KLOG, CREATE_SHARED_MEMORY, @@ -44,7 +44,7 @@ namespace MaxOS{ uint64_t arg4; uint64_t arg5; uint64_t return_value; - system::cpu_status_t* return_state; + cpu_status_t* return_state; } syscall_args_t; // Could use a class based response but a single class might want multiple handlers e.g. fs @@ -65,25 +65,25 @@ namespace MaxOS{ SyscallManager(); ~SyscallManager(); - system::cpu_status_t* handle_interrupt(system::cpu_status_t* esp) final; + cpu_status_t* handle_interrupt(cpu_status_t* esp) final; - void set_syscall_handler(uint8_t syscall, syscall_func_t handler); - void remove_syscall_handler(uint8_t syscall); + void set_syscall_handler(SyscallType syscall, syscall_func_t handler); + void remove_syscall_handler(SyscallType syscall); // Syscalls - static system::syscall_args_t* syscall_close_process(system::syscall_args_t* args); - static system::syscall_args_t* syscall_klog(system::syscall_args_t* args); - static system::syscall_args_t* syscall_create_shared_memory(system::syscall_args_t* args); - static system::syscall_args_t* syscall_open_shared_memory(system::syscall_args_t* args); - static system::syscall_args_t* syscall_allocate_memory(system::syscall_args_t* args); - static system::syscall_args_t* syscall_free_memory(system::syscall_args_t* args); - static system::syscall_args_t* syscall_create_ipc_endpoint(system::syscall_args_t* args); - static system::syscall_args_t* syscall_send_ipc_message(system::syscall_args_t* args); - static system::syscall_args_t* syscall_remove_ipc_endpoint(system::syscall_args_t* args); - static system::syscall_args_t* syscall_thread_yield(system::syscall_args_t* args); - static system::syscall_args_t* syscall_thread_sleep(system::syscall_args_t* args); - static system::syscall_args_t* syscall_thread_close(system::syscall_args_t* args); + static syscall_args_t* syscall_close_process(syscall_args_t* args); + static syscall_args_t* syscall_klog(syscall_args_t* args); + static syscall_args_t* syscall_create_shared_memory(syscall_args_t* args); + static syscall_args_t* syscall_open_shared_memory(syscall_args_t* args); + static syscall_args_t* syscall_allocate_memory(syscall_args_t* args); + static syscall_args_t* syscall_free_memory(syscall_args_t* args); + static syscall_args_t* syscall_create_ipc_endpoint(syscall_args_t* args); + static syscall_args_t* syscall_send_ipc_message(syscall_args_t* args); + static syscall_args_t* syscall_remove_ipc_endpoint(syscall_args_t* args); + static syscall_args_t* syscall_thread_yield(syscall_args_t* args); + static syscall_args_t* syscall_thread_sleep(syscall_args_t* args); + static syscall_args_t* syscall_thread_close(syscall_args_t* args); }; } diff --git a/kernel/src/common/colour.cpp b/kernel/src/common/colour.cpp index c9511289..283edc49 100644 --- a/kernel/src/common/colour.cpp +++ b/kernel/src/common/colour.cpp @@ -31,7 +31,7 @@ Colour::Colour(ConsoleColour colour) { parse_console_colour(colour); } -Colour::Colour(MaxOS::string string) { +Colour::Colour(string string) { if(string[0] == '#') parse_hex_string(string); @@ -52,9 +52,9 @@ void Colour::parse_hex_string(string hex_string) { return; // Parse the red, green and blue values - red = (hex_string[1] - '0') * 16 + (hex_string[2] - '0'); + red = (hex_string[1] - '0') * 16 + (hex_string[2] - '0'); green = (hex_string[3] - '0') * 16 + (hex_string[4] - '0'); - blue = (hex_string[5] - '0') * 16 + (hex_string[6] - '0'); + blue = (hex_string[5] - '0') * 16 + (hex_string[6] - '0'); // Parse the alpha value alpha = 255; @@ -77,8 +77,6 @@ void Colour::parse_ansi_string(string ansi_string) { // Parse the colour uint8_t colour = ansi_string[5] - '0'; - - // Set the colour switch (colour) { case 0: diff --git a/kernel/src/common/graphicsContext.cpp b/kernel/src/common/graphicsContext.cpp index 9febe0b8..684db137 100644 --- a/kernel/src/common/graphicsContext.cpp +++ b/kernel/src/common/graphicsContext.cpp @@ -414,21 +414,13 @@ void GraphicsContext::putPixel(int32_t x, int32_t y, uint32_t colour) { */ Colour GraphicsContext::get_pixel(int32_t x, int32_t y) { - // Check if the pixel is within the m_width of the screen - if (0 > x || (uint32_t)x >= m_width) { - return {0,0,0}; - } - - // Check if the pixel is within the m_height of the screen - if (0 > y || (uint32_t) y >= m_height) { + // Check if the pixel is within the bounds of the screen + if (0 > x || (uint32_t)x >= m_width || 0 > y || (uint32_t) y >= m_height) return {0,0,0}; - } // Get the pixel and convert it to a colour uint32_t translated_color = get_rendered_pixel(x, mirror_y_axis ? m_height - y - 1 : y); return int_to_colour(translated_color); - - } /** @@ -438,13 +430,14 @@ Colour GraphicsContext::get_pixel(int32_t x, int32_t y) { * @param y The y coordinate of the pixel */ void GraphicsContext::invert_pixel(int32_t x, int32_t y) { + // Get the pixel Colour colour = get_pixel(x, y); // Invert the pixel - colour.red = 255 - colour.red; - colour.green = 255 - colour.green; - colour.blue = 255 - colour.blue; + colour.red = 255 - colour.red; + colour.green = 255 - colour.green; + colour.blue = 255 - colour.blue; // Render the pixel put_pixel(x, y, colour); diff --git a/kernel/src/common/logger.cpp b/kernel/src/common/logger.cpp index 6d13246b..886bba73 100644 --- a/kernel/src/common/logger.cpp +++ b/kernel/src/common/logger.cpp @@ -77,7 +77,7 @@ void Logger::set_log_level(LogLevel log_level) { // Update the progress bar if(log_level == LogLevel::INFO){ VESABootConsole::update_progress_bar((m_progress_current * 100) / s_progress_total); - m_progress_current ++; + m_progress_current++; } // Print the header @@ -138,10 +138,8 @@ Logger& Logger::Out() { */ Logger Logger::HEADER(){ - // Set the log level to task s_active_logger->set_log_level(LogLevel::HEADER); - // Return the logger return Out(); } @@ -152,12 +150,9 @@ Logger Logger::HEADER(){ */ Logger Logger::INFO() { - // Set the log level to info s_active_logger->set_log_level(LogLevel::INFO); - // Return the logger return Out(); - } /** @@ -166,10 +161,8 @@ Logger Logger::INFO() { */ Logger Logger::DEBUG() { - // Set the log level to debug s_active_logger->set_log_level(LogLevel::DEBUG); - // Return the logger return Out(); } @@ -180,10 +173,8 @@ Logger Logger::DEBUG() { */ Logger Logger::WARNING() { - // Set the log level to warning s_active_logger->set_log_level(LogLevel::WARNING); - // Return the logger return Out(); } @@ -194,10 +185,8 @@ Logger Logger::WARNING() { */ Logger Logger::ERROR() { - // Set the log level to error s_active_logger->set_log_level(LogLevel::ERROR); - // Return the logger return Out(); } @@ -207,7 +196,7 @@ Logger Logger::ERROR() { * * @return The active logger */ -Logger *Logger::active_logger() { +Logger* Logger::active_logger() { return s_active_logger; } @@ -286,29 +275,10 @@ void Logger::ASSERT(bool condition, char const *message, ...) { * @param log_level The log level to set * @return This logger */ -Logger& Logger::operator<<(LogLevel log_level) { +Logger& Logger::operator << (LogLevel log_level) { - // Set the log level set_log_level(log_level); - // Return this logger return *this; -} - -/** - * @brief Handles a line feed for different log levels - */ -void Logger::lineFeed() { - - switch (s_active_logger -> m_log_level) { - case LogLevel::HEADER: - case LogLevel::INFO: - case LogLevel::DEBUG: - case LogLevel::WARNING: - case LogLevel::ERROR: - write_char('\n'); - - } - -} +} \ No newline at end of file diff --git a/kernel/src/common/spinlock.cpp b/kernel/src/common/spinlock.cpp index 660d6e8d..f6f3f39b 100644 --- a/kernel/src/common/spinlock.cpp +++ b/kernel/src/common/spinlock.cpp @@ -21,13 +21,8 @@ Spinlock::~Spinlock() = default; */ void Spinlock::lock() { - // Wait for the lock to be available acquire(); - - // Set the lock to be locked m_locked = true; - - } /** @@ -35,10 +30,7 @@ void Spinlock::lock() { */ void Spinlock::unlock() { - // Set the lock to be unlocked m_locked = false; - - // Release the lock release(); } @@ -63,8 +55,6 @@ void Spinlock::acquire() { if(m_should_yield) Scheduler::system_scheduler()->yield(); - // don't optimise this loop - asm("nop"); } } diff --git a/kernel/src/common/string.cpp b/kernel/src/common/string.cpp index b2b374dd..c0973e89 100644 --- a/kernel/src/common/string.cpp +++ b/kernel/src/common/string.cpp @@ -9,7 +9,7 @@ String::String() { // String that only contains the null terminator - m_string = new char[1]; + allocate_self(); m_string[0] = '\0'; m_length = 1; @@ -22,10 +22,7 @@ String::String(char const *string) m_length = 0; while (string[m_length] != '\0' && m_length <= 10000) m_length++; - - - // Allocate memory for the string (and null terminator) - m_string = new char[m_length + 1]; + allocate_self(); // Copy the string for (int i = 0; i < m_length; i++) @@ -38,14 +35,14 @@ String::String(char const *string) m_string[m_length - 52 + i] = warning[i]; - // Write the null terminator m_string[m_length] = '\0'; } String::String(uint8_t const* string, int length) { // Allocate memory for the string (and null terminator) - m_string = new char[length + 1]; + m_length = length; + allocate_self(); // Copy the string for (int i = 0; i < length; i++) @@ -66,13 +63,10 @@ String::String(uint64_t value) { String::String(float value) { - - } String::String(String const &other) { - // Copy the other string copy(other); } @@ -91,11 +85,9 @@ String::~String() { */ void String::copy(String const &other) { - // Get the length of the string - m_length = other.length(); - // Allocate memory for the string (and null terminator) - m_string = new char[m_length + 1]; + m_length = other.length(); + allocate_self(); // Copy the string for (int i = 0; i < m_length; i++) @@ -114,18 +106,24 @@ void String::copy(String const &other) { */ int String::lex_value(String const &string) { - // The sum of the ascii values of the characters in the string + // Sum the ascii values of the characters in the string int sum = 0; - - // Add the ascii values of the characters in the string for (int i = 0; i < string.length(); i++) sum += string[i]; - // Return the sum return sum; - } +/** + * @brief Allocates memory for the string + */ +void String::allocate_self() +{ + + // Create space for this string and the null terminator + m_string = new char[m_length + 1]; + +} /** @@ -158,7 +156,6 @@ String &String::operator = (String const &other) { */ char* String::c_str() { - // Return the string return m_string; } @@ -170,7 +167,6 @@ char* String::c_str() { */ const char* String::c_str() const { - // Return the string return m_string; } @@ -218,7 +214,7 @@ String String::substring(int start, int length) const // Allocate memory for the substring (and null terminator) String substring; substring.m_length = length; - substring.m_string = new char[length + 1]; + substring.allocate_self(); // Copy the substring for (int i = 0; i < length; i++) @@ -238,28 +234,25 @@ String String::substring(int start, int length) const */ common::Vector String::split(String const& delimiter) const { - // The vector of strings common::Vector strings; // Go through the string and split it by the delimiter int start = 0; int end = 0; - while (end < m_length) { + for ( ; end < m_length; end++) { - // If the character is the delimiter, add the string to the vector - if (m_string[end] == delimiter[0]) { - strings.push_back(substring(start, end - start)); - start = end + 1; - } + // Not splitting + if (m_string[end] != delimiter[0]) + continue; - // Increment the end - end++; + // Add the splice of the string + strings.push_back(substring(start, end - start)); + start = end + 1; } // Add the last string to the vector strings.push_back(substring(start, end - start)); - // Return the vector of strings return strings; } @@ -282,10 +275,9 @@ int String::length(bool count_ansi) const { while (m_string[total_length] != '\0'){ // If the character is an ansi character, skip it - if (m_string[total_length] == '\033'){ + if (m_string[total_length] == '\033') while (m_string[total_length] != 'm') total_length++; - } // Increment the length clean_length++; @@ -339,12 +331,12 @@ bool String::operator == (String const &other) const { */ bool String::operator != (String const &other) const { - // If the strings are equal, return false + // Self assignment check if (*this == other) return false; - // The strings are not equal - return true; + + return !equals(other); } @@ -356,7 +348,6 @@ bool String::operator != (String const &other) const { */ bool String::operator < (String const &other) const { - // If the sum of this is less than the sum of the other, return true return lex_value(*this) < lex_value(other); } @@ -369,7 +360,6 @@ bool String::operator < (String const &other) const { */ bool String::operator > (String const &other) const { - // If the sum of this is greater than the sum of the other, return true return lex_value(*this) > lex_value(other); } @@ -382,7 +372,6 @@ bool String::operator > (String const &other) const { */ bool String::operator <= (String const &other) const { - // If the sum of this is less than or equal to the sum of the other, return true return lex_value(*this) <= lex_value(other); } @@ -395,7 +384,6 @@ bool String::operator <= (String const &other) const { */ bool String::operator >= (String const &other) const { - // If the sum of this is greater than or equal to the sum of the other, return true return lex_value(*this) >= lex_value(other); } @@ -410,13 +398,8 @@ String String::operator + (String const &other) const { // The concatenated string String concatenated; - - // The length of the concatenated string - int length = m_length + other.length(); - concatenated.m_length = length; - - // Allocate memory for the concatenated string (and null terminator) - concatenated.m_string = new char[length + 1]; + concatenated.m_length = m_length + other.length(); + concatenated.allocate_self(); // Copy the first string for (int i = 0; i < m_length; i++) @@ -427,7 +410,7 @@ String String::operator + (String const &other) const { concatenated.m_string[m_length + i] = other[i]; // Write the null terminator - concatenated.m_string[length] = '\0'; + concatenated.m_string[m_length] = '\0'; // Return the concatenated string return concatenated; @@ -443,13 +426,8 @@ String &String::operator += (String const &other) { // The concatenated string String concatenated; - - // The length of the concatenated string - int length = m_length + other.length(); - concatenated.m_length = length; - - // Allocate memory for the concatenated string (and null terminator) - concatenated.m_string = new char[length + 1]; + concatenated.m_length = m_length + other.length(); + concatenated.allocate_self(); // Copy the first string for (int i = 0; i < m_length; i++) @@ -460,7 +438,7 @@ String &String::operator += (String const &other) { concatenated.m_string[m_length + i] = other[i]; // Write the null terminator - concatenated.m_string[length] = '\0'; + concatenated.m_string[m_length] = '\0'; // Free the old memory delete[] m_string; @@ -504,13 +482,8 @@ String String::operator*(int times) const { // The repeated string String repeated; - - // The length of the repeated string - int length = m_length * times; - repeated.m_length = length; - - // Allocate memory for the repeated string (and null terminator) - repeated.m_string = new char[length + 1]; + repeated.m_length *= times; + repeated.allocate_self(); // Copy the string for (int i = 0; i < times; i++) @@ -518,7 +491,7 @@ String String::operator*(int times) const { repeated.m_string[i * m_length + j] = m_string[j]; // Write the null terminator - repeated.m_string[length] = '\0'; + repeated.m_string[m_length] = '\0'; // Return the repeated string return repeated; @@ -534,39 +507,30 @@ String String::operator*(int times) const { */ String String::center(int width, char fill) const { - // The centered string - String centered; - - // The length of the string - int length = m_length; - // The number of characters to add - int add = (width - length) / 2; + int add = (width - m_length) / 2; - // The length of the centered string + // The centered string + String centered; centered.m_length = width; + centered.allocate_self(); - // Allocate memory for the centered string (and null terminator) - centered.m_string = new char[width + 1]; - - // Fill the string with the fill character + // Fill the right side (before) for (int i = 0; i < add; i++) centered.m_string[i] = fill; - // Copy the string - for (int i = 0; i < length; i++) + // Copy the string (middle) + for (int i = 0; i < m_length; i++) centered.m_string[add + i] = m_string[i]; - // Fill the string with the fill character - for (int i = add + length; i < width; i++) + // Fill the left side (after) + for (int i = add + m_length; i < width; i++) centered.m_string[i] = fill; // Write the null terminator centered.m_string[width] = '\0'; - // Return the centered string return centered; - } /** diff --git a/kernel/src/drivers/clock/clock.cpp b/kernel/src/drivers/clock/clock.cpp index a9c72eba..386e16b6 100644 --- a/kernel/src/drivers/clock/clock.cpp +++ b/kernel/src/drivers/clock/clock.cpp @@ -42,7 +42,6 @@ Event* ClockEventHandler::on_event(Event* event) { break; } - // Return the event return event; } @@ -70,20 +69,20 @@ Clock::~Clock() = default; */ void Clock::handle_interrupt() { - // Increment the number of ticks and decrement the number of ticks until the next event + // Clock has ticked m_ticks++; m_ticks_until_next_event--; - // If the number of ticks until the next event is not 0 then return + // Dont raise events until needed if(m_ticks_until_next_event != 0) return; - // Otherwise, reset the number of ticks until the next event - m_ticks_until_next_event = m_ticks_between_events; - // Raise the time event // Time time = get_time(); // raise_event(new TimeEvent(&time)); + + // Reset + m_ticks_until_next_event = m_ticks_between_events; } @@ -95,10 +94,8 @@ void Clock::handle_interrupt() { */ uint8_t Clock::read_hardware_clock(uint8_t address) { - // Send the address to the hardware clock - m_command_port.write(address); - // read the value from the hardware clock + m_command_port.write(address); return m_data_port.read(); } @@ -110,11 +107,11 @@ uint8_t Clock::read_hardware_clock(uint8_t address) */ uint8_t Clock::binary_representation(uint8_t number) const { - // If the binary coded decimal representation is not used, return the number + // Check if the conversion needed if(m_binary) return number; - // Otherwise, return the binary representation + // Convert to the binary represnation return ((number / 16) * 10) + (number & 0x0f); } @@ -124,10 +121,10 @@ uint8_t Clock::binary_representation(uint8_t number) const { */ void Clock::activate() { - // read the status register + // Get the stats from the clock uint8_t status = read_hardware_clock(0xB); - // Set the clock information + // Store the clock status m_24_hour_clock = status & 0x02; m_binary = status & 0x04; @@ -150,10 +147,8 @@ void Clock::delay(uint32_t milliseconds) const { // Round the number of milliseconds UP to the nearest clock accuracy uint64_t rounded_milliseconds = (milliseconds + s_clock_accuracy - 1) / s_clock_accuracy; - // Calculate the number of ticks until the delay is over + // Wait until the time has passed uint64_t ticks_until_delay_is_over = m_ticks + rounded_milliseconds; - - // Wait until the number of ticks is equal to the number of ticks until the delay is over while(m_ticks < ticks_until_delay_is_over) asm volatile("nop"); } @@ -192,14 +187,14 @@ void Clock::calibrate(uint64_t ms_per_tick) { PIT pit(m_apic); uint32_t ticks_per_ms = pit.ticks_per_ms(); - // Set the timer vector to 0x20 and configure it for periodic mode + // Configure the clock to periodic mode uint32_t lvt = 0x20 | (1 << 17); m_apic -> local_apic() -> write(0x320, lvt); // Set the initial count m_apic -> local_apic() -> write(0x380, ms_per_tick * ticks_per_ms); - // Clear the mask bit + // Clear the interrupt mask for the clock lvt &= ~(1 << 16); m_apic -> local_apic() -> write(0x380, lvt); @@ -217,10 +212,8 @@ common::Time Clock::get_time() { while(read_hardware_clock(0xA) & 0x80) asm volatile("nop"); - // Create a time object + // Read the time from the clock Time time{}; - - // read the time from the hardware clock time.year = binary_representation(read_hardware_clock(0x9)) + 2000; time.month = binary_representation(read_hardware_clock(0x8)); time.day = binary_representation(read_hardware_clock(0x7)); @@ -232,8 +225,6 @@ common::Time Clock::get_time() { if(!m_24_hour_clock && (time.hour & 0x80) != 0) time.hour = ((time.hour & 0x7F) + 12) % 24; - - //Raise the clock event return time; } diff --git a/kernel/src/drivers/console/console.cpp b/kernel/src/drivers/console/console.cpp index ada5e196..3f4c878a 100644 --- a/kernel/src/drivers/console/console.cpp +++ b/kernel/src/drivers/console/console.cpp @@ -131,17 +131,15 @@ void Console::put_string(uint16_t x, uint16_t y, string string, ConsoleColour fo // Print each character on the screen for(int i = 0; i < string.length(); i++) - put_character(x + i, y, string[i], foreground, background); + put_character(x + i, y, string[i], foreground, background); } /** - * @brief Scroll the console up by 1 line + * @brief Scroll the entire console up by 1 line */ void Console::scroll_up() { - // Scroll the console up by 1 line scroll_up(0, 0, width(), height()); - } /** @@ -157,32 +155,22 @@ void Console::scroll_up() { */ void Console::scroll_up(uint16_t left, uint16_t top, uint16_t width, uint16_t height, ConsoleColour foreground, ConsoleColour background, char fill) { - // For each line in the area to scroll (except the last line) - for(uint32_t y = top; y < top+height-1; y++){ - - // For each character in the line - for(uint32_t x = left; x < left+width; x++) { - - // Put the character from the line below - put_character(x, y, get_character(x, y + 1), - get_foreground_color(x, y + 1), - get_background_color(x, y + 1)); + // Shift everything but the last line by getting what is below it + for(uint32_t y = top; y < top+height-1; y++) + for(uint32_t x = left; x < left+width; x++) + put_character(x, y, get_character(x, y + 1), get_foreground_color(x, y + 1), get_background_color(x, y + 1)); - } - } - - // Fill the last line with the fill character + // Fill the last line for(uint16_t x = left; x < left+width; x++) put_character(x, top + height - 1, fill, foreground, background); } /** - * Clear the console + * Clear the entire console */ void Console::clear() { - // Clear the console clear(0, 0, width(), height()); } @@ -200,11 +188,14 @@ void Console::clear() { */ void Console::clear(uint16_t left, uint16_t top, uint16_t width, uint16_t height, ConsoleColour foreground, ConsoleColour background, char fill) { - // Put the fill character in the areas - for(uint16_t y = top; y < top+height; y++) - for(uint16_t x = left; x < left+width; x++){ + // Check bounds + if ( left > this -> width() || top > this -> height() || width > this -> width() || height > this -> height() ) + return; + + // Fill the area with the character + for(uint16_t y = top; y < top + height; y++) + for(uint16_t x = left; x < left + width; x++) put_character(x, y, fill, foreground, background); - } } @@ -247,7 +238,7 @@ ConsoleArea::ConsoleArea(Console *console, uint16_t left, uint16_t top, uint16_t m_height(height) { - // Loop through the area setting the colors + // Store the colours of the console for(uint16_t y = top; y < top+height; y++) for(uint16_t x = left; x < left+width; x++){ console->set_foreground_color(x, y, foreground); @@ -286,13 +277,12 @@ uint16_t ConsoleArea::height() { */ void ConsoleArea::put_character(uint16_t x, uint16_t y, char c) { - // Make sure the coordinates are within the console area + // Check bounds if(x >= m_width || y >= m_height) return; - // Put the character on the console + // Write to the console m_console->put_character(m_left + x, m_top + y, c); - } @@ -389,7 +379,7 @@ ConsoleColour ConsoleArea::get_background_color(uint16_t x, uint16_t y) { */ void ConsoleArea::scroll_up() { - // Get the console + m_console->scroll_up(m_left, m_top, m_width, m_height); } @@ -446,13 +436,11 @@ void ConsoleStream::write_char(char c) { switch (c) { // New line case '\n': - // Increment the y coordinate but if it is more than the height of the console scroll the console + // Go to the next line, if it is more then what the console can fit then scroll if(++m_cursor_y >= m_console->height()){ - - // Scroll the console m_console->scroll_up(); - // Decrement the y coordinate + // Ensure there is space at the bottom to write m_cursor_y = m_console->height()-1; } @@ -461,23 +449,22 @@ void ConsoleStream::write_char(char c) { // Carriage return case '\r': - // Go to the start of the next line m_cursor_x = 0; break; - // Null Terminator + // End of string case '\0': break; // Backspace case '\b': - // Decrement the x coordinate m_cursor_x--; break; // Tab case '\t': - // Figure out how many spaces to the next tab stop + + // Pad with spaces until a tab is reached spaces = 8 - (m_cursor_x % 8); for(uint16_t i = 0; i < spaces; i++) write_char(' '); @@ -487,7 +474,7 @@ void ConsoleStream::write_char(char c) { // Put the character on the console m_console->put_character(m_cursor_x, m_cursor_y, c); - // Increment the x coordinate + // Increment the x coordinate (ANSI aren't rendered) if(!is_ansi) m_cursor_x++; diff --git a/kernel/src/drivers/console/serial.cpp b/kernel/src/drivers/console/serial.cpp index ffd36cd7..f7f25a0a 100644 --- a/kernel/src/drivers/console/serial.cpp +++ b/kernel/src/drivers/console/serial.cpp @@ -46,8 +46,6 @@ SerialConsole::SerialConsole(Logger* logger) // Set the active serial console logger->add_log_writer(this); - - } SerialConsole::~SerialConsole() { diff --git a/kernel/src/drivers/console/textmode.cpp b/kernel/src/drivers/console/textmode.cpp index 8dd4769a..ef7db3b3 100644 --- a/kernel/src/drivers/console/textmode.cpp +++ b/kernel/src/drivers/console/textmode.cpp @@ -42,14 +42,14 @@ uint16_t TextModeConsole::height() */ void TextModeConsole::put_character(uint16_t x, uint16_t y, char c) { - // If the coordinates are out of bounds, return + // Check bounds if(x >= width() || y >= height()) return; // Calculate the offset - int offset = (y*width() + x); + int offset = (y * width() + x); - // Set the character at the offset, by masking the character with the current character (last 8 bits) + // Set the character at the offset, mask away the colour information m_video_memory[offset] = (m_video_memory[offset] & 0xFF00) | (uint16_t)c; } @@ -63,14 +63,14 @@ void TextModeConsole::put_character(uint16_t x, uint16_t y, char c) { */ void TextModeConsole::set_foreground_color(uint16_t x, uint16_t y, ConsoleColour foreground) { - // If the coordinates are out of bounds, return + // Check bounds if(x >= width() || y >= height()) return; // Calculate the offset int offset = (y* width() + x); - // Set the foreground color at the offset, by masking the foreground color with the current foreground color (bits 8-11) + // Set the foreground color at the offset, mask to get the foreground bits m_video_memory[offset] = (m_video_memory[offset] & 0xF0FF) | ((uint16_t)foreground << 8); } @@ -83,14 +83,14 @@ void TextModeConsole::set_foreground_color(uint16_t x, uint16_t y, ConsoleColour */ void TextModeConsole::set_background_color(uint16_t x, uint16_t y, ConsoleColour background) { - // If the coordinates are out of bounds, return + // Check bounds if(x >= width() || y >= height()) return; // Calculate the offset int offset = (y* width() + x); - // Set the background color at the offset, by masking the background color with the current background color (bits 12-15) + // Set the background color at the offset, mask to get the backgroun bits m_video_memory[offset] = (m_video_memory[offset] & 0x0FFF) | ((uint16_t)background << 12); } @@ -104,14 +104,14 @@ void TextModeConsole::set_background_color(uint16_t x, uint16_t y, ConsoleColour */ char TextModeConsole::get_character(uint16_t x, uint16_t y) { - // If the coordinates are out of bounds, return + // Check bounds if(x >= width() || y >= height()) return ' '; // Calculate the offset int offset = (y* width() + x); - // Return the character at the offset, by masking the character with the current character (last 8 bits) + // Return the character at the offset, mask away the colour information return (char)(m_video_memory[offset] & 0x00FF); } @@ -124,7 +124,7 @@ char TextModeConsole::get_character(uint16_t x, uint16_t y) { */ ConsoleColour TextModeConsole::get_foreground_color(uint16_t x, uint16_t y) { - // If the coordinates are out of bounds, return + // Check bounds if(x >= width() || y >= height()) return ConsoleColour::White; @@ -144,7 +144,7 @@ ConsoleColour TextModeConsole::get_foreground_color(uint16_t x, uint16_t y) { */ ConsoleColour TextModeConsole::get_background_color(uint16_t x, uint16_t y) { - // If the coordinates are out of bounds, return + // Check bounds if(x >= width() || y >= height()) return ConsoleColour::Black; diff --git a/kernel/src/drivers/console/vesaboot.cpp b/kernel/src/drivers/console/vesaboot.cpp index 10159dcd..31fa519d 100644 --- a/kernel/src/drivers/console/vesaboot.cpp +++ b/kernel/src/drivers/console/vesaboot.cpp @@ -26,11 +26,10 @@ VESABootConsole::VESABootConsole(GraphicsContext *graphics_context) // Prepare the console VESABootConsole::clear(); print_logo(); - - // Connect to the loggers m_console_area = new ConsoleArea(this, 0, 0, width() / 2 - 25, height(), ConsoleColour::DarkGrey, ConsoleColour::Black); cout = new ConsoleStream(m_console_area); + // Only log to the screen when debugging #ifdef TARGET_DEBUG Logger::active_logger() -> add_log_writer(cout); Logger::INFO() << "Console Stream set up \n"; @@ -80,7 +79,9 @@ void VESABootConsole::put_character(uint16_t x, uint16_t y, char c) { // Do not draw the escape character return; - } else if (ansi_code_length < 8) { + } + + if (ansi_code_length < 8) { // Add the character to the ANSI code ansi_code[ansi_code_length++] = c; @@ -289,20 +290,18 @@ void VESABootConsole::scroll_up(uint16_t left, uint16_t top, uint16_t width, // Get the framebuffer info auto* framebuffer_address = (uint8_t*)s_graphics_context->framebuffer_address(); uint64_t framebuffer_width = s_graphics_context->width(); - uint64_t framebuffer_height = s_graphics_context->height(); uint64_t framebuffer_bpp = s_graphics_context->color_depth(); // in bits per pixel uint64_t bytes_per_pixel = framebuffer_bpp / 8; uint64_t framebuffer_pitch = framebuffer_width * bytes_per_pixel; - // Calculate the number of pixels per line uint16_t line_height = Font::font_height; - // Calculate the number of pixels in the region - uint16_t region_pixel_y = top * line_height; + // Region conversions + uint16_t region_pixel_y = top * line_height; uint16_t region_pixel_height = height * line_height; uint16_t region_pixel_left = left * Font::font_width; uint16_t region_pixel_width = width * Font::font_width; - size_t row_bytes = region_pixel_width * bytes_per_pixel; + size_t row_bytes = region_pixel_width * bytes_per_pixel; // Decide the colour of the pixel ConsoleColour to_set_foreground = CPU::is_panicking ? ConsoleColour::White : get_foreground_color(left, top + height - 1); @@ -369,14 +368,13 @@ void VESABootConsole::print_logo_kernel_panic() { void VESABootConsole::finish() { // Done - Logger::INFO() << "MaxOS Kernel Successfully Booted\n"; + Logger::HEADER() << "MaxOS Kernel Successfully Booted\n"; - // Move COUT to the bottom of the screen + // CPU::PANIC will override a disabled logger so the console should scroll itself into view as it is unknown what + // will be on the screen now and that may mess with the presentation of the text (ie white text on a white background) cout->set_cursor(width(), height()); - // Disable the logger Logger::active_logger()->disable_log_writer(cout); - } /** @@ -386,7 +384,7 @@ void VESABootConsole::finish() { */ void VESABootConsole::update_progress_bar(uint8_t percentage) { - // Must be within bounds + // Check bounds if(percentage > 100) percentage = 100; @@ -403,10 +401,10 @@ void VESABootConsole::update_progress_bar(uint8_t percentage) { uint32_t bottom_y = (s_graphics_context->height()/2 - 80) - logo_height / 2; // Find the bounds - uint32_t start_x = progress_width_cull; - uint32_t start_y = logo_height + progress_spacing; - uint32_t end_x = logo_width - progress_width_cull; - uint32_t end_y = logo_height + progress_height + progress_spacing; + uint32_t start_x = progress_width_cull; + uint32_t start_y = logo_height + progress_spacing; + uint32_t end_x = logo_width - progress_width_cull; + uint32_t end_y = logo_height + progress_height + progress_spacing; // Draw the progress bar for (uint32_t progress_y = start_y; progress_y < end_y; ++progress_y) { @@ -423,11 +421,8 @@ void VESABootConsole::update_progress_bar(uint8_t percentage) { if (progress_x > logo_width * percentage / 100 && !is_border) continue; - - // Draw the pixel s_graphics_context->put_pixel(right_x + progress_x, bottom_y + progress_y, Colour(0xFF, 0xFF, 0xFF)); } } - } diff --git a/kernel/src/drivers/disk/ata.cpp b/kernel/src/drivers/disk/ata.cpp index 2d54299d..0f45d216 100644 --- a/kernel/src/drivers/disk/ata.cpp +++ b/kernel/src/drivers/disk/ata.cpp @@ -37,7 +37,7 @@ bool AdvancedTechnologyAttachment::identify() { // Select the device (master or slave) m_device_port.write(m_is_master ? 0xA0 : 0xB0); - // Reset the HOB (High Order Byte) + // Reset the High Order Byte m_control_port.write(0); // Check if the master is present @@ -57,10 +57,8 @@ bool AdvancedTechnologyAttachment::identify() { m_LBA_mid_port.write(0); m_LBA_high_Port.write(0); - // Send the identify command - m_command_port.write(0x0EC); - // Check if the device is present + m_command_port.write(0x0EC); status = m_command_port.read(); if(status == 0x00) return false; @@ -75,7 +73,7 @@ bool AdvancedTechnologyAttachment::identify() { return false; } - // Read the rest of the data + // Read the rest of the data as a whole sector needs to be read for (uint16_t i = 0; i < 256; ++i) uint16_t data = m_data_port.read(); @@ -106,7 +104,7 @@ void AdvancedTechnologyAttachment::read(uint32_t sector, uint8_t* data_buffer, s m_LBA_mid_port.write((sector & 0x0000FF00) >> 8); m_LBA_high_Port.write((sector & 0x00FF0000) >> 16); - // Send the read command + // Tell the device to prepare for reading m_command_port.write(0x20); // Make sure the device is there @@ -122,11 +120,10 @@ void AdvancedTechnologyAttachment::read(uint32_t sector, uint8_t* data_buffer, s if(status & 0x01) return; - // read the data and store it in the array + // Read the data into the array for(int i = 0; i < amount; i+= 2) { uint16_t read_data = m_data_port.read(); - data_buffer[i] = read_data & 0x00FF; // Place the next byte in the array if there is one @@ -134,7 +131,7 @@ void AdvancedTechnologyAttachment::read(uint32_t sector, uint8_t* data_buffer, s data_buffer[i+1] = (read_data >> 8) & 0x00FF; } - // read the remaining bytes + // Read the remaining bytes as a full sector has to be read for(uint16_t i = amount + (amount % 2); i < m_bytes_per_sector; i+= 2) m_data_port.read(); } @@ -148,7 +145,7 @@ void AdvancedTechnologyAttachment::read(uint32_t sector, uint8_t* data_buffer, s */ void AdvancedTechnologyAttachment::write(uint32_t sector, const uint8_t* data, size_t count){ - // Don't allow writing more han a sector + // Don't allow writing more than a sector if(sector > 0x0FFFFFFF || count > m_bytes_per_sector) return; @@ -177,7 +174,7 @@ void AdvancedTechnologyAttachment::write(uint32_t sector, const uint8_t* data, s m_data_port.write(writeData); } - // write the remaining bytes + // Write the remaining bytes as a full sector has to be written for(int i = count + (count%2); i < m_bytes_per_sector; i += 2) m_data_port.write(0x0000); } @@ -202,18 +199,11 @@ void AdvancedTechnologyAttachment::flush() { while (((status & 0x80) == 0x80) && ((status & 0x01) != 0x01)) status = m_command_port.read(); + // Check for an error if(status & 0x01) return; -} - -/** - * @brief Activate the ATA device by mounting it to the virtual file system - */ -void AdvancedTechnologyAttachment::activate() { - - - + // ... } /** diff --git a/kernel/src/drivers/disk/ide.cpp b/kernel/src/drivers/disk/ide.cpp index 05f6b38b..fc39ba67 100644 --- a/kernel/src/drivers/disk/ide.cpp +++ b/kernel/src/drivers/disk/ide.cpp @@ -41,10 +41,8 @@ void IntegratedDriveElectronicsController::initialise() for(auto& device : devices) { - // Get the ata device - auto ata_device = device.first; - // Check if the device is present + auto ata_device = device.first; if(ata_device == nullptr) continue; @@ -70,7 +68,6 @@ void IntegratedDriveElectronicsController::initialise() void IntegratedDriveElectronicsController::activate() { - // Loop through the devices and load the partitions for(auto& device : devices) { diff --git a/kernel/src/drivers/driver.cpp b/kernel/src/drivers/driver.cpp index 6e653e12..c4481837 100644 --- a/kernel/src/drivers/driver.cpp +++ b/kernel/src/drivers/driver.cpp @@ -44,7 +44,7 @@ uint32_t Driver::reset(){ } /** - * @brief Get the vendor name of the driver + * @brief Get who created the device * * @return The vendor name of the driver */ diff --git a/kernel/src/drivers/ethernet/amd_am79c973.cpp b/kernel/src/drivers/ethernet/amd_am79c973.cpp index e20f7ec1..01962c7c 100644 --- a/kernel/src/drivers/ethernet/amd_am79c973.cpp +++ b/kernel/src/drivers/ethernet/amd_am79c973.cpp @@ -11,6 +11,10 @@ using namespace MaxOS::drivers; using namespace MaxOS::drivers::ethernet; using namespace MaxOS::hardwarecommunication; +/// MAX OS NET CODE: +/// All the old (this) networking code poorly written and not used, this will be moved to userspace in the future +/// but is kept here as a reference for now. + AMD_AM79C973::AMD_AM79C973(PeripheralComponentInterconnectDeviceDescriptor *dev) : InterruptHandler(0x20 + dev -> interrupt), MACAddress0Port(dev ->port_base), diff --git a/kernel/src/drivers/ethernet/intel_i217.cpp b/kernel/src/drivers/ethernet/intel_i217.cpp index 9baaed06..d9832764 100644 --- a/kernel/src/drivers/ethernet/intel_i217.cpp +++ b/kernel/src/drivers/ethernet/intel_i217.cpp @@ -11,6 +11,12 @@ using namespace MaxOS::drivers::ethernet; using namespace MaxOS::hardwarecommunication; using namespace memory; +/// MAX OS NET CODE: +/// All the old (this) networking code poorly written and not used, this will be moved to userspace in the future +/// but is kept here as a reference for now. +/// +/// See OSDEV wiki for the credit for this driver + // Buffer Sizes #define buffer256 (3 << 16) #define buffer512 (2 << 16) diff --git a/kernel/src/drivers/peripherals/keyboard.cpp b/kernel/src/drivers/peripherals/keyboard.cpp index d1b4ff78..3f9eaa18 100644 --- a/kernel/src/drivers/peripherals/keyboard.cpp +++ b/kernel/src/drivers/peripherals/keyboard.cpp @@ -101,7 +101,7 @@ void KeyboardDriver::activate() { m_command_port.write(0x60); m_data_port.write(status); - // activate the keyboard + // Activate the keyboard m_data_port.write(0xF4); } @@ -110,13 +110,10 @@ void KeyboardDriver::activate() { */ void KeyboardDriver::handle_interrupt(){ - // read the scancode from the keyboard - uint8_t key = m_data_port.read(); - // Pass the scan code to the m_handlers - for(auto& handler : this -> m_input_stream_event_handlers){ + uint8_t key = m_data_port.read(); + for(auto& handler : m_input_stream_event_handlers) handler -> on_stream_read(key); - } } /** @@ -171,27 +168,29 @@ KeyboardInterpreterEN_US::~KeyboardInterpreterEN_US() = default; */ void KeyboardInterpreterEN_US::on_stream_read(uint8_t scan_code) { - // 0 is a regular key, 1 is an extended code, 2 is an extended code with e1CodeBuffer - int keyType = 0; // Check if the key was released - bool released = (scan_code & 0x80) && (m_current_extended_code_1 || (scan_code != 0xe1)) && (m_next_is_extended_code_0 || (scan_code != 0xe0)); + bool released = (scan_code & 0x80) && (m_current_extended_code_1 + || (scan_code != 0xe1)) && (m_next_is_extended_code_0 + || (scan_code != 0xe0)); // Clear the released bit if (released) scan_code &= ~0x80; - // Set the e0Code flag to true + // Start of an extended scan code if (scan_code == 0xe0) { m_next_is_extended_code_0 = true; return; } - // If e0Code is true, set keyType to 1 and reset e0Code + // Handle extended scan codes + ScanCodeType type = ScanCodeType::REGULAR; if (m_next_is_extended_code_0) { - keyType = 1; + + type = ScanCodeType::EXTENDED; m_next_is_extended_code_0 = false; // Check if the scan_code represents a shift key and return (fake shift) @@ -217,17 +216,18 @@ void KeyboardInterpreterEN_US::on_stream_read(uint8_t scan_code) { // If e1Code is 2, set keyType to 2, reset e1Code, and update e1CodeBuffer if (m_current_extended_code_1 == 2) { - keyType = 2; + type = ScanCodeType::EXTENDED; m_current_extended_code_1 = 0; m_extended_code_1_buffer |= (((uint16_t)scan_code) << 8); } + // Scan code value manipulations bool is_shifting = this ->m_keyboard_state.left_shift || this ->m_keyboard_state.right_shift; bool should_be_upper_case = is_shifting != this ->m_keyboard_state.caps_lock; // TODO: Probably a better way to do this (investigate when adding more keyboard layouts) - if(keyType == 0) + if(type == ScanCodeType::REGULAR) switch ((KeyCodeEN_US)scan_code) { // First row diff --git a/kernel/src/drivers/peripherals/mouse.cpp b/kernel/src/drivers/peripherals/mouse.cpp index 31b68471..d9040ccf 100644 --- a/kernel/src/drivers/peripherals/mouse.cpp +++ b/kernel/src/drivers/peripherals/mouse.cpp @@ -39,7 +39,6 @@ Event* MouseEventHandler::on_event(Event *event) { } - // Return the event return event; } @@ -90,19 +89,18 @@ MouseDriver::~MouseDriver()= default; void MouseDriver::activate() { - // Get the current state of the mouse command_port.write(0x20); uint8_t status = (data_port.read() | 2); - // write the new state + // Write the new state command_port.write(0x60); data_port.write(status); // Tell the PIC to start listening to the mouse command_port.write(0xAB); - // activate the mouse + // Activate the mouse command_port.write(0xD4); data_port.write(0xF4); data_port.read(); @@ -113,39 +111,39 @@ void MouseDriver::activate() { */ void MouseDriver::handle_interrupt(){ - //Only if the 6th bit of data is one then there is data to handle + // Check if there is data to handle uint8_t status = command_port.read(); if(!(status & 0x20)) return; - // read the data and store it in the buffer - buffer[offset] = data_port.read(); - offset = (offset + 1) % 3; + // Read the data + m_buffer[m_offset] = data_port.read(); + m_offset = (m_offset + 1) % 3; // If the mouse data transmission is incomplete (3rd piece of data isn't through) - if(offset != 0) + if(m_offset != 0) return; // If the mouse is moved (y-axis is inverted) - if(buffer[1] != 0 || buffer[2] != 0) - raise_event(new MouseMoveEvent(buffer[1], -buffer[2])); + if(m_buffer[1] != 0 || m_buffer[2] != 0) + raise_event(new MouseMoveEvent(m_buffer[1], -m_buffer[2])); + // Handle button presses for (int i = 0; i < 3; ++i) { - // Check if the button state has changed - if((buffer[0] & (0x1<common.framebuffer_bpp; m_pitch = m_framebuffer_info->common.framebuffer_pitch; m_framebuffer_size = m_framebuffer_info->common.framebuffer_height * m_pitch; - + this -> set_mode(framebuffer_info->common.framebuffer_width,framebuffer_info->common.framebuffer_height, framebuffer_info->common.framebuffer_bpp); Logger::DEBUG() << "Framebuffer: bpp=" << m_bpp << ", pitch=" << m_pitch << ", size=" << m_framebuffer_size << "\n"; - // Get the framebuffer address - auto physical_address = (uint64_t)m_framebuffer_info->common.framebuffer_addr; - auto virtual_address = (uint64_t)PhysicalMemoryManager::to_dm_region(physical_address); - uint64_t end = physical_address + m_framebuffer_size; - m_framebuffer_address = (uint64_t*)virtual_address; - - Logger::DEBUG() << "Framebuffer address: physical=0x" << physical_address << ", virtual=0x" << virtual_address << "\n"; - - // Map the framebuffer - while (physical_address < end) { - - PhysicalMemoryManager::s_current_manager->map((physical_address_t*)physical_address, (virtual_address_t*)virtual_address, Write | Present); - physical_address += PhysicalMemoryManager::s_page_size; - virtual_address += PhysicalMemoryManager::s_page_size; - } - - size_t pages = PhysicalMemoryManager::size_to_frames(virtual_address - (uint64_t)m_framebuffer_address); - Logger::DEBUG() << "Framebuffer mapped: 0x" << (uint64_t)m_framebuffer_address << " - 0x" << virtual_address << " (pages: " << pages << ")\n"; + // Map the frame buffer into the higher half + auto physical_address = (uint64_t)m_framebuffer_info -> common.framebuffer_addr; + m_framebuffer_address = (uint64_t*)PhysicalMemoryManager::to_dm_region(physical_address); + PhysicalMemoryManager::s_current_manager -> map_area((physical_address_t*)physical_address, m_framebuffer_address, m_framebuffer_size, Write | Present); // Reserve the physical memory + size_t pages = PhysicalMemoryManager::size_to_frames(m_framebuffer_size); PhysicalMemoryManager::s_current_manager->reserve(m_framebuffer_info->common.framebuffer_addr, pages); - // Set the default video mode - this -> set_mode(framebuffer_info->common.framebuffer_width,framebuffer_info->common.framebuffer_height, framebuffer_info->common.framebuffer_bpp); + // Log info + Logger::DEBUG() << "Framebuffer address: physical=0x" << (uint64_t)physical_address << ", virtual=0x" << (uint64_t)m_framebuffer_address << "\n"; + Logger::DEBUG() << "Framebuffer mapped: 0x" << (uint64_t)m_framebuffer_address << " - 0x" << (uint64_t)(m_framebuffer_address + m_framebuffer_size) << " (pages: " << pages << ")\n"; } VideoElectronicsStandardsAssociation::~VideoElectronicsStandardsAssociation()= default; -/** - * @brief Initializes the VESA driver - * - * @return True if the driver was initialized successfully, false otherwise - */ -bool VideoElectronicsStandardsAssociation::init() { - - //Multiboot inits this for us - return true; -} - /** * @brief Sets the mode of the VESA driver * @@ -74,11 +50,12 @@ bool VideoElectronicsStandardsAssociation::init() { * @param color_depth Color depth of the screen * @return True if the mode was set successfully, false otherwise */ -bool VideoElectronicsStandardsAssociation::internal_set_mode(uint32_t, uint32_t, uint32_t) { - - // Best mode is set by the bootloader - return true; +bool VideoElectronicsStandardsAssociation::internal_set_mode(uint32_t width, uint32_t height, uint32_t color_depth) { + // Can only use the mode set up already by grub + return width == m_framebuffer_info->common.framebuffer_width + && height == m_framebuffer_info->common.framebuffer_height + && color_depth == m_framebuffer_info->common.framebuffer_bpp; } @@ -93,10 +70,9 @@ bool VideoElectronicsStandardsAssociation::internal_set_mode(uint32_t, uint32_t, bool VideoElectronicsStandardsAssociation::supports_mode(uint32_t width, uint32_t height, uint32_t color_depth) { // Check if the mode is supported - if(width == (uint32_t)m_framebuffer_info->common.framebuffer_width && height == (uint32_t)m_framebuffer_info->common.framebuffer_height && color_depth == (uint32_t)m_framebuffer_info->common.framebuffer_bpp) { - return true; - } - return false; + return width == m_framebuffer_info->common.framebuffer_width + && height == m_framebuffer_info->common.framebuffer_height + && color_depth == m_framebuffer_info->common.framebuffer_bpp; } /** @@ -108,10 +84,7 @@ bool VideoElectronicsStandardsAssociation::supports_mode(uint32_t width, uint32_ */ void VideoElectronicsStandardsAssociation::render_pixel_32_bit(uint32_t x, uint32_t y, uint32_t colour) { - // Get the address of the pixel - auto* pixel_address = (uint32_t*)((uint8_t *)m_framebuffer_address + m_pitch * (y) + m_bpp * (x) / 8); - - // Set the pixel + auto* pixel_address = (uint32_t*)((uint8_t *)m_framebuffer_address + (m_pitch * y) + (m_bpp * x) / 8); *pixel_address = colour; } @@ -125,10 +98,7 @@ void VideoElectronicsStandardsAssociation::render_pixel_32_bit(uint32_t x, uint3 */ uint32_t VideoElectronicsStandardsAssociation::get_rendered_pixel_32_bit(uint32_t x, uint32_t y) { - // Get the address of the pixel auto* pixel_address = (uint32_t*)((uint8_t *)m_framebuffer_address + m_pitch * (y) + m_bpp * (x) / 8); - - // Return the pixel return *pixel_address; } @@ -138,7 +108,9 @@ uint32_t VideoElectronicsStandardsAssociation::get_rendered_pixel_32_bit(uint32_ * @return The name of the vendor */ string VideoElectronicsStandardsAssociation::vendor_name() { - return "NEC Home Electronics"; // Creator of the VESA standard + + // Creator of the VESA standard + return "NEC Home Electronics"; } /** diff --git a/kernel/src/drivers/video/vga.cpp b/kernel/src/drivers/video/vga.cpp index a1831d08..5bf09245 100644 --- a/kernel/src/drivers/video/vga.cpp +++ b/kernel/src/drivers/video/vga.cpp @@ -54,20 +54,20 @@ void VideoGraphicsArray::write_registers(uint8_t* registers) registers[0x03] = registers[0x03] | 0x80; registers[0x11] = registers[0x11] & ~0x80; - // write the CRTC registers + // Write the CRTC registers for (uint8_t i = 0; i < 25; i++ ) { m_crtc_index_port.write(i); crtc_data_port.write(*(registers++)); } - // write the graphics controller registers + // Write the graphics controller registers for(uint8_t i = 0; i < 9; i++) { m_graphics_controller_index_port.write(i); m_graphics_controller_data_port.write(*(registers++)); } - // write the attribute controller registers + // Write the attribute controller registers for(uint8_t i = 0; i < 21; i++) { m_attribute_controller_reset_port.read(); @@ -102,8 +102,6 @@ bool VideoGraphicsArray::supports_mode(uint32_t width, uint32_t height, uint32_t */ bool VideoGraphicsArray::internal_set_mode(uint32_t width, uint32_t height, uint32_t colour_depth) { - if(!supports_mode(width, height, colour_depth)) - return false; //Values from osdev / modes.c unsigned char g_320x200x256[] = @@ -165,10 +163,7 @@ uint8_t* VideoGraphicsArray::get_frame_buffer_segment() */ void VideoGraphicsArray::render_pixel_8_bit(uint32_t x, uint32_t y, uint8_t colour){ - // Get the address of the pixel - uint8_t*pixel_address = get_frame_buffer_segment() + 320*y + x; - - // Set the pixel + uint8_t*pixel_address = get_frame_buffer_segment() + 320 * y + x; *pixel_address = colour; } @@ -181,11 +176,8 @@ void VideoGraphicsArray::render_pixel_8_bit(uint32_t x, uint32_t y, uint8_t colo */ uint8_t VideoGraphicsArray::get_rendered_pixel_8_bit(uint32_t x, uint32_t y) { - // Get the address of the pixel - uint8_t*pixel_address = get_frame_buffer_segment() + 320*y + x; - - // Return the pixel - return *pixel_address; + uint8_t* pixel_address = get_frame_buffer_segment() + 320 * y + x; + return* pixel_address; } /** @@ -194,7 +186,9 @@ uint8_t VideoGraphicsArray::get_rendered_pixel_8_bit(uint32_t x, uint32_t y) { * @return The name of the vendor. */ string VideoGraphicsArray::vendor_name() { - return "IBM"; // VGA was made by IBM + + // VGA was made by IBM + return "IBM"; } /** diff --git a/kernel/src/drivers/video/video.cpp b/kernel/src/drivers/video/video.cpp index 70bae311..f5a98331 100644 --- a/kernel/src/drivers/video/video.cpp +++ b/kernel/src/drivers/video/video.cpp @@ -46,19 +46,17 @@ bool VideoDriver::supports_mode(uint32_t, uint32_t, uint32_t) { */ bool VideoDriver::set_mode(uint32_t width, uint32_t height, uint32_t colorDepth) { - // Check if the mode is supported + // Cant set it if not supported if(!supports_mode(width, height, colorDepth)) return false; - // Set the mode - if(internal_set_mode(width, height, colorDepth)) - { - this -> m_width = width; - this -> m_height = height; - this -> m_color_depth = colorDepth; - return true; - } + // Try set the mode + if(!internal_set_mode(width, height, colorDepth)) + return false; - // If setting the mode failed, return false - return false; + // Store the mode + m_width = width; + m_height = height; + m_color_depth = colorDepth; + return true; } \ No newline at end of file diff --git a/kernel/src/filesystem/fat32.cpp b/kernel/src/filesystem/fat32.cpp index 072af209..5e5e6e87 100644 --- a/kernel/src/filesystem/fat32.cpp +++ b/kernel/src/filesystem/fat32.cpp @@ -94,9 +94,7 @@ lba_t Fat32Volume::set_next_cluster(lba_t cluster, lba_t next_cluster) *(uint32_t *)&fat[entry] = next_cluster & 0x0FFFFFFF; disk -> write(sector, fat, bytes_per_sector); - // Return the next cluster return next_cluster; - } /** @@ -117,8 +115,6 @@ lba_t Fat32Volume::find_free_cluster() if (next_cluster(start) == 0) return start; - - // No free clusters found ASSERT(false, "No free clusters found in the FAT table"); return 0; } @@ -134,8 +130,6 @@ lba_t Fat32Volume::allocate_cluster(lba_t cluster) // Allocate 1 cluster allocate_cluster(cluster, 1); - - } /** @@ -155,28 +149,24 @@ lba_t Fat32Volume::allocate_cluster(lba_t cluster, size_t amount) // Go through allocating the clusters for (size_t i = 0; i < amount; i++) { - // Get the next cluster lba_t next_cluster = find_free_cluster(); // Update the fsinfo fsinfo.next_free_cluster = next_cluster + 1; fsinfo.free_cluster_count -= 1; - // Update the chain (if this is an existing chain) + // If there is an existing chain it needs to be updated if (cluster != 0) set_next_cluster(cluster, next_cluster); - // Move to the next cluster cluster = next_cluster; } - // Save the fsinfo + // Once all the updates are done flush the changes to the disk disk -> write(fat_info_lba, (uint8_t *)&fsinfo, sizeof(fs_info_t)); - // Mark the end of the chain + // Finish the chin set_next_cluster(cluster, (lba_t)ClusterState::END_OF_CHAIN); - - // Return the cluster return cluster; } @@ -211,7 +201,7 @@ void Fat32Volume::free_cluster(lba_t cluster, size_t amount) for (size_t i = 0; i < amount; i++) { - // Get the next cluster + // Store the next cluster before it is set to free lba_t next_in_chain = next_cluster(cluster); // Update the fsinfo @@ -236,7 +226,6 @@ Fat32File::Fat32File(Fat32Volume* volume, lba_t cluster, size_t size, const stri m_first_cluster(cluster) { - // Set the base file information m_name = name; m_size = size; m_offset = 0; @@ -279,11 +268,10 @@ Fat32Directory::Fat32Directory(Fat32Volume* volume, lba_t cluster, const string& m_first_cluster(cluster) { - // Set the name m_name = name; // Read the directory - while (cluster >= 2 && cluster < (lba_t)ClusterState::BAD) + for (; cluster != (lba_t)ClusterState::END_OF_CHAIN; cluster = m_volume -> next_cluster(cluster)) { // Get the buffer and address to read @@ -301,31 +289,23 @@ Fat32Directory::Fat32Directory(Fat32Volume* volume, lba_t cluster, const string& { // Get the entry - auto entry = (dir_entry_t *)&buffer[entry_offset]; + auto entry = (dir_entry_t*)&buffer[entry_offset]; + m_entries.push_back(*entry); - // Skip the MacOS specific entries - if (strncmp(string(entry -> extension, 3), "Mac", 3)) + // Skip free entries and volume labels + if (entry -> name[0] == (uint8_t)DirectoryEntryType::DELETED || (entry -> attributes & 0x08) == 0x08) continue; - // Store the entry - m_entries.push_back(*entry); - // Check if the entry is the end of the directory - if (entry -> name[0] == (uint8_t)DirectoryEntryType::EMPTY) + if (entry -> name[0] == (uint8_t)DirectoryEntryType::LAST) return; - // Check if the entry is free - if (entry -> name[0] == (uint8_t)DirectoryEntryType::DELETED) - continue; - // Parse the long name entry if ((entry -> attributes & 0x0F) == 0x0F) { - // Get the long name entry - auto* long_name_entry = (long_file_name_entry_t*)entry; - // Extract the long name from each part (in reverse order) + auto* long_name_entry = (long_file_name_entry_t*)entry; for (int i = 0; i < 13; i++) { // Get the character (in utf8 encoding) @@ -341,29 +321,24 @@ Fat32Directory::Fat32Directory(Fat32Volume* volume, lba_t cluster, const string& if (c == '\0' || c == (char)0xFF) break; - // Add the character to the long name + // Add to the start as the entries are stored in reverse long_name = string(c) + long_name; } } // Get the name of the entry string name = long_name == "" ? string(entry->name, 8) : long_name; - - // Reset the long name for the next entry long_name = ""; // Get the starting cluster lba_t start_cluster = (entry -> first_cluster_high << 16) | entry -> first_cluster_low; // Store the file or directory - if (entry -> attributes & 0x10) + if (entry -> attributes & (uint8_t)DirectoryEntryType::DIRECTORY) m_subdirectories.push_back(new Fat32Directory(volume, start_cluster, name)); else m_files.push_back(new Fat32File(volume, start_cluster, entry -> size, name)); } - - // Get the next cluster in the chain - cluster = m_volume -> next_cluster(cluster); } } @@ -436,8 +411,6 @@ lba_t Fat32Directory::create_entry(const string& name, bool is_directory) else lfn_entry.name3[j - 11] = c; } - - // Add the entry to the list lfn_entries.push_back(lfn_entry); } @@ -452,20 +425,16 @@ lba_t Fat32Directory::create_entry(const string& name, bool is_directory) // Find free space for the new entry and the name size_t entries_needed = 1 + lfn_entries.size(); size_t entry_index = 0; - for (size_t i = 0; i < m_entries.size(); i++) + for (; entry_index < m_entries.size(); entry_index++) { // Check if there are enough free entries in a row bool found = true; for (size_t j = 0; j < entries_needed; j++) - if (m_entries[i + j].name[0] != 0x00 && m_entries[i + j].name[0] != 0xE5) + if (m_entries[entry_index + j].name[0] != 0x00 && m_entries[entry_index + j].name[0] != 0xE5) found = false; - // If free space is found, set the entry index if (found) - { - entry_index = i; break; - } } // Store the entries in the cache @@ -530,12 +499,12 @@ void Fat32Directory::remove_entry(lba_t cluster, const string& name) // Find the entry in the directory size_t entry_index = 0; - for (size_t i = 0; i < m_entries.size(); i++) + for (; entry_index < m_entries.size(); entry_index++) { - auto& entry = m_entries[i]; + auto& entry = m_entries[entry_index]; // End of directory means no more entries - if (entry.name[0] == (uint8_t)DirectoryEntryType::EMPTY) + if (entry.name[0] == (uint8_t)DirectoryEntryType::LAST) return; // Skip deleted entries @@ -545,17 +514,14 @@ void Fat32Directory::remove_entry(lba_t cluster, const string& name) // Check if the entry is the one to remove lba_t start_cluster = (entry.first_cluster_high << 16) | entry.first_cluster_low; if (start_cluster == cluster) - { - entry_index = i; break; - } } // Make sure the entry is valid if (entry_index >= m_entries.size()) return; - // Find any lfn entries that belong to this entry + // Find any long file name entries that belong to this entry size_t delete_entry_index = entry_index; while (delete_entry_index > 0 && (m_entries[delete_entry_index - 1].attributes & 0x0F) == 0x0F) delete_entry_index--; @@ -568,7 +534,6 @@ void Fat32Directory::remove_entry(lba_t cluster, const string& name) lba_t first_directory_entry_lba = m_volume -> data_lba + (m_first_cluster - 2) * m_volume -> sectors_per_cluster; for (size_t i = delete_entry_index; i <= entry_index; i++) { - // Get the offset to write the entry uint32_t offset = (i * sizeof(dir_entry_t)) / m_volume -> bytes_per_sector; m_volume -> disk -> write(first_directory_entry_lba + offset, (uint8_t *)&m_entries[i], sizeof(dir_entry_t)); } @@ -576,11 +541,8 @@ void Fat32Directory::remove_entry(lba_t cluster, const string& name) // Count the number of clusters in the chain size_t cluster_count = 0; lba_t next_cluster = cluster; - while (next_cluster >= 2 && next_cluster < (lba_t)ClusterState::BAD) - { + for (; next_cluster < (lba_t)ClusterState::BAD; next_cluster = m_volume -> next_cluster(next_cluster)) cluster_count++; - next_cluster = m_volume -> next_cluster(next_cluster); - } // Free the clusters in the chain m_volume -> free_cluster(cluster, cluster_count); @@ -618,13 +580,12 @@ void Fat32Directory::remove_file(const string& name) for (auto& file : m_files) if (file -> name() == name) { + // Remove the file from the directory m_files.erase(file); - - // Remove the entry remove_entry(((Fat32File*)file) -> first_cluster(), name); - // Delete the file + // Delete the file reference delete file; return; } @@ -675,10 +636,8 @@ void Fat32Directory::remove_subdirectory(const string& name) for (auto& subdirectory : subdirectory -> subdirectories()) subdirectory -> remove_subdirectory(subdirectory -> name()); - // Remove the directory from this directory - m_subdirectories.erase(subdirectory); - // Remove the entry + m_subdirectories.erase(subdirectory); remove_entry(((Fat32Directory*)subdirectory) -> first_cluster(), name); // Delete the directory diff --git a/kernel/src/filesystem/filesystem.cpp b/kernel/src/filesystem/filesystem.cpp index 386dee08..4176ff32 100644 --- a/kernel/src/filesystem/filesystem.cpp +++ b/kernel/src/filesystem/filesystem.cpp @@ -363,7 +363,6 @@ Directory* FileSystem::get_directory(const string& path) directory_path = Path::file_path(directory_path); } - // Return the directory return directory; } diff --git a/kernel/src/filesystem/partition/msdos.cpp b/kernel/src/filesystem/partition/msdos.cpp index 0a1afa3e..b00eae43 100644 --- a/kernel/src/filesystem/partition/msdos.cpp +++ b/kernel/src/filesystem/partition/msdos.cpp @@ -16,7 +16,7 @@ using namespace MaxOS::drivers::disk; */ void MSDOSPartition::mount_partitions(Disk* hd) { - // read the MBR from the hard disk + // Read the MBR from the hard disk MasterBootRecord mbr = {}; hd -> read(0, (uint8_t *)&mbr, sizeof(MasterBootRecord)); @@ -30,7 +30,6 @@ void MSDOSPartition::mount_partitions(Disk* hd) { // Get the VFS VirtualFileSystem* vfs = VirtualFileSystem::current_file_system(); - // Loop through the primary partitions for(auto& entry : mbr.primary_partition){ diff --git a/kernel/src/gui/widgets/button.cpp b/kernel/src/gui/widgets/button.cpp index e6d94f29..59f97ba8 100644 --- a/kernel/src/gui/widgets/button.cpp +++ b/kernel/src/gui/widgets/button.cpp @@ -24,15 +24,12 @@ ButtonEventHandler::~ButtonEventHandler() = default; */ Event* ButtonEventHandler::on_event(Event *event) { - // Check the event type switch (event -> type) { - // Button pressed case ButtonEvents::PRESSED: on_button_pressed(((ButtonPressedEvent *)event)->source); break; - // Button released case ButtonEvents::RELEASED: on_button_released(((ButtonReleasedEvent *)event)->source); break; diff --git a/kernel/src/hardwarecommunication/apic.cpp b/kernel/src/hardwarecommunication/apic.cpp index 33631326..254c022c 100644 --- a/kernel/src/hardwarecommunication/apic.cpp +++ b/kernel/src/hardwarecommunication/apic.cpp @@ -12,13 +12,12 @@ using namespace MaxOS::memory; LocalAPIC::LocalAPIC() { - // Read information about the local APIC - uint64_t msr_info = CPU::read_msr(0x1B); - // Get the APIC base address + uint64_t msr_info = CPU::read_msr(0x1B); m_apic_base = msr_info & 0xFFFFF000; + PhysicalMemoryManager::s_current_manager->reserve(m_apic_base); - // Read if the APIC supports x2APIC + // Check if the APIC supports x2APIC uint32_t ignored, xleaf, x2leaf; CPU::cpuid(0x01, &ignored, &ignored, &x2leaf, &xleaf); @@ -59,39 +58,48 @@ LocalAPIC::LocalAPIC() // Enable the APIC write(0xF0, (1 << 8) | 0x100); Logger::DEBUG() << "APIC Enabled\n"; - - // Reserve the APIC base - PhysicalMemoryManager::s_current_manager->reserve(m_apic_base); - - // Read the APIC version - uint32_t version = read(0x30); - Logger::DEBUG() << "APIC Version: 0x" << (uint64_t)(version & 0xFF) << "\n"; } LocalAPIC::~LocalAPIC() = default; +/** + * @brief Read a value from the apic register using the MSR or memory I/O depending on the local apic version + * + * @param reg The register to read + * @return The value of the register + */ uint32_t LocalAPIC::read(uint32_t reg) const{ - // If x2APIC is enabled, use the x2APIC MSR - if(m_x2apic) { + // If x2APIC is enabled I/O is done through the MSR + if(m_x2apic) return (uint32_t)CPU::read_msr((reg >> 4) + 0x800); - } else { - return (*(volatile uint32_t*)((uintptr_t)m_apic_base_high + reg)); - } + return *(volatile uint32_t*)((uintptr_t)m_apic_base_high + reg); + } +/** + * @brief Write a value to the apic register using the MSR or memory I/O depending on the local apic version + * + * @param reg The register to write to + * @param value The value to write + */ void LocalAPIC::write(uint32_t reg, uint32_t value) const { - // If x2APIC is enabled, use the x2APIC MSR - if(m_x2apic) { + // If x2APIC is enabled I/O is done through the MSR + if(m_x2apic) CPU::write_msr((reg >> 4) + 0x800, value); - } else { - (*(volatile uint32_t*)((uintptr_t)m_apic_base_high + reg)) = value; - } + + // Default to memory I/O + *(volatile uint32_t*)((uintptr_t)m_apic_base_high + reg) = value; } +/** + * @brief Get the id of the local apic + * + * @return The id of the local apic + */ uint32_t LocalAPIC::id() const { // Read the id @@ -102,9 +110,11 @@ uint32_t LocalAPIC::id() const { } +/** + * @brief Acknowledge that the interrupt has been handled, allowing the PIC to process the next interrupt + */ void LocalAPIC::send_eoi() const { - // Send the EOI write(0xB0, 0); } @@ -112,7 +122,6 @@ IOAPIC::IOAPIC(AdvancedConfigurationAndPowerInterface* acpi) : m_acpi(acpi) { - // Get the information about the IO APIC m_madt = (MADT*)m_acpi->find("APIC"); MADT_Item* io_apic_item = get_madt_item(1, 0); @@ -121,7 +130,6 @@ IOAPIC::IOAPIC(AdvancedConfigurationAndPowerInterface* acpi) auto* io_apic = (MADT_IOAPIC*)PhysicalMemoryManager::to_io_region((uint64_t)io_apic_item + sizeof(MADT_Item)); PhysicalMemoryManager::s_current_manager->map((physical_address_t*)io_apic_item, (virtual_address_t*)(io_apic - sizeof(MADT_Item)), Present | Write); - // Map the IO APIC address to the higher half m_address = io_apic->io_apic_address; m_address_high = (uint64_t)PhysicalMemoryManager::to_io_region(m_address); @@ -138,12 +146,9 @@ IOAPIC::IOAPIC(AdvancedConfigurationAndPowerInterface* acpi) // Get the source override item MADT_Item* source_override_item = get_madt_item(2, m_override_array_size); - - // Loop through the source override items uint32_t total_length = sizeof(MADT); while (total_length < m_madt->header.length && m_override_array_size < 0x10){ // 0x10 is the max items - // Increment the total length total_length += source_override_item->length; // If there is an override, populate the array @@ -155,26 +160,28 @@ IOAPIC::IOAPIC(AdvancedConfigurationAndPowerInterface* acpi) m_override_array[m_override_array_size].source = override->source; m_override_array[m_override_array_size].global_system_interrupt = override->global_system_interrupt; m_override_array[m_override_array_size].flags = override->flags; - - // Increment the override array size m_override_array_size++; } // Get the next item source_override_item = get_madt_item(2, m_override_array_size); - - // If there is no next item then break if(source_override_item == nullptr) break; } - // Log how many overrides were found Logger::DEBUG() << "IO APIC Source Overrides: 0x" << m_override_array_size << "\n"; } IOAPIC::~IOAPIC() = default; -MADT_Item *IOAPIC::get_madt_item(uint8_t type, uint8_t index) { +/** + * @brief Get an item in the MADT + * + * @param type The type of item + * @param index The index of the item + * @return The item or null if not found + */ +MADT_Item* IOAPIC::get_madt_item(uint8_t type, uint8_t index) { // The item starts at the start of the MADT auto* item = (MADT_Item*)((uint64_t)m_madt + sizeof(MADT)); @@ -184,41 +191,48 @@ MADT_Item *IOAPIC::get_madt_item(uint8_t type, uint8_t index) { // Loop through the items while (total_length + sizeof(MADT) < m_madt->header.length && current_index <= index) { - // Check if the item is the correct type + // Correct type if(item->type == type) { - // Check if the item is the correct index - if(current_index == index) { + // Correct index means found + if(current_index == index) return item; - } - // Increment the index + // Right type wrong index so move on current_index++; } // Increment the total length total_length += item->length; - - // Increment the item item = (MADT_Item*)((uint64_t)item + item->length); } - // Return null if the item was not found + // No item found return nullptr; } +/** + * @brief Read a value from a IO Apic register + * + * @param reg The register to read from + * @return The value at the register + */ uint32_t IOAPIC::read(uint32_t reg) const { - // Write the register + // Tell the APIC what register to read from *(volatile uint32_t*)(m_address_high + 0x00) = reg; - // Return the value + // Read the value return *(volatile uint32_t*)(m_address_high + 0x10); - - - } +/** + * @brief Write a value to an IO Apic register + * + * @param reg The register to write to + * @param value The value to set the register to + * @return The value at the register + */ void IOAPIC::write(uint32_t reg, uint32_t value) const { // Write the register @@ -228,28 +242,38 @@ void IOAPIC::write(uint32_t reg, uint32_t value) const { *(volatile uint32_t*)(m_address_high + 0x10) = value; } +/** + * @brief Read a redirect entry into a buffer + * + * @param index The index of the entry + * @param entry The buffer to read into + */ void IOAPIC::read_redirect(uint8_t index, RedirectionEntry *entry) { - // If the index is out of bounds, return + // Check bounds if(index < 0x10 || index > 0x3F) return; - // Low and high registers + // Read the entry chunks into the buffer (struct is auto extracted from the raw information) uint32_t low = read(index); uint32_t high = read(index + 1); - - // Set the entry entry->raw = ((uint64_t)high << 32) | ((uint64_t)low); } +/** + * @brief Write a redirect entry from a buffer + * + * @param index The index of the entry + * @param entry The buffer to write into + */ void IOAPIC::write_redirect(uint8_t index, RedirectionEntry *entry) { - // If the index is out of bounds, return + // Check bounds if(index < 0x10 || index > 0x3F) return; - // Low and high registers + // Break the entry down into 32bit chunks auto low = (uint32_t)entry->raw; auto high = (uint32_t)(entry->raw >> 32); @@ -258,7 +282,12 @@ void IOAPIC::write_redirect(uint8_t index, RedirectionEntry *entry) { write(index + 1, high); } -void IOAPIC::set_redirect(interrupt_redirect_t *redirect) { +/** + * @brief Redirect a system interrupt to a different IRQ + * + * @param redirect The redirection entry + */ +void IOAPIC::set_redirect(interrupt_redirect_t* redirect) { // Create the redirection entry RedirectionEntry entry = {}; @@ -278,15 +307,20 @@ void IOAPIC::set_redirect(interrupt_redirect_t *redirect) { // Set the trigger mode entry.trigger_mode = (((m_override_array[i].flags >> 2) & 0b11) == 2); - break; } // Write the redirect write_redirect(redirect->index, &entry); - } + +/** + * @brief Enables/Disables an interrupt redirect by masking it + * + * @param index The index of the redirect mask + * @param mask True = masked = disabled, False = unmasked = enabled + */ void IOAPIC::set_redirect_mask(uint8_t index, bool mask) { // Read the current entry @@ -295,8 +329,6 @@ void IOAPIC::set_redirect_mask(uint8_t index, bool mask) { // Set the mask entry.mask = mask; - - // Write the entry write_redirect(index, &entry); } @@ -307,13 +339,15 @@ AdvancedProgrammableInterruptController::AdvancedProgrammableInterruptController m_pic_slave_data_port(0xA1) { + // Register this APIC Logger::INFO() << "Setting up APIC\n"; + InterruptManager::active_interrupt_manager()->set_apic(this); // Init the Local APIC Logger::DEBUG() << "Initialising Local APIC\n"; m_local_apic = new LocalAPIC(); - // Disable the old PIC + // Disable the legacy mode PIC Logger::DEBUG() << "Disabling PIC\n"; disable_pic(); @@ -321,9 +355,6 @@ AdvancedProgrammableInterruptController::AdvancedProgrammableInterruptController Logger::DEBUG() << "Initialising IO APIC\n"; m_io_apic = new IOAPIC(acpi); - // Register the APIC - InterruptManager::active_interrupt_manager()->set_apic(this); - } AdvancedProgrammableInterruptController::~AdvancedProgrammableInterruptController() diff --git a/kernel/src/hardwarecommunication/interrupts.cpp b/kernel/src/hardwarecommunication/interrupts.cpp index 18466634..b7e408e5 100644 --- a/kernel/src/hardwarecommunication/interrupts.cpp +++ b/kernel/src/hardwarecommunication/interrupts.cpp @@ -19,21 +19,18 @@ InterruptHandler::InterruptHandler(uint8_t interrupt_number, int64_t redirect, u // Get the interrupt manager InterruptManager* interrupt_manager = InterruptManager::s_active_interrupt_manager; - - // If there is no interrupt manager, nothing can be done ASSERT(interrupt_manager != nullptr, "No active interrupt manager"); - // Set the handler in the array + // Register the handler interrupt_manager -> set_interrupt_handler(m_interrupt_number, this); - // If there is a redirect, set it - if(redirect == -1) return; + // Not all interrupts need redirecting + if(redirect == -1) + return; - // Get the IO-APIC + // Redirect a legacy interrupt to an interrupt the kernel expects IOAPIC* io_apic = interrupt_manager -> active_apic() -> io_apic(); - - // Register the driver - interrupt_redirect_t mouseRedirect = { + interrupt_redirect_t temp = { .type = (uint8_t)redirect, .index = (uint8_t)redirect_index, .interrupt = m_interrupt_number, @@ -41,7 +38,7 @@ InterruptHandler::InterruptHandler(uint8_t interrupt_number, int64_t redirect, u .flags = 0x00, .mask = false, }; - io_apic -> set_redirect(&mouseRedirect); + io_apic -> set_redirect(&temp); } @@ -49,8 +46,6 @@ InterruptHandler::~InterruptHandler(){ // Get the interrupt manager InterruptManager* interrupt_manager = InterruptManager::s_active_interrupt_manager; - - // If there is no interrupt manager, no need to remove the handler if(interrupt_manager == nullptr) return; // Remove the handler @@ -74,21 +69,18 @@ system::cpu_status_t* InterruptHandler::handle_interrupt(system::cpu_status_t *s // For handlers that don't care about the status handle_interrupt(); - // Return the status + // Return the default status return status; } -///__Manger__ - - - InterruptManager::InterruptManager() { Logger::INFO() << "Setting up Interrupt Manager\n"; + s_active_interrupt_manager = this; - // Full the table of interrupts with 0 + // Clear the table for(auto& descriptor : s_interrupt_descriptor_table) { descriptor.address_low_bits = 0; descriptor.address_mid_bits = 0; @@ -141,14 +133,11 @@ InterruptManager::InterruptManager() // Set up the system call interrupt set_interrupt_descriptor_table_entry(s_hardware_interrupt_offset + 0x60, &HandleInterruptRequest0x60, 3); // System Call Interrupt - Privilege Level 3 so that user space can call it - //Tell the processor to use the IDT + // Tell the processor to use the IDT IDTR idt = {}; idt.limit = 256 * sizeof(InterruptDescriptor) - 1; idt.base = (uint64_t)s_interrupt_descriptor_table; asm volatile("lidt %0" : : "m" (idt)); - - // Set the active interrupt manager - s_active_interrupt_manager = this; } InterruptManager::~InterruptManager() @@ -196,7 +185,7 @@ void InterruptManager::activate() { Logger::INFO() << "Activating Interrupts \n"; - // Deactivate the current interrupt manager + // Deactivate the current (old) interrupt manager if(s_active_interrupt_manager != nullptr) s_active_interrupt_manager->deactivate(); @@ -211,11 +200,13 @@ void InterruptManager::activate() { void InterruptManager::deactivate() { - // If this is the active interrupt manager, deactivate it and stop interrupts - if(s_active_interrupt_manager == this){ - s_active_interrupt_manager = nullptr; - asm("cli"); - } + // Cant deactivate if it isn't the system one + if (s_active_interrupt_manager != nullptr) + return; + + // Prevent interrupts from firing when nothing is set up to handle them + asm("cli"); + s_active_interrupt_manager = nullptr; } /** @@ -226,7 +217,7 @@ void InterruptManager::deactivate() */ system::cpu_status_t* InterruptManager::HandleInterrupt(system::cpu_status_t *status) { - // Fault Handlers + // Default Fault (haha) Handlers switch (status->interrupt_number) { case 0x7: @@ -302,18 +293,22 @@ void InterruptManager::set_apic(AdvancedProgrammableInterruptController *apic) { } cpu_status_t* InterruptManager::page_fault(system::cpu_status_t *status) { - bool present = (status ->error_code & 0x1) != 0; // Bit 0: Page present flag - bool write = (status ->error_code & 0x2) != 0; // Bit 1: Write operation flag - bool user_mode = (status ->error_code & 0x4) != 0; // Bit 2: User mode flag - bool reserved_write = (status ->error_code & 0x8) != 0; // Bit 3: Reserved bit write flag - bool instruction_fetch = (status ->error_code & 0x10) != 0; // Bit 4: Instruction fetch flag (on some CPUs) + + // Extract the information about the fault + bool present = (status ->error_code & 0x1) != 0; + bool write = (status ->error_code & 0x2) != 0; + bool user_mode = (status ->error_code & 0x4) != 0; + bool reserved_write = (status ->error_code & 0x8) != 0; + bool instruction_fetch = (status ->error_code & 0x10) != 0; uint64_t faulting_address; asm volatile("movq %%cr2, %0" : "=r" (faulting_address)); + // Try kill the process so the system doesnt die cpu_status_t* can_avoid = CPU::prepare_for_panic(status); if(can_avoid != nullptr) return can_avoid; + // Cant avoid it so halt the kernel Logger::ERROR() << "Page Fault: (0x" << faulting_address << "): present: " << (present ? "Yes" : "No") << ", write: " << (write ? "Yes" : "No") << ", user-mode: " << (user_mode ? "Yes" : "No") << ", reserved write: " << (reserved_write ? "Yes" : "No") << ", instruction fetch: " << (instruction_fetch ? "Yes" : "No") << "\n"; CPU::PANIC("See above message for more information", status); diff --git a/kernel/src/hardwarecommunication/pci.cpp b/kernel/src/hardwarecommunication/pci.cpp index 8c4c4070..b4057420 100644 --- a/kernel/src/hardwarecommunication/pci.cpp +++ b/kernel/src/hardwarecommunication/pci.cpp @@ -106,7 +106,7 @@ uint32_t PeripheralComponentInterconnectController::read(uint16_t bus, uint16_t | (registeroffset & 0xFC); m_command_port.write(id); - // read the data from the port + // Read the data from the port uint32_t result = m_data_port.read(); return result >> (8* (registeroffset % 4)); @@ -131,7 +131,7 @@ void PeripheralComponentInterconnectController::write(uint16_t bus, uint16_t dev | (registeroffset & 0xFC); m_command_port.write(id); - // write the data to the port + // Write the data to the port m_data_port.write(value); } @@ -144,7 +144,7 @@ void PeripheralComponentInterconnectController::write(uint16_t bus, uint16_t dev */ bool PeripheralComponentInterconnectController::device_has_functions(uint16_t bus, uint16_t device) { - return read(bus, device, 0, 0x0E) & (1<<7); + return read(bus, device, 0, 0x0E) & (1 << 7); } /** @@ -175,7 +175,6 @@ void PeripheralComponentInterconnectController::select_drivers(DriverSelectorEve deviceDescriptor.port_base = (uint64_t )bar.address; } - // write to the debug stream Logger::DEBUG() << "DEVICE FOUND: " << deviceDescriptor.get_type() << " - "; // Select the driver and print information about the device @@ -187,7 +186,6 @@ void PeripheralComponentInterconnectController::select_drivers(DriverSelectorEve list_known_device(deviceDescriptor); } - // New line Logger::Out() << "\n"; } } @@ -437,7 +435,7 @@ BaseAddressRegister PeripheralComponentInterconnectController::get_base_address_ BaseAddressRegister result{}; - // only types 0x00 (normal devices) and 0x01 (PCI-to-PCI bridges) are supported: + // Only types 0x00 (normal devices) and 0x01 (PCI-to-PCI bridges) are supported: uint32_t headerType = read(bus, device, function, 0x0E); if (headerType & 0x3F) return result; @@ -447,7 +445,7 @@ BaseAddressRegister PeripheralComponentInterconnectController::get_base_address_ result.type = (bar_value & 0x1) ? BaseAddressRegisterType::InputOutput : BaseAddressRegisterType::MemoryMapped; result.address = (uint8_t*) (bar_value & ~0xF); - // read the size of the base address register + // Read the size of the base address register write(bus, device, function, 0x10 + 4 * bar, 0xFFFFFFF0 | (int)result.type); result.size = read(bus, device, function, 0x10 + 4 * bar); result.size = (~result.size | 0xF) + 1; diff --git a/kernel/src/memory/memorymanagement.cpp b/kernel/src/memory/memorymanagement.cpp index e957a4b9..aa715be2 100644 --- a/kernel/src/memory/memorymanagement.cpp +++ b/kernel/src/memory/memorymanagement.cpp @@ -21,16 +21,12 @@ MemoryManager::MemoryManager(VirtualMemoryManager* vmm) // Enable the memory manager switch_active_memory_manager(this); - // Get the first chunk of memory + // Setup the first chunk of memory this -> m_first_memory_chunk = (MemoryChunk*)m_virtual_memory_manager->allocate(PhysicalMemoryManager::s_page_size + sizeof(MemoryChunk), 0); - - // Set the first chunk's properties m_first_memory_chunk-> allocated = false; m_first_memory_chunk-> prev = nullptr; m_first_memory_chunk-> next = nullptr; m_first_memory_chunk-> size = PhysicalMemoryManager::s_page_size - sizeof(MemoryChunk); - - // Set the last chunk to the first chunk m_last_memory_chunk = m_first_memory_chunk; // First memory manager is the kernel memory manager @@ -46,18 +42,18 @@ MemoryManager::~MemoryManager() { if(m_virtual_memory_manager != nullptr && s_current_memory_manager != s_kernel_memory_manager) delete m_virtual_memory_manager; - // Check if the current memory manager is this one + // Remove the kernel reference to this if(s_kernel_memory_manager == this) s_kernel_memory_manager = nullptr; - // Check if the current memory manager is this one + // Remove the active reference to this if(s_current_memory_manager == this) s_current_memory_manager = nullptr; } /** - * @brief Allocates a block of memory using the current memory manager + * @brief Allocates a block of memory in the current USERSPACE heap * * @param size size of the block * @return a pointer to the block, 0 if no block is available or no memory manager is set @@ -68,12 +64,12 @@ void* MemoryManager::malloc(size_t size) { if(s_current_memory_manager == nullptr) return nullptr; - return s_current_memory_manager->handle_malloc(size); + return s_current_memory_manager -> handle_malloc(size); } /** - * @brief Allocates a block of memory using the kernel memory manager + * @brief Allocates a block of memory in the KERNEL space * * @param size The size of the block * @return The pointer to the block, or nullptr if no block is available @@ -97,11 +93,11 @@ void *MemoryManager::kmalloc(size_t size) { void *MemoryManager::handle_malloc(size_t size) { MemoryChunk* result = nullptr; - // Don't allocate a block of size 0 + // Nothing to allocate if(size == 0) return nullptr; - // Size must include the size of the chunk and be aligned + // Add room to store the chunk information size = align(size + sizeof(MemoryChunk)); // Find the next free chunk that is big enough @@ -110,37 +106,37 @@ void *MemoryManager::handle_malloc(size_t size) { result = chunk; } - // If there is no free chunk then expand the heap + // If there is no free chunk then make more room if(result == nullptr) result = expand_heap(size); - // If there is not enough space to create a new chunk then just allocate the current chunk + // If there is not left over space to store extra chunks there is no need to split the chunk if(result -> size < size + sizeof(MemoryChunk) + 1) { result->allocated = true; return (void *)(((size_t)result) + sizeof(MemoryChunk)); } - // Create a new chunk after the current one - auto* temp = (MemoryChunk*)((size_t)result + sizeof(MemoryChunk) + size); - - // Set the new chunk's properties and insert it into the linked list - temp -> allocated = false; - temp -> size = result->size - size - sizeof(MemoryChunk); - temp -> prev = result; - temp -> next = result -> next; - - // If there is a chunk after the current one then set it's previous to the new chunk - if(temp -> next != nullptr) - temp -> next -> prev = temp; - - // Current chunk is now allocated and is pointing to the new chunk - result->size = size; + // Split the chunk into: what was requested + free overflow space for future allocates + // - This prevents waste in the event that a big free chunk was found but the requested size would only use a portion of that + auto* extra = (MemoryChunk*)((size_t)result + sizeof(MemoryChunk) + size); + extra -> allocated = false; + extra -> size = result->size - size - sizeof(MemoryChunk); + extra -> prev = result; + + // Add to the linked list + extra -> next = result -> next; + if(extra -> next != nullptr) + extra -> next -> prev = extra; + + // Requested chunk is now allocated exactly to the size requested and points to the free (split) block of memory that + // it did not use + result -> size = size; result -> allocated = true; - result->next = temp; + result -> next = extra; // Update the last memory chunk if necessary if(result == m_last_memory_chunk) - m_last_memory_chunk = temp; + m_last_memory_chunk = extra; return (void*)(((size_t)result) + sizeof(MemoryChunk)); } @@ -182,31 +178,30 @@ void MemoryManager::kfree(void *pointer) { */ void MemoryManager::handle_free(void *pointer) { - - // If nothing to free then return + // Cant free unallocated memory if(pointer == nullptr) return; - // If block is not in the memory manager's range then return + // Check bounds if((uint64_t ) pointer < (uint64_t ) m_first_memory_chunk || (uint64_t ) pointer > (uint64_t ) m_last_memory_chunk) return; - // Create a new free chunk + // Get the chunk information from the pointer auto* chunk = (MemoryChunk*)((size_t)pointer - sizeof(MemoryChunk)); chunk -> allocated = false; // If there is a free chunk before this chunk then merge them if(chunk -> prev != nullptr && !chunk -> prev -> allocated){ - // Increase the previous chunk's size and remove the current chunk from the linked list - chunk->prev->size += chunk->size + sizeof(MemoryChunk); + // Grow the chunk behind this one so that it now contains the freed one + chunk -> prev -> size += chunk -> size + sizeof(MemoryChunk); chunk -> prev -> next = chunk -> next; - // If there is a next chunk then ensure this chunk is removed from its linked list + // The chunk in front of the freed one now needs to point to the merged chunk if(chunk -> next != nullptr) chunk -> next -> prev = chunk->prev; - // Chunk is now the previous chunk + // Freed chunk doesn't exist anymore so now working with the merged chunk chunk = chunk -> prev; } @@ -214,11 +209,12 @@ void MemoryManager::handle_free(void *pointer) { // If there is a free chunk after this chunk then merge them if(chunk -> next != nullptr && !chunk -> next -> allocated){ - // Increase the current chunk's size and remove the next chunk from the linked list + // Grow this chunk so that it now contains the free chunk in front of the old (now freed) one chunk -> size += chunk -> next -> size + sizeof(MemoryChunk); - chunk -> next = chunk -> next -> next; - // Remove the just merged chunk from the linked list + // Now that this chunk contains the next one, it has to point to the one in front of what has just been merged + // and that has to point to this + chunk -> next = chunk -> next -> next; if(chunk -> next != nullptr) chunk -> next -> prev = chunk; @@ -235,8 +231,6 @@ MemoryChunk *MemoryManager::expand_heap(size_t size) { // Create a new chunk of memory auto* chunk = (MemoryChunk*)m_virtual_memory_manager->allocate(size, Present | Write | NoExecute); - - // If the chunk is null then there is no more memory ASSERT(chunk != nullptr, "Out of memory - kernel cannot allocate any more memory"); // Handled by assert, but just in case @@ -245,15 +239,16 @@ MemoryChunk *MemoryManager::expand_heap(size_t size) { // Set the chunk's properties chunk -> allocated = false; - chunk -> size = size; - chunk -> next = nullptr; + chunk -> size = size; + chunk -> next = nullptr; // Insert the chunk into the linked list m_last_memory_chunk -> next = chunk; chunk -> prev = m_last_memory_chunk; m_last_memory_chunk = chunk; - // If it is possible to move to merge the new chunk with the previous chunk then do so (note: this happens if the previous chunk is free but cant contain the size required) + // If it is possible to merge the new chunk with the previous chunk then do so (note: this happens if the + // previous chunk is free but cant contain the size required) if(!chunk -> prev -> allocated) free((void*)((size_t)chunk + sizeof(MemoryChunk))); @@ -297,7 +292,7 @@ size_t MemoryManager::align(size_t size) { * * @param manager The new memory manager */ -void MemoryManager::switch_active_memory_manager(MemoryManager *manager) { +void MemoryManager::switch_active_memory_manager(MemoryManager* manager) { // Make sure there is a manager if(manager == nullptr) diff --git a/kernel/src/memory/physical.cpp b/kernel/src/memory/physical.cpp index a9ec369d..b3576d9b 100644 --- a/kernel/src/memory/physical.cpp +++ b/kernel/src/memory/physical.cpp @@ -29,14 +29,10 @@ PhysicalMemoryManager::PhysicalMemoryManager(Multiboot* multiboot) { Logger::INFO() << "Setting up Physical Memory Manager\n"; - - // Unload the kernel from the lower half - unmap_lower_kernel(); - - // Log the kernel memory Logger::DEBUG() << "Kernel Memory: kernel_end = 0x" << (uint64_t)&_kernel_end << ", kernel_size = 0x" << (uint64_t)&_kernel_size << ", kernel_physical_end = 0x" << (uint64_t)&_kernel_physical_end << "\n"; // Set up the current manager + unmap_lower_kernel(); m_lock.unlock(); s_current_manager = this; m_nx_allowed = CPU::check_nx(); @@ -47,7 +43,7 @@ PhysicalMemoryManager::PhysicalMemoryManager(Multiboot* multiboot) m_total_entries = m_bitmap_size / s_row_bits + 1; Logger::DEBUG() << "Memory Info: size = " << (int)(m_memory_size / 1024 / 1024) << "mb, bitmap size = 0x" << (uint64_t)m_bitmap_size << ", total entries = " << (int)m_total_entries << ", page size = 0x" << (uint64_t)s_page_size << "\n"; - // Get the mmap that stores the memory to use + // Find a region of memory available to be used m_mmap_tag = m_multiboot->mmap(); for (multiboot_mmap_entry *entry = m_mmap_tag->entries; (multiboot_uint8_t *)entry < (multiboot_uint8_t *)m_mmap_tag + m_mmap_tag->size; entry = (multiboot_mmap_entry *)((unsigned long)entry + m_mmap_tag->entry_size)) { @@ -55,14 +51,14 @@ PhysicalMemoryManager::PhysicalMemoryManager(Multiboot* multiboot) if (entry->type != MULTIBOOT_MEMORY_AVAILABLE) continue; - // We want the last entry + // Store the entry. (note: dont break here as it is desired to find the last usable entry as that is normally biggest) m_mmap = entry; } Logger::DEBUG() << "Mmap in use: 0x" << (uint64_t)m_mmap->addr << " - 0x" << (uint64_t)(m_mmap->addr + m_mmap->len) << "\n"; - // Memory after the kernel to be used for direct mapping (when there is no bitmap of the physical memory) - m_anonymous_memory_physical_address = (uint64_t)align_up_to_page((size_t)&_kernel_physical_end + s_page_size, s_page_size); - m_anonymous_memory_virtual_address = (uint64_t)align_up_to_page((size_t)&_kernel_end + s_page_size, s_page_size); + // Memory after the kernel to be used for direct mapping (the bitmap is not ready while mapping the higher half so have to use this) + m_anonymous_memory_physical_address = align_up_to_page((size_t)&_kernel_physical_end + s_page_size, s_page_size); + m_anonymous_memory_virtual_address = align_up_to_page((size_t)&_kernel_end + s_page_size, s_page_size); Logger::DEBUG() << "Anonymous Memory: physical = " << (uint64_t)m_anonymous_memory_physical_address << ", virtual = " << (uint64_t)m_anonymous_memory_virtual_address << "\n"; // Map the physical memory into the virtual memory @@ -94,6 +90,7 @@ void PhysicalMemoryManager::reserve_kernel_regions(Multiboot *multiboot) { if ((((uint32_t)(m_anonymous_memory_physical_address)) % s_page_size) != 0) kernel_entries += 1; + // Reserve the kernel entries Logger::DEBUG() << "Kernel: location: 0x" << (uint64_t)m_anonymous_memory_physical_address << " - 0x" << (uint64_t)(m_anonymous_memory_physical_address + kernel_entries * s_page_size) << " (range of 0x" << (uint64_t)kernel_entries * s_page_size << ")\n"; reserve(0, kernel_entries * s_page_size); @@ -101,29 +98,25 @@ void PhysicalMemoryManager::reserve_kernel_regions(Multiboot *multiboot) { uint64_t mem_end = m_mmap->addr + m_mmap->len; for (multiboot_mmap_entry *entry = m_mmap_tag->entries; (multiboot_uint8_t *)entry < (multiboot_uint8_t *)m_mmap_tag + m_mmap_tag->size; entry = (multiboot_mmap_entry *)((unsigned long)entry + m_mmap_tag->entry_size)) { - // Check if the entry is to be mapped + // Dont reserve free regions if (entry->type <= MULTIBOOT_MEMORY_AVAILABLE) continue; - // Where the free mem starts + // Dont reserve the memory being managed by pmm if(entry->addr >= mem_end) continue; - // Reserve the area reserve(entry->addr, entry->len); } // Reserve the area for each multiboot module for(multiboot_tag* tag = multiboot -> start_tag(); tag->type != MULTIBOOT_TAG_TYPE_END; tag = (struct multiboot_tag *) ((multiboot_uint8_t *) tag + ((tag->size + 7) & ~7))) { - // Check if the tag is a module if(tag -> type != MULTIBOOT_TAG_TYPE_MODULE) continue; - // Get the module tag + // Reserve the module's address auto* module = (struct multiboot_tag_module*)tag; - - // Reserve the address reserve(module->mod_start, module->mod_end - module->mod_start); } } @@ -179,10 +172,9 @@ void* PhysicalMemoryManager::allocate_frame() { // Wait for the lock m_lock.lock(); - // Check if the pmm is initialized + // If not initialised, cant use the bitmap or higher half mapped physical memory so use leftover kernel memory already + // mapped in loader.s if(!m_initialized){ - - // TODO: This seems to destroy the multiboot memory map, need to fix this // Find the first free frame @@ -195,10 +187,8 @@ void* PhysicalMemoryManager::allocate_frame() { m_anonymous_memory_physical_address += s_page_size; m_anonymous_memory_virtual_address += s_page_size; - // Clear the lock - m_lock.unlock(); - // Return the address + m_lock.unlock(); return (void*)(m_anonymous_memory_physical_address - s_page_size); } @@ -206,7 +196,6 @@ void* PhysicalMemoryManager::allocate_frame() { // Check if there are enough frames ASSERT(m_used_frames < m_bitmap_size, "No more frames available\n"); - // Loop through the bitmap for (uint32_t row = 0; row < m_total_entries; ++row) { // If the row is full continue @@ -215,25 +204,20 @@ void* PhysicalMemoryManager::allocate_frame() { for (uint32_t column = 0; column < s_row_bits; ++column) { - // Check if the bitmap is free + // Entry isn't free if (m_bit_map[row] & (1ULL << column)) continue; - // Mark the frame as used m_bit_map[row] |= (1ULL << column); m_used_frames++; - // Return the address - uint64_t frame_address = (row * s_row_bits) + column; - frame_address *= s_page_size; - - - // Clear the lock + // Thread safe m_lock.unlock(); // Return the address - return (void*)(frame_address); + uint64_t frame_address = (row * s_row_bits) + column; + return (void*)(frame_address * s_page_size); } } @@ -251,17 +235,13 @@ void* PhysicalMemoryManager::allocate_frame() { */ void PhysicalMemoryManager::free_frame(void *address) { - // Wait for the lock m_lock.lock(); // Mark the frame as not used m_used_frames--; - - // Set the bit to 0 uint64_t frame_address = (uint64_t)address / s_page_size; m_bit_map[frame_address / s_row_bits] &= ~(1 << (frame_address % s_row_bits)); - // Clear the lock m_lock.unlock(); } @@ -273,71 +253,63 @@ void PhysicalMemoryManager::free_frame(void *address) { * @return A pointer to the start of the block (physical address) */ void* PhysicalMemoryManager::allocate_area(uint64_t start_address, size_t size) { - - // Wait to be able to allocate m_lock.lock(); - // Check how many frames are needed - size_t frame_count = size_to_frames(size); - // Store the information about the frames needed to be allocated for this size + size_t frame_count = size_to_frames(size); uint32_t start_row = 0; uint32_t start_column = 0; size_t adjacent_frames = 0; - // Loop through the bitmap for (uint32_t row = 0; row < m_total_entries; ++row) { - // If the row is full continue + // Skip full rows if(m_bit_map[row] == 0xFFFFFFFFFFFFFFF) continue; for (uint32_t column = 0; column < s_row_bits; ++column) { - // If this bit is not free, reset the adjacent frames + // Not enough adjacent frames if (m_bit_map[row] & (1ULL << column)) { adjacent_frames = 0; continue; } - // Store the start of the area if it is not already stored + // Store the address of the first frame in set of adjacent ones if(adjacent_frames == 0){ start_row = row; start_column = column; } - // Increment the adjacent frames - adjacent_frames++; - // If enough frames are found we can allocate the area - if(adjacent_frames == frame_count){ + // Make sure there are enough frames in a row found + adjacent_frames++; + if(adjacent_frames != frame_count) + continue; - // Mark the frames as used - m_used_frames += frame_count; - for (uint32_t i = 0; i < frame_count; ++i) { + // Mark the frames as used + m_used_frames += frame_count; + for (uint32_t i = 0; i < frame_count; ++i) { - // Get the location of the bit - uint32_t index = start_row + (start_column + i) / s_row_bits; - uint32_t bit = (start_column + i) % s_row_bits; + // Get the location of the bit + uint32_t index = start_row + (start_column + i) / s_row_bits; + uint32_t bit = (start_column + i) % s_row_bits; - // Skip if index exceeds bounds - if (index >= m_total_entries || bit >= s_row_bits) { - ASSERT(false, "Index out of bounds\n"); - } + // Check bounds + ASSERT(index >= m_total_entries || bit >= s_row_bits, "Index out of bounds\n"); - m_bit_map[index] |= (1ULL << bit); // Mark the bit as used - } + // Mark the bit as used + m_bit_map[index] |= (1ULL << bit); + } - // Clear the lock - m_lock.unlock(); + // Return start of the block of adjacent frames + m_lock.unlock(); + return (void*)(start_address + (start_row * s_row_bits + start_column) * s_page_size); - // Return the address - return (void*)(start_address + (start_row * s_row_bits + start_column) * s_page_size); - } } } - // Error cant allocate that much + // Not enough free frames adjacent to each other m_lock.unlock(); ASSERT(false, "Cannot allocate that much memory\n"); return nullptr; @@ -351,15 +323,15 @@ void* PhysicalMemoryManager::allocate_area(uint64_t start_address, size_t size) */ void PhysicalMemoryManager::free_area(uint64_t start_address, size_t size) { - // Check how many frames are needed - size_t frame_count = size_to_frames(size); - uint64_t frame_address = start_address / s_page_size; + // Convert address into frames + size_t frame_count = size_to_frames(size); + uint64_t frame_address = start_address / s_page_size; - // Check if the address is valid + // Check bounds if(frame_address >= m_bitmap_size) return; - // Wait to be able to free + // Wait until other threads have finished other memory operations m_lock.lock(); // Mark the frames as not used @@ -367,8 +339,6 @@ void PhysicalMemoryManager::free_area(uint64_t start_address, size_t size) { for (uint32_t i = 0; i < frame_count; ++i) m_bit_map[(frame_address + i) / s_row_bits] &= ~(1 << ((frame_address + i) % s_row_bits)); - - // Clear the lock m_lock.unlock(); } @@ -395,22 +365,20 @@ pml_t* PhysicalMemoryManager::get_higher_half_table(uint64_t index, uint64_t ind */ pml_t* PhysicalMemoryManager::get_or_create_table(pml_t* table, size_t index, size_t flags) { - - // If the table is already created return it + // Table is already created so just find the entry if(table -> entries[index].present) - return (pml_t *) to_dm_region((uintptr_t)physical_address_of_entry(&table -> entries[index])); + return (pml_t *) to_dm_region(physical_address_of_entry(&table -> entries[index])); - // Need to create the table + // Create the table auto* new_table = (uint64_t*)allocate_frame(); table -> entries[index] = create_page_table_entry((uint64_t)new_table, flags); - // Move the table to the higher half (can't rely on the direct map if the pmm is not initialized) + // Move the table to the higher half new_table = (uint64_t*)to_dm_region((uintptr_t) new_table); - // Clear the table + // Reset the memory contents at the address clean_page_table(new_table); - // All done return (pml_t*)new_table; } @@ -419,16 +387,15 @@ pml_t* PhysicalMemoryManager::get_or_create_table(pml_t* table, size_t index, si * * @param parent_table The parent table to create the entry in * @param table_index The index of the table to create - * @param pml4_index The index of the PML4 table - * @param pdpr_index The index of the PDPR table (defaults to 510) - * @param pd_index The index of the PD table (defaults to 510) - * @param pt_index The index of the PT table (defaults to 510) + * @param table The table to create * * @return The created page table entry */ pml_t* PhysicalMemoryManager::get_and_create_table(pml_t* parent_table, uint64_t table_index, pml_t* table) { - // If the table is already created return it + // Table already created so dont need to do anything + /// Note: this is where it differs when not having pmm init done as cant find the entry (direct map not setup) thus + /// requiring get_higher_half_table() to be passed in as the *table arg by the caller if(parent_table -> entries[table_index].present) return table; @@ -436,14 +403,13 @@ pml_t* PhysicalMemoryManager::get_and_create_table(pml_t* parent_table, uint64_t auto* new_table = (uint64_t *)allocate_frame(); parent_table -> entries[table_index] = create_page_table_entry((uint64_t)new_table, Present | Write); - // Invalidate the table - asm volatile("invlpg (%0)" ::"r" (table) : "memory"); + // Move the table to higher half + // Except this doesnt need to be done because using anom memory - // Clear the table + // Reset the memory contents at the address clean_page_table((uint64_t*)table); - // Return the table - return (pml_t *)table; + return table; } @@ -456,18 +422,16 @@ pml_t* PhysicalMemoryManager::get_and_create_table(pml_t* parent_table, uint64_t */ pte_t *PhysicalMemoryManager::get_entry(virtual_address_t* virtual_address, pml_t* pml4_table) { - // Check if the address is in the higher region - uint8_t is_user = !(in_higher_region((uint64_t)virtual_address)); - if(is_user) - is_user = User; + // Kernel memory must be in the higher half + size_t flags = Present | Write; + if(!in_higher_region((uint64_t)virtual_address)) + flags |= User; - // Get the indexes uint16_t pml4_index = PML4_GET_INDEX((uint64_t) virtual_address); uint16_t pdpr_index = PML3_GET_INDEX((uint64_t) virtual_address); uint16_t pd_index = PML2_GET_INDEX((uint64_t) virtual_address); uint16_t pt_index = PML1_GET_INDEX((uint64_t) virtual_address); - // Get the tables pml_t* pdpr_table = nullptr; pml_t* pd_table = nullptr; pml_t* pt_table = nullptr; @@ -479,9 +443,9 @@ pte_t *PhysicalMemoryManager::get_entry(virtual_address_t* virtual_address, pml_ pt_table = get_and_create_table(pd_table, pd_index, get_higher_half_table(pd_index, pdpr_index, pml4_index)); }else{ - pdpr_table = get_or_create_table(pml4_table, pml4_index, Present | Write | is_user); - pd_table = get_or_create_table(pdpr_table, pdpr_index, Present | Write | is_user); - pt_table = get_or_create_table(pd_table, pd_index, Present | Write | is_user); + pdpr_table = get_or_create_table(pml4_table, pml4_index, flags); + pd_table = get_or_create_table(pdpr_table, pdpr_index, flags); + pt_table = get_or_create_table(pd_table, pd_index, flags); } // Get the entry @@ -504,14 +468,14 @@ uint64_t PhysicalMemoryManager::physical_address_of_entry(pte_t *entry) { * @brief Maps a physical address to a virtual address, using the kernel's pml4 table * * @param physical_address The physical address to map - * @param address The virtual address to map to + * @param virtual_address The virtual address to map to * @param flags The flags to set the mapping to * @return The virtual address */ virtual_address_t* PhysicalMemoryManager::map(physical_address_t *physical_address, virtual_address_t* virtual_address, size_t flags) { // Map using the kernel's pml4 table - return map(physical_address, virtual_address, flags, (uint64_t*)m_pml4_root_address); + return map(physical_address, virtual_address, flags, m_pml4_root_address); } /** @@ -526,23 +490,18 @@ virtual_address_t* PhysicalMemoryManager::map(physical_address_t *physical_addre virtual_address_t* PhysicalMemoryManager::map(physical_address_t* physical_address, virtual_address_t* virtual_address, size_t flags, uint64_t* pml4_table) { // If it is in a lower region then assume it is the user space - uint8_t is_user = !(in_higher_region((uint64_t)virtual_address)); - if(is_user) { - // Change the flags to user + if(!in_higher_region((uint64_t)virtual_address)) flags |= User; - } - // Get the entry + // If the entry already exists then the mapping is already done pte_t* pte = get_entry(virtual_address, (pml_t *)pml4_table); - - // If it already exists return the address if(pte -> present) return virtual_address; // Map the physical address to the virtual address *pte = create_page_table_entry((uint64_t)physical_address, flags); - // Flush the TLB + // Flush the TLB (cache) asm volatile("invlpg (%0)" ::"r" (virtual_address) : "memory"); return virtual_address; @@ -557,11 +516,8 @@ virtual_address_t* PhysicalMemoryManager::map(physical_address_t* physical_addre */ virtual_address_t* PhysicalMemoryManager::map(virtual_address_t *virtual_address, size_t flags) { - // Create a new physical address for the frame - auto* physical_address = (physical_address_t *)allocate_frame(); - - // Map the physical address to the virtual address - return map(physical_address, virtual_address, flags); + // Map a new physical address to the requested virtual address + return map(allocate_frame(), virtual_address, flags); } @@ -574,11 +530,9 @@ virtual_address_t* PhysicalMemoryManager::map(virtual_address_t *virtual_address */ void PhysicalMemoryManager::map_area(virtual_address_t* virtual_address_start, size_t length, size_t flags) { - // Get the size of the area - size_t size = size_to_frames(length); // Map the required frames - for (size_t i = 0; i < size; ++i) + for (size_t i = 0; i < size_to_frames(length); ++i) map(virtual_address_start + (i * s_page_size), flags); } @@ -593,11 +547,9 @@ void PhysicalMemoryManager::map_area(virtual_address_t* virtual_address_start, s */ void PhysicalMemoryManager::map_area(physical_address_t* physical_address_start, virtual_address_t* virtual_address_start, size_t length, size_t flags) { - // Get the size of the area - size_t size = size_to_frames(length); // Map the required frames - for (size_t i = 0; i < size; ++i) + for (size_t i = 0; i < size_to_frames(length); ++i) map(physical_address_start + (i * s_page_size), virtual_address_start + (i * s_page_size), flags); } @@ -608,7 +560,7 @@ void PhysicalMemoryManager::map_area(physical_address_t* physical_address_start, * @param physical_address The physical address to map * @param flags The flags to set the mapping to */ -void PhysicalMemoryManager::identity_map(physical_address_t *physical_address, size_t flags) { +void PhysicalMemoryManager::identity_map(physical_address_t* physical_address, size_t flags) { // Map the physical address to its virtual address counter-part map(physical_address, physical_address, flags); @@ -623,7 +575,7 @@ void PhysicalMemoryManager::identity_map(physical_address_t *physical_address, s void PhysicalMemoryManager::unmap(virtual_address_t* virtual_address) { // Pass the kernel's pml4 table - unmap(virtual_address, (uint64_t*)m_pml4_root_address); + unmap(virtual_address, m_pml4_root_address); } /** @@ -634,17 +586,17 @@ void PhysicalMemoryManager::unmap(virtual_address_t* virtual_address) { */ void PhysicalMemoryManager::unmap(virtual_address_t *virtual_address, uint64_t* pml4_root) { - // Get the entries + // Get the entry pte_t* pte = get_entry(virtual_address, (pml_t *)pml4_root); - // Check if the entry is present + // Make sure the address is actually mapped if(!pte -> present) return; // Unmap the entry pte -> present = false; - // Flush the TLB + // Flush the TLB (cache) asm volatile("invlpg (%0)" ::"r" (virtual_address) : "memory"); } @@ -657,11 +609,8 @@ void PhysicalMemoryManager::unmap(virtual_address_t *virtual_address, uint64_t* */ void PhysicalMemoryManager::unmap_area(virtual_address_t *virtual_address_start, size_t length) { - // Get the size of the area - size_t size = size_to_frames(length); - // Unmap the required frames - for (size_t i = 0; i < size; ++i) + for (size_t i = 0; i < size_to_frames(length); ++i) unmap(virtual_address_start + (i * s_page_size)); } @@ -671,9 +620,10 @@ void PhysicalMemoryManager::unmap_area(virtual_address_t *virtual_address_start, * @param table The table to clean */ void PhysicalMemoryManager::clean_page_table(uint64_t *table) { - for(int i = 0; i < 512; i++){ + + // Null the table (prevents false mappings when re-using frames) + for(int i = 0; i < 512; i++) table[i] = 0x00l; - } } /** @@ -696,7 +646,7 @@ pte_t PhysicalMemoryManager::create_page_table_entry(uintptr_t address, size_t f .huge_page = (flags & HugePage) != 0, .global = (flags & Global) != 0, .available = 0, - .physical_address = (uint64_t)address >> 12, + .physical_address = address >> 12, }; // Set the NX bit if it is allowed @@ -717,33 +667,33 @@ pte_t PhysicalMemoryManager::create_page_table_entry(uintptr_t address, size_t f */ bool PhysicalMemoryManager::is_anonymous_available(size_t address) { - // Return false if the address range is entirely within or overlaps with the multiboot reserved region + // Make sure the address isn't (entirely) within or overlapping with the multiboot memory chunk if ((address > m_multiboot-> start_address && address + s_page_size < m_multiboot -> end_address) || (address + s_page_size > m_multiboot-> start_address && address < m_multiboot -> end_address)) { return false; } - // Loop through the mmmap entries + // Make sure the address isn't used by a multiboot module + if(m_multiboot -> is_reserved(address)) + return false; + + // Make sure the address doesnt overlap with a reserved chunk of physical memory for (multiboot_mmap_entry *entry = m_mmap_tag->entries; (multiboot_uint8_t *)entry < (multiboot_uint8_t *)m_mmap_tag + m_mmap_tag->size; entry = (multiboot_mmap_entry *)((unsigned long)entry + m_mmap_tag->entry_size)) { - // If it doesn't overlap with the mmap entry + // This entry doesnt contain the address if ((entry -> addr + entry -> len) < (address + s_page_size)) continue; - // If it is not available + // This entry is not free if(entry -> type != MULTIBOOT_MEMORY_AVAILABLE) continue; - // Check if the address is overwriting with some reserved memory - if(m_multiboot -> is_reserved(address)) - return false; - - // Memory is available + // This entry must contain the address and must be free so it can be used return true; } - // Memory is not available + // Memory is not available (it was not found in a free region) return false; } @@ -759,35 +709,34 @@ void PhysicalMemoryManager::initialise_bit_map() { // Earliest address to place the bitmap (after the kernel and hh direct map) uint64_t limit = m_anonymous_memory_physical_address; - // Loop through the mmap entries + // Find a region for the bitmap to handle for (multiboot_mmap_entry *entry = m_mmap_tag->entries; (multiboot_uint8_t *)entry < (multiboot_uint8_t *)m_mmap_tag + m_mmap_tag->size; entry = (multiboot_mmap_entry *)((unsigned long)entry + m_mmap_tag->entry_size)) { - // If the entry is not available or the address is before the limit + // Cant use a non-free entry or an entry past limit (ie dont user higher addresses that will be overridden later) if (entry -> type != MULTIBOOT_MEMORY_AVAILABLE || entry -> len > limit) continue; size_t space = entry -> len; size_t offset = 0; - // If the entry starts before the limit then adjust the space to start where the limit is + // Determine how much of the region is below the limit and adjust the starting point to there if(entry -> addr < limit){ offset = limit - entry -> addr; space -= offset; } - // Make sure there is enough space - ASSERT(space >= (m_bitmap_size / 8 + 1), "Not enough space for the bitmap\n"); + ASSERT(space >= (m_bitmap_size / 8 + 1), "Not enough space for the bitmap (to big)\n"); - // Return the address + // Return the address (ensuring that it is in the safe region) m_bit_map = (uint64_t*)to_dm_region(entry -> addr + offset); break; } - // Error no space for the bitmap - ASSERT(m_bit_map != nullptr, "No space for the bitmap\n"); + // Error no suitable space for the bitmap found + ASSERT(m_bit_map != nullptr, "No space for the bitmap (no region)\n"); - // Clear the bitmap + // Clear the bitmap (mark all as free) for (uint32_t i = 0; i < m_total_entries; ++i) m_bit_map[i] = 0; } @@ -837,19 +786,16 @@ size_t PhysicalMemoryManager::align_direct_to_page(size_t size) { void PhysicalMemoryManager::reserve(uint64_t address) { - // If the address is not part of physical memory then return + // Cant reserve virtual adresses (ensure the address is physical) if(address >= m_memory_size) return; - // Get the address to a page + // Mark as used in the bitmap address = align_direct_to_page(address); - - // Set the bit to 1 in the bitmap m_bit_map[address / s_row_bits] |= (1 << (address % s_row_bits)); Logger::DEBUG() << "Reserved Address: 0x" << address << "\n"; - } /** @@ -860,24 +806,25 @@ void PhysicalMemoryManager::reserve(uint64_t address) { */ void PhysicalMemoryManager::reserve(uint64_t address, size_t size) { + // Cant reserve virtual adresses (ensure the address is physical) if(address >= m_memory_size) return; // Wait to be able to reserve m_lock.lock(); - // Align address and size to page boundaries + // Ensure the area is a range of pages address = align_direct_to_page(address); - size = align_up_to_page(size, s_page_size); + size = align_up_to_page(size, s_page_size); - // Calculate how many pages need to be reserved - size_t page_count = size / s_page_size; - // Convert the starting address to a frame index - uint64_t frame_index = address / s_page_size; + // Convert in to amount of pages + size_t page_count = size / s_page_size; + uint64_t frame_index = address / s_page_size; - for (size_t i = 0; i < page_count; ++i) { + // Mark all as free + for (size_t i = 0; i < page_count; ++i) m_bit_map[(frame_index + i) / s_row_bits] |= (1ULL << ((frame_index + i) % s_row_bits)); - } + // Update the used frames m_used_frames += page_count; @@ -895,14 +842,12 @@ void PhysicalMemoryManager::reserve(uint64_t address, size_t size) { */ physical_address_t *PhysicalMemoryManager::get_physical_address(virtual_address_t *virtual_address, uint64_t *pml4_root) { - // Get the entry pte_t* entry = get_entry(virtual_address, (pml_t *)pml4_root); - // Check if the entry is present + // Cant get a physical address if it inst free if(!entry -> present) return nullptr; - // Get the physical address return (physical_address_t*)physical_address_of_entry(entry); } @@ -914,17 +859,15 @@ physical_address_t *PhysicalMemoryManager::get_physical_address(virtual_address_ */ void PhysicalMemoryManager::change_page_flags(virtual_address_t *virtual_address, size_t flags, uint64_t *pml4_root) { - // Get the entry pte_t* entry = get_entry(virtual_address, (pml_t *)pml4_root); - // Check if the entry is present + // Cant edit a non-present entry (will page fault) if(!entry -> present) return; - // Change the flags *entry = create_page_table_entry(physical_address_of_entry(entry), flags); - // Flush the TLB + // Flush the TLB (cache) asm volatile("invlpg (%0)" ::"r" (virtual_address) : "memory"); } @@ -957,7 +900,6 @@ void* PhysicalMemoryManager::to_higher_region(uintptr_t physical_address) { // Must be in the higher half return (void*)physical_address; - } /** @@ -967,6 +909,7 @@ void* PhysicalMemoryManager::to_higher_region(uintptr_t physical_address) { * @return The lower region address */ void* PhysicalMemoryManager::to_lower_region(uintptr_t virtual_address) { + // If it's in the lower half then add the offset if(virtual_address > s_higher_half_kernel_offset) return (void*)(virtual_address - s_higher_half_kernel_offset); @@ -1020,7 +963,6 @@ void* PhysicalMemoryManager::from_dm_region(uintptr_t physical_address) { // Must be in the lower half return (void*)physical_address; - } diff --git a/kernel/src/processes/ipc.cpp b/kernel/src/processes/ipc.cpp index 83eb50cf..0f816b7f 100644 --- a/kernel/src/processes/ipc.cpp +++ b/kernel/src/processes/ipc.cpp @@ -203,10 +203,9 @@ SharedMessageEndpoint* InterProcessCommunicationManager::create_message_endpoint SharedMessageEndpoint* InterProcessCommunicationManager::get_message_endpoint(const string& name) { // Try to find the endpoint - for(auto endpoint : m_message_endpoints){ + for(auto endpoint : m_message_endpoints) if(endpoint -> name -> equals(name)) return endpoint; - } // Not found return nullptr; diff --git a/kernel/src/system/syscalls.cpp b/kernel/src/system/syscalls.cpp index c6f9ca71..cd05497b 100644 --- a/kernel/src/system/syscalls.cpp +++ b/kernel/src/system/syscalls.cpp @@ -92,8 +92,8 @@ cpu_status_t* SyscallManager::handle_interrupt(cpu_status_t* status) { * @param syscall The syscall ID number * @param handler The handler to set */ -void SyscallManager::set_syscall_handler(uint8_t syscall, syscall_func_t handler) { - m_syscall_handlers[syscall] = handler; +void SyscallManager::set_syscall_handler(SyscallType syscall, syscall_func_t handler) { + m_syscall_handlers[(uint8_t)syscall] = handler; } /** @@ -101,8 +101,8 @@ void SyscallManager::set_syscall_handler(uint8_t syscall, syscall_func_t handler * * @param syscall The syscall ID number */ -void SyscallManager::remove_syscall_handler(uint8_t syscall) { - m_syscall_handlers[syscall] = nullptr; +void SyscallManager::remove_syscall_handler(SyscallType syscall) { + m_syscall_handlers[(uint8_t)syscall] = nullptr; } From 50d16478329272f593081975469a2ebca9db30e8 Mon Sep 17 00:00:00 2001 From: maxtyson123 <98maxt98@gmail.com> Date: Fri, 2 May 2025 15:05:35 +1200 Subject: [PATCH 07/38] First half code and comment clean up --- kernel/src/kernel.cpp | 2 - kernel/src/memory/virtual.cpp | 6 +- kernel/src/processes/elf.cpp | 52 +++++++--------- kernel/src/processes/ipc.cpp | 95 ++++++++++-------------------- kernel/src/processes/process.cpp | 63 +++++++++----------- kernel/src/processes/scheduler.cpp | 66 +++++++++------------ kernel/src/system/cpu.cpp | 23 ++++---- 7 files changed, 119 insertions(+), 188 deletions(-) diff --git a/kernel/src/kernel.cpp b/kernel/src/kernel.cpp index 557cfabd..b7f8a4f1 100644 --- a/kernel/src/kernel.cpp +++ b/kernel/src/kernel.cpp @@ -5,8 +5,6 @@ #include #include #include -#include -#include #include #include #include diff --git a/kernel/src/memory/virtual.cpp b/kernel/src/memory/virtual.cpp index 5db0118c..aafa2cfd 100644 --- a/kernel/src/memory/virtual.cpp +++ b/kernel/src/memory/virtual.cpp @@ -13,10 +13,8 @@ using namespace MaxOS::processes; VirtualMemoryManager::VirtualMemoryManager() { - // Set the kernel flag - bool is_kernel = MemoryManager::s_kernel_memory_manager == nullptr; - - + // Set the kernel flag + bool is_kernel = MemoryManager::s_kernel_memory_manager == nullptr; if(!is_kernel){ // Get a new pml4 table diff --git a/kernel/src/processes/elf.cpp b/kernel/src/processes/elf.cpp index c3e2b5ca..293f26a9 100644 --- a/kernel/src/processes/elf.cpp +++ b/kernel/src/processes/elf.cpp @@ -32,10 +32,10 @@ Elf64::~Elf64() */ void Elf64::load() { - // Check if valid - if(!is_valid()) return; //TODO: error handling when the syscall for this is implemented + //TODO: error handling when the syscall for this is implemented + if(!is_valid()) + return; - // Load the program headers load_program_headers(); } @@ -47,7 +47,6 @@ void Elf64::load() { */ elf_64_header_t *Elf64::header() const { - // Return the header return (elf_64_header_t*)m_elf_header_address; } @@ -59,13 +58,12 @@ elf_64_header_t *Elf64::header() const { */ elf_64_program_header_t* Elf64::get_program_header(size_t index) { - // Check if within bounds - if(index >= header() -> program_header_count) return nullptr; + // Check bounds + if(index >= header() -> program_header_count) + return nullptr; - // Get the address of the program headers + // Find the program headers and return the item auto* program_headers = (elf_64_program_header_t*)(m_elf_header_address + header() -> program_header_offset); - - // Return the requested program header return &program_headers[index]; } @@ -78,13 +76,12 @@ elf_64_program_header_t* Elf64::get_program_header(size_t index) { */ elf_64_section_header_t *Elf64::get_section_header(size_t index) { - // Check if within bounds - if(index >= header() -> section_header_count) return nullptr; + // Check bound + if(index >= header() -> section_header_count) + return nullptr; - // Get the address of the section headers + // Find the section headers and return the item auto* section_headers = (elf_64_section_header_t*)(m_elf_header_address + header() -> section_header_offset); - - // Return the requested section header return §ion_headers[index]; } @@ -133,32 +130,29 @@ bool Elf64::is_valid() { */ void Elf64::load_program_headers() { - // Loop through the program headers for (size_t i = 0; i < header() -> program_header_count; i++) { - // Get the program header + // Get the header information elf_64_program_header_t* program_header = get_program_header(i); - // Check if the program header is loadable + // Only load headers that actually need loading if(program_header -> type != (int)ElfProgramType::Load) continue; - // Type of the program header - uint64_t flags = to_vmm_flags(program_header->flags); - // Allocate space at the requested address - void* address = MemoryManager::s_current_memory_manager -> vmm() -> allocate(program_header -> virtual_address, program_header -> memory_size, Present | PageFlags::Write); + void* address = MemoryManager::s_current_memory_manager -> vmm() -> allocate(program_header -> virtual_address, program_header -> memory_size, Present | Write); ASSERT(address != nullptr, "Failed to allocate memory for program header\n"); - // Copy the program to the address - memcpy((void*)address, (void*)(m_elf_header_address + program_header -> offset), program_header -> file_size); + // Copy the program into memory at that address + memcpy(address, (void*)(m_elf_header_address + program_header -> offset), program_header -> file_size); // Zero the rest of the memory if needed size_t zero_size = program_header -> memory_size - program_header -> file_size; memset((void*)((uintptr_t)address + program_header -> file_size), 0, zero_size); - // Now that we are done with modifying the memory, we should set the flags to the correct ones - PhysicalMemoryManager::s_current_manager -> change_page_flags((virtual_address_t*)address, flags, MemoryManager::s_current_memory_manager -> vmm() -> pml4_root_address()); + // Once the memory has been copied can now mark the pages as read only etc + uint64_t flags = to_vmm_flags(program_header->flags); + PhysicalMemoryManager::s_current_manager -> change_page_flags(address, flags, MemoryManager::s_current_memory_manager -> vmm() -> pml4_root_address()); } } @@ -173,7 +167,7 @@ uint64_t Elf64::to_vmm_flags(uint32_t type) { // Conversion // ELF | VMM - // 0x0 | Executable (not used) + // 0x0 | Executable // 0x1 | Write // 0x2 | Read @@ -184,11 +178,9 @@ uint64_t Elf64::to_vmm_flags(uint32_t type) { flags |= Write; // Disable no execute - if(type & ElfProgramFlags::ElfExecute) + if(type & ElfExecute) flags &= ~NoExecute; - return flags; -} - +} \ No newline at end of file diff --git a/kernel/src/processes/ipc.cpp b/kernel/src/processes/ipc.cpp index 0f816b7f..8d9b3674 100644 --- a/kernel/src/processes/ipc.cpp +++ b/kernel/src/processes/ipc.cpp @@ -13,22 +13,13 @@ using namespace MaxOS::memory; /** * @brief Creates a new IPC handler */ -InterProcessCommunicationManager::InterProcessCommunicationManager() { - - // Clear the spinlock - m_lock = Spinlock(); - -} - +InterProcessCommunicationManager::InterProcessCommunicationManager() = default; InterProcessCommunicationManager::~InterProcessCommunicationManager() { // Free all the shared memory - for(auto block : m_shared_memory_blocks){ + for(auto block : m_shared_memory_blocks) delete block; - } - - } /** @@ -40,29 +31,23 @@ InterProcessCommunicationManager::~InterProcessCommunicationManager() { */ SharedMemory* InterProcessCommunicationManager::alloc_shared_memory(size_t size, string name) { - // Wait for the lock + // Wait other processes to finish creating their blocks in order to ensure uniqueness m_lock.lock(); // Make sure the name is unique - for(auto endpoint : m_message_endpoints){ + for(auto endpoint : m_message_endpoints) if(endpoint -> name -> equals(name)){ m_lock.unlock(); return nullptr; } - } // Create the shared memory block auto* block = new SharedMemory(name, size); - - // Add the block to the list m_shared_memory_blocks.push_back(block); - - // Clear the lock - m_lock.unlock(); - Logger::DEBUG() << "Created shared memory block " << name << " at 0x" << block -> physical_address() << "\n"; // Return the block + m_lock.unlock(); return block; } @@ -74,22 +59,24 @@ SharedMemory* InterProcessCommunicationManager::alloc_shared_memory(size_t size, */ SharedMemory* InterProcessCommunicationManager::get_shared_memory(const string& name) { - // Wait for the lock + // Wait for shared memory to be fully created before trying to search for it m_lock.lock(); // Find the block for(auto block : m_shared_memory_blocks){ - if(block -> name -> equals(name)){ - block -> use_count++; - m_lock.unlock(); - return block; - } - } - // Clear the lock - m_lock.unlock(); + // Block has wrong name + if(!block -> name -> equals(name)) + continue; + + // Block is now in use + block -> use_count++; + m_lock.unlock(); + return block; + } // Not found + m_lock.unlock(); return nullptr; } @@ -102,14 +89,12 @@ SharedMemory* InterProcessCommunicationManager::get_shared_memory(const string& void InterProcessCommunicationManager::free_shared_memory(uintptr_t physical_address) { // Find the block - for(auto block : m_shared_memory_blocks){ - - if(block -> physical_address() == physical_address){ - free_shared_memory(block); - return; - } + for(auto block : m_shared_memory_blocks) + if(block -> physical_address() == physical_address){ + free_shared_memory(block); + return; + } - } } /** @@ -131,7 +116,6 @@ void InterProcessCommunicationManager::free_shared_memory(const string& name) { } - } /** @@ -144,10 +128,10 @@ void InterProcessCommunicationManager::free_shared_memory(SharedMemory* block) { // Wait for the lock m_lock.lock(); - // Decrement the use count + // One less process is using it block->use_count--; - // If the block is still in use + // If the block is in use let those processes handle it if (block->use_count > 0) { m_lock.unlock(); return; @@ -157,8 +141,6 @@ void InterProcessCommunicationManager::free_shared_memory(SharedMemory* block) { // Free the block delete block; - - // Clear the lock m_lock.unlock(); } @@ -175,22 +157,16 @@ SharedMessageEndpoint* InterProcessCommunicationManager::create_message_endpoint m_lock.lock(); // Make sure the name is unique - SharedMessageEndpoint* existing = get_message_endpoint(name); - if(existing != nullptr){ + if(get_message_endpoint(name) != nullptr){ m_lock.unlock(); return nullptr; } // Create the endpoint auto* endpoint = new SharedMessageEndpoint(name); - - // Add the endpoint to the list m_message_endpoints.push_back(endpoint); - // Free the lock m_lock.unlock(); - - // Return the endpoint return endpoint; } @@ -219,12 +195,7 @@ SharedMessageEndpoint* InterProcessCommunicationManager::get_message_endpoint(co */ void InterProcessCommunicationManager::free_message_endpoint(const string& name) { - // Find the endpoint - SharedMessageEndpoint* endpoint = get_message_endpoint(name); - - // Free the endpoint - free_message_endpoint(endpoint); - + free_message_endpoint(get_message_endpoint(name)); } /** @@ -264,7 +235,6 @@ SharedMemory::SharedMemory(string name, size_t size) SharedMemory::~SharedMemory() { - // Free the memory PhysicalMemoryManager::s_current_manager->free_area(m_physical_address, m_size); } @@ -297,7 +267,7 @@ SharedMessageEndpoint::SharedMessageEndpoint(string name) // Make the queue in the user's memory space m_queue = (ipc_message_queue_t*)MemoryManager::malloc(sizeof(ipc_message_queue_t)); - // Get the owner + // Store that this process owns it m_owner_pid = Scheduler::current_process() -> pid(); @@ -305,7 +275,6 @@ SharedMessageEndpoint::SharedMessageEndpoint(string name) SharedMessageEndpoint::~SharedMessageEndpoint() { - // Wait for the lock m_message_lock.lock(); // Delete the messages @@ -316,10 +285,8 @@ SharedMessageEndpoint::~SharedMessageEndpoint() { message = next; } - // Free the queue + // Free the memory MemoryManager::free(m_queue); - - // Free the name delete name; m_message_lock.unlock(); @@ -332,7 +299,6 @@ SharedMessageEndpoint::~SharedMessageEndpoint() { */ ipc_message_queue_t *SharedMessageEndpoint::queue() const { - // Return the queue return m_queue; } @@ -344,7 +310,6 @@ ipc_message_queue_t *SharedMessageEndpoint::queue() const { */ bool SharedMessageEndpoint::owned_by_current_process() const { - // Check if the owner is the current process return m_owner_pid == Scheduler::current_process() -> pid(); } @@ -360,7 +325,7 @@ void SharedMessageEndpoint::queue_message(void *message, size_t size) { // Wait for the lock m_message_lock.lock(); - // Copy the buffer into the kernel so that the endpoint can access it + // Copy the buffer into the kernel so that the endpoint (this code) can access it when the memory spaces are switched auto* kernel_copy = (uintptr_t*)new char[size]; memcpy(kernel_copy, message, size); @@ -388,10 +353,10 @@ void SharedMessageEndpoint::queue_message(void *message, size_t size) { if (current == nullptr) m_queue->messages = new_message; - //Switch back from endpoint's memory space + // Return to the caller's memory space MemoryManager::switch_active_memory_manager(Scheduler::current_process() -> memory_manager); // Free the lock & kernel copy delete[] kernel_copy; m_message_lock.unlock(); -} +} \ No newline at end of file diff --git a/kernel/src/processes/process.cpp b/kernel/src/processes/process.cpp index 723aaa31..5df186dc 100644 --- a/kernel/src/processes/process.cpp +++ b/kernel/src/processes/process.cpp @@ -39,22 +39,21 @@ Thread::Thread(void (*_entry_point)(void *), void *args, int arg_amount, Process ASSERT(m_stack_pointer != 0 && m_tss_stack_pointer != 0, "Failed to allocate stack for thread"); // Set up the execution state - execution_state = new cpu_status_t(); - execution_state->rip = (uint64_t)_entry_point; - execution_state->ss = parent -> is_kernel ? 0x10 : 0x23; - execution_state->cs = parent -> is_kernel ? 0x8 : 0x1B; - execution_state->rflags = 0x202; - execution_state->interrupt_number = 0; - execution_state->error_code = 0; - execution_state->rsp = (uint64_t)m_stack_pointer; - execution_state->rbp = 0; + execution_state = new cpu_status_t; + execution_state -> rip = (uint64_t)_entry_point; + execution_state -> ss = parent -> is_kernel ? 0x10 : 0x23; + execution_state -> cs = parent -> is_kernel ? 0x8 : 0x1B; + execution_state -> rflags = 0x202; + execution_state -> interrupt_number = 0; + execution_state -> error_code = 0; + execution_state -> rsp = m_stack_pointer; + execution_state -> rbp = 0; // Copy the args into userspace uint64_t argc = arg_amount; void* argv = MemoryManager::malloc(arg_amount * sizeof(void*)); memcpy(argv, args, arg_amount * sizeof(void*)); - execution_state->rdi = argc; execution_state->rsi = (uint64_t)argv; //execution_state->rdx = (uint64_t)env_args; @@ -78,11 +77,11 @@ Thread::~Thread() = default; */ cpu_status_t* Thread::sleep(size_t milliseconds) { - // Update the vars + // Update the state thread_state = ThreadState::SLEEPING; wakeup_time = Scheduler::system_scheduler() -> ticks() + milliseconds; - // Yield + // Let other processes do stuff while this thread is sleeping return Scheduler::system_scheduler() -> yield(); } @@ -133,6 +132,8 @@ Process::Process(const string& p_name, bool is_kernel) // If it is a kernel process then don't need a new memory manager memory_manager = is_kernel ? MemoryManager::s_kernel_memory_manager : new MemoryManager(); + + // Resuming interrupts are done when adding a thread } /** @@ -169,7 +170,6 @@ Process::Process(const string& p_name, void *args, int arg_amount, Elf64* elf, b : Process(p_name, is_kernel) { - // Get the entry point elf -> load(); auto* entry_point = (void (*)(void *))elf -> header() -> entry; @@ -177,9 +177,6 @@ Process::Process(const string& p_name, void *args, int arg_amount, Elf64* elf, b // Create the main thread auto* main_thread = new Thread(entry_point, args, arg_amount, this); add_thread(main_thread); - - // Free the elf - delete elf; } @@ -214,9 +211,7 @@ void Process::add_thread(Thread *thread) { // Store the thread m_threads.push_back(thread); - - // Set the pid - thread->parent_pid = m_pid; + thread -> parent_pid = m_pid; // Can now resume interrupts asm("sti"); @@ -232,24 +227,23 @@ void Process::remove_thread(uint64_t tid) { // Find the thread for (uint32_t i = 0; i < m_threads.size(); i++) { - if (m_threads[i]->tid == tid) { - // Get the thread - Thread* thread = m_threads[i]; + // Thread is not what is being removed + if (m_threads[i]->tid != tid) + continue; - // Delete the thread - delete thread; - - // Remove the thread from the list - m_threads.erase(m_threads.begin() + i); + // Delete the thread + Thread* thread = m_threads[i]; + delete thread; + // Remove the thread from the list + m_threads.erase(m_threads.begin() + i); - // If there are no more threads then delete the process (done on the scheduler side) - if (m_threads.empty()) - Scheduler::system_scheduler() -> remove_process(this); + // If there are no more threads then delete the process from the scheduler + if (m_threads.empty()) + Scheduler::system_scheduler() -> remove_process(this); - return; - } + return; } } @@ -269,7 +263,7 @@ void Process::set_pid(uint64_t pid) { // Assign the pid to the threads for (auto thread : m_threads) - thread->parent_pid = pid; + thread->parent_pid = pid; } @@ -278,10 +272,7 @@ void Process::set_pid(uint64_t pid) { * @brief Gets the threads of the process */ Vector Process::threads() { - - // Return the threads return m_threads; - } /** diff --git a/kernel/src/processes/scheduler.cpp b/kernel/src/processes/scheduler.cpp index 91f792e2..80d139bb 100644 --- a/kernel/src/processes/scheduler.cpp +++ b/kernel/src/processes/scheduler.cpp @@ -22,22 +22,24 @@ Scheduler::Scheduler(Multiboot& multiboot) { - /// Setup the basic scheduler + // Setup the basic scheduler Logger::INFO() << "Setting up Scheduler \n"; s_instance = this; m_ipc = new InterProcessCommunicationManager(); // Create the idle process auto* idle = new Process("kernelMain Idle", nullptr, nullptr,0, true); - idle->memory_manager = MemoryManager::s_kernel_memory_manager; + idle -> memory_manager = MemoryManager::s_kernel_memory_manager; add_process(idle); - idle->set_pid(0); + idle -> set_pid(0); // Load the elfs load_multiboot_elfs(&multiboot); } Scheduler::~Scheduler() { + + // Deactivate this scheduler s_instance = nullptr; m_active = false; @@ -54,11 +56,7 @@ Scheduler::~Scheduler() { */ cpu_status_t* Scheduler::handle_interrupt(cpu_status_t *status) { - // Schedule the next thread return schedule(status); - - /// Note: Could have set scheduler to just be the handle interrupt function, - //// but in the future there may be a need to schedule at other times } @@ -70,11 +68,10 @@ cpu_status_t* Scheduler::handle_interrupt(cpu_status_t *status) { */ cpu_status_t *Scheduler::schedule(cpu_status_t* cpu_state) { - // If there are no threads to schedule or not active, return the current state + // Scheduler cant schedule anything if (m_threads.empty() || !m_active) return cpu_state; - // Thread that we are dealing with Thread* current_thread = m_threads[m_current_thread_index]; @@ -82,7 +79,7 @@ cpu_status_t *Scheduler::schedule(cpu_status_t* cpu_state) { m_ticks++; current_thread->ticks++; - // Wait for a bit so that the scheduler doesn't run too fast + // Wait for a bit so that the scheduler doesn't run too fast TODO: fix if (m_ticks % s_ticks_per_event != 0) return cpu_state; // Schedule the next thread @@ -96,24 +93,22 @@ cpu_status_t *Scheduler::schedule(cpu_status_t* cpu_state) { * @param status The current CPU status of the thread * @return The next CPU status */ -system::cpu_status_t *Scheduler::schedule_next(system::cpu_status_t* cpu_state) { +cpu_status_t* Scheduler::schedule_next(cpu_status_t* cpu_state) { - // Get the current thread + // Get the thread that is executing right now Thread* current_thread = m_threads[m_current_thread_index]; - // Save the current state + // Save its state current_thread->execution_state = cpu_state; current_thread -> save_sse_state(); if(current_thread->thread_state == ThreadState::RUNNING) current_thread->thread_state = ThreadState::WAITING; - // Switch to the next thread + // Switch to the thread that will now run m_current_thread_index++; m_current_thread_index %= m_threads.size(); - current_thread = m_threads[m_current_thread_index]; - // If the current thread is in the process then we can get the process Process* owner_process = current_process(); // Handle state changes @@ -155,13 +150,10 @@ system::cpu_status_t *Scheduler::schedule_next(system::cpu_status_t* cpu_state) current_thread -> thread_state = ThreadState::RUNNING; current_thread -> restore_sse_state(); - // Load the threads memory manager + // Load the thread's memory manager and task state MemoryManager::switch_active_memory_manager(owner_process->memory_manager); + CPU::tss.rsp0 = current_thread->tss_pointer(); - // Load the TSS for the thread - system::CPU::tss.rsp0 = current_thread->tss_pointer(); - - // Return the next thread's state return current_thread->execution_state; } @@ -212,11 +204,7 @@ uint64_t Scheduler::add_thread(Thread *thread) { * @return The system scheduler or nullptr if not found */ Scheduler *Scheduler::system_scheduler() { - - if(s_instance) - return s_instance; - - return nullptr; + return s_instance; } /** @@ -229,7 +217,7 @@ uint64_t Scheduler::ticks() const { } /** - * @brief Yield the current thread + * @brief Pass execution to the next thread */ cpu_status_t* Scheduler::yield() { @@ -297,7 +285,7 @@ uint64_t Scheduler::remove_process(Process *process) { * @param process The process to remove * @return The status of the CPU for the next process to run or nullptr if the process was not found */ -cpu_status_t* Scheduler::force_remove_process(Process *process) { +cpu_status_t* Scheduler::force_remove_process(Process* process) { // If there is no process, fail if (!process) @@ -311,12 +299,12 @@ cpu_status_t* Scheduler::force_remove_process(Process *process) { m_threads.erase(m_threads.begin() + index); // Delete the thread - process->remove_thread(thread->tid); + process -> remove_thread(thread->tid); } - - // Process will be dead now so run the next process (don't care about the execution state being outdated as we are removing it anyway) + // Process will be dead now so run the next process (don't care about the execution state being outdated as it is being + // removed regardless) return schedule_next(current_thread()->execution_state); } @@ -329,7 +317,7 @@ Process *Scheduler::current_process() { Process* current_process = nullptr; - // Make sure there is a active scheduler + // Make sure there is something with process attached if(!s_instance) return nullptr; @@ -377,7 +365,6 @@ Thread *Scheduler::current_thread() { */ void Scheduler::deactivate() { m_active = false; - } /** @@ -385,29 +372,30 @@ void Scheduler::deactivate() { * * @param multiboot The multiboot structure */ -void Scheduler::load_multiboot_elfs(system::Multiboot *multiboot) { +void Scheduler::load_multiboot_elfs(Multiboot *multiboot) { for(multiboot_tag* tag = multiboot -> start_tag(); tag->type != MULTIBOOT_TAG_TYPE_END; tag = (struct multiboot_tag *) ((multiboot_uint8_t *) tag + ((tag->size + 7) & ~7))) { + + // Tag is not an ELF if(tag -> type != MULTIBOOT_TAG_TYPE_MODULE) continue; - // Get the module tag + // Try create the elf from the module auto* module = (struct multiboot_tag_module*)tag; - - // Create the elf - auto* elf = new Elf64((uintptr_t)PhysicalMemoryManager::to_dm_region((uintptr_t )module->mod_start)); + auto* elf = new Elf64((uintptr_t)PhysicalMemoryManager::to_dm_region(module->mod_start)); if(!elf->is_valid()) continue; Logger::DEBUG() << "Creating process from multiboot module for " << module->cmdline << " (at 0x" << (uint64_t)module->mod_start << ")\n"; - // Create an array of args for the process + // Create an array of args for the process TODO: handle multiple args ("" & spaces) char* args[1] = {module->cmdline}; // Create the process auto* process = new Process(module->cmdline, args, 1, elf); Logger::DEBUG() << "Elf loaded to pid " << process->pid() << "\n"; + delete elf; } } diff --git a/kernel/src/system/cpu.cpp b/kernel/src/system/cpu.cpp index 77ee213b..bd95f576 100644 --- a/kernel/src/system/cpu.cpp +++ b/kernel/src/system/cpu.cpp @@ -31,9 +31,6 @@ CPU::CPU(GlobalDescriptorTable* gdt, Multiboot* multiboot){ init_sse(); } -/** - * @brief Destructor for the CPU class - */ CPU::~CPU() = default; [[noreturn]] void CPU::halt() { @@ -113,10 +110,8 @@ void CPU::print_registers(cpu_status_t *status) { uint64_t CPU::read_msr(uint32_t msr) { - // Low and high parts of the MSR - uint32_t low, high; - // Read the MSR + uint32_t low, high; asm volatile("rdmsr" : "=a" (low), "=d" (high) : "c" (msr)); // Return the value @@ -140,17 +135,17 @@ void CPU::stack_trace(size_t level) { // Get the first stack frame auto* frame = (stack_frame_t*)__builtin_frame_address(0); - size_t current_level = 0; // Loop through the frames logging - while (current_level < level && frame != nullptr){ + for (size_t current_level = 0; current_level < level; current_level++){ // Print the frame Logger::ERROR() << "(" << current_level << "):\t at 0x" << frame->rip << "\n"; // Next frame frame = frame -> next; - current_level++; + if (frame == nullptr) + break; } } @@ -163,7 +158,8 @@ void CPU::PANIC(char const *message, cpu_status_t* status) { // Get the current process Process* process = Scheduler::current_process(); - // Ensure ready to panic - At this point it is not an issue if it is possible can avoid the panic as it is most likely called by a place that cant switch to the avoidable state + // Ensure ready to panic - At this point it is not an issue if it is possible can avoid the panic as it is most + // likely called by a place that cant switch to the avoidable state if(!is_panicking) prepare_for_panic(); @@ -185,8 +181,10 @@ void CPU::PANIC(char const *message, cpu_status_t* status) { Logger::ERROR() << "----------------------------\n"; Logger::ERROR() << "Register Dump:\n"; + // Log the regs + cpu_status_t* new_status = nullptr; if(!status){ - auto* new_status = new cpu_status_t(); // Who cares about freeing we're fucked anyway at this point + new_status = new cpu_status_t; get_status(new_status); status = new_status; } @@ -205,6 +203,8 @@ void CPU::PANIC(char const *message, cpu_status_t* status) { // Halt halt(); + // Should really never get here but if somehow that happens why not be memory safe + delete new_status; } /** @@ -250,7 +250,6 @@ void CPU::init_tss(GlobalDescriptorTable* gdt) { uint8_t flags_1 = 0x89; uint8_t flags_2 = 0; - // Create the TSS descriptors uint64_t tss_descriptor_low = (uint64_t) base_3 << 56 | (uint64_t) flags_2 << 48 | (uint64_t) flags_1 << 40 | (uint64_t) base_2 << 32 | (uint64_t) base_1 << 16 | (uint64_t) limit_low; uint64_t tss_descriptor_high = base_4; From e008275001fa0af0d86ccd0237c8008bc5d1a8cd Mon Sep 17 00:00:00 2001 From: Max Tyson <98maxt98@gmail.com> Date: Sat, 3 May 2025 20:06:40 +1200 Subject: [PATCH 08/38] Fix linux FS --- kernel/src/filesystem/partition/msdos.cpp | 2 +- toolchain/create_disk_img.sh | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/kernel/src/filesystem/partition/msdos.cpp b/kernel/src/filesystem/partition/msdos.cpp index b00eae43..2a45d7c5 100644 --- a/kernel/src/filesystem/partition/msdos.cpp +++ b/kernel/src/filesystem/partition/msdos.cpp @@ -52,7 +52,7 @@ void MSDOSPartition::mount_partitions(Disk* hd) { break; default: - Logger::Out() << "Unknown or unimplemented partition type: " << entry.type << "\n"; + Logger::Out() << "Unknown or unimplemented partition type: 0x" << (uint64_t)entry.type << "\n"; } } diff --git a/toolchain/create_disk_img.sh b/toolchain/create_disk_img.sh index f18400c2..a1de386f 100755 --- a/toolchain/create_disk_img.sh +++ b/toolchain/create_disk_img.sh @@ -3,7 +3,8 @@ SCRIPTDIR=$(dirname "$BASH_SOURCE") source $SCRIPTDIR/MaxOS.sh -#TODO: Scalibitlity for partition size and amount of partitions +# TODO: Scalibitlity for partition size and amount of partitions +# TODO: Better loop device handling # If the disk image already exists no need to setup if [ -f ../MaxOS.img ]; then @@ -32,13 +33,18 @@ else 1 1 130 + t + b + a + 1 n p 2 131 243 - a - 1 + t + 2 + b w EOF @@ -55,8 +61,7 @@ fi # Get the partions part1="" part2="" -if [ -f ../MaxOS.img ]; then - msg "MacOS: Image mounted already" +if [ "$IS_MACOS" -eq 1 ]; then part1="${dev}s1" part2="${dev}s2" sudo diskutil unmount /Volumes/BOOT @@ -64,6 +69,7 @@ if [ -f ../MaxOS.img ]; then else msg "Attaching image to loop device" + dev="/dev/loop0" sudo losetup -D sudo losetup --partscan /dev/loop0 ../MaxOS.img || fail "Could not mount image to loop device" fi @@ -87,7 +93,6 @@ if [ "$IS_MACOS" -eq 1 ]; then sudo diskutil unmount "$part2" || warn "Couldn't unmount $part2 before formatting" sudo mount -t msdos "$part2" "$MOUNT_DIR/MaxOS_img_2" || fail "Could not mount partition 2" else - sudo diskutil unmount "$part2" || warn "Couldn't unmount $part2 before formatting" sudo mkfs.vfat -F 32 /dev/loop0p2 || fail "Could not create filesystem" sudo mount -o loop /dev/loop0p2 "$MOUNT_DIR/MaxOS_img_2" || fail "Could not mount image to mount point" fi @@ -124,5 +129,6 @@ if [ "$IS_MACOS" -eq 1 ]; then sync sudo sync else + msg "Installing GRUB to disk image: $dev" sudo grub-install --root-directory="$MOUNT_DIR/MaxOS_img_1" --no-floppy --modules="$GRUB_MODULES" "$dev" || fail "Could not install grub" fi \ No newline at end of file From fba458041acc1214365b95bcc4ab0786f6b7871b Mon Sep 17 00:00:00 2001 From: Max Tyson <98maxt98@gmail.com> Date: Sat, 3 May 2025 20:21:41 +1200 Subject: [PATCH 09/38] Fix directory read recursion --- kernel/include/filesystem/fat32.h | 2 + kernel/include/filesystem/filesystem.h | 2 + kernel/src/filesystem/fat32.cpp | 147 +++++++++++++------------ kernel/src/filesystem/filesystem.cpp | 11 +- kernel/src/filesystem/vfs.cpp | 15 ++- kernel/src/kernel.cpp | 3 +- 6 files changed, 102 insertions(+), 78 deletions(-) diff --git a/kernel/include/filesystem/fat32.h b/kernel/include/filesystem/fat32.h index 65bf17c5..104f6deb 100644 --- a/kernel/include/filesystem/fat32.h +++ b/kernel/include/filesystem/fat32.h @@ -205,6 +205,8 @@ namespace MaxOS{ static const size_t MAX_NAME_LENGTH = 255; + void read_from_disk() final; + File* create_file(const string& name) final; void remove_file(const string& name) final; diff --git a/kernel/include/filesystem/filesystem.h b/kernel/include/filesystem/filesystem.h index eae0ed6d..09379ec9 100644 --- a/kernel/include/filesystem/filesystem.h +++ b/kernel/include/filesystem/filesystem.h @@ -78,6 +78,8 @@ namespace MaxOS{ Directory(); virtual ~Directory(); + virtual void read_from_disk(); + common::Vector files(); File* open_file(const string& name); virtual File* create_file(const string& name); diff --git a/kernel/src/filesystem/fat32.cpp b/kernel/src/filesystem/fat32.cpp index 5e5e6e87..72038d68 100644 --- a/kernel/src/filesystem/fat32.cpp +++ b/kernel/src/filesystem/fat32.cpp @@ -270,76 +270,6 @@ Fat32Directory::Fat32Directory(Fat32Volume* volume, lba_t cluster, const string& m_name = name; - // Read the directory - for (; cluster != (lba_t)ClusterState::END_OF_CHAIN; cluster = m_volume -> next_cluster(cluster)) - { - - // Get the buffer and address to read - uint8_t buffer[volume -> bytes_per_sector * volume -> sectors_per_cluster]; - lba_t lba = volume -> data_lba + (cluster - 2) * volume -> sectors_per_cluster; - - // Read each sector in the cluster - for (size_t sector = 0; sector < volume -> sectors_per_cluster; sector++) - volume -> disk -> read(lba + sector, buffer + sector * volume -> bytes_per_sector, volume -> bytes_per_sector); - - string long_name = ""; - - // Parse the directory entries (each entry is 32 bytes) - for (size_t entry_offset = 0; entry_offset < sizeof(buffer); entry_offset += 32) - { - - // Get the entry - auto entry = (dir_entry_t*)&buffer[entry_offset]; - m_entries.push_back(*entry); - - // Skip free entries and volume labels - if (entry -> name[0] == (uint8_t)DirectoryEntryType::DELETED || (entry -> attributes & 0x08) == 0x08) - continue; - - // Check if the entry is the end of the directory - if (entry -> name[0] == (uint8_t)DirectoryEntryType::LAST) - return; - - // Parse the long name entry - if ((entry -> attributes & 0x0F) == 0x0F) - { - - // Extract the long name from each part (in reverse order) - auto* long_name_entry = (long_file_name_entry_t*)entry; - for (int i = 0; i < 13; i++) { - - // Get the character (in utf8 encoding) - char c; - if (i < 5) - c = long_name_entry->name1[i] & 0xFF; - else if (i < 11) - c = long_name_entry->name2[i - 5] & 0xFF; - else - c = long_name_entry->name3[i - 11] & 0xFF; - - // Check if the character is valid - if (c == '\0' || c == (char)0xFF) - break; - - // Add to the start as the entries are stored in reverse - long_name = string(c) + long_name; - } - } - - // Get the name of the entry - string name = long_name == "" ? string(entry->name, 8) : long_name; - long_name = ""; - - // Get the starting cluster - lba_t start_cluster = (entry -> first_cluster_high << 16) | entry -> first_cluster_low; - - // Store the file or directory - if (entry -> attributes & (uint8_t)DirectoryEntryType::DIRECTORY) - m_subdirectories.push_back(new Fat32Directory(volume, start_cluster, name)); - else - m_files.push_back(new Fat32File(volume, start_cluster, entry -> size, name)); - } - } } Fat32Directory::~Fat32Directory() @@ -549,6 +479,81 @@ void Fat32Directory::remove_entry(lba_t cluster, const string& name) } +void Fat32Directory::read_from_disk() { + + // Read the directory + for (lba_t cluster = m_first_cluster; cluster != (lba_t)ClusterState::END_OF_CHAIN; cluster = m_volume -> next_cluster(cluster)) + { + + // Get the buffer and address to read + uint8_t buffer[m_volume -> bytes_per_sector * m_volume -> sectors_per_cluster]; + lba_t lba = m_volume -> data_lba + (cluster - 2) * m_volume -> sectors_per_cluster; + + // Read each sector in the cluster + for (size_t sector = 0; sector < m_volume -> sectors_per_cluster; sector++) + m_volume -> disk -> read(lba + sector, buffer + sector * m_volume -> bytes_per_sector, m_volume -> bytes_per_sector); + + string long_name = ""; + + // Parse the directory entries (each entry is 32 bytes) + for (size_t entry_offset = 0; entry_offset < sizeof(buffer); entry_offset += 32) + { + + // Get the entry + auto entry = (dir_entry_t*)&buffer[entry_offset]; + m_entries.push_back(*entry); + + // Skip free entries and volume labels + if (entry -> name[0] == (uint8_t)DirectoryEntryType::DELETED || (entry -> attributes & 0x08) == 0x08) + continue; + + // Check if the entry is the end of the directory + if (entry -> name[0] == (uint8_t)DirectoryEntryType::LAST) + return; + + // Parse the long name entry + if ((entry -> attributes & 0x0F) == 0x0F) + { + + // Extract the long name from each part (in reverse order) + auto* long_name_entry = (long_file_name_entry_t*)entry; + for (int i = 0; i < 13; i++) { + + // Get the character (in utf8 encoding) + char c; + if (i < 5) + c = long_name_entry->name1[i] & 0xFF; + else if (i < 11) + c = long_name_entry->name2[i - 5] & 0xFF; + else + c = long_name_entry->name3[i - 11] & 0xFF; + + // Check if the character is valid + if (c == '\0' || c == (char)0xFF) + break; + + // Add to the start as the entries are stored in reverse + long_name = string(c) + long_name; + } + } + + // Get the name of the entry + string name = long_name == "" ? string(entry->name, 8) : long_name; + long_name = ""; + + // Get the starting cluster + lba_t start_cluster = (entry -> first_cluster_high << 16) | entry -> first_cluster_low; + + // Store the file or directory + if (entry -> attributes & (uint8_t)DirectoryEntryType::DIRECTORY) + m_subdirectories.push_back(new Fat32Directory(m_volume, start_cluster, name)); + else + m_files.push_back(new Fat32File(m_volume, start_cluster, entry -> size, name)); + } + } + +} + /** * @brief Create a new file in the directory * @@ -646,13 +651,13 @@ void Fat32Directory::remove_subdirectory(const string& name) } } - Fat32FileSystem::Fat32FileSystem(Disk* disk, uint32_t partition_offset) : m_volume(disk, partition_offset) { // Create the root directory m_root_directory = new Fat32Directory(&m_volume, m_volume.root_lba, "/"); + m_root_directory -> read_from_disk(); } diff --git a/kernel/src/filesystem/filesystem.cpp b/kernel/src/filesystem/filesystem.cpp index 4176ff32..7400403d 100644 --- a/kernel/src/filesystem/filesystem.cpp +++ b/kernel/src/filesystem/filesystem.cpp @@ -193,6 +193,13 @@ Directory::Directory() = default; Directory::~Directory() = default; +/** + * @brief Read the directory from the disk + */ +void Directory::read_from_disk(){ + +} + /** * @brief Get the files in the directory * @@ -262,8 +269,10 @@ Directory* Directory::open_subdirectory(const string& name) // Try to find the directory for (auto& subdirectory : m_subdirectories) - if (subdirectory->name() == name) + if (subdirectory->name() == name) { + subdirectory -> read_from_disk(); return subdirectory; + } // Directory not found return nullptr; diff --git a/kernel/src/filesystem/vfs.cpp b/kernel/src/filesystem/vfs.cpp index 87ee1c5f..f5ce9c7d 100644 --- a/kernel/src/filesystem/vfs.cpp +++ b/kernel/src/filesystem/vfs.cpp @@ -251,7 +251,7 @@ Directory* VirtualFileSystem::root_directory() } /** - * @brief Try to create a directory on the virtual file system + * @brief Try to open a directory on the virtual file system and read it's contents * * @param path The path to the directory * @return The directory object or null if it could not be opened @@ -272,12 +272,16 @@ Directory* VirtualFileSystem::open_directory(string path) string directory_path = Path::file_path(relative_path); // Open the directory - return fs -> get_directory(directory_path); + Directory* directory = fs -> get_directory(directory_path); + if (!directory) + return nullptr; + directory -> read_from_disk(); + return directory; } /** - * @brief Try to create a directory on the virtual file system + * @brief Try to create a directory on the virtual file system & read its contents * * @param path The path to the directory * @return The directory object or null if it could not be opened @@ -300,7 +304,10 @@ Directory* VirtualFileSystem::create_directory(string path) // Create the directory string directory_name = Path::file_name(path); - return parent_directory -> create_subdirectory(directory_name); + Directory* directory = parent_directory -> create_subdirectory(directory_name); + directory -> read_from_disk(); + + return directory; } /** diff --git a/kernel/src/kernel.cpp b/kernel/src/kernel.cpp index b7f8a4f1..eddc9a1c 100644 --- a/kernel/src/kernel.cpp +++ b/kernel/src/kernel.cpp @@ -97,8 +97,7 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic } // TODO: -// - Implement FAT32 -// - Test FAT32, When created I think it recursively reads all the subdirectories so it will be slow - need to open on demand instead +// - Fix FAT32 // - Userspace Files // - Implement ext2 // - Class & Struct docstrings From 0636fff9adb59dfb9edec723a2abca60f50b43f3 Mon Sep 17 00:00:00 2001 From: Max Tyson <98maxt98@gmail.com> Date: Mon, 5 May 2025 22:56:46 +1200 Subject: [PATCH 10/38] Fix File reading --- kernel/include/common/string.h | 2 + kernel/include/drivers/clock/clock.h | 2 +- kernel/include/filesystem/fat32.h | 24 +++--- kernel/include/filesystem/filesystem.h | 2 + kernel/src/common/string.cpp | 26 ++++++- kernel/src/drivers/clock/clock.cpp | 6 +- kernel/src/drivers/disk/ata.cpp | 9 ++- kernel/src/filesystem/fat32.cpp | 103 ++++++++++++------------- kernel/src/filesystem/filesystem.cpp | 36 +++++++++ kernel/src/filesystem/vfs.cpp | 2 +- kernel/src/kernel.cpp | 15 +--- toolchain/copy_filesystem.sh | 8 +- toolchain/run_qemu.sh | 1 + 13 files changed, 140 insertions(+), 96 deletions(-) diff --git a/kernel/include/common/string.h b/kernel/include/common/string.h index e668baaf..940ced4d 100644 --- a/kernel/include/common/string.h +++ b/kernel/include/common/string.h @@ -42,7 +42,9 @@ namespace MaxOS { bool starts_with(String const &other); String substring(int start, int length) const; + common::Vector split(String const &delimiter) const; + String strip() const; [[nodiscard]] String center(int width, char fill = ' ') const; diff --git a/kernel/include/drivers/clock/clock.h b/kernel/include/drivers/clock/clock.h index 6a0e8653..c95e69bc 100644 --- a/kernel/include/drivers/clock/clock.h +++ b/kernel/include/drivers/clock/clock.h @@ -155,7 +155,7 @@ namespace MaxOS { Clock(hardwarecommunication::AdvancedProgrammableInterruptController* apic, uint16_t time_between_events = 10); ~Clock(); - inline static uint64_t s_clock_accuracy = 1; + uint64_t clock_accuracy = 1; void activate() override; void delay(uint32_t milliseconds) const; diff --git a/kernel/include/filesystem/fat32.h b/kernel/include/filesystem/fat32.h index 104f6deb..0f8ff838 100644 --- a/kernel/include/filesystem/fat32.h +++ b/kernel/include/filesystem/fat32.h @@ -127,10 +127,6 @@ namespace MaxOS{ bpb32_t bpb; fs_info_t fsinfo; - uint8_t sectors_per_cluster; - uint16_t bytes_per_sector; - - size_t fat_size; size_t fat_total_clusters; lba_t fat_first_sector; lba_t fat_lba; @@ -141,15 +137,15 @@ namespace MaxOS{ drivers::disk::Disk* disk; - lba_t next_cluster(lba_t cluster); - lba_t set_next_cluster(lba_t cluster, lba_t next_cluster); - lba_t find_free_cluster(); + uint32_t next_cluster(uint32_t cluster); + uint32_t set_next_cluster(uint32_t cluster, uint32_t next_cluster); + uint32_t find_free_cluster(); - lba_t allocate_cluster(lba_t cluster); - lba_t allocate_cluster(lba_t cluster, size_t amount); + uint32_t allocate_cluster(uint32_t cluster); + uint32_t allocate_cluster(uint32_t cluster, size_t amount); - void free_cluster(lba_t cluster); - void free_cluster(lba_t cluster, size_t amount); + void free_cluster(uint32_t cluster); + void free_cluster(uint32_t cluster, size_t amount); }; /** @@ -162,17 +158,17 @@ namespace MaxOS{ private: Fat32Volume* m_volume; - lba_t m_first_cluster; + uint32_t m_first_cluster; public: - Fat32File(Fat32Volume* volume, lba_t cluster, size_t size, const string& name); + Fat32File(Fat32Volume* volume, uint32_t cluster, size_t size, const string& name); ~Fat32File() final; void write(const uint8_t* data, size_t size) final; void read(uint8_t* data, size_t size) final; void flush() final; - lba_t first_cluster() const { return m_first_cluster; } + uint32_t first_cluster() const { return m_first_cluster; } }; enum class DirectoryEntryType diff --git a/kernel/include/filesystem/filesystem.h b/kernel/include/filesystem/filesystem.h index 09379ec9..1560bbc3 100644 --- a/kernel/include/filesystem/filesystem.h +++ b/kernel/include/filesystem/filesystem.h @@ -92,6 +92,8 @@ namespace MaxOS{ string name(); size_t size(); + + void debug_print(int level = 0); }; /** diff --git a/kernel/src/common/string.cpp b/kernel/src/common/string.cpp index c0973e89..abfacdfe 100644 --- a/kernel/src/common/string.cpp +++ b/kernel/src/common/string.cpp @@ -491,7 +491,7 @@ String String::operator*(int times) const { repeated.m_string[i * m_length + j] = m_string[j]; // Write the null terminator - repeated.m_string[m_length] = '\0'; + repeated.m_string[repeated.m_length] = '\0'; // Return the repeated string return repeated; @@ -533,6 +533,30 @@ String String::center(int width, char fill) const { return centered; } +/** + * @brief Strips the string of whitespace + * + * @return The stripped string (new string) + */ +String String::strip() const { + + // The stripped string + String stripped; + stripped.copy(*this); + + // Search from the back for the earliest non-whitespace character + int end = m_length - 1; + while (end >= 0 && (m_string[end] == ' ' || m_string[end] == '\n' || m_string[end] == '\t')) + end--; + + // Make sure there is something to strip + if (end < 0) + return stripped; + + // Split the string to remove the end + return stripped.substring(0, end + 1); +} + /** * @brief Gets the length of a string * diff --git a/kernel/src/drivers/clock/clock.cpp b/kernel/src/drivers/clock/clock.cpp index 386e16b6..e3add5c8 100644 --- a/kernel/src/drivers/clock/clock.cpp +++ b/kernel/src/drivers/clock/clock.cpp @@ -145,7 +145,7 @@ void Clock::delay(uint32_t milliseconds) const { // Round the number of milliseconds UP to the nearest clock accuracy - uint64_t rounded_milliseconds = (milliseconds + s_clock_accuracy - 1) / s_clock_accuracy; + uint64_t rounded_milliseconds = (milliseconds + clock_accuracy - 1) / clock_accuracy; // Wait until the time has passed uint64_t ticks_until_delay_is_over = m_ticks + rounded_milliseconds; @@ -179,9 +179,7 @@ string Clock::device_name() { void Clock::calibrate(uint64_t ms_per_tick) { Logger::INFO() << "Calibrating Clock \n"; - - // Update the clock accuracy - s_clock_accuracy = ms_per_tick; + clock_accuracy = ms_per_tick; // Get the ticks per ms PIT pit(m_apic); diff --git a/kernel/src/drivers/disk/ata.cpp b/kernel/src/drivers/disk/ata.cpp index 0f45d216..bd574278 100644 --- a/kernel/src/drivers/disk/ata.cpp +++ b/kernel/src/drivers/disk/ata.cpp @@ -120,14 +120,15 @@ void AdvancedTechnologyAttachment::read(uint32_t sector, uint8_t* data_buffer, s if(status & 0x01) return; - // Read the data into the array - for(int i = 0; i < amount; i+= 2) + for(size_t i = 0; i < amount; i+= 2) { + + // Read from the disk (2 bytes) and store the first byte uint16_t read_data = m_data_port.read(); data_buffer[i] = read_data & 0x00FF; - // Place the next byte in the array if there is one - if(i+1 < amount) + // Place the second byte in the array if there is one + if(i + 1 < amount) data_buffer[i+1] = (read_data >> 8) & 0x00FF; } diff --git a/kernel/src/filesystem/fat32.cpp b/kernel/src/filesystem/fat32.cpp index 72038d68..d94ee21a 100644 --- a/kernel/src/filesystem/fat32.cpp +++ b/kernel/src/filesystem/fat32.cpp @@ -16,25 +16,18 @@ Fat32Volume::Fat32Volume(Disk* hd, uint32_t partition_offset) : disk(hd) { - // TODO: Table copies? - // Read the BIOS parameter block disk -> read(partition_offset, (uint8_t *)&bpb, sizeof(bpb32_t)); - // Set the volume information - bytes_per_sector = bpb.bytes_per_sector; - sectors_per_cluster = bpb.sectors_per_cluster; - - fat_size = bpb.table_size_32; - fat_total_clusters = bpb.total_sectors_32 - (bpb.reserved_sectors + (bpb.table_copies * fat_size)); + // Parse the FAT info + fat_total_clusters = bpb.total_sectors_32 - (bpb.reserved_sectors + (bpb.table_copies * bpb.table_size_32)); fat_first_sector = bpb.reserved_sectors; fat_lba = partition_offset + fat_first_sector; fat_info_lba = partition_offset + bpb.fat_info; disk -> read(fat_info_lba, (uint8_t *)&fsinfo, sizeof(fs_info_t)); - - data_lba = fat_lba + (bpb.table_copies * fat_size); - root_lba = data_lba + sectors_per_cluster * (bpb.root_cluster - 2); + data_lba = fat_lba + (bpb.table_copies * bpb.table_size_32); + root_lba = data_lba + bpb.sectors_per_cluster * (bpb.root_cluster - 2); // Validate the fat information if (fsinfo.lead_signature != 0x41615252 || fsinfo.structure_signature != 0x61417272 || fsinfo.trail_signature != 0xAA550000) @@ -58,13 +51,13 @@ lba_t Fat32Volume::next_cluster(lba_t cluster) { // Get the location in the FAT table - uint32_t offset = cluster * sizeof(uint32_t); - uint32_t sector = fat_first_sector + (offset / bytes_per_sector); - uint32_t entry = offset % bytes_per_sector; + lba_t offset = cluster * sizeof(uint32_t); + lba_t sector = fat_first_sector + (offset / bpb.bytes_per_sector); + uint32_t entry = offset % bpb.bytes_per_sector; // Read the FAT entry - uint8_t fat[bytes_per_sector]; - disk -> read(sector, fat, bytes_per_sector); + uint8_t fat[bpb.bytes_per_sector]; + disk -> read(sector, fat, bpb.bytes_per_sector); // Get the next cluster info (mask the upper 4 bits) uint32_t next_cluster = *(uint32_t *)&fat[entry]; @@ -78,21 +71,21 @@ lba_t Fat32Volume::next_cluster(lba_t cluster) * @param next_cluster The next cluster in the chain * @return The next cluster in the chain */ -lba_t Fat32Volume::set_next_cluster(lba_t cluster, lba_t next_cluster) +uint32_t Fat32Volume::set_next_cluster(uint32_t cluster, uint32_t next_cluster) { // Get the location in the FAT table - uint32_t offset = cluster * sizeof(uint32_t); - uint32_t sector = fat_first_sector + (offset / bytes_per_sector); - uint32_t entry = offset % bytes_per_sector; + lba_t offset = cluster * sizeof(uint32_t); + lba_t sector = fat_first_sector + (offset / bpb.bytes_per_sector); + uint32_t entry = offset % bpb.bytes_per_sector; // Read the FAT entry - uint8_t fat[bytes_per_sector]; - disk -> read(sector, fat, bytes_per_sector); + uint8_t fat[bpb.bytes_per_sector]; + disk -> read(sector, fat, bpb.bytes_per_sector); // Set the next cluster info (mask the upper 4 bits) *(uint32_t *)&fat[entry] = next_cluster & 0x0FFFFFFF; - disk -> write(sector, fat, bytes_per_sector); + disk -> write(sector, fat, bpb.bytes_per_sector); return next_cluster; } @@ -102,16 +95,16 @@ lba_t Fat32Volume::set_next_cluster(lba_t cluster, lba_t next_cluster) * * @return The first free cluster in the FAT table */ -lba_t Fat32Volume::find_free_cluster() +uint32_t Fat32Volume::find_free_cluster() { // Get the first free cluster - for (lba_t start = fsinfo.next_free_cluster; start < fat_total_clusters + 1; start++) + for (uint32_t start = fsinfo.next_free_cluster; start < fat_total_clusters + 1; start++) if (next_cluster(start) == 0) return start; // Check any clusters before the first free cluster - for (lba_t start = 0; start < fsinfo.next_free_cluster; start++) + for (uint32_t start = 0; start < fsinfo.next_free_cluster; start++) if (next_cluster(start) == 0) return start; @@ -125,11 +118,11 @@ lba_t Fat32Volume::find_free_cluster() * @param cluster The base cluster to start from or 0 if this is a new chain * @return The next cluster in the chain */ -lba_t Fat32Volume::allocate_cluster(lba_t cluster) +uint32_t Fat32Volume::allocate_cluster(uint32_t cluster) { // Allocate 1 cluster - allocate_cluster(cluster, 1); + return allocate_cluster(cluster, 1); } /** @@ -139,7 +132,7 @@ lba_t Fat32Volume::allocate_cluster(lba_t cluster) * @param amount The number of clusters to allocate * @return The next cluster in the chain */ -lba_t Fat32Volume::allocate_cluster(lba_t cluster, size_t amount) +uint32_t Fat32Volume::allocate_cluster(uint32_t cluster, size_t amount) { // Make sure within bounds @@ -149,7 +142,7 @@ lba_t Fat32Volume::allocate_cluster(lba_t cluster, size_t amount) // Go through allocating the clusters for (size_t i = 0; i < amount; i++) { - lba_t next_cluster = find_free_cluster(); + uint32_t next_cluster = find_free_cluster(); // Update the fsinfo fsinfo.next_free_cluster = next_cluster + 1; @@ -166,7 +159,7 @@ lba_t Fat32Volume::allocate_cluster(lba_t cluster, size_t amount) disk -> write(fat_info_lba, (uint8_t *)&fsinfo, sizeof(fs_info_t)); // Finish the chin - set_next_cluster(cluster, (lba_t)ClusterState::END_OF_CHAIN); + set_next_cluster(cluster, (uint32_t)ClusterState::END_OF_CHAIN); return cluster; } @@ -190,7 +183,7 @@ void Fat32Volume::free_cluster(lba_t cluster) * @param cluster The base cluster to start from * @param amount The number of clusters to free */ -void Fat32Volume::free_cluster(lba_t cluster, size_t amount) +void Fat32Volume::free_cluster(uint32_t cluster, size_t amount) { // Make sure within bounds @@ -201,8 +194,8 @@ void Fat32Volume::free_cluster(lba_t cluster, size_t amount) for (size_t i = 0; i < amount; i++) { - // Store the next cluster before it is set to free - lba_t next_in_chain = next_cluster(cluster); + // Find the next cluster before it is removed from the chain + uint32_t next_in_chain = next_cluster(cluster); // Update the fsinfo fsinfo.next_free_cluster = cluster; @@ -217,11 +210,11 @@ void Fat32Volume::free_cluster(lba_t cluster, size_t amount) disk -> write(fat_info_lba, (uint8_t *)&fsinfo, sizeof(fs_info_t)); // Mark the end of the chain - set_next_cluster(cluster, (lba_t)ClusterState::END_OF_CHAIN); + set_next_cluster(cluster, (uint32_t)ClusterState::END_OF_CHAIN); } -Fat32File::Fat32File(Fat32Volume* volume, lba_t cluster, size_t size, const string& name) +Fat32File::Fat32File(Fat32Volume* volume, uint32_t cluster, size_t size, const string& name) : m_volume(volume), m_first_cluster(cluster) { @@ -263,7 +256,7 @@ void Fat32File::flush() File::flush(); } -Fat32Directory::Fat32Directory(Fat32Volume* volume, lba_t cluster, const string& name) +Fat32Directory::Fat32Directory(Fat32Volume* volume, uint32_t cluster, const string& name) : m_volume(volume), m_first_cluster(cluster) { @@ -294,8 +287,10 @@ Fat32Directory::~Fat32Directory() lba_t Fat32Directory::create_entry(const string& name, bool is_directory) { + //TODO: Write to all the FAT copies + // Allocate a cluster for the new entry - lba_t cluster = m_volume -> allocate_cluster(0); + uint32_t cluster = m_volume -> allocate_cluster(0); if (cluster == 0) return 0; @@ -377,7 +372,7 @@ lba_t Fat32Directory::create_entry(const string& name, bool is_directory) } // Get where to write the entry - lba_t lba = m_volume -> data_lba + (m_first_cluster - 2) * m_volume -> sectors_per_cluster; + lba_t lba = m_volume -> data_lba + (m_first_cluster - 2) * m_volume -> bpb.sectors_per_cluster; uint32_t offset = entry_index * sizeof(dir_entry_t); // Write the long file name entries @@ -410,7 +405,7 @@ lba_t Fat32Directory::create_entry(const string& name, bool is_directory) parent_dir_entry.first_cluster_low = m_first_cluster & 0xFFFF; // Write the entries to the disk - lba_t child_lba = m_volume -> data_lba + (cluster - 2) * m_volume -> sectors_per_cluster; + lba_t child_lba = m_volume -> data_lba + (cluster - 2) * m_volume -> bpb.sectors_per_cluster; m_volume -> disk -> write(child_lba, (uint8_t *)¤t_dir_entry, sizeof(dir_entry_t)); m_volume -> disk -> write(child_lba + sizeof(dir_entry_t), (uint8_t *)&parent_dir_entry, sizeof(dir_entry_t)); @@ -424,7 +419,7 @@ lba_t Fat32Directory::create_entry(const string& name, bool is_directory) * @param cluster The cluster of the entry to remove * @param name The name of the entry to remove */ -void Fat32Directory::remove_entry(lba_t cluster, const string& name) +void Fat32Directory::remove_entry(uint32_t cluster, const string& name) { // Find the entry in the directory @@ -442,7 +437,7 @@ void Fat32Directory::remove_entry(lba_t cluster, const string& name) continue; // Check if the entry is the one to remove - lba_t start_cluster = (entry.first_cluster_high << 16) | entry.first_cluster_low; + uint32_t start_cluster = (entry.first_cluster_high << 16) | entry.first_cluster_low; if (start_cluster == cluster) break; } @@ -461,20 +456,20 @@ void Fat32Directory::remove_entry(lba_t cluster, const string& name) m_entries[i].name[0] = (uint8_t)DirectoryEntryType::DELETED; // Update the entries on the disk - lba_t first_directory_entry_lba = m_volume -> data_lba + (m_first_cluster - 2) * m_volume -> sectors_per_cluster; + lba_t first_directory_entry_lba = m_volume -> data_lba + (m_first_cluster - 2) * m_volume -> bpb.sectors_per_cluster; for (size_t i = delete_entry_index; i <= entry_index; i++) { - uint32_t offset = (i * sizeof(dir_entry_t)) / m_volume -> bytes_per_sector; + uint32_t offset = (i * sizeof(dir_entry_t)) / m_volume -> bpb.bytes_per_sector; m_volume -> disk -> write(first_directory_entry_lba + offset, (uint8_t *)&m_entries[i], sizeof(dir_entry_t)); } // Count the number of clusters in the chain size_t cluster_count = 0; - lba_t next_cluster = cluster; - for (; next_cluster < (lba_t)ClusterState::BAD; next_cluster = m_volume -> next_cluster(next_cluster)) + for (uint32_t next_cluster = cluster; next_cluster < (lba_t)ClusterState::BAD; next_cluster = m_volume -> next_cluster(next_cluster)) cluster_count++; - // Free the clusters in the chain + // Free the clusters in the chain (more performant than calling a single + // free_cluser(cluster) as fs is updated at the end) m_volume -> free_cluster(cluster, cluster_count); } @@ -482,16 +477,16 @@ void Fat32Directory::remove_entry(lba_t cluster, const string& name) void Fat32Directory::read_from_disk() { // Read the directory - for (lba_t cluster = m_first_cluster; cluster != (lba_t)ClusterState::END_OF_CHAIN; cluster = m_volume -> next_cluster(cluster)) + for (uint32_t cluster = m_first_cluster; cluster != (uint32_t)ClusterState::END_OF_CHAIN; cluster = m_volume -> next_cluster(cluster)) { // Get the buffer and address to read - uint8_t buffer[m_volume -> bytes_per_sector * m_volume -> sectors_per_cluster]; - lba_t lba = m_volume -> data_lba + (cluster - 2) * m_volume -> sectors_per_cluster; + uint8_t buffer[m_volume -> bpb.bytes_per_sector * m_volume -> bpb.sectors_per_cluster]; + lba_t lba = m_volume -> data_lba + (cluster - 2) * m_volume -> bpb.sectors_per_cluster; // Read each sector in the cluster - for (size_t sector = 0; sector < m_volume -> sectors_per_cluster; sector++) - m_volume -> disk -> read(lba + sector, buffer + sector * m_volume -> bytes_per_sector, m_volume -> bytes_per_sector); + for (size_t sector = 0; sector < m_volume -> bpb.sectors_per_cluster; sector++) + m_volume -> disk -> read(lba + sector, buffer + sector * m_volume -> bpb.bytes_per_sector, m_volume -> bpb.bytes_per_sector); string long_name = ""; @@ -542,7 +537,7 @@ void Fat32Directory::read_from_disk() { long_name = ""; // Get the starting cluster - lba_t start_cluster = (entry -> first_cluster_high << 16) | entry -> first_cluster_low; + uint32_t start_cluster = (entry -> first_cluster_high << 16) | entry -> first_cluster_low; // Store the file or directory if (entry -> attributes & (uint8_t)DirectoryEntryType::DIRECTORY) @@ -656,7 +651,7 @@ Fat32FileSystem::Fat32FileSystem(Disk* disk, uint32_t partition_offset) { // Create the root directory - m_root_directory = new Fat32Directory(&m_volume, m_volume.root_lba, "/"); + m_root_directory = new Fat32Directory(&m_volume, m_volume.bpb.root_cluster, "/"); m_root_directory -> read_from_disk(); } diff --git a/kernel/src/filesystem/filesystem.cpp b/kernel/src/filesystem/filesystem.cpp index 7400403d..a0467d66 100644 --- a/kernel/src/filesystem/filesystem.cpp +++ b/kernel/src/filesystem/filesystem.cpp @@ -3,9 +3,11 @@ // #include +#include using namespace MaxOS; using namespace MaxOS::filesystem; +using namespace MaxOS::common; /** * @brief Check if a path is valid @@ -323,6 +325,40 @@ size_t Directory::size() return size; +} + +/** + * @brief Print the contents of this directory (recursive print of sub-dirs) + * + * @param level Level of recursion + */ +void Directory::debug_print(int level){ + + // Prevent infinite recursion bugs + level++; + ASSERT(level < 1000, "Infinte recursion in tree printing of directory"); + + // Print all the files + for (auto& file : m_files) + Logger::DEBUG() << (string)"-" * level << " " << file -> name() << " (file)" << " Size: 0x" << file -> size() << "\n"; + + // Recursive call all the directories + for(auto& directory : m_subdirectories){ + + // Prevent trying to re read this directory or the parent one + string name = directory -> name(); + name = name.strip(); + if(name == "." || name == "..") + continue; + + Logger::DEBUG() << string("-") * level << name << " (directory)" "\n"; + directory -> read_from_disk(); + directory -> debug_print(level); + + } + + + } FileSystem::FileSystem() = default; diff --git a/kernel/src/filesystem/vfs.cpp b/kernel/src/filesystem/vfs.cpp index f5ce9c7d..a148013c 100644 --- a/kernel/src/filesystem/vfs.cpp +++ b/kernel/src/filesystem/vfs.cpp @@ -418,4 +418,4 @@ void VirtualFileSystem::delete_file(string path) // Delete the file string file_name = Path::file_name(path); directory -> remove_file(file_name); -} \ No newline at end of file +} diff --git a/kernel/src/kernel.cpp b/kernel/src/kernel.cpp index eddc9a1c..8ff58510 100644 --- a/kernel/src/kernel.cpp +++ b/kernel/src/kernel.cpp @@ -74,16 +74,7 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic // FS Tests Directory* root = vfs.root_directory(); ASSERT(root != nullptr, "Root directory is null\n"); - for (auto& file : root->files()) - { - Logger::DEBUG() << "File: " << file->name() << "\n"; - Logger::DEBUG() << "Size: " << file->size() << "\n"; - } - for (auto& directory : root->subdirectories()) - { - Logger::DEBUG() << "Directory: " << directory->name() << "\n"; - Logger::DEBUG() << "Size: " << directory->size() << "\n"; - } + root ->debug_print(); Logger::HEADER() << "Stage {4}: System Finalisation\n"; Scheduler scheduler(multiboot); @@ -97,7 +88,9 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic } // TODO: -// - Fix FAT32 +// - Fix FAT32 (extenstions, strip, lfn) +// - FAT32 Tests +// - Fix tabs (mac mess up) // - Userspace Files // - Implement ext2 // - Class & Struct docstrings diff --git a/toolchain/copy_filesystem.sh b/toolchain/copy_filesystem.sh index e8a38c2c..1952e399 100755 --- a/toolchain/copy_filesystem.sh +++ b/toolchain/copy_filesystem.sh @@ -13,7 +13,8 @@ fi DESTINATION="$MOUNT_DIR/MaxOS_img_1" -# MacOS uses an ISO for now +# Produce an ISO? default to no +: "${USE_ISO:=0}" if [ "$USE_ISO" -eq 1 ]; then DESTINATION="$SCRIPTDIR/../iso" if [ ! -d "$DESTINATION" ]; then @@ -27,11 +28,6 @@ sudo rm -rf "$DESTINATION/boot" && sudo cp -r $SCRIPTDIR/../filesystem/boot sudo rm -rf "$DESTINATION/os" && sudo cp -r $SCRIPTDIR/../filesystem/os $DESTINATION sudo rm -rf "$DESTINATION/user" && sudo cp -r $SCRIPTDIR/../filesystem/user $DESTINATION -# If MacOS is used remove the MacOS folder -if [ "$USE_ISO" -eq 1 ]; then - sudo rm -rf "$DESTINATION/MacOS" -fi - # Sync filesystem msg "Syncing filesystem" sudo sync diff --git a/toolchain/run_qemu.sh b/toolchain/run_qemu.sh index 5abde671..6ff7c89b 100755 --- a/toolchain/run_qemu.sh +++ b/toolchain/run_qemu.sh @@ -135,6 +135,7 @@ if [ "$USE_DEBUG" -ne "0" ]; then fi BOOT_DEVICE="" +: "${USE_ISO:=0}" if [ "$USE_ISO" -eq 1 ]; then msg "Using ISO boot device." BOOT_DEVICE="-cdrom ../MaxOS.iso" From 88ac60c07110feafbcb5612d1a201d54fc84c34c Mon Sep 17 00:00:00 2001 From: Max Tyson <98maxt98@gmail.com> Date: Tue, 6 May 2025 19:36:27 +1200 Subject: [PATCH 11/38] Fix large rebuild on version changes and grub end of file error --- .github/workflows/max-os.yml | 2 +- kernel/CMakeLists.txt | 30 +++++++++++-------- kernel/include/common/logger.h | 3 +- kernel/src/common/logger.cpp | 6 ++-- kernel/src/filesystem/filesystem.cpp | 9 +++--- kernel/src/kernel.cpp | 3 +- toolchain/copy_filesystem.sh | 27 ++++++++++++----- toolchain/create_disk_img.sh | 29 ++++++++++-------- toolchain/make_cross_compiler.sh | 3 +- .../{post_process => pre_process}/checks.sh | 0 .../{post_process => pre_process}/progress.sh | 4 ++- .../{post_process => pre_process}/run.sh | 0 .../{post_process => pre_process}/version.sh | 0 13 files changed, 70 insertions(+), 46 deletions(-) rename toolchain/{post_process => pre_process}/checks.sh (100%) rename toolchain/{post_process => pre_process}/progress.sh (59%) rename toolchain/{post_process => pre_process}/run.sh (100%) rename toolchain/{post_process => pre_process}/version.sh (100%) diff --git a/.github/workflows/max-os.yml b/.github/workflows/max-os.yml index 3b1c199c..7fbaa424 100644 --- a/.github/workflows/max-os.yml +++ b/.github/workflows/max-os.yml @@ -42,7 +42,7 @@ jobs: - name: Build MaxOS (Release) run: | - cd toolchain/post_process + cd toolchain/pre_process ./version.sh --force cd ../../ mkdir -p cmake-build diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index cb05c1f0..28fa5ed0 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -13,21 +13,27 @@ SET_SOURCE_FILES_PROPERTIES(${ASM_SRCS} PROPERTIES LANGUAGE ASM) # Find all the cpp and s files in the src directory (recursive) FILE(GLOB_RECURSE KERNEL_SRCS src/*.cpp src/*.s) -# Create the kernel -ADD_EXECUTABLE(MaxOSk64 ${KERNEL_SRCS}) -TARGET_COMPILE_DEFINITIONS(MaxOSk64 PUBLIC MAXOS_KERNEL) - # Update the version before building -ADD_CUSTOM_COMMAND( - COMMENT "post_processing kernel" - TARGET MaxOSk64 - PRE_BUILD - COMMAND ${CMAKE_SOURCE_DIR}/toolchain/post_process/run.sh - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_SOURCE_DIR}/kernel/include/common/version.h.tmp ${CMAKE_SOURCE_DIR}/kernel/include/common/version.h - COMMAND ${CMAKE_COMMAND} -E remove ${CMAKE_SOURCE_DIR}/kernel/include/common/version.h.tmp - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} +set(VERSION_HEADER "${CMAKE_SOURCE_DIR}/kernel/include/common/version.h") +set(VERSION_HEADER_TMP "${CMAKE_SOURCE_DIR}/kernel/include/common/version.h.tmp") +set(POST_PROCESS_SCRIPT "${CMAKE_SOURCE_DIR}/toolchain/pre_process/run.sh") +add_custom_command( + OUTPUT "${VERSION_HEADER}" + COMMAND "${POST_PROCESS_SCRIPT}" + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${VERSION_HEADER_TMP}" "${VERSION_HEADER}" + COMMAND ${CMAKE_COMMAND} -E remove "${VERSION_HEADER_TMP}" + DEPENDS ${KERNEL_SRCS} + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" + COMMENT "Regenerating version.h because kernel sources changed" +) +add_custom_target(VersionScript DEPENDS "${VERSION_HEADER}" ) +# Create the kernel +ADD_EXECUTABLE(MaxOSk64 ${KERNEL_SRCS} ${VERSION_HEADER}) +add_dependencies(MaxOSk64 VersionScript) +TARGET_COMPILE_DEFINITIONS(MaxOSk64 PUBLIC MAXOS_KERNEL) + # Linker SET(LINKER_SCRIPT ${CMAKE_SOURCE_DIR}/kernel/src/linker.ld) SET_TARGET_PROPERTIES(MaxOSk64 PROPERTIES LINK_DEPENDS ${LINKER_SCRIPT}) diff --git a/kernel/include/common/logger.h b/kernel/include/common/logger.h index 18dbcaa1..7f2b78d2 100644 --- a/kernel/include/common/logger.h +++ b/kernel/include/common/logger.h @@ -7,7 +7,6 @@ #include #include -#include @@ -40,7 +39,7 @@ bool m_log_writers_enabled[m_max_log_writers] = {false, false, false, false, false}; // Progress bar - static const uint8_t s_progress_total = 25; + static inline uint8_t s_progress_total = 100; uint8_t m_progress_current = 0; static inline Logger* s_active_logger = nullptr; diff --git a/kernel/src/common/logger.cpp b/kernel/src/common/logger.cpp index 886bba73..9e096b43 100644 --- a/kernel/src/common/logger.cpp +++ b/kernel/src/common/logger.cpp @@ -4,19 +4,21 @@ #include #include #include +#include using namespace MaxOS; using namespace MaxOS::common; using namespace MaxOS::drivers::console; - Logger::Logger() :m_log_writers() { - // Set the logger to this s_active_logger = this; + // The following line is generated automatically by the MaxOS build system. + s_progress_total = 23; + } diff --git a/kernel/src/filesystem/filesystem.cpp b/kernel/src/filesystem/filesystem.cpp index a0467d66..efd83d14 100644 --- a/kernel/src/filesystem/filesystem.cpp +++ b/kernel/src/filesystem/filesystem.cpp @@ -336,7 +336,7 @@ void Directory::debug_print(int level){ // Prevent infinite recursion bugs level++; - ASSERT(level < 1000, "Infinte recursion in tree printing of directory"); + ASSERT(level < 1000, "Infinite recursion in tree printing of directory"); // Print all the files for (auto& file : m_files) @@ -345,13 +345,12 @@ void Directory::debug_print(int level){ // Recursive call all the directories for(auto& directory : m_subdirectories){ - // Prevent trying to re read this directory or the parent one - string name = directory -> name(); - name = name.strip(); + // Prevent trying to re-read this directory or the parent one + string name = directory -> name().strip(); if(name == "." || name == "..") continue; - Logger::DEBUG() << string("-") * level << name << " (directory)" "\n"; + Logger::DEBUG() << string("-") * level << " " << name << " (directory) \n"; directory -> read_from_disk(); directory -> debug_print(level); diff --git a/kernel/src/kernel.cpp b/kernel/src/kernel.cpp index 8ff58510..bfc2e242 100644 --- a/kernel/src/kernel.cpp +++ b/kernel/src/kernel.cpp @@ -70,11 +70,10 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic driver_manager.initialise_drivers(); driver_manager.activate_drivers(); - // FS Tests Directory* root = vfs.root_directory(); ASSERT(root != nullptr, "Root directory is null\n"); - root ->debug_print(); + root -> debug_print(); Logger::HEADER() << "Stage {4}: System Finalisation\n"; Scheduler scheduler(multiboot); diff --git a/toolchain/copy_filesystem.sh b/toolchain/copy_filesystem.sh index 1952e399..c3a75c26 100755 --- a/toolchain/copy_filesystem.sh +++ b/toolchain/copy_filesystem.sh @@ -22,19 +22,30 @@ if [ "$USE_ISO" -eq 1 ]; then fi fi -#Copy filesystem -msg "Copying filesystem to image" -sudo rm -rf "$DESTINATION/boot" && sudo cp -r $SCRIPTDIR/../filesystem/boot $DESTINATION -sudo rm -rf "$DESTINATION/os" && sudo cp -r $SCRIPTDIR/../filesystem/os $DESTINATION -sudo rm -rf "$DESTINATION/user" && sudo cp -r $SCRIPTDIR/../filesystem/user $DESTINATION +# Linux: setup loop device to write to +dev="" +IMAGE="../MaxOS.img" +if [ "$IS_MACOS" -ne 1 ]; then + msg "Mounting img" + dev=$(sudo losetup --find --show --partscan "$IMAGE") + sudo mount "$dev"p1 "$MOUNT_DIR/MaxOS_img_1" + sudo mount "$dev"p2 "$MOUNT_DIR/MaxOS_img_2" +fi -# Sync filesystem -msg "Syncing filesystem" -sudo sync +# Syncing local filesystem +msg "Copying filesystem to image" +sudo rsync --no-owner --no-group -a --delete -c "$SCRIPTDIR/../filesystem/" "$MOUNT_DIR/MaxOS_img_1/" +#TODO: rsync Mac, unmount/remount mac # Create the iso if [ "$USE_ISO" -eq 1 ]; then msg "Creating ISO" i686-elf-grub-mkrescue --modules="part_msdos fat normal" --output="$SCRIPTDIR/../MaxOS.iso" $DESTINATION || fail "Failed to create rescue ISO" sudo rm -rf $DESTINATION +fi + +# Linux: clean up the loop device +if [ "$IS_MACOS" -ne 1 ]; then + sudo umount "$dev"p1 "$dev"p2 + sudo losetup -d "$dev" fi \ No newline at end of file diff --git a/toolchain/create_disk_img.sh b/toolchain/create_disk_img.sh index a1de386f..35c0e007 100755 --- a/toolchain/create_disk_img.sh +++ b/toolchain/create_disk_img.sh @@ -6,17 +6,19 @@ source $SCRIPTDIR/MaxOS.sh # TODO: Scalibitlity for partition size and amount of partitions # TODO: Better loop device handling +IMAGE="../MaxOS.img" + # If the disk image already exists no need to setup -if [ -f ../MaxOS.img ]; then +if [ -f "$IMAGE" ]; then msg "Image already exists" exit 0 fi # Remove the disk on failure TODO: Also unmount the image -ON_FAIL="rm -f ../MaxOS.img" +ON_FAIL="rm -f $IMAGE" #Create a 2GB image -qemu-img create ../MaxOS.img 2G || fail "Could not create image" +qemu-img create "$IMAGE" 2G || fail "Could not create image" #Partion & Mount the image dev="" @@ -26,7 +28,7 @@ if [ "$IS_MACOS" -eq 1 ]; then dev=${dev_arr[0]} sudo diskutil partitionDisk $dev MBRFormat "MS-DOS FAT32" "BOOT" 1G "MS-DOS FAT32" "DATA" R else - fdisk ../MaxOS.img -u=cylinders << EOF + fdisk "$IMAGE" -u=cylinders << EOF o n p @@ -47,7 +49,7 @@ else b w EOF - + dev=$(sudo losetup --find --show --partscan "$IMAGE") fi #Try and unmount the old mount points @@ -69,10 +71,11 @@ if [ "$IS_MACOS" -eq 1 ]; then else msg "Attaching image to loop device" - dev="/dev/loop0" - sudo losetup -D - sudo losetup --partscan /dev/loop0 ../MaxOS.img || fail "Could not mount image to loop device" + part1="${dev}p1" + part2="${dev}p2" fi +msg "${part1}" +msg "${part2}" ## IMAGE 1 msg "Creating filesystem for partition 1" @@ -81,8 +84,8 @@ if [ "$IS_MACOS" -eq 1 ]; then sudo diskutil unmount "$part1" || warn "Couldn't unmount $part1 before formatting" sudo mount -t msdos "$part1" "$MOUNT_DIR/MaxOS_img_1" || fail "Could not mount partition 1" else - sudo mkfs.vfat -F 32 /dev/loop0p1 || fail "Could not create filesystem" - sudo mount -o loop /dev/loop0p1 "$MOUNT_DIR/MaxOS_img_1" || fail "Could not mount image to mount point" + sudo mkfs.vfat -F 32 "$part1" || fail "Could not create filesystem" + sudo mount "$part1" "$MOUNT_DIR/MaxOS_img_1" || fail "Could not mount image to mount point" fi @@ -93,8 +96,8 @@ if [ "$IS_MACOS" -eq 1 ]; then sudo diskutil unmount "$part2" || warn "Couldn't unmount $part2 before formatting" sudo mount -t msdos "$part2" "$MOUNT_DIR/MaxOS_img_2" || fail "Could not mount partition 2" else - sudo mkfs.vfat -F 32 /dev/loop0p2 || fail "Could not create filesystem" - sudo mount -o loop /dev/loop0p2 "$MOUNT_DIR/MaxOS_img_2" || fail "Could not mount image to mount point" + sudo mkfs.vfat -F 32 "$part2" || fail "Could not create filesystem" + sudo mount "$part2" "$MOUNT_DIR/MaxOS_img_2" || fail "Could not mount image to mount point" fi # Sync @@ -131,4 +134,6 @@ if [ "$IS_MACOS" -eq 1 ]; then else msg "Installing GRUB to disk image: $dev" sudo grub-install --root-directory="$MOUNT_DIR/MaxOS_img_1" --no-floppy --modules="$GRUB_MODULES" "$dev" || fail "Could not install grub" + sudo umount "${part1}" "${part2}" + sudo losetup -d "$dev" fi \ No newline at end of file diff --git a/toolchain/make_cross_compiler.sh b/toolchain/make_cross_compiler.sh index 7f318466..1252c22f 100755 --- a/toolchain/make_cross_compiler.sh +++ b/toolchain/make_cross_compiler.sh @@ -35,6 +35,7 @@ if [ "$1" != "--no-deps" ]; then cmake \ nasm \ telnet \ + rsync \ || fail "Couldn't install dependencies" fi @@ -170,6 +171,6 @@ cd ../ ls # Setup the first version of the kernel -cd post_process +cd pre_process ./version.sh --force cd ../ \ No newline at end of file diff --git a/toolchain/post_process/checks.sh b/toolchain/pre_process/checks.sh similarity index 100% rename from toolchain/post_process/checks.sh rename to toolchain/pre_process/checks.sh diff --git a/toolchain/post_process/progress.sh b/toolchain/pre_process/progress.sh similarity index 59% rename from toolchain/post_process/progress.sh rename to toolchain/pre_process/progress.sh index b25de2f4..ecf60d18 100755 --- a/toolchain/post_process/progress.sh +++ b/toolchain/pre_process/progress.sh @@ -8,4 +8,6 @@ TOTAL=$(grep -r "Logger::INFO" "${SCRIPTDIR}/../../kernel/src/" | wc -l) msg "Total INFO: ${TOTAL}" # Store in the source file -$SED_EXC -i "s/static const uint8_t s_progress_total { 100 };/static const uint8_t s_progress_total { ${TOTAL} };/" "${SCRIPTDIR}/../../kernel/include/common/logger.h" \ No newline at end of file +$SED_EXC -i \ + -E "s/(s_progress_total[[:space:]]*=[[:space:]]*)[0-9]+;/\1${TOTAL};/" \ + "${SCRIPTDIR}/../../kernel/src/common/logger.cpp" \ No newline at end of file diff --git a/toolchain/post_process/run.sh b/toolchain/pre_process/run.sh similarity index 100% rename from toolchain/post_process/run.sh rename to toolchain/pre_process/run.sh diff --git a/toolchain/post_process/version.sh b/toolchain/pre_process/version.sh similarity index 100% rename from toolchain/post_process/version.sh rename to toolchain/pre_process/version.sh From 885849586be8340777616290f72a0cf3a77392b0 Mon Sep 17 00:00:00 2001 From: Max Tyson <98maxt98@gmail.com> Date: Sat, 10 May 2025 11:42:18 +1200 Subject: [PATCH 12/38] Fix LFN reading --- filesystem/test/a.txt | 0 filesystem/test/longfilename.extension | 1 + kernel/include/common/string.h | 3 +- kernel/include/filesystem/fat32.h | 32 ++-- kernel/include/filesystem/filesystem.h | 2 + kernel/include/runtime/ubsan.h | 6 + kernel/src/common/string.cpp | 46 +++--- kernel/src/drivers/disk/ata.cpp | 2 +- kernel/src/filesystem/fat32.cpp | 205 ++++++++++++++----------- kernel/src/filesystem/filesystem.cpp | 79 +++++----- kernel/src/filesystem/vfs.cpp | 2 +- kernel/src/kernel.cpp | 10 +- kernel/src/memory/virtual.cpp | 3 +- kernel/src/processes/scheduler.cpp | 4 +- kernel/src/runtime/ubsan.cpp | 5 + 15 files changed, 224 insertions(+), 176 deletions(-) create mode 100644 filesystem/test/a.txt create mode 100644 filesystem/test/longfilename.extension diff --git a/filesystem/test/a.txt b/filesystem/test/a.txt new file mode 100644 index 00000000..e69de29b diff --git a/filesystem/test/longfilename.extension b/filesystem/test/longfilename.extension new file mode 100644 index 00000000..0371c1f1 --- /dev/null +++ b/filesystem/test/longfilename.extension @@ -0,0 +1 @@ +test data to read \ No newline at end of file diff --git a/kernel/include/common/string.h b/kernel/include/common/string.h index 940ced4d..b50af6ed 100644 --- a/kernel/include/common/string.h +++ b/kernel/include/common/string.h @@ -18,7 +18,7 @@ namespace MaxOS { class String { private: char* m_string = nullptr; - int m_length = 0; + int m_length = 0; // Does not include the null terminator [[nodiscard]] static int lex_value(String const &other) ; void allocate_self(); @@ -26,6 +26,7 @@ namespace MaxOS { public: String(); + explicit String(char c); String(char const* string); String(uint8_t const* string, int length); String(String const &other); diff --git a/kernel/include/filesystem/fat32.h b/kernel/include/filesystem/fat32.h index 0f8ff838..f50c2700 100644 --- a/kernel/include/filesystem/fat32.h +++ b/kernel/include/filesystem/fat32.h @@ -92,6 +92,24 @@ namespace MaxOS{ } __attribute__((packed)) dir_entry_t; + enum class DirectoryEntryAttributes + { + FREE = 0x00, + READ_ONLY = 0x01, + HIDDEN = 0x02, + SYSTEM = 0x04, + VOLUME_ID = 0x08, + DIRECTORY = 0x10, + ARCHIVE = 0x20, + LONG_NAME = READ_ONLY | HIDDEN | SYSTEM | VOLUME_ID, + }; + + enum class DirectoryEntryType + { + LAST = 0x00, + DELETED = 0xE5, + }; + typedef struct LongFileNameEntry { uint8_t order; @@ -128,7 +146,6 @@ namespace MaxOS{ fs_info_t fsinfo; size_t fat_total_clusters; - lba_t fat_first_sector; lba_t fat_lba; lba_t fat_info_lba; @@ -171,14 +188,6 @@ namespace MaxOS{ uint32_t first_cluster() const { return m_first_cluster; } }; - enum class DirectoryEntryType - { - LAST = 0x00, - DIRECTORY = 0x10, - FILE = 0x20, - DELETED = 0xE5, - }; - /** * @class Fat32Directory * @brief Handles the directory operations on the FAT32 filesystem @@ -194,10 +203,11 @@ namespace MaxOS{ lba_t create_entry(const string& name, bool is_directory); void remove_entry(lba_t cluster, const string& name); + void read_all_entries(); public: Fat32Directory(Fat32Volume* volume, lba_t cluster, const string& name); - ~Fat32Directory() final; + ~Fat32Directory(); static const size_t MAX_NAME_LENGTH = 255; @@ -223,7 +233,7 @@ namespace MaxOS{ public: Fat32FileSystem(drivers::disk::Disk* disk, uint32_t partition_offset); - ~Fat32FileSystem() final; + ~Fat32FileSystem(); }; } diff --git a/kernel/include/filesystem/filesystem.h b/kernel/include/filesystem/filesystem.h index 1560bbc3..7b4be92b 100644 --- a/kernel/include/filesystem/filesystem.h +++ b/kernel/include/filesystem/filesystem.h @@ -33,6 +33,8 @@ namespace MaxOS{ static string file_extension(string path); static string file_path(string path); + static string top_directory(string path); + }; /** diff --git a/kernel/include/runtime/ubsan.h b/kernel/include/runtime/ubsan.h index e41c5ef2..1e1198ee 100644 --- a/kernel/include/runtime/ubsan.h +++ b/kernel/include/runtime/ubsan.h @@ -77,6 +77,12 @@ namespace MaxOS { "cast to virtual base of", }; + + typedef struct vla_bound_not_positive_info{ + source_location_t location; + type_descriptor_t* type; + } vla_bound_not_positive_info_t; + /** * @class UBSanHandler * @brief Handles undefined behaviour sanitizer diff --git a/kernel/src/common/string.cpp b/kernel/src/common/string.cpp index abfacdfe..b4bd8b84 100644 --- a/kernel/src/common/string.cpp +++ b/kernel/src/common/string.cpp @@ -11,7 +11,20 @@ String::String() // String that only contains the null terminator allocate_self(); m_string[0] = '\0'; + m_length = 0; + +} + +String::String(char c) { + + // Create the mem m_length = 1; + allocate_self(); + + // Store the char + m_string[0] = c; + m_string[m_length] = '\0'; + } @@ -34,7 +47,6 @@ String::String(char const *string) for (int i = 0; i < 52; i++) m_string[m_length - 52 + i] = warning[i]; - m_string[m_length] = '\0'; } @@ -54,15 +66,17 @@ String::String(uint8_t const* string, int length) String::String(int value) { + //TODO + } String::String(uint64_t value) { - + //TODO } String::String(float value) { - + //TODO } @@ -410,7 +424,7 @@ String String::operator + (String const &other) const { concatenated.m_string[m_length + i] = other[i]; // Write the null terminator - concatenated.m_string[m_length] = '\0'; + concatenated.m_string[concatenated.m_length] = '\0'; // Return the concatenated string return concatenated; @@ -424,29 +438,9 @@ String String::operator + (String const &other) const { */ String &String::operator += (String const &other) { - // The concatenated string - String concatenated; - concatenated.m_length = m_length + other.length(); - concatenated.allocate_self(); - - // Copy the first string - for (int i = 0; i < m_length; i++) - concatenated.m_string[i] = m_string[i]; - - // Copy the second string - for (int i = 0; i < other.length(); i++) - concatenated.m_string[m_length + i] = other[i]; - - // Write the null terminator - concatenated.m_string[m_length] = '\0'; - - // Free the old memory - delete[] m_string; - - // Copy the concatenated string + // Add the other string to this string + String concatenated = *this + other; copy(concatenated); - - // Return the concatenated string return *this; } diff --git a/kernel/src/drivers/disk/ata.cpp b/kernel/src/drivers/disk/ata.cpp index bd574278..b3eb9017 100644 --- a/kernel/src/drivers/disk/ata.cpp +++ b/kernel/src/drivers/disk/ata.cpp @@ -112,7 +112,7 @@ void AdvancedTechnologyAttachment::read(uint32_t sector, uint8_t* data_buffer, s if(status == 0x00) return; - // Wait for the device to be ready or for an error to occur + // Wait for the device to be ready or for an error to occur TODO: Userspace block here while(((status & 0x80) == 0x80) && ((status & 0x01) != 0x01)) status = m_command_port.read(); diff --git a/kernel/src/filesystem/fat32.cpp b/kernel/src/filesystem/fat32.cpp index d94ee21a..f39dd10a 100644 --- a/kernel/src/filesystem/fat32.cpp +++ b/kernel/src/filesystem/fat32.cpp @@ -21,8 +21,7 @@ Fat32Volume::Fat32Volume(Disk* hd, uint32_t partition_offset) // Parse the FAT info fat_total_clusters = bpb.total_sectors_32 - (bpb.reserved_sectors + (bpb.table_copies * bpb.table_size_32)); - fat_first_sector = bpb.reserved_sectors; - fat_lba = partition_offset + fat_first_sector; + fat_lba = partition_offset + bpb.reserved_sectors; fat_info_lba = partition_offset + bpb.fat_info; disk -> read(fat_info_lba, (uint8_t *)&fsinfo, sizeof(fs_info_t)); @@ -52,7 +51,7 @@ lba_t Fat32Volume::next_cluster(lba_t cluster) // Get the location in the FAT table lba_t offset = cluster * sizeof(uint32_t); - lba_t sector = fat_first_sector + (offset / bpb.bytes_per_sector); + lba_t sector = fat_lba + (offset / bpb.bytes_per_sector); uint32_t entry = offset % bpb.bytes_per_sector; // Read the FAT entry @@ -76,7 +75,7 @@ uint32_t Fat32Volume::set_next_cluster(uint32_t cluster, uint32_t next_cluster) // Get the location in the FAT table lba_t offset = cluster * sizeof(uint32_t); - lba_t sector = fat_first_sector + (offset / bpb.bytes_per_sector); + lba_t sector = fat_lba + (offset / bpb.bytes_per_sector); uint32_t entry = offset % bpb.bytes_per_sector; // Read the FAT entry @@ -265,17 +264,7 @@ Fat32Directory::Fat32Directory(Fat32Volume* volume, uint32_t cluster, const stri } -Fat32Directory::~Fat32Directory() -{ - - // Free the files - for (auto & file : m_files) - delete file; - - // Free the subdirectories - for (auto & subdirectory : m_subdirectories) - delete subdirectory; -} +Fat32Directory::~Fat32Directory() = default; /** * @brief Create a new entry in the directory @@ -474,79 +463,120 @@ void Fat32Directory::remove_entry(uint32_t cluster, const string& name) } -void Fat32Directory::read_from_disk() { +/** + * @brief Read all of the directory entries for each cluster in this directory + */ +void Fat32Directory::read_all_entries() { + m_entries.clear(); - // Read the directory - for (uint32_t cluster = m_first_cluster; cluster != (uint32_t)ClusterState::END_OF_CHAIN; cluster = m_volume -> next_cluster(cluster)) - { + size_t buffer_space = m_volume->bpb.bytes_per_sector * m_volume->bpb.sectors_per_cluster; + auto buffer = new uint8_t[buffer_space]; - // Get the buffer and address to read - uint8_t buffer[m_volume -> bpb.bytes_per_sector * m_volume -> bpb.sectors_per_cluster]; - lba_t lba = m_volume -> data_lba + (cluster - 2) * m_volume -> bpb.sectors_per_cluster; + // Read the directory + for (uint32_t cluster = m_first_cluster; cluster != (uint32_t)ClusterState::END_OF_CHAIN; cluster = m_volume -> next_cluster(cluster)) { // Read each sector in the cluster - for (size_t sector = 0; sector < m_volume -> bpb.sectors_per_cluster; sector++) - m_volume -> disk -> read(lba + sector, buffer + sector * m_volume -> bpb.bytes_per_sector, m_volume -> bpb.bytes_per_sector); - - string long_name = ""; + memset(buffer, 0, buffer_space); + lba_t lba = m_volume->data_lba + (cluster - 2) * m_volume->bpb.sectors_per_cluster; + for (size_t sector = 0; sector < m_volume->bpb.sectors_per_cluster; sector++) + m_volume->disk->read(lba + sector,buffer + sector * m_volume->bpb.bytes_per_sector,m_volume->bpb.bytes_per_sector); // Parse the directory entries (each entry is 32 bytes) - for (size_t entry_offset = 0; entry_offset < sizeof(buffer); entry_offset += 32) - { + for (size_t entry_offset = 0; entry_offset < buffer_space; entry_offset += 32) { - // Get the entry + // Store the entry auto entry = (dir_entry_t*)&buffer[entry_offset]; m_entries.push_back(*entry); - // Skip free entries and volume labels - if (entry -> name[0] == (uint8_t)DirectoryEntryType::DELETED || (entry -> attributes & 0x08) == 0x08) - continue; - // Check if the entry is the end of the directory if (entry -> name[0] == (uint8_t)DirectoryEntryType::LAST) return; + } + + } + + delete[] buffer; +} + +void Fat32Directory::read_from_disk() { + + for(auto& file : m_files) + delete file; + m_files.clear(); + + for(auto& directory : m_subdirectories) + delete directory; + m_subdirectories.clear(); + + // Load the entries from the disk into memory + read_all_entries(); - // Parse the long name entry - if ((entry -> attributes & 0x0F) == 0x0F) - { - - // Extract the long name from each part (in reverse order) - auto* long_name_entry = (long_file_name_entry_t*)entry; - for (int i = 0; i < 13; i++) { - - // Get the character (in utf8 encoding) - char c; - if (i < 5) - c = long_name_entry->name1[i] & 0xFF; - else if (i < 11) - c = long_name_entry->name2[i - 5] & 0xFF; - else - c = long_name_entry->name3[i - 11] & 0xFF; - - // Check if the character is valid - if (c == '\0' || c == (char)0xFF) - break; - - // Add to the start as the entries are stored in reverse - long_name = string(c) + long_name; - } + // Parse the entries + string long_name = ""; + for(auto& entry : m_entries){ + + // Skip free entries and volume labels + if (entry.name[0] == (uint8_t)DirectoryEntryType::DELETED + || entry.attributes == (uint8_t)DirectoryEntryAttributes::FREE + || entry.attributes == (uint8_t)DirectoryEntryAttributes::VOLUME_ID) + continue; + + if (entry.attributes == (uint8_t)DirectoryEntryAttributes::LONG_NAME) + { + + // Extract the long name from each part (in reverse order) + auto long_name_entry = (long_file_name_entry_t*)&entry; + string current_long_name = ""; + for (int i = 0; i < 13; i++) { + + // Get the character (in utf8 encoding) + char c; + if (i < 5) + c = long_name_entry -> name1[i] & 0xFF; + else if (i < 11) + c = long_name_entry -> name2[i - 5] & 0xFF; + else + c = long_name_entry -> name3[i - 11] & 0xFF; + + // Padding / invalid or end of string + if (c == (char)0xFF || c == '\0') + break; + + // Add to the start as the entries are stored in reverse + current_long_name += string(c); } - // Get the name of the entry - string name = long_name == "" ? string(entry->name, 8) : long_name; - long_name = ""; + // Entry parsed (prepend name) + long_name = current_long_name + long_name; + continue; + + } - // Get the starting cluster - uint32_t start_cluster = (entry -> first_cluster_high << 16) | entry -> first_cluster_low; + bool is_directory = entry.attributes == (uint8_t)DirectoryEntryAttributes::DIRECTORY; - // Store the file or directory - if (entry -> attributes & (uint8_t)DirectoryEntryType::DIRECTORY) - m_subdirectories.push_back(new Fat32Directory(m_volume, start_cluster, name)); - else - m_files.push_back(new Fat32File(m_volume, start_cluster, entry -> size, name)); + // Get the name of the entry + string name = long_name; + if(long_name == ""){ + name = string(entry.name, 8); + if(!is_directory){ + name = name.strip(); + name += "."; + name += string(entry.extension, 3); + } } - } + long_name = ""; + + // Get the starting cluster + uint32_t start_cluster = (entry.first_cluster_high << 16) | entry.first_cluster_low; + + // Store the file or directory + if (is_directory) + m_subdirectories.push_back(new Fat32Directory(m_volume, start_cluster, name.strip())); + else + m_files.push_back(new Fat32File(m_volume, start_cluster, entry.size, name)); + + } } /** @@ -624,26 +654,26 @@ Directory* Fat32Directory::create_subdirectory(const string& name) void Fat32Directory::remove_subdirectory(const string& name) { // Find the directory if it exists - for (auto& subdirectory : m_subdirectories) - if (subdirectory -> name() == name) - { + for (auto& subdirectory : m_subdirectories) { + if (subdirectory->name() != name) + continue; - // Remove all the files in the directory - for (auto& file : subdirectory -> files()) - subdirectory -> remove_file(file -> name()); + // Remove all the files in the directory + for (auto &file : subdirectory->files()) + subdirectory->remove_file(file->name()); - // Remove all the subdirectories in the directory - for (auto& subdirectory : subdirectory -> subdirectories()) - subdirectory -> remove_subdirectory(subdirectory -> name()); + // Remove all the subdirectories in the directory + for (auto &subdirectory : subdirectory->subdirectories()) + subdirectory->remove_subdirectory(subdirectory->name()); - // Remove the entry - m_subdirectories.erase(subdirectory); - remove_entry(((Fat32Directory*)subdirectory) -> first_cluster(), name); + // Remove the entry + m_subdirectories.erase(subdirectory); + remove_entry(((Fat32Directory *)subdirectory)->first_cluster(), name); - // Delete the directory - delete subdirectory; - return; - } + // Delete the directory + delete subdirectory; + return; + } } Fat32FileSystem::Fat32FileSystem(Disk* disk, uint32_t partition_offset) @@ -656,9 +686,4 @@ Fat32FileSystem::Fat32FileSystem(Disk* disk, uint32_t partition_offset) } -Fat32FileSystem::~Fat32FileSystem(){ - - // Free the root directory - delete m_root_directory; - -} \ No newline at end of file +Fat32FileSystem::~Fat32FileSystem() = default; \ No newline at end of file diff --git a/kernel/src/filesystem/filesystem.cpp b/kernel/src/filesystem/filesystem.cpp index efd83d14..94fb0828 100644 --- a/kernel/src/filesystem/filesystem.cpp +++ b/kernel/src/filesystem/filesystem.cpp @@ -103,6 +103,29 @@ string Path::file_path(string path) } +/** + * @brief Get the top directory of a path + * + * @param path The path to get the top directory from + * @return The top directory or the original path if it does not exist + */ +string Path::top_directory(string path) { + + // Find the first / + int first_slash = -1; + for (int i = 0; i < path.length(); i++) + if (path[i] == '/') + first_slash = i; + + // Make sure there was a slash to split + if (first_slash == -1) + return path; + + // Get the top directory + string top_directory = path.substring(0, first_slash); + return top_directory; +} + File::File() = default; File::~File() = default; @@ -193,7 +216,17 @@ size_t File::size() Directory::Directory() = default; -Directory::~Directory() = default; +Directory::~Directory() { + + // Free the files + for (auto & file : m_files) + delete file; + + // Free the subdirectories + for (auto & subdirectory : m_subdirectories) + delete subdirectory; + +} /** * @brief Read the directory from the disk @@ -327,42 +360,14 @@ size_t Directory::size() } -/** - * @brief Print the contents of this directory (recursive print of sub-dirs) - * - * @param level Level of recursion - */ -void Directory::debug_print(int level){ - - // Prevent infinite recursion bugs - level++; - ASSERT(level < 1000, "Infinite recursion in tree printing of directory"); - - // Print all the files - for (auto& file : m_files) - Logger::DEBUG() << (string)"-" * level << " " << file -> name() << " (file)" << " Size: 0x" << file -> size() << "\n"; - - // Recursive call all the directories - for(auto& directory : m_subdirectories){ - - // Prevent trying to re-read this directory or the parent one - string name = directory -> name().strip(); - if(name == "." || name == "..") - continue; - - Logger::DEBUG() << string("-") * level << " " << name << " (directory) \n"; - directory -> read_from_disk(); - directory -> debug_print(level); - - } - - +FileSystem::FileSystem() = default; -} +FileSystem::~FileSystem() { -FileSystem::FileSystem() = default; + // Free the root directory + delete m_root_directory; -FileSystem::~FileSystem() = default; +}; /** * @brief Get the directory at "/" @@ -393,10 +398,10 @@ Directory* FileSystem::get_directory(const string& path) while (directory_path.length() > 0) { // Get the name of the directory - string directory_name = Path::file_name(directory_path); + string directory_name = Path::top_directory(directory_path); // Open the directory - Directory* subdirectory = directory->open_subdirectory(directory_name); + Directory* subdirectory = directory -> open_subdirectory(directory_name); if (!subdirectory) return nullptr; @@ -404,7 +409,7 @@ Directory* FileSystem::get_directory(const string& path) directory = subdirectory; // Get the path to the next directory - directory_path = Path::file_path(directory_path); + directory_path = directory_path.substring(directory_name.length() + 1, directory_path.length() - directory_name.length() - 1); } return directory; diff --git a/kernel/src/filesystem/vfs.cpp b/kernel/src/filesystem/vfs.cpp index a148013c..1e745443 100644 --- a/kernel/src/filesystem/vfs.cpp +++ b/kernel/src/filesystem/vfs.cpp @@ -228,7 +228,7 @@ string VirtualFileSystem::get_relative_path(FileSystem* filesystem, string path) return ""; // Get the relative path - string relative_path = path.substring(0, mount_point.length()); + string relative_path = path.substring(mount_point.length(), path.length() - mount_point.length()); return relative_path; } diff --git a/kernel/src/kernel.cpp b/kernel/src/kernel.cpp index bfc2e242..d403a2f1 100644 --- a/kernel/src/kernel.cpp +++ b/kernel/src/kernel.cpp @@ -71,9 +71,10 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic driver_manager.activate_drivers(); // FS Tests - Directory* root = vfs.root_directory(); - ASSERT(root != nullptr, "Root directory is null\n"); - root -> debug_print(); + Directory* boot_dir = vfs.open_directory("/boot/grub/"); + for(auto& file : boot_dir -> files()) + Logger::DEBUG() << "File: " << file -> name() << "\n"; + Logger::HEADER() << "Stage {4}: System Finalisation\n"; Scheduler scheduler(multiboot); @@ -87,10 +88,9 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic } // TODO: -// - Fix FAT32 (extenstions, strip, lfn) // - FAT32 Tests // - Fix tabs (mac mess up) -// - Userspace Files +// - Userspace Files (syscalls, proper path handling, working directories, file handles) // - Implement ext2 // - Class & Struct docstrings // - Logo on fail in center \ No newline at end of file diff --git a/kernel/src/memory/virtual.cpp b/kernel/src/memory/virtual.cpp index aafa2cfd..75e03b27 100644 --- a/kernel/src/memory/virtual.cpp +++ b/kernel/src/memory/virtual.cpp @@ -46,8 +46,7 @@ VirtualMemoryManager::VirtualMemoryManager() m_pml4_root_physical_address = (uint64_t*)PhysicalMemoryManager::to_lower_region((uint64_t)m_pml4_root_address); } - // Log the VMM's PML4 address - Logger::DEBUG() << "VMM PML4: physical - 0x" << (uint64_t)m_pml4_root_physical_address << ", virtual - 0x" << (uint64_t)m_pml4_root_address << "\n"; + // Log the VMM's PML4 addressLogger::DEBUG() << "VMM PML4: physical - 0x" << (uint64_t)m_pml4_root_physical_address << ", virtual - 0x" << (uint64_t)m_pml4_root_address << "\n"; // Allocate space for the vmm uint64_t vmm_space = PhysicalMemoryManager::align_to_page(PhysicalMemoryManager::s_hh_direct_map_offset + PhysicalMemoryManager::s_current_manager->memory_size() + PhysicalMemoryManager::s_page_size); //TODO: Check that am not slowly overwriting the kernel (filling first space with 0s bugs out the kernel) diff --git a/kernel/src/processes/scheduler.cpp b/kernel/src/processes/scheduler.cpp index 80d139bb..e98e7992 100644 --- a/kernel/src/processes/scheduler.cpp +++ b/kernel/src/processes/scheduler.cpp @@ -102,7 +102,7 @@ cpu_status_t* Scheduler::schedule_next(cpu_status_t* cpu_state) { current_thread->execution_state = cpu_state; current_thread -> save_sse_state(); if(current_thread->thread_state == ThreadState::RUNNING) - current_thread->thread_state = ThreadState::WAITING; + current_thread->thread_state = ThreadState::READY; // Switch to the thread that will now run m_current_thread_index++; @@ -227,7 +227,7 @@ cpu_status_t* Scheduler::yield() { // Set the current thread to waiting if running if (m_threads[m_current_thread_index]->thread_state == ThreadState::RUNNING) - m_threads[m_current_thread_index]->thread_state = ThreadState::WAITING; + m_threads[m_current_thread_index]->thread_state = ThreadState::READY; // Schedule the next thread diff --git a/kernel/src/runtime/ubsan.cpp b/kernel/src/runtime/ubsan.cpp index a510aa5c..7f8c67f4 100644 --- a/kernel/src/runtime/ubsan.cpp +++ b/kernel/src/runtime/ubsan.cpp @@ -136,4 +136,9 @@ extern "C" void __ubsan_handle_load_invalid_value(invalid_value_info_t* info) { extern "C" void __ubsan_handle_missing_return(location_only_info_t* info) { Logger::DEBUG() << "UBSan: Missing return\n"; UBSanHandler::handle(info -> location); +} + +extern "C" void __ubsan_handle_vla_bound_not_positive(vla_bound_not_positive_info_t* info) { + Logger::DEBUG() << "UBSan: VLA bound not positive\n"; + UBSanHandler::handle(info -> location); } \ No newline at end of file From 86aec6f63ae4d5d78f6a438b3a2810a21d6bfdd2 Mon Sep 17 00:00:00 2001 From: Max Tyson <98maxt98@gmail.com> Date: Sun, 11 May 2025 17:01:06 +1200 Subject: [PATCH 13/38] File read/writes --- filesystem/test/a.txt | 1 + kernel/include/filesystem/fat32.h | 20 +- kernel/include/filesystem/filesystem.h | 12 +- kernel/src/filesystem/fat32.cpp | 258 ++++++++++++++++++++----- kernel/src/filesystem/filesystem.cpp | 49 ++++- kernel/src/kernel.cpp | 38 +++- 6 files changed, 324 insertions(+), 54 deletions(-) diff --git a/filesystem/test/a.txt b/filesystem/test/a.txt index e69de29b..694cfb7e 100644 --- a/filesystem/test/a.txt +++ b/filesystem/test/a.txt @@ -0,0 +1 @@ +slkjnsdfhskldflksfjklsdjfklsjfksdjfkdjs \ No newline at end of file diff --git a/kernel/include/filesystem/fat32.h b/kernel/include/filesystem/fat32.h index f50c2700..e148b8cc 100644 --- a/kernel/include/filesystem/fat32.h +++ b/kernel/include/filesystem/fat32.h @@ -165,6 +165,9 @@ namespace MaxOS{ void free_cluster(uint32_t cluster, size_t amount); }; + // Forward def + class Fat32Directory; + /** * @class Fat32File * @brief Handles the file operations on the FAT32 filesystem @@ -174,15 +177,17 @@ namespace MaxOS{ private: Fat32Volume* m_volume; + Fat32Directory* m_parent_directory; + dir_entry_t* m_entry; uint32_t m_first_cluster; public: - Fat32File(Fat32Volume* volume, uint32_t cluster, size_t size, const string& name); + Fat32File(Fat32Volume* volume, Fat32Directory* parent, dir_entry_t* info, const string& name); ~Fat32File() final; - void write(const uint8_t* data, size_t size) final; - void read(uint8_t* data, size_t size) final; + void write(const uint8_t* data, size_t amount) final; + void read(uint8_t* data, size_t amount) final; void flush() final; uint32_t first_cluster() const { return m_first_cluster; } @@ -194,6 +199,7 @@ namespace MaxOS{ */ class Fat32Directory : public Directory { + friend class Fat32File; private: Fat32Volume* m_volume; @@ -201,10 +207,16 @@ namespace MaxOS{ common::Vector m_entries; - lba_t create_entry(const string& name, bool is_directory); + dir_entry_t* create_entry(const string& name, bool is_directory); void remove_entry(lba_t cluster, const string& name); void read_all_entries(); + int entry_index(lba_t cluster); + + protected: + + void save_entry_to_disk(dir_entry_t* entry); + public: Fat32Directory(Fat32Volume* volume, lba_t cluster, const string& name); ~Fat32Directory(); diff --git a/kernel/include/filesystem/filesystem.h b/kernel/include/filesystem/filesystem.h index 7b4be92b..b99a6bba 100644 --- a/kernel/include/filesystem/filesystem.h +++ b/kernel/include/filesystem/filesystem.h @@ -83,12 +83,20 @@ namespace MaxOS{ virtual void read_from_disk(); common::Vector files(); + common::Vector subdirectories(); + File* open_file(const string& name); + Directory* open_subdirectory(const string& name); + virtual File* create_file(const string& name); virtual void remove_file(const string& name); - common::Vector subdirectories(); - Directory* open_subdirectory(const string& name); + void rename_file(File* file, const string& new_name); + virtual void rename_file(const string& old_name, const string& new_name); + + void rename_subdirectory(Directory* directory, const string& new_name); + virtual void rename_subdirectory(const string& old_name, const string& new_name); + virtual Directory* create_subdirectory(const string& name); virtual void remove_subdirectory(const string& name); diff --git a/kernel/src/filesystem/fat32.cpp b/kernel/src/filesystem/fat32.cpp index f39dd10a..aebb92b9 100644 --- a/kernel/src/filesystem/fat32.cpp +++ b/kernel/src/filesystem/fat32.cpp @@ -125,7 +125,7 @@ uint32_t Fat32Volume::allocate_cluster(uint32_t cluster) } /** - * @brief Allocate a number of clusters in the FAT table + * @brief Allocate a number of clusters in the FAT table, updates the fsinfo and the chain * * @param cluster The base cluster to start from or 0 if this is a new chain * @param amount The number of clusters to allocate @@ -213,38 +213,166 @@ void Fat32Volume::free_cluster(uint32_t cluster, size_t amount) } -Fat32File::Fat32File(Fat32Volume* volume, uint32_t cluster, size_t size, const string& name) +Fat32File::Fat32File(Fat32Volume* volume, Fat32Directory* parent, dir_entry_t* info, const string& name) : m_volume(volume), - m_first_cluster(cluster) + m_parent_directory(parent), + m_entry(info), + m_first_cluster((info -> first_cluster_high << 16) | info -> first_cluster_low) { m_name = name; - m_size = size; + m_size = info -> size; m_offset = 0; } Fat32File::~Fat32File() = default; /** - * @brief Write data to the file + * @brief Write data to the file (at the current seek position, updated to be += amount) * * @param data The byte buffer to write - * @param size The amount of data to write + * @param amount The amount of data to write */ -void Fat32File::write(const uint8_t* data, size_t size) +void Fat32File::write(const uint8_t* data, size_t amount) { - File::write(data, size); + + size_t buffer_space = m_volume->bpb.bytes_per_sector * m_volume->bpb.sectors_per_cluster; + auto buffer = new uint8_t[buffer_space]; + + uint64_t current_offset = 0; + uint64_t bytes_written = 0; + uint32_t last = m_first_cluster; + + // Read the file + for (uint32_t cluster = last; cluster != (uint32_t)ClusterState::END_OF_CHAIN; cluster = m_volume -> next_cluster(cluster)) { + last = cluster; + + // No cluster to read from (blank file) + if (cluster == 0) + break; + + // Skip clusters before the offset + if((current_offset + buffer_space) < m_offset){ + current_offset += buffer_space; + continue; + } + + // Read each sector in the cluster (prevent overwriting the data) + memset(buffer, 0, buffer_space); + lba_t lba = m_volume->data_lba + (cluster - 2) * m_volume->bpb.sectors_per_cluster; + for (size_t sector = 0; sector < m_volume->bpb.sectors_per_cluster; sector++) + m_volume->disk->read(lba + sector,buffer + sector * m_volume->bpb.bytes_per_sector,m_volume->bpb.bytes_per_sector); + + // If the offset is in the middle of the cluster + size_t buffer_offset = 0; + if(m_offset > current_offset) + buffer_offset = m_offset - current_offset; + + // Calculate how many bytes are being copied (read from cluster at offset? + // or read part of cluster?) + size_t cluster_remaining_bytes = buffer_space - buffer_offset; + size_t data_remaining_bytes = amount - bytes_written ; + size_t bytes_to_copy = (cluster_remaining_bytes < data_remaining_bytes) ? cluster_remaining_bytes : data_remaining_bytes; + bytes_to_copy = (bytes_to_copy > buffer_space) ? buffer_space : bytes_to_copy; + + // Update the data + memcpy(buffer + buffer_offset, data + bytes_written , bytes_to_copy); + bytes_written += bytes_to_copy; + current_offset += bytes_to_copy; + + // Write the data back to the disk + for (size_t sector = 0; sector < m_volume->bpb.sectors_per_cluster; sector++) + m_volume->disk->write(lba + sector,buffer + sector * m_volume->bpb.bytes_per_sector,m_volume->bpb.bytes_per_sector); + } + + // Extend the file + while(bytes_written < amount) + { + // Allocate a new cluster + uint32_t new_cluster = m_volume -> allocate_cluster(last); + if (new_cluster == 0) + break; + + if(last == 0) + m_first_cluster = new_cluster; + + // Update the data + size_t bytes_to_copy = (amount - bytes_written) > buffer_space ? buffer_space : (amount - bytes_written); + memcpy(buffer, data + bytes_written , bytes_to_copy); + bytes_written += bytes_to_copy; + current_offset += bytes_to_copy; + + // Write the data back to the disk + lba_t lba = m_volume->data_lba + (new_cluster - 2) * m_volume->bpb.sectors_per_cluster; + for (size_t sector = 0; sector < m_volume->bpb.sectors_per_cluster; sector++) + m_volume->disk->write(lba + sector,buffer + sector * m_volume->bpb.bytes_per_sector,m_volume->bpb.bytes_per_sector); + + } + + m_offset += bytes_written; + + // Update the file size on disk + if (m_offset > m_size){ + m_size = m_offset; + m_entry -> size = m_size; + m_entry -> first_cluster_high = (m_first_cluster >> 16) & 0xFFFF; + m_entry -> first_cluster_low = m_first_cluster & 0xFFFF; + m_parent_directory -> save_entry_to_disk(m_entry); + + } + + delete[] buffer; } /** - * @brief Read data from the file + * @brief Read data from the file (at the current seek position, updated to be += amount) * * @param data The byte buffer to read into - * @param size The amount of data to read + * @param amount The amount of data to read */ -void Fat32File::read(uint8_t* data, size_t size) +void Fat32File::read(uint8_t* data, size_t amount) { - File::read(data, size); + size_t buffer_space = m_volume->bpb.bytes_per_sector * m_volume->bpb.sectors_per_cluster; + auto buffer = new uint8_t[buffer_space]; + + uint64_t current_offset = 0; + uint64_t bytes_read = 0; + + // Read the file + for (uint32_t cluster = m_first_cluster; cluster != (uint32_t)ClusterState::END_OF_CHAIN; cluster = m_volume -> next_cluster(cluster)) { + + // Skip clusters before the offset + if((current_offset + buffer_space) < m_offset){ + current_offset += buffer_space; + continue; + } + + // Read each sector in the cluster + memset(buffer, 0, buffer_space); + lba_t lba = m_volume->data_lba + (cluster - 2) * m_volume->bpb.sectors_per_cluster; + for (size_t sector = 0; sector < m_volume->bpb.sectors_per_cluster; sector++) + m_volume->disk->read(lba + sector,buffer + sector * m_volume->bpb.bytes_per_sector,m_volume->bpb.bytes_per_sector); + + // If the offset is in the middle of the cluster + size_t buffer_offset = 0; + if(m_offset > current_offset) + buffer_offset = m_offset - current_offset; + + // Calculate how many bytes are being copied (read from cluster at offset? + // or read part of cluster?) + size_t cluster_remaining_bytes = buffer_space - buffer_offset; + size_t data_remaining_bytes = amount - bytes_read; + size_t bytes_to_copy = (cluster_remaining_bytes < data_remaining_bytes) ? cluster_remaining_bytes : data_remaining_bytes; + bytes_to_copy = (bytes_to_copy > buffer_space) ? buffer_space : bytes_to_copy; + + // Read the data + memcpy(data + bytes_read, buffer + buffer_offset, bytes_to_copy); + bytes_read += bytes_to_copy; + current_offset += buffer_space; + } + + m_offset += bytes_read; + delete[] buffer; } /** @@ -273,10 +401,11 @@ Fat32Directory::~Fat32Directory() = default; * @param is_directory True if the entry is a directory, false if it is a file * @return The cluster of the new entry */ -lba_t Fat32Directory::create_entry(const string& name, bool is_directory) +dir_entry_t* Fat32Directory::create_entry(const string& name, bool is_directory) { - //TODO: Write to all the FAT copies + // TODO: Write to all the FAT copies + // TODO: Grow the cluster chain if needed // Allocate a cluster for the new entry uint32_t cluster = m_volume -> allocate_cluster(0); @@ -377,7 +506,7 @@ lba_t Fat32Directory::create_entry(const string& name, bool is_directory) // Creating the file is done if (!is_directory) - return cluster; + return &m_entries[entry_index]; // Create the "." in the directory dir_entry_t current_dir_entry; @@ -399,7 +528,7 @@ lba_t Fat32Directory::create_entry(const string& name, bool is_directory) m_volume -> disk -> write(child_lba + sizeof(dir_entry_t), (uint8_t *)&parent_dir_entry, sizeof(dir_entry_t)); // Directory created - return cluster; + return &m_entries[entry_index]; } /** @@ -412,41 +541,22 @@ void Fat32Directory::remove_entry(uint32_t cluster, const string& name) { // Find the entry in the directory - size_t entry_index = 0; - for (; entry_index < m_entries.size(); entry_index++) - { - auto& entry = m_entries[entry_index]; - - // End of directory means no more entries - if (entry.name[0] == (uint8_t)DirectoryEntryType::LAST) - return; - - // Skip deleted entries - if (entry.name[0] == (uint8_t)DirectoryEntryType::DELETED) - continue; - - // Check if the entry is the one to remove - uint32_t start_cluster = (entry.first_cluster_high << 16) | entry.first_cluster_low; - if (start_cluster == cluster) - break; - } - - // Make sure the entry is valid - if (entry_index >= m_entries.size()) + int entry = entry_index(cluster); + if(entry == -1) return; // Find any long file name entries that belong to this entry - size_t delete_entry_index = entry_index; + size_t delete_entry_index = entry; while (delete_entry_index > 0 && (m_entries[delete_entry_index - 1].attributes & 0x0F) == 0x0F) delete_entry_index--; // Mark the entries as deleted - for (size_t i = delete_entry_index; i < entry_index; i++) + for (size_t i = delete_entry_index; i < entry; i++) m_entries[i].name[0] = (uint8_t)DirectoryEntryType::DELETED; // Update the entries on the disk lba_t first_directory_entry_lba = m_volume -> data_lba + (m_first_cluster - 2) * m_volume -> bpb.sectors_per_cluster; - for (size_t i = delete_entry_index; i <= entry_index; i++) + for (size_t i = delete_entry_index; i <= entry; i++) { uint32_t offset = (i * sizeof(dir_entry_t)) / m_volume -> bpb.bytes_per_sector; m_volume -> disk -> write(first_directory_entry_lba + offset, (uint8_t *)&m_entries[i], sizeof(dir_entry_t)); @@ -498,6 +608,63 @@ void Fat32Directory::read_all_entries() { delete[] buffer; } + +/** + * @brief Writes an updated directory entry to the disk + * + * @param entry The entry to write + */ +void Fat32Directory::save_entry_to_disk(DirectoryEntry* entry) { + + int index = 0; + for (auto & m_entry : m_entries){ + index++; + if (&m_entry == entry) + break; + } + + // Write the entry to the disk + lba_t lba = m_volume -> data_lba + (m_first_cluster - 2) * m_volume -> bpb.sectors_per_cluster; + uint32_t offset = (index * sizeof(dir_entry_t)) / m_volume -> bpb.bytes_per_sector; + m_volume -> disk -> write(lba + offset, (uint8_t *)entry, sizeof(dir_entry_t)); + +} + +/** + * @brief Find cluster's directory entry index in the store entries + * + * @param cluster The cluster + * @return The index or -1 if not found + */ +int Fat32Directory::entry_index(lba_t cluster) { + + int entry_index = 0; + for (; entry_index < m_entries.size(); entry_index++) + { + auto& entry = m_entries[entry_index]; + + // End of directory means no more entries + if (entry.name[0] == (uint8_t)DirectoryEntryType::LAST) + return -1; + + // Skip deleted entries + if (entry.name[0] == (uint8_t)DirectoryEntryType::DELETED) + continue; + + // Check if the entry is the one + uint32_t start_cluster = (entry.first_cluster_high << 16) | entry.first_cluster_low; + if (start_cluster == cluster) + break; + } + + // Make sure the entry is valid + if (entry_index >= m_entries.size()) + return -1; + + return entry_index; + +} + void Fat32Directory::read_from_disk() { for(auto& file : m_files) @@ -574,7 +741,7 @@ void Fat32Directory::read_from_disk() { if (is_directory) m_subdirectories.push_back(new Fat32Directory(m_volume, start_cluster, name.strip())); else - m_files.push_back(new Fat32File(m_volume, start_cluster, entry.size, name)); + m_files.push_back(new Fat32File(m_volume, this, &entry, name)); } } @@ -598,7 +765,7 @@ File* Fat32Directory::create_file(const string& name) return nullptr; // Create the file - auto file = new Fat32File(m_volume, create_entry(name, false), 0, name); + auto file = new Fat32File(m_volume, this, create_entry(name, false), name); m_files.push_back(file); return file; } @@ -640,7 +807,11 @@ Directory* Fat32Directory::create_subdirectory(const string& name) return nullptr; // Create the directory - auto directory = new Fat32Directory(m_volume, create_entry(name, true), name); + auto entry = create_entry(name, true); + uint32_t cluster = ((entry -> first_cluster_high << 16) | entry -> first_cluster_low); + + // Store the directory + auto directory = new Fat32Directory(m_volume, cluster, name); m_subdirectories.push_back(directory); return directory; @@ -676,6 +847,7 @@ void Fat32Directory::remove_subdirectory(const string& name) } } + Fat32FileSystem::Fat32FileSystem(Disk* disk, uint32_t partition_offset) : m_volume(disk, partition_offset) { diff --git a/kernel/src/filesystem/filesystem.cpp b/kernel/src/filesystem/filesystem.cpp index 94fb0828..7fe07525 100644 --- a/kernel/src/filesystem/filesystem.cpp +++ b/kernel/src/filesystem/filesystem.cpp @@ -136,7 +136,7 @@ File::~File() = default; * @param data The byte buffer to write * @param size The amount of data to write */ -void File::write(const uint8_t* data, size_t size) +void File::write(const uint8_t* data, size_t amount) { } @@ -146,7 +146,7 @@ void File::write(const uint8_t* data, size_t size) * @param data The byte buffer to read into * @param size The amount of data to read */ -void File::read(uint8_t* data, size_t size) +void File::read(uint8_t* data, size_t amount) { } @@ -359,6 +359,51 @@ size_t Directory::size() return size; } +/** + * @brief Rename a file in the directory + * + * @param file The file to rename + * @param new_name The new name of the file + */ +void Directory::rename_file(File *file, string const &new_name) { + + rename_file(file -> name(), new_name); + +} + +/** + * @brief Rename a file in the directory + * + * @param old_name The file to rename + * @param new_name The new name of the file + */ +void Directory::rename_file(string const &old_name, string const &new_name) { + + ASSERT(false, "not implemented"); + +} + +/** + * @brief Rename a subdirectory in the directory + * + * @param directory The directory to rename + * @param new_name The new name of the directory + */ +void Directory::rename_subdirectory(Directory *directory, string const &new_name) { + + rename_subdirectory(directory -> name(), new_name); + +} + +/** + * @brief Rename a subdirectory in the directory + * + * @param old_name The directory to rename + * @param new_name The new name of the directory + */ +void Directory::rename_subdirectory(string const &old_name, string const &new_name) { + ASSERT(false, "not implemented"); +} FileSystem::FileSystem() = default; diff --git a/kernel/src/kernel.cpp b/kernel/src/kernel.cpp index d403a2f1..0baee83e 100644 --- a/kernel/src/kernel.cpp +++ b/kernel/src/kernel.cpp @@ -71,9 +71,16 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic driver_manager.activate_drivers(); // FS Tests - Directory* boot_dir = vfs.open_directory("/boot/grub/"); - for(auto& file : boot_dir -> files()) - Logger::DEBUG() << "File: " << file -> name() << "\n"; + File* grub_cfg = vfs.open_file("/test/a.txt"); + Logger::DEBUG() << "Opened file: " << grub_cfg->name() << "\n"; + + string test_data = "Hello World!"; + grub_cfg -> write((uint8_t*)test_data.c_str(), test_data.length()); + + grub_cfg ->seek(SeekType::SET, 0); + uint8_t buffer[100]; + grub_cfg ->read(buffer, 100); + Logger::DEBUG() << (char*)buffer << "\n"; Logger::HEADER() << "Stage {4}: System Finalisation\n"; @@ -88,7 +95,32 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic } // TODO: +// - Read from files +// - Timing storage // - FAT32 Tests +// - [x] Read subdirectories contents +// - [x] Read long path subdirectories contents +// - [ ] Create subdirectories +// - [ ] Create long path subdirectories +// - [ ] Delete subdirectories +// - [ ] Delete long path subdirectories +// - [ ] Rename directory +// - [ ] Rename file +// - [x] Read files +// - [ ] Read large files +// - [ ] Write files +// - [ ] Write large files +// - [ ] Create files +// - [ ] Delete files +// - [x] Read long path files +// - [ ] Create long path files +// - [ ] Delete long path files +// - [ ] Create files on a different mount point +// - [ ] Delete files on a different mount point +// - [ ] Read directories on a different mount point +// - [ ] Create directories on a different mount point +// - [ ] Stress test the filesystem: 1000s of files in a directory, long nested directories, long path files, etc + // - Fix tabs (mac mess up) // - Userspace Files (syscalls, proper path handling, working directories, file handles) // - Implement ext2 From cad14c23ec45890453ae1e43907dde9687fb5602 Mon Sep 17 00:00:00 2001 From: Max Tyson <98maxt98@gmail.com> Date: Thu, 22 May 2025 22:34:37 +1200 Subject: [PATCH 14/38] Fix dirnt updates & chain growing for files --- filesystem/boot/grub/grub.cfg | 0 filesystem/os/.gitkeep | 0 filesystem/test/a.txt | 2 +- filesystem/test/longfilename.extension | 0 filesystem/user/.gitkeep | 0 kernel/include/filesystem/fat32.h | 4 +- .../asm/{interruptstubs.s => interrupts.s} | 0 kernel/src/asm/loader.s | 24 ++-- kernel/src/asm/multiboot_header.s | 24 ++-- kernel/src/common/string.cpp | 1 - kernel/src/drivers/disk/ata.cpp | 28 +++- kernel/src/filesystem/fat32.cpp | 130 ++++++++++-------- kernel/src/kernel.cpp | 6 +- toolchain/copy_filesystem.sh | 44 +++++- toolchain/run_qemu.sh | 5 +- 15 files changed, 172 insertions(+), 96 deletions(-) mode change 100644 => 100755 filesystem/boot/grub/grub.cfg mode change 100644 => 100755 filesystem/os/.gitkeep mode change 100644 => 100755 filesystem/test/a.txt mode change 100644 => 100755 filesystem/test/longfilename.extension mode change 100644 => 100755 filesystem/user/.gitkeep rename kernel/src/asm/{interruptstubs.s => interrupts.s} (100%) diff --git a/filesystem/boot/grub/grub.cfg b/filesystem/boot/grub/grub.cfg old mode 100644 new mode 100755 diff --git a/filesystem/os/.gitkeep b/filesystem/os/.gitkeep old mode 100644 new mode 100755 diff --git a/filesystem/test/a.txt b/filesystem/test/a.txt old mode 100644 new mode 100755 index 694cfb7e..c57eff55 --- a/filesystem/test/a.txt +++ b/filesystem/test/a.txt @@ -1 +1 @@ -slkjnsdfhskldflksfjklsdjfklsjfksdjfkdjs \ No newline at end of file +Hello World! \ No newline at end of file diff --git a/filesystem/test/longfilename.extension b/filesystem/test/longfilename.extension old mode 100644 new mode 100755 diff --git a/filesystem/user/.gitkeep b/filesystem/user/.gitkeep old mode 100644 new mode 100755 diff --git a/kernel/include/filesystem/fat32.h b/kernel/include/filesystem/fat32.h index e148b8cc..9416794a 100644 --- a/kernel/include/filesystem/fat32.h +++ b/kernel/include/filesystem/fat32.h @@ -212,10 +212,12 @@ namespace MaxOS{ void read_all_entries(); int entry_index(lba_t cluster); + int find_free_entries(size_t amount); protected: void save_entry_to_disk(dir_entry_t* entry); + void update_entry_on_disk(int index); public: Fat32Directory(Fat32Volume* volume, lba_t cluster, const string& name); @@ -231,7 +233,7 @@ namespace MaxOS{ Directory* create_subdirectory(const string& name) final; void remove_subdirectory(const string& name) final; - lba_t first_cluster() const { return m_first_cluster; } + [[nodiscard]] lba_t first_cluster() const { return m_first_cluster; } }; /** diff --git a/kernel/src/asm/interruptstubs.s b/kernel/src/asm/interrupts.s similarity index 100% rename from kernel/src/asm/interruptstubs.s rename to kernel/src/asm/interrupts.s diff --git a/kernel/src/asm/loader.s b/kernel/src/asm/loader.s index 4fd42a69..b55588fc 100644 --- a/kernel/src/asm/loader.s +++ b/kernel/src/asm/loader.s @@ -27,17 +27,7 @@ start: ; Setup lower half of the stack mov esp, stack.top - KERNEL_VIRTUAL_ADDR - ; Map the kernel into the higher half - mov eax, p3_table_hh - KERNEL_VIRTUAL_ADDR - or eax, FLAGS - mov dword [(p4_table - KERNEL_VIRTUAL_ADDR) + 511 * 8], eax - - ; Map the kernel into the higher half (second level) - mov eax, p2_table - KERNEL_VIRTUAL_ADDR - or eax, FLAGS - mov dword[(p3_table_hh - KERNEL_VIRTUAL_ADDR) + 510 * 8], eax - - ; Map the pml4 into itself + ; Identity map the p4 table mov eax, p4_table - KERNEL_VIRTUAL_ADDR or eax, FLAGS mov dword [(p4_table - KERNEL_VIRTUAL_ADDR) + 510 * 8], eax @@ -52,6 +42,16 @@ start: or eax, FLAGS mov dword [(p3_table - KERNEL_VIRTUAL_ADDR) + 0], eax + ; Map the kernel into the higher half + mov eax, p3_table_hh - KERNEL_VIRTUAL_ADDR + or eax, FLAGS + mov dword [(p4_table - KERNEL_VIRTUAL_ADDR) + 511 * 8], eax + + ; Map the kernel into the higher half (second level) + mov eax, p2_table - KERNEL_VIRTUAL_ADDR + or eax, FLAGS + mov dword[(p3_table_hh - KERNEL_VIRTUAL_ADDR) + 510 * 8], eax + ; Map 8MB of kernel memory (2 page directories) mov ebx, 0 mov eax, p1_tables - KERNEL_VIRTUAL_ADDR @@ -67,7 +67,7 @@ start: cmp ebx, PD_LOOP_LIMIT jne .map_pd_table - ; Fill the page directory with the page tables + ; Fill the page directory with the kernel page tables mov ecx, 0 .map_p2_table: diff --git a/kernel/src/asm/multiboot_header.s b/kernel/src/asm/multiboot_header.s index b35c64ea..3bd4441c 100644 --- a/kernel/src/asm/multiboot_header.s +++ b/kernel/src/asm/multiboot_header.s @@ -1,25 +1,23 @@ section .multiboot_header header_start: align 8 - dd 0xe85250d6 ;magic_number - dd 0 ;Protected mode - dd header_end - header_start ;Header length + dd 0xe85250d6 ; Magic number + dd 0 ; Protected mode + dd header_end - header_start ; Header length - ;compute checksum + ; Checksum dd 0x100000000 - (0xe85250d6 + 0 + (header_end - header_start)) framebuffer_tag_start: - dw 0x05 ;Type: framebuffer - dw 0x01 ;Optional tag - dd framebuffer_tag_end - framebuffer_tag_start ;size - dd 0 ;Width - if 0 we let the bootloader decide - dd 0 ;Height - same as above - dd 0 ;Depth - same as above + dw 0x05 ; Tag: Framebuffer + dw 0x01 ; Tag is optional + dd framebuffer_tag_end - framebuffer_tag_start ; Size + dd 0 ; Width - let GRUB pick + dd 0 ; Height - let GRUB pick + dd 0 ; Depth - let GRUB pick framebuffer_tag_end: - ;here ends the required part of the multiboot header - ;The following is the end tag, must be always present - ;end tag + ; End Of multiboot tag align 8 dw 0 ;type dw 0 ;flags diff --git a/kernel/src/common/string.cpp b/kernel/src/common/string.cpp index b4bd8b84..88bba2f0 100644 --- a/kernel/src/common/string.cpp +++ b/kernel/src/common/string.cpp @@ -282,7 +282,6 @@ int String::length(bool count_ansi) const { if (count_ansi) return m_length; - // Calculate the length of the string without ansi characters int total_length = 0; int clean_length = 0; diff --git a/kernel/src/drivers/disk/ata.cpp b/kernel/src/drivers/disk/ata.cpp index b3eb9017..616d95b1 100644 --- a/kernel/src/drivers/disk/ata.cpp +++ b/kernel/src/drivers/disk/ata.cpp @@ -94,8 +94,13 @@ void AdvancedTechnologyAttachment::read(uint32_t sector, uint8_t* data_buffer, s if(sector & 0xF0000000 || amount > m_bytes_per_sector) return; - // Select the device (master or slave) and reset it + // Select the device (master or slave) m_device_port.write((m_is_master ? 0xE0 : 0xF0) | ((sector & 0x0F000000) >> 24)); + + // Device is busy (TODO: YIELD) + while((m_command_port.read() & 0x80) != 0); + + // Reset the device m_error_port.write(0); m_sector_count_port.write(1); @@ -150,8 +155,13 @@ void AdvancedTechnologyAttachment::write(uint32_t sector, const uint8_t* data, s if(sector > 0x0FFFFFFF || count > m_bytes_per_sector) return; - // Select the device (master or slave) and reset it + // Select the device (master or slave) m_device_port.write(m_is_master ? 0xE0 : 0xF0 | ((sector & 0x0F000000) >> 24)); + + // Device is busy (TODO: YIELD) + while((m_command_port.read() & 0x80) != 0); + + // Reset the device m_error_port.write(0); m_sector_count_port.write(1); @@ -163,7 +173,12 @@ void AdvancedTechnologyAttachment::write(uint32_t sector, const uint8_t* data, s // Send the write command m_command_port.write(0x30); - // write the data to the device + // Wait for the device be ready writing (TODO: YIELD) + uint8_t status = m_command_port.read(); + while ((status & 0x80) != 0 || (status & 0x08) == 0) + status = m_command_port.read(); + + // Write the data to the device for (uint16_t i = 0; i < m_bytes_per_sector; i+= 2) { uint16_t writeData = data[i]; @@ -178,6 +193,13 @@ void AdvancedTechnologyAttachment::write(uint32_t sector, const uint8_t* data, s // Write the remaining bytes as a full sector has to be written for(int i = count + (count%2); i < m_bytes_per_sector; i += 2) m_data_port.write(0x0000); + + // Wait for the device to finish writing (TODO: YIELD) + status = m_command_port.read(); + while ((status & 0x80) != 0 || (status & 0x08) != 0) + status = m_command_port.read(); + + flush(); } /** * @brief Flush the cache of the ATA device diff --git a/kernel/src/filesystem/fat32.cpp b/kernel/src/filesystem/fat32.cpp index aebb92b9..f157b00d 100644 --- a/kernel/src/filesystem/fat32.cpp +++ b/kernel/src/filesystem/fat32.cpp @@ -20,7 +20,8 @@ Fat32Volume::Fat32Volume(Disk* hd, uint32_t partition_offset) disk -> read(partition_offset, (uint8_t *)&bpb, sizeof(bpb32_t)); // Parse the FAT info - fat_total_clusters = bpb.total_sectors_32 - (bpb.reserved_sectors + (bpb.table_copies * bpb.table_size_32)); + uint32_t total_data_sectors = bpb.total_sectors_32 - (bpb.reserved_sectors + (bpb.table_copies * bpb.table_size_32)); + fat_total_clusters = total_data_sectors / bpb.sectors_per_cluster; fat_lba = partition_offset + bpb.reserved_sectors; fat_info_lba = partition_offset + bpb.fat_info; disk -> read(fat_info_lba, (uint8_t *)&fsinfo, sizeof(fs_info_t)); @@ -103,7 +104,7 @@ uint32_t Fat32Volume::find_free_cluster() return start; // Check any clusters before the first free cluster - for (uint32_t start = 0; start < fsinfo.next_free_cluster; start++) + for (uint32_t start = 2; start < fsinfo.next_free_cluster; start++) if (next_cluster(start) == 0) return start; @@ -307,19 +308,22 @@ void Fat32File::write(const uint8_t* data, size_t amount) for (size_t sector = 0; sector < m_volume->bpb.sectors_per_cluster; sector++) m_volume->disk->write(lba + sector,buffer + sector * m_volume->bpb.bytes_per_sector,m_volume->bpb.bytes_per_sector); + // Go to the next cluster + last = new_cluster; + } + // Update file size m_offset += bytes_written; - - // Update the file size on disk - if (m_offset > m_size){ + if (m_offset > m_size) m_size = m_offset; - m_entry -> size = m_size; - m_entry -> first_cluster_high = (m_first_cluster >> 16) & 0xFFFF; - m_entry -> first_cluster_low = m_first_cluster & 0xFFFF; - m_parent_directory -> save_entry_to_disk(m_entry); - } + // Update entry info + m_entry -> size = m_size; + m_entry -> first_cluster_high = (m_first_cluster >> 16) & 0xFFFF; + m_entry -> first_cluster_low = m_first_cluster & 0xFFFF; + // TODO: When implemented as a usermode driver save the time + m_parent_directory -> save_entry_to_disk(m_entry); delete[] buffer; } @@ -369,6 +373,10 @@ void Fat32File::read(uint8_t* data, size_t amount) memcpy(data + bytes_read, buffer + buffer_offset, bytes_to_copy); bytes_read += bytes_to_copy; current_offset += buffer_space; + + // Dont read more than needed + if(bytes_read >= amount) + break; } m_offset += bytes_read; @@ -467,42 +475,15 @@ dir_entry_t* Fat32Directory::create_entry(const string& name, bool is_directory) // Find free space for the new entry and the name size_t entries_needed = 1 + lfn_entries.size(); - size_t entry_index = 0; - for (; entry_index < m_entries.size(); entry_index++) - { - // Check if there are enough free entries in a row - bool found = true; - for (size_t j = 0; j < entries_needed; j++) - if (m_entries[entry_index + j].name[0] != 0x00 && m_entries[entry_index + j].name[0] != 0xE5) - found = false; - - if (found) - break; - } + int entry_index = find_free_entries(entries_needed); // Store the entries in the cache for (size_t i = 0; i < entries_needed; i++) - { - if (i == 0) - m_entries[entry_index + i] = entry; - else - m_entries[entry_index + i] = *(dir_entry_t *)&lfn_entries[i - 1]; - } - - // Get where to write the entry - lba_t lba = m_volume -> data_lba + (m_first_cluster - 2) * m_volume -> bpb.sectors_per_cluster; - uint32_t offset = entry_index * sizeof(dir_entry_t); + m_entries[entry_index + i] = i == 0 ? entry : *(dir_entry_t *)&lfn_entries[i - 1]; // Write the long file name entries - for (auto& lfn_entry : lfn_entries) - { - // Write the entry to the disk - m_volume -> disk -> write(lba + offset, (uint8_t *)&lfn_entry, sizeof(long_file_name_entry_t)); - offset += sizeof(long_file_name_entry_t); - } - - // Write the directory entry - m_volume -> disk -> write(lba + offset, (uint8_t *)&entry, sizeof(dir_entry_t)); + for (size_t index = entry_index + lfn_count - 1; index >= entry_index; index--) + update_entry_on_disk(index); // Creating the file is done if (!is_directory) @@ -547,7 +528,7 @@ void Fat32Directory::remove_entry(uint32_t cluster, const string& name) // Find any long file name entries that belong to this entry size_t delete_entry_index = entry; - while (delete_entry_index > 0 && (m_entries[delete_entry_index - 1].attributes & 0x0F) == 0x0F) + while (delete_entry_index > 0 && m_entries[delete_entry_index - 1].attributes == (uint8_t)DirectoryEntryAttributes::LONG_NAME) delete_entry_index--; // Mark the entries as deleted @@ -555,22 +536,16 @@ void Fat32Directory::remove_entry(uint32_t cluster, const string& name) m_entries[i].name[0] = (uint8_t)DirectoryEntryType::DELETED; // Update the entries on the disk - lba_t first_directory_entry_lba = m_volume -> data_lba + (m_first_cluster - 2) * m_volume -> bpb.sectors_per_cluster; for (size_t i = delete_entry_index; i <= entry; i++) - { - uint32_t offset = (i * sizeof(dir_entry_t)) / m_volume -> bpb.bytes_per_sector; - m_volume -> disk -> write(first_directory_entry_lba + offset, (uint8_t *)&m_entries[i], sizeof(dir_entry_t)); - } + update_entry_on_disk(i); // Count the number of clusters in the chain size_t cluster_count = 0; for (uint32_t next_cluster = cluster; next_cluster < (lba_t)ClusterState::BAD; next_cluster = m_volume -> next_cluster(next_cluster)) cluster_count++; - // Free the clusters in the chain (more performant than calling a single - // free_cluser(cluster) as fs is updated at the end) + // Free all the clusters in the chain m_volume -> free_cluster(cluster, cluster_count); - } /** @@ -618,16 +593,35 @@ void Fat32Directory::save_entry_to_disk(DirectoryEntry* entry) { int index = 0; for (auto & m_entry : m_entries){ - index++; if (&m_entry == entry) break; + index++; } - // Write the entry to the disk - lba_t lba = m_volume -> data_lba + (m_first_cluster - 2) * m_volume -> bpb.sectors_per_cluster; - uint32_t offset = (index * sizeof(dir_entry_t)) / m_volume -> bpb.bytes_per_sector; - m_volume -> disk -> write(lba + offset, (uint8_t *)entry, sizeof(dir_entry_t)); + update_entry_on_disk(index); +} + +void Fat32Directory::update_entry_on_disk(int index) { + + // Get the entry + auto entry = m_entries[index]; + + //TODO: Multiple clusters? + lba_t base_lba = m_volume->data_lba + (m_first_cluster - 2) * m_volume->bpb.sectors_per_cluster; + + // Determine sector offset and in-sector byte offset + uint32_t bytes_per_sector = m_volume->bpb.bytes_per_sector; + uint32_t entry_offset = index * sizeof(dir_entry_t); + uint32_t sector_offset = entry_offset / bytes_per_sector; + uint32_t in_sector_offset = entry_offset % bytes_per_sector; + + // Read the full sector into a buffer + uint8_t sector_buffer[bytes_per_sector]; + m_volume->disk->read(base_lba + sector_offset, sector_buffer, bytes_per_sector); + // Update the entry in the buffer + memcpy(sector_buffer + in_sector_offset, &entry, sizeof(dir_entry_t)); + m_volume->disk->write(base_lba + sector_offset, sector_buffer, bytes_per_sector); } /** @@ -665,6 +659,32 @@ int Fat32Directory::entry_index(lba_t cluster) { } +/** + * @brief Find a series of free/deleted entries in a row + * + * @param amount The amount of adjacent entries to find + * + * @return The index of the first free entry or -1 if cant find that many free entries + */ +int Fat32Directory::find_free_entries(size_t amount) { + + int entry_index = -1; + for (; entry_index < m_entries.size(); entry_index++) + { + // Check if there are enough free entries in a row + bool found = true; + for (size_t j = 0; j < amount; j++) + if (m_entries[entry_index + j].name[0] != (char)DirectoryEntryType::DELETED + && m_entries[entry_index + j].name[0] != (char)DirectoryEntryType::LAST) + found = false; + + if (found) + break; + } + + return entry_index; +} + void Fat32Directory::read_from_disk() { for(auto& file : m_files) diff --git a/kernel/src/kernel.cpp b/kernel/src/kernel.cpp index 0baee83e..8285dc1c 100644 --- a/kernel/src/kernel.cpp +++ b/kernel/src/kernel.cpp @@ -78,11 +78,10 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic grub_cfg -> write((uint8_t*)test_data.c_str(), test_data.length()); grub_cfg ->seek(SeekType::SET, 0); - uint8_t buffer[100]; + auto buffer = new uint8_t[100]; grub_cfg ->read(buffer, 100); Logger::DEBUG() << (char*)buffer << "\n"; - Logger::HEADER() << "Stage {4}: System Finalisation\n"; Scheduler scheduler(multiboot); SyscallManager syscalls; @@ -95,8 +94,7 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic } // TODO: -// - Read from files -// - Timing storage +/// - FAT32 TODOS // - FAT32 Tests // - [x] Read subdirectories contents // - [x] Read long path subdirectories contents diff --git a/toolchain/copy_filesystem.sh b/toolchain/copy_filesystem.sh index c3a75c26..26fcc07c 100755 --- a/toolchain/copy_filesystem.sh +++ b/toolchain/copy_filesystem.sh @@ -1,9 +1,31 @@ #!/bin/bash +#TODO: rsync Mac, unmount/remount mac + SCRIPTDIR=$(dirname "$BASH_SOURCE") source $SCRIPTDIR/MaxOS.sh -msg "Copying boot files to image" +# Parse the args +REVERSE=0 +while [ "$#" -gt "0" ]; do + case "$1" in + --reverse) + REVERSE=1 + shift 1 + ;; + *) + warn "Error: Unknown argument $1" + ;; + esac +done + + + +if [ "$REVERSE" -ne 1 ]; then + msg "Copying boot files to image" +else + msg "Pulling changes made during run" +fi # Bootscript maps 8MB of kernel memory so ensure that the elf file is less than 8MB KERNEL_SIZE=$($STAT_EXC -c %s "$SCRIPTDIR/../filesystem/boot/MaxOSk64") @@ -13,9 +35,17 @@ fi DESTINATION="$MOUNT_DIR/MaxOS_img_1" -# Produce an ISO? default to no + + : "${USE_ISO:=0}" +# Produce an ISO? default to no if [ "$USE_ISO" -eq 1 ]; then + + # Cant pull changes from the iso + if [ "$REVERSE" -ne 1 ]; then + exit 0 + fi + DESTINATION="$SCRIPTDIR/../iso" if [ ! -d "$DESTINATION" ]; then mkdir -p "$DESTINATION" @@ -33,9 +63,13 @@ if [ "$IS_MACOS" -ne 1 ]; then fi # Syncing local filesystem -msg "Copying filesystem to image" -sudo rsync --no-owner --no-group -a --delete -c "$SCRIPTDIR/../filesystem/" "$MOUNT_DIR/MaxOS_img_1/" -#TODO: rsync Mac, unmount/remount mac +if [ "$REVERSE" -ne 1 ]; then + msg "Copying filesystem to image" + sudo rsync --no-o --no-g -a --delete -c "$SCRIPTDIR/../filesystem/" "$MOUNT_DIR/MaxOS_img_1/" +else + msg "Copying changes on image to local filesystem" + sudo rsync --chown=$(id -un):$(id -gn) -a --delete -c "$MOUNT_DIR/MaxOS_img_1/" "$SCRIPTDIR/../filesystem/" +fi # Create the iso if [ "$USE_ISO" -eq 1 ]; then diff --git a/toolchain/run_qemu.sh b/toolchain/run_qemu.sh index 6ff7c89b..28698d76 100755 --- a/toolchain/run_qemu.sh +++ b/toolchain/run_qemu.sh @@ -161,4 +161,7 @@ QEMU_ARGS="$QEMU_ARGS -no-reboot -no-shutdown" # Don't # Run qemu msg "Running qemu with args: $QEMU_ARGS" -"$QEMU_EXECUTABLE" $QEMU_ARGS \ No newline at end of file +"$QEMU_EXECUTABLE" $QEMU_ARGS + +# Cleanup +bash $SCRIPTDIR/copy_filesystem.sh --reverse \ No newline at end of file From 719acd0e144434b92685e2788a99474cfc3100ec Mon Sep 17 00:00:00 2001 From: Max Tyson <98maxt98@gmail.com> Date: Fri, 23 May 2025 11:07:07 +1200 Subject: [PATCH 15/38] FAT 32 TODO Clean up --- filesystem/test/a.txt | 2 +- kernel/include/filesystem/fat32.h | 12 +- kernel/src/filesystem/fat32.cpp | 293 ++++++++++++++++++++---------- kernel/src/kernel.cpp | 1 - 4 files changed, 203 insertions(+), 105 deletions(-) diff --git a/filesystem/test/a.txt b/filesystem/test/a.txt index c57eff55..f0bdd772 100755 --- a/filesystem/test/a.txt +++ b/filesystem/test/a.txt @@ -1 +1 @@ -Hello World! \ No newline at end of file +Hello World!OR A TESTHello World!EXTRA DATA FOR A TEST \ No newline at end of file diff --git a/kernel/include/filesystem/fat32.h b/kernel/include/filesystem/fat32.h index 9416794a..a258d4cd 100644 --- a/kernel/include/filesystem/fat32.h +++ b/kernel/include/filesystem/fat32.h @@ -107,13 +107,13 @@ namespace MaxOS{ enum class DirectoryEntryType { LAST = 0x00, - DELETED = 0xE5, + FREE = 0xE5, }; typedef struct LongFileNameEntry { uint8_t order; - uint16_t name1[5]; + uint16_t name1[5]; uint8_t attributes; uint8_t type; uint8_t checksum; @@ -148,6 +148,7 @@ namespace MaxOS{ size_t fat_total_clusters; lba_t fat_lba; lba_t fat_info_lba; + lba_t fat_copies; lba_t data_lba; lba_t root_lba; @@ -203,7 +204,10 @@ namespace MaxOS{ private: Fat32Volume* m_volume; + lba_t m_first_cluster; + lba_t m_last_cluster; + size_t m_current_cluster_length = 0; common::Vector m_entries; @@ -213,6 +217,10 @@ namespace MaxOS{ int entry_index(lba_t cluster); int find_free_entries(size_t amount); + int expand_directory(size_t amount); + + static common::Vector to_long_filenames(string name); + static string parse_long_filename(long_file_name_entry_t* entry, const string& current); protected: diff --git a/kernel/src/filesystem/fat32.cpp b/kernel/src/filesystem/fat32.cpp index f157b00d..c9c11c01 100644 --- a/kernel/src/filesystem/fat32.cpp +++ b/kernel/src/filesystem/fat32.cpp @@ -23,6 +23,7 @@ Fat32Volume::Fat32Volume(Disk* hd, uint32_t partition_offset) uint32_t total_data_sectors = bpb.total_sectors_32 - (bpb.reserved_sectors + (bpb.table_copies * bpb.table_size_32)); fat_total_clusters = total_data_sectors / bpb.sectors_per_cluster; fat_lba = partition_offset + bpb.reserved_sectors; + fat_copies = bpb.table_copies; fat_info_lba = partition_offset + bpb.fat_info; disk -> read(fat_info_lba, (uint8_t *)&fsinfo, sizeof(fs_info_t)); @@ -53,15 +54,15 @@ lba_t Fat32Volume::next_cluster(lba_t cluster) // Get the location in the FAT table lba_t offset = cluster * sizeof(uint32_t); lba_t sector = fat_lba + (offset / bpb.bytes_per_sector); - uint32_t entry = offset % bpb.bytes_per_sector; + uint32_t entry_index = offset % bpb.bytes_per_sector; // Read the FAT entry uint8_t fat[bpb.bytes_per_sector]; disk -> read(sector, fat, bpb.bytes_per_sector); // Get the next cluster info (mask the upper 4 bits) - uint32_t next_cluster = *(uint32_t *)&fat[entry]; - return next_cluster & 0x0FFFFFFF; + auto entry = (uint32_t *)(&fat[entry_index]); + return *entry & 0x0FFFFFFF; } /** @@ -74,18 +75,26 @@ lba_t Fat32Volume::next_cluster(lba_t cluster) uint32_t Fat32Volume::set_next_cluster(uint32_t cluster, uint32_t next_cluster) { + // TODO - when in userspace: For performance cache fat entirely, cache file data, cache cluster chains + // Get the location in the FAT table lba_t offset = cluster * sizeof(uint32_t); - lba_t sector = fat_lba + (offset / bpb.bytes_per_sector); - uint32_t entry = offset % bpb.bytes_per_sector; - // Read the FAT entry - uint8_t fat[bpb.bytes_per_sector]; - disk -> read(sector, fat, bpb.bytes_per_sector); + for (int i = 0; i < fat_copies; ++i) { - // Set the next cluster info (mask the upper 4 bits) - *(uint32_t *)&fat[entry] = next_cluster & 0x0FFFFFFF; - disk -> write(sector, fat, bpb.bytes_per_sector); + lba_t sector = (fat_lba + i * bpb.table_size_32) + (offset / bpb.bytes_per_sector); + uint32_t entry_index = offset % bpb.bytes_per_sector; + + // Read the FAT entry + uint8_t fat[bpb.bytes_per_sector]; + disk -> read(sector, fat, bpb.bytes_per_sector); + + // Set the next cluster info (mask the upper 4 bits) + auto entry = (uint32_t *)(&fat[entry_index]); + *entry = next_cluster & 0x0FFFFFFF; + disk -> write(sector, fat, bpb.bytes_per_sector); + + } return next_cluster; } @@ -411,16 +420,15 @@ Fat32Directory::~Fat32Directory() = default; */ dir_entry_t* Fat32Directory::create_entry(const string& name, bool is_directory) { - - // TODO: Write to all the FAT copies // TODO: Grow the cluster chain if needed // Allocate a cluster for the new entry uint32_t cluster = m_volume -> allocate_cluster(0); if (cluster == 0) - return 0; + return nullptr; - // Store the short name and extension + // Store the name + Vector lfn_entries = to_long_filenames(name); char short_name[8]; char short_extension[3]; for (int i = 0; i < 8; i++) @@ -428,43 +436,6 @@ dir_entry_t* Fat32Directory::create_entry(const string& name, bool is_directory) for (int i = 0; i < 3; i++) short_extension[i] = (8 + i < name.length()) ? name[8 + i] : ' '; - // Information for the new long file name entry - size_t lfn_count = (name.length() + 12) / 13; - Vector lfn_entries; - - // Create the long file name entries (in reverse order) - for (int i = lfn_count - 1; i >= 0; i--) - { - // Create the long file name entry - long_file_name_entry_t lfn_entry; - lfn_entry.order = i + 1; - lfn_entry.attributes = 0x0F; - lfn_entry.type = 0; - lfn_entry.checksum = 0; - - // If it is the last entry, set the last bit - if (i == lfn_entries.size() - 1) - lfn_entry.order |= 0x40; - - // Set the name - for (int j = 0; j < 13; j++) - { - - // Get the character info (0xFFFF if out of bounds) - size_t char_index = i * 13 + j; - char c = (char_index < name.length()) ? name[char_index] : 0xFFFF; - - // Set the character in the entry - if (j < 5) - lfn_entry.name1[j] = c; - else if (j < 11) - lfn_entry.name2[j - 5] = c; - else - lfn_entry.name3[j - 11] = c; - } - lfn_entries.push_back(lfn_entry); - } - // Create the directory entry dir_entry_t entry; memcpy(entry.name, short_name, sizeof(short_name)); @@ -476,13 +447,15 @@ dir_entry_t* Fat32Directory::create_entry(const string& name, bool is_directory) // Find free space for the new entry and the name size_t entries_needed = 1 + lfn_entries.size(); int entry_index = find_free_entries(entries_needed); + if(entry_index == -1) + entry_index = expand_directory(entries_needed); // Store the entries in the cache for (size_t i = 0; i < entries_needed; i++) m_entries[entry_index + i] = i == 0 ? entry : *(dir_entry_t *)&lfn_entries[i - 1]; // Write the long file name entries - for (size_t index = entry_index + lfn_count - 1; index >= entry_index; index--) + for (size_t index = entry_index + lfn_entries.size() - 1; index >= entry_index; index--) update_entry_on_disk(index); // Creating the file is done @@ -531,9 +504,9 @@ void Fat32Directory::remove_entry(uint32_t cluster, const string& name) while (delete_entry_index > 0 && m_entries[delete_entry_index - 1].attributes == (uint8_t)DirectoryEntryAttributes::LONG_NAME) delete_entry_index--; - // Mark the entries as deleted + // Mark the entries as free for (size_t i = delete_entry_index; i < entry; i++) - m_entries[i].name[0] = (uint8_t)DirectoryEntryType::DELETED; + m_entries[i].name[0] = (uint8_t)DirectoryEntryType::FREE; // Update the entries on the disk for (size_t i = delete_entry_index; i <= entry; i++) @@ -560,6 +533,10 @@ void Fat32Directory::read_all_entries() { // Read the directory for (uint32_t cluster = m_first_cluster; cluster != (uint32_t)ClusterState::END_OF_CHAIN; cluster = m_volume -> next_cluster(cluster)) { + // Cache info to prevent re-traversing the chain + m_last_cluster = cluster; + m_current_cluster_length++; + // Read each sector in the cluster memset(buffer, 0, buffer_space); lba_t lba = m_volume->data_lba + (cluster - 2) * m_volume->bpb.sectors_per_cluster; @@ -606,16 +583,19 @@ void Fat32Directory::update_entry_on_disk(int index) { // Get the entry auto entry = m_entries[index]; - //TODO: Multiple clusters? - lba_t base_lba = m_volume->data_lba + (m_first_cluster - 2) * m_volume->bpb.sectors_per_cluster; - // Determine sector offset and in-sector byte offset uint32_t bytes_per_sector = m_volume->bpb.bytes_per_sector; uint32_t entry_offset = index * sizeof(dir_entry_t); uint32_t sector_offset = entry_offset / bytes_per_sector; uint32_t in_sector_offset = entry_offset % bytes_per_sector; + // Find which cluster has the entry + uint32_t cluster = m_first_cluster; + for (uint32_t offset_remaining = entry_offset; offset_remaining >= bytes_per_sector; offset_remaining -= bytes_per_sector) + cluster = m_volume -> next_cluster(cluster); + // Read the full sector into a buffer + lba_t base_lba = m_volume->data_lba + (cluster - 2) * m_volume->bpb.sectors_per_cluster; uint8_t sector_buffer[bytes_per_sector]; m_volume->disk->read(base_lba + sector_offset, sector_buffer, bytes_per_sector); @@ -641,8 +621,8 @@ int Fat32Directory::entry_index(lba_t cluster) { if (entry.name[0] == (uint8_t)DirectoryEntryType::LAST) return -1; - // Skip deleted entries - if (entry.name[0] == (uint8_t)DirectoryEntryType::DELETED) + // Skip free entries + if (entry.name[0] == (uint8_t)DirectoryEntryType::FREE) continue; // Check if the entry is the one @@ -660,7 +640,7 @@ int Fat32Directory::entry_index(lba_t cluster) { } /** - * @brief Find a series of free/deleted entries in a row + * @brief Find a series of free entries in a row * * @param amount The amount of adjacent entries to find * @@ -668,21 +648,159 @@ int Fat32Directory::entry_index(lba_t cluster) { */ int Fat32Directory::find_free_entries(size_t amount) { - int entry_index = -1; - for (; entry_index < m_entries.size(); entry_index++) + for (int entry_index = 0; entry_index < m_entries.size(); entry_index++) { // Check if there are enough free entries in a row bool found = true; for (size_t j = 0; j < amount; j++) - if (m_entries[entry_index + j].name[0] != (char)DirectoryEntryType::DELETED - && m_entries[entry_index + j].name[0] != (char)DirectoryEntryType::LAST) + if (m_entries[entry_index + j].name[0] != (char)DirectoryEntryType::FREE) found = false; if (found) + return entry_index; + } + + return -1; +} + + +/** + * @brief Expand the directory to fit 'amount - free' more entries. + * + * @note Caller must write data to the entries as only the updated LAST entry is + * saved to disk. Theoretically there wont be an issue if caller doesn't as the + * old LAST will still be in the same space. + * + * @param amount How many free entries are needed + * @return The index of the first free entry in the chain + */ +int Fat32Directory::expand_directory(size_t amount) { + + // Remove the old end of directory marker + int free_start = m_entries.size() - 1; + ASSERT(m_entries[free_start].name[0] == (uint8_t)DirectoryEntryType::LAST, "Last entry is not marked"); + m_entries[free_start].name[0] = (uint8_t)DirectoryEntryType::FREE; + + // Count how many free entries there is before the end + for (int i = free_start; i >= 0; --i) { + if(m_entries[i].name[0] == (char)DirectoryEntryType::FREE) + free_start = i; + else break; } - return entry_index; + // Calculate how many entries are need to be created (ie was there enough free entries already) + uint32_t found = m_entries.size() - free_start; + uint32_t needed_entries = amount + 1; + uint32_t additional_entries = 0; + if(needed_entries > found) + additional_entries = needed_entries - found; + + // Find the length of the current cluster chain + uint32_t total_entries = m_entries.size() + additional_entries; + uint32_t total_clusters = (total_entries * sizeof(dir_entry_t)) / (m_volume->bpb.bytes_per_sector * m_volume->bpb.sectors_per_cluster); + + // Expand the cluster chain if needed + if(total_clusters > m_current_cluster_length){ + m_volume->allocate_cluster(m_last_cluster, total_clusters - m_current_cluster_length); + m_current_cluster_length = total_clusters; + } + + // Expand the directory to fit the remaining entries + for (int i = 0; i < additional_entries; ++i) { + dir_entry_t free = {}; + free.name[0] = (uint8_t)DirectoryEntryType::FREE; + m_entries.push_back(free); + } + + // Write the updated end of entries + m_entries[m_entries.size() - 1].name[0] = (uint8_t)DirectoryEntryType::LAST; + update_entry_on_disk(m_entries.size() - 1); + + return free_start; +} + +/** + * @brief Converts a string into a series of long file name entries (in reverse + * order, correct directory entry order) + * + * @param name The name + * @return A vector of longfile names + */ +Vector Fat32Directory::to_long_filenames(string name) { + + size_t lfn_count = (name.length() + 12) / 13; + Vector lfn_entries; + + // Create the long file name entries (in reverse order) + for (int i = lfn_count - 1; i >= 0; i--) + { + // Create the long file name entry + long_file_name_entry_t lfn_entry; + lfn_entry.order = i + 1; + lfn_entry.attributes = 0x0F; + lfn_entry.type = 0; + lfn_entry.checksum = 0; + + // If it is the last entry, set the last bit + if (i == lfn_entries.size() - 1) + lfn_entry.order |= 0x40; + + // Set the name + for (int j = 0; j < 13; j++) + { + + // Get the character info (0xFFFF if out of bounds) + size_t char_index = i * 13 + j; + char c = (char_index < name.length()) ? name[char_index] : 0xFFFF; + + // Set the character in the entry + if (j < 5) + lfn_entry.name1[j] = c; + else if (j < 11) + lfn_entry.name2[j - 5] = c; + else + lfn_entry.name3[j - 11] = c; + } + lfn_entries.push_back(lfn_entry); + } + + return lfn_entries; +} + +/** + * @brief Loads the characters from the entry and correctly appends to the whole name + * + * @param entry The entry to parse + * @param current The currently parsed string + * + * @return The parsed entry prepended to the current string + */ +string Fat32Directory::parse_long_filename(long_file_name_entry_t* entry, const string& current) { + + // Extract the long name from each part (in reverse order) + string current_long_name = ""; + for (int i = 0; i < 13; i++) { + + // Get the character (in utf8 encoding) + char c; + if (i < 5) + c = entry -> name1[i] & 0xFF; + else if (i < 11) + c = entry -> name2[i - 5] & 0xFF; + else + c = entry -> name3[i - 11] & 0xFF; + + // Padding / invalid or end of string + if (c == (char)0xFF || c == '\0') + break; + + // Add to the start as the entries are stored in reverse + current_long_name += string(c); + } + + // Entry parsed (prepend name) + return current_long_name + current; } void Fat32Directory::read_from_disk() { @@ -703,40 +821,15 @@ void Fat32Directory::read_from_disk() { for(auto& entry : m_entries){ // Skip free entries and volume labels - if (entry.name[0] == (uint8_t)DirectoryEntryType::DELETED + if (entry.name[0] == (uint8_t)DirectoryEntryType::FREE || entry.attributes == (uint8_t)DirectoryEntryAttributes::FREE || entry.attributes == (uint8_t)DirectoryEntryAttributes::VOLUME_ID) continue; - if (entry.attributes == (uint8_t)DirectoryEntryAttributes::LONG_NAME) - { - - // Extract the long name from each part (in reverse order) - auto long_name_entry = (long_file_name_entry_t*)&entry; - string current_long_name = ""; - for (int i = 0; i < 13; i++) { - - // Get the character (in utf8 encoding) - char c; - if (i < 5) - c = long_name_entry -> name1[i] & 0xFF; - else if (i < 11) - c = long_name_entry -> name2[i - 5] & 0xFF; - else - c = long_name_entry -> name3[i - 11] & 0xFF; - - // Padding / invalid or end of string - if (c == (char)0xFF || c == '\0') - break; - - // Add to the start as the entries are stored in reverse - current_long_name += string(c); - } - - // Entry parsed (prepend name) - long_name = current_long_name + long_name; + // Extract the long name + if (entry.attributes == (uint8_t)DirectoryEntryAttributes::LONG_NAME) { + long_name = parse_long_filename((long_file_name_entry_t*)&entry, long_name); continue; - } bool is_directory = entry.attributes == (uint8_t)DirectoryEntryAttributes::DIRECTORY; @@ -745,11 +838,10 @@ void Fat32Directory::read_from_disk() { string name = long_name; if(long_name == ""){ name = string(entry.name, 8); - if(!is_directory){ - name = name.strip(); - name += "."; - name += string(entry.extension, 3); - } + + // Add the extension + if(!is_directory) + name = name.strip() + "." + string(entry.extension, 3); } long_name = ""; @@ -867,7 +959,6 @@ void Fat32Directory::remove_subdirectory(const string& name) } } - Fat32FileSystem::Fat32FileSystem(Disk* disk, uint32_t partition_offset) : m_volume(disk, partition_offset) { diff --git a/kernel/src/kernel.cpp b/kernel/src/kernel.cpp index 8285dc1c..efb80602 100644 --- a/kernel/src/kernel.cpp +++ b/kernel/src/kernel.cpp @@ -94,7 +94,6 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic } // TODO: -/// - FAT32 TODOS // - FAT32 Tests // - [x] Read subdirectories contents // - [x] Read long path subdirectories contents From b24b5a536899bf2ebd509ec59e06888a93fe2654 Mon Sep 17 00:00:00 2001 From: Max Tyson <98maxt98@gmail.com> Date: Sat, 14 Jun 2025 23:29:34 +1200 Subject: [PATCH 16/38] WIP --- .github/workflows/max-os.yml | 2 +- kernel/include/common/string.h | 2 +- kernel/include/filesystem/fat32.h | 4 ++-- kernel/include/filesystem/partition/msdos.h | 3 --- kernel/src/common/string.cpp | 5 +++-- kernel/src/filesystem/fat32.cpp | 20 +++++++++++------- kernel/src/filesystem/vfs.cpp | 4 ++++ kernel/src/kernel.cpp | 23 ++++++++++----------- 8 files changed, 34 insertions(+), 29 deletions(-) diff --git a/.github/workflows/max-os.yml b/.github/workflows/max-os.yml index 7fbaa424..2743ebb8 100644 --- a/.github/workflows/max-os.yml +++ b/.github/workflows/max-os.yml @@ -58,7 +58,7 @@ jobs: generate-docs: # The type of runner that the job will run on - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest # Don't generate docs on dev branch if: github.ref == 'refs/heads/main' diff --git a/kernel/include/common/string.h b/kernel/include/common/string.h index b50af6ed..d3fd9ecd 100644 --- a/kernel/include/common/string.h +++ b/kernel/include/common/string.h @@ -45,7 +45,7 @@ namespace MaxOS { String substring(int start, int length) const; common::Vector split(String const &delimiter) const; - String strip() const; + String strip(char strip_char = ' ') const; [[nodiscard]] String center(int width, char fill = ' ') const; diff --git a/kernel/include/filesystem/fat32.h b/kernel/include/filesystem/fat32.h index a258d4cd..232bfcb1 100644 --- a/kernel/include/filesystem/fat32.h +++ b/kernel/include/filesystem/fat32.h @@ -125,11 +125,11 @@ namespace MaxOS{ typedef uint32_t lba_t; - enum class ClusterState + enum class ClusterState: uint32_t { FREE = 0x00000000, BAD = 0x0FFFFFF7, - END_OF_CHAIN = 0x0FFFFFF8, + END_OF_CHAIN = 0xFFFFFFFF, }; /** diff --git a/kernel/include/filesystem/partition/msdos.h b/kernel/include/filesystem/partition/msdos.h index b0ca908c..05fd9a84 100644 --- a/kernel/include/filesystem/partition/msdos.h +++ b/kernel/include/filesystem/partition/msdos.h @@ -342,9 +342,6 @@ namespace MaxOS{ // TODO: Abstract some of this into a base class and use it for GPT and other partition tables - - - } } } diff --git a/kernel/src/common/string.cpp b/kernel/src/common/string.cpp index 88bba2f0..799b0cd9 100644 --- a/kernel/src/common/string.cpp +++ b/kernel/src/common/string.cpp @@ -529,9 +529,10 @@ String String::center(int width, char fill) const { /** * @brief Strips the string of whitespace * + * @param strip_char The character to strip (default = ' ') * @return The stripped string (new string) */ -String String::strip() const { +String String::strip(char strip_char) const { // The stripped string String stripped; @@ -539,7 +540,7 @@ String String::strip() const { // Search from the back for the earliest non-whitespace character int end = m_length - 1; - while (end >= 0 && (m_string[end] == ' ' || m_string[end] == '\n' || m_string[end] == '\t')) + while (end >= 0 && (m_string[end] == strip_char || m_string[end] == '\n' || m_string[end] == '\t')) end--; // Make sure there is something to strip diff --git a/kernel/src/filesystem/fat32.cpp b/kernel/src/filesystem/fat32.cpp index c9c11c01..f88178f3 100644 --- a/kernel/src/filesystem/fat32.cpp +++ b/kernel/src/filesystem/fat32.cpp @@ -169,6 +169,7 @@ uint32_t Fat32Volume::allocate_cluster(uint32_t cluster, size_t amount) // Finish the chin set_next_cluster(cluster, (uint32_t)ClusterState::END_OF_CHAIN); + uint32_t next = next_cluster(cluster); return cluster; } @@ -420,7 +421,6 @@ Fat32Directory::~Fat32Directory() = default; */ dir_entry_t* Fat32Directory::create_entry(const string& name, bool is_directory) { - // TODO: Grow the cluster chain if needed // Allocate a cluster for the new entry uint32_t cluster = m_volume -> allocate_cluster(0); @@ -437,10 +437,10 @@ dir_entry_t* Fat32Directory::create_entry(const string& name, bool is_directory) short_extension[i] = (8 + i < name.length()) ? name[8 + i] : ' '; // Create the directory entry - dir_entry_t entry; + dir_entry_t entry = {}; memcpy(entry.name, short_name, sizeof(short_name)); memcpy(entry.extension, short_extension, sizeof(short_extension)); - entry.attributes = is_directory ? 0x10 : 0x20; + entry.attributes = is_directory ? (uint8_t)DirectoryEntryAttributes::DIRECTORY : (uint8_t)DirectoryEntryAttributes::ARCHIVE; entry.first_cluster_high = (cluster >> 16) & 0xFFFF; entry.first_cluster_low = cluster & 0xFFFF; @@ -463,23 +463,27 @@ dir_entry_t* Fat32Directory::create_entry(const string& name, bool is_directory) return &m_entries[entry_index]; // Create the "." in the directory - dir_entry_t current_dir_entry; + dir_entry_t current_dir_entry = {}; memcpy(current_dir_entry.name, ".", 1); current_dir_entry.attributes = 0x10; current_dir_entry.first_cluster_high = (cluster >> 16) & 0xFFFF; current_dir_entry.first_cluster_low = cluster & 0xFFFF; // Create the ".." in the directory - dir_entry_t parent_dir_entry; + dir_entry_t parent_dir_entry = {}; memcpy(parent_dir_entry.name, "..", 2); parent_dir_entry.attributes = 0x10; parent_dir_entry.first_cluster_high = (m_first_cluster >> 16) & 0xFFFF; parent_dir_entry.first_cluster_low = m_first_cluster & 0xFFFF; // Write the entries to the disk - lba_t child_lba = m_volume -> data_lba + (cluster - 2) * m_volume -> bpb.sectors_per_cluster; - m_volume -> disk -> write(child_lba, (uint8_t *)¤t_dir_entry, sizeof(dir_entry_t)); - m_volume -> disk -> write(child_lba + sizeof(dir_entry_t), (uint8_t *)&parent_dir_entry, sizeof(dir_entry_t)); + uint32_t bytes_per_sector = m_volume -> bpb.bytes_per_sector; + lba_t child_lba = m_volume -> data_lba + (cluster - 2) * bytes_per_sector; + uint8_t buffer[bytes_per_sector]; + memset(buffer, 0, bytes_per_sector); + memcpy(buffer, (uint8_t *)¤t_dir_entry, sizeof(dir_entry_t)); + memcpy(buffer + sizeof(dir_entry_t), (uint8_t *)&parent_dir_entry, sizeof(dir_entry_t)); + m_volume -> disk -> write(child_lba, buffer, bytes_per_sector); // Directory created return &m_entries[entry_index]; diff --git a/kernel/src/filesystem/vfs.cpp b/kernel/src/filesystem/vfs.cpp index 1e745443..27a672bb 100644 --- a/kernel/src/filesystem/vfs.cpp +++ b/kernel/src/filesystem/vfs.cpp @@ -292,6 +292,8 @@ Directory* VirtualFileSystem::create_directory(string path) if (!Path::vaild(path)) return nullptr; + path = path.strip('/'); + // Try to find the filesystem that is responsible for the path FileSystem* fs = find_filesystem(path); if (!fs) @@ -321,6 +323,8 @@ void VirtualFileSystem::delete_directory(string path) if (!Path::vaild(path)) return; + path = path.strip('/'); + // Open the directory Directory* directory = open_directory(path); if (!directory) diff --git a/kernel/src/kernel.cpp b/kernel/src/kernel.cpp index efb80602..b734d5b8 100644 --- a/kernel/src/kernel.cpp +++ b/kernel/src/kernel.cpp @@ -71,16 +71,12 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic driver_manager.activate_drivers(); // FS Tests - File* grub_cfg = vfs.open_file("/test/a.txt"); - Logger::DEBUG() << "Opened file: " << grub_cfg->name() << "\n"; + Directory* test = vfs.create_directory("/test/sub/"); + Logger::DEBUG() << "Created directory: " << test->name() << "\n"; - string test_data = "Hello World!"; - grub_cfg -> write((uint8_t*)test_data.c_str(), test_data.length()); - - grub_cfg ->seek(SeekType::SET, 0); - auto buffer = new uint8_t[100]; - grub_cfg ->read(buffer, 100); - Logger::DEBUG() << (char*)buffer << "\n"; + Directory* test2 = vfs.open_directory("/test/"); + for(auto& folder : test2 -> subdirectories()) + Logger::DEBUG() << " " << folder -> name() << "\n"; Logger::HEADER() << "Stage {4}: System Finalisation\n"; Scheduler scheduler(multiboot); @@ -94,18 +90,20 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic } // TODO: -// - FAT32 Tests +// - FAT32 Tests: // - [x] Read subdirectories contents // - [x] Read long path subdirectories contents // - [ ] Create subdirectories // - [ ] Create long path subdirectories -// - [ ] Delete subdirectories +// - [ ] Delete subdirectories (need to add ability to free clusters first // - [ ] Delete long path subdirectories // - [ ] Rename directory // - [ ] Rename file +// - [ ] Rename lfn directory +// - [ ] Rename lfn file // - [x] Read files // - [ ] Read large files -// - [ ] Write files +// - [x] Write files // - [ ] Write large files // - [ ] Create files // - [ ] Delete files @@ -118,6 +116,7 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic // - [ ] Create directories on a different mount point // - [ ] Stress test the filesystem: 1000s of files in a directory, long nested directories, long path files, etc + // - Fix tabs (mac mess up) // - Userspace Files (syscalls, proper path handling, working directories, file handles) // - Implement ext2 From 5ac1dd2fbad5a32f820a655db7decc8f8396fcee Mon Sep 17 00:00:00 2001 From: Max Tyson <98maxt98@gmail.com> Date: Thu, 17 Jul 2025 20:38:53 +1200 Subject: [PATCH 17/38] Give up on FAT for now --- kernel/include/filesystem/fat32.h | 29 +++++++++++++++++++++++++++++ kernel/src/kernel.cpp | 27 +-------------------------- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/kernel/include/filesystem/fat32.h b/kernel/include/filesystem/fat32.h index 232bfcb1..cc7a6854 100644 --- a/kernel/include/filesystem/fat32.h +++ b/kernel/include/filesystem/fat32.h @@ -13,6 +13,35 @@ namespace MaxOS{ namespace filesystem{ + // TODO: Revisit when I have the energy. + // BUG: Subdirectory seems to write to the disk this end but tools like + // fatcat complain the that the EOC isn't written (cluster 3037) + // - FAT32 Tests: + // - [x] Read subdirectories contents + // - [x] Read long path subdirectories contents + // - [ ] Create subdirectories + // - [ ] Create long path subdirectories + // - [ ] Delete subdirectories (need to add ability to free clusters first + // - [ ] Delete long path subdirectories + // - [ ] Rename directory + // - [ ] Rename file + // - [ ] Rename lfn directory + // - [ ] Rename lfn file + // - [x] Read files + // - [ ] Read large files + // - [x] Write files + // - [ ] Write large files + // - [ ] Create files + // - [ ] Delete files + // - [x] Read long path files + // - [ ] Create long path files + // - [ ] Delete long path files + // - [ ] Create files on a different mount point + // - [ ] Delete files on a different mount point + // - [ ] Read directories on a different mount point + // - [ ] Create directories on a different mount point + // - [ ] Stress test the filesystem: 1000s of files in a directory, long nested directories, long path files, etc + /** * @struct BiosParameterBlock * @brief Stores information about the FAT32 filesystem diff --git a/kernel/src/kernel.cpp b/kernel/src/kernel.cpp index b734d5b8..96ed8c84 100644 --- a/kernel/src/kernel.cpp +++ b/kernel/src/kernel.cpp @@ -90,35 +90,10 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic } // TODO: -// - FAT32 Tests: -// - [x] Read subdirectories contents -// - [x] Read long path subdirectories contents -// - [ ] Create subdirectories -// - [ ] Create long path subdirectories -// - [ ] Delete subdirectories (need to add ability to free clusters first -// - [ ] Delete long path subdirectories -// - [ ] Rename directory -// - [ ] Rename file -// - [ ] Rename lfn directory -// - [ ] Rename lfn file -// - [x] Read files -// - [ ] Read large files -// - [x] Write files -// - [ ] Write large files -// - [ ] Create files -// - [ ] Delete files -// - [x] Read long path files -// - [ ] Create long path files -// - [ ] Delete long path files -// - [ ] Create files on a different mount point -// - [ ] Delete files on a different mount point -// - [ ] Read directories on a different mount point -// - [ ] Create directories on a different mount point -// - [ ] Stress test the filesystem: 1000s of files in a directory, long nested directories, long path files, etc + // - Fix tabs (mac mess up) // - Userspace Files (syscalls, proper path handling, working directories, file handles) -// - Implement ext2 // - Class & Struct docstrings // - Logo on fail in center \ No newline at end of file From f5d2c2fe94a81e13953a979e0d5dd9665170751f Mon Sep 17 00:00:00 2001 From: Max Tyson <98maxt98@gmail.com> Date: Thu, 17 Jul 2025 21:19:32 +1200 Subject: [PATCH 18/38] Boot from ext2 --- kernel/src/filesystem/partition/msdos.cpp | 4 ++ kernel/src/kernel.cpp | 26 ++++++++- toolchain/MaxOS.sh | 3 ++ toolchain/create_disk_img.sh | 66 +++++++++++++++++------ 4 files changed, 82 insertions(+), 17 deletions(-) diff --git a/kernel/src/filesystem/partition/msdos.cpp b/kernel/src/filesystem/partition/msdos.cpp index 2a45d7c5..3b87bc03 100644 --- a/kernel/src/filesystem/partition/msdos.cpp +++ b/kernel/src/filesystem/partition/msdos.cpp @@ -51,6 +51,10 @@ void MSDOSPartition::mount_partitions(Disk* hd) { vfs -> mount_filesystem(new Fat32FileSystem(hd, entry.start_LBA)); break; + case PartitionType::LINUX_EXT2: + Logger::Out() << "EXT2 partition\n"; + break; + default: Logger::Out() << "Unknown or unimplemented partition type: 0x" << (uint64_t)entry.type << "\n"; diff --git a/kernel/src/kernel.cpp b/kernel/src/kernel.cpp index 96ed8c84..cc341902 100644 --- a/kernel/src/kernel.cpp +++ b/kernel/src/kernel.cpp @@ -90,7 +90,31 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic } // TODO: - +// - EXT2 Tests: +// - [ ] Read subdirectories contents +// - [ ] Read long path subdirectories contents +// - [ ] Create subdirectories +// - [ ] Create long path subdirectories +// - [ ] Delete subdirectories (need to add ability to free clusters first +// - [ ] Delete long path subdirectories +// - [ ] Rename directory +// - [ ] Rename file +// - [ ] Rename lfn directory +// - [ ] Rename lfn file +// - [ ] Read files +// - [ ] Read large files +// - [ ] Write files +// - [ ] Write large files +// - [ ] Create files +// - [ ] Delete files +// - [ ] Read long path files +// - [ ] Create long path files +// - [ ] Delete long path files +// - [ ] Create files on a different mount point +// - [ ] Delete files on a different mount point +// - [ ] Read directories on a different mount point +// - [ ] Create directories on a different mount point +// - [ ] Stress test the filesystem: 1000s of files in a directory, long nested directories, long path files, etc // - Fix tabs (mac mess up) diff --git a/toolchain/MaxOS.sh b/toolchain/MaxOS.sh index 1ac98412..73611058 100755 --- a/toolchain/MaxOS.sh +++ b/toolchain/MaxOS.sh @@ -40,6 +40,9 @@ STAT_EXC=stat MOUNT_DIR="/mnt" +# Filesystem type: "FAT" or "EXT2" +FILESYSTEM_TYPE="EXT2" + # Set a variable if this is MacOS IS_MACOS=0 if [[ ($(uname) == "Darwin") ]]; then diff --git a/toolchain/create_disk_img.sh b/toolchain/create_disk_img.sh index 35c0e007..5f1308f3 100755 --- a/toolchain/create_disk_img.sh +++ b/toolchain/create_disk_img.sh @@ -3,7 +3,7 @@ SCRIPTDIR=$(dirname "$BASH_SOURCE") source $SCRIPTDIR/MaxOS.sh -# TODO: Scalibitlity for partition size and amount of partitions +# TODO: Scalability for partition size and amount of partitions # TODO: Better loop device handling IMAGE="../MaxOS.img" @@ -28,6 +28,13 @@ if [ "$IS_MACOS" -eq 1 ]; then dev=${dev_arr[0]} sudo diskutil partitionDisk $dev MBRFormat "MS-DOS FAT32" "BOOT" 1G "MS-DOS FAT32" "DATA" R else + + if [ "$FILESYSTEM_TYPE" = "FAT" ]; then + TYPE_CODE="b" # FAT32 + else + TYPE_CODE="83" # EXT2 + fi + fdisk "$IMAGE" -u=cylinders << EOF o n @@ -36,7 +43,7 @@ else 1 130 t - b + $TYPE_CODE a 1 n @@ -46,7 +53,7 @@ else 243 t 2 - b + $TYPE_CODE w EOF dev=$(sudo losetup --find --show --partscan "$IMAGE") @@ -80,24 +87,42 @@ msg "${part2}" ## IMAGE 1 msg "Creating filesystem for partition 1" sudo mkdir -p "$MOUNT_DIR/MaxOS_img_1" || fail "Could not create mount point" -if [ "$IS_MACOS" -eq 1 ]; then - sudo diskutil unmount "$part1" || warn "Couldn't unmount $part1 before formatting" - sudo mount -t msdos "$part1" "$MOUNT_DIR/MaxOS_img_1" || fail "Could not mount partition 1" +if [ "$FILESYSTEM_TYPE" = "FAT" ]; then + if [ "$IS_MACOS" -eq 1 ]; then + sudo diskutil unmount "$part1" || warn "Couldn't unmount $part1 before formatting" + sudo mount -t msdos "$part1" "$MOUNT_DIR/MaxOS_img_1" || fail "Could not mount partition 1" + else + sudo mkfs.vfat -F 32 "$part1" || fail "Could not create FAT32 filesystem" + sudo mount "$part1" "$MOUNT_DIR/MaxOS_img_1" || fail "Could not mount image to mount point" + fi else - sudo mkfs.vfat -F 32 "$part1" || fail "Could not create filesystem" - sudo mount "$part1" "$MOUNT_DIR/MaxOS_img_1" || fail "Could not mount image to mount point" + if [ "$IS_MACOS" -eq 1 ]; then + fail "EXT2 is not supported on macOS by default" + else + sudo mkfs.ext2 "$part1" || fail "Could not create EXT2 filesystem" + sudo mount "$part1" "$MOUNT_DIR/MaxOS_img_1" || fail "Could not mount image to mount point" + fi fi - ## IMAGE 2 msg "Creating filesystem for partition 2" -sudo mkdir -p "$MOUNT_DIR/MaxOS_img_2" || fail "Could not create mount point" -if [ "$IS_MACOS" -eq 1 ]; then - sudo diskutil unmount "$part2" || warn "Couldn't unmount $part2 before formatting" - sudo mount -t msdos "$part2" "$MOUNT_DIR/MaxOS_img_2" || fail "Could not mount partition 2" +sudo mkdir -p "$MOUNT_DIR/MaxOS_img_2" || fail "Could not create mount point" + +if [ "$FILESYSTEM_TYPE" = "FAT" ]; then + if [ "$IS_MACOS" -eq 1 ]; then + sudo diskutil unmount "$part2" || warn "Couldn't unmount $part2 before formatting" + sudo mount -t msdos "$part2" "$MOUNT_DIR/MaxOS_img_2" || fail "Could not mount partition 2" + else + sudo mkfs.vfat -F 32 "$part2" || fail "Could not create FAT32 filesystem" + sudo mount "$part2" "$MOUNT_DIR/MaxOS_img_2" || fail "Could not mount image to mount point" + fi else - sudo mkfs.vfat -F 32 "$part2" || fail "Could not create filesystem" - sudo mount "$part2" "$MOUNT_DIR/MaxOS_img_2" || fail "Could not mount image to mount point" + if [ "$IS_MACOS" -eq 1 ]; then + fail "EXT2 is not supported on macOS by default" + else + sudo mkfs.ext2 "$part2" || fail "Could not create EXT2 filesystem" + sudo mount "$part2" "$MOUNT_DIR/MaxOS_img_2" || fail "Could not mount image to mount point" + fi fi # Sync @@ -105,8 +130,17 @@ msg "Syncing filesystem" sync sudo sync +# Define grub modules +GRUB_MODULES="normal part_msdos biosdisk echo multiboot2" +if [ "$FILESYSTEM_TYPE" = "FAT" ]; then + GRUB_MODULES="$GRUB_MODULES fat" +fi + +if [ "$FILESYSTEM_TYPE" = "EXT2" ]; then + GRUB_MODULES="$GRUB_MODULES ext2" +fi + #Install grub to the image -GRUB_MODULES="normal part_msdos fat biosdisk echo multiboot2" if [ "$IS_MACOS" -eq 1 ]; then msg "Installing GRUB manually on macOS" From 09d302bcd46dd7ac3579b89235af4fe38ceb2c9e Mon Sep 17 00:00:00 2001 From: Max Tyson <98maxt98@gmail.com> Date: Sun, 20 Jul 2025 17:50:40 +1200 Subject: [PATCH 19/38] Ext2 Directory Parsing --- .idea/codeStyles/codeStyleConfig.xml | 2 +- kernel/include/filesystem/ext2.h | 299 +++++++++++++++++ kernel/include/filesystem/fat32.h | 4 +- kernel/include/filesystem/filesystem.h | 2 + kernel/include/filesystem/partition/msdos.h | 1 + kernel/src/drivers/disk/ata.cpp | 2 +- kernel/src/filesystem/ext2.cpp | 348 ++++++++++++++++++++ kernel/src/filesystem/fat32.cpp | 6 +- kernel/src/filesystem/partition/msdos.cpp | 1 + kernel/src/kernel.cpp | 43 ++- toolchain/run_gdb.sh | 60 +--- toolchain/run_qemu.sh | 9 + 12 files changed, 706 insertions(+), 71 deletions(-) create mode 100644 kernel/include/filesystem/ext2.h create mode 100644 kernel/src/filesystem/ext2.cpp diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml index 41aee021..7f9d6a5d 100644 --- a/.idea/codeStyles/codeStyleConfig.xml +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -1,5 +1,5 @@ - \ No newline at end of file diff --git a/kernel/include/filesystem/ext2.h b/kernel/include/filesystem/ext2.h new file mode 100644 index 00000000..5569367b --- /dev/null +++ b/kernel/include/filesystem/ext2.h @@ -0,0 +1,299 @@ +// +// Created by 98max on 17/07/2025. +// + +#ifndef MAXOS_FILESYSTEM_EXT2_H +#define MAXOS_FILESYSTEM_EXT2_H + +#include +#include +#include +#include + +namespace MaxOS { + namespace filesystem { + namespace ext2{ + + + typedef struct SuperBlock{ + uint32_t total_inodes; + uint32_t total_blocks; + uint32_t reserved_blocks; + uint32_t unallocated_blocks; + uint32_t unallocated_inodes; + uint32_t starting_block; + uint32_t block_size; + uint32_t fragment_size; + uint32_t blocks_per_group; + uint32_t fragments_per_group; + uint32_t inodes_per_group; + uint32_t last_mount_time; + uint32_t late_write_time; + uint16_t mounts_since_check; + uint16_t mounts_until_check; + uint16_t signature; + uint16_t state; + uint16_t error_operation; + uint16_t version_minor; + uint32_t last_check_time; + uint32_t time_until_check; + uint32_t os_id; + uint32_t version_major; + uint16_t reserved_user; + uint16_t reserved_group; + + // Extended Fields (version >= 1) + uint32_t first_inode; + uint16_t inode_size; + uint16_t superblock_group; + uint32_t optional_features; + uint32_t required_features; + uint16_t read_only_features; + uint8_t filesystem_id[16]; + uint8_t volume_name[16]; + uint8_t last_mount_path[64]; + uint32_t compression; + uint8_t file_preallocation_blocks; + uint8_t directory_preallocation_blocks; + uint16_t unused; + uint8_t journal_id[16]; + uint32_t journal_inode; + uint32_t journal_device; + uint32_t orphan_inodes_start; + uint8_t free[276]; + + } __attribute__((packed)) superblock_t; + + + enum class FileSystemState{ + CLEAN = 1, + ERROR = 2, + }; + + enum class ErrorOperation{ + IGNORE = 1, + REMOUNT = 2, + PANIC = 3, + }; + + enum class CreatorOS{ + LINUX, + GNU_HURD, + MASIX, + FREE_BSD, + OTHER_LITES, + }; + + enum class OptionalFeatures{ + PREALLOCATE_DIRECTORY = 0x1, + AFS_SERVER_INODES = 0x2, + JOURNAL_ENABLED = 0x4, + ATTRIBUTES_EXTENDED = 0x8, + RESIZEABLE = 0x10, + HASH_INDEXING = 0x20, + }; + + enum class RequiredFeatures { + COMPRESSION = 0x1, + DIRECTORY_HAS_TYPE = 0x2, + MUST_REPLAY_JOURNAL = 0x4, + JOURNAL_DEVICE = 0x8, + }; + + enum class ReadOnlyFeatures { + SPARSE_SUPER_BLOCKS = 0x1, + FILES_64_BIT = 0x2, + BINARY_TREE_DIRECTORIES = 0x4, + }; + + typedef struct BlockGroupDescriptor{ + uint32_t block_usage_bitmap; + uint32_t block_inode_bitmap; + uint32_t inode_table_address; + uint16_t free_blocks; + uint16_t free_inodes; + uint16_t directory_count; + uint8_t free[14]; + + } __attribute__((packed)) block_group_descriptor_t; + + typedef struct Inode{ + union { + uint16_t type_permissions; + struct { + uint16_t permissions : 12; + uint16_t type : 4; + }; + }; + uint16_t user_id; + uint32_t size_lower; + uint32_t last_access_time; + uint32_t creation_time; + uint32_t last_modification_time; + uint32_t deletion_time; + uint16_t group_id; + uint16_t hard_links; + uint32_t sectors_used; + uint32_t flags; + uint32_t os_1; + uint32_t block_pointers[12]; + uint32_t l1_indirect; + uint32_t l2_indirect; + uint32_t l3_indirect; + uint32_t generation; + uint32_t extended_attribute; // File ACL + uint32_t size_upper; // Dir ACL + uint32_t os_2[3]; + } __attribute__((packed)) inode_t; + + enum class InodeType { + UNKNOWN, + FILE, + DIRECTORY, + CHARACTER_DEVICE, + BLOCK_DEVICE, + FIFO, + SOCKET, + SYMBOLIC_LINK + }; + + enum class InodePermissions{ + OTHER_EXECUTE = 0x1, + OTHER_WRITE = 0x2, + OTHER_READ = 0x4, + GROUP_EXECUTE = 0x8, + GROUP_WRITE = 0x10, + GROUP_READ = 0x20, + USER_EXECUTE = 0x40, + USER_WRITE = 0x80, + USER_READ = 0x100, + STICKY = 0x200, + GROUP_ID = 0x400, + USER_ID = 0x800, + }; + + enum class InodeFlags{ + SECURE_DELETE = 0x1, // Zero out data on deletion + KEEP_DATA = 0x2, + FILE_COMPRESSION = 0x4, + SYNC_UPDATES = 0x8, + FILE_IMMUTABLE = 0x10, + APPEND_ONLY = 0x20, + DONT_DUMP = 0x40, + NO_LAST_ACCESS = 0x80, + HASH_INDEXED = 0x10000, + AFS_DIRECTORY = 0x20000, + JOURNAL_FILE_DATA = 0x40000, + }; + + // TODO: Also HURD, MASIX + typedef struct InodeOS2Linux{ + uint8_t fragment; + uint8_t fragment_size; + uint16_t high_type_permissions; + uint16_t high_user_id; + uint16_t high_group_id; + uint32_t author_id; //0xFFFFFFFF = use user_id + } __attribute__((packed)) linux_os_2_t; + + typedef struct DirectoryEntry{ + uint32_t inode; + uint16_t size; + uint8_t name_length; + uint8_t type; + // Rest are name chars + } __attribute__((packed)) directory_entry_t; + + class Ext2Volume { + + public: + Ext2Volume(drivers::disk::Disk* disk, lba_t partition_offset); + ~Ext2Volume(); + + drivers::disk::Disk* disk; + lba_t partition_offset; + + superblock_t superblock; + block_group_descriptor_t** block_groups; + + size_t block_size; + uint32_t block_group_descriptor_table; + uint32_t block_group_descriptor_table_size; + uint32_t total_block_groups; + size_t pointers_per_block; + uint32_t inodes_per_block; + uint32_t sectors_per_block; + + uint32_t blocks_per_inode_table; + uint32_t sectors_per_inode_table; + + void read_block(uint32_t block_num, uint8_t* buffer); + inode_t read_inode(uint32_t inode_num); + block_group_descriptor_t read_block_group(uint32_t group_num); + }; + + /** + * @class Ext2File + * @brief Handles the file operations on the ext2 filesystem + */ + class Ext2File final : public File { + private: + Ext2Volume* m_volume; + uint32_t m_inode_number; + inode_t m_inode; + + public: + Ext2File(Ext2Volume* volume, uint32_t inode, const string& name); + ~Ext2File() final; + + void write(const uint8_t* data, size_t amount) final; + void read(uint8_t* data, size_t amount) final; + void flush() final; + }; + + /** + * @class Ext2Directory + * @brief Handles the directory operations on the ext2 filesystem + */ + class Ext2Directory final : public Directory { + + private: + Ext2Volume* m_volume; + uint32_t m_inode_number; + inode_t m_inode; + + common::Vector m_entries; + void parse_entries(uint8_t* buffer); + void parse_indirect(uint32_t level, uint32_t block, uint8_t* buffer); + + public: + Ext2Directory(Ext2Volume* volume, uint32_t inode, const string& name); + ~Ext2Directory() final; + + void read_from_disk() final; + + File* create_file(const string& name) final; + void remove_file(const string& name) final; + + Directory* create_subdirectory(const string& name) final; + void remove_subdirectory(const string& name) final; + }; + + /** + * @class Ext2FileSystem + * @brief Handles the ext2 filesystem operations + */ + class Ext2FileSystem final : public FileSystem { + private: + Ext2Volume m_volume; + + public: + Ext2FileSystem(drivers::disk::Disk* disk, uint32_t partition_offset); + ~Ext2FileSystem() final; + }; + + } + } +} + +#endif // MAXOS_FILESYSTEM_EXT2_H diff --git a/kernel/include/filesystem/fat32.h b/kernel/include/filesystem/fat32.h index cc7a6854..ddf0aa16 100644 --- a/kernel/include/filesystem/fat32.h +++ b/kernel/include/filesystem/fat32.h @@ -152,8 +152,6 @@ namespace MaxOS{ } __attribute__((packed)) long_file_name_entry_t; - typedef uint32_t lba_t; - enum class ClusterState: uint32_t { FREE = 0x00000000, @@ -202,7 +200,7 @@ namespace MaxOS{ * @class Fat32File * @brief Handles the file operations on the FAT32 filesystem */ - class Fat32File : public File + class Fat32File final : public File { private: diff --git a/kernel/include/filesystem/filesystem.h b/kernel/include/filesystem/filesystem.h index b99a6bba..e418d9c6 100644 --- a/kernel/include/filesystem/filesystem.h +++ b/kernel/include/filesystem/filesystem.h @@ -14,6 +14,8 @@ namespace MaxOS{ namespace filesystem{ + // Easier to read + typedef uint32_t lba_t; enum class SeekType{ SET, diff --git a/kernel/include/filesystem/partition/msdos.h b/kernel/include/filesystem/partition/msdos.h index 05fd9a84..0d24cea5 100644 --- a/kernel/include/filesystem/partition/msdos.h +++ b/kernel/include/filesystem/partition/msdos.h @@ -8,6 +8,7 @@ #include #include #include +#include #include namespace MaxOS{ diff --git a/kernel/src/drivers/disk/ata.cpp b/kernel/src/drivers/disk/ata.cpp index 616d95b1..c8ed4ef5 100644 --- a/kernel/src/drivers/disk/ata.cpp +++ b/kernel/src/drivers/disk/ata.cpp @@ -86,7 +86,7 @@ bool AdvancedTechnologyAttachment::identify() { * * @param sector The sector to read * @param data_buffer The data to read into - * @param amount The amount of data to read from that sector + * @param amount The amount of bytes to read from that sector */ void AdvancedTechnologyAttachment::read(uint32_t sector, uint8_t* data_buffer, size_t amount) { diff --git a/kernel/src/filesystem/ext2.cpp b/kernel/src/filesystem/ext2.cpp new file mode 100644 index 00000000..bc7fed90 --- /dev/null +++ b/kernel/src/filesystem/ext2.cpp @@ -0,0 +1,348 @@ +// +// Created by 98max on 17/07/2025. +// +#include + +using namespace MaxOS; +using namespace MaxOS::filesystem; +using namespace MaxOS::filesystem::ext2; +using namespace MaxOS::drivers; +using namespace MaxOS::drivers::disk; + +Ext2Volume::Ext2Volume(drivers::disk::Disk *disk, lba_t partition_offset) +: disk(disk), + partition_offset(partition_offset) +{ + + // Read superblock + uint8_t buffer[1024]; + disk->read(partition_offset + 2, buffer, 512); + disk->read(partition_offset + 3, buffer + 512, 512); + memcpy(&superblock, buffer, sizeof(superblock_t)); + + // Validate signature + ASSERT(superblock.signature == 0xEF53, "Ext2 Filesystem doesnt have a valid signature\n"); + + // Version 0 has constant inode info + if(superblock.version_major < 1){ + superblock.first_inode = 11; + superblock.inode_size = 128; + } + + // Parse the superblock + block_size = 1024 << superblock.block_size; + total_block_groups = (superblock.total_blocks + superblock.blocks_per_group - 1) / superblock.blocks_per_group; + block_group_descriptor_table = (block_size == 1024) ? 2 : 1; + block_group_descriptor_table_size = total_block_groups * sizeof(block_group_descriptor_t); + pointers_per_block = block_size / sizeof(uint32_t); + inodes_per_block = block_size / superblock.block_size; + sectors_per_block = block_size / 512; + blocks_per_inode_table = (superblock.inode_size * superblock.inodes_per_group) / block_size; + sectors_per_inode_table = (superblock.inode_size * superblock.inodes_per_group) / 512; + + // Read the block groups + block_groups = new block_group_descriptor_t*[total_block_groups] {nullptr}; + uint32_t sectors_to_read = (block_group_descriptor_table_size + block_size - 1) / block_size * sectors_per_block; + auto* bg_buffer = new uint8_t[sectors_to_read * 512]; + for (uint32_t i = 0; i < sectors_to_read; ++i) + disk->read(partition_offset + block_group_descriptor_table * sectors_per_block + i, bg_buffer + i * 512, 512); + + // Store the block groups + for (uint32_t i = 0; i < total_block_groups; ++i) { + block_groups[i] = new block_group_descriptor_t; + memcpy(block_groups[i], bg_buffer + i * sizeof(block_group_descriptor_t), sizeof(block_group_descriptor_t)); + } + delete[] bg_buffer; + +} + +Ext2Volume::~Ext2Volume() = default; + +/** + * @breif Reads a single block from the disk into a buffer + * + * @param block_num The block to read + * @param buffer The buffer to read into + */ +void Ext2Volume::read_block(uint32_t block_num, uint8_t *buffer) { + + // Read each sector of the block + for (size_t i = 0; i < sectors_per_block; ++i) + disk->read(partition_offset + block_num * sectors_per_block + i, buffer + i * 512, 512); + +} + +/** + * @brief Read an inode from the filesystem + * + * @param inode_num The inode index + */ +inode_t Ext2Volume::read_inode(uint32_t inode_num) { + + inode_t inode; + + // Locate the inode + uint32_t group = (inode_num - 1) / superblock.inodes_per_group; + uint32_t index = (inode_num - 1) % superblock.inodes_per_group; + + // Locate the block + uint32_t inode_table = block_groups[group] -> inode_table_address; + uint32_t offset = index * superblock.inode_size; + uint32_t block = offset / block_size; + uint32_t in_block_offset = offset % block_size; + + // Read the inode + auto* buffer = new uint8_t[block_size]; + read_block(inode_table + block, buffer); + memcpy(&inode, buffer + in_block_offset, sizeof(inode_t)); + + delete[] buffer; + return inode; +} + +/** + * @brief Read a blockgroup from the disk + * + * @param group_num The group to read + * @return The block group descriptor for the specified group + */ +block_group_descriptor_t Ext2Volume::read_block_group(uint32_t group_num) { + block_group_descriptor_t descriptor; + + // Locate the group + uint32_t offset = group_num * sizeof(block_group_descriptor_t); + uint32_t block = offset / block_size; + uint32_t in_block_offset = offset % block_size; + + // Read the block group + auto* buffer = new uint8_t[block_size]; + read_block(2 + block, buffer); + memcpy(&descriptor, buffer + in_block_offset, sizeof(block_group_descriptor_t)); + + delete[] buffer; + return descriptor; +}; + +Ext2File::Ext2File(Ext2Volume *volume, uint32_t inode, string const &name) +: m_volume(volume), + m_inode_number(inode), + m_inode() +{ + m_name = name; +} + +/** + * @brief Write data to the file (at the current seek position, updated to be += amount) + * + * @param data The byte buffer to write + * @param amount The amount of data to write + */ +void Ext2File::write(uint8_t const *data, size_t amount) { + File::write(data, amount); +} + +/** +* @brief Read data from the file (at the current seek position, updated to be += amount) +* +* @param data The byte buffer to read into +* @param amount The amount of data to read +*/ +void Ext2File::read(uint8_t *data, size_t amount) { + File::read(data, amount); +} + +/** + * @brief Flush the file to the disk + */ +void Ext2File::flush() { + File::flush(); +} + +Ext2File::~Ext2File() = default; + + +Ext2Directory::Ext2Directory(Ext2Volume *volume, uint32_t inode, const string& name) +: m_volume(volume), + m_inode_number(inode), + m_inode() +{ + m_name = name; +} + +/** + * @brief Store all entries from a buffer and convert to File or Directory objects + */ +void Ext2Directory::parse_entries(uint8_t* buffer) { + + size_t offset = 0; + while (offset < m_volume -> block_size){ + + // Read the entry + auto* entry = (directory_entry_t*)(buffer + offset); + m_entries.push_back(*entry); + + // Not valid + if (entry -> inode == 0 || entry -> name_length == 0) + break; + + // Parse + string filename(buffer + offset + sizeof(directory_entry_t), entry->name_length); + uint32_t inode = entry->inode; + + // Create the object + switch ((InodeType)entry->type) { + + case InodeType::FILE: + m_files.push_back(new Ext2File(m_volume, inode, filename)); + break; + + case InodeType::DIRECTORY: + m_subdirectories.push_back(new Ext2Directory(m_volume, inode, filename)); + break; + + default: + Logger::WARNING() << "Unknown entry type: " << entry->type << "\n"; + + } + + // Go to next + offset += entry -> size; + } + +} + +/** + * @brief Recursively parses the indirect entries + * + * @param level Recursion level + * @param block The block number to parse from + * @param buffer Buffer to read into + */ +void Ext2Directory::parse_indirect(uint32_t level, uint32_t block, uint8_t* buffer) { + + // Invalid + if(block == 0) + return; + + // Read the block + m_volume ->read_block(block, buffer); + auto* pointers = (uint32_t*)buffer; + + // Parse the pointers + for (size_t i = 0; i < m_volume->pointers_per_block; ++i) { + uint32_t pointer = pointers[i]; + + // Invaild + if(pointer == 0) + break; + + // Has indirect sub entries + if(level > 1){ + parse_indirect(level - 1, pointer, buffer); + continue; + } + + // Parse the entry + m_volume ->read_block(pointer, buffer); + parse_entries(buffer); + } +} + +/** + * @brief Read the directory from the inode on the disk + */ +void Ext2Directory::read_from_disk() { + + // Read the inode + m_inode = m_volume ->read_inode(m_inode_number); + + // Clear the old files & Directories + for(auto& file : m_files) + delete file; + m_files.clear(); + + for(auto& directory : m_subdirectories) + delete directory; + m_subdirectories.clear(); + + // Read the direct blocks (cant use for( : )) + auto* buffer = new uint8_t[m_volume -> block_size]; + for (int i = 0; i < 12; ++i) { + uint32_t block_pointer = m_inode.block_pointers[i]; + + // Invalid block + if(block_pointer == 0) + break; + + // Parse the block + m_volume->read_block(block_pointer, buffer); + parse_entries(buffer); + } + + // Single Indirect + parse_indirect(1, m_inode.l1_indirect, buffer); + + // Double Indirect + parse_indirect(2, m_inode.l2_indirect, buffer); + + // Triple Indirect + parse_indirect(3, m_inode.l3_indirect, buffer); + + delete[] buffer; +} + +/** + * @brief Create a new file in the directory + * + * @param name The name of the file to create + * @return The new file object or null if it could not be created + */ +File *Ext2Directory::create_file(string const &name) { + // TODO + return nullptr; +} + +/** + * @brief Delete a file from the subdirectory + * + * @param name The name of the file to delete + */ +void Ext2Directory::remove_file(string const &name) { + Directory::remove_file(name); +} + +/** + * @brief Create a new directory in the directory + * + * @param name The name of the directory to create + * @return The new directory object or null if it could not be created + */ +Directory *Ext2Directory::create_subdirectory(string const &name) { + return Directory::create_subdirectory(name); +} + +/** + * @brief Remove a directory entry from the directory + * + * @param name The name of the entry to remove + */ +void Ext2Directory::remove_subdirectory(string const &name) { + Directory::remove_subdirectory(name); +} + +Ext2Directory::~Ext2Directory() = default; + + +Ext2FileSystem::Ext2FileSystem(Disk *disk, uint32_t partition_offset) +: m_volume(disk, partition_offset) +{ + + // Create the root directory + m_root_directory = new Ext2Directory(&m_volume, 2, "/"); + m_root_directory -> read_from_disk(); + +} + +Ext2FileSystem::~Ext2FileSystem(){ + delete m_root_directory; +}; \ No newline at end of file diff --git a/kernel/src/filesystem/fat32.cpp b/kernel/src/filesystem/fat32.cpp index f88178f3..6816f1be 100644 --- a/kernel/src/filesystem/fat32.cpp +++ b/kernel/src/filesystem/fat32.cpp @@ -886,7 +886,11 @@ File* Fat32Directory::create_file(const string& name) return file; } - +/** + * @brief Delete a file from the subdirectory + * + * @param name The name of the file to delete + */ void Fat32Directory::remove_file(const string& name) { // Find the file if it exists diff --git a/kernel/src/filesystem/partition/msdos.cpp b/kernel/src/filesystem/partition/msdos.cpp index 3b87bc03..7b67f15c 100644 --- a/kernel/src/filesystem/partition/msdos.cpp +++ b/kernel/src/filesystem/partition/msdos.cpp @@ -53,6 +53,7 @@ void MSDOSPartition::mount_partitions(Disk* hd) { case PartitionType::LINUX_EXT2: Logger::Out() << "EXT2 partition\n"; + vfs -> mount_filesystem(new ext2::Ext2FileSystem(hd, entry.start_LBA)); break; default: diff --git a/kernel/src/kernel.cpp b/kernel/src/kernel.cpp index cc341902..29bdf79f 100644 --- a/kernel/src/kernel.cpp +++ b/kernel/src/kernel.cpp @@ -71,14 +71,21 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic driver_manager.activate_drivers(); // FS Tests - Directory* test = vfs.create_directory("/test/sub/"); - Logger::DEBUG() << "Created directory: " << test->name() << "\n"; - - Directory* test2 = vfs.open_directory("/test/"); - for(auto& folder : test2 -> subdirectories()) - Logger::DEBUG() << " " << folder -> name() << "\n"; - - Logger::HEADER() << "Stage {4}: System Finalisation\n"; + Directory* root = vfs.open_directory("/test"); + ASSERT(root != nullptr, "Root directory is null\n"); + for (auto& file : root->files()) + { + Logger::DEBUG() << "File: " << file->name() << "\n"; + Logger::DEBUG() << "Size: " << file->size() << "\n"; + } + for (auto& directory : root->subdirectories()) + { + Logger::DEBUG() << "Directory: " << directory->name() << "\n"; + Logger::DEBUG() << "Size: " << directory->size() << "\n"; + } + + + Logger::HEADER() << "Stage {4}: System Finalisation\n"; Scheduler scheduler(multiboot); SyscallManager syscalls; console.finish(); @@ -91,30 +98,20 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic // TODO: // - EXT2 Tests: -// - [ ] Read subdirectories contents -// - [ ] Read long path subdirectories contents +// - [x] Read subdirectories contents +// - [ ] Read files +// - [ ] Write files // - [ ] Create subdirectories -// - [ ] Create long path subdirectories -// - [ ] Delete subdirectories (need to add ability to free clusters first -// - [ ] Delete long path subdirectories +// - [ ] Delete subdirectories // - [ ] Rename directory // - [ ] Rename file -// - [ ] Rename lfn directory -// - [ ] Rename lfn file -// - [ ] Read files -// - [ ] Read large files -// - [ ] Write files -// - [ ] Write large files // - [ ] Create files // - [ ] Delete files -// - [ ] Read long path files -// - [ ] Create long path files -// - [ ] Delete long path files // - [ ] Create files on a different mount point // - [ ] Delete files on a different mount point // - [ ] Read directories on a different mount point // - [ ] Create directories on a different mount point -// - [ ] Stress test the filesystem: 1000s of files in a directory, long nested directories, long path files, etc +// - [ ] Stress test the filesystem: 1000s of files in a directory, long nested directories, long path files, large file r/w // - Fix tabs (mac mess up) diff --git a/toolchain/run_gdb.sh b/toolchain/run_gdb.sh index 15b53334..6f2367b6 100755 --- a/toolchain/run_gdb.sh +++ b/toolchain/run_gdb.sh @@ -2,50 +2,26 @@ SCRIPTDIR=$(dirname "$BASH_SOURCE") source $SCRIPTDIR/MaxOS.sh -IP=localhost -IN_WSL=0 -if command -v wslpath >/dev/null; then - msg "WSL detected." +msg "QEMU GDB Runner Started" - # Get the ip from ipconfig.exe - LOCAL_IP=${LOCAL_IP:-`ipconfig.exe | grep -im1 'IPv4 Address' | cut -d ':' -f2`} +while true; do - echo "WSL IP is: ${LOCAL_IP}" - IP=${LOCAL_IP} + msg "Waiting for GDB to start" + while true; do + GDB_PID=$(pgrep -fx '/usr/bin/gdb.*') + if [[ -n "$GDB_PID" ]]; then + msg "GDB Started with PID $GDB_PID" + tmux send-keys -t 0 "make install gdb" C-m + break + fi + sleep 0.2 + done - # Strip the carriage return - IP=${IP%$'\r'} -fi + msg "Waiting for debug session to end" + while kill -0 "$GDB_PID" 2>/dev/null; do + sleep 0.2 + done + taskkill.exe /IM "qemu-system-x86_64.exe" /F +done -# Make the GDB .init file -cat > ~/.gdbinit < Date: Sun, 20 Jul 2025 21:38:34 +1200 Subject: [PATCH 20/38] Ext2 File Reading --- kernel/include/filesystem/ext2.h | 26 ++++--- kernel/src/filesystem/ext2.cpp | 125 +++++++++++++++++++++++++++++-- kernel/src/kernel.cpp | 23 +++--- 3 files changed, 143 insertions(+), 31 deletions(-) diff --git a/kernel/include/filesystem/ext2.h b/kernel/include/filesystem/ext2.h index 5569367b..9256a3c3 100644 --- a/kernel/include/filesystem/ext2.h +++ b/kernel/include/filesystem/ext2.h @@ -9,6 +9,7 @@ #include #include #include +#include namespace MaxOS { namespace filesystem { @@ -227,6 +228,8 @@ namespace MaxOS { uint32_t blocks_per_inode_table; uint32_t sectors_per_inode_table; + common::Spinlock ext2_lock; + void read_block(uint32_t block_num, uint8_t* buffer); inode_t read_inode(uint32_t inode_num); block_group_descriptor_t read_block_group(uint32_t group_num); @@ -237,18 +240,21 @@ namespace MaxOS { * @brief Handles the file operations on the ext2 filesystem */ class Ext2File final : public File { - private: - Ext2Volume* m_volume; - uint32_t m_inode_number; - inode_t m_inode; + private: + Ext2Volume* m_volume; + uint32_t m_inode_number; + inode_t m_inode; - public: - Ext2File(Ext2Volume* volume, uint32_t inode, const string& name); - ~Ext2File() final; + common::Vector m_block_pointers; + void parse_indirect(uint32_t level, uint32_t block, uint8_t* buffer); + + public: + Ext2File(Ext2Volume* volume, uint32_t inode, const string& name); + ~Ext2File() final; - void write(const uint8_t* data, size_t amount) final; - void read(uint8_t* data, size_t amount) final; - void flush() final; + void write(const uint8_t* data, size_t amount) final; + void read(uint8_t* data, size_t amount) final; + void flush() final; }; /** diff --git a/kernel/src/filesystem/ext2.cpp b/kernel/src/filesystem/ext2.cpp index bc7fed90..c18bcd4d 100644 --- a/kernel/src/filesystem/ext2.cpp +++ b/kernel/src/filesystem/ext2.cpp @@ -14,6 +14,8 @@ Ext2Volume::Ext2Volume(drivers::disk::Disk *disk, lba_t partition_offset) partition_offset(partition_offset) { + ext2_lock.unlock(); + // Read superblock uint8_t buffer[1024]; disk->read(partition_offset + 2, buffer, 512); @@ -128,7 +130,21 @@ Ext2File::Ext2File(Ext2Volume *volume, uint32_t inode, string const &name) m_inode_number(inode), m_inode() { + + // Set up the base information m_name = name; + m_inode = m_volume ->read_inode(m_inode_number); + m_size = (size_t)m_inode.size_upper | m_inode.size_lower; + + // Read the block pointers + for (uint32_t direct_pointer = 0; direct_pointer < 12; ++direct_pointer) + m_block_pointers.push_back(m_inode.block_pointers[direct_pointer]); + + auto* buffer = new uint8_t[m_volume -> block_size]; + parse_indirect(1, m_inode.l1_indirect, buffer); + parse_indirect(2, m_inode.l2_indirect, buffer); + parse_indirect(3, m_inode.l3_indirect, buffer); + delete[] buffer; } /** @@ -138,6 +154,7 @@ Ext2File::Ext2File(Ext2Volume *volume, uint32_t inode, string const &name) * @param amount The amount of data to write */ void Ext2File::write(uint8_t const *data, size_t amount) { + File::write(data, amount); } @@ -147,8 +164,65 @@ void Ext2File::write(uint8_t const *data, size_t amount) { * @param data The byte buffer to read into * @param amount The amount of data to read */ -void Ext2File::read(uint8_t *data, size_t amount) { - File::read(data, amount); +void Ext2File::read(uint8_t* data, size_t amount) { + + // Nothing to read + if(m_size == 0 || amount == 0) + return; + + // Prepare for reading + m_volume -> ext2_lock.lock(); + auto* buffer = new uint8_t[m_volume -> block_size]; + + // Force bounds + if(m_offset + amount > m_size) + amount = m_size - m_offset; + + // Convert bytes to blocks + uint32_t block_start = m_offset / m_volume -> block_size; + uint32_t block_offset = m_offset % m_volume -> block_size; + + // Read each block + size_t current_block = block_start; + for (size_t remaining = amount; remaining > 0; remaining -= m_volume -> block_size) { + + // Read the block + m_volume -> read_block(m_block_pointers[current_block], buffer); + + // Data may be part-way through the first block + if(current_block == block_start){ + + size_t block_remaining = m_volume -> block_size - block_offset; + + // Read ends partway through block + if(amount < block_remaining){ + memcpy(data, buffer + block_offset, amount); + break; + } + + // Copy the entire rest of the block + memcpy(data, buffer + block_offset, block_remaining); + remaining += block_offset; + continue; + } + + size_t block_remaining = amount - remaining; + + // Last part of the data is part-way through the block + if(remaining < m_volume -> block_size){ + memcpy(data, buffer + block_remaining, remaining); + break; + } + + // Copy the entire block + memcpy(data, buffer + block_remaining, m_volume -> block_size); + } + + // Clean up + m_offset += amount; + m_volume -> ext2_lock.unlock(); + delete[] buffer; + } /** @@ -158,6 +232,42 @@ void Ext2File::flush() { File::flush(); } +/** + * @brief Caches the indirect layers block pointers + * + * @param level Recursion level + * @param block The block number to parse from + * @param buffer Buffer to read into + */ +void Ext2File::parse_indirect(uint32_t level, uint32_t block, uint8_t *buffer) { + + // Invalid + if(block == 0) + return; + + // Read the block + m_volume -> read_block(block, buffer); + auto* pointers = (uint32_t*)buffer; + + // Parse the pointers + for (size_t i = 0; i < m_volume->pointers_per_block; ++i) { + uint32_t pointer = pointers[i]; + + // Invaild + if(pointer == 0) + break; + + // Has indirect sub entries + if(level > 1){ + parse_indirect(level - 1, pointer, buffer); + continue; + } + + // Parse the entry + m_block_pointers.push_back(pointer); + } +} + Ext2File::~Ext2File() = default; @@ -225,7 +335,7 @@ void Ext2Directory::parse_indirect(uint32_t level, uint32_t block, uint8_t* buff return; // Read the block - m_volume ->read_block(block, buffer); + m_volume -> read_block(block, buffer); auto* pointers = (uint32_t*)buffer; // Parse the pointers @@ -253,6 +363,8 @@ void Ext2Directory::parse_indirect(uint32_t level, uint32_t block, uint8_t* buff */ void Ext2Directory::read_from_disk() { + m_volume -> ext2_lock.lock(); + // Read the inode m_inode = m_volume ->read_inode(m_inode_number); @@ -279,15 +391,12 @@ void Ext2Directory::read_from_disk() { parse_entries(buffer); } - // Single Indirect + // Indirect blocks parse_indirect(1, m_inode.l1_indirect, buffer); - - // Double Indirect parse_indirect(2, m_inode.l2_indirect, buffer); - - // Triple Indirect parse_indirect(3, m_inode.l3_indirect, buffer); + m_volume -> ext2_lock.unlock(); delete[] buffer; } diff --git a/kernel/src/kernel.cpp b/kernel/src/kernel.cpp index 29bdf79f..437e6d26 100644 --- a/kernel/src/kernel.cpp +++ b/kernel/src/kernel.cpp @@ -70,20 +70,17 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic driver_manager.initialise_drivers(); driver_manager.activate_drivers(); - // FS Tests - Directory* root = vfs.open_directory("/test"); - ASSERT(root != nullptr, "Root directory is null\n"); - for (auto& file : root->files()) - { - Logger::DEBUG() << "File: " << file->name() << "\n"; - Logger::DEBUG() << "Size: " << file->size() << "\n"; - } - for (auto& directory : root->subdirectories()) - { - Logger::DEBUG() << "Directory: " << directory->name() << "\n"; - Logger::DEBUG() << "Size: " << directory->size() << "\n"; - } + // FS Tests + File* grub_cfg = vfs.open_file("/test/a.txt"); + Logger::DEBUG() << "Opened file: " << grub_cfg->name() << "\n"; +// string test_data = "Hello World!"; +// grub_cfg -> write((uint8_t*)test_data.c_str(), test_data.length()); + + grub_cfg ->seek(SeekType::SET, 0); + uint8_t buffer[100]; + grub_cfg ->read(buffer, 100); + Logger::DEBUG() << (char*)buffer << "\n"; Logger::HEADER() << "Stage {4}: System Finalisation\n"; Scheduler scheduler(multiboot); From 1e6ddc017a0a655ec46528ccc9783e8575c676d2 Mon Sep 17 00:00:00 2001 From: Max Tyson <98maxt98@gmail.com> Date: Tue, 22 Jul 2025 11:58:31 +1200 Subject: [PATCH 21/38] Ext2 File Writing --- filesystem/test/a.txt | 2 +- kernel/include/drivers/clock/clock.h | 4 + kernel/include/filesystem/ext2.h | 8 +- kernel/src/drivers/clock/clock.cpp | 8 +- kernel/src/filesystem/ext2.cpp | 121 +++++++++++++++++++++++++-- kernel/src/kernel.cpp | 4 +- toolchain/copy_filesystem.sh | 5 +- 7 files changed, 137 insertions(+), 15 deletions(-) diff --git a/filesystem/test/a.txt b/filesystem/test/a.txt index f0bdd772..836692bf 100755 --- a/filesystem/test/a.txt +++ b/filesystem/test/a.txt @@ -1 +1 @@ -Hello World!OR A TESTHello World!EXTRA DATA FOR A TEST \ No newline at end of file +Hello World!ING DATA HERE WE FINNA TO WRITE SOON \ No newline at end of file diff --git a/kernel/include/drivers/clock/clock.h b/kernel/include/drivers/clock/clock.h index c95e69bc..7785f01c 100644 --- a/kernel/include/drivers/clock/clock.h +++ b/kernel/include/drivers/clock/clock.h @@ -151,6 +151,8 @@ namespace MaxOS { uint8_t read_hardware_clock(uint8_t address); [[nodiscard]] uint8_t binary_representation(uint8_t number) const; + inline static Clock* s_active_clock = nullptr; + public: Clock(hardwarecommunication::AdvancedProgrammableInterruptController* apic, uint16_t time_between_events = 10); ~Clock(); @@ -165,6 +167,8 @@ namespace MaxOS { string vendor_name() final; string device_name() final; + + static Clock* active_clock(); }; } diff --git a/kernel/include/filesystem/ext2.h b/kernel/include/filesystem/ext2.h index 9256a3c3..cb839ca7 100644 --- a/kernel/include/filesystem/ext2.h +++ b/kernel/include/filesystem/ext2.h @@ -10,6 +10,7 @@ #include #include #include +#include namespace MaxOS { namespace filesystem { @@ -230,8 +231,11 @@ namespace MaxOS { common::Spinlock ext2_lock; - void read_block(uint32_t block_num, uint8_t* buffer); - inode_t read_inode(uint32_t inode_num); + void write_block(uint32_t block_num, uint8_t* buffer); + void write_inode(uint32_t inode_num, inode_t* inode); + + void read_block(uint32_t block_num, uint8_t* buffer) const; + inode_t read_inode(uint32_t inode_num) const; block_group_descriptor_t read_block_group(uint32_t group_num); }; diff --git a/kernel/src/drivers/clock/clock.cpp b/kernel/src/drivers/clock/clock.cpp index e3add5c8..ba84e142 100644 --- a/kernel/src/drivers/clock/clock.cpp +++ b/kernel/src/drivers/clock/clock.cpp @@ -111,7 +111,7 @@ uint8_t Clock::binary_representation(uint8_t number) const { if(m_binary) return number; - // Convert to the binary represnation + // Convert to the binary representation return ((number / 16) * 10) + (number & 0x0f); } @@ -121,6 +121,8 @@ uint8_t Clock::binary_representation(uint8_t number) const { */ void Clock::activate() { + s_active_clock = this; + // Get the stats from the clock uint8_t status = read_hardware_clock(0xB); @@ -226,6 +228,10 @@ common::Time Clock::get_time() { return time; } +Clock *Clock::active_clock() { + return s_active_clock; +} + TimeEvent::TimeEvent(Time* time) :Event(ClockEvents::TIME), diff --git a/kernel/src/filesystem/ext2.cpp b/kernel/src/filesystem/ext2.cpp index c18bcd4d..80548f0b 100644 --- a/kernel/src/filesystem/ext2.cpp +++ b/kernel/src/filesystem/ext2.cpp @@ -8,6 +8,7 @@ using namespace MaxOS::filesystem; using namespace MaxOS::filesystem::ext2; using namespace MaxOS::drivers; using namespace MaxOS::drivers::disk; +using namespace MaxOS::drivers::clock; Ext2Volume::Ext2Volume(drivers::disk::Disk *disk, lba_t partition_offset) : disk(disk), @@ -60,13 +61,55 @@ Ext2Volume::Ext2Volume(drivers::disk::Disk *disk, lba_t partition_offset) Ext2Volume::~Ext2Volume() = default; +/** + * @breif Write a single block from a buffer into onto the disk + * + * @param block_num The block to update + * @param buffer The buffer to read from + */ +void Ext2Volume::write_block(uint32_t block_num, uint8_t *buffer) { + + // Read each sector of the block + for (size_t i = 0; i < sectors_per_block; ++i) + disk->write(partition_offset + block_num * sectors_per_block + i, buffer + i * 512, 512); + +}; + +/** + * @brief Write an inode to the filesystem + * + * @param inode_num The inode index + * @param inode The inode to read from + */ +void Ext2Volume::write_inode(uint32_t inode_num, inode_t* inode) { + + // Locate the inode + uint32_t group = (inode_num - 1) / superblock.inodes_per_group; + uint32_t index = (inode_num - 1) % superblock.inodes_per_group; + + // Locate the block + uint32_t inode_table = block_groups[group] -> inode_table_address; + uint32_t offset = index * superblock.inode_size; + uint32_t block = offset / block_size; + uint32_t in_block_offset = offset % block_size; + + // Read the inode + auto* buffer = new uint8_t[block_size]; + read_block(inode_table + block, buffer); + memcpy(buffer + in_block_offset, inode, sizeof(inode_t)); + + // Modify the block + write_block(inode_table + block, buffer); + delete[] buffer; +} + /** * @breif Reads a single block from the disk into a buffer * * @param block_num The block to read * @param buffer The buffer to read into */ -void Ext2Volume::read_block(uint32_t block_num, uint8_t *buffer) { +void Ext2Volume::read_block(uint32_t block_num, uint8_t *buffer) const { // Read each sector of the block for (size_t i = 0; i < sectors_per_block; ++i) @@ -79,7 +122,7 @@ void Ext2Volume::read_block(uint32_t block_num, uint8_t *buffer) { * * @param inode_num The inode index */ -inode_t Ext2Volume::read_inode(uint32_t inode_num) { +inode_t Ext2Volume::read_inode(uint32_t inode_num) const { inode_t inode; @@ -123,7 +166,7 @@ block_group_descriptor_t Ext2Volume::read_block_group(uint32_t group_num) { delete[] buffer; return descriptor; -}; +} Ext2File::Ext2File(Ext2Volume *volume, uint32_t inode, string const &name) : m_volume(volume), @@ -155,7 +198,76 @@ Ext2File::Ext2File(Ext2Volume *volume, uint32_t inode, string const &name) */ void Ext2File::write(uint8_t const *data, size_t amount) { - File::write(data, amount); + // Nothing to write + if(m_size == 0) + return; + + // Prepare for writing + m_volume -> ext2_lock.lock(); + auto* buffer = new uint8_t[m_volume -> block_size]; + + // Force bounds + if(m_offset + amount > m_size) + amount = m_size - m_offset; + + // Convert bytes to blocks + uint32_t block_start = m_offset / m_volume -> block_size; + uint32_t block_offset = m_offset % m_volume -> block_size; + + // TODO: Expand the file + + // Save the updated metadata + m_inode.size_lower = (uint32_t)(m_size & 0xFFFFFFFFULL); + m_inode.size_upper = (uint32_t)(m_size >> 32); + m_inode.last_modification_time = time_to_epoch(Clock::active_clock() -> get_time()); + m_volume -> write_inode(m_inode_number, &m_inode); + + // Read each block + size_t current_block = block_start; + for (size_t remaining = amount; remaining > 0; remaining -= m_volume -> block_size) { + + // Read the block + uint32_t block = m_block_pointers[current_block]; + m_volume -> read_block(block, buffer); + current_block++; + + // Data may be part-way through the first block + if((current_block - 1) == block_start){ + + size_t block_remaining = m_volume -> block_size - block_offset; + + // Replacement ends partway through block + if(amount < block_remaining){ + memcpy(buffer + block_offset, data, amount); + m_volume ->write_block(block, buffer); + break; + } + + // Replace the entire rest of the block + memcpy(buffer + block_offset, data, block_remaining); + m_volume ->write_block(block, buffer); + remaining += block_offset; + continue; + } + + size_t block_remaining = amount - remaining; + + // Last part of the data to be replaced is part-way through the block + if(remaining < m_volume -> block_size){ + memcpy(buffer + block_remaining, data, remaining); + m_volume ->write_block(block, buffer); + break; + } + + // Replace the entire block + memcpy(buffer + block_remaining, data, m_volume -> block_size); + m_volume ->write_block(block, buffer); + } + + // Clean up + m_offset += amount; + m_volume -> ext2_lock.unlock(); + delete[] buffer; } /** @@ -222,7 +334,6 @@ void Ext2File::read(uint8_t* data, size_t amount) { m_offset += amount; m_volume -> ext2_lock.unlock(); delete[] buffer; - } /** diff --git a/kernel/src/kernel.cpp b/kernel/src/kernel.cpp index 437e6d26..860894f3 100644 --- a/kernel/src/kernel.cpp +++ b/kernel/src/kernel.cpp @@ -74,8 +74,8 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic File* grub_cfg = vfs.open_file("/test/a.txt"); Logger::DEBUG() << "Opened file: " << grub_cfg->name() << "\n"; -// string test_data = "Hello World!"; -// grub_cfg -> write((uint8_t*)test_data.c_str(), test_data.length()); + string test_data = "Hello World!"; + grub_cfg -> write((uint8_t*)test_data.c_str(), test_data.length()); grub_cfg ->seek(SeekType::SET, 0); uint8_t buffer[100]; diff --git a/toolchain/copy_filesystem.sh b/toolchain/copy_filesystem.sh index 26fcc07c..15b6e20d 100755 --- a/toolchain/copy_filesystem.sh +++ b/toolchain/copy_filesystem.sh @@ -19,8 +19,6 @@ while [ "$#" -gt "0" ]; do esac done - - if [ "$REVERSE" -ne 1 ]; then msg "Copying boot files to image" else @@ -36,7 +34,6 @@ fi DESTINATION="$MOUNT_DIR/MaxOS_img_1" - : "${USE_ISO:=0}" # Produce an ISO? default to no if [ "$USE_ISO" -eq 1 ]; then @@ -68,7 +65,7 @@ if [ "$REVERSE" -ne 1 ]; then sudo rsync --no-o --no-g -a --delete -c "$SCRIPTDIR/../filesystem/" "$MOUNT_DIR/MaxOS_img_1/" else msg "Copying changes on image to local filesystem" - sudo rsync --chown=$(id -un):$(id -gn) -a --delete -c "$MOUNT_DIR/MaxOS_img_1/" "$SCRIPTDIR/../filesystem/" + sudo rsync --itemize-changes --chown=$(id -un):$(id -gn) -a --delete -c "$MOUNT_DIR/MaxOS_img_1/" "$SCRIPTDIR/../filesystem/" fi # Create the iso From 9c9ec654c3abb3fa7dc67babb7a73c89e9c5226d Mon Sep 17 00:00:00 2001 From: Max Tyson <98maxt98@gmail.com> Date: Thu, 24 Jul 2025 21:34:41 +1200 Subject: [PATCH 22/38] Ext2 File Expanding --- docs/Notes.md | 4 + filesystem/test/a.txt | 2 +- kernel/include/common/vector.h | 117 +++++++- kernel/include/filesystem/ext2.h | 57 ++-- kernel/src/common/string.cpp | 2 +- kernel/src/drivers/console/vesaboot.cpp | 9 +- kernel/src/filesystem/ext2.cpp | 361 +++++++++++++++++++----- kernel/src/kernel.cpp | 12 +- kernel/src/memory/memorymanagement.cpp | 6 +- 9 files changed, 451 insertions(+), 119 deletions(-) diff --git a/docs/Notes.md b/docs/Notes.md index 9d29d110..ff994afc 100644 --- a/docs/Notes.md +++ b/docs/Notes.md @@ -42,6 +42,10 @@ These are my notes relative to various parts of the OS +NOTE TO SELF: FUNC AS VARIABLE: auto func_name = [&](uint32_t p) { + // Code +}; + # Hardware Communication Here are some notes on how the communication with hardware works, this is used for the keyboard and mouse communication and setting up other devices, e.g. GPU diff --git a/filesystem/test/a.txt b/filesystem/test/a.txt index 836692bf..2f650f9d 100755 --- a/filesystem/test/a.txt +++ b/filesystem/test/a.txt @@ -1 +1 @@ -Hello World!ING DATA HERE WE FINNA TO WRITE SOON \ No newline at end of file +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ No newline at end of file diff --git a/kernel/include/common/vector.h b/kernel/include/common/vector.h index 16a6a042..a5c50570 100644 --- a/kernel/include/common/vector.h +++ b/kernel/include/common/vector.h @@ -47,9 +47,13 @@ namespace MaxOS{ Vector(); Vector(int Size, Type element); + Vector(const Vector& other); + Vector(Vector&& other); ~Vector(); Type& operator[](uint32_t index) const; + Vector& operator=(const Vector& other); + Vector& operator=(Vector&& other); [[nodiscard]] bool empty() const; [[nodiscard]] uint32_t size() const; @@ -72,7 +76,7 @@ namespace MaxOS{ void Iterate(void callback(Type&)); }; - ///______________________________________Implementation__________________________________________________ + ///______________________________________Implementation__________________________________________________ /** * @brief Constructor for Vector * @@ -96,12 +100,48 @@ namespace MaxOS{ // Allocate space for the array m_elements = new Type[size]; + m_capacity = size > 0 ? size : 1; + m_size = 0; // Push all the elements to the Vector for (int i = 0; i < size; ++i) push_back(element); } + /** + * @brief Copy constructor for Vector + * + * @tparam Type The type of data to be stored + * @param other The vector to copy from + */ + template Vector::Vector(const Vector& other) + : m_size(other.m_size), + m_capacity(other.m_capacity) + { + // Copy each element into a new array + m_elements = new Type[m_capacity]; + for (uint32_t i = 0; i < m_size; ++i) + m_elements[i] = other.m_elements[i]; + } + + /** + * @brief Move constructor for Vector + * + * @tparam Type The type of data to be stored + * @param other The vector to copy from + */ + template Vector::Vector(Vector &&other) + : m_elements(other.m_elements), + m_size(other.m_size), + m_capacity(other.m_capacity) + { + + // Clear the other Vector + other.m_elements = nullptr; + other.m_size = 0; + other.m_capacity = 0; + + } template Vector::~Vector() { @@ -117,6 +157,7 @@ namespace MaxOS{ */ template void Vector::increase_size() { + // Allocate more space for the array Type* new_elements = new Type[m_capacity * 2]; @@ -145,7 +186,7 @@ namespace MaxOS{ template Type &Vector::operator[](uint32_t index) const{ // If the index is in the Vector - if (index <= m_size) + if (index < m_size) return m_elements[index]; // Return the last element of the Vector @@ -153,6 +194,59 @@ namespace MaxOS{ } + /** + * @brief Assignment by copy, data is copied into a new buffer stored in this vector + * + * @tparam Type Type of the Vector + * @param other The vector to copy from + * @return This vector, with the copied elements + */ + template Vector& Vector::operator=(const Vector& other) { + + // Setting to itself? + if (this == &other) + return *this; + + // Create a new buffer to store the elements + delete[] m_elements; + m_elements = new Type[other.m_capacity]; + + // Copy data + m_size = other.m_size; + m_capacity = other.m_capacity; + for (uint32_t i = 0; i < m_size; ++i) + m_elements[i] = other.m_elements[i]; + + return *this; + } + + /** + * @brief Assignment by move, data is moved into the buffer stored in this vector and the other vector is cleared + * + * @tparam Type Type of the Vector + * @param other The vector to copy from + * @return This vector, with the copied elements + */ + template Vector& Vector::operator=(Vector&& other) noexcept { + + // Moving to itself? + if (this == &other) + return *this; + + // Move into this vector + delete[] m_elements; + m_elements = other.m_elements; + m_size = other.m_size; + m_capacity = other.m_capacity; + + // Remove from other vector + other.m_elements = nullptr; + other.m_size = 0; + other.m_capacity = 0; + + return *this; + } + /** * @brief Returns the number of elements in the Vector * @@ -251,9 +345,8 @@ namespace MaxOS{ template typename Vector::iterator Vector::push_front(Type element) { // Check if we need to allocate more space for the array - if(m_size == m_capacity){ + if(m_size == m_capacity) increase_size(); - } // Move all elements one index to the right for (iterator i = end(); i > begin(); --i) @@ -274,16 +367,16 @@ namespace MaxOS{ */ template void Vector::pop_front() { - // Make sure the Vector is not empty - if (m_size == 0) - return; + // Make sure the Vector is not empty + if (m_size == 0) + return; - // Move all elements one index to the left - for (iterator i = begin(); i != end(); ++i) - *i = *(i + 1); + // Move all elements one index to the left + for (uint32_t i = 0; i < m_size - 1; ++i) + m_elements[i] = m_elements[i + 1]; - // Decrease the size of the Vector - --m_size; + // Decrease the size of the Vector + --m_size; } /** diff --git a/kernel/include/filesystem/ext2.h b/kernel/include/filesystem/ext2.h index cb839ca7..a1779402 100644 --- a/kernel/include/filesystem/ext2.h +++ b/kernel/include/filesystem/ext2.h @@ -208,35 +208,46 @@ namespace MaxOS { class Ext2Volume { - public: - Ext2Volume(drivers::disk::Disk* disk, lba_t partition_offset); - ~Ext2Volume(); + private: + common::Vector allocate_group_blocks(uint32_t block_group, uint32_t amount); + void write_back_block_groups(); + void write_back_superblock(); + + public: + Ext2Volume(drivers::disk::Disk* disk, lba_t partition_offset); + ~Ext2Volume(); + + drivers::disk::Disk* disk; + lba_t partition_offset; + + superblock_t superblock; + block_group_descriptor_t** block_groups; - drivers::disk::Disk* disk; - lba_t partition_offset; + size_t block_size; + uint32_t block_group_descriptor_table; + uint32_t block_group_descriptor_table_size; + uint32_t total_block_groups; + size_t pointers_per_block; + uint32_t inodes_per_block; + uint32_t sectors_per_block; - superblock_t superblock; - block_group_descriptor_t** block_groups; + uint32_t blocks_per_inode_table; + uint32_t sectors_per_inode_table; - size_t block_size; - uint32_t block_group_descriptor_table; - uint32_t block_group_descriptor_table_size; - uint32_t total_block_groups; - size_t pointers_per_block; - uint32_t inodes_per_block; - uint32_t sectors_per_block; + common::Spinlock ext2_lock; - uint32_t blocks_per_inode_table; - uint32_t sectors_per_inode_table; + void write_block(uint32_t block_num, uint8_t* buffer); + void write_inode(uint32_t inode_num, inode_t* inode); - common::Spinlock ext2_lock; + void read_block(uint32_t block_num, uint8_t* buffer) const; + [[nodiscard]] inode_t read_inode(uint32_t inode_num) const; + block_group_descriptor_t read_block_group(uint32_t group_num); - void write_block(uint32_t block_num, uint8_t* buffer); - void write_inode(uint32_t inode_num, inode_t* inode); + uint32_t allocate_block(); + common::Vector allocate_blocks(uint32_t amount); + uint32_t bytes_to_blocks(size_t bytes) const; - void read_block(uint32_t block_num, uint8_t* buffer) const; - inode_t read_inode(uint32_t inode_num) const; - block_group_descriptor_t read_block_group(uint32_t group_num); + // TODO: free blocks }; /** @@ -251,6 +262,8 @@ namespace MaxOS { common::Vector m_block_pointers; void parse_indirect(uint32_t level, uint32_t block, uint8_t* buffer); + void write_indirect(uint32_t level, uint32_t& block, size_t& index); + void store_blocks(const common::Vector& blocks); public: Ext2File(Ext2Volume* volume, uint32_t inode, const string& name); diff --git a/kernel/src/common/string.cpp b/kernel/src/common/string.cpp index 799b0cd9..eddf6307 100644 --- a/kernel/src/common/string.cpp +++ b/kernel/src/common/string.cpp @@ -475,7 +475,7 @@ String String::operator*(int times) const { // The repeated string String repeated; - repeated.m_length *= times; + repeated.m_length = m_length * times; repeated.allocate_self(); // Copy the string diff --git a/kernel/src/drivers/console/vesaboot.cpp b/kernel/src/drivers/console/vesaboot.cpp index 31fa519d..69948d2c 100644 --- a/kernel/src/drivers/console/vesaboot.cpp +++ b/kernel/src/drivers/console/vesaboot.cpp @@ -98,17 +98,14 @@ void VESABootConsole::put_character(uint16_t x, uint16_t y, char c) { } // Get the colour from the ANSI code - auto* colour = new Colour(ansi_code); + const Colour colour(ansi_code); // Set the colour bool foreground = ansi_code[4] == '3'; if (foreground) - m_foreground_color = colour->to_console_colour(); + m_foreground_color = colour.to_console_colour(); else - m_background_color = colour->to_console_colour(); - - // Delete the colour - delete colour; + m_background_color = colour.to_console_colour(); } diff --git a/kernel/src/filesystem/ext2.cpp b/kernel/src/filesystem/ext2.cpp index 80548f0b..b14080d0 100644 --- a/kernel/src/filesystem/ext2.cpp +++ b/kernel/src/filesystem/ext2.cpp @@ -5,6 +5,7 @@ using namespace MaxOS; using namespace MaxOS::filesystem; +using namespace MaxOS::common; using namespace MaxOS::filesystem::ext2; using namespace MaxOS::drivers; using namespace MaxOS::drivers::disk; @@ -168,6 +169,159 @@ block_group_descriptor_t Ext2Volume::read_block_group(uint32_t group_num) { return descriptor; } +/** + * @brief Allocates a single block to be used by an inode + * + * @return The new block number or 0 if the allocation failed + */ +uint32_t Ext2Volume::allocate_block() { + + return allocate_blocks(1)[0]; +} + +/** + * @brief Allocates a set of blocks to be used by an inode + * + * @param amount The amount of blocks to allocate + * @return A list of the allocated blocks or [0] if the allocation failed + */ +Vector Ext2Volume::allocate_blocks(uint32_t amount) { + + Logger::DEBUG() << "ALLOCATING BLOCKS: 0x" << (uint64_t)amount << "\n"; + + // No blocks to allocate + if (!amount) + return {1,0}; + + // Find the block group with enough free blocks + block_group_descriptor_t* block_group = block_groups[0]; + for (uint32_t bg_index = 0; bg_index < total_block_groups; block_group = block_groups[++bg_index]) + if (block_group -> free_blocks >= amount) + return allocate_group_blocks(bg_index, amount); + + // No block group can contain the block so split across multiple + Vector result {}; + while (amount > 0){ + + // Find the block group with most free blocks + block_group = block_groups[0]; + uint32_t bg_index = 0; + for (; bg_index < total_block_groups; ++bg_index) + if (block_groups[bg_index] -> free_blocks > block_group -> free_blocks) + block_group = block_groups[bg_index]; + + // No space + if(block_group -> free_blocks == 0) + return {1, 0}; + + // Allocate the remaining blocks + auto allocated = allocate_group_blocks(bg_index, 1); + amount -= allocated.size(); + for (auto block : allocated) + result.push_back(block); + } + + return result; +} + +/** + * @brief Allocate a certain amount of blocks in a block group + * + * @param block_group The block group where the allocation is performed + * @param amount The amount of blocks to allocate + * @return The block numbers allocated + */ +common::Vector Ext2Volume::allocate_group_blocks(uint32_t block_group, uint32_t amount) { + + // Ensure enough space + block_group_descriptor_t* descriptor = block_groups[block_group]; + if (amount > descriptor -> free_blocks) + return {1, 0}; + + // Prepare + Vector result {}; + auto zeros = new uint8_t [block_size]; + memset(zeros, 0, block_size); + + // Read bitmap + auto bitmap = new uint8_t[block_size]; + read_block(descriptor -> block_usage_bitmap, bitmap); + + // Allocate the blocks + for (uint32_t i = 0; i < superblock.blocks_per_group; ++i) { + + // Block is already used + if((bitmap[i / 8] & (1u << (i % 8))) != 0) + continue; + + // Mark as used + descriptor -> free_blocks--; + superblock.unallocated_blocks--; + bitmap[i / 8] |= (uint8_t) (1u << (i % 8)); + + // Zero out data + uint32_t block = block_group * superblock.blocks_per_group + (block_size == 1024 ? 1 : 0) + i; + write_block(block, zeros); + result.push_back(block); + + // All done + amount--; + if(!amount) + break; + } + + // Save the changed metadata + write_block(descriptor -> block_usage_bitmap, bitmap); + write_back_block_groups(); + write_back_superblock(); + + delete[] bitmap; + delete[] zeros; + return result; +} + +/** + * @brief Save any changes of the block groups to the disk + */ +void Ext2Volume::write_back_block_groups() { + + // Store the block groups + uint32_t sectors_to_write = (block_group_descriptor_table_size + block_size - 1) / block_size * sectors_per_block; + auto* bg_buffer = new uint8_t[sectors_to_write * 512]; + for (uint32_t i = 0; i < total_block_groups; ++i) + memcpy(bg_buffer + i * sizeof(block_group_descriptor_t), block_groups[i], sizeof(block_group_descriptor_t)); + + // Write the block groups + for (uint32_t i = 0; i < sectors_to_write; ++i) + disk->write(partition_offset + block_group_descriptor_table * sectors_per_block + i, bg_buffer + i * 512, 512); + + delete[] bg_buffer; +} + +/** + * @brief Save the in memory superblock to the disk + */ +void Ext2Volume::write_back_superblock() { + + // Store superblock + uint8_t buffer[1024]; + memcpy(buffer, &superblock, sizeof(superblock_t)); + + // Write to disk + disk->write(partition_offset + 2, buffer, 512); + disk->write(partition_offset + 3, buffer + 512, 512); +} + +/** + * @brief How many blocks are needed to contain a set amount of bytes + * + * @param bytes Bytes needed + * @return The blocks required + */ +uint32_t Ext2Volume::bytes_to_blocks(size_t bytes) const { + return (bytes + block_size - 1) / block_size; +} + Ext2File::Ext2File(Ext2Volume *volume, uint32_t inode, string const &name) : m_volume(volume), m_inode_number(inode), @@ -181,7 +335,8 @@ Ext2File::Ext2File(Ext2Volume *volume, uint32_t inode, string const &name) // Read the block pointers for (uint32_t direct_pointer = 0; direct_pointer < 12; ++direct_pointer) - m_block_pointers.push_back(m_inode.block_pointers[direct_pointer]); + if(m_inode.block_pointers[direct_pointer]) + m_block_pointers.push_back(m_inode.block_pointers[direct_pointer]); auto* buffer = new uint8_t[m_volume -> block_size]; parse_indirect(1, m_inode.l1_indirect, buffer); @@ -204,64 +359,49 @@ void Ext2File::write(uint8_t const *data, size_t amount) { // Prepare for writing m_volume -> ext2_lock.lock(); - auto* buffer = new uint8_t[m_volume -> block_size]; + const uint32_t block_size = m_volume -> block_size; + auto* buffer = new uint8_t[block_size]; - // Force bounds - if(m_offset + amount > m_size) - amount = m_size - m_offset; + // Expand the file + if(m_offset + amount > m_size) { - // Convert bytes to blocks - uint32_t block_start = m_offset / m_volume -> block_size; - uint32_t block_offset = m_offset % m_volume -> block_size; + // Allocate new blocks + auto blocks = m_volume ->allocate_blocks(m_volume -> bytes_to_blocks((m_offset + amount) - m_size)); + ASSERT(blocks[0] != 0, "Failed to allocate new blocks for file"); - // TODO: Expand the file + // Save the changes + store_blocks(blocks); + m_size = (m_offset + amount); + + } // Save the updated metadata m_inode.size_lower = (uint32_t)(m_size & 0xFFFFFFFFULL); m_inode.size_upper = (uint32_t)(m_size >> 32); m_inode.last_modification_time = time_to_epoch(Clock::active_clock() -> get_time()); - m_volume -> write_inode(m_inode_number, &m_inode); + m_volume -> write_inode(m_inode_number, &m_inode); // - Also saves blocks - // Read each block + // Convert bytes to blocks + uint32_t block_start = m_offset / block_size; + uint32_t block_offset = m_offset % block_size; + + // Write each block size_t current_block = block_start; - for (size_t remaining = amount; remaining > 0; remaining -= m_volume -> block_size) { + size_t written = 0; + while (written < amount) { // Read the block - uint32_t block = m_block_pointers[current_block]; + uint32_t block = m_block_pointers[current_block++]; m_volume -> read_block(block, buffer); - current_block++; - - // Data may be part-way through the first block - if((current_block - 1) == block_start){ - size_t block_remaining = m_volume -> block_size - block_offset; - - // Replacement ends partway through block - if(amount < block_remaining){ - memcpy(buffer + block_offset, data, amount); - m_volume ->write_block(block, buffer); - break; - } + // Where in this block to start writing + size_t buffer_start = (current_block - 1 == block_start) ? block_offset : 0; + size_t writable = (amount - written < block_size - buffer_start) ? (amount - written) : (block_size - buffer_start); - // Replace the entire rest of the block - memcpy(buffer + block_offset, data, block_remaining); - m_volume ->write_block(block, buffer); - remaining += block_offset; - continue; - } - - size_t block_remaining = amount - remaining; - - // Last part of the data to be replaced is part-way through the block - if(remaining < m_volume -> block_size){ - memcpy(buffer + block_remaining, data, remaining); - m_volume ->write_block(block, buffer); - break; - } - - // Replace the entire block - memcpy(buffer + block_remaining, data, m_volume -> block_size); + // Update the block + memcpy(buffer + buffer_start, data + written, writable); m_volume ->write_block(block, buffer); + written += writable; } // Clean up @@ -284,50 +424,34 @@ void Ext2File::read(uint8_t* data, size_t amount) { // Prepare for reading m_volume -> ext2_lock.lock(); - auto* buffer = new uint8_t[m_volume -> block_size]; + const uint32_t block_size = m_volume -> block_size; + auto* buffer = new uint8_t[block_size]; // Force bounds if(m_offset + amount > m_size) amount = m_size - m_offset; // Convert bytes to blocks - uint32_t block_start = m_offset / m_volume -> block_size; - uint32_t block_offset = m_offset % m_volume -> block_size; + uint32_t block_start = m_offset / block_size; + uint32_t block_offset = m_offset % block_size; // Read each block size_t current_block = block_start; - for (size_t remaining = amount; remaining > 0; remaining -= m_volume -> block_size) { + size_t read = 0; + while (read < amount){ // Read the block - m_volume -> read_block(m_block_pointers[current_block], buffer); - - // Data may be part-way through the first block - if(current_block == block_start){ - - size_t block_remaining = m_volume -> block_size - block_offset; - - // Read ends partway through block - if(amount < block_remaining){ - memcpy(data, buffer + block_offset, amount); - break; - } - - // Copy the entire rest of the block - memcpy(data, buffer + block_offset, block_remaining); - remaining += block_offset; - continue; - } + uint32_t block = m_block_pointers[current_block++]; + m_volume -> read_block(block, buffer); - size_t block_remaining = amount - remaining; + // Where in this block to start reading + size_t buffer_start = (current_block - 1 == block_start) ? block_offset : 0; + size_t readable = (amount - read < block_size - buffer_start) ? (amount - read) : (block_size - buffer_start); - // Last part of the data is part-way through the block - if(remaining < m_volume -> block_size){ - memcpy(data, buffer + block_remaining, remaining); - break; - } + // Read the block + memcpy(data + read, buffer + buffer_start, readable); + read += readable; - // Copy the entire block - memcpy(data, buffer + block_remaining, m_volume -> block_size); } // Clean up @@ -379,6 +503,97 @@ void Ext2File::parse_indirect(uint32_t level, uint32_t block, uint8_t *buffer) { } } +/** + * @brief Writes the Cache to the indirect layer block pointers + * + * @param level Recursion level + * @param block The block number to parse from + * @param buffer Buffer to read into + * @param index Current entry to write + */ +void Ext2File::write_indirect(uint32_t level, uint32_t& block, size_t& index) { + + // Nothing left to write + size_t remaining = m_block_pointers.size() - index; + if (remaining == 0 || index >= m_block_pointers.size()) + return; + + // Level hasn't been set yet + if(block == 0) + block = m_volume -> allocate_block(); + + // Allocate a local buffer for this recursion level + auto* buffer = new uint8_t[m_volume->block_size]; + memset(buffer, 0, m_volume->block_size); + auto* pointers = (uint32_t*)buffer; + + // Write the pointers + for (size_t i = 0; i < m_volume->pointers_per_block; ++i) { + + // Invalid + if (index >= m_block_pointers.size()) + break; + + // Has indirect + if (level > 1) { + write_indirect(level - 1, pointers[i], index); + continue; + } + + // Save the pointer + pointers[i] = m_block_pointers[index++]; + } + + m_volume ->write_block(block, buffer); + delete[] buffer; +} + +/** + * @brief Saves the blocks to both the cached array and on disk inode + * + * @param blocks The blocks to save + */ +void Ext2File::store_blocks(const common::Vector& blocks) { + + Logger::DEBUG() << "STORING BLOCKS\n"; + + // Store in cache + for(auto block : blocks) + m_block_pointers.push_back(block); + + // Direct blocks + for (uint32_t i = 0; i < 12; ++i) + m_inode.block_pointers[i] = i < m_block_pointers.size() ? m_block_pointers[i] : 0; + + // No need to do any indirects + if(m_block_pointers.size() < 12) + return; + + // Setup Recursive blocks + size_t index = 12; + auto* buffer = new uint8_t[m_volume -> block_size]; + + // Write the blocks + uint32_t indirect_blocks[3] = { m_inode.l1_indirect, m_inode.l2_indirect, m_inode.l3_indirect }; + for (int i = 0; i < 3; ++i) { + + // Have to use temp because of packed field + uint32_t temp = indirect_blocks[i]; + write_indirect(i + 1, temp, index); + indirect_blocks[i] = temp; + } + + // Save the new blocks + m_inode.l1_indirect = indirect_blocks[0]; + m_inode.l2_indirect = indirect_blocks[1]; + m_inode.l3_indirect = indirect_blocks[2]; + + delete[] buffer; + + // NOTE: Blocks get allocated when writing indirects. This is then saved later in the write() function + +} + Ext2File::~Ext2File() = default; diff --git a/kernel/src/kernel.cpp b/kernel/src/kernel.cpp index 860894f3..7ca2afe4 100644 --- a/kernel/src/kernel.cpp +++ b/kernel/src/kernel.cpp @@ -74,8 +74,16 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic File* grub_cfg = vfs.open_file("/test/a.txt"); Logger::DEBUG() << "Opened file: " << grub_cfg->name() << "\n"; - string test_data = "Hello World!"; - grub_cfg -> write((uint8_t*)test_data.c_str(), test_data.length()); + uint32_t write = 12 * 700; + auto* test_data = new uint8_t[write]; + memset(test_data, (uint32_t)'a', write); + test_data[write - 1] = '\0'; + grub_cfg -> write(test_data, write); + + // DEBUG CMDS: + // watch *((char[32]*)0xffff800478025000), p *(MemoryChunk*)0xffff800478025000 - 'a' buffer memchunk + // watch *((char[32]*)0xffff800478028000), p *(MemoryChunk*)0xffff800478028000 - 'a' buffer memchunk -> next + // grub_cfg ->seek(SeekType::SET, 0); uint8_t buffer[100]; diff --git a/kernel/src/memory/memorymanagement.cpp b/kernel/src/memory/memorymanagement.cpp index aa715be2..7b566cda 100644 --- a/kernel/src/memory/memorymanagement.cpp +++ b/kernel/src/memory/memorymanagement.cpp @@ -113,7 +113,8 @@ void *MemoryManager::handle_malloc(size_t size) { // If there is not left over space to store extra chunks there is no need to split the chunk if(result -> size < size + sizeof(MemoryChunk) + 1) { result->allocated = true; - return (void *)(((size_t)result) + sizeof(MemoryChunk)); + void* p = (void *)(((size_t)result) + sizeof(MemoryChunk)); + return p; } // Split the chunk into: what was requested + free overflow space for future allocates @@ -343,7 +344,8 @@ void* operator new(size_t size) throw(){ void* operator new[](size_t size) throw(){ // Handle the memory allocation - return MaxOS::memory::MemoryManager::kmalloc(size); + void* p = MaxOS::memory::MemoryManager::kmalloc(size); + return p; } From a5c08056afe416ed584e85b8890c0a8d67507a93 Mon Sep 17 00:00:00 2001 From: Max Tyson <98maxt98@gmail.com> Date: Fri, 25 Jul 2025 16:00:46 +1200 Subject: [PATCH 23/38] String Memory Fixes * Optimisation --- filesystem/test/a.txt | 2 +- kernel/include/common/string.h | 4 ++ kernel/src/common/string.cpp | 66 +++++++++++++++---- kernel/src/filesystem/partition/msdos.cpp | 2 +- .../src/hardwarecommunication/interrupts.cpp | 2 +- kernel/src/kernel.cpp | 6 -- 6 files changed, 61 insertions(+), 21 deletions(-) diff --git a/filesystem/test/a.txt b/filesystem/test/a.txt index 2f650f9d..728a6822 100755 --- a/filesystem/test/a.txt +++ b/filesystem/test/a.txt @@ -1 +1 @@ -aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ No newline at end of file +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ No newline at end of file diff --git a/kernel/include/common/string.h b/kernel/include/common/string.h index d3fd9ecd..20da7721 100644 --- a/kernel/include/common/string.h +++ b/kernel/include/common/string.h @@ -20,6 +20,10 @@ namespace MaxOS { char* m_string = nullptr; int m_length = 0; // Does not include the null terminator + const static uint8_t s_small_storage = 0x99; + char m_small_string[s_small_storage]; + bool m_using_small = true; + [[nodiscard]] static int lex_value(String const &other) ; void allocate_self(); diff --git a/kernel/src/common/string.cpp b/kernel/src/common/string.cpp index eddf6307..2e8d4ff0 100644 --- a/kernel/src/common/string.cpp +++ b/kernel/src/common/string.cpp @@ -66,20 +66,60 @@ String::String(uint8_t const* string, int length) String::String(int value) { - //TODO + // Convert to a string + const char* str = itoa(10, value); + m_length = strlen(str); -} + // Create space to store + allocate_self(); + + // Store the string + for (int i = 0; i < m_length; i++) + m_string[i] = str[i]; + m_string[m_length] = '\0'; +} +/** + * @brief Constructs a string from a hex value (Excludes 0x____) + * @param value + */ String::String(uint64_t value) { - //TODO + + // Convert to a string + const char* str = htoa(value); + m_length = strlen(str); + + // Create space to store + allocate_self(); + + // Store the string + for (int i = 0; i < m_length; i++) + m_string[i] = str[i]; + m_string[m_length] = '\0'; } String::String(float value) { - //TODO -} + // Convert to a string + const char* str = ftoa(value); + m_length = strlen(str); + // Create space to store + allocate_self(); + + // Store the string + for (int i = 0; i < m_length; i++) + m_string[i] = str[i]; + m_string[m_length] = '\0'; + +} + +/** + * @brief Copy constructor for the string + * + * @param other String to copy from + */ String::String(String const &other) { copy(other); } @@ -88,7 +128,8 @@ String::String(String const &other) { String::~String() { // Free the memory - delete[] m_string; + if(!m_using_small) + delete[] m_string; } @@ -133,9 +174,13 @@ int String::lex_value(String const &string) { */ void String::allocate_self() { + // Clear the old buffer if in use + if(m_string && !m_using_small) + delete[] m_string; - // Create space for this string and the null terminator - m_string = new char[m_length + 1]; + // Try to use the small string buffer + m_using_small = m_length + 1 <= s_small_storage; + m_string = m_using_small ? m_small_string : new char[m_length + 1]; } @@ -144,7 +189,7 @@ void String::allocate_self() * @brief Sets the string to the other string * * @param other The string for this one to be updated to - * @return String& The string + * @return String The string */ String &String::operator = (String const &other) { @@ -152,9 +197,6 @@ String &String::operator = (String const &other) { if (this == &other) return *this; - // Free the old memory - delete[] m_string; - // Copy the other string copy(other); diff --git a/kernel/src/filesystem/partition/msdos.cpp b/kernel/src/filesystem/partition/msdos.cpp index 7b67f15c..7c9b61b9 100644 --- a/kernel/src/filesystem/partition/msdos.cpp +++ b/kernel/src/filesystem/partition/msdos.cpp @@ -53,7 +53,7 @@ void MSDOSPartition::mount_partitions(Disk* hd) { case PartitionType::LINUX_EXT2: Logger::Out() << "EXT2 partition\n"; - vfs -> mount_filesystem(new ext2::Ext2FileSystem(hd, entry.start_LBA)); + vfs -> mount_filesystem(new ext2::Ext2FileSystem(hd, entry.start_LBA)); break; default: diff --git a/kernel/src/hardwarecommunication/interrupts.cpp b/kernel/src/hardwarecommunication/interrupts.cpp index b7e408e5..1d49bc3e 100644 --- a/kernel/src/hardwarecommunication/interrupts.cpp +++ b/kernel/src/hardwarecommunication/interrupts.cpp @@ -303,7 +303,7 @@ cpu_status_t* InterruptManager::page_fault(system::cpu_status_t *status) { uint64_t faulting_address; asm volatile("movq %%cr2, %0" : "=r" (faulting_address)); - // Try kill the process so the system doesnt die + // Try kill the process so the system doesn't die cpu_status_t* can_avoid = CPU::prepare_for_panic(status); if(can_avoid != nullptr) return can_avoid; diff --git a/kernel/src/kernel.cpp b/kernel/src/kernel.cpp index 7ca2afe4..25341746 100644 --- a/kernel/src/kernel.cpp +++ b/kernel/src/kernel.cpp @@ -77,14 +77,8 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic uint32_t write = 12 * 700; auto* test_data = new uint8_t[write]; memset(test_data, (uint32_t)'a', write); - test_data[write - 1] = '\0'; grub_cfg -> write(test_data, write); - // DEBUG CMDS: - // watch *((char[32]*)0xffff800478025000), p *(MemoryChunk*)0xffff800478025000 - 'a' buffer memchunk - // watch *((char[32]*)0xffff800478028000), p *(MemoryChunk*)0xffff800478028000 - 'a' buffer memchunk -> next - // - grub_cfg ->seek(SeekType::SET, 0); uint8_t buffer[100]; grub_cfg ->read(buffer, 100); From c19cec7ee5d7e414e32e55313c1b6c3fb881eaf6 Mon Sep 17 00:00:00 2001 From: Max Tyson <98maxt98@gmail.com> Date: Tue, 29 Jul 2025 21:16:01 +1200 Subject: [PATCH 24/38] Buggy Ext2 Dir Adding --- kernel/include/common/vector.h | 3 +- kernel/include/filesystem/ext2.h | 81 +++-- kernel/src/filesystem/ext2.cpp | 552 ++++++++++++++++++++----------- kernel/src/kernel.cpp | 23 +- kernel/src/system/cpu.cpp | 10 +- kernel/src/system/syscalls.cpp | 2 +- 6 files changed, 433 insertions(+), 238 deletions(-) diff --git a/kernel/include/common/vector.h b/kernel/include/common/vector.h index a5c50570..67f79bca 100644 --- a/kernel/include/common/vector.h +++ b/kernel/include/common/vector.h @@ -55,8 +55,7 @@ namespace MaxOS{ Vector& operator=(const Vector& other); Vector& operator=(Vector&& other); - [[nodiscard]] bool empty() const; - [[nodiscard]] uint32_t size() const; + [[nodiscard]] bool empty() const; [[nodiscard]] uint32_t size() const; iterator begin() const; iterator end() const; diff --git a/kernel/include/filesystem/ext2.h b/kernel/include/filesystem/ext2.h index a1779402..224b1b4a 100644 --- a/kernel/include/filesystem/ext2.h +++ b/kernel/include/filesystem/ext2.h @@ -236,20 +236,50 @@ namespace MaxOS { common::Spinlock ext2_lock; - void write_block(uint32_t block_num, uint8_t* buffer); - void write_inode(uint32_t inode_num, inode_t* inode); + void write_block(uint32_t block_num, uint8_t* buffer); + void write_inode(uint32_t inode_num, inode_t* inode); + [[nodiscard]] uint32_t create_inode(bool is_directory); void read_block(uint32_t block_num, uint8_t* buffer) const; [[nodiscard]] inode_t read_inode(uint32_t inode_num) const; block_group_descriptor_t read_block_group(uint32_t group_num); - uint32_t allocate_block(); - common::Vector allocate_blocks(uint32_t amount); - uint32_t bytes_to_blocks(size_t bytes) const; + [[nodiscard]] uint32_t allocate_block(); + [[nodiscard]] common::Vector allocate_blocks(uint32_t amount); + [[nodiscard]] uint32_t bytes_to_blocks(size_t bytes) const; // TODO: free blocks }; + /** + * @class InodeHandler + * @brief Simplfies the management of an inode & its blocks + */ + class InodeHandler { + + private: + Ext2Volume* m_volume = nullptr; + + void parse_indirect(uint32_t level, uint32_t block, uint8_t* buffer); + void write_indirect(uint32_t level, uint32_t& block, size_t& index); + void store_blocks(const common::Vector& blocks); + + public: + InodeHandler(Ext2Volume* volume, uint32_t inode); + ~InodeHandler(); + + uint32_t inode_number; + inode_t inode; + common::Vector block_cache; + + [[nodiscard]] size_t size() const; + void set_size(size_t size); + size_t grow(size_t amount, bool flush = true); + + void save(); + + }; + /** * @class Ext2File * @brief Handles the file operations on the ext2 filesystem @@ -257,13 +287,7 @@ namespace MaxOS { class Ext2File final : public File { private: Ext2Volume* m_volume; - uint32_t m_inode_number; - inode_t m_inode; - - common::Vector m_block_pointers; - void parse_indirect(uint32_t level, uint32_t block, uint8_t* buffer); - void write_indirect(uint32_t level, uint32_t& block, size_t& index); - void store_blocks(const common::Vector& blocks); + InodeHandler m_inode; public: Ext2File(Ext2Volume* volume, uint32_t inode, const string& name); @@ -280,26 +304,29 @@ namespace MaxOS { */ class Ext2Directory final : public Directory { - private: - Ext2Volume* m_volume; - uint32_t m_inode_number; - inode_t m_inode; + private: + Ext2Volume* m_volume; + InodeHandler m_inode; - common::Vector m_entries; - void parse_entries(uint8_t* buffer); - void parse_indirect(uint32_t level, uint32_t block, uint8_t* buffer); + common::Vector m_entries; + common::Vector m_entry_names; - public: - Ext2Directory(Ext2Volume* volume, uint32_t inode, const string& name); - ~Ext2Directory() final; + void write_entries(); + directory_entry_t create_entry(const string& name, uint32_t inode, bool is_directory = false); + + void parse_block(uint8_t* buffer); + + public: + Ext2Directory(Ext2Volume* volume, uint32_t inode, const string& name); + ~Ext2Directory() final; - void read_from_disk() final; + void read_from_disk() final; - File* create_file(const string& name) final; - void remove_file(const string& name) final; + File* create_file(const string& name) final; + void remove_file(const string& name) final; - Directory* create_subdirectory(const string& name) final; - void remove_subdirectory(const string& name) final; + Directory* create_subdirectory(const string& name) final; + void remove_subdirectory(const string& name) final; }; /** diff --git a/kernel/src/filesystem/ext2.cpp b/kernel/src/filesystem/ext2.cpp index b14080d0..dee923a5 100644 --- a/kernel/src/filesystem/ext2.cpp +++ b/kernel/src/filesystem/ext2.cpp @@ -187,7 +187,7 @@ uint32_t Ext2Volume::allocate_block() { */ Vector Ext2Volume::allocate_blocks(uint32_t amount) { - Logger::DEBUG() << "ALLOCATING BLOCKS: 0x" << (uint64_t)amount << "\n"; + Logger::DEBUG() << "ALLOCATING 0x" << (uint64_t )amount << " BLOCKS"; // No blocks to allocate if (!amount) @@ -322,149 +322,99 @@ uint32_t Ext2Volume::bytes_to_blocks(size_t bytes) const { return (bytes + block_size - 1) / block_size; } -Ext2File::Ext2File(Ext2Volume *volume, uint32_t inode, string const &name) -: m_volume(volume), - m_inode_number(inode), - m_inode() -{ - - // Set up the base information - m_name = name; - m_inode = m_volume ->read_inode(m_inode_number); - m_size = (size_t)m_inode.size_upper | m_inode.size_lower; - - // Read the block pointers - for (uint32_t direct_pointer = 0; direct_pointer < 12; ++direct_pointer) - if(m_inode.block_pointers[direct_pointer]) - m_block_pointers.push_back(m_inode.block_pointers[direct_pointer]); - - auto* buffer = new uint8_t[m_volume -> block_size]; - parse_indirect(1, m_inode.l1_indirect, buffer); - parse_indirect(2, m_inode.l2_indirect, buffer); - parse_indirect(3, m_inode.l3_indirect, buffer); - delete[] buffer; -} - /** - * @brief Write data to the file (at the current seek position, updated to be += amount) + * @brief Creates a new inode * - * @param data The byte buffer to write - * @param amount The amount of data to write + * @param is_directory is the inode to be used for a directory + * @return The new inode */ -void Ext2File::write(uint8_t const *data, size_t amount) { +uint32_t Ext2Volume::create_inode(bool is_directory) { - // Nothing to write - if(m_size == 0) - return; + ext2_lock.lock(); - // Prepare for writing - m_volume -> ext2_lock.lock(); - const uint32_t block_size = m_volume -> block_size; - auto* buffer = new uint8_t[block_size]; + // Find the block group with enough free inodes + block_group_descriptor_t* block_group = block_groups[0]; + uint32_t bg_index = 0; + for (; bg_index < total_block_groups; block_group = block_groups[++bg_index]) + if (block_group -> free_inodes >= 1) + break; - // Expand the file - if(m_offset + amount > m_size) { + // Read bitmap + auto bitmap = new uint8_t[block_size]; + read_block(block_group -> block_inode_bitmap, bitmap); - // Allocate new blocks - auto blocks = m_volume ->allocate_blocks(m_volume -> bytes_to_blocks((m_offset + amount) - m_size)); - ASSERT(blocks[0] != 0, "Failed to allocate new blocks for file"); + // Find a free inode + uint32_t inode_index = 0; + for (; inode_index < superblock.blocks_per_group; ++inode_index) { - // Save the changes - store_blocks(blocks); - m_size = (m_offset + amount); + // Block is already used + if ((bitmap[inode_index / 8] & (1u << (inode_index % 8))) != 0) + continue; + // Mark as used + block_group -> free_inodes--; + superblock.unallocated_inodes--; + bitmap[inode_index / 8] |= (uint8_t) (1u << (inode_index % 8)); + + break; } + inode_index += bg_index * superblock.inodes_per_group; - // Save the updated metadata - m_inode.size_lower = (uint32_t)(m_size & 0xFFFFFFFFULL); - m_inode.size_upper = (uint32_t)(m_size >> 32); - m_inode.last_modification_time = time_to_epoch(Clock::active_clock() -> get_time()); - m_volume -> write_inode(m_inode_number, &m_inode); // - Also saves blocks + // Save the changed metadata + write_block(block_group -> block_usage_bitmap, bitmap); + write_back_block_groups(); + write_back_superblock(); - // Convert bytes to blocks - uint32_t block_start = m_offset / block_size; - uint32_t block_offset = m_offset % block_size; + // Create the inode + inode_t inode {}; + inode.creation_time = time_to_epoch(Clock::active_clock()->get_time()); + inode.last_modification_time = time_to_epoch(Clock::active_clock()->get_time()); + inode.block_pointers[0] = allocate_block(); + inode.hard_links = is_directory ? 1 : 0; + inode.type = (uint32_t)(is_directory ? InodeType::DIRECTORY : InodeType::FILE); + write_inode(inode_index, &inode); - // Write each block - size_t current_block = block_start; - size_t written = 0; - while (written < amount) { + ext2_lock.unlock(); + return inode_index; - // Read the block - uint32_t block = m_block_pointers[current_block++]; - m_volume -> read_block(block, buffer); +} - // Where in this block to start writing - size_t buffer_start = (current_block - 1 == block_start) ? block_offset : 0; - size_t writable = (amount - written < block_size - buffer_start) ? (amount - written) : (block_size - buffer_start); +InodeHandler::InodeHandler(Ext2Volume* volume, uint32_t inode_index) +: m_volume(volume), + inode_number(inode_index), + inode(m_volume -> read_inode(inode_number)) +{ - // Update the block - memcpy(buffer + buffer_start, data + written, writable); - m_volume ->write_block(block, buffer); - written += writable; - } + // Read the block pointers + for (uint32_t direct_pointer = 0; direct_pointer < 12; ++direct_pointer) + if(inode.block_pointers[direct_pointer]) + block_cache.push_back(inode.block_pointers[direct_pointer]); - // Clean up - m_offset += amount; - m_volume -> ext2_lock.unlock(); + auto* buffer = new uint8_t[m_volume -> block_size]; + parse_indirect(1, inode.l1_indirect, buffer); + parse_indirect(2, inode.l2_indirect, buffer); + parse_indirect(3, inode.l3_indirect, buffer); delete[] buffer; + } /** -* @brief Read data from the file (at the current seek position, updated to be += amount) -* -* @param data The byte buffer to read into -* @param amount The amount of data to read -*/ -void Ext2File::read(uint8_t* data, size_t amount) { - - // Nothing to read - if(m_size == 0 || amount == 0) - return; - - // Prepare for reading - m_volume -> ext2_lock.lock(); - const uint32_t block_size = m_volume -> block_size; - auto* buffer = new uint8_t[block_size]; - - // Force bounds - if(m_offset + amount > m_size) - amount = m_size - m_offset; - - // Convert bytes to blocks - uint32_t block_start = m_offset / block_size; - uint32_t block_offset = m_offset % block_size; - - // Read each block - size_t current_block = block_start; - size_t read = 0; - while (read < amount){ - - // Read the block - uint32_t block = m_block_pointers[current_block++]; - m_volume -> read_block(block, buffer); - - // Where in this block to start reading - size_t buffer_start = (current_block - 1 == block_start) ? block_offset : 0; - size_t readable = (amount - read < block_size - buffer_start) ? (amount - read) : (block_size - buffer_start); - - // Read the block - memcpy(data + read, buffer + buffer_start, readable); - read += readable; - - } - - // Clean up - m_offset += amount; - m_volume -> ext2_lock.unlock(); - delete[] buffer; + * @brief Read the size upper and lower into a single size_t + * @return The size of the inode data (not the inode itself) + */ +size_t InodeHandler::size() const { + return (size_t)inode.size_upper | inode.size_lower; } /** - * @brief Flush the file to the disk + * @brief Store the size of the inode data. (does not write to disk) + * @param size The new size */ -void Ext2File::flush() { - File::flush(); +void InodeHandler::set_size(size_t size) { + + inode.size_lower = (uint32_t)(size & 0xFFFFFFFFULL); + inode.size_upper = (uint32_t)(size >> 32); + } /** @@ -474,7 +424,7 @@ void Ext2File::flush() { * @param block The block number to parse from * @param buffer Buffer to read into */ -void Ext2File::parse_indirect(uint32_t level, uint32_t block, uint8_t *buffer) { +void InodeHandler::parse_indirect(uint32_t level, uint32_t block, uint8_t *buffer) { // Invalid if(block == 0) @@ -499,8 +449,9 @@ void Ext2File::parse_indirect(uint32_t level, uint32_t block, uint8_t *buffer) { } // Parse the entry - m_block_pointers.push_back(pointer); + block_cache.push_back(pointer); } + } /** @@ -511,11 +462,11 @@ void Ext2File::parse_indirect(uint32_t level, uint32_t block, uint8_t *buffer) { * @param buffer Buffer to read into * @param index Current entry to write */ -void Ext2File::write_indirect(uint32_t level, uint32_t& block, size_t& index) { +void InodeHandler::write_indirect(uint32_t level, uint32_t &block, size_t &index) { // Nothing left to write - size_t remaining = m_block_pointers.size() - index; - if (remaining == 0 || index >= m_block_pointers.size()) + size_t remaining = block_cache.size() - index; + if (remaining == 0 || index >= block_cache.size()) return; // Level hasn't been set yet @@ -531,7 +482,7 @@ void Ext2File::write_indirect(uint32_t level, uint32_t& block, size_t& index) { for (size_t i = 0; i < m_volume->pointers_per_block; ++i) { // Invalid - if (index >= m_block_pointers.size()) + if (index >= block_cache.size()) break; // Has indirect @@ -541,7 +492,7 @@ void Ext2File::write_indirect(uint32_t level, uint32_t& block, size_t& index) { } // Save the pointer - pointers[i] = m_block_pointers[index++]; + pointers[i] = block_cache[index++]; } m_volume ->write_block(block, buffer); @@ -553,20 +504,20 @@ void Ext2File::write_indirect(uint32_t level, uint32_t& block, size_t& index) { * * @param blocks The blocks to save */ -void Ext2File::store_blocks(const common::Vector& blocks) { +void InodeHandler::store_blocks(Vector const &blocks) { Logger::DEBUG() << "STORING BLOCKS\n"; // Store in cache for(auto block : blocks) - m_block_pointers.push_back(block); + block_cache.push_back(block); // Direct blocks for (uint32_t i = 0; i < 12; ++i) - m_inode.block_pointers[i] = i < m_block_pointers.size() ? m_block_pointers[i] : 0; + inode.block_pointers[i] = i < block_cache.size() ? block_cache[i] : 0; // No need to do any indirects - if(m_block_pointers.size() < 12) + if(block_cache.size() < 12) return; // Setup Recursive blocks @@ -574,7 +525,7 @@ void Ext2File::store_blocks(const common::Vector& blocks) { auto* buffer = new uint8_t[m_volume -> block_size]; // Write the blocks - uint32_t indirect_blocks[3] = { m_inode.l1_indirect, m_inode.l2_indirect, m_inode.l3_indirect }; + uint32_t indirect_blocks[3] = { inode.l1_indirect, inode.l2_indirect, inode.l3_indirect }; for (int i = 0; i < 3; ++i) { // Have to use temp because of packed field @@ -584,9 +535,9 @@ void Ext2File::store_blocks(const common::Vector& blocks) { } // Save the new blocks - m_inode.l1_indirect = indirect_blocks[0]; - m_inode.l2_indirect = indirect_blocks[1]; - m_inode.l3_indirect = indirect_blocks[2]; + inode.l1_indirect = indirect_blocks[0]; + inode.l2_indirect = indirect_blocks[1]; + inode.l3_indirect = indirect_blocks[2]; delete[] buffer; @@ -594,13 +545,167 @@ void Ext2File::store_blocks(const common::Vector& blocks) { } +/** + * @brief Increase the size of the inode's storage capacity by allocating new blocks. + * + * @param amount The amount to grow to in bytes + * @return + */ +size_t InodeHandler::grow(size_t amount, bool flush) { + + // Nothing to grow + if(amount <= 0) + return size(); + + // Allocate new blocks + auto blocks = m_volume -> allocate_blocks(m_volume -> bytes_to_blocks(amount)); + ASSERT(blocks[0] != 0, "Failed to allocate new blocks for file"); + + // Save the changes + store_blocks(blocks); + set_size(size() + amount); + if(flush) + save(); + + return size() + amount; + +} + +void InodeHandler::save() { + + m_volume -> write_inode(inode_number, &inode); +} + +InodeHandler::~InodeHandler() = default; + +Ext2File::Ext2File(Ext2Volume *volume, uint32_t inode, string const &name) +: m_volume(volume), + m_inode(volume, inode) +{ + + // Set up the base information + m_name = name; + m_size = m_inode.size(); + +} + +/** + * @brief Write data to the file (at the current seek position, updated to be += amount) + * + * @param data The byte buffer to write + * @param amount The amount of data to write + */ +void Ext2File::write(uint8_t const *data, size_t amount) { + + // Nothing to write + if(m_size == 0) + return; + + // Prepare for writing + m_volume -> ext2_lock.lock(); + const uint32_t block_size = m_volume -> block_size; + auto* buffer = new uint8_t[block_size]; + + // Expand the file + if(m_offset + amount > m_size) + m_size = m_inode.grow((m_offset + amount) - m_size, false); + + // Save the updated metadata + m_inode.inode.last_modification_time = time_to_epoch(Clock::active_clock() -> get_time()); + m_inode.save(); + + // Convert bytes to blocks + uint32_t block_start = m_offset / block_size; + uint32_t block_offset = m_offset % block_size; + + // Write each block + size_t current_block = block_start; + size_t written = 0; + while (written < amount) { + + // Read the block + uint32_t block = m_inode.block_cache[current_block++]; + m_volume -> read_block(block, buffer); + + // Where in this block to start writing + size_t buffer_start = (current_block - 1 == block_start) ? block_offset : 0; + size_t writable = (amount - written < block_size - buffer_start) ? (amount - written) : (block_size - buffer_start); + + // Update the block + memcpy(buffer + buffer_start, data + written, writable); + m_volume ->write_block(block, buffer); + written += writable; + } + + // Clean up + m_offset += amount; + m_volume -> ext2_lock.unlock(); + delete[] buffer; +} + +/** +* @brief Read data from the file (at the current seek position, updated to be += amount) +* +* @param data The byte buffer to read into +* @param amount The amount of data to read +*/ +void Ext2File::read(uint8_t* data, size_t amount) { + + // Nothing to read + if(m_size == 0 || amount == 0) + return; + + // Prepare for reading + m_volume -> ext2_lock.lock(); + const uint32_t block_size = m_volume -> block_size; + auto* buffer = new uint8_t[block_size]; + + // Force bounds + if(m_offset + amount > m_size) + amount = m_size - m_offset; + + // Convert bytes to blocks + uint32_t block_start = m_offset / block_size; + uint32_t block_offset = m_offset % block_size; + + // Read each block + size_t current_block = block_start; + size_t read = 0; + while (read < amount){ + + // Read the block + uint32_t block = m_inode.block_cache[current_block++]; + m_volume -> read_block(block, buffer); + + // Where in this block to start reading + size_t buffer_start = (current_block - 1 == block_start) ? block_offset : 0; + size_t readable = (amount - read < block_size - buffer_start) ? (amount - read) : (block_size - buffer_start); + + // Read the block + memcpy(data + read, buffer + buffer_start, readable); + read += readable; + + } + + // Clean up + m_offset += amount; + m_volume -> ext2_lock.unlock(); + delete[] buffer; +} + +/** + * @brief Flush the file to the disk + */ +void Ext2File::flush() { + File::flush(); +} + Ext2File::~Ext2File() = default; Ext2Directory::Ext2Directory(Ext2Volume *volume, uint32_t inode, const string& name) : m_volume(volume), - m_inode_number(inode), - m_inode() + m_inode(m_volume, inode) { m_name = name; } @@ -608,7 +713,7 @@ Ext2Directory::Ext2Directory(Ext2Volume *volume, uint32_t inode, const string& n /** * @brief Store all entries from a buffer and convert to File or Directory objects */ -void Ext2Directory::parse_entries(uint8_t* buffer) { +void Ext2Directory::parse_block(uint8_t* buffer) { size_t offset = 0; while (offset < m_volume -> block_size){ @@ -623,8 +728,13 @@ void Ext2Directory::parse_entries(uint8_t* buffer) { // Parse string filename(buffer + offset + sizeof(directory_entry_t), entry->name_length); + m_entry_names.push_back(filename); uint32_t inode = entry->inode; + // Correct the entries size (last one fulls the rest of the block) + entry -> size = sizeof(directory_entry_t) + filename.length(); + entry -> size += entry -> size % 4 ? 4 - entry -> size % 4 : 0; + // Create the object switch ((InodeType)entry->type) { @@ -647,52 +757,14 @@ void Ext2Directory::parse_entries(uint8_t* buffer) { } -/** - * @brief Recursively parses the indirect entries - * - * @param level Recursion level - * @param block The block number to parse from - * @param buffer Buffer to read into - */ -void Ext2Directory::parse_indirect(uint32_t level, uint32_t block, uint8_t* buffer) { - - // Invalid - if(block == 0) - return; - - // Read the block - m_volume -> read_block(block, buffer); - auto* pointers = (uint32_t*)buffer; - - // Parse the pointers - for (size_t i = 0; i < m_volume->pointers_per_block; ++i) { - uint32_t pointer = pointers[i]; - - // Invaild - if(pointer == 0) - break; - - // Has indirect sub entries - if(level > 1){ - parse_indirect(level - 1, pointer, buffer); - continue; - } - - // Parse the entry - m_volume ->read_block(pointer, buffer); - parse_entries(buffer); - } -} - /** * @brief Read the directory from the inode on the disk */ void Ext2Directory::read_from_disk() { m_volume -> ext2_lock.lock(); - - // Read the inode - m_inode = m_volume ->read_inode(m_inode_number); + m_entries.clear(); + m_entry_names.clear(); // Clear the old files & Directories for(auto& file : m_files) @@ -705,8 +777,8 @@ void Ext2Directory::read_from_disk() { // Read the direct blocks (cant use for( : )) auto* buffer = new uint8_t[m_volume -> block_size]; - for (int i = 0; i < 12; ++i) { - uint32_t block_pointer = m_inode.block_pointers[i]; + for (int i = 0; i < m_inode.block_cache.size(); ++i) { + uint32_t block_pointer = m_inode.block_cache[i]; // Invalid block if(block_pointer == 0) @@ -714,18 +786,100 @@ void Ext2Directory::read_from_disk() { // Parse the block m_volume->read_block(block_pointer, buffer); - parse_entries(buffer); + parse_block(buffer); } - // Indirect blocks - parse_indirect(1, m_inode.l1_indirect, buffer); - parse_indirect(2, m_inode.l2_indirect, buffer); - parse_indirect(3, m_inode.l3_indirect, buffer); - m_volume -> ext2_lock.unlock(); delete[] buffer; } +void Ext2Directory::write_entries() { + + // Calculate the size needed to store the entries and the null entry + size_t size_required = sizeof(directory_entry_t); + for (int i = 0; i < m_entries.size(); ++i) { + size_t size = sizeof(directory_entry_t) + m_entries[i].name_length; + size += (size % 4) ? 4 - size % 4 : 0; + size_required += size; + } + size_required = m_volume -> bytes_to_blocks(size_required); + + // Prepare for writing + m_volume -> ext2_lock.lock(); + const uint32_t block_size = m_volume -> block_size; + auto* buffer = new uint8_t[block_size]; + memset(buffer, 0, block_size); + + // Expand the directory + if(size_required > m_inode.block_cache.size()) + m_inode.grow((size_required - m_inode.block_cache.size()) * m_volume -> block_size, false); + + // Save the updated metadata + m_inode.inode.last_modification_time = time_to_epoch(Clock::active_clock() -> get_time()); + m_inode.save(); + + // Write each entry + size_t current_block = 0; + size_t buffer_offset = 0; + for (int i = 0; i < m_entries.size(); ++i){ + + // Get the current entry + directory_entry_t& entry = m_entries[i]; + char* name = m_entry_names[i].c_str(); + + + // Entry needs to be stored in the next block + if(entry.size + buffer_offset > block_size){ + m_volume ->write_block(m_inode.block_cache[current_block], buffer); + memset(buffer, 0, block_size); + current_block++; + buffer_offset = 0; + } + + // If it is the last entry it takes up the rest of the block + if(i == m_entries.size() - 1) + entry.size = block_size - buffer_offset; + + // Copy the entry and the name + memcpy(buffer + buffer_offset, &entry, sizeof(entry)); + memcpy(buffer + buffer_offset + sizeof(entry), name, entry.name_length + 1); + buffer_offset += entry.size; + } + + // Save the last block + m_volume -> write_block(m_inode.block_cache[current_block], buffer); + + // Clean up + m_entries.pop_back(); + m_entry_names.pop_back(); + delete[] buffer; + m_volume->ext2_lock.unlock(); +} + +directory_entry_t Ext2Directory::create_entry(const string& name, uint32_t inode, bool is_directory) { + + // Create the inode + directory_entry_t entry {}; + entry.inode = inode ? inode : m_volume -> create_inode(is_directory); + entry.type = (uint32_t)InodeType::DIRECTORY; + entry.name_length = name.length(); + entry.size = sizeof(entry) + entry.name_length; + entry.size += entry.size % 4 ? 4 - entry.size % 4 : 0; + + // Save the inode + m_entries.push_back(entry); + m_entry_names.push_back(name); + write_entries(); + + // subdirectories' ".." hard links to this entry + if(is_directory && !inode){ + m_inode.inode.hard_links++; + m_inode.save(); + } + + return entry; +} + /** * @brief Create a new file in the directory * @@ -733,8 +887,16 @@ void Ext2Directory::read_from_disk() { * @return The new file object or null if it could not be created */ File *Ext2Directory::create_file(string const &name) { - // TODO - return nullptr; + + // Check if the file already exists + for (auto & file : m_files) + if (file -> name() == name) + return nullptr; + + // Create the file + auto file = new Ext2File(m_volume, create_entry(name, 0, false).inode, name); + m_files.push_back(file); + return file; } /** @@ -753,7 +915,21 @@ void Ext2Directory::remove_file(string const &name) { * @return The new directory object or null if it could not be created */ Directory *Ext2Directory::create_subdirectory(string const &name) { - return Directory::create_subdirectory(name); + + // Check if the directory already exists + for (auto & subdirectory : m_subdirectories) + if (subdirectory -> name() == name) + return nullptr; + + // Store the directory + auto directory = new Ext2Directory(m_volume, create_entry(name, 0, true).inode, name); + m_subdirectories.push_back(directory); + + // Create self & parent references + directory->create_entry(".", directory->m_inode.inode_number, true); + directory->create_entry("..", m_inode.inode_number, true); + + return directory; } /** diff --git a/kernel/src/kernel.cpp b/kernel/src/kernel.cpp index 25341746..d0aad507 100644 --- a/kernel/src/kernel.cpp +++ b/kernel/src/kernel.cpp @@ -71,18 +71,15 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic driver_manager.activate_drivers(); // FS Tests - File* grub_cfg = vfs.open_file("/test/a.txt"); - Logger::DEBUG() << "Opened file: " << grub_cfg->name() << "\n"; + Directory* newdir = vfs.create_directory("/test/a"); + for (const auto &item: newdir->subdirectories()) + Logger::DEBUG() << "DIRECTORY: " << item->name() << "\n"; - uint32_t write = 12 * 700; - auto* test_data = new uint8_t[write]; - memset(test_data, (uint32_t)'a', write); - grub_cfg -> write(test_data, write); - - grub_cfg ->seek(SeekType::SET, 0); - uint8_t buffer[100]; - grub_cfg ->read(buffer, 100); - Logger::DEBUG() << (char*)buffer << "\n"; +// File* newf = vfs.create_file("/test/bob.txt"); +// uint32_t write = 12 * 700; +// auto* test_data = new uint8_t[write]; +// memset(test_data, (uint32_t)'a', write); +// newf -> write(test_data, write); Logger::HEADER() << "Stage {4}: System Finalisation\n"; Scheduler scheduler(multiboot); @@ -98,8 +95,8 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic // TODO: // - EXT2 Tests: // - [x] Read subdirectories contents -// - [ ] Read files -// - [ ] Write files +// - [x] Read files +// - [x] Write files // - [ ] Create subdirectories // - [ ] Delete subdirectories // - [ ] Rename directory diff --git a/kernel/src/system/cpu.cpp b/kernel/src/system/cpu.cpp index bd95f576..343bcae9 100644 --- a/kernel/src/system/cpu.cpp +++ b/kernel/src/system/cpu.cpp @@ -182,11 +182,10 @@ void CPU::PANIC(char const *message, cpu_status_t* status) { Logger::ERROR() << "Register Dump:\n"; // Log the regs - cpu_status_t* new_status = nullptr; + cpu_status_t new_status {}; if(!status){ - new_status = new cpu_status_t; - get_status(new_status); - status = new_status; + get_status(&new_status); + status = &new_status; } print_registers(status); @@ -202,9 +201,6 @@ void CPU::PANIC(char const *message, cpu_status_t* status) { // Halt halt(); - - // Should really never get here but if somehow that happens why not be memory safe - delete new_status; } /** diff --git a/kernel/src/system/syscalls.cpp b/kernel/src/system/syscalls.cpp index cd05497b..5b02841d 100644 --- a/kernel/src/system/syscalls.cpp +++ b/kernel/src/system/syscalls.cpp @@ -19,7 +19,7 @@ SyscallManager::SyscallManager() // Clear the args Logger::INFO() << "Setting up Syscalls \n"; - m_current_args = new syscall_args_t; + m_current_args = {}; // Register the handlers set_syscall_handler(SyscallType::CLOSE_PROCESS, syscall_close_process); From 3e08d718fcfc8886d0dee93c19c435aa6a67bcb6 Mon Sep 17 00:00:00 2001 From: Max Tyson <98maxt98@gmail.com> Date: Fri, 1 Aug 2025 20:57:26 +1200 Subject: [PATCH 25/38] Buffer Class Implementation --- kernel/include/common/buffer.h | 70 +++++ kernel/include/common/logger.h | 12 + kernel/include/drivers/disk/ata.h | 5 +- kernel/include/drivers/disk/disk.h | 9 +- kernel/include/filesystem/ext2.h | 12 +- kernel/include/filesystem/fat32.h | 4 +- kernel/include/processes/ipc.h | 1 + kernel/src/common/buffer.cpp | 323 ++++++++++++++++++++++ kernel/src/drivers/disk/ata.cpp | 12 +- kernel/src/drivers/disk/disk.cpp | 28 +- kernel/src/filesystem/ext2.cpp | 177 ++++++------ kernel/src/filesystem/fat32.cpp | 87 +++--- kernel/src/filesystem/partition/msdos.cpp | 4 +- kernel/src/kernel.cpp | 1 + kernel/src/processes/ipc.cpp | 11 +- 15 files changed, 594 insertions(+), 162 deletions(-) create mode 100644 kernel/include/common/buffer.h create mode 100644 kernel/src/common/buffer.cpp diff --git a/kernel/include/common/buffer.h b/kernel/include/common/buffer.h new file mode 100644 index 00000000..59e1a4b9 --- /dev/null +++ b/kernel/include/common/buffer.h @@ -0,0 +1,70 @@ +// +// Created by 98max on 30/07/2025. +// + +#ifndef MAXOS_COMMON_BUFFER_H +#define MAXOS_COMMON_BUFFER_H + +#include +#include +#include +#include + +namespace MaxOS{ + + namespace common{ + + /** + * @class Buffer + * @brief Wrapper class for a region of bytes in memor in an attempt to add some memory safety. Automatically + * allocates the size specified and frees it once done, adds boundary to I/O. + */ + class Buffer{ + + private: + uint8_t* m_bytes = nullptr; + size_t m_capacity; + bool m_dont_delete = false; + + size_t m_offset = 0; + + public: + Buffer(size_t size, bool update_offset = true); + Buffer(void* source, size_t size, bool update_offset = true); + ~Buffer(); + + uint8_t* raw() const; + void clear(); + + bool update_offset = true; + + size_t capacity() const; + void resize(size_t size); + + void set_offset(size_t offset); + + void write(size_t offset, uint8_t byte); + uint8_t read(size_t offset) const; + + void copy_from(const Buffer* buffer); + void copy_from(const Buffer* buffer, size_t length); + void copy_from(const Buffer* buffer, size_t length, size_t offset); + void copy_from(const Buffer* buffer, size_t length, size_t offset, size_t offset_other); + void copy_from(const void* source, size_t length); + void copy_from(const void* source, size_t length, size_t offset); + + void copy_to(Buffer* buffer); + void copy_to(Buffer* buffer, size_t length); + void copy_to(Buffer* buffer, size_t length, size_t offset); + void copy_to(Buffer* buffer, size_t length, size_t offset, size_t offset_other); + void copy_to(void* destination, size_t length); + void copy_to(void* destination, size_t length, size_t offset); + }; + + typedef Buffer buffer_t; + + } + +} + +#endif //MAXOS_COMMON_BUFFER_H diff --git a/kernel/include/common/logger.h b/kernel/include/common/logger.h index 7f2b78d2..907315b7 100644 --- a/kernel/include/common/logger.h +++ b/kernel/include/common/logger.h @@ -75,5 +75,17 @@ Logger& operator << (LogLevel log_level); }; + /** + * @brief If the specified condition is not met then the kernel will crash with the specified message. + * + * This macro wraps Logger::ASSERT and supports printf-style formatting with variadic arguments. + * + * @param condition The condition to check. + * @param format The format string (like printf). + * @param ... Additional arguments to format. + * + * @see Logger::ASSERT + */ #define ASSERT(condition, format, ...) Logger::ASSERT(condition, format, ##__VA_ARGS__) + #endif // MAXOS_COMMON_LOGGER_H diff --git a/kernel/include/drivers/disk/ata.h b/kernel/include/drivers/disk/ata.h index b9bfc5ef..4eff54e4 100644 --- a/kernel/include/drivers/disk/ata.h +++ b/kernel/include/drivers/disk/ata.h @@ -5,6 +5,7 @@ #ifndef MAXOS_DRIVERS_DISK_ATA_H #define MAXOS_DRIVERS_DISK_ATA_H +#include #include #include #include @@ -41,8 +42,8 @@ namespace MaxOS{ ~AdvancedTechnologyAttachment(); bool identify(); - void read(uint32_t sector, uint8_t* data_buffer, size_t amount) final; - void write(uint32_t sector, const uint8_t* data, size_t count) final; + void read(uint32_t sector, common::buffer_t* data_buffer, size_t amount) final; + void write(uint32_t sector, const common::buffer_t* data, size_t count) final; void flush() final; string device_name() final; diff --git a/kernel/include/drivers/disk/disk.h b/kernel/include/drivers/disk/disk.h index ae0848a5..f51626e6 100644 --- a/kernel/include/drivers/disk/disk.h +++ b/kernel/include/drivers/disk/disk.h @@ -6,6 +6,7 @@ #define MAXOS_DRIVERS_DISK_H #include +#include #include #include @@ -26,8 +27,12 @@ namespace MaxOS{ Disk(); ~Disk(); - virtual void read(uint32_t sector, uint8_t* data_buffer, size_t amount); - virtual void write(uint32_t sector, const uint8_t* data, size_t count); + void read(uint32_t sector, common::buffer_t* data_buffer); + virtual void read(uint32_t sector, common::buffer_t* data_buffer, size_t amount); + + void write(uint32_t sector, const common::buffer_t* data); + virtual void write(uint32_t sector, const common::buffer_t* data, size_t count); + virtual void flush(); void activate() override; diff --git a/kernel/include/filesystem/ext2.h b/kernel/include/filesystem/ext2.h index 224b1b4a..ad3e2f0b 100644 --- a/kernel/include/filesystem/ext2.h +++ b/kernel/include/filesystem/ext2.h @@ -236,11 +236,11 @@ namespace MaxOS { common::Spinlock ext2_lock; - void write_block(uint32_t block_num, uint8_t* buffer); + void write_block(uint32_t block_num, common::buffer_t* buffer); void write_inode(uint32_t inode_num, inode_t* inode); [[nodiscard]] uint32_t create_inode(bool is_directory); - void read_block(uint32_t block_num, uint8_t* buffer) const; + void read_block(uint32_t block_num, common::buffer_t* buffer) const; [[nodiscard]] inode_t read_inode(uint32_t inode_num) const; block_group_descriptor_t read_block_group(uint32_t group_num); @@ -260,7 +260,7 @@ namespace MaxOS { private: Ext2Volume* m_volume = nullptr; - void parse_indirect(uint32_t level, uint32_t block, uint8_t* buffer); + void parse_indirect(uint32_t level, uint32_t block, common::buffer_t* buffer); void write_indirect(uint32_t level, uint32_t& block, size_t& index); void store_blocks(const common::Vector& blocks); @@ -293,8 +293,8 @@ namespace MaxOS { Ext2File(Ext2Volume* volume, uint32_t inode, const string& name); ~Ext2File() final; - void write(const uint8_t* data, size_t amount) final; - void read(uint8_t* data, size_t amount) final; + void write(const common::buffer_t* data, size_t amount) final; + void read(common::buffer_t* data, size_t amount) final; void flush() final; }; @@ -314,7 +314,7 @@ namespace MaxOS { void write_entries(); directory_entry_t create_entry(const string& name, uint32_t inode, bool is_directory = false); - void parse_block(uint8_t* buffer); + void parse_block(common::buffer_t* buffer); public: Ext2Directory(Ext2Volume* volume, uint32_t inode, const string& name); diff --git a/kernel/include/filesystem/fat32.h b/kernel/include/filesystem/fat32.h index ddf0aa16..9f1ce94b 100644 --- a/kernel/include/filesystem/fat32.h +++ b/kernel/include/filesystem/fat32.h @@ -214,8 +214,8 @@ namespace MaxOS{ Fat32File(Fat32Volume* volume, Fat32Directory* parent, dir_entry_t* info, const string& name); ~Fat32File() final; - void write(const uint8_t* data, size_t amount) final; - void read(uint8_t* data, size_t amount) final; + void write(const common::buffer_t* data, size_t amount) final; + void read(common::buffer_t* data, size_t amount) final; void flush() final; uint32_t first_cluster() const { return m_first_cluster; } diff --git a/kernel/include/processes/ipc.h b/kernel/include/processes/ipc.h index 4a82dc09..2b6e0928 100644 --- a/kernel/include/processes/ipc.h +++ b/kernel/include/processes/ipc.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include diff --git a/kernel/src/common/buffer.cpp b/kernel/src/common/buffer.cpp new file mode 100644 index 00000000..14f8558f --- /dev/null +++ b/kernel/src/common/buffer.cpp @@ -0,0 +1,323 @@ +// +// Created by 98max on 30/07/2025. +// + +#include + +using namespace MaxOS; +using namespace MaxOS::common; + +/** + * @brief Creates a buffer of the specified size + * + * @param size + */ +Buffer::Buffer(size_t size, bool update_offset) +: m_capacity(size), + update_offset(update_offset) +{ + + // Create the buffer + m_bytes = new uint8_t[size]; + +} + +/** + * @brief Creates a buffer pointing to a source + * + * @param source The source + * @param size The capacity of the source + */ +Buffer::Buffer(void* source, size_t size, bool update_offset) +: update_offset(update_offset) +{ + + m_bytes = (uint8_t*)source; + m_capacity = size; + m_dont_delete = true; + +} + +/** + * @brief Destroy the buffer, freeing the memory + */ +Buffer::~Buffer() { + Logger::DEBUG() << "CLEARING BUFFER\n"; + + if(!m_dont_delete) + delete m_bytes; +} + +/** + * @brief The raw pointer to the bytes stored in memory, use is not recommended + * @return The address of buffer's storage + */ +uint8_t *Buffer::raw() const{ + + return m_bytes; +} + +/** + * @brief Fulls the buffer with 0's + */ +void Buffer::clear() { + + memset(m_bytes, 0, m_capacity); +} + +/** + * @brief The max bytes the buffer can currently store. @see Buffer:grow(size_t size) to increase this. + * @return The length of the buffer storage array + */ +size_t Buffer::capacity() const { + return m_capacity; +} + +/** + * @brief Grow the buffer to fit a new size + * + * @param size The new max capacity of the buffer + */ +void Buffer::resize(size_t size) { + + // Create the new buffer + auto* new_buffer = new uint8_t[size]; + + // Copy the old buffer + memcpy(new_buffer, m_bytes, m_capacity); + + // Store the new buffer + delete m_bytes; + m_bytes = new_buffer; + m_capacity = size; + +} + +/** + * @brief Set the offset for where operations should begin from + * + * @param offset The new offset + */ +void Buffer::set_offset(size_t offset) { + + m_offset = offset; + +} + +/** + * @brief Safely writes a byte to the buffer at the specified offset + * + * @param offset The offset into the buffer storage array + * @param byte The byte to write + */ +void Buffer::write(size_t offset, uint8_t byte) { + + // Prevent writing past the buffer bounds + ASSERT(m_offset + offset < capacity() && offset >= 0, "Buffer overflow"); + + // Set the byte + m_bytes[m_offset + offset] = byte; + +} + +/** + * @brief Safely reads a byte from the buffer at the specified offset + * + * @param offset The offset into the buffer storage array + */ +uint8_t Buffer::read(size_t offset) const { + + // Prevent writing past the buffer bounds + ASSERT(m_offset + offset < capacity() && offset >= 0, "Buffer overflow"); + + // Set the byte + return m_bytes[m_offset + offset]; +} + +/** + * @brief Copies all the bytes from another buffer into this buffer. + * + * @param buffer Where to read from + */ +void Buffer::copy_from(const Buffer* buffer) { + + // Copy the buffer + ASSERT(buffer -> capacity() <= capacity(), "Copy exceeds buffer capacity"); + copy_from(buffer -> raw(), buffer -> capacity() ); + +} + +/** + * @brief Copies a range of bytes from another buffer into this buffer. + * + * @param buffer Where to read from + * @param length How much to read + */ +void Buffer::copy_from(const Buffer *buffer, size_t length) { + + // Copy the buffer + ASSERT(length <= buffer -> capacity(), "Copy exceeds external buffer capacity"); + copy_from(buffer -> raw(), length); + +} + +/** + * @brief Copies a range of bytes from another buffer into this buffer at a specified offset. + * @param source Where to read from + * @param length How much to read + * @param offset Where to start writing into this at + */ +void Buffer::copy_from(const Buffer *buffer, size_t length, size_t offset) { + + // Copy the buffer + ASSERT(length <= buffer -> capacity(), "Copy exceeds external buffer capacity"); + copy_from(buffer -> raw(), length, offset); + +} + +/** + * @brief Copies a range of bytes from another buffer into this buffer at a specified offset for both. + * + * @param source Where to read from + * @param length How much to read + * @param offset Where to start writing into this at + */ +void Buffer::copy_from(const Buffer *buffer, size_t length, size_t offset, size_t offset_other) { + + // Copy the buffer + ASSERT(length + offset_other <= buffer -> capacity(), "Copy exceeds external buffer capacity"); + copy_from(buffer -> raw() + offset_other, length, offset); + +} + +/** + * @brief Copies a range of bytes from a source into this buffer. + * + * @param source Where to read from + * @param length How much to read + */ +void Buffer::copy_from(void const *source, size_t length) { + + // Copy the bytes + ASSERT(length + m_offset <= m_capacity, "Copy exceeds buffer capacity"); + memcpy(m_bytes + m_offset, source, length); + + // Update the offset + if(update_offset) + set_offset(m_offset + length); +} + +/** + * @brief Copies a range of bytes from a source into this buffer at a specified offset. + * + * @param source Where to read from + * @param length How much to read + * @param offset Where to start writing at + */ +void Buffer::copy_from(void const *source, size_t length, size_t offset) { + + // Copy the bytes + ASSERT((length + offset + m_offset) <= m_capacity, "Copy exceeds buffer capacity"); + memcpy(m_bytes + offset + m_offset, source, length); + + // Update the offset + if(update_offset) + set_offset(m_offset + length); + +} + +/** + * @brief Copies all the bytes from this buffer into another buffer. + * + * @param buffer Where to write to + */ +void Buffer::copy_to(Buffer *buffer) { + + // Copy the buffer + ASSERT(capacity() <= buffer -> capacity(), "Copy exceeds external buffer capacity"); + copy_to(buffer -> raw(), capacity()); + +} + +/** + * @brief Writes a range of bytes from this buffer into another buffer. + * + * @param buffer Where to write to + * @param length How much to write + */ +void Buffer::copy_to(Buffer *buffer, size_t length) { + + // Copy the buffer + ASSERT(length <= buffer -> capacity(), "Copy exceeds external buffer capacity"); + copy_to(buffer -> raw(), length); + +} + +/** + * @brief Copies a range of bytes from this buffer into another buffer at a specified offset. + * + * @param source Where to write to + * @param length How much to write + * @param offset Where to start reading at + */ +void Buffer::copy_to(Buffer *buffer, size_t length, size_t offset) { + + // Copy the bytes + ASSERT((length + offset) <= buffer -> capacity(), "Copy exceeds external buffer capacity"); + copy_to(buffer -> raw(), length, offset); + +} + +/** + * @brief Copies a range of bytes from this buffer into another at an offset for each. + * + * @param destination Where to write to + * @param length How much to write + * @param offset Where to start reading from + * @param offset_other Where to start writing to + */ +void Buffer::copy_to(Buffer *buffer, size_t length, size_t offset, size_t offset_other) { + + // Copy the bytes + ASSERT((length + offset) <= buffer -> capacity(), "Copy exceeds external buffer capacity"); + copy_to(buffer -> raw() + offset_other, length, offset); + +} + + +/** + * @brief Copies a range of bytes from this buffer into a source. + * + * @param destination Where to write to + * @param length How much to write + */ +void Buffer::copy_to(void* destination, size_t length) { + + // Copy the bytes + ASSERT(length + m_offset <= (m_capacity), "Copy exceeds buffer capacity"); + memcpy(destination, m_bytes + m_offset, length); + + // Update the offset + if(update_offset) + set_offset(m_offset + length); + +} + +/** + * @brief Copies a range of bytes from this buffer at an offset into a source. + * + * @param destination Where to write to + * @param length How much to write + * @param offset Where to start reading from + */ +void Buffer::copy_to(void *destination, size_t length, size_t offset) { + + // Copy the bytes + ASSERT((length + offset + m_offset) <= m_capacity, "Copy exceeds buffer capacity"); + memcpy(destination, m_bytes + offset + m_offset, length); + + // Update the offset + if(update_offset) + set_offset(m_offset + length); + +} \ No newline at end of file diff --git a/kernel/src/drivers/disk/ata.cpp b/kernel/src/drivers/disk/ata.cpp index c8ed4ef5..96d7ba43 100644 --- a/kernel/src/drivers/disk/ata.cpp +++ b/kernel/src/drivers/disk/ata.cpp @@ -88,7 +88,7 @@ bool AdvancedTechnologyAttachment::identify() { * @param data_buffer The data to read into * @param amount The amount of bytes to read from that sector */ -void AdvancedTechnologyAttachment::read(uint32_t sector, uint8_t* data_buffer, size_t amount) +void AdvancedTechnologyAttachment::read(uint32_t sector, buffer_t* data_buffer, size_t amount) { // Don't allow reading more than a sector if(sector & 0xF0000000 || amount > m_bytes_per_sector) @@ -130,11 +130,11 @@ void AdvancedTechnologyAttachment::read(uint32_t sector, uint8_t* data_buffer, s // Read from the disk (2 bytes) and store the first byte uint16_t read_data = m_data_port.read(); - data_buffer[i] = read_data & 0x00FF; + data_buffer -> write(i, read_data & 0x00FF); // Place the second byte in the array if there is one if(i + 1 < amount) - data_buffer[i+1] = (read_data >> 8) & 0x00FF; + data_buffer -> write(i + 1, (read_data >> 8) & 0x00FF); } // Read the remaining bytes as a full sector has to be read @@ -149,7 +149,7 @@ void AdvancedTechnologyAttachment::read(uint32_t sector, uint8_t* data_buffer, s * @param data The data to write * @param count The amount of data to write to that sector */ -void AdvancedTechnologyAttachment::write(uint32_t sector, const uint8_t* data, size_t count){ +void AdvancedTechnologyAttachment::write(uint32_t sector, const buffer_t* data, size_t count){ // Don't allow writing more than a sector if(sector > 0x0FFFFFFF || count > m_bytes_per_sector) @@ -181,11 +181,11 @@ void AdvancedTechnologyAttachment::write(uint32_t sector, const uint8_t* data, s // Write the data to the device for (uint16_t i = 0; i < m_bytes_per_sector; i+= 2) { - uint16_t writeData = data[i]; + uint16_t writeData = data -> read(i); // Place the next byte in the array if there is one if(i+1 < count) - writeData |= ((uint16_t)data[i+1]) << 8; + writeData |= (uint16_t)(data ->read(i+1)) << 8; m_data_port.write(writeData); } diff --git a/kernel/src/drivers/disk/disk.cpp b/kernel/src/drivers/disk/disk.cpp index 48e36781..0d35a3e5 100644 --- a/kernel/src/drivers/disk/disk.cpp +++ b/kernel/src/drivers/disk/disk.cpp @@ -5,12 +5,25 @@ #include using namespace MaxOS; +using namespace MaxOS::common; using namespace MaxOS::drivers; using namespace MaxOS::drivers::disk; Disk::Disk() = default; Disk::~Disk() = default; +/** + * @brief Read data from the disk into a buffer (max capacity 512 bytes) + * + * @param sector The sector to read from + * @param data_buffer The buffer to read the data into + */ +void Disk::read(uint32_t sector, common::buffer_t *data_buffer) { + + size_t amount = (data_buffer -> capacity() > 512) ? 512 : data_buffer -> capacity(); + read(sector, data_buffer, amount); + +} /** * @brief Read data from the disk @@ -19,8 +32,15 @@ Disk::~Disk() = default; * @param data_buffer The buffer to read the data into * @param amount The amount of data to read */ -void Disk::read(uint32_t sector, uint8_t* data_buffer, size_t amount) -{ +void Disk::read(uint32_t sector, buffer_t* data_buffer, size_t amount){ + +} + +void Disk::write(uint32_t sector, common::buffer_t const *data) { + + size_t amount = (data -> capacity() > 512) ? 512 : data -> capacity(); + write(sector, data, amount); + } /** @@ -30,7 +50,7 @@ void Disk::read(uint32_t sector, uint8_t* data_buffer, size_t amount) * @param data_buffer The buffer to write the data into * @param amount The amount of data to write */ -void Disk::write(uint32_t sector, const uint8_t* data, size_t count) +void Disk::write(uint32_t sector, const buffer_t* data, size_t count) { } @@ -69,4 +89,4 @@ string Disk::device_name() string Disk::vendor_name() { return "Generic"; -} +} \ No newline at end of file diff --git a/kernel/src/filesystem/ext2.cpp b/kernel/src/filesystem/ext2.cpp index dee923a5..68e3a7d0 100644 --- a/kernel/src/filesystem/ext2.cpp +++ b/kernel/src/filesystem/ext2.cpp @@ -19,10 +19,9 @@ Ext2Volume::Ext2Volume(drivers::disk::Disk *disk, lba_t partition_offset) ext2_lock.unlock(); // Read superblock - uint8_t buffer[1024]; - disk->read(partition_offset + 2, buffer, 512); - disk->read(partition_offset + 3, buffer + 512, 512); - memcpy(&superblock, buffer, sizeof(superblock_t)); + buffer_t superblock_buffer(&superblock, 512); + disk->read(partition_offset + 2, &superblock_buffer, 512); + disk->read(partition_offset + 3, &superblock_buffer, 512); // Validate signature ASSERT(superblock.signature == 0xEF53, "Ext2 Filesystem doesnt have a valid signature\n"); @@ -47,17 +46,15 @@ Ext2Volume::Ext2Volume(drivers::disk::Disk *disk, lba_t partition_offset) // Read the block groups block_groups = new block_group_descriptor_t*[total_block_groups] {nullptr}; uint32_t sectors_to_read = (block_group_descriptor_table_size + block_size - 1) / block_size * sectors_per_block; - auto* bg_buffer = new uint8_t[sectors_to_read * 512]; + buffer_t bg_buffer(sectors_to_read * 512); for (uint32_t i = 0; i < sectors_to_read; ++i) - disk->read(partition_offset + block_group_descriptor_table * sectors_per_block + i, bg_buffer + i * 512, 512); + disk->read(partition_offset + block_group_descriptor_table * sectors_per_block + i, &bg_buffer, 512); // Store the block groups for (uint32_t i = 0; i < total_block_groups; ++i) { block_groups[i] = new block_group_descriptor_t; - memcpy(block_groups[i], bg_buffer + i * sizeof(block_group_descriptor_t), sizeof(block_group_descriptor_t)); + memcpy(block_groups[i], bg_buffer.raw() + i * sizeof(block_group_descriptor_t), sizeof(block_group_descriptor_t)); } - delete[] bg_buffer; - } Ext2Volume::~Ext2Volume() = default; @@ -68,12 +65,18 @@ Ext2Volume::~Ext2Volume() = default; * @param block_num The block to update * @param buffer The buffer to read from */ -void Ext2Volume::write_block(uint32_t block_num, uint8_t *buffer) { +void Ext2Volume::write_block(uint32_t block_num, buffer_t* buffer) { + + // Ensure the buffer is in the right format + buffer -> set_offset(0); + buffer -> update_offset = true; // Read each sector of the block for (size_t i = 0; i < sectors_per_block; ++i) - disk->write(partition_offset + block_num * sectors_per_block + i, buffer + i * 512, 512); + disk->write(partition_offset + block_num * sectors_per_block + i, buffer, 512); + // Reset buffer + buffer -> set_offset(0); }; /** @@ -95,13 +98,12 @@ void Ext2Volume::write_inode(uint32_t inode_num, inode_t* inode) { uint32_t in_block_offset = offset % block_size; // Read the inode - auto* buffer = new uint8_t[block_size]; - read_block(inode_table + block, buffer); - memcpy(buffer + in_block_offset, inode, sizeof(inode_t)); + buffer_t buffer(block_size); + read_block(inode_table + block, &buffer); + buffer.copy_from(inode, sizeof(inode_t), in_block_offset); // Modify the block - write_block(inode_table + block, buffer); - delete[] buffer; + write_block(inode_table + block, &buffer); } /** @@ -110,11 +112,18 @@ void Ext2Volume::write_inode(uint32_t inode_num, inode_t* inode) { * @param block_num The block to read * @param buffer The buffer to read into */ -void Ext2Volume::read_block(uint32_t block_num, uint8_t *buffer) const { +void Ext2Volume::read_block(uint32_t block_num, buffer_t* buffer) const { + + // Ensure the buffer is in the right format + buffer -> set_offset(0); + buffer -> update_offset = true; // Read each sector of the block for (size_t i = 0; i < sectors_per_block; ++i) - disk->read(partition_offset + block_num * sectors_per_block + i, buffer + i * 512, 512); + disk->read(partition_offset + block_num * sectors_per_block + i, buffer, 512); + + // Reset buffer + buffer -> set_offset(0); } @@ -137,12 +146,12 @@ inode_t Ext2Volume::read_inode(uint32_t inode_num) const { uint32_t block = offset / block_size; uint32_t in_block_offset = offset % block_size; - // Read the inode - auto* buffer = new uint8_t[block_size]; - read_block(inode_table + block, buffer); - memcpy(&inode, buffer + in_block_offset, sizeof(inode_t)); + // Read the block + buffer_t buffer(block_size); + read_block(inode_table + block, &buffer); - delete[] buffer; + // Read the inode from the block + buffer.copy_to(&inode, sizeof(inode_t), in_block_offset); return inode; } @@ -161,11 +170,11 @@ block_group_descriptor_t Ext2Volume::read_block_group(uint32_t group_num) { uint32_t in_block_offset = offset % block_size; // Read the block group - auto* buffer = new uint8_t[block_size]; - read_block(2 + block, buffer); - memcpy(&descriptor, buffer + in_block_offset, sizeof(block_group_descriptor_t)); + buffer_t buffer(block_size); + read_block(2 + block, &buffer); - delete[] buffer; + // Read the descriptor from the group + buffer.copy_to(&descriptor, sizeof(block_group_descriptor_t), in_block_offset); return descriptor; } @@ -240,28 +249,28 @@ common::Vector Ext2Volume::allocate_group_blocks(uint32_t block_group, // Prepare Vector result {}; - auto zeros = new uint8_t [block_size]; - memset(zeros, 0, block_size); + buffer_t zeros (block_size); + zeros.clear(); // Read bitmap - auto bitmap = new uint8_t[block_size]; - read_block(descriptor -> block_usage_bitmap, bitmap); + buffer_t bitmap (block_size); + read_block(descriptor -> block_usage_bitmap, &bitmap); // Allocate the blocks for (uint32_t i = 0; i < superblock.blocks_per_group; ++i) { // Block is already used - if((bitmap[i / 8] & (1u << (i % 8))) != 0) + if((bitmap.raw()[i / 8] & (1u << (i % 8))) != 0) continue; // Mark as used descriptor -> free_blocks--; superblock.unallocated_blocks--; - bitmap[i / 8] |= (uint8_t) (1u << (i % 8)); + bitmap.raw()[i / 8] |= (uint8_t) (1u << (i % 8)); // Zero out data uint32_t block = block_group * superblock.blocks_per_group + (block_size == 1024 ? 1 : 0) + i; - write_block(block, zeros); + write_block(block, &zeros); result.push_back(block); // All done @@ -271,12 +280,10 @@ common::Vector Ext2Volume::allocate_group_blocks(uint32_t block_group, } // Save the changed metadata - write_block(descriptor -> block_usage_bitmap, bitmap); + write_block(descriptor -> block_usage_bitmap, &bitmap); write_back_block_groups(); write_back_superblock(); - delete[] bitmap; - delete[] zeros; return result; } @@ -287,15 +294,14 @@ void Ext2Volume::write_back_block_groups() { // Store the block groups uint32_t sectors_to_write = (block_group_descriptor_table_size + block_size - 1) / block_size * sectors_per_block; - auto* bg_buffer = new uint8_t[sectors_to_write * 512]; + buffer_t bg_buffer(sectors_to_write * 512); for (uint32_t i = 0; i < total_block_groups; ++i) - memcpy(bg_buffer + i * sizeof(block_group_descriptor_t), block_groups[i], sizeof(block_group_descriptor_t)); + bg_buffer.copy_from(block_groups[i], sizeof(block_group_descriptor_t)); // Write the block groups + bg_buffer.set_offset(0); for (uint32_t i = 0; i < sectors_to_write; ++i) - disk->write(partition_offset + block_group_descriptor_table * sectors_per_block + i, bg_buffer + i * 512, 512); - - delete[] bg_buffer; + disk->write(partition_offset + block_group_descriptor_table * sectors_per_block + i, &bg_buffer, 512); } /** @@ -304,12 +310,12 @@ void Ext2Volume::write_back_block_groups() { void Ext2Volume::write_back_superblock() { // Store superblock - uint8_t buffer[1024]; - memcpy(buffer, &superblock, sizeof(superblock_t)); + buffer_t buffer(1024); + buffer.copy_from(&superblock, sizeof(superblock_t)); // Write to disk - disk->write(partition_offset + 2, buffer, 512); - disk->write(partition_offset + 3, buffer + 512, 512); + disk->write(partition_offset + 2, &buffer, 512); + disk->write(partition_offset + 3, &buffer, 512); } /** @@ -340,28 +346,28 @@ uint32_t Ext2Volume::create_inode(bool is_directory) { break; // Read bitmap - auto bitmap = new uint8_t[block_size]; - read_block(block_group -> block_inode_bitmap, bitmap); + buffer_t bitmap(block_size); + read_block(block_group -> block_inode_bitmap, &bitmap); // Find a free inode uint32_t inode_index = 0; for (; inode_index < superblock.blocks_per_group; ++inode_index) { // Block is already used - if ((bitmap[inode_index / 8] & (1u << (inode_index % 8))) != 0) + if ((bitmap.raw()[inode_index / 8] & (1u << (inode_index % 8))) != 0) continue; // Mark as used block_group -> free_inodes--; superblock.unallocated_inodes--; - bitmap[inode_index / 8] |= (uint8_t) (1u << (inode_index % 8)); + bitmap.raw()[inode_index / 8] |= (uint8_t) (1u << (inode_index % 8)); break; } inode_index += bg_index * superblock.inodes_per_group; // Save the changed metadata - write_block(block_group -> block_usage_bitmap, bitmap); + write_block(block_group -> block_usage_bitmap, &bitmap); write_back_block_groups(); write_back_superblock(); @@ -390,11 +396,10 @@ InodeHandler::InodeHandler(Ext2Volume* volume, uint32_t inode_index) if(inode.block_pointers[direct_pointer]) block_cache.push_back(inode.block_pointers[direct_pointer]); - auto* buffer = new uint8_t[m_volume -> block_size]; - parse_indirect(1, inode.l1_indirect, buffer); - parse_indirect(2, inode.l2_indirect, buffer); - parse_indirect(3, inode.l3_indirect, buffer); - delete[] buffer; + buffer_t buffer(m_volume -> block_size); + parse_indirect(1, inode.l1_indirect, &buffer); + parse_indirect(2, inode.l2_indirect, &buffer); + parse_indirect(3, inode.l3_indirect, &buffer); } @@ -424,7 +429,7 @@ void InodeHandler::set_size(size_t size) { * @param block The block number to parse from * @param buffer Buffer to read into */ -void InodeHandler::parse_indirect(uint32_t level, uint32_t block, uint8_t *buffer) { +void InodeHandler::parse_indirect(uint32_t level, uint32_t block, buffer_t* buffer) { // Invalid if(block == 0) @@ -474,9 +479,9 @@ void InodeHandler::write_indirect(uint32_t level, uint32_t &block, size_t &index block = m_volume -> allocate_block(); // Allocate a local buffer for this recursion level - auto* buffer = new uint8_t[m_volume->block_size]; - memset(buffer, 0, m_volume->block_size); - auto* pointers = (uint32_t*)buffer; + buffer_t buffer(m_volume->block_size); + buffer.clear(); + auto* pointers = (uint32_t*)buffer.raw(); // Write the pointers for (size_t i = 0; i < m_volume->pointers_per_block; ++i) { @@ -495,8 +500,7 @@ void InodeHandler::write_indirect(uint32_t level, uint32_t &block, size_t &index pointers[i] = block_cache[index++]; } - m_volume ->write_block(block, buffer); - delete[] buffer; + m_volume ->write_block(block, &buffer); } /** @@ -522,7 +526,6 @@ void InodeHandler::store_blocks(Vector const &blocks) { // Setup Recursive blocks size_t index = 12; - auto* buffer = new uint8_t[m_volume -> block_size]; // Write the blocks uint32_t indirect_blocks[3] = { inode.l1_indirect, inode.l2_indirect, inode.l3_indirect }; @@ -539,8 +542,6 @@ void InodeHandler::store_blocks(Vector const &blocks) { inode.l2_indirect = indirect_blocks[1]; inode.l3_indirect = indirect_blocks[2]; - delete[] buffer; - // NOTE: Blocks get allocated when writing indirects. This is then saved later in the write() function } @@ -595,7 +596,7 @@ Ext2File::Ext2File(Ext2Volume *volume, uint32_t inode, string const &name) * @param data The byte buffer to write * @param amount The amount of data to write */ -void Ext2File::write(uint8_t const *data, size_t amount) { +void Ext2File::write(buffer_t const *data, size_t amount) { // Nothing to write if(m_size == 0) @@ -604,7 +605,7 @@ void Ext2File::write(uint8_t const *data, size_t amount) { // Prepare for writing m_volume -> ext2_lock.lock(); const uint32_t block_size = m_volume -> block_size; - auto* buffer = new uint8_t[block_size]; + buffer_t buffer(block_size, false); // Expand the file if(m_offset + amount > m_size) @@ -625,22 +626,21 @@ void Ext2File::write(uint8_t const *data, size_t amount) { // Read the block uint32_t block = m_inode.block_cache[current_block++]; - m_volume -> read_block(block, buffer); + m_volume -> read_block(block, &buffer); // Where in this block to start writing size_t buffer_start = (current_block - 1 == block_start) ? block_offset : 0; size_t writable = (amount - written < block_size - buffer_start) ? (amount - written) : (block_size - buffer_start); // Update the block - memcpy(buffer + buffer_start, data + written, writable); - m_volume ->write_block(block, buffer); + buffer.copy_from(data + written, writable, buffer_start); + m_volume ->write_block(block, &buffer); written += writable; } // Clean up m_offset += amount; m_volume -> ext2_lock.unlock(); - delete[] buffer; } /** @@ -649,7 +649,7 @@ void Ext2File::write(uint8_t const *data, size_t amount) { * @param data The byte buffer to read into * @param amount The amount of data to read */ -void Ext2File::read(uint8_t* data, size_t amount) { +void Ext2File::read(buffer_t* data, size_t amount) { // Nothing to read if(m_size == 0 || amount == 0) @@ -658,7 +658,7 @@ void Ext2File::read(uint8_t* data, size_t amount) { // Prepare for reading m_volume -> ext2_lock.lock(); const uint32_t block_size = m_volume -> block_size; - auto* buffer = new uint8_t[block_size]; + buffer_t buffer(block_size, false); // Force bounds if(m_offset + amount > m_size) @@ -675,14 +675,14 @@ void Ext2File::read(uint8_t* data, size_t amount) { // Read the block uint32_t block = m_inode.block_cache[current_block++]; - m_volume -> read_block(block, buffer); + m_volume -> read_block(block, &buffer); // Where in this block to start reading size_t buffer_start = (current_block - 1 == block_start) ? block_offset : 0; size_t readable = (amount - read < block_size - buffer_start) ? (amount - read) : (block_size - buffer_start); // Read the block - memcpy(data + read, buffer + buffer_start, readable); + buffer.copy_from(data + read, readable, buffer_start); read += readable; } @@ -690,7 +690,6 @@ void Ext2File::read(uint8_t* data, size_t amount) { // Clean up m_offset += amount; m_volume -> ext2_lock.unlock(); - delete[] buffer; } /** @@ -713,7 +712,7 @@ Ext2Directory::Ext2Directory(Ext2Volume *volume, uint32_t inode, const string& n /** * @brief Store all entries from a buffer and convert to File or Directory objects */ -void Ext2Directory::parse_block(uint8_t* buffer) { +void Ext2Directory::parse_block(buffer_t * buffer) { size_t offset = 0; while (offset < m_volume -> block_size){ @@ -727,7 +726,7 @@ void Ext2Directory::parse_block(uint8_t* buffer) { break; // Parse - string filename(buffer + offset + sizeof(directory_entry_t), entry->name_length); + string filename(buffer->raw() + offset + sizeof(directory_entry_t), entry->name_length); m_entry_names.push_back(filename); uint32_t inode = entry->inode; @@ -776,7 +775,7 @@ void Ext2Directory::read_from_disk() { m_subdirectories.clear(); // Read the direct blocks (cant use for( : )) - auto* buffer = new uint8_t[m_volume -> block_size]; + buffer_t buffer(m_volume -> block_size); for (int i = 0; i < m_inode.block_cache.size(); ++i) { uint32_t block_pointer = m_inode.block_cache[i]; @@ -785,12 +784,11 @@ void Ext2Directory::read_from_disk() { break; // Parse the block - m_volume->read_block(block_pointer, buffer); - parse_block(buffer); + m_volume -> read_block(block_pointer, &buffer); + parse_block(&buffer); } m_volume -> ext2_lock.unlock(); - delete[] buffer; } void Ext2Directory::write_entries() { @@ -807,8 +805,8 @@ void Ext2Directory::write_entries() { // Prepare for writing m_volume -> ext2_lock.lock(); const uint32_t block_size = m_volume -> block_size; - auto* buffer = new uint8_t[block_size]; - memset(buffer, 0, block_size); + buffer_t buffer(block_size, false); + buffer.clear(); // Expand the directory if(size_required > m_inode.block_cache.size()) @@ -830,8 +828,8 @@ void Ext2Directory::write_entries() { // Entry needs to be stored in the next block if(entry.size + buffer_offset > block_size){ - m_volume ->write_block(m_inode.block_cache[current_block], buffer); - memset(buffer, 0, block_size); + m_volume ->write_block(m_inode.block_cache[current_block], &buffer); + buffer.clear(); current_block++; buffer_offset = 0; } @@ -841,18 +839,17 @@ void Ext2Directory::write_entries() { entry.size = block_size - buffer_offset; // Copy the entry and the name - memcpy(buffer + buffer_offset, &entry, sizeof(entry)); - memcpy(buffer + buffer_offset + sizeof(entry), name, entry.name_length + 1); + buffer.copy_from(&entry, sizeof(entry), buffer_offset); + buffer.copy_from(name, entry.name_length + 1, buffer_offset + sizeof(entry)); buffer_offset += entry.size; } // Save the last block - m_volume -> write_block(m_inode.block_cache[current_block], buffer); + m_volume -> write_block(m_inode.block_cache[current_block], &buffer); // Clean up m_entries.pop_back(); m_entry_names.pop_back(); - delete[] buffer; m_volume->ext2_lock.unlock(); } diff --git a/kernel/src/filesystem/fat32.cpp b/kernel/src/filesystem/fat32.cpp index 6816f1be..54279a40 100644 --- a/kernel/src/filesystem/fat32.cpp +++ b/kernel/src/filesystem/fat32.cpp @@ -17,7 +17,8 @@ Fat32Volume::Fat32Volume(Disk* hd, uint32_t partition_offset) { // Read the BIOS parameter block - disk -> read(partition_offset, (uint8_t *)&bpb, sizeof(bpb32_t)); + buffer_t bpb_buffer(&bpb,sizeof(bpb32_t)); + disk -> read(partition_offset, &bpb_buffer); // Parse the FAT info uint32_t total_data_sectors = bpb.total_sectors_32 - (bpb.reserved_sectors + (bpb.table_copies * bpb.table_size_32)); @@ -25,7 +26,8 @@ Fat32Volume::Fat32Volume(Disk* hd, uint32_t partition_offset) fat_lba = partition_offset + bpb.reserved_sectors; fat_copies = bpb.table_copies; fat_info_lba = partition_offset + bpb.fat_info; - disk -> read(fat_info_lba, (uint8_t *)&fsinfo, sizeof(fs_info_t)); + buffer_t fs_buffer(&fsinfo, sizeof(fs_info_t)); + disk -> read(fat_info_lba, &fs_buffer); data_lba = fat_lba + (bpb.table_copies * bpb.table_size_32); root_lba = data_lba + bpb.sectors_per_cluster * (bpb.root_cluster - 2); @@ -57,11 +59,11 @@ lba_t Fat32Volume::next_cluster(lba_t cluster) uint32_t entry_index = offset % bpb.bytes_per_sector; // Read the FAT entry - uint8_t fat[bpb.bytes_per_sector]; - disk -> read(sector, fat, bpb.bytes_per_sector); + buffer_t fat(bpb.bytes_per_sector); + disk -> read(sector, &fat); // Get the next cluster info (mask the upper 4 bits) - auto entry = (uint32_t *)(&fat[entry_index]); + auto entry = (uint32_t *)(&(fat.raw()[entry_index])); // TODO & here is weird return *entry & 0x0FFFFFFF; } @@ -86,13 +88,13 @@ uint32_t Fat32Volume::set_next_cluster(uint32_t cluster, uint32_t next_cluster) uint32_t entry_index = offset % bpb.bytes_per_sector; // Read the FAT entry - uint8_t fat[bpb.bytes_per_sector]; - disk -> read(sector, fat, bpb.bytes_per_sector); + buffer_t fat(bpb.bytes_per_sector); + disk -> read(sector, &fat); // Set the next cluster info (mask the upper 4 bits) - auto entry = (uint32_t *)(&fat[entry_index]); + auto entry = (uint32_t *)(&(fat.raw()[entry_index])); *entry = next_cluster & 0x0FFFFFFF; - disk -> write(sector, fat, bpb.bytes_per_sector); + disk -> write(sector, &fat); } @@ -165,7 +167,8 @@ uint32_t Fat32Volume::allocate_cluster(uint32_t cluster, size_t amount) } // Once all the updates are done flush the changes to the disk - disk -> write(fat_info_lba, (uint8_t *)&fsinfo, sizeof(fs_info_t)); + buffer_t fs_info_buffer(&fsinfo, sizeof(fs_info_t)); + disk -> write(fat_info_lba, &fs_info_buffer); // Finish the chin set_next_cluster(cluster, (uint32_t)ClusterState::END_OF_CHAIN); @@ -217,7 +220,8 @@ void Fat32Volume::free_cluster(uint32_t cluster, size_t amount) } // Save the fsinfo - disk -> write(fat_info_lba, (uint8_t *)&fsinfo, sizeof(fs_info_t)); + buffer_t fs_info_buffer(&fsinfo, sizeof(fs_info_t)); + disk -> write(fat_info_lba, &fs_info_buffer); // Mark the end of the chain set_next_cluster(cluster, (uint32_t)ClusterState::END_OF_CHAIN); @@ -244,11 +248,12 @@ Fat32File::~Fat32File() = default; * @param data The byte buffer to write * @param amount The amount of data to write */ -void Fat32File::write(const uint8_t* data, size_t amount) +void Fat32File::write(const buffer_t* data, size_t amount) { size_t buffer_space = m_volume->bpb.bytes_per_sector * m_volume->bpb.sectors_per_cluster; - auto buffer = new uint8_t[buffer_space]; + buffer_t buffer(buffer_space); + buffer.clear(); uint64_t current_offset = 0; uint64_t bytes_written = 0; @@ -269,10 +274,10 @@ void Fat32File::write(const uint8_t* data, size_t amount) } // Read each sector in the cluster (prevent overwriting the data) - memset(buffer, 0, buffer_space); lba_t lba = m_volume->data_lba + (cluster - 2) * m_volume->bpb.sectors_per_cluster; for (size_t sector = 0; sector < m_volume->bpb.sectors_per_cluster; sector++) - m_volume->disk->read(lba + sector,buffer + sector * m_volume->bpb.bytes_per_sector,m_volume->bpb.bytes_per_sector); + m_volume->disk->read(lba + sector, &buffer, m_volume->bpb.bytes_per_sector); + buffer.set_offset(0); // If the offset is in the middle of the cluster size_t buffer_offset = 0; @@ -287,13 +292,14 @@ void Fat32File::write(const uint8_t* data, size_t amount) bytes_to_copy = (bytes_to_copy > buffer_space) ? buffer_space : bytes_to_copy; // Update the data - memcpy(buffer + buffer_offset, data + bytes_written , bytes_to_copy); + buffer.copy_from(data, bytes_to_copy, buffer_offset, bytes_written); bytes_written += bytes_to_copy; current_offset += bytes_to_copy; + buffer.set_offset(0); // Write the data back to the disk for (size_t sector = 0; sector < m_volume->bpb.sectors_per_cluster; sector++) - m_volume->disk->write(lba + sector,buffer + sector * m_volume->bpb.bytes_per_sector,m_volume->bpb.bytes_per_sector); + m_volume->disk->write(lba + sector, &buffer, m_volume->bpb.bytes_per_sector); } // Extend the file @@ -309,18 +315,18 @@ void Fat32File::write(const uint8_t* data, size_t amount) // Update the data size_t bytes_to_copy = (amount - bytes_written) > buffer_space ? buffer_space : (amount - bytes_written); - memcpy(buffer, data + bytes_written , bytes_to_copy); + buffer.copy_from(data, bytes_to_copy, 0, bytes_written); bytes_written += bytes_to_copy; current_offset += bytes_to_copy; + buffer.set_offset(0); // Write the data back to the disk lba_t lba = m_volume->data_lba + (new_cluster - 2) * m_volume->bpb.sectors_per_cluster; for (size_t sector = 0; sector < m_volume->bpb.sectors_per_cluster; sector++) - m_volume->disk->write(lba + sector,buffer + sector * m_volume->bpb.bytes_per_sector,m_volume->bpb.bytes_per_sector); + m_volume->disk->write(lba + sector, &buffer, m_volume->bpb.bytes_per_sector); // Go to the next cluster last = new_cluster; - } // Update file size @@ -335,7 +341,6 @@ void Fat32File::write(const uint8_t* data, size_t amount) // TODO: When implemented as a usermode driver save the time m_parent_directory -> save_entry_to_disk(m_entry); - delete[] buffer; } /** @@ -344,10 +349,11 @@ void Fat32File::write(const uint8_t* data, size_t amount) * @param data The byte buffer to read into * @param amount The amount of data to read */ -void Fat32File::read(uint8_t* data, size_t amount) +void Fat32File::read(buffer_t* data, size_t amount) { size_t buffer_space = m_volume->bpb.bytes_per_sector * m_volume->bpb.sectors_per_cluster; - auto buffer = new uint8_t[buffer_space]; + buffer_t buffer(buffer_space); + buffer.clear(); uint64_t current_offset = 0; uint64_t bytes_read = 0; @@ -362,10 +368,10 @@ void Fat32File::read(uint8_t* data, size_t amount) } // Read each sector in the cluster - memset(buffer, 0, buffer_space); lba_t lba = m_volume->data_lba + (cluster - 2) * m_volume->bpb.sectors_per_cluster; for (size_t sector = 0; sector < m_volume->bpb.sectors_per_cluster; sector++) - m_volume->disk->read(lba + sector,buffer + sector * m_volume->bpb.bytes_per_sector,m_volume->bpb.bytes_per_sector); + m_volume->disk->read(lba + sector, &buffer, m_volume->bpb.bytes_per_sector); + buffer.set_offset(0); // If the offset is in the middle of the cluster size_t buffer_offset = 0; @@ -380,7 +386,7 @@ void Fat32File::read(uint8_t* data, size_t amount) bytes_to_copy = (bytes_to_copy > buffer_space) ? buffer_space : bytes_to_copy; // Read the data - memcpy(data + bytes_read, buffer + buffer_offset, bytes_to_copy); + buffer.copy_from(data, bytes_to_copy, buffer_offset, bytes_read); bytes_read += bytes_to_copy; current_offset += buffer_space; @@ -390,7 +396,6 @@ void Fat32File::read(uint8_t* data, size_t amount) } m_offset += bytes_read; - delete[] buffer; } /** @@ -479,11 +484,11 @@ dir_entry_t* Fat32Directory::create_entry(const string& name, bool is_directory) // Write the entries to the disk uint32_t bytes_per_sector = m_volume -> bpb.bytes_per_sector; lba_t child_lba = m_volume -> data_lba + (cluster - 2) * bytes_per_sector; - uint8_t buffer[bytes_per_sector]; - memset(buffer, 0, bytes_per_sector); - memcpy(buffer, (uint8_t *)¤t_dir_entry, sizeof(dir_entry_t)); - memcpy(buffer + sizeof(dir_entry_t), (uint8_t *)&parent_dir_entry, sizeof(dir_entry_t)); - m_volume -> disk -> write(child_lba, buffer, bytes_per_sector); + buffer_t buffer(bytes_per_sector); + buffer.clear(); + buffer.copy_from(¤t_dir_entry, sizeof(dir_entry_t)); + buffer.copy_from(&parent_dir_entry, sizeof(dir_entry_t)); + m_volume -> disk -> write(child_lba, &buffer); // Directory created return &m_entries[entry_index]; @@ -532,7 +537,8 @@ void Fat32Directory::read_all_entries() { m_entries.clear(); size_t buffer_space = m_volume->bpb.bytes_per_sector * m_volume->bpb.sectors_per_cluster; - auto buffer = new uint8_t[buffer_space]; + buffer_t buffer (buffer_space); + buffer.clear(); // Read the directory for (uint32_t cluster = m_first_cluster; cluster != (uint32_t)ClusterState::END_OF_CHAIN; cluster = m_volume -> next_cluster(cluster)) { @@ -542,16 +548,15 @@ void Fat32Directory::read_all_entries() { m_current_cluster_length++; // Read each sector in the cluster - memset(buffer, 0, buffer_space); lba_t lba = m_volume->data_lba + (cluster - 2) * m_volume->bpb.sectors_per_cluster; for (size_t sector = 0; sector < m_volume->bpb.sectors_per_cluster; sector++) - m_volume->disk->read(lba + sector,buffer + sector * m_volume->bpb.bytes_per_sector,m_volume->bpb.bytes_per_sector); + m_volume->disk->read(lba + sector, &buffer, m_volume->bpb.bytes_per_sector); // Parse the directory entries (each entry is 32 bytes) for (size_t entry_offset = 0; entry_offset < buffer_space; entry_offset += 32) { // Store the entry - auto entry = (dir_entry_t*)&buffer[entry_offset]; + auto entry = (dir_entry_t*)&(buffer.raw()[entry_offset]); m_entries.push_back(*entry); // Check if the entry is the end of the directory @@ -560,8 +565,6 @@ void Fat32Directory::read_all_entries() { } } - - delete[] buffer; } @@ -600,12 +603,12 @@ void Fat32Directory::update_entry_on_disk(int index) { // Read the full sector into a buffer lba_t base_lba = m_volume->data_lba + (cluster - 2) * m_volume->bpb.sectors_per_cluster; - uint8_t sector_buffer[bytes_per_sector]; - m_volume->disk->read(base_lba + sector_offset, sector_buffer, bytes_per_sector); + buffer_t sector_buffer(bytes_per_sector, false); + m_volume->disk->read(base_lba + sector_offset, §or_buffer); // Update the entry in the buffer - memcpy(sector_buffer + in_sector_offset, &entry, sizeof(dir_entry_t)); - m_volume->disk->write(base_lba + sector_offset, sector_buffer, bytes_per_sector); + sector_buffer.copy_from(&entry, sizeof(dir_entry_t), in_sector_offset); + m_volume->disk->write(base_lba + sector_offset, §or_buffer); } /** diff --git a/kernel/src/filesystem/partition/msdos.cpp b/kernel/src/filesystem/partition/msdos.cpp index 7c9b61b9..8e5963ce 100644 --- a/kernel/src/filesystem/partition/msdos.cpp +++ b/kernel/src/filesystem/partition/msdos.cpp @@ -5,6 +5,7 @@ #include using namespace MaxOS; +using namespace MaxOS::common; using namespace MaxOS::filesystem; using namespace MaxOS::filesystem::partition; using namespace MaxOS::drivers::disk; @@ -18,7 +19,8 @@ void MSDOSPartition::mount_partitions(Disk* hd) { // Read the MBR from the hard disk MasterBootRecord mbr = {}; - hd -> read(0, (uint8_t *)&mbr, sizeof(MasterBootRecord)); + buffer_t mbr_buffer(&mbr,sizeof(MasterBootRecord)); + hd -> read(0, &mbr_buffer); // Check if the magic number is correct if(mbr.magic != 0xAA55) diff --git a/kernel/src/kernel.cpp b/kernel/src/kernel.cpp index d0aad507..e9491e65 100644 --- a/kernel/src/kernel.cpp +++ b/kernel/src/kernel.cpp @@ -1,6 +1,7 @@ //Common #include #include +#include #include #include #include diff --git a/kernel/src/processes/ipc.cpp b/kernel/src/processes/ipc.cpp index 8d9b3674..f42f909f 100644 --- a/kernel/src/processes/ipc.cpp +++ b/kernel/src/processes/ipc.cpp @@ -326,8 +326,8 @@ void SharedMessageEndpoint::queue_message(void *message, size_t size) { m_message_lock.lock(); // Copy the buffer into the kernel so that the endpoint (this code) can access it when the memory spaces are switched - auto* kernel_copy = (uintptr_t*)new char[size]; - memcpy(kernel_copy, message, size); + buffer_t kernel_copy(size); + kernel_copy.copy_from(message,size); //Switch to endpoint's memory space MemoryManager::switch_active_memory_manager(Scheduler::get_process(m_owner_pid) -> memory_manager); @@ -335,7 +335,7 @@ void SharedMessageEndpoint::queue_message(void *message, size_t size) { // Create the message & copy it into the endpoint's memory space auto* new_message = (ipc_message_t*)MemoryManager::malloc(sizeof(ipc_message_t)); void* new_buffer = MemoryManager::malloc(size); - new_message -> message_buffer = memcpy(new_buffer, kernel_copy, size); + new_message -> message_buffer = memcpy(new_buffer, kernel_copy.raw(), size); new_message -> message_size = size; new_message -> next_message = 0; @@ -353,10 +353,7 @@ void SharedMessageEndpoint::queue_message(void *message, size_t size) { if (current == nullptr) m_queue->messages = new_message; - // Return to the caller's memory space + // Clean up MemoryManager::switch_active_memory_manager(Scheduler::current_process() -> memory_manager); - - // Free the lock & kernel copy - delete[] kernel_copy; m_message_lock.unlock(); } \ No newline at end of file From 7f379467b7e2cb1bd44478110bb87718214c4320 Mon Sep 17 00:00:00 2001 From: Max Tyson <98maxt98@gmail.com> Date: Tue, 19 Aug 2025 10:12:39 +1200 Subject: [PATCH 26/38] Creation of Files/Dirs --- README.md | 17 +-- docs/Screenshots/Boot/Console.png | Bin 6601 -> 0 bytes docs/Screenshots/Boot/Console_v2.png | Bin 38427 -> 0 bytes docs/Screenshots/Boot/Console_v3.png | Bin 44779 -> 0 bytes docs/Screenshots/Drivers/PCI Readout.png | Bin 29602 -> 0 bytes docs/Screenshots/Filesystem/ATA_Hardrives.png | Bin 19487 -> 0 bytes .../Filesystem/FAT32_read_dirs_and_files.png | Bin 148684 -> 0 bytes docs/Screenshots/GUI/Circles.png | Bin 18710 -> 0 bytes docs/Screenshots/GUI/Square_Widgets_Test.png | Bin 4495 -> 0 bytes docs/Screenshots/GUI/Windows.png | Bin 5458 -> 0 bytes docs/Screenshots/GUI/Windows_VESA.png | Bin 6975 -> 0 bytes docs/Screenshots/WTF/Button Text.png | Bin 15075 -> 0 bytes .../WTF/Console clearing in GUI mode.png | Bin 25348 -> 0 bytes docs/Screenshots/WTF/Yellow Tint.png | Bin 6838 -> 0 bytes filesystem/test/bob/asd | 1 + kernel/include/common/buffer.h | 9 +- kernel/include/filesystem/ext2.h | 34 ++++-- kernel/include/filesystem/filesystem.h | 7 +- kernel/src/common/buffer.cpp | 89 +++++++++----- kernel/src/drivers/disk/ata.cpp | 8 +- kernel/src/filesystem/ext2.cpp | 113 ++++++++---------- kernel/src/filesystem/filesystem.cpp | 4 +- kernel/src/kernel.cpp | 23 ++-- 23 files changed, 167 insertions(+), 138 deletions(-) delete mode 100644 docs/Screenshots/Boot/Console.png delete mode 100644 docs/Screenshots/Boot/Console_v2.png delete mode 100644 docs/Screenshots/Boot/Console_v3.png delete mode 100644 docs/Screenshots/Drivers/PCI Readout.png delete mode 100644 docs/Screenshots/Filesystem/ATA_Hardrives.png delete mode 100644 docs/Screenshots/Filesystem/FAT32_read_dirs_and_files.png delete mode 100644 docs/Screenshots/GUI/Circles.png delete mode 100644 docs/Screenshots/GUI/Square_Widgets_Test.png delete mode 100644 docs/Screenshots/GUI/Windows.png delete mode 100644 docs/Screenshots/GUI/Windows_VESA.png delete mode 100644 docs/Screenshots/WTF/Button Text.png delete mode 100644 docs/Screenshots/WTF/Console clearing in GUI mode.png delete mode 100644 docs/Screenshots/WTF/Yellow Tint.png create mode 100644 filesystem/test/bob/asd diff --git a/README.md b/README.md index 9eed2c3e..c0afee93 100644 --- a/README.md +++ b/README.md @@ -194,7 +194,7 @@ No user usage so far (userland will be added in the future) ## Roadmap - +#### Core Kernel - [x] Bootloader - [x] GDT - [x] IDT @@ -215,23 +215,24 @@ No user usage so far (userland will be added in the future) - [ ] Multiple Cores Support (SMP & Scheduler) - [ ] Move drivers to userspace - [ ] Move VFS to userspace -- [ ] Userland GUI -- [ ] CLI - [ ] Porting & Dynamically Linking Libc -- [ ] Self-hosted os -- [ ] App Framework & System Apps -- [ ] DOOM Port - [ ] Move networking to userspace (& rewrite, fix) + +#### Userland +- [ ] GUI +- [ ] Terminal - [ ] Connect to Clion with SMB for files and GDB for debugging in userspace +- [ ] DOOM Port +- [ ] Self-hosted os +- [ ] GUI Framework +- [ ] App Framework & System Apps - [ ] Auto Updater & Image Builder (ISO Release) - [ ] Store - [ ] User Switching - [ ] Real Hardware Support -- [ ] Pretty GUI - [ ] Port NeoVim, Wakatime & Some hot reloader - [ ] Create port of my 2048 - [ ] Own LibC -- [ ] Compatibility Layer(s) See the [open issues](https://github.com/maxtyson123/MaxOS/issues) for a full list of proposed features (and known issues). diff --git a/docs/Screenshots/Boot/Console.png b/docs/Screenshots/Boot/Console.png deleted file mode 100644 index aba6cb5685da50bed2a83b67a41fb0aceed26cb8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6601 zcmd^DXH-*JyFLL0WhgofU??({0Ra&-gd!2f0$2zTK&cXXfFLbW0))V*poSs>3ZX<{ zfFK=2K%~q>TBL{xq4$y@p@-1RjWcuC`tG0m=l;BVt+USB`|R@W^S=9epJzWaGcgho zk`@90K;+iV8|DDO_Z0v@aYy)gE!CYpc-{+yF*mvflyu3?@hXQxp! zA@kpFw1=tX%Y4o?d+hOJx%Y7^dlC9kR#Wd7) zw&9fV;>q*{v*o(cIFOhre~5yNXidqmHGQ(SZu74P>w;p7n%8E_w9ho1l@Se5 ztWB~WGIrgwjlso_V1!rY`?Z8>j>w2^o~u<13CEb7oOf2b)7G|hQy^Bln|i?*9eDUCbw3U*SIom1eTi-R?RNeTy-$KMrGSbkTzk$Qtj9o(hvGTlnMC)bWVm=ieTB_ZKT324oj` z?K@WwxR&vXYhaVm^`QqqQ*-^E+J40l5Xr6^j?S6?(K$!mr7ZV^1_&@+Uq?=maZ7h! zX{kI?0LHz8BUY?FyrP~{`ykob_alSY0;+z8x^>)-XUu<=rlGBz-M-yJb9p2=RhPc) zgmfw?;wDsBl7;J)>g^cl-6Ql}Xk$!+YS`W3>oMj&K70I+vN%^GYPucBpU8_;A4JGP zML6mK3FP;C-)!HBpU>WN0=Gn$kbDLq1hSuIZO<&lZcrA#`hQwS&C(031>p$8- z&Wf?i5GTIuzZKKgHb!#|GFwIv2wF#=obHc>F&Y$3(RB-?Xyx@QQ-kDNa8Up@iKZZg z7yE2zV|c;f>6OmV@~V;yrGz-6g{K1#Wp1Q;3*q4YT8^d9mIDY(D1+pVJSpFmKrDAT z0-$|-3zF-+hOMU*>{Or=LAw@Ec9M$b2Vcug2d-N6)NO&ZgmuWsz2#P9J)pKRwBh%e zR#>Zxzj8{YdQe`;1kdL89wSeN)#Gb>E1DlH>|+WKkIywk+;(F>T2}nRWaJqPX$b*( zEzutF-K^2Pn-2Nsvoze1KsV&{4-BGz!2^>2_%K zUfik8&bz>LVlizk75%)M%o4na&$ntTC_g)j7t5|cDV_Dj!9#5==rPSvaUbzL!r=Fe zqZ;k8E~0dPrV(gahnn;T`+Oc_nPHGGAQ&0E);iu3l6X)}5SBWS3aZYTTha;?{>Xtt-aCA0i&>7!Kxrtr|SEXHs}Yh zO7y&=AEy~V51pSr2DCIp&^)#ZhwoljQX4Ih5((`R#Ojvc?r0;hO6Db>Y;V_wJ-guG z-2)M_$$HQ8fQw87?XRfeujqR@#|z6nyF;rhB?Ll>k-i1zM-$U`Le}S&mLRQqn2sw6?Qy zU!-t|SWw94J}d3jQMus$WUb*BP@93*gzd5m`3-qbsXrcseJ{!}0ic)=_^{s@^N8k&{Y_G5BQEzj#tm_U%Xe>`>j^>E8eGyS<|^fvU|W4(o~x?H7mXD z^7T*?CVVfMDi*GN1-=MOste`o9o$}aH~;N0DCDh(ey;gqAG&%T2PeJLnF-%Da+CFW z=@J%bao=GGL&r(SXz>Ck_&mL7;uc?7dnR%h>LE1X)KCyV&@BNC)Dg4L@5{a%^;*z- zJ3rKMdHQH52AmEs=-Ba%Ktn5^cX z;w(_IY^yU6Sy%4kCm#zP+S81QDM(%Ri{j2SMoi}m^`u*fgiKk!U;THKQflHZj6N0o zu#V@AwM{mCZ630f0cd`TZSxNp%qzeCsT zgnFn}To=b6cj@*EZGRz=#-{OMb;TMZmKN3JB6;OP|I(aEqQ8@9AIMU>SkergC9>>(QaQ2{^H9_ znOS$Uh=j+~y4k%(|6)Sd0!itjx=XDa6P{nAqqgd19>j|KFa#qx zyW1z@kHrugNVT#u<4ejw>A}y zU1)%LcuiO8Y%hf;(vUJJ0EL_+Hc+S zTdFeS9imeFof)G7QjQw-RRT0&OUgZD<;i#2^#7-%F1IfDMtXjyZpHMioux+KUm~fb z>P8S?;z9ud8C}u*fOe*cAmDddM)W_}N+fN-4*>Ejzw;Q>|JL#U&#(M@r{kyayWBox z^<*{vU~l7|G!>DwCyzjFKAr<2DSK%N%jYz95}@7T%UWPQz?a!QG&ZKWpriMz4vmDV z0G6-elMg1}+AV3WqK|fRYWAhCkI?(n3{?Uw3+C9d{OE< ze*3%`IF`Xo!+?PL{0OmDi2QDM#t*=Wo$Y31x1}#%S46_p_m2QjN{^tO1DYmAS2uWp zvNXe1kv-UN9BXKtC_NZjh9+DJvN(Xyw@{ZJC3z9Qol#tU4~>0Bq=mycm;ao)NSZB9 z!kg*Pa!eqSst<(fl;UQ4vAfNzGq95!&(X_Uf+>(IEDe{QZ+T_u-#i>La~z5;A8=mc zpXpKbF};&H$!v~%`Vuzl@&PQC`0;E0;KPs`nUukY=-IF=Wm?Tmz4T9kt2?qGdB>@} zJIcP9yLy6Io{vM>g<9WAet?wVtIoS`j`|i%_e#c~)vWQSG_PpYegK9VLVYt~ z2^j95+`Z5{ZYytx!lhkF`!tu?wEL!XV`2;#i*6cq{HT7iq5Wq|jof(}T?&$>A>JEf z)78cSg#~udJM1(p@O>lTyQ1k6&CZWu_B7*UXU7rS80z@ITVD}4U*Li8k5Dc-yEK6a z&!IX&K6ym{H1OUo>F$FJrw0mg?%CVwU%*&B$I|#TB9`FJTKcH_cQ@f2o#dJJMQ&S=J|XH7Q2Hfd!0Pb&_7oBGk+|7H(eEP8lh zK&9C3k*h^dAM6~TJ~BVr8axH)5QDgaGh_;pY>w!xsw`R6c>OcDlS2D zo;R50WNDnwLx`ql;i~frtYuOv8m!UOBe+v`7V>3__o^jzJ`|`)?uZS;&lGpwH8$C3 zv#&q#P4F49DsmuS%@yrs_@v0VlbpRoBP)s!Jd-JTsIg*CWrksHks5<|=^C<)K^H8l z>n%2jf50L%@AC)KZlW;~Nwh1M`iP;9^6Fl!0BRJ(v#8TtnD{*O)j746^DQnpesA%2 za&^+AKAI@l)%(uI5lC$*Gz_;PqcB*@YDs3mpSrJ_a@g%Ko*syS_U+PDZ48{Hm@J&C z)zTUJdeVY2Ppj&K?frH>aiukbDd^cHSY&iBg0|XnW1vxIn3*^& z7&QGV%{cxYL|=IUchZJ(DX0No`L1(9G!1)52R4q8)%J`QO6Pl3&e==P^$`vJ>pP~U zogR9xwD5P=^GSsdcdfJoCO6xNV9piO)tjsQrn;p(B+EFyCnZ7@U2CK2Kfw0EUhYnu zj=~P$*-b9>+{1pG-|DBUw7tGnM<`v-^G^&{xR0Ny&t277^&(KtFE^~VfM6E2nR-m{ zZqD!)JZDG4v|(`2e(#QMk|6|vOPz^@iI;ge=lx-iO=YkMmskH*#Z~UZ#aZa`ZpTe$ z3%RnI^wIml;x`BK3)I}dc__RStISA5@kzovY@>ztKA|xq#cBeO3!19ZYo$zOmR?|} z9m@XwnYg@$)b_V&WW5fa1U+i4!Oup3m0c}pm=$$;&-1Ft=0UP>mYl%F^gOwMkJ?4O z;u8O30Ve-CE7cRWWJRg2LNveeBt@*h*R--gcKkPXp{h&qpH$WRWRI8~CpX^$?J>ZvI z(!69PA5EjyuD5^$xij6e=`&`1@~893D5ZdGo&$^Kzx*>b?E*FShfnB|uo2n{ZoS5I=Hzos-S_rfe& z?K9992M5WMK22X^mA7qL1VD;RWrh4O*AfF=J_eUiSrpi|Z4REPnvW z5_|SIAD4vge{s%zUArTSgOSiRRMkwOo-;yU`F&BIhiuK%sL+quC$&9<0xuy>@TPPS zo_lnov%@~O;^-xaG9CW*Xkg#ecaU31W$*h+$0a6#?WqN6jY}1~ z8#=jr2Z`F{>+U8;RWpXoy>d$Zrs2Q;lx;6am4T00(p_?v-#pdVPBO6D(Rbdk*L~$N zwyKALu4DJvRe>!`R1GDsFuV3u7WTKMgbI#oN{z}w*!+T-Y4Es6`5~Atrleh>%A@tS zPdiHdrWj_r;m%F~(g8w48H%G;)Qfwi;0gn8OMHKm3WUp5E?LODXTY0nJL*`UuQ6OZ zydMOd+m0##Lyu_tcu~0%gJfGx2{?M>J;nAO*NNCs)+QQ=6aPd;vr=)S!8cVt$_!`G zH6`dwY^|o%eWcM5Tu1UqMfcC{@6sdhczpi+r8%|;AYH>3%!^Zs!~ z0S20w0WGxWY;34dot zoVm?FGGWE*`!I6+SodA;Oph1gIG%nt1o^spQ4sidjkjx?KesM~v=7|snlAJ3ca&`i zx=VA8_IxodzM7zPsX8E}?e{4QQKQZ(cZC=O0{_%7Sen%hXq(;;!T**fyaAOv{hL^px$cdw{JbmcK~{7G#%=IXB-|o7p&p1- z)b*y6&l&m+$QX_G@$QSz8#l6)NWT)S;#F9eO)k&;`1YB-N(zD76gWTZcYf5u!N-pV z)_|_kvk&5BW*ked(jfI#(Sg|IHl6EN0$sk+LvmESf}`WwEwYRo7GEV!9Sb>A*|>>b zd3EtG^^-Ei=<2tIG%U}pop&|h+@>l7`ugp?5mCfB*0bl$c~z0${nSLZ5w`9=@gzI` zo-1weeDj+EGV0l?bB8lbQ>b~G+z`>@e7n=hHoee;$cjSeFrt&U>*TV{=;9ItI2sfN z>3QC)JtaywS_Ib;SB7^Th9NlvwqbuX=1ww}lQLIWb=eWxQhaS6G@ARA>6Dpm?h888WVtq3QSK9&8sd%{0-81DR9abTxiw?z zm=de^cv7dOIJL(GAfKO zd+#-X9kY>2N4fX`W8nRY zJ*G!0X*ei(o+#Pp8q48A&OL#0wY{Ykz4O(5Sd=Rq1Sbc(;v#lJp;QLpXZbyolE}(g{c@BZ0O><_J;8v7h2?p1kTmlVm0MoVV-V4Mq0m@9ixfaP z;{XCRo!AWmMY)E7N)h-fgOZxR(TjP5ft_O-nu3MQI~o3{vAG?hOB*e=`mQWkx1O;QQa{RdB$pB z=mMNDdB!(v>mwLF*(|GPH9sd7QJM@``|fcNX#C;lYkR71PhQAv!pRWTJN)<1b9hI~ zj(KwQdAXJ1HW(T2^^-P?*Cq)Cu#$0FM@3Baz8$6&%w68${6t!P21MmtuAvx}J z?%ewO@zsOIiZt-r%K7l_&tFHohmBm2_9s20k5!sJCPh>@SfRNi%o>_Di`7i>(0fxL zzbSQR_|fe(?bZ!k`&x`{$t0loorc`-Bk|7>^zMbyVEHwzvN!uPKsvft^F=+$dN}yi zt6;uheyZQ_IwzVY;!(L zJY_yFxjH(ug7@d_@2nICG!Y`OS#--%e`y#h!|DVt4h}cJJ220%EY(|o{)Q*2puyjq z?bGb#f9jr*#|ZGs+rC3#{mpw-0KW3qmFoZohgEi4<@c*x+TFVO zU^D436xzn9eQZ-a=8-j;DW2NjK&^x1no@0ji(ch#lwWDwcid@TYYnQt7$0U(O^@fZ zs~t+S;2pw+(6KWY>5t!K_RT{lE7<44n&~;F#ak^$Ut|)3;eDjUvC0l1U0B|mBv&(N zZ)ujwP^M2g;8zc31A5@I79SUk^X%9QMKz$R3bJ|SLSmf@$F|CH6t}*Bt)L~deE@g0 zZ(c6Om4o8oH0_$uZP}Yo#0c~8Fxm6T!;HX=fWjQ=n^l%pHU0BYB0swl0_C&(ZVr@5 z2j=lraG!EMYq@Wgw0hRoT)s-xajRs_E z54ISb>uEr2HN(B4IRxwTOQ3G-Sbpg|p2oULIU}ox$PyAoJN{SBh%r#J9u2)$&v%+J zPH}O!qj%L&PkP^H{Krf$9s&+ukjV7{)ztd$Rcvk@ ztkiiy(~2y}AvkjmJR)0GkBL^-zaL84{q*Y*h9kpE9*zvkSytN$AmOCnIz&OfPz?o3 zkm%aa)pXI}(&%+B9k%t9Ti2~@alp}sHn5J>8jGf6m(*VsBDW;NFEX2>eZVKEVZ)Vu zF&QFZ=Fe3HmFsc`!QvmOep2-F_sB7>)0wbDSm6f$G!&-f> zE=YjDq47H>2C7{-c*65zgRphz1wotKm36NuPsmkBy=eG%EX*F+{uD>TN#99r*>MhL zk?-=mD((D}s2>J?Ci67p7sp?LWPr>=7rAX!N85DDCq3RSe<<*A9G#gD-Q`xWe#uQ1 zRzsoGuwB%HW=sk9^{keDHeNu+pql52pZBxLj?l@&r1&tk>MS@TB>e7~CNm`8Hh@=e zAKWYg(8FI>^yW1W<F$IcIXbHPCVSFMxQK(-4+) zXYNN{_;4@YFR5?dHsUIBwgf>0B)tICE+D691IA2Lc%XS}#<~<*@8H6r*HyoWE7HYL z=FTzQP-I)XfDWQ9ZnW3YCSdKHh!8eBc#&~MiiEOJxCm|{Zgv34moF6cj=z8Vwl{|D z`x|(P{Bq%5TY}Y1h}9#j^Sm-FJQAWOXhSzJYs{wk0O^)kHBSa3QYbS6K?`@#~+E`}`I%t&(k;qPuDpKxHU{>rSo2k@T zc|INQW+DKH8x8*p;sGJ(Nd3v`pRV>Lg_H3fVPeTuR3?pIon85V=~M5AZEx^HSd<%| z*g`r{F-j9O{r~a^5Ge09T-tBt$)iqNn;{WCMZX_A!EAe1 zOT+KuR|54(A1Y<~M$!;;dTr?{u}&M7g{nVjy`go6v^oT zuu5sID-&XtH7{Y>!pGwOkK9T4bzdwq+4tbxfmNa-qfFMuaBFVyfjj}z>G|eXZ9Lw< zBgO|*wQq1U$N#QT!@;|q0(*d}J&I_XN%mp= z0@w8|aN&8HluK9TUK~fm<(gOnH0et;!iVEn9@OHi|9l@w&qzIUFH{-|2GYaB(6uv3 zh0ElmdKMmL;0tcZqL;}A!wgJw+e{;~hyXJMsK?f!JixO0Yysks)f4R_ z;?~>$7y*Q1(m=8qlXL-xas_kq@rf(Z{M3pqjG-VK$I*_}@>P-M@W&Ln@vI6HjZwyLy%z!g&V@L);?@24TaM z#l2vwLGP<{OEacz<*_U}b|F+pF64NsJeWeaRip(~bOPn=OE3|BJvTKNQ-K>q@=F0A zFsL~V{?vWAVk~Yw0#o$>bB-U{bJC2xvaYF2`EPb`IX44@I+rvG$)WEm=Y=KdEDqrv zFmb0BJZszM@7~qT|dOzZ@P=yn`ROKk^7B;toFSBBwhZsPniipqytt?xobXhmGW+Tv<&%eNDFE8r-q{e*KEcQxi z@bx?0>w&uab9}dR84a@yCf5OU=-0nV+9eGI!WbI4d)J|tLyc#o#=N3~mJXD?8!ZGg zLD?_}b*hcSpmO8pM(p?UGUfFmH7yUwW}AU*Hk4N@B97aKkCk!y|8H2=#hX?vT0X4f z5uzDe&JQR)$0R$J_m&}h*GUhjP*bl2{x8z=Y_SK#{0XRC=P6-LgE$}k5?KQP@Fe%y z$S@{p@*K~~%-}@++)q?y9X_luA&~l!3u%~1lxur5S6b(!cH!8q_6>W8|3TasR*>_I zB2%0%j=!JoLN!pypY!;#1Rlu&+#vCuX_l{1vqKh|3h!_Ik?CN2C0lfcoC7Z`UHBM^ zF*8_P@e#RSw4;{^p_2~~;;3#uQBpCR7>Pa8BoE7fr=g~(V@~OJ&*am+cvObl0<17+ z;6jrNebA=9`FX_xjBIst&}$dvR1-d|i4NhZlTh4Mds|B@N4n$r@>tH)zKaREa$&Z7 zbkvR_^%#aR(|Vn0YVwa-X(NGuQSqQ_KwXlzO4mh?FBL|UigK(b^|EuUCOR?Cb<4=| zt3l`ME03pYS|=l*gS*&w`#xJv7x=LH@f_xxpBbCw7X)o+@>G6!&4uYA4a=vOAD8C7 zio*44fZvz>ulT<~NA zubBa8yv~PH` +;aFZj=%{8W&<}m7F+fe>4PB7C1&Qz;TxjzRXc|m{`^bk5paSD{ zTITZKX;~Z&x-i3F1oB)LZ~)btWYlkw-(c+_51cpSJ^#rDLanS&N*MDVhi8$$V@xPecI+GfI$T$adB)^hLhk1AA z+rpB(qg$-YbJh)H{dIhy>USI3@L8fhOPp7R$o!Gj zD-=rqH(U(XSJxYFw*Ihg#(O%zc5+OXDw5Oiu84iy2V9AZpB6HkE|MeG&mTqhl5hvzHlti z?6K`pdE1Y#i*hCH?`%@naXht!+cIY=;N9sH);F7#vVoDo>%TDCXfQ0d&zXPhqZ8+8 z#^K^84LR$+9r2o#r}MM$e@o}k)q*qNCM-SVDiXWy&sM|jPt(d&9{Aw+P*--nX9x7} z`j_j@&j%`0WljU_FX|m4P(6M4^g!#m$+ob6IIXvIN)jf!FsvbUpIL``UWL5v81ElU z;_K^`@NH}<>#_ZCS_=@aS#nN31zFd#=EZTRZRy@X8^vxY9?YE{Vd(~pJ}-MOXn-^3 z{_3<-3)gAmy)Qv2Q~57-ZvZt4VSTB4-pPK%WjB7Q<+|PRm*nK4QTPwJ=(d3X4`2Uo z=!w6HqN_8OUm~mTOH7` zRD1!#g^u-jyD$i{)!fGUC*HidfmeOY*DE6kw(g@Scp-hR=P;wFWB~^7GFz|qAuTvC z2U_pDQ>%e%47&||m014{ljpt7-qJ^mFGBw+r_V>PMalnaJ@8NC51>ZMClDTFntpe8 ztZfDAx*)hDL%bzdzi|0q^prI}T5hko>jclPWkx-xkG&~F64+#-E5CMjU=pC9f48xq z*^} z19aUSzLzxgKkq4*OZQg#PvHu>d2IVOo%3c$fI;lXC!qku2FNZTD+0b0e`t9GHypzKUFbUTdbihw_jut`iuWbv5L+ahJ}F)h6TodG%GU0PQ-148uU9RO68Oy;N8qnpdCo$c_DoK=reeOYu}2AY8Y4gIswH01A&kig7r2PQLwRPj{$_9w6PXI6v!yg=k6`w(3aaI^?L8a-ydg#5uj%|Mm@SKWT zG!YS6ivn#++7J=d}NM|`pB1;Xq=R)eX| zcUKLn0geYQ#bEtAyn&oR&LKAS#*Z^Fdir&3@rO0-HT3Nr}a)DKrBDKZn*Bl_d9Xlsaj&i6zf)&dF3yg{O z+8RSL5saeB1ug_IJTTt|%l$L{^^nj`@gbgPZ82z02f_i5V6E1BVFSXDSo<59yMSSu z8LSXeY+Bi?TMLXG*K1R8*!V-Hm9LxofJq1JPiZ~TQF z3P2CCAerO>bER6_mi~k)v@0@9KZ{n)c7^j6dnC2~bV9H<^0@eHgfNDkM>Js6lL-Wjj&z#;6c;6J zv+2wm(P!B91#lC7pIYv`L4l%60B#Y0qBs1e1Hhw?WD9(=X7Y0K2rjEBf1?|uq!fMdbz4Xr=D7O z)L7->a8eWV-bf=qduCqgC85~^dvwnxd}l+ckhS!(A*_u%lJQbyuhLuJYLJC@s{{9}NkcGMa0 zb+@tN_fP${*j~DrzLkkZczfu# zq~yl&R_IbFu>3c`@=j|#AT?d%?5FJ>s(EL*Hg%v(o+B(@|8nE=AxwHy!bVPzWJ%K%6Zf{RLW^%*dk%>NeQj4+D8ROEex`UiP3cZ1{9FN*~r(3HG2 zYn3fK;R`fxxki*x~bZhO4i!RROL11c@mmTu`TSzlL zq_1pbxnyukxk~5H>!4Dy|DHo`iu-jIy}nK_iA5iB05`5R&W%nbXJk=gJ*s~%G+hu! zg~nlC%nJ_q@y1eOlhJ>{?9kG3Yq+7n2o#Qya-XYAwilF+cfdOOgEcl==gyQ&=#DU?b?v37knn*tjiCo)S??>u<%)kX>6PobcF}|m6|YIYDU%ZsH1%^Q@ls6s1(m-T}p6Hu$Y{3(kDYSZbe8h%3r4U=Pk zJOJ(8^zQBWIrm6?y$~K*>Th%E>KjLm8<$(}8l3FX@bn5y_-P^U7jxBmI+HZpFu2Pv zvdn>~g24I}Cz?G;j$FOGwY-y`P$vy-t)3w&egR7BRe`G3{r5ko>%akSYn#O?(#1NQ zJm|h{Y~MDDsiBclk&rz@d=(ZF-Pt6<30@+8qYo*#ty+F{Y3y8^(GjW^MQdTcc@MA= zd>aVMrz;C-&`UKxr*BoOudj;B?Bzcs){kcTzU=XO#`&r|_h?J(uBEGrLA{6wsi#(c zWpP#hSazW=b*K&xY^i@b4vhQ0qw^Mo&ox65Exg|={IP==`nj7{vUBS7eLN^@6F)S$3q1W<4hfX=dC*W;>G&A?D zMubO~=3mvMO+#Uqw({PW8EUt}^EWldvPW7wn+9rGD=aP3l%Jli6a;m*+>o$H$zOwC z#QP+(@^i9ER0P>c^z$V$ty7H%#)I1Vqre&}vPNaWP1wq%*t9A1$0PV zp7>C^gFaV27U?N6##lL1xA^D73TR`aUHqhN7lv(O3XVHPHCIrU(`Rg6Pl%p|SSSbz zJE&3o_ca3iVS;Z{bfiM~gd&R<$c0@b3@Z?&qd zvZ%_(8fz7@ts-YNsHdKO5DnNy7R;Ni_H_CNT1bnvXT;oj++KeT(Q18 zIdN0^dOYFLg^z7wVO<$BP&^>xw;gZYMJVN{{iVTcU?)f2m8CYWV#u<$rKmXIF^#yG zg{ihY+a@ZydT0HZW>knjYVyVm@%3*ntl8j!fed7B79vGw&6PxT2(aSY7p`hQz+cYX z!pG5}2h_QIT|BK`7;B64YAg0bUmV|M?nToB1sTiprV0RcrF&W@69u48p;4D2$>Tq! z>sBYcOD7MbfkvLEg`-(drMLrt#3qL%&qXbbEE1$yI2DHJJQhrjFCf(hISyx6EHG4H zcd8tw1Gnu^@cQsi8G0>Hy`^wNdH1*9Zo84N;o&u1597v5-B9V32&qjc|9RTIxqX`& zZTY5urWq-HzkB@n(W!QvdzZo1yKiGL@oOAKDJ=L$D0|VTpSju+5D6ur)Fo6{cMhU} z53Y4w+1;j*s zxGU~QAaoYm#VHoqR=cRH_@x*=CC#TB-_j~E*m+37^T^Fj4_u)tsQSpT0xjhqh^zkh z`o(I&OoO_DR`oRzcAZ1<8<=xm9RI)^7o(KSnp#&>=fg>|`;)Wc?SidfffR z&c%gAutB1|!%d(*$>+o;F&06WPEV-pi4IS&Q+;v_#NEQE&w|kJ&QKqtv%UlIw*B~|CRd$6LGPQ6k1Q)m+Ogha$kWRs=D<#3eo5;@*&yBbX~Kc-kz*>nrQ_G?Bl;-PpkVBDdL* z57(HvfoP6H+1^uYuNt1wtAB+vOpw)CCnemX_p(O9Oa0(A+k<|uy=?9mxcyxrz}ojV z8^#=Sn&CchIw8G!n*IKMKhySTSCAQh56TU;NHuRxP`=)}@F%#8v!h8k9-B}7putW{ z&I!;cbasMcw{kR+P0lyc$)HE zP0)eqT3)cUZTyrdXEVMIKvs6LT8nY0;`6tJW($Ynp+Zwu&xUTKhCaq+OACJ}5lBwh zAIN+`{_3gx{>jVIX`$`l7(j^bnqXkbz+V5~j8Gmu_7?+!E_3cXYU`>opoY1-DpA?8 zcyYGVceF}&Q_Cq#A$X69w~3=KB&*rhq1-w#3+w}-zSdO!g=+0+DY=jd>~IPz#o4fv zoImR)vF2pa2UMh9fa>01@M?SejDXv*S@W+yBK1M`vWTK9PNTse9w<^MiAhwqn&@0 z$>R6G1*{&82cT~=5XS$IgbOWuT2<6NgPy%SZmx3a%1V&Fo5TcOOJgOY8nRy8oYiGn z^EG+Kw*qyS-qXuDJ2!G3gGng`;jJzA6U_?TBiX{=gy{{j^fP7l)3C^f(f(nFO;H!kc5qAge(t4R_6MpFE`QpljIoRm0@N_40!kqHN&cR&FbM*M7`$fLtW4Xn? z5SJ!%Rd;<(MLp&K4CSK4)A}PMUGU!C#ugKp3!H)i{}kJKkhX$f9Qix&Wy`HEEAtOu zxUgp^DjjAqSmc{f>7?e_OJ-yu zHQUoo&0F^k3>#iOkT)|}3FrCNv3vb(u7%G?Pdz|`zZ+&xOJ-aY7+I=u1{$|;o&>>Hq4r|6 z8Jo7YZ#Zj|j#&G~xn`<6j}3(*gmVr1VuxVg5i>8N$*j?356a7w$Bymx$py$|#15nk zL9&+GJ&4@`%G(6+ozzv1l1qB|4ioEBQyB`k;T@yNv9KmqSG1%JwIx$}`P)N%Hg=Ni zYw&vrpxV9#y01HT4+Nm8pHtMYg`x`1ri8nXUI4M^Vfi^tq#jhBqjn9GY=qG{LLCPV z2-Fq!ZAV*-SNs!KL2D4D@TZp)Z~Gm(udI96X{K$fqLGPe9gO6BZ>}9X`3M@wv95!D zK;epCXd-4?7#=Eq!kaTmZIXj1<|?}+Ytm?b{&1`jJ(Wes!kn082L6CNlgl#|MmN*S zQPgQ|;ygb=`BNrvj?zr7o#Xa*JyQN_ zY(886J)EmTr-&W&ulSu*E)W*Z5LN!_f7W_Z!%sihv|l9+*pgQUGpPQ!ZZLgLs5jdo zvUt#7g5Nu~({NV2L3jD)q8~K)Hg7DS7;ye0?Kip$4w@j7f zY>~uaUjK%bExE=bErE{ci+1w~%5MU{sxcbsW*%mY>2L-fJ)HWi`1}&)C=D)9TrIF1 zBH^$_Yh{ZmP*QBb3jmz$P{T4&Mm_o+JhRX{lI&Q7@ANAtCe=q&k%Lzwn8oTNm71KE zBMxJK!7~(NVW_h2RL$?3K&1-Qv#dnlLCf+nI7^q6$k*ryFd&=Nwr|K+wI1H+%lBm% ztUg#QfIfIzZNS5xLSo%MSp0mQXY{Tij7;8I$7eu9-I=DdU4`@+D+Yu^pU0=ffVW3& z{r7}8emNI(=w9M0C-fA)P1M>6ag)>+=f98q6ak7wh9@}!7ToV3_?x(BC}_tG zJH7Je!gw*V`mD^t?>W7s5e6P7YH!$JIV5;T1ol9IaZEe2FE;DVhY|Y0^m9_r)bQO- z29-9SY0e1|;W6e$DkP)U&vuH5e-k~YH9Z9-?ZmLB-zwaMRrzxUl5t*D1cL2(W&^OL z$<5VB$9U7i(s4*GcRRg|WAFn3ZL~1=a9=a^46d|-Fe+ObOh(-%Gck8E_wipGm-6zk zG{9bYs)^eF_~Yala8Bu>(p|qpbsnV_u5fRLL*=5{v5Oy!Kq?ujZ9<^!1B?6@b4Vy% zi9jXKaHgDIZpL(EU{N1d7*tVTQnL-5JVJ`KlRdDAi9tP^8$t;`j0iltz<;raUouj) zMi>Bv*r#3dG1TzIX6<8>qNkIH;0FqP-{Hc`PD>wJLsl+Gw4Mg6jO7mUHI=tw$vYgx z8;$1P%50nvxJA>u`x+`Xp|S#H1i5PUkXf)TB8 zEuV!?Z#g%3um|OX#&4H?#vvcWSl@ z09UEo0goj6@ZDitQ(?qfBG}3Wi+GqJi7Uhv@XXxGN`9H8KQnFjq4u`+(d zXA|%Nnz4Wp^69b^@`mC^%)X@vmn78Q@GM2DS>d)T`>zHPnC{r(9a;sv-47p`aFDHy+C47qr7Hf_)ky&< zu5B?E;2$1mhc;cSb$KbSShFXc@) z77Y$V-@h;6?8a(}b~qM&UWAlqZTt&*0P58A89^^w9_T5l>BQwp@uT>Q_+nSdNN#42 zp$n%b_U)>VX5f7Y-t{$hY|Wq4s|#BnKX1_}tR4hSuO{lEH|!upoiA-gPxGZ0P-7JG2HfkawS=W$7t zKPs7ygG+J#}5$L zw(><_pDxFj$K6XA(-ljfu-Bwh_9=BXCY{UHgN4OWIy5iq5CZCVY{b%Oe}5ULGcRFq zU;#t4aRCp#N7Q3mmhQx>=eW_nmygFTLULtS{q>f4lk>WQOf%gQWW@XBS_Hn>!G$WQsPqM&fk4QW z%XS#(f?%S02G}%c`XU&S((h1_`SK>QGsElm^v#%qg$p${l1&;1Za{Alilom*HmFTl zHY--dD)ADR1`vpuwQy{w-IJhW0oiSaxA>u_nS%)g6vY~^A^pVy*n08zA5k6%#GE4) zzy7%0%JSvf+EtNkeuCJ;)-F&-^19z-T&2z-cqkG!K(Y~mm{Ko8DuHzE0Wj1keY?FI z{MhnkBwklgJi9}sGkXfxVpu4yX4uFPV0as=RKAzJAMIDm2-dp`Xxk z+f#Qsd?t}dQw|-w3RI%77CQH~zVj@;Tov%T$ePYN0;1rD*>v);(KY9$S$a-! zA3<8%%pNdGqlfIBUwO+cnDe`Fy|o1Ak+lI-qh|zc4Qw1a?xdPi{ptFMqmAH=V3TU* z$`7{Uip52`d2zEzM(SbDsP2|M<3<^vs>}1g6Z$(Mmju|7M~Z_x1K-ATEa`}~^U_9j zBI~sfwK7LaeyNkqw%(?WiuWH~p-Yhh1oE@!59)IQ^I7{Z7c@m0j)U{}IHgo&(9qU( zTuj(0GE7Lup(lqRJsvTJS1G8b3gk=Q-es>xqRomzYu8zusB5WFcP7e5;`E$QZ;x`h zzli53n_Iu4LkvE?v&bo-f4G41!bR^{W=^+V@8Sfmg&>5JA?4Htv#g|xFVIUw%AA&~ zGv?IVTg<%KiXDiz08;4{$* zApFgB6s%=}IHw{F4h>0wEx_)^IfohTro}&v6K&!N!-NF)&(FX%t5L9Pw=8>lMp_<1 z6n~1EQ@5ax_)9V7B1y-F+52eCRww#5m{U*tA#^RH-McvgNQ1%7P(@K}i=6|_!t9mZ z-c0m+8}OB}bjn3vJc`Vwj|V0$3>i7U!WwkB#jcg9(+89&-`QA#TECG~54~yNFwh4L zq2IQNl$ii`mFuY|>BDm8ur(4#UsUj<)AMiRb)s%SVG)56Y@CB(qJ)OX+lF1%NQgAw6*Vfo>_rP8FJxEJgD7uMR;h#e7R{&| zfhUs&b$Z!~pm0r~pBYMf3G_0)=GO6wH=MhJ+Vo_hB-%lHo=fgaH-jxU(k&`pOVF`9 z+rE2~pzPV&0y9=NJt7Y zdBpUB->1F~;sXk0)5vP_LDx7(q$47r(#|Vd22B_kprF2w2?wQ`$+yAm4lRYIYN%H2 zX*M0-8|g*24JEL89KzJ4f9hpb=bEfWv~EvNb=aYB(t+DHB{^iUv76V%CO^S3Air7U z4M=+fIZ?w7+_7{nOW0?;JXcj2*}UIjbzjR$Rq&B-muc5M>%^~!VbV-xiA}nZ+l1e9 z>$wLB&!{S(A;c9q3MuF5-Fy3%(hQtM4z-r*)aT}b(y~-q zV;A?l1`*W8g|<6NMK3D{Za5KS)4zI;?e`xeTFRu`CL$#{>g81M%MU+}QVv5oSE?LN zf#M`t^KW7wBM=vyjkcq39&_yX<>{88cC!i51y^^lb=Irz%LXXqv@@N3&Ck3H|i@F z?;pbt}zJtz}S3LcKFh&uxu z&k-o8pMJfqSD%8hjRnUF6_%*OjVkuaP+GU}cDE+Xv z=st8U{z#^kjFtqCajc`sFga^m-w62AVp~KpzSu}1T+vVj@A4qf6N^+}C$6Z8R~|b~ zL_11_GqwL{{So-Bs#iB(BUF0bwnTP~+}7hYIFR1uK_ATl8heA#s)wptu-sHL-Hu0? z0^sRXi?rk=>(_TN{)FPg>NvbHQV;<*JgVwri>iUrP}U|Bv7?ZcF#kd^dq%=b9_j4( z_&VbX#e{@-#bPoe;2o764L5Cy#Zz&e`(7pW6RFdz4~LJNJb{hRoAfrcSf@@R(2eoJ|TZQ6VL z)8-B7CiC%GBymMtc)|fl#8PG|6$8`Aqm*oNcVungBBjn=1}kw0l+#0-GUuAFIhzPm z=rQxzH5w^-M$kX72kRkjc+Qcnpg-(ZDVo=r7?tfJ30~@pb58x@GLnbA7}H~ZfGXy+ z`c|4UNiUNehxObNbQ~EwGQH_v9Yji_930xah;DYXNE;ZJYhmTo}wlV03a3@&c*J8Sfz!g z1l!g)$JG&>0=63{BTr~y`Zo3nV_KFkjAFpYW7P84`XgNZVA(dUUm$geYQq$TD@%=S zhMR)u{lZ7pR^sf1!9pC*@ic@ZJFf9(%O9KK`#8sMuPxl>y@gQz2(Vu$=W>Hbn|4bm zuSZxh$X@hM1zq-o)cn|03r!>Gu37$Xnr1k{%Le}Jp5?~tbd!-bbmuadxAB4S{O4Bl z%1ReX6*l10?ar|&mmHWHYgc!*4z_wN>Sy3Ma#YkhLolLf-%C1Dw0R^(0{OM6bz*Ag zwWfV7^SxJCU9-4cHANx%0F%ARYYFb(wo~9K{wXF}h@Gq>dYn0fNMzBT5a3|aZoi`w`BMlF>W4qUCqcRF zg3Wvl4pGP!Drw%nTs;^}$yT|YZt?IU(-E6~{2KcgN=4<-mJ;mhFxX8Tltmflv~*Q# zhP2dSBUbX~1T^ER0*kuu*CN8#YSXQ|STU?mnfE%-4#QooP_F~HhFPI5{oQ~Kd_p40 z3G|>d-hgE*Q+@`Om6|sbcyZv^A^q5ss$>(sI>`FMI%RxdzWJEf(nja&NoO3j<1$1$ z-%Onn-dMBn$N1A*nwV~3V>V@&!zVLvPVv-3!V-nRg=Ug%59*f@Y~RM!=+VYpcrWx3 zJ0fs?F5)#brc(u+q1BRSuCv7SB?ta-n|^+2M9j`Ah50~M;;q^qhwK&YTm)uS(T@{w z_R}ND(Y`5Gt?dH_uX`H?2^Lh@h?UjLncOm-9WXMYX`=nfjnig`(u9@qxQJ6Qo6|}*jH(Wv$U8E`du6_hlCiPkwOmx< zu0M@%=M!M+*XR*Iu=D&1+=*8j>&%G z8PCpNuR~2HN3Nxet`KIDd-^b;N$>f~4Y7~sE5D-7k64w#e(aJGBMQ$ABRu0dCnfdN zs04opHsu-b8>3So-b(Qw%r__I^!61wJI&qV;60R&9b9^R*^3;DxjgJeABf4Z3Hj;5 z!Y-ZW?BM5^g-;~gwVOO{@oHe0&OeIoP?*mkjL?HBzf*~yz&9Bv-Sh}Zn>!G(d`ko5 zIhwy||A$1>-ZKrS?wnY1}8IruKVRDk;7E{93Jdtyl-2Jq2R&d9>v=@a?Q+I`@ztL*pBFn5og-!$<6v}=Waz6 z96n7`c@YgBtrodqhp`9kRX8mN{bP^sSg8zIeRhgA9S~a(+h1%_IfGod`M^O)lTFOI z9VRb(m2plmg}QH72`lWEt2;J<#{1<*r|j;nq&#&|0s&n-JX6cvfI`61hQ|4L^OOZN zC{NAEwOPi*&!JZoUudNeGE%`FV(*$z%Jr(v;Tw@={BLUliw4QuvAG{u8@ksXKH&>$ zvu-p?^lzl{d&dA3AKdgGot?l=+RsOO?oiw-^2?Lmi2Ox8@Yr&v%T1oYX3HN@xXtdu zrQiL?cghF7-2fJ9@RuTB-lLm1Z(5GW6AEh8wR$N__a7p3=7?1 zXsr3l#aci9R2^>cB5-D@C-vjdz>tM5VqtWphmrA#m2XEjRt2D;NP@c6Q!Neg?+At$<5deTV7^1KOFA!0-S?P& zUpGrl(iCH`$g9>XSwJ9fy(~~c%tr(BqZyz_g#KDi#=4!)Tb4LX(ws*sXG_P%+9&N@ z9GfYgOGO!{bM057(T`y@cp`u7+imX>=(k;15B0N!uZjaRJ@iY|-Vh_MW(pScbFemP zDl#la+(;r#8XU>EljwOla9)7=lxRJ~mFr7XC#+T@}YLbFV_#d%B8!A7ziBk3HwEu{HkX?TeJX zX|nMSkV!wGfSmyoIuq@`k%%NBx*qGHF7@)nBMusEbF)3Vtxin}jWa9Pk-eNAsSe7ubJ6=2zM-OYddyh-hsJ4{FBXwJ21T~5JXLDi72KBvb{?#}$ogJ3It4fVI?*0Z891Fw*>Kt_U5aK<7WX<>10ic-60FOZ?YZlf+HMu?d5-^ubiLhNkRQ zltIa*z@#l_6Zfuu>*Xe5&4E{X+n!QrF!~F+wEaE1nbThp!E%0OI2(&Y>I$A$!hk8% z*k@DWhYTv0yU=1gYz6$%Cl}0F5ny@s^AVV zEchr~mGlT(<>mLzs*FM4w@AQgAVs#;HiE)Ewa@fTE7fy}QtCmL?JIW7@58(#@B@A7 z*@?v0cAJg&D%qwlL>kzQmK?Qw>>{<9WX@exT)8jUFnxfwS`?~ob$l&1{iPoXFz~t#H!|3~2p5B^P6V}0MS0}r! z@#@F&#Wuf%xd_}9gLJkfZpC=Ec4oC6M~kEm`}UdmC3Hz1$93(x%~^vSr4VvVUZ4f6 z(P7HK5XW_t)8$mjX}Qap*me(NLtM@p>{V3vuEJDZaLZO?AUq zj6&iD4B`}GLbxN#VUILz)=b%z@B5m<0b)v=#JG8|IsBnLK;6S(zQkMISpbygQb6`ri7x^;R>pUFCjtN6%O2;UGU5-(O1VfP91S3@xZ}iw ztW(Cbv985S--s*eUdm_Hlh2b=Led>D6J~uLQAwTBT-<5!u?6I8(M|u0KwMC`BBauZ zF0VN)53)yHy@7hD{ah$%Za+=ZGBwZJ@l88pS32*6z!cY66j;7mM)4BcOw}bE;B?1U z1vh0n6rU=_uKvDuA3KndhI9v|&^Rfcra5jI3Xw%E{0$VJA>@g<$l;a`=y{w8^-%Z! z*WP#jHMMPP(-agH(Ji1-Y={bisPsUvcd${U1%1a&L25u&1iwCF}ffl#D| zl7LF0w5UjegdV~ILI_EK5YpbI=bU@*@BJIj7e7hXnsbdg%JV#9Ow5I=iw!nd36a{y z%<-^Eu4KeOTB8}a*t5dFMhZ?Tra0f>DoXmRewRC!)+ygL`Lgua&w_-x6}1|-fpger zGu#Xx)j`JOC_CChqJ=qD z;9wLE{X(k85QXoIpnS>#YJzkIc|C8o^z~-Y3BgcT<9w77xxZ1XVL94gbksKXFhMr= zrKXD5;sd5|^?~vUIVqooHjm$z+0z;$#1=q;W?EU9a?O6L}-0szu$m8iu+g1 zY_H!!!xyjQN*FT%K_;0x1JQJYT*U`sB+wl>n$VD>f53IoDLsk2!_i!+IqG+Y4hq5+ zk}I5&=_WtC==Ox61Iqq~B!9EOTCj!owm8YJu@MjXA{zH_@y;!5s^(1f-T_B@S)Wx? zgT)7V)vnHzxs0HnO^7E9Qz)bNMQ=DI+d*wP+Uy@}pz~xuGoe=&MDECHN$8f34xJWR z^{}yZxq)FAQ5(S2^g?=v4Z6NUM6NN@o}IRd#3H)#O_Bl6zbtxD^fS4VzU$oho1zVi z7rNk^X#u7pcDio^y_Gmk*5p!yZ-oxTabqQa`Z`Mgo~u(*VjlS&=|OdhNoHP4hit7q8zD$UJfDc6cN=LxpvB_ODPTcA2j#-3;(# zvbbsD8)97pcflNk!7Uan=&oJ&zSc?mh0GfOrD`` zPv|p|MwQyqyDY8oyKBjtW=V5WWV?rBS*anar1S%?Gzq32Gg5Z2r6<%JHi_l)%*m5- zflH%NNP(%m?q}fQsI&OGWGrhg>!Te5lQ%1ws;8a%i>24Nw}0kTdwu9$gs;btN&E+7 zj1&yXNyx>OGq1@_xwIzhXEVj+j1&d=7NP5qu}UATV1m~DAlOI(Hh+@L&L;$*nqU%A zpCZfoD)uhbgM-SIhxmH%Kp~#Sm&93{qW|SAzBs5*vP?R;rtIs}!`qeY@xs5DR`gO? z1j_ml^Yx%9F}$SoH7YqQmw^OZPF(A%OU>iX`1&eH_4W}be4GCH$unKZfSuKGIx#@n z!<*jT0IYqbqtbKj#nbTV1Cu11NR)ITCczAaD+`J?qJEHtIp&KBCJo)^@6?(D-hloT zzt`hc*-|>V5PPtZed7zESt&*o0Yw;{=~d)Q3A|@mr}D9%&>mUGpV8+g8dkqQJQUNE zG?wY3h(m+y@d~?ApNqppa;~AKlE1@mhkq6vJ(TbyIMl%d`vPP=8#-(YAu$(AIlCK3 zT^pI_$mY&heCG4x_&*f=%etSFLVi{daLXang8H~Gp$Dl6)G(N}t-jh^95}%38*8?! zFyq+b6)IM^IA?3kf56FxlTR!ZNWMk{9(-^BjK+7IfD+E5=|uIn@ZmquYiRi_(CvdDGvns&aNHcl%cIF&d-z48`cjQ~9z3k@N4h zB7rOPa*m0JEycC@cqhxKL5i$S*(~pkq3VB@_E3&~0hz3IeZ>m=QYKEXj}sJUkR6R+ zTrPFO^Z9mAX5a@iD3Bog>1yRL$@jSMuHuGI&TmtYuVXdY=bSdJ#Y9d2J%s3T)o?$) z5VAql`kcm%fufH5(RwP>+XFWHX)eY(vV~R+b$@|hXGd{ zvw_+C=)H{Z9`@<;^rvb^m>LhWWd;r}K=sj}4jvuuKa+c-DdU(%)JNBz$!vrdy60Fs zzIGK3X7ng~zMrh!M48TS;B1)|{*>!3-+8EU{apO5{$|a=_x*@h@e5N=;1Q)m1)Y(C zN2-g0#uFelxR?CS?=N!0qRo%xtc41V+VSuT$uy4>o_kRC+EHBFK7otJ{Zd;V`kg-S z%h1rPvXk{itKCXYRCgq-Sm{&^bx~a&Dt+O{;7px%?LrQ{85+%SSb}@!3LPb&Ix}_m z-^%lY5&4`!d_MWfnZX);hPqk3Rs%j#^oG4ys)U5vZT(uA-K0+jhCw}kTy>>My!i)$ zouUAsJX{Sxq@yjFcn~Qr%}fdPzs|M*$^hG}`xXdzZO)booV^DQfAX9pXQ__oIn-mU z&^dP%cPT9gENB0Nx>{(?eKH|?4Fy3R6f-h$zPNk9v`Mg|9^d|a`^4R%*}s^Bl(;hh z_b-nbn)oHgA_WDjR@@}5?t{R&+{|Sps3X>Hq(P}KdV3b@hMmN4Jw>!)_2yZJ1WC=| z!&{0un`y1e#wt3XLS+EsdMClnMv@gb|Gf}OeiNGWVyWOyZndujn3sEh3^0-{5m87H zxfU{m!JiwrgxvxzSGno;a#>vosaIekkKu(>)Gg|*Rvnsfk0IuqkjHTC={bWf^XnIA zwd3ATkR7cz?u(f|9U<#rodl!Xnl-;;!zq}~+uFRf!9m-$)Qyzo@}JxNy&yPLrR&`0 zEz$-4I-2N9%W-9Ncp7Vm4PvkCRDR-^??aEBFP<@Zul)0P?v{nbb;>=fu{F8awJ&xz z=)OCXc7@WqFq^l5CjNO#W@VPI7?*9S)EeVr?pzcYFJ!V-F#y83wUSt^GxT6aPvYPT z`HA()T5)JlQc;c?bPu3~MsHqFJj9<`NH~q|TsA){1<{58-rM3Fr|-t2)6o~Q;GQY5 z{lENV%dcK|kOzG4yyD_=*X+Fd#emz{8ZmR{A|+x$i|O00#VwMIsjoWJpqyIj^`_9e zkaAq>m9m4fGw{5G$v6-vbb6EzoqtBVv4r`cCLLK&gz^UOBENKfDCZr3Cqv)<Llrp18xHS#<6D5PMQxQ)Y|G3fZ z4g2;W1jFh{oj|`Ej#cF(TbR6_C80c~S6ES6tM_dzyX`H%G60N&<4T=w_BJlT^l>li zmz#f;K9+J>>P8#WoF+|+!(pq_oaSq;3ja3RY#k^cvrSH4Z~IdkefeoUKXy*7u!1&!v`;UBa@$IlR8F%*FI0evdkNsrn zift6Zj`srk&Kd*~hXaD3DE4St-%&H$V`_eVuP9;U1-g2=< zR1@kRHCIBmTh>ifKx!M25JM(Wn0T~#(SZw>U7z)8W9fb8i^ErauALFHuA+nv_dDA$ z=w9?st<^c1Xskf><4oXsT`plh{oX5Q@xkW0rl+93W-K!l;OnaIRDKiLmuj3*7`s>H zM}(glzK!C%XhrtKO;(YllENKl4bNnxo2G`FKm3vxp39ua@S#3)|5`3;0JrpZWTKtK zv3-7t4pE;I#zb5>^DdJv5)WM{>pnHJas3|VJP|J8Q7#Q$HX{2_@SwEl&=EHa2#jT! ztd!V(Vw=+NN>8-ai-46%IxRhOF`}Nt6O8rBG%@9!%PQ-L;R|=(y#0nnVD-Cu7lb6j zOgv}exdJg2Z(n#*sMK(@nRhwC4BeGLl@XTvej{0JJja=R12wRAl}ud~Y8&~AbY+1| zg#~Xj?-hxDLPtgJ+nh(o1_KwtSzXeu6|9Xof>oy@i7ZPT{2NwBfvWa*~YOKcyZD=7ouSir=l9e z_cYfWw^qFOD@ccMHm5Pk7F7oZnJi6meQA8MK`OMzbKIRk_0+)^SI<9h14nF_8-crE z`eNy3b6?6(U0&{7a^g1xCgH}A>6RL!^-74_m)73Rv5}G^@#zJDXOk26BtMowo3|z> z>Y{=0CR<8TnSSuWrEHZa1(xNCSDQH)FFA*+$P6`yzN6o$d~#P&E)ST_+@l@V4#ltA zFQH`Mo-JLlFWk`K2IOjfc_CVzLu;%y917}RO8Xi$({Pg0cXs(f%0{Y$B3hFZi0KTQ z`%6)@W{x54K@M0fEnP*?jPz?O7jJ!fy|0s4CEftNQbt>mM%f(F1vh2oj6-%dQPe1b z{TK40ck4GzA;Hg5`eiGv`|{5JF?AEnwk@PBE|MhBIq(CR^+p;zbWLBqGs_`A-pLC+ z*HN46sy4k^>3FJTt3>l5ju+?JL;bMmI94(94_?pt4}|iGA{5hs<3yKc;vvQ=^gi0P zN7?5hDR_88@6qN=jIcaDGhSEP8apm7-AHqo%=I+n>||AkjYsRWpsWVd(NhRqS;M$+ z6QN{s(DDtOb#4O>LyKBXhWDZc+>hgBn6uj#6-M_9OopP+7KsPQ@p8tV_czfd$tXl8 zN@62BWl_cm#M7F9#eVe1Pg!gAa5(&+tuX21IE70-=SnOK0S-gh9SgCV_^c}>r&xAH z>uv^hz*Ve7%FVCAz!A1g|Eb0ZWMoC zd2ur{<(wLt&Z*)4<+@w{U`@tkTu|Q79&aBQyFbWbIiprusp+Z~Y>>dF-Ty>i!8lLS zSxB>4NsYn=GNWcBLROA&fbY#D6Nl{OKbk!kX14=nLcK*QDOV8bxsd60t^bZ*|lP^;~}0E%C}2;vcJ-YC#xg&0Iu|@kIafE{3C!V{WTmjv9iknh*U^ z$p6D`<9SMbOuyIN@Q!y)u~5;*G&jWB%UG?X>^=h^9@N2d!k>^AK5&WfeTKjv_b{Vg zPl1E^2jn@zSuiB#DA2wAzigo+R4c~;E9`w9y*kH6HdrH%dZ?8!Y|F1iAmY||(T;KY zU=K~4@+o7*%1W6ya3eOHH&zj7jyO_YjD9`Z9M}{dcL6iA*w_<2b!p4jl`AP!3u#w9 zZfrvH^tLUvwRF)9<6U}8yWQIRma~e5H-qL5h-#5QK6InCLR{z}yX;rxn{v|yf$BV@ z$VLvN5KlC7S<#cU`#>;0Js;nQ-Ae7sBS%pHfgEa-CZ}$m3OAaH9ddGHAmDx#^*ZlT z^`$i>HIbGA$andoNZvvAD5IXD@Z>ZWvLoP|>(l|-f#pK6XS)y7m(|0#wh6G#6aMn( zr+$0%9V%^_Ko#uSJ>41jzBxDb9V*9d6o;2CJL{<4dA8*$`UHPwh`+pOL(kT6cr_8*iM=j^Mpk!ldtN-Jh_YdZ){0ie<@stM9um~szldGk zz#FzunR!-SOj%2PLanxv&-LHiq2edN;mT54Vf0cNbxjyAbm}U&h&yk4^qaQ+O^S3O$zG`hc3BnUoZddN7)D3<0p~m=9m7tky zmdP%~_0)u=oBxxbL5*yinhAc709`iw9-qhp^ODiUgUN~f>#!7Eg-N5v&@w^6B)=H@ zIt11F4H!TQ&fud%N=AB|B@EVwxW`4{e z$#-;n&E|BU@Wzx^CwAi^%?pZT<;!Dz2q;Q?1iFtqFV(s*v*E%rSuEro4bt^S|Etla zMOT4|X3@e&+i@0?YJo*Kx+2DCiEhx2S-!OPhPNgIjb_Ww zgvwK{3N2{_rg!+Fo7_rM`s6O|~NGx{`W?W*kTYxwZN^Z_yaQB9Leh^+Ohkf>#7veTXE85CI9|D$Y& z%HhNQK2(YM@ObpM1B!+6#ZxUY15YD+2Qpt+(S0ypyR#fmZuHRmm_1OR5Y`KkVc!7> zi3Na^AWWbjCl!x~JOb+CSM2wSUTXfl9*$}c1A#2!e+;+tsI$A@T0nrhv~76k2X{ue z+eWKl_*zH^6U(nj9t1 zJr<@-`8r+fX1L%H8kf~aK5y9zuCvoo#c#p2(BB5P7iT%6JGm%@I7Ud_o_wcvLT!HV zrl=@X&V}tO&Oc;)HXvW1O&%mo@bIq7r=Qpm6_xF3hY$|5czm@|ZFpmxi|aZ{y+a^c zdi$y{x2z0^e{0hiZ&!wv8RdSmt=*qQ_tL*+PyRXX#0>AByEG3`uDb=YQ>BSLN1bCv zyq~v4E7O+kP>GVs&)-1Ag*z=@CS2Y4Mjn0WQ_Y|+8CDy9r=T!*h4Pi>?NxcI^to-= ziq%3Rrrp@idw9|L?KiZzPor{_Pa1Mw3bMQ-WU5^oRhdUX$UrfL9MHPIrOdd>f#zt0 zH_U7f4;BVZ=1o{ymKUe@mC+A~pgWZguk)Ax)qL3+!ySD+J}W#2>h=jVjz#3+NQr6Wu9 z<)bEdoBAnMOn&%M;@Cvx3`CKBFQuZ)(NEL8%OGDvuhn(gA1Pzarxtp0`n(}0F3HwS zKWi>tDc`j4%qR|*$S;@|`cQ@4i|(o|Rma0aSaCzM@JJXoPbDdMz?*@D_+5u{}=g?drvpVL8 zBl(hT{k(1((;K3d8ve<0di=Y+j>^)_F8KSa0_Vq!?fD0|fKzrrYXK8mCV!i~T`3bB zwF8Du8G3l}L(?=>cZQ(LXTDD|pIxt9K*PptHj{oYC$hQ7T2g{-JzF=aGgIN5Fr%CX zN+$u;*&hG7wU@$fiFj@a2NR^G_rdVZDo5+u*Aguz9w3ImK+PmbYLJ80JGTmA12b>? z{j5?+HVOVW*Ym?OC%7U}>)@D~l1~FkZG)%hbIJ^PzDLI*r6Kt)XH4*PlHK zn!`R>*G%JFyt7o^sl$GqdU0@~;B`s+bI}i!+OPney;7$#YNFz0@q^{?T)e%CC2!rK zVqOVmt&i6!?==;6MN)|QcCC!{`SbH1XW-x9`|1|s4nI}RL_`B&JSRH{TdiWR5g`joL8AwlbA{ zlJOUxmT!cP2(qK;3Hjf@eft)6>!X$yRrF!kmlyp&U-opjMvN%}=^`87(Rm{}HAGe9 zA;tIjn12oHYM*{17+L(b(((Uxhup|}9Q*N|x&@JK4& zal4b8!FxJZdJ61$4-t*~Yu3H}tHPjZH2z|k;nMCFu-C3OU6R|^pQ)j@-arfe*00#^ z>lviSyufax-$J}lPo9odP{Y6aIo1ZO_D1btJ&>JaUUVuQ@a|fD!eWtLPrKA>ta2P5 z%P5bPH58I|+O4RFM*h9OFyXK)0huw34XoekS1a2ruktR6^{L84i^_?B>j$fiz@TRe zq|0`&PRH^EEjh7~?3vRzG*S5wCB00`#GG(6Q8Ksq9dcaMKIwiW=(fjV<0>HaUh9=z zG2DZA$jtssO769=D$P9<*=~QLaGPIr3t`%z@}B4E>QqBbs850^{EFLcV*rdq=r!X5lX zwM}4wFg*fwTiI;9n5*MHFV9-N9&atzR?3+-xd$tg*P-{;7Yl8AY)(Ip09cN?c_*Y;A#WrTsDD_)tt13w14egNj;N=M!T z)-E_79q$;TRI2~$`)|Q)d$CflJ%uf}7Wao=Nv6!9^t-DXdWqYuDYYH+U$gv8EUo2m zR;b3wv>E%^>$ee337gfL7Ce#$L==|8OJN*E76W;j+05@SGEjd;#N|+2)n3`rXaRA4 zfcTtpHze&M?+8=z5DfPjco&#sR#)dxMd?zAK}yY`-azR2P80PXjL?kI_%9+ua=X6j zhyAozQny~zhrUm8VK2ol%}-o&^K8;*Rz7%Srw_~+TEqC2)Mx-li`=j_$!-jJkQNOo z%r&ZE`A}TnGztRmcbW{jr;053U~bmgj-6PfJkrAW#~HRSrp)HDV=eNBW@9{@jMbvr z>VpS7`iwX(ToNBwqgaDcl#6ZgN=kKmerMRQ+1mS^sZh6ut-&eodEYe2l_8>={5DFR za8ZcK=Uk4O6Yz?epal9Clu=!E2YDRfj0(wfSY~4C_)@&;e){bM%Z{R9|+^;5=}& z*?c32h^{)pTsY1^GV4Ro=p$=Ig>kP->_p#eYrhq5LWCtkX29X+?RfcY*jy3bmUKX- z%ELcd%R(959o~RS^f7b=X6{Uhczuxi$rB)o>UcSoUnCfU1b~^_-d{=npxjqVS@2RQ z?)z@L*|>2s#PN+d=vUTslX$c!9Fnama5VWKeJ6K zk=o$vytsm9P%3G(So_>pY-ghUp1l7Ho0!+$gdyUqk2-e)jp4R&gEm?@9MrupMk#{ZR3d zE~z;*4Fl0(b_4IOH`7^hjwy?m2XBUia{8BwKaSt8U44Qke{)s%Guj9tTJ{<_j;wHfds6L(GRAbp*VD_#qRd50 zY>dk@4cmMD7D_%sFcXYvY*Y4iQG)Q3T}w)gRSjv%SMz~WEsFIfb{iitiT#`m$uBK| z>ZxD9`b^b6Ik5)fnw9E&17ocE^l_&~vzNLTwP zS-M}j&_gcyVnQlkxy8bMt#lEvqQ2JyNUWqb+L^)YJ$#^xgPMCKL`3d(lL3|`sgd^X z`APb{u(R8Sx8q8^@u7P{Qzt)BbvYNS-RKUrHZuT1ClLdFv2r_M!^iO!tu;AGHHP^Q zaYmu;!2HNaJs_90irOvgwP%O%!A#lLs{HPnDUs#*d|-61Q9rcCM{)W&bxuAq@h%jy zOM7kJQ;pt6{*W2a6uR0l+9?b&l!GlIg(y$l{k6J=A%|c?TZiBoJS1#J%F6H*RUAwc zKB_Pqr`y&bfqDRa>A;GxmgGc>B?_`RvVUQ`B|MjvonKyxfI|q~xzJ8*Y}aj8<5MrV zAvLnyq6GBGJ3>Z1oUZLJe5RCz?>@jQ1^PFAwBkLk2``2556nkuIvauCxwl`YerccQ z*+WNTA)zUaEVEih4Cx&YXJcIVV|>}Bm>QDbzpM+q zE=t?Bq)qwr?mLfDigHBWYu=%vmX0=uZ+qdDb7rK2=L)y$^lDuaH~M2rzw@W;I*@GZ zZcL>7^}+c{P60Y{TN?Z0$8jMK&#+LRLwL0)rcg`wvEDe7vXLO)iEQrbIW7I}(MG|k zq>Ra|qe{+0zYI&!lUPT#NbG2Ckr!_C;~Oq%a!K zlBoz{1ejj)JB^+;c*gfB_>f)fs`Ne1j)VkO*3+ClgMu?D^X<1~L2fkajk9WIPTFg0 zaPO)C_O(wE)w7gp%ro$zwctZNL9~8(pkKbM{{&($ zHJN9AN0Q6AflGfkn-8aQ9#c0`bcXi&brkd*@3l@>hkLWbr=tnMaX$0dm)D>yJ(H`7 z36e-F$HXzdBFe5DN|++*HOXN{xOY29?u?~Pk>7oyk`qC7*s???&{^yQVUk09y|1_60GgS%qqe%nkq|Q&L~)zCzvQt^dtbfCwGcyfU0(7qpsemX0yxJ;t<~dv5{`-q zH3E5S8`p|r>7)e$Y&QoY{IOodraYTZN1OkI4WmEvsh9nRE=a2r1(ml)ij`rL>y?!T zzn??QUiM#0(KV>F2{J?6R-#FFEi6Ml9kinVzOS@N;FWzikY*z-*+mv1ef+zQ~ zjb`w&y>2fqOr0y*vYSVJA5(Hchu7xUlR~|ir?-ncDD!#eG4&KT0ek1F_Rvo;vwP#d zR5;)2wk>_1Jl34(V&`X=X?0@-s*Gg#sI~$VQZT2xWM8L{+6Gg{I=e}4{K`55&Z<5A z>)UAkZqOY$20`&2eTX04()}mi)@m$>l98=Z5wqlLqGTm7TMYrDWjRsO)ZTopq7&yG zG2kw{n4gDKJA>H7^#H~(sGL+8c)s@!n(bS9np2yA33Hf1*GlQVLI+|x$X$LST8%rH zD37LU!t>mrCZAgngLo!Q(t^Mi_!YNLreQZw;$FV7vtr&T0kX%%7mD*PM$JgYD2LQ$ zY`8%xt`=yTg8ksA{C2*!c#Q`w?YD1t=+9MmISxySm>|<&4>m@?w*oSd)WoIJVBuz= z;J+k=`=41e{60voPGDry`R__f&SCtrW%p3JR21{j6~!;m#cA@{yuGhWv~(!7BL9V* zqVD?0A{xoApY$9YkBazNRG#mqvITYow+NmU9(Y!4!2_ya!uH>;dArjGyYgfd^M}iU zi50*GR6GLy8J^p9%ubXSC0Qx02Sj z^y)p$(@ajRyMJue=qA*=t}~p*U7=i{<_$H)3aXo5HfKvm8Xy+pG4Z!}MhpbX_#y0= zXNRNRgqJB!Sl=4C8?k?e8u=MHCaaFS>X-v;uw3zVpC8KRjGEG|U)g9YP2K!X&3%2e z*_e`X^#;{~Yo+*FcFdD@0j9k#rmceiCfs8Q#A>_;iH+g@2z2~wz19uK+72F_%l-5=@ zxfkUuPn(B}Yf^KtBtKoHB@jYr<~yofd~{)%uH_3vklJy!!Ph#NAq`29AC9%ka95c8 zK+JF8JVlj`b*dEL66EC)j1*FHkOU?fo(Ulzyv+I@AOneIy?zBH)f91V?5gRNYoaq)VFd>!-;vB&j0bRWTXG6TTXpUZWFe_;+y%}ScXDmmz|N=CsHn(`U!kEND=f9cQvMylZkVWy%D3JIPCvTzjEa1U zV+*_a*U#&4C@+YXa=pur?jnr`AxrOA(QN>3oEjO_xjU1M5)j#*I-L9O-UD+e8Cc=Y58vfoWrNh^#GV!sN3y}j$hJtq=ecAh z8%?!|3@{Z~3B=s3%RgmezYTNv>$ID@>l_q}4%G!@Quh|!|Hcylifzj9(9qnRN zOAL9(*rUlcyYuRgU2Z_8JrRAevmoovKh&w~x4)rCGg6TV;PC-|X%<61%_kNk@>>Px zEu1plyb1LtdZ8|&;iUVWfsHToMevaxg&@X<)vL8p;o?(vKL74q>wFG9D*T+Q1mp=a zswfrm#=UI9LSZ6#X=4j`jorV4#y)Ub>l*WjE&B~M4;@>R8!_Cy>iVW^ZpkyNJNZ{k z*Y?3zorVuk1s5jPQ=sN%4dC`vib35XbSkc^%alCO|iVt$4+B~yXvzld^@Yg&1T`Th2lGSiTOic`_27t_sj7;1l9`Zl9Q-wahk7m(f z=NCvop5b`<6*gimBcpMf8AnM@Z62y2k+^@k!t(>$x2WqiY}u~9{#fzM3i{Nu_Yshp z+Nqij>ZL6QagqHN^B*9kHQ*MQV&fd>SM6qg9xPjbmlQM@iv+53(}2jmh?vj9yi{tG z4I%zkFNk+d#rm!eiIwmdbr@%3`mk zshXye(gymeqlYfvX_(biNf|Dya6(t5 z;${Np!A$qpwTiilqs}WvH?i4esv6Xb zl*v-B%8BHgdq<^D7WG@JQ#g>aGfswm4``z7*kJeoccE*D0EAl+Crv=P!A)OQ|MyMo zC1R>s{$@roac{ysC%6MasO!aOZbQX_F7tQolLgg2k2xHs9rmjEn)aX5E6!fqY|<0t zSgMkH*@BLvj@#SmWw%}1_s89TitT>+2q%5V#g?G_0P_nhVzO|sAtZHSyxI&&r~<`& z1G@ekxt3N=q0@O9>t;l^tD%WXTb8bH1*bMA+G#K*mtm5jONi6GNOrR}(xfTSt6yuq{-SHM=kRY?)lsU6w6>+eHn*7!wiNoR)iAbZ*a2&9Onff_YOV~ssP zYvRQ8obWbKIs=+4h?sTQtkGBPZQXPZ)fSPM)5-Z}n^8@V|JHS58r9Gw*5&YF$Pnqf zM;g5c3L%PCv_~6*Zi=sUH-U!qbb^Z%i(Msr-UvPVKRLE%KX_iYI_!Rtb@%V%&inD* z!RTztinlE(X z#lswDiodN6&0D7&FdaU~uFs-}*-)$A#HI@D4I+XoFsF)BeFjS|Q8t#{owe;^(Ggp& zsy4Gh{Z#Y#42Vk~@ESCLsclS6ytZbKyUJ6&w;dls>1Fg4)N2hyNd8dKdg{izqr}u8 z?3#yNzMGR1X$d!MCziL$L4rP$PQgk_EPg3xYntWU~9J1b)E4nEJn1jIjoFS_VxbK~AaM|#~41+F~mC8>ZI zfrXQwQ${-~@0je_mR-Ee7ZYBmf7QO;ANi@On}yl6sa9>A=M1YdK50ovTSmh6qCW^Z zmVTWfi{IEZ_3>B>`Yp2@Itu9+5$pG{F^T|s8AF<^%8)np2C=0J9-f+us&r1w|5fXr zQ5$=4Z4iIw{+_oFT245b7_u=E8yZtN(=lL4VZ| zK+AUfV3z{eF%Y|y=VH1vD`|kU=UaC}@h1ohDxKH~s3V_Q3do#_da{TaqPq=fNSWb-o;gA-p6< z<;DJS{k0nyUf}k>H`*A5U-=nflS6X)`iIlGwe8W+Lfsn4!$``Gi-wG9e;@Gb#pR!5 ztqv2fODfig`%IY;!cL#TJ&5Xb3E~+W15~`Jpdz2+L6=AdGltRxyIC6e24~gA{Nc6d zT8^1&e_eUDC~Cpe!H`D7i53j*<_9%~W7pg*%iNn%vk3{r5iz?Fz6=DillxTuA?iX|2m9Mq6vu%R zsr(xt{r#-4ZT`|2(V`^}&8k|riqLlGq+Q_!o!TqK5OpVnq*K^!3&_Gs)Q%eaKhcin1e(j^9-K@ng*5%&a^(uUD zZqux`>OXdMy8?Ev{Q^*jcNo$ly(CG7?bn7Y_2O*}seWmv)aKZrc=UW}Qfs76S>9Y- z5wm=8^G*FB;4mi>0Y>teE+n`^zrd@y1)l|wNj3Xx%Zz@UtX++NW? zW~p|#mp?X(K*jQ})F6!e`E~+Gq=1x*=i&q0;)A??7h@9@otBS(T&`NDMA#At3#;6z zZdMWe?fQyaZJ4!^PWZqnB#rAzHC9>Lzf(mp|E#PjZ*nQ?E+;0B`xs}@7pmWm>|(@C zqF(cC*MtDWS zUsSr2Y%{=1qOj3j2epzDNsVK8vrwI^veW?#Y4Au>Op_b%T9)Bs!lMeRF{Av1$cWfj zP^uhphrgr8OC^j8vP^YRKHrWuOJi1IuK71v)RFXvO^`R~9fvNg8EnJc40Q>~P1l&{ z)FcC-HdXTaA*H&i&S`Z1kJ4%%s8KspV2fuK)$YVYVM5(7IcKoiJ4Js2|H|tOXj#9` zNt*Ny#6)eBq0)vS1^I`dfQ~GU8)|KKi?AKae}{FQV(LH0t?CR&sW9##29=;TheIJ5 zpaG|lfW=rG^!diJ$H!)kL?F$WcaKUyVshKP=IK)>2q-TcqsyQ+S{5Q4N;gw9phn3i z=|`rNX7zrrXiRq`(;C#HYFs+s_(dkEe$*O;y`!wk`>DQyqXiGe^ z|M`05-8Ve5R3?x4KUGQTJ#OAk^ zybKr7^b~P@U0ESo#+G^3(2uv1;8&~`p2>V=llAy@%HpBN!LVyDycp+Xyvt5&n%%%# z#)kVI!~BhwROFv@#|ZIiRZo@v*@HWDybs4FP)(4+x&?#cKCKq-k^`E;$KZN2NkVC- zN-^gjA84$D@p5odgF7b)S!`yQaxMPcOI{-Z;$>ukY?O;!=6NUOwSRJlo=X4Ev(jNV z09}Fjm?YWaGmEBPy8I8*h%M_>GeGe=0O#kTOClk3M0FmmbN>3)7x8b7I(zsn8jN_T zt|7V~&O5@k23++HeyT@MBbpK-scOxt@OA$EZ-h~iRIo+DMj`vGNE^et7FFktVME{h z7$s6c3b47_gxu^G--4OdoVC`NTGajCJ(Q3t&g(g0tP&@D zt#}K9NfZ*tfsw+9aLVU!i!0r+Lv{o?mAfdy03dYUW4~NHlxN0JAGZ9Z=a~Ou@j{p~ z=|hie$*27^1qrkbC&%7TKeFGe8#fqKt@1`-l{Da?YB&$}Vu95GNC&=&ZWmTR=BAy4 zZN1WvzwnER$(S81A9d;&9qp3s~^L)zy}`PXkZY}u-=y*n(QTWoX(H{}@u zFS22riyZCzSrjF}6#Id_`;y$F&Y}4Zpo-h$S`p&)>quX=kQGJn$|>90_Q(ETq4U%+m!qYJ{qOug3tBDE diff --git a/docs/Screenshots/Boot/Console_v3.png b/docs/Screenshots/Boot/Console_v3.png deleted file mode 100644 index 6e59bb881b06cfbf281783ba3432ecabff7cffda..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44779 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 diff --git a/docs/Screenshots/Drivers/PCI Readout.png b/docs/Screenshots/Drivers/PCI Readout.png deleted file mode 100644 index 671d55daf1ea9cac5681abbad212add19caa117d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29602 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 N%+uA+Wt~$(698^5%dP+b diff --git a/docs/Screenshots/Filesystem/FAT32_read_dirs_and_files.png b/docs/Screenshots/Filesystem/FAT32_read_dirs_and_files.png deleted file mode 100644 index e87b161b41673482a9f5115a43239264b76ba034..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 148684 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 KbFCPF9RCZ)du#Uq diff --git a/docs/Screenshots/GUI/Square_Widgets_Test.png b/docs/Screenshots/GUI/Square_Widgets_Test.png deleted file mode 100644 index c706a54ea7933785c44dadda2f5330a4c1c79c46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4495 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 V(o8F;GQF38&vpjypl*-(`rk7z{Ja1F diff --git a/docs/Screenshots/GUI/Windows.png b/docs/Screenshots/GUI/Windows.png deleted file mode 100644 index 624a13622b99859c1f20a91d94e9ec0ee6b461d2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5458 zcmeHLe^38s3M+SJ2n9TL7l&FhN$jeyhaz`N>ZB(N-go3&mfK}_ySs=q zY0PwNJI!?XV`t~S+wcAHywCf*-|nxr=Wlu9v2Q#ELC_QSt((3NL8)#CN_oTdh_#FwY%jo#!-dvNyA zc#Dg&T%OLD>9;B=aV&qyi^7+tt9-HW8e7Kn^S(uAq|~|#g~EZ8Ii|X%!q=`MDVA^x zy!LWN8hN>;?=}f}V}nG~)GFI~J~;>v_&L~msa>=PKjWUgkV_Ra0(vqOjej{m94xON zJ&%i#g2{6vO`KZ)>)89b4xaEf^Xux|nuFnnC2s9Uk}ZLSb;3b?I~EdWd|j>J;!`hF zgXSzZd-ikwmb-Ee7qK{5Q)CrdLs19lAThq%f(gXHPH*K`^h`b;V$})Q!w3P(P!y0t zQ5*f?*G^ri8A!ia=U2mGx1+;CxdOb5(|j@rPKU%(@C((8!@s2zrHoCSn==^Li!Abv z>S^Dq9}NORM8^}j;CD@R56~vZlZ>jw20caB$1rx^u{ zUlMMzA0-(~pbN^8zYwQ&kjpkwB2bd$bGcd$naG_~l#?`*tenJo7)T*(r?+#R zR>{oKfzx`uHTvred^|p85OZ04|3ec0Q*L)HbvL5Ix`od3oz>m|5;jga;BArGm3gf&;48 zM*&}mtuZUID>uSVdr-`NZ3nToTEkuXsqH*qa5)yu)-I|0B*#$E8!%GpC?STdAc02^ z^@pFcUt@z9rp?+VR6fVx7~L+2J^l->02YZl2uW$r$Jl~43bD~CYE&lr{CVD3<+zfiLDWyH?yu$#k*ErgVD1QK2V_Pc%z@ z9ziHVN|pgDtT293eyeg|EaUAY9h%g;I#9nR zzEU@C9SRum@A_x>cXJYdj&#zT^juiWkB_zirU(g?wt4X~UPfcwob%A9TVl9BJ=u{Tj}Ce{a5aXqKg`(^f3ODX?Bw1p7@sGH6V+6IJK5v~&$wzJCZV zG$eKVwYL`xYI^zqjNv1R zX_Mic>FHuZEN;x^BPVi9&;BXngg*JQ+>i~eF_rF#+g+Uz{q3@TvAgk)IVR|!)<{sVE^Lr~uz^N2wOd&Hp8ztbroQGmd?j<+Wry~y6tZv5 L-{k-9Zuj2-YxV}= diff --git a/docs/Screenshots/GUI/Windows_VESA.png b/docs/Screenshots/GUI/Windows_VESA.png deleted file mode 100644 index 2acb77dca3b17a0166ed44b7d598ef26b2b4c726..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6975 zcmeHLe`pg|9Dk|7Mp><=8~!$3TDA$JsGWAuB~giM1=qUa2+WyUWGZ(3!5qeLO)gl$ z>ZB)3H%l;A*qDQEMj8HEOp|S3Q5(+VaFEv30y?Oo!LUaps1_D+&Uhx;LH;2-zL zd%U~%-uL@`-|y%9{l51-vZcDRVD{qK0Dyu`RfhKfP$~eJp-|0)f1D-Xe*wUfnVSsj zw;nJ}^uN=-?cOIF-z^wjbglmK$h|#B*W6s)*g6xry!o3K`4#HDt{JbNNSvAH8*5G{ z0$7R+LmEE)0G1Z8v=|H2VyOc7C;7t>*n_z*=fQs6A(ixjXr$iICiQG+tj`Pt~rIJuN&a9 zenj!+t|f}@8`r(UNy;BcSS98U+Q3pM80i+0ZmyRjx!#~>lD=ZLQDrTgl>pe6A@-eX zk)ghQW|Hh7Nl0_@PLIopQfA#RJz2VuGGvJN;`*?M`HHI1*+gc9B;pms@Xjm`wlr^W zg0{MJ*i zVFpODN@dVw1D5O-a-_)wZaJ1dpV`8UfVH`-mP@0L-!>K^cGq+s4-~FV>}7AkXirMF z5m2-NCR1QnNof;kBvPPlt~?>DLUS{iB*Z%NPg05QG+e;v3a~X6%}~) zRhVMFhVP(~Ht-f4o{)xDj{-b(YoB-A!e01161l$yK4?CIZPtJ)`hK+-G)K}+y3%8W_*7Tp2F&bZx4sqk`8Fjcp+(!3uw|cPO z;@V!SyHy3&b-?y>m#Wz!*0pfO7Mi|b#3_mim2t^Cyl7-BqkR#f+9J-Omt*+|u&;*g z5Uv5(wI@g%R7F!~nK~V}3|is_Z1W6u#%wKQuP{ZKa~0)9pE7J-p!n`Q@A)jJ$^iRi zPh^CA5bH1!C^xmsj4{D|4(nt%eTPfk^E0U_&MyG_Y)O#&hr`KL2O7jZr*Xa8EG=hf zn(?5GZc;o=Si{6%GUUR-R|~dGjQ lQ6i%xPj-Xn)(woxgx>0mBBz$toq%Qnn>JP(&TiOo@DE!WQN{oO diff --git a/docs/Screenshots/WTF/Button Text.png b/docs/Screenshots/WTF/Button Text.png deleted file mode 100644 index 45ff25f6a5e85e3439c6b22205262bcaff694ab9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15075 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 allocate_blocks(uint32_t amount); diff --git a/kernel/include/filesystem/filesystem.h b/kernel/include/filesystem/filesystem.h index e418d9c6..8ace4b36 100644 --- a/kernel/include/filesystem/filesystem.h +++ b/kernel/include/filesystem/filesystem.h @@ -9,6 +9,7 @@ #include #include #include +#include namespace MaxOS{ @@ -55,8 +56,8 @@ namespace MaxOS{ File(); virtual ~File(); - virtual void write(const uint8_t* data, size_t size); - virtual void read(uint8_t* data, size_t size); + virtual void write(const common::buffer_t* data, size_t size); + virtual void read(common::buffer_t* data, size_t size); virtual void flush(); void seek(SeekType seek_type, size_t offset); @@ -104,8 +105,6 @@ namespace MaxOS{ string name(); size_t size(); - - void debug_print(int level = 0); }; /** diff --git a/kernel/src/common/buffer.cpp b/kernel/src/common/buffer.cpp index 14f8558f..588bdd39 100644 --- a/kernel/src/common/buffer.cpp +++ b/kernel/src/common/buffer.cpp @@ -42,10 +42,8 @@ Buffer::Buffer(void* source, size_t size, bool update_offset) * @brief Destroy the buffer, freeing the memory */ Buffer::~Buffer() { - Logger::DEBUG() << "CLEARING BUFFER\n"; - - if(!m_dont_delete) - delete m_bytes; + if(!m_dont_delete && m_bytes) + delete[] m_bytes; } /** @@ -58,13 +56,31 @@ uint8_t *Buffer::raw() const{ } /** - * @brief Fulls the buffer with 0's + * @brief Fulls the buffer with 0's and resets the offset */ void Buffer::clear() { + m_offset = 0; memset(m_bytes, 0, m_capacity); } +/** + * @brief Full the buffer with a specified byte at an offset + * + * @param byte The byte to write + * @param offset Where to start writing (deafults to 0) + * @param amount + */ +void Buffer::full(uint8_t byte, size_t offset, size_t amount) { + + // Prevent writing past the buffer bounds + if (amount == 0) amount = m_capacity - m_offset - offset; + ASSERT(m_offset + offset + amount <= capacity(), "Buffer overflow"); + + memset(m_bytes + m_offset + offset, byte, amount); + +} + /** * @brief The max bytes the buffer can currently store. @see Buffer:grow(size_t size) to increase this. * @return The length of the buffer storage array @@ -84,12 +100,16 @@ void Buffer::resize(size_t size) { auto* new_buffer = new uint8_t[size]; // Copy the old buffer - memcpy(new_buffer, m_bytes, m_capacity); + if(m_bytes) + memcpy(new_buffer, m_bytes, m_capacity); // Store the new buffer - delete m_bytes; - m_bytes = new_buffer; - m_capacity = size; + if(!m_dont_delete && m_bytes) + delete[] m_bytes; + + m_bytes = new_buffer; + m_capacity = size; + m_dont_delete = true; } @@ -100,10 +120,22 @@ void Buffer::resize(size_t size) { */ void Buffer::set_offset(size_t offset) { - m_offset = offset; + if(update_offset) + m_offset = offset; } +/** + * @brief Safely writes a byte to the buffer at the current offset + * + * @param byte The byte to write + */ +void Buffer::write(uint8_t byte) { + m_bytes[m_offset] = byte; + set_offset(m_offset + 1); +} + + /** * @brief Safely writes a byte to the buffer at the specified offset * @@ -117,7 +149,18 @@ void Buffer::write(size_t offset, uint8_t byte) { // Set the byte m_bytes[m_offset + offset] = byte; + set_offset(m_offset + 1); + +} +/** + * @brief Safely reads a byte from the buffer at the current offset + * + * @param offset The offset into the buffer storage array + */ +uint8_t Buffer::read() { + set_offset(m_offset + 1); + return m_bytes[m_offset - 1]; } /** @@ -125,13 +168,14 @@ void Buffer::write(size_t offset, uint8_t byte) { * * @param offset The offset into the buffer storage array */ -uint8_t Buffer::read(size_t offset) const { +uint8_t Buffer::read(size_t offset) { // Prevent writing past the buffer bounds ASSERT(m_offset + offset < capacity() && offset >= 0, "Buffer overflow"); // Set the byte - return m_bytes[m_offset + offset]; + set_offset(m_offset + 1); + return m_bytes[(m_offset - 1) + offset]; } /** @@ -181,6 +225,7 @@ void Buffer::copy_from(const Buffer *buffer, size_t length, size_t offset) { * @param source Where to read from * @param length How much to read * @param offset Where to start writing into this at + * @param offset Where to start reading from the other buffer */ void Buffer::copy_from(const Buffer *buffer, size_t length, size_t offset, size_t offset_other) { @@ -201,10 +246,7 @@ void Buffer::copy_from(void const *source, size_t length) { // Copy the bytes ASSERT(length + m_offset <= m_capacity, "Copy exceeds buffer capacity"); memcpy(m_bytes + m_offset, source, length); - - // Update the offset - if(update_offset) - set_offset(m_offset + length); + set_offset(m_offset + length); } /** @@ -219,10 +261,7 @@ void Buffer::copy_from(void const *source, size_t length, size_t offset) { // Copy the bytes ASSERT((length + offset + m_offset) <= m_capacity, "Copy exceeds buffer capacity"); memcpy(m_bytes + offset + m_offset, source, length); - - // Update the offset - if(update_offset) - set_offset(m_offset + length); + set_offset(m_offset + length); } @@ -296,10 +335,7 @@ void Buffer::copy_to(void* destination, size_t length) { // Copy the bytes ASSERT(length + m_offset <= (m_capacity), "Copy exceeds buffer capacity"); memcpy(destination, m_bytes + m_offset, length); - - // Update the offset - if(update_offset) - set_offset(m_offset + length); + set_offset(m_offset + length); } @@ -315,9 +351,6 @@ void Buffer::copy_to(void *destination, size_t length, size_t offset) { // Copy the bytes ASSERT((length + offset + m_offset) <= m_capacity, "Copy exceeds buffer capacity"); memcpy(destination, m_bytes + offset + m_offset, length); - - // Update the offset - if(update_offset) - set_offset(m_offset + length); + set_offset(m_offset + length); } \ No newline at end of file diff --git a/kernel/src/drivers/disk/ata.cpp b/kernel/src/drivers/disk/ata.cpp index 96d7ba43..28d18306 100644 --- a/kernel/src/drivers/disk/ata.cpp +++ b/kernel/src/drivers/disk/ata.cpp @@ -130,11 +130,11 @@ void AdvancedTechnologyAttachment::read(uint32_t sector, buffer_t* data_buffer, // Read from the disk (2 bytes) and store the first byte uint16_t read_data = m_data_port.read(); - data_buffer -> write(i, read_data & 0x00FF); + data_buffer -> write(read_data & 0x00FF); // Place the second byte in the array if there is one if(i + 1 < amount) - data_buffer -> write(i + 1, (read_data >> 8) & 0x00FF); + data_buffer -> write((read_data >> 8) & 0x00FF); } // Read the remaining bytes as a full sector has to be read @@ -181,11 +181,11 @@ void AdvancedTechnologyAttachment::write(uint32_t sector, const buffer_t* data, // Write the data to the device for (uint16_t i = 0; i < m_bytes_per_sector; i+= 2) { - uint16_t writeData = data -> read(i); + uint16_t writeData = data -> read(); // Place the next byte in the array if there is one if(i+1 < count) - writeData |= (uint16_t)(data ->read(i+1)) << 8; + writeData |= (uint16_t)(data ->read()) << 8; m_data_port.write(writeData); } diff --git a/kernel/src/filesystem/ext2.cpp b/kernel/src/filesystem/ext2.cpp index 68e3a7d0..a8ba2fe9 100644 --- a/kernel/src/filesystem/ext2.cpp +++ b/kernel/src/filesystem/ext2.cpp @@ -16,10 +16,8 @@ Ext2Volume::Ext2Volume(drivers::disk::Disk *disk, lba_t partition_offset) partition_offset(partition_offset) { - ext2_lock.unlock(); - // Read superblock - buffer_t superblock_buffer(&superblock, 512); + buffer_t superblock_buffer(&superblock, 1024); disk->read(partition_offset + 2, &superblock_buffer, 512); disk->read(partition_offset + 3, &superblock_buffer, 512); @@ -35,20 +33,21 @@ Ext2Volume::Ext2Volume(drivers::disk::Disk *disk, lba_t partition_offset) // Parse the superblock block_size = 1024 << superblock.block_size; total_block_groups = (superblock.total_blocks + superblock.blocks_per_group - 1) / superblock.blocks_per_group; - block_group_descriptor_table = (block_size == 1024) ? 2 : 1; + block_group_descriptor_table_block = superblock.starting_block + 1; block_group_descriptor_table_size = total_block_groups * sizeof(block_group_descriptor_t); pointers_per_block = block_size / sizeof(uint32_t); - inodes_per_block = block_size / superblock.block_size; + inodes_per_block = block_size / superblock.inode_size; sectors_per_block = block_size / 512; - blocks_per_inode_table = (superblock.inode_size * superblock.inodes_per_group) / block_size; - sectors_per_inode_table = (superblock.inode_size * superblock.inodes_per_group) / 512; + blocks_per_inode_table = (superblock.inode_size * superblock.inodes_per_group + (block_size - 1)) / block_size; + sectors_per_inode_table = (superblock.inode_size * superblock.inodes_per_group + (512 - 1)) / 512; // Read the block groups block_groups = new block_group_descriptor_t*[total_block_groups] {nullptr}; + uint32_t bgdt_lba = partition_offset + block_group_descriptor_table_block * sectors_per_block; uint32_t sectors_to_read = (block_group_descriptor_table_size + block_size - 1) / block_size * sectors_per_block; buffer_t bg_buffer(sectors_to_read * 512); for (uint32_t i = 0; i < sectors_to_read; ++i) - disk->read(partition_offset + block_group_descriptor_table * sectors_per_block + i, &bg_buffer, 512); + disk->read(bgdt_lba + i, &bg_buffer, 512); // Store the block groups for (uint32_t i = 0; i < total_block_groups; ++i) { @@ -69,6 +68,7 @@ void Ext2Volume::write_block(uint32_t block_num, buffer_t* buffer) { // Ensure the buffer is in the right format buffer -> set_offset(0); + bool old = buffer->update_offset; buffer -> update_offset = true; // Read each sector of the block @@ -77,6 +77,7 @@ void Ext2Volume::write_block(uint32_t block_num, buffer_t* buffer) { // Reset buffer buffer -> set_offset(0); + buffer -> update_offset = old; }; /** @@ -116,7 +117,6 @@ void Ext2Volume::read_block(uint32_t block_num, buffer_t* buffer) const { // Ensure the buffer is in the right format buffer -> set_offset(0); - buffer -> update_offset = true; // Read each sector of the block for (size_t i = 0; i < sectors_per_block; ++i) @@ -155,29 +155,6 @@ inode_t Ext2Volume::read_inode(uint32_t inode_num) const { return inode; } -/** - * @brief Read a blockgroup from the disk - * - * @param group_num The group to read - * @return The block group descriptor for the specified group - */ -block_group_descriptor_t Ext2Volume::read_block_group(uint32_t group_num) { - block_group_descriptor_t descriptor; - - // Locate the group - uint32_t offset = group_num * sizeof(block_group_descriptor_t); - uint32_t block = offset / block_size; - uint32_t in_block_offset = offset % block_size; - - // Read the block group - buffer_t buffer(block_size); - read_block(2 + block, &buffer); - - // Read the descriptor from the group - buffer.copy_to(&descriptor, sizeof(block_group_descriptor_t), in_block_offset); - return descriptor; -} - /** * @brief Allocates a single block to be used by an inode * @@ -196,8 +173,6 @@ uint32_t Ext2Volume::allocate_block() { */ Vector Ext2Volume::allocate_blocks(uint32_t amount) { - Logger::DEBUG() << "ALLOCATING 0x" << (uint64_t )amount << " BLOCKS"; - // No blocks to allocate if (!amount) return {1,0}; @@ -269,7 +244,7 @@ common::Vector Ext2Volume::allocate_group_blocks(uint32_t block_group, bitmap.raw()[i / 8] |= (uint8_t) (1u << (i % 8)); // Zero out data - uint32_t block = block_group * superblock.blocks_per_group + (block_size == 1024 ? 1 : 0) + i; + uint32_t block = block_group * superblock.blocks_per_group + superblock.starting_block + i; write_block(block, &zeros); result.push_back(block); @@ -292,16 +267,20 @@ common::Vector Ext2Volume::allocate_group_blocks(uint32_t block_group, */ void Ext2Volume::write_back_block_groups() { - // Store the block groups - uint32_t sectors_to_write = (block_group_descriptor_table_size + block_size - 1) / block_size * sectors_per_block; + // Locate the block groups + uint32_t bgdt_blocks = (block_group_descriptor_table_size + block_size - 1) / block_size; + uint32_t sectors_to_write = bgdt_blocks * sectors_per_block; + uint32_t bgdt_lba = partition_offset + block_group_descriptor_table_block * sectors_per_block; + + // Copy the block groups into the buffer buffer_t bg_buffer(sectors_to_write * 512); for (uint32_t i = 0; i < total_block_groups; ++i) bg_buffer.copy_from(block_groups[i], sizeof(block_group_descriptor_t)); - // Write the block groups + // Write the buffer to disk bg_buffer.set_offset(0); for (uint32_t i = 0; i < sectors_to_write; ++i) - disk->write(partition_offset + block_group_descriptor_table * sectors_per_block + i, &bg_buffer, 512); + disk->write(bgdt_lba + i, &bg_buffer, 512); } /** @@ -309,9 +288,11 @@ void Ext2Volume::write_back_block_groups() { */ void Ext2Volume::write_back_superblock() { + // Store superblock buffer_t buffer(1024); buffer.copy_from(&superblock, sizeof(superblock_t)); + buffer.set_offset(0); // Write to disk disk->write(partition_offset + 2, &buffer, 512); @@ -349,9 +330,13 @@ uint32_t Ext2Volume::create_inode(bool is_directory) { buffer_t bitmap(block_size); read_block(block_group -> block_inode_bitmap, &bitmap); - // Find a free inode + // First group contains reserved inodes uint32_t inode_index = 0; - for (; inode_index < superblock.blocks_per_group; ++inode_index) { + if (bg_index == 0 && superblock.first_inode > 1) + inode_index = superblock.first_inode - 1; + + // Find a free inode + for (; inode_index < superblock.inodes_per_group; ++inode_index) { // Block is already used if ((bitmap.raw()[inode_index / 8] & (1u << (inode_index % 8))) != 0) @@ -364,10 +349,12 @@ uint32_t Ext2Volume::create_inode(bool is_directory) { break; } - inode_index += bg_index * superblock.inodes_per_group; + + // Convert into the 1-based inode index in the group + inode_index += bg_index * superblock.inodes_per_group + 1; // Save the changed metadata - write_block(block_group -> block_usage_bitmap, &bitmap); + write_block(block_group -> block_inode_bitmap, &bitmap); write_back_block_groups(); write_back_superblock(); @@ -376,8 +363,9 @@ uint32_t Ext2Volume::create_inode(bool is_directory) { inode.creation_time = time_to_epoch(Clock::active_clock()->get_time()); inode.last_modification_time = time_to_epoch(Clock::active_clock()->get_time()); inode.block_pointers[0] = allocate_block(); - inode.hard_links = is_directory ? 1 : 0; - inode.type = (uint32_t)(is_directory ? InodeType::DIRECTORY : InodeType::FILE); + inode.hard_links = is_directory ? 2 : 1; + inode.type = ((uint16_t)(is_directory ? InodeType::DIRECTORY : InodeType::FILE) >> 12) & 0xF; + inode.permissions = (uint16_t)(is_directory ? InodePermissionsDefaults::DIRECTORY : InodePermissionsDefaults::FILE) & 0x0FFF; write_inode(inode_index, &inode); ext2_lock.unlock(); @@ -408,7 +396,7 @@ InodeHandler::InodeHandler(Ext2Volume* volume, uint32_t inode_index) * @return The size of the inode data (not the inode itself) */ size_t InodeHandler::size() const { - return (size_t)inode.size_upper | inode.size_lower; + return ((size_t)inode.size_upper << 32) | (size_t)inode.size_lower; } /** @@ -437,7 +425,7 @@ void InodeHandler::parse_indirect(uint32_t level, uint32_t block, buffer_t* buff // Read the block m_volume -> read_block(block, buffer); - auto* pointers = (uint32_t*)buffer; + auto* pointers = (uint32_t*)(buffer ->raw()); // Parse the pointers for (size_t i = 0; i < m_volume->pointers_per_block; ++i) { @@ -605,7 +593,7 @@ void Ext2File::write(buffer_t const *data, size_t amount) { // Prepare for writing m_volume -> ext2_lock.lock(); const uint32_t block_size = m_volume -> block_size; - buffer_t buffer(block_size, false); + buffer_t buffer(block_size); // Expand the file if(m_offset + amount > m_size) @@ -633,7 +621,7 @@ void Ext2File::write(buffer_t const *data, size_t amount) { size_t writable = (amount - written < block_size - buffer_start) ? (amount - written) : (block_size - buffer_start); // Update the block - buffer.copy_from(data + written, writable, buffer_start); + buffer.copy_from(data, writable, buffer_start, written); m_volume ->write_block(block, &buffer); written += writable; } @@ -658,7 +646,7 @@ void Ext2File::read(buffer_t* data, size_t amount) { // Prepare for reading m_volume -> ext2_lock.lock(); const uint32_t block_size = m_volume -> block_size; - buffer_t buffer(block_size, false); + buffer_t buffer(block_size); // Force bounds if(m_offset + amount > m_size) @@ -682,7 +670,7 @@ void Ext2File::read(buffer_t* data, size_t amount) { size_t readable = (amount - read < block_size - buffer_start) ? (amount - read) : (block_size - buffer_start); // Read the block - buffer.copy_from(data + read, readable, buffer_start); + buffer.copy_from(data, readable, buffer_start, read); read += readable; } @@ -718,7 +706,7 @@ void Ext2Directory::parse_block(buffer_t * buffer) { while (offset < m_volume -> block_size){ // Read the entry - auto* entry = (directory_entry_t*)(buffer + offset); + auto* entry = (directory_entry_t*)(buffer->raw() + offset); m_entries.push_back(*entry); // Not valid @@ -730,18 +718,14 @@ void Ext2Directory::parse_block(buffer_t * buffer) { m_entry_names.push_back(filename); uint32_t inode = entry->inode; - // Correct the entries size (last one fulls the rest of the block) - entry -> size = sizeof(directory_entry_t) + filename.length(); - entry -> size += entry -> size % 4 ? 4 - entry -> size % 4 : 0; - // Create the object - switch ((InodeType)entry->type) { + switch ((EntryType)entry->type) { - case InodeType::FILE: + case EntryType::FILE: m_files.push_back(new Ext2File(m_volume, inode, filename)); break; - case InodeType::DIRECTORY: + case EntryType::DIRECTORY: m_subdirectories.push_back(new Ext2Directory(m_volume, inode, filename)); break; @@ -751,7 +735,7 @@ void Ext2Directory::parse_block(buffer_t * buffer) { } // Go to next - offset += entry -> size; + offset += entry-> size; } } @@ -796,7 +780,7 @@ void Ext2Directory::write_entries() { // Calculate the size needed to store the entries and the null entry size_t size_required = sizeof(directory_entry_t); for (int i = 0; i < m_entries.size(); ++i) { - size_t size = sizeof(directory_entry_t) + m_entries[i].name_length; + size_t size = sizeof(directory_entry_t) + m_entries[i].name_length + 1; size += (size % 4) ? 4 - size % 4 : 0; size_required += size; } @@ -825,6 +809,9 @@ void Ext2Directory::write_entries() { directory_entry_t& entry = m_entries[i]; char* name = m_entry_names[i].c_str(); + // Update the size + entry.size = sizeof(directory_entry_t) + entry.name_length + 1; + entry.size += (entry.size % 4) ? 4 - (entry.size % 4) : 0; // Entry needs to be stored in the next block if(entry.size + buffer_offset > block_size){ @@ -848,8 +835,6 @@ void Ext2Directory::write_entries() { m_volume -> write_block(m_inode.block_cache[current_block], &buffer); // Clean up - m_entries.pop_back(); - m_entry_names.pop_back(); m_volume->ext2_lock.unlock(); } @@ -858,7 +843,7 @@ directory_entry_t Ext2Directory::create_entry(const string& name, uint32_t inode // Create the inode directory_entry_t entry {}; entry.inode = inode ? inode : m_volume -> create_inode(is_directory); - entry.type = (uint32_t)InodeType::DIRECTORY; + entry.type = (uint32_t)(is_directory ? EntryType::DIRECTORY : EntryType::FILE); entry.name_length = name.length(); entry.size = sizeof(entry) + entry.name_length; entry.size += entry.size % 4 ? 4 - entry.size % 4 : 0; diff --git a/kernel/src/filesystem/filesystem.cpp b/kernel/src/filesystem/filesystem.cpp index 7fe07525..b1b7bfc1 100644 --- a/kernel/src/filesystem/filesystem.cpp +++ b/kernel/src/filesystem/filesystem.cpp @@ -136,7 +136,7 @@ File::~File() = default; * @param data The byte buffer to write * @param size The amount of data to write */ -void File::write(const uint8_t* data, size_t amount) +void File::write(const common::buffer_t* data, size_t amount) { } @@ -146,7 +146,7 @@ void File::write(const uint8_t* data, size_t amount) * @param data The byte buffer to read into * @param size The amount of data to read */ -void File::read(uint8_t* data, size_t amount) +void File::read(common::buffer_t* data, size_t amount) { } diff --git a/kernel/src/kernel.cpp b/kernel/src/kernel.cpp index e9491e65..b27898a7 100644 --- a/kernel/src/kernel.cpp +++ b/kernel/src/kernel.cpp @@ -71,16 +71,11 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic driver_manager.initialise_drivers(); driver_manager.activate_drivers(); - // FS Tests - Directory* newdir = vfs.create_directory("/test/a"); - for (const auto &item: newdir->subdirectories()) - Logger::DEBUG() << "DIRECTORY: " << item->name() << "\n"; - -// File* newf = vfs.create_file("/test/bob.txt"); -// uint32_t write = 12 * 700; -// auto* test_data = new uint8_t[write]; -// memset(test_data, (uint32_t)'a', write); -// newf -> write(test_data, write); + // FS Tests (TOOD: Cant read contents of maxos created entries) + File* file = vfs.open_file("/test/working.file"); + buffer_t fb(100); + file->read(&fb, 100); + Logger::DEBUG() << "FILE:" << (char*)fb.raw() << "\n"; Logger::HEADER() << "Stage {4}: System Finalisation\n"; Scheduler scheduler(multiboot); @@ -98,16 +93,12 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic // - [x] Read subdirectories contents // - [x] Read files // - [x] Write files -// - [ ] Create subdirectories +// - [x] Create subdirectories // - [ ] Delete subdirectories // - [ ] Rename directory // - [ ] Rename file -// - [ ] Create files +// - [x] Create files // - [ ] Delete files -// - [ ] Create files on a different mount point -// - [ ] Delete files on a different mount point -// - [ ] Read directories on a different mount point -// - [ ] Create directories on a different mount point // - [ ] Stress test the filesystem: 1000s of files in a directory, long nested directories, long path files, large file r/w From e41d1f5a94bc6b950e8d183d2d46aeaecbf6af84 Mon Sep 17 00:00:00 2001 From: Max Tyson <98maxt98@gmail.com> Date: Tue, 19 Aug 2025 10:29:00 +1200 Subject: [PATCH 27/38] Undo Deleted Images --- docs/Screenshots/Boot/Console.png | Bin 0 -> 6601 bytes docs/Screenshots/Boot/Console_v2.png | Bin 0 -> 38427 bytes docs/Screenshots/Boot/Console_v3.png | Bin 0 -> 44779 bytes docs/Screenshots/Drivers/PCI Readout.png | Bin 0 -> 29602 bytes docs/Screenshots/Filesystem/ATA_Hardrives.png | Bin 0 -> 19487 bytes .../Filesystem/FAT32_read_dirs_and_files.png | Bin 0 -> 148684 bytes docs/Screenshots/GUI/Circles.png | Bin 0 -> 18710 bytes docs/Screenshots/GUI/Square_Widgets_Test.png | Bin 0 -> 4495 bytes docs/Screenshots/GUI/Windows.png | Bin 0 -> 5458 bytes docs/Screenshots/GUI/Windows_VESA.png | Bin 0 -> 6975 bytes docs/Screenshots/WTF/Button Text.png | Bin 0 -> 15075 bytes .../WTF/Console clearing in GUI mode.png | Bin 0 -> 25348 bytes docs/Screenshots/WTF/Yellow Tint.png | Bin 0 -> 6838 bytes 13 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/Screenshots/Boot/Console.png create mode 100644 docs/Screenshots/Boot/Console_v2.png create mode 100644 docs/Screenshots/Boot/Console_v3.png create mode 100644 docs/Screenshots/Drivers/PCI Readout.png create mode 100644 docs/Screenshots/Filesystem/ATA_Hardrives.png create mode 100644 docs/Screenshots/Filesystem/FAT32_read_dirs_and_files.png create mode 100644 docs/Screenshots/GUI/Circles.png create mode 100644 docs/Screenshots/GUI/Square_Widgets_Test.png create mode 100644 docs/Screenshots/GUI/Windows.png create mode 100644 docs/Screenshots/GUI/Windows_VESA.png create mode 100644 docs/Screenshots/WTF/Button Text.png create mode 100644 docs/Screenshots/WTF/Console clearing in GUI mode.png create mode 100644 docs/Screenshots/WTF/Yellow Tint.png diff --git a/docs/Screenshots/Boot/Console.png b/docs/Screenshots/Boot/Console.png new file mode 100644 index 0000000000000000000000000000000000000000..aba6cb5685da50bed2a83b67a41fb0aceed26cb8 GIT binary patch literal 6601 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 literal 0 HcmV?d00001 diff --git a/docs/Screenshots/Boot/Console_v3.png b/docs/Screenshots/Boot/Console_v3.png new file mode 100644 index 0000000000000000000000000000000000000000..6e59bb881b06cfbf281783ba3432ecabff7cffda GIT binary patch literal 44779 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 literal 0 HcmV?d00001 diff --git a/docs/Screenshots/Drivers/PCI Readout.png b/docs/Screenshots/Drivers/PCI Readout.png new file mode 100644 index 0000000000000000000000000000000000000000..671d55daf1ea9cac5681abbad212add19caa117d GIT binary patch literal 29602 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 N%+uA+Wt~$(698^5%dP+b literal 0 HcmV?d00001 diff --git a/docs/Screenshots/Filesystem/FAT32_read_dirs_and_files.png b/docs/Screenshots/Filesystem/FAT32_read_dirs_and_files.png new file mode 100644 index 0000000000000000000000000000000000000000..e87b161b41673482a9f5115a43239264b76ba034 GIT binary patch literal 148684 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 KbFCPF9RCZ)du#Uq literal 0 HcmV?d00001 diff --git a/docs/Screenshots/GUI/Square_Widgets_Test.png b/docs/Screenshots/GUI/Square_Widgets_Test.png new file mode 100644 index 0000000000000000000000000000000000000000..c706a54ea7933785c44dadda2f5330a4c1c79c46 GIT binary patch literal 4495 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 V(o8F;GQF38&vpjypl*-(`rk7z{Ja1F literal 0 HcmV?d00001 diff --git a/docs/Screenshots/GUI/Windows.png b/docs/Screenshots/GUI/Windows.png new file mode 100644 index 0000000000000000000000000000000000000000..624a13622b99859c1f20a91d94e9ec0ee6b461d2 GIT binary patch literal 5458 zcmeHLe^38s3M+SJ2n9TL7l&FhN$jeyhaz`N>ZB(N-go3&mfK}_ySs=q zY0PwNJI!?XV`t~S+wcAHywCf*-|nxr=Wlu9v2Q#ELC_QSt((3NL8)#CN_oTdh_#FwY%jo#!-dvNyA zc#Dg&T%OLD>9;B=aV&qyi^7+tt9-HW8e7Kn^S(uAq|~|#g~EZ8Ii|X%!q=`MDVA^x zy!LWN8hN>;?=}f}V}nG~)GFI~J~;>v_&L~msa>=PKjWUgkV_Ra0(vqOjej{m94xON zJ&%i#g2{6vO`KZ)>)89b4xaEf^Xux|nuFnnC2s9Uk}ZLSb;3b?I~EdWd|j>J;!`hF zgXSzZd-ikwmb-Ee7qK{5Q)CrdLs19lAThq%f(gXHPH*K`^h`b;V$})Q!w3P(P!y0t zQ5*f?*G^ri8A!ia=U2mGx1+;CxdOb5(|j@rPKU%(@C((8!@s2zrHoCSn==^Li!Abv z>S^Dq9}NORM8^}j;CD@R56~vZlZ>jw20caB$1rx^u{ zUlMMzA0-(~pbN^8zYwQ&kjpkwB2bd$bGcd$naG_~l#?`*tenJo7)T*(r?+#R zR>{oKfzx`uHTvred^|p85OZ04|3ec0Q*L)HbvL5Ix`od3oz>m|5;jga;BArGm3gf&;48 zM*&}mtuZUID>uSVdr-`NZ3nToTEkuXsqH*qa5)yu)-I|0B*#$E8!%GpC?STdAc02^ z^@pFcUt@z9rp?+VR6fVx7~L+2J^l->02YZl2uW$r$Jl~43bD~CYE&lr{CVD3<+zfiLDWyH?yu$#k*ErgVD1QK2V_Pc%z@ z9ziHVN|pgDtT293eyeg|EaUAY9h%g;I#9nR zzEU@C9SRum@A_x>cXJYdj&#zT^juiWkB_zirU(g?wt4X~UPfcwob%A9TVl9BJ=u{Tj}Ce{a5aXqKg`(^f3ODX?Bw1p7@sGH6V+6IJK5v~&$wzJCZV zG$eKVwYL`xYI^zqjNv1R zX_Mic>FHuZEN;x^BPVi9&;BXngg*JQ+>i~eF_rF#+g+Uz{q3@TvAgk)IVR|!)<{sVE^Lr~uz^N2wOd&Hp8ztbroQGmd?j<+Wry~y6tZv5 L-{k-9Zuj2-YxV}= literal 0 HcmV?d00001 diff --git a/docs/Screenshots/GUI/Windows_VESA.png b/docs/Screenshots/GUI/Windows_VESA.png new file mode 100644 index 0000000000000000000000000000000000000000..2acb77dca3b17a0166ed44b7d598ef26b2b4c726 GIT binary patch literal 6975 zcmeHLe`pg|9Dk|7Mp><=8~!$3TDA$JsGWAuB~giM1=qUa2+WyUWGZ(3!5qeLO)gl$ z>ZB)3H%l;A*qDQEMj8HEOp|S3Q5(+VaFEv30y?Oo!LUaps1_D+&Uhx;LH;2-zL zd%U~%-uL@`-|y%9{l51-vZcDRVD{qK0Dyu`RfhKfP$~eJp-|0)f1D-Xe*wUfnVSsj zw;nJ}^uN=-?cOIF-z^wjbglmK$h|#B*W6s)*g6xry!o3K`4#HDt{JbNNSvAH8*5G{ z0$7R+LmEE)0G1Z8v=|H2VyOc7C;7t>*n_z*=fQs6A(ixjXr$iICiQG+tj`Pt~rIJuN&a9 zenj!+t|f}@8`r(UNy;BcSS98U+Q3pM80i+0ZmyRjx!#~>lD=ZLQDrTgl>pe6A@-eX zk)ghQW|Hh7Nl0_@PLIopQfA#RJz2VuGGvJN;`*?M`HHI1*+gc9B;pms@Xjm`wlr^W zg0{MJ*i zVFpODN@dVw1D5O-a-_)wZaJ1dpV`8UfVH`-mP@0L-!>K^cGq+s4-~FV>}7AkXirMF z5m2-NCR1QnNof;kBvPPlt~?>DLUS{iB*Z%NPg05QG+e;v3a~X6%}~) zRhVMFhVP(~Ht-f4o{)xDj{-b(YoB-A!e01161l$yK4?CIZPtJ)`hK+-G)K}+y3%8W_*7Tp2F&bZx4sqk`8Fjcp+(!3uw|cPO z;@V!SyHy3&b-?y>m#Wz!*0pfO7Mi|b#3_mim2t^Cyl7-BqkR#f+9J-Omt*+|u&;*g z5Uv5(wI@g%R7F!~nK~V}3|is_Z1W6u#%wKQuP{ZKa~0)9pE7J-p!n`Q@A)jJ$^iRi zPh^CA5bH1!C^xmsj4{D|4(nt%eTPfk^E0U_&MyG_Y)O#&hr`KL2O7jZr*Xa8EG=hf zn(?5GZc;o=Si{6%GUUR-R|~dGjQ lQ6i%xPj-Xn)(woxgx>0mBBz$toq%Qnn>JP(&TiOo@DE!WQN{oO literal 0 HcmV?d00001 diff --git a/docs/Screenshots/WTF/Button Text.png b/docs/Screenshots/WTF/Button Text.png new file mode 100644 index 0000000000000000000000000000000000000000..45ff25f6a5e85e3439c6b22205262bcaff694ab9 GIT binary patch literal 15075 zcmeHudsLHWw)cxv?Pwj&)TynXZJALK6)3F?QUsHBMnOSJt3V9O#Zw^)SXwA*Bo}Ot z?Wsj6w@|KWOOX;VQ6eY^31i1HVpWKL@S>Kwo)~s*MS!@1i zUA*t}KF_o7d;j*%CvUzHx?uPziZ z;Ex}Q{{7Mam=S|VwMrQ-REx-5Jvl3npH&4+uKG&ldZ0dx%F=qzQR)8m@qGmpV14aI zV@Gdnat-4OS5=oBs(1>+W`A4b{zI%-Qe+~KC{9nj#z{Zm9Q=w%a`CK9`1w3V&SNWapB z?vnmb+4}#xDtgOsHqm@vwqL_PsNu)YR9*~K_P0ikc=EO%(1`TXCcgSe)g(cCIhhx9 zK-RYsJ9)m_ts|pxNAl{<|19ex@h7sPGnjL&?BH)BCG}=ye^zz-b$mJU5i{tw`2ZH+oCkx#l&fF499b2DQE%p)W*k*zGN=WoTcx^z)`5~W(&r1f8d5&ks3^Z`?ybkXSd=>hgVMpwo# z&dIL0s3(`>Tv1k+UkfpNII`-;T6vFIJJot`WLU47tQb`;!Dt@=9p)6#4Ho>(V0{~( zg;dg0zFX}tmaV|%pQ>?BboC)O)GM+MUtuHWOuR^$WW>~Q#kpglA=%pZ6mOfRCpo!u zN;Uf$Y33X^)v9Yf_h}JT zHd@?L#y@>Cnidu26;Hth&;=K~#v3Pn&=Se2`lX15g4f0iS*9w2ch>&fcY z6yBX9zVcFy2wytM5iqKGnZ@_w1zY8XNU2&}a?BT0uCzC!KRAE*kw|+4iEt>6R5usO zs~7tb@5v=R){qvyahR!=HlBEawb~r7b^l*6}It z+nmWmSIitSzI6Tz@0VH}{A0`FtJZKQ*!2cwvAVK9-!O06ZsVunNK}YIS zRX^Ti_)jJj_Kn?IikZs0-DY1W$2Q(Qe7Q6(VH&hwP~G0lmVH!DsC4XdWOSp^@Lyi$ zj5;7XpU9;(~bw!=`qQFMTpPklPz5Ha2skEY{~keP@4M@jYdwT}6Y}U{es(o~$l- z*P${~yQ?@NzQZ}e}Yv@n|menADYk9vlwrc9=dduiAu zFSyf{!{p~KdQtjF`ksc?^l;dUw0)4CEOAGST1VZA{ZQQP_8%h!!2u22&C|sUVXI(( zm)t`?ps;OlnEZ30{dxpyURwfeadof#t?K5sa4Jjg{gWjqkZK0x6hzYo;jc9DTEt{Gj|w zlw=KG&gUb=2!0l8D+SW3_Eaqv><;&O=uF!|+hw+Vb|$gec1wl#Y(&iNs^!?o9*_XJ zSHHI^o8RBl((Puh)d_AhU)egqv(lrmem?t*B7!c^x`V^Agh|)u*3jKYy*0`FdL@G; zTa**9Pj{O4*OD`)PKPnEf+3jF3cB5BX9)bg#8{Gr3%|y&`SmsK^ULU_3o1>^HV`HR zlgPd3^DwG>7fa1Iro;?i^}t;Y7D^68FW7Jwe^JLBh@@V@#lcJ8?QD2o1}1e^W3^mX zuou<<#0YLotuUnVlCG5SI`z`PoK&6ss=*}A>dLSyO9OUk3!MKsi}rSWdmUlR0B#Nb zUXy#`{mQ{bJdgEBHPW<7{t)qir1S-m?bx?t73bbZW_K*Ejtgot8LJrTkVQ*)E!+y5 zjTz%X>9Ck;m8<)G;UVtG!8~LE_gcGa%U%Fsvo$Pfi%X(EWB{-DkehMz(kj1zSt{kI zMA+WXK>p_Vape0NLdKU8E_g_S!I|upR~K^yVz(Ht{9bsOahJ;-3J2P$Fj)Bewk z6vNKTpTz3l(w)w0DWnxvreC3C`&75nj*WQYD;f^ML@|NSe^<7V6?lWJ_{9HyJzL3>UO?23+&0HlmCoTJtLS#M9fOMeL=4;`aWj z3B#0$`VMvyB>q&Bw0z_s$Y-#1Zycy>UlUwTzR%e9v!A{+(_+G4*S0+`Z$AJlLnp2J zzJkUMBJNXAMEwl^`k6sw|F(5?mUVdV2}r(%IyB@>VA0mY4)?$KnBC74N)4s?#DHW6 zy-P5pVPDeLm9L(bdG{F`T5t7lE0iq}M6xF+Aw77*Xg zU_mGzewbDXWJth{|5P1tQJWkfnYTwXRvFQDTDmXoTAI~tSSv=d5?~;Jo5vDz2 zq`F3L+;*mqq+)LJPQSXxCh#XPj0@g`dEgal2!^3(Gk^6hMZhEN;Cz3)*cz(*78$5d=Q)H^ZDx<_xA$a?ON;YlGv>K#WF zvHU^d7A=2jN(K=RS1V#NF-(eUSp)OMGx0yMT6awC~O~@|B z41cI`7es}t#~M$qjGb27Ok?q-4-BQnNXu-u)?yDfTNf_jv+cUKJaMts&#TYjTimgO zHL7n*QjVs->}6zR(VMIxqy>xljL1{S_M7&U+uWBAm4ICf8~@n1DGc1c*M7&}J)X?# zMZ!a64iP`wv5X_dFBwV$tVu$sMnncj1eTwWqFS{NFdDRQ^K}>m3hcoL>&Sw^){Nu!o3FO*(eQb& zeZ^zyabVc95B!h-k|6Be*NeS}tllYD&eq!=m*2S(@R4@xWlGBorDuCrFZHFIxO0qO zk1cqi#T_U1=??9qbKCkG?D-2UYKkH*rw~b2A9N6-LAJEDXK(u^LwNujGG4I!<%uL8 zEq@F1oiSW&4`jB@e0tX0=r0%BB6bHC1>hX{1=Y6~Vq;hHRC-9etrj3#^g}e;(DFHP zjl*FlluUxJ&PI2thn(P2J+e1QFhYCy z+k;I-jDH-_6qT;iS(9h5#Kp6n3d-Sd)4>^yl{U+&rP%yu>n`S!bVin)lqWhypvcZ~ zBmbVd0iEcI(RPKKHV~L=h>kNIFB0zPRs5myPHtB*BS((FieT^XQ{g5S!C|&am)ZC* zVp~-4#JM=4BfD6UldX_VHdIJV7;Np9 zlN?#3ZLj3?Aw|SN!IwdU_LOmmAFH5YZ6)1qz83#Dw9mtwlxkYu=kQW?>NZHOjFz`K z9`jw^jl7_v4&fHN5N;7?x?UpGxUXWRH|wsn583h&ewII;={k##;sY$i34Xhs6RTbJ zv%B4vPfBR&()QEhfK=^wKDM}#iy6+FWbawZ151nwDxf@(NDm`=Pwf&sy7QRhHk@Cl z<-h7|Zj+#2he8$SNDDj)!5Fvl4dkK6l8O#p5o7;;^H{xyBzJTT-^q19IAO6$%SK)s z@FzRu&v}?{rkTY34(k0DS%}>lCismw;2nND)RL&eqCD~7L{uH)xW=k6^<3vK`8#5f3O=ig(lP5Q&P!Xc{hxxR&;HTrU91Q7C9D^49IWlHn?;!} zmsTN1awg{XBgrJb@Fz0BCUE;!L~YeQvyga4YR>+;BCpq)A>dD9SR3sA={I2~x-y7j z8n4wJ6ERHwmFnBIx0I-61k{ELWQwo6RN7THnkAPMI?34cYSgT-b3W zOY&H@wWx>uGzUknHvSurPv=lUWzWms!3g2wOHZC&H@zZj#zj)+A-24>)muv*@QWBH zJM!nozB%>b`%XYZuvhBO!kCt&v|5%Skg!mtvp{-mh&!&FfG~P;db8qOv_N#nQv5=y zp8A{z&sA{i0OVDcjbDrI2zBBjnf&cWV_!gm>(ulc3iFFn-0aXED`ml7Fz*+of0eP2 zqbsP>sf7sub#0TwS1Ah0PHQ@gYBWUI$4l>?J#fGQBETKk0lL_+J|E#nRAYWy0ES6G zKua4c;6sw7cEuWn0*TVq zr!~}Y$SG+oDB#8CRXc;B;?!CppAyJ6b362R1rpMKv%xgp)VVOo>hBoihXma1vBrG)dEB(z9?`*SbrsuG|yx3I`9aButTlaz(T z*8`vT(d7~X>?n$0+nQnFw3_*1TnUqz=+>jrpS6)fyPZ$_5Fu3dyQ~)8%r{g>p-f=+ z@gYZ>;~4KX^-R5?kh@L&c$=v0pDiC}zBAh5GVfjEPFOE_A{u>c&7L0}xDv%K**ZBo zawIP4ctq2QtX@xiFsJ~XIjv`^A&anlCEdlk>V1UVo9yt_?~!^W+ljtCvZ=I6O~aP! z+yOD!d|5HTO5w3bWv(F4FIC@u3@+*+H{_-^Z5Yq2c*^hsY}Z+08vhWkcz`sRPKj=VSGC#`QvrtyrKGp?K?He?He~@f1 zhk6TSV^q2#H9)^jqp!X@r&DR_X8Li30ifqKMGRKK&7besoJ^`HQ`t7Bo>12%8);V( zJQD85_n8Cnr5FZjF*$5iZNDRfnuDcOpPs&-C#F)8@jnuAzeQNbeb`O`C8V~&P_l)- zTG%*Ks2S(seN7ffJf=p)FRB-Pa6Co|K%%0lhprf+#W02ylYY9^Xpzz6Kh4%Y0(9F4 zw)tzV??Ha|R(oB|SLX*4*~JOhCsLz2b<{y|wky{02COSg@=*DqpaOmpZ(TQ6kd&TfiHY$$)LAD5`+Cv+Kp#4UOTrC;UcR7qcszLLAbp*sQO-( zPFPCkc4bsi7GXJ+n~dJF6(YadpfmpV#3AyiEId>(MX^9nGf?fjU>#jRWTppsHpGKB zV8PPr_0rOP%wr?#3t|KwnO;*%g>ny z9jW(nz*Lskut~QYOK!dtJ~E|CvGy61HG4+93TyEg;>>2JCw5c51xE(+IyWNb;k!usxdXIZYsQ zU)BW>49WC@LSAS~>|%1_F4`{rZ2~~xrA`h8eK2#+DS)BHWpm0ve0c|DlX1305z%?X z5g#&57y4Vk*d370REk|24DmV2xVJ^_{lk6J*Sa>BxQ6W4! zDkX!WuR%v7izKhf(!X?M=|FZ@flMsK9RQSLX-HjIiWO!s)HMc)`vWr(*utDpg5RkB z2U;@x3KMgpXF!2n~oMh|VE;n?)sB+9g(!|<}Ip2aXhF;ef2QQ#DN z=VdFq)pcY1ynKx~TV}DFaK^8h2daLLjL~piD)F-Al5JR!r3A04R`Dym-=jE~ADiKe z*4k<}{Ks^0W`zS30jy;45B+BWHGsYfd-QNVAm`o|EAeS5ark)4i2#e(1)^LumDcn; zu|%1R1&1O7mWC`q0-iD|9Y8UA{U@8fI=b5F_2UQLHcDXuQk#yhm_1d$$&qaYYu#n~ zE`V7$=m7ipS;cLnct_2Gm`5s*-4*sZBu8~Q-u#~m+jT-4r>qQoIs8yO!n4lpjVpBb zMSwU03{B1aipl^B80txM$0B1lO;g^p9bL=3|5^Il5C`+lOG{lIY_jCWnaDq^ch0jY z05k2+2Z5b;hP33~)v1nn#D?}@P6)6l{q{FkWNdv_jV&d`p(Xqp5>>ruZAH>aJ1}HY z^O(>^_Kt*4NPCObR*wKkFJ!joz~H4Hf=B5HL>^@+s7p>JxS`wTjdVRl?cTJYZ2jAC zKyELstzjprLoL?(jW=GnlvVNh2NkarQ;|T>x9)J~3ab__+jf zbLfJY7q%HG3J!uxody297x0U8*zs{t-JMfbi#mu}<0Al<{)1gfa)?QIE7ja!lE?tF za&Xp(3K4?1oYH+h^w}GBjZ;?B8;`J^RNekbL1UWbv9-|<5BGQmV#Fr#+>yAz>-Hu? z8<;vYcFElnBIP3Me!EEp!cTC?ceuUpg)BC4XBaVjTX=M@t+|C7!n0e^|Np5|0Lsm! z&>c1$@OPBmpa|$}UU#kUPvkrNyOhhgEn$aSb)03?s3_zQ*nh3!2|m+|J1Ph5UBsN9 z`@+mZliGLwm0Ny2RlIf4tL64)4IXTjW&EKi9UxF*8?bAAOo-KmiY6I1BtuuX;XVCv z^o%^S@L_2R-nUFdc@bU6-PbtHhOTOB{rS9N{-2oUAWmO2pRFj@j6_81l#k_Iv*HSeN=aopgH$XEL+5_LQ;TpGuD}tefjf4%#N@oz`yqg++Akts%-NlMmk> z-Oicha^g>9#dKy4<2va`kNQY+wEzuVyxUn&Lo6KS1@;cNeo*^rAo0GC+ZuXdV9iCPy2roN)I>G@T=v2X36pa)_4ubJSqg7uyESj}U}wW= zJ+wBJ`D?g%?)De(sBA$jXDz`?ZNmWY?I41b_&A;RMT>Y?oLmUw*7k-BaT$6NJb(wC z`$jFusF8MrCxd+&Kf$!|Mk}!-U?YG{$)f zXM2JVx%IK)nBfj9f+NODJPL9A(Viw*y~n2~Q2lIUqXaI8Meeg7Fs)4yDUd6))-8YM zYw-!ElG8wduTPfQX#*AKh~%J8IklUNjfWN5@o=UonZSHF;uNJA$m)ZWMBQxOC^>RH z{w^ZR-)k7cucNW5MF7TZQBt@$-#doPP0l{Cf<@U}w% z+sOgG88wFP+Rd*|n9O(?Jgyzx5jlM+D~uiW{kd_ z&@z_p*Qz;Us#D<+LE-Sp*V~wm=97(izD7k_l`dAQyk7!d1mnJ?ODd}stkN1RK$T{b zaZBgEgimPO*6HdLz;{~?m@qtL#E#?EFy%m3w%6|)$EUAQ*g*gBM`)4mN@Jad42eXMwrMbJ+R*?*snLBCFZ_TZ{fT z8yX=-_ahs?REy7}F19&NHt83S1PNZo+53t>aFvNOBuh3v8;llP#&Gx12beuySx2Ox z?+NY|ysg_BPPbXUE=qY1UHv9fAt2P&_VU7w4vNTs?nEOf!3gfh9<(DT{jW9>eQnw@!dd{?ddD^xrD(2J zd6^)QhSuM;i@pY=QVr08c}FZIz|$o)?w?OE>)GbZCKhdl?Lo>;X4HJ1G!>F0e;7yp zKA-$!x`-M0vOe-jsin;@>jzUKoXz7t$6}^sX)50*&O^rm!x%6`x~KEbsqlC|Knq?F z<$(qnU~=^HGLPjnmJYQD5}vBKs7!;aRlw4u*YUOSojRxDm10fnL_!k|GzhE+Gl|-Lg0M`&vTcgq)YVCPw z0li#&M&LU`{s|4AvJCrykI+ImUB~H=S%=2*7K5OmL-IK%%yfiK>Gd?9`SaA<(pC?c zq{To3KHyBRn!f6b=br?P695hLr+qz3lG zY4px}vU=~x7H|MEWBpfb%S?miP4L>siZIm1c>#!7=iFg+dU!RfM#8hr3bTU2K~KFl zzVyY*s8^3&a;g zBHGB&v`0EWvI9=sS3HG^Bc};P(A>69y4`{=_zw9jps5HD#2!R|LwF_hF&HTsN|Kdu z-@EzVgVkB14x}HMMmaFxZ8XUWTb_X}VSvvG2!RP0q#x;Ds$BD03cMfKCq)piPcE-< z-~Y#iM*jOw{prP6&Q(wvkGo-~oC;PLskH3weaMP=LvttbR}_%DEdAjE<_+jLIw<_} zn#Z^YBKmvgn7CU@yinr6umM&CSL6lbULNOu(PGbpt`v-WChqnjw;|--{h}A01GBDB zr=mem*7uLi3OPN!?mP>)63_@3Qwfjtr@jHm0!+zzA7%YQ{uQ7YFcP}Idt)>iP%9K% zyx{U}Ff9xu{6jy(;a!5>0BrL#$m>jWq3j7Prx--!)lg=+FX%%&3|vBNPxOH4&t>%+ zEM$Xuk@j0J271wUgCjyjhk+_Q6BAK|u8u|+{`MC>r%C!gp$UxYDLA8k)Xg94oA^Ht zc{~;Trh-}Tth;bz6*y+zA2uU|VF6)6w?zY#q!iIl7Qn;S83TgQz~pS04_UC#T|qm+&s z+NqT^(hYp0_Wn5j8S)v3D5vFZ-GxGtun?IQ`nnDN!!Q{l<-C;16q-x07E`D6mfYgC z&=~W{niy_q=`?){GA;A~6y{&@XIUM+R&QZ=0)DbIfCeuh5&Z&w=%j}qK$&A-F}9#l z*4dT7r&O;J6Tw0c3_o7-N3IZ!pdWxAGGOyC*M6yKT|J9ou7nhe0f}mwk5~~99}My`R+Amu_uZi-Oz@{5uq`0@HsS{B|7QFJ zyffniwC~^X!7u^ZF$bL&OAEs~2Y1)TlWcI!kIi92o{MHcn>8O<+_&Y^zn7|5_&rh_ zmDFyX`{#l|qd{8GlfU`J-lcYvkVw?adr0=7Vc;0j(CMb9O!K{_ z_u6x(hY1v_V36sT&1>_;u!^<1)2CJsM(RZ~+HqQ0grq%Y1iArl{KoiNJ2QcrfMKuh zRJp1NWUVK`{2i~0tA3v)A3LECKm~O6=cNF5a;?CZ93wd0kekJi3*xS=hrw9gPF#t-t5? z{oqBzN;dN$iPCp!oA`t>XyMb;vueFJ9>b56eFfT|@Ea7$-V8&&__a;@9nkk4TBI-gx~$B`I45jzkYx87Z~?*KlgLp z*URVnT-W{df185>XTCD$6#&4@k3agy767I>0^sQIk`w%wdH-~o4#2;}AOGY1ZOO#W zmhDG6>Q63}%{g@VTTfcsoq4z4@TMML`-j7U8-=~0hyMC82fRfUVqeRutjd}lk~cuI zb=dmStt2>;GRY#hX4=|p79k1%Mt-V~bFWF~27if9oCR+Pl-GTn;RnO5Z8iJ=^o%Ox zqxaos{^)WLWiyg&`^sz<4L0~9x!T>Jr@GogE4i{h0hNn9g0A~!6{E?iG-qx~WKayT zT6dIEQq7qMf2k1TXE{>m-o3c2|01sAeyx>gsp~5bs1Bp#b-@)e@P6~1vhtNp-})HB z-0nAsBdQB6A2o{0a>U8Ib)JzPnji1@!)cPQ-P$Qyrux>``zB}8UU%$qgI9^lYr%b^ zDVqBTKC8&xpE`SUb9+uXL9kS_Td+hfDXY&;IK{kKP)u&ALq=SoJzBlv#N4iyM?KN} z%6e0K6Ms!~d6-}yxu3cQxy8XuH>ZNU!#?o4P!Jc}AX0OR2TW2q~vJCA3EFHD6(r0=Ss%P+aX%?%w~%ll0ZpSKvGhu5y`#N+Eg(=SXUNImwbpK{i{3 zZ}Y-mp>u3^>pC=iFVlmv{R72N+AUY16mQne#8C>RP2WV)*AN@H)#!xA<*kXXuNr*Mww=Z!8xyxnuI&8g$ZVBO$mas*)Vl z6%s5X*h;Pyni1(dglfcg_dc}t|KMK{(yE;zVY&@|g{s`wz_1MnZKkjNFyFt6F2%F* z6kdj=K;toCV|pmjyDjOYS=akrooyA{7LyQ5p|kz#R85?HRN|=_a4Io;aP=RDMN+vf zhemQ0Hs9wYTeilyhDMu==lWPl^^%@Hi z`L5%`hm~pBwXGS612~U<{W`vt5Fs8g_6f}7Zn-?$K(TEi4DfAheV0*E2Exi$LCfB|enhEJN1!>Sr)}~T znyhGie^TQf6@FkJw1N28-1W}CQO@9k)tMz^czvxkLmY7)CyC(5z6(tJB!xpLS>v55 z?Ba4)Xma<;ymF0A>4lw=4oTfU6kw#9`*v>NaMxfBu9%&R2LD$7SyI$bHCkZ*%4EgZt2*yw3SIZxp{_gW79B1J}rh zQ#Gw~H}IHHGz89@MIo5T^yD?Y5wZy8X8)J_%jx#kQDAv*Ka699~s&ukau$zhz;h<>f-k5v^id^e z(%4LlJD&hoB2wR9z0muwq&;>>P7Q zi$P2A*(g||$=)lY_O!|fn3M*@+z~sB`g&_md%#jk7}vNP)9;17V3J89s`qRQPU&iD z2@!PGTIwyr+q@hjAx(dSU+ITUJl44k@VQv_6(vum~H9wRR&iW*HY^h9*@nVaBOerM_#|3j&UtzBy_ zYvCQY&2mgwoIUqKJz20U72g&@-zK3|pATg){vvQmU8tRtce+A5qPT zkP)E1gUT;wcSlEvGqvYiRz?M@VbCG>g&Q--e08LM8-adSxTf?h9$pezayz^TC)sxq zM#JuopnbD9tE2e@lDp!9Nv*aJb<43^b(~wjhGP?5O;j|A4fVy?xuUIDQ?ac>+(DiX zP0I$IANb?vV^Fre83(Hv={74|Iq)_;R5&MJw;7Y7V!8fkVWgU$sa>3Y%67q`P@j{> zw>Kr3j@Z(o)ID0XAv}uMf6h7!&4qjQptB8{M4{V%xRqA#Y+YBQ&QN#6s!(Y=BiUD6-ESDh;0GwbDAK2u8NXtV?! zMPh92|6+`5ERGb&NOap zB3sxE|7Dwn)k#t+D^HS~m5xulEw9Vea-hZS&2v|+gF<|jIm^-i2jgFu=8Qmd((S~; z%^eigpyOj6ORP(_C0#Jee#aZaf;r5B^tU;t+@Wz|o0U_Eqo4KX#nw^XodAQB&|k$M zkSKIyEo5yN+ZY+_&KkT6MpaP2+S^bM{ce5c?@T(7s*wJ7K(|QO@x#byw1~{qZQ^DoNI!iGbu`!|`g9k0)!2`He^Okvku+|su`0cR{ zkZXw&t+#Sz0<-S3^dC#D%SuQmgm1qqksWP}H#ap163iF`Sfq?;5yq&5|{^-?P3A=_MBgbQvI{z6lj z{n4?mM!q%=1~Ms;)N@I{qJuLVC{HB3K0|lZbSE!OwOT1H%W;$5BC?IUS3#5Arc=R* z&qNe0BQ^g)-J@>e5Y&NtB@oT6G@dNr&5^?uHc_C;M@EPxk4m+!j)1l$_IKrcz5jaN zRtQ$h2^^Wcu2d^1_Z7<5Lwt}Ye(Juj@m&TVlP&Edm~jGcZ;go>XwIX?%?FG^)zVo* zJN3s-kXC-|;>Qkvv8#RfA0cqB@pgc^(2}V@06RS>He+xF=!4KFh$OX}=2dKLrI1w! zjQ^vqVb+ZtEOzLxUYqKy(`UI@{Lr8D4Y8lWc_po3zHlPO+`5^sbi0~)$v6b^E5A+n z+JP2eUCGR@f)kbf&~h5Ej$ywe8{=mmjYIBV#)6&*!Tq5rU#;HtUfee92bEl5g)r_) zU&jn^+S9G&0Hubd;!*q12U>PS`R#g-ybZgBxW3K3Tu+$+dUI&;Gm?ue^}g&gRSY60 zSC5--V5D}-u+S5^6lO4~KJ!sgRw|FlJ7#I?B=`obJ0r>7N{&SCDYqgF!{dcx%n zAm_4nA!67ZyP1+N-K3^q1eSCDiCs9qU&B++`fF>Opk>@*=kD!6frx+lf8-buMz204 zy%h%ofasY1s;e?ckJc5#aH-z}Y+-{dMuj%@UfQ0;R7nqw|}P+TjK@(Amk|B%-CG{`8JFfsF@ z|6&dQ_j;4;oRolJ8$0hyfBn=2#HX82zarYJNB0(=Jc#1Y)GS{xER-JKGsXPhrL^i5 z9j-;igJE+Ph~H!uy(}`flTBf3TpWA8g%2eD&ma)9qLjc%`&6Cy4O31(ORVqNY8 z8a>_KL6zlLBCzZ#7e}17ycGd6?UcMQE`-R?VB}PCKgF1VVi?5m(MpzJQlJ9FhjjF8RmHvF?qQ*NheP#`z6y(!?~N^wZyajgeX->4yr{0HxNt%joYyb-M_9H<<_Galz?JkIt>n zAZMsEw1IULGHnT--UZ?#&B__zilx!VUR8`Qrgb|qw$sT4p@!9`FE5&ZZP2?(ldJuU zZeP%>xtRV&k(3HUe-?DAx@;FJxN%&u2vOVL=GK+r-C@w9s`1En^7|I(_*nwJ6gdp=FOeS#0;a}nONtru0MXB|Yfd`y=Y4C$oh5RKtj zS+x@+9apRwv2W!|%_vdHTaMQWR`h|e!j!_k*Ys%uy%oZ=J@}MUpnHY+FVhMRI)Vp( z7p6CA?@Oj7IOaXfxLFV~@KH)v2D!pZGZ)cfR+lS$rH!HxZPbWgJAj zD8`lA8Sg@mVmY#ot==hW-CQjooX;K#4$C#|7EF*}O|N!yc}!!QGR8)8?ZneU!NUvOK&J@qg< z!puhoY(@R8X@RT02WTD$h06kf%u6P#K`LpvH zDjV5-q5!`!HI6OBAse&{lzm0}+<~#aRwv+5eVmtjpW`=Zml`0tp>JcA?{{BtpM%-z zX51aaCwAPIihcTlvRMDa%ZN^ypiX(iKy)O_WfO>BW)>vjpc0v3x&~9tu*r3@WuyP+ zK*X)pRS!j-4P&B?E4SYKExRHJl&o;0L)KuylM*CAJWGK{4T9O8N6~xE&H%)07c3CX zoaGojw=u+T7`AjKeiBBx#)e2nh7JlF%#!Crf{50O01hp7ak z)EM|KsMzXn0brKn0(HGCQ2^a&W3AkaM`UxekV`GmT!U|C_?I#paw*Pb;QR%I!at1%G5|R-Ax==9RHtj5B*J9x?NX#1_G0k~nAD4FFKGJst@Vx@Hv0dB zxIKlyjzDd>Ok0iu{>y~95_tm;<<& zPo)p}zj>Z+^B}`iqq96ske)hSbBVPN^VrnZI8m z3>&fkGB@a36giSt>6=Q8v>f^W_})n{sr=JMY(QLa>?C8fzch8f3J57k#uNUZdyVx@)^o7`T<8^ z`KLc+2;zu5YdzxuLs&yTJe3w!E)l+j0<9}pm?2E_^%zXUZLj0bSBC3foePww^mp2d zb71{~?-j&@jJ51yQL&^ZB{HHviC9=q=HMjE;=|f*}pE~(yL3q-BA$N@Ve`bR2AamzlTtgK@@Iyu_e~bH2n)B|-t!&*x;>J7SV#)2q zyPb2dfsE0qpOLrt)p?SUywXrJWX89fqV|o2F^^=-^O@9(mkC_sBW+|s8bC@x{+_JB zp%|=Or5C#b<>zLLuOns){HO84&e=m+b_5{ zockpmBUJ(b4cUF7Ay3pp2sg!V17)*5MK#8d3r=t&qXdxfQd}TMbe!tqLYAj2-=d!m zdN0$6zDUlI;7DlBi1i|M+((#uO6l*%ntp-&NZ@TlpKsE8>Dwem)Pto$8VXc;upmB1 z& z9EZzh(`K!k1lvqT<%sUI=l&g5167GPruK+W3?4^9Kn>$mUQhLijPt;5mF(liz2R68 zN{4(|OQ`>hQ_nz%@i_C6mqC1>+4;*Tm{zEq_?N@(?yi*Gf_y$&9(!%EfKP_iyJMmGm6jr#obyx1OVa zLnCo3gm_I_Qz&s+_guYr1}Mw5JkVH}Pm9cDLfQ^kty&>$D=(SIh)sZq6(9O92SAgp zXtSJE@7xem<$Ea59D*^xEG#W(1%;EmhJ|oekj=?0-%{e(9cw-c5hb3UX~>76JoQ6Z zmpX$RN(KuHP8du+M%g*lt#d`8r0wZ%IL=$*<^>prI^ACStr47kEK$#|FP6hfC9HE$ z1iS%0$`6kalU}{p%?BtS=&$^xq!Z>Uar-9- zGCd`Iur3s_mHvM~bhkapt!0Q1Lyr<-q}puKdW5c;21zF@G2Qt&ka%YaBOn0EWj8cl;x)|3fmoD2NCbnS*TO6`u^>gZw%7VkecZ)*urMY zYpfv!xW^(4GH;j;@Fx^88#JqT#4T?la&8YNg&FGG+SCg9mL3@_dPaEn2fQnKO@H7O z(D*kYkCE~WhxnTQ>Z{<$amD+hy@S$=j^T}iHdKn_b|5A!97ac~(W}uPVwi(y(EE+W z-{FV>k={`kGU$HWjB%tXpcYzhAk6Gj2*m@5wVp7wUWC=5o~ndCnY83-$_0AEf*to) zpT@hly%!FvgC41MeHjJvLuf^KA&z1!mhp=48_^hG7QG}|)2{Iq9kN3MH`g892uc=V zB`Dl`Q*(*uYT@X{#aJGyRW-tc)%cnTQ8Egw8F8r9{MI{G0%jNEB&~ZIWCzi6mD}x8 zz~59=OMxbfr2slTBElAu{}&G3^UY@G?iC2da1Y6r$0vKkIm!WDjVH`i7h^L~m0bwq zH31#=9-;Y6SEBWX*~(@+(cu}|!s$wbp69A;9>m!9$5OI54ENYTp$|tkSeTe`I?Lrp zP=TjEaEN%{Fr^M|SmB04`2^6d4iQ&JjCew_4EH4zSeIhSD8&9ww@k?))*Sro@puH& zl$Frprz_L+=(m-12&QoyEYFXlxJFYHxb_;y?q4h!)5}f_(hZZvWJvAqMkQ~jS39)S zPR$oGQfp^~0j=Kfx>61mYbvfrS*hRoHscqIWm@l=CjoXyW34&0fJ(Wo-*fxc1#SuU z(1CvO!R=syO0!hNU1wRn;|#NdgH-h^bMlOYw85ezC+RX|yJkc8{l|1=2tC%pa=|We zc%VakkO28u-W!ZMSQ>ySKjT%=XY?G0zd#+l`tnnCFvWazrif?hoOW6_sS19d>rw&y z#F8M;XP*Yb{Qm1zB2Tg8BVau{6-~&1tKPvN<+4ry%ciMSFh0%P{c87jdfsac)A|wl z1te09M2{{32O2O!;zCi(_!on8g7qDk*JhUvmMn4m7DW%G`#Z#EAcT#Qr+VVtqR6$%fD$pPGvlc^E74u zL^&dL&^6I~oFp2PN=53PG@SoJN6S~1m2kKnsd6{Q%y5o>+nh2(RBlIQrE;0uNtjGv zO~9x0nU$3Ty4r(LkPeq%Kx3DEm6ZpJym!VsrLY|0V-dz$l!{apyek^^2dkDk`!B*a zgR;v|p)EINDB~`>G_}-UC4FJq)=CC|Jr?ae4${KEdne}0YATbGBkwkKh2oeG`)RzIbE6GWiF~9>WL{W4Y>ryr^x%BUZ&xo^3LKQCCnMV=09BJU zI9c-?*qy+FCF~WNY@5!iBal4PcSl%-{X*RrSBesL&0y;jUtuT`M+y`f>PK1up(!;S zfn5;RIX<4KPM%d5+H5;z%xOJm5a;@0 zQ__80}%tq3lFqLH+=TIrOCOZ=^3ki5?)XKbbqM4Y9qaVWk!JG{NF9X(6yQ z^t_*+@7wzwlybi_*dd*iIQ@onba;$s;^(!J#o)KWy2oV3*s?uVt@ON{`i?j4C$Yp0 zd>%G+tuwyj@#4VFToxAG_!TJ{`;09bM{?fe!j^-u!D159bHI?)H5BYD&ZtO7?!~Yi zvCvQqygdfG*gNstpS|=}MC=Ro>-C1|fcR`$t9yQV9y%lYm`MUH&FNTbo#d||xyZiI zp{p6Q%)>2P={b;J8ca)c+4;UzpSh;kppC}PDQ8*cp%_U>&w%IOfHkWBbi?R{Iy7K} zPEr#%NIE_`2R$zlYxZ>jx}HfnS;X2$p4%rO&0LNGf#DN2XVNkOw5rji{@u68vJx=R zd#0jk5#uJ$c3-SxAtt;G;<)Ls-u7f9pGcL7d{t46t8oFX*k(+2eo*5I?5jZe>C;PB z%7O%RsaL|P>pq4W`7d|3mfvqL(DPv8(r#CPYJ>{mzDr?|&U=>_=1r25hzgh%TI)U= zP_*Ad7B751b{%}?JowBhNI|>RedvOiU8Fdh=}KU^Ra;Z;Dj6)ON`LMX))ocArKf-0 z@?}C$Kju|HeuUWCzlLqCJUgUE=hnhM=Jvq%BC%(;e+L?N5yGB_ky7g}WBQ5UOsLp$ zmI-?K3u8-=qxej?5LtzK^+H2o*_x>S=*5aeT_5ZU?f)FQhy3c%_KjJWDv!$tvH92rOM{smE*uR5sliD`S26NJDof@?R^oJrBhd0_Y2C5JgiA=fLB(T1Q6x||eo};k_~}qLn!ZgO zo7l++52O7N-~nrRNW;&J-eP240ajjrlMTDCRI{a&#qtu)F{IQh%)|}$B#g+9nZLPl zk?up_!<)L+EJ}h-HL|#8Zbw_E?XY3cJ$$22Gbg$LGIW-kj|hHf*{oK3!S17{L(ASH z$m$Bj?>74B`LWQh|6#7Ql+R%Bpj|KbKFx3VV{A$FJg4~(I{hCQE-Nw65NM2WLoRo% zuB+)M!??1z&ZL6ipx`cy!l0Wt#svjr>BOv5;By5D_WyDm4)%{P)cZSv&-h~+RUQ+S zy-UW-k?YIK#cDwOnvFKMmm22`7aw{*DX}!&%WWUEzqwnF`ajjF}*Gc!I&reOA_9urXHcyeqPOfCp_jM3rn#oC)yqsT(m0woYo;({o_yh7JLT6NOe%IG) zNBwVw1OS}Jm<||XN;E_f8+TNxE!o6(qmLZPG}E=)tPdDO)P^L^IkMBwd=C zrB^18xraJiSj!3nz0(_PF4HJel+TsCF)qDfW%>y$4>ozW_T{67yJ{xsHL4sNaO*Fw zN)JLQlh6J=TEpUt|e~#R4EpQNV2Y2ZCOlX(*7mM$7 z25FFgslZP@X^kgr-TG7U7QHNeElQa?X%9%1XYFYG_AallWo-hzk@lnF(ZIPXQBQsy z(8KJ(zN31bkaq%`54y!qGKjn0E5a$-1UVN=b6Dp!zufUYbRcmok}CHTvpxjMn@?vU z#+lngRhV_21IVxt((VoMe620axu&Em%cE;_^m^mSb&k)}6|2%ONNKAoJA0sMy zp6#L~#Sy5Vnl>Uxb;R8)vPPN>PN3dSwEEcBCVXfS>oqgM_qU!GvHn-MGQAZre4m6v zlMox@AmrogMwrS;+fo$A#sIeeO%JseqkzFa&TX8zz5yH5!=Ml+0@g#Llj=9VHRUD^ z({kfN3rU@ngAImh?BEtX*+ezofa-^buC%M23i798jPLQagOt;=D!TEj#`wi=&5e4^ z%OE-PN$4=U;k?w8%stb|ft~2(u|0?Xg#geId!1=GrINf3x)+S>(1hYze9&a~LGp>O z3C+dh>XfZbXd^sag?rp)?vptNAym=GX2Ud4aUE$`9fuIUHOK17FM(tsf)Ko%ra}m# zt*rOn9uvCZ2A#lu2t@tQ%=SiXD5Wyr;_3iepMub0(bpyln*t3o3x8b3G#)uwY`+P>^)ZeKmJAmHG=QN^*o;xbZ zG|-)9rx8UxzR&X;5ibpAhRI^VL?b%e|EbiA zXHV9aJB~StvNY?NP@X!w*v|3h*IU{ym<1vx*(A#?{el^wM1yQn=aifDxYeBg@u(j$ zM_Tu!WI6)5VQS)}ApL_@IS}zDc4SS~);m59X#>Tnb3hrig$*$ut{YPWJ|8Nf?6xfr zVh=Y-H!c~2>xKv^lYO4@4hr=!s0`H>?5HKRuY{Mxd8 z74fi}E-7~m+G$rT(uD(Ms~weqd7fkBTqAr(7YYyG+f$etn+0teVC#v~0X^`^5=&NM zf^|Vr>l{99j`5O~-I4jPaVq`zy6YoId$OWE!b*&CVSRX&;1$?c`6q&q(Oqwy2I#jY zKn17tf3~uKC=O|!odJ(4{HaVRvmFCDimA<~)6_oj68LE%%AT5b>7>9+n?N;`fSQ^M*7c?r!WVnxHy0x?T$NxC7D?3d|3Tw zKL!NzEZ6o2`@&kb6=?x?TLp&cg6_8=0s0IIQ1r{dMn3}pyr(t^(BEhN_dlfef$89< zXgg3Dxjkx@^=~>)$Tr&%N01g=*^~6l#IOO&duvC+_@vniJP)L{D<;6He)B_GdSH)y z9MS+_3y1G2cuK!KZ1PDR6wLRd&9#I47VHr3WloH*leIs=$sbLLc!mc})$RnJGUMsa z7)}ypSMdi9yfN4e0p}CIqb?(->?fy*;V8{h;X9m*Wk**h95z&Xo8oW}lz6Ef6&R>D z4bOCX@P0qp$M(RHYX=YSz&4QPs?irsgpWCOGN2krYaTqw4iu9|cqk*2eV7=k&gr75 z3h!`H$e=0D<-i>urNJhiU<9k}E3iXx<_w;MfJcyFA$jDDCzJDwaB=>F-3XH&0fUxN zJkc^HbhN>4`cykg=kQ)a&XaB{?rSaDjvPw`-6?~IEhj@(&@#vqq3(pk2S_Bag8_r9 z=#810@PbCRW3-q)Sy}-n`ocao3(=gx^P_g-H{6E0f6(RLU=#&GO4izWqhlGZSe1c$4=lVNh)#>e9|L%(0foOSjNP5LwNXy zX$PAFB_2mv;51$k2|u`R#S~Q$@e)ixN%N0t}Cza+%gIqo_*mGtM4GMeeoC!nj)LL za(?^rS&ktoBBJ-llpTcumMigFR*;NkhG9T3^{dP@2=wUh!1notZok$qEDtKf>Zs) z%R%gvxTU;;J%%?^Vz-gg_Xm2;ZRk|{qQ$?~AzN%hM>stq`mle|;!}E(I^NXe;i3wu zrL28*ejQmUvKVf*366@7tBMr)-jMN%*9?#A%JWN??b#p5WnF-^=G631xKvSFoE?*- znDaJN?uZind)-zQzK%>D*i>oeA>P9`T+9fQ$@Z+#HSEqo$5j4J!r!OU(E^$!#krbK zqe7V`Qxx(J5rD3W+@SlPOBdMmY;t-jaW8YvVLdIN5x=jgfft*~sX#U_>RT}*@id1V zM!C(^cj{#B7tjJrv1Fy{3$F0EM&OH=!a+a%u8$N2F-={_&pcYrnZ*6?Da5>>yuqh; zXXc=~UX9j88W;5o^1_65cqZn8JMRi!AH9@!x567OZeFK>v+QVBVFw+@3nh4=1TQ?n z3r_%n-ivSm9pj5|@B$OOP=Xi0@P#LM5e{C2gBO_KMRxE47{163UStQ8kVTq721OaQ z5+PEa=L2#y*Y_g91>2Yo$zb>r`_G>D!_ULfP$pZ6n$;56?#Bo~3j z#|Gb$2j6A9>v=y6qd0NMMq;bIkeQz2L%qi}ejY-8b+=i_$h9pQk5N_G$c$@U+-w$x#Z+hu{ZKbQ z=hnHzxeaXC z-p#%Dopau~zjJ%euP1kGFG^viGXVf8#jgtA0N|-40Olsop9g0OzVwd)kgh2f7QCqx zkGc!?uW(fsiazZq@A&!nGuENIzp|(o-cLJr<9fxGtm`#ZOPWje7Mwk^xR{|zSGS5# za!f)IE1{y{aCRnqfRx&}6cQ_w$RPo>0FKoMQtND1av}weCvOSoz{iOt&;#)7Yb+=a zaYRsp+8Gvh6&{wMgOpvfEvQu+y ztbfD#o%pz?6X6qbtC`n27l3-=8YeLF`Te2ZhH4KHG~kHXf;$>pvk~o5#Ukm1MeSK` zXl>v@&w6E_FiM;m?-)&}q*~Ulz$b1t>iH4ZPPi{L9z+7}DVr*mlEV0DdFIe?#H1!j z6vx%Kr5JV3zoPp_b$Eo!S4b5A*cv72Ng1kx=*^%tGO;SCP`x+6n{qNsAQgLq{*grh zG>5j<+G_CRi$%vTNsnJvm9-lXf!XWwB2kmSB})*M8mUuI^T?erGU)Ah!E-AgJy$qq zSA6bhNy7JUIJT|s4d-18O_dK!uNNIU*DU{+7z^i!Z*~7+7b_bRVY2X+$}?l?xP=$pGw*ek&U6WRI7azE^=}GwFy1BWmId zj$0|*p%YXQBfaW8Q=U0rLM@n9#TC&W@<)LNo_6AGHZB++4tji-OM7nS#r3WE3XS$* zIrutDKi61eT!`2Scj7RcgoklwhDC9U(6{OQ4kn;6I$lqTh%Jf z&k-y$d>w5`pf&R(L#+u`TjQsE&7)0VX*Dyj_DP=$jdM2jT Date: Fri, 22 Aug 2025 14:08:07 +1200 Subject: [PATCH 28/38] Delete Files & Directories --- README.md | 1 + filesystem/test/bob/asd | 1 - kernel/include/common/map.h | 63 +++++++--- kernel/include/common/vector.h | 20 ++-- kernel/include/filesystem/ext2.h | 12 +- kernel/include/filesystem/vfs.h | 22 +++- kernel/src/filesystem/ext2.cpp | 194 ++++++++++++++++++++++++++++-- kernel/src/filesystem/vfs.cpp | 195 +++++++++++++++++++++++-------- kernel/src/kernel.cpp | 10 +- 9 files changed, 423 insertions(+), 95 deletions(-) delete mode 100644 filesystem/test/bob/asd diff --git a/README.md b/README.md index c0afee93..02d72fdc 100644 --- a/README.md +++ b/README.md @@ -228,6 +228,7 @@ No user usage so far (userland will be added in the future) - [ ] App Framework & System Apps - [ ] Auto Updater & Image Builder (ISO Release) - [ ] Store +- [ ] Security of some sort - [ ] User Switching - [ ] Real Hardware Support - [ ] Port NeoVim, Wakatime & Some hot reloader diff --git a/filesystem/test/bob/asd b/filesystem/test/bob/asd deleted file mode 100644 index 62aa824d..00000000 --- a/filesystem/test/bob/asd +++ /dev/null @@ -1 +0,0 @@ -cant read? \ No newline at end of file diff --git a/kernel/include/common/map.h b/kernel/include/common/map.h index 85d569d0..efe0aae1 100644 --- a/kernel/include/common/map.h +++ b/kernel/include/common/map.h @@ -42,29 +42,39 @@ namespace MaxOS{ Vector> m_elements; public: - typedef typename Vector >::iterator iterator; + typedef typename Vector>::iterator iterator; Map(); ~Map(); - Value& operator[](Key); + Value& operator[](Key); + + bool empty(); + int size(); + iterator begin(); iterator end(); iterator find(Key); - bool empty(); - int size(); - - void clear(); - void insert(Key, Value); - void erase(Key); + iterator push_back(Key, Value); + Pair pop_back(); + + iterator push_front(Key, Value); + Pair pop_front(); + + void insert(Key, Value); + + void erase(Key); + void erase(iterator position); + void clear(); void iterate(MapIterationHandler* handler); void iterate(void callback(Key&, Value&)); }; - /// ______________ TEMPLATE IMPLEMENTATION ______________ + + /// ______________ TEMPLATE IMPLEMENTATION ______________ template MapIterationHandler::MapIterationHandler() = default; template MapIterationHandler::~MapIterationHandler() = default; @@ -139,21 +149,33 @@ namespace MaxOS{ */ template typename Map::iterator Map::find(Key element) { - // Loop through the elements - for (iterator it = begin(); it != end(); it++) { - - // If the key of the current element is equal to the key we are looking for - if (it -> first == element) { - // Return the iterator + // Search for the element + for (iterator it = begin(); it != end(); it++) + if (it -> first == element) return it; - } - } - // If it is not found, return the end iterator + // Item not found return end(); } + template Map::iterator Map::push_back(Key key, Value value) { + return m_elements.push_back(Pair(key, value)); + } + + template Pair Map::pop_back() { + return m_elements.pop_back(); + } + + template Map::iterator Map::push_front(Key key, Value value) { + return m_elements.push_front({key, value}); + } + + template Pair Map::pop_front() { + return m_elements.pop_front(); + } + + /** * @brief Returns whether the map is empty * @@ -228,6 +250,11 @@ namespace MaxOS{ } + template void Map::erase(Map::iterator position) { + m_elements.erase(position); + + } + /** * @brief Iterates through the map and calls the handler * diff --git a/kernel/include/common/vector.h b/kernel/include/common/vector.h index 67f79bca..edb48f60 100644 --- a/kernel/include/common/vector.h +++ b/kernel/include/common/vector.h @@ -55,17 +55,18 @@ namespace MaxOS{ Vector& operator=(const Vector& other); Vector& operator=(Vector&& other); - [[nodiscard]] bool empty() const; [[nodiscard]] uint32_t size() const; + [[nodiscard]] bool empty() const; + [[nodiscard]] uint32_t size() const; iterator begin() const; iterator end() const; iterator find(Type) const; iterator push_back(Type); - void pop_back(); + Type pop_back(); iterator push_front(Type); - void pop_front(); + Type pop_front(); void erase(Type); void erase(iterator position); @@ -257,10 +258,10 @@ namespace MaxOS{ } /** - * @brief Returns the m_first_memory_chunk element of the Vector + * @brief Returns the first element of the Vector * * @tparam Type Type of the Vector - * @return The m_first_memory_chunk element of the Vector + * @return The first element of the Vector */ template typename Vector::iterator Vector::begin() const{ return &m_elements[0]; @@ -327,11 +328,13 @@ namespace MaxOS{ * @brief Removes the last element from the Vector * @tparam Type Type of the Vector */ - template void Vector::pop_back() { + template Type Vector::pop_back() { // Remove the last element from the Vector if (m_size > 0) --m_size; + + return m_elements[m_size]; } /** @@ -364,18 +367,21 @@ namespace MaxOS{ * * @tparam Type Type of the Vector */ - template void Vector::pop_front() { + template Type Vector::pop_front() { // Make sure the Vector is not empty if (m_size == 0) return; + Type element = m_elements[0]; + // Move all elements one index to the left for (uint32_t i = 0; i < m_size - 1; ++i) m_elements[i] = m_elements[i + 1]; // Decrease the size of the Vector --m_size; + return element; } /** diff --git a/kernel/include/filesystem/ext2.h b/kernel/include/filesystem/ext2.h index 8ae22364..2eaa8ac7 100644 --- a/kernel/include/filesystem/ext2.h +++ b/kernel/include/filesystem/ext2.h @@ -227,7 +227,10 @@ namespace MaxOS { private: common::Vector allocate_group_blocks(uint32_t block_group, uint32_t amount); - void write_back_block_groups(); + void free_group_blocks(uint32_t block_group, uint32_t amount, uint32_t start); + + + void write_back_block_groups() const; void write_back_superblock(); public: @@ -255,7 +258,9 @@ namespace MaxOS { void write_block(uint32_t block_num, common::buffer_t* buffer); void write_inode(uint32_t inode_num, inode_t* inode); + [[nodiscard]] uint32_t create_inode(bool is_directory); + void free_inode(uint32_t inode); void read_block(uint32_t block_num, common::buffer_t* buffer) const; [[nodiscard]] inode_t read_inode(uint32_t inode_num) const; @@ -264,6 +269,9 @@ namespace MaxOS { [[nodiscard]] common::Vector allocate_blocks(uint32_t amount); [[nodiscard]] uint32_t bytes_to_blocks(size_t bytes) const; + void free_blocks(const common::Vector& blocks); + + // TODO: free blocks }; @@ -294,6 +302,8 @@ namespace MaxOS { void save(); + void free(); + }; /** diff --git a/kernel/include/filesystem/vfs.h b/kernel/include/filesystem/vfs.h index 9c5e48c8..6edcb46c 100644 --- a/kernel/include/filesystem/vfs.h +++ b/kernel/include/filesystem/vfs.h @@ -6,6 +6,7 @@ #define MAXOS_FILESYSTEM_VFS_H #include +#include #include namespace MaxOS{ @@ -36,14 +37,23 @@ namespace MaxOS{ string get_relative_path(FileSystem* filesystem, string path); Directory* root_directory(); - Directory* open_directory(string path); - Directory* create_directory(string path); + Directory* open_directory(const string& path); + + Directory* create_directory(string path); + static Directory* create_directory(Directory* parent, const string& name); + void delete_directory(string path); + static void delete_directory(Directory* parent, const string& name); + static void delete_directory(Directory* parent, Directory* directory); + + File* create_file(const string& path); + static File* create_file(Directory* parent, const string& name); + + File* open_file(const string& path, size_t offset = 0); + static File* open_file(Directory* parent, const string& name, size_t offset = 0); - File* create_file(string path); - File* open_file(string path); - File* open_file(string path, size_t offset); - void delete_file(string path); + void delete_file(const string& path); + static void delete_file(Directory* parent, const string& name); }; } } diff --git a/kernel/src/filesystem/ext2.cpp b/kernel/src/filesystem/ext2.cpp index a8ba2fe9..d6f194d1 100644 --- a/kernel/src/filesystem/ext2.cpp +++ b/kernel/src/filesystem/ext2.cpp @@ -262,10 +262,50 @@ common::Vector Ext2Volume::allocate_group_blocks(uint32_t block_group, return result; } +/** + * @brief Free a certain amount of blocks in a block group and zero's them out. + * + * @param block_group The block group where the blocks exist + * @param amount The amount of blocks to free + */ +void Ext2Volume::free_group_blocks(uint32_t block_group, uint32_t amount, uint32_t start) { + + + // Read bitmap + block_group_descriptor_t* descriptor = block_groups[block_group]; + buffer_t bitmap (block_size); + read_block(descriptor -> block_usage_bitmap, &bitmap); + + // Convert start to be index based on the group instead of global + start -= (block_group * superblock.blocks_per_group + superblock.starting_block); + + // Free the blocks + for (uint32_t i = start; i < amount; ++i) { + + // Block is already free (shouldn't happen) + if((bitmap.raw()[i / 8] & (1u << (i % 8))) == 0) + continue; + + // Mark as free + descriptor -> free_blocks++; + superblock.unallocated_blocks++; + bitmap.raw()[i / 8] &= ~(1u << (i % 8)); + + // TODO: Decide whether to zero out or not, my implementation zeros on allocation but idk about others + } + + // Save the changed metadata + write_block(descriptor -> block_usage_bitmap, &bitmap); + write_back_block_groups(); + write_back_superblock(); + +} + + /** * @brief Save any changes of the block groups to the disk */ -void Ext2Volume::write_back_block_groups() { +void Ext2Volume::write_back_block_groups() const { // Locate the block groups uint32_t bgdt_blocks = (block_group_descriptor_table_size + block_size - 1) / block_size; @@ -310,7 +350,7 @@ uint32_t Ext2Volume::bytes_to_blocks(size_t bytes) const { } /** - * @brief Creates a new inode + * @brief Allocates a new inode and sets the base metadata. Also allocates block 0 of the inode * * @param is_directory is the inode to be used for a directory * @return The new inode @@ -373,6 +413,86 @@ uint32_t Ext2Volume::create_inode(bool is_directory) { } +/** + * @brief Mark an inode as free. Note: does NOT unallocated the inodes blocks, use free_group_blocks(). + * @see free_group_blocks + * + * @param inode The inode number to mark as free + */ +void Ext2Volume::free_inode(uint32_t inode) { + + + // Find the block group containing the inode + uint32_t bg_index = (inode - 1) / superblock.inodes_per_group; + block_group_descriptor_t* block_group = block_groups[bg_index]; + + // Read bitmap + buffer_t bitmap(block_size); + read_block(block_group -> block_inode_bitmap, &bitmap); + + // First group contains reserved inodes + uint32_t inode_index = (inode - 1) % superblock.inodes_per_group; + if (bg_index == 0 && superblock.first_inode > 1) + return; + + // Mark as used + block_group -> free_inodes++; + superblock.unallocated_inodes++; + bitmap.raw()[inode_index / 8] &= (uint8_t) ~(1u << (inode_index % 8)); + + + // Save the changed metadata + write_block(block_group -> block_inode_bitmap, &bitmap); + write_back_block_groups(); + write_back_superblock(); +} + + +/** + * @brief Frees a group of blocks. Preferably with adjacent blocks apearing next to each other but not enforced. + * @param blocks The blocks to free + */ +void Ext2Volume::free_blocks(const common::Vector& blocks) { + + // No blocks to free + if(blocks.empty()) + return; + + uint32_t start = blocks[0]; + uint32_t previous = start; + uint32_t amount = 1; + + // Free each adjacent set of blocks + for(auto& block : blocks ){ + + // First is already accounted for + if (block == start) + continue; + + // Is this block adjacent + if((previous + 1) == block){ + previous = block; + amount += 1; + continue; + } + + // Adjacent set has ended + uint32_t group = (start - superblock.starting_block) / superblock.blocks_per_group; + free_group_blocks(group, amount, start); + + // Reset + start = block; + previous = start; + amount = 1; + } + + // Account for the last set of blocks in the loop + uint32_t group = (start - superblock.starting_block) / superblock.blocks_per_group; + free_group_blocks(group, amount, start); + +} + + InodeHandler::InodeHandler(Ext2Volume* volume, uint32_t inode_index) : m_volume(volume), inode_number(inode_index), @@ -560,11 +680,29 @@ size_t InodeHandler::grow(size_t amount, bool flush) { } +/** + * @brief Writes the inode meta data to disk + */ void InodeHandler::save() { m_volume -> write_inode(inode_number, &inode); } +/** + * @brief Marks this inode's blocks as free and then the inode as free + */ +void InodeHandler::free() { + + m_volume -> ext2_lock.lock(); + + // Free the inode + m_volume -> free_blocks(block_cache); + m_volume -> free_inode(inode_number); + + m_volume -> ext2_lock.unlock(); + +} + InodeHandler::~InodeHandler() = default; Ext2File::Ext2File(Ext2Volume *volume, uint32_t inode, string const &name) @@ -853,12 +991,6 @@ directory_entry_t Ext2Directory::create_entry(const string& name, uint32_t inode m_entry_names.push_back(name); write_entries(); - // subdirectories' ".." hard links to this entry - if(is_directory && !inode){ - m_inode.inode.hard_links++; - m_inode.save(); - } - return entry; } @@ -887,7 +1019,28 @@ File *Ext2Directory::create_file(string const &name) { * @param name The name of the file to delete */ void Ext2Directory::remove_file(string const &name) { - Directory::remove_file(name); + + // Find the entry + uint32_t index = 0; + directory_entry_t* entry = nullptr; + for (; index < m_entries.size(); ++index) + if(m_entry_names[index] == name){ + entry = &m_entries[index]; + break; + } + + // No entry found + if(!entry || entry -> type != (uint8_t)EntryType::FILE) + return; + + // Clear the inode + InodeHandler inode(m_volume, entry -> inode); + inode.free(); + + // Remove the reference from this directory + m_entries.erase(entry); + m_entry_names.erase(m_entry_names.begin() + index); + write_entries(); } /** @@ -920,7 +1073,28 @@ Directory *Ext2Directory::create_subdirectory(string const &name) { * @param name The name of the entry to remove */ void Ext2Directory::remove_subdirectory(string const &name) { - Directory::remove_subdirectory(name); + + // Find the entry + uint32_t index = 0; + directory_entry_t* entry = nullptr; + for (; index < m_entries.size(); ++index) + if(m_entry_names[index] == name){ + entry = &m_entries[index]; + break; + } + + // No entry found + if(!entry || entry -> type != (uint8_t)EntryType::DIRECTORY) + return; + + // Clear the inode + InodeHandler inode(m_volume, entry -> inode); + inode.free(); + + // Remove the reference from this directory + m_entries.erase(entry); + m_entry_names.erase(m_entry_names.begin() + index); + write_entries(); } Ext2Directory::~Ext2Directory() = default; diff --git a/kernel/src/filesystem/vfs.cpp b/kernel/src/filesystem/vfs.cpp index 27a672bb..c39713c4 100644 --- a/kernel/src/filesystem/vfs.cpp +++ b/kernel/src/filesystem/vfs.cpp @@ -6,6 +6,7 @@ using namespace MaxOS; using namespace MaxOS::filesystem; +using namespace MaxOS::common; VirtualFileSystem::VirtualFileSystem() { @@ -256,7 +257,7 @@ Directory* VirtualFileSystem::root_directory() * @param path The path to the directory * @return The directory object or null if it could not be opened */ -Directory* VirtualFileSystem::open_directory(string path) +Directory* VirtualFileSystem::open_directory(const string& path) { // Ensure a valid path is given if (!Path::vaild(path)) @@ -281,7 +282,7 @@ Directory* VirtualFileSystem::open_directory(string path) } /** - * @brief Try to create a directory on the virtual file system & read its contents + * @brief Attempts to open the parent directory and creates the sub directory at the end of the path * * @param path The path to the directory * @return The directory object or null if it could not be opened @@ -301,19 +302,34 @@ Directory* VirtualFileSystem::create_directory(string path) // Open the parent directory Directory* parent_directory = open_directory(path); - if (!parent_directory) - return nullptr; + if(!parent_directory) + return nullptr; - // Create the directory - string directory_name = Path::file_name(path); - Directory* directory = parent_directory -> create_subdirectory(directory_name); - directory -> read_from_disk(); + string directory_name = Path::file_name(path); + return create_directory(parent_directory, directory_name); + +} + +/** + * @brief Creates a subdirectory in the specified directory and + * + * @param parent Where to create the directory + * @param name The name of the new directory + * @return The created directory + */ +Directory *VirtualFileSystem::create_directory(Directory *parent, string const &name) { + + // Create the directory + Directory* directory = parent -> create_subdirectory(name); + directory -> read_from_disk(); + + return directory; - return directory; } + /** - * @brief Delete a directory on the virtual file system + * @brief Attempts to open the parent directory and deletes the sub directory at the end of the path * * @param path The path to the directory */ @@ -326,23 +342,87 @@ void VirtualFileSystem::delete_directory(string path) path = path.strip('/'); // Open the directory - Directory* directory = open_directory(path); - if (!directory) - return; + Directory* parent_directory = open_directory(path); + if (!parent_directory) + return; + + // Delete the directory + string directory_name = Path::file_name(path); + delete_directory(parent_directory, directory_name); + +} + +/** + * @brief Delete a directory on the virtual file system and it's sub contents + * + * @param parent The directory that contains the reference to the directory being deleted + * @param name The name of the directory to delete + */ +void VirtualFileSystem::delete_directory(Directory* parent, const string& name) { + + // Find the directory and delete it + for(const auto& directory : parent -> subdirectories()) + if(directory -> name() == name) + delete_directory(parent, directory); + +} + +/** + * @brief Delete a directory on the virtual file system and it's sub contents + * + * @param parent The directory that contains the reference to the directory being deleted + * @param directory The the directory to delete + */ +void VirtualFileSystem::delete_directory(Directory *parent, Directory *directory) { + + // Nothing to delete + if(!directory) + return; - // Delete the directory - string directory_name = Path::file_name(path); - directory -> remove_subdirectory(directory_name); + // Store a reference to each subdirectory and its parent + Map stack; + Vector> to_delete; + stack.push_back(parent, directory); + + while (!stack.empty()){ + + // Save the current + auto current = stack.pop_back(); + auto current_directory = current.second; + current_directory->read_from_disk(); + to_delete.push_back({current.first, current_directory}); + + // Empty the directory + for(const auto& file : current_directory->files()) + delete_file(current_directory, file->name()); + + // Process the subdirectories + for(const auto& subdir : current_directory->subdirectories()) + if(subdir -> name() != "." && subdir -> name() != "..") + stack.push_back(current_directory, subdir); + + } + + // Delete the directory from the bottom of the tree + for (int i = to_delete.size() - 1; i >= 0; --i) { + + // Get the parent and child + const auto& current = to_delete[i]; + Directory* owner = current.first; + Directory* subdirectory = current.second; + + owner->remove_subdirectory(subdirectory->name()); + } } /** - * @brief Try to create a file on the virtual file system + * @brief Attempts to open the parent directory and create the file at the end of the path * * @param path The path to the file (including the extension) * @return The file object or null if it could not be created */ -File* VirtualFileSystem::create_file(string path) +File* VirtualFileSystem::create_file(const string& path) { // Ensure a valid path is given if (!Path::vaild(path)) @@ -355,21 +435,17 @@ File* VirtualFileSystem::create_file(string path) // Create the file string file_name = Path::file_name(path); - return directory -> create_file(file_name); + return create_file(directory, file_name); } /** - * @brief Try to open a file on the virtual file system + * @brief Create a file in a directory * - * @param path The path to the file (including the extension) - * @return The file object or null if it could not be opened + * @param parent The directory where the file should be created + * @param name The name of the file to create */ -File* VirtualFileSystem::open_file(string path) -{ - - // Open the file at the start - return open_file(path, 0); - +File* VirtualFileSystem::create_file(Directory *parent, string const &name) { + return parent -> create_file(name); } @@ -377,10 +453,10 @@ File* VirtualFileSystem::open_file(string path) * @brief Try to open a file on the virtual file system with a given offset * * @param path The path to the file (including the extension) - * @param offset The offset to seek to - * @return + * @param offset The offset to seek to (default = 0) + * @return The file or null pointer if not found */ -File* VirtualFileSystem::open_file(string path, size_t offset) +File* VirtualFileSystem::open_file(const string& path, size_t offset) { // Ensure a valid path is given if (!Path::vaild(path)) @@ -389,26 +465,39 @@ File* VirtualFileSystem::open_file(string path, size_t offset) // Open the directory Directory* directory = open_directory(path); if (!directory) - return nullptr; + return nullptr; - // Open the file - File* opened_file = directory -> open_file(Path::file_name(path)); - if (!opened_file) - return nullptr; + return open_file(directory, Path::file_name(path), offset); - // Seek to the offset - opened_file -> seek(SeekType::SET, offset); +} - // File opened successfully - return opened_file; +/** + * @brief Opens a file in a directory with the given offset + * + * @param parent The directory containing the file + * @param name The name of the file to open + * @param offset How far in the file to open (default = 0) + * @return The file or null pointer if not found + */ +File *VirtualFileSystem::open_file(Directory *parent, string const &name, size_t offset) { + + // Open the file + File* opened_file = parent -> open_file(name); + if (!opened_file) + return nullptr; + + // Seek to the offset + opened_file -> seek(SeekType::SET, offset); + + return opened_file; } /** - * @brief Delete a file on the virtual file system + * @brief Opens a directory on the vfs and deletes the file at the end of the path * * @param path The path to the file (including the extension) */ -void VirtualFileSystem::delete_file(string path) +void VirtualFileSystem::delete_file(const string& path) { // Ensure a valid path is given if (!Path::vaild(path)) @@ -417,9 +506,23 @@ void VirtualFileSystem::delete_file(string path) // Open the directory Directory* directory = open_directory(path); if (!directory) - return; + return; + + // Delete the file + string file_name = Path::file_name(path); + delete_file(directory, file_name); + +} + +/** + * @brief Delete a file in the given directory + * + * @param parent The directory containing the file + * @param name The name of the file + */ +void VirtualFileSystem::delete_file(Directory *parent, string const &name) { + + // Delete the file + parent -> remove_file(name); - // Delete the file - string file_name = Path::file_name(path); - directory -> remove_file(file_name); } diff --git a/kernel/src/kernel.cpp b/kernel/src/kernel.cpp index b27898a7..cc40472f 100644 --- a/kernel/src/kernel.cpp +++ b/kernel/src/kernel.cpp @@ -72,10 +72,7 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic driver_manager.activate_drivers(); // FS Tests (TOOD: Cant read contents of maxos created entries) - File* file = vfs.open_file("/test/working.file"); - buffer_t fb(100); - file->read(&fb, 100); - Logger::DEBUG() << "FILE:" << (char*)fb.raw() << "\n"; + vfs.delete_directory("/test/bob/"); Logger::HEADER() << "Stage {4}: System Finalisation\n"; Scheduler scheduler(multiboot); @@ -94,14 +91,15 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic // - [x] Read files // - [x] Write files // - [x] Create subdirectories -// - [ ] Delete subdirectories +// - [X] Delete subdirectories (and test deletes sub contents) // - [ ] Rename directory // - [ ] Rename file // - [x] Create files -// - [ ] Delete files +// - [X] Delete files // - [ ] Stress test the filesystem: 1000s of files in a directory, long nested directories, long path files, large file r/w +// - Fix multiple def where could be a parameter (idk why I needed two functions for that) // - Fix tabs (mac mess up) // - Userspace Files (syscalls, proper path handling, working directories, file handles) // - Class & Struct docstrings From 5596c0315a38720a980fc5750aa7337b35278108 Mon Sep 17 00:00:00 2001 From: Max Tyson <98maxt98@gmail.com> Date: Fri, 22 Aug 2025 20:24:39 +1200 Subject: [PATCH 29/38] Rename Directories & Files --- kernel/include/filesystem/ext2.h | 5 ++ kernel/src/filesystem/ext2.cpp | 121 ++++++++++++++++++++----------- kernel/src/kernel.cpp | 10 ++- 3 files changed, 88 insertions(+), 48 deletions(-) diff --git a/kernel/include/filesystem/ext2.h b/kernel/include/filesystem/ext2.h index 2eaa8ac7..5ac522cc 100644 --- a/kernel/include/filesystem/ext2.h +++ b/kernel/include/filesystem/ext2.h @@ -342,6 +342,9 @@ namespace MaxOS { void parse_block(common::buffer_t* buffer); + void remove_entry(const string& name, bool is_directory, bool clear = true); + void rename_entry(const string& old_name, const string& new_name, bool is_directory); + public: Ext2Directory(Ext2Volume* volume, uint32_t inode, const string& name); ~Ext2Directory() final; @@ -350,9 +353,11 @@ namespace MaxOS { File* create_file(const string& name) final; void remove_file(const string& name) final; + void rename_file(const string& old_name, const string& new_name) final; Directory* create_subdirectory(const string& name) final; void remove_subdirectory(const string& name) final; + void rename_subdirectory(const string& old_name, const string& new_name) final; }; /** diff --git a/kernel/src/filesystem/ext2.cpp b/kernel/src/filesystem/ext2.cpp index d6f194d1..678f3a45 100644 --- a/kernel/src/filesystem/ext2.cpp +++ b/kernel/src/filesystem/ext2.cpp @@ -878,6 +878,60 @@ void Ext2Directory::parse_block(buffer_t * buffer) { } +/** + * @brief Removes an entry reference from the directory + * + * @param name The name of the entry to remove + * @param is_directory Is the entry expected to be a directory (otherwise assumed to be a file) + * @param clear Should the inode be freed and the data blocks unallocated + */ +void Ext2Directory::remove_entry(string const &name, bool is_directory, bool clear) { + + // Find the entry + uint32_t index = 0; + directory_entry_t* entry = nullptr; + for (; index < m_entries.size(); ++index) + if(m_entry_names[index] == name){ + entry = &m_entries[index]; + break; + } + + // No entry found + if(!entry || entry -> type != (uint8_t)(is_directory ? EntryType::DIRECTORY : EntryType::FILE)) + return; + + // Clear the inode + if(clear){ + InodeHandler inode(m_volume, entry -> inode); + inode.free(); + } + + // Remove the reference from this directory + m_entries.erase(entry); + m_entry_names.erase(m_entry_names.begin() + index); + write_entries(); + +} + +/** + * @brief Rename a directory entry and save it to disk + * + * @param old_name The current name of the entry + * @param new_name The name to change it to + * @param is_directory Search for entries with the type DIRECTORY (otherwise assume FILE) + */ +void Ext2Directory::rename_entry(string const &old_name, string const &new_name, bool is_directory){ + + // Change the name + for (int i = 0; i < m_entry_names.size(); ++i) + if(m_entry_names[i] == old_name && m_entries[i].type == (uint8_t)(is_directory ? EntryType::DIRECTORY : EntryType::FILE)) + m_entry_names[i] = new_name; + + // Save the change + write_entries(); + +} + /** * @brief Read the directory from the inode on the disk */ @@ -918,7 +972,7 @@ void Ext2Directory::write_entries() { // Calculate the size needed to store the entries and the null entry size_t size_required = sizeof(directory_entry_t); for (int i = 0; i < m_entries.size(); ++i) { - size_t size = sizeof(directory_entry_t) + m_entries[i].name_length + 1; + size_t size = sizeof(directory_entry_t) + m_entry_names[i].length() + 1; size += (size % 4) ? 4 - size % 4 : 0; size_required += size; } @@ -948,6 +1002,7 @@ void Ext2Directory::write_entries() { char* name = m_entry_names[i].c_str(); // Update the size + entry.name_length = m_entry_names[i].length(); entry.size = sizeof(directory_entry_t) + entry.name_length + 1; entry.size += (entry.size % 4) ? 4 - (entry.size % 4) : 0; @@ -1014,33 +1069,22 @@ File *Ext2Directory::create_file(string const &name) { } /** - * @brief Delete a file from the subdirectory + * @brief Delete a file from this directory * * @param name The name of the file to delete */ void Ext2Directory::remove_file(string const &name) { + remove_entry(name, false); +} - // Find the entry - uint32_t index = 0; - directory_entry_t* entry = nullptr; - for (; index < m_entries.size(); ++index) - if(m_entry_names[index] == name){ - entry = &m_entries[index]; - break; - } - - // No entry found - if(!entry || entry -> type != (uint8_t)EntryType::FILE) - return; - - // Clear the inode - InodeHandler inode(m_volume, entry -> inode); - inode.free(); - - // Remove the reference from this directory - m_entries.erase(entry); - m_entry_names.erase(m_entry_names.begin() + index); - write_entries(); +/** + * @brief Renames the file from the old name to the new name if it exists + * + * @param old_name The current name of the file that is to be changed + * @param new_name What the new name should be + */ +void Ext2Directory::rename_file(string const &old_name, string const &new_name) { + rename_entry(old_name, new_name, false); } /** @@ -1073,28 +1117,17 @@ Directory *Ext2Directory::create_subdirectory(string const &name) { * @param name The name of the entry to remove */ void Ext2Directory::remove_subdirectory(string const &name) { + remove_entry(name, true); +} - // Find the entry - uint32_t index = 0; - directory_entry_t* entry = nullptr; - for (; index < m_entries.size(); ++index) - if(m_entry_names[index] == name){ - entry = &m_entries[index]; - break; - } - - // No entry found - if(!entry || entry -> type != (uint8_t)EntryType::DIRECTORY) - return; - - // Clear the inode - InodeHandler inode(m_volume, entry -> inode); - inode.free(); - - // Remove the reference from this directory - m_entries.erase(entry); - m_entry_names.erase(m_entry_names.begin() + index); - write_entries(); +/** + * @brief Renames the directory from the old name to the new name if it exists + * + * @param old_name The current name of the directory that is to be changed + * @param new_name What the new name should be + */ +void Ext2Directory::rename_subdirectory(string const &old_name, string const &new_name) { + rename_entry(old_name, new_name, true); } Ext2Directory::~Ext2Directory() = default; diff --git a/kernel/src/kernel.cpp b/kernel/src/kernel.cpp index cc40472f..9aa66131 100644 --- a/kernel/src/kernel.cpp +++ b/kernel/src/kernel.cpp @@ -71,8 +71,9 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic driver_manager.initialise_drivers(); driver_manager.activate_drivers(); - // FS Tests (TOOD: Cant read contents of maxos created entries) - vfs.delete_directory("/test/bob/"); + // FS Tests + Directory* dir = vfs.open_directory("/test/bob/"); + dir->rename_subdirectory("super","idol"); Logger::HEADER() << "Stage {4}: System Finalisation\n"; Scheduler scheduler(multiboot); @@ -92,10 +93,11 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic // - [x] Write files // - [x] Create subdirectories // - [X] Delete subdirectories (and test deletes sub contents) -// - [ ] Rename directory -// - [ ] Rename file +// - [x] Rename directory +// - [x] Rename file // - [x] Create files // - [X] Delete files +// - [ ] Cant read contents of maxos created entries // - [ ] Stress test the filesystem: 1000s of files in a directory, long nested directories, long path files, large file r/w From 8fb19b8b5113385d4710644f4fa2e92e4da83f10 Mon Sep 17 00:00:00 2001 From: Max Tyson <98maxt98@gmail.com> Date: Mon, 25 Aug 2025 20:26:28 +1200 Subject: [PATCH 30/38] Fix Crashing --- README.md | 1 + kernel/src/kernel.cpp | 4 ---- kernel/src/system/syscalls.cpp | 13 +++---------- programs/Example/src/main.cpp | 2 +- toolchain/fix_scripts_github.sh | 1 + 5 files changed, 6 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 02d72fdc..d1b9edd4 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,7 @@ This is the list of required packages to build the operating system from source. * libisl-dev * cmake * telnet +* rsync Linux: ```sh diff --git a/kernel/src/kernel.cpp b/kernel/src/kernel.cpp index 9aa66131..d81bbe8b 100644 --- a/kernel/src/kernel.cpp +++ b/kernel/src/kernel.cpp @@ -71,10 +71,6 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic driver_manager.initialise_drivers(); driver_manager.activate_drivers(); - // FS Tests - Directory* dir = vfs.open_directory("/test/bob/"); - dir->rename_subdirectory("super","idol"); - Logger::HEADER() << "Stage {4}: System Finalisation\n"; Scheduler scheduler(multiboot); SyscallManager syscalls; diff --git a/kernel/src/system/syscalls.cpp b/kernel/src/system/syscalls.cpp index 5b02841d..418a9d4d 100644 --- a/kernel/src/system/syscalls.cpp +++ b/kernel/src/system/syscalls.cpp @@ -19,7 +19,7 @@ SyscallManager::SyscallManager() // Clear the args Logger::INFO() << "Setting up Syscalls \n"; - m_current_args = {}; + m_current_args = new syscall_args_t; // Register the handlers set_syscall_handler(SyscallType::CLOSE_PROCESS, syscall_close_process); @@ -36,12 +36,7 @@ SyscallManager::SyscallManager() } -SyscallManager::~SyscallManager() -{ - // Delete the args - delete m_current_args; - -} +SyscallManager::~SyscallManager() = default; /** * @brief Loads the args from the registers and delegates the syscall to the relevant handler if defined @@ -118,10 +113,8 @@ system::syscall_args_t* SyscallManager::syscall_close_process(system::syscall_ar uint64_t pid = args -> arg0; int exit_code = (int)args -> arg1; - // Get the process if it is 0 then it is the current process - Process* process = pid == 0 ? Scheduler::current_process() : Scheduler::get_process(pid); - // Close the process + Process* process = pid == 0 ? Scheduler::current_process() : Scheduler::get_process(pid); Scheduler::system_scheduler() -> remove_process(process); // Schedule the next process diff --git a/programs/Example/src/main.cpp b/programs/Example/src/main.cpp index d19bca07..0bcfba8f 100644 --- a/programs/Example/src/main.cpp +++ b/programs/Example/src/main.cpp @@ -52,7 +52,7 @@ typedef struct SharedMessageQueue{ ipc_message_t* messages; } ipc_message_queue_t; -void* make_message_queue(char* name) +void* make_message_queue(const char* name) { void* result = nullptr; asm volatile("int $0x80" : "=a" (result) : "a" (0x06), "b" (name)); diff --git a/toolchain/fix_scripts_github.sh b/toolchain/fix_scripts_github.sh index 90df4e3b..4ac90b64 100755 --- a/toolchain/fix_scripts_github.sh +++ b/toolchain/fix_scripts_github.sh @@ -1 +1,2 @@ dos2unix * +dos2unix pre_process/* From 81925e24b5453887cd2fc6d4cca8590eaf92bcf3 Mon Sep 17 00:00:00 2001 From: Max Tyson <98maxt98@gmail.com> Date: Tue, 26 Aug 2025 14:43:09 +1200 Subject: [PATCH 31/38] Fix MaxOS Entry Issues --- kernel/src/common/logger.cpp | 2 +- kernel/src/filesystem/ext2.cpp | 23 ++++++++++++----------- kernel/src/kernel.cpp | 22 +++++++--------------- 3 files changed, 20 insertions(+), 27 deletions(-) diff --git a/kernel/src/common/logger.cpp b/kernel/src/common/logger.cpp index 9e096b43..57248d57 100644 --- a/kernel/src/common/logger.cpp +++ b/kernel/src/common/logger.cpp @@ -17,7 +17,7 @@ Logger::Logger() s_active_logger = this; // The following line is generated automatically by the MaxOS build system. - s_progress_total = 23; + s_progress_total = 24; } diff --git a/kernel/src/filesystem/ext2.cpp b/kernel/src/filesystem/ext2.cpp index 678f3a45..25e899c8 100644 --- a/kernel/src/filesystem/ext2.cpp +++ b/kernel/src/filesystem/ext2.cpp @@ -280,7 +280,7 @@ void Ext2Volume::free_group_blocks(uint32_t block_group, uint32_t amount, uint32 start -= (block_group * superblock.blocks_per_group + superblock.starting_block); // Free the blocks - for (uint32_t i = start; i < amount; ++i) { + for (uint32_t i = start; i < start + amount; ++i) { // Block is already free (shouldn't happen) if((bitmap.raw()[i / 8] & (1u << (i % 8))) == 0) @@ -432,10 +432,10 @@ void Ext2Volume::free_inode(uint32_t inode) { // First group contains reserved inodes uint32_t inode_index = (inode - 1) % superblock.inodes_per_group; - if (bg_index == 0 && superblock.first_inode > 1) - return; + if(bg_index == 0 && (inode_index < (superblock.first_inode - 1))) + return; - // Mark as used + // Mark as used block_group -> free_inodes++; superblock.unallocated_inodes++; bitmap.raw()[inode_index / 8] &= (uint8_t) ~(1u << (inode_index % 8)); @@ -725,7 +725,7 @@ Ext2File::Ext2File(Ext2Volume *volume, uint32_t inode, string const &name) void Ext2File::write(buffer_t const *data, size_t amount) { // Nothing to write - if(m_size == 0) + if(amount == 0) return; // Prepare for writing @@ -808,7 +808,7 @@ void Ext2File::read(buffer_t* data, size_t amount) { size_t readable = (amount - read < block_size - buffer_start) ? (amount - read) : (block_size - buffer_start); // Read the block - buffer.copy_from(data, readable, buffer_start, read); + buffer.copy_to(data, readable, buffer_start, read); read += readable; } @@ -976,7 +976,11 @@ void Ext2Directory::write_entries() { size += (size % 4) ? 4 - size % 4 : 0; size_required += size; } - size_required = m_volume -> bytes_to_blocks(size_required); + + // Expand the directory + size_t blocks_required = m_volume -> bytes_to_blocks(size_required); + if(blocks_required > m_inode.block_cache.size()) + m_inode.grow((blocks_required - m_inode.block_cache.size()) * m_volume -> block_size, false); // Prepare for writing m_volume -> ext2_lock.lock(); @@ -984,11 +988,8 @@ void Ext2Directory::write_entries() { buffer_t buffer(block_size, false); buffer.clear(); - // Expand the directory - if(size_required > m_inode.block_cache.size()) - m_inode.grow((size_required - m_inode.block_cache.size()) * m_volume -> block_size, false); - // Save the updated metadata + m_inode.set_size(blocks_required * block_size); m_inode.inode.last_modification_time = time_to_epoch(Clock::active_clock() -> get_time()); m_inode.save(); diff --git a/kernel/src/kernel.cpp b/kernel/src/kernel.cpp index d81bbe8b..f35f54cf 100644 --- a/kernel/src/kernel.cpp +++ b/kernel/src/kernel.cpp @@ -71,6 +71,13 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic driver_manager.initialise_drivers(); driver_manager.activate_drivers(); + auto dir = vfs.create_directory("/test/bob"); + auto file = vfs.create_file("/test/bob/file.txt"); + if(!dir || !file) + Logger::ERROR() << "Failed to create test directory or file\n"; + else + Logger::INFO() << "Created test directory and file\n"; + Logger::HEADER() << "Stage {4}: System Finalisation\n"; Scheduler scheduler(multiboot); SyscallManager syscalls; @@ -82,21 +89,6 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic asm("nop"); } -// TODO: -// - EXT2 Tests: -// - [x] Read subdirectories contents -// - [x] Read files -// - [x] Write files -// - [x] Create subdirectories -// - [X] Delete subdirectories (and test deletes sub contents) -// - [x] Rename directory -// - [x] Rename file -// - [x] Create files -// - [X] Delete files -// - [ ] Cant read contents of maxos created entries -// - [ ] Stress test the filesystem: 1000s of files in a directory, long nested directories, long path files, large file r/w - - // - Fix multiple def where could be a parameter (idk why I needed two functions for that) // - Fix tabs (mac mess up) // - Userspace Files (syscalls, proper path handling, working directories, file handles) From f5d2792c0ca638fcfad656f5bd1672776c4355a0 Mon Sep 17 00:00:00 2001 From: Max Tyson <98maxt98@gmail.com> Date: Tue, 26 Aug 2025 17:57:13 +1200 Subject: [PATCH 32/38] Fix Style Issues --- docs/Styles/.clang-format | 8 + kernel/include/filesystem/vfs.h | 6 +- kernel/include/hardwarecommunication/pci.h | 4 +- kernel/include/memory/physical.h | 2 +- kernel/include/processes/elf.h | 8 +- kernel/include/processes/ipc.h | 4 +- kernel/src/common/colour.cpp | 384 +++-- kernel/src/common/graphicsContext.cpp | 770 +++++----- kernel/src/common/logger.cpp | 262 ++-- kernel/src/common/outputStream.cpp | 108 +- kernel/src/common/spinlock.cpp | 23 +- kernel/src/common/string.cpp | 665 ++++----- kernel/src/drivers/clock/clock.cpp | 270 ++-- kernel/src/drivers/console/console.cpp | 10 - kernel/src/drivers/console/serial.cpp | 64 +- kernel/src/drivers/console/textmode.cpp | 96 +- kernel/src/drivers/console/vesaboot.cpp | 456 +++--- kernel/src/drivers/disk/ata.cpp | 281 ++-- kernel/src/drivers/disk/disk.cpp | 30 +- kernel/src/drivers/disk/ide.cpp | 102 +- kernel/src/drivers/driver.cpp | 137 +- kernel/src/drivers/peripherals/keyboard.cpp | 1083 +++++++------- kernel/src/drivers/peripherals/mouse.cpp | 127 +- kernel/src/drivers/video/vesa.cpp | 69 +- kernel/src/drivers/video/vga.cpp | 200 ++- kernel/src/drivers/video/video.cpp | 1 - kernel/src/filesystem/ext2.cpp | 362 +++-- kernel/src/filesystem/fat32.cpp | 1311 ++++++++--------- kernel/src/filesystem/filesystem.cpp | 381 +++-- kernel/src/filesystem/partition/msdos.cpp | 72 +- kernel/src/filesystem/vfs.cpp | 461 +++--- kernel/src/gui/desktop.cpp | 251 ++-- kernel/src/gui/font.cpp | 133 +- kernel/src/gui/widget.cpp | 365 +++-- kernel/src/gui/widgets/button.cpp | 146 +- kernel/src/gui/widgets/inputbox.cpp | 334 +++-- kernel/src/gui/widgets/text.cpp | 48 +- kernel/src/gui/window.cpp | 246 ++-- kernel/src/hardwarecommunication/acpi.cpp | 161 +- kernel/src/hardwarecommunication/apic.cpp | 440 +++--- .../src/hardwarecommunication/interrupts.cpp | 437 +++--- kernel/src/hardwarecommunication/pci.cpp | 646 ++++---- kernel/src/hardwarecommunication/port.cpp | 61 +- kernel/src/kernel.cpp | 88 +- kernel/src/memory/memoryIO.cpp | 143 +- kernel/src/memory/memorymanagement.cpp | 388 +++-- kernel/src/memory/physical.cpp | 856 +++++------ kernel/src/memory/virtual.cpp | 561 ++++--- kernel/src/processes/elf.cpp | 159 +- kernel/src/processes/ipc.cpp | 313 ++-- kernel/src/processes/process.cpp | 262 ++-- kernel/src/processes/scheduler.cpp | 385 +++-- kernel/src/runtime/cplusplus.cpp | 12 +- kernel/src/runtime/ubsan.cpp | 122 +- kernel/src/system/cpu.cpp | 540 ++++--- kernel/src/system/gdt.cpp | 106 +- kernel/src/system/multiboot.cpp | 155 +- kernel/src/system/syscalls.cpp | 352 +++-- 58 files changed, 7572 insertions(+), 7895 deletions(-) create mode 100644 docs/Styles/.clang-format diff --git a/docs/Styles/.clang-format b/docs/Styles/.clang-format new file mode 100644 index 00000000..2d90114e --- /dev/null +++ b/docs/Styles/.clang-format @@ -0,0 +1,8 @@ +BasedOnStyle: LLVM +IndentWidth: 2 +ContinuationIndentWidth: 2 +BreakConstructorInitializers: AfterColon +ConstructorInitializerIndentWidth: 2 +ConstructorInitializerAllOnOneLineOrOnePerLine: false +AllowShortFunctionsOnASingleLine: None +ColumnLimit: 0 \ No newline at end of file diff --git a/kernel/include/filesystem/vfs.h b/kernel/include/filesystem/vfs.h index 6edcb46c..0380949d 100644 --- a/kernel/include/filesystem/vfs.h +++ b/kernel/include/filesystem/vfs.h @@ -26,13 +26,13 @@ namespace MaxOS{ static VirtualFileSystem* current_file_system(); void mount_filesystem(FileSystem* filesystem); - void mount_filesystem(FileSystem* filesystem, string mount_point); + void mount_filesystem(FileSystem* filesystem, const string& mount_point); void unmount_filesystem(FileSystem* filesystem); - void unmount_filesystem(string mount_point); + void unmount_filesystem(const string& mount_point); void unmount_all(); FileSystem* root_filesystem(); - FileSystem* get_filesystem(string mount_point); + FileSystem* get_filesystem(const string& mount_point); FileSystem* find_filesystem(string path); string get_relative_path(FileSystem* filesystem, string path); diff --git a/kernel/include/hardwarecommunication/pci.h b/kernel/include/hardwarecommunication/pci.h index d4faf211..858b4fe0 100644 --- a/kernel/include/hardwarecommunication/pci.h +++ b/kernel/include/hardwarecommunication/pci.h @@ -82,8 +82,8 @@ namespace MaxOS Port32Bit m_command_port; // I/O - uint32_t read(uint16_t bus, uint16_t device, uint16_t function, uint32_t registeroffset); - void write(uint16_t bus, uint16_t device, uint16_t function, uint32_t registeroffset, uint32_t value); + uint32_t read(uint16_t bus, uint16_t device, uint16_t function, uint32_t register_offset); + void write(uint16_t bus, uint16_t device, uint16_t function, uint32_t register_offset, uint32_t value); // Device PeripheralComponentInterconnectDeviceDescriptor get_device_descriptor(uint16_t bus, uint16_t device, uint16_t function); diff --git a/kernel/include/memory/physical.h b/kernel/include/memory/physical.h index ba4f8e11..bb98180c 100644 --- a/kernel/include/memory/physical.h +++ b/kernel/include/memory/physical.h @@ -100,7 +100,7 @@ namespace MaxOS { // Table Management pml_t* get_or_create_table(pml_t* table, size_t index, size_t flags); pml_t* get_and_create_table(pml_t* parent_table, uint64_t table_index, pml_t* table); - pte_t create_page_table_entry(uintptr_t address, size_t flags); + pte_t create_page_table_entry(uintptr_t address, size_t flags) const; static uint64_t physical_address_of_entry(pte_t* entry); pte_t* get_entry(virtual_address_t* virtual_address, pml_t* pml4_root); diff --git a/kernel/include/processes/elf.h b/kernel/include/processes/elf.h index d0ff7891..8c412519 100644 --- a/kernel/include/processes/elf.h +++ b/kernel/include/processes/elf.h @@ -158,7 +158,7 @@ namespace MaxOS static char elf_magic[4]; - void load_program_headers(); + void load_program_headers() const; public: @@ -166,11 +166,11 @@ namespace MaxOS ~Elf64(); void load(); - bool is_valid(); + bool is_valid() const; [[nodiscard]] elf_64_header_t* header() const; - elf_64_program_header_t* get_program_header(size_t index); - elf_64_section_header_t* get_section_header(size_t index); + elf_64_program_header_t* get_program_header(size_t index) const; + elf_64_section_header_t* get_section_header(size_t index) const; static uint64_t to_vmm_flags(uint32_t type); diff --git a/kernel/include/processes/ipc.h b/kernel/include/processes/ipc.h index 2b6e0928..b68439fa 100644 --- a/kernel/include/processes/ipc.h +++ b/kernel/include/processes/ipc.h @@ -29,7 +29,7 @@ namespace MaxOS { public: - SharedMemory(string name, size_t size); + SharedMemory(const string& name, size_t size); ~SharedMemory(); string* name; @@ -58,7 +58,7 @@ namespace MaxOS { common::Spinlock m_message_lock; public: - SharedMessageEndpoint(string name); + SharedMessageEndpoint(const string& name); ~SharedMessageEndpoint(); string* name; diff --git a/kernel/src/common/colour.cpp b/kernel/src/common/colour.cpp index 283edc49..765acebf 100644 --- a/kernel/src/common/colour.cpp +++ b/kernel/src/common/colour.cpp @@ -9,34 +9,32 @@ using namespace MaxOS::common; Colour::Colour() = default; Colour::Colour(uint8_t red, uint8_t green, uint8_t blue) -: red(red), - green(green), - blue(blue) -{ + : red(red), + green(green), + blue(blue) { } Colour::Colour(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) -: red(red), - green(green), - blue(blue), - alpha(alpha) -{ + : red(red), + green(green), + blue(blue), + alpha(alpha) { } Colour::~Colour() = default; Colour::Colour(ConsoleColour colour) { - parse_console_colour(colour); + parse_console_colour(colour); } Colour::Colour(string string) { - if(string[0] == '#') - parse_hex_string(string); - else - parse_ansi_string(string); + if (string[0] == '#') + parse_hex_string(string); + else + parse_ansi_string(string); } @@ -47,19 +45,19 @@ Colour::Colour(string string) { */ void Colour::parse_hex_string(string hex_string) { - // Check if the string is a valid hex string - if(hex_string.length() != 7 || hex_string.length() != 9) - return; + // Check if the string is a valid hex string + if (hex_string.length() != 7 || hex_string.length() != 9) + return; - // Parse the red, green and blue values - red = (hex_string[1] - '0') * 16 + (hex_string[2] - '0'); - green = (hex_string[3] - '0') * 16 + (hex_string[4] - '0'); - blue = (hex_string[5] - '0') * 16 + (hex_string[6] - '0'); + // Parse the red, green and blue values + red = (hex_string[1] - '0') * 16 + (hex_string[2] - '0'); + green = (hex_string[3] - '0') * 16 + (hex_string[4] - '0'); + blue = (hex_string[5] - '0') * 16 + (hex_string[6] - '0'); - // Parse the alpha value - alpha = 255; - if(hex_string.length() == 9) - alpha = (hex_string[7] - '0') * 16 + (hex_string[8] - '0'); + // Parse the alpha value + alpha = 255; + if (hex_string.length() == 9) + alpha = (hex_string[7] - '0') * 16 + (hex_string[8] - '0'); } @@ -71,50 +69,48 @@ void Colour::parse_hex_string(string hex_string) { */ void Colour::parse_ansi_string(string ansi_string) { - // Check if the string is a valid ANSI string - if(ansi_string.length() != 7 || ansi_string[0] != '\033' || ansi_string[1] != '[' || ansi_string[6] != 'm') - return; + // Check if the string is a valid ANSI string + if (ansi_string.length() != 7 || ansi_string[0] != '\033' || ansi_string[1] != '[' || ansi_string[6] != 'm') + return; - // Parse the colour - uint8_t colour = ansi_string[5] - '0'; - switch (colour) { + // Parse the colour + uint8_t colour = ansi_string[5] - '0'; + switch (colour) { + case 0: + parse_console_colour(ConsoleColour::Black); + break; - case 0: - parse_console_colour(ConsoleColour::Black); - break; + case 1: + parse_console_colour(ConsoleColour::Red); + break; - case 1: - parse_console_colour(ConsoleColour::Red); - break; + case 2: + parse_console_colour(ConsoleColour::Green); + break; - case 2: - parse_console_colour(ConsoleColour::Green); - break; + case 3: + parse_console_colour(ConsoleColour::Yellow); + break; - case 3: - parse_console_colour(ConsoleColour::Yellow); - break; + case 4: + parse_console_colour(ConsoleColour::Blue); + break; - case 4: - parse_console_colour(ConsoleColour::Blue); - break; + case 5: + parse_console_colour(ConsoleColour::Magenta); + break; - case 5: - parse_console_colour(ConsoleColour::Magenta); - break; + case 6: + parse_console_colour(ConsoleColour::Cyan); + break; - case 6: - parse_console_colour(ConsoleColour::Cyan); - break; + case 7: + parse_console_colour(ConsoleColour::White); + break; - case 7: - parse_console_colour(ConsoleColour::White); - break; - - - default: - break; - } + default: + break; + } } /** @@ -123,106 +119,106 @@ void Colour::parse_ansi_string(string ansi_string) { * @param colour The console colour */ void Colour::parse_console_colour(ConsoleColour colour) { - switch (colour) { - - case ConsoleColour::Uninitialised: - case ConsoleColour::Black: - red = 0; - green = 0; - blue = 0; - break; - - case ConsoleColour::Blue: - red = 0; - green = 128; - blue = 253; - break; - - case ConsoleColour::Green: - red = 0; - green = 170; - blue = 0; - break; - - case ConsoleColour::Cyan: - red = 0; - green = 170; - blue = 170; - break; - - case ConsoleColour::Red: - red = 170; - green = 0; - blue = 0; - break; - - case ConsoleColour::Magenta: - red = 170; - green = 0; - blue = 170; - break; - - case ConsoleColour::Brown: - red = 170; - green = 85; - blue = 0; - break; - - case ConsoleColour::LightGrey: - red = 170; - green = 170; - blue = 170; - break; - - case ConsoleColour::DarkGrey: - red = 85; - green = 85; - blue = 85; - break; - - case ConsoleColour::LightBlue: - red = 85; - green = 85; - blue = 255; - break; - - case ConsoleColour::LightGreen: - red = 85; - green = 255; - blue = 85; - break; - - case ConsoleColour::LightCyan: - red = 85; - green = 255; - blue = 255; - break; - - case ConsoleColour::LightRed: - red = 255; - green = 85; - blue = 85; - break; - - case ConsoleColour::LightMagenta: - red = 255; - green = 85; - blue = 255; - break; - - // Same as CLION yellow - case ConsoleColour::Yellow: - red = 0x96; - green = 0x82; - blue = 0x0E; - break; - - case ConsoleColour::White: - red = 255; - green = 255; - blue = 255; - break; - } + switch (colour) { + + case ConsoleColour::Uninitialised: + case ConsoleColour::Black: + red = 0; + green = 0; + blue = 0; + break; + + case ConsoleColour::Blue: + red = 0; + green = 128; + blue = 253; + break; + + case ConsoleColour::Green: + red = 0; + green = 170; + blue = 0; + break; + + case ConsoleColour::Cyan: + red = 0; + green = 170; + blue = 170; + break; + + case ConsoleColour::Red: + red = 170; + green = 0; + blue = 0; + break; + + case ConsoleColour::Magenta: + red = 170; + green = 0; + blue = 170; + break; + + case ConsoleColour::Brown: + red = 170; + green = 85; + blue = 0; + break; + + case ConsoleColour::LightGrey: + red = 170; + green = 170; + blue = 170; + break; + + case ConsoleColour::DarkGrey: + red = 85; + green = 85; + blue = 85; + break; + + case ConsoleColour::LightBlue: + red = 85; + green = 85; + blue = 255; + break; + + case ConsoleColour::LightGreen: + red = 85; + green = 255; + blue = 85; + break; + + case ConsoleColour::LightCyan: + red = 85; + green = 255; + blue = 255; + break; + + case ConsoleColour::LightRed: + red = 255; + green = 85; + blue = 85; + break; + + case ConsoleColour::LightMagenta: + red = 255; + green = 85; + blue = 255; + break; + + // Same as CLION yellow + case ConsoleColour::Yellow: + red = 0x96; + green = 0x82; + blue = 0x0E; + break; + + case ConsoleColour::White: + red = 255; + green = 255; + blue = 255; + break; + } } /** @@ -233,54 +229,54 @@ void Colour::parse_console_colour(ConsoleColour colour) { ConsoleColour Colour::to_console_colour() const { - if (red == 0 && green == 0 && blue == 0) - return ConsoleColour::Black; + if (red == 0 && green == 0 && blue == 0) + return ConsoleColour::Black; - if (red == 0 && green == 128 && blue == 253) - return ConsoleColour::Blue; + if (red == 0 && green == 128 && blue == 253) + return ConsoleColour::Blue; - if (red == 0 && green == 170 && blue == 0) - return ConsoleColour::Green; + if (red == 0 && green == 170 && blue == 0) + return ConsoleColour::Green; - if (red == 0 && green == 170 && blue == 170) - return ConsoleColour::Cyan; + if (red == 0 && green == 170 && blue == 170) + return ConsoleColour::Cyan; - if (red == 170 && green == 0 && blue == 0) - return ConsoleColour::Red; + if (red == 170 && green == 0 && blue == 0) + return ConsoleColour::Red; - if (red == 170 && green == 0 && blue == 170) - return ConsoleColour::Magenta; + if (red == 170 && green == 0 && blue == 170) + return ConsoleColour::Magenta; - if (red == 170 && green == 85 && blue == 0) - return ConsoleColour::Brown; + if (red == 170 && green == 85 && blue == 0) + return ConsoleColour::Brown; - if (red == 170 && green == 170 && blue == 170) - return ConsoleColour::LightGrey; + if (red == 170 && green == 170 && blue == 170) + return ConsoleColour::LightGrey; - if (red == 85 && green == 85 && blue == 85) - return ConsoleColour::DarkGrey; + if (red == 85 && green == 85 && blue == 85) + return ConsoleColour::DarkGrey; - if (red == 85 && green == 85 && blue == 255) - return ConsoleColour::LightBlue; + if (red == 85 && green == 85 && blue == 255) + return ConsoleColour::LightBlue; - if (red == 85 && green == 255 && blue == 85) - return ConsoleColour::LightGreen; + if (red == 85 && green == 255 && blue == 85) + return ConsoleColour::LightGreen; - if (red == 85 && green == 255 && blue == 255) - return ConsoleColour::LightCyan; + if (red == 85 && green == 255 && blue == 255) + return ConsoleColour::LightCyan; - if (red == 255 && green == 85 && blue == 85) - return ConsoleColour::LightRed; + if (red == 255 && green == 85 && blue == 85) + return ConsoleColour::LightRed; - if (red == 255 && green == 85 && blue == 255) - return ConsoleColour::LightMagenta; + if (red == 255 && green == 85 && blue == 255) + return ConsoleColour::LightMagenta; - if (red == 0x96 && green == 0x82 && blue == 0x0E) - return ConsoleColour::Yellow; + if (red == 0x96 && green == 0x82 && blue == 0x0E) + return ConsoleColour::Yellow; - if (red == 255 && green == 255 && blue == 255) - return ConsoleColour::White; + if (red == 255 && green == 255 && blue == 255) + return ConsoleColour::White; - // Return a default value in case no match is found - return ConsoleColour::Black; + // Return a default value in case no match is found + return ConsoleColour::Black; } diff --git a/kernel/src/common/graphicsContext.cpp b/kernel/src/common/graphicsContext.cpp index 684db137..8946199d 100644 --- a/kernel/src/common/graphicsContext.cpp +++ b/kernel/src/common/graphicsContext.cpp @@ -6,84 +6,83 @@ using namespace MaxOS::common; -GraphicsContext::GraphicsContext() -{ - - - // VirtualBox VGA palette - m_colour_pallet[0x00] = Colour(0x00,0x00,0x00); // Black - m_colour_pallet[0x01] = Colour(0x00,0x00,0xA8); // Duke Blue - m_colour_pallet[0x02] = Colour(0x00,0xA8,0x00); // Islamic Green - m_colour_pallet[0x03] = Colour(0x00,0xA8,0xA8); // Persian Green - m_colour_pallet[0x04] = Colour(0xA8,0x00,0x00); // Dark Candy Apple Red - m_colour_pallet[0x05] = Colour(0xA8,0x00,0xA8); // Heliotrope Magenta - - m_colour_pallet[0x06] = Colour(0xA8,0xA8,0x00); // Light Gold - m_colour_pallet[0x07] = Colour(0xA8,0xA8,0xA8); // Dark Gray (X11) - m_colour_pallet[0x08] = Colour(0x00,0x00,0x57); // Cetacean Blue - m_colour_pallet[0x09] = Colour(0x00,0x00,0xFF); // Blue - m_colour_pallet[0x0A] = Colour(0x00,0xA8,0x57); // Green (Pigment) - m_colour_pallet[0x0B] = Colour(0x00,0xA8,0xFF); // Vivid Cerulean - m_colour_pallet[0x0C] = Colour(0xA8,0x00,0x57); // Jazz berry Jam - m_colour_pallet[0x0D] = Colour(0xA8,0x00,0x57); // Jazz berry Jam - m_colour_pallet[0x0E] = Colour(0xA8,0xA8,0x57); // Olive Green - m_colour_pallet[0x0F] = Colour(0xA8,0xA8,0xFF); // Maximum Blue Purple - - m_colour_pallet[0x10] = Colour(0x00,0x57,0x00); // Dark Green (X11) - m_colour_pallet[0x11] = Colour(0x00,0x57,0xA8); // Cobalt Blue - m_colour_pallet[0x12] = Colour(0x00,0xFF,0x00); // Electric Green - m_colour_pallet[0x13] = Colour(0x00,0xFF,0xA8); // Medium Spring Green - m_colour_pallet[0x14] = Colour(0xA8,0x57,0x00); // Windsor Tan - m_colour_pallet[0x15] = Colour(0xA8,0x57,0xA8); // Purpureus - m_colour_pallet[0x16] = Colour(0xA8,0xFF,0x00); // Spring Bud - m_colour_pallet[0x17] = Colour(0xA8,0xFF,0xA8); // Mint Green - m_colour_pallet[0x18] = Colour(0x00,0x57,0x57); // Midnight Green (Eagle Green) - m_colour_pallet[0x19] = Colour(0x00,0x57,0xFF); // Blue (RYB) - m_colour_pallet[0x1A] = Colour(0x00,0xFF,0x57); // Malachite - m_colour_pallet[0x1B] = Colour(0x00,0xFF,0xFF); // Aqua - m_colour_pallet[0x1C] = Colour(0xA8,0x57,0x57); // Middle Red Purple - m_colour_pallet[0x1D] = Colour(0xA8,0x57,0xFF); // Lavender Indigo - m_colour_pallet[0x1E] = Colour(0xA8,0xFF,0x57); // Olive Green - m_colour_pallet[0x1F] = Colour(0xA8,0xFF,0xFF); // Celeste - - m_colour_pallet[0x20] = Colour(0x57,0x00,0x00); // Blood Red - m_colour_pallet[0x21] = Colour(0x57,0x00,0xA8); // Metallic Violet - m_colour_pallet[0x22] = Colour(0x57,0xA8,0x00); // Kelly Green - m_colour_pallet[0x23] = Colour(0x57,0xA8,0xA8); // Cadet Blue - m_colour_pallet[0x24] = Colour(0xFF,0x00,0x00); // Red - m_colour_pallet[0x25] = Colour(0xFF,0x00,0xA8); // Fashion Fuchsia - m_colour_pallet[0x26] = Colour(0xFF,0xA8,0x00); // Chrome Yellow - m_colour_pallet[0x27] = Colour(0xFF,0xA8,0xA8); // Light Salmon Pink - m_colour_pallet[0x28] = Colour(0x57,0x00,0x57); // Imperial Purple - m_colour_pallet[0x29] = Colour(0x57,0x00,0xFF); // Electric Indigo - m_colour_pallet[0x2A] = Colour(0x57,0xA8,0x57); // Apple - m_colour_pallet[0x2B] = Colour(0x57,0xA8,0xFF); // Blue Jeans - m_colour_pallet[0x2C] = Colour(0xFF,0x00,0x57); // Folly - m_colour_pallet[0x2D] = Colour(0xFF,0x00,0xFF); // Fuchsia - m_colour_pallet[0x2E] = Colour(0xFF,0xA8,0x57); // Rajah - m_colour_pallet[0x2F] = Colour(0xFF,0xA8,0xFF); // Rich Brilliant Lavender - - m_colour_pallet[0x30] = Colour(0x57,0x57,0x00); // Dark Bronze (Coin) - m_colour_pallet[0x31] = Colour(0x57,0x57,0xA8); // Liberty - m_colour_pallet[0x32] = Colour(0x57,0xFF,0x00); // Chlorophyll Green - m_colour_pallet[0x33] = Colour(0x57,0xFF,0xA8); // Medium Aquamarine - m_colour_pallet[0x34] = Colour(0xFF,0x57,0x00); // Orange (Pantone) - m_colour_pallet[0x35] = Colour(0xFF,0x57,0xA8); // Brilliant Rose - m_colour_pallet[0x36] = Colour(0xFF,0xFF,0x00); // Yellow - m_colour_pallet[0x37] = Colour(0xFF,0xFF,0xA8); // Calamansi - m_colour_pallet[0x38] = Colour(0x57,0x57,0x57); // Davy's Grey - m_colour_pallet[0x39] = Colour(0x57,0x57,0xFF); // Very Light Blue - m_colour_pallet[0x3A] = Colour(0x57,0xFF,0x57); // Screamin' Green - m_colour_pallet[0x3B] = Colour(0x57,0xFF,0xFF); // Electric Blue - m_colour_pallet[0x3C] = Colour(0xFF,0x57,0x57); // Sunset Orange - m_colour_pallet[0x3D] = Colour(0xFF,0x57,0xFF); // Shocking Pink (Crayola) - m_colour_pallet[0x3E] = Colour(0xFF,0xFF,0x57); // Shocking Pink (Crayola) - m_colour_pallet[0x3F] = Colour(0xFF,0xFF,0xFF); // White - - - // Set the rest of the palette to black - for(uint8_t color_code = 255; color_code >= 0x40; --color_code) - m_colour_pallet[color_code] = Colour(0,0,0); +GraphicsContext::GraphicsContext() { + + + // VirtualBox VGA palette + m_colour_pallet[0x00] = Colour(0x00, 0x00, 0x00); // Black + m_colour_pallet[0x01] = Colour(0x00, 0x00, 0xA8); // Duke Blue + m_colour_pallet[0x02] = Colour(0x00, 0xA8, 0x00); // Islamic Green + m_colour_pallet[0x03] = Colour(0x00, 0xA8, 0xA8); // Persian Green + m_colour_pallet[0x04] = Colour(0xA8, 0x00, 0x00); // Dark Candy Apple Red + m_colour_pallet[0x05] = Colour(0xA8, 0x00, 0xA8); // Heliotrope Magenta + + m_colour_pallet[0x06] = Colour(0xA8, 0xA8, 0x00); // Light Gold + m_colour_pallet[0x07] = Colour(0xA8, 0xA8, 0xA8); // Dark Gray (X11) + m_colour_pallet[0x08] = Colour(0x00, 0x00, 0x57); // Cetacean Blue + m_colour_pallet[0x09] = Colour(0x00, 0x00, 0xFF); // Blue + m_colour_pallet[0x0A] = Colour(0x00, 0xA8, 0x57); // Green (Pigment) + m_colour_pallet[0x0B] = Colour(0x00, 0xA8, 0xFF); // Vivid Cerulean + m_colour_pallet[0x0C] = Colour(0xA8, 0x00, 0x57); // Jazz berry Jam + m_colour_pallet[0x0D] = Colour(0xA8, 0x00, 0x57); // Jazz berry Jam + m_colour_pallet[0x0E] = Colour(0xA8, 0xA8, 0x57); // Olive Green + m_colour_pallet[0x0F] = Colour(0xA8, 0xA8, 0xFF); // Maximum Blue Purple + + m_colour_pallet[0x10] = Colour(0x00, 0x57, 0x00); // Dark Green (X11) + m_colour_pallet[0x11] = Colour(0x00, 0x57, 0xA8); // Cobalt Blue + m_colour_pallet[0x12] = Colour(0x00, 0xFF, 0x00); // Electric Green + m_colour_pallet[0x13] = Colour(0x00, 0xFF, 0xA8); // Medium Spring Green + m_colour_pallet[0x14] = Colour(0xA8, 0x57, 0x00); // Windsor Tan + m_colour_pallet[0x15] = Colour(0xA8, 0x57, 0xA8); // Purpureus + m_colour_pallet[0x16] = Colour(0xA8, 0xFF, 0x00); // Spring Bud + m_colour_pallet[0x17] = Colour(0xA8, 0xFF, 0xA8); // Mint Green + m_colour_pallet[0x18] = Colour(0x00, 0x57, 0x57); // Midnight Green (Eagle Green) + m_colour_pallet[0x19] = Colour(0x00, 0x57, 0xFF); // Blue (RYB) + m_colour_pallet[0x1A] = Colour(0x00, 0xFF, 0x57); // Malachite + m_colour_pallet[0x1B] = Colour(0x00, 0xFF, 0xFF); // Aqua + m_colour_pallet[0x1C] = Colour(0xA8, 0x57, 0x57); // Middle Red Purple + m_colour_pallet[0x1D] = Colour(0xA8, 0x57, 0xFF); // Lavender Indigo + m_colour_pallet[0x1E] = Colour(0xA8, 0xFF, 0x57); // Olive Green + m_colour_pallet[0x1F] = Colour(0xA8, 0xFF, 0xFF); // Celeste + + m_colour_pallet[0x20] = Colour(0x57, 0x00, 0x00); // Blood Red + m_colour_pallet[0x21] = Colour(0x57, 0x00, 0xA8); // Metallic Violet + m_colour_pallet[0x22] = Colour(0x57, 0xA8, 0x00); // Kelly Green + m_colour_pallet[0x23] = Colour(0x57, 0xA8, 0xA8); // Cadet Blue + m_colour_pallet[0x24] = Colour(0xFF, 0x00, 0x00); // Red + m_colour_pallet[0x25] = Colour(0xFF, 0x00, 0xA8); // Fashion Fuchsia + m_colour_pallet[0x26] = Colour(0xFF, 0xA8, 0x00); // Chrome Yellow + m_colour_pallet[0x27] = Colour(0xFF, 0xA8, 0xA8); // Light Salmon Pink + m_colour_pallet[0x28] = Colour(0x57, 0x00, 0x57); // Imperial Purple + m_colour_pallet[0x29] = Colour(0x57, 0x00, 0xFF); // Electric Indigo + m_colour_pallet[0x2A] = Colour(0x57, 0xA8, 0x57); // Apple + m_colour_pallet[0x2B] = Colour(0x57, 0xA8, 0xFF); // Blue Jeans + m_colour_pallet[0x2C] = Colour(0xFF, 0x00, 0x57); // Folly + m_colour_pallet[0x2D] = Colour(0xFF, 0x00, 0xFF); // Fuchsia + m_colour_pallet[0x2E] = Colour(0xFF, 0xA8, 0x57); // Rajah + m_colour_pallet[0x2F] = Colour(0xFF, 0xA8, 0xFF); // Rich Brilliant Lavender + + m_colour_pallet[0x30] = Colour(0x57, 0x57, 0x00); // Dark Bronze (Coin) + m_colour_pallet[0x31] = Colour(0x57, 0x57, 0xA8); // Liberty + m_colour_pallet[0x32] = Colour(0x57, 0xFF, 0x00); // Chlorophyll Green + m_colour_pallet[0x33] = Colour(0x57, 0xFF, 0xA8); // Medium Aquamarine + m_colour_pallet[0x34] = Colour(0xFF, 0x57, 0x00); // Orange (Pantone) + m_colour_pallet[0x35] = Colour(0xFF, 0x57, 0xA8); // Brilliant Rose + m_colour_pallet[0x36] = Colour(0xFF, 0xFF, 0x00); // Yellow + m_colour_pallet[0x37] = Colour(0xFF, 0xFF, 0xA8); // Calamansi + m_colour_pallet[0x38] = Colour(0x57, 0x57, 0x57); // Davy's Grey + m_colour_pallet[0x39] = Colour(0x57, 0x57, 0xFF); // Very Light Blue + m_colour_pallet[0x3A] = Colour(0x57, 0xFF, 0x57); // Screamin' Green + m_colour_pallet[0x3B] = Colour(0x57, 0xFF, 0xFF); // Electric Blue + m_colour_pallet[0x3C] = Colour(0xFF, 0x57, 0x57); // Sunset Orange + m_colour_pallet[0x3D] = Colour(0xFF, 0x57, 0xFF); // Shocking Pink (Crayola) + m_colour_pallet[0x3E] = Colour(0xFF, 0xFF, 0x57); // Shocking Pink (Crayola) + m_colour_pallet[0x3F] = Colour(0xFF, 0xFF, 0xFF); // White + + + // Set the rest of the palette to black + for (uint8_t color_code = 255; color_code >= 0x40; --color_code) + m_colour_pallet[color_code] = Colour(0, 0, 0); } @@ -99,21 +98,21 @@ GraphicsContext::~GraphicsContext() = default; */ void GraphicsContext::render_pixel(uint32_t x, uint32_t y, uint32_t colour) { - // Call the correct put_pixel function based on the color depth - switch (m_color_depth) { - case 8: - render_pixel_8_bit(x, y, colour); - break; - case 16: - render_pixel_16_bit(x, y, colour); - break; - case 24: - render_pixel_24_bit(x, y, colour); - break; - case 32: - render_pixel_32_bit(x, y, colour); - break; - } + // Call the correct put_pixel function based on the color depth + switch (m_color_depth) { + case 8: + render_pixel_8_bit(x, y, colour); + break; + case 16: + render_pixel_16_bit(x, y, colour); + break; + case 24: + render_pixel_24_bit(x, y, colour); + break; + case 32: + render_pixel_32_bit(x, y, colour); + break; + } } @@ -170,19 +169,19 @@ void GraphicsContext::render_pixel_32_bit(uint32_t, uint32_t, uint32_t) { * @return The colour of the pixel or white if the pixel is not supported */ uint32_t GraphicsContext::get_rendered_pixel(uint32_t x, uint32_t y) { - // Call the correct get_pixel function based on the color depth - switch (m_color_depth) { - case 8: - return get_rendered_pixel_8_bit(x, y); - case 16: - return get_rendered_pixel_16_bit(x, y); - case 24: - return get_rendered_pixel_24_bit(x, y); - case 32: - return get_rendered_pixel_32_bit(x, y); - } - - return colour_to_int(Colour(0xFF, 0xFF, 0xFF)); + // Call the correct get_pixel function based on the color depth + switch (m_color_depth) { + case 8: + return get_rendered_pixel_8_bit(x, y); + case 16: + return get_rendered_pixel_16_bit(x, y); + case 24: + return get_rendered_pixel_24_bit(x, y); + case 32: + return get_rendered_pixel_32_bit(x, y); + } + + return colour_to_int(Colour(0xFF, 0xFF, 0xFF)); } /** @@ -193,7 +192,7 @@ uint32_t GraphicsContext::get_rendered_pixel(uint32_t x, uint32_t y) { * @return The 8Bit colour of the pixel */ uint8_t GraphicsContext::get_rendered_pixel_8_bit(uint32_t, uint32_t) { - return 0; + return 0; } /** @@ -204,7 +203,7 @@ uint8_t GraphicsContext::get_rendered_pixel_8_bit(uint32_t, uint32_t) { * @return The 16Bit colour of the pixel */ uint16_t GraphicsContext::get_rendered_pixel_16_bit(uint32_t, uint32_t) { - return 0; + return 0; } /** @@ -215,7 +214,7 @@ uint16_t GraphicsContext::get_rendered_pixel_16_bit(uint32_t, uint32_t) { * @return The 24Bit colour of the pixel */ uint32_t GraphicsContext::get_rendered_pixel_24_bit(uint32_t, uint32_t) { - return 0; + return 0; } /** @@ -226,7 +225,7 @@ uint32_t GraphicsContext::get_rendered_pixel_24_bit(uint32_t, uint32_t) { * @return The 32Bit colour of the pixel */ uint32_t GraphicsContext::get_rendered_pixel_32_bit(uint32_t, uint32_t) { - return 0; + return 0; } /** @@ -235,56 +234,49 @@ uint32_t GraphicsContext::get_rendered_pixel_32_bit(uint32_t, uint32_t) { * @param colour The colour class to convert * @return The integer value of the colour */ -uint32_t GraphicsContext::colour_to_int(const Colour& colour) { - - switch(m_color_depth) - { - case 8: - { - uint32_t result = 0; - int mindistance = 0xfffffff; - for(uint32_t i = 0; i <= 255; ++i) - { - Colour* c = &m_colour_pallet[i]; - int distance = - ((int)colour.red-(int)c->red)*((int)colour.red-(int)c->red) - +((int)colour.green-(int)c->green)*((int)colour.green-(int)c->green) - +((int)colour.blue-(int)c->blue)*((int)colour.blue-(int)c->blue); - if(distance < mindistance) - { - mindistance = distance; - result = i; - } - } - return result; - } - case 16: - { - // 16-Bit colours RRRRRGGGGGGBBBBB - return ((uint16_t)(colour.red & 0xF8)) << 8 - | ((uint16_t)(colour.green & 0xFC)) << 3 - | ((uint16_t)(colour.blue & 0xF8) >> 3); - } - case 24: - { - return (uint32_t)colour.red << 16 - | (uint32_t)colour.green << 8 - | (uint32_t)colour.blue; - } - default: - case 32: - { - uint32_t red_hex = ((uint32_t)colour.red & 0xFF) << 16; - uint32_t green_hex = ((uint32_t)colour.green & 0xFF) << 8; - uint32_t blue_hex = (uint32_t)colour.blue & 0xFF; - uint32_t alpha_hex = ((uint32_t)colour.alpha & 0xFF) << 24; - - uint32_t hexValue = red_hex | green_hex | blue_hex | alpha_hex; - - - return hexValue; - } - } +uint32_t GraphicsContext::colour_to_int(const Colour &colour) { + + switch (m_color_depth) { + case 8: { + uint32_t result = 0; + int mindistance = 0xfffffff; + for (uint32_t i = 0; i <= 255; ++i) { + Colour *c = &m_colour_pallet[i]; + int distance = + ((int) colour.red - (int) c->red) * ((int) colour.red - (int) c->red) + + ((int) colour.green - (int) c->green) * ((int) colour.green - (int) c->green) + + ((int) colour.blue - (int) c->blue) * ((int) colour.blue - (int) c->blue); + if (distance < mindistance) { + mindistance = distance; + result = i; + } + } + return result; + } + case 16: { + // 16-Bit colours RRRRRGGGGGGBBBBB + return ((uint16_t) (colour.red & 0xF8)) << 8 + | ((uint16_t) (colour.green & 0xFC)) << 3 + | ((uint16_t) (colour.blue & 0xF8) >> 3); + } + case 24: { + return (uint32_t) colour.red << 16 + | (uint32_t) colour.green << 8 + | (uint32_t) colour.blue; + } + default: + case 32: { + uint32_t red_hex = ((uint32_t) colour.red & 0xFF) << 16; + uint32_t green_hex = ((uint32_t) colour.green & 0xFF) << 8; + uint32_t blue_hex = (uint32_t) colour.blue & 0xFF; + uint32_t alpha_hex = ((uint32_t) colour.alpha & 0xFF) << 24; + + uint32_t hexValue = red_hex | green_hex | blue_hex | alpha_hex; + + + return hexValue; + } + } } /** @@ -294,53 +286,49 @@ uint32_t GraphicsContext::colour_to_int(const Colour& colour) { * @return The colour class of the integer value */ Colour GraphicsContext::int_to_colour(uint32_t colour) { - switch (m_color_depth) { + switch (m_color_depth) { - case 8: - { - // Return the colour from the palette - return m_colour_pallet[colour & 0xFF]; - } + case 8: { + // Return the colour from the palette + return m_colour_pallet[colour & 0xFF]; + } - case 16: - { - // 16-Bit Colour: 5 bits for red, 6 bits for green, 5 bits for blue (RRRRR,GGGGGG,BBBBB) - Colour result; + case 16: { + // 16-Bit Colour: 5 bits for red, 6 bits for green, 5 bits for blue (RRRRR,GGGGGG,BBBBB) + Colour result; - result.red = (colour & 0xF800) >> 8; - result.green = (colour & 0x07E0) >> 3; - result.blue = (colour & 0x001F) << 3; + result.red = (colour & 0xF800) >> 8; + result.green = (colour & 0x07E0) >> 3; + result.blue = (colour & 0x001F) << 3; - return result; - } + return result; + } - case 24: - { - // 24-Bit Colour: 8 bits for red, 8 bits for green, 8 bits for blue (RRRRRRRR,GGGGGGGG,BBBBBBBB) - Colour result; + case 24: { + // 24-Bit Colour: 8 bits for red, 8 bits for green, 8 bits for blue (RRRRRRRR,GGGGGGGG,BBBBBBBB) + Colour result; - result.red = (colour & 0xFF0000) >> 16; - result.green = (colour & 0x00FF00) >> 8; - result.blue = (colour & 0x0000FF); + result.red = (colour & 0xFF0000) >> 16; + result.green = (colour & 0x00FF00) >> 8; + result.blue = (colour & 0x0000FF); - return result; - } + return result; + } - default: - case 32: - { - Colour result; + default: + case 32: { + Colour result; - uint32_t hex_value = colour; - result.red = (hex_value >> 16) & 0xFF; - result.green = (hex_value >> 8) & 0xFF; - result.blue = hex_value & 0xFF; - result.alpha = (hex_value >> 24) & 0xFF; + uint32_t hex_value = colour; + result.red = (hex_value >> 16) & 0xFF; + result.green = (hex_value >> 8) & 0xFF; + result.blue = hex_value & 0xFF; + result.alpha = (hex_value >> 24) & 0xFF; - return result; + return result; - } - } + } + } } /** @@ -349,7 +337,7 @@ Colour GraphicsContext::int_to_colour(uint32_t colour) { * @return The width of the screen */ uint32_t GraphicsContext::width() const { - return m_width; + return m_width; } /** @@ -358,7 +346,7 @@ uint32_t GraphicsContext::width() const { * @return The height of the screen */ uint32_t GraphicsContext::height() const { - return m_height; + return m_height; } /** @@ -366,7 +354,7 @@ uint32_t GraphicsContext::height() const { * @return The color depth */ uint32_t GraphicsContext::color_depth() const { - return m_color_depth; + return m_color_depth; } /** @@ -376,10 +364,10 @@ uint32_t GraphicsContext::color_depth() const { * @param y The y coordinate of the pixel * @param colour The colour of the pixel */ -void GraphicsContext::put_pixel(int32_t x, int32_t y, const Colour& colour) { +void GraphicsContext::put_pixel(int32_t x, int32_t y, const Colour &colour) { - // Convert the colour to an integer and then print it - putPixel(x,y, colour_to_int(colour)); + // Convert the colour to an integer and then print it + putPixel(x, y, colour_to_int(colour)); } /** @@ -391,17 +379,17 @@ void GraphicsContext::put_pixel(int32_t x, int32_t y, const Colour& colour) { */ void GraphicsContext::putPixel(int32_t x, int32_t y, uint32_t colour) { - if (0 > x || (uint32_t)x >= m_width) { - return; - } + if (0 > x || (uint32_t) x >= m_width) { + return; + } - // Check if the pixel is within the m_height of the screen - if (0 > y || (uint32_t) y >= m_height) { - return; - } + // Check if the pixel is within the m_height of the screen + if (0 > y || (uint32_t) y >= m_height) { + return; + } - // Render the pixel - render_pixel(x, mirror_y_axis ? m_height - y - 1 : y, colour); + // Render the pixel + render_pixel(x, mirror_y_axis ? m_height - y - 1 : y, colour); } @@ -414,13 +402,13 @@ void GraphicsContext::putPixel(int32_t x, int32_t y, uint32_t colour) { */ Colour GraphicsContext::get_pixel(int32_t x, int32_t y) { - // Check if the pixel is within the bounds of the screen - if (0 > x || (uint32_t)x >= m_width || 0 > y || (uint32_t) y >= m_height) - return {0,0,0}; + // Check if the pixel is within the bounds of the screen + if (0 > x || (uint32_t) x >= m_width || 0 > y || (uint32_t) y >= m_height) + return {0, 0, 0}; - // Get the pixel and convert it to a colour - uint32_t translated_color = get_rendered_pixel(x, mirror_y_axis ? m_height - y - 1 : y); - return int_to_colour(translated_color); + // Get the pixel and convert it to a colour + uint32_t translated_color = get_rendered_pixel(x, mirror_y_axis ? m_height - y - 1 : y); + return int_to_colour(translated_color); } /** @@ -431,16 +419,16 @@ Colour GraphicsContext::get_pixel(int32_t x, int32_t y) { */ void GraphicsContext::invert_pixel(int32_t x, int32_t y) { - // Get the pixel - Colour colour = get_pixel(x, y); + // Get the pixel + Colour colour = get_pixel(x, y); - // Invert the pixel - colour.red = 255 - colour.red; - colour.green = 255 - colour.green; - colour.blue = 255 - colour.blue; + // Invert the pixel + colour.red = 255 - colour.red; + colour.green = 255 - colour.green; + colour.blue = 255 - colour.blue; - // Render the pixel - put_pixel(x, y, colour); + // Render the pixel + put_pixel(x, y, colour); } @@ -453,8 +441,8 @@ void GraphicsContext::invert_pixel(int32_t x, int32_t y) { * @param y1 The y coordinate of the final point * @param colour The colour of the line */ -void GraphicsContext::draw_line(int32_t x0, int32_t y0, int32_t x1, int32_t y1, const Colour& colour) { - drawLine(x0,y0,x1,y1, colour_to_int(colour)); +void GraphicsContext::draw_line(int32_t x0, int32_t y0, int32_t x1, int32_t y1, const Colour &colour) { + drawLine(x0, y0, x1, y1, colour_to_int(colour)); } /** @@ -468,87 +456,82 @@ void GraphicsContext::draw_line(int32_t x0, int32_t y0, int32_t x1, int32_t y1, */ void GraphicsContext::drawLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint32_t colour) { - // Store the minimum and maximum y values - bool y_0_is_smaller = y0 < y1; - int32_t y_min = y_0_is_smaller ? y0 : y1; - int32_t y_max = y_0_is_smaller ? y1 : y0; - - //Reverse the points to draw from left to right - if(x1 < x0){ - drawLine(x1,y1,x0,y0,colour); - return; - } - - // Vertical line - if(x1 == x0) - { - // Force the line to be within the screen - if(y_min < 0) y_min = 0; - if(y_max >= m_height) - y_max = m_height - 1; - - // Mirror the Y axis as directly calling put_pixel will not do this - if(mirror_y_axis) - { - int32_t temp = y_max; - y_max = m_height - y_min - 1; - y_min = m_height - temp - 1; - } - - // Check that the line is within the screen - if (0 > x0 || (uint32_t) x0 >= m_width) { - return; - } - - // Draw the line - for(int32_t y = y_min; y <= y_max; ++y) - putPixel(x0, y, colour); - - return; - } - - // Horizontal line - if(y1 == y0) - { - // Ensure the line is within the screen - if(x0 < 0) x0 = 0; - if(x1 >= m_width) x1 = m_width -1; - - // Mirror the Y axis as directly calling put_pixel will not do this - if(mirror_y_axis) - y0 = m_height -y0-1; - - // Check that the line is within the screen - if (0 > y0 || y0 >= m_height) - return; - - // Draw the line - for(int32_t x = x0; x <= x1; ++x) - putPixel(x,y0,colour); - } - - // If the line is not horizontal or vertical then it must be a diagonal line - // Find the slope of the line - float slope = ((float)(y1-y0))/(x1-x0); - - // A slope that is more horizontal should be drawn by incrementing x - if(-1 <= slope && slope <= 1) - { - float y = y0; - for(int32_t x = x0; x <= x1; x++, y+=slope) - putPixel(x, (int32_t)y, colour); - } - - // A slope that is more vertical should be drawn by incrementing y - else - { - // Invert the slope - slope = 1.0f/slope; - - float x = x0; - for(int32_t y = y_min; y <= y_max; x+=slope, y++) - putPixel((int32_t)x, y, colour); - } + // Store the minimum and maximum y values + bool y_0_is_smaller = y0 < y1; + int32_t y_min = y_0_is_smaller ? y0 : y1; + int32_t y_max = y_0_is_smaller ? y1 : y0; + + //Reverse the points to draw from left to right + if (x1 < x0) { + drawLine(x1, y1, x0, y0, colour); + return; + } + + // Vertical line + if (x1 == x0) { + // Force the line to be within the screen + if (y_min < 0) y_min = 0; + if (y_max >= m_height) + y_max = m_height - 1; + + // Mirror the Y axis as directly calling put_pixel will not do this + if (mirror_y_axis) { + int32_t temp = y_max; + y_max = m_height - y_min - 1; + y_min = m_height - temp - 1; + } + + // Check that the line is within the screen + if (0 > x0 || (uint32_t) x0 >= m_width) { + return; + } + + // Draw the line + for (int32_t y = y_min; y <= y_max; ++y) + putPixel(x0, y, colour); + + return; + } + + // Horizontal line + if (y1 == y0) { + // Ensure the line is within the screen + if (x0 < 0) x0 = 0; + if (x1 >= m_width) x1 = m_width - 1; + + // Mirror the Y axis as directly calling put_pixel will not do this + if (mirror_y_axis) + y0 = m_height - y0 - 1; + + // Check that the line is within the screen + if (0 > y0 || y0 >= m_height) + return; + + // Draw the line + for (int32_t x = x0; x <= x1; ++x) + putPixel(x, y0, colour); + } + + // If the line is not horizontal or vertical then it must be a diagonal line + // Find the slope of the line + float slope = ((float) (y1 - y0)) / (x1 - x0); + + // A slope that is more horizontal should be drawn by incrementing x + if (-1 <= slope && slope <= 1) { + float y = y0; + for (int32_t x = x0; x <= x1; x++, y += slope) + putPixel(x, (int32_t) y, colour); + } + + // A slope that is more vertical should be drawn by incrementing y + else { + // Invert the slope + slope = 1.0f / slope; + + float x = x0; + for (int32_t y = y_min; y <= y_max; x += slope, y++) + putPixel((int32_t) x, y, colour); + } } /** @@ -560,8 +543,8 @@ void GraphicsContext::drawLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1, u * @param y1 The y coordinate of the bottom right corner * @param colour The colour of the rectangle */ -void GraphicsContext::draw_rectangle(int32_t x0, int32_t y0, int32_t x1, int32_t y1, const Colour& colour) { - draw_rectangle(x0, y0, x1, y1, colour_to_int(colour)); +void GraphicsContext::draw_rectangle(int32_t x0, int32_t y0, int32_t x1, int32_t y1, const Colour &colour) { + draw_rectangle(x0, y0, x1, y1, colour_to_int(colour)); } @@ -576,15 +559,15 @@ void GraphicsContext::draw_rectangle(int32_t x0, int32_t y0, int32_t x1, int32_t */ void GraphicsContext::draw_rectangle(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint32_t colour) { - // Ensure x and y 0 is smaller than x and y 1 - --y0; - --x0; + // Ensure x and y 0 is smaller than x and y 1 + --y0; + --x0; - // Draw the rectangle - drawLine(x0,y0,x1,y0,colour); // Top - drawLine(x0,y1,x1,y1,colour); // Bottom - drawLine(x0,y0,x0,y1,colour); // Left - drawLine(x1,y0,x1,y1,colour); // Right + // Draw the rectangle + drawLine(x0, y0, x1, y0, colour); // Top + drawLine(x0, y1, x1, y1, colour); // Bottom + drawLine(x0, y0, x0, y1, colour); // Left + drawLine(x1, y0, x1, y1, colour); // Right } @@ -597,8 +580,8 @@ void GraphicsContext::draw_rectangle(int32_t x0, int32_t y0, int32_t x1, int32_t * @param y1 The y coordinate of the bottom right corner * @param colour The colour of the rectangle */ -void GraphicsContext::fill_rectangle(int32_t x0, int32_t y0, int32_t x1, int32_t y1, const Colour& colour) { - fill_rectangle(x0, y0, x1, y1, colour_to_int(colour)); +void GraphicsContext::fill_rectangle(int32_t x0, int32_t y0, int32_t x1, int32_t y1, const Colour &colour) { + fill_rectangle(x0, y0, x1, y1, colour_to_int(colour)); } /** @@ -612,39 +595,38 @@ void GraphicsContext::fill_rectangle(int32_t x0, int32_t y0, int32_t x1, int32_t */ void GraphicsContext::fill_rectangle(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint32_t colour) { - // Draw from left to right - if(y1 < y0){ - fill_rectangle(x1, y1, x0, y0, colour); - return; - } - - // Make sure the rectangle is within the height of the screen - if(y0 < 0) y0 = 0; - if(y1 > m_height) y1 = m_height; - - // Make sure the rectangle is within the width of the screen - bool x_0_is_smaller = x0 < x1; - int32_t x_min = x_0_is_smaller ? x0 : x1; - int32_t x_max = x_0_is_smaller ? x1 : x0; - - if(x_min < 0) x_min = 0; - if(x_max > m_width) - x_max = m_width; - - // Mirror the Y axis as directly calling put_pixel will not do this - if(mirror_y_axis) - { - int32_t temp = y1; - y1 = m_height - y0 - 1; - y0 = m_height - temp - 1; - } - - // Draw the rectangle - for(int32_t y = y0; y < y1; ++y){ - for (int32_t x = x_min; x < x_max; ++x) { - putPixel(x, y, colour); - } - } + // Draw from left to right + if (y1 < y0) { + fill_rectangle(x1, y1, x0, y0, colour); + return; + } + + // Make sure the rectangle is within the height of the screen + if (y0 < 0) y0 = 0; + if (y1 > m_height) y1 = m_height; + + // Make sure the rectangle is within the width of the screen + bool x_0_is_smaller = x0 < x1; + int32_t x_min = x_0_is_smaller ? x0 : x1; + int32_t x_max = x_0_is_smaller ? x1 : x0; + + if (x_min < 0) x_min = 0; + if (x_max > m_width) + x_max = m_width; + + // Mirror the Y axis as directly calling put_pixel will not do this + if (mirror_y_axis) { + int32_t temp = y1; + y1 = m_height - y0 - 1; + y0 = m_height - temp - 1; + } + + // Draw the rectangle + for (int32_t y = y0; y < y1; ++y) { + for (int32_t x = x_min; x < x_max; ++x) { + putPixel(x, y, colour); + } + } } @@ -656,8 +638,8 @@ void GraphicsContext::fill_rectangle(int32_t x0, int32_t y0, int32_t x1, int32_t * @param radius The radius of the circle * @param colour The colour of the circle */ -void GraphicsContext::draw_circle(int32_t x0, int32_t y0, int32_t radius, const Colour& colour){ - draw_circle(x0, y0, radius, colour_to_int(colour)); +void GraphicsContext::draw_circle(int32_t x0, int32_t y0, int32_t radius, const Colour &colour) { + draw_circle(x0, y0, radius, colour_to_int(colour)); } /** @@ -670,28 +652,28 @@ void GraphicsContext::draw_circle(int32_t x0, int32_t y0, int32_t radius, const */ void GraphicsContext::draw_circle(int32_t x0, int32_t y0, int32_t radius, uint32_t colour) { - // Make sure the circle is with in the width and height of the screen - if(x0 < 0) x0 = 0; - if(x0 > m_width) x0 = m_width; - if(y0 < 0) y0 = 0; - if(y0 > m_height) y0 = m_height; + // Make sure the circle is with in the width and height of the screen + if (x0 < 0) x0 = 0; + if (x0 > m_width) x0 = m_width; + if (y0 < 0) y0 = 0; + if (y0 > m_height) y0 = m_height; - // Mirror the Y axis as directly calling put_pixel will not do this - if(mirror_y_axis) - y0 = m_height -y0-1; + // Mirror the Y axis as directly calling put_pixel will not do this + if (mirror_y_axis) + y0 = m_height - y0 - 1; - // Begin drawing at the left most point of the circle and draw a line to the right most point of the circle - for(int32_t x = -radius; x <= radius; ++x){ + // Begin drawing at the left most point of the circle and draw a line to the right most point of the circle + for (int32_t x = -radius; x <= radius; ++x) { - // Draw a line from the top most point of the circle to the bottom most point of the circle - for(int32_t y = -radius; y <= radius; ++y){ + // Draw a line from the top most point of the circle to the bottom most point of the circle + for (int32_t y = -radius; y <= radius; ++y) { - // If the point is within the circle, draw it but make sure it is only part of the outline - if(x*x + y*y <= radius*radius && x*x + y*y >= (radius-1)*(radius-1)) - putPixel(x0+x,y0+y,colour); - } - } + // If the point is within the circle, draw it but make sure it is only part of the outline + if (x * x + y * y <= radius * radius && x * x + y * y >= (radius - 1) * (radius - 1)) + putPixel(x0 + x, y0 + y, colour); + } + } } @@ -704,8 +686,8 @@ void GraphicsContext::draw_circle(int32_t x0, int32_t y0, int32_t radius, uint32 * @param radius The radius of the circle * @param colour The colour of the circle */ -void GraphicsContext::fill_circle(int32_t x0, int32_t y0, int32_t radius, const Colour& colour) { - fillCircle(x0,y0,radius, colour_to_int(colour)); +void GraphicsContext::fill_circle(int32_t x0, int32_t y0, int32_t radius, const Colour &colour) { + fillCircle(x0, y0, radius, colour_to_int(colour)); } @@ -719,29 +701,29 @@ void GraphicsContext::fill_circle(int32_t x0, int32_t y0, int32_t radius, const */ void GraphicsContext::fillCircle(int32_t x0, int32_t y0, int32_t radius, uint32_t colour) { - // Make sure the circle is with in the width and height of the screen - if(x0 < 0) x0 = 0; - if(x0 > m_width) x0 = m_width; - if(y0 < 0) y0 = 0; - if(y0 > m_height) y0 = m_height; + // Make sure the circle is with in the width and height of the screen + if (x0 < 0) x0 = 0; + if (x0 > m_width) x0 = m_width; + if (y0 < 0) y0 = 0; + if (y0 > m_height) y0 = m_height; - // Mirror the Y axis as directly calling put_pixel will not do this - if(mirror_y_axis) - y0 = m_height -y0-1; + // Mirror the Y axis as directly calling put_pixel will not do this + if (mirror_y_axis) + y0 = m_height - y0 - 1; - // Draw the circle + // Draw the circle - // Begin drawing at the left most point of the circle and draw a line to the right most point of the circle - for(int32_t x = -radius; x <= radius; ++x){ + // Begin drawing at the left most point of the circle and draw a line to the right most point of the circle + for (int32_t x = -radius; x <= radius; ++x) { - // Draw a line from the top most point of the circle to the bottom most point of the circle - for(int32_t y = -radius; y <= radius; ++y){ + // Draw a line from the top most point of the circle to the bottom most point of the circle + for (int32_t y = -radius; y <= radius; ++y) { - // Only draw the pixel if it is within the circle - if(x*x + y*y <= radius*radius) - putPixel(x0+x,y0+y,colour); - } - } + // Only draw the pixel if it is within the circle + if (x * x + y * y <= radius * radius) + putPixel(x0 + x, y0 + y, colour); + } + } } /** @@ -749,6 +731,6 @@ void GraphicsContext::fillCircle(int32_t x0, int32_t y0, int32_t radius, uint32_ * * @return The framebuffer address */ -uint64_t* GraphicsContext::framebuffer_address() { - return m_framebuffer_address; +uint64_t *GraphicsContext::framebuffer_address() { + return m_framebuffer_address; } diff --git a/kernel/src/common/logger.cpp b/kernel/src/common/logger.cpp index 57248d57..e1ed8b35 100644 --- a/kernel/src/common/logger.cpp +++ b/kernel/src/common/logger.cpp @@ -5,19 +5,20 @@ #include #include #include +#include using namespace MaxOS; using namespace MaxOS::common; using namespace MaxOS::drivers::console; Logger::Logger() -:m_log_writers() +: m_log_writers() { - s_active_logger = this; + s_active_logger = this; - // The following line is generated automatically by the MaxOS build system. - s_progress_total = 24; + // The following line is generated automatically by the MaxOS build system. + s_progress_total = 24; } @@ -32,20 +33,20 @@ Logger::~Logger() { * * @param output_stream The output stream to add */ -void Logger::add_log_writer(OutputStream* log_writer) { +void Logger::add_log_writer(OutputStream *log_writer) { - // If the list is not empty - if(m_log_writer_count >= m_max_log_writers) - return; + // If the list is not empty + if (m_log_writer_count >= m_max_log_writers) + return; - // Add the output stream to the list - m_log_writers[m_log_writer_count] = log_writer; - m_log_writers_enabled[m_log_writer_count] = true; - m_log_writer_count++; + // Add the output stream to the list + m_log_writers[m_log_writer_count] = log_writer; + m_log_writers_enabled[m_log_writer_count] = true; + m_log_writer_count++; - // Print the setup info - *this << LogLevel::INFO << "Logger setup: " << m_log_writer_count << "\n"; - *this << LogLevel::HEADER << "MaxOS v" << VERSION_STRING << " [ build " << BUILD_NUMBER << " ]\n"; + // Print the setup info + *this << LogLevel::INFO << "Logger setup: " << m_log_writer_count << "\n"; + *this << LogLevel::HEADER << "MaxOS v" << VERSION_STRING << " [ build " << BUILD_NUMBER << " ]\n"; } @@ -54,16 +55,16 @@ void Logger::add_log_writer(OutputStream* log_writer) { * * @param output_stream The output stream to remove */ -void Logger::disable_log_writer(OutputStream* log_writer) { +void Logger::disable_log_writer(OutputStream *log_writer) { - // If the list is empty - if(m_log_writer_count == 0) - return; + // If the list is empty + if (m_log_writer_count == 0) + return; - // Find the output stream in the list - for(int i = 0; i < m_log_writer_count; i++) - if(m_log_writers[i] == log_writer) - m_log_writers_enabled[i] = false; + // Find the output stream in the list + for (int i = 0; i < m_log_writer_count; i++) + if (m_log_writers[i] == log_writer) + m_log_writers_enabled[i] = false; } /** @@ -73,38 +74,39 @@ void Logger::disable_log_writer(OutputStream* log_writer) { */ void Logger::set_log_level(LogLevel log_level) { - // Set the log level - m_log_level = log_level; + // Set the log level + m_log_level = log_level; - // Update the progress bar - if(log_level == LogLevel::INFO){ - VESABootConsole::update_progress_bar((m_progress_current * 100) / s_progress_total); - m_progress_current++; - } + // Update the progress bar + if (log_level == LogLevel::INFO) { + VESABootConsole::update_progress_bar((m_progress_current * 100) / s_progress_total); + m_progress_current++; + } - // Print the header - switch (log_level) { + // Print the header + switch (log_level) { - case LogLevel::HEADER: - *this << ANSI_COLOURS[ANSIColour::FG_Blue] << "[ BOOT ] "; - break; + case LogLevel::HEADER: + *this << ANSI_COLOURS[ANSIColour::FG_Blue] << "[ BOOT ] "; + break; - case LogLevel::INFO: - *this << ANSI_COLOURS[ANSIColour::FG_Cyan] << "[ INFO ]" << ANSI_COLOURS[ANSIColour::FG_White] << " "; - break; + case LogLevel::INFO: + *this << ANSI_COLOURS[ANSIColour::FG_Cyan] << "[ INFO ]" << ANSI_COLOURS[ANSIColour::FG_White] << " "; + break; - case LogLevel::DEBUG: - *this << ANSI_COLOURS[ANSIColour::FG_Yellow] << "[ DEBUG ]" << ANSI_COLOURS[ANSIColour::Reset] << " "; - break; + case LogLevel::DEBUG: + *this << ANSI_COLOURS[ANSIColour::FG_Yellow] << "[ DEBUG ]" << ANSI_COLOURS[ANSIColour::Reset] << " "; + break; - case LogLevel::WARNING: - *this << ANSI_COLOURS[ANSIColour::BG_Yellow] << ANSI_COLOURS[FG_White] << "[ WARNING ]" << ANSI_COLOURS[ANSIColour::Reset] << " "; - break; + case LogLevel::WARNING: + *this << ANSI_COLOURS[ANSIColour::BG_Yellow] << ANSI_COLOURS[FG_White] << "[ WARNING ]" + << ANSI_COLOURS[ANSIColour::Reset] << " "; + break; - case LogLevel::ERROR: - *this << ANSI_COLOURS[ANSIColour::BG_Red] << "[ ERROR ]" << ANSI_COLOURS[ANSIColour::Reset] << " "; - break; - } + case LogLevel::ERROR: + *this << ANSI_COLOURS[ANSIColour::BG_Red] << "[ ERROR ]" << ANSI_COLOURS[ANSIColour::Reset] << " "; + break; + } } @@ -115,69 +117,70 @@ void Logger::set_log_level(LogLevel log_level) { */ void Logger::write_char(char c) { - // Ensure logging at this level is enabled - if(m_log_level > s_max_log_level) - return; + // Ensure logging at this level is enabled + if (m_log_level > s_max_log_level) + return; - // Write the character to all output streams - for(int i = 0; i < m_log_writer_count; i++) - if(m_log_writers_enabled[i] || m_log_level == LogLevel::ERROR) - m_log_writers[i]->write_char(c); + // Write the character to all output streams + for (int i = 0; i < m_log_writer_count; i++) + if (m_log_writers_enabled[i] || m_log_level == LogLevel::ERROR) + m_log_writers[i]->write_char(c); } /** * @brief Gets the active logger + * * @return The active logger */ -Logger& Logger::Out() { - return *active_logger(); +Logger &Logger::Out() { + return *active_logger(); } /** * @brief Gets active logger set to task level + * * @return The task logger */ -Logger Logger::HEADER(){ - - s_active_logger->set_log_level(LogLevel::HEADER); +Logger Logger::HEADER() { - return Out(); + s_active_logger->set_log_level(LogLevel::HEADER); + return Out(); } /** * @brief Gets active logger set to info level + * * @return The info logger */ Logger Logger::INFO() { - s_active_logger->set_log_level(LogLevel::INFO); - - return Out(); + s_active_logger->set_log_level(LogLevel::INFO); + return Out(); } /** * @brief Gets active logger set to DEBUG level + * * @return The debug logger */ Logger Logger::DEBUG() { - s_active_logger->set_log_level(LogLevel::DEBUG); - - return Out(); + s_active_logger->set_log_level(LogLevel::DEBUG); + return Out(); } /** * @brief Gets active logger set to WARNING level + * * @return The warning logger */ Logger Logger::WARNING() { - s_active_logger->set_log_level(LogLevel::WARNING); - - return Out(); + s_active_logger->set_log_level(LogLevel::WARNING); + return Out(); } /** @@ -187,9 +190,8 @@ Logger Logger::WARNING() { */ Logger Logger::ERROR() { - s_active_logger->set_log_level(LogLevel::ERROR); - - return Out(); + s_active_logger->set_log_level(LogLevel::ERROR); + return Out(); } @@ -198,8 +200,8 @@ Logger Logger::ERROR() { * * @return The active logger */ -Logger* Logger::active_logger() { - return s_active_logger; +Logger *Logger::active_logger() { + return s_active_logger; } /** @@ -210,65 +212,62 @@ Logger* Logger::active_logger() { */ void Logger::printf(char const *format, ...) { - // Create a pointer to the data - va_list parameters; - va_start(parameters, format); - - - // Loop through the format string - for (; *format != '\0'; format++) - { - - // If it is not a %, print the character - if (*format != '%') - { - write_char(*format); - continue; - } - - // Move to the next character - format++; - - switch (*format) - { - case 'd': - { - // Print a decimal - int number = va_arg (parameters, int); - write_int(number); - break; - } - case 'x': - { - // Print a hex - uint64_t number = va_arg (parameters, uint64_t ); - write_hex(number); - break; - } - case 's': - { - // Print a string - char* str = va_arg (parameters, char*); - write(str); - break; - } - } - } + // Create a pointer to the data + va_list parameters; + va_start(parameters, format); + + // Loop through the format string + for (; *format != '\0'; format++) { + + // If it is not a %, print the character + if (*format != '%') { + write_char(*format); + continue; + } + + // Move to the next character + format++; + switch (*format) { + case 'd': { + // Print a decimal + int number = va_arg (parameters, int); + write_int(number); + break; + } + case 'x': { + // Print a hex + uint64_t number = va_arg (parameters, uint64_t); + write_hex(number); + break; + } + case 's': { + // Print a string + char *str = va_arg (parameters, char*); + write(str); + break; + } + } + } } -#include +/** + * @brief Puts the system into a panic state if the condition is false, printing the message first + * + * @param condition The condition to check if is met + * @param message The message to print if the condition fails + */ void Logger::ASSERT(bool condition, char const *message, ...) { - // If the condition is met then everything is ok - if(condition) - return; + // If the condition is met then everything is ok + if (condition) + return; - // Print the message - s_active_logger -> set_log_level(LogLevel::ERROR); - s_active_logger -> printf(message); + // Print the message + s_active_logger->set_log_level(LogLevel::ERROR); + s_active_logger->printf(message); - // Hang the system - system::CPU::PANIC("Check previous logs for more information"); + // Hang the system + system::CPU::PANIC("Check previous logs for more information"); } /** @@ -277,10 +276,9 @@ void Logger::ASSERT(bool condition, char const *message, ...) { * @param log_level The log level to set * @return This logger */ -Logger& Logger::operator << (LogLevel log_level) { - - set_log_level(log_level); +Logger &Logger::operator<<(LogLevel log_level) { - return *this; + set_log_level(log_level); + return *this; } \ No newline at end of file diff --git a/kernel/src/common/outputStream.cpp b/kernel/src/common/outputStream.cpp index 214242dd..e8f0cfa3 100644 --- a/kernel/src/common/outputStream.cpp +++ b/kernel/src/common/outputStream.cpp @@ -20,8 +20,7 @@ OutputStream::~OutputStream() = default; */ void OutputStream::lineFeed() { - // write the text representation of a newline to the output stream. - write_char('\n'); + write_char('\n'); } @@ -30,8 +29,8 @@ void OutputStream::lineFeed() { */ void OutputStream::carriageReturn() { - // write the text representation of a carriage return to the output stream. - write_char('\r'); + // write the text representation of a carriage return to the output stream. + write_char('\r'); } @@ -49,7 +48,7 @@ void OutputStream::clear() { */ void OutputStream::write(string string_to_write) { - write(string_to_write.c_str()); + write(string_to_write.c_str()); } /** @@ -57,44 +56,31 @@ void OutputStream::write(string string_to_write) { * * @param string_to_write The string to write to the output stream. */ -void OutputStream::write(const char* string_to_write){ +void OutputStream::write(const char *string_to_write) { - // Loop through the string - int i = 0; - while (string_to_write[i] != '\0') { + int i = 0; + while (string_to_write[i] != '\0') { - // Switch on the current character - switch (string_to_write[i]) { + switch (string_to_write[i]) { - // If the current character is a newline - case '\n': + case '\n': + lineFeed(); + break; - // write a newline to the output stream - lineFeed(); - break; + case '\r': + carriageReturn(); + break; - // If the current character is a carriage return - case '\r': + case '\0': + return; - // write a carriage return to the output stream - carriageReturn(); - break; + default: + write_char(string_to_write[i]); + break; - - // If the current character is a null terminator - case '\0': - return; - - // If the current character is any other character - default: - - // write the current character to the output stream - write_char(string_to_write[i]); - break; - - } - i++; - } + } + i++; + } } /** @@ -113,7 +99,7 @@ void OutputStream::write_char(char) { */ void OutputStream::write_int(int int_to_write) { - write(itoa(10, int_to_write)); + write(itoa(10, int_to_write)); } @@ -124,23 +110,20 @@ void OutputStream::write_int(int int_to_write) { */ void OutputStream::write_hex(uint64_t hex_to_write) { - write(htoa(hex_to_write)); + write(htoa(hex_to_write)); } /** - * @brief Writes a interger to the output stream. + * @brief Writes a integer to the output stream. * * @param int_to_write The integer to write to the output stream. * @return The output stream. */ -OutputStream &OutputStream::operator << (int int_to_write) { - - // Call the writeInt function to write the integer to the output stream - write_int(int_to_write); +OutputStream &OutputStream::operator<<(int int_to_write) { - // Return the output stream - return *this; + write_int(int_to_write); + return *this; } /** @@ -149,13 +132,10 @@ OutputStream &OutputStream::operator << (int int_to_write) { * @param hex_to_write The hex to write to the output stream. * @return The output stream. */ -OutputStream &OutputStream::operator << (uint64_t hex_to_write) { - - // Call the write_hex function to write the hex to the output stream - write_hex(hex_to_write); +OutputStream &OutputStream::operator<<(uint64_t hex_to_write) { - // Return the output stream - return *this; + write_hex(hex_to_write); + return *this; } /** @@ -164,13 +144,10 @@ OutputStream &OutputStream::operator << (uint64_t hex_to_write) { * @param string_to_write The string to write to the output stream. * @return The output stream. */ -OutputStream &OutputStream::operator << (string string_to_write) { +OutputStream &OutputStream::operator<<(string string_to_write) { - // Call the write function to write the string to the output stream - write(string_to_write); - - // Return the output stream - return *this; + write(string_to_write); + return *this; } /** @@ -181,11 +158,8 @@ OutputStream &OutputStream::operator << (string string_to_write) { */ OutputStream &OutputStream::operator<<(char char_to_write) { - // Call the writeChar function to write the character to the output stream - write_char(char_to_write); - - // Return the output stream - return *this; + write_char(char_to_write); + return *this; } /** @@ -196,10 +170,6 @@ OutputStream &OutputStream::operator<<(char char_to_write) { */ OutputStream &OutputStream::operator<<(char const *string_to_write) { - // Call the write function to write the string to the output stream - write(string_to_write); - - // Return the output stream - return *this; -} - + write(string_to_write); + return *this; +} \ No newline at end of file diff --git a/kernel/src/common/spinlock.cpp b/kernel/src/common/spinlock.cpp index f6f3f39b..f8aecafb 100644 --- a/kernel/src/common/spinlock.cpp +++ b/kernel/src/common/spinlock.cpp @@ -21,8 +21,8 @@ Spinlock::~Spinlock() = default; */ void Spinlock::lock() { - acquire(); - m_locked = true; + acquire(); + m_locked = true; } /** @@ -30,9 +30,8 @@ void Spinlock::lock() { */ void Spinlock::unlock() { - m_locked = false; - release(); - + m_locked = false; + release(); } /** @@ -41,7 +40,7 @@ void Spinlock::unlock() { * @return True if the spinlock is locked, false otherwise */ bool Spinlock::is_locked() const { - return m_locked; + return m_locked; } @@ -49,18 +48,18 @@ bool Spinlock::is_locked() const { * @brief Acquire the spinlock: wait until the lock is available, yielding if desired until that happens. */ void Spinlock::acquire() { - while (__atomic_test_and_set(&m_locked, __ATOMIC_ACQUIRE)) { + while (__atomic_test_and_set(&m_locked, __ATOMIC_ACQUIRE)) { - // Wait for the lock to be available - if(m_should_yield) - Scheduler::system_scheduler()->yield(); + // Wait for the lock to be available + if (m_should_yield) + Scheduler::system_scheduler()->yield(); - } + } } /** * @brief Release the spinlock */ void Spinlock::release() { - __atomic_clear(&m_locked, __ATOMIC_RELEASE); + __atomic_clear(&m_locked, __ATOMIC_RELEASE); } diff --git a/kernel/src/common/string.cpp b/kernel/src/common/string.cpp index 2e8d4ff0..0b14547a 100644 --- a/kernel/src/common/string.cpp +++ b/kernel/src/common/string.cpp @@ -5,69 +5,66 @@ using namespace MaxOS; -String::String() -{ +String::String() { - // String that only contains the null terminator - allocate_self(); - m_string[0] = '\0'; - m_length = 0; + // String that only contains the null terminator + allocate_self(); + m_string[0] = '\0'; + m_length = 0; } String::String(char c) { - // Create the mem - m_length = 1; - allocate_self(); + // Create the memory + m_length = 1; + allocate_self(); - // Store the char - m_string[0] = c; - m_string[m_length] = '\0'; + // Store the char + m_string[0] = c; + m_string[m_length] = '\0'; } -String::String(char const *string) -{ +String::String(char const *string) { - // Get the length of the string, prevent longer than 10000 because this should mean somethings gone wrong - m_length = 0; - while (string[m_length] != '\0' && m_length <= 10000) - m_length++; - allocate_self(); + // Get the length of the string, prevent longer than 10000 because this should mean something's gone wrong + m_length = 0; + while (string[m_length] != '\0' && m_length <= 10000) + m_length++; + allocate_self(); - // Copy the string - for (int i = 0; i < m_length; i++) - m_string[i] = string[i]; + // Copy the string + for (int i = 0; i < m_length; i++) + m_string[i] = string[i]; - // If the length is more than 10,000 Replace the end with a warning incase future use actually requires that - const char* warning = "MAXOS: String length exceeded 10000 - might be a bug"; - if(m_length > 10000) - for (int i = 0; i < 52; i++) - m_string[m_length - 52 + i] = warning[i]; + // If the length is more than 10,000 Replace the end with a warning incase future use actually requires that + const char *warning = "MAXOS: String length exceeded 10000 - might be a bug"; + if (m_length > 10000) + for (int i = 0; i < 52; i++) + m_string[m_length - 52 + i] = warning[i]; - m_string[m_length] = '\0'; + m_string[m_length] = '\0'; } -String::String(uint8_t const* string, int length) -{ - // Allocate memory for the string (and null terminator) - m_length = length; - allocate_self(); +String::String(uint8_t const *string, int length) { + // Allocate memory for the string (and null terminator) + m_length = length; + allocate_self(); - // Copy the string - for (int i = 0; i < length; i++) - m_string[i] = string[i]; + // Copy the string + for (int i = 0; i < length; i++) + m_string[i] = string[i]; - // Write the null terminator - m_string[length] = '\0'; + // Write the null terminator + m_string[length] = '\0'; } String::String(int value) { // Convert to a string - const char* str = itoa(10, value); + const char *str = itoa(10, value); m_length = strlen(str); // Create space to store @@ -82,12 +79,13 @@ String::String(int value) { /** * @brief Constructs a string from a hex value (Excludes 0x____) + * * @param value */ String::String(uint64_t value) { // Convert to a string - const char* str = htoa(value); + const char *str = htoa(value); m_length = strlen(str); // Create space to store @@ -102,7 +100,7 @@ String::String(uint64_t value) { String::String(float value) { // Convert to a string - const char* str = ftoa(value); + const char *str = ftoa(value); m_length = strlen(str); // Create space to store @@ -121,15 +119,15 @@ String::String(float value) { * @param other String to copy from */ String::String(String const &other) { - copy(other); + copy(other); } String::~String() { - // Free the memory - if(!m_using_small) - delete[] m_string; + // Free the memory + if (!m_using_small) + delete[] m_string; } @@ -140,16 +138,16 @@ String::~String() { */ void String::copy(String const &other) { - // Allocate memory for the string (and null terminator) - m_length = other.length(); - allocate_self(); + // Allocate memory for the string (and null terminator) + m_length = other.length(); + allocate_self(); - // Copy the string - for (int i = 0; i < m_length; i++) - m_string[i] = other[i]; + // Copy the string + for (int i = 0; i < m_length; i++) + m_string[i] = other[i]; - // Write the null terminator - m_string[m_length] = '\0'; + // Write the null terminator + m_string[m_length] = '\0'; } @@ -161,26 +159,26 @@ void String::copy(String const &other) { */ int String::lex_value(String const &string) { - // Sum the ascii values of the characters in the string - int sum = 0; - for (int i = 0; i < string.length(); i++) - sum += string[i]; + // Sum the ascii values of the characters in the string + int sum = 0; + for (int i = 0; i < string.length(); i++) + sum += string[i]; - return sum; + return sum; } /** * @brief Allocates memory for the string */ -void String::allocate_self() -{ +void String::allocate_self() { + // Clear the old buffer if in use - if(m_string && !m_using_small) + if (m_string && !m_using_small) delete[] m_string; // Try to use the small string buffer m_using_small = m_length + 1 <= s_small_storage; - m_string = m_using_small ? m_small_string : new char[m_length + 1]; + m_string = m_using_small ? m_small_string : new char[m_length + 1]; } @@ -191,18 +189,15 @@ void String::allocate_self() * @param other The string for this one to be updated to * @return String The string */ -String &String::operator = (String const &other) { - - // Self assignment check - if (this == &other) - return *this; - - // Copy the other string - copy(other); +String &String::operator=(String const &other) { - // Return the string - return *this; + // Self assignment check + if (this == &other) + return *this; + // Copy the other string + copy(other); + return *this; } /** @@ -210,10 +205,9 @@ String &String::operator = (String const &other) { * * @return The char* string */ -char* String::c_str() { - - return m_string; +char *String::c_str() { + return m_string; } /** @@ -221,10 +215,9 @@ char* String::c_str() { * * @return The string as an array of characters */ -const char* String::c_str() const { - - return m_string; +const char *String::c_str() const { + return m_string; } /** @@ -233,20 +226,19 @@ const char* String::c_str() const { * @param other The other string * @return True if the string starts with the other string, false otherwise */ -bool String::starts_with(String const& other) -{ +bool String::starts_with(String const &other) { - // Must at least be able to fit the other string - if (m_length < other.length()) - return false; + // Must at least be able to fit the other string + if (m_length < other.length()) + return false; - // Check if the string starts with the other string - for (int i = 0; i < other.length(); i++) - if (m_string[i] != other[i]) - return false; + // Check if the string starts with the other string + for (int i = 0; i < other.length(); i++) + if (m_string[i] != other[i]) + return false; - // No string left over to check so it must contain other - return true; + // No string left over to check so it must contain other + return true; } /** @@ -256,30 +248,29 @@ bool String::starts_with(String const& other) * @param length The length of the substring * @return The substring or empty string if out of bounds */ -String String::substring(int start, int length) const -{ +String String::substring(int start, int length) const { - // Ensure the start is within bounds - if (start < 0 || start >= m_length) - return {}; + // Ensure the start is within bounds + if (start < 0 || start >= m_length) + return {}; - // Ensure the length is within bounds - if (length < 0 || start + length > m_length) - return {}; + // Ensure the length is within bounds + if (length < 0 || start + length > m_length) + return {}; - // Allocate memory for the substring (and null terminator) - String substring; - substring.m_length = length; - substring.allocate_self(); + // Allocate memory for the substring (and null terminator) + String substring; + substring.m_length = length; + substring.allocate_self(); - // Copy the substring - for (int i = 0; i < length; i++) - substring.m_string[i] = m_string[start + i]; + // Copy the substring + for (int i = 0; i < length; i++) + substring.m_string[i] = m_string[start + i]; - // Write the null terminator - substring.m_string[length] = '\0'; + // Write the null terminator + substring.m_string[length] = '\0'; - return substring; + return substring; } /** @@ -288,28 +279,27 @@ String String::substring(int start, int length) const * @param delimiter What to split the string by * @return A vector of strings that were split by the delimiter */ -common::Vector String::split(String const& delimiter) const -{ - common::Vector strings; +common::Vector String::split(String const &delimiter) const { + common::Vector strings; - // Go through the string and split it by the delimiter - int start = 0; - int end = 0; - for ( ; end < m_length; end++) { + // Go through the string and split it by the delimiter + int start = 0; + int end = 0; + for (; end < m_length; end++) { - // Not splitting - if (m_string[end] != delimiter[0]) - continue; + // Not splitting + if (m_string[end] != delimiter[0]) + continue; - // Add the splice of the string - strings.push_back(substring(start, end - start)); - start = end + 1; - } + // Add the splice of the string + strings.push_back(substring(start, end - start)); + start = end + 1; + } - // Add the last string to the vector - strings.push_back(substring(start, end - start)); + // Add the last string to the vector + strings.push_back(substring(start, end - start)); - return strings; + return strings; } /** @@ -320,27 +310,27 @@ common::Vector String::split(String const& delimiter) const */ int String::length(bool count_ansi) const { - // If ansi characters are not to be counted - if (count_ansi) - return m_length; + // If ansi characters are not to be counted + if (count_ansi) + return m_length; - // Calculate the length of the string without ansi characters - int total_length = 0; - int clean_length = 0; - while (m_string[total_length] != '\0'){ + // Calculate the length of the string without ansi characters + int total_length = 0; + int clean_length = 0; + while (m_string[total_length] != '\0') { - // If the character is an ansi character, skip it - if (m_string[total_length] == '\033') - while (m_string[total_length] != 'm') - total_length++; + // If the character is an ansi character, skip it + if (m_string[total_length] == '\033') + while (m_string[total_length] != 'm') + total_length++; - // Increment the length - clean_length++; - total_length++; - } + // Increment the length + clean_length++; + total_length++; + } - // Return the length - return clean_length; + // Return the length + return clean_length; } /** @@ -351,17 +341,17 @@ int String::length(bool count_ansi) const { */ bool String::equals(String const &other) const { - // Check if the lengths are equal - if (m_length != other.length()) - return false; + // Check if the lengths are equal + if (m_length != other.length()) + return false; - // Check if the characters are equal - for (int i = 0; i < m_length; i++) - if (m_string[i] != other[i]) - return false; + // Check if the characters are equal + for (int i = 0; i < m_length; i++) + if (m_string[i] != other[i]) + return false; - // The strings are equal - return true; + // The strings are equal + return true; } @@ -371,11 +361,10 @@ bool String::equals(String const &other) const { * @param other The other string * @return True if the strings are equal, false otherwise */ -bool String::operator == (String const &other) const { - - // Check if the strings are equal - return equals(other); +bool String::operator==(String const &other) const { + // Check if the strings are equal + return equals(other); } /** @@ -384,15 +373,13 @@ bool String::operator == (String const &other) const { * @param other The other string * @return True if the strings are not equal, false otherwise */ -bool String::operator != (String const &other) const { - - // Self assignment check - if (*this == other) - return false; - +bool String::operator!=(String const &other) const { - return !equals(other); + // Self assignment check + if (*this == other) + return false; + return !equals(other); } /** @@ -401,9 +388,9 @@ bool String::operator != (String const &other) const { * @param other The other string * @return True if the string is less than the other, false otherwise */ -bool String::operator < (String const &other) const { +bool String::operator<(String const &other) const { - return lex_value(*this) < lex_value(other); + return lex_value(*this) < lex_value(other); } @@ -413,9 +400,9 @@ bool String::operator < (String const &other) const { * @param other The other string * @return True if the string is greater than the other, false otherwise */ -bool String::operator > (String const &other) const { +bool String::operator>(String const &other) const { - return lex_value(*this) > lex_value(other); + return lex_value(*this) > lex_value(other); } @@ -425,9 +412,9 @@ bool String::operator > (String const &other) const { * @param other The other string * @return True if the string is less than or equal to the other, false otherwise */ -bool String::operator <= (String const &other) const { +bool String::operator<=(String const &other) const { - return lex_value(*this) <= lex_value(other); + return lex_value(*this) <= lex_value(other); } @@ -437,9 +424,9 @@ bool String::operator <= (String const &other) const { * @param other The other string * @return True if the string is greater than or equal to the other, false otherwise */ -bool String::operator >= (String const &other) const { +bool String::operator>=(String const &other) const { - return lex_value(*this) >= lex_value(other); + return lex_value(*this) >= lex_value(other); } @@ -449,26 +436,26 @@ bool String::operator >= (String const &other) const { * @param other The other string * @return The concatenated string */ -String String::operator + (String const &other) const { +String String::operator+(String const &other) const { - // The concatenated string - String concatenated; - concatenated.m_length = m_length + other.length(); - concatenated.allocate_self(); + // The concatenated string + String concatenated; + concatenated.m_length = m_length + other.length(); + concatenated.allocate_self(); - // Copy the first string - for (int i = 0; i < m_length; i++) - concatenated.m_string[i] = m_string[i]; + // Copy the first string + for (int i = 0; i < m_length; i++) + concatenated.m_string[i] = m_string[i]; - // Copy the second string - for(int i = 0; i < other.length(); i++) - concatenated.m_string[m_length + i] = other[i]; + // Copy the second string + for (int i = 0; i < other.length(); i++) + concatenated.m_string[m_length + i] = other[i]; - // Write the null terminator - concatenated.m_string[concatenated.m_length] = '\0'; + // Write the null terminator + concatenated.m_string[concatenated.m_length] = '\0'; - // Return the concatenated string - return concatenated; + // Return the concatenated string + return concatenated; } /** @@ -477,12 +464,12 @@ String String::operator + (String const &other) const { * @param other The other string * @return The concatenated string */ -String &String::operator += (String const &other) { +String &String::operator+=(String const &other) { - // Add the other string to this string - String concatenated = *this + other; - copy(concatenated); - return *this; + // Add the other string to this string + String concatenated = *this + other; + copy(concatenated); + return *this; } @@ -492,8 +479,8 @@ String &String::operator += (String const &other) { * @param index The index of the character * @return The character at the specified index */ -char& String::operator[](int index) { - return m_string[index]; +char &String::operator[](int index) { + return m_string[index]; } @@ -503,8 +490,8 @@ char& String::operator[](int index) { * @param index The index of the character * @return The character at the specified index */ -char& String::operator[](int index) const { - return m_string[index]; +char &String::operator[](int index) const { + return m_string[index]; } /** @@ -515,21 +502,21 @@ char& String::operator[](int index) const { */ String String::operator*(int times) const { - // The repeated string - String repeated; - repeated.m_length = m_length * times; - repeated.allocate_self(); + // The repeated string + String repeated; + repeated.m_length = m_length * times; + repeated.allocate_self(); - // Copy the string - for (int i = 0; i < times; i++) - for (int j = 0; j < m_length; j++) - repeated.m_string[i * m_length + j] = m_string[j]; + // Copy the string + for (int i = 0; i < times; i++) + for (int j = 0; j < m_length; j++) + repeated.m_string[i * m_length + j] = m_string[j]; - // Write the null terminator - repeated.m_string[repeated.m_length] = '\0'; + // Write the null terminator + repeated.m_string[repeated.m_length] = '\0'; - // Return the repeated string - return repeated; + // Return the repeated string + return repeated; } @@ -542,30 +529,30 @@ String String::operator*(int times) const { */ String String::center(int width, char fill) const { - // The number of characters to add - int add = (width - m_length) / 2; + // The number of characters to add + int add = (width - m_length) / 2; - // The centered string - String centered; - centered.m_length = width; - centered.allocate_self(); + // The centered string + String centered; + centered.m_length = width; + centered.allocate_self(); - // Fill the right side (before) - for (int i = 0; i < add; i++) - centered.m_string[i] = fill; + // Fill the right side (before) + for (int i = 0; i < add; i++) + centered.m_string[i] = fill; - // Copy the string (middle) - for (int i = 0; i < m_length; i++) - centered.m_string[add + i] = m_string[i]; + // Copy the string (middle) + for (int i = 0; i < m_length; i++) + centered.m_string[add + i] = m_string[i]; - // Fill the left side (after) - for (int i = add + m_length; i < width; i++) - centered.m_string[i] = fill; + // Fill the left side (after) + for (int i = add + m_length; i < width; i++) + centered.m_string[i] = fill; - // Write the null terminator - centered.m_string[width] = '\0'; + // Write the null terminator + centered.m_string[width] = '\0'; - return centered; + return centered; } /** @@ -576,21 +563,21 @@ String String::center(int width, char fill) const { */ String String::strip(char strip_char) const { - // The stripped string - String stripped; - stripped.copy(*this); + // The stripped string + String stripped; + stripped.copy(*this); - // Search from the back for the earliest non-whitespace character - int end = m_length - 1; - while (end >= 0 && (m_string[end] == strip_char || m_string[end] == '\n' || m_string[end] == '\t')) - end--; + // Search from the back for the earliest non-whitespace character + int end = m_length - 1; + while (end >= 0 && (m_string[end] == strip_char || m_string[end] == '\n' || m_string[end] == '\t')) + end--; - // Make sure there is something to strip - if (end < 0) - return stripped; + // Make sure there is something to strip + if (end < 0) + return stripped; - // Split the string to remove the end - return stripped.substring(0, end + 1); + // Split the string to remove the end + return stripped.substring(0, end + 1); } /** @@ -599,11 +586,10 @@ String String::strip(char strip_char) const { * @param str The string to get the length of * @return The length of the string */ -int strlen(const char* str) -{ - int len = 0; - for (; str[len] != '\0'; len++); - return len; +int strlen(const char *str) { + int len = 0; + for (; str[len] != '\0'; len++); + return len; } /** @@ -615,32 +601,29 @@ int strlen(const char* str) * * @return The converted string */ -char* itoa(int base, int64_t number) -{ +char *itoa(int base, int64_t number) { - // If there is no buffer use a default buffer - static char buffer[50] = {0}; + // If there is no buffer use a default buffer + static char buffer[50] = {0}; - int i = 49; - bool isNegative = number < 0; + int i = 49; + bool isNegative = number < 0; - if (number == 0) - { - buffer[i] = '0'; - return &buffer[i]; - } + if (number == 0) { + buffer[i] = '0'; + return &buffer[i]; + } - for (; number && i; --i, number /= base) - buffer[i] = "0123456789ABCDEF"[number % base]; + for (; number && i; --i, number /= base) + buffer[i] = "0123456789ABCDEF"[number % base]; - if (isNegative) - { - buffer[i] = '-'; - return &buffer[i]; - } + if (isNegative) { + buffer[i] = '-'; + return &buffer[i]; + } - return &buffer[i + 1]; + return &buffer[i + 1]; } /** @@ -650,22 +633,20 @@ char* itoa(int base, int64_t number) * @param buffer The buffer to store the converted string * @return The converted string */ -char* htoa(uint64_t number) -{ - // If there is no buffer use a default buffer - static char buffer[50] = {0}; - int i = 49; +char *htoa(uint64_t number) { + // If there is no buffer use a default buffer + static char buffer[50] = {0}; + int i = 49; - if (number == 0) - { - buffer[i] = '0'; - return &buffer[i]; - } + if (number == 0) { + buffer[i] = '0'; + return &buffer[i]; + } - for (; number && i; --i, number /= 16) - buffer[i] = "0123456789ABCDEF"[number % 16]; + for (; number && i; --i, number /= 16) + buffer[i] = "0123456789ABCDEF"[number % 16]; - return &buffer[i + 1]; + return &buffer[i + 1]; } /** @@ -675,65 +656,65 @@ char* htoa(uint64_t number) * @param buffer The buffer to store the converted string * @return The converted string */ -char* ftoa(float number) { +char *ftoa(float number) { - static char buffer[50]; - char* ptr = buffer; + static char buffer[50]; + char *ptr = buffer; - // Handle negative numbers. - if (number < 0) { - *ptr++ = '-'; - number = -number; - } + // Handle negative numbers. + if (number < 0) { + *ptr++ = '-'; + number = -number; + } - // Separate integer and fractional parts. - int64_t intPart = (int64_t)number; - float fraction = number - (float)intPart; + // Separate integer and fractional parts. + int64_t intPart = (int64_t) number; + float fraction = number - (float) intPart; - // Convert integer part to string using itoa. - char* intStr = itoa(10, intPart); - while (*intStr) { - *ptr++ = *intStr++; - } + // Convert integer part to string using itoa. + char *intStr = itoa(10, intPart); + while (*intStr) { + *ptr++ = *intStr++; + } - // Add the decimal point. - *ptr++ = '.'; + // Add the decimal point. + *ptr++ = '.'; - // Define the desired precision for the fractional part. - const int precision = 6; + // Define the desired precision for the fractional part. + const int precision = 6; - // Multiply the fraction to shift the decimal digits into integer range. - float fracValue = fraction; - for (int i = 0; i < precision; i++) { - fracValue *= 10.0f; - } + // Multiply the fraction to shift the decimal digits into integer range. + float fracValue = fraction; + for (int i = 0; i < precision; i++) { + fracValue *= 10.0f; + } - // Optionally, round the value. - auto fracInt = (int64_t)(fracValue + 0.5f); + // Optionally, round the value. + auto fracInt = (int64_t) (fracValue + 0.5f); - // Convert the fractional part to string. - char fracBuffer[50]; - char* fracStr = itoa(10, fracInt); + // Convert the fractional part to string. + char fracBuffer[50]; + char *fracStr = itoa(10, fracInt); - // Ensure we have leading zeros if the fractional part doesn't produce enough digits. - // Calculate length of the converted fractional string. - int len = 0; - for (char* p = fracStr; *p; p++) { - len++; - } - for (int i = 0; i < precision - len; i++) { - *ptr++ = '0'; - } + // Ensure we have leading zeros if the fractional part doesn't produce enough digits. + // Calculate length of the converted fractional string. + int len = 0; + for (char *p = fracStr; *p; p++) { + len++; + } + for (int i = 0; i < precision - len; i++) { + *ptr++ = '0'; + } - // Copy the fractional digits. - while (*fracStr) { - *ptr++ = *fracStr++; - } + // Copy the fractional digits. + while (*fracStr) { + *ptr++ = *fracStr++; + } - // Null-terminate the string. - *ptr = '\0'; + // Null-terminate the string. + *ptr = '\0'; - return buffer; + return buffer; } /** @@ -745,13 +726,13 @@ char* ftoa(float number) { */ bool strcmp(char const *str1, char const *str2) { - // Check if the strings are equal - for (int i = 0; str1[i] != '\0' || str2[i] != '\0'; i++) - if (str1[i] != str2[i]) - return false; + // Check if the strings are equal + for (int i = 0; str1[i] != '\0' || str2[i] != '\0'; i++) + if (str1[i] != str2[i]) + return false; - // The strings are equal - return true; + // The strings are equal + return true; } @@ -764,8 +745,8 @@ bool strcmp(char const *str1, char const *str2) { */ bool strcmp(char const *str1, String const &str2) { - // Use the other strcmp function - return strcmp(str1, str2.c_str()); + // Use the other strcmp function + return strcmp(str1, str2.c_str()); } @@ -778,8 +759,8 @@ bool strcmp(char const *str1, String const &str2) { */ bool strcmp(String const &str1, char const *str2) { - // Use the other strcmp function - return strcmp(str1.c_str(), str2); + // Use the other strcmp function + return strcmp(str1.c_str(), str2); } /** @@ -791,8 +772,8 @@ bool strcmp(String const &str1, char const *str2) { */ bool strcmp(String const &str1, String const &str2) { - // Use the other strcmp function - return strcmp(str1.c_str(), str2.c_str()); + // Use the other strcmp function + return strcmp(str1.c_str(), str2.c_str()); } @@ -806,13 +787,13 @@ bool strcmp(String const &str1, String const &str2) { */ bool strncmp(char const *str1, char const *str2, int length) { - // Check if the strings are equal - for (int i = 0; i < length; i++) - if (str1[i] != str2[i]) - return false; + // Check if the strings are equal + for (int i = 0; i < length; i++) + if (str1[i] != str2[i]) + return false; - // Strings are equal - return true; + // Strings are equal + return true; } @@ -826,8 +807,8 @@ bool strncmp(char const *str1, char const *str2, int length) { */ bool strncmp(char const *str1, String const &str2, int length) { - // Use the other strncmp function - return strncmp(str1, str2.c_str(), length); + // Use the other strncmp function + return strncmp(str1, str2.c_str(), length); } @@ -841,8 +822,8 @@ bool strncmp(char const *str1, String const &str2, int length) { */ bool strncmp(String const &str1, char const *str2, int length) { - // Use the other strncmp function - return strncmp(str1.c_str(), str2, length); + // Use the other strncmp function + return strncmp(str1.c_str(), str2, length); } @@ -856,6 +837,6 @@ bool strncmp(String const &str1, char const *str2, int length) { */ bool strncmp(String const &str1, String const &str2, int length) { - // Use the other strncmp function - return strncmp(str1.c_str(), str2.c_str(), length); + // Use the other strncmp function + return strncmp(str1.c_str(), str2.c_str(), length); } diff --git a/kernel/src/drivers/clock/clock.cpp b/kernel/src/drivers/clock/clock.cpp index ba84e142..32e3e2a6 100644 --- a/kernel/src/drivers/clock/clock.cpp +++ b/kernel/src/drivers/clock/clock.cpp @@ -10,7 +10,6 @@ using namespace MaxOS::hardwarecommunication; using namespace MaxOS::drivers; using namespace MaxOS::drivers::clock; -///__Handler__ ClockEventHandler::ClockEventHandler() = default; @@ -31,21 +30,19 @@ void ClockEventHandler::on_time(common::Time const &) { * @param event The event being fired * @return The event (may have been modified by the handler) */ -Event* ClockEventHandler::on_event(Event* event) { - - switch (event -> type) { - case ClockEvents::TIME: - on_time(*((TimeEvent *)event)->time); - break; - - default: - break; - } - - return event; -} +Event *ClockEventHandler::on_event(Event *event) { + + switch (event->type) { + case ClockEvents::TIME: + on_time(*((TimeEvent *) event)->time); + break; + + default: + break; + } -///__Clock__ + return event; +} /** * @brief Constructor for the Clock class @@ -53,12 +50,12 @@ Event* ClockEventHandler::on_event(Event* event) { * @param interrupt_manager The interrupt manager * @param time_between_events The time between events in 10ths of a second */ -Clock::Clock(AdvancedProgrammableInterruptController* apic, uint16_t time_between_events) +Clock::Clock(AdvancedProgrammableInterruptController *apic, uint16_t time_between_events) : InterruptHandler(0x20), m_apic(apic), m_ticks_between_events(time_between_events) { - Logger::INFO() << "Setting up Clock \n"; + Logger::INFO() << "Setting up Clock \n"; } Clock::~Clock() = default; @@ -69,20 +66,20 @@ Clock::~Clock() = default; */ void Clock::handle_interrupt() { - // Clock has ticked - m_ticks++; - m_ticks_until_next_event--; + // Clock has ticked + m_ticks++; + m_ticks_until_next_event--; - // Dont raise events until needed - if(m_ticks_until_next_event != 0) - return; + // Dont raise events until needed + if (m_ticks_until_next_event != 0) + return; - // Raise the time event + // Raise the time event // Time time = get_time(); // raise_event(new TimeEvent(&time)); - // Reset - m_ticks_until_next_event = m_ticks_between_events; + // Reset + m_ticks_until_next_event = m_ticks_between_events; } @@ -92,11 +89,10 @@ void Clock::handle_interrupt() { * @param address The address of the register to read from * @return The value of the register */ -uint8_t Clock::read_hardware_clock(uint8_t address) -{ +uint8_t Clock::read_hardware_clock(uint8_t address) { - m_command_port.write(address); - return m_data_port.read(); + m_command_port.write(address); + return m_data_port.read(); } /** @@ -107,13 +103,12 @@ uint8_t Clock::read_hardware_clock(uint8_t address) */ uint8_t Clock::binary_representation(uint8_t number) const { - // Check if the conversion needed - if(m_binary) - return number; - - // Convert to the binary representation - return ((number / 16) * 10) + (number & 0x0f); + // Check if the conversion needed + if (m_binary) + return number; + // Convert to the binary representation + return ((number / 16) * 10) + (number & 0x0f); } /** @@ -123,16 +118,14 @@ void Clock::activate() { s_active_clock = this; - // Get the stats from the clock - uint8_t status = read_hardware_clock(0xB); - - // Store the clock status - m_24_hour_clock = status & 0x02; - m_binary = status & 0x04; + // Get the stats from the clock + uint8_t status = read_hardware_clock(0xB); + // Store the clock status + m_24_hour_clock = status & 0x02; + m_binary = status & 0x04; } - /** * @brief Delays the program for a specified number of milliseconds * (rounded up to the nearest degree of accuracy - ensured the delay is at least the specified number of milliseconds). @@ -145,14 +138,13 @@ void Clock::activate() { */ void Clock::delay(uint32_t milliseconds) const { + // Round the number of milliseconds UP to the nearest clock accuracy + uint64_t rounded_milliseconds = (milliseconds + clock_accuracy - 1) / clock_accuracy; - // Round the number of milliseconds UP to the nearest clock accuracy - uint64_t rounded_milliseconds = (milliseconds + clock_accuracy - 1) / clock_accuracy; - - // Wait until the time has passed - uint64_t ticks_until_delay_is_over = m_ticks + rounded_milliseconds; - while(m_ticks < ticks_until_delay_is_over) - asm volatile("nop"); + // Wait until the time has passed + uint64_t ticks_until_delay_is_over = m_ticks + rounded_milliseconds; + while (m_ticks < ticks_until_delay_is_over) + asm volatile("nop"); } /** @@ -161,7 +153,7 @@ void Clock::delay(uint32_t milliseconds) const { * @return The name of the vendor */ string Clock::vendor_name() { - return "Generic"; + return "Generic"; } /** @@ -170,7 +162,7 @@ string Clock::vendor_name() { * @return The name of the device */ string Clock::device_name() { - return "Clock"; + return "Clock"; } /** @@ -180,25 +172,25 @@ string Clock::device_name() { */ void Clock::calibrate(uint64_t ms_per_tick) { - Logger::INFO() << "Calibrating Clock \n"; - clock_accuracy = ms_per_tick; + Logger::INFO() << "Calibrating Clock \n"; + clock_accuracy = ms_per_tick; - // Get the ticks per ms - PIT pit(m_apic); - uint32_t ticks_per_ms = pit.ticks_per_ms(); + // Get the ticks per ms + PIT pit(m_apic); + uint32_t ticks_per_ms = pit.ticks_per_ms(); - // Configure the clock to periodic mode - uint32_t lvt = 0x20 | (1 << 17); - m_apic -> local_apic() -> write(0x320, lvt); + // Configure the clock to periodic mode + uint32_t lvt = 0x20 | (1 << 17); + m_apic->local_apic()->write(0x320, lvt); - // Set the initial count - m_apic -> local_apic() -> write(0x380, ms_per_tick * ticks_per_ms); + // Set the initial count + m_apic->local_apic()->write(0x380, ms_per_tick * ticks_per_ms); - // Clear the interrupt mask for the clock - lvt &= ~(1 << 16); - m_apic -> local_apic() -> write(0x380, lvt); + // Clear the interrupt mask for the clock + lvt &= ~(1 << 16); + m_apic->local_apic()->write(0x380, lvt); - Logger::DEBUG() << "Clock: Calibrated to " << ms_per_tick << "ms per tick\n"; + Logger::DEBUG() << "Clock: Calibrated to " << ms_per_tick << "ms per tick\n"; } /** @@ -208,34 +200,33 @@ void Clock::calibrate(uint64_t ms_per_tick) { */ common::Time Clock::get_time() { - // Wait for the clock to be ready - while(read_hardware_clock(0xA) & 0x80) - asm volatile("nop"); + // Wait for the clock to be ready + while (read_hardware_clock(0xA) & 0x80) + asm volatile("nop"); - // Read the time from the clock - Time time{}; - time.year = binary_representation(read_hardware_clock(0x9)) + 2000; - time.month = binary_representation(read_hardware_clock(0x8)); - time.day = binary_representation(read_hardware_clock(0x7)); - time.hour = binary_representation(read_hardware_clock(0x4)); - time.minute = binary_representation(read_hardware_clock(0x2)); - time.second = binary_representation(read_hardware_clock(0x0)); + // Read the time from the clock + Time time{}; + time.year = binary_representation(read_hardware_clock(0x9)) + 2000; + time.month = binary_representation(read_hardware_clock(0x8)); + time.day = binary_representation(read_hardware_clock(0x7)); + time.hour = binary_representation(read_hardware_clock(0x4)); + time.minute = binary_representation(read_hardware_clock(0x2)); + time.second = binary_representation(read_hardware_clock(0x0)); - // If the clock is using 12hr format and PM is set then add 12 to the hour - if(!m_24_hour_clock && (time.hour & 0x80) != 0) - time.hour = ((time.hour & 0x7F) + 12) % 24; + // If the clock is using 12hr format and PM is set then add 12 to the hour + if (!m_24_hour_clock && (time.hour & 0x80) != 0) + time.hour = ((time.hour & 0x7F) + 12) % 24; - return time; + return time; } Clock *Clock::active_clock() { return s_active_clock; } - -TimeEvent::TimeEvent(Time* time) -:Event(ClockEvents::TIME), -time(time) +TimeEvent::TimeEvent(Time *time) +: Event(ClockEvents::TIME), + time(time) { } @@ -246,8 +237,8 @@ PIT::PIT(AdvancedProgrammableInterruptController *apic) : InterruptHandler(0x22), m_data_port(0x40), m_command_port(0x43), - m_local_apic(apic -> local_apic()), - m_io_apic(apic -> io_apic()) + m_local_apic(apic->local_apic()), + m_io_apic(apic->io_apic()) { } @@ -258,7 +249,7 @@ PIT::~PIT() = default; * @brief Tick on each interrupt */ void PIT::handle_interrupt() { - m_ticks++; + m_ticks++; } /** @@ -268,56 +259,55 @@ void PIT::handle_interrupt() { */ uint32_t PIT::ticks_per_ms() { - // Set the redirect for the timer interrupt - interrupt_redirect_t redirect = { - .type = 0x2, - .index = 0x14, - .interrupt = 0x22, - .destination = 0x00, - .flags = 0x00, - .mask = true, - }; - m_io_apic -> set_redirect(&redirect); - - // Configure the PIT clock - pit_command_t command = { - .bcd_mode = (uint8_t)BCDMode::BINARY, - .operating_mode = (uint8_t)OperatingMode::RATE_GENERATOR, - .access_mode = (uint8_t)AccessMode::LOW_HIGH_BYTE, - .channel = (uint8_t)Channel::INTERRUPT - }; - m_command_port.write(*(uint8_t *)&command); - - // Set the clock rate to 1 ms; - uint16_t rate = 1193182 / 1000; - m_data_port.write(rate & 0xFF); - m_data_port.write(rate >> 8); - - // Stop the clock (write 0 as the initial count) - m_local_apic -> write(0x380, 0x00); - - // Set the divisor to 2 - m_local_apic -> write(0x3E0, 0x0); - - // Unmask the PIT interrupt - m_io_apic ->set_redirect_mask(redirect.index, false); - - // Calculate the number of ticks in 1 ms - auto max = (uint32_t) - 1; - m_local_apic -> write(0x380, max); - - while (m_ticks < s_calibrate_ticks) - asm volatile("nop"); - - uint32_t elapsed = max - (m_local_apic -> read(0x390)); - uint32_t ticks_per_ms = elapsed / s_calibrate_ticks; - - Logger::DEBUG() << "Ticks per ms: " << (int)ticks_per_ms << "\n"; - - // Disable the PIT interrupt again - m_local_apic -> write(0x380, 0x00); - m_io_apic -> set_redirect_mask(redirect.index, true); - - return ticks_per_ms; - + // Set the redirect for the timer interrupt + interrupt_redirect_t redirect = { + .type = 0x2, + .index = 0x14, + .interrupt = 0x22, + .destination = 0x00, + .flags = 0x00, + .mask = true, + }; + m_io_apic->set_redirect(&redirect); + + // Configure the PIT clock + pit_command_t command = { + .bcd_mode = (uint8_t) BCDMode::BINARY, + .operating_mode = (uint8_t) OperatingMode::RATE_GENERATOR, + .access_mode = (uint8_t) AccessMode::LOW_HIGH_BYTE, + .channel = (uint8_t) Channel::INTERRUPT + }; + m_command_port.write(*(uint8_t *) &command); + + // Set the clock rate to 1 ms; + uint16_t rate = 1193182 / 1000; + m_data_port.write(rate & 0xFF); + m_data_port.write(rate >> 8); + + // Stop the clock + m_local_apic->write(0x380, 0x00); + + // Set the divisor to 2 + m_local_apic->write(0x3E0, 0x0); + + // Unmask the PIT interrupt + m_io_apic->set_redirect_mask(redirect.index, false); + + // Calculate the number of ticks in 1 ms + auto max = (uint32_t) -1; + m_local_apic->write(0x380, max); + + while (m_ticks < s_calibrate_ticks) + asm volatile("nop"); + + uint32_t elapsed = max - (m_local_apic->read(0x390)); + uint32_t ticks_per_ms = elapsed / s_calibrate_ticks; + + Logger::DEBUG() << "Ticks per ms: " << (int) ticks_per_ms << "\n"; + + // Disable the PIT interrupt again + m_local_apic->write(0x380, 0x00); + m_io_apic->set_redirect_mask(redirect.index, true); + + return ticks_per_ms; } diff --git a/kernel/src/drivers/console/console.cpp b/kernel/src/drivers/console/console.cpp index 3f4c878a..43a2dbed 100644 --- a/kernel/src/drivers/console/console.cpp +++ b/kernel/src/drivers/console/console.cpp @@ -9,8 +9,6 @@ using namespace MaxOS::common; using namespace MaxOS::drivers; using namespace MaxOS::drivers::console; -///____ Console ____ - Console::Console() = default; Console::~Console() = default; @@ -216,10 +214,6 @@ void Console::invert_colors(uint16_t x, uint16_t y) { set_background_color(x, y, foreground); } - -///____ Console Area ____/// - - ConsoleArea::ConsoleArea(Console *console, uint16_t left, uint16_t top, uint16_t width, uint16_t height) : m_console(console), m_left(left), @@ -247,7 +241,6 @@ ConsoleArea::ConsoleArea(Console *console, uint16_t left, uint16_t top, uint16_t } - ConsoleArea::~ConsoleArea() = default; /** @@ -379,9 +372,7 @@ ConsoleColour ConsoleArea::get_background_color(uint16_t x, uint16_t y) { */ void ConsoleArea::scroll_up() { - m_console->scroll_up(m_left, m_top, m_width, m_height); - } /** @@ -403,7 +394,6 @@ void ConsoleArea::scroll_up(uint16_t left, uint16_t top, uint16_t width, } -///____ Console Stream ____/// ConsoleStream::ConsoleStream(Console *console) : m_console(console) { diff --git a/kernel/src/drivers/console/serial.cpp b/kernel/src/drivers/console/serial.cpp index f7f25a0a..c0013b1d 100644 --- a/kernel/src/drivers/console/serial.cpp +++ b/kernel/src/drivers/console/serial.cpp @@ -7,7 +7,7 @@ using namespace MaxOS; using namespace MaxOS::drivers; -SerialConsole::SerialConsole(Logger* logger) +SerialConsole::SerialConsole(Logger *logger) : m_data_port(0x3F8), m_interrupt_enable_port(0x3F9), m_fifo_control_port(0x3FA), @@ -16,42 +16,39 @@ SerialConsole::SerialConsole(Logger* logger) m_line_status_port(0x3FD) { - // Disable all interrupts - m_interrupt_enable_port.write(0x00); + // Disable all interrupts + m_interrupt_enable_port.write(0x00); - // Enable DLAB (set baud rate divisor) - m_line_control_port.write(0x80); + // Enable DLAB (set baud rate divisor) + m_line_control_port.write(0x80); - // Set divisor to 3 - m_data_port.write(0x03); - m_interrupt_enable_port.write(0x00); + // Set divisor to 3 + m_data_port.write(0x03); + m_interrupt_enable_port.write(0x00); - // 8 bits, no parity, one stop bit - m_line_control_port.write(0x03); + // 8 bits, no parity, one stop bit + m_line_control_port.write(0x03); - // Enable FIFO, clear them, with 14-byte threshold - m_fifo_control_port.write(0xC7); + // Enable FIFO, clear them, with 14-byte threshold + m_fifo_control_port.write(0xC7); - // IRQs enabled, RTS/DSR set - m_modem_control_port.write(0x0B); + // IRQs enabled, RTS/DSR set + m_modem_control_port.write(0x0B); - // Test serial chip - m_modem_control_port.write(0x1E); - m_data_port.write(0xAE); - if (m_data_port.read() != 0xAE) - return; + // Test serial chip + m_modem_control_port.write(0x1E); + m_data_port.write(0xAE); + if (m_data_port.read() != 0xAE) + return; - // Enable serial chip - m_modem_control_port.write(0x0F); + // Enable serial chip + m_modem_control_port.write(0x0F); - // Set the active serial console - logger->add_log_writer(this); + // Set the active serial console + logger->add_log_writer(this); } -SerialConsole::~SerialConsole() { - - -} +SerialConsole::~SerialConsole() = default; /** * @brief Waits for the serial port to be ready, then writes a character to it @@ -60,13 +57,14 @@ SerialConsole::~SerialConsole() { */ void SerialConsole::put_character(char c) { - // Wait for the serial port to be ready - while (0 == (m_line_status_port.read() & 0x20)); + // Wait for the serial port to be ready + while (0 == (m_line_status_port.read() & 0x20)); - // Write the character - m_data_port.write(c); + // Write the character + m_data_port.write(c); } + void SerialConsole::write_char(char c) { - put_character(c); -} + put_character(c); +} \ No newline at end of file diff --git a/kernel/src/drivers/console/textmode.cpp b/kernel/src/drivers/console/textmode.cpp index ef7db3b3..56fce4f4 100644 --- a/kernel/src/drivers/console/textmode.cpp +++ b/kernel/src/drivers/console/textmode.cpp @@ -18,9 +18,8 @@ TextModeConsole::~TextModeConsole() = default; * * @return The width of the console in characters */ -uint16_t TextModeConsole::width() -{ - return 80; +uint16_t TextModeConsole::width() { + return 80; } /** @@ -28,9 +27,8 @@ uint16_t TextModeConsole::width() * * @return The height of the console in characters */ -uint16_t TextModeConsole::height() -{ - return 25; +uint16_t TextModeConsole::height() { + return 25; } /** @@ -42,16 +40,15 @@ uint16_t TextModeConsole::height() */ void TextModeConsole::put_character(uint16_t x, uint16_t y, char c) { - // Check bounds - if(x >= width() || y >= height()) - return; + // Check bounds + if (x >= width() || y >= height()) + return; - // Calculate the offset - int offset = (y * width() + x); - - // Set the character at the offset, mask away the colour information - m_video_memory[offset] = (m_video_memory[offset] & 0xFF00) | (uint16_t)c; + // Calculate the offset + int offset = (y * width() + x); + // Set the character at the offset, mask away the colour information + m_video_memory[offset] = (m_video_memory[offset] & 0xFF00) | (uint16_t) c; } /** @@ -63,15 +60,15 @@ void TextModeConsole::put_character(uint16_t x, uint16_t y, char c) { */ void TextModeConsole::set_foreground_color(uint16_t x, uint16_t y, ConsoleColour foreground) { - // Check bounds - if(x >= width() || y >= height()) - return; + // Check bounds + if (x >= width() || y >= height()) + return; - // Calculate the offset - int offset = (y* width() + x); + // Calculate the offset + int offset = (y * width() + x); - // Set the foreground color at the offset, mask to get the foreground bits - m_video_memory[offset] = (m_video_memory[offset] & 0xF0FF) | ((uint16_t)foreground << 8); + // Set the foreground color at the offset, mask to get the foreground bits + m_video_memory[offset] = (m_video_memory[offset] & 0xF0FF) | ((uint16_t) foreground << 8); } /** @@ -83,16 +80,15 @@ void TextModeConsole::set_foreground_color(uint16_t x, uint16_t y, ConsoleColour */ void TextModeConsole::set_background_color(uint16_t x, uint16_t y, ConsoleColour background) { - // Check bounds - if(x >= width() || y >= height()) - return; - - // Calculate the offset - int offset = (y* width() + x); + // Check bounds + if (x >= width() || y >= height()) + return; - // Set the background color at the offset, mask to get the backgroun bits - m_video_memory[offset] = (m_video_memory[offset] & 0x0FFF) | ((uint16_t)background << 12); + // Calculate the offset + int offset = (y * width() + x); + // Set the background color at the offset, mask to get the backgroun bits + m_video_memory[offset] = (m_video_memory[offset] & 0x0FFF) | ((uint16_t) background << 12); } /** @@ -104,15 +100,15 @@ void TextModeConsole::set_background_color(uint16_t x, uint16_t y, ConsoleColour */ char TextModeConsole::get_character(uint16_t x, uint16_t y) { - // Check bounds - if(x >= width() || y >= height()) - return ' '; + // Check bounds + if (x >= width() || y >= height()) + return ' '; - // Calculate the offset - int offset = (y* width() + x); + // Calculate the offset + int offset = (y * width() + x); - // Return the character at the offset, mask away the colour information - return (char)(m_video_memory[offset] & 0x00FF); + // Return the character at the offset, mask away the colour information + return (char) (m_video_memory[offset] & 0x00FF); } /** @@ -124,15 +120,15 @@ char TextModeConsole::get_character(uint16_t x, uint16_t y) { */ ConsoleColour TextModeConsole::get_foreground_color(uint16_t x, uint16_t y) { - // Check bounds - if(x >= width() || y >= height()) - return ConsoleColour::White; + // Check bounds + if (x >= width() || y >= height()) + return ConsoleColour::White; - // Calculate the offset - int offset = (y* width() + x); + // Calculate the offset + int offset = (y * width() + x); - // Return the foreground color at the offset, by masking the foreground color with the current foreground color (bits 8-11) - return (ConsoleColour)((m_video_memory[offset] & 0x0F00) >> 8); + // Return the foreground color at the offset, by masking the foreground color with the current foreground color (bits 8-11) + return (ConsoleColour) ((m_video_memory[offset] & 0x0F00) >> 8); } /** @@ -144,13 +140,13 @@ ConsoleColour TextModeConsole::get_foreground_color(uint16_t x, uint16_t y) { */ ConsoleColour TextModeConsole::get_background_color(uint16_t x, uint16_t y) { - // Check bounds - if(x >= width() || y >= height()) - return ConsoleColour::Black; + // Check bounds + if (x >= width() || y >= height()) + return ConsoleColour::Black; - // Calculate the offset - int offset = (y* width() + x); + // Calculate the offset + int offset = (y * width() + x); - // Return the background color at the offset, by masking the background color with the current background color (bits 12-15) - return (ConsoleColour)((m_video_memory[offset] & 0xF000) >> 12); + // Return the background color at the offset, by masking the background color with the current background color (bits 12-15) + return (ConsoleColour) ((m_video_memory[offset] & 0xF000) >> 12); } \ No newline at end of file diff --git a/kernel/src/drivers/console/vesaboot.cpp b/kernel/src/drivers/console/vesaboot.cpp index 69948d2c..a7a063ff 100644 --- a/kernel/src/drivers/console/vesaboot.cpp +++ b/kernel/src/drivers/console/vesaboot.cpp @@ -15,27 +15,28 @@ using namespace MaxOS::drivers::console; using namespace MaxOS::system; VESABootConsole::VESABootConsole(GraphicsContext *graphics_context) -: m_font((uint8_t*)AMIGA_FONT) +: m_font((uint8_t *)AMIGA_FONT) { - // Set up - Logger::INFO() << "Setting up VESA console\n"; - s_graphics_context = graphics_context; - m_video_memory_meta = (uint16_t*)MemoryManager::kmalloc(width() * height() * sizeof(uint16_t)); - - // Prepare the console - VESABootConsole::clear(); - print_logo(); - m_console_area = new ConsoleArea(this, 0, 0, width() / 2 - 25, height(), ConsoleColour::DarkGrey, ConsoleColour::Black); - cout = new ConsoleStream(m_console_area); - - // Only log to the screen when debugging - #ifdef TARGET_DEBUG - Logger::active_logger() -> add_log_writer(cout); - Logger::INFO() << "Console Stream set up \n"; - #endif - - update_progress_bar(0); + // Set up + Logger::INFO() << "Setting up VESA console\n"; + s_graphics_context = graphics_context; + m_video_memory_meta = (uint16_t *) MemoryManager::kmalloc(width() * height() * sizeof(uint16_t)); + + // Prepare the console + VESABootConsole::clear(); + print_logo(); + m_console_area = new ConsoleArea(this, 0, 0, width() / 2 - 25, height(), ConsoleColour::DarkGrey, + ConsoleColour::Black); + cout = new ConsoleStream(m_console_area); + + // Only log to the screen when debugging + #ifdef TARGET_DEBUG + Logger::active_logger()->add_log_writer(cout); + Logger::INFO() << "Console Stream set up \n"; + #endif + + update_progress_bar(0); } VESABootConsole::~VESABootConsole() = default; @@ -45,9 +46,8 @@ VESABootConsole::~VESABootConsole() = default; * * @return The width of the console in characters */ -uint16_t VESABootConsole::width() -{ - return s_graphics_context->width() / 8; // 8 pixels per character +uint16_t VESABootConsole::width() { + return s_graphics_context->width() / 8; // 8 pixels per character } /** @@ -55,9 +55,8 @@ uint16_t VESABootConsole::width() * * @return The height of the console in characters */ -uint16_t VESABootConsole::height() -{ - return s_graphics_context->height() / Font::font_height; +uint16_t VESABootConsole::height() { + return s_graphics_context->height() / Font::font_height; } /** @@ -69,70 +68,68 @@ uint16_t VESABootConsole::height() */ void VESABootConsole::put_character(uint16_t x, uint16_t y, char c) { - // Parse any ansi codes - if (c == '\033') { + // Parse any ansi codes + if (c == '\033') { - // Store the character - ansi_code_length = 0; - ansi_code[ansi_code_length++] = c; + // Store the character + ansi_code_length = 0; + ansi_code[ansi_code_length++] = c; - // Do not draw the escape character - return; + // Do not draw the escape character + return; + } - } + if (ansi_code_length < 8) { - if (ansi_code_length < 8) { + // Add the character to the ANSI code + ansi_code[ansi_code_length++] = c; - // Add the character to the ANSI code - ansi_code[ansi_code_length++] = c; + // If the ANSI code is complete + if (c == 'm') { + ansi_code[ansi_code_length] = '\0'; + ansi_code_length = -1; - // If the ANSI code is complete - if (c == 'm') { - ansi_code[ansi_code_length] = '\0'; - ansi_code_length = -1; + if (strcmp("\033[0m", ansi_code) != 0) { + m_foreground_color = ConsoleColour::Uninitialised; + m_background_color = ConsoleColour::Uninitialised; + return; + } - if(strcmp("\033[0m", ansi_code) != 0) { - m_foreground_color = ConsoleColour::Uninitialised; - m_background_color = ConsoleColour::Uninitialised; - return; - } + // Get the colour from the ANSI code + const Colour colour(ansi_code); - // Get the colour from the ANSI code - const Colour colour(ansi_code); + // Set the colour + bool foreground = ansi_code[4] == '3'; + if (foreground) + m_foreground_color = colour.to_console_colour(); + else + m_background_color = colour.to_console_colour(); - // Set the colour - bool foreground = ansi_code[4] == '3'; - if (foreground) - m_foreground_color = colour.to_console_colour(); - else - m_background_color = colour.to_console_colour(); + } - } + // Do not draw the escape character + return; + } - // Do not draw the escape character - return; - } + // If the coordinates are out of bounds, return + if (x >= width() || y >= height()) + return; - // If the coordinates are out of bounds, return - if(x >= width() || y >= height()) - return; + // Calculate the offset + int offset = (y * width() + x); - // Calculate the offset - int offset = (y* width() + x); + // Set the character at the offset, by masking the character with the current character (last 8 bits) + m_video_memory_meta[offset] = (m_video_memory_meta[offset] & 0xFF00) | (uint16_t) c; - // Set the character at the offset, by masking the character with the current character (last 8 bits) - m_video_memory_meta[offset] = (m_video_memory_meta[offset] & 0xFF00) | (uint16_t)c; + // Convert the char into a string + char s[] = " "; + s[0] = c; - // Convert the char into a string - char s[] = " "; - s[0] = c; - - Colour foreground = m_foreground_color == ConsoleColour::Uninitialised ? get_foreground_color(x, y) : Colour(m_foreground_color); - Colour background = m_background_color == ConsoleColour::Uninitialised ? get_background_color(x, y) : Colour(m_background_color); - - // Use the m_font to draw the character - m_font.draw_text(x * 8, y * Font::font_height, foreground, background, s_graphics_context, s); + Colour foreground = m_foreground_color == ConsoleColour::Uninitialised ? get_foreground_color(x, y) : Colour(m_foreground_color); + Colour background = m_background_color == ConsoleColour::Uninitialised ? get_background_color(x, y) : Colour(m_background_color); + // Use the m_font to draw the character + m_font.draw_text(x * 8, y * Font::font_height, foreground, background, s_graphics_context, s); } /** @@ -144,15 +141,15 @@ void VESABootConsole::put_character(uint16_t x, uint16_t y, char c) { */ void VESABootConsole::set_foreground_color(uint16_t x, uint16_t y, ConsoleColour foreground) { - // If the coordinates are out of bounds, return - if(x >= width() || y >= height()) - return; + // If the coordinates are out of bounds, return + if (x >= width() || y >= height()) + return; - // Calculate the offset - int offset = (y* width() + x); + // Calculate the offset + int offset = (y * width() + x); - // Set the foreground color at the offset, by masking the foreground color with the current foreground color (bits 8-11) - m_video_memory_meta[offset] = (m_video_memory_meta[offset] & 0xF0FF) | ((uint16_t)foreground << 8); + // Set the foreground color at the offset, by masking the foreground color with the current foreground color (bits 8-11) + m_video_memory_meta[offset] = (m_video_memory_meta[offset] & 0xF0FF) | ((uint16_t) foreground << 8); } /** @@ -164,16 +161,15 @@ void VESABootConsole::set_foreground_color(uint16_t x, uint16_t y, ConsoleColour */ void VESABootConsole::set_background_color(uint16_t x, uint16_t y, ConsoleColour background) { - // If the coordinates are out of bounds, return - if(x >= width() || y >= height()) - return; + // If the coordinates are out of bounds, return + if (x >= width() || y >= height()) + return; - // Calculate the offset - int offset = (y* width() + x); - - // Set the background color at the offset, by masking the background color with the current background color (bits 12-15) - m_video_memory_meta[offset] = (m_video_memory_meta[offset] & 0x0FFF) | ((uint16_t)background << 12); + // Calculate the offset + int offset = (y * width() + x); + // Set the background color at the offset, by masking the background color with the current background color (bits 12-15) + m_video_memory_meta[offset] = (m_video_memory_meta[offset] & 0x0FFF) | ((uint16_t) background << 12); } /** @@ -185,15 +181,15 @@ void VESABootConsole::set_background_color(uint16_t x, uint16_t y, ConsoleColour */ char VESABootConsole::get_character(uint16_t x, uint16_t y) { - // If the coordinates are out of bounds, return - if(x >= width() || y >= height()) - return ' '; + // If the coordinates are out of bounds, return + if (x >= width() || y >= height()) + return ' '; - // Calculate the offset - int offset = (y* width() + x); + // Calculate the offset + int offset = (y * width() + x); - // Return the character at the offset, by masking the character with the current character (last 8 bits) - return (char)(m_video_memory_meta[offset] & 0x00FF); + // Return the character at the offset, by masking the character with the current character (last 8 bits) + return (char) (m_video_memory_meta[offset] & 0x00FF); } /** @@ -205,15 +201,15 @@ char VESABootConsole::get_character(uint16_t x, uint16_t y) { */ ConsoleColour VESABootConsole::get_foreground_color(uint16_t x, uint16_t y) { - // If the coordinates are out of bounds, return - if(x >= width() || y >= height()) - return ConsoleColour::White; + // If the coordinates are out of bounds, return + if (x >= width() || y >= height()) + return ConsoleColour::White; - // Calculate the offset - int offset = (y* width() + x); + // Calculate the offset + int offset = (y * width() + x); - // Return the foreground color at the offset, by masking the foreground color with the current foreground color (bits 8-11) - return (ConsoleColour)((m_video_memory_meta[offset] & 0x0F00) >> 8); + // Return the foreground color at the offset, by masking the foreground color with the current foreground color (bits 8-11) + return (ConsoleColour) ((m_video_memory_meta[offset] & 0x0F00) >> 8); } /** @@ -225,15 +221,15 @@ ConsoleColour VESABootConsole::get_foreground_color(uint16_t x, uint16_t y) { */ ConsoleColour VESABootConsole::get_background_color(uint16_t x, uint16_t y) { - // If the coordinates are out of bounds, return - if(x >= width() || y >= height()) - return ConsoleColour::Black; + // If the coordinates are out of bounds, return + if (x >= width() || y >= height()) + return ConsoleColour::Black; - // Calculate the offset - int offset = (y* width() + x); + // Calculate the offset + int offset = (y * width() + x); - // Return the background color at the offset, by masking the background color with the current background color (bits 12-15) - return (ConsoleColour)((m_video_memory_meta[offset] & 0xF000) >> 12); + // Return the background color at the offset, by masking the background color with the current background color (bits 12-15) + return (ConsoleColour) ((m_video_memory_meta[offset] & 0xF000) >> 12); } /** @@ -241,29 +237,29 @@ ConsoleColour VESABootConsole::get_background_color(uint16_t x, uint16_t y) { */ void VESABootConsole::print_logo() { - // Load the logo - const char* logo = header_data; + // Load the logo + const char *logo = header_data; - // Find the center of the screen - uint32_t center_x = s_graphics_context->width()/2; - uint32_t center_y = s_graphics_context->height()/2 - 80; + // Find the center of the screen + uint32_t center_x = s_graphics_context->width() / 2; + uint32_t center_y = s_graphics_context->height() / 2 - 80; - // Draw the logo - for (uint32_t logoY = 0; logoY < logo_height; ++logoY) { - for (uint32_t logoX = 0; logoX < logo_width; ++logoX) { + // Draw the logo + for (uint32_t logoY = 0; logoY < logo_height; ++logoY) { + for (uint32_t logoX = 0; logoX < logo_width; ++logoX) { - // Store the pixel in the logo - uint8_t pixel[3] = {0}; + // Store the pixel in the logo + uint8_t pixel[3] = {0}; - // Get the pixel from the logo - LOGO_HEADER_PIXEL(logo, pixel) + // Get the pixel from the logo + LOGO_HEADER_PIXEL(logo, pixel) - // Draw the pixel - s_graphics_context->put_pixel(center_x - logo_width / 2 + logoX, - center_y - logo_height / 2 + logoY, - common::Colour(pixel[0], pixel[1], pixel[2])); - } - } + // Draw the pixel + s_graphics_context->put_pixel(center_x - logo_width / 2 + logoX, + center_y - logo_height / 2 + logoY, + common::Colour(pixel[0], pixel[1], pixel[2])); + } + } } @@ -279,55 +275,55 @@ void VESABootConsole::print_logo() { * @param fill The character to fill the new line with */ void VESABootConsole::scroll_up(uint16_t left, uint16_t top, uint16_t width, - uint16_t height, - common::ConsoleColour foreground, - common::ConsoleColour background, char) { - - - // Get the framebuffer info - auto* framebuffer_address = (uint8_t*)s_graphics_context->framebuffer_address(); - uint64_t framebuffer_width = s_graphics_context->width(); - uint64_t framebuffer_bpp = s_graphics_context->color_depth(); // in bits per pixel - uint64_t bytes_per_pixel = framebuffer_bpp / 8; - uint64_t framebuffer_pitch = framebuffer_width * bytes_per_pixel; - - uint16_t line_height = Font::font_height; - - // Region conversions - uint16_t region_pixel_y = top * line_height; - uint16_t region_pixel_height = height * line_height; - uint16_t region_pixel_left = left * Font::font_width; - uint16_t region_pixel_width = width * Font::font_width; - size_t row_bytes = region_pixel_width * bytes_per_pixel; - - // Decide the colour of the pixel - ConsoleColour to_set_foreground = CPU::is_panicking ? ConsoleColour::White : get_foreground_color(left, top + height - 1); - ConsoleColour to_set_background = CPU::is_panicking ? ConsoleColour::Red : get_background_color(left, top + height - 1); - Colour fill_colour = Colour(to_set_background); - uint32_t fill_value = s_graphics_context->colour_to_int(to_set_background); - - // Scroll the region upward by one text line - for (uint16_t row = 0; row < region_pixel_height - line_height; row++) { - uint8_t* src = framebuffer_address + (region_pixel_y + row + line_height) * framebuffer_pitch + region_pixel_left * bytes_per_pixel; - uint8_t* dest = framebuffer_address + (region_pixel_y + row) * framebuffer_pitch + region_pixel_left * bytes_per_pixel; - memmove(dest, src, row_bytes); - } - - // Clear the last line of the region - uint16_t clear_start_y = region_pixel_y + region_pixel_height - line_height; - for (uint16_t row = 0; row < line_height; row++) { - auto row_add = (uint32_t*)(framebuffer_address + (clear_start_y + row) * framebuffer_pitch + region_pixel_left * 4); - for (uint16_t col = 0; col < region_pixel_width; col++) { - row_add[col] = fill_value; - } - } - - //Update any per-pixel colour metadata - uint16_t text_row = top + height - 1; - for (uint16_t x = left; x < left + width; x++) { - set_foreground_color(x, text_row, to_set_foreground); - set_background_color(x, text_row, to_set_background); - } + uint16_t height, + common::ConsoleColour foreground, + common::ConsoleColour background, char fill) { + + + // Get the framebuffer info + auto *framebuffer_address = (uint8_t *) s_graphics_context->framebuffer_address(); + uint64_t framebuffer_width = s_graphics_context->width(); + uint64_t framebuffer_bpp = s_graphics_context->color_depth(); // in bits per pixel + uint64_t bytes_per_pixel = framebuffer_bpp / 8; + uint64_t framebuffer_pitch = framebuffer_width * bytes_per_pixel; + + uint16_t line_height = Font::font_height; + + // Region conversions + uint16_t region_pixel_y = top * line_height; + uint16_t region_pixel_height = height * line_height; + uint16_t region_pixel_left = left * Font::font_width; + uint16_t region_pixel_width = width * Font::font_width; + size_t row_bytes = region_pixel_width * bytes_per_pixel; + + // Decide the colour of the pixel + ConsoleColour to_set_foreground = CPU::is_panicking ? ConsoleColour::White : get_foreground_color(left, top + height - 1); + ConsoleColour to_set_background = CPU::is_panicking ? ConsoleColour::Red : get_background_color(left, top + height - 1); + Colour fill_colour = Colour(to_set_background); + uint32_t fill_value = s_graphics_context->colour_to_int(to_set_background); + + // Scroll the region upward by one text line + for (uint16_t row = 0; row < region_pixel_height - line_height; row++) { + uint8_t *src = framebuffer_address + (region_pixel_y + row + line_height) * framebuffer_pitch + region_pixel_left * bytes_per_pixel; + uint8_t *dest = framebuffer_address + (region_pixel_y + row) * framebuffer_pitch + region_pixel_left * bytes_per_pixel; + memmove(dest, src, row_bytes); + } + + // Clear the last line of the region + uint16_t clear_start_y = region_pixel_y + region_pixel_height - line_height; + for (uint16_t row = 0; row < line_height; row++) { + auto row_add = (uint32_t *) (framebuffer_address + (clear_start_y + row) * framebuffer_pitch + region_pixel_left * 4); + for (uint16_t col = 0; col < region_pixel_width; col++) { + row_add[col] = fill_value; + } + } + + //Update any per-pixel colour metadata + uint16_t text_row = top + height - 1; + for (uint16_t x = left; x < left + width; x++) { + set_foreground_color(x, text_row, to_set_foreground); + set_background_color(x, text_row, to_set_background); + } } /** @@ -335,27 +331,27 @@ void VESABootConsole::scroll_up(uint16_t left, uint16_t top, uint16_t width, */ void VESABootConsole::print_logo_kernel_panic() { - // Load the logo - const char* logo = header_data_kp; + // Load the logo + const char *logo = header_data_kp; - // Find the bottom right of the screen - uint32_t right_x = s_graphics_context->width() - kp_width - 10; - uint32_t bottom_y = s_graphics_context->height() - kp_height - 10; + // Find the bottom right of the screen + uint32_t right_x = s_graphics_context->width() - kp_width - 10; + uint32_t bottom_y = s_graphics_context->height() - kp_height - 10; - // Draw the logo - for (uint32_t logoY = 0; logoY < kp_height; ++logoY) { - for (uint32_t logoX = 0; logoX < kp_width; ++logoX) { + // Draw the logo + for (uint32_t logoY = 0; logoY < kp_height; ++logoY) { + for (uint32_t logoX = 0; logoX < kp_width; ++logoX) { - // Store the pixel in the logo - uint8_t pixel[3] = {0}; + // Store the pixel in the logo + uint8_t pixel[3] = {0}; - // Get the pixel from the logo - LOGO_HEADER_PIXEL(logo, pixel) + // Get the pixel from the logo + LOGO_HEADER_PIXEL(logo, pixel) - // Draw the pixel - s_graphics_context->put_pixel(right_x + logoX, bottom_y + logoY, common::Colour(pixel[0], pixel[1], pixel[2])); - } - } + // Draw the pixel + s_graphics_context->put_pixel(right_x + logoX, bottom_y + logoY, common::Colour(pixel[0], pixel[1], pixel[2])); + } + } } @@ -364,14 +360,14 @@ void VESABootConsole::print_logo_kernel_panic() { */ void VESABootConsole::finish() { - // Done - Logger::HEADER() << "MaxOS Kernel Successfully Booted\n"; + // Done + Logger::HEADER() << "MaxOS Kernel Successfully Booted\n"; - // CPU::PANIC will override a disabled logger so the console should scroll itself into view as it is unknown what - // will be on the screen now and that may mess with the presentation of the text (ie white text on a white background) - cout->set_cursor(width(), height()); + // CPU::PANIC will override a disabled logger so the console should scroll itself into view as it is unknown what + // will be on the screen now and that may mess with the presentation of the text (ie white text on a white background) + cout->set_cursor(width(), height()); - Logger::active_logger()->disable_log_writer(cout); + Logger::active_logger()->disable_log_writer(cout); } /** @@ -381,45 +377,45 @@ void VESABootConsole::finish() { */ void VESABootConsole::update_progress_bar(uint8_t percentage) { - // Check bounds - if(percentage > 100) - percentage = 100; + // Check bounds + if (percentage > 100) + percentage = 100; - // Must have a valid graphics context - if(s_graphics_context == nullptr) - return; + // Must have a valid graphics context + if (s_graphics_context == nullptr) + return; - uint8_t progress_height = 15; - uint8_t progress_spacing = 20; - uint8_t progress_width_cull = 40; + uint8_t progress_height = 15; + uint8_t progress_spacing = 20; + uint8_t progress_width_cull = 40; - // Find the center of the screen - uint32_t right_x = (s_graphics_context->width()/2) - logo_width / 2; - uint32_t bottom_y = (s_graphics_context->height()/2 - 80) - logo_height / 2; + // Find the center of the screen + uint32_t right_x = (s_graphics_context->width() / 2) - logo_width / 2; + uint32_t bottom_y = (s_graphics_context->height() / 2 - 80) - logo_height / 2; - // Find the bounds - uint32_t start_x = progress_width_cull; - uint32_t start_y = logo_height + progress_spacing; - uint32_t end_x = logo_width - progress_width_cull; - uint32_t end_y = logo_height + progress_height + progress_spacing; + // Find the bounds + uint32_t start_x = progress_width_cull; + uint32_t start_y = logo_height + progress_spacing; + uint32_t end_x = logo_width - progress_width_cull; + uint32_t end_y = logo_height + progress_height + progress_spacing; - // Draw the progress bar - for (uint32_t progress_y = start_y; progress_y < end_y; ++progress_y) { - for (uint32_t progress_x = start_x; progress_x < end_x; ++progress_x) { + // Draw the progress bar + for (uint32_t progress_y = start_y; progress_y < end_y; ++progress_y) { + for (uint32_t progress_x = start_x; progress_x < end_x; ++progress_x) { - // Check if drawing border - bool is_border = (progress_y == start_y) || (progress_y == end_y - 1) || - (progress_x == start_x) || (progress_x == end_x - 1); + // Check if drawing border + bool is_border = (progress_y == start_y) || (progress_y == end_y - 1) || + (progress_x == start_x) || (progress_x == end_x - 1); - // Only draw the border if it is the first time drawing it - is_border = is_border && percentage == 0; + // Only draw the border if it is the first time drawing it + is_border = is_border && percentage == 0; - // If it is not within the percentage, skip it - if (progress_x > logo_width * percentage / 100 && !is_border) - continue; + // If it is not within the percentage, skip it + if (progress_x > logo_width * percentage / 100 && !is_border) + continue; - s_graphics_context->put_pixel(right_x + progress_x, bottom_y + progress_y, Colour(0xFF, 0xFF, 0xFF)); + s_graphics_context->put_pixel(right_x + progress_x, bottom_y + progress_y, Colour(0xFF, 0xFF, 0xFF)); - } - } -} + } + } +} \ No newline at end of file diff --git a/kernel/src/drivers/disk/ata.cpp b/kernel/src/drivers/disk/ata.cpp index 28d18306..9365bcb3 100644 --- a/kernel/src/drivers/disk/ata.cpp +++ b/kernel/src/drivers/disk/ata.cpp @@ -34,51 +34,51 @@ AdvancedTechnologyAttachment::~AdvancedTechnologyAttachment() = default; */ bool AdvancedTechnologyAttachment::identify() { - // Select the device (master or slave) - m_device_port.write(m_is_master ? 0xA0 : 0xB0); - - // Reset the High Order Byte - m_control_port.write(0); - - // Check if the master is present - m_device_port.write(0xA0); - uint8_t status = m_command_port.read(); - if(status == 0xFF){ - Logger::WARNING() << "ATA Device: Invalid status"; - return false; - } - - // Select the device (master or slave) - m_device_port.write(m_is_master ? 0xA0 : 0xB0); - - // Clear the ports - m_sector_count_port.write(0); - m_LBA_low_port.write(0); - m_LBA_mid_port.write(0); - m_LBA_high_Port.write(0); - - // Check if the device is present - m_command_port.write(0x0EC); - status = m_command_port.read(); - if(status == 0x00) - return false; - - // Wait for the device to be ready or for an error to occur - while (((status & 0x80) == 0x80) && ((status & 0x01) != 0x01)) - status = m_command_port.read(); - - //Check for any errors - if(status & 0x01){ - Logger::WARNING() << "ATA Device: Error reading status\n"; - return false; - } - - // Read the rest of the data as a whole sector needs to be read - for (uint16_t i = 0; i < 256; ++i) - uint16_t data = m_data_port.read(); - - // Device is present and ready - return true; + // Select the device (master or slave) + m_device_port.write(m_is_master ? 0xA0 : 0xB0); + + // Reset the High Order Byte + m_control_port.write(0); + + // Check if the master is present + m_device_port.write(0xA0); + uint8_t status = m_command_port.read(); + if (status == 0xFF) { + Logger::WARNING() << "ATA Device: Invalid status"; + return false; + } + + // Select the device (master or slave) + m_device_port.write(m_is_master ? 0xA0 : 0xB0); + + // Clear the ports + m_sector_count_port.write(0); + m_LBA_low_port.write(0); + m_LBA_mid_port.write(0); + m_LBA_high_Port.write(0); + + // Check if the device is present + m_command_port.write(0x0EC); + status = m_command_port.read(); + if (status == 0x00) + return false; + + // Wait for the device to be ready or for an error to occur + while (((status & 0x80) == 0x80) && ((status & 0x01) != 0x01)) + status = m_command_port.read(); + + //Check for any errors + if (status & 0x01) { + Logger::WARNING() << "ATA Device: Error reading status\n"; + return false; + } + + // Read the rest of the data as a whole sector needs to be read + for (uint16_t i = 0; i < 256; ++i) + uint16_t data = m_data_port.read(); + + // Device is present and ready + return true; } /** @@ -88,58 +88,57 @@ bool AdvancedTechnologyAttachment::identify() { * @param data_buffer The data to read into * @param amount The amount of bytes to read from that sector */ -void AdvancedTechnologyAttachment::read(uint32_t sector, buffer_t* data_buffer, size_t amount) -{ - // Don't allow reading more than a sector - if(sector & 0xF0000000 || amount > m_bytes_per_sector) - return; +void AdvancedTechnologyAttachment::read(uint32_t sector, buffer_t *data_buffer, size_t amount) { + + // Don't allow reading more than a sector + if (sector & 0xF0000000 || amount > m_bytes_per_sector) + return; - // Select the device (master or slave) - m_device_port.write((m_is_master ? 0xE0 : 0xF0) | ((sector & 0x0F000000) >> 24)); + // Select the device (master or slave) + m_device_port.write((m_is_master ? 0xE0 : 0xF0) | ((sector & 0x0F000000) >> 24)); - // Device is busy (TODO: YIELD) - while((m_command_port.read() & 0x80) != 0); + // Device is busy (TODO: YIELD) + while ((m_command_port.read() & 0x80) != 0); - // Reset the device - m_error_port.write(0); - m_sector_count_port.write(1); + // Reset the device + m_error_port.write(0); + m_sector_count_port.write(1); - // Split the sector into the ports - m_LBA_low_port.write(sector & 0x000000FF); - m_LBA_mid_port.write((sector & 0x0000FF00) >> 8); - m_LBA_high_Port.write((sector & 0x00FF0000) >> 16); + // Split the sector into the ports + m_LBA_low_port.write(sector & 0x000000FF); + m_LBA_mid_port.write((sector & 0x0000FF00) >> 8); + m_LBA_high_Port.write((sector & 0x00FF0000) >> 16); - // Tell the device to prepare for reading - m_command_port.write(0x20); + // Tell the device to prepare for reading + m_command_port.write(0x20); - // Make sure the device is there - uint8_t status = m_command_port.read(); - if(status == 0x00) - return; + // Make sure the device is there + uint8_t status = m_command_port.read(); + if (status == 0x00) + return; - // Wait for the device to be ready or for an error to occur TODO: Userspace block here - while(((status & 0x80) == 0x80) && ((status & 0x01) != 0x01)) - status = m_command_port.read(); + // Wait for the device to be ready or for an error to occur TODO: Userspace block here + while (((status & 0x80) == 0x80) && ((status & 0x01) != 0x01)) + status = m_command_port.read(); - //Check for any errors - if(status & 0x01) - return; + //Check for any errors + if (status & 0x01) + return; - for(size_t i = 0; i < amount; i+= 2) - { + for (size_t i = 0; i < amount; i += 2) { - // Read from the disk (2 bytes) and store the first byte - uint16_t read_data = m_data_port.read(); - data_buffer -> write(read_data & 0x00FF); + // Read from the disk (2 bytes) and store the first byte + uint16_t read_data = m_data_port.read(); + data_buffer->write(read_data & 0x00FF); - // Place the second byte in the array if there is one - if(i + 1 < amount) - data_buffer -> write((read_data >> 8) & 0x00FF); - } + // Place the second byte in the array if there is one + if (i + 1 < amount) + data_buffer->write((read_data >> 8) & 0x00FF); + } - // Read the remaining bytes as a full sector has to be read - for(uint16_t i = amount + (amount % 2); i < m_bytes_per_sector; i+= 2) - m_data_port.read(); + // Read the remaining bytes as a full sector has to be read + for (uint16_t i = amount + (amount % 2); i < m_bytes_per_sector; i += 2) + m_data_port.read(); } /** @@ -149,84 +148,84 @@ void AdvancedTechnologyAttachment::read(uint32_t sector, buffer_t* data_buffer, * @param data The data to write * @param count The amount of data to write to that sector */ -void AdvancedTechnologyAttachment::write(uint32_t sector, const buffer_t* data, size_t count){ +void AdvancedTechnologyAttachment::write(uint32_t sector, const buffer_t *data, size_t count) { - // Don't allow writing more than a sector - if(sector > 0x0FFFFFFF || count > m_bytes_per_sector) - return; + // Don't allow writing more than a sector + if (sector > 0x0FFFFFFF || count > m_bytes_per_sector) + return; - // Select the device (master or slave) - m_device_port.write(m_is_master ? 0xE0 : 0xF0 | ((sector & 0x0F000000) >> 24)); + // Select the device (master or slave) + m_device_port.write(m_is_master ? 0xE0 : 0xF0 | ((sector & 0x0F000000) >> 24)); - // Device is busy (TODO: YIELD) - while((m_command_port.read() & 0x80) != 0); + // Device is busy (TODO: YIELD) + while ((m_command_port.read() & 0x80) != 0); - // Reset the device - m_error_port.write(0); - m_sector_count_port.write(1); + // Reset the device + m_error_port.write(0); + m_sector_count_port.write(1); - // Split the sector into the ports - m_LBA_low_port.write(sector & 0x000000FF); - m_LBA_mid_port.write((sector & 0x0000FF00) >> 8); - m_LBA_high_Port.write((sector & 0x00FF0000) >> 16); + // Split the sector into the ports + m_LBA_low_port.write(sector & 0x000000FF); + m_LBA_mid_port.write((sector & 0x0000FF00) >> 8); + m_LBA_high_Port.write((sector & 0x00FF0000) >> 16); - // Send the write command - m_command_port.write(0x30); + // Send the write command + m_command_port.write(0x30); - // Wait for the device be ready writing (TODO: YIELD) - uint8_t status = m_command_port.read(); - while ((status & 0x80) != 0 || (status & 0x08) == 0) - status = m_command_port.read(); + // Wait for the device be ready writing (TODO: YIELD) + uint8_t status = m_command_port.read(); + while ((status & 0x80) != 0 || (status & 0x08) == 0) + status = m_command_port.read(); - // Write the data to the device - for (uint16_t i = 0; i < m_bytes_per_sector; i+= 2) { + // Write the data to the device + for (uint16_t i = 0; i < m_bytes_per_sector; i += 2) { - uint16_t writeData = data -> read(); + uint16_t writeData = data->read(); - // Place the next byte in the array if there is one - if(i+1 < count) - writeData |= (uint16_t)(data ->read()) << 8; + // Place the next byte in the array if there is one + if (i + 1 < count) + writeData |= (uint16_t) (data->read()) << 8; - m_data_port.write(writeData); - } + m_data_port.write(writeData); + } - // Write the remaining bytes as a full sector has to be written - for(int i = count + (count%2); i < m_bytes_per_sector; i += 2) - m_data_port.write(0x0000); + // Write the remaining bytes as a full sector has to be written + for (int i = count + (count % 2); i < m_bytes_per_sector; i += 2) + m_data_port.write(0x0000); - // Wait for the device to finish writing (TODO: YIELD) - status = m_command_port.read(); - while ((status & 0x80) != 0 || (status & 0x08) != 0) - status = m_command_port.read(); + // Wait for the device to finish writing (TODO: YIELD) + status = m_command_port.read(); + while ((status & 0x80) != 0 || (status & 0x08) != 0) + status = m_command_port.read(); - flush(); + flush(); } + /** * @brief Flush the cache of the ATA device */ void AdvancedTechnologyAttachment::flush() { - // Select the device (master or slave) - m_device_port.write(m_is_master ? 0xE0 : 0xF0); + // Select the device (master or slave) + m_device_port.write(m_is_master ? 0xE0 : 0xF0); - // Send the flush command - m_command_port.write(0xE7); + // Send the flush command + m_command_port.write(0xE7); - // Make sure the device is there - uint8_t status = m_command_port.read(); - if(status == 0x00) - return; + // Make sure the device is there + uint8_t status = m_command_port.read(); + if (status == 0x00) + return; + // Wait for the device to be ready or for an error to occur + while (((status & 0x80) == 0x80) && ((status & 0x01) != 0x01)) + status = m_command_port.read(); - // Wait for the device to be ready or for an error to occur - while (((status & 0x80) == 0x80) && ((status & 0x01) != 0x01)) - status = m_command_port.read(); + // Check for an error + if (status & 0x01) + return; - // Check for an error - if(status & 0x01) - return; - - // ... + // ... } /** @@ -235,9 +234,7 @@ void AdvancedTechnologyAttachment::flush() { * @return The name of the device */ string AdvancedTechnologyAttachment::device_name() { - - return "Advanced Technology Attachment"; - + return "Advanced Technology Attachment"; } /** @@ -246,5 +243,5 @@ string AdvancedTechnologyAttachment::device_name() { * @return The name of the vendor */ string AdvancedTechnologyAttachment::vendor_name() { - return "IDE"; + return "IDE"; } diff --git a/kernel/src/drivers/disk/disk.cpp b/kernel/src/drivers/disk/disk.cpp index 0d35a3e5..ed97099a 100644 --- a/kernel/src/drivers/disk/disk.cpp +++ b/kernel/src/drivers/disk/disk.cpp @@ -10,6 +10,7 @@ using namespace MaxOS::drivers; using namespace MaxOS::drivers::disk; Disk::Disk() = default; + Disk::~Disk() = default; /** @@ -18,9 +19,9 @@ Disk::~Disk() = default; * @param sector The sector to read from * @param data_buffer The buffer to read the data into */ -void Disk::read(uint32_t sector, common::buffer_t *data_buffer) { +void Disk::read(uint32_t sector, common::buffer_t* data_buffer) { - size_t amount = (data_buffer -> capacity() > 512) ? 512 : data_buffer -> capacity(); + size_t amount = (data_buffer->capacity() > 512) ? 512 : data_buffer->capacity(); read(sector, data_buffer, amount); } @@ -32,13 +33,13 @@ void Disk::read(uint32_t sector, common::buffer_t *data_buffer) { * @param data_buffer The buffer to read the data into * @param amount The amount of data to read */ -void Disk::read(uint32_t sector, buffer_t* data_buffer, size_t amount){ +void Disk::read(uint32_t sector, buffer_t *data_buffer, size_t amount) { } void Disk::write(uint32_t sector, common::buffer_t const *data) { - size_t amount = (data -> capacity() > 512) ? 512 : data -> capacity(); + size_t amount = (data->capacity() > 512) ? 512 : data->capacity(); write(sector, data, amount); } @@ -50,8 +51,7 @@ void Disk::write(uint32_t sector, common::buffer_t const *data) { * @param data_buffer The buffer to write the data into * @param amount The amount of data to write */ -void Disk::write(uint32_t sector, const buffer_t* data, size_t count) -{ +void Disk::write(uint32_t sector, const buffer_t *data, size_t count) { } /** @@ -59,16 +59,14 @@ void Disk::write(uint32_t sector, const buffer_t* data, size_t count) * * This function is used to flush the disk cache to ensure that all data is written to the disk. */ -void Disk::flush() -{ +void Disk::flush() { } /** * @brief Activate the disk driver */ -void Disk::activate() -{ - Driver::activate(); +void Disk::activate() { + Driver::activate(); } /** @@ -76,9 +74,8 @@ void Disk::activate() * * @return The name of the device */ -string Disk::device_name() -{ - return "Disk"; +string Disk::device_name() { + return "Disk"; } /** @@ -86,7 +83,6 @@ string Disk::device_name() * * @return The name of the vendor */ -string Disk::vendor_name() -{ - return "Generic"; +string Disk::vendor_name() { + return "Generic"; } \ No newline at end of file diff --git a/kernel/src/drivers/disk/ide.cpp b/kernel/src/drivers/disk/ide.cpp index fc39ba67..404ee9be 100644 --- a/kernel/src/drivers/disk/ide.cpp +++ b/kernel/src/drivers/disk/ide.cpp @@ -11,21 +11,22 @@ using namespace MaxOS::drivers::disk; using namespace MaxOS::filesystem; using namespace MaxOS::filesystem::partition; -IntegratedDriveElectronicsController::IntegratedDriveElectronicsController(PeripheralComponentInterconnectDeviceDescriptor* device_descriptor) +IntegratedDriveElectronicsController::IntegratedDriveElectronicsController( +PeripheralComponentInterconnectDeviceDescriptor *device_descriptor) { - // TODO: Use the device descriptor to get the port base and add the devices dynamically + // TODO: Use the device descriptor to get the port base and add the devices dynamically - // Primary - auto primary_maser = new AdvancedTechnologyAttachment(0x1F0, true); - auto primary_slave = new AdvancedTechnologyAttachment(0x1F0, false); - devices.insert(primary_maser, true); - devices.insert(primary_slave, false); + // Primary + auto primary_maser = new AdvancedTechnologyAttachment(0x1F0, true); + auto primary_slave = new AdvancedTechnologyAttachment(0x1F0, false); + devices.insert(primary_maser, true); + devices.insert(primary_slave, false); - // Secondary - auto secondary_maser = new AdvancedTechnologyAttachment(0x170, true); - auto secondary_slave = new AdvancedTechnologyAttachment(0x170, false); - devices.insert(secondary_maser, true); - devices.insert(secondary_slave, false); + // Secondary + auto secondary_maser = new AdvancedTechnologyAttachment(0x170, true); + auto secondary_slave = new AdvancedTechnologyAttachment(0x170, false); + devices.insert(secondary_maser, true); + devices.insert(secondary_slave, false); } @@ -34,67 +35,60 @@ IntegratedDriveElectronicsController::~IntegratedDriveElectronicsController() = /** * @brief Initialise the IDE controller by identifying the devices */ -void IntegratedDriveElectronicsController::initialise() -{ +void IntegratedDriveElectronicsController::initialise() { + + // Loop through the devices and identify them + for (auto &device: devices) { + + // Check if the device is present + auto ata_device = device.first; + if (ata_device == nullptr) + continue; + + // Identify the device + bool exists = ata_device->identify(); - // Loop through the devices and identify them - for(auto& device : devices) - { - - // Check if the device is present - auto ata_device = device.first; - if(ata_device == nullptr) - continue; - - // Identify the device - bool exists = ata_device->identify(); - - // Remove the device if it does not exist - if(!exists) - { - devices.erase(ata_device); - delete ata_device; - continue; - } - } - - // Log the init done - Logger::DEBUG() << "IDE Controller: Initialised " << devices.size() << " devices\n"; + // Remove the device if it does not exist + if (!exists) { + devices.erase(ata_device); + delete ata_device; + continue; + } + } + + // Log the init done + Logger::DEBUG() << "IDE Controller: Initialised " << devices.size() << " devices\n"; } /** * @brief Activate the IDE controller by mounting the devices to the virtual file system */ -void IntegratedDriveElectronicsController::activate() -{ +void IntegratedDriveElectronicsController::activate() { - // Loop through the devices and load the partitions - for(auto& device : devices) - { + // Loop through the devices and load the partitions + for (auto &device: devices) { - // Ensure there is a device and that it is the master - if(device.first == nullptr || !device.second) - continue; + // Ensure there is a device and that it is the master + if (device.first == nullptr || !device.second) + continue; - // Mount the device - MSDOSPartition::mount_partitions(device.first); + // Mount the device + MSDOSPartition::mount_partitions(device.first); - } + } } /** * @brief Get the vendor name */ -string IntegratedDriveElectronicsController::vendor_name() -{ - return "Intel"; +string IntegratedDriveElectronicsController::vendor_name() { + return "Intel"; } /** * @brief Get the device name */ -string IntegratedDriveElectronicsController::device_name() -{ - return "PIIX4"; +string IntegratedDriveElectronicsController::device_name() { + return "PIIX4"; } \ No newline at end of file diff --git a/kernel/src/drivers/driver.cpp b/kernel/src/drivers/driver.cpp index c4481837..619054a1 100644 --- a/kernel/src/drivers/driver.cpp +++ b/kernel/src/drivers/driver.cpp @@ -4,6 +4,7 @@ #include #include + using namespace MaxOS; using namespace MaxOS::common; using namespace MaxOS::drivers; @@ -11,19 +12,20 @@ using namespace MaxOS::memory; using namespace MaxOS::hardwarecommunication; Driver::Driver() = default; + Driver::~Driver() = default; /** * @brief activate the driver */ -void Driver::activate(){ +void Driver::activate() { } /** * @brief deactivate the driver */ -void Driver::deactivate(){ +void Driver::deactivate() { } @@ -39,8 +41,8 @@ void Driver::initialise() { * * @return How long in milliseconds it took to reset the driver */ -uint32_t Driver::reset(){ - return 0; +uint32_t Driver::reset() { + return 0; } /** @@ -48,9 +50,8 @@ uint32_t Driver::reset(){ * * @return The vendor name of the driver */ -string Driver::vendor_name() -{ - return "Generic"; +string Driver::vendor_name() { + return "Generic"; } /** @@ -58,57 +59,49 @@ string Driver::vendor_name() * * @return The device name of the driver */ -string Driver::device_name() -{ - return "Unknown Driver"; +string Driver::device_name() { + return "Unknown Driver"; } -DriverSelectorEventHandler::DriverSelectorEventHandler() -= default; +DriverSelectorEventHandler::DriverSelectorEventHandler() = default; -DriverSelectorEventHandler::~DriverSelectorEventHandler() -= default; +DriverSelectorEventHandler::~DriverSelectorEventHandler() = default; /** * @brief This function is called when a driver is selected * * @param driver The driver that was selected */ -void DriverSelectorEventHandler::on_driver_selected(Driver*) -{ +void DriverSelectorEventHandler::on_driver_selected(Driver *) { } -DriverSelector::DriverSelector() -= default; +DriverSelector::DriverSelector() = default; -DriverSelector::~DriverSelector() -= default; +DriverSelector::~DriverSelector() = default; /** * @brief Select the drivers */ -void DriverSelector::select_drivers(DriverSelectorEventHandler*) -{ +void DriverSelector::select_drivers(DriverSelectorEventHandler *) { } -DriverManager::DriverManager() -{ +DriverManager::DriverManager() { - Logger::INFO() << "Setting up Driver Manager \n"; - add_driver_selector(new PeripheralComponentInterconnectController); - // add_driver_selector(new UniversalSerialBusController); + Logger::INFO() << "Setting up Driver Manager \n"; + add_driver_selector(new PeripheralComponentInterconnectController); + // add_driver_selector(new UniversalSerialBusController); } DriverManager::~DriverManager() { - // Remove any drivers that are still attached - while (!m_drivers.empty()) - remove_driver(*m_drivers.begin()); + // Remove any drivers that are still attached + while (!m_drivers.empty()) + remove_driver(*m_drivers.begin()); - // Free the driver selectors - for(auto & driver_selector : m_driver_selectors) - delete driver_selector; + // Free the driver selectors + for (auto &driver_selector: m_driver_selectors) + delete driver_selector; } @@ -117,8 +110,8 @@ DriverManager::~DriverManager() { * * @param driver The driver to add */ -void DriverManager::add_driver(Driver* driver){ - m_drivers.push_back(driver); +void DriverManager::add_driver(Driver *driver) { + m_drivers.push_back(driver); } /** @@ -126,40 +119,35 @@ void DriverManager::add_driver(Driver* driver){ * * @param driver The driver to remove */ -void DriverManager::remove_driver(Driver* driver) { - - // deactivate the driver - driver -> deactivate(); +void DriverManager::remove_driver(Driver *driver) { - // Remove the driver - m_drivers.erase(driver); + driver->deactivate(); + m_drivers.erase(driver); } /** * @brief When a driver is selected add it to the manager */ -void DriverManager::on_driver_selected(Driver* driver) { - add_driver(driver); +void DriverManager::on_driver_selected(Driver *driver) { + add_driver(driver); } /** * @brief Add a driver selector to the manager */ -void DriverManager::add_driver_selector(DriverSelector* driver_selector) { +void DriverManager::add_driver_selector(DriverSelector *driver_selector) { - // Add the driver selector - m_driver_selectors.push_back(driver_selector); + m_driver_selectors.push_back(driver_selector); } /** * @brief Remove a driver selector from the manager */ -void DriverManager::remove_driver_selector(DriverSelector* driver_selector) { +void DriverManager::remove_driver_selector(DriverSelector *driver_selector) { - // Remove the driver selector - m_driver_selectors.erase(driver_selector); + m_driver_selectors.erase(driver_selector); } @@ -168,11 +156,11 @@ void DriverManager::remove_driver_selector(DriverSelector* driver_selector) { */ void DriverManager::find_drivers() { - Logger::INFO() << "Finding Drivers \n"; + Logger::INFO() << "Finding Drivers \n"; - // Select the drivers - for(auto & driver_selector : m_driver_selectors) - driver_selector -> select_drivers(this); + // Select the drivers + for (auto &driver_selector: m_driver_selectors) + driver_selector->select_drivers(this); } /** @@ -182,20 +170,19 @@ void DriverManager::find_drivers() { */ uint32_t DriverManager::reset_devices() { - Logger::INFO() << "Resetting Devices \n"; + Logger::INFO() << "Resetting Devices \n"; - uint32_t resetWaitTime = 0; - for(auto & driver : m_drivers) - { - // Reset the driver - uint32_t waitTime = driver->reset(); + uint32_t resetWaitTime = 0; + for (auto &driver: m_drivers) { + // Reset the driver + uint32_t waitTime = driver->reset(); - // If the wait time is longer than the current longest wait time, set it as the new longest wait time - if(waitTime > resetWaitTime) - resetWaitTime = waitTime; - } + // If the wait time is longer than the current longest wait time, set it as the new longest wait time + if (waitTime > resetWaitTime) + resetWaitTime = waitTime; + } - return resetWaitTime; + return resetWaitTime; } /** @@ -203,11 +190,11 @@ uint32_t DriverManager::reset_devices() { */ void DriverManager::initialise_drivers() { - Logger::INFO() << "Initialising Drivers \n"; + Logger::INFO() << "Initialising Drivers \n"; - // Initialise the drivers - for(auto& driver : m_drivers) - driver->initialise(); + // Initialise the drivers + for (auto &driver: m_drivers) + driver->initialise(); } @@ -216,9 +203,9 @@ void DriverManager::initialise_drivers() { */ void DriverManager::deactivate_drivers() { - // Deactivate the drivers - for(auto & driver : m_drivers) - driver->deactivate(); + // Deactivate the drivers + for (auto &driver: m_drivers) + driver->deactivate(); } @@ -228,10 +215,10 @@ void DriverManager::deactivate_drivers() { void DriverManager::activate_drivers() { - Logger::INFO() << "Activating Drivers \n"; + Logger::INFO() << "Activating Drivers \n"; - // Activate the drivers - for(auto & driver : m_drivers) - driver->activate(); + // Activate the drivers + for (auto &driver: m_drivers) + driver->activate(); } diff --git a/kernel/src/drivers/peripherals/keyboard.cpp b/kernel/src/drivers/peripherals/keyboard.cpp index 3f9eaa18..8ff75780 100644 --- a/kernel/src/drivers/peripherals/keyboard.cpp +++ b/kernel/src/drivers/peripherals/keyboard.cpp @@ -4,18 +4,13 @@ #include - - using namespace MaxOS; using namespace MaxOS::common; using namespace MaxOS::drivers; using namespace MaxOS::drivers::peripherals; using namespace MaxOS::hardwarecommunication; - -///___Handler___ - -KeyboardEventHandler::KeyboardEventHandler()= default; +KeyboardEventHandler::KeyboardEventHandler() = default; KeyboardEventHandler::~KeyboardEventHandler() = default; @@ -25,8 +20,7 @@ KeyboardEventHandler::~KeyboardEventHandler() = default; * @param key_down_code The keycode of the key that was pressed * @param key_down_state The state of the keyboard when the key was pressed */ -void KeyboardEventHandler::on_key_down(KeyCode, KeyboardState) -{ +void KeyboardEventHandler::on_key_down(KeyCode, KeyboardState) { } /** @@ -35,8 +29,7 @@ void KeyboardEventHandler::on_key_down(KeyCode, KeyboardState) * @param key_up_code The keycode of the key that was released * @param key_up_state The state of the keyboard when the key was released */ -void KeyboardEventHandler::on_key_up(KeyCode, KeyboardState) -{ +void KeyboardEventHandler::on_key_up(KeyCode, KeyboardState) { } /** @@ -45,31 +38,25 @@ void KeyboardEventHandler::on_key_up(KeyCode, KeyboardState) * @param event The event to handle * @return The event that was passed with the data modified */ -Event* KeyboardEventHandler::on_event(Event *event) { +Event *KeyboardEventHandler::on_event(Event *event) { - switch (event -> type) { + switch (event->type) { - case KeyboardEvents::KEYDOWN: - this->on_key_down(((KeyDownEvent *)event)->key_code, - ((KeyDownEvent *)event)->keyboard_state); - break; + case KeyboardEvents::KEYDOWN: + this->on_key_down(((KeyDownEvent *) event)->key_code, ((KeyDownEvent *) event)->keyboard_state); + break; - case KeyboardEvents::KEYUP: - this->on_key_up(((KeyUpEvent *)event)->key_code, - ((KeyUpEvent *)event)->keyboard_state); - break; + case KeyboardEvents::KEYUP: + this->on_key_up(((KeyUpEvent *) event)->key_code, ((KeyUpEvent *) event)->keyboard_state); + break; - default: - break; - } + default: + break; + } - return event; + return event; } - - -///___Driver___ - KeyboardDriver::KeyboardDriver() : InterruptHandler(0x21, 0x1, 0x12), m_data_port(0x60), @@ -79,41 +66,41 @@ KeyboardDriver::KeyboardDriver() } -KeyboardDriver::~KeyboardDriver()= default; +KeyboardDriver::~KeyboardDriver() = default; /** * @brief activate the keyboard driver */ void KeyboardDriver::activate() { - // Wait for user to stop pressing key (this is for the start-up key e.g. hold 'F12' for boot menu or hold 'del' for bios ) - while (m_command_port.read() & 0x1) - m_data_port.read(); + // Wait for user to stop pressing key (this is for the start-up key e.g. hold 'F12' for boot menu or hold 'del' for bios ) + while (m_command_port.read() & 0x1) + m_data_port.read(); - // Enable keyboard interrupts - m_command_port.write(0xAE); + // Enable keyboard interrupts + m_command_port.write(0xAE); - // Get the current state of the keyboard - m_command_port.write(0x20); - uint8_t status = (m_data_port.read() | 1) & ~ 0x10; + // Get the current state of the keyboard + m_command_port.write(0x20); + uint8_t status = (m_data_port.read() | 1) & ~0x10; - // Reset the keyboard - m_command_port.write(0x60); - m_data_port.write(status); + // Reset the keyboard + m_command_port.write(0x60); + m_data_port.write(status); - // Activate the keyboard - m_data_port.write(0xF4); + // Activate the keyboard + m_data_port.write(0xF4); } /** * @brief deactivate the keyboard driver */ -void KeyboardDriver::handle_interrupt(){ +void KeyboardDriver::handle_interrupt() { - // Pass the scan code to the m_handlers - uint8_t key = m_data_port.read(); - for(auto& handler : m_input_stream_event_handlers) - handler -> on_stream_read(key); + // Pass the scan code to the m_handlers + uint8_t key = m_data_port.read(); + for (auto &handler: m_input_stream_event_handlers) + handler->on_stream_read(key); } /** @@ -121,18 +108,13 @@ void KeyboardDriver::handle_interrupt(){ * @return The device name */ string KeyboardDriver::device_name() { - return "Keyboard"; + return "Keyboard"; } -///___State___ - - KeyboardState::KeyboardState() = default; KeyboardState::~KeyboardState() = default; -///___Interpreter___ - KeyboardInterpreter::KeyboardInterpreter() : InputStreamEventHandler() { @@ -141,18 +123,16 @@ KeyboardInterpreter::KeyboardInterpreter() KeyboardInterpreter::~KeyboardInterpreter() = default; -void KeyboardInterpreter::on_key_read(bool released, const KeyboardState& state, KeyCode key_code) { +void KeyboardInterpreter::on_key_read(bool released, const KeyboardState &state, KeyCode key_code) { - // Pass the key event to the handlers - if(released) - raise_event(new KeyUpEvent(key_code, state)); - else - raise_event(new KeyDownEvent(key_code, state)); + // Pass the key event to the handlers + if (released) + raise_event(new KeyUpEvent(key_code, state)); + else + raise_event(new KeyDownEvent(key_code, state)); } -///___Interpreter EN_US___ - KeyboardInterpreterEN_US::KeyboardInterpreterEN_US() : KeyboardInterpreter() { @@ -169,545 +149,490 @@ KeyboardInterpreterEN_US::~KeyboardInterpreterEN_US() = default; void KeyboardInterpreterEN_US::on_stream_read(uint8_t scan_code) { - // Check if the key was released - bool released = (scan_code & 0x80) && (m_current_extended_code_1 - || (scan_code != 0xe1)) && (m_next_is_extended_code_0 - || (scan_code != 0xe0)); - - // Clear the released bit - if (released) - scan_code &= ~0x80; - - // Start of an extended scan code - if (scan_code == 0xe0) - { - m_next_is_extended_code_0 = true; - return; - } - - // Handle extended scan codes - ScanCodeType type = ScanCodeType::REGULAR; - if (m_next_is_extended_code_0) - { - - type = ScanCodeType::EXTENDED; - m_next_is_extended_code_0 = false; - - // Check if the scan_code represents a shift key and return (fake shift) - if ((KeyboardInterpreterEN_US::KeyCodeEN_US)scan_code == KeyCodeEN_US::leftShift || (KeyboardInterpreterEN_US::KeyCodeEN_US)scan_code == KeyCodeEN_US::rightShift) - return; - } - - // If the scan_code is 0xe1, set the e1Code flag to 1 and return - if (scan_code == 0xe1) - { - m_current_extended_code_1 = 1; - return; - } - - // If e1Code is 1, set e1Code to 2, store the scan_code in e1CodeBuffer, and return - if (m_current_extended_code_1 == 1) - { - m_current_extended_code_1 = 2; - m_extended_code_1_buffer = scan_code; - return; - } - - // If e1Code is 2, set keyType to 2, reset e1Code, and update e1CodeBuffer - if (m_current_extended_code_1 == 2) - { - type = ScanCodeType::EXTENDED; - m_current_extended_code_1 = 0; - m_extended_code_1_buffer |= (((uint16_t)scan_code) << 8); - } - - // Scan code value manipulations - bool is_shifting = this ->m_keyboard_state.left_shift || this ->m_keyboard_state.right_shift; - bool should_be_upper_case = is_shifting != this ->m_keyboard_state.caps_lock; - - - // TODO: Probably a better way to do this (investigate when adding more keyboard layouts) - if(type == ScanCodeType::REGULAR) - switch ((KeyCodeEN_US)scan_code) { - - // First row - case KeyCodeEN_US::escape: - on_key_read(released, this ->m_keyboard_state, KeyCode::escape); - break; - - case KeyCodeEN_US::f1: - on_key_read(released, this ->m_keyboard_state, KeyCode::f1); - break; - - case KeyCodeEN_US::f2: - on_key_read(released, this ->m_keyboard_state, KeyCode::f2); - break; - - case KeyCodeEN_US::f3: - on_key_read(released, this ->m_keyboard_state, KeyCode::f3); - break; - - case KeyCodeEN_US::f4: - on_key_read(released, this ->m_keyboard_state, KeyCode::f4); - break; - - case KeyCodeEN_US::f5: - on_key_read(released, this ->m_keyboard_state, KeyCode::f5); - break; - - case KeyCodeEN_US::f6: - on_key_read(released, this ->m_keyboard_state, KeyCode::f6); - break; - - case KeyCodeEN_US::f7: - on_key_read(released, this ->m_keyboard_state, KeyCode::f7); - break; - - case KeyCodeEN_US::f8: - on_key_read(released, this ->m_keyboard_state, KeyCode::f8); - break; - - case KeyCodeEN_US::f9: - on_key_read(released, this ->m_keyboard_state, KeyCode::f9); - break; - - case KeyCodeEN_US::f10: - on_key_read(released, this ->m_keyboard_state, KeyCode::f10); - break; - - case KeyCodeEN_US::f11: - on_key_read(released, this ->m_keyboard_state, KeyCode::f11); - break; - - case KeyCodeEN_US::f12: - on_key_read(released, this ->m_keyboard_state, KeyCode::f12); - break; - - case KeyCodeEN_US::printScreen: - on_key_read(released, this ->m_keyboard_state, this ->m_keyboard_state.number_pad_lock - ? KeyCode::numberPadMultiply : KeyCode::printScreen); - break; - - case KeyCodeEN_US::scrollLock: - on_key_read(released, this ->m_keyboard_state, KeyCode::scrollLock); - break; - - // Second row - case KeyCodeEN_US::squigglyLine: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::squigglyLine : KeyCode::slantedApostrophe); - break; - - case KeyCodeEN_US::one: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::exclamationMark : KeyCode::one); - break; - - case KeyCodeEN_US::two: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::atSign: KeyCode::two); - break; - - case KeyCodeEN_US::three: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::hash : KeyCode::three); - break; - - case KeyCodeEN_US::four: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::dollarSign : KeyCode::four); - break; - - case KeyCodeEN_US::five: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::percentSign : KeyCode::five); - break; - - case KeyCodeEN_US::six: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::powerSign : KeyCode::six); - break; - - case KeyCodeEN_US::seven: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::andSign : KeyCode::seven); - break; - - case KeyCodeEN_US::eight: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::multiply : KeyCode::eight); - break; - - case KeyCodeEN_US::nine: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::openBracket : KeyCode::nine); - break; - - case KeyCodeEN_US::zero: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::closeBracket : KeyCode::zero); - break; - - case KeyCodeEN_US::minus: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::underscore : KeyCode::minus); - break; - - case KeyCodeEN_US::equals: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::plus : KeyCode::equals); - break; - - case KeyCodeEN_US::backspace: - on_key_read(released, this ->m_keyboard_state, KeyCode::backspace); - break; - - case KeyCodeEN_US::insert: - on_key_read(released, this ->m_keyboard_state, this ->m_keyboard_state.number_pad_lock ? KeyCode::numberPadZero : KeyCode::insert); - break; - - case KeyCodeEN_US::home: - on_key_read(released, this ->m_keyboard_state, this ->m_keyboard_state.number_pad_lock - ? KeyCode::numberPadSeven : KeyCode::home); - break; - - case KeyCodeEN_US::pageUp: - on_key_read(released, this ->m_keyboard_state, this ->m_keyboard_state.number_pad_lock ? KeyCode::numberPadNine : KeyCode::pageUp); - break; - - case KeyCodeEN_US::numberPadLock: - - // Ensure this is not a repeat - if(!released){ - this ->m_keyboard_state.number_pad_lock = !this ->m_keyboard_state.number_pad_lock; - } - on_key_read(released, this ->m_keyboard_state, KeyCode::numberPadLock); - break; - - case KeyCodeEN_US::numberPadForwardSlash: - - // Check if number pad lock is on - if(this ->m_keyboard_state.number_pad_lock){ - on_key_read(released, this ->m_keyboard_state, KeyCode::numberPadForwardSlash); - }else{ - - // Normal Forward Slash - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::questionMark : KeyCode::forwardSlash); - } - break; - - // Number Pad Multiply is same as print screen - - case KeyCodeEN_US::numberPadMinus: - on_key_read(released, this ->m_keyboard_state, KeyCode::numberPadMinus); - break; - - // Third row - case KeyCodeEN_US::tab: - on_key_read(released, this ->m_keyboard_state, KeyCode::tab); - break; - - case KeyCodeEN_US::Q: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::Q : KeyCode::q); - break; - - case KeyCodeEN_US::W: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::W : KeyCode::w); - break; - - case KeyCodeEN_US::E: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::E : KeyCode::e); - break; - - case KeyCodeEN_US::R: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::R : KeyCode::r); - break; - - case KeyCodeEN_US::T: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::T : KeyCode::t); - break; - - case KeyCodeEN_US::Y: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::Y : KeyCode::y); - break; - - case KeyCodeEN_US::U: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::U : KeyCode::u); - break; - - case KeyCodeEN_US::I: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::I : KeyCode::i); - break; - - case KeyCodeEN_US::O: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::O : KeyCode::o); - break; - - case KeyCodeEN_US::P: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::P : KeyCode::p); - break; - - case KeyCodeEN_US::openSquareBracket: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::openCurlyBracket : KeyCode::openSquareBracket); - break; - - case KeyCodeEN_US::closeSquareBracket: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::closeCurlyBracket : KeyCode::closeSquareBracket); - break; - - case KeyCodeEN_US::backslash: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::lineThing : KeyCode::backslash); - break; - - case KeyCodeEN_US::deleteKey: - on_key_read(released, this ->m_keyboard_state, this ->m_keyboard_state.number_pad_lock - ? KeyCode::numberPadFullStop : KeyCode::deleteKey); - break; - - case KeyCodeEN_US::end: - on_key_read(released, this ->m_keyboard_state, this ->m_keyboard_state.number_pad_lock ? KeyCode::numberPadOne : KeyCode::end); - break; - - case KeyCodeEN_US::pageDown: - on_key_read(released, this ->m_keyboard_state, this ->m_keyboard_state.number_pad_lock - ? KeyCode::numberPadThree : KeyCode::pageDown); - break; - - // Number pad 7 is same as home - - case KeyCodeEN_US::numberPadEight: - on_key_read(released, this ->m_keyboard_state, this ->m_keyboard_state.number_pad_lock - ? KeyCode::numberPadEight : KeyCode::upArrow); - break; - - // Number pad 9 is same as page up - - case KeyCodeEN_US::numberPadPlus: - on_key_read(released, this ->m_keyboard_state, KeyCode::numberPadPlus); - break; - - // Fourth row - - case KeyCodeEN_US::capsLock: - // Ensure this is not a repeat - if(!released){ - this ->m_keyboard_state.caps_lock = !this ->m_keyboard_state.caps_lock; - } - - on_key_read(released, this ->m_keyboard_state, KeyCode::capsLock); - break; - - case KeyCodeEN_US::A: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::A : KeyCode::a); - break; - - case KeyCodeEN_US::S: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::S : KeyCode::s); - break; - - case KeyCodeEN_US::D: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::D : KeyCode::d); - break; - - case KeyCodeEN_US::F: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::F : KeyCode::f); - break; - - case KeyCodeEN_US::G: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::G : KeyCode::g); - break; - - case KeyCodeEN_US::H: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::H : KeyCode::h); - break; - - case KeyCodeEN_US::J: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::J : KeyCode::j); - break; - - case KeyCodeEN_US::K: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::K : KeyCode::k); - break; - - case KeyCodeEN_US::L: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::L : KeyCode::l); - break; - - case KeyCodeEN_US::semicolon: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::colon : KeyCode::semicolon); - break; - - case KeyCodeEN_US::apostrophe: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::quotationMark : KeyCode::apostrophe); - break; - - case KeyCodeEN_US::enter: - on_key_read(released, this ->m_keyboard_state, this ->m_keyboard_state.number_pad_lock - ? KeyCode::numberPadEnter : KeyCode::enter); - break; - - case KeyCodeEN_US::numberPadFour: - on_key_read(released, this ->m_keyboard_state, this ->m_keyboard_state.number_pad_lock ? KeyCode::numberPadFour : KeyCode::leftArrow); - break; - - case KeyCodeEN_US::numberPadFive: - on_key_read(released, this ->m_keyboard_state, KeyCode::numberPadFive); - break; - - case KeyCodeEN_US::numberPadSix: - on_key_read(released, this ->m_keyboard_state, this ->m_keyboard_state.number_pad_lock ? KeyCode::numberPadSix : KeyCode::rightArrow); - break; - - // Fifth row - case KeyCodeEN_US::leftShift: - this ->m_keyboard_state.left_shift = !this ->m_keyboard_state.left_shift; - on_key_read(released, this ->m_keyboard_state, KeyCode::leftShift); - break; - - case KeyCodeEN_US::Z: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::Z : KeyCode::z); - break; - - case KeyCodeEN_US::X: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::X : KeyCode::x); - break; - - case KeyCodeEN_US::C: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::C : KeyCode::c); - break; - - case KeyCodeEN_US::V: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::V : KeyCode::v); - break; - - case KeyCodeEN_US::B: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::B : KeyCode::b); - break; - - case KeyCodeEN_US::N: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::N : KeyCode::n); - break; - - case KeyCodeEN_US::M: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::M : KeyCode::m); - break; - - case KeyCodeEN_US::comma: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::lessThan : KeyCode::comma); - break; - - case KeyCodeEN_US::fullStop: - on_key_read(released, this ->m_keyboard_state, - should_be_upper_case ? KeyCode::greaterThan : KeyCode::fullStop); - break; - - // Forward slash is same as number pad forward slash + // Check if the key was released + bool released = (scan_code & 0x80) && (m_current_extended_code_1 + || (scan_code != 0xe1)) && (m_next_is_extended_code_0 + || (scan_code != 0xe0)); + + // Clear the released bit + if (released) + scan_code &= ~0x80; + + // Start of an extended scan code + if (scan_code == 0xe0) { + m_next_is_extended_code_0 = true; + return; + } + + // Handle extended scan codes + ScanCodeType type = ScanCodeType::REGULAR; + if (m_next_is_extended_code_0) { + + type = ScanCodeType::EXTENDED; + m_next_is_extended_code_0 = false; + + // Check if the scan_code represents a shift key and return (fake shift) + if ((KeyboardInterpreterEN_US::KeyCodeEN_US) scan_code == KeyCodeEN_US::leftShift || + (KeyboardInterpreterEN_US::KeyCodeEN_US) scan_code == KeyCodeEN_US::rightShift) + return; + } + + // If the scan_code is 0xe1, set the e1Code flag to 1 and return + if (scan_code == 0xe1) { + m_current_extended_code_1 = 1; + return; + } + + // If e1Code is 1, set e1Code to 2, store the scan_code in e1CodeBuffer, and return + if (m_current_extended_code_1 == 1) { + m_current_extended_code_1 = 2; + m_extended_code_1_buffer = scan_code; + return; + } + + // If e1Code is 2, set keyType to 2, reset e1Code, and update e1CodeBuffer + if (m_current_extended_code_1 == 2) { + type = ScanCodeType::EXTENDED; + m_current_extended_code_1 = 0; + m_extended_code_1_buffer |= (((uint16_t) scan_code) << 8); + } + + // Scan code value manipulations + bool is_shifting = this->m_keyboard_state.left_shift || this->m_keyboard_state.right_shift; + bool should_be_upper_case = is_shifting != this->m_keyboard_state.caps_lock; + + // TODO: Probably a better way to do this (investigate when adding more keyboard layouts) + if (type == ScanCodeType::REGULAR) + switch ((KeyCodeEN_US) scan_code) { + + /// First row + case KeyCodeEN_US::escape: + on_key_read(released, this->m_keyboard_state, KeyCode::escape); + break; + + case KeyCodeEN_US::f1: + on_key_read(released, this->m_keyboard_state, KeyCode::f1); + break; + + case KeyCodeEN_US::f2: + on_key_read(released, this->m_keyboard_state, KeyCode::f2); + break; + + case KeyCodeEN_US::f3: + on_key_read(released, this->m_keyboard_state, KeyCode::f3); + break; + + case KeyCodeEN_US::f4: + on_key_read(released, this->m_keyboard_state, KeyCode::f4); + break; + + case KeyCodeEN_US::f5: + on_key_read(released, this->m_keyboard_state, KeyCode::f5); + break; + + case KeyCodeEN_US::f6: + on_key_read(released, this->m_keyboard_state, KeyCode::f6); + break; + + case KeyCodeEN_US::f7: + on_key_read(released, this->m_keyboard_state, KeyCode::f7); + break; + + case KeyCodeEN_US::f8: + on_key_read(released, this->m_keyboard_state, KeyCode::f8); + break; + + case KeyCodeEN_US::f9: + on_key_read(released, this->m_keyboard_state, KeyCode::f9); + break; + + case KeyCodeEN_US::f10: + on_key_read(released, this->m_keyboard_state, KeyCode::f10); + break; + + case KeyCodeEN_US::f11: + on_key_read(released, this->m_keyboard_state, KeyCode::f11); + break; + + case KeyCodeEN_US::f12: + on_key_read(released, this->m_keyboard_state, KeyCode::f12); + break; + + case KeyCodeEN_US::printScreen: + on_key_read(released, this->m_keyboard_state, this->m_keyboard_state.number_pad_lock ? KeyCode::numberPadMultiply : KeyCode::printScreen); + break; + + case KeyCodeEN_US::scrollLock: + on_key_read(released, this->m_keyboard_state, KeyCode::scrollLock); + break; + + /// Second row + case KeyCodeEN_US::squigglyLine: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::squigglyLine : KeyCode::slantedApostrophe); + break; + + case KeyCodeEN_US::one: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::exclamationMark : KeyCode::one); + break; + + case KeyCodeEN_US::two: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::atSign : KeyCode::two); + break; + + case KeyCodeEN_US::three: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::hash : KeyCode::three); + break; + + case KeyCodeEN_US::four: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::dollarSign : KeyCode::four); + break; + + case KeyCodeEN_US::five: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::percentSign : KeyCode::five); + break; + + case KeyCodeEN_US::six: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::powerSign : KeyCode::six); + break; + + case KeyCodeEN_US::seven: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::andSign : KeyCode::seven); + break; + + case KeyCodeEN_US::eight: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::multiply : KeyCode::eight); + break; + + case KeyCodeEN_US::nine: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::openBracket : KeyCode::nine); + break; + + case KeyCodeEN_US::zero: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::closeBracket : KeyCode::zero); + break; + + case KeyCodeEN_US::minus: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::underscore : KeyCode::minus); + break; + + case KeyCodeEN_US::equals: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::plus : KeyCode::equals); + break; + + case KeyCodeEN_US::backspace: + on_key_read(released, this->m_keyboard_state, KeyCode::backspace); + break; + + case KeyCodeEN_US::insert: + on_key_read(released, this->m_keyboard_state, this->m_keyboard_state.number_pad_lock ? KeyCode::numberPadZero : KeyCode::insert); + break; + + case KeyCodeEN_US::home: + on_key_read(released, this->m_keyboard_state, this->m_keyboard_state.number_pad_lock + ? KeyCode::numberPadSeven : KeyCode::home); + break; + + case KeyCodeEN_US::pageUp: + on_key_read(released, this->m_keyboard_state, this->m_keyboard_state.number_pad_lock ? KeyCode::numberPadNine : KeyCode::pageUp); + break; + + case KeyCodeEN_US::numberPadLock: + + // Ensure this is not a repeat + if (!released) { + this->m_keyboard_state.number_pad_lock = !this->m_keyboard_state.number_pad_lock; + } + on_key_read(released, this->m_keyboard_state, KeyCode::numberPadLock); + break; + + case KeyCodeEN_US::numberPadForwardSlash: + + // Check if number pad lock is on + if (this->m_keyboard_state.number_pad_lock) { + on_key_read(released, this->m_keyboard_state, KeyCode::numberPadForwardSlash); + } else { + + // Normal Forward Slash + on_key_read(released, this->m_keyboard_state, + should_be_upper_case ? KeyCode::questionMark : KeyCode::forwardSlash); + } + break; + + // Number Pad Multiply is same as print screen + + case KeyCodeEN_US::numberPadMinus: + on_key_read(released, this->m_keyboard_state, KeyCode::numberPadMinus); + break; + + // Third row + case KeyCodeEN_US::tab: + on_key_read(released, this->m_keyboard_state, KeyCode::tab); + break; + + case KeyCodeEN_US::Q: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::Q : KeyCode::q); + break; + + case KeyCodeEN_US::W: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::W : KeyCode::w); + break; + + case KeyCodeEN_US::E: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::E : KeyCode::e); + break; + + case KeyCodeEN_US::R: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::R : KeyCode::r); + break; + + case KeyCodeEN_US::T: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::T : KeyCode::t); + break; + + case KeyCodeEN_US::Y: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::Y : KeyCode::y); + break; + + case KeyCodeEN_US::U: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::U : KeyCode::u); + break; + + case KeyCodeEN_US::I: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::I : KeyCode::i); + break; + + case KeyCodeEN_US::O: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::O : KeyCode::o); + break; + + case KeyCodeEN_US::P: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::P : KeyCode::p); + break; + + case KeyCodeEN_US::openSquareBracket: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::openCurlyBracket : KeyCode::openSquareBracket); + break; + + case KeyCodeEN_US::closeSquareBracket: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::closeCurlyBracket : KeyCode::closeSquareBracket); + break; + + case KeyCodeEN_US::backslash: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::lineThing : KeyCode::backslash); + break; + + case KeyCodeEN_US::deleteKey: + on_key_read(released, this->m_keyboard_state, this->m_keyboard_state.number_pad_lock + ? KeyCode::numberPadFullStop : KeyCode::deleteKey); + break; + + case KeyCodeEN_US::end: + on_key_read(released, this->m_keyboard_state, this->m_keyboard_state.number_pad_lock ? KeyCode::numberPadOne : KeyCode::end); + break; + + case KeyCodeEN_US::pageDown: + on_key_read(released, this->m_keyboard_state, this->m_keyboard_state.number_pad_lock + ? KeyCode::numberPadThree : KeyCode::pageDown); + break; + + // Number pad 7 is same as home + + case KeyCodeEN_US::numberPadEight: + on_key_read(released, this->m_keyboard_state, this->m_keyboard_state.number_pad_lock ? KeyCode::numberPadEight : KeyCode::upArrow); + break; + + // Number pad 9 is same as page up + + case KeyCodeEN_US::numberPadPlus: + on_key_read(released, this->m_keyboard_state, KeyCode::numberPadPlus); + break; + + /// Fourth row + case KeyCodeEN_US::capsLock: + // Ensure this is not a repeat + if (!released) { + this->m_keyboard_state.caps_lock = !this->m_keyboard_state.caps_lock; + } + + on_key_read(released, this->m_keyboard_state, KeyCode::capsLock); + break; + + case KeyCodeEN_US::A: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::A : KeyCode::a); + break; + + case KeyCodeEN_US::S: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::S : KeyCode::s); + break; + + case KeyCodeEN_US::D: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::D : KeyCode::d); + break; + + case KeyCodeEN_US::F: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::F : KeyCode::f); + break; + + case KeyCodeEN_US::G: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::G : KeyCode::g); + break; + + case KeyCodeEN_US::H: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::H : KeyCode::h); + break; + + case KeyCodeEN_US::J: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::J : KeyCode::j); + break; + + case KeyCodeEN_US::K: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::K : KeyCode::k); + break; + + case KeyCodeEN_US::L: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::L : KeyCode::l); + break; + + case KeyCodeEN_US::semicolon: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::colon : KeyCode::semicolon); + break; + + case KeyCodeEN_US::apostrophe: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::quotationMark : KeyCode::apostrophe); + break; + + case KeyCodeEN_US::enter: + on_key_read(released, this->m_keyboard_state, this->m_keyboard_state.number_pad_lock ? KeyCode::numberPadEnter : KeyCode::enter); + break; + + case KeyCodeEN_US::numberPadFour: + on_key_read(released, this->m_keyboard_state, this->m_keyboard_state.number_pad_lock ? KeyCode::numberPadFour : KeyCode::leftArrow); + break; + + case KeyCodeEN_US::numberPadFive: + on_key_read(released, this->m_keyboard_state, KeyCode::numberPadFive); + break; + + case KeyCodeEN_US::numberPadSix: + on_key_read(released, this->m_keyboard_state, this->m_keyboard_state.number_pad_lock ? KeyCode::numberPadSix : KeyCode::rightArrow); + break; + + /// Fifth row + case KeyCodeEN_US::leftShift: + this->m_keyboard_state.left_shift = !this->m_keyboard_state.left_shift; + on_key_read(released, this->m_keyboard_state, KeyCode::leftShift); + break; + + case KeyCodeEN_US::Z: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::Z : KeyCode::z); + break; + + case KeyCodeEN_US::X: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::X : KeyCode::x); + break; + + case KeyCodeEN_US::C: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::C : KeyCode::c); + break; + + case KeyCodeEN_US::V: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::V : KeyCode::v); + break; + + case KeyCodeEN_US::B: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::B : KeyCode::b); + break; + + case KeyCodeEN_US::N: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::N : KeyCode::n); + break; + + case KeyCodeEN_US::M: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::M : KeyCode::m); + break; + + case KeyCodeEN_US::comma: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::lessThan : KeyCode::comma); + break; + + case KeyCodeEN_US::fullStop: + on_key_read(released, this->m_keyboard_state, should_be_upper_case ? KeyCode::greaterThan : KeyCode::fullStop); + break; + + // Forward slash is same as number pad forward slash + + case KeyCodeEN_US::rightShift: + // Check if this is a repeat + if (!released) { + this->m_keyboard_state.right_shift = !this->m_keyboard_state.right_shift; + } + + on_key_read(released, this->m_keyboard_state, KeyCode::rightShift); + break; + + // Up Arrow is the same as number pad 8 - case KeyCodeEN_US::rightShift: - // Check if this is a repeat - if(!released){ - this ->m_keyboard_state.right_shift = !this ->m_keyboard_state.right_shift; - } - - on_key_read(released, this ->m_keyboard_state, KeyCode::rightShift); - break; + // Number pad 1 is the same as end - // Up Arrow is the same as number pad 8 + case KeyCodeEN_US::numberPadTwo: + on_key_read(released, this->m_keyboard_state, this->m_keyboard_state.number_pad_lock ? KeyCode::numberPadTwo : KeyCode::downArrow); + break; - // Number pad 1 is the same as end + // Number pad 3 is the same as page down - case KeyCodeEN_US::numberPadTwo: - on_key_read(released, this ->m_keyboard_state, this ->m_keyboard_state.number_pad_lock ? KeyCode::numberPadTwo : KeyCode::downArrow); - break; + // Number pad enter is the same as enter - // Number pad 3 is the same as page down + /// Sixth row + case KeyCodeEN_US::leftControl: + // Check if this is a repeat + if (!released) { + this->m_keyboard_state.left_control = !this->m_keyboard_state.left_control; + this->m_keyboard_state.right_control = !this->m_keyboard_state.right_control; + } - // Number pad enter is the same as enter + on_key_read(released, this->m_keyboard_state, KeyCode::leftControl); + break; - // Sixth row - case KeyCodeEN_US::leftControl: - // Check if this is a repeat - if(!released){ - this ->m_keyboard_state.left_control = !this ->m_keyboard_state.left_control; - this ->m_keyboard_state.right_control = !this ->m_keyboard_state.right_control; - } + case KeyCodeEN_US::leftOS: + on_key_read(released, this->m_keyboard_state, KeyCode::leftOS); + break; - on_key_read(released, this ->m_keyboard_state, KeyCode::leftControl); - break; + case KeyCodeEN_US::leftAlt: + // Check if this is a repeat + if (!released) { + this->m_keyboard_state.left_alt = !this->m_keyboard_state.left_alt; + this->m_keyboard_state.right_alt = !this->m_keyboard_state.right_alt; + } - case KeyCodeEN_US::leftOS: - on_key_read(released, this ->m_keyboard_state, KeyCode::leftOS); - break; + on_key_read(released, this->m_keyboard_state, KeyCode::leftAlt); + break; - case KeyCodeEN_US::leftAlt: - // Check if this is a repeat - if(!released){ - this ->m_keyboard_state.left_alt = !this ->m_keyboard_state.left_alt; - this ->m_keyboard_state.right_alt = !this ->m_keyboard_state.right_alt; - } + case KeyCodeEN_US::space: + on_key_read(released, this->m_keyboard_state, KeyCode::space); + break; - on_key_read(released, this ->m_keyboard_state, KeyCode::leftAlt); - break; + // Right Alt is the same as left alt - case KeyCodeEN_US::space: - on_key_read(released, this ->m_keyboard_state, KeyCode::space); - break; + // Right Control is the same as left control - // Right Alt is the same as left alt + // Left Arrow is the same as number pad 4 - // Right Control is the same as left control + // Down Arrow is the same as number pad 2 - // Left Arrow is the same as number pad 4 + // Right Arrow is the same as number pad 6 - // Down Arrow is the same as number pad 2 + // Number pad 0 is the same as insert - // Right Arrow is the same as number pad 6 + // Number pad full stop is the same as delete - // Number pad 0 is the same as insert + default: + break; - // Number pad full stop is the same as delete + } - default: - break; - - } - } -KeyDownEvent::KeyDownEvent(KeyCode keyCode, const KeyboardState& keyboardState) +KeyDownEvent::KeyDownEvent(KeyCode keyCode, const KeyboardState &keyboardState) : Event(KeyboardEvents::KEYDOWN), key_code(keyCode), keyboard_state(keyboardState) @@ -716,7 +641,7 @@ KeyDownEvent::KeyDownEvent(KeyCode keyCode, const KeyboardState& keyboardState) KeyDownEvent::~KeyDownEvent() = default; -KeyUpEvent::KeyUpEvent(KeyCode key_code, const KeyboardState& keyboard_state) +KeyUpEvent::KeyUpEvent(KeyCode key_code, const KeyboardState &keyboard_state) : Event(KeyboardEvents::KEYUP), key_code(key_code), keyboard_state(keyboard_state) diff --git a/kernel/src/drivers/peripherals/mouse.cpp b/kernel/src/drivers/peripherals/mouse.cpp index d9040ccf..bc5841da 100644 --- a/kernel/src/drivers/peripherals/mouse.cpp +++ b/kernel/src/drivers/peripherals/mouse.cpp @@ -10,9 +10,6 @@ using namespace MaxOS::drivers; using namespace MaxOS::drivers::peripherals; using namespace MaxOS::hardwarecommunication; - -///__Handler__ - MouseEventHandler::MouseEventHandler() = default; /** @@ -21,25 +18,25 @@ MouseEventHandler::MouseEventHandler() = default; * @param event The event that was triggered * @return The event that was triggered with the modified data */ -Event* MouseEventHandler::on_event(Event *event) { - switch (event->type){ +Event *MouseEventHandler::on_event(Event *event) { + switch (event->type) { - case MouseEvents::MOVE: - this->on_mouse_move_event(((MouseMoveEvent *)event)->x, - ((MouseMoveEvent *)event)->y); - break; + case MouseEvents::MOVE: + this->on_mouse_move_event(((MouseMoveEvent *) event)->x, + ((MouseMoveEvent *) event)->y); + break; - case MouseEvents::DOWN: - this->on_mouse_down_event(((MouseDownEvent *)event)->button); - break; + case MouseEvents::DOWN: + this->on_mouse_down_event(((MouseDownEvent *) event)->button); + break; - case MouseEvents::UP: - this->on_mouse_up_event(((MouseUpEvent *)event)->button); - break; + case MouseEvents::UP: + this->on_mouse_up_event(((MouseUpEvent *) event)->button); + break; - } + } - return event; + return event; } /** @@ -47,7 +44,7 @@ Event* MouseEventHandler::on_event(Event *event) { * * @param button The button that was pressed */ -void MouseEventHandler::on_mouse_down_event(uint8_t){ +void MouseEventHandler::on_mouse_down_event(uint8_t) { } @@ -56,7 +53,7 @@ void MouseEventHandler::on_mouse_down_event(uint8_t){ * * @param button The button that was released */ -void MouseEventHandler::on_mouse_up_event(uint8_t){ +void MouseEventHandler::on_mouse_up_event(uint8_t) { } @@ -66,14 +63,12 @@ void MouseEventHandler::on_mouse_up_event(uint8_t){ * @param x How much the mouse moved in the x direction * @param y How much the mouse moved in the y direction */ -void MouseEventHandler::on_mouse_move_event(int8_t, int8_t){ +void MouseEventHandler::on_mouse_move_event(int8_t, int8_t) { } MouseEventHandler::~MouseEventHandler() = default; -///__Driver__ - MouseDriver::MouseDriver() : InterruptHandler(0x2C, 0xC, 0x28), data_port(0x60), @@ -81,69 +76,69 @@ MouseDriver::MouseDriver() { } -MouseDriver::~MouseDriver()= default; + +MouseDriver::~MouseDriver() = default; /** * @brief activate the mouse */ void MouseDriver::activate() { + // Get the current state of the mouse + command_port.write(0x20); + uint8_t status = (data_port.read() | 2); - // Get the current state of the mouse - command_port.write(0x20); - uint8_t status = (data_port.read() | 2); - - // Write the new state - command_port.write(0x60); - data_port.write(status); + // Write the new state + command_port.write(0x60); + data_port.write(status); - // Tell the PIC to start listening to the mouse - command_port.write(0xAB); + // Tell the PIC to start listening to the mouse + command_port.write(0xAB); - // Activate the mouse - command_port.write(0xD4); - data_port.write(0xF4); - data_port.read(); + // Activate the mouse + command_port.write(0xD4); + data_port.write(0xF4); + data_port.read(); } /** * @brief Handle the mouse interrupt */ -void MouseDriver::handle_interrupt(){ +void MouseDriver::handle_interrupt() { - // Check if there is data to handle - uint8_t status = command_port.read(); - if(!(status & 0x20)) - return; + // Check if there is data to handle + uint8_t status = command_port.read(); + if (!(status & 0x20)) + return; - // Read the data - m_buffer[m_offset] = data_port.read(); - m_offset = (m_offset + 1) % 3; + // Read the data + m_buffer[m_offset] = data_port.read(); + m_offset = (m_offset + 1) % 3; - // If the mouse data transmission is incomplete (3rd piece of data isn't through) - if(m_offset != 0) - return; + // If the mouse data transmission is incomplete (3rd piece of data isn't through) + if (m_offset != 0) + return; - // If the mouse is moved (y-axis is inverted) - if(m_buffer[1] != 0 || m_buffer[2] != 0) - raise_event(new MouseMoveEvent(m_buffer[1], -m_buffer[2])); + // If the mouse is moved (y-axis is inverted) + if (m_buffer[1] != 0 || m_buffer[2] != 0) + raise_event(new MouseMoveEvent(m_buffer[1], -m_buffer[2])); - // Handle button presses - for (int i = 0; i < 3; ++i) { + // Handle button presses + for (int i = 0; i < 3; ++i) { - // This button is still in the same state - if((m_buffer[0] & (0x1 << i)) == (m_buttons & (0x1 << i))) - continue; + // This button is still in the same state + if ((m_buffer[0] & (0x1 << i)) == (m_buttons & (0x1 << i))) + continue; - // Pass to handlers - bool is_pressed = (m_buttons & (0x1 << i)) != 0; - if(is_pressed) - raise_event(new MouseDownEvent(m_buffer[i])); - else - raise_event(new MouseUpEvent(m_buffer[i])); - } + // Pass to handlers + bool is_pressed = (m_buttons & (0x1 << i)) != 0; + if (is_pressed) + raise_event(new MouseDownEvent(m_buffer[i])); + else + raise_event(new MouseUpEvent(m_buffer[i])); + } - m_buttons = m_buffer[0]; + m_buttons = m_buffer[0]; } /** @@ -152,11 +147,9 @@ void MouseDriver::handle_interrupt(){ * @return The name of the device */ string MouseDriver::device_name() { - return "Mouse"; + return "Mouse"; } -///__Events__ - MouseUpEvent::MouseUpEvent(uint8_t button) : Event(MouseEvents::UP), button(button) @@ -181,4 +174,4 @@ MouseMoveEvent::MouseMoveEvent(int8_t x, int8_t y) { } -MouseMoveEvent::~MouseMoveEvent() = default; +MouseMoveEvent::~MouseMoveEvent() = default; \ No newline at end of file diff --git a/kernel/src/drivers/video/vesa.cpp b/kernel/src/drivers/video/vesa.cpp index ba9acf5c..cc0639ed 100644 --- a/kernel/src/drivers/video/vesa.cpp +++ b/kernel/src/drivers/video/vesa.cpp @@ -12,35 +12,36 @@ using namespace MaxOS::memory; using namespace MaxOS::system; using namespace MaxOS::common; -VideoElectronicsStandardsAssociation::VideoElectronicsStandardsAssociation(multiboot_tag_framebuffer* framebuffer_info) +VideoElectronicsStandardsAssociation::VideoElectronicsStandardsAssociation(multiboot_tag_framebuffer *framebuffer_info) : m_framebuffer_info(framebuffer_info) { - Logger::INFO() << "Setting up VESA driver\n"; + Logger::INFO() << "Setting up VESA driver\n"; - // Save the framebuffer info - m_bpp = m_framebuffer_info->common.framebuffer_bpp; - m_pitch = m_framebuffer_info->common.framebuffer_pitch; - m_framebuffer_size = m_framebuffer_info->common.framebuffer_height * m_pitch; - this -> set_mode(framebuffer_info->common.framebuffer_width,framebuffer_info->common.framebuffer_height, framebuffer_info->common.framebuffer_bpp); - Logger::DEBUG() << "Framebuffer: bpp=" << m_bpp << ", pitch=" << m_pitch << ", size=" << m_framebuffer_size << "\n"; + // Save the framebuffer info + m_bpp = m_framebuffer_info->common.framebuffer_bpp; + m_pitch = m_framebuffer_info->common.framebuffer_pitch; + m_framebuffer_size = m_framebuffer_info->common.framebuffer_height * m_pitch; + this->set_mode(framebuffer_info->common.framebuffer_width, framebuffer_info->common.framebuffer_height, + framebuffer_info->common.framebuffer_bpp); + Logger::DEBUG() << "Framebuffer: bpp=" << m_bpp << ", pitch=" << m_pitch << ", size=" << m_framebuffer_size << "\n"; - // Map the frame buffer into the higher half - auto physical_address = (uint64_t)m_framebuffer_info -> common.framebuffer_addr; - m_framebuffer_address = (uint64_t*)PhysicalMemoryManager::to_dm_region(physical_address); - PhysicalMemoryManager::s_current_manager -> map_area((physical_address_t*)physical_address, m_framebuffer_address, m_framebuffer_size, Write | Present); + // Map the frame buffer into the higher half + auto physical_address = (uint64_t) m_framebuffer_info->common.framebuffer_addr; + m_framebuffer_address = (uint64_t *) PhysicalMemoryManager::to_dm_region(physical_address); + PhysicalMemoryManager::s_current_manager->map_area((physical_address_t *) physical_address, m_framebuffer_address, m_framebuffer_size, Write | Present); - // Reserve the physical memory - size_t pages = PhysicalMemoryManager::size_to_frames(m_framebuffer_size); - PhysicalMemoryManager::s_current_manager->reserve(m_framebuffer_info->common.framebuffer_addr, pages); + // Reserve the physical memory + size_t pages = PhysicalMemoryManager::size_to_frames(m_framebuffer_size); + PhysicalMemoryManager::s_current_manager->reserve(m_framebuffer_info->common.framebuffer_addr, pages); - // Log info - Logger::DEBUG() << "Framebuffer address: physical=0x" << (uint64_t)physical_address << ", virtual=0x" << (uint64_t)m_framebuffer_address << "\n"; - Logger::DEBUG() << "Framebuffer mapped: 0x" << (uint64_t)m_framebuffer_address << " - 0x" << (uint64_t)(m_framebuffer_address + m_framebuffer_size) << " (pages: " << pages << ")\n"; + // Log info + Logger::DEBUG() << "Framebuffer address: physical=0x" << (uint64_t) physical_address << ", virtual=0x" << (uint64_t) m_framebuffer_address << "\n"; + Logger::DEBUG() << "Framebuffer mapped: 0x" << (uint64_t) m_framebuffer_address << " - 0x" << (uint64_t) (m_framebuffer_address + m_framebuffer_size) << " (pages: " << pages << ")\n"; } -VideoElectronicsStandardsAssociation::~VideoElectronicsStandardsAssociation()= default; +VideoElectronicsStandardsAssociation::~VideoElectronicsStandardsAssociation() = default; /** * @brief Sets the mode of the VESA driver @@ -52,10 +53,10 @@ VideoElectronicsStandardsAssociation::~VideoElectronicsStandardsAssociation()= d */ bool VideoElectronicsStandardsAssociation::internal_set_mode(uint32_t width, uint32_t height, uint32_t color_depth) { - // Can only use the mode set up already by grub - return width == m_framebuffer_info->common.framebuffer_width - && height == m_framebuffer_info->common.framebuffer_height - && color_depth == m_framebuffer_info->common.framebuffer_bpp; + // Can only use the mode set up already by grub + return width == m_framebuffer_info->common.framebuffer_width + && height == m_framebuffer_info->common.framebuffer_height + && color_depth == m_framebuffer_info->common.framebuffer_bpp; } @@ -69,10 +70,10 @@ bool VideoElectronicsStandardsAssociation::internal_set_mode(uint32_t width, uin */ bool VideoElectronicsStandardsAssociation::supports_mode(uint32_t width, uint32_t height, uint32_t color_depth) { - // Check if the mode is supported - return width == m_framebuffer_info->common.framebuffer_width - && height == m_framebuffer_info->common.framebuffer_height - && color_depth == m_framebuffer_info->common.framebuffer_bpp; + // Check if the mode is supported + return width == m_framebuffer_info->common.framebuffer_width + && height == m_framebuffer_info->common.framebuffer_height + && color_depth == m_framebuffer_info->common.framebuffer_bpp; } /** @@ -84,8 +85,8 @@ bool VideoElectronicsStandardsAssociation::supports_mode(uint32_t width, uint32_ */ void VideoElectronicsStandardsAssociation::render_pixel_32_bit(uint32_t x, uint32_t y, uint32_t colour) { - auto* pixel_address = (uint32_t*)((uint8_t *)m_framebuffer_address + (m_pitch * y) + (m_bpp * x) / 8); - *pixel_address = colour; + auto *pixel_address = (uint32_t *) ((uint8_t *) m_framebuffer_address + (m_pitch * y) + (m_bpp * x) / 8); + *pixel_address = colour; } @@ -98,8 +99,8 @@ void VideoElectronicsStandardsAssociation::render_pixel_32_bit(uint32_t x, uint3 */ uint32_t VideoElectronicsStandardsAssociation::get_rendered_pixel_32_bit(uint32_t x, uint32_t y) { - auto* pixel_address = (uint32_t*)((uint8_t *)m_framebuffer_address + m_pitch * (y) + m_bpp * (x) / 8); - return *pixel_address; + auto *pixel_address = (uint32_t *) ((uint8_t *) m_framebuffer_address + m_pitch * (y) + m_bpp * (x) / 8); + return *pixel_address; } /** @@ -109,8 +110,8 @@ uint32_t VideoElectronicsStandardsAssociation::get_rendered_pixel_32_bit(uint32_ */ string VideoElectronicsStandardsAssociation::vendor_name() { - // Creator of the VESA standard - return "NEC Home Electronics"; + // Creator of the VESA standard + return "NEC Home Electronics"; } /** @@ -119,5 +120,5 @@ string VideoElectronicsStandardsAssociation::vendor_name() { * @return The name of the device */ string VideoElectronicsStandardsAssociation::device_name() { - return "VESA compatible graphics card"; + return "VESA compatible graphics card"; } diff --git a/kernel/src/drivers/video/vga.cpp b/kernel/src/drivers/video/vga.cpp index 5bf09245..0894866b 100644 --- a/kernel/src/drivers/video/vga.cpp +++ b/kernel/src/drivers/video/vga.cpp @@ -33,51 +33,49 @@ VideoGraphicsArray::~VideoGraphicsArray() = default; * * @param registers The VGA registers to write to. */ -void VideoGraphicsArray::write_registers(uint8_t* registers) -{ - // Move to the next register - m_misc_port.write(*(registers++)); - - // Set the sequencer registers - for (uint8_t i = 0; i < 5; i++ ) { - m_sequence_index_port.write(i); - m_sequence_data_port.write(*(registers++)); - } - - // Clear protection bit to enable writing to CR0-7 - m_crtc_index_port.write(0x03); - crtc_data_port.write(crtc_data_port.read() | 0x80); - m_crtc_index_port.write(0x11); - crtc_data_port.write(crtc_data_port.read() | ~0x80); - - // Ensure protection bit is set - registers[0x03] = registers[0x03] | 0x80; - registers[0x11] = registers[0x11] & ~0x80; - - // Write the CRTC registers - for (uint8_t i = 0; i < 25; i++ ) { - m_crtc_index_port.write(i); - crtc_data_port.write(*(registers++)); - } - - // Write the graphics controller registers - for(uint8_t i = 0; i < 9; i++) - { - m_graphics_controller_index_port.write(i); - m_graphics_controller_data_port.write(*(registers++)); - } - - // Write the attribute controller registers - for(uint8_t i = 0; i < 21; i++) - { - m_attribute_controller_reset_port.read(); - m_attribute_controller_index_port.write(i); - m_attribute_controller_write_port.write(*(registers++)); - } - - // Re-Lock CRTC and unblank display - m_attribute_controller_reset_port.read(); - m_attribute_controller_index_port.write(0x20); +void VideoGraphicsArray::write_registers(uint8_t *registers) { + + // Move to the next register + m_misc_port.write(*(registers++)); + + // Set the sequencer registers + for (uint8_t i = 0; i < 5; i++) { + m_sequence_index_port.write(i); + m_sequence_data_port.write(*(registers++)); + } + + // Clear protection bit to enable writing to CR0-7 + m_crtc_index_port.write(0x03); + crtc_data_port.write(crtc_data_port.read() | 0x80); + m_crtc_index_port.write(0x11); + crtc_data_port.write(crtc_data_port.read() | ~0x80); + + // Ensure protection bit is set + registers[0x03] = registers[0x03] | 0x80; + registers[0x11] = registers[0x11] & ~0x80; + + // Write the CRTC registers + for (uint8_t i = 0; i < 25; i++) { + m_crtc_index_port.write(i); + crtc_data_port.write(*(registers++)); + } + + // Write the graphics controller registers + for (uint8_t i = 0; i < 9; i++) { + m_graphics_controller_index_port.write(i); + m_graphics_controller_data_port.write(*(registers++)); + } + + // Write the attribute controller registers + for (uint8_t i = 0; i < 21; i++) { + m_attribute_controller_reset_port.read(); + m_attribute_controller_index_port.write(i); + m_attribute_controller_write_port.write(*(registers++)); + } + + // Re-Lock CRTC and unblank display + m_attribute_controller_reset_port.read(); + m_attribute_controller_index_port.write(0x20); } @@ -86,9 +84,8 @@ void VideoGraphicsArray::write_registers(uint8_t* registers) * * @return True if the specified resolution is supported, otherwise false. */ -bool VideoGraphicsArray::supports_mode(uint32_t width, uint32_t height, uint32_t colour_depth) -{ - return width == 320 && height == 200 && colour_depth == 8; +bool VideoGraphicsArray::supports_mode(uint32_t width, uint32_t height, uint32_t colour_depth) { + return width == 320 && height == 200 && colour_depth == 8; } /** @@ -100,32 +97,31 @@ bool VideoGraphicsArray::supports_mode(uint32_t width, uint32_t height, uint32_t * * @return True if the card was able to set the resolution, otherwise false. */ -bool VideoGraphicsArray::internal_set_mode(uint32_t width, uint32_t height, uint32_t colour_depth) -{ - - //Values from osdev / modes.c - unsigned char g_320x200x256[] = - { - // MISC - 0x63, - // SEQ - 0x03, 0x01, 0x0F, 0x00, 0x0E, - // CRTC - 0x5F, 0x4F, 0x50, 0x82, 0x54, 0x80, 0xBF, 0x1F, - 0x00, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x9C, 0x0E, 0x8F, 0x28, 0x40, 0x96, 0xB9, 0xA3, - 0xFF, - // GC - 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x05, 0x0F, - 0xFF, - // AC - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, - 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, - 0x41, 0x00, 0x0F, 0x00, 0x00 - }; - - write_registers(g_320x200x256); - return true; +bool VideoGraphicsArray::internal_set_mode(uint32_t width, uint32_t height, uint32_t colour_depth) { + + //Values from osdev / modes.c + unsigned char g_320x200x256[] = + { + // MISC + 0x63, + // SEQ + 0x03, 0x01, 0x0F, 0x00, 0x0E, + // CRTC + 0x5F, 0x4F, 0x50, 0x82, 0x54, 0x80, 0xBF, 0x1F, + 0x00, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x9C, 0x0E, 0x8F, 0x28, 0x40, 0x96, 0xB9, 0xA3, + 0xFF, + // GC + 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x05, 0x0F, + 0xFF, + // AC + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x41, 0x00, 0x0F, 0x00, 0x00 + }; + + write_registers(g_320x200x256); + return true; } /** @@ -133,27 +129,29 @@ bool VideoGraphicsArray::internal_set_mode(uint32_t width, uint32_t height, uint * * @return The framebuffer address. */ -uint8_t* VideoGraphicsArray::get_frame_buffer_segment() -{ - - // Optimise so that don't have to read and write to the port every time - return (uint8_t*)0xA0000; - - //read data from index number 6 - m_graphics_controller_index_port.write(0x06); - uint8_t segmentNumber = - m_graphics_controller_data_port.read() & (3<<2); //Shift by 2 as only interested in bits 3 & 4 (& 3 so all the other bits are removed) - switch(segmentNumber) - { - default: - case 0<<2: return (uint8_t*)nullptr; - case 1<<2: return (uint8_t*)0xA0000; - case 2<<2: return (uint8_t*)0xB0000; - case 3<<2: return (uint8_t*)0xB8000; - } +uint8_t *VideoGraphicsArray::get_frame_buffer_segment() { + + // Optimise so that don't have to read and write to the port every time + return (uint8_t *) 0xA0000; + + //read data from index number 6 + m_graphics_controller_index_port.write(0x06); + uint8_t segmentNumber = + m_graphics_controller_data_port.read() & + (3 << 2); //Shift by 2 as only interested in bits 3 & 4 (& 3 so all the other bits are removed) + switch (segmentNumber) { + default: + case 0 << 2: + return (uint8_t *) nullptr; + case 1 << 2: + return (uint8_t *) 0xA0000; + case 2 << 2: + return (uint8_t *) 0xB0000; + case 3 << 2: + return (uint8_t *) 0xB8000; + } } - /** * @brief Puts a 8 bit pixel on the screen. * @@ -161,10 +159,10 @@ uint8_t* VideoGraphicsArray::get_frame_buffer_segment() * @param y The y coordinate of the pixel. * @param colour The colour of the pixel. */ -void VideoGraphicsArray::render_pixel_8_bit(uint32_t x, uint32_t y, uint8_t colour){ +void VideoGraphicsArray::render_pixel_8_bit(uint32_t x, uint32_t y, uint8_t colour) { - uint8_t*pixel_address = get_frame_buffer_segment() + 320 * y + x; - *pixel_address = colour; + uint8_t *pixel_address = get_frame_buffer_segment() + 320 * y + x; + *pixel_address = colour; } /** @@ -176,8 +174,8 @@ void VideoGraphicsArray::render_pixel_8_bit(uint32_t x, uint32_t y, uint8_t colo */ uint8_t VideoGraphicsArray::get_rendered_pixel_8_bit(uint32_t x, uint32_t y) { - uint8_t* pixel_address = get_frame_buffer_segment() + 320 * y + x; - return* pixel_address; + uint8_t *pixel_address = get_frame_buffer_segment() + 320 * y + x; + return *pixel_address; } /** @@ -187,8 +185,8 @@ uint8_t VideoGraphicsArray::get_rendered_pixel_8_bit(uint32_t x, uint32_t y) { */ string VideoGraphicsArray::vendor_name() { - // VGA was made by IBM - return "IBM"; + // VGA was made by IBM + return "IBM"; } /** @@ -197,5 +195,5 @@ string VideoGraphicsArray::vendor_name() { * @return The name of the device. */ string VideoGraphicsArray::device_name() { - return "VGA compatible graphics card"; + return "VGA compatible graphics card"; } \ No newline at end of file diff --git a/kernel/src/drivers/video/video.cpp b/kernel/src/drivers/video/video.cpp index f5a98331..7b805130 100644 --- a/kernel/src/drivers/video/video.cpp +++ b/kernel/src/drivers/video/video.cpp @@ -7,7 +7,6 @@ using namespace MaxOS::drivers::video; using namespace MaxOS::common; - VideoDriver::VideoDriver() = default; VideoDriver::~VideoDriver() = default; diff --git a/kernel/src/filesystem/ext2.cpp b/kernel/src/filesystem/ext2.cpp index 25e899c8..59096835 100644 --- a/kernel/src/filesystem/ext2.cpp +++ b/kernel/src/filesystem/ext2.cpp @@ -25,9 +25,9 @@ Ext2Volume::Ext2Volume(drivers::disk::Disk *disk, lba_t partition_offset) ASSERT(superblock.signature == 0xEF53, "Ext2 Filesystem doesnt have a valid signature\n"); // Version 0 has constant inode info - if(superblock.version_major < 1){ + if (superblock.version_major < 1) { superblock.first_inode = 11; - superblock.inode_size = 128; + superblock.inode_size = 128; } // Parse the superblock @@ -38,11 +38,11 @@ Ext2Volume::Ext2Volume(drivers::disk::Disk *disk, lba_t partition_offset) pointers_per_block = block_size / sizeof(uint32_t); inodes_per_block = block_size / superblock.inode_size; sectors_per_block = block_size / 512; - blocks_per_inode_table = (superblock.inode_size * superblock.inodes_per_group + (block_size - 1)) / block_size; - sectors_per_inode_table = (superblock.inode_size * superblock.inodes_per_group + (512 - 1)) / 512; + blocks_per_inode_table = (superblock.inode_size * superblock.inodes_per_group + (block_size - 1)) / block_size; + sectors_per_inode_table = (superblock.inode_size * superblock.inodes_per_group + (512 - 1)) / 512; // Read the block groups - block_groups = new block_group_descriptor_t*[total_block_groups] {nullptr}; + block_groups = new block_group_descriptor_t *[total_block_groups]{nullptr}; uint32_t bgdt_lba = partition_offset + block_group_descriptor_table_block * sectors_per_block; uint32_t sectors_to_read = (block_group_descriptor_table_size + block_size - 1) / block_size * sectors_per_block; buffer_t bg_buffer(sectors_to_read * 512); @@ -64,20 +64,20 @@ Ext2Volume::~Ext2Volume() = default; * @param block_num The block to update * @param buffer The buffer to read from */ -void Ext2Volume::write_block(uint32_t block_num, buffer_t* buffer) { +void Ext2Volume::write_block(uint32_t block_num, buffer_t *buffer) { // Ensure the buffer is in the right format - buffer -> set_offset(0); + buffer->set_offset(0); bool old = buffer->update_offset; - buffer -> update_offset = true; + buffer->update_offset = true; // Read each sector of the block for (size_t i = 0; i < sectors_per_block; ++i) disk->write(partition_offset + block_num * sectors_per_block + i, buffer, 512); // Reset buffer - buffer -> set_offset(0); - buffer -> update_offset = old; + buffer->set_offset(0); + buffer->update_offset = old; }; /** @@ -86,17 +86,17 @@ void Ext2Volume::write_block(uint32_t block_num, buffer_t* buffer) { * @param inode_num The inode index * @param inode The inode to read from */ -void Ext2Volume::write_inode(uint32_t inode_num, inode_t* inode) { +void Ext2Volume::write_inode(uint32_t inode_num, inode_t *inode) { // Locate the inode uint32_t group = (inode_num - 1) / superblock.inodes_per_group; uint32_t index = (inode_num - 1) % superblock.inodes_per_group; // Locate the block - uint32_t inode_table = block_groups[group] -> inode_table_address; - uint32_t offset = index * superblock.inode_size; - uint32_t block = offset / block_size; - uint32_t in_block_offset = offset % block_size; + uint32_t inode_table = block_groups[group]->inode_table_address; + uint32_t offset = index * superblock.inode_size; + uint32_t block = offset / block_size; + uint32_t in_block_offset = offset % block_size; // Read the inode buffer_t buffer(block_size); @@ -113,17 +113,17 @@ void Ext2Volume::write_inode(uint32_t inode_num, inode_t* inode) { * @param block_num The block to read * @param buffer The buffer to read into */ -void Ext2Volume::read_block(uint32_t block_num, buffer_t* buffer) const { +void Ext2Volume::read_block(uint32_t block_num, buffer_t *buffer) const { // Ensure the buffer is in the right format - buffer -> set_offset(0); + buffer->set_offset(0); // Read each sector of the block for (size_t i = 0; i < sectors_per_block; ++i) disk->read(partition_offset + block_num * sectors_per_block + i, buffer, 512); // Reset buffer - buffer -> set_offset(0); + buffer->set_offset(0); } @@ -141,10 +141,10 @@ inode_t Ext2Volume::read_inode(uint32_t inode_num) const { uint32_t index = (inode_num - 1) % superblock.inodes_per_group; // Locate the block - uint32_t inode_table = block_groups[group] -> inode_table_address; - uint32_t offset = index * superblock.inode_size; - uint32_t block = offset / block_size; - uint32_t in_block_offset = offset % block_size; + uint32_t inode_table = block_groups[group]->inode_table_address; + uint32_t offset = index * superblock.inode_size; + uint32_t block = offset / block_size; + uint32_t in_block_offset = offset % block_size; // Read the block buffer_t buffer(block_size); @@ -175,33 +175,33 @@ Vector Ext2Volume::allocate_blocks(uint32_t amount) { // No blocks to allocate if (!amount) - return {1,0}; + return {1, 0}; // Find the block group with enough free blocks - block_group_descriptor_t* block_group = block_groups[0]; + block_group_descriptor_t *block_group = block_groups[0]; for (uint32_t bg_index = 0; bg_index < total_block_groups; block_group = block_groups[++bg_index]) - if (block_group -> free_blocks >= amount) + if (block_group->free_blocks >= amount) return allocate_group_blocks(bg_index, amount); // No block group can contain the block so split across multiple - Vector result {}; - while (amount > 0){ + Vector result{}; + while (amount > 0) { // Find the block group with most free blocks block_group = block_groups[0]; uint32_t bg_index = 0; for (; bg_index < total_block_groups; ++bg_index) - if (block_groups[bg_index] -> free_blocks > block_group -> free_blocks) + if (block_groups[bg_index]->free_blocks > block_group->free_blocks) block_group = block_groups[bg_index]; // No space - if(block_group -> free_blocks == 0) + if (block_group->free_blocks == 0) return {1, 0}; // Allocate the remaining blocks auto allocated = allocate_group_blocks(bg_index, 1); amount -= allocated.size(); - for (auto block : allocated) + for (auto block: allocated) result.push_back(block); } @@ -218,28 +218,28 @@ Vector Ext2Volume::allocate_blocks(uint32_t amount) { common::Vector Ext2Volume::allocate_group_blocks(uint32_t block_group, uint32_t amount) { // Ensure enough space - block_group_descriptor_t* descriptor = block_groups[block_group]; - if (amount > descriptor -> free_blocks) + block_group_descriptor_t *descriptor = block_groups[block_group]; + if (amount > descriptor->free_blocks) return {1, 0}; // Prepare - Vector result {}; - buffer_t zeros (block_size); + Vector result{}; + buffer_t zeros(block_size); zeros.clear(); // Read bitmap - buffer_t bitmap (block_size); - read_block(descriptor -> block_usage_bitmap, &bitmap); + buffer_t bitmap(block_size); + read_block(descriptor->block_usage_bitmap, &bitmap); // Allocate the blocks for (uint32_t i = 0; i < superblock.blocks_per_group; ++i) { // Block is already used - if((bitmap.raw()[i / 8] & (1u << (i % 8))) != 0) + if ((bitmap.raw()[i / 8] & (1u << (i % 8))) != 0) continue; // Mark as used - descriptor -> free_blocks--; + descriptor->free_blocks--; superblock.unallocated_blocks--; bitmap.raw()[i / 8] |= (uint8_t) (1u << (i % 8)); @@ -250,12 +250,12 @@ common::Vector Ext2Volume::allocate_group_blocks(uint32_t block_group, // All done amount--; - if(!amount) + if (!amount) break; } // Save the changed metadata - write_block(descriptor -> block_usage_bitmap, &bitmap); + write_block(descriptor->block_usage_bitmap, &bitmap); write_back_block_groups(); write_back_superblock(); @@ -270,11 +270,10 @@ common::Vector Ext2Volume::allocate_group_blocks(uint32_t block_group, */ void Ext2Volume::free_group_blocks(uint32_t block_group, uint32_t amount, uint32_t start) { - // Read bitmap - block_group_descriptor_t* descriptor = block_groups[block_group]; - buffer_t bitmap (block_size); - read_block(descriptor -> block_usage_bitmap, &bitmap); + block_group_descriptor_t *descriptor = block_groups[block_group]; + buffer_t bitmap(block_size); + read_block(descriptor->block_usage_bitmap, &bitmap); // Convert start to be index based on the group instead of global start -= (block_group * superblock.blocks_per_group + superblock.starting_block); @@ -283,11 +282,11 @@ void Ext2Volume::free_group_blocks(uint32_t block_group, uint32_t amount, uint32 for (uint32_t i = start; i < start + amount; ++i) { // Block is already free (shouldn't happen) - if((bitmap.raw()[i / 8] & (1u << (i % 8))) == 0) + if ((bitmap.raw()[i / 8] & (1u << (i % 8))) == 0) continue; // Mark as free - descriptor -> free_blocks++; + descriptor->free_blocks++; superblock.unallocated_blocks++; bitmap.raw()[i / 8] &= ~(1u << (i % 8)); @@ -295,7 +294,7 @@ void Ext2Volume::free_group_blocks(uint32_t block_group, uint32_t amount, uint32 } // Save the changed metadata - write_block(descriptor -> block_usage_bitmap, &bitmap); + write_block(descriptor->block_usage_bitmap, &bitmap); write_back_block_groups(); write_back_superblock(); @@ -308,9 +307,9 @@ void Ext2Volume::free_group_blocks(uint32_t block_group, uint32_t amount, uint32 void Ext2Volume::write_back_block_groups() const { // Locate the block groups - uint32_t bgdt_blocks = (block_group_descriptor_table_size + block_size - 1) / block_size; - uint32_t sectors_to_write = bgdt_blocks * sectors_per_block; - uint32_t bgdt_lba = partition_offset + block_group_descriptor_table_block * sectors_per_block; + uint32_t bgdt_blocks = (block_group_descriptor_table_size + block_size - 1) / block_size; + uint32_t sectors_to_write = bgdt_blocks * sectors_per_block; + uint32_t bgdt_lba = partition_offset + block_group_descriptor_table_block * sectors_per_block; // Copy the block groups into the buffer buffer_t bg_buffer(sectors_to_write * 512); @@ -328,7 +327,6 @@ void Ext2Volume::write_back_block_groups() const { */ void Ext2Volume::write_back_superblock() { - // Store superblock buffer_t buffer(1024); buffer.copy_from(&superblock, sizeof(superblock_t)); @@ -360,15 +358,15 @@ uint32_t Ext2Volume::create_inode(bool is_directory) { ext2_lock.lock(); // Find the block group with enough free inodes - block_group_descriptor_t* block_group = block_groups[0]; + block_group_descriptor_t *block_group = block_groups[0]; uint32_t bg_index = 0; for (; bg_index < total_block_groups; block_group = block_groups[++bg_index]) - if (block_group -> free_inodes >= 1) + if (block_group->free_inodes >= 1) break; // Read bitmap buffer_t bitmap(block_size); - read_block(block_group -> block_inode_bitmap, &bitmap); + read_block(block_group->block_inode_bitmap, &bitmap); // First group contains reserved inodes uint32_t inode_index = 0; @@ -383,7 +381,7 @@ uint32_t Ext2Volume::create_inode(bool is_directory) { continue; // Mark as used - block_group -> free_inodes--; + block_group->free_inodes--; superblock.unallocated_inodes--; bitmap.raw()[inode_index / 8] |= (uint8_t) (1u << (inode_index % 8)); @@ -394,23 +392,23 @@ uint32_t Ext2Volume::create_inode(bool is_directory) { inode_index += bg_index * superblock.inodes_per_group + 1; // Save the changed metadata - write_block(block_group -> block_inode_bitmap, &bitmap); + write_block(block_group->block_inode_bitmap, &bitmap); write_back_block_groups(); write_back_superblock(); // Create the inode - inode_t inode {}; - inode.creation_time = time_to_epoch(Clock::active_clock()->get_time()); + inode_t inode{}; + inode.creation_time = time_to_epoch(Clock::active_clock()->get_time()); inode.last_modification_time = time_to_epoch(Clock::active_clock()->get_time()); - inode.block_pointers[0] = allocate_block(); - inode.hard_links = is_directory ? 2 : 1; - inode.type = ((uint16_t)(is_directory ? InodeType::DIRECTORY : InodeType::FILE) >> 12) & 0xF; - inode.permissions = (uint16_t)(is_directory ? InodePermissionsDefaults::DIRECTORY : InodePermissionsDefaults::FILE) & 0x0FFF; + inode.block_pointers[0] = allocate_block(); + inode.hard_links = is_directory ? 2 : 1; + inode.type = ((uint16_t) (is_directory ? InodeType::DIRECTORY : InodeType::FILE) >> 12) & 0xF; + inode.permissions = + (uint16_t) (is_directory ? InodePermissionsDefaults::DIRECTORY : InodePermissionsDefaults::FILE) & 0x0FFF; write_inode(inode_index, &inode); ext2_lock.unlock(); return inode_index; - } /** @@ -421,56 +419,53 @@ uint32_t Ext2Volume::create_inode(bool is_directory) { */ void Ext2Volume::free_inode(uint32_t inode) { - // Find the block group containing the inode uint32_t bg_index = (inode - 1) / superblock.inodes_per_group; - block_group_descriptor_t* block_group = block_groups[bg_index]; + block_group_descriptor_t *block_group = block_groups[bg_index]; // Read bitmap buffer_t bitmap(block_size); - read_block(block_group -> block_inode_bitmap, &bitmap); + read_block(block_group->block_inode_bitmap, &bitmap); // First group contains reserved inodes uint32_t inode_index = (inode - 1) % superblock.inodes_per_group; - if(bg_index == 0 && (inode_index < (superblock.first_inode - 1))) - return; + if (bg_index == 0 && (inode_index < (superblock.first_inode - 1))) + return; - // Mark as used - block_group -> free_inodes++; + // Mark as used + block_group->free_inodes++; superblock.unallocated_inodes++; bitmap.raw()[inode_index / 8] &= (uint8_t) ~(1u << (inode_index % 8)); - // Save the changed metadata - write_block(block_group -> block_inode_bitmap, &bitmap); + write_block(block_group->block_inode_bitmap, &bitmap); write_back_block_groups(); write_back_superblock(); } - /** * @brief Frees a group of blocks. Preferably with adjacent blocks apearing next to each other but not enforced. * @param blocks The blocks to free */ -void Ext2Volume::free_blocks(const common::Vector& blocks) { +void Ext2Volume::free_blocks(const common::Vector &blocks) { // No blocks to free - if(blocks.empty()) + if (blocks.empty()) return; - uint32_t start = blocks[0]; - uint32_t previous = start; - uint32_t amount = 1; + uint32_t start = blocks[0]; + uint32_t previous = start; + uint32_t amount = 1; // Free each adjacent set of blocks - for(auto& block : blocks ){ + for (auto &block: blocks) { // First is already accounted for if (block == start) continue; // Is this block adjacent - if((previous + 1) == block){ + if ((previous + 1) == block) { previous = block; amount += 1; continue; @@ -489,26 +484,24 @@ void Ext2Volume::free_blocks(const common::Vector& blocks) { // Account for the last set of blocks in the loop uint32_t group = (start - superblock.starting_block) / superblock.blocks_per_group; free_group_blocks(group, amount, start); - } -InodeHandler::InodeHandler(Ext2Volume* volume, uint32_t inode_index) +InodeHandler::InodeHandler(Ext2Volume *volume, uint32_t inode_index) : m_volume(volume), inode_number(inode_index), - inode(m_volume -> read_inode(inode_number)) + inode(m_volume->read_inode(inode_number)) { // Read the block pointers for (uint32_t direct_pointer = 0; direct_pointer < 12; ++direct_pointer) - if(inode.block_pointers[direct_pointer]) + if (inode.block_pointers[direct_pointer]) block_cache.push_back(inode.block_pointers[direct_pointer]); - buffer_t buffer(m_volume -> block_size); + buffer_t buffer(m_volume->block_size); parse_indirect(1, inode.l1_indirect, &buffer); parse_indirect(2, inode.l2_indirect, &buffer); parse_indirect(3, inode.l3_indirect, &buffer); - } /** @@ -516,7 +509,7 @@ InodeHandler::InodeHandler(Ext2Volume* volume, uint32_t inode_index) * @return The size of the inode data (not the inode itself) */ size_t InodeHandler::size() const { - return ((size_t)inode.size_upper << 32) | (size_t)inode.size_lower; + return ((size_t) inode.size_upper << 32) | (size_t) inode.size_lower; } /** @@ -525,8 +518,8 @@ size_t InodeHandler::size() const { */ void InodeHandler::set_size(size_t size) { - inode.size_lower = (uint32_t)(size & 0xFFFFFFFFULL); - inode.size_upper = (uint32_t)(size >> 32); + inode.size_lower = (uint32_t) (size & 0xFFFFFFFFULL); + inode.size_upper = (uint32_t) (size >> 32); } @@ -537,26 +530,26 @@ void InodeHandler::set_size(size_t size) { * @param block The block number to parse from * @param buffer Buffer to read into */ -void InodeHandler::parse_indirect(uint32_t level, uint32_t block, buffer_t* buffer) { +void InodeHandler::parse_indirect(uint32_t level, uint32_t block, buffer_t *buffer) { // Invalid - if(block == 0) + if (block == 0) return; // Read the block - m_volume -> read_block(block, buffer); - auto* pointers = (uint32_t*)(buffer ->raw()); + m_volume->read_block(block, buffer); + auto *pointers = (uint32_t *) (buffer->raw()); // Parse the pointers for (size_t i = 0; i < m_volume->pointers_per_block; ++i) { uint32_t pointer = pointers[i]; // Invaild - if(pointer == 0) + if (pointer == 0) break; // Has indirect sub entries - if(level > 1){ + if (level > 1) { parse_indirect(level - 1, pointer, buffer); continue; } @@ -564,7 +557,6 @@ void InodeHandler::parse_indirect(uint32_t level, uint32_t block, buffer_t* buff // Parse the entry block_cache.push_back(pointer); } - } /** @@ -583,13 +575,13 @@ void InodeHandler::write_indirect(uint32_t level, uint32_t &block, size_t &index return; // Level hasn't been set yet - if(block == 0) - block = m_volume -> allocate_block(); + if (block == 0) + block = m_volume->allocate_block(); // Allocate a local buffer for this recursion level buffer_t buffer(m_volume->block_size); buffer.clear(); - auto* pointers = (uint32_t*)buffer.raw(); + auto *pointers = (uint32_t *) buffer.raw(); // Write the pointers for (size_t i = 0; i < m_volume->pointers_per_block; ++i) { @@ -608,7 +600,7 @@ void InodeHandler::write_indirect(uint32_t level, uint32_t &block, size_t &index pointers[i] = block_cache[index++]; } - m_volume ->write_block(block, &buffer); + m_volume->write_block(block, &buffer); } /** @@ -621,7 +613,7 @@ void InodeHandler::store_blocks(Vector const &blocks) { Logger::DEBUG() << "STORING BLOCKS\n"; // Store in cache - for(auto block : blocks) + for (auto block: blocks) block_cache.push_back(block); // Direct blocks @@ -629,14 +621,14 @@ void InodeHandler::store_blocks(Vector const &blocks) { inode.block_pointers[i] = i < block_cache.size() ? block_cache[i] : 0; // No need to do any indirects - if(block_cache.size() < 12) + if (block_cache.size() < 12) return; // Setup Recursive blocks size_t index = 12; // Write the blocks - uint32_t indirect_blocks[3] = { inode.l1_indirect, inode.l2_indirect, inode.l3_indirect }; + uint32_t indirect_blocks[3] = {inode.l1_indirect, inode.l2_indirect, inode.l3_indirect}; for (int i = 0; i < 3; ++i) { // Have to use temp because of packed field @@ -651,7 +643,6 @@ void InodeHandler::store_blocks(Vector const &blocks) { inode.l3_indirect = indirect_blocks[2]; // NOTE: Blocks get allocated when writing indirects. This is then saved later in the write() function - } /** @@ -663,21 +654,20 @@ void InodeHandler::store_blocks(Vector const &blocks) { size_t InodeHandler::grow(size_t amount, bool flush) { // Nothing to grow - if(amount <= 0) + if (amount <= 0) return size(); // Allocate new blocks - auto blocks = m_volume -> allocate_blocks(m_volume -> bytes_to_blocks(amount)); + auto blocks = m_volume->allocate_blocks(m_volume->bytes_to_blocks(amount)); ASSERT(blocks[0] != 0, "Failed to allocate new blocks for file"); // Save the changes store_blocks(blocks); set_size(size() + amount); - if(flush) + if (flush) save(); return size() + amount; - } /** @@ -685,7 +675,7 @@ size_t InodeHandler::grow(size_t amount, bool flush) { */ void InodeHandler::save() { - m_volume -> write_inode(inode_number, &inode); + m_volume->write_inode(inode_number, &inode); } /** @@ -693,14 +683,13 @@ void InodeHandler::save() { */ void InodeHandler::free() { - m_volume -> ext2_lock.lock(); + m_volume->ext2_lock.lock(); // Free the inode - m_volume -> free_blocks(block_cache); - m_volume -> free_inode(inode_number); - - m_volume -> ext2_lock.unlock(); + m_volume->free_blocks(block_cache); + m_volume->free_inode(inode_number); + m_volume->ext2_lock.unlock(); } InodeHandler::~InodeHandler() = default; @@ -725,25 +714,25 @@ Ext2File::Ext2File(Ext2Volume *volume, uint32_t inode, string const &name) void Ext2File::write(buffer_t const *data, size_t amount) { // Nothing to write - if(amount == 0) + if (amount == 0) return; // Prepare for writing - m_volume -> ext2_lock.lock(); - const uint32_t block_size = m_volume -> block_size; + m_volume->ext2_lock.lock(); + const uint32_t block_size = m_volume->block_size; buffer_t buffer(block_size); // Expand the file - if(m_offset + amount > m_size) + if (m_offset + amount > m_size) m_size = m_inode.grow((m_offset + amount) - m_size, false); // Save the updated metadata - m_inode.inode.last_modification_time = time_to_epoch(Clock::active_clock() -> get_time()); + m_inode.inode.last_modification_time = time_to_epoch(Clock::active_clock()->get_time()); m_inode.save(); // Convert bytes to blocks - uint32_t block_start = m_offset / block_size; - uint32_t block_offset = m_offset % block_size; + uint32_t block_start = m_offset / block_size; + uint32_t block_offset = m_offset % block_size; // Write each block size_t current_block = block_start; @@ -752,21 +741,22 @@ void Ext2File::write(buffer_t const *data, size_t amount) { // Read the block uint32_t block = m_inode.block_cache[current_block++]; - m_volume -> read_block(block, &buffer); + m_volume->read_block(block, &buffer); // Where in this block to start writing size_t buffer_start = (current_block - 1 == block_start) ? block_offset : 0; - size_t writable = (amount - written < block_size - buffer_start) ? (amount - written) : (block_size - buffer_start); + size_t writable = (amount - written < block_size - buffer_start) ? (amount - written) : (block_size - + buffer_start); // Update the block buffer.copy_from(data, writable, buffer_start, written); - m_volume ->write_block(block, &buffer); + m_volume->write_block(block, &buffer); written += writable; } // Clean up m_offset += amount; - m_volume -> ext2_lock.unlock(); + m_volume->ext2_lock.unlock(); } /** @@ -775,33 +765,33 @@ void Ext2File::write(buffer_t const *data, size_t amount) { * @param data The byte buffer to read into * @param amount The amount of data to read */ -void Ext2File::read(buffer_t* data, size_t amount) { +void Ext2File::read(buffer_t *data, size_t amount) { // Nothing to read - if(m_size == 0 || amount == 0) + if (m_size == 0 || amount == 0) return; // Prepare for reading - m_volume -> ext2_lock.lock(); - const uint32_t block_size = m_volume -> block_size; + m_volume->ext2_lock.lock(); + const uint32_t block_size = m_volume->block_size; buffer_t buffer(block_size); // Force bounds - if(m_offset + amount > m_size) + if (m_offset + amount > m_size) amount = m_size - m_offset; // Convert bytes to blocks - uint32_t block_start = m_offset / block_size; - uint32_t block_offset = m_offset % block_size; + uint32_t block_start = m_offset / block_size; + uint32_t block_offset = m_offset % block_size; // Read each block size_t current_block = block_start; size_t read = 0; - while (read < amount){ + while (read < amount) { // Read the block uint32_t block = m_inode.block_cache[current_block++]; - m_volume -> read_block(block, &buffer); + m_volume->read_block(block, &buffer); // Where in this block to start reading size_t buffer_start = (current_block - 1 == block_start) ? block_offset : 0; @@ -815,7 +805,7 @@ void Ext2File::read(buffer_t* data, size_t amount) { // Clean up m_offset += amount; - m_volume -> ext2_lock.unlock(); + m_volume->ext2_lock.unlock(); } /** @@ -827,8 +817,7 @@ void Ext2File::flush() { Ext2File::~Ext2File() = default; - -Ext2Directory::Ext2Directory(Ext2Volume *volume, uint32_t inode, const string& name) +Ext2Directory::Ext2Directory(Ext2Volume *volume, uint32_t inode, const string &name) : m_volume(volume), m_inode(m_volume, inode) { @@ -838,17 +827,17 @@ Ext2Directory::Ext2Directory(Ext2Volume *volume, uint32_t inode, const string& n /** * @brief Store all entries from a buffer and convert to File or Directory objects */ -void Ext2Directory::parse_block(buffer_t * buffer) { +void Ext2Directory::parse_block(buffer_t *buffer) { size_t offset = 0; - while (offset < m_volume -> block_size){ + while (offset < m_volume->block_size) { // Read the entry - auto* entry = (directory_entry_t*)(buffer->raw() + offset); + auto *entry = (directory_entry_t *) (buffer->raw() + offset); m_entries.push_back(*entry); // Not valid - if (entry -> inode == 0 || entry -> name_length == 0) + if (entry->inode == 0 || entry->name_length == 0) break; // Parse @@ -857,7 +846,7 @@ void Ext2Directory::parse_block(buffer_t * buffer) { uint32_t inode = entry->inode; // Create the object - switch ((EntryType)entry->type) { + switch ((EntryType) entry->type) { case EntryType::FILE: m_files.push_back(new Ext2File(m_volume, inode, filename)); @@ -873,9 +862,8 @@ void Ext2Directory::parse_block(buffer_t * buffer) { } // Go to next - offset += entry-> size; + offset += entry->size; } - } /** @@ -889,20 +877,20 @@ void Ext2Directory::remove_entry(string const &name, bool is_directory, bool cle // Find the entry uint32_t index = 0; - directory_entry_t* entry = nullptr; + directory_entry_t *entry = nullptr; for (; index < m_entries.size(); ++index) - if(m_entry_names[index] == name){ + if (m_entry_names[index] == name) { entry = &m_entries[index]; break; } // No entry found - if(!entry || entry -> type != (uint8_t)(is_directory ? EntryType::DIRECTORY : EntryType::FILE)) + if (!entry || entry->type != (uint8_t) (is_directory ? EntryType::DIRECTORY : EntryType::FILE)) return; // Clear the inode - if(clear){ - InodeHandler inode(m_volume, entry -> inode); + if (clear) { + InodeHandler inode(m_volume, entry->inode); inode.free(); } @@ -910,7 +898,6 @@ void Ext2Directory::remove_entry(string const &name, bool is_directory, bool cle m_entries.erase(entry); m_entry_names.erase(m_entry_names.begin() + index); write_entries(); - } /** @@ -920,11 +907,12 @@ void Ext2Directory::remove_entry(string const &name, bool is_directory, bool cle * @param new_name The name to change it to * @param is_directory Search for entries with the type DIRECTORY (otherwise assume FILE) */ -void Ext2Directory::rename_entry(string const &old_name, string const &new_name, bool is_directory){ +void Ext2Directory::rename_entry(string const &old_name, string const &new_name, bool is_directory) { // Change the name for (int i = 0; i < m_entry_names.size(); ++i) - if(m_entry_names[i] == old_name && m_entries[i].type == (uint8_t)(is_directory ? EntryType::DIRECTORY : EntryType::FILE)) + if (m_entry_names[i] == old_name && + m_entries[i].type == (uint8_t) (is_directory ? EntryType::DIRECTORY : EntryType::FILE)) m_entry_names[i] = new_name; // Save the change @@ -937,34 +925,34 @@ void Ext2Directory::rename_entry(string const &old_name, string const &new_name, */ void Ext2Directory::read_from_disk() { - m_volume -> ext2_lock.lock(); + m_volume->ext2_lock.lock(); m_entries.clear(); m_entry_names.clear(); // Clear the old files & Directories - for(auto& file : m_files) + for (auto &file: m_files) delete file; m_files.clear(); - for(auto& directory : m_subdirectories) + for (auto &directory: m_subdirectories) delete directory; m_subdirectories.clear(); // Read the direct blocks (cant use for( : )) - buffer_t buffer(m_volume -> block_size); + buffer_t buffer(m_volume->block_size); for (int i = 0; i < m_inode.block_cache.size(); ++i) { uint32_t block_pointer = m_inode.block_cache[i]; // Invalid block - if(block_pointer == 0) + if (block_pointer == 0) break; // Parse the block - m_volume -> read_block(block_pointer, &buffer); + m_volume->read_block(block_pointer, &buffer); parse_block(&buffer); } - m_volume -> ext2_lock.unlock(); + m_volume->ext2_lock.unlock(); } void Ext2Directory::write_entries() { @@ -977,30 +965,30 @@ void Ext2Directory::write_entries() { size_required += size; } - // Expand the directory - size_t blocks_required = m_volume -> bytes_to_blocks(size_required); - if(blocks_required > m_inode.block_cache.size()) - m_inode.grow((blocks_required - m_inode.block_cache.size()) * m_volume -> block_size, false); + // Expand the directory + size_t blocks_required = m_volume->bytes_to_blocks(size_required); + if (blocks_required > m_inode.block_cache.size()) + m_inode.grow((blocks_required - m_inode.block_cache.size()) * m_volume->block_size, false); // Prepare for writing - m_volume -> ext2_lock.lock(); - const uint32_t block_size = m_volume -> block_size; + m_volume->ext2_lock.lock(); + const uint32_t block_size = m_volume->block_size; buffer_t buffer(block_size, false); buffer.clear(); // Save the updated metadata - m_inode.set_size(blocks_required * block_size); - m_inode.inode.last_modification_time = time_to_epoch(Clock::active_clock() -> get_time()); + m_inode.set_size(blocks_required * block_size); + m_inode.inode.last_modification_time = time_to_epoch(Clock::active_clock()->get_time()); m_inode.save(); // Write each entry size_t current_block = 0; - size_t buffer_offset = 0; - for (int i = 0; i < m_entries.size(); ++i){ + size_t buffer_offset = 0; + for (int i = 0; i < m_entries.size(); ++i) { // Get the current entry - directory_entry_t& entry = m_entries[i]; - char* name = m_entry_names[i].c_str(); + directory_entry_t &entry = m_entries[i]; + char *name = m_entry_names[i].c_str(); // Update the size entry.name_length = m_entry_names[i].length(); @@ -1008,15 +996,15 @@ void Ext2Directory::write_entries() { entry.size += (entry.size % 4) ? 4 - (entry.size % 4) : 0; // Entry needs to be stored in the next block - if(entry.size + buffer_offset > block_size){ - m_volume ->write_block(m_inode.block_cache[current_block], &buffer); + if (entry.size + buffer_offset > block_size) { + m_volume->write_block(m_inode.block_cache[current_block], &buffer); buffer.clear(); current_block++; buffer_offset = 0; } // If it is the last entry it takes up the rest of the block - if(i == m_entries.size() - 1) + if (i == m_entries.size() - 1) entry.size = block_size - buffer_offset; // Copy the entry and the name @@ -1026,18 +1014,18 @@ void Ext2Directory::write_entries() { } // Save the last block - m_volume -> write_block(m_inode.block_cache[current_block], &buffer); + m_volume->write_block(m_inode.block_cache[current_block], &buffer); // Clean up m_volume->ext2_lock.unlock(); } -directory_entry_t Ext2Directory::create_entry(const string& name, uint32_t inode, bool is_directory) { +directory_entry_t Ext2Directory::create_entry(const string &name, uint32_t inode, bool is_directory) { // Create the inode - directory_entry_t entry {}; - entry.inode = inode ? inode : m_volume -> create_inode(is_directory); - entry.type = (uint32_t)(is_directory ? EntryType::DIRECTORY : EntryType::FILE); + directory_entry_t entry{}; + entry.inode = inode ? inode : m_volume->create_inode(is_directory); + entry.type = (uint32_t) (is_directory ? EntryType::DIRECTORY : EntryType::FILE); entry.name_length = name.length(); entry.size = sizeof(entry) + entry.name_length; entry.size += entry.size % 4 ? 4 - entry.size % 4 : 0; @@ -1059,8 +1047,8 @@ directory_entry_t Ext2Directory::create_entry(const string& name, uint32_t inode File *Ext2Directory::create_file(string const &name) { // Check if the file already exists - for (auto & file : m_files) - if (file -> name() == name) + for (auto &file: m_files) + if (file->name() == name) return nullptr; // Create the file @@ -1097,8 +1085,8 @@ void Ext2Directory::rename_file(string const &old_name, string const &new_name) Directory *Ext2Directory::create_subdirectory(string const &name) { // Check if the directory already exists - for (auto & subdirectory : m_subdirectories) - if (subdirectory -> name() == name) + for (auto &subdirectory: m_subdirectories) + if (subdirectory->name() == name) return nullptr; // Store the directory @@ -1133,17 +1121,15 @@ void Ext2Directory::rename_subdirectory(string const &old_name, string const &ne Ext2Directory::~Ext2Directory() = default; - Ext2FileSystem::Ext2FileSystem(Disk *disk, uint32_t partition_offset) -: m_volume(disk, partition_offset) -{ + : m_volume(disk, partition_offset) { // Create the root directory m_root_directory = new Ext2Directory(&m_volume, 2, "/"); - m_root_directory -> read_from_disk(); + m_root_directory->read_from_disk(); } -Ext2FileSystem::~Ext2FileSystem(){ +Ext2FileSystem::~Ext2FileSystem() { delete m_root_directory; }; \ No newline at end of file diff --git a/kernel/src/filesystem/fat32.cpp b/kernel/src/filesystem/fat32.cpp index 54279a40..1dae8b45 100644 --- a/kernel/src/filesystem/fat32.cpp +++ b/kernel/src/filesystem/fat32.cpp @@ -12,59 +12,57 @@ using namespace MaxOS::drivers::disk; using namespace MaxOS::filesystem; using namespace MaxOS::memory; -Fat32Volume::Fat32Volume(Disk* hd, uint32_t partition_offset) +Fat32Volume::Fat32Volume(Disk *hd, uint32_t partition_offset) : disk(hd) { - // Read the BIOS parameter block - buffer_t bpb_buffer(&bpb,sizeof(bpb32_t)); - disk -> read(partition_offset, &bpb_buffer); - - // Parse the FAT info - uint32_t total_data_sectors = bpb.total_sectors_32 - (bpb.reserved_sectors + (bpb.table_copies * bpb.table_size_32)); - fat_total_clusters = total_data_sectors / bpb.sectors_per_cluster; - fat_lba = partition_offset + bpb.reserved_sectors; - fat_copies = bpb.table_copies; - fat_info_lba = partition_offset + bpb.fat_info; - buffer_t fs_buffer(&fsinfo, sizeof(fs_info_t)); - disk -> read(fat_info_lba, &fs_buffer); - - data_lba = fat_lba + (bpb.table_copies * bpb.table_size_32); - root_lba = data_lba + bpb.sectors_per_cluster * (bpb.root_cluster - 2); - - // Validate the fat information - if (fsinfo.lead_signature != 0x41615252 || fsinfo.structure_signature != 0x61417272 || fsinfo.trail_signature != 0xAA550000) - { - Logger::ERROR() << "Invalid FAT32 filesystem information TODO: Handle this\n"; - return; - } - + // Read the BIOS parameter block + buffer_t bpb_buffer(&bpb, sizeof(bpb32_t)); + disk->read(partition_offset, &bpb_buffer); + + // Parse the FAT info + uint32_t total_data_sectors = bpb.total_sectors_32 - (bpb.reserved_sectors + (bpb.table_copies * bpb.table_size_32)); + fat_total_clusters = total_data_sectors / bpb.sectors_per_cluster; + fat_lba = partition_offset + bpb.reserved_sectors; + fat_copies = bpb.table_copies; + fat_info_lba = partition_offset + bpb.fat_info; + data_lba = fat_lba + (bpb.table_copies * bpb.table_size_32); + root_lba = data_lba + bpb.sectors_per_cluster * (bpb.root_cluster - 2); + + // Read the fs info + buffer_t fs_buffer(&fsinfo, sizeof(fs_info_t)); + disk->read(fat_info_lba, &fs_buffer); + + // Validate the fat information + if (fsinfo.lead_signature != 0x41615252 || fsinfo.structure_signature != 0x61417272 || + fsinfo.trail_signature != 0xAA550000) { + Logger::ERROR() << "Invalid FAT32 filesystem information TODO: Handle this\n"; + return; + } } Fat32Volume::~Fat32Volume() = default; - /** * @brief Take the cluster and gets the next cluster in the chain * * @param cluster The base cluster to start from * @return The next cluster in the chain */ -lba_t Fat32Volume::next_cluster(lba_t cluster) -{ +lba_t Fat32Volume::next_cluster(lba_t cluster) { - // Get the location in the FAT table - lba_t offset = cluster * sizeof(uint32_t); - lba_t sector = fat_lba + (offset / bpb.bytes_per_sector); - uint32_t entry_index = offset % bpb.bytes_per_sector; + // Get the location in the FAT table + lba_t offset = cluster * sizeof(uint32_t); + lba_t sector = fat_lba + (offset / bpb.bytes_per_sector); + uint32_t entry_index = offset % bpb.bytes_per_sector; - // Read the FAT entry - buffer_t fat(bpb.bytes_per_sector); - disk -> read(sector, &fat); + // Read the FAT entry + buffer_t fat(bpb.bytes_per_sector); + disk->read(sector, &fat); - // Get the next cluster info (mask the upper 4 bits) - auto entry = (uint32_t *)(&(fat.raw()[entry_index])); // TODO & here is weird - return *entry & 0x0FFFFFFF; + // Get the next cluster info (mask the upper 4 bits) + auto entry = (uint32_t *) (&(fat.raw()[entry_index])); // TODO & here is weird + return *entry & 0x0FFFFFFF; } /** @@ -74,31 +72,30 @@ lba_t Fat32Volume::next_cluster(lba_t cluster) * @param next_cluster The next cluster in the chain * @return The next cluster in the chain */ -uint32_t Fat32Volume::set_next_cluster(uint32_t cluster, uint32_t next_cluster) -{ +uint32_t Fat32Volume::set_next_cluster(uint32_t cluster, uint32_t next_cluster) { - // TODO - when in userspace: For performance cache fat entirely, cache file data, cache cluster chains + // TODO - when in userspace: For performance cache fat entirely, cache file data, cache cluster chains - // Get the location in the FAT table - lba_t offset = cluster * sizeof(uint32_t); + // Get the location in the FAT table + lba_t offset = cluster * sizeof(uint32_t); - for (int i = 0; i < fat_copies; ++i) { + for (int i = 0; i < fat_copies; ++i) { - lba_t sector = (fat_lba + i * bpb.table_size_32) + (offset / bpb.bytes_per_sector); - uint32_t entry_index = offset % bpb.bytes_per_sector; + lba_t sector = (fat_lba + i * bpb.table_size_32) + (offset / bpb.bytes_per_sector); + uint32_t entry_index = offset % bpb.bytes_per_sector; - // Read the FAT entry - buffer_t fat(bpb.bytes_per_sector); - disk -> read(sector, &fat); + // Read the FAT entry + buffer_t fat(bpb.bytes_per_sector); + disk->read(sector, &fat); - // Set the next cluster info (mask the upper 4 bits) - auto entry = (uint32_t *)(&(fat.raw()[entry_index])); - *entry = next_cluster & 0x0FFFFFFF; - disk -> write(sector, &fat); + // Set the next cluster info (mask the upper 4 bits) + auto entry = (uint32_t *) (&(fat.raw()[entry_index])); + *entry = next_cluster & 0x0FFFFFFF; + disk->write(sector, &fat); - } + } - return next_cluster; + return next_cluster; } /** @@ -106,21 +103,20 @@ uint32_t Fat32Volume::set_next_cluster(uint32_t cluster, uint32_t next_cluster) * * @return The first free cluster in the FAT table */ -uint32_t Fat32Volume::find_free_cluster() -{ +uint32_t Fat32Volume::find_free_cluster() { - // Get the first free cluster - for (uint32_t start = fsinfo.next_free_cluster; start < fat_total_clusters + 1; start++) - if (next_cluster(start) == 0) - return start; + // Get the first free cluster + for (uint32_t start = fsinfo.next_free_cluster; start < fat_total_clusters + 1; start++) + if (next_cluster(start) == 0) + return start; - // Check any clusters before the first free cluster - for (uint32_t start = 2; start < fsinfo.next_free_cluster; start++) - if (next_cluster(start) == 0) - return start; + // Check any clusters before the first free cluster + for (uint32_t start = 2; start < fsinfo.next_free_cluster; start++) + if (next_cluster(start) == 0) + return start; - ASSERT(false, "No free clusters found in the FAT table"); - return 0; + ASSERT(false, "No free clusters found in the FAT table"); + return 0; } /** @@ -129,11 +125,10 @@ uint32_t Fat32Volume::find_free_cluster() * @param cluster The base cluster to start from or 0 if this is a new chain * @return The next cluster in the chain */ -uint32_t Fat32Volume::allocate_cluster(uint32_t cluster) -{ +uint32_t Fat32Volume::allocate_cluster(uint32_t cluster) { - // Allocate 1 cluster - return allocate_cluster(cluster, 1); + // Allocate 1 cluster + return allocate_cluster(cluster, 1); } /** @@ -143,37 +138,35 @@ uint32_t Fat32Volume::allocate_cluster(uint32_t cluster) * @param amount The number of clusters to allocate * @return The next cluster in the chain */ -uint32_t Fat32Volume::allocate_cluster(uint32_t cluster, size_t amount) -{ +uint32_t Fat32Volume::allocate_cluster(uint32_t cluster, size_t amount) { - // Make sure within bounds - if (cluster > fat_total_clusters || cluster + amount > fat_total_clusters) - return 0; + // Make sure within bounds + if (cluster > fat_total_clusters || cluster + amount > fat_total_clusters) + return 0; - // Go through allocating the clusters - for (size_t i = 0; i < amount; i++) - { - uint32_t next_cluster = find_free_cluster(); + // Go through allocating the clusters + for (size_t i = 0; i < amount; i++) { + uint32_t next_cluster = find_free_cluster(); - // Update the fsinfo - fsinfo.next_free_cluster = next_cluster + 1; - fsinfo.free_cluster_count -= 1; + // Update the fsinfo + fsinfo.next_free_cluster = next_cluster + 1; + fsinfo.free_cluster_count -= 1; - // If there is an existing chain it needs to be updated - if (cluster != 0) - set_next_cluster(cluster, next_cluster); + // If there is an existing chain it needs to be updated + if (cluster != 0) + set_next_cluster(cluster, next_cluster); - cluster = next_cluster; - } + cluster = next_cluster; + } - // Once all the updates are done flush the changes to the disk - buffer_t fs_info_buffer(&fsinfo, sizeof(fs_info_t)); - disk -> write(fat_info_lba, &fs_info_buffer); + // Once all the updates are done flush the changes to the disk + buffer_t fs_info_buffer(&fsinfo, sizeof(fs_info_t)); + disk->write(fat_info_lba, &fs_info_buffer); - // Finish the chin - set_next_cluster(cluster, (uint32_t)ClusterState::END_OF_CHAIN); - uint32_t next = next_cluster(cluster); - return cluster; + // Finish the chin + set_next_cluster(cluster, (uint32_t) ClusterState::END_OF_CHAIN); + uint32_t next = next_cluster(cluster); + return cluster; } /** @@ -182,11 +175,10 @@ uint32_t Fat32Volume::allocate_cluster(uint32_t cluster, size_t amount) * @param cluster The base cluster to start from * @param full Weather the chain's length is 1 or not */ -void Fat32Volume::free_cluster(lba_t cluster) -{ +void Fat32Volume::free_cluster(lba_t cluster) { - // Free 1 cluster - free_cluster(cluster, 1); + // Free 1 cluster + free_cluster(cluster, 1); } @@ -196,48 +188,46 @@ void Fat32Volume::free_cluster(lba_t cluster) * @param cluster The base cluster to start from * @param amount The number of clusters to free */ -void Fat32Volume::free_cluster(uint32_t cluster, size_t amount) -{ +void Fat32Volume::free_cluster(uint32_t cluster, size_t amount) { - // Make sure within bounds - if (cluster < 2 || cluster > fat_total_clusters || cluster + amount > fat_total_clusters) - return; + // Make sure within bounds + if (cluster < 2 || cluster > fat_total_clusters || cluster + amount > fat_total_clusters) + return; - // Go through freeing the clusters - for (size_t i = 0; i < amount; i++) - { + // Go through freeing the clusters + for (size_t i = 0; i < amount; i++) { - // Find the next cluster before it is removed from the chain - uint32_t next_in_chain = next_cluster(cluster); + // Find the next cluster before it is removed from the chain + uint32_t next_in_chain = next_cluster(cluster); - // Update the fsinfo - fsinfo.next_free_cluster = cluster; - fsinfo.free_cluster_count += 1; + // Update the fsinfo + fsinfo.next_free_cluster = cluster; + fsinfo.free_cluster_count += 1; - // Update the chain - set_next_cluster(cluster, (lba_t)ClusterState::FREE); - cluster = next_in_chain; - } + // Update the chain + set_next_cluster(cluster, (lba_t) ClusterState::FREE); + cluster = next_in_chain; + } - // Save the fsinfo - buffer_t fs_info_buffer(&fsinfo, sizeof(fs_info_t)); - disk -> write(fat_info_lba, &fs_info_buffer); + // Save the fsinfo + buffer_t fs_info_buffer(&fsinfo, sizeof(fs_info_t)); + disk->write(fat_info_lba, &fs_info_buffer); - // Mark the end of the chain - set_next_cluster(cluster, (uint32_t)ClusterState::END_OF_CHAIN); + // Mark the end of the chain + set_next_cluster(cluster, (uint32_t) ClusterState::END_OF_CHAIN); } -Fat32File::Fat32File(Fat32Volume* volume, Fat32Directory* parent, dir_entry_t* info, const string& name) +Fat32File::Fat32File(Fat32Volume *volume, Fat32Directory *parent, dir_entry_t *info, const string &name) : m_volume(volume), m_parent_directory(parent), m_entry(info), - m_first_cluster((info -> first_cluster_high << 16) | info -> first_cluster_low) + m_first_cluster((info->first_cluster_high << 16) | info->first_cluster_low) { - m_name = name; - m_size = info -> size; - m_offset = 0; + m_name = name; + m_size = info->size; + m_offset = 0; } Fat32File::~Fat32File() = default; @@ -248,98 +238,98 @@ Fat32File::~Fat32File() = default; * @param data The byte buffer to write * @param amount The amount of data to write */ -void Fat32File::write(const buffer_t* data, size_t amount) -{ - - size_t buffer_space = m_volume->bpb.bytes_per_sector * m_volume->bpb.sectors_per_cluster; - buffer_t buffer(buffer_space); - buffer.clear(); - - uint64_t current_offset = 0; - uint64_t bytes_written = 0; - uint32_t last = m_first_cluster; - - // Read the file - for (uint32_t cluster = last; cluster != (uint32_t)ClusterState::END_OF_CHAIN; cluster = m_volume -> next_cluster(cluster)) { - last = cluster; - - // No cluster to read from (blank file) - if (cluster == 0) - break; - - // Skip clusters before the offset - if((current_offset + buffer_space) < m_offset){ - current_offset += buffer_space; - continue; - } - - // Read each sector in the cluster (prevent overwriting the data) - lba_t lba = m_volume->data_lba + (cluster - 2) * m_volume->bpb.sectors_per_cluster; - for (size_t sector = 0; sector < m_volume->bpb.sectors_per_cluster; sector++) - m_volume->disk->read(lba + sector, &buffer, m_volume->bpb.bytes_per_sector); - buffer.set_offset(0); - - // If the offset is in the middle of the cluster - size_t buffer_offset = 0; - if(m_offset > current_offset) - buffer_offset = m_offset - current_offset; - - // Calculate how many bytes are being copied (read from cluster at offset? - // or read part of cluster?) - size_t cluster_remaining_bytes = buffer_space - buffer_offset; - size_t data_remaining_bytes = amount - bytes_written ; - size_t bytes_to_copy = (cluster_remaining_bytes < data_remaining_bytes) ? cluster_remaining_bytes : data_remaining_bytes; - bytes_to_copy = (bytes_to_copy > buffer_space) ? buffer_space : bytes_to_copy; - - // Update the data - buffer.copy_from(data, bytes_to_copy, buffer_offset, bytes_written); - bytes_written += bytes_to_copy; - current_offset += bytes_to_copy; - buffer.set_offset(0); - - // Write the data back to the disk - for (size_t sector = 0; sector < m_volume->bpb.sectors_per_cluster; sector++) - m_volume->disk->write(lba + sector, &buffer, m_volume->bpb.bytes_per_sector); - } - - // Extend the file - while(bytes_written < amount) - { - // Allocate a new cluster - uint32_t new_cluster = m_volume -> allocate_cluster(last); - if (new_cluster == 0) - break; - - if(last == 0) - m_first_cluster = new_cluster; - - // Update the data - size_t bytes_to_copy = (amount - bytes_written) > buffer_space ? buffer_space : (amount - bytes_written); - buffer.copy_from(data, bytes_to_copy, 0, bytes_written); - bytes_written += bytes_to_copy; - current_offset += bytes_to_copy; - buffer.set_offset(0); - - // Write the data back to the disk - lba_t lba = m_volume->data_lba + (new_cluster - 2) * m_volume->bpb.sectors_per_cluster; - for (size_t sector = 0; sector < m_volume->bpb.sectors_per_cluster; sector++) - m_volume->disk->write(lba + sector, &buffer, m_volume->bpb.bytes_per_sector); - - // Go to the next cluster - last = new_cluster; - } - - // Update file size - m_offset += bytes_written; - if (m_offset > m_size) - m_size = m_offset; - - // Update entry info - m_entry -> size = m_size; - m_entry -> first_cluster_high = (m_first_cluster >> 16) & 0xFFFF; - m_entry -> first_cluster_low = m_first_cluster & 0xFFFF; - // TODO: When implemented as a usermode driver save the time - m_parent_directory -> save_entry_to_disk(m_entry); +void Fat32File::write(const buffer_t *data, size_t amount) { + + size_t buffer_space = m_volume->bpb.bytes_per_sector * m_volume->bpb.sectors_per_cluster; + buffer_t buffer(buffer_space); + buffer.clear(); + + uint64_t current_offset = 0; + uint64_t bytes_written = 0; + uint32_t last = m_first_cluster; + + // Read the file + for (uint32_t cluster = last; + cluster != (uint32_t) ClusterState::END_OF_CHAIN; cluster = m_volume->next_cluster(cluster)) { + last = cluster; + + // No cluster to read from (blank file) + if (cluster == 0) + break; + + // Skip clusters before the offset + if ((current_offset + buffer_space) < m_offset) { + current_offset += buffer_space; + continue; + } + + // Read each sector in the cluster (prevent overwriting the data) + lba_t lba = m_volume->data_lba + (cluster - 2) * m_volume->bpb.sectors_per_cluster; + for (size_t sector = 0; sector < m_volume->bpb.sectors_per_cluster; sector++) + m_volume->disk->read(lba + sector, &buffer, m_volume->bpb.bytes_per_sector); + buffer.set_offset(0); + + // If the offset is in the middle of the cluster + size_t buffer_offset = 0; + if (m_offset > current_offset) + buffer_offset = m_offset - current_offset; + + // Calculate how many bytes are being copied (read from cluster at offset? + // or read part of cluster?) + size_t cluster_remaining_bytes = buffer_space - buffer_offset; + size_t data_remaining_bytes = amount - bytes_written; + size_t bytes_to_copy = (cluster_remaining_bytes < data_remaining_bytes) ? cluster_remaining_bytes + : data_remaining_bytes; + bytes_to_copy = (bytes_to_copy > buffer_space) ? buffer_space : bytes_to_copy; + + // Update the data + buffer.copy_from(data, bytes_to_copy, buffer_offset, bytes_written); + bytes_written += bytes_to_copy; + current_offset += bytes_to_copy; + buffer.set_offset(0); + + // Write the data back to the disk + for (size_t sector = 0; sector < m_volume->bpb.sectors_per_cluster; sector++) + m_volume->disk->write(lba + sector, &buffer, m_volume->bpb.bytes_per_sector); + } + + // Extend the file + while (bytes_written < amount) { + // Allocate a new cluster + uint32_t new_cluster = m_volume->allocate_cluster(last); + if (new_cluster == 0) + break; + + if (last == 0) + m_first_cluster = new_cluster; + + // Update the data + size_t bytes_to_copy = (amount - bytes_written) > buffer_space ? buffer_space : (amount - bytes_written); + buffer.copy_from(data, bytes_to_copy, 0, bytes_written); + bytes_written += bytes_to_copy; + current_offset += bytes_to_copy; + buffer.set_offset(0); + + // Write the data back to the disk + lba_t lba = m_volume->data_lba + (new_cluster - 2) * m_volume->bpb.sectors_per_cluster; + for (size_t sector = 0; sector < m_volume->bpb.sectors_per_cluster; sector++) + m_volume->disk->write(lba + sector, &buffer, m_volume->bpb.bytes_per_sector); + + // Go to the next cluster + last = new_cluster; + } + + // Update file size + m_offset += bytes_written; + if (m_offset > m_size) + m_size = m_offset; + + // Update entry info + m_entry->size = m_size; + m_entry->first_cluster_high = (m_first_cluster >> 16) & 0xFFFF; + m_entry->first_cluster_low = m_first_cluster & 0xFFFF; + // TODO: When implemented as a usermode driver save the time + m_parent_directory->save_entry_to_disk(m_entry); } @@ -349,70 +339,67 @@ void Fat32File::write(const buffer_t* data, size_t amount) * @param data The byte buffer to read into * @param amount The amount of data to read */ -void Fat32File::read(buffer_t* data, size_t amount) -{ - size_t buffer_space = m_volume->bpb.bytes_per_sector * m_volume->bpb.sectors_per_cluster; - buffer_t buffer(buffer_space); - buffer.clear(); - - uint64_t current_offset = 0; - uint64_t bytes_read = 0; - - // Read the file - for (uint32_t cluster = m_first_cluster; cluster != (uint32_t)ClusterState::END_OF_CHAIN; cluster = m_volume -> next_cluster(cluster)) { - - // Skip clusters before the offset - if((current_offset + buffer_space) < m_offset){ - current_offset += buffer_space; - continue; - } - - // Read each sector in the cluster - lba_t lba = m_volume->data_lba + (cluster - 2) * m_volume->bpb.sectors_per_cluster; - for (size_t sector = 0; sector < m_volume->bpb.sectors_per_cluster; sector++) - m_volume->disk->read(lba + sector, &buffer, m_volume->bpb.bytes_per_sector); - buffer.set_offset(0); - - // If the offset is in the middle of the cluster - size_t buffer_offset = 0; - if(m_offset > current_offset) - buffer_offset = m_offset - current_offset; - - // Calculate how many bytes are being copied (read from cluster at offset? - // or read part of cluster?) - size_t cluster_remaining_bytes = buffer_space - buffer_offset; - size_t data_remaining_bytes = amount - bytes_read; - size_t bytes_to_copy = (cluster_remaining_bytes < data_remaining_bytes) ? cluster_remaining_bytes : data_remaining_bytes; - bytes_to_copy = (bytes_to_copy > buffer_space) ? buffer_space : bytes_to_copy; - - // Read the data - buffer.copy_from(data, bytes_to_copy, buffer_offset, bytes_read); - bytes_read += bytes_to_copy; - current_offset += buffer_space; - - // Dont read more than needed - if(bytes_read >= amount) - break; - } - - m_offset += bytes_read; +void Fat32File::read(buffer_t *data, size_t amount) { + size_t buffer_space = m_volume->bpb.bytes_per_sector * m_volume->bpb.sectors_per_cluster; + buffer_t buffer(buffer_space); + buffer.clear(); + + uint64_t current_offset = 0; + uint64_t bytes_read = 0; + + // Read the file + for (uint32_t cluster = m_first_cluster; + cluster != (uint32_t) ClusterState::END_OF_CHAIN; cluster = m_volume->next_cluster(cluster)) { + + // Skip clusters before the offset + if ((current_offset + buffer_space) < m_offset) { + current_offset += buffer_space; + continue; + } + + // Read each sector in the cluster + lba_t lba = m_volume->data_lba + (cluster - 2) * m_volume->bpb.sectors_per_cluster; + for (size_t sector = 0; sector < m_volume->bpb.sectors_per_cluster; sector++) + m_volume->disk->read(lba + sector, &buffer, m_volume->bpb.bytes_per_sector); + buffer.set_offset(0); + + // If the offset is in the middle of the cluster + size_t buffer_offset = 0; + if (m_offset > current_offset) + buffer_offset = m_offset - current_offset; + + // Calculate how many bytes are being copied (read from cluster at offset? or read part of cluster?) + size_t cluster_remaining_bytes = buffer_space - buffer_offset; + size_t data_remaining_bytes = amount - bytes_read; + size_t bytes_to_copy = (cluster_remaining_bytes < data_remaining_bytes) ? cluster_remaining_bytes + : data_remaining_bytes; + bytes_to_copy = (bytes_to_copy > buffer_space) ? buffer_space : bytes_to_copy; + + // Read the data + buffer.copy_from(data, bytes_to_copy, buffer_offset, bytes_read); + bytes_read += bytes_to_copy; + current_offset += buffer_space; + + // Dont read more than needed + if (bytes_read >= amount) + break; + } + + m_offset += bytes_read; } /** * @brief Flush the file to the disk */ -void Fat32File::flush() -{ - File::flush(); +void Fat32File::flush() { + File::flush(); } -Fat32Directory::Fat32Directory(Fat32Volume* volume, uint32_t cluster, const string& name) +Fat32Directory::Fat32Directory(Fat32Volume *volume, uint32_t cluster, const string &name) : m_volume(volume), m_first_cluster(cluster) { - - m_name = name; - + m_name = name; } Fat32Directory::~Fat32Directory() = default; @@ -424,74 +411,74 @@ Fat32Directory::~Fat32Directory() = default; * @param is_directory True if the entry is a directory, false if it is a file * @return The cluster of the new entry */ -dir_entry_t* Fat32Directory::create_entry(const string& name, bool is_directory) -{ - - // Allocate a cluster for the new entry - uint32_t cluster = m_volume -> allocate_cluster(0); - if (cluster == 0) - return nullptr; - - // Store the name - Vector lfn_entries = to_long_filenames(name); - char short_name[8]; - char short_extension[3]; - for (int i = 0; i < 8; i++) - short_name[i] = (i < name.length()) ? name[i] : ' '; - for (int i = 0; i < 3; i++) - short_extension[i] = (8 + i < name.length()) ? name[8 + i] : ' '; - - // Create the directory entry - dir_entry_t entry = {}; - memcpy(entry.name, short_name, sizeof(short_name)); - memcpy(entry.extension, short_extension, sizeof(short_extension)); - entry.attributes = is_directory ? (uint8_t)DirectoryEntryAttributes::DIRECTORY : (uint8_t)DirectoryEntryAttributes::ARCHIVE; - entry.first_cluster_high = (cluster >> 16) & 0xFFFF; - entry.first_cluster_low = cluster & 0xFFFF; - - // Find free space for the new entry and the name - size_t entries_needed = 1 + lfn_entries.size(); - int entry_index = find_free_entries(entries_needed); - if(entry_index == -1) - entry_index = expand_directory(entries_needed); - - // Store the entries in the cache - for (size_t i = 0; i < entries_needed; i++) - m_entries[entry_index + i] = i == 0 ? entry : *(dir_entry_t *)&lfn_entries[i - 1]; - - // Write the long file name entries - for (size_t index = entry_index + lfn_entries.size() - 1; index >= entry_index; index--) - update_entry_on_disk(index); - - // Creating the file is done - if (!is_directory) - return &m_entries[entry_index]; - - // Create the "." in the directory - dir_entry_t current_dir_entry = {}; - memcpy(current_dir_entry.name, ".", 1); - current_dir_entry.attributes = 0x10; - current_dir_entry.first_cluster_high = (cluster >> 16) & 0xFFFF; - current_dir_entry.first_cluster_low = cluster & 0xFFFF; - - // Create the ".." in the directory - dir_entry_t parent_dir_entry = {}; - memcpy(parent_dir_entry.name, "..", 2); - parent_dir_entry.attributes = 0x10; - parent_dir_entry.first_cluster_high = (m_first_cluster >> 16) & 0xFFFF; - parent_dir_entry.first_cluster_low = m_first_cluster & 0xFFFF; - - // Write the entries to the disk - uint32_t bytes_per_sector = m_volume -> bpb.bytes_per_sector; - lba_t child_lba = m_volume -> data_lba + (cluster - 2) * bytes_per_sector; - buffer_t buffer(bytes_per_sector); - buffer.clear(); - buffer.copy_from(¤t_dir_entry, sizeof(dir_entry_t)); - buffer.copy_from(&parent_dir_entry, sizeof(dir_entry_t)); - m_volume -> disk -> write(child_lba, &buffer); - - // Directory created - return &m_entries[entry_index]; +dir_entry_t *Fat32Directory::create_entry(const string &name, bool is_directory) { + + // Allocate a cluster for the new entry + uint32_t cluster = m_volume->allocate_cluster(0); + if (cluster == 0) + return nullptr; + + // Store the name + Vector lfn_entries = to_long_filenames(name); + char short_name[8]; + char short_extension[3]; + for (int i = 0; i < 8; i++) + short_name[i] = (i < name.length()) ? name[i] : ' '; + for (int i = 0; i < 3; i++) + short_extension[i] = (8 + i < name.length()) ? name[8 + i] : ' '; + + // Create the directory entry + dir_entry_t entry = {}; + memcpy(entry.name, short_name, sizeof(short_name)); + memcpy(entry.extension, short_extension, sizeof(short_extension)); + entry.attributes = is_directory ? (uint8_t) DirectoryEntryAttributes::DIRECTORY + : (uint8_t) DirectoryEntryAttributes::ARCHIVE; + entry.first_cluster_high = (cluster >> 16) & 0xFFFF; + entry.first_cluster_low = cluster & 0xFFFF; + + // Find free space for the new entry and the name + size_t entries_needed = 1 + lfn_entries.size(); + int entry_index = find_free_entries(entries_needed); + if (entry_index == -1) + entry_index = expand_directory(entries_needed); + + // Store the entries in the cache + for (size_t i = 0; i < entries_needed; i++) + m_entries[entry_index + i] = i == 0 ? entry : *(dir_entry_t *) &lfn_entries[i - 1]; + + // Write the long file name entries + for (size_t index = entry_index + lfn_entries.size() - 1; index >= entry_index; index--) + update_entry_on_disk(index); + + // Creating the file is done + if (!is_directory) + return &m_entries[entry_index]; + + // Create the "." in the directory + dir_entry_t current_dir_entry = {}; + memcpy(current_dir_entry.name, ".", 1); + current_dir_entry.attributes = 0x10; + current_dir_entry.first_cluster_high = (cluster >> 16) & 0xFFFF; + current_dir_entry.first_cluster_low = cluster & 0xFFFF; + + // Create the ".." in the directory + dir_entry_t parent_dir_entry = {}; + memcpy(parent_dir_entry.name, "..", 2); + parent_dir_entry.attributes = 0x10; + parent_dir_entry.first_cluster_high = (m_first_cluster >> 16) & 0xFFFF; + parent_dir_entry.first_cluster_low = m_first_cluster & 0xFFFF; + + // Write the entries to the disk + uint32_t bytes_per_sector = m_volume->bpb.bytes_per_sector; + lba_t child_lba = m_volume->data_lba + (cluster - 2) * bytes_per_sector; + buffer_t buffer(bytes_per_sector); + buffer.clear(); + buffer.copy_from(¤t_dir_entry, sizeof(dir_entry_t)); + buffer.copy_from(&parent_dir_entry, sizeof(dir_entry_t)); + m_volume->disk->write(child_lba, &buffer); + + // Directory created + return &m_entries[entry_index]; } /** @@ -500,71 +487,73 @@ dir_entry_t* Fat32Directory::create_entry(const string& name, bool is_directory) * @param cluster The cluster of the entry to remove * @param name The name of the entry to remove */ -void Fat32Directory::remove_entry(uint32_t cluster, const string& name) -{ - - // Find the entry in the directory - int entry = entry_index(cluster); - if(entry == -1) - return; - - // Find any long file name entries that belong to this entry - size_t delete_entry_index = entry; - while (delete_entry_index > 0 && m_entries[delete_entry_index - 1].attributes == (uint8_t)DirectoryEntryAttributes::LONG_NAME) - delete_entry_index--; - - // Mark the entries as free - for (size_t i = delete_entry_index; i < entry; i++) - m_entries[i].name[0] = (uint8_t)DirectoryEntryType::FREE; - - // Update the entries on the disk - for (size_t i = delete_entry_index; i <= entry; i++) - update_entry_on_disk(i); - - // Count the number of clusters in the chain - size_t cluster_count = 0; - for (uint32_t next_cluster = cluster; next_cluster < (lba_t)ClusterState::BAD; next_cluster = m_volume -> next_cluster(next_cluster)) - cluster_count++; - - // Free all the clusters in the chain - m_volume -> free_cluster(cluster, cluster_count); +void Fat32Directory::remove_entry(uint32_t cluster, const string &name) { + + // Find the entry in the directory + int entry = entry_index(cluster); + if (entry == -1) + return; + + // Find any long file name entries that belong to this entry + size_t delete_entry_index = entry; + while (delete_entry_index > 0 && + m_entries[delete_entry_index - 1].attributes == (uint8_t) DirectoryEntryAttributes::LONG_NAME) + delete_entry_index--; + + // Mark the entries as free + for (size_t i = delete_entry_index; i < entry; i++) + m_entries[i].name[0] = (uint8_t) DirectoryEntryType::FREE; + + // Update the entries on the disk + for (size_t i = delete_entry_index; i <= entry; i++) + update_entry_on_disk(i); + + // Count the number of clusters in the chain + size_t cluster_count = 0; + for (uint32_t next_cluster = cluster; + next_cluster < (lba_t) ClusterState::BAD; next_cluster = m_volume->next_cluster(next_cluster)) + cluster_count++; + + // Free all the clusters in the chain + m_volume->free_cluster(cluster, cluster_count); } /** * @brief Read all of the directory entries for each cluster in this directory */ void Fat32Directory::read_all_entries() { - m_entries.clear(); + m_entries.clear(); - size_t buffer_space = m_volume->bpb.bytes_per_sector * m_volume->bpb.sectors_per_cluster; - buffer_t buffer (buffer_space); - buffer.clear(); + size_t buffer_space = m_volume->bpb.bytes_per_sector * m_volume->bpb.sectors_per_cluster; + buffer_t buffer(buffer_space); + buffer.clear(); - // Read the directory - for (uint32_t cluster = m_first_cluster; cluster != (uint32_t)ClusterState::END_OF_CHAIN; cluster = m_volume -> next_cluster(cluster)) { + // Read the directory + for (uint32_t cluster = m_first_cluster; + cluster != (uint32_t) ClusterState::END_OF_CHAIN; cluster = m_volume->next_cluster(cluster)) { - // Cache info to prevent re-traversing the chain - m_last_cluster = cluster; - m_current_cluster_length++; + // Cache info to prevent re-traversing the chain + m_last_cluster = cluster; + m_current_cluster_length++; - // Read each sector in the cluster - lba_t lba = m_volume->data_lba + (cluster - 2) * m_volume->bpb.sectors_per_cluster; - for (size_t sector = 0; sector < m_volume->bpb.sectors_per_cluster; sector++) - m_volume->disk->read(lba + sector, &buffer, m_volume->bpb.bytes_per_sector); + // Read each sector in the cluster + lba_t lba = m_volume->data_lba + (cluster - 2) * m_volume->bpb.sectors_per_cluster; + for (size_t sector = 0; sector < m_volume->bpb.sectors_per_cluster; sector++) + m_volume->disk->read(lba + sector, &buffer, m_volume->bpb.bytes_per_sector); - // Parse the directory entries (each entry is 32 bytes) - for (size_t entry_offset = 0; entry_offset < buffer_space; entry_offset += 32) { + // Parse the directory entries (each entry is 32 bytes) + for (size_t entry_offset = 0; entry_offset < buffer_space; entry_offset += 32) { - // Store the entry - auto entry = (dir_entry_t*)&(buffer.raw()[entry_offset]); - m_entries.push_back(*entry); + // Store the entry + auto entry = (dir_entry_t *) &(buffer.raw()[entry_offset]); + m_entries.push_back(*entry); - // Check if the entry is the end of the directory - if (entry -> name[0] == (uint8_t)DirectoryEntryType::LAST) - return; - } + // Check if the entry is the end of the directory + if (entry->name[0] == (uint8_t) DirectoryEntryType::LAST) + return; + } - } + } } @@ -573,42 +562,43 @@ void Fat32Directory::read_all_entries() { * * @param entry The entry to write */ -void Fat32Directory::save_entry_to_disk(DirectoryEntry* entry) { +void Fat32Directory::save_entry_to_disk(DirectoryEntry *entry) { - int index = 0; - for (auto & m_entry : m_entries){ - if (&m_entry == entry) - break; - index++; - } + int index = 0; + for (auto &m_entry: m_entries) { + if (&m_entry == entry) + break; + index++; + } - update_entry_on_disk(index); + update_entry_on_disk(index); } void Fat32Directory::update_entry_on_disk(int index) { - // Get the entry - auto entry = m_entries[index]; - - // Determine sector offset and in-sector byte offset - uint32_t bytes_per_sector = m_volume->bpb.bytes_per_sector; - uint32_t entry_offset = index * sizeof(dir_entry_t); - uint32_t sector_offset = entry_offset / bytes_per_sector; - uint32_t in_sector_offset = entry_offset % bytes_per_sector; - - // Find which cluster has the entry - uint32_t cluster = m_first_cluster; - for (uint32_t offset_remaining = entry_offset; offset_remaining >= bytes_per_sector; offset_remaining -= bytes_per_sector) - cluster = m_volume -> next_cluster(cluster); - - // Read the full sector into a buffer - lba_t base_lba = m_volume->data_lba + (cluster - 2) * m_volume->bpb.sectors_per_cluster; - buffer_t sector_buffer(bytes_per_sector, false); - m_volume->disk->read(base_lba + sector_offset, §or_buffer); - - // Update the entry in the buffer - sector_buffer.copy_from(&entry, sizeof(dir_entry_t), in_sector_offset); - m_volume->disk->write(base_lba + sector_offset, §or_buffer); + // Get the entry + auto entry = m_entries[index]; + + // Determine sector offset and in-sector byte offset + uint32_t bytes_per_sector = m_volume->bpb.bytes_per_sector; + uint32_t entry_offset = index * sizeof(dir_entry_t); + uint32_t sector_offset = entry_offset / bytes_per_sector; + uint32_t in_sector_offset = entry_offset % bytes_per_sector; + + // Find which cluster has the entry + uint32_t cluster = m_first_cluster; + for (uint32_t offset_remaining = entry_offset; + offset_remaining >= bytes_per_sector; offset_remaining -= bytes_per_sector) + cluster = m_volume->next_cluster(cluster); + + // Read the full sector into a buffer + lba_t base_lba = m_volume->data_lba + (cluster - 2) * m_volume->bpb.sectors_per_cluster; + buffer_t sector_buffer(bytes_per_sector, false); + m_volume->disk->read(base_lba + sector_offset, §or_buffer); + + // Update the entry in the buffer + sector_buffer.copy_from(&entry, sizeof(dir_entry_t), in_sector_offset); + m_volume->disk->write(base_lba + sector_offset, §or_buffer); } /** @@ -619,30 +609,29 @@ void Fat32Directory::update_entry_on_disk(int index) { */ int Fat32Directory::entry_index(lba_t cluster) { - int entry_index = 0; - for (; entry_index < m_entries.size(); entry_index++) - { - auto& entry = m_entries[entry_index]; + int entry_index = 0; + for (; entry_index < m_entries.size(); entry_index++) { + auto &entry = m_entries[entry_index]; - // End of directory means no more entries - if (entry.name[0] == (uint8_t)DirectoryEntryType::LAST) - return -1; + // End of directory means no more entries + if (entry.name[0] == (uint8_t) DirectoryEntryType::LAST) + return -1; - // Skip free entries - if (entry.name[0] == (uint8_t)DirectoryEntryType::FREE) - continue; + // Skip free entries + if (entry.name[0] == (uint8_t) DirectoryEntryType::FREE) + continue; - // Check if the entry is the one - uint32_t start_cluster = (entry.first_cluster_high << 16) | entry.first_cluster_low; - if (start_cluster == cluster) - break; - } + // Check if the entry is the one + uint32_t start_cluster = (entry.first_cluster_high << 16) | entry.first_cluster_low; + if (start_cluster == cluster) + break; + } - // Make sure the entry is valid - if (entry_index >= m_entries.size()) - return -1; + // Make sure the entry is valid + if (entry_index >= m_entries.size()) + return -1; - return entry_index; + return entry_index; } @@ -655,19 +644,18 @@ int Fat32Directory::entry_index(lba_t cluster) { */ int Fat32Directory::find_free_entries(size_t amount) { - for (int entry_index = 0; entry_index < m_entries.size(); entry_index++) - { - // Check if there are enough free entries in a row - bool found = true; - for (size_t j = 0; j < amount; j++) - if (m_entries[entry_index + j].name[0] != (char)DirectoryEntryType::FREE) - found = false; + for (int entry_index = 0; entry_index < m_entries.size(); entry_index++) { + // Check if there are enough free entries in a row + bool found = true; + for (size_t j = 0; j < amount; j++) + if (m_entries[entry_index + j].name[0] != (char) DirectoryEntryType::FREE) + found = false; - if (found) - return entry_index; - } + if (found) + return entry_index; + } - return -1; + return -1; } @@ -683,48 +671,49 @@ int Fat32Directory::find_free_entries(size_t amount) { */ int Fat32Directory::expand_directory(size_t amount) { - // Remove the old end of directory marker - int free_start = m_entries.size() - 1; - ASSERT(m_entries[free_start].name[0] == (uint8_t)DirectoryEntryType::LAST, "Last entry is not marked"); - m_entries[free_start].name[0] = (uint8_t)DirectoryEntryType::FREE; - - // Count how many free entries there is before the end - for (int i = free_start; i >= 0; --i) { - if(m_entries[i].name[0] == (char)DirectoryEntryType::FREE) - free_start = i; - else - break; - } - - // Calculate how many entries are need to be created (ie was there enough free entries already) - uint32_t found = m_entries.size() - free_start; - uint32_t needed_entries = amount + 1; - uint32_t additional_entries = 0; - if(needed_entries > found) - additional_entries = needed_entries - found; - - // Find the length of the current cluster chain - uint32_t total_entries = m_entries.size() + additional_entries; - uint32_t total_clusters = (total_entries * sizeof(dir_entry_t)) / (m_volume->bpb.bytes_per_sector * m_volume->bpb.sectors_per_cluster); - - // Expand the cluster chain if needed - if(total_clusters > m_current_cluster_length){ - m_volume->allocate_cluster(m_last_cluster, total_clusters - m_current_cluster_length); - m_current_cluster_length = total_clusters; - } - - // Expand the directory to fit the remaining entries - for (int i = 0; i < additional_entries; ++i) { - dir_entry_t free = {}; - free.name[0] = (uint8_t)DirectoryEntryType::FREE; - m_entries.push_back(free); - } - - // Write the updated end of entries - m_entries[m_entries.size() - 1].name[0] = (uint8_t)DirectoryEntryType::LAST; - update_entry_on_disk(m_entries.size() - 1); - - return free_start; + // Remove the old end of directory marker + int free_start = m_entries.size() - 1; + ASSERT(m_entries[free_start].name[0] == (uint8_t) DirectoryEntryType::LAST, "Last entry is not marked"); + m_entries[free_start].name[0] = (uint8_t) DirectoryEntryType::FREE; + + // Count how many free entries there is before the end + for (int i = free_start; i >= 0; --i) { + if (m_entries[i].name[0] == (char) DirectoryEntryType::FREE) + free_start = i; + else + break; + } + + // Calculate how many entries are need to be created (ie was there enough free entries already) + uint32_t found = m_entries.size() - free_start; + uint32_t needed_entries = amount + 1; + uint32_t additional_entries = 0; + if (needed_entries > found) + additional_entries = needed_entries - found; + + // Find the length of the current cluster chain + uint32_t total_entries = m_entries.size() + additional_entries; + uint32_t total_clusters = (total_entries * sizeof(dir_entry_t)) / + (m_volume->bpb.bytes_per_sector * m_volume->bpb.sectors_per_cluster); + + // Expand the cluster chain if needed + if (total_clusters > m_current_cluster_length) { + m_volume->allocate_cluster(m_last_cluster, total_clusters - m_current_cluster_length); + m_current_cluster_length = total_clusters; + } + + // Expand the directory to fit the remaining entries + for (int i = 0; i < additional_entries; ++i) { + dir_entry_t free = {}; + free.name[0] = (uint8_t) DirectoryEntryType::FREE; + m_entries.push_back(free); + } + + // Write the updated end of entries + m_entries[m_entries.size() - 1].name[0] = (uint8_t) DirectoryEntryType::LAST; + update_entry_on_disk(m_entries.size() - 1); + + return free_start; } /** @@ -736,43 +725,41 @@ int Fat32Directory::expand_directory(size_t amount) { */ Vector Fat32Directory::to_long_filenames(string name) { - size_t lfn_count = (name.length() + 12) / 13; - Vector lfn_entries; - - // Create the long file name entries (in reverse order) - for (int i = lfn_count - 1; i >= 0; i--) - { - // Create the long file name entry - long_file_name_entry_t lfn_entry; - lfn_entry.order = i + 1; - lfn_entry.attributes = 0x0F; - lfn_entry.type = 0; - lfn_entry.checksum = 0; - - // If it is the last entry, set the last bit - if (i == lfn_entries.size() - 1) - lfn_entry.order |= 0x40; - - // Set the name - for (int j = 0; j < 13; j++) - { - - // Get the character info (0xFFFF if out of bounds) - size_t char_index = i * 13 + j; - char c = (char_index < name.length()) ? name[char_index] : 0xFFFF; - - // Set the character in the entry - if (j < 5) - lfn_entry.name1[j] = c; - else if (j < 11) - lfn_entry.name2[j - 5] = c; - else - lfn_entry.name3[j - 11] = c; - } - lfn_entries.push_back(lfn_entry); - } - - return lfn_entries; + size_t lfn_count = (name.length() + 12) / 13; + Vector lfn_entries; + + // Create the long file name entries (in reverse order) + for (int i = lfn_count - 1; i >= 0; i--) { + // Create the long file name entry + long_file_name_entry_t lfn_entry; + lfn_entry.order = i + 1; + lfn_entry.attributes = 0x0F; + lfn_entry.type = 0; + lfn_entry.checksum = 0; + + // If it is the last entry, set the last bit + if (i == lfn_entries.size() - 1) + lfn_entry.order |= 0x40; + + // Set the name + for (int j = 0; j < 13; j++) { + + // Get the character info (0xFFFF if out of bounds) + size_t char_index = i * 13 + j; + char c = (char_index < name.length()) ? name[char_index] : 0xFFFF; + + // Set the character in the entry + if (j < 5) + lfn_entry.name1[j] = c; + else if (j < 11) + lfn_entry.name2[j - 5] = c; + else + lfn_entry.name3[j - 11] = c; + } + lfn_entries.push_back(lfn_entry); + } + + return lfn_entries; } /** @@ -783,86 +770,86 @@ Vector Fat32Directory::to_long_filenames(string name) { * * @return The parsed entry prepended to the current string */ -string Fat32Directory::parse_long_filename(long_file_name_entry_t* entry, const string& current) { - - // Extract the long name from each part (in reverse order) - string current_long_name = ""; - for (int i = 0; i < 13; i++) { - - // Get the character (in utf8 encoding) - char c; - if (i < 5) - c = entry -> name1[i] & 0xFF; - else if (i < 11) - c = entry -> name2[i - 5] & 0xFF; - else - c = entry -> name3[i - 11] & 0xFF; - - // Padding / invalid or end of string - if (c == (char)0xFF || c == '\0') - break; - - // Add to the start as the entries are stored in reverse - current_long_name += string(c); - } - - // Entry parsed (prepend name) - return current_long_name + current; +string Fat32Directory::parse_long_filename(long_file_name_entry_t *entry, const string ¤t) { + + // Extract the long name from each part (in reverse order) + string current_long_name = ""; + for (int i = 0; i < 13; i++) { + + // Get the character (in utf8 encoding) + char c; + if (i < 5) + c = entry->name1[i] & 0xFF; + else if (i < 11) + c = entry->name2[i - 5] & 0xFF; + else + c = entry->name3[i - 11] & 0xFF; + + // Padding / invalid or end of string + if (c == (char) 0xFF || c == '\0') + break; + + // Add to the start as the entries are stored in reverse + current_long_name += string(c); + } + + // Entry parsed (prepend name) + return current_long_name + current; } void Fat32Directory::read_from_disk() { - for(auto& file : m_files) - delete file; - m_files.clear(); + for (auto &file: m_files) + delete file; + m_files.clear(); - for(auto& directory : m_subdirectories) - delete directory; - m_subdirectories.clear(); + for (auto &directory: m_subdirectories) + delete directory; + m_subdirectories.clear(); - // Load the entries from the disk into memory - read_all_entries(); + // Load the entries from the disk into memory + read_all_entries(); - // Parse the entries - string long_name = ""; - for(auto& entry : m_entries){ + // Parse the entries + string long_name = ""; + for (auto &entry: m_entries) { - // Skip free entries and volume labels - if (entry.name[0] == (uint8_t)DirectoryEntryType::FREE - || entry.attributes == (uint8_t)DirectoryEntryAttributes::FREE - || entry.attributes == (uint8_t)DirectoryEntryAttributes::VOLUME_ID) - continue; + // Skip free entries and volume labels + if (entry.name[0] == (uint8_t) DirectoryEntryType::FREE + || entry.attributes == (uint8_t) DirectoryEntryAttributes::FREE + || entry.attributes == (uint8_t) DirectoryEntryAttributes::VOLUME_ID) + continue; - // Extract the long name - if (entry.attributes == (uint8_t)DirectoryEntryAttributes::LONG_NAME) { - long_name = parse_long_filename((long_file_name_entry_t*)&entry, long_name); - continue; - } + // Extract the long name + if (entry.attributes == (uint8_t) DirectoryEntryAttributes::LONG_NAME) { + long_name = parse_long_filename((long_file_name_entry_t *) &entry, long_name); + continue; + } - bool is_directory = entry.attributes == (uint8_t)DirectoryEntryAttributes::DIRECTORY; + bool is_directory = entry.attributes == (uint8_t) DirectoryEntryAttributes::DIRECTORY; - // Get the name of the entry - string name = long_name; - if(long_name == ""){ - name = string(entry.name, 8); + // Get the name of the entry + string name = long_name; + if (long_name == "") { + name = string(entry.name, 8); - // Add the extension - if(!is_directory) - name = name.strip() + "." + string(entry.extension, 3); - } + // Add the extension + if (!is_directory) + name = name.strip() + "." + string(entry.extension, 3); + } - long_name = ""; + long_name = ""; - // Get the starting cluster - uint32_t start_cluster = (entry.first_cluster_high << 16) | entry.first_cluster_low; + // Get the starting cluster + uint32_t start_cluster = (entry.first_cluster_high << 16) | entry.first_cluster_low; - // Store the file or directory - if (is_directory) - m_subdirectories.push_back(new Fat32Directory(m_volume, start_cluster, name.strip())); - else - m_files.push_back(new Fat32File(m_volume, this, &entry, name)); + // Store the file or directory + if (is_directory) + m_subdirectories.push_back(new Fat32Directory(m_volume, start_cluster, name.strip())); + else + m_files.push_back(new Fat32File(m_volume, this, &entry, name)); - } + } } /** @@ -871,22 +858,21 @@ void Fat32Directory::read_from_disk() { * @param name The name of the file to create * @return The new file object or null if it could not be created */ -File* Fat32Directory::create_file(const string& name) -{ +File *Fat32Directory::create_file(const string &name) { - // Check if the file already exists - for (auto & file : m_files) - if (file -> name() == name) - return nullptr; + // Check if the file already exists + for (auto &file: m_files) + if (file->name() == name) + return nullptr; - // Check if the name is too long - if (name.length() > MAX_NAME_LENGTH) - return nullptr; + // Check if the name is too long + if (name.length() > MAX_NAME_LENGTH) + return nullptr; - // Create the file - auto file = new Fat32File(m_volume, this, create_entry(name, false), name); - m_files.push_back(file); - return file; + // Create the file + auto file = new Fat32File(m_volume, this, create_entry(name, false), name); + m_files.push_back(file); + return file; } /** @@ -894,21 +880,19 @@ File* Fat32Directory::create_file(const string& name) * * @param name The name of the file to delete */ -void Fat32Directory::remove_file(const string& name) -{ - // Find the file if it exists - for (auto& file : m_files) - if (file -> name() == name) - { - - // Remove the file from the directory - m_files.erase(file); - remove_entry(((Fat32File*)file) -> first_cluster(), name); - - // Delete the file reference - delete file; - return; - } +void Fat32Directory::remove_file(const string &name) { + // Find the file if it exists + for (auto &file: m_files) + if (file->name() == name) { + + // Remove the file from the directory + m_files.erase(file); + remove_entry(((Fat32File *) file)->first_cluster(), name); + + // Delete the file reference + delete file; + return; + } } /** @@ -917,27 +901,25 @@ void Fat32Directory::remove_file(const string& name) * @param name The name of the directory to create * @return The new directory object or null if it could not be created */ -Directory* Fat32Directory::create_subdirectory(const string& name) -{ +Directory *Fat32Directory::create_subdirectory(const string &name) { - // Check if the directory already exists - for (auto & subdirectory : m_subdirectories) - if (subdirectory -> name() == name) - return nullptr; + // Check if the directory already exists + for (auto &subdirectory: m_subdirectories) + if (subdirectory->name() == name) + return nullptr; - // Check if the name is too long - if (name.length() > MAX_NAME_LENGTH) - return nullptr; + // Check if the name is too long + if (name.length() > MAX_NAME_LENGTH) + return nullptr; - // Create the directory - auto entry = create_entry(name, true); - uint32_t cluster = ((entry -> first_cluster_high << 16) | entry -> first_cluster_low); - - // Store the directory - auto directory = new Fat32Directory(m_volume, cluster, name); - m_subdirectories.push_back(directory); - return directory; + // Create the directory + auto entry = create_entry(name, true); + uint32_t cluster = ((entry->first_cluster_high << 16) | entry->first_cluster_low); + // Store the directory + auto directory = new Fat32Directory(m_volume, cluster, name); + m_subdirectories.push_back(directory); + return directory; } /** @@ -945,38 +927,37 @@ Directory* Fat32Directory::create_subdirectory(const string& name) * * @param name The name of the entry to remove */ -void Fat32Directory::remove_subdirectory(const string& name) -{ - // Find the directory if it exists - for (auto& subdirectory : m_subdirectories) { - if (subdirectory->name() != name) - continue; - - // Remove all the files in the directory - for (auto &file : subdirectory->files()) - subdirectory->remove_file(file->name()); - - // Remove all the subdirectories in the directory - for (auto &subdirectory : subdirectory->subdirectories()) - subdirectory->remove_subdirectory(subdirectory->name()); - - // Remove the entry - m_subdirectories.erase(subdirectory); - remove_entry(((Fat32Directory *)subdirectory)->first_cluster(), name); - - // Delete the directory - delete subdirectory; - return; - } +void Fat32Directory::remove_subdirectory(const string &name) { + // Find the directory if it exists + for (auto &subdirectory: m_subdirectories) { + if (subdirectory->name() != name) + continue; + + // Remove all the files in the directory + for (auto &file: subdirectory->files()) + subdirectory->remove_file(file->name()); + + // Remove all the subdirectories in the directory + for (auto &subdirectory: subdirectory->subdirectories()) + subdirectory->remove_subdirectory(subdirectory->name()); + + // Remove the entry + m_subdirectories.erase(subdirectory); + remove_entry(((Fat32Directory *) subdirectory)->first_cluster(), name); + + // Delete the directory + delete subdirectory; + return; + } } -Fat32FileSystem::Fat32FileSystem(Disk* disk, uint32_t partition_offset) +Fat32FileSystem::Fat32FileSystem(Disk *disk, uint32_t partition_offset) : m_volume(disk, partition_offset) { - // Create the root directory - m_root_directory = new Fat32Directory(&m_volume, m_volume.bpb.root_cluster, "/"); - m_root_directory -> read_from_disk(); + // Create the root directory + m_root_directory = new Fat32Directory(&m_volume, m_volume.bpb.root_cluster, "/"); + m_root_directory->read_from_disk(); } diff --git a/kernel/src/filesystem/filesystem.cpp b/kernel/src/filesystem/filesystem.cpp index b1b7bfc1..c916c9b8 100644 --- a/kernel/src/filesystem/filesystem.cpp +++ b/kernel/src/filesystem/filesystem.cpp @@ -15,18 +15,18 @@ using namespace MaxOS::common; * @param path The path to check * @return True if the path is valid, false otherwise */ -bool Path::vaild(string path) -{ - // Must not be empty - if (path.length() == 0) - return false; +bool Path::vaild(string path) { - // Must start with a / - if (path[0] != '/') - return false; + // Must not be empty + if (path.length() == 0) + return false; - // Valid - return true; + // Must start with a / + if (path[0] != '/') + return false; + + // Valid + return true; } @@ -36,22 +36,21 @@ bool Path::vaild(string path) * @param path The path to get the file name from * @return The file name or the original path if it does not exist */ -string Path::file_name(string path) -{ - - // Find the last / - int last_slash = -1; - for (int i = 0; i < path.length(); i++) - if (path[i] == '/') - last_slash = i; - - // Make sure there was a slash to split - if (last_slash == -1) - return path; - - // Get the file name - string file_name = path.substring(last_slash + 1, path.length() - last_slash - 1); - return file_name; +string Path::file_name(string path) { + + // Find the last / + int last_slash = -1; + for (int i = 0; i < path.length(); i++) + if (path[i] == '/') + last_slash = i; + + // Make sure there was a slash to split + if (last_slash == -1) + return path; + + // Get the file name + string file_name = path.substring(last_slash + 1, path.length() - last_slash - 1); + return file_name; } /** @@ -60,22 +59,21 @@ string Path::file_name(string path) * @param path The path to get the file extension from * @return The file extension or the original path if it does not exist */ -string Path::file_extension(string path) -{ +string Path::file_extension(string path) { - // Find the last . - int last_dot = -1; - for (int i = 0; i < path.length(); i++) - if (path[i] == '.') - last_dot = i; + // Find the last . + int last_dot = -1; + for (int i = 0; i < path.length(); i++) + if (path[i] == '.') + last_dot = i; - // Make sure there was a dot to split - if (last_dot == -1) - return path; + // Make sure there was a dot to split + if (last_dot == -1) + return path; - // Get the file extension (what is after the ".") - string file_extension = path.substring(last_dot + 1, path.length() - last_dot - 1); - return file_extension; + // Get the file extension (what is after the ".") + string file_extension = path.substring(last_dot + 1, path.length() - last_dot - 1); + return file_extension; } @@ -85,21 +83,21 @@ string Path::file_extension(string path) * @param path The path to get the file path from * @return The file path or the original path if it does not exist */ -string Path::file_path(string path) -{ - // Try to find the last / - int last_slash = -1; - for (int i = 0; i < path.length(); i++) - if (path[i] == '/') - last_slash = i; - - // Make sure there was a slash to split - if (last_slash == -1) - return path; - - // Get the file path - string file_path = path.substring(0, last_slash); - return file_path; +string Path::file_path(string path) { + + // Try to find the last / + int last_slash = -1; + for (int i = 0; i < path.length(); i++) + if (path[i] == '/') + last_slash = i; + + // Make sure there was a slash to split + if (last_slash == -1) + return path; + + // Get the file path + string file_path = path.substring(0, last_slash); + return file_path; } @@ -111,19 +109,19 @@ string Path::file_path(string path) */ string Path::top_directory(string path) { - // Find the first / - int first_slash = -1; - for (int i = 0; i < path.length(); i++) - if (path[i] == '/') - first_slash = i; + // Find the first / + int first_slash = -1; + for (int i = 0; i < path.length(); i++) + if (path[i] == '/') + first_slash = i; - // Make sure there was a slash to split - if (first_slash == -1) - return path; + // Make sure there was a slash to split + if (first_slash == -1) + return path; - // Get the top directory - string top_directory = path.substring(0, first_slash); - return top_directory; + // Get the top directory + string top_directory = path.substring(0, first_slash); + return top_directory; } File::File() = default; @@ -136,8 +134,7 @@ File::~File() = default; * @param data The byte buffer to write * @param size The amount of data to write */ -void File::write(const common::buffer_t* data, size_t amount) -{ +void File::write(const common::buffer_t* data, size_t amount) { } /** @@ -146,15 +143,13 @@ void File::write(const common::buffer_t* data, size_t amount) * @param data The byte buffer to read into * @param size The amount of data to read */ -void File::read(common::buffer_t* data, size_t amount) -{ +void File::read(common::buffer_t* data, size_t amount) { } /** * @brief Flush the file to the disk */ -void File::flush() -{ +void File::flush() { } /** @@ -163,25 +158,22 @@ void File::flush() * @param seek_type The type of seek to perform (where to seek to) * @param offset The amount to seek */ -void File::seek(SeekType seek_type, size_t offset) -{ - - // Seek based on the type - switch (seek_type) - { - case SeekType::SET: - m_offset = offset; - break; - - case SeekType::CURRENT: - m_offset += offset; - break; - - case SeekType::END: - m_offset = size() - offset; - break; - } - +void File::seek(SeekType seek_type, size_t offset) { + + // Seek based on the type + switch (seek_type) { + case SeekType::SET: + m_offset = offset; + break; + + case SeekType::CURRENT: + m_offset += offset; + break; + + case SeekType::END: + m_offset = size() - offset; + break; + } } /** @@ -189,9 +181,8 @@ void File::seek(SeekType seek_type, size_t offset) * * @return The current position in the file */ -uint32_t File::position() -{ - return m_offset; +uint32_t File::position() { + return m_offset; } /** @@ -199,9 +190,8 @@ uint32_t File::position() * * @return The name of the file */ -string File::name() -{ - return m_name; +string File::name() { + return m_name; } /** @@ -209,29 +199,28 @@ string File::name() * * @return The size of the file (in bytes) */ -size_t File::size() -{ - return m_size; +size_t File::size() { + return m_size; } Directory::Directory() = default; Directory::~Directory() { - // Free the files - for (auto & file : m_files) - delete file; + // Free the files + for (auto &file: m_files) + delete file; - // Free the subdirectories - for (auto & subdirectory : m_subdirectories) - delete subdirectory; + // Free the subdirectories + for (auto &subdirectory: m_subdirectories) + delete subdirectory; } /** * @brief Read the directory from the disk */ -void Directory::read_from_disk(){ +void Directory::read_from_disk() { } @@ -240,9 +229,8 @@ void Directory::read_from_disk(){ * * @return A list of all the files in the directory */ -common::Vector Directory::files() -{ - return m_files; +common::Vector Directory::files() { + return m_files; } /** @@ -251,16 +239,15 @@ common::Vector Directory::files() * @param name The name of the file to open * @return */ -File* Directory::open_file(const string& name) -{ +File* Directory::open_file(const string &name) { - // Try to find the file - for (auto& file : m_files) - if (file->name() == name) - return file; + // Try to find the file + for (auto &file: m_files) + if (file->name() == name) + return file; - // File not found - return nullptr; + // File not found + return nullptr; } /** @@ -269,9 +256,8 @@ File* Directory::open_file(const string& name) * @param name The name of the file to create * @return A new file object or null if it could not be created */ -File* Directory::create_file(const string& name) -{ - return nullptr; +File* Directory::create_file(const string &name) { + return nullptr; } /** @@ -279,8 +265,7 @@ File* Directory::create_file(const string& name) * * @param name The name of the file to remove */ -void Directory::remove_file(const string& name) -{ +void Directory::remove_file(const string &name) { } /** @@ -288,9 +273,8 @@ void Directory::remove_file(const string& name) * * @return The subdirectories in the directory */ -common::Vector Directory::subdirectories() -{ - return m_subdirectories; +common::Vector Directory::subdirectories() { + return m_subdirectories; } /** @@ -299,18 +283,17 @@ common::Vector Directory::subdirectories() * @param name The name of the directory to open * @return The directory object or null if it could not be opened */ -Directory* Directory::open_subdirectory(const string& name) -{ - - // Try to find the directory - for (auto& subdirectory : m_subdirectories) - if (subdirectory->name() == name) { - subdirectory -> read_from_disk(); - return subdirectory; - } - - // Directory not found - return nullptr; +Directory* Directory::open_subdirectory(const string &name) { + + // Try to find the directory + for (auto &subdirectory: m_subdirectories) + if (subdirectory->name() == name) { + subdirectory->read_from_disk(); + return subdirectory; + } + + // Directory not found + return nullptr; } /** @@ -319,17 +302,15 @@ Directory* Directory::open_subdirectory(const string& name) * @param name The name of the directory to create * @return The new directory object or null if it could not be created */ -Directory* Directory::create_subdirectory(const string& name) -{ - return nullptr; +Directory* Directory::create_subdirectory(const string &name) { + return nullptr; } /** * @brief Try to remove a directory in the directory * @param name The name of the directory to remove */ -void Directory::remove_subdirectory(const string& name) -{ +void Directory::remove_subdirectory(const string &name) { } @@ -338,9 +319,8 @@ void Directory::remove_subdirectory(const string& name) * * @return The name of the directory */ -string Directory::name() -{ - return m_name; +string Directory::name() { + return m_name; } /** @@ -348,26 +328,26 @@ string Directory::name() * * @return The size of the directory (sum of all the files in bytes) */ -size_t Directory::size() -{ +size_t Directory::size() { - // Sum the size of all the files - size_t size = 0; - for (auto& file : m_files) - size += file->size(); + // Sum the size of all the files + size_t size = 0; + for (auto &file: m_files) + size += file->size(); - return size; + return size; } + /** * @brief Rename a file in the directory * * @param file The file to rename * @param new_name The new name of the file */ -void Directory::rename_file(File *file, string const &new_name) { +void Directory::rename_file(File* file, string const &new_name) { - rename_file(file -> name(), new_name); + rename_file(file->name(), new_name); } @@ -379,7 +359,7 @@ void Directory::rename_file(File *file, string const &new_name) { */ void Directory::rename_file(string const &old_name, string const &new_name) { - ASSERT(false, "not implemented"); + ASSERT(false, "not implemented"); } @@ -389,9 +369,9 @@ void Directory::rename_file(string const &old_name, string const &new_name) { * @param directory The directory to rename * @param new_name The new name of the directory */ -void Directory::rename_subdirectory(Directory *directory, string const &new_name) { +void Directory::rename_subdirectory(Directory* directory, string const &new_name) { - rename_subdirectory(directory -> name(), new_name); + rename_subdirectory(directory->name(), new_name); } @@ -402,15 +382,15 @@ void Directory::rename_subdirectory(Directory *directory, string const &new_name * @param new_name The new name of the directory */ void Directory::rename_subdirectory(string const &old_name, string const &new_name) { - ASSERT(false, "not implemented"); + ASSERT(false, "not implemented"); } FileSystem::FileSystem() = default; FileSystem::~FileSystem() { - // Free the root directory - delete m_root_directory; + // Free the root directory + delete m_root_directory; }; @@ -419,9 +399,8 @@ FileSystem::~FileSystem() { * * @return The root directory of the filesystem */ -Directory* FileSystem::root_directory() -{ - return m_root_directory; +Directory* FileSystem::root_directory() { + return m_root_directory; } /** @@ -430,34 +409,33 @@ Directory* FileSystem::root_directory() * @param path The path to the directory * @return The directory object or null if it could not be opened */ -Directory* FileSystem::get_directory(const string& path) -{ - - // Check if the path is the root - if (path == "/") - return root_directory(); - - // Recursively open the directory - Directory* directory = root_directory(); - string directory_path = path; - while (directory_path.length() > 0) - { - // Get the name of the directory - string directory_name = Path::top_directory(directory_path); - - // Open the directory - Directory* subdirectory = directory -> open_subdirectory(directory_name); - if (!subdirectory) - return nullptr; - - // Set the new directory - directory = subdirectory; - - // Get the path to the next directory - directory_path = directory_path.substring(directory_name.length() + 1, directory_path.length() - directory_name.length() - 1); - } - - return directory; +Directory* FileSystem::get_directory(const string &path) { + + // Check if the path is the root + if (path == "/") + return root_directory(); + + // Recursively open the directory + Directory* directory = root_directory(); + string directory_path = path; + while (directory_path.length() > 0) { + + // Get the name of the directory + string directory_name = Path::top_directory(directory_path); + + // Open the directory + Directory* subdirectory = directory->open_subdirectory(directory_name); + if (!subdirectory) + return nullptr; + + // Set the new directory + directory = subdirectory; + + // Get the path to the next directory + directory_path = directory_path.substring(directory_name.length() + 1, directory_path.length() - directory_name.length() - 1); + } + + return directory; } /** @@ -466,18 +444,17 @@ Directory* FileSystem::get_directory(const string& path) * @param path The path to check * @return True if the path exists, false otherwise */ -bool FileSystem::exists(const string& path) -{ +bool FileSystem::exists(const string &path) { - // Get the directory at the path - string directory_path = Path::file_path(path); - Directory* directory = get_directory(directory_path); + // Get the directory at the path + string directory_path = Path::file_path(path); + Directory* directory = get_directory(directory_path); - // Check if the directory exists - if (!directory) - return false; + // Check if the directory exists + if (!directory) + return false; - // Check if the file exists - const string file_name = Path::file_name(path); - return directory->open_file(file_name) != nullptr; + // Check if the file exists + const string file_name = Path::file_name(path); + return directory->open_file(file_name) != nullptr; } \ No newline at end of file diff --git a/kernel/src/filesystem/partition/msdos.cpp b/kernel/src/filesystem/partition/msdos.cpp index 8e5963ce..834bc98f 100644 --- a/kernel/src/filesystem/partition/msdos.cpp +++ b/kernel/src/filesystem/partition/msdos.cpp @@ -15,52 +15,50 @@ using namespace MaxOS::drivers::disk; * * @param hd The hard disk to read the partition table from */ -void MSDOSPartition::mount_partitions(Disk* hd) { +void MSDOSPartition::mount_partitions(Disk *hd) { - // Read the MBR from the hard disk - MasterBootRecord mbr = {}; - buffer_t mbr_buffer(&mbr,sizeof(MasterBootRecord)); - hd -> read(0, &mbr_buffer); + // Read the MBR from the hard disk + MasterBootRecord mbr = {}; + buffer_t mbr_buffer(&mbr, sizeof(MasterBootRecord)); + hd->read(0, &mbr_buffer); - // Check if the magic number is correct - if(mbr.magic != 0xAA55) - { - Logger::WARNING() << "Could not find valid MBR on disk 0x" << (uint64_t)hd << "\n"; - return; - } + // Check if the magic number is correct + if (mbr.magic != 0xAA55) { + Logger::WARNING() << "Could not find valid MBR on disk 0x" << (uint64_t) hd << "\n"; + return; + } - // Get the VFS - VirtualFileSystem* vfs = VirtualFileSystem::current_file_system(); + // Get the VFS + VirtualFileSystem *vfs = VirtualFileSystem::current_file_system(); - // Loop through the primary partitions - for(auto& entry : mbr.primary_partition){ + // Loop through the primary partitions + for (auto &entry: mbr.primary_partition) { - // Empty entry - if(entry.type == 0) - continue; + // Empty entry + if (entry.type == 0) + continue; - Logger::DEBUG() << "Partition 0x" << (uint64_t)entry.type << " at 0x" << (uint64_t)entry.start_LBA << ": "; + Logger::DEBUG() << "Partition 0x" << (uint64_t) entry.type << " at 0x" << (uint64_t) entry.start_LBA << ": "; - // Create a file system for the partition - switch ((PartitionType)entry.type) - { - case PartitionType::EMPTY: - Logger::Out() << "Empty partition\n"; - break; + // Create a file system for the partition + switch ((PartitionType) entry.type) { + case PartitionType::EMPTY: + Logger::Out() << "Empty partition\n"; + break; - case PartitionType::FAT32: - Logger::Out() << "FAT32 partition\n"; - vfs -> mount_filesystem(new Fat32FileSystem(hd, entry.start_LBA)); - break; + case PartitionType::FAT32: + Logger::Out() << "FAT32 partition\n"; + vfs->mount_filesystem(new Fat32FileSystem(hd, entry.start_LBA)); + break; - case PartitionType::LINUX_EXT2: - Logger::Out() << "EXT2 partition\n"; - vfs -> mount_filesystem(new ext2::Ext2FileSystem(hd, entry.start_LBA)); - break; + case PartitionType::LINUX_EXT2: + Logger::Out() << "EXT2 partition\n"; + vfs->mount_filesystem(new ext2::Ext2FileSystem(hd, entry.start_LBA)); + break; - default: - Logger::Out() << "Unknown or unimplemented partition type: 0x" << (uint64_t)entry.type << "\n"; + default: + Logger::Out() << "Unknown or unimplemented partition type: 0x" << (uint64_t) entry.type << "\n"; - } - } + } + } } diff --git a/kernel/src/filesystem/vfs.cpp b/kernel/src/filesystem/vfs.cpp index c39713c4..0a722e07 100644 --- a/kernel/src/filesystem/vfs.cpp +++ b/kernel/src/filesystem/vfs.cpp @@ -8,21 +8,20 @@ using namespace MaxOS; using namespace MaxOS::filesystem; using namespace MaxOS::common; -VirtualFileSystem::VirtualFileSystem() -{ +VirtualFileSystem::VirtualFileSystem() { - // Set the current file system to this instance - s_current_file_system = this; + // Set the current file system to this instance + s_current_file_system = this; } -VirtualFileSystem::~VirtualFileSystem() -{ - // Remove all mounted filesystems - unmount_all(); +VirtualFileSystem::~VirtualFileSystem() { - // Set the current file system to null - s_current_file_system = nullptr; + // Remove all mounted filesystems + unmount_all(); + + // Set the current file system to null + s_current_file_system = nullptr; } @@ -31,9 +30,8 @@ VirtualFileSystem::~VirtualFileSystem() * * @return The current virtual file system or null if none is set */ -VirtualFileSystem* VirtualFileSystem::current_file_system() -{ - return s_current_file_system; +VirtualFileSystem* VirtualFileSystem::current_file_system() { + return s_current_file_system; } /** @@ -41,25 +39,23 @@ VirtualFileSystem* VirtualFileSystem::current_file_system() * * @param filesystem The filesystem to add */ -void VirtualFileSystem::mount_filesystem(FileSystem* filesystem) -{ +void VirtualFileSystem::mount_filesystem(FileSystem* filesystem) { - // Check if the filesystem is already mounted - if (filesystems.find(filesystem) != filesystems.end()) - { - Logger::WARNING() << "Filesystem already mounted\n"; - return; - } + // Check if the filesystem is already mounted + if (filesystems.find(filesystem) != filesystems.end()) { + Logger::WARNING() << "Filesystem already mounted\n"; + return; + } - // Get the mount point for the filesystem - string mount_point = "/filesystem_" + filesystems.size(); + // Get the mount point for the filesystem + string mount_point = "/filesystem_" + filesystems.size(); - // If this is the first filesystem to be mounted, set the root filesystem - if (filesystems.size() == 0) - mount_point = "/"; + // If this is the first filesystem to be mounted, set the root filesystem + if (filesystems.size() == 0) + mount_point = "/"; - // Add the filesystem to the map - filesystems.insert(filesystem, mount_point); + // Add the filesystem to the map + filesystems.insert(filesystem, mount_point); } /** @@ -68,18 +64,16 @@ void VirtualFileSystem::mount_filesystem(FileSystem* filesystem) * @param filesystem The filesystem to add * @param mount_point The mount point for the filesystem */ -void VirtualFileSystem::mount_filesystem(FileSystem* filesystem, string mount_point) -{ - - // Check if the filesystem is already mounted - if (filesystems.find(filesystem) != filesystems.end()) - { - Logger::WARNING() << "Filesystem already mounted at " << mount_point << "\n"; - return; - } - - // Add the filesystem to the map - filesystems.insert(filesystem, mount_point); +void VirtualFileSystem::mount_filesystem(FileSystem* filesystem, const string& mount_point) { + + // Check if the filesystem is already mounted + if (filesystems.find(filesystem) != filesystems.end()) { + Logger::WARNING() << "Filesystem already mounted at " << mount_point << "\n"; + return; + } + + // Add the filesystem to the map + filesystems.insert(filesystem, mount_point); } /** @@ -87,18 +81,17 @@ void VirtualFileSystem::mount_filesystem(FileSystem* filesystem, string mount_po * * @param filesystem The filesystem to remove */ -void VirtualFileSystem::unmount_filesystem(FileSystem* filesystem) -{ +void VirtualFileSystem::unmount_filesystem(FileSystem* filesystem) { - // Check if the filesystem is mounted - if (filesystems.find(filesystem) == filesystems.end()) - return; + // Check if the filesystem is mounted + if (filesystems.find(filesystem) == filesystems.end()) + return; - // Remove the filesystem from the map - filesystems.erase(filesystem); + // Remove the filesystem from the map + filesystems.erase(filesystem); - // Delete the filesystem - delete filesystem; + // Delete the filesystem + delete filesystem; } /** @@ -106,36 +99,26 @@ void VirtualFileSystem::unmount_filesystem(FileSystem* filesystem) * * @param mount_point Where the filesystem is mounted */ -void VirtualFileSystem::unmount_filesystem(string mount_point) -{ - // Check if the filesystem is mounted - auto it = filesystems.begin(); - while (it != filesystems.end()) - { - if (it->second == mount_point) - { - // Remove the filesystem from the map - return unmount_filesystem(it->first); - } - ++it; - } - - // Filesystem not found - Logger::WARNING() << "Filesystem not found at " << mount_point << "\n"; +void VirtualFileSystem::unmount_filesystem(const string& mount_point) { + + // Remove the filesystem from the map + for(const auto& filesystem : filesystems) + if (filesystem.second == mount_point) + return unmount_filesystem(filesystem.first); + + // Filesystem not found + Logger::WARNING() << "Filesystem not found at " << mount_point << "\n"; } /** * @brief Remove all mounted filesystems */ -void VirtualFileSystem::unmount_all() -{ - - // Loop through the filesystems and unmount them - for (auto it = filesystems.begin(); it != filesystems.end();) - { - unmount_filesystem(it->first); - it = filesystems.begin(); - } +void VirtualFileSystem::unmount_all() { + + // Loop through the filesystems and unmount them + for(const auto& filesystem : filesystems) + unmount_filesystem(filesystem.first); + } /** @@ -143,15 +126,14 @@ void VirtualFileSystem::unmount_all() * * @return The file system mounted "/" or null if none is mounted */ -FileSystem* VirtualFileSystem::root_filesystem() -{ +FileSystem* VirtualFileSystem::root_filesystem() { - // Ensure there is at least one filesystem mounted - if (filesystems.size() == 0) - return nullptr; + // Ensure there is at least one filesystem mounted + if (filesystems.size() == 0) + return nullptr; - // It is always the first filesystem mounted - return filesystems.begin()->first; + // It is always the first filesystem mounted + return filesystems.begin()->first; } /** @@ -160,20 +142,15 @@ FileSystem* VirtualFileSystem::root_filesystem() * @param mount_point The mount point to search for * @return The filesystem mounted at the given mount point or null if none is found */ -FileSystem* VirtualFileSystem::get_filesystem(string mount_point) -{ - - // Check if the filesystem is mounted - auto it = filesystems.begin(); - while (it != filesystems.end()) - { - if (it->second == mount_point) - return it->first; - ++it; - } - - // Filesystem not found - return nullptr; +FileSystem* VirtualFileSystem::get_filesystem(const string& mount_point) { + + // Check if the filesystem is mounted + for(const auto& filesystem : filesystems) + if (filesystem.second == mount_point) + return filesystem.first; + + // Filesystem not found + return nullptr; } /** @@ -182,29 +159,26 @@ FileSystem* VirtualFileSystem::get_filesystem(string mount_point) * @param path The path to search for * @return The filesystem that contains the path or null if none is found */ -FileSystem* VirtualFileSystem::find_filesystem(string path) -{ - - // Longest matching path will be where the filesystem is mounted - string longest_match = ""; - FileSystem* longest_match_fs = nullptr; - - // Search through the filesystems - for (auto it = filesystems.begin(); it != filesystems.end(); ++it) - { - // Get the filesystem and mount point - FileSystem* fs = it->first; - string mount_point = it->second; - - // Check if the path starts with the mount point - if (path.starts_with(mount_point) && mount_point.length() > longest_match.length()) - { - longest_match = mount_point; - longest_match_fs = fs; - } - } - - return longest_match_fs; +FileSystem* VirtualFileSystem::find_filesystem(string path) { + + // Longest matching path will be where the filesystem is mounted + string longest_match = ""; + FileSystem* longest_match_fs = nullptr; + + // Search through the filesystems + for (const auto& filesystem : filesystems) { + // Get the filesystem and mount point + FileSystem* fs = filesystem.first; + string mount_point = filesystem.second; + + // Check if the path starts with the mount point + if (path.starts_with(mount_point) && mount_point.length() > longest_match.length()) { + longest_match = mount_point; + longest_match_fs = fs; + } + } + + return longest_match_fs; } /** @@ -214,23 +188,23 @@ FileSystem* VirtualFileSystem::find_filesystem(string path) * @param path The path to get the relative path for * @return The relative path on the filesystem or an empty string if none is found */ -string VirtualFileSystem::get_relative_path(FileSystem* filesystem, string path) -{ - // Find the mount point for the filesystem - auto it = filesystems.find(filesystem); - if (it == filesystems.end()) - return ""; - - // Get the mount point - string mount_point = it->second; - - // Make sure that the path points to the filesystem - if (!path.starts_with(mount_point)) - return ""; - - // Get the relative path - string relative_path = path.substring(mount_point.length(), path.length() - mount_point.length()); - return relative_path; +string VirtualFileSystem::get_relative_path(FileSystem* filesystem, string path) { + + // Find the mount point for the filesystem + auto fs = filesystems.find(filesystem); + if (fs == filesystems.end()) + return ""; + + // Get the mount point + string mount_point = fs->second; + + // Make sure that the path points to the filesystem + if (!path.starts_with(mount_point)) + return ""; + + // Get the relative path + string relative_path = path.substring(mount_point.length(), path.length() - mount_point.length()); + return relative_path; } /** @@ -238,17 +212,15 @@ string VirtualFileSystem::get_relative_path(FileSystem* filesystem, string path) * * @return The root directory of the virtual file system */ -Directory* VirtualFileSystem::root_directory() -{ - // Get the root filesystem - FileSystem* fs = root_filesystem(); +Directory* VirtualFileSystem::root_directory() { - // Check if the filesystem is mounted - if (!fs) - return nullptr; + // Get the root filesystem + FileSystem* fs = root_filesystem(); + if (!fs) + return nullptr; - // Get the root directory - return fs->root_directory(); + // Get the root directory + return fs->root_directory(); } /** @@ -257,28 +229,28 @@ Directory* VirtualFileSystem::root_directory() * @param path The path to the directory * @return The directory object or null if it could not be opened */ -Directory* VirtualFileSystem::open_directory(const string& path) -{ - // Ensure a valid path is given - if (!Path::vaild(path)) - return nullptr; - - // Try to find the filesystem that is responsible for the path - FileSystem* fs = find_filesystem(path); - if (!fs) - return nullptr; - - // Get where to open the directory - string relative_path = get_relative_path(fs, path); - string directory_path = Path::file_path(relative_path); - - // Open the directory - Directory* directory = fs -> get_directory(directory_path); - if (!directory) - return nullptr; - directory -> read_from_disk(); - - return directory; +Directory* VirtualFileSystem::open_directory(const string &path) { + + // Ensure a valid path is given + if (!Path::vaild(path)) + return nullptr; + + // Try to find the filesystem that is responsible for the path + FileSystem* fs = find_filesystem(path); + if (!fs) + return nullptr; + + // Get where to open the directory + string relative_path = get_relative_path(fs, path); + string directory_path = Path::file_path(relative_path); + + // Open the directory + Directory* directory = fs->get_directory(directory_path); + if (!directory) + return nullptr; + directory->read_from_disk(); + + return directory; } /** @@ -287,27 +259,26 @@ Directory* VirtualFileSystem::open_directory(const string& path) * @param path The path to the directory * @return The directory object or null if it could not be opened */ -Directory* VirtualFileSystem::create_directory(string path) -{ - // Ensure a valid path is given - if (!Path::vaild(path)) - return nullptr; +Directory* VirtualFileSystem::create_directory(string path) { - path = path.strip('/'); + // Ensure a valid path is given + if (!Path::vaild(path)) + return nullptr; - // Try to find the filesystem that is responsible for the path - FileSystem* fs = find_filesystem(path); - if (!fs) - return nullptr; + path = path.strip('/'); - // Open the parent directory - Directory* parent_directory = open_directory(path); - if(!parent_directory) - return nullptr; + // Try to find the filesystem that is responsible for the path + FileSystem* fs = find_filesystem(path); + if (!fs) + return nullptr; + + // Open the parent directory + Directory* parent_directory = open_directory(path); + if (!parent_directory) + return nullptr; string directory_name = Path::file_name(path); return create_directory(parent_directory, directory_name); - } /** @@ -317,14 +288,13 @@ Directory* VirtualFileSystem::create_directory(string path) * @param name The name of the new directory * @return The created directory */ -Directory *VirtualFileSystem::create_directory(Directory *parent, string const &name) { +Directory* VirtualFileSystem::create_directory(Directory* parent, string const &name) { // Create the directory - Directory* directory = parent -> create_subdirectory(name); - directory -> read_from_disk(); + Directory* directory = parent->create_subdirectory(name); + directory->read_from_disk(); return directory; - } @@ -333,23 +303,22 @@ Directory *VirtualFileSystem::create_directory(Directory *parent, string const & * * @param path The path to the directory */ -void VirtualFileSystem::delete_directory(string path) -{ - // Ensure a valid path is given - if (!Path::vaild(path)) - return; +void VirtualFileSystem::delete_directory(string path) { + + // Ensure a valid path is given + if (!Path::vaild(path)) + return; - path = path.strip('/'); + path = path.strip('/'); - // Open the directory - Directory* parent_directory = open_directory(path); + // Open the directory + Directory* parent_directory = open_directory(path); if (!parent_directory) return; // Delete the directory string directory_name = Path::file_name(path); delete_directory(parent_directory, directory_name); - } /** @@ -358,11 +327,11 @@ void VirtualFileSystem::delete_directory(string path) * @param parent The directory that contains the reference to the directory being deleted * @param name The name of the directory to delete */ -void VirtualFileSystem::delete_directory(Directory* parent, const string& name) { +void VirtualFileSystem::delete_directory(Directory* parent, const string &name) { // Find the directory and delete it - for(const auto& directory : parent -> subdirectories()) - if(directory -> name() == name) + for (const auto& directory: parent->subdirectories()) + if (directory->name() == name) delete_directory(parent, directory); } @@ -373,10 +342,10 @@ void VirtualFileSystem::delete_directory(Directory* parent, const string& name) * @param parent The directory that contains the reference to the directory being deleted * @param directory The the directory to delete */ -void VirtualFileSystem::delete_directory(Directory *parent, Directory *directory) { +void VirtualFileSystem::delete_directory(Directory* parent, Directory* directory) { // Nothing to delete - if(!directory) + if (!directory) return; // Store a reference to each subdirectory and its parent @@ -384,7 +353,7 @@ void VirtualFileSystem::delete_directory(Directory *parent, Directory *directory Vector> to_delete; stack.push_back(parent, directory); - while (!stack.empty()){ + while (!stack.empty()) { // Save the current auto current = stack.pop_back(); @@ -393,12 +362,12 @@ void VirtualFileSystem::delete_directory(Directory *parent, Directory *directory to_delete.push_back({current.first, current_directory}); // Empty the directory - for(const auto& file : current_directory->files()) + for (const auto &file: current_directory->files()) delete_file(current_directory, file->name()); // Process the subdirectories - for(const auto& subdir : current_directory->subdirectories()) - if(subdir -> name() != "." && subdir -> name() != "..") + for (const auto &subdir: current_directory->subdirectories()) + if (subdir->name() != "." && subdir->name() != "..") stack.push_back(current_directory, subdir); } @@ -407,9 +376,9 @@ void VirtualFileSystem::delete_directory(Directory *parent, Directory *directory for (int i = to_delete.size() - 1; i >= 0; --i) { // Get the parent and child - const auto& current = to_delete[i]; - Directory* owner = current.first; - Directory* subdirectory = current.second; + const auto ¤t = to_delete[i]; + Directory* owner = current.first; + Directory* subdirectory = current.second; owner->remove_subdirectory(subdirectory->name()); } @@ -422,19 +391,19 @@ void VirtualFileSystem::delete_directory(Directory *parent, Directory *directory * @param path The path to the file (including the extension) * @return The file object or null if it could not be created */ -File* VirtualFileSystem::create_file(const string& path) -{ - // Ensure a valid path is given - if (!Path::vaild(path)) - return nullptr; - - // Open the directory - Directory* directory = open_directory(path); - if (!directory) - return nullptr; - - // Create the file - string file_name = Path::file_name(path); +File* VirtualFileSystem::create_file(const string &path) { + + // Ensure a valid path is given + if (!Path::vaild(path)) + return nullptr; + + // Open the directory + Directory* directory = open_directory(path); + if (!directory) + return nullptr; + + // Create the file + string file_name = Path::file_name(path); return create_file(directory, file_name); } @@ -444,11 +413,10 @@ File* VirtualFileSystem::create_file(const string& path) * @param parent The directory where the file should be created * @param name The name of the file to create */ -File* VirtualFileSystem::create_file(Directory *parent, string const &name) { - return parent -> create_file(name); +File* VirtualFileSystem::create_file(Directory* parent, string const &name) { + return parent->create_file(name); } - /** * @brief Try to open a file on the virtual file system with a given offset * @@ -456,19 +424,18 @@ File* VirtualFileSystem::create_file(Directory *parent, string const &name) { * @param offset The offset to seek to (default = 0) * @return The file or null pointer if not found */ -File* VirtualFileSystem::open_file(const string& path, size_t offset) -{ - // Ensure a valid path is given - if (!Path::vaild(path)) - return nullptr; - - // Open the directory - Directory* directory = open_directory(path); - if (!directory) +File* VirtualFileSystem::open_file(const string &path, size_t offset) { + + // Ensure a valid path is given + if (!Path::vaild(path)) return nullptr; - return open_file(directory, Path::file_name(path), offset); + // Open the directory + Directory* directory = open_directory(path); + if (!directory) + return nullptr; + return open_file(directory, Path::file_name(path), offset); } /** @@ -479,15 +446,15 @@ File* VirtualFileSystem::open_file(const string& path, size_t offset) * @param offset How far in the file to open (default = 0) * @return The file or null pointer if not found */ -File *VirtualFileSystem::open_file(Directory *parent, string const &name, size_t offset) { +File* VirtualFileSystem::open_file(Directory* parent, string const &name, size_t offset) { // Open the file - File* opened_file = parent -> open_file(name); + File* opened_file = parent->open_file(name); if (!opened_file) return nullptr; // Seek to the offset - opened_file -> seek(SeekType::SET, offset); + opened_file->seek(SeekType::SET, offset); return opened_file; } @@ -497,21 +464,20 @@ File *VirtualFileSystem::open_file(Directory *parent, string const &name, size_t * * @param path The path to the file (including the extension) */ -void VirtualFileSystem::delete_file(const string& path) -{ - // Ensure a valid path is given - if (!Path::vaild(path)) - return; +void VirtualFileSystem::delete_file(const string &path) { - // Open the directory - Directory* directory = open_directory(path); - if (!directory) - return; + // Ensure a valid path is given + if (!Path::vaild(path)) + return; + + // Open the directory + Directory* directory = open_directory(path); + if (!directory) + return; // Delete the file string file_name = Path::file_name(path); delete_file(directory, file_name); - } /** @@ -520,9 +486,8 @@ void VirtualFileSystem::delete_file(const string& path) * @param parent The directory containing the file * @param name The name of the file */ -void VirtualFileSystem::delete_file(Directory *parent, string const &name) { +void VirtualFileSystem::delete_file(Directory* parent, string const &name) { // Delete the file - parent -> remove_file(name); - + parent->remove_file(name); } diff --git a/kernel/src/gui/desktop.cpp b/kernel/src/gui/desktop.cpp index 0eef46c2..f98e1b91 100644 --- a/kernel/src/gui/desktop.cpp +++ b/kernel/src/gui/desktop.cpp @@ -14,23 +14,23 @@ using namespace MaxOS::drivers::peripherals; * * @param gc The graphics context to use */ -Desktop::Desktop(GraphicsContext *gc) -: CompositeWidget(0,0, gc->width(), gc->height()), +Desktop::Desktop(GraphicsContext* gc) +: CompositeWidget(0, 0, gc->width(), gc->height()), MouseEventHandler(), ClockEventHandler(), m_graphics_context(gc), colour(Colour(0xA8, 0xA8, 0xA8)) { - // Set the mouse m_position to the center of the screen - m_mouse_x = gc->width() / 2; - m_mouse_y = gc->height() / 2; + // Set the mouse m_position to the center of the screen + m_mouse_x = gc->width() / 2; + m_mouse_y = gc->height() / 2; - // Draw the initial mouse cursor - invert_mouse_cursor(); + // Draw the initial mouse cursor + invert_mouse_cursor(); - // Draw the desktop - Widget::invalidate(); + // Draw the desktop + Widget::invalidate(); } Desktop::~Desktop() = default; @@ -42,14 +42,13 @@ Desktop::~Desktop() = default; */ void Desktop::set_focus(Widget* widget) { - // If there is a widget in focus then send a focus lost event to it - if(this ->m_focussed_widget != nullptr) - this->m_focussed_widget->on_focus_lost(); - - // Focus the new widget and send a focus event to it - this ->m_focussed_widget = widget; - this->m_focussed_widget->on_focus(); + // If there is a widget in focus then send a focus lost event to it + if (this->m_focussed_widget != nullptr) + this->m_focussed_widget->on_focus_lost(); + // Focus the new widget and send a focus event to it + this->m_focussed_widget = widget; + this->m_focussed_widget->on_focus(); } /** @@ -57,13 +56,13 @@ void Desktop::set_focus(Widget* widget) { * * @param front_widget The widget to bring to the front */ -void Desktop::bring_to_front(Widget*front_widget) { +void Desktop::bring_to_front(Widget* front_widget) { - // Remove the widget from where ever it already is - m_children.erase(front_widget); + // Remove the widget from where ever it already is + m_children.erase(front_widget); - // Add it back in the front - m_children.push_front(front_widget); + // Add it back in the front + m_children.push_front(front_widget); } /** @@ -71,17 +70,17 @@ void Desktop::bring_to_front(Widget*front_widget) { */ void Desktop::invert_mouse_cursor() { - //TODO: Get image drawing going and draw a proper mouse + //TODO: Get image drawing going and draw a proper mouse - // Draw the horizontal line - for (int32_t x = m_mouse_x - 3; x <= m_mouse_x + 3; ++x) { - m_graphics_context->invert_pixel(x, m_mouse_y); - } + // Draw the horizontal line + for (int32_t x = m_mouse_x - 3; x <= m_mouse_x + 3; ++x) { + m_graphics_context->invert_pixel(x, m_mouse_y); + } - // Draw the vertical line - for (int32_t y = m_mouse_y - 3; y <= m_mouse_y + 3; ++y) { - m_graphics_context->invert_pixel(m_mouse_x, y); - } + // Draw the vertical line + for (int32_t y = m_mouse_y - 3; y <= m_mouse_y + 3; ++y) { + m_graphics_context->invert_pixel(m_mouse_x, y); + } } /** @@ -91,38 +90,39 @@ void Desktop::invert_mouse_cursor() { * @param start The start of the invalid areas * @param stop The end of the invalid areas */ -void Desktop::internal_invalidate(common::Rectangle&area, Vector>::iterator start, Vector>::iterator stop) { +void Desktop::internal_invalidate(common::Rectangle& area, Vector>::iterator start, + Vector>::iterator stop) { - // Loop through the invalid rectangles - for(Vector>::iterator invaild_rect = start; invaild_rect != stop; invaild_rect++){ + // Loop through the invalid rectangles + for (Vector>::iterator invaild_rect = start; invaild_rect != stop; invaild_rect++) { - // Check if the area intersects with the invalid rectangle - if(!area.intersects(*invaild_rect)) - continue; + // Check if the area intersects with the invalid rectangle + if (!area.intersects(*invaild_rect)) + continue; - // Get the parts of the area that are covered by the invalid rectangle - Vector> coveredAreas = area.subtract(*invaild_rect); + // Get the parts of the area that are covered by the invalid rectangle + Vector> coveredAreas = area.subtract(*invaild_rect); - // Invalidate the covered areas - for(auto& coveredArea : coveredAreas) - internal_invalidate(coveredArea, invaild_rect + 1, stop); + // Invalidate the covered areas + for (auto& coveredArea: coveredAreas) + internal_invalidate(coveredArea, invaild_rect + 1, stop); - // The entire area will be invalidated by now - return; + // The entire area will be invalidated by now + return; - } + } - // Add the area to the invalid areas, store where it was added - Vector>::iterator vectorPosition = m_invalid_areas.push_back(area); + // Add the area to the invalid areas, store where it was added + Vector>::iterator vectorPosition = m_invalid_areas.push_back(area); - // If the m_position is the last item then the invalidation buffer is full - if(vectorPosition == m_invalid_areas.end()){ + // If the m_position is the last item then the invalidation buffer is full + if (vectorPosition == m_invalid_areas.end()) { - // Invalidate the entire desktop - m_invalid_areas.clear(); - Widget::invalidate(); + // Invalidate the entire desktop + m_invalid_areas.clear(); + Widget::invalidate(); - } + } } /** @@ -131,19 +131,18 @@ void Desktop::internal_invalidate(common::Rectangle&area, Vector &area) { - - //TODO: Draw a background image instead +void Desktop::draw_self(common::GraphicsContext* gc, common::Rectangle& area) { - // Calculate the rectangle - int32_t topCornerX = area.left; - int32_t topCornerY = area.top; - int32_t bottomCornerX = area.left + area.width; - int32_t bottomCornerY = area.top + area.height; + //TODO: Draw a background image instead - // Draw the background, a rectangle the size of the desktop of the given colour - gc->fill_rectangle(topCornerX, topCornerY, bottomCornerX, bottomCornerY, colour); + // Calculate the rectangle + int32_t topCornerX = area.left; + int32_t topCornerY = area.top; + int32_t bottomCornerX = area.left + area.width; + int32_t bottomCornerY = area.top + area.height; + // Draw the background, a rectangle the size of the desktop of the given colour + gc->fill_rectangle(topCornerX, topCornerY, bottomCornerX, bottomCornerY, colour); } /** @@ -151,21 +150,21 @@ void Desktop::draw_self(common::GraphicsContext *gc, common::Rectangle * * @param widget The widget to add */ -void Desktop::add_child(Widget*child_widget) { +void Desktop::add_child(Widget* child_widget) { - // Check if the new widget is under the mouse - bool underMouse = child_widget->contains_coordinate(m_mouse_x, m_mouse_y); + // Check if the new widget is under the mouse + bool underMouse = child_widget->contains_coordinate(m_mouse_x, m_mouse_y); - // If the mouse is over the widget then send a mouse leave event to the child widget as it is no longer under the mouse - if(underMouse) - CompositeWidget::on_mouse_leave_widget(m_mouse_x, m_mouse_y); + // If the mouse is over the widget then send a mouse leave event to the child widget as it is no longer under the mouse + if (underMouse) + CompositeWidget::on_mouse_leave_widget(m_mouse_x, m_mouse_y); - // Add the widget to the desktop - CompositeWidget::add_child(child_widget); + // Add the widget to the desktop + CompositeWidget::add_child(child_widget); - // If the mouse is over the new widget then send a mouse enter event to the child widget - if(underMouse) - CompositeWidget::on_mouse_enter_widget(m_mouse_x, m_mouse_y); + // If the mouse is over the new widget then send a mouse enter event to the child widget + if (underMouse) + CompositeWidget::on_mouse_enter_widget(m_mouse_x, m_mouse_y); } /** @@ -173,27 +172,27 @@ void Desktop::add_child(Widget*child_widget) { * * @param time The time when the event occurred */ -void Desktop::on_time(common::Time const &) { +void Desktop::on_time(common::Time const&) { - // Check if anything is invalid and needs to be redrawn - if(m_invalid_areas.empty()) - return; + // Check if anything is invalid and needs to be redrawn + if (m_invalid_areas.empty()) + return; - // Erase the mouse cursor - invert_mouse_cursor(); + // Erase the mouse cursor + invert_mouse_cursor(); - // Loop through the invalid areas - while (!m_invalid_areas.empty()) { + // Loop through the invalid areas + while (!m_invalid_areas.empty()) { - // Redraw the m_first_memory_chunk area - Rectangle invalidArea = *(m_invalid_areas.begin()); - m_invalid_areas.pop_front(); - draw(m_graphics_context, invalidArea); + // Redraw the m_first_memory_chunk area + Rectangle invalidArea = *(m_invalid_areas.begin()); + m_invalid_areas.pop_front(); + draw(m_graphics_context, invalidArea); - } + } - // Can now draw the mouse cursor - invert_mouse_cursor(); + // Can now draw the mouse cursor + invert_mouse_cursor(); } @@ -202,10 +201,10 @@ void Desktop::on_time(common::Time const &) { * * @param area The area that is now invalid */ -void Desktop::invalidate(Rectangle &area) { +void Desktop::invalidate(Rectangle& area) { - // Invalidate the area - internal_invalidate(area, m_invalid_areas.begin(), m_invalid_areas.end()); + // Invalidate the area + internal_invalidate(area, m_invalid_areas.begin(), m_invalid_areas.end()); } @@ -218,34 +217,34 @@ void Desktop::invalidate(Rectangle &area) { */ void Desktop::on_mouse_move_event(int8_t x, int8_t y) { - // Calculate the m_position of the mouse on the desktop - Rectangle desktopPosition = position(); - int32_t newMouseX = m_mouse_x + x; - int32_t newMouseY = m_mouse_y + y; + // Calculate the m_position of the mouse on the desktop + Rectangle desktopPosition = position(); + int32_t newMouseX = m_mouse_x + x; + int32_t newMouseY = m_mouse_y + y; - // Restrain the mouse to the desktop - if(newMouseX < 0) newMouseX = 0; - if(newMouseY < 0) newMouseY = 0; - if(newMouseX > desktopPosition.width) newMouseX = desktopPosition.width - 1; - if(newMouseY > desktopPosition.height) newMouseY = desktopPosition.height - 1; + // Restrain the mouse to the desktop + if (newMouseX < 0) newMouseX = 0; + if (newMouseY < 0) newMouseY = 0; + if (newMouseX > desktopPosition.width) newMouseX = desktopPosition.width - 1; + if (newMouseY > desktopPosition.height) newMouseY = desktopPosition.height - 1; - // Remove the old cursor from the screen as it will be redrawn in the new m_position - invert_mouse_cursor(); + // Remove the old cursor from the screen as it will be redrawn in the new m_position + invert_mouse_cursor(); - // If a widget is being dragged then pass the event to it - if(m_dragged_widget != nullptr) - m_dragged_widget->on_mouse_move_event(newMouseX - m_mouse_x, newMouseY - m_mouse_y); + // If a widget is being dragged then pass the event to it + if (m_dragged_widget != nullptr) + m_dragged_widget->on_mouse_move_event(newMouseX - m_mouse_x, newMouseY - m_mouse_y); - // Handle the mouse moving event (pass it to the widget that the mouse is over) - CompositeWidget::on_mouse_move_widget(m_mouse_x, m_mouse_y, newMouseX, - newMouseY); + // Handle the mouse moving event (pass it to the widget that the mouse is over) + CompositeWidget::on_mouse_move_widget(m_mouse_x, m_mouse_y, newMouseX, + newMouseY); - // Update the mouse m_position - m_mouse_x = newMouseX; - m_mouse_y = newMouseY; + // Update the mouse m_position + m_mouse_x = newMouseX; + m_mouse_y = newMouseY; - // Draw the new cursor - invert_mouse_cursor(); + // Draw the new cursor + invert_mouse_cursor(); } /** @@ -255,9 +254,8 @@ void Desktop::on_mouse_move_event(int8_t x, int8_t y) { */ void Desktop::on_mouse_down_event(uint8_t button) { - // The widget that handled the event becomes the widget being dragged - m_dragged_widget = CompositeWidget::on_mouse_button_pressed(m_mouse_x, m_mouse_y, button); - + // The widget that handled the event becomes the widget being dragged + m_dragged_widget = CompositeWidget::on_mouse_button_pressed(m_mouse_x, m_mouse_y, button); } /** @@ -267,12 +265,11 @@ void Desktop::on_mouse_down_event(uint8_t button) { */ void Desktop::on_mouse_up_event(uint8_t button) { - // Pass the event to the widget - CompositeWidget::on_mouse_button_released(m_mouse_x, m_mouse_y, button); - - // Dragging has stopped - m_dragged_widget = nullptr; + // Pass the event to the widget + CompositeWidget::on_mouse_button_released(m_mouse_x, m_mouse_y, button); + // Dragging has stopped + m_dragged_widget = nullptr; } /** @@ -282,9 +279,9 @@ void Desktop::on_mouse_up_event(uint8_t button) { */ void Desktop::on_key_down(KeyCode keyDownCode, KeyboardState keyDownState) { - // Pass the event to the widget that is in focus - if (m_focussed_widget != nullptr) - m_focussed_widget->on_key_down(keyDownCode, keyDownState); + // Pass the event to the widget that is in focus + if (m_focussed_widget != nullptr) + m_focussed_widget->on_key_down(keyDownCode, keyDownState); } /** @@ -294,7 +291,7 @@ void Desktop::on_key_down(KeyCode keyDownCode, KeyboardState keyDownState) { */ void Desktop::on_key_up(KeyCode keyUpCode, KeyboardState keyUpState) { - // Pass the event to the widget that is in focus - if (m_focussed_widget != nullptr) - m_focussed_widget->on_key_up(keyUpCode, keyUpState); + // Pass the event to the widget that is in focus + if (m_focussed_widget != nullptr) + m_focussed_widget->on_key_up(keyUpCode, keyUpState); } \ No newline at end of file diff --git a/kernel/src/gui/font.cpp b/kernel/src/gui/font.cpp index 2ad54aeb..a9d56b74 100644 --- a/kernel/src/gui/font.cpp +++ b/kernel/src/gui/font.cpp @@ -8,13 +8,12 @@ using namespace MaxOS; using namespace MaxOS::common; using namespace MaxOS::gui; -Font::Font(const uint8_t* font_data) -{ +Font::Font(const uint8_t* font_data) { - // Store the font data - for (int i = 0; i < 2048; ++i) { - m_font8x8[i] = font_data[i]; - } + // Store the font data + for (int i = 0; i < 2048; ++i) { + m_font8x8[i] = font_data[i]; + } } Font::~Font() = default; @@ -27,20 +26,20 @@ Font::~Font() = default; * @param text The text to draw */ void Font::draw_text(int32_t x, int32_t y, common::Colour foreground_colour, - common::Colour background_colour, - common::GraphicsContext *context, string text) { + common::Colour background_colour, + common::GraphicsContext* context, string text) { - // Calculate the rectangle of the text - int32_t top = 0; - int32_t left = 0; - int32_t width = get_text_width(text); - int32_t height = get_text_height(text); + // Calculate the rectangle of the text + int32_t top = 0; + int32_t left = 0; + int32_t width = get_text_width(text); + int32_t height = get_text_height(text); - // Create the rectangle - Rectangle text_area(left, top, width, height); + // Create the rectangle + Rectangle text_area(left, top, width, height); - // Draw the text - draw_text(x, y, foreground_colour, background_colour, context, text, text_area); + // Draw the text + draw_text(x, y, foreground_colour, background_colour, context, text, text_area); } @@ -54,70 +53,69 @@ void Font::draw_text(int32_t x, int32_t y, common::Colour foreground_colour, * @param limitArea The area of the text to draw */ void Font::draw_text(int32_t x, int32_t y, common::Colour foreground_colour, - common::Colour background_colour, - common::GraphicsContext *context, string text, - common::Rectangle limitArea) -{ - // Convert the colours - uint32_t foreground = context->colour_to_int(foreground_colour); - uint32_t background = context->colour_to_int(background_colour); + common::Colour background_colour, + common::GraphicsContext* context, string text, + common::Rectangle limitArea) { - // Ensure the area is within the actual area of the text - if (limitArea.top < 0) { - limitArea.height += limitArea.top; - limitArea.top = 0; - } + // Convert the colours + uint32_t foreground = context->colour_to_int(foreground_colour); + uint32_t background = context->colour_to_int(background_colour); - if (limitArea.left < 0) { - limitArea.width += limitArea.left; - limitArea.left = 0; - } + // Ensure the area is within the actual area of the text + if (limitArea.top < 0) { + limitArea.height += limitArea.top; + limitArea.top = 0; + } - // Clamp the height and width max - if (limitArea.top + limitArea.height > (int)get_text_height(text)) - limitArea.height = get_text_height(text) - limitArea.top; + if (limitArea.left < 0) { + limitArea.width += limitArea.left; + limitArea.left = 0; + } - if (limitArea.left + limitArea.width > (int)get_text_width(text)) - limitArea.width = get_text_width(text) - limitArea.left; + // Clamp the height and width max + if (limitArea.top + limitArea.height > (int) get_text_height(text)) + limitArea.height = get_text_height(text) - limitArea.top; + if (limitArea.left + limitArea.width > (int) get_text_width(text)) + limitArea.width = get_text_width(text) - limitArea.left; - // Calculate limits - int32_t xLimit = limitArea.left + limitArea.width; - int32_t yLimit = limitArea.top + limitArea.height; + // Calculate limits + int32_t xLimit = limitArea.left + limitArea.width; + int32_t yLimit = limitArea.top + limitArea.height; - // Draw the text from top to bottom - for (int yBitMapOffset = limitArea.top; yBitMapOffset putPixel(x + xBitMapOffset, y + yBitMapOffset, foreground); - continue; - } + // Draw the pixel + context->putPixel(x + xBitMapOffset, y + yBitMapOffset, foreground); + continue; + } - // If the y is the bottom then add an underline - if (is_underlined && yBitMapOffset == yLimit - 1) { + // If the y is the bottom then add an underline + if (is_underlined && yBitMapOffset == yLimit - 1) { - // Draw the pixel - context -> putPixel(x + xBitMapOffset, y + yBitMapOffset, foreground); - continue; - } + // Draw the pixel + context->putPixel(x + xBitMapOffset, y + yBitMapOffset, foreground); + continue; + } - //TODO: Bold, Italic when other fonts are working + //TODO: Bold, Italic when other fonts are working - // Get the character - uint8_t character = text[xBitMapOffset/8]; + // Get the character + uint8_t character = text[xBitMapOffset / 8]; - // Check if this pixel is set or not - bool set = m_font8x8[(uint16_t)character * 8 + yBitMapOffset] & (128 >> (xBitMapOffset % 8)); + // Check if this pixel is set or not + bool set = m_font8x8[(uint16_t) character * 8 + yBitMapOffset] & (128 >> (xBitMapOffset % 8)); - // Draw the pixel - context -> putPixel(x + xBitMapOffset, y + yBitMapOffset, set ? foreground : background); + // Draw the pixel + context->putPixel(x + xBitMapOffset, y + yBitMapOffset, set ? foreground : background); - } - } + } + } } /** @@ -128,7 +126,7 @@ void Font::draw_text(int32_t x, int32_t y, common::Colour foreground_colour, */ int32_t Font::get_text_height(string) { - return 8; + return 8; } @@ -139,5 +137,6 @@ int32_t Font::get_text_height(string) { * @return The width of the text */ int32_t Font::get_text_width(string text) { - return text.length()*8; + + return text.length() * 8; } diff --git a/kernel/src/gui/widget.cpp b/kernel/src/gui/widget.cpp index f95d2bef..5c696216 100644 --- a/kernel/src/gui/widget.cpp +++ b/kernel/src/gui/widget.cpp @@ -8,16 +8,12 @@ using namespace MaxOS::gui; using namespace MaxOS::drivers; using namespace MaxOS::drivers::peripherals; -///__DEFAULT WIDGET__ - Widget::Widget() : KeyboardEventHandler() { } - - Widget::Widget(int32_t left, int32_t top, uint32_t width, uint32_t height) : KeyboardEventHandler(), m_position(left, top, width, height) @@ -43,14 +39,14 @@ void Widget::draw(GraphicsContext*, Rectangle&) { */ void Widget::invalidate() { - // Convert the relative coordinates to absolute coordinates - Coordinates coordinates = absolute_coordinates(Coordinates(0, 0)); + // Convert the relative coordinates to absolute coordinates + Coordinates coordinates = absolute_coordinates(Coordinates(0, 0)); - // Create a rectangle with the absolute coordinates and the size of the widget - Rectangle invalidArea = Rectangle(coordinates.first, coordinates.second, m_position.width, m_position.height); + // Create a rectangle with the absolute coordinates and the size of the widget + Rectangle invalidArea = Rectangle(coordinates.first, coordinates.second, m_position.width, m_position.height); - // Invalidate the area - invalidate(invalidArea); + // Invalidate the area + invalidate(invalidArea); } @@ -59,11 +55,11 @@ void Widget::invalidate() { * * @param area The area of the widget to invalidate */ -void Widget::invalidate(Rectangle &area) { +void Widget::invalidate(Rectangle& area) { - // If the widget has a parent, invalidate the area of the parent - if(m_parent != nullptr) - m_parent->invalidate(area); + // If the widget has a parent, invalidate the area of the parent + if (m_parent != nullptr) + m_parent->invalidate(area); } /** @@ -71,21 +67,21 @@ void Widget::invalidate(Rectangle &area) { * * @param child The child to add */ -void Widget::add_child(Widget *child) { +void Widget::add_child(Widget* child) { - // Parent the child to this widget - child ->m_parent = this; + // Parent the child to this widget + child->m_parent = this; } Coordinates Widget::absolute_coordinates(common::Coordinates coordinates) { - // Return the parents absolute coordinates - if(m_parent != nullptr) - return m_parent->absolute_coordinates(Coordinates(coordinates.first + m_position.left, coordinates.second + m_position.top)); + // Return the parents absolute coordinates + if (m_parent != nullptr) + return m_parent->absolute_coordinates( Coordinates(coordinates.first + m_position.left, coordinates.second + m_position.top)); - // If the widget has no m_parent, return the coordinates of the widget - return {coordinates.first + m_position.left, coordinates.second + m_position.top}; + // If the widget has no m_parent, return the coordinates of the widget + return {coordinates.first + m_position.left, coordinates.second + m_position.top}; } @@ -98,8 +94,8 @@ Coordinates Widget::absolute_coordinates(common::Coordinates coordinates) { */ bool Widget::contains_coordinate(uint32_t x, uint32_t y) { - // Check if the coordinates are within the bounds of the widget - return m_position.contains(x,y); + // Check if the coordinates are within the bounds of the widget + return m_position.contains(x, y); } /** @@ -108,7 +104,8 @@ bool Widget::contains_coordinate(uint32_t x, uint32_t y) { * @return The position of the widget */ Rectangle Widget::position() { - return m_position; + + return m_position; } /** @@ -119,15 +116,15 @@ Rectangle Widget::position() { */ void Widget::move(int32_t left, int32_t top) { - // Invalidate the old position - invalidate(); + // Invalidate the old position + invalidate(); - // Set the new position - m_position.left = left; - m_position.top = top; + // Set the new position + m_position.left = left; + m_position.top = top; - // Re draw the widget in the new position - invalidate(); + // Re draw the widget in the new position + invalidate(); } /** @@ -138,33 +135,34 @@ void Widget::move(int32_t left, int32_t top) { */ void Widget::resize(int32_t width, int32_t height) { - // Restrict the width and height to the minimum and maximum values - if(width < (int)m_min_width) width = m_min_width; - if(height < (int)m_min_height) height = m_min_height; - if(width > (int)m_max_width) width = m_max_width; - if(height > (int)m_max_height) height = m_max_height; + // Restrict the width and height to the minimum and maximum values + if (width < (int) m_min_width) width = m_min_width; + if (height < (int) m_min_height) height = m_min_height; + if (width > (int) m_max_width) width = m_max_width; + if (height > (int) m_max_height) height = m_max_height; - // Store the old position, set the new position - Rectangle old_position = m_position; - m_position.width = width; - m_position.height = height; + // Store the old position, set the new position + Rectangle old_position = m_position; + m_position.width = width; + m_position.height = height; - // Find the areas that need to be redrawn by subtracting the old position from the new position, and vice versa - Vector> invalid_areas_old = old_position.subtract(m_position); - Vector> invalid_areas_new = m_position.subtract(old_position); + // Find the areas that need to be redrawn by subtracting the old position from the new position, and vice versa + Vector> invalid_areas_old = old_position.subtract(m_position); + Vector> invalid_areas_new = m_position.subtract(old_position); - // Right and Bottom require to be fully invalidated TODO: Fix this hack - if(m_position.width > old_position.width || m_position.height > old_position.height || old_position.width > m_position.width || old_position.height > m_position.height){ - invalidate(); - return; - } + // Right and Bottom require to be fully invalidated TODO: Fix this hack + if (m_position.width > old_position.width || m_position.height > old_position.height || + old_position.width > m_position.width || old_position.height > m_position.height) { + invalidate(); + return; + } - //Loop through the areas that need to be redrawn and invalidate them - for(auto& area : invalid_areas_old) - invalidate(area); + //Loop through the areas that need to be redrawn and invalidate them + for (auto& area: invalid_areas_old) + invalidate(area); - for(auto& area : invalid_areas_new) - invalidate(area); + for (auto& area: invalid_areas_new) + invalidate(area); } @@ -173,8 +171,8 @@ void Widget::resize(int32_t width, int32_t height) { */ void Widget::focus() { - // Set the focus the widget to this widget - set_focus(this); + // Set the focus the widget to this widget + set_focus(this); } /** @@ -182,11 +180,11 @@ void Widget::focus() { * * @param widget The widget to set as focussed */ -void Widget::set_focus(Widget *widget) { +void Widget::set_focus(Widget* widget) { - // Focus the parent to this widget - if(m_parent != nullptr) - m_parent->set_focus(widget); + // Focus the parent to this widget + if (m_parent != nullptr) + m_parent->set_focus(widget); } /** @@ -208,9 +206,8 @@ void Widget::on_focus_lost() { */ void Widget::bring_to_front() { - // Bring this widget to the front of the screen - bring_to_front(this); - + // Bring this widget to the front of the screen + bring_to_front(this); } /** @@ -218,11 +215,11 @@ void Widget::bring_to_front() { * * @param widget The widget to bring to the front */ -void Widget::bring_to_front(Widget *widget) { +void Widget::bring_to_front(Widget* widget) { - // Bring the parent to the front of the screen - if(m_parent != nullptr) - m_parent->bring_to_front(widget); + // Bring the parent to the front of the screen + if (m_parent != nullptr) + m_parent->bring_to_front(widget); } @@ -268,14 +265,14 @@ void Widget::on_mouse_move_widget(uint32_t, uint32_t, uint32_t, uint32_t) { */ peripherals::MouseEventHandler* Widget::on_mouse_button_pressed(uint32_t, uint32_t, uint8_t) { - // Bring the widget to the front of the screen - bring_to_front(); + // Bring the widget to the front of the screen + bring_to_front(); - // Focus the widget - focus(); + // Focus the widget + focus(); - // Return 0 as the event has been handled - return nullptr; + // Return 0 as the event has been handled + return nullptr; } /** @@ -289,13 +286,10 @@ void Widget::on_mouse_button_released(uint32_t, uint32_t, uint8_t) { } -///__COMPOSITE WIDGET__ - CompositeWidget::CompositeWidget() = default; CompositeWidget::CompositeWidget(int32_t left, int32_t top, uint32_t width, uint32_t height) -: Widget(left, top, width,height) -{ + : Widget(left, top, width, height) { } @@ -307,11 +301,10 @@ CompositeWidget::~CompositeWidget() = default; * @param gc The graphics context to draw to * @param area The area to draw */ -void CompositeWidget::draw(GraphicsContext *gc, Rectangle &area) { - - // Draw the widget with its m_children - draw(gc, area, m_children.begin()); +void CompositeWidget::draw(GraphicsContext* gc, Rectangle& area) { + // Draw the widget with its m_children + draw(gc, area, m_children.begin()); } /** @@ -321,44 +314,44 @@ void CompositeWidget::draw(GraphicsContext *gc, Rectangle &area) { * @param area The area to draw * @param start The child to start drawing from */ -void CompositeWidget::draw(GraphicsContext *gc, Rectangle &area, Vector::iterator start) { +void CompositeWidget::draw(GraphicsContext* gc, Rectangle& area, Vector::iterator start) { - // Draw the widget - Widget::draw(gc, area); + // Draw the widget + Widget::draw(gc, area); - // Get the area of the widget - Rectangle own_area = position(); + // Get the area of the widget + Rectangle own_area = position(); - //Note: has to use iterator as the start is not necessarily the m_first_memory_chunk child - for(Vector::iterator child_widget = start; child_widget != m_children.end(); child_widget++){ + //Note: has to use iterator as the start is not necessarily the m_first_memory_chunk child + for (Vector::iterator child_widget = start; child_widget != m_children.end(); child_widget++) { - Rectangle child_area = (*child_widget)->position(); + Rectangle child_area = (*child_widget)->position(); - // Check if the child is in the area that needs to be redrawn - if(area.intersects(child_area)){ + // Check if the child is in the area that needs to be redrawn + if (area.intersects(child_area)) { - // Get the area that needs to be redrawn - Rectangle rectangle = area.intersection(child_area); + // Get the area that needs to be redrawn + Rectangle rectangle = area.intersection(child_area); - // Translate the area so that it is relative to the child - rectangle.left -= child_area.left; - rectangle.top -= child_area.top; + // Translate the area so that it is relative to the child + rectangle.left -= child_area.left; + rectangle.top -= child_area.top; - // Draw the child - (*child_widget)->draw(gc, rectangle); + // Draw the child + (*child_widget)->draw(gc, rectangle); - // Draw what is left of the area that needs to be redrawn - Vector> rest_draw_area = area.subtract(child_area); - for(auto & rest_area_part : rest_draw_area) - draw(gc, rest_area_part, child_widget + 1); + // Draw what is left of the area that needs to be redrawn + Vector> rest_draw_area = area.subtract(child_area); + for (auto& rest_area_part: rest_draw_area) + draw(gc, rest_area_part, child_widget + 1); - // Return as the entire area has now been drawn - return; - } - } + // Return as the entire area has now been drawn + return; + } + } - // Now draw the widget itself - draw_self(gc, area); + // Now draw the widget itself + draw_self(gc, area); } /** @@ -376,11 +369,11 @@ void CompositeWidget::draw_self(common::GraphicsContext*, common::Rectangle child_area = child_widget->position(); - if(child_area.contains(toX, toY)){ + for (auto& child_widget: m_children) { - // Get the position of the mouse relative to the child - uint32_t child_x = toX - child_area.left; - uint32_t child_y = toY - child_area.top; + // Check if the mouse is in the child + Rectangle child_area = child_widget->position(); + if (child_area.contains(toX, toY)) { - // Call the child's on_mouse_enter_widget function - child_widget->on_mouse_enter_widget(child_x, child_y); + // Get the position of the mouse relative to the child + uint32_t child_x = toX - child_area.left; + uint32_t child_y = toY - child_area.top; - // Break as the event has been handled - break; - } - } + // Call the child's on_mouse_enter_widget function + child_widget->on_mouse_enter_widget(child_x, child_y); + // Break as the event has been handled + break; + } + } } + /** * @brief Passes the event to the child that the mouse is over. (Event handling should be done by the derived class) * @@ -418,23 +411,23 @@ void CompositeWidget::on_mouse_enter_widget(uint32_t toX, uint32_t toY) { */ void CompositeWidget::on_mouse_leave_widget(uint32_t fromX, uint32_t fromY) { - for(auto&child_widget : m_children){ + for (auto& child_widget: m_children) { - // Check if the mouse is in the child - Rectangle child_area = child_widget->position(); - if(child_area.contains(fromX, fromY)){ + // Check if the mouse is in the child + Rectangle child_area = child_widget->position(); + if (child_area.contains(fromX, fromY)) { - // Get the position of the mouse relative to the child - uint32_t child_x = fromX - child_area.left; - uint32_t child_y = fromY - child_area.top; + // Get the position of the mouse relative to the child + uint32_t child_x = fromX - child_area.left; + uint32_t child_y = fromY - child_area.top; - // Call the child's on_mouse_leave_widget function - child_widget->on_mouse_leave_widget(child_x, child_y); + // Call the child's on_mouse_leave_widget function + child_widget->on_mouse_leave_widget(child_x, child_y); - // Event has been handled - break; - } - } + // Event has been handled + break; + } + } } /** @@ -447,42 +440,42 @@ void CompositeWidget::on_mouse_leave_widget(uint32_t fromX, uint32_t fromY) { */ void CompositeWidget::on_mouse_move_widget(uint32_t fromX, uint32_t fromY, uint32_t toX, uint32_t toY) { - Widget* left_child = nullptr; - Widget* entered_child = nullptr; + Widget* left_child = nullptr; + Widget* entered_child = nullptr; - for(auto&child_widget : m_children){ + for (auto& child_widget: m_children) { - // Check if the mouse is in the child - Rectangle child_area = child_widget->position(); - bool mouse_in_from = child_area.contains(fromX, fromY); - bool mouse_in_to = child_area.contains(toX, toY); + // Check if the mouse is in the child + Rectangle child_area = child_widget->position(); + bool mouse_in_from = child_area.contains(fromX, fromY); + bool mouse_in_to = child_area.contains(toX, toY); - // If the mouse started in the child - if(mouse_in_from){ + // If the mouse started in the child + if (mouse_in_from) { - // The mouse moved out of the child - if(!mouse_in_to){ - left_child = child_widget; - continue; - } + // The mouse moved out of the child + if (!mouse_in_to) { + left_child = child_widget; + continue; + } - // Mouse still in the child - child_widget->on_mouse_move_widget(fromX, fromY, toX, toY); + // Mouse still in the child + child_widget->on_mouse_move_widget(fromX, fromY, toX, toY); - }else{ + } else { - // Mouse moved into the child - if(mouse_in_to) - entered_child = child_widget; - } + // Mouse moved into the child + if (mouse_in_to) + entered_child = child_widget; + } - // Pass the events to the child - if(left_child != nullptr) - left_child->on_mouse_leave_widget(fromX, fromY); + // Pass the events to the child + if (left_child != nullptr) + left_child->on_mouse_leave_widget(fromX, fromY); - if(entered_child != nullptr) - entered_child->on_mouse_enter_widget(toX, toY); - } + if (entered_child != nullptr) + entered_child->on_mouse_enter_widget(toX, toY); + } } /** @@ -493,22 +486,21 @@ void CompositeWidget::on_mouse_move_widget(uint32_t fromX, uint32_t fromY, uint3 * @param button The button that was pressed * @return The object that has the mouseEventHandler which handled the event */ -peripherals::MouseEventHandler *CompositeWidget::on_mouse_button_pressed(uint32_t x, uint32_t y, uint8_t button) { - - MouseEventHandler*mouse_event_handler = nullptr; - - for(auto&child_widget : m_children){ +peripherals::MouseEventHandler* CompositeWidget::on_mouse_button_pressed(uint32_t x, uint32_t y, uint8_t button) { - // Pass the event to the child - if(child_widget->contains_coordinate(x, y)){ - mouse_event_handler = child_widget -> on_mouse_button_pressed(x - child_widget->m_position.left, y - child_widget->m_position.top, button); - break; - } + MouseEventHandler* mouse_event_handler = nullptr; - } + for (auto& child_widget: m_children) { - return mouse_event_handler; + // Pass the event to the child + if (child_widget->contains_coordinate(x, y)) { + mouse_event_handler = child_widget->on_mouse_button_pressed(x - child_widget->m_position.left, + y - child_widget->m_position.top, button); + break; + } + } + return mouse_event_handler; } /** @@ -520,13 +512,14 @@ peripherals::MouseEventHandler *CompositeWidget::on_mouse_button_pressed(uint32_ */ void CompositeWidget::on_mouse_button_released(uint32_t x, uint32_t y, uint8_t button) { - // Loop through the m_children - for(auto&child_widget : m_children){ + // Loop through the m_children + for (auto& child_widget: m_children) { - // Pass the event to the child - if(child_widget->contains_coordinate(x, y)){ - child_widget->on_mouse_button_released(x - child_widget->m_position.left, y - child_widget->m_position.top, button); - break; - } - } + // Pass the event to the child + if (child_widget->contains_coordinate(x, y)) { + child_widget->on_mouse_button_released(x - child_widget->m_position.left, y - child_widget->m_position.top, + button); + break; + } + } } diff --git a/kernel/src/gui/widgets/button.cpp b/kernel/src/gui/widgets/button.cpp index 59f97ba8..598f9ce2 100644 --- a/kernel/src/gui/widgets/button.cpp +++ b/kernel/src/gui/widgets/button.cpp @@ -11,8 +11,6 @@ using namespace MaxOS::gui::widgets; using namespace MaxOS::drivers; using namespace MaxOS::drivers::peripherals; -/// ___ Button Event Handler ___ - ButtonEventHandler::ButtonEventHandler() = default; ButtonEventHandler::~ButtonEventHandler() = default; @@ -22,20 +20,20 @@ ButtonEventHandler::~ButtonEventHandler() = default; * * @param event The event to handle */ -Event* ButtonEventHandler::on_event(Event *event) { +Event* ButtonEventHandler::on_event(Event* event) { - switch (event -> type) { + switch (event->type) { - case ButtonEvents::PRESSED: - on_button_pressed(((ButtonPressedEvent *)event)->source); - break; + case ButtonEvents::PRESSED: + on_button_pressed(((ButtonPressedEvent*) event)->source); + break; - case ButtonEvents::RELEASED: - on_button_released(((ButtonReleasedEvent *)event)->source); - break; + case ButtonEvents::RELEASED: + on_button_released(((ButtonReleasedEvent*) event)->source); + break; - } - return event; + } + return event; } /** @@ -56,16 +54,12 @@ void ButtonEventHandler::on_button_released(Button*) { } - - -//// ___ Button ___ - Button::Button(int32_t left, int32_t top, uint32_t width, uint32_t height, const string& text) : Widget(left, top, width, height), background_colour(Colour(0xFF, 0xFF, 0xFF)), foreground_colour(Colour(0x00, 0x00, 0x00)), border_colour(Colour(0x57, 0x57, 0x57)), - font((uint8_t*)AMIGA_FONT), + font((uint8_t*) AMIGA_FONT), text(text) { @@ -79,63 +73,63 @@ Button::~Button() = default; * @param gc The graphics context to draw to * @param area The area to draw to */ -void Button::draw(GraphicsContext *gc, Rectangle &area) { +void Button::draw(GraphicsContext* gc, Rectangle& area) { - // Default Draw Operation - Widget::draw(gc, area); + // Default Draw Operation + Widget::draw(gc, area); - // Get the absolute m_position of the button - Coordinates buttonCoordinates = absolute_coordinates(Coordinates(0, 0)); - Rectangle buttonPosition = position(); + // Get the absolute m_position of the button + Coordinates buttonCoordinates = absolute_coordinates(Coordinates(0, 0)); + Rectangle buttonPosition = position(); - // Get the x and y m_position of the button - int32_t x = buttonCoordinates.first; - int32_t y = buttonCoordinates.second; + // Get the x and y m_position of the button + int32_t x = buttonCoordinates.first; + int32_t y = buttonCoordinates.second; - // Draw the background for the button - gc->fill_rectangle(x + area.left, y + area.top, x + area.left + area.width, - y + area.top + area.height, background_colour); + // Draw the background for the button + gc->fill_rectangle(x + area.left, y + area.top, x + area.left + area.width, + y + area.top + area.height, background_colour); - // Draw the border (TODO: Make a border class?? Window uses it too) + // Draw the border (TODO: Make a border class?? Window uses it too) - // Top Border - if(area.intersects(Rectangle(0,0,buttonPosition.width,1))){ + // Top Border + if (area.intersects(Rectangle(0, 0, buttonPosition.width, 1))) { - // Start in the top left corner of the button and end in the top right corner - gc->draw_line(x + area.left, y, x + area.left + area.width - 1, y, - border_colour); - } + // Start in the top left corner of the button and end in the top right corner + gc->draw_line(x + area.left, y, x + area.left + area.width - 1, y, + border_colour); + } - // Left Border - if(area.intersects(Rectangle(0,0,1,buttonPosition.height))){ + // Left Border + if (area.intersects(Rectangle(0, 0, 1, buttonPosition.height))) { - // Start in the top left corner and end in the bottom left corner - gc->draw_line(x, y + area.top, x, y + area.top + area.height - 1, - border_colour); - } + // Start in the top left corner and end in the bottom left corner + gc->draw_line(x, y + area.top, x, y + area.top + area.height - 1, + border_colour); + } - // Right Border - if(area.intersects(Rectangle(0,buttonPosition.height - 1,buttonPosition.width,1))){ + // Right Border + if (area.intersects(Rectangle(0, buttonPosition.height - 1, buttonPosition.width, 1))) { - // Start in the top right corner and end in the bottom right corner - gc->draw_line(x + area.left, y + buttonPosition.height - 1, - x + area.left + area.width - 1, - y + buttonPosition.height - 1, border_colour); - } + // Start in the top right corner and end in the bottom right corner + gc->draw_line(x + area.left, y + buttonPosition.height - 1, + x + area.left + area.width - 1, + y + buttonPosition.height - 1, border_colour); + } - // Bottom Border - if(area.intersects(Rectangle(buttonPosition.width - 1,0,1,buttonPosition.height))){ + // Bottom Border + if (area.intersects(Rectangle(buttonPosition.width - 1, 0, 1, buttonPosition.height))) { - // Start in the bottom left corner and end in the bottom right corner - gc->draw_line(x + buttonPosition.width - 1, y + area.top, - x + buttonPosition.width - 1, - y + area.top + area.height - 1, border_colour); - } + // Start in the bottom left corner and end in the bottom right corner + gc->draw_line(x + buttonPosition.width - 1, y + area.top, + x + buttonPosition.width - 1, + y + area.top + area.height - 1, border_colour); + } - // Draw the text - common::Rectangle textArea(area.left - 1, area.top - 1, area.width, area.height); - font.draw_text(x + 1, y + 1, foreground_colour, background_colour, gc, text, - textArea); + // Draw the text + common::Rectangle textArea(area.left - 1, area.top - 1, area.width, area.height); + font.draw_text(x + 1, y + 1, foreground_colour, background_colour, gc, text, + textArea); } @@ -149,15 +143,15 @@ void Button::draw(GraphicsContext *gc, Rectangle &area) { */ MouseEventHandler* Button::on_mouse_button_pressed(uint32_t x, uint32_t y, uint8_t button) { - // Raise the event - raise_event(new ButtonPressedEvent(this)); + // Raise the event + raise_event(new ButtonPressedEvent(this)); - // Change the button colour - background_colour = Colour(0x57, 0x57, 0x57); - Widget::invalidate(); + // Change the button colour + background_colour = Colour(0x57, 0x57, 0x57); + Widget::invalidate(); - // Pass the event on (that it was handled) - return Widget::on_mouse_button_pressed(x, y, button); + // Pass the event on (that it was handled) + return Widget::on_mouse_button_pressed(x, y, button); } /** @@ -169,20 +163,20 @@ MouseEventHandler* Button::on_mouse_button_pressed(uint32_t x, uint32_t y, uint8 */ void Button::on_mouse_button_released(uint32_t x, uint32_t y, uint8_t button) { - // Raise the button released event - raise_event(new ButtonReleasedEvent(this)); + // Raise the button released event + raise_event(new ButtonReleasedEvent(this)); - // Change the button colour - background_colour = Colour(0xFF, 0xFF, 0xFF); - Widget::invalidate(); + // Change the button colour + background_colour = Colour(0xFF, 0xFF, 0xFF); + Widget::invalidate(); - // Pass the event on (that it was handled) - Widget::on_mouse_button_released(x, y, button); + // Pass the event on (that it was handled) + Widget::on_mouse_button_released(x, y, button); } /// ___ Event ___ -ButtonReleasedEvent::ButtonReleasedEvent(Button *source) +ButtonReleasedEvent::ButtonReleasedEvent(Button* source) : Event(ButtonEvents::RELEASED), source(source) { @@ -191,7 +185,7 @@ ButtonReleasedEvent::ButtonReleasedEvent(Button *source) ButtonReleasedEvent::~ButtonReleasedEvent() = default; -ButtonPressedEvent::ButtonPressedEvent(Button *source) +ButtonPressedEvent::ButtonPressedEvent(Button* source) : Event(ButtonEvents::PRESSED), source(source) { diff --git a/kernel/src/gui/widgets/inputbox.cpp b/kernel/src/gui/widgets/inputbox.cpp index 8e0b6dbb..de2e4473 100644 --- a/kernel/src/gui/widgets/inputbox.cpp +++ b/kernel/src/gui/widgets/inputbox.cpp @@ -11,34 +11,40 @@ using namespace MaxOS::gui; using namespace MaxOS::gui::widgets; using namespace MaxOS::drivers::peripherals; -/// ___ Event Handlers ___ /// - InputBoxEventHandler::InputBoxEventHandler() = default; InputBoxEventHandler::~InputBoxEventHandler() = default; -Event* InputBoxEventHandler::on_event(Event *event) { - switch (event->type) { - case InputBoxEvents::TEXT_CHANGED: - on_input_box_text_changed(((InputBoxTextChangedEvent *)event)->new_text); - break; - } - - return event; +/** + * @brief Delegates an event to the event handler for that event type + * + * @param event The event triggered + * @return The event triggered potentially modified by the handler. + */ +Event* InputBoxEventHandler::on_event(Event* event) { + + switch (event->type) { + case InputBoxEvents::TEXT_CHANGED: + on_input_box_text_changed(((InputBoxTextChangedEvent*) event)->new_text); + break; + } + + return event; } +/** + * @brief Event triggered when the text in the input box is changed + */ void InputBoxEventHandler::on_input_box_text_changed(string) { } -/// ___ InputBox ___ /// - InputBox::InputBox(int32_t left, int32_t top, uint32_t width, uint32_t height) : Widget(left, top, width, height), background_colour(Colour(0xFF, 0xFF, 0xFF)), foreground_colour(Colour(0x00, 0x00, 0x00)), border_colour(Colour(0x57, 0x57, 0x57)), - font((uint8_t*)AMIGA_FONT) + font((uint8_t*) AMIGA_FONT) { } @@ -48,197 +54,217 @@ InputBox::InputBox(int32_t left, int32_t top, uint32_t width, uint32_t height, c background_colour(Colour(0xFF, 0xFF, 0xFF)), foreground_colour(Colour(0x00, 0x00, 0x00)), border_colour(Colour(0x57, 0x57, 0x57)), - font((uint8_t*)AMIGA_FONT) + font((uint8_t*) AMIGA_FONT) { - // Update the text - update_text(text); + // Update the text + update_text(text); } InputBox::~InputBox() = default; -void InputBox::draw(GraphicsContext *gc, Rectangle &area) { +/** + * @brief Draw an area of the input box onto a graphics context + * + * @param gc The context to draw onto + * @param area What part of the input box to draw + */ +void InputBox::draw(GraphicsContext* gc, Rectangle& area) { - // Default Draw - Widget::draw(gc, area); + // Default Draw + Widget::draw(gc, area); - // Get the absolute m_position of the input box - Coordinates inputBoxCoordinates = absolute_coordinates(Coordinates(0, 0)); - Rectangle inputBoxPosition = position(); + // Get the absolute m_position of the input box + Coordinates inputBoxCoordinates = absolute_coordinates(Coordinates(0, 0)); + Rectangle inputBoxPosition = position(); - // Get the x and y m_position of the input box - int32_t x = inputBoxCoordinates.first; - int32_t y = inputBoxCoordinates.second; + // Get the x and y m_position of the input box + int32_t x = inputBoxCoordinates.first; + int32_t y = inputBoxCoordinates.second; - // Draw the background for the input box - gc->fill_rectangle(x + area.left, y + area.top, x + area.left + area.width, - y + area.top + area.height, background_colour); + // Draw the background for the input box + gc->fill_rectangle(x + area.left, y + area.top, x + area.left + area.width, + y + area.top + area.height, background_colour); - // Draw the border (TODO: Make this a function because it is used in multiple places) + // Draw the border (TODO: Make this a function because it is used in multiple places) - // Top Border - if(area.intersects(Rectangle(0,0,inputBoxPosition.width,1))){ + // Top Border + if (area.intersects(Rectangle(0, 0, inputBoxPosition.width, 1))) { - // Start in the top left corner of the button and end in the top right corner - gc->draw_line(x + area.left, y, x + area.left + area.width - 1, y, - border_colour); - } + // Start in the top left corner of the button and end in the top right corner + gc->draw_line(x + area.left, y, x + area.left + area.width - 1, y, + border_colour); + } - // Left Border - if(area.intersects(Rectangle(0,0,1,inputBoxPosition.height))){ + // Left Border + if (area.intersects(Rectangle(0, 0, 1, inputBoxPosition.height))) { - // Start in the top left corner and end in the bottom left corner - gc->draw_line(x, y + area.top, x, y + area.top + area.height - 1, - border_colour); - } + // Start in the top left corner and end in the bottom left corner + gc->draw_line(x, y + area.top, x, y + area.top + area.height - 1, + border_colour); + } - // Right Border - if(area.intersects(Rectangle(0,inputBoxPosition.height - 1,inputBoxPosition.width,1))){ + // Right Border + if (area.intersects(Rectangle(0, inputBoxPosition.height - 1, inputBoxPosition.width, 1))) { - // Start in the top right corner and end in the bottom right corner - gc->draw_line(x + area.left, y + inputBoxPosition.height - 1, - x + area.left + area.width - 1, - y + inputBoxPosition.height - 1, border_colour); - } + // Start in the top right corner and end in the bottom right corner + gc->draw_line(x + area.left, y + inputBoxPosition.height - 1, + x + area.left + area.width - 1, + y + inputBoxPosition.height - 1, border_colour); + } - // Bottom Border - if(area.intersects(Rectangle(inputBoxPosition.width - 1,0,1,inputBoxPosition.height))){ + // Bottom Border + if (area.intersects(Rectangle(inputBoxPosition.width - 1, 0, 1, inputBoxPosition.height))) { - // Start in the bottom left corner and end in the bottom right corner - gc->draw_line(x + inputBoxPosition.width - 1, y + area.top, - x + inputBoxPosition.width - 1, - y + area.top + area.height - 1, border_colour); - } + // Start in the bottom left corner and end in the bottom right corner + gc->draw_line(x + inputBoxPosition.width - 1, y + area.top, + x + inputBoxPosition.width - 1, + y + area.top + area.height - 1, border_colour); + } - // Draw the text - common::Rectangle textArea(area.left - 1, area.top - 1, area.width, area.height); - font.draw_text(x + 1, y + 1, foreground_colour, background_colour, gc, m_widget_text, textArea); + // Draw the text + common::Rectangle textArea(area.left - 1, area.top - 1, area.width, area.height); + font.draw_text(x + 1, y + 1, foreground_colour, background_colour, gc, m_widget_text, textArea); } +/** + * @brief Update the border when the input box is focussed + */ void InputBox::on_focus() { - // Make the border black on focus - border_colour = Colour(0x00, 0x00, 0x00); - invalidate(); + // Make the border black on focus + border_colour = Colour(0x00, 0x00, 0x00); + invalidate(); } +/** + * @brief Reset to the original input box style when it is no longer focussed + */ void InputBox::on_focus_lost() { - // Reset the border colour - border_colour = Colour(0x57, 0x57, 0x57); - invalidate(); + // Reset the border colour + border_colour = Colour(0x57, 0x57, 0x57); + invalidate(); } +/** + * @brief Handles a keypress event by updating the rendered text in the input box + * + * @param keyDownCode The key being pressed + */ void InputBox::on_key_down(KeyCode keyDownCode, KeyboardState) { - // Handle the key press - switch(keyDownCode) - { - case KeyCode::backspace: - { - if(cursor_position == 0) - break; - - cursor_position--; - // no break - we move the cursor to the left and use the code - [[fallthrough]]; - } - case KeyCode::deleteKey: - { - // Move the text to the left - for (int i = cursor_position; i < m_widget_text.length(); ++i) - m_widget_text[i] = m_widget_text[i+1]; - - // Put a null character at the end of the string - m_widget_text[m_widget_text.length() - 1] = '\0'; - - break; - } - case KeyCode::leftArrow: - { - // If the cursor is not at the beginning of the text, move it to the left - if(cursor_position > 0) - cursor_position--; - break; - } - case KeyCode::rightArrow: - { - - // If the cursor is not at the end of the text, move it to the right - if(m_widget_text[cursor_position] != '\0') - cursor_position++; - break; - } - default: - { - - // If the key is a printable character, add it to the text - if(31 < (int)keyDownCode && (int)keyDownCode < 127) - { - uint32_t length = cursor_position; - - // Find the length of the text buffer - while (m_widget_text[length] != '\0') { - ++length; - } - - // Check if we need to make space for the new character - if (length >= (uint32_t)m_widget_text.length()) { - m_widget_text += " "; - } - - // Shift elements to the right - while (length > cursor_position) { - m_widget_text[length + 1] = m_widget_text[length]; - --length; - } - - // Insert the new character - m_widget_text[cursor_position + 1] = m_widget_text[cursor_position]; - m_widget_text[cursor_position] = (uint8_t)keyDownCode; - cursor_position++; - }else{ - - // Don't want to redraw the widget if nothing has changed - return; - } - break; - } - } - - // Redraw the widget - invalidate(); - - // Fire the text changed event - if(keyDownCode != KeyCode::leftArrow && keyDownCode != KeyCode::rightArrow) - raise_event(new InputBoxTextChangedEvent(m_widget_text)); + // Handle the key press + switch (keyDownCode) { + case KeyCode::backspace: { + if (cursor_position == 0) + break; + + cursor_position--; + // no break - we move the cursor to the left and use the code + [[fallthrough]]; + } + case KeyCode::deleteKey: { + // Move the text to the left + for (int i = cursor_position; i < m_widget_text.length(); ++i) + m_widget_text[i] = m_widget_text[i + 1]; + + // Put a null character at the end of the string + m_widget_text[m_widget_text.length() - 1] = '\0'; + + break; + } + case KeyCode::leftArrow: { + // If the cursor is not at the beginning of the text, move it to the left + if (cursor_position > 0) + cursor_position--; + break; + } + case KeyCode::rightArrow: { + + // If the cursor is not at the end of the text, move it to the right + if (m_widget_text[cursor_position] != '\0') + cursor_position++; + break; + } + default: { + + // If the key is a printable character, add it to the text + if (31 < (int) keyDownCode && (int) keyDownCode < 127) { + uint32_t length = cursor_position; + + // Find the length of the text buffer + while (m_widget_text[length] != '\0') { + ++length; + } + + // Check if we need to make space for the new character + if (length >= (uint32_t) m_widget_text.length()) { + m_widget_text += " "; + } + + // Shift elements to the right + while (length > cursor_position) { + m_widget_text[length + 1] = m_widget_text[length]; + --length; + } + + // Insert the new character + m_widget_text[cursor_position + 1] = m_widget_text[cursor_position]; + m_widget_text[cursor_position] = (uint8_t) keyDownCode; + cursor_position++; + } else { + + // Don't want to redraw the widget if nothing has changed + return; + } + break; + } + } + + // Redraw the widget + invalidate(); + + // Fire the text changed event + if (keyDownCode != KeyCode::leftArrow && keyDownCode != KeyCode::rightArrow) + raise_event(new InputBoxTextChangedEvent(m_widget_text)); } +/** + * @brief Update the text in the input box + * + * @param new_text The new text to display + */ void InputBox::update_text(const string& new_text) { - m_widget_text.copy(new_text); - cursor_position = m_widget_text.length(); + m_widget_text.copy(new_text); + cursor_position = m_widget_text.length(); - // Redraw the widget - invalidate(); - - // Fire the text changed event - raise_event(new InputBoxTextChangedEvent(new_text)); + // Redraw the widget + invalidate(); + // Fire the text changed event + raise_event(new InputBoxTextChangedEvent(new_text)); } +/** + * @brief Get the text currently in the input box + * + * @return The text in the input box + */ string InputBox::text() { - return m_widget_text; + + return m_widget_text; } -/// ___ Events ___ /// InputBoxTextChangedEvent::InputBoxTextChangedEvent(const string& new_text) : Event(InputBoxEvents::TEXT_CHANGED), new_text(new_text) { + } InputBoxTextChangedEvent::~InputBoxTextChangedEvent() = default; \ No newline at end of file diff --git a/kernel/src/gui/widgets/text.cpp b/kernel/src/gui/widgets/text.cpp index 7614d0a3..f090f198 100644 --- a/kernel/src/gui/widgets/text.cpp +++ b/kernel/src/gui/widgets/text.cpp @@ -12,12 +12,13 @@ using namespace MaxOS::gui::widgets; Text::Text(int32_t left, int32_t top, uint32_t width, uint32_t height, const string& text) : Widget(left, top, width, height), - font((uint8_t*)AMIGA_FONT), - foreground_colour(Colour(0,0,0)), - background_colour(Colour(255,255,255)) + font((uint8_t*) AMIGA_FONT), + foreground_colour(Colour(0, 0, 0)), + background_colour(Colour(255, 255, 255)) { - // Set the text - update_text(text); + + // Set the text + update_text(text); } Text::~Text() = default; @@ -28,25 +29,24 @@ Text::~Text() = default; * @param gc The graphics context to draw on * @param area The area of the text to draw */ -void Text::draw(GraphicsContext *gc, Rectangle& area) { - - // Default Draw Operation - Widget::draw(gc, area); +void Text::draw(GraphicsContext* gc, Rectangle& area) { - // Get the absolute m_position of the text - Coordinates textCoordinates = absolute_coordinates(Coordinates(0, 0)); - Rectangle textPosition = position(); + // Default Draw Operation + Widget::draw(gc, area); - // Get the x and y m_position of the text - int32_t x = textCoordinates.first; - int32_t y = textCoordinates.second; + // Get the absolute m_position of the text + Coordinates textCoordinates = absolute_coordinates(Coordinates(0, 0)); + Rectangle textPosition = position(); - // Draw the background (as the text might not fill the entire area) - gc->fill_rectangle(x + area.left, y + area.top, x + area.left + area.width, y + area.top + area.height, background_colour); + // Get the x and y m_position of the text + int32_t x = textCoordinates.first; + int32_t y = textCoordinates.second; - // Draw the text - this->font.draw_text(x, y, foreground_colour, background_colour, gc, m_widget_text, area); + // Draw the background (as the text might not fill the entire area) + gc->fill_rectangle(x + area.left, y + area.top, x + area.left + area.width, y + area.top + area.height, background_colour); + // Draw the text + this->font.draw_text(x, y, foreground_colour, background_colour, gc, m_widget_text, area); } /** @@ -55,12 +55,8 @@ void Text::draw(GraphicsContext *gc, Rectangle& area) { */ void Text::update_text(const string& new_text) { - - // Set the text - m_widget_text.copy(new_text); - - // New text has been set so invalidate the widget - invalidate(); + // Set the text + m_widget_text.copy(new_text); + invalidate(); } - diff --git a/kernel/src/gui/window.cpp b/kernel/src/gui/window.cpp index b0496e89..8e5581f6 100644 --- a/kernel/src/gui/window.cpp +++ b/kernel/src/gui/window.cpp @@ -24,25 +24,26 @@ Window::Window(int32_t left, int32_t top, uint32_t width, uint32_t height, const m_resizer_bottom_right(this) { - // Set the sizing - m_min_width = 2 * frame_thickness; - m_min_height = 2 * frame_thickness + title_bar_height; + // Set the sizing + m_min_width = 2 * frame_thickness; + m_min_height = 2 * frame_thickness + title_bar_height; - // Set the colours - area_colour = Colour(0xff, 0xff, 0xff); // White - frame_border_colour = Colour(0x00, 0x00, 0x00); // Black - frame_colour = Colour(0x57,0x57,0x57); // Davy's Grey - m_title.foreground_colour = Colour(0xff, 0xff, 0xff); // White - m_title.background_colour = frame_colour; + // Set the colours + area_colour = Colour(0xff, 0xff, 0xff); // White + frame_border_colour = Colour(0x00, 0x00, 0x00); // Black + frame_colour = Colour(0x57, 0x57, 0x57); // Davy's Grey + m_title.foreground_colour = Colour(0xff, 0xff, 0xff); // White + m_title.background_colour = frame_colour; - // Add the title to the window - Window::add_child(&m_title); + // Add the title to the window + Window::add_child(&m_title); } -Window::Window(Widget *containedWidget, const string& title_text) -: CompositeWidget(0, 0, containedWidget->position().width + 2 * 5 + 2, containedWidget->position().height + 2 * 5 + 10 + 2), - m_title(0, -(10 + 5) + 2, containedWidget->position().width, 10 + 5 - 3,title_text), +Window::Window(Widget* containedWidget, const string& title_text) +: CompositeWidget(0, 0, containedWidget->position().width + 2 * 5 + 2, + containedWidget->position().height + 2 * 5 + 10 + 2), + m_title(0, -(10 + 5) + 2, containedWidget->position().width, 10 + 5 - 3, title_text), m_mover(this), m_resizer_top(this), m_resizer_bottom(this), @@ -53,21 +54,21 @@ Window::Window(Widget *containedWidget, const string& title_text) m_resizer_bottom_left(this), m_resizer_bottom_right(this) { - // Set the sizing - m_min_width = 2 * frame_thickness; - m_min_height = 2 * frame_thickness + title_bar_height; - // Set the colours - area_colour = Colour(0xff, 0xff, 0xff); // White - frame_border_colour = Colour(0x00, 0x00, 0x00); // Black - frame_colour = Colour(0x57,0x57,0x57); // Davy's Grey - m_title.foreground_colour = Colour(0xff, 0xff, 0xff); // White - m_title.background_colour = frame_colour; + // Set the sizing + m_min_width = 2 * frame_thickness; + m_min_height = 2 * frame_thickness + title_bar_height; - // Add the m_title to the window - Window::add_child(&m_title); - Window::add_child(containedWidget); + // Set the colours + area_colour = Colour(0xff, 0xff, 0xff); // White + frame_border_colour = Colour(0x00, 0x00, 0x00); // Black + frame_colour = Colour(0x57, 0x57, 0x57); // Davy's Grey + m_title.foreground_colour = Colour(0xff, 0xff, 0xff); // White + m_title.background_colour = frame_colour; + // Add the title to the window + Window::add_child(&m_title); + Window::add_child(containedWidget); } Window::~Window() = default; @@ -79,55 +80,49 @@ Window::~Window() = default; * @param y The y coordinate of the mouse. * @param button The button that is pressed. */ -MouseEventHandler* Window::on_mouse_button_pressed(uint32_t mouseX, uint32_t mouseY, uint8_t button){ +MouseEventHandler* Window::on_mouse_button_pressed(uint32_t mouseX, uint32_t mouseY, uint8_t button) { - // Pass the mouse event to the children - drivers::peripherals::MouseEventHandler* child_result = CompositeWidget::on_mouse_button_pressed(mouseX, mouseY, button); - Rectangle window_position = position(); + // Pass the mouse event to the children + drivers::peripherals::MouseEventHandler* child_result = CompositeWidget::on_mouse_button_pressed(mouseX, mouseY, button); + Rectangle window_position = position(); - // Bring the window to the front - bring_to_front(); + // Bring the window to the front + bring_to_front(); - // Convert the mouse coordinates to an int32_t - auto x = (int32_t) mouseX; - auto y = (int32_t) mouseY; + // Convert the mouse coordinates to an int32_t + auto x = (int32_t) mouseX; + auto y = (int32_t) mouseY; - if(x <= frame_thickness) - { - if(y <= frame_thickness) - return &m_resizer_top_left; + if (x <= frame_thickness) { + if (y <= frame_thickness) + return &m_resizer_top_left; - else if(y < window_position.height - frame_thickness) - return &m_resizer_left; + else if (y < window_position.height - frame_thickness) + return &m_resizer_left; - else - return &m_resizer_bottom_left; - } - else if(x < window_position.width - frame_thickness) - { - if(y <= frame_thickness) - return &m_resizer_top; + else + return &m_resizer_bottom_left; + } else if (x < window_position.width - frame_thickness) { + if (y <= frame_thickness) + return &m_resizer_top; - else if(y < frame_thickness + title_bar_height) - return &m_mover; + else if (y < frame_thickness + title_bar_height) + return &m_mover; - else if(y >= window_position.height - frame_thickness) - return &m_resizer_bottom; - } - else - { - if(y <= frame_thickness) - return &m_resizer_top_right; + else if (y >= window_position.height - frame_thickness) + return &m_resizer_bottom; + } else { + if (y <= frame_thickness) + return &m_resizer_top_right; - else if(y < window_position.height-frame_thickness) - return &m_resizer_right; + else if (y < window_position.height - frame_thickness) + return &m_resizer_right; - else - return &m_resizer_bottom_right; - } - - return child_result; + else + return &m_resizer_bottom_right; + } + return child_result; } /** @@ -135,50 +130,65 @@ MouseEventHandler* Window::on_mouse_button_pressed(uint32_t mouseX, uint32_t mou * * @param gc The graphics context to draw on. */ -void Window::draw_self(common::GraphicsContext* gc, common::Rectangle& area){ - - // Get the positioning of the window - Coordinates window_absolute_position = CompositeWidget::absolute_coordinates(Coordinates(0, 0)); - Rectangle windowPosition = this->position(); - int32_t window_x = window_absolute_position.first; - int32_t window_y = window_absolute_position.second; - - // Create an area for the window contents - Rectangle window_contents_area( frame_thickness, frame_thickness + title_bar_height, windowPosition.width - 2 * frame_thickness, windowPosition.height - 2 * frame_thickness - title_bar_height); - - // Draw the window contents if they are in the area to draw - if(window_contents_area.intersects(area)){ - Rectangle contents_drawable = window_contents_area.intersection(area); - gc->fill_rectangle(contents_drawable.left + window_x, contents_drawable.top + window_y, contents_drawable.left + contents_drawable.width + window_x, contents_drawable.top + contents_drawable.height + window_y,area_colour); - } - - // Draw the frame if it is in the area to draw - Rectangle window_frame_top_area(frame_thickness, 0, windowPosition.width - 2 * frame_thickness,frame_thickness + title_bar_height); - if(window_frame_top_area.intersects(area)){ - Rectangle frame_drawable = window_frame_top_area.intersection(area); - gc->fill_rectangle(frame_drawable.left + window_x, frame_drawable.top + window_y, frame_drawable.left + frame_drawable.width + window_x, frame_drawable.top + frame_drawable.height + window_y, frame_colour); - } - - // Draw the bottom of the window frame - Rectangle window_frame_bottom_area(frame_thickness, windowPosition.height - frame_thickness, windowPosition.width - 2* frame_thickness, frame_thickness); - if(window_frame_bottom_area.intersects(area)){ - Rectangle bottom_drawable = window_frame_bottom_area.intersection(area); - gc->fill_rectangle(window_x + bottom_drawable.left, window_y + bottom_drawable.top, window_x + bottom_drawable.left + bottom_drawable.width, window_y + bottom_drawable.top + bottom_drawable.height, frame_colour); - } - - // Draw the left of the window frame - Rectangle window_frame_left_area(0,0, frame_thickness, windowPosition.height); - if(window_frame_left_area.intersects(area)){ - Rectangle left_drawable = window_frame_left_area.intersection(area); - gc->fill_rectangle(window_x + left_drawable.left, window_y + left_drawable.top, window_x + left_drawable.left + left_drawable.width, window_y + left_drawable.top + left_drawable.height,frame_colour); - } - - // Draw the right of the window frame - Rectangle window_frame_right_area(windowPosition.width - frame_thickness, 0, frame_thickness, windowPosition.height); - if(window_frame_right_area.intersects(area)){ - Rectangle right_drawable = window_frame_right_area.intersection(area); - gc->fill_rectangle(window_x + right_drawable.left, window_y + right_drawable.top, window_x + right_drawable.left + right_drawable.width, window_y + right_drawable.top + right_drawable.height, frame_colour); - } +void Window::draw_self(common::GraphicsContext* gc, common::Rectangle& area) { + + // Get the positioning of the window + Coordinates window_absolute_position = CompositeWidget::absolute_coordinates(Coordinates(0, 0)); + Rectangle windowPosition = this->position(); + int32_t window_x = window_absolute_position.first; + int32_t window_y = window_absolute_position.second; + + // Create an area for the window contents + Rectangle window_contents_area(frame_thickness, frame_thickness + title_bar_height, + windowPosition.width - 2 * frame_thickness, + windowPosition.height - 2 * frame_thickness - title_bar_height); + + // Draw the window contents if they are in the area to draw + if (window_contents_area.intersects(area)) { + Rectangle contents_drawable = window_contents_area.intersection(area); + gc->fill_rectangle(contents_drawable.left + window_x, contents_drawable.top + window_y, + contents_drawable.left + contents_drawable.width + window_x, + contents_drawable.top + contents_drawable.height + window_y, area_colour); + } + + // Draw the frame if it is in the area to draw + Rectangle window_frame_top_area(frame_thickness, 0, windowPosition.width - 2 * frame_thickness, + frame_thickness + title_bar_height); + if (window_frame_top_area.intersects(area)) { + Rectangle frame_drawable = window_frame_top_area.intersection(area); + gc->fill_rectangle(frame_drawable.left + window_x, frame_drawable.top + window_y, + frame_drawable.left + frame_drawable.width + window_x, + frame_drawable.top + frame_drawable.height + window_y, frame_colour); + } + + // Draw the bottom of the window frame + Rectangle window_frame_bottom_area(frame_thickness, windowPosition.height - frame_thickness, + windowPosition.width - 2 * frame_thickness, frame_thickness); + if (window_frame_bottom_area.intersects(area)) { + Rectangle bottom_drawable = window_frame_bottom_area.intersection(area); + gc->fill_rectangle(window_x + bottom_drawable.left, window_y + bottom_drawable.top, + window_x + bottom_drawable.left + bottom_drawable.width, + window_y + bottom_drawable.top + bottom_drawable.height, frame_colour); + } + + // Draw the left of the window frame + Rectangle window_frame_left_area(0, 0, frame_thickness, windowPosition.height); + if (window_frame_left_area.intersects(area)) { + Rectangle left_drawable = window_frame_left_area.intersection(area); + gc->fill_rectangle(window_x + left_drawable.left, window_y + left_drawable.top, + window_x + left_drawable.left + left_drawable.width, + window_y + left_drawable.top + left_drawable.height, frame_colour); + } + + // Draw the right of the window frame + Rectangle window_frame_right_area(windowPosition.width - frame_thickness, 0, frame_thickness, + windowPosition.height); + if (window_frame_right_area.intersects(area)) { + Rectangle right_drawable = window_frame_right_area.intersection(area); + gc->fill_rectangle(window_x + right_drawable.left, window_y + right_drawable.top, + window_x + right_drawable.left + right_drawable.width, + window_y + right_drawable.top + right_drawable.height, frame_colour); + } } /** @@ -186,18 +196,18 @@ void Window::draw_self(common::GraphicsContext* gc, common::Rectangle& * * @param child The child to add. */ +void Window::add_child(Widget* child) { -void Window::add_child(Widget *child) { - - // If there is a child to add - if(child != nullptr){ + // If there is a child to add + if (child != nullptr) { - // Change the position of the child to be inside the window contents - Rectangle childPosition = child->position(); - child -> move(childPosition.left + frame_thickness + 1, childPosition.top + frame_thickness + title_bar_height + 1); + // Change the position of the child to be inside the window contents + Rectangle childPosition = child->position(); + child->move(childPosition.left + frame_thickness + 1, + childPosition.top + frame_thickness + title_bar_height + 1); - } + } - // Add the child to the window - CompositeWidget::add_child(child); + // Add the child to the window + CompositeWidget::add_child(child); } \ No newline at end of file diff --git a/kernel/src/hardwarecommunication/acpi.cpp b/kernel/src/hardwarecommunication/acpi.cpp index e976431f..b8af2c47 100644 --- a/kernel/src/hardwarecommunication/acpi.cpp +++ b/kernel/src/hardwarecommunication/acpi.cpp @@ -9,53 +9,52 @@ using namespace MaxOS::hardwarecommunication; using namespace MaxOS::system; using namespace MaxOS::memory; using namespace MaxOS::common; -AdvancedConfigurationAndPowerInterface::AdvancedConfigurationAndPowerInterface(system::Multiboot* multiboot) { - Logger::INFO() << "Setting up ACPI\n"; +AdvancedConfigurationAndPowerInterface::AdvancedConfigurationAndPowerInterface(system::Multiboot* multiboot) { - // If the new ACPI is not supported, panic - ASSERT(multiboot->new_acpi() != nullptr || multiboot->old_acpi() != nullptr, "No ACPI found!"); + Logger::INFO() << "Setting up ACPI\n"; - // Check if the new ACPI is supported - m_using_new_acpi = multiboot->old_acpi() == nullptr; - Logger::DEBUG() << "CPU Supports " << (m_using_new_acpi ? "New" : "Old") << " ACPI\n"; + // If the new ACPI is not supported, panic + ASSERT(multiboot->new_acpi() != nullptr || multiboot->old_acpi() != nullptr, "No ACPI found!"); + // Check if the new ACPI is supported + m_using_new_acpi = multiboot->old_acpi() == nullptr; + Logger::DEBUG() << "CPU Supports " << (m_using_new_acpi ? "New" : "Old") << " ACPI\n"; - if(m_using_new_acpi){ + if (m_using_new_acpi) { - // Get the RSDP & XSDT - m_rsdp2 = (RSDPDescriptor2*)(multiboot->new_acpi() + 1); - m_xsdt = (XSDT*) PhysicalMemoryManager::to_higher_region((uint64_t)m_rsdp2->xsdt_address); - }else{ + // Get the RSDP & XSDT + m_rsdp2 = (RSDPDescriptor2*) (multiboot->new_acpi() + 1); + m_xsdt = (XSDT*) PhysicalMemoryManager::to_higher_region((uint64_t) m_rsdp2->xsdt_address); + } else { - // Get the RSDP & RSDT - m_rsdp = (RSDPDescriptor*)(multiboot->old_acpi() + 1); - m_rsdt = (RSDT*) PhysicalMemoryManager::to_higher_region((uint64_t)m_rsdp->rsdt_address); - } + // Get the RSDP & RSDT + m_rsdp = (RSDPDescriptor*) (multiboot->old_acpi() + 1); + m_rsdt = (RSDT*) PhysicalMemoryManager::to_higher_region((uint64_t) m_rsdp->rsdt_address); + } - // Map the XSDT/RSDT - uint64_t physical_address = m_using_new_acpi ? m_rsdp2->xsdt_address : m_rsdp->rsdt_address; - auto virtual_address = (uint64_t)PhysicalMemoryManager::to_higher_region(physical_address); - PhysicalMemoryManager::s_current_manager->map((physical_address_t*)PhysicalMemoryManager::align_direct_to_page(physical_address), (virtual_address_t*)virtual_address, Present | Write); - Logger::DEBUG() << "XSDT/RSDT: physical: 0x" << physical_address << ", virtual: 0x" << virtual_address << "\n"; + // Map the XSDT/RSDT + uint64_t physical_address = m_using_new_acpi ? m_rsdp2->xsdt_address : m_rsdp->rsdt_address; + auto virtual_address = (uint64_t) PhysicalMemoryManager::to_higher_region(physical_address); + PhysicalMemoryManager::s_current_manager->map((physical_address_t*) PhysicalMemoryManager::align_direct_to_page(physical_address), (virtual_address_t*) virtual_address, Present | Write); + Logger::DEBUG() << "XSDT/RSDT: physical: 0x" << physical_address << ", virtual: 0x" << virtual_address << "\n"; - // Reserve the XSDT/RSDT - PhysicalMemoryManager::s_current_manager->reserve(m_using_new_acpi ? (uint64_t)m_rsdp2->xsdt_address : (uint64_t)m_rsdp->rsdt_address); + // Reserve the XSDT/RSDT + PhysicalMemoryManager::s_current_manager->reserve( m_using_new_acpi ? (uint64_t) m_rsdp2->xsdt_address : (uint64_t) m_rsdp->rsdt_address); - // Load the header - m_header = m_using_new_acpi ? &m_xsdt->header : &m_rsdt->header; + // Load the header + m_header = m_using_new_acpi ? &m_xsdt->header : &m_rsdt->header; - // Map the Tables - Logger::DEBUG() << "Mapping ACPI Tables\n"; - map_tables(m_using_new_acpi ? sizeof (uint64_t) : sizeof (uint32_t)); + // Map the Tables + Logger::DEBUG() << "Mapping ACPI Tables\n"; + map_tables(m_using_new_acpi ? sizeof(uint64_t) : sizeof(uint32_t)); - // Check if the checksum is valid - ASSERT(valid_checksum(), "ACPI: Invalid checksum!"); + // Check if the checksum is valid + ASSERT(valid_checksum(), "ACPI: Invalid checksum!"); } AdvancedConfigurationAndPowerInterface::~AdvancedConfigurationAndPowerInterface() = default; - /** * @brief Maps the tables into the higher half * @@ -63,60 +62,69 @@ AdvancedConfigurationAndPowerInterface::~AdvancedConfigurationAndPowerInterface( */ void AdvancedConfigurationAndPowerInterface::map_tables(uint8_t size_of_tables) { - for(uint32_t i = 0; i < (m_header->length - sizeof(ACPISDTHeader)) / size_of_tables; i++) { + for (uint32_t i = 0; i < (m_header->length - sizeof(ACPISDTHeader)) / size_of_tables; i++) { - // Get the address (aligned to page) - auto address = (uint64_t) (m_using_new_acpi ? m_xsdt->pointers[i] : m_rsdt->pointers[i]); - address = PhysicalMemoryManager::align_direct_to_page((size_t)address); + // Get the address (aligned to page) + auto address = (uint64_t) (m_using_new_acpi ? m_xsdt->pointers[i] : m_rsdt->pointers[i]); + address = PhysicalMemoryManager::align_direct_to_page((size_t) address); - // Map to the higher half - PhysicalMemoryManager::s_current_manager->map((physical_address_t*)address, (void*)PhysicalMemoryManager::to_io_region(address), Present | Write); + // Map to the higher half + PhysicalMemoryManager::s_current_manager->map((physical_address_t*) address, (void*) PhysicalMemoryManager::to_io_region(address), Present | Write); - // Reserve the memory - PhysicalMemoryManager::s_current_manager->reserve(address); - } + // Reserve the memory + PhysicalMemoryManager::s_current_manager->reserve(address); + } } +/** + * @brief Validates the checksum of a descriptor + * + * @param descriptor The descriptor to validate + * @param length The length of the descriptor + * @return True if the checksum is valid, false otherwise + */ +bool AdvancedConfigurationAndPowerInterface::validate(const char* descriptor, size_t length) { -bool AdvancedConfigurationAndPowerInterface::validate(const char*descriptor, size_t length) { - // Checksum - uint32_t sum = 0; - - // Calculate the checksum - for(uint32_t i = 0; i < length; i++) - sum += ((char*)descriptor)[i]; + // Checksum + uint32_t sum = 0; - // Check if the checksum is valid - return ((sum & 0xFF) == 0); + // Calculate the checksum + for (uint32_t i = 0; i < length; i++) + sum += ((char*) descriptor)[i]; + // Check if the checksum is valid + return ((sum & 0xFF) == 0); } +/** + * @brief Finds a table with the given signature + * + * @param signature The signature to search for + * @return The table with the given signature, or nullptr if not found + */ +ACPISDTHeader* AdvancedConfigurationAndPowerInterface::find(char const* signature) { + // Get the number of entries + size_t entries = (m_header->length - sizeof(ACPISDTHeader)) / sizeof(uint32_t); + if (m_using_new_acpi) entries = (m_header->length - sizeof(ACPISDTHeader)) / sizeof(uint64_t); -ACPISDTHeader* AdvancedConfigurationAndPowerInterface::find(char const *signature) { - - - // Get the number of entries - size_t entries = (m_header->length - sizeof(ACPISDTHeader)) / sizeof(uint32_t); - if(m_using_new_acpi) entries = (m_header->length - sizeof(ACPISDTHeader)) / sizeof(uint64_t); - - // Loop through all the entries - for (size_t i = 0; i < entries; ++i) { + // Loop through all the entries + for (size_t i = 0; i < entries; ++i) { - // Get the entry - auto* header = (ACPISDTHeader*) (m_using_new_acpi ? m_xsdt->pointers[i] : m_rsdt->pointers[i]); + // Get the entry + auto* header = (ACPISDTHeader*) (m_using_new_acpi ? m_xsdt->pointers[i] : m_rsdt->pointers[i]); - // Move the header to the higher half - header = (ACPISDTHeader*)PhysicalMemoryManager::to_io_region((uint64_t)header); + // Move the header to the higher half + header = (ACPISDTHeader*) PhysicalMemoryManager::to_io_region((uint64_t) header); - // Check if the signature matches - if(strncmp(header->signature, signature, 4) != 0) - return header; - } + // Check if the signature matches + if (strncmp(header->signature, signature, 4) != 0) + return header; + } - // Return null if no entry was found - return nullptr; + // Return null if no entry was found + return nullptr; } /** @@ -126,15 +134,14 @@ ACPISDTHeader* AdvancedConfigurationAndPowerInterface::find(char const *signatur */ bool AdvancedConfigurationAndPowerInterface::valid_checksum() { - // Get the information about the ACPI - char* check = m_using_new_acpi ? (char*)m_rsdp2 : (char*)m_rsdp; - uint32_t length = m_using_new_acpi ? sizeof(RSDPDescriptor2) : sizeof(RSDPDescriptor); - - // Calculate the checksum - uint8_t sum = 0; - for(uint32_t i = 0; i < length; i++) - sum += check[i]; + // Get the information about the ACPI + char* check = m_using_new_acpi ? (char*) m_rsdp2 : (char*) m_rsdp; + uint32_t length = m_using_new_acpi ? sizeof(RSDPDescriptor2) : sizeof(RSDPDescriptor); - return sum == 0; + // Calculate the checksum + uint8_t sum = 0; + for (uint32_t i = 0; i < length; i++) + sum += check[i]; + return sum == 0; } diff --git a/kernel/src/hardwarecommunication/apic.cpp b/kernel/src/hardwarecommunication/apic.cpp index 254c022c..3e510755 100644 --- a/kernel/src/hardwarecommunication/apic.cpp +++ b/kernel/src/hardwarecommunication/apic.cpp @@ -10,54 +10,55 @@ using namespace MaxOS::hardwarecommunication; using namespace MaxOS::system; using namespace MaxOS::memory; -LocalAPIC::LocalAPIC() -{ - // Get the APIC base address - uint64_t msr_info = CPU::read_msr(0x1B); - m_apic_base = msr_info & 0xFFFFF000; - PhysicalMemoryManager::s_current_manager->reserve(m_apic_base); +LocalAPIC::LocalAPIC() { + + // Get the APIC base address + uint64_t msr_info = CPU::read_msr(0x1B); + m_apic_base = msr_info & 0xFFFFF000; + PhysicalMemoryManager::s_current_manager->reserve(m_apic_base); - // Check if the APIC supports x2APIC - uint32_t ignored, xleaf, x2leaf; - CPU::cpuid(0x01, &ignored, &ignored, &x2leaf, &xleaf); + // Check if the APIC supports x2APIC + uint32_t ignored, xleaf, x2leaf; + CPU::cpuid(0x01, &ignored, &ignored, &x2leaf, &xleaf); - if(x2leaf & (1 << 21)) { + if (x2leaf & (1 << 21)) { - // Enable x2APIC - m_x2apic = true; - msr_info |= (1 << 10); - CPU::write_msr(0x1B, msr_info); - Logger::DEBUG() << "CPU supports x2APIC\n"; + // Enable x2APIC + m_x2apic = true; + msr_info |= (1 << 10); + CPU::write_msr(0x1B, msr_info); + Logger::DEBUG() << "CPU supports x2APIC\n"; - } else if (xleaf & (1 << 9)) { + } else if (xleaf & (1 << 9)) { - m_x2apic = false; - Logger::DEBUG() << "CPU supports xAPIC\n"; + m_x2apic = false; + Logger::DEBUG() << "CPU supports xAPIC\n"; - // Map the APIC base address to the higher half - m_apic_base_high = (uint64_t)PhysicalMemoryManager::to_io_region(m_apic_base); - PhysicalMemoryManager::s_current_manager->map((physical_address_t*)m_apic_base, (virtual_address_t*)m_apic_base_high, Write | Present); - Logger::DEBUG() << "APIC Base: phy=0x" << m_apic_base << ", virt=0x" << m_apic_base_high << "\n"; + // Map the APIC base address to the higher half + m_apic_base_high = (uint64_t) PhysicalMemoryManager::to_io_region(m_apic_base); + PhysicalMemoryManager::s_current_manager->map((physical_address_t*) m_apic_base, + (virtual_address_t*) m_apic_base_high, Write | Present); + Logger::DEBUG() << "APIC Base: phy=0x" << m_apic_base << ", virt=0x" << m_apic_base_high << "\n"; - } else { - ASSERT(false, "CPU does not support xAPIC"); - } + } else { + ASSERT(false, "CPU does not support xAPIC"); + } - // Get information about the APIC - uint32_t spurious_vector = read(0xF0); - bool is_enabled = msr_info & (1 << 11); - bool is_bsp = msr_info & (1 << 8); - Logger::DEBUG() << "APIC: boot processor: " << (is_bsp ? "Yes" : "No") << ", enabled (globally): " << (is_enabled ? "Yes" : "No") << " Spurious Vector: 0x" << (uint64_t)(spurious_vector & 0xFF) << "\n"; + // Get information about the APIC + uint32_t spurious_vector = read(0xF0); + bool is_enabled = msr_info & (1 << 11); + bool is_bsp = msr_info & (1 << 8); + Logger::DEBUG() << "APIC: boot processor: " << (is_bsp ? "Yes" : "No") << ", enabled (globally): " << (is_enabled ? "Yes" : "No") << " Spurious Vector: 0x" << (uint64_t) (spurious_vector & 0xFF) << "\n"; - if(!is_enabled) { - Logger::WARNING() << "APIC is not enabled\n"; - return; - } + if (!is_enabled) { + Logger::WARNING() << "APIC is not enabled\n"; + return; + } - // Enable the APIC - write(0xF0, (1 << 8) | 0x100); - Logger::DEBUG() << "APIC Enabled\n"; + // Enable the APIC + write(0xF0, (1 << 8) | 0x100); + Logger::DEBUG() << "APIC Enabled\n"; } LocalAPIC::~LocalAPIC() = default; @@ -68,15 +69,13 @@ LocalAPIC::~LocalAPIC() = default; * @param reg The register to read * @return The value of the register */ -uint32_t LocalAPIC::read(uint32_t reg) const{ - - // If x2APIC is enabled I/O is done through the MSR - if(m_x2apic) - return (uint32_t)CPU::read_msr((reg >> 4) + 0x800); - - return *(volatile uint32_t*)((uintptr_t)m_apic_base_high + reg); +uint32_t LocalAPIC::read(uint32_t reg) const { + // If x2APIC is enabled I/O is done through the MSR + if (m_x2apic) + return (uint32_t) CPU::read_msr((reg >> 4) + 0x800); + return *(volatile uint32_t*) ((uintptr_t) m_apic_base_high + reg); } /** @@ -87,12 +86,12 @@ uint32_t LocalAPIC::read(uint32_t reg) const{ */ void LocalAPIC::write(uint32_t reg, uint32_t value) const { - // If x2APIC is enabled I/O is done through the MSR - if(m_x2apic) - CPU::write_msr((reg >> 4) + 0x800, value); + // If x2APIC is enabled I/O is done through the MSR + if (m_x2apic) + CPU::write_msr((reg >> 4) + 0x800, value); - // Default to memory I/O - *(volatile uint32_t*)((uintptr_t)m_apic_base_high + reg) = value; + // Default to memory I/O + *(volatile uint32_t*) ((uintptr_t) m_apic_base_high + reg) = value; } /** @@ -102,12 +101,11 @@ void LocalAPIC::write(uint32_t reg, uint32_t value) const { */ uint32_t LocalAPIC::id() const { - // Read the id - uint32_t id = read(0x20); - - // Return the id - return m_x2apic ? id : (id >> 24); + // Read the id + uint32_t id = read(0x20); + // Return the id + return m_x2apic ? id : (id >> 24); } /** @@ -115,61 +113,61 @@ uint32_t LocalAPIC::id() const { */ void LocalAPIC::send_eoi() const { - write(0xB0, 0); + write(0xB0, 0); } IOAPIC::IOAPIC(AdvancedConfigurationAndPowerInterface* acpi) : m_acpi(acpi) { - // Get the information about the IO APIC - m_madt = (MADT*)m_acpi->find("APIC"); - MADT_Item* io_apic_item = get_madt_item(1, 0); - - // Get the IO APIC - auto* io_apic = (MADT_IOAPIC*)PhysicalMemoryManager::to_io_region((uint64_t)io_apic_item + sizeof(MADT_Item)); - PhysicalMemoryManager::s_current_manager->map((physical_address_t*)io_apic_item, (virtual_address_t*)(io_apic - sizeof(MADT_Item)), Present | Write); - - // Map the IO APIC address to the higher half - m_address = io_apic->io_apic_address; - m_address_high = (uint64_t)PhysicalMemoryManager::to_io_region(m_address); - PhysicalMemoryManager::s_current_manager->map((physical_address_t*)m_address, (virtual_address_t*)m_address_high, Present | Write); - Logger::DEBUG() << "IO APIC Address: phy=0x" << m_address << ", virt=0x" << m_address_high << "\n"; - - // Get the IO APIC version and max redirection entry - m_version = read(0x1); - m_max_redirect_entry = (uint8_t)(m_version >> 16); - - // Log the IO APIC information - Logger::DEBUG() << "IO APIC Version: 0x" << (uint64_t)(m_version & 0xFF) << "\n"; - Logger::DEBUG() << "IO APIC Max Redirection Entry: 0x" << (uint64_t)m_max_redirect_entry << "\n"; - - // Get the source override item - MADT_Item* source_override_item = get_madt_item(2, m_override_array_size); - uint32_t total_length = sizeof(MADT); - while (total_length < m_madt->header.length && m_override_array_size < 0x10){ // 0x10 is the max items - - total_length += source_override_item->length; - - // If there is an override, populate the array - if(source_override_item != nullptr && source_override_item->type == 2) { - - // Get the override and populate the array - auto* override = (Override *)(source_override_item + 1); - m_override_array[m_override_array_size].bus = override->bus; - m_override_array[m_override_array_size].source = override->source; - m_override_array[m_override_array_size].global_system_interrupt = override->global_system_interrupt; - m_override_array[m_override_array_size].flags = override->flags; - m_override_array_size++; - } - - // Get the next item - source_override_item = get_madt_item(2, m_override_array_size); - if(source_override_item == nullptr) - break; - } - - Logger::DEBUG() << "IO APIC Source Overrides: 0x" << m_override_array_size << "\n"; + // Get the information about the IO APIC + m_madt = (MADT*) m_acpi->find("APIC"); + MADT_Item* io_apic_item = get_madt_item(1, 0); + + // Get the IO APIC + auto* io_apic = (MADT_IOAPIC*) PhysicalMemoryManager::to_io_region((uint64_t) io_apic_item + sizeof(MADT_Item)); + PhysicalMemoryManager::s_current_manager->map((physical_address_t*) io_apic_item, (virtual_address_t*) (io_apic - sizeof(MADT_Item)), Present | Write); + + // Map the IO APIC address to the higher half + m_address = io_apic->io_apic_address; + m_address_high = (uint64_t) PhysicalMemoryManager::to_io_region(m_address); + PhysicalMemoryManager::s_current_manager->map((physical_address_t*) m_address, (virtual_address_t*) m_address_high, Present | Write); + Logger::DEBUG() << "IO APIC Address: phy=0x" << m_address << ", virt=0x" << m_address_high << "\n"; + + // Get the IO APIC version and max redirection entry + m_version = read(0x1); + m_max_redirect_entry = (uint8_t) (m_version >> 16); + + // Log the IO APIC information + Logger::DEBUG() << "IO APIC Version: 0x" << (uint64_t) (m_version & 0xFF) << "\n"; + Logger::DEBUG() << "IO APIC Max Redirection Entry: 0x" << (uint64_t) m_max_redirect_entry << "\n"; + + // Get the source override item + MADT_Item* source_override_item = get_madt_item(2, m_override_array_size); + uint32_t total_length = sizeof(MADT); + while (total_length < m_madt->header.length && m_override_array_size < 0x10) { // 0x10 is the max items + + total_length += source_override_item->length; + + // If there is an override, populate the array + if (source_override_item != nullptr && source_override_item->type == 2) { + + // Get the override and populate the array + auto* override = (Override*) (source_override_item + 1); + m_override_array[m_override_array_size].bus = override->bus; + m_override_array[m_override_array_size].source = override->source; + m_override_array[m_override_array_size].global_system_interrupt = override->global_system_interrupt; + m_override_array[m_override_array_size].flags = override->flags; + m_override_array_size++; + } + + // Get the next item + source_override_item = get_madt_item(2, m_override_array_size); + if (source_override_item == nullptr) + break; + } + + Logger::DEBUG() << "IO APIC Source Overrides: 0x" << m_override_array_size << "\n"; } IOAPIC::~IOAPIC() = default; @@ -183,32 +181,32 @@ IOAPIC::~IOAPIC() = default; */ MADT_Item* IOAPIC::get_madt_item(uint8_t type, uint8_t index) { - // The item starts at the start of the MADT - auto* item = (MADT_Item*)((uint64_t)m_madt + sizeof(MADT)); - uint64_t total_length = 0; - uint8_t current_index = 0; + // The item starts at the start of the MADT + auto* item = (MADT_Item*) ((uint64_t) m_madt + sizeof(MADT)); + uint64_t total_length = 0; + uint8_t current_index = 0; - // Loop through the items - while (total_length + sizeof(MADT) < m_madt->header.length && current_index <= index) { + // Loop through the items + while (total_length + sizeof(MADT) < m_madt->header.length && current_index <= index) { - // Correct type - if(item->type == type) { + // Correct type + if (item->type == type) { - // Correct index means found - if(current_index == index) - return item; + // Correct index means found + if (current_index == index) + return item; - // Right type wrong index so move on - current_index++; - } + // Right type wrong index so move on + current_index++; + } - // Increment the total length - total_length += item->length; - item = (MADT_Item*)((uint64_t)item + item->length); - } + // Increment the total length + total_length += item->length; + item = (MADT_Item*) ((uint64_t) item + item->length); + } - // No item found - return nullptr; + // No item found + return nullptr; } /** @@ -219,11 +217,11 @@ MADT_Item* IOAPIC::get_madt_item(uint8_t type, uint8_t index) { */ uint32_t IOAPIC::read(uint32_t reg) const { - // Tell the APIC what register to read from - *(volatile uint32_t*)(m_address_high + 0x00) = reg; + // Tell the APIC what register to read from + *(volatile uint32_t*) (m_address_high + 0x00) = reg; - // Read the value - return *(volatile uint32_t*)(m_address_high + 0x10); + // Read the value + return *(volatile uint32_t*) (m_address_high + 0x10); } /** @@ -235,11 +233,11 @@ uint32_t IOAPIC::read(uint32_t reg) const { */ void IOAPIC::write(uint32_t reg, uint32_t value) const { - // Write the register - *(volatile uint32_t*)(m_address_high + 0x00) = reg; + // Write the register + *(volatile uint32_t*) (m_address_high + 0x00) = reg; - // Write the value - *(volatile uint32_t*)(m_address_high + 0x10) = value; + // Write the value + *(volatile uint32_t*) (m_address_high + 0x10) = value; } /** @@ -248,17 +246,16 @@ void IOAPIC::write(uint32_t reg, uint32_t value) const { * @param index The index of the entry * @param entry The buffer to read into */ -void IOAPIC::read_redirect(uint8_t index, RedirectionEntry *entry) { - - // Check bounds - if(index < 0x10 || index > 0x3F) - return; +void IOAPIC::read_redirect(uint8_t index, RedirectionEntry* entry) { - // Read the entry chunks into the buffer (struct is auto extracted from the raw information) - uint32_t low = read(index); - uint32_t high = read(index + 1); - entry->raw = ((uint64_t)high << 32) | ((uint64_t)low); + // Check bounds + if (index < 0x10 || index > 0x3F) + return; + // Read the entry chunks into the buffer (struct is auto extracted from the raw information) + uint32_t low = read(index); + uint32_t high = read(index + 1); + entry->raw = ((uint64_t) high << 32) | ((uint64_t) low); } /** @@ -267,19 +264,19 @@ void IOAPIC::read_redirect(uint8_t index, RedirectionEntry *entry) { * @param index The index of the entry * @param entry The buffer to write into */ -void IOAPIC::write_redirect(uint8_t index, RedirectionEntry *entry) { +void IOAPIC::write_redirect(uint8_t index, RedirectionEntry* entry) { - // Check bounds - if(index < 0x10 || index > 0x3F) - return; + // Check bounds + if (index < 0x10 || index > 0x3F) + return; - // Break the entry down into 32bit chunks - auto low = (uint32_t)entry->raw; - auto high = (uint32_t)(entry->raw >> 32); + // Break the entry down into 32bit chunks + auto low = (uint32_t) entry->raw; + auto high = (uint32_t) (entry->raw >> 32); - // Set the entry - write(index, low); - write(index + 1, high); + // Set the entry + write(index, low); + write(index + 1, high); } /** @@ -289,30 +286,30 @@ void IOAPIC::write_redirect(uint8_t index, RedirectionEntry *entry) { */ void IOAPIC::set_redirect(interrupt_redirect_t* redirect) { - // Create the redirection entry - RedirectionEntry entry = {}; - entry.raw = redirect->flags | (redirect -> interrupt & 0xFF); - entry.destination = redirect->destination; - entry.mask = redirect->mask; + // Create the redirection entry + RedirectionEntry entry = {}; + entry.raw = redirect->flags | (redirect->interrupt & 0xFF); + entry.destination = redirect->destination; + entry.mask = redirect->mask; - // Check if a global system interrupt is used - for (uint8_t i = 0; i < m_override_array_size; i++) { + // Check if a global system interrupt is used + for (uint8_t i = 0; i < m_override_array_size; i++) { - if (m_override_array[i].source != redirect->type) - continue; + if (m_override_array[i].source != redirect->type) + continue; - // Set the lower 4 bits of the pin - entry.pin_polarity = ((m_override_array[i].flags & 0b11) == 2) ? 0b1 : 0b0; - entry.pin_polarity = (((m_override_array[i].flags >> 2) & 0b11) == 2) ? 0b1 : 0b0; + // Set the lower 4 bits of the pin + entry.pin_polarity = ((m_override_array[i].flags & 0b11) == 2) ? 0b1 : 0b0; + entry.pin_polarity = (((m_override_array[i].flags >> 2) & 0b11) == 2) ? 0b1 : 0b0; - // Set the trigger mode - entry.trigger_mode = (((m_override_array[i].flags >> 2) & 0b11) == 2); - break; + // Set the trigger mode + entry.trigger_mode = (((m_override_array[i].flags >> 2) & 0b11) == 2); + break; - } + } - // Write the redirect - write_redirect(redirect->index, &entry); + // Write the redirect + write_redirect(redirect->index, &entry); } /** @@ -323,13 +320,13 @@ void IOAPIC::set_redirect(interrupt_redirect_t* redirect) { */ void IOAPIC::set_redirect_mask(uint8_t index, bool mask) { - // Read the current entry - RedirectionEntry entry = {}; - read_redirect(index, &entry); + // Read the current entry + RedirectionEntry entry = {}; + read_redirect(index, &entry); - // Set the mask - entry.mask = mask; - write_redirect(index, &entry); + // Set the mask + entry.mask = mask; + write_redirect(index, &entry); } AdvancedProgrammableInterruptController::AdvancedProgrammableInterruptController(AdvancedConfigurationAndPowerInterface* acpi) @@ -339,61 +336,72 @@ AdvancedProgrammableInterruptController::AdvancedProgrammableInterruptController m_pic_slave_data_port(0xA1) { - // Register this APIC - Logger::INFO() << "Setting up APIC\n"; - InterruptManager::active_interrupt_manager()->set_apic(this); - - // Init the Local APIC - Logger::DEBUG() << "Initialising Local APIC\n"; - m_local_apic = new LocalAPIC(); + // Register this APIC + Logger::INFO() << "Setting up APIC\n"; + InterruptManager::active_interrupt_manager()->set_apic(this); - // Disable the legacy mode PIC - Logger::DEBUG() << "Disabling PIC\n"; - disable_pic(); + // Init the Local APIC + Logger::DEBUG() << "Initialising Local APIC\n"; + m_local_apic = new LocalAPIC(); - // Init the IO APIC - Logger::DEBUG() << "Initialising IO APIC\n"; - m_io_apic = new IOAPIC(acpi); + // Disable the legacy mode PIC + Logger::DEBUG() << "Disabling PIC\n"; + disable_pic(); + // Init the IO APIC + Logger::DEBUG() << "Initialising IO APIC\n"; + m_io_apic = new IOAPIC(acpi); } -AdvancedProgrammableInterruptController::~AdvancedProgrammableInterruptController() -{ - - // Free the memory - delete m_local_apic; - delete m_io_apic; +AdvancedProgrammableInterruptController::~AdvancedProgrammableInterruptController() { + // Free the memory + delete m_local_apic; + delete m_io_apic; } +/** + * @brief Disable the legacy PIC to prevent it from sending interrupts + */ void AdvancedProgrammableInterruptController::disable_pic() { - // Initialise the PIC - m_pic_master_command_port.write(0x11); - m_pic_slave_command_port.write(0x11); - - // Set the offsets - m_pic_master_data_port.write(0x20); - m_pic_slave_data_port.write(0x28); + // Initialise the PIC + m_pic_master_command_port.write(0x11); + m_pic_slave_command_port.write(0x11); - // Set the slave/master relationships - m_pic_master_data_port.write(0x04); - m_pic_slave_data_port.write(0x02); + // Set the offsets + m_pic_master_data_port.write(0x20); + m_pic_slave_data_port.write(0x28); - // Set the modes (8086/8086) - m_pic_master_data_port.write(0x01); - m_pic_slave_data_port.write(0x01); + // Set the slave/master relationships + m_pic_master_data_port.write(0x04); + m_pic_slave_data_port.write(0x02); - // Mask the interrupts - m_pic_master_data_port.write(0xFF); - m_pic_slave_data_port.write(0xFF); + // Set the modes (8086/8086) + m_pic_master_data_port.write(0x01); + m_pic_slave_data_port.write(0x01); + // Mask the interrupts + m_pic_master_data_port.write(0xFF); + m_pic_slave_data_port.write(0xFF); } -LocalAPIC *AdvancedProgrammableInterruptController::local_apic() const -{ - return m_local_apic; -} -IOAPIC* AdvancedProgrammableInterruptController::io_apic() const -{ - return m_io_apic; + +/** + * @brief Get the local apic + * + * @return The local apic + */ +LocalAPIC* AdvancedProgrammableInterruptController::local_apic() const { + + return m_local_apic; } + +/** + * @brief Get the io apic + * + * @return The io apic + */ +IOAPIC* AdvancedProgrammableInterruptController::io_apic() const { + + return m_io_apic; +} \ No newline at end of file diff --git a/kernel/src/hardwarecommunication/interrupts.cpp b/kernel/src/hardwarecommunication/interrupts.cpp index 1d49bc3e..38f65b72 100644 --- a/kernel/src/hardwarecommunication/interrupts.cpp +++ b/kernel/src/hardwarecommunication/interrupts.cpp @@ -10,46 +10,43 @@ using namespace MaxOS::common; using namespace MaxOS::hardwarecommunication; using namespace MaxOS::system; - -///__Handler__ - InterruptHandler::InterruptHandler(uint8_t interrupt_number, int64_t redirect, uint64_t redirect_index) : m_interrupt_number(interrupt_number) { - // Get the interrupt manager - InterruptManager* interrupt_manager = InterruptManager::s_active_interrupt_manager; - ASSERT(interrupt_manager != nullptr, "No active interrupt manager"); - - // Register the handler - interrupt_manager -> set_interrupt_handler(m_interrupt_number, this); - - // Not all interrupts need redirecting - if(redirect == -1) - return; - - // Redirect a legacy interrupt to an interrupt the kernel expects - IOAPIC* io_apic = interrupt_manager -> active_apic() -> io_apic(); - interrupt_redirect_t temp = { - .type = (uint8_t)redirect, - .index = (uint8_t)redirect_index, - .interrupt = m_interrupt_number, - .destination = 0x00, - .flags = 0x00, - .mask = false, - }; - io_apic -> set_redirect(&temp); + // Get the interrupt manager + InterruptManager* interrupt_manager = InterruptManager::s_active_interrupt_manager; + ASSERT(interrupt_manager != nullptr, "No active interrupt manager"); + + // Register the handler + interrupt_manager->set_interrupt_handler(m_interrupt_number, this); + + // Not all interrupts need redirecting + if (redirect == -1) + return; + + // Redirect a legacy interrupt to an interrupt the kernel expects + IOAPIC* io_apic = interrupt_manager->active_apic()->io_apic(); + interrupt_redirect_t temp = { + .type = (uint8_t) redirect, + .index = (uint8_t) redirect_index, + .interrupt = m_interrupt_number, + .destination = 0x00, + .flags = 0x00, + .mask = false, + }; + io_apic->set_redirect(&temp); } -InterruptHandler::~InterruptHandler(){ +InterruptHandler::~InterruptHandler() { - // Get the interrupt manager - InterruptManager* interrupt_manager = InterruptManager::s_active_interrupt_manager; - if(interrupt_manager == nullptr) return; + // Get the interrupt manager + InterruptManager* interrupt_manager = InterruptManager::s_active_interrupt_manager; + if (interrupt_manager == nullptr) return; - // Remove the handler - interrupt_manager -> set_interrupt_handler(m_interrupt_number, nullptr); + // Remove the handler + interrupt_manager->set_interrupt_handler(m_interrupt_number, nullptr); } /** @@ -61,91 +58,88 @@ void InterruptHandler::handle_interrupt() { /** * @brief Handles an interrupt and returns the status + * * @param status The status of the CPU * @return The status of the CPU */ -system::cpu_status_t* InterruptHandler::handle_interrupt(system::cpu_status_t *status) { +system::cpu_status_t* InterruptHandler::handle_interrupt(system::cpu_status_t* status) { - // For handlers that don't care about the status - handle_interrupt(); - - // Return the default status - return status; + // For handlers that don't care about the status + handle_interrupt(); + // Return the default status + return status; } -InterruptManager::InterruptManager() -{ - - Logger::INFO() << "Setting up Interrupt Manager\n"; - s_active_interrupt_manager = this; - - // Clear the table - for(auto& descriptor : s_interrupt_descriptor_table) { - descriptor.address_low_bits = 0; - descriptor.address_mid_bits = 0; - descriptor.address_high_bits = 0; - descriptor.segment_selector = 0; - descriptor.ist = 0; - descriptor.flags = 0; - } - - //Set Up the base interrupts - set_interrupt_descriptor_table_entry(0x00, &HandleException0x00, 0); // Division by zero - set_interrupt_descriptor_table_entry(0x01, &HandleException0x01, 0); // Debug - set_interrupt_descriptor_table_entry(0x02, &HandleException0x02, 0); // Non-maskable interrupt - set_interrupt_descriptor_table_entry(0x03, &HandleException0x03, 0); // Breakpoint - set_interrupt_descriptor_table_entry(0x04, &HandleException0x04, 0); // Overflow - set_interrupt_descriptor_table_entry(0x05, &HandleException0x05, 0); // Bound Range Exceeded - set_interrupt_descriptor_table_entry(0x06, &HandleException0x06, 0); // Invalid Opcode - set_interrupt_descriptor_table_entry(0x06, &HandleException0x07, 0); // Device Not Available - set_interrupt_descriptor_table_entry(0x08, &HandleInterruptError0x08, 0); // Double Fault - set_interrupt_descriptor_table_entry(0x09, &HandleException0x09, 0); // Coprocessor Segment Overrun - set_interrupt_descriptor_table_entry(0x0A, &HandleInterruptError0x0A, 0); // Invalid TSS - set_interrupt_descriptor_table_entry(0x0B, &HandleInterruptError0x0B, 0); // Segment Not Present - set_interrupt_descriptor_table_entry(0x0C, &HandleInterruptError0x0C, 0); // Stack-Segment Fault - set_interrupt_descriptor_table_entry(0x0D, &HandleInterruptError0x0D, 0); // General Protection Fault - set_interrupt_descriptor_table_entry(0x0E, &HandleInterruptError0x0E, 0); // Page Fault - set_interrupt_descriptor_table_entry(0x0F, &HandleException0x0F, 0); // Reserved - set_interrupt_descriptor_table_entry(0x10, &HandleException0x10, 0); // x87 Floating-Point Exception - set_interrupt_descriptor_table_entry(0x11, &HandleInterruptError0x11, 0); // Alignment Check - set_interrupt_descriptor_table_entry(0x12, &HandleException0x12, 0); // Machine Check - set_interrupt_descriptor_table_entry(0x13, &HandleException0x13, 0); // SIMD Floating-Point Exception - set_interrupt_descriptor_table_entry(0x14, &HandleException0x14, 0); // Reserved: Virtualization Exception - set_interrupt_descriptor_table_entry(0x15, &HandleException0x15, 0); // Reserved - set_interrupt_descriptor_table_entry(0x16, &HandleException0x16, 0); // Reserved - set_interrupt_descriptor_table_entry(0x17, &HandleException0x17, 0); // Reserved - set_interrupt_descriptor_table_entry(0x18, &HandleException0x18, 0); // Reserved - set_interrupt_descriptor_table_entry(0x19, &HandleException0x19, 0); // Reserved - set_interrupt_descriptor_table_entry(0x1A, &HandleException0x1A, 0); // Reserved - set_interrupt_descriptor_table_entry(0x1B, &HandleException0x1B, 0); // Reserved - set_interrupt_descriptor_table_entry(0x1C, &HandleException0x1C, 0); // Reserved - set_interrupt_descriptor_table_entry(0x1D, &HandleException0x1D, 0); // Reserved - set_interrupt_descriptor_table_entry(0x1E, &HandleException0x1E, 0); // Security Exception - set_interrupt_descriptor_table_entry(0x1F, &HandleException0x1F, 0); // Reserved - - // Set up the hardware interrupts - set_interrupt_descriptor_table_entry(s_hardware_interrupt_offset + 0x00, &HandleInterruptRequest0x00, 0); // APIC Timer Interrupt - set_interrupt_descriptor_table_entry(s_hardware_interrupt_offset + 0x01, &HandleInterruptRequest0x01, 0); // Keyboard Interrupt - set_interrupt_descriptor_table_entry(s_hardware_interrupt_offset + 0x02, &HandleInterruptRequest0x02, 0); // PIT Interrupt - set_interrupt_descriptor_table_entry(s_hardware_interrupt_offset + 0x0C, &HandleInterruptRequest0x0C, 0); // Mouse Interrupt - - // Set up the system call interrupt - set_interrupt_descriptor_table_entry(s_hardware_interrupt_offset + 0x60, &HandleInterruptRequest0x60, 3); // System Call Interrupt - Privilege Level 3 so that user space can call it - - // Tell the processor to use the IDT - IDTR idt = {}; - idt.limit = 256 * sizeof(InterruptDescriptor) - 1; - idt.base = (uint64_t)s_interrupt_descriptor_table; - asm volatile("lidt %0" : : "m" (idt)); +InterruptManager::InterruptManager() { + + Logger::INFO() << "Setting up Interrupt Manager\n"; + s_active_interrupt_manager = this; + + // Clear the table + for (auto& descriptor: s_interrupt_descriptor_table) { + descriptor.address_low_bits = 0; + descriptor.address_mid_bits = 0; + descriptor.address_high_bits = 0; + descriptor.segment_selector = 0; + descriptor.ist = 0; + descriptor.flags = 0; + } + + //Set Up the base interrupts + set_interrupt_descriptor_table_entry(0x00, &HandleException0x00, 0); // Division by zero + set_interrupt_descriptor_table_entry(0x01, &HandleException0x01, 0); // Debug + set_interrupt_descriptor_table_entry(0x02, &HandleException0x02, 0); // Non-maskable interrupt + set_interrupt_descriptor_table_entry(0x03, &HandleException0x03, 0); // Breakpoint + set_interrupt_descriptor_table_entry(0x04, &HandleException0x04, 0); // Overflow + set_interrupt_descriptor_table_entry(0x05, &HandleException0x05, 0); // Bound Range Exceeded + set_interrupt_descriptor_table_entry(0x06, &HandleException0x06, 0); // Invalid Opcode + set_interrupt_descriptor_table_entry(0x06, &HandleException0x07, 0); // Device Not Available + set_interrupt_descriptor_table_entry(0x08, &HandleInterruptError0x08, 0); // Double Fault + set_interrupt_descriptor_table_entry(0x09, &HandleException0x09, 0); // Coprocessor Segment Overrun + set_interrupt_descriptor_table_entry(0x0A, &HandleInterruptError0x0A, 0); // Invalid TSS + set_interrupt_descriptor_table_entry(0x0B, &HandleInterruptError0x0B, 0); // Segment Not Present + set_interrupt_descriptor_table_entry(0x0C, &HandleInterruptError0x0C, 0); // Stack-Segment Fault + set_interrupt_descriptor_table_entry(0x0D, &HandleInterruptError0x0D, 0); // General Protection Fault + set_interrupt_descriptor_table_entry(0x0E, &HandleInterruptError0x0E, 0); // Page Fault + set_interrupt_descriptor_table_entry(0x0F, &HandleException0x0F, 0); // Reserved + set_interrupt_descriptor_table_entry(0x10, &HandleException0x10, 0); // x87 Floating-Point Exception + set_interrupt_descriptor_table_entry(0x11, &HandleInterruptError0x11, 0); // Alignment Check + set_interrupt_descriptor_table_entry(0x12, &HandleException0x12, 0); // Machine Check + set_interrupt_descriptor_table_entry(0x13, &HandleException0x13, 0); // SIMD Floating-Point Exception + set_interrupt_descriptor_table_entry(0x14, &HandleException0x14, 0); // Reserved: Virtualization Exception + set_interrupt_descriptor_table_entry(0x15, &HandleException0x15, 0); // Reserved + set_interrupt_descriptor_table_entry(0x16, &HandleException0x16, 0); // Reserved + set_interrupt_descriptor_table_entry(0x17, &HandleException0x17, 0); // Reserved + set_interrupt_descriptor_table_entry(0x18, &HandleException0x18, 0); // Reserved + set_interrupt_descriptor_table_entry(0x19, &HandleException0x19, 0); // Reserved + set_interrupt_descriptor_table_entry(0x1A, &HandleException0x1A, 0); // Reserved + set_interrupt_descriptor_table_entry(0x1B, &HandleException0x1B, 0); // Reserved + set_interrupt_descriptor_table_entry(0x1C, &HandleException0x1C, 0); // Reserved + set_interrupt_descriptor_table_entry(0x1D, &HandleException0x1D, 0); // Reserved + set_interrupt_descriptor_table_entry(0x1E, &HandleException0x1E, 0); // Security Exception + set_interrupt_descriptor_table_entry(0x1F, &HandleException0x1F, 0); // Reserved + + // Set up the hardware interrupts + set_interrupt_descriptor_table_entry(s_hardware_interrupt_offset + 0x00, &HandleInterruptRequest0x00, 0); // APIC Timer Interrupt + set_interrupt_descriptor_table_entry(s_hardware_interrupt_offset + 0x01, &HandleInterruptRequest0x01, 0); // Keyboard Interrupt + set_interrupt_descriptor_table_entry(s_hardware_interrupt_offset + 0x02, &HandleInterruptRequest0x02, 0); // PIT Interrupt + set_interrupt_descriptor_table_entry(s_hardware_interrupt_offset + 0x0C, &HandleInterruptRequest0x0C, 0); // Mouse Interrupt + + // Set up the system call interrupt + set_interrupt_descriptor_table_entry(s_hardware_interrupt_offset + 0x60, &HandleInterruptRequest0x60, 3); // System Call Interrupt - Privilege Level 3 so that user space can call it + + // Tell the processor to use the IDT + IDTR idt = {}; + idt.limit = 256 * sizeof(InterruptDescriptor) - 1; + idt.base = (uint64_t) s_interrupt_descriptor_table; + asm volatile("lidt %0" : : "m" (idt)); } -InterruptManager::~InterruptManager() -{ - deactivate(); +InterruptManager::~InterruptManager() { + deactivate(); } - /** * @brief Sets an entry in the Interrupt Descriptor Table * @@ -155,58 +149,55 @@ InterruptManager::~InterruptManager() * @param descriptor_privilege_level Descriptor Privilege Level * @param descriptor_type Descriptor Type */ -void InterruptManager::set_interrupt_descriptor_table_entry(uint8_t interrupt, void (*handler)(), uint8_t descriptor_privilege_level) -{ +void InterruptManager::set_interrupt_descriptor_table_entry(uint8_t interrupt, void (* handler)(), uint8_t descriptor_privilege_level) { - // Get the address of the handler and the entry in the IDT - auto handler_address = (uint64_t)handler; - InterruptDescriptor* interrupt_descriptor = &s_interrupt_descriptor_table[interrupt]; + // Get the address of the handler and the entry in the IDT + auto handler_address = (uint64_t) handler; + InterruptDescriptor* interrupt_descriptor = &s_interrupt_descriptor_table[interrupt]; - // Set the handler address - interrupt_descriptor->address_low_bits = handler_address & 0xFFFF; - interrupt_descriptor->address_mid_bits = (handler_address >> 16) & 0xFFFF; - interrupt_descriptor->address_high_bits = (handler_address >> 32) & 0xFFFFFFFF; + // Set the handler address + interrupt_descriptor->address_low_bits = handler_address & 0xFFFF; + interrupt_descriptor->address_mid_bits = (handler_address >> 16) & 0xFFFF; + interrupt_descriptor->address_high_bits = (handler_address >> 32) & 0xFFFFFFFF; - // Set the kernel code segment offset - interrupt_descriptor->segment_selector = 0x08; + // Set the kernel code segment offset + interrupt_descriptor->segment_selector = 0x08; - // Disable IST - interrupt_descriptor->ist = 0; + // Disable IST + interrupt_descriptor->ist = 0; - // Set the flags (Trap Gate, Present and the Descriptor Privilege Level) - interrupt_descriptor->flags = 0b1110 | ((descriptor_privilege_level & 0b11) << 5) | (1 << 7); + // Set the flags (Trap Gate, Present and the Descriptor Privilege Level) + interrupt_descriptor->flags = 0b1110 | ((descriptor_privilege_level & 0b11) << 5) | (1 << 7); } - /** * @brief Activates the interrupt manager and starts interrupts (also deactivates the current interrupt manager) */ void InterruptManager::activate() { - Logger::INFO() << "Activating Interrupts \n"; + Logger::INFO() << "Activating Interrupts \n"; - // Deactivate the current (old) interrupt manager - if(s_active_interrupt_manager != nullptr) - s_active_interrupt_manager->deactivate(); + // Deactivate the current (old) interrupt manager + if (s_active_interrupt_manager != nullptr) + s_active_interrupt_manager->deactivate(); - // Set the current interrupt manager and start interrupts - s_active_interrupt_manager = this; - asm("sti"); + // Set the current interrupt manager and start interrupts + s_active_interrupt_manager = this; + asm("sti"); } /** * @brief Deactivates the interrupt manager and stops interrupts */ -void InterruptManager::deactivate() -{ +void InterruptManager::deactivate() { - // Cant deactivate if it isn't the system one - if (s_active_interrupt_manager != nullptr) - return; + // Cant deactivate if it isn't the system one + if (s_active_interrupt_manager != nullptr) + return; - // Prevent interrupts from firing when nothing is set up to handle them - asm("cli"); - s_active_interrupt_manager = nullptr; + // Prevent interrupts from firing when nothing is set up to handle them + asm("cli"); + s_active_interrupt_manager = nullptr; } /** @@ -215,30 +206,30 @@ void InterruptManager::deactivate() * @param status The current cpu status * @return The updated cpu status */ -system::cpu_status_t* InterruptManager::HandleInterrupt(system::cpu_status_t *status) { +system::cpu_status_t* InterruptManager::HandleInterrupt(system::cpu_status_t* status) { - // Default Fault (haha) Handlers - switch (status->interrupt_number) { + // Default Fault Handlers + switch (status->interrupt_number) { - case 0x7: - Logger::ERROR() << "Device Not Available: FPU Not Enabled\n"; - CPU::prepare_for_panic(status); - CPU::PANIC("See above message for more information", status); - break; + case 0x7: + Logger::ERROR() << "Device Not Available: FPU Not Enabled\n"; + CPU::prepare_for_panic(status); + CPU::PANIC("See above message for more information", status); + break; - case 0x0D: - return general_protection_fault(status); + case 0x0D: + return general_protection_fault(status); - case 0x0E: - return page_fault(status); - } + case 0x0E: + return page_fault(status); + } - // If there is an interrupt manager handle interrupt - if(s_active_interrupt_manager != nullptr) - return s_active_interrupt_manager->handle_interrupt_request(status); + // If there is an interrupt manager handle interrupt + if (s_active_interrupt_manager != nullptr) + return s_active_interrupt_manager->handle_interrupt_request(status); - // CPU Can continue - return status; + // CPU Can continue + return status; } /** @@ -247,7 +238,8 @@ system::cpu_status_t* InterruptManager::HandleInterrupt(system::cpu_status_t *st * @return The offset of the hardware interrupt */ uint16_t InterruptManager::hardware_interrupt_offset() { - return s_hardware_interrupt_offset; + + return s_hardware_interrupt_offset; } /** @@ -256,8 +248,9 @@ uint16_t InterruptManager::hardware_interrupt_offset() { * @param interrupt The interrupt number * @param handler The interrupt handler */ -void InterruptManager::set_interrupt_handler(uint8_t interrupt, InterruptHandler *handler) { - m_interrupt_handlers[interrupt] = handler; +void InterruptManager::set_interrupt_handler(uint8_t interrupt, InterruptHandler* handler) { + + m_interrupt_handlers[interrupt] = handler; } /** @@ -266,74 +259,100 @@ void InterruptManager::set_interrupt_handler(uint8_t interrupt, InterruptHandler * @param interrupt The interrupt number */ void InterruptManager::remove_interrupt_handler(uint8_t interrupt) { - m_interrupt_handlers[interrupt] = nullptr; + + m_interrupt_handlers[interrupt] = nullptr; } +/** + * @brief Handles the interrupt request by passing it to the appropriate handler + * + * @param status The current cpu status + * @return The updated cpu status + */ cpu_status_t* InterruptManager::handle_interrupt_request(cpu_status_t* status) { - // Where to go afterward - cpu_status_t* new_status = status; - - // If there is an interrupt manager, handle the interrupt - if(m_interrupt_handlers[status -> interrupt_number] != nullptr) - new_status = m_interrupt_handlers[status -> interrupt_number]->handle_interrupt(status); - else - Logger::WARNING() << "Interrupt " << (int)status->interrupt_number << " not handled\n"; + // Where to go afterward + cpu_status_t* new_status = status; - // Send the EOI to the APIC - if(s_hardware_interrupt_offset <= status->interrupt_number && status->interrupt_number < s_hardware_interrupt_offset + 16) - m_apic->local_apic()->send_eoi(); + // If there is an interrupt manager, handle the interrupt + if (m_interrupt_handlers[status->interrupt_number] != nullptr) + new_status = m_interrupt_handlers[status->interrupt_number]->handle_interrupt(status); + else + Logger::WARNING() << "Interrupt " << (int) status->interrupt_number << " not handled\n"; - // Return the status - return new_status; -} + // Send the EOI to the APIC + if (s_hardware_interrupt_offset <= status->interrupt_number && status->interrupt_number < s_hardware_interrupt_offset + 16) + m_apic->local_apic()->send_eoi(); -void InterruptManager::set_apic(AdvancedProgrammableInterruptController *apic) { - m_apic = apic; + // Return the status + return new_status; } -cpu_status_t* InterruptManager::page_fault(system::cpu_status_t *status) { - - // Extract the information about the fault - bool present = (status ->error_code & 0x1) != 0; - bool write = (status ->error_code & 0x2) != 0; - bool user_mode = (status ->error_code & 0x4) != 0; - bool reserved_write = (status ->error_code & 0x8) != 0; - bool instruction_fetch = (status ->error_code & 0x10) != 0; - uint64_t faulting_address; - asm volatile("movq %%cr2, %0" : "=r" (faulting_address)); - - // Try kill the process so the system doesn't die - cpu_status_t* can_avoid = CPU::prepare_for_panic(status); - if(can_avoid != nullptr) - return can_avoid; +/** + * @brief Sets the APIC + * + * @param apic The APIC + */ +void InterruptManager::set_apic(AdvancedProgrammableInterruptController* apic) { - // Cant avoid it so halt the kernel - Logger::ERROR() << "Page Fault: (0x" << faulting_address << "): present: " << (present ? "Yes" : "No") << ", write: " << (write ? "Yes" : "No") << ", user-mode: " << (user_mode ? "Yes" : "No") << ", reserved write: " << (reserved_write ? "Yes" : "No") << ", instruction fetch: " << (instruction_fetch ? "Yes" : "No") << "\n"; - CPU::PANIC("See above message for more information", status); + m_apic = apic; +} - // Probably should never get here - return status; +/** + * @brief Handles a page fault + * + * @param status The cpu status + * @return The updated cpu status (won't return if it panics) + */ +cpu_status_t* InterruptManager::page_fault(system::cpu_status_t* status) { + + // Extract the information about the fault + bool present = (status->error_code & 0x1) != 0; + bool write = (status->error_code & 0x2) != 0; + bool user_mode = (status->error_code & 0x4) != 0; + bool reserved_write = (status->error_code & 0x8) != 0; + bool instruction_fetch = (status->error_code & 0x10) != 0; + uint64_t faulting_address; + asm volatile("movq %%cr2, %0" : "=r" (faulting_address)); + + // Try kill the process so the system doesn't die + cpu_status_t* can_avoid = CPU::prepare_for_panic(status); + if (can_avoid != nullptr) + return can_avoid; + + // Cant avoid it so halt the kernel + Logger::ERROR() << "Page Fault: (0x" << faulting_address << "): present: " << (present ? "Yes" : "No") + << ", write: " << (write ? "Yes" : "No") << ", user-mode: " << (user_mode ? "Yes" : "No") + << ", reserved write: " << (reserved_write ? "Yes" : "No") << ", instruction fetch: " + << (instruction_fetch ? "Yes" : "No") << "\n"; + CPU::PANIC("See above message for more information", status); + + // Probably should never get here + return status; } /** * @brief Handles a general protection fault + * * @param status The cpu status + * @return The updated cpu status (won't return if it panics) */ -cpu_status_t* InterruptManager::general_protection_fault(system::cpu_status_t *status) { - uint64_t error_code = status->error_code; +cpu_status_t* InterruptManager::general_protection_fault(system::cpu_status_t* status) { + + uint64_t error_code = status->error_code; - // Try to avoid the panic - cpu_status_t* can_avoid = CPU::prepare_for_panic(status); - if(can_avoid != nullptr) - return can_avoid; + // Try to avoid the panic + cpu_status_t* can_avoid = CPU::prepare_for_panic(status); + if (can_avoid != nullptr) + return can_avoid; - // Have to panic - Logger::ERROR() << "General Protection Fault: (0x" << status->rip << "): " << (error_code & 0x1 ? "Protection-Exception" : "Not a Protection Exception") << "\n"; - CPU::PANIC("See above message for more information", status); + // Have to panic + Logger::ERROR() << "General Protection Fault: (0x" << status->rip << "): " + << (error_code & 0x1 ? "Protection-Exception" : "Not a Protection Exception") << "\n"; + CPU::PANIC("See above message for more information", status); - // Probably should never get here - return status; + // Probably should never get here + return status; } /** @@ -342,7 +361,8 @@ cpu_status_t* InterruptManager::general_protection_fault(system::cpu_status_t *s * @return The APIC */ AdvancedProgrammableInterruptController* InterruptManager::active_apic() { - return m_apic; + + return m_apic; } /** @@ -350,6 +370,7 @@ AdvancedProgrammableInterruptController* InterruptManager::active_apic() { * * @return The active interrupt manager */ -InterruptManager *InterruptManager::active_interrupt_manager() { - return s_active_interrupt_manager; +InterruptManager* InterruptManager::active_interrupt_manager() { + + return s_active_interrupt_manager; } diff --git a/kernel/src/hardwarecommunication/pci.cpp b/kernel/src/hardwarecommunication/pci.cpp index b4057420..a403e197 100644 --- a/kernel/src/hardwarecommunication/pci.cpp +++ b/kernel/src/hardwarecommunication/pci.cpp @@ -18,64 +18,73 @@ using namespace MaxOS::drivers::ethernet; using namespace MaxOS::drivers::video; using namespace MaxOS::drivers::disk; -///__DESCRIPTOR___ - PeripheralComponentInterconnectDeviceDescriptor::PeripheralComponentInterconnectDeviceDescriptor() = default; PeripheralComponentInterconnectDeviceDescriptor::~PeripheralComponentInterconnectDeviceDescriptor() = default; /** * @brief Get the type of the device + * * @return Type of the device as a string (or Unknown if the type is not known) */ string PeripheralComponentInterconnectDeviceDescriptor::get_type() const { - switch (class_id) - { - case 0x00: return (subclass_id == 0x01) ? "VGA" : "Legacy"; - case 0x01: - switch(subclass_id) - { - case 0x01: return "IDE interface"; - case 0x06: return "SATA controller"; - default: return "Storage"; - } - case 0x02: return "Network"; - case 0x03: return "Display"; - case 0x04: - switch(subclass_id) - { - case 0x00: return "Video"; - case 0x01: - case 0x03: return "Audio"; - default: return "Multimedia"; - } - case 0x06: - switch(subclass_id) - { - case 0x00: return "Host bridge"; - case 0x01: return "ISA bridge"; - case 0x04: return "PCI bridge"; - default: return "Bridge"; - } - case 0x07: - switch(subclass_id) - { - case 0x00: return "Serial controller"; - case 0x80: return "Communication controller"; - } - break; - case 0x0C: - switch(subclass_id) - { - case 0x03: return "USB"; - case 0x05: return "System Management Bus"; - } - break; - } - return "Unknown"; -} -///__CONTROLLER___ + switch (class_id) { + case 0x00: + return (subclass_id == 0x01) ? "VGA" : "Legacy"; + case 0x01: + switch (subclass_id) { + case 0x01: + return "IDE interface"; + case 0x06: + return "SATA controller"; + default: + return "Storage"; + } + case 0x02: + return "Network"; + case 0x03: + return "Display"; + case 0x04: + switch (subclass_id) { + case 0x00: + return "Video"; + case 0x01: + case 0x03: + return "Audio"; + default: + return "Multimedia"; + } + case 0x06: + switch (subclass_id) { + case 0x00: + return "Host bridge"; + case 0x01: + return "ISA bridge"; + case 0x04: + return "PCI bridge"; + default: + return "Bridge"; + } + case 0x07: + switch (subclass_id) { + case 0x00: + return "Serial controller"; + case 0x80: + return "Communication controller"; + } + break; + case 0x0C: + switch (subclass_id) { + case 0x03: + return "USB"; + case 0x05: + return "System Management Bus"; + } + break; + } + return "Unknown"; +} PeripheralComponentInterconnectController::PeripheralComponentInterconnectController() : m_data_port(0xCFC), @@ -93,22 +102,23 @@ PeripheralComponentInterconnectController::~PeripheralComponentInterconnectContr * @param bus Bus number * @param device Device number * @param function Function number - * @param registeroffset Register offset + * @param register_offset Register offset * @return data from the PCI Controller */ -uint32_t PeripheralComponentInterconnectController::read(uint16_t bus, uint16_t device, uint16_t function, uint32_t registeroffset) { +uint32_t PeripheralComponentInterconnectController::read(uint16_t bus, uint16_t device, uint16_t function, + uint32_t register_offset) { - // Calculate the id - uint32_t id = 0x1 << 31 - | ((bus & 0xFF) << 16) - | ((device & 0x1F) << 11) - | ((function & 0x07) << 8) - | (registeroffset & 0xFC); - m_command_port.write(id); + // Calculate the id + uint32_t id = 0x1 << 31 + | ((bus & 0xFF) << 16) + | ((device & 0x1F) << 11) + | ((function & 0x07) << 8) + | (register_offset & 0xFC); + m_command_port.write(id); - // Read the data from the port - uint32_t result = m_data_port.read(); - return result >> (8* (registeroffset % 4)); + // Read the data from the port + uint32_t result = m_data_port.read(); + return result >> (8 * (register_offset % 4)); } @@ -118,21 +128,22 @@ uint32_t PeripheralComponentInterconnectController::read(uint16_t bus, uint16_t * @param bus Bus number * @param device Device number * @param function Function number - * @param registeroffset Register offset + * @param register_offset Register offset * @param value Value to write */ -void PeripheralComponentInterconnectController::write(uint16_t bus, uint16_t device, uint16_t function, uint32_t registeroffset, uint32_t value) { - - // Calculate the id - uint32_t id = 0x1 << 31 - | ((bus & 0xFF) << 16) - | ((device & 0x1F) << 11) - | ((function & 0x07) << 8) - | (registeroffset & 0xFC); - m_command_port.write(id); - - // Write the data to the port - m_data_port.write(value); +void PeripheralComponentInterconnectController::write(uint16_t bus, uint16_t device, uint16_t function, + uint32_t register_offset, uint32_t value) { + + // Calculate the id + uint32_t id = 0x1 << 31 + | ((bus & 0xFF) << 16) + | ((device & 0x1F) << 11) + | ((function & 0x07) << 8) + | (register_offset & 0xFC); + m_command_port.write(id); + + // Write the data to the port + m_data_port.write(value); } /** @@ -144,7 +155,7 @@ void PeripheralComponentInterconnectController::write(uint16_t bus, uint16_t dev */ bool PeripheralComponentInterconnectController::device_has_functions(uint16_t bus, uint16_t device) { - return read(bus, device, 0, 0x0E) & (1 << 7); + return read(bus, device, 0, 0x0E) & (1 << 7); } /** @@ -154,42 +165,44 @@ bool PeripheralComponentInterconnectController::device_has_functions(uint16_t bu * @param interrupt_manager Interrupt manager * @return Driver for the device */ -void PeripheralComponentInterconnectController::select_drivers(DriverSelectorEventHandler *handler) -{ - for (int bus = 0; bus < 8; ++bus) { - for (int device = 0; device < 32; ++device) { - - int numFunctions = (device_has_functions(bus, device)) ? 8 : 1; - - for (int function = 0; function < numFunctions; ++function) { - - // Get the device descriptor, if the vendor id is 0x0000 or 0xFFFF, the device is not present/ready - PeripheralComponentInterconnectDeviceDescriptor deviceDescriptor = get_device_descriptor(bus, device, function); - if(deviceDescriptor.vendor_id == 0x0000 || deviceDescriptor.vendor_id == 0x0001 || deviceDescriptor.vendor_id == 0xFFFF) - continue; - - // Get the earliest port number - for(int barNum = 5; barNum >= 0; barNum--){ - BaseAddressRegister bar = get_base_address_register(bus, device, function, barNum); - if(bar.address && (bar.type == BaseAddressRegisterType::InputOutput)) - deviceDescriptor.port_base = (uint64_t )bar.address; - } - - Logger::DEBUG() << "DEVICE FOUND: " << deviceDescriptor.get_type() << " - "; - - // Select the driver and print information about the device - Driver* driver = get_driver(deviceDescriptor); - if(driver != nullptr){ - handler->on_driver_selected(driver); - Logger::Out() << driver->vendor_name() << " " << driver->device_name(); - }else{ - list_known_device(deviceDescriptor); - } - - Logger::Out() << "\n"; - } - } - } +void PeripheralComponentInterconnectController::select_drivers(DriverSelectorEventHandler* handler) { + + for (int bus = 0; bus < 8; ++bus) { + for (int device = 0; device < 32; ++device) { + + int numFunctions = (device_has_functions(bus, device)) ? 8 : 1; + + for (int function = 0; function < numFunctions; ++function) { + + // Get the device descriptor, if the vendor id is 0x0000 or 0xFFFF, the device is not present/ready + PeripheralComponentInterconnectDeviceDescriptor deviceDescriptor = get_device_descriptor(bus, device, + function); + if (deviceDescriptor.vendor_id == 0x0000 || deviceDescriptor.vendor_id == 0x0001 || + deviceDescriptor.vendor_id == 0xFFFF) + continue; + + // Get the earliest port number + for (int barNum = 5; barNum >= 0; barNum--) { + BaseAddressRegister bar = get_base_address_register(bus, device, function, barNum); + if (bar.address && (bar.type == BaseAddressRegisterType::InputOutput)) + deviceDescriptor.port_base = (uint64_t) bar.address; + } + + Logger::DEBUG() << "DEVICE FOUND: " << deviceDescriptor.get_type() << " - "; + + // Select the driver and print information about the device + Driver* driver = get_driver(deviceDescriptor); + if (driver != nullptr) { + handler->on_driver_selected(driver); + Logger::Out() << driver->vendor_name() << " " << driver->device_name(); + } else { + list_known_device(deviceDescriptor); + } + + Logger::Out() << "\n"; + } + } + } } /** @@ -201,23 +214,24 @@ void PeripheralComponentInterconnectController::select_drivers(DriverSelectorEve * @return Device descriptor */ PeripheralComponentInterconnectDeviceDescriptor PeripheralComponentInterconnectController::get_device_descriptor(uint16_t bus, uint16_t device, uint16_t function) { - PeripheralComponentInterconnectDeviceDescriptor result; - result.bus = bus; - result.device = device; - result.function = function; + PeripheralComponentInterconnectDeviceDescriptor result; + + result.bus = bus; + result.device = device; + result.function = function; - result.vendor_id = read(bus, device, function, 0x00); - result.device_id = read(bus, device, function, 0x02); + result.vendor_id = read(bus, device, function, 0x00); + result.device_id = read(bus, device, function, 0x02); - result.class_id = read(bus, device, function, 0x0B); - result.subclass_id = read(bus, device, function, 0x0A); - result.interface_id = read(bus, device, function, 0x09); + result.class_id = read(bus, device, function, 0x0B); + result.subclass_id = read(bus, device, function, 0x0A); + result.interface_id = read(bus, device, function, 0x09); - result.revision = read(bus, device, function, 0x8); - result.interrupt = read(bus, device, function, 0x3C); + result.revision = read(bus, device, function, 0x8); + result.interrupt = read(bus, device, function, 0x3C); - return result; + return result; } /** @@ -229,197 +243,177 @@ PeripheralComponentInterconnectDeviceDescriptor PeripheralComponentInterconnectC */ Driver* PeripheralComponentInterconnectController::get_driver(PeripheralComponentInterconnectDeviceDescriptor dev) { - switch (dev.vendor_id) - { - case 0x1022: //AMD - { - switch (dev.device_id) - { - case 0x2000: - { - return new AMD_AM79C973(&dev); - - } - default: - break; - } - break; - } - case 0x8086: //Intel - { - switch (dev.device_id) - { - - case 0x100E: //i217 (Ethernet Controller) - { - return new intel_i217(&dev); - } - - case 0x7010: // PIIX4 (IDE Controller) - { - return new IntegratedDriveElectronicsController(&dev); - } - - default: - break; - } - break; - }//End Intel - } - - //If there is no driver for the particular device, go into generic devices - switch (dev.class_id) - { - case 0x03: //Graphics - { - - switch (dev.subclass_id) - { - case 0x00: //VGA - { - return new VideoGraphicsArray(); - } - } - break; - } - } - - return nullptr; + switch (dev.vendor_id) { + case 0x1022: //AMD + { + switch (dev.device_id) { + case 0x2000: { + return new AMD_AM79C973(&dev); + + } + default: + break; + } + break; + } + case 0x8086: //Intel + { + switch (dev.device_id) { + + case 0x100E: //i217 (Ethernet Controller) + { + return new intel_i217(&dev); + } + + case 0x7010: // PIIX4 (IDE Controller) + { + return new IntegratedDriveElectronicsController(&dev); + } + + default: + break; + } + break; + }//End Intel + } + + //If there is no driver for the particular device, go into generic devices + switch (dev.class_id) { + case 0x03: //Graphics + { + + switch (dev.subclass_id) { + case 0x00: //VGA + { + return new VideoGraphicsArray(); + } + } + break; + } + } + + return nullptr; } -void PeripheralComponentInterconnectController::list_known_device(const PeripheralComponentInterconnectDeviceDescriptor& dev) { - switch (dev.vendor_id) - { - case 0x1022: - { - // The vendor is AMD - Logger::Out() << "AMD "; - - // List the device - switch (dev.device_id) - { - default: - Logger::Out() << "0x%x" << dev.device_id; - break; - } - break; - } - - case 0x106B: - { - // The vendor is Apple - Logger::Out() << "Apple "; - - // List the device - switch (dev.device_id) - { - case 0x003F: - { - Logger::Out() << "KeyLargo/Intrepid USB"; - break; - } - - default: - Logger::Out() << "0x%x" << dev.device_id; - break; - } - break; - } - - case 1234: - { - // The vendor is QEMU - Logger::Out() << "QEMU "; - - // List the device - switch (dev.device_id) - { - - case 0x1111: - { - Logger::Out() << "Virtual Video Controller"; - break; - } - } - break; - } - - case 0x8086: - { - // The vendor is Intel - Logger::Out() << "Intel "; - - // List the device - switch (dev.device_id) - { - - case 0x1237: - { - Logger::Out() << "440FX"; - break; - } - - case 0x2415: - { - Logger::Out() << "AC'97"; - break; - } - - case 0x7000: - { - Logger::Out() << "PIIX3"; - break; - - } - - case 0x7111: - { - Logger::Out() << "PIIX3 ACPI"; - break; - } - - case 0x7113: - { - Logger::Out() << "PIIX4 ACPI"; - break; - } - - default: - Logger::Out() << "0x%x" << dev.device_id; - break; - - } - break; - } - - case 0x80EE: { - - // The vendor is VirtualBox - Logger::Out() << "VirtualBox "; - - // List the device - switch (dev.device_id) { - - case 0xBEEF: { - Logger::Out() << "Graphics Adapter"; - break; - } - - case 0xCAFE: { - Logger::Out() << "Guest Service"; - break; - } - } - break; - } - - // Unknown - default: - Logger::Out() << "Unknown (0x" << dev.vendor_id << ":0x" << dev.device_id << ")"; - break; - - } +void PeripheralComponentInterconnectController::list_known_device( + const PeripheralComponentInterconnectDeviceDescriptor& dev) { + + switch (dev.vendor_id) { + case 0x1022: { + // The vendor is AMD + Logger::Out() << "AMD "; + + // List the device + switch (dev.device_id) { + default: + Logger::Out() << "0x%x" << dev.device_id; + break; + } + break; + } + + case 0x106B: { + // The vendor is Apple + Logger::Out() << "Apple "; + + // List the device + switch (dev.device_id) { + case 0x003F: { + Logger::Out() << "KeyLargo/Intrepid USB"; + break; + } + + default: + Logger::Out() << "0x%x" << dev.device_id; + break; + } + break; + } + + case 1234: { + // The vendor is QEMU + Logger::Out() << "QEMU "; + + // List the device + switch (dev.device_id) { + + case 0x1111: { + Logger::Out() << "Virtual Video Controller"; + break; + } + } + break; + } + + case 0x8086: { + // The vendor is Intel + Logger::Out() << "Intel "; + + // List the device + switch (dev.device_id) { + + case 0x1237: { + Logger::Out() << "440FX"; + break; + } + + case 0x2415: { + Logger::Out() << "AC'97"; + break; + } + + case 0x7000: { + Logger::Out() << "PIIX3"; + break; + + } + + case 0x7111: { + Logger::Out() << "PIIX3 ACPI"; + break; + } + + case 0x7113: { + Logger::Out() << "PIIX4 ACPI"; + break; + } + + default: + Logger::Out() << "0x%x" << dev.device_id; + break; + + } + break; + } + + case 0x80EE: { + + // The vendor is VirtualBox + Logger::Out() << "VirtualBox "; + + // List the device + switch (dev.device_id) { + + case 0xBEEF: { + Logger::Out() << "Graphics Adapter"; + break; + } + + case 0xCAFE: { + Logger::Out() << "Guest Service"; + break; + } + } + break; + } + + // Unknown + default: + Logger::Out() << "Unknown (0x" << dev.vendor_id << ":0x" << dev.device_id << ")"; + break; + + } } /** @@ -433,25 +427,25 @@ void PeripheralComponentInterconnectController::list_known_device(const Peripher */ BaseAddressRegister PeripheralComponentInterconnectController::get_base_address_register(uint16_t bus, uint16_t device, uint16_t function, uint16_t bar) { - BaseAddressRegister result{}; + BaseAddressRegister result{}; - // Only types 0x00 (normal devices) and 0x01 (PCI-to-PCI bridges) are supported: - uint32_t headerType = read(bus, device, function, 0x0E); - if (headerType & 0x3F) - return result; + // Only types 0x00 (normal devices) and 0x01 (PCI-to-PCI bridges) are supported: + uint32_t headerType = read(bus, device, function, 0x0E); + if (headerType & 0x3F) + return result; - // read the base address register - uint64_t bar_value = read(bus, device, function, 0x10 + 4 * bar); - result.type = (bar_value & 0x1) ? BaseAddressRegisterType::InputOutput : BaseAddressRegisterType::MemoryMapped; - result.address = (uint8_t*) (bar_value & ~0xF); + // read the base address register + uint64_t bar_value = read(bus, device, function, 0x10 + 4 * bar); + result.type = (bar_value & 0x1) ? BaseAddressRegisterType::InputOutput : BaseAddressRegisterType::MemoryMapped; + result.address = (uint8_t*) (bar_value & ~0xF); - // Read the size of the base address register - write(bus, device, function, 0x10 + 4 * bar, 0xFFFFFFF0 | (int)result.type); - result.size = read(bus, device, function, 0x10 + 4 * bar); - result.size = (~result.size | 0xF) + 1; + // Read the size of the base address register + write(bus, device, function, 0x10 + 4 * bar, 0xFFFFFFF0 | (int) result.type); + result.size = read(bus, device, function, 0x10 + 4 * bar); + result.size = (~result.size | 0xF) + 1; - // Restore the original value of the base address register - write(bus, device, function, 0x10 + 4 * bar, bar_value); + // Restore the original value of the base address register + write(bus, device, function, 0x10 + 4 * bar, bar_value); - return result; + return result; } \ No newline at end of file diff --git a/kernel/src/hardwarecommunication/port.cpp b/kernel/src/hardwarecommunication/port.cpp index c5dbe5be..1ad2f427 100644 --- a/kernel/src/hardwarecommunication/port.cpp +++ b/kernel/src/hardwarecommunication/port.cpp @@ -11,11 +11,10 @@ Port::Port(uint16_t port_number) { } -Port::~Port()= default; +Port::~Port() = default; Port8Bit::Port8Bit(uint16_t port_number) -: Port(port_number) -{ +: Port(port_number) { } Port8Bit::~Port8Bit() = default; @@ -25,8 +24,9 @@ Port8Bit::~Port8Bit() = default; * * @param data the byte to write */ -void Port8Bit::write(uint8_t data){ - asm volatile("outb %0, %1" : : "a" (data), "Nd" (m_port_number)); +void Port8Bit::write(uint8_t data) { + + asm volatile("outb %0, %1" : : "a" (data), "Nd" (m_port_number)); } /** @@ -34,15 +34,15 @@ void Port8Bit::write(uint8_t data){ * * @return the byte read */ -uint8_t Port8Bit::read(){ - uint8_t result; - asm volatile("inb %1, %0" : "=a" (result) : "Nd" (m_port_number)); - return result; +uint8_t Port8Bit::read() { + + uint8_t result; + asm volatile("inb %1, %0" : "=a" (result) : "Nd" (m_port_number)); + return result; } Port8BitSlow::Port8BitSlow(uint16_t port_number) -: Port8Bit(port_number) -{ +: Port8Bit(port_number) { } Port8BitSlow::~Port8BitSlow() = default; @@ -52,14 +52,14 @@ Port8BitSlow::~Port8BitSlow() = default; * * @param data the byte to write */ -void Port8BitSlow::write(uint8_t data){ - asm volatile("outb %0, %1\njmp 1f\n1: jmp 1f\n1:" : : "a" (data), "Nd" (m_port_number)); +void Port8BitSlow::write(uint8_t data) { + + asm volatile("outb %0, %1\njmp 1f\n1: jmp 1f\n1:" : : "a" (data), "Nd" (m_port_number)); } Port16Bit::Port16Bit(uint16_t port_number) -: Port(port_number) -{ +: Port(port_number) { } Port16Bit::~Port16Bit() = default; @@ -69,8 +69,9 @@ Port16Bit::~Port16Bit() = default; * * @param data the word to write */ -void Port16Bit::write(uint16_t data){ - asm volatile("outw %0, %1" : : "a" (data), "Nd" (m_port_number)); +void Port16Bit::write(uint16_t data) { + + asm volatile("outw %0, %1" : : "a" (data), "Nd" (m_port_number)); } /** @@ -78,16 +79,16 @@ void Port16Bit::write(uint16_t data){ * * @return the word read */ -uint16_t Port16Bit::read(){ - uint16_t result; - asm volatile("inw %1, %0" : "=a" (result) : "Nd" (m_port_number)); - return result; +uint16_t Port16Bit::read() { + + uint16_t result; + asm volatile("inw %1, %0" : "=a" (result) : "Nd" (m_port_number)); + return result; } Port32Bit::Port32Bit(uint16_t port_number) -: Port(port_number) -{ +: Port(port_number) { } Port32Bit::~Port32Bit() = default; @@ -97,8 +98,9 @@ Port32Bit::~Port32Bit() = default; * * @param data the double word to write */ -void Port32Bit::write(uint32_t data){ - asm volatile("outl %0, %1" : : "a" (data), "Nd" (m_port_number)); +void Port32Bit::write(uint32_t data) { + + asm volatile("outl %0, %1" : : "a" (data), "Nd" (m_port_number)); } /** @@ -106,8 +108,9 @@ void Port32Bit::write(uint32_t data){ * * @return the double word read */ -uint32_t Port32Bit::read(){ - uint32_t result; - asm volatile("inl %1, %0" : "=a" (result) : "Nd" (m_port_number)); - return result; +uint32_t Port32Bit::read() { + + uint32_t result; + asm volatile("inl %1, %0" : "=a" (result) : "Nd" (m_port_number)); + return result; } \ No newline at end of file diff --git a/kernel/src/kernel.cpp b/kernel/src/kernel.cpp index f35f54cf..3e72a5ff 100644 --- a/kernel/src/kernel.cpp +++ b/kernel/src/kernel.cpp @@ -1,7 +1,5 @@ -//Common #include #include -#include #include #include #include @@ -16,7 +14,6 @@ #include #include - using namespace MaxOS; using namespace MaxOS::common; using namespace MaxOS::drivers; @@ -32,65 +29,56 @@ using namespace MaxOS::memory; using namespace MaxOS::filesystem; extern "C" void call_constructors(); -extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic) -{ - call_constructors(); +extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic) { - // Initialise the logger - Logger logger; - SerialConsole serial_console(&logger); - Logger::INFO() << "MaxOS Booted Successfully \n"; + call_constructors(); - Logger::HEADER() << "Stage {1}: System Initialisation\n"; - Multiboot multiboot(addr, magic); - GlobalDescriptorTable gdt; - InterruptManager interrupts; + // Initialise the logger + Logger logger; + SerialConsole serial_console(&logger); + Logger::INFO() << "MaxOS Booted Successfully \n"; - Logger::HEADER() << "Stage {1.1}: Memory Initialisation\n"; - PhysicalMemoryManager pmm(&multiboot); - VirtualMemoryManager vmm; - MemoryManager memoryManager(&vmm); + Logger::HEADER() << "Stage {1}: System Initialisation\n"; + Multiboot multiboot(addr, magic); + GlobalDescriptorTable gdt; + InterruptManager interrupts; - Logger::HEADER() << "Stage {1.2}: Console Initialisation\n"; - VideoElectronicsStandardsAssociation vesa(multiboot.framebuffer()); - VESABootConsole console(&vesa); + Logger::HEADER() << "Stage {1.1}: Memory Initialisation\n"; + PhysicalMemoryManager pmm(&multiboot); + VirtualMemoryManager vmm; + MemoryManager memoryManager(&vmm); - Logger::HEADER() << "Stage {2}: Hardware Initialisation\n"; - VirtualFileSystem vfs; - CPU cpu(&gdt, &multiboot); - Clock kernel_clock(cpu.apic, 1); - DriverManager driver_manager; - driver_manager.add_driver(&kernel_clock); - driver_manager.find_drivers(); - uint32_t reset_wait_time = driver_manager.reset_devices(); + Logger::HEADER() << "Stage {1.2}: Console Initialisation\n"; + VideoElectronicsStandardsAssociation vesa(multiboot.framebuffer()); + VESABootConsole console(&vesa); - Logger::HEADER() << "Stage {3}: Device Finalisation\n"; - interrupts.activate(); - kernel_clock.calibrate(); - kernel_clock.delay(reset_wait_time); - driver_manager.initialise_drivers(); - driver_manager.activate_drivers(); + Logger::HEADER() << "Stage {2}: Hardware Initialisation\n"; + VirtualFileSystem vfs; + CPU cpu(&gdt, &multiboot); + Clock kernel_clock(cpu.apic, 1); + DriverManager driver_manager; + driver_manager.add_driver(&kernel_clock); + driver_manager.find_drivers(); + uint32_t reset_wait_time = driver_manager.reset_devices(); - auto dir = vfs.create_directory("/test/bob"); - auto file = vfs.create_file("/test/bob/file.txt"); - if(!dir || !file) - Logger::ERROR() << "Failed to create test directory or file\n"; - else - Logger::INFO() << "Created test directory and file\n"; + Logger::HEADER() << "Stage {3}: Device Finalisation\n"; + interrupts.activate(); + kernel_clock.calibrate(); + kernel_clock.delay(reset_wait_time); + driver_manager.initialise_drivers(); + driver_manager.activate_drivers(); Logger::HEADER() << "Stage {4}: System Finalisation\n"; - Scheduler scheduler(multiboot); - SyscallManager syscalls; - console.finish(); - scheduler.activate(); + Scheduler scheduler(multiboot); + SyscallManager syscalls; + console.finish(); + scheduler.activate(); - // Idle loop (read Idle.md) - while (true) - asm("nop"); + // Idle loop (read Idle.md) + while (true) + asm("nop"); } -// - Fix multiple def where could be a parameter (idk why I needed two functions for that) -// - Fix tabs (mac mess up) // - Userspace Files (syscalls, proper path handling, working directories, file handles) // - Class & Struct docstrings // - Logo on fail in center \ No newline at end of file diff --git a/kernel/src/memory/memoryIO.cpp b/kernel/src/memory/memoryIO.cpp index 97224c3a..e77c386c 100644 --- a/kernel/src/memory/memoryIO.cpp +++ b/kernel/src/memory/memoryIO.cpp @@ -26,8 +26,9 @@ MemIO8Bit::~MemIO8Bit() = default; * * @param data the data to write */ -void MemIO8Bit::write(uint8_t data){ - (*((volatile uint8_t*)(m_address)))=(data); +void MemIO8Bit::write(uint8_t data) { + + (*((volatile uint8_t*) (m_address))) = (data); } /** @@ -35,8 +36,9 @@ void MemIO8Bit::write(uint8_t data){ * * @return the data read */ -uint8_t MemIO8Bit::read(){ - return *((volatile uint8_t*)(m_address)); +uint8_t MemIO8Bit::read() { + + return *((volatile uint8_t*) (m_address)); } MemIO16Bit::MemIO16Bit(uintptr_t address) @@ -51,8 +53,9 @@ MemIO16Bit::~MemIO16Bit() = default; * * @param data the data to write */ -void MemIO16Bit::write(uint16_t data){ - (*((volatile uint16_t*)(m_address)))=(data); +void MemIO16Bit::write(uint16_t data) { + + (*((volatile uint16_t*) (m_address))) = (data); } /** @@ -60,12 +63,13 @@ void MemIO16Bit::write(uint16_t data){ * * @return the data read */ -uint16_t MemIO16Bit::read(){ - return *((volatile uint16_t*)(m_address)); +uint16_t MemIO16Bit::read() { + + return *((volatile uint16_t*) (m_address)); } MemIO32Bit::MemIO32Bit(uintptr_t address) - : MemIO(address) +: MemIO(address) { } @@ -76,8 +80,9 @@ MemIO32Bit::~MemIO32Bit() = default; * * @param data the data to write */ -void MemIO32Bit::write(uint32_t data){ - (*((volatile uint32_t*)(m_address)))=(data); +void MemIO32Bit::write(uint32_t data) { + + (*((volatile uint32_t*) (m_address))) = (data); } /** @@ -85,8 +90,9 @@ void MemIO32Bit::write(uint32_t data){ * * @return the data read */ -uint32_t MemIO32Bit::read(){ - return *((volatile uint32_t*)(m_address)); +uint32_t MemIO32Bit::read() { + + return *((volatile uint32_t*) (m_address)); } MemIO64Bit::MemIO64Bit(uintptr_t address) @@ -101,8 +107,9 @@ MemIO64Bit::~MemIO64Bit() = default; * * @param data the data to write */ -void MemIO64Bit::write(uint64_t data){ - (*((volatile uint64_t*)(m_address)))=(data); +void MemIO64Bit::write(uint64_t data) { + + (*((volatile uint64_t*) (m_address))) = (data); } /** @@ -110,8 +117,9 @@ void MemIO64Bit::write(uint64_t data){ * * @return the data read */ -uint64_t MemIO64Bit::read(){ - return *((volatile uint64_t*)(m_address)); +uint64_t MemIO64Bit::read() { + + return *((volatile uint64_t*) (m_address)); } @@ -127,29 +135,28 @@ uint64_t MemIO64Bit::read(){ */ void* memcpy(void* destination, const void* source, uint64_t num) { - // Make sure the source and destination are not the same - if (destination == source) - return destination; + // Make sure the source and destination are not the same + if (destination == source) + return destination; - // Make sure they exist - if (destination == nullptr || source == nullptr) - return destination; + // Make sure they exist + if (destination == nullptr || source == nullptr) + return destination; - // Get the source and destination - auto* dst = (unsigned char*) destination; - const auto* src = (const unsigned char*) source; + // Get the source and destination + auto* dst = (unsigned char*) destination; + const auto* src = (const unsigned char*) source; - // Copy the data - for (size_t i = 0; i < num; i++) - dst[i] = src[i]; + // Copy the data + for (size_t i = 0; i < num; i++) + dst[i] = src[i]; - // Usefully for easier code writing - return destination; + // Usefully for easier code writing + return destination; } /** * @brief Fills a block of memory with a specified value - * * @param ptr The pointer to the block of memory * @param value The value to fill the block of memory with @@ -158,14 +165,14 @@ void* memcpy(void* destination, const void* source, uint64_t num) { */ void* memset(void* ptr, uint32_t value, uint64_t num) { - // Make sure the pointer exists - if (ptr == nullptr) - return ptr; + // Make sure the pointer exists + if (ptr == nullptr) + return ptr; - auto* dst = (unsigned char*) ptr; - for (size_t i = 0; i < num; i++) - dst[i] = (unsigned char) value; - return ptr; + auto* dst = (unsigned char*) ptr; + for (size_t i = 0; i < num; i++) + dst[i] = (unsigned char) value; + return ptr; } /** @@ -178,24 +185,24 @@ void* memset(void* ptr, uint32_t value, uint64_t num) { */ void* memmove(void* destination, const void* source, uint64_t num) { - // Make sure the source and destination are not the same - if (destination == source) - return destination; + // Make sure the source and destination are not the same + if (destination == source) + return destination; - // Make sure they exist - if (destination == nullptr || source == nullptr) - return destination; + // Make sure they exist + if (destination == nullptr || source == nullptr) + return destination; - auto* dst = (unsigned char*) destination; - const auto* src = (const unsigned char*) source; - if (dst < src) { - for (size_t i = 0; i < num; i++) - dst[i] = src[i]; - } else { - for (size_t i = num; i != 0; i--) - dst[i-1] = src[i-1]; - } - return destination; + auto* dst = (unsigned char*) destination; + const auto* src = (const unsigned char*) source; + if (dst < src) { + for (size_t i = 0; i < num; i++) + dst[i] = src[i]; + } else { + for (size_t i = num; i != 0; i--) + dst[i - 1] = src[i - 1]; + } + return destination; } /** @@ -208,17 +215,17 @@ void* memmove(void* destination, const void* source, uint64_t num) { */ int memcmp(const void* ptr1, const void* ptr2, uint64_t num) { - // Make sure the pointers exist - if (ptr1 == nullptr || ptr2 == nullptr) - return 0; - - const auto *p1 = (const unsigned char *)ptr1; - const auto *p2 = (const unsigned char *)ptr2; - for (size_t i = 0; i < num; i++) { - if (p1[i] < p2[i]) - return -1; - if (p1[i] > p2[i]) - return 1; - } - return 0; + // Make sure the pointers exist + if (ptr1 == nullptr || ptr2 == nullptr) + return 0; + + const auto* p1 = (const unsigned char*) ptr1; + const auto* p2 = (const unsigned char*) ptr2; + for (size_t i = 0; i < num; i++) { + if (p1[i] < p2[i]) + return -1; + if (p1[i] > p2[i]) + return 1; + } + return 0; } \ No newline at end of file diff --git a/kernel/src/memory/memorymanagement.cpp b/kernel/src/memory/memorymanagement.cpp index 7b566cda..362c563b 100644 --- a/kernel/src/memory/memorymanagement.cpp +++ b/kernel/src/memory/memorymanagement.cpp @@ -14,42 +14,40 @@ MemoryManager::MemoryManager(VirtualMemoryManager* vmm) : m_virtual_memory_manager(vmm) { - // Create the VMM if not provided - if(m_virtual_memory_manager == nullptr) - m_virtual_memory_manager = new VirtualMemoryManager(); + // Create the VMM if not provided + if (m_virtual_memory_manager == nullptr) + m_virtual_memory_manager = new VirtualMemoryManager(); - // Enable the memory manager - switch_active_memory_manager(this); + // Enable the memory manager + switch_active_memory_manager(this); - // Setup the first chunk of memory - this -> m_first_memory_chunk = (MemoryChunk*)m_virtual_memory_manager->allocate(PhysicalMemoryManager::s_page_size + sizeof(MemoryChunk), 0); - m_first_memory_chunk-> allocated = false; - m_first_memory_chunk-> prev = nullptr; - m_first_memory_chunk-> next = nullptr; - m_first_memory_chunk-> size = PhysicalMemoryManager::s_page_size - sizeof(MemoryChunk); - m_last_memory_chunk = m_first_memory_chunk; + // Setup the first chunk of memory + this->m_first_memory_chunk = (MemoryChunk*) m_virtual_memory_manager->allocate(PhysicalMemoryManager::s_page_size + sizeof(MemoryChunk), 0); + m_first_memory_chunk->allocated = false; + m_first_memory_chunk->prev = nullptr; + m_first_memory_chunk->next = nullptr; + m_first_memory_chunk->size = PhysicalMemoryManager::s_page_size - sizeof(MemoryChunk); + m_last_memory_chunk = m_first_memory_chunk; - // First memory manager is the kernel memory manager - if(s_kernel_memory_manager == nullptr) - s_kernel_memory_manager = this; + // First memory manager is the kernel memory manager + if (s_kernel_memory_manager == nullptr) + s_kernel_memory_manager = this; } MemoryManager::~MemoryManager() { + // Free the VMM (if this is not the kernel memory manager) + if (m_virtual_memory_manager != nullptr && s_current_memory_manager != s_kernel_memory_manager) + delete m_virtual_memory_manager; - // Free the VMM (if this is not the kernel memory manager) - if(m_virtual_memory_manager != nullptr && s_current_memory_manager != s_kernel_memory_manager) - delete m_virtual_memory_manager; - - // Remove the kernel reference to this - if(s_kernel_memory_manager == this) - s_kernel_memory_manager = nullptr; - - // Remove the active reference to this - if(s_current_memory_manager == this) - s_current_memory_manager = nullptr; + // Remove the kernel reference to this + if (s_kernel_memory_manager == this) + s_kernel_memory_manager = nullptr; + // Remove the active reference to this + if (s_current_memory_manager == this) + s_current_memory_manager = nullptr; } /** @@ -60,12 +58,11 @@ MemoryManager::~MemoryManager() { */ void* MemoryManager::malloc(size_t size) { - // Make sure there is somthing to do the allocation - if(s_current_memory_manager == nullptr) - return nullptr; - - return s_current_memory_manager -> handle_malloc(size); + // Make sure there is somthing to do the allocation + if (s_current_memory_manager == nullptr) + return nullptr; + return s_current_memory_manager->handle_malloc(size); } /** @@ -74,14 +71,13 @@ void* MemoryManager::malloc(size_t size) { * @param size The size of the block * @return The pointer to the block, or nullptr if no block is available */ -void *MemoryManager::kmalloc(size_t size) { +void* MemoryManager::kmalloc(size_t size) { - // Make sure there is a kernel memory manager - if(s_kernel_memory_manager == nullptr) - return nullptr; - - return s_kernel_memory_manager->handle_malloc(size); + // Make sure there is a kernel memory manager + if (s_kernel_memory_manager == nullptr) + return nullptr; + return s_kernel_memory_manager->handle_malloc(size); } /** @@ -90,56 +86,57 @@ void *MemoryManager::kmalloc(size_t size) { * @param size The size of the block to allocate * @return A pointer to the block, or nullptr if no block is available */ -void *MemoryManager::handle_malloc(size_t size) { - MemoryChunk* result = nullptr; - - // Nothing to allocate - if(size == 0) - return nullptr; - - // Add room to store the chunk information - size = align(size + sizeof(MemoryChunk)); - - // Find the next free chunk that is big enough - for (MemoryChunk* chunk = m_first_memory_chunk; chunk != nullptr && result == nullptr; chunk = chunk->next) { - if(chunk -> size > size && !chunk -> allocated) - result = chunk; - } - - // If there is no free chunk then make more room - if(result == nullptr) - result = expand_heap(size); - - // If there is not left over space to store extra chunks there is no need to split the chunk - if(result -> size < size + sizeof(MemoryChunk) + 1) { - result->allocated = true; - void* p = (void *)(((size_t)result) + sizeof(MemoryChunk)); - return p; - } - - // Split the chunk into: what was requested + free overflow space for future allocates - // - This prevents waste in the event that a big free chunk was found but the requested size would only use a portion of that - auto* extra = (MemoryChunk*)((size_t)result + sizeof(MemoryChunk) + size); - extra -> allocated = false; - extra -> size = result->size - size - sizeof(MemoryChunk); - extra -> prev = result; - - // Add to the linked list - extra -> next = result -> next; - if(extra -> next != nullptr) - extra -> next -> prev = extra; - - // Requested chunk is now allocated exactly to the size requested and points to the free (split) block of memory that - // it did not use - result -> size = size; - result -> allocated = true; - result -> next = extra; - - // Update the last memory chunk if necessary - if(result == m_last_memory_chunk) - m_last_memory_chunk = extra; - - return (void*)(((size_t)result) + sizeof(MemoryChunk)); +void* MemoryManager::handle_malloc(size_t size) { + + MemoryChunk* result = nullptr; + + // Nothing to allocate + if (size == 0) + return nullptr; + + // Add room to store the chunk information + size = align(size + sizeof(MemoryChunk)); + + // Find the next free chunk that is big enough + for (MemoryChunk* chunk = m_first_memory_chunk; chunk != nullptr && result == nullptr; chunk = chunk->next) { + if (chunk->size > size && !chunk->allocated) + result = chunk; + } + + // If there is no free chunk then make more room + if (result == nullptr) + result = expand_heap(size); + + // If there is not left over space to store extra chunks there is no need to split the chunk + if (result->size < size + sizeof(MemoryChunk) + 1) { + result->allocated = true; + void* p = (void*) (((size_t) result) + sizeof(MemoryChunk)); + return p; + } + + // Split the chunk into: what was requested + free overflow space for future allocates + // - This prevents waste in the event that a big free chunk was found but the requested size would only use a portion of that + auto* extra = (MemoryChunk*) ((size_t) result + sizeof(MemoryChunk) + size); + extra->allocated = false; + extra->size = result->size - size - sizeof(MemoryChunk); + extra->prev = result; + + // Add to the linked list + extra->next = result->next; + if (extra->next != nullptr) + extra->next->prev = extra; + + // Requested chunk is now allocated exactly to the size requested and points to the free (split) block of memory that + // it did not use + result->size = size; + result->allocated = true; + result->next = extra; + + // Update the last memory chunk if necessary + if (result == m_last_memory_chunk) + m_last_memory_chunk = extra; + + return (void*) (((size_t) result) + sizeof(MemoryChunk)); } /** @@ -147,14 +144,13 @@ void *MemoryManager::handle_malloc(size_t size) { * * @param pointer The pointer to the block */ -void MemoryManager::free(void *pointer) { - - // Make sure there is a memory manager - if(s_current_memory_manager == nullptr) - return; +void MemoryManager::free(void* pointer) { - s_current_memory_manager->handle_free(pointer); + // Make sure there is a memory manager + if (s_current_memory_manager == nullptr) + return; + s_current_memory_manager->handle_free(pointer); } /** @@ -162,14 +158,13 @@ void MemoryManager::free(void *pointer) { * * @param pointer The pointer to the block */ -void MemoryManager::kfree(void *pointer) { - - // Make sure there is a kernel memory manager - if(s_kernel_memory_manager == nullptr) - return; +void MemoryManager::kfree(void* pointer) { - s_kernel_memory_manager->handle_free(pointer); + // Make sure there is a kernel memory manager + if (s_kernel_memory_manager == nullptr) + return; + s_kernel_memory_manager->handle_free(pointer); } /** @@ -177,49 +172,49 @@ void MemoryManager::kfree(void *pointer) { * * @param pointer A pointer to the block */ -void MemoryManager::handle_free(void *pointer) { +void MemoryManager::handle_free(void* pointer) { - // Cant free unallocated memory - if(pointer == nullptr) - return; + // Cant free unallocated memory + if (pointer == nullptr) + return; - // Check bounds - if((uint64_t ) pointer < (uint64_t ) m_first_memory_chunk || (uint64_t ) pointer > (uint64_t ) m_last_memory_chunk) - return; + // Check bounds + if ((uint64_t) pointer < (uint64_t) m_first_memory_chunk || (uint64_t) pointer > (uint64_t) m_last_memory_chunk) + return; - // Get the chunk information from the pointer - auto* chunk = (MemoryChunk*)((size_t)pointer - sizeof(MemoryChunk)); - chunk -> allocated = false; + // Get the chunk information from the pointer + auto* chunk = (MemoryChunk*) ((size_t) pointer - sizeof(MemoryChunk)); + chunk->allocated = false; - // If there is a free chunk before this chunk then merge them - if(chunk -> prev != nullptr && !chunk -> prev -> allocated){ + // If there is a free chunk before this chunk then merge them + if (chunk->prev != nullptr && !chunk->prev->allocated) { - // Grow the chunk behind this one so that it now contains the freed one - chunk -> prev -> size += chunk -> size + sizeof(MemoryChunk); - chunk -> prev -> next = chunk -> next; + // Grow the chunk behind this one so that it now contains the freed one + chunk->prev->size += chunk->size + sizeof(MemoryChunk); + chunk->prev->next = chunk->next; - // The chunk in front of the freed one now needs to point to the merged chunk - if(chunk -> next != nullptr) - chunk -> next -> prev = chunk->prev; + // The chunk in front of the freed one now needs to point to the merged chunk + if (chunk->next != nullptr) + chunk->next->prev = chunk->prev; - // Freed chunk doesn't exist anymore so now working with the merged chunk - chunk = chunk -> prev; + // Freed chunk doesn't exist anymore so now working with the merged chunk + chunk = chunk->prev; - } + } - // If there is a free chunk after this chunk then merge them - if(chunk -> next != nullptr && !chunk -> next -> allocated){ + // If there is a free chunk after this chunk then merge them + if (chunk->next != nullptr && !chunk->next->allocated) { - // Grow this chunk so that it now contains the free chunk in front of the old (now freed) one - chunk -> size += chunk -> next -> size + sizeof(MemoryChunk); + // Grow this chunk so that it now contains the free chunk in front of the old (now freed) one + chunk->size += chunk->next->size + sizeof(MemoryChunk); - // Now that this chunk contains the next one, it has to point to the one in front of what has just been merged - // and that has to point to this - chunk -> next = chunk -> next -> next; - if(chunk -> next != nullptr) - chunk -> next -> prev = chunk; + // Now that this chunk contains the next one, it has to point to the one in front of what has just been merged + // and that has to point to this + chunk->next = chunk->next->next; + if (chunk->next != nullptr) + chunk->next->prev = chunk; - } + } } /** @@ -228,36 +223,34 @@ void MemoryManager::handle_free(void *pointer) { * @param size The size to expand the heap by * @return The new chunk of memory */ -MemoryChunk *MemoryManager::expand_heap(size_t size) { +MemoryChunk* MemoryManager::expand_heap(size_t size) { - // Create a new chunk of memory - auto* chunk = (MemoryChunk*)m_virtual_memory_manager->allocate(size, Present | Write | NoExecute); - ASSERT(chunk != nullptr, "Out of memory - kernel cannot allocate any more memory"); + // Create a new chunk of memory + auto* chunk = (MemoryChunk*) m_virtual_memory_manager->allocate(size, Present | Write | NoExecute); + ASSERT(chunk != nullptr, "Out of memory - kernel cannot allocate any more memory"); - // Handled by assert, but just in case - if(chunk == nullptr) - return nullptr; + // Handled by assert, but just in case + if (chunk == nullptr) + return nullptr; - // Set the chunk's properties - chunk -> allocated = false; - chunk -> size = size; - chunk -> next = nullptr; + // Set the chunk's properties + chunk->allocated = false; + chunk->size = size; + chunk->next = nullptr; - // Insert the chunk into the linked list - m_last_memory_chunk -> next = chunk; - chunk -> prev = m_last_memory_chunk; - m_last_memory_chunk = chunk; + // Insert the chunk into the linked list + m_last_memory_chunk->next = chunk; + chunk->prev = m_last_memory_chunk; + m_last_memory_chunk = chunk; - // If it is possible to merge the new chunk with the previous chunk then do so (note: this happens if the - // previous chunk is free but cant contain the size required) - if(!chunk -> prev -> allocated) - free((void*)((size_t)chunk + sizeof(MemoryChunk))); - - return chunk; + // If it is possible to merge the new chunk with the previous chunk then do so (note: this happens if the + // previous chunk is free but cant contain the size required) + if (!chunk->prev->allocated) + free((void*) ((size_t) chunk + sizeof(MemoryChunk))); + return chunk; } - /** * @brief Returns the amount of memory used * @@ -265,14 +258,14 @@ MemoryChunk *MemoryManager::expand_heap(size_t size) { */ int MemoryManager::memory_used() { - int result = 0; + int result = 0; - // Loop through all the chunks and add up the size of the allocated chunks - for (MemoryChunk* chunk = m_first_memory_chunk; chunk != nullptr; chunk = chunk->next) - if(chunk -> allocated) - result += chunk -> size; + // Loop through all the chunks and add up the size of the allocated chunks + for (MemoryChunk* chunk = m_first_memory_chunk; chunk != nullptr; chunk = chunk->next) + if (chunk->allocated) + result += chunk->size; - return result; + return result; } /** @@ -282,10 +275,9 @@ int MemoryManager::memory_used() { * @return The aligned size */ size_t MemoryManager::align(size_t size) { - return (size / s_chunk_alignment + 1) * s_chunk_alignment; -} - + return (size / s_chunk_alignment + 1) * s_chunk_alignment; +} /** @@ -295,16 +287,15 @@ size_t MemoryManager::align(size_t size) { */ void MemoryManager::switch_active_memory_manager(MemoryManager* manager) { - // Make sure there is a manager - if(manager == nullptr) - return; + // Make sure there is a manager + if (manager == nullptr) + return; - // Switch the address space - asm volatile("mov %0, %%cr3" :: "r"((uint64_t)manager->m_virtual_memory_manager->pml4_root_address_physical()) : "memory"); - - // Set the active memory manager - s_current_memory_manager = manager; + // Switch the address space + asm volatile("mov %0, %%cr3"::"r"((uint64_t) manager->m_virtual_memory_manager->pml4_root_address_physical()) : "memory"); + // Set the active memory manager + s_current_memory_manager = manager; } /** @@ -314,9 +305,7 @@ void MemoryManager::switch_active_memory_manager(MemoryManager* manager) { */ VirtualMemoryManager* MemoryManager::vmm() { - // Return the virtual memory manager - return m_virtual_memory_manager; - + return m_virtual_memory_manager; } //Redefine the default object functions with memory orientated ones (defaults disabled in makefile) @@ -328,11 +317,10 @@ VirtualMemoryManager* MemoryManager::vmm() { * @param size The size of the memory to allocate * @return The pointer to the allocated memory */ -void* operator new(size_t size) throw(){ - - // Handle the memory allocation - return MaxOS::memory::MemoryManager::kmalloc(size); +void* operator new(size_t size) throw() { + // Handle the memory allocation + return MaxOS::memory::MemoryManager::kmalloc(size); } /** @@ -341,12 +329,11 @@ void* operator new(size_t size) throw(){ * @param size The size of the memory to allocate * @return The pointer to the allocated memory */ -void* operator new[](size_t size) throw(){ - - // Handle the memory allocation - void* p = MaxOS::memory::MemoryManager::kmalloc(size); - return p; +void* operator new[](size_t size) throw() { + // Handle the memory allocation + void* p = MaxOS::memory::MemoryManager::kmalloc(size); + return p; } /** @@ -355,10 +342,9 @@ void* operator new[](size_t size) throw(){ * @param pointer The pointer to the memory to allocate * @return The pointer to the memory */ -void* operator new(size_t, void* pointer){ - - return pointer; +void* operator new(size_t, void* pointer) { + return pointer; } /** @@ -367,11 +353,10 @@ void* operator new(size_t, void* pointer){ * @param pointer The pointer to the memory to allocate * @return The pointer to the memory */ -void* operator new[](size_t, void* pointer){ - +void* operator new[](size_t, void* pointer) { - return pointer; + return pointer; } /** @@ -379,11 +364,10 @@ void* operator new[](size_t, void* pointer){ * * @param pointer The pointer to the memory to free */ -void operator delete(void* pointer){ - - // Handle the memory freeing - return MaxOS::memory::MemoryManager::kfree(pointer); +void operator delete(void* pointer) { + // Handle the memory freeing + return MaxOS::memory::MemoryManager::kfree(pointer); } /** @@ -391,25 +375,22 @@ void operator delete(void* pointer){ * * @param pointer The pointer to the memory to free */ -void operator delete[](void* pointer){ - - // Handle the memory freeing - return MaxOS::memory::MemoryManager::kfree(pointer); +void operator delete[](void* pointer) { + // Handle the memory freeing + return MaxOS::memory::MemoryManager::kfree(pointer); } - /** * @brief Overloaded delete operator, frees memory using the KERNEL memory manager * * @param pointer The pointer to the memory to free * @param size The size of the memory to free - ignored in this implementation */ -void operator delete(void* pointer, size_t){ - - // Handle the memory freeing - return MaxOS::memory::MemoryManager::kfree(pointer); +void operator delete(void* pointer, size_t) { + // Handle the memory freeing + return MaxOS::memory::MemoryManager::kfree(pointer); } /** @@ -417,9 +398,8 @@ void operator delete(void* pointer, size_t){ * * @param pointer The pointer to the memory to free */ -void operator delete[](void* pointer, size_t){ - - // Handle the memory freeing - return MaxOS::memory::MemoryManager::kfree(pointer); +void operator delete[](void* pointer, size_t) { + // Handle the memory freeing + return MaxOS::memory::MemoryManager::kfree(pointer); } \ No newline at end of file diff --git a/kernel/src/memory/physical.cpp b/kernel/src/memory/physical.cpp index b3576d9b..c606a74b 100644 --- a/kernel/src/memory/physical.cpp +++ b/kernel/src/memory/physical.cpp @@ -6,7 +6,6 @@ #include #include - using namespace MaxOS::memory; using namespace MaxOS::system; using namespace MaxOS::common; @@ -22,103 +21,103 @@ extern uint64_t _kernel_size; extern uint64_t _kernel_physical_end; PhysicalMemoryManager::PhysicalMemoryManager(Multiboot* multiboot) -: m_kernel_end((uint64_t)&_kernel_physical_end), +: m_kernel_end((uint64_t) &_kernel_physical_end), m_multiboot(multiboot), - m_pml4_root_address((uint64_t*)p4_table), - m_pml4_root((pte_t *)p4_table) + m_pml4_root_address((uint64_t*) p4_table), + m_pml4_root((pte_t*) p4_table) { - Logger::INFO() << "Setting up Physical Memory Manager\n"; - Logger::DEBUG() << "Kernel Memory: kernel_end = 0x" << (uint64_t)&_kernel_end << ", kernel_size = 0x" << (uint64_t)&_kernel_size << ", kernel_physical_end = 0x" << (uint64_t)&_kernel_physical_end << "\n"; + Logger::INFO() << "Setting up Physical Memory Manager\n"; + Logger::DEBUG() << "Kernel Memory: kernel_end = 0x" << (uint64_t) &_kernel_end << ", kernel_size = 0x" << (uint64_t) &_kernel_size << ", kernel_physical_end = 0x" << (uint64_t) &_kernel_physical_end << "\n"; - // Set up the current manager - unmap_lower_kernel(); - m_lock.unlock(); - s_current_manager = this; - m_nx_allowed = CPU::check_nx(); + // Set up the current manager + unmap_lower_kernel(); + m_lock.unlock(); + s_current_manager = this; + m_nx_allowed = CPU::check_nx(); - // Store the information about the bitmap - m_memory_size = (m_multiboot->basic_meminfo()->mem_upper + 1024) * 1024; - m_bitmap_size = m_memory_size / s_page_size + 1; - m_total_entries = m_bitmap_size / s_row_bits + 1; - Logger::DEBUG() << "Memory Info: size = " << (int)(m_memory_size / 1024 / 1024) << "mb, bitmap size = 0x" << (uint64_t)m_bitmap_size << ", total entries = " << (int)m_total_entries << ", page size = 0x" << (uint64_t)s_page_size << "\n"; + // Store the information about the bitmap + m_memory_size = (m_multiboot->basic_meminfo()->mem_upper + 1024) * 1024; + m_bitmap_size = m_memory_size / s_page_size + 1; + m_total_entries = m_bitmap_size / s_row_bits + 1; + Logger::DEBUG() << "Memory Info: size = " << (int) (m_memory_size / 1024 / 1024) << "mb, bitmap size = 0x" << (uint64_t) m_bitmap_size << ", total entries = " << (int) m_total_entries << ", page size = 0x" << (uint64_t) s_page_size << "\n"; - // Find a region of memory available to be used - m_mmap_tag = m_multiboot->mmap(); - for (multiboot_mmap_entry *entry = m_mmap_tag->entries; (multiboot_uint8_t *)entry < (multiboot_uint8_t *)m_mmap_tag + m_mmap_tag->size; entry = (multiboot_mmap_entry *)((unsigned long)entry + m_mmap_tag->entry_size)) { + // Find a region of memory available to be used + m_mmap_tag = m_multiboot->mmap(); + for (multiboot_mmap_entry *entry = m_mmap_tag->entries; (multiboot_uint8_t *)entry < (multiboot_uint8_t *)m_mmap_tag + m_mmap_tag->size; entry = (multiboot_mmap_entry *)((unsigned long)entry + m_mmap_tag->entry_size)) { - // Skip if the region is not free or there is not enough space - if (entry->type != MULTIBOOT_MEMORY_AVAILABLE) - continue; + // Skip if the region is not free or there is not enough space + if (entry->type != MULTIBOOT_MEMORY_AVAILABLE) + continue; - // Store the entry. (note: dont break here as it is desired to find the last usable entry as that is normally biggest) - m_mmap = entry; - } - Logger::DEBUG() << "Mmap in use: 0x" << (uint64_t)m_mmap->addr << " - 0x" << (uint64_t)(m_mmap->addr + m_mmap->len) << "\n"; + // Store the entry. (note: don't break here as it is desired to find the last usable entry as that is normally biggest) + m_mmap = entry; + } + Logger::DEBUG() << "Mmap in use: 0x" << (uint64_t) m_mmap->addr << " - 0x" << (uint64_t) (m_mmap->addr + m_mmap->len) << "\n"; - // Memory after the kernel to be used for direct mapping (the bitmap is not ready while mapping the higher half so have to use this) - m_anonymous_memory_physical_address = align_up_to_page((size_t)&_kernel_physical_end + s_page_size, s_page_size); - m_anonymous_memory_virtual_address = align_up_to_page((size_t)&_kernel_end + s_page_size, s_page_size); - Logger::DEBUG() << "Anonymous Memory: physical = " << (uint64_t)m_anonymous_memory_physical_address << ", virtual = " << (uint64_t)m_anonymous_memory_virtual_address << "\n"; + // Memory after the kernel to be used for direct mapping (the bitmap is not ready while mapping the higher half so have to use this) + m_anonymous_memory_physical_address = align_up_to_page((size_t) &_kernel_physical_end + s_page_size, s_page_size); + m_anonymous_memory_virtual_address = align_up_to_page((size_t) &_kernel_end + s_page_size, s_page_size); + Logger::DEBUG() << "Anonymous Memory: physical = " << (uint64_t) m_anonymous_memory_physical_address << ", virtual = " << (uint64_t) m_anonymous_memory_virtual_address << "\n"; - // Map the physical memory into the virtual memory - for (uint64_t physical_address = 0; physical_address < (m_mmap->addr + m_mmap->len); physical_address += s_page_size) - map((physical_address_t *)physical_address, (virtual_address_t *)(s_hh_direct_map_offset + physical_address), Present | Write); + // Map the physical memory into the virtual memory + for (uint64_t physical_address = 0; physical_address < (m_mmap->addr + m_mmap->len); physical_address += s_page_size) + map((physical_address_t*) physical_address, (virtual_address_t*) (s_hh_direct_map_offset + physical_address), Present | Write); - m_anonymous_memory_physical_address += s_page_size; - Logger::DEBUG() << "Mapped physical memory to higher half direct map at offset 0x" << s_hh_direct_map_offset << "\n"; + m_anonymous_memory_physical_address += s_page_size; + Logger::DEBUG() << "Mapped physical memory to higher half direct map at offset 0x" << s_hh_direct_map_offset << "\n"; - // Set up the bitmap - initialise_bit_map(); - reserve_kernel_regions(multiboot); + // Set up the bitmap + initialise_bit_map(); + reserve_kernel_regions(multiboot); - // Initialisation Done - m_initialized = true; + // Initialisation Done + m_initialized = true; } PhysicalMemoryManager::~PhysicalMemoryManager() = default; -void PhysicalMemoryManager::reserve_kernel_regions(Multiboot *multiboot) { +void PhysicalMemoryManager::reserve_kernel_regions(Multiboot* multiboot) { - // Reserve the area for the bitmap - Logger::DEBUG() << "Bitmap: location: 0x" << (uint64_t)m_bit_map << " - 0x" << (uint64_t)(m_bit_map + m_bitmap_size / 8) << " (range of 0x" << (uint64_t)m_bitmap_size / 8 << ")\n"; - reserve((uint64_t)from_dm_region((uint64_t)m_bit_map), m_bitmap_size / 8 ); + // Reserve the area for the bitmap + Logger::DEBUG() << "Bitmap: location: 0x" << (uint64_t) m_bit_map << " - 0x" << (uint64_t) (m_bit_map + m_bitmap_size / 8) << " (range of 0x" << (uint64_t) m_bitmap_size / 8 << ")\n"; + reserve((uint64_t) from_dm_region((uint64_t) m_bit_map), m_bitmap_size / 8); - // Calculate how much space the kernel takes up - uint32_t kernel_entries = (m_anonymous_memory_physical_address / s_page_size) + 1; - if ((((uint32_t)(m_anonymous_memory_physical_address)) % s_page_size) != 0) - kernel_entries += 1; + // Calculate how much space the kernel takes up + uint32_t kernel_entries = (m_anonymous_memory_physical_address / s_page_size) + 1; + if ((((uint32_t) (m_anonymous_memory_physical_address)) % s_page_size) != 0) + kernel_entries += 1; - // Reserve the kernel entries - Logger::DEBUG() << "Kernel: location: 0x" << (uint64_t)m_anonymous_memory_physical_address << " - 0x" << (uint64_t)(m_anonymous_memory_physical_address + kernel_entries * s_page_size) << " (range of 0x" << (uint64_t)kernel_entries * s_page_size << ")\n"; - reserve(0, kernel_entries * s_page_size); + // Reserve the kernel entries + Logger::DEBUG() << "Kernel: location: 0x" << (uint64_t) m_anonymous_memory_physical_address << " - 0x" << (uint64_t) (m_anonymous_memory_physical_address + kernel_entries * s_page_size) << " (range of 0x" << (uint64_t) kernel_entries * s_page_size << ")\n"; + reserve(0, kernel_entries * s_page_size); - // Reserve the area for the mmap - uint64_t mem_end = m_mmap->addr + m_mmap->len; - for (multiboot_mmap_entry *entry = m_mmap_tag->entries; (multiboot_uint8_t *)entry < (multiboot_uint8_t *)m_mmap_tag + m_mmap_tag->size; entry = (multiboot_mmap_entry *)((unsigned long)entry + m_mmap_tag->entry_size)) { + // Reserve the area for the mmap + uint64_t mem_end = m_mmap->addr + m_mmap->len; + for (multiboot_mmap_entry* entry = m_mmap_tag->entries; (multiboot_uint8_t*) entry < (multiboot_uint8_t*) m_mmap_tag + m_mmap_tag->size; entry = (multiboot_mmap_entry*) ((unsigned long) entry + m_mmap_tag->entry_size)) { - // Dont reserve free regions - if (entry->type <= MULTIBOOT_MEMORY_AVAILABLE) - continue; + // Dont reserve free regions + if (entry->type <= MULTIBOOT_MEMORY_AVAILABLE) + continue; - // Dont reserve the memory being managed by pmm - if(entry->addr >= mem_end) - continue; + // Don't reserve the memory being managed by pmm + if (entry->addr >= mem_end) + continue; - reserve(entry->addr, entry->len); - } + reserve(entry->addr, entry->len); + } - // Reserve the area for each multiboot module - for(multiboot_tag* tag = multiboot -> start_tag(); tag->type != MULTIBOOT_TAG_TYPE_END; tag = (struct multiboot_tag *) ((multiboot_uint8_t *) tag + ((tag->size + 7) & ~7))) { + // Reserve the area for each multiboot module + for (multiboot_tag* tag = multiboot->start_tag(); tag->type != MULTIBOOT_TAG_TYPE_END; tag = (struct multiboot_tag*) ((multiboot_uint8_t*) tag + ((tag->size + 7) & ~7))) { - if(tag -> type != MULTIBOOT_TAG_TYPE_MODULE) - continue; + if (tag->type != MULTIBOOT_TAG_TYPE_MODULE) + continue; - // Reserve the module's address - auto* module = (struct multiboot_tag_module*)tag; - reserve(module->mod_start, module->mod_end - module->mod_start); - } + // Reserve the module's address + auto* module = (struct multiboot_tag_module*) tag; + reserve(module->mod_start, module->mod_end - module->mod_start); + } } /** @@ -128,7 +127,8 @@ void PhysicalMemoryManager::reserve_kernel_regions(Multiboot *multiboot) { * @return The number of frames */ size_t PhysicalMemoryManager::size_to_frames(size_t size) { - return align_to_page(size) / s_page_size; + + return align_to_page(size) / s_page_size; } /** @@ -138,7 +138,8 @@ size_t PhysicalMemoryManager::size_to_frames(size_t size) { * @return The aligned size */ size_t PhysicalMemoryManager::align_to_page(size_t size) { - return ((size + s_page_size - 1) /s_page_size) * s_page_size; + + return ((size + s_page_size - 1) / s_page_size) * s_page_size; } /** @@ -149,7 +150,8 @@ size_t PhysicalMemoryManager::align_to_page(size_t size) { * @return The aligned size */ size_t PhysicalMemoryManager::align_up_to_page(size_t size, size_t page_size) { - return (size + page_size - 1) & ~(page_size - 1); + + return (size + page_size - 1) & ~(page_size - 1); } /** @@ -158,8 +160,9 @@ size_t PhysicalMemoryManager::align_up_to_page(size_t size, size_t page_size) { * @param size The address to check * @return True if the address is aligned */ -bool PhysicalMemoryManager::check_aligned(size_t size){ - return (size % s_page_size) == 0; +bool PhysicalMemoryManager::check_aligned(size_t size) { + + return (size % s_page_size) == 0; } /** @@ -169,63 +172,62 @@ bool PhysicalMemoryManager::check_aligned(size_t size){ */ void* PhysicalMemoryManager::allocate_frame() { - // Wait for the lock - m_lock.lock(); - - // If not initialised, cant use the bitmap or higher half mapped physical memory so use leftover kernel memory already - // mapped in loader.s - if(!m_initialized){ - // TODO: This seems to destroy the multiboot memory map, need to fix this + // Wait for the lock + m_lock.lock(); - // Find the first free frame - while ((!is_anonymous_available(m_anonymous_memory_physical_address)) && (m_anonymous_memory_physical_address < m_memory_size)) { - m_anonymous_memory_physical_address += s_page_size; - m_anonymous_memory_virtual_address += s_page_size; - } + // If not initialised, cant use the bitmap or higher half mapped physical memory so use leftover kernel memory already + // mapped in loader.s + if (!m_initialized) { + // TODO: This seems to destroy the multiboot memory map, need to fix this - // Mark frame as used - m_anonymous_memory_physical_address += s_page_size; - m_anonymous_memory_virtual_address += s_page_size; + // Find the first free frame + while ((!is_anonymous_available(m_anonymous_memory_physical_address)) && (m_anonymous_memory_physical_address < m_memory_size)) { + m_anonymous_memory_physical_address += s_page_size; + m_anonymous_memory_virtual_address += s_page_size; + } - // Return the address - m_lock.unlock(); - return (void*)(m_anonymous_memory_physical_address - s_page_size); + // Mark frame as used + m_anonymous_memory_physical_address += s_page_size; + m_anonymous_memory_virtual_address += s_page_size; - } + // Return the address + m_lock.unlock(); + return (void*) (m_anonymous_memory_physical_address - s_page_size); - // Check if there are enough frames - ASSERT(m_used_frames < m_bitmap_size, "No more frames available\n"); + } - for (uint32_t row = 0; row < m_total_entries; ++row) { + // Check if there are enough frames + ASSERT(m_used_frames < m_bitmap_size, "No more frames available\n"); - // If the row is full continue - if(m_bit_map[row] == 0xFFFFFFFFFFFFFFF) - continue; + for (uint32_t row = 0; row < m_total_entries; ++row) { - for (uint32_t column = 0; column < s_row_bits; ++column) { + // If the row is full continue + if (m_bit_map[row] == 0xFFFFFFFFFFFFFFF) + continue; - // Entry isn't free - if (m_bit_map[row] & (1ULL << column)) - continue; + for (uint32_t column = 0; column < s_row_bits; ++column) { - // Mark the frame as used - m_bit_map[row] |= (1ULL << column); - m_used_frames++; + // Entry isn't free + if (m_bit_map[row] & (1ULL << column)) + continue; - // Thread safe - m_lock.unlock(); + // Mark the frame as used + m_bit_map[row] |= (1ULL << column); + m_used_frames++; - // Return the address - uint64_t frame_address = (row * s_row_bits) + column; - return (void*)(frame_address * s_page_size); - } - } + // Thread safe + m_lock.unlock(); - // Error frame not found - ASSERT(false, "Frame not found\n"); - m_lock.unlock(); - return nullptr; + // Return the address + uint64_t frame_address = (row * s_row_bits) + column; + return (void*) (frame_address * s_page_size); + } + } + // Error frame not found + ASSERT(false, "Frame not found\n"); + m_lock.unlock(); + return nullptr; } /** @@ -233,16 +235,16 @@ void* PhysicalMemoryManager::allocate_frame() { * * @param address The address to free */ -void PhysicalMemoryManager::free_frame(void *address) { +void PhysicalMemoryManager::free_frame(void* address) { - m_lock.lock(); + m_lock.lock(); - // Mark the frame as not used - m_used_frames--; - uint64_t frame_address = (uint64_t)address / s_page_size; - m_bit_map[frame_address / s_row_bits] &= ~(1 << (frame_address % s_row_bits)); + // Mark the frame as not used + m_used_frames--; + uint64_t frame_address = (uint64_t) address / s_page_size; + m_bit_map[frame_address / s_row_bits] &= ~(1 << (frame_address % s_row_bits)); - m_lock.unlock(); + m_lock.unlock(); } /** @@ -253,66 +255,66 @@ void PhysicalMemoryManager::free_frame(void *address) { * @return A pointer to the start of the block (physical address) */ void* PhysicalMemoryManager::allocate_area(uint64_t start_address, size_t size) { - m_lock.lock(); - // Store the information about the frames needed to be allocated for this size - size_t frame_count = size_to_frames(size); - uint32_t start_row = 0; - uint32_t start_column = 0; - size_t adjacent_frames = 0; + m_lock.lock(); - for (uint32_t row = 0; row < m_total_entries; ++row) { + // Store the information about the frames needed to be allocated for this size + size_t frame_count = size_to_frames(size); + uint32_t start_row = 0; + uint32_t start_column = 0; + size_t adjacent_frames = 0; - // Skip full rows - if(m_bit_map[row] == 0xFFFFFFFFFFFFFFF) - continue; + for (uint32_t row = 0; row < m_total_entries; ++row) { - for (uint32_t column = 0; column < s_row_bits; ++column) { + // Skip full rows + if (m_bit_map[row] == 0xFFFFFFFFFFFFFFF) + continue; - // Not enough adjacent frames - if (m_bit_map[row] & (1ULL << column)) { - adjacent_frames = 0; - continue; - } + for (uint32_t column = 0; column < s_row_bits; ++column) { - // Store the address of the first frame in set of adjacent ones - if(adjacent_frames == 0){ - start_row = row; - start_column = column; - } + // Not enough adjacent frames + if (m_bit_map[row] & (1ULL << column)) { + adjacent_frames = 0; + continue; + } + // Store the address of the first frame in set of adjacent ones + if (adjacent_frames == 0) { + start_row = row; + start_column = column; + } - // Make sure there are enough frames in a row found - adjacent_frames++; - if(adjacent_frames != frame_count) - continue; + // Make sure there are enough frames in a row found + adjacent_frames++; + if (adjacent_frames != frame_count) + continue; - // Mark the frames as used - m_used_frames += frame_count; - for (uint32_t i = 0; i < frame_count; ++i) { + // Mark the frames as used + m_used_frames += frame_count; + for (uint32_t i = 0; i < frame_count; ++i) { - // Get the location of the bit - uint32_t index = start_row + (start_column + i) / s_row_bits; - uint32_t bit = (start_column + i) % s_row_bits; + // Get the location of the bit + uint32_t index = start_row + (start_column + i) / s_row_bits; + uint32_t bit = (start_column + i) % s_row_bits; - // Check bounds - ASSERT(index >= m_total_entries || bit >= s_row_bits, "Index out of bounds\n"); + // Check bounds + ASSERT(index >= m_total_entries || bit >= s_row_bits, "Index out of bounds\n"); - // Mark the bit as used - m_bit_map[index] |= (1ULL << bit); - } + // Mark the bit as used + m_bit_map[index] |= (1ULL << bit); + } - // Return start of the block of adjacent frames - m_lock.unlock(); - return (void*)(start_address + (start_row * s_row_bits + start_column) * s_page_size); + // Return start of the block of adjacent frames + m_lock.unlock(); + return (void*) (start_address + (start_row * s_row_bits + start_column) * s_page_size); - } - } + } + } - // Not enough free frames adjacent to each other - m_lock.unlock(); - ASSERT(false, "Cannot allocate that much memory\n"); - return nullptr; + // Not enough free frames adjacent to each other + m_lock.unlock(); + ASSERT(false, "Cannot allocate that much memory\n"); + return nullptr; } /** @@ -323,23 +325,23 @@ void* PhysicalMemoryManager::allocate_area(uint64_t start_address, size_t size) */ void PhysicalMemoryManager::free_area(uint64_t start_address, size_t size) { - // Convert address into frames - size_t frame_count = size_to_frames(size); - uint64_t frame_address = start_address / s_page_size; + // Convert address into frames + size_t frame_count = size_to_frames(size); + uint64_t frame_address = start_address / s_page_size; - // Check bounds - if(frame_address >= m_bitmap_size) - return; + // Check bounds + if (frame_address >= m_bitmap_size) + return; - // Wait until other threads have finished other memory operations - m_lock.lock(); + // Wait until other threads have finished other memory operations + m_lock.lock(); - // Mark the frames as not used - m_used_frames -= frame_count; - for (uint32_t i = 0; i < frame_count; ++i) - m_bit_map[(frame_address + i) / s_row_bits] &= ~(1 << ((frame_address + i) % s_row_bits)); + // Mark the frames as not used + m_used_frames -= frame_count; + for (uint32_t i = 0; i < frame_count; ++i) + m_bit_map[(frame_address + i) / s_row_bits] &= ~(1 << ((frame_address + i) % s_row_bits)); - m_lock.unlock(); + m_lock.unlock(); } /** @@ -351,7 +353,8 @@ void PhysicalMemoryManager::free_area(uint64_t start_address, size_t size) { * @return */ pml_t* PhysicalMemoryManager::get_higher_half_table(uint64_t index, uint64_t index2, uint64_t index3) { - return (pml_t*)(0xFFFF000000000000 | ((510UL << 39) | (index3 << 30) | (index2 << 21) | (index << 12))); + + return (pml_t*) (0xFFFF000000000000 | ((510UL << 39) | (index3 << 30) | (index2 << 21) | (index << 12))); } @@ -365,21 +368,21 @@ pml_t* PhysicalMemoryManager::get_higher_half_table(uint64_t index, uint64_t ind */ pml_t* PhysicalMemoryManager::get_or_create_table(pml_t* table, size_t index, size_t flags) { - // Table is already created so just find the entry - if(table -> entries[index].present) - return (pml_t *) to_dm_region(physical_address_of_entry(&table -> entries[index])); + // Table is already created so just find the entry + if (table->entries[index].present) + return (pml_t*) to_dm_region(physical_address_of_entry(&table->entries[index])); - // Create the table - auto* new_table = (uint64_t*)allocate_frame(); - table -> entries[index] = create_page_table_entry((uint64_t)new_table, flags); + // Create the table + auto* new_table = (uint64_t*) allocate_frame(); + table->entries[index] = create_page_table_entry((uint64_t) new_table, flags); - // Move the table to the higher half - new_table = (uint64_t*)to_dm_region((uintptr_t) new_table); + // Move the table to the higher half + new_table = (uint64_t*) to_dm_region((uintptr_t) new_table); - // Reset the memory contents at the address - clean_page_table(new_table); + // Reset the memory contents at the address + clean_page_table(new_table); - return (pml_t*)new_table; + return (pml_t*) new_table; } /** @@ -393,63 +396,62 @@ pml_t* PhysicalMemoryManager::get_or_create_table(pml_t* table, size_t index, si */ pml_t* PhysicalMemoryManager::get_and_create_table(pml_t* parent_table, uint64_t table_index, pml_t* table) { - // Table already created so dont need to do anything - /// Note: this is where it differs when not having pmm init done as cant find the entry (direct map not setup) thus - /// requiring get_higher_half_table() to be passed in as the *table arg by the caller - if(parent_table -> entries[table_index].present) - return table; + // Table already created so dont need to do anything + /// Note: this is where it differs when not having pmm init done as cant find the entry (direct map not setup) thus + /// requiring get_higher_half_table() to be passed in as the *table arg by the caller + if (parent_table->entries[table_index].present) + return table; - // Create the table - auto* new_table = (uint64_t *)allocate_frame(); - parent_table -> entries[table_index] = create_page_table_entry((uint64_t)new_table, Present | Write); + // Create the table + auto* new_table = (uint64_t*) allocate_frame(); + parent_table->entries[table_index] = create_page_table_entry((uint64_t) new_table, Present | Write); - // Move the table to higher half - // Except this doesnt need to be done because using anom memory + // Move the table to higher half + // Except this doesn't need to be done because using anom memory - // Reset the memory contents at the address - clean_page_table((uint64_t*)table); + // Reset the memory contents at the address + clean_page_table((uint64_t*) table); - return table; + return table; } - /** * @brief Get the page table entry for a virtual address * * @param virtual_address The virtual address to get the entry for * @return The page table entry for the virtual address or nullptr if not found */ -pte_t *PhysicalMemoryManager::get_entry(virtual_address_t* virtual_address, pml_t* pml4_table) { +pte_t* PhysicalMemoryManager::get_entry(virtual_address_t* virtual_address, pml_t* pml4_table) { - // Kernel memory must be in the higher half - size_t flags = Present | Write; - if(!in_higher_region((uint64_t)virtual_address)) - flags |= User; + // Kernel memory must be in the higher half + size_t flags = Present | Write; + if (!in_higher_region((uint64_t) virtual_address)) + flags |= User; - uint16_t pml4_index = PML4_GET_INDEX((uint64_t) virtual_address); - uint16_t pdpr_index = PML3_GET_INDEX((uint64_t) virtual_address); - uint16_t pd_index = PML2_GET_INDEX((uint64_t) virtual_address); - uint16_t pt_index = PML1_GET_INDEX((uint64_t) virtual_address); + uint16_t pml4_index = PML4_GET_INDEX((uint64_t) virtual_address); + uint16_t pdpr_index = PML3_GET_INDEX((uint64_t) virtual_address); + uint16_t pd_index = PML2_GET_INDEX((uint64_t) virtual_address); + uint16_t pt_index = PML1_GET_INDEX((uint64_t) virtual_address); - pml_t* pdpr_table = nullptr; - pml_t* pd_table = nullptr; - pml_t* pt_table = nullptr; + pml_t* pdpr_table = nullptr; + pml_t* pd_table = nullptr; + pml_t* pt_table = nullptr; - // If it is before initialization then cant rely on the direct map - if(!m_initialized) { - pdpr_table = get_and_create_table(pml4_table, pml4_index, get_higher_half_table(pml4_index)); - pd_table = get_and_create_table(pdpr_table, pdpr_index, get_higher_half_table(pdpr_index, pml4_index)); - pt_table = get_and_create_table(pd_table, pd_index, get_higher_half_table(pd_index, pdpr_index, pml4_index)); + // If it is before initialization then cant rely on the direct map + if (!m_initialized) { + pdpr_table = get_and_create_table(pml4_table, pml4_index, get_higher_half_table(pml4_index)); + pd_table = get_and_create_table(pdpr_table, pdpr_index, get_higher_half_table(pdpr_index, pml4_index)); + pt_table = get_and_create_table(pd_table, pd_index, get_higher_half_table(pd_index, pdpr_index, pml4_index)); - }else{ - pdpr_table = get_or_create_table(pml4_table, pml4_index, flags); - pd_table = get_or_create_table(pdpr_table, pdpr_index, flags); - pt_table = get_or_create_table(pd_table, pd_index, flags); - } + } else { + pdpr_table = get_or_create_table(pml4_table, pml4_index, flags); + pd_table = get_or_create_table(pdpr_table, pdpr_index, flags); + pt_table = get_or_create_table(pd_table, pd_index, flags); + } - // Get the entry - return &pt_table -> entries[pt_index]; + // Get the entry + return &pt_table->entries[pt_index]; } @@ -459,8 +461,9 @@ pte_t *PhysicalMemoryManager::get_entry(virtual_address_t* virtual_address, pml_ * @param entry The entry to get the physical address of * @return The physical address of the entry */ -uint64_t PhysicalMemoryManager::physical_address_of_entry(pte_t *entry) { - return entry -> physical_address << 12; +uint64_t PhysicalMemoryManager::physical_address_of_entry(pte_t* entry) { + + return entry->physical_address << 12; } @@ -472,10 +475,10 @@ uint64_t PhysicalMemoryManager::physical_address_of_entry(pte_t *entry) { * @param flags The flags to set the mapping to * @return The virtual address */ -virtual_address_t* PhysicalMemoryManager::map(physical_address_t *physical_address, virtual_address_t* virtual_address, size_t flags) { +virtual_address_t* PhysicalMemoryManager::map(physical_address_t* physical_address, virtual_address_t* virtual_address, size_t flags) { - // Map using the kernel's pml4 table - return map(physical_address, virtual_address, flags, m_pml4_root_address); + // Map using the kernel's pml4 table + return map(physical_address, virtual_address, flags, m_pml4_root_address); } /** @@ -489,22 +492,22 @@ virtual_address_t* PhysicalMemoryManager::map(physical_address_t *physical_addre */ virtual_address_t* PhysicalMemoryManager::map(physical_address_t* physical_address, virtual_address_t* virtual_address, size_t flags, uint64_t* pml4_table) { - // If it is in a lower region then assume it is the user space - if(!in_higher_region((uint64_t)virtual_address)) - flags |= User; + // If it is in a lower region then assume it is the user space + if (!in_higher_region((uint64_t) virtual_address)) + flags |= User; - // If the entry already exists then the mapping is already done - pte_t* pte = get_entry(virtual_address, (pml_t *)pml4_table); - if(pte -> present) - return virtual_address; + // If the entry already exists then the mapping is already done + pte_t* pte = get_entry(virtual_address, (pml_t*) pml4_table); + if (pte->present) + return virtual_address; - // Map the physical address to the virtual address - *pte = create_page_table_entry((uint64_t)physical_address, flags); + // Map the physical address to the virtual address + *pte = create_page_table_entry((uint64_t) physical_address, flags); - // Flush the TLB (cache) - asm volatile("invlpg (%0)" ::"r" (virtual_address) : "memory"); + // Flush the TLB (cache) + asm volatile("invlpg (%0)"::"r" (virtual_address) : "memory"); - return virtual_address; + return virtual_address; } /** @@ -514,10 +517,10 @@ virtual_address_t* PhysicalMemoryManager::map(physical_address_t* physical_addre * @param flags The flags to set the mapping to * @return The virtual address */ -virtual_address_t* PhysicalMemoryManager::map(virtual_address_t *virtual_address, size_t flags) { +virtual_address_t* PhysicalMemoryManager::map(virtual_address_t* virtual_address, size_t flags) { - // Map a new physical address to the requested virtual address - return map(allocate_frame(), virtual_address, flags); + // Map a new physical address to the requested virtual address + return map(allocate_frame(), virtual_address, flags); } @@ -530,10 +533,9 @@ virtual_address_t* PhysicalMemoryManager::map(virtual_address_t *virtual_address */ void PhysicalMemoryManager::map_area(virtual_address_t* virtual_address_start, size_t length, size_t flags) { - - // Map the required frames - for (size_t i = 0; i < size_to_frames(length); ++i) - map(virtual_address_start + (i * s_page_size), flags); + // Map the required frames + for (size_t i = 0; i < size_to_frames(length); ++i) + map(virtual_address_start + (i * s_page_size), flags); } @@ -547,11 +549,9 @@ void PhysicalMemoryManager::map_area(virtual_address_t* virtual_address_start, s */ void PhysicalMemoryManager::map_area(physical_address_t* physical_address_start, virtual_address_t* virtual_address_start, size_t length, size_t flags) { - - // Map the required frames - for (size_t i = 0; i < size_to_frames(length); ++i) - map(physical_address_start + (i * s_page_size), virtual_address_start + (i * s_page_size), flags); - + // Map the required frames + for (size_t i = 0; i < size_to_frames(length); ++i) + map(physical_address_start + (i * s_page_size), virtual_address_start + (i * s_page_size), flags); } /** @@ -562,9 +562,8 @@ void PhysicalMemoryManager::map_area(physical_address_t* physical_address_start, */ void PhysicalMemoryManager::identity_map(physical_address_t* physical_address, size_t flags) { - // Map the physical address to its virtual address counter-part - map(physical_address, physical_address, flags); - + // Map the physical address to its virtual address counter-part + map(physical_address, physical_address, flags); } /** @@ -574,8 +573,8 @@ void PhysicalMemoryManager::identity_map(physical_address_t* physical_address, s */ void PhysicalMemoryManager::unmap(virtual_address_t* virtual_address) { - // Pass the kernel's pml4 table - unmap(virtual_address, m_pml4_root_address); + // Pass the kernel's pml4 table + unmap(virtual_address, m_pml4_root_address); } /** @@ -584,21 +583,20 @@ void PhysicalMemoryManager::unmap(virtual_address_t* virtual_address) { * @param virtual_address The virtual address to unmap * @param pml4_root The pml4 table to use */ -void PhysicalMemoryManager::unmap(virtual_address_t *virtual_address, uint64_t* pml4_root) { - - // Get the entry - pte_t* pte = get_entry(virtual_address, (pml_t *)pml4_root); +void PhysicalMemoryManager::unmap(virtual_address_t* virtual_address, uint64_t* pml4_root) { - // Make sure the address is actually mapped - if(!pte -> present) - return; + // Get the entry + pte_t* pte = get_entry(virtual_address, (pml_t*) pml4_root); - // Unmap the entry - pte -> present = false; + // Make sure the address is actually mapped + if (!pte->present) + return; - // Flush the TLB (cache) - asm volatile("invlpg (%0)" ::"r" (virtual_address) : "memory"); + // Unmap the entry + pte->present = false; + // Flush the TLB (cache) + asm volatile("invlpg (%0)"::"r" (virtual_address) : "memory"); } /** @@ -607,11 +605,11 @@ void PhysicalMemoryManager::unmap(virtual_address_t *virtual_address, uint64_t* * @param virtual_address_start The start of the area * @param length The length of the area */ -void PhysicalMemoryManager::unmap_area(virtual_address_t *virtual_address_start, size_t length) { +void PhysicalMemoryManager::unmap_area(virtual_address_t* virtual_address_start, size_t length) { - // Unmap the required frames - for (size_t i = 0; i < size_to_frames(length); ++i) - unmap(virtual_address_start + (i * s_page_size)); + // Unmap the required frames + for (size_t i = 0; i < size_to_frames(length); ++i) + unmap(virtual_address_start + (i * s_page_size)); } /** @@ -619,11 +617,11 @@ void PhysicalMemoryManager::unmap_area(virtual_address_t *virtual_address_start, * * @param table The table to clean */ -void PhysicalMemoryManager::clean_page_table(uint64_t *table) { +void PhysicalMemoryManager::clean_page_table(uint64_t* table) { - // Null the table (prevents false mappings when re-using frames) - for(int i = 0; i < 512; i++) - table[i] = 0x00l; + // Null the table (prevents false mappings when re-using frames) + for (int i = 0; i < 512; i++) + table[i] = 0x00l; } /** @@ -633,30 +631,30 @@ void PhysicalMemoryManager::clean_page_table(uint64_t *table) { * @param flags The flags to set the entry to * @return The created page table entry */ -pte_t PhysicalMemoryManager::create_page_table_entry(uintptr_t address, size_t flags) { +pte_t PhysicalMemoryManager::create_page_table_entry(uintptr_t address, size_t flags) const { - pte_t page = (pte_t){ - .present = (flags & Present) != 0, - .write = (flags & Write) != 0, - .user = (flags & User) != 0, - .write_through = (flags & WriteThrough) != 0, - .cache_disabled = (flags & CacheDisabled) != 0, - .accessed = (flags & Accessed) != 0, - .dirty = (flags & Dirty) != 0, - .huge_page = (flags & HugePage) != 0, - .global = (flags & Global) != 0, - .available = 0, - .physical_address = address >> 12, - }; + pte_t page = (pte_t) { + .present = (flags & Present) != 0, + .write = (flags & Write) != 0, + .user = (flags & User) != 0, + .write_through = (flags & WriteThrough) != 0, + .cache_disabled = (flags & CacheDisabled) != 0, + .accessed = (flags & Accessed) != 0, + .dirty = (flags & Dirty) != 0, + .huge_page = (flags & HugePage) != 0, + .global = (flags & Global) != 0, + .available = 0, + .physical_address = address >> 12, + }; - // Set the NX bit if it is allowed - if(m_nx_allowed && (flags & NoExecute)){ - auto page_raw = (uint64_t)&page; - page_raw |= NoExecute; - page = *(pte_t*)page_raw; - } + // Set the NX bit if it is allowed + if (m_nx_allowed && (flags & NoExecute)) { + auto page_raw = (uint64_t) &page; + page_raw |= NoExecute; + page = *(pte_t*) page_raw; + } - return page; + return page; } /** @@ -667,34 +665,34 @@ pte_t PhysicalMemoryManager::create_page_table_entry(uintptr_t address, size_t f */ bool PhysicalMemoryManager::is_anonymous_available(size_t address) { - // Make sure the address isn't (entirely) within or overlapping with the multiboot memory chunk - if ((address > m_multiboot-> start_address && address + s_page_size < m_multiboot -> end_address) - || (address + s_page_size > m_multiboot-> start_address && address < m_multiboot -> end_address)) { - return false; - } + // Make sure the address isn't (entirely) within or overlapping with the multiboot memory chunk + if ((address > m_multiboot->start_address && address + s_page_size < m_multiboot->end_address) + || (address + s_page_size > m_multiboot->start_address && address < m_multiboot->end_address)) { + return false; + } - // Make sure the address isn't used by a multiboot module - if(m_multiboot -> is_reserved(address)) - return false; + // Make sure the address isn't used by a multiboot module + if (m_multiboot->is_reserved(address)) + return false; - // Make sure the address doesnt overlap with a reserved chunk of physical memory - for (multiboot_mmap_entry *entry = m_mmap_tag->entries; (multiboot_uint8_t *)entry < (multiboot_uint8_t *)m_mmap_tag + m_mmap_tag->size; entry = (multiboot_mmap_entry *)((unsigned long)entry + m_mmap_tag->entry_size)) { + // Make sure the address doesn't overlap with a reserved chunk of physical memory + for (multiboot_mmap_entry* entry = m_mmap_tag->entries; (multiboot_uint8_t*) entry < (multiboot_uint8_t*) m_mmap_tag + m_mmap_tag->size; entry = (multiboot_mmap_entry*) ((unsigned long) entry + m_mmap_tag->entry_size)) { - // This entry doesnt contain the address - if ((entry -> addr + entry -> len) < (address + s_page_size)) - continue; + // This entry doesnt contain the address + if ((entry->addr + entry->len) < (address + s_page_size)) + continue; - // This entry is not free - if(entry -> type != MULTIBOOT_MEMORY_AVAILABLE) - continue; + // This entry is not free + if (entry->type != MULTIBOOT_MEMORY_AVAILABLE) + continue; - // This entry must contain the address and must be free so it can be used - return true; + // This entry must contain the address and must be free so it can be used + return true; - } + } - // Memory is not available (it was not found in a free region) - return false; + // Memory is not available (it was not found in a free region) + return false; } @@ -706,39 +704,39 @@ bool PhysicalMemoryManager::is_anonymous_available(size_t address) { void PhysicalMemoryManager::initialise_bit_map() { - // Earliest address to place the bitmap (after the kernel and hh direct map) - uint64_t limit = m_anonymous_memory_physical_address; + // Earliest address to place the bitmap (after the kernel and hh direct map) + uint64_t limit = m_anonymous_memory_physical_address; - // Find a region for the bitmap to handle - for (multiboot_mmap_entry *entry = m_mmap_tag->entries; (multiboot_uint8_t *)entry < (multiboot_uint8_t *)m_mmap_tag + m_mmap_tag->size; entry = (multiboot_mmap_entry *)((unsigned long)entry + m_mmap_tag->entry_size)) { + // Find a region for the bitmap to handle + for (multiboot_mmap_entry* entry = m_mmap_tag->entries; (multiboot_uint8_t*) entry < (multiboot_uint8_t*) m_mmap_tag + m_mmap_tag->size; entry = (multiboot_mmap_entry*) ((unsigned long) entry + m_mmap_tag->entry_size)) { - // Cant use a non-free entry or an entry past limit (ie dont user higher addresses that will be overridden later) - if (entry -> type != MULTIBOOT_MEMORY_AVAILABLE || entry -> len > limit) - continue; + // Cant use a non-free entry or an entry past limit (ie dont user higher addresses that will be overridden later) + if (entry->type != MULTIBOOT_MEMORY_AVAILABLE || entry->len > limit) + continue; - size_t space = entry -> len; - size_t offset = 0; + size_t space = entry->len; + size_t offset = 0; - // Determine how much of the region is below the limit and adjust the starting point to there - if(entry -> addr < limit){ - offset = limit - entry -> addr; - space -= offset; - } + // Determine how much of the region is below the limit and adjust the starting point to there + if (entry->addr < limit) { + offset = limit - entry->addr; + space -= offset; + } - // Make sure there is enough space - ASSERT(space >= (m_bitmap_size / 8 + 1), "Not enough space for the bitmap (to big)\n"); + // Make sure there is enough space + ASSERT(space >= (m_bitmap_size / 8 + 1), "Not enough space for the bitmap (to big)\n"); - // Return the address (ensuring that it is in the safe region) - m_bit_map = (uint64_t*)to_dm_region(entry -> addr + offset); - break; - } + // Return the address (ensuring that it is in the safe region) + m_bit_map = (uint64_t*) to_dm_region(entry->addr + offset); + break; + } - // Error no suitable space for the bitmap found - ASSERT(m_bit_map != nullptr, "No space for the bitmap (no region)\n"); + // Error no suitable space for the bitmap found + ASSERT(m_bit_map != nullptr, "No space for the bitmap (no region)\n"); - // Clear the bitmap (mark all as free) - for (uint32_t i = 0; i < m_total_entries; ++i) - m_bit_map[i] = 0; + // Clear the bitmap (mark all as free) + for (uint32_t i = 0; i < m_total_entries; ++i) + m_bit_map[i] = 0; } /** @@ -746,8 +744,9 @@ void PhysicalMemoryManager::initialise_bit_map() { * * @return The pml4 root address */ -uint64_t *PhysicalMemoryManager::pml4_root_address() { - return m_pml4_root_address; +uint64_t* PhysicalMemoryManager::pml4_root_address() { + + return m_pml4_root_address; } /** @@ -756,7 +755,8 @@ uint64_t *PhysicalMemoryManager::pml4_root_address() { * @return The memory size in bytes */ uint64_t PhysicalMemoryManager::memory_size() const { - return m_memory_size; + + return m_memory_size; } /** @@ -765,7 +765,8 @@ uint64_t PhysicalMemoryManager::memory_size() const { * @return The memory size in bytes */ uint64_t PhysicalMemoryManager::memory_used() const { - return m_used_frames * s_page_size; + + return m_used_frames * s_page_size; } /** @@ -775,7 +776,8 @@ uint64_t PhysicalMemoryManager::memory_used() const { * @return The aligned address */ size_t PhysicalMemoryManager::align_direct_to_page(size_t size) { - return (size & (~(s_page_size - 1))); + + return (size & (~(s_page_size - 1))); } /** @@ -785,17 +787,15 @@ size_t PhysicalMemoryManager::align_direct_to_page(size_t size) { */ void PhysicalMemoryManager::reserve(uint64_t address) { + // Cant reserve virtual addresses (ensure the address is physical) + if (address >= m_memory_size) + return; - // Cant reserve virtual adresses (ensure the address is physical) - if(address >= m_memory_size) - return; + // Mark as used in the bitmap + address = align_direct_to_page(address); + m_bit_map[address / s_row_bits] |= (1 << (address % s_row_bits)); - // Mark as used in the bitmap - address = align_direct_to_page(address); - m_bit_map[address / s_row_bits] |= (1 << (address % s_row_bits)); - - - Logger::DEBUG() << "Reserved Address: 0x" << address << "\n"; + Logger::DEBUG() << "Reserved Address: 0x" << address << "\n"; } /** @@ -806,32 +806,32 @@ void PhysicalMemoryManager::reserve(uint64_t address) { */ void PhysicalMemoryManager::reserve(uint64_t address, size_t size) { - // Cant reserve virtual adresses (ensure the address is physical) - if(address >= m_memory_size) - return; + // Cant reserve virtual addresses (ensure the address is physical) + if (address >= m_memory_size) + return; - // Wait to be able to reserve - m_lock.lock(); + // Wait to be able to reserve + m_lock.lock(); - // Ensure the area is a range of pages - address = align_direct_to_page(address); - size = align_up_to_page(size, s_page_size); + // Ensure the area is a range of pages + address = align_direct_to_page(address); + size = align_up_to_page(size, s_page_size); - // Convert in to amount of pages - size_t page_count = size / s_page_size; - uint64_t frame_index = address / s_page_size; + // Convert in to amount of pages + size_t page_count = size / s_page_size; + uint64_t frame_index = address / s_page_size; - // Mark all as free - for (size_t i = 0; i < page_count; ++i) - m_bit_map[(frame_index + i) / s_row_bits] |= (1ULL << ((frame_index + i) % s_row_bits)); + // Mark all as free + for (size_t i = 0; i < page_count; ++i) + m_bit_map[(frame_index + i) / s_row_bits] |= (1ULL << ((frame_index + i) % s_row_bits)); - // Update the used frames - m_used_frames += page_count; + // Update the used frames + m_used_frames += page_count; - // Clear the lock - m_lock.unlock(); - Logger::DEBUG() << "Reserved Address: 0x" << address << " - 0x" << address + size << " (length of 0x" << size << ")\n"; + // Clear the lock + m_lock.unlock(); + Logger::DEBUG() << "Reserved Address: 0x" << address << " - 0x" << address + size << " (length of 0x" << size << ")\n"; } /** @@ -840,15 +840,15 @@ void PhysicalMemoryManager::reserve(uint64_t address, size_t size) { * @param virtual_address The virtual address to get the physical address from * @return The physical address or nullptr if it does not exist */ -physical_address_t *PhysicalMemoryManager::get_physical_address(virtual_address_t *virtual_address, uint64_t *pml4_root) { +physical_address_t* PhysicalMemoryManager::get_physical_address(virtual_address_t* virtual_address, uint64_t* pml4_root) { - pte_t* entry = get_entry(virtual_address, (pml_t *)pml4_root); + pte_t* entry = get_entry(virtual_address, (pml_t*) pml4_root); - // Cant get a physical address if it inst free - if(!entry -> present) - return nullptr; + // Cant get a physical address if its inst free + if (!entry->present) + return nullptr; - return (physical_address_t*)physical_address_of_entry(entry); + return (physical_address_t*) physical_address_of_entry(entry); } /** @@ -857,18 +857,18 @@ physical_address_t *PhysicalMemoryManager::get_physical_address(virtual_address_ * @param virtual_address The virtual address of the page * @param flags The flags to set the page to */ -void PhysicalMemoryManager::change_page_flags(virtual_address_t *virtual_address, size_t flags, uint64_t *pml4_root) { +void PhysicalMemoryManager::change_page_flags(virtual_address_t* virtual_address, size_t flags, uint64_t* pml4_root) { - pte_t* entry = get_entry(virtual_address, (pml_t *)pml4_root); + pte_t* entry = get_entry(virtual_address, (pml_t*) pml4_root); - // Cant edit a non-present entry (will page fault) - if(!entry -> present) - return; + // Cant edit a non-present entry (will page fault) + if (!entry->present) + return; - *entry = create_page_table_entry(physical_address_of_entry(entry), flags); + *entry = create_page_table_entry(physical_address_of_entry(entry), flags); - // Flush the TLB (cache) - asm volatile("invlpg (%0)" ::"r" (virtual_address) : "memory"); + // Flush the TLB (cache) + asm volatile("invlpg (%0)"::"r" (virtual_address) : "memory"); } @@ -880,9 +880,9 @@ void PhysicalMemoryManager::change_page_flags(virtual_address_t *virtual_address * @param pml4_root The pml4 table to use * @return True if the physical address is mapped to the virtual address */ -bool PhysicalMemoryManager::is_mapped(uintptr_t physical_address, uintptr_t virtual_address, uint64_t *pml4_root) { +bool PhysicalMemoryManager::is_mapped(uintptr_t physical_address, uintptr_t virtual_address, uint64_t* pml4_root) { - return get_physical_address((virtual_address_t*)virtual_address, pml4_root) == (physical_address_t*)physical_address; + return get_physical_address((virtual_address_t*) virtual_address, pml4_root) == (physical_address_t*) physical_address; } @@ -894,12 +894,12 @@ bool PhysicalMemoryManager::is_mapped(uintptr_t physical_address, uintptr_t virt */ void* PhysicalMemoryManager::to_higher_region(uintptr_t physical_address) { - // If it's in the lower half then add the offset - if(physical_address < s_higher_half_kernel_offset) - return (void*)(physical_address + s_higher_half_kernel_offset); + // If it's in the lower half then add the offset + if (physical_address < s_higher_half_kernel_offset) + return (void*) (physical_address + s_higher_half_kernel_offset); - // Must be in the higher half - return (void*)physical_address; + // Must be in the higher half + return (void*) physical_address; } /** @@ -910,12 +910,12 @@ void* PhysicalMemoryManager::to_higher_region(uintptr_t physical_address) { */ void* PhysicalMemoryManager::to_lower_region(uintptr_t virtual_address) { - // If it's in the lower half then add the offset - if(virtual_address > s_higher_half_kernel_offset) - return (void*)(virtual_address - s_higher_half_kernel_offset); + // If it's in the lower half then add the offset + if (virtual_address > s_higher_half_kernel_offset) + return (void*) (virtual_address - s_higher_half_kernel_offset); - // Must be in the lower half - return (void*)virtual_address; + // Must be in the lower half + return (void*) virtual_address; } /** @@ -926,12 +926,11 @@ void* PhysicalMemoryManager::to_lower_region(uintptr_t virtual_address) { */ void* PhysicalMemoryManager::to_io_region(uintptr_t physical_address) { - if(physical_address < s_higher_half_mem_offset) - return (void*)(physical_address + s_higher_half_mem_offset); - - // Must be in the higher half - return (void*)physical_address; + if (physical_address < s_higher_half_mem_offset) + return (void*) (physical_address + s_higher_half_mem_offset); + // Must be in the higher half + return (void*) physical_address; } /** @@ -942,12 +941,11 @@ void* PhysicalMemoryManager::to_io_region(uintptr_t physical_address) { */ void* PhysicalMemoryManager::to_dm_region(uintptr_t physical_address) { - if(physical_address < s_higher_half_offset) - return (void*)(physical_address + s_hh_direct_map_offset); - - // Must be in the higher half - return (void*)physical_address; + if (physical_address < s_higher_half_offset) + return (void*) (physical_address + s_hh_direct_map_offset); + // Must be in the higher half + return (void*) physical_address; } /** @@ -958,11 +956,11 @@ void* PhysicalMemoryManager::to_dm_region(uintptr_t physical_address) { */ void* PhysicalMemoryManager::from_dm_region(uintptr_t physical_address) { - if(physical_address > s_hh_direct_map_offset) - return (void*)(physical_address - s_hh_direct_map_offset); + if (physical_address > s_hh_direct_map_offset) + return (void*) (physical_address - s_hh_direct_map_offset); - // Must be in the lower half - return (void*)physical_address; + // Must be in the lower half + return (void*) physical_address; } @@ -973,7 +971,8 @@ void* PhysicalMemoryManager::from_dm_region(uintptr_t physical_address) { * @return True if the address is in the higher region, false otherwise */ bool PhysicalMemoryManager::in_higher_region(uintptr_t virtual_address) { - return virtual_address & (1l << 62); + + return virtual_address & (1l << 62); } @@ -981,5 +980,6 @@ bool PhysicalMemoryManager::in_higher_region(uintptr_t virtual_address) { * @brief Unmaps the kernel physical memory from the lower half that was set up during the kernel boot */ void PhysicalMemoryManager::unmap_lower_kernel() { - p4_table[0] = 0; + + p4_table[0] = 0; } \ No newline at end of file diff --git a/kernel/src/memory/virtual.cpp b/kernel/src/memory/virtual.cpp index 75e03b27..ae9e1afc 100644 --- a/kernel/src/memory/virtual.cpp +++ b/kernel/src/memory/virtual.cpp @@ -10,97 +10,94 @@ using namespace MaxOS::memory; using namespace MaxOS::common; using namespace MaxOS::processes; -VirtualMemoryManager::VirtualMemoryManager() -{ +VirtualMemoryManager::VirtualMemoryManager() { - // Set the kernel flag - bool is_kernel = MemoryManager::s_kernel_memory_manager == nullptr; - if(!is_kernel){ + // Set the kernel flag + bool is_kernel = MemoryManager::s_kernel_memory_manager == nullptr; + if (!is_kernel) { - // Get a new pml4 table - m_pml4_root_physical_address = (uint64_t*)PhysicalMemoryManager::s_current_manager->allocate_frame(); - m_pml4_root_address = (uint64_t*)PhysicalMemoryManager::to_dm_region((uint64_t)m_pml4_root_physical_address); + // Get a new pml4 table + m_pml4_root_physical_address = (uint64_t*) PhysicalMemoryManager::s_current_manager->allocate_frame(); + m_pml4_root_address = (uint64_t*) PhysicalMemoryManager::to_dm_region((uint64_t) m_pml4_root_physical_address); - // Clear the table - PhysicalMemoryManager::clean_page_table(m_pml4_root_address); + // Clear the table + PhysicalMemoryManager::clean_page_table(m_pml4_root_address); - // Map the higher half of the kernel (p4 256 - 511) - for (size_t i = 256; i < 512; i++){ + // Map the higher half of the kernel (p4 256 - 511) + for (size_t i = 256; i < 512; i++) { - // Recursive Map the pml4 table (so that we can access the new pml4 table later on) - if(i == 510) { - m_pml4_root_address[i] = (uint64_t)m_pml4_root_physical_address | Present | Write; - continue; - } + // Recursive Map the pml4 table (so that we can access the new pml4 table later on) + if (i == 510) { + m_pml4_root_address[i] = (uint64_t) m_pml4_root_physical_address | Present | Write; + continue; + } - // Set the new pml4 table to the old (kernel) pml4 table - m_pml4_root_address[i] = PhysicalMemoryManager::s_current_manager->pml4_root_address()[i]; + // Set the new pml4 table to the old (kernel) pml4 table + m_pml4_root_address[i] = PhysicalMemoryManager::s_current_manager->pml4_root_address()[i]; - } - Logger::DEBUG() << "Mapped higher half of kernel\n"; + } + Logger::DEBUG() << "Mapped higher half of kernel\n"; + } else { + m_pml4_root_address = PhysicalMemoryManager::s_current_manager->pml4_root_address(); + m_pml4_root_physical_address = (uint64_t*) PhysicalMemoryManager::to_lower_region((uint64_t) m_pml4_root_address); + } - }else{ - m_pml4_root_address = PhysicalMemoryManager::s_current_manager->pml4_root_address(); - m_pml4_root_physical_address = (uint64_t*)PhysicalMemoryManager::to_lower_region((uint64_t)m_pml4_root_address); - } + // Allocate space for the vmm + uint64_t vmm_space = PhysicalMemoryManager::align_to_page(PhysicalMemoryManager::s_hh_direct_map_offset + PhysicalMemoryManager::s_current_manager->memory_size() + PhysicalMemoryManager::s_page_size); //TODO: Check that am not slowly overwriting the kernel (filling first space with 0s bugs out the kernel) + void* vmm_space_physical = PhysicalMemoryManager::s_current_manager->allocate_frame(); + PhysicalMemoryManager::s_current_manager->map(vmm_space_physical, (virtual_address_t*) vmm_space, Present | Write, m_pml4_root_address); + Logger::DEBUG() << "VMM space: physical - 0x" << (uint64_t) vmm_space_physical << ", virtual - 0x" << (uint64_t) vmm_space << "\n"; - // Log the VMM's PML4 addressLogger::DEBUG() << "VMM PML4: physical - 0x" << (uint64_t)m_pml4_root_physical_address << ", virtual - 0x" << (uint64_t)m_pml4_root_address << "\n"; + // Make sure everything is mapped correctly + if (!is_kernel) + ASSERT(vmm_space_physical != PhysicalMemoryManager::s_current_manager->get_physical_address((virtual_address_t*) vmm_space, m_pml4_root_address), "Physical address does not match mapped address: 0x%x != 0x%x\n", vmm_space_physical, + PhysicalMemoryManager::s_current_manager->get_physical_address((virtual_address_t*) vmm_space, m_pml4_root_address)); - // Allocate space for the vmm - uint64_t vmm_space = PhysicalMemoryManager::align_to_page(PhysicalMemoryManager::s_hh_direct_map_offset + PhysicalMemoryManager::s_current_manager->memory_size() + PhysicalMemoryManager::s_page_size); //TODO: Check that am not slowly overwriting the kernel (filling first space with 0s bugs out the kernel) - void* vmm_space_physical = PhysicalMemoryManager::s_current_manager->allocate_frame(); - PhysicalMemoryManager::s_current_manager->map(vmm_space_physical, (virtual_address_t*)vmm_space, Present | Write, m_pml4_root_address); - Logger::DEBUG() << "VMM space: physical - 0x" << (uint64_t)vmm_space_physical << ", virtual - 0x" << (uint64_t)vmm_space << "\n"; - if(!is_kernel) - ASSERT(vmm_space_physical != PhysicalMemoryManager::s_current_manager->get_physical_address((virtual_address_t*)vmm_space, m_pml4_root_address), "Physical address does not match mapped address: 0x%x != 0x%x\n", vmm_space_physical, PhysicalMemoryManager::s_current_manager->get_physical_address((virtual_address_t*)vmm_space, m_pml4_root_address)); + // Set the first region + m_first_region = (virtual_memory_region_t*) vmm_space; + m_current_region = m_first_region; + m_first_region->next = nullptr; - // Set the first region - m_first_region = (virtual_memory_region_t*)vmm_space; - m_current_region = m_first_region; - m_first_region->next = nullptr; - - // Calculate the next available address (kernel needs to reserve space for the higher half) - m_next_available_address = is_kernel ? vmm_space + s_reserved_space : PhysicalMemoryManager::s_page_size; - Logger::DEBUG() << "Next available address: 0x" << m_next_available_address << "\n"; + // Calculate the next available address (kernel needs to reserve space for the higher half) + m_next_available_address = is_kernel ? vmm_space + s_reserved_space : PhysicalMemoryManager::s_page_size; + Logger::DEBUG() << "Next available address: 0x" << m_next_available_address << "\n"; } VirtualMemoryManager::~VirtualMemoryManager() { - // Free all the frames used by the VMM - virtual_memory_region_t* region = m_first_region; - - // Loop through the regions - while(region != nullptr){ + // Free all the frames used by the VMM + virtual_memory_region_t* region = m_first_region; - // Loop through the chunks - for (size_t i = 0; i < s_chunks_per_page; i++){ + // Loop through the regions + while (region != nullptr) { - // Have reached the end? - if(i == m_current_chunk && region == m_current_region) - break; + // Loop through the chunks + for (size_t i = 0; i < s_chunks_per_page; i++) { - // Loop through the pages - size_t pages = PhysicalMemoryManager::size_to_frames(region->chunks[i].size); - for (size_t j = 0; j < pages; j++){ + // Have reached the end? + if (i == m_current_chunk && region == m_current_region) + break; - // Get the frame - physical_address_t* frame = PhysicalMemoryManager::s_current_manager -> get_physical_address((virtual_address_t*)region->chunks[i].start_address + (j * PhysicalMemoryManager::s_page_size), m_pml4_root_address); + // Loop through the pages + size_t pages = PhysicalMemoryManager::size_to_frames(region->chunks[i].size); + for (size_t j = 0; j < pages; j++) { - // Free the frame - PhysicalMemoryManager::s_current_manager->free_frame(frame); + // Get the frame + physical_address_t* frame = PhysicalMemoryManager::s_current_manager->get_physical_address((virtual_address_t*) region->chunks[i].start_address + (j * PhysicalMemoryManager::s_page_size), m_pml4_root_address); - } + // Free the frame + PhysicalMemoryManager::s_current_manager->free_frame(frame); - } + } + } - // Move to the next region - region = region->next; - - } + // Move to the next region + region = region->next; + } } @@ -113,7 +110,7 @@ VirtualMemoryManager::~VirtualMemoryManager() { */ void* VirtualMemoryManager::allocate(size_t size, size_t flags) { - return allocate(0, size, flags); + return allocate(0, size, flags); } @@ -125,90 +122,90 @@ void* VirtualMemoryManager::allocate(size_t size, size_t flags) { * @param flags The flags to set on the memory * @return The address of the allocated memory or nullptr if failed */ -void *VirtualMemoryManager::allocate(uint64_t address, size_t size, size_t flags) { +void* VirtualMemoryManager::allocate(uint64_t address, size_t size, size_t flags) { - // Make sure allocating something - if(size == 0) - return nullptr; + // Make sure allocating something + if (size == 0) + return nullptr; - // If specific address is given - if(address != 0){ + // If specific address is given + if (address != 0) { - // Make sure isn't already allocated - if(address < m_next_available_address) - return nullptr; + // Make sure isn't already allocated + if (address < m_next_available_address) + return nullptr; - // Make sure its aligned - if(!PhysicalMemoryManager::check_aligned(address)) - return nullptr; + // Make sure its aligned + if (!PhysicalMemoryManager::check_aligned(address)) + return nullptr; - } + } - // Make sure the size is aligned - size = PhysicalMemoryManager::align_up_to_page(size, PhysicalMemoryManager::s_page_size); + // Make sure the size is aligned + size = PhysicalMemoryManager::align_up_to_page(size, PhysicalMemoryManager::s_page_size); - // Check the free list for a chunk (if not asking for a specific address) - free_chunk_t* reusable_chunk = address == 0 ? find_and_remove_free_chunk(size) : nullptr; - if(reusable_chunk != nullptr){ + // Check the free list for a chunk (if not asking for a specific address) + free_chunk_t* reusable_chunk = address == 0 ? find_and_remove_free_chunk(size) : nullptr; + if (reusable_chunk != nullptr) { - // If the chunk is not being reserved then the old memory needs to be unmapped - if(flags & Reserve){ + // If the chunk is not being reserved then the old memory needs to be unmapped + if (flags & Reserve) { - // Unmap the memory - size_t pages = PhysicalMemoryManager::size_to_frames(size); - for (size_t i = 0; i < pages; i++){ + // Unmap the memory + size_t pages = PhysicalMemoryManager::size_to_frames(size); + for (size_t i = 0; i < pages; i++) { - // Get the frame - physical_address_t* frame = PhysicalMemoryManager::s_current_manager -> get_physical_address((virtual_address_t*)reusable_chunk->start_address + (i * PhysicalMemoryManager::s_page_size), m_pml4_root_address); + // Get the frame + physical_address_t* frame = PhysicalMemoryManager::s_current_manager->get_physical_address((virtual_address_t*) reusable_chunk->start_address + (i * PhysicalMemoryManager::s_page_size), m_pml4_root_address); - // Free the frame - PhysicalMemoryManager::s_current_manager->free_frame(frame); + // Free the frame + PhysicalMemoryManager::s_current_manager->free_frame(frame); - } - } + } + } - // Return the address - return (void*)reusable_chunk->start_address; - } + // Return the address + return (void*) reusable_chunk->start_address; + } - // Is there space in the current region - if(m_current_chunk >= s_chunks_per_page) - new_region(); + // Is there space in the current region + if (m_current_chunk >= s_chunks_per_page) + new_region(); - // If needed to allocate at a specific address, fill with free memory up to that address to prevent fragmentation - if(address != 0) - fill_up_to_address(address, flags, false); + // If needed to allocate at a specific address, fill with free memory up to that address to prevent fragmentation + if (address != 0) + fill_up_to_address(address, flags, false); - // Allocate the memory - virtual_memory_chunk_t* chunk = &m_current_region->chunks[m_current_chunk]; - chunk->size = size; - chunk->flags = flags; - chunk->start_address = m_next_available_address; + // Allocate the memory + virtual_memory_chunk_t* chunk = &m_current_region->chunks[m_current_chunk]; + chunk->size = size; + chunk->flags = flags; + chunk->start_address = m_next_available_address; - // Update the next available address - m_next_available_address += size; - m_current_chunk++; + // Update the next available address + m_next_available_address += size; + m_current_chunk++; - // If just reserving the space don't map it - if(flags & Reserve) - return (void*)chunk->start_address; + // If just reserving the space don't map it + if (flags & Reserve) + return (void*) chunk->start_address; - // Map the memory - size_t pages = PhysicalMemoryManager::size_to_frames(size); - for (size_t i = 0; i < pages; i++){ + // Map the memory + size_t pages = PhysicalMemoryManager::size_to_frames(size); + for (size_t i = 0; i < pages; i++) { - // Allocate a new frame - physical_address_t* frame = PhysicalMemoryManager::s_current_manager->allocate_frame(); - ASSERT(frame != nullptr, "Failed to allocate frame (from current region)\n"); + // Allocate a new frame + physical_address_t* frame = PhysicalMemoryManager::s_current_manager->allocate_frame(); + ASSERT(frame != nullptr, "Failed to allocate frame (from current region)\n"); - // Map the frame - PhysicalMemoryManager::s_current_manager->map(frame, (virtual_address_t*)chunk->start_address + (i * PhysicalMemoryManager::s_page_size), Present | Write, m_pml4_root_address); + // Map the frame + PhysicalMemoryManager::s_current_manager->map(frame, (virtual_address_t*) chunk->start_address + (i * PhysicalMemoryManager::s_page_size), Present | Write, m_pml4_root_address); - } + } - // Return the address - return (void*)chunk->start_address; + // Return the address + return (void*) chunk->start_address; } /** @@ -216,29 +213,28 @@ void *VirtualMemoryManager::allocate(uint64_t address, size_t size, size_t flags */ void VirtualMemoryManager::new_region() { - // Space for the new region - physical_address_t* new_region_physical = PhysicalMemoryManager::s_current_manager->allocate_frame(); - ASSERT(new_region_physical != nullptr, "Failed to allocate new VMM region\n"); - - // Align the new region - auto* new_region = (virtual_memory_region_t*)PhysicalMemoryManager::align_to_page((uint64_t)m_current_region + PhysicalMemoryManager::s_page_size); + // Space for the new region + physical_address_t* new_region_physical = PhysicalMemoryManager::s_current_manager->allocate_frame(); + ASSERT(new_region_physical != nullptr, "Failed to allocate new VMM region\n"); - // Map the new region - PhysicalMemoryManager::s_current_manager->map(new_region_physical, (virtual_address_t*)new_region, Present | Write, m_pml4_root_address); - new_region->next = nullptr; + // Align the new region + auto* new_region = (virtual_memory_region_t*) PhysicalMemoryManager::align_to_page((uint64_t) m_current_region + PhysicalMemoryManager::s_page_size); - // Clear the new region - for (size_t i = 0; i < s_chunks_per_page; i++){ - new_region->chunks[i].size = 0; - new_region->chunks[i].flags = 0; - new_region->chunks[i].start_address = 0; - } + // Map the new region + PhysicalMemoryManager::s_current_manager->map(new_region_physical, (virtual_address_t*) new_region, Present | Write, m_pml4_root_address); + new_region->next = nullptr; - // Set the current region - m_current_region -> next = new_region; - m_current_chunk = 0; - m_current_region = new_region; + // Clear the new region + for (size_t i = 0; i < s_chunks_per_page; i++) { + new_region->chunks[i].size = 0; + new_region->chunks[i].flags = 0; + new_region->chunks[i].start_address = 0; + } + // Set the current region + m_current_region->next = new_region; + m_current_chunk = 0; + m_current_region = new_region; } /** @@ -246,54 +242,54 @@ void VirtualMemoryManager::new_region() { * * @param address The address of the memory to free */ -void VirtualMemoryManager::free(void *address) { +void VirtualMemoryManager::free(void* address) { - // Make sure freeing something - if(address == nullptr) - return; + // Make sure freeing something + if (address == nullptr) + return; - // Find the chunk - virtual_memory_region_t* region = m_first_region; - virtual_memory_chunk_t* chunk = nullptr; - while(region != nullptr){ + // Find the chunk + virtual_memory_region_t* region = m_first_region; + virtual_memory_chunk_t* chunk = nullptr; + while (region != nullptr) { - // Loop through the chunks - for (size_t i = 0; i < s_chunks_per_page; i++){ + // Loop through the chunks + for (size_t i = 0; i < s_chunks_per_page; i++) { - // Check if the address is in the chunk - if(region->chunks[i].start_address == (uintptr_t)address){ - chunk = ®ion->chunks[i]; - break; - } - } + // Check if the address is in the chunk + if (region->chunks[i].start_address == (uintptr_t) address) { + chunk = ®ion->chunks[i]; + break; + } + } - // If the chunk was found - if(chunk != nullptr) - break; + // If the chunk was found + if (chunk != nullptr) + break; - // Move to the next region - region = region->next; - } + // Move to the next region + region = region->next; + } - // Make sure the chunk was found - if(chunk == nullptr) - return; + // Make sure the chunk was found + if (chunk == nullptr) + return; - // If the chunk is shared, don't unmap it incase other processes are using it - if(chunk->flags & Shared){ + // If the chunk is shared, don't unmap it incase other processes are using it + if (chunk->flags & Shared) { - // Let the IPC handle the shared memory - Scheduler::scheduler_ipc()->free_shared_memory((uintptr_t)address); + // Let the IPC handle the shared memory + Scheduler::scheduler_ipc()->free_shared_memory((uintptr_t) address); - } + } - // Add the chunk to the free list - add_free_chunk(chunk->start_address, chunk->size); + // Add the chunk to the free list + add_free_chunk(chunk->start_address, chunk->size); - // Clear the chunk - chunk->size = 0; - chunk->flags = 0; - chunk->start_address = 0; + // Clear the chunk + chunk->size = 0; + chunk->flags = 0; + chunk->start_address = 0; } /** @@ -303,26 +299,26 @@ void VirtualMemoryManager::free(void *address) { */ size_t VirtualMemoryManager::memory_used() { - // Loop through all the regions and add up the size of the allocated chunks - size_t result = 0; + // Loop through all the regions and add up the size of the allocated chunks + size_t result = 0; - // Iterate through the regions - virtual_memory_region_t *region = m_first_region; - while (region != nullptr) { + // Iterate through the regions + virtual_memory_region_t* region = m_first_region; + while (region != nullptr) { - // Loop through the chunks - for (size_t i = 0; i < s_chunks_per_page; i++) { + // Loop through the chunks + for (size_t i = 0; i < s_chunks_per_page; i++) { - // Check if the address is in the chunk - if (region->chunks[i].size != 0) - result += region->chunks[i].size; - } + // Check if the address is in the chunk + if (region->chunks[i].size != 0) + result += region->chunks[i].size; + } - // Move to the next region - region = region->next; - } + // Move to the next region + region = region->next; + } - return result; + return result; } /** @@ -333,15 +329,14 @@ size_t VirtualMemoryManager::memory_used() { */ void VirtualMemoryManager::add_free_chunk(uintptr_t start_address, size_t size) { + // Create the new chunk + auto* new_chunk = (free_chunk_t*) start_address; + new_chunk->start_address = start_address; + new_chunk->size = size; + new_chunk->next = m_free_chunks; - // Create the new chunk - auto* new_chunk = (free_chunk_t*)start_address; - new_chunk->start_address = start_address; - new_chunk->size = size; - new_chunk->next = m_free_chunks; - - // Set the new chunk - m_free_chunks = new_chunk; + // Set the new chunk + m_free_chunks = new_chunk; } /** @@ -352,35 +347,35 @@ void VirtualMemoryManager::add_free_chunk(uintptr_t start_address, size_t size) */ free_chunk_t* VirtualMemoryManager::find_and_remove_free_chunk(size_t size) { - // Find the chunk - free_chunk_t* current = m_free_chunks; - free_chunk_t* previous = nullptr; - while(current != nullptr){ + // Find the chunk + free_chunk_t* current = m_free_chunks; + free_chunk_t* previous = nullptr; + while (current != nullptr) { - // Check if the chunk is big enough - if(current->size >= size){ + // Check if the chunk is big enough + if (current->size >= size) { - // TODO: Some other time this will need to be split if the chunk is too big + // TODO: Some other time this will need to be split if the chunk is too big - // Remove the chunk - if(previous != nullptr){ - previous->next = current->next; - }else{ - m_free_chunks = current->next; - } + // Remove the chunk + if (previous != nullptr) { + previous->next = current->next; + } else { + m_free_chunks = current->next; + } - // Return the chunk - return current; + // Return the chunk + return current; - } + } - // Move to the next chunk - previous = current; - current = current->next; - } + // Move to the next chunk + previous = current; + current = current->next; + } - // No chunk found - return nullptr; + // No chunk found + return nullptr; } @@ -389,8 +384,9 @@ free_chunk_t* VirtualMemoryManager::find_and_remove_free_chunk(size_t size) { * * @return The physical address of the PML4 root */ -uint64_t *VirtualMemoryManager::pml4_root_address_physical() { - return m_pml4_root_physical_address; +uint64_t* VirtualMemoryManager::pml4_root_address_physical() { + + return m_pml4_root_physical_address; } /** @@ -399,16 +395,16 @@ uint64_t *VirtualMemoryManager::pml4_root_address_physical() { * @param name The name of the shared memory * @return The address of the shared memory in the VMM's address space */ -void *VirtualMemoryManager::load_shared_memory(const string& name) { +void* VirtualMemoryManager::load_shared_memory(const string& name) { - // Get the shared memory block - SharedMemory* block = Scheduler::scheduler_ipc()->get_shared_memory(name); + // Get the shared memory block + SharedMemory* block = Scheduler::scheduler_ipc()->get_shared_memory(name); - // Load the shared memory - if(block != nullptr) - return load_shared_memory(block -> physical_address(), block -> size()); + // Load the shared memory + if (block != nullptr) + return load_shared_memory(block->physical_address(), block->size()); - return nullptr; + return nullptr; } /** @@ -418,14 +414,14 @@ void *VirtualMemoryManager::load_shared_memory(const string& name) { * @param size The size of the shared memory * @return The address of the shared memory in the VMM's address space */ -void *VirtualMemoryManager::load_shared_memory(uintptr_t physical_address, size_t size) { +void* VirtualMemoryManager::load_shared_memory(uintptr_t physical_address, size_t size) { - // Make sure there is somthing to map - if(size == 0 || physical_address == 0) - return nullptr; + // Make sure there is somthing to map + if (size == 0 || physical_address == 0) + return nullptr; - // Load it into physical memory - return load_physical_into_address_space(physical_address, size, Shared); + // Load it into physical memory + return load_physical_into_address_space(physical_address, size, Shared); } /** @@ -436,22 +432,19 @@ void *VirtualMemoryManager::load_shared_memory(uintptr_t physical_address, size_ * @param flags The flags to set on the memory * @return The address of the memory in the VMM's address space */ -void *VirtualMemoryManager::load_physical_into_address_space(uintptr_t physical_address, size_t size, size_t flags){ - - // Reserve some space - void* address = allocate(size, flags | Reserve); +void* VirtualMemoryManager::load_physical_into_address_space(uintptr_t physical_address, size_t size, size_t flags) { - // Map the shared memory - size_t pages = PhysicalMemoryManager::size_to_frames(size); - for (size_t i = 0; i < pages; i++){ + // Reserve some space + void* address = allocate(size, flags | Reserve); - // Map the frame - PhysicalMemoryManager::s_current_manager->map((physical_address_t*)(physical_address + (i * PhysicalMemoryManager::s_page_size)), (virtual_address_t*)((uintptr_t)address + (i * PhysicalMemoryManager::s_page_size)), Present | Write, m_pml4_root_address); + // Map the shared memory + size_t pages = PhysicalMemoryManager::size_to_frames(size); + for (size_t i = 0; i < pages; i++) + PhysicalMemoryManager::s_current_manager->map((physical_address_t*) (physical_address + (i * PhysicalMemoryManager::s_page_size)), (virtual_address_t*) ((uintptr_t) address + (i * PhysicalMemoryManager::s_page_size)), Present | Write, m_pml4_root_address); - } - // All done - return address; + // All done + return address; } /** @@ -463,41 +456,41 @@ void *VirtualMemoryManager::load_physical_into_address_space(uintptr_t physical_ */ void VirtualMemoryManager::fill_up_to_address(uintptr_t address, size_t flags, bool mark_used) { - // Make sure the address is aligned - address = PhysicalMemoryManager::align_to_page(address); + // Make sure the address is aligned + address = PhysicalMemoryManager::align_to_page(address); - // Make sure the address is not before the next available address - ASSERT(address >= m_next_available_address, "FAILED TO FILL: Address is before the next available address - 0x%x < 0x%x\n", address, m_next_available_address); + // Make sure the address is not before the next available address + ASSERT(address >= m_next_available_address, "FAILED TO FILL: Address is before the next available address - 0x%x < 0x%x\n", address, m_next_available_address); - // Calculate the size - size_t size = address - m_next_available_address; + // Calculate the size + size_t size = address - m_next_available_address; - // Allocate the memory - virtual_memory_chunk_t* chunk = &m_current_region->chunks[m_current_chunk]; - chunk->size = size; - chunk->flags = flags; - chunk->start_address = m_next_available_address; + // Allocate the memory + virtual_memory_chunk_t* chunk = &m_current_region->chunks[m_current_chunk]; + chunk->size = size; + chunk->flags = flags; + chunk->start_address = m_next_available_address; - // Update the next available address - m_next_available_address += size; - m_current_chunk++; + // Update the next available address + m_next_available_address += size; + m_current_chunk++; - // Map the memory - size_t pages = PhysicalMemoryManager::size_to_frames(size); - for (size_t i = 0; i < pages; i++){ + // Map the memory + size_t pages = PhysicalMemoryManager::size_to_frames(size); + for (size_t i = 0; i < pages; i++) { - // Allocate a new frame - physical_address_t* frame = PhysicalMemoryManager::s_current_manager->allocate_frame(); - ASSERT(frame != nullptr, "Failed to allocate frame (from fill up)\n"); + // Allocate a new frame + physical_address_t* frame = PhysicalMemoryManager::s_current_manager->allocate_frame(); + ASSERT(frame != nullptr, "Failed to allocate frame (from fill up)\n"); - // Map the frame - PhysicalMemoryManager::s_current_manager->map(frame, (virtual_address_t*)chunk->start_address + (i * PhysicalMemoryManager::s_page_size), Present | Write, m_pml4_root_address); + // Map the frame + PhysicalMemoryManager::s_current_manager->map(frame, (virtual_address_t*) chunk->start_address + (i * PhysicalMemoryManager::s_page_size), Present | Write, m_pml4_root_address); - } + } - // Mark as free if needed - if(!mark_used) - free((void*)chunk->start_address); + // Mark as free if needed + if (!mark_used) + free((void*) chunk->start_address); } /** @@ -505,12 +498,12 @@ void VirtualMemoryManager::fill_up_to_address(uintptr_t address, size_t flags, b * * @return The virtual address or nullptr if not found */ -uint64_t *VirtualMemoryManager::pml4_root_address() { +uint64_t* VirtualMemoryManager::pml4_root_address() { - // Make sure the address is valid - if(m_pml4_root_address == nullptr) - return nullptr; + // Make sure the address is valid + if (m_pml4_root_address == nullptr) + return nullptr; - // Return the address - return m_pml4_root_address; + // Return the address + return m_pml4_root_address; } \ No newline at end of file diff --git a/kernel/src/processes/elf.cpp b/kernel/src/processes/elf.cpp index 293f26a9..fe7c5b6d 100644 --- a/kernel/src/processes/elf.cpp +++ b/kernel/src/processes/elf.cpp @@ -24,19 +24,18 @@ Elf64::Elf64(uintptr_t elf_header_address) /** * @brief Destructor for the Elf64 class */ -Elf64::~Elf64() -= default; +Elf64::~Elf64() = default; /** * @brief Loads the elf program into memory if a valid elf file */ void Elf64::load() { - //TODO: error handling when the syscall for this is implemented - if(!is_valid()) - return; + //TODO: error handling when the syscall for this is implemented + if (!is_valid()) + return; - load_program_headers(); + load_program_headers(); } @@ -45,9 +44,9 @@ void Elf64::load() { * * @return The header of the elf file */ -elf_64_header_t *Elf64::header() const { +elf_64_header_t* Elf64::header() const { - return (elf_64_header_t*)m_elf_header_address; + return (elf_64_header_t*) m_elf_header_address; } /** @@ -56,15 +55,15 @@ elf_64_header_t *Elf64::header() const { * @param index The index of the program header * @return The program header at that index or nullptr if out of bounds */ -elf_64_program_header_t* Elf64::get_program_header(size_t index) { +elf_64_program_header_t* Elf64::get_program_header(size_t index) const { - // Check bounds - if(index >= header() -> program_header_count) - return nullptr; + // Check bounds + if (index >= header()->program_header_count) + return nullptr; - // Find the program headers and return the item - auto* program_headers = (elf_64_program_header_t*)(m_elf_header_address + header() -> program_header_offset); - return &program_headers[index]; + // Find the program headers and return the item + auto* program_headers = (elf_64_program_header_t*) (m_elf_header_address + header()->program_header_offset); + return &program_headers[index]; } @@ -74,87 +73,86 @@ elf_64_program_header_t* Elf64::get_program_header(size_t index) { * @param index The index of the section header * @return The section header at that index or nullptr if out of bounds */ -elf_64_section_header_t *Elf64::get_section_header(size_t index) { +elf_64_section_header_t* Elf64::get_section_header(size_t index) const { - // Check bound - if(index >= header() -> section_header_count) - return nullptr; + // Check bound + if (index >= header()->section_header_count) + return nullptr; - // Find the section headers and return the item - auto* section_headers = (elf_64_section_header_t*)(m_elf_header_address + header() -> section_header_offset); - return §ion_headers[index]; + // Find the section headers and return the item + auto* section_headers = (elf_64_section_header_t*) (m_elf_header_address + header()->section_header_offset); + return §ion_headers[index]; } /** * @brief Checks if the elf file is valid for MaxOS runtime */ -bool Elf64::is_valid() { +bool Elf64::is_valid() const { - // Validate the magic number - for (size_t i = 0; i < 4; i++) - if (header() -> identification[i] != elf_magic[i]) - return false; + // Validate the magic number + for (size_t i = 0; i < 4; i++) + if (header()->identification[i] != elf_magic[i]) + return false; - // Check if the elf is 64 bit - if(header() -> identification[(int)ElfIdentification::Class] != (int)ElfClass::Bits64) - return false; + // Check if the elf is 64 bit + if (header()->identification[(int) ElfIdentification::Class] != (int) ElfClass::Bits64) + return false; - // Check if the elf is little endian - if(header() -> identification[(int)ElfIdentification::Data] != (int)ElfData::LittleEndian) - return false; + // Check if the elf is little endian + if (header()->identification[(int) ElfIdentification::Data] != (int) ElfData::LittleEndian) + return false; - // Check if the elf is version 1 - if(header() -> identification[(int)ElfIdentification::Version] != (int)ElfVersion::Current) - return false; + // Check if the elf is version 1 + if (header()->identification[(int) ElfIdentification::Version] != (int) ElfVersion::Current) + return false; - // Check if the elf is for the MaxOS platform - // if(header() -> identification[OSABI] != MaxOSABI) - // return false; TODO: Would be nice to have an OSABI + // Check if the elf is for the MaxOS platform + // if(header() -> identification[OSABI] != MaxOSABI) + // return false; TODO: Would be nice to have an OS ABI - // Check if the elf is executable - if(header() -> type != (int)ElfType::Executable) - return false; + // Check if the elf is executable + if (header()->type != (int) ElfType::Executable) + return false; - // Check if the elf is for the x86_64 platform - if(header() -> machine != (int)ElfMachine::x86_64) - return false; + // Check if the elf is for the x86_64 platform + if (header()->machine != (int) ElfMachine::x86_64) + return false; - // LGTM - return true; + // LGTM + return true; } /** * @brief Loop through the program headers and load the program into memory at the give address with the given size */ -void Elf64::load_program_headers() { +void Elf64::load_program_headers() const { - for (size_t i = 0; i < header() -> program_header_count; i++) { + for (size_t i = 0; i < header()->program_header_count; i++) { - // Get the header information - elf_64_program_header_t* program_header = get_program_header(i); + // Get the header information + elf_64_program_header_t* program_header = get_program_header(i); - // Only load headers that actually need loading - if(program_header -> type != (int)ElfProgramType::Load) - continue; + // Only load headers that actually need loading + if (program_header->type != (int) ElfProgramType::Load) + continue; - // Allocate space at the requested address - void* address = MemoryManager::s_current_memory_manager -> vmm() -> allocate(program_header -> virtual_address, program_header -> memory_size, Present | Write); - ASSERT(address != nullptr, "Failed to allocate memory for program header\n"); + // Allocate space at the requested address + void* address = MemoryManager::s_current_memory_manager->vmm()->allocate(program_header->virtual_address, program_header->memory_size, Present | Write); + ASSERT(address != nullptr, "Failed to allocate memory for program header\n"); - // Copy the program into memory at that address - memcpy(address, (void*)(m_elf_header_address + program_header -> offset), program_header -> file_size); + // Copy the program into memory at that address + memcpy(address, (void*) (m_elf_header_address + program_header->offset), program_header->file_size); - // Zero the rest of the memory if needed - size_t zero_size = program_header -> memory_size - program_header -> file_size; - memset((void*)((uintptr_t)address + program_header -> file_size), 0, zero_size); - - // Once the memory has been copied can now mark the pages as read only etc - uint64_t flags = to_vmm_flags(program_header->flags); - PhysicalMemoryManager::s_current_manager -> change_page_flags(address, flags, MemoryManager::s_current_memory_manager -> vmm() -> pml4_root_address()); - } + // Zero the rest of the memory if needed + size_t zero_size = program_header->memory_size - program_header->file_size; + memset((void*) ((uintptr_t) address + program_header->file_size), 0, zero_size); + // Once the memory has been copied can now mark the pages as read only etc + uint64_t flags = to_vmm_flags(program_header->flags); + PhysicalMemoryManager::s_current_manager->change_page_flags(address, flags, MemoryManager::s_current_memory_manager->vmm()->pml4_root_address()); + } } /** @@ -165,22 +163,21 @@ void Elf64::load_program_headers() { */ uint64_t Elf64::to_vmm_flags(uint32_t type) { - // Conversion - // ELF | VMM - // 0x0 | Executable - // 0x1 | Write - // 0x2 | Read - - uint64_t flags = Present | User | NoExecute; + // Conversion + // ELF | VMM + // 0x0 | Executable + // 0x1 | Write + // 0x2 | Read - // Enable write - if(type & ElfWrite) - flags |= Write; + uint64_t flags = Present | User | NoExecute; - // Disable no execute - if(type & ElfExecute) - flags &= ~NoExecute; + // Enable write + if (type & ElfWrite) + flags |= Write; - return flags; + // Disable no execute + if (type & ElfExecute) + flags &= ~NoExecute; + return flags; } \ No newline at end of file diff --git a/kernel/src/processes/ipc.cpp b/kernel/src/processes/ipc.cpp index f42f909f..689f7b87 100644 --- a/kernel/src/processes/ipc.cpp +++ b/kernel/src/processes/ipc.cpp @@ -2,6 +2,7 @@ // Created by 98max on 24/03/2025. // #include + using namespace MaxOS; using namespace MaxOS::processes; using namespace MaxOS::common; @@ -17,9 +18,9 @@ InterProcessCommunicationManager::InterProcessCommunicationManager() = default; InterProcessCommunicationManager::~InterProcessCommunicationManager() { - // Free all the shared memory - for(auto block : m_shared_memory_blocks) - delete block; + // Free all the shared memory + for (auto block: m_shared_memory_blocks) + delete block; } /** @@ -31,24 +32,24 @@ InterProcessCommunicationManager::~InterProcessCommunicationManager() { */ SharedMemory* InterProcessCommunicationManager::alloc_shared_memory(size_t size, string name) { - // Wait other processes to finish creating their blocks in order to ensure uniqueness - m_lock.lock(); + // Wait other processes to finish creating their blocks in order to ensure uniqueness + m_lock.lock(); - // Make sure the name is unique - for(auto endpoint : m_message_endpoints) - if(endpoint -> name -> equals(name)){ - m_lock.unlock(); - return nullptr; - } + // Make sure the name is unique + for (auto endpoint: m_message_endpoints) + if (endpoint->name->equals(name)) { + m_lock.unlock(); + return nullptr; + } - // Create the shared memory block - auto* block = new SharedMemory(name, size); - m_shared_memory_blocks.push_back(block); - Logger::DEBUG() << "Created shared memory block " << name << " at 0x" << block -> physical_address() << "\n"; + // Create the shared memory block + auto* block = new SharedMemory(name, size); + m_shared_memory_blocks.push_back(block); + Logger::DEBUG() << "Created shared memory block " << name << " at 0x" << block->physical_address() << "\n"; - // Return the block - m_lock.unlock(); - return block; + // Return the block + m_lock.unlock(); + return block; } /** @@ -59,26 +60,25 @@ SharedMemory* InterProcessCommunicationManager::alloc_shared_memory(size_t size, */ SharedMemory* InterProcessCommunicationManager::get_shared_memory(const string& name) { - // Wait for shared memory to be fully created before trying to search for it - m_lock.lock(); - - // Find the block - for(auto block : m_shared_memory_blocks){ + // Wait for shared memory to be fully created before trying to search for it + m_lock.lock(); - // Block has wrong name - if(!block -> name -> equals(name)) - continue; + // Find the block + for (auto block: m_shared_memory_blocks) { - // Block is now in use - block -> use_count++; - m_lock.unlock(); - return block; - } + // Block has wrong name + if (!block->name->equals(name)) + continue; - // Not found - m_lock.unlock(); - return nullptr; + // Block is now in use + block->use_count++; + m_lock.unlock(); + return block; + } + // Not found + m_lock.unlock(); + return nullptr; } /** @@ -88,13 +88,12 @@ SharedMemory* InterProcessCommunicationManager::get_shared_memory(const string& */ void InterProcessCommunicationManager::free_shared_memory(uintptr_t physical_address) { - // Find the block - for(auto block : m_shared_memory_blocks) - if(block -> physical_address() == physical_address){ - free_shared_memory(block); - return; - } - + // Find the block + for (auto block: m_shared_memory_blocks) + if (block->physical_address() == physical_address) { + free_shared_memory(block); + return; + } } /** @@ -104,18 +103,16 @@ void InterProcessCommunicationManager::free_shared_memory(uintptr_t physical_add */ void InterProcessCommunicationManager::free_shared_memory(const string& name) { + // Find the block + for (auto block: m_shared_memory_blocks) { - // Find the block - for (auto block : m_shared_memory_blocks) { - - // Check if the block is the one we are looking for - if (!block->name->equals(name)) - continue; + // Check if the block is the one we are looking for + if (!block->name->equals(name)) + continue; - free_shared_memory(block); - - } + free_shared_memory(block); + } } /** @@ -125,24 +122,23 @@ void InterProcessCommunicationManager::free_shared_memory(const string& name) { */ void InterProcessCommunicationManager::free_shared_memory(SharedMemory* block) { - // Wait for the lock - m_lock.lock(); - - // One less process is using it - block->use_count--; + // Wait for the lock + m_lock.lock(); - // If the block is in use let those processes handle it - if (block->use_count > 0) { - m_lock.unlock(); - return; - } + // One less process is using it + block->use_count--; - Logger::DEBUG() << "Deleting shared memory block " << block->name->c_str() << " at 0x" << block -> physical_address() << "\n"; + // If the block is in use let those processes handle it + if (block->use_count > 0) { + m_lock.unlock(); + return; + } - // Free the block - delete block; - m_lock.unlock(); + Logger::DEBUG() << "Deleting shared memory block " << block->name->c_str() << " at 0x" << block->physical_address() << "\n"; + // Free the block + delete block; + m_lock.unlock(); } /** @@ -153,21 +149,21 @@ void InterProcessCommunicationManager::free_shared_memory(SharedMemory* block) { */ SharedMessageEndpoint* InterProcessCommunicationManager::create_message_endpoint(const string& name) { - // Wait for the lock - m_lock.lock(); + // Wait for the lock + m_lock.lock(); - // Make sure the name is unique - if(get_message_endpoint(name) != nullptr){ - m_lock.unlock(); - return nullptr; - } + // Make sure the name is unique + if (get_message_endpoint(name) != nullptr) { + m_lock.unlock(); + return nullptr; + } - // Create the endpoint - auto* endpoint = new SharedMessageEndpoint(name); - m_message_endpoints.push_back(endpoint); + // Create the endpoint + auto* endpoint = new SharedMessageEndpoint(name); + m_message_endpoints.push_back(endpoint); - m_lock.unlock(); - return endpoint; + m_lock.unlock(); + return endpoint; } /** @@ -178,14 +174,13 @@ SharedMessageEndpoint* InterProcessCommunicationManager::create_message_endpoint */ SharedMessageEndpoint* InterProcessCommunicationManager::get_message_endpoint(const string& name) { - // Try to find the endpoint - for(auto endpoint : m_message_endpoints) - if(endpoint -> name -> equals(name)) - return endpoint; - - // Not found - return nullptr; + // Try to find the endpoint + for (auto endpoint: m_message_endpoints) + if (endpoint->name->equals(name)) + return endpoint; + // Not found + return nullptr; } /** @@ -195,7 +190,7 @@ SharedMessageEndpoint* InterProcessCommunicationManager::get_message_endpoint(co */ void InterProcessCommunicationManager::free_message_endpoint(const string& name) { - free_message_endpoint(get_message_endpoint(name)); + free_message_endpoint(get_message_endpoint(name)); } /** @@ -205,17 +200,16 @@ void InterProcessCommunicationManager::free_message_endpoint(const string& name) */ void InterProcessCommunicationManager::free_message_endpoint(SharedMessageEndpoint* endpoint) { - // Make sure the endpoint exists - if(endpoint == nullptr) - return; - - // Make sure the endpoint is owned by the current process - if(endpoint -> owned_by_current_process()) - return; + // Make sure the endpoint exists + if (endpoint == nullptr) + return; - // Delete the endpoint - delete endpoint; + // Make sure the endpoint is owned by the current process + if (endpoint->owned_by_current_process()) + return; + // Delete the endpoint + delete endpoint; } /** @@ -223,19 +217,18 @@ void InterProcessCommunicationManager::free_message_endpoint(SharedMessageEndpoi * * @param name The name of the block */ -SharedMemory::SharedMemory(string name, size_t size) +SharedMemory::SharedMemory(const string& name, size_t size) : m_size(size), - name(new string(name)) + name(new string(name)) //TODO: using a new here? { - m_physical_address = (uintptr_t)PhysicalMemoryManager::s_current_manager -> allocate_area(0, size); - m_owner_pid = Scheduler::current_process()->pid(); - + m_physical_address = (uintptr_t) PhysicalMemoryManager::s_current_manager->allocate_area(0, size); + m_owner_pid = Scheduler::current_process()->pid(); } SharedMemory::~SharedMemory() { - PhysicalMemoryManager::s_current_manager->free_area(m_physical_address, m_size); + PhysicalMemoryManager::s_current_manager->free_area(m_physical_address, m_size); } /** @@ -245,8 +238,7 @@ SharedMemory::~SharedMemory() { */ uintptr_t SharedMemory::physical_address() const { - return m_physical_address; - + return m_physical_address; } /** @@ -256,40 +248,37 @@ uintptr_t SharedMemory::physical_address() const { */ size_t SharedMemory::size() const { - return m_size; - + return m_size; } -SharedMessageEndpoint::SharedMessageEndpoint(string name) +SharedMessageEndpoint::SharedMessageEndpoint(const string& name) : name(new string(name)) { - // Make the queue in the user's memory space - m_queue = (ipc_message_queue_t*)MemoryManager::malloc(sizeof(ipc_message_queue_t)); - - // Store that this process owns it - m_owner_pid = Scheduler::current_process() -> pid(); - + // Make the queue in the user's memory space + m_queue = (ipc_message_queue_t*) MemoryManager::malloc(sizeof(ipc_message_queue_t)); + // Store that this process owns it + m_owner_pid = Scheduler::current_process()->pid(); } SharedMessageEndpoint::~SharedMessageEndpoint() { - m_message_lock.lock(); + m_message_lock.lock(); - // Delete the messages - ipc_message_t* message = m_queue -> messages; - while(message != nullptr){ - auto* next = (ipc_message_t*)message -> next_message; - MemoryManager::free(message -> message_buffer); - message = next; - } + // Delete the messages + ipc_message_t* message = m_queue->messages; + while (message != nullptr) { + auto* next = (ipc_message_t*) message->next_message; + MemoryManager::free(message->message_buffer); + message = next; + } - // Free the memory - MemoryManager::free(m_queue); - delete name; + // Free the memory + MemoryManager::free(m_queue); + delete name; - m_message_lock.unlock(); + m_message_lock.unlock(); } /** @@ -297,10 +286,9 @@ SharedMessageEndpoint::~SharedMessageEndpoint() { * * @return The message queue */ -ipc_message_queue_t *SharedMessageEndpoint::queue() const { - - return m_queue; +ipc_message_queue_t* SharedMessageEndpoint::queue() const { + return m_queue; } /** @@ -310,8 +298,7 @@ ipc_message_queue_t *SharedMessageEndpoint::queue() const { */ bool SharedMessageEndpoint::owned_by_current_process() const { - return m_owner_pid == Scheduler::current_process() -> pid(); - + return m_owner_pid == Scheduler::current_process()->pid(); } /** @@ -320,40 +307,40 @@ bool SharedMessageEndpoint::owned_by_current_process() const { * @param message The message to queue * @param size The size of the message */ -void SharedMessageEndpoint::queue_message(void *message, size_t size) { - - // Wait for the lock - m_message_lock.lock(); - - // Copy the buffer into the kernel so that the endpoint (this code) can access it when the memory spaces are switched - buffer_t kernel_copy(size); - kernel_copy.copy_from(message,size); - - //Switch to endpoint's memory space - MemoryManager::switch_active_memory_manager(Scheduler::get_process(m_owner_pid) -> memory_manager); - - // Create the message & copy it into the endpoint's memory space - auto* new_message = (ipc_message_t*)MemoryManager::malloc(sizeof(ipc_message_t)); - void* new_buffer = MemoryManager::malloc(size); - new_message -> message_buffer = memcpy(new_buffer, kernel_copy.raw(), size); - new_message -> message_size = size; - new_message -> next_message = 0; - - // Add the message to the end of the queue - ipc_message_t* current = m_queue -> messages; - while(current != nullptr){ - if(current -> next_message == 0){ - current -> next_message = (uintptr_t)new_message; - break; - } - current = (ipc_message_t*)current -> next_message; - } - - // If it was the first message - if (current == nullptr) - m_queue->messages = new_message; - - // Clean up - MemoryManager::switch_active_memory_manager(Scheduler::current_process() -> memory_manager); - m_message_lock.unlock(); +void SharedMessageEndpoint::queue_message(void* message, size_t size) { + + // Wait for the lock + m_message_lock.lock(); + + // Copy the buffer into the kernel so that the endpoint (this code) can access it when the memory spaces are switched + buffer_t kernel_copy(size); + kernel_copy.copy_from(message, size); + + //Switch to endpoint's memory space + MemoryManager::switch_active_memory_manager(Scheduler::get_process(m_owner_pid)->memory_manager); + + // Create the message & copy it into the endpoint's memory space + auto* new_message = (ipc_message_t*) MemoryManager::malloc(sizeof(ipc_message_t)); + void* new_buffer = MemoryManager::malloc(size); + new_message->message_buffer = memcpy(new_buffer, kernel_copy.raw(), size); + new_message->message_size = size; + new_message->next_message = 0; + + // Add the message to the end of the queue + ipc_message_t* current = m_queue->messages; + while (current != nullptr) { + if (current->next_message == 0) { + current->next_message = (uintptr_t) new_message; + break; + } + current = (ipc_message_t*) current->next_message; + } + + // If it was the first message + if (current == nullptr) + m_queue->messages = new_message; + + // Clean up + MemoryManager::switch_active_memory_manager(Scheduler::current_process()->memory_manager); + m_message_lock.unlock(); } \ No newline at end of file diff --git a/kernel/src/processes/process.cpp b/kernel/src/processes/process.cpp index 5df186dc..2a5fb274 100644 --- a/kernel/src/processes/process.cpp +++ b/kernel/src/processes/process.cpp @@ -15,53 +15,52 @@ using namespace MaxOS::common; /** * @brief Constructor for the Thread class */ -Thread::Thread(void (*_entry_point)(void *), void *args, int arg_amount, Process* parent) { - - // Basic setup - thread_state = ThreadState::NEW; - wakeup_time = 0; - ticks = 0; - - // Create the stack - m_stack_pointer = (uintptr_t)MemoryManager::malloc(s_stack_size); - - // Create the TSS stack - if(parent -> is_kernel) { - - // Use the kernel stack - m_tss_stack_pointer = CPU::tss.rsp0; - - } else{ - m_tss_stack_pointer = (uintptr_t)MemoryManager::kmalloc(s_stack_size) + s_stack_size; - } - - // Mak sure there is a stack - ASSERT(m_stack_pointer != 0 && m_tss_stack_pointer != 0, "Failed to allocate stack for thread"); - - // Set up the execution state - execution_state = new cpu_status_t; - execution_state -> rip = (uint64_t)_entry_point; - execution_state -> ss = parent -> is_kernel ? 0x10 : 0x23; - execution_state -> cs = parent -> is_kernel ? 0x8 : 0x1B; - execution_state -> rflags = 0x202; - execution_state -> interrupt_number = 0; - execution_state -> error_code = 0; - execution_state -> rsp = m_stack_pointer; - execution_state -> rbp = 0; - - // Copy the args into userspace - uint64_t argc = arg_amount; - void* argv = MemoryManager::malloc(arg_amount * sizeof(void*)); - memcpy(argv, args, arg_amount * sizeof(void*)); - - execution_state->rdi = argc; - execution_state->rsi = (uint64_t)argv; - //execution_state->rdx = (uint64_t)env_args; - - // Begin scheduling this thread - parent_pid = parent->pid(); - tid = Scheduler::system_scheduler() -> add_thread(this); - +Thread::Thread(void (* _entry_point)(void*), void* args, int arg_amount, Process* parent) { + + // Basic setup + thread_state = ThreadState::NEW; + wakeup_time = 0; + ticks = 0; + + // Create the stack + m_stack_pointer = (uintptr_t) MemoryManager::malloc(s_stack_size); + + // Create the TSS stack + if (parent->is_kernel) { + + // Use the kernel stack + m_tss_stack_pointer = CPU::tss.rsp0; + + } else { + m_tss_stack_pointer = (uintptr_t) MemoryManager::kmalloc(s_stack_size) + s_stack_size; + } + + // Mak sure there is a stack + ASSERT(m_stack_pointer != 0 && m_tss_stack_pointer != 0, "Failed to allocate stack for thread"); + + // Set up the execution state + execution_state = new cpu_status_t; + execution_state->rip = (uint64_t) _entry_point; + execution_state->ss = parent->is_kernel ? 0x10 : 0x23; + execution_state->cs = parent->is_kernel ? 0x8 : 0x1B; + execution_state->rflags = 0x202; + execution_state->interrupt_number = 0; + execution_state->error_code = 0; + execution_state->rsp = m_stack_pointer; + execution_state->rbp = 0; + + // Copy the args into userspace + uint64_t argc = arg_amount; + void* argv = MemoryManager::malloc(arg_amount * sizeof(void*)); + memcpy(argv, args, arg_amount * sizeof(void*)); + + execution_state->rdi = argc; + execution_state->rsi = (uint64_t) argv; + //execution_state->rdx = (uint64_t)env_args; + + // Begin scheduling this thread + parent_pid = parent->pid(); + tid = Scheduler::system_scheduler()->add_thread(this); } /** @@ -77,13 +76,12 @@ Thread::~Thread() = default; */ cpu_status_t* Thread::sleep(size_t milliseconds) { - // Update the state - thread_state = ThreadState::SLEEPING; - wakeup_time = Scheduler::system_scheduler() -> ticks() + milliseconds; - - // Let other processes do stuff while this thread is sleeping - return Scheduler::system_scheduler() -> yield(); + // Update the state + thread_state = ThreadState::SLEEPING; + wakeup_time = Scheduler::system_scheduler()->ticks() + milliseconds; + // Let other processes do stuff while this thread is sleeping + return Scheduler::system_scheduler()->yield(); } /** @@ -91,13 +89,12 @@ cpu_status_t* Thread::sleep(size_t milliseconds) { */ void Thread::save_sse_state() { - // Ensure the state saving is enabled - if(!CPU::s_xsave) - return; - - // Save the state - asm volatile("fxsave %0" : "=m" (m_sse_save_region)); + // Ensure the state saving is enabled + if (!CPU::s_xsave) + return; + // Save the state + asm volatile("fxsave %0" : "=m" (m_sse_save_region)); } /** @@ -105,13 +102,12 @@ void Thread::save_sse_state() { */ void Thread::restore_sse_state() { - // Ensure the state saving is enabled - if(!CPU::s_xsave) - return; - - // Restore the state - asm volatile("fxrstor %0" : : "m" (m_sse_save_region)); + // Ensure the state saving is enabled + if (!CPU::s_xsave) + return; + // Restore the state + asm volatile("fxrstor %0" : : "m" (m_sse_save_region)); } /** @@ -124,16 +120,17 @@ Process::Process(const string& p_name, bool is_kernel) : is_kernel(is_kernel), name(p_name) { - // Pause interrupts while creating the process - asm("cli"); - // Basic setup - m_pid = Scheduler::system_scheduler() ->add_process(this); + // Pause interrupts while creating the process + asm("cli"); + + // Basic setup + m_pid = Scheduler::system_scheduler()->add_process(this); - // If it is a kernel process then don't need a new memory manager - memory_manager = is_kernel ? MemoryManager::s_kernel_memory_manager : new MemoryManager(); + // If it is a kernel process then don't need a new memory manager + memory_manager = is_kernel ? MemoryManager::s_kernel_memory_manager : new MemoryManager(); - // Resuming interrupts are done when adding a thread + // Resuming interrupts are done when adding a thread } /** @@ -145,16 +142,15 @@ Process::Process(const string& p_name, bool is_kernel) * @param arg_amount The amount of arguments * @param is_kernel If the process is a kernel process */ -Process::Process(const string& p_name, void (*_entry_point)(void *), void *args, int arg_amount, bool is_kernel) +Process::Process(const string& p_name, void (* _entry_point)(void*), void* args, int arg_amount, bool is_kernel) : Process(p_name, is_kernel) { - // Create the main thread - auto* main_thread = new Thread(_entry_point, args, arg_amount, this); - - // Add the thread - add_thread(main_thread); + // Create the main thread + auto* main_thread = new Thread(_entry_point, args, arg_amount, this); + // Add the thread + add_thread(main_thread); } /** @@ -166,17 +162,17 @@ Process::Process(const string& p_name, void (*_entry_point)(void *), void *args, * @param elf The elf file to load the process from * @param is_kernel If the process is a kernel process */ -Process::Process(const string& p_name, void *args, int arg_amount, Elf64* elf, bool is_kernel) +Process::Process(const string& p_name, void* args, int arg_amount, Elf64* elf, bool is_kernel) : Process(p_name, is_kernel) { - // Get the entry point - elf -> load(); - auto* entry_point = (void (*)(void *))elf -> header() -> entry; + // Get the entry point + elf->load(); + auto* entry_point = (void (*)(void*)) elf->header()->entry; - // Create the main thread - auto* main_thread = new Thread(entry_point, args, arg_amount, this); - add_thread(main_thread); + // Create the main thread + auto* main_thread = new Thread(entry_point, args, arg_amount, this); + add_thread(main_thread); } @@ -185,18 +181,18 @@ Process::Process(const string& p_name, void *args, int arg_amount, Elf64* elf, b */ Process::~Process() { - uint64_t pages = PhysicalMemoryManager::s_current_manager -> memory_used(); + uint64_t pages = PhysicalMemoryManager::s_current_manager->memory_used(); - // Free the threads - for (auto thread : m_threads) - delete thread; + // Free the threads + for (auto thread: m_threads) + delete thread; - // Free the memory manager (only if it was created) - if(!is_kernel) - delete memory_manager; + // Free the memory manager (only if it was created) + if (!is_kernel) + delete memory_manager; - // Log the cleanup - Logger::DEBUG() << "Process " << name.c_str() << " cleaned up, memory before: " << pages << " bytes, after cleanup: " << PhysicalMemoryManager::s_current_manager -> memory_used() << " bytes\n"; + // Log the cleanup + Logger::DEBUG() << "Process " << name.c_str() << " cleaned up, memory before: " << pages << " bytes, after cleanup: " << PhysicalMemoryManager::s_current_manager->memory_used() << " bytes\n"; } /** @@ -204,18 +200,17 @@ Process::~Process() { * * @param thread The thread to add */ -void Process::add_thread(Thread *thread) { - - // Pause interrupts while adding the thread - asm("cli"); +void Process::add_thread(Thread* thread) { - // Store the thread - m_threads.push_back(thread); - thread -> parent_pid = m_pid; + // Pause interrupts while adding the thread + asm("cli"); - // Can now resume interrupts - asm("sti"); + // Store the thread + m_threads.push_back(thread); + thread->parent_pid = m_pid; + // Can now resume interrupts + asm("sti"); } /** @@ -225,26 +220,26 @@ void Process::add_thread(Thread *thread) { */ void Process::remove_thread(uint64_t tid) { - // Find the thread - for (uint32_t i = 0; i < m_threads.size(); i++) { + // Find the thread + for (uint32_t i = 0; i < m_threads.size(); i++) { - // Thread is not what is being removed - if (m_threads[i]->tid != tid) - continue; + // Thread is not what is being removed + if (m_threads[i]->tid != tid) + continue; - // Delete the thread - Thread* thread = m_threads[i]; - delete thread; + // Delete the thread + Thread* thread = m_threads[i]; + delete thread; - // Remove the thread from the list - m_threads.erase(m_threads.begin() + i); + // Remove the thread from the list + m_threads.erase(m_threads.begin() + i); - // If there are no more threads then delete the process from the scheduler - if (m_threads.empty()) - Scheduler::system_scheduler() -> remove_process(this); + // If there are no more threads then delete the process from the scheduler + if (m_threads.empty()) + Scheduler::system_scheduler()->remove_process(this); - return; - } + return; + } } /** @@ -254,17 +249,16 @@ void Process::remove_thread(uint64_t tid) { */ void Process::set_pid(uint64_t pid) { - // Check if the pid is already set - if (m_pid != 0) - return; - - // Set the pid - m_pid = pid; + // Check if the pid is already set + if (m_pid != 0) + return; - // Assign the pid to the threads - for (auto thread : m_threads) - thread->parent_pid = pid; + // Set the pid + m_pid = pid; + // Assign the pid to the threads + for (auto thread: m_threads) + thread->parent_pid = pid; } @@ -272,7 +266,8 @@ void Process::set_pid(uint64_t pid) { * @brief Gets the threads of the process */ Vector Process::threads() { - return m_threads; + + return m_threads; } /** @@ -281,7 +276,8 @@ Vector Process::threads() { * @return The pid of the process */ uint64_t Process::pid() const { - return m_pid; + + return m_pid; } /** @@ -291,9 +287,9 @@ uint64_t Process::pid() const { */ uint64_t Process::total_ticks() { - uint64_t total_ticks = 0; - for (auto thread : m_threads) - total_ticks += thread->ticks; + uint64_t total_ticks = 0; + for (auto thread: m_threads) + total_ticks += thread->ticks; - return total_ticks; + return total_ticks; } diff --git a/kernel/src/processes/scheduler.cpp b/kernel/src/processes/scheduler.cpp index e98e7992..d432545b 100644 --- a/kernel/src/processes/scheduler.cpp +++ b/kernel/src/processes/scheduler.cpp @@ -11,7 +11,6 @@ using namespace MaxOS::memory; using namespace MaxOS::hardwarecommunication; using namespace MaxOS::system; - Scheduler::Scheduler(Multiboot& multiboot) : InterruptHandler(0x20), m_current_thread_index(0), @@ -19,33 +18,31 @@ Scheduler::Scheduler(Multiboot& multiboot) m_ticks(0), m_next_pid(-1), m_next_tid(-1) - { - // Setup the basic scheduler - Logger::INFO() << "Setting up Scheduler \n"; - s_instance = this; - m_ipc = new InterProcessCommunicationManager(); + // Set up the basic scheduler + Logger::INFO() << "Setting up Scheduler \n"; + s_instance = this; + m_ipc = new InterProcessCommunicationManager(); - // Create the idle process - auto* idle = new Process("kernelMain Idle", nullptr, nullptr,0, true); - idle -> memory_manager = MemoryManager::s_kernel_memory_manager; - add_process(idle); - idle -> set_pid(0); + // Create the idle process + auto* idle = new Process("kernelMain Idle", nullptr, nullptr, 0, true); + idle->memory_manager = MemoryManager::s_kernel_memory_manager; + add_process(idle); + idle->set_pid(0); - // Load the elfs - load_multiboot_elfs(&multiboot); + // Load the elfs + load_multiboot_elfs(&multiboot); } Scheduler::~Scheduler() { - // Deactivate this scheduler - s_instance = nullptr; - m_active = false; - - // Delete the IPC handler - delete m_ipc; + // Deactivate this scheduler + s_instance = nullptr; + m_active = false; + // Delete the IPC handler + delete m_ipc; } /** @@ -54,9 +51,9 @@ Scheduler::~Scheduler() { * @param status The current CPU status * @return The new CPU status */ -cpu_status_t* Scheduler::handle_interrupt(cpu_status_t *status) { +cpu_status_t* Scheduler::handle_interrupt(cpu_status_t* status) { - return schedule(status); + return schedule(status); } @@ -66,25 +63,24 @@ cpu_status_t* Scheduler::handle_interrupt(cpu_status_t *status) { * @param cpu_state The current CPU state * @return The next CPU state */ -cpu_status_t *Scheduler::schedule(cpu_status_t* cpu_state) { - - // Scheduler cant schedule anything - if (m_threads.empty() || !m_active) - return cpu_state; +cpu_status_t* Scheduler::schedule(cpu_status_t* cpu_state) { - // Thread that we are dealing with - Thread* current_thread = m_threads[m_current_thread_index]; + // Scheduler cant schedule anything + if (m_threads.empty() || !m_active) + return cpu_state; - // Ticked - m_ticks++; - current_thread->ticks++; + // Thread that we are dealing with + Thread* current_thread = m_threads[m_current_thread_index]; - // Wait for a bit so that the scheduler doesn't run too fast TODO: fix - if (m_ticks % s_ticks_per_event != 0) return cpu_state; + // Ticked + m_ticks++; + current_thread->ticks++; - // Schedule the next thread - return schedule_next(cpu_state); + // Wait for a bit so that the scheduler doesn't run too fast TODO: fix + if (m_ticks % s_ticks_per_event != 0) return cpu_state; + // Schedule the next thread + return schedule_next(cpu_state); } /** @@ -95,66 +91,66 @@ cpu_status_t *Scheduler::schedule(cpu_status_t* cpu_state) { */ cpu_status_t* Scheduler::schedule_next(cpu_status_t* cpu_state) { - // Get the thread that is executing right now - Thread* current_thread = m_threads[m_current_thread_index]; + // Get the thread that is executing right now + Thread* current_thread = m_threads[m_current_thread_index]; - // Save its state - current_thread->execution_state = cpu_state; - current_thread -> save_sse_state(); - if(current_thread->thread_state == ThreadState::RUNNING) - current_thread->thread_state = ThreadState::READY; + // Save its state + current_thread->execution_state = cpu_state; + current_thread->save_sse_state(); + if (current_thread->thread_state == ThreadState::RUNNING) + current_thread->thread_state = ThreadState::READY; - // Switch to the thread that will now run - m_current_thread_index++; - m_current_thread_index %= m_threads.size(); - current_thread = m_threads[m_current_thread_index]; + // Switch to the thread that will now run + m_current_thread_index++; + m_current_thread_index %= m_threads.size(); + current_thread = m_threads[m_current_thread_index]; - Process* owner_process = current_process(); + Process* owner_process = current_process(); - // Handle state changes - switch (current_thread->thread_state) { + // Handle state changes + switch (current_thread->thread_state) { - case ThreadState::NEW: - current_thread->thread_state = ThreadState::RUNNING; - break; + case ThreadState::NEW: + current_thread->thread_state = ThreadState::RUNNING; + break; - case ThreadState::SLEEPING: + case ThreadState::SLEEPING: - // If the wake-up time hasn't occurred yet, run the next thread - if (current_thread->wakeup_time > m_ticks) - return schedule_next(current_thread->execution_state); + // If the wake-up time hasn't occurred yet, run the next thread + if (current_thread->wakeup_time > m_ticks) + return schedule_next(current_thread->execution_state); - break; + break; - case ThreadState::STOPPED: + case ThreadState::STOPPED: - // Find the process that has the thread and remove it - for (auto thread : owner_process->threads()) { - if (thread == current_thread) { - owner_process->remove_thread(m_current_thread_index); - break; - } - } + // Find the process that has the thread and remove it + for (auto thread: owner_process->threads()) { + if (thread == current_thread) { + owner_process->remove_thread(m_current_thread_index); + break; + } + } - // Remove the thread - m_threads.erase(m_threads.begin() + m_current_thread_index); + // Remove the thread + m_threads.erase(m_threads.begin() + m_current_thread_index); - // Run the next thread - return schedule_next(cpu_state); + // Run the next thread + return schedule_next(cpu_state); - default: - break; - } + default: + break; + } - // Prepare the next thread to run - current_thread -> thread_state = ThreadState::RUNNING; - current_thread -> restore_sse_state(); + // Prepare the next thread to run + current_thread->thread_state = ThreadState::RUNNING; + current_thread->restore_sse_state(); - // Load the thread's memory manager and task state - MemoryManager::switch_active_memory_manager(owner_process->memory_manager); - CPU::tss.rsp0 = current_thread->tss_pointer(); + // Load the thread's memory manager and task state + MemoryManager::switch_active_memory_manager(owner_process->memory_manager); + CPU::tss.rsp0 = current_thread->tss_pointer(); - return current_thread->execution_state; + return current_thread->execution_state; } @@ -164,18 +160,17 @@ cpu_status_t* Scheduler::schedule_next(cpu_status_t* cpu_state) { * @param process The process to add * @return The process ID */ -uint64_t Scheduler::add_process(Process *process) { +uint64_t Scheduler::add_process(Process* process) { - // Get the next process ID - m_next_pid++; + // Get the next process ID + m_next_pid++; - // Add the process to the list - m_processes.push_back(process); - Logger::DEBUG() << "Adding process " << m_next_pid << ": " << process->name << "\n"; - - // Return the process ID - return m_next_pid; + // Add the process to the list + m_processes.push_back(process); + Logger::DEBUG() << "Adding process " << m_next_pid << ": " << process->name << "\n"; + // Return the process ID + return m_next_pid; } /** @@ -184,18 +179,17 @@ uint64_t Scheduler::add_process(Process *process) { * @param thread The thread to add * @return The thread ID */ -uint64_t Scheduler::add_thread(Thread *thread) { - - // Get the next thread ID - m_next_tid++; +uint64_t Scheduler::add_thread(Thread* thread) { - // Add the thread to the list - m_threads.push_back(thread); - Logger::DEBUG() << "Adding thread " << m_next_tid << " to process " << thread->parent_pid << "\n"; + // Get the next thread ID + m_next_tid++; - // Return the thread ID - return m_next_tid; + // Add the thread to the list + m_threads.push_back(thread); + Logger::DEBUG() << "Adding thread " << m_next_tid << " to process " << thread->parent_pid << "\n"; + // Return the thread ID + return m_next_tid; } /** @@ -203,8 +197,9 @@ uint64_t Scheduler::add_thread(Thread *thread) { * * @return The system scheduler or nullptr if not found */ -Scheduler *Scheduler::system_scheduler() { - return s_instance; +Scheduler* Scheduler::system_scheduler() { + + return s_instance; } /** @@ -213,7 +208,8 @@ Scheduler *Scheduler::system_scheduler() { * @return The number of ticks */ uint64_t Scheduler::ticks() const { - return m_ticks; + + return m_ticks; } /** @@ -221,25 +217,25 @@ uint64_t Scheduler::ticks() const { */ cpu_status_t* Scheduler::yield() { - // If this is the only thread, can't yield - if (m_threads.size() <= 1) - return current_thread()->execution_state; - - // Set the current thread to waiting if running - if (m_threads[m_current_thread_index]->thread_state == ThreadState::RUNNING) - m_threads[m_current_thread_index]->thread_state = ThreadState::READY; + // If this is the only thread, can't yield + if (m_threads.size() <= 1) + return current_thread()->execution_state; + // Set the current thread to waiting if running + if (m_threads[m_current_thread_index]->thread_state == ThreadState::RUNNING) + m_threads[m_current_thread_index]->thread_state = ThreadState::READY; - // Schedule the next thread - return schedule_next(current_thread()->execution_state); + // Schedule the next thread + return schedule_next(current_thread()->execution_state); } /** * @brief Activates the scheduler */ void Scheduler::activate() { - m_active = true; + + m_active = true; } /** @@ -249,34 +245,33 @@ void Scheduler::activate() { * @param force If true, the process will be removed and so will all threads * @return -1 if the process has threads, 0 otherwise */ -uint64_t Scheduler::remove_process(Process *process) { - - // Check if the process has no threads - if (!process->threads().empty()) { +uint64_t Scheduler::remove_process(Process* process) { - // Set the threads to stopped or remove them if forced - for (auto thread : process->threads()) - thread->thread_state = ThreadState::STOPPED; + // Check if the process has no threads + if (!process->threads().empty()) { - // Need to wait until the threads are stopped before removing the process (this will be called again when all threads are stopped) - return -1; + // Set the threads to stopped or remove them if forced + for (auto thread: process->threads()) + thread->thread_state = ThreadState::STOPPED; - } + // Need to wait until the threads are stopped before removing the process (this will be called again when all threads are stopped) + return -1; - // Remove the process - for (uint32_t i = 0; i < m_processes.size(); i++) { - if (m_processes[i] == process) { - m_processes.erase(m_processes.begin() + i); + } - // Delete the process mem - delete process; - return 0; - } - } + // Remove the process + for (uint32_t i = 0; i < m_processes.size(); i++) { + if (m_processes[i] == process) { + m_processes.erase(m_processes.begin() + i); - // Process not found - return -1; + // Delete the process mem + delete process; + return 0; + } + } + // Process not found + return -1; } /** @@ -287,25 +282,25 @@ uint64_t Scheduler::remove_process(Process *process) { */ cpu_status_t* Scheduler::force_remove_process(Process* process) { - // If there is no process, fail - if (!process) - return nullptr; + // If there is no process, fail + if (!process) + return nullptr; - // Remove all the threads - for (auto thread : process->threads()){ + // Remove all the threads + for (auto thread: process->threads()) { - // Remove the thread from the scheduler - int index = m_threads.find(thread) - m_threads.begin(); - m_threads.erase(m_threads.begin() + index); + // Remove the thread from the scheduler + int index = m_threads.find(thread) - m_threads.begin(); + m_threads.erase(m_threads.begin() + index); - // Delete the thread - process -> remove_thread(thread->tid); + // Delete the thread + process->remove_thread(thread->tid); - } + } - // Process will be dead now so run the next process (don't care about the execution state being outdated as it is being - // removed regardless) - return schedule_next(current_thread()->execution_state); + // Process will be dead now so run the next process (don't care about the execution state being outdated as it is being + // removed regardless) + return schedule_next(current_thread()->execution_state); } /** @@ -313,22 +308,22 @@ cpu_status_t* Scheduler::force_remove_process(Process* process) { * * @return The current process, or nullptr if not found */ -Process *Scheduler::current_process() { +Process* Scheduler::current_process() { - Process* current_process = nullptr; + Process* current_process = nullptr; - // Make sure there is something with process attached - if(!s_instance) - return nullptr; + // Make sure there is something with process attached + if (!s_instance) + return nullptr; - // Find the process that has the thread being executed - for (auto process : s_instance -> m_processes) - if (process->pid() == current_thread() -> parent_pid) { - current_process = process; - break; - } + // Find the process that has the thread being executed + for (auto process: s_instance->m_processes) + if (process->pid() == current_thread()->parent_pid) { + current_process = process; + break; + } - return current_process; + return current_process; } /** @@ -337,15 +332,15 @@ Process *Scheduler::current_process() { * @param pid The process ID * @return The process or nullptr if not found */ -Process *Scheduler::get_process(uint64_t pid) { +Process* Scheduler::get_process(uint64_t pid) { - // Try to find the process - for (auto process : s_instance->m_processes) - if (process->pid() == pid) - return process; + // Try to find the process + for (auto process: s_instance->m_processes) + if (process->pid() == pid) + return process; - // Not found - return nullptr; + // Not found + return nullptr; } @@ -354,17 +349,17 @@ Process *Scheduler::get_process(uint64_t pid) { * * @return The currently executing thread */ -Thread *Scheduler::current_thread() { - - return s_instance -> m_threads[s_instance -> m_current_thread_index]; +Thread* Scheduler::current_thread() { + return s_instance->m_threads[s_instance->m_current_thread_index]; } /** * @brief Deactivates the scheduler */ void Scheduler::deactivate() { - m_active = false; + + m_active = false; } /** @@ -372,32 +367,31 @@ void Scheduler::deactivate() { * * @param multiboot The multiboot structure */ -void Scheduler::load_multiboot_elfs(Multiboot *multiboot) { +void Scheduler::load_multiboot_elfs(Multiboot* multiboot) { - for(multiboot_tag* tag = multiboot -> start_tag(); tag->type != MULTIBOOT_TAG_TYPE_END; tag = (struct multiboot_tag *) ((multiboot_uint8_t *) tag + ((tag->size + 7) & ~7))) { + for (multiboot_tag* tag = multiboot->start_tag(); tag->type != MULTIBOOT_TAG_TYPE_END; tag = (struct multiboot_tag*) ((multiboot_uint8_t*) tag + ((tag->size + 7) & ~7))) { - // Tag is not an ELF - if(tag -> type != MULTIBOOT_TAG_TYPE_MODULE) - continue; + // Tag is not an ELF + if (tag->type != MULTIBOOT_TAG_TYPE_MODULE) + continue; - // Try create the elf from the module - auto* module = (struct multiboot_tag_module*)tag; - auto* elf = new Elf64((uintptr_t)PhysicalMemoryManager::to_dm_region(module->mod_start)); - if(!elf->is_valid()) - continue; + // Try create the elf from the module + auto* module = (struct multiboot_tag_module*) tag; + auto* elf = new Elf64((uintptr_t) PhysicalMemoryManager::to_dm_region(module->mod_start)); + if (!elf->is_valid()) + continue; - Logger::DEBUG() << "Creating process from multiboot module for " << module->cmdline << " (at 0x" << (uint64_t)module->mod_start << ")\n"; + Logger::DEBUG() << "Creating process from multiboot module for " << module->cmdline << " (at 0x" << (uint64_t) module->mod_start << ")\n"; - // Create an array of args for the process TODO: handle multiple args ("" & spaces) - char* args[1] = {module->cmdline}; + // Create an array of args for the process TODO: handle multiple args ("" & spaces) + char* args[1] = {module->cmdline}; - // Create the process - auto* process = new Process(module->cmdline, args, 1, elf); - - Logger::DEBUG() << "Elf loaded to pid " << process->pid() << "\n"; - delete elf; - } + // Create the process + auto* process = new Process(module->cmdline, args, 1, elf); + Logger::DEBUG() << "Elf loaded to pid " << process->pid() << "\n"; + delete elf; + } } /** @@ -405,10 +399,9 @@ void Scheduler::load_multiboot_elfs(Multiboot *multiboot) { * * @return The IPC handler or nullptr if not found */ -InterProcessCommunicationManager *Scheduler::scheduler_ipc() { - - return s_instance -> m_ipc; +InterProcessCommunicationManager* Scheduler::scheduler_ipc() { + return s_instance->m_ipc; } /** @@ -417,12 +410,12 @@ InterProcessCommunicationManager *Scheduler::scheduler_ipc() { * @param tid The thread ID * @return The thread or nullptr if not found */ -Thread *Scheduler::get_thread(uint64_t tid) { +Thread* Scheduler::get_thread(uint64_t tid) { - // Try to find the thread - for (auto thread : s_instance -> m_threads) - if (thread -> tid == tid) - return thread; + // Try to find the thread + for (auto thread: s_instance->m_threads) + if (thread->tid == tid) + return thread; - return nullptr; + return nullptr; } diff --git a/kernel/src/runtime/cplusplus.cpp b/kernel/src/runtime/cplusplus.cpp index e4340d22..aad7919e 100644 --- a/kernel/src/runtime/cplusplus.cpp +++ b/kernel/src/runtime/cplusplus.cpp @@ -12,15 +12,15 @@ extern "C" void* __dso_handle = nullptr; // Pure virtual function call extern "C" void __cxa_pure_virtual() { - ASSERT(false, "Pure virtual function call failed"); + + ASSERT(false, "Pure virtual function call failed"); } extern "C" constructor start_ctors; extern "C" constructor end_ctors; -extern "C" void call_constructors() -{ - // Loop through and initialise all the global constructors - for(constructor* i = &start_ctors; i != &end_ctors; i++) - (*i)(); +extern "C" void call_constructors() { + // Loop through and initialise all the global constructors + for (constructor* i = &start_ctors; i != &end_ctors; i++) + (*i)(); } \ No newline at end of file diff --git a/kernel/src/runtime/ubsan.cpp b/kernel/src/runtime/ubsan.cpp index 7f8c67f4..0f627789 100644 --- a/kernel/src/runtime/ubsan.cpp +++ b/kernel/src/runtime/ubsan.cpp @@ -19,8 +19,8 @@ UBSanHandler::~UBSanHandler() = default; */ void UBSanHandler::handle(source_location_t location) { - // Print the location - ASSERT(false, "UBSAN ERROR AT %s:%d:%d\n", location.file, location.line, location.column); + // Print the location + ASSERT(false, "UBSAN ERROR AT %s:%d:%d\n", location.file, location.line, location.column); } @@ -31,17 +31,17 @@ void UBSanHandler::handle(source_location_t location) { * @param info The type mismatch info * @param ptr The pointer to the object */ -void UBSanHandler::print_type_mismatch(type_mismatch_info_t *info, uintptr_t ptr) { +void UBSanHandler::print_type_mismatch(type_mismatch_info_t* info, uintptr_t ptr) { - // Print the error - Logger::DEBUG() << "UBSan: "; - if(info -> alignment != 0 && ubsan_aligned(ptr, info -> alignment)) - Logger::Out() << "misaligned memory access\n"; - else - Logger::Out() << Type_Check_Kinds[info -> type_check_kind] << " address 0x" << ptr << " with insufficient space for an object of type " << info -> type -> name << "\n"; + // Print the error + Logger::DEBUG() << "UBSan: "; + if (info->alignment != 0 && ubsan_aligned(ptr, info->alignment)) + Logger::Out() << "misaligned memory access\n"; + else + Logger::Out() << Type_Check_Kinds[info->type_check_kind] << " address 0x" << ptr << " with insufficient space for an object of type " << info->type->name << "\n"; - // Print the location - handle(info -> location); + // Print the location + handle(info->location); } /** @@ -50,95 +50,109 @@ void UBSanHandler::print_type_mismatch(type_mismatch_info_t *info, uintptr_t ptr * @param info The type mismatch info * @param ptr The pointer to the object */ -void UBSanHandler::print_type_mismatch_v1(type_mismatch_info_v1_t *info, uintptr_t ptr) { +void UBSanHandler::print_type_mismatch_v1(type_mismatch_info_v1_t* info, uintptr_t ptr) { - // Print the error - Logger::DEBUG() << "UBSan: "; - if(info -> log_alignment != 0 && ubsan_aligned(ptr, (1 << (info -> log_alignment)))) - Logger::Out() << "misaligned memory access\n"; - else - Logger::Out() << Type_Check_Kinds[info -> type_check_kind] << " address 0x" << ptr << " with insufficient space for an object of type " << info -> type -> name << "\n"; + // Print the error + Logger::DEBUG() << "UBSan: "; + if (info->log_alignment != 0 && ubsan_aligned(ptr, (1 << (info->log_alignment)))) + Logger::Out() << "misaligned memory access\n"; + else + Logger::Out() << Type_Check_Kinds[info->type_check_kind] << " address 0x" << ptr << " with insufficient space for an object of type " << info->type->name << "\n"; - // Print the location - handle(info -> location); + // Print the location + handle(info->location); } -extern "C" void __ubsan_handle_type_mismatch(type_mismatch_info_t *info, uintptr_t ptr) { - UBSanHandler::print_type_mismatch(info, ptr); - UBSanHandler::handle(info -> location); +extern "C" void __ubsan_handle_type_mismatch(type_mismatch_info_t* info, uintptr_t ptr) { + + UBSanHandler::print_type_mismatch(info, ptr); + UBSanHandler::handle(info->location); } -extern "C" void __ubsan_handle_type_mismatch_v1(type_mismatch_info_v1_t *info, uintptr_t ptr) { - UBSanHandler::print_type_mismatch_v1(info, ptr); - UBSanHandler::handle(info -> location); +extern "C" void __ubsan_handle_type_mismatch_v1(type_mismatch_info_v1_t* info, uintptr_t ptr) { + + UBSanHandler::print_type_mismatch_v1(info, ptr); + UBSanHandler::handle(info->location); } -extern "C" void __ubsan_handle_pointer_overflow(overflow_info_t *info) { - Logger::DEBUG() << "UBSan: Pointer overflow\n"; - UBSanHandler::handle(info -> location); +extern "C" void __ubsan_handle_pointer_overflow(overflow_info_t* info) { + + Logger::DEBUG() << "UBSan: Pointer overflow\n"; + UBSanHandler::handle(info->location); } -extern "C" void __ubsan_handle_sub_overflow(overflow_info_t *info) { - Logger::DEBUG() << "UBSan: Subtraction overflow\n"; - UBSanHandler::handle(info -> location); +extern "C" void __ubsan_handle_sub_overflow(overflow_info_t* info) { + + Logger::DEBUG() << "UBSan: Subtraction overflow\n"; + UBSanHandler::handle(info->location); } -extern "C" void __ubsan_handle_shift_out_of_bounds(shift_out_of_bounds_info_t *info) { - Logger::DEBUG() << "UBSan: Shift out of bounds\n"; - UBSanHandler::handle(info -> location); +extern "C" void __ubsan_handle_shift_out_of_bounds(shift_out_of_bounds_info_t* info) { + + Logger::DEBUG() << "UBSan: Shift out of bounds\n"; + UBSanHandler::handle(info->location); } -extern "C" void __ubsan_handle_out_of_bounds(out_of_bounds_info_t *info) { - Logger::DEBUG() << "UBSan: Array out of bounds\n"; - UBSanHandler::handle(info -> location); +extern "C" void __ubsan_handle_out_of_bounds(out_of_bounds_info_t* info) { + + Logger::DEBUG() << "UBSan: Array out of bounds\n"; + UBSanHandler::handle(info->location); } extern "C" void __ubsan_handle_add_overflow(overflow_info_t* info) { - Logger::DEBUG() << "UBSan: Addition overflow\n"; - UBSanHandler::handle(info -> location); + + Logger::DEBUG() << "UBSan: Addition overflow\n"; + UBSanHandler::handle(info->location); } extern "C" void __ubsan_handle_divrem_overflow(overflow_info_t* info) { - Logger::DEBUG() << "UBSan: Division overflow\n"; - UBSanHandler::handle(info -> location); + + Logger::DEBUG() << "UBSan: Division overflow\n"; + UBSanHandler::handle(info->location); } extern "C" void __ubsan_handle_negate_overflow(overflow_info_t* info) { - Logger::DEBUG() << "UBSan: Negation overflow\n"; - UBSanHandler::handle(info -> location); + + Logger::DEBUG() << "UBSan: Negation overflow\n"; + UBSanHandler::handle(info->location); } extern "C" void __ubsan_handle_builtin_unreachable(location_only_info_t* info) { - Logger::DEBUG() << "UBSan: Unreachable code\n"; - UBSanHandler::handle(info -> location); + + Logger::DEBUG() << "UBSan: Unreachable code\n"; + UBSanHandler::handle(info->location); } extern "C" void __ubsan_handle_mul_overflow(overflow_info_t* info) { - Logger::DEBUG() << "UBSan: Multiplication overflow\n"; - UBSanHandler::handle(info -> location); + + Logger::DEBUG() << "UBSan: Multiplication overflow\n"; + UBSanHandler::handle(info->location); } extern "C" void __ubsan_handle_load_invalid_value(invalid_value_info_t* info) { - Logger::DEBUG() << "UBSan: Load of invalid value\n"; - UBSanHandler::handle(info -> location); + + Logger::DEBUG() << "UBSan: Load of invalid value\n"; + UBSanHandler::handle(info->location); } extern "C" void __ubsan_handle_missing_return(location_only_info_t* info) { - Logger::DEBUG() << "UBSan: Missing return\n"; - UBSanHandler::handle(info -> location); + + Logger::DEBUG() << "UBSan: Missing return\n"; + UBSanHandler::handle(info->location); } extern "C" void __ubsan_handle_vla_bound_not_positive(vla_bound_not_positive_info_t* info) { - Logger::DEBUG() << "UBSan: VLA bound not positive\n"; - UBSanHandler::handle(info -> location); + + Logger::DEBUG() << "UBSan: VLA bound not positive\n"; + UBSanHandler::handle(info->location); } \ No newline at end of file diff --git a/kernel/src/system/cpu.cpp b/kernel/src/system/cpu.cpp index 343bcae9..6def79fc 100644 --- a/kernel/src/system/cpu.cpp +++ b/kernel/src/system/cpu.cpp @@ -5,6 +5,7 @@ #include #include #include +#include using namespace MaxOS; using namespace MaxOS::system; @@ -17,190 +18,186 @@ extern uint64_t stack[]; /** * @brief Constructor for the CPU class */ -CPU::CPU(GlobalDescriptorTable* gdt, Multiboot* multiboot){ +CPU::CPU(GlobalDescriptorTable* gdt, Multiboot* multiboot) { - Logger::INFO() << "Setting up CPU \n"; - acpi = new AdvancedConfigurationAndPowerInterface(multiboot); - apic = new AdvancedProgrammableInterruptController(acpi); + Logger::INFO() << "Setting up CPU \n"; + acpi = new AdvancedConfigurationAndPowerInterface(multiboot); + apic = new AdvancedProgrammableInterruptController(acpi); - // TODO: Multicore + // TODO: Multicore - // Setup cpu features - init_tss(gdt); - init_sse(); + // Setup cpu features + init_tss(gdt); + init_sse(); } CPU::~CPU() = default; [[noreturn]] void CPU::halt() { - while (true) - asm volatile("hlt"); -} -void CPU::get_status(cpu_status_t *status) { - - // Get the registers - asm volatile("mov %%r15, %0" : "=r" (status->r15)); - asm volatile("mov %%r14, %0" : "=r" (status->r14)); - asm volatile("mov %%r13, %0" : "=r" (status->r13)); - asm volatile("mov %%r12, %0" : "=r" (status->r12)); - asm volatile("mov %%r11, %0" : "=r" (status->r11)); - asm volatile("mov %%r10, %0" : "=r" (status->r10)); - asm volatile("mov %%r9, %0" : "=r" (status->r9)); - asm volatile("mov %%r8, %0" : "=r" (status->r8)); - asm volatile("mov %%rdi, %0" : "=r" (status->rdi)); - asm volatile("mov %%rsi, %0" : "=r" (status->rsi)); - asm volatile("mov %%rbp, %0" : "=r" (status->rbp)); - asm volatile("mov %%rdx, %0" : "=r" (status->rdx)); - asm volatile("mov %%rcx, %0" : "=r" (status->rcx)); - asm volatile("mov %%rbx, %0" : "=r" (status->rbx)); - asm volatile("mov %%rax, %0" : "=r" (status->rax)); + while (true) + asm volatile("hlt"); +} +void CPU::get_status(cpu_status_t* status) { + + // Get the registers + asm volatile("mov %%r15, %0" : "=r" (status->r15)); + asm volatile("mov %%r14, %0" : "=r" (status->r14)); + asm volatile("mov %%r13, %0" : "=r" (status->r13)); + asm volatile("mov %%r12, %0" : "=r" (status->r12)); + asm volatile("mov %%r11, %0" : "=r" (status->r11)); + asm volatile("mov %%r10, %0" : "=r" (status->r10)); + asm volatile("mov %%r9, %0" : "=r" (status->r9)); + asm volatile("mov %%r8, %0" : "=r" (status->r8)); + asm volatile("mov %%rdi, %0" : "=r" (status->rdi)); + asm volatile("mov %%rsi, %0" : "=r" (status->rsi)); + asm volatile("mov %%rbp, %0" : "=r" (status->rbp)); + asm volatile("mov %%rdx, %0" : "=r" (status->rdx)); + asm volatile("mov %%rcx, %0" : "=r" (status->rcx)); + asm volatile("mov %%rbx, %0" : "=r" (status->rbx)); + asm volatile("mov %%rax, %0" : "=r" (status->rax)); } -void CPU::set_status(cpu_status_t *status) { - - // Set the registers - asm volatile("mov %0, %%r15" : : "r" (status->r15)); - asm volatile("mov %0, %%r14" : : "r" (status->r14)); - asm volatile("mov %0, %%r13" : : "r" (status->r13)); - asm volatile("mov %0, %%r12" : : "r" (status->r12)); - asm volatile("mov %0, %%r11" : : "r" (status->r11)); - asm volatile("mov %0, %%r10" : : "r" (status->r10)); - asm volatile("mov %0, %%r9" : : "r" (status->r9)); - asm volatile("mov %0, %%r8" : : "r" (status->r8)); - asm volatile("mov %0, %%rdi" : : "r" (status->rdi)); - asm volatile("mov %0, %%rsi" : : "r" (status->rsi)); - asm volatile("mov %0, %%rbp" : : "r" (status->rbp)); - asm volatile("mov %0, %%rdx" : : "r" (status->rdx)); - asm volatile("mov %0, %%rcx" : : "r" (status->rcx)); - asm volatile("mov %0, %%rbx" : : "r" (status->rbx)); - asm volatile("mov %0, %%rax" : : "r" (status->rax)); + +void CPU::set_status(cpu_status_t* status) { + + // Set the registers + asm volatile("mov %0, %%r15" : : "r" (status->r15)); + asm volatile("mov %0, %%r14" : : "r" (status->r14)); + asm volatile("mov %0, %%r13" : : "r" (status->r13)); + asm volatile("mov %0, %%r12" : : "r" (status->r12)); + asm volatile("mov %0, %%r11" : : "r" (status->r11)); + asm volatile("mov %0, %%r10" : : "r" (status->r10)); + asm volatile("mov %0, %%r9" : : "r" (status->r9)); + asm volatile("mov %0, %%r8" : : "r" (status->r8)); + asm volatile("mov %0, %%rdi" : : "r" (status->rdi)); + asm volatile("mov %0, %%rsi" : : "r" (status->rsi)); + asm volatile("mov %0, %%rbp" : : "r" (status->rbp)); + asm volatile("mov %0, %%rdx" : : "r" (status->rdx)); + asm volatile("mov %0, %%rcx" : : "r" (status->rcx)); + asm volatile("mov %0, %%rbx" : : "r" (status->rbx)); + asm volatile("mov %0, %%rax" : : "r" (status->rax)); } -void CPU::print_registers(cpu_status_t *status) { - - // Print the registers - Logger::ERROR() << "R15: \t0x" << status->r15 << "\n"; - Logger::ERROR() << "R14: \t0x" << status->r14 << "\n"; - Logger::ERROR() << "R13: \t0x" << status->r13 << "\n"; - Logger::ERROR() << "R12: \t0x" << status->r12 << "\n"; - Logger::ERROR() << "R11: \t0x" << status->r11 << "\n"; - Logger::ERROR() << "R10: \t0x" << status->r10 << "\n"; - Logger::ERROR() << "R9: \t0x" << status->r9 << "\n"; - Logger::ERROR() << "R8: \t0x" << status->r8 << "\n"; - Logger::ERROR() << "RDI: \t0x" << status->rdi << "\n"; - Logger::ERROR() << "RSI: \t0x" << status->rsi << "\n"; - Logger::ERROR() << "RBP: \t0x" << status->rbp << "\n"; - Logger::ERROR() << "RDX: \t0x" << status->rdx << "\n"; - Logger::ERROR() << "RCX: \t0x" << status->rcx << "\n"; - Logger::ERROR() << "RBX: \t0x" << status->rbx << "\n"; - Logger::ERROR() << "RAX: \t0x" << status->rax << "\n"; - Logger::ERROR() << "INT: \t0x" << status->interrupt_number << "\n"; - Logger::ERROR() << "ERRCD: \t0x" << status->error_code << "\n"; - Logger::ERROR() << "RIP: \t0x" << status->rip << "\n"; - Logger::ERROR() << "CS: \t0x" << status->cs << "\n"; - Logger::ERROR() << "RFlGS: \t0x" << status->rflags << "\n"; - Logger::ERROR() << "RSP: \t0x" << status->rsp << "\n"; - Logger::ERROR() << "SS: \t0x" << status->ss << "\n"; +void CPU::print_registers(cpu_status_t* status) { + + // Print the registers + Logger::ERROR() << "R15: \t0x" << status->r15 << "\n"; + Logger::ERROR() << "R14: \t0x" << status->r14 << "\n"; + Logger::ERROR() << "R13: \t0x" << status->r13 << "\n"; + Logger::ERROR() << "R12: \t0x" << status->r12 << "\n"; + Logger::ERROR() << "R11: \t0x" << status->r11 << "\n"; + Logger::ERROR() << "R10: \t0x" << status->r10 << "\n"; + Logger::ERROR() << "R9: \t0x" << status->r9 << "\n"; + Logger::ERROR() << "R8: \t0x" << status->r8 << "\n"; + Logger::ERROR() << "RDI: \t0x" << status->rdi << "\n"; + Logger::ERROR() << "RSI: \t0x" << status->rsi << "\n"; + Logger::ERROR() << "RBP: \t0x" << status->rbp << "\n"; + Logger::ERROR() << "RDX: \t0x" << status->rdx << "\n"; + Logger::ERROR() << "RCX: \t0x" << status->rcx << "\n"; + Logger::ERROR() << "RBX: \t0x" << status->rbx << "\n"; + Logger::ERROR() << "RAX: \t0x" << status->rax << "\n"; + Logger::ERROR() << "INT: \t0x" << status->interrupt_number << "\n"; + Logger::ERROR() << "ERRCD: \t0x" << status->error_code << "\n"; + Logger::ERROR() << "RIP: \t0x" << status->rip << "\n"; + Logger::ERROR() << "CS: \t0x" << status->cs << "\n"; + Logger::ERROR() << "RFlGS: \t0x" << status->rflags << "\n"; + Logger::ERROR() << "RSP: \t0x" << status->rsp << "\n"; + Logger::ERROR() << "SS: \t0x" << status->ss << "\n"; } uint64_t CPU::read_msr(uint32_t msr) { - // Read the MSR - uint32_t low, high; - asm volatile("rdmsr" : "=a" (low), "=d" (high) : "c" (msr)); - - // Return the value - return (uint64_t) low | ((uint64_t) high << 32); + // Read the MSR + uint32_t low, high; + asm volatile("rdmsr" : "=a" (low), "=d" (high) : "c" (msr)); + // Return the value + return (uint64_t) low | ((uint64_t) high << 32); } void CPU::write_msr(uint32_t msr, uint64_t value) { - // Write the MSR - asm volatile("wrmsr" : : "a" ((uint32_t) value), "d" ((uint32_t) (value >> 32)), "c" (msr)); - + // Write the MSR + asm volatile("wrmsr" : : "a" ((uint32_t) value), "d" ((uint32_t) (value >> 32)), "c" (msr)); } + void CPU::cpuid(uint32_t leaf, uint32_t* eax, uint32_t* ebx, uint32_t* ecx, uint32_t* edx) { - // Call the cpuid instruction - __get_cpuid(leaf, eax, ebx, ecx, edx); + // Call the cpuid instruction + __get_cpuid(leaf, eax, ebx, ecx, edx); } void CPU::stack_trace(size_t level) { - // Get the first stack frame - auto* frame = (stack_frame_t*)__builtin_frame_address(0); + // Get the first stack frame + auto* frame = (stack_frame_t*) __builtin_frame_address(0); - // Loop through the frames logging - for (size_t current_level = 0; current_level < level; current_level++){ + // Loop through the frames logging + for (size_t current_level = 0; current_level < level; current_level++) { - // Print the frame - Logger::ERROR() << "(" << current_level << "):\t at 0x" << frame->rip << "\n"; + // Print the frame + Logger::ERROR() << "(" << current_level << "):\t at 0x" << frame->rip << "\n"; - // Next frame - frame = frame -> next; - if (frame == nullptr) - break; + // Next frame + frame = frame->next; + if (frame == nullptr) + break; - } + } } -#include -#include - -void CPU::PANIC(char const *message, cpu_status_t* status) { - - // Get the current process - Process* process = Scheduler::current_process(); - - // Ensure ready to panic - At this point it is not an issue if it is possible can avoid the panic as it is most - // likely called by a place that cant switch to the avoidable state - if(!is_panicking) - prepare_for_panic(); - - // Print using the backend - Logger::ERROR() << "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n"; - Logger::ERROR() << "Kernel Panic: " << message << "\n"; - - // Info about the running process - Logger::ERROR() << "Process: " << (process ? process->name.c_str() : "Kernel") << "\n"; - if(process) - Logger::ERROR() << "After running for " << process->total_ticks() << " ticks (system uptime: " << Scheduler::system_scheduler()->ticks() << " ticks)\n"; - - // Stack trace - Logger::ERROR() << "----------------------------\n"; - Logger::ERROR() << "Stack Trace:\n"; - stack_trace(10); - - // Register dump - Logger::ERROR() << "----------------------------\n"; - Logger::ERROR() << "Register Dump:\n"; - - // Log the regs - cpu_status_t new_status {}; - if(!status){ - get_status(&new_status); - status = &new_status; - } - print_registers(status); - - // Print some text to the user - Logger::ERROR() << "----------------------------\n"; - Logger::ERROR() << "There has been a fatal error in MaxOS and the system has been halted.\n"; - Logger::ERROR() << "Please restart the system.\n"; - - - // Print the logo - Logger::ERROR() << "----------------------------\n"; - console::VESABootConsole::print_logo_kernel_panic(); - - // Halt - halt(); +void CPU::PANIC(char const* message, cpu_status_t* status) { + + // Get the current process + Process* process = Scheduler::current_process(); + + // Ensure ready to panic - At this point it is not an issue if it is possible can avoid the panic as it is most + // likely called by a place that cant switch to the avoidable state + if (!is_panicking) + prepare_for_panic(); + + // Print using the backend + Logger::ERROR() << "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n"; + Logger::ERROR() << "Kernel Panic: " << message << "\n"; + + // Info about the running process + Logger::ERROR() << "Process: " << (process ? process->name.c_str() : "Kernel") << "\n"; + if (process) + Logger::ERROR() << "After running for " << process->total_ticks() << " ticks (system uptime: " << Scheduler::system_scheduler()->ticks() << " ticks)\n"; + + // Stack trace + Logger::ERROR() << "----------------------------\n"; + Logger::ERROR() << "Stack Trace:\n"; + stack_trace(10); + + // Register dump + Logger::ERROR() << "----------------------------\n"; + Logger::ERROR() << "Register Dump:\n"; + + // Log the regs + cpu_status_t new_status{}; + if (!status) { + get_status(&new_status); + status = &new_status; + } + print_registers(status); + + // Print some text to the user + Logger::ERROR() << "----------------------------\n"; + Logger::ERROR() << "There has been a fatal error in MaxOS and the system has been halted.\n"; + Logger::ERROR() << "Please restart the system.\n"; + + // Print the logo + Logger::ERROR() << "----------------------------\n"; + console::VESABootConsole::print_logo_kernel_panic(); + + // Halt + halt(); } /** @@ -208,59 +205,57 @@ void CPU::PANIC(char const *message, cpu_status_t* status) { */ void CPU::init_tss(GlobalDescriptorTable* gdt) { - // The reserved have to be 0 - tss.reserved0 = 0; - tss.reserved1 = 0; - tss.reserved2 = 0; - tss.reserved3 = 0; - tss.reserved4 = 0; - - // The stacks - tss.rsp0 = (uint64_t)stack + 16384; // Kernel stack (scheduler will set the threads stack) - tss.rsp1 = 0; - tss.rsp2 = 0; - - // Interrupt stacks can all be 0 - tss.ist1 = 0; - tss.ist2 = 0; - tss.ist3 = 0; - tss.ist4 = 0; - tss.ist5 = 0; - tss.ist6 = 0; - tss.ist7 = 0; - - // Ports TODO when setting up userspace drivers come back to this - tss.io_bitmap_offset = 0; - - // Split the base into 4 parts (16 bits, 8 bits, 8 bits, 32 bits) - auto base = (uint64_t)&tss; - uint16_t base_1 = base & 0xFFFF; - uint8_t base_2 = (base >> 16) & 0xFF; - uint8_t base_3 = (base >> 24) & 0xFF; - uint32_t base_4 = (base >> 32) & 0xFFFFFFFF; - - uint16_t limit_low = sizeof(tss); - - // Flags: 1 - Type = 0x9, Descriptor Privilege Level = 0, Present = 1 - // 2 - Available = 0, Granularity = 0 - uint8_t flags_1 = 0x89; - uint8_t flags_2 = 0; - - // Create the TSS descriptors - uint64_t tss_descriptor_low = (uint64_t) base_3 << 56 | (uint64_t) flags_2 << 48 | (uint64_t) flags_1 << 40 | (uint64_t) base_2 << 32 | (uint64_t) base_1 << 16 | (uint64_t) limit_low; - uint64_t tss_descriptor_high = base_4; - - // Store in the GDT - gdt -> table[5] = tss_descriptor_low; - gdt -> table[6] = tss_descriptor_high; - - // Load the TSS - Logger::DEBUG() << "Loading TSS: 0x0" << tss_descriptor_low << " 0x0" << tss_descriptor_high << " at 0x" << (uint64_t )&tss << "\n"; - asm volatile("ltr %%ax" : : "a" (0x28)); - - //TODO: For smp - load the TSS for each core or find a better way to do this - - + // The reserved have to be 0 + tss.reserved0 = 0; + tss.reserved1 = 0; + tss.reserved2 = 0; + tss.reserved3 = 0; + tss.reserved4 = 0; + + // The stacks + tss.rsp0 = (uint64_t) stack + 16384; // Kernel stack (scheduler will set the threads stack) + tss.rsp1 = 0; + tss.rsp2 = 0; + + // Interrupt stacks can all be 0 + tss.ist1 = 0; + tss.ist2 = 0; + tss.ist3 = 0; + tss.ist4 = 0; + tss.ist5 = 0; + tss.ist6 = 0; + tss.ist7 = 0; + + // Ports TODO when setting up userspace drivers come back to this + tss.io_bitmap_offset = 0; + + // Split the base into 4 parts (16 bits, 8 bits, 8 bits, 32 bits) + auto base = (uint64_t) &tss; + uint16_t base_1 = base & 0xFFFF; + uint8_t base_2 = (base >> 16) & 0xFF; + uint8_t base_3 = (base >> 24) & 0xFF; + uint32_t base_4 = (base >> 32) & 0xFFFFFFFF; + + uint16_t limit_low = sizeof(tss); + + // Flags: 1 - Type = 0x9, Descriptor Privilege Level = 0, Present = 1 + // 2 - Available = 0, Granularity = 0 + uint8_t flags_1 = 0x89; + uint8_t flags_2 = 0; + + // Create the TSS descriptors + uint64_t tss_descriptor_low = (uint64_t) base_3 << 56 | (uint64_t) flags_2 << 48 | (uint64_t) flags_1 << 40 | (uint64_t) base_2 << 32 | (uint64_t) base_1 << 16 | (uint64_t) limit_low; + uint64_t tss_descriptor_high = base_4; + + // Store in the GDT + gdt->table[5] = tss_descriptor_low; + gdt->table[6] = tss_descriptor_high; + + // Load the TSS + Logger::DEBUG() << "Loading TSS: 0x0" << tss_descriptor_low << " 0x0" << tss_descriptor_high << " at 0x" << (uint64_t) &tss << "\n"; + asm volatile("ltr %%ax" : : "a" (0x28)); + + //TODO: For smp - load the TSS for each core or find a better way to do this } /** @@ -272,24 +267,23 @@ void CPU::init_tss(GlobalDescriptorTable* gdt) { cpu_status_t* CPU::prepare_for_panic(cpu_status_t* status) { - // If it may have occurred in a process, switch to the avoidable state - if(Scheduler::system_scheduler() != nullptr && Scheduler::current_process() != nullptr){ - - // Get the current process - Process* process = Scheduler::current_process(); + // If it may have occurred in a process, switch to the avoidable state + if (Scheduler::system_scheduler() != nullptr && Scheduler::current_process() != nullptr) { - // If the faulting address is in lower half just kill the process and move on - if(status && !memory::PhysicalMemoryManager::in_higher_region(status->rip)){ - Logger::ERROR() << "CPU Panicked in process " << process->name.c_str() << " at 0x" << status->rip << " - killing process\n"; - return Scheduler::system_scheduler()->force_remove_process(process); - } - } + // Get the current process + Process* process = Scheduler::current_process(); - // We are panicking - is_panicking = true; + // If the faulting address is in lower half just kill the process and move on + if (status && !memory::PhysicalMemoryManager::in_higher_region(status->rip)) { + Logger::ERROR() << "CPU Panicked in process " << process->name.c_str() << " at 0x" << status->rip << " - killing process\n"; + return Scheduler::system_scheduler()->force_remove_process(process); + } + } - return nullptr; + // We are panicking + is_panicking = true; + return nullptr; } /** @@ -297,62 +291,60 @@ cpu_status_t* CPU::prepare_for_panic(cpu_status_t* status) { */ void CPU::init_sse() { - // Get the CR0 register - uint64_t cr0; - asm volatile("mov %%cr0, %0" : "=r" (cr0)); + // Get the CR0 register + uint64_t cr0; + asm volatile("mov %%cr0, %0" : "=r" (cr0)); - // Get the CR4 register - uint64_t cr4; - asm volatile("mov %%cr4, %0" : "=r" (cr4)); + // Get the CR4 register + uint64_t cr4; + asm volatile("mov %%cr4, %0" : "=r" (cr4)); + // Check if FPU is supported + ASSERT(check_cpu_feature(CPU_FEATURE_EDX::FPU), "FPU not supported - needed for SSE"); - // Check if FPU is supported - ASSERT(check_cpu_feature(CPU_FEATURE_EDX::FPU), "FPU not supported - needed for SSE"); + // Clear the emulation flag, task switch flags and enable the monitor coprocessor, native exception bits + cr0 |= (1 << 1); + cr0 &= ~(1 << 2); + cr0 &= ~(1 << 3); + cr0 |= (1 << 5); + asm volatile("mov %0, %%cr0" : : "r" (cr0)); - // Clear the emulation flag, task switch flags and enable the monitor coprocessor, native exception bits - cr0 |= (1 << 1); - cr0 &= ~(1 << 2); - cr0 &= ~(1 << 3); - cr0 |= (1 << 5); - asm volatile("mov %0, %%cr0" : : "r" (cr0)); + // Enable the FPU + asm volatile("fninit"); - // Enable the FPU - asm volatile("fninit"); + // Check if SSE is supported + ASSERT(check_cpu_feature(CPU_FEATURE_EDX::SSE), "SSE not supported"); - // Check if SSE is supported - ASSERT(check_cpu_feature(CPU_FEATURE_EDX::SSE), "SSE not supported"); + // Enable FSAVE, FSTORE and SSE instructions + cr4 |= (1 << 9); + cr4 |= (1 << 10); + asm volatile("mov %0, %%cr4" : : "r" (cr4)); - // Enable FSAVE, FSTORE and SSE instructions - cr4 |= (1 << 9); - cr4 |= (1 << 10); - asm volatile("mov %0, %%cr4" : : "r" (cr4)); + // Check if XSAVE is supported + s_xsave = check_cpu_feature(CPU_FEATURE_ECX::XSAVE) && check_cpu_feature(CPU_FEATURE_ECX::OSXSAVE); + Logger::DEBUG() << "XSAVE: " << (s_xsave ? "Supported" : "Not Supported") << "\n"; + if (!s_xsave) return; + // Enable the XSAVE and XRESTORE instructions + cr4 |= (1 << 18); + asm volatile("mov %0, %%cr4" : : "r" (cr4)); - // Check if XSAVE is supported - s_xsave = check_cpu_feature(CPU_FEATURE_ECX::XSAVE) && check_cpu_feature(CPU_FEATURE_ECX::OSXSAVE); - Logger::DEBUG() << "XSAVE: " << (s_xsave ? "Supported" : "Not Supported") << "\n"; - if(!s_xsave) return; + // Set the SSE and x87 bits + uint64_t xcr0; + asm volatile("xgetbv" : "=a" (xcr0) : "c" (0)); + xcr0 |= 0x7; + asm volatile("xsetbv" : : "c" (0), "a" (xcr0)); - // Enable the XSAVE and XRESTORE instructions - cr4 |= (1 << 18); - asm volatile("mov %0, %%cr4" : : "r" (cr4)); + // Check if AVX is supported + s_avx = check_cpu_feature(CPU_FEATURE_ECX::AVX); + Logger::DEBUG() << "AVX: " << (s_avx ? "Supported" : "Not Supported") << "\n"; + if (!s_avx) return; - // Set the SSE and x87 bits - uint64_t xcr0; - asm volatile("xgetbv" : "=a" (xcr0) : "c" (0)); - xcr0 |= 0x7; - asm volatile("xsetbv" : : "c" (0), "a" (xcr0)); + // Enable the AVX instructions + cr4 |= (1 << 14); + asm volatile("mov %0, %%cr4" : : "r" (cr4)); - // Check if AVX is supported - s_avx = check_cpu_feature(CPU_FEATURE_ECX::AVX); - Logger::DEBUG() << "AVX: " << (s_avx ? "Supported" : "Not Supported") << "\n"; - if(!s_avx) return; - - // Enable the AVX instructions - cr4 |= (1 << 14); - asm volatile("mov %0, %%cr4" : : "r" (cr4)); - - Logger::DEBUG() << "SSE Enabled\n"; + Logger::DEBUG() << "SSE Enabled\n"; } /** @@ -363,13 +355,12 @@ void CPU::init_sse() { */ bool CPU::check_cpu_feature(CPU_FEATURE_ECX feature) { - // Get the CPUID - uint32_t eax, ebx, ecx, edx; - cpuid(0x1, &eax, &ebx, &ecx, &edx); - - // Check the feature - return ecx & (uint32_t)feature; + // Get the CPUID + uint32_t eax, ebx, ecx, edx; + cpuid(0x1, &eax, &ebx, &ecx, &edx); + // Check the feature + return ecx & (uint32_t) feature; } /** @@ -380,13 +371,12 @@ bool CPU::check_cpu_feature(CPU_FEATURE_ECX feature) { */ bool CPU::check_cpu_feature(CPU_FEATURE_EDX feature) { - // Get the CPUID - uint32_t eax, ebx, ecx, edx; - cpuid(0x1, &eax, &ebx, &ecx, &edx); - - // Check the feature - return edx & (uint32_t)feature; + // Get the CPUID + uint32_t eax, ebx, ecx, edx; + cpuid(0x1, &eax, &ebx, &ecx, &edx); + // Check the feature + return edx & (uint32_t) feature; } /** @@ -395,13 +385,13 @@ bool CPU::check_cpu_feature(CPU_FEATURE_EDX feature) { */ bool CPU::check_nx() { - // Get the EFER MSR - uint64_t efer = read_msr(0xC0000080); + // Get the EFER MSR + uint64_t efer = read_msr(0xC0000080); - // Check if the NX flag is supported (bit 11) - bool supported = efer & (1 << 11); - Logger::DEBUG() << "NX: " << (supported ? "Supported" : "Not Supported") << "\n"; + // Check if the NX flag is supported (bit 11) + bool supported = efer & (1 << 11); + Logger::DEBUG() << "NX: " << (supported ? "Supported" : "Not Supported") << "\n"; - // Return if the NX flag is supported - return supported; + // Return if the NX flag is supported + return supported; } diff --git a/kernel/src/system/gdt.cpp b/kernel/src/system/gdt.cpp index 5134ce51..1a39b286 100644 --- a/kernel/src/system/gdt.cpp +++ b/kernel/src/system/gdt.cpp @@ -11,68 +11,66 @@ using namespace MaxOS::system; GlobalDescriptorTable::GlobalDescriptorTable() { - Logger::INFO() << "Setting up Global Descriptor Table\n"; + Logger::INFO() << "Setting up Global Descriptor Table\n"; - // Null descriptor - table[0] = 0; + // Null descriptor + table[0] = 0; - // Kernel Code Segment descriptor - uint64_t kernel_cs = 0; - kernel_cs |= (uint64_t)DescriptorFlags::Write; - kernel_cs |= (uint64_t)DescriptorFlags::Execute; - kernel_cs |= (uint64_t)DescriptorFlags::CodeOrDataSegment; - kernel_cs |= (uint64_t)DescriptorFlags::Present; - kernel_cs |= (uint64_t)DescriptorFlags::LongMode; - table[1] = kernel_cs; + // Kernel Code Segment descriptor + uint64_t kernel_cs = 0; + kernel_cs |= (uint64_t) DescriptorFlags::Write; + kernel_cs |= (uint64_t) DescriptorFlags::Execute; + kernel_cs |= (uint64_t) DescriptorFlags::CodeOrDataSegment; + kernel_cs |= (uint64_t) DescriptorFlags::Present; + kernel_cs |= (uint64_t) DescriptorFlags::LongMode; + table[1] = kernel_cs; - // Kernel Data Segment descriptor - uint64_t kernel_ds = 0; - kernel_ds |= (uint64_t)DescriptorFlags::Write; - kernel_ds |= (uint64_t)DescriptorFlags::CodeOrDataSegment; - kernel_ds |= (uint64_t)DescriptorFlags::Present; - table[2] = kernel_ds; + // Kernel Data Segment descriptor + uint64_t kernel_ds = 0; + kernel_ds |= (uint64_t) DescriptorFlags::Write; + kernel_ds |= (uint64_t) DescriptorFlags::CodeOrDataSegment; + kernel_ds |= (uint64_t) DescriptorFlags::Present; + table[2] = kernel_ds; - // User code segment descriptor (Change the privilege level to 3) - uint64_t user_cs = 0; - user_cs |= (uint64_t)DescriptorFlags::Write; - user_cs |= (uint64_t)DescriptorFlags::Execute; - user_cs |= (uint64_t)DescriptorFlags::CodeOrDataSegment; - user_cs |= (uint64_t)DescriptorFlags::Present; - user_cs |= (uint64_t)DescriptorFlags::LongMode; - user_cs |= (3ULL << 45); - table[3] = user_cs; + // User code segment descriptor (Change the privilege level to 3) + uint64_t user_cs = 0; + user_cs |= (uint64_t) DescriptorFlags::Write; + user_cs |= (uint64_t) DescriptorFlags::Execute; + user_cs |= (uint64_t) DescriptorFlags::CodeOrDataSegment; + user_cs |= (uint64_t) DescriptorFlags::Present; + user_cs |= (uint64_t) DescriptorFlags::LongMode; + user_cs |= (3ULL << 45); + table[3] = user_cs; - // User data segment descriptor (Change the privilege level to 3) - uint64_t user_ds = 0; - user_ds |= (uint64_t)DescriptorFlags::Write; - user_ds |= (uint64_t)DescriptorFlags::CodeOrDataSegment; - user_ds |= (uint64_t)DescriptorFlags::Present; - user_ds |= (3ULL << 45); - table[4] = user_ds; + // User data segment descriptor (Change the privilege level to 3) + uint64_t user_ds = 0; + user_ds |= (uint64_t) DescriptorFlags::Write; + user_ds |= (uint64_t) DescriptorFlags::CodeOrDataSegment; + user_ds |= (uint64_t) DescriptorFlags::Present; + user_ds |= (3ULL << 45); + table[4] = user_ds; - // Reserve space for the TSS - table[5] = 0; - table[6] = 0; + // Reserve space for the TSS + table[5] = 0; + table[6] = 0; - Logger::DEBUG() << "Created GDT entries\n"; + Logger::DEBUG() << "Created GDT entries\n"; - // Load the GDT - gdtr_t gdtr = { - .size = sizeof(table) - 1, - .address = (uint64_t)table, - }; - asm volatile("lgdt %0" : : "m"(gdtr)); - Logger::DEBUG() << "Loaded GDT of limit 0x" << (uint64_t)gdtr.size << " and address 0x" << (uint64_t)gdtr.address << "\n"; + // Load the GDT + gdtr_t gdtr = { + .size = sizeof(table) - 1, + .address = (uint64_t) table, + }; + asm volatile("lgdt %0" : : "m"(gdtr)); + Logger::DEBUG() << "Loaded GDT of limit 0x" << (uint64_t) gdtr.size << " and address 0x" << (uint64_t) gdtr.address << "\n"; - // Reload the segment registers - asm volatile("mov %0, %%ds" : : "r"(0x10)); - asm volatile("mov %0, %%es" : : "r"(0x10)); - asm volatile("mov %0, %%fs" : : "r"(0x10)); - asm volatile("mov %0, %%gs" : : "r"(0x10)); - asm volatile("mov %0, %%ss" : : "r"(0x10)); - Logger::DEBUG() << "Reloaded segment registers\n"; + // Reload the segment registers + asm volatile("mov %0, %%ds" : : "r"(0x10)); + asm volatile("mov %0, %%es" : : "r"(0x10)); + asm volatile("mov %0, %%fs" : : "r"(0x10)); + asm volatile("mov %0, %%gs" : : "r"(0x10)); + asm volatile("mov %0, %%ss" : : "r"(0x10)); + Logger::DEBUG() << "Reloaded segment registers\n"; } -GlobalDescriptorTable::~GlobalDescriptorTable() -{ -} \ No newline at end of file +GlobalDescriptorTable::~GlobalDescriptorTable() = default; \ No newline at end of file diff --git a/kernel/src/system/multiboot.cpp b/kernel/src/system/multiboot.cpp index e2801639..4b23fac5 100644 --- a/kernel/src/system/multiboot.cpp +++ b/kernel/src/system/multiboot.cpp @@ -15,110 +15,103 @@ Multiboot::Multiboot(unsigned long address, unsigned long magic) : start_address(address) { - // Confirm the bootloader - ASSERT(magic == MULTIBOOT2_BOOTLOADER_MAGIC, "Multiboot2 Bootloader Not Detected"); - Logger::DEBUG() << "Multiboot2 Bootloader Detected at 0x" << (uint64_t)address << "\n"; + // Confirm the bootloader + ASSERT(magic == MULTIBOOT2_BOOTLOADER_MAGIC, "Multiboot2 Bootloader Not Detected"); + Logger::DEBUG() << "Multiboot2 Bootloader Detected at 0x" << (uint64_t) address << "\n"; - multiboot_tag* tag = start_tag(); + multiboot_tag* tag = start_tag(); - // Loop through the tags and load them - while (true) { + // Loop through the tags and load them + while (true) { - // Handle the tag - switch (tag -> type) { + // Handle the tag + switch (tag->type) { - case MULTIBOOT_TAG_TYPE_END: - end_address = (unsigned long)PhysicalMemoryManager::to_lower_region((uint64_t)tag); - return; + case MULTIBOOT_TAG_TYPE_END: + end_address = (unsigned long) PhysicalMemoryManager::to_lower_region((uint64_t) tag); + return; - //0xffffffff80394dd8 - //0xFFFFFFFF80000000 + case MULTIBOOT_TAG_TYPE_FRAMEBUFFER: + m_framebuffer = (multiboot_tag_framebuffer*) tag; + break; - case MULTIBOOT_TAG_TYPE_FRAMEBUFFER: - m_framebuffer = (multiboot_tag_framebuffer *)tag; - break; + case MULTIBOOT_TAG_TYPE_BASIC_MEMINFO: + m_basic_meminfo = (multiboot_tag_basic_meminfo*) tag; + break; - case MULTIBOOT_TAG_TYPE_BASIC_MEMINFO: - m_basic_meminfo = (multiboot_tag_basic_meminfo *)tag; - break; + case MULTIBOOT_TAG_TYPE_BOOT_LOADER_NAME: + m_bootloader_name = (multiboot_tag_string*) tag; + Logger::DEBUG() << "Bootloader: " << m_bootloader_name->string << "\n"; + break; - case MULTIBOOT_TAG_TYPE_BOOT_LOADER_NAME: - m_bootloader_name = (multiboot_tag_string *)tag; - Logger::DEBUG() << "Bootloader: " << m_bootloader_name->string << "\n"; - break; + case MULTIBOOT_TAG_TYPE_BOOTDEV: + multiboot_tag_bootdev* bootdev; + bootdev = (multiboot_tag_bootdev*) tag; + Logger::DEBUG() << "Boot device: drive=0x" << (uint64_t) bootdev->biosdev << ", partition=0x" << (uint64_t) bootdev->part << "\n"; + break; - case MULTIBOOT_TAG_TYPE_BOOTDEV: - multiboot_tag_bootdev *bootdev; - bootdev = (multiboot_tag_bootdev *)tag; - Logger::DEBUG() << "Boot device: drive=0x" << (uint64_t)bootdev->biosdev << ", partition=0x" << (uint64_t)bootdev->part << "\n"; - break; + case MULTIBOOT_TAG_TYPE_MMAP: - case MULTIBOOT_TAG_TYPE_MMAP: + // If there is not already a mmap tag, set it + if (m_mmap == nullptr) + m_mmap = (multiboot_tag_mmap*) tag; - // If there is not already a mmap tag, set it - if (m_mmap == nullptr) - m_mmap = (multiboot_tag_mmap *)tag; + break; - break; + case MULTIBOOT_TAG_TYPE_ACPI_OLD: + m_old_acpi = (multiboot_tag_old_acpi*) tag; + break; - case MULTIBOOT_TAG_TYPE_ACPI_OLD: - m_old_acpi = (multiboot_tag_old_acpi *)tag; - break; + case MULTIBOOT_TAG_TYPE_ACPI_NEW: + m_new_acpi = (multiboot_tag_new_acpi*) tag; + break; - case MULTIBOOT_TAG_TYPE_ACPI_NEW: - m_new_acpi = (multiboot_tag_new_acpi *)tag; - break; + case MULTIBOOT_TAG_TYPE_MODULE: + multiboot_tag_module* module; + module = (multiboot_tag_module*) tag; + Logger::DEBUG() << "Module: start=0x" << (uint64_t) module->mod_start << ", end=0x" << (uint64_t) module->mod_end << ", cmdline=" << module->cmdline << "\n"; + m_module = module; + break; + } - case MULTIBOOT_TAG_TYPE_MODULE: - multiboot_tag_module *module; - module = (multiboot_tag_module *)tag; - Logger::DEBUG() << "Module: start=0x" << (uint64_t) module->mod_start << ", end=0x" << (uint64_t) module->mod_end << ", cmdline=" << module->cmdline << "\n"; - m_module = module; - break; - } - - // Move to the next tag - tag = (struct multiboot_tag *) ((multiboot_uint8_t *) tag + ((tag->size + 7) & ~7)); - } + // Move to the next tag + tag = (struct multiboot_tag*) ((multiboot_uint8_t*) tag + ((tag->size + 7) & ~7)); + } } Multiboot::~Multiboot() = default; -multiboot_tag_framebuffer *Multiboot::framebuffer() { - - return m_framebuffer; +multiboot_tag_framebuffer* Multiboot::framebuffer() { + return m_framebuffer; } -multiboot_tag_basic_meminfo *Multiboot::basic_meminfo() { - - return m_basic_meminfo; +multiboot_tag_basic_meminfo* Multiboot::basic_meminfo() { + return m_basic_meminfo; } -multiboot_tag_string *Multiboot::bootloader_name() { - - return m_bootloader_name; +multiboot_tag_string* Multiboot::bootloader_name() { + return m_bootloader_name; } -multiboot_tag_mmap *Multiboot::mmap() { - - return m_mmap; +multiboot_tag_mmap* Multiboot::mmap() { + return m_mmap; } -multiboot_tag_old_acpi *Multiboot::old_acpi() { +multiboot_tag_old_acpi* Multiboot::old_acpi() { - return m_old_acpi; + return m_old_acpi; } -multiboot_tag_new_acpi *Multiboot::new_acpi() { +multiboot_tag_new_acpi* Multiboot::new_acpi() { - return m_new_acpi; + return m_new_acpi; } /** @@ -128,24 +121,24 @@ multiboot_tag_new_acpi *Multiboot::new_acpi() { */ bool Multiboot::is_reserved(multiboot_uint64_t address) { - // Loop through the tags checking if the address is reserved - for(multiboot_tag* tag = start_tag(); tag->type != MULTIBOOT_TAG_TYPE_END; tag = (struct multiboot_tag *) ((multiboot_uint8_t *) tag + ((tag->size + 7) & ~7))) { + // Loop through the tags checking if the address is reserved + for (multiboot_tag* tag = start_tag(); tag->type != MULTIBOOT_TAG_TYPE_END; tag = (struct multiboot_tag*) ((multiboot_uint8_t*) tag + ((tag->size + 7) & ~7))) { - // Check if the tag is a module or mmap - if(tag -> type != MULTIBOOT_TAG_TYPE_MODULE && tag -> type != MULTIBOOT_TAG_TYPE_MMAP) - continue; + // Check if the tag is a module or mmap + if (tag->type != MULTIBOOT_TAG_TYPE_MODULE && tag->type != MULTIBOOT_TAG_TYPE_MMAP) + continue; - // Get the module tag - auto* module = (struct multiboot_tag_module*)tag; + // Get the module tag + auto* module = (struct multiboot_tag_module*) tag; - // Check if the address is within the module - if(address >= module -> mod_start && address < module -> mod_end) - return true; - } + // Check if the address is within the module + if (address >= module->mod_start && address < module->mod_end) + return true; + } - // Not part of multiboot - return false; + // Not part of multiboot + return false; } @@ -154,7 +147,7 @@ bool Multiboot::is_reserved(multiboot_uint64_t address) { * * @return The start tag */ -multiboot_tag *Multiboot::start_tag() const { +multiboot_tag* Multiboot::start_tag() const { - return (multiboot_tag*)(start_address + PhysicalMemoryManager::s_higher_half_kernel_offset + 8); + return (multiboot_tag*) (start_address + PhysicalMemoryManager::s_higher_half_kernel_offset + 8); } diff --git a/kernel/src/system/syscalls.cpp b/kernel/src/system/syscalls.cpp index 418a9d4d..049cada1 100644 --- a/kernel/src/system/syscalls.cpp +++ b/kernel/src/system/syscalls.cpp @@ -14,25 +14,25 @@ using namespace MaxOS::memory; SyscallManager::SyscallManager() -: InterruptHandler(0x80) +: InterruptHandler(0x80) { - // Clear the args - Logger::INFO() << "Setting up Syscalls \n"; - m_current_args = new syscall_args_t; - - // Register the handlers - set_syscall_handler(SyscallType::CLOSE_PROCESS, syscall_close_process); - set_syscall_handler(SyscallType::KLOG, syscall_klog); - set_syscall_handler(SyscallType::CREATE_SHARED_MEMORY, syscall_create_shared_memory); - set_syscall_handler(SyscallType::OPEN_SHARED_MEMORY, syscall_open_shared_memory); - set_syscall_handler(SyscallType::ALLOCATE_MEMORY, syscall_allocate_memory); - set_syscall_handler(SyscallType::FREE_MEMORY, syscall_free_memory); - set_syscall_handler(SyscallType::CREATE_IPC_ENDPOINT, syscall_create_ipc_endpoint); - set_syscall_handler(SyscallType::SEND_IPC_MESSAGE, syscall_send_ipc_message); - set_syscall_handler(SyscallType::REMOVE_IPC_ENDPOINT, syscall_remove_ipc_endpoint); - set_syscall_handler(SyscallType::THREAD_YIELD, syscall_thread_yield); - set_syscall_handler(SyscallType::THREAD_SLEEP, syscall_thread_sleep); + // Clear the args + Logger::INFO() << "Setting up Syscalls \n"; + m_current_args = new syscall_args_t; + + // Register the handlers + set_syscall_handler(SyscallType::CLOSE_PROCESS, syscall_close_process); + set_syscall_handler(SyscallType::KLOG, syscall_klog); + set_syscall_handler(SyscallType::CREATE_SHARED_MEMORY, syscall_create_shared_memory); + set_syscall_handler(SyscallType::OPEN_SHARED_MEMORY, syscall_open_shared_memory); + set_syscall_handler(SyscallType::ALLOCATE_MEMORY, syscall_allocate_memory); + set_syscall_handler(SyscallType::FREE_MEMORY, syscall_free_memory); + set_syscall_handler(SyscallType::CREATE_IPC_ENDPOINT, syscall_create_ipc_endpoint); + set_syscall_handler(SyscallType::SEND_IPC_MESSAGE, syscall_send_ipc_message); + set_syscall_handler(SyscallType::REMOVE_IPC_ENDPOINT, syscall_remove_ipc_endpoint); + set_syscall_handler(SyscallType::THREAD_YIELD, syscall_thread_yield); + set_syscall_handler(SyscallType::THREAD_SLEEP, syscall_thread_sleep); } @@ -46,39 +46,39 @@ SyscallManager::~SyscallManager() = default; */ cpu_status_t* SyscallManager::handle_interrupt(cpu_status_t* status) { - // Get the args from the cpu state - m_current_args -> arg0 = status -> rdi; - m_current_args -> arg1 = status -> rsi; - m_current_args -> arg2 = status -> rdx; - m_current_args -> arg3 = status -> r10; - m_current_args -> arg4 = status -> r8; - m_current_args -> arg5 = status -> r9; - m_current_args -> return_value = 0; - m_current_args -> return_state = status; - - // Call the handler - uint64_t syscall = status -> rax; - if(m_syscall_handlers[syscall] != nullptr) - m_current_args = m_syscall_handlers[syscall](m_current_args); - else - Logger::ERROR() << "Syscall " << syscall << " not found\n"; - - // If there is a specific return state, use that - if(m_current_args -> return_state != status) - return m_current_args -> return_state; - - // Update the cpu state - status -> rdi = m_current_args -> arg0; - status -> rsi = m_current_args -> arg1; - status -> rdx = m_current_args -> arg2; - status -> r10 = m_current_args -> arg3; - status -> r8 = m_current_args -> arg4; - status -> r9 = m_current_args -> arg5; - status -> rax = m_current_args -> return_value; - - - // Return the status - return status; + // Get the args from the cpu state + m_current_args->arg0 = status->rdi; + m_current_args->arg1 = status->rsi; + m_current_args->arg2 = status->rdx; + m_current_args->arg3 = status->r10; + m_current_args->arg4 = status->r8; + m_current_args->arg5 = status->r9; + m_current_args->return_value = 0; + m_current_args->return_state = status; + + // Call the handler + uint64_t syscall = status->rax; + if (m_syscall_handlers[syscall] != nullptr) + m_current_args = m_syscall_handlers[syscall](m_current_args); + else + Logger::ERROR() << "Syscall " << syscall << " not found\n"; + + // If there is a specific return state, use that + if (m_current_args->return_state != status) + return m_current_args->return_state; + + // Update the cpu state + status->rdi = m_current_args->arg0; + status->rsi = m_current_args->arg1; + status->rdx = m_current_args->arg2; + status->r10 = m_current_args->arg3; + status->r8 = m_current_args->arg4; + status->r9 = m_current_args->arg5; + status->rax = m_current_args->return_value; + + + // Return the status + return status; } /** @@ -88,7 +88,8 @@ cpu_status_t* SyscallManager::handle_interrupt(cpu_status_t* status) { * @param handler The handler to set */ void SyscallManager::set_syscall_handler(SyscallType syscall, syscall_func_t handler) { - m_syscall_handlers[(uint8_t)syscall] = handler; + + m_syscall_handlers[(uint8_t) syscall] = handler; } /** @@ -97,7 +98,8 @@ void SyscallManager::set_syscall_handler(SyscallType syscall, syscall_func_t han * @param syscall The syscall ID number */ void SyscallManager::remove_syscall_handler(SyscallType syscall) { - m_syscall_handlers[(uint8_t)syscall] = nullptr; + + m_syscall_handlers[(uint8_t) syscall] = nullptr; } @@ -107,65 +109,62 @@ void SyscallManager::remove_syscall_handler(SyscallType syscall) { * @param args Arg0 = pid Arg1 = exit code * @return Nothing */ -system::syscall_args_t* SyscallManager::syscall_close_process(system::syscall_args_t *args) { +system::syscall_args_t* SyscallManager::syscall_close_process(system::syscall_args_t* args) { - // Get the args - uint64_t pid = args -> arg0; - int exit_code = (int)args -> arg1; + // Get the args + uint64_t pid = args->arg0; + int exit_code = (int) args->arg1; - // Close the process - Process* process = pid == 0 ? Scheduler::current_process() : Scheduler::get_process(pid); - Scheduler::system_scheduler() -> remove_process(process); + // Close the process + Process* process = pid == 0 ? Scheduler::current_process() : Scheduler::get_process(pid); + Scheduler::system_scheduler()->remove_process(process); - // Schedule the next process - cpu_status_t* next_process = Scheduler::system_scheduler() -> schedule_next(args -> return_state); - args -> return_state = next_process; + // Schedule the next process + cpu_status_t* next_process = Scheduler::system_scheduler()->schedule_next(args->return_state); + args->return_state = next_process; - // Done - return args; + // Done + return args; } -syscall_args_t *SyscallManager::syscall_klog(syscall_args_t *args) { +syscall_args_t* SyscallManager::syscall_klog(syscall_args_t* args) { - char* message = (char*)args -> arg0; + char* message = (char*) args->arg0; - // If the first two characters are %h then no header - if(message[0] == '%' && message[1] == 'h') - Logger::INFO() << message + 2; - else - Logger::INFO() << ANSI_COLOURS[FG_Blue] << "(" << Scheduler::current_process() -> name.c_str() << ":" << Scheduler::current_thread() -> tid << "): " << ANSI_COLOURS[Reset] << message; + // If the first two characters are %h then no header + if (message[0] == '%' && message[1] == 'h') + Logger::INFO() << message + 2; + else + Logger::INFO() << ANSI_COLOURS[FG_Blue] << "(" << Scheduler::current_process()->name.c_str() << ":" << Scheduler::current_thread()->tid << "): " << ANSI_COLOURS[Reset] << message; - return args; + return args; } - - /** * @brief System call to create a shared memory block (To close, free the memory, it is automatically handled when the process exits) * * @param args Arg0 = size, Arg1 = name * @return The virtual address of the shared memory block or null if failed */ -syscall_args_t* SyscallManager::syscall_create_shared_memory(syscall_args_t *args) { - - // Extract the arguments - size_t size = args->arg0; - char* name = (char*)args->arg1; +syscall_args_t* SyscallManager::syscall_create_shared_memory(syscall_args_t* args) { - // Ensure they are valid - if(size == 0 || name == nullptr) - return nullptr; + // Extract the arguments + size_t size = args->arg0; + char* name = (char*) args->arg1; - // Create the memory block - SharedMemory* new_block = Scheduler::scheduler_ipc() ->alloc_shared_memory(size, name); + // Ensure they are valid + if (size == 0 || name == nullptr) + return nullptr; - // Load the block - void* virtual_address = MemoryManager::s_current_memory_manager -> vmm() ->load_shared_memory(new_block -> physical_address(), size); + // Create the memory block + SharedMemory* new_block = Scheduler::scheduler_ipc()->alloc_shared_memory(size, name); - // Return to the user - args -> return_value = (uint64_t)virtual_address; - return args; + // Load the block + void* virtual_address = MemoryManager::s_current_memory_manager->vmm()->load_shared_memory(new_block->physical_address(), size); + // Return to the user + args->return_value = (uint64_t) virtual_address; + return args; } /** @@ -174,25 +173,24 @@ syscall_args_t* SyscallManager::syscall_create_shared_memory(syscall_args_t *arg * @param args Arg0 = name * @return The virtual address of the shared memory block or null if failed */ -syscall_args_t * SyscallManager::syscall_open_shared_memory(syscall_args_t *args) { - - // Extract the arguments - uint64_t name = args->arg0; +syscall_args_t* SyscallManager::syscall_open_shared_memory(syscall_args_t* args) { - // Ensure they are valid - if(name == 0) - return nullptr; + // Extract the arguments + uint64_t name = args->arg0; - // Get the block (don't care if null as that is caught in the load_shared_memory function) - SharedMemory* block = Scheduler::scheduler_ipc() -> get_shared_memory((char*)name); + // Ensure they are valid + if (name == 0) + return nullptr; - // Load the block - void* virtual_address = MemoryManager::s_current_memory_manager -> vmm() ->load_shared_memory(block -> physical_address(), block -> size()); + // Get the block (don't care if null as that is caught in the load_shared_memory function) + SharedMemory* block = Scheduler::scheduler_ipc()->get_shared_memory((char*) name); - // Return to the user - args -> return_value = (uint64_t)virtual_address; - return args; + // Load the block + void* virtual_address = MemoryManager::s_current_memory_manager->vmm()->load_shared_memory(block->physical_address(), block->size()); + // Return to the user + args->return_value = (uint64_t) virtual_address; + return args; } /** @@ -201,18 +199,17 @@ syscall_args_t * SyscallManager::syscall_open_shared_memory(syscall_args_t *args * @param args Arg0 = size * @return The address of the memory block or null if failed */ -syscall_args_t* SyscallManager::syscall_allocate_memory(syscall_args_t *args) { - - // Get the size - size_t size = args -> arg0; +syscall_args_t* SyscallManager::syscall_allocate_memory(syscall_args_t* args) { - // Malloc the memory - void* address = MemoryManager::malloc(size); + // Get the size + size_t size = args->arg0; - // Return the address - args -> return_value = (uint64_t)address; - return args; + // Malloc the memory + void* address = MemoryManager::malloc(size); + // Return the address + args->return_value = (uint64_t) address; + return args; } /** @@ -221,17 +218,16 @@ syscall_args_t* SyscallManager::syscall_allocate_memory(syscall_args_t *args) { * @param args Arg0 = address * @return Nothing */ -syscall_args_t* SyscallManager::syscall_free_memory(syscall_args_t *args) { +syscall_args_t* SyscallManager::syscall_free_memory(syscall_args_t* args) { - // Get the address - void* address = (void*)args -> arg0; + // Get the address + void* address = (void*) args->arg0; - // Free the memory - MemoryManager::free(address); - - // Done - return args; + // Free the memory + MemoryManager::free(address); + // Done + return args; } /** @@ -240,20 +236,19 @@ syscall_args_t* SyscallManager::syscall_free_memory(syscall_args_t *args) { * @param args Arg0 = name * @return The IPC endpoint buffer linked list address */ -system::syscall_args_t* SyscallManager::syscall_create_ipc_endpoint(system::syscall_args_t *args) { - - // Get the name - char* name = (char*)args -> arg0; - if(name == nullptr) - return nullptr; +system::syscall_args_t* SyscallManager::syscall_create_ipc_endpoint(system::syscall_args_t* args) { - // Create the endpoint - SharedMessageEndpoint* endpoint = Scheduler::scheduler_ipc() -> create_message_endpoint(name); + // Get the name + char* name = (char*) args->arg0; + if (name == nullptr) + return nullptr; - // Return the endpoint - args -> return_value = (uint64_t)endpoint -> queue(); - return args; + // Create the endpoint + SharedMessageEndpoint* endpoint = Scheduler::scheduler_ipc()->create_message_endpoint(name); + // Return the endpoint + args->return_value = (uint64_t) endpoint->queue(); + return args; } /** @@ -262,22 +257,22 @@ system::syscall_args_t* SyscallManager::syscall_create_ipc_endpoint(system::sysc * @param args Arg0 = endpoint name, Arg1 = message, Arg2 = size * @return Nothing */ -system::syscall_args_t* SyscallManager::syscall_send_ipc_message(system::syscall_args_t *args) { +system::syscall_args_t* SyscallManager::syscall_send_ipc_message(system::syscall_args_t* args) { - // Get the args - char* endpoint = (char*)args -> arg0; - void* message = (void*)args -> arg1; - size_t size = args -> arg2; + // Get the args + char* endpoint = (char*) args->arg0; + void* message = (void*) args->arg1; + size_t size = args->arg2; - // Validate the args - if(endpoint == nullptr || message == nullptr || size == 0) - return nullptr; + // Validate the args + if (endpoint == nullptr || message == nullptr || size == 0) + return nullptr; - // Send the message - Scheduler::scheduler_ipc() ->get_message_endpoint(endpoint) -> queue_message(message, size); + // Send the message + Scheduler::scheduler_ipc()->get_message_endpoint(endpoint)->queue_message(message, size); - // All done - return args; + // All done + return args; } /** @@ -286,14 +281,13 @@ system::syscall_args_t* SyscallManager::syscall_send_ipc_message(system::syscall * @param args Arg0 = endpoint name * @return Nothing */ -system::syscall_args_t * SyscallManager::syscall_remove_ipc_endpoint(system::syscall_args_t *args) { - - // Remove the endpoint - Scheduler::scheduler_ipc() -> free_message_endpoint((char*)args -> arg0); +system::syscall_args_t* SyscallManager::syscall_remove_ipc_endpoint(system::syscall_args_t* args) { - // Done - return args; + // Remove the endpoint + Scheduler::scheduler_ipc()->free_message_endpoint((char*) args->arg0); + // Done + return args; } @@ -303,13 +297,13 @@ system::syscall_args_t * SyscallManager::syscall_remove_ipc_endpoint(system::sys * @param args Nothing * @return Nothing */ -system::syscall_args_t* SyscallManager::syscall_thread_yield(system::syscall_args_t *args) { +system::syscall_args_t* SyscallManager::syscall_thread_yield(system::syscall_args_t* args) { - // Yield - cpu_status_t* next_process = Scheduler::system_scheduler() -> yield(); - args -> return_state = next_process; + // Yield + cpu_status_t* next_process = Scheduler::system_scheduler()->yield(); + args->return_state = next_process; - return args; + return args; } /** @@ -318,21 +312,20 @@ system::syscall_args_t* SyscallManager::syscall_thread_yield(system::syscall_arg * @param args Arg0 = milliseconds * @return Nothing */ -system::syscall_args_t* SyscallManager::syscall_thread_sleep(system::syscall_args_t *args) { +system::syscall_args_t* SyscallManager::syscall_thread_sleep(system::syscall_args_t* args) { - // Get the milliseconds - size_t milliseconds = args -> arg0; + // Get the milliseconds + size_t milliseconds = args->arg0; - // Store the updated state in the thread as the scheduler will not have the updated state when switching to the next thread - Scheduler::current_thread() -> execution_state = args -> return_state; + // Store the updated state in the thread as the scheduler will not have the updated state when switching to the next thread + Scheduler::current_thread()->execution_state = args->return_state; - // Sleep the thread - cpu_status_t* next_thread = Scheduler::current_thread() -> sleep(milliseconds); - args -> return_state = next_thread; - - // Done - return args; + // Sleep the thread + cpu_status_t* next_thread = Scheduler::current_thread()->sleep(milliseconds); + args->return_state = next_thread; + // Done + return args; } /** @@ -341,24 +334,23 @@ system::syscall_args_t* SyscallManager::syscall_thread_sleep(system::syscall_arg * @param args Arg0 = tid Arg1 = exit code * @return Nothing */ -system::syscall_args_t* SyscallManager::syscall_thread_close(system::syscall_args_t *args) { - +system::syscall_args_t* SyscallManager::syscall_thread_close(system::syscall_args_t* args) { - // Get the args - uint64_t tid = args -> arg0; - int exit_code = (int)args -> arg1; - // Get the thread if it is 0 then it is the current thread - Thread* thread = tid == 0 ? Scheduler::current_thread() : Scheduler::get_thread(tid); + // Get the args + uint64_t tid = args->arg0; + int exit_code = (int) args->arg1; - // Close the thread - Scheduler::get_process(thread -> parent_pid) -> remove_thread(tid); + // Get the thread if it is 0 then it is the current thread + Thread* thread = tid == 0 ? Scheduler::current_thread() : Scheduler::get_thread(tid); - // Schedule the next thread - cpu_status_t* next_thread = Scheduler::system_scheduler() -> schedule_next(args -> return_state); - args -> return_state = next_thread; + // Close the thread + Scheduler::get_process(thread->parent_pid)->remove_thread(tid); - // Done - return args; + // Schedule the next thread + cpu_status_t* next_thread = Scheduler::system_scheduler()->schedule_next(args->return_state); + args->return_state = next_thread; -} + // Done + return args; +} \ No newline at end of file From 56fad28f46be3822673cd060d4e0c57e67d01ed6 Mon Sep 17 00:00:00 2001 From: Max Tyson <98maxt98@gmail.com> Date: Tue, 26 Aug 2025 20:42:18 +1200 Subject: [PATCH 33/38] Process Resource Management --- kernel/include/processes/ipc.h | 7 +- kernel/include/processes/process.h | 7 +- kernel/include/processes/resource.h | 101 ++++++++++ kernel/src/common/logger.cpp | 2 +- kernel/src/processes/resource.cpp | 287 ++++++++++++++++++++++++++++ 5 files changed, 396 insertions(+), 8 deletions(-) create mode 100644 kernel/include/processes/resource.h create mode 100644 kernel/src/processes/resource.cpp diff --git a/kernel/include/processes/ipc.h b/kernel/include/processes/ipc.h index b68439fa..ae8626b4 100644 --- a/kernel/include/processes/ipc.h +++ b/kernel/include/processes/ipc.h @@ -74,10 +74,11 @@ namespace MaxOS { */ class InterProcessCommunicationManager { - common::Vector m_shared_memory_blocks; - common::Vector m_message_endpoints; + private: + common::Vector m_shared_memory_blocks; + common::Vector m_message_endpoints; - common::Spinlock m_lock; + common::Spinlock m_lock; public: diff --git a/kernel/include/processes/process.h b/kernel/include/processes/process.h index e98d5cf0..2e67f152 100644 --- a/kernel/include/processes/process.h +++ b/kernel/include/processes/process.h @@ -7,12 +7,14 @@ #include #include +#include #include #include #include #include #include #include +#include namespace MaxOS @@ -67,8 +69,6 @@ namespace MaxOS void save_sse_state(); void restore_sse_state(); - - }; /** @@ -79,8 +79,6 @@ namespace MaxOS { private: - - common::Vector m_resource_ids; common::Vector m_threads; uint64_t m_pid = 0; @@ -103,6 +101,7 @@ namespace MaxOS string name; memory::MemoryManager* memory_manager = nullptr; + ResourceManager resources; }; diff --git a/kernel/include/processes/resource.h b/kernel/include/processes/resource.h new file mode 100644 index 00000000..3dadf73e --- /dev/null +++ b/kernel/include/processes/resource.h @@ -0,0 +1,101 @@ +// +// Created by 98max on 8/26/2025. +// + +#ifndef MAXOS_PROCESSES_RESOURCE_H +#define MAXOS_PROCESSES_RESOURCE_H + +#include +#include +#include +#include + +namespace MaxOS { + namespace processes { + + enum class ResourceType{ + MESSAGE_ENDPOINT, + SHARED_MEMORY + }; + + class Resource { + + private: + + string m_name; + ResourceType m_type; + + public: + + Resource(const string& name, ResourceType type); + ~Resource(); + + string name(); + ResourceType type(); + + virtual void open(); + virtual void close(); + + virtual size_t read(void* buffer, size_t size); + virtual size_t write(const void* buffer, size_t size); + }; + + class ResourceRegistry{ + + private: + common::Map m_resources; + common::Map m_resource_uses; + + ResourceType m_type; + + public: + explicit ResourceRegistry(ResourceType type); + ~ResourceRegistry(); + + ResourceType type(); + + Resource* get_resource(const string& name); + void close_resource(Resource* resource); + + void register_resource(Resource* resource, const string& name); + }; + + class GlobalResourceRegistry{ + + private: + common::Map m_registries; + inline static GlobalResourceRegistry* s_current; + + public: + GlobalResourceRegistry(); + ~GlobalResourceRegistry(); + + static ResourceRegistry* get_registry(ResourceType type); + + static void add_registry(ResourceType type, ResourceRegistry* registry); + static void remove_registry(ResourceRegistry* registry); + + + }; + + class ResourceManager{ + + private: + common::Map m_resources; + + uint64_t m_next_handle = 1; + + public: + ResourceManager(); + ~ResourceManager(); + + uint64_t open_resource(ResourceType type, const string& name); + void close_resource(uint64_t handle); + + Resource* get_resource(uint64_t handle); + Resource* get_resource(const string& name); + }; + } +} + +#endif //MAXOS_PROCESSES_RESOURCE_H diff --git a/kernel/src/common/logger.cpp b/kernel/src/common/logger.cpp index e1ed8b35..636208cb 100644 --- a/kernel/src/common/logger.cpp +++ b/kernel/src/common/logger.cpp @@ -18,7 +18,7 @@ Logger::Logger() s_active_logger = this; // The following line is generated automatically by the MaxOS build system. - s_progress_total = 24; + s_progress_total = 23; } diff --git a/kernel/src/processes/resource.cpp b/kernel/src/processes/resource.cpp new file mode 100644 index 00000000..91c977b1 --- /dev/null +++ b/kernel/src/processes/resource.cpp @@ -0,0 +1,287 @@ +// +// Created by 98max on 8/26/2025. +// +#include + +using namespace MaxOS; +using namespace MaxOS::processes; + +Resource::Resource(const string& name, ResourceType type) +: m_name(name), + m_type(type) +{ + +} + +Resource::~Resource() = default; + +/** + * @brief Opens the resource + */ +void Resource::open() { + +} + + +/** + * @brief Closes the resource + */ +void Resource::close() { + +} + +/** + * @brief Read a certain amount of bytes from a resource + * + * @param buffer The buffer to read into + * @param size How many bytes to read + * @return How many bytes were successfully read + */ +size_t Resource::read(void* buffer, size_t size) { + return 0; +} + +/** + * @brief Write a certain amount of bytes to a resource + * + * @param buffer The buffer to read from + * @param size How many bytes to write + * @return How many bytes were successfully written + */ +size_t Resource::write(void const* buffer, size_t size) { + + return 0; +} + +/** + * @brief Gets the name of this resource + * + * @return The name + */ +string Resource::name() { + return m_name; +} + +/** + * @brief Gets the type of this resource + * + * @return Tje type + */ +ResourceType Resource::type() { + + return m_type; +} + +ResourceRegistry::ResourceRegistry(ResourceType type) +: m_type(type) +{ + + GlobalResourceRegistry::add_registry(type, this); + +} + +ResourceRegistry::~ResourceRegistry(){ + + GlobalResourceRegistry::remove_registry(this); +} + +/** + * @brief Gets the type of resources this registry handles + * + * @return The type + */ +ResourceType ResourceRegistry::type() { + + return m_type; +} + +/** + * @brief Gets a resource from the registry by name + * + * @param name The name of the resource to find + * @return The resource or nullptr if the resource was not found + */ +Resource* ResourceRegistry::get_resource(string const& name) { + + // Resource isn't stored in this registry + if(m_resources.find(name) == m_resources.end()) + return nullptr; + + // Increment the use count of the resource and return it + m_resource_uses[name]++; + return m_resources[name]; + +} + +/** + * @brief Close the resource provided, if it exists in this registry + * + * @param resource The resource to close + */ +void ResourceRegistry::close_resource(Resource* resource) { + + // Resource isn't stored in this registry + if(m_resources.find(resource->name()) == m_resources.end()) + return; + + m_resource_uses[resource->name()]--; + + // Don't close a resource that has more processes using it + if(m_resource_uses[resource->name()]) + return; + + // Can safely close the resource + resource->close(); + m_resources.erase(resource->name()); + m_resource_uses.erase(resource->name()); + delete resource; +} + +/** + * @brief Registers a resource in the registry + * + * @param resource The resource to store + * @param name The name of the resource (must not already be in use) + */ +void ResourceRegistry::register_resource(Resource* resource, const string& name) { + + // Resource name is already stored in this registry + if(m_resources.find(resource->name()) != m_resources.end()) + return; + + m_resources.insert(name, resource); + m_resource_uses.insert(name, 0); +} + +GlobalResourceRegistry::GlobalResourceRegistry() { + s_current = this; + +} + +GlobalResourceRegistry::~GlobalResourceRegistry() { + if(s_current == this) + s_current = nullptr; + +} + +/** + * @brief Gets a registry of a type + * + * @param type The type of registry to get + * @return The registry or nullptr if not found + */ +ResourceRegistry* GlobalResourceRegistry::get_registry(ResourceType type) { + + auto registry = s_current->m_registries.find(type); + if(registry == s_current->m_registries.end()) + return nullptr; + + return registry->second; +} + +/** + * @brief Adds a registry to the global list if there is not already one for that type + * + * @param type The type of registry being added + * @param registry The registry to add + */ +void GlobalResourceRegistry::add_registry(ResourceType type, ResourceRegistry* registry) { + + // Does it already exist? + if(s_current->m_registries.find(type) != s_current->m_registries.end()) + return; + + s_current->m_registries.insert(type, registry); +} + +/** + * @brief Adds a registry to the global list if there is not already one for that type + * + * @param type The type of registry being added + * @param registry The registry to add + */ +void GlobalResourceRegistry::remove_registry(ResourceRegistry* registry) { + + // Does it already exist? + if(s_current->m_registries.find(registry->type()) != s_current->m_registries.end()) + return; + + s_current->m_registries.erase(registry->type()); + +} + +ResourceManager::ResourceManager() = default; + +ResourceManager::~ResourceManager(){ + + // Close all resources + for(const auto& resource : m_resources) + close_resource(resource.first); + +}; + +/** + * @brief Registers a resource with the resource manager and then opens it + * + * @param resource The resource to register + * @param name The name of the resource + * @return The handle id of the resource or 0 if failed + */ +uint64_t ResourceManager::open_resource(ResourceType type, string const& name) { + + // Get the resource + auto resource = GlobalResourceRegistry::get_registry(type) -> get_resource(name); + if(!resource) + return 0; + + // Store it + m_resources.insert(m_next_handle, resource); + + // Open it + resource->open(); + return m_next_handle++; +} + +/** + * @brief Un registers the resource and then closes it + * @param handle + */ +void ResourceManager::close_resource(uint64_t handle) { + + Resource* resource = m_resources[handle]; + + // Remove it + m_resources.erase(handle); + + // Close it + GlobalResourceRegistry::get_registry(resource->type()) -> close_resource(resource); +} + +/** + * @brief Gets a resource based on the handle id + * + * @param handle The handle number of the resource + * @return The resource or nullptr if not found + */ +Resource* ResourceManager::get_resource(uint64_t handle) { + + if(m_resources.find(handle) == m_resources.end()) + return nullptr; + + return m_resources[handle]; +} + +/** + * @brief Finds a resource by name + * + * @param name The name of the resource + * @return The resource or nullptr if not found + */ +Resource* ResourceManager::get_resource(string const& name) { + + for(const auto& resource : m_resources) + if(resource.second->name() == name) + return resource.second; + + return nullptr; +} \ No newline at end of file From d495f7299d75f1121ea0aba511c3fc0f0c6011bf Mon Sep 17 00:00:00 2001 From: Max Tyson <98maxt98@gmail.com> Date: Sun, 31 Aug 2025 12:51:50 +1200 Subject: [PATCH 34/38] Resource Syscalls & IPC setup --- docs/Syscalls.md | 28 +-- kernel/include/common/buffer.h | 4 +- kernel/include/common/vector.h | 5 +- kernel/include/processes/ipc.h | 70 ++---- kernel/include/processes/process.h | 2 +- kernel/include/processes/resource.h | 60 +++-- kernel/include/processes/scheduler.h | 5 +- kernel/include/system/syscalls.h | 25 +-- kernel/src/memory/virtual.cpp | 18 +- kernel/src/processes/ipc.cpp | 325 +++++---------------------- kernel/src/processes/resource.cpp | 107 ++++++--- kernel/src/processes/scheduler.cpp | 18 +- kernel/src/system/syscalls.cpp | 191 ++++++++-------- 13 files changed, 325 insertions(+), 533 deletions(-) diff --git a/docs/Syscalls.md b/docs/Syscalls.md index 1946fb2e..e07ad26c 100644 --- a/docs/Syscalls.md +++ b/docs/Syscalls.md @@ -14,17 +14,17 @@ This is helpful for updating syscall table (I don't know if that will even happe | rax | rdi | rsi | rdx | r10 | r8 | r9 | ## Syscall Table (v1) -| Number | Name | Description | Arg0 | Arg1 | Arg2 | Arg3 | Arg4 | Arg5 | Return | -|--------|-----------------------|---------------------------------|----------------------------------------|------------|-------------|------|------|------|------------------------------------------------| -| 0 | close_process | Exit a process | uint64_t pid (= 0 for current process) | int status | | | | | | -| 1 | klog | Log a message to the kernel log | char* msg | | | | | | | -| 2 | created_shared_memory | Create a shared memory region | size_t size | char* name | | | | | void* address (null if failed) | -| 3 | open_shared_memory | Open a shared memory region | char* name | | | | | | void* address (null if failed) | -| 4 | allocate_memory | Allocate memory | size_t size | | | | | | void* address (null if failed) | -| 5 | free_memory | Free memory | void* address | | | | | | | -| 6 | create_ipc_endpoint | Create an IPC endpoint | char* name | | | | | | void* message buffer address (null if failed) | -| 7 | send_ipc_message | Send an IPC message | char* name | void* data | size_t size | | | | | -| 8 | remove_ipc_endpoint | Remove an IPC endpoint | char* name | | | | | | | -| 9 | thread_yield | Yield the current thread | | | | | | | | -| 10 | thread_sleep | Put the current thread to sleep | uint64_t time (ms) | | | | | | | -| 11 | thread_exit | Exit the current thread | | | | | | | | \ No newline at end of file +| Number | Name | Description | Arg0 | Arg1 | Arg2 | Arg3 | Arg4 | Arg5 | Return | +|--------|-----------------|---------------------------------|----------------------------------------|--------------|--------------|--------------|------|------|---------------------------------------| +| 0 | close_process | Exit a process | uint64_t pid (= 0 for current process) | int status | | | | | | +| 1 | klog | Log a message to the kernel log | char* msg | | | | | | | +| 2 | allocate_memory | Allocate memory | size_t size | | | | | | void* address (null if failed) | +| 3 | free_memory | Free memory | void* address | | | | | | | +| 4 | resource_create | Create a resource | ResourceType type | char* name | size_t flags | | | | int success (1 = success, 0 = failed) | +| 5 | resource_open | Open a resource | ResourceType type | char* name | size_t flags | | | | uint64_t handle (0 if failed) | +| 6 | resource_close | Close a resource | uint64_t handle | size_t flags | | | | | | +| 7 | resource_write | Write to a resource | uint64_t handle | void* buffer | size_t size | size_t flags | | | size_t bytes written (0 if failed) | +| 8 | resource_read | Read from a resource | uint64_t handle | void* buffer | size_t size | size_t flags | | | size_t bytes read (0 if failed) | +| 9 | thread_yield | Yield the current thread | | | | | | | | +| 10 | thread_sleep | Put the current thread to sleep | uint64_t time (ms) | | | | | | | +| 11 | thread_exit | Exit the current thread | | | | | | | | \ No newline at end of file diff --git a/kernel/include/common/buffer.h b/kernel/include/common/buffer.h index 72b26a9b..4e825e08 100644 --- a/kernel/include/common/buffer.h +++ b/kernel/include/common/buffer.h @@ -33,7 +33,7 @@ namespace MaxOS{ Buffer(void* source, size_t size, bool update_offset = true); ~Buffer(); - uint8_t* raw() const; + [[nodiscard]] uint8_t* raw() const; void clear(); void full(uint8_t byte, size_t offset = 0, size_t amount = 0); @@ -41,7 +41,7 @@ namespace MaxOS{ bool update_offset = true; void set_offset(size_t offset); - size_t capacity() const; + [[nodiscard]] size_t capacity() const; void resize(size_t size); void write(uint8_t byte); diff --git a/kernel/include/common/vector.h b/kernel/include/common/vector.h index edb48f60..077ccd18 100644 --- a/kernel/include/common/vector.h +++ b/kernel/include/common/vector.h @@ -366,12 +366,15 @@ namespace MaxOS{ * @brief Removes the m_first_memory_chunk element from the Vector * * @tparam Type Type of the Vector + * @return The element that was removed, or a default constructed element if the Vector is empty */ template Type Vector::pop_front() { // Make sure the Vector is not empty if (m_size == 0) - return; + return Type(); + + // Store the element to return Type element = m_elements[0]; diff --git a/kernel/include/processes/ipc.h b/kernel/include/processes/ipc.h index ae8626b4..cc788c5e 100644 --- a/kernel/include/processes/ipc.h +++ b/kernel/include/processes/ipc.h @@ -13,90 +13,48 @@ #include #include #include - +#include namespace MaxOS { namespace processes { //TODO: LibIPC - class SharedMemory{ + class SharedMemory final : public Resource { private: uintptr_t m_physical_address; size_t m_size; uint64_t m_owner_pid; - public: - SharedMemory(const string& name, size_t size); - ~SharedMemory(); + SharedMemory(const string& name, size_t size, ResourceType type); + ~SharedMemory() final; - string* name; + string name; uint64_t use_count = 1; + size_t read(void* buffer, size_t size, size_t flags) final; + [[nodiscard]] uintptr_t physical_address() const; [[nodiscard]] size_t size() const; }; - - typedef struct SharedMessage{ - void* message_buffer; - size_t message_size; - uintptr_t next_message; - } ipc_message_t; - - typedef struct SharedMessageQueue{ - ipc_message_t* messages; - } ipc_message_queue_t; - - class SharedMessageEndpoint{ + class SharedMessageEndpoint final : public Resource{ private: - ipc_message_queue_t* m_queue = nullptr; - uint64_t m_owner_pid; + common::Vector m_queue {}; common::Spinlock m_message_lock; public: - SharedMessageEndpoint(const string& name); - ~SharedMessageEndpoint(); + SharedMessageEndpoint(const string& name, size_t size, ResourceType type); + ~SharedMessageEndpoint() final; - string* name; - [[nodiscard]] ipc_message_queue_t* queue() const; + size_t read(void* buffer, size_t size, size_t flags) final; + size_t write(const void* buffer, size_t size, size_t flags) final; - void queue_message(void* message, size_t size); - [[nodiscard]] bool owned_by_current_process() const; + void queue_message(void const* message, size_t size); }; - - /** - * @class InterProcessCommunicationManager (IPC) - * @brief Manages the Inter-Process Communication between processes via shared memory and message passing - */ - class InterProcessCommunicationManager { - - private: - common::Vector m_shared_memory_blocks; - common::Vector m_message_endpoints; - - common::Spinlock m_lock; - - public: - - InterProcessCommunicationManager(); - ~InterProcessCommunicationManager(); - - SharedMemory* alloc_shared_memory(size_t size, string name); - SharedMemory* get_shared_memory(const string& name); - void free_shared_memory(const string& name); - void free_shared_memory(uintptr_t physical_address); - void free_shared_memory(SharedMemory* block); - - SharedMessageEndpoint* create_message_endpoint(const string& name); - SharedMessageEndpoint* get_message_endpoint(const string& name); - void free_message_endpoint(const string& name); - static void free_message_endpoint(SharedMessageEndpoint* endpoint); - }; - } } diff --git a/kernel/include/processes/process.h b/kernel/include/processes/process.h index 2e67f152..b035eca7 100644 --- a/kernel/include/processes/process.h +++ b/kernel/include/processes/process.h @@ -101,7 +101,7 @@ namespace MaxOS string name; memory::MemoryManager* memory_manager = nullptr; - ResourceManager resources; + ResourceManager resource_manager; }; diff --git a/kernel/include/processes/resource.h b/kernel/include/processes/resource.h index 3dadf73e..1576b7ad 100644 --- a/kernel/include/processes/resource.h +++ b/kernel/include/processes/resource.h @@ -9,6 +9,7 @@ #include #include #include +#include namespace MaxOS { namespace processes { @@ -27,8 +28,8 @@ namespace MaxOS { public: - Resource(const string& name, ResourceType type); - ~Resource(); + Resource(const string& name, size_t flags, ResourceType type); + virtual ~Resource(); string name(); ResourceType type(); @@ -36,11 +37,11 @@ namespace MaxOS { virtual void open(); virtual void close(); - virtual size_t read(void* buffer, size_t size); - virtual size_t write(const void* buffer, size_t size); + virtual size_t read(void* buffer, size_t size, size_t flags); + virtual size_t write(const void* buffer, size_t size, size_t flags); }; - class ResourceRegistry{ + class BaseResourceRegistry{ private: common::Map m_resources; @@ -49,33 +50,54 @@ namespace MaxOS { ResourceType m_type; public: - explicit ResourceRegistry(ResourceType type); - ~ResourceRegistry(); + explicit BaseResourceRegistry(ResourceType type); + ~BaseResourceRegistry(); ResourceType type(); - Resource* get_resource(const string& name); - void close_resource(Resource* resource); + virtual Resource* get_resource(const string& name); + virtual bool register_resource(Resource* resource); - void register_resource(Resource* resource, const string& name); + virtual void close_resource(Resource* resource, size_t flags); + virtual Resource* create_resource(const string& name, size_t flags); }; + template class ResourceRegistry : public BaseResourceRegistry{ + + public: + explicit ResourceRegistry(ResourceType type); + ~ResourceRegistry() = default; + + Resource* create_resource(const string& name, size_t flags) final { + + auto resource = new Type(name, flags, type()); + + // Creation failed + if(!register_resource(resource)){ + delete resource; + return nullptr; + } + + return resource; + } + }; + + template ResourceRegistry::ResourceRegistry(ResourceType type):BaseResourceRegistry(type) {} + class GlobalResourceRegistry{ private: - common::Map m_registries; + common::Map m_registries; inline static GlobalResourceRegistry* s_current; public: GlobalResourceRegistry(); ~GlobalResourceRegistry(); - static ResourceRegistry* get_registry(ResourceType type); - - static void add_registry(ResourceType type, ResourceRegistry* registry); - static void remove_registry(ResourceRegistry* registry); - + static BaseResourceRegistry* get_registry(ResourceType type); + static void add_registry(ResourceType type, BaseResourceRegistry* registry); + static void remove_registry(BaseResourceRegistry* registry); }; class ResourceManager{ @@ -89,8 +111,10 @@ namespace MaxOS { ResourceManager(); ~ResourceManager(); - uint64_t open_resource(ResourceType type, const string& name); - void close_resource(uint64_t handle); + common::Map resources(); + + uint64_t open_resource(ResourceType type, const string& name, size_t flags); + void close_resource(uint64_t handle, size_t flags); Resource* get_resource(uint64_t handle); Resource* get_resource(const string& name); diff --git a/kernel/include/processes/scheduler.h b/kernel/include/processes/scheduler.h index a8aaf5aa..2ce54688 100644 --- a/kernel/include/processes/scheduler.h +++ b/kernel/include/processes/scheduler.h @@ -36,13 +36,13 @@ namespace MaxOS{ inline static Scheduler* s_instance = nullptr; static const uint64_t s_ticks_per_event = { 3 }; - InterProcessCommunicationManager* m_ipc; + ResourceRegistry m_shared_memory_registry; + ResourceRegistry m_shared_messages_registry; public: Scheduler(system::Multiboot& multiboot); ~Scheduler(); - system::cpu_status_t* handle_interrupt(system::cpu_status_t* status) final; system::cpu_status_t* schedule(system::cpu_status_t* status); @@ -59,7 +59,6 @@ namespace MaxOS{ static Process* get_process(uint64_t pid); static Thread* current_thread(); static Thread* get_thread(uint64_t tid); - static InterProcessCommunicationManager* scheduler_ipc(); [[nodiscard]] uint64_t ticks() const; diff --git a/kernel/include/system/syscalls.h b/kernel/include/system/syscalls.h index a2415f62..372bd22a 100644 --- a/kernel/include/system/syscalls.h +++ b/kernel/include/system/syscalls.h @@ -22,15 +22,15 @@ namespace MaxOS{ /// DO NOT REARRANGE ONLY APPEND TO enum class SyscallType{ - CLOSE_PROCESS, + CLOSE_PROCESS, // TODO: merge into THREAD_CLOSE KLOG, - CREATE_SHARED_MEMORY, - OPEN_SHARED_MEMORY, ALLOCATE_MEMORY, FREE_MEMORY, - CREATE_IPC_ENDPOINT, - SEND_IPC_MESSAGE, - REMOVE_IPC_ENDPOINT, + RESOURCE_CREATE, + RESOURCE_OPEN, + RESOURCE_CLOSE, + RESOURCE_WRITE, + RESOURCE_READ, THREAD_YIELD, THREAD_SLEEP, THREAD_CLOSE, @@ -70,17 +70,16 @@ namespace MaxOS{ void set_syscall_handler(SyscallType syscall, syscall_func_t handler); void remove_syscall_handler(SyscallType syscall); - - // Syscalls + // Syscalls (TODO: Very c style, should be made class based that automatically registers) static syscall_args_t* syscall_close_process(syscall_args_t* args); static syscall_args_t* syscall_klog(syscall_args_t* args); - static syscall_args_t* syscall_create_shared_memory(syscall_args_t* args); - static syscall_args_t* syscall_open_shared_memory(syscall_args_t* args); static syscall_args_t* syscall_allocate_memory(syscall_args_t* args); static syscall_args_t* syscall_free_memory(syscall_args_t* args); - static syscall_args_t* syscall_create_ipc_endpoint(syscall_args_t* args); - static syscall_args_t* syscall_send_ipc_message(syscall_args_t* args); - static syscall_args_t* syscall_remove_ipc_endpoint(syscall_args_t* args); + static syscall_args_t* syscall_resource_create(syscall_args_t* args); + static syscall_args_t* syscall_resource_open(syscall_args_t* args); + static syscall_args_t* syscall_resource_close(syscall_args_t* args); + static syscall_args_t* syscall_resource_write(syscall_args_t* args); + static syscall_args_t* syscall_resource_read(syscall_args_t* args); static syscall_args_t* syscall_thread_yield(syscall_args_t* args); static syscall_args_t* syscall_thread_sleep(syscall_args_t* args); static syscall_args_t* syscall_thread_close(syscall_args_t* args); diff --git a/kernel/src/memory/virtual.cpp b/kernel/src/memory/virtual.cpp index ae9e1afc..f3e2c006 100644 --- a/kernel/src/memory/virtual.cpp +++ b/kernel/src/memory/virtual.cpp @@ -278,9 +278,21 @@ void VirtualMemoryManager::free(void* address) { // If the chunk is shared, don't unmap it incase other processes are using it if (chunk->flags & Shared) { - // Let the IPC handle the shared memory - Scheduler::scheduler_ipc()->free_shared_memory((uintptr_t) address); + // Find the resource + for(const auto& resource : Scheduler::current_process()->resource_manager.resources()){ + // Skip non-shared memory resources + if(resource.second->type() != ResourceType::SHARED_MEMORY) + continue; + + // Skip shared memory that points elsewhere + auto shared = (SharedMemory*)resource.second; + if((void*)shared->physical_address() != address) + continue; + + // Close the resource + Scheduler::current_process()->resource_manager.close_resource(resource.first, 0); + } } // Add the chunk to the free list @@ -398,7 +410,7 @@ uint64_t* VirtualMemoryManager::pml4_root_address_physical() { void* VirtualMemoryManager::load_shared_memory(const string& name) { // Get the shared memory block - SharedMemory* block = Scheduler::scheduler_ipc()->get_shared_memory(name); + auto block = (SharedMemory*)Scheduler::current_process()->resource_manager.get_resource(name); // Load the shared memory if (block != nullptr) diff --git a/kernel/src/processes/ipc.cpp b/kernel/src/processes/ipc.cpp index 689f7b87..77906648 100644 --- a/kernel/src/processes/ipc.cpp +++ b/kernel/src/processes/ipc.cpp @@ -11,215 +11,15 @@ using namespace MaxOS::memory; #include #include //TODO: Circular dependency, need to fix -/** - * @brief Creates a new IPC handler - */ -InterProcessCommunicationManager::InterProcessCommunicationManager() = default; - -InterProcessCommunicationManager::~InterProcessCommunicationManager() { - - // Free all the shared memory - for (auto block: m_shared_memory_blocks) - delete block; -} - /** * @brief Creates a new shared memory block * - * @param size The size of the block - * @param name The name of the block - * @return The shared memory block - */ -SharedMemory* InterProcessCommunicationManager::alloc_shared_memory(size_t size, string name) { - - // Wait other processes to finish creating their blocks in order to ensure uniqueness - m_lock.lock(); - - // Make sure the name is unique - for (auto endpoint: m_message_endpoints) - if (endpoint->name->equals(name)) { - m_lock.unlock(); - return nullptr; - } - - // Create the shared memory block - auto* block = new SharedMemory(name, size); - m_shared_memory_blocks.push_back(block); - Logger::DEBUG() << "Created shared memory block " << name << " at 0x" << block->physical_address() << "\n"; - - // Return the block - m_lock.unlock(); - return block; -} - -/** - * @brief Gets a shared memory block by name - * - * @param name The name of the block - * @return The shared memory block or nullptr if not found - */ -SharedMemory* InterProcessCommunicationManager::get_shared_memory(const string& name) { - - // Wait for shared memory to be fully created before trying to search for it - m_lock.lock(); - - // Find the block - for (auto block: m_shared_memory_blocks) { - - // Block has wrong name - if (!block->name->equals(name)) - continue; - - // Block is now in use - block->use_count++; - m_lock.unlock(); - return block; - } - - // Not found - m_lock.unlock(); - return nullptr; -} - -/** - * @brief Deletes a shared memory block by physical address - * - * @param physical_address The physical address of the block - */ -void InterProcessCommunicationManager::free_shared_memory(uintptr_t physical_address) { - - // Find the block - for (auto block: m_shared_memory_blocks) - if (block->physical_address() == physical_address) { - free_shared_memory(block); - return; - } -} - -/** - * @brief Deletes a shared memory block by name - * * @param name The name of the block */ -void InterProcessCommunicationManager::free_shared_memory(const string& name) { - - // Find the block - for (auto block: m_shared_memory_blocks) { - - // Check if the block is the one we are looking for - if (!block->name->equals(name)) - continue; - - free_shared_memory(block); - - } -} - -/** - * @brief Deletes a shared memory block - * - * @param block The block to delete (will only free memory if no one is using it) - */ -void InterProcessCommunicationManager::free_shared_memory(SharedMemory* block) { - - // Wait for the lock - m_lock.lock(); - - // One less process is using it - block->use_count--; - - // If the block is in use let those processes handle it - if (block->use_count > 0) { - m_lock.unlock(); - return; - } - - Logger::DEBUG() << "Deleting shared memory block " << block->name->c_str() << " at 0x" << block->physical_address() << "\n"; - - // Free the block - delete block; - m_lock.unlock(); -} - -/** - * @brief Creates a endpoint for message passing - * - * @param name The name of the endpoint - * @return The endpoint - */ -SharedMessageEndpoint* InterProcessCommunicationManager::create_message_endpoint(const string& name) { - - // Wait for the lock - m_lock.lock(); - - // Make sure the name is unique - if (get_message_endpoint(name) != nullptr) { - m_lock.unlock(); - return nullptr; - } - - // Create the endpoint - auto* endpoint = new SharedMessageEndpoint(name); - m_message_endpoints.push_back(endpoint); - - m_lock.unlock(); - return endpoint; -} - -/** - * @brief Gets a message endpoint by name - * - * @param name The name of the endpoint - * @return The endpoint or nullptr if not found - */ -SharedMessageEndpoint* InterProcessCommunicationManager::get_message_endpoint(const string& name) { - - // Try to find the endpoint - for (auto endpoint: m_message_endpoints) - if (endpoint->name->equals(name)) - return endpoint; - - // Not found - return nullptr; -} - -/** - * @brief Deletes a message endpoint by name - all messages will be lost - * - * @param name The name of the endpoint - */ -void InterProcessCommunicationManager::free_message_endpoint(const string& name) { - - free_message_endpoint(get_message_endpoint(name)); -} - -/** - * @brief Deletes a message endpoint - all messages will be lost - * - * @param endpoint - */ -void InterProcessCommunicationManager::free_message_endpoint(SharedMessageEndpoint* endpoint) { - - // Make sure the endpoint exists - if (endpoint == nullptr) - return; - - // Make sure the endpoint is owned by the current process - if (endpoint->owned_by_current_process()) - return; - - // Delete the endpoint - delete endpoint; -} - -/** - * @brief Creates a new shared memory block - * - * @param name The name of the block - */ -SharedMemory::SharedMemory(const string& name, size_t size) -: m_size(size), - name(new string(name)) //TODO: using a new here? +SharedMemory::SharedMemory(const string& name, size_t size, ResourceType type) +: Resource(name, size, type), + m_size(size), + name(name) { m_physical_address = (uintptr_t) PhysicalMemoryManager::s_current_manager->allocate_area(0, size); @@ -231,6 +31,16 @@ SharedMemory::~SharedMemory() { PhysicalMemoryManager::s_current_manager->free_area(m_physical_address, m_size); } +size_t SharedMemory::read(void* buffer, size_t size, size_t flags) { + + // Reading gets the address + if(size < sizeof(m_physical_address)) + return 0; + + memcpy(buffer, &m_physical_address, sizeof(m_physical_address)); + return sizeof(m_physical_address); +} + /** * @brief Gets the physical address of the shared memory block * @@ -251,96 +61,69 @@ size_t SharedMemory::size() const { return m_size; } -SharedMessageEndpoint::SharedMessageEndpoint(const string& name) -: name(new string(name)) +SharedMessageEndpoint::SharedMessageEndpoint(const string& name, size_t size, ResourceType type) +: Resource(name, size, type) { - // Make the queue in the user's memory space - m_queue = (ipc_message_queue_t*) MemoryManager::malloc(sizeof(ipc_message_queue_t)); - - // Store that this process owns it - m_owner_pid = Scheduler::current_process()->pid(); } SharedMessageEndpoint::~SharedMessageEndpoint() { - m_message_lock.lock(); - - // Delete the messages - ipc_message_t* message = m_queue->messages; - while (message != nullptr) { - auto* next = (ipc_message_t*) message->next_message; - MemoryManager::free(message->message_buffer); - message = next; - } - - // Free the memory - MemoryManager::free(m_queue); - delete name; - - m_message_lock.unlock(); + // Free the messages + for(auto& message : m_queue) + delete message; } /** - * @brief Gets the message queue for the endpoint + * @brief Queues a message to be processed by the endpoint (buffer can be freed as it is copied in to the receiving process's memory) * - * @return The message queue + * @param message The message to queue + * @param size The size of the message */ -ipc_message_queue_t* SharedMessageEndpoint::queue() const { +void SharedMessageEndpoint::queue_message(void const* message, size_t size) { + + m_message_lock.lock(); + + // Create the message + auto* new_message = new buffer_t(size); + new_message->copy_from(message, size); + m_queue.push_back(new_message); - return m_queue; + m_message_lock.unlock(); } +// TODO: Add a min() max() to common somewhere + /** - * @brief Checks if the current process can delete the endpoint + * @brief Reads the first message from the endpoint or will yield until a message has been written * - * @return True if the current process can delete the endpoint + * @param buffer Where to write the message to + * @param size Max size of the message to be read + * @return The amount of bytes read */ -bool SharedMessageEndpoint::owned_by_current_process() const { +size_t SharedMessageEndpoint::read(void* buffer, size_t size, size_t flags) { + + // Wait for a message + while(m_queue.empty()) + Scheduler::system_scheduler()->yield(); + + // Read the message into the buffer + buffer_t* message = m_queue.pop_front(); + size_t readable = size > message->capacity() ? message->capacity() : size; + memcpy(buffer, message -> raw(), readable); - return m_owner_pid == Scheduler::current_process()->pid(); + return readable; } /** - * @brief Queues a message to be processed by the endpoint (buffer can be freed as it is copied in to the receiving process's memory) + * @brief Handles writing to the message endpoint resource by writing the buffer as a message * - * @param message The message to queue + * @param buffer The message to write to the endpoint * @param size The size of the message + * @return The amount of bytes written */ -void SharedMessageEndpoint::queue_message(void* message, size_t size) { - - // Wait for the lock - m_message_lock.lock(); - - // Copy the buffer into the kernel so that the endpoint (this code) can access it when the memory spaces are switched - buffer_t kernel_copy(size); - kernel_copy.copy_from(message, size); - - //Switch to endpoint's memory space - MemoryManager::switch_active_memory_manager(Scheduler::get_process(m_owner_pid)->memory_manager); - - // Create the message & copy it into the endpoint's memory space - auto* new_message = (ipc_message_t*) MemoryManager::malloc(sizeof(ipc_message_t)); - void* new_buffer = MemoryManager::malloc(size); - new_message->message_buffer = memcpy(new_buffer, kernel_copy.raw(), size); - new_message->message_size = size; - new_message->next_message = 0; +size_t SharedMessageEndpoint::write(void const* buffer, size_t size, size_t flags) { - // Add the message to the end of the queue - ipc_message_t* current = m_queue->messages; - while (current != nullptr) { - if (current->next_message == 0) { - current->next_message = (uintptr_t) new_message; - break; - } - current = (ipc_message_t*) current->next_message; - } - - // If it was the first message - if (current == nullptr) - m_queue->messages = new_message; - - // Clean up - MemoryManager::switch_active_memory_manager(Scheduler::current_process()->memory_manager); - m_message_lock.unlock(); + queue_message(buffer, size); + return size; } \ No newline at end of file diff --git a/kernel/src/processes/resource.cpp b/kernel/src/processes/resource.cpp index 91c977b1..4660ca36 100644 --- a/kernel/src/processes/resource.cpp +++ b/kernel/src/processes/resource.cpp @@ -6,7 +6,7 @@ using namespace MaxOS; using namespace MaxOS::processes; -Resource::Resource(const string& name, ResourceType type) +Resource::Resource(const string& name, size_t flags, ResourceType type) : m_name(name), m_type(type) { @@ -35,9 +35,10 @@ void Resource::close() { * * @param buffer The buffer to read into * @param size How many bytes to read + * @param flags Optional flags to pass (unused by default but resource type specific) * @return How many bytes were successfully read */ -size_t Resource::read(void* buffer, size_t size) { +size_t Resource::read(void* buffer, size_t size, size_t flags) { return 0; } @@ -46,9 +47,10 @@ size_t Resource::read(void* buffer, size_t size) { * * @param buffer The buffer to read from * @param size How many bytes to write + * @param flags Optional flags to pass (unused by default but resource type specific) * @return How many bytes were successfully written */ -size_t Resource::write(void const* buffer, size_t size) { +size_t Resource::write(void const* buffer, size_t size, size_t flags) { return 0; } @@ -65,22 +67,20 @@ string Resource::name() { /** * @brief Gets the type of this resource * - * @return Tje type + * @return The type */ ResourceType Resource::type() { return m_type; } -ResourceRegistry::ResourceRegistry(ResourceType type) +BaseResourceRegistry::BaseResourceRegistry(ResourceType type) : m_type(type) { - GlobalResourceRegistry::add_registry(type, this); - } -ResourceRegistry::~ResourceRegistry(){ +BaseResourceRegistry::~BaseResourceRegistry(){ GlobalResourceRegistry::remove_registry(this); } @@ -90,7 +90,7 @@ ResourceRegistry::~ResourceRegistry(){ * * @return The type */ -ResourceType ResourceRegistry::type() { +ResourceType BaseResourceRegistry::type() { return m_type; } @@ -101,7 +101,7 @@ ResourceType ResourceRegistry::type() { * @param name The name of the resource to find * @return The resource or nullptr if the resource was not found */ -Resource* ResourceRegistry::get_resource(string const& name) { +Resource* BaseResourceRegistry::get_resource(string const& name) { // Resource isn't stored in this registry if(m_resources.find(name) == m_resources.end()) @@ -113,12 +113,32 @@ Resource* ResourceRegistry::get_resource(string const& name) { } +/** + * @brief Registers a resource in the registry + * + * @param resource The resource to store + * @param name The name of the resource (must not already be in use) + * @return True if the register was successful, false if not + */ +bool BaseResourceRegistry::register_resource(Resource* resource) { + + // Resource name is already stored in this registry + if(m_resources.find(resource->name()) != m_resources.end()) + return false; + + m_resources.insert(resource->name(), resource); + m_resource_uses.insert(resource->name(), 0); + return true; +} + + /** * @brief Close the resource provided, if it exists in this registry * * @param resource The resource to close + * @param flags Optional flags to pass (unused by default but registry type specific) */ -void ResourceRegistry::close_resource(Resource* resource) { +void BaseResourceRegistry::close_resource(Resource* resource, size_t flags) { // Resource isn't stored in this registry if(m_resources.find(resource->name()) == m_resources.end()) @@ -138,19 +158,14 @@ void ResourceRegistry::close_resource(Resource* resource) { } /** - * @brief Registers a resource in the registry + * @brief Try to create a new resource with the specified name, will fail if the name is in use * - * @param resource The resource to store - * @param name The name of the resource (must not already be in use) + * @param name The name of the resource to create + * @param flags Optional flags to pass (unused by default but registry type specific) + * @return The resource created or nullptr if failed to create the resource */ -void ResourceRegistry::register_resource(Resource* resource, const string& name) { - - // Resource name is already stored in this registry - if(m_resources.find(resource->name()) != m_resources.end()) - return; - - m_resources.insert(name, resource); - m_resource_uses.insert(name, 0); +Resource* BaseResourceRegistry::create_resource(string const& name, size_t flags) { + return nullptr; } GlobalResourceRegistry::GlobalResourceRegistry() { @@ -170,7 +185,7 @@ GlobalResourceRegistry::~GlobalResourceRegistry() { * @param type The type of registry to get * @return The registry or nullptr if not found */ -ResourceRegistry* GlobalResourceRegistry::get_registry(ResourceType type) { +BaseResourceRegistry* GlobalResourceRegistry::get_registry(ResourceType type) { auto registry = s_current->m_registries.find(type); if(registry == s_current->m_registries.end()) @@ -185,7 +200,7 @@ ResourceRegistry* GlobalResourceRegistry::get_registry(ResourceType type) { * @param type The type of registry being added * @param registry The registry to add */ -void GlobalResourceRegistry::add_registry(ResourceType type, ResourceRegistry* registry) { +void GlobalResourceRegistry::add_registry(ResourceType type, BaseResourceRegistry* registry) { // Does it already exist? if(s_current->m_registries.find(type) != s_current->m_registries.end()) @@ -200,10 +215,10 @@ void GlobalResourceRegistry::add_registry(ResourceType type, ResourceRegistry* r * @param type The type of registry being added * @param registry The registry to add */ -void GlobalResourceRegistry::remove_registry(ResourceRegistry* registry) { +void GlobalResourceRegistry::remove_registry(BaseResourceRegistry* registry) { // Does it already exist? - if(s_current->m_registries.find(registry->type()) != s_current->m_registries.end()) + if(s_current->m_registries.find(registry->type()) == s_current->m_registries.end()) return; s_current->m_registries.erase(registry->type()); @@ -214,12 +229,27 @@ ResourceManager::ResourceManager() = default; ResourceManager::~ResourceManager(){ - // Close all resources - for(const auto& resource : m_resources) - close_resource(resource.first); + // Collect all resources (as closing will break iteration) + common::Vector handles; + for (const auto& kv : m_resources) + handles.push_back(kv.first); + + // Close the resources + for (auto h : handles) + close_resource(h, 0); }; +/** + * @brief Get the resources currently open + * + * @return The resources + */ +common::Map ResourceManager::resources() { + + return m_resources; +} + /** * @brief Registers a resource with the resource manager and then opens it * @@ -227,7 +257,7 @@ ResourceManager::~ResourceManager(){ * @param name The name of the resource * @return The handle id of the resource or 0 if failed */ -uint64_t ResourceManager::open_resource(ResourceType type, string const& name) { +uint64_t ResourceManager::open_resource(ResourceType type, string const& name, size_t flags) { // Get the resource auto resource = GlobalResourceRegistry::get_registry(type) -> get_resource(name); @@ -244,17 +274,22 @@ uint64_t ResourceManager::open_resource(ResourceType type, string const& name) { /** * @brief Un registers the resource and then closes it - * @param handle + * + * @param handle The handle number of the resource + * @param flags Flags to pass to the closing */ -void ResourceManager::close_resource(uint64_t handle) { +void ResourceManager::close_resource(uint64_t handle, size_t flags) { - Resource* resource = m_resources[handle]; + auto it = m_resources.find(handle); + if(it == m_resources.end()) + return; // Remove it + Resource* resource = it->second; m_resources.erase(handle); // Close it - GlobalResourceRegistry::get_registry(resource->type()) -> close_resource(resource); + GlobalResourceRegistry::get_registry(resource->type()) -> close_resource(resource, flags); } /** @@ -284,4 +319,6 @@ Resource* ResourceManager::get_resource(string const& name) { return resource.second; return nullptr; -} \ No newline at end of file +} + + diff --git a/kernel/src/processes/scheduler.cpp b/kernel/src/processes/scheduler.cpp index d432545b..f19ffa24 100644 --- a/kernel/src/processes/scheduler.cpp +++ b/kernel/src/processes/scheduler.cpp @@ -17,13 +17,14 @@ Scheduler::Scheduler(Multiboot& multiboot) m_active(false), m_ticks(0), m_next_pid(-1), - m_next_tid(-1) + m_next_tid(-1), + m_shared_memory_registry(ResourceType::SHARED_MEMORY), + m_shared_messages_registry(ResourceType::MESSAGE_ENDPOINT) { // Set up the basic scheduler Logger::INFO() << "Setting up Scheduler \n"; s_instance = this; - m_ipc = new InterProcessCommunicationManager(); // Create the idle process auto* idle = new Process("kernelMain Idle", nullptr, nullptr, 0, true); @@ -40,9 +41,6 @@ Scheduler::~Scheduler() { // Deactivate this scheduler s_instance = nullptr; m_active = false; - - // Delete the IPC handler - delete m_ipc; } /** @@ -394,16 +392,6 @@ void Scheduler::load_multiboot_elfs(Multiboot* multiboot) { } } -/** - * @brief Gets the IPC handler - * - * @return The IPC handler or nullptr if not found - */ -InterProcessCommunicationManager* Scheduler::scheduler_ipc() { - - return s_instance->m_ipc; -} - /** * @brief Gets a thread by its TID * diff --git a/kernel/src/system/syscalls.cpp b/kernel/src/system/syscalls.cpp index 049cada1..f9fd7320 100644 --- a/kernel/src/system/syscalls.cpp +++ b/kernel/src/system/syscalls.cpp @@ -24,15 +24,16 @@ SyscallManager::SyscallManager() // Register the handlers set_syscall_handler(SyscallType::CLOSE_PROCESS, syscall_close_process); set_syscall_handler(SyscallType::KLOG, syscall_klog); - set_syscall_handler(SyscallType::CREATE_SHARED_MEMORY, syscall_create_shared_memory); - set_syscall_handler(SyscallType::OPEN_SHARED_MEMORY, syscall_open_shared_memory); set_syscall_handler(SyscallType::ALLOCATE_MEMORY, syscall_allocate_memory); set_syscall_handler(SyscallType::FREE_MEMORY, syscall_free_memory); - set_syscall_handler(SyscallType::CREATE_IPC_ENDPOINT, syscall_create_ipc_endpoint); - set_syscall_handler(SyscallType::SEND_IPC_MESSAGE, syscall_send_ipc_message); - set_syscall_handler(SyscallType::REMOVE_IPC_ENDPOINT, syscall_remove_ipc_endpoint); + set_syscall_handler(SyscallType::RESOURCE_CREATE, syscall_resource_create); + set_syscall_handler(SyscallType::RESOURCE_OPEN, syscall_resource_open); + set_syscall_handler(SyscallType::RESOURCE_CLOSE, syscall_resource_close); + set_syscall_handler(SyscallType::RESOURCE_WRITE, syscall_resource_write); + set_syscall_handler(SyscallType::RESOURCE_READ, syscall_resource_read); set_syscall_handler(SyscallType::THREAD_YIELD, syscall_thread_yield); set_syscall_handler(SyscallType::THREAD_SLEEP, syscall_thread_sleep); + set_syscall_handler(SyscallType::THREAD_CLOSE, syscall_thread_close); } @@ -140,59 +141,6 @@ syscall_args_t* SyscallManager::syscall_klog(syscall_args_t* args) { return args; } -/** - * @brief System call to create a shared memory block (To close, free the memory, it is automatically handled when the process exits) - * - * @param args Arg0 = size, Arg1 = name - * @return The virtual address of the shared memory block or null if failed - */ -syscall_args_t* SyscallManager::syscall_create_shared_memory(syscall_args_t* args) { - - // Extract the arguments - size_t size = args->arg0; - char* name = (char*) args->arg1; - - // Ensure they are valid - if (size == 0 || name == nullptr) - return nullptr; - - // Create the memory block - SharedMemory* new_block = Scheduler::scheduler_ipc()->alloc_shared_memory(size, name); - - // Load the block - void* virtual_address = MemoryManager::s_current_memory_manager->vmm()->load_shared_memory(new_block->physical_address(), size); - - // Return to the user - args->return_value = (uint64_t) virtual_address; - return args; -} - -/** - * @brief System call to open a shared memory block (To close, free the memory, it is automatically handled when the process exits) - * - * @param args Arg0 = name - * @return The virtual address of the shared memory block or null if failed - */ -syscall_args_t* SyscallManager::syscall_open_shared_memory(syscall_args_t* args) { - - // Extract the arguments - uint64_t name = args->arg0; - - // Ensure they are valid - if (name == 0) - return nullptr; - - // Get the block (don't care if null as that is caught in the load_shared_memory function) - SharedMemory* block = Scheduler::scheduler_ipc()->get_shared_memory((char*) name); - - // Load the block - void* virtual_address = MemoryManager::s_current_memory_manager->vmm()->load_shared_memory(block->physical_address(), block->size()); - - // Return to the user - args->return_value = (uint64_t) virtual_address; - return args; -} - /** * @brief System call to free a block of memory * @@ -201,14 +149,12 @@ syscall_args_t* SyscallManager::syscall_open_shared_memory(syscall_args_t* args) */ syscall_args_t* SyscallManager::syscall_allocate_memory(syscall_args_t* args) { - // Get the size - size_t size = args->arg0; - // Malloc the memory + size_t size = args->arg0; void* address = MemoryManager::malloc(size); // Return the address - args->return_value = (uint64_t) address; + args->return_value = (uint64_t)address; return args; } @@ -220,76 +166,120 @@ syscall_args_t* SyscallManager::syscall_allocate_memory(syscall_args_t* args) { */ syscall_args_t* SyscallManager::syscall_free_memory(syscall_args_t* args) { - // Get the address - void* address = (void*) args->arg0; - // Free the memory + void* address = (void*) args->arg0; MemoryManager::free(address); - // Done return args; } /** - * @brief System call to create an IPC endpoint + * @brief System call to create a resource * - * @param args Arg0 = name - * @return The IPC endpoint buffer linked list address + * @param args Arg0 = ResourceType Arg1 = Name Arg2 = Flags + * @return 1 if resource was created sucessfully, 0 otherwise */ -system::syscall_args_t* SyscallManager::syscall_create_ipc_endpoint(system::syscall_args_t* args) { +syscall_args_t* SyscallManager::syscall_resource_create(syscall_args_t* args) { - // Get the name - char* name = (char*) args->arg0; - if (name == nullptr) - return nullptr; + // Parse params + auto type = (ResourceType)args->arg0; + auto name = (char*)args->arg1; + auto flags = (size_t)args->arg2; - // Create the endpoint - SharedMessageEndpoint* endpoint = Scheduler::scheduler_ipc()->create_message_endpoint(name); + // Try to create the resource + auto resource = GlobalResourceRegistry::get_registry(type)->create_resource(name, flags); - // Return the endpoint - args->return_value = (uint64_t) endpoint->queue(); + // Handle response + args->return_value = resource ? 1 : 0; return args; } /** - * @brief System call to send an IPC message + * @brief System call to open a resource * - * @param args Arg0 = endpoint name, Arg1 = message, Arg2 = size - * @return Nothing + * @param args Arg0 = ResourceType Arg1 = Name Arg2 = Flags + * @return The handle of the resource or 0 if failed */ -system::syscall_args_t* SyscallManager::syscall_send_ipc_message(system::syscall_args_t* args) { - - // Get the args - char* endpoint = (char*) args->arg0; - void* message = (void*) args->arg1; - size_t size = args->arg2; +syscall_args_t* SyscallManager::syscall_resource_open(syscall_args_t* args) { - // Validate the args - if (endpoint == nullptr || message == nullptr || size == 0) - return nullptr; + // Parse params + auto type = (ResourceType)args->arg0; + auto name = (char*)args->arg1; + auto flags = (size_t)args->arg2; - // Send the message - Scheduler::scheduler_ipc()->get_message_endpoint(endpoint)->queue_message(message, size); - - // All done + // Open the resource + args->return_value = Scheduler::current_process()->resource_manager.open_resource(type, name, flags); return args; } /** - * @brief System call to remove an IPC endpoint + * @brief System call to close a resource * - * @param args Arg0 = endpoint name + * @param args Arg0 = Handle Arg1 = Flags * @return Nothing */ -system::syscall_args_t* SyscallManager::syscall_remove_ipc_endpoint(system::syscall_args_t* args) { +syscall_args_t* SyscallManager::syscall_resource_close(syscall_args_t* args) { - // Remove the endpoint - Scheduler::scheduler_ipc()->free_message_endpoint((char*) args->arg0); + // Parse params + auto handle = (uint64_t )args->arg0; + auto flags = (size_t)args->arg1; - // Done + // Close the resource + Scheduler::current_process()->resource_manager.close_resource(handle, flags); return args; } +/** + * @brief System call to write to a resource + * + * @param args Arg0 = Handle Arg1 = Buffer Arg2 = Size Arg3 = Flags + * @return The number of bytes written or 0 if failed + */ +syscall_args_t* SyscallManager::syscall_resource_write(syscall_args_t* args) { + + // Parse params + auto handle = (uint64_t )args->arg0; + auto buffer = (void*)args->arg1; + auto size = (size_t)args->arg2; + auto flags = (size_t)args->arg3; + + // Open the resource + auto resource = Scheduler::current_process()->resource_manager.get_resource(handle); + if(!resource){ + args->return_value = 0; + return args; + } + + // Write to the resource + args->return_value = resource->write(buffer,size,flags); + return args; +} + +/** + * @brief System call to read from a resource + * + * @param args Arg0 = Handle Arg1 = Buffer Arg2 = Size Arg3 = Flags + * @return The number of bytes read or 0 if failed + */ +syscall_args_t* SyscallManager::syscall_resource_read(syscall_args_t* args) { + + // Parse params + auto handle = (uint64_t )args->arg0; + auto buffer = (void*)args->arg1; + auto size = (size_t)args->arg2; + auto flags = (size_t)args->arg3; + + // Open the resource + auto resource = Scheduler::current_process()->resource_manager.get_resource(handle); + if(!resource){ + args->return_value = 0; + return args; + } + + // Write to the resource + args->return_value = resource->read(buffer,size,flags); + return args; +} /** * @brief System call to yield the current process @@ -336,7 +326,6 @@ system::syscall_args_t* SyscallManager::syscall_thread_sleep(system::syscall_arg */ system::syscall_args_t* SyscallManager::syscall_thread_close(system::syscall_args_t* args) { - // Get the args uint64_t tid = args->arg0; int exit_code = (int) args->arg1; From 01992ba51db83ca130d0aa0bf5813964c16c6f5e Mon Sep 17 00:00:00 2001 From: Max Tyson <98maxt98@gmail.com> Date: Sun, 31 Aug 2025 21:01:34 +1200 Subject: [PATCH 35/38] Userspace Resources & IPC --- CMakeLists.txt | 34 ++++- filesystem/os/{ => lib}/.gitkeep | 0 kernel/CMakeLists.txt | 15 +- kernel/include/processes/ipc.h | 20 +-- kernel/include/processes/resource.h | 40 +++-- kernel/include/processes/scheduler.h | 1 + kernel/include/system/syscalls.h | 21 +-- kernel/src/memory/virtual.cpp | 2 +- kernel/src/processes/ipc.cpp | 89 ++++++----- kernel/src/processes/resource.cpp | 32 ++-- kernel/src/processes/scheduler.cpp | 4 +- kernel/src/system/cpu.cpp | 3 +- kernel/src/system/syscalls.cpp | 10 +- libraries/CMakeLists.txt | 3 + libraries/ipc/CMakeLists.txt | 22 +++ libraries/ipc/include/ipc/messages.h | 23 +++ libraries/ipc/include/ipc/sharedmemory.h | 18 +++ libraries/ipc/src/messages.cpp | 73 +++++++++ libraries/ipc/src/sharedmemory.cpp | 40 +++++ libraries/system/CMakeLists.txt | 20 +++ libraries/system/include/syscalls.h | 59 +++++++ libraries/system/src/syscalls.cpp | 187 +++++++++++++++++++++++ programs/Example/CMakeLists.txt | 2 +- programs/Example/src/main.cpp | 129 ++++++---------- programs/Example2/CMakeLists.txt | 2 +- programs/Example2/src/main.cpp | 59 +------ 26 files changed, 651 insertions(+), 257 deletions(-) rename filesystem/os/{ => lib}/.gitkeep (100%) create mode 100644 libraries/CMakeLists.txt create mode 100644 libraries/ipc/CMakeLists.txt create mode 100644 libraries/ipc/include/ipc/messages.h create mode 100644 libraries/ipc/include/ipc/sharedmemory.h create mode 100644 libraries/ipc/src/messages.cpp create mode 100644 libraries/ipc/src/sharedmemory.cpp create mode 100644 libraries/system/CMakeLists.txt create mode 100644 libraries/system/include/syscalls.h create mode 100644 libraries/system/src/syscalls.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 6cd1bb4f..b91d86d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,13 +2,13 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.0.0) # Set the project name and the languages -project(MaxOS C CXX ASM) +PROJECT(MaxOS C CXX ASM) # Logs the compiler commands -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +SET(CMAKE_EXPORT_COMPILE_COMMANDS ON) # don't enable runtime type information -set(CMAKE_SKIP_RPATH TRUE) +SET(CMAKE_SKIP_RPATH TRUE) # Set the standard to C++20 SET(CMAKE_CXX_STANDARD 20) @@ -22,19 +22,37 @@ LINK_DIRECTORIES(${TOOLCHAIN_ROOT_DIR}/${TOOLCHAIN_PLATFORM}/lib/) # DEBUG / PROD IF(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the type of build." FORCE) + SET(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the type of build." FORCE) ENDIF() -## Set flags based on build type +# Set flags based on build type IF(CMAKE_BUILD_TYPE STREQUAL "Debug") - add_definitions(-DTARGET_DEBUG) + ADD_DEFINITIONS(-DTARGET_DEBUG) ENDIF() +# Function to make a library easier +function(MAKE_LIBRARY LIBNAME) + + # TODO: ADD_DEPENDENCIES(${LIBNAME} libc) (& for static) + + # Install dynamic library + ADD_LIBRARY(${LIBNAME} SHARED ${LIBRARY_SRCS}) + TARGET_INCLUDE_DIRECTORIES(${LIBNAME} PUBLIC include) + INSTALL(TARGETS ${LIBNAME} LIBRARY DESTINATION ${CMAKE_SOURCE_DIR}/filesystem/os/lib) + + # Install static library + ADD_LIBRARY(${LIBNAME}_static STATIC ${LIBRARY_SRCS}) + TARGET_INCLUDE_DIRECTORIES(${LIBNAME}_static PUBLIC include) + INSTALL(TARGETS ${LIBNAME}_static DESTINATION ${CMAKE_SOURCE_DIR}/filesystem/os/lib) + + # TODO: Install headers onto fs + +endfunction() + # Look to build in these directories ADD_SUBDIRECTORY(kernel) -#ADD_SUBDIRECTORY(libraries) +ADD_SUBDIRECTORY(libraries) ADD_SUBDIRECTORY(programs) -#ADD_SUBDIRECTORY(ports) ADD_CUSTOM_TARGET(image # Everything needs to be installed (compiled) before we can create the disk image diff --git a/filesystem/os/.gitkeep b/filesystem/os/lib/.gitkeep similarity index 100% rename from filesystem/os/.gitkeep rename to filesystem/os/lib/.gitkeep diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index 28fa5ed0..8f232e5a 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -14,10 +14,10 @@ SET_SOURCE_FILES_PROPERTIES(${ASM_SRCS} PROPERTIES LANGUAGE ASM) FILE(GLOB_RECURSE KERNEL_SRCS src/*.cpp src/*.s) # Update the version before building -set(VERSION_HEADER "${CMAKE_SOURCE_DIR}/kernel/include/common/version.h") -set(VERSION_HEADER_TMP "${CMAKE_SOURCE_DIR}/kernel/include/common/version.h.tmp") -set(POST_PROCESS_SCRIPT "${CMAKE_SOURCE_DIR}/toolchain/pre_process/run.sh") -add_custom_command( +SET(VERSION_HEADER "${CMAKE_SOURCE_DIR}/kernel/include/common/version.h") +SET(VERSION_HEADER_TMP "${CMAKE_SOURCE_DIR}/kernel/include/common/version.h.tmp") +SET(POST_PROCESS_SCRIPT "${CMAKE_SOURCE_DIR}/toolchain/pre_process/run.sh") +ADD_CUSTOM_COMMAND( OUTPUT "${VERSION_HEADER}" COMMAND "${POST_PROCESS_SCRIPT}" COMMAND ${CMAKE_COMMAND} -E copy_if_different "${VERSION_HEADER_TMP}" "${VERSION_HEADER}" @@ -26,18 +26,17 @@ add_custom_command( WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" COMMENT "Regenerating version.h because kernel sources changed" ) -add_custom_target(VersionScript DEPENDS "${VERSION_HEADER}" -) +ADD_CUSTOM_TARGET(VersionScript DEPENDS "${VERSION_HEADER}") # Create the kernel ADD_EXECUTABLE(MaxOSk64 ${KERNEL_SRCS} ${VERSION_HEADER}) -add_dependencies(MaxOSk64 VersionScript) +ADD_DEPENDENCIES(MaxOSk64 VersionScript) TARGET_COMPILE_DEFINITIONS(MaxOSk64 PUBLIC MAXOS_KERNEL) # Linker SET(LINKER_SCRIPT ${CMAKE_SOURCE_DIR}/kernel/src/linker.ld) SET_TARGET_PROPERTIES(MaxOSk64 PROPERTIES LINK_DEPENDS ${LINKER_SCRIPT}) -TARGET_LINK_LIBRARIES(MaxOSk64 gcc) +TARGET_LINK_LIBRARIES(MaxOSk64 PRIVATE gcc system_static) TARGET_LINK_OPTIONS(MaxOSk64 PRIVATE -T ${LINKER_SCRIPT} -nostdlib -n) # Set the include directories diff --git a/kernel/include/processes/ipc.h b/kernel/include/processes/ipc.h index cc788c5e..b4a62dbe 100644 --- a/kernel/include/processes/ipc.h +++ b/kernel/include/processes/ipc.h @@ -18,23 +18,25 @@ namespace MaxOS { namespace processes { - //TODO: LibIPC - class SharedMemory final : public Resource { private: uintptr_t m_physical_address; size_t m_size; - uint64_t m_owner_pid; + + common::Map m_mappings; public: - SharedMemory(const string& name, size_t size, ResourceType type); + SharedMemory(const string& name, size_t size, resource_type_t type); ~SharedMemory() final; string name; uint64_t use_count = 1; - size_t read(void* buffer, size_t size, size_t flags) final; + void open(size_t flags) final; + void close(size_t flags) final; + + int read(void* buffer, size_t size, size_t flags) final; [[nodiscard]] uintptr_t physical_address() const; [[nodiscard]] size_t size() const; @@ -47,13 +49,11 @@ namespace MaxOS { common::Spinlock m_message_lock; public: - SharedMessageEndpoint(const string& name, size_t size, ResourceType type); + SharedMessageEndpoint(const string& name, size_t size, resource_type_t type); ~SharedMessageEndpoint() final; - size_t read(void* buffer, size_t size, size_t flags) final; - size_t write(const void* buffer, size_t size, size_t flags) final; - - void queue_message(void const* message, size_t size); + int read(void* buffer, size_t size, size_t flags) final; + int write(const void* buffer, size_t size, size_t flags) final; }; } diff --git a/kernel/include/processes/resource.h b/kernel/include/processes/resource.h index 1576b7ad..b8ae277d 100644 --- a/kernel/include/processes/resource.h +++ b/kernel/include/processes/resource.h @@ -10,35 +10,33 @@ #include #include #include +#include namespace MaxOS { namespace processes { - enum class ResourceType{ - MESSAGE_ENDPOINT, - SHARED_MEMORY - }; + typedef ::system::ResourceType resource_type_t; + typedef ::system::ResourceErrorBase resource_error_base_t; class Resource { private: - string m_name; - ResourceType m_type; + resource_type_t m_type; public: - Resource(const string& name, size_t flags, ResourceType type); + Resource(const string& name, size_t flags, resource_type_t type); virtual ~Resource(); string name(); - ResourceType type(); + resource_type_t type(); - virtual void open(); - virtual void close(); + virtual void open(size_t flags); + virtual void close(size_t flags); - virtual size_t read(void* buffer, size_t size, size_t flags); - virtual size_t write(const void* buffer, size_t size, size_t flags); + virtual int read(void* buffer, size_t size, size_t flags); + virtual int write(const void* buffer, size_t size, size_t flags); }; class BaseResourceRegistry{ @@ -47,13 +45,13 @@ namespace MaxOS { common::Map m_resources; common::Map m_resource_uses; - ResourceType m_type; + resource_type_t m_type; public: - explicit BaseResourceRegistry(ResourceType type); + explicit BaseResourceRegistry(resource_type_t type); ~BaseResourceRegistry(); - ResourceType type(); + resource_type_t type(); virtual Resource* get_resource(const string& name); virtual bool register_resource(Resource* resource); @@ -65,7 +63,7 @@ namespace MaxOS { template class ResourceRegistry : public BaseResourceRegistry{ public: - explicit ResourceRegistry(ResourceType type); + explicit ResourceRegistry(resource_type_t type); ~ResourceRegistry() = default; Resource* create_resource(const string& name, size_t flags) final { @@ -82,21 +80,21 @@ namespace MaxOS { } }; - template ResourceRegistry::ResourceRegistry(ResourceType type):BaseResourceRegistry(type) {} + template ResourceRegistry::ResourceRegistry(resource_type_t type):BaseResourceRegistry(type) {} class GlobalResourceRegistry{ private: - common::Map m_registries; + common::Map m_registries; inline static GlobalResourceRegistry* s_current; public: GlobalResourceRegistry(); ~GlobalResourceRegistry(); - static BaseResourceRegistry* get_registry(ResourceType type); + static BaseResourceRegistry* get_registry(resource_type_t type); - static void add_registry(ResourceType type, BaseResourceRegistry* registry); + static void add_registry(resource_type_t type, BaseResourceRegistry* registry); static void remove_registry(BaseResourceRegistry* registry); }; @@ -113,7 +111,7 @@ namespace MaxOS { common::Map resources(); - uint64_t open_resource(ResourceType type, const string& name, size_t flags); + uint64_t open_resource(resource_type_t type, const string& name, size_t flags); void close_resource(uint64_t handle, size_t flags); Resource* get_resource(uint64_t handle); diff --git a/kernel/include/processes/scheduler.h b/kernel/include/processes/scheduler.h index 2ce54688..614b2382 100644 --- a/kernel/include/processes/scheduler.h +++ b/kernel/include/processes/scheduler.h @@ -36,6 +36,7 @@ namespace MaxOS{ inline static Scheduler* s_instance = nullptr; static const uint64_t s_ticks_per_event = { 3 }; + GlobalResourceRegistry m_global_resource_registry = {}; ResourceRegistry m_shared_memory_registry; ResourceRegistry m_shared_messages_registry; diff --git a/kernel/include/system/syscalls.h b/kernel/include/system/syscalls.h index 372bd22a..cf9f67ae 100644 --- a/kernel/include/system/syscalls.h +++ b/kernel/include/system/syscalls.h @@ -12,6 +12,7 @@ #include #include #include +#include // TODO: Rename / make clear that this references the system lib namespace MaxOS{ @@ -20,22 +21,6 @@ namespace MaxOS{ // Forward declaration class SyscallManager; - /// DO NOT REARRANGE ONLY APPEND TO - enum class SyscallType{ - CLOSE_PROCESS, // TODO: merge into THREAD_CLOSE - KLOG, - ALLOCATE_MEMORY, - FREE_MEMORY, - RESOURCE_CREATE, - RESOURCE_OPEN, - RESOURCE_CLOSE, - RESOURCE_WRITE, - RESOURCE_READ, - THREAD_YIELD, - THREAD_SLEEP, - THREAD_CLOSE, - }; - typedef struct SyscallArguments{ uint64_t arg0; uint64_t arg1; @@ -67,8 +52,8 @@ namespace MaxOS{ cpu_status_t* handle_interrupt(cpu_status_t* esp) final; - void set_syscall_handler(SyscallType syscall, syscall_func_t handler); - void remove_syscall_handler(SyscallType syscall); + void set_syscall_handler(::system::SyscallType syscall, syscall_func_t handler); + void remove_syscall_handler(::system::SyscallType syscall); // Syscalls (TODO: Very c style, should be made class based that automatically registers) static syscall_args_t* syscall_close_process(syscall_args_t* args); diff --git a/kernel/src/memory/virtual.cpp b/kernel/src/memory/virtual.cpp index f3e2c006..b1b27cd5 100644 --- a/kernel/src/memory/virtual.cpp +++ b/kernel/src/memory/virtual.cpp @@ -282,7 +282,7 @@ void VirtualMemoryManager::free(void* address) { for(const auto& resource : Scheduler::current_process()->resource_manager.resources()){ // Skip non-shared memory resources - if(resource.second->type() != ResourceType::SHARED_MEMORY) + if(resource.second->type() != resource_type_t::SHARED_MEMORY) continue; // Skip shared memory that points elsewhere diff --git a/kernel/src/processes/ipc.cpp b/kernel/src/processes/ipc.cpp index 77906648..b65e9d32 100644 --- a/kernel/src/processes/ipc.cpp +++ b/kernel/src/processes/ipc.cpp @@ -16,29 +16,33 @@ using namespace MaxOS::memory; * * @param name The name of the block */ -SharedMemory::SharedMemory(const string& name, size_t size, ResourceType type) +SharedMemory::SharedMemory(const string& name, size_t size, resource_type_t type) : Resource(name, size, type), m_size(size), name(name) { m_physical_address = (uintptr_t) PhysicalMemoryManager::s_current_manager->allocate_area(0, size); - m_owner_pid = Scheduler::current_process()->pid(); } -SharedMemory::~SharedMemory() { +SharedMemory::~SharedMemory() = default; - PhysicalMemoryManager::s_current_manager->free_area(m_physical_address, m_size); -} - -size_t SharedMemory::read(void* buffer, size_t size, size_t flags) { +/** + * @brief Get the address that the current process has this shared memory mapped to + * + * @param buffer Not used + * @param size Not used + * @param flags Not used + * @return The virtual address in the current process's address space or 0 if not found + */ +int SharedMemory::read(void* buffer, size_t size, size_t flags) { - // Reading gets the address - if(size < sizeof(m_physical_address)) + // Process hasn't opened the resource + auto it = m_mappings.find(Scheduler::current_process()->pid()); + if(it == m_mappings.end()) return 0; - memcpy(buffer, &m_physical_address, sizeof(m_physical_address)); - return sizeof(m_physical_address); + return it->second; } /** @@ -61,35 +65,45 @@ size_t SharedMemory::size() const { return m_size; } -SharedMessageEndpoint::SharedMessageEndpoint(const string& name, size_t size, ResourceType type) -: Resource(name, size, type) -{ +/** + * @brief Map the shared memory into the address space of the owning process + * + * @param flags Not used for this resource + */ +void SharedMemory::open(size_t flags) { -} + // Process has already opened this memory + auto it = m_mappings.find(Scheduler::current_process()->pid()); + if(it != m_mappings.end()) + return; -SharedMessageEndpoint::~SharedMessageEndpoint() { + auto virtual_address = (uintptr_t)Scheduler::current_process()->memory_manager->vmm()->load_shared_memory(m_physical_address, m_size); + m_mappings.insert(Scheduler::current_process()->pid(), virtual_address); - // Free the messages - for(auto& message : m_queue) - delete message; } /** - * @brief Queues a message to be processed by the endpoint (buffer can be freed as it is copied in to the receiving process's memory) + * @brief Frees the page containing the shared memory * - * @param message The message to queue - * @param size The size of the message + * @param flags Not used for this resource */ -void SharedMessageEndpoint::queue_message(void const* message, size_t size) { +void SharedMemory::close(size_t flags) { - m_message_lock.lock(); + PhysicalMemoryManager::s_current_manager->free_area(m_physical_address, m_size); - // Create the message - auto* new_message = new buffer_t(size); - new_message->copy_from(message, size); - m_queue.push_back(new_message); +} - m_message_lock.unlock(); +SharedMessageEndpoint::SharedMessageEndpoint(const string& name, size_t size, resource_type_t type) +: Resource(name, size, type) +{ + +} + +SharedMessageEndpoint::~SharedMessageEndpoint() { + + // Free the messages + for(auto& message : m_queue) + delete message; } // TODO: Add a min() max() to common somewhere @@ -101,11 +115,11 @@ void SharedMessageEndpoint::queue_message(void const* message, size_t size) { * @param size Max size of the message to be read * @return The amount of bytes read */ -size_t SharedMessageEndpoint::read(void* buffer, size_t size, size_t flags) { +int SharedMessageEndpoint::read(void* buffer, size_t size, size_t flags) { // Wait for a message - while(m_queue.empty()) - Scheduler::system_scheduler()->yield(); + if(m_queue.empty()) + return -1 * (int)resource_error_base_t::SHOULD_BLOCK; // Read the message into the buffer buffer_t* message = m_queue.pop_front(); @@ -122,8 +136,15 @@ size_t SharedMessageEndpoint::read(void* buffer, size_t size, size_t flags) { * @param size The size of the message * @return The amount of bytes written */ -size_t SharedMessageEndpoint::write(void const* buffer, size_t size, size_t flags) { +int SharedMessageEndpoint::write(void const* buffer, size_t size, size_t flags) { + + m_message_lock.lock(); - queue_message(buffer, size); + // Create the message + auto* new_message = new buffer_t(size); + new_message->copy_from(buffer, size); + m_queue.push_back(new_message); + + m_message_lock.unlock(); return size; } \ No newline at end of file diff --git a/kernel/src/processes/resource.cpp b/kernel/src/processes/resource.cpp index 4660ca36..4befd963 100644 --- a/kernel/src/processes/resource.cpp +++ b/kernel/src/processes/resource.cpp @@ -6,7 +6,7 @@ using namespace MaxOS; using namespace MaxOS::processes; -Resource::Resource(const string& name, size_t flags, ResourceType type) +Resource::Resource(const string& name, size_t flags, resource_type_t type) : m_name(name), m_type(type) { @@ -17,16 +17,20 @@ Resource::~Resource() = default; /** * @brief Opens the resource + * + * @param flags Optional flags to pass (unused by default but resource type specific) */ -void Resource::open() { +void Resource::open(size_t flags) { } /** * @brief Closes the resource + * + * @param flags Optional flags to pass (unused by default but resource type specific) */ -void Resource::close() { +void Resource::close(size_t flags) { } @@ -36,9 +40,9 @@ void Resource::close() { * @param buffer The buffer to read into * @param size How many bytes to read * @param flags Optional flags to pass (unused by default but resource type specific) - * @return How many bytes were successfully read + * @return How many bytes were successfully read (negative can be used as errors) */ -size_t Resource::read(void* buffer, size_t size, size_t flags) { +int Resource::read(void* buffer, size_t size, size_t flags) { return 0; } @@ -50,7 +54,7 @@ size_t Resource::read(void* buffer, size_t size, size_t flags) { * @param flags Optional flags to pass (unused by default but resource type specific) * @return How many bytes were successfully written */ -size_t Resource::write(void const* buffer, size_t size, size_t flags) { +int Resource::write(void const* buffer, size_t size, size_t flags) { return 0; } @@ -69,12 +73,12 @@ string Resource::name() { * * @return The type */ -ResourceType Resource::type() { +resource_type_t Resource::type() { return m_type; } -BaseResourceRegistry::BaseResourceRegistry(ResourceType type) +BaseResourceRegistry::BaseResourceRegistry(resource_type_t type) : m_type(type) { GlobalResourceRegistry::add_registry(type, this); @@ -90,7 +94,7 @@ BaseResourceRegistry::~BaseResourceRegistry(){ * * @return The type */ -ResourceType BaseResourceRegistry::type() { +resource_type_t BaseResourceRegistry::type() { return m_type; } @@ -151,7 +155,7 @@ void BaseResourceRegistry::close_resource(Resource* resource, size_t flags) { return; // Can safely close the resource - resource->close(); + resource->close(flags); m_resources.erase(resource->name()); m_resource_uses.erase(resource->name()); delete resource; @@ -185,7 +189,7 @@ GlobalResourceRegistry::~GlobalResourceRegistry() { * @param type The type of registry to get * @return The registry or nullptr if not found */ -BaseResourceRegistry* GlobalResourceRegistry::get_registry(ResourceType type) { +BaseResourceRegistry* GlobalResourceRegistry::get_registry(resource_type_t type) { auto registry = s_current->m_registries.find(type); if(registry == s_current->m_registries.end()) @@ -200,7 +204,7 @@ BaseResourceRegistry* GlobalResourceRegistry::get_registry(ResourceType type) { * @param type The type of registry being added * @param registry The registry to add */ -void GlobalResourceRegistry::add_registry(ResourceType type, BaseResourceRegistry* registry) { +void GlobalResourceRegistry::add_registry(resource_type_t type, BaseResourceRegistry* registry) { // Does it already exist? if(s_current->m_registries.find(type) != s_current->m_registries.end()) @@ -257,7 +261,7 @@ common::Map ResourceManager::resources() { * @param name The name of the resource * @return The handle id of the resource or 0 if failed */ -uint64_t ResourceManager::open_resource(ResourceType type, string const& name, size_t flags) { +uint64_t ResourceManager::open_resource(resource_type_t type, string const& name, size_t flags) { // Get the resource auto resource = GlobalResourceRegistry::get_registry(type) -> get_resource(name); @@ -268,7 +272,7 @@ uint64_t ResourceManager::open_resource(ResourceType type, string const& name, s m_resources.insert(m_next_handle, resource); // Open it - resource->open(); + resource->open(flags); return m_next_handle++; } diff --git a/kernel/src/processes/scheduler.cpp b/kernel/src/processes/scheduler.cpp index f19ffa24..8a0586fb 100644 --- a/kernel/src/processes/scheduler.cpp +++ b/kernel/src/processes/scheduler.cpp @@ -18,8 +18,8 @@ Scheduler::Scheduler(Multiboot& multiboot) m_ticks(0), m_next_pid(-1), m_next_tid(-1), - m_shared_memory_registry(ResourceType::SHARED_MEMORY), - m_shared_messages_registry(ResourceType::MESSAGE_ENDPOINT) + m_shared_memory_registry(resource_type_t::SHARED_MEMORY), + m_shared_messages_registry(resource_type_t::MESSAGE_ENDPOINT) { // Set up the basic scheduler diff --git a/kernel/src/system/cpu.cpp b/kernel/src/system/cpu.cpp index 6def79fc..d45751ef 100644 --- a/kernel/src/system/cpu.cpp +++ b/kernel/src/system/cpu.cpp @@ -278,11 +278,12 @@ cpu_status_t* CPU::prepare_for_panic(cpu_status_t* status) { Logger::ERROR() << "CPU Panicked in process " << process->name.c_str() << " at 0x" << status->rip << " - killing process\n"; return Scheduler::system_scheduler()->force_remove_process(process); } + + // Otherwise occurred whilst the kernel was doing something for the process } // We are panicking is_panicking = true; - return nullptr; } diff --git a/kernel/src/system/syscalls.cpp b/kernel/src/system/syscalls.cpp index f9fd7320..366958b9 100644 --- a/kernel/src/system/syscalls.cpp +++ b/kernel/src/system/syscalls.cpp @@ -5,6 +5,7 @@ #include #include +using namespace ::system; using namespace MaxOS; using namespace MaxOS::common; using namespace MaxOS::hardwarecommunication; @@ -110,7 +111,7 @@ void SyscallManager::remove_syscall_handler(SyscallType syscall) { * @param args Arg0 = pid Arg1 = exit code * @return Nothing */ -system::syscall_args_t* SyscallManager::syscall_close_process(system::syscall_args_t* args) { +syscall_args_t* SyscallManager::syscall_close_process(system::syscall_args_t* args) { // Get the args uint64_t pid = args->arg0; @@ -287,9 +288,10 @@ syscall_args_t* SyscallManager::syscall_resource_read(syscall_args_t* args) { * @param args Nothing * @return Nothing */ -system::syscall_args_t* SyscallManager::syscall_thread_yield(system::syscall_args_t* args) { +syscall_args_t* SyscallManager::syscall_thread_yield(syscall_args_t* args) { // Yield + Scheduler::current_thread()->execution_state = args->return_state; cpu_status_t* next_process = Scheduler::system_scheduler()->yield(); args->return_state = next_process; @@ -302,7 +304,7 @@ system::syscall_args_t* SyscallManager::syscall_thread_yield(system::syscall_arg * @param args Arg0 = milliseconds * @return Nothing */ -system::syscall_args_t* SyscallManager::syscall_thread_sleep(system::syscall_args_t* args) { +syscall_args_t* SyscallManager::syscall_thread_sleep(syscall_args_t* args) { // Get the milliseconds size_t milliseconds = args->arg0; @@ -324,7 +326,7 @@ system::syscall_args_t* SyscallManager::syscall_thread_sleep(system::syscall_arg * @param args Arg0 = tid Arg1 = exit code * @return Nothing */ -system::syscall_args_t* SyscallManager::syscall_thread_close(system::syscall_args_t* args) { +syscall_args_t* SyscallManager::syscall_thread_close(syscall_args_t* args) { // Get the args uint64_t tid = args->arg0; diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt new file mode 100644 index 00000000..f51fb9af --- /dev/null +++ b/libraries/CMakeLists.txt @@ -0,0 +1,3 @@ +# The libraries +add_subdirectory(ipc) +add_subdirectory(system) \ No newline at end of file diff --git a/libraries/ipc/CMakeLists.txt b/libraries/ipc/CMakeLists.txt new file mode 100644 index 00000000..5d82d78a --- /dev/null +++ b/libraries/ipc/CMakeLists.txt @@ -0,0 +1,22 @@ +# NOTE: Cant use MAKE_LIBRARY() as dependency of libc + +# Set compiler flags +SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ffreestanding -fno-exceptions -fno-rtti -fpermissive -nostdlib -Wall -Wextra -mno-red-zone -mno-80387 -mno-mmx -fno-use-cxa-atexit") + +# Disable noisy warnings +SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-pointer-arith -Wno-address-of-packed-member -Wno-trigraphs -Wno-unused-variable -Wno-unused-function") + +# Collect sources +FILE(GLOB_RECURSE IPC_SRCS src/*.cpp src/*.s) + +# Build as a dynamic library +ADD_LIBRARY(ipc SHARED ${IPC_SRCS}) +TARGET_INCLUDE_DIRECTORIES(ipc PUBLIC include) +TARGET_LINK_LIBRARIES(ipc PUBLIC system_static) # Has to be free standing +INSTALL(TARGETS ipc LIBRARY DESTINATION ${CMAKE_SOURCE_DIR}/filesystem/os/lib) + +# Build as a static library +ADD_LIBRARY(ipc_static STATIC ${IPC_SRCS}) +TARGET_INCLUDE_DIRECTORIES(ipc_static PUBLIC include) +TARGET_LINK_LIBRARIES(ipc_static PUBLIC system_static) +INSTALL(TARGETS ipc_static DESTINATION ${CMAKE_SOURCE_DIR}/filesystem/os/lib) \ No newline at end of file diff --git a/libraries/ipc/include/ipc/messages.h b/libraries/ipc/include/ipc/messages.h new file mode 100644 index 00000000..2272a5f6 --- /dev/null +++ b/libraries/ipc/include/ipc/messages.h @@ -0,0 +1,23 @@ +// +// Created by 98max on 8/31/2025. +// + +#ifndef IPC_MESSAGES_H +#define IPC_MESSAGES_H + +#include +#include +#include + +namespace IPC{ + + uint64_t create_endpoint(const char* name); + uint64_t open_endpoint(const char* name); + void close_endpoint(uint64_t endpoint); + + void send_message(uint64_t endpoint, void* buffer, size_t size); + void read_message(uint64_t endpoint, void* buffer, size_t size); +} + + +#endif //IPC_MESSAGES_H diff --git a/libraries/ipc/include/ipc/sharedmemory.h b/libraries/ipc/include/ipc/sharedmemory.h new file mode 100644 index 00000000..7e1b00a2 --- /dev/null +++ b/libraries/ipc/include/ipc/sharedmemory.h @@ -0,0 +1,18 @@ +// +// Created by 98max on 8/31/2025. +// + +#ifndef IPC_SHAREDMEMORY_H +#define IPC_SHAREDMEMORY_H + +#include +#include +#include + +namespace IPC{ + + void* create_shared_memory(const char* name, size_t size); + void* open_shared_memory(const char* name); +} + +#endif //IPC_SHAREDMEMORY_H diff --git a/libraries/ipc/src/messages.cpp b/libraries/ipc/src/messages.cpp new file mode 100644 index 00000000..e6ce0801 --- /dev/null +++ b/libraries/ipc/src/messages.cpp @@ -0,0 +1,73 @@ +// +// Created by 98max on 8/31/2025. +// + +#include + +using namespace system; + +namespace IPC{ + + /** + * @brief Create a new endpoint for sending and receiving messages + * + * @param name The name of the endpoint + * @return The handle of the new endpoint or 0 if it failed + */ + uint64_t create_endpoint(const char* name){ + + // Create the resource + if(!resource_create(ResourceType::MESSAGE_ENDPOINT, name, 0)) + return 0; + + return open_endpoint(name); + } + + /** + * @brief Opens an endpoint under a specific name + * + * @param name The name of the endpoint to open + * @return The endpoint handle or 0 if it failed to open + */ + uint64_t open_endpoint(const char* name){ + return (uint64_t) resource_open(ResourceType::MESSAGE_ENDPOINT, name, 0); + } + + /** + * @brief Closes an endpoint + * + * @param endpoint The endpoint handle to close + */ + void close_endpoint(uint64_t endpoint){ + + if(endpoint) + resource_close(endpoint, 0); + + } + + /** + * @brief Queues a message on the endpoint (FIFO) + * + * @param endpoint The endpoint handle + * @param buffer The message + * @param size The size of the message + */ + void send_message(uint64_t endpoint, void* buffer, size_t size){ + + if(endpoint) + resource_write(endpoint, buffer, size, 0); + + } + + /** + * @brief Fetches the oldest message on the endpoint (FIFO) + * + * @param endpoint The endpoint handle + * @param buffer Where to read the message into + * @param size How much of the message to read + */ + void read_message(uint64_t endpoint, void* buffer, size_t size){ + if(endpoint) + resource_read(endpoint, buffer, size, 0); + } +} diff --git a/libraries/ipc/src/sharedmemory.cpp b/libraries/ipc/src/sharedmemory.cpp new file mode 100644 index 00000000..5bdcc866 --- /dev/null +++ b/libraries/ipc/src/sharedmemory.cpp @@ -0,0 +1,40 @@ +// +// Created by 98max on 8/31/2025. +// + +#include + +using namespace system; +namespace IPC{ + + /** + * @brief Create a new region of shared memory + * + * @param name The name of the region + * @param size The size of the region + * @return The start address of the region or nullptr if it failed to create + */ + void* create_shared_memory(const char* name, size_t size){ + + // Create the resource + if(!resource_create(ResourceType::SHARED_MEMORY, name, size)) + return nullptr; + + return open_shared_memory(name); + } + + void* open_shared_memory(const char* name){ + + // Open the resource + uint64_t handle = resource_open(ResourceType::SHARED_MEMORY, name, 0); + if(!handle) + return nullptr; + + // Get the address + auto address = resource_read(handle, nullptr, 0, 0); + if(!address) + return nullptr; + + return (void*)address; + } +}; \ No newline at end of file diff --git a/libraries/system/CMakeLists.txt b/libraries/system/CMakeLists.txt new file mode 100644 index 00000000..9e59df41 --- /dev/null +++ b/libraries/system/CMakeLists.txt @@ -0,0 +1,20 @@ +# NOTE: Cant use MAKE_LIBRARY() as dependency of libc + +# Set compiler flags +SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ffreestanding -fno-exceptions -fno-rtti -fpermissive -nostdlib -Wall -Wextra -mno-red-zone -mno-80387 -mno-mmx -fno-use-cxa-atexit") + +# Disable noisy warnings +SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-pointer-arith -Wno-address-of-packed-member -Wno-trigraphs -Wno-unused-variable -Wno-unused-function") + +# Collect sources +FILE(GLOB_RECURSE SYSTEM_SRCS src/*.cpp src/*.s) + +# Build as a dynamic library +ADD_LIBRARY(system SHARED ${SYSTEM_SRCS}) +TARGET_INCLUDE_DIRECTORIES(system PUBLIC include) +INSTALL(TARGETS system LIBRARY DESTINATION ${CMAKE_SOURCE_DIR}/filesystem/os/lib) + +# Build as a static library +ADD_LIBRARY(system_static STATIC ${SYSTEM_SRCS}) +TARGET_INCLUDE_DIRECTORIES(system_static PUBLIC include) +INSTALL(TARGETS system_static DESTINATION ${CMAKE_SOURCE_DIR}/filesystem/os/lib) \ No newline at end of file diff --git a/libraries/system/include/syscalls.h b/libraries/system/include/syscalls.h new file mode 100644 index 00000000..524ff483 --- /dev/null +++ b/libraries/system/include/syscalls.h @@ -0,0 +1,59 @@ +// +// Created by 98max on 8/31/2025. +// + +#ifndef SYSTEM_SYSCALLS_H +#define SYSTEM_SYSCALLS_H + +#include +#include + +namespace system{ + + enum class ResourceType{ + MESSAGE_ENDPOINT, + SHARED_MEMORY + }; + + enum class ResourceErrorBase{ + SHOULD_BLOCK = 1, + + _END // Use this to extend errors + }; + int as_error(ResourceErrorBase code); + + enum class SyscallType{ + CLOSE_PROCESS, + KLOG, // TODO: Turn into open proc + ALLOCATE_MEMORY, + FREE_MEMORY, + RESOURCE_CREATE, + RESOURCE_OPEN, + RESOURCE_CLOSE, + RESOURCE_WRITE, + RESOURCE_READ, + THREAD_YIELD, + THREAD_SLEEP, + THREAD_CLOSE, + }; + + void* make_syscall(SyscallType type, uint64_t arg0, uint64_t arg1, uint64_t arg2, uint64_t arg3, uint64_t arg4, uint64_t arg5); + + void close_process(uint64_t pid, int status); + void klog(const char* message); + + void* allocate_memory(size_t size); + void free_memory(void* address); + + bool resource_create(ResourceType type, const char* name, size_t flags); + uint64_t resource_open(ResourceType type, const char* name, size_t flags); + void resource_close(uint64_t handle, size_t flags); + size_t resource_write(uint64_t handle, const void* buffer, size_t size, size_t flags); + size_t resource_read(uint64_t handle, void* buffer, size_t size, size_t flags); + + void thread_yield(); + void thread_sleep(uint64_t time); + void thread_exit(); +} + +#endif //SYSTEM_SYSCALLS_H diff --git a/libraries/system/src/syscalls.cpp b/libraries/system/src/syscalls.cpp new file mode 100644 index 00000000..74fc449d --- /dev/null +++ b/libraries/system/src/syscalls.cpp @@ -0,0 +1,187 @@ +// +// Created by 98max on 8/31/2025. +// + +#include + +namespace system{ + + int as_error(ResourceErrorBase code){ + return -1 * (int)code; + } + + /** + * @brief Make a syscall + * + * @param type The type of syscall + * @param arg0 The first argument + * @param arg1 The second argument + * @param arg2 The third argument + * @param arg3 The fourth argument + * @param arg4 The fifth argument + * @param arg5 The sixth argument + * @return The result of the syscall + */ + void* make_syscall(SyscallType type, uint64_t arg0, uint64_t arg1, uint64_t arg2, uint64_t arg3, uint64_t arg4, uint64_t arg5){ + + void* result; + asm volatile( + "mov %[a0], %%rdi\n\t" // arg0 -> rdi + "mov %[a1], %%rsi\n\t" // arg1 -> rsi + "mov %[a2], %%rdx\n\t" // arg2 -> rdx + "mov %[a3], %%r10\n\t" // arg3 -> r10 + "mov %[a4], %%r8\n\t" // arg4 -> r8 + "mov %[a5], %%r9\n\t" // arg5 -> r9 + "mov %[num], %%rax\n\t" // syscall number -> rax + "int $0x80\n\t" + : "=a"(result) + : [num] "r"((uint64_t)type), + [a0] "r"(arg0), + [a1] "r"(arg1), + [a2] "r"(arg2), + [a3] "r"(arg3), + [a4] "r"(arg4), + [a5] "r"(arg5) + : "rdi", "rsi", "rdx", "r10", "r8", "r9", "memory" + ); + + return result; + + } + + /** + * @brief Close a process + * + * @param pid The process ID (0 for current process) + * @param status The exit status + */ + void close_process(uint64_t pid, int status){ + make_syscall(SyscallType::CLOSE_PROCESS, pid, (uint64_t)status, 0, 0, 0, 0); + } + + /** + * @brief Log a message to the kernel log + * + * @param message The message to log + */ + void klog(const char* message){ + make_syscall(SyscallType::KLOG, (uint64_t)message, 0, 0, 0, 0, 0); + } + + /** + * @brief Allocate memory + * + * @param size The size of memory to allocate + * @return Pointer to the allocated memory + */ + void* allocate_memory(size_t size){ + return make_syscall(SyscallType::ALLOCATE_MEMORY, size, 0, 0, 0, 0, 0); + } + + /** + * @brief Free allocated memory + * + * @param address The address of the memory to free + */ + void free_memory(void* address){ + make_syscall(SyscallType::FREE_MEMORY, (uint64_t)address, 0, 0, 0, 0, 0); + } + + /** + * @brief Create a resource + * + * @param type The type of resource + * @param name The name of the resource + * @param flags Optional flags to pass (unused by default but resource type specific) + * @return The handle id of the resource or 0 if failed + */ + bool resource_create(ResourceType type, const char* name, size_t flags){ + return (bool)make_syscall(SyscallType::RESOURCE_CREATE, (uint64_t)type, (uint64_t)name, flags, 0, 0, 0); + } + + /** + * @brief Open an existing resource + * + * @param type The type of resource + * @param name The name of the resource + * @param flags Optional flags to pass + * @return The handle id of the resource or 0 if failed + */ + uint64_t resource_open(ResourceType type, const char* name, size_t flags){ + return (uint64_t)make_syscall(SyscallType::RESOURCE_OPEN, (uint64_t)type, (uint64_t)name, flags, 0, 0, 0); + } + + /** + * @brief Close a resource + * + * @param handle The handle number of the resource + * @param flags Flags to pass to the closing + */ + void resource_close(uint64_t handle, size_t flags){ + make_syscall(SyscallType::RESOURCE_CLOSE, handle, flags, 0, 0, 0, 0); + } + + /** + * @brief Write a certain amount of bytes to a resource + * + * @param handle The handle number of the resource + * @param buffer The buffer to read from + * @param size The number of bytes to write + * @param flags The flags to pass to the writing + * @return The number of bytes successfully written + */ + size_t resource_write(uint64_t handle, const void* buffer, size_t size, size_t flags){ + + int response = (int)(uintptr_t)make_syscall(SyscallType::RESOURCE_WRITE, handle, (uint64_t)buffer, size, flags, 0, 0); + while (response == as_error(ResourceErrorBase::SHOULD_BLOCK)){ + thread_yield(); + response = (int)(uintptr_t)make_syscall(SyscallType::RESOURCE_WRITE, handle, (uint64_t)buffer, size, flags, 0, 0); + } + + return response; + } + + /** + * @brief Read a certain amount of bytes from a resource + * + * @param handle The handle number of the resource + * @param buffer The buffer to read into + * @param size The number of bytes to read + * @param flags The flags to pass to the reading + * @return The number of bytes successfully read + */ + size_t resource_read(uint64_t handle, void* buffer, size_t size, size_t flags){ + int response = (int)(uintptr_t)make_syscall(SyscallType::RESOURCE_READ, handle, (uint64_t)buffer, size, flags, 0, 0); + while (response == as_error(ResourceErrorBase::SHOULD_BLOCK)){ + thread_yield(); + response = (int)(uintptr_t)make_syscall(SyscallType::RESOURCE_READ, handle, (uint64_t)buffer, size, flags, 0, 0); + } + + return response; + } + + /** + * @brief Yield the current thread's execution + * + */ + void thread_yield(){ + make_syscall(SyscallType::THREAD_YIELD, 0, 0, 0, 0, 0, 0); + } + + /** + * @brief Sleep the current thread for a certain amount of time + * + * @param time The time to sleep in milliseconds + */ + void thread_sleep(uint64_t time){ + make_syscall(SyscallType::THREAD_SLEEP, time, 0, 0, 0, 0, 0); + } + + /** + * @brief Exit the current thread + * + */ + void thread_exit(){ + make_syscall(SyscallType::THREAD_CLOSE, 0, 0, 0, 0, 0, 0); + } +} \ No newline at end of file diff --git a/programs/Example/CMakeLists.txt b/programs/Example/CMakeLists.txt index 36ec7b11..d6ddf594 100644 --- a/programs/Example/CMakeLists.txt +++ b/programs/Example/CMakeLists.txt @@ -12,7 +12,7 @@ ADD_EXECUTABLE(test.elf ${PROJ_SRCS}) # Set the linker info SET(LINKER_SCRIPT ${CMAKE_SOURCE_DIR}/programs/Example/src/linker.ld) SET_TARGET_PROPERTIES(test.elf PROPERTIES LINK_DEPENDS ${LINKER_SCRIPT}) -TARGET_LINK_LIBRARIES(test.elf gcc) +TARGET_LINK_LIBRARIES(test.elf gcc ipc_static) TARGET_LINK_OPTIONS(test.elf PRIVATE -T ${LINKER_SCRIPT} -nostdlib -n) # Install the executable diff --git a/programs/Example/src/main.cpp b/programs/Example/src/main.cpp index 0bcfba8f..6bf5a739 100644 --- a/programs/Example/src/main.cpp +++ b/programs/Example/src/main.cpp @@ -3,101 +3,68 @@ // #include #include +#include + +using namespace IPC; // Write using a syscall (int 0x80 with syscall 0x01 for write) -void write(const char* data) -{ - // don't care abt length for now - asm volatile("int $0x80" : : "a" (0x01), "b" (data)); +void write(const char* data) { + // don't care abt length for now + asm volatile("int $0x80" : : "a" (0x01), "b" (data)); } -void write_hex(uint64_t data) -{ - // Convert to hex - char buffer[20]; - buffer[0] = '0'; - buffer[1] = 'x'; - buffer[18] = '\n'; - buffer[19] = '\0'; - - // Convert to hex - for(int i = 0; i < 16; i++) - { - // Get the current nibble - uint8_t nibble = (data >> (60 - i * 4)) & 0xF; - - // Convert to hex - buffer[2 + i] = nibble < 10 ? '0' + nibble : 'A' + nibble - 10; - } - - // Write the hex - write(buffer); +void write_hex(uint64_t data) { + // Convert to hex + char buffer[20]; + buffer[0] = '0'; + buffer[1] = 'x'; + buffer[18] = '\n'; + buffer[19] = '\0'; + + // Convert to hex + for (int i = 0; i < 16; i++) { + // Get the current nibble + uint8_t nibble = (data >> (60 - i * 4)) & 0xF; + + // Convert to hex + buffer[2 + i] = nibble < 10 ? '0' + nibble : 'A' + nibble - 10; + } + + // Write the hex + write(buffer); } -// Create a shared memory block (int 0x80 with syscall 0x02 for create shared memory, result in rax) -void* create_shared_memory(uint64_t size, const char* name) -{ - void* result = nullptr; - asm volatile("int $0x80" : "=a" (result) : "a" (0x02), "b" (size), "c" (name)); - return result; -} +void yield() { -typedef struct SharedMessage{ - void* message_buffer; - size_t message_size; - uintptr_t next_message; -} ipc_message_t; - -typedef struct SharedMessageQueue{ - ipc_message_t* messages; -} ipc_message_queue_t; - -void* make_message_queue(const char* name) -{ - void* result = nullptr; - asm volatile("int $0x80" : "=a" (result) : "a" (0x06), "b" (name)); - return result; + asm volatile("int $0x80" : : "a" (0x09)); } -void yield() -{ - asm volatile("int $0x80" : : "a" (0x09)); -} - -extern "C" [[noreturn]] void _start(void) -{ - - // Write to the console - write("MaxOS Test Program v3\n"); +extern "C" [[noreturn]] void _start(void) { - // Create a message endpoint - ipc_message_queue_t* message_queue = (ipc_message_queue_t *)make_message_queue("TestQueue"); - if (!message_queue) - { - write("Failed to create message queue\n"); - while (true) - yield(); - } + // Write to the console + write("MaxOS Test Program v3\n"); - write("Message queue created: \n"); - write_hex((uint64_t)message_queue); + // Create a message endpoint + uint64_t queue = create_endpoint("TestQueue"); + if (!queue) { + write("Failed to create message queue\n"); + while (true) + yield(); + } - // Process events forever: - write("Waiting for messages\n"); - while(true) - if(message_queue->messages == nullptr) - yield(); - else{ + write("Message queue created: \n"); + write_hex(queue); + // Process events forever: + write("Waiting for messages\n"); + uint8_t message[255] = {}; - // Store the message - ipc_message_t* message = message_queue->messages; - write("Message received: \n"); - write_hex((uint64_t)message); - write((char*)message->message_buffer); + while (true){ - // Move to the next message - message_queue->messages = (ipc_message_t*)message->next_message; + // Store the message + read_message(queue, message, 255); + write("Message received: \n"); + write((char*)message); - } + } } \ No newline at end of file diff --git a/programs/Example2/CMakeLists.txt b/programs/Example2/CMakeLists.txt index 8cb46b50..22f75e65 100644 --- a/programs/Example2/CMakeLists.txt +++ b/programs/Example2/CMakeLists.txt @@ -10,7 +10,7 @@ ADD_EXECUTABLE(test1.elf ${PROJ_SRCS}) # Set the linker info SET(LINKER_SCRIPT ${CMAKE_SOURCE_DIR}/programs/Example/src/linker.ld) SET_TARGET_PROPERTIES(test1.elf PROPERTIES LINK_DEPENDS ${LINKER_SCRIPT}) -TARGET_LINK_LIBRARIES(test1.elf gcc) +TARGET_LINK_LIBRARIES(test1.elf gcc ipc_static) TARGET_LINK_OPTIONS(test1.elf PRIVATE -T ${LINKER_SCRIPT} -nostdlib -n) # Install the executable diff --git a/programs/Example2/src/main.cpp b/programs/Example2/src/main.cpp index f4b14ec3..3fe677b7 100644 --- a/programs/Example2/src/main.cpp +++ b/programs/Example2/src/main.cpp @@ -3,6 +3,9 @@ // #include #include +#include + +using namespace IPC; // Write using a syscall (int 0x80 with syscall 0x01 for write) void write(const char* data, uint64_t length = 0) @@ -34,54 +37,6 @@ void write_hex(uint64_t data) write(buffer); } -// Create a shared memory block (int 0x80 with syscall 0x03, taking a string as the name) -void* open_shared_memory(char* name) -{ - void* result = nullptr; - - while (result == nullptr) - asm volatile("int $0x80" : "=a" (result) : "a" (0x03), "b" (name)); - return result; -} - -void setstring(char* str, const char* message) -{ - while(*message != '\0') - *str++ = *message++; - *str = '\0'; -} - -typedef struct TestSharedMemoryBlock -{ - char message[100]; - -} TestSharedMemoryBlock; - -typedef struct SharedMessage{ - void* message_buffer; - size_t message_size; - uintptr_t next_message; -} ipc_message_t; - -void send_message(const char* endpoint, void* message, size_t size) -{ - asm volatile( - "mov %[endpoint], %%rdi\n\t" // arg0: endpoint - "mov %[message], %%rsi\n\t" // arg1: message - "mov %[size], %%rdx\n\t" // arg2: size - "mov $0, %%r10\n\t" // arg3: if not used, set to 0 - "mov $0, %%r8\n\t" // arg4: if not used, set to 0 - "mov $0, %%r9\n\t" // arg5: if not used, set to 0 - "mov $0x07, %%rax\n\t" // syscall number for send_message - "int $0x80\n\t" - : - : [endpoint] "r"(endpoint), - [message] "r"(message), - [size] "r"(size) - : "rax", "rdi", "rsi", "rdx", "r10", "r8", "r9" - ); -} - void close() { // syscall 0, arg0 = pid (0 for current process), arg1 = exit code @@ -96,7 +51,6 @@ void close() ); } - void wait(uint64_t ms) { // syscall 0x010, arg0 = milliseconds @@ -127,16 +81,15 @@ extern "C" int _start(int argc, char** argv) write("MaxOS Test Program v3.1\n"); const char* message = "Message from process 2\n"; - const char* endpoint = "TestQueue"; // Wait 4seconds - wait(4000); - write("Waited 4 seconds\n"); + wait(1000); + write("Waited 1 seconds\n"); // Send a message via IPC + uint64_t endpoint = open_endpoint("TestQueue"); send_message(endpoint, (void*)message, 24); - // Close the process write("Closing process\n"); close(); From 41d1a7594a53ad05b2cca191d463934677ef536f Mon Sep 17 00:00:00 2001 From: Max Tyson <98maxt98@gmail.com> Date: Tue, 2 Sep 2025 11:56:06 +1200 Subject: [PATCH 36/38] Userspace Files & Directories --- filesystem/boot/grub/grub.cfg | 4 +- filesystem/test/a.txt | 1 - kernel/CMakeLists.txt | 2 +- kernel/include/filesystem/ext2.h | 380 ----------------- kernel/include/filesystem/filesystem.h | 14 +- kernel/include/filesystem/format/ext2.h | 382 ++++++++++++++++++ .../include/filesystem/{ => format}/fat32.h | 3 +- kernel/include/filesystem/partition/msdos.h | 4 +- kernel/include/filesystem/vfs.h | 4 +- kernel/include/filesystem/vfsresource.h | 71 ++++ kernel/include/processes/resource.h | 8 +- kernel/include/system/syscalls.h | 4 +- kernel/src/asm/loader.s | 6 +- kernel/src/common/logger.cpp | 2 +- kernel/src/filesystem/filesystem.cpp | 38 +- kernel/src/filesystem/{ => format}/ext2.cpp | 5 +- kernel/src/filesystem/{ => format}/fat32.cpp | 3 +- kernel/src/filesystem/partition/msdos.cpp | 1 + kernel/src/filesystem/vfs.cpp | 57 +-- kernel/src/filesystem/vfsresource.cpp | 381 +++++++++++++++++ kernel/src/kernel.cpp | 11 +- kernel/src/linker.ld | 1 + kernel/src/system/syscalls.cpp | 4 +- libraries/CMakeLists.txt | 3 +- libraries/ipc/CMakeLists.txt | 22 - libraries/ipc/include/ipc/messages.h | 23 -- libraries/ipc/include/ipc/sharedmemory.h | 18 - libraries/ipc/src/messages.cpp | 73 ---- libraries/ipc/src/sharedmemory.cpp | 40 -- libraries/{system => syscore}/CMakeLists.txt | 12 +- libraries/syscore/include/common.h | 24 ++ .../syscore/include/filesystem/directory.h | 50 +++ libraries/syscore/include/filesystem/file.h | 46 +++ libraries/syscore/include/ipc/messages.h | 27 ++ libraries/syscore/include/ipc/sharedmemory.h | 21 + .../{system => syscore}/include/syscalls.h | 11 +- .../syscore/src/filesystem/directory.cpp | 107 +++++ libraries/syscore/src/filesystem/file.cpp | 59 +++ libraries/syscore/src/ipc/messages.cpp | 73 ++++ libraries/syscore/src/ipc/sharedmemory.cpp | 41 ++ .../{system => syscore}/src/syscalls.cpp | 2 +- programs/Example/CMakeLists.txt | 2 +- programs/Example/src/main.cpp | 55 ++- programs/Example2/CMakeLists.txt | 2 +- programs/Example2/src/main.cpp | 58 +-- 45 files changed, 1480 insertions(+), 675 deletions(-) delete mode 100755 filesystem/test/a.txt delete mode 100644 kernel/include/filesystem/ext2.h create mode 100644 kernel/include/filesystem/format/ext2.h rename kernel/include/filesystem/{ => format}/fat32.h (99%) create mode 100644 kernel/include/filesystem/vfsresource.h rename kernel/src/filesystem/{ => format}/ext2.cpp (99%) rename kernel/src/filesystem/{ => format}/fat32.cpp (99%) create mode 100644 kernel/src/filesystem/vfsresource.cpp delete mode 100644 libraries/ipc/CMakeLists.txt delete mode 100644 libraries/ipc/include/ipc/messages.h delete mode 100644 libraries/ipc/include/ipc/sharedmemory.h delete mode 100644 libraries/ipc/src/messages.cpp delete mode 100644 libraries/ipc/src/sharedmemory.cpp rename libraries/{system => syscore}/CMakeLists.txt (60%) create mode 100644 libraries/syscore/include/common.h create mode 100644 libraries/syscore/include/filesystem/directory.h create mode 100644 libraries/syscore/include/filesystem/file.h create mode 100644 libraries/syscore/include/ipc/messages.h create mode 100644 libraries/syscore/include/ipc/sharedmemory.h rename libraries/{system => syscore}/include/syscalls.h (90%) create mode 100644 libraries/syscore/src/filesystem/directory.cpp create mode 100644 libraries/syscore/src/filesystem/file.cpp create mode 100644 libraries/syscore/src/ipc/messages.cpp create mode 100644 libraries/syscore/src/ipc/sharedmemory.cpp rename libraries/{system => syscore}/src/syscalls.cpp (99%) diff --git a/filesystem/boot/grub/grub.cfg b/filesystem/boot/grub/grub.cfg index dfe59552..1977c537 100755 --- a/filesystem/boot/grub/grub.cfg +++ b/filesystem/boot/grub/grub.cfg @@ -7,7 +7,7 @@ menuentry "Max OS" { echo "Loading Kernel..." multiboot2 /boot/MaxOSk64 echo "Loading Test Elf..." - module2 /boot/test.elf Receiver - module2 /boot/test1.elf Sender + module2 /boot/test.elf File + module2 /boot/test1.elf Folder boot } \ No newline at end of file diff --git a/filesystem/test/a.txt b/filesystem/test/a.txt deleted file mode 100755 index 728a6822..00000000 --- a/filesystem/test/a.txt +++ /dev/null @@ -1 +0,0 @@ -aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ No newline at end of file diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index 8f232e5a..7696a80d 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -36,7 +36,7 @@ TARGET_COMPILE_DEFINITIONS(MaxOSk64 PUBLIC MAXOS_KERNEL) # Linker SET(LINKER_SCRIPT ${CMAKE_SOURCE_DIR}/kernel/src/linker.ld) SET_TARGET_PROPERTIES(MaxOSk64 PROPERTIES LINK_DEPENDS ${LINKER_SCRIPT}) -TARGET_LINK_LIBRARIES(MaxOSk64 PRIVATE gcc system_static) +TARGET_LINK_LIBRARIES(MaxOSk64 PRIVATE gcc syscore_static) TARGET_LINK_OPTIONS(MaxOSk64 PRIVATE -T ${LINKER_SCRIPT} -nostdlib -n) # Set the include directories diff --git a/kernel/include/filesystem/ext2.h b/kernel/include/filesystem/ext2.h deleted file mode 100644 index 5ac522cc..00000000 --- a/kernel/include/filesystem/ext2.h +++ /dev/null @@ -1,380 +0,0 @@ -// -// Created by 98max on 17/07/2025. -// - -#ifndef MAXOS_FILESYSTEM_EXT2_H -#define MAXOS_FILESYSTEM_EXT2_H - -#include -#include -#include -#include -#include -#include - -namespace MaxOS { - namespace filesystem { - namespace ext2{ - - - typedef struct SuperBlock{ - uint32_t total_inodes; - uint32_t total_blocks; - uint32_t reserved_blocks; - uint32_t unallocated_blocks; - uint32_t unallocated_inodes; - uint32_t starting_block; - uint32_t block_size; - uint32_t fragment_size; - uint32_t blocks_per_group; - uint32_t fragments_per_group; - uint32_t inodes_per_group; - uint32_t last_mount_time; - uint32_t late_write_time; - uint16_t mounts_since_check; - uint16_t mounts_until_check; - uint16_t signature; - uint16_t state; - uint16_t error_operation; - uint16_t version_minor; - uint32_t last_check_time; - uint32_t time_until_check; - uint32_t os_id; - uint32_t version_major; - uint16_t reserved_user; - uint16_t reserved_group; - - // Extended Fields (version >= 1) - uint32_t first_inode; - uint16_t inode_size; - uint16_t superblock_group; - uint32_t optional_features; - uint32_t required_features; - uint16_t read_only_features; - uint8_t filesystem_id[16]; - uint8_t volume_name[16]; - uint8_t last_mount_path[64]; - uint32_t compression; - uint8_t file_preallocation_blocks; - uint8_t directory_preallocation_blocks; - uint16_t unused; - uint8_t journal_id[16]; - uint32_t journal_inode; - uint32_t journal_device; - uint32_t orphan_inodes_start; - uint8_t free[276]; - - } __attribute__((packed)) superblock_t; - - - enum class FileSystemState{ - CLEAN = 1, - ERROR = 2, - }; - - enum class ErrorOperation{ - IGNORE = 1, - REMOUNT = 2, - PANIC = 3, - }; - - enum class CreatorOS{ - LINUX, - GNU_HURD, - MASIX, - FREE_BSD, - OTHER_LITES, - }; - - enum class OptionalFeatures{ - PREALLOCATE_DIRECTORY = 0x1, - AFS_SERVER_INODES = 0x2, - JOURNAL_ENABLED = 0x4, - ATTRIBUTES_EXTENDED = 0x8, - RESIZEABLE = 0x10, - HASH_INDEXING = 0x20, - }; - - enum class RequiredFeatures { - COMPRESSION = 0x1, - DIRECTORY_HAS_TYPE = 0x2, - MUST_REPLAY_JOURNAL = 0x4, - JOURNAL_DEVICE = 0x8, - }; - - enum class ReadOnlyFeatures { - SPARSE_SUPER_BLOCKS = 0x1, - FILES_64_BIT = 0x2, - BINARY_TREE_DIRECTORIES = 0x4, - }; - - typedef struct BlockGroupDescriptor{ - uint32_t block_usage_bitmap; - uint32_t block_inode_bitmap; - uint32_t inode_table_address; - uint16_t free_blocks; - uint16_t free_inodes; - uint16_t directory_count; - uint8_t free[14]; - - } __attribute__((packed)) block_group_descriptor_t; - - typedef struct Inode{ - union { - uint16_t type_permissions; - struct { - uint16_t permissions : 12; - uint16_t type : 4; - }; - }; - uint16_t user_id; - uint32_t size_lower; - uint32_t last_access_time; - uint32_t creation_time; - uint32_t last_modification_time; - uint32_t deletion_time; - uint16_t group_id; - uint16_t hard_links; - uint32_t sectors_used; - uint32_t flags; - uint32_t os_1; - uint32_t block_pointers[12]; - uint32_t l1_indirect; - uint32_t l2_indirect; - uint32_t l3_indirect; - uint32_t generation; - uint32_t extended_attribute; // File ACL - uint32_t size_upper; // Dir ACL - uint32_t os_2[3]; - } __attribute__((packed)) inode_t; - - enum class InodeType { - UNKNOWN, - FIFO = 0x1000, - CHARACTER_DEVICE = 0x2000, - DIRECTORY = 0x4000, - BLOCK_DEVICE = 0x6000, - FILE = 0x8000, - SYMBOLIC_LINK = 0xA000, - SOCKET = 0xC000, - }; - - enum class InodePermissions{ - OTHER_EXECUTE = 0x1, - OTHER_WRITE = 0x2, - OTHER_READ = 0x4, - GROUP_EXECUTE = 0x8, - GROUP_WRITE = 0x10, - GROUP_READ = 0x20, - USER_EXECUTE = 0x40, - USER_WRITE = 0x80, - USER_READ = 0x100, - STICKY = 0x200, - GROUP_ID = 0x400, - USER_ID = 0x800, - }; - - enum class InodePermissionsDefaults{ - FILE = 0x1A4, - DIRECTORY = 0x1ED, - - }; - - enum class InodeFlags{ - SECURE_DELETE = 0x1, // Zero out data on deletion - KEEP_DATA = 0x2, - FILE_COMPRESSION = 0x4, - SYNC_UPDATES = 0x8, - FILE_IMMUTABLE = 0x10, - APPEND_ONLY = 0x20, - DONT_DUMP = 0x40, - NO_LAST_ACCESS = 0x80, - HASH_INDEXED = 0x10000, - AFS_DIRECTORY = 0x20000, - JOURNAL_FILE_DATA = 0x40000, - }; - - // TODO: Also HURD, MASIX - typedef struct InodeOS2Linux{ - uint8_t fragment; - uint8_t fragment_size; - uint16_t high_type_permissions; - uint16_t high_user_id; - uint16_t high_group_id; - uint32_t author_id; //0xFFFFFFFF = use user_id - } __attribute__((packed)) linux_os_2_t; - - typedef struct DirectoryEntry{ - uint32_t inode; - uint16_t size; - uint8_t name_length; - uint8_t type; - // Rest are name chars - } __attribute__((packed)) directory_entry_t; - - enum class EntryType { - UNKNOWN, - FILE, - DIRECTORY, - CHARACTER_DEVICE, - BLOCK_DEVICE, - FIFO, - SOCKET, - SYMBOLIC_LINK - }; - - class Ext2Volume { - - private: - common::Vector allocate_group_blocks(uint32_t block_group, uint32_t amount); - void free_group_blocks(uint32_t block_group, uint32_t amount, uint32_t start); - - - void write_back_block_groups() const; - void write_back_superblock(); - - public: - Ext2Volume(drivers::disk::Disk* disk, lba_t partition_offset); - ~Ext2Volume(); - - drivers::disk::Disk* disk; - lba_t partition_offset; - - superblock_t superblock; - block_group_descriptor_t** block_groups; - - size_t block_size; - uint32_t block_group_descriptor_table_block; - uint32_t block_group_descriptor_table_size; - uint32_t total_block_groups; - size_t pointers_per_block; - uint32_t inodes_per_block; - uint32_t sectors_per_block; - - uint32_t blocks_per_inode_table; - uint32_t sectors_per_inode_table; - - common::Spinlock ext2_lock; - - void write_block(uint32_t block_num, common::buffer_t* buffer); - void write_inode(uint32_t inode_num, inode_t* inode); - - [[nodiscard]] uint32_t create_inode(bool is_directory); - void free_inode(uint32_t inode); - - void read_block(uint32_t block_num, common::buffer_t* buffer) const; - [[nodiscard]] inode_t read_inode(uint32_t inode_num) const; - - [[nodiscard]] uint32_t allocate_block(); - [[nodiscard]] common::Vector allocate_blocks(uint32_t amount); - [[nodiscard]] uint32_t bytes_to_blocks(size_t bytes) const; - - void free_blocks(const common::Vector& blocks); - - - // TODO: free blocks - }; - - /** - * @class InodeHandler - * @brief Simplfies the management of an inode & its blocks - */ - class InodeHandler { - - private: - Ext2Volume* m_volume = nullptr; - - void parse_indirect(uint32_t level, uint32_t block, common::buffer_t* buffer); - void write_indirect(uint32_t level, uint32_t& block, size_t& index); - void store_blocks(const common::Vector& blocks); - - public: - InodeHandler(Ext2Volume* volume, uint32_t inode); - ~InodeHandler(); - - uint32_t inode_number; - inode_t inode; - common::Vector block_cache; - - [[nodiscard]] size_t size() const; - void set_size(size_t size); - size_t grow(size_t amount, bool flush = true); - - void save(); - - void free(); - - }; - - /** - * @class Ext2File - * @brief Handles the file operations on the ext2 filesystem - */ - class Ext2File final : public File { - private: - Ext2Volume* m_volume; - InodeHandler m_inode; - - public: - Ext2File(Ext2Volume* volume, uint32_t inode, const string& name); - ~Ext2File() final; - - void write(const common::buffer_t* data, size_t amount) final; - void read(common::buffer_t* data, size_t amount) final; - void flush() final; - }; - - /** - * @class Ext2Directory - * @brief Handles the directory operations on the ext2 filesystem - */ - class Ext2Directory final : public Directory { - - private: - Ext2Volume* m_volume; - InodeHandler m_inode; - - common::Vector m_entries; - common::Vector m_entry_names; - - void write_entries(); - directory_entry_t create_entry(const string& name, uint32_t inode, bool is_directory = false); - - void parse_block(common::buffer_t* buffer); - - void remove_entry(const string& name, bool is_directory, bool clear = true); - void rename_entry(const string& old_name, const string& new_name, bool is_directory); - - public: - Ext2Directory(Ext2Volume* volume, uint32_t inode, const string& name); - ~Ext2Directory() final; - - void read_from_disk() final; - - File* create_file(const string& name) final; - void remove_file(const string& name) final; - void rename_file(const string& old_name, const string& new_name) final; - - Directory* create_subdirectory(const string& name) final; - void remove_subdirectory(const string& name) final; - void rename_subdirectory(const string& old_name, const string& new_name) final; - }; - - /** - * @class Ext2FileSystem - * @brief Handles the ext2 filesystem operations - */ - class Ext2FileSystem final : public FileSystem { - private: - Ext2Volume m_volume; - - public: - Ext2FileSystem(drivers::disk::Disk* disk, uint32_t partition_offset); - ~Ext2FileSystem() final; - }; - - } - } -} - -#endif // MAXOS_FILESYSTEM_EXT2_H diff --git a/kernel/include/filesystem/filesystem.h b/kernel/include/filesystem/filesystem.h index 8ace4b36..9638045f 100644 --- a/kernel/include/filesystem/filesystem.h +++ b/kernel/include/filesystem/filesystem.h @@ -10,6 +10,7 @@ #include #include #include +#include namespace MaxOS{ @@ -17,12 +18,7 @@ namespace MaxOS{ // Easier to read typedef uint32_t lba_t; - - enum class SeekType{ - SET, - CURRENT, - END - }; + typedef syscore::filesystem::SeekType SeekType; /** * @class Path @@ -31,12 +27,15 @@ namespace MaxOS{ class Path { public: - static bool vaild(string path); + static bool valid(string path); + static bool is_file(const string& path); + static string file_name(string path); static string file_extension(string path); static string file_path(string path); static string top_directory(string path); + static string parent_directory(string path); }; @@ -67,6 +66,7 @@ namespace MaxOS{ string name(); }; + /** * @class Directory * @brief Handles a group of files (directory) diff --git a/kernel/include/filesystem/format/ext2.h b/kernel/include/filesystem/format/ext2.h new file mode 100644 index 00000000..79df5e54 --- /dev/null +++ b/kernel/include/filesystem/format/ext2.h @@ -0,0 +1,382 @@ +// +// Created by 98max on 17/07/2025. +// + +#ifndef MAXOS_FILESYSTEM_EXT2_H +#define MAXOS_FILESYSTEM_EXT2_H + +#include +#include +#include +#include +#include +#include + +namespace MaxOS { + namespace filesystem { + namespace format{ + namespace ext2{ + + + typedef struct SuperBlock{ + uint32_t total_inodes; + uint32_t total_blocks; + uint32_t reserved_blocks; + uint32_t unallocated_blocks; + uint32_t unallocated_inodes; + uint32_t starting_block; + uint32_t block_size; + uint32_t fragment_size; + uint32_t blocks_per_group; + uint32_t fragments_per_group; + uint32_t inodes_per_group; + uint32_t last_mount_time; + uint32_t late_write_time; + uint16_t mounts_since_check; + uint16_t mounts_until_check; + uint16_t signature; + uint16_t state; + uint16_t error_operation; + uint16_t version_minor; + uint32_t last_check_time; + uint32_t time_until_check; + uint32_t os_id; + uint32_t version_major; + uint16_t reserved_user; + uint16_t reserved_group; + + // Extended Fields (version >= 1) + uint32_t first_inode; + uint16_t inode_size; + uint16_t superblock_group; + uint32_t optional_features; + uint32_t required_features; + uint16_t read_only_features; + uint8_t filesystem_id[16]; + uint8_t volume_name[16]; + uint8_t last_mount_path[64]; + uint32_t compression; + uint8_t file_preallocation_blocks; + uint8_t directory_preallocation_blocks; + uint16_t unused; + uint8_t journal_id[16]; + uint32_t journal_inode; + uint32_t journal_device; + uint32_t orphan_inodes_start; + uint8_t free[276]; + + } __attribute__((packed)) superblock_t; + + + enum class FileSystemState{ + CLEAN = 1, + ERROR = 2, + }; + + enum class ErrorOperation{ + IGNORE = 1, + REMOUNT = 2, + PANIC = 3, + }; + + enum class CreatorOS{ + LINUX, + GNU_HURD, + MASIX, + FREE_BSD, + OTHER_LITES, + }; + + enum class OptionalFeatures{ + PREALLOCATE_DIRECTORY = 0x1, + AFS_SERVER_INODES = 0x2, + JOURNAL_ENABLED = 0x4, + ATTRIBUTES_EXTENDED = 0x8, + RESIZEABLE = 0x10, + HASH_INDEXING = 0x20, + }; + + enum class RequiredFeatures { + COMPRESSION = 0x1, + DIRECTORY_HAS_TYPE = 0x2, + MUST_REPLAY_JOURNAL = 0x4, + JOURNAL_DEVICE = 0x8, + }; + + enum class ReadOnlyFeatures { + SPARSE_SUPER_BLOCKS = 0x1, + FILES_64_BIT = 0x2, + BINARY_TREE_DIRECTORIES = 0x4, + }; + + typedef struct BlockGroupDescriptor{ + uint32_t block_usage_bitmap; + uint32_t block_inode_bitmap; + uint32_t inode_table_address; + uint16_t free_blocks; + uint16_t free_inodes; + uint16_t directory_count; + uint8_t free[14]; + + } __attribute__((packed)) block_group_descriptor_t; + + typedef struct Inode{ + union { + uint16_t type_permissions; + struct { + uint16_t permissions : 12; + uint16_t type : 4; + }; + }; + uint16_t user_id; + uint32_t size_lower; + uint32_t last_access_time; + uint32_t creation_time; + uint32_t last_modification_time; + uint32_t deletion_time; + uint16_t group_id; + uint16_t hard_links; + uint32_t sectors_used; + uint32_t flags; + uint32_t os_1; + uint32_t block_pointers[12]; + uint32_t l1_indirect; + uint32_t l2_indirect; + uint32_t l3_indirect; + uint32_t generation; + uint32_t extended_attribute; // File ACL + uint32_t size_upper; // Dir ACL + uint32_t os_2[3]; + } __attribute__((packed)) inode_t; + + enum class InodeType { + UNKNOWN, + FIFO = 0x1000, + CHARACTER_DEVICE = 0x2000, + DIRECTORY = 0x4000, + BLOCK_DEVICE = 0x6000, + FILE = 0x8000, + SYMBOLIC_LINK = 0xA000, + SOCKET = 0xC000, + }; + + enum class InodePermissions{ + OTHER_EXECUTE = 0x1, + OTHER_WRITE = 0x2, + OTHER_READ = 0x4, + GROUP_EXECUTE = 0x8, + GROUP_WRITE = 0x10, + GROUP_READ = 0x20, + USER_EXECUTE = 0x40, + USER_WRITE = 0x80, + USER_READ = 0x100, + STICKY = 0x200, + GROUP_ID = 0x400, + USER_ID = 0x800, + }; + + enum class InodePermissionsDefaults{ + FILE = 0x1A4, + DIRECTORY = 0x1ED, + + }; + + enum class InodeFlags{ + SECURE_DELETE = 0x1, // Zero out data on deletion + KEEP_DATA = 0x2, + FILE_COMPRESSION = 0x4, + SYNC_UPDATES = 0x8, + FILE_IMMUTABLE = 0x10, + APPEND_ONLY = 0x20, + DONT_DUMP = 0x40, + NO_LAST_ACCESS = 0x80, + HASH_INDEXED = 0x10000, + AFS_DIRECTORY = 0x20000, + JOURNAL_FILE_DATA = 0x40000, + }; + + // TODO: Also HURD, MASIX + typedef struct InodeOS2Linux{ + uint8_t fragment; + uint8_t fragment_size; + uint16_t high_type_permissions; + uint16_t high_user_id; + uint16_t high_group_id; + uint32_t author_id; //0xFFFFFFFF = use user_id + } __attribute__((packed)) linux_os_2_t; + + typedef struct DirectoryEntry{ + uint32_t inode; + uint16_t size; + uint8_t name_length; + uint8_t type; + // Rest are name chars + } __attribute__((packed)) directory_entry_t; + + enum class EntryType { + UNKNOWN, + FILE, + DIRECTORY, + CHARACTER_DEVICE, + BLOCK_DEVICE, + FIFO, + SOCKET, + SYMBOLIC_LINK + }; + + class Ext2Volume { + + private: + common::Vector allocate_group_blocks(uint32_t block_group, uint32_t amount); + void free_group_blocks(uint32_t block_group, uint32_t amount, uint32_t start); + + + void write_back_block_groups() const; + void write_back_superblock(); + + public: + Ext2Volume(drivers::disk::Disk* disk, lba_t partition_offset); + ~Ext2Volume(); + + drivers::disk::Disk* disk; + lba_t partition_offset; + + superblock_t superblock; + block_group_descriptor_t** block_groups; + + size_t block_size; + uint32_t block_group_descriptor_table_block; + uint32_t block_group_descriptor_table_size; + uint32_t total_block_groups; + size_t pointers_per_block; + uint32_t inodes_per_block; + uint32_t sectors_per_block; + + uint32_t blocks_per_inode_table; + uint32_t sectors_per_inode_table; + + common::Spinlock ext2_lock; + + void write_block(uint32_t block_num, common::buffer_t* buffer); + void write_inode(uint32_t inode_num, inode_t* inode); + + [[nodiscard]] uint32_t create_inode(bool is_directory); + void free_inode(uint32_t inode); + + void read_block(uint32_t block_num, common::buffer_t* buffer) const; + [[nodiscard]] inode_t read_inode(uint32_t inode_num) const; + + [[nodiscard]] uint32_t allocate_block(); + [[nodiscard]] common::Vector allocate_blocks(uint32_t amount); + [[nodiscard]] uint32_t bytes_to_blocks(size_t bytes) const; + + void free_blocks(const common::Vector& blocks); + + + // TODO: free blocks + }; + + /** + * @class InodeHandler + * @brief Simplfies the management of an inode & its blocks + */ + class InodeHandler { + + private: + Ext2Volume* m_volume = nullptr; + + void parse_indirect(uint32_t level, uint32_t block, common::buffer_t* buffer); + void write_indirect(uint32_t level, uint32_t& block, size_t& index); + void store_blocks(const common::Vector& blocks); + + public: + InodeHandler(Ext2Volume* volume, uint32_t inode); + ~InodeHandler(); + + uint32_t inode_number; + inode_t inode; + common::Vector block_cache; + + [[nodiscard]] size_t size() const; + void set_size(size_t size); + size_t grow(size_t amount, bool flush = true); + + void save(); + + void free(); + + }; + + /** + * @class Ext2File + * @brief Handles the file operations on the ext2 filesystem + */ + class Ext2File final : public File { + private: + Ext2Volume* m_volume; + InodeHandler m_inode; + + public: + Ext2File(Ext2Volume* volume, uint32_t inode, const string& name); + ~Ext2File() final; + + void write(const common::buffer_t* data, size_t amount) final; + void read(common::buffer_t* data, size_t amount) final; + void flush() final; + }; + + /** + * @class Ext2Directory + * @brief Handles the directory operations on the ext2 filesystem + */ + class Ext2Directory final : public Directory { + + private: + Ext2Volume* m_volume; + InodeHandler m_inode; + + common::Vector m_entries; + common::Vector m_entry_names; + + void write_entries(); + directory_entry_t create_entry(const string& name, uint32_t inode, bool is_directory = false); + + void parse_block(common::buffer_t* buffer); + + void remove_entry(const string& name, bool is_directory, bool clear = true); + void rename_entry(const string& old_name, const string& new_name, bool is_directory); + + public: + Ext2Directory(Ext2Volume* volume, uint32_t inode, const string& name); + ~Ext2Directory() final; + + void read_from_disk() final; + + File* create_file(const string& name) final; + void remove_file(const string& name) final; + void rename_file(const string& old_name, const string& new_name) final; + + Directory* create_subdirectory(const string& name) final; + void remove_subdirectory(const string& name) final; + void rename_subdirectory(const string& old_name, const string& new_name) final; + }; + + /** + * @class Ext2FileSystem + * @brief Handles the ext2 filesystem operations + */ + class Ext2FileSystem final : public FileSystem { + private: + Ext2Volume m_volume; + + public: + Ext2FileSystem(drivers::disk::Disk* disk, uint32_t partition_offset); + ~Ext2FileSystem() final; + }; + + } + } + } +} + +#endif // MAXOS_FILESYSTEM_EXT2_H diff --git a/kernel/include/filesystem/fat32.h b/kernel/include/filesystem/format/fat32.h similarity index 99% rename from kernel/include/filesystem/fat32.h rename to kernel/include/filesystem/format/fat32.h index 9f1ce94b..9e27fb94 100644 --- a/kernel/include/filesystem/fat32.h +++ b/kernel/include/filesystem/format/fat32.h @@ -10,8 +10,8 @@ #include namespace MaxOS{ - namespace filesystem{ + namespace format{ // TODO: Revisit when I have the energy. // BUG: Subdirectory seems to write to the disk this end but tools like @@ -285,6 +285,7 @@ namespace MaxOS{ ~Fat32FileSystem(); }; + } } } diff --git a/kernel/include/filesystem/partition/msdos.h b/kernel/include/filesystem/partition/msdos.h index 0d24cea5..96fed8f9 100644 --- a/kernel/include/filesystem/partition/msdos.h +++ b/kernel/include/filesystem/partition/msdos.h @@ -7,8 +7,8 @@ #include #include -#include -#include +#include +#include #include namespace MaxOS{ diff --git a/kernel/include/filesystem/vfs.h b/kernel/include/filesystem/vfs.h index 0380949d..a371f052 100644 --- a/kernel/include/filesystem/vfs.h +++ b/kernel/include/filesystem/vfs.h @@ -31,13 +31,15 @@ namespace MaxOS{ void unmount_filesystem(const string& mount_point); void unmount_all(); + Directory* root_directory(); FileSystem* root_filesystem(); + FileSystem* get_filesystem(const string& mount_point); FileSystem* find_filesystem(string path); string get_relative_path(FileSystem* filesystem, string path); - Directory* root_directory(); Directory* open_directory(const string& path); + static Directory* open_directory(Directory* parent, const string& name); Directory* create_directory(string path); static Directory* create_directory(Directory* parent, const string& name); diff --git a/kernel/include/filesystem/vfsresource.h b/kernel/include/filesystem/vfsresource.h new file mode 100644 index 00000000..fb59157c --- /dev/null +++ b/kernel/include/filesystem/vfsresource.h @@ -0,0 +1,71 @@ +// +// Created by 98max on 9/1/2025. +// + +#ifndef MAXOS_FILESYSTEM_VFSRESOURCE_H +#define MAXOS_FILESYSTEM_VFSRESOURCE_H + +#include +#include +#include +#include +#include + +namespace MaxOS { + + namespace filesystem { + + class FileResource final : public processes::Resource{ + + public: + FileResource(const string& name, size_t flags, processes::resource_type_t type); + ~FileResource() final; + + File* file; + + int read(void* buffer, size_t size, size_t flags) final; + int write(const void* buffer, size_t size, size_t flags) final; + + }; + + class DirectoryResource final : public processes::Resource{ + + private: + + void write_entries(const void* buffer, size_t size) const; + [[nodiscard]] size_t entries_size() const; + + public: + + DirectoryResource(const string& name, size_t flags, processes::resource_type_t type); + ~DirectoryResource() final; + + Directory* directory; + + int read(void* buffer, size_t size, size_t flags) final; + int write(const void* buffer, size_t size, size_t flags) final; + + }; + + class VFSResourceRegistry : processes::BaseResourceRegistry{ + + private: + VirtualFileSystem* m_vfs; + + processes::Resource* open_as_resource(const string& name, Directory* directory); + processes::Resource* open_as_resource(const string& name, File* file); + + public: + + explicit VFSResourceRegistry(VirtualFileSystem* vfs); + ~VFSResourceRegistry(); + + processes::Resource* get_resource(const string& name) final; + processes::Resource* create_resource(const string& name, size_t flags) final; + + }; + + } +} + +#endif //MAXOS_FILESYSTEM_VFSRESOURCE_H diff --git a/kernel/include/processes/resource.h b/kernel/include/processes/resource.h index b8ae277d..897ff5d4 100644 --- a/kernel/include/processes/resource.h +++ b/kernel/include/processes/resource.h @@ -15,8 +15,8 @@ namespace MaxOS { namespace processes { - typedef ::system::ResourceType resource_type_t; - typedef ::system::ResourceErrorBase resource_error_base_t; + typedef ::syscore::ResourceType resource_type_t; + typedef ::syscore::ResourceErrorBase resource_error_base_t; class Resource { @@ -41,7 +41,7 @@ namespace MaxOS { class BaseResourceRegistry{ - private: + protected: common::Map m_resources; common::Map m_resource_uses; @@ -66,7 +66,7 @@ namespace MaxOS { explicit ResourceRegistry(resource_type_t type); ~ResourceRegistry() = default; - Resource* create_resource(const string& name, size_t flags) final { + Resource* create_resource(const string& name, size_t flags) override { auto resource = new Type(name, flags, type()); diff --git a/kernel/include/system/syscalls.h b/kernel/include/system/syscalls.h index cf9f67ae..fe437280 100644 --- a/kernel/include/system/syscalls.h +++ b/kernel/include/system/syscalls.h @@ -52,8 +52,8 @@ namespace MaxOS{ cpu_status_t* handle_interrupt(cpu_status_t* esp) final; - void set_syscall_handler(::system::SyscallType syscall, syscall_func_t handler); - void remove_syscall_handler(::system::SyscallType syscall); + void set_syscall_handler(::syscore::SyscallType syscall, syscall_func_t handler); + void remove_syscall_handler(::syscore::SyscallType syscall); // Syscalls (TODO: Very c style, should be made class based that automatically registers) static syscall_args_t* syscall_close_process(syscall_args_t* args); diff --git a/kernel/src/asm/loader.s b/kernel/src/asm/loader.s index b55588fc..db13be80 100644 --- a/kernel/src/asm/loader.s +++ b/kernel/src/asm/loader.s @@ -1,8 +1,8 @@ %define KERNEL_VIRTUAL_ADDR 0xFFFFFFFF80000000 %define PAGE_SIZE 0x1000 %define FLAGS 0b10 | 1 -%define LOOP_LIMIT 1024 -%define PD_LOOP_LIMIT 2 +%define LOOP_LIMIT 2048 +%define PD_LOOP_LIMIT 4 global p2_table @@ -137,7 +137,7 @@ p3_table_hh: p2_table: resb 4096 p1_tables: - resb 8192 + resb 16384 ; The stack for the kernel diff --git a/kernel/src/common/logger.cpp b/kernel/src/common/logger.cpp index 636208cb..59bb0004 100644 --- a/kernel/src/common/logger.cpp +++ b/kernel/src/common/logger.cpp @@ -18,7 +18,7 @@ Logger::Logger() s_active_logger = this; // The following line is generated automatically by the MaxOS build system. - s_progress_total = 23; + s_progress_total = 22; } diff --git a/kernel/src/filesystem/filesystem.cpp b/kernel/src/filesystem/filesystem.cpp index c916c9b8..2401de28 100644 --- a/kernel/src/filesystem/filesystem.cpp +++ b/kernel/src/filesystem/filesystem.cpp @@ -15,7 +15,7 @@ using namespace MaxOS::common; * @param path The path to check * @return True if the path is valid, false otherwise */ -bool Path::vaild(string path) { +bool Path::valid(string path) { // Must not be empty if (path.length() == 0) @@ -30,6 +30,18 @@ bool Path::vaild(string path) { } +/** + * @brief Check if a path is a file + * + * @param path The path to check + * @return True if the path is a file, false otherwise + */ +bool Path::is_file(const string& path) { + + return file_name(path).length() > 0 && file_extension(path).length() > 0; +} + + /** * @brief Get the file name component of a path if it exists or an empty string * @@ -69,7 +81,7 @@ string Path::file_extension(string path) { // Make sure there was a dot to split if (last_dot == -1) - return path; + return ""; // Get the file extension (what is after the ".") string file_extension = path.substring(last_dot + 1, path.length() - last_dot - 1); @@ -124,6 +136,28 @@ string Path::top_directory(string path) { return top_directory; } +/** + * @brief Get the parent directory of the path + * + * @param path The path to either the file or the directory + * @return + */ +string Path::parent_directory(string path) { + + // Find the last / + int last_slash = -1; + for (int i = 0; i < path.length(); i++) + if (path[i] == '/') + last_slash = i; + + // If no slash or only root, return empty string + if (last_slash <= 0) + return "/"; + + // Return substring up to last slash + return path.substring(0, last_slash); +} + File::File() = default; File::~File() = default; diff --git a/kernel/src/filesystem/ext2.cpp b/kernel/src/filesystem/format/ext2.cpp similarity index 99% rename from kernel/src/filesystem/ext2.cpp rename to kernel/src/filesystem/format/ext2.cpp index 59096835..409cc751 100644 --- a/kernel/src/filesystem/ext2.cpp +++ b/kernel/src/filesystem/format/ext2.cpp @@ -1,12 +1,13 @@ // // Created by 98max on 17/07/2025. // -#include +#include using namespace MaxOS; using namespace MaxOS::filesystem; using namespace MaxOS::common; -using namespace MaxOS::filesystem::ext2; +using namespace MaxOS::filesystem::format; +using namespace MaxOS::filesystem::format::ext2; using namespace MaxOS::drivers; using namespace MaxOS::drivers::disk; using namespace MaxOS::drivers::clock; diff --git a/kernel/src/filesystem/fat32.cpp b/kernel/src/filesystem/format/fat32.cpp similarity index 99% rename from kernel/src/filesystem/fat32.cpp rename to kernel/src/filesystem/format/fat32.cpp index 1dae8b45..381f77f3 100644 --- a/kernel/src/filesystem/fat32.cpp +++ b/kernel/src/filesystem/format/fat32.cpp @@ -2,7 +2,7 @@ // Created by 98max on 1/01/2023. // -#include +#include #include using namespace MaxOS; @@ -10,6 +10,7 @@ using namespace MaxOS::common; using namespace MaxOS::drivers; using namespace MaxOS::drivers::disk; using namespace MaxOS::filesystem; +using namespace MaxOS::filesystem::format; using namespace MaxOS::memory; Fat32Volume::Fat32Volume(Disk *hd, uint32_t partition_offset) diff --git a/kernel/src/filesystem/partition/msdos.cpp b/kernel/src/filesystem/partition/msdos.cpp index 834bc98f..956576b3 100644 --- a/kernel/src/filesystem/partition/msdos.cpp +++ b/kernel/src/filesystem/partition/msdos.cpp @@ -8,6 +8,7 @@ using namespace MaxOS; using namespace MaxOS::common; using namespace MaxOS::filesystem; using namespace MaxOS::filesystem::partition; +using namespace MaxOS::filesystem::format; using namespace MaxOS::drivers::disk; /** diff --git a/kernel/src/filesystem/vfs.cpp b/kernel/src/filesystem/vfs.cpp index 0a722e07..65f46f25 100644 --- a/kernel/src/filesystem/vfs.cpp +++ b/kernel/src/filesystem/vfs.cpp @@ -121,6 +121,22 @@ void VirtualFileSystem::unmount_all() { } +/** + * @brief Get the root directory of the virtual file system + * + * @return The root directory of the virtual file system + */ +Directory* VirtualFileSystem::root_directory() { + + // Get the root filesystem + FileSystem* fs = root_filesystem(); + if (!fs) + return nullptr; + + // Get the root directory + return fs->root_directory(); +} + /** * @brief Get the filesystem mounted at the root * @@ -207,22 +223,6 @@ string VirtualFileSystem::get_relative_path(FileSystem* filesystem, string path) return relative_path; } -/** - * @brief Get the root directory of the virtual file system - * - * @return The root directory of the virtual file system - */ -Directory* VirtualFileSystem::root_directory() { - - // Get the root filesystem - FileSystem* fs = root_filesystem(); - if (!fs) - return nullptr; - - // Get the root directory - return fs->root_directory(); -} - /** * @brief Try to open a directory on the virtual file system and read it's contents * @@ -232,7 +232,7 @@ Directory* VirtualFileSystem::root_directory() { Directory* VirtualFileSystem::open_directory(const string &path) { // Ensure a valid path is given - if (!Path::vaild(path)) + if (!Path::valid(path)) return nullptr; // Try to find the filesystem that is responsible for the path @@ -253,6 +253,17 @@ Directory* VirtualFileSystem::open_directory(const string &path) { return directory; } +Directory* VirtualFileSystem::open_directory(Directory* parent, string const& name) { + + // Open the file + Directory* opened_directory = parent->open_subdirectory(name); + if (!opened_directory) + return nullptr; + + opened_directory->read_from_disk(); + return opened_directory; +} + /** * @brief Attempts to open the parent directory and creates the sub directory at the end of the path * @@ -262,7 +273,7 @@ Directory* VirtualFileSystem::open_directory(const string &path) { Directory* VirtualFileSystem::create_directory(string path) { // Ensure a valid path is given - if (!Path::vaild(path)) + if (!Path::valid(path)) return nullptr; path = path.strip('/'); @@ -306,7 +317,7 @@ Directory* VirtualFileSystem::create_directory(Directory* parent, string const & void VirtualFileSystem::delete_directory(string path) { // Ensure a valid path is given - if (!Path::vaild(path)) + if (!Path::valid(path)) return; path = path.strip('/'); @@ -394,7 +405,7 @@ void VirtualFileSystem::delete_directory(Directory* parent, Directory* directory File* VirtualFileSystem::create_file(const string &path) { // Ensure a valid path is given - if (!Path::vaild(path)) + if (!Path::valid(path)) return nullptr; // Open the directory @@ -427,7 +438,7 @@ File* VirtualFileSystem::create_file(Directory* parent, string const &name) { File* VirtualFileSystem::open_file(const string &path, size_t offset) { // Ensure a valid path is given - if (!Path::vaild(path)) + if (!Path::valid(path)) return nullptr; // Open the directory @@ -467,7 +478,7 @@ File* VirtualFileSystem::open_file(Directory* parent, string const &name, size_t void VirtualFileSystem::delete_file(const string &path) { // Ensure a valid path is given - if (!Path::vaild(path)) + if (!Path::valid(path)) return; // Open the directory @@ -490,4 +501,4 @@ void VirtualFileSystem::delete_file(Directory* parent, string const &name) { // Delete the file parent->remove_file(name); -} +} \ No newline at end of file diff --git a/kernel/src/filesystem/vfsresource.cpp b/kernel/src/filesystem/vfsresource.cpp new file mode 100644 index 00000000..dba793a9 --- /dev/null +++ b/kernel/src/filesystem/vfsresource.cpp @@ -0,0 +1,381 @@ +// +// Created by 98max on 9/1/2025. +// +#include + +using namespace MaxOS; +using namespace MaxOS::filesystem; +using namespace MaxOS::processes; +using namespace MaxOS::common; +using namespace syscore::filesystem; + +FileResource::FileResource(string const& name, size_t flags, processes::resource_type_t type) +: Resource(name, flags, type), + file(nullptr) // Initialised by the registry +{ + +} + +FileResource::~FileResource() = default; + +/** + * @brief Read from a file resource + * + * @param buffer The buffer to read into + * @param size The number of bytes to read + * @param flags The flags to pass to the reading + * @return The number of bytes successfully read or -1 on error + */ +int FileResource::read(void* buffer, size_t size, size_t flags) { + + // File not found + if(!file) + return -1; + + // Handle the operation + switch ((FileFlags)flags) { + + case FileFlags::DEFAULT:{ + + buffer_t file_buffer(buffer, size); + file->read(&file_buffer, size); + break; + } + + case FileFlags::READ_SIZE:{ + return file->size(); + } + + case FileFlags::READ_OFFSET:{ + + return file->position(); + } + + default: + return -1; + } + return size; +} + +/** + * @brief Write to a file resource + * + * @param buffer The buffer to write from + * @param size The number of bytes to write + * @param flags The flags to pass to the writing + * @return The number of bytes successfully written or -1 on error + */ +int FileResource::write(void const* buffer, size_t size, size_t flags) { + + // File not found + if(!file) + return -1; + + // Handle the operation + switch ((FileFlags)flags) { + case FileFlags::DEFAULT:{ + + buffer_t file_buffer((void*)buffer, size); + file->write(&file_buffer, size); + break; + } + + case FileFlags::WRITE_SEEK_SET: + file->seek(SeekType::SET, size); + break; + + case FileFlags::WRITE_SEEK_CUR: + file->seek(SeekType::CURRENT, size); + break; + + case FileFlags::WRITE_SEEK_END: + file->seek(SeekType::END, size); + break; + + case FileFlags::WRITE_NAME:{ + + // Open the parent + Resource* parent = GlobalResourceRegistry::get_registry(resource_type_t::FILESYSTEM)->get_resource(Path::parent_directory(name())); + auto parent_directory = ((DirectoryResource*)parent)->directory; + + // Rename + string new_name = string((uint8_t*)buffer, size); + parent_directory->rename_file(file, new_name); + break; + } + + default: + return -1; + + } + + return size; +} + +DirectoryResource::DirectoryResource(string const& name, size_t flags, processes::resource_type_t type) +: Resource(name, flags, type), + directory(nullptr) +{ + +} + +DirectoryResource::~DirectoryResource() = default; + +/** + * @brief Copies all the entries in this directory into a buffer + * + * @param buffer The buffer to copy into + * @param size The size of the buffer + */ +void DirectoryResource::write_entries(void const* buffer, size_t size) const { + + size_t amount_written = 0; + + entry_information_t* entry = nullptr; + size_t entry_capacity = 0; + + auto write_single_entry = [&](const string& name, size_t entry_size, bool is_file) { + + // Make sure there is enough space + size_t required_size = sizeof(entry_information_t) + name.length() + 1; + if (required_size > entry_capacity) { + delete[] (uint8_t*)entry; + entry = (entry_information_t*)(new uint8_t[required_size]); + entry_capacity = required_size; + } + + // Create the entry + entry->is_file = is_file; + entry->size = entry_size; + entry->entry_length = required_size; + memcpy(entry->name, name.c_str(), name.length()); + entry->name[name.length()] = '\0'; + + // Not enough space + if (amount_written + entry->entry_length > size) + return false; + + // Copy the entry + memcpy((uint8_t*)buffer + amount_written, entry, entry->entry_length); + amount_written += entry->entry_length; + return true; + }; + + // Write files + for (const auto& file : directory->files()) { + if (!write_single_entry(file->name(), file->size(), true)) + break; + } + + // Write directories + for (const auto& dir : directory->subdirectories()) { + if (!write_single_entry(dir->name(), dir->size(), false)) + break; + } + + delete[] (uint8_t*)entry; +} + +/** + * @brief Gets the size required to store all the entries + * + * @return The total size + */ +size_t DirectoryResource::entries_size() const { + + size_t size = 0; + + // Files + for (const auto& file : directory->files()) { + size_t entry_size = sizeof(entry_information_t) + file->name().length() + 1; + size += entry_size; + } + + // Subdirectories + for (const auto& dir : directory->subdirectories()) { + size_t entry_size = sizeof(entry_information_t) + dir->name().length() + 1; + size += entry_size; + } + + return size; +} + +/** + * @brief Read from a directory resource + * + * @param buffer The buffer to read into + * @param size The number of bytes to read + * @param flags The flags to pass to the reading + * @return The number of bytes successfully read or -1 on error + */ +int DirectoryResource::read(void* buffer, size_t size, size_t flags) { + + // Directory not found + if(!directory) + return -1; + + switch ((DirectoryFlags)flags){ + case DirectoryFlags::READ_ENTRIES:{ + write_entries(buffer, size); + break; + } + + case DirectoryFlags::READ_ENTRIES_SIZE:{ + return entries_size(); + } + + default: + return -1; + } + + return size; + +} + +/** + * @brief Write to a directory resource + * + * @param buffer The buffer to write from + * @param size The number of bytes to write + * @param flags The flags to pass to the writing + * @return The number of bytes successfully written or -1 on error + */ +int DirectoryResource::write(void const* buffer, size_t size, size_t flags) { + + // Directory not found + if(!directory) + return -1; + + auto registry = GlobalResourceRegistry::get_registry(resource_type_t::FILESYSTEM); + + switch ((DirectoryFlags)flags){ + + case DirectoryFlags::WRITE_NAME : { + + // Open the parent + Resource* parent = registry->get_resource(Path::parent_directory(name())); + auto parent_directory = ((DirectoryResource*)parent)->directory; + + // Rename + string new_name = string((uint8_t*)buffer, size); + parent_directory->rename_subdirectory(directory, new_name); + break; + } + + case DirectoryFlags::WRITE_NEW_FILE:{ + + string new_name = string((uint8_t*)buffer, size); + directory->create_file(new_name); + break; + } + + + case DirectoryFlags::WRITE_NEW_DIR:{ + + string new_name = string((uint8_t*)buffer, size); + directory->create_subdirectory(new_name); + break; + } + + case DirectoryFlags::WRITE_REMOVE_FILE:{ + + string new_name = string((uint8_t*)buffer, size); + directory->remove_file(new_name); + break; + } + case DirectoryFlags::WRITE_REMOVE_DIR:{ + + string new_name = string((uint8_t*)buffer, size); + directory->remove_subdirectory(new_name); + break; + } + + default: + return -1; + } + + return size; +} + +VFSResourceRegistry::VFSResourceRegistry(VirtualFileSystem* vfs) +: BaseResourceRegistry(resource_type_t::FILESYSTEM), + m_vfs(vfs) +{ + +} + +VFSResourceRegistry::~VFSResourceRegistry() = default; + +/** + * @brief Open a directory as a resource + * + * @param name The name to call the resource + * @param directory The directory to open + * @return The new resource or nullptr if it failed to open + */ +Resource* VFSResourceRegistry::open_as_resource(const string& name, Directory* directory) { + + // Doesnt exist + if(!directory) + return nullptr; + + // Create the resource + auto resource = new DirectoryResource(name, 0, resource_type_t::FILESYSTEM); + resource->directory = directory; + + register_resource(resource); + return resource; +} + +/** + * @brief Open a file as a resource + * + * @param name The name to call the resource + * @param file The file to open + * @return The new resource or nullptr if it failed to open + */ +Resource* VFSResourceRegistry::open_as_resource(string const& name, File* file) { + + // Doesnt exist + if(!file) + return nullptr; + + // Create the resource + auto resource = new FileResource(name, 0, resource_type_t::FILESYSTEM); + resource->file = file; + + register_resource(resource); + return resource; +} + + +Resource* VFSResourceRegistry::get_resource(string const& name) { + + // Resource already opened + auto resource = BaseResourceRegistry::get_resource(name); + if(resource != nullptr) + return resource; + + // Open the resource + bool is_file = Path::is_file(name); + if(is_file) + return open_as_resource(name, m_vfs->open_file(name)); + else + return open_as_resource(name, m_vfs->open_directory(name)); +} + +Resource* VFSResourceRegistry::create_resource(string const& name, size_t flags) { + + // Resource already opened + auto resource = BaseResourceRegistry::get_resource(name); + if(resource != nullptr) + return resource; + + // Open the resource + bool is_file = Path::is_file(name); + if(is_file) + return open_as_resource(name, m_vfs->create_file(name)); + else + return open_as_resource(name, m_vfs->create_directory(name)); + +} \ No newline at end of file diff --git a/kernel/src/kernel.cpp b/kernel/src/kernel.cpp index 3e72a5ff..b51089be 100644 --- a/kernel/src/kernel.cpp +++ b/kernel/src/kernel.cpp @@ -13,6 +13,7 @@ #include #include #include +#include using namespace MaxOS; using namespace MaxOS::common; @@ -70,6 +71,7 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic Logger::HEADER() << "Stage {4}: System Finalisation\n"; Scheduler scheduler(multiboot); + VFSResourceRegistry vfs_registry(&vfs); SyscallManager syscalls; console.finish(); scheduler.activate(); @@ -79,6 +81,11 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic asm("nop"); } -// - Userspace Files (syscalls, proper path handling, working directories, file handles) +// TODO: +// - Userspace Files (proper path handling, working directories) + +// TODO: +// - SMP // - Class & Struct docstrings -// - Logo on fail in center \ No newline at end of file +// - Logo on fail in center +// - Sanitize syscall input \ No newline at end of file diff --git a/kernel/src/linker.ld b/kernel/src/linker.ld index df5865df..9ca02006 100644 --- a/kernel/src/linker.ld +++ b/kernel/src/linker.ld @@ -8,6 +8,7 @@ SECTIONS { .multiboot_header : { /* Be sure that multiboot header is at the beginning */ + _multiboot_header_start = .; *(.multiboot_header) } diff --git a/kernel/src/system/syscalls.cpp b/kernel/src/system/syscalls.cpp index 366958b9..7320ff58 100644 --- a/kernel/src/system/syscalls.cpp +++ b/kernel/src/system/syscalls.cpp @@ -5,7 +5,7 @@ #include #include -using namespace ::system; +using namespace ::syscore; using namespace MaxOS; using namespace MaxOS::common; using namespace MaxOS::hardwarecommunication; @@ -135,7 +135,7 @@ syscall_args_t* SyscallManager::syscall_klog(syscall_args_t* args) { // If the first two characters are %h then no header if (message[0] == '%' && message[1] == 'h') - Logger::INFO() << message + 2; + Logger::Out() << message + 2; else Logger::INFO() << ANSI_COLOURS[FG_Blue] << "(" << Scheduler::current_process()->name.c_str() << ":" << Scheduler::current_thread()->tid << "): " << ANSI_COLOURS[Reset] << message; diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt index f51fb9af..f5eb0cdb 100644 --- a/libraries/CMakeLists.txt +++ b/libraries/CMakeLists.txt @@ -1,3 +1,2 @@ # The libraries -add_subdirectory(ipc) -add_subdirectory(system) \ No newline at end of file +add_subdirectory(syscore) \ No newline at end of file diff --git a/libraries/ipc/CMakeLists.txt b/libraries/ipc/CMakeLists.txt deleted file mode 100644 index 5d82d78a..00000000 --- a/libraries/ipc/CMakeLists.txt +++ /dev/null @@ -1,22 +0,0 @@ -# NOTE: Cant use MAKE_LIBRARY() as dependency of libc - -# Set compiler flags -SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ffreestanding -fno-exceptions -fno-rtti -fpermissive -nostdlib -Wall -Wextra -mno-red-zone -mno-80387 -mno-mmx -fno-use-cxa-atexit") - -# Disable noisy warnings -SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-pointer-arith -Wno-address-of-packed-member -Wno-trigraphs -Wno-unused-variable -Wno-unused-function") - -# Collect sources -FILE(GLOB_RECURSE IPC_SRCS src/*.cpp src/*.s) - -# Build as a dynamic library -ADD_LIBRARY(ipc SHARED ${IPC_SRCS}) -TARGET_INCLUDE_DIRECTORIES(ipc PUBLIC include) -TARGET_LINK_LIBRARIES(ipc PUBLIC system_static) # Has to be free standing -INSTALL(TARGETS ipc LIBRARY DESTINATION ${CMAKE_SOURCE_DIR}/filesystem/os/lib) - -# Build as a static library -ADD_LIBRARY(ipc_static STATIC ${IPC_SRCS}) -TARGET_INCLUDE_DIRECTORIES(ipc_static PUBLIC include) -TARGET_LINK_LIBRARIES(ipc_static PUBLIC system_static) -INSTALL(TARGETS ipc_static DESTINATION ${CMAKE_SOURCE_DIR}/filesystem/os/lib) \ No newline at end of file diff --git a/libraries/ipc/include/ipc/messages.h b/libraries/ipc/include/ipc/messages.h deleted file mode 100644 index 2272a5f6..00000000 --- a/libraries/ipc/include/ipc/messages.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// Created by 98max on 8/31/2025. -// - -#ifndef IPC_MESSAGES_H -#define IPC_MESSAGES_H - -#include -#include -#include - -namespace IPC{ - - uint64_t create_endpoint(const char* name); - uint64_t open_endpoint(const char* name); - void close_endpoint(uint64_t endpoint); - - void send_message(uint64_t endpoint, void* buffer, size_t size); - void read_message(uint64_t endpoint, void* buffer, size_t size); -} - - -#endif //IPC_MESSAGES_H diff --git a/libraries/ipc/include/ipc/sharedmemory.h b/libraries/ipc/include/ipc/sharedmemory.h deleted file mode 100644 index 7e1b00a2..00000000 --- a/libraries/ipc/include/ipc/sharedmemory.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// Created by 98max on 8/31/2025. -// - -#ifndef IPC_SHAREDMEMORY_H -#define IPC_SHAREDMEMORY_H - -#include -#include -#include - -namespace IPC{ - - void* create_shared_memory(const char* name, size_t size); - void* open_shared_memory(const char* name); -} - -#endif //IPC_SHAREDMEMORY_H diff --git a/libraries/ipc/src/messages.cpp b/libraries/ipc/src/messages.cpp deleted file mode 100644 index e6ce0801..00000000 --- a/libraries/ipc/src/messages.cpp +++ /dev/null @@ -1,73 +0,0 @@ -// -// Created by 98max on 8/31/2025. -// - -#include - -using namespace system; - -namespace IPC{ - - /** - * @brief Create a new endpoint for sending and receiving messages - * - * @param name The name of the endpoint - * @return The handle of the new endpoint or 0 if it failed - */ - uint64_t create_endpoint(const char* name){ - - // Create the resource - if(!resource_create(ResourceType::MESSAGE_ENDPOINT, name, 0)) - return 0; - - return open_endpoint(name); - } - - /** - * @brief Opens an endpoint under a specific name - * - * @param name The name of the endpoint to open - * @return The endpoint handle or 0 if it failed to open - */ - uint64_t open_endpoint(const char* name){ - return (uint64_t) resource_open(ResourceType::MESSAGE_ENDPOINT, name, 0); - } - - /** - * @brief Closes an endpoint - * - * @param endpoint The endpoint handle to close - */ - void close_endpoint(uint64_t endpoint){ - - if(endpoint) - resource_close(endpoint, 0); - - } - - /** - * @brief Queues a message on the endpoint (FIFO) - * - * @param endpoint The endpoint handle - * @param buffer The message - * @param size The size of the message - */ - void send_message(uint64_t endpoint, void* buffer, size_t size){ - - if(endpoint) - resource_write(endpoint, buffer, size, 0); - - } - - /** - * @brief Fetches the oldest message on the endpoint (FIFO) - * - * @param endpoint The endpoint handle - * @param buffer Where to read the message into - * @param size How much of the message to read - */ - void read_message(uint64_t endpoint, void* buffer, size_t size){ - if(endpoint) - resource_read(endpoint, buffer, size, 0); - } -} diff --git a/libraries/ipc/src/sharedmemory.cpp b/libraries/ipc/src/sharedmemory.cpp deleted file mode 100644 index 5bdcc866..00000000 --- a/libraries/ipc/src/sharedmemory.cpp +++ /dev/null @@ -1,40 +0,0 @@ -// -// Created by 98max on 8/31/2025. -// - -#include - -using namespace system; -namespace IPC{ - - /** - * @brief Create a new region of shared memory - * - * @param name The name of the region - * @param size The size of the region - * @return The start address of the region or nullptr if it failed to create - */ - void* create_shared_memory(const char* name, size_t size){ - - // Create the resource - if(!resource_create(ResourceType::SHARED_MEMORY, name, size)) - return nullptr; - - return open_shared_memory(name); - } - - void* open_shared_memory(const char* name){ - - // Open the resource - uint64_t handle = resource_open(ResourceType::SHARED_MEMORY, name, 0); - if(!handle) - return nullptr; - - // Get the address - auto address = resource_read(handle, nullptr, 0, 0); - if(!address) - return nullptr; - - return (void*)address; - } -}; \ No newline at end of file diff --git a/libraries/system/CMakeLists.txt b/libraries/syscore/CMakeLists.txt similarity index 60% rename from libraries/system/CMakeLists.txt rename to libraries/syscore/CMakeLists.txt index 9e59df41..4c1d4ec0 100644 --- a/libraries/system/CMakeLists.txt +++ b/libraries/syscore/CMakeLists.txt @@ -10,11 +10,11 @@ SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-pointer-arith -Wno-address-of-packe FILE(GLOB_RECURSE SYSTEM_SRCS src/*.cpp src/*.s) # Build as a dynamic library -ADD_LIBRARY(system SHARED ${SYSTEM_SRCS}) -TARGET_INCLUDE_DIRECTORIES(system PUBLIC include) -INSTALL(TARGETS system LIBRARY DESTINATION ${CMAKE_SOURCE_DIR}/filesystem/os/lib) +ADD_LIBRARY(syscore SHARED ${SYSTEM_SRCS}) +TARGET_INCLUDE_DIRECTORIES(syscore PUBLIC include) +INSTALL(TARGETS syscore LIBRARY DESTINATION ${CMAKE_SOURCE_DIR}/filesystem/os/lib) # Build as a static library -ADD_LIBRARY(system_static STATIC ${SYSTEM_SRCS}) -TARGET_INCLUDE_DIRECTORIES(system_static PUBLIC include) -INSTALL(TARGETS system_static DESTINATION ${CMAKE_SOURCE_DIR}/filesystem/os/lib) \ No newline at end of file +ADD_LIBRARY(syscore_static STATIC ${SYSTEM_SRCS}) +TARGET_INCLUDE_DIRECTORIES(syscore_static PUBLIC include) +INSTALL(TARGETS syscore_static DESTINATION ${CMAKE_SOURCE_DIR}/filesystem/os/lib) \ No newline at end of file diff --git a/libraries/syscore/include/common.h b/libraries/syscore/include/common.h new file mode 100644 index 00000000..537b469e --- /dev/null +++ b/libraries/syscore/include/common.h @@ -0,0 +1,24 @@ +// +// Created by 98max on 9/1/2025. +// + +#ifndef SYSCORE_COMMON_H +#define SYSCORE_COMMON_H + +namespace syscore{ + + /** + * @brief Gets the length of a string + * + * @param str The string to get the length of + * @return The length of the string + */ + inline int strlen(const char *str) { + int len = 0; + for (; str[len] != '\0'; len++); + return len; + } + +} + +#endif //SYSCORE_COMMON_H diff --git a/libraries/syscore/include/filesystem/directory.h b/libraries/syscore/include/filesystem/directory.h new file mode 100644 index 00000000..8df9d749 --- /dev/null +++ b/libraries/syscore/include/filesystem/directory.h @@ -0,0 +1,50 @@ +// +// Created by 98max on 9/1/2025. +// + +#ifndef SYSCORE_FILESYSTEM_DIRECTORY_H +#define SYSCORE_FILESYSTEM_DIRECTORY_H + +#include +#include +#include +#include + + +namespace syscore{ + namespace filesystem{ + + enum class DirectoryFlags{ + READ_ENTRIES, + READ_ENTRIES_SIZE, + WRITE_NAME, + WRITE_NEW_FILE, + WRITE_NEW_DIR, + WRITE_REMOVE_FILE, + WRITE_REMOVE_DIR + }; + + typedef struct EntryInformation{ + size_t entry_length; + size_t size; + bool is_file; + char name[]; + } entry_information_t; + + uint64_t open_directory(const char* path); + void rename_directory(uint64_t handle, const char* name); + void close_directory(uint64_t handle); + + size_t directory_entries_size(uint64_t handle); + void directory_entries(uint64_t handle, void* buffer, size_t size); + + void new_file(uint64_t handle, const char* name); + void new_directory(uint64_t handle, const char* name); + + void remove_file(uint64_t handle, const char* name); + void remove_directory(uint64_t handle, const char* name); + + } +} + +#endif //SYSCORE_FILESYSTEM_DIRECTORY_H diff --git a/libraries/syscore/include/filesystem/file.h b/libraries/syscore/include/filesystem/file.h new file mode 100644 index 00000000..52f35b94 --- /dev/null +++ b/libraries/syscore/include/filesystem/file.h @@ -0,0 +1,46 @@ +// +// Created by 98max on 9/1/2025. +// + +#ifndef SYSCORE_FILESYSTEM_FILE_H +#define SYSCORE_FILESYSTEM_FILE_H + +#include +#include +#include +#include + +namespace syscore{ + namespace filesystem{ + + enum class FileFlags{ + DEFAULT, + READ_SIZE, + READ_OFFSET, + WRITE_SEEK_SET, + WRITE_SEEK_CUR, + WRITE_SEEK_END, + WRITE_NAME, + }; + + enum class SeekType{ + SET, + CURRENT, + END + }; + + uint64_t open_file(const char* path); + void close_file(uint64_t handle); + + size_t file_size(uint64_t handle); + size_t file_offset(uint64_t handle); + + size_t file_read(uint64_t handle, void* buffer, size_t size); + size_t file_write(uint64_t handle, void* buffer, size_t size); + + void rename_file(uint64_t handle, const char* name); + void seek_file(uint64_t handle, size_t position, SeekType seek_type); + } +} + +#endif //SYSCORE_FILESYSTEM_FILE_H diff --git a/libraries/syscore/include/ipc/messages.h b/libraries/syscore/include/ipc/messages.h new file mode 100644 index 00000000..cf42e5f1 --- /dev/null +++ b/libraries/syscore/include/ipc/messages.h @@ -0,0 +1,27 @@ +// +// Created by 98max on 8/31/2025. +// + +#ifndef SYSCORE_IPC_MESSAGES_H +#define SYSCORE_IPC_MESSAGES_H + +#include +#include +#include + +namespace syscore{ + namespace ipc{ + + uint64_t create_endpoint(const char* name); + uint64_t open_endpoint(const char* name); + void close_endpoint(uint64_t endpoint); + + void send_message(uint64_t endpoint, void* buffer, size_t size); + void read_message(uint64_t endpoint, void* buffer, size_t size); + } +} + + + + +#endif //IPC_MESSAGES_H diff --git a/libraries/syscore/include/ipc/sharedmemory.h b/libraries/syscore/include/ipc/sharedmemory.h new file mode 100644 index 00000000..3953517a --- /dev/null +++ b/libraries/syscore/include/ipc/sharedmemory.h @@ -0,0 +1,21 @@ +// +// Created by 98max on 8/31/2025. +// + +#ifndef SYSCORE_IPC_SHAREDMEMORY_H +#define SYSCORE_IPC_SHAREDMEMORY_H + +#include +#include +#include + +namespace syscore { + namespace ipc { + + void* create_shared_memory(const char* name, size_t size); + + void* open_shared_memory(const char* name); + } +} + +#endif //SYSCORE_IPC_SHAREDMEMORY_H diff --git a/libraries/system/include/syscalls.h b/libraries/syscore/include/syscalls.h similarity index 90% rename from libraries/system/include/syscalls.h rename to libraries/syscore/include/syscalls.h index 524ff483..30e4c5ce 100644 --- a/libraries/system/include/syscalls.h +++ b/libraries/syscore/include/syscalls.h @@ -2,17 +2,18 @@ // Created by 98max on 8/31/2025. // -#ifndef SYSTEM_SYSCALLS_H -#define SYSTEM_SYSCALLS_H +#ifndef SYSCORE_SYSCALLS_H +#define SYSCORE_SYSCALLS_H #include #include -namespace system{ +namespace syscore{ enum class ResourceType{ MESSAGE_ENDPOINT, - SHARED_MEMORY + SHARED_MEMORY, + FILESYSTEM, }; enum class ResourceErrorBase{ @@ -56,4 +57,4 @@ namespace system{ void thread_exit(); } -#endif //SYSTEM_SYSCALLS_H +#endif //SYSCORE_SYSCALLS_H diff --git a/libraries/syscore/src/filesystem/directory.cpp b/libraries/syscore/src/filesystem/directory.cpp new file mode 100644 index 00000000..d0fa5887 --- /dev/null +++ b/libraries/syscore/src/filesystem/directory.cpp @@ -0,0 +1,107 @@ +// +// Created by 98max on 9/1/2025. +// + +#include + +namespace syscore{ + namespace filesystem{ + + + /** + * @brief Opens a directory from a path (must end in /) + * + * @param path The path to the directory + * @return The handle of the opened directory or 0 if it failed + */ + uint64_t open_directory(const char* path){ + return resource_open(ResourceType::FILESYSTEM, path, 0); + } + + /** + * @brief Rename the directory + * + * @param handle The directory to rename + * @param name What to rename the directory to + */ + void rename_directory(uint64_t handle, const char* name){ + resource_write(handle, name, strlen(name), (size_t)DirectoryFlags::WRITE_NAME); + } + + /** + * @brief Closes an open directory + * + * @param handle The directory open + */ + void close_directory(uint64_t handle){ + resource_close(handle, 0); + } + + /** + * @brief Gets the size required to hold the list of directory entries + * + * @param handle The directory to list + * @return The size of the total entries + */ + size_t directory_entries_size(uint64_t handle){ + return resource_read(handle, 0,0, (size_t)DirectoryFlags::READ_ENTRIES_SIZE); + } + + /** + * @brief Reads the contents of a directory (as a list of entry_information_t) + * + * @param handle The directory to read + * @param buffer Where to read the list + * @param size How big is the buffer + */ + void directory_entries(uint64_t handle, void* buffer, size_t size){ + + resource_read(handle, buffer, size,(size_t)DirectoryFlags::READ_ENTRIES); + } + + /** + * @brief Creates a new file in the directory + * + * @param handle The directory to create the file in + * @param name The name of the new file + */ + void new_file(uint64_t handle, const char* name){ + + resource_write(handle, name, strlen(name), (size_t)DirectoryFlags::WRITE_NEW_FILE); + } + + /** + * @brief Creates a new subdirectory in the directory + * + * @param handle The directory to create the subdirectory in + * @param name The name of the new directory + */ + void new_directory(uint64_t handle, const char* name){ + + resource_write(handle, name, strlen(name), (size_t)DirectoryFlags::WRITE_NEW_DIR); + } + + /** + * @brief Removes a file from the directory + * + * @param handle The directory to remove the file from + * @param name The name of the file to remove + */ + void remove_file(uint64_t handle, const char* name){ + + resource_write(handle, name, strlen(name), (size_t)DirectoryFlags::WRITE_REMOVE_FILE); + } + + /** + * @brief Removes a subdirectory from the directory + * + * @param handle The directory to remove the subdirectory from + * @param name The name of the subdirectory to remove + */ + void remove_directory(uint64_t handle, const char* name){ + + resource_write(handle, name, strlen(name), (size_t)DirectoryFlags::WRITE_REMOVE_DIR); + } + + } +} \ No newline at end of file diff --git a/libraries/syscore/src/filesystem/file.cpp b/libraries/syscore/src/filesystem/file.cpp new file mode 100644 index 00000000..81f28191 --- /dev/null +++ b/libraries/syscore/src/filesystem/file.cpp @@ -0,0 +1,59 @@ +// +// Created by 98max on 9/1/2025. +// + +#include + +namespace syscore { + namespace filesystem { + + uint64_t open_file(const char* path){ + return resource_open(ResourceType::FILESYSTEM, path, 0); + } + + void close_file(uint64_t handle){ + resource_close(handle, 0); + } + + size_t file_size(uint64_t handle){ + return resource_read(handle, 0, 0, (size_t)FileFlags::READ_SIZE); + } + + size_t file_offset(uint64_t handle){ + return resource_read(handle, 0, 0, (size_t)FileFlags::READ_OFFSET); + } + + size_t file_read(uint64_t handle, void* buffer, size_t size){ + return resource_read(handle, buffer, size, (size_t)FileFlags::DEFAULT); + } + + size_t file_write(uint64_t handle, void* buffer, size_t size){ + return resource_write(handle, buffer, size, (size_t)FileFlags::DEFAULT); + } + + void rename_file(uint64_t handle, const char* name){ + + resource_write(handle, name, strlen(name), (size_t)FileFlags::WRITE_NAME); + } + + void seek_file(uint64_t handle, size_t position, SeekType seek_type){ + + switch (seek_type) { + + case SeekType::SET: + resource_write(handle, 0, position, (size_t)FileFlags::WRITE_SEEK_SET); + break; + + case SeekType::CURRENT: + resource_write(handle, 0, position, (size_t)FileFlags::WRITE_SEEK_CUR); + break; + + case SeekType::END: + resource_write(handle, 0, position, (size_t)FileFlags::WRITE_SEEK_END); + break; + + } + } + + } +} \ No newline at end of file diff --git a/libraries/syscore/src/ipc/messages.cpp b/libraries/syscore/src/ipc/messages.cpp new file mode 100644 index 00000000..6aa3f50a --- /dev/null +++ b/libraries/syscore/src/ipc/messages.cpp @@ -0,0 +1,73 @@ +// +// Created by 98max on 8/31/2025. +// + +#include + +namespace syscore{ + namespace ipc{ + + /** + * @brief Create a new endpoint for sending and receiving messages + * + * @param name The name of the endpoint + * @return The handle of the new endpoint or 0 if it failed + */ + uint64_t create_endpoint(const char* name){ + + // Create the resource + if(!resource_create(ResourceType::MESSAGE_ENDPOINT, name, 0)) + return 0; + + return open_endpoint(name); + } + + /** + * @brief Opens an endpoint under a specific name + * + * @param name The name of the endpoint to open + * @return The endpoint handle or 0 if it failed to open + */ + uint64_t open_endpoint(const char* name){ + return (uint64_t) resource_open(ResourceType::MESSAGE_ENDPOINT, name, 0); + } + + /** + * @brief Closes an endpoint + * + * @param endpoint The endpoint handle to close + */ + void close_endpoint(uint64_t endpoint){ + + if(endpoint) + resource_close(endpoint, 0); + + } + + /** + * @brief Queues a message on the endpoint (FIFO) + * + * @param endpoint The endpoint handle + * @param buffer The message + * @param size The size of the message + */ + void send_message(uint64_t endpoint, void* buffer, size_t size){ + + if(endpoint) + resource_write(endpoint, buffer, size, 0); + + } + + /** + * @brief Fetches the oldest message on the endpoint (FIFO) + * + * @param endpoint The endpoint handle + * @param buffer Where to read the message into + * @param size How much of the message to read + */ + void read_message(uint64_t endpoint, void* buffer, size_t size){ + if(endpoint) + resource_read(endpoint, buffer, size, 0); + } + } +} \ No newline at end of file diff --git a/libraries/syscore/src/ipc/sharedmemory.cpp b/libraries/syscore/src/ipc/sharedmemory.cpp new file mode 100644 index 00000000..530e3d5a --- /dev/null +++ b/libraries/syscore/src/ipc/sharedmemory.cpp @@ -0,0 +1,41 @@ +// +// Created by 98max on 8/31/2025. +// + +#include + +namespace syscore{ + namespace ipc{ + + /** + * @brief Create a new region of shared memory + * + * @param name The name of the region + * @param size The size of the region + * @return The start address of the region or nullptr if it failed to create + */ + void* create_shared_memory(const char* name, size_t size){ + + // Create the resource + if(!resource_create(ResourceType::SHARED_MEMORY, name, size)) + return nullptr; + + return open_shared_memory(name); + } + + void* open_shared_memory(const char* name){ + + // Open the resource + uint64_t handle = resource_open(ResourceType::SHARED_MEMORY, name, 0); + if(!handle) + return nullptr; + + // Get the address + auto address = resource_read(handle, nullptr, 0, 0); + if(!address) + return nullptr; + + return (void*)address; + } + }; +} diff --git a/libraries/system/src/syscalls.cpp b/libraries/syscore/src/syscalls.cpp similarity index 99% rename from libraries/system/src/syscalls.cpp rename to libraries/syscore/src/syscalls.cpp index 74fc449d..536ef3f2 100644 --- a/libraries/system/src/syscalls.cpp +++ b/libraries/syscore/src/syscalls.cpp @@ -4,7 +4,7 @@ #include -namespace system{ +namespace syscore{ int as_error(ResourceErrorBase code){ return -1 * (int)code; diff --git a/programs/Example/CMakeLists.txt b/programs/Example/CMakeLists.txt index d6ddf594..02d8e382 100644 --- a/programs/Example/CMakeLists.txt +++ b/programs/Example/CMakeLists.txt @@ -12,7 +12,7 @@ ADD_EXECUTABLE(test.elf ${PROJ_SRCS}) # Set the linker info SET(LINKER_SCRIPT ${CMAKE_SOURCE_DIR}/programs/Example/src/linker.ld) SET_TARGET_PROPERTIES(test.elf PROPERTIES LINK_DEPENDS ${LINKER_SCRIPT}) -TARGET_LINK_LIBRARIES(test.elf gcc ipc_static) +TARGET_LINK_LIBRARIES(test.elf gcc syscore_static) TARGET_LINK_OPTIONS(test.elf PRIVATE -T ${LINKER_SCRIPT} -nostdlib -n) # Install the executable diff --git a/programs/Example/src/main.cpp b/programs/Example/src/main.cpp index 6bf5a739..b582ae37 100644 --- a/programs/Example/src/main.cpp +++ b/programs/Example/src/main.cpp @@ -4,8 +4,11 @@ #include #include #include +#include -using namespace IPC; +using namespace syscore; +using namespace syscore::ipc; +using namespace syscore::filesystem; // Write using a syscall (int 0x80 with syscall 0x01 for write) void write(const char* data) { @@ -44,27 +47,35 @@ extern "C" [[noreturn]] void _start(void) { // Write to the console write("MaxOS Test Program v3\n"); - // Create a message endpoint - uint64_t queue = create_endpoint("TestQueue"); - if (!queue) { - write("Failed to create message queue\n"); - while (true) - yield(); - } - - write("Message queue created: \n"); - write_hex(queue); - - // Process events forever: - write("Waiting for messages\n"); - uint8_t message[255] = {}; + uint64_t fd = open_file("/test/a.txt"); + write("fd: \n"); + write_hex(fd); - while (true){ - - // Store the message - read_message(queue, message, 255); - write("Message received: \n"); - write((char*)message); + // Read Tests +// uint8_t buffer[50] = {}; +// file_read(fd, buffer, 50); +// write("contents: \n"); +// write((char*)buffer); +// write("%h\n"); +// +// write("offset: \n"); +// write_hex(file_offset(fd)); +// +// write("size: \n"); +// write_hex(file_size(fd)); +// +// write("new offset: \n"); +// seek_file(fd, 0, SeekType::SET); +// write_hex(file_offset(fd)); + + // Write tests +// const char* message = "Heyyyy kernel"; +// write("writing: \n"); +// file_write(fd, (void*)message, strlen(message)); +// +// write("renaming: \n"); +// rename_file(fd, "b.txt"); - } + while (true) + yield(); } \ No newline at end of file diff --git a/programs/Example2/CMakeLists.txt b/programs/Example2/CMakeLists.txt index 22f75e65..0ee4de51 100644 --- a/programs/Example2/CMakeLists.txt +++ b/programs/Example2/CMakeLists.txt @@ -10,7 +10,7 @@ ADD_EXECUTABLE(test1.elf ${PROJ_SRCS}) # Set the linker info SET(LINKER_SCRIPT ${CMAKE_SOURCE_DIR}/programs/Example/src/linker.ld) SET_TARGET_PROPERTIES(test1.elf PROPERTIES LINK_DEPENDS ${LINKER_SCRIPT}) -TARGET_LINK_LIBRARIES(test1.elf gcc ipc_static) +TARGET_LINK_LIBRARIES(test1.elf gcc syscore_static) TARGET_LINK_OPTIONS(test1.elf PRIVATE -T ${LINKER_SCRIPT} -nostdlib -n) # Install the executable diff --git a/programs/Example2/src/main.cpp b/programs/Example2/src/main.cpp index 3fe677b7..82257ebb 100644 --- a/programs/Example2/src/main.cpp +++ b/programs/Example2/src/main.cpp @@ -4,8 +4,12 @@ #include #include #include +#include +#include -using namespace IPC; +using namespace syscore; +using namespace syscore::ipc; +using namespace syscore::filesystem; // Write using a syscall (int 0x80 with syscall 0x01 for write) void write(const char* data, uint64_t length = 0) @@ -66,32 +70,38 @@ void wait(uint64_t ms) extern "C" int _start(int argc, char** argv) { + // Write to the console + write("MaxOS Test Program v3\n"); - // Print the args - write("Args:\n"); - for(int i = 0; i < argc; i++) - { - - // Write the arg - write(argv[i]); - } - write("%h\n"); - - // Write to the console - write("MaxOS Test Program v3.1\n"); + uint64_t dd = open_directory("/test/abc/"); + write("dd: \n"); + write_hex(dd); - const char* message = "Message from process 2\n"; - - // Wait 4seconds - wait(1000); - write("Waited 1 seconds\n"); + // Read Tests +// write("size: \n"); +// write_hex(directory_entries_size(dd)); +// +// uint8_t buffer[163]; +// directory_entries(dd, buffer, 163); +// size_t offset = 0; +// while (offset < 163) +// { +// auto* entry = (entry_information_t*)(buffer + offset); +// write(entry->name); +// write("%h\n"); +// offset += entry->entry_length; +// } + + // Write Tests +// new_file(dd, "c.txt"); +// new_directory(dd, "sub"); +// +// remove_file(dd, "c.txt"); +// remove_directory(dd, "sub"); - // Send a message via IPC - uint64_t endpoint = open_endpoint("TestQueue"); - send_message(endpoint, (void*)message, 24); +// rename_directory(dd, "def"); - // Close the process - write("Closing process\n"); - close(); + while (true) + thread_yield(); } \ No newline at end of file From 5c17ff1900c2106084e53be42544d827fcee48c6 Mon Sep 17 00:00:00 2001 From: Max Tyson <98maxt98@gmail.com> Date: Tue, 2 Sep 2025 13:38:12 +1200 Subject: [PATCH 37/38] More Path stuff --- kernel/include/filesystem/filesystem.h | 20 +-- kernel/include/filesystem/path.h | 39 +++++ kernel/include/filesystem/vfsresource.h | 1 + kernel/include/processes/process.h | 2 + kernel/src/common/string.cpp | 21 ++- kernel/src/filesystem/filesystem.cpp | 149 ----------------- kernel/src/filesystem/path.cpp | 210 ++++++++++++++++++++++++ kernel/src/filesystem/vfsresource.cpp | 22 ++- kernel/src/kernel.cpp | 4 +- 9 files changed, 282 insertions(+), 186 deletions(-) create mode 100644 kernel/include/filesystem/path.h create mode 100644 kernel/src/filesystem/path.cpp diff --git a/kernel/include/filesystem/filesystem.h b/kernel/include/filesystem/filesystem.h index 9638045f..67008069 100644 --- a/kernel/include/filesystem/filesystem.h +++ b/kernel/include/filesystem/filesystem.h @@ -10,6 +10,7 @@ #include #include #include +#include #include namespace MaxOS{ @@ -20,25 +21,6 @@ namespace MaxOS{ typedef uint32_t lba_t; typedef syscore::filesystem::SeekType SeekType; - /** - * @class Path - * @brief Handles file & directory paths - */ - class Path - { - public: - static bool valid(string path); - static bool is_file(const string& path); - - static string file_name(string path); - static string file_extension(string path); - static string file_path(string path); - - static string top_directory(string path); - static string parent_directory(string path); - - }; - /** * @class File * @brief Handles file operations and information diff --git a/kernel/include/filesystem/path.h b/kernel/include/filesystem/path.h new file mode 100644 index 00000000..e612f0d8 --- /dev/null +++ b/kernel/include/filesystem/path.h @@ -0,0 +1,39 @@ +// +// Created by 98max on 9/2/2025. +// + +#ifndef MAXOS_FILESYSTEM_PATH_H +#define MAXOS_FILESYSTEM_PATH_H + +#include + +namespace MaxOS { + + namespace filesystem { + + /** + * @class Path + * @brief Handles file & directory paths + */ + class Path + { + public: + static bool valid(string path); + static bool is_file(const string& path); + + static string file_name(string path); + static string file_extension(string path); + static string file_path(string path); + + static string top_directory(string path); + static string parent_directory(string path); + + static string absolute_path(string path); + static string join_path(string base, string extended); + }; + + + } +} + +#endif //MAXOS_FILESYSTEM_PATH_H diff --git a/kernel/include/filesystem/vfsresource.h b/kernel/include/filesystem/vfsresource.h index fb59157c..ede19f8a 100644 --- a/kernel/include/filesystem/vfsresource.h +++ b/kernel/include/filesystem/vfsresource.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include diff --git a/kernel/include/processes/process.h b/kernel/include/processes/process.h index b035eca7..a0903c19 100644 --- a/kernel/include/processes/process.h +++ b/kernel/include/processes/process.h @@ -100,6 +100,8 @@ namespace MaxOS bool is_kernel; string name; + string working_directory = "/"; + memory::MemoryManager* memory_manager = nullptr; ResourceManager resource_manager; diff --git a/kernel/src/common/string.cpp b/kernel/src/common/string.cpp index 0b14547a..b898bf8c 100644 --- a/kernel/src/common/string.cpp +++ b/kernel/src/common/string.cpp @@ -284,20 +284,27 @@ common::Vector String::split(String const &delimiter) const { // Go through the string and split it by the delimiter int start = 0; - int end = 0; - for (; end < m_length; end++) { + for (int i = 0; i <= m_length - delimiter.length(); i++) { - // Not splitting - if (m_string[end] != delimiter[0]) + // Check if matches at this position + bool matches = true; + for (int j = 0; j < delimiter.length(); j++) + if (m_string[i + j] != delimiter[j]) { + matches = false; + break; + } + + if(!matches) continue; // Add the splice of the string - strings.push_back(substring(start, end - start)); - start = end + 1; + strings.push_back(substring(start, i - start)); + start = i + delimiter.length(); + i += delimiter.length() - 1; } // Add the last string to the vector - strings.push_back(substring(start, end - start)); + strings.push_back(substring(start, m_length - start)); return strings; } diff --git a/kernel/src/filesystem/filesystem.cpp b/kernel/src/filesystem/filesystem.cpp index 2401de28..d7c2c075 100644 --- a/kernel/src/filesystem/filesystem.cpp +++ b/kernel/src/filesystem/filesystem.cpp @@ -9,155 +9,6 @@ using namespace MaxOS; using namespace MaxOS::filesystem; using namespace MaxOS::common; -/** - * @brief Check if a path is valid - * - * @param path The path to check - * @return True if the path is valid, false otherwise - */ -bool Path::valid(string path) { - - // Must not be empty - if (path.length() == 0) - return false; - - // Must start with a / - if (path[0] != '/') - return false; - - // Valid - return true; - -} - -/** - * @brief Check if a path is a file - * - * @param path The path to check - * @return True if the path is a file, false otherwise - */ -bool Path::is_file(const string& path) { - - return file_name(path).length() > 0 && file_extension(path).length() > 0; -} - - -/** - * @brief Get the file name component of a path if it exists or an empty string - * - * @param path The path to get the file name from - * @return The file name or the original path if it does not exist - */ -string Path::file_name(string path) { - - // Find the last / - int last_slash = -1; - for (int i = 0; i < path.length(); i++) - if (path[i] == '/') - last_slash = i; - - // Make sure there was a slash to split - if (last_slash == -1) - return path; - - // Get the file name - string file_name = path.substring(last_slash + 1, path.length() - last_slash - 1); - return file_name; -} - -/** - * @brief Try to get the file extension from a path (assumes split by ".") - * - * @param path The path to get the file extension from - * @return The file extension or the original path if it does not exist - */ -string Path::file_extension(string path) { - - // Find the last . - int last_dot = -1; - for (int i = 0; i < path.length(); i++) - if (path[i] == '.') - last_dot = i; - - // Make sure there was a dot to split - if (last_dot == -1) - return ""; - - // Get the file extension (what is after the ".") - string file_extension = path.substring(last_dot + 1, path.length() - last_dot - 1); - return file_extension; - -} - -/** - * @brief Finds the path to the directory the file is in - * - * @param path The path to get the file path from - * @return The file path or the original path if it does not exist - */ -string Path::file_path(string path) { - - // Try to find the last / - int last_slash = -1; - for (int i = 0; i < path.length(); i++) - if (path[i] == '/') - last_slash = i; - - // Make sure there was a slash to split - if (last_slash == -1) - return path; - - // Get the file path - string file_path = path.substring(0, last_slash); - return file_path; - -} - -/** - * @brief Get the top directory of a path - * - * @param path The path to get the top directory from - * @return The top directory or the original path if it does not exist - */ -string Path::top_directory(string path) { - - // Find the first / - int first_slash = -1; - for (int i = 0; i < path.length(); i++) - if (path[i] == '/') - first_slash = i; - - // Make sure there was a slash to split - if (first_slash == -1) - return path; - - // Get the top directory - string top_directory = path.substring(0, first_slash); - return top_directory; -} - -/** - * @brief Get the parent directory of the path - * - * @param path The path to either the file or the directory - * @return - */ -string Path::parent_directory(string path) { - - // Find the last / - int last_slash = -1; - for (int i = 0; i < path.length(); i++) - if (path[i] == '/') - last_slash = i; - - // If no slash or only root, return empty string - if (last_slash <= 0) - return "/"; - - // Return substring up to last slash - return path.substring(0, last_slash); -} - File::File() = default; File::~File() = default; diff --git a/kernel/src/filesystem/path.cpp b/kernel/src/filesystem/path.cpp new file mode 100644 index 00000000..89354538 --- /dev/null +++ b/kernel/src/filesystem/path.cpp @@ -0,0 +1,210 @@ +// +// Created by 98max on 9/2/2025. +// +#include + +using namespace MaxOS; +using namespace MaxOS::common; +using namespace MaxOS::filesystem; + +/** + * @brief Check if a path is valid + * + * @param path The path to check + * @return True if the path is valid, false otherwise + */ +bool Path::valid(string path) { + + // Must not be empty + if (path.length() == 0) + return false; + + // Must start with a / + if (path[0] != '/') + return false; + + // Valid + return true; + +} + +/** + * @brief Check if a path is a file + * + * @param path The path to check + * @return True if the path is a file, false otherwise + */ +bool Path::is_file(const string& path) { + + return file_name(path).length() > 0 && file_extension(path).length() > 0; +} + + +/** + * @brief Get the file name component of a path if it exists or an empty string + * + * @param path The path to get the file name from + * @return The file name or the original path if it does not exist + */ +string Path::file_name(string path) { + + // Find the last / + int last_slash = -1; + for (int i = 0; i < path.length(); i++) + if (path[i] == '/') + last_slash = i; + + // Make sure there was a slash to split + if (last_slash == -1) + return path; + + // Get the file name + string file_name = path.substring(last_slash + 1, path.length() - last_slash - 1); + return file_name; +} + +/** + * @brief Try to get the file extension from a path (assumes split by ".") + * + * @param path The path to get the file extension from + * @return The file extension or the original path if it does not exist + */ +string Path::file_extension(string path) { + + // Find the last . + int last_dot = -1; + for (int i = 0; i < path.length(); i++) + if (path[i] == '.') + last_dot = i; + + // Make sure there was a dot to split + if (last_dot == -1) + return ""; + + // Get the file extension (what is after the ".") + string file_extension = path.substring(last_dot + 1, path.length() - last_dot - 1); + return file_extension; + +} + +/** + * @brief Finds the path to the directory the file is in + * + * @param path The path to get the file path from + * @return The file path or the original path if it does not exist + */ +string Path::file_path(string path) { + + // Try to find the last / + int last_slash = -1; + for (int i = 0; i < path.length(); i++) + if (path[i] == '/') + last_slash = i; + + // Make sure there was a slash to split + if (last_slash == -1) + return path; + + // Get the file path + string file_path = path.substring(0, last_slash); + return file_path; + +} + +/** + * @brief Get the top directory of a path + * + * @param path The path to get the top directory from + * @return The top directory or the original path if it does not exist + */ +string Path::top_directory(string path) { + + // Find the first / + int first_slash = -1; + for (int i = 0; i < path.length(); i++) + if (path[i] == '/') + first_slash = i; + + // Make sure there was a slash to split + if (first_slash == -1) + return path; + + // Get the top directory + string top_directory = path.substring(0, first_slash); + return top_directory; +} + +/** + * @brief Get the parent directory of the path + * + * @param path The path to either the file or the directory + * @return + */ +string Path::parent_directory(string path) { + + // Find the last / + int last_slash = -1; + for (int i = 0; i < path.length(); i++) + if (path[i] == '/') + last_slash = i; + + // If no slash or only root, return empty string + if (last_slash <= 0) + return "/"; + + // Return substring up to last slash + return path.substring(0, last_slash); +} + +/** + * @brief Calculates path with out any "../" or "./" + * + * @param path The path + * @return The new path, direct from root + */ +string Path::absolute_path(string path) { + + // Split the path into components + auto components = path.split("/"); + common::Vector absolute_components; + for (auto &component: components) { + + // Skip empty or self references + if (component == "" || component == ".") + continue; + + // Handle parent directory references + if (component == "..") { + if (!absolute_components.empty()) + absolute_components.pop_back(); + } else + absolute_components.push_back(component); + } + + // Join the components back into a path + string absolute_path = "/"; + for(const auto& component : absolute_components) + absolute_path += component + "/"; + + // Remove trailing slash if file + if(Path::is_file(absolute_path) && absolute_path.length() > 1) + absolute_path = absolute_path.substring(0, absolute_path.length() - 1); + + return absolute_path; +} + +/** + * @brief Joins two paths together + * + * @param base The base path + * @param extended What to add to the base path (if its from root, "/", then it will just return this) + * @return The joint path + */ +string Path::join_path(string base, string extended) { + + // The new path is from root + if(extended[0] == '/') + return extended; + + return base + extended; +} diff --git a/kernel/src/filesystem/vfsresource.cpp b/kernel/src/filesystem/vfsresource.cpp index dba793a9..8f520f33 100644 --- a/kernel/src/filesystem/vfsresource.cpp +++ b/kernel/src/filesystem/vfsresource.cpp @@ -351,31 +351,37 @@ Resource* VFSResourceRegistry::open_as_resource(string const& name, File* file) Resource* VFSResourceRegistry::get_resource(string const& name) { + string path = Path::absolute_path(name); + path = Path::join_path(Scheduler::current_process()->working_directory, path); + // Resource already opened - auto resource = BaseResourceRegistry::get_resource(name); + auto resource = BaseResourceRegistry::get_resource(path); if(resource != nullptr) return resource; // Open the resource - bool is_file = Path::is_file(name); + bool is_file = Path::is_file(path); if(is_file) - return open_as_resource(name, m_vfs->open_file(name)); + return open_as_resource(path, m_vfs->open_file(path)); else - return open_as_resource(name, m_vfs->open_directory(name)); + return open_as_resource(path, m_vfs->open_directory(path)); } Resource* VFSResourceRegistry::create_resource(string const& name, size_t flags) { + string path = Path::absolute_path(name); + path = Path::join_path(Scheduler::current_process()->working_directory, path); + // Resource already opened - auto resource = BaseResourceRegistry::get_resource(name); + auto resource = BaseResourceRegistry::get_resource(path); if(resource != nullptr) return resource; // Open the resource - bool is_file = Path::is_file(name); + bool is_file = Path::is_file(path); if(is_file) - return open_as_resource(name, m_vfs->create_file(name)); + return open_as_resource(path, m_vfs->create_file(path)); else - return open_as_resource(name, m_vfs->create_directory(name)); + return open_as_resource(path, m_vfs->create_directory(path)); } \ No newline at end of file diff --git a/kernel/src/kernel.cpp b/kernel/src/kernel.cpp index b51089be..f2c9a988 100644 --- a/kernel/src/kernel.cpp +++ b/kernel/src/kernel.cpp @@ -81,11 +81,9 @@ extern "C" [[noreturn]] void kernel_main(unsigned long addr, unsigned long magic asm("nop"); } -// TODO: -// - Userspace Files (proper path handling, working directories) - // TODO: // - SMP +// - Test suite of common functions & other statics (paths) // - Class & Struct docstrings // - Logo on fail in center // - Sanitize syscall input \ No newline at end of file From 0ed9fa31b95cde20f694851789b08fe768efe610 Mon Sep 17 00:00:00 2001 From: Max Tyson <98maxt98@gmail.com> Date: Tue, 2 Sep 2025 14:08:02 +1200 Subject: [PATCH 38/38] Pre merge fixes --- README.md | 2 +- kernel/src/memory/physical.cpp | 2 +- kernel/src/system/syscalls.cpp | 2 +- libraries/syscore/include/common.h | 4 ++++ 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d1b9edd4..f98bdcb2 100644 --- a/README.md +++ b/README.md @@ -211,7 +211,7 @@ No user usage so far (userland will be added in the future) - [x] Paging - [x] Userspace - [x] IPC -- [ ] VFS +- [x] VFS - [x] Loading ELF - [ ] Multiple Cores Support (SMP & Scheduler) - [ ] Move drivers to userspace diff --git a/kernel/src/memory/physical.cpp b/kernel/src/memory/physical.cpp index c606a74b..8b5a2841 100644 --- a/kernel/src/memory/physical.cpp +++ b/kernel/src/memory/physical.cpp @@ -724,7 +724,7 @@ void PhysicalMemoryManager::initialise_bit_map() { } // Make sure there is enough space - ASSERT(space >= (m_bitmap_size / 8 + 1), "Not enough space for the bitmap (to big)\n"); + ASSERT(space >= (m_bitmap_size / 8 + 1), "Not enough space for the bitmap (too big)\n"); // Return the address (ensuring that it is in the safe region) m_bit_map = (uint64_t*) to_dm_region(entry->addr + offset); diff --git a/kernel/src/system/syscalls.cpp b/kernel/src/system/syscalls.cpp index 7320ff58..a3fc57d1 100644 --- a/kernel/src/system/syscalls.cpp +++ b/kernel/src/system/syscalls.cpp @@ -5,7 +5,7 @@ #include #include -using namespace ::syscore; +using namespace syscore; using namespace MaxOS; using namespace MaxOS::common; using namespace MaxOS::hardwarecommunication; diff --git a/libraries/syscore/include/common.h b/libraries/syscore/include/common.h index 537b469e..b7bb7412 100644 --- a/libraries/syscore/include/common.h +++ b/libraries/syscore/include/common.h @@ -14,6 +14,10 @@ namespace syscore{ * @return The length of the string */ inline int strlen(const char *str) { + + if(str == nullptr) + return 0; + int len = 0; for (; str[len] != '\0'; len++); return len;