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