linux/drivers/cxl/pmem.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/* Copyright(c) 2021 Intel Corporation. All rights reserved. */
   3#include <linux/libnvdimm.h>
   4#include <linux/device.h>
   5#include <linux/module.h>
   6#include <linux/ndctl.h>
   7#include <linux/async.h>
   8#include <linux/slab.h>
   9#include "cxlmem.h"
  10#include "cxl.h"
  11
  12/*
  13 * Ordered workqueue for cxl nvdimm device arrival and departure
  14 * to coordinate bus rescans when a bridge arrives and trigger remove
  15 * operations when the bridge is removed.
  16 */
  17static struct workqueue_struct *cxl_pmem_wq;
  18
  19static void unregister_nvdimm(void *nvdimm)
  20{
  21        nvdimm_delete(nvdimm);
  22}
  23
  24static int match_nvdimm_bridge(struct device *dev, const void *data)
  25{
  26        return strcmp(dev_name(dev), "nvdimm-bridge") == 0;
  27}
  28
  29static struct cxl_nvdimm_bridge *cxl_find_nvdimm_bridge(void)
  30{
  31        struct device *dev;
  32
  33        dev = bus_find_device(&cxl_bus_type, NULL, NULL, match_nvdimm_bridge);
  34        if (!dev)
  35                return NULL;
  36        return to_cxl_nvdimm_bridge(dev);
  37}
  38
  39static int cxl_nvdimm_probe(struct device *dev)
  40{
  41        struct cxl_nvdimm *cxl_nvd = to_cxl_nvdimm(dev);
  42        struct cxl_nvdimm_bridge *cxl_nvb;
  43        unsigned long flags = 0;
  44        struct nvdimm *nvdimm;
  45        int rc = -ENXIO;
  46
  47        cxl_nvb = cxl_find_nvdimm_bridge();
  48        if (!cxl_nvb)
  49                return -ENXIO;
  50
  51        device_lock(&cxl_nvb->dev);
  52        if (!cxl_nvb->nvdimm_bus)
  53                goto out;
  54
  55        set_bit(NDD_LABELING, &flags);
  56        nvdimm = nvdimm_create(cxl_nvb->nvdimm_bus, cxl_nvd, NULL, flags, 0, 0,
  57                               NULL);
  58        if (!nvdimm)
  59                goto out;
  60
  61        rc = devm_add_action_or_reset(dev, unregister_nvdimm, nvdimm);
  62out:
  63        device_unlock(&cxl_nvb->dev);
  64        put_device(&cxl_nvb->dev);
  65
  66        return rc;
  67}
  68
  69static struct cxl_driver cxl_nvdimm_driver = {
  70        .name = "cxl_nvdimm",
  71        .probe = cxl_nvdimm_probe,
  72        .id = CXL_DEVICE_NVDIMM,
  73};
  74
  75static int cxl_pmem_ctl(struct nvdimm_bus_descriptor *nd_desc,
  76                        struct nvdimm *nvdimm, unsigned int cmd, void *buf,
  77                        unsigned int buf_len, int *cmd_rc)
  78{
  79        return -ENOTTY;
  80}
  81
  82static bool online_nvdimm_bus(struct cxl_nvdimm_bridge *cxl_nvb)
  83{
  84        if (cxl_nvb->nvdimm_bus)
  85                return true;
  86        cxl_nvb->nvdimm_bus =
  87                nvdimm_bus_register(&cxl_nvb->dev, &cxl_nvb->nd_desc);
  88        return cxl_nvb->nvdimm_bus != NULL;
  89}
  90
  91static int cxl_nvdimm_release_driver(struct device *dev, void *data)
  92{
  93        if (!is_cxl_nvdimm(dev))
  94                return 0;
  95        device_release_driver(dev);
  96        return 0;
  97}
  98
  99static void offline_nvdimm_bus(struct nvdimm_bus *nvdimm_bus)
 100{
 101        if (!nvdimm_bus)
 102                return;
 103
 104        /*
 105         * Set the state of cxl_nvdimm devices to unbound / idle before
 106         * nvdimm_bus_unregister() rips the nvdimm objects out from
 107         * underneath them.
 108         */
 109        bus_for_each_dev(&cxl_bus_type, NULL, NULL, cxl_nvdimm_release_driver);
 110        nvdimm_bus_unregister(nvdimm_bus);
 111}
 112
 113static void cxl_nvb_update_state(struct work_struct *work)
 114{
 115        struct cxl_nvdimm_bridge *cxl_nvb =
 116                container_of(work, typeof(*cxl_nvb), state_work);
 117        struct nvdimm_bus *victim_bus = NULL;
 118        bool release = false, rescan = false;
 119
 120        device_lock(&cxl_nvb->dev);
 121        switch (cxl_nvb->state) {
 122        case CXL_NVB_ONLINE:
 123                if (!online_nvdimm_bus(cxl_nvb)) {
 124                        dev_err(&cxl_nvb->dev,
 125                                "failed to establish nvdimm bus\n");
 126                        release = true;
 127                } else
 128                        rescan = true;
 129                break;
 130        case CXL_NVB_OFFLINE:
 131        case CXL_NVB_DEAD:
 132                victim_bus = cxl_nvb->nvdimm_bus;
 133                cxl_nvb->nvdimm_bus = NULL;
 134                break;
 135        default:
 136                break;
 137        }
 138        device_unlock(&cxl_nvb->dev);
 139
 140        if (release)
 141                device_release_driver(&cxl_nvb->dev);
 142        if (rescan) {
 143                int rc = bus_rescan_devices(&cxl_bus_type);
 144
 145                dev_dbg(&cxl_nvb->dev, "rescan: %d\n", rc);
 146        }
 147        offline_nvdimm_bus(victim_bus);
 148
 149        put_device(&cxl_nvb->dev);
 150}
 151
 152static void cxl_nvdimm_bridge_remove(struct device *dev)
 153{
 154        struct cxl_nvdimm_bridge *cxl_nvb = to_cxl_nvdimm_bridge(dev);
 155
 156        if (cxl_nvb->state == CXL_NVB_ONLINE)
 157                cxl_nvb->state = CXL_NVB_OFFLINE;
 158        if (queue_work(cxl_pmem_wq, &cxl_nvb->state_work))
 159                get_device(&cxl_nvb->dev);
 160}
 161
 162static int cxl_nvdimm_bridge_probe(struct device *dev)
 163{
 164        struct cxl_nvdimm_bridge *cxl_nvb = to_cxl_nvdimm_bridge(dev);
 165
 166        if (cxl_nvb->state == CXL_NVB_DEAD)
 167                return -ENXIO;
 168
 169        if (cxl_nvb->state == CXL_NVB_NEW) {
 170                cxl_nvb->nd_desc = (struct nvdimm_bus_descriptor) {
 171                        .provider_name = "CXL",
 172                        .module = THIS_MODULE,
 173                        .ndctl = cxl_pmem_ctl,
 174                };
 175
 176                INIT_WORK(&cxl_nvb->state_work, cxl_nvb_update_state);
 177        }
 178
 179        cxl_nvb->state = CXL_NVB_ONLINE;
 180        if (queue_work(cxl_pmem_wq, &cxl_nvb->state_work))
 181                get_device(&cxl_nvb->dev);
 182
 183        return 0;
 184}
 185
 186static struct cxl_driver cxl_nvdimm_bridge_driver = {
 187        .name = "cxl_nvdimm_bridge",
 188        .probe = cxl_nvdimm_bridge_probe,
 189        .remove = cxl_nvdimm_bridge_remove,
 190        .id = CXL_DEVICE_NVDIMM_BRIDGE,
 191};
 192
 193static __init int cxl_pmem_init(void)
 194{
 195        int rc;
 196
 197        cxl_pmem_wq = alloc_ordered_workqueue("cxl_pmem", 0);
 198        if (!cxl_pmem_wq)
 199                return -ENXIO;
 200
 201        rc = cxl_driver_register(&cxl_nvdimm_bridge_driver);
 202        if (rc)
 203                goto err_bridge;
 204
 205        rc = cxl_driver_register(&cxl_nvdimm_driver);
 206        if (rc)
 207                goto err_nvdimm;
 208
 209        return 0;
 210
 211err_nvdimm:
 212        cxl_driver_unregister(&cxl_nvdimm_bridge_driver);
 213err_bridge:
 214        destroy_workqueue(cxl_pmem_wq);
 215        return rc;
 216}
 217
 218static __exit void cxl_pmem_exit(void)
 219{
 220        cxl_driver_unregister(&cxl_nvdimm_driver);
 221        cxl_driver_unregister(&cxl_nvdimm_bridge_driver);
 222        destroy_workqueue(cxl_pmem_wq);
 223}
 224
 225MODULE_LICENSE("GPL v2");
 226module_init(cxl_pmem_init);
 227module_exit(cxl_pmem_exit);
 228MODULE_IMPORT_NS(CXL);
 229MODULE_ALIAS_CXL(CXL_DEVICE_NVDIMM_BRIDGE);
 230MODULE_ALIAS_CXL(CXL_DEVICE_NVDIMM);
 231