Skip to content

Commit 0caeef6

Browse files
stellarhopperdjbw
authored andcommitted
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>
1 parent d26f73f commit 0caeef6

File tree

6 files changed

+406
-0
lines changed

6 files changed

+406
-0
lines changed

drivers/acpi/nfit.c

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <linux/module.h>
1616
#include <linux/mutex.h>
1717
#include <linux/ndctl.h>
18+
#include <linux/delay.h>
1819
#include <linux/list.h>
1920
#include <linux/acpi.h>
2021
#include <linux/sort.h>
@@ -1473,6 +1474,201 @@ static void acpi_nfit_blk_region_disable(struct nvdimm_bus *nvdimm_bus,
14731474
/* devm will free nfit_blk */
14741475
}
14751476

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+
14761672
static int acpi_nfit_init_mapping(struct acpi_nfit_desc *acpi_desc,
14771673
struct nd_mapping *nd_mapping, struct nd_region_desc *ndr_desc,
14781674
struct acpi_nfit_memory_map *memdev,
@@ -1585,6 +1781,13 @@ static int acpi_nfit_register_region(struct acpi_nfit_desc *acpi_desc,
15851781

15861782
nvdimm_bus = acpi_desc->nvdimm_bus;
15871783
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+
}
15881791
if (!nvdimm_pmem_region_create(nvdimm_bus, ndr_desc))
15891792
return -ENOMEM;
15901793
} else if (nfit_spa_type(spa) == NFIT_SPA_VOLATILE) {

0 commit comments

Comments
 (0)