/* * Copyright (c) 2021, the SerenityOS developers. * Copyright (c) 2021, Kyle Pereira * * SPDX-License-Identifier: BSD-2-Clause */ #include #include namespace Kernel { unsigned VirtIOConsole::next_device_id = 0; UNMAP_AFTER_INIT VirtIOConsole::VirtIOConsole(PCI::Address address) : VirtIODevice(address, "VirtIOConsole") , m_device_id(next_device_id++) { 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)) negotiated |= VIRTIO_CONSOLE_F_MULTIPORT; 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); m_ports.resize(max_nr_ports); } }); dbgln("VirtIOConsole: cols: {}, rows: {}, max nr ports {}", cols, rows, max_nr_ports); // 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(); if (is_feature_accepted(VIRTIO_CONSOLE_F_MULTIPORT)) setup_multiport(); else m_ports.append(new VirtIOConsolePort(0u, *this)); } } } 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 {}", 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); 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(m_control_receive_buffer->vaddr().offset(offset).as_ptr()); process_control_message(*message); }); supply_chain_and_notify(CONTROL_RECEIVEQ, popped_chain); popped_chain = queue.pop_used_buffer_chain(used); } } 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_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()); 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); } } void VirtIOConsole::setup_multiport() { m_control_receive_buffer = make("VirtIOConsole control receive queue", CONTROL_BUFFER_SIZE); m_control_transmit_buffer = make("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); } void VirtIOConsole::process_control_message(ControlMessage message) { 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; } m_ports.at(id) = new VirtIOConsolePort(id, *this); ControlMessage ready_event { .id = static_cast(id), .event = (u16)ControlEvent::PortReady, .value = (u16)ControlMessage::Status::Success }; 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; } 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); } } void VirtIOConsole::write_control_message(ControlMessage message) { ScopedSpinLock ringbuffer_lock(m_control_transmit_buffer->lock()); PhysicalAddress start_of_chunk; size_t length_of_chunk; 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(); } auto& queue = get_queue(CONTROL_TRANSMITQ); ScopedSpinLock queue_lock(queue.lock()); VirtIOQueueChain chain(queue); bool did_add_buffer = chain.add_buffer_to_chain(start_of_chunk, length_of_chunk, BufferType::DeviceReadable); VERIFY(did_add_buffer); supply_chain_and_notify(CONTROL_TRANSMITQ, chain); } void VirtIOConsole::send_open_control_message(unsigned port_number, bool open) { ControlMessage port_open { .id = static_cast(port_number), .event = (u16)ControlEvent::PortOpen, .value = open }; write_control_message(port_open); } }