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 geometry->bytes_per_physical_sector = info->physical_block_size; 103 104 geometry->device_type = B_DISK; 105 geometry->removable = info->removable; 106 107 // TBD: for all but CD-ROMs, read mode sense - medium type 108 // (bit 7 of block device specific parameter for Optical Memory Block Device) 109 // (same for Direct-Access Block Devices) 110 // (same for write-once block devices) 111 // (same for optical memory block devices) 112 geometry->read_only = false; 113 geometry->write_once = false; 114 115 TRACE("scsi_disk: get_geometry(): %" B_PRId32 ", %" B_PRId32 ", %" B_PRId32 116 ", %" B_PRId32 ", %d, %d, %d, %d, %" B_PRId32 "\n", geometry->bytes_per_sector, 117 geometry->sectors_per_track, geometry->cylinder_count, 118 geometry->head_count, geometry->device_type, 119 geometry->removable, geometry->read_only, geometry->write_once, 120 geometry->bytes_per_physical_sector); 121 122 return B_OK; 123 } 124 125 126 static status_t 127 load_eject(das_driver_info *device, bool load) 128 { 129 TRACE("load_eject()\n"); 130 131 scsi_ccb *ccb = device->scsi->alloc_ccb(device->scsi_device); 132 if (ccb == NULL) 133 return B_NO_MEMORY; 134 135 err_res result = sSCSIPeripheral->send_start_stop( 136 device->scsi_periph_device, ccb, load, true); 137 138 device->scsi->free_ccb(ccb); 139 140 return result.error_code; 141 } 142 143 144 static status_t 145 synchronize_cache(das_driver_info *device) 146 { 147 TRACE("synchronize_cache()\n"); 148 149 scsi_ccb *ccb = device->scsi->alloc_ccb(device->scsi_device); 150 if (ccb == NULL) 151 return B_NO_MEMORY; 152 153 err_res result = sSCSIPeripheral->synchronize_cache( 154 device->scsi_periph_device, ccb); 155 156 device->scsi->free_ccb(ccb); 157 158 return result.error_code; 159 } 160 161 162 static status_t 163 trim_device(das_driver_info* device, fs_trim_data* trimData) 164 { 165 TRACE("trim_device()\n"); 166 167 trimData->trimmed_size = 0; 168 169 scsi_ccb* request = device->scsi->alloc_ccb(device->scsi_device); 170 if (request == NULL) 171 return B_NO_MEMORY; 172 173 scsi_block_range* blockRanges = (scsi_block_range*) 174 malloc(trimData->range_count * sizeof(*blockRanges)); 175 if (blockRanges == NULL) 176 return B_NO_MEMORY; 177 178 MemoryDeleter deleter(blockRanges); 179 180 for (uint32 i = 0; i < trimData->range_count; i++) { 181 uint64 startBytes = trimData->ranges[i].offset; 182 uint64 sizeBytes = trimData->ranges[i].size; 183 uint32 blockSize = device->block_size; 184 185 // Align to a block boundary so we don't discard blocks 186 // that could also contain some other data 187 uint64 blockOffset = startBytes % blockSize; 188 if (blockOffset == 0) { 189 blockRanges[i].lba = startBytes / blockSize; 190 blockRanges[i].size = sizeBytes / blockSize; 191 } else { 192 blockRanges[i].lba = startBytes / blockSize + 1; 193 blockRanges[i].size = (sizeBytes - (blockSize - blockOffset)) 194 / blockSize; 195 } 196 } 197 198 // Check ranges against device capacity and make them fit 199 for (uint32 i = 0; i < trimData->range_count; i++) { 200 if (blockRanges[i].lba >= device->capacity) { 201 dprintf("trim_device(): range offset (LBA) %" B_PRIu64 202 " exceeds device capacity %" B_PRIu64 "\n", 203 blockRanges[i].lba, device->capacity); 204 return B_BAD_VALUE; 205 } 206 uint64 maxSize = device->capacity - blockRanges[i].lba; 207 blockRanges[i].size = min_c(blockRanges[i].size, maxSize); 208 } 209 210 uint64 trimmedBlocks; 211 status_t status = sSCSIPeripheral->trim_device(device->scsi_periph_device, 212 request, blockRanges, trimData->range_count, &trimmedBlocks); 213 214 device->scsi->free_ccb(request); 215 // Some blocks may have been trimmed even if trim_device returns a failure 216 trimData->trimmed_size = trimmedBlocks * device->block_size; 217 218 return status; 219 } 220 221 222 static int 223 log2(uint32 x) 224 { 225 int y; 226 227 for (y = 31; y >= 0; --y) { 228 if (x == ((uint32)1 << y)) 229 break; 230 } 231 232 return y; 233 } 234 235 236 static status_t 237 do_io(void* cookie, IOOperation* operation) 238 { 239 das_driver_info* info = (das_driver_info*)cookie; 240 241 // TODO: this can go away as soon as we pushed the IOOperation to the upper 242 // layers - we can then set scsi_periph::io() as callback for the scheduler 243 size_t bytesTransferred; 244 status_t status = sSCSIPeripheral->io(info->scsi_periph_device, operation, 245 &bytesTransferred); 246 247 info->io_scheduler->OperationCompleted(operation, status, bytesTransferred); 248 return status; 249 } 250 251 252 // #pragma mark - device module API 253 254 255 static status_t 256 das_init_device(void* _info, void** _cookie) 257 { 258 das_driver_info* info = (das_driver_info*)_info; 259 260 // and get (initial) capacity 261 scsi_ccb *request = info->scsi->alloc_ccb(info->scsi_device); 262 if (request == NULL) 263 return B_NO_MEMORY; 264 265 sSCSIPeripheral->check_capacity(info->scsi_periph_device, request); 266 info->scsi->free_ccb(request); 267 268 *_cookie = info; 269 return B_OK; 270 } 271 272 273 static void 274 das_uninit_device(void* _cookie) 275 { 276 das_driver_info* info = (das_driver_info*)_cookie; 277 278 delete info->io_scheduler; 279 } 280 281 282 static status_t 283 das_open(void* _info, const char* path, int openMode, void** _cookie) 284 { 285 das_driver_info* info = (das_driver_info*)_info; 286 287 das_handle* handle = (das_handle*)malloc(sizeof(das_handle)); 288 if (handle == NULL) 289 return B_NO_MEMORY; 290 291 handle->info = info; 292 293 status_t status = sSCSIPeripheral->handle_open(info->scsi_periph_device, 294 (periph_handle_cookie)handle, &handle->scsi_periph_handle); 295 if (status < B_OK) { 296 free(handle); 297 return status; 298 } 299 300 *_cookie = handle; 301 return B_OK; 302 } 303 304 305 static status_t 306 das_close(void* cookie) 307 { 308 das_handle* handle = (das_handle*)cookie; 309 TRACE("close()\n"); 310 311 sSCSIPeripheral->handle_close(handle->scsi_periph_handle); 312 return B_OK; 313 } 314 315 316 static status_t 317 das_free(void* cookie) 318 { 319 das_handle* handle = (das_handle*)cookie; 320 TRACE("free()\n"); 321 322 sSCSIPeripheral->handle_free(handle->scsi_periph_handle); 323 free(handle); 324 return B_OK; 325 } 326 327 328 static status_t 329 das_read(void* cookie, off_t pos, void* buffer, size_t* _length) 330 { 331 das_handle* handle = (das_handle*)cookie; 332 size_t length = *_length; 333 334 IORequest request; 335 status_t status = request.Init(pos, (addr_t)buffer, length, false, 0); 336 if (status != B_OK) 337 return status; 338 339 status = handle->info->io_scheduler->ScheduleRequest(&request); 340 if (status != B_OK) 341 return status; 342 343 status = request.Wait(0, 0); 344 if (status == B_OK) 345 *_length = length; 346 else 347 dprintf("das_read(): request.Wait() returned: %s\n", strerror(status)); 348 349 return status; 350 } 351 352 353 static status_t 354 das_write(void* cookie, off_t pos, const void* buffer, size_t* _length) 355 { 356 das_handle* handle = (das_handle*)cookie; 357 size_t length = *_length; 358 359 IORequest request; 360 status_t status = request.Init(pos, (addr_t)buffer, length, true, 0); 361 if (status != B_OK) 362 return status; 363 364 status = handle->info->io_scheduler->ScheduleRequest(&request); 365 if (status != B_OK) 366 return status; 367 368 status = request.Wait(0, 0); 369 if (status == B_OK) 370 *_length = length; 371 else 372 dprintf("das_write(): request.Wait() returned: %s\n", strerror(status)); 373 374 return status; 375 } 376 377 378 static status_t 379 das_io(void *cookie, io_request *request) 380 { 381 das_handle* handle = (das_handle*)cookie; 382 383 return handle->info->io_scheduler->ScheduleRequest(request); 384 } 385 386 387 static status_t 388 das_ioctl(void* cookie, uint32 op, void* buffer, size_t length) 389 { 390 das_handle* handle = (das_handle*)cookie; 391 das_driver_info* info = handle->info; 392 393 TRACE("ioctl(op = %" B_PRIu32 ")\n", op); 394 395 switch (op) { 396 case B_GET_DEVICE_SIZE: 397 { 398 status_t status = update_capacity(info); 399 if (status != B_OK) 400 return status; 401 402 size_t size = info->capacity * info->block_size; 403 return user_memcpy(buffer, &size, sizeof(size_t)); 404 } 405 406 case B_GET_GEOMETRY: 407 { 408 if (buffer == NULL || length > sizeof(device_geometry)) 409 return B_BAD_VALUE; 410 411 device_geometry geometry; 412 status_t status = get_geometry(handle, &geometry); 413 if (status != B_OK) 414 return status; 415 416 return user_memcpy(buffer, &geometry, length); 417 } 418 419 case B_GET_ICON_NAME: 420 // TODO: take device type into account! 421 return user_strlcpy((char*)buffer, info->removable 422 ? "devices/drive-removable-media" : "devices/drive-harddisk", 423 B_FILE_NAME_LENGTH); 424 425 case B_GET_VECTOR_ICON: 426 { 427 // TODO: take device type into account! 428 device_icon iconData; 429 if (length != sizeof(device_icon)) 430 return B_BAD_VALUE; 431 if (user_memcpy(&iconData, buffer, sizeof(device_icon)) != B_OK) 432 return B_BAD_ADDRESS; 433 434 if (iconData.icon_size >= (int32)sizeof(kDriveIcon)) { 435 if (user_memcpy(iconData.icon_data, kDriveIcon, 436 sizeof(kDriveIcon)) != B_OK) 437 return B_BAD_ADDRESS; 438 } 439 440 iconData.icon_size = sizeof(kDriveIcon); 441 return user_memcpy(buffer, &iconData, sizeof(device_icon)); 442 } 443 444 case B_EJECT_DEVICE: 445 case B_SCSI_EJECT: 446 return load_eject(info, false); 447 448 case B_LOAD_MEDIA: 449 return load_eject(info, true); 450 451 case B_FLUSH_DRIVE_CACHE: 452 return synchronize_cache(info); 453 454 case B_TRIM_DEVICE: 455 { 456 // We know the buffer is kernel-side because it has been 457 // preprocessed in devfs 458 ASSERT(IS_KERNEL_ADDRESS(buffer)); 459 return trim_device(info, (fs_trim_data*)buffer); 460 } 461 462 default: 463 return sSCSIPeripheral->ioctl(handle->scsi_periph_handle, op, 464 buffer, length); 465 } 466 } 467 468 469 // #pragma mark - scsi_periph callbacks 470 471 472 static void 473 das_set_capacity(das_driver_info* info, uint64 capacity, uint32 blockSize, uint32 physicalBlockSize) 474 { 475 TRACE("das_set_capacity(device = %p, capacity = %" B_PRIu64 476 ", blockSize = %" B_PRIu32 ")\n", info, capacity, blockSize); 477 478 // get log2, if possible 479 uint32 blockShift = log2(blockSize); 480 481 if ((1UL << blockShift) != blockSize) 482 blockShift = 0; 483 484 info->capacity = capacity; 485 486 if (info->block_size != blockSize) { 487 if (info->block_size != 0) { 488 dprintf("old %" B_PRId32 ", new %" B_PRId32 "\n", info->block_size, 489 blockSize); 490 panic("updating DMAResource not yet implemented..."); 491 } 492 493 // TODO: we need to replace the DMAResource in our IOScheduler 494 status_t status = info->dma_resource->Init(info->node, blockSize, 1024, 495 32); 496 if (status != B_OK) 497 panic("initializing DMAResource failed: %s", strerror(status)); 498 499 info->io_scheduler = new(std::nothrow) IOSchedulerSimple( 500 info->dma_resource); 501 if (info->io_scheduler == NULL) 502 panic("allocating IOScheduler failed."); 503 504 // TODO: use whole device name here 505 status = info->io_scheduler->Init("scsi"); 506 if (status != B_OK) 507 panic("initializing IOScheduler failed: %s", strerror(status)); 508 509 info->io_scheduler->SetCallback(do_io, info); 510 } 511 512 info->block_size = blockSize; 513 info->physical_block_size = physicalBlockSize; 514 } 515 516 517 static void 518 das_media_changed(das_driver_info *device, scsi_ccb *request) 519 { 520 // do a capacity check 521 // TODO: is this a good idea (e.g. if this is an empty CD)? 522 sSCSIPeripheral->check_capacity(device->scsi_periph_device, request); 523 } 524 525 526 scsi_periph_callbacks callbacks = { 527 (void (*)(periph_device_cookie, uint64, uint32, uint32))das_set_capacity, 528 (void (*)(periph_device_cookie, scsi_ccb *))das_media_changed 529 }; 530 531 532 // #pragma mark - driver module API 533 534 535 static float 536 das_supports_device(device_node *parent) 537 { 538 const char *bus; 539 uint8 deviceType; 540 541 // make sure parent is really the SCSI bus manager 542 if (sDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false)) 543 return -1; 544 545 if (strcmp(bus, "scsi")) 546 return 0.0; 547 548 // check whether it's really a Direct Access Device 549 if (sDeviceManager->get_attr_uint8(parent, SCSI_DEVICE_TYPE_ITEM, 550 &deviceType, true) != B_OK || deviceType != scsi_dev_direct_access) 551 return 0.0; 552 553 return 0.6; 554 } 555 556 557 /*! Called whenever a new device was added to system; 558 if we really support it, we create a new node that gets 559 server by the block_io module 560 */ 561 static status_t 562 das_register_device(device_node *node) 563 { 564 const scsi_res_inquiry *deviceInquiry = NULL; 565 size_t inquiryLength; 566 uint32 maxBlocks; 567 568 // get inquiry data 569 if (sDeviceManager->get_attr_raw(node, SCSI_DEVICE_INQUIRY_ITEM, 570 (const void **)&deviceInquiry, &inquiryLength, true) != B_OK 571 || inquiryLength < sizeof(scsi_res_inquiry)) 572 return B_ERROR; 573 574 // get block limit of underlying hardware to lower it (if necessary) 575 if (sDeviceManager->get_attr_uint32(node, B_DMA_MAX_TRANSFER_BLOCKS, 576 &maxBlocks, true) != B_OK) 577 maxBlocks = INT_MAX; 578 579 // using 10 byte commands, at most 0xffff blocks can be transmitted at once 580 // (sadly, we cannot update this value later on if only 6 byte commands 581 // are supported, but the block_io module can live with that) 582 maxBlocks = min_c(maxBlocks, 0xffff); 583 584 // ready to register 585 device_attr attrs[] = { 586 { B_DEVICE_PRETTY_NAME, B_STRING_TYPE, { .string = "SCSI Disk" }}, 587 // tell block_io whether the device is removable 588 {"removable", B_UINT8_TYPE, {.ui8 = deviceInquiry->removable_medium}}, 589 // impose own max block restriction 590 {B_DMA_MAX_TRANSFER_BLOCKS, B_UINT32_TYPE, {.ui32 = maxBlocks}}, 591 { NULL } 592 }; 593 594 return sDeviceManager->register_node(node, SCSI_DISK_DRIVER_MODULE_NAME, 595 attrs, NULL, NULL); 596 } 597 598 599 static status_t 600 das_init_driver(device_node *node, void **cookie) 601 { 602 TRACE("das_init_driver"); 603 604 uint8 removable; 605 status_t status = sDeviceManager->get_attr_uint8(node, "removable", 606 &removable, false); 607 if (status != B_OK) 608 return status; 609 610 das_driver_info* info = (das_driver_info*)malloc(sizeof(das_driver_info)); 611 if (info == NULL) 612 return B_NO_MEMORY; 613 614 memset(info, 0, sizeof(*info)); 615 616 info->dma_resource = new(std::nothrow) DMAResource; 617 if (info->dma_resource == NULL) { 618 free(info); 619 return B_NO_MEMORY; 620 } 621 622 info->node = node; 623 info->removable = removable; 624 625 device_node* parent = sDeviceManager->get_parent_node(node); 626 sDeviceManager->get_driver(parent, (driver_module_info **)&info->scsi, 627 (void **)&info->scsi_device); 628 sDeviceManager->put_node(parent); 629 630 status = sSCSIPeripheral->register_device((periph_device_cookie)info, 631 &callbacks, info->scsi_device, info->scsi, info->node, 632 info->removable, 10, &info->scsi_periph_device); 633 if (status != B_OK) { 634 delete info->dma_resource; 635 free(info); 636 return status; 637 } 638 639 *cookie = info; 640 return B_OK; 641 } 642 643 644 static void 645 das_uninit_driver(void *_cookie) 646 { 647 das_driver_info* info = (das_driver_info*)_cookie; 648 649 sSCSIPeripheral->unregister_device(info->scsi_periph_device); 650 delete info->dma_resource; 651 free(info); 652 } 653 654 655 static status_t 656 das_register_child_devices(void* _cookie) 657 { 658 das_driver_info* info = (das_driver_info*)_cookie; 659 status_t status; 660 661 char* name = sSCSIPeripheral->compose_device_name(info->node, 662 "disk/scsi"); 663 if (name == NULL) 664 return B_ERROR; 665 666 status = sDeviceManager->publish_device(info->node, name, 667 SCSI_DISK_DEVICE_MODULE_NAME); 668 669 free(name); 670 return status; 671 } 672 673 674 static status_t 675 das_rescan_child_devices(void* _cookie) 676 { 677 das_driver_info* info = (das_driver_info*)_cookie; 678 uint64 capacity = info->capacity; 679 update_capacity(info); 680 if (info->capacity != capacity) 681 sSCSIPeripheral->media_changed(info->scsi_periph_device); 682 return B_OK; 683 } 684 685 686 687 module_dependency module_dependencies[] = { 688 {SCSI_PERIPH_MODULE_NAME, (module_info**)&sSCSIPeripheral}, 689 {B_DEVICE_MANAGER_MODULE_NAME, (module_info**)&sDeviceManager}, 690 {} 691 }; 692 693 struct device_module_info sSCSIDiskDevice = { 694 { 695 SCSI_DISK_DEVICE_MODULE_NAME, 696 0, 697 NULL 698 }, 699 700 das_init_device, 701 das_uninit_device, 702 NULL, //das_remove, 703 704 das_open, 705 das_close, 706 das_free, 707 das_read, 708 das_write, 709 das_io, 710 das_ioctl, 711 712 NULL, // select 713 NULL, // deselect 714 }; 715 716 struct driver_module_info sSCSIDiskDriver = { 717 { 718 SCSI_DISK_DRIVER_MODULE_NAME, 719 0, 720 NULL 721 }, 722 723 das_supports_device, 724 das_register_device, 725 das_init_driver, 726 das_uninit_driver, 727 das_register_child_devices, 728 das_rescan_child_devices, 729 NULL, // removed 730 }; 731 732 module_info* modules[] = { 733 (module_info*)&sSCSIDiskDriver, 734 (module_info*)&sSCSIDiskDevice, 735 NULL 736 }; 737