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
|
// Asynchronous Event Notification Capable
|
||||||
);
|
);
|
||||||
uint8 additional_length; // total (whished) length = this + 4
|
uint8 additional_length; // total (whished) length = this + 4
|
||||||
uint8 _res5;
|
B_LBITFIELD8_2(
|
||||||
|
protect : 1,
|
||||||
|
_res5_1 : 7
|
||||||
|
);
|
||||||
uint8 _res6;
|
uint8 _res6;
|
||||||
B_LBITFIELD8_8(
|
B_LBITFIELD8_8(
|
||||||
soft_reset : 1, // 0 = soft reset leads to hard reset
|
soft_reset : 1, // 0 = soft reset leads to hard reset
|
||||||
@@ -350,7 +353,7 @@ typedef struct scsi_page_block_limits {
|
|||||||
);
|
);
|
||||||
uint8 page_code;
|
uint8 page_code;
|
||||||
|
|
||||||
uint16 _page_length;
|
uint16 page_length;
|
||||||
B_LBITFIELD8_2(
|
B_LBITFIELD8_2(
|
||||||
wsnz : 1,
|
wsnz : 1,
|
||||||
_res4_1 : 7
|
_res4_1 : 7
|
||||||
@@ -424,13 +427,36 @@ typedef struct scsi_cmd_read_capacity_long {
|
|||||||
uint8 service_action;
|
uint8 service_action;
|
||||||
uint64 lba;
|
uint64 lba;
|
||||||
uint32 alloc_length;
|
uint32 alloc_length;
|
||||||
uint8 relative_address;
|
B_LBITFIELD8_2(
|
||||||
|
pmi : 1,
|
||||||
|
_res14_1 : 7
|
||||||
|
);
|
||||||
uint8 control;
|
uint8 control;
|
||||||
} _PACKED scsi_cmd_read_capacity_long;
|
} _PACKED scsi_cmd_read_capacity_long;
|
||||||
|
|
||||||
typedef struct scsi_res_read_capacity_long {
|
typedef struct scsi_res_read_capacity_long {
|
||||||
uint64 lba; // big endian
|
uint64 lba; // big endian
|
||||||
uint32 block_size; // in bytes
|
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;
|
} _PACKED scsi_res_read_capacity_long;
|
||||||
|
|
||||||
|
|
||||||
@@ -508,16 +534,38 @@ typedef struct scsi_cmd_rw_16 {
|
|||||||
} _PACKED 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)
|
// WRITE SAME (16)
|
||||||
|
|
||||||
typedef struct scsi_cmd_wsame_16 {
|
typedef struct scsi_cmd_wsame_16 {
|
||||||
uint8 opcode;
|
uint8 opcode;
|
||||||
B_LBITFIELD8_6(
|
B_LBITFIELD8_6(
|
||||||
_res1_0 : 1,
|
ndob : 1,
|
||||||
lb_data : 1,
|
lb_data : 1,
|
||||||
pb_data : 1,
|
pb_data : 1,
|
||||||
unmap : 1,
|
unmap : 1,
|
||||||
_res1_4 : 1,
|
anchor : 1,
|
||||||
write_protect : 3
|
write_protect : 3
|
||||||
);
|
);
|
||||||
uint64 lba;
|
uint64 lba;
|
||||||
|
|||||||
@@ -73,7 +73,8 @@ typedef struct scsi_periph_callbacks {
|
|||||||
} scsi_periph_callbacks;
|
} scsi_periph_callbacks;
|
||||||
|
|
||||||
typedef struct scsi_block_range {
|
typedef struct scsi_block_range {
|
||||||
uint64 offset;
|
// values are in blocks
|
||||||
|
uint64 lba;
|
||||||
uint64 size;
|
uint64 size;
|
||||||
} scsi_block_range;
|
} scsi_block_range;
|
||||||
|
|
||||||
@@ -123,7 +124,7 @@ typedef struct scsi_periph_interface {
|
|||||||
err_res (*synchronize_cache)(scsi_periph_device device, scsi_ccb *request);
|
err_res (*synchronize_cache)(scsi_periph_device device, scsi_ccb *request);
|
||||||
|
|
||||||
status_t (*trim_device)(scsi_periph_device_info *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 ***
|
// *** removable media ***
|
||||||
// to be called when a medium change is detected to block subsequent commands
|
// 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;
|
scsi_res_read_capacity data;
|
||||||
|
memset(&data, 0, sizeof(data));
|
||||||
|
|
||||||
data.block_size = B_HOST_TO_BENDIAN_INT32(fBlockSize);
|
data.block_size = B_HOST_TO_BENDIAN_INT32(fBlockSize);
|
||||||
|
|
||||||
if (fTotalSectors <= UINT_MAX) {
|
if (fTotalSectors <= UINT_MAX) {
|
||||||
@@ -184,7 +186,8 @@ ATADevice::ReadCapacity(ATARequest *request)
|
|||||||
data.lba = B_HOST_TO_BENDIAN_INT32(lastBlock);
|
data.lba = B_HOST_TO_BENDIAN_INT32(lastBlock);
|
||||||
} else
|
} else
|
||||||
data.lba = UINT_MAX;
|
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);
|
copy_sg_data(ccb, 0, ccb->data_length, &data, sizeof(data), false);
|
||||||
ccb->data_resid = MAX(ccb->data_length - sizeof(data), 0);
|
ccb->data_resid = MAX(ccb->data_length - sizeof(data), 0);
|
||||||
@@ -198,15 +201,29 @@ ATADevice::ReadCapacity16(ATARequest *request)
|
|||||||
TRACE_FUNCTION("%p\n", request);
|
TRACE_FUNCTION("%p\n", request);
|
||||||
|
|
||||||
scsi_ccb *ccb = request->CCB();
|
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;
|
scsi_res_read_capacity_long data;
|
||||||
|
memset(&data, 0, sizeof(data));
|
||||||
|
|
||||||
data.block_size = B_HOST_TO_BENDIAN_INT32(fBlockSize);
|
data.block_size = B_HOST_TO_BENDIAN_INT32(fBlockSize);
|
||||||
|
|
||||||
uint64 lastBlock = fTotalSectors - 1;
|
uint64 lastBlock = fTotalSectors - 1;
|
||||||
data.lba = B_HOST_TO_BENDIAN_INT64(lastBlock);
|
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);
|
size_t copySize = min_c(allocationLength, sizeof(data));
|
||||||
ccb->data_resid = MAX(ccb->data_length - sizeof(data), 0);
|
|
||||||
|
copy_sg_data(ccb, 0, ccb->data_length, &data, copySize, false);
|
||||||
|
ccb->data_resid = MAX(ccb->data_length - copySize, 0);
|
||||||
return B_OK;
|
return B_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -334,7 +334,7 @@ extern pci_x86_module_info* gPCIx86Module;
|
|||||||
|
|
||||||
#define LO32(val) ((uint32)(addr_t)(val))
|
#define LO32(val) ((uint32)(addr_t)(val))
|
||||||
#define HI32(val) ((uint32)(((uint64)(addr_t)(val)) >> 32))
|
#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_INTEL 0x8086
|
||||||
#define PCI_VENDOR_JMICRON 0x197b
|
#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.
|
* Copyright 2007-2009, Marcus Overhagen. All rights reserved.
|
||||||
* Distributed under the terms of the MIT License.
|
* Distributed under the terms of the MIT License.
|
||||||
*
|
*
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
* Axel Dörfler, [email protected]
|
* Axel Dörfler, [email protected]
|
||||||
* Michael Lotz, [email protected]
|
* Michael Lotz, [email protected]
|
||||||
* Alexander von Gluck IV, [email protected]
|
* Alexander von Gluck IV, [email protected]
|
||||||
|
* David Sebek, [email protected]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
@@ -47,6 +48,18 @@
|
|||||||
#define INQUIRY_BASE_LENGTH 36
|
#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)
|
AHCIPort::AHCIPort(AHCIController* controller, int index)
|
||||||
:
|
:
|
||||||
fController(controller),
|
fController(controller),
|
||||||
@@ -64,7 +77,9 @@ AHCIPort::AHCIPort(AHCIController* controller, int index)
|
|||||||
fTestUnitReadyActive(false),
|
fTestUnitReadyActive(false),
|
||||||
fPortReset(false),
|
fPortReset(false),
|
||||||
fError(false),
|
fError(false),
|
||||||
fTrimSupported(false)
|
fTrimSupported(false),
|
||||||
|
fTrimReturnsZeros(false),
|
||||||
|
fMaxTrimRangeBlocks(0)
|
||||||
{
|
{
|
||||||
B_INITIALIZE_SPINLOCK(&fSpinlock);
|
B_INITIALIZE_SPINLOCK(&fSpinlock);
|
||||||
fRequestSem = create_sem(1, "ahci request");
|
fRequestSem = create_sem(1, "ahci request");
|
||||||
@@ -585,26 +600,69 @@ AHCIPort::ScsiVPDInquiry(scsi_ccb* request, ata_device_infoblock* ataData)
|
|||||||
switch (cmd->page_code) {
|
switch (cmd->page_code) {
|
||||||
case SCSI_PAGE_SUPPORTED_VPD:
|
case SCSI_PAGE_SUPPORTED_VPD:
|
||||||
{
|
{
|
||||||
scsi_page_list vpdPageData;
|
// supported pages should be in ascending numerical order
|
||||||
vpdDataLength = sizeof(vpdPageData);
|
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
|
// Our supported pages
|
||||||
vpdPageData.page_length = 1;
|
vpdPageData->page_length = sizeof(supportedPages);
|
||||||
vpdPageData.pages[0] = SCSI_PAGE_BLOCK_LIMITS;
|
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,
|
transactionResult = sg_memcpy(request->sg_list, request->sg_count,
|
||||||
&vpdPageData, vpdDataLength);
|
vpdPageData, vpdDataLength);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SCSI_PAGE_BLOCK_LIMITS:
|
case SCSI_PAGE_BLOCK_LIMITS:
|
||||||
{
|
{
|
||||||
scsi_page_block_limits vpdPageData;
|
scsi_page_block_limits vpdPageData;
|
||||||
vpdDataLength = sizeof(vpdPageData);
|
memset(&vpdPageData, 0, sizeof(vpdPageData));
|
||||||
|
|
||||||
vpdPageData.page_code = cmd->page_code;
|
vpdPageData.page_code = cmd->page_code;
|
||||||
vpdPageData.max_unmap_lba_count
|
vpdPageData.page_length
|
||||||
= ataData->max_data_set_management_lba_range_blocks;
|
= 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,
|
transactionResult = sg_memcpy(request->sg_list, request->sg_count,
|
||||||
&vpdPageData, vpdDataLength);
|
&vpdPageData, vpdDataLength);
|
||||||
@@ -612,7 +670,6 @@ AHCIPort::ScsiVPDInquiry(scsi_ccb* request, ata_device_infoblock* ataData)
|
|||||||
}
|
}
|
||||||
case SCSI_PAGE_USN:
|
case SCSI_PAGE_USN:
|
||||||
case SCSI_PAGE_BLOCK_DEVICE_CHARS:
|
case SCSI_PAGE_BLOCK_DEVICE_CHARS:
|
||||||
case SCSI_PAGE_LB_PROVISIONING:
|
|
||||||
case SCSI_PAGE_REFERRALS:
|
case SCSI_PAGE_REFERRALS:
|
||||||
ERROR("VPD AHCI page %d not yet implemented!\n",
|
ERROR("VPD AHCI page %d not yet implemented!\n",
|
||||||
cmd->page_code);
|
cmd->page_code);
|
||||||
@@ -669,6 +726,8 @@ AHCIPort::ScsiInquiry(scsi_ccb* request)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
memset(&ataData, 0, sizeof(ataData));
|
||||||
|
|
||||||
sata_request sreq;
|
sata_request sreq;
|
||||||
sreq.SetData(&ataData, sizeof(ataData));
|
sreq.SetData(&ataData, sizeof(ataData));
|
||||||
sreq.SetATACommand(fIsATAPI
|
sreq.SetATACommand(fIsATAPI
|
||||||
@@ -698,12 +757,16 @@ AHCIPort::ScsiInquiry(scsi_ccb* request)
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
memset(&scsiData, 0, sizeof(scsiData));
|
||||||
|
|
||||||
scsiData.device_type = fIsATAPI
|
scsiData.device_type = fIsATAPI
|
||||||
? ataData.word_0.atapi.command_packet_set : scsi_dev_direct_access;
|
? ataData.word_0.atapi.command_packet_set : scsi_dev_direct_access;
|
||||||
scsiData.device_qualifier = scsi_periph_qual_connected;
|
scsiData.device_qualifier = scsi_periph_qual_connected;
|
||||||
scsiData.device_type_modifier = 0;
|
scsiData.device_type_modifier = 0;
|
||||||
scsiData.removable_medium = ataData.word_0.ata.removable_media_device;
|
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.ecma_version = 0;
|
||||||
scsiData.iso_version = 0;
|
scsiData.iso_version = 0;
|
||||||
scsiData.response_data_format = 2;
|
scsiData.response_data_format = 2;
|
||||||
@@ -721,6 +784,7 @@ AHCIPort::ScsiInquiry(scsi_ccb* request)
|
|||||||
fSectorCount = ataData.SectorCount(fUse48BitCommands, true);
|
fSectorCount = ataData.SectorCount(fUse48BitCommands, true);
|
||||||
fSectorSize = ataData.SectorSize();
|
fSectorSize = ataData.SectorSize();
|
||||||
fTrimSupported = ataData.data_set_management_support;
|
fTrimSupported = ataData.data_set_management_support;
|
||||||
|
fTrimReturnsZeros = ataData.supports_read_zero_after_trim;
|
||||||
fMaxTrimRangeBlocks = B_LENDIAN_TO_HOST_INT16(
|
fMaxTrimRangeBlocks = B_LENDIAN_TO_HOST_INT16(
|
||||||
ataData.max_data_set_management_lba_range_blocks);
|
ataData.max_data_set_management_lba_range_blocks);
|
||||||
TRACE("lba %d, lba48 %d, fUse48BitCommands %d, sectors %" B_PRIu32
|
TRACE("lba %d, lba48 %d, fUse48BitCommands %d, sectors %" B_PRIu32
|
||||||
@@ -738,7 +802,7 @@ AHCIPort::ScsiInquiry(scsi_ccb* request)
|
|||||||
"%sdeterministic%s.\n", fMaxTrimRangeBlocks,
|
"%sdeterministic%s.\n", fMaxTrimRangeBlocks,
|
||||||
deterministic ? "" : "non-", deterministic
|
deterministic ? "" : "non-", deterministic
|
||||||
? (ataData.supports_read_zero_after_trim
|
? (ataData.supports_read_zero_after_trim
|
||||||
? ", zero" : ", random") : "");
|
? ", zero" : ", undefined") : "");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -842,6 +906,8 @@ AHCIPort::ScsiReadCapacity(scsi_ccb* request)
|
|||||||
TRACE("SectorSize %" B_PRIu32 ", SectorCount 0x%" B_PRIx64 "\n",
|
TRACE("SectorSize %" B_PRIu32 ", SectorCount 0x%" B_PRIx64 "\n",
|
||||||
fSectorSize, fSectorCount);
|
fSectorSize, fSectorCount);
|
||||||
|
|
||||||
|
memset(&scsiData, 0, sizeof(scsiData));
|
||||||
|
|
||||||
scsiData.block_size = B_HOST_TO_BENDIAN_INT32(fSectorSize);
|
scsiData.block_size = B_HOST_TO_BENDIAN_INT32(fSectorSize);
|
||||||
|
|
||||||
if (fSectorCount <= 0xffffffff)
|
if (fSectorCount <= 0xffffffff)
|
||||||
@@ -865,20 +931,37 @@ AHCIPort::ScsiReadCapacity16(scsi_ccb* request)
|
|||||||
{
|
{
|
||||||
TRACE("AHCIPort::ScsiReadCapacity16 port %d\n", fIndex);
|
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;
|
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",
|
TRACE("SectorSize %" B_PRIu32 ", SectorCount 0x%" B_PRIx64 "\n",
|
||||||
fSectorSize, fSectorCount);
|
fSectorSize, fSectorCount);
|
||||||
|
|
||||||
|
memset(&scsiData, 0, sizeof(scsiData));
|
||||||
|
|
||||||
scsiData.block_size = B_HOST_TO_BENDIAN_INT32(fSectorSize);
|
scsiData.block_size = B_HOST_TO_BENDIAN_INT32(fSectorSize);
|
||||||
scsiData.lba = B_HOST_TO_BENDIAN_INT64(fSectorCount - 1);
|
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,
|
if (sg_memcpy(request->sg_list, request->sg_count, &scsiData,
|
||||||
sizeof(scsiData)) < B_OK) {
|
copySize) < B_OK) {
|
||||||
request->subsys_status = SCSI_DATA_RUN_ERR;
|
request->subsys_status = SCSI_DATA_RUN_ERR;
|
||||||
} else {
|
} else {
|
||||||
request->subsys_status = SCSI_REQ_CMP;
|
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);
|
gSCSI->finished(request, 1);
|
||||||
}
|
}
|
||||||
@@ -939,108 +1022,147 @@ AHCIPort::ScsiReadWrite(scsi_ccb* request, uint64 lba, size_t sectorCount,
|
|||||||
void
|
void
|
||||||
AHCIPort::ScsiUnmap(scsi_ccb* request, scsi_unmap_parameter_list* unmapBlocks)
|
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
|
// 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);
|
unmapBlocks->block_data_length) / sizeof(scsi_unmap_block_descriptor);
|
||||||
|
|
||||||
dprintf("TRIM SCSI:\n");
|
#ifdef DEBUG_TRIM
|
||||||
for (uint32 i = 0; i < scsiRangeCount; i++) {
|
dprintf("TRIM: AHCI: received a SCSI UNMAP command (blocks):\n");
|
||||||
dprintf("[%3" B_PRIu32 "] %" B_PRIu64 " : %" B_PRIu32 "\n", i,
|
for (uint32 i = 0; i < scsiRangeCount; i++) {
|
||||||
(uint64)B_BENDIAN_TO_HOST_INT64(unmapBlocks->blocks[i].lba),
|
dprintf("[%3" B_PRIu32 "] %" B_PRIu64 " : %" B_PRIu32 "\n", i,
|
||||||
(uint32)B_BENDIAN_TO_HOST_INT32(unmapBlocks->blocks[i].block_count));
|
(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;
|
if (scsiRangeCount == 0) {
|
||||||
uint32 scsiLastBlocks = 0;
|
request->subsys_status = SCSI_REQ_CMP;
|
||||||
uint32 maxLBARangeCount = fMaxTrimRangeBlocks * 512 / 8;
|
request->data_resid = 0;
|
||||||
// 512 bytes per range block, 8 bytes per range
|
request->device_status = SCSI_STATUS_GOOD;
|
||||||
|
gSCSI->finished(request, 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Split the SCSI ranges into ATA ranges as large as allowed.
|
size_t lbaRangeCount = 0;
|
||||||
// We assume that the SCSI unmap ranges cannot be merged together
|
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) {
|
size_t lbaRangesAllocatedSize = lbaRangeCount * sizeof(uint64);
|
||||||
// Determine how many LBA ranges we need for the next chunk
|
// Request data is transferred in 512-byte blocks
|
||||||
uint32 lbaRangeCount = 0;
|
if (lbaRangesAllocatedSize % 512 != 0) {
|
||||||
for (uint32 i = scsiIndex; i < scsiRangeCount; i++) {
|
lbaRangesAllocatedSize += 512 - (lbaRangesAllocatedSize % 512);
|
||||||
uint32 scsiBlocks = B_BENDIAN_TO_HOST_INT32(
|
}
|
||||||
unmapBlocks->blocks[i].block_count);
|
// Apply reported device limits
|
||||||
if (scsiBlocks == 0)
|
if (lbaRangesAllocatedSize > (size_t)fMaxTrimRangeBlocks * 512) {
|
||||||
break;
|
lbaRangesAllocatedSize = (size_t)fMaxTrimRangeBlocks * 512;
|
||||||
if (i == scsiIndex)
|
}
|
||||||
scsiBlocks -= scsiLastBlocks;
|
// 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;
|
MemoryDeleter deleter(lbaRanges);
|
||||||
if (lbaRangeCount >= maxLBARangeCount) {
|
|
||||||
lbaRangeCount = maxLBARangeCount;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (lbaRangeCount == 0)
|
|
||||||
break;
|
|
||||||
|
|
||||||
uint32 lbaRangesSize = lbaRangeCount * sizeof(uint64);
|
memset(lbaRanges, 0, lbaRangesAllocatedSize);
|
||||||
uint64* lbaRanges = (uint64*)malloc(lbaRangesSize);
|
// Entries with range length of 0 will be ignored
|
||||||
if (lbaRanges == NULL) {
|
uint32 lbaIndex = 0;
|
||||||
ERROR("out of memory when allocating %" B_PRIu32 " unmap ranges\n",
|
for (uint32 i = 0; i < scsiRangeCount; i++) {
|
||||||
lbaRangeCount);
|
uint64 lba = B_BENDIAN_TO_HOST_INT64(unmapBlocks->blocks[i].lba);
|
||||||
request->subsys_status = SCSI_REQ_ABORTED;
|
uint64 length = (uint32)B_BENDIAN_TO_HOST_INT32(
|
||||||
gSCSI->finished(request, 1);
|
unmapBlocks->blocks[i].block_count);
|
||||||
return;
|
|
||||||
|
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;
|
// Split into multiple requests if needed.
|
||||||
scsiIndex < scsiRangeCount && lbaIndex < lbaRangeCount;) {
|
// The number of entries in a request is limited by:
|
||||||
uint64 scsiOffset = B_BENDIAN_TO_HOST_INT64(
|
// - the maximum number of 512-byte blocks reported by the device
|
||||||
unmapBlocks->blocks[scsiIndex].lba) + scsiLastBlocks;
|
// - maximum possible value of the COUNT field
|
||||||
uint32 scsiBlocksLeft = B_BENDIAN_TO_HOST_INT32(
|
// - the size of our buffer
|
||||||
unmapBlocks->blocks[scsiIndex].block_count) - scsiLastBlocks;
|
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) {
|
#ifdef DEBUG_TRIM
|
||||||
// Ignore the rest of the ranges (they are empty)
|
dprintf("TRIM: AHCI: sending a DATA SET MANAGEMENT command"
|
||||||
scsiIndex = scsiRangeCount;
|
" to the device (blocks):\n");
|
||||||
break;
|
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) {
|
length -= ataRange;
|
||||||
uint16 blocks = scsiBlocksLeft > 65535
|
lba += ataRange;
|
||||||
? 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++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
request->data_resid = 0;
|
||||||
@@ -1301,9 +1423,15 @@ AHCIPort::ScsiExecuteRequest(scsi_ccb* request)
|
|||||||
scsi_unmap_parameter_list* unmapBlocks
|
scsi_unmap_parameter_list* unmapBlocks
|
||||||
= (scsi_unmap_parameter_list*)request->data;
|
= (scsi_unmap_parameter_list*)request->data;
|
||||||
if (unmapBlocks == NULL
|
if (unmapBlocks == NULL
|
||||||
|| B_BENDIAN_TO_HOST_INT16(cmd->length) != request->data_length
|
|| (uint16)B_BENDIAN_TO_HOST_INT16(cmd->length)
|
||||||
|| B_BENDIAN_TO_HOST_INT16(unmapBlocks->data_length)
|
!= request->data_length
|
||||||
!= request->data_length - 1) {
|
|| (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",
|
ERROR("%s port %d: invalid unmap parameter data length\n",
|
||||||
__func__, fIndex);
|
__func__, fIndex);
|
||||||
request->subsys_status = SCSI_REQ_ABORTED;
|
request->subsys_status = SCSI_REQ_ABORTED;
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ private:
|
|||||||
bool fPortReset;
|
bool fPortReset;
|
||||||
bool fError;
|
bool fError;
|
||||||
bool fTrimSupported;
|
bool fTrimSupported;
|
||||||
|
bool fTrimReturnsZeros;
|
||||||
uint32 fMaxTrimRangeBlocks;
|
uint32 fMaxTrimRangeBlocks;
|
||||||
|
|
||||||
volatile fis * fFIS;
|
volatile fis * fFIS;
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2008-2013, Axel Dörfler, axeld@pinc-software.de.
|
* Copyright 2021 David Sebek, dasebek@gmail.com
|
||||||
* Copyright 2002/03, Thomas Kurschel. All rights reserved.
|
* Copyright 2008-2013 Axel Dörfler, axeld@pinc-software.de
|
||||||
* Distributed under the terms of the MIT License.
|
* 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
|
static status_t
|
||||||
trim_device(das_driver_info* device, fs_trim_data* trimData)
|
trim_device(das_driver_info* device, fs_trim_data* trimData)
|
||||||
{
|
{
|
||||||
TRACE("trim_device()\n");
|
TRACE("trim_device()\n");
|
||||||
|
|
||||||
|
trimData->trimmed_size = 0;
|
||||||
|
|
||||||
scsi_ccb* request = device->scsi->alloc_ccb(device->scsi_device);
|
scsi_ccb* request = device->scsi->alloc_ccb(device->scsi_device);
|
||||||
if (request == NULL)
|
if (request == NULL)
|
||||||
return B_NO_MEMORY;
|
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++) {
|
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,
|
status_t status = sSCSIPeripheral->trim_device(device->scsi_periph_device,
|
||||||
request, (scsi_block_range*)&trimData->ranges[0],
|
request, blockRanges, trimData->range_count, &trimmedBlocks);
|
||||||
trimData->range_count);
|
|
||||||
|
|
||||||
device->scsi->free_ccb(request);
|
device->scsi->free_ccb(request);
|
||||||
if (status == B_OK)
|
// Some blocks may have been trimmed even if trim_device returns a failure
|
||||||
trimData->trimmed_size = trimmedSize;
|
trimData->trimmed_size = trimmedBlocks * device->block_size;
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@@ -415,7 +449,6 @@ das_ioctl(void* cookie, uint32 op, void* buffer, size_t length)
|
|||||||
case B_FLUSH_DRIVE_CACHE:
|
case B_FLUSH_DRIVE_CACHE:
|
||||||
return synchronize_cache(info);
|
return synchronize_cache(info);
|
||||||
|
|
||||||
#if 0
|
|
||||||
case B_TRIM_DEVICE:
|
case B_TRIM_DEVICE:
|
||||||
{
|
{
|
||||||
// We know the buffer is kernel-side because it has been
|
// 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));
|
ASSERT(IS_KERNEL_ADDRESS(buffer));
|
||||||
return trim_device(info, (fs_trim_data*)buffer);
|
return trim_device(info, (fs_trim_data*)buffer);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return sSCSIPeripheral->ioctl(handle->scsi_periph_handle, op,
|
return sSCSIPeripheral->ioctl(handle->scsi_periph_handle, op,
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2004-2013, Haiku, Inc. All RightsReserved.
|
* Copyright 2021 David Sebek, dasebek@gmail.com
|
||||||
* Copyright 2002-2003, Thomas Kurschel. All rights reserved.
|
* Copyright 2004-2013 Haiku, Inc.
|
||||||
*
|
* Copyright 2002-2003 Thomas Kurschel
|
||||||
* Distributed under the terms of the MIT License.
|
* All rights reserved. Distributed under the terms of the MIT License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
@@ -16,13 +16,320 @@
|
|||||||
#include "scsi_periph_int.h"
|
#include "scsi_periph_int.h"
|
||||||
|
|
||||||
|
|
||||||
status_t
|
// UNMAP command limits
|
||||||
periph_check_capacity(scsi_periph_device_info *device, scsi_ccb *request)
|
#define UNMAP_MAX_LBA_VALUE UINT64_MAX
|
||||||
{
|
#define UNMAP_MAX_BLOCK_COUNT_VALUE UINT32_MAX
|
||||||
scsi_res_read_capacity capacityResult;
|
#define UNMAP_MAX_DESCRIPTORS 4095
|
||||||
scsi_cmd_read_capacity *cmd = (scsi_cmd_read_capacity *)request->cdb;
|
// Limit imposed by the UNMAP command structure
|
||||||
uint64 capacity;
|
#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;
|
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;
|
status_t res;
|
||||||
|
|
||||||
SHOW_FLOW(3, "%p, %p", device, request);
|
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)
|
if (device->callbacks->set_capacity == NULL)
|
||||||
return B_OK;
|
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;
|
if (res == B_ERROR) {
|
||||||
request->data_length = sizeof(capacityResult);
|
SHOW_FLOW0(3, "READ CAPACITY 16 failed, trying READ CAPACITY 10");
|
||||||
request->cdb_length = sizeof(scsi_cmd_read_capacity);
|
res = read_capacity_10(device, request, &capacityInfo);
|
||||||
request->timeout = device->std_timeout;
|
}
|
||||||
request->sort = -1;
|
} else {
|
||||||
request->sg_list = NULL;
|
SHOW_FLOW0(3, "READ CAPACITY 10 tried first");
|
||||||
|
res = read_capacity_10(device, request, &capacityInfo);
|
||||||
|
|
||||||
memset(cmd, 0, sizeof(*cmd));
|
if (res == B_OK && capacityInfo.capacityFilled
|
||||||
cmd->opcode = SCSI_OP_READ_CAPACITY;
|
&& capacityInfo.lastLba == UINT32_MAX) {
|
||||||
// we don't set PMI (partial medium indicator) as we want the whole capacity;
|
SHOW_FLOW0(3, "Device is too large, trying READ CAPACITY 16");
|
||||||
// in this case, all other parameters must be zero
|
res = read_capacity_16(device, request, &capacityInfo);
|
||||||
|
}
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mutex_lock(&device->mutex);
|
uint64 capacity;
|
||||||
|
uint32 blockSize;
|
||||||
|
|
||||||
if (res == B_OK && request->data_resid == 0) {
|
if (capacityInfo.capacityFilled) {
|
||||||
capacity = B_BENDIAN_TO_HOST_INT32(capacityResult.lba);
|
capacity = capacityInfo.lastLba + 1;
|
||||||
|
blockSize = capacityInfo.blockSize;
|
||||||
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);
|
|
||||||
} else {
|
} else {
|
||||||
capacity = 0;
|
capacity = 0;
|
||||||
blockSize = 0;
|
blockSize = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
SHOW_FLOW(3, "capacity = %" B_PRId64 ", block_size = %" B_PRId32, capacity,
|
enum trim_command unmapCommand = TRIM_NONE;
|
||||||
blockSize);
|
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;
|
device->block_size = blockSize;
|
||||||
|
|
||||||
@@ -118,61 +454,341 @@ periph_check_capacity(scsi_periph_device_info *device, scsi_ccb *request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
status_t
|
static status_t
|
||||||
periph_trim_device(scsi_periph_device_info *device, scsi_ccb *request,
|
trim_unmap(scsi_periph_device_info* device, scsi_ccb* request,
|
||||||
scsi_block_range* ranges, uint32 rangeCount)
|
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_block_descriptor)
|
||||||
+ sizeof(scsi_unmap_parameter_list);
|
+ sizeof(scsi_unmap_parameter_list);
|
||||||
|
|
||||||
// TODO: check block limits VPD page
|
scsi_unmap_parameter_list* unmapList
|
||||||
// TODO: instead of failing, we should try to complete the request in
|
= (scsi_unmap_parameter_list*)malloc(unmapListAllocatedSize);
|
||||||
// several passes.
|
if (unmapList == NULL)
|
||||||
if (unmapBlockSize > 65536 || rangeCount == 0)
|
|
||||||
return B_BAD_VALUE;
|
|
||||||
|
|
||||||
scsi_unmap_parameter_list* unmapBlocks
|
|
||||||
= (scsi_unmap_parameter_list*)malloc(unmapBlockSize);
|
|
||||||
if (unmapBlocks == NULL)
|
|
||||||
return B_NO_MEMORY;
|
return B_NO_MEMORY;
|
||||||
|
|
||||||
MemoryDeleter deleter(unmapBlocks);
|
MemoryDeleter deleter(unmapList);
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
|
status_t status = B_OK;
|
||||||
|
uint32 descriptorIndex = 0;
|
||||||
|
uint64 trimmedBlocksInRequest = 0;
|
||||||
|
memset(unmapList, 0, unmapListAllocatedSize);
|
||||||
for (uint32 i = 0; i < rangeCount; i++) {
|
for (uint32 i = 0; i < rangeCount; i++) {
|
||||||
unmapBlocks->blocks[i].lba = B_HOST_TO_BENDIAN_INT64(
|
uint64 lba = ranges[i].lba;
|
||||||
ranges[i].offset / device->block_size);
|
uint64 length = ranges[i].size;
|
||||||
unmapBlocks->blocks[i].block_count = B_HOST_TO_BENDIAN_INT32(
|
|
||||||
ranges[i].size / device->block_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;
|
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->next_tag_action = 0;
|
||||||
device->preferred_ccb_size = preferredCcbSize;
|
device->preferred_ccb_size = preferredCcbSize;
|
||||||
device->rw10_enabled = true;
|
device->rw10_enabled = true;
|
||||||
|
device->unmap_command = TRIM_NONE;
|
||||||
|
device->max_unmap_lba_count = 0;
|
||||||
|
device->max_unmap_descriptor_count = 0;
|
||||||
|
|
||||||
// launch sync daemon
|
// launch sync daemon
|
||||||
status_t status = register_kernel_daemon(periph_sync_queue_daemon, device,
|
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
|
static status_t
|
||||||
vpd_page_inquiry(scsi_periph_device_info *device, uint8 page, void* data,
|
vpd_page_inquiry(scsi_periph_device_info* device, scsi_ccb* ccb,
|
||||||
uint16 length)
|
uint8 page, void* data, uint16 length)
|
||||||
{
|
{
|
||||||
SHOW_FLOW0(0, "");
|
SHOW_FLOW0(0, "");
|
||||||
|
|
||||||
scsi_ccb* ccb = device->scsi->alloc_ccb(device->scsi_device);
|
scsi_cmd_inquiry* cmd = (scsi_cmd_inquiry*)ccb->cdb;
|
||||||
if (ccb == NULL)
|
|
||||||
return B_NO_MEMORY;
|
|
||||||
|
|
||||||
scsi_cmd_inquiry *cmd = (scsi_cmd_inquiry *)ccb->cdb;
|
|
||||||
memset(cmd, 0, sizeof(scsi_cmd_inquiry));
|
memset(cmd, 0, sizeof(scsi_cmd_inquiry));
|
||||||
cmd->opcode = SCSI_OP_INQUIRY;
|
cmd->opcode = SCSI_OP_INQUIRY;
|
||||||
cmd->lun = ccb->target_lun;
|
cmd->lun = ccb->target_lun;
|
||||||
cmd->evpd = 1;
|
cmd->evpd = 1;
|
||||||
cmd->page_code = page;
|
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;
|
cmd->allocation_length = length;
|
||||||
|
|
||||||
ccb->flags = SCSI_DIR_IN;
|
ccb->flags = SCSI_DIR_IN;
|
||||||
ccb->cdb_length = sizeof(scsi_cmd_inquiry);
|
ccb->cdb_length = sizeof(scsi_cmd_inquiry);
|
||||||
|
|
||||||
ccb->sort = -1;
|
ccb->sort = -1;
|
||||||
ccb->timeout = device->std_timeout;
|
ccb->timeout = device->std_timeout;
|
||||||
|
|
||||||
ccb->data = (uint8*)data;
|
ccb->data = (uint8*)data;
|
||||||
ccb->sg_list = NULL;
|
|
||||||
ccb->data_length = length;
|
ccb->data_length = length;
|
||||||
|
ccb->sg_list = NULL;
|
||||||
|
|
||||||
status_t status = periph_safe_exec(device, ccb);
|
return periph_safe_exec(device, ccb);
|
||||||
|
|
||||||
device->scsi->free_ccb(ccb);
|
|
||||||
|
|
||||||
return status;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
status_t
|
status_t
|
||||||
vpd_page_get(scsi_periph_device_info *device, uint8 page, void* data,
|
vpd_page_get(scsi_periph_device_info* device, scsi_ccb* request,
|
||||||
uint16 length)
|
uint8 page, void* data, uint16 length)
|
||||||
{
|
{
|
||||||
SHOW_FLOW0(0, "");
|
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)
|
if (status != B_OK)
|
||||||
return status; // or B_BAD_VALUE
|
return status;
|
||||||
|
|
||||||
if (page == 0)
|
if (vpdPage->page_code != SCSI_PAGE_SUPPORTED_VPD)
|
||||||
return B_OK;
|
return B_ERROR;
|
||||||
|
|
||||||
scsi_page_list *list_data = (scsi_page_list*)data;
|
uint16 pageLength = min_c(vpdPage->page_length,
|
||||||
int page_length = min_c(list_data->page_length, length -
|
bufferLength - offsetof(scsi_page_list, pages));
|
||||||
offsetof(scsi_page_list, pages));
|
for (uint16 i = 0; i < pageLength; i++) {
|
||||||
for (int i = 0; i < page_length; i++) {
|
if (vpdPage->pages[i] == page)
|
||||||
if (list_data->pages[i] == page)
|
return vpd_page_inquiry(device, request, page, data, length);
|
||||||
return vpd_page_inquiry(device, page, data, length);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO buffer might be not big enough
|
|
||||||
|
|
||||||
return B_BAD_VALUE;
|
return B_BAD_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,14 @@
|
|||||||
#include "wrapper.h"
|
#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 {
|
typedef struct scsi_periph_device_info {
|
||||||
struct scsi_periph_handle_info *handles;
|
struct scsi_periph_handle_info *handles;
|
||||||
|
|
||||||
@@ -28,6 +36,10 @@ typedef struct scsi_periph_device_info {
|
|||||||
|
|
||||||
bool removable; // true, if device is removable
|
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;
|
uint32 block_size;
|
||||||
int32 preferred_ccb_size;
|
int32 preferred_ccb_size;
|
||||||
int32 rw10_enabled; // 10 byte r/w commands supported; access must be atomic
|
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_check_capacity(scsi_periph_device_info *device, scsi_ccb *ccb);
|
||||||
status_t periph_trim_device(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);
|
scsi_block_range* ranges, uint32 rangeCount, uint64* trimmedBlocks);
|
||||||
|
|
||||||
|
|
||||||
// device.c
|
// 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,
|
status_t periph_ioctl(scsi_periph_handle_info *handle, int op,
|
||||||
void *buf, size_t len);
|
void *buf, size_t len);
|
||||||
void periph_sync_queue_daemon(void *arg, int iteration);
|
void periph_sync_queue_daemon(void *arg, int iteration);
|
||||||
status_t vpd_page_get(scsi_periph_device_info *device, uint8 page, void* data,
|
status_t vpd_page_get(scsi_periph_device_info *device, scsi_ccb* request,
|
||||||
uint16 length);
|
uint8 page, void* data, uint16 length);
|
||||||
|
|
||||||
|
|
||||||
// scsi_periph.c
|
// scsi_periph.c
|
||||||
|
|||||||
Reference in New Issue
Block a user