summaryrefslogtreecommitdiff
path: root/Kernel/VirtIO/VirtIOConsole.cpp
blob: 69b80ac2c38ff870785b5b7788537824dd387a00 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/*
 * 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;
}

}