Improve SCSI and SATA trim support

Fixes:
* scsi: Fix a bug that caused the device capacity to be set
  to an undefined value for some large SCSI devices when
  READ CAPACITY (16) was used
* ahci: Fix VPD page reporting so that it does not return
  undefined values
* ahci: Set the write bit to true when sending a DATA SET
  MANAGEMENT (trim) command to a device. The command would
  otherwise fail and time out on some devices.

Improvements:
* scsi: Extend the READ CAPACITY (16) support to also
  include logical block provisioning information
* scsi: Prefer READ CAPACITY (16) over READ CAPACITY (10)
  on devices that are expected to support this command
* scsi, ahci: Enable trim on SCSI and SATA devices that
  are expected to support trim and which correctly report
  trim support
* ahci: Redo the implementation of the SCSI UNMAP command
* scsi: Redo UNMAP-related code
* scsi: Add support for UNMAP via WRITE SAME (10) and
  WRITE SAME (16) commands
* When copying trim ranges between different data types,
  make sure that the values don't change (detect overflows)
* Report the number of trimmed blocks even if the trim
  operation fails

Change-Id: Ie5fc993bbbc19546b4308138ba10184bf7b9986a
Reviewed-on: https://review.haiku-os.org/c/haiku/+/4157
Tested-by: Commit checker robot <[email protected]>
Reviewed-by: Adrien Destugues <[email protected]>
This commit is contained in:
David Sebek
2021-07-16 18:35:57 +00:00
committed by Adrien Destugues
parent bd02d81c24
commit 8b1d35bdbb
11 changed files with 1133 additions and 272 deletions
+53 -5
View File
@@ -255,7 +255,10 @@ typedef struct scsi_res_inquiry {
// Asynchronous Event Notification Capable
);
uint8 additional_length; // total (whished) length = this + 4
uint8 _res5;
B_LBITFIELD8_2(
protect : 1,
_res5_1 : 7
);
uint8 _res6;
B_LBITFIELD8_8(
soft_reset : 1, // 0 = soft reset leads to hard reset
@@ -350,7 +353,7 @@ typedef struct scsi_page_block_limits {
);
uint8 page_code;
uint16 _page_length;
uint16 page_length;
B_LBITFIELD8_2(
wsnz : 1,
_res4_1 : 7
@@ -424,13 +427,36 @@ typedef struct scsi_cmd_read_capacity_long {
uint8 service_action;
uint64 lba;
uint32 alloc_length;
uint8 relative_address;
B_LBITFIELD8_2(
pmi : 1,
_res14_1 : 7
);
uint8 control;
} _PACKED scsi_cmd_read_capacity_long;
typedef struct scsi_res_read_capacity_long {
uint64 lba; // big endian
uint32 block_size; // in bytes
B_LBITFIELD8_4(
prot_en : 1,
p_type : 3,
rc_basis : 2,
_res12_6 : 2
);
B_LBITFIELD8_2(
logical_blocks_per_physical_block_exponent : 4,
p_i_exponent : 4
);
B_LBITFIELD8_3(
lowest_aligned_lba_p1 : 6,
// first part of the Lowest Aligned LBA field
lbprz : 1,
lbpme : 1
);
uint8 lowest_aligned_lba_p2;
// second part of the Lowest Aligned LBA field
// (B_LBITFIELD16_3 would not help here because of its alignment)
uint8 _res16[16];
} _PACKED scsi_res_read_capacity_long;
@@ -508,16 +534,38 @@ typedef struct scsi_cmd_rw_16 {
} _PACKED scsi_cmd_rw_16;
// WRITE SAME (10)
typedef struct scsi_cmd_wsame_10 {
uint8 opcode;
B_LBITFIELD8_6(
_obsolete1_0 : 1,
_obsolete1_1 : 1,
_obsolete1_2 : 1,
unmap : 1,
anchor : 1,
write_protect : 3
);
uint32 lba;
B_LBITFIELD8_2(
group_number : 5,
_res6_5 : 3
);
uint16 length;
uint8 control;
} _PACKED scsi_cmd_wsame_10;
// WRITE SAME (16)
typedef struct scsi_cmd_wsame_16 {
uint8 opcode;
B_LBITFIELD8_6(
_res1_0 : 1,
ndob : 1,
lb_data : 1,
pb_data : 1,
unmap : 1,
_res1_4 : 1,
anchor : 1,
write_protect : 3
);
uint64 lba;
+3 -2
View File
@@ -73,7 +73,8 @@ typedef struct scsi_periph_callbacks {
} scsi_periph_callbacks;
typedef struct scsi_block_range {
uint64 offset;
// values are in blocks
uint64 lba;
uint64 size;
} scsi_block_range;
@@ -123,7 +124,7 @@ typedef struct scsi_periph_interface {
err_res (*synchronize_cache)(scsi_periph_device device, scsi_ccb *request);
status_t (*trim_device)(scsi_periph_device_info *device, scsi_ccb *request,
scsi_block_range* ranges, uint32 rangeCount);
scsi_block_range* ranges, uint32 rangeCount, uint64* trimmedBlocks);
// *** removable media ***
// to be called when a medium change is detected to block subsequent commands
@@ -177,6 +177,8 @@ ATADevice::ReadCapacity(ATARequest *request)
}
scsi_res_read_capacity data;
memset(&data, 0, sizeof(data));
data.block_size = B_HOST_TO_BENDIAN_INT32(fBlockSize);
if (fTotalSectors <= UINT_MAX) {
@@ -184,7 +186,8 @@ ATADevice::ReadCapacity(ATARequest *request)
data.lba = B_HOST_TO_BENDIAN_INT32(lastBlock);
} else
data.lba = UINT_MAX;
TRACE("returning last block: %lu\n", B_BENDIAN_TO_HOST_INT32(data.lba));
TRACE("returning last block: %" B_PRIu32 "\n",
B_BENDIAN_TO_HOST_INT32(data.lba));
copy_sg_data(ccb, 0, ccb->data_length, &data, sizeof(data), false);
ccb->data_resid = MAX(ccb->data_length - sizeof(data), 0);
@@ -198,15 +201,29 @@ ATADevice::ReadCapacity16(ATARequest *request)
TRACE_FUNCTION("%p\n", request);
scsi_ccb *ccb = request->CCB();
scsi_cmd_read_capacity_long *command
= (scsi_cmd_read_capacity_long *)ccb->cdb;
if (command->pmi || command->lba) {
request->SetSense(SCSIS_KEY_ILLEGAL_REQUEST, SCSIS_ASC_INV_CDB_FIELD);
return B_ERROR;
}
uint32 allocationLength = B_BENDIAN_TO_HOST_INT32(command->alloc_length);
scsi_res_read_capacity_long data;
memset(&data, 0, sizeof(data));
data.block_size = B_HOST_TO_BENDIAN_INT32(fBlockSize);
uint64 lastBlock = fTotalSectors - 1;
data.lba = B_HOST_TO_BENDIAN_INT64(lastBlock);
TRACE("returning last block: %llu\n", data.lba);
TRACE("returning last block: %" B_PRIu64 "\n",
B_BENDIAN_TO_HOST_INT64(data.lba));
copy_sg_data(ccb, 0, ccb->data_length, &data, sizeof(data), false);
ccb->data_resid = MAX(ccb->data_length - sizeof(data), 0);
size_t copySize = min_c(allocationLength, sizeof(data));
copy_sg_data(ccb, 0, ccb->data_length, &data, copySize, false);
ccb->data_resid = MAX(ccb->data_length - copySize, 0);
return B_OK;
}
@@ -334,7 +334,7 @@ extern pci_x86_module_info* gPCIx86Module;
#define LO32(val) ((uint32)(addr_t)(val))
#define HI32(val) ((uint32)(((uint64)(addr_t)(val)) >> 32))
#define ASSERT(expr) if (expr) {} else panic(#expr)
#define ASSERT(expr) if (expr) {} else panic("%s", #expr)
#define PCI_VENDOR_INTEL 0x8086
#define PCI_VENDOR_JMICRON 0x197b
+234 -106
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2008-2015 Haiku, Inc. All rights reserved.
* Copyright 2008-2021 Haiku, Inc. All rights reserved.
* Copyright 2007-2009, Marcus Overhagen. All rights reserved.
* Distributed under the terms of the MIT License.
*
@@ -7,6 +7,7 @@
* Axel Dörfler, axeld@pinc-software.de
* Michael Lotz, mmlr@mlotz.ch
* Alexander von Gluck IV, kallisti5@unixzen.com
* David Sebek, dasebek@gmail.com
*/
@@ -47,6 +48,18 @@
#define INQUIRY_BASE_LENGTH 36
// DATA SET MANAGEMENT command limits
#define DSM_MAX_COUNT_48 UINT16_MAX
// max number of 512-byte blocks (48-bit command)
#define DSM_MAX_COUNT_28 UINT8_MAX
// max number of 512-byte blocks (28-bit command)
#define DSM_RANGE_BLOCK_ENTRIES 64
// max entries in a 512-byte block (512 / 8)
#define DSM_MAX_RANGE_VALUE UINT16_C(0xffff)
#define DSM_MAX_LBA_VALUE UINT64_C(0xffffffffffff)
AHCIPort::AHCIPort(AHCIController* controller, int index)
:
fController(controller),
@@ -64,7 +77,9 @@ AHCIPort::AHCIPort(AHCIController* controller, int index)
fTestUnitReadyActive(false),
fPortReset(false),
fError(false),
fTrimSupported(false)
fTrimSupported(false),
fTrimReturnsZeros(false),
fMaxTrimRangeBlocks(0)
{
B_INITIALIZE_SPINLOCK(&fSpinlock);
fRequestSem = create_sem(1, "ahci request");
@@ -585,26 +600,69 @@ AHCIPort::ScsiVPDInquiry(scsi_ccb* request, ata_device_infoblock* ataData)
switch (cmd->page_code) {
case SCSI_PAGE_SUPPORTED_VPD:
{
scsi_page_list vpdPageData;
vpdDataLength = sizeof(vpdPageData);
// supported pages should be in ascending numerical order
const uint8 supportedPages[] = {
SCSI_PAGE_SUPPORTED_VPD,
SCSI_PAGE_BLOCK_LIMITS,
SCSI_PAGE_LB_PROVISIONING
};
vpdPageData.page_code = cmd->page_code;
const size_t bufferLength = sizeof(scsi_page_list)
+ sizeof(supportedPages) - 1;
uint8 buffer[bufferLength];
scsi_page_list* vpdPageData = (scsi_page_list*)buffer;
memset(vpdPageData, 0, bufferLength);
vpdPageData->page_code = cmd->page_code;
// Our supported pages
vpdPageData.page_length = 1;
vpdPageData.pages[0] = SCSI_PAGE_BLOCK_LIMITS;
vpdPageData->page_length = sizeof(supportedPages);
memcpy(vpdPageData->pages, supportedPages, sizeof(supportedPages));
uint8 allocationLength = cmd->allocation_length;
vpdDataLength = min_c(allocationLength, bufferLength);
transactionResult = sg_memcpy(request->sg_list, request->sg_count,
&vpdPageData, vpdDataLength);
vpdPageData, vpdDataLength);
break;
}
case SCSI_PAGE_BLOCK_LIMITS:
{
scsi_page_block_limits vpdPageData;
vpdDataLength = sizeof(vpdPageData);
memset(&vpdPageData, 0, sizeof(vpdPageData));
vpdPageData.page_code = cmd->page_code;
vpdPageData.max_unmap_lba_count
= ataData->max_data_set_management_lba_range_blocks;
vpdPageData.page_length
= B_HOST_TO_BENDIAN_INT16(sizeof(vpdPageData) - 4);
if (fTrimSupported) {
// We can handle anything as long as we have enough memory
// (UNMAP structure can realistically be max. 65528 bytes)
vpdPageData.max_unmap_lba_count
= B_HOST_TO_BENDIAN_INT32(UINT32_MAX);
vpdPageData.max_unmap_blk_count
= B_HOST_TO_BENDIAN_INT32(UINT32_MAX);
}
uint8 allocationLength = cmd->allocation_length;
vpdDataLength = min_c(allocationLength, sizeof(vpdPageData));
transactionResult = sg_memcpy(request->sg_list, request->sg_count,
&vpdPageData, vpdDataLength);
break;
}
case SCSI_PAGE_LB_PROVISIONING:
{
scsi_page_lb_provisioning vpdPageData;
memset(&vpdPageData, 0, sizeof(vpdPageData));
vpdPageData.page_code = cmd->page_code;
vpdPageData.page_length
= B_HOST_TO_BENDIAN_INT16(sizeof(vpdPageData) - 4);
vpdPageData.lbpu = fTrimSupported;
vpdPageData.lbprz = fTrimReturnsZeros;
uint8 allocationLength = cmd->allocation_length;
vpdDataLength = min_c(allocationLength, sizeof(vpdPageData));
transactionResult = sg_memcpy(request->sg_list, request->sg_count,
&vpdPageData, vpdDataLength);
@@ -612,7 +670,6 @@ AHCIPort::ScsiVPDInquiry(scsi_ccb* request, ata_device_infoblock* ataData)
}
case SCSI_PAGE_USN:
case SCSI_PAGE_BLOCK_DEVICE_CHARS:
case SCSI_PAGE_LB_PROVISIONING:
case SCSI_PAGE_REFERRALS:
ERROR("VPD AHCI page %d not yet implemented!\n",
cmd->page_code);
@@ -669,6 +726,8 @@ AHCIPort::ScsiInquiry(scsi_ccb* request)
return;
}
memset(&ataData, 0, sizeof(ataData));
sata_request sreq;
sreq.SetData(&ataData, sizeof(ataData));
sreq.SetATACommand(fIsATAPI
@@ -698,12 +757,16 @@ AHCIPort::ScsiInquiry(scsi_ccb* request)
}
*/
memset(&scsiData, 0, sizeof(scsiData));
scsiData.device_type = fIsATAPI
? ataData.word_0.atapi.command_packet_set : scsi_dev_direct_access;
scsiData.device_qualifier = scsi_periph_qual_connected;
scsiData.device_type_modifier = 0;
scsiData.removable_medium = ataData.word_0.ata.removable_media_device;
scsiData.ansi_version = 2;
scsiData.ansi_version = 5;
// Set the version to SPC-3 so that scsi_periph
// uses READ CAPACITY (16) and attempts to read VPD pages
scsiData.ecma_version = 0;
scsiData.iso_version = 0;
scsiData.response_data_format = 2;
@@ -721,6 +784,7 @@ AHCIPort::ScsiInquiry(scsi_ccb* request)
fSectorCount = ataData.SectorCount(fUse48BitCommands, true);
fSectorSize = ataData.SectorSize();
fTrimSupported = ataData.data_set_management_support;
fTrimReturnsZeros = ataData.supports_read_zero_after_trim;
fMaxTrimRangeBlocks = B_LENDIAN_TO_HOST_INT16(
ataData.max_data_set_management_lba_range_blocks);
TRACE("lba %d, lba48 %d, fUse48BitCommands %d, sectors %" B_PRIu32
@@ -738,7 +802,7 @@ AHCIPort::ScsiInquiry(scsi_ccb* request)
"%sdeterministic%s.\n", fMaxTrimRangeBlocks,
deterministic ? "" : "non-", deterministic
? (ataData.supports_read_zero_after_trim
? ", zero" : ", random") : "");
? ", zero" : ", undefined") : "");
#endif
}
}
@@ -842,6 +906,8 @@ AHCIPort::ScsiReadCapacity(scsi_ccb* request)
TRACE("SectorSize %" B_PRIu32 ", SectorCount 0x%" B_PRIx64 "\n",
fSectorSize, fSectorCount);
memset(&scsiData, 0, sizeof(scsiData));
scsiData.block_size = B_HOST_TO_BENDIAN_INT32(fSectorSize);
if (fSectorCount <= 0xffffffff)
@@ -865,20 +931,37 @@ AHCIPort::ScsiReadCapacity16(scsi_ccb* request)
{
TRACE("AHCIPort::ScsiReadCapacity16 port %d\n", fIndex);
const scsi_cmd_read_capacity_long* cmd
= (const scsi_cmd_read_capacity_long*)request->cdb;
scsi_res_read_capacity_long scsiData;
uint32 allocationLength = B_BENDIAN_TO_HOST_INT32(cmd->alloc_length);
size_t copySize = min_c(allocationLength, sizeof(scsiData));
if (cmd->pmi || cmd->lba || request->data_length < copySize) {
TRACE("invalid request\n");
request->subsys_status = SCSI_REQ_ABORTED;
gSCSI->finished(request, 1);
return;
}
TRACE("SectorSize %" B_PRIu32 ", SectorCount 0x%" B_PRIx64 "\n",
fSectorSize, fSectorCount);
memset(&scsiData, 0, sizeof(scsiData));
scsiData.block_size = B_HOST_TO_BENDIAN_INT32(fSectorSize);
scsiData.lba = B_HOST_TO_BENDIAN_INT64(fSectorCount - 1);
scsiData.rc_basis = 0x01;
scsiData.lbpme = fTrimSupported;
scsiData.lbprz = fTrimReturnsZeros;
if (sg_memcpy(request->sg_list, request->sg_count, &scsiData,
sizeof(scsiData)) < B_OK) {
copySize) < B_OK) {
request->subsys_status = SCSI_DATA_RUN_ERR;
} else {
request->subsys_status = SCSI_REQ_CMP;
request->data_resid = request->data_length - sizeof(scsiData);
request->data_resid = request->data_length - copySize;
}
gSCSI->finished(request, 1);
}
@@ -939,108 +1022,147 @@ AHCIPort::ScsiReadWrite(scsi_ccb* request, uint64 lba, size_t sectorCount,
void
AHCIPort::ScsiUnmap(scsi_ccb* request, scsi_unmap_parameter_list* unmapBlocks)
{
if (!fTrimSupported || fMaxTrimRangeBlocks == 0) {
ERROR("TRIM error: Invalid TRIM support values detected\n");
return;
}
// Determine how many blocks are supposed to be trimmed in total
uint32 scsiRangeCount = B_BENDIAN_TO_HOST_INT16(
uint32 scsiRangeCount = (uint16)B_BENDIAN_TO_HOST_INT16(
unmapBlocks->block_data_length) / sizeof(scsi_unmap_block_descriptor);
dprintf("TRIM SCSI:\n");
for (uint32 i = 0; i < scsiRangeCount; i++) {
dprintf("[%3" B_PRIu32 "] %" B_PRIu64 " : %" B_PRIu32 "\n", i,
(uint64)B_BENDIAN_TO_HOST_INT64(unmapBlocks->blocks[i].lba),
(uint32)B_BENDIAN_TO_HOST_INT32(unmapBlocks->blocks[i].block_count));
}
#ifdef DEBUG_TRIM
dprintf("TRIM: AHCI: received a SCSI UNMAP command (blocks):\n");
for (uint32 i = 0; i < scsiRangeCount; i++) {
dprintf("[%3" B_PRIu32 "] %" B_PRIu64 " : %" B_PRIu32 "\n", i,
(uint64)B_BENDIAN_TO_HOST_INT64(unmapBlocks->blocks[i].lba),
(uint32)B_BENDIAN_TO_HOST_INT32(
unmapBlocks->blocks[i].block_count));
}
#endif
uint32 scsiIndex = 0;
uint32 scsiLastBlocks = 0;
uint32 maxLBARangeCount = fMaxTrimRangeBlocks * 512 / 8;
// 512 bytes per range block, 8 bytes per range
if (scsiRangeCount == 0) {
request->subsys_status = SCSI_REQ_CMP;
request->data_resid = 0;
request->device_status = SCSI_STATUS_GOOD;
gSCSI->finished(request, 1);
return;
}
// Split the SCSI ranges into ATA ranges as large as allowed.
// We assume that the SCSI unmap ranges cannot be merged together
size_t lbaRangeCount = 0;
for (uint32 i = 0; i < scsiRangeCount; i++) {
uint32 range
= B_BENDIAN_TO_HOST_INT32(unmapBlocks->blocks[i].block_count);
lbaRangeCount += range / DSM_MAX_RANGE_VALUE;
if (range % DSM_MAX_RANGE_VALUE != 0)
lbaRangeCount++;
}
TRACE("Total number of ATA ranges: %" B_PRIuSIZE "\n", lbaRangeCount);
while (scsiIndex < scsiRangeCount) {
// Determine how many LBA ranges we need for the next chunk
uint32 lbaRangeCount = 0;
for (uint32 i = scsiIndex; i < scsiRangeCount; i++) {
uint32 scsiBlocks = B_BENDIAN_TO_HOST_INT32(
unmapBlocks->blocks[i].block_count);
if (scsiBlocks == 0)
break;
if (i == scsiIndex)
scsiBlocks -= scsiLastBlocks;
size_t lbaRangesAllocatedSize = lbaRangeCount * sizeof(uint64);
// Request data is transferred in 512-byte blocks
if (lbaRangesAllocatedSize % 512 != 0) {
lbaRangesAllocatedSize += 512 - (lbaRangesAllocatedSize % 512);
}
// Apply reported device limits
if (lbaRangesAllocatedSize > (size_t)fMaxTrimRangeBlocks * 512) {
lbaRangesAllocatedSize = (size_t)fMaxTrimRangeBlocks * 512;
}
// Allocate a single buffer and re-use it between requests
TRACE("Allocating a %" B_PRIuSIZE "-byte buffer for ATA request ranges\n",
lbaRangesAllocatedSize);
uint64* lbaRanges = (uint64*)malloc(lbaRangesAllocatedSize);
if (lbaRanges == NULL) {
ERROR("out of memory when allocating space for %" B_PRIuSIZE
" unmap ranges\n", lbaRangesAllocatedSize / sizeof(uint64));
request->subsys_status = SCSI_REQ_ABORTED;
gSCSI->finished(request, 1);
return;
}
lbaRangeCount += (scsiBlocks + 65534) / 65535;
if (lbaRangeCount >= maxLBARangeCount) {
lbaRangeCount = maxLBARangeCount;
break;
}
}
if (lbaRangeCount == 0)
break;
MemoryDeleter deleter(lbaRanges);
uint32 lbaRangesSize = lbaRangeCount * sizeof(uint64);
uint64* lbaRanges = (uint64*)malloc(lbaRangesSize);
if (lbaRanges == NULL) {
ERROR("out of memory when allocating %" B_PRIu32 " unmap ranges\n",
lbaRangeCount);
request->subsys_status = SCSI_REQ_ABORTED;
gSCSI->finished(request, 1);
return;
memset(lbaRanges, 0, lbaRangesAllocatedSize);
// Entries with range length of 0 will be ignored
uint32 lbaIndex = 0;
for (uint32 i = 0; i < scsiRangeCount; i++) {
uint64 lba = B_BENDIAN_TO_HOST_INT64(unmapBlocks->blocks[i].lba);
uint64 length = (uint32)B_BENDIAN_TO_HOST_INT32(
unmapBlocks->blocks[i].block_count);
if (length == 0)
continue; // Length of 0 would be ignored by the device anyway
if (lba > DSM_MAX_LBA_VALUE) {
ERROR("LBA value is too large!"
" This unmap range will be skipped.\n");
continue;
}
MemoryDeleter deleter(lbaRanges);
// Split large ranges if needed.
// Range length is limited by:
// - max value of the range field (DSM_MAX_RANGE_VALUE)
while (length > 0) {
uint64 ataRange = min_c(length, DSM_MAX_RANGE_VALUE);
lbaRanges[lbaIndex++]
= B_HOST_TO_LENDIAN_INT64((ataRange << 48) | lba);
for (uint32 lbaIndex = 0;
scsiIndex < scsiRangeCount && lbaIndex < lbaRangeCount;) {
uint64 scsiOffset = B_BENDIAN_TO_HOST_INT64(
unmapBlocks->blocks[scsiIndex].lba) + scsiLastBlocks;
uint32 scsiBlocksLeft = B_BENDIAN_TO_HOST_INT32(
unmapBlocks->blocks[scsiIndex].block_count) - scsiLastBlocks;
// Split into multiple requests if needed.
// The number of entries in a request is limited by:
// - the maximum number of 512-byte blocks reported by the device
// - maximum possible value of the COUNT field
// - the size of our buffer
if (lbaIndex >= fMaxTrimRangeBlocks * DSM_RANGE_BLOCK_ENTRIES
|| (((lbaIndex + 1) * sizeof(uint64) + 511) / 512)
> DSM_MAX_COUNT_48
|| lbaIndex >= lbaRangesAllocatedSize / sizeof(uint64)
|| (i == scsiRangeCount - 1 && length <= DSM_MAX_RANGE_VALUE))
{
uint32 lbaRangeCount = lbaIndex;
if (lbaRangeCount % DSM_RANGE_BLOCK_ENTRIES != 0)
lbaRangeCount += DSM_RANGE_BLOCK_ENTRIES
- (lbaRangeCount % DSM_RANGE_BLOCK_ENTRIES);
uint32 lbaRangesSize = lbaRangeCount * sizeof(uint64);
if (scsiBlocksLeft == 0) {
// Ignore the rest of the ranges (they are empty)
scsiIndex = scsiRangeCount;
break;
#ifdef DEBUG_TRIM
dprintf("TRIM: AHCI: sending a DATA SET MANAGEMENT command"
" to the device (blocks):\n");
for (uint32 i = 0; i < lbaRangeCount; i++) {
uint64 value = B_LENDIAN_TO_HOST_INT64(lbaRanges[i]);
dprintf("[%3" B_PRIu32 "] %" B_PRIu64 " : %" B_PRIu64 "\n", i,
value & (((uint64)1 << 48) - 1), value >> 48);
}
#endif
ASSERT(lbaRangesSize % 512 == 0);
ASSERT(lbaRangesSize <= lbaRangesAllocatedSize);
sata_request sreq;
sreq.SetATA48Command(ATA_COMMAND_DATA_SET_MANAGEMENT, 0,
lbaRangesSize / 512);
sreq.SetFeature(1);
sreq.SetData(lbaRanges, lbaRangesSize);
ExecuteSataRequest(&sreq, true);
sreq.WaitForCompletion();
if ((sreq.CompletionStatus() & ATA_STATUS_ERROR) != 0) {
ERROR("trim failed (%" B_PRIu32
" ATA ranges)!\n", lbaRangeCount);
request->subsys_status = SCSI_REQ_CMP_ERR;
request->device_status = SCSI_STATUS_CHECK_CONDITION;
gSCSI->finished(request, 1);
return;
} else
request->subsys_status = SCSI_REQ_CMP;
lbaIndex = 0;
memset(lbaRanges, 0, lbaRangesSize);
}
while (scsiBlocksLeft > 0 && lbaIndex < lbaRangeCount) {
uint16 blocks = scsiBlocksLeft > 65535
? 65535 : (uint16)scsiBlocksLeft;
lbaRanges[lbaIndex++] = B_HOST_TO_LENDIAN_INT64(
((uint64)blocks << 48) | scsiOffset);
scsiOffset += blocks;
scsiLastBlocks += blocks;
scsiBlocksLeft -= blocks;
}
if (scsiBlocksLeft == 0) {
scsiLastBlocks = 0;
scsiIndex++;
}
length -= ataRange;
lba += ataRange;
}
dprintf("TRIM AHCI:\n");
for (uint32 i = 0; i < lbaRangeCount; i++) {
uint64 value = B_HOST_TO_LENDIAN_INT64(lbaRanges[i]);
dprintf("[%3" B_PRIu32 "] %" B_PRIu64 " : %" B_PRIu64 "\n", i,
value & (((uint64)1 << 48) - 1), value >> 48);
}
sata_request sreq;
sreq.SetATA48Command(ATA_COMMAND_DATA_SET_MANAGEMENT, 0,
(lbaRangesSize + 511) / 512);
sreq.SetFeature(1);
sreq.SetData(lbaRanges, lbaRangesSize);
ExecuteSataRequest(&sreq);
sreq.WaitForCompletion();
if ((sreq.CompletionStatus() & ATA_STATUS_ERROR) != 0) {
ERROR("trim failed (%" B_PRIu32 " ranges)!\n", lbaRangeCount);
request->subsys_status = SCSI_REQ_CMP_ERR;
} else
request->subsys_status = SCSI_REQ_CMP;
}
request->data_resid = 0;
@@ -1301,9 +1423,15 @@ AHCIPort::ScsiExecuteRequest(scsi_ccb* request)
scsi_unmap_parameter_list* unmapBlocks
= (scsi_unmap_parameter_list*)request->data;
if (unmapBlocks == NULL
|| B_BENDIAN_TO_HOST_INT16(cmd->length) != request->data_length
|| B_BENDIAN_TO_HOST_INT16(unmapBlocks->data_length)
!= request->data_length - 1) {
|| (uint16)B_BENDIAN_TO_HOST_INT16(cmd->length)
!= request->data_length
|| (uint16)B_BENDIAN_TO_HOST_INT16(unmapBlocks->data_length)
!= request->data_length
- offsetof(scsi_unmap_parameter_list, block_data_length)
|| (uint16)B_BENDIAN_TO_HOST_INT16(
unmapBlocks->block_data_length)
!= request->data_length
- offsetof(scsi_unmap_parameter_list, blocks)) {
ERROR("%s port %d: invalid unmap parameter data length\n",
__func__, fIndex);
request->subsys_status = SCSI_REQ_ABORTED;
@@ -86,6 +86,7 @@ private:
bool fPortReset;
bool fError;
bool fTrimSupported;
bool fTrimReturnsZeros;
uint32 fMaxTrimRangeBlocks;
volatile fis * fFIS;
@@ -1,7 +1,8 @@
/*
* Copyright 2008-2013, Axel Dörfler, axeld@pinc-software.de.
* Copyright 2002/03, Thomas Kurschel. All rights reserved.
* Distributed under the terms of the MIT License.
* Copyright 2021 David Sebek, dasebek@gmail.com
* Copyright 2008-2013 Axel Dörfler, axeld@pinc-software.de
* Copyright 2002/03 Thomas Kurschel
* All rights reserved. Distributed under the terms of the MIT License.
*/
@@ -156,31 +157,64 @@ synchronize_cache(das_driver_info *device)
}
#if 0
static status_t
trim_device(das_driver_info* device, fs_trim_data* trimData)
{
TRACE("trim_device()\n");
trimData->trimmed_size = 0;
scsi_ccb* request = device->scsi->alloc_ccb(device->scsi_device);
if (request == NULL)
return B_NO_MEMORY;
uint64 trimmedSize = 0;
scsi_block_range* blockRanges = (scsi_block_range*)
malloc(trimData->range_count * sizeof(*blockRanges));
if (blockRanges == NULL)
return B_NO_MEMORY;
MemoryDeleter deleter(blockRanges);
for (uint32 i = 0; i < trimData->range_count; i++) {
trimmedSize += trimData->ranges[i].size;
uint64 startBytes = trimData->ranges[i].offset;
uint64 sizeBytes = trimData->ranges[i].size;
uint32 blockSize = device->block_size;
// Align to a block boundary so we don't discard blocks
// that could also contain some other data
uint64 blockOffset = startBytes % blockSize;
if (blockOffset == 0) {
blockRanges[i].lba = startBytes / blockSize;
blockRanges[i].size = sizeBytes / blockSize;
} else {
blockRanges[i].lba = startBytes / blockSize + 1;
blockRanges[i].size = (sizeBytes - (blockSize - blockOffset))
/ blockSize;
}
}
// Check ranges against device capacity and make them fit
for (uint32 i = 0; i < trimData->range_count; i++) {
if (blockRanges[i].lba >= device->capacity) {
dprintf("trim_device(): range offset (LBA) %" B_PRIu64
" exceeds device capacity %" B_PRIu64 "\n",
blockRanges[i].lba, device->capacity);
return B_BAD_VALUE;
}
uint64 maxSize = device->capacity - blockRanges[i].lba;
blockRanges[i].size = min_c(blockRanges[i].size, maxSize);
}
uint64 trimmedBlocks;
status_t status = sSCSIPeripheral->trim_device(device->scsi_periph_device,
request, (scsi_block_range*)&trimData->ranges[0],
trimData->range_count);
request, blockRanges, trimData->range_count, &trimmedBlocks);
device->scsi->free_ccb(request);
if (status == B_OK)
trimData->trimmed_size = trimmedSize;
// Some blocks may have been trimmed even if trim_device returns a failure
trimData->trimmed_size = trimmedBlocks * device->block_size;
return status;
}
#endif
static int
@@ -415,7 +449,6 @@ das_ioctl(void* cookie, uint32 op, void* buffer, size_t length)
case B_FLUSH_DRIVE_CACHE:
return synchronize_cache(info);
#if 0
case B_TRIM_DEVICE:
{
// We know the buffer is kernel-side because it has been
@@ -423,7 +456,6 @@ das_ioctl(void* cookie, uint32 op, void* buffer, size_t length)
ASSERT(IS_KERNEL_ADDRESS(buffer));
return trim_device(info, (fs_trim_data*)buffer);
}
#endif
default:
return sSCSIPeripheral->ioctl(handle->scsi_periph_handle, op,
+726 -110
View File
@@ -1,8 +1,8 @@
/*
* Copyright 2004-2013, Haiku, Inc. All RightsReserved.
* Copyright 2002-2003, Thomas Kurschel. All rights reserved.
*
* Distributed under the terms of the MIT License.
* Copyright 2021 David Sebek, dasebek@gmail.com
* Copyright 2004-2013 Haiku, Inc.
* Copyright 2002-2003 Thomas Kurschel
* All rights reserved. Distributed under the terms of the MIT License.
*/
@@ -16,13 +16,320 @@
#include "scsi_periph_int.h"
status_t
periph_check_capacity(scsi_periph_device_info *device, scsi_ccb *request)
{
scsi_res_read_capacity capacityResult;
scsi_cmd_read_capacity *cmd = (scsi_cmd_read_capacity *)request->cdb;
uint64 capacity;
// UNMAP command limits
#define UNMAP_MAX_LBA_VALUE UINT64_MAX
#define UNMAP_MAX_BLOCK_COUNT_VALUE UINT32_MAX
#define UNMAP_MAX_DESCRIPTORS 4095
// Limit imposed by the UNMAP command structure
#define UNMAP_DEFAULT_DESCRIPTORS 255
// Reasonable default (?) when not specified by the device
// WRITE SAME (16) command limits
#define WS16_MAX_LBA_VALUE UINT64_MAX
#define WS16_MAX_BLOCK_COUNT_VALUE UINT32_MAX
// WRITE SAME (10) command limits
#define WS10_MAX_LBA_VALUE UINT32_MAX
#define WS10_MAX_BLOCK_COUNT_VALUE UINT16_MAX
struct CapacityInfo {
// Result of the READ CAPACITY command
bool capacityFilled;
uint64 lastLba;
uint32 blockSize;
// Provisioining info from READ CAPACITY
bool provisioningFilled;
bool lbpme;
bool lbprz;
};
struct UnmapSupport {
// UNMAP commands supported by the device
bool commandSupportFilled;
bool unmapSupported;
bool ws16Supported;
bool ws10Supported;
// Block limits for UNMAP commands
bool blockLimitsFilled;
uint32 maxUnmapLbaCount;
uint32 maxUnmapDescriptorCount;
uint64 maxWritesameLength;
};
static bool
prefer_read_capacity_16(scsi_periph_device_info* device)
{
const scsi_res_inquiry* inquiryData = NULL;
size_t inquiryDataLength;
if (gDeviceManager->get_attr_raw(device->node, SCSI_DEVICE_INQUIRY_ITEM,
(const void**)&inquiryData, &inquiryDataLength, true) != B_OK
|| inquiryDataLength != sizeof(*inquiryData)) {
return false;
}
if (inquiryData->protect)
return true;
if (inquiryData->ansi_version > 0x04 /* SPC-2 */)
return true;
return false;
}
static bool
vpd_pages_supported(scsi_periph_device_info* device)
{
const scsi_res_inquiry* inquiryData = NULL;
size_t inquiryDataLength;
if (gDeviceManager->get_attr_raw(device->node, SCSI_DEVICE_INQUIRY_ITEM,
(const void**)&inquiryData, &inquiryDataLength, true) != B_OK
|| inquiryDataLength != sizeof(*inquiryData)) {
return false;
}
if (inquiryData->ansi_version >= 0x04 /* SPC-2 */)
return true;
return false;
}
static status_t
read_capacity_10(scsi_periph_device_info* device, scsi_ccb* request,
CapacityInfo* capacityInfo)
{
capacityInfo->capacityFilled = false;
capacityInfo->provisioningFilled = false;
scsi_res_read_capacity capacityResult;
memset(&capacityResult, 0, sizeof(capacityResult));
scsi_cmd_read_capacity* cmd = (scsi_cmd_read_capacity*)request->cdb;
memset(cmd, 0, sizeof(*cmd));
cmd->opcode = SCSI_OP_READ_CAPACITY;
// we don't set PMI (partial medium indicator) as we want the whole capacity;
// in this case, all other parameters must be zero
request->flags = SCSI_DIR_IN;
request->cdb_length = sizeof(*cmd);
request->sort = -1;
request->timeout = device->std_timeout;
request->data = (uint8*)&capacityResult;
request->data_length = sizeof(capacityResult);
request->sg_list = NULL;
status_t res = periph_safe_exec(device, request);
if (res == B_OK && request->data_resid == 0) {
capacityInfo->capacityFilled = true;
capacityInfo->lastLba
= (uint32)B_BENDIAN_TO_HOST_INT32(capacityResult.lba);
capacityInfo->blockSize
= B_BENDIAN_TO_HOST_INT32(capacityResult.block_size);
}
return res;
}
static status_t
read_capacity_16(scsi_periph_device_info* device, scsi_ccb* request,
CapacityInfo* capacityInfo)
{
capacityInfo->capacityFilled = false;
capacityInfo->provisioningFilled = false;
scsi_res_read_capacity_long capacityLongResult;
memset(&capacityLongResult, 0, sizeof(capacityLongResult));
scsi_cmd_read_capacity_long* cmd
= (scsi_cmd_read_capacity_long*)request->cdb;
memset(cmd, 0, sizeof(*cmd));
cmd->opcode = SCSI_OP_SERVICE_ACTION_IN;
cmd->service_action = SCSI_SAI_READ_CAPACITY_16;
cmd->alloc_length = B_HOST_TO_BENDIAN_INT32(sizeof(capacityLongResult));
request->flags = SCSI_DIR_IN;
request->cdb_length = sizeof(*cmd);
request->sort = -1;
request->timeout = device->std_timeout;
request->data = (uint8*)&capacityLongResult;
request->data_length = sizeof(capacityLongResult);
request->sg_list = NULL;
status_t res = periph_safe_exec(device, request);
if (res == B_OK && request->data_resid
<= (int32)sizeof(scsi_res_read_capacity_long) - 12) {
// At least the last LBA and sector size have been transfered
capacityInfo->capacityFilled = true;
capacityInfo->lastLba
= B_BENDIAN_TO_HOST_INT64(capacityLongResult.lba);
capacityInfo->blockSize
= B_BENDIAN_TO_HOST_INT32(capacityLongResult.block_size);
}
if (res == B_OK && request->data_resid
<= (int32)sizeof(scsi_res_read_capacity_long) - 15) {
// lbpme and lbprz bits were received too
capacityInfo->provisioningFilled = true;
capacityInfo->lbpme = capacityLongResult.lbpme;
capacityInfo->lbprz = capacityLongResult.lbprz;
}
return res;
}
static status_t
get_unmap_commands(scsi_periph_device_info* device, scsi_ccb* request,
UnmapSupport* unmapSupport)
{
unmapSupport->commandSupportFilled = false;
scsi_page_lb_provisioning vpdProvisioning;
memset(&vpdProvisioning, 0, sizeof(vpdProvisioning));
status_t vpdStatus = vpd_page_get(device, request,
SCSI_PAGE_LB_PROVISIONING, &vpdProvisioning, sizeof(vpdProvisioning));
if (vpdStatus == B_OK
&& request->data_resid <= (int32)sizeof(scsi_page_lb_provisioning) - 6
&& vpdProvisioning.page_code == SCSI_PAGE_LB_PROVISIONING
&& B_BENDIAN_TO_HOST_INT16(vpdProvisioning.page_length) >= 2) {
unmapSupport->commandSupportFilled = true;
unmapSupport->unmapSupported = vpdProvisioning.lbpu;
unmapSupport->ws16Supported = vpdProvisioning.lbpws;
unmapSupport->ws10Supported = vpdProvisioning.lbpws10;
}
if (vpdStatus == B_BAD_VALUE)
return B_ERROR;
return vpdStatus;
}
static status_t
get_unmap_limits(scsi_periph_device_info* device, scsi_ccb* request,
UnmapSupport* unmapSupport)
{
unmapSupport->blockLimitsFilled = false;
scsi_page_block_limits vpdBlockLimits;
memset(&vpdBlockLimits, 0, sizeof(vpdBlockLimits));
status_t vpdStatus = vpd_page_get(device, request,
SCSI_PAGE_BLOCK_LIMITS, &vpdBlockLimits, sizeof(vpdBlockLimits));
if (vpdStatus == B_OK
&& request->data_resid <= (int32)sizeof(scsi_page_block_limits) - 44
&& vpdBlockLimits.page_code == SCSI_PAGE_BLOCK_LIMITS
&& B_BENDIAN_TO_HOST_INT16(vpdBlockLimits.page_length) == 0x3c) {
unmapSupport->blockLimitsFilled = true;
unmapSupport->maxUnmapLbaCount = B_BENDIAN_TO_HOST_INT32(
vpdBlockLimits.max_unmap_lba_count);
unmapSupport->maxUnmapDescriptorCount = B_BENDIAN_TO_HOST_INT32(
vpdBlockLimits.max_unmap_blk_count);
unmapSupport->maxWritesameLength = B_BENDIAN_TO_HOST_INT64(
vpdBlockLimits.max_write_same_length);
}
if (vpdStatus == B_BAD_VALUE)
return B_ERROR;
return vpdStatus;
}
static void
determine_unmap_support(const UnmapSupport* unmapSupport,
enum trim_command* unmapCommand, uint32* maxLbaCount,
uint32* maxDescriptorCount)
{
#ifdef DEBUG_TRIM
if (unmapSupport->commandSupportFilled)
dprintf("TRIM: device reports (LBP VPD): LBPU = %d, LBPWS = %d,"
" LBPWS10 = %d\n", unmapSupport->unmapSupported,
unmapSupport->ws16Supported, unmapSupport->ws10Supported);
else
dprintf("TRIM: could not get the LBP VPD of the device\n");
if (unmapSupport->blockLimitsFilled)
dprintf("TRIM: device reports (Block Limits VPD):"
"\nTRIM: MAXIMUM UNMAP LBA COUNT = %" B_PRIu32
"\nTRIM: MAXIMUM UNMAP BLOCK DESCRIPTOR COUNT = %" B_PRIu32
"\nTRIM: MAXIMUM WRITESAME LENGTH = %" B_PRIu64 "\n",
unmapSupport->maxUnmapLbaCount,
unmapSupport->maxUnmapDescriptorCount,
unmapSupport->maxWritesameLength);
else
dprintf("TRIM: could not get Block Limits VPD of the device\n");
#endif
*unmapCommand = TRIM_NONE;
*maxLbaCount = 0;
*maxDescriptorCount = 0;
if (!unmapSupport->commandSupportFilled
|| !unmapSupport->blockLimitsFilled)
return;
if (unmapSupport->unmapSupported
&& unmapSupport->maxUnmapLbaCount > 0
&& unmapSupport->maxUnmapDescriptorCount > 0) {
*unmapCommand = TRIM_UNMAP;
*maxLbaCount = unmapSupport->maxUnmapLbaCount;
if (unmapSupport->maxUnmapDescriptorCount == UINT32_MAX
|| unmapSupport->maxUnmapDescriptorCount > UNMAP_MAX_DESCRIPTORS) {
// Choose a reasonable value instead
*maxDescriptorCount = UNMAP_DEFAULT_DESCRIPTORS;
} else {
*maxDescriptorCount = unmapSupport->maxUnmapDescriptorCount;
}
}
if (*unmapCommand == TRIM_NONE && unmapSupport->ws16Supported) {
uint64 maxLength = unmapSupport->maxWritesameLength;
if (maxLength == 0) {
// WRITE SAME limit not reported, try UNMAP limit instead
if (unmapSupport->maxUnmapLbaCount > 0)
maxLength = unmapSupport->maxUnmapLbaCount;
else
maxLength = WS16_MAX_BLOCK_COUNT_VALUE;
}
*unmapCommand = TRIM_WRITESAME16;
*maxLbaCount = min_c(maxLength, WS16_MAX_BLOCK_COUNT_VALUE);
*maxDescriptorCount = 1;
}
if (*unmapCommand == TRIM_NONE && unmapSupport->ws10Supported) {
uint64 maxLength = unmapSupport->maxWritesameLength;
if (maxLength == 0) {
// WRITE SAME limit not reported, try UNMAP limit instead
if (unmapSupport->maxUnmapLbaCount > 0)
maxLength = unmapSupport->maxUnmapLbaCount;
else
maxLength = WS10_MAX_BLOCK_COUNT_VALUE;
}
*unmapCommand = TRIM_WRITESAME10;
*maxLbaCount = min_c(maxLength, WS10_MAX_BLOCK_COUNT_VALUE);
*maxDescriptorCount = 1;
}
}
status_t
periph_check_capacity(scsi_periph_device_info* device, scsi_ccb* request)
{
CapacityInfo capacityInfo = {0};
status_t res;
SHOW_FLOW(3, "%p, %p", device, request);
@@ -32,71 +339,100 @@ periph_check_capacity(scsi_periph_device_info *device, scsi_ccb *request)
if (device->callbacks->set_capacity == NULL)
return B_OK;
request->flags = SCSI_DIR_IN;
if (prefer_read_capacity_16(device)) {
SHOW_FLOW0(3, "READ CAPACITY 16 tried first");
res = read_capacity_16(device, request, &capacityInfo);
request->data = (uint8*)&capacityResult;
request->data_length = sizeof(capacityResult);
request->cdb_length = sizeof(scsi_cmd_read_capacity);
request->timeout = device->std_timeout;
request->sort = -1;
request->sg_list = NULL;
if (res == B_ERROR) {
SHOW_FLOW0(3, "READ CAPACITY 16 failed, trying READ CAPACITY 10");
res = read_capacity_10(device, request, &capacityInfo);
}
} else {
SHOW_FLOW0(3, "READ CAPACITY 10 tried first");
res = read_capacity_10(device, request, &capacityInfo);
memset(cmd, 0, sizeof(*cmd));
cmd->opcode = SCSI_OP_READ_CAPACITY;
// we don't set PMI (partial medium indicator) as we want the whole capacity;
// in this case, all other parameters must be zero
res = periph_safe_exec(device, request);
if (res == B_DEV_MEDIA_CHANGED) {
// in this case, the error handler has already called check_capacity
// recursively, so we ignore our (invalid) result
SHOW_FLOW0( 3, "ignore result because medium change" );
return B_DEV_MEDIA_CHANGED;
if (res == B_OK && capacityInfo.capacityFilled
&& capacityInfo.lastLba == UINT32_MAX) {
SHOW_FLOW0(3, "Device is too large, trying READ CAPACITY 16");
res = read_capacity_16(device, request, &capacityInfo);
}
}
mutex_lock(&device->mutex);
uint64 capacity;
uint32 blockSize;
if (res == B_OK && request->data_resid == 0) {
capacity = B_BENDIAN_TO_HOST_INT32(capacityResult.lba);
if (capacity == UINT_MAX) {
mutex_unlock(&device->mutex);
scsi_cmd_read_capacity_long *cmd
= (scsi_cmd_read_capacity_long *)request->cdb;
scsi_res_read_capacity_long capacityLongResult;
request->data = (uint8*)&capacityLongResult;
request->data_length = sizeof(capacityLongResult);
request->cdb_length = sizeof(scsi_cmd_read_capacity_long);
memset(cmd, 0, sizeof(*cmd));
cmd->opcode = SCSI_OP_SERVICE_ACTION_IN;
cmd->service_action = SCSI_SAI_READ_CAPACITY_16;
res = periph_safe_exec(device, request);
mutex_lock(&device->mutex);
if (res == B_OK && request->data_resid == 0) {
capacity = B_BENDIAN_TO_HOST_INT64(capacityLongResult.lba);
} else
capacity = 0;
}
// the command returns the index of the _last_ block,
// i.e. the size is one larger
++capacity;
blockSize = B_BENDIAN_TO_HOST_INT32(capacityResult.block_size);
if (capacityInfo.capacityFilled) {
capacity = capacityInfo.lastLba + 1;
blockSize = capacityInfo.blockSize;
} else {
capacity = 0;
blockSize = 0;
}
SHOW_FLOW(3, "capacity = %" B_PRId64 ", block_size = %" B_PRId32, capacity,
blockSize);
enum trim_command unmapCommand = TRIM_NONE;
uint32 maxLbaCount = 0;
uint32 maxDescriptorCount = 0;
if (capacityInfo.provisioningFilled
&& capacityInfo.lbpme
&& vpd_pages_supported(device)) {
UnmapSupport unmapSupport = {0};
// Don't fail if the device doesn't support the command
// but fail if some other error happens
if (res == B_OK) {
status_t vpdStatus = get_unmap_commands(device, request,
&unmapSupport);
if (vpdStatus != B_OK && vpdStatus != B_ERROR)
res = vpdStatus;
}
if (res == B_OK) {
status_t vpdStatus = get_unmap_limits(device, request,
&unmapSupport);
if (vpdStatus != B_OK && vpdStatus != B_ERROR)
res = vpdStatus;
}
determine_unmap_support(&unmapSupport, &unmapCommand,
&maxLbaCount, &maxDescriptorCount);
if (maxLbaCount == 0 || maxDescriptorCount == 0)
unmapCommand = TRIM_NONE;
}
if (res == B_DEV_MEDIA_CHANGED) {
// in this case, the error handler has already called check_capacity
// recursively, so we ignore our (invalid) result
SHOW_FLOW0(3, "ignore result because medium change");
return B_DEV_MEDIA_CHANGED;
}
if (res == B_OK && !capacityInfo.capacityFilled)
// Although the capacity and block size will be set to 0 in this case,
// it is also better to inform the caller that these values were not
// reported by the device
res = B_ERROR;
SHOW_FLOW(3, "capacity = %" B_PRIu64 ", block_size = %" B_PRIu32
" (%sreported)", capacity, blockSize,
capacityInfo.capacityFilled ? "" : "not ");
SHOW_INFO(1, "TRIM: Setting trim support to %s",
unmapCommand == TRIM_NONE ? "disabled"
: unmapCommand == TRIM_UNMAP ? "UNMAP"
: unmapCommand == TRIM_WRITESAME16 ? "WRITE SAME (16)"
: unmapCommand == TRIM_WRITESAME10 ? "WRITE SAME (10)"
: "unknown");
SHOW_FLOW(3, "TRIM: Block limits: size = %" B_PRIu32
", descriptors = %" B_PRIu32, maxLbaCount, maxDescriptorCount);
mutex_lock(&device->mutex);
// Was there a reason why this mutex
// was previously locked much earlier?
device->unmap_command = unmapCommand;
device->max_unmap_lba_count = maxLbaCount;
device->max_unmap_descriptor_count = maxDescriptorCount;
device->block_size = blockSize;
@@ -118,61 +454,341 @@ periph_check_capacity(scsi_periph_device_info *device, scsi_ccb *request)
}
status_t
periph_trim_device(scsi_periph_device_info *device, scsi_ccb *request,
scsi_block_range* ranges, uint32 rangeCount)
static status_t
trim_unmap(scsi_periph_device_info* device, scsi_ccb* request,
scsi_block_range* ranges, uint32 rangeCount, uint64* trimmedBlocks)
{
size_t unmapBlockSize = (rangeCount - 1)
uint64 maxLength = UNMAP_MAX_BLOCK_COUNT_VALUE;
uint64 maxBlocksInRequest = device->max_unmap_lba_count;
uint32 maxDescriptors = device->max_unmap_descriptor_count;
*trimmedBlocks = 0;
// Allocate a single buffer and re-use it between requests
size_t expectedDescriptorCount = 0;
for (uint32 i = 0; i < rangeCount; i++) {
expectedDescriptorCount += ranges[i].size / maxLength;
if (ranges[i].size % maxLength != 0)
expectedDescriptorCount++;
}
expectedDescriptorCount = min_c(expectedDescriptorCount, maxDescriptors);
size_t unmapListAllocatedSize = (expectedDescriptorCount - 1)
* sizeof(scsi_unmap_block_descriptor)
+ sizeof(scsi_unmap_parameter_list);
// TODO: check block limits VPD page
// TODO: instead of failing, we should try to complete the request in
// several passes.
if (unmapBlockSize > 65536 || rangeCount == 0)
return B_BAD_VALUE;
scsi_unmap_parameter_list* unmapBlocks
= (scsi_unmap_parameter_list*)malloc(unmapBlockSize);
if (unmapBlocks == NULL)
scsi_unmap_parameter_list* unmapList
= (scsi_unmap_parameter_list*)malloc(unmapListAllocatedSize);
if (unmapList == NULL)
return B_NO_MEMORY;
MemoryDeleter deleter(unmapBlocks);
// Prepare request data
memset(unmapBlocks, 0, unmapBlockSize);
unmapBlocks->data_length = B_HOST_TO_BENDIAN_INT16(unmapBlockSize - 1);
unmapBlocks->block_data_length
= B_HOST_TO_BENDIAN_INT16(unmapBlockSize - 7);
MemoryDeleter deleter(unmapList);
status_t status = B_OK;
uint32 descriptorIndex = 0;
uint64 trimmedBlocksInRequest = 0;
memset(unmapList, 0, unmapListAllocatedSize);
for (uint32 i = 0; i < rangeCount; i++) {
unmapBlocks->blocks[i].lba = B_HOST_TO_BENDIAN_INT64(
ranges[i].offset / device->block_size);
unmapBlocks->blocks[i].block_count = B_HOST_TO_BENDIAN_INT32(
ranges[i].size / device->block_size);
uint64 lba = ranges[i].lba;
uint64 length = ranges[i].size;
if (length == 0)
continue; // Length of 0 would be ignored by the device anyway
if (lba > UNMAP_MAX_LBA_VALUE) {
SHOW_ERROR0(1, "LBA value is too large!"
" This unmap range will be skipped.");
continue;
}
// Split large ranges if needed.
// Range length is limited by:
// - the UNMAP_MAX_BLOCK_COUNT_VALUE constant
// - the total number of LBAs in one UNMAP command is limited by
// the MAX UNMAP LBA COUNT field in the Block Limits VPD page
while (length > 0) {
uint64 trimLength = min_c(length, maxLength);
trimLength = min_c(trimLength,
maxBlocksInRequest - trimmedBlocksInRequest);
unmapList->blocks[descriptorIndex].lba
= B_HOST_TO_BENDIAN_INT64(lba);
unmapList->blocks[descriptorIndex].block_count
= B_HOST_TO_BENDIAN_INT32(trimLength);
descriptorIndex++;
trimmedBlocksInRequest += trimLength;
// Split into multiple requests if needed.
// The number of UNMAP block descriptors is limited by:
// - the number of block descriptors cannot exceed the
// MAXIMUM UNMAP PARAMETER COUNT value in the Block Limits VPD
// - the size of our buffer
// - what fits in one UNMAP command
// - the total number of LBAs in one UNMAP command is limited by
// the MAX UNMAP LBA COUNT field in the Block Limits VPD page
if (descriptorIndex >= maxDescriptors
|| descriptorIndex >= expectedDescriptorCount
|| descriptorIndex >= UNMAP_MAX_DESCRIPTORS
|| trimmedBlocksInRequest >= maxBlocksInRequest
|| (i == rangeCount - 1 && length <= maxLength))
{
uint16 unmapListSize = (descriptorIndex - 1)
* sizeof(scsi_unmap_block_descriptor)
+ sizeof(scsi_unmap_parameter_list);
unmapList->data_length = B_HOST_TO_BENDIAN_INT16(unmapListSize
- offsetof(scsi_unmap_parameter_list, block_data_length));
unmapList->block_data_length
= B_HOST_TO_BENDIAN_INT16(unmapListSize
- offsetof(scsi_unmap_parameter_list, blocks));
scsi_cmd_unmap* cmd = (scsi_cmd_unmap*)request->cdb;
memset(cmd, 0, sizeof(*cmd));
cmd->opcode = SCSI_OP_UNMAP;
cmd->length = B_HOST_TO_BENDIAN_INT16(unmapListSize);
request->flags = SCSI_DIR_OUT;
request->cdb_length = sizeof(*cmd);
request->sort = B_BENDIAN_TO_HOST_INT64(
unmapList->blocks[0].lba);
request->timeout = device->std_timeout;
request->data = (uint8*)unmapList;
request->data_length = unmapListSize;
request->sg_list = NULL;
SHOW_FLOW(3, "UNMAP data used %" B_PRIu16
" of %" B_PRIuSIZE " allocated bytes",
unmapListSize, unmapListAllocatedSize);
#ifdef DEBUG_TRIM
uint16 scsiRangeCount = (uint16)B_BENDIAN_TO_HOST_INT16(
unmapList->block_data_length)
/ sizeof(scsi_unmap_block_descriptor);
uint64 count = 0;
dprintf("TRIM: SCSI: sending an UNMAP command to"
" the device (blocks):\n");
for (uint16 i = 0; i < scsiRangeCount; i++) {
dprintf("[%3" B_PRIu16 "] %" B_PRIu64 " : %" B_PRIu32 "\n",
i, (uint64)B_BENDIAN_TO_HOST_INT64(
unmapList->blocks[i].lba),
(uint32)B_BENDIAN_TO_HOST_INT32(
unmapList->blocks[i].block_count));
count += (uint32)B_BENDIAN_TO_HOST_INT32(
unmapList->blocks[i].block_count);
}
if (device->max_unmap_lba_count >= count)
dprintf("TRIM: SCSI: Previous UNMAP command would fit %"
B_PRIu64 " more LBAs\n",
device->max_unmap_lba_count - count);
else
dprintf("TRIM: SCSI: Previous UNMAP ranges exceed the"
" device limit!\n");
#endif /* DEBUG_TRIM */
status = periph_safe_exec(device, request);
// peripheral layer only creates "read" error
if (status == B_DEV_READ_ERROR)
return B_DEV_WRITE_ERROR;
else if (status != B_OK)
return status;
*trimmedBlocks += trimmedBlocksInRequest;
descriptorIndex = 0;
trimmedBlocksInRequest = 0;
memset(unmapList, 0, unmapListSize);
}
length -= trimLength;
lba += trimLength;
}
}
request->flags = SCSI_DIR_OUT;
request->sort = ranges[0].offset / device->block_size;
request->timeout = device->std_timeout;
scsi_cmd_unmap* cmd = (scsi_cmd_unmap*)request->cdb;
memset(cmd, 0, sizeof(*cmd));
cmd->opcode = SCSI_OP_UNMAP;
cmd->length = B_HOST_TO_BENDIAN_INT16(unmapBlockSize);
request->data = (uint8*)unmapBlocks;
request->data_length = unmapBlockSize;
request->cdb_length = sizeof(*cmd);
status_t status = periph_safe_exec(device, request);
// peripheral layer only creates "read" error
if (status == B_DEV_READ_ERROR)
return B_DEV_WRITE_ERROR;
return status;
}
static status_t
trim_writesame16(scsi_periph_device_info* device, scsi_ccb* request,
scsi_block_range* ranges, uint32 rangeCount, uint64* trimmedBlocks)
{
status_t status = B_OK;
*trimmedBlocks = 0;
for (uint32 i = 0; i < rangeCount; i++) {
uint64 lba = ranges[i].lba;
uint64 length = ranges[i].size;
if (length == 0)
continue; // length of 0 would mean the rest of the device!
if (lba > WS16_MAX_LBA_VALUE) {
SHOW_ERROR0(1, "LBA value is too large!"
" This unmap range will be skipped.");
continue;
}
// Split the range into multiple requests if needed
uint64 maxLength = min_c(device->max_unmap_lba_count,
WS16_MAX_BLOCK_COUNT_VALUE);
while (length > 0) {
uint64 trimLength = min_c(length, maxLength);
if (trimLength == 0) {
SHOW_ERROR0(1,
"Error: Length of zero in WRITE SAME (16) detected");
break;
}
void* block = malloc(device->block_size);
if (block == NULL)
return B_NO_MEMORY;
MemoryDeleter deleter(block);
memset(block, 0, device->block_size);
scsi_cmd_wsame_16* cmd = (scsi_cmd_wsame_16*)request->cdb;
memset(cmd, 0, sizeof(*cmd));
cmd->opcode = SCSI_OP_WRITE_SAME_16;
cmd->unmap = 1;
cmd->lba = B_HOST_TO_BENDIAN_INT64(lba);
cmd->length = B_HOST_TO_BENDIAN_INT32(trimLength);
//cmd->ndob = 1; // no data is needed if this bit is enabled
request->flags = SCSI_DIR_OUT;
request->cdb_length = sizeof(*cmd);
request->sort = lba;
request->timeout = device->std_timeout;
request->data = (uint8*)block;
request->data_length = device->block_size;
request->sg_list = NULL;
#ifdef DEBUG_TRIM
dprintf("TRIM: SCSI: sending a WRITE SAME (16) command to"
" the device (blocks):\n");
dprintf("%" B_PRIu64 " : %" B_PRIu32 "\n",
(uint64)B_BENDIAN_TO_HOST_INT64(cmd->lba),
(uint32)B_BENDIAN_TO_HOST_INT32(cmd->length));
#endif
status = periph_safe_exec(device, request);
// peripheral layer only creates "read" error
if (status == B_DEV_READ_ERROR)
return B_DEV_WRITE_ERROR;
else if (status != B_OK)
return status;
*trimmedBlocks += trimLength;
length -= trimLength;
lba += trimLength;
}
}
return status;
}
static status_t
trim_writesame10(scsi_periph_device_info* device, scsi_ccb* request,
scsi_block_range* ranges, uint32 rangeCount, uint64* trimmedBlocks)
{
status_t status = B_OK;
*trimmedBlocks = 0;
for (uint32 i = 0; i < rangeCount; i++) {
uint64 lba = ranges[i].lba;
uint64 length = ranges[i].size;
if (length == 0)
continue; // length of 0 would mean the rest of the device!
if (lba > WS10_MAX_LBA_VALUE) {
SHOW_ERROR0(1, "LBA value is too large!"
" This unmap range will be skipped.");
continue;
}
// Split the range into multiple requests if needed
uint64 maxLength = min_c(device->max_unmap_lba_count,
WS10_MAX_BLOCK_COUNT_VALUE);
while (length > 0) {
uint64 trimLength = min_c(length, maxLength);
if (trimLength == 0) {
SHOW_ERROR0(1,
"Error: Length of zero in WRITE SAME (10) detected");
break;
}
void* block = malloc(device->block_size);
if (block == NULL)
return B_NO_MEMORY;
MemoryDeleter deleter(block);
memset(block, 0, device->block_size);
scsi_cmd_wsame_10* cmd = (scsi_cmd_wsame_10*)request->cdb;
memset(cmd, 0, sizeof(*cmd));
cmd->opcode = SCSI_OP_WRITE_SAME_10;
cmd->unmap = 1;
cmd->lba = B_HOST_TO_BENDIAN_INT32(lba);
cmd->length = B_HOST_TO_BENDIAN_INT16(trimLength);
request->flags = SCSI_DIR_OUT;
request->cdb_length = sizeof(*cmd);
request->sort = lba;
request->timeout = device->std_timeout;
request->data = (uint8*)block;
request->data_length = device->block_size;
request->sg_list = NULL;
#ifdef DEBUG_TRIM
dprintf("TRIM: SCSI: sending a WRITE SAME (10) command to"
" the device (blocks):\n");
dprintf("%" B_PRIu32 " : %" B_PRIu16 "\n",
(uint32)B_BENDIAN_TO_HOST_INT32(cmd->lba),
(uint16)B_BENDIAN_TO_HOST_INT16(cmd->length));
#endif
status = periph_safe_exec(device, request);
// peripheral layer only creates "read" error
if (status == B_DEV_READ_ERROR)
return B_DEV_WRITE_ERROR;
else if (status != B_OK)
return status;
*trimmedBlocks += trimLength;
length -= trimLength;
lba += trimLength;
}
}
return status;
}
status_t
periph_trim_device(scsi_periph_device_info* device, scsi_ccb* request,
scsi_block_range* ranges, uint32 rangeCount, uint64* trimmedBlocks)
{
*trimmedBlocks = 0;
if (device->unmap_command == TRIM_NONE
|| device->max_unmap_lba_count == 0
|| device->max_unmap_descriptor_count == 0)
return B_UNSUPPORTED;
switch (device->unmap_command) {
case TRIM_UNMAP:
return trim_unmap(device, request, ranges, rangeCount,
trimmedBlocks);
case TRIM_WRITESAME16:
return trim_writesame16(device, request, ranges, rangeCount,
trimmedBlocks);
case TRIM_WRITESAME10:
return trim_writesame10(device, request, ranges, rangeCount,
trimmedBlocks);
default:
return B_UNSUPPORTED;
}
}
@@ -81,6 +81,9 @@ periph_register_device(periph_device_cookie periph_device,
device->next_tag_action = 0;
device->preferred_ccb_size = preferredCcbSize;
device->rw10_enabled = true;
device->unmap_command = TRIM_NONE;
device->max_unmap_lba_count = 0;
device->max_unmap_descriptor_count = 0;
// launch sync daemon
status_t status = register_kernel_daemon(periph_sync_queue_daemon, device,
+31 -28
View File
@@ -46,64 +46,67 @@ inquiry(scsi_periph_device_info *device, scsi_inquiry *inquiry)
static status_t
vpd_page_inquiry(scsi_periph_device_info *device, uint8 page, void* data,
uint16 length)
vpd_page_inquiry(scsi_periph_device_info* device, scsi_ccb* ccb,
uint8 page, void* data, uint16 length)
{
SHOW_FLOW0(0, "");
scsi_ccb* ccb = device->scsi->alloc_ccb(device->scsi_device);
if (ccb == NULL)
return B_NO_MEMORY;
scsi_cmd_inquiry *cmd = (scsi_cmd_inquiry *)ccb->cdb;
scsi_cmd_inquiry* cmd = (scsi_cmd_inquiry*)ccb->cdb;
memset(cmd, 0, sizeof(scsi_cmd_inquiry));
cmd->opcode = SCSI_OP_INQUIRY;
cmd->lun = ccb->target_lun;
cmd->evpd = 1;
cmd->page_code = page;
// the scsi_cmd_inquiry structure follows an older SCSI standard
// which uses only 8 bits for allocation_length
if (length > UINT8_MAX)
return EINVAL;
cmd->allocation_length = length;
ccb->flags = SCSI_DIR_IN;
ccb->cdb_length = sizeof(scsi_cmd_inquiry);
ccb->sort = -1;
ccb->timeout = device->std_timeout;
ccb->data = (uint8*)data;
ccb->sg_list = NULL;
ccb->data_length = length;
ccb->sg_list = NULL;
status_t status = periph_safe_exec(device, ccb);
device->scsi->free_ccb(ccb);
return status;
return periph_safe_exec(device, ccb);
}
status_t
vpd_page_get(scsi_periph_device_info *device, uint8 page, void* data,
uint16 length)
vpd_page_get(scsi_periph_device_info* device, scsi_ccb* request,
uint8 page, void* data, uint16 length)
{
SHOW_FLOW0(0, "");
status_t status = vpd_page_inquiry(device, 0, data, length);
if (page == SCSI_PAGE_SUPPORTED_VPD)
return vpd_page_inquiry(device, request, page, data, length);
const uint16 bufferLength = 252;
// maximum word-aligned value that fits in a byte,
// theoretical maximum is offsetof(scsi_page_list, pages) + UINT8_MAX;
uint8 buffer[bufferLength];
scsi_page_list* vpdPage = (scsi_page_list*)buffer;
memset(vpdPage, 0, bufferLength);
status_t status = vpd_page_inquiry(device, request,
SCSI_PAGE_SUPPORTED_VPD, vpdPage, bufferLength);
if (status != B_OK)
return status; // or B_BAD_VALUE
return status;
if (page == 0)
return B_OK;
if (vpdPage->page_code != SCSI_PAGE_SUPPORTED_VPD)
return B_ERROR;
scsi_page_list *list_data = (scsi_page_list*)data;
int page_length = min_c(list_data->page_length, length -
offsetof(scsi_page_list, pages));
for (int i = 0; i < page_length; i++) {
if (list_data->pages[i] == page)
return vpd_page_inquiry(device, page, data, length);
uint16 pageLength = min_c(vpdPage->page_length,
bufferLength - offsetof(scsi_page_list, pages));
for (uint16 i = 0; i < pageLength; i++) {
if (vpdPage->pages[i] == page)
return vpd_page_inquiry(device, request, page, data, length);
}
// TODO buffer might be not big enough
return B_BAD_VALUE;
}
@@ -16,6 +16,14 @@
#include "wrapper.h"
enum trim_command {
TRIM_NONE, // TRIM operation is disabled for this device
TRIM_UNMAP, // UNMAP command wil be used
TRIM_WRITESAME10, // WRITE SAME (10) with UNMAP bit enabled will be used
TRIM_WRITESAME16 // WRITE SAME (16) with UNMAP bit enabled will be used
};
typedef struct scsi_periph_device_info {
struct scsi_periph_handle_info *handles;
@@ -28,6 +36,10 @@ typedef struct scsi_periph_device_info {
bool removable; // true, if device is removable
enum trim_command unmap_command; // command to be used to discard free blocks
uint32 max_unmap_lba_count; // max. number of LBAs in one command
uint32 max_unmap_descriptor_count; // max. number of ranges in one command
uint32 block_size;
int32 preferred_ccb_size;
int32 rw10_enabled; // 10 byte r/w commands supported; access must be atomic
@@ -78,7 +90,7 @@ status_t periph_handle_free(scsi_periph_handle_info *handle);
status_t periph_check_capacity(scsi_periph_device_info *device, scsi_ccb *ccb);
status_t periph_trim_device(scsi_periph_device_info *device, scsi_ccb *request,
scsi_block_range* ranges, uint32 rangeCount);
scsi_block_range* ranges, uint32 rangeCount, uint64* trimmedBlocks);
// device.c
@@ -101,8 +113,8 @@ status_t periph_io(scsi_periph_device_info* device, io_operation* operation,
status_t periph_ioctl(scsi_periph_handle_info *handle, int op,
void *buf, size_t len);
void periph_sync_queue_daemon(void *arg, int iteration);
status_t vpd_page_get(scsi_periph_device_info *device, uint8 page, void* data,
uint16 length);
status_t vpd_page_get(scsi_periph_device_info *device, scsi_ccb* request,
uint8 page, void* data, uint16 length);
// scsi_periph.c