aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/acpi
diff options
context:
space:
mode:
authorVishal Verma <vishal.l.verma@intel.com>2015-12-24 21:21:43 -0500
committerDan Williams <dan.j.williams@intel.com>2016-01-09 11:39:03 -0500
commit0caeef63e6d2f866d85bb507bf63e0ce8ec91cef (patch)
treedbd09f34ab455ca2dfc8c246ff7d19d17edd1de7 /drivers/acpi
parentd26f73f083ed6fbea7fd3fdbacb527b7f3e75ac0 (diff)
libnvdimm: Add a poison list and export badblocks
During region creation, perform Address Range Scrubs (ARS) for the SPA (System Physical Address) ranges to retrieve known poison locations from firmware. Add a new data structure 'nd_poison' which is used as a list in nvdimm_bus to store these poison locations. When creating a pmem namespace, if there is any known poison associated with its physical address space, convert the poison ranges to bad sectors that are exposed using the badblocks interface. Signed-off-by: Vishal Verma <vishal.l.verma@intel.com> Signed-off-by: Dan Williams <dan.j.williams@intel.com>
Diffstat (limited to 'drivers/acpi')
-rw-r--r--drivers/acpi/nfit.c203
1 files changed, 203 insertions, 0 deletions
diff --git a/drivers/acpi/nfit.c b/drivers/acpi/nfit.c
index e7ed39bab97d..e1dbc8da09b7 100644
--- a/drivers/acpi/nfit.c
+++ b/drivers/acpi/nfit.c
@@ -15,6 +15,7 @@
15#include <linux/module.h> 15#include <linux/module.h>
16#include <linux/mutex.h> 16#include <linux/mutex.h>
17#include <linux/ndctl.h> 17#include <linux/ndctl.h>
18#include <linux/delay.h>
18#include <linux/list.h> 19#include <linux/list.h>
19#include <linux/acpi.h> 20#include <linux/acpi.h>
20#include <linux/sort.h> 21#include <linux/sort.h>
@@ -1473,6 +1474,201 @@ static void acpi_nfit_blk_region_disable(struct nvdimm_bus *nvdimm_bus,
1473 /* devm will free nfit_blk */ 1474 /* devm will free nfit_blk */
1474} 1475}
1475 1476
1477static int ars_get_cap(struct nvdimm_bus_descriptor *nd_desc,
1478 struct nd_cmd_ars_cap *cmd, u64 addr, u64 length)
1479{
1480 cmd->address = addr;
1481 cmd->length = length;
1482
1483 return nd_desc->ndctl(nd_desc, NULL, ND_CMD_ARS_CAP, cmd,
1484 sizeof(*cmd));
1485}
1486
1487static int ars_do_start(struct nvdimm_bus_descriptor *nd_desc,
1488 struct nd_cmd_ars_start *cmd, u64 addr, u64 length)
1489{
1490 int rc;
1491
1492 cmd->address = addr;
1493 cmd->length = length;
1494 cmd->type = ND_ARS_PERSISTENT;
1495
1496 while (1) {
1497 rc = nd_desc->ndctl(nd_desc, NULL, ND_CMD_ARS_START, cmd,
1498 sizeof(*cmd));
1499 if (rc)
1500 return rc;
1501 switch (cmd->status) {
1502 case 0:
1503 return 0;
1504 case 1:
1505 /* ARS unsupported, but we should never get here */
1506 return 0;
1507 case 2:
1508 return -EINVAL;
1509 case 3:
1510 /* ARS is in progress */
1511 msleep(1000);
1512 break;
1513 default:
1514 return -ENXIO;
1515 }
1516 }
1517}
1518
1519static int ars_get_status(struct nvdimm_bus_descriptor *nd_desc,
1520 struct nd_cmd_ars_status *cmd)
1521{
1522 int rc;
1523
1524 while (1) {
1525 rc = nd_desc->ndctl(nd_desc, NULL, ND_CMD_ARS_STATUS, cmd,
1526 sizeof(*cmd));
1527 if (rc || cmd->status & 0xffff)
1528 return -ENXIO;
1529
1530 /* Check extended status (Upper two bytes) */
1531 switch (cmd->status >> 16) {
1532 case 0:
1533 return 0;
1534 case 1:
1535 /* ARS is in progress */
1536 msleep(1000);
1537 break;
1538 case 2:
1539 /* No ARS performed for the current boot */
1540 return 0;
1541 default:
1542 return -ENXIO;
1543 }
1544 }
1545}
1546
1547static int ars_status_process_records(struct nvdimm_bus *nvdimm_bus,
1548 struct nd_cmd_ars_status *ars_status, u64 start)
1549{
1550 int rc;
1551 u32 i;
1552
1553 /*
1554 * The address field returned by ars_status should be either
1555 * less than or equal to the address we last started ARS for.
1556 * The (start, length) returned by ars_status should also have
1557 * non-zero overlap with the range we started ARS for.
1558 * If this is not the case, bail.
1559 */
1560 if (ars_status->address > start ||
1561 (ars_status->address + ars_status->length < start))
1562 return -ENXIO;
1563
1564 for (i = 0; i < ars_status->num_records; i++) {
1565 rc = nvdimm_bus_add_poison(nvdimm_bus,
1566 ars_status->records[i].err_address,
1567 ars_status->records[i].length);
1568 if (rc)
1569 return rc;
1570 }
1571
1572 return 0;
1573}
1574
1575static int acpi_nfit_find_poison(struct acpi_nfit_desc *acpi_desc,
1576 struct nd_region_desc *ndr_desc)
1577{
1578 struct nvdimm_bus_descriptor *nd_desc = &acpi_desc->nd_desc;
1579 struct nvdimm_bus *nvdimm_bus = acpi_desc->nvdimm_bus;
1580 struct nd_cmd_ars_status *ars_status = NULL;
1581 struct nd_cmd_ars_start *ars_start = NULL;
1582 struct nd_cmd_ars_cap *ars_cap = NULL;
1583 u64 start, len, cur, remaining;
1584 int rc;
1585
1586 ars_cap = kzalloc(sizeof(*ars_cap), GFP_KERNEL);
1587 if (!ars_cap)
1588 return -ENOMEM;
1589
1590 start = ndr_desc->res->start;
1591 len = ndr_desc->res->end - ndr_desc->res->start + 1;
1592
1593 rc = ars_get_cap(nd_desc, ars_cap, start, len);
1594 if (rc)
1595 goto out;
1596
1597 /*
1598 * If ARS is unsupported, or if the 'Persistent Memory Scrub' flag in
1599 * extended status is not set, skip this but continue initialization
1600 */
1601 if ((ars_cap->status & 0xffff) ||
1602 !(ars_cap->status >> 16 & ND_ARS_PERSISTENT)) {
1603 dev_warn(acpi_desc->dev,
1604 "ARS unsupported (status: 0x%x), won't create an error list\n",
1605 ars_cap->status);
1606 goto out;
1607 }
1608
1609 /*
1610 * Check if a full-range ARS has been run. If so, use those results
1611 * without having to start a new ARS.
1612 */
1613 ars_status = kzalloc(ars_cap->max_ars_out + sizeof(*ars_status),
1614 GFP_KERNEL);
1615 if (!ars_status) {
1616 rc = -ENOMEM;
1617 goto out;
1618 }
1619
1620 rc = ars_get_status(nd_desc, ars_status);
1621 if (rc)
1622 goto out;
1623
1624 if (ars_status->address <= start &&
1625 (ars_status->address + ars_status->length >= start + len)) {
1626 rc = ars_status_process_records(nvdimm_bus, ars_status, start);
1627 goto out;
1628 }
1629
1630 /*
1631 * ARS_STATUS can overflow if the number of poison entries found is
1632 * greater than the maximum buffer size (ars_cap->max_ars_out)
1633 * To detect overflow, check if the length field of ars_status
1634 * is less than the length we supplied. If so, process the
1635 * error entries we got, adjust the start point, and start again
1636 */
1637 ars_start = kzalloc(sizeof(*ars_start), GFP_KERNEL);
1638 if (!ars_start)
1639 return -ENOMEM;
1640
1641 cur = start;
1642 remaining = len;
1643 do {
1644 u64 done, end;
1645
1646 rc = ars_do_start(nd_desc, ars_start, cur, remaining);
1647 if (rc)
1648 goto out;
1649
1650 rc = ars_get_status(nd_desc, ars_status);
1651 if (rc)
1652 goto out;
1653
1654 rc = ars_status_process_records(nvdimm_bus, ars_status, cur);
1655 if (rc)
1656 goto out;
1657
1658 end = min(cur + remaining,
1659 ars_status->address + ars_status->length);
1660 done = end - cur;
1661 cur += done;
1662 remaining -= done;
1663 } while (remaining);
1664
1665 out:
1666 kfree(ars_cap);
1667 kfree(ars_start);
1668 kfree(ars_status);
1669 return rc;
1670}
1671
1476static int acpi_nfit_init_mapping(struct acpi_nfit_desc *acpi_desc, 1672static int acpi_nfit_init_mapping(struct acpi_nfit_desc *acpi_desc,
1477 struct nd_mapping *nd_mapping, struct nd_region_desc *ndr_desc, 1673 struct nd_mapping *nd_mapping, struct nd_region_desc *ndr_desc,
1478 struct acpi_nfit_memory_map *memdev, 1674 struct acpi_nfit_memory_map *memdev,
@@ -1585,6 +1781,13 @@ static int acpi_nfit_register_region(struct acpi_nfit_desc *acpi_desc,
1585 1781
1586 nvdimm_bus = acpi_desc->nvdimm_bus; 1782 nvdimm_bus = acpi_desc->nvdimm_bus;
1587 if (nfit_spa_type(spa) == NFIT_SPA_PM) { 1783 if (nfit_spa_type(spa) == NFIT_SPA_PM) {
1784 rc = acpi_nfit_find_poison(acpi_desc, ndr_desc);
1785 if (rc) {
1786 dev_err(acpi_desc->dev,
1787 "error while performing ARS to find poison: %d\n",
1788 rc);
1789 return rc;
1790 }
1588 if (!nvdimm_pmem_region_create(nvdimm_bus, ndr_desc)) 1791 if (!nvdimm_pmem_region_create(nvdimm_bus, ndr_desc))
1589 return -ENOMEM; 1792 return -ENOMEM;
1590 } else if (nfit_spa_type(spa) == NFIT_SPA_VOLATILE) { 1793 } else if (nfit_spa_type(spa) == NFIT_SPA_VOLATILE) {