1 /* 2 * (C) Copyright 2017 Rockchip Electronics Co., Ltd 3 * 4 * SPDX-License-Identifier: GPL-2.0+ 5 */ 6 7 #include <common.h> 8 #include <image.h> 9 #include <android_image.h> 10 #include <malloc.h> 11 #include <mapmem.h> 12 #include <errno.h> 13 #include <command.h> 14 #include <mmc.h> 15 #include <blk.h> 16 #include <part.h> 17 #include <android_avb/avb_ops_user.h> 18 #include <android_avb/libavb_ab.h> 19 #include <android_avb/avb_atx_validate.h> 20 #include <android_avb/avb_atx_types.h> 21 #include <optee_include/OpteeClientInterface.h> 22 #include <optee_include/tee_api_defines.h> 23 #include <android_avb/avb_vbmeta_image.h> 24 #include <android_avb/avb_atx_validate.h> 25 #include <android_avb/rk_avb_ops_user.h> 26 #include <boot_rkimg.h> 27 28 /* rk used */ 29 int rk_avb_get_perm_attr_cer(uint8_t *cer, uint32_t size) 30 { 31 #ifdef CONFIG_OPTEE_CLIENT 32 if (trusty_read_permanent_attributes_cer((uint8_t *)cer, size)) 33 return -EIO; 34 35 return 0; 36 #else 37 return -1; 38 #endif 39 } 40 41 int rk_avb_set_perm_attr_cer(uint8_t *cer, uint32_t size) 42 { 43 #ifdef CONFIG_OPTEE_CLIENT 44 if (trusty_write_permanent_attributes_cer((uint8_t *)cer, size)) 45 return -EIO; 46 47 return 0; 48 #else 49 return -1; 50 #endif 51 } 52 53 int rk_avb_read_slot_count(char *slot_count) 54 { 55 *slot_count = SLOT_NUM; 56 57 return 0; 58 } 59 60 int rk_avb_read_slot_suffixes(char *slot_suffixes) 61 { 62 memcpy(slot_suffixes, CURR_SYSTEM_SLOT_SUFFIX, 63 strlen(CURR_SYSTEM_SLOT_SUFFIX)); 64 65 return 0; 66 } 67 68 int rk_avb_set_slot_active(unsigned int *slot_number) 69 { 70 AvbOps* ops; 71 ops = avb_ops_user_new(); 72 int ret = 0; 73 74 if (ops == NULL) { 75 printf("avb_ops_user_new() failed!\n"); 76 return -1; 77 } 78 79 debug("set_slot_active\n"); 80 if (avb_ab_mark_slot_active(ops->ab_ops, *slot_number) != 0) { 81 printf("set_slot_active error!\n"); 82 ret = -1; 83 } 84 85 avb_ops_user_free(ops); 86 return ret; 87 } 88 89 static bool slot_is_bootable(AvbABSlotData* slot) { 90 return (slot->priority > 0) && 91 (slot->successful_boot || (slot->tries_remaining > 0)); 92 } 93 94 AvbABFlowResult rk_avb_ab_slot_select(AvbABOps* ab_ops,char* select_slot) 95 { 96 AvbABFlowResult ret = AVB_AB_FLOW_RESULT_OK; 97 AvbIOResult io_ret = AVB_IO_RESULT_OK; 98 AvbABData ab_data; 99 size_t slot_index_to_boot; 100 101 io_ret = ab_ops->read_ab_metadata(ab_ops, &ab_data); 102 if (io_ret != AVB_IO_RESULT_OK) { 103 avb_error("I/O error while loading A/B metadata.\n"); 104 ret = AVB_AB_FLOW_RESULT_ERROR_IO; 105 goto out; 106 } 107 if (slot_is_bootable(&ab_data.slots[0]) && slot_is_bootable(&ab_data.slots[1])) { 108 if (ab_data.slots[1].priority > ab_data.slots[0].priority) { 109 slot_index_to_boot = 1; 110 } else { 111 slot_index_to_boot = 0; 112 } 113 } else if(slot_is_bootable(&ab_data.slots[0])) { 114 slot_index_to_boot = 0; 115 } else if(slot_is_bootable(&ab_data.slots[1])) { 116 slot_index_to_boot = 1; 117 } else { 118 avb_error("No bootable slots found.\n"); 119 ret = AVB_AB_FLOW_RESULT_ERROR_NO_BOOTABLE_SLOTS; 120 goto out; 121 } 122 123 if (slot_index_to_boot == 0) { 124 strcpy(select_slot, "_a"); 125 } else if(slot_index_to_boot == 1) { 126 strcpy(select_slot, "_b"); 127 } 128 out: 129 return ret; 130 } 131 132 int rk_avb_get_current_slot(char *select_slot) 133 { 134 AvbOps* ops; 135 int ret = 0; 136 137 ops = avb_ops_user_new(); 138 if (ops == NULL) { 139 printf("avb_ops_user_new() failed!\n"); 140 return -1; 141 } 142 143 if (rk_avb_ab_slot_select(ops->ab_ops, select_slot) != 0) { 144 printf("###There is no bootable slot, bring up last_boot!###\n"); 145 if (rk_get_lastboot() == 1) 146 memcpy(select_slot, "_b", 2); 147 else if(rk_get_lastboot() == 0) 148 memcpy(select_slot, "_a", 2); 149 else 150 return -1; 151 ret = 0; 152 } 153 154 avb_ops_user_free(ops); 155 return ret; 156 } 157 158 int rk_avb_read_permanent_attributes(uint8_t *attributes, uint32_t size) 159 { 160 #ifdef CONFIG_OPTEE_CLIENT 161 if(trusty_read_permanent_attributes(attributes, size) != 0) { 162 printf("trusty_read_permanent_attributes failed!\n"); 163 return -1; 164 } 165 166 return 0; 167 #else 168 return -1; 169 #endif 170 } 171 172 int rk_avb_write_permanent_attributes(uint8_t *attributes, uint32_t size) 173 { 174 #ifdef CONFIG_OPTEE_CLIENT 175 if(trusty_write_permanent_attributes(attributes, size) != 0) { 176 printf("trusty_write_permanent_attributes failed!\n"); 177 return -1; 178 } 179 180 return 0; 181 #else 182 return -1; 183 #endif 184 } 185 186 int rk_avb_read_flash_lock_state(uint8_t *flash_lock_state) 187 { 188 #ifdef CONFIG_OPTEE_CLIENT 189 int ret; 190 191 ret = trusty_read_flash_lock_state(flash_lock_state); 192 switch (ret) { 193 case TEE_SUCCESS: 194 break; 195 case TEE_ERROR_GENERIC: 196 case TEE_ERROR_NO_DATA: 197 case TEE_ERROR_ITEM_NOT_FOUND: 198 *flash_lock_state = 1; 199 if (trusty_write_flash_lock_state(*flash_lock_state)) { 200 avb_error("trusty_write_flash_lock_state error!"); 201 ret = -1; 202 } else { 203 ret = trusty_read_flash_lock_state(flash_lock_state); 204 } 205 break; 206 default: 207 printf("%s: trusty_read_flash_lock_state failed\n", __FILE__); 208 } 209 210 return ret; 211 #else 212 return -1; 213 #endif 214 } 215 216 int rk_avb_write_flash_lock_state(uint8_t flash_lock_state) 217 { 218 #ifdef CONFIG_OPTEE_CLIENT 219 if (trusty_write_flash_lock_state(flash_lock_state)) { 220 printf("trusty_write_flash_lock_state error!\n"); 221 return -1; 222 } 223 224 return 0; 225 #else 226 return -1; 227 #endif 228 } 229 230 int rk_avb_write_lock_state(uint8_t lock_state) 231 { 232 #ifdef CONFIG_OPTEE_CLIENT 233 if (trusty_write_lock_state(lock_state)) { 234 printf("trusty_write_lock_state error!\n"); 235 return -1; 236 } 237 238 return 0; 239 #else 240 return -1; 241 #endif 242 } 243 244 int rk_avb_read_lock_state(uint8_t *lock_state) 245 { 246 #ifdef CONFIG_OPTEE_CLIENT 247 int ret; 248 249 ret = trusty_read_lock_state(lock_state); 250 switch (ret) { 251 case TEE_SUCCESS: 252 break; 253 case TEE_ERROR_GENERIC: 254 case TEE_ERROR_NO_DATA: 255 case TEE_ERROR_ITEM_NOT_FOUND: 256 *lock_state = 1; 257 if (rk_avb_write_lock_state(*lock_state)) { 258 avb_error("avb_write_lock_state error!"); 259 ret = -1; 260 } else { 261 ret = trusty_read_lock_state(lock_state); 262 } 263 break; 264 default: 265 printf("%s: trusty_read_lock_state failed\n", __FILE__); 266 } 267 268 return ret; 269 #else 270 return -1; 271 #endif 272 } 273 274 int rk_avb_write_perm_attr_flag(uint8_t flag) 275 { 276 #ifdef CONFIG_OPTEE_CLIENT 277 if (trusty_write_permanent_attributes_flag(flag)) { 278 printf("trusty_write_permanent_attributes_flag error!\n"); 279 return -1; 280 } 281 282 return 0; 283 #else 284 return -1; 285 #endif 286 } 287 288 int rk_avb_read_perm_attr_flag(uint8_t *flag) 289 { 290 #ifdef CONFIG_OPTEE_CLIENT 291 int ret; 292 293 ret = trusty_read_permanent_attributes_flag(flag); 294 switch (ret) { 295 case TEE_SUCCESS: 296 break; 297 case TEE_ERROR_GENERIC: 298 case TEE_ERROR_NO_DATA: 299 case TEE_ERROR_ITEM_NOT_FOUND: 300 *flag = 0; 301 if (rk_avb_write_perm_attr_flag(*flag)) { 302 avb_error("avb_write_perm_attr_flag error!"); 303 ret = -1; 304 } else { 305 ret = trusty_read_permanent_attributes_flag(flag); 306 } 307 break; 308 default: 309 printf("%s: trusty_read_permanent_attributes_flag failed", 310 __FILE__); 311 } 312 313 return ret; 314 #else 315 return -1; 316 #endif 317 } 318 319 int rk_avb_read_vbootkey_hash(uint8_t *buf, uint8_t length) 320 { 321 #ifdef CONFIG_OPTEE_CLIENT 322 if (trusty_read_vbootkey_hash((uint32_t *)buf, 323 (uint32_t)length / sizeof(uint32_t))) { 324 printf("trusty_read_vbootkey_hash error!\n"); 325 return -1; 326 } 327 328 return 0; 329 #else 330 return -1; 331 #endif 332 } 333 334 int rk_avb_write_vbootkey_hash(uint8_t *buf, uint8_t length) 335 { 336 #ifdef CONFIG_OPTEE_CLIENT 337 if (trusty_write_vbootkey_hash((uint32_t *)buf, 338 (uint32_t)length / sizeof(uint32_t))) { 339 printf("trusty_write_vbootkey_hash error!\n"); 340 return -1; 341 } 342 343 return 0; 344 #else 345 return -1; 346 #endif 347 } 348 349 int rk_avb_close_optee_client(void) 350 { 351 #ifdef CONFIG_OPTEE_CLIENT 352 if(trusty_notify_optee_uboot_end()) { 353 printf("trusty_notify_optee_uboot_end error!\n"); 354 return -1; 355 } 356 357 return 0; 358 #else 359 return -1; 360 #endif 361 } 362 363 int rk_avb_read_attribute_hash(uint8_t *buf, uint8_t length) 364 { 365 #ifdef CONFIG_OPTEE_CLIENT 366 if (trusty_read_attribute_hash((uint32_t *)buf, 367 (uint32_t)(length/sizeof(uint32_t)))) { 368 printf("trusty_read_attribute_hash error!\n"); 369 return -1; 370 } 371 372 return 0; 373 #else 374 return -1; 375 #endif 376 } 377 378 int rk_avb_write_attribute_hash(uint8_t *buf, uint8_t length) 379 { 380 #ifdef CONFIG_OPTEE_CLIENT 381 if (trusty_write_attribute_hash((uint32_t *)buf, 382 (uint32_t)(length/sizeof(uint32_t)))) { 383 printf("trusty_write_attribute_hash error!\n"); 384 return -1; 385 } 386 387 return 0; 388 #else 389 return -1; 390 #endif 391 } 392 393 int rk_avb_read_all_rollback_index(char *buffer) 394 { 395 AvbOps* ops; 396 uint64_t stored_rollback_index = 0; 397 AvbIOResult io_ret; 398 char temp[ROLLBACK_MAX_SIZE] = {0}; 399 int n; 400 401 ops = avb_ops_user_new(); 402 if (ops == NULL) { 403 printf("avb_ops_user_new() failed!\n"); 404 return -1; 405 } 406 407 for (n = 0; n < AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_LOCATIONS; n++) { 408 io_ret = ops->read_rollback_index( 409 ops, n, &stored_rollback_index); 410 if (io_ret != AVB_IO_RESULT_OK) 411 goto out; 412 snprintf(temp, sizeof(int) + 1, "%d", n); 413 strncat(buffer, temp, ROLLBACK_MAX_SIZE); 414 strncat(buffer, ":", 1); 415 snprintf(temp, sizeof(uint64_t) + 1, "%lld", 416 stored_rollback_index); 417 strncat(buffer, temp, ROLLBACK_MAX_SIZE); 418 strncat(buffer, ",", 1); 419 } 420 421 io_ret = 422 ops->read_rollback_index(ops, 423 AVB_ATX_PIK_VERSION_LOCATION, 424 &stored_rollback_index); 425 if (io_ret != AVB_IO_RESULT_OK) { 426 avb_error("Failed to read PIK minimum version.\n"); 427 goto out; 428 } 429 /* PIK rollback index */ 430 snprintf(temp, sizeof(int) + 1, "%d", AVB_ATX_PIK_VERSION_LOCATION); 431 strncat(buffer, temp, ROLLBACK_MAX_SIZE); 432 strncat(buffer, ":", 1); 433 snprintf(temp, sizeof(uint64_t) + 1, "%lld", stored_rollback_index); 434 strncat(buffer, temp, ROLLBACK_MAX_SIZE); 435 strncat(buffer, ",", 1); 436 io_ret = ops->read_rollback_index(ops, 437 AVB_ATX_PSK_VERSION_LOCATION, 438 &stored_rollback_index); 439 if (io_ret != AVB_IO_RESULT_OK) { 440 avb_error("Failed to read PSK minimum version.\n"); 441 goto out; 442 } 443 /* PSK rollback index */ 444 snprintf(temp, sizeof(int) + 1, "%d", AVB_ATX_PSK_VERSION_LOCATION); 445 strncat(buffer, temp, ROLLBACK_MAX_SIZE); 446 strncat(buffer, ":", 1); 447 snprintf(temp, sizeof(uint64_t) + 1, "%lld", stored_rollback_index); 448 strncat(buffer, temp, ROLLBACK_MAX_SIZE); 449 debug("%s\n", buffer); 450 avb_ops_user_free(ops); 451 452 return 0; 453 out: 454 avb_ops_user_free(ops); 455 456 return -1; 457 } 458 459 int rk_avb_read_bootloader_locked_flag(uint8_t *flag) 460 { 461 #ifdef CONFIG_OPTEE_CLIENT 462 if (trusty_read_vbootkey_enable_flag(flag)) { 463 return -1; 464 } 465 return 0; 466 #else 467 return -1; 468 #endif 469 } 470 471 #ifdef CONFIG_SUPPORT_EMMC_RPMB 472 static int curr_device = -1; 473 474 int rk_bootloader_rollback_index_read(uint32_t offset, uint32_t bytes, 475 void *rb_index) 476 { 477 478 struct mmc *mmc; 479 uint8_t rpmb_buf[256] = {0}; 480 uint32_t n; 481 char original_part; 482 483 if ((offset + bytes) > 256) 484 return -1; 485 486 if (curr_device < 0) { 487 if (get_mmc_num() > 0) 488 curr_device = 0; 489 else { 490 avb_error("No MMC device available"); 491 return -1; 492 } 493 } 494 495 mmc = find_mmc_device(curr_device); 496 /* Switch to the RPMB partition */ 497 #ifndef CONFIG_BLK 498 original_part = mmc->block_dev.hwpart; 499 #else 500 original_part = mmc_get_blk_desc(mmc)->hwpart; 501 #endif 502 if (blk_select_hwpart_devnum(IF_TYPE_MMC, curr_device, MMC_PART_RPMB) != 503 0) 504 return -1; 505 506 n = mmc_rpmb_read(mmc, rpmb_buf, RPMB_BASE_ADDR, 1, NULL); 507 if (n != 1) 508 return -1; 509 510 /* Return to original partition */ 511 if (blk_select_hwpart_devnum(IF_TYPE_MMC, curr_device, original_part) != 512 0) 513 return -1; 514 515 memcpy(rb_index, (void*)&rpmb_buf[offset], bytes); 516 517 return 0; 518 } 519 520 int rk_avb_get_bootloader_min_version(char *buffer) 521 { 522 uint32_t rb_index; 523 char temp[ROLLBACK_MAX_SIZE] = {0}; 524 525 if (rk_bootloader_rollback_index_read(UBOOT_RB_INDEX_OFFSET, 526 sizeof(uint32_t), &rb_index)) { 527 avb_error("Can not read uboot rollback index"); 528 return -1; 529 } 530 snprintf(temp, sizeof(int) + 1, "%d", 0); 531 strncat(buffer, temp, ROLLBACK_MAX_SIZE); 532 strncat(buffer, ":", 1); 533 snprintf(temp, sizeof(uint32_t) + 1, "%d", rb_index); 534 strncat(buffer, temp, ROLLBACK_MAX_SIZE); 535 strncat(buffer, ",", 1); 536 537 if (rk_bootloader_rollback_index_read(TRUST_RB_INDEX_OFFSET, 538 sizeof(uint32_t), &rb_index)) { 539 avb_error("Can not read trust rollback index"); 540 return -1; 541 } 542 543 snprintf(temp, sizeof(int) + 1, "%d", 1); 544 strncat(buffer, temp, ROLLBACK_MAX_SIZE); 545 strncat(buffer, ":", 1); 546 snprintf(temp, sizeof(uint32_t) + 1, "%d", rb_index); 547 strncat(buffer, temp, ROLLBACK_MAX_SIZE); 548 549 return 0; 550 } 551 #endif 552 553 void rk_avb_get_at_vboot_state(char *buf) 554 { 555 char temp_flag = 0; 556 char *lock_val = NULL; 557 char *unlock_dis_val = NULL; 558 char *perm_attr_flag = NULL; 559 char *bootloader_locked_flag = NULL; 560 char *rollback_indices; 561 char min_versions[ROLLBACK_MAX_SIZE + 1] = {0}; 562 int n; 563 564 if (rk_avb_read_perm_attr_flag((uint8_t *)&temp_flag)) { 565 avb_error("Can not read perm_attr_flag!"); 566 perm_attr_flag = ""; 567 } else { 568 perm_attr_flag = temp_flag ? "1" : "0"; 569 } 570 571 temp_flag = 0; 572 if (rk_avb_read_lock_state((uint8_t *)&temp_flag)) { 573 avb_error("Can not read lock state!"); 574 lock_val = ""; 575 unlock_dis_val = ""; 576 } else { 577 lock_val = (temp_flag & LOCK_MASK) ? "0" : "1"; 578 unlock_dis_val = (temp_flag & UNLOCK_DISABLE_MASK) ? "1" : "0"; 579 } 580 581 temp_flag = 0; 582 if (rk_avb_read_bootloader_locked_flag((uint8_t *)&temp_flag)) { 583 avb_error("Can not read bootloader locked flag!"); 584 bootloader_locked_flag = ""; 585 } else { 586 bootloader_locked_flag = temp_flag ? "1" : "0"; 587 } 588 589 rollback_indices = malloc(VBOOT_STATE_SIZE); 590 if (!rollback_indices) { 591 avb_error("No buff to malloc!"); 592 return; 593 } 594 595 memset(rollback_indices, 0, VBOOT_STATE_SIZE); 596 if (rk_avb_read_all_rollback_index(rollback_indices)) 597 avb_error("Can not read avb_min_ver!"); 598 599 /* bootloader-min-versions */ 600 if (rk_avb_get_bootloader_min_version(min_versions)) 601 avb_error("Call rk_avb_get_bootloader_min_version error!"); 602 603 n = snprintf(buf, VBOOT_STATE_SIZE - 1, 604 "avb-perm-attr-set=%s\n" 605 "avb-locked=%s\n" 606 "avb-unlock-disabled=%s\n" 607 "bootloader-locked=%s\n" 608 "avb-min-versions=%s\n" 609 "bootloader-min-versions=%s\n", 610 perm_attr_flag, 611 lock_val, 612 unlock_dis_val, 613 bootloader_locked_flag, 614 rollback_indices, 615 min_versions); 616 if (n >= VBOOT_STATE_SIZE) { 617 avb_error("The VBOOT_STATE buf is truncated\n"); 618 buf[VBOOT_STATE_SIZE - 1] = 0; 619 } 620 debug("The vboot state buf is %s\n", buf); 621 free(rollback_indices); 622 } 623 624 int rk_avb_get_ab_info(AvbABData* ab_data) 625 { 626 AvbOps* ops; 627 AvbIOResult io_ret = AVB_IO_RESULT_OK; 628 int ret = 0; 629 630 ops = avb_ops_user_new(); 631 if (ops == NULL) { 632 printf("%s: avb_ops_user_new() failed!\n", __FILE__); 633 return -1; 634 } 635 636 io_ret = ops->ab_ops->read_ab_metadata(ops->ab_ops, ab_data); 637 if (io_ret != AVB_IO_RESULT_OK) { 638 avb_error("I/O error while loading A/B metadata.\n"); 639 ret = -1; 640 } 641 642 avb_ops_user_free(ops); 643 644 return ret; 645 } 646 647 int rk_avb_get_part_has_slot_info(const char *base_name) 648 { 649 char *part_name; 650 int part_num; 651 size_t part_name_len; 652 disk_partition_t part_info; 653 struct blk_desc *dev_desc; 654 const char *slot_suffix = "_a"; 655 656 dev_desc = rockchip_get_bootdev(); 657 if (!dev_desc) { 658 printf("%s: Could not find device!\n", __func__); 659 return -1; 660 } 661 662 if (base_name == NULL) { 663 printf("The base_name is NULL!\n"); 664 return -1; 665 } 666 667 part_name_len = strlen(base_name) + 1; 668 part_name_len += strlen(slot_suffix); 669 part_name = malloc(part_name_len); 670 if (!part_name) { 671 printf("%s can not malloc a buffer!\n", __FILE__); 672 return -1; 673 } 674 675 memset(part_name, 0, part_name_len); 676 snprintf(part_name, part_name_len, "%s%s", base_name, slot_suffix); 677 part_num = part_get_info_by_name(dev_desc, part_name, &part_info); 678 if (part_num < 0) { 679 printf("Could not find partition \"%s\"\n", part_name); 680 part_num = -1; 681 } 682 683 free(part_name); 684 return part_num; 685 } 686 687 int rk_auth_unlock(void *buffer, char *out_is_trusted) 688 { 689 AvbOps* ops; 690 691 ops = avb_ops_user_new(); 692 if (ops == NULL) { 693 avb_error("avb_ops_user_new() failed!"); 694 return -1; 695 } 696 697 if (avb_atx_validate_unlock_credential(ops->atx_ops, 698 (AvbAtxUnlockCredential*)buffer, 699 (bool*)out_is_trusted)) { 700 avb_ops_user_free(ops); 701 return -1; 702 } 703 avb_ops_user_free(ops); 704 if (*out_is_trusted == true) 705 return 0; 706 else 707 return -1; 708 } 709 710 int rk_generate_unlock_challenge(void *buffer, uint32_t *challenge_len) 711 { 712 AvbOps* ops; 713 AvbIOResult result = AVB_IO_RESULT_OK; 714 715 ops = avb_ops_user_new(); 716 if (ops == NULL) { 717 avb_error("avb_ops_user_new() failed!"); 718 return -1; 719 } 720 721 result = avb_atx_generate_unlock_challenge(ops->atx_ops, 722 (AvbAtxUnlockChallenge *)buffer); 723 avb_ops_user_free(ops); 724 *challenge_len = sizeof(AvbAtxUnlockChallenge); 725 if (result == AVB_IO_RESULT_OK) 726 return 0; 727 else 728 return -1; 729 } 730 731 int rk_get_lastboot(void) 732 { 733 734 AvbIOResult io_ret = AVB_IO_RESULT_OK; 735 AvbABData ab_data; 736 int lastboot = -1; 737 AvbOps* ops; 738 739 ops = avb_ops_user_new(); 740 if (ops == NULL) { 741 printf("avb_ops_user_new() failed!\n"); 742 return -1; 743 } 744 745 io_ret = ops->ab_ops->read_ab_metadata(ops->ab_ops, &ab_data); 746 if (io_ret != AVB_IO_RESULT_OK) { 747 avb_error("I/O error while loading A/B metadata.\n"); 748 goto out; 749 } 750 751 lastboot = ab_data.last_boot; 752 out: 753 avb_ops_user_free(ops); 754 755 return lastboot; 756 } 757