nata - properly teardown interrupts on ENXIO
authorMatthew Dillon <dillon@apollo.backplane.com>
Fri, 29 Oct 2010 06:23:48 +0000 (23:23 -0700)
committerMatthew Dillon <dillon@apollo.backplane.com>
Fri, 29 Oct 2010 06:26:17 +0000 (23:26 -0700)
* When a ata controller cannot be attached (e.g. due to AHCI having
  already taken its resources) be sure to teardown and release any
  resources that had been obtained.

* This fixes a panic on certain motherboards (e.g. gigabyte / phenomX6)
  where the ata controller ENXIO'd but left interrupt handlers setup.
  An interrupt would then occur, access stale memory, and blow the machine
  up.

sys/dev/disk/nata/ata-chipset.c
sys/dev/disk/nata/ata-pci.c

index 6607d6f..91d4444 100644 (file)
@@ -187,6 +187,7 @@ static void ata_via_family_setmode(device_t dev, int mode);
 static struct ata_chip_id *ata_match_chip(device_t dev, struct ata_chip_id *index);
 static struct ata_chip_id *ata_find_chip(device_t dev, struct ata_chip_id *index, int slot);
 static int ata_setup_interrupt(device_t dev);
+static void ata_teardown_interrupt(device_t dev);
 static int ata_serialize(device_t dev, int flags);
 static void ata_print_cable(device_t dev, u_int8_t *who);
 static int ata_atapi(device_t dev);
@@ -1876,8 +1877,10 @@ ata_intel_chipinit(device_t dev)
            ctlr->r_rid2 = PCIR_BAR(0);
            if (!(ctlr->r_res2 = bus_alloc_resource_any(dev, ctlr->r_type2,
                                                        &ctlr->r_rid2,
-                                                       RF_ACTIVE)))
+                                                       RF_ACTIVE))) {
+               ata_teardown_interrupt(dev);
                return ENXIO;
+           }
            ctlr->channels = 4;
            ctlr->allocate = ata_intel_31244_allocate;
            ctlr->reset = ata_intel_31244_reset;
@@ -2517,8 +2520,10 @@ ata_jmicron_chipinit(device_t dev)
        pci_write_config(dev, 0x40, 0x80c0a131, 4);
        pci_write_config(dev, 0x80, 0x01200000, 4);
 
-       if ((error = ata_ahci_chipinit(dev)))
+       if ((error = ata_ahci_chipinit(dev))) {
+           ata_teardown_interrupt(dev);
            return error;
+       }
 
        ctlr->allocate = ata_jmicron_allocate;
        ctlr->reset = ata_jmicron_reset;
@@ -2706,8 +2711,10 @@ ata_marvell_edma_chipinit(device_t dev)
     ctlr->r_type1 = SYS_RES_MEMORY;
     ctlr->r_rid1 = PCIR_BAR(0);
     if (!(ctlr->r_res1 = bus_alloc_resource_any(dev, ctlr->r_type1,
-                                               &ctlr->r_rid1, RF_ACTIVE)))
+                                               &ctlr->r_rid1, RF_ACTIVE))) {
+       ata_teardown_interrupt(dev);
        return ENXIO;
+    }
 
     /* mask all host controller interrupts */
     ATA_OUTL(ctlr->r_res1, 0x01d64, 0x00000000);
@@ -4370,8 +4377,10 @@ ata_serverworks_chipinit(device_t dev)
        ctlr->r_type2 = SYS_RES_MEMORY;
        ctlr->r_rid2 = PCIR_BAR(5);
        if (!(ctlr->r_res2 = bus_alloc_resource_any(dev, ctlr->r_type2,
-                                                   &ctlr->r_rid2, RF_ACTIVE)))
+                                                   &ctlr->r_rid2, RF_ACTIVE))){
+           ata_teardown_interrupt(dev);
            return ENXIO;
+       }
 
        ctlr->channels = ctlr->chip->cfg2;
        ctlr->allocate = ata_serverworks_allocate;
@@ -4559,14 +4568,17 @@ ata_sii_chipinit(device_t dev)
        ctlr->r_type1 = SYS_RES_MEMORY;
        ctlr->r_rid1 = PCIR_BAR(0);
        if (!(ctlr->r_res1 = bus_alloc_resource_any(dev, ctlr->r_type1,
-                                                   &ctlr->r_rid1, RF_ACTIVE)))
+                                                   &ctlr->r_rid1, RF_ACTIVE))){
+           ata_teardown_interrupt(dev);
            return ENXIO;
+       }
 
        ctlr->r_rid2 = PCIR_BAR(2);
        ctlr->r_type2 = SYS_RES_MEMORY;
        if (!(ctlr->r_res2 = bus_alloc_resource_any(dev, ctlr->r_type2,
                                                    &ctlr->r_rid2, RF_ACTIVE))){
            bus_release_resource(dev, ctlr->r_type1, ctlr->r_rid1,ctlr->r_res1);
+           ata_teardown_interrupt(dev);
            return ENXIO;
        }
        ctlr->allocate = ata_siiprb_allocate;
@@ -4589,8 +4601,10 @@ ata_sii_chipinit(device_t dev)
        ctlr->r_type2 = SYS_RES_MEMORY;
        ctlr->r_rid2 = PCIR_BAR(5);
        if (!(ctlr->r_res2 = bus_alloc_resource_any(dev, ctlr->r_type2,
-                                                   &ctlr->r_rid2, RF_ACTIVE)))
+                                                   &ctlr->r_rid2, RF_ACTIVE))){
+           ata_teardown_interrupt(dev);
            return ENXIO;
+       }
 
        if (ctlr->chip->cfg2 & SIISETCLK) {
            if ((pci_read_config(dev, 0x8a, 1) & 0x30) != 0x10)
@@ -5293,6 +5307,7 @@ ata_sis_chipinit(device_t dev)
        ctlr->setmode = ata_sata_setmode;
        return 0;
     default:
+       ata_teardown_interrupt(dev);
        return ENXIO;
     }
     ctlr->setmode = ata_sis_setmode;
@@ -5758,12 +5773,28 @@ ata_setup_interrupt(device_t dev)
        if ((bus_setup_intr(dev, ctlr->r_irq, ATA_INTR_FLAGS,
                            ata_generic_intr, ctlr, &ctlr->handle, NULL))) {
            device_printf(dev, "unable to setup interrupt\n");
+           bus_release_resource(dev, SYS_RES_IRQ, rid, ctlr->r_irq);
+           ctlr->r_irq = 0;
            return ENXIO;
        }
     }
     return 0;
 }
 
+static void
+ata_teardown_interrupt(device_t dev)
+{
+    struct ata_pci_controller *ctlr = device_get_softc(dev);
+
+    if (!ctlr->legacy) {
+       if (ctlr->r_irq) {
+           bus_teardown_intr(dev, ctlr->r_irq, ctlr->handle);
+           bus_release_resource(dev, SYS_RES_IRQ, ATA_IRQ_RID, ctlr->r_irq);
+           ctlr->r_irq = 0;
+       }
+    }
+}
+
 struct ata_serialize {
     struct spinlock     locked_mtx;
     int                 locked_ch;
index b0d8d43..205b772 100644 (file)
@@ -276,11 +276,16 @@ ata_pci_detach(device_t dev)
     if (ctlr->r_irq) {
        bus_teardown_intr(dev, ctlr->r_irq, ctlr->handle);
        bus_release_resource(dev, SYS_RES_IRQ, ATA_IRQ_RID, ctlr->r_irq);
+       ctlr->r_irq = 0;
     }
-    if (ctlr->r_res2)
+    if (ctlr->r_res2) {
        bus_release_resource(dev, ctlr->r_type2, ctlr->r_rid2, ctlr->r_res2);
-    if (ctlr->r_res1)
+       ctlr->r_res2 = 0;
+    }
+    if (ctlr->r_res1) {
        bus_release_resource(dev, ctlr->r_type1, ctlr->r_rid1, ctlr->r_res1);
+       ctlr->r_res1 = 0;
+    }
 
     return 0;
 }