1 /* SPDX-License-Identifier: BSD-3-Clause 2 * Copyright (C) 2022 Intel Corporation. 3 * All rights reserved. 4 */ 5 6 #include "spdk/bdev.h" 7 8 #include "ftl_core.h" 9 #include "ftl_utils.h" 10 #include "ftl_band.h" 11 #include "ftl_layout.h" 12 #include "ftl_nv_cache.h" 13 #include "ftl_sb.h" 14 15 #define FTL_NV_CACHE_CHUNK_DATA_SIZE(blocks) ((uint64_t)blocks * FTL_BLOCK_SIZE) 16 #define FTL_NV_CACHE_CHUNK_SIZE(blocks) \ 17 (FTL_NV_CACHE_CHUNK_DATA_SIZE(blocks) + (2 * FTL_NV_CACHE_CHUNK_MD_SIZE)) 18 19 static inline float 20 blocks2mib(uint64_t blocks) 21 { 22 float result; 23 24 result = blocks; 25 result *= FTL_BLOCK_SIZE; 26 result /= 1024UL; 27 result /= 1024UL; 28 29 return result; 30 } 31 /* TODO: This should be aligned to the write unit size of the device a given piece of md is on. 32 * The tricky part is to make sure interpreting old alignment values will still be valid... 33 */ 34 #define FTL_LAYOUT_REGION_ALIGNMENT_BLOCKS 32ULL 35 #define FTL_LAYOUT_REGION_ALIGNMENT_BYTES (FTL_LAYOUT_REGION_ALIGNMENT_BLOCKS * FTL_BLOCK_SIZE) 36 37 static inline uint64_t 38 blocks_region(uint64_t bytes) 39 { 40 const uint64_t alignment = FTL_LAYOUT_REGION_ALIGNMENT_BYTES; 41 uint64_t result; 42 43 result = spdk_divide_round_up(bytes, alignment); 44 result *= alignment; 45 result /= FTL_BLOCK_SIZE; 46 47 return result; 48 } 49 50 static void 51 dump_region(struct spdk_ftl_dev *dev, struct ftl_layout_region *region) 52 { 53 assert(!(region->current.offset % FTL_LAYOUT_REGION_ALIGNMENT_BLOCKS)); 54 assert(!(region->current.blocks % FTL_LAYOUT_REGION_ALIGNMENT_BLOCKS)); 55 56 FTL_NOTICELOG(dev, "Region %s\n", region->name); 57 FTL_NOTICELOG(dev, " offset: %.2f MiB\n", 58 blocks2mib(region->current.offset)); 59 FTL_NOTICELOG(dev, " blocks: %.2f MiB\n", 60 blocks2mib(region->current.blocks)); 61 } 62 63 int 64 ftl_validate_regions(struct spdk_ftl_dev *dev, struct ftl_layout *layout) 65 { 66 uint64_t i, j; 67 68 /* Validate if regions doesn't overlap each other */ 69 /* TODO: major upgrades: keep track of and validate free_nvc/free_btm regions */ 70 for (i = 0; i < FTL_LAYOUT_REGION_TYPE_MAX; i++) { 71 struct ftl_layout_region *r1 = &layout->region[i]; 72 73 for (j = 0; j < FTL_LAYOUT_REGION_TYPE_MAX; j++) { 74 struct ftl_layout_region *r2 = &layout->region[j]; 75 76 if (r1->bdev_desc != r2->bdev_desc) { 77 continue; 78 } 79 80 if (i == j) { 81 continue; 82 } 83 84 uint64_t r1_begin = r1->current.offset; 85 uint64_t r1_end = r1->current.offset + r1->current.blocks - 1; 86 uint64_t r2_begin = r2->current.offset; 87 uint64_t r2_end = r2->current.offset + r2->current.blocks - 1; 88 89 if (spdk_max(r1_begin, r2_begin) <= spdk_min(r1_end, r2_end)) { 90 FTL_ERRLOG(dev, "Layout initialization ERROR, two regions overlap, " 91 "%s and %s\n", r1->name, r2->name); 92 return -1; 93 } 94 } 95 } 96 97 return 0; 98 } 99 100 static uint64_t 101 get_num_user_lbas(struct spdk_ftl_dev *dev) 102 { 103 uint64_t blocks; 104 105 blocks = dev->num_bands * ftl_get_num_blocks_in_band(dev); 106 blocks = (blocks * (100 - dev->conf.overprovisioning)) / 100; 107 108 return blocks; 109 } 110 111 static void 112 set_region_bdev_nvc(struct ftl_layout_region *reg, struct spdk_ftl_dev *dev) 113 { 114 reg->bdev_desc = dev->nv_cache.bdev_desc; 115 reg->ioch = dev->nv_cache.cache_ioch; 116 reg->vss_blksz = dev->nv_cache.md_size; 117 } 118 119 static void 120 set_region_bdev_btm(struct ftl_layout_region *reg, struct spdk_ftl_dev *dev) 121 { 122 reg->bdev_desc = dev->base_bdev_desc; 123 reg->ioch = dev->base_ioch; 124 reg->vss_blksz = 0; 125 } 126 127 static int 128 setup_layout_nvc(struct spdk_ftl_dev *dev) 129 { 130 int region_type; 131 uint64_t left, offset = 0, l2p_blocks; 132 struct ftl_layout *layout = &dev->layout; 133 struct ftl_layout_region *region, *mirror; 134 static const char *p2l_region_name[] = { 135 "p2l0", 136 "p2l1", 137 "p2l2", 138 "p2l3" 139 }; 140 141 #ifdef SPDK_FTL_VSS_EMU 142 /* Skip the already init`d VSS region */ 143 region = &layout->region[FTL_LAYOUT_REGION_TYPE_VSS]; 144 offset += region->current.blocks; 145 146 if (offset >= layout->nvc.total_blocks) { 147 goto error; 148 } 149 #endif 150 151 /* Skip the superblock region. Already init`d in ftl_layout_setup_superblock */ 152 region = &layout->region[FTL_LAYOUT_REGION_TYPE_SB]; 153 offset += region->current.blocks; 154 155 /* Initialize L2P region */ 156 if (offset >= layout->nvc.total_blocks) { 157 goto error; 158 } 159 region = &layout->region[FTL_LAYOUT_REGION_TYPE_L2P]; 160 region->type = FTL_LAYOUT_REGION_TYPE_L2P; 161 region->name = "l2p"; 162 region->current.version = 0; 163 region->prev.version = 0; 164 region->current.offset = offset; 165 region->current.blocks = blocks_region(layout->l2p.addr_size * dev->num_lbas); 166 set_region_bdev_nvc(region, dev); 167 offset += region->current.blocks; 168 169 /* Initialize band info metadata */ 170 if (offset >= layout->nvc.total_blocks) { 171 goto error; 172 } 173 region = &layout->region[FTL_LAYOUT_REGION_TYPE_BAND_MD]; 174 region->type = FTL_LAYOUT_REGION_TYPE_BAND_MD; 175 region->mirror_type = FTL_LAYOUT_REGION_TYPE_BAND_MD_MIRROR; 176 region->name = "band_md"; 177 region->current.version = region->prev.version = FTL_BAND_VERSION_CURRENT; 178 region->current.offset = offset; 179 region->current.blocks = blocks_region(ftl_get_num_bands(dev) * sizeof(struct ftl_band_md)); 180 region->entry_size = sizeof(struct ftl_band_md) / FTL_BLOCK_SIZE; 181 region->num_entries = ftl_get_num_bands(dev); 182 set_region_bdev_nvc(region, dev); 183 offset += region->current.blocks; 184 185 /* Initialize band info metadata mirror */ 186 if (offset >= layout->nvc.total_blocks) { 187 goto error; 188 } 189 mirror = &layout->region[FTL_LAYOUT_REGION_TYPE_BAND_MD_MIRROR]; 190 *mirror = *region; 191 mirror->type = FTL_LAYOUT_REGION_TYPE_BAND_MD_MIRROR; 192 mirror->mirror_type = FTL_LAYOUT_REGION_TYPE_INVALID; 193 mirror->name = "band_md_mirror"; 194 mirror->current.offset += region->current.blocks; 195 offset += mirror->current.blocks; 196 197 if (offset >= layout->nvc.total_blocks) { 198 goto error; 199 } 200 201 /* 202 * Initialize P2L checkpointing regions 203 */ 204 SPDK_STATIC_ASSERT(SPDK_COUNTOF(p2l_region_name) == FTL_LAYOUT_REGION_TYPE_P2L_COUNT, 205 "Incorrect # of P2L region names"); 206 for (region_type = FTL_LAYOUT_REGION_TYPE_P2L_CKPT_MIN; 207 region_type <= FTL_LAYOUT_REGION_TYPE_P2L_CKPT_MAX; 208 region_type++) { 209 if (offset >= layout->nvc.total_blocks) { 210 goto error; 211 } 212 region = &layout->region[region_type]; 213 region->type = region_type; 214 region->name = p2l_region_name[region_type - FTL_LAYOUT_REGION_TYPE_P2L_CKPT_MIN]; 215 region->current.version = FTL_P2L_VERSION_CURRENT; 216 region->prev.version = FTL_P2L_VERSION_CURRENT; 217 region->current.offset = offset; 218 region->current.blocks = blocks_region(layout->p2l.ckpt_pages * FTL_BLOCK_SIZE); 219 region->entry_size = 1; 220 region->num_entries = region->current.blocks; 221 set_region_bdev_nvc(region, dev); 222 offset += region->current.blocks; 223 } 224 225 /* 226 * Initialize trim metadata region 227 */ 228 if (offset >= layout->nvc.total_blocks) { 229 goto error; 230 } 231 l2p_blocks = layout->region[FTL_LAYOUT_REGION_TYPE_L2P].current.blocks; 232 region = &layout->region[FTL_LAYOUT_REGION_TYPE_TRIM_MD]; 233 region->type = FTL_LAYOUT_REGION_TYPE_TRIM_MD; 234 region->mirror_type = FTL_LAYOUT_REGION_TYPE_TRIM_MD_MIRROR; 235 region->name = "trim_md"; 236 region->current.version = 0; 237 region->prev.version = 0; 238 region->current.offset = offset; 239 region->current.blocks = blocks_region(l2p_blocks * sizeof(uint64_t)); 240 region->entry_size = 1; 241 region->num_entries = region->current.blocks; 242 set_region_bdev_nvc(region, dev); 243 offset += region->current.blocks; 244 245 /* Initialize trim metadata mirror region */ 246 if (offset >= layout->nvc.total_blocks) { 247 goto error; 248 } 249 mirror = &layout->region[FTL_LAYOUT_REGION_TYPE_TRIM_MD_MIRROR]; 250 *mirror = *region; 251 mirror->type = FTL_LAYOUT_REGION_TYPE_TRIM_MD_MIRROR; 252 mirror->mirror_type = FTL_LAYOUT_REGION_TYPE_INVALID; 253 mirror->name = "trim_md_mirror"; 254 mirror->current.offset += region->current.blocks; 255 offset += mirror->current.blocks; 256 257 /* 258 * Initialize NV Cache metadata 259 */ 260 if (offset >= layout->nvc.total_blocks) { 261 goto error; 262 } 263 264 left = layout->nvc.total_blocks - offset; 265 layout->nvc.chunk_data_blocks = 266 FTL_NV_CACHE_CHUNK_DATA_SIZE(ftl_get_num_blocks_in_band(dev)) / FTL_BLOCK_SIZE; 267 layout->nvc.chunk_meta_size = FTL_NV_CACHE_CHUNK_MD_SIZE; 268 layout->nvc.chunk_count = (left * FTL_BLOCK_SIZE) / 269 FTL_NV_CACHE_CHUNK_SIZE(ftl_get_num_blocks_in_band(dev)); 270 layout->nvc.chunk_tail_md_num_blocks = ftl_nv_cache_chunk_tail_md_num_blocks(&dev->nv_cache); 271 272 if (0 == layout->nvc.chunk_count) { 273 goto error; 274 } 275 region = &layout->region[FTL_LAYOUT_REGION_TYPE_NVC_MD]; 276 region->type = FTL_LAYOUT_REGION_TYPE_NVC_MD; 277 region->mirror_type = FTL_LAYOUT_REGION_TYPE_NVC_MD_MIRROR; 278 region->name = "nvc_md"; 279 region->current.version = region->prev.version = FTL_NVC_VERSION_CURRENT; 280 region->current.offset = offset; 281 region->current.blocks = blocks_region(layout->nvc.chunk_count * 282 sizeof(struct ftl_nv_cache_chunk_md)); 283 region->entry_size = sizeof(struct ftl_nv_cache_chunk_md) / FTL_BLOCK_SIZE; 284 region->num_entries = layout->nvc.chunk_count; 285 set_region_bdev_nvc(region, dev); 286 offset += region->current.blocks; 287 288 /* 289 * Initialize NV Cache metadata mirror 290 */ 291 mirror = &layout->region[FTL_LAYOUT_REGION_TYPE_NVC_MD_MIRROR]; 292 *mirror = *region; 293 mirror->type = FTL_LAYOUT_REGION_TYPE_NVC_MD_MIRROR; 294 mirror->mirror_type = FTL_LAYOUT_REGION_TYPE_INVALID; 295 mirror->name = "nvc_md_mirror"; 296 mirror->current.offset += region->current.blocks; 297 offset += mirror->current.blocks; 298 299 /* 300 * Initialize data region on NV cache 301 */ 302 if (offset >= layout->nvc.total_blocks) { 303 goto error; 304 } 305 region = &layout->region[FTL_LAYOUT_REGION_TYPE_DATA_NVC]; 306 region->type = FTL_LAYOUT_REGION_TYPE_DATA_NVC; 307 region->name = "data_nvc"; 308 region->current.version = region->prev.version = 0; 309 region->current.offset = offset; 310 region->current.blocks = layout->nvc.chunk_count * layout->nvc.chunk_data_blocks; 311 set_region_bdev_nvc(region, dev); 312 offset += region->current.blocks; 313 314 left = layout->nvc.total_blocks - offset; 315 if (left > layout->nvc.chunk_data_blocks) { 316 FTL_ERRLOG(dev, "Error when setup NV cache layout\n"); 317 return -1; 318 } 319 320 if (offset > layout->nvc.total_blocks) { 321 FTL_ERRLOG(dev, "Error when setup NV cache layout\n"); 322 goto error; 323 } 324 325 return 0; 326 327 error: 328 FTL_ERRLOG(dev, "Insufficient NV Cache capacity to preserve metadata\n"); 329 return -1; 330 } 331 332 static ftl_addr 333 layout_base_offset(struct spdk_ftl_dev *dev) 334 { 335 ftl_addr addr; 336 337 addr = dev->num_bands * ftl_get_num_blocks_in_band(dev); 338 return addr; 339 } 340 341 static int 342 setup_layout_base(struct spdk_ftl_dev *dev) 343 { 344 uint64_t left, offset; 345 struct ftl_layout *layout = &dev->layout; 346 struct ftl_layout_region *region; 347 uint64_t data_base_alignment = 8 * ftl_bitmap_buffer_alignment; 348 /* Allocating a ftl_bitmap requires a 8B input buffer alignment, since we're reusing the global valid map md buffer 349 * this means that each band starting address needs to be aligned too - each device sector takes 1b in the valid map, 350 * so 64 sectors (8*8) is the needed alignment 351 */ 352 353 layout->base.num_usable_blocks = ftl_get_num_blocks_in_band(dev); 354 layout->base.user_blocks = ftl_band_user_blocks(dev->bands); 355 356 /* Base device layout is following: 357 * - superblock 358 * - data 359 * - valid map 360 */ 361 offset = layout->region[FTL_LAYOUT_REGION_TYPE_SB_BASE].current.blocks; 362 offset = SPDK_ALIGN_CEIL(offset, data_base_alignment); 363 364 /* Setup data region on base device */ 365 region = &layout->region[FTL_LAYOUT_REGION_TYPE_DATA_BASE]; 366 region->type = FTL_LAYOUT_REGION_TYPE_DATA_BASE; 367 region->name = "data_btm"; 368 region->current.version = region->prev.version = 0; 369 region->current.offset = offset; 370 region->current.blocks = layout_base_offset(dev); 371 set_region_bdev_btm(region, dev); 372 373 offset += region->current.blocks; 374 375 /* Setup validity map */ 376 region = &layout->region[FTL_LAYOUT_REGION_TYPE_VALID_MAP]; 377 region->type = FTL_LAYOUT_REGION_TYPE_VALID_MAP; 378 region->name = "vmap"; 379 region->current.version = region->prev.version = 0; 380 region->current.offset = offset; 381 region->current.blocks = blocks_region(spdk_divide_round_up( 382 layout->base.total_blocks + layout->nvc.total_blocks, 8)); 383 set_region_bdev_btm(region, dev); 384 offset += region->current.blocks; 385 386 /* Checking for underflow */ 387 left = layout->base.total_blocks - offset; 388 if (left > layout->base.total_blocks) { 389 FTL_ERRLOG(dev, "Error when setup base device layout\n"); 390 return -1; 391 } 392 393 if (offset > layout->base.total_blocks) { 394 FTL_ERRLOG(dev, "Error when setup base device layout\n"); 395 return -1; 396 } 397 398 return 0; 399 } 400 401 int 402 ftl_layout_setup(struct spdk_ftl_dev *dev) 403 { 404 const struct spdk_bdev *bdev; 405 struct ftl_layout *layout = &dev->layout; 406 uint64_t i; 407 uint64_t num_lbas; 408 409 bdev = spdk_bdev_desc_get_bdev(dev->base_bdev_desc); 410 layout->base.total_blocks = spdk_bdev_get_num_blocks(bdev); 411 412 bdev = spdk_bdev_desc_get_bdev(dev->nv_cache.bdev_desc); 413 layout->nvc.total_blocks = spdk_bdev_get_num_blocks(bdev); 414 415 /* Initialize mirrors types */ 416 for (i = 0; i < FTL_LAYOUT_REGION_TYPE_MAX; ++i) { 417 if (i == FTL_LAYOUT_REGION_TYPE_SB) { 418 /* Super block has been already initialized */ 419 continue; 420 } 421 422 layout->region[i].mirror_type = FTL_LAYOUT_REGION_TYPE_INVALID; 423 } 424 425 /* 426 * Initialize L2P information 427 */ 428 num_lbas = get_num_user_lbas(dev); 429 if (dev->num_lbas == 0) { 430 assert(dev->conf.mode & SPDK_FTL_MODE_CREATE); 431 dev->num_lbas = num_lbas; 432 dev->sb->lba_cnt = num_lbas; 433 } else if (dev->num_lbas != num_lbas) { 434 FTL_ERRLOG(dev, "Mismatched FTL num_lbas\n"); 435 return -EINVAL; 436 } 437 layout->l2p.addr_length = spdk_u64log2(layout->base.total_blocks + layout->nvc.total_blocks) + 1; 438 layout->l2p.addr_size = layout->l2p.addr_length > 32 ? 8 : 4; 439 layout->l2p.lbas_in_page = FTL_BLOCK_SIZE / layout->l2p.addr_size; 440 441 /* Setup P2L ckpt */ 442 layout->p2l.ckpt_pages = spdk_divide_round_up(ftl_get_num_blocks_in_band(dev), dev->xfer_size); 443 444 if (setup_layout_nvc(dev)) { 445 return -EINVAL; 446 } 447 448 if (setup_layout_base(dev)) { 449 return -EINVAL; 450 } 451 452 if (ftl_validate_regions(dev, layout)) { 453 return -EINVAL; 454 } 455 456 FTL_NOTICELOG(dev, "Base device capacity: %.2f MiB\n", 457 blocks2mib(layout->base.total_blocks)); 458 FTL_NOTICELOG(dev, "NV cache device capacity: %.2f MiB\n", 459 blocks2mib(layout->nvc.total_blocks)); 460 FTL_NOTICELOG(dev, "L2P entries: %"PRIu64"\n", dev->num_lbas); 461 FTL_NOTICELOG(dev, "L2P address size: %"PRIu64"\n", layout->l2p.addr_size); 462 FTL_NOTICELOG(dev, "P2L checkpoint pages: %"PRIu64"\n", layout->p2l.ckpt_pages); 463 464 return 0; 465 } 466 467 #ifdef SPDK_FTL_VSS_EMU 468 void 469 ftl_layout_setup_vss_emu(struct spdk_ftl_dev *dev) 470 { 471 const struct spdk_bdev *bdev; 472 struct ftl_layout *layout = &dev->layout; 473 struct ftl_layout_region *region = &layout->region[FTL_LAYOUT_REGION_TYPE_VSS]; 474 475 assert(layout->md[FTL_LAYOUT_REGION_TYPE_VSS] == NULL); 476 477 region = &layout->region[FTL_LAYOUT_REGION_TYPE_VSS]; 478 region->type = FTL_LAYOUT_REGION_TYPE_VSS; 479 region->name = "vss"; 480 region->current.version = region->prev.version = 0; 481 region->current.offset = 0; 482 483 bdev = spdk_bdev_desc_get_bdev(dev->nv_cache.bdev_desc); 484 layout->nvc.total_blocks = spdk_bdev_get_num_blocks(bdev); 485 region->current.blocks = blocks_region(dev->nv_cache.md_size * layout->nvc.total_blocks); 486 487 region->vss_blksz = 0; 488 region->bdev_desc = dev->nv_cache.bdev_desc; 489 region->ioch = dev->nv_cache.cache_ioch; 490 491 assert(region->bdev_desc != NULL); 492 assert(region->ioch != NULL); 493 } 494 #endif 495 496 int 497 ftl_layout_setup_superblock(struct spdk_ftl_dev *dev) 498 { 499 struct ftl_layout *layout = &dev->layout; 500 struct ftl_layout_region *region = &layout->region[FTL_LAYOUT_REGION_TYPE_SB]; 501 uint64_t total_blocks, offset, left; 502 503 assert(layout->md[FTL_LAYOUT_REGION_TYPE_SB] == NULL); 504 505 /* Initialize superblock region */ 506 region->type = FTL_LAYOUT_REGION_TYPE_SB; 507 region->mirror_type = FTL_LAYOUT_REGION_TYPE_SB_BASE; 508 region->name = "sb"; 509 region->current.version = FTL_SB_VERSION_CURRENT; 510 region->prev.version = FTL_SB_VERSION_CURRENT; 511 region->current.offset = 0; 512 513 /* 514 * VSS region must go first in case SB to make calculating its relative size easier 515 */ 516 #ifdef SPDK_FTL_VSS_EMU 517 region->current.offset = layout->region[FTL_LAYOUT_REGION_TYPE_VSS].current.offset + 518 layout->region[FTL_LAYOUT_REGION_TYPE_VSS].current.blocks; 519 #endif 520 521 region->current.blocks = blocks_region(FTL_SUPERBLOCK_SIZE); 522 region->vss_blksz = 0; 523 region->bdev_desc = dev->nv_cache.bdev_desc; 524 region->ioch = dev->nv_cache.cache_ioch; 525 526 assert(region->bdev_desc != NULL); 527 assert(region->ioch != NULL); 528 529 region = &layout->region[FTL_LAYOUT_REGION_TYPE_SB_BASE]; 530 region->type = FTL_LAYOUT_REGION_TYPE_SB_BASE; 531 region->mirror_type = FTL_LAYOUT_REGION_TYPE_INVALID; 532 region->name = "sb_mirror"; 533 region->current.version = FTL_SB_VERSION_CURRENT; 534 region->prev.version = FTL_SB_VERSION_CURRENT; 535 region->current.offset = 0; 536 region->current.blocks = blocks_region(FTL_SUPERBLOCK_SIZE); 537 set_region_bdev_btm(region, dev); 538 539 /* Check if SB can be stored at the end of base device */ 540 total_blocks = spdk_bdev_get_num_blocks( 541 spdk_bdev_desc_get_bdev(dev->base_bdev_desc)); 542 offset = region->current.offset + region->current.blocks; 543 left = total_blocks - offset; 544 if ((left > total_blocks) || (offset > total_blocks)) { 545 FTL_ERRLOG(dev, "Error when setup base device super block\n"); 546 return -1; 547 } 548 549 return 0; 550 } 551 552 void 553 ftl_layout_dump(struct spdk_ftl_dev *dev) 554 { 555 struct ftl_layout *layout = &dev->layout; 556 int i; 557 FTL_NOTICELOG(dev, "NV cache layout:\n"); 558 for (i = 0; i < FTL_LAYOUT_REGION_TYPE_MAX; ++i) { 559 if (layout->region[i].bdev_desc == dev->nv_cache.bdev_desc) { 560 dump_region(dev, &layout->region[i]); 561 } 562 } 563 FTL_NOTICELOG(dev, "Bottom device layout:\n"); 564 for (i = 0; i < FTL_LAYOUT_REGION_TYPE_MAX; ++i) { 565 if (layout->region[i].bdev_desc == dev->base_bdev_desc) { 566 dump_region(dev, &layout->region[i]); 567 } 568 } 569 } 570