staticbool force_bbm;
module_param(force_bbm, bool, 0444);
MODULE_PARM_DESC(force_bbm, "Force Big Block Mode. Default is 0 (auto-selection)");
staticunsignedlong bbm_block_size;
module_param(bbm_block_size, ulong, 0444);
MODULE_PARM_DESC(bbm_block_size, "Big Block size in bytes. Default is 0 (auto-detection).");
/* * virtio-mem currently supports the following modes of operation: * * * Sub Block Mode (SBM): A Linux memory block spans 2..X subblocks (SB). The * size of a Sub Block (SB) is determined based on the device block size, the * pageblock size, and the maximum allocation granularity of the buddy. * Subblocks within a Linux memory block might either be plugged or unplugged. * Memory is added/removed to Linux MM in Linux memory block granularity. * * * Big Block Mode (BBM): A Big Block (BB) spans 1..X Linux memory blocks. * Memory is added/removed to Linux MM in Big Block granularity. * * The mode is determined automatically based on the Linux memory block size * and the device block size. * * User space / core MM (auto onlining) is responsible for onlining added * Linux memory blocks - and for selecting a zone. Linux Memory Blocks are * always onlined separately, and all memory within a Linux memory block is * onlined to the same zone - virtio-mem relies on this behavior.
*/
/* * State of a Linux memory block in SBM.
*/ enum virtio_mem_sbm_mb_state { /* Unplugged, not added to Linux. Can be reused later. */
VIRTIO_MEM_SBM_MB_UNUSED = 0, /* (Partially) plugged, not added to Linux. Error on add_memory(). */
VIRTIO_MEM_SBM_MB_PLUGGED, /* Fully plugged, fully added to Linux, offline. */
VIRTIO_MEM_SBM_MB_OFFLINE, /* Partially plugged, fully added to Linux, offline. */
VIRTIO_MEM_SBM_MB_OFFLINE_PARTIAL, /* Fully plugged, fully added to Linux, onlined to a kernel zone. */
VIRTIO_MEM_SBM_MB_KERNEL, /* Partially plugged, fully added to Linux, online to a kernel zone */
VIRTIO_MEM_SBM_MB_KERNEL_PARTIAL, /* Fully plugged, fully added to Linux, onlined to ZONE_MOVABLE. */
VIRTIO_MEM_SBM_MB_MOVABLE, /* Partially plugged, fully added to Linux, onlined to ZONE_MOVABLE. */
VIRTIO_MEM_SBM_MB_MOVABLE_PARTIAL,
VIRTIO_MEM_SBM_MB_COUNT
};
/* * State of a Big Block (BB) in BBM, covering 1..X Linux memory blocks.
*/ enum virtio_mem_bbm_bb_state { /* Unplugged, not added to Linux. Can be reused later. */
VIRTIO_MEM_BBM_BB_UNUSED = 0, /* Plugged, not added to Linux. Error on add_memory(). */
VIRTIO_MEM_BBM_BB_PLUGGED, /* Plugged and added to Linux. */
VIRTIO_MEM_BBM_BB_ADDED, /* All online parts are fake-offline, ready to remove. */
VIRTIO_MEM_BBM_BB_FAKE_OFFLINE,
VIRTIO_MEM_BBM_BB_COUNT
};
struct virtio_mem { struct virtio_device *vdev;
/* We might first have to unplug all memory when starting up. */ bool unplug_all_required;
/* Workqueue that processes the plug/unplug requests. */ struct work_struct wq;
atomic_t wq_active;
atomic_t config_changed;
/* Virtqueue for guest->host requests. */ struct virtqueue *vq;
/* Wait for a host response to a guest request. */
wait_queue_head_t host_resp;
/* Space for one guest request and the host response. */ struct virtio_mem_req req; struct virtio_mem_resp resp;
/* The current size of the device. */
uint64_t plugged_size; /* The requested size of the device. */
uint64_t requested_size;
/* The device block size (for communicating with the device). */
uint64_t device_block_size; /* The determined node id for all memory of the device. */ int nid; /* Physical start address of the memory region. */
uint64_t addr; /* Maximum region size in bytes. */
uint64_t region_size; /* Usable region size in bytes. */
uint64_t usable_region_size;
/* The parent resource for all memory added via this device. */ struct resource *parent_resource; /* * Copy of "System RAM (virtio_mem)" to be used for * add_memory_driver_managed().
*/ constchar *resource_name; /* Memory group identification. */ int mgid;
/* * We don't want to add too much memory if it's not getting onlined, * to avoid running OOM. Besides this threshold, we allow to have at * least two offline blocks at a time (whatever is bigger).
*/ #define VIRTIO_MEM_DEFAULT_OFFLINE_THRESHOLD (1024 * 1024 * 1024)
atomic64_t offline_size;
uint64_t offline_threshold;
/* If set, the driver is in SBM, otherwise in BBM. */ bool in_sbm;
union { struct { /* Id of the first memory block of this device. */ unsignedlong first_mb_id; /* Id of the last usable memory block of this device. */ unsignedlong last_usable_mb_id; /* Id of the next memory bock to prepare when needed. */ unsignedlong next_mb_id;
/* The subblock size. */
uint64_t sb_size; /* The number of subblocks per Linux memory block. */
uint32_t sbs_per_mb;
/* * Some of the Linux memory blocks tracked as "partially * plugged" are completely unplugged and can be offlined * and removed -- which previously failed.
*/ bool have_unplugged_mb;
/* Summary of all memory block states. */ unsignedlong mb_count[VIRTIO_MEM_SBM_MB_COUNT];
/* * One byte state per memory block. Allocated via * vmalloc(). Resized (alloc+copy+free) on demand. * * With 128 MiB memory blocks, we have states for 512 * GiB of memory in one 4 KiB page.
*/
uint8_t *mb_states;
/* * Bitmap: one bit per subblock. Allocated similar to * sbm.mb_states. * * A set bit means the corresponding subblock is * plugged, otherwise it's unblocked. * * With 4 MiB subblocks, we manage 128 GiB of memory * in one 4 KiB page.
*/ unsignedlong *sb_states;
} sbm;
struct { /* Id of the first big block of this device. */ unsignedlong first_bb_id; /* Id of the last usable big block of this device. */ unsignedlong last_usable_bb_id; /* Id of the next device bock to prepare when needed. */ unsignedlong next_bb_id;
/* Summary of all big block states. */ unsignedlong bb_count[VIRTIO_MEM_BBM_BB_COUNT];
/* One byte state per big block. See sbm.mb_states. */
uint8_t *bb_states;
/* The block size used for plugging/adding/removing. */
uint64_t bb_size;
} bbm;
};
/* * Mutex that protects the sbm.mb_count, sbm.mb_states, * sbm.sb_states, bbm.bb_count, and bbm.bb_states * * When this lock is held the pointers can't change, ONLINE and * OFFLINE blocks can't change the state and no subblocks will get * plugged/unplugged. * * In kdump mode, used to serialize requests, last_block_addr and * last_block_plugged.
*/ struct mutex hotplug_mutex; bool hotplug_active;
/* An error occurred we cannot handle - stop processing requests. */ bool broken;
/* Cached valued of is_kdump_kernel() when the device was probed. */ bool in_kdump;
/* The driver is being removed. */
spinlock_t removal_lock; bool removing;
/* Timer for retrying to plug/unplug memory. */ struct hrtimer retry_timer; unsignedint retry_timer_ms; #define VIRTIO_MEM_RETRY_TIMER_MIN_MS 50000 #define VIRTIO_MEM_RETRY_TIMER_MAX_MS 300000
/* Next device in the list of virtio-mem devices. */ struct list_head next;
};
/* * We have to share a single online_page callback among all virtio-mem * devices. We use RCU to iterate the list in the callback.
*/ static DEFINE_MUTEX(virtio_mem_mutex); static LIST_HEAD(virtio_mem_devices);
/* * Register a virtio-mem device so it will be considered for the online_page * callback.
*/ staticint register_virtio_mem_device(struct virtio_mem *vm)
{ int rc = 0;
/* First device registers the callback. */
mutex_lock(&virtio_mem_mutex); if (list_empty(&virtio_mem_devices))
rc = set_online_page_callback(&virtio_mem_online_page_cb); if (!rc)
list_add_rcu(&vm->next, &virtio_mem_devices);
mutex_unlock(&virtio_mem_mutex);
return rc;
}
/* * Unregister a virtio-mem device so it will no longer be considered for the * online_page callback.
*/ staticvoid unregister_virtio_mem_device(struct virtio_mem *vm)
{ /* Last device unregisters the callback. */
mutex_lock(&virtio_mem_mutex);
list_del_rcu(&vm->next); if (list_empty(&virtio_mem_devices))
restore_online_page_callback(&virtio_mem_online_page_cb);
mutex_unlock(&virtio_mem_mutex);
synchronize_rcu();
}
/* * Calculate the memory block id of a given address.
*/ staticunsignedlong virtio_mem_phys_to_mb_id(unsignedlong addr)
{ return addr / memory_block_size_bytes();
}
/* * Calculate the physical start address of a given memory block id.
*/ staticunsignedlong virtio_mem_mb_id_to_phys(unsignedlong mb_id)
{ return mb_id * memory_block_size_bytes();
}
/* * Calculate the big block id of a given address.
*/ staticunsignedlong virtio_mem_phys_to_bb_id(struct virtio_mem *vm,
uint64_t addr)
{ return addr / vm->bbm.bb_size;
}
/* * Calculate the physical start address of a given big block id.
*/ static uint64_t virtio_mem_bb_id_to_phys(struct virtio_mem *vm, unsignedlong bb_id)
{ return bb_id * vm->bbm.bb_size;
}
/* * Calculate the subblock id of a given address.
*/ staticunsignedlong virtio_mem_phys_to_sb_id(struct virtio_mem *vm, unsignedlong addr)
{ constunsignedlong mb_id = virtio_mem_phys_to_mb_id(addr); constunsignedlong mb_addr = virtio_mem_mb_id_to_phys(mb_id);
return (addr - mb_addr) / vm->sbm.sb_size;
}
/* * Set the state of a big block, taking care of the state counter.
*/ staticvoid virtio_mem_bbm_set_bb_state(struct virtio_mem *vm, unsignedlong bb_id, enum virtio_mem_bbm_bb_state state)
{ constunsignedlong idx = bb_id - vm->bbm.first_bb_id; enum virtio_mem_bbm_bb_state old_state;
/* * Get the state of a big block.
*/ staticenum virtio_mem_bbm_bb_state virtio_mem_bbm_get_bb_state(struct virtio_mem *vm, unsignedlong bb_id)
{ return vm->bbm.bb_states[bb_id - vm->bbm.first_bb_id];
}
/* * Prepare the big block state array for the next big block.
*/ staticint virtio_mem_bbm_bb_states_prepare_next_bb(struct virtio_mem *vm)
{ unsignedlong old_bytes = vm->bbm.next_bb_id - vm->bbm.first_bb_id; unsignedlong new_bytes = old_bytes + 1; int old_pages = PFN_UP(old_bytes); int new_pages = PFN_UP(new_bytes);
uint8_t *new_array;
if (vm->bbm.bb_states && old_pages == new_pages) return 0;
new_array = vzalloc(new_pages * PAGE_SIZE); if (!new_array) return -ENOMEM;
/* * Set the state of a memory block, taking care of the state counter.
*/ staticvoid virtio_mem_sbm_set_mb_state(struct virtio_mem *vm, unsignedlong mb_id, uint8_t state)
{ constunsignedlong idx = mb_id - vm->sbm.first_mb_id;
uint8_t old_state;
/* * Get the state of a memory block.
*/ static uint8_t virtio_mem_sbm_get_mb_state(struct virtio_mem *vm, unsignedlong mb_id)
{ constunsignedlong idx = mb_id - vm->sbm.first_mb_id;
return vm->sbm.mb_states[idx];
}
/* * Prepare the state array for the next memory block.
*/ staticint virtio_mem_sbm_mb_states_prepare_next_mb(struct virtio_mem *vm)
{ int old_pages = PFN_UP(vm->sbm.next_mb_id - vm->sbm.first_mb_id); int new_pages = PFN_UP(vm->sbm.next_mb_id - vm->sbm.first_mb_id + 1);
uint8_t *new_array;
if (vm->sbm.mb_states && old_pages == new_pages) return 0;
new_array = vzalloc(new_pages * PAGE_SIZE); if (!new_array) return -ENOMEM;
/* * Calculate the bit number in the subblock bitmap for the given subblock * inside the given memory block.
*/ staticint virtio_mem_sbm_sb_state_bit_nr(struct virtio_mem *vm, unsignedlong mb_id, int sb_id)
{ return (mb_id - vm->sbm.first_mb_id) * vm->sbm.sbs_per_mb + sb_id;
}
/* * Mark all selected subblocks plugged. * * Will not modify the state of the memory block.
*/ staticvoid virtio_mem_sbm_set_sb_plugged(struct virtio_mem *vm, unsignedlong mb_id, int sb_id, int count)
{ constint bit = virtio_mem_sbm_sb_state_bit_nr(vm, mb_id, sb_id);
__bitmap_set(vm->sbm.sb_states, bit, count);
}
/* * Mark all selected subblocks unplugged. * * Will not modify the state of the memory block.
*/ staticvoid virtio_mem_sbm_set_sb_unplugged(struct virtio_mem *vm, unsignedlong mb_id, int sb_id, int count)
{ constint bit = virtio_mem_sbm_sb_state_bit_nr(vm, mb_id, sb_id);
__bitmap_clear(vm->sbm.sb_states, bit, count);
}
/* * Test if all selected subblocks are plugged.
*/ staticbool virtio_mem_sbm_test_sb_plugged(struct virtio_mem *vm, unsignedlong mb_id, int sb_id, int count)
{ constint bit = virtio_mem_sbm_sb_state_bit_nr(vm, mb_id, sb_id);
if (count == 1) return test_bit(bit, vm->sbm.sb_states);
/* TODO: Helper similar to bitmap_set() */ return find_next_zero_bit(vm->sbm.sb_states, bit + count, bit) >=
bit + count;
}
/* * Test if all selected subblocks are unplugged.
*/ staticbool virtio_mem_sbm_test_sb_unplugged(struct virtio_mem *vm, unsignedlong mb_id, int sb_id, int count)
{ constint bit = virtio_mem_sbm_sb_state_bit_nr(vm, mb_id, sb_id);
/* TODO: Helper similar to bitmap_set() */ return find_next_bit(vm->sbm.sb_states, bit + count, bit) >=
bit + count;
}
/* * Find the first unplugged subblock. Returns vm->sbm.sbs_per_mb in case there is * none.
*/ staticint virtio_mem_sbm_first_unplugged_sb(struct virtio_mem *vm, unsignedlong mb_id)
{ constint bit = virtio_mem_sbm_sb_state_bit_nr(vm, mb_id, 0);
return find_next_zero_bit(vm->sbm.sb_states,
bit + vm->sbm.sbs_per_mb, bit) - bit;
}
/* * Prepare the subblock bitmap for the next memory block.
*/ staticint virtio_mem_sbm_sb_states_prepare_next_mb(struct virtio_mem *vm)
{ constunsignedlong old_nb_mb = vm->sbm.next_mb_id - vm->sbm.first_mb_id; constunsignedlong old_nb_bits = old_nb_mb * vm->sbm.sbs_per_mb; constunsignedlong new_nb_bits = (old_nb_mb + 1) * vm->sbm.sbs_per_mb; int old_pages = PFN_UP(BITS_TO_LONGS(old_nb_bits) * sizeof(long)); int new_pages = PFN_UP(BITS_TO_LONGS(new_nb_bits) * sizeof(long)); unsignedlong *new_bitmap, *old_bitmap;
if (vm->sbm.sb_states && old_pages == new_pages) return 0;
new_bitmap = vzalloc(new_pages * PAGE_SIZE); if (!new_bitmap) return -ENOMEM;
mutex_lock(&vm->hotplug_mutex); if (vm->sbm.sb_states)
memcpy(new_bitmap, vm->sbm.sb_states, old_pages * PAGE_SIZE);
/* * Test if we could add memory without creating too much offline memory - * to avoid running OOM if memory is getting onlined deferred.
*/ staticbool virtio_mem_could_add_memory(struct virtio_mem *vm, uint64_t size)
{ if (WARN_ON_ONCE(size > vm->offline_threshold)) returnfalse;
/* * Try adding memory to Linux. Will usually only fail if out of memory. * * Must not be called with the vm->hotplug_mutex held (possible deadlock with * onlining code). * * Will not modify the state of memory blocks in virtio-mem.
*/ staticint virtio_mem_add_memory(struct virtio_mem *vm, uint64_t addr,
uint64_t size)
{ int rc;
/* * When force-unloading the driver and we still have memory added to * Linux, the resource name has to stay.
*/ if (!vm->resource_name) {
vm->resource_name = kstrdup_const("System RAM (virtio_mem)",
GFP_KERNEL); if (!vm->resource_name) return -ENOMEM;
}
dev_dbg(&vm->vdev->dev, "adding memory: 0x%llx - 0x%llx\n", addr,
addr + size - 1); /* Memory might get onlined immediately. */
atomic64_add(size, &vm->offline_size);
rc = add_memory_driver_managed(vm->mgid, addr, size, vm->resource_name,
MHP_MERGE_RESOURCE | MHP_NID_IS_MGID); if (rc) {
atomic64_sub(size, &vm->offline_size);
dev_warn(&vm->vdev->dev, "adding memory failed: %d\n", rc); /* * TODO: Linux MM does not properly clean up yet in all cases * where adding of memory failed - especially on -ENOMEM.
*/
} return rc;
}
/* * See virtio_mem_add_memory(): Try adding a single Linux memory block.
*/ staticint virtio_mem_sbm_add_mb(struct virtio_mem *vm, unsignedlong mb_id)
{ const uint64_t addr = virtio_mem_mb_id_to_phys(mb_id); const uint64_t size = memory_block_size_bytes();
return virtio_mem_add_memory(vm, addr, size);
}
/* * See virtio_mem_add_memory(): Try adding a big block.
*/ staticint virtio_mem_bbm_add_bb(struct virtio_mem *vm, unsignedlong bb_id)
{ const uint64_t addr = virtio_mem_bb_id_to_phys(vm, bb_id); const uint64_t size = vm->bbm.bb_size;
return virtio_mem_add_memory(vm, addr, size);
}
/* * Try removing memory from Linux. Will only fail if memory blocks aren't * offline. * * Must not be called with the vm->hotplug_mutex held (possible deadlock with * onlining code). * * Will not modify the state of memory blocks in virtio-mem.
*/ staticint virtio_mem_remove_memory(struct virtio_mem *vm, uint64_t addr,
uint64_t size)
{ int rc;
dev_dbg(&vm->vdev->dev, "removing memory: 0x%llx - 0x%llx\n", addr,
addr + size - 1);
rc = remove_memory(addr, size); if (!rc) {
atomic64_sub(size, &vm->offline_size); /* * We might have freed up memory we can now unplug, retry * immediately instead of waiting.
*/
virtio_mem_retry(vm);
} else {
dev_dbg(&vm->vdev->dev, "removing memory failed: %d\n", rc);
} return rc;
}
/* * See virtio_mem_remove_memory(): Try removing a single Linux memory block.
*/ staticint virtio_mem_sbm_remove_mb(struct virtio_mem *vm, unsignedlong mb_id)
{ const uint64_t addr = virtio_mem_mb_id_to_phys(mb_id); const uint64_t size = memory_block_size_bytes();
/* * Try offlining and removing memory from Linux. * * Must not be called with the vm->hotplug_mutex held (possible deadlock with * onlining code). * * Will not modify the state of memory blocks in virtio-mem.
*/ staticint virtio_mem_offline_and_remove_memory(struct virtio_mem *vm,
uint64_t addr,
uint64_t size)
{ int rc;
rc = offline_and_remove_memory(addr, size); if (!rc) {
atomic64_sub(size, &vm->offline_size); /* * We might have freed up memory we can now unplug, retry * immediately instead of waiting.
*/
virtio_mem_retry(vm); return 0;
}
dev_dbg(&vm->vdev->dev, "offlining and removing memory failed: %d\n", rc); /* * We don't really expect this to fail, because we fake-offlined all * memory already. But it could fail in corner cases.
*/
WARN_ON_ONCE(rc != -ENOMEM && rc != -EBUSY); return rc == -ENOMEM ? -ENOMEM : -EBUSY;
}
/* * See virtio_mem_offline_and_remove_memory(): Try offlining and removing * a single Linux memory block.
*/ staticint virtio_mem_sbm_offline_and_remove_mb(struct virtio_mem *vm, unsignedlong mb_id)
{ const uint64_t addr = virtio_mem_mb_id_to_phys(mb_id); const uint64_t size = memory_block_size_bytes();
/* * Try (offlining and) removing memory from Linux in case all subblocks are * unplugged. Can be called on online and offline memory blocks. * * May modify the state of memory blocks in virtio-mem.
*/ staticint virtio_mem_sbm_try_remove_unplugged_mb(struct virtio_mem *vm, unsignedlong mb_id)
{ int rc;
/* * Once all subblocks of a memory block were unplugged, offline and * remove it.
*/ if (!virtio_mem_sbm_test_sb_unplugged(vm, mb_id, 0, vm->sbm.sbs_per_mb)) return 0;
/* offline_and_remove_memory() works for online and offline memory. */
mutex_unlock(&vm->hotplug_mutex);
rc = virtio_mem_sbm_offline_and_remove_mb(vm, mb_id);
mutex_lock(&vm->hotplug_mutex); if (!rc)
virtio_mem_sbm_set_mb_state(vm, mb_id,
VIRTIO_MEM_SBM_MB_UNUSED); return rc;
}
/* * See virtio_mem_offline_and_remove_memory(): Try to offline and remove a * all Linux memory blocks covered by the big block.
*/ staticint virtio_mem_bbm_offline_and_remove_bb(struct virtio_mem *vm, unsignedlong bb_id)
{ const uint64_t addr = virtio_mem_bb_id_to_phys(vm, bb_id); const uint64_t size = vm->bbm.bb_size;
/* * Test if a virtio-mem device overlaps with the given range. Can be called * from (notifier) callbacks lockless.
*/ staticbool virtio_mem_overlaps_range(struct virtio_mem *vm, uint64_t start,
uint64_t size)
{ return start < vm->addr + vm->region_size && vm->addr < start + size;
}
/* * Test if a virtio-mem device contains a given range. Can be called from * (notifier) callbacks lockless.
*/ staticbool virtio_mem_contains_range(struct virtio_mem *vm, uint64_t start,
uint64_t size)
{ return start >= vm->addr && start + size <= vm->addr + vm->region_size;
}
staticvoid virtio_mem_bbm_notify_going_offline(struct virtio_mem *vm, unsignedlong bb_id, unsignedlong pfn, unsignedlong nr_pages)
{ /* * When marked as "fake-offline", all online memory of this device block * is allocated by us. Otherwise, we don't have any memory allocated.
*/ if (virtio_mem_bbm_get_bb_state(vm, bb_id) !=
VIRTIO_MEM_BBM_BB_FAKE_OFFLINE) return;
virtio_mem_fake_offline_going_offline(pfn, nr_pages);
}
/* * This callback will either be called synchronously from add_memory() or * asynchronously (e.g., triggered via user space). We have to be careful * with locking when calling add_memory().
*/ staticint virtio_mem_memory_notifier_cb(struct notifier_block *nb, unsignedlong action, void *arg)
{ struct virtio_mem *vm = container_of(nb, struct virtio_mem,
memory_notifier); struct memory_notify *mhp = arg; constunsignedlong start = PFN_PHYS(mhp->start_pfn); constunsignedlong size = PFN_PHYS(mhp->nr_pages); int rc = NOTIFY_OK; unsignedlong id;
if (!virtio_mem_overlaps_range(vm, start, size)) return NOTIFY_DONE;
if (vm->in_sbm) {
id = virtio_mem_phys_to_mb_id(start); /* * In SBM, we add memory in separate memory blocks - we expect * it to be onlined/offlined in the same granularity. Bail out * if this ever changes.
*/ if (WARN_ON_ONCE(size != memory_block_size_bytes() ||
!IS_ALIGNED(start, memory_block_size_bytes()))) return NOTIFY_BAD;
} else {
id = virtio_mem_phys_to_bb_id(vm, start); /* * In BBM, we only care about onlining/offlining happening * within a single big block, we don't care about the * actual granularity as we don't track individual Linux * memory blocks.
*/ if (WARN_ON_ONCE(id != virtio_mem_phys_to_bb_id(vm, start + size - 1))) return NOTIFY_BAD;
}
/* * Avoid circular locking lockdep warnings. We lock the mutex * e.g., in MEM_GOING_ONLINE and unlock it in MEM_ONLINE. The * blocking_notifier_call_chain() has it's own lock, which gets unlocked * between both notifier calls and will bail out. False positive.
*/
lockdep_off();
switch (action) { case MEM_GOING_OFFLINE:
mutex_lock(&vm->hotplug_mutex); if (vm->removing) {
rc = notifier_from_errno(-EBUSY);
mutex_unlock(&vm->hotplug_mutex); break;
}
vm->hotplug_active = true; if (vm->in_sbm)
virtio_mem_sbm_notify_going_offline(vm, id); else
virtio_mem_bbm_notify_going_offline(vm, id,
mhp->start_pfn,
mhp->nr_pages); break; case MEM_GOING_ONLINE:
mutex_lock(&vm->hotplug_mutex); if (vm->removing) {
rc = notifier_from_errno(-EBUSY);
mutex_unlock(&vm->hotplug_mutex); break;
}
vm->hotplug_active = true; if (vm->in_sbm)
rc = virtio_mem_sbm_notify_going_online(vm, id); break; case MEM_OFFLINE: if (vm->in_sbm)
virtio_mem_sbm_notify_offline(vm, id);
atomic64_add(size, &vm->offline_size); /* * Trigger the workqueue. Now that we have some offline memory, * maybe we can handle pending unplug requests.
*/ if (!unplug_online)
virtio_mem_retry(vm);
vm->hotplug_active = false;
mutex_unlock(&vm->hotplug_mutex); break; case MEM_ONLINE: if (vm->in_sbm)
virtio_mem_sbm_notify_online(vm, id, mhp->start_pfn);
atomic64_sub(size, &vm->offline_size); /* * Start adding more memory once we onlined half of our * threshold. Don't trigger if it's possibly due to our actipn * (e.g., us adding memory which gets onlined immediately from * the core).
*/ if (!atomic_read(&vm->wq_active) &&
virtio_mem_could_add_memory(vm, vm->offline_threshold / 2))
virtio_mem_retry(vm);
vm->hotplug_active = false;
mutex_unlock(&vm->hotplug_mutex); break; case MEM_CANCEL_OFFLINE: if (!vm->hotplug_active) break; if (vm->in_sbm)
virtio_mem_sbm_notify_cancel_offline(vm, id); else
virtio_mem_bbm_notify_cancel_offline(vm, id,
mhp->start_pfn,
mhp->nr_pages);
vm->hotplug_active = false;
mutex_unlock(&vm->hotplug_mutex); break; case MEM_CANCEL_ONLINE: if (!vm->hotplug_active) break;
vm->hotplug_active = false;
mutex_unlock(&vm->hotplug_mutex); break; default: break;
}
lockdep_on();
return rc;
}
staticint virtio_mem_pm_notifier_cb(struct notifier_block *nb, unsignedlong action, void *arg)
{ struct virtio_mem *vm = container_of(nb, struct virtio_mem,
pm_notifier); switch (action) { case PM_HIBERNATION_PREPARE: case PM_RESTORE_PREPARE: /* * When restarting the VM, all memory is unplugged. Don't * allow to hibernate and restore from an image.
*/
dev_err(&vm->vdev->dev, "hibernation is not supported.\n"); return NOTIFY_BAD; default: return NOTIFY_OK;
}
}
/* * Set a range of pages PG_offline. Remember pages that were never onlined * (via generic_online_page()) using PageDirty().
*/ staticvoid virtio_mem_set_fake_offline(unsignedlong pfn, unsignedlong nr_pages, bool onlined)
{
page_offline_begin(); for (; nr_pages--; pfn++) { struct page *page = pfn_to_page(pfn);
if (!onlined) /* * Pages that have not been onlined yet were initialized * to PageOffline(). Remember that we have to route them * through generic_online_page().
*/
SetPageDirty(page); else
__SetPageOffline(page);
VM_WARN_ON_ONCE(!PageOffline(page));
}
page_offline_end();
}
/* * Clear PG_offline from a range of pages. If the pages were never onlined, * (via generic_online_page()), clear PageDirty().
*/ staticvoid virtio_mem_clear_fake_offline(unsignedlong pfn, unsignedlong nr_pages, bool onlined)
{ for (; nr_pages--; pfn++) { struct page *page = pfn_to_page(pfn);
if (!onlined) /* generic_online_page() will clear PageOffline(). */
ClearPageDirty(page); else
__ClearPageOffline(page);
}
}
/* * Release a range of fake-offline pages to the buddy, effectively * fake-onlining them.
*/ staticvoid virtio_mem_fake_online(unsignedlong pfn, unsignedlong nr_pages)
{ unsignedlong order = MAX_PAGE_ORDER; unsignedlong i;
/* * We might get called for ranges that don't cover properly aligned * MAX_PAGE_ORDER pages; however, we can only online properly aligned * pages with an order of MAX_PAGE_ORDER at maximum.
*/ while (!IS_ALIGNED(pfn | nr_pages, 1 << order))
order--;
for (i = 0; i < nr_pages; i += 1 << order) { struct page *page = pfn_to_page(pfn + i);
/* * If the page is PageDirty(), it was kept fake-offline when * onlining the memory block. Otherwise, it was allocated * using alloc_contig_range(). All pages in a subblock are * alike.
*/ if (PageDirty(page)) {
virtio_mem_clear_fake_offline(pfn + i, 1 << order, false);
generic_online_page(page, order);
} else {
virtio_mem_clear_fake_offline(pfn + i, 1 << order, true);
free_contig_range(pfn + i, 1 << order);
adjust_managed_page_count(page, 1 << order);
}
}
}
/* * TODO: We want an alloc_contig_range() mode that tries to allocate * harder (e.g., dealing with temporarily pinned pages, PCP), especially * with ZONE_MOVABLE. So for now, retry a couple of times with * ZONE_MOVABLE before giving up - because that zone is supposed to give * some guarantees.
*/ for (retry_count = 0; retry_count < 5; retry_count++) { /* * If the config changed, stop immediately and go back to the * main loop: avoid trying to keep unplugging if the device * might have decided to not remove any more memory.
*/ if (atomic_read(&vm->config_changed)) return -EAGAIN;
rc = alloc_contig_range(pfn, pfn + nr_pages, ACR_FLAGS_NONE,
GFP_KERNEL); if (rc == -ENOMEM) /* whoops, out of memory */ return rc; elseif (rc && !is_movable) break; elseif (rc) continue;
/* * Handle fake-offline pages when memory is going offline - such that the * pages can be skipped by mm-core when offlining.
*/ staticvoid virtio_mem_fake_offline_going_offline(unsignedlong pfn, unsignedlong nr_pages)
{ struct page *page; unsignedlong i;
/* Drop our reference to the pages so the memory can get offlined. */ for (i = 0; i < nr_pages; i++) {
page = pfn_to_page(pfn + i); if (WARN_ON(!page_ref_dec_and_test(page)))
dump_page(page, "fake-offline page referenced");
}
}
/* * Handle fake-offline pages when memory offlining is canceled - to undo * what we did in virtio_mem_fake_offline_going_offline().
*/ staticvoid virtio_mem_fake_offline_cancel_offline(unsignedlong pfn, unsignedlong nr_pages)
{ unsignedlong i;
/* * Get the reference again that we dropped via page_ref_dec_and_test() * when going offline.
*/ for (i = 0; i < nr_pages; i++)
page_ref_inc(pfn_to_page(pfn + i));
}
/* * We can get called with any order up to MAX_PAGE_ORDER. If our subblock * size is smaller than that and we have a mixture of plugged and * unplugged subblocks within such a page, we have to process in * smaller granularity. In that case we'll adjust the order exactly once * within the loop.
*/ for (addr = start; addr < end; ) {
next = addr + PFN_PHYS(1 << order);
if (vm->in_sbm) {
id = virtio_mem_phys_to_mb_id(addr);
sb_id = virtio_mem_phys_to_sb_id(vm, addr);
count = virtio_mem_phys_to_sb_id(vm, next - 1) - sb_id + 1;
if (virtio_mem_sbm_test_sb_plugged(vm, id, sb_id, count)) { /* Fully plugged. */
do_online = true;
} elseif (count == 1 ||
virtio_mem_sbm_test_sb_unplugged(vm, id, sb_id, count)) { /* Fully unplugged. */
do_online = false;
} else { /* * Mixture, process sub-blocks instead. This * will be at least the size of a pageblock. * We'll run into this case exactly once.
*/
order = ilog2(vm->sbm.sb_size) - PAGE_SHIFT;
do_online = virtio_mem_sbm_test_sb_plugged(vm, id, sb_id, 1); continue;
}
} else { /* * If the whole block is marked fake offline, keep * everything that way.
*/
id = virtio_mem_phys_to_bb_id(vm, addr);
do_online = virtio_mem_bbm_get_bb_state(vm, id) !=
VIRTIO_MEM_BBM_BB_FAKE_OFFLINE;
}
rcu_read_lock();
list_for_each_entry_rcu(vm, &virtio_mem_devices, next) { /* * Pages we're onlining will never cross memory blocks and, * therefore, not virtio-mem devices.
*/ if (!virtio_mem_contains_range(vm, addr, PFN_PHYS(1 << order))) continue;
/* * virtio_mem_set_fake_offline() might sleep. We can safely * drop the RCU lock at this point because the device * cannot go away. See virtio_mem_remove() how races * between memory onlining and device removal are handled.
*/
rcu_read_unlock();
switch (virtio_mem_send_request(vm, &req)) { case VIRTIO_MEM_RESP_ACK:
vm->unplug_all_required = false;
vm->plugged_size = 0; /* usable region might have shrunk */
atomic_set(&vm->config_changed, 1); return 0; case VIRTIO_MEM_RESP_BUSY:
rc = -ETXTBSY; break; default: break;
}
dev_dbg(&vm->vdev->dev, "unplugging all memory failed: %d\n", rc); return rc;
}
/* * Plug selected subblocks. Updates the plugged state, but not the state * of the memory block.
*/ staticint virtio_mem_sbm_plug_sb(struct virtio_mem *vm, unsignedlong mb_id, int sb_id, int count)
{ const uint64_t addr = virtio_mem_mb_id_to_phys(mb_id) +
sb_id * vm->sbm.sb_size; const uint64_t size = count * vm->sbm.sb_size; int rc;
/* * Unplug selected subblocks. Updates the plugged state, but not the state * of the memory block.
*/ staticint virtio_mem_sbm_unplug_sb(struct virtio_mem *vm, unsignedlong mb_id, int sb_id, int count)
{ const uint64_t addr = virtio_mem_mb_id_to_phys(mb_id) +
sb_id * vm->sbm.sb_size; const uint64_t size = count * vm->sbm.sb_size; int rc;
/* * Request to unplug a big block. * * Will not modify the state of the big block.
*/ staticint virtio_mem_bbm_unplug_bb(struct virtio_mem *vm, unsignedlong bb_id)
{ const uint64_t addr = virtio_mem_bb_id_to_phys(vm, bb_id); const uint64_t size = vm->bbm.bb_size;
/* * Request to plug a big block. * * Will not modify the state of the big block.
*/ staticint virtio_mem_bbm_plug_bb(struct virtio_mem *vm, unsignedlong bb_id)
{ const uint64_t addr = virtio_mem_bb_id_to_phys(vm, bb_id); const uint64_t size = vm->bbm.bb_size;
/* * Unplug the desired number of plugged subblocks of a offline or not-added * memory block. Will fail if any subblock cannot get unplugged (instead of * skipping it). * * Will not modify the state of the memory block. * * Note: can fail after some subblocks were unplugged.
*/ staticint virtio_mem_sbm_unplug_any_sb_raw(struct virtio_mem *vm, unsignedlong mb_id, uint64_t *nb_sb)
{ int sb_id, count; int rc;
sb_id = vm->sbm.sbs_per_mb - 1; while (*nb_sb) { /* Find the next candidate subblock */ while (sb_id >= 0 &&
virtio_mem_sbm_test_sb_unplugged(vm, mb_id, sb_id, 1))
sb_id--; if (sb_id < 0) break; /* Try to unplug multiple subblocks at a time */
count = 1; while (count < *nb_sb && sb_id > 0 &&
virtio_mem_sbm_test_sb_plugged(vm, mb_id, sb_id - 1, 1)) {
count++;
sb_id--;
}
/* * Unplug all plugged subblocks of an offline or not-added memory block. * * Will not modify the state of the memory block. * * Note: can fail after some subblocks were unplugged.
*/ staticint virtio_mem_sbm_unplug_mb(struct virtio_mem *vm, unsignedlong mb_id)
{
uint64_t nb_sb = vm->sbm.sbs_per_mb;
/* * Prepare tracking data for the next memory block.
*/ staticint virtio_mem_sbm_prepare_next_mb(struct virtio_mem *vm, unsignedlong *mb_id)
{ int rc;
if (vm->sbm.next_mb_id > vm->sbm.last_usable_mb_id) return -ENOSPC;
/* Resize the state array if required. */
rc = virtio_mem_sbm_mb_states_prepare_next_mb(vm); if (rc) return rc;
/* Resize the subblock bitmap if required. */
rc = virtio_mem_sbm_sb_states_prepare_next_mb(vm); if (rc) return rc;
/* * Try to plug the desired number of subblocks and add the memory block * to Linux. * * Will modify the state of the memory block.
*/ staticint virtio_mem_sbm_plug_and_add_mb(struct virtio_mem *vm, unsignedlong mb_id, uint64_t *nb_sb)
{ constint count = min_t(int, *nb_sb, vm->sbm.sbs_per_mb); int rc;
if (WARN_ON_ONCE(!count)) return -EINVAL;
/* * Plug the requested number of subblocks before adding it to linux, * so that onlining will directly online all plugged subblocks.
*/
rc = virtio_mem_sbm_plug_sb(vm, mb_id, 0, count); if (rc) return rc;
/* * Mark the block properly offline before adding it to Linux, * so the memory notifiers will find the block in the right state.
*/ if (count == vm->sbm.sbs_per_mb)
virtio_mem_sbm_set_mb_state(vm, mb_id,
VIRTIO_MEM_SBM_MB_OFFLINE); else
virtio_mem_sbm_set_mb_state(vm, mb_id,
VIRTIO_MEM_SBM_MB_OFFLINE_PARTIAL);
/* Add the memory block to linux - if that fails, try to unplug. */
rc = virtio_mem_sbm_add_mb(vm, mb_id); if (rc) { int new_state = VIRTIO_MEM_SBM_MB_UNUSED;
/* * Try to plug the desired number of subblocks of a memory block that * is already added to Linux. * * Will modify the state of the memory block. * * Note: Can fail after some subblocks were successfully plugged.
*/ staticint virtio_mem_sbm_plug_any_sb(struct virtio_mem *vm, unsignedlong mb_id, uint64_t *nb_sb)
{ constint old_state = virtio_mem_sbm_get_mb_state(vm, mb_id); unsignedlong pfn, nr_pages; int sb_id, count; int rc;
/* Don't race with onlining/offlining */
mutex_lock(&vm->hotplug_mutex);
for (i = 0; i < ARRAY_SIZE(mb_states); i++) {
virtio_mem_sbm_for_each_mb(vm, mb_id, mb_states[i]) {
rc = virtio_mem_sbm_plug_any_sb(vm, mb_id, &nb_sb); if (rc || !nb_sb) goto out_unlock;
cond_resched();
}
}
/* * We won't be working on online/offline memory blocks from this point, * so we can't race with memory onlining/offlining. Drop the mutex.
*/
mutex_unlock(&vm->hotplug_mutex);
/* Try to plug and add unused blocks */
virtio_mem_sbm_for_each_mb(vm, mb_id, VIRTIO_MEM_SBM_MB_UNUSED) { if (!virtio_mem_could_add_memory(vm, memory_block_size_bytes())) return -ENOSPC;
/* * Plug a big block and add it to Linux. * * Will modify the state of the big block.
*/ staticint virtio_mem_bbm_plug_and_add_bb(struct virtio_mem *vm, unsignedlong bb_id)
{ int rc;
if (WARN_ON_ONCE(virtio_mem_bbm_get_bb_state(vm, bb_id) !=
VIRTIO_MEM_BBM_BB_UNUSED)) return -EINVAL;
/* Try to plug and add unused big blocks */
virtio_mem_bbm_for_each_bb(vm, bb_id, VIRTIO_MEM_BBM_BB_UNUSED) { if (!virtio_mem_could_add_memory(vm, vm->bbm.bb_size)) return -ENOSPC;
rc = virtio_mem_bbm_plug_and_add_bb(vm, bb_id); if (!rc)
nb_bb--; if (rc || !nb_bb) return rc;
cond_resched();
}
/* Try to prepare, plug and add new big blocks */ while (nb_bb) { if (!virtio_mem_could_add_memory(vm, vm->bbm.bb_size)) return -ENOSPC;
rc = virtio_mem_bbm_prepare_next_bb(vm, &bb_id); if (rc) return rc;
rc = virtio_mem_bbm_plug_and_add_bb(vm, bb_id); if (!rc)
nb_bb--; if (rc) return rc;
cond_resched();
}
return 0;
}
/* * Try to plug the requested amount of memory.
*/ staticint virtio_mem_plug_request(struct virtio_mem *vm, uint64_t diff)
{ if (vm->in_sbm) return virtio_mem_sbm_plug_request(vm, diff); return virtio_mem_bbm_plug_request(vm, diff);
}
/* * Unplug the desired number of plugged subblocks of an offline memory block. * Will fail if any subblock cannot get unplugged (instead of skipping it). * * Will modify the state of the memory block. Might temporarily drop the * hotplug_mutex. * * Note: Can fail after some subblocks were successfully unplugged.
*/ staticint virtio_mem_sbm_unplug_any_sb_offline(struct virtio_mem *vm, unsignedlong mb_id,
uint64_t *nb_sb)
{ int rc;
/* some subblocks might have been unplugged even on failure */ if (!virtio_mem_sbm_test_sb_plugged(vm, mb_id, 0, vm->sbm.sbs_per_mb))
virtio_mem_sbm_set_mb_state(vm, mb_id,
VIRTIO_MEM_SBM_MB_OFFLINE_PARTIAL); if (rc) return rc;
if (virtio_mem_sbm_test_sb_unplugged(vm, mb_id, 0, vm->sbm.sbs_per_mb)) { /* * Remove the block from Linux - this should never fail. * Hinder the block from getting onlined by marking it * unplugged. Temporarily drop the mutex, so * any pending GOING_ONLINE requests can be serviced/rejected.
*/
virtio_mem_sbm_set_mb_state(vm, mb_id,
VIRTIO_MEM_SBM_MB_UNUSED);
/* * Unplug the given plugged subblocks of an online memory block. * * Will modify the state of the memory block.
*/ staticint virtio_mem_sbm_unplug_sb_online(struct virtio_mem *vm, unsignedlong mb_id, int sb_id, int count)
{ constunsignedlong nr_pages = PFN_DOWN(vm->sbm.sb_size) * count; constint old_state = virtio_mem_sbm_get_mb_state(vm, mb_id); unsignedlong start_pfn; int rc;
rc = virtio_mem_fake_offline(vm, start_pfn, nr_pages); if (rc) return rc;
/* Try to unplug the allocated memory */
rc = virtio_mem_sbm_unplug_sb(vm, mb_id, sb_id, count); if (rc) { /* Return the memory to the buddy. */
virtio_mem_fake_online(start_pfn, nr_pages); return rc;
}
switch (old_state) { case VIRTIO_MEM_SBM_MB_KERNEL:
virtio_mem_sbm_set_mb_state(vm, mb_id,
VIRTIO_MEM_SBM_MB_KERNEL_PARTIAL); break; case VIRTIO_MEM_SBM_MB_MOVABLE:
virtio_mem_sbm_set_mb_state(vm, mb_id,
VIRTIO_MEM_SBM_MB_MOVABLE_PARTIAL); break;
}
return 0;
}
/* * Unplug the desired number of plugged subblocks of an online memory block. * Will skip subblock that are busy. * * Will modify the state of the memory block. Might temporarily drop the * hotplug_mutex. * * Note: Can fail after some subblocks were successfully unplugged. Can * return 0 even if subblocks were busy and could not get unplugged.
*/ staticint virtio_mem_sbm_unplug_any_sb_online(struct virtio_mem *vm, unsignedlong mb_id,
uint64_t *nb_sb)
{ int rc, sb_id;
/* If possible, try to unplug the complete block in one shot. */ if (*nb_sb >= vm->sbm.sbs_per_mb &&
virtio_mem_sbm_test_sb_plugged(vm, mb_id, 0, vm->sbm.sbs_per_mb)) {
rc = virtio_mem_sbm_unplug_sb_online(vm, mb_id, 0,
vm->sbm.sbs_per_mb); if (!rc) {
*nb_sb -= vm->sbm.sbs_per_mb; goto unplugged;
} elseif (rc != -EBUSY) return rc;
}
/* Fallback to single subblocks. */ for (sb_id = vm->sbm.sbs_per_mb - 1; sb_id >= 0 && *nb_sb; sb_id--) { /* Find the next candidate subblock */ while (sb_id >= 0 &&
!virtio_mem_sbm_test_sb_plugged(vm, mb_id, sb_id, 1))
sb_id--; if (sb_id < 0) break;
unplugged:
rc = virtio_mem_sbm_try_remove_unplugged_mb(vm, mb_id); if (rc)
vm->sbm.have_unplugged_mb = 1; /* Ignore errors, this is not critical. We'll retry later. */ return 0;
}
/* * Unplug the desired number of plugged subblocks of a memory block that is * already added to Linux. Will skip subblock of online memory blocks that are * busy (by the OS). Will fail if any subblock that's not busy cannot get * unplugged. * * Will modify the state of the memory block. Might temporarily drop the * hotplug_mutex. * * Note: Can fail after some subblocks were successfully unplugged. Can * return 0 even if subblocks were busy and could not get unplugged.
*/ staticint virtio_mem_sbm_unplug_any_sb(struct virtio_mem *vm, unsignedlong mb_id,
uint64_t *nb_sb)
{ constint old_state = virtio_mem_sbm_get_mb_state(vm, mb_id);
switch (old_state) { case VIRTIO_MEM_SBM_MB_KERNEL_PARTIAL: case VIRTIO_MEM_SBM_MB_KERNEL: case VIRTIO_MEM_SBM_MB_MOVABLE_PARTIAL: case VIRTIO_MEM_SBM_MB_MOVABLE: return virtio_mem_sbm_unplug_any_sb_online(vm, mb_id, nb_sb); case VIRTIO_MEM_SBM_MB_OFFLINE_PARTIAL: case VIRTIO_MEM_SBM_MB_OFFLINE: return virtio_mem_sbm_unplug_any_sb_offline(vm, mb_id, nb_sb);
} return -EINVAL;
}
/* * We'll drop the mutex a couple of times when it is safe to do so. * This might result in some blocks switching the state (online/offline) * and we could miss them in this run - we will retry again later.
*/
mutex_lock(&vm->hotplug_mutex);
/* * We try unplug from partially plugged blocks first, to try removing * whole memory blocks along with metadata. We prioritize ZONE_MOVABLE * as it's more reliable to unplug memory and remove whole memory * blocks, and we don't want to trigger a zone imbalances by * accidentially removing too much kernel memory.
*/ for (i = 0; i < ARRAY_SIZE(mb_states); i++) {
virtio_mem_sbm_for_each_mb_rev(vm, mb_id, mb_states[i]) {
rc = virtio_mem_sbm_unplug_any_sb(vm, mb_id, &nb_sb); if (rc || !nb_sb) goto out_unlock;
mutex_unlock(&vm->hotplug_mutex);
cond_resched();
mutex_lock(&vm->hotplug_mutex);
} if (!unplug_online && i == 1) {
mutex_unlock(&vm->hotplug_mutex); return 0;
}
}
/* * Try to offline and remove a big block from Linux and unplug it. Will fail * with -EBUSY if some memory is busy and cannot get unplugged. * * Will modify the state of the memory block. Might temporarily drop the * hotplug_mutex.
*/ staticint virtio_mem_bbm_offline_remove_and_unplug_bb(struct virtio_mem *vm, unsignedlong bb_id)
{ constunsignedlong start_pfn = PFN_DOWN(virtio_mem_bb_id_to_phys(vm, bb_id)); constunsignedlong nr_pages = PFN_DOWN(vm->bbm.bb_size); unsignedlong end_pfn = start_pfn + nr_pages; unsignedlong pfn; struct page *page; int rc;
if (WARN_ON_ONCE(virtio_mem_bbm_get_bb_state(vm, bb_id) !=
VIRTIO_MEM_BBM_BB_ADDED)) return -EINVAL;
/* * Start by fake-offlining all memory. Once we marked the device * block as fake-offline, all newly onlined memory will * automatically be kept fake-offline. Protect from concurrent * onlining/offlining until we have a consistent state.
*/
mutex_lock(&vm->hotplug_mutex);
virtio_mem_bbm_set_bb_state(vm, bb_id, VIRTIO_MEM_BBM_BB_FAKE_OFFLINE);
for (pfn = start_pfn; pfn < end_pfn; pfn += PAGES_PER_SECTION) {
page = pfn_to_online_page(pfn); if (!page) continue;
/* * Try to unplug big blocks. Similar to SBM, start with offline * big blocks.
*/ for (i = 0; i < 3; i++) {
virtio_mem_bbm_for_each_bb_rev(vm, bb_id, VIRTIO_MEM_BBM_BB_ADDED) {
cond_resched();
/* * As we're holding no locks, these checks are racy, * but we don't care.
*/ if (i == 0 && !virtio_mem_bbm_bb_is_offline(vm, bb_id)) continue; if (i == 1 && !virtio_mem_bbm_bb_is_movable(vm, bb_id)) continue;
rc = virtio_mem_bbm_offline_remove_and_unplug_bb(vm, bb_id); if (rc == -EBUSY) continue; if (!rc)
nb_bb--; if (rc || !nb_bb) return rc;
} if (i == 0 && !unplug_online) return 0;
}
return nb_bb ? -EBUSY : 0;
}
/* * Try to unplug the requested amount of memory.
*/ staticint virtio_mem_unplug_request(struct virtio_mem *vm, uint64_t diff)
{ if (vm->in_sbm) return virtio_mem_sbm_unplug_request(vm, diff); return virtio_mem_bbm_unplug_request(vm, diff);
}
/* * Try to unplug all blocks that couldn't be unplugged before, for example, * because the hypervisor was busy. Further, offline and remove any memory * blocks where we previously failed.
*/ staticint virtio_mem_cleanup_pending_mb(struct virtio_mem *vm)
{ unsignedlong id; int rc = 0;
if (rc)
vm->sbm.have_unplugged_mb = true; /* Ignore errors, this is not critical. We'll retry later. */ return 0;
}
/* * Update all parts of the config that could have changed.
*/ staticvoid virtio_mem_refresh_config(struct virtio_mem *vm)
{ conststruct range pluggable_range = mhp_get_pluggable_range(true);
uint64_t new_plugged_size, end_addr;
/* the plugged_size is just a reflection of what _we_ did previously */
virtio_cread_le(vm->vdev, struct virtio_mem_config, plugged_size,
&new_plugged_size); if (WARN_ON_ONCE(new_plugged_size != vm->plugged_size))
vm->plugged_size = new_plugged_size;
/* calculate the last usable memory block id */
virtio_cread_le(vm->vdev, struct virtio_mem_config,
usable_region_size, &vm->usable_region_size);
--> --------------------
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.