root/drivers/nvdimm/claim.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright(c) 2013-2015 Intel Corporation. All rights reserved.
 */
#include <linux/device.h>
#include <linux/sizes.h>
#include <linux/badblocks.h>
#include "nd-core.h"
#include "pmem.h"
#include "pfn.h"
#include "btt.h"
#include "nd.h"

void __nd_detach_ndns(struct device *dev, struct nd_namespace_common **_ndns)
{
        struct nd_namespace_common *ndns = *_ndns;
        struct nvdimm_bus *nvdimm_bus;

        if (!ndns)
                return;

        nvdimm_bus = walk_to_nvdimm_bus(&ndns->dev);
        lockdep_assert_held(&nvdimm_bus->reconfig_mutex);
        dev_WARN_ONCE(dev, ndns->claim != dev, "%s: invalid claim\n", __func__);
        ndns->claim = NULL;
        *_ndns = NULL;
        put_device(&ndns->dev);
}

void nd_detach_ndns(struct device *dev,
                struct nd_namespace_common **_ndns)
{
        struct nd_namespace_common *ndns = *_ndns;

        if (!ndns)
                return;

        struct device *ndev __free(put_device) = get_device(&ndns->dev);
        guard(nvdimm_bus)(ndev);
        __nd_detach_ndns(dev, _ndns);
}

bool __nd_attach_ndns(struct device *dev, struct nd_namespace_common *attach,
                struct nd_namespace_common **_ndns)
{
        struct nvdimm_bus *nvdimm_bus = walk_to_nvdimm_bus(&attach->dev);

        if (attach->claim)
                return false;
        lockdep_assert_held(&nvdimm_bus->reconfig_mutex);
        dev_WARN_ONCE(dev, *_ndns, "%s: invalid claim\n", __func__);
        attach->claim = dev;
        *_ndns = attach;
        get_device(&attach->dev);
        return true;
}

static bool is_idle(struct device *dev, struct nd_namespace_common *ndns)
{
        struct nd_region *nd_region = to_nd_region(dev->parent);
        struct device *seed = NULL;

        if (is_nd_btt(dev))
                seed = nd_region->btt_seed;
        else if (is_nd_pfn(dev))
                seed = nd_region->pfn_seed;
        else if (is_nd_dax(dev))
                seed = nd_region->dax_seed;

        if (seed == dev || ndns || dev->driver)
                return false;
        return true;
}

struct nd_pfn *to_nd_pfn_safe(struct device *dev)
{
        /*
         * pfn device attributes are re-used by dax device instances, so we
         * need to be careful to correct device-to-nd_pfn conversion.
         */
        if (is_nd_pfn(dev))
                return to_nd_pfn(dev);

        if (is_nd_dax(dev)) {
                struct nd_dax *nd_dax = to_nd_dax(dev);

                return &nd_dax->nd_pfn;
        }

        WARN_ON(1);
        return NULL;
}

static void nd_detach_and_reset(struct device *dev,
                struct nd_namespace_common **_ndns)
{
        /* detach the namespace and destroy / reset the device */
        __nd_detach_ndns(dev, _ndns);
        if (is_idle(dev, *_ndns)) {
                nd_device_unregister(dev, ND_ASYNC);
        } else if (is_nd_btt(dev)) {
                struct nd_btt *nd_btt = to_nd_btt(dev);

                nd_btt->lbasize = 0;
                kfree(nd_btt->uuid);
                nd_btt->uuid = NULL;
        } else if (is_nd_pfn(dev) || is_nd_dax(dev)) {
                struct nd_pfn *nd_pfn = to_nd_pfn_safe(dev);

                kfree(nd_pfn->uuid);
                nd_pfn->uuid = NULL;
                nd_pfn->mode = PFN_MODE_NONE;
        }
}

ssize_t nd_namespace_store(struct device *dev,
                struct nd_namespace_common **_ndns, const char *buf,
                size_t len)
{
        struct nd_namespace_common *ndns;
        struct device *found;
        char *name;

        if (dev->driver) {
                dev_dbg(dev, "namespace already active\n");
                return -EBUSY;
        }

        name = kstrndup(buf, len, GFP_KERNEL);
        if (!name)
                return -ENOMEM;
        strim(name);

        if (strncmp(name, "namespace", 9) == 0 || strcmp(name, "") == 0)
                /* pass */;
        else {
                len = -EINVAL;
                goto out;
        }

        ndns = *_ndns;
        if (strcmp(name, "") == 0) {
                nd_detach_and_reset(dev, _ndns);
                goto out;
        } else if (ndns) {
                dev_dbg(dev, "namespace already set to: %s\n",
                                dev_name(&ndns->dev));
                len = -EBUSY;
                goto out;
        }

        found = device_find_child_by_name(dev->parent, name);
        if (!found) {
                dev_dbg(dev, "'%s' not found under %s\n", name,
                                dev_name(dev->parent));
                len = -ENODEV;
                goto out;
        }

        ndns = to_ndns(found);

        switch (ndns->claim_class) {
        case NVDIMM_CCLASS_NONE:
                break;
        case NVDIMM_CCLASS_BTT:
        case NVDIMM_CCLASS_BTT2:
                if (!is_nd_btt(dev)) {
                        len = -EBUSY;
                        goto out_attach;
                }
                break;
        case NVDIMM_CCLASS_PFN:
                if (!is_nd_pfn(dev)) {
                        len = -EBUSY;
                        goto out_attach;
                }
                break;
        case NVDIMM_CCLASS_DAX:
                if (!is_nd_dax(dev)) {
                        len = -EBUSY;
                        goto out_attach;
                }
                break;
        default:
                len = -EBUSY;
                goto out_attach;
                break;
        }

        if (__nvdimm_namespace_capacity(ndns) < SZ_16M) {
                dev_dbg(dev, "%s too small to host\n", name);
                len = -ENXIO;
                goto out_attach;
        }

        WARN_ON_ONCE(!is_nvdimm_bus_locked(dev));
        if (!__nd_attach_ndns(dev, ndns, _ndns)) {
                dev_dbg(dev, "%s already claimed\n",
                                dev_name(&ndns->dev));
                len = -EBUSY;
        }

 out_attach:
        put_device(&ndns->dev); /* from device_find_child */
 out:
        kfree(name);
        return len;
}

/*
 * nd_sb_checksum: compute checksum for a generic info block
 *
 * Returns a fletcher64 checksum of everything in the given info block
 * except the last field (since that's where the checksum lives).
 */
u64 nd_sb_checksum(struct nd_gen_sb *nd_gen_sb)
{
        u64 sum;
        __le64 sum_save;

        BUILD_BUG_ON(sizeof(struct btt_sb) != SZ_4K);
        BUILD_BUG_ON(sizeof(struct nd_pfn_sb) != SZ_4K);
        BUILD_BUG_ON(sizeof(struct nd_gen_sb) != SZ_4K);

        sum_save = nd_gen_sb->checksum;
        nd_gen_sb->checksum = 0;
        sum = nd_fletcher64(nd_gen_sb, sizeof(*nd_gen_sb), 1);
        nd_gen_sb->checksum = sum_save;
        return sum;
}
EXPORT_SYMBOL(nd_sb_checksum);

static int nsio_rw_bytes(struct nd_namespace_common *ndns,
                resource_size_t offset, void *buf, size_t size, int rw,
                unsigned long flags)
{
        struct nd_namespace_io *nsio = to_nd_namespace_io(&ndns->dev);
        unsigned int sz_align = ALIGN(size + (offset & (512 - 1)), 512);
        sector_t sector = offset >> 9;
        int rc = 0, ret = 0;

        if (unlikely(!size))
                return 0;

        if (unlikely(offset + size > nsio->size)) {
                dev_WARN_ONCE(&ndns->dev, 1, "request out of range\n");
                return -EFAULT;
        }

        if (rw == READ) {
                if (unlikely(is_bad_pmem(&nsio->bb, sector, sz_align)))
                        return -EIO;
                if (copy_mc_to_kernel(buf, nsio->addr + offset, size) != 0)
                        return -EIO;
                return 0;
        }

        if (unlikely(is_bad_pmem(&nsio->bb, sector, sz_align))) {
                if (IS_ALIGNED(offset, 512) && IS_ALIGNED(size, 512)
                                && !(flags & NVDIMM_IO_ATOMIC)) {
                        long cleared;

                        might_sleep();
                        cleared = nvdimm_clear_poison(&ndns->dev,
                                        nsio->res.start + offset, size);
                        if (cleared < size)
                                rc = -EIO;
                        if (cleared > 0 && cleared / 512) {
                                cleared /= 512;
                                badblocks_clear(&nsio->bb, sector, cleared);
                        }
                        arch_invalidate_pmem(nsio->addr + offset, size);
                } else
                        rc = -EIO;
        }

        memcpy_flushcache(nsio->addr + offset, buf, size);
        ret = nvdimm_flush(to_nd_region(ndns->dev.parent), NULL);
        if (ret)
                rc = ret;

        return rc;
}

int devm_nsio_enable(struct device *dev, struct nd_namespace_io *nsio,
                resource_size_t size)
{
        struct nd_namespace_common *ndns = &nsio->common;
        struct range range = {
                .start = nsio->res.start,
                .end = nsio->res.end,
        };

        nsio->size = size;
        if (!devm_request_mem_region(dev, range.start, size,
                                dev_name(&ndns->dev))) {
                dev_warn(dev, "could not reserve region %pR\n", &nsio->res);
                return -EBUSY;
        }

        ndns->rw_bytes = nsio_rw_bytes;
        if (devm_init_badblocks(dev, &nsio->bb))
                return -ENOMEM;
        nvdimm_badblocks_populate(to_nd_region(ndns->dev.parent), &nsio->bb,
                        &range);

        nsio->addr = devm_memremap(dev, range.start, size, ARCH_MEMREMAP_PMEM);

        return PTR_ERR_OR_ZERO(nsio->addr);
}

void devm_nsio_disable(struct device *dev, struct nd_namespace_io *nsio)
{
        struct resource *res = &nsio->res;

        devm_memunmap(dev, nsio->addr);
        devm_exit_badblocks(dev, &nsio->bb);
        devm_release_mem_region(dev, res->start, nsio->size);
}