// SPDX-License-Identifier: GPL-2.0
/*
* This is a module to test the HMM (Heterogeneous Memory Management)
* mirror and zone device private memory migration APIs of the kernel.
* Userspace programs can register with the driver to mirror their own address
* space and can use the device to read/write any valid virtual address.
*/
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/memremap.h>
#include <linux/mutex.h>
#include <linux/rwsem.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/highmem.h>
#include <linux/delay.h>
#include <linux/pagemap.h>
#include <linux/hmm.h>
#include <linux/vmalloc.h>
#include <linux/swap.h>
#include <linux/swapops.h>
#include <linux/sched/mm.h>
#include <linux/platform_device.h>
#include <linux/rmap.h>
#include <linux/mmu_notifier.h>
#include <linux/migrate.h>
#include "test_hmm_uapi.h"
#define DMIRROR_NDEVICES 4
#define DMIRROR_RANGE_FAULT_TIMEOUT 1000
#define DEVMEM_CHUNK_SIZE (256 * 1024 * 1024U)
#define DEVMEM_CHUNKS_RESERVE 16
/*
* For device_private pages, dpage is just a dummy struct page
* representing a piece of device memory. dmirror_devmem_alloc_page
* allocates a real system memory page as backing storage to fake a
* real device. zone_device_data points to that backing page. But
* for device_coherent memory, the struct page represents real
* physical CPU-accessible memory that we can use directly.
*/
#define BACKING_PAGE(page) (is_device_private_page((page)) ? \
(page)->zone_device_data : (page))
static unsigned long spm_addr_dev0;
module_param(spm_addr_dev0, long, 0644);
MODULE_PARM_DESC(spm_addr_dev0,
"Specify start address for SPM (special purpose memory) used for device 0. By setting this Coherent device type will be used. Make sure spm_addr_dev1 is set too. Minimum SPM size should be DEVMEM_CHUNK_SIZE.");
static unsigned long spm_addr_dev1;
module_param(spm_addr_dev1, long, 0644);
MODULE_PARM_DESC(spm_addr_dev1,
"Specify start address for SPM (special purpose memory) used for device 1. By setting this Coherent device type will be used. Make sure spm_addr_dev0 is set too. Minimum SPM size should be DEVMEM_CHUNK_SIZE.");
static const struct dev_pagemap_ops dmirror_devmem_ops;
static const struct mmu_interval_notifier_ops dmirror_min_ops;
static dev_t dmirror_dev;
struct dmirror_device;
struct dmirror_bounce {
void *ptr;
unsigned long size;
unsigned long addr;
unsigned long cpages;
};
#define DPT_XA_TAG_ATOMIC 1UL
#define DPT_XA_TAG_WRITE 3UL
/*
* Data structure to track address ranges and register for mmu interval
* notifier updates.
*/
struct dmirror_interval {
struct mmu_interval_notifier notifier;
struct dmirror *dmirror;
};
/*
* Data attached to the open device file.
* Note that it might be shared after a fork().
*/
struct dmirror {
struct dmirror_device *mdevice;
struct xarray pt;
struct mmu_interval_notifier notifier;
struct mutex mutex;
};
/*
* ZONE_DEVICE pages for migration and simulating device memory.
*/
struct dmirror_chunk {
struct dev_pagemap pagemap;
struct dmirror_device *mdevice;
bool remove;
};
/*
* Per device data.
*/
struct dmirror_device {
struct cdev cdevice;
unsigned int zone_device_type;
struct device device;
unsigned int devmem_capacity;
unsigned int devmem_count;
struct dmirror_chunk **devmem_chunks;
struct mutex devmem_lock; /* protects the above */
unsigned long calloc;
unsigned long cfree;
struct page *free_pages;
spinlock_t lock; /* protects the above */
};
static struct dmirror_device dmirror_devices[DMIRROR_NDEVICES];
static int dmirror_bounce_init(struct dmirror_bounce *bounce,
unsigned long addr,
unsigned long size)
{
bounce->addr = addr;
bounce->size = size;
bounce->cpages = 0;
bounce->ptr = vmalloc(size);
if (!bounce->ptr)
return -ENOMEM;
return 0;
}
static bool dmirror_is_private_zone(struct dmirror_device *mdevice)
{
return (mdevice->zone_device_type ==
HMM_DMIRROR_MEMORY_DEVICE_PRIVATE) ? true : false;
}
static enum migrate_vma_direction
dmirror_select_device(struct dmirror *dmirror)
{
return (dmirror->mdevice->zone_device_type ==
HMM_DMIRROR_MEMORY_DEVICE_PRIVATE) ?
MIGRATE_VMA_SELECT_DEVICE_PRIVATE :
MIGRATE_VMA_SELECT_DEVICE_COHERENT;
}
static void dmirror_bounce_fini(struct dmirror_bounce *bounce)
{
vfree(bounce->ptr);
}
static int dmirror_fops_open(struct inode *inode, struct file *filp)
{
struct cdev *cdev = inode->i_cdev;
struct dmirror *dmirror;
int ret;
/* Mirror this process address space */
dmirror = kzalloc(sizeof(*dmirror), GFP_KERNEL);
if (dmirror == NULL)
return -ENOMEM;
dmirror->mdevice = container_of(cdev, struct dmirror_device, cdevice);
mutex_init(&dmirror->mutex);
xa_init(&dmirror->pt);
ret = mmu_interval_notifier_insert(&dmirror->notifier, current->mm,
0, ULONG_MAX & PAGE_MASK, &dmirror_min_ops);
if (ret) {
kfree(dmirror);
return ret;
}
filp->private_data = dmirror;
return 0;
}
static int dmirror_fops_release(struct inode *inode, struct file *filp)
{
struct dmirror *dmirror = filp->private_data;
mmu_interval_notifier_remove(&dmirror->notifier);
xa_destroy(&dmirror->pt);
kfree(dmirror);
return 0;
}
static struct dmirror_chunk *dmirror_page_to_chunk(struct page *page)
{
return container_of(page->pgmap, struct dmirror_chunk, pagemap);
}
static struct dmirror_device *dmirror_page_to_device(struct page *page)
{
return dmirror_page_to_chunk(page)->mdevice;
}
static int dmirror_do_fault(struct dmirror *dmirror, struct hmm_range *range)
{
unsigned long *pfns = range->hmm_pfns;
unsigned long pfn;
for (pfn = (range->start >> PAGE_SHIFT);
pfn < (range->end >> PAGE_SHIFT);
pfn++, pfns++) {
struct page *page;
void *entry;
/*
* Since we asked for hmm_range_fault() to populate pages,
* it shouldn't return an error entry on success.
*/
WARN_ON(*pfns & HMM_PFN_ERROR);
WARN_ON(!(*pfns & HMM_PFN_VALID));
page = hmm_pfn_to_page(*pfns);
WARN_ON(!page);
entry = page;
if (*pfns & HMM_PFN_WRITE)
entry = xa_tag_pointer(entry, DPT_XA_TAG_WRITE);
else if (WARN_ON(range->default_flags & HMM_PFN_WRITE))
return -EFAULT;
entry = xa_store(&dmirror->pt, pfn, entry, GFP_ATOMIC);
if (xa_is_err(entry))
return xa_err(entry);
}
return 0;
}
static void dmirror_do_update(struct dmirror *dmirror, unsigned long start,
unsigned long end)
{
unsigned long pfn;
void *entry;
/*
* The XArray doesn't hold references to pages since it relies on
|