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