|
| 1 | +#include <kernel.h> |
| 2 | + |
| 3 | +extern uint8_t* aligned_read_buf; |
| 4 | + |
| 5 | +/// @brief Extract TRIM/DSM capability flags from IDENTIFY DEVICE. |
| 6 | +ahci_trim_caps ahci_probe_trim_caps(const uint16_t *id_words) { |
| 7 | + ahci_trim_caps caps = {0}; |
| 8 | + |
| 9 | + if (!id_words) { |
| 10 | + return caps; |
| 11 | + } |
| 12 | + |
| 13 | + uint16_t w69 = id_words[69]; |
| 14 | + uint16_t w105 = id_words[105]; |
| 15 | + uint16_t w169 = id_words[169]; |
| 16 | + |
| 17 | + caps.has_trim = (w169 & 0x0001) != 0; |
| 18 | + caps.drat = (w69 & 0x4000) != 0; |
| 19 | + caps.rzat = (w69 & 0x0020) != 0; |
| 20 | + caps.max_dsm_blocks = w105 != 0 ? w105 : 1; |
| 21 | + |
| 22 | + return caps; |
| 23 | +} |
| 24 | + |
| 25 | +/// @brief Trim a single contiguous LBA range. No batching/merging of disjoint ranges. |
| 26 | +/// @return true on success, or if TRIM unsupported (no-op); false on submission error. |
| 27 | +bool ahci_trim_one_range(ahci_hba_port_t *port, ahci_hba_mem_t *abar, const ahci_trim_caps *caps, uint64_t lba, uint32_t sectors) { |
| 28 | + if (sectors == 0) { |
| 29 | + return true; |
| 30 | + } |
| 31 | + if (!caps || !caps->has_trim) { |
| 32 | + /* No-op on devices without TRIM so callers need no special cases. */ |
| 33 | + return true; |
| 34 | + } |
| 35 | + |
| 36 | + /* One DSM block is 512 bytes, containing 64 entries of 8 bytes. |
| 37 | + * We only use a single entry per command here. Sector-count field is 16-bit. |
| 38 | + */ |
| 39 | + const uint32_t dsm_block_bytes = 512; |
| 40 | + const uint16_t max_entry_sectors = 65535; |
| 41 | + |
| 42 | + while (sectors != 0) { |
| 43 | + uint16_t chunk = (sectors > max_entry_sectors) ? max_entry_sectors : (uint16_t)sectors; |
| 44 | + |
| 45 | + memset(aligned_read_buf, 0, dsm_block_bytes); |
| 46 | + /* 48-bit LBA, little-endian, bytes 0..5 */ |
| 47 | + aligned_read_buf[0] = (uint8_t)(lba >> 0); |
| 48 | + aligned_read_buf[1] = (uint8_t)(lba >> 8); |
| 49 | + aligned_read_buf[2] = (uint8_t)(lba >> 16); |
| 50 | + aligned_read_buf[3] = (uint8_t)(lba >> 24); |
| 51 | + aligned_read_buf[4] = (uint8_t)(lba >> 32); |
| 52 | + aligned_read_buf[5] = (uint8_t)(lba >> 40); |
| 53 | + /* 16-bit sector count, little-endian, bytes 6..7 */ |
| 54 | + aligned_read_buf[6] = (uint8_t)(chunk & 0xff); |
| 55 | + aligned_read_buf[7] = (uint8_t)(chunk >> 8); |
| 56 | + |
| 57 | + GET_SLOT(port, abar); |
| 58 | + |
| 59 | + ahci_hba_cmd_header_t *command_header = get_cmdheader_for_slot(port, slot, true, false, 1); |
| 60 | + ahci_hba_cmd_tbl_t *cmdtbl = get_and_clear_cmdtbl(command_header); |
| 61 | + |
| 62 | + fill_prdt(cmdtbl, 0, aligned_read_buf, dsm_block_bytes, false); |
| 63 | + |
| 64 | + ahci_fis_reg_h2d_t *cfis = setup_reg_h2d(cmdtbl, FIS_TYPE_REG_H2D, ATA_CMD_DATA_SET_MANAGEMENT, 0x01 /* TRIM */); |
| 65 | + cfis->count_low = 1; |
| 66 | + cfis->count_high = 0; |
| 67 | + |
| 68 | + if (!issue_and_wait(port, abar, slot)) { |
| 69 | + return atapi_handle_check_condition(port, abar, "trim"); |
| 70 | + } |
| 71 | + |
| 72 | + lba += (uint64_t)chunk; |
| 73 | + sectors -= (uint32_t)chunk; |
| 74 | + } |
| 75 | + |
| 76 | + return true; |
| 77 | +} |
| 78 | + |
0 commit comments