diff mbox series

[v4,3/3] partitions/efi: Support NVIDIA Tegra devices

Message ID 20210817013643.13061-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 Aug. 17, 2021, 1:36 a.m. UTC
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@gmail.com>
---
 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

Comments

Christoph Hellwig Aug. 17, 2021, 4:43 a.m. UTC | #1
Just curious, why not add a new block_device_operations member
'alternative_gpt_sector', then move all the code calculating it to
the mmc layer?
Ulf Hansson Aug. 17, 2021, 8:24 a.m. UTC | #2
On Tue, 17 Aug 2021 at 03:37, Dmitry Osipenko <digetx@gmail.com> wrote:
>
> 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@gmail.com>
> ---
>  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)) {

I think you need to convince Rob Herring that the location of the GPT
table in the eMMC flash memory is allowed to depend on the compatible
string of the sdhci controller.

In any case, I think Christoph raised some interesting ideas in his
reply. Moving more of this code into the mmc core/block layer seems
reasonable to me as well.

> +               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
>

Kind regards
Uffe
Thierry Reding Aug. 17, 2021, 2:11 p.m. UTC | #3
On Tue, Aug 17, 2021 at 04:36:43AM +0300, Dmitry Osipenko wrote:
[...]
> 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", },

I know of a couple of OEM devices using the above SoCs that support this
alternate GPT sector mechanism...

> +	{ .compatible = "nvidia,tegra124-sdhci", },

... but I'm unaware of any using this. The only one that I could imagine
employing this quirk is the SHIELD Tablet K1 (a.k.a. ST8), but I thought
it had been changed on that device already. Do you know specifics?

Thierry
Dmitry Osipenko Aug. 17, 2021, 4:10 p.m. UTC | #4
17.08.2021 17:11, Thierry Reding пишет:
> On Tue, Aug 17, 2021 at 04:36:43AM +0300, Dmitry Osipenko wrote:
> [...]
>> 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", },
> 
> I know of a couple of OEM devices using the above SoCs that support this
> alternate GPT sector mechanism...
> 
>> +	{ .compatible = "nvidia,tegra124-sdhci", },
> 
> ... but I'm unaware of any using this. The only one that I could imagine
> employing this quirk is the SHIELD Tablet K1 (a.k.a. ST8), but I thought
> it had been changed on that device already. Do you know specifics?

Nexus 9 tablet (T132) needs that.
Dmitry Osipenko Aug. 17, 2021, 4:11 p.m. UTC | #5
17.08.2021 07:43, Christoph Hellwig пишет:
> Just curious, why not add a new block_device_operations member
> 'alternative_gpt_sector', then move all the code calculating it to
> the mmc layer?
> 

That will work too.
Dmitry Osipenko Aug. 17, 2021, 4:16 p.m. UTC | #6
17.08.2021 11:24, Ulf Hansson пишет:
>> +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)) {
> I think you need to convince Rob Herring that the location of the GPT
> table in the eMMC flash memory is allowed to depend on the compatible
> string of the sdhci controller.

I'll CC Rob on the next revision, although I think he doesn't care much
about this.

> In any case, I think Christoph raised some interesting ideas in his
> reply. Moving more of this code into the mmc core/block layer seems
> reasonable to me as well.

Will prepare v5, thanks.
diff mbox series

Patch

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;
+}