1 /* 2 * Copyright 2021 David Sebek, dasebek@gmail.com 3 * Copyright 2008-2013 Axel Dörfler, axeld@pinc-software.de 4 * Copyright 2002/03 Thomas Kurschel 5 * All rights reserved. Distributed under the terms of the MIT License. 6 */ 7 8 9 /*! Peripheral driver to handle any kind of SCSI disks, 10 i.e. hard disk and floopy disks (ZIP etc.) 11 12 Much work is done by scsi_periph and block_io. 13 14 You'll find das_... all over the place. This stands for 15 "Direct Access Storage" which is the official SCSI name for 16 normal (floppy/hard/ZIP)-disk drives. 17 */ 18 19 20 #include "scsi_disk.h" 21 22 #include <string.h> 23 #include <stdlib.h> 24 25 #include <AutoDeleter.h> 26 27 #include <fs/devfs.h> 28 #include <util/fs_trim_support.h> 29 30 #include "dma_resources.h" 31 #include "IORequest.h" 32 #include "IOSchedulerSimple.h" 33 34 35 //#define TRACE_SCSI_DISK 36 #ifdef TRACE_SCSI_DISK 37 # define TRACE(x...) dprintf("scsi_disk: " x) 38 #else 39 # define TRACE(x...) ; 40 #endif 41 42 43 static const uint8 kDriveIcon[] = { 44 0x6e, 0x63, 0x69, 0x66, 0x08, 0x03, 0x01, 0x00, 0x00, 0x02, 0x00, 0x16, 45 0x02, 0x3c, 0xc7, 0xee, 0x38, 0x9b, 0xc0, 0xba, 0x16, 0x57, 0x3e, 0x39, 46 0xb0, 0x49, 0x77, 0xc8, 0x42, 0xad, 0xc7, 0x00, 0xff, 0xff, 0xd3, 0x02, 47 0x00, 0x06, 0x02, 0x3c, 0x96, 0x32, 0x3a, 0x4d, 0x3f, 0xba, 0xfc, 0x01, 48 0x3d, 0x5a, 0x97, 0x4b, 0x57, 0xa5, 0x49, 0x84, 0x4d, 0x00, 0x47, 0x47, 49 0x47, 0xff, 0xa5, 0xa0, 0xa0, 0x02, 0x00, 0x16, 0x02, 0xbc, 0x59, 0x2f, 50 0xbb, 0x29, 0xa7, 0x3c, 0x0c, 0xe4, 0xbd, 0x0b, 0x7c, 0x48, 0x92, 0xc0, 51 0x4b, 0x79, 0x66, 0x00, 0x7d, 0xff, 0xd4, 0x02, 0x00, 0x06, 0x02, 0x38, 52 0xdb, 0xb4, 0x39, 0x97, 0x33, 0xbc, 0x4a, 0x33, 0x3b, 0xa5, 0x42, 0x48, 53 0x6e, 0x66, 0x49, 0xee, 0x7b, 0x00, 0x59, 0x67, 0x56, 0xff, 0xeb, 0xb2, 54 0xb2, 0x03, 0xa7, 0xff, 0x00, 0x03, 0xff, 0x00, 0x00, 0x04, 0x01, 0x80, 55 0x07, 0x0a, 0x06, 0x22, 0x3c, 0x22, 0x49, 0x44, 0x5b, 0x5a, 0x3e, 0x5a, 56 0x31, 0x39, 0x25, 0x0a, 0x04, 0x22, 0x3c, 0x44, 0x4b, 0x5a, 0x31, 0x39, 57 0x25, 0x0a, 0x04, 0x44, 0x4b, 0x44, 0x5b, 0x5a, 0x3e, 0x5a, 0x31, 0x0a, 58 0x04, 0x22, 0x3c, 0x22, 0x49, 0x44, 0x5b, 0x44, 0x4b, 0x08, 0x02, 0x27, 59 0x43, 0xb8, 0x14, 0xc1, 0xf1, 0x08, 0x02, 0x26, 0x43, 0x29, 0x44, 0x0a, 60 0x05, 0x44, 0x5d, 0x49, 0x5d, 0x60, 0x3e, 0x5a, 0x3b, 0x5b, 0x3f, 0x08, 61 0x0a, 0x07, 0x01, 0x06, 0x00, 0x0a, 0x00, 0x01, 0x00, 0x10, 0x01, 0x17, 62 0x84, 0x00, 0x04, 0x0a, 0x01, 0x01, 0x01, 0x00, 0x0a, 0x02, 0x01, 0x02, 63 0x00, 0x0a, 0x03, 0x01, 0x03, 0x00, 0x0a, 0x04, 0x01, 0x04, 0x10, 0x01, 64 0x17, 0x85, 0x20, 0x04, 0x0a, 0x06, 0x01, 0x05, 0x30, 0x24, 0xb3, 0x99, 65 0x01, 0x17, 0x82, 0x00, 0x04, 0x0a, 0x05, 0x01, 0x05, 0x30, 0x20, 0xb2, 66 0xe6, 0x01, 0x17, 0x82, 0x00, 0x04 67 }; 68 69 70 static scsi_periph_interface* sSCSIPeripheral; 71 static device_manager_info* sDeviceManager; 72 73 74 static status_t 75 update_capacity(das_driver_info* device) 76 { 77 TRACE("update_capacity()\n"); 78 79 scsi_ccb *ccb = device->scsi->alloc_ccb(device->scsi_device); 80 if (ccb == NULL) 81 return B_NO_MEMORY; 82 83 status_t status = sSCSIPeripheral->check_capacity( 84 device->scsi_periph_device, ccb); 85 86 device->scsi->free_ccb(ccb); 87 88 return status; 89 } 90 91 92 static status_t 93 get_geometry(das_handle* handle, device_geometry* geometry) 94 { 95 das_driver_info* info = handle->info; 96 97 status_t status = update_capacity(info); 98 if (status != B_OK) 99 return status; 100 101 devfs_compute_geometry_size(geometry, info->capacity, info->block_size); 102 103 geometry->device_type = B_DISK; 104 geometry->removable = info->removable; 105 106 // TBD: for all but CD-ROMs, read mode sense - medium type 107 // (bit 7 of block device specific parameter for Optical Memory Block Device) 108 // (same for Direct-Access Block Devices) 109 // (same for write-once block devices) 110 // (same for optical memory block devices) 111 geometry->read_only = false; 112 geometry->write_once = false; 113 114 TRACE("scsi_disk: get_geometry(): %" B_PRId32 ", %" B_PRId32 ", %" B_PRId32 115 ", %" B_PRId32 ", %d, %d, %d, %d\n", geometry->bytes_per_sector, 116 geometry->sectors_per_track, geometry->cylinder_count, 117 geometry->head_count, geometry->device_type, 118 geometry->removable, geometry->read_only, geometry->write_once); 119 120 return B_OK; 121 } 122 123 124 static status_t 125 load_eject(das_driver_info *device, bool load) 126 { 127 TRACE("load_eject()\n"); 128 129 scsi_ccb *ccb = device->scsi->alloc_ccb(device->scsi_device); 130 if (ccb == NULL) 131 return B_NO_MEMORY; 132 133 err_res result = sSCSIPeripheral->send_start_stop( 134 device->scsi_periph_device, ccb, load, true); 135 136 device->scsi->free_ccb(ccb); 137 138 return result.error_code; 139 } 140 141 142 static status_t 143 synchronize_cache(das_driver_info *device) 144 { 145 TRACE("synchronize_cache()\n"); 146 147 scsi_ccb *ccb = device->scsi->alloc_ccb(device->scsi_device); 148 if (ccb == NULL) 149 return B_NO_MEMORY; 150 151 err_res result = sSCSIPeripheral->synchronize_cache( 152 device->scsi_periph_device, ccb); 153 154 device->scsi->free_ccb(ccb); 155 156 return result.error_code; 157 } 158 159 160 static status_t 161 trim_device(das_driver_info* device, fs_trim_data* trimData) 162 { 163 TRACE("trim_device()\n"); 164 165 trimData->trimmed_size = 0; 166 167 scsi_ccb* request = device->scsi->alloc_ccb(device->scsi_device); 168 if (request == NULL) 169 return B_NO_MEMORY; 170 171 scsi_block_range* blockRanges = (scsi_block_range*) 172 malloc(trimData->range_count * sizeof(*blockRanges)); 173 if (blockRanges == NULL) 174 return B_NO_MEMORY; 175 176 MemoryDeleter deleter(blockRanges); 177 178 for (uint32 i = 0; i < trimData->range_count; i++) { 179 uint64 startBytes = trimData->ranges[i].offset; 180 uint64 sizeBytes = trimData->ranges[i].size; 181 uint32 blockSize = device->block_size; 182 183 // Align to a block boundary so we don't discard blocks 184 // that could also contain some other data 185 uint64 blockOffset = startBytes % blockSize; 186 if (blockOffset == 0) { 187 blockRanges[i].lba = startBytes / blockSize; 188 blockRanges[i].size = sizeBytes / blockSize; 189 } else { 190 blockRanges[i].lba = startBytes / blockSize + 1; 191 blockRanges[i].size = (sizeBytes - (blockSize - blockOffset)) 192 / blockSize; 193 } 194 } 195 196 // Check ranges against device capacity and make them fit 197 for (uint32 i = 0; i < trimData->range_count; i++) { 198 if (blockRanges[i].lba >= device->capacity) { 199 dprintf("trim_device(): range offset (LBA) %" B_PRIu64 200 " exceeds device capacity %" B_PRIu64 "\n", 201 blockRanges[i].lba, device->capacity); 202 return B_BAD_VALUE; 203 } 204 uint64 maxSize = device->capacity - blockRanges[i].lba; 205 blockRanges[i].size = min_c(blockRanges[i].size, maxSize); 206 } 207 208 uint64 trimmedBlocks; 209 status_t status = sSCSIPeripheral->trim_device(device->scsi_periph_device, 210 request, blockRanges, trimData->range_count, &trimmedBlocks); 211 212 device->scsi->free_ccb(request); 213 // Some blocks may have been trimmed even if trim_device returns a failure 214 trimData->trimmed_size = trimmedBlocks * device->block_size; 215 216 return status; 217 } 218 219 220 static int 221 log2(uint32 x) 222 { 223 int y; 224 225 for (y = 31; y >= 0; --y) { 226 if (x == ((uint32)1 << y)) 227 break; 228 } 229 230 return y; 231 } 232 233 234 static status_t 235 do_io(void* cookie, IOOperation* operation) 236 { 237 das_driver_info* info = (das_driver_info*)cookie; 238 239 // TODO: this can go away as soon as we pushed the IOOperation to the upper 240 // layers - we can then set scsi_periph::io() as callback for the scheduler 241 size_t bytesTransferred; 242 status_t status = sSCSIPeripheral->io(info->scsi_periph_device, operation, 243 &bytesTransferred); 244 245 info->io_scheduler->OperationCompleted(operation, status, bytesTransferred); 246 return status; 247 } 248 249 250 // #pragma mark - device module API 251 252 253 static status_t 254 das_init_device(void* _info, void** _cookie) 255 { 256 das_driver_info* info = (das_driver_info*)_info; 257 258 // and get (initial) capacity 259 scsi_ccb *request = info->scsi->alloc_ccb(info->scsi_device); 260 if (request == NULL) 261 return B_NO_MEMORY; 262 263 sSCSIPeripheral->check_capacity(info->scsi_periph_device, request); 264 info->scsi->free_ccb(request); 265 266 *_cookie = info; 267 return B_OK; 268 } 269 270 271 static void 272 das_uninit_device(void* _cookie) 273 { 274 das_driver_info* info = (das_driver_info*)_cookie; 275 276 delete info->io_scheduler; 277 } 278 279 280 static status_t 281 das_open(void* _info, const char* path, int openMode, void** _cookie) 282 { 283 das_driver_info* info = (das_driver_info*)_info; 284 285 das_handle* handle = (das_handle*)malloc(sizeof(das_handle)); 286 if (handle == NULL) 287 return B_NO_MEMORY; 288 289 handle->info = info; 290 291 status_t status = sSCSIPeripheral->handle_open(info->scsi_periph_device, 292 (periph_handle_cookie)handle, &handle->scsi_periph_handle); 293 if (status < B_OK) { 294 free(handle); 295 return status; 296 } 297 298 *_cookie = handle; 299 return B_OK; 300 } 301 302 303 static status_t 304 das_close(void* cookie) 305 { 306 das_handle* handle = (das_handle*)cookie; 307 TRACE("close()\n"); 308 309 sSCSIPeripheral->handle_close(handle->scsi_periph_handle); 310 return B_OK; 311 } 312 313 314 static status_t 315 das_free(void* cookie) 316 { 317 das_handle* handle = (das_handle*)cookie; 318 TRACE("free()\n"); 319 320 sSCSIPeripheral->handle_free(handle->scsi_periph_handle); 321 free(handle); 322 return B_OK; 323 } 324 325 326 static status_t 327 das_read(void* cookie, off_t pos, void* buffer, size_t* _length) 328 { 329 das_handle* handle = (das_handle*)cookie; 330 size_t length = *_length; 331 332 IORequest request; 333 status_t status = request.Init(pos, (addr_t)buffer, length, false, 0); 334 if (status != B_OK) 335 return status; 336 337 status = handle->info->io_scheduler->ScheduleRequest(&request); 338 if (status != B_OK) 339 return status; 340 341 status = request.Wait(0, 0); 342 if (status == B_OK) 343 *_length = length; 344 else 345 dprintf("das_read(): request.Wait() returned: %s\n", strerror(status)); 346 347 return status; 348 } 349 350 351 static status_t 352 das_write(void* cookie, off_t pos, const void* buffer, size_t* _length) 353 { 354 das_handle* handle = (das_handle*)cookie; 355 size_t length = *_length; 356 357 IORequest request; 358 status_t status = request.Init(pos, (addr_t)buffer, length, true, 0); 359 if (status != B_OK) 360 return status; 361 362 status = handle->info->io_scheduler->ScheduleRequest(&request); 363 if (status != B_OK) 364 return status; 365 366 status = request.Wait(0, 0); 367 if (status == B_OK) 368 *_length = length; 369 else 370 dprintf("das_write(): request.Wait() returned: %s\n", strerror(status)); 371 372 return status; 373 } 374 375 376 static status_t 377 das_io(void *cookie, io_request *request) 378 { 379 das_handle* handle = (das_handle*)cookie; 380 381 return handle->info->io_scheduler->ScheduleRequest(request); 382 } 383 384 385 static status_t 386 das_ioctl(void* cookie, uint32 op, void* buffer, size_t length) 387 { 388 das_handle* handle = (das_handle*)cookie; 389 das_driver_info* info = handle->info; 390 391 TRACE("ioctl(op = %" B_PRIu32 ")\n", op); 392 393 switch (op) { 394 case B_GET_DEVICE_SIZE: 395 { 396 status_t status = update_capacity(info); 397 if (status != B_OK) 398 return status; 399 400 size_t size = info->capacity * info->block_size; 401 return user_memcpy(buffer, &size, sizeof(size_t)); 402 } 403 404 case B_GET_GEOMETRY: 405 { 406 if (buffer == NULL /*|| length != sizeof(device_geometry)*/) 407 return B_BAD_VALUE; 408 409 device_geometry geometry; 410 status_t status = get_geometry(handle, &geometry); 411 if (status != B_OK) 412 return status; 413 414 return user_memcpy(buffer, &geometry, sizeof(device_geometry)); 415 } 416 417 case B_GET_ICON_NAME: 418 // TODO: take device type into account! 419 return user_strlcpy((char*)buffer, info->removable 420 ? "devices/drive-removable-media" : "devices/drive-harddisk", 421 B_FILE_NAME_LENGTH); 422 423 case B_GET_VECTOR_ICON: 424 { 425 // TODO: take device type into account! 426 device_icon iconData; 427 if (length != sizeof(device_icon)) 428 return B_BAD_VALUE; 429 if (user_memcpy(&iconData, buffer, sizeof(device_icon)) != B_OK) 430 return B_BAD_ADDRESS; 431 432 if (iconData.icon_size >= (int32)sizeof(kDriveIcon)) { 433 if (user_memcpy(iconData.icon_data, kDriveIcon, 434 sizeof(kDriveIcon)) != B_OK) 435 return B_BAD_ADDRESS; 436 } 437 438 iconData.icon_size = sizeof(kDriveIcon); 439 return user_memcpy(buffer, &iconData, sizeof(device_icon)); 440 } 441 442 case B_EJECT_DEVICE: 443 case B_SCSI_EJECT: 444 return load_eject(info, false); 445 446 case B_LOAD_MEDIA: 447 return load_eject(info, true); 448 449 case B_FLUSH_DRIVE_CACHE: 450 return synchronize_cache(info); 451 452 case B_TRIM_DEVICE: 453 { 454 // We know the buffer is kernel-side because it has been 455 // preprocessed in devfs 456 ASSERT(IS_KERNEL_ADDRESS(buffer)); 457 return trim_device(info, (fs_trim_data*)buffer); 458 } 459 460 default: 461 return sSCSIPeripheral->ioctl(handle->scsi_periph_handle, op, 462 buffer, length); 463 } 464 } 465 466 467 // #pragma mark - scsi_periph callbacks 468 469 470 static void 471 das_set_capacity(das_driver_info* info, uint64 capacity, uint32 blockSize) 472 { 473 TRACE("das_set_capacity(device = %p, capacity = %" B_PRIu64 474 ", blockSize = %" B_PRIu32 ")\n", info, capacity, blockSize); 475 476 // get log2, if possible 477 uint32 blockShift = log2(blockSize); 478 479 if ((1UL << blockShift) != blockSize) 480 blockShift = 0; 481 482 info->capacity = capacity; 483 484 if (info->block_size != blockSize) { 485 if (info->block_size != 0) { 486 dprintf("old %" B_PRId32 ", new %" B_PRId32 "\n", info->block_size, 487 blockSize); 488 panic("updating DMAResource not yet implemented..."); 489 } 490 491 // TODO: we need to replace the DMAResource in our IOScheduler 492 status_t status = info->dma_resource->Init(info->node, blockSize, 1024, 493 32); 494 if (status != B_OK) 495 panic("initializing DMAResource failed: %s", strerror(status)); 496 497 info->io_scheduler = new(std::nothrow) IOSchedulerSimple( 498 info->dma_resource); 499 if (info->io_scheduler == NULL) 500 panic("allocating IOScheduler failed."); 501 502 // TODO: use whole device name here 503 status = info->io_scheduler->Init("scsi"); 504 if (status != B_OK) 505 panic("initializing IOScheduler failed: %s", strerror(status)); 506 507 info->io_scheduler->SetCallback(do_io, info); 508 } 509 510 info->block_size = blockSize; 511 } 512 513 514 static void 515 das_media_changed(das_driver_info *device, scsi_ccb *request) 516 { 517 // do a capacity check 518 // TODO: is this a good idea (e.g. if this is an empty CD)? 519 sSCSIPeripheral->check_capacity(device->scsi_periph_device, request); 520 } 521 522 523 scsi_periph_callbacks callbacks = { 524 (void (*)(periph_device_cookie, uint64, uint32))das_set_capacity, 525 (void (*)(periph_device_cookie, scsi_ccb *))das_media_changed 526 }; 527 528 529 // #pragma mark - driver module API 530 531 532 static float 533 das_supports_device(device_node *parent) 534 { 535 const char *bus; 536 uint8 deviceType; 537 538 // make sure parent is really the SCSI bus manager 539 if (sDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false)) 540 return -1; 541 542 if (strcmp(bus, "scsi")) 543 return 0.0; 544 545 // check whether it's really a Direct Access Device 546 if (sDeviceManager->get_attr_uint8(parent, SCSI_DEVICE_TYPE_ITEM, 547 &deviceType, true) != B_OK || deviceType != scsi_dev_direct_access) 548 return 0.0; 549 550 return 0.6; 551 } 552 553 554 /*! Called whenever a new device was added to system; 555 if we really support it, we create a new node that gets 556 server by the block_io module 557 */ 558 static status_t 559 das_register_device(device_node *node) 560 { 561 const scsi_res_inquiry *deviceInquiry = NULL; 562 size_t inquiryLength; 563 uint32 maxBlocks; 564 565 // get inquiry data 566 if (sDeviceManager->get_attr_raw(node, SCSI_DEVICE_INQUIRY_ITEM, 567 (const void **)&deviceInquiry, &inquiryLength, true) != B_OK 568 || inquiryLength < sizeof(scsi_res_inquiry)) 569 return B_ERROR; 570 571 // get block limit of underlying hardware to lower it (if necessary) 572 if (sDeviceManager->get_attr_uint32(node, B_DMA_MAX_TRANSFER_BLOCKS, 573 &maxBlocks, true) != B_OK) 574 maxBlocks = INT_MAX; 575 576 // using 10 byte commands, at most 0xffff blocks can be transmitted at once 577 // (sadly, we cannot update this value later on if only 6 byte commands 578 // are supported, but the block_io module can live with that) 579 maxBlocks = min_c(maxBlocks, 0xffff); 580 581 // ready to register 582 device_attr attrs[] = { 583 { B_DEVICE_PRETTY_NAME, B_STRING_TYPE, { string: "SCSI Disk" }}, 584 // tell block_io whether the device is removable 585 {"removable", B_UINT8_TYPE, {ui8: deviceInquiry->removable_medium}}, 586 // impose own max block restriction 587 {B_DMA_MAX_TRANSFER_BLOCKS, B_UINT32_TYPE, {ui32: maxBlocks}}, 588 { NULL } 589 }; 590 591 return sDeviceManager->register_node(node, SCSI_DISK_DRIVER_MODULE_NAME, 592 attrs, NULL, NULL); 593 } 594 595 596 static status_t 597 das_init_driver(device_node *node, void **cookie) 598 { 599 TRACE("das_init_driver"); 600 601 uint8 removable; 602 status_t status = sDeviceManager->get_attr_uint8(node, "removable", 603 &removable, false); 604 if (status != B_OK) 605 return status; 606 607 das_driver_info* info = (das_driver_info*)malloc(sizeof(das_driver_info)); 608 if (info == NULL) 609 return B_NO_MEMORY; 610 611 memset(info, 0, sizeof(*info)); 612 613 info->dma_resource = new(std::nothrow) DMAResource; 614 if (info->dma_resource == NULL) { 615 free(info); 616 return B_NO_MEMORY; 617 } 618 619 info->node = node; 620 info->removable = removable; 621 622 device_node* parent = sDeviceManager->get_parent_node(node); 623 sDeviceManager->get_driver(parent, (driver_module_info **)&info->scsi, 624 (void **)&info->scsi_device); 625 sDeviceManager->put_node(parent); 626 627 status = sSCSIPeripheral->register_device((periph_device_cookie)info, 628 &callbacks, info->scsi_device, info->scsi, info->node, 629 info->removable, 10, &info->scsi_periph_device); 630 if (status != B_OK) { 631 delete info->dma_resource; 632 free(info); 633 return status; 634 } 635 636 *cookie = info; 637 return B_OK; 638 } 639 640 641 static void 642 das_uninit_driver(void *_cookie) 643 { 644 das_driver_info* info = (das_driver_info*)_cookie; 645 646 sSCSIPeripheral->unregister_device(info->scsi_periph_device); 647 delete info->dma_resource; 648 free(info); 649 } 650 651 652 static status_t 653 das_register_child_devices(void* _cookie) 654 { 655 das_driver_info* info = (das_driver_info*)_cookie; 656 status_t status; 657 658 char* name = sSCSIPeripheral->compose_device_name(info->node, 659 "disk/scsi"); 660 if (name == NULL) 661 return B_ERROR; 662 663 status = sDeviceManager->publish_device(info->node, name, 664 SCSI_DISK_DEVICE_MODULE_NAME); 665 666 free(name); 667 return status; 668 } 669 670 671 static status_t 672 das_rescan_child_devices(void* _cookie) 673 { 674 das_driver_info* info = (das_driver_info*)_cookie; 675 uint64 capacity = info->capacity; 676 update_capacity(info); 677 if (info->capacity != capacity) 678 sSCSIPeripheral->media_changed(info->scsi_periph_device); 679 return B_OK; 680 } 681 682 683 684 module_dependency module_dependencies[] = { 685 {SCSI_PERIPH_MODULE_NAME, (module_info**)&sSCSIPeripheral}, 686 {B_DEVICE_MANAGER_MODULE_NAME, (module_info**)&sDeviceManager}, 687 {} 688 }; 689 690 struct device_module_info sSCSIDiskDevice = { 691 { 692 SCSI_DISK_DEVICE_MODULE_NAME, 693 0, 694 NULL 695 }, 696 697 das_init_device, 698 das_uninit_device, 699 NULL, //das_remove, 700 701 das_open, 702 das_close, 703 das_free, 704 das_read, 705 das_write, 706 das_io, 707 das_ioctl, 708 709 NULL, // select 710 NULL, // deselect 711 }; 712 713 struct driver_module_info sSCSIDiskDriver = { 714 { 715 SCSI_DISK_DRIVER_MODULE_NAME, 716 0, 717 NULL 718 }, 719 720 das_supports_device, 721 das_register_device, 722 das_init_driver, 723 das_uninit_driver, 724 das_register_child_devices, 725 das_rescan_child_devices, 726 NULL, // removed 727 }; 728 729 module_info* modules[] = { 730 (module_info*)&sSCSIDiskDriver, 731 (module_info*)&sSCSIDiskDevice, 732 NULL 733 }; 734