summaryrefslogtreecommitdiff
path: root/Kernel/VirtIO
diff options
context:
space:
mode:
authorx-yl <kylepereira@mail.com>2021-06-23 15:19:24 +0400
committerAli Mohammad Pur <Ali.mpfard@gmail.com>2021-07-09 13:19:21 +0430
commit1fe08759e3abe40b4a91e244db9d5affb59e3e9f (patch)
tree3e5ea8ee9b38626914eabc98ccc4377da7d34daa /Kernel/VirtIO
parent1492bb2fd6893e2a39d461c3c8a12ec6cf4e7ffd (diff)
downloadserenity-1fe08759e3abe40b4a91e244db9d5affb59e3e9f.zip
Kernel: Support multiport for VirtIOConsole
This involves refactoring VirtIOConsole into VirtIOConsole and VirtIOConsolePort. VirtIOConsole is the VirtIODevice, it owns multiple VirtIOConsolePorts as well as two control queues. Each VirtIOConsolePort is a CharacterDevice.
Diffstat (limited to 'Kernel/VirtIO')
-rw-r--r--Kernel/VirtIO/VirtIOConsole.cpp224
-rw-r--r--Kernel/VirtIO/VirtIOConsole.h75
-rw-r--r--Kernel/VirtIO/VirtIOConsolePort.cpp167
-rw-r--r--Kernel/VirtIO/VirtIOConsolePort.h63
4 files changed, 409 insertions, 120 deletions
diff --git a/Kernel/VirtIO/VirtIOConsole.cpp b/Kernel/VirtIO/VirtIOConsole.cpp
index b443d171dd..b1df8def1b 100644
--- a/Kernel/VirtIO/VirtIOConsole.cpp
+++ b/Kernel/VirtIO/VirtIOConsole.cpp
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2021, the SerenityOS developers.
+ * Copyright (c) 2021, Kyle Pereira <hey@xylepereira.me>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@@ -12,8 +13,8 @@ namespace Kernel {
unsigned VirtIOConsole::next_device_id = 0;
UNMAP_AFTER_INIT VirtIOConsole::VirtIOConsole(PCI::Address address)
- : CharacterDevice(229, next_device_id++)
- , VirtIODevice(address, "VirtIOConsole")
+ : VirtIODevice(address, "VirtIOConsole")
+ , m_device_id(next_device_id++)
{
if (auto cfg = get_config(ConfigurationType::Device)) {
bool success = negotiate_features([&](u64 supported_features) {
@@ -21,7 +22,7 @@ UNMAP_AFTER_INIT VirtIOConsole::VirtIOConsole(PCI::Address address)
if (is_feature_set(supported_features, VIRTIO_CONSOLE_F_SIZE))
dbgln("VirtIOConsole: Console size is not yet supported!");
if (is_feature_set(supported_features, VIRTIO_CONSOLE_F_MULTIPORT))
- dbgln("VirtIOConsole: Multi port is not yet supported!");
+ negotiated |= VIRTIO_CONSOLE_F_MULTIPORT;
return negotiated;
});
if (success) {
@@ -34,37 +35,24 @@ UNMAP_AFTER_INIT VirtIOConsole::VirtIOConsole(PCI::Address address)
}
if (is_feature_accepted(VIRTIO_CONSOLE_F_MULTIPORT)) {
max_nr_ports = config_read32(*cfg, 0x4);
+ m_ports.resize(max_nr_ports);
}
});
dbgln("VirtIOConsole: cols: {}, rows: {}, max nr ports {}", cols, rows, max_nr_ports);
- success = setup_queues(2 + max_nr_ports * 2); // base receiveq/transmitq for port0 + 2 per every additional port
+ // Base receiveq/transmitq for port0 + optional control queues and 2 per every additional port
+ success = setup_queues(2 + max_nr_ports > 0 ? 2 + 2 * max_nr_ports : 0);
}
if (success) {
finish_init();
- m_receive_buffer = make<RingBuffer>("VirtIOConsole Receive", RINGBUFFER_SIZE);
- m_transmit_buffer = make<RingBuffer>("VirtIOConsole Transmit", RINGBUFFER_SIZE);
- init_receive_buffer();
+ if (is_feature_accepted(VIRTIO_CONSOLE_F_MULTIPORT))
+ setup_multiport();
+ else
+ m_ports.append(new VirtIOConsolePort(0u, *this));
}
}
}
-VirtIOConsole::~VirtIOConsole()
-{
-}
-
-void VirtIOConsole::init_receive_buffer()
-{
- auto& queue = get_queue(RECEIVEQ);
- ScopedSpinLock queue_lock(queue.lock());
- VirtIOQueueChain chain(queue);
-
- auto buffer_start = m_receive_buffer->start_of_region();
- auto did_add_buffer = chain.add_buffer_to_chain(buffer_start, RINGBUFFER_SIZE, BufferType::DeviceWritable);
- VERIFY(did_add_buffer);
- supply_chain_and_notify(RECEIVEQ, chain);
-}
-
bool VirtIOConsole::handle_device_config_change()
{
dbgln("VirtIOConsole: Handle device config change");
@@ -73,112 +61,158 @@ bool VirtIOConsole::handle_device_config_change()
void VirtIOConsole::handle_queue_update(u16 queue_index)
{
- dbgln_if(VIRTIO_DEBUG, "VirtIOConsole: Handle queue update");
- VERIFY(queue_index <= TRANSMITQ);
- switch (queue_index) {
- case RECEIVEQ: {
- auto& queue = get_queue(RECEIVEQ);
+ dbgln_if(VIRTIO_DEBUG, "VirtIOConsole: Handle queue update {}", queue_index);
+
+ if (queue_index == CONTROL_RECEIVEQ) {
+ ScopedSpinLock ringbuffer_lock(m_control_receive_buffer->lock());
+ auto& queue = get_queue(CONTROL_RECEIVEQ);
ScopedSpinLock queue_lock(queue.lock());
size_t used;
VirtIOQueueChain popped_chain = queue.pop_used_buffer_chain(used);
- ScopedSpinLock ringbuffer_lock(m_receive_buffer->lock());
-
- auto used_space = m_receive_buffer->reserve_space(used).value();
- auto remaining_space = RINGBUFFER_SIZE - used;
-
- // Our algorithm always has only one buffer in the queue.
- VERIFY(!queue.new_data_available());
- popped_chain.release_buffer_slots_to_queue();
+ while (!popped_chain.is_empty()) {
+ popped_chain.for_each([&](auto addr, auto) {
+ auto offset = addr.as_ptr() - m_control_receive_buffer->start_of_region().as_ptr();
+ auto* message = reinterpret_cast<ControlMessage*>(m_control_receive_buffer->vaddr().offset(offset).as_ptr());
+ process_control_message(*message);
+ });
- VirtIOQueueChain new_chain(queue);
- if (remaining_space != 0) {
- new_chain.add_buffer_to_chain(used_space.offset(used), remaining_space, BufferType::DeviceWritable);
- supply_chain_and_notify(RECEIVEQ, new_chain);
+ supply_chain_and_notify(CONTROL_RECEIVEQ, popped_chain);
+ popped_chain = queue.pop_used_buffer_chain(used);
}
-
- evaluate_block_conditions();
- break;
- }
- case TRANSMITQ: {
- ScopedSpinLock ringbuffer_lock(m_transmit_buffer->lock());
- auto& queue = get_queue(TRANSMITQ);
+ } else if (queue_index == CONTROL_TRANSMITQ) {
+ ScopedSpinLock ringbuffer_lock(m_control_transmit_buffer->lock());
+ auto& queue = get_queue(CONTROL_TRANSMITQ);
ScopedSpinLock queue_lock(queue.lock());
size_t used;
VirtIOQueueChain popped_chain = queue.pop_used_buffer_chain(used);
+ auto number_of_messages = 0;
do {
popped_chain.for_each([this](PhysicalAddress address, size_t length) {
- m_transmit_buffer->reclaim_space(address, length);
+ m_control_transmit_buffer->reclaim_space(address, length);
});
popped_chain.release_buffer_slots_to_queue();
popped_chain = queue.pop_used_buffer_chain(used);
+ number_of_messages++;
} while (!popped_chain.is_empty());
- // Unblock any IO tasks that were blocked because can_write() returned false
- evaluate_block_conditions();
- break;
- }
- default:
- VERIFY_NOT_REACHED();
+ m_control_wait_queue.wake_n(number_of_messages);
+ } else {
+ u32 port_index = queue_index < 2 ? 0 : (queue_index - 2) / 2;
+ if (port_index >= m_ports.size() || !m_ports.at(port_index)) {
+ dbgln("Invalid queue_index {}", queue_index);
+ return;
+ }
+ m_ports.at(port_index)->handle_queue_update({}, queue_index);
}
}
-bool VirtIOConsole::can_read(const FileDescription&, size_t) const
+void VirtIOConsole::setup_multiport()
{
- return m_receive_buffer->used_bytes() > 0;
+ m_control_receive_buffer = make<RingBuffer>("VirtIOConsole control receive queue", CONTROL_BUFFER_SIZE);
+ m_control_transmit_buffer = make<RingBuffer>("VirtIOConsole control transmit queue", CONTROL_BUFFER_SIZE);
+
+ auto& queue = get_queue(CONTROL_RECEIVEQ);
+ ScopedSpinLock queue_lock(queue.lock());
+ VirtIOQueueChain chain(queue);
+ auto offset = 0ul;
+
+ while (offset < CONTROL_BUFFER_SIZE) {
+ auto buffer_start = m_control_receive_buffer->start_of_region().offset(offset);
+ auto did_add_buffer = chain.add_buffer_to_chain(buffer_start, CONTROL_MESSAGE_SIZE, BufferType::DeviceWritable);
+ VERIFY(did_add_buffer);
+ offset += CONTROL_MESSAGE_SIZE;
+ supply_chain_and_notify(CONTROL_RECEIVEQ, chain);
+ }
+
+ ControlMessage ready_event {
+ .id = 0, // Unused
+ .event = (u16)ControlEvent::DeviceReady,
+ .value = (u16)ControlMessage::Status::Success
+ };
+ write_control_message(ready_event);
}
-KResultOr<size_t> VirtIOConsole::read(FileDescription& desc, u64, UserOrKernelBuffer& buffer, size_t size)
+void VirtIOConsole::process_control_message(ControlMessage message)
{
- if (!size)
- return 0;
-
- if (!can_read(desc, size))
- return EAGAIN;
+ switch (message.event) {
+ case (u16)ControlEvent::DeviceAdd: {
+ u32 id = message.id;
+ if (id >= m_ports.size()) {
+ dbgln("Device provided an invalid port number {}. max_nr_ports: {}", id, m_ports.size());
+ return;
+ } else if (!m_ports.at(id).is_null()) {
+ dbgln("Device tried to add port {} which was already added!", id);
+ return;
+ }
- ScopedSpinLock ringbuffer_lock(m_receive_buffer->lock());
+ m_ports.at(id) = new VirtIOConsolePort(id, *this);
+ ControlMessage ready_event {
+ .id = static_cast<u32>(id),
+ .event = (u16)ControlEvent::PortReady,
+ .value = (u16)ControlMessage::Status::Success
+ };
- auto bytes_copied = m_receive_buffer->copy_data_out(size, buffer);
- m_receive_buffer->reclaim_space(m_receive_buffer->start_of_used(), bytes_copied.value());
+ write_control_message(ready_event);
+ break;
+ }
+ case (u16)ControlEvent::ConsolePort:
+ case (u16)ControlEvent::PortOpen: {
+ if (message.id >= m_ports.size()) {
+ dbgln("Device provided an invalid port number {}. max_nr_ports: {}", message.id, m_ports.size());
+ return;
+ } else if (m_ports.at(message.id).is_null()) {
+ dbgln("Device tried to open port {} which was not added!", message.id);
+ return;
+ }
- return bytes_copied;
+ if (message.value == (u16)ControlMessage::PortStatus::Open) {
+ auto is_open = m_ports.at(message.id)->is_open();
+ if (!is_open) {
+ m_ports.at(message.id)->set_open({}, true);
+ send_open_control_message(message.id, true);
+ }
+ } else if (message.value == (u16)ControlMessage::PortStatus::Close) {
+ m_ports.at(message.id)->set_open({}, false);
+ } else {
+ dbgln("Device specified invalid value {}. Must be 0 or 1.", message.value);
+ }
+ break;
+ }
+ default:
+ dbgln("Unhandled message event {}!", message.event);
+ }
}
-
-bool VirtIOConsole::can_write(const FileDescription&, size_t) const
+void VirtIOConsole::write_control_message(ControlMessage message)
{
- return get_queue(TRANSMITQ).has_free_slots() && m_transmit_buffer->has_space();
-}
+ ScopedSpinLock ringbuffer_lock(m_control_transmit_buffer->lock());
-KResultOr<size_t> VirtIOConsole::write(FileDescription& desc, u64, const UserOrKernelBuffer& data, size_t size)
-{
- if (!size)
- return 0;
+ PhysicalAddress start_of_chunk;
+ size_t length_of_chunk;
- if (!can_write(desc, size))
- return EAGAIN;
+ auto data = UserOrKernelBuffer::for_kernel_buffer((u8*)&message);
+ while (!m_control_transmit_buffer->copy_data_in(data, 0, sizeof(message), start_of_chunk, length_of_chunk)) {
+ ringbuffer_lock.unlock();
+ m_control_wait_queue.wait_forever();
+ ringbuffer_lock.lock();
+ }
- ScopedSpinLock ringbuffer_lock(m_transmit_buffer->lock());
- auto& queue = get_queue(TRANSMITQ);
+ auto& queue = get_queue(CONTROL_TRANSMITQ);
ScopedSpinLock queue_lock(queue.lock());
VirtIOQueueChain chain(queue);
- size_t total_bytes_copied = 0;
- do {
- PhysicalAddress start_of_chunk;
- size_t length_of_chunk;
-
- if (!m_transmit_buffer->copy_data_in(data, total_bytes_copied, size - total_bytes_copied, start_of_chunk, length_of_chunk)) {
- chain.release_buffer_slots_to_queue();
- return EINVAL;
- }
-
- bool did_add_buffer = chain.add_buffer_to_chain(start_of_chunk, length_of_chunk, BufferType::DeviceReadable);
- VERIFY(did_add_buffer);
- total_bytes_copied += length_of_chunk;
- } while (total_bytes_copied < size && can_write(desc, size - total_bytes_copied));
-
- supply_chain_and_notify(TRANSMITQ, chain);
+ bool did_add_buffer = chain.add_buffer_to_chain(start_of_chunk, length_of_chunk, BufferType::DeviceReadable);
+ VERIFY(did_add_buffer);
- return total_bytes_copied;
+ supply_chain_and_notify(CONTROL_TRANSMITQ, chain);
}
+void VirtIOConsole::send_open_control_message(unsigned port_number, bool open)
+{
+ ControlMessage port_open {
+ .id = static_cast<u32>(port_number),
+ .event = (u16)ControlEvent::PortOpen,
+ .value = open
+ };
+ write_control_message(port_open);
+}
}
diff --git a/Kernel/VirtIO/VirtIOConsole.h b/Kernel/VirtIO/VirtIOConsole.h
index 2514352826..267c448b2b 100644
--- a/Kernel/VirtIO/VirtIOConsole.h
+++ b/Kernel/VirtIO/VirtIOConsole.h
@@ -1,4 +1,5 @@
/*
+ * Copyright (c) 2021, Kyle Pereira <hey@xylepereira.me>
* Copyright (c) 2021, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
@@ -6,48 +7,72 @@
#pragma once
-#include <Kernel/Devices/CharacterDevice.h>
#include <Kernel/VM/RingBuffer.h>
#include <Kernel/VirtIO/VirtIO.h>
+#include <Kernel/VirtIO/VirtIOConsolePort.h>
namespace Kernel {
+class VirtIOConsole
+ : public VirtIODevice
+ , public RefCounted<VirtIOConsole> {
+ friend VirtIOConsolePort;
-#define VIRTIO_CONSOLE_F_SIZE (1 << 0)
-#define VIRTIO_CONSOLE_F_MULTIPORT (1 << 1)
-#define VIRTIO_CONSOLE_F_EMERG_WRITE (1 << 2)
-
-#define RECEIVEQ 0
-#define TRANSMITQ 1
-
-class VirtIOConsole final : public CharacterDevice
- , public VirtIODevice {
public:
VirtIOConsole(PCI::Address);
- virtual ~VirtIOConsole() override;
+ virtual ~VirtIOConsole() override = default;
- virtual const char* purpose() const override { return class_name(); }
+ virtual const char* purpose() const override { return "VirtIOConsole"; }
+
+ unsigned device_id() const
+ {
+ return m_device_id;
+ }
private:
- constexpr static size_t RINGBUFFER_SIZE = 2 * PAGE_SIZE;
- virtual const char* class_name() const override { return m_class_name.characters(); }
+ enum class ControlEvent : u16 {
+ DeviceReady = 0,
+ DeviceAdd = 1,
+ PortReady = 3,
+ ConsolePort = 4,
+ PortOpen = 6,
+ };
+ struct [[gnu::packed]] ControlMessage {
+ u32 id;
+ u16 event;
+ u16 value;
+
+ enum class Status : u16 {
+ Success = 1,
+ Failure = 0
+ };
- virtual bool can_read(const FileDescription&, size_t) const override;
- virtual KResultOr<size_t> read(FileDescription&, u64, UserOrKernelBuffer&, size_t) override;
- virtual bool can_write(const FileDescription&, size_t) const override;
- virtual KResultOr<size_t> write(FileDescription&, u64, const UserOrKernelBuffer&, size_t) override;
+ enum class PortStatus : u16 {
+ Open = 1,
+ Close = 0
+ };
+ };
- virtual mode_t required_mode() const override { return 0666; }
+ constexpr static u16 CONTROL_RECEIVEQ = 2;
+ constexpr static u16 CONTROL_TRANSMITQ = 3;
+ constexpr static size_t CONTROL_MESSAGE_SIZE = sizeof(ControlMessage);
+ constexpr static size_t CONTROL_BUFFER_SIZE = CONTROL_MESSAGE_SIZE * 32;
virtual bool handle_device_config_change() override;
- virtual String device_name() const override { return String::formatted("hvc{}", minor()); }
virtual void handle_queue_update(u16 queue_index) override;
- void init_receive_buffer();
-
- OwnPtr<RingBuffer> m_receive_buffer;
- OwnPtr<RingBuffer> m_transmit_buffer;
+ Vector<RefPtr<VirtIOConsolePort>> m_ports;
+ void setup_multiport();
+ void process_control_message(ControlMessage message);
+ void write_control_message(ControlMessage message);
+ void send_open_control_message(unsigned port_number, bool open);
+
+ unsigned m_device_id;
+
+ OwnPtr<RingBuffer> m_control_transmit_buffer;
+ OwnPtr<RingBuffer> m_control_receive_buffer;
+
+ WaitQueue m_control_wait_queue;
static unsigned next_device_id;
};
-
}
diff --git a/Kernel/VirtIO/VirtIOConsolePort.cpp b/Kernel/VirtIO/VirtIOConsolePort.cpp
new file mode 100644
index 0000000000..e51fbdb260
--- /dev/null
+++ b/Kernel/VirtIO/VirtIOConsolePort.cpp
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2021, the SerenityOS developers.
+ * Copyright (c) 2021, Kyle Pereira <hey@xylepereira.me>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <Kernel/VirtIO/VirtIOConsole.h>
+#include <Kernel/VirtIO/VirtIOConsolePort.h>
+
+namespace Kernel {
+
+unsigned VirtIOConsolePort::next_device_id = 0;
+
+VirtIOConsolePort::VirtIOConsolePort(unsigned port, VirtIOConsole& console)
+ : CharacterDevice(229, next_device_id++)
+ , m_console(console)
+ , m_port(port)
+{
+ m_receive_buffer = make<RingBuffer>("VirtIOConsolePort Receive", RINGBUFFER_SIZE);
+ m_transmit_buffer = make<RingBuffer>("VirtIOConsolePort Transmit", RINGBUFFER_SIZE);
+ m_receive_queue = m_port == 0 ? 0 : m_port * 2 + 2;
+ m_transmit_queue = m_port == 0 ? 1 : m_port * 2 + 3;
+ init_receive_buffer();
+}
+
+void VirtIOConsolePort::init_receive_buffer()
+{
+ auto& queue = m_console.get_queue(m_receive_queue);
+ ScopedSpinLock queue_lock(queue.lock());
+ VirtIOQueueChain chain(queue);
+
+ auto buffer_start = m_receive_buffer->start_of_region();
+ auto did_add_buffer = chain.add_buffer_to_chain(buffer_start, RINGBUFFER_SIZE, BufferType::DeviceWritable);
+ VERIFY(did_add_buffer);
+ m_console.supply_chain_and_notify(m_receive_queue, chain);
+}
+
+void VirtIOConsolePort::handle_queue_update(Badge<VirtIOConsole>, u16 queue_index)
+{
+ dbgln_if(VIRTIO_DEBUG, "VirtIOConsolePort: Handle queue update for port {}", m_port);
+ VERIFY(queue_index == m_transmit_queue || queue_index == m_receive_queue);
+ if (queue_index == m_receive_queue) {
+ auto& queue = m_console.get_queue(m_receive_queue);
+ ScopedSpinLock queue_lock(queue.lock());
+ size_t used;
+ VirtIOQueueChain popped_chain = queue.pop_used_buffer_chain(used);
+
+ ScopedSpinLock ringbuffer_lock(m_receive_buffer->lock());
+ auto used_space = m_receive_buffer->reserve_space(used).value();
+ auto remaining_space = m_receive_buffer->bytes_till_end();
+
+ // Our algorithm always has only one buffer in the queue.
+ VERIFY(popped_chain.length() == 1);
+ VERIFY(!queue.new_data_available());
+ popped_chain.release_buffer_slots_to_queue();
+
+ VirtIOQueueChain new_chain(queue);
+ if (remaining_space != 0) {
+ new_chain.add_buffer_to_chain(used_space.offset(used), remaining_space, BufferType::DeviceWritable);
+ m_console.supply_chain_and_notify(m_receive_queue, new_chain);
+ } else {
+ m_receive_buffer_exhausted = true;
+ }
+
+ evaluate_block_conditions();
+ } else {
+ ScopedSpinLock ringbuffer_lock(m_transmit_buffer->lock());
+ auto& queue = m_console.get_queue(m_transmit_queue);
+ ScopedSpinLock queue_lock(queue.lock());
+ size_t used;
+ VirtIOQueueChain popped_chain = queue.pop_used_buffer_chain(used);
+ do {
+ popped_chain.for_each([this](PhysicalAddress address, size_t length) {
+ m_transmit_buffer->reclaim_space(address, length);
+ });
+ popped_chain.release_buffer_slots_to_queue();
+ popped_chain = queue.pop_used_buffer_chain(used);
+ } while (!popped_chain.is_empty());
+ // Unblock any IO tasks that were blocked because can_write() returned false
+ evaluate_block_conditions();
+ }
+}
+
+bool VirtIOConsolePort::can_read(const FileDescription&, size_t) const
+{
+ return m_receive_buffer->used_bytes() > 0;
+}
+
+KResultOr<size_t> VirtIOConsolePort::read(FileDescription& desc, u64, UserOrKernelBuffer& buffer, size_t size)
+{
+ if (!size)
+ return 0;
+
+ ScopedSpinLock ringbuffer_lock(m_receive_buffer->lock());
+
+ if (!can_read(desc, size))
+ return EAGAIN;
+
+ auto bytes_copied = m_receive_buffer->copy_data_out(size, buffer).value();
+ m_receive_buffer->reclaim_space(m_receive_buffer->start_of_used(), bytes_copied);
+
+ if (m_receive_buffer_exhausted && m_receive_buffer->used_bytes() == 0) {
+ auto& queue = m_console.get_queue(m_receive_queue);
+ ScopedSpinLock queue_lock(queue.lock());
+ VirtIOQueueChain new_chain(queue);
+ new_chain.add_buffer_to_chain(m_receive_buffer->start_of_region(), RINGBUFFER_SIZE, BufferType::DeviceWritable);
+ m_console.supply_chain_and_notify(m_receive_queue, new_chain);
+ m_receive_buffer_exhausted = false;
+ }
+
+ return bytes_copied;
+}
+
+bool VirtIOConsolePort::can_write(const FileDescription&, size_t) const
+{
+ return m_console.get_queue(m_transmit_queue).has_free_slots() && m_transmit_buffer->has_space();
+}
+
+KResultOr<size_t> VirtIOConsolePort::write(FileDescription& desc, u64, const UserOrKernelBuffer& data, size_t size)
+{
+ if (!size)
+ return 0;
+
+ ScopedSpinLock ringbuffer_lock(m_transmit_buffer->lock());
+ auto& queue = m_console.get_queue(m_transmit_queue);
+ ScopedSpinLock queue_lock(queue.lock());
+
+ if (!can_write(desc, size))
+ return EAGAIN;
+
+ VirtIOQueueChain chain(queue);
+
+ size_t total_bytes_copied = 0;
+ do {
+ PhysicalAddress start_of_chunk;
+ size_t length_of_chunk;
+
+ if (!m_transmit_buffer->copy_data_in(data, total_bytes_copied, size - total_bytes_copied, start_of_chunk, length_of_chunk)) {
+ chain.release_buffer_slots_to_queue();
+ return EINVAL;
+ }
+
+ bool did_add_buffer = chain.add_buffer_to_chain(start_of_chunk, length_of_chunk, BufferType::DeviceReadable);
+ VERIFY(did_add_buffer);
+ total_bytes_copied += length_of_chunk;
+ } while (total_bytes_copied < size && can_write(desc, size));
+
+ m_console.supply_chain_and_notify(m_transmit_queue, chain);
+
+ return total_bytes_copied;
+}
+
+String VirtIOConsolePort::device_name() const
+{
+ return String::formatted("hvc{}p{}", m_console.device_id(), m_port);
+}
+
+KResultOr<NonnullRefPtr<FileDescription>> VirtIOConsolePort::open(int options)
+{
+ if (m_open)
+ m_console.send_open_control_message(m_port, true);
+
+ return File::open(options);
+}
+
+}
diff --git a/Kernel/VirtIO/VirtIOConsolePort.h b/Kernel/VirtIO/VirtIOConsolePort.h
new file mode 100644
index 0000000000..6390a4ae77
--- /dev/null
+++ b/Kernel/VirtIO/VirtIOConsolePort.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2021, the SerenityOS developers.
+ * Copyright (c) 2021, Kyle Pereira <hey@xylepereira.me>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <Kernel/Devices/CharacterDevice.h>
+#include <Kernel/FileSystem/FileDescription.h>
+#include <Kernel/VM/RingBuffer.h>
+#include <Kernel/VirtIO/VirtIO.h>
+
+namespace Kernel {
+
+class VirtIOConsole;
+
+#define VIRTIO_CONSOLE_F_SIZE (1 << 0)
+#define VIRTIO_CONSOLE_F_MULTIPORT (1 << 1)
+#define VIRTIO_CONSOLE_F_EMERG_WRITE (1 << 2)
+
+class VirtIOConsolePort
+ : public CharacterDevice {
+public:
+ explicit VirtIOConsolePort(unsigned port, VirtIOConsole&);
+ void handle_queue_update(Badge<VirtIOConsole>, u16 queue_index);
+
+ void set_open(Badge<VirtIOConsole>, bool state) { m_open = state; }
+ bool is_open() const { return m_open; }
+
+private:
+ constexpr static size_t RINGBUFFER_SIZE = 2 * PAGE_SIZE;
+
+ virtual const char* class_name() const override { return "VirtIOConsolePort"; }
+
+ virtual bool can_read(const FileDescription&, size_t) const override;
+ virtual KResultOr<size_t> read(FileDescription&, u64, UserOrKernelBuffer&, size_t) override;
+ virtual bool can_write(const FileDescription&, size_t) const override;
+ virtual KResultOr<size_t> write(FileDescription&, u64, const UserOrKernelBuffer&, size_t) override;
+ virtual KResultOr<NonnullRefPtr<FileDescription>> open(int options) override;
+
+ mode_t required_mode() const override { return 0666; }
+
+ String device_name() const override;
+
+ void init_receive_buffer();
+
+ static unsigned next_device_id;
+ u16 m_receive_queue {};
+ u16 m_transmit_queue {};
+
+ OwnPtr<RingBuffer> m_receive_buffer;
+ OwnPtr<RingBuffer> m_transmit_buffer;
+
+ VirtIOConsole& m_console;
+ unsigned m_port;
+
+ bool m_open { false };
+ Atomic<bool> m_receive_buffer_exhausted;
+};
+
+}