summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSolomon Peachy <pizza@shaftnet.org>2024-10-23 14:16:15 -0400
committerSolomon Peachy <pizza@shaftnet.org>2024-10-31 12:51:54 -0400
commite829ea9a5ea05c6dedd91f741f91cb8723e50b19 (patch)
treebb554a4ec31b3b5501189993de990d2419306c2f
parent825e4069655065ffd49bcc9ec64b53f1225e8186 (diff)
downloadrockbox-e829ea9a5ea05c6dedd91f741f91cb8723e50b19.tar.gz
rockbox-e829ea9a5ea05c6dedd91f741f91cb8723e50b19.zip
ata: Rework how flushing, sleeping, and power off interacts
* FLUSH_EXT is used if featureflag is set and we are using LBA48 (unconditionally used for CE-ATA on ipod6g) * FLUSH is used if featureflag is set (ATA6+) or if device claims to be ATA5+ * Rename ata_disk_can_power_off() to ata_disk_can_sleep() as that is what it actually tests for. Only use it to gate issuing the STANDBY IMMEDIATE command. * Restore behavior of ata_disk_is_active() to return 1 if drive is "spinning" or powered up. * Allow poweroff if drive claims PM support OR we are able to issue FLUSH/FLUSH_EXT commands. * Added ata_flush() to explicitly trigger a flush operation, and hook it up to storage_flush() in the device shutdown path. (Flushes were only previously used in the storage device power management path) * After issuing all settings, re-issue IDENTIFY_DEVICE to make sure it reflects everything we've enabled. * Update manual section on Flash/SSD mods. Change-Id: I6770a54ef3a87f4c47120bcb96c944a6652f1bf4
-rw-r--r--apps/debug_menu.c2
-rw-r--r--firmware/drivers/ata.c52
-rw-r--r--firmware/export/ata.h6
-rw-r--r--firmware/export/storage.h2
-rw-r--r--firmware/storage.c2
-rw-r--r--firmware/target/arm/s5l8702/ipod6g/storage_ata-6g.c103
-rw-r--r--manual/getting_started/installation.tex9
7 files changed, 110 insertions, 66 deletions
diff --git a/apps/debug_menu.c b/apps/debug_menu.c
index 57c51e7b54..6f30d00d49 100644
--- a/apps/debug_menu.c
+++ b/apps/debug_menu.c
@@ -1481,6 +1481,8 @@ static int disk_callback(int btn, struct gui_synclist *lists)
1481 i = identify_info[83] & (1<<9); 1481 i = identify_info[83] & (1<<9);
1482 simplelist_addline( 1482 simplelist_addline(
1483 "Noise mgmt: %s", i ? "enabled" : "unsupported"); 1483 "Noise mgmt: %s", i ? "enabled" : "unsupported");
1484 simplelist_addline(
1485 "Flush cache: %s", identify_info[83] & (1<<13) ? "extended" : identify_info[83] & (1<<12) ? "standard" : identify_info[80] >= (1<<5) ? "ATA-5" : "unsupported");
1484 i = identify_info[82] & (1<<6); 1486 i = identify_info[82] & (1<<6);
1485 simplelist_addline( 1487 simplelist_addline(
1486 "Read-ahead: %s", i ? "enabled" : "unsupported"); 1488 "Read-ahead: %s", i ? "enabled" : "unsupported");
diff --git a/firmware/drivers/ata.c b/firmware/drivers/ata.c
index c995f6de85..8df9acb9da 100644
--- a/firmware/drivers/ata.c
+++ b/firmware/drivers/ata.c
@@ -103,6 +103,7 @@ static long sleep_timeout = 5*HZ;
103#ifdef HAVE_LBA48 103#ifdef HAVE_LBA48
104static bool lba48 = false; /* set for 48 bit addressing */ 104static bool lba48 = false; /* set for 48 bit addressing */
105#endif 105#endif
106static bool canflush = true;
106 107
107static long last_disk_activity = -1; 108static long last_disk_activity = -1;
108#ifdef HAVE_ATA_POWER_OFF 109#ifdef HAVE_ATA_POWER_OFF
@@ -141,11 +142,6 @@ static inline void keep_ata_active(void)
141 last_disk_activity = current_tick; 142 last_disk_activity = current_tick;
142} 143}
143 144
144static inline void schedule_ata_sleep(long from_now)
145{
146 last_disk_activity = current_tick - sleep_timeout + from_now;
147}
148
149static inline bool ata_sleep_timed_out(void) 145static inline bool ata_sleep_timed_out(void)
150{ 146{
151 return sleep_timeout && 147 return sleep_timeout &&
@@ -223,7 +219,7 @@ static int ata_perform_wakeup(int state)
223static int ata_perform_sleep(void) 219static int ata_perform_sleep(void)
224{ 220{
225 /* If device doesn't support PM features, don't try to sleep. */ 221 /* If device doesn't support PM features, don't try to sleep. */
226 if (!ata_disk_can_poweroff()) 222 if (!ata_disk_can_sleep())
227 return 0; // XXX or return a failure? 223 return 0; // XXX or return a failure?
228 224
229 logf("ata SLEEP %ld", current_tick); 225 logf("ata SLEEP %ld", current_tick);
@@ -257,13 +253,18 @@ static int ata_perform_flush_cache(void)
257{ 253{
258 uint8_t cmd; 254 uint8_t cmd;
259 255
260 if (identify_info[83] & (1 << 13)) { 256 if (!canflush) {
261 cmd = CMD_FLUSH_CACHE_EXT; 257 return 0;
258 } else if (lba48 && identify_info[83] & (1 << 13)) {
259 cmd = CMD_FLUSH_CACHE_EXT; /* Flag, optional, ATA-6 and up, for use with LBA48 devices */
262 } else if (identify_info[83] & (1 << 12)) { 260 } else if (identify_info[83] & (1 << 12)) {
263 cmd = CMD_FLUSH_CACHE; 261 cmd = CMD_FLUSH_CACHE; /* Flag, mandatory, ATA-6 and up */
262 } else if (identify_info[80] >= (1 << 5)) { /* Use >= instead of '&' because bits lower than the latest standard we support don't have to be set */
263 cmd = CMD_FLUSH_CACHE; /* No flag, mandatory, ATA-5 (Optional for ATA-4) */
264 } else { 264 } else {
265 /* If neither (mandatory!) command is supported 265 /* If neither (mandatory!) command is supported
266 then don't issue it. */ 266 then don't issue it. */
267 canflush = 0;
267 return 0; 268 return 0;
268 } 269 }
269 270
@@ -286,6 +287,16 @@ static int ata_perform_flush_cache(void)
286 return 0; 287 return 0;
287} 288}
288 289
290int ata_flush(void)
291{
292 if (ata_state >= ATA_SPINUP) {
293 mutex_lock(&ata_mtx);
294 ata_perform_flush_cache();
295 mutex_unlock(&ata_mtx);
296 }
297 return 0;
298}
299
289static ICODE_ATTR int wait_for_start_of_transfer(void) 300static ICODE_ATTR int wait_for_start_of_transfer(void)
290{ 301{
291 if (!wait_for_bsy()) 302 if (!wait_for_bsy())
@@ -844,7 +855,7 @@ void ata_spindown(int seconds)
844 855
845bool ata_disk_is_active(void) 856bool ata_disk_is_active(void)
846{ 857{
847 return ata_disk_can_poweroff() ? (ata_state >= ATA_SPINUP) : 0; 858 return (ata_state >= ATA_SPINUP);
848} 859}
849 860
850void ata_sleepnow(void) 861void ata_sleepnow(void)
@@ -856,8 +867,9 @@ void ata_sleepnow(void)
856 if (!ata_perform_flush_cache() && !ata_perform_sleep()) { 867 if (!ata_perform_flush_cache() && !ata_perform_sleep()) {
857 ata_state = ATA_SLEEPING; 868 ata_state = ATA_SLEEPING;
858#ifdef HAVE_ATA_POWER_OFF 869#ifdef HAVE_ATA_POWER_OFF
859 if (ata_disk_can_poweroff()) 870 if (ata_disk_can_sleep() || canflush) {
860 power_off_tick = current_tick + ATA_POWER_OFF_TIMEOUT; 871 power_off_tick = current_tick + ATA_POWER_OFF_TIMEOUT;
872 }
861#endif 873#endif
862 } 874 }
863 } 875 }
@@ -967,6 +979,9 @@ static int perform_soft_reset(void)
967 if (set_multiple_mode(multisectors)) 979 if (set_multiple_mode(multisectors))
968 return -3; 980 return -3;
969 981
982 if (identify())
983 return -2;
984
970 if (freeze_lock()) 985 if (freeze_lock())
971 return -4; 986 return -4;
972 987
@@ -1017,6 +1032,9 @@ static int ata_power_on(void)
1017 if (set_multiple_mode(multisectors)) 1032 if (set_multiple_mode(multisectors))
1018 return -3; 1033 return -3;
1019 1034
1035 if (identify())
1036 return -2;
1037
1020 if (freeze_lock()) 1038 if (freeze_lock())
1021 return -4; 1039 return -4;
1022 1040
@@ -1255,7 +1273,6 @@ int STORAGE_INIT_ATTR ata_init(void)
1255 } 1273 }
1256 1274
1257 rc = identify(); 1275 rc = identify();
1258
1259 if (rc) { 1276 if (rc) {
1260 rc = -40 + rc; 1277 rc = -40 + rc;
1261 goto error; 1278 goto error;
@@ -1280,13 +1297,12 @@ int STORAGE_INIT_ATTR ata_init(void)
1280#endif /* HAVE_LBA48 */ 1297#endif /* HAVE_LBA48 */
1281 1298
1282 rc = freeze_lock(); 1299 rc = freeze_lock();
1283
1284 if (rc) { 1300 if (rc) {
1285 rc = -50 + rc; 1301 rc = -50 + rc;
1286 goto error; 1302 goto error;
1287 } 1303 }
1288 1304
1289 rc = set_features(); // rror codes are between -1 and -49 1305 rc = set_features(); // error codes are between -1 and -49
1290 if (rc) { 1306 if (rc) {
1291 rc = -60 + rc; 1307 rc = -60 + rc;
1292 goto error; 1308 goto error;
@@ -1325,6 +1341,12 @@ int STORAGE_INIT_ATTR ata_init(void)
1325 if (rc) 1341 if (rc)
1326 rc = -100 + rc; 1342 rc = -100 + rc;
1327 1343
1344 rc = identify();
1345 if (rc) {
1346 rc = -40 + rc;
1347 goto error;
1348 }
1349
1328error: 1350error:
1329 mutex_unlock(&ata_mtx); 1351 mutex_unlock(&ata_mtx);
1330 return rc; 1352 return rc;
@@ -1440,7 +1462,7 @@ int ata_event(long id, intptr_t data)
1440 ata_sleepnow(); 1462 ata_sleepnow();
1441 } 1463 }
1442 else if (id == Q_STORAGE_SLEEP) { 1464 else if (id == Q_STORAGE_SLEEP) {
1443 schedule_ata_sleep(HZ/5); 1465 last_disk_activity = current_tick - sleep_timeout + HZ / 5;
1444 } 1466 }
1445#ifndef USB_NONE 1467#ifndef USB_NONE
1446 else if (id == SYS_USB_CONNECTED) { 1468 else if (id == SYS_USB_CONNECTED) {
diff --git a/firmware/export/ata.h b/firmware/export/ata.h
index 0d84a91524..dfa73eebf5 100644
--- a/firmware/export/ata.h
+++ b/firmware/export/ata.h
@@ -208,8 +208,8 @@ static inline int ata_disk_isssd(void)
208 ); 208 );
209} 209}
210 210
211/* Returns 1 if the drive can be powered off safely */ 211/* Returns 1 if the drive supports power management commands */
212static inline int ata_disk_can_poweroff(void) 212static inline int ata_disk_can_sleep(void)
213{ 213{
214 unsigned short *identify_info = ata_get_identify(); 214 unsigned short *identify_info = ata_get_identify();
215 /* Only devices that claim to support PM can be safely powered off. 215 /* Only devices that claim to support PM can be safely powered off.
@@ -217,6 +217,8 @@ static inline int ata_disk_can_poweroff(void)
217 return (identify_info[82] & (1<<3) && identify_info[85] & (1<<3)); 217 return (identify_info[82] & (1<<3) && identify_info[85] & (1<<3));
218} 218}
219 219
220int ata_flush(void);
221
220#ifdef HAVE_ATA_DMA 222#ifdef HAVE_ATA_DMA
221/* Returns current DMA mode */ 223/* Returns current DMA mode */
222int ata_get_dma_mode(void); 224int ata_get_dma_mode(void);
diff --git a/firmware/export/storage.h b/firmware/export/storage.h
index e2ae4056be..f97cbf34d5 100644
--- a/firmware/export/storage.h
+++ b/firmware/export/storage.h
@@ -174,7 +174,7 @@ static inline void storage_sleep(void) {};
174 #define storage_disk_is_active() ata_disk_is_active() 174 #define storage_disk_is_active() ata_disk_is_active()
175 #define storage_soft_reset() ata_soft_reset() 175 #define storage_soft_reset() ata_soft_reset()
176 #ifdef HAVE_STORAGE_FLUSH 176 #ifdef HAVE_STORAGE_FLUSH
177 #define storage_flush() (void)0 177 #define storage_flush() ata_flush()
178 #endif 178 #endif
179 #define storage_last_disk_activity() ata_last_disk_activity() 179 #define storage_last_disk_activity() ata_last_disk_activity()
180 #define storage_spinup_time() ata_spinup_time() 180 #define storage_spinup_time() ata_spinup_time()
diff --git a/firmware/storage.c b/firmware/storage.c
index da3e06146d..2d4490369e 100644
--- a/firmware/storage.c
+++ b/firmware/storage.c
@@ -555,7 +555,7 @@ int storage_flush(void)
555 int rc=0; 555 int rc=0;
556 556
557#if (CONFIG_STORAGE & STORAGE_ATA) 557#if (CONFIG_STORAGE & STORAGE_ATA)
558 //if ((rc=ata_flush())) return rc; 558 if ((rc=ata_flush())) return rc;
559#endif 559#endif
560 560
561#if (CONFIG_STORAGE & STORAGE_MMC) 561#if (CONFIG_STORAGE & STORAGE_MMC)
diff --git a/firmware/target/arm/s5l8702/ipod6g/storage_ata-6g.c b/firmware/target/arm/s5l8702/ipod6g/storage_ata-6g.c
index 87450aca73..21c6f3f7c0 100644
--- a/firmware/target/arm/s5l8702/ipod6g/storage_ata-6g.c
+++ b/firmware/target/arm/s5l8702/ipod6g/storage_ata-6g.c
@@ -83,6 +83,7 @@ static uint32_t ata_dma_flags;
83static long ata_last_activity_value = -1; 83static long ata_last_activity_value = -1;
84static long ata_sleep_timeout = 7 * HZ; 84static long ata_sleep_timeout = 7 * HZ;
85static bool ata_powered; 85static bool ata_powered;
86static bool canflush = true;
86static struct semaphore mmc_wakeup; 87static struct semaphore mmc_wakeup;
87static struct semaphore mmc_comp_wakeup; 88static struct semaphore mmc_comp_wakeup;
88static int spinup_time = 0; 89static int spinup_time = 0;
@@ -578,7 +579,7 @@ static void ata_set_active(void)
578 579
579bool ata_disk_is_active(void) 580bool ata_disk_is_active(void)
580{ 581{
581 return ata_disk_can_poweroff() ? ata_powered : 0; 582 return ata_powered;
582} 583}
583 584
584static int ata_set_feature(uint32_t feature, uint32_t param) 585static int ata_set_feature(uint32_t feature, uint32_t param)
@@ -745,6 +746,8 @@ static int ata_power_up(void)
745 ata_dma = param ? true : false; 746 ata_dma = param ? true : false;
746 dma_mode = param; 747 dma_mode = param;
747 PASS_RC(ata_set_feature(0x03, param), 3, 4); /* Transfer mode */ 748 PASS_RC(ata_set_feature(0x03, param), 3, 4); /* Transfer mode */
749
750 /* SET_FEATURE only supported on PATA, not CE-ATA */
748 if (ata_identify_data[82] & BIT(5)) 751 if (ata_identify_data[82] & BIT(5))
749 PASS_RC(ata_set_feature(0x02, 0), 3, 5); /* Enable volatile write cache */ 752 PASS_RC(ata_set_feature(0x02, 0), 3, 5); /* Enable volatile write cache */
750 if (ata_identify_data[82] & BIT(6)) 753 if (ata_identify_data[82] & BIT(6))
@@ -753,7 +756,10 @@ static int ata_power_up(void)
753 PASS_RC(ata_set_feature(0x05, 0x80), 3, 7); /* Enable lowest power mode w/o standby */ 756 PASS_RC(ata_set_feature(0x05, 0x80), 3, 7); /* Enable lowest power mode w/o standby */
754 if (ata_identify_data[83] & BIT(9)) 757 if (ata_identify_data[83] & BIT(9))
755 PASS_RC(ata_set_feature(0x42, 0x80), 3, 8); /* Enable lowest noise mode */ 758 PASS_RC(ata_set_feature(0x42, 0x80), 3, 8); /* Enable lowest noise mode */
759
760 PASS_RC(ata_identify(ata_identify_data), 3, 9); /* Finally, re-read identify info */
756 } 761 }
762
757 spinup_time = current_tick - spinup_start; 763 spinup_time = current_tick - spinup_start;
758 764
759 ata_total_sectors = (ata_identify_data[61] << 16) | ata_identify_data[60]; 765 ata_total_sectors = (ata_identify_data[61] << 16) | ata_identify_data[60];
@@ -778,28 +784,6 @@ static void ata_power_down(void)
778{ 784{
779 if (!ata_powered) 785 if (!ata_powered)
780 return; 786 return;
781 if (ceata)
782 {
783 memset(ceata_taskfile, 0, 16);
784 ceata_taskfile[0xf] = CMD_STANDBY_IMMEDIATE;
785 ceata_wait_idle();
786 ceata_write_multiple_register(0, ceata_taskfile, 16);
787 ceata_wait_idle();
788 sleep(HZ);
789 PWRCON(0) |= (1 << 9);
790 }
791 else
792 {
793 ata_wait_for_rdy(1000000);
794 ata_write_cbr(&ATA_PIO_DVR, 0);
795 ata_write_cbr(&ATA_PIO_CSD, CMD_STANDBY_IMMEDIATE);
796 ata_wait_for_rdy(1000000);
797 sleep(HZ / 30);
798 ATA_CONTROL = 0;
799 while (!(ATA_CONTROL & BIT(1)))
800 yield();
801 PWRCON(0) |= (1 << 5);
802 }
803 PCON(7) = 0; 787 PCON(7) = 0;
804 PCON(8) = 0; 788 PCON(8) = 0;
805 PCON(9) = 0; 789 PCON(9) = 0;
@@ -1080,26 +1064,27 @@ static void ata_flush_cache(void)
1080{ 1064{
1081 uint8_t cmd; 1065 uint8_t cmd;
1082 1066
1083 if (ata_identify_data[83] & BIT(13)) { 1067 if (ceata) {
1084 cmd = CMD_FLUSH_CACHE_EXT;
1085 } else if (ata_identify_data[83] & BIT(12)) {
1086 cmd = CMD_FLUSH_CACHE;
1087 } else {
1088 /* If neither (mandatory!) command is supported
1089 then don't issue it. */
1090 return;
1091 }
1092
1093 if (ceata)
1094 {
1095 memset(ceata_taskfile, 0, 16); 1068 memset(ceata_taskfile, 0, 16);
1096 ceata_taskfile[0xf] = cmd; 1069 ceata_taskfile[0xf] = CMD_FLUSH_CACHE_EXT; /* CE-ATA only supports EXT */
1097 ceata_wait_idle(); 1070 ceata_wait_idle();
1098 ceata_write_multiple_register(0, ceata_taskfile, 16); 1071 ceata_write_multiple_register(0, ceata_taskfile, 16);
1099 ceata_wait_idle(); 1072 ceata_wait_idle();
1100 } 1073 } else {
1101 else 1074 if (!canflush) {
1102 { 1075 return;
1076 } else if (ata_lba48 && ata_identify_data[83] & BIT(13)) {
1077 cmd = CMD_FLUSH_CACHE_EXT; /* Flag, optional, ATA-6 and up, for use with LBA48 devices. Mandatory for CE-ATA */
1078 } else if (ata_identify_data[83] & BIT(12)) {
1079 cmd = CMD_FLUSH_CACHE; /* Flag, mandatory, ATA-6 and up */
1080 } else if (ata_identify_data[80] >= BIT(5)) { /* Use >= instead of '&' because bits lower than the latest standard we support don't have to be set */
1081 cmd = CMD_FLUSH_CACHE; /* No flag, mandatory, ATA-5 (Optional for ATA-4) */
1082 } else {
1083 /* If neither command is supported then don't issue it. */
1084 canflush = 0;
1085 return;
1086 }
1087
1103 ata_wait_for_rdy(1000000); 1088 ata_wait_for_rdy(1000000);
1104 ata_write_cbr(&ATA_PIO_DVR, 0); 1089 ata_write_cbr(&ATA_PIO_DVR, 0);
1105 ata_write_cbr(&ATA_PIO_CSD, cmd); 1090 ata_write_cbr(&ATA_PIO_CSD, cmd);
@@ -1107,14 +1092,46 @@ static void ata_flush_cache(void)
1107 } 1092 }
1108} 1093}
1109 1094
1095int ata_flush(void)
1096{
1097 if (ata_powered) {
1098 mutex_lock(&ata_mutex);
1099 ata_flush_cache();
1100 mutex_unlock(&ata_mutex);
1101 }
1102 return 0;
1103}
1104
1110void ata_sleepnow(void) 1105void ata_sleepnow(void)
1111{ 1106{
1112 mutex_lock(&ata_mutex); 1107 mutex_lock(&ata_mutex);
1113 1108
1114 if (ata_disk_can_poweroff()) 1109 ata_flush_cache();
1115 ata_power_down(); 1110
1116 else 1111 if (ata_disk_can_sleep()) {
1117 ata_flush_cache(); 1112 if (ceata) {
1113 memset(ceata_taskfile, 0, 16);
1114 ceata_taskfile[0xf] = CMD_STANDBY_IMMEDIATE;
1115 ceata_wait_idle();
1116 ceata_write_multiple_register(0, ceata_taskfile, 16);
1117 ceata_wait_idle();
1118 sleep(HZ);
1119 PWRCON(0) |= (1 << 9);
1120 } else {
1121 ata_wait_for_rdy(1000000);
1122 ata_write_cbr(&ATA_PIO_DVR, 0);
1123 ata_write_cbr(&ATA_PIO_CSD, CMD_STANDBY_IMMEDIATE);
1124 ata_wait_for_rdy(1000000);
1125 sleep(HZ / 30);
1126 ATA_CONTROL = 0;
1127 while (!(ATA_CONTROL & BIT(1)))
1128 yield();
1129 PWRCON(0) |= (1 << 5);
1130 }
1131 }
1132
1133 if (ata_disk_can_sleep() || canflush)
1134 ata_power_down(); // XXX add a powerdown delay similar to main ATA driver?
1118 1135
1119 mutex_unlock(&ata_mutex); 1136 mutex_unlock(&ata_mutex);
1120} 1137}
diff --git a/manual/getting_started/installation.tex b/manual/getting_started/installation.tex
index 1b09ce1f22..b42fcc47e6 100644
--- a/manual/getting_started/installation.tex
+++ b/manual/getting_started/installation.tex
@@ -305,14 +305,15 @@ For example, the stock Apple firmware on earlier 6th generation iPod Classic mod
305 robust, but tend to be expensive and not available in larger sizes. 305 robust, but tend to be expensive and not available in larger sizes.
306\item[SATA.] These are fast, reliable, and available in high capacities, but are typically optimized for high performance 306\item[SATA.] These are fast, reliable, and available in high capacities, but are typically optimized for high performance
307 at the expense of power consumption.. However, as they implement the full ATA command set, we are able 307 at the expense of power consumption.. However, as they implement the full ATA command set, we are able
308 to aggressively power them down when not being actively used. 308 to minimize their power consumption and power them down when not being actively used.
309\item[Single Secure Digital (SD).] While these adapters come in different form factors from multiple vendors, 309\item[Single Secure Digital (SD).] While these adapters come in different form factors from multiple vendors,
310 they are all based on the same basic design. The ATA command set is incompletely emulated, notably lacking 310 they are all based on the same basic design. The ATA command set is incompletely emulated, notably lacking
311 support for the \emph{mandatory} ATA power management commands that Rockbox uses to flush caches and safely 311 support for \emph{mandatory} ATA power management commands that Rockbox uses to safely
312 transition the device in and out of low power states. Additionally, SD cards vary widely in quality 312 transition the device in and out of low power states. Additionally, SD cards vary widely in quality
313 and power consumption with the resultant effects on data longevity and battery life. Finally, these SD adapters do not support 2TiB or larger SDUC cards. 313 and power consumption with the resultant effects on data longevity and battery life. Finally, these SD adapters
314 do not support 2TiB or larger SDUC cards.
314\item[Dual/Quad SD.] These are similar to the above, only allowing use of mulitiple SD cards to 315\item[Dual/Quad SD.] These are similar to the above, only allowing use of mulitiple SD cards to
315 increase the overall storage capacity. While typically described as JBOD\footnote{Just a Bunch Of Disks}, this is not accurate as each card is not individually accessable. Instead, the adapter claims to be to be a single logical drive of the combined capacity of the individual cards in a RAID0-like manner. Consquently, if any one card fails, all data on all other cards is most likely permenantly lost. Given the quality and power management concerns mentioned earlier, this means use of multiple SD cards in one of these adapters is the least reliable/robust and the most power hungry of the various SSD mods. Finally, in another violation of the ATA specification, these ATA-SD adapters fail to properly support LBA48 addressing, meaning that no matter which combination of cards is used, they simply will not work if their combined capacity exceeds 2TiB. 316 increase the overall storage capacity. While typically described as JBOD\footnote{Just a Bunch Of Disks}, this is not accurate as each card is not individually accessable. Instead, the adapter claims to be to be a single logical drive of the combined capacity of the individual cards in a RAID0-like manner. Consquently, if any one card fails, all data on all other cards may be rendered inacessible. Given the quality concerns mentioned earlier, this means use of multiple SD cards in one of these adapters is the least reliable/robust of the various SSD mods. Finally, in another violation of the ATA specification, these ATA-SD adapters fail to properly support LBA48 addressing, meaning that no matter what combination of cards is used, if their combined capacity exceeds 2TiB, the extra capacity will not be usable, and the device may even present as having (considerably) less space.
316\end{description} 317\end{description}
317 318
318\note{All of these flash/SSD mods take up less physical space in the device enclosure than the original hard drive, so care must be taken to ensure they are securely mounted and resistant to the vibration and impacts that typically occur in portable devices. Ribbon cables are particularly vulnerable.} 319\note{All of these flash/SSD mods take up less physical space in the device enclosure than the original hard drive, so care must be taken to ensure they are securely mounted and resistant to the vibration and impacts that typically occur in portable devices. Ribbon cables are particularly vulnerable.}