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:
committed by
Adrien Destugues
parent
bd02d81c24
commit
8b1d35bdbb
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user