Linux Audio

Check our new training course

Embedded Linux training

Mar 10-20, 2025, special US time zones
Register
Loading...
Note: File does not exist in v5.9.
  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);