The Pedigree Project 0.1
VirtioInput.cc
1/* Copyright (c) 2026, Pedigree Developers. SPDX-License-Identifier: ISC */
2#include "VirtioInput.h"
3#include "pedigree/kernel/LockGuard.h"
4#include "pedigree/kernel/Log.h"
5#include "pedigree/kernel/TargetInfo.h"
6#include "pedigree/kernel/machine/HidInputManager.h"
7#include "pedigree/kernel/machine/InputManager.h"
8#include "pedigree/kernel/machine/IrqManager.h"
9#include "pedigree/kernel/machine/Machine.h"
10#include "pedigree/kernel/panic.h"
11#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
12#include "pedigree/kernel/processor/VirtualAddressSpace.h"
13#include "pedigree/kernel/utilities/utility.h"
14
15namespace {
16constexpr uint8_t ConfigEventBits = 0x11;
17constexpr uint8_t ConfigAbsInfo = 0x12;
18constexpr uint8_t EvSyn = 0;
19constexpr uint8_t EvKey = 1;
20constexpr uint8_t EvRel = 2;
21constexpr uint8_t EvAbs = 3;
22constexpr uint8_t RelX = 0;
23constexpr uint8_t RelY = 1;
24constexpr uint8_t RelWheel = 8;
25constexpr uint8_t AbsX = 0;
26constexpr uint8_t AbsY = 1;
27constexpr uint16_t BtnLeft = 0x110;
28constexpr uint16_t BtnRight = 0x111;
29constexpr uint16_t BtnMiddle = 0x112;
30constexpr uint16_t BtnSide = 0x113;
31constexpr uint16_t BtnExtra = 0x114;
32
33uint8_t hidFromLinux(uint16_t code) {
34 static constexpr uint8_t letters[] = {
35 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38, 50,
36 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44,
37 };
38 for (size_t i = 0; i < sizeof(letters); ++i) {
39 if (letters[i] == code) {
40 return i + 4;
41 }
42 }
43 if (code >= 2 && code <= 10) {
44 return code + 28;
45 }
46 if (code >= 59 && code <= 68) {
47 return code - 1;
48 }
49
50 switch (code) {
51 case 11:
52 return 39;
53 case 28:
54 return 40;
55 case 1:
56 return 41;
57 case 14:
58 return 42;
59 case 15:
60 return 43;
61 case 57:
62 return 44;
63 case 12:
64 return 45;
65 case 13:
66 return 46;
67 case 26:
68 return 47;
69 case 27:
70 return 48;
71 case 43:
72 return 49;
73 case 86:
74 return 50;
75 case 39:
76 return 51;
77 case 40:
78 return 52;
79 case 41:
80 return 53;
81 case 51:
82 return 54;
83 case 52:
84 return 55;
85 case 53:
86 return 56;
87 case 58:
88 return 57;
89 case 87:
90 return 68;
91 case 88:
92 return 69;
93 case 99:
94 return 70;
95 case 70:
96 return 71;
97 case 119:
98 return 72;
99 case 110:
100 return 73;
101 case 102:
102 return 74;
103 case 104:
104 return 75;
105 case 111:
106 return 76;
107 case 107:
108 return 77;
109 case 109:
110 return 78;
111 case 106:
112 return 79;
113 case 105:
114 return 80;
115 case 108:
116 return 81;
117 case 103:
118 return 82;
119 case 69:
120 return 83;
121 case 29:
122 return 224;
123 case 42:
124 return 225;
125 case 56:
126 return 226;
127 case 125:
128 return 227;
129 case 97:
130 return 228;
131 case 54:
132 return 229;
133 case 100:
134 return 230;
135 case 126:
136 return 231;
137 default:
138 return 0;
139 }
140}
141
142uint32_t buttonMask(uint16_t code) {
143 switch (code) {
144 case BtnLeft:
145 return 1U << 0;
146 case BtnMiddle:
147 return 1U << 1;
148 case BtnRight:
149 return 1U << 2;
150 case BtnSide:
151 return 1U << 3;
152 case BtnExtra:
153 return 1U << 4;
154 default:
155 return 0;
156 }
157}
158} // namespace
159
160VirtioInputDevice::VirtioInputDevice(Device* pci)
161 : m_Pci(pci),
162 m_Transport(pci),
163 m_EventQueue(),
164 m_StatusQueue(),
165 m_Events("virtio-input-events"),
166 m_Lock(),
167 m_Slots{},
168 m_SlotCount(0),
169 m_Irq(0),
170 m_Type(Keyboard),
171 m_AbsMinimum{},
172 m_AbsMaximum{},
173 m_AbsPosition{},
174 m_Relative{},
175 m_Buttons(0),
176 m_KeyDown{},
177 m_PointerChanged(false),
178 m_Active(false),
179 m_Stopping(false) {
180 setSpecificType(String("virtio-input-device"));
181}
182
183VirtioInputDevice::~VirtioInputDevice() {
184 {
185 LockGuard<Mutex> guard(m_Lock);
186 m_Stopping = true;
187 m_Active = false;
188 if (!m_Transport.reset()) {
189 panic("virtio-input could not stop DMA");
190 }
191 }
192 if (m_Irq && !Machine::instance().getIrqManager()->unregisterHandler(m_Irq, this)) {
193 panic("virtio-input could not synchronously unregister its IRQ");
194 }
195 m_EventQueue.stop();
196 m_StatusQueue.stop();
197
198 if (m_Type == Keyboard) {
199 for (size_t key = 0; key < sizeof(m_KeyDown); ++key) {
200 if (m_KeyDown[key]) {
202 }
203 }
204 } else if (m_Buttons) {
205 if (m_Type == Mouse) {
207 } else {
208 InputManager::instance().absoluteMouseUpdate(m_AbsPosition[0], m_AbsPosition[1], 0, 0);
209 }
210 }
211}
212
213bool VirtioInputDevice::supportsEvent(uint8_t type, uint16_t code, bool& supported) {
214 if (!m_Transport.writeDeviceConfig8(0, ConfigEventBits) ||
215 !m_Transport.writeDeviceConfig8(1, type)) {
216 return false;
217 }
218 uint8_t size = 0;
219 if (!m_Transport.readDeviceConfig8(2, size) || size > 128) {
220 return false;
221 }
222 supported = false;
223 if (code / 8 < size) {
224 uint8_t bits = 0;
225 if (!m_Transport.readDeviceConfig8(8 + code / 8, bits)) {
226 return false;
227 }
228 supported = bits & (1U << (code % 8));
229 }
230 return true;
231}
232
233bool VirtioInputDevice::readAxis(uint8_t axis, int32_t& minimum, int32_t& maximum) {
234 if (!m_Transport.writeDeviceConfig8(0, ConfigAbsInfo) ||
235 !m_Transport.writeDeviceConfig8(1, axis)) {
236 return false;
237 }
238 uint8_t size = 0;
239 uint32_t minValue = 0;
240 uint32_t maxValue = 0;
241 if (!m_Transport.readDeviceConfig8(2, size) || size < 20 ||
242 !m_Transport.readDeviceConfig32(8, minValue) ||
243 !m_Transport.readDeviceConfig32(12, maxValue)) {
244 return false;
245 }
246 minimum = static_cast<int32_t>(LITTLE_TO_HOST32(minValue));
247 maximum = static_cast<int32_t>(LITTLE_TO_HOST32(maxValue));
248 return maximum > minimum;
249}
250
251bool VirtioInputDevice::postReceive(Slot& slot) {
252 Virtio::Buffer buffer = {slot.physical, sizeof(Event), true};
253 return m_EventQueue.submit(&buffer, 1, &slot);
254}
255
256bool VirtioInputDevice::initialise() {
257 if (!m_Pci || m_Pci->getPciVendorId() != 0x1af4 || m_Pci->getPciDeviceId() != 0x1052 ||
258 !m_Transport.initialise() || !m_Transport.negotiate(0) ||
259 !m_Transport.setupQueue(0, m_EventQueue) || !m_Transport.setupQueue(1, m_StatusQueue)) {
260 ERROR("virtio-input: PCI transport or queues unavailable");
261 return false;
262 }
263
264 bool absoluteX = false, absoluteY = false, relativeX = false, relativeY = false;
265 bool keyA = false;
266 if (!supportsEvent(EvAbs, AbsX, absoluteX) || !supportsEvent(EvAbs, AbsY, absoluteY) ||
267 !supportsEvent(EvRel, RelX, relativeX) || !supportsEvent(EvRel, RelY, relativeY) ||
268 !supportsEvent(EvKey, 30, keyA)) {
269 ERROR("virtio-input: could not query supported input events");
270 return false;
271 }
272 if (absoluteX && absoluteY) {
273 m_Type = Tablet;
274 if (!readAxis(AbsX, m_AbsMinimum[0], m_AbsMaximum[0]) ||
275 !readAxis(AbsY, m_AbsMinimum[1], m_AbsMaximum[1])) {
276 ERROR("virtio-input: invalid tablet axis ranges");
277 return false;
278 }
279 } else if (relativeX && relativeY) {
280 m_Type = Mouse;
281 } else if (keyA) {
282 m_Type = Keyboard;
283 } else {
284 ERROR("virtio-input: unsupported event set");
285 return false;
286 }
287
288 m_SlotCount = m_EventQueue.depth() < MaxSlots ? m_EventQueue.depth() : MaxSlots;
289 const size_t page = TargetInfo::getPageSize();
290 const size_t pages = (m_SlotCount * sizeof(Event) + page - 1) / page;
291 if (!m_SlotCount || !PhysicalMemoryManager::instance().allocateRegion(
292 m_Events, pages, PhysicalMemoryManager::continuous,
294 ERROR("virtio-input: could not allocate event buffers");
295 return false;
296 }
297
298 for (size_t i = 0; i < m_SlotCount; ++i) {
299 m_Slots[i] = {static_cast<Event*>(m_Events.virtualAddress()) + i,
300 m_Events.physicalAddress() + i * sizeof(Event)};
301 if (!postReceive(m_Slots[i])) {
302 ERROR("virtio-input: could not populate event queue");
303 return false;
304 }
305 }
306
307 m_Irq = m_Transport.registerInterrupt(this);
308 if (!m_Irq) {
309 ERROR("virtio-input: could not register PCI interrupt");
310 return false;
311 }
312 m_Active = true;
313 if (!m_Transport.ready()) {
314 m_Active = false;
315 ERROR("virtio-input: device did not accept DRIVER_OK");
316 return false;
317 }
318 m_Transport.notify(0);
319 NOTICE(
320 "virtio-input: " << (m_Type == Keyboard ? "keyboard" : (m_Type == Mouse ? "mouse" : "tablet"))
321 << " ready");
322 return true;
323}
324
325uint32_t VirtioInputDevice::scaleAxis(int32_t value, unsigned axis) const {
326 const int64_t minimum = m_AbsMinimum[axis];
327 const int64_t maximum = m_AbsMaximum[axis];
328 if (value <= minimum) {
329 return 0;
330 }
331 if (value >= maximum) {
332 return AbsoluteMaximum;
333 }
334 return uint64_t(int64_t(value) - minimum) * AbsoluteMaximum / (maximum - minimum);
335}
336
337void VirtioInputDevice::reportPointer() {
338 if (!m_PointerChanged) {
339 return;
340 }
341 if (m_Type == Mouse) {
342 InputManager::instance().mouseUpdate(m_Relative[0], -m_Relative[1], m_Relative[2], m_Buttons);
343 } else {
344 InputManager::instance().absoluteMouseUpdate(m_AbsPosition[0], m_AbsPosition[1], m_Relative[2],
345 m_Buttons);
346 }
347 m_Relative[0] = m_Relative[1] = m_Relative[2] = 0;
348 m_PointerChanged = false;
349}
350
351void VirtioInputDevice::handle(const Event& event) {
352 const uint16_t type = LITTLE_TO_HOST16(event.type);
353 const uint16_t code = LITTLE_TO_HOST16(event.code);
354 const int32_t value = static_cast<int32_t>(LITTLE_TO_HOST32(event.value));
355
356 if (m_Type == Keyboard) {
357 if (type == EvKey && (value == 0 || value == 1)) {
358 const uint8_t hid = hidFromLinux(code);
359 if (hid && m_KeyDown[hid] != (value == 1)) {
360 m_KeyDown[hid] = value == 1;
361 if (value) {
363 } else {
365 }
366 }
367 }
368 return;
369 }
370
371 if (type == EvSyn && code == 0) {
372 reportPointer();
373 } else if (type == EvKey && (value == 0 || value == 1)) {
374 const uint32_t mask = buttonMask(code);
375 if (mask) {
376 const uint32_t before = m_Buttons;
377 if (value) {
378 m_Buttons |= mask;
379 } else {
380 m_Buttons &= ~mask;
381 }
382 m_PointerChanged |= before != m_Buttons;
383 }
384 } else if (type == EvRel) {
385 if (code == RelWheel) {
386 m_Relative[2] += value;
387 m_PointerChanged = true;
388 } else if (m_Type == Mouse && code <= RelY) {
389 m_Relative[code] += value;
390 m_PointerChanged = true;
391 }
392 } else if (type == EvAbs && m_Type == Tablet && code <= AbsY) {
393 const uint32_t position = scaleAxis(value, code);
394 m_PointerChanged |= m_AbsPosition[code] != position;
395 m_AbsPosition[code] = position;
396 }
397}
398
400 (void)number;
401 LockGuard<Mutex> guard(m_Lock);
402 if (m_Stopping || !m_Active) {
404 }
405 if (!m_Transport.readIsr()) {
406 return IrqDisposition::NotHandled;
407 }
408
409 bool replenished = false;
410 Virtio::Completion completion;
411 while (m_EventQueue.pop(completion)) {
412 auto* slot = static_cast<Slot*>(completion.cookie);
413 if (slot && completion.length == sizeof(Event)) {
414 handle(*slot->data);
415 }
416 if (slot && postReceive(*slot)) {
417 replenished = true;
418 } else {
419 ERROR("virtio-input: could not replenish event queue");
420 }
421 }
422 if (replenished) {
423 m_Transport.notify(0);
424 }
425 return IrqDisposition::Handled;
426}
uint16_t getPciDeviceId()
Definition Device.h:228
uint16_t getPciVendorId()
Definition Device.h:224
Definition Event.h:49
static HidInputManager & instance()
Singleton design.
void keyDown(uint8_t keyCode)
Called when a key transitions to the "down" state.
void keyUp(uint8_t keyCode)
Called when a key transitions to the "up" state.
static InputManager & instance()
Singleton design.
void absoluteMouseUpdate(uint32_t x, uint32_t y, ssize_t wheel, uint32_t buttonBitmap)
Position coordinates use the evdev 0..0x7fff absolute pointer range.
void mouseUpdate(ssize_t relX, ssize_t relY, ssize_t relZ, uint32_t buttonBitmap)
Called whenever mouse input comes in.
void * virtualAddress() const
physical_uintptr_t physicalAddress() const
static PhysicalMemoryManager & instance()
virtual bool allocateRegion(MemoryRegion &Region, size_t cPages, size_t pageConstraints, size_t Flags, physical_uintptr_t start=-1)=0
static constexpr size_t getPageSize() noexcept
Definition TargetInfo.h:40
IrqDisposition irq(irq_id_t number) override
void EXPORTED_PUBLIC panic(const char *msg) NORETURN
Definition panic.cc:117
IrqDisposition
Definition IrqHandler.h:31