summaryrefslogtreecommitdiffstats
path: root/sys/boot/i386/zfsboot/zfsboot.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/boot/i386/zfsboot/zfsboot.c')
-rw-r--r--sys/boot/i386/zfsboot/zfsboot.c69
1 files changed, 50 insertions, 19 deletions
diff --git a/sys/boot/i386/zfsboot/zfsboot.c b/sys/boot/i386/zfsboot/zfsboot.c
index 4b371dc..71d6cfa 100644
--- a/sys/boot/i386/zfsboot/zfsboot.c
+++ b/sys/boot/i386/zfsboot/zfsboot.c
@@ -46,18 +46,20 @@ __FBSDID("$FreeBSD$");
#include "libzfs.h"
-#define ARGS 0x900
-#define NOPT 14
-#define NDEV 3
+#define ARGS 0x900
+#define NOPT 14
+#define NDEV 3
-#define BIOS_NUMDRIVES 0x475
-#define DRV_HARD 0x80
-#define DRV_MASK 0x7f
+#define BIOS_NUMDRIVES 0x475
+#define DRV_HARD 0x80
+#define DRV_MASK 0x7f
-#define TYPE_AD 0
-#define TYPE_DA 1
-#define TYPE_MAXHARD TYPE_DA
-#define TYPE_FD 2
+#define TYPE_AD 0
+#define TYPE_DA 1
+#define TYPE_MAXHARD TYPE_DA
+#define TYPE_FD 2
+
+#define DEV_GELIBOOT_BSIZE 4096
extern uint32_t _end;
@@ -104,13 +106,13 @@ static struct bios_smap smap;
/*
* The minimum amount of memory to reserve in bios_extmem for the heap.
*/
-#define HEAP_MIN (3 * 1024 * 1024)
+#define HEAP_MIN (3 * 1024 * 1024)
static char *heap_next;
static char *heap_end;
/* Buffers that must not span a 64k boundary. */
-#define READ_BUF_SIZE 8192
+#define READ_BUF_SIZE 8192
struct dmadat {
char rdbuf[READ_BUF_SIZE]; /* for reading large things */
char secbuf[READ_BUF_SIZE]; /* for MBR/disklabel */
@@ -198,8 +200,9 @@ static int
vdev_read(vdev_t *vdev, void *priv, off_t off, void *buf, size_t bytes)
{
char *p;
- daddr_t lba;
- unsigned int nb;
+ daddr_t lba, alignlba;
+ off_t alignoff, diff;
+ unsigned int nb, alignnb;
struct dsk *dsk = (struct dsk *) priv;
if ((off & (DEV_BSIZE - 1)) || (bytes & (DEV_BSIZE - 1)))
@@ -208,24 +211,52 @@ vdev_read(vdev_t *vdev, void *priv, off_t off, void *buf, size_t bytes)
p = buf;
lba = off / DEV_BSIZE;
lba += dsk->start;
+ /* Align reads to 4k else 4k sector GELIs will not decrypt. */
+ alignoff = off & ~ (off_t)(DEV_GELIBOOT_BSIZE - 1);
+ /* Round LBA down to nearest multiple of DEV_GELIBOOT_BSIZE bytes. */
+ alignlba = alignoff / DEV_BSIZE;
+ /*
+ * The read must be aligned to DEV_GELIBOOT_BSIZE bytes relative to the
+ * start of the GELI partition, not the start of the actual disk.
+ */
+ alignlba += dsk->start;
+ diff = (lba - alignlba) * DEV_BSIZE;
+
while (bytes > 0) {
nb = bytes / DEV_BSIZE;
if (nb > READ_BUF_SIZE / DEV_BSIZE)
nb = READ_BUF_SIZE / DEV_BSIZE;
- if (drvread(dsk, dmadat->rdbuf, lba, nb))
+ /*
+ * Ensure that the read size plus the leading offset does not
+ * exceed the size of the read buffer.
+ */
+ if (nb * DEV_BSIZE + diff > READ_BUF_SIZE)
+ nb -= diff / DEV_BSIZE;
+ /*
+ * Round the number of blocks to read up to the nearest multiple
+ * of DEV_GELIBOOT_BSIZE.
+ */
+ alignnb = nb + (diff / DEV_BSIZE) +
+ (DEV_GELIBOOT_BSIZE / DEV_BSIZE - 1) & ~
+ (unsigned int)(DEV_GELIBOOT_BSIZE / DEV_BSIZE - 1);
+
+ if (drvread(dsk, dmadat->rdbuf, alignlba, alignnb))
return -1;
#ifdef LOADER_GELI_SUPPORT
/* decrypt */
if (is_geli(dsk) == 0) {
- if (geli_read(dsk, ((lba - dsk->start) * DEV_BSIZE),
- dmadat->rdbuf, nb * DEV_BSIZE))
- return (-1);
+ if (geli_read(dsk, ((alignlba - dsk->start) *
+ DEV_BSIZE), dmadat->rdbuf, alignnb * DEV_BSIZE))
+ return (-1);
}
#endif
- memcpy(p, dmadat->rdbuf, nb * DEV_BSIZE);
+ memcpy(p, dmadat->rdbuf + diff, nb * DEV_BSIZE);
p += nb * DEV_BSIZE;
lba += nb;
+ alignlba += alignnb;
bytes -= nb * DEV_BSIZE;
+ /* Don't need the leading offset after the first block. */
+ diff = 0;
}
return 0;
OpenPOWER on IntegriCloud