mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-14 22:31:29 +03:00
ed0e7b53a5
Currently, when passing buffers into VirtIOQueues, we use scatter-gather lists, which contain an internal vector of buffers. This vector is allocated, filled and the destroy whenever we try to provide buffers into a virtqueue, which would happen a lot in performance cricital code (the main transport mechanism for certain paravirtualized devices). This commit moves it over to using VirtIOQueueChains and building the chain in place in the VirtIOQueue. Also included are a bunch of fixups for the VirtIO Console device, making it use an internal VM::RingBuffer instead.
140 lines
4.6 KiB
C++
140 lines
4.6 KiB
C++
/*
|
|
* Copyright (c) 2021, the SerenityOS developers.
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <Kernel/VirtIO/VirtIOConsole.h>
|
|
|
|
namespace Kernel {
|
|
|
|
unsigned VirtIOConsole::next_device_id = 0;
|
|
|
|
VirtIOConsole::VirtIOConsole(PCI::Address address)
|
|
: CharacterDevice(229, next_device_id++)
|
|
, VirtIODevice(address, "VirtIOConsole")
|
|
{
|
|
if (auto cfg = get_config(ConfigurationType::Device)) {
|
|
bool success = negotiate_features([&](u64 supported_features) {
|
|
u64 negotiated = 0;
|
|
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!");
|
|
return negotiated;
|
|
});
|
|
if (success) {
|
|
u32 max_nr_ports = 0;
|
|
u16 cols = 0, rows = 0;
|
|
read_config_atomic([&]() {
|
|
if (is_feature_accepted(VIRTIO_CONSOLE_F_SIZE)) {
|
|
cols = config_read16(*cfg, 0x0);
|
|
rows = config_read16(*cfg, 0x2);
|
|
}
|
|
if (is_feature_accepted(VIRTIO_CONSOLE_F_MULTIPORT)) {
|
|
max_nr_ports = config_read32(*cfg, 0x4);
|
|
}
|
|
});
|
|
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
|
|
}
|
|
if (success) {
|
|
finish_init();
|
|
m_receive_buffer = make<RingBuffer>("VirtIOConsole Receive", RINGBUFFER_SIZE);
|
|
m_transmit_buffer = make<RingBuffer>("VirtIOConsole Transmit", RINGBUFFER_SIZE);
|
|
}
|
|
}
|
|
}
|
|
|
|
VirtIOConsole::~VirtIOConsole()
|
|
{
|
|
}
|
|
|
|
bool VirtIOConsole::handle_device_config_change()
|
|
{
|
|
dbgln("VirtIOConsole: Handle device config change");
|
|
return true;
|
|
}
|
|
|
|
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: {
|
|
ScopedSpinLock lock(get_queue(RECEIVEQ).lock());
|
|
get_queue(RECEIVEQ).discard_used_buffers(); // TODO: do something with incoming data (users writing into qemu console) instead of just clearing
|
|
break;
|
|
}
|
|
case TRANSMITQ: {
|
|
ScopedSpinLock ringbuffer_lock(m_transmit_buffer->lock());
|
|
auto& queue = get_queue(TRANSMITQ);
|
|
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();
|
|
break;
|
|
}
|
|
default:
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
}
|
|
|
|
bool VirtIOConsole::can_read(const FileDescription&, size_t) const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
KResultOr<size_t> VirtIOConsole::read(FileDescription&, u64, [[maybe_unused]] UserOrKernelBuffer& data, size_t)
|
|
{
|
|
return ENOTSUP;
|
|
}
|
|
|
|
bool VirtIOConsole::can_write(const FileDescription&, size_t) const
|
|
{
|
|
return get_queue(TRANSMITQ).has_free_slots() && m_transmit_buffer->has_space();
|
|
}
|
|
|
|
KResultOr<size_t> VirtIOConsole::write(FileDescription& desc, u64, const UserOrKernelBuffer& data, size_t size)
|
|
{
|
|
if (!size)
|
|
return 0;
|
|
|
|
if (!can_write(desc, size))
|
|
return EAGAIN;
|
|
|
|
ScopedSpinLock ringbuffer_lock(m_transmit_buffer->lock());
|
|
auto& queue = get_queue(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);
|
|
|
|
return total_bytes_copied;
|
|
}
|
|
|
|
}
|