diff options
author | Solomon Peachy <pizza@shaftnet.org> | 2024-04-11 11:54:02 -0400 |
---|---|---|
committer | Solomon Peachy <pizza@shaftnet.org> | 2024-04-22 15:46:30 -0400 |
commit | 886060475e25d04b9eb1753dbbaea0db8b78a0d4 (patch) | |
tree | 472b29d2dbb994cbfa1addac945f9043796614c6 /firmware | |
parent | 6cbcde13b9eb10b50724f956ba5696fc82ed7cf4 (diff) | |
download | rockbox-886060475e25d04b9eb1753dbbaea0db8b78a0d4.tar.gz rockbox-886060475e25d04b9eb1753dbbaea0db8b78a0d4.zip |
ata: Heavily rework sleep and poweroff logic
* Use of ata_disk_can_poweroff() was inverted, resulting in SATA SSDs
getting powered off but leaving _everything_ else on, including spinning
rust!
* Replace the can_poweroff() heuristic with a test for the mandatory
ATA power mgmt feature flag. Notably, the CF->SD adapters don't claim
to support this!
* Eliminate duplicated tests in sleep code
* Wrap all poweroff-related code with HAVE_ATA_POWER_OFF
* Don't ever use SLEEP command, only STANDBY_IMMEDIATE
* Gate call to STANDBY_IMMEDIATE behind a can_poweroff() test
* Prefer FLUSH_CACHE_EXT to FLUSH_CACHE where available.
* Improve SSD detection heuristics to any of these:
* Explicltly identifies as SSD (covers newer CF and SATA)
* TRIM support
* CFA compliant AND (CF level 0 OR high speed support)
* Report SSD detection in debug menu
Change-Id: I7fcb83b6d6eabddc11c64326a573b08ab85412b5
Diffstat (limited to 'firmware')
-rw-r--r-- | firmware/drivers/ata.c | 111 | ||||
-rw-r--r-- | firmware/export/ata.h | 52 |
2 files changed, 84 insertions, 79 deletions
diff --git a/firmware/drivers/ata.c b/firmware/drivers/ata.c index 1c85b7bd5f..9896fa87e0 100644 --- a/firmware/drivers/ata.c +++ b/firmware/drivers/ata.c | |||
@@ -58,6 +58,7 @@ | |||
58 | #define CMD_IDENTIFY 0xEC | 58 | #define CMD_IDENTIFY 0xEC |
59 | #define CMD_SLEEP 0xE6 | 59 | #define CMD_SLEEP 0xE6 |
60 | #define CMD_FLUSH_CACHE 0xE7 | 60 | #define CMD_FLUSH_CACHE 0xE7 |
61 | #define CMD_FLUSH_CACHE_EXT 0xEA | ||
61 | #define CMD_SET_FEATURES 0xEF | 62 | #define CMD_SET_FEATURES 0xEF |
62 | #define CMD_SECURITY_FREEZE_LOCK 0xF5 | 63 | #define CMD_SECURITY_FREEZE_LOCK 0xF5 |
63 | #ifdef HAVE_ATA_DMA | 64 | #ifdef HAVE_ATA_DMA |
@@ -105,7 +106,7 @@ static bool lba48 = false; /* set for 48 bit addressing */ | |||
105 | 106 | ||
106 | static long last_disk_activity = -1; | 107 | static long last_disk_activity = -1; |
107 | #ifdef HAVE_ATA_POWER_OFF | 108 | #ifdef HAVE_ATA_POWER_OFF |
108 | static long power_off_tick; | 109 | static long power_off_tick = 0; |
109 | #endif | 110 | #endif |
110 | 111 | ||
111 | static unsigned long total_sectors; | 112 | static unsigned long total_sectors; |
@@ -152,18 +153,10 @@ static inline bool ata_sleep_timed_out(void) | |||
152 | TIME_AFTER(current_tick, last_disk_activity + sleep_timeout); | 153 | TIME_AFTER(current_tick, last_disk_activity + sleep_timeout); |
153 | } | 154 | } |
154 | 155 | ||
155 | static inline void schedule_ata_power_off(void) | ||
156 | { | ||
157 | #ifdef HAVE_ATA_POWER_OFF | ||
158 | if (!ata_disk_can_poweroff()) | ||
159 | power_off_tick = current_tick + ATA_POWER_OFF_TIMEOUT; | ||
160 | #endif | ||
161 | } | ||
162 | |||
163 | static inline bool ata_power_off_timed_out(void) | 156 | static inline bool ata_power_off_timed_out(void) |
164 | { | 157 | { |
165 | #ifdef HAVE_ATA_POWER_OFF | 158 | #ifdef HAVE_ATA_POWER_OFF |
166 | return TIME_AFTER(current_tick, power_off_tick); | 159 | return power_off_tick && TIME_AFTER(current_tick, power_off_tick); |
167 | #else | 160 | #else |
168 | return false; | 161 | return false; |
169 | #endif | 162 | #endif |
@@ -230,6 +223,10 @@ static int ata_perform_wakeup(int state) | |||
230 | 223 | ||
231 | static int ata_perform_sleep(void) | 224 | static int ata_perform_sleep(void) |
232 | { | 225 | { |
226 | /* If device doesn't support PM features, don't try to sleep. */ | ||
227 | if (!ata_disk_can_poweroff()) | ||
228 | return 0; // XXX or return a failure? | ||
229 | |||
233 | logf("ata SLEEP %ld", current_tick); | 230 | logf("ata SLEEP %ld", current_tick); |
234 | 231 | ||
235 | ATA_OUT8(ATA_SELECT, ata_device); | 232 | ATA_OUT8(ATA_SELECT, ata_device); |
@@ -244,18 +241,10 @@ static int ata_perform_sleep(void) | |||
244 | - transitions to PM2:Standby | 241 | - transitions to PM2:Standby |
245 | - enters Standby_z power condition | 242 | - enters Standby_z power condition |
246 | 243 | ||
247 | This places the device into a state where power-off is safe, but | 244 | This places the device into a state where power-off is safe. We |
248 | it is not the lowest-theoretical power state -- that is SLEEP, but | 245 | will cut power at a later time. |
249 | that is bugged on some SSDs (FC1307A-based). | ||
250 | |||
251 | TODO: Is there a practical downside to using STANDBY_IMMEDIATE instead | ||
252 | of SLEEP, assuming the former spins down the drive? | ||
253 | */ | 246 | */ |
254 | if (ata_disk_isssd()) { | 247 | ATA_OUT8(ATA_COMMAND, CMD_STANDBY_IMMEDIATE); |
255 | ATA_OUT8(ATA_COMMAND, CMD_STANDBY_IMMEDIATE); | ||
256 | } else { | ||
257 | ATA_OUT8(ATA_COMMAND, CMD_SLEEP); | ||
258 | } | ||
259 | 248 | ||
260 | if (!wait_for_rdy()) { | 249 | if (!wait_for_rdy()) { |
261 | DEBUGF("ata_perform_sleep() - CMD failed\n"); | 250 | DEBUGF("ata_perform_sleep() - CMD failed\n"); |
@@ -267,11 +256,17 @@ static int ata_perform_sleep(void) | |||
267 | 256 | ||
268 | static int ata_perform_flush_cache(void) | 257 | static int ata_perform_flush_cache(void) |
269 | { | 258 | { |
270 | /* Don't issue the flush cache command if the device | 259 | uint8_t cmd; |
271 | doesn't support it, even though it's mandatory. | 260 | |
272 | */ | 261 | if (identify_info[83] & (1 << 13)) { |
273 | if (!(identify_info[83] & (1 << 12))) | 262 | cmd = CMD_FLUSH_CACHE_EXT; |
263 | } else if (identify_info[83] & (1 << 12)) { | ||
264 | cmd = CMD_FLUSH_CACHE; | ||
265 | } else { | ||
266 | /* If neither (mandatory!) command is supported | ||
267 | then don't issue it. */ | ||
274 | return 0; | 268 | return 0; |
269 | } | ||
275 | 270 | ||
276 | logf("ata FLUSH CACHE %ld", current_tick); | 271 | logf("ata FLUSH CACHE %ld", current_tick); |
277 | 272 | ||
@@ -282,7 +277,7 @@ static int ata_perform_flush_cache(void) | |||
282 | return -1; | 277 | return -1; |
283 | } | 278 | } |
284 | 279 | ||
285 | ATA_OUT8(ATA_COMMAND, CMD_FLUSH_CACHE); | 280 | ATA_OUT8(ATA_COMMAND, cmd); |
286 | 281 | ||
287 | if (!wait_for_rdy()) { | 282 | if (!wait_for_rdy()) { |
288 | DEBUGF("ata_perform_flush_cache() - CMD failed\n"); | 283 | DEBUGF("ata_perform_flush_cache() - CMD failed\n"); |
@@ -387,47 +382,6 @@ static ICODE_ATTR void copy_write_sectors(const unsigned char* buf, | |||
387 | } | 382 | } |
388 | #endif /* !ATA_OPTIMIZED_WRITING */ | 383 | #endif /* !ATA_OPTIMIZED_WRITING */ |
389 | 384 | ||
390 | int ata_disk_isssd(void) | ||
391 | { | ||
392 | /* | ||
393 | Offset 217 is "Nominal Rotation rate" | ||
394 | 0x0000 == Not reported | ||
395 | 0x0001 == Solid State | ||
396 | 0x0401 -> 0xffe == RPM | ||
397 | All others reserved | ||
398 | |||
399 | Some CF cards return 0x0100 (ie byteswapped 0x0001) so accept either. | ||
400 | However, this is a relatively recent change, and we can't rely on it, | ||
401 | especially for the FC1307A CF->SD adapters! | ||
402 | |||
403 | Offset 168 is "Nominal Form Factor" | ||
404 | all values >= 0x06 are guaranteed to be Solid State (mSATA, m.2, etc) | ||
405 | |||
406 | Offset 83 b2 and/or 86 b2 is set to show device implementes CFA commands | ||
407 | |||
408 | Offset 169 b0 is set to show device implements TRIM. | ||
409 | |||
410 | Offset 0 is 0x848a for CF, but that's not guaranteed, because reasons. | ||
411 | */ | ||
412 | return (identify_info[83] & (1<<2) || identify_info[86] & (1<<2) || | ||
413 | (identify_info[168] & 0x0f) >= 0x06 || | ||
414 | identify_info[169] & (1<<0) || | ||
415 | identify_info[217] == 0x0001 || identify_info[217] == 0x0100); | ||
416 | } | ||
417 | |||
418 | int ata_disk_can_poweroff(void) | ||
419 | { | ||
420 | /* Some SSDs don't like getting powered off, presumably because | ||
421 | in the real world they're not in removable form factors and | ||
422 | don't expect to have power removed. | ||
423 | |||
424 | In particular, mSATA, m.2, and MicroSSD are suspect. | ||
425 | */ | ||
426 | |||
427 | return ((identify_info[168] & 0x0f) < 0x06 || | ||
428 | (identify_info[168] & 0x0f) > 0x08); | ||
429 | } | ||
430 | |||
431 | static int ata_transfer_sectors(unsigned long start, | 385 | static int ata_transfer_sectors(unsigned long start, |
432 | int incount, | 386 | int incount, |
433 | void* inbuf, | 387 | void* inbuf, |
@@ -903,7 +857,10 @@ void ata_sleepnow(void) | |||
903 | if (ata_state == ATA_ON) { | 857 | if (ata_state == ATA_ON) { |
904 | if (!ata_perform_flush_cache() && !ata_perform_sleep()) { | 858 | if (!ata_perform_flush_cache() && !ata_perform_sleep()) { |
905 | ata_state = ATA_SLEEPING; | 859 | ata_state = ATA_SLEEPING; |
906 | schedule_ata_power_off(); | 860 | #ifdef HAVE_ATA_POWER_OFF |
861 | if (ata_disk_can_poweroff()) | ||
862 | power_off_tick = current_tick + ATA_POWER_OFF_TIMEOUT; | ||
863 | #endif | ||
907 | } | 864 | } |
908 | } | 865 | } |
909 | mutex_unlock(&ata_mtx); | 866 | mutex_unlock(&ata_mtx); |
@@ -1136,7 +1093,7 @@ static int set_features(void) | |||
1136 | unsigned char parameter; | 1093 | unsigned char parameter; |
1137 | } features[] = { | 1094 | } features[] = { |
1138 | { 83, 14, 0x03, 0 }, /* force PIO mode */ | 1095 | { 83, 14, 0x03, 0 }, /* force PIO mode */ |
1139 | { 83, 3, 0x05, 0x80 }, /* adv. power management: lowest w/o standby */ // TODO: What about FC1307A that doesn't advertise this properly? | 1096 | { 83, 3, 0x05, 0x80 }, /* adv. power management: lowest w/o standby */ |
1140 | { 83, 9, 0x42, 0x80 }, /* acoustic management: lowest noise */ | 1097 | { 83, 9, 0x42, 0x80 }, /* acoustic management: lowest noise */ |
1141 | { 82, 6, 0xaa, 0 }, /* enable read look-ahead */ | 1098 | { 82, 6, 0xaa, 0 }, /* enable read look-ahead */ |
1142 | #ifdef HAVE_ATA_DMA | 1099 | #ifdef HAVE_ATA_DMA |
@@ -1455,17 +1412,17 @@ int ata_event(long id, intptr_t data) | |||
1455 | the first case is frequently hit anyway. */ | 1412 | the first case is frequently hit anyway. */ |
1456 | if (LIKELY(id == Q_STORAGE_TICK)) { | 1413 | if (LIKELY(id == Q_STORAGE_TICK)) { |
1457 | /* won't see ATA_BOOT in here */ | 1414 | /* won't see ATA_BOOT in here */ |
1458 | int state = ata_state; | 1415 | if (ata_state != ATA_ON || !ata_sleep_timed_out()) { |
1459 | if (state != ATA_ON || !ata_sleep_timed_out()) { | 1416 | #ifdef HAVE_ATA_POWER_OFF |
1460 | if (state == ATA_SLEEPING && ata_power_off_timed_out()) { | 1417 | if (ata_state == ATA_SLEEPING && ata_power_off_timed_out()) { |
1418 | power_off_tick = 0; | ||
1461 | mutex_lock(&ata_mtx); | 1419 | mutex_lock(&ata_mtx); |
1462 | if (ata_state == ATA_SLEEPING) { | 1420 | logf("ata OFF %ld", current_tick); |
1463 | logf("ata OFF %ld", current_tick); | 1421 | ide_power_enable(false); |
1464 | ide_power_enable(false); | 1422 | ata_state = ATA_OFF; |
1465 | ata_state = ATA_OFF; | ||
1466 | } | ||
1467 | mutex_unlock(&ata_mtx); | 1423 | mutex_unlock(&ata_mtx); |
1468 | } | 1424 | } |
1425 | #endif | ||
1469 | STG_EVENT_ASSERT_ACTIVE(STORAGE_ATA); | 1426 | STG_EVENT_ASSERT_ACTIVE(STORAGE_ATA); |
1470 | } | 1427 | } |
1471 | } | 1428 | } |
diff --git a/firmware/export/ata.h b/firmware/export/ata.h index 7c7c60e898..62c9467643 100644 --- a/firmware/export/ata.h +++ b/firmware/export/ata.h | |||
@@ -166,10 +166,58 @@ long ata_last_disk_activity(void); | |||
166 | int ata_spinup_time(void); /* ticks */ | 166 | int ata_spinup_time(void); /* ticks */ |
167 | 167 | ||
168 | /* Returns 1 if drive is solid-state */ | 168 | /* Returns 1 if drive is solid-state */ |
169 | int ata_disk_isssd(void); | 169 | static inline int ata_disk_isssd(void) |
170 | { | ||
171 | unsigned short *identify_info = ata_get_identify(); | ||
172 | /* | ||
173 | Offset 217 is "Nominal Rotation rate" | ||
174 | 0x0000 == Not reported | ||
175 | 0x0001 == Solid State | ||
176 | 0x0401 -> 0xffe == RPM | ||
177 | All others reserved | ||
178 | |||
179 | Some CF cards return 0x0100 (ie byteswapped 0x0001) so accept either. | ||
180 | However, this is a relatively recent change, and we can't rely on it, | ||
181 | especially for the FC1307A CF->SD adapters! | ||
182 | |||
183 | Offset 168 is "Nominal Form Factor" | ||
184 | all values >= 0x06 are guaranteed to be Solid State (mSATA, m.2, etc) | ||
185 | |||
186 | Offset 169 b0 is set to show device implements TRIM. | ||
187 | |||
188 | Unreliable mechanisms: | ||
189 | |||
190 | Offset 83 b2 shows device implements CFA commands. | ||
191 | However microdrives pose a problem as they support CFA but are not | ||
192 | SSD. | ||
193 | |||
194 | Offset 160 b15 indicates support for CF+ power level 1, if not set | ||
195 | then device is standard flash CF. However this is not foolproof | ||
196 | as newer CF cards may support it for extra performance. | ||
197 | |||
198 | Offset 163 shows CF Advanced timing modes; microdrive seems to | ||
199 | report 0, but all others (including iFlash) report higher! | ||
200 | |||
201 | So if device support CFA _AND_ reports higher speeds modes, it is SSD. | ||
202 | |||
203 | */ | ||
204 | return ( (identify_info[217] == 0x0001 || identify_info[217] == 0x0100) /* "Solid state" rotational rate */ | ||
205 | || ((identify_info[168] & 0x0f) >= 0x06) /* Explicit SSD form factors */ | ||
206 | || (identify_info[169] & (1<<0)) /* TRIM supported */ | ||
207 | || ((identify_info[83] & (1<<2)) && /* CFA compliant */ | ||
208 | (((identify_info[160] & (1<<15)) == 0) || /* CF level 0 */ | ||
209 | (identify_info[163] > 0))) /* Advanced timing modes */ | ||
210 | ); | ||
211 | } | ||
170 | 212 | ||
171 | /* Returns 1 if the drive can be powered off safely */ | 213 | /* Returns 1 if the drive can be powered off safely */ |
172 | int ata_disk_can_poweroff(void); | 214 | static inline int ata_disk_can_poweroff(void) |
215 | { | ||
216 | unsigned short *identify_info = ata_get_identify(); | ||
217 | /* Only devices that claim to support PM can be safely powered off. | ||
218 | This notably excludes the various SD adapters! */ | ||
219 | return (identify_info[82] & (1<<3) && identify_info[85] & (1<<3)); | ||
220 | } | ||
173 | 221 | ||
174 | #ifdef HAVE_ATA_DMA | 222 | #ifdef HAVE_ATA_DMA |
175 | /* Returns current DMA mode */ | 223 | /* Returns current DMA mode */ |