xref: /spdk/lib/ftl/upgrade/ftl_sb_v3.c (revision 524129a908ef13b65cdf5d03e27711ae31dec8f7)
1 /*   SPDX-License-Identifier: BSD-3-Clause
2  *   Copyright 2023 Solidigm All Rights Reserved
3  *   Copyright (C) 2022 Intel Corporation.
4  *   All rights reserved.
5  */
6 
7 #include "ftl_sb_v3.h"
8 #include "ftl_core.h"
9 #include "ftl_layout.h"
10 #include "upgrade/ftl_sb_upgrade.h"
11 
12 bool
13 ftl_superblock_v3_check_magic(union ftl_superblock_ver *sb_ver)
14 {
15 	return sb_ver->header.magic == FTL_SUPERBLOCK_MAGIC;
16 }
17 
18 bool
19 ftl_superblock_v3_md_layout_is_empty(union ftl_superblock_ver *sb_ver)
20 {
21 	return sb_ver->v3.md_layout_head.type == FTL_LAYOUT_REGION_TYPE_INVALID;
22 }
23 
24 static bool
25 md_region_is_fixed(int reg_type)
26 {
27 	switch (reg_type) {
28 	case FTL_LAYOUT_REGION_TYPE_SB:
29 	case FTL_LAYOUT_REGION_TYPE_SB_BASE:
30 	case FTL_LAYOUT_REGION_TYPE_DATA_BASE:
31 		return true;
32 
33 	default:
34 		return false;
35 	}
36 }
37 
38 static uint32_t
39 md_regions_count(void)
40 {
41 	uint32_t reg_cnt = 0, reg_type;
42 
43 	for (reg_type = 0; reg_type < FTL_LAYOUT_REGION_TYPE_MAX; reg_type++) {
44 		if (md_region_is_fixed(reg_type)) {
45 			continue;
46 		}
47 
48 		reg_cnt++;
49 	}
50 	return reg_cnt;
51 }
52 
53 static bool
54 superblock_v3_md_region_overflow(struct spdk_ftl_dev *dev,
55 				 struct ftl_superblock_v3_md_region *sb_reg)
56 {
57 	/* sb_reg is part of the sb structure - the pointer should be at a positive offset */
58 	if ((uintptr_t)sb_reg < (uintptr_t)dev->sb) {
59 		return true;
60 	}
61 
62 	/* Make sure the entry doesn't overflow the pointer value (probably overkill to check) */
63 	if (UINT64_MAX - (uintptr_t)sb_reg <= sizeof(*sb_reg)) {
64 		return true;
65 	}
66 
67 	/* There's only a finite (FTL_SUPERBLOCK_SIZE) amount of space in the superblock. Make sure the region wholly fits in that space. */
68 	if ((uintptr_t)(sb_reg + 1) > ((uintptr_t)(dev->sb) + FTL_SUPERBLOCK_SIZE)) {
69 		return true;
70 	}
71 
72 	return false;
73 }
74 
75 static int
76 superblock_v3_md_layout_add(struct spdk_ftl_dev *dev, struct ftl_superblock_v3_md_region *sb_reg,
77 			    uint32_t reg_type, uint32_t reg_version, uint64_t blk_offs, uint64_t blk_sz)
78 {
79 	if (superblock_v3_md_region_overflow(dev, sb_reg)) {
80 		FTL_ERRLOG(dev, "Buffer overflow\n");
81 		return -EOVERFLOW;
82 	}
83 
84 	sb_reg->type = reg_type;
85 	sb_reg->version = reg_version;
86 	sb_reg->blk_offs = blk_offs;
87 	sb_reg->blk_sz = blk_sz;
88 	return 0;
89 }
90 
91 static int
92 superblock_v3_md_layout_add_free(struct spdk_ftl_dev *dev,
93 				 struct ftl_superblock_v3_md_region **sb_reg,
94 				 uint32_t reg_type, uint32_t free_type, uint64_t total_blocks)
95 {
96 	struct ftl_layout *layout = &dev->layout;
97 	struct ftl_layout_region *reg = &layout->region[reg_type];
98 	uint64_t blks_left = total_blocks - reg->current.offset - reg->current.blocks;
99 
100 	if (blks_left == 0) {
101 		return 0;
102 	}
103 
104 	(*sb_reg)->df_next = ftl_df_get_obj_id(dev->sb, (*sb_reg) + 1);
105 	(*sb_reg) = (*sb_reg) + 1;
106 
107 	if (superblock_v3_md_layout_add(dev, *sb_reg, free_type, 0,
108 					reg->current.offset + reg->current.blocks, blks_left)) {
109 		return -1;
110 	}
111 
112 	(*sb_reg)->df_next = FTL_DF_OBJ_ID_INVALID;
113 
114 	return 0;
115 }
116 
117 int
118 ftl_superblock_v3_md_layout_build(struct spdk_ftl_dev *dev)
119 {
120 	union ftl_superblock_ver *sb_ver = (union ftl_superblock_ver *)dev->sb;
121 	struct ftl_layout *layout = &dev->layout;
122 	struct ftl_layout_region *reg;
123 	int n = 0;
124 	bool is_empty = ftl_superblock_v3_md_layout_is_empty(sb_ver);
125 	struct ftl_superblock_v3_md_region *sb_reg = &sb_ver->v3.md_layout_head;
126 
127 	/* TODO: major upgrades: add all free regions being tracked
128 	 * For now SB MD layout must be empty - otherwise md free regions may be lost */
129 	assert(is_empty);
130 
131 	for (; n < FTL_LAYOUT_REGION_TYPE_MAX;) {
132 		reg = &layout->region[n];
133 		if (md_region_is_fixed(reg->type)) {
134 			reg->current.sb_md_reg = NULL;
135 			n++;
136 
137 			if (n >= FTL_LAYOUT_REGION_TYPE_MAX) {
138 				/* For VSS emulation the last layout type is a fixed region, we need to move back the list and end the list on previous entry */
139 				sb_reg--;
140 				break;
141 			}
142 			continue;
143 		}
144 
145 		if (superblock_v3_md_layout_add(dev, sb_reg, reg->type, reg->current.version,
146 						reg->current.offset, reg->current.blocks)) {
147 			return -1;
148 		}
149 		reg->current.sb_md_reg = sb_reg;
150 
151 		n++;
152 		if (n < FTL_LAYOUT_REGION_TYPE_MAX) {
153 			/* next region */
154 			sb_reg->df_next = ftl_df_get_obj_id(sb_ver, sb_reg + 1);
155 			sb_reg++;
156 		}
157 	}
158 
159 	/* terminate the list */
160 	sb_reg->df_next = FTL_DF_OBJ_ID_INVALID;
161 
162 	/* create free_nvc/free_base regions on the first run */
163 	if (is_empty) {
164 		superblock_v3_md_layout_add_free(dev, &sb_reg, FTL_LAYOUT_REGION_LAST_NVC,
165 						 FTL_LAYOUT_REGION_TYPE_FREE_NVC, layout->nvc.total_blocks);
166 
167 		superblock_v3_md_layout_add_free(dev, &sb_reg, FTL_LAYOUT_REGION_LAST_BASE,
168 						 FTL_LAYOUT_REGION_TYPE_FREE_BASE, layout->base.total_blocks);
169 	}
170 
171 	return 0;
172 }
173 
174 int
175 ftl_superblock_v3_md_layout_load_all(struct spdk_ftl_dev *dev)
176 {
177 	struct ftl_superblock_v3 *sb = (struct ftl_superblock_v3 *)dev->sb;
178 	struct ftl_superblock_v3_md_region *sb_reg = &sb->md_layout_head;
179 	struct ftl_layout *layout = &dev->layout;
180 	uint32_t regs_found = 0;
181 
182 	for (int n = 0; n < FTL_LAYOUT_REGION_TYPE_MAX; n++) {
183 		if (md_region_is_fixed(sb_reg->type)) {
184 			continue;
185 		}
186 
187 		layout->region[n].current.sb_md_reg = NULL;
188 		layout->region[n].prev = layout->region[n].current;
189 	}
190 
191 	while (sb_reg->type != FTL_LAYOUT_REGION_TYPE_INVALID) {
192 		struct ftl_layout_region *reg;
193 
194 		/* TODO: major upgrades: add free regions tracking */
195 		if (sb_reg->type == FTL_LAYOUT_REGION_TYPE_FREE_NVC ||
196 		    sb_reg->type == FTL_LAYOUT_REGION_TYPE_FREE_BASE) {
197 			goto next_sb_reg;
198 		}
199 
200 		if (sb_reg->type >= FTL_LAYOUT_REGION_TYPE_MAX) {
201 			FTL_ERRLOG(dev, "Invalid MD region type found\n");
202 			return -1;
203 		}
204 
205 		if (md_region_is_fixed(sb_reg->type)) {
206 			FTL_ERRLOG(dev, "Unsupported MD region type found\n");
207 			return -1;
208 		}
209 
210 		reg = &layout->region[sb_reg->type];
211 		if (sb_reg->version == reg->current.version) {
212 			if (reg->current.sb_md_reg) {
213 				FTL_ERRLOG(dev, "Multiple/looping current regions found\n");
214 				return -1;
215 			}
216 
217 			reg->current.offset = sb_reg->blk_offs;
218 			reg->current.blocks = sb_reg->blk_sz;
219 			reg->current.sb_md_reg = sb_reg;
220 
221 			if (!reg->prev.sb_md_reg) {
222 				regs_found++;
223 			}
224 		} else {
225 			if (sb_reg->version > reg->current.version) {
226 				FTL_ERRLOG(dev, "Unknown region version found\n");
227 				return -1;
228 			}
229 			/*
230 			 * The metadata regions are kept as a linked list, it's therefore possible to have it corrupted and contain loops.
231 			 * If the prev region has already been updated to the oldest found version (see following comment/block) and we see
232 			 * the same version again, it's a loop and error.
233 			 */
234 			if (sb_reg->version == reg->prev.version) {
235 				FTL_ERRLOG(dev, "Multiple/looping prev regions found\n");
236 				return -1;
237 			}
238 
239 			/*
240 			 * The following check is in preparation for major metadata upgrade.
241 			 * It's possible that a region will need to be increased in size. It will allocate an additional region in this case
242 			 * and upgrade each entries one by one. If in the middle of this upgrade process a dirty shutdown occurs, there will be
243 			 * multiple entries of the same metadata region in the superblock (old original and partially updated one). This will then
244 			 * effectively rollback the upgrade to the oldest found metadata section and start from the beginning.
245 			 */
246 			if (sb_reg->version < reg->prev.version) {
247 				if ((!reg->current.sb_md_reg) && (!reg->prev.sb_md_reg)) {
248 					regs_found++;
249 				}
250 
251 				reg->prev.offset = sb_reg->blk_offs;
252 				reg->prev.blocks = sb_reg->blk_sz;
253 				reg->prev.version = sb_reg->version;
254 				reg->prev.sb_md_reg = sb_reg;
255 			}
256 		}
257 
258 next_sb_reg:
259 		if (sb_reg->df_next == FTL_DF_OBJ_ID_INVALID) {
260 			break;
261 		}
262 
263 		if (UINT64_MAX - (uintptr_t)sb <= sb_reg->df_next) {
264 			FTL_ERRLOG(dev, "Buffer overflow\n");
265 			return -EOVERFLOW;
266 		}
267 
268 		sb_reg = ftl_df_get_obj_ptr(sb, sb_reg->df_next);
269 		if (superblock_v3_md_region_overflow(dev, sb_reg)) {
270 			FTL_ERRLOG(dev, "Buffer overflow\n");
271 			return -EOVERFLOW;
272 		}
273 	}
274 
275 	if (regs_found != md_regions_count()) {
276 		FTL_ERRLOG(dev, "Missing regions\n");
277 		return -1;
278 	}
279 
280 	return 0;
281 }
282 
283 static void
284 ftl_superblock_v3_md_layout_free_region(struct spdk_ftl_dev *dev,
285 					struct ftl_superblock_v3_md_region *sb_reg)
286 {
287 	/* Major upgrades are not implemented in v3 */
288 	sb_reg->type = FTL_LAYOUT_REGION_TYPE_FREE_NVC;
289 }
290 
291 int
292 ftl_superblock_v3_md_layout_upgrade_region(struct spdk_ftl_dev *dev,
293 		struct ftl_layout_region *reg, uint32_t new_version)
294 {
295 	struct ftl_superblock_v3 *sb = (struct ftl_superblock_v3 *)dev->sb;
296 	struct ftl_superblock_v3_md_region *sb_reg = reg->prev.sb_md_reg;
297 	struct ftl_superblock_v3_md_region *sb_reg_iter = &sb->md_layout_head;
298 	uint32_t old_version = sb_reg->version;
299 
300 	assert(sb_reg);
301 	assert(reg->prev.sb_md_reg == sb_reg);
302 	assert(new_version > old_version);
303 
304 	while (sb_reg_iter->type != FTL_LAYOUT_REGION_TYPE_INVALID) {
305 		if (sb_reg_iter->type != sb_reg->type) {
306 			goto next_sb_reg_iter;
307 		}
308 
309 		/* Verify all region versions up to new_version are updated: */
310 		if (sb_reg_iter->version != old_version && sb_reg_iter->version < new_version) {
311 			FTL_ERRLOG(dev, "Region upgrade skipped\n");
312 			return -1;
313 		}
314 
315 		if (sb_reg_iter->version == new_version) {
316 			/* Major upgrades: update region prev version to the new version region found */
317 			assert(sb_reg != sb_reg_iter);
318 			reg->prev.offset = sb_reg_iter->blk_offs;
319 			reg->prev.blocks = sb_reg_iter->blk_sz;
320 			reg->prev.version = sb_reg_iter->version;
321 			reg->prev.sb_md_reg = sb_reg_iter;
322 
323 			ftl_superblock_v3_md_layout_free_region(dev, sb_reg);
324 			goto exit;
325 		}
326 
327 next_sb_reg_iter:
328 		if (sb_reg_iter->df_next == FTL_DF_OBJ_ID_INVALID) {
329 			break;
330 		}
331 
332 		sb_reg_iter = ftl_df_get_obj_ptr(sb, sb_reg_iter->df_next);
333 		if (superblock_v3_md_region_overflow(dev, sb_reg_iter)) {
334 			FTL_ERRLOG(dev, "Buffer overflow\n");
335 			return -EOVERFLOW;
336 		}
337 	}
338 
339 	/* Minor upgrades: update the region in place (only the new version) */
340 	assert(sb_reg == reg->prev.sb_md_reg);
341 	sb_reg->version = new_version;
342 	reg->prev.version = new_version;
343 
344 exit:
345 	/* Update the region current version */
346 	if (new_version == reg->current.version) {
347 		reg->current.offset = sb_reg->blk_offs;
348 		reg->current.blocks = sb_reg->blk_sz;
349 		reg->current.version = sb_reg->version;
350 		reg->current.sb_md_reg = sb_reg;
351 	}
352 
353 	return 0;
354 }
355 
356 void
357 ftl_superblock_v3_md_layout_dump(struct spdk_ftl_dev *dev)
358 {
359 	struct ftl_superblock_v3 *sb = (struct ftl_superblock_v3 *)dev->sb;
360 	struct ftl_superblock_v3_md_region *sb_reg = &sb->md_layout_head;
361 
362 	FTL_NOTICELOG(dev, "SB metadata layout:\n");
363 	while (sb_reg->type != FTL_LAYOUT_REGION_TYPE_INVALID) {
364 		FTL_NOTICELOG(dev,
365 			      "Region df:0x%"PRIx64" type:0x%"PRIx32" ver:%"PRIu32" blk_offs:0x%"PRIx64" blk_sz:0x%"PRIx64"\n",
366 			      ftl_df_get_obj_id(sb, sb_reg), sb_reg->type, sb_reg->version, sb_reg->blk_offs, sb_reg->blk_sz);
367 
368 		if (sb_reg->df_next == FTL_DF_OBJ_ID_INVALID) {
369 			break;
370 		}
371 
372 		sb_reg = ftl_df_get_obj_ptr(sb, sb_reg->df_next);
373 		if (superblock_v3_md_region_overflow(dev, sb_reg)) {
374 			FTL_ERRLOG(dev, "Buffer overflow\n");
375 			return;
376 		}
377 	}
378 }
379