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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
|
#include "drivers/virtio.h"
#include "kernel/riscv.h"
#include "kernel/pmm.h"
#include "kernel/spinlock.h"
#include "lib/string.h"
#include "drivers/uart.h"
#include "kernel/proc.h"
#include "kernel/buf.h"
static uint64 virtio_base = 0x10001000;
// VirtIO Block Device Config Structure
struct virtio_blk_config
{
uint64 capacity;
uint32 size_max;
uint32 seg_max;
struct virtio_blk_geometry
{
uint16 cylinders;
uint8 heads;
uint8 sectors;
} geometry;
uint32 blk_size;
uint8 physical_block_exp;
uint8 alignment_offset;
uint16 min_io_size;
uint32 opt_io_size;
} __attribute__((packed));
static inline volatile uint32 *REG(uint32 offset)
{
return reinterpret_cast<volatile uint32 *>(virtio_base + offset);
}
namespace VirtIO
{
struct Disk
{
struct VRingDesc *desc;
struct VRingAvail *avail;
struct VRingUsed *used;
int free_head;
int num_free;
int free_chain[NUM_DESCS];
uint16 used_idx;
struct TraceInfo
{
int busy;
int status;
} info[NUM_DESCS];
struct BlockRequest reqs[NUM_DESCS];
uint8 req_status[NUM_DESCS];
Spinlock lock;
} disk;
void init()
{
disk.lock.init("virtio_disk");
// 1. Probing for VirtIO-MMIO device
bool found = false;
// Probe common VirtIO addresses in QEMU virt machine
for (uint64 addr = 0x10001000; addr < 0x10009000; addr += 0x1000)
{
volatile uint32 *magic = (volatile uint32 *)(addr + VIRTIO_MMIO_MAGIC_VALUE);
volatile uint32 *dev_id = (volatile uint32 *)(addr + VIRTIO_MMIO_DEVICE_ID);
if (*magic == 0x74726976 && *dev_id == 2) // ID 2 = Block Device
{
virtio_base = addr;
found = true;
Drivers::uart_puts(ANSI_GREEN "[VirtIO] Found Block Device @ ");
Drivers::print_hex(addr);
Drivers::uart_puts(ANSI_RESET "\n");
break;
}
}
if (!found)
{
Drivers::uart_puts(ANSI_RED "[VirtIO] Fatal - No Block Device found.\n" ANSI_RESET);
return;
}
// 2. Check Device Version (We want Modern v2)
if (*REG(VIRTIO_MMIO_VERSION) != 2)
{
Drivers::uart_puts(ANSI_YELLOW "[VirtIO] Warning: Legacy device detected (expected v2).\n" ANSI_RESET);
}
// 3. Reset Device
*REG(VIRTIO_MMIO_STATUS) = 0;
// 4. Set ACKNOWLEDGE & DRIVER status
uint32 status = *REG(VIRTIO_MMIO_STATUS);
status |= VIRTIO_CONFIG_S_ACKNOWLEDGE | VIRTIO_CONFIG_S_DRIVER;
*REG(VIRTIO_MMIO_STATUS) = status;
// 5. Feature Negotiation
*REG(VIRTIO_MMIO_DEVICE_FEATURES_SEL) = 0;
uint32 device_features_lo = *REG(VIRTIO_MMIO_DEVICE_FEATURES);
(void)device_features_lo;
*REG(VIRTIO_MMIO_DEVICE_FEATURES_SEL) = 1;
uint32 device_features_hi = *REG(VIRTIO_MMIO_DEVICE_FEATURES);
(void)device_features_hi;
*REG(VIRTIO_MMIO_DRIVER_FEATURES_SEL) = 0;
*REG(VIRTIO_MMIO_DRIVER_FEATURES) = 0;
*REG(VIRTIO_MMIO_DRIVER_FEATURES_SEL) = 1;
// VIRTIO_F_VERSION_1 is usually bit 32.
// If not defined in header, we hardcode logic here: (1ULL << 0) for high bits
*REG(VIRTIO_MMIO_DRIVER_FEATURES) = 1;
status |= VIRTIO_CONFIG_S_FEATURES_OK;
*REG(VIRTIO_MMIO_STATUS) = status;
if (!(*REG(VIRTIO_MMIO_STATUS) & VIRTIO_CONFIG_S_FEATURES_OK))
{
Drivers::uart_puts("VirtIO: Feature negotiation failed.\n");
return;
}
// 6. Queue Configuration
*REG(VIRTIO_MMIO_QUEUE_SEL) = 0;
if (*REG(VIRTIO_MMIO_QUEUE_READY))
{
Drivers::uart_puts("VirtIO: Queue 0 not ready.\n");
}
uint32 max_queue = *REG(VIRTIO_MMIO_QUEUE_NUM_MAX);
if (max_queue < NUM_DESCS)
{
Drivers::uart_puts("VirtIO: Queue size too small.\n");
return;
}
*REG(VIRTIO_MMIO_QUEUE_NUM) = NUM_DESCS;
// Allocate Memory for VirtQueues
void *ptr = PMM::alloc_pages(1); // 4KB is enough for small rings
memset(ptr, 0, PGSIZE);
uint64 base_addr = (uint64)ptr;
disk.desc = reinterpret_cast<struct VRingDesc *>(base_addr);
disk.avail = reinterpret_cast<struct VRingAvail *>(base_addr + NUM_DESCS * 16);
disk.used = reinterpret_cast<struct VRingUsed *>(base_addr + PGSIZE / 2);
*REG(VIRTIO_MMIO_QUEUE_DESC_LOW) = (uint32)base_addr;
*REG(VIRTIO_MMIO_QUEUE_DESC_HIGH) = (uint32)(base_addr >> 32);
uint64 avail_addr = (uint64)disk.avail;
*REG(VIRTIO_MMIO_QUEUE_DRIVER_LOW) = (uint32)avail_addr;
*REG(VIRTIO_MMIO_QUEUE_DRIVER_HIGH) = (uint32)(avail_addr >> 32);
uint64 used_addr = (uint64)disk.used;
*REG(VIRTIO_MMIO_QUEUE_DEVICE_LOW) = (uint32)used_addr;
*REG(VIRTIO_MMIO_QUEUE_DEVICE_HIGH) = (uint32)(used_addr >> 32);
*REG(VIRTIO_MMIO_QUEUE_READY) = 1;
// 7. Initialize Internal Structures
disk.free_head = 0;
disk.num_free = NUM_DESCS;
disk.used_idx = 0;
for (int i = 0; i < NUM_DESCS; i++)
{
disk.free_chain[i] = i + 1;
disk.info[i].busy = 0;
}
// 8. Finalize Setup
status |= VIRTIO_CONFIG_S_DRIVER_OK;
*REG(VIRTIO_MMIO_STATUS) = status;
// === [Feature] Read Capacity ===
volatile struct virtio_blk_config *config = (volatile struct virtio_blk_config *)(virtio_base + VIRTIO_MMIO_CONFIG);
uint64 capacity_sectors = config->capacity;
uint64 size_mb = (capacity_sectors * 512) / (1024 * 1024);
Drivers::uart_puts(ANSI_GREEN "[VirtIO] Initialized. Capacity: ");
Drivers::uart_put_int(size_mb);
Drivers::uart_puts(" MB\n" ANSI_RESET);
}
static int alloc_desc()
{
if (disk.num_free == 0)
return -1;
int id = disk.free_head;
disk.free_head = disk.free_chain[id];
disk.num_free--;
return id;
}
static void free_desc(int id)
{
disk.free_chain[id] = disk.free_head;
disk.free_head = id;
disk.num_free++;
ProcManager::wakeup(&disk.free_head);
}
static void free_chain_soft(int head_idx)
{
int curr = head_idx;
while (1)
{
int flag = disk.desc[curr].flags;
int next = disk.desc[curr].next;
free_desc(curr);
if (flag & VRING_DESC_F_NEXT)
curr = next;
else
break;
}
}
void rw(int write, uint64 blockno, char *buf)
{
uint64 sector = blockno * (1024 / 512); // Assume 1KB blocks
disk.lock.acquire();
// Wait for enough descriptors
while (disk.num_free < 3)
{
ProcManager::sleep(&disk.free_head, &disk.lock);
}
int idx[3];
idx[0] = alloc_desc();
idx[1] = alloc_desc();
idx[2] = alloc_desc();
// 1. Request Header
struct BlockRequest *req = &disk.reqs[idx[0]];
req->type = write ? VIRTIO_BLK_T_OUT : VIRTIO_BLK_T_IN;
req->reserved = 0;
req->sector = sector;
disk.desc[idx[0]].addr = (uint64)req;
disk.desc[idx[0]].len = sizeof(struct BlockRequest);
disk.desc[idx[0]].flags = VRING_DESC_F_NEXT;
disk.desc[idx[0]].next = idx[1];
// 2. Data Buffer
disk.desc[idx[1]].addr = (uint64)buf;
disk.desc[idx[1]].len = 1024; // Fixed 1KB for this test
disk.desc[idx[1]].flags = VRING_DESC_F_NEXT;
if (!write)
disk.desc[idx[1]].flags |= VRING_DESC_F_WRITE;
disk.desc[idx[1]].next = idx[2];
// 3. Status Byte
disk.desc[idx[2]].addr = (uint64)&disk.req_status[idx[0]];
disk.desc[idx[2]].len = 1;
disk.desc[idx[2]].flags = VRING_DESC_F_WRITE;
disk.desc[idx[2]].next = 0;
disk.info[idx[0]].busy = 1;
// Submit to Avail Ring
disk.avail->ring[disk.avail->idx % NUM_DESCS] = idx[0];
__sync_synchronize();
disk.avail->idx++;
__sync_synchronize();
// Notify Device
*REG(VIRTIO_MMIO_QUEUE_NOTIFY) = 0;
// Wait for Completion
while (disk.info[idx[0]].busy)
{
ProcManager::sleep(&disk.info[idx[0]], &disk.lock);
}
free_chain_soft(idx[0]);
disk.lock.release();
}
void intr()
{
disk.lock.acquire();
uint32 status = *REG(VIRTIO_MMIO_INTERRUPT_STATUS);
*REG(VIRTIO_MMIO_INTERRUPT_ACK) = status & 0x3;
volatile struct VRingUsed *used_ring = (volatile struct VRingUsed *)disk.used;
while (disk.used_idx != used_ring->idx)
{
int id = used_ring->ring[disk.used_idx % NUM_DESCS].id;
if (disk.info[id].busy)
{
disk.info[id].busy = 0;
ProcManager::wakeup(&disk.info[id]);
}
disk.used_idx++;
}
disk.lock.release();
}
void test_rw()
{
Drivers::uart_puts(ANSI_BLUE "[VirtIO] Disk Test Start...\n" ANSI_RESET);
char *buf = (char *)PMM::alloc_page();
if (!buf)
return;
const char *msg = "Lume OS Disk Driver Test Pattern";
strcpy(buf, msg);
rw(1, 0, buf); // Write
memset(buf, 0, PGSIZE);
rw(0, 0, buf); // Read
if (strcmp(buf, msg) == 0)
{
Drivers::uart_puts(ANSI_GREEN "[VirtIO] Test PASSED (R/W Verified).\n" ANSI_RESET);
}
else
{
Drivers::uart_puts(ANSI_RED "[VirtIO] Test FAILED.\n" ANSI_RESET);
}
PMM::free_page(buf);
}
void test_bio()
{
Drivers::uart_puts(ANSI_BLUE "[Bio] Starting Buffer Cache Test...\n" ANSI_RESET);
struct Buf *b = BufferCache::bread(0, 100);
Drivers::uart_puts("[Bio] Bread block 100 (1st time).\n");
b->data[0] = 'L';
b->data[1] = 'u';
b->data[2] = 'm';
b->data[3] = 'e';
b->data[4] = '\0';
BufferCache::bwrite(b);
Drivers::uart_puts("[Bio] Bwrite block 100.\n");
BufferCache::brelse(b);
Drivers::uart_puts("[Bio] Brelease block 100.\n");
struct Buf *b2 = BufferCache::bread(0, 100);
Drivers::uart_puts("[Bio] Bread block 100 (2nd time).\n");
if (b2->data[0] == 'L' && b2->data[1] == 'u' && b2->data[3] == 'e')
{
Drivers::uart_puts(ANSI_GREEN "[Bio] Data verification PASSED.\n" ANSI_RESET);
}
else
{
Drivers::uart_puts(ANSI_RED "[Bio] Data verification FAILED.\n" ANSI_RESET);
}
BufferCache::brelse(b2);
}
}
|