* 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. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@36989 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
@@ -19,6 +19,8 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include <io_requests.h>
|
#include <io_requests.h>
|
||||||
#include <vm/vm_page.h>
|
#include <vm/vm_page.h>
|
||||||
|
|
||||||
@@ -84,6 +86,104 @@ update_capacity(cd_driver_info *info)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*! 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
|
static status_t
|
||||||
get_geometry(cd_handle *handle, device_geometry *geometry)
|
get_geometry(cd_handle *handle, device_geometry *geometry)
|
||||||
{
|
{
|
||||||
@@ -866,8 +966,6 @@ cd_set_capacity(cd_driver_info* info, uint64 capacity, uint32 blockSize)
|
|||||||
if ((1UL << blockShift) != blockSize)
|
if ((1UL << blockShift) != blockSize)
|
||||||
blockShift = 0;
|
blockShift = 0;
|
||||||
|
|
||||||
info->capacity = capacity;
|
|
||||||
|
|
||||||
if (info->block_size != blockSize) {
|
if (info->block_size != blockSize) {
|
||||||
if (capacity == 0) {
|
if (capacity == 0) {
|
||||||
// there is obviously no medium in the drive, don't try to update
|
// there is obviously no medium in the drive, don't try to update
|
||||||
@@ -912,12 +1010,20 @@ cd_set_capacity(cd_driver_info* info, uint64 capacity, uint32 blockSize)
|
|||||||
panic("initializing IOScheduler failed: %s", strerror(status));
|
panic("initializing IOScheduler failed: %s", strerror(status));
|
||||||
|
|
||||||
info->io_scheduler->SetCallback(do_io, info);
|
info->io_scheduler->SetCallback(do_io, info);
|
||||||
|
info->block_size = blockSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (info->io_scheduler != NULL)
|
if (info->original_capacity != capacity) {
|
||||||
info->io_scheduler->SetDeviceCapacity(capacity * blockSize);
|
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 @@ cd_media_changed(cd_driver_info* info, scsi_ccb* request)
|
|||||||
{
|
{
|
||||||
// do a capacity check
|
// do a capacity check
|
||||||
// TBD: is this a good idea (e.g. if this is an empty CD)?
|
// 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);
|
sSCSIPeripheral->check_capacity(info->scsi_periph_device, request);
|
||||||
|
|
||||||
if (info->io_scheduler != NULL)
|
if (info->io_scheduler != NULL)
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ struct cd_driver_info {
|
|||||||
DMAResource* dma_resource;
|
DMAResource* dma_resource;
|
||||||
|
|
||||||
uint64 capacity;
|
uint64 capacity;
|
||||||
|
uint64 original_capacity;
|
||||||
uint32 block_size;
|
uint32 block_size;
|
||||||
|
|
||||||
bool removable;
|
bool removable;
|
||||||
|
|||||||
Reference in New Issue
Block a user