Voxia OS v0.0.1
Hobby Project Operating System Targeting x86-64
Loading...
Searching...
No Matches
virtio-gpu.cpp
Go to the documentation of this file.
1#include "ioforge/ioforge.h"
2#include "type.h"
3#include <cstdint>
4#include <stdint.h>
6#include <str.h>
7
8//TODO: refactor
9// Semua ini tidak ada hubungannya dengan GPU spesifik
10// virtq_init()
11// virtq_alloc_desc()
12// virtq_free_desc()
13// virtq_add_buf()
14// virtq_kick()
15// virtq_get_used_elem()
16
17#define ALIGN_UP(x, align) (((x) + (align) - 1) & ~((align) - 1))
18
19struct virtio_gpu_queue VirtioGpu::control_queue_ = {0};
20
21static volatile boolean_t interrupt_exist = 0;
22
24 uint8_t common_bar = dev_->common_cfg.bar;
25 log(mod, "common config BAR: %d", common_bar);
26
27 uintptr_t common_phys =
28 dev_->pci.bar[common_bar].address + dev_->common_cfg.offset;
29 log(mod, "common config phys addr: 0x%x", common_phys);
30
31 volatile virtio_pci_common_cfg_t* common_cfg =
32 (volatile virtio_pci_common_cfg_t*) common_phys;
33
34 uintptr_t notify_phys =
35 dev_->pci.bar[common_bar].address + dev_->notify_cfg.offset;
36 notify_base_ = notify_phys;
37 log(mod, "notify config phys addr: 0x%x", notify_phys);
38
39 notify_multiplier_ = dev_->notify_cfg.length;
40
41 // Acknowledge device
42 uint8_t status = 0;
43 common_cfg->device_status = status; // write: 0x0
44
46 common_cfg->device_status = status; // write: 0x1
47
49 common_cfg->device_status = status;
50
51 // Feature negotiation
53 common_cfg->device_status = status;
54
55 // Verify features
56 if (!(common_cfg->device_status & VIRTIO_STATUS_FEATURES_OK)) {
57 log(mod, "Feature negotiation failed");
58 return;
59 }
60
61 auto device_features = common_cfg->device_feature;
62 log(mod, "Device features: 0x%x", device_features);
63
65 common_cfg->device_status = status;
66
67 //
68
69 // Feature negotiation
70 common_cfg->device_feature_select = 0;
71
72 // Accept only essential features for now
73 common_cfg->driver_feature_select = 0;
74 common_cfg->driver_feature =
75 device_features & 0x1; // Only basic feature bit
76 common_cfg->device_status |= VIRTIO_STATUS_FEATURES_OK;
77
78 // Verify features
79 if (!(common_cfg->device_status & VIRTIO_STATUS_FEATURES_OK)) {
80 log(mod, "Feature negotiation failed");
81 return;
82 }
83 log(mod, "Features OK");
84
85 // Setup control queue
86 common_cfg->queue_select = 0;
87 uint16_t queue_size = common_cfg->queue_size;
90 }
91 log(mod, "Queue size: %d", queue_size);
92
93 // Allocate and initialize virtqueue
94 uintptr_t desc_size = queue_size * sizeof(struct virtq_desc);
95 uintptr_t avail_off = desc_size;
96 uintptr_t avail_size = sizeof(uint16_t) * (2 + queue_size);
97 uintptr_t used_off =
98 ALIGN_UP(avail_off + avail_size, VIRTIO_GPU_QUEUE_ALIGN);
99 uintptr_t used_size = sizeof(struct virtq_used)
100 + sizeof(struct virtq_used_elem) * queue_size;
101 size_t vq_total_size =
102 ALIGN_UP(used_off + used_size, VIRTIO_GPU_QUEUE_ALIGN);
103
104 uintptr_t vq_phys = 0;
105 void* vq_mem = IOUtils::DMAAlloc(vq_total_size, &vq_phys);
106
107 if (!vq_mem) {
108 log(mod, "Failed to allocate virtqueue memory");
109 return;
110 }
111 memset(vq_mem, 0, vq_total_size);
112
113 virtq_init(&control_queue_, vq_mem, queue_size, vq_phys);
114
115 // Configure queue with physical addresses
116 common_cfg->queue_desc = vq_phys;
117 common_cfg->queue_avail =
118 vq_phys + queue_size * sizeof(struct virtq_desc);
119
120 // Calculate used ring address properly
121 uintptr_t used_offset =
122 (uintptr_t) control_queue_.used - (uintptr_t) vq_mem;
123 common_cfg->queue_used = vq_phys + used_offset;
124
125 notify_offset_ = common_cfg->queue_notify_off * notify_multiplier_;
126
127 log(mod,
128 "Virtqueue configured: desc=0x%x, avail=0x%x, used=0x%x, "
129 "notify_offset_=0x%x",
130 common_cfg->queue_desc, common_cfg->queue_avail,
131 common_cfg->queue_used, notify_offset_);
132
133 // Enable queue
134 common_cfg->queue_enable = 1;
135 log(mod, "Queue enabled");
136
137 // Driver OK
138 common_cfg->device_status |= VIRTIO_STATUS_DRIVER_OK;
139 log(mod, "Driver OK");
140
141 // FIX: Add delay to ensure device is ready
142 for (volatile int i = 0; i < 100000; i++)
143 ;
144
145 // gpu_dev.initialized = true;
146
147 log(mod, "VirtIO GPU initialized successfully");
148
149 // irq
150 if (dev_->pci.interrupt_line) {
151 isr_irq_register(dev_->pci.interrupt_line, (void *)VirtioGpu::fireHandler);
152 }
153
154 // -----
155 // TEST
156 // -----
157 // Allocate buffers untuk test
158 uintptr_t test_phys = 0;
159 struct virtio_gpu_ctrl_hdr* test_cmd =
160 (struct virtio_gpu_ctrl_hdr*) IOUtils::DMAAlloc(
161 sizeof(*test_cmd), &test_phys);
162
163 uintptr_t test_resp_phys = 0;
164 struct virtio_gpu_resp_display_info* test_resp =
165 (struct virtio_gpu_resp_display_info*) IOUtils::DMAAlloc(
166 sizeof(*test_resp), &test_resp_phys);
167
168 if (!test_cmd || !test_resp) {
169 log(mod, "Failed to allocate test buffers");
170 return;
171 }
172
173 // Test communication
174 memset(test_cmd, 0, sizeof(*test_cmd));
176
177 memset(test_resp, 0, sizeof(*test_resp));
178
179 log(mod, "Test buffers allocated: cmd=0x%x, resp=0x%x", test_cmd,
180 test_resp);
181
182 // Beri waktu untuk device stabil
183 for (volatile int i = 0; i < 1000000; i++)
184 ;
185
186 // Coba kirim command
187 int result = virtio_gpu_send_command(
188 (void*) test_phys, sizeof(*test_cmd), (void*) test_resp_phys,
189 sizeof(*test_resp));
190
191 if (result == 0) {
192 log(mod, "GPU communication test PASSED");
193
194 // print resp
195 log(mod, "Response type: 0x%x len : %d", test_resp->hdr.type,
196 sizeof(*test_resp));
197
198 if (test_resp->hdr.type == VIRTIO_GPU_RESP_OK_DISPLAY_INFO) {
199 log(mod, "Display info received successfully");
200 int enabled_count = 0;
201 for (int i = 0; i < 16; i++) {
202 if (test_resp->pmodes[i].enabled) {
203 log(mod, "Scanout %d: %dx%d at (%d,%d)",
204 i, test_resp->pmodes[i].rect.width,
205 test_resp->pmodes[i].rect.height,
206 test_resp->pmodes[i].rect.x,
207 test_resp->pmodes[i].rect.y);
208 enabled_count++;
209 }
210 }
211 if (enabled_count == 0) {
212 log(mod, "No enabled display modes found");
213 }
214 } else {
215 log(mod, "Unexpected response type: 0x%x",
216 test_resp->hdr.type);
217 }
218 } else {
219 log(mod, "GPU communication test FAILED");
220 }
221
222 // / Create a test resource
223 if (virtio_gpu_create_resource(1, 1366, 768) == 0) {
224 log(mod, "Test resource created successfully");
225 } else {
226 log(mod, "Failed to create test resource");
227 }
228}
229
232 uintptr_t isr_addr =
233 instance->dev_->pci.bar[instance->dev_->isr_cfg.bar].address
234 + instance->dev_->isr_cfg.offset;
235
236 uint8_t isr = *(uint8_t*) isr_addr;
237 if (isr & (1 << 0)) {
238 interrupt_exist = true;
239 }
240}
241
243 if (vq->num_free == 0) {
244 log(mod, "No free descriptors");
245 return vq->queue_size;
246 }
247
248 uint16_t desc_idx = vq->free_head;
249 vq->free_head = vq->desc[desc_idx].next;
250 vq->num_free--;
251
252 // Clear the descriptor
253 vq->desc[desc_idx].addr = 0;
254 vq->desc[desc_idx].len = 0;
255 vq->desc[desc_idx].flags = 0;
256 vq->desc[desc_idx].next = 0;
257
258 return desc_idx;
259}
260
262 uint16_t desc_idx) {
263 vq->desc[desc_idx].next = vq->free_head;
264 vq->free_head = desc_idx;
265 vq->num_free++;
266}
267
268void VirtioGpu::virtq_init(struct virtio_gpu_queue* vq, void* vq_mem,
270 uintptr_t base = (uintptr_t) vq_mem;
271
272 vq->desc = (struct virtq_desc*) base;
273
274 uintptr_t avail_off = queue_size * sizeof(struct virtq_desc);
275 vq->avail = (struct virtq_avail*) (base + avail_off);
276
277 // avail size = flags(2) + idx(2) + ring[queue_size](2 each) + used_event(2)
278 uintptr_t used_off = avail_off + sizeof(uint16_t) * (3 + queue_size);
279 used_off = ALIGN_UP(used_off, VIRTIO_GPU_QUEUE_ALIGN);
280 vq->used = (struct virtq_used*) (base + used_off);
281
283 vq->free_head = 0;
284 vq->num_free = queue_size;
285 vq->last_used_idx = 0;
286 vq->phys_addr = phys_addr;
287
288 // Initialize descriptor chain
289 for (uint16_t i = 0; i < queue_size - 1; i++) {
290 vq->desc[i].next = i + 1;
291 }
292 vq->desc[queue_size - 1].next = 0;
293
294 // Initialize rings
295 memset(vq->avail, 0,
296 sizeof(struct virtq_avail) + sizeof(uint16_t) * queue_size);
297 memset(vq->used, 0,
298 sizeof(struct virtq_used)
299 + sizeof(struct virtq_used_elem) * queue_size);
300
301 vq->avail->idx = 0;
302 vq->used->idx = 0;
303
304 log(mod, "Virtqueue initialized: desc=%x, avail=%x, used=%x", vq->desc,
305 vq->avail, vq->used);
306}
307
308int VirtioGpu::virtq_add_buf(struct virtio_gpu_queue* vq, void** buffers,
309 uint32_t* lengths, uint16_t num_out,
310 uint16_t num_in, uint16_t* head_out) {
311
312 if (num_out + num_in == 0 || num_out + num_in > vq->num_free) {
313 log(mod, "Not enough descriptors: free=%d, needed=%d",
314 vq->num_free, num_out + num_in);
315 return -1;
316 }
317
320 *head_out = head;
321
322 log(mod, "Building descriptor chain, head=%d, num_out=%d, num_in=%d",
323 head, num_out, num_in);
324
325 // Add output buffers (device reads)
326 for (uint16_t i = 0; i < num_out; i++) {
327 // uint64_t phys_addr = virt_to_phys(buffers[i]);
328 uint64_t phys_addr = (uint64_t) buffers[i];
329 vq->desc[prev].addr = phys_addr;
330 vq->desc[prev].len = lengths[i];
332
333 log(mod, "OUT desc[%d]: phys=0x%x, len=%d, flags=0x%x, next=%d",
334 prev, phys_addr, lengths[i], vq->desc[prev].flags,
335 vq->desc[prev].next);
336
337 if (i < num_out - 1) {
339 vq->desc[prev - 1].next = prev;
340 }
341 }
342
343 // Add input buffers (device writes)
344 for (uint16_t i = 0; i < num_in; i++) {
346 if (i == 0 && num_out == 0) {
347 idx = head;
348 } else {
349 idx = virtq_alloc_desc(vq);
350 vq->desc[prev].next = idx;
351 prev = idx;
352 }
353
354 uint64_t phys_addr = (uint64_t) (buffers[num_out + i]);
355 // uint64_t phys_addr = (uint64_t) buffers[i];
356 vq->desc[idx].addr = phys_addr;
357 vq->desc[idx].len = lengths[num_out + i];
359
360 log(mod, "IN desc[%d]: phys=0x%x, len=%d, flags=0x%x", idx,
361 phys_addr, lengths[num_out + i], vq->desc[idx].flags);
362
363 if (i < num_in - 1) {
365 prev = idx;
366 }
367 }
368
369 // Remove NEXT flag from last descriptor
370 if (num_in > 0) {
372 log(mod, "Last IN desc[%d]: flags=0x%x (NEXT removed)", prev,
373 vq->desc[prev].flags);
374 } else if (num_out > 0) {
376 log(mod, "Last OUT desc[%d]: flags=0x%x (NEXT removed)", prev,
377 vq->desc[prev].flags);
378 }
379
380 return 0;
381}
382
384 // Strong memory barrier before notifying device
385 asm volatile("mfence" ::: "memory");
386
387 uint32_t notify_addr =
388 notify_offset_ + queue_index * notify_multiplier_;
389 volatile uint32_t* notify_reg =
390 (volatile uint32_t*) ((uintptr_t) notify_base_ + notify_addr);
391
392 log(mod,
393 "Notifying queue %d at address 0x%x (notify_base=%0x%x, "
394 "offset=0x%x, "
395 "multiplier=0x%x)",
396 queue_index, notify_reg, notify_base_, notify_offset_,
398
399 // Write the queue index to notify the device
400 *notify_reg = queue_index;
401
402 // Strong memory barrier after notification
403 asm volatile("mfence" ::: "memory");
404}
405
407 uint32_t* len) {
408 // Memory barrier to ensure we see updated used ring
409 asm volatile("" ::: "memory");
410
411 if (vq->last_used_idx == vq->used->idx) {
412 return -1;
413 }
414
415 uint16_t used_idx = vq->last_used_idx % vq->queue_size;
416 struct virtq_used_elem* elem = &vq->used->ring[used_idx];
417
418 *id = elem->id;
419 *len = elem->len;
420 vq->last_used_idx++;
421
422 return 0;
423}
424
425// ---------------- VirtIO GPU Commands ----------------
426int VirtioGpu::virtio_gpu_send_command(void* cmd, uint32_t cmd_size, void* resp,
427 uint32_t resp_size) {
428 void* buffers[2];
429 uint32_t lengths[2];
430 uint16_t num_out = 0, num_in = 0;
431
432 // Setup command buffer (output - device reads)
433 if (cmd && cmd_size > 0) {
434 buffers[0] = cmd;
435 lengths[0] = cmd_size;
436 num_out = 1;
437 log(mod, "Command buffer: virt=%x, size=%d", cmd, cmd_size);
438 }
439
440 // Setup response buffer (input - device writes)
441 if (resp && resp_size > 0) {
442 buffers[num_out] = resp;
443 lengths[num_out] = resp_size;
444 num_in = 1;
445 log(mod, "Response buffer: virt=%x, size=%d", resp, resp_size);
446 }
447
449 if (virtq_add_buf(&control_queue_, buffers, lengths, num_out, num_in,
450 &head)
451 < 0) {
452 log(mod, "Failed to add buffer to virtqueue");
453 return -1;
454 }
455
456 // Add to available ring
457 // avail->idx dipakai langsung (uint16 wrap natural), ring yang di-mod
458 uint16_t avail_idx =
459 control_queue_.avail->idx % control_queue_.queue_size;
460 control_queue_.avail->ring[avail_idx] = head;
461
462 // Memory barrier sebelum update avail->idx agar device melihat
463 // descriptor sebelum melihat idx yang baru
464 asm volatile("mfence" ::: "memory");
465
466 control_queue_.avail->idx++;
467
468 log(mod, "Added buffer head=%d to avail ring[%d], new avail->idx=%d",
469 head, avail_idx, control_queue_.avail->idx);
470
471 // Notify device
472 virtq_kick(0);
473
474 int timeout = 5000000;
475
476 for (int i = 0; i < timeout; i++) {
477 // Pastikan CPU melihat perubahan memori dari device
478 asm volatile("" ::: "memory");
479
480 uint16_t used_id;
481 uint32_t used_len;
482
483 // Konsumsi semua used element yang tersedia sampai kita temukan
484 // head kita. Elemen lain yang bukan milik kita di-log tapi tidak
485 // dibuang begitu saja — idealnya masuk pending queue, tapi di sini
486 // kita skip dan lanjut polling agar tidak hang.
487 while (virtq_get_used_elem(&control_queue_, &used_id, &used_len)
488 == 0) {
489 log(mod,
490 "Got used element: id=%d, len=%d (expected "
491 "head=%d)",
492 used_id, used_len, head);
493
494 if (used_id == head) {
495 // Bebaskan descriptor chain
496 uint16_t desc_idx = used_id;
497 while (desc_idx < control_queue_.queue_size
498 && (control_queue_.desc[desc_idx].flags
500 uint16_t next =
501 control_queue_.desc[desc_idx]
502 .next;
504 desc_idx);
505 desc_idx = next;
506 }
507 if (desc_idx < control_queue_.queue_size) {
509 desc_idx);
510 }
511
512 log(mod,
513 "Command completed successfully (head=%d)",
514 head);
515 return 0;
516 } else {
517 // Used element bukan milik command ini.
518 // Descriptor tetap sudah di-consume dari ring;
519 // catat saja dan lanjutkan polling.
520 log(mod,
521 "Unexpected used id=%d (expected %d), "
522 "skipping",
523 used_id, head);
524 }
525 }
526
527 // Interrupt hanya sebagai sinyal: reset flag lalu re-check used ring
528 // di iterasi berikutnya. Jangan break di sini!
529 if (interrupt_exist) {
530 log(mod, "ISR triggered, re-checking used ring");
531 interrupt_exist = false;
532 // Tidak break — kembali ke atas untuk cek used ring
533 continue;
534 }
535
536 IOUtils::sleep(5);
537
538 if (i % 100000 == 0) {
539 log(mod, "Still waiting for response... (i=%d)", i);
540 }
541 }
542
543 // Timeout — dump state untuk debug
544 log(mod, "Timeout waiting for command response (head=%d)", head);
545 log(mod, "Used ring state: last_used_idx=%d, used->idx=%d",
546 control_queue_.last_used_idx, control_queue_.used->idx);
547
548 log(mod, "Descriptor chain for head=%d:", head);
549 uint16_t desc_idx = head;
550 int chain_len = 0;
551 while (desc_idx < control_queue_.queue_size && chain_len < 10) {
552 log(mod, " desc[%d]: addr=0x%lx, len=%d, flags=0x%x, next=%d",
553 desc_idx, control_queue_.desc[desc_idx].addr,
554 control_queue_.desc[desc_idx].len,
555 control_queue_.desc[desc_idx].flags,
556 control_queue_.desc[desc_idx].next);
557
558 if (!(control_queue_.desc[desc_idx].flags & VIRTQ_DESC_F_NEXT))
559 break;
560
561 desc_idx = control_queue_.desc[desc_idx].next;
562 chain_len++;
563 }
564
565 return -1;
566}
static AHCIModule instance
Definition init.cpp:5
const char * mod
Definition ioforge.hpp:65
void setup()
ioforge_virtio_device * dev_
uintptr_t notify_base_
static VirtioGpu * getInstance()
Definition init.cpp:11
uint32_t notify_multiplier_
int virtq_get_used_elem(struct virtio_gpu_queue *vq, uint16_t *id, uint32_t *len)
int virtio_gpu_create_resource(uint32_t resource_id, uint32_t width, uint32_t height)
Definition ops.cpp:57
void virtq_free_desc(struct virtio_gpu_queue *vq, uint16_t desc_idx)
uint16_t virtq_alloc_desc(struct virtio_gpu_queue *vq)
static struct virtio_gpu_queue control_queue_
void virtq_init(struct virtio_gpu_queue *vq, void *vq_mem, uint16_t queue_size, uintptr_t phys_addr)
static void fireHandler()
void virtq_kick(uint16_t queue_index)
int virtq_add_buf(struct virtio_gpu_queue *vq, void **buffers, uint32_t *lengths, uint16_t num_out, uint16_t num_in, uint16_t *head_out)
int virtio_gpu_send_command(void *cmd, uint32_t cmd_size, void *resp, uint32_t resp_size)
uintptr_t notify_offset_
volatile uint8_t cmd
Definition e1000.hpp:3
volatile uint8_t status
Definition e1000.hpp:3
elf_section_map uintptr_t base
Definition elf.h:296
uint32_t isr_irq_register(uint8_t irq, void *handler)
#define log(mod, fmt,...)
Definition ioforge.hpp:12
struct virtio_pci_cap common_cfg
#define ALIGN_UP(x, align)
Definition memory_utils.h:6
struct process * prev
Definition process.h:19
uintptr_t phys_addr
Definition slab.h:6
void memset(void *ptr, int value, size_t num)
uint16_t last_used_idx
struct virtq_desc * desc
struct virtq_avail * avail
struct virtq_used * used
uintptr_t phys_addr
struct virtio_gpu_resp_display_info::virtio_gpu_display_one pmodes[16]
struct virtio_gpu_ctrl_hdr hdr
uint16_t idx
uint64_t addr
uint32_t len
uint16_t next
uint16_t flags
struct virtq_used_elem ring[64]
uint16_t idx
uint32_t head
Definition tty.h:10
unsigned short uint16_t
Definition type.h:13
unsigned int uint32_t
Definition type.h:19
uint8_t boolean_t
Definition type.h:89
unsigned long uintptr_t
Definition type.h:73
unsigned long uint64_t
Definition type.h:25
unsigned char uint8_t
Definition type.h:7
static volatile boolean_t interrupt_exist
#define VIRTIO_STATUS_DRIVER_OK
#define VIRTQ_DESC_F_WRITE
#define VIRTQ_DESC_F_NEXT
uint16_t next
Definition virtio-gpu.hpp:3
uint16_t idx
Definition virtio-gpu.hpp:1
#define VIRTIO_GPU_QUEUE_ALIGN
#define VIRTIO_STATUS_DRIVER
#define VIRTIO_GPU_QUEUE_SIZE
#define VIRTIO_STATUS_ACKNOWLEDGE
uint32_t len
Definition virtio-gpu.hpp:1
uint16_t queue_size
#define VIRTIO_GPU_RESP_OK_DISPLAY_INFO
#define VIRTIO_GPU_CMD_GET_DISPLAY_INFO
#define VIRTIO_STATUS_FEATURES_OK