1 /* 2 * Copyright 2013, Jérôme Duval, korli@users.berlios.de. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include <condition_variable.h> 8 #include <lock.h> 9 #include <StackOrHeapArray.h> 10 #include <virtio.h> 11 12 #include "virtio_blk.h" 13 14 15 class DMAResource; 16 class IOScheduler; 17 18 19 static const uint8 kDriveIcon[] = { 20 0x6e, 0x63, 0x69, 0x66, 0x08, 0x03, 0x01, 0x00, 0x00, 0x02, 0x00, 0x16, 21 0x02, 0x3c, 0xc7, 0xee, 0x38, 0x9b, 0xc0, 0xba, 0x16, 0x57, 0x3e, 0x39, 22 0xb0, 0x49, 0x77, 0xc8, 0x42, 0xad, 0xc7, 0x00, 0xff, 0xff, 0xd3, 0x02, 23 0x00, 0x06, 0x02, 0x3c, 0x96, 0x32, 0x3a, 0x4d, 0x3f, 0xba, 0xfc, 0x01, 24 0x3d, 0x5a, 0x97, 0x4b, 0x57, 0xa5, 0x49, 0x84, 0x4d, 0x00, 0x47, 0x47, 25 0x47, 0xff, 0xa5, 0xa0, 0xa0, 0x02, 0x00, 0x16, 0x02, 0xbc, 0x59, 0x2f, 26 0xbb, 0x29, 0xa7, 0x3c, 0x0c, 0xe4, 0xbd, 0x0b, 0x7c, 0x48, 0x92, 0xc0, 27 0x4b, 0x79, 0x66, 0x00, 0x7d, 0xff, 0xd4, 0x02, 0x00, 0x06, 0x02, 0x38, 28 0xdb, 0xb4, 0x39, 0x97, 0x33, 0xbc, 0x4a, 0x33, 0x3b, 0xa5, 0x42, 0x48, 29 0x6e, 0x66, 0x49, 0xee, 0x7b, 0x00, 0x59, 0x67, 0x56, 0xff, 0xeb, 0xb2, 30 0xb2, 0x03, 0xa7, 0xff, 0x00, 0x03, 0xff, 0x00, 0x00, 0x04, 0x01, 0x80, 31 0x07, 0x0a, 0x06, 0x22, 0x3c, 0x22, 0x49, 0x44, 0x5b, 0x5a, 0x3e, 0x5a, 32 0x31, 0x39, 0x25, 0x0a, 0x04, 0x22, 0x3c, 0x44, 0x4b, 0x5a, 0x31, 0x39, 33 0x25, 0x0a, 0x04, 0x44, 0x4b, 0x44, 0x5b, 0x5a, 0x3e, 0x5a, 0x31, 0x0a, 34 0x04, 0x22, 0x3c, 0x22, 0x49, 0x44, 0x5b, 0x44, 0x4b, 0x08, 0x02, 0x27, 35 0x43, 0xb8, 0x14, 0xc1, 0xf1, 0x08, 0x02, 0x26, 0x43, 0x29, 0x44, 0x0a, 36 0x05, 0x44, 0x5d, 0x49, 0x5d, 0x60, 0x3e, 0x5a, 0x3b, 0x5b, 0x3f, 0x08, 37 0x0a, 0x07, 0x01, 0x06, 0x00, 0x0a, 0x00, 0x01, 0x00, 0x10, 0x01, 0x17, 38 0x84, 0x00, 0x04, 0x0a, 0x01, 0x01, 0x01, 0x00, 0x0a, 0x02, 0x01, 0x02, 39 0x00, 0x0a, 0x03, 0x01, 0x03, 0x00, 0x0a, 0x04, 0x01, 0x04, 0x10, 0x01, 40 0x17, 0x85, 0x20, 0x04, 0x0a, 0x06, 0x01, 0x05, 0x30, 0x24, 0xb3, 0x99, 41 0x01, 0x17, 0x82, 0x00, 0x04, 0x0a, 0x05, 0x01, 0x05, 0x30, 0x20, 0xb2, 42 0xe6, 0x01, 0x17, 0x82, 0x00, 0x04 43 }; 44 45 46 #define VIRTIO_BLOCK_DRIVER_MODULE_NAME "drivers/disk/virtual/virtio_block/driver_v1" 47 #define VIRTIO_BLOCK_DEVICE_MODULE_NAME "drivers/disk/virtual/virtio_block/device_v1" 48 #define VIRTIO_BLOCK_DEVICE_ID_GENERATOR "virtio_block/device_id" 49 50 51 typedef struct { 52 device_node* node; 53 ::virtio_device virtio_device; 54 virtio_device_interface* virtio; 55 ::virtio_queue virtio_queue; 56 IOScheduler* io_scheduler; 57 DMAResource* dma_resource; 58 59 struct virtio_blk_config config; 60 61 area_id bufferArea; 62 addr_t bufferAddr; 63 phys_addr_t bufferPhysAddr; 64 65 uint64 features; 66 uint64 capacity; 67 uint32 block_size; 68 uint32 physical_block_size; 69 status_t media_status; 70 71 mutex lock; 72 int32 currentRequest; 73 ConditionVariable interruptCondition; 74 ConditionVariableEntry interruptConditionEntry; 75 } virtio_block_driver_info; 76 77 78 typedef struct { 79 virtio_block_driver_info* info; 80 } virtio_block_handle; 81 82 83 #include <stdio.h> 84 #include <string.h> 85 #include <stdlib.h> 86 87 #include <fs/devfs.h> 88 89 #include "dma_resources.h" 90 #include "IORequest.h" 91 #include "IOSchedulerSimple.h" 92 93 94 //#define TRACE_VIRTIO_BLOCK 95 #ifdef TRACE_VIRTIO_BLOCK 96 # define TRACE(x...) dprintf("virtio_block: " x) 97 #else 98 # define TRACE(x...) ; 99 #endif 100 #define ERROR(x...) dprintf("\33[33mvirtio_block:\33[0m " x) 101 #define CALLED() TRACE("CALLED %s\n", __PRETTY_FUNCTION__) 102 103 104 static device_manager_info* sDeviceManager; 105 106 107 bool virtio_block_set_capacity(virtio_block_driver_info* info); 108 109 110 const char * 111 get_feature_name(uint64 feature) 112 { 113 switch (feature) { 114 case VIRTIO_BLK_F_BARRIER: 115 return "host barrier"; 116 case VIRTIO_BLK_F_SIZE_MAX: 117 return "maximum segment size"; 118 case VIRTIO_BLK_F_SEG_MAX: 119 return "maximum segment count"; 120 case VIRTIO_BLK_F_GEOMETRY: 121 return "disk geometry"; 122 case VIRTIO_BLK_F_RO: 123 return "read only"; 124 case VIRTIO_BLK_F_BLK_SIZE: 125 return "block size"; 126 case VIRTIO_BLK_F_SCSI: 127 return "scsi commands"; 128 case VIRTIO_BLK_F_FLUSH: 129 return "flush command"; 130 case VIRTIO_BLK_F_TOPOLOGY: 131 return "topology"; 132 case VIRTIO_BLK_F_CONFIG_WCE: 133 return "config wce"; 134 } 135 return NULL; 136 } 137 138 139 static status_t 140 get_geometry(virtio_block_handle* handle, device_geometry* geometry) 141 { 142 virtio_block_driver_info* info = handle->info; 143 144 devfs_compute_geometry_size(geometry, info->capacity, info->block_size); 145 geometry->bytes_per_physical_sector = info->physical_block_size; 146 147 geometry->device_type = B_DISK; 148 geometry->removable = false; 149 150 geometry->read_only = ((info->features & VIRTIO_BLK_F_RO) != 0); 151 geometry->write_once = false; 152 153 TRACE("virtio_block: get_geometry(): %" B_PRIu32 ", %" B_PRIu32 ", %" B_PRIu32 ", %" B_PRIu32 154 ", %d, %d, %d, %d\n", geometry->bytes_per_sector, geometry->sectors_per_track, 155 geometry->cylinder_count, geometry->head_count, geometry->device_type, 156 geometry->removable, geometry->read_only, geometry->write_once); 157 158 return B_OK; 159 } 160 161 162 static void 163 virtio_block_config_callback(void* driverCookie) 164 { 165 virtio_block_driver_info* info = (virtio_block_driver_info*)driverCookie; 166 167 status_t status = info->virtio->read_device_config(info->virtio_device, 0, 168 &info->config, sizeof(struct virtio_blk_config)); 169 if (status != B_OK) 170 return; 171 172 if (virtio_block_set_capacity(info)) 173 info->media_status = B_DEV_MEDIA_CHANGED; 174 } 175 176 177 static void 178 virtio_block_callback(void* driverCookie, void* _cookie) 179 { 180 virtio_block_driver_info* info = (virtio_block_driver_info*)_cookie; 181 182 void* cookie = NULL; 183 while (info->virtio->queue_dequeue(info->virtio_queue, &cookie, NULL)) { 184 if ((int32)(addr_t)cookie == atomic_get(&info->currentRequest)) 185 info->interruptCondition.NotifyAll(); 186 } 187 } 188 189 190 static status_t 191 do_io(void* cookie, IOOperation* operation) 192 { 193 virtio_block_driver_info* info = (virtio_block_driver_info*)cookie; 194 195 if (mutex_trylock(&info->lock) != B_OK) 196 return B_BUSY; 197 198 BStackOrHeapArray<physical_entry, 16> entries(operation->VecCount() + 2); 199 200 struct virtio_blk_outhdr *header = (struct virtio_blk_outhdr*)info->bufferAddr; 201 header->type = operation->IsWrite() ? VIRTIO_BLK_T_OUT : VIRTIO_BLK_T_IN; 202 header->sector = operation->Offset() / 512; 203 header->ioprio = 1; 204 205 uint8* ack = (uint8*)info->bufferAddr + sizeof(struct virtio_blk_outhdr); 206 *ack = 0xff; 207 208 entries[0].address = info->bufferPhysAddr; 209 entries[0].size = sizeof(struct virtio_blk_outhdr); 210 entries[operation->VecCount() + 1].address = entries[0].address 211 + sizeof(struct virtio_blk_outhdr); 212 entries[operation->VecCount() + 1].size = sizeof(uint8); 213 214 memcpy(entries + 1, operation->Vecs(), operation->VecCount() 215 * sizeof(physical_entry)); 216 217 atomic_add(&info->currentRequest, 1); 218 info->interruptCondition.Add(&info->interruptConditionEntry); 219 220 info->virtio->queue_request_v(info->virtio_queue, entries, 221 1 + (operation->IsWrite() ? operation->VecCount() : 0 ), 222 1 + (operation->IsWrite() ? 0 : operation->VecCount()), 223 (void *)(addr_t)info->currentRequest); 224 225 status_t result = info->interruptConditionEntry.Wait(B_RELATIVE_TIMEOUT, 226 10 * 1000 * 1000); 227 228 size_t bytesTransferred = 0; 229 status_t status = B_OK; 230 if (result != B_OK) { 231 status = EIO; 232 } else { 233 switch (*ack) { 234 case VIRTIO_BLK_S_OK: 235 status = B_OK; 236 bytesTransferred = operation->Length(); 237 break; 238 case VIRTIO_BLK_S_UNSUPP: 239 status = ENOTSUP; 240 break; 241 default: 242 status = EIO; 243 break; 244 } 245 } 246 247 info->io_scheduler->OperationCompleted(operation, status, 248 bytesTransferred); 249 mutex_unlock(&info->lock); 250 return status; 251 } 252 253 254 // #pragma mark - device module API 255 256 257 static status_t 258 virtio_block_init_device(void* _info, void** _cookie) 259 { 260 CALLED(); 261 virtio_block_driver_info* info = (virtio_block_driver_info*)_info; 262 263 device_node* parent = sDeviceManager->get_parent_node(info->node); 264 sDeviceManager->get_driver(parent, (driver_module_info **)&info->virtio, 265 (void **)&info->virtio_device); 266 sDeviceManager->put_node(parent); 267 268 info->virtio->negotiate_features(info->virtio_device, 269 VIRTIO_BLK_F_BARRIER | VIRTIO_BLK_F_SIZE_MAX 270 | VIRTIO_BLK_F_SEG_MAX | VIRTIO_BLK_F_GEOMETRY 271 | VIRTIO_BLK_F_RO | VIRTIO_BLK_F_BLK_SIZE 272 | VIRTIO_BLK_F_FLUSH | VIRTIO_BLK_F_TOPOLOGY 273 | VIRTIO_FEATURE_RING_INDIRECT_DESC, 274 &info->features, &get_feature_name); 275 276 status_t status = info->virtio->read_device_config( 277 info->virtio_device, 0, &info->config, 278 sizeof(struct virtio_blk_config)); 279 if (status != B_OK) 280 return status; 281 282 virtio_block_set_capacity(info); 283 284 TRACE("virtio_block: capacity: %" B_PRIu64 ", block_size %" B_PRIu32 "\n", 285 info->capacity, info->block_size); 286 287 status = info->virtio->alloc_queues(info->virtio_device, 1, 288 &info->virtio_queue); 289 if (status != B_OK) { 290 ERROR("queue allocation failed (%s)\n", strerror(status)); 291 return status; 292 } 293 status = info->virtio->setup_interrupt(info->virtio_device, 294 virtio_block_config_callback, info); 295 296 if (status == B_OK) { 297 status = info->virtio->queue_setup_interrupt(info->virtio_queue, 298 virtio_block_callback, info); 299 } 300 301 *_cookie = info; 302 return status; 303 } 304 305 306 static void 307 virtio_block_uninit_device(void* _cookie) 308 { 309 CALLED(); 310 virtio_block_driver_info* info = (virtio_block_driver_info*)_cookie; 311 312 delete info->io_scheduler; 313 delete info->dma_resource; 314 } 315 316 317 static status_t 318 virtio_block_open(void* _info, const char* path, int openMode, void** _cookie) 319 { 320 CALLED(); 321 virtio_block_driver_info* info = (virtio_block_driver_info*)_info; 322 323 virtio_block_handle* handle = (virtio_block_handle*)malloc( 324 sizeof(virtio_block_handle)); 325 if (handle == NULL) 326 return B_NO_MEMORY; 327 328 handle->info = info; 329 330 *_cookie = handle; 331 return B_OK; 332 } 333 334 335 static status_t 336 virtio_block_close(void* cookie) 337 { 338 //virtio_block_handle* handle = (virtio_block_handle*)cookie; 339 CALLED(); 340 341 return B_OK; 342 } 343 344 345 static status_t 346 virtio_block_free(void* cookie) 347 { 348 CALLED(); 349 virtio_block_handle* handle = (virtio_block_handle*)cookie; 350 351 free(handle); 352 return B_OK; 353 } 354 355 356 static status_t 357 virtio_block_io(void *cookie, io_request *request) 358 { 359 CALLED(); 360 virtio_block_handle* handle = (virtio_block_handle*)cookie; 361 362 return handle->info->io_scheduler->ScheduleRequest(request); 363 } 364 365 366 static status_t 367 virtio_block_ioctl(void* cookie, uint32 op, void* buffer, size_t length) 368 { 369 CALLED(); 370 virtio_block_handle* handle = (virtio_block_handle*)cookie; 371 virtio_block_driver_info* info = handle->info; 372 373 TRACE("ioctl(op = %" B_PRIu32 ")\n", op); 374 375 switch (op) { 376 case B_GET_MEDIA_STATUS: 377 { 378 user_memcpy(buffer, &info->media_status, sizeof(info->media_status)); 379 TRACE("B_GET_MEDIA_STATUS: 0x%08" B_PRIx32 "\n", info->media_status); 380 info->media_status = B_OK; 381 return B_OK; 382 } 383 384 case B_GET_DEVICE_SIZE: 385 { 386 size_t size = info->capacity * info->block_size; 387 return user_memcpy(buffer, &size, sizeof(size_t)); 388 } 389 390 case B_GET_GEOMETRY: 391 { 392 if (buffer == NULL || length > sizeof(device_geometry)) 393 return B_BAD_VALUE; 394 395 device_geometry geometry; 396 status_t status = get_geometry(handle, &geometry); 397 if (status != B_OK) 398 return status; 399 400 return user_memcpy(buffer, &geometry, length); 401 } 402 403 case B_GET_ICON_NAME: 404 return user_strlcpy((char*)buffer, "devices/drive-harddisk", 405 B_FILE_NAME_LENGTH); 406 407 case B_GET_VECTOR_ICON: 408 { 409 // TODO: take device type into account! 410 device_icon iconData; 411 if (length != sizeof(device_icon)) 412 return B_BAD_VALUE; 413 if (user_memcpy(&iconData, buffer, sizeof(device_icon)) != B_OK) 414 return B_BAD_ADDRESS; 415 416 if (iconData.icon_size >= (int32)sizeof(kDriveIcon)) { 417 if (user_memcpy(iconData.icon_data, kDriveIcon, 418 sizeof(kDriveIcon)) != B_OK) 419 return B_BAD_ADDRESS; 420 } 421 422 iconData.icon_size = sizeof(kDriveIcon); 423 return user_memcpy(buffer, &iconData, sizeof(device_icon)); 424 } 425 426 /*case B_FLUSH_DRIVE_CACHE: 427 return synchronize_cache(info);*/ 428 } 429 430 return B_DEV_INVALID_IOCTL; 431 } 432 433 434 bool 435 virtio_block_set_capacity(virtio_block_driver_info* info) 436 { 437 // get capacity 438 uint32 blockSize = 512; 439 if ((info->features & VIRTIO_BLK_F_BLK_SIZE) != 0) 440 blockSize = info->config.blk_size; 441 uint64 capacity = info->config.capacity * 512 / blockSize; 442 uint32 physicalBlockSize = blockSize; 443 444 if ((info->features & VIRTIO_BLK_F_TOPOLOGY) != 0 445 && info->config.topology.physical_block_exp > 0) { 446 physicalBlockSize = blockSize * (1 << info->config.topology.physical_block_exp); 447 } 448 449 TRACE("set_capacity(device = %p, capacity = %" B_PRIu64 ", blockSize = %" B_PRIu32 ")\n", 450 info, capacity, blockSize); 451 452 if (info->block_size == blockSize && info->capacity == capacity) 453 return false; 454 455 info->capacity = capacity; 456 457 if (info->block_size != 0) { 458 ERROR("old %" B_PRId32 ", new %" B_PRId32 "\n", info->block_size, 459 blockSize); 460 panic("updating DMAResource not yet implemented..."); 461 } 462 463 dma_restrictions restrictions; 464 memset(&restrictions, 0, sizeof(restrictions)); 465 if ((info->features & VIRTIO_BLK_F_SIZE_MAX) != 0) 466 restrictions.max_segment_size = info->config.size_max; 467 if ((info->features & VIRTIO_BLK_F_SEG_MAX) != 0) 468 restrictions.max_segment_count = info->config.seg_max; 469 470 // TODO: we need to replace the DMAResource in our IOScheduler 471 status_t status = info->dma_resource->Init(restrictions, blockSize, 472 1024, 32); 473 if (status != B_OK) 474 panic("initializing DMAResource failed: %s", strerror(status)); 475 476 info->io_scheduler = new(std::nothrow) IOSchedulerSimple( 477 info->dma_resource); 478 if (info->io_scheduler == NULL) 479 panic("allocating IOScheduler failed."); 480 481 // TODO: use whole device name here 482 status = info->io_scheduler->Init("virtio"); 483 if (status != B_OK) 484 panic("initializing IOScheduler failed: %s", strerror(status)); 485 486 info->io_scheduler->SetCallback(do_io, info); 487 488 info->block_size = blockSize; 489 info->physical_block_size = physicalBlockSize; 490 return true; 491 } 492 493 494 // #pragma mark - driver module API 495 496 497 static float 498 virtio_block_supports_device(device_node *parent) 499 { 500 CALLED(); 501 const char *bus; 502 uint16 deviceType; 503 504 // make sure parent is really the Virtio bus manager 505 if (sDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false)) 506 return -1; 507 508 if (strcmp(bus, "virtio")) 509 return 0.0; 510 511 // check whether it's really a Direct Access Device 512 if (sDeviceManager->get_attr_uint16(parent, VIRTIO_DEVICE_TYPE_ITEM, 513 &deviceType, true) != B_OK || deviceType != VIRTIO_DEVICE_ID_BLOCK) 514 return 0.0; 515 516 TRACE("Virtio block device found!\n"); 517 518 return 0.6; 519 } 520 521 522 static status_t 523 virtio_block_register_device(device_node *node) 524 { 525 CALLED(); 526 527 device_attr attrs[] = { 528 { B_DEVICE_PRETTY_NAME, B_STRING_TYPE, {.string = "Virtio Block"} }, 529 { NULL } 530 }; 531 532 return sDeviceManager->register_node(node, VIRTIO_BLOCK_DRIVER_MODULE_NAME, 533 attrs, NULL, NULL); 534 } 535 536 537 static status_t 538 virtio_block_init_driver(device_node *node, void **cookie) 539 { 540 CALLED(); 541 542 virtio_block_driver_info* info = (virtio_block_driver_info*)malloc( 543 sizeof(virtio_block_driver_info)); 544 if (info == NULL) 545 return B_NO_MEMORY; 546 547 memset(info, 0, sizeof(*info)); 548 549 info->media_status = B_OK; 550 info->dma_resource = new(std::nothrow) DMAResource; 551 if (info->dma_resource == NULL) { 552 free(info); 553 return B_NO_MEMORY; 554 } 555 556 // create command buffer area 557 info->bufferArea = create_area("virtio_block command buffer", (void**)&info->bufferAddr, 558 B_ANY_KERNEL_BLOCK_ADDRESS, B_PAGE_SIZE, 559 B_FULL_LOCK, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA); 560 if (info->bufferArea < B_OK) { 561 status_t status = info->bufferArea; 562 delete info->dma_resource; 563 free(info); 564 return status; 565 } 566 567 physical_entry entry; 568 status_t status = get_memory_map((void*)info->bufferAddr, B_PAGE_SIZE, &entry, 1); 569 if (status != B_OK) { 570 delete_area(info->bufferArea); 571 delete info->dma_resource; 572 free(info); 573 return status; 574 } 575 576 info->bufferPhysAddr = entry.address; 577 info->interruptCondition.Init(info, "virtio block transfer"); 578 info->currentRequest = 0; 579 mutex_init(&info->lock, "virtio block request"); 580 581 info->node = node; 582 583 *cookie = info; 584 return B_OK; 585 } 586 587 588 static void 589 virtio_block_uninit_driver(void *_cookie) 590 { 591 CALLED(); 592 virtio_block_driver_info* info = (virtio_block_driver_info*)_cookie; 593 mutex_destroy(&info->lock); 594 delete_area(info->bufferArea); 595 free(info); 596 } 597 598 599 static status_t 600 virtio_block_register_child_devices(void* _cookie) 601 { 602 CALLED(); 603 virtio_block_driver_info* info = (virtio_block_driver_info*)_cookie; 604 status_t status; 605 606 int32 id = sDeviceManager->create_id(VIRTIO_BLOCK_DEVICE_ID_GENERATOR); 607 if (id < 0) 608 return id; 609 610 char name[64]; 611 snprintf(name, sizeof(name), "disk/virtual/virtio_block/%" B_PRId32 "/raw", 612 id); 613 614 status = sDeviceManager->publish_device(info->node, name, 615 VIRTIO_BLOCK_DEVICE_MODULE_NAME); 616 617 return status; 618 } 619 620 621 // #pragma mark - 622 623 624 module_dependency module_dependencies[] = { 625 { B_DEVICE_MANAGER_MODULE_NAME, (module_info**)&sDeviceManager }, 626 { NULL } 627 }; 628 629 struct device_module_info sVirtioBlockDevice = { 630 { 631 VIRTIO_BLOCK_DEVICE_MODULE_NAME, 632 0, 633 NULL 634 }, 635 636 virtio_block_init_device, 637 virtio_block_uninit_device, 638 NULL, // remove, 639 640 virtio_block_open, 641 virtio_block_close, 642 virtio_block_free, 643 NULL, // read 644 NULL, // write 645 virtio_block_io, 646 virtio_block_ioctl, 647 648 NULL, // select 649 NULL, // deselect 650 }; 651 652 struct driver_module_info sVirtioBlockDriver = { 653 { 654 VIRTIO_BLOCK_DRIVER_MODULE_NAME, 655 0, 656 NULL 657 }, 658 659 virtio_block_supports_device, 660 virtio_block_register_device, 661 virtio_block_init_driver, 662 virtio_block_uninit_driver, 663 virtio_block_register_child_devices, 664 NULL, // rescan 665 NULL, // removed 666 }; 667 668 module_info* modules[] = { 669 (module_info*)&sVirtioBlockDriver, 670 (module_info*)&sVirtioBlockDevice, 671 NULL 672 }; 673