Author: axeld Date: 2010-06-01 15:39:12 +0200 (Tue, 01 Jun 2010) New Revision: 36989 Changeset: http://dev.haiku-os.org/changeset/36989/haiku Ticket: http://dev.haiku-os.org/ticket/6036 Modified: haiku/trunk/src/add-ons/kernel/drivers/disk/scsi/scsi_cd/scsi_cd.cpp haiku/trunk/src/add-ons/kernel/drivers/disk/scsi/scsi_cd/scsi_cd.h Log: * Try at solving bug #6036; similar to Linux, we now try to cut down the capacity by trying to read at the end of the medium. * Not tested at all yet. Modified: haiku/trunk/src/add-ons/kernel/drivers/disk/scsi/scsi_cd/scsi_cd.cpp =================================================================== --- haiku/trunk/src/add-ons/kernel/drivers/disk/scsi/scsi_cd/scsi_cd.cpp 2010-06-01 13:37:55 UTC (rev 36988) +++ haiku/trunk/src/add-ons/kernel/drivers/disk/scsi/scsi_cd/scsi_cd.cpp 2010-06-01 13:39:12 UTC (rev 36989) @@ -19,6 +19,8 @@ #include <stdlib.h> #include <string.h> +#include <algorithm> + #include <io_requests.h> #include <vm/vm_page.h> @@ -84,6 +86,104 @@ } +/*! Iteratively correct the reported capacity by trying to read from the device + close to its end. +*/ +static uint64 +test_capacity(cd_driver_info *info) +{ + static const size_t kMaxEntries = 4; + const uint32 blockSize = info->block_size; + const size_t kBufferSize = blockSize * 4; + + size_t numBlocks = B_PAGE_SIZE / blockSize; + uint64 offset = info->original_capacity; + if (offset <= numBlocks) + return B_OK; + + offset -= numBlocks; + + scsi_ccb *request = info->scsi->alloc_ccb(info->scsi_device); + if (request == NULL) + return B_NO_MEMORY; + + // Allocate buffer + + physical_entry entries[4]; + size_t numEntries = 0; + + vm_page_reservation reservation; + vm_page_reserve_pages(&reservation, + (kBufferSize - 1 + B_PAGE_SIZE) / B_PAGE_SIZE, VM_PRIORITY_SYSTEM); + + for (size_t left = kBufferSize; numEntries < kMaxEntries && left > 0; + numEntries++) { + size_t bytes = std::min(left, (size_t)B_PAGE_SIZE); + + vm_page* page = vm_page_allocate_page(&reservation, + PAGE_STATE_WIRED | VM_PAGE_ALLOC_BUSY); + + entries[numEntries].address = page->physical_page_number * B_PAGE_SIZE; + entries[numEntries].size = bytes;; + + left -= bytes; + } + + vm_page_unreserve_pages(&reservation); + + // Read close to the end of the device to find out its real end + + info->capacity = info->original_capacity; + + // Only try 1 second before the end (= 75 blocks) + while (offset > info->original_capacity - 75) { + size_t bytesTransferred; + status_t status = sSCSIPeripheral->read_write(info->scsi_periph_device, + request, offset, numBlocks, entries, numEntries, false, + &bytesTransferred); + if (status == B_OK || (request->sense[0] & 0x7f) != 0x70) + break; + + switch (request->sense[2]) { + case SCSIS_KEY_MEDIUM_ERROR: + case SCSIS_KEY_ILLEGAL_REQUEST: + case SCSIS_KEY_VOLUME_OVERFLOW: + { + // find out the problematic sector + uint32 errorBlock = (request->sense[3] << 24U) + | (request->sense[4] << 16U) | (request->sense[5] << 8U) + | request->sense[6]; + if (errorBlock >= offset) + info->capacity = errorBlock - 1; + break; + } + + default: + break; + } + + if (numBlocks > offset) + break; + + offset -= numBlocks; + } + + info->scsi->free_ccb(request); + + for (size_t i = 0; i < numEntries; i++) { + vm_page_set_state(vm_lookup_page(entries[i].address / B_PAGE_SIZE), + PAGE_STATE_FREE); + } + + if (info->capacity != info->original_capacity) { + dprintf("scsi_cd: adjusted capacity from %llu to %llu blocks.\n", + info->original_capacity, info->capacity); + } + + return B_OK; +} + + static status_t get_geometry(cd_handle *handle, device_geometry *geometry) { @@ -866,8 +966,6 @@ if ((1UL << blockShift) != blockSize) blockShift = 0; - info->capacity = capacity; - if (info->block_size != blockSize) { if (capacity == 0) { // there is obviously no medium in the drive, don't try to update @@ -912,12 +1010,20 @@ panic("initializing IOScheduler failed: %s", strerror(status)); info->io_scheduler->SetCallback(do_io, info); + info->block_size = blockSize; } - if (info->io_scheduler != NULL) - info->io_scheduler->SetDeviceCapacity(capacity * blockSize); + if (info->original_capacity != capacity) { + info->original_capacity = capacity; - info->block_size = blockSize; + // For CDs, it's obviously relatively normal that they report a larger + // capacity than it can actually address. Therefore we'll manually + // correct the value here. + test_capacity(info); + + if (info->io_scheduler != NULL) + info->io_scheduler->SetDeviceCapacity(info->capacity * blockSize); + } } @@ -926,6 +1032,7 @@ { // do a capacity check // TBD: is this a good idea (e.g. if this is an empty CD)? + info->original_capacity = 0; sSCSIPeripheral->check_capacity(info->scsi_periph_device, request); if (info->io_scheduler != NULL) Modified: haiku/trunk/src/add-ons/kernel/drivers/disk/scsi/scsi_cd/scsi_cd.h =================================================================== --- haiku/trunk/src/add-ons/kernel/drivers/disk/scsi/scsi_cd/scsi_cd.h 2010-06-01 13:37:55 UTC (rev 36988) +++ haiku/trunk/src/add-ons/kernel/drivers/disk/scsi/scsi_cd/scsi_cd.h 2010-06-01 13:39:12 UTC (rev 36989) @@ -32,6 +32,7 @@ DMAResource* dma_resource; uint64 capacity; + uint64 original_capacity; uint32 block_size; bool removable;