Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ahci: per-port msix support

Some AHCI controllers support per-port MSI-X vectors. At the same time
the Linux AHCI driver needs to support one-off architectures that
implement a single MSI-X vector for all ports. The heuristic for
enabling AHCI ports becomes, in order of preference:

1/ per-port multi-MSI-X

2/ per-port multi-MSI

3/ single MSI

4/ single MSI-X

5/ legacy INTX

This all depends on AHCI implementations with potentially broken MSI-X
requesting less vectors than the number of ports. If this assumption is
violated we will need to start explicitly white-listing AHCI-MSIX
implementations.

Reported-by: Ricardo Neri <ricardo.neri@intel.com>
[ricardo: fix struct msix_entry handling]
Reported-by: kernel test robot <ying.huang@linux.intel.com>
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
Signed-off-by: Tejun Heo <tj@kernel.org>

authored by

Dan Williams and committed by
Tejun Heo
d684a90d 4d92f009

+61 -27
+45 -22
drivers/ata/ahci.c
··· 1306 1306 #endif 1307 1307 1308 1308 /* 1309 - * ahci_init_msix() only implements single MSI-X support, not multiple 1310 - * MSI-X per-port interrupts. This is needed for host controllers that only 1311 - * have MSI-X support implemented, but no MSI or intx. 1309 + * ahci_init_msix() - optionally enable per-port MSI-X otherwise defer 1310 + * to single msi. 1312 1311 */ 1313 1312 static int ahci_init_msix(struct pci_dev *pdev, unsigned int n_ports, 1314 - struct ahci_host_priv *hpriv) 1313 + struct ahci_host_priv *hpriv, unsigned long flags) 1315 1314 { 1316 - int rc, nvec; 1317 - struct msix_entry entry = {}; 1315 + int nvec, i, rc; 1318 1316 1319 1317 /* Do not init MSI-X if MSI is disabled for the device */ 1320 1318 if (hpriv->flags & AHCI_HFLAG_NO_MSI) ··· 1322 1324 if (nvec < 0) 1323 1325 return nvec; 1324 1326 1325 - if (!nvec) { 1327 + /* 1328 + * Proper MSI-X implementations will have a vector per-port. 1329 + * Barring that, we prefer single-MSI over single-MSIX. If this 1330 + * check fails (not enough MSI-X vectors for all ports) we will 1331 + * be called again with the flag clear iff ahci_init_msi() 1332 + * fails. 1333 + */ 1334 + if (flags & AHCI_HFLAG_MULTI_MSIX) { 1335 + if (nvec < n_ports) 1336 + return -ENODEV; 1337 + nvec = n_ports; 1338 + } else if (nvec) { 1339 + nvec = 1; 1340 + } else { 1341 + /* 1342 + * Emit dev_err() since this was the non-legacy irq 1343 + * method of last resort. 1344 + */ 1326 1345 rc = -ENODEV; 1327 1346 goto fail; 1328 1347 } 1329 1348 1330 - /* 1331 - * There can be more than one vector (e.g. for error detection or 1332 - * hdd hotplug). Only the first vector (entry.entry = 0) is used. 1333 - */ 1334 - rc = pci_enable_msix_exact(pdev, &entry, 1); 1349 + for (i = 0; i < nvec; i++) 1350 + hpriv->msix[i].entry = i; 1351 + rc = pci_enable_msix_exact(pdev, hpriv->msix, nvec); 1335 1352 if (rc < 0) 1336 1353 goto fail; 1337 1354 1338 - hpriv->irq = entry.vector; 1355 + if (nvec > 1) 1356 + hpriv->flags |= AHCI_HFLAG_MULTI_MSIX; 1357 + hpriv->irq = hpriv->msix[0].vector; /* for single msi-x */ 1339 1358 1340 - return 1; 1359 + return nvec; 1341 1360 fail: 1342 1361 dev_err(&pdev->dev, 1343 1362 "failed to enable MSI-X with error %d, # of vectors: %d\n", ··· 1418 1403 { 1419 1404 int nvec; 1420 1405 1406 + /* 1407 + * Try to enable per-port MSI-X. If the host is not capable 1408 + * fall back to single MSI before finally attempting single 1409 + * MSI-X. 1410 + */ 1411 + nvec = ahci_init_msix(pdev, n_ports, hpriv, AHCI_HFLAG_MULTI_MSIX); 1412 + if (nvec >= 0) 1413 + return nvec; 1414 + 1421 1415 nvec = ahci_init_msi(pdev, n_ports, hpriv); 1422 1416 if (nvec >= 0) 1423 1417 return nvec; 1424 1418 1425 - /* 1426 - * Currently, MSI-X support only implements single IRQ mode and 1427 - * exists for controllers which can't do other types of IRQ. Only 1428 - * set it up if MSI fails. 1429 - */ 1430 - nvec = ahci_init_msix(pdev, n_ports, hpriv); 1419 + /* try single-msix */ 1420 + nvec = ahci_init_msix(pdev, n_ports, hpriv, 0); 1431 1421 if (nvec >= 0) 1432 1422 return nvec; 1433 1423 1434 - /* lagacy intx interrupts */ 1424 + /* legacy intx interrupts */ 1435 1425 pci_intx(pdev, 1); 1436 1426 hpriv->irq = pdev->irq; 1437 1427 ··· 1598 1578 if (!host) 1599 1579 return -ENOMEM; 1600 1580 host->private_data = hpriv; 1601 - 1581 + hpriv->msix = devm_kzalloc(&pdev->dev, 1582 + sizeof(struct msix_entry) * n_ports, GFP_KERNEL); 1583 + if (!hpriv->msix) 1584 + return -ENOMEM; 1602 1585 ahci_init_interrupts(pdev, n_ports, hpriv); 1603 1586 1604 1587 if (!(hpriv->cap & HOST_CAP_SSS) || ahci_ignore_sss)
+2
drivers/ata/ahci.h
··· 242 242 AHCI_HFLAG_NO_FBS = (1 << 18), /* no FBS */ 243 243 AHCI_HFLAG_EDGE_IRQ = (1 << 19), /* HOST_IRQ_STAT behaves as 244 244 Edge Triggered */ 245 + AHCI_HFLAG_MULTI_MSIX = (1 << 20), /* per-port MSI-X */ 245 246 246 247 /* ap->flags bits */ 247 248 ··· 344 343 * the PHY position in this array. 345 344 */ 346 345 struct phy **phys; 346 + struct msix_entry *msix; /* Optional MSI-X support */ 347 347 unsigned nports; /* Number of ports */ 348 348 void *plat_data; /* Other platform data */ 349 349 unsigned int irq; /* interrupt line */
+14 -5
drivers/ata/libahci.c
··· 43 43 #include <scsi/scsi_host.h> 44 44 #include <scsi/scsi_cmnd.h> 45 45 #include <linux/libata.h> 46 + #include <linux/pci.h> 46 47 #include "ahci.h" 47 48 #include "libata.h" 48 49 ··· 2471 2470 } 2472 2471 EXPORT_SYMBOL_GPL(ahci_set_em_messages); 2473 2472 2474 - static int ahci_host_activate_multi_irqs(struct ata_host *host, int irq, 2473 + static int ahci_host_activate_multi_irqs(struct ata_host *host, 2475 2474 struct scsi_host_template *sht) 2476 2475 { 2476 + struct ahci_host_priv *hpriv = host->private_data; 2477 2477 int i, rc; 2478 2478 2479 2479 rc = ata_host_start(host); ··· 2486 2484 */ 2487 2485 for (i = 0; i < host->n_ports; i++) { 2488 2486 struct ahci_port_priv *pp = host->ports[i]->private_data; 2487 + int irq; 2488 + 2489 + if (hpriv->flags & AHCI_HFLAG_MULTI_MSIX) 2490 + irq = hpriv->msix[i].vector; 2491 + else 2492 + irq = hpriv->irq + i; 2489 2493 2490 2494 /* Do not receive interrupts sent by dummy ports */ 2491 2495 if (!pp) { ··· 2499 2491 continue; 2500 2492 } 2501 2493 2502 - rc = devm_request_threaded_irq(host->dev, irq + i, 2494 + rc = devm_request_threaded_irq(host->dev, irq, 2503 2495 ahci_multi_irqs_intr, 2504 2496 ahci_port_thread_fn, 0, 2505 2497 pp->irq_desc, host->ports[i]); 2506 2498 if (rc) 2507 2499 return rc; 2508 - ata_port_desc(host->ports[i], "irq %d", irq + i); 2500 + ata_port_desc(host->ports[i], "irq %d", irq); 2509 2501 } 2502 + 2510 2503 return ata_host_register(host, sht); 2511 2504 } 2512 2505 ··· 2528 2519 int irq = hpriv->irq; 2529 2520 int rc; 2530 2521 2531 - if (hpriv->flags & AHCI_HFLAG_MULTI_MSI) 2532 - rc = ahci_host_activate_multi_irqs(host, irq, sht); 2522 + if (hpriv->flags & (AHCI_HFLAG_MULTI_MSI | AHCI_HFLAG_MULTI_MSIX)) 2523 + rc = ahci_host_activate_multi_irqs(host, sht); 2533 2524 else if (hpriv->flags & AHCI_HFLAG_EDGE_IRQ) 2534 2525 rc = ata_host_activate(host, irq, ahci_single_edge_irq_intr, 2535 2526 IRQF_SHARED, sht);