Kernel: Rework whole Terminal structure
Serial monitors can now be used as a output. This requires editing init code for the stdio opening. Serial input is not supported, so qemu still needs graphical window for ps/2 keyboard.
This commit is contained in:
		
							parent
							
								
									9b47603a1d
								
							
						
					
					
						commit
						55714b90cd
					
				|  | @ -44,7 +44,6 @@ set(KERNEL_SOURCES | |||
| 	kernel/Process.cpp | ||||
| 	kernel/Scheduler.cpp | ||||
| 	kernel/Semaphore.cpp | ||||
| 	kernel/Serial.cpp | ||||
| 	kernel/SpinLock.cpp | ||||
| 	kernel/SSP.cpp | ||||
| 	kernel/Storage/ATABus.cpp | ||||
|  | @ -54,8 +53,10 @@ set(KERNEL_SOURCES | |||
| 	kernel/Storage/StorageDevice.cpp | ||||
| 	kernel/Syscall.cpp | ||||
| 	kernel/Syscall.S | ||||
| 	kernel/Terminal/Serial.cpp | ||||
| 	kernel/Terminal/TTY.cpp | ||||
| 	kernel/Terminal/VesaTerminalDriver.cpp | ||||
| 	kernel/Terminal/VirtualTTY.cpp | ||||
| 	kernel/Thread.cpp | ||||
| 	kernel/Timer/HPET.cpp | ||||
| 	kernel/Timer/PIT.cpp | ||||
|  |  | |||
|  | @ -16,7 +16,7 @@ namespace Kernel | |||
| 	 | ||||
| 		void add_device(BAN::StringView path, BAN::RefPtr<Device>); | ||||
| 
 | ||||
| 		dev_t get_next_rdev(); | ||||
| 		dev_t get_next_dev(); | ||||
| 
 | ||||
| 	private: | ||||
| 		DevFileSystem(size_t size) | ||||
|  |  | |||
|  | @ -1,28 +0,0 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <BAN/Errors.h> | ||||
| 
 | ||||
| namespace Kernel | ||||
| { | ||||
| 
 | ||||
| 	class Serial | ||||
| 	{ | ||||
| 	public: | ||||
| 		static void initialize(); | ||||
| 		static bool has_devices(); | ||||
| 		static void putchar_any(char); | ||||
| 
 | ||||
| 		void putchar(char); | ||||
| 
 | ||||
| 	private: | ||||
| 		static bool port_has_device(uint16_t); | ||||
| 
 | ||||
| 		bool is_transmit_empty() const; | ||||
| 
 | ||||
| 		bool is_valid() const { return m_port != 0; } | ||||
| 
 | ||||
| 	private: | ||||
| 		uint16_t m_port { 0 }; | ||||
| 	}; | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,58 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <BAN/Errors.h> | ||||
| #include <kernel/Terminal/TTY.h> | ||||
| 
 | ||||
| namespace Kernel | ||||
| { | ||||
| 
 | ||||
| 	class Serial | ||||
| 	{ | ||||
| 	public: | ||||
| 		static void initialize(); | ||||
| 		static bool has_devices(); | ||||
| 		static void putchar_any(char); | ||||
| 
 | ||||
| 		static void initialize_devices(); | ||||
| 
 | ||||
| 		void putchar(char); | ||||
| 		char getchar(); | ||||
| 
 | ||||
| 		uint16_t port() const { return m_port; } | ||||
| 		uint32_t width() const { return m_width; } | ||||
| 		uint32_t height() const { return m_height; } | ||||
| 
 | ||||
| 	private: | ||||
| 		static bool port_has_device(uint16_t); | ||||
| 		bool initialize_size(); | ||||
| 		bool is_valid() const { return m_port != 0; } | ||||
| 
 | ||||
| 	private: | ||||
| 		uint16_t m_port { 0 }; | ||||
| 		uint32_t m_width { 0 }; | ||||
| 		uint32_t m_height { 0 }; | ||||
| 	}; | ||||
| 
 | ||||
| 	class SerialTTY final : public TTY | ||||
| 	{ | ||||
| 	public: | ||||
| 		static BAN::ErrorOr<BAN::RefPtr<SerialTTY>> create(Serial); | ||||
| 		 | ||||
| 		virtual uint32_t width() const override; | ||||
| 		virtual uint32_t height() const override; | ||||
| 		virtual void putchar(uint8_t) override; | ||||
| 	 | ||||
| 	private: | ||||
| 		SerialTTY(Serial); | ||||
| 		bool initialize(); | ||||
| 
 | ||||
| 	private: | ||||
| 		Serial m_serial; | ||||
| 	 | ||||
| 	public: | ||||
| 		virtual dev_t rdev() const override { return m_rdev; } | ||||
| 	private: | ||||
| 		const dev_t m_rdev; | ||||
| 	}; | ||||
| 
 | ||||
| } | ||||
|  | @ -10,18 +10,13 @@ | |||
| 
 | ||||
| namespace Kernel | ||||
| { | ||||
| 	 | ||||
| 
 | ||||
| 	class TTY : public CharacterDevice | ||||
| 	{ | ||||
| 	public: | ||||
| 		TTY(TerminalDriver*); | ||||
| 
 | ||||
| 		void set_termios(const termios& termios) { m_termios = termios; } | ||||
| 		termios get_termios() const { return m_termios; }	 | ||||
| 		void set_font(const Kernel::Font&); | ||||
| 
 | ||||
| 		uint32_t height() const { return m_height; } | ||||
| 		uint32_t width() const { return m_width; } | ||||
| 		virtual void set_font(const Font&) {}; | ||||
| 
 | ||||
| 		void set_foreground_pgrp(pid_t pgrp) { m_foreground_pgrp = pgrp; } | ||||
| 		pid_t foreground_pgrp() const { return m_foreground_pgrp; } | ||||
|  | @ -30,80 +25,40 @@ namespace Kernel | |||
| 		static void putchar_current(uint8_t ch); | ||||
| 		static bool is_initialized(); | ||||
| 		static BAN::RefPtr<TTY> current(); | ||||
| 		void set_as_current(); | ||||
| 
 | ||||
| 		void initialize_device(); | ||||
| 		static void initialize_devices(); | ||||
| 		void on_key_event(Input::KeyEvent); | ||||
| 
 | ||||
| 		virtual bool is_tty() const override { return true; } | ||||
| 
 | ||||
| 		virtual BAN::ErrorOr<size_t> read(size_t, void*, size_t) override; | ||||
| 		virtual BAN::ErrorOr<size_t> write(size_t, const void*, size_t) override; | ||||
| 		virtual bool has_data() const override; | ||||
| 
 | ||||
| 		virtual uint32_t height() const = 0; | ||||
| 		virtual uint32_t width() const = 0; | ||||
| 		virtual void putchar(uint8_t ch) = 0; | ||||
| 
 | ||||
| 		bool has_data() const; | ||||
| 
 | ||||
| 	protected: | ||||
| 		TTY(mode_t mode, uid_t uid, gid_t gid) | ||||
| 			: CharacterDevice(mode, uid, gid) | ||||
| 		{ } | ||||
| 
 | ||||
| 	private: | ||||
| 		void clear(); | ||||
| 		void putchar(uint8_t ch); | ||||
| 		void reset_ansi(); | ||||
| 		void handle_ansi_csi(uint8_t ch); | ||||
| 		void handle_ansi_csi_color(); | ||||
| 		void putchar_at(uint32_t codepoint, uint32_t x, uint32_t y); | ||||
| 		void render_from_buffer(uint32_t x, uint32_t y); | ||||
| 		void set_cursor_position(uint32_t x, uint32_t y); | ||||
| 
 | ||||
| 		void on_key(Input::KeyEvent); | ||||
| 		void do_backspace(); | ||||
| 
 | ||||
| 	private: | ||||
| 		enum class State | ||||
| 		{ | ||||
| 			Normal, | ||||
| 			WaitingAnsiEscape, | ||||
| 			WaitingAnsiCSI, | ||||
| 			WaitingUTF8, | ||||
| 		}; | ||||
| 
 | ||||
| 		struct AnsiState | ||||
| 		{ | ||||
| 			int32_t nums[2]	{ -1, -1 }; | ||||
| 			int32_t index { 0 }; | ||||
| 			bool question { false }; | ||||
| 		}; | ||||
| 
 | ||||
| 		struct UTF8State | ||||
| 		{ | ||||
| 			uint32_t codepoint { 0 }; | ||||
| 			uint8_t bytes_missing { 0 }; | ||||
| 		}; | ||||
| 
 | ||||
| 		struct Cell | ||||
| 		{ | ||||
| 			TerminalDriver::Color foreground { TerminalColor::BRIGHT_WHITE }; | ||||
| 			TerminalDriver::Color background { TerminalColor::BLACK }; | ||||
| 			uint32_t codepoint { ' ' }; | ||||
| 		}; | ||||
| 
 | ||||
| 	private: | ||||
| 	protected: | ||||
| 		mutable Kernel::SpinLock m_lock; | ||||
| 
 | ||||
| 		State m_state { State::Normal }; | ||||
| 		AnsiState m_ansi_state { }; | ||||
| 		UTF8State m_utf8_state { }; | ||||
| 
 | ||||
| 		uint32_t m_width { 0 }; | ||||
| 		uint32_t m_height { 0 }; | ||||
| 
 | ||||
| 		uint32_t m_saved_row { 0 }; | ||||
| 		uint32_t m_saved_column { 0 }; | ||||
| 
 | ||||
| 		uint32_t m_row { 0 }; | ||||
| 		uint32_t m_column { 0 }; | ||||
| 		Cell* m_buffer { nullptr }; | ||||
| 		bool m_show_cursor { true }; | ||||
| 
 | ||||
| 		TerminalDriver::Color m_foreground { TerminalColor::BRIGHT_WHITE }; | ||||
| 		TerminalDriver::Color m_background { TerminalColor::BLACK }; | ||||
| 
 | ||||
| 		pid_t m_foreground_pgrp { 0 }; | ||||
| 
 | ||||
| 		termios m_termios; | ||||
| 
 | ||||
| 	private: | ||||
| 		pid_t m_foreground_pgrp { 0 }; | ||||
| 
 | ||||
| 		struct Buffer | ||||
| 		{ | ||||
| 			BAN::Array<uint8_t, 1024> buffer; | ||||
|  | @ -112,15 +67,6 @@ namespace Kernel | |||
| 			Semaphore semaphore; | ||||
| 		}; | ||||
| 		Buffer m_output; | ||||
| 
 | ||||
| 		TerminalDriver* m_terminal_driver { nullptr }; | ||||
| 
 | ||||
| 	public: | ||||
| 		virtual dev_t rdev() const override { return m_rdev; } | ||||
| 		virtual bool is_tty() const override { return true; } | ||||
| 
 | ||||
| 	private: | ||||
| 		dev_t m_rdev; | ||||
| 	}; | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,90 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <BAN/Array.h> | ||||
| #include <kernel/Device/Device.h> | ||||
| #include <kernel/Input/KeyEvent.h> | ||||
| #include <kernel/SpinLock.h> | ||||
| #include <kernel/Terminal/TerminalDriver.h> | ||||
| #include <kernel/Terminal/termios.h> | ||||
| #include <kernel/Terminal/TTY.h> | ||||
| #include <kernel/Semaphore.h> | ||||
| 
 | ||||
| namespace Kernel | ||||
| { | ||||
| 	 | ||||
| 	class VirtualTTY : public TTY | ||||
| 	{ | ||||
| 	public: | ||||
| 		static BAN::ErrorOr<BAN::RefPtr<VirtualTTY>> create(TerminalDriver*); | ||||
| 
 | ||||
| 		virtual void set_font(const Font&) override; | ||||
| 
 | ||||
| 		virtual uint32_t height() const override { return m_height; } | ||||
| 		virtual uint32_t width() const override { return m_width; } | ||||
| 		virtual void putchar(uint8_t ch) override; | ||||
| 
 | ||||
| 	private: | ||||
| 		VirtualTTY(TerminalDriver*); | ||||
| 
 | ||||
| 		void clear(); | ||||
| 		void reset_ansi(); | ||||
| 		void handle_ansi_csi(uint8_t ch); | ||||
| 		void handle_ansi_csi_color(); | ||||
| 		void putchar_at(uint32_t codepoint, uint32_t x, uint32_t y); | ||||
| 		void render_from_buffer(uint32_t x, uint32_t y); | ||||
| 		void set_cursor_position(uint32_t x, uint32_t y); | ||||
| 
 | ||||
| 	private: | ||||
| 		enum class State | ||||
| 		{ | ||||
| 			Normal, | ||||
| 			WaitingAnsiEscape, | ||||
| 			WaitingAnsiCSI, | ||||
| 			WaitingUTF8, | ||||
| 		}; | ||||
| 
 | ||||
| 		struct AnsiState | ||||
| 		{ | ||||
| 			int32_t nums[2]	{ -1, -1 }; | ||||
| 			int32_t index { 0 }; | ||||
| 			bool question { false }; | ||||
| 		}; | ||||
| 
 | ||||
| 		struct UTF8State | ||||
| 		{ | ||||
| 			uint32_t codepoint { 0 }; | ||||
| 			uint8_t bytes_missing { 0 }; | ||||
| 		}; | ||||
| 
 | ||||
| 		struct Cell | ||||
| 		{ | ||||
| 			TerminalDriver::Color foreground { TerminalColor::BRIGHT_WHITE }; | ||||
| 			TerminalDriver::Color background { TerminalColor::BLACK }; | ||||
| 			uint32_t codepoint { ' ' }; | ||||
| 		}; | ||||
| 
 | ||||
| 	private: | ||||
| 		State m_state { State::Normal }; | ||||
| 		AnsiState m_ansi_state { }; | ||||
| 		UTF8State m_utf8_state { }; | ||||
| 
 | ||||
| 		uint32_t m_width { 0 }; | ||||
| 		uint32_t m_height { 0 }; | ||||
| 
 | ||||
| 		uint32_t m_saved_row { 0 }; | ||||
| 		uint32_t m_saved_column { 0 }; | ||||
| 
 | ||||
| 		uint32_t m_row { 0 }; | ||||
| 		uint32_t m_column { 0 }; | ||||
| 		Cell* m_buffer { nullptr }; | ||||
| 		bool m_show_cursor { true }; | ||||
| 
 | ||||
| 		TerminalDriver* m_terminal_driver { nullptr }; | ||||
| 
 | ||||
| 	public: | ||||
| 		virtual dev_t rdev() const override { return m_rdev; } | ||||
| 	private: | ||||
| 		const dev_t m_rdev; | ||||
| 	}; | ||||
| 
 | ||||
| } | ||||
|  | @ -1,8 +1,8 @@ | |||
| #include <kernel/Debug.h> | ||||
| #include <kernel/InterruptController.h> | ||||
| #include <kernel/Memory/PageTable.h> | ||||
| #include <kernel/Serial.h> | ||||
| #include <kernel/SpinLock.h> | ||||
| #include <kernel/Terminal/Serial.h> | ||||
| #include <kernel/Terminal/TTY.h> | ||||
| #include <kernel/Timer/Timer.h> | ||||
| 
 | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ namespace Kernel | |||
| 
 | ||||
| 	BAN::ErrorOr<BAN::RefPtr<NullDevice>> NullDevice::create(mode_t mode, uid_t uid, gid_t gid) | ||||
| 	{ | ||||
| 		auto* result = new NullDevice(mode, uid, gid, DevFileSystem::get().get_next_rdev()); | ||||
| 		auto* result = new NullDevice(mode, uid, gid, DevFileSystem::get().get_next_dev()); | ||||
| 		if (result == nullptr) | ||||
| 			return BAN::Error::from_errno(ENOMEM); | ||||
| 		return BAN::RefPtr<NullDevice>::adopt(result); | ||||
|  |  | |||
|  | @ -58,10 +58,10 @@ namespace Kernel | |||
| 		MUST(reinterpret_cast<RamDirectoryInode*>(root_inode().ptr())->add_inode(path, device)); | ||||
| 	} | ||||
| 
 | ||||
| 	dev_t DevFileSystem::get_next_rdev() | ||||
| 	dev_t DevFileSystem::get_next_dev() | ||||
| 	{ | ||||
| 		static dev_t next_rdev = 1; | ||||
| 		return next_rdev++; | ||||
| 		static dev_t next_dev = 1; | ||||
| 		return next_dev++; | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  | @ -54,7 +54,7 @@ namespace Kernel::Input | |||
| 
 | ||||
| 	PS2Keyboard::PS2Keyboard(PS2Controller& controller) | ||||
| 		: m_controller(controller) | ||||
| 		, m_rdev(makedev(DevFileSystem::get().get_next_rdev(), 0)) | ||||
| 		, m_rdev(makedev(DevFileSystem::get().get_next_dev(), 0)) | ||||
| 	{ } | ||||
| 
 | ||||
| 	void PS2Keyboard::on_byte(uint8_t byte) | ||||
|  |  | |||
|  | @ -1,76 +0,0 @@ | |||
| #include <BAN/Array.h> | ||||
| #include <kernel/IO.h> | ||||
| #include <kernel/Serial.h> | ||||
| 
 | ||||
| #define COM1_PORT 0x3f8 | ||||
| 
 | ||||
| namespace Kernel | ||||
| { | ||||
| 
 | ||||
| 	static constexpr uint16_t s_serial_ports[] = { 0x3F8, 0x2F8, 0x3E8, 0x2E8, 0x5F8, 0x4F8, 0x5E8, 0x4E8 }; | ||||
| 	static BAN::Array<Serial, sizeof(s_serial_ports) / sizeof(*s_serial_ports)> s_serial_devices; | ||||
| 	static bool s_has_devices { false }; | ||||
| 
 | ||||
| 	void Serial::initialize() | ||||
| 	{ | ||||
| 		int count = 0; | ||||
| 		for (size_t i = 0; i < s_serial_devices.size(); i++) | ||||
| 		{ | ||||
| 			if (port_has_device(s_serial_ports[i])) | ||||
| 			{ | ||||
| 				s_serial_devices[i].m_port = s_serial_ports[i]; | ||||
| 				count++; | ||||
| 			} | ||||
| 		} | ||||
| 		s_has_devices = !!count; | ||||
| 		dprintln("Initialized {} serial devices", count); | ||||
| 	} | ||||
| 
 | ||||
| 	bool Serial::port_has_device(uint16_t port) | ||||
| 	{ | ||||
| 		IO::outb(port + 1, 0x00);	// Disable all interrupts
 | ||||
| 		IO::outb(port + 3, 0x80);	// Enable DLAB (set baud rate divisor)
 | ||||
| 		IO::outb(port + 0, 0x03);	// Set divisor to 3 (lo byte) 38400 baud
 | ||||
| 		IO::outb(port + 1, 0x00);	//                  (hi byte)
 | ||||
| 		IO::outb(port + 3, 0x03);	// 8 bits, no parity, one stop bit
 | ||||
| 		IO::outb(port + 2, 0xC7);	// Enable FIFO, clear them, with 14-byte threshold
 | ||||
| 		IO::outb(port + 4, 0x0B);	// IRQs enabled, RTS/DSR set
 | ||||
| 		IO::outb(port + 4, 0x1E);	// Set in loopback mode, test the serial chip
 | ||||
| 		IO::outb(port + 0, 0xAE);	// Test serial chip (send byte 0xAE and check if serial returns same byte)
 | ||||
| 
 | ||||
| 		// Check if serial is faulty (i.e: not same byte as sent)
 | ||||
| 		if(IO::inb(COM1_PORT + 0) != 0xAE) | ||||
| 			return false; | ||||
| 
 | ||||
| 		// If serial is not faulty set it in normal operation mode
 | ||||
| 		// (not-loopback with IRQs enabled and OUT#1 and OUT#2 bits enabled)
 | ||||
| 		IO::outb(port + 4, 0x0F); | ||||
| 
 | ||||
| 		return true; | ||||
| 	} | ||||
| 
 | ||||
| 	bool Serial::has_devices() | ||||
| 	{ | ||||
| 		return s_has_devices; | ||||
| 	} | ||||
| 
 | ||||
| 	bool Serial::is_transmit_empty() const | ||||
| 	{ | ||||
| 		return !(IO::inb(m_port + 5) & 0x20); | ||||
| 	} | ||||
|   | ||||
| 	void Serial::putchar(char c) | ||||
| 	{ | ||||
| 		while (is_transmit_empty()) | ||||
| 			continue; | ||||
| 		IO::outb(m_port, c); | ||||
| 	} | ||||
| 
 | ||||
| 	void Serial::putchar_any(char c) | ||||
| 	{ | ||||
| 		for (auto& device : s_serial_devices) | ||||
| 			if (device.is_valid()) | ||||
| 				device.putchar(c); | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  | @ -47,7 +47,7 @@ namespace Kernel | |||
| 	} | ||||
| 
 | ||||
| 	ATAController::ATAController() | ||||
| 		: m_rdev(makedev(DevFileSystem::get().get_next_rdev(), 0)) | ||||
| 		: m_rdev(makedev(DevFileSystem::get().get_next_dev(), 0)) | ||||
| 	{ } | ||||
| 
 | ||||
| 	BAN::ErrorOr<void> ATAController::initialize(const PCIDevice& pci_device) | ||||
|  |  | |||
|  | @ -11,7 +11,7 @@ namespace Kernel | |||
| 
 | ||||
| 	ATADevice::ATADevice(ATABus& bus) | ||||
| 		: m_bus(bus) | ||||
| 		, m_rdev(makedev(DevFileSystem::get().get_next_rdev(), 0)) | ||||
| 		, m_rdev(makedev(DevFileSystem::get().get_next_dev(), 0)) | ||||
| 	{ } | ||||
| 
 | ||||
| 	BAN::ErrorOr<void> ATADevice::initialize(ATABus::DeviceType type, const uint16_t* identify_buffer) | ||||
|  |  | |||
|  | @ -0,0 +1,180 @@ | |||
| #include <BAN/Array.h> | ||||
| #include <kernel/FS/DevFS/FileSystem.h> | ||||
| #include <kernel/IO.h> | ||||
| #include <kernel/Terminal/Serial.h> | ||||
| 
 | ||||
| #include <ctype.h> | ||||
| #include <sys/sysmacros.h> | ||||
| 
 | ||||
| namespace Kernel | ||||
| { | ||||
| 
 | ||||
| 	static constexpr uint16_t s_serial_ports[] = { 0x3F8, 0x2F8, 0x3E8, 0x2E8, 0x5F8, 0x4F8, 0x5E8, 0x4E8 }; | ||||
| 	static BAN::Array<Serial, sizeof(s_serial_ports) / sizeof(*s_serial_ports)> s_serial_drivers; | ||||
| 	static bool s_has_devices { false }; | ||||
| 
 | ||||
| 
 | ||||
| 	static dev_t next_rdev() | ||||
| 	{ | ||||
| 		static dev_t major = DevFileSystem::get().get_next_dev(); | ||||
| 		static dev_t minor = 0; | ||||
| 		return makedev(major, minor++); | ||||
| 	} | ||||
| 
 | ||||
| 	void Serial::initialize() | ||||
| 	{ | ||||
| 		int count = 0; | ||||
| 		for (size_t i = 0; i < s_serial_drivers.size(); i++) | ||||
| 		{ | ||||
| 			if (port_has_device(s_serial_ports[i])) | ||||
| 			{ | ||||
| 				auto& driver = s_serial_drivers[i]; | ||||
| 				driver.m_port = s_serial_ports[i]; | ||||
| 				if (!driver.initialize_size()) | ||||
| 					continue; | ||||
| 				count++; | ||||
| 			} | ||||
| 		} | ||||
| 		s_has_devices = !!count; | ||||
| 
 | ||||
| 		for (auto& driver : s_serial_drivers) | ||||
| 			dprintln("{}x{} serial device at 0x{H}", driver.width(), driver.height(), driver.port()); | ||||
| 	} | ||||
| 
 | ||||
| 	void Serial::initialize_devices() | ||||
| 	{ | ||||
| 		for (auto& serial : s_serial_drivers) | ||||
| 			if (serial.is_valid()) | ||||
| 				MUST(SerialTTY::create(serial)); | ||||
| 	} | ||||
| 
 | ||||
| 	bool Serial::port_has_device(uint16_t port) | ||||
| 	{ | ||||
| 		IO::outb(port + 1, 0x00);	// Disable all interrupts
 | ||||
| 		IO::outb(port + 3, 0x80);	// Enable DLAB (set baud rate divisor)
 | ||||
| 		IO::outb(port + 0, 0x03);	// Set divisor to 3 (lo byte) 38400 baud
 | ||||
| 		IO::outb(port + 1, 0x00);	//                  (hi byte)
 | ||||
| 		IO::outb(port + 3, 0x03);	// 8 bits, no parity, one stop bit
 | ||||
| 		IO::outb(port + 2, 0xC7);	// Enable FIFO, clear them, with 14-byte threshold
 | ||||
| 		IO::outb(port + 4, 0x0B);	// IRQs enabled, RTS/DSR set
 | ||||
| 		IO::outb(port + 4, 0x1E);	// Set in loopback mode, test the serial chip
 | ||||
| 		IO::outb(port + 0, 0xAE);	// Test serial chip (send byte 0xAE and check if serial returns same byte)
 | ||||
| 
 | ||||
| 		// Check if serial is faulty (i.e: not same byte as sent)
 | ||||
| 		if(IO::inb(port + 0) != 0xAE) | ||||
| 			return false; | ||||
| 
 | ||||
| 		// If serial is not faulty set it in normal operation mode
 | ||||
| 		// (not-loopback with IRQs enabled and OUT#1 and OUT#2 bits enabled)
 | ||||
| 		IO::outb(port + 4, 0x0F); | ||||
| 
 | ||||
| 		return true; | ||||
| 	} | ||||
| 
 | ||||
| 	bool Serial::initialize_size() | ||||
| 	{ | ||||
| 		const char* query = "\e[999;999H\e[6n\e[H\e[J"; | ||||
| 
 | ||||
| 		const char* ptr = query; | ||||
| 		while (*ptr) | ||||
| 			putchar(*ptr++); | ||||
| 		 | ||||
| 		if (getchar() != '\033') | ||||
| 			return false; | ||||
| 		if (getchar() != '[') | ||||
| 			return false; | ||||
| 
 | ||||
| 		auto read_number = | ||||
| 			[&](char end) | ||||
| 			{ | ||||
| 				uint32_t number = 0; | ||||
| 				while (true) | ||||
| 				{ | ||||
| 					char c = getchar(); | ||||
| 					if (c == end) | ||||
| 						break; | ||||
| 					if (!isdigit(c)) | ||||
| 						return UINT32_MAX; | ||||
| 					number = (number * 10) + (c - '0'); | ||||
| 				} | ||||
| 				return number; | ||||
| 			}; | ||||
| 
 | ||||
| 		m_height = read_number(';'); | ||||
| 		if (m_height == UINT32_MAX) | ||||
| 		{ | ||||
| 			m_port = 0; | ||||
| 			return false; | ||||
| 		} | ||||
| 
 | ||||
| 		m_width = read_number('R'); | ||||
| 		if (m_width == UINT32_MAX) | ||||
| 		{ | ||||
| 			m_port = 0; | ||||
| 			return false; | ||||
| 		} | ||||
| 
 | ||||
| 		return true; | ||||
| 	} | ||||
| 
 | ||||
| 	bool Serial::has_devices() | ||||
| 	{ | ||||
| 		return s_has_devices; | ||||
| 	} | ||||
|   | ||||
| 	void Serial::putchar(char c) | ||||
| 	{ | ||||
| 		while (!(IO::inb(m_port + 5) & 0x20)) | ||||
| 			continue; | ||||
| 		IO::outb(m_port, c); | ||||
| 	} | ||||
| 
 | ||||
| 	char Serial::getchar() | ||||
| 	{ | ||||
| 		while (!(IO::inb(m_port + 5) & 0x01)) | ||||
| 			continue; | ||||
| 		return IO::inb(m_port); | ||||
| 	} | ||||
| 
 | ||||
| 	void Serial::putchar_any(char c) | ||||
| 	{ | ||||
| 		for (auto& device : s_serial_drivers) | ||||
| 			if (device.is_valid()) | ||||
| 				device.putchar(c); | ||||
| 	} | ||||
| 
 | ||||
| 	SerialTTY::SerialTTY(Serial serial) | ||||
| 		: TTY(0660, 0, 0) | ||||
| 		, m_serial(serial) | ||||
| 		, m_rdev(next_rdev()) | ||||
| 	{} | ||||
| 
 | ||||
| 	BAN::ErrorOr<BAN::RefPtr<SerialTTY>> SerialTTY::create(Serial serial) | ||||
| 	{ | ||||
| 		auto* tty = new SerialTTY(serial); | ||||
| 		ASSERT(tty); | ||||
| 
 | ||||
| 		ASSERT(minor(tty->rdev()) < 10); | ||||
| 		char name[] = { 't', 't', 'y', 'S', (char)('0' + minor(tty->rdev())), '\0' }; | ||||
| 		 | ||||
| 		auto ref_ptr = BAN::RefPtr<SerialTTY>::adopt(tty); | ||||
| 		DevFileSystem::get().add_device(name, ref_ptr); | ||||
| 		return ref_ptr; | ||||
| 	} | ||||
| 	 | ||||
| 	uint32_t SerialTTY::width() const | ||||
| 	{ | ||||
| 		return m_serial.width(); | ||||
| 	} | ||||
| 
 | ||||
| 	uint32_t SerialTTY::height() const | ||||
| 	{ | ||||
| 		return m_serial.height(); | ||||
| 	} | ||||
| 	 | ||||
| 	void SerialTTY::putchar(uint8_t ch) | ||||
| 	{ | ||||
| 		m_serial.putchar(ch); | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  | @ -11,61 +11,28 @@ | |||
| #include <string.h> | ||||
| #include <sys/sysmacros.h> | ||||
| 
 | ||||
| #define BEL	0x07 | ||||
| #define BS	0x08 | ||||
| #define HT	0x09 | ||||
| #define LF	0x0A | ||||
| #define FF	0x0C | ||||
| #define CR	0x0D | ||||
| #define ESC	0x1B | ||||
| 
 | ||||
| #define CSI '[' | ||||
| 
 | ||||
| namespace Kernel | ||||
| { | ||||
| 
 | ||||
| 	static Process* s_input_process = nullptr; | ||||
| 
 | ||||
| 	static dev_t next_tty_rdev() | ||||
| 	{ | ||||
| 		static dev_t major = DevFileSystem::get().get_next_rdev(); | ||||
| 		static dev_t minor = 0; | ||||
| 		return makedev(major, minor++); | ||||
| 	} | ||||
| 
 | ||||
| 	static BAN::RefPtr<TTY> s_tty; | ||||
| 
 | ||||
| 	TTY::TTY(TerminalDriver* driver) | ||||
| 		: CharacterDevice(0666, 0, 0) | ||||
| 		, m_terminal_driver(driver) | ||||
| 	{ | ||||
| 		m_width = m_terminal_driver->width(); | ||||
| 		m_height = m_terminal_driver->height(); | ||||
| 
 | ||||
| 		m_buffer = new Cell[m_width * m_height]; | ||||
| 		ASSERT(m_buffer); | ||||
| 
 | ||||
| 		if (!s_tty) | ||||
| 			s_tty = this; | ||||
| 	} | ||||
| 
 | ||||
| 	BAN::RefPtr<TTY> TTY::current() | ||||
| 	{ | ||||
| 		ASSERT(s_tty); | ||||
| 		return s_tty; | ||||
| 	} | ||||
| 
 | ||||
| 	void TTY::initialize_device() | ||||
| 	void TTY::set_as_current() | ||||
| 	{ | ||||
| 		m_rdev = next_tty_rdev(); | ||||
| 		s_tty = this; | ||||
| 	} | ||||
| 
 | ||||
| 		ASSERT(minor(m_rdev) < 10); | ||||
| 		char name[5] { 't', 't', 'y', (char)('0' + minor(m_rdev)), '\0' }; | ||||
| 	void TTY::initialize_devices() | ||||
| 	{ | ||||
| 		static bool initialized = false; | ||||
| 		ASSERT(!initialized); | ||||
| 
 | ||||
| 		DevFileSystem::get().add_device(name, BAN::RefPtr<TTY>::adopt(this)); | ||||
| 
 | ||||
| 		if (s_input_process) | ||||
| 			return; | ||||
| 		s_input_process = Process::create_kernel( | ||||
| 		Process::create_kernel( | ||||
| 			[](void*) | ||||
| 			{ | ||||
| 				int fd = MUST(Process::current().sys_open("/dev/input0"sv, O_RDONLY)); | ||||
|  | @ -86,13 +53,15 @@ namespace Kernel | |||
| 							dwarnln("TTY: {}", ret.error()); | ||||
| 					} | ||||
| 					else | ||||
| 						current_tty.on_key(event); | ||||
| 						current_tty.on_key_event(event); | ||||
| 				} | ||||
| 			}, nullptr | ||||
| 		); | ||||
| 
 | ||||
| 		initialized = true; | ||||
| 	} | ||||
| 
 | ||||
| 	void TTY::on_key(Input::KeyEvent event) | ||||
| 	void TTY::on_key_event(Input::KeyEvent event) | ||||
| 	{ | ||||
| 		LockGuard _(m_lock); | ||||
| 
 | ||||
|  | @ -249,24 +218,14 @@ flush: | |||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	void TTY::clear() | ||||
| 	{ | ||||
| 		for (uint32_t i = 0; i < m_width * m_height; i++) | ||||
| 			m_buffer[i] = { .foreground = m_foreground, .background = m_background, .codepoint = ' ' }; | ||||
| 		m_terminal_driver->clear(m_background); | ||||
| 	} | ||||
| 
 | ||||
| 	void TTY::do_backspace() | ||||
| 	{ | ||||
| 		auto print_backspace = | ||||
| 			[this] | ||||
| 			{ | ||||
| 				if (m_termios.echo && m_column > 0) | ||||
| 				{ | ||||
| 					m_column--; | ||||
| 					putchar_at(' ', m_column, m_row); | ||||
| 					set_cursor_position(m_column, m_row); | ||||
| 				} | ||||
| 				putchar('\b'); | ||||
| 				putchar(' '); | ||||
| 				putchar('\b'); | ||||
| 			}; | ||||
| 
 | ||||
| 		if (m_output.bytes > 0) | ||||
|  | @ -302,369 +261,6 @@ flush: | |||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	void TTY::set_cursor_position(uint32_t x, uint32_t y) | ||||
| 	{ | ||||
| 		static uint32_t last_x = -1; | ||||
| 		static uint32_t last_y = -1; | ||||
| 		if (last_x != uint32_t(-1) && last_y != uint32_t(-1)) | ||||
| 			render_from_buffer(last_x, last_y); | ||||
| 		if (m_show_cursor) | ||||
| 			m_terminal_driver->set_cursor_position(x, y); | ||||
| 		last_x = m_column = x; | ||||
| 		last_y = m_row = y; | ||||
| 	} | ||||
| 
 | ||||
| 	void TTY::set_font(const Kernel::Font& font) | ||||
| 	{ | ||||
| 		m_terminal_driver->set_font(font); | ||||
| 
 | ||||
| 		uint32_t new_width = m_terminal_driver->width(); | ||||
| 		uint32_t new_height = m_terminal_driver->height(); | ||||
| 
 | ||||
| 		if (m_width != new_width || m_height != new_height) | ||||
| 		{ | ||||
| 			Cell* new_buffer = new Cell[new_width * new_height]; | ||||
| 			ASSERT(new_buffer); | ||||
| 
 | ||||
| 			for (uint32_t i = 0; i < new_width * m_height; i++) | ||||
| 				new_buffer[i] = { .foreground = m_foreground, .background = m_background, .codepoint = ' ' }; | ||||
| 
 | ||||
| 			for (uint32_t y = 0; y < BAN::Math::min<uint32_t>(m_height, new_height); y++) | ||||
| 				for (uint32_t x = 0; x < BAN::Math::min<uint32_t>(m_width, new_width); x++) | ||||
| 					new_buffer[y * new_width + x] = m_buffer[y * m_width + x]; | ||||
| 
 | ||||
| 			delete[] m_buffer; | ||||
| 			m_buffer = new_buffer; | ||||
| 			m_width = new_width; | ||||
| 			m_height = new_height; | ||||
| 		} | ||||
| 		 | ||||
| 		for (uint32_t y = 0; y < m_height; y++) | ||||
| 			for (uint32_t x = 0; x < m_width; x++) | ||||
| 				render_from_buffer(x, y); | ||||
| 	} | ||||
| 
 | ||||
| 	void TTY::reset_ansi() | ||||
| 	{ | ||||
| 		m_ansi_state.index = 0; | ||||
| 		m_ansi_state.nums[0] = -1; | ||||
| 		m_ansi_state.nums[1] = -1; | ||||
| 		m_ansi_state.question = false; | ||||
| 		m_state = State::Normal; | ||||
| 	} | ||||
| 
 | ||||
| 	void TTY::handle_ansi_csi_color() | ||||
| 	{ | ||||
| 		switch (m_ansi_state.nums[0]) | ||||
| 		{ | ||||
| 			case -1: | ||||
| 			case 0: | ||||
| 				m_foreground = TerminalColor::BRIGHT_WHITE; | ||||
| 				m_background = TerminalColor::BLACK; | ||||
| 				break; | ||||
| 
 | ||||
| 			case 30: m_foreground = TerminalColor::BRIGHT_BLACK;	break; | ||||
| 			case 31: m_foreground = TerminalColor::BRIGHT_RED;		break; | ||||
| 			case 32: m_foreground = TerminalColor::BRIGHT_GREEN;	break; | ||||
| 			case 33: m_foreground = TerminalColor::BRIGHT_YELLOW;	break; | ||||
| 			case 34: m_foreground = TerminalColor::BRIGHT_BLUE;		break; | ||||
| 			case 35: m_foreground = TerminalColor::BRIGHT_MAGENTA;	break; | ||||
| 			case 36: m_foreground = TerminalColor::BRIGHT_CYAN;		break; | ||||
| 			case 37: m_foreground = TerminalColor::BRIGHT_WHITE;	break; | ||||
| 
 | ||||
| 			case 40: m_background = TerminalColor::BRIGHT_BLACK;	break; | ||||
| 			case 41: m_background = TerminalColor::BRIGHT_RED;		break; | ||||
| 			case 42: m_background = TerminalColor::BRIGHT_GREEN;	break; | ||||
| 			case 43: m_background = TerminalColor::BRIGHT_YELLOW;	break; | ||||
| 			case 44: m_background = TerminalColor::BRIGHT_BLUE;		break; | ||||
| 			case 45: m_background = TerminalColor::BRIGHT_MAGENTA;	break; | ||||
| 			case 46: m_background = TerminalColor::BRIGHT_CYAN;		break; | ||||
| 			case 47: m_background = TerminalColor::BRIGHT_WHITE;	break; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	void TTY::handle_ansi_csi(uint8_t ch) | ||||
| 	{ | ||||
| 		switch (ch) | ||||
| 		{ | ||||
| 			case '0': case '1': case '2': case '3': case '4': | ||||
| 			case '5': case '6': case '7': case '8': case '9': | ||||
| 			{ | ||||
| 				int32_t& val = m_ansi_state.nums[m_ansi_state.index]; | ||||
| 				val = (val == -1) ? (ch - '0') : (val * 10 + ch - '0'); | ||||
| 				return; | ||||
| 			} | ||||
| 			case ';': | ||||
| 				m_ansi_state.index++; | ||||
| 				return; | ||||
| 			case 'A': // Cursor Up
 | ||||
| 				if (m_ansi_state.nums[0] == -1) | ||||
| 					m_ansi_state.nums[0] = 1; | ||||
| 				m_row = BAN::Math::max<int32_t>(m_row - m_ansi_state.nums[0], 0); | ||||
| 				return reset_ansi(); | ||||
| 			case 'B': // Curson Down
 | ||||
| 				if (m_ansi_state.nums[0] == -1) | ||||
| 					m_ansi_state.nums[0] = 1; | ||||
| 				m_row = BAN::Math::min<int32_t>(m_row + m_ansi_state.nums[0], m_height - 1); | ||||
| 				return reset_ansi(); | ||||
| 			case 'C': // Cursor Forward
 | ||||
| 				if (m_ansi_state.nums[0] == -1) | ||||
| 					m_ansi_state.nums[0] = 1; | ||||
| 				m_column = BAN::Math::min<int32_t>(m_column + m_ansi_state.nums[0], m_width - 1); | ||||
| 				return reset_ansi(); | ||||
| 			case 'D': // Cursor Back
 | ||||
| 				if (m_ansi_state.nums[0] == -1) | ||||
| 					m_ansi_state.nums[0] = 1; | ||||
| 				m_column = BAN::Math::max<int32_t>(m_column - m_ansi_state.nums[0], 0); | ||||
| 				return reset_ansi(); | ||||
| 			case 'E': // Cursor Next Line
 | ||||
| 				if (m_ansi_state.nums[0] == -1) | ||||
| 					m_ansi_state.nums[0] = 1; | ||||
| 				m_row = BAN::Math::min<int32_t>(m_row + m_ansi_state.nums[0], m_height - 1); | ||||
| 				m_column = 0; | ||||
| 				return reset_ansi(); | ||||
| 			case 'F': // Cursor Previous Line
 | ||||
| 				if (m_ansi_state.nums[0] == -1) | ||||
| 					m_ansi_state.nums[0] = 1; | ||||
| 				m_row = BAN::Math::max<int32_t>(m_row - m_ansi_state.nums[0], 0); | ||||
| 				m_column = 0; | ||||
| 				return reset_ansi(); | ||||
| 			case 'G': // Cursor Horizontal Absolute
 | ||||
| 				if (m_ansi_state.nums[0] == -1) | ||||
| 					m_ansi_state.nums[0] = 1; | ||||
| 				m_column = BAN::Math::clamp<int32_t>(m_ansi_state.nums[0] - 1, 0, m_width - 1); | ||||
| 				return reset_ansi(); | ||||
| 			case 'H': // Cursor Position
 | ||||
| 				if (m_ansi_state.nums[0] == -1) | ||||
| 					m_ansi_state.nums[0] = 1; | ||||
| 				if (m_ansi_state.nums[1] == -1) | ||||
| 					m_ansi_state.nums[1] = 1; | ||||
| 				m_row = BAN::Math::clamp<int32_t>(m_ansi_state.nums[0] - 1, 0, m_height - 1); | ||||
| 				m_column = BAN::Math::clamp<int32_t>(m_ansi_state.nums[1] - 1, 0, m_width - 1); | ||||
| 				return reset_ansi(); | ||||
| 			case 'J': // Erase in Display
 | ||||
| 				if (m_ansi_state.nums[0] == -1 || m_ansi_state.nums[0] == 0) | ||||
| 				{ | ||||
| 					// Clear from cursor to the end of screen
 | ||||
| 					for (uint32_t i = m_column; i < m_width; i++) | ||||
| 						putchar_at(' ', i, m_row); | ||||
| 					for (uint32_t row = 0; row < m_height; row++) | ||||
| 						for (uint32_t col = 0; col < m_width; col++) | ||||
| 							putchar_at(' ', col, row); | ||||
| 				} | ||||
| 				else if (m_ansi_state.nums[0] == 1) | ||||
| 				{ | ||||
| 					// Clear from cursor to the beginning of screen
 | ||||
| 					for (uint32_t row = 0; row < m_row; row++) | ||||
| 						for (uint32_t col = 0; col < m_width; col++) | ||||
| 							putchar_at(' ', col, row); | ||||
| 					for (uint32_t i = 0; i <= m_column; i++) | ||||
| 						putchar_at(' ', i, m_row); | ||||
| 				} | ||||
| 				else if (m_ansi_state.nums[0] == 2 || m_ansi_state.nums[0] == 3) | ||||
| 				{ | ||||
| 					// Clean entire screen
 | ||||
| 					clear(); | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					dprintln("Unsupported ANSI CSI character J"); | ||||
| 				} | ||||
| 				 | ||||
| 				if (m_ansi_state.nums[0] == 3) | ||||
| 				{ | ||||
| 					// FIXME: Clear scroll backbuffer if/when added
 | ||||
| 				} | ||||
| 				return reset_ansi(); | ||||
| 			case 'K': // Erase in Line
 | ||||
| 				if (m_ansi_state.nums[0] == -1 || m_ansi_state.nums[0] == 0) | ||||
| 					for (uint32_t i = m_column; i < m_width; i++) | ||||
| 						putchar_at(' ', i, m_row); | ||||
| 				else | ||||
| 					dprintln("Unsupported ANSI CSI character K"); | ||||
| 				return reset_ansi(); | ||||
| 			case 'S': // Scroll Up
 | ||||
| 				dprintln("Unsupported ANSI CSI character S"); | ||||
| 				return reset_ansi(); | ||||
| 			case 'T': // Scroll Down
 | ||||
| 				dprintln("Unsupported ANSI CSI character T"); | ||||
| 				return reset_ansi(); | ||||
| 			case 'f': // Horizontal Vertical Position
 | ||||
| 				dprintln("Unsupported ANSI CSI character f"); | ||||
| 				return reset_ansi(); | ||||
| 			case 'm': | ||||
| 				handle_ansi_csi_color(); | ||||
| 				return reset_ansi(); | ||||
| 			case 's': | ||||
| 				m_saved_row = m_row; | ||||
| 				m_saved_column = m_column; | ||||
| 				return reset_ansi(); | ||||
| 			case 'u': | ||||
| 				m_row = m_saved_row; | ||||
| 				m_column = m_saved_column; | ||||
| 				return reset_ansi(); | ||||
| 
 | ||||
| 			case '?': | ||||
| 				if (m_ansi_state.index != 0 || m_ansi_state.nums[0] != -1) | ||||
| 				{ | ||||
| 					dprintln("invalid ANSI CSI ?"); | ||||
| 					return reset_ansi(); | ||||
| 				} | ||||
| 				m_ansi_state.question = true; | ||||
| 				return; | ||||
| 			case 'h': | ||||
| 			case 'l': | ||||
| 				if (!m_ansi_state.question || m_ansi_state.nums[0] != 25) | ||||
| 				{ | ||||
| 					dprintln("invalid ANSI CSI ?{}{}", m_ansi_state.nums[0], (char)ch); | ||||
| 					return reset_ansi(); | ||||
| 				} | ||||
| 				m_show_cursor = (ch == 'h'); | ||||
| 				return reset_ansi(); | ||||
| 			default: | ||||
| 				dprintln("Unsupported ANSI CSI character {}", ch); | ||||
| 				return reset_ansi(); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	void TTY::render_from_buffer(uint32_t x, uint32_t y) | ||||
| 	{ | ||||
| 		ASSERT(x < m_width && y < m_height); | ||||
| 		const auto& cell = m_buffer[y * m_width + x]; | ||||
| 		m_terminal_driver->putchar_at(cell.codepoint, x, y, cell.foreground, cell.background); | ||||
| 	} | ||||
| 
 | ||||
| 	void TTY::putchar_at(uint32_t codepoint, uint32_t x, uint32_t y) | ||||
| 	{ | ||||
| 		ASSERT(x < m_width && y < m_height); | ||||
| 		auto& cell = m_buffer[y * m_width + x]; | ||||
| 		cell.codepoint = codepoint; | ||||
| 		cell.foreground = m_foreground; | ||||
| 		cell.background = m_background; | ||||
| 		m_terminal_driver->putchar_at(codepoint, x, y, m_foreground, m_background); | ||||
| 	} | ||||
| 
 | ||||
| 	void TTY::putchar(uint8_t ch) | ||||
| 	{ | ||||
| 		ASSERT(m_lock.is_locked()); | ||||
| 		 | ||||
| 		uint32_t codepoint = ch; | ||||
| 
 | ||||
| 		switch (m_state) | ||||
| 		{ | ||||
| 			case State::Normal: | ||||
| 				if ((ch & 0x80) == 0) | ||||
| 					break; | ||||
| 				if ((ch & 0xE0) == 0xC0) | ||||
| 				{ | ||||
| 					m_utf8_state.codepoint = ch & 0x1F; | ||||
| 					m_utf8_state.bytes_missing = 1; | ||||
| 				} | ||||
| 				else if ((ch & 0xF0) == 0xE0) | ||||
| 				{ | ||||
| 					m_utf8_state.codepoint = ch & 0x0F; | ||||
| 					m_utf8_state.bytes_missing = 2; | ||||
| 				} | ||||
| 				else if ((ch & 0xF8) == 0xF0) | ||||
| 				{ | ||||
| 					m_utf8_state.codepoint = ch & 0x07; | ||||
| 					m_utf8_state.bytes_missing = 3; | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					dprintln("invalid utf8"); | ||||
| 				} | ||||
| 				m_state = State::WaitingUTF8; | ||||
| 				return; | ||||
| 			case State::WaitingAnsiEscape: | ||||
| 				if (ch == CSI) | ||||
| 					m_state = State::WaitingAnsiCSI; | ||||
| 				else | ||||
| 				{ | ||||
| 					dprintln("unsupported byte after ansi escape {2H}", (uint8_t)ch); | ||||
| 					reset_ansi(); | ||||
| 				} | ||||
| 				return; | ||||
| 			case State::WaitingAnsiCSI: | ||||
| 				handle_ansi_csi(ch); | ||||
| 				set_cursor_position(m_column, m_row); | ||||
| 				return; | ||||
| 			case State::WaitingUTF8: | ||||
| 				if ((ch & 0xC0) != 0x80) | ||||
| 				{ | ||||
| 					dprintln("invalid utf8"); | ||||
| 					m_state = State::Normal; | ||||
| 					return; | ||||
| 				} | ||||
| 				m_utf8_state.codepoint = (m_utf8_state.codepoint << 6) | (ch & 0x3F); | ||||
| 				m_utf8_state.bytes_missing--; | ||||
| 				if (m_utf8_state.bytes_missing) | ||||
| 					return; | ||||
| 				m_state = State::Normal; | ||||
| 				codepoint = m_utf8_state.codepoint; | ||||
| 				break; | ||||
| 			default: | ||||
| 				ASSERT_NOT_REACHED(); | ||||
| 		} | ||||
| 
 | ||||
| 		switch (codepoint) | ||||
| 		{ | ||||
| 			case BEL: // TODO
 | ||||
| 				break; | ||||
| 			case BS: | ||||
| 				if (m_column > 0) | ||||
| 					m_column--; | ||||
| 				break; | ||||
| 			case HT: | ||||
| 				m_column++; | ||||
| 				while (m_column % 8) | ||||
| 					m_column++; | ||||
| 				break; | ||||
| 			case LF: | ||||
| 				m_column = 0; | ||||
| 				m_row++; | ||||
| 				break; | ||||
| 			case FF: | ||||
| 				m_row++; | ||||
| 				break; | ||||
| 			case CR: | ||||
| 				m_column = 0; | ||||
| 				break; | ||||
| 			case ESC: | ||||
| 				m_state = State::WaitingAnsiEscape; | ||||
| 				break;; | ||||
| 			default: | ||||
| 				putchar_at(codepoint, m_column, m_row); | ||||
| 				m_column++; | ||||
| 				break; | ||||
| 		} | ||||
| 
 | ||||
| 		if (m_column >= m_width) | ||||
| 		{ | ||||
| 			m_column = 0; | ||||
| 			m_row++; | ||||
| 		} | ||||
| 
 | ||||
| 		while (m_row >= m_height) | ||||
| 		{ | ||||
| 			memmove(m_buffer, m_buffer + m_width, m_width * (m_height - 1) * sizeof(Cell)); | ||||
| 
 | ||||
| 			// Clear last line in buffer
 | ||||
| 			for (uint32_t x = 0; x < m_width; x++) | ||||
| 				m_buffer[(m_height - 1) * m_width + x] = { .foreground = m_foreground, .background = m_background, .codepoint = ' ' }; | ||||
| 
 | ||||
| 			// Render the whole buffer to the screen
 | ||||
| 			for (uint32_t y = 0; y < m_height; y++) | ||||
| 				for (uint32_t x = 0; x < m_width; x++) | ||||
| 					render_from_buffer(x, y); | ||||
| 
 | ||||
| 			m_column = 0; | ||||
| 			m_row--; | ||||
| 		} | ||||
| 
 | ||||
| 		set_cursor_position(m_column, m_row); | ||||
| 	} | ||||
| 
 | ||||
| 	BAN::ErrorOr<size_t> TTY::read(size_t, void* buffer, size_t count) | ||||
| 	{ | ||||
| 		m_lock.lock(); | ||||
|  |  | |||
|  | @ -0,0 +1,429 @@ | |||
| #include <BAN/Errors.h> | ||||
| #include <BAN/ScopeGuard.h> | ||||
| #include <BAN/UTF8.h> | ||||
| #include <kernel/Debug.h> | ||||
| #include <kernel/FS/DevFS/FileSystem.h> | ||||
| #include <kernel/LockGuard.h> | ||||
| #include <kernel/Process.h> | ||||
| #include <kernel/Terminal/VirtualTTY.h> | ||||
| 
 | ||||
| #include <fcntl.h> | ||||
| #include <string.h> | ||||
| #include <sys/sysmacros.h> | ||||
| 
 | ||||
| #define BEL	0x07 | ||||
| #define BS	0x08 | ||||
| #define HT	0x09 | ||||
| #define LF	0x0A | ||||
| #define FF	0x0C | ||||
| #define CR	0x0D | ||||
| #define ESC	0x1B | ||||
| 
 | ||||
| #define CSI '[' | ||||
| 
 | ||||
| namespace Kernel | ||||
| { | ||||
| 
 | ||||
| 	static dev_t next_rdev() | ||||
| 	{ | ||||
| 		static dev_t major = DevFileSystem::get().get_next_dev(); | ||||
| 		static dev_t minor = 0; | ||||
| 		return makedev(major, minor++); | ||||
| 	} | ||||
| 
 | ||||
| 	BAN::ErrorOr<BAN::RefPtr<VirtualTTY>> VirtualTTY::create(TerminalDriver* driver) | ||||
| 	{ | ||||
| 		auto* tty = new VirtualTTY(driver); | ||||
| 		ASSERT(tty); | ||||
| 
 | ||||
| 		ASSERT(minor(tty->rdev()) < 10); | ||||
| 		char name[5] { 't', 't', 'y', (char)('0' + minor(tty->rdev())), '\0' }; | ||||
| 
 | ||||
| 		auto ref_ptr = BAN::RefPtr<VirtualTTY>::adopt(tty); | ||||
| 		DevFileSystem::get().add_device(name, ref_ptr); | ||||
| 		return ref_ptr; | ||||
| 	} | ||||
| 
 | ||||
| 	VirtualTTY::VirtualTTY(TerminalDriver* driver) | ||||
| 		: TTY(0666, 0, 0) | ||||
| 		, m_terminal_driver(driver) | ||||
| 		, m_rdev(next_rdev()) | ||||
| 	{ | ||||
| 		m_width = m_terminal_driver->width(); | ||||
| 		m_height = m_terminal_driver->height(); | ||||
| 
 | ||||
| 		m_buffer = new Cell[m_width * m_height]; | ||||
| 		ASSERT(m_buffer); | ||||
| 	} | ||||
| 
 | ||||
| 	void VirtualTTY::clear() | ||||
| 	{ | ||||
| 		for (uint32_t i = 0; i < m_width * m_height; i++) | ||||
| 			m_buffer[i] = { .foreground = m_foreground, .background = m_background, .codepoint = ' ' }; | ||||
| 		m_terminal_driver->clear(m_background); | ||||
| 	} | ||||
| 
 | ||||
| 	void VirtualTTY::set_font(const Kernel::Font& font) | ||||
| 	{ | ||||
| 		m_terminal_driver->set_font(font); | ||||
| 
 | ||||
| 		uint32_t new_width = m_terminal_driver->width(); | ||||
| 		uint32_t new_height = m_terminal_driver->height(); | ||||
| 
 | ||||
| 		if (m_width != new_width || m_height != new_height) | ||||
| 		{ | ||||
| 			Cell* new_buffer = new Cell[new_width * new_height]; | ||||
| 			ASSERT(new_buffer); | ||||
| 
 | ||||
| 			for (uint32_t i = 0; i < new_width * m_height; i++) | ||||
| 				new_buffer[i] = { .foreground = m_foreground, .background = m_background, .codepoint = ' ' }; | ||||
| 
 | ||||
| 			for (uint32_t y = 0; y < BAN::Math::min<uint32_t>(m_height, new_height); y++) | ||||
| 				for (uint32_t x = 0; x < BAN::Math::min<uint32_t>(m_width, new_width); x++) | ||||
| 					new_buffer[y * new_width + x] = m_buffer[y * m_width + x]; | ||||
| 
 | ||||
| 			delete[] m_buffer; | ||||
| 			m_buffer = new_buffer; | ||||
| 			m_width = new_width; | ||||
| 			m_height = new_height; | ||||
| 		} | ||||
| 		 | ||||
| 		for (uint32_t y = 0; y < m_height; y++) | ||||
| 			for (uint32_t x = 0; x < m_width; x++) | ||||
| 				render_from_buffer(x, y); | ||||
| 	} | ||||
| 
 | ||||
| 	void VirtualTTY::set_cursor_position(uint32_t x, uint32_t y) | ||||
| 	{ | ||||
| 		static uint32_t last_x = -1; | ||||
| 		static uint32_t last_y = -1; | ||||
| 		if (last_x != uint32_t(-1) && last_y != uint32_t(-1)) | ||||
| 			render_from_buffer(last_x, last_y); | ||||
| 		if (m_show_cursor) | ||||
| 			m_terminal_driver->set_cursor_position(x, y); | ||||
| 		last_x = m_column = x; | ||||
| 		last_y = m_row = y; | ||||
| 	} | ||||
| 
 | ||||
| 	void VirtualTTY::reset_ansi() | ||||
| 	{ | ||||
| 		m_ansi_state.index = 0; | ||||
| 		m_ansi_state.nums[0] = -1; | ||||
| 		m_ansi_state.nums[1] = -1; | ||||
| 		m_ansi_state.question = false; | ||||
| 		m_state = State::Normal; | ||||
| 	} | ||||
| 
 | ||||
| 	void VirtualTTY::handle_ansi_csi_color() | ||||
| 	{ | ||||
| 		switch (m_ansi_state.nums[0]) | ||||
| 		{ | ||||
| 			case -1: | ||||
| 			case 0: | ||||
| 				m_foreground = TerminalColor::BRIGHT_WHITE; | ||||
| 				m_background = TerminalColor::BLACK; | ||||
| 				break; | ||||
| 
 | ||||
| 			case 30: m_foreground = TerminalColor::BRIGHT_BLACK;	break; | ||||
| 			case 31: m_foreground = TerminalColor::BRIGHT_RED;		break; | ||||
| 			case 32: m_foreground = TerminalColor::BRIGHT_GREEN;	break; | ||||
| 			case 33: m_foreground = TerminalColor::BRIGHT_YELLOW;	break; | ||||
| 			case 34: m_foreground = TerminalColor::BRIGHT_BLUE;		break; | ||||
| 			case 35: m_foreground = TerminalColor::BRIGHT_MAGENTA;	break; | ||||
| 			case 36: m_foreground = TerminalColor::BRIGHT_CYAN;		break; | ||||
| 			case 37: m_foreground = TerminalColor::BRIGHT_WHITE;	break; | ||||
| 
 | ||||
| 			case 40: m_background = TerminalColor::BRIGHT_BLACK;	break; | ||||
| 			case 41: m_background = TerminalColor::BRIGHT_RED;		break; | ||||
| 			case 42: m_background = TerminalColor::BRIGHT_GREEN;	break; | ||||
| 			case 43: m_background = TerminalColor::BRIGHT_YELLOW;	break; | ||||
| 			case 44: m_background = TerminalColor::BRIGHT_BLUE;		break; | ||||
| 			case 45: m_background = TerminalColor::BRIGHT_MAGENTA;	break; | ||||
| 			case 46: m_background = TerminalColor::BRIGHT_CYAN;		break; | ||||
| 			case 47: m_background = TerminalColor::BRIGHT_WHITE;	break; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	void VirtualTTY::handle_ansi_csi(uint8_t ch) | ||||
| 	{ | ||||
| 		switch (ch) | ||||
| 		{ | ||||
| 			case '0': case '1': case '2': case '3': case '4': | ||||
| 			case '5': case '6': case '7': case '8': case '9': | ||||
| 			{ | ||||
| 				int32_t& val = m_ansi_state.nums[m_ansi_state.index]; | ||||
| 				val = (val == -1) ? (ch - '0') : (val * 10 + ch - '0'); | ||||
| 				return; | ||||
| 			} | ||||
| 			case ';': | ||||
| 				m_ansi_state.index++; | ||||
| 				return; | ||||
| 			case 'A': // Cursor Up
 | ||||
| 				if (m_ansi_state.nums[0] == -1) | ||||
| 					m_ansi_state.nums[0] = 1; | ||||
| 				m_row = BAN::Math::max<int32_t>(m_row - m_ansi_state.nums[0], 0); | ||||
| 				return reset_ansi(); | ||||
| 			case 'B': // Curson Down
 | ||||
| 				if (m_ansi_state.nums[0] == -1) | ||||
| 					m_ansi_state.nums[0] = 1; | ||||
| 				m_row = BAN::Math::min<int32_t>(m_row + m_ansi_state.nums[0], m_height - 1); | ||||
| 				return reset_ansi(); | ||||
| 			case 'C': // Cursor Forward
 | ||||
| 				if (m_ansi_state.nums[0] == -1) | ||||
| 					m_ansi_state.nums[0] = 1; | ||||
| 				m_column = BAN::Math::min<int32_t>(m_column + m_ansi_state.nums[0], m_width - 1); | ||||
| 				return reset_ansi(); | ||||
| 			case 'D': // Cursor Back
 | ||||
| 				if (m_ansi_state.nums[0] == -1) | ||||
| 					m_ansi_state.nums[0] = 1; | ||||
| 				m_column = BAN::Math::max<int32_t>(m_column - m_ansi_state.nums[0], 0); | ||||
| 				return reset_ansi(); | ||||
| 			case 'E': // Cursor Next Line
 | ||||
| 				if (m_ansi_state.nums[0] == -1) | ||||
| 					m_ansi_state.nums[0] = 1; | ||||
| 				m_row = BAN::Math::min<int32_t>(m_row + m_ansi_state.nums[0], m_height - 1); | ||||
| 				m_column = 0; | ||||
| 				return reset_ansi(); | ||||
| 			case 'F': // Cursor Previous Line
 | ||||
| 				if (m_ansi_state.nums[0] == -1) | ||||
| 					m_ansi_state.nums[0] = 1; | ||||
| 				m_row = BAN::Math::max<int32_t>(m_row - m_ansi_state.nums[0], 0); | ||||
| 				m_column = 0; | ||||
| 				return reset_ansi(); | ||||
| 			case 'G': // Cursor Horizontal Absolute
 | ||||
| 				if (m_ansi_state.nums[0] == -1) | ||||
| 					m_ansi_state.nums[0] = 1; | ||||
| 				m_column = BAN::Math::clamp<int32_t>(m_ansi_state.nums[0] - 1, 0, m_width - 1); | ||||
| 				return reset_ansi(); | ||||
| 			case 'H': // Cursor Position
 | ||||
| 				if (m_ansi_state.nums[0] == -1) | ||||
| 					m_ansi_state.nums[0] = 1; | ||||
| 				if (m_ansi_state.nums[1] == -1) | ||||
| 					m_ansi_state.nums[1] = 1; | ||||
| 				m_row = BAN::Math::clamp<int32_t>(m_ansi_state.nums[0] - 1, 0, m_height - 1); | ||||
| 				m_column = BAN::Math::clamp<int32_t>(m_ansi_state.nums[1] - 1, 0, m_width - 1); | ||||
| 				return reset_ansi(); | ||||
| 			case 'J': // Erase in Display
 | ||||
| 				if (m_ansi_state.nums[0] == -1 || m_ansi_state.nums[0] == 0) | ||||
| 				{ | ||||
| 					// Clear from cursor to the end of screen
 | ||||
| 					for (uint32_t i = m_column; i < m_width; i++) | ||||
| 						putchar_at(' ', i, m_row); | ||||
| 					for (uint32_t row = 0; row < m_height; row++) | ||||
| 						for (uint32_t col = 0; col < m_width; col++) | ||||
| 							putchar_at(' ', col, row); | ||||
| 				} | ||||
| 				else if (m_ansi_state.nums[0] == 1) | ||||
| 				{ | ||||
| 					// Clear from cursor to the beginning of screen
 | ||||
| 					for (uint32_t row = 0; row < m_row; row++) | ||||
| 						for (uint32_t col = 0; col < m_width; col++) | ||||
| 							putchar_at(' ', col, row); | ||||
| 					for (uint32_t i = 0; i <= m_column; i++) | ||||
| 						putchar_at(' ', i, m_row); | ||||
| 				} | ||||
| 				else if (m_ansi_state.nums[0] == 2 || m_ansi_state.nums[0] == 3) | ||||
| 				{ | ||||
| 					// Clean entire screen
 | ||||
| 					clear(); | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					dprintln("Unsupported ANSI CSI character J"); | ||||
| 				} | ||||
| 				 | ||||
| 				if (m_ansi_state.nums[0] == 3) | ||||
| 				{ | ||||
| 					// FIXME: Clear scroll backbuffer if/when added
 | ||||
| 				} | ||||
| 				return reset_ansi(); | ||||
| 			case 'K': // Erase in Line
 | ||||
| 				if (m_ansi_state.nums[0] == -1 || m_ansi_state.nums[0] == 0) | ||||
| 					for (uint32_t i = m_column; i < m_width; i++) | ||||
| 						putchar_at(' ', i, m_row); | ||||
| 				else | ||||
| 					dprintln("Unsupported ANSI CSI character K"); | ||||
| 				return reset_ansi(); | ||||
| 			case 'S': // Scroll Up
 | ||||
| 				dprintln("Unsupported ANSI CSI character S"); | ||||
| 				return reset_ansi(); | ||||
| 			case 'T': // Scroll Down
 | ||||
| 				dprintln("Unsupported ANSI CSI character T"); | ||||
| 				return reset_ansi(); | ||||
| 			case 'f': // Horizontal Vertical Position
 | ||||
| 				dprintln("Unsupported ANSI CSI character f"); | ||||
| 				return reset_ansi(); | ||||
| 			case 'm': | ||||
| 				handle_ansi_csi_color(); | ||||
| 				return reset_ansi(); | ||||
| 			case 's': | ||||
| 				m_saved_row = m_row; | ||||
| 				m_saved_column = m_column; | ||||
| 				return reset_ansi(); | ||||
| 			case 'u': | ||||
| 				m_row = m_saved_row; | ||||
| 				m_column = m_saved_column; | ||||
| 				return reset_ansi(); | ||||
| 
 | ||||
| 			case '?': | ||||
| 				if (m_ansi_state.index != 0 || m_ansi_state.nums[0] != -1) | ||||
| 				{ | ||||
| 					dprintln("invalid ANSI CSI ?"); | ||||
| 					return reset_ansi(); | ||||
| 				} | ||||
| 				m_ansi_state.question = true; | ||||
| 				return; | ||||
| 			case 'h': | ||||
| 			case 'l': | ||||
| 				if (!m_ansi_state.question || m_ansi_state.nums[0] != 25) | ||||
| 				{ | ||||
| 					dprintln("invalid ANSI CSI ?{}{}", m_ansi_state.nums[0], (char)ch); | ||||
| 					return reset_ansi(); | ||||
| 				} | ||||
| 				m_show_cursor = (ch == 'h'); | ||||
| 				return reset_ansi(); | ||||
| 			default: | ||||
| 				dprintln("Unsupported ANSI CSI character {}", ch); | ||||
| 				return reset_ansi(); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	void VirtualTTY::render_from_buffer(uint32_t x, uint32_t y) | ||||
| 	{ | ||||
| 		ASSERT(x < m_width && y < m_height); | ||||
| 		const auto& cell = m_buffer[y * m_width + x]; | ||||
| 		m_terminal_driver->putchar_at(cell.codepoint, x, y, cell.foreground, cell.background); | ||||
| 	} | ||||
| 
 | ||||
| 	void VirtualTTY::putchar_at(uint32_t codepoint, uint32_t x, uint32_t y) | ||||
| 	{ | ||||
| 		ASSERT(x < m_width && y < m_height); | ||||
| 		auto& cell = m_buffer[y * m_width + x]; | ||||
| 		cell.codepoint = codepoint; | ||||
| 		cell.foreground = m_foreground; | ||||
| 		cell.background = m_background; | ||||
| 		m_terminal_driver->putchar_at(codepoint, x, y, m_foreground, m_background); | ||||
| 	} | ||||
| 
 | ||||
| 	void VirtualTTY::putchar(uint8_t ch) | ||||
| 	{ | ||||
| 		ASSERT(m_lock.is_locked()); | ||||
| 		 | ||||
| 		uint32_t codepoint = ch; | ||||
| 
 | ||||
| 		switch (m_state) | ||||
| 		{ | ||||
| 			case State::Normal: | ||||
| 				if ((ch & 0x80) == 0) | ||||
| 					break; | ||||
| 				if ((ch & 0xE0) == 0xC0) | ||||
| 				{ | ||||
| 					m_utf8_state.codepoint = ch & 0x1F; | ||||
| 					m_utf8_state.bytes_missing = 1; | ||||
| 				} | ||||
| 				else if ((ch & 0xF0) == 0xE0) | ||||
| 				{ | ||||
| 					m_utf8_state.codepoint = ch & 0x0F; | ||||
| 					m_utf8_state.bytes_missing = 2; | ||||
| 				} | ||||
| 				else if ((ch & 0xF8) == 0xF0) | ||||
| 				{ | ||||
| 					m_utf8_state.codepoint = ch & 0x07; | ||||
| 					m_utf8_state.bytes_missing = 3; | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					dprintln("invalid utf8"); | ||||
| 				} | ||||
| 				m_state = State::WaitingUTF8; | ||||
| 				return; | ||||
| 			case State::WaitingAnsiEscape: | ||||
| 				if (ch == CSI) | ||||
| 					m_state = State::WaitingAnsiCSI; | ||||
| 				else | ||||
| 				{ | ||||
| 					dprintln("unsupported byte after ansi escape {2H}", (uint8_t)ch); | ||||
| 					reset_ansi(); | ||||
| 				} | ||||
| 				return; | ||||
| 			case State::WaitingAnsiCSI: | ||||
| 				handle_ansi_csi(ch); | ||||
| 				set_cursor_position(m_column, m_row); | ||||
| 				return; | ||||
| 			case State::WaitingUTF8: | ||||
| 				if ((ch & 0xC0) != 0x80) | ||||
| 				{ | ||||
| 					dprintln("invalid utf8"); | ||||
| 					m_state = State::Normal; | ||||
| 					return; | ||||
| 				} | ||||
| 				m_utf8_state.codepoint = (m_utf8_state.codepoint << 6) | (ch & 0x3F); | ||||
| 				m_utf8_state.bytes_missing--; | ||||
| 				if (m_utf8_state.bytes_missing) | ||||
| 					return; | ||||
| 				m_state = State::Normal; | ||||
| 				codepoint = m_utf8_state.codepoint; | ||||
| 				break; | ||||
| 			default: | ||||
| 				ASSERT_NOT_REACHED(); | ||||
| 		} | ||||
| 
 | ||||
| 		switch (codepoint) | ||||
| 		{ | ||||
| 			case BEL: // TODO
 | ||||
| 				break; | ||||
| 			case BS: | ||||
| 				if (m_column > 0) | ||||
| 					m_column--; | ||||
| 				break; | ||||
| 			case HT: | ||||
| 				m_column++; | ||||
| 				while (m_column % 8) | ||||
| 					m_column++; | ||||
| 				break; | ||||
| 			case LF: | ||||
| 				m_column = 0; | ||||
| 				m_row++; | ||||
| 				break; | ||||
| 			case FF: | ||||
| 				m_row++; | ||||
| 				break; | ||||
| 			case CR: | ||||
| 				m_column = 0; | ||||
| 				break; | ||||
| 			case ESC: | ||||
| 				m_state = State::WaitingAnsiEscape; | ||||
| 				break;; | ||||
| 			default: | ||||
| 				putchar_at(codepoint, m_column, m_row); | ||||
| 				m_column++; | ||||
| 				break; | ||||
| 		} | ||||
| 
 | ||||
| 		if (m_column >= m_width) | ||||
| 		{ | ||||
| 			m_column = 0; | ||||
| 			m_row++; | ||||
| 		} | ||||
| 
 | ||||
| 		while (m_row >= m_height) | ||||
| 		{ | ||||
| 			memmove(m_buffer, m_buffer + m_width, m_width * (m_height - 1) * sizeof(Cell)); | ||||
| 
 | ||||
| 			// Clear last line in buffer
 | ||||
| 			for (uint32_t x = 0; x < m_width; x++) | ||||
| 				m_buffer[(m_height - 1) * m_width + x] = { .foreground = m_foreground, .background = m_background, .codepoint = ' ' }; | ||||
| 
 | ||||
| 			// Render the whole buffer to the screen
 | ||||
| 			for (uint32_t y = 0; y < m_height; y++) | ||||
| 				for (uint32_t x = 0; x < m_width; x++) | ||||
| 					render_from_buffer(x, y); | ||||
| 
 | ||||
| 			m_column = 0; | ||||
| 			m_row--; | ||||
| 		} | ||||
| 
 | ||||
| 		set_cursor_position(m_column, m_row); | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  | @ -16,9 +16,9 @@ | |||
| #include <kernel/PIC.h> | ||||
| #include <kernel/Process.h> | ||||
| #include <kernel/Scheduler.h> | ||||
| #include <kernel/Serial.h> | ||||
| #include <kernel/Syscall.h> | ||||
| #include <kernel/Terminal/TTY.h> | ||||
| #include <kernel/Terminal/Serial.h> | ||||
| #include <kernel/Terminal/VirtualTTY.h> | ||||
| #include <kernel/Terminal/VesaTerminalDriver.h> | ||||
| #include <kernel/Timer/Timer.h> | ||||
| 
 | ||||
|  | @ -28,6 +28,7 @@ struct ParsedCommandLine | |||
| { | ||||
| 	bool force_pic		= false; | ||||
| 	bool disable_serial	= false; | ||||
| 	BAN::StringView console = "tty0"sv; | ||||
| 	BAN::StringView root; | ||||
| }; | ||||
| 
 | ||||
|  | @ -72,30 +73,11 @@ static void parse_command_line() | |||
| 			cmdline.disable_serial = true; | ||||
| 		else if (argument.size() > 5 && argument.substring(0, 5) == "root=") | ||||
| 			cmdline.root = argument.substring(5); | ||||
| 		else if (argument.size() > 8 && argument.substring(0, 8) == "console=") | ||||
| 			cmdline.console = argument.substring(8); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| struct Test | ||||
| { | ||||
| 	Test()							{ dprintln("construct (default)"); } | ||||
| 	Test(const Test&)				{ dprintln("construct (copy)"); } | ||||
| 	Test(Test&&)					{ dprintln("construct (move)"); } | ||||
| 	~Test()							{ dprintln("destruct"); } | ||||
| 	Test& operator=(const Test&)	{ dprintln("assign (copy)"); return *this; } | ||||
| 	Test& operator=(Test&&)			{ dprintln("assign (move)"); return *this; } | ||||
| }; | ||||
| 
 | ||||
| namespace BAN::Formatter | ||||
| { | ||||
| 
 | ||||
| 	template<typename F> | ||||
| 	void print_argument(F putc, const Test& test, const ValueFormat& format) | ||||
| 	{ | ||||
| 		print_argument(putc, &test, format); | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| extern "C" uint8_t g_userspace_start[]; | ||||
| extern "C" uint8_t g_userspace_end[]; | ||||
| 
 | ||||
|  | @ -112,7 +94,7 @@ extern "C" void kernel_main() | |||
| 		Serial::initialize(); | ||||
| 		dprintln("Serial output initialized"); | ||||
| 	} | ||||
| 	 | ||||
| 
 | ||||
| 	if (g_multiboot_magic != 0x2BADB002) | ||||
| 	{ | ||||
| 		dprintln("Invalid multiboot magic number"); | ||||
|  | @ -134,16 +116,12 @@ extern "C" void kernel_main() | |||
| 	Heap::initialize(); | ||||
| 	dprintln("Heap initialzed"); | ||||
| 
 | ||||
| 	TerminalDriver* terminal_driver = VesaTerminalDriver::create(); | ||||
| 	ASSERT(terminal_driver); | ||||
| 	dprintln("VESA initialized"); | ||||
| 	parse_command_line(); | ||||
| 	dprintln("command line parsed, root='{}', console='{}'", cmdline.root, cmdline.console); | ||||
| 
 | ||||
| 	MUST(ACPI::initialize()); | ||||
| 	dprintln("ACPI initialized"); | ||||
| 
 | ||||
| 	parse_command_line(); | ||||
| 	dprintln("command line parsed, root='{}'", cmdline.root); | ||||
| 
 | ||||
| 	InterruptController::initialize(cmdline.force_pic); | ||||
| 	dprintln("Interrupt controller initialized"); | ||||
| 
 | ||||
|  | @ -153,21 +131,34 @@ extern "C" void kernel_main() | |||
| 	DevFileSystem::initialize(); | ||||
| 	dprintln("devfs initialized"); | ||||
| 
 | ||||
| 	TTY* tty1 = new TTY(terminal_driver); | ||||
| 	ASSERT(tty1); | ||||
| 	dprintln("TTY initialized"); | ||||
| 	if (Serial::has_devices()) | ||||
| 	{ | ||||
| 		Serial::initialize_devices(); | ||||
| 		dprintln("Serial devices initialized"); | ||||
| 	} | ||||
| 
 | ||||
| 	TerminalDriver* terminal_driver = VesaTerminalDriver::create(); | ||||
| 	ASSERT(terminal_driver); | ||||
| 	dprintln("VESA initialized"); | ||||
| 
 | ||||
| 	auto vtty = MUST(VirtualTTY::create(terminal_driver)); | ||||
| 	dprintln("Virtual TTY initialized"); | ||||
| 
 | ||||
| 	auto console = MUST(DevFileSystem::get().root_inode()->directory_find_inode(cmdline.console)); | ||||
| 	ASSERT(console->is_tty()); | ||||
| 	((TTY*)console.ptr())->set_as_current(); | ||||
| 
 | ||||
| 	MUST(Scheduler::initialize()); | ||||
| 	dprintln("Scheduler initialized"); | ||||
| 
 | ||||
| 	Scheduler& scheduler = Scheduler::get(); | ||||
| 	Process::create_kernel(init2, tty1); | ||||
| 	Process::create_kernel(init2, nullptr); | ||||
| 	scheduler.start(); | ||||
| 
 | ||||
| 	ASSERT_NOT_REACHED(); | ||||
| } | ||||
| 
 | ||||
| static void init2(void* tty1) | ||||
| static void init2(void*) | ||||
| { | ||||
| 	using namespace Kernel; | ||||
| 	using namespace Kernel::Input; | ||||
|  | @ -184,7 +175,7 @@ static void init2(void* tty1) | |||
| 	if (auto res = PS2Controller::initialize(); res.is_error()) | ||||
| 		dprintln("{}", res.error()); | ||||
| 
 | ||||
| 	((TTY*)tty1)->initialize_device(); | ||||
| 	TTY::initialize_devices(); | ||||
| 
 | ||||
| 	MUST(Process::create_userspace({ 0, 0, 0, 0 }, "/usr/bin/init"sv)); | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue