mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 15:32:46 +00:00 
			
		
		
		
	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.
This commit is contained in:
		
							parent
							
								
									1492bb2fd6
								
							
						
					
					
						commit
						1fe08759e3
					
				
					 6 changed files with 419 additions and 126 deletions
				
			
		|  | @ -249,6 +249,7 @@ set(KERNEL_SOURCES | |||
|     UserOrKernelBuffer.cpp | ||||
|     VirtIO/VirtIO.cpp | ||||
|     VirtIO/VirtIOConsole.cpp | ||||
|     VirtIO/VirtIOConsolePort.cpp | ||||
|     VirtIO/VirtIOQueue.cpp | ||||
|     VirtIO/VirtIORNG.cpp | ||||
|     VM/AnonymousVMObject.cpp | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ | |||
| 
 | ||||
| #include <AK/String.h> | ||||
| #include <Kernel/PhysicalAddress.h> | ||||
| #include <Kernel/UserOrKernelBuffer.h> | ||||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
|  | @ -25,6 +26,8 @@ public: | |||
|     SpinLock<u8>& lock() { return m_lock; } | ||||
|     size_t used_bytes() const { return m_num_used_bytes; } | ||||
|     PhysicalAddress start_of_region() const { return m_region->physical_page(0)->paddr(); } | ||||
|     VirtualAddress vaddr() const { return m_region->vaddr(); } | ||||
|     size_t bytes_till_end() const { return (m_capacity_in_bytes - ((m_start_of_used + m_num_used_bytes) % m_capacity_in_bytes)) % m_capacity_in_bytes; }; | ||||
| 
 | ||||
| private: | ||||
|     OwnPtr<Region> m_region; | ||||
|  |  | |||
|  | @ -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()); | ||||
|         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); | ||||
|             }); | ||||
| 
 | ||||
|         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(); | ||||
| 
 | ||||
|         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(); | ||||
|         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<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); | ||||
| } | ||||
| 
 | ||||
| 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<u32>(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: | ||||
|         VERIFY_NOT_REACHED(); | ||||
|         dbgln("Unhandled message event {}!", message.event); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool VirtIOConsole::can_read(const FileDescription&, size_t) const | ||||
| void VirtIOConsole::write_control_message(ControlMessage message) | ||||
| { | ||||
|     return m_receive_buffer->used_bytes() > 0; | ||||
| } | ||||
|     ScopedSpinLock ringbuffer_lock(m_control_transmit_buffer->lock()); | ||||
| 
 | ||||
| KResultOr<size_t> VirtIOConsole::read(FileDescription& desc, u64, UserOrKernelBuffer& buffer, size_t size) | ||||
| { | ||||
|     if (!size) | ||||
|         return 0; | ||||
|     PhysicalAddress start_of_chunk; | ||||
|     size_t length_of_chunk; | ||||
| 
 | ||||
|     if (!can_read(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_receive_buffer->lock()); | ||||
| 
 | ||||
|     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()); | ||||
| 
 | ||||
|     return bytes_copied; | ||||
| } | ||||
| 
 | ||||
| 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); | ||||
|     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; | ||||
|     bool did_add_buffer = chain.add_buffer_to_chain(start_of_chunk, length_of_chunk, BufferType::DeviceReadable); | ||||
|     VERIFY(did_add_buffer); | ||||
| 
 | ||||
|         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; | ||||
|     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); | ||||
| } | ||||
| } | ||||
|  |  | |||
|  | @ -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; | ||||
| 
 | ||||
|     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 Status : u16 { | ||||
|             Success = 1, | ||||
|             Failure = 0 | ||||
|         }; | ||||
| 
 | ||||
|     virtual mode_t required_mode() const override { return 0666; } | ||||
|         enum class PortStatus : u16 { | ||||
|             Open = 1, | ||||
|             Close = 0 | ||||
|         }; | ||||
|     }; | ||||
| 
 | ||||
|     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; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
|  |  | |||
							
								
								
									
										167
									
								
								Kernel/VirtIO/VirtIOConsolePort.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								Kernel/VirtIO/VirtIOConsolePort.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -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); | ||||
| } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										63
									
								
								Kernel/VirtIO/VirtIOConsolePort.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								Kernel/VirtIO/VirtIOConsolePort.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 x-yl
						x-yl