[SeaBIOS] [RFC] Add LBA 64bit support for reads beyond 2TB.

Max Tottenham via SeaBIOS posted 1 patch 3 months, 1 week ago
Patches applied successfully (tree, apply log)
git fetch https://github.com/patchew-project/seabios tags/patchew/20240119193353.2340707-1-mtottenh@akamai.com
src/hw/blockcmd.c | 75 +++++++++++++++++++++++++++++++++++------------
src/hw/blockcmd.h | 27 +++++++++++++++++
2 files changed, 84 insertions(+), 18 deletions(-)
[SeaBIOS] [RFC] Add LBA 64bit support for reads beyond 2TB.
Posted by Max Tottenham via SeaBIOS 3 months, 1 week ago
When booting from a >2TB drive/filesystem, it's possible what the
kernel/bootloader may be updated and written out at an LBA address
beyond what is normally accessible by the READ(10) SCSI commands.
If this happens to the kernel grub will fail to boot the kernel
as it will call into the BIOS with an LBA address >2TB, and the
BIOS will return an error. Per the SCSI spec, >2TB drives should
return 0XFFFFFFFF, and a READ CAPACITY(16) command should be issues
to determine the full size of the drive, READ(16) commands can then
be used in order to read data at LBA addresses beyond 2TB (64 bit
LBA addresses).
---
 src/hw/blockcmd.c | 75 +++++++++++++++++++++++++++++++++++------------
 src/hw/blockcmd.h | 27 +++++++++++++++++
 2 files changed, 84 insertions(+), 18 deletions(-)

diff --git a/src/hw/blockcmd.c b/src/hw/blockcmd.c
index 6b6fea97..4f5dc2d7 100644
--- a/src/hw/blockcmd.c
+++ b/src/hw/blockcmd.c
@@ -66,6 +66,23 @@ cdb_test_unit_ready(struct disk_op_s *op)
     return process_op(op);
 }
 
+static int
+cdb_read_capacity16(struct disk_op_s *op, struct cdbres_read_capacity_16 *data)
+{
+    struct cdb_sai_read_capacity_16 cmd;
+    memset(&cmd, 0, sizeof(cmd));
+    cmd.command = CDB_CMD_SERVICE_ACTION_IN;
+    cmd.flags = CDB_CMD_SAI_READ_CAPACITY_16;
+    cmd.len = cpu_to_be32(sizeof(struct cdbres_read_capacity_16));
+    op->command = CMD_SCSI;
+    op->count = 1;
+    op->buf_fl = data;
+    op->cdbcmd = &cmd;
+    op->blocksize = sizeof(*data);
+    return process_op(op);
+}
+
+
 // Request capacity
 static int
 cdb_read_capacity(struct disk_op_s *op, struct cdbres_read_capacity *data)
@@ -111,13 +128,23 @@ scsi_fill_cmd(struct disk_op_s *op, void *cdbcmd, int maxcdb)
     switch (op->command) {
     case CMD_READ:
     case CMD_WRITE: ;
-        struct cdb_rwdata_10 *cmd = cdbcmd;
-        memset(cmd, 0, maxcdb);
-        cmd->command = (op->command == CMD_READ ? CDB_CMD_READ_10
-                        : CDB_CMD_WRITE_10);
-        cmd->lba = cpu_to_be32(op->lba);
-        cmd->count = cpu_to_be16(op->count);
-        return GET_FLATPTR(op->drive_fl->blksize);
+        if (op->lba < 0xFFFFFFFFULL) {
+            struct cdb_rwdata_10 *cmd = cdbcmd;
+            memset(cmd, 0, maxcdb);
+            cmd->command = (op->command == CMD_READ ? CDB_CMD_READ_10
+                            : CDB_CMD_WRITE_10);
+            cmd->lba = cpu_to_be32(op->lba);
+            cmd->count = cpu_to_be16(op->count);
+            return GET_FLATPTR(op->drive_fl->blksize);
+        } else {
+            struct cdb_rwdata_16 *cmd = cdbcmd;
+            memset(cmd, 0, maxcdb);
+            cmd->command = (op->command == CMD_READ ? CDB_CMD_READ_16
+                            : CDB_CMD_WRITE_16);
+            cmd->lba = cpu_to_be64(op->lba);
+            cmd->count = cpu_to_be32(op->count);
+            return GET_FLATPTR(op->drive_fl->blksize);
+        }
     case CMD_SCSI:
         if (MODESEGMENT)
             return -1;
@@ -331,18 +358,30 @@ scsi_drive_setup(struct drive_s *drive, const char *s, int prio)
     if (ret)
         return ret;
 
-    // READ CAPACITY returns the address of the last block.
-    // We do not bother with READ CAPACITY(16) because BIOS does not support
-    // 64-bit LBA anyway.
-    drive->blksize = be32_to_cpu(capdata.blksize);
-    if (drive->blksize != DISK_SECTOR_SIZE) {
-        dprintf(1, "%s: unsupported block size %d\n", s, drive->blksize);
-        return -1;
-    }
-    drive->sectors = (u64)be32_to_cpu(capdata.sectors) + 1;
-    dprintf(1, "%s blksize=%d sectors=%u\n"
-            , s, drive->blksize, (unsigned)drive->sectors);
+    if (be32_to_cpu(capdata.sectors) == 0xFFFFFFFFUL) {
+        dprintf(1, "%s: >2TB Detected trying READCAP(16)\n", s);
+        struct cdbres_read_capacity_16 capdata16;
+        ret = cdb_read_capacity16(&dop, &capdata16);
+
+        drive->blksize = be32_to_cpu(capdata16.blksize);
+        if (drive->blksize != DISK_SECTOR_SIZE) {
+            dprintf(1, "%s: unsupported block size %d\n", s, drive->blksize);
+            return -1;
+        }
 
+        drive->sectors = be64_to_cpu(capdata16.sectors);
+        dprintf(1, "READCAP(16) %s blksize=%d sectors=%llX\n"
+                , s, drive->blksize, drive->sectors);
+    } else {
+        drive->blksize = be32_to_cpu(capdata.blksize);
+        if (drive->blksize != DISK_SECTOR_SIZE) {
+            dprintf(1, "%s: unsupported block size %d\n", s, drive->blksize);
+            return -1;
+        }
+        drive->sectors = (u64)be32_to_cpu(capdata.sectors) + 1;
+        dprintf(1, "%s blksize=%d sectors=%u\n"
+                , s, drive->blksize, (unsigned)drive->sectors);
+    }
     // We do not recover from USB stalls, so try to be safe and avoid
     // sending the command if the (obsolete, but still provided by QEMU)
     // fixed disk geometry page may not be supported.
diff --git a/src/hw/blockcmd.h b/src/hw/blockcmd.h
index f18543ed..2683186c 100644
--- a/src/hw/blockcmd.h
+++ b/src/hw/blockcmd.h
@@ -18,6 +18,16 @@ struct cdb_rwdata_10 {
     u8 pad[6];
 } PACKED;
 
+#define CDB_CMD_READ_16 0x88
+#define CDB_CMD_WRITE_16 0x8A
+struct cdb_rwdata_16 {
+    u8 command;
+    u8 flags;
+    u64 lba;
+    u32 count;
+    u16 reserved_14;
+} PACKED;
+
 #define CDB_CMD_READ_CAPACITY 0x25
 
 struct cdb_read_capacity {
@@ -32,6 +42,23 @@ struct cdbres_read_capacity {
     u32 blksize;
 } PACKED;
 
+
+#define CDB_CMD_SERVICE_ACTION_IN 0x9E
+#define CDB_CMD_SAI_READ_CAPACITY_16 0x10
+struct cdb_sai_read_capacity_16 {
+    u8 command;
+    u8 flags;
+    u64 lba; //marked as obsolete?
+    u32 len;
+    u16 reserved_14;
+} PACKED;
+
+struct cdbres_read_capacity_16 {
+    u64 sectors;
+    u32 blksize;
+    u8 reserved_12[20];
+} PACKED;
+
 #define CDB_CMD_TEST_UNIT_READY  0x00
 #define CDB_CMD_INQUIRY          0x12
 #define CDB_CMD_REQUEST_SENSE    0x03
-- 
2.34.1

_______________________________________________
SeaBIOS mailing list -- seabios@seabios.org
To unsubscribe send an email to seabios-leave@seabios.org
[SeaBIOS] Re: [RFC] Add LBA 64bit support for reads beyond 2TB.
Posted by Gerd Hoffmann 3 months ago
  Hi,

> +    if (be32_to_cpu(capdata.sectors) == 0xFFFFFFFFUL) {
> +        dprintf(1, "%s: >2TB Detected trying READCAP(16)\n", s);
> +        struct cdbres_read_capacity_16 capdata16;
> +        ret = cdb_read_capacity16(&dop, &capdata16);
> +
> +        drive->blksize = be32_to_cpu(capdata16.blksize);
> +        if (drive->blksize != DISK_SECTOR_SIZE) {
> +            dprintf(1, "%s: unsupported block size %d\n", s, drive->blksize);
> +            return -1;
> +        }
>  
> +        drive->sectors = be64_to_cpu(capdata16.sectors);
> +        dprintf(1, "READCAP(16) %s blksize=%d sectors=%llX\n"
> +                , s, drive->blksize, drive->sectors);
> +    } else {
> +        drive->blksize = be32_to_cpu(capdata.blksize);
> +        if (drive->blksize != DISK_SECTOR_SIZE) {
> +            dprintf(1, "%s: unsupported block size %d\n", s, drive->blksize);
> +            return -1;
> +        }
> +        drive->sectors = (u64)be32_to_cpu(capdata.sectors) + 1;
> +        dprintf(1, "%s blksize=%d sectors=%u\n"
> +                , s, drive->blksize, (unsigned)drive->sectors);
> +    }

The blksize check and the capacity logging can be moved out of the if
block so it is not duplicated.

take care,
  Gerd

_______________________________________________
SeaBIOS mailing list -- seabios@seabios.org
To unsubscribe send an email to seabios-leave@seabios.org