diff options
author | Vishal Verma <vishal.l.verma@intel.com> | 2015-12-24 21:21:43 -0500 |
---|---|---|
committer | Dan Williams <dan.j.williams@intel.com> | 2016-01-09 11:39:03 -0500 |
commit | 0caeef63e6d2f866d85bb507bf63e0ce8ec91cef (patch) | |
tree | dbd09f34ab455ca2dfc8c246ff7d19d17edd1de7 /drivers/acpi | |
parent | d26f73f083ed6fbea7fd3fdbacb527b7f3e75ac0 (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.c | 203 |
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 | ||
1477 | static 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 | |||
1487 | static 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 | |||
1519 | static 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 | |||
1547 | static 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 | |||
1575 | static 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 | |||
1476 | static int acpi_nfit_init_mapping(struct acpi_nfit_desc *acpi_desc, | 1672 | static 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) { |