diff --git a/headers/private/drivers/scsi_cmds.h b/headers/private/drivers/scsi_cmds.h index 7d3a1e2164..160ce884d8 100644 --- a/headers/private/drivers/scsi_cmds.h +++ b/headers/private/drivers/scsi_cmds.h @@ -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; diff --git a/headers/private/drivers/scsi_periph.h b/headers/private/drivers/scsi_periph.h index 9d470c6c1a..a6e78f2a37 100644 --- a/headers/private/drivers/scsi_periph.h +++ b/headers/private/drivers/scsi_periph.h @@ -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 diff --git a/src/add-ons/kernel/bus_managers/ata/ATADevice.cpp b/src/add-ons/kernel/bus_managers/ata/ATADevice.cpp index 41f633da5a..d8d19f77a5 100644 --- a/src/add-ons/kernel/bus_managers/ata/ATADevice.cpp +++ b/src/add-ons/kernel/bus_managers/ata/ATADevice.cpp @@ -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; } diff --git a/src/add-ons/kernel/busses/scsi/ahci/ahci_defs.h b/src/add-ons/kernel/busses/scsi/ahci/ahci_defs.h index b016dab232..d222281ce1 100644 --- a/src/add-ons/kernel/busses/scsi/ahci/ahci_defs.h +++ b/src/add-ons/kernel/busses/scsi/ahci/ahci_defs.h @@ -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 diff --git a/src/add-ons/kernel/busses/scsi/ahci/ahci_port.cpp b/src/add-ons/kernel/busses/scsi/ahci/ahci_port.cpp index bf5f662b7d..29841b52e6 100644 --- a/src/add-ons/kernel/busses/scsi/ahci/ahci_port.cpp +++ b/src/add-ons/kernel/busses/scsi/ahci/ahci_port.cpp @@ -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; diff --git a/src/add-ons/kernel/busses/scsi/ahci/ahci_port.h b/src/add-ons/kernel/busses/scsi/ahci/ahci_port.h index 0b8c802905..1622be9926 100644 --- a/src/add-ons/kernel/busses/scsi/ahci/ahci_port.h +++ b/src/add-ons/kernel/busses/scsi/ahci/ahci_port.h @@ -86,6 +86,7 @@ private: bool fPortReset; bool fError; bool fTrimSupported; + bool fTrimReturnsZeros; uint32 fMaxTrimRangeBlocks; volatile fis * fFIS; diff --git a/src/add-ons/kernel/drivers/disk/scsi/scsi_disk/scsi_disk.cpp b/src/add-ons/kernel/drivers/disk/scsi/scsi_disk/scsi_disk.cpp index 0d6b1188df..eba0d5ccf4 100644 --- a/src/add-ons/kernel/drivers/disk/scsi/scsi_disk/scsi_disk.cpp +++ b/src/add-ons/kernel/drivers/disk/scsi/scsi_disk/scsi_disk.cpp @@ -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, diff --git a/src/add-ons/kernel/generic/scsi_periph/block.cpp b/src/add-ons/kernel/generic/scsi_periph/block.cpp index 000726e7d9..e9b4d2dc73 100644 --- a/src/add-ons/kernel/generic/scsi_periph/block.cpp +++ b/src/add-ons/kernel/generic/scsi_periph/block.cpp @@ -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; + } +} diff --git a/src/add-ons/kernel/generic/scsi_periph/device.cpp b/src/add-ons/kernel/generic/scsi_periph/device.cpp index 5d8ab33055..c6bcb83c07 100644 --- a/src/add-ons/kernel/generic/scsi_periph/device.cpp +++ b/src/add-ons/kernel/generic/scsi_periph/device.cpp @@ -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, diff --git a/src/add-ons/kernel/generic/scsi_periph/io.cpp b/src/add-ons/kernel/generic/scsi_periph/io.cpp index f3d8bcdc1a..6bce83f90d 100644 --- a/src/add-ons/kernel/generic/scsi_periph/io.cpp +++ b/src/add-ons/kernel/generic/scsi_periph/io.cpp @@ -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; } diff --git a/src/add-ons/kernel/generic/scsi_periph/scsi_periph_int.h b/src/add-ons/kernel/generic/scsi_periph/scsi_periph_int.h index 29e4ba6d51..8a25e1f5b9 100644 --- a/src/add-ons/kernel/generic/scsi_periph/scsi_periph_int.h +++ b/src/add-ons/kernel/generic/scsi_periph/scsi_periph_int.h @@ -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