dax/kmem: extract hotplug/hotremove helper functions

Refactor kmem _probe() _remove() by extracting init, cleanup, hotplug, and
hot-remove logic into separate helper functions:

  - dax_kmem_init_resources: inits IO_RESOURCE w/ request_mem_region
  - dax_kmem_cleanup_resources: cleans up initialized IO_RESOURCE
  - dax_kmem_do_hotplug: handles memory region reservation and adding
  - dax_kmem_do_hotremove: handles memory removal and resource cleanup

This is a pure refactoring with no functional change.  The helpers will
enable future extensions to support more granular control over memory
hotplug operations.

We need to split hotplug/hotunplug and init/cleanup in order to have the
resources available for hot-add.  Otherwise, when probe occurs, the dax
devices are never added to sysfs because the resources are never
registered.

Detaching hotunplug/cleanup allows us to re-use the hotunplug code without
destroying the underlying resources.

Link: https://lore.kernel.org/20260712154505.3564379-9-gourry@gourry.net
Signed-off-by: Gregory Price <gourry@gourry.net>
Reviewed-by: Dave Jiang <dave.jiang@intel.com>
Cc: Alison Schofield <alison.schofield@intel.com>
Cc: Danilo Krummrich <dakr@kernel.org>
Cc: David Hildenbrand (Arm) <david@kernel.org>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Hannes Reinecke <hare@suse.de>
Cc: Liam R. Howlett <liam@infradead.org>
Cc: Lorenzo Stoakes <ljs@kernel.org>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Mike Rapoport <rppt@kernel.org>
Cc: Oscar Salvador <osalvador@suse.de>
Cc: Pankaj Gupta <pankaj.gupta@amd.com>
Cc: "Rafael J. Wysocki" <rafael@kernel.org>
Cc: Shuah Khan <shuah@kernel.org>
Cc: Suren Baghdasaryan <surenb@google.com>
Cc: Vishal Verma <vishal.l.verma@intel.com>
Cc: Vlastimil Babka <vbabka@kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
This commit is contained in:
Gregory Price 2026-07-12 11:45:02 -04:00 committed by Andrew Morton
parent 496a4cd658
commit bb76d369b6

View File

@ -63,14 +63,206 @@ static void kmem_put_memory_types(void)
mt_put_memory_types(&kmem_memory_types);
}
/**
* dax_kmem_do_hotplug - hotplug memory for dax kmem device
* @dev_dax: the dev_dax instance
* @data: the dax_kmem_data structure with resource tracking
*
* Hotplugs all ranges in the dev_dax region as system memory.
*
* Returns the number of successfully mapped ranges, or negative error.
*/
static int dax_kmem_do_hotplug(struct dev_dax *dev_dax,
struct dax_kmem_data *data,
int online_type)
{
struct device *dev = &dev_dax->dev;
int i, rc, onlined = 0;
mhp_t mhp_flags;
for (i = 0; i < dev_dax->nr_range; i++) {
struct range range;
rc = dax_kmem_range(dev_dax, i, &range);
if (rc)
continue;
/*
* init_resources() is best-effort: if a reservation conflict
* occurs it keeps the range but leaves res[i]=NULL. For hotplug
* on probe systems, this means kmem will partially online.
*
* We have to keep this behavior not to break those systems.
* For those systems - atomicity only applies to valid ranges.
*/
if (!data->res[i])
continue;
mhp_flags = MHP_NID_IS_MGID;
if (dev_dax->memmap_on_memory)
mhp_flags |= MHP_MEMMAP_ON_MEMORY;
/*
* Ensure that future kexec'd kernels will not treat
* this as RAM automatically.
*/
rc = __add_memory_driver_managed(data->mgid, range.start,
range_len(&range), kmem_name, mhp_flags,
online_type);
if (rc) {
dev_warn(dev, "mapping%d: %#llx-%#llx memory add failed\n",
i, range.start, range.end);
/*
* Release the reservation for the range that failed to
* add so a later hotremove does not try to remove memory
* that was never added.
*/
if (data->res[i]) {
remove_resource(data->res[i]);
kfree(data->res[i]);
data->res[i] = NULL;
}
if (onlined)
continue;
return rc;
}
onlined++;
}
return onlined;
}
/**
* dax_kmem_init_resources - create memory regions for dax kmem
* @dev_dax: the dev_dax instance
* @data: the dax_kmem_data structure with resource tracking
*
* Initializes all the resources for the DAX
*
* Returns the number of successfully mapped ranges, or negative error.
*/
static int dax_kmem_init_resources(struct dev_dax *dev_dax,
struct dax_kmem_data *data)
{
struct device *dev = &dev_dax->dev;
int i, rc, mapped = 0;
for (i = 0; i < dev_dax->nr_range; i++) {
struct resource *res;
struct range range;
rc = dax_kmem_range(dev_dax, i, &range);
if (rc)
continue;
/* Skip ranges already added */
if (data->res[i])
continue;
/* Region is permanently reserved if hotremove fails. */
res = request_mem_region(range.start, range_len(&range),
data->res_name);
if (!res) {
dev_warn(dev, "mapping%d: %#llx-%#llx could not reserve region\n",
i, range.start, range.end);
/*
* Once some memory has been onlined we can't
* assume that it can be un-onlined safely.
*/
if (mapped)
continue;
return -EBUSY;
}
data->res[i] = res;
/*
* Set flags appropriate for System RAM. Leave ..._BUSY clear
* so that add_memory() can add a child resource. Do not
* inherit flags from the parent since it may set new flags
* unknown to us that will break add_memory() later.
*/
res->flags = IORESOURCE_SYSTEM_RAM;
mapped++;
}
return mapped;
}
#ifdef CONFIG_MEMORY_HOTREMOVE
/**
* dax_kmem_do_hotremove - hot-remove memory for dax kmem device
* @dev_dax: the dev_dax instance
* @data: the dax_kmem_data structure with resource tracking
*
* Removes all ranges in the dev_dax region.
*
* Returns the number of successfully removed ranges.
*/
static int dax_kmem_do_hotremove(struct dev_dax *dev_dax,
struct dax_kmem_data *data)
{
struct device *dev = &dev_dax->dev;
int i, success = 0;
for (i = 0; i < dev_dax->nr_range; i++) {
struct range range;
int rc;
rc = dax_kmem_range(dev_dax, i, &range);
if (rc)
continue;
/* range was never added during probe, count as removed */
if (!data->res[i]) {
success++;
continue;
}
rc = remove_memory(range.start, range_len(&range));
if (rc == 0) {
/* Release the resource for the successfully removed range */
remove_resource(data->res[i]);
kfree(data->res[i]);
data->res[i] = NULL;
success++;
continue;
}
any_hotremove_failed = true;
dev_err(dev, "mapping%d: %#llx-%#llx hotremove failed\n",
i, range.start, range.end);
}
return success;
}
#endif /* CONFIG_MEMORY_HOTREMOVE */
/**
* dax_kmem_cleanup_resources - remove the dax memory resources
* @dev_dax: the dev_dax instance
* @data: the dax_kmem_data structure with resource tracking
*
* Removes all resources in the dev_dax region.
*/
static void dax_kmem_cleanup_resources(struct dev_dax *dev_dax,
struct dax_kmem_data *data)
{
int i;
for (i = 0; i < dev_dax->nr_range; i++) {
if (!data->res[i])
continue;
remove_resource(data->res[i]);
kfree(data->res[i]);
data->res[i] = NULL;
}
}
static int dev_dax_kmem_probe(struct dev_dax *dev_dax)
{
struct device *dev = &dev_dax->dev;
unsigned long total_len = 0, orig_len = 0;
struct dax_kmem_data *data;
struct memory_dev_type *mtype;
int i, rc, mapped = 0;
mhp_t mhp_flags;
int i, rc;
int numa_node;
int adist = MEMTIER_DEFAULT_DAX_ADISTANCE;
int online_type = mhp_get_default_online_type();
@ -133,68 +325,22 @@ static int dev_dax_kmem_probe(struct dev_dax *dev_dax)
goto err_reg_mgid;
data->mgid = rc;
for (i = 0; i < dev_dax->nr_range; i++) {
struct resource *res;
struct range range;
rc = dax_kmem_range(dev_dax, i, &range);
if (rc)
continue;
/* Region is permanently reserved if hotremove fails. */
res = request_mem_region(range.start, range_len(&range), data->res_name);
if (!res) {
dev_warn(dev, "mapping%d: %#llx-%#llx could not reserve region\n",
i, range.start, range.end);
/*
* Once some memory has been onlined we can't
* assume that it can be un-onlined safely.
*/
if (mapped)
continue;
rc = -EBUSY;
goto err_request_mem;
}
data->res[i] = res;
/*
* Set flags appropriate for System RAM. Leave ..._BUSY clear
* so that add_memory() can add a child resource. Do not
* inherit flags from the parent since it may set new flags
* unknown to us that will break add_memory() below.
*/
res->flags = IORESOURCE_SYSTEM_RAM;
mhp_flags = MHP_NID_IS_MGID;
if (dev_dax->memmap_on_memory)
mhp_flags |= MHP_MEMMAP_ON_MEMORY;
/*
* Ensure that future kexec'd kernels will not treat
* this as RAM automatically.
*/
rc = __add_memory_driver_managed(data->mgid, range.start,
range_len(&range), kmem_name, mhp_flags,
online_type);
if (rc) {
dev_warn(dev, "mapping%d: %#llx-%#llx memory add failed\n",
i, range.start, range.end);
remove_resource(res);
kfree(res);
data->res[i] = NULL;
if (mapped)
continue;
goto err_request_mem;
}
mapped++;
}
dev_set_drvdata(dev, data);
rc = dax_kmem_init_resources(dev_dax, data);
if (rc < 0)
goto err_resources;
rc = dax_kmem_do_hotplug(dev_dax, data, online_type);
if (rc < 0)
goto err_hotplug;
return 0;
err_request_mem:
err_hotplug:
dax_kmem_cleanup_resources(dev_dax, data);
err_resources:
dev_set_drvdata(dev, NULL);
memory_group_unregister(data->mgid);
err_reg_mgid:
kfree(data->res_name);
@ -208,7 +354,7 @@ static int dev_dax_kmem_probe(struct dev_dax *dev_dax)
#ifdef CONFIG_MEMORY_HOTREMOVE
static void dev_dax_kmem_remove(struct dev_dax *dev_dax)
{
int i, success = 0;
int success;
int node = dev_dax->target_node;
struct device *dev = &dev_dax->dev;
struct dax_kmem_data *data = dev_get_drvdata(dev);
@ -219,48 +365,25 @@ static void dev_dax_kmem_remove(struct dev_dax *dev_dax)
* there is no way to hotremove this memory until reboot because device
* unbind will succeed even if we return failure.
*/
for (i = 0; i < dev_dax->nr_range; i++) {
struct range range;
int rc;
rc = dax_kmem_range(dev_dax, i, &range);
if (rc)
continue;
/* range was never added during probe */
if (!data->res[i]) {
success++;
continue;
}
rc = remove_memory(range.start, range_len(&range));
if (rc == 0) {
remove_resource(data->res[i]);
kfree(data->res[i]);
data->res[i] = NULL;
success++;
continue;
}
any_hotremove_failed = true;
dev_err(dev,
"mapping%d: %#llx-%#llx cannot be hotremoved until the next reboot\n",
i, range.start, range.end);
success = dax_kmem_do_hotremove(dev_dax, data);
if (success < dev_dax->nr_range) {
dev_err(dev, "Hotplug regions stuck online until reboot\n");
return;
}
if (success >= dev_dax->nr_range) {
memory_group_unregister(data->mgid);
kfree(data->res_name);
kfree(data);
dev_set_drvdata(dev, NULL);
/*
* Clear the memtype association on successful unplug.
* If not, we have memory blocks left which can be
* offlined/onlined later. We need to keep memory_dev_type
* for that. This implies this reference will be around
* till next reboot.
*/
clear_node_memory_type(node, NULL);
}
dax_kmem_cleanup_resources(dev_dax, data);
memory_group_unregister(data->mgid);
kfree(data->res_name);
kfree(data);
dev_set_drvdata(dev, NULL);
/*
* Clear the memtype association on successful unplug.
* If not, we have memory blocks left which can be
* offlined/onlined later. We need to keep memory_dev_type
* for that. This implies this reference will be around
* till next reboot.
*/
clear_node_memory_type(node, NULL);
}
#else
static void dev_dax_kmem_remove(struct dev_dax *dev_dax)