blob: 58edf5e305cdb626b7c4ca1c835a026f16dc116d [file] [log] [blame]
Steve Pfetschd7b03992016-04-20 17:53:38 -07001/*
2 * Copyright (c) 2016, The Linux Foundation. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above
10 * copyright notice, this list of conditions and the following
11 * disclaimer in the documentation and/or other materials provided
12 * with the distribution.
13 * * Neither the name of The Linux Foundation nor the names of its
14 * contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
18 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
21 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
24 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
25 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
26 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
27 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29#include <errno.h>
30#define LOG_TAG "bootcontrolhal"
31#include <cutils/log.h>
32#include <hardware/boot_control.h>
33#include <stdio.h>
34#include <string.h>
35#include <unistd.h>
36#include <dirent.h>
37#include <sys/types.h>
38#include <sys/stat.h>
39#include <fcntl.h>
40#include <limits.h>
41#include "gpt-utils.h"
42
43#define BOOTDEV_DIR "/dev/block/bootdevice/by-name"
44#define BOOT_IMG_PTN_NAME "boot"
45#define LUN_NAME_END_LOC 14
46
47const char *slot_suffix_arr[] = {
48 AB_SLOT_A_SUFFIX,
49 AB_SLOT_B_SUFFIX,
50 NULL};
51
52enum part_attr_type {
53 ATTR_SLOT_ACTIVE = 0,
54 ATTR_BOOT_SUCCESSFUL,
55 ATTR_UNBOOTABLE,
56};
57
58void boot_control_init(struct boot_control_module *module)
59{
60 if (!module) {
61 ALOGE("Invalid argument passed to %s", __func__);
62 return;
63 }
64 return;
65}
66
67//Get the value of one of the attribute fields for a partition.
68static int get_partition_attribute(char *partname,
69 enum part_attr_type part_attr)
70{
71 struct gpt_disk *disk = NULL;
72 uint8_t *pentry = NULL;
73 int retval = -1;
74 uint8_t *attr = NULL;
75 if (!partname)
76 goto error;
77 disk = gpt_disk_alloc();
78 if (!disk) {
79 ALOGE("%s: Failed to alloc disk struct", __func__);
80 goto error;
81 }
82 if (gpt_disk_get_disk_info(partname, disk)) {
83 ALOGE("%s: Failed to get disk info", __func__);
84 goto error;
85 }
86 pentry = gpt_disk_get_pentry(disk, partname, PRIMARY_GPT);
87 if (!pentry) {
88 ALOGE("%s: pentry does not exist in disk struct",
89 __func__);
90 goto error;
91 }
92 attr = pentry + AB_FLAG_OFFSET;
93 if (part_attr == ATTR_SLOT_ACTIVE)
94 retval = !!(*attr & AB_PARTITION_ATTR_SLOT_ACTIVE);
95 else if (part_attr == ATTR_BOOT_SUCCESSFUL)
96 retval = !!(*attr & AB_PARTITION_ATTR_BOOT_SUCCESSFUL);
97 else if (part_attr == ATTR_UNBOOTABLE)
98 retval = !!(*attr & AB_PARTITION_ATTR_UNBOOTABLE);
99 else
100 retval = -1;
101 gpt_disk_free(disk);
102 return retval;
103error:
104 if (disk)
105 gpt_disk_free(disk);
106 return retval;
107}
108
109//Set a particular attribute for all the partitions in a
110//slot
111static int update_slot_attribute(const char *slot,
112 enum part_attr_type ab_attr)
113{
114 unsigned int i = 0;
115 char buf[PATH_MAX];
116 struct stat st;
117 struct gpt_disk *disk = NULL;
118 uint8_t *pentry = NULL;
119 uint8_t *pentry_bak = NULL;
120 int rc = -1;
121 uint8_t *attr = NULL;
122 uint8_t *attr_bak = NULL;
123 char partName[MAX_GPT_NAME_SIZE + 1] = {0};
124 const char ptn_list[][MAX_GPT_NAME_SIZE] = { AB_PTN_LIST };
125 int slot_name_valid = 0;
126 if (!slot) {
127 ALOGE("%s: Invalid argument", __func__);
128 goto error;
129 }
130 for (i = 0; slot_suffix_arr[i] != NULL; i++)
131 {
132 if (!strncmp(slot, slot_suffix_arr[i],
133 strlen(slot_suffix_arr[i])))
134 slot_name_valid = 1;
135 }
136 if (!slot_name_valid) {
137 ALOGE("%s: Invalid slot name", __func__);
138 goto error;
139 }
140 for (i=0; i < ARRAY_SIZE(ptn_list); i++) {
141 memset(buf, '\0', sizeof(buf));
142 //Check if A/B versions of this ptn exist
143 snprintf(buf, sizeof(buf) - 1,
144 "%s/%s%s",
145 BOOT_DEV_DIR,
146 ptn_list[i],
147 AB_SLOT_A_SUFFIX
148 );
149 if (stat(buf, &st)) {
150 //partition does not have _a version
151 continue;
152 }
153 memset(buf, '\0', sizeof(buf));
154 snprintf(buf, sizeof(buf) - 1,
155 "%s/%s%s",
156 BOOT_DEV_DIR,
157 ptn_list[i],
158 AB_SLOT_B_SUFFIX
159 );
160 if (stat(buf, &st)) {
161 //partition does not have _a version
162 continue;
163 }
164 memset(partName, '\0', sizeof(partName));
165 snprintf(partName,
166 sizeof(partName) - 1,
167 "%s%s",
168 ptn_list[i],
169 slot);
170 disk = gpt_disk_alloc(disk);
171 if (!disk) {
172 ALOGE("%s: Failed to alloc disk struct",
173 __func__);
174 goto error;
175 }
176 rc = gpt_disk_get_disk_info(partName, disk);
177 if (rc != 0) {
178 ALOGE("%s: Failed to get disk info for %s",
179 __func__,
180 partName);
181 goto error;
182 }
183 pentry = gpt_disk_get_pentry(disk, partName, PRIMARY_GPT);
184 pentry_bak = gpt_disk_get_pentry(disk, partName, SECONDARY_GPT);
185 if (!pentry || !pentry_bak) {
186 ALOGE("%s: Failed to get pentry/pentry_bak for %s",
187 __func__,
188 partName);
189 goto error;
190 }
191 attr = pentry + AB_FLAG_OFFSET;
192 attr_bak = pentry_bak + AB_FLAG_OFFSET;
193 if (ab_attr == ATTR_BOOT_SUCCESSFUL) {
194 *attr = (*attr) | AB_PARTITION_ATTR_BOOT_SUCCESSFUL;
195 *attr_bak = (*attr_bak) |
196 AB_PARTITION_ATTR_BOOT_SUCCESSFUL;
197 } else if (ab_attr == ATTR_UNBOOTABLE) {
198 *attr = (*attr) | AB_PARTITION_ATTR_UNBOOTABLE;
199 *attr_bak = (*attr_bak) | AB_PARTITION_ATTR_UNBOOTABLE;
200 } else if (ab_attr == ATTR_SLOT_ACTIVE) {
201 *attr = (*attr) | AB_PARTITION_ATTR_SLOT_ACTIVE;
202 *attr_bak = (*attr) | AB_PARTITION_ATTR_SLOT_ACTIVE;
203 } else {
204 ALOGE("%s: Unrecognized attr", __func__);
205 goto error;
206 }
207 if (gpt_disk_update_crc(disk)) {
208 ALOGE("%s: Failed to update crc for %s",
209 __func__,
210 partName);
211 goto error;
212 }
213 if (gpt_disk_commit(disk)) {
214 ALOGE("%s: Failed to write back entry for %s",
215 __func__,
216 partName);
217 goto error;
218 }
219 gpt_disk_free(disk);
220 disk = NULL;
221 }
222 return 0;
223error:
224 if (disk)
225 gpt_disk_free(disk);
226 return -1;
227}
228
229unsigned get_number_slots(struct boot_control_module *module)
230{
231 struct dirent *de = NULL;
232 DIR *dir_bootdev = NULL;
233 unsigned slot_count = 0;
234 if (!module) {
235 ALOGE("%s: Invalid argument", __func__);
236 goto error;
237 }
238 dir_bootdev = opendir(BOOTDEV_DIR);
239 if (!dir_bootdev) {
240 ALOGE("%s: Failed to open bootdev dir (%s)",
241 __func__,
242 strerror(errno));
243 goto error;
244 }
245 while ((de = readdir(dir_bootdev))) {
246 if (de->d_name[0] == '.')
247 continue;
248 if (!strncmp(de->d_name, BOOT_IMG_PTN_NAME,
249 strlen(BOOT_IMG_PTN_NAME)))
250 slot_count++;
251 }
252 closedir(dir_bootdev);
253 return slot_count;
254error:
255 if (dir_bootdev)
256 closedir(dir_bootdev);
257 return 0;
258}
259
260unsigned get_current_slot(struct boot_control_module *module)
261{
262 uint32_t num_slots = 0;
263 char bootPartition[MAX_GPT_NAME_SIZE + 1];
264 unsigned i = 0;
265 if (!module) {
266 ALOGE("%s: Invalid argument", __func__);
267 goto error;
268 }
269 num_slots = get_number_slots(module);
270 if (num_slots <= 1) {
271 //Slot 0 is the only slot around.
272 return 0;
273 }
274 //Iterate through a list of partitons named as boot+suffix
275 //and see which one is currently active.
276 for (i = 0; slot_suffix_arr[i] != NULL ; i++) {
277 memset(bootPartition, '\0', sizeof(bootPartition));
278 snprintf(bootPartition, sizeof(bootPartition) - 1,
279 "boot%s",
280 slot_suffix_arr[i]);
281 if (get_partition_attribute(bootPartition,
282 ATTR_SLOT_ACTIVE) == 1)
283 return i;
284 }
285error:
286 //The HAL spec requires that we return a number between
287 //0 to num_slots - 1. Since something went wrong here we
288 //are just going to return the default slot.
289 return 0;
290}
291
292int mark_boot_successful(struct boot_control_module *module)
293{
294 unsigned cur_slot = 0;
295 if (!module) {
296 ALOGE("%s: Invalid argument", __func__);
297 goto error;
298 }
299 cur_slot = get_current_slot(module);
300 if (update_slot_attribute(slot_suffix_arr[cur_slot],
301 ATTR_BOOT_SUCCESSFUL)) {
302 goto error;
303 }
304 return 0;
305error:
306 ALOGE("%s: Failed to mark boot successful", __func__);
307 return -1;
308}
309
310const char *get_suffix(struct boot_control_module *module, unsigned slot)
311{
312 unsigned num_slots = 0;
313 if (!module) {
314 ALOGE("%s: Invalid arg", __func__);
315 }
316 num_slots = get_number_slots(module);
317 if (num_slots < 1 || slot > num_slots - 1)
318 return NULL;
319 else
320 return slot_suffix_arr[slot];
321}
322
323int set_active_boot_slot(struct boot_control_module *module, unsigned slot)
324{
325 const char ptn_list[][MAX_GPT_NAME_SIZE] = { AB_PTN_LIST };
326 char slotA[MAX_GPT_NAME_SIZE + 1] = {0};
327 char slotB[MAX_GPT_NAME_SIZE + 1] = {0};
328 char active_guid[TYPE_GUID_SIZE + 1] = {0};
329 char inactive_guid[TYPE_GUID_SIZE + 1] = {0};
330 struct gpt_disk *disk = NULL;
331 //Pointer to partition entry of current 'A' partition
332 uint8_t *pentryA = NULL;
333 uint8_t *pentryA_bak = NULL;
334 //Pointer to partition entry of current 'B' partition
335 uint8_t *pentryB = NULL;
336 uint8_t *pentryB_bak = NULL;
337 uint8_t *slot_info = NULL;
338 uint32_t i;
339 int rc = -1;
340 char buf[PATH_MAX] = {0};
341 struct stat st;
342 unsigned num_slots = 0;
343 unsigned current_slot = 0;
344 int is_ufs = gpt_utils_is_ufs_device();
345
346 if (!module) {
347 ALOGE("%s: Invalid arg", __func__);
348 goto error;
349 }
350 num_slots = get_number_slots(module);
351 if ((num_slots < 1) || (slot > num_slots - 1)) {
352 ALOGE("%s: Unable to get num slots/Invalid slot value",
353 __func__);
354 goto error;
355 }
356 current_slot = get_current_slot(module);
357 if (current_slot == slot) {
358 //Nothing to do here. Just return
359 return 0;
360 }
361 for (i=0; i < ARRAY_SIZE(ptn_list); i++) {
362 //XBL is handled differrently for ufs devices
363 if (is_ufs && !strncmp(ptn_list[i], PTN_XBL, strlen(PTN_XBL)))
364 continue;
365 memset(buf, '\0', sizeof(buf));
366 //Check if A/B versions of this ptn exist
367 snprintf(buf, sizeof(buf) - 1,
368 "%s/%s%s",
369 BOOT_DEV_DIR,
370 ptn_list[i],
371 AB_SLOT_A_SUFFIX
372 );
373 if (stat(buf, &st)) {
374 //partition does not have _a version
375 continue;
376 }
377 memset(buf, '\0', sizeof(buf));
378 snprintf(buf, sizeof(buf) - 1,
379 "%s/%s%s",
380 BOOT_DEV_DIR,
381 ptn_list[i],
382 AB_SLOT_B_SUFFIX
383 );
384 if (stat(buf, &st)) {
385 //partition does not have _a version
386 continue;
387 }
388 disk = gpt_disk_alloc();
389 if (!disk)
390 goto error;
391 memset(slotA, 0, sizeof(slotA));
392 memset(slotB, 0, sizeof(slotB));
393 snprintf(slotA, sizeof(slotA) - 1, "%s%s",
394 ptn_list[i],
395 AB_SLOT_A_SUFFIX);
396 snprintf(slotB, sizeof(slotB) - 1,"%s%s",
397 ptn_list[i],
398 AB_SLOT_B_SUFFIX);
399 //It is assumed that both the A and B slots reside on the
400 //same physical disk
401 if (gpt_disk_get_disk_info(slotA, disk))
402 goto error;
403 //Get partition entry for slot A from primary table
404 pentryA = gpt_disk_get_pentry(disk, slotA, PRIMARY_GPT);
405 //Get partition entry for slot A from backup table
406 pentryA_bak = gpt_disk_get_pentry(disk, slotA, SECONDARY_GPT);
407 //Get partition entry for slot B from primary table
408 pentryB = gpt_disk_get_pentry(disk, slotB, PRIMARY_GPT);
409 //Get partition entry for slot B from backup table
410 pentryB_bak = gpt_disk_get_pentry(disk, slotB, SECONDARY_GPT);
411 if ( !pentryA || !pentryA_bak || !pentryB || !pentryB_bak) {
412 //Something has gone wrong here.We know that we have
413 //_a and _b versions of this partition due to the
414 //check at the start of the loop so none of these
415 //should be NULL.
416 ALOGE("Slot pentries for %s not found.",
417 ptn_list[i]);
418 goto error;
419 }
420 memset(active_guid, '\0', sizeof(active_guid));
421 memset(inactive_guid, '\0', sizeof(inactive_guid));
422 if (get_partition_attribute(slotA, ATTR_SLOT_ACTIVE) == 1) {
423 //A is the current active slot
424 memcpy((void*)active_guid,
425 (const void*)pentryA,
426 TYPE_GUID_SIZE);
427 memcpy((void*)inactive_guid,
428 (const void*)pentryB,
429 TYPE_GUID_SIZE);
430
431 } else if (get_partition_attribute(slotB,
432 ATTR_SLOT_ACTIVE) == 1) {
433 //B is the current active slot
434 memcpy((void*)active_guid,
435 (const void*)pentryB,
436 TYPE_GUID_SIZE);
437 memcpy((void*)inactive_guid,
438 (const void*)pentryA,
439 TYPE_GUID_SIZE);
440 } else {
441 ALOGE("Both A & B are inactive..Aborting");
442 goto error;
443 }
444 if (!strncmp(slot_suffix_arr[slot], AB_SLOT_A_SUFFIX,
445 strlen(AB_SLOT_A_SUFFIX))){
446 //Mark A as active in primary table
447 memcpy(pentryA, active_guid, TYPE_GUID_SIZE);
448 slot_info = pentryA + AB_FLAG_OFFSET;
449 *slot_info = AB_SLOT_ACTIVE_VAL;
450
451 //Mark A as active in backup table
452 memcpy(pentryA_bak, active_guid, TYPE_GUID_SIZE);
453 slot_info = pentryA_bak + AB_FLAG_OFFSET;
454 *slot_info = AB_SLOT_ACTIVE_VAL;
455
456 //Mark B as inactive in primary table
457 memcpy(pentryB, inactive_guid, TYPE_GUID_SIZE);
458 slot_info = pentryB + AB_FLAG_OFFSET;
459 *slot_info = *(slot_info) &
460 ~AB_PARTITION_ATTR_SLOT_ACTIVE;
461
462 //Mark B as inactive in backup table
463 memcpy(pentryB_bak, inactive_guid, TYPE_GUID_SIZE);
464 slot_info = pentryB_bak + AB_FLAG_OFFSET;
465 *slot_info = *(slot_info) &
466 ~AB_PARTITION_ATTR_SLOT_ACTIVE;
467 } else if (!strncmp(slot_suffix_arr[slot], AB_SLOT_B_SUFFIX,
468 strlen(AB_SLOT_B_SUFFIX))){
469 //Mark B as active in primary table
470 memcpy(pentryB, active_guid, TYPE_GUID_SIZE);
471 slot_info = pentryB + AB_FLAG_OFFSET;
472 *slot_info = AB_SLOT_ACTIVE_VAL;
473
474 //Mark B as active in backup table
475 memcpy(pentryB_bak, active_guid, TYPE_GUID_SIZE);
476 slot_info = pentryB_bak + AB_FLAG_OFFSET;
477 *slot_info = AB_SLOT_ACTIVE_VAL;
478
479 //Mark A as inavtive in primary table
480 memcpy(pentryA, inactive_guid, TYPE_GUID_SIZE);
481 slot_info = pentryA + AB_FLAG_OFFSET;
482 *slot_info = *(slot_info) &
483 ~AB_PARTITION_ATTR_SLOT_ACTIVE;
484
485 //Mark A as inactive in backup table
486 memcpy(pentryA_bak, inactive_guid, TYPE_GUID_SIZE);
487 slot_info = pentryA_bak + AB_FLAG_OFFSET;
488 *slot_info = *(slot_info) &
489 ~AB_PARTITION_ATTR_SLOT_ACTIVE;
490 } else {
491 //Something has gone terribly terribly wrong
492 ALOGE("%s: Unknown slot suffix!", __func__);
493 goto error;
494 }
495 if (gpt_disk_update_crc(disk) != 0) {
496 ALOGE("%s: Failed to update gpt_disk crc", __func__);
497 goto error;
498 }
499 if (gpt_disk_commit(disk) != 0) {
500 ALOGE("%s: Failed to commit disk info", __func__);
501 goto error;
502 }
503 gpt_disk_free(disk);
504 disk = NULL;
505 }
506 if (is_ufs) {
507 if (!strncmp(slot_suffix_arr[slot], AB_SLOT_A_SUFFIX,
508 strlen(AB_SLOT_A_SUFFIX))){
509 //Set xbl_a as the boot lun
510 rc = gpt_utils_set_xbl_boot_partition(NORMAL_BOOT);
511 } else if (!strncmp(slot_suffix_arr[slot], AB_SLOT_B_SUFFIX,
512 strlen(AB_SLOT_B_SUFFIX))){
513 //Set xbl_b as the boot lun
514 rc = gpt_utils_set_xbl_boot_partition(BACKUP_BOOT);
515 } else {
516 //Something has gone terribly terribly wrong
517 ALOGE("%s: Unknown slot suffix!", __func__);
518 goto error;
519 }
520 if (rc) {
521 ALOGE("%s: Failed to switch xbl boot partition",
522 __func__);
523 goto error;
524 }
525 }
526 return 0;
527error:
528 if (disk)
529 gpt_disk_free(disk);
530 return -1;
531}
532
533int set_slot_as_unbootable(struct boot_control_module *module, unsigned slot)
534{
535 unsigned num_slots = 0;
536 if (!module) {
537 ALOGE("%s: Invalid argument", __func__);
538 goto error;
539 }
540 num_slots = get_number_slots(module);
541 if (num_slots < 1 || slot > num_slots - 1) {
542 ALOGE("%s: Unable to get num_slots/Invalid slot value",
543 __func__);
544 goto error;
545 }
546 if (update_slot_attribute(slot_suffix_arr[slot],
547 ATTR_UNBOOTABLE)) {
548 goto error;
549 }
550 return 0;
551error:
552 ALOGE("%s: Failed to mark slot unbootable", __func__);
553 return -1;
554}
555
556int is_slot_bootable(struct boot_control_module *module, unsigned slot)
557{
558 unsigned num_slots = 0;
559 int attr = 0;
560 char bootPartition[MAX_GPT_NAME_SIZE + 1] = {0};
561 if (!module) {
562 ALOGE("%s: Invalid argument", __func__);
563 goto error;
564 }
565 num_slots = get_number_slots(module);
566 if (num_slots < 1 || slot > num_slots - 1) {
567 ALOGE("%s: Unable to get num_slots/Invalid slot value",
568 __func__);
569 goto error;
570 }
571 snprintf(bootPartition,
572 sizeof(bootPartition) - 1, "boot%s",
573 slot_suffix_arr[slot]);
574 attr = get_partition_attribute(bootPartition, ATTR_UNBOOTABLE);
575 if (attr >= 0)
576 return !attr;
577error:
578 return -1;
579}
580
581int is_slot_marked_successful(struct boot_control_module *module, unsigned slot)
582{
583 unsigned num_slots = 0;
584 int attr = 0;
585 char bootPartition[MAX_GPT_NAME_SIZE + 1] = {0};
586 if (!module) {
587 ALOGE("%s: Invalid argument", __func__);
588 goto error;
589 }
590 num_slots = get_number_slots(module);
591 if (num_slots < 1 || slot > num_slots - 1) {
592 ALOGE("%s: Unable to get num_slots/Invalid slot value",
593 __func__);
594 goto error;
595 }
596 snprintf(bootPartition,
597 sizeof(bootPartition) - 1,
598 "boot%s", slot_suffix_arr[slot]);
599 attr = get_partition_attribute(bootPartition, ATTR_BOOT_SUCCESSFUL);
600 if (attr >= 0)
601 return attr;
602error:
603 return -1;
604}
605
606static hw_module_methods_t boot_control_module_methods = {
607 .open = NULL,
608};
609
610boot_control_module_t HAL_MODULE_INFO_SYM = {
611 .common = {
612 .tag = HARDWARE_MODULE_TAG,
613 .module_api_version = 1,
614 .hal_api_version = 0,
615 .id = BOOT_CONTROL_HARDWARE_MODULE_ID,
616 .name = "Boot control HAL",
617 .author = "Code Aurora Forum",
618 .methods = &boot_control_module_methods,
619 },
620 .init = boot_control_init,
621 .getNumberSlots = get_number_slots,
622 .getCurrentSlot = get_current_slot,
623 .markBootSuccessful = mark_boot_successful,
624 .setActiveBootSlot = set_active_boot_slot,
625 .setSlotAsUnbootable = set_slot_as_unbootable,
626 .isSlotBootable = is_slot_bootable,
627 .getSuffix = get_suffix,
628 .isSlotMarkedSuccessful = is_slot_marked_successful,
629};