cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

pmem.c (7003B)


      1// SPDX-License-Identifier: GPL-2.0-only
      2/* Copyright(c) 2020 Intel Corporation. */
      3#include <linux/device.h>
      4#include <linux/slab.h>
      5#include <linux/idr.h>
      6#include <cxlmem.h>
      7#include <cxl.h>
      8#include "core.h"
      9
     10/**
     11 * DOC: cxl pmem
     12 *
     13 * The core CXL PMEM infrastructure supports persistent memory
     14 * provisioning and serves as a bridge to the LIBNVDIMM subsystem. A CXL
     15 * 'bridge' device is added at the root of a CXL device topology if
     16 * platform firmware advertises at least one persistent memory capable
     17 * CXL window. That root-level bridge corresponds to a LIBNVDIMM 'bus'
     18 * device. Then for each cxl_memdev in the CXL device topology a bridge
     19 * device is added to host a LIBNVDIMM dimm object. When these bridges
     20 * are registered native LIBNVDIMM uapis are translated to CXL
     21 * operations, for example, namespace label access commands.
     22 */
     23
     24static DEFINE_IDA(cxl_nvdimm_bridge_ida);
     25
     26static void cxl_nvdimm_bridge_release(struct device *dev)
     27{
     28	struct cxl_nvdimm_bridge *cxl_nvb = to_cxl_nvdimm_bridge(dev);
     29
     30	ida_free(&cxl_nvdimm_bridge_ida, cxl_nvb->id);
     31	kfree(cxl_nvb);
     32}
     33
     34static const struct attribute_group *cxl_nvdimm_bridge_attribute_groups[] = {
     35	&cxl_base_attribute_group,
     36	NULL,
     37};
     38
     39const struct device_type cxl_nvdimm_bridge_type = {
     40	.name = "cxl_nvdimm_bridge",
     41	.release = cxl_nvdimm_bridge_release,
     42	.groups = cxl_nvdimm_bridge_attribute_groups,
     43};
     44
     45struct cxl_nvdimm_bridge *to_cxl_nvdimm_bridge(struct device *dev)
     46{
     47	if (dev_WARN_ONCE(dev, dev->type != &cxl_nvdimm_bridge_type,
     48			  "not a cxl_nvdimm_bridge device\n"))
     49		return NULL;
     50	return container_of(dev, struct cxl_nvdimm_bridge, dev);
     51}
     52EXPORT_SYMBOL_NS_GPL(to_cxl_nvdimm_bridge, CXL);
     53
     54bool is_cxl_nvdimm_bridge(struct device *dev)
     55{
     56	return dev->type == &cxl_nvdimm_bridge_type;
     57}
     58EXPORT_SYMBOL_NS_GPL(is_cxl_nvdimm_bridge, CXL);
     59
     60static int match_nvdimm_bridge(struct device *dev, void *data)
     61{
     62	return is_cxl_nvdimm_bridge(dev);
     63}
     64
     65struct cxl_nvdimm_bridge *cxl_find_nvdimm_bridge(struct cxl_nvdimm *cxl_nvd)
     66{
     67	struct cxl_port *port = find_cxl_root(&cxl_nvd->dev);
     68	struct device *dev;
     69
     70	if (!port)
     71		return NULL;
     72
     73	dev = device_find_child(&port->dev, NULL, match_nvdimm_bridge);
     74	put_device(&port->dev);
     75
     76	if (!dev)
     77		return NULL;
     78
     79	return to_cxl_nvdimm_bridge(dev);
     80}
     81EXPORT_SYMBOL_NS_GPL(cxl_find_nvdimm_bridge, CXL);
     82
     83static struct lock_class_key cxl_nvdimm_bridge_key;
     84
     85static struct cxl_nvdimm_bridge *cxl_nvdimm_bridge_alloc(struct cxl_port *port)
     86{
     87	struct cxl_nvdimm_bridge *cxl_nvb;
     88	struct device *dev;
     89	int rc;
     90
     91	cxl_nvb = kzalloc(sizeof(*cxl_nvb), GFP_KERNEL);
     92	if (!cxl_nvb)
     93		return ERR_PTR(-ENOMEM);
     94
     95	rc = ida_alloc(&cxl_nvdimm_bridge_ida, GFP_KERNEL);
     96	if (rc < 0)
     97		goto err;
     98	cxl_nvb->id = rc;
     99
    100	dev = &cxl_nvb->dev;
    101	cxl_nvb->port = port;
    102	cxl_nvb->state = CXL_NVB_NEW;
    103	device_initialize(dev);
    104	lockdep_set_class(&dev->mutex, &cxl_nvdimm_bridge_key);
    105	device_set_pm_not_required(dev);
    106	dev->parent = &port->dev;
    107	dev->bus = &cxl_bus_type;
    108	dev->type = &cxl_nvdimm_bridge_type;
    109
    110	return cxl_nvb;
    111
    112err:
    113	kfree(cxl_nvb);
    114	return ERR_PTR(rc);
    115}
    116
    117static void unregister_nvb(void *_cxl_nvb)
    118{
    119	struct cxl_nvdimm_bridge *cxl_nvb = _cxl_nvb;
    120	bool flush;
    121
    122	/*
    123	 * If the bridge was ever activated then there might be in-flight state
    124	 * work to flush. Once the state has been changed to 'dead' then no new
    125	 * work can be queued by user-triggered bind.
    126	 */
    127	device_lock(&cxl_nvb->dev);
    128	flush = cxl_nvb->state != CXL_NVB_NEW;
    129	cxl_nvb->state = CXL_NVB_DEAD;
    130	device_unlock(&cxl_nvb->dev);
    131
    132	/*
    133	 * Even though the device core will trigger device_release_driver()
    134	 * before the unregister, it does not know about the fact that
    135	 * cxl_nvdimm_bridge_driver defers ->remove() work. So, do the driver
    136	 * release not and flush it before tearing down the nvdimm device
    137	 * hierarchy.
    138	 */
    139	device_release_driver(&cxl_nvb->dev);
    140	if (flush)
    141		flush_work(&cxl_nvb->state_work);
    142	device_unregister(&cxl_nvb->dev);
    143}
    144
    145/**
    146 * devm_cxl_add_nvdimm_bridge() - add the root of a LIBNVDIMM topology
    147 * @host: platform firmware root device
    148 * @port: CXL port at the root of a CXL topology
    149 *
    150 * Return: bridge device that can host cxl_nvdimm objects
    151 */
    152struct cxl_nvdimm_bridge *devm_cxl_add_nvdimm_bridge(struct device *host,
    153						     struct cxl_port *port)
    154{
    155	struct cxl_nvdimm_bridge *cxl_nvb;
    156	struct device *dev;
    157	int rc;
    158
    159	if (!IS_ENABLED(CONFIG_CXL_PMEM))
    160		return ERR_PTR(-ENXIO);
    161
    162	cxl_nvb = cxl_nvdimm_bridge_alloc(port);
    163	if (IS_ERR(cxl_nvb))
    164		return cxl_nvb;
    165
    166	dev = &cxl_nvb->dev;
    167	rc = dev_set_name(dev, "nvdimm-bridge%d", cxl_nvb->id);
    168	if (rc)
    169		goto err;
    170
    171	rc = device_add(dev);
    172	if (rc)
    173		goto err;
    174
    175	rc = devm_add_action_or_reset(host, unregister_nvb, cxl_nvb);
    176	if (rc)
    177		return ERR_PTR(rc);
    178
    179	return cxl_nvb;
    180
    181err:
    182	put_device(dev);
    183	return ERR_PTR(rc);
    184}
    185EXPORT_SYMBOL_NS_GPL(devm_cxl_add_nvdimm_bridge, CXL);
    186
    187static void cxl_nvdimm_release(struct device *dev)
    188{
    189	struct cxl_nvdimm *cxl_nvd = to_cxl_nvdimm(dev);
    190
    191	kfree(cxl_nvd);
    192}
    193
    194static const struct attribute_group *cxl_nvdimm_attribute_groups[] = {
    195	&cxl_base_attribute_group,
    196	NULL,
    197};
    198
    199const struct device_type cxl_nvdimm_type = {
    200	.name = "cxl_nvdimm",
    201	.release = cxl_nvdimm_release,
    202	.groups = cxl_nvdimm_attribute_groups,
    203};
    204
    205bool is_cxl_nvdimm(struct device *dev)
    206{
    207	return dev->type == &cxl_nvdimm_type;
    208}
    209EXPORT_SYMBOL_NS_GPL(is_cxl_nvdimm, CXL);
    210
    211struct cxl_nvdimm *to_cxl_nvdimm(struct device *dev)
    212{
    213	if (dev_WARN_ONCE(dev, !is_cxl_nvdimm(dev),
    214			  "not a cxl_nvdimm device\n"))
    215		return NULL;
    216	return container_of(dev, struct cxl_nvdimm, dev);
    217}
    218EXPORT_SYMBOL_NS_GPL(to_cxl_nvdimm, CXL);
    219
    220static struct lock_class_key cxl_nvdimm_key;
    221
    222static struct cxl_nvdimm *cxl_nvdimm_alloc(struct cxl_memdev *cxlmd)
    223{
    224	struct cxl_nvdimm *cxl_nvd;
    225	struct device *dev;
    226
    227	cxl_nvd = kzalloc(sizeof(*cxl_nvd), GFP_KERNEL);
    228	if (!cxl_nvd)
    229		return ERR_PTR(-ENOMEM);
    230
    231	dev = &cxl_nvd->dev;
    232	cxl_nvd->cxlmd = cxlmd;
    233	device_initialize(dev);
    234	lockdep_set_class(&dev->mutex, &cxl_nvdimm_key);
    235	device_set_pm_not_required(dev);
    236	dev->parent = &cxlmd->dev;
    237	dev->bus = &cxl_bus_type;
    238	dev->type = &cxl_nvdimm_type;
    239
    240	return cxl_nvd;
    241}
    242
    243static void cxl_nvd_unregister(void *dev)
    244{
    245	device_unregister(dev);
    246}
    247
    248/**
    249 * devm_cxl_add_nvdimm() - add a bridge between a cxl_memdev and an nvdimm
    250 * @host: same host as @cxlmd
    251 * @cxlmd: cxl_memdev instance that will perform LIBNVDIMM operations
    252 *
    253 * Return: 0 on success negative error code on failure.
    254 */
    255int devm_cxl_add_nvdimm(struct device *host, struct cxl_memdev *cxlmd)
    256{
    257	struct cxl_nvdimm *cxl_nvd;
    258	struct device *dev;
    259	int rc;
    260
    261	cxl_nvd = cxl_nvdimm_alloc(cxlmd);
    262	if (IS_ERR(cxl_nvd))
    263		return PTR_ERR(cxl_nvd);
    264
    265	dev = &cxl_nvd->dev;
    266	rc = dev_set_name(dev, "pmem%d", cxlmd->id);
    267	if (rc)
    268		goto err;
    269
    270	rc = device_add(dev);
    271	if (rc)
    272		goto err;
    273
    274	dev_dbg(host, "%s: register %s\n", dev_name(dev->parent),
    275		dev_name(dev));
    276
    277	return devm_add_action_or_reset(host, cxl_nvd_unregister, dev);
    278
    279err:
    280	put_device(dev);
    281	return rc;
    282}
    283EXPORT_SYMBOL_NS_GPL(devm_cxl_add_nvdimm, CXL);