more rework, cleanup, port to pure C

The only C++ was for handling discovering and iterating over the
partitions PER block device, this was implemented in a really
overcomplicated way.

Simplify things, port everything over to c11 and drop the libstdc++
requirement entirely.
main
Caleb Connolly 1 year ago
parent 04f4ac81ea
commit bca9aa5dd7
No known key found for this signature in database
GPG Key ID: 0583312B195F64B6
  1. 129
      bootctrl.h
  2. 197
      bootctrl_impl.c
  3. 1
      bootctrl_test.c
  4. 97
      gpt-utils.c
  5. 101
      gpt-utils.h
  6. 14
      meson.build
  7. 1
      qbootctl.c
  8. 22
      ufs-bsg.c

@ -19,85 +19,86 @@
#ifndef __BOOTCTRL_H__ #ifndef __BOOTCTRL_H__
#define __BOOTCTRL_H__ #define __BOOTCTRL_H__
#include <stdbool.h>
struct slot_info { struct slot_info {
bool active; bool active;
bool bootable; bool bootable;
bool successful; bool successful;
}; };
struct boot_control_module { struct boot_control_module {
/*
* (*getCurrentSlot)() returns the value letting the system know
* whether the current slot is A or B. The meaning of A and B is
* left up to the implementer. It is assumed that if the current slot
* is A, then the block devices underlying B can be accessed directly
* without any risk of corruption.
* The returned value is always guaranteed to be strictly less than the
* value returned by getNumberSlots. Slots start at 0 and
* finish at getNumberSlots() - 1
*/
unsigned (*getCurrentSlot)();
/* /*
* (*getCurrentSlot)() returns the value letting the system know * (*markBootSuccessful)() marks the specified slot
* whether the current slot is A or B. The meaning of A and B is * as boot successful
* left up to the implementer. It is assumed that if the current slot *
* is A, then the block devices underlying B can be accessed directly * Returns 0 on success, -errno on error.
* without any risk of corruption. */
* The returned value is always guaranteed to be strictly less than the int (*markBootSuccessful)(unsigned slot);
* value returned by getNumberSlots. Slots start at 0 and
* finish at getNumberSlots() - 1
*/
unsigned (*getCurrentSlot)();
/*
* (*markBootSuccessful)() marks the specified slot
* as boot successful
*
* Returns 0 on success, -errno on error.
*/
int (*markBootSuccessful)(unsigned slot);
/* /*
* (*setActiveBootSlot)() marks the slot passed in parameter as * (*setActiveBootSlot)() marks the slot passed in parameter as
* the active boot slot (see getCurrentSlot for an explanation * the active boot slot (see getCurrentSlot for an explanation
* of the "slot" parameter). This overrides any previous call to * of the "slot" parameter). This overrides any previous call to
* setSlotAsUnbootable. * setSlotAsUnbootable.
* Returns 0 on success, -errno on error. * Returns 0 on success, -errno on error.
*/ */
int (*setActiveBootSlot)(unsigned slot); int (*setActiveBootSlot)(unsigned slot);
/* /*
* (*setSlotAsUnbootable)() marks the slot passed in parameter as * (*setSlotAsUnbootable)() marks the slot passed in parameter as
* an unbootable. This can be used while updating the contents of the slot's * an unbootable. This can be used while updating the contents of the slot's
* partitions, so that the system will not attempt to boot a known bad set up. * partitions, so that the system will not attempt to boot a known bad set up.
* Returns 0 on success, -errno on error. * Returns 0 on success, -errno on error.
*/ */
int (*setSlotAsUnbootable)(unsigned slot); int (*setSlotAsUnbootable)(unsigned slot);
/* /*
* (*isSlotBootable)() returns if the slot passed in parameter is * (*isSlotBootable)() returns if the slot passed in parameter is
* bootable. Note that slots can be made unbootable by both the * bootable. Note that slots can be made unbootable by both the
* bootloader and by the OS using setSlotAsUnbootable. * bootloader and by the OS using setSlotAsUnbootable.
* Returns 1 if the slot is bootable, 0 if it's not, and -errno on * Returns 1 if the slot is bootable, 0 if it's not, and -errno on
* error. * error.
*/ */
int (*isSlotBootable)(unsigned slot); int (*isSlotBootable)(unsigned slot);
/* /*
* (*getSuffix)() returns the string suffix used by partitions that * (*getSuffix)() returns the string suffix used by partitions that
* correspond to the slot number passed in parameter. The returned string * correspond to the slot number passed in parameter. The returned string
* is expected to be statically allocated and not need to be freed. * is expected to be statically allocated and not need to be freed.
* Returns NULL if slot does not match an existing slot. * Returns NULL if slot does not match an existing slot.
*/ */
const char* (*getSuffix)(unsigned slot); const char *(*getSuffix)(unsigned slot);
/* /*
* (*isSlotMarkedSucessful)() returns if the slot passed in parameter has * (*isSlotMarkedSucessful)() returns if the slot passed in parameter has
* been marked as successful using markBootSuccessful. * been marked as successful using markBootSuccessful.
* Returns 1 if the slot has been marked as successful, 0 if it's * Returns 1 if the slot has been marked as successful, 0 if it's
* not the case, and -errno on error. * not the case, and -errno on error.
*/ */
int (*isSlotMarkedSuccessful)(unsigned slot); int (*isSlotMarkedSuccessful)(unsigned slot);
/** /**
* Returns the active slot to boot into on the next boot. If * Returns the active slot to boot into on the next boot. If
* setActiveBootSlot() has been called, the getter function should return * setActiveBootSlot() has been called, the getter function should return
* the same slot as the one provided in the last setActiveBootSlot() call. * the same slot as the one provided in the last setActiveBootSlot() call.
*/ */
unsigned (*getActiveBootSlot)(); unsigned (*getActiveBootSlot)();
}; };
extern const struct boot_control_module bootctl; extern const struct boot_control_module bootctl;
extern const struct boot_control_module bootctl_test; extern const struct boot_control_module bootctl_test;
#endif // __BOOTCTRL_H__ #endif // __BOOTCTRL_H__

@ -16,21 +16,18 @@
* along with this program. If not, see <http:// www.gnu.org/licenses/>. * along with this program. If not, see <http:// www.gnu.org/licenses/>.
*/ */
#include <assert.h>
#include <dirent.h> #include <dirent.h>
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <limits.h> #include <limits.h>
#include <list> #include <stdbool.h>
#include <map>
#include <regex>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <string>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include <unistd.h> #include <unistd.h>
#include <vector>
#include "gpt-utils.h" #include "gpt-utils.h"
#include "ufs-bsg.h" #include "ufs-bsg.h"
@ -57,7 +54,6 @@
(*(pentry + AB_FLAG_OFFSET) & ~AB_PARTITION_ATTR_SLOT_ACTIVE); \ (*(pentry + AB_FLAG_OFFSET) & ~AB_PARTITION_ATTR_SLOT_ACTIVE); \
}) })
using namespace std;
const char *slot_suffix_arr[] = { AB_SLOT_A_SUFFIX, AB_SLOT_B_SUFFIX, NULL }; const char *slot_suffix_arr[] = { AB_SLOT_A_SUFFIX, AB_SLOT_B_SUFFIX, NULL };
enum part_attr_type { enum part_attr_type {
@ -98,12 +94,12 @@ error:
} }
// Get the value of one of the attribute fields for a partition. // Get the value of one of the attribute fields for a partition.
static int get_partition_attribute(struct gpt_disk *disk, char *partname, static int get_partition_attribute(struct gpt_disk *disk, const char *partname,
enum part_attr_type part_attr) enum part_attr_type part_attr)
{ {
uint8_t *pentry = nullptr; uint8_t *pentry = NULL;
int retval = -1; int retval = -1;
uint8_t *attr = nullptr; uint8_t *attr = NULL;
if (!partname) if (!partname)
return -1; return -1;
@ -151,13 +147,12 @@ static int update_slot_attribute(struct gpt_disk *disk, const char *slot,
unsigned int i = 0; unsigned int i = 0;
char buf[PATH_MAX]; char buf[PATH_MAX];
struct stat st; struct stat st;
uint8_t *pentry = nullptr; uint8_t *pentry = NULL;
uint8_t *pentry_bak = nullptr; uint8_t *pentry_bak = NULL;
int rc = -1; int rc = -1;
uint8_t *attr = nullptr; uint8_t *attr = NULL;
uint8_t *attr_bak = nullptr; uint8_t *attr_bak = NULL;
char partName[MAX_GPT_NAME_SIZE + 1] = { 0 }; char partName[MAX_GPT_NAME_SIZE + 1] = { 0 };
static const char ptn_list[][MAX_GPT_NAME_SIZE - 1] = { AB_PTN_LIST };
int slot_name_valid = 0; int slot_name_valid = 0;
char devpath[PATH_MAX] = { 0 }; char devpath[PATH_MAX] = { 0 };
@ -166,7 +161,7 @@ static int update_slot_attribute(struct gpt_disk *disk, const char *slot,
return -1; return -1;
} }
for (i = 0; slot_suffix_arr[i] != nullptr; i++) { for (i = 0; slot_suffix_arr[i] != NULL; i++) {
if (!strncmp(slot, slot_suffix_arr[i], strlen(slot_suffix_arr[i]))) if (!strncmp(slot, slot_suffix_arr[i], strlen(slot_suffix_arr[i])))
slot_name_valid = 1; slot_name_valid = 1;
} }
@ -176,10 +171,10 @@ static int update_slot_attribute(struct gpt_disk *disk, const char *slot,
return -1; return -1;
} }
for (i = 0; i < ARRAY_SIZE(ptn_list); i++) { for (i = 0; i < ARRAY_SIZE(g_all_ptns); i++) {
memset(buf, '\0', sizeof(buf)); memset(buf, '\0', sizeof(buf));
// Check if A/B versions of this ptn exist // Check if A/B versions of this ptn exist
snprintf(buf, sizeof(buf) - 1, "%s/%s%s", BOOT_DEV_DIR, ptn_list[i], snprintf(buf, sizeof(buf) - 1, "%s/%s%s", BOOT_DEV_DIR, g_all_ptns[i],
AB_SLOT_A_SUFFIX); AB_SLOT_A_SUFFIX);
if (stat(buf, &st) < 0) { if (stat(buf, &st) < 0) {
// partition does not have _a version // partition does not have _a version
@ -187,7 +182,7 @@ static int update_slot_attribute(struct gpt_disk *disk, const char *slot,
} }
memset(buf, '\0', sizeof(buf)); memset(buf, '\0', sizeof(buf));
snprintf(buf, sizeof(buf) - 1, "%s/%s%s", BOOT_DEV_DIR, ptn_list[i], snprintf(buf, sizeof(buf) - 1, "%s/%s%s", BOOT_DEV_DIR, g_all_ptns[i],
AB_SLOT_B_SUFFIX); AB_SLOT_B_SUFFIX);
if (stat(buf, &st) < 0) { if (stat(buf, &st) < 0) {
// partition does not have _b version // partition does not have _b version
@ -195,11 +190,11 @@ static int update_slot_attribute(struct gpt_disk *disk, const char *slot,
} }
memset(partName, '\0', sizeof(partName)); memset(partName, '\0', sizeof(partName));
snprintf(partName, sizeof(partName) - 1, "%s%s", ptn_list[i], slot); snprintf(partName, sizeof(partName) - 1, "%s%s", g_all_ptns[i], slot);
// If the current partition is for a different disk (e.g. /dev/sde when the current disk is /dev/sda) // If the current partition is for a different disk (e.g. /dev/sde when the current disk is /dev/sda)
// Then commit the current disk // Then commit the current disk
if (!partition_is_for_disk(partName, disk, devpath, sizeof(devpath))) { if (partition_is_for_disk(disk, partName, devpath, sizeof(devpath)) != 0) {
if (!gpt_disk_commit(disk)) { if (!gpt_disk_commit(disk)) {
fprintf(stderr, "%s: Failed to commit disk\n", __func__); fprintf(stderr, "%s: Failed to commit disk\n", __func__);
return -1; return -1;
@ -265,8 +260,8 @@ static int update_slot_attribute(struct gpt_disk *disk, const char *slot,
*/ */
unsigned get_number_slots() unsigned get_number_slots()
{ {
struct dirent *de = nullptr; struct dirent *de = NULL;
DIR *dir_bootdev = nullptr; DIR *dir_bootdev = NULL;
static int slot_count = 0; static int slot_count = 0;
// If we've already counted the slots, return the cached value. // If we've already counted the slots, return the cached value.
@ -274,8 +269,8 @@ unsigned get_number_slots()
if (slot_count > 0) if (slot_count > 0)
return slot_count; return slot_count;
static_assert(AB_SLOT_A_SUFFIX[0] == '_', "Breaking change to slot A suffix"); assert(AB_SLOT_A_SUFFIX[0] == '_');
static_assert(AB_SLOT_B_SUFFIX[0] == '_', "Breaking change to slot B suffix"); assert(AB_SLOT_B_SUFFIX[0] == '_');
dir_bootdev = opendir(BOOTDEV_DIR); dir_bootdev = opendir(BOOTDEV_DIR);
// Shouldn't this be an assert? // Shouldn't this be an assert?
@ -344,7 +339,7 @@ static unsigned int get_current_slot_from_kernel_cmdline()
// Iterate through a list of partitons named as boot+suffix // Iterate through a list of partitons named as boot+suffix
// and see which one is currently active. // and see which one is currently active.
for (i = 0; slot_suffix_arr[i] != nullptr; i++) { for (i = 0; slot_suffix_arr[i] != NULL; i++) {
if (!strncmp(bootSlotProp, slot_suffix_arr[i], strlen(slot_suffix_arr[i]))) { if (!strncmp(bootSlotProp, slot_suffix_arr[i], strlen(slot_suffix_arr[i]))) {
// printf("%s current_slot = %d\n", __func__, i); // printf("%s current_slot = %d\n", __func__, i);
return i; return i;
@ -413,68 +408,66 @@ const char *get_suffix(unsigned slot)
return slot_suffix_arr[slot]; return slot_suffix_arr[slot];
} }
// The argument here is a vector of partition names(including the slot suffix) // The argument here is a vector of partition names(including the slot suffix)
// that lie on a single disk // that lie on a single disk
static int boot_ctl_set_active_slot_for_partitions(struct gpt_disk *disk, vector<string> part_list, static int boot_ctl_set_active_slot_for_partitions(struct gpt_disk *disk, const char ptn_list[][MAX_GPT_NAME_SIZE], int len,
unsigned slot) unsigned slot)
{ {
char buf[PATH_MAX] = { 0 }; char buf[PATH_MAX] = { 0 };
char slotA[MAX_GPT_NAME_SIZE + 1] = { 0 }; const char *slotA;
char slotB[MAX_GPT_NAME_SIZE + 1] = { 0 }; char slotB[MAX_GPT_NAME_SIZE] = { 0 };
char active_guid[TYPE_GUID_SIZE + 1] = { 0 }; char active_guid[TYPE_GUID_SIZE + 1] = { 0 };
char inactive_guid[TYPE_GUID_SIZE + 1] = { 0 }; char inactive_guid[TYPE_GUID_SIZE + 1] = { 0 };
int rc; int rc, i;
// Pointer to the partition entry of current 'A' partition // Pointer to the partition entry of current 'A' partition
uint8_t *pentryA = nullptr; uint8_t *pentryA = NULL;
uint8_t *pentryA_bak = nullptr; uint8_t *pentryA_bak = NULL;
// Pointer to partition entry of current 'B' partition // Pointer to partition entry of current 'B' partition
uint8_t *pentryB = nullptr; uint8_t *pentryB = NULL;
uint8_t *pentryB_bak = nullptr; uint8_t *pentryB_bak = NULL;
struct stat st; struct stat st;
vector<string>::iterator partition_iterator;
LOGD("Marking slot %s as active:\n", slot_suffix_arr[slot]); LOGD("Marking slot %s as active:\n", slot_suffix_arr[slot]);
for (partition_iterator = part_list.begin(); partition_iterator != part_list.end(); for (i = 0, slotA = ptn_list[0]; i < len; slotA = ptn_list[++i]) {
partition_iterator++) {
// Chop off the slot suffix from the partition name to // Chop off the slot suffix from the partition name to
// make the string easier to work with. // make the string easier to work with.
string prefix = *partition_iterator; LOGD("Part: %s\n", slotA);
LOGD("Part: %s\n", prefix.c_str()); int n = strlen(slotA) - strlen(AB_SLOT_A_SUFFIX);
if (prefix.size() < (strlen(AB_SLOT_A_SUFFIX) + 1)) { if (n + 1 < 3 || n + 1 > MAX_GPT_NAME_SIZE) {
fprintf(stderr, "Invalid partition name: %s\n", prefix.c_str()); fprintf(stderr, "Invalid partition name: %s\n", slotA);
return -1; return -1;
} }
prefix.resize(prefix.size() - strlen(AB_SLOT_A_SUFFIX));
// Check if A/B versions of this ptn exist memset(slotB, 0, sizeof(slotB));
snprintf(buf, sizeof(buf) - 1, "%s/%s%s", BOOT_DEV_DIR, prefix.c_str(), strncat(slotB, slotA, n);
AB_SLOT_A_SUFFIX); strncat(slotB + n, AB_SLOT_B_SUFFIX, 3);
LOGD("\t_a Path: '%s'\n", buf);
rc = stat(buf, &st); rc = snprintf(buf, sizeof(buf) - 1, "%s", BOOT_DEV_DIR);
if (rc < 0) { snprintf(buf + rc, PATH_MAX - rc, "/%s", slotA);
fprintf(stderr, "Failed to stat() path: %d: %s\n", rc, strerror(errno)); LOGD("Checking for partition %s\n", buf);
continue; if (stat(buf, &st)) {
} if (!strcmp(slotA, "boot_a") || !strcmp(slotA, "dtbo_a")) {
memset(buf, '\0', sizeof(buf)); fprintf(stderr, "Couldn't find required partition %s\n", slotA);
snprintf(buf, sizeof(buf) - 1, "%s/%s%s", BOOT_DEV_DIR, prefix.c_str(), return -1;
AB_SLOT_B_SUFFIX); }
// LOGD("\t_b Path: '%s'\n", buf); // Not every device has every partition
rc = stat(buf, &st);
if (rc < 0) {
fprintf(stderr, "Failed to stat() path: %d: %s\n", rc, strerror(errno));
continue; continue;
} }
memset(slotA, 0, sizeof(slotA));
memset(slotB, 0, sizeof(slotA)); snprintf(buf + rc, PATH_MAX - rc, "/%s", slotB);
snprintf(slotA, sizeof(slotA) - 1, "%s%s", prefix.c_str(), AB_SLOT_A_SUFFIX); if (stat(buf, &st)) {
snprintf(slotB, sizeof(slotB) - 1, "%s%s", prefix.c_str(), AB_SLOT_B_SUFFIX); fprintf(stderr, "Partition %s does not exist\n", slotB);
return -1;
// Get the disk containing the partitions that were passed in.
// All partitions passed in must lie on the same disk.
if (!gpt_disk_is_valid(disk)) {
if (gpt_disk_get_disk_info(slotA, disk) < 0)
return -1;
} }
// Get the disk containing this partition. This only
// actually re-initialises disk if this partition refers
// to a different block device than the last one.
if (gpt_disk_get_disk_info(slotA, disk) < 0)
return -1;
// Get partition entry for slot A & B from the primary // Get partition entry for slot A & B from the primary
// and backup tables. // and backup tables.
pentryA = gpt_disk_get_pentry(disk, slotA, PRIMARY_GPT); pentryA = gpt_disk_get_pentry(disk, slotA, PRIMARY_GPT);
@ -484,7 +477,7 @@ static int boot_ctl_set_active_slot_for_partitions(struct gpt_disk *disk, vector
if (!pentryA || !pentryA_bak || !pentryB || !pentryB_bak) { if (!pentryA || !pentryA_bak || !pentryB || !pentryB_bak) {
// None of these should be NULL since we have already // None of these should be NULL since we have already
// checked for A & B versions earlier. // checked for A & B versions earlier.
fprintf(stderr, "Slot pentries for %s not found.\n", prefix.c_str()); fprintf(stderr, "Slot pentries for %s not found.\n", slotA);
return -1; return -1;
} }
LOGD("\tAB attr (A): 0x%x (backup: 0x%x)\n", *(uint16_t *)(pentryA + AB_FLAG_OFFSET), LOGD("\tAB attr (A): 0x%x (backup: 0x%x)\n", *(uint16_t *)(pentryA + AB_FLAG_OFFSET),
@ -558,86 +551,50 @@ unsigned get_active_boot_slot()
int set_active_boot_slot(unsigned slot) int set_active_boot_slot(unsigned slot)
{ {
map<string, vector<string>> ptn_map; enum boot_chain chain = (enum boot_chain)slot;
vector<string> ptn_vec;
const char ptn_list[][MAX_GPT_NAME_SIZE] = { AB_PTN_LIST };
struct gpt_disk disk = { 0 }; struct gpt_disk disk = { 0 };
uint32_t i; int rc;
int rc = -1;
map<string, vector<string>>::iterator map_iter;
bool ismmc; bool ismmc;
if (boot_control_check_slot_sanity(slot)) { if (boot_control_check_slot_sanity(slot)) {
fprintf(stderr, "%s: Bad arguments\n", __func__); fprintf(stderr, "%s: Bad arguments\n", __func__);
goto out; return -1;
} }
ismmc = gpt_utils_is_partition_backed_by_emmc(PTN_XBL AB_SLOT_A_SUFFIX); ismmc = gpt_utils_is_partition_backed_by_emmc(PTN_XBL AB_SLOT_A_SUFFIX);
// Do this *before* updating all the slot attributes
// to make sure we can
if (!ismmc && ufs_bsg_dev_open() < 0) { if (!ismmc && ufs_bsg_dev_open() < 0) {
goto out; return -1;
} }
// The partition list just contains prefixes(without the _a/_b) of the rc = boot_ctl_set_active_slot_for_partitions(&disk, g_all_ptns, ARRAY_SIZE(g_all_ptns), slot);
// partitions that support A/B. In order to get the layout we need the
// actual names. To do this we append the slot suffix to every member
// in the list.
for (i = 0; i < ARRAY_SIZE(ptn_list); i++) {
// XBL is handled differrently for ufs devices so ignore it
if (!ismmc && !strncmp(ptn_list[i], PTN_XBL, strlen(PTN_XBL)))
continue;
// The partition list will be the list of _a partitions
string cur_ptn = ptn_list[i];
cur_ptn.append(AB_SLOT_A_SUFFIX);
ptn_vec.push_back(cur_ptn);
}
// The partition map gives us info in the following format: if (rc) {
// [path_to_block_device_1]--><partitions on device 1> fprintf(stderr, "%s: Failed to set active slot for partitions \n", __func__);
// [path_to_block_device_2]--><partitions on device 2>
// ...
// ...
// eg:
// [/dev/block/sdb]---><system, boot, rpm, tz,....>
if (gpt_utils_get_partition_map(ptn_vec, ptn_map)) {
fprintf(stderr, "%s: Failed to get partition map\n", __func__);
goto out; goto out;
} }
for (map_iter = ptn_map.begin(); map_iter != ptn_map.end(); map_iter++) {
if (map_iter->second.size() < 1)
continue;
if (boot_ctl_set_active_slot_for_partitions(&disk, map_iter->second, slot)) {
fprintf(stderr, "%s: Failed to set active slot for partitions \n", __func__);
goto out;
}
}
// EMMC doesn't need attributes to be set. // EMMC doesn't need attributes to be set.
if (ismmc) if (ismmc)
return 0; goto out;
if (slot == 0) { if (chain > BACKUP_BOOT) {
// Set xbl_a as the boot lun fprintf(stderr, "%s: Unknown slot %d!\n", __func__, slot);
rc = gpt_utils_set_xbl_boot_partition(NORMAL_BOOT); rc = -1;
} else if (slot == 1) {
// Set xbl_b as the boot lun
rc = gpt_utils_set_xbl_boot_partition(BACKUP_BOOT);
} else {
// Something has gone terribly terribly wrong
fprintf(stderr, "%s: Unknown slot suffix!\n", __func__);
goto out; goto out;
} }
rc = gpt_utils_set_xbl_boot_partition(chain);
if (rc) { if (rc) {
fprintf(stderr, "%s: Failed to switch xbl boot partition\n", __func__); fprintf(stderr, "%s: Failed to switch xbl boot partition\n", __func__);
goto out; goto out;
} }
gpt_disk_free(&disk);
return 0;
out: out:
gpt_disk_free(&disk); gpt_disk_free(&disk);
return -1; return rc;
} }
int set_slot_as_unbootable(unsigned slot) int set_slot_as_unbootable(unsigned slot)

@ -16,6 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <stdbool.h>
#include "bootctrl.h" #include "bootctrl.h"
struct test_state { struct test_state {

@ -30,8 +30,10 @@
#define _LARGEFILE64_SOURCE /* enable lseek64() */ #define _LARGEFILE64_SOURCE /* enable lseek64() */
#include "assert.h" #include <assert.h>
#include <asm/byteorder.h> #include <asm/byteorder.h>
#include <ctype.h>
#include <stdlib.h>
#include <dirent.h> #include <dirent.h>
#include <endian.h> #include <endian.h>
#include <errno.h> #include <errno.h>
@ -40,14 +42,11 @@
#include <limits.h> #include <limits.h>
#include <linux/fs.h> #include <linux/fs.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <map>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <string>
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.h> #include <unistd.h>
#include <vector>
#include <zlib.h> #include <zlib.h>
#include "gpt-utils.h" #include "gpt-utils.h"
@ -75,9 +74,6 @@
#define LUN_NAME_START_LOC (sizeof("/dev/") - 1) #define LUN_NAME_START_LOC (sizeof("/dev/") - 1)
#define BOOT_LUN_A_ID 1 #define BOOT_LUN_A_ID 1
#define BOOT_LUN_B_ID 2 #define BOOT_LUN_B_ID 2
/******************************************************************************
* MACROS
******************************************************************************/
#define GET_4_BYTES(ptr) \ #define GET_4_BYTES(ptr) \
((uint32_t) * ((uint8_t *)(ptr)) | ((uint32_t) * ((uint8_t *)(ptr) + 1) << 8) | \ ((uint32_t) * ((uint8_t *)(ptr)) | ((uint32_t) * ((uint8_t *)(ptr) + 1) << 8) | \
@ -97,10 +93,6 @@
*((uint8_t *)(ptr) + 2) = ((y) >> 16) & 0xff; \ *((uint8_t *)(ptr) + 2) = ((y) >> 16) & 0xff; \
*((uint8_t *)(ptr) + 3) = ((y) >> 24) & 0xff; *((uint8_t *)(ptr) + 3) = ((y) >> 24) & 0xff;
/******************************************************************************
* TYPES
******************************************************************************/
using namespace std;
enum gpt_state { GPT_OK = 0, GPT_BAD_SIGNATURE, GPT_BAD_CRC }; enum gpt_state { GPT_OK = 0, GPT_BAD_SIGNATURE, GPT_BAD_CRC };
// List of LUN's containing boot critical images. // List of LUN's containing boot critical images.
// Required in the case of UFS devices // Required in the case of UFS devices
@ -109,9 +101,6 @@ struct update_data {
uint32_t num_valid_entries; uint32_t num_valid_entries;
}; };
/******************************************************************************
* FUNCTIONS
******************************************************************************/
void DumpHex(const void *data, size_t size) void DumpHex(const void *data, size_t size)
{ {
char ascii[17]; char ascii[17];
@ -222,7 +211,7 @@ static uint8_t *gpt_pentry_seek(const char *ptn_name, const uint8_t *pentries_st
return (uint8_t *)(pentry_name - PARTITION_NAME_OFFSET); return (uint8_t *)(pentry_name - PARTITION_NAME_OFFSET);
} }
return nullptr; return NULL;
} }
// Defined in ufs-bsg.cpp // Defined in ufs-bsg.cpp
@ -249,7 +238,7 @@ int gpt_utils_set_xbl_boot_partition(enum boot_chain chain)
{ {
struct stat st; struct stat st;
uint8_t boot_lun_id = 0; uint8_t boot_lun_id = 0;
const char *boot_dev = nullptr; const char *boot_dev = NULL;
(void)st; (void)st;
(void)boot_dev; (void)boot_dev;
@ -332,42 +321,6 @@ static int get_dev_path_from_partition_name(const char *partname, char *buf, siz
return 0; return 0;
} }
int gpt_utils_get_partition_map(vector<string> &ptn_list, map<string, vector<string>> &partition_map)
{
char devpath[PATH_MAX] = { '\0' };
map<string, vector<string>>::iterator it;
if (ptn_list.size() < 1) {
fprintf(stderr, "%s: Invalid ptn list\n", __func__);
return -1;
}
// Go through the passed in list
for (uint32_t i = 0; i < ptn_list.size(); i++) {
// Key in the map is the path to the device that holds the
// partition
if (get_dev_path_from_partition_name(ptn_list[i].c_str(), devpath, sizeof(devpath))) {
// Not necessarily an error. The partition may just
// not be present.
continue;
}
string path = devpath;
it = partition_map.find(path);
if (it != partition_map.end()) {
it->second.push_back(ptn_list[i]);
} else {
vector<string> str_vec;
str_vec.push_back(ptn_list[i]);
partition_map.insert(pair<string, vector<string>>(path, str_vec));
}
memset(devpath, '\0', sizeof(devpath));
}
return 0;
}
// Get the block size of the disk represented by decsriptor fd // Get the block size of the disk represented by decsriptor fd
static uint32_t gpt_get_block_size(int fd) static uint32_t gpt_get_block_size(int fd)
{ {
@ -432,7 +385,7 @@ error:
// Read out the GPT headers for the disk that contains the partition partname // Read out the GPT headers for the disk that contains the partition partname
static int gpt_get_headers(const char *partname, uint8_t **primary, uint8_t **backup) static int gpt_get_headers(const char *partname, uint8_t **primary, uint8_t **backup)
{ {
uint8_t *hdr = nullptr; uint8_t *hdr = NULL;
char devpath[PATH_MAX] = { 0 }; char devpath[PATH_MAX] = { 0 };
off_t hdr_offset = 0; off_t hdr_offset = 0;
uint32_t block_size = 0; uint32_t block_size = 0;
@ -505,7 +458,7 @@ static uint8_t *gpt_get_pentry_arr(uint8_t *hdr, int fd)
uint32_t pentry_size = 0; uint32_t pentry_size = 0;
uint32_t block_size = 0; uint32_t block_size = 0;
uint32_t pentries_arr_size = 0; uint32_t pentries_arr_size = 0;
uint8_t *pentry_arr = nullptr; uint8_t *pentry_arr = NULL;
int rc = 0; int rc = 0;
if (!hdr) { if (!hdr) {
fprintf(stderr, "%s: Invalid header\n", __func__); fprintf(stderr, "%s: Invalid header\n", __func__);
@ -537,7 +490,7 @@ static uint8_t *gpt_get_pentry_arr(uint8_t *hdr, int fd)
error: error:
if (pentry_arr) if (pentry_arr)
free(pentry_arr); free(pentry_arr);
return nullptr; return NULL;
} }
static int gpt_set_pentry_arr(uint8_t *hdr, int fd, uint8_t *arr) static int gpt_set_pentry_arr(uint8_t *hdr, int fd, uint8_t *arr)
@ -586,19 +539,19 @@ void gpt_disk_free(struct gpt_disk *disk)
if (disk->hdr) { if (disk->hdr) {
free(disk->hdr); free(disk->hdr);
disk->hdr = nullptr; disk->hdr = NULL;
} }
if (disk->hdr_bak) { if (disk->hdr_bak) {
free(disk->hdr_bak); free(disk->hdr_bak);
disk->hdr_bak = nullptr; disk->hdr_bak = NULL;
} }
if (disk->pentry_arr) { if (disk->pentry_arr) {
free(disk->pentry_arr); free(disk->pentry_arr);
disk->pentry_arr = nullptr; disk->pentry_arr = NULL;
} }
if (disk->pentry_arr_bak) { if (disk->pentry_arr_bak) {
free(disk->pentry_arr_bak); free(disk->pentry_arr_bak);
disk->pentry_arr_bak = nullptr; disk->pentry_arr_bak = NULL;
} }
disk->is_initialized = 0; disk->is_initialized = 0;
@ -616,14 +569,14 @@ bool gpt_disk_is_valid(struct gpt_disk *disk)
* and populate the blockdev path. * and populate the blockdev path.
* e.g. for /dev/disk/by-partlabel/system_a blockdev would be /dev/sda * e.g. for /dev/disk/by-partlabel/system_a blockdev would be /dev/sda
*/ */
bool partition_is_for_disk(const char *part, struct gpt_disk *disk, char *blockdev, int blockdev_len) int partition_is_for_disk(const struct gpt_disk *disk, const char *part, char *blockdev, int blockdev_len)
{ {
int ret; int ret;
ret = get_dev_path_from_partition_name(part, blockdev, blockdev_len); ret = get_dev_path_from_partition_name(part, blockdev, blockdev_len);
if (ret) { if (ret) {
fprintf(stderr, "%s: Failed to resolve path for %s\n", __func__, part); fprintf(stderr, "%s: Failed to resolve path for %s\n", __func__, part);
return false; return -1;
} }
if (!strcmp(blockdev, disk->devpath)) { if (!strcmp(blockdev, disk->devpath)) {
@ -639,7 +592,7 @@ bool partition_is_for_disk(const char *part, struct gpt_disk *disk, char *blockd
*/ */
int gpt_disk_get_disk_info(const char *dev, struct gpt_disk *disk) int gpt_disk_get_disk_info(const char *dev, struct gpt_disk *disk)
{ {
int fd = -1; int fd = -1, rc;
uint32_t gpt_header_size = 0; uint32_t gpt_header_size = 0;
char devpath[PATH_MAX] = { 0 }; char devpath[PATH_MAX] = { 0 };
@ -648,8 +601,14 @@ int gpt_disk_get_disk_info(const char *dev, struct gpt_disk *disk)
goto error; goto error;
} }
if (partition_is_for_disk(dev, disk, devpath, sizeof(devpath))) { rc = partition_is_for_disk(disk, dev, devpath, sizeof(devpath));
if (rc > 0)
return 0; return 0;
if (rc < 0) {
fprintf(stderr, "%s: Failed to resolve path for %s\n", __func__, dev);
return -1;
} }
if (disk->is_initialized == GPT_DISK_INIT_MAGIC) { if (disk->is_initialized == GPT_DISK_INIT_MAGIC) {
@ -666,8 +625,8 @@ int gpt_disk_get_disk_info(const char *dev, struct gpt_disk *disk)
goto error; goto error;
} }
assert(disk->hdr != nullptr); assert(disk->hdr != NULL);
assert(disk->hdr_bak != nullptr); assert(disk->hdr_bak != NULL);
gpt_header_size = GET_4_BYTES(disk->hdr + HEADER_SIZE_OFFSET); gpt_header_size = GET_4_BYTES(disk->hdr + HEADER_SIZE_OFFSET);
@ -683,14 +642,14 @@ int gpt_disk_get_disk_info(const char *dev, struct gpt_disk *disk)
goto error; goto error;
} }
assert(disk->pentry_arr == nullptr); assert(disk->pentry_arr == NULL);
disk->pentry_arr = gpt_get_pentry_arr(disk->hdr, fd); disk->pentry_arr = gpt_get_pentry_arr(disk->hdr, fd);
if (!disk->pentry_arr) { if (!disk->pentry_arr) {
fprintf(stderr, "%s: Failed to obtain partition entry array\n", __func__); fprintf(stderr, "%s: Failed to obtain partition entry array\n", __func__);
goto error; goto error;
} }
assert(disk->pentry_arr_bak == nullptr); assert(disk->pentry_arr_bak == NULL);
disk->pentry_arr_bak = gpt_get_pentry_arr(disk->hdr_bak, fd); disk->pentry_arr_bak = gpt_get_pentry_arr(disk->hdr_bak, fd);
if (!disk->pentry_arr_bak) { if (!disk->pentry_arr_bak) {
fprintf(stderr, "%s: Failed to obtain backup partition entry array\n", __func__); fprintf(stderr, "%s: Failed to obtain backup partition entry array\n", __func__);
@ -714,10 +673,10 @@ error:
// Get pointer to partition entry from a allocated gpt_disk structure // Get pointer to partition entry from a allocated gpt_disk structure
uint8_t *gpt_disk_get_pentry(struct gpt_disk *disk, const char *partname, enum gpt_instance instance) uint8_t *gpt_disk_get_pentry(struct gpt_disk *disk, const char *partname, enum gpt_instance instance)
{ {
uint8_t *ptn_arr = nullptr; uint8_t *ptn_arr = NULL;
if (!disk || !partname || disk->is_initialized != GPT_DISK_INIT_MAGIC) { if (!disk || !partname || disk->is_initialized != GPT_DISK_INIT_MAGIC) {
fprintf(stderr, "%s: disk handle not initialised\n", __func__); fprintf(stderr, "%s: disk handle not initialised\n", __func__);
return nullptr; return NULL;
} }
ptn_arr = (instance == PRIMARY_GPT) ? disk->pentry_arr : disk->pentry_arr_bak; ptn_arr = (instance == PRIMARY_GPT) ? disk->pentry_arr : disk->pentry_arr_bak;
return (gpt_pentry_seek(partname, ptn_arr, ptn_arr + disk->pentry_arr_size, return (gpt_pentry_seek(partname, ptn_arr, ptn_arr + disk->pentry_arr_size,

@ -29,13 +29,11 @@
#ifndef __GPT_UTILS_H__ #ifndef __GPT_UTILS_H__
#define __GPT_UTILS_H__ #define __GPT_UTILS_H__
#include <vector>
#include <string>
#include <map>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
#include <unistd.h> #include <unistd.h>
#include <stdbool.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdint.h> #include <stdint.h>
#include <linux/limits.h> #include <linux/limits.h>
@ -62,8 +60,8 @@ extern "C" {
#define PARTITION_NAME_OFFSET 56 #define PARTITION_NAME_OFFSET 56
#define MAX_GPT_NAME_SIZE 72 #define MAX_GPT_NAME_SIZE 72
//Bit 48 onwords in the attribute field are the ones where we are allowed to // Bit 48 onwords in the attribute field are the ones where we are allowed to
//store our AB attributes. // store our AB attributes.
#define AB_FLAG_OFFSET (ATTRIBUTE_FLAG_OFFSET + 6) #define AB_FLAG_OFFSET (ATTRIBUTE_FLAG_OFFSET + 6)
#define GPT_DISK_INIT_MAGIC 0xABCD #define GPT_DISK_INIT_MAGIC 0xABCD
#define AB_PARTITION_ATTR_SLOT_ACTIVE (0x1 << 2) #define AB_PARTITION_ATTR_SLOT_ACTIVE (0x1 << 2)
@ -76,14 +74,22 @@ extern "C" {
#define AB_SLOT_A_SUFFIX "_a" #define AB_SLOT_A_SUFFIX "_a"
#define AB_SLOT_B_SUFFIX "_b" #define AB_SLOT_B_SUFFIX "_b"
#define PTN_XBL "xbl" #define PTN_XBL "xbl"
#define PTN_SWAP_LIST \ // XBL is not included because the slot attributes are meaningless there
PTN_XBL, "abl", "aop", "apdp", "cmnlib", "cmnlib64", "devcfg", "dtbo", \ // *which* XBL partition is active is determined via the UFS bBootLunEn field
"hyp", "keymaster", "msadp", "qupfw", "storsec", "tz", \ // as it needs to be handled by PBL
"vbmeta", "vbmeta_system", "xbl_config" #define PTN_SWAP_LIST \
"abl_a", "aop_a", "apdp_a", "cmnlib_a", "cmnlib64_a", "devcfg_a", "dtbo_a", \
#define AB_PTN_LIST \ "hyp_a", "keymaster_a", "msadp_a", "qupfw_a", "storsec_a", "tz_a", \
PTN_SWAP_LIST, "boot", "system", "vendor", "modem", "system_ext", \ "vbmeta_a", "vbmeta_system_a"
"product"
static const char g_all_ptns[][MAX_GPT_NAME_SIZE] = {
PTN_SWAP_LIST, "boot_a", "system",
"vendor_a", "modem_a", "system_ext_a", "product_a"
};
// No more than /dev/sdk
#define MAX_BLOCK_DEVICES 10
#define BOOT_DEV_DIR "/dev/disk/by-partlabel" #define BOOT_DEV_DIR "/dev/disk/by-partlabel"
#define EMMC_DEVICE "/dev/mmcblk0" #define EMMC_DEVICE "/dev/mmcblk0"
@ -95,78 +101,69 @@ enum gpt_instance { PRIMARY_GPT = 0, SECONDARY_GPT };
enum boot_chain { NORMAL_BOOT = 0, BACKUP_BOOT }; enum boot_chain { NORMAL_BOOT = 0, BACKUP_BOOT };
struct gpt_disk { struct gpt_disk {
//GPT primary header // GPT primary header
uint8_t *hdr; uint8_t *hdr;
//primary header crc // primary header crc
uint32_t hdr_crc; uint32_t hdr_crc;
//GPT backup header // GPT backup header
uint8_t *hdr_bak; uint8_t *hdr_bak;
//backup header crc // backup header crc
uint32_t hdr_bak_crc; uint32_t hdr_bak_crc;
//Partition entries array // Partition entries array
uint8_t *pentry_arr; uint8_t *pentry_arr;
//Partition entries array for backup table // Partition entries array for backup table
uint8_t *pentry_arr_bak; uint8_t *pentry_arr_bak;
//Size of the pentry array // Size of the pentry array
uint32_t pentry_arr_size; uint32_t pentry_arr_size;
//Size of each element in the pentry array // Size of each element in the pentry array
uint32_t pentry_size; uint32_t pentry_size;
//CRC of the partition entry array // CRC of the partition entry array
uint32_t pentry_arr_crc; uint32_t pentry_arr_crc;
//CRC of the backup partition entry array // CRC of the backup partition entry array
uint32_t pentry_arr_bak_crc; uint32_t pentry_arr_bak_crc;
//Path to block dev representing the disk // Path to block dev representing the disk
char devpath[PATH_MAX]; char devpath[PATH_MAX];
//Block size of disk // Block size of disk
uint32_t block_size; uint32_t block_size;
uint32_t is_initialized; uint32_t is_initialized;
}; };
//GPT disk methods // GPT disk methods
bool gpt_disk_is_valid(struct gpt_disk *disk); bool gpt_disk_is_valid(struct gpt_disk *disk);
//Free previously allocated gpt_disk struct // Free previously allocated gpt_disk struct
void gpt_disk_free(struct gpt_disk *disk); void gpt_disk_free(struct gpt_disk *disk);
//Get the details of the disk holding the partition whose name // Get the details of the disk holding the partition whose name
//is passed in via dev // is passed in via dev
int gpt_disk_get_disk_info(const char *dev, struct gpt_disk *disk); int gpt_disk_get_disk_info(const char *dev, struct gpt_disk *disk);
bool partition_is_for_disk(const char *part, struct gpt_disk *disk, char *blockdev, int blockdev_len); int partition_is_for_disk(const struct gpt_disk *disk, const char *part, char *blockdev, int blockdev_len);
//Get pointer to partition entry from a allocated gpt_disk structure // Get pointer to partition entry from a allocated gpt_disk structure
uint8_t *gpt_disk_get_pentry(struct gpt_disk *disk, const char *partname, uint8_t *gpt_disk_get_pentry(struct gpt_disk *disk, const char *partname,
enum gpt_instance instance); enum gpt_instance instance);
//Write the contents of struct gpt_disk back to the actual disk // Write the contents of struct gpt_disk back to the actual disk
int gpt_disk_commit(struct gpt_disk *disk); int gpt_disk_commit(struct gpt_disk *disk);
//Swtich betwieen using either the primary or the backup // Swtich betwieen using either the primary or the backup
//boot LUN for boot. This is required since UFS boot partitions // boot LUN for boot. This is required since UFS boot partitions
//cannot have a backup GPT which is what we use for failsafe // cannot have a backup GPT which is what we use for failsafe
//updates of the other 'critical' partitions. This function will // updates of the other 'critical' partitions. This function will
//not be invoked for emmc targets and on UFS targets is only required // not be invoked for emmc targets and on UFS targets is only required
//to be invoked for XBL. // to be invoked for XBL.
// //
//The algorithm to do this is as follows: // The algorithm to do this is as follows:
//- Find the real block device(eg: /dev/block/sdb) that corresponds // - Find the real block device(eg: /dev/block/sdb) that corresponds
// to the /dev/block/bootdevice/by-name/xbl(bak) symlink // to the /dev/block/bootdevice/by-name/xbl(bak) symlink
// //
//- Once we have the block device 'node' name(sdb in the above example) // - Once we have the block device 'node' name(sdb in the above example)
// use this node to to locate the scsi generic device that represents // use this node to to locate the scsi generic device that represents
// it by checking the file /sys/block/sdb/device/scsi_generic/sgY // it by checking the file /sys/block/sdb/device/scsi_generic/sgY
// //
//- Once we locate sgY we call the query ioctl on /dev/sgy to switch // - Once we locate sgY we call the query ioctl on /dev/sgy to switch
//the boot lun to either LUNA or LUNB // the boot lun to either LUNA or LUNB
int gpt_utils_set_xbl_boot_partition(enum boot_chain chain); int gpt_utils_set_xbl_boot_partition(enum boot_chain chain);
//Given a vector of partition names as a input and a reference to a map,
//populate the map to indicate which physical disk each of the partitions
//sits on. The key in the map is the path to the block device where the
//partition lies and the value is a vector of strings indicating which of
//the passed in partition names sits on that device.
int gpt_utils_get_partition_map(
std::vector<std::string> &partition_list,
std::map<std::string, std::vector<std::string> > &partition_map);
bool gpt_utils_is_partition_backed_by_emmc(const char *part); bool gpt_utils_is_partition_backed_by_emmc(const char *part);
#ifdef __cplusplus #ifdef __cplusplus
} }

@ -1,6 +1,6 @@
project('qbootctl', 'cpp', default_options : ['c_std=c11', 'cpp_std=c++17']) project('qbootctl', 'c', default_options : ['c_std=c11'])
cc = meson.get_compiler('cpp') cc = meson.get_compiler('c')
deps = [ deps = [
dependency('zlib'), dependency('zlib'),
@ -11,11 +11,11 @@ if not cc.has_header('linux/bsg.h')
endif endif
src = [ src = [
'qbootctl.cpp', 'qbootctl.c',
'bootctrl_impl.cpp', 'bootctrl_impl.c',
'bootctrl_test.cpp', 'bootctrl_test.c',
'gpt-utils.cpp', 'gpt-utils.c',
'ufs-bsg.cpp', 'ufs-bsg.c',
] ]
inc = [ inc = [

@ -20,6 +20,7 @@
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h> #include <unistd.h>
#include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include "bootctrl.h" #include "bootctrl.h"

@ -81,16 +81,16 @@ static int ufs_bsg_ioctl(int fd, struct ufs_bsg_request *req,
enum bsg_ioctl_dir dir) enum bsg_ioctl_dir dir)
{ {
int ret; int ret;
struct sg_io_v4 sg_io { struct sg_io_v4 sg_io = {
.guard = 'Q',
.protocol = BSG_PROTOCOL_SCSI,
.subprotocol = BSG_SUB_PROTOCOL_SCSI_TRANSPORT,
.request_len = sizeof(*req),
.request = (__u64)req,
.response = (__u64)rsp,
.max_response_len = sizeof(*rsp),
}; };
sg_io.guard = 'Q';
sg_io.protocol = BSG_PROTOCOL_SCSI;
sg_io.subprotocol = BSG_SUB_PROTOCOL_SCSI_TRANSPORT;
sg_io.request_len = sizeof(*req);
sg_io.request = (__u64)req;
sg_io.response = (__u64)rsp;
sg_io.max_response_len = sizeof(*rsp);
if (dir == BSG_IOCTL_DIR_FROM_DEV) { if (dir == BSG_IOCTL_DIR_FROM_DEV) {
sg_io.din_xfer_len = buf_len; sg_io.din_xfer_len = buf_len;
sg_io.din_xferp = (__u64)(buf); sg_io.din_xferp = (__u64)(buf);
@ -137,10 +137,8 @@ static void compose_ufs_bsg_query_req(struct ufs_bsg_request *req, __u8 func,
static int ufs_query_attr(int fd, __u32 value, __u8 func, __u8 opcode, __u8 idn, static int ufs_query_attr(int fd, __u32 value, __u8 func, __u8 opcode, __u8 idn,
__u8 index, __u8 sel) __u8 index, __u8 sel)
{ {
struct ufs_bsg_request req { struct ufs_bsg_request req = { 0 };
}; struct ufs_bsg_reply rsp = { 0 };
struct ufs_bsg_reply rsp {
};
enum bsg_ioctl_dir dir = BSG_IOCTL_DIR_FROM_DEV; enum bsg_ioctl_dir dir = BSG_IOCTL_DIR_FROM_DEV;
int ret = 0; int ret = 0;
Loading…
Cancel
Save