diff mbox series

[v1,3/3] partitions/efi: Support gpt_sector parameter needed by NVIDIA Tegra devices

Message ID 20210327212100.3834-4-digetx@gmail.com (mailing list archive)
State New, archived
Headers show
Series Support EFI partition on NVIDIA Tegra devices | expand

Commit Message

Dmitry Osipenko March 27, 2021, 9:21 p.m. UTC
All NVIDIA Tegra20..124 Android devices use proprietary bootloader
which supplies the gpt_sector=<sector> kernel cmdline parameter that
should be used for looking up the EFI partition table on internal EMMC
storage.  If the kernel cmdline parameter isn't supplied, then the
partition is expected to be placed around the last but one sector of EMMC.

Apparently this was done in order to hide the PT from a usual userspace
tools since EFI entry exists only for compatibility with a Linux kernel,
while a custom proprietary partition table is what is really used by
these Android devices, thus these tools may corrupt the real PT, making
device unbootable and very difficult to restore.

Add support for the gpt_sector cmdline parameter which will be used
for finding EFI entry on internal EMMC storage of NVIDIA Tegra20+ devices.

Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
 block/partitions/Kconfig  |   8 +++
 block/partitions/Makefile |   1 +
 block/partitions/check.h  |   2 +
 block/partitions/core.c   |   3 ++
 block/partitions/efi.c    |  18 +++++++
 block/partitions/tegra.c  | 108 ++++++++++++++++++++++++++++++++++++++
 6 files changed, 140 insertions(+)
 create mode 100644 block/partitions/tegra.c

Comments

Christoph Hellwig March 29, 2021, 6:18 a.m. UTC | #1
On Sun, Mar 28, 2021 at 12:21:00AM +0300, Dmitry Osipenko wrote:
> All NVIDIA Tegra20..124 Android devices use proprietary bootloader
> which supplies the gpt_sector=<sector> kernel cmdline parameter that
> should be used for looking up the EFI partition table on internal EMMC
> storage.  If the kernel cmdline parameter isn't supplied, then the
> partition is expected to be placed around the last but one sector of EMMC.
> 
> Apparently this was done in order to hide the PT from a usual userspace
> tools since EFI entry exists only for compatibility with a Linux kernel,

That's weird, because nothin in Linux relies in EFI partition tables.

> while a custom proprietary partition table is what is really used by
> these Android devices, thus these tools may corrupt the real PT, making
> device unbootable and very difficult to restore.
> 
> Add support for the gpt_sector cmdline parameter which will be used
> for finding EFI entry on internal EMMC storage of NVIDIA Tegra20+ devices.

Didn't we plan to just support the actual partition table format insted?
Dmitry Osipenko March 29, 2021, 1:07 p.m. UTC | #2
29.03.2021 09:18, Christoph Hellwig пишет:
> On Sun, Mar 28, 2021 at 12:21:00AM +0300, Dmitry Osipenko wrote:
>> All NVIDIA Tegra20..124 Android devices use proprietary bootloader
>> which supplies the gpt_sector=<sector> kernel cmdline parameter that
>> should be used for looking up the EFI partition table on internal EMMC
>> storage.  If the kernel cmdline parameter isn't supplied, then the
>> partition is expected to be placed around the last but one sector of EMMC.
>>
>> Apparently this was done in order to hide the PT from a usual userspace
>> tools since EFI entry exists only for compatibility with a Linux kernel,
> 
> That's weird, because nothin in Linux relies in EFI partition tables.
> 
>> while a custom proprietary partition table is what is really used by
>> these Android devices, thus these tools may corrupt the real PT, making
>> device unbootable and very difficult to restore.
>>
>> Add support for the gpt_sector cmdline parameter which will be used
>> for finding EFI entry on internal EMMC storage of NVIDIA Tegra20+ devices.
> 
> Didn't we plan to just support the actual partition table format insted?
> 

Ideally we need to support both forced-gpt and tegra-partition.  At
first we should check whether GPT entry exists and use it on success,
otherwise fall back to tegra-partition.  This will allow to properly
support all kind of devices.  The majority of devices have the GPT
entry, so will be good to support it at least.

My understanding that this all require involvement from Jens Axboe in
order to get the patches merged.  Previously he didn't show interest to
any of the patches and my assumption was that he didn't want to have
another partition, although I don't know for sure because he never answered.

If you could help with reviewing and applying of the patches, then I
will be happy to try again with re-sending a full patchset, which
includes the tegra-partition support.
Davidlohr Bueso March 29, 2021, 5:31 p.m. UTC | #3
On Sun, 28 Mar 2021, Dmitry Osipenko wrote:

>All NVIDIA Tegra20..124 Android devices use proprietary bootloader
>which supplies the gpt_sector=<sector> kernel cmdline parameter that
>should be used for looking up the EFI partition table on internal EMMC
>storage.  If the kernel cmdline parameter isn't supplied, then the
>partition is expected to be placed around the last but one sector of EMMC.
>
>Apparently this was done in order to hide the PT from a usual userspace
>tools since EFI entry exists only for compatibility with a Linux kernel,
>while a custom proprietary partition table is what is really used by
>these Android devices, thus these tools may corrupt the real PT, making
>device unbootable and very difficult to restore.
>
>Add support for the gpt_sector cmdline parameter which will be used
>for finding EFI entry on internal EMMC storage of NVIDIA Tegra20+ devices.

Since this is proprietary and playing yucky games hiding the pt, why not
just force for the fallback on Nvidia's side and always just use the entry
at the end of the block device? I'm not loving introducing a generic parameter
for an obscure ad-hoc feature.

Thanks,
Davidlohr
Dmitry Osipenko March 29, 2021, 6:38 p.m. UTC | #4
29.03.2021 20:31, Davidlohr Bueso пишет:
> On Sun, 28 Mar 2021, Dmitry Osipenko wrote:
> 
>> All NVIDIA Tegra20..124 Android devices use proprietary bootloader
>> which supplies the gpt_sector=<sector> kernel cmdline parameter that
>> should be used for looking up the EFI partition table on internal EMMC
>> storage.  If the kernel cmdline parameter isn't supplied, then the
>> partition is expected to be placed around the last but one sector of
>> EMMC.
>>
>> Apparently this was done in order to hide the PT from a usual userspace
>> tools since EFI entry exists only for compatibility with a Linux kernel,
>> while a custom proprietary partition table is what is really used by
>> these Android devices, thus these tools may corrupt the real PT, making
>> device unbootable and very difficult to restore.
>>
>> Add support for the gpt_sector cmdline parameter which will be used
>> for finding EFI entry on internal EMMC storage of NVIDIA Tegra20+
>> devices.
> 
> Since this is proprietary and playing yucky games hiding the pt, why not
> just force for the fallback on Nvidia's side and always just use the entry
> at the end of the block device? I'm not loving introducing a generic
> parameter for an obscure ad-hoc feature.

I understand the reluctance to support the ad-hoc features, I wouldn't
want to support this myself if I was in yours position.  This all is
necessary solely in order to provide a good user experience using
mainline kernel on the consumer devices that have Tegra SoC inside.  A
more advanced users could bypass the secure boot restrictions and
reformat the partition table as they wish, but this is very involved.
The only reason I'm submitting these patches is to allow more people to
have fun with their devices running mainline Linux without a need to
bother with extra out-of-tree patches.

There are total four possible variants of the PT that I'm aware about:

1. GPT entry exists at the proper "backup" location in the end of a
block device.  Unfortunately only Nexus 7 is known to have this.

2. GPT entry exists at the give sector offset using the gpt_sector=<>
parameter.  This offset may or may not match the offset that we can
calculate based on EMMC boot offset.  This case is common for the vast
majority of devices.

3. GPT entry exists at the expected sector offset which is calculated
based on EMMC boot offset.  This is common for devices that use older
versions of NVIDIA bootloader which doesn't provide the gpt_sector=<>
parameter.

4. GPT entry doesn't exist at all and other means are used to convey the
partition table info.  This is rare, but happens for the oldest devices.
 Using a reversed-engineered tegra-partition format helps in this case.

Technically we could use the reversed-engineered tegra-partition without
caring about GPT at all, but this may result in a different number of a
visible partitions to the system in comparison to the GPT entry because
some of partitions may be hidden from the GPT, like partition where
media keys are stored and etc.  This could be confusing and quite
inconvenient when /dev/mmcblk0p4 becomes /dev/mmcblk0p6, so it's more
preferred to use the existing GPT entry.
diff mbox series

Patch

diff --git a/block/partitions/Kconfig b/block/partitions/Kconfig
index 6e2a649669e5..be086916c6a6 100644
--- a/block/partitions/Kconfig
+++ b/block/partitions/Kconfig
@@ -268,3 +268,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 c577e9ee67f0..5fcc85087465 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 1a7558917c47..1a0247f3354c 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 b64bfdd4326c..f016a7f11239 100644
--- a/block/partitions/efi.c
+++ b/block/partitions/efi.c
@@ -98,6 +98,15 @@  static int force_gpt;
 static int __init
 force_gpt_fn(char *str)
 {
+	/*
+	 * This check allows to properly parse cmdline variants like
+	 * "gpt gpt_sector=<sector>" and "gpt_sector=<sector> gpt" since
+	 * "gpt" overlaps with the "gpt_sector=", see tegra_gpt_sector_fn().
+	 * The argument is absent for a boolean cmdline option.
+	 */
+	if (strlen(str))
+		return 0;
+
 	force_gpt = 1;
 	return 1;
 }
@@ -621,6 +630,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..585a2778ecc4
--- /dev/null
+++ b/block/partitions/tegra.c
@@ -0,0 +1,108 @@ 
+// SPDX-License-Identifier: GPL-2.0
+
+#define pr_fmt(fmt) "tegra-partition: " fmt
+
+#include <linux/blkdev.h>
+#include <linux/init.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"
+
+#define TEGRA_PT_ERR(_state, fmt, ...)					\
+	pr_debug("%s: " fmt,						\
+		 (_state)->bdev->bd_disk->disk_name, ##__VA_ARGS__)
+
+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", },
+	{}
+};
+
+static int
+tegra_partition_table_emmc_boot_offset(struct parsed_partitions *state)
+{
+	struct mmc_card *card = mmc_bdev_to_card(state->bdev);
+
+	/* 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(state->bdev) != SZ_512 ||
+	    !of_match_node(tegra_sdhci_match, card->host->parent->of_node)) {
+		TEGRA_PT_ERR(state, "unexpected boot source\n");
+		return -1;
+	}
+
+	/*
+	 * 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.
+	 */
+	return card->ext_csd.raw_boot_mult * SZ_128K /
+	       SZ_512 * MMC_NUM_BOOT_PARTITION;
+}
+
+/*
+ * This allows a kernel command line option 'gpt_sector=<sector>' to
+ * enable GPT header lookup at a non-standard location. This option
+ * is provided to kernel by NVIDIA's proprietary bootloader.
+ */
+static sector_t tegra_gpt_sector;
+static int __init tegra_gpt_sector_fn(char *str)
+{
+	WARN_ON(kstrtoull(str, 10, &tegra_gpt_sector) < 0);
+	return 1;
+}
+__setup("gpt_sector=", tegra_gpt_sector_fn);
+
+int tegra_partition_forced_gpt(struct parsed_partitions *state)
+{
+	int ret, boot_offset;
+
+	if (!soc_is_tegra())
+		return 0;
+
+	boot_offset = tegra_partition_table_emmc_boot_offset(state);
+	if (boot_offset < 0)
+		return 0;
+
+	if (tegra_gpt_sector) {
+		state->force_gpt_sector = tegra_gpt_sector;
+	} else {
+		/*
+		 * Some Tegra devices do not use gpt_sector=<sector> kernel
+		 * command line option.  In this case these devices should
+		 * have a GPT entry at the end of the block device and then
+		 * the 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 on Android
+		 * devices.
+		 */
+		state->force_gpt_sector  = get_capacity(state->bdev->bd_disk);
+		state->force_gpt_sector -= boot_offset + 1;
+	}
+
+	ret = efi_partition(state);
+	state->force_gpt_sector = 0;
+
+	return ret;
+}