2018-07-12 14:56:34 +00:00
|
|
|
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
2018-07-10 19:08:14 +00:00
|
|
|
|
From: Stefan Fritsch <fritsch@genua.de>
|
|
|
|
|
Date: Fri, 19 Jan 2018 14:13:29 +0100
|
|
|
|
|
Subject: [PATCH] ahci: Improve error handling
|
|
|
|
|
MIME-Version: 1.0
|
|
|
|
|
Content-Type: text/plain; charset=UTF-8
|
|
|
|
|
Content-Transfer-Encoding: 8bit
|
|
|
|
|
|
|
|
|
|
Check the error bits in the interrupt status register. According to the
|
|
|
|
|
AHCI 1.2 spec, "Interrupt sources that are disabled (‘0’) are still
|
|
|
|
|
reflected in the status registers.", so this should work even though
|
|
|
|
|
grub uses polling
|
|
|
|
|
|
|
|
|
|
This fixes the following problem on a Fujitsu E744 laptop:
|
|
|
|
|
|
|
|
|
|
Sometimes there is a very long delay (up to several minutes) when
|
|
|
|
|
booting from hard disk. It seems accessing the DVD drive (which has no
|
|
|
|
|
disk inserted) sometimes fails with some errors, which leads to each
|
|
|
|
|
access being stalled until the 20s timeout triggers. This seems to
|
|
|
|
|
happen when grub is trying to read filesystem/partition data.
|
|
|
|
|
|
|
|
|
|
The problem is that the command_issue bit that is checked in the loop is
|
|
|
|
|
only reset if the "HBA receives a FIS which clears the BSY, DRQ, and ERR
|
|
|
|
|
bits for the command", but the ERR bit is never cleared. Therefore
|
|
|
|
|
command_issue is never reset and grub waits for the timeout.
|
|
|
|
|
|
|
|
|
|
The relevant bit in our case is the Task File Error Status (TFES), which
|
|
|
|
|
is equivalent to the ERR bit 0 in tfd. But this patch also checks
|
|
|
|
|
the other error bits except for the "Interface non-fatal error status"
|
|
|
|
|
bit.
|
|
|
|
|
|
|
|
|
|
Signed-off-by: Stefan Fritsch <fritsch@genua.de>
|
|
|
|
|
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
|
|
|
|
|
---
|
|
|
|
|
grub-core/disk/ahci.c | 22 ++++++++++++++++++++--
|
|
|
|
|
1 file changed, 20 insertions(+), 2 deletions(-)
|
|
|
|
|
|
|
|
|
|
diff --git a/grub-core/disk/ahci.c b/grub-core/disk/ahci.c
|
|
|
|
|
index 494a1b7734e..f2f606423ac 100644
|
|
|
|
|
--- a/grub-core/disk/ahci.c
|
|
|
|
|
+++ b/grub-core/disk/ahci.c
|
|
|
|
|
@@ -82,6 +82,20 @@ enum grub_ahci_hba_port_command
|
|
|
|
|
GRUB_AHCI_HBA_PORT_CMD_FR = 0x4000,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
+enum grub_ahci_hba_port_int_status
|
|
|
|
|
+ {
|
|
|
|
|
+ GRUB_AHCI_HBA_PORT_IS_IFS = (1UL << 27),
|
|
|
|
|
+ GRUB_AHCI_HBA_PORT_IS_HBDS = (1UL << 28),
|
|
|
|
|
+ GRUB_AHCI_HBA_PORT_IS_HBFS = (1UL << 29),
|
|
|
|
|
+ GRUB_AHCI_HBA_PORT_IS_TFES = (1UL << 30),
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+#define GRUB_AHCI_HBA_PORT_IS_FATAL_MASK ( \
|
|
|
|
|
+ GRUB_AHCI_HBA_PORT_IS_IFS | \
|
|
|
|
|
+ GRUB_AHCI_HBA_PORT_IS_HBDS | \
|
|
|
|
|
+ GRUB_AHCI_HBA_PORT_IS_HBFS | \
|
|
|
|
|
+ GRUB_AHCI_HBA_PORT_IS_TFES)
|
|
|
|
|
+
|
|
|
|
|
struct grub_ahci_hba
|
|
|
|
|
{
|
|
|
|
|
grub_uint32_t cap;
|
|
|
|
|
@@ -1026,7 +1040,8 @@ grub_ahci_readwrite_real (struct grub_ahci_device *dev,
|
|
|
|
|
|
|
|
|
|
endtime = grub_get_time_ms () + (spinup ? 20000 : 20000);
|
|
|
|
|
while ((dev->hba->ports[dev->port].command_issue & 1))
|
|
|
|
|
- if (grub_get_time_ms () > endtime)
|
|
|
|
|
+ if (grub_get_time_ms () > endtime ||
|
|
|
|
|
+ (dev->hba->ports[dev->port].intstatus & GRUB_AHCI_HBA_PORT_IS_FATAL_MASK))
|
|
|
|
|
{
|
|
|
|
|
grub_dprintf ("ahci", "AHCI status <%x %x %x %x>\n",
|
|
|
|
|
dev->hba->ports[dev->port].command_issue,
|
|
|
|
|
@@ -1034,7 +1049,10 @@ grub_ahci_readwrite_real (struct grub_ahci_device *dev,
|
|
|
|
|
dev->hba->ports[dev->port].intstatus,
|
|
|
|
|
dev->hba->ports[dev->port].task_file_data);
|
|
|
|
|
dev->hba->ports[dev->port].command_issue = 0;
|
|
|
|
|
- err = grub_error (GRUB_ERR_IO, "AHCI transfer timed out");
|
|
|
|
|
+ if (dev->hba->ports[dev->port].intstatus & GRUB_AHCI_HBA_PORT_IS_FATAL_MASK)
|
|
|
|
|
+ err = grub_error (GRUB_ERR_IO, "AHCI transfer error");
|
|
|
|
|
+ else
|
|
|
|
|
+ err = grub_error (GRUB_ERR_IO, "AHCI transfer timed out");
|
|
|
|
|
if (!reset)
|
|
|
|
|
grub_ahci_reset_port (dev, 1);
|
|
|
|
|
break;
|