From 6147bab8ead4576c4a34bd766bc7ecbde16be77b Mon Sep 17 00:00:00 2001 From: Toomas Soome Date: Wed, 15 Mar 2017 19:48:22 +0000 Subject: [PATCH] boot1.efi: can't boot from ZFS on 4kn HDD The boot1.efi immediate issue from PR216964 is that we are reading into too small buffer, from UEFI spec 2.6: The size of the Buffer in bytes. This must be a multiple of the intrinsic block size of the device. The secondary issue is that LBA calculation does not check reminder from division. This fix does check the provided buffer size and if we read less than media sector size or the read offset is not aligned to sector boundary, we allocate bounce buffer and perform the read by single sector. PR: 216964 Reported by: Sergey Kozlov Reviewed by: allanjude, Sergey Kozlov Approved by: allanjude (mentor) MFC after: 2 weeks Differential Revision: https://reviews.freebsd.org/D9870 --- sys/boot/efi/boot1/zfs_module.c | 56 +++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/sys/boot/efi/boot1/zfs_module.c b/sys/boot/efi/boot1/zfs_module.c index 1926a33010d..78d635cde76 100644 --- a/sys/boot/efi/boot1/zfs_module.c +++ b/sys/boot/efi/boot1/zfs_module.c @@ -44,23 +44,61 @@ static int vdev_read(vdev_t *vdev, void *priv, off_t off, void *buf, size_t bytes) { dev_info_t *devinfo; - off_t lba; + uint64_t lba; + size_t size, remainder, rb_size, blksz; + char *bouncebuf = NULL, *rb_buf; EFI_STATUS status; devinfo = (dev_info_t *)priv; lba = off / devinfo->dev->Media->BlockSize; + remainder = off % devinfo->dev->Media->BlockSize; - status = devinfo->dev->ReadBlocks(devinfo->dev, - devinfo->dev->Media->MediaId, lba, bytes, buf); - if (status != EFI_SUCCESS) { - DPRINTF("vdev_read: failed dev: %p, id: %u, lba: %jd, size: %zu," - " status: %lu\n", devinfo->dev, - devinfo->dev->Media->MediaId, (intmax_t)lba, bytes, - EFI_ERROR_CODE(status)); - return (-1); + rb_buf = buf; + rb_size = bytes; + + /* + * If we have remainder from off, we need to add remainder part. + * Since buffer must be multiple of the BlockSize, round it all up. + */ + size = roundup2(bytes + remainder, devinfo->dev->Media->BlockSize); + blksz = size; + if (remainder != 0 || size != bytes) { + rb_size = devinfo->dev->Media->BlockSize; + bouncebuf = malloc(rb_size); + if (bouncebuf == NULL) { + printf("vdev_read: out of memory\n"); + return (-1); + } + rb_buf = bouncebuf; + blksz = rb_size - remainder; } + while (bytes > 0) { + status = devinfo->dev->ReadBlocks(devinfo->dev, + devinfo->dev->Media->MediaId, lba, rb_size, rb_buf); + if (EFI_ERROR(status)) + goto error; + if (bytes < blksz) + blksz = bytes; + if (bouncebuf != NULL) + memcpy(buf, rb_buf + remainder, blksz); + buf = (void *)((uintptr_t)buf + blksz); + bytes -= blksz; + lba++; + remainder = 0; + blksz = rb_size; + } + + free(bouncebuf); return (0); + +error: + free(bouncebuf); + DPRINTF("vdev_read: failed dev: %p, id: %u, lba: %ju, size: %zu," + " rb_size: %zu, status: %lu\n", devinfo->dev, + devinfo->dev->Media->MediaId, (uintmax_t)lba, bytes, rb_size, + EFI_ERROR_CODE(status)); + return (-1); } static EFI_STATUS