Skip to content

Commit 6b23149

Browse files
committed
[nrf fromlist] settings: zms: add more robustness to the save function
When a power off happens after writing the settings name and before writing the linked list node we cannot write the settings name again in the future. Fix this by writing the linked list node before writing the name. When loading all settings, we already delete linked list node that do not have any name or value written. Adds as well a recover path if a power down happens in the middle of unlinking an LL node after a delete. Upstream PR #: 87792 Signed-off-by: Riadh Ghaddab <[email protected]> (cherry picked from commit 7c012ccc51ff4dff6d363beaa5d27daca788567e)
1 parent 2d4e83e commit 6b23149

File tree

2 files changed

+65
-36
lines changed

2 files changed

+65
-36
lines changed

subsys/settings/include/settings/settings_zms.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ extern "C" {
7272

7373
/* some useful macros */
7474
#define ZMS_NAME_ID_FROM_LL_NODE(x) (x & ~BIT(0))
75+
#define ZMS_LL_NODE_FROM_NAME_ID(x) (x | BIT(0))
7576
#define ZMS_UPDATE_COLLISION_NUM(x, y) \
7677
((x & ~ZMS_COLLISIONS_MASK) | ((y << 1) & ZMS_COLLISIONS_MASK))
7778
#define ZMS_COLLISION_NUM(x) ((x & ZMS_COLLISIONS_MASK) >> 1)

subsys/settings/src/settings_zms.c

Lines changed: 64 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,39 @@ static int settings_zms_unlink_ll_node(struct settings_zms *cf, uint32_t name_ha
7272
struct settings_hash_linked_list settings_update_element;
7373

7474
/* let's update the linked list */
75-
rc = zms_read(&cf->cf_zms, name_hash | 1, &settings_element,
75+
rc = zms_read(&cf->cf_zms, ZMS_LL_NODE_FROM_NAME_ID(name_hash), &settings_element,
7676
sizeof(struct settings_hash_linked_list));
7777
if (rc < 0) {
7878
return rc;
7979
}
80+
81+
/* update the previous element */
82+
if (settings_element.previous_hash) {
83+
rc = zms_read(&cf->cf_zms, settings_element.previous_hash, &settings_update_element,
84+
sizeof(struct settings_hash_linked_list));
85+
if (rc < 0) {
86+
return rc;
87+
}
88+
if (!settings_element.next_hash) {
89+
/* we are deleting the last element of the linked list,
90+
* let's update the second_to_last_hash_id
91+
*/
92+
cf->second_to_last_hash_id = settings_update_element.previous_hash;
93+
}
94+
settings_update_element.next_hash = settings_element.next_hash;
95+
rc = zms_write(&cf->cf_zms, settings_element.previous_hash,
96+
&settings_update_element, sizeof(struct settings_hash_linked_list));
97+
if (rc < 0) {
98+
return rc;
99+
}
100+
}
101+
102+
/* Now delete the current linked list element */
103+
rc = zms_delete(&cf->cf_zms, ZMS_LL_NODE_FROM_NAME_ID(name_hash));
104+
if (rc < 0) {
105+
return rc;
106+
}
107+
80108
/* update the next element */
81109
if (settings_element.next_hash) {
82110
rc = zms_read(&cf->cf_zms, settings_element.next_hash, &settings_update_element,
@@ -100,28 +128,8 @@ static int settings_zms_unlink_ll_node(struct settings_zms *cf, uint32_t name_ha
100128
*/
101129
cf->last_hash_id = settings_element.previous_hash;
102130
}
103-
/* update the previous element */
104-
if (settings_element.previous_hash) {
105-
rc = zms_read(&cf->cf_zms, settings_element.previous_hash, &settings_update_element,
106-
sizeof(struct settings_hash_linked_list));
107-
if (rc < 0) {
108-
return rc;
109-
}
110-
if (!settings_element.next_hash) {
111-
/* we are deleting the last element of the linked list,
112-
* let's update the second_to_last_hash_id
113-
*/
114-
cf->second_to_last_hash_id = settings_update_element.previous_hash;
115-
}
116-
settings_update_element.next_hash = settings_element.next_hash;
117-
rc = zms_write(&cf->cf_zms, settings_element.previous_hash,
118-
&settings_update_element, sizeof(struct settings_hash_linked_list));
119-
if (rc < 0) {
120-
return rc;
121-
}
122-
}
123131

124-
return rc;
132+
return 0;
125133
}
126134
#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
127135

@@ -182,11 +190,6 @@ static int settings_zms_delete(struct settings_zms *cf, uint32_t name_hash)
182190
return rc;
183191
}
184192

185-
/* Now delete the current linked list element */
186-
rc = zms_delete(&cf->cf_zms, name_hash | 1);
187-
if (rc < 0) {
188-
return rc;
189-
}
190193
#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
191194
#ifdef CONFIG_SETTINGS_ZMS_NAME_CACHE
192195
/* Update the flag of the Settings entry in cache. */
@@ -538,16 +541,13 @@ static int settings_zms_save(struct settings_store *cs, const char *name, const
538541

539542
/* write the name if required */
540543
if (write_name) {
541-
rc = zms_write(&cf->cf_zms, name_hash, name, strlen(name));
542-
if (rc < 0) {
543-
return rc;
544-
}
544+
/* First let's update the linked list */
545545
#ifdef CONFIG_SETTINGS_ZMS_NO_LL_DELETE
546546
if (ll_node_exist) {
547547
goto no_ll_update;
548548
}
549549
/* verify that the ll_node doesn't exist otherwise do not update it */
550-
rc = zms_read(&cf->cf_zms, name_hash | 1, &settings_element,
550+
rc = zms_read(&cf->cf_zms, ZMS_LL_NODE_FROM_NAME_ID(name_hash), &settings_element,
551551
sizeof(struct settings_hash_linked_list));
552552
if (rc >= 0) {
553553
goto no_ll_update;
@@ -569,7 +569,7 @@ static int settings_zms_save(struct settings_store *cs, const char *name, const
569569
}
570570
}
571571
settings_element.previous_hash = cf->last_hash_id;
572-
rc = zms_write(&cf->cf_zms, name_hash | 1, &settings_element,
572+
rc = zms_write(&cf->cf_zms, ZMS_LL_NODE_FROM_NAME_ID(name_hash), &settings_element,
573573
sizeof(struct settings_hash_linked_list));
574574
if (rc < 0) {
575575
return rc;
@@ -580,25 +580,31 @@ static int settings_zms_save(struct settings_store *cs, const char *name, const
580580
}
581581
#endif
582582
/* Now update the previous linked list element */
583-
settings_element.next_hash = name_hash | 1;
583+
settings_element.next_hash = ZMS_LL_NODE_FROM_NAME_ID(name_hash);
584584
settings_element.previous_hash = cf->second_to_last_hash_id;
585585
rc = zms_write(&cf->cf_zms, cf->last_hash_id, &settings_element,
586586
sizeof(struct settings_hash_linked_list));
587587
if (rc < 0) {
588588
return rc;
589589
}
590590
cf->second_to_last_hash_id = cf->last_hash_id;
591-
cf->last_hash_id = name_hash | 1;
591+
cf->last_hash_id = ZMS_LL_NODE_FROM_NAME_ID(name_hash);
592592
#ifdef CONFIG_SETTINGS_ZMS_LL_CACHE
593593
if ((cf->ll_cache_next <= CONFIG_SETTINGS_ZMS_LL_CACHE_SIZE) &&
594594
(cf->ll_cache_next > 1)) {
595595
cf->ll_cache[cf->ll_cache_next - 2] = settings_element;
596596
}
597597
#endif
598-
}
599598
#ifdef CONFIG_SETTINGS_ZMS_NO_LL_DELETE
600599
no_ll_update:
601600
#endif /* CONFIG_SETTINGS_ZMS_NO_LL_DELETE */
601+
/* Now let's write the name */
602+
rc = zms_write(&cf->cf_zms, name_hash, name, strlen(name));
603+
if (rc < 0) {
604+
return rc;
605+
}
606+
}
607+
602608
#ifdef CONFIG_SETTINGS_ZMS_NAME_CACHE
603609
/* Add the flags of the written settings entry in cache */
604610
cache_flags = 0;
@@ -615,6 +621,7 @@ static int settings_zms_get_last_hash_ids(struct settings_zms *cf)
615621
{
616622
struct settings_hash_linked_list settings_element;
617623
uint32_t ll_last_hash_id = ZMS_LL_HEAD_HASH_ID;
624+
uint32_t previous_ll_hash_id = 0;
618625
int rc = 0;
619626

620627
#ifdef CONFIG_SETTINGS_ZMS_LL_CACHE
@@ -646,6 +653,27 @@ static int settings_zms_get_last_hash_ids(struct settings_zms *cf)
646653
return rc;
647654
}
648655

656+
if (settings_element.previous_hash != previous_ll_hash_id) {
657+
/* This is a special case that can happen when a power down occurred
658+
* when deleting a linked list node.
659+
* If the power down occurred after updating the previous linked list node,
660+
* then we would end up with a state where the previous_hash of the linked
661+
* list is broken. Let's recover from this
662+
*/
663+
rc = zms_delete(&cf->cf_zms, settings_element.previous_hash);
664+
if (rc < 0) {
665+
return rc;
666+
}
667+
/* Now recover the linked list */
668+
settings_element.previous_hash = previous_ll_hash_id;
669+
zms_write(&cf->cf_zms, ll_last_hash_id, &settings_element,
670+
sizeof(struct settings_hash_linked_list));
671+
if (rc < 0) {
672+
return rc;
673+
}
674+
}
675+
previous_ll_hash_id = ll_last_hash_id;
676+
649677
#ifdef CONFIG_SETTINGS_ZMS_LL_CACHE
650678
if (cf->ll_cache_next <= CONFIG_SETTINGS_ZMS_LL_CACHE_SIZE) {
651679
cf->ll_cache[cf->ll_cache_next++] = settings_element;

0 commit comments

Comments
 (0)