Skip to content

Commit 66a1377

Browse files
add TRIM support
1 parent 0677a6f commit 66a1377

File tree

11 files changed

+215
-5
lines changed

11 files changed

+215
-5
lines changed

include/ahci.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,27 @@ typedef struct ahci_hba_cmd_tbl_t {
261261
ahci_hba_prdt_entry_t prdt_entry[1]; /**< PRDT entries (0–65535, flexible array) */
262262
} __attribute__((packed)) ahci_hba_cmd_tbl_t;
263263

264+
#define ATA_CMD_DATA_SET_MANAGEMENT 0x06
265+
266+
struct ahci_trim_range {
267+
uint64_t lba; /* 48-bit used */
268+
uint32_t sectors; /* 512-byte sectors */
269+
};
270+
271+
/* IDENTIFY DEVICE words of interest:
272+
* - w169 bit0: DATA SET MANAGEMENT / TRIM supported
273+
* - w69 bit14: Deterministic read after TRIM (DRAT)
274+
* - w69 bit5 : Read zeroes after TRIM (RZAT)
275+
* - w105 : Max 512B DSM parameter blocks per command (0 => 1)
276+
*/
277+
278+
typedef struct {
279+
bool has_trim;
280+
bool drat;
281+
bool rzat;
282+
uint16_t max_dsm_blocks; /* 512-byte blocks; 0 => 1 */
283+
} ahci_trim_caps;
284+
264285
/* ----------------------------- ATA identify offsets ----------------------------- */
265286

266287
/** @brief ATA IDENTIFY: device type offset (bytes). */
@@ -670,6 +691,12 @@ void scsi_extract_fixed_sense(const uint8_t* buf, scsi_sense_key_t* sense_key, s
670691
*/
671692
bool atapi_handle_check_condition(ahci_hba_port_t* port, ahci_hba_mem_t* abar, const char* function);
672693

694+
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);
695+
696+
ahci_trim_caps ahci_probe_trim_caps(const uint16_t *id_words);
697+
698+
bool storage_device_ahci_block_clear(void *dev, uint64_t lba, uint32_t sectors);
699+
673700
/* ----------------------------- Layout sanity checks ----------------------------- */
674701

675702
/** @brief Compile-time size check: command header must be 0x20 bytes. */

include/filesystem.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ typedef uint64_t (*get_free_space)(void*);
4444
/* Prototypes for block storage device drivers (see storage_device_t) */
4545
typedef int (*block_read)(void*, uint64_t, uint32_t, unsigned char*);
4646
typedef int (*block_write)(void*, uint64_t, uint32_t, const unsigned char*);
47+
typedef bool (*block_clear)(void*, uint64_t, uint32_t);
4748

4849
typedef enum fs_error_t {
4950
FS_ERR_NO_ERROR = 0,
@@ -298,6 +299,11 @@ typedef struct storage_device_t {
298299
*/
299300
block_write blockwrite;
300301

302+
/**
303+
* @brief Mark blocks as freed on the device
304+
*/
305+
block_clear blockclear;
306+
301307
/**
302308
* @brief The block size read and write operations.
303309
* This is usually a sector size on disk drives.
@@ -317,6 +323,12 @@ typedef struct storage_device_t {
317323
*/
318324
void* opaque2;
319325

326+
/**
327+
* @brief An opaque pointer value which can be given meaning by
328+
* the storage device driver.
329+
*/
330+
void* opaque3;
331+
320332
/**
321333
* @brief User interface strings, model name, capacity, type
322334
* as human-readable text.
@@ -557,8 +569,24 @@ int fs_truncate_file(fs_directory_entry_t* file, uint32_t length);
557569
*/
558570
int fs_read_file(fs_directory_entry_t* file, uint32_t start, uint32_t length, unsigned char* buffer);
559571

572+
/**
573+
* @brief Returns the free space in bytes for a path depending on the filesystem attached
574+
* @param path path to return space for
575+
* @return free space in bytes
576+
*/
560577
uint64_t fs_get_free_space(const char* path);
561578

579+
/**
580+
* @brief Mark blocks on a block storage as freed, as a hint to the storage device.
581+
* May be sent as TRIM or other similar command.
582+
* @param dev Storage device
583+
* @param start Start sector
584+
* @param bytes Number of bytes, must be a multiple of block size
585+
* @return nonzero on success, zero on failure or not supported. Lack of support
586+
* is not considered fatal.
587+
*/
588+
int storage_device_clear_blocks(void* dev, uint64_t start, uint32_t bytes);
589+
562590
/**
563591
* @brief POSIX style _open function
564592
*

include/retrofs.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,16 @@ bool rfs_read_device(rfs_t *rfs, uint64_t start_sectors, uint64_t size_bytes, vo
259259
*/
260260
bool rfs_write_device(rfs_t *rfs, uint64_t start_sectors, uint64_t size_bytes, const void *buffer);
261261

262+
/**
263+
* @brief Hint to the storage driver that we can clear blocks on the device, e.g.
264+
* TRIM or similar.
265+
* @param rfs Filesystem context
266+
* @param start_sectors Sector index (relative to the filesystem start)
267+
* @param size_bytes Number of bytes to write (must be a multiple of sector size).
268+
* @return true on success, false on error or not supported
269+
*/
270+
bool rfs_clear_device(rfs_t *rfs, uint64_t start_sectors, uint64_t size_bytes);
271+
262272
/**
263273
* @brief Find a contiguous extent of free sectors.
264274
*

src/block/ahci/main.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,19 +217,28 @@ void probe_port(ahci_hba_mem_t *abar)
217217
}
218218
sd->opaque1 = i;
219219
sd->opaque2 = (void*)abar;
220+
sd->opaque3 = NULL;
220221
sd->cache = NULL;
221222
sd->blockread = storage_device_ahci_block_read;
222223
sd->blockwrite = storage_device_ahci_block_write;
224+
sd->blockclear = storage_device_ahci_block_clear;
223225
sd->size = dt == AHCI_DEV_SATA ? ahci_read_size(&abar->ports[i], abar) : SIZE_MAX;
224226
make_unique_device_name(dt == AHCI_DEV_SATA ? "hd" : "cd", sd->name, sizeof(sd->name));
225227
sd->block_size = dt == AHCI_DEV_SATA ? HDD_SECTOR_SIZE : ATAPI_SECTOR_SIZE;
226228
/* Build end-user label */
227229
sd->ui.label[0] = 0;
228230
sd->ui.is_optical = false;
231+
bool has_trim = false;
229232
if (dt == AHCI_DEV_SATA) {
230233
uint8_t id_page[512] = {0};
231234
if (ahci_identify_page(&abar->ports[i], abar, id_page)) {
232235
build_sata_label(sd, id_page);
236+
ahci_trim_caps* caps = kmalloc(sizeof(ahci_trim_caps));
237+
if (caps) {
238+
*caps = ahci_probe_trim_caps((uint16_t *) id_page);
239+
sd->opaque3 = caps;
240+
has_trim = caps->has_trim;
241+
}
233242
} else {
234243
char size_str[24] = {0};
235244
humanise_capacity(size_str, sizeof(size_str), sd->size * sd->block_size);
@@ -244,7 +253,7 @@ void probe_port(ahci_hba_mem_t *abar)
244253
sd->ui.is_optical = true;
245254
}
246255
}
247-
kprintf("%s storage, Port #%d: %s (%d bit)\n", dt == AHCI_DEV_SATA ? "SATA" : "ATAPI", i, sd->ui.label, ahci_hba_supports_64b(abar) ? 64 : 32);
256+
kprintf("%s storage, Port #%d: %s (%d bit%s)\n", dt == AHCI_DEV_SATA ? "SATA" : "ATAPI", i, sd->ui.label, ahci_hba_supports_64b(abar) ? 64 : 32, has_trim ? ", TRIM" : "");
248257
register_storage_device(sd);
249258
storage_enable_cache(sd);
250259
}

src/block/ahci/storage_device.c

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ int storage_device_ahci_block_read(void* dev, uint64_t start, uint32_t bytes, un
99
uint32_t sectors = (bytes + sd->block_size - 1) / sd->block_size;
1010
unsigned char* end = buffer + bytes;
1111

12-
ahci_hba_mem_t* abar = (ahci_hba_mem_t*)sd->opaque2;
12+
ahci_hba_mem_t* abar = sd->opaque2;
1313
ahci_hba_port_t* port = &abar->ports[sd->opaque1];
1414

1515
const uint32_t max_per_cmd = 16;
@@ -51,6 +51,26 @@ int storage_device_ahci_block_read(void* dev, uint64_t start, uint32_t bytes, un
5151
}
5252
}
5353

54+
bool storage_device_ahci_block_clear(void *dev, uint64_t lba, uint32_t bytes) {
55+
storage_device_t* sd = (storage_device_t*)dev;
56+
if (!sd) {
57+
return 0;
58+
}
59+
60+
uint32_t sectors = (bytes + sd->block_size - 1) / sd->block_size;
61+
if (sectors < 1) {
62+
sectors = 1;
63+
}
64+
65+
ahci_hba_mem_t* abar = sd->opaque2;
66+
ahci_hba_port_t* port = &abar->ports[sd->opaque1];
67+
ahci_trim_caps* caps = sd->opaque3;
68+
if (caps && caps->has_trim) {
69+
return ahci_trim_one_range(port, abar, caps, lba, sectors) > 0;
70+
}
71+
return false;
72+
}
73+
5474
int storage_device_ahci_block_write(void* dev, uint64_t start, uint32_t bytes, const unsigned char* buffer) {
5575
storage_device_t* sd = (storage_device_t*)dev;
5676
if (!sd) {
@@ -62,7 +82,7 @@ int storage_device_ahci_block_write(void* dev, uint64_t start, uint32_t bytes, c
6282
sectors = 1;
6383
}
6484

65-
ahci_hba_mem_t* abar = (ahci_hba_mem_t*)sd->opaque2;
85+
ahci_hba_mem_t* abar = sd->opaque2;
6686
ahci_hba_port_t* port = &abar->ports[sd->opaque1];
6787

6888
const uint32_t max_per_cmd = 16;

src/block/ahci/trim.c

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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+

src/fs/filesystem.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,20 @@ uint64_t fs_get_free_space(const char* path)
355355
return dir && dir->responsible_driver && dir->responsible_driver->freespace ? dir->responsible_driver->freespace(dir) : 0;
356356
}
357357

358+
359+
int storage_device_clear_blocks(void* dev, uint64_t start, uint32_t bytes) {
360+
storage_device_t* sd = (storage_device_t*)dev;
361+
if (!sd) {
362+
return 0;
363+
}
364+
365+
if (sd->blockclear) {
366+
return sd->blockclear(sd, start, bytes);
367+
}
368+
return 0;
369+
}
370+
371+
358372
int read_storage_device(const char* name, uint64_t start_block, uint32_t bytes, unsigned char* data)
359373
{
360374
storage_device_t* cur = find_storage_device(name);

src/fs/retrofs/helpers.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,26 @@ bool rfs_write_device(rfs_t *rfs, uint64_t start_sectors, uint64_t size_bytes, c
3939
return write_storage_device(rfs->dev->name, volume_start + start_sectors, size_bytes, buffer);
4040
}
4141

42+
bool rfs_clear_device(rfs_t *rfs, uint64_t start_sectors, uint64_t size_bytes) {
43+
const uint64_t total_sectors = rfs->total_sectors;
44+
const uint64_t nsectors = size_bytes / RFS_SECTOR_SIZE;
45+
46+
if ((size_bytes % RFS_SECTOR_SIZE) != 0) {
47+
dprintf("rfs_clear_device: size not sector-aligned\n");
48+
fs_set_error(FS_ERR_OUTSIDE_VOLUME);
49+
return 0;
50+
}
51+
if (start_sectors + nsectors > total_sectors) {
52+
dprintf("rfs_clear_device: would write past end of device\n");
53+
fs_set_error(FS_ERR_OUTSIDE_VOLUME);
54+
return 0;
55+
}
56+
57+
uint64_t volume_start = rfs->start;
58+
return storage_device_clear_blocks(rfs->dev, volume_start + start_sectors, size_bytes);
59+
}
60+
61+
4262
bool rfs_locate_entry(rfs_t *info, fs_tree_t *tree, const char *name, uint64_t *out_sector, size_t *out_index, rfs_directory_entry_inner_t *out_entry_copy) {
4363
if (info == NULL || tree == NULL || name == NULL || name[0] == '\0') {
4464
fs_set_error(FS_ERR_INVALID_ARG);

src/fs/retrofs/unlink.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,11 @@ bool rfs_delete_directory_entry(fs_directory_entry_t *file) {
7373
}
7474

7575
if (found_idx != (size_t) - 1) {
76+
7677
/* Compact: shift following entries left by one within this block, then clear the tail slot. */
7778
if (found_idx < last_used) {
7879
size_t move_count = last_used - found_idx;
79-
memmove(&ents[found_idx], &ents[found_idx + 1],
80-
move_count * sizeof(rfs_directory_entry_t));
80+
memmove(&ents[found_idx], &ents[found_idx + 1], move_count * sizeof(rfs_directory_entry_t));
8181
}
8282

8383
/* Clear the last_used slot (now duplicated or original tail). */
@@ -142,6 +142,8 @@ bool rfs_unlink_file(void *dir, const char *name) {
142142
if (!rfs_mark_extent(info, on_disk.sector_start, on_disk.sector_length, false)) {
143143
return false;
144144
}
145+
rfs_clear_device(info, on_disk.sector_start, on_disk.sector_length * RFS_SECTOR_SIZE);
146+
145147
}
146148
return done;
147149
}

src/fs/retrofs/unlinkdirectory.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ bool rfs_unlink_dir(void *dir, const char *name) {
102102
uint64_t span = start_entry->sectors;
103103

104104
rfs_mark_extent(info, cur, span, false);
105+
rfs_clear_device(info, cur, span * RFS_SECTOR_SIZE);
105106

106107
cur = next;
107108
}

0 commit comments

Comments
 (0)