NVIDIA Tegra consumer devices have EMMC storage that has GPT entry at a non-standard location. Support looking up GPT entry at a special sector to enable such devices. Signed-off-by: Dmitry Osipenko <digetx@xxxxxxxxx> --- block/partitions/Kconfig | 8 +++++ block/partitions/Makefile | 1 + block/partitions/check.h | 2 ++ block/partitions/core.c | 3 ++ block/partitions/efi.c | 9 +++++ block/partitions/tegra.c | 75 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 98 insertions(+) create mode 100644 block/partitions/tegra.c diff --git a/block/partitions/Kconfig b/block/partitions/Kconfig index 278593b8e4e9..5db25e7efbb7 100644 --- a/block/partitions/Kconfig +++ b/block/partitions/Kconfig @@ -267,3 +267,11 @@ config CMDLINE_PARTITION help Say Y here if you want to read the partition table from bootargs. The format for the command line is just like mtdparts. + +config TEGRA_PARTITION + bool "NVIDIA Tegra Partition support" if PARTITION_ADVANCED + default y if ARCH_TEGRA + depends on EFI_PARTITION && MMC_BLOCK && (ARCH_TEGRA || COMPILE_TEST) + help + Say Y here if you would like to be able to read the hard disk + partition table format used by NVIDIA Tegra machines. diff --git a/block/partitions/Makefile b/block/partitions/Makefile index a7f05cdb02a8..83cb70c6d08d 100644 --- a/block/partitions/Makefile +++ b/block/partitions/Makefile @@ -20,3 +20,4 @@ obj-$(CONFIG_IBM_PARTITION) += ibm.o obj-$(CONFIG_EFI_PARTITION) += efi.o obj-$(CONFIG_KARMA_PARTITION) += karma.o obj-$(CONFIG_SYSV68_PARTITION) += sysv68.o +obj-$(CONFIG_TEGRA_PARTITION) += tegra.o diff --git a/block/partitions/check.h b/block/partitions/check.h index d5b28e309d64..bd4b66443cf4 100644 --- a/block/partitions/check.h +++ b/block/partitions/check.h @@ -22,6 +22,7 @@ struct parsed_partitions { int limit; bool access_beyond_eod; char *pp_buf; + sector_t force_gpt_sector; }; typedef struct { @@ -67,4 +68,5 @@ int osf_partition(struct parsed_partitions *state); int sgi_partition(struct parsed_partitions *state); int sun_partition(struct parsed_partitions *state); int sysv68_partition(struct parsed_partitions *state); +int tegra_partition_forced_gpt(struct parsed_partitions *state); int ultrix_partition(struct parsed_partitions *state); diff --git a/block/partitions/core.c b/block/partitions/core.c index 9265936df77e..bbfbb1621581 100644 --- a/block/partitions/core.c +++ b/block/partitions/core.c @@ -82,6 +82,9 @@ static int (*check_part[])(struct parsed_partitions *) = { #endif #ifdef CONFIG_SYSV68_PARTITION sysv68_partition, +#endif +#ifdef CONFIG_TEGRA_PARTITION + tegra_partition_forced_gpt, #endif NULL }; diff --git a/block/partitions/efi.c b/block/partitions/efi.c index aaa3dc487cb5..4ad151176204 100644 --- a/block/partitions/efi.c +++ b/block/partitions/efi.c @@ -619,6 +619,15 @@ static int find_valid_gpt(struct parsed_partitions *state, gpt_header **gpt, if (!good_agpt && force_gpt) good_agpt = is_gpt_valid(state, lastlba, &agpt, &aptes); + /* + * The force_gpt_sector is used by NVIDIA Tegra partition parser in + * order to convey a non-standard location of the GPT entry for lookup. + * By default force_gpt_sector is set to 0 and has no effect. + */ + if (!good_agpt && force_gpt && state->force_gpt_sector) + good_agpt = is_gpt_valid(state, state->force_gpt_sector, + &agpt, &aptes); + /* The obviously unsuccessful case */ if (!good_pgpt && !good_agpt) goto fail; diff --git a/block/partitions/tegra.c b/block/partitions/tegra.c new file mode 100644 index 000000000000..4937e9f62398 --- /dev/null +++ b/block/partitions/tegra.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0 + +#define pr_fmt(fmt) "tegra-partition: " fmt + +#include <linux/blkdev.h> +#include <linux/kernel.h> +#include <linux/of.h> +#include <linux/sizes.h> + +#include <linux/mmc/blkdev.h> +#include <linux/mmc/card.h> +#include <linux/mmc/host.h> + +#include <soc/tegra/common.h> + +#include "check.h" + +static const struct of_device_id tegra_sdhci_match[] = { + { .compatible = "nvidia,tegra20-sdhci", }, + { .compatible = "nvidia,tegra30-sdhci", }, + { .compatible = "nvidia,tegra114-sdhci", }, + { .compatible = "nvidia,tegra124-sdhci", }, + {} +}; + +int tegra_partition_forced_gpt(struct parsed_partitions *state) +{ + struct gendisk *disk = state->disk; + struct block_device *bdev = disk->part0; + struct mmc_card *card = mmc_bdev_to_card(bdev); + int ret, boot_offset; + + if (!soc_is_tegra()) + return 0; + + /* filter out unrelated and untested boot sources */ + if (!card || card->ext_csd.rev < 3 || + !mmc_card_is_blockaddr(card) || + mmc_card_is_removable(card->host) || + bdev_logical_block_size(bdev) != SZ_512 || + !of_match_node(tegra_sdhci_match, card->host->parent->of_node)) { + pr_debug("%s: unexpected boot source\n", disk->disk_name); + return 0; + } + + /* + * eMMC storage has two special boot partitions in addition to the + * main one. NVIDIA's bootloader linearizes eMMC boot0->boot1->main + * accesses, this means that the partition table addresses are shifted + * by the size of boot partitions. In accordance with the eMMC + * specification, the boot partition size is calculated as follows: + * + * boot partition size = 128K byte x BOOT_SIZE_MULT + * + * This function returns number of sectors occupied by the both boot + * partitions. + */ + boot_offset = card->ext_csd.raw_boot_mult * SZ_128K / + SZ_512 * MMC_NUM_BOOT_PARTITION; + + /* + * The fixed GPT entry address is calculated like this: + * + * gpt_sector = ext_csd.sectors_num - ext_csd.boot_sectors_num - 1 + * + * This algorithm is defined by NVIDIA and used by Android devices. + */ + state->force_gpt_sector = get_capacity(disk); + state->force_gpt_sector -= boot_offset + 1; + + ret = efi_partition(state); + state->force_gpt_sector = 0; + + return ret; +} -- 2.32.0