diff --git a/thermosphere/src/drivers/arm/hvisor_drivers_arm_pl011.cpp b/thermosphere/src/drivers/arm/hvisor_drivers_arm_pl011.cpp
new file mode 100644
index 000000000..64ad9b015
--- /dev/null
+++ b/thermosphere/src/drivers/arm/hvisor_drivers_arm_pl011.cpp
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2019-2020 Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#pragma once
+
+#include "hvisor_drivers_arm_pl011.hpp"
+
+namespace ams::hvisor::drivers::arm {
+
+ void PL011::Initialize(u32 baudRate, u32 clkRate) const
+ {
+ /* The TRM (DDI0183) reads:
+ Program the control registers as follows:
+ 1. Disable the UART.
+ 2. Wait for the end of transmission or reception of the current character.
+ 3. Flush the transmit FIFO by disabling bit 4 (FEN) in the line control register
+ (UARTCLR_H).
+ 4. Reprogram the control register.
+ 5. Enable the UART.
+ */
+ // First, disable the UART. Flush the receive FIFO, wait for tx to complete, and disable both FIFOs.
+ m_regs->cr &= ~UARTCR_UARTEN;
+ while (!(m_regs->fr & UARTFR_RXFE)) {
+ m_regs->dr;
+ }
+ while (m_regs->fr & UARTFR_BUSY);
+ // This flushes the transmit FIFO:
+ m_regs->lcr_h &= ~UARTLCR_H_FEN;
+
+ // Set baudrate, Divisor = (Uart clock * 4) / baudrate; stored in IBRD|FBRD
+ u32 divisor = (4 * clkRate) / baudRate;
+ m_regs->ibrd = divisor >> 6;
+ m_regs->fbrd = divisor & 0x3F;
+
+ // Select FIFO fill levels for interrupts
+ m_regs->ifls = IFLS_RX4_8 | IFLS_TX4_8;
+
+ // FIFO Enabled / No Parity / 8 Data bit / One Stop Bit
+ m_regs->lcr_h = UARTLCR_H_FEN | UARTLCR_H_WLEN_8;
+
+ // Select the interrupts we want to have
+ // RX timeout and TX/RX fill interrupts
+ m_regs->imsc = RTI | RXI | RXI;
+
+ // Clear any pending errors
+ m_regs->ecr = 0;
+
+ // Clear all interrupts
+ m_regs->icr = ALL_INTERRUPTS;
+
+ // Enable tx, rx, and uart overall
+ m_regs->cr = UARTCR_RXE | UARTCR_TXE | UARTCR_UARTEN;
+ }
+
+ void PL011::WriteData(const void *buffer, size_t size) const
+ {
+ const u8 *buf8 = reinterpret_cast(buffer);
+ for (size_t i = 0; i < size; i++) {
+ while (m_regs->fr & UARTFR_TXFF); // while TX FIFO full
+ m_regs->dr = buf8[i];
+ }
+
+ }
+
+ void PL011::ReadData(void *buffer, size_t size) const
+ {
+ u8 *buf8 = reinterpret_cast(buffer);
+ size_t i;
+
+ for (i = 0; i < size; i++) {
+ while (m_regs->fr & UARTFR_RXFE);
+ buf8[i] = m_regs->dr;
+ }
+
+ }
+
+ size_t PL011::ReadDataMax(void *buffer, size_t maxSize) const
+ {
+ u8 *buf8 = reinterpret_cast(buffer);
+ size_t count = 0;
+
+ for (size_t i = 0; i < maxSize && !(m_regs->fr & UARTFR_RXFE); i++) {
+ buf8[i] = m_regs->dr;
+ ++count;
+ }
+
+ return count;
+
+ }
+
+ size_t PL011::ReadDataUntil(char *buffer, size_t maxSize, char delimiter) const
+ {
+ size_t count = 0;
+
+ for (size_t i = 0; i < maxSize; i++) {
+ while (m_regs->fr & UARTFR_RXFE);
+ buffer[i] = m_regs->dr;
+ ++count;
+ if (buffer[i] == delimiter) {
+ break;
+ }
+ }
+
+ return count;
+ }
+
+ void PL011::SetRxInterruptEnabled(bool enabled) const
+ {
+ constexpr u32 mask = RTI | RXI;
+
+ if (enabled) {
+ m_regs->imsc |= mask;
+ } else {
+ m_regs->imsc &= ~mask;
+ }
+ }
+
+}
diff --git a/thermosphere/src/drivers/arm/hvisor_drivers_arm_pl011.hpp b/thermosphere/src/drivers/arm/hvisor_drivers_arm_pl011.hpp
new file mode 100644
index 000000000..bf11c7109
--- /dev/null
+++ b/thermosphere/src/drivers/arm/hvisor_drivers_arm_pl011.hpp
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2019-2020 Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#pragma once
+
+#include "../../defines.hpp"
+
+// AMBA PL011 driver
+// Originally from
+/*
+ * Copyright (c) 2013-2018, ARM Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+namespace ams::hvisor::drivers::arm {
+
+ class PL011 final {
+ private:
+ struct Registers {
+ u32 dr;
+ union {
+ u32 sr;
+ u32 ecr;
+ };
+ u32 _0x08, _0x0C, _0x10, _0x14;
+ u32 fr;
+ u32 _0x1C;
+ u32 ilpr;
+ u32 ibrd;
+ u32 fbrd;
+ u32 lcr_h;
+ u32 cr;
+ u32 ifls;
+ u32 imsc;
+ u32 ris;
+ u32 mis;
+ u32 icr;
+ u32 dmacr;
+ };
+
+ enum Mask : u32 {
+ DATA_ERROR_MASK = 0x0F00, // Data status bits
+ PL011_STATUS_ERROR_MASK = 0x0F, // Status reg bits
+ };
+
+ enum Error : u32 {
+ OE = BIT(3), // Overrun error
+ BE = BIT(2), // Break error
+ PE = BIT(1), // Parity error
+ FE = BIT(0), // Framing error
+ };
+
+ enum Interrupt : u32 {
+ OEI = BIT(10), // Overrun error interrupt
+ BEI = BIT(9), // Break error interrupt
+ PEI = BIT(8), // Parity error interrupt
+ FEI = BIT(7), // Framing error interrupt
+ RTI = BIT(6), // Receive timeout interrupt
+ TXI = BIT(5), // Transmit interrupt
+ RXI = BIT(4), // Receive interrupt
+ DSRMI = BIT(3), // DSR modem interrupt
+ DCDMI = BIT(2), // DCD modem interrupt
+ CTSMI = BIT(1), // CTS modem interrupt
+ RIMI = BIT(0), // RI modem interrupt
+ ALL_INTERRUPTS = MASK(11),
+ };
+
+ // Flag reg bits
+ enum FrFlags : u32 {
+ UARTFR_RI = BIT(8), // Ring indicator
+ UARTFR_TXFE = BIT(7), // Transmit FIFO empty
+ UARTFR_RXFF = BIT(6), // Receive FIFO full
+ UARTFR_TXFF = BIT(5), // Transmit FIFO full
+ UARTFR_RXFE = BIT(4), // Receive FIFO empty
+ UARTFR_BUSY = BIT(3), // UART busy
+ UARTFR_DCD = BIT(2), // Data carrier detect
+ UARTFR_DSR = BIT(1), // Data set ready
+ UARTFR_CTS = BIT(0), // Clear to send
+ };
+
+ // Control reg bits
+ enum CrFlags : u32 {
+ UARTCR_CTSEN = BIT(15), // CTS hardware flow control enable
+ UARTCR_RTSEN = BIT(14), // RTS hardware flow control enable
+ UARTCR_RTS = BIT(11), // Request to send
+ UARTCR_DTR = BIT(10), // Data transmit ready.
+ UARTCR_RXE = BIT(9), // Receive enable
+ UARTCR_TXE = BIT(8), // Transmit enable
+ UARTCR_LBE = BIT(7), // Loopback enable
+ UARTCR_UARTEN = BIT(0), // UART Enable
+ };
+
+ // Line Control Register Bits
+ enum LcrFlags : u32 {
+ UARTLCR_H_SPS = BIT(7), // Stick parity select
+ UARTLCR_H_WLEN_8 = (3 << 5),
+ UARTLCR_H_WLEN_7 = (2 << 5),
+ UARTLCR_H_WLEN_6 = BIT(5),
+ UARTLCR_H_WLEN_5 = (0 << 5),
+ UARTLCR_H_FEN = BIT(4), // FIFOs Enable
+ UARTLCR_H_STP2 = BIT(3), // Two stop bits select
+ UARTLCR_H_EPS = BIT(2), // Even parity select
+ UARTLCR_H_PEN = BIT(1), // Parity Enable
+ UARTLCR_H_BRK = BIT(0), // Send break
+ };
+
+ // FIFO level select register
+ enum IflsLevels : u32 {
+ IFLS_RX1_8 = (0 << 3),
+ IFLS_RX2_8 = (1 << 3),
+ IFLS_RX4_8 = (2 << 3),
+ IFLS_RX6_8 = (3 << 3),
+ IFLS_RX7_8 = (4 << 3),
+ IFLS_TX1_8 = (0 << 0),
+ IFLS_TX2_8 = (1 << 0),
+ IFLS_TX4_8 = (2 << 0),
+ IFLS_TX6_8 = (3 << 0),
+ IFLS_TX7_8 = (4 << 0),
+ };
+
+ private:
+ volatile Registers *m_regs = nullptr;
+ void Initialize(u32 baudRate, u32 clkRate = 1) const;
+
+ // TODO friend
+ public:
+
+ void WriteData(const void *buffer, size_t size) const;
+ void ReadData(void *buffer, size_t size) const;
+ size_t ReadDataMax(void *buffer, size_t maxSize) const;
+ size_t ReadDataUntil(char *buffer, size_t maxSize, char delimiter) const;
+
+ void SetRxInterruptEnabled(bool enabled) const;
+ };
+}