All of lore.kernel.org
 help / color / mirror / Atom feed
From: Mihai Moldovan <ionic@ionic.de>
To: The development of GNU GRUB <grub-devel@gnu.org>
Subject: [PATCH 3/7] setup: add support for native sector addressing w/ 512-bytes lengths
Date: Sun, 24 May 2020 13:43:04 +0200	[thread overview]
Message-ID: <20200524114308.1009-4-ionic@ionic.de> (raw)
In-Reply-To: <20200524114308.1009-1-ionic@ionic.de>

In order to successfully boot with non-512-bytes sector disks and
buggy/older system firmware, core.img must be written in a special way.

This means that:
  - each part of core.img that needs to be directly addressable MUST
    start on a hardware sector
  - the addressing must be native-sector-size-based
  - lengths are still based on 512-bytes blocks.

We can get such a layout by padding data out a bit, like this:
  - write the first 512-bytes block to the start of a native sector
    (this already is implicitly true through the embedding routine)
  - potentially leave a gap up until the next native sector (i.e.,
    zero-fill)
  - write the next 512-bytes block
  - write the next 126 512-bytes blocks, since data is read-in in 127
    512-bytes blocks at a time, inherited from buggy "Phoenix BIOS"
    system firmware that was not able to read more data at a time
  - potentially leave a gap until the next native sector
  - repeat writing blocks as specified above until reaching core.img's
    end
  - DON'T add useless padding at the end.

This is all optional and comes with a new switch to grub-install:
--emu-512b

It also:
  - only works when embedding core.img (sorry, no blocklists support)
  - requires MBR- or GPT-embedding (sorry, no BtrFS- or zfs-embedding
    support)
  - will only work for x86 BIOS targets (because the SPARC code is so
    different in certain aspects that I cannot meaningfully generalize
    it).

Additionally, this commit fixes a small SPARC issue as a drive-by: SPARC
does not support Reed-Solomon Codes and the code for generating them was
properly #ifdef'd out, but the maximum needed sectors was still
uselessly doubled to accomodate RS. From now on, only x86 BIOS targets
will reserve space for RS codes (unless disabled, of course).
---
 grub-core/disk/ldm.c        |  10 +-
 grub-core/fs/btrfs.c        |   9 +-
 grub-core/fs/zfs/zfs.c      |   9 +-
 grub-core/partmap/gpt.c     |  44 ++++-
 grub-core/partmap/msdos.c   |  30 +++-
 include/grub/emu/hostdisk.h |   3 +-
 include/grub/fs.h           |   3 +-
 include/grub/partition.h    |   3 +-
 include/grub/util/install.h |   4 +-
 util/grub-install.c         |  18 +-
 util/grub-setup.c           |  10 +-
 util/setup.c                | 349 +++++++++++++++++++++++++++++++++---
 12 files changed, 449 insertions(+), 43 deletions(-)

diff --git a/grub-core/disk/ldm.c b/grub-core/disk/ldm.c
index 2a22d2d6c..47fda7a39 100644
--- a/grub-core/disk/ldm.c
+++ b/grub-core/disk/ldm.c
@@ -954,7 +954,8 @@ grub_err_t
 grub_util_ldm_embed (struct grub_disk *disk, unsigned int *nsectors,
 		     unsigned int max_nsectors,
 		     grub_embed_type_t embed_type,
-		     grub_disk_addr_t **sectors)
+		     grub_disk_addr_t **sectors,
+		     int emu_512b)
 {
   struct grub_diskfilter_pv *pv = NULL;
   struct grub_diskfilter_vg *vg;
@@ -964,6 +965,13 @@ grub_util_ldm_embed (struct grub_disk *disk, unsigned int *nsectors,
   if (embed_type != GRUB_EMBED_PCBIOS)
     return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
 		       "LDM currently supports only PC-BIOS embedding");
+
+  if (emu_512b)
+    {
+      return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+			 "512 bytes sector length emulation is not implemented for LDM-embedding");
+    }
+
   if (disk->partition)
     return grub_error (GRUB_ERR_BUG, "disk isn't LDM");
   pv = grub_diskfilter_get_pv_from_disk (disk, &vg);
diff --git a/grub-core/fs/btrfs.c b/grub-core/fs/btrfs.c
index 63f9657a6..3949ea1a0 100644
--- a/grub-core/fs/btrfs.c
+++ b/grub-core/fs/btrfs.c
@@ -2151,7 +2151,8 @@ grub_btrfs_embed (grub_device_t device __attribute__ ((unused)),
 		  unsigned int *nsectors,
 		  unsigned int max_nsectors,
 		  grub_embed_type_t embed_type,
-		  grub_disk_addr_t **sectors)
+		  grub_disk_addr_t **sectors,
+		  int emu_512b)
 {
   unsigned i;
 
@@ -2159,6 +2160,12 @@ grub_btrfs_embed (grub_device_t device __attribute__ ((unused)),
     return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
 		       "BtrFS currently supports only PC-BIOS embedding");
 
+  if (emu_512b)
+    {
+      return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+			 "512 bytes sector length emulation is not implemented for BtrFS-embedding");
+    }
+
   if (64 * 2 - 1 < *nsectors)
     return grub_error (GRUB_ERR_OUT_OF_RANGE,
 		       N_("your core.img is unusually large.  "
diff --git a/grub-core/fs/zfs/zfs.c b/grub-core/fs/zfs/zfs.c
index b5e10fd0b..3b4c65f56 100644
--- a/grub-core/fs/zfs/zfs.c
+++ b/grub-core/fs/zfs/zfs.c
@@ -4323,7 +4323,8 @@ grub_zfs_embed (grub_device_t device __attribute__ ((unused)),
 		unsigned int *nsectors,
 		unsigned int max_nsectors,
 		grub_embed_type_t embed_type,
-		grub_disk_addr_t **sectors)
+		grub_disk_addr_t **sectors,
+		int emu_512b)
 {
   unsigned i;
 
@@ -4331,6 +4332,12 @@ grub_zfs_embed (grub_device_t device __attribute__ ((unused)),
     return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
 		       "ZFS currently supports only PC-BIOS embedding");
 
+  if (emu_512b)
+    {
+      return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+			 "512 bytes sector length emulation is not implemented for zfs-embedding");
+    }
+
   if ((VDEV_BOOT_SIZE >> GRUB_DISK_SECTOR_BITS) < *nsectors)
     return grub_error (GRUB_ERR_OUT_OF_RANGE,
 		       N_("your core.img is unusually large.  "
diff --git a/grub-core/partmap/gpt.c b/grub-core/partmap/gpt.c
index 103f6796f..4b968529a 100644
--- a/grub-core/partmap/gpt.c
+++ b/grub-core/partmap/gpt.c
@@ -169,8 +169,13 @@ static grub_err_t
 gpt_partition_map_embed (struct grub_disk *disk, unsigned int *nsectors,
 			 unsigned int max_nsectors,
 			 grub_embed_type_t embed_type,
-			 grub_disk_addr_t **sectors)
+			 grub_disk_addr_t **sectors,
+			 int emu_512b)
 {
+  struct gpt_partition_map_embed_ctx orig_ctx = {
+    .start = 0,
+    .len = 0
+  };
   struct gpt_partition_map_embed_ctx ctx = {
     .start = 0,
     .len = 0
@@ -186,6 +191,43 @@ gpt_partition_map_embed (struct grub_disk *disk, unsigned int *nsectors,
   if (err)
     return err;
 
+  orig_ctx = ctx;
+  if (emu_512b)
+    {
+      unsigned int emu_sec_factor_bits = disk->log_sector_size
+					 - GRUB_DISK_SECTOR_BITS;
+      /*
+       * Make sure the start is HW-sector-aligned and modify the partition size
+       * accordingly.
+       */
+
+      /*
+       * Align and truncate to physical sector (always less or equal to current
+       * value.
+       */
+      ctx.start = ctx.start >> emu_sec_factor_bits;
+      ctx.start = ctx.start << emu_sec_factor_bits;
+
+      if (ctx.start != orig_ctx.start)
+	{
+	  /*
+	   * ctx.start is unaligned, so add a physical sector factor and align
+	   * and truncate again.
+	   */
+	  ctx.start = orig_ctx.start + ((1U) << emu_sec_factor_bits);
+
+	  ctx.start = ctx.start >> emu_sec_factor_bits;
+	  ctx.start = ctx.start << emu_sec_factor_bits;
+
+	  ctx.len -= (ctx.start - orig_ctx.start);
+	}
+    }
+
+  /*
+   * N.B.: ctx can either be an aligned version or just its original value from
+   * this point on, depending on whether emu_512b was requested or not.
+   */
+
   if (ctx.len == 0)
     return grub_error (GRUB_ERR_FILE_NOT_FOUND,
 		       N_("this GPT partition label contains no BIOS Boot Partition;"
diff --git a/grub-core/partmap/msdos.c b/grub-core/partmap/msdos.c
index 7b8e45076..314bd8afd 100644
--- a/grub-core/partmap/msdos.c
+++ b/grub-core/partmap/msdos.c
@@ -236,7 +236,8 @@ static grub_err_t
 pc_partition_map_embed (struct grub_disk *disk, unsigned int *nsectors,
 			unsigned int max_nsectors,
 			grub_embed_type_t embed_type,
-			grub_disk_addr_t **sectors)
+			grub_disk_addr_t **sectors,
+			int emu_512b)
 {
   grub_disk_addr_t end = ~0ULL;
   struct grub_msdos_partition_mbr mbr;
@@ -246,6 +247,13 @@ pc_partition_map_embed (struct grub_disk *disk, unsigned int *nsectors,
   grub_disk_addr_t lastaddr = 1;
   grub_disk_addr_t ext_offset = 0;
   grub_disk_addr_t offset = 0;
+  unsigned int emu_sec_factor = (1U) << (disk->log_sector_size
+					 - GRUB_DISK_SECTOR_BITS);
+
+  if (!emu_512b)
+    {
+      emu_sec_factor = 1;
+    }
 
   if (embed_type != GRUB_EMBED_PCBIOS)
     return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
@@ -326,22 +334,23 @@ pc_partition_map_embed (struct grub_disk *disk, unsigned int *nsectors,
 	break;
     }
 
-  if (end >= *nsectors + 1)
+  if (end >= *nsectors + emu_sec_factor)
     {
       unsigned i, j;
       char *embed_signature_check;
       unsigned int orig_nsectors, avail_nsectors;
 
       orig_nsectors = *nsectors;
-      *nsectors = end - 1;
+      *nsectors = end - emu_sec_factor;
       avail_nsectors = *nsectors;
+
       if (*nsectors > max_nsectors)
 	*nsectors = max_nsectors;
       *sectors = grub_malloc (*nsectors * sizeof (**sectors));
       if (!*sectors)
 	return grub_errno;
       for (i = 0; i < *nsectors; i++)
-	(*sectors)[i] = 1 + i;
+	(*sectors)[i] = emu_sec_factor + i;
 
       /* Check for software that is already using parts of the embedding
        * area.
@@ -362,6 +371,15 @@ pc_partition_map_embed (struct grub_disk *disk, unsigned int *nsectors,
 	    continue;
 	  grub_util_warn (_(message_warn[embed_signatures[j].type]),
 			  (*sectors)[i], embed_signatures[j].name);
+
+	  if (emu_512b)
+	    {
+	      return grub_error (GRUB_ERR_OUT_OF_RANGE,
+				 N_("leaving gaps for other software in the "
+				    "embedding area is not supported in 512 "
+				    "bytes emulation mode"));
+	    }
+
 	  avail_nsectors--;
 	  if (avail_nsectors < *nsectors)
 	    *nsectors = avail_nsectors;
@@ -390,12 +408,12 @@ pc_partition_map_embed (struct grub_disk *disk, unsigned int *nsectors,
       return GRUB_ERR_NONE;
     }
 
-  if (end <= 1)
+  if (end <= emu_sec_factor)
     return grub_error (GRUB_ERR_FILE_NOT_FOUND,
 		       N_("this msdos-style partition label has no "
 			  "post-MBR gap; embedding won't be possible"));
 
-  if (*nsectors > 62)
+  if (*nsectors > (63 - emu_sec_factor))
     return grub_error (GRUB_ERR_OUT_OF_RANGE,
 		       N_("your core.img is unusually large.  "
 			  "It won't fit in the embedding area"));
diff --git a/include/grub/emu/hostdisk.h b/include/grub/emu/hostdisk.h
index e006f0b38..e70d474e8 100644
--- a/include/grub/emu/hostdisk.h
+++ b/include/grub/emu/hostdisk.h
@@ -51,7 +51,8 @@ grub_err_t
 grub_util_ldm_embed (struct grub_disk *disk, unsigned int *nsectors,
 		     unsigned int max_nsectors,
 		     grub_embed_type_t embed_type,
-		     grub_disk_addr_t **sectors);
+		     grub_disk_addr_t **sectors,
+		     int emu_512b);
 #endif
 const char *
 grub_hostdisk_os_dev_to_grub_drive (const char *os_dev, int add);
diff --git a/include/grub/fs.h b/include/grub/fs.h
index 302e48d4b..a3f72b6fa 100644
--- a/include/grub/fs.h
+++ b/include/grub/fs.h
@@ -88,7 +88,8 @@ struct grub_fs
   grub_err_t (*fs_embed) (grub_device_t device, unsigned int *nsectors,
 		       unsigned int max_nsectors,
 		       grub_embed_type_t embed_type,
-		       grub_disk_addr_t **sectors);
+		       grub_disk_addr_t **sectors,
+		       int emu_512b);
 
   /* Whether this filesystem reserves first sector for DOS-style boot.  */
   int reserved_first_sector;
diff --git a/include/grub/partition.h b/include/grub/partition.h
index 7adb7ec6e..2a6f5e6d1 100644
--- a/include/grub/partition.h
+++ b/include/grub/partition.h
@@ -55,7 +55,8 @@ struct grub_partition_map
   grub_err_t (*embed) (struct grub_disk *disk, unsigned int *nsectors,
 		       unsigned int max_nsectors,
 		       grub_embed_type_t embed_type,
-		       grub_disk_addr_t **sectors);
+		       grub_disk_addr_t **sectors,
+		       int emu_512b);
 #endif
 };
 typedef struct grub_partition_map *grub_partition_map_t;
diff --git a/include/grub/util/install.h b/include/grub/util/install.h
index 2631b1074..daaef1725 100644
--- a/include/grub/util/install.h
+++ b/include/grub/util/install.h
@@ -193,13 +193,13 @@ grub_util_bios_setup (const char *dir,
 		      const char *boot_file, const char *core_file,
 		      const char *dest, int force,
 		      int fs_probe, int allow_floppy,
-		      int add_rs_codes);
+		      int add_rs_codes, int emu_512b);
 void
 grub_util_sparc_setup (const char *dir,
 		       const char *boot_file, const char *core_file,
 		       const char *dest, int force,
 		       int fs_probe, int allow_floppy,
-		       int add_rs_codes);
+		       int add_rs_codes, int emu_512b);
 
 char *
 grub_install_get_image_targets_string (void);
diff --git a/util/grub-install.c b/util/grub-install.c
index 8970b73aa..57ef3ef33 100644
--- a/util/grub-install.c
+++ b/util/grub-install.c
@@ -79,6 +79,7 @@ static char *label_color;
 static char *label_bgcolor;
 static char *product_version;
 static int add_rs_codes = 1;
+static int emu_512b = 0;
 
 enum
   {
@@ -105,6 +106,7 @@ enum
     OPTION_DISK_MODULE,
     OPTION_NO_BOOTSECTOR,
     OPTION_NO_RS_CODES,
+    OPTION_EMU_512B,
     OPTION_MACPPC_DIRECTORY,
     OPTION_LABEL_FONT,
     OPTION_LABEL_COLOR,
@@ -224,6 +226,10 @@ argp_parser (int key, char *arg, struct argp_state *state)
       add_rs_codes = 0;
       return 0;
 
+    case OPTION_EMU_512B:
+      emu_512b = 1;
+      return 0;
+
     case OPTION_DEBUG:
       verbosity++;
       return 0;
@@ -285,6 +291,10 @@ static struct argp_option options[] = {
   {"no-rs-codes", OPTION_NO_RS_CODES, 0, 0,
    N_("Do not apply any reed-solomon codes when embedding core.img. "
       "This option is only available on x86 BIOS targets."), 0},
+  {"emu-512b", OPTION_EMU_512B, 0, 0,
+   N_("Use native-sector-size addressing, but emulate 512-bytes sector lengths when embedding the core image. "
+      "Workaround for broken firmware. "
+      "This option is only available on x86 BIOS targets."), 0},
 
   {"debug", OPTION_DEBUG, 0, OPTION_HIDDEN, 0, 2},
   {"no-floppy", OPTION_NO_FLOPPY, 0, OPTION_HIDDEN, 0, 2},
@@ -1704,7 +1714,7 @@ main (int argc, char *argv[])
 					      "boot.img");
 	grub_install_copy_file (boot_img_src, boot_img, 1);
 
-	grub_util_info ("%sgrub-bios-setup %s %s %s %s %s --directory='%s' --device-map='%s' '%s'",
+	grub_util_info ("%sgrub-bios-setup %s %s %s %s %s %s --directory='%s' --device-map='%s' '%s'",
 			/* TRANSLATORS: This is a prefix in the log to indicate that usually
 			   a command would be executed but due to an option was skipped.  */
 			install_bootsector ? "" : _("NOT RUNNING: "),
@@ -1713,6 +1723,7 @@ main (int argc, char *argv[])
 			force ? "--force " : "",
 			!fs_probe ? "--skip-fs-probe" : "",
 			!add_rs_codes ? "--no-rs-codes" : "",
+			emu_512b ? "--emu-512b" : "",
 			platdir,
 			device_map,
 			install_device);
@@ -1721,7 +1732,8 @@ main (int argc, char *argv[])
 	if (install_bootsector)
 	  grub_util_bios_setup (platdir, "boot.img", "core.img",
 				install_drive, force,
-				fs_probe, allow_floppy, add_rs_codes);
+				fs_probe, allow_floppy, add_rs_codes,
+				emu_512b);
 	break;
       }
     case GRUB_INSTALL_PLATFORM_SPARC64_IEEE1275:
@@ -1748,7 +1760,7 @@ main (int argc, char *argv[])
 	  grub_util_sparc_setup (platdir, "boot.img", "core.img",
 				 install_drive, force,
 				 fs_probe, allow_floppy,
-				 0 /* unused */ );
+				 0 /* unused */, 8 /* unused */);
 	break;
       }
 
diff --git a/util/grub-setup.c b/util/grub-setup.c
index 42b98ad3c..8297436df 100644
--- a/util/grub-setup.c
+++ b/util/grub-setup.c
@@ -95,6 +95,9 @@ static struct argp_option options[] = {
   {"no-rs-codes", NO_RS_CODES_KEY, 0,      0,
    N_("Do not apply any reed-solomon codes when embedding core.img. "
       "This option is only available on x86 BIOS targets."), 0},
+  {"emu-512b", 'e', 0,      0,
+   N_("Use native-sector-size addressing, but emulate 512 bytes sector lengths when embedding the core image. "
+      "Workaround for broken firmware."), 0},
   { 0, 0, 0, 0, 0, 0 }
 };
 
@@ -135,6 +138,7 @@ struct arguments
   int allow_floppy;
   char *device;
   int add_rs_codes;
+  int emu_512b;
 };
 
 static error_t
@@ -194,6 +198,10 @@ argp_parser (int key, char *arg, struct argp_state *state)
         arguments->add_rs_codes = 0;
         break;
 
+      case 'e':
+        arguments->emu_512b = 1;
+        break;
+
       case ARGP_KEY_ARG:
         if (state->arg_num == 0)
           arguments->device = xstrdup(arg);
@@ -315,7 +323,7 @@ main (int argc, char *argv[])
 		   arguments.core_file ? : DEFAULT_CORE_FILE,
 		   dest_dev, arguments.force,
 		   arguments.fs_probe, arguments.allow_floppy,
-		   arguments.add_rs_codes);
+		   arguments.add_rs_codes, arguments.emu_512b);
 
   /* Free resources.  */
   grub_fini_all ();
diff --git a/util/setup.c b/util/setup.c
index 3be88aae1..0574a826a 100644
--- a/util/setup.c
+++ b/util/setup.c
@@ -254,7 +254,8 @@ SETUP (const char *dir,
        const char *boot_file, const char *core_file,
        const char *dest, int force,
        int fs_probe, int allow_floppy,
-       int add_rs_codes __attribute__ ((unused))) /* unused on sparc64 */
+       int add_rs_codes __attribute__ ((unused)), /* unused on sparc64 */
+       int emu_512b)
 {
   char *core_path;
   char *boot_img, *core_img, *boot_path;
@@ -267,6 +268,14 @@ SETUP (const char *dir,
 
   bl.first_sector = (grub_disk_addr_t) -1;
 
+#ifdef GRUB_SETUP_SPARC64
+  if (emu_512b)
+    {
+	grub_util_error ("%s", _("512 bytes sector length emulation mode"
+				 "is currently not available on SPARC platforms"));
+    }
+#endif
+
 #ifdef GRUB_SETUP_BIOS
   bl.current_segment =
     GRUB_BOOT_I386_PC_KERNEL_SEG + (GRUB_DISK_SECTOR_SIZE >> 4);
@@ -408,6 +417,25 @@ SETUP (const char *dir,
     int i;
     grub_fs_t fs;
     unsigned int nsec, maxsec;
+    unsigned int emu_sec_factor = (1U) << (dest_dev->disk->log_sector_size
+					   - GRUB_DISK_SECTOR_BITS);
+    grub_uint64_t fill = 0;
+
+    /*
+     * If the block doesn't end on physical sector size, calculate emulated
+     * sector count to fill up to physical sector size.
+     */
+    fill = emu_sec_factor - (0x7f % emu_sec_factor);
+
+    if (emu_512b)
+      {
+	/* Sanity checks. */
+	if ((int)(emu_sec_factor) < 1)
+	  {
+	    grub_util_error ("%s", _("invalid hardware sector size; "
+				     "embedding won't be possible"));
+	  }
+      }
 
     grub_partition_iterate (dest_dev->disk, identify_partmap, &ctx);
 
@@ -501,19 +529,7 @@ SETUP (const char *dir,
 	goto unable_to_embed;
       }
 
-    nsec = core_sectors;
-
-    if (add_rs_codes)
-      maxsec = 2 * core_sectors;
-    else
-      maxsec = core_sectors;
-
-#ifdef GRUB_SETUP_BIOS
-    if (maxsec > ((0x78000 - GRUB_KERNEL_I386_PC_LINK_ADDR)
-		>> GRUB_DISK_SECTOR_BITS))
-      maxsec = ((0x78000 - GRUB_KERNEL_I386_PC_LINK_ADDR)
-		>> GRUB_DISK_SECTOR_BITS);
-#endif
+    nsec = maxsec = core_sectors;
 
 #ifdef GRUB_SETUP_SPARC64
     /*
@@ -525,15 +541,66 @@ SETUP (const char *dir,
     maxsec += 2;
 #endif
 
+    if (emu_512b)
+      {
+	grub_int64_t read_blocks_quot = (nsec - 1) / 0x7f;
+	grub_int64_t read_blocks_rem = (nsec - 1) % 0x7f;
+
+	/*
+	 * We want to fill/pad each complete 127-sectors-block, but not the
+	 * last one.
+	 *
+	 * To do so, we calculate the number of blocks (complete or incomplete)
+	 * and subtract one.
+	 */
+	grub_int64_t emu_blocks_fill = read_blocks_quot + (!!read_blocks_rem)
+				       - 1;
+
+	if (emu_blocks_fill >= 0)
+	  {
+	    nsec = nsec
+		   /*
+		    * Padding to fill first HW sector - keep
+		    * boot.img/diskboot.img isolated!
+		    *
+		    * N.B.: calculation is safe as long as the grub-internal
+		    * sector size is smaller than or equal to the hardware
+		    * sector size.
+		    * We checked for this previously.
+		    */
+		   + (emu_sec_factor - 1)
+		   /*
+		    * Pad each completed(!) 127-sectors-block to a full
+		    * hardware sector, so that each block starts on a proper
+		    * hardware sector.
+		    */
+		   + ((unsigned int)(emu_blocks_fill) * fill);
+	    maxsec = nsec;
+	  }
+      }
+
+#ifdef GRUB_SETUP_BIOS
+    /*
+     * SPARC currently does not support RS codes.
+     */
+    if (add_rs_codes)
+      maxsec *= 2;
+
+    if (maxsec > ((0x78000 - GRUB_KERNEL_I386_PC_LINK_ADDR)
+		>> GRUB_DISK_SECTOR_BITS))
+      maxsec = ((0x78000 - GRUB_KERNEL_I386_PC_LINK_ADDR)
+		>> GRUB_DISK_SECTOR_BITS);
+#endif
+
     if (is_ldm)
       err = grub_util_ldm_embed (dest_dev->disk, &nsec, maxsec,
-				 GRUB_EMBED_PCBIOS, &sectors);
+				 GRUB_EMBED_PCBIOS, &sectors, emu_512b);
     else if (ctx.dest_partmap)
       err = ctx.dest_partmap->embed (dest_dev->disk, &nsec, maxsec,
-				     GRUB_EMBED_PCBIOS, &sectors);
+				     GRUB_EMBED_PCBIOS, &sectors, emu_512b);
     else
       err = fs->fs_embed (dest_dev, &nsec, maxsec,
-			  GRUB_EMBED_PCBIOS, &sectors);
+			  GRUB_EMBED_PCBIOS, &sectors, emu_512b);
     if (!err && nsec < core_sectors)
       {
 	err = grub_error (GRUB_ERR_OUT_OF_RANGE,
@@ -563,9 +630,150 @@ SETUP (const char *dir,
       }
 
     bl.block = bl.first_block;
-    for (i = 0; i < nsec; i++)
-      save_blocklists (sectors[i] + grub_partition_get_start (ctx.container),
-		       0, GRUB_DISK_SECTOR_SIZE, &bl);
+      {
+	grub_disk_addr_t last_hw_sector = 0;
+	unsigned int block_pos = 0;
+	int first_sec = 0;
+	int block_end = 0;
+
+	for (i = 0; i < nsec; i++)
+	  {
+	    grub_disk_addr_t sector = sectors[i]
+				      + grub_partition_get_start (ctx.container);
+
+	    if (emu_512b)
+	      {
+		/*
+		 * When booting off a disk, grub reads in core.img in chunks of
+		 * 127 blocks, which tradionally are 512 bytes long. The block
+		 * length is inherited from old, buggy "Phoenix BIOS" system
+		 * firmare that didn't support reading more data at a time.
+		 *
+		 * The nifty (or scary, depending on your point of view)
+		 * consequence of that is that only each 127th sector is
+		 * directly addressed, while the others sectors are read-in
+		 * via a length parameter based on 512-bytes blocks.
+		 *
+		 * For buggy firmware that is not aware of anything but
+		 * 512-bytes-sector-hardware, but always assumes (and returns)
+		 * 512-bytes blocks, sector *addressing* is based on the native
+		 * hardware sector size (because this parameter is just passed
+		 * to the drive directly), while the length is 512-bytes based.
+		 *
+		 * In 512-bytes-emulation mode, we hence have to modify the
+		 * (internal) blocklist to use native sector adressing, like
+		 * this:
+		 *   - put the first 512-bytes block at the start of a physical
+		 *     sector (implicitly true through the embedding routine),
+		 *     since is the code reading in additional data
+		 *   - put the second 512-bytes block at the start of the next
+		 *     physical sector (i.e., leaving a gap if necessary)
+		 *   - put the next 126 512-bytes blocks right after the
+		 *     previous (i.e., no gap)
+		 *   - put the next 512-bytes block at the start of the next
+		 *     possible physical sector (i.e., leaving a gap)
+		 *   - put the next 126 ...
+		 *
+		 * This eventually leads to:
+		 *   - correct native-sector-addressing sector addresses at the
+		 *     start of each block that NEEDS to be directly addressed
+		 *   - fake, incrementing sector addresses for the next 126
+		 *     512-bytes blocks (which shouldn't hurt since they are
+		 *     not used in any meaningful way)
+		 *   - implicitly generating a new blocklist after 127
+		 *     512-bytes blocks because the next native sector address
+		 *     will be smaller than the previously written fake sector
+		 *     address.
+		 *
+		 * Even more eventually, this scheme should lead to grub
+		 * correctly reading in all data of its core.img file and
+		 * successful booting.
+		 */
+		if (0 == i)
+		  {
+		    first_sec = 1;
+		    last_hw_sector = sector / emu_sec_factor;
+		  }
+		else
+		 {
+		    /* Sanity check. */
+		    grub_disk_addr_t prev_sector = sectors[i - 1]
+						   + grub_partition_get_start (ctx.container);
+
+		    if (1 != (sector - prev_sector))
+		      {
+			grub_util_error ("%s", _("embedding non-contiguous "
+						 "ranges is not supported in "
+						 "512-bytes sector emulation "
+						 "mode"));
+		      }
+
+		    /* Must be somewhere within a block. */
+		    ++block_pos;
+
+		    if (block_end)
+		      {
+			/*
+			 * Previously been at a block end, recalculate HW sector.
+			 * This will automatically create a new blocklist if necessary.
+			 */
+			last_hw_sector = sector / emu_sec_factor;
+
+			/* Reset block end marker. */
+			block_end = 0;
+		      }
+		    else
+		      {
+			++last_hw_sector;
+		      }
+
+		    if (0x7f == block_pos)
+		      {
+			/* Last emulated sector in this block. */
+			block_end = 1;
+		      }
+		  }
+	      }
+	    else
+	      {
+		last_hw_sector = sector;
+	      }
+
+	    save_blocklists (last_hw_sector, 0, GRUB_DISK_SECTOR_SIZE, &bl);
+
+	    if (emu_512b)
+	      {
+		/*
+		 * N.B.: advancing i here past nsec is safe as long as we don't
+		 *       use it - in the worst case, the loop will terminate
+		 *       right away.
+		 */
+		if (first_sec)
+		  {
+		    /*
+		     * Skip over the next logical sectors to reach the next HW
+		     * sector.
+		     *
+		     * N.B.: decrease by one due to the implicit increment at
+		     * the end of the loop!
+		     */
+		    i += (emu_sec_factor - 1);
+
+		    /* Reset first sector counter to not jump again next time. */
+		    first_sec = 0;
+		  }
+
+		if (block_end)
+		  {
+		    /* Likewise, if necessary, but with a different offset. */
+		    i += fill;
+
+		    /* Reset position counter within a block. */
+		    block_pos = 0;
+		  }
+	      }
+	  }
+      }
 
     /* Make sure that the last blocklist is a terminator.  */
     if (bl.block == bl.first_block)
@@ -641,10 +849,97 @@ SETUP (const char *dir,
       }
 
     /* Write the core image onto the disk.  */
-    for (i = 0; i < nsec; i++)
-      grub_disk_write (dest_dev->disk, sectors[i], 0,
-		       GRUB_DISK_SECTOR_SIZE,
-		       core_img + i * GRUB_DISK_SECTOR_SIZE);
+    char *null_sector = xmalloc (GRUB_DISK_SECTOR_SIZE);
+    memset (null_sector, 0, GRUB_DISK_SECTOR_SIZE);
+
+      {
+	int fill_null = 0;
+	unsigned int block_pos = 0;
+	int first_hw_sec = 0;
+	int block_end = 0;
+	char *core_img_location = core_img;
+
+	for (i = 0; i < nsec; i++)
+	  {
+	    grub_disk_addr_t sector = sectors[i];
+	    char *read_location = core_img_location;
+
+	    /*
+	     * For a description of what we're doing here in 512-bytes-emulation
+	     * mode, refer to the blocklist writing section.
+	     *
+	     * The magic in this part is that we have to add some padding to
+	     * regions that will NOT contain data from core.img.
+	     * For simplicity's sake, we'll pad those regions with zero-filled
+	     * 512-bytes blocks.
+	     */
+	    if (emu_512b)
+	      {
+		/* Set flag when processing first HW sector. */
+		if (0 == i)
+		  {
+		    first_hw_sec = 1;
+		  }
+		else
+		  {
+		    /* Reset, completely handled first HW sector. */
+		    if (emu_sec_factor == i)
+		      {
+			first_hw_sec = 0;
+			fill_null = 0;
+		      }
+
+		    /* Reset, handled full block, up to HW sector end. */
+		    if ((0x7f + fill) == block_pos)
+		      {
+			block_end = 0;
+			fill_null = 0;
+			block_pos = 0;
+		      }
+
+		    ++block_pos;
+
+		    if (0x7f == block_pos)
+		      {
+			/* Last emulated sector in this block. */
+			block_end = 1;
+		      }
+		  }
+	      }
+
+	    if (fill_null)
+	      {
+		read_location = null_sector;
+	      }
+
+	    grub_util_info ("writing to (virtual) disk sector %" PRIuGRUB_UINT64_T, sectors[i]);
+	    grub_disk_write (dest_dev->disk,
+			     sector, 0,
+			     GRUB_DISK_SECTOR_SIZE,
+			     read_location);
+
+	    /* Advance core_img_location if we actually used data from it. */
+	    if (!fill_null)
+	      {
+		core_img_location += GRUB_DISK_SECTOR_SIZE;
+	      }
+
+	    if (emu_512b)
+	      {
+		/*
+		 * Note that fill_null will be reset once reaching the HW
+		 * sector end in the first part of the iteration loop.
+		 */
+		if ((first_hw_sec) || (block_end))
+		  {
+		    /* Zero-fill rest of HW sector. */
+		    fill_null = 1;
+		  }
+	      }
+	  }
+      }
+
+    grub_free (null_sector);
 #endif
 
 #ifdef GRUB_SETUP_SPARC64
@@ -678,6 +973,12 @@ unable_to_embed:
     grub_util_error ("%s", _("embedding is not possible, but this is required for "
 			     "RAID and LVM install"));
 
+  if (emu_512b)
+    {
+      grub_util_error ("%s", _("512-bytess-emulation only available when "
+			       "embedding"));
+    }
+
   {
     grub_fs_t fs;
     fs = grub_fs_probe (root_dev);
-- 
2.25.1



  parent reply	other threads:[~2020-05-24 11:43 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-05-24 11:43 [PATCH 0/7] support >512b sector disks with old/buggy firmware Mihai Moldovan
2020-05-24 11:43 ` [PATCH 1/7] biosdisk: autodetect hardware sector size (opt-in) Mihai Moldovan
2020-05-24 11:43 ` [PATCH 2/7] biosdisk: restore LBA mode after read/write failures Mihai Moldovan
2020-05-24 11:43 ` Mihai Moldovan [this message]
2020-05-24 11:43 ` [PATCH 4/7] grub-install: hook up --emu-512b to sector size autodetection in biosdisk Mihai Moldovan
2020-05-24 11:43 ` [PATCH 5/7] docs/grub: document --emu-512b install option Mihai Moldovan
2020-05-24 11:43 ` [PATCH 6/7] diskfilter: write out currently scanned partition Mihai Moldovan
2020-05-24 11:43 ` [PATCH 7/7] gpt: respect native sector size if set/detected Mihai Moldovan

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20200524114308.1009-4-ionic@ionic.de \
    --to=ionic@ionic.de \
    --cc=grub-devel@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.