Explorar o código

Kernel: Add support for reading from VirtIOConsole

This allows two-way communication with the host through a VirtIOConsole.
This is necessary for features like clipboard sharing.
x-yl %!s(int64=4) %!d(string=hai) anos
pai
achega
1492bb2fd6

+ 0 - 3
Kernel/CMakeLists.txt

@@ -53,9 +53,6 @@ set(KERNEL_SOURCES
     Devices/RandomDevice.cpp
     Devices/SB16.cpp
     Devices/SerialDevice.cpp
-    VirtIO/VirtIO.cpp
-    VirtIO/VirtIOQueue.cpp
-    VirtIO/VirtIOConsole.cpp
     Devices/VMWareBackdoor.cpp
     Devices/ZeroDevice.cpp
     Devices/HID/I8042Controller.cpp

+ 19 - 0
Kernel/VM/RingBuffer.cpp

@@ -30,6 +30,25 @@ bool RingBuffer::copy_data_in(const UserOrKernelBuffer& buffer, size_t offset, s
     return false;
 }
 
+KResultOr<size_t> RingBuffer::copy_data_out(size_t size, UserOrKernelBuffer& buffer) const
+{
+    auto start = m_start_of_used % m_capacity_in_bytes;
+    auto num_bytes = min(min(m_num_used_bytes, size), m_capacity_in_bytes - start);
+    if (!buffer.write(m_region->vaddr().offset(start).as_ptr(), num_bytes))
+        return EIO;
+    return num_bytes;
+}
+
+KResultOr<PhysicalAddress> RingBuffer::reserve_space(size_t size)
+{
+    if (m_capacity_in_bytes < m_num_used_bytes + size)
+        return ENOSPC;
+    size_t start_of_free_area = (m_start_of_used + m_num_used_bytes) % m_capacity_in_bytes;
+    m_num_used_bytes += size;
+    PhysicalAddress start_of_reserved_space = m_region->physical_page(start_of_free_area / PAGE_SIZE)->paddr().offset(start_of_free_area % PAGE_SIZE);
+    return start_of_reserved_space;
+}
+
 void RingBuffer::reclaim_space(PhysicalAddress chunk_start, size_t chunk_size)
 {
     VERIFY(start_of_used() == chunk_start);

+ 4 - 0
Kernel/VM/RingBuffer.h

@@ -17,10 +17,14 @@ public:
 
     bool has_space() const { return m_num_used_bytes < m_capacity_in_bytes; }
     bool copy_data_in(const UserOrKernelBuffer& buffer, size_t offset, size_t length, PhysicalAddress& start_of_copied_data, size_t& bytes_copied);
+    KResultOr<size_t> copy_data_out(size_t size, UserOrKernelBuffer& buffer) const;
+    KResultOr<PhysicalAddress> reserve_space(size_t size);
     void reclaim_space(PhysicalAddress chunk_start, size_t chunk_size);
     PhysicalAddress start_of_used() const;
 
     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(); }
 
 private:
     OwnPtr<Region> m_region;

+ 49 - 5
Kernel/VirtIO/VirtIOConsole.cpp

@@ -43,6 +43,8 @@ UNMAP_AFTER_INIT VirtIOConsole::VirtIOConsole(PCI::Address address)
             finish_init();
             m_receive_buffer = make<RingBuffer>("VirtIOConsole Receive", RINGBUFFER_SIZE);
             m_transmit_buffer = make<RingBuffer>("VirtIOConsole Transmit", RINGBUFFER_SIZE);
+
+            init_receive_buffer();
         }
     }
 }
@@ -51,6 +53,18 @@ 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");
@@ -63,8 +77,27 @@ void VirtIOConsole::handle_queue_update(u16 queue_index)
     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
+        auto& queue = get_queue(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();
+
+        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);
+        }
+
+        evaluate_block_conditions();
         break;
     }
     case TRANSMITQ: {
@@ -91,12 +124,23 @@ void VirtIOConsole::handle_queue_update(u16 queue_index)
 
 bool VirtIOConsole::can_read(const FileDescription&, size_t) const
 {
-    return true;
+    return m_receive_buffer->used_bytes() > 0;
 }
 
-KResultOr<size_t> VirtIOConsole::read(FileDescription&, u64, [[maybe_unused]] UserOrKernelBuffer& data, size_t)
+KResultOr<size_t> VirtIOConsole::read(FileDescription& desc, u64, UserOrKernelBuffer& buffer, size_t size)
 {
-    return ENOTSUP;
+    if (!size)
+        return 0;
+
+    if (!can_read(desc, size))
+        return EAGAIN;
+
+    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

+ 2 - 0
Kernel/VirtIO/VirtIOConsole.h

@@ -42,6 +42,8 @@ private:
     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;