The Pedigree Project 0.1
VirtioNet.cc
1/* Copyright (c) 2026, Pedigree Developers. SPDX-License-Identifier: ISC */
2#include "VirtioNet.h"
3#include "pedigree/kernel/LockGuard.h"
4#include "pedigree/kernel/Log.h"
5#include "pedigree/kernel/Metrics.h"
6#include "pedigree/kernel/machine/IrqManager.h"
7#include "pedigree/kernel/machine/Machine.h"
8#include "pedigree/kernel/panic.h"
9#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
10#include "pedigree/kernel/processor/VirtualAddressSpace.h"
11#include "pedigree/kernel/utilities/utility.h"
12
13#include "modules/system/network-stack/NetworkStack.h"
14
15namespace {
16constexpr uint64_t NetMac = 1ULL << 5;
17constexpr uint64_t NetStatus = 1ULL << 16;
18constexpr uint16_t LinkUp = 1;
19constexpr size_t EthernetHeaderSize = 14;
20constexpr size_t EthernetFrameMax = 1518;
21constexpr size_t EthernetFrameMin = 60;
22
23struct VirtioNetHeader {
24 uint8_t flags;
25 uint8_t gsoType;
26 uint16_t headerLength;
27 uint16_t gsoSize;
28 uint16_t checksumStart;
29 uint16_t checksumOffset;
30 uint16_t numBuffers;
31} __attribute__((packed));
32static_assert(sizeof(VirtioNetHeader) == 12, "unexpected virtio-net header size");
33
34size_t pagesFor(size_t bytes) {
35 return (bytes + PAGE_SIZE - 1) / PAGE_SIZE;
36}
37} // namespace
38
39VirtioNet::VirtioNet(Device* pciDevice)
40 : Network(),
41 m_PciDevice(pciDevice),
42 m_Transport(pciDevice),
43 m_RxQueue(),
44 m_TxQueue(),
45 m_RxRegion("virtio-net-rx"),
46 m_TxRegion("virtio-net-tx"),
47 m_Lock(),
48 m_RxSlots(),
49 m_TxSlots(),
50 m_RxCount(0),
51 m_TxCount(0),
52 m_NextTx(0),
53 m_IrqId(0),
54 m_HasStatus(false),
55 m_NetworkRegistered(false),
56 m_Initialised(false),
57 m_Stopping(false) {
58 setSpecificType(String("virtio-net-card"));
59}
60
61VirtioNet::~VirtioNet() {
62 {
63 LockGuard<Mutex> guard(m_Lock);
64 m_Stopping = true;
65 m_Initialised = false;
66 if (!m_Transport.reset()) {
67 panic("virtio-net could not stop DMA");
68 }
69 }
70
71 if (m_IrqId && !Machine::instance().getIrqManager()->unregisterHandler(m_IrqId, this)) {
72 panic("virtio-net could not synchronously unregister its IRQ");
73 }
74 if (m_NetworkRegistered) {
76 }
77 m_RxQueue.stop();
78 m_TxQueue.stop();
79}
80
81bool VirtioNet::initialise() {
82 if (!m_Transport.initialise() || !m_Transport.negotiate(NetMac | NetStatus, NetMac) ||
83 !m_Transport.setupQueue(0, m_RxQueue) || !m_Transport.setupQueue(1, m_TxQueue)) {
84 ERROR("virtio-net: PCI transport or queues unavailable");
85 return false;
86 }
87
88 m_RxCount = m_RxQueue.depth() < MaxRxSlots ? m_RxQueue.depth() : MaxRxSlots;
89 m_TxCount = m_TxQueue.depth() < MaxTxSlots ? m_TxQueue.depth() : MaxTxSlots;
90 if (!m_RxCount || !m_TxCount) {
91 ERROR("virtio-net: no receive or transmit descriptors");
92 return false;
93 }
94
95 auto& memory = PhysicalMemoryManager::instance();
96 constexpr size_t dmaFlags = PhysicalMemoryManager::continuous;
98 if (!memory.allocateRegion(m_RxRegion, pagesFor(m_RxCount * BufferSize), dmaFlags, mapFlags,
99 -1) ||
100 !memory.allocateRegion(m_TxRegion, pagesFor(m_TxCount * BufferSize), dmaFlags, mapFlags,
101 -1)) {
102 ERROR("virtio-net: could not allocate DMA buffers");
103 return false;
104 }
105
106 for (size_t i = 0; i < m_RxCount; ++i) {
107 m_RxSlots[i] = {static_cast<uint8_t*>(m_RxRegion.virtualAddress()) + i * BufferSize,
108 m_RxRegion.physicalAddress() + i * BufferSize, false};
109 }
110 for (size_t i = 0; i < m_TxCount; ++i) {
111 m_TxSlots[i] = {static_cast<uint8_t*>(m_TxRegion.virtualAddress()) + i * BufferSize,
112 m_TxRegion.physicalAddress() + i * BufferSize, false};
113 }
114
115 for (size_t i = 0; i < 6; ++i) {
116 uint8_t byte = 0;
117 if (!m_Transport.readDeviceConfig8(i, byte)) {
118 ERROR("virtio-net: could not read MAC address");
119 return false;
120 }
121 m_StationInfo.mac.setMac(byte, i);
122 }
123 m_HasStatus = (m_Transport.features() & NetStatus) != 0;
124
125 for (size_t i = 0; i < m_RxCount; ++i) {
126 if (!postReceive(m_RxSlots[i])) {
127 ERROR("virtio-net: could not populate receive queue");
128 return false;
129 }
130 }
131
132 m_IrqId = m_Transport.registerInterrupt(this);
133 if (!m_IrqId) {
134 ERROR("virtio-net: could not register PCI interrupt");
135 return false;
136 }
137
139 m_NetworkRegistered = true;
140 m_Initialised = true;
141 if (!m_Transport.ready()) {
142 m_Initialised = false;
143 ERROR("virtio-net: device did not accept DRIVER_OK");
144 return false;
145 }
146 m_Transport.notify(0);
147 NOTICE("virtio-net: MAC " << m_StationInfo.mac[0] << ":" << m_StationInfo.mac[1] << ":"
148 << m_StationInfo.mac[2] << ":" << m_StationInfo.mac[3] << ":"
149 << m_StationInfo.mac[4] << ":" << m_StationInfo.mac[5]);
150 return true;
151}
152
154 name.assign("virtio-net", 10);
155}
156
157bool VirtioNet::postReceive(Slot& slot) {
158 Virtio::Buffer buffer = {slot.physical, BufferSize, true};
159 return m_RxQueue.submit(&buffer, 1, &slot);
160}
161
162void VirtioNet::reclaimTransmit() {
163 Virtio::Completion completion;
164 while (m_TxQueue.pop(completion)) {
165 auto* slot = static_cast<Slot*>(completion.cookie);
166 slot->busy = false;
167 }
168}
169
170bool VirtioNet::send(size_t length, uintptr_t buffer) {
171 if (!buffer || length < EthernetHeaderSize || length > EthernetFrameMax) {
172 return false;
173 }
174
175 LockGuard<Mutex> guard(m_Lock);
176 if (!m_Initialised || m_Stopping) {
177 return false;
178 }
179
180 reclaimTransmit();
181 for (size_t n = 0; n < m_TxCount; ++n) {
182 const size_t index = (m_NextTx + n) % m_TxCount;
183 Slot& slot = m_TxSlots[index];
184 if (slot.busy) {
185 continue;
186 }
187
188 const size_t frameLength = length < EthernetFrameMin ? EthernetFrameMin : length;
189 ByteSet(slot.data, 0, sizeof(VirtioNetHeader));
190 MemoryCopy(slot.data + sizeof(VirtioNetHeader), reinterpret_cast<void*>(buffer), length);
191 if (frameLength > length) {
192 ByteSet(slot.data + sizeof(VirtioNetHeader) + length, 0, frameLength - length);
193 }
194
195 Virtio::Buffer descriptor = {
196 slot.physical, static_cast<uint32_t>(sizeof(VirtioNetHeader) + frameLength), false};
197 slot.busy = true;
198 if (!m_TxQueue.submit(&descriptor, 1, &slot)) {
199 slot.busy = false;
200 return false;
201 }
202 m_NextTx = (index + 1) % m_TxCount;
203 m_Transport.notify(1);
204 return true;
205 }
206 return false;
207}
208
210 LockGuard<Mutex> guard(m_Lock);
211 if (m_StationInfo.dnsServers != info.dnsServers) {
212 delete[] m_StationInfo.dnsServers;
213 }
214 m_StationInfo.ipv4 = info.ipv4;
215 m_StationInfo.ipv6 = info.ipv6;
216 m_StationInfo.nIpv6Addresses = info.nIpv6Addresses;
217 m_StationInfo.subnetMask = info.subnetMask;
218 m_StationInfo.broadcast = info.broadcast;
219 m_StationInfo.gateway = info.gateway;
220 m_StationInfo.gatewayIpv6 = info.gatewayIpv6;
221 m_StationInfo.dnsServers = info.dnsServers;
222 m_StationInfo.nDnsServers = info.nDnsServers;
223 return true;
224}
225
227 return m_StationInfo;
228}
229
231 LockGuard<Mutex> guard(m_Lock);
232 if (!m_Initialised || m_Stopping) {
233 return false;
234 }
235 if (!m_HasStatus) {
236 return true;
237 }
238 uint16_t status = 0;
239 return m_Transport.readDeviceConfig16(6, status) && (status & LinkUp);
240}
241
243 (void)number;
244 Virtio::Completion received[MaxRxSlots];
245 size_t receiveCount = 0;
246 {
247 LockGuard<Mutex> guard(m_Lock);
248 if (m_Stopping || !m_Initialised) {
250 }
251 if (!m_Transport.readIsr()) {
252 return IrqDisposition::NotHandled;
253 }
254 reclaimTransmit();
255 while (receiveCount < m_RxCount && m_RxQueue.pop(received[receiveCount])) {
256 ++receiveCount;
257 }
258 }
259
262 const bool admitted = receiveCount && stack.acquireDevice(this, device);
263 for (size_t i = 0; i < receiveCount; ++i) {
264 const auto& completion = received[i];
265 auto* slot = static_cast<Slot*>(completion.cookie);
266 const auto* header = reinterpret_cast<const VirtioNetHeader*>(slot->data);
267 const size_t frameLength = completion.length >= sizeof(VirtioNetHeader)
268 ? completion.length - sizeof(VirtioNetHeader)
269 : 0;
270 // Some QEMU versions leave this field zero without mergeable RX buffers.
271 const bool valid = completion.length >= sizeof(VirtioNetHeader) + EthernetHeaderSize &&
272 completion.length <= BufferSize && frameLength <= EthernetFrameMax &&
273 !header->flags && !header->gsoType &&
274 LITTLE_TO_HOST16(header->numBuffers) <= 1;
275 if (valid) {
276 gotPacket();
277 if (admitted) {
278 stack.receive(frameLength,
279 reinterpret_cast<uintptr_t>(slot->data + sizeof(VirtioNetHeader)), device, 0);
280 } else {
281 Metrics::increment(Metrics::NetworkRxNoDevice);
282 }
283 } else {
284 badPacket();
285 }
286
287 LockGuard<Mutex> guard(m_Lock);
288 if (!m_Stopping && m_Initialised) {
289 if (postReceive(*slot)) {
290 m_Transport.notify(0);
291 } else {
292 ERROR("virtio-net: could not replace receive buffer");
293 }
294 }
295 }
296 return IrqDisposition::Handled;
297}
void * virtualAddress() const
physical_uintptr_t physicalAddress() const
void deRegisterDevice(Network *pDevice)
static NetworkStack & instance()
void receive(size_t nBytes, uintptr_t packet, Network *pCard, uint32_t offset)
MUST_USE_RESULT bool acquireDevice(size_t n, DeviceLease &lease)
void registerDevice(Network *pDevice)
virtual void badPacket()
Definition Network.cc:131
virtual void gotPacket()
Definition Network.cc:123
static PhysicalMemoryManager & instance()
size_t nDnsServers
Can contain IPv6 addresses.
Definition Network.h:51
IpAddress gateway
Automatically calculated?
Definition Network.h:47
IrqDisposition irq(irq_id_t number) override
Definition VirtioNet.cc:242
const StationInfo & getStationInfo() override
Definition VirtioNet.cc:226
bool setStationInfo(const StationInfo &info) override
Definition VirtioNet.cc:209
bool isConnected() override
Definition VirtioNet.cc:230
void getName(String &name) override
Definition VirtioNet.cc:153
bool send(size_t length, uintptr_t buffer) override
Definition VirtioNet.cc:170
void EXPORTED_PUBLIC panic(const char *msg) NORETURN
Definition panic.cc:118
IrqDisposition
Definition IrqHandler.h:31