// SPDX-License-Identifier: GPL-2.0+ /* * (C) Copyright 2001 * Wolfgang Denk, DENX Software Engineering, wd@denx.de. */ #include #include #include #include #include #include #include #include #include #include #undef PART_DEBUG #ifdef PART_DEBUG #define PRINTF(fmt,args...) printf (fmt ,##args) #else #define PRINTF(fmt,args...) #endif /* Check all partition types */ #define PART_TYPE_ALL -1 /** * part_driver_get_type() - Get a driver given its type * * @part_type: Partition type to find the driver for * Return: Driver for that type, or NULL if none */ static struct part_driver *part_driver_get_type(int part_type) { struct part_driver *drv = ll_entry_start(struct part_driver, part_driver); const int n_ents = ll_entry_count(struct part_driver, part_driver); struct part_driver *entry; for (entry = drv; entry != drv + n_ents; entry++) { if (part_type == entry->part_type) return entry; } /* Not found */ return NULL; } /** * part_driver_lookup_type() - Look up the partition driver for a blk device * * If @desc->part_type is PART_TYPE_UNKNOWN, this checks each parition driver * against the blk device to see if there is a valid partition table acceptable * to that driver. * * If @desc->part_type is already set, it just returns the driver for that * type, without testing if the driver can find a valid partition on the * descriptor. * * On success it updates @desc->part_type if set to PART_TYPE_UNKNOWN on entry * * @dev_desc: Device descriptor * Return: Driver found, or NULL if none */ static struct part_driver *part_driver_lookup_type(struct blk_desc *desc) { struct part_driver *drv = ll_entry_start(struct part_driver, part_driver); const int n_ents = ll_entry_count(struct part_driver, part_driver); struct part_driver *entry; if (desc->part_type == PART_TYPE_UNKNOWN) { for (entry = drv; entry != drv + n_ents; entry++) { int ret; ret = entry->test(desc); if (!ret) { desc->part_type = entry->part_type; return entry; } } } else { return part_driver_get_type(desc->part_type); } /* Not found */ return NULL; } int part_get_type_by_name(const char *name) { struct part_driver *drv = ll_entry_start(struct part_driver, part_driver); const int n_ents = ll_entry_count(struct part_driver, part_driver); struct part_driver *entry; for (entry = drv; entry != drv + n_ents; entry++) { if (!strcasecmp(name, entry->name)) return entry->part_type; } /* Not found */ return PART_TYPE_UNKNOWN; } /** * get_dev_hwpart() - Get the descriptor for a device with hardware partitions * * @ifname: Interface name (e.g. "ide", "scsi") * @dev: Device number (0 for first device on that interface, 1 for * second, etc. * @hwpart: Hardware partition, or 0 if none (used for MMC) * Return: pointer to the block device, or NULL if not available, or an * error occurred. */ static struct blk_desc *get_dev_hwpart(const char *ifname, int dev, int hwpart) { struct blk_desc *desc; int ret; if (!blk_enabled()) return NULL; desc = blk_get_devnum_by_uclass_idname(ifname, dev); if (!desc) { debug("%s: No device for iface '%s', dev %d\n", __func__, ifname, dev); return NULL; } ret = blk_dselect_hwpart(desc, hwpart); if (ret) { debug("%s: Failed to select h/w partition: err-%d\n", __func__, ret); return NULL; } return desc; } struct blk_desc *blk_get_dev(const char *ifname, int dev) { if (!blk_enabled()) return NULL; return get_dev_hwpart(ifname, dev, 0); } /* ------------------------------------------------------------------------- */ /* * reports device info to the user */ #ifdef CONFIG_LBA48 typedef uint64_t lba512_t; #else typedef lbaint_t lba512_t; #endif /* * Overflowless variant of (block_count * mul_by / 2**right_shift) * when 2**right_shift > mul_by */ static lba512_t lba512_muldiv(lba512_t block_count, lba512_t mul_by, int right_shift) { lba512_t bc_quot, bc_rem; /* x * m / d == x / d * m + (x % d) * m / d */ bc_quot = block_count >> right_shift; bc_rem = block_count - (bc_quot << right_shift); return bc_quot * mul_by + ((bc_rem * mul_by) >> right_shift); } void dev_print(struct blk_desc *desc) { lba512_t lba512; /* number of blocks if 512bytes block size */ if (desc->type == DEV_TYPE_UNKNOWN) { puts ("not available\n"); return; } switch (desc->uclass_id) { case UCLASS_SCSI: printf("(%d:%d) Vendor: %s Prod.: %s Rev: %s\n", desc->target, desc->lun, desc->vendor, desc->product, desc->revision); break; case UCLASS_IDE: case UCLASS_AHCI: printf("Model: %s Firm: %s Ser#: %s\n", desc->vendor, desc->revision, desc->product); break; case UCLASS_MMC: case UCLASS_USB: case UCLASS_NVME: case UCLASS_PVBLOCK: case UCLASS_HOST: case UCLASS_BLKMAP: printf ("Vendor: %s Rev: %s Prod: %s\n", desc->vendor, desc->revision, desc->product); break; case UCLASS_VIRTIO: printf("%s VirtIO Block Device\n", desc->vendor); break; case UCLASS_EFI_MEDIA: printf("EFI media Block Device %d\n", desc->devnum); break; case UCLASS_INVALID: puts("device type unknown\n"); return; default: printf("Unhandled device type: %i\n", desc->uclass_id); return; } puts (" Type: "); if (desc->removable) puts ("Removable "); switch (desc->type & 0x1F) { case DEV_TYPE_HARDDISK: puts ("Hard Disk"); break; case DEV_TYPE_CDROM: puts ("CD ROM"); break; case DEV_TYPE_OPDISK: puts ("Optical Device"); break; case DEV_TYPE_TAPE: puts ("Tape"); break; default: printf("# %02X #", desc->type & 0x1F); break; } puts ("\n"); if (desc->lba > 0L && desc->blksz > 0L) { ulong mb, mb_quot, mb_rem, gb, gb_quot, gb_rem; lbaint_t lba; lba = desc->lba; lba512 = lba * (desc->blksz / 512); /* round to 1 digit */ /* 2048 = (1024 * 1024) / 512 MB */ mb = lba512_muldiv(lba512, 10, 11); mb_quot = mb / 10; mb_rem = mb - (10 * mb_quot); gb = mb / 1024; gb_quot = gb / 10; gb_rem = gb - (10 * gb_quot); #ifdef CONFIG_LBA48 if (desc->lba48) printf (" Supports 48-bit addressing\n"); #endif #if defined(CONFIG_SYS_64BIT_LBA) printf (" Capacity: %lu.%lu MB = %lu.%lu GB (%llu x %lu)\n", mb_quot, mb_rem, gb_quot, gb_rem, lba, desc->blksz); #else printf (" Capacity: %lu.%lu MB = %lu.%lu GB (%lu x %lu)\n", mb_quot, mb_rem, gb_quot, gb_rem, (ulong)lba, desc->blksz); #endif } else { puts (" Capacity: not available\n"); } } void part_init(struct blk_desc *desc) { struct part_driver *drv = ll_entry_start(struct part_driver, part_driver); const int n_ents = ll_entry_count(struct part_driver, part_driver); struct part_driver *entry; blkcache_invalidate(desc->uclass_id, desc->devnum); desc->part_type = PART_TYPE_UNKNOWN; for (entry = drv; entry != drv + n_ents; entry++) { int ret; ret = entry->test(desc); debug("%s: try '%s': ret=%d\n", __func__, entry->name, ret); if (!ret) { desc->part_type = entry->part_type; break; } } } static void print_part_header(const char *type, struct blk_desc *desc) { #if CONFIG_IS_ENABLED(MAC_PARTITION) || \ CONFIG_IS_ENABLED(DOS_PARTITION) || \ CONFIG_IS_ENABLED(ISO_PARTITION) || \ CONFIG_IS_ENABLED(AMIGA_PARTITION) || \ CONFIG_IS_ENABLED(EFI_PARTITION) puts ("\nPartition Map for "); switch (desc->uclass_id) { case UCLASS_IDE: puts ("IDE"); break; case UCLASS_AHCI: puts ("SATA"); break; case UCLASS_SCSI: puts ("SCSI"); break; case UCLASS_USB: puts ("USB"); break; case UCLASS_MMC: puts ("MMC"); break; case UCLASS_HOST: puts ("HOST"); break; case UCLASS_NVME: puts ("NVMe"); break; case UCLASS_PVBLOCK: puts("PV BLOCK"); break; case UCLASS_VIRTIO: puts("VirtIO"); break; case UCLASS_EFI_MEDIA: puts("EFI"); break; case UCLASS_BLKMAP: puts("BLKMAP"); break; default: printf("UNKNOWN(%d)", desc->uclass_id); break; } printf (" device %d -- Partition Type: %s\n\n", desc->devnum, type); #endif /* any CONFIG_..._PARTITION */ } void part_print(struct blk_desc *desc) { struct part_driver *drv; drv = part_driver_lookup_type(desc); if (!drv) { printf("## Unknown partition table type %x\n", desc->part_type); return; } PRINTF("## Testing for valid %s partition ##\n", drv->name); print_part_header(drv->name, desc); if (drv->print) drv->print(desc); } int part_get_info_by_type(struct blk_desc *desc, int part, int part_type, struct disk_partition *info) { struct part_driver *drv; if (blk_enabled()) { /* The common case is no UUID support */ disk_partition_clr_uuid(info); disk_partition_clr_type_guid(info); if (part_type == PART_TYPE_UNKNOWN) { drv = part_driver_lookup_type(desc); } else { drv = part_driver_get_type(part_type); } if (!drv) { debug("## Unknown partition table type %x\n", desc->part_type); return -EPROTONOSUPPORT; } if (!drv->get_info) { PRINTF("## Driver %s does not have the get_info() method\n", drv->name); return -ENOSYS; } if (drv->get_info(desc, part, info) == 0) { PRINTF("## Valid %s partition found ##\n", drv->name); return 0; } } return -ENOENT; } int part_get_info(struct blk_desc *desc, int part, struct disk_partition *info) { return part_get_info_by_type(desc, part, PART_TYPE_UNKNOWN, info); } int part_get_info_whole_disk(struct blk_desc *desc, struct disk_partition *info) { info->start = 0; info->size = desc->lba; info->blksz = desc->blksz; info->bootable = 0; strcpy((char *)info->type, BOOT_PART_TYPE); strcpy((char *)info->name, "Whole Disk"); disk_partition_clr_uuid(info); disk_partition_clr_type_guid(info); return 0; } int blk_get_device_by_str(const char *ifname, const char *dev_hwpart_str, struct blk_desc **desc) { char *ep; char *dup_str = NULL; const char *dev_str, *hwpart_str; int dev, hwpart; hwpart_str = strchr(dev_hwpart_str, '.'); if (hwpart_str) { dup_str = strdup(dev_hwpart_str); dup_str[hwpart_str - dev_hwpart_str] = 0; dev_str = dup_str; hwpart_str++; } else { dev_str = dev_hwpart_str; hwpart = 0; } dev = hextoul(dev_str, &ep); if (*ep) { printf("** Bad device specification %s %s **\n", ifname, dev_str); dev = -EINVAL; goto cleanup; } if (hwpart_str) { hwpart = hextoul(hwpart_str, &ep); if (*ep) { printf("** Bad HW partition specification %s %s **\n", ifname, hwpart_str); dev = -EINVAL; goto cleanup; } } *desc = get_dev_hwpart(ifname, dev, hwpart); if (!(*desc) || ((*desc)->type == DEV_TYPE_UNKNOWN)) { debug("** Bad device %s %s **\n", ifname, dev_hwpart_str); dev = -ENODEV; goto cleanup; } if (blk_enabled()) { /* * Updates the partition table for the specified hw partition. * Always should be done, otherwise hw partition 0 will return * stale data after displaying a non-zero hw partition. */ if ((*desc)->uclass_id == UCLASS_MMC) part_init(*desc); } cleanup: free(dup_str); return dev; } #define PART_UNSPECIFIED -2 #define PART_AUTO -1 int blk_get_device_part_str(const char *ifname, const char *dev_part_str, struct blk_desc **desc, struct disk_partition *info, int allow_whole_dev) { int ret; const char *part_str; char *dup_str = NULL; const char *dev_str; int dev; char *ep; int p; int part; struct disk_partition tmpinfo; *desc = NULL; memset(info, 0, sizeof(*info)); #if IS_ENABLED(CONFIG_SANDBOX) || IS_ENABLED(CONFIG_SEMIHOSTING) /* * Special-case a pseudo block device "hostfs", to allow access to the * host's own filesystem. */ if (!strcmp(ifname, "hostfs")) { strcpy((char *)info->type, BOOT_PART_TYPE); strcpy((char *)info->name, "Host filesystem"); return 0; } #endif #if IS_ENABLED(CONFIG_CMD_UBIFS) && !IS_ENABLED(CONFIG_SPL_BUILD) /* * Special-case ubi, ubi goes through a mtd, rather than through * a regular block device. */ if (!strcmp(ifname, "ubi")) { if (!ubifs_is_mounted()) { printf("UBIFS not mounted, use ubifsmount to mount volume first!\n"); return -EINVAL; } strcpy((char *)info->type, BOOT_PART_TYPE); strcpy((char *)info->name, "UBI"); return 0; } #endif /* If no dev_part_str, use bootdevice environment variable */ if (CONFIG_IS_ENABLED(ENV_SUPPORT)) { if (!dev_part_str || !strlen(dev_part_str) || !strcmp(dev_part_str, "-")) dev_part_str = env_get("bootdevice"); } /* If still no dev_part_str, it's an error */ if (!dev_part_str) { printf("** No device specified **\n"); ret = -ENODEV; goto cleanup; } /* Separate device and partition ID specification */ part_str = strchr(dev_part_str, ':'); if (part_str) { dup_str = strdup(dev_part_str); dup_str[part_str - dev_part_str] = 0; dev_str = dup_str; part_str++; } else { dev_str = dev_part_str; } /* Look up the device */ dev = blk_get_device_by_str(ifname, dev_str, desc); if (dev < 0) { printf("** Bad device specification %s %s **\n", ifname, dev_str); ret = dev; goto cleanup; } /* Convert partition ID string to number */ if (!part_str || !*part_str) { part = PART_UNSPECIFIED; } else if (!strcmp(part_str, "auto")) { part = PART_AUTO; } else { /* Something specified -> use exactly that */ part = (int)hextoul(part_str, &ep); /* * Less than whole string converted, * or request for whole device, but caller requires partition. */ if (*ep || (part == 0 && !allow_whole_dev)) { printf("** Bad partition specification %s %s **\n", ifname, dev_part_str); ret = -ENOENT; goto cleanup; } } /* * No partition table on device, * or user requested partition 0 (entire device). */ if (((*desc)->part_type == PART_TYPE_UNKNOWN) || !part) { if (!(*desc)->lba) { printf("** Bad device size - %s %s **\n", ifname, dev_str); ret = -EINVAL; goto cleanup; } /* * If user specified a partition ID other than 0, * or the calling command only accepts partitions, * it's an error. */ if ((part > 0) || (!allow_whole_dev)) { printf("** No partition table - %s %s **\n", ifname, dev_str); ret = -EPROTONOSUPPORT; goto cleanup; } (*desc)->log2blksz = LOG2((*desc)->blksz); part_get_info_whole_disk(*desc, info); ret = 0; goto cleanup; } /* * Now there's known to be a partition table, * not specifying a partition means to pick partition 1. */ if (part == PART_UNSPECIFIED) part = 1; /* * If user didn't specify a partition number, or did specify something * other than "auto", use that partition number directly. */ if (part != PART_AUTO) { ret = part_get_info(*desc, part, info); if (ret) { printf("** Invalid partition %d **\n", part); goto cleanup; } } else { /* * Find the first bootable partition. * If none are bootable, fall back to the first valid partition. */ part = 0; for (p = 1; p <= MAX_SEARCH_PARTITIONS; p++) { ret = part_get_info(*desc, p, info); if (ret) continue; /* * First valid partition, or new better partition? * If so, save partition ID. */ if (!part || info->bootable) part = p; /* Best possible partition? Stop searching. */ if (info->bootable) break; /* * We now need to search further for best possible. * If we what we just queried was the best so far, * save the info since we over-write it next loop. */ if (part == p) tmpinfo = *info; } /* If we found any acceptable partition */ if (part) { /* * If we searched all possible partition IDs, * return the first valid partition we found. */ if (p == MAX_SEARCH_PARTITIONS + 1) *info = tmpinfo; } else { printf("** No valid partitions found **\n"); goto cleanup; } } if (strncmp((char *)info->type, BOOT_PART_TYPE, sizeof(info->type)) != 0) { printf("** Invalid partition type \"%.32s\"" " (expect \"" BOOT_PART_TYPE "\")\n", info->type); ret = -EINVAL; goto cleanup; } (*desc)->log2blksz = LOG2((*desc)->blksz); ret = part; goto cleanup; cleanup: free(dup_str); return ret; } int part_get_info_by_name(struct blk_desc *desc, const char *name, struct disk_partition *info) { struct part_driver *part_drv; int ret; int i; part_drv = part_driver_lookup_type(desc); if (!part_drv) return -1; if (!part_drv->get_info) { log_debug("## Driver %s does not have the get_info() method\n", part_drv->name); return -ENOSYS; } for (i = 1; i < part_drv->max_entries; i++) { ret = part_drv->get_info(desc, i, info); if (ret != 0) { /* no more entries in table */ break; } if (strcmp(name, (const char *)info->name) == 0) { /* matched */ return i; } } return -ENOENT; } /** * Get partition info from device number and partition name. * * Parse a device number and partition name string in the form of * "devicenum.hwpartnum#partition_name", for example "0.1#misc". devicenum and * hwpartnum are both optional, defaulting to 0. If the partition is found, * sets desc and part_info accordingly with the information of the * partition with the given partition_name. * * @param[in] dev_iface Device interface * @param[in] dev_part_str Input string argument, like "0.1#misc" * @param[out] desc Place to store the device description pointer * @param[out] part_info Place to store the partition information * Return: 0 on success, or a negative on error */ static int part_get_info_by_dev_and_name(const char *dev_iface, const char *dev_part_str, struct blk_desc **desc, struct disk_partition *part_info) { char *dup_str = NULL; const char *dev_str, *part_str; int ret; /* Separate device and partition name specification */ if (dev_part_str) part_str = strchr(dev_part_str, '#'); else part_str = NULL; if (part_str) { dup_str = strdup(dev_part_str); dup_str[part_str - dev_part_str] = 0; dev_str = dup_str; part_str++; } else { return -EINVAL; } ret = blk_get_device_by_str(dev_iface, dev_str, desc); if (ret < 0) goto cleanup; ret = part_get_info_by_name(*desc, part_str, part_info); if (ret < 0) printf("Could not find \"%s\" partition\n", part_str); cleanup: free(dup_str); return ret; } int part_get_info_by_dev_and_name_or_num(const char *dev_iface, const char *dev_part_str, struct blk_desc **desc, struct disk_partition *part_info, int allow_whole_dev) { int ret; /* Split the part_name if passed as "$dev_num#part_name". */ ret = part_get_info_by_dev_and_name(dev_iface, dev_part_str, desc, part_info); if (ret >= 0) return ret; /* * Couldn't lookup by name, try looking up the partition description * directly. */ ret = blk_get_device_part_str(dev_iface, dev_part_str, desc, part_info, allow_whole_dev); if (ret < 0) printf("Couldn't find partition %s %s\n", dev_iface, dev_part_str); return ret; } void part_set_generic_name(const struct blk_desc *desc, int part_num, char *name) { char *devtype; switch (desc->uclass_id) { case UCLASS_IDE: case UCLASS_AHCI: devtype = "hd"; break; case UCLASS_SCSI: devtype = "sd"; break; case UCLASS_USB: devtype = "usbd"; break; case UCLASS_MMC: devtype = "mmcsd"; break; default: devtype = "xx"; break; } sprintf(name, "%s%c%d", devtype, 'a' + desc->devnum, part_num); } int part_get_bootable(struct blk_desc *desc) { struct disk_partition info; int p; for (p = 1; p <= MAX_SEARCH_PARTITIONS; p++) { int ret; ret = part_get_info(desc, p, &info); if (!ret && info.bootable) return p; } return 0; }