mirror of
https://github.com/RGBCube/serenity
synced 2025-05-25 20:05:07 +00:00

Simplify core methods in the VirtIO bus handling code by ensuring proper error propagation. This makes initialization of queues, handling changes in device configuration, and other core patterns more readable as well. It also allows us to remove the obnoxious pattern of checking for boolean "success" and if we get false answer then returning an actual errno code.
169 lines
6.3 KiB
C++
169 lines
6.3 KiB
C++
/*
|
|
* Copyright (c) 2021, the SerenityOS developers.
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <Kernel/Bus/VirtIO/Device.h>
|
|
#include <Kernel/Sections.h>
|
|
|
|
namespace Kernel::VirtIO {
|
|
|
|
UNMAP_AFTER_INIT ErrorOr<void> Device::initialize_virtio_resources()
|
|
{
|
|
TRY(m_transport_entity->locate_configurations_and_resources({}, *this));
|
|
// NOTE: We enable interrupts at least after the m_register_bases[0] ptr is
|
|
// assigned with an IOWindow, to ensure that in case of getting an interrupt
|
|
// we can access registers from that IO window range.
|
|
m_transport_entity->enable_interrupts({});
|
|
|
|
// NOTE: Status bits should be set to 0 to keep them in sync, because
|
|
// we reset the device shortly afterwards.
|
|
m_status = 0;
|
|
m_transport_entity->reset_device({});
|
|
set_status_bit(DEVICE_STATUS_ACKNOWLEDGE);
|
|
set_status_bit(DEVICE_STATUS_DRIVER);
|
|
return {};
|
|
}
|
|
|
|
UNMAP_AFTER_INIT VirtIO::Device::Device(NonnullOwnPtr<TransportEntity> transport_entity)
|
|
: m_class_name(transport_entity->determine_device_class_name())
|
|
, m_transport_entity(move(transport_entity))
|
|
{
|
|
}
|
|
|
|
void Device::set_status_bit(u8 status_bit)
|
|
{
|
|
m_status |= status_bit;
|
|
m_transport_entity->set_status_bits({}, m_status);
|
|
}
|
|
|
|
ErrorOr<void> Device::accept_device_features(u64 device_features, u64 accepted_features)
|
|
{
|
|
VERIFY(!m_did_accept_features);
|
|
m_did_accept_features = true;
|
|
|
|
if (is_feature_set(device_features, VIRTIO_F_VERSION_1)) {
|
|
accepted_features |= VIRTIO_F_VERSION_1; // let the device know were not a legacy driver
|
|
}
|
|
|
|
if (is_feature_set(device_features, VIRTIO_F_RING_PACKED)) {
|
|
dbgln_if(VIRTIO_DEBUG, "{}: packed queues not yet supported", m_class_name);
|
|
accepted_features &= ~(VIRTIO_F_RING_PACKED);
|
|
}
|
|
|
|
// TODO: implement indirect descriptors to allow queue_size buffers instead of buffers totalling (PAGE_SIZE * queue_size) bytes
|
|
if (is_feature_set(device_features, VIRTIO_F_INDIRECT_DESC)) {
|
|
// accepted_features |= VIRTIO_F_INDIRECT_DESC;
|
|
}
|
|
|
|
if (is_feature_set(device_features, VIRTIO_F_IN_ORDER)) {
|
|
accepted_features |= VIRTIO_F_IN_ORDER;
|
|
}
|
|
|
|
dbgln_if(VIRTIO_DEBUG, "{}: Device features: {}", m_class_name, device_features);
|
|
dbgln_if(VIRTIO_DEBUG, "{}: Accepted features: {}", m_class_name, accepted_features);
|
|
|
|
m_transport_entity->accept_device_features({}, accepted_features);
|
|
set_status_bit(DEVICE_STATUS_FEATURES_OK);
|
|
m_status = m_transport_entity->read_status_bits();
|
|
if (!(m_status & DEVICE_STATUS_FEATURES_OK)) {
|
|
set_status_bit(DEVICE_STATUS_FAILED);
|
|
dbgln("{}: Features not accepted by host!", m_class_name);
|
|
return Error::from_errno(EIO);
|
|
}
|
|
|
|
m_accepted_features = accepted_features;
|
|
dbgln_if(VIRTIO_DEBUG, "{}: Features accepted by host", m_class_name);
|
|
return {};
|
|
}
|
|
|
|
ErrorOr<void> Device::setup_queue(u16 queue_index)
|
|
{
|
|
auto queue = TRY(m_transport_entity->setup_queue({}, queue_index));
|
|
dbgln_if(VIRTIO_DEBUG, "{}: Queue[{}] configured with size: {}", m_class_name, queue_index, queue->size());
|
|
|
|
TRY(m_queues.try_append(move(queue)));
|
|
return {};
|
|
}
|
|
|
|
ErrorOr<void> Device::setup_queues(u16 requested_queue_count)
|
|
{
|
|
VERIFY(!m_did_setup_queues);
|
|
m_did_setup_queues = true;
|
|
|
|
auto* common_cfg = m_transport_entity->get_config(ConfigurationType::Common).release_value_but_fixme_should_propagate_errors();
|
|
if (common_cfg) {
|
|
auto maximum_queue_count = m_transport_entity->config_read16(*common_cfg, COMMON_CFG_NUM_QUEUES);
|
|
if (requested_queue_count == 0) {
|
|
m_queue_count = maximum_queue_count;
|
|
} else if (requested_queue_count > maximum_queue_count) {
|
|
dbgln("{}: {} queues requested but only {} available!", m_class_name, m_queue_count, maximum_queue_count);
|
|
return Error::from_errno(ENXIO);
|
|
} else {
|
|
m_queue_count = requested_queue_count;
|
|
}
|
|
} else {
|
|
m_queue_count = requested_queue_count;
|
|
dbgln("{}: device's available queue count could not be determined!", m_class_name);
|
|
}
|
|
|
|
dbgln_if(VIRTIO_DEBUG, "{}: Setting up {} queues", m_class_name, m_queue_count);
|
|
for (u16 i = 0; i < m_queue_count; i++)
|
|
TRY(setup_queue(i));
|
|
|
|
// NOTE: Queues can only be activated *after* all others queues were also configured
|
|
for (u16 i = 0; i < m_queue_count; i++)
|
|
TRY(m_transport_entity->activate_queue({}, i));
|
|
return {};
|
|
}
|
|
|
|
void Device::finish_init()
|
|
{
|
|
VERIFY(m_did_accept_features); // ensure features were negotiated
|
|
VERIFY(m_did_setup_queues); // ensure queues were set-up
|
|
VERIFY(!(m_status & DEVICE_STATUS_DRIVER_OK)); // ensure we didn't already finish the initialization
|
|
|
|
set_status_bit(DEVICE_STATUS_DRIVER_OK);
|
|
dbgln_if(VIRTIO_DEBUG, "{}: Finished initialization", m_class_name);
|
|
}
|
|
|
|
bool Device::handle_irq(Badge<TransportInterruptHandler>)
|
|
{
|
|
u8 isr_type = m_transport_entity->isr_status();
|
|
if ((isr_type & (QUEUE_INTERRUPT | DEVICE_CONFIG_INTERRUPT)) == 0) {
|
|
dbgln_if(VIRTIO_DEBUG, "{}: Handling interrupt with unknown type: {}", class_name(), isr_type);
|
|
return false;
|
|
}
|
|
if (isr_type & DEVICE_CONFIG_INTERRUPT) {
|
|
dbgln_if(VIRTIO_DEBUG, "{}: VirtIO Device config interrupt!", class_name());
|
|
if (handle_device_config_change().is_error()) {
|
|
set_status_bit(DEVICE_STATUS_FAILED);
|
|
dbgln("{}: Failed to handle device config change!", class_name());
|
|
}
|
|
}
|
|
if (isr_type & QUEUE_INTERRUPT) {
|
|
dbgln_if(VIRTIO_DEBUG, "{}: VirtIO Queue interrupt!", class_name());
|
|
for (size_t i = 0; i < m_queues.size(); i++) {
|
|
if (get_queue(i).new_data_available()) {
|
|
handle_queue_update(i);
|
|
return true;
|
|
}
|
|
}
|
|
dbgln_if(VIRTIO_DEBUG, "{}: Got queue interrupt but all queues are up to date!", class_name());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void Device::supply_chain_and_notify(u16 queue_index, QueueChain& chain)
|
|
{
|
|
auto& queue = get_queue(queue_index);
|
|
VERIFY(&chain.queue() == &queue);
|
|
VERIFY(queue.lock().is_locked());
|
|
chain.submit_to_queue();
|
|
auto descriptor = TransportEntity::NotifyQueueDescriptor { queue_index, get_queue(queue_index).notify_offset() };
|
|
if (queue.should_notify())
|
|
m_transport_entity->notify_queue({}, descriptor);
|
|
}
|
|
|
|
}
|