18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Virtio-mem device driver. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright Red Hat, Inc. 2020 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Author(s): David Hildenbrand <david@redhat.com> 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include <linux/virtio.h> 118c2ecf20Sopenharmony_ci#include <linux/virtio_mem.h> 128c2ecf20Sopenharmony_ci#include <linux/workqueue.h> 138c2ecf20Sopenharmony_ci#include <linux/slab.h> 148c2ecf20Sopenharmony_ci#include <linux/module.h> 158c2ecf20Sopenharmony_ci#include <linux/mm.h> 168c2ecf20Sopenharmony_ci#include <linux/memory_hotplug.h> 178c2ecf20Sopenharmony_ci#include <linux/memory.h> 188c2ecf20Sopenharmony_ci#include <linux/hrtimer.h> 198c2ecf20Sopenharmony_ci#include <linux/crash_dump.h> 208c2ecf20Sopenharmony_ci#include <linux/mutex.h> 218c2ecf20Sopenharmony_ci#include <linux/bitmap.h> 228c2ecf20Sopenharmony_ci#include <linux/lockdep.h> 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci#include <acpi/acpi_numa.h> 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_cistatic bool unplug_online = true; 278c2ecf20Sopenharmony_cimodule_param(unplug_online, bool, 0644); 288c2ecf20Sopenharmony_ciMODULE_PARM_DESC(unplug_online, "Try to unplug online memory"); 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_cienum virtio_mem_mb_state { 318c2ecf20Sopenharmony_ci /* Unplugged, not added to Linux. Can be reused later. */ 328c2ecf20Sopenharmony_ci VIRTIO_MEM_MB_STATE_UNUSED = 0, 338c2ecf20Sopenharmony_ci /* (Partially) plugged, not added to Linux. Error on add_memory(). */ 348c2ecf20Sopenharmony_ci VIRTIO_MEM_MB_STATE_PLUGGED, 358c2ecf20Sopenharmony_ci /* Fully plugged, fully added to Linux, offline. */ 368c2ecf20Sopenharmony_ci VIRTIO_MEM_MB_STATE_OFFLINE, 378c2ecf20Sopenharmony_ci /* Partially plugged, fully added to Linux, offline. */ 388c2ecf20Sopenharmony_ci VIRTIO_MEM_MB_STATE_OFFLINE_PARTIAL, 398c2ecf20Sopenharmony_ci /* Fully plugged, fully added to Linux, online. */ 408c2ecf20Sopenharmony_ci VIRTIO_MEM_MB_STATE_ONLINE, 418c2ecf20Sopenharmony_ci /* Partially plugged, fully added to Linux, online. */ 428c2ecf20Sopenharmony_ci VIRTIO_MEM_MB_STATE_ONLINE_PARTIAL, 438c2ecf20Sopenharmony_ci VIRTIO_MEM_MB_STATE_COUNT 448c2ecf20Sopenharmony_ci}; 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_cistruct virtio_mem { 478c2ecf20Sopenharmony_ci struct virtio_device *vdev; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci /* We might first have to unplug all memory when starting up. */ 508c2ecf20Sopenharmony_ci bool unplug_all_required; 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci /* Workqueue that processes the plug/unplug requests. */ 538c2ecf20Sopenharmony_ci struct work_struct wq; 548c2ecf20Sopenharmony_ci atomic_t config_changed; 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci /* Virtqueue for guest->host requests. */ 578c2ecf20Sopenharmony_ci struct virtqueue *vq; 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci /* Wait for a host response to a guest request. */ 608c2ecf20Sopenharmony_ci wait_queue_head_t host_resp; 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci /* Space for one guest request and the host response. */ 638c2ecf20Sopenharmony_ci struct virtio_mem_req req; 648c2ecf20Sopenharmony_ci struct virtio_mem_resp resp; 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ci /* The current size of the device. */ 678c2ecf20Sopenharmony_ci uint64_t plugged_size; 688c2ecf20Sopenharmony_ci /* The requested size of the device. */ 698c2ecf20Sopenharmony_ci uint64_t requested_size; 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci /* The device block size (for communicating with the device). */ 728c2ecf20Sopenharmony_ci uint64_t device_block_size; 738c2ecf20Sopenharmony_ci /* The translated node id. NUMA_NO_NODE in case not specified. */ 748c2ecf20Sopenharmony_ci int nid; 758c2ecf20Sopenharmony_ci /* Physical start address of the memory region. */ 768c2ecf20Sopenharmony_ci uint64_t addr; 778c2ecf20Sopenharmony_ci /* Maximum region size in bytes. */ 788c2ecf20Sopenharmony_ci uint64_t region_size; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci /* The subblock size. */ 818c2ecf20Sopenharmony_ci uint64_t subblock_size; 828c2ecf20Sopenharmony_ci /* The number of subblocks per memory block. */ 838c2ecf20Sopenharmony_ci uint32_t nb_sb_per_mb; 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci /* Id of the first memory block of this device. */ 868c2ecf20Sopenharmony_ci unsigned long first_mb_id; 878c2ecf20Sopenharmony_ci /* Id of the last memory block of this device. */ 888c2ecf20Sopenharmony_ci unsigned long last_mb_id; 898c2ecf20Sopenharmony_ci /* Id of the last usable memory block of this device. */ 908c2ecf20Sopenharmony_ci unsigned long last_usable_mb_id; 918c2ecf20Sopenharmony_ci /* Id of the next memory bock to prepare when needed. */ 928c2ecf20Sopenharmony_ci unsigned long next_mb_id; 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci /* The parent resource for all memory added via this device. */ 958c2ecf20Sopenharmony_ci struct resource *parent_resource; 968c2ecf20Sopenharmony_ci /* 978c2ecf20Sopenharmony_ci * Copy of "System RAM (virtio_mem)" to be used for 988c2ecf20Sopenharmony_ci * add_memory_driver_managed(). 998c2ecf20Sopenharmony_ci */ 1008c2ecf20Sopenharmony_ci const char *resource_name; 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci /* Summary of all memory block states. */ 1038c2ecf20Sopenharmony_ci unsigned long nb_mb_state[VIRTIO_MEM_MB_STATE_COUNT]; 1048c2ecf20Sopenharmony_ci#define VIRTIO_MEM_NB_OFFLINE_THRESHOLD 10 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci /* 1078c2ecf20Sopenharmony_ci * One byte state per memory block. 1088c2ecf20Sopenharmony_ci * 1098c2ecf20Sopenharmony_ci * Allocated via vmalloc(). When preparing new blocks, resized 1108c2ecf20Sopenharmony_ci * (alloc+copy+free) when needed (crossing pages with the next mb). 1118c2ecf20Sopenharmony_ci * (when crossing pages). 1128c2ecf20Sopenharmony_ci * 1138c2ecf20Sopenharmony_ci * With 128MB memory blocks, we have states for 512GB of memory in one 1148c2ecf20Sopenharmony_ci * page. 1158c2ecf20Sopenharmony_ci */ 1168c2ecf20Sopenharmony_ci uint8_t *mb_state; 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci /* 1198c2ecf20Sopenharmony_ci * $nb_sb_per_mb bit per memory block. Handled similar to mb_state. 1208c2ecf20Sopenharmony_ci * 1218c2ecf20Sopenharmony_ci * With 4MB subblocks, we manage 128GB of memory in one page. 1228c2ecf20Sopenharmony_ci */ 1238c2ecf20Sopenharmony_ci unsigned long *sb_bitmap; 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci /* 1268c2ecf20Sopenharmony_ci * Mutex that protects the nb_mb_state, mb_state, and sb_bitmap. 1278c2ecf20Sopenharmony_ci * 1288c2ecf20Sopenharmony_ci * When this lock is held the pointers can't change, ONLINE and 1298c2ecf20Sopenharmony_ci * OFFLINE blocks can't change the state and no subblocks will get 1308c2ecf20Sopenharmony_ci * plugged/unplugged. 1318c2ecf20Sopenharmony_ci */ 1328c2ecf20Sopenharmony_ci struct mutex hotplug_mutex; 1338c2ecf20Sopenharmony_ci bool hotplug_active; 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci /* An error occurred we cannot handle - stop processing requests. */ 1368c2ecf20Sopenharmony_ci bool broken; 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci /* The driver is being removed. */ 1398c2ecf20Sopenharmony_ci spinlock_t removal_lock; 1408c2ecf20Sopenharmony_ci bool removing; 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci /* Timer for retrying to plug/unplug memory. */ 1438c2ecf20Sopenharmony_ci struct hrtimer retry_timer; 1448c2ecf20Sopenharmony_ci unsigned int retry_timer_ms; 1458c2ecf20Sopenharmony_ci#define VIRTIO_MEM_RETRY_TIMER_MIN_MS 50000 1468c2ecf20Sopenharmony_ci#define VIRTIO_MEM_RETRY_TIMER_MAX_MS 300000 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci /* Memory notifier (online/offline events). */ 1498c2ecf20Sopenharmony_ci struct notifier_block memory_notifier; 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci /* Next device in the list of virtio-mem devices. */ 1528c2ecf20Sopenharmony_ci struct list_head next; 1538c2ecf20Sopenharmony_ci}; 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci/* 1568c2ecf20Sopenharmony_ci * We have to share a single online_page callback among all virtio-mem 1578c2ecf20Sopenharmony_ci * devices. We use RCU to iterate the list in the callback. 1588c2ecf20Sopenharmony_ci */ 1598c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(virtio_mem_mutex); 1608c2ecf20Sopenharmony_cistatic LIST_HEAD(virtio_mem_devices); 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_cistatic void virtio_mem_online_page_cb(struct page *page, unsigned int order); 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci/* 1658c2ecf20Sopenharmony_ci * Register a virtio-mem device so it will be considered for the online_page 1668c2ecf20Sopenharmony_ci * callback. 1678c2ecf20Sopenharmony_ci */ 1688c2ecf20Sopenharmony_cistatic int register_virtio_mem_device(struct virtio_mem *vm) 1698c2ecf20Sopenharmony_ci{ 1708c2ecf20Sopenharmony_ci int rc = 0; 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci /* First device registers the callback. */ 1738c2ecf20Sopenharmony_ci mutex_lock(&virtio_mem_mutex); 1748c2ecf20Sopenharmony_ci if (list_empty(&virtio_mem_devices)) 1758c2ecf20Sopenharmony_ci rc = set_online_page_callback(&virtio_mem_online_page_cb); 1768c2ecf20Sopenharmony_ci if (!rc) 1778c2ecf20Sopenharmony_ci list_add_rcu(&vm->next, &virtio_mem_devices); 1788c2ecf20Sopenharmony_ci mutex_unlock(&virtio_mem_mutex); 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci return rc; 1818c2ecf20Sopenharmony_ci} 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci/* 1848c2ecf20Sopenharmony_ci * Unregister a virtio-mem device so it will no longer be considered for the 1858c2ecf20Sopenharmony_ci * online_page callback. 1868c2ecf20Sopenharmony_ci */ 1878c2ecf20Sopenharmony_cistatic void unregister_virtio_mem_device(struct virtio_mem *vm) 1888c2ecf20Sopenharmony_ci{ 1898c2ecf20Sopenharmony_ci /* Last device unregisters the callback. */ 1908c2ecf20Sopenharmony_ci mutex_lock(&virtio_mem_mutex); 1918c2ecf20Sopenharmony_ci list_del_rcu(&vm->next); 1928c2ecf20Sopenharmony_ci if (list_empty(&virtio_mem_devices)) 1938c2ecf20Sopenharmony_ci restore_online_page_callback(&virtio_mem_online_page_cb); 1948c2ecf20Sopenharmony_ci mutex_unlock(&virtio_mem_mutex); 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci synchronize_rcu(); 1978c2ecf20Sopenharmony_ci} 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_ci/* 2008c2ecf20Sopenharmony_ci * Calculate the memory block id of a given address. 2018c2ecf20Sopenharmony_ci */ 2028c2ecf20Sopenharmony_cistatic unsigned long virtio_mem_phys_to_mb_id(unsigned long addr) 2038c2ecf20Sopenharmony_ci{ 2048c2ecf20Sopenharmony_ci return addr / memory_block_size_bytes(); 2058c2ecf20Sopenharmony_ci} 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci/* 2088c2ecf20Sopenharmony_ci * Calculate the physical start address of a given memory block id. 2098c2ecf20Sopenharmony_ci */ 2108c2ecf20Sopenharmony_cistatic unsigned long virtio_mem_mb_id_to_phys(unsigned long mb_id) 2118c2ecf20Sopenharmony_ci{ 2128c2ecf20Sopenharmony_ci return mb_id * memory_block_size_bytes(); 2138c2ecf20Sopenharmony_ci} 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci/* 2168c2ecf20Sopenharmony_ci * Calculate the subblock id of a given address. 2178c2ecf20Sopenharmony_ci */ 2188c2ecf20Sopenharmony_cistatic unsigned long virtio_mem_phys_to_sb_id(struct virtio_mem *vm, 2198c2ecf20Sopenharmony_ci unsigned long addr) 2208c2ecf20Sopenharmony_ci{ 2218c2ecf20Sopenharmony_ci const unsigned long mb_id = virtio_mem_phys_to_mb_id(addr); 2228c2ecf20Sopenharmony_ci const unsigned long mb_addr = virtio_mem_mb_id_to_phys(mb_id); 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci return (addr - mb_addr) / vm->subblock_size; 2258c2ecf20Sopenharmony_ci} 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_ci/* 2288c2ecf20Sopenharmony_ci * Set the state of a memory block, taking care of the state counter. 2298c2ecf20Sopenharmony_ci */ 2308c2ecf20Sopenharmony_cistatic void virtio_mem_mb_set_state(struct virtio_mem *vm, unsigned long mb_id, 2318c2ecf20Sopenharmony_ci enum virtio_mem_mb_state state) 2328c2ecf20Sopenharmony_ci{ 2338c2ecf20Sopenharmony_ci const unsigned long idx = mb_id - vm->first_mb_id; 2348c2ecf20Sopenharmony_ci enum virtio_mem_mb_state old_state; 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ci old_state = vm->mb_state[idx]; 2378c2ecf20Sopenharmony_ci vm->mb_state[idx] = state; 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_ci BUG_ON(vm->nb_mb_state[old_state] == 0); 2408c2ecf20Sopenharmony_ci vm->nb_mb_state[old_state]--; 2418c2ecf20Sopenharmony_ci vm->nb_mb_state[state]++; 2428c2ecf20Sopenharmony_ci} 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci/* 2458c2ecf20Sopenharmony_ci * Get the state of a memory block. 2468c2ecf20Sopenharmony_ci */ 2478c2ecf20Sopenharmony_cistatic enum virtio_mem_mb_state virtio_mem_mb_get_state(struct virtio_mem *vm, 2488c2ecf20Sopenharmony_ci unsigned long mb_id) 2498c2ecf20Sopenharmony_ci{ 2508c2ecf20Sopenharmony_ci const unsigned long idx = mb_id - vm->first_mb_id; 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_ci return vm->mb_state[idx]; 2538c2ecf20Sopenharmony_ci} 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_ci/* 2568c2ecf20Sopenharmony_ci * Prepare the state array for the next memory block. 2578c2ecf20Sopenharmony_ci */ 2588c2ecf20Sopenharmony_cistatic int virtio_mem_mb_state_prepare_next_mb(struct virtio_mem *vm) 2598c2ecf20Sopenharmony_ci{ 2608c2ecf20Sopenharmony_ci unsigned long old_bytes = vm->next_mb_id - vm->first_mb_id + 1; 2618c2ecf20Sopenharmony_ci unsigned long new_bytes = vm->next_mb_id - vm->first_mb_id + 2; 2628c2ecf20Sopenharmony_ci int old_pages = PFN_UP(old_bytes); 2638c2ecf20Sopenharmony_ci int new_pages = PFN_UP(new_bytes); 2648c2ecf20Sopenharmony_ci uint8_t *new_mb_state; 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_ci if (vm->mb_state && old_pages == new_pages) 2678c2ecf20Sopenharmony_ci return 0; 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_ci new_mb_state = vzalloc(new_pages * PAGE_SIZE); 2708c2ecf20Sopenharmony_ci if (!new_mb_state) 2718c2ecf20Sopenharmony_ci return -ENOMEM; 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_ci mutex_lock(&vm->hotplug_mutex); 2748c2ecf20Sopenharmony_ci if (vm->mb_state) 2758c2ecf20Sopenharmony_ci memcpy(new_mb_state, vm->mb_state, old_pages * PAGE_SIZE); 2768c2ecf20Sopenharmony_ci vfree(vm->mb_state); 2778c2ecf20Sopenharmony_ci vm->mb_state = new_mb_state; 2788c2ecf20Sopenharmony_ci mutex_unlock(&vm->hotplug_mutex); 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_ci return 0; 2818c2ecf20Sopenharmony_ci} 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_ci#define virtio_mem_for_each_mb_state(_vm, _mb_id, _state) \ 2848c2ecf20Sopenharmony_ci for (_mb_id = _vm->first_mb_id; \ 2858c2ecf20Sopenharmony_ci _mb_id < _vm->next_mb_id && _vm->nb_mb_state[_state]; \ 2868c2ecf20Sopenharmony_ci _mb_id++) \ 2878c2ecf20Sopenharmony_ci if (virtio_mem_mb_get_state(_vm, _mb_id) == _state) 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_ci#define virtio_mem_for_each_mb_state_rev(_vm, _mb_id, _state) \ 2908c2ecf20Sopenharmony_ci for (_mb_id = _vm->next_mb_id - 1; \ 2918c2ecf20Sopenharmony_ci _mb_id >= _vm->first_mb_id && _vm->nb_mb_state[_state]; \ 2928c2ecf20Sopenharmony_ci _mb_id--) \ 2938c2ecf20Sopenharmony_ci if (virtio_mem_mb_get_state(_vm, _mb_id) == _state) 2948c2ecf20Sopenharmony_ci 2958c2ecf20Sopenharmony_ci/* 2968c2ecf20Sopenharmony_ci * Mark all selected subblocks plugged. 2978c2ecf20Sopenharmony_ci * 2988c2ecf20Sopenharmony_ci * Will not modify the state of the memory block. 2998c2ecf20Sopenharmony_ci */ 3008c2ecf20Sopenharmony_cistatic void virtio_mem_mb_set_sb_plugged(struct virtio_mem *vm, 3018c2ecf20Sopenharmony_ci unsigned long mb_id, int sb_id, 3028c2ecf20Sopenharmony_ci int count) 3038c2ecf20Sopenharmony_ci{ 3048c2ecf20Sopenharmony_ci const int bit = (mb_id - vm->first_mb_id) * vm->nb_sb_per_mb + sb_id; 3058c2ecf20Sopenharmony_ci 3068c2ecf20Sopenharmony_ci __bitmap_set(vm->sb_bitmap, bit, count); 3078c2ecf20Sopenharmony_ci} 3088c2ecf20Sopenharmony_ci 3098c2ecf20Sopenharmony_ci/* 3108c2ecf20Sopenharmony_ci * Mark all selected subblocks unplugged. 3118c2ecf20Sopenharmony_ci * 3128c2ecf20Sopenharmony_ci * Will not modify the state of the memory block. 3138c2ecf20Sopenharmony_ci */ 3148c2ecf20Sopenharmony_cistatic void virtio_mem_mb_set_sb_unplugged(struct virtio_mem *vm, 3158c2ecf20Sopenharmony_ci unsigned long mb_id, int sb_id, 3168c2ecf20Sopenharmony_ci int count) 3178c2ecf20Sopenharmony_ci{ 3188c2ecf20Sopenharmony_ci const int bit = (mb_id - vm->first_mb_id) * vm->nb_sb_per_mb + sb_id; 3198c2ecf20Sopenharmony_ci 3208c2ecf20Sopenharmony_ci __bitmap_clear(vm->sb_bitmap, bit, count); 3218c2ecf20Sopenharmony_ci} 3228c2ecf20Sopenharmony_ci 3238c2ecf20Sopenharmony_ci/* 3248c2ecf20Sopenharmony_ci * Test if all selected subblocks are plugged. 3258c2ecf20Sopenharmony_ci */ 3268c2ecf20Sopenharmony_cistatic bool virtio_mem_mb_test_sb_plugged(struct virtio_mem *vm, 3278c2ecf20Sopenharmony_ci unsigned long mb_id, int sb_id, 3288c2ecf20Sopenharmony_ci int count) 3298c2ecf20Sopenharmony_ci{ 3308c2ecf20Sopenharmony_ci const int bit = (mb_id - vm->first_mb_id) * vm->nb_sb_per_mb + sb_id; 3318c2ecf20Sopenharmony_ci 3328c2ecf20Sopenharmony_ci if (count == 1) 3338c2ecf20Sopenharmony_ci return test_bit(bit, vm->sb_bitmap); 3348c2ecf20Sopenharmony_ci 3358c2ecf20Sopenharmony_ci /* TODO: Helper similar to bitmap_set() */ 3368c2ecf20Sopenharmony_ci return find_next_zero_bit(vm->sb_bitmap, bit + count, bit) >= 3378c2ecf20Sopenharmony_ci bit + count; 3388c2ecf20Sopenharmony_ci} 3398c2ecf20Sopenharmony_ci 3408c2ecf20Sopenharmony_ci/* 3418c2ecf20Sopenharmony_ci * Test if all selected subblocks are unplugged. 3428c2ecf20Sopenharmony_ci */ 3438c2ecf20Sopenharmony_cistatic bool virtio_mem_mb_test_sb_unplugged(struct virtio_mem *vm, 3448c2ecf20Sopenharmony_ci unsigned long mb_id, int sb_id, 3458c2ecf20Sopenharmony_ci int count) 3468c2ecf20Sopenharmony_ci{ 3478c2ecf20Sopenharmony_ci const int bit = (mb_id - vm->first_mb_id) * vm->nb_sb_per_mb + sb_id; 3488c2ecf20Sopenharmony_ci 3498c2ecf20Sopenharmony_ci /* TODO: Helper similar to bitmap_set() */ 3508c2ecf20Sopenharmony_ci return find_next_bit(vm->sb_bitmap, bit + count, bit) >= bit + count; 3518c2ecf20Sopenharmony_ci} 3528c2ecf20Sopenharmony_ci 3538c2ecf20Sopenharmony_ci/* 3548c2ecf20Sopenharmony_ci * Find the first unplugged subblock. Returns vm->nb_sb_per_mb in case there is 3558c2ecf20Sopenharmony_ci * none. 3568c2ecf20Sopenharmony_ci */ 3578c2ecf20Sopenharmony_cistatic int virtio_mem_mb_first_unplugged_sb(struct virtio_mem *vm, 3588c2ecf20Sopenharmony_ci unsigned long mb_id) 3598c2ecf20Sopenharmony_ci{ 3608c2ecf20Sopenharmony_ci const int bit = (mb_id - vm->first_mb_id) * vm->nb_sb_per_mb; 3618c2ecf20Sopenharmony_ci 3628c2ecf20Sopenharmony_ci return find_next_zero_bit(vm->sb_bitmap, bit + vm->nb_sb_per_mb, bit) - 3638c2ecf20Sopenharmony_ci bit; 3648c2ecf20Sopenharmony_ci} 3658c2ecf20Sopenharmony_ci 3668c2ecf20Sopenharmony_ci/* 3678c2ecf20Sopenharmony_ci * Prepare the subblock bitmap for the next memory block. 3688c2ecf20Sopenharmony_ci */ 3698c2ecf20Sopenharmony_cistatic int virtio_mem_sb_bitmap_prepare_next_mb(struct virtio_mem *vm) 3708c2ecf20Sopenharmony_ci{ 3718c2ecf20Sopenharmony_ci const unsigned long old_nb_mb = vm->next_mb_id - vm->first_mb_id; 3728c2ecf20Sopenharmony_ci const unsigned long old_nb_bits = old_nb_mb * vm->nb_sb_per_mb; 3738c2ecf20Sopenharmony_ci const unsigned long new_nb_bits = (old_nb_mb + 1) * vm->nb_sb_per_mb; 3748c2ecf20Sopenharmony_ci int old_pages = PFN_UP(BITS_TO_LONGS(old_nb_bits) * sizeof(long)); 3758c2ecf20Sopenharmony_ci int new_pages = PFN_UP(BITS_TO_LONGS(new_nb_bits) * sizeof(long)); 3768c2ecf20Sopenharmony_ci unsigned long *new_sb_bitmap, *old_sb_bitmap; 3778c2ecf20Sopenharmony_ci 3788c2ecf20Sopenharmony_ci if (vm->sb_bitmap && old_pages == new_pages) 3798c2ecf20Sopenharmony_ci return 0; 3808c2ecf20Sopenharmony_ci 3818c2ecf20Sopenharmony_ci new_sb_bitmap = vzalloc(new_pages * PAGE_SIZE); 3828c2ecf20Sopenharmony_ci if (!new_sb_bitmap) 3838c2ecf20Sopenharmony_ci return -ENOMEM; 3848c2ecf20Sopenharmony_ci 3858c2ecf20Sopenharmony_ci mutex_lock(&vm->hotplug_mutex); 3868c2ecf20Sopenharmony_ci if (new_sb_bitmap) 3878c2ecf20Sopenharmony_ci memcpy(new_sb_bitmap, vm->sb_bitmap, old_pages * PAGE_SIZE); 3888c2ecf20Sopenharmony_ci 3898c2ecf20Sopenharmony_ci old_sb_bitmap = vm->sb_bitmap; 3908c2ecf20Sopenharmony_ci vm->sb_bitmap = new_sb_bitmap; 3918c2ecf20Sopenharmony_ci mutex_unlock(&vm->hotplug_mutex); 3928c2ecf20Sopenharmony_ci 3938c2ecf20Sopenharmony_ci vfree(old_sb_bitmap); 3948c2ecf20Sopenharmony_ci return 0; 3958c2ecf20Sopenharmony_ci} 3968c2ecf20Sopenharmony_ci 3978c2ecf20Sopenharmony_ci/* 3988c2ecf20Sopenharmony_ci * Try to add a memory block to Linux. This will usually only fail 3998c2ecf20Sopenharmony_ci * if out of memory. 4008c2ecf20Sopenharmony_ci * 4018c2ecf20Sopenharmony_ci * Must not be called with the vm->hotplug_mutex held (possible deadlock with 4028c2ecf20Sopenharmony_ci * onlining code). 4038c2ecf20Sopenharmony_ci * 4048c2ecf20Sopenharmony_ci * Will not modify the state of the memory block. 4058c2ecf20Sopenharmony_ci */ 4068c2ecf20Sopenharmony_cistatic int virtio_mem_mb_add(struct virtio_mem *vm, unsigned long mb_id) 4078c2ecf20Sopenharmony_ci{ 4088c2ecf20Sopenharmony_ci const uint64_t addr = virtio_mem_mb_id_to_phys(mb_id); 4098c2ecf20Sopenharmony_ci int nid = vm->nid; 4108c2ecf20Sopenharmony_ci 4118c2ecf20Sopenharmony_ci if (nid == NUMA_NO_NODE) 4128c2ecf20Sopenharmony_ci nid = memory_add_physaddr_to_nid(addr); 4138c2ecf20Sopenharmony_ci 4148c2ecf20Sopenharmony_ci /* 4158c2ecf20Sopenharmony_ci * When force-unloading the driver and we still have memory added to 4168c2ecf20Sopenharmony_ci * Linux, the resource name has to stay. 4178c2ecf20Sopenharmony_ci */ 4188c2ecf20Sopenharmony_ci if (!vm->resource_name) { 4198c2ecf20Sopenharmony_ci vm->resource_name = kstrdup_const("System RAM (virtio_mem)", 4208c2ecf20Sopenharmony_ci GFP_KERNEL); 4218c2ecf20Sopenharmony_ci if (!vm->resource_name) 4228c2ecf20Sopenharmony_ci return -ENOMEM; 4238c2ecf20Sopenharmony_ci } 4248c2ecf20Sopenharmony_ci 4258c2ecf20Sopenharmony_ci dev_dbg(&vm->vdev->dev, "adding memory block: %lu\n", mb_id); 4268c2ecf20Sopenharmony_ci return add_memory_driver_managed(nid, addr, memory_block_size_bytes(), 4278c2ecf20Sopenharmony_ci vm->resource_name, 4288c2ecf20Sopenharmony_ci MEMHP_MERGE_RESOURCE); 4298c2ecf20Sopenharmony_ci} 4308c2ecf20Sopenharmony_ci 4318c2ecf20Sopenharmony_ci/* 4328c2ecf20Sopenharmony_ci * Try to remove a memory block from Linux. Will only fail if the memory block 4338c2ecf20Sopenharmony_ci * is not offline. 4348c2ecf20Sopenharmony_ci * 4358c2ecf20Sopenharmony_ci * Must not be called with the vm->hotplug_mutex held (possible deadlock with 4368c2ecf20Sopenharmony_ci * onlining code). 4378c2ecf20Sopenharmony_ci * 4388c2ecf20Sopenharmony_ci * Will not modify the state of the memory block. 4398c2ecf20Sopenharmony_ci */ 4408c2ecf20Sopenharmony_cistatic int virtio_mem_mb_remove(struct virtio_mem *vm, unsigned long mb_id) 4418c2ecf20Sopenharmony_ci{ 4428c2ecf20Sopenharmony_ci const uint64_t addr = virtio_mem_mb_id_to_phys(mb_id); 4438c2ecf20Sopenharmony_ci int nid = vm->nid; 4448c2ecf20Sopenharmony_ci 4458c2ecf20Sopenharmony_ci if (nid == NUMA_NO_NODE) 4468c2ecf20Sopenharmony_ci nid = memory_add_physaddr_to_nid(addr); 4478c2ecf20Sopenharmony_ci 4488c2ecf20Sopenharmony_ci dev_dbg(&vm->vdev->dev, "removing memory block: %lu\n", mb_id); 4498c2ecf20Sopenharmony_ci return remove_memory(nid, addr, memory_block_size_bytes()); 4508c2ecf20Sopenharmony_ci} 4518c2ecf20Sopenharmony_ci 4528c2ecf20Sopenharmony_ci/* 4538c2ecf20Sopenharmony_ci * Try to offline and remove a memory block from Linux. 4548c2ecf20Sopenharmony_ci * 4558c2ecf20Sopenharmony_ci * Must not be called with the vm->hotplug_mutex held (possible deadlock with 4568c2ecf20Sopenharmony_ci * onlining code). 4578c2ecf20Sopenharmony_ci * 4588c2ecf20Sopenharmony_ci * Will not modify the state of the memory block. 4598c2ecf20Sopenharmony_ci */ 4608c2ecf20Sopenharmony_cistatic int virtio_mem_mb_offline_and_remove(struct virtio_mem *vm, 4618c2ecf20Sopenharmony_ci unsigned long mb_id) 4628c2ecf20Sopenharmony_ci{ 4638c2ecf20Sopenharmony_ci const uint64_t addr = virtio_mem_mb_id_to_phys(mb_id); 4648c2ecf20Sopenharmony_ci int nid = vm->nid; 4658c2ecf20Sopenharmony_ci 4668c2ecf20Sopenharmony_ci if (nid == NUMA_NO_NODE) 4678c2ecf20Sopenharmony_ci nid = memory_add_physaddr_to_nid(addr); 4688c2ecf20Sopenharmony_ci 4698c2ecf20Sopenharmony_ci dev_dbg(&vm->vdev->dev, "offlining and removing memory block: %lu\n", 4708c2ecf20Sopenharmony_ci mb_id); 4718c2ecf20Sopenharmony_ci return offline_and_remove_memory(nid, addr, memory_block_size_bytes()); 4728c2ecf20Sopenharmony_ci} 4738c2ecf20Sopenharmony_ci 4748c2ecf20Sopenharmony_ci/* 4758c2ecf20Sopenharmony_ci * Trigger the workqueue so the device can perform its magic. 4768c2ecf20Sopenharmony_ci */ 4778c2ecf20Sopenharmony_cistatic void virtio_mem_retry(struct virtio_mem *vm) 4788c2ecf20Sopenharmony_ci{ 4798c2ecf20Sopenharmony_ci unsigned long flags; 4808c2ecf20Sopenharmony_ci 4818c2ecf20Sopenharmony_ci spin_lock_irqsave(&vm->removal_lock, flags); 4828c2ecf20Sopenharmony_ci if (!vm->removing) 4838c2ecf20Sopenharmony_ci queue_work(system_freezable_wq, &vm->wq); 4848c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&vm->removal_lock, flags); 4858c2ecf20Sopenharmony_ci} 4868c2ecf20Sopenharmony_ci 4878c2ecf20Sopenharmony_cistatic int virtio_mem_translate_node_id(struct virtio_mem *vm, uint16_t node_id) 4888c2ecf20Sopenharmony_ci{ 4898c2ecf20Sopenharmony_ci int node = NUMA_NO_NODE; 4908c2ecf20Sopenharmony_ci 4918c2ecf20Sopenharmony_ci#if defined(CONFIG_ACPI_NUMA) 4928c2ecf20Sopenharmony_ci if (virtio_has_feature(vm->vdev, VIRTIO_MEM_F_ACPI_PXM)) 4938c2ecf20Sopenharmony_ci node = pxm_to_node(node_id); 4948c2ecf20Sopenharmony_ci#endif 4958c2ecf20Sopenharmony_ci return node; 4968c2ecf20Sopenharmony_ci} 4978c2ecf20Sopenharmony_ci 4988c2ecf20Sopenharmony_ci/* 4998c2ecf20Sopenharmony_ci * Test if a virtio-mem device overlaps with the given range. Can be called 5008c2ecf20Sopenharmony_ci * from (notifier) callbacks lockless. 5018c2ecf20Sopenharmony_ci */ 5028c2ecf20Sopenharmony_cistatic bool virtio_mem_overlaps_range(struct virtio_mem *vm, 5038c2ecf20Sopenharmony_ci unsigned long start, unsigned long size) 5048c2ecf20Sopenharmony_ci{ 5058c2ecf20Sopenharmony_ci unsigned long dev_start = virtio_mem_mb_id_to_phys(vm->first_mb_id); 5068c2ecf20Sopenharmony_ci unsigned long dev_end = virtio_mem_mb_id_to_phys(vm->last_mb_id) + 5078c2ecf20Sopenharmony_ci memory_block_size_bytes(); 5088c2ecf20Sopenharmony_ci 5098c2ecf20Sopenharmony_ci return start < dev_end && dev_start < start + size; 5108c2ecf20Sopenharmony_ci} 5118c2ecf20Sopenharmony_ci 5128c2ecf20Sopenharmony_ci/* 5138c2ecf20Sopenharmony_ci * Test if a virtio-mem device owns a memory block. Can be called from 5148c2ecf20Sopenharmony_ci * (notifier) callbacks lockless. 5158c2ecf20Sopenharmony_ci */ 5168c2ecf20Sopenharmony_cistatic bool virtio_mem_owned_mb(struct virtio_mem *vm, unsigned long mb_id) 5178c2ecf20Sopenharmony_ci{ 5188c2ecf20Sopenharmony_ci return mb_id >= vm->first_mb_id && mb_id <= vm->last_mb_id; 5198c2ecf20Sopenharmony_ci} 5208c2ecf20Sopenharmony_ci 5218c2ecf20Sopenharmony_cistatic int virtio_mem_notify_going_online(struct virtio_mem *vm, 5228c2ecf20Sopenharmony_ci unsigned long mb_id) 5238c2ecf20Sopenharmony_ci{ 5248c2ecf20Sopenharmony_ci switch (virtio_mem_mb_get_state(vm, mb_id)) { 5258c2ecf20Sopenharmony_ci case VIRTIO_MEM_MB_STATE_OFFLINE_PARTIAL: 5268c2ecf20Sopenharmony_ci case VIRTIO_MEM_MB_STATE_OFFLINE: 5278c2ecf20Sopenharmony_ci return NOTIFY_OK; 5288c2ecf20Sopenharmony_ci default: 5298c2ecf20Sopenharmony_ci break; 5308c2ecf20Sopenharmony_ci } 5318c2ecf20Sopenharmony_ci dev_warn_ratelimited(&vm->vdev->dev, 5328c2ecf20Sopenharmony_ci "memory block onlining denied\n"); 5338c2ecf20Sopenharmony_ci return NOTIFY_BAD; 5348c2ecf20Sopenharmony_ci} 5358c2ecf20Sopenharmony_ci 5368c2ecf20Sopenharmony_cistatic void virtio_mem_notify_offline(struct virtio_mem *vm, 5378c2ecf20Sopenharmony_ci unsigned long mb_id) 5388c2ecf20Sopenharmony_ci{ 5398c2ecf20Sopenharmony_ci switch (virtio_mem_mb_get_state(vm, mb_id)) { 5408c2ecf20Sopenharmony_ci case VIRTIO_MEM_MB_STATE_ONLINE_PARTIAL: 5418c2ecf20Sopenharmony_ci virtio_mem_mb_set_state(vm, mb_id, 5428c2ecf20Sopenharmony_ci VIRTIO_MEM_MB_STATE_OFFLINE_PARTIAL); 5438c2ecf20Sopenharmony_ci break; 5448c2ecf20Sopenharmony_ci case VIRTIO_MEM_MB_STATE_ONLINE: 5458c2ecf20Sopenharmony_ci virtio_mem_mb_set_state(vm, mb_id, 5468c2ecf20Sopenharmony_ci VIRTIO_MEM_MB_STATE_OFFLINE); 5478c2ecf20Sopenharmony_ci break; 5488c2ecf20Sopenharmony_ci default: 5498c2ecf20Sopenharmony_ci BUG(); 5508c2ecf20Sopenharmony_ci break; 5518c2ecf20Sopenharmony_ci } 5528c2ecf20Sopenharmony_ci 5538c2ecf20Sopenharmony_ci /* 5548c2ecf20Sopenharmony_ci * Trigger the workqueue, maybe we can now unplug memory. Also, 5558c2ecf20Sopenharmony_ci * when we offline and remove a memory block, this will re-trigger 5568c2ecf20Sopenharmony_ci * us immediately - which is often nice because the removal of 5578c2ecf20Sopenharmony_ci * the memory block (e.g., memmap) might have freed up memory 5588c2ecf20Sopenharmony_ci * on other memory blocks we manage. 5598c2ecf20Sopenharmony_ci */ 5608c2ecf20Sopenharmony_ci virtio_mem_retry(vm); 5618c2ecf20Sopenharmony_ci} 5628c2ecf20Sopenharmony_ci 5638c2ecf20Sopenharmony_cistatic void virtio_mem_notify_online(struct virtio_mem *vm, unsigned long mb_id) 5648c2ecf20Sopenharmony_ci{ 5658c2ecf20Sopenharmony_ci unsigned long nb_offline; 5668c2ecf20Sopenharmony_ci 5678c2ecf20Sopenharmony_ci switch (virtio_mem_mb_get_state(vm, mb_id)) { 5688c2ecf20Sopenharmony_ci case VIRTIO_MEM_MB_STATE_OFFLINE_PARTIAL: 5698c2ecf20Sopenharmony_ci virtio_mem_mb_set_state(vm, mb_id, 5708c2ecf20Sopenharmony_ci VIRTIO_MEM_MB_STATE_ONLINE_PARTIAL); 5718c2ecf20Sopenharmony_ci break; 5728c2ecf20Sopenharmony_ci case VIRTIO_MEM_MB_STATE_OFFLINE: 5738c2ecf20Sopenharmony_ci virtio_mem_mb_set_state(vm, mb_id, VIRTIO_MEM_MB_STATE_ONLINE); 5748c2ecf20Sopenharmony_ci break; 5758c2ecf20Sopenharmony_ci default: 5768c2ecf20Sopenharmony_ci BUG(); 5778c2ecf20Sopenharmony_ci break; 5788c2ecf20Sopenharmony_ci } 5798c2ecf20Sopenharmony_ci nb_offline = vm->nb_mb_state[VIRTIO_MEM_MB_STATE_OFFLINE] + 5808c2ecf20Sopenharmony_ci vm->nb_mb_state[VIRTIO_MEM_MB_STATE_OFFLINE_PARTIAL]; 5818c2ecf20Sopenharmony_ci 5828c2ecf20Sopenharmony_ci /* see if we can add new blocks now that we onlined one block */ 5838c2ecf20Sopenharmony_ci if (nb_offline == VIRTIO_MEM_NB_OFFLINE_THRESHOLD - 1) 5848c2ecf20Sopenharmony_ci virtio_mem_retry(vm); 5858c2ecf20Sopenharmony_ci} 5868c2ecf20Sopenharmony_ci 5878c2ecf20Sopenharmony_cistatic void virtio_mem_notify_going_offline(struct virtio_mem *vm, 5888c2ecf20Sopenharmony_ci unsigned long mb_id) 5898c2ecf20Sopenharmony_ci{ 5908c2ecf20Sopenharmony_ci const unsigned long nr_pages = PFN_DOWN(vm->subblock_size); 5918c2ecf20Sopenharmony_ci struct page *page; 5928c2ecf20Sopenharmony_ci unsigned long pfn; 5938c2ecf20Sopenharmony_ci int sb_id, i; 5948c2ecf20Sopenharmony_ci 5958c2ecf20Sopenharmony_ci for (sb_id = 0; sb_id < vm->nb_sb_per_mb; sb_id++) { 5968c2ecf20Sopenharmony_ci if (virtio_mem_mb_test_sb_plugged(vm, mb_id, sb_id, 1)) 5978c2ecf20Sopenharmony_ci continue; 5988c2ecf20Sopenharmony_ci /* 5998c2ecf20Sopenharmony_ci * Drop our reference to the pages so the memory can get 6008c2ecf20Sopenharmony_ci * offlined and add the unplugged pages to the managed 6018c2ecf20Sopenharmony_ci * page counters (so offlining code can correctly subtract 6028c2ecf20Sopenharmony_ci * them again). 6038c2ecf20Sopenharmony_ci */ 6048c2ecf20Sopenharmony_ci pfn = PFN_DOWN(virtio_mem_mb_id_to_phys(mb_id) + 6058c2ecf20Sopenharmony_ci sb_id * vm->subblock_size); 6068c2ecf20Sopenharmony_ci adjust_managed_page_count(pfn_to_page(pfn), nr_pages); 6078c2ecf20Sopenharmony_ci for (i = 0; i < nr_pages; i++) { 6088c2ecf20Sopenharmony_ci page = pfn_to_page(pfn + i); 6098c2ecf20Sopenharmony_ci if (WARN_ON(!page_ref_dec_and_test(page))) 6108c2ecf20Sopenharmony_ci dump_page(page, "unplugged page referenced"); 6118c2ecf20Sopenharmony_ci } 6128c2ecf20Sopenharmony_ci } 6138c2ecf20Sopenharmony_ci} 6148c2ecf20Sopenharmony_ci 6158c2ecf20Sopenharmony_cistatic void virtio_mem_notify_cancel_offline(struct virtio_mem *vm, 6168c2ecf20Sopenharmony_ci unsigned long mb_id) 6178c2ecf20Sopenharmony_ci{ 6188c2ecf20Sopenharmony_ci const unsigned long nr_pages = PFN_DOWN(vm->subblock_size); 6198c2ecf20Sopenharmony_ci unsigned long pfn; 6208c2ecf20Sopenharmony_ci int sb_id, i; 6218c2ecf20Sopenharmony_ci 6228c2ecf20Sopenharmony_ci for (sb_id = 0; sb_id < vm->nb_sb_per_mb; sb_id++) { 6238c2ecf20Sopenharmony_ci if (virtio_mem_mb_test_sb_plugged(vm, mb_id, sb_id, 1)) 6248c2ecf20Sopenharmony_ci continue; 6258c2ecf20Sopenharmony_ci /* 6268c2ecf20Sopenharmony_ci * Get the reference we dropped when going offline and 6278c2ecf20Sopenharmony_ci * subtract the unplugged pages from the managed page 6288c2ecf20Sopenharmony_ci * counters. 6298c2ecf20Sopenharmony_ci */ 6308c2ecf20Sopenharmony_ci pfn = PFN_DOWN(virtio_mem_mb_id_to_phys(mb_id) + 6318c2ecf20Sopenharmony_ci sb_id * vm->subblock_size); 6328c2ecf20Sopenharmony_ci adjust_managed_page_count(pfn_to_page(pfn), -nr_pages); 6338c2ecf20Sopenharmony_ci for (i = 0; i < nr_pages; i++) 6348c2ecf20Sopenharmony_ci page_ref_inc(pfn_to_page(pfn + i)); 6358c2ecf20Sopenharmony_ci } 6368c2ecf20Sopenharmony_ci} 6378c2ecf20Sopenharmony_ci 6388c2ecf20Sopenharmony_ci/* 6398c2ecf20Sopenharmony_ci * This callback will either be called synchronously from add_memory() or 6408c2ecf20Sopenharmony_ci * asynchronously (e.g., triggered via user space). We have to be careful 6418c2ecf20Sopenharmony_ci * with locking when calling add_memory(). 6428c2ecf20Sopenharmony_ci */ 6438c2ecf20Sopenharmony_cistatic int virtio_mem_memory_notifier_cb(struct notifier_block *nb, 6448c2ecf20Sopenharmony_ci unsigned long action, void *arg) 6458c2ecf20Sopenharmony_ci{ 6468c2ecf20Sopenharmony_ci struct virtio_mem *vm = container_of(nb, struct virtio_mem, 6478c2ecf20Sopenharmony_ci memory_notifier); 6488c2ecf20Sopenharmony_ci struct memory_notify *mhp = arg; 6498c2ecf20Sopenharmony_ci const unsigned long start = PFN_PHYS(mhp->start_pfn); 6508c2ecf20Sopenharmony_ci const unsigned long size = PFN_PHYS(mhp->nr_pages); 6518c2ecf20Sopenharmony_ci const unsigned long mb_id = virtio_mem_phys_to_mb_id(start); 6528c2ecf20Sopenharmony_ci int rc = NOTIFY_OK; 6538c2ecf20Sopenharmony_ci 6548c2ecf20Sopenharmony_ci if (!virtio_mem_overlaps_range(vm, start, size)) 6558c2ecf20Sopenharmony_ci return NOTIFY_DONE; 6568c2ecf20Sopenharmony_ci 6578c2ecf20Sopenharmony_ci /* 6588c2ecf20Sopenharmony_ci * Memory is onlined/offlined in memory block granularity. We cannot 6598c2ecf20Sopenharmony_ci * cross virtio-mem device boundaries and memory block boundaries. Bail 6608c2ecf20Sopenharmony_ci * out if this ever changes. 6618c2ecf20Sopenharmony_ci */ 6628c2ecf20Sopenharmony_ci if (WARN_ON_ONCE(size != memory_block_size_bytes() || 6638c2ecf20Sopenharmony_ci !IS_ALIGNED(start, memory_block_size_bytes()))) 6648c2ecf20Sopenharmony_ci return NOTIFY_BAD; 6658c2ecf20Sopenharmony_ci 6668c2ecf20Sopenharmony_ci /* 6678c2ecf20Sopenharmony_ci * Avoid circular locking lockdep warnings. We lock the mutex 6688c2ecf20Sopenharmony_ci * e.g., in MEM_GOING_ONLINE and unlock it in MEM_ONLINE. The 6698c2ecf20Sopenharmony_ci * blocking_notifier_call_chain() has it's own lock, which gets unlocked 6708c2ecf20Sopenharmony_ci * between both notifier calls and will bail out. False positive. 6718c2ecf20Sopenharmony_ci */ 6728c2ecf20Sopenharmony_ci lockdep_off(); 6738c2ecf20Sopenharmony_ci 6748c2ecf20Sopenharmony_ci switch (action) { 6758c2ecf20Sopenharmony_ci case MEM_GOING_OFFLINE: 6768c2ecf20Sopenharmony_ci mutex_lock(&vm->hotplug_mutex); 6778c2ecf20Sopenharmony_ci if (vm->removing) { 6788c2ecf20Sopenharmony_ci rc = notifier_from_errno(-EBUSY); 6798c2ecf20Sopenharmony_ci mutex_unlock(&vm->hotplug_mutex); 6808c2ecf20Sopenharmony_ci break; 6818c2ecf20Sopenharmony_ci } 6828c2ecf20Sopenharmony_ci vm->hotplug_active = true; 6838c2ecf20Sopenharmony_ci virtio_mem_notify_going_offline(vm, mb_id); 6848c2ecf20Sopenharmony_ci break; 6858c2ecf20Sopenharmony_ci case MEM_GOING_ONLINE: 6868c2ecf20Sopenharmony_ci mutex_lock(&vm->hotplug_mutex); 6878c2ecf20Sopenharmony_ci if (vm->removing) { 6888c2ecf20Sopenharmony_ci rc = notifier_from_errno(-EBUSY); 6898c2ecf20Sopenharmony_ci mutex_unlock(&vm->hotplug_mutex); 6908c2ecf20Sopenharmony_ci break; 6918c2ecf20Sopenharmony_ci } 6928c2ecf20Sopenharmony_ci vm->hotplug_active = true; 6938c2ecf20Sopenharmony_ci rc = virtio_mem_notify_going_online(vm, mb_id); 6948c2ecf20Sopenharmony_ci break; 6958c2ecf20Sopenharmony_ci case MEM_OFFLINE: 6968c2ecf20Sopenharmony_ci virtio_mem_notify_offline(vm, mb_id); 6978c2ecf20Sopenharmony_ci vm->hotplug_active = false; 6988c2ecf20Sopenharmony_ci mutex_unlock(&vm->hotplug_mutex); 6998c2ecf20Sopenharmony_ci break; 7008c2ecf20Sopenharmony_ci case MEM_ONLINE: 7018c2ecf20Sopenharmony_ci virtio_mem_notify_online(vm, mb_id); 7028c2ecf20Sopenharmony_ci vm->hotplug_active = false; 7038c2ecf20Sopenharmony_ci mutex_unlock(&vm->hotplug_mutex); 7048c2ecf20Sopenharmony_ci break; 7058c2ecf20Sopenharmony_ci case MEM_CANCEL_OFFLINE: 7068c2ecf20Sopenharmony_ci if (!vm->hotplug_active) 7078c2ecf20Sopenharmony_ci break; 7088c2ecf20Sopenharmony_ci virtio_mem_notify_cancel_offline(vm, mb_id); 7098c2ecf20Sopenharmony_ci vm->hotplug_active = false; 7108c2ecf20Sopenharmony_ci mutex_unlock(&vm->hotplug_mutex); 7118c2ecf20Sopenharmony_ci break; 7128c2ecf20Sopenharmony_ci case MEM_CANCEL_ONLINE: 7138c2ecf20Sopenharmony_ci if (!vm->hotplug_active) 7148c2ecf20Sopenharmony_ci break; 7158c2ecf20Sopenharmony_ci vm->hotplug_active = false; 7168c2ecf20Sopenharmony_ci mutex_unlock(&vm->hotplug_mutex); 7178c2ecf20Sopenharmony_ci break; 7188c2ecf20Sopenharmony_ci default: 7198c2ecf20Sopenharmony_ci break; 7208c2ecf20Sopenharmony_ci } 7218c2ecf20Sopenharmony_ci 7228c2ecf20Sopenharmony_ci lockdep_on(); 7238c2ecf20Sopenharmony_ci 7248c2ecf20Sopenharmony_ci return rc; 7258c2ecf20Sopenharmony_ci} 7268c2ecf20Sopenharmony_ci 7278c2ecf20Sopenharmony_ci/* 7288c2ecf20Sopenharmony_ci * Set a range of pages PG_offline. Remember pages that were never onlined 7298c2ecf20Sopenharmony_ci * (via generic_online_page()) using PageDirty(). 7308c2ecf20Sopenharmony_ci */ 7318c2ecf20Sopenharmony_cistatic void virtio_mem_set_fake_offline(unsigned long pfn, 7328c2ecf20Sopenharmony_ci unsigned int nr_pages, bool onlined) 7338c2ecf20Sopenharmony_ci{ 7348c2ecf20Sopenharmony_ci for (; nr_pages--; pfn++) { 7358c2ecf20Sopenharmony_ci struct page *page = pfn_to_page(pfn); 7368c2ecf20Sopenharmony_ci 7378c2ecf20Sopenharmony_ci __SetPageOffline(page); 7388c2ecf20Sopenharmony_ci if (!onlined) { 7398c2ecf20Sopenharmony_ci SetPageDirty(page); 7408c2ecf20Sopenharmony_ci /* FIXME: remove after cleanups */ 7418c2ecf20Sopenharmony_ci ClearPageReserved(page); 7428c2ecf20Sopenharmony_ci } 7438c2ecf20Sopenharmony_ci } 7448c2ecf20Sopenharmony_ci} 7458c2ecf20Sopenharmony_ci 7468c2ecf20Sopenharmony_ci/* 7478c2ecf20Sopenharmony_ci * Clear PG_offline from a range of pages. If the pages were never onlined, 7488c2ecf20Sopenharmony_ci * (via generic_online_page()), clear PageDirty(). 7498c2ecf20Sopenharmony_ci */ 7508c2ecf20Sopenharmony_cistatic void virtio_mem_clear_fake_offline(unsigned long pfn, 7518c2ecf20Sopenharmony_ci unsigned int nr_pages, bool onlined) 7528c2ecf20Sopenharmony_ci{ 7538c2ecf20Sopenharmony_ci for (; nr_pages--; pfn++) { 7548c2ecf20Sopenharmony_ci struct page *page = pfn_to_page(pfn); 7558c2ecf20Sopenharmony_ci 7568c2ecf20Sopenharmony_ci __ClearPageOffline(page); 7578c2ecf20Sopenharmony_ci if (!onlined) 7588c2ecf20Sopenharmony_ci ClearPageDirty(page); 7598c2ecf20Sopenharmony_ci } 7608c2ecf20Sopenharmony_ci} 7618c2ecf20Sopenharmony_ci 7628c2ecf20Sopenharmony_ci/* 7638c2ecf20Sopenharmony_ci * Release a range of fake-offline pages to the buddy, effectively 7648c2ecf20Sopenharmony_ci * fake-onlining them. 7658c2ecf20Sopenharmony_ci */ 7668c2ecf20Sopenharmony_cistatic void virtio_mem_fake_online(unsigned long pfn, unsigned int nr_pages) 7678c2ecf20Sopenharmony_ci{ 7688c2ecf20Sopenharmony_ci const int order = MAX_ORDER - 1; 7698c2ecf20Sopenharmony_ci int i; 7708c2ecf20Sopenharmony_ci 7718c2ecf20Sopenharmony_ci /* 7728c2ecf20Sopenharmony_ci * We are always called with subblock granularity, which is at least 7738c2ecf20Sopenharmony_ci * aligned to MAX_ORDER - 1. 7748c2ecf20Sopenharmony_ci */ 7758c2ecf20Sopenharmony_ci for (i = 0; i < nr_pages; i += 1 << order) { 7768c2ecf20Sopenharmony_ci struct page *page = pfn_to_page(pfn + i); 7778c2ecf20Sopenharmony_ci 7788c2ecf20Sopenharmony_ci /* 7798c2ecf20Sopenharmony_ci * If the page is PageDirty(), it was kept fake-offline when 7808c2ecf20Sopenharmony_ci * onlining the memory block. Otherwise, it was allocated 7818c2ecf20Sopenharmony_ci * using alloc_contig_range(). All pages in a subblock are 7828c2ecf20Sopenharmony_ci * alike. 7838c2ecf20Sopenharmony_ci */ 7848c2ecf20Sopenharmony_ci if (PageDirty(page)) { 7858c2ecf20Sopenharmony_ci virtio_mem_clear_fake_offline(pfn + i, 1 << order, 7868c2ecf20Sopenharmony_ci false); 7878c2ecf20Sopenharmony_ci generic_online_page(page, order); 7888c2ecf20Sopenharmony_ci } else { 7898c2ecf20Sopenharmony_ci virtio_mem_clear_fake_offline(pfn + i, 1 << order, 7908c2ecf20Sopenharmony_ci true); 7918c2ecf20Sopenharmony_ci free_contig_range(pfn + i, 1 << order); 7928c2ecf20Sopenharmony_ci adjust_managed_page_count(page, 1 << order); 7938c2ecf20Sopenharmony_ci } 7948c2ecf20Sopenharmony_ci } 7958c2ecf20Sopenharmony_ci} 7968c2ecf20Sopenharmony_ci 7978c2ecf20Sopenharmony_cistatic void virtio_mem_online_page_cb(struct page *page, unsigned int order) 7988c2ecf20Sopenharmony_ci{ 7998c2ecf20Sopenharmony_ci const unsigned long addr = page_to_phys(page); 8008c2ecf20Sopenharmony_ci const unsigned long mb_id = virtio_mem_phys_to_mb_id(addr); 8018c2ecf20Sopenharmony_ci struct virtio_mem *vm; 8028c2ecf20Sopenharmony_ci int sb_id; 8038c2ecf20Sopenharmony_ci 8048c2ecf20Sopenharmony_ci /* 8058c2ecf20Sopenharmony_ci * We exploit here that subblocks have at least MAX_ORDER - 1 8068c2ecf20Sopenharmony_ci * size/alignment and that this callback is is called with such a 8078c2ecf20Sopenharmony_ci * size/alignment. So we cannot cross subblocks and therefore 8088c2ecf20Sopenharmony_ci * also not memory blocks. 8098c2ecf20Sopenharmony_ci */ 8108c2ecf20Sopenharmony_ci rcu_read_lock(); 8118c2ecf20Sopenharmony_ci list_for_each_entry_rcu(vm, &virtio_mem_devices, next) { 8128c2ecf20Sopenharmony_ci if (!virtio_mem_owned_mb(vm, mb_id)) 8138c2ecf20Sopenharmony_ci continue; 8148c2ecf20Sopenharmony_ci 8158c2ecf20Sopenharmony_ci sb_id = virtio_mem_phys_to_sb_id(vm, addr); 8168c2ecf20Sopenharmony_ci /* 8178c2ecf20Sopenharmony_ci * If plugged, online the pages, otherwise, set them fake 8188c2ecf20Sopenharmony_ci * offline (PageOffline). 8198c2ecf20Sopenharmony_ci */ 8208c2ecf20Sopenharmony_ci if (virtio_mem_mb_test_sb_plugged(vm, mb_id, sb_id, 1)) 8218c2ecf20Sopenharmony_ci generic_online_page(page, order); 8228c2ecf20Sopenharmony_ci else 8238c2ecf20Sopenharmony_ci virtio_mem_set_fake_offline(PFN_DOWN(addr), 1 << order, 8248c2ecf20Sopenharmony_ci false); 8258c2ecf20Sopenharmony_ci rcu_read_unlock(); 8268c2ecf20Sopenharmony_ci return; 8278c2ecf20Sopenharmony_ci } 8288c2ecf20Sopenharmony_ci rcu_read_unlock(); 8298c2ecf20Sopenharmony_ci 8308c2ecf20Sopenharmony_ci /* not virtio-mem memory, but e.g., a DIMM. online it */ 8318c2ecf20Sopenharmony_ci generic_online_page(page, order); 8328c2ecf20Sopenharmony_ci} 8338c2ecf20Sopenharmony_ci 8348c2ecf20Sopenharmony_cistatic uint64_t virtio_mem_send_request(struct virtio_mem *vm, 8358c2ecf20Sopenharmony_ci const struct virtio_mem_req *req) 8368c2ecf20Sopenharmony_ci{ 8378c2ecf20Sopenharmony_ci struct scatterlist *sgs[2], sg_req, sg_resp; 8388c2ecf20Sopenharmony_ci unsigned int len; 8398c2ecf20Sopenharmony_ci int rc; 8408c2ecf20Sopenharmony_ci 8418c2ecf20Sopenharmony_ci /* don't use the request residing on the stack (vaddr) */ 8428c2ecf20Sopenharmony_ci vm->req = *req; 8438c2ecf20Sopenharmony_ci 8448c2ecf20Sopenharmony_ci /* out: buffer for request */ 8458c2ecf20Sopenharmony_ci sg_init_one(&sg_req, &vm->req, sizeof(vm->req)); 8468c2ecf20Sopenharmony_ci sgs[0] = &sg_req; 8478c2ecf20Sopenharmony_ci 8488c2ecf20Sopenharmony_ci /* in: buffer for response */ 8498c2ecf20Sopenharmony_ci sg_init_one(&sg_resp, &vm->resp, sizeof(vm->resp)); 8508c2ecf20Sopenharmony_ci sgs[1] = &sg_resp; 8518c2ecf20Sopenharmony_ci 8528c2ecf20Sopenharmony_ci rc = virtqueue_add_sgs(vm->vq, sgs, 1, 1, vm, GFP_KERNEL); 8538c2ecf20Sopenharmony_ci if (rc < 0) 8548c2ecf20Sopenharmony_ci return rc; 8558c2ecf20Sopenharmony_ci 8568c2ecf20Sopenharmony_ci virtqueue_kick(vm->vq); 8578c2ecf20Sopenharmony_ci 8588c2ecf20Sopenharmony_ci /* wait for a response */ 8598c2ecf20Sopenharmony_ci wait_event(vm->host_resp, virtqueue_get_buf(vm->vq, &len)); 8608c2ecf20Sopenharmony_ci 8618c2ecf20Sopenharmony_ci return virtio16_to_cpu(vm->vdev, vm->resp.type); 8628c2ecf20Sopenharmony_ci} 8638c2ecf20Sopenharmony_ci 8648c2ecf20Sopenharmony_cistatic int virtio_mem_send_plug_request(struct virtio_mem *vm, uint64_t addr, 8658c2ecf20Sopenharmony_ci uint64_t size) 8668c2ecf20Sopenharmony_ci{ 8678c2ecf20Sopenharmony_ci const uint64_t nb_vm_blocks = size / vm->device_block_size; 8688c2ecf20Sopenharmony_ci const struct virtio_mem_req req = { 8698c2ecf20Sopenharmony_ci .type = cpu_to_virtio16(vm->vdev, VIRTIO_MEM_REQ_PLUG), 8708c2ecf20Sopenharmony_ci .u.plug.addr = cpu_to_virtio64(vm->vdev, addr), 8718c2ecf20Sopenharmony_ci .u.plug.nb_blocks = cpu_to_virtio16(vm->vdev, nb_vm_blocks), 8728c2ecf20Sopenharmony_ci }; 8738c2ecf20Sopenharmony_ci 8748c2ecf20Sopenharmony_ci if (atomic_read(&vm->config_changed)) 8758c2ecf20Sopenharmony_ci return -EAGAIN; 8768c2ecf20Sopenharmony_ci 8778c2ecf20Sopenharmony_ci switch (virtio_mem_send_request(vm, &req)) { 8788c2ecf20Sopenharmony_ci case VIRTIO_MEM_RESP_ACK: 8798c2ecf20Sopenharmony_ci vm->plugged_size += size; 8808c2ecf20Sopenharmony_ci return 0; 8818c2ecf20Sopenharmony_ci case VIRTIO_MEM_RESP_NACK: 8828c2ecf20Sopenharmony_ci return -EAGAIN; 8838c2ecf20Sopenharmony_ci case VIRTIO_MEM_RESP_BUSY: 8848c2ecf20Sopenharmony_ci return -ETXTBSY; 8858c2ecf20Sopenharmony_ci case VIRTIO_MEM_RESP_ERROR: 8868c2ecf20Sopenharmony_ci return -EINVAL; 8878c2ecf20Sopenharmony_ci default: 8888c2ecf20Sopenharmony_ci return -ENOMEM; 8898c2ecf20Sopenharmony_ci } 8908c2ecf20Sopenharmony_ci} 8918c2ecf20Sopenharmony_ci 8928c2ecf20Sopenharmony_cistatic int virtio_mem_send_unplug_request(struct virtio_mem *vm, uint64_t addr, 8938c2ecf20Sopenharmony_ci uint64_t size) 8948c2ecf20Sopenharmony_ci{ 8958c2ecf20Sopenharmony_ci const uint64_t nb_vm_blocks = size / vm->device_block_size; 8968c2ecf20Sopenharmony_ci const struct virtio_mem_req req = { 8978c2ecf20Sopenharmony_ci .type = cpu_to_virtio16(vm->vdev, VIRTIO_MEM_REQ_UNPLUG), 8988c2ecf20Sopenharmony_ci .u.unplug.addr = cpu_to_virtio64(vm->vdev, addr), 8998c2ecf20Sopenharmony_ci .u.unplug.nb_blocks = cpu_to_virtio16(vm->vdev, nb_vm_blocks), 9008c2ecf20Sopenharmony_ci }; 9018c2ecf20Sopenharmony_ci 9028c2ecf20Sopenharmony_ci if (atomic_read(&vm->config_changed)) 9038c2ecf20Sopenharmony_ci return -EAGAIN; 9048c2ecf20Sopenharmony_ci 9058c2ecf20Sopenharmony_ci switch (virtio_mem_send_request(vm, &req)) { 9068c2ecf20Sopenharmony_ci case VIRTIO_MEM_RESP_ACK: 9078c2ecf20Sopenharmony_ci vm->plugged_size -= size; 9088c2ecf20Sopenharmony_ci return 0; 9098c2ecf20Sopenharmony_ci case VIRTIO_MEM_RESP_BUSY: 9108c2ecf20Sopenharmony_ci return -ETXTBSY; 9118c2ecf20Sopenharmony_ci case VIRTIO_MEM_RESP_ERROR: 9128c2ecf20Sopenharmony_ci return -EINVAL; 9138c2ecf20Sopenharmony_ci default: 9148c2ecf20Sopenharmony_ci return -ENOMEM; 9158c2ecf20Sopenharmony_ci } 9168c2ecf20Sopenharmony_ci} 9178c2ecf20Sopenharmony_ci 9188c2ecf20Sopenharmony_cistatic int virtio_mem_send_unplug_all_request(struct virtio_mem *vm) 9198c2ecf20Sopenharmony_ci{ 9208c2ecf20Sopenharmony_ci const struct virtio_mem_req req = { 9218c2ecf20Sopenharmony_ci .type = cpu_to_virtio16(vm->vdev, VIRTIO_MEM_REQ_UNPLUG_ALL), 9228c2ecf20Sopenharmony_ci }; 9238c2ecf20Sopenharmony_ci 9248c2ecf20Sopenharmony_ci switch (virtio_mem_send_request(vm, &req)) { 9258c2ecf20Sopenharmony_ci case VIRTIO_MEM_RESP_ACK: 9268c2ecf20Sopenharmony_ci vm->unplug_all_required = false; 9278c2ecf20Sopenharmony_ci vm->plugged_size = 0; 9288c2ecf20Sopenharmony_ci /* usable region might have shrunk */ 9298c2ecf20Sopenharmony_ci atomic_set(&vm->config_changed, 1); 9308c2ecf20Sopenharmony_ci return 0; 9318c2ecf20Sopenharmony_ci case VIRTIO_MEM_RESP_BUSY: 9328c2ecf20Sopenharmony_ci return -ETXTBSY; 9338c2ecf20Sopenharmony_ci default: 9348c2ecf20Sopenharmony_ci return -ENOMEM; 9358c2ecf20Sopenharmony_ci } 9368c2ecf20Sopenharmony_ci} 9378c2ecf20Sopenharmony_ci 9388c2ecf20Sopenharmony_ci/* 9398c2ecf20Sopenharmony_ci * Plug selected subblocks. Updates the plugged state, but not the state 9408c2ecf20Sopenharmony_ci * of the memory block. 9418c2ecf20Sopenharmony_ci */ 9428c2ecf20Sopenharmony_cistatic int virtio_mem_mb_plug_sb(struct virtio_mem *vm, unsigned long mb_id, 9438c2ecf20Sopenharmony_ci int sb_id, int count) 9448c2ecf20Sopenharmony_ci{ 9458c2ecf20Sopenharmony_ci const uint64_t addr = virtio_mem_mb_id_to_phys(mb_id) + 9468c2ecf20Sopenharmony_ci sb_id * vm->subblock_size; 9478c2ecf20Sopenharmony_ci const uint64_t size = count * vm->subblock_size; 9488c2ecf20Sopenharmony_ci int rc; 9498c2ecf20Sopenharmony_ci 9508c2ecf20Sopenharmony_ci dev_dbg(&vm->vdev->dev, "plugging memory block: %lu : %i - %i\n", mb_id, 9518c2ecf20Sopenharmony_ci sb_id, sb_id + count - 1); 9528c2ecf20Sopenharmony_ci 9538c2ecf20Sopenharmony_ci rc = virtio_mem_send_plug_request(vm, addr, size); 9548c2ecf20Sopenharmony_ci if (!rc) 9558c2ecf20Sopenharmony_ci virtio_mem_mb_set_sb_plugged(vm, mb_id, sb_id, count); 9568c2ecf20Sopenharmony_ci return rc; 9578c2ecf20Sopenharmony_ci} 9588c2ecf20Sopenharmony_ci 9598c2ecf20Sopenharmony_ci/* 9608c2ecf20Sopenharmony_ci * Unplug selected subblocks. Updates the plugged state, but not the state 9618c2ecf20Sopenharmony_ci * of the memory block. 9628c2ecf20Sopenharmony_ci */ 9638c2ecf20Sopenharmony_cistatic int virtio_mem_mb_unplug_sb(struct virtio_mem *vm, unsigned long mb_id, 9648c2ecf20Sopenharmony_ci int sb_id, int count) 9658c2ecf20Sopenharmony_ci{ 9668c2ecf20Sopenharmony_ci const uint64_t addr = virtio_mem_mb_id_to_phys(mb_id) + 9678c2ecf20Sopenharmony_ci sb_id * vm->subblock_size; 9688c2ecf20Sopenharmony_ci const uint64_t size = count * vm->subblock_size; 9698c2ecf20Sopenharmony_ci int rc; 9708c2ecf20Sopenharmony_ci 9718c2ecf20Sopenharmony_ci dev_dbg(&vm->vdev->dev, "unplugging memory block: %lu : %i - %i\n", 9728c2ecf20Sopenharmony_ci mb_id, sb_id, sb_id + count - 1); 9738c2ecf20Sopenharmony_ci 9748c2ecf20Sopenharmony_ci rc = virtio_mem_send_unplug_request(vm, addr, size); 9758c2ecf20Sopenharmony_ci if (!rc) 9768c2ecf20Sopenharmony_ci virtio_mem_mb_set_sb_unplugged(vm, mb_id, sb_id, count); 9778c2ecf20Sopenharmony_ci return rc; 9788c2ecf20Sopenharmony_ci} 9798c2ecf20Sopenharmony_ci 9808c2ecf20Sopenharmony_ci/* 9818c2ecf20Sopenharmony_ci * Unplug the desired number of plugged subblocks of a offline or not-added 9828c2ecf20Sopenharmony_ci * memory block. Will fail if any subblock cannot get unplugged (instead of 9838c2ecf20Sopenharmony_ci * skipping it). 9848c2ecf20Sopenharmony_ci * 9858c2ecf20Sopenharmony_ci * Will not modify the state of the memory block. 9868c2ecf20Sopenharmony_ci * 9878c2ecf20Sopenharmony_ci * Note: can fail after some subblocks were unplugged. 9888c2ecf20Sopenharmony_ci */ 9898c2ecf20Sopenharmony_cistatic int virtio_mem_mb_unplug_any_sb(struct virtio_mem *vm, 9908c2ecf20Sopenharmony_ci unsigned long mb_id, uint64_t *nb_sb) 9918c2ecf20Sopenharmony_ci{ 9928c2ecf20Sopenharmony_ci int sb_id, count; 9938c2ecf20Sopenharmony_ci int rc; 9948c2ecf20Sopenharmony_ci 9958c2ecf20Sopenharmony_ci sb_id = vm->nb_sb_per_mb - 1; 9968c2ecf20Sopenharmony_ci while (*nb_sb) { 9978c2ecf20Sopenharmony_ci /* Find the next candidate subblock */ 9988c2ecf20Sopenharmony_ci while (sb_id >= 0 && 9998c2ecf20Sopenharmony_ci virtio_mem_mb_test_sb_unplugged(vm, mb_id, sb_id, 1)) 10008c2ecf20Sopenharmony_ci sb_id--; 10018c2ecf20Sopenharmony_ci if (sb_id < 0) 10028c2ecf20Sopenharmony_ci break; 10038c2ecf20Sopenharmony_ci /* Try to unplug multiple subblocks at a time */ 10048c2ecf20Sopenharmony_ci count = 1; 10058c2ecf20Sopenharmony_ci while (count < *nb_sb && sb_id > 0 && 10068c2ecf20Sopenharmony_ci virtio_mem_mb_test_sb_plugged(vm, mb_id, sb_id - 1, 1)) { 10078c2ecf20Sopenharmony_ci count++; 10088c2ecf20Sopenharmony_ci sb_id--; 10098c2ecf20Sopenharmony_ci } 10108c2ecf20Sopenharmony_ci 10118c2ecf20Sopenharmony_ci rc = virtio_mem_mb_unplug_sb(vm, mb_id, sb_id, count); 10128c2ecf20Sopenharmony_ci if (rc) 10138c2ecf20Sopenharmony_ci return rc; 10148c2ecf20Sopenharmony_ci *nb_sb -= count; 10158c2ecf20Sopenharmony_ci sb_id--; 10168c2ecf20Sopenharmony_ci } 10178c2ecf20Sopenharmony_ci 10188c2ecf20Sopenharmony_ci return 0; 10198c2ecf20Sopenharmony_ci} 10208c2ecf20Sopenharmony_ci 10218c2ecf20Sopenharmony_ci/* 10228c2ecf20Sopenharmony_ci * Unplug all plugged subblocks of an offline or not-added memory block. 10238c2ecf20Sopenharmony_ci * 10248c2ecf20Sopenharmony_ci * Will not modify the state of the memory block. 10258c2ecf20Sopenharmony_ci * 10268c2ecf20Sopenharmony_ci * Note: can fail after some subblocks were unplugged. 10278c2ecf20Sopenharmony_ci */ 10288c2ecf20Sopenharmony_cistatic int virtio_mem_mb_unplug(struct virtio_mem *vm, unsigned long mb_id) 10298c2ecf20Sopenharmony_ci{ 10308c2ecf20Sopenharmony_ci uint64_t nb_sb = vm->nb_sb_per_mb; 10318c2ecf20Sopenharmony_ci 10328c2ecf20Sopenharmony_ci return virtio_mem_mb_unplug_any_sb(vm, mb_id, &nb_sb); 10338c2ecf20Sopenharmony_ci} 10348c2ecf20Sopenharmony_ci 10358c2ecf20Sopenharmony_ci/* 10368c2ecf20Sopenharmony_ci * Prepare tracking data for the next memory block. 10378c2ecf20Sopenharmony_ci */ 10388c2ecf20Sopenharmony_cistatic int virtio_mem_prepare_next_mb(struct virtio_mem *vm, 10398c2ecf20Sopenharmony_ci unsigned long *mb_id) 10408c2ecf20Sopenharmony_ci{ 10418c2ecf20Sopenharmony_ci int rc; 10428c2ecf20Sopenharmony_ci 10438c2ecf20Sopenharmony_ci if (vm->next_mb_id > vm->last_usable_mb_id) 10448c2ecf20Sopenharmony_ci return -ENOSPC; 10458c2ecf20Sopenharmony_ci 10468c2ecf20Sopenharmony_ci /* Resize the state array if required. */ 10478c2ecf20Sopenharmony_ci rc = virtio_mem_mb_state_prepare_next_mb(vm); 10488c2ecf20Sopenharmony_ci if (rc) 10498c2ecf20Sopenharmony_ci return rc; 10508c2ecf20Sopenharmony_ci 10518c2ecf20Sopenharmony_ci /* Resize the subblock bitmap if required. */ 10528c2ecf20Sopenharmony_ci rc = virtio_mem_sb_bitmap_prepare_next_mb(vm); 10538c2ecf20Sopenharmony_ci if (rc) 10548c2ecf20Sopenharmony_ci return rc; 10558c2ecf20Sopenharmony_ci 10568c2ecf20Sopenharmony_ci vm->nb_mb_state[VIRTIO_MEM_MB_STATE_UNUSED]++; 10578c2ecf20Sopenharmony_ci *mb_id = vm->next_mb_id++; 10588c2ecf20Sopenharmony_ci return 0; 10598c2ecf20Sopenharmony_ci} 10608c2ecf20Sopenharmony_ci 10618c2ecf20Sopenharmony_ci/* 10628c2ecf20Sopenharmony_ci * Don't add too many blocks that are not onlined yet to avoid running OOM. 10638c2ecf20Sopenharmony_ci */ 10648c2ecf20Sopenharmony_cistatic bool virtio_mem_too_many_mb_offline(struct virtio_mem *vm) 10658c2ecf20Sopenharmony_ci{ 10668c2ecf20Sopenharmony_ci unsigned long nb_offline; 10678c2ecf20Sopenharmony_ci 10688c2ecf20Sopenharmony_ci nb_offline = vm->nb_mb_state[VIRTIO_MEM_MB_STATE_OFFLINE] + 10698c2ecf20Sopenharmony_ci vm->nb_mb_state[VIRTIO_MEM_MB_STATE_OFFLINE_PARTIAL]; 10708c2ecf20Sopenharmony_ci return nb_offline >= VIRTIO_MEM_NB_OFFLINE_THRESHOLD; 10718c2ecf20Sopenharmony_ci} 10728c2ecf20Sopenharmony_ci 10738c2ecf20Sopenharmony_ci/* 10748c2ecf20Sopenharmony_ci * Try to plug the desired number of subblocks and add the memory block 10758c2ecf20Sopenharmony_ci * to Linux. 10768c2ecf20Sopenharmony_ci * 10778c2ecf20Sopenharmony_ci * Will modify the state of the memory block. 10788c2ecf20Sopenharmony_ci */ 10798c2ecf20Sopenharmony_cistatic int virtio_mem_mb_plug_and_add(struct virtio_mem *vm, 10808c2ecf20Sopenharmony_ci unsigned long mb_id, 10818c2ecf20Sopenharmony_ci uint64_t *nb_sb) 10828c2ecf20Sopenharmony_ci{ 10838c2ecf20Sopenharmony_ci const int count = min_t(int, *nb_sb, vm->nb_sb_per_mb); 10848c2ecf20Sopenharmony_ci int rc, rc2; 10858c2ecf20Sopenharmony_ci 10868c2ecf20Sopenharmony_ci if (WARN_ON_ONCE(!count)) 10878c2ecf20Sopenharmony_ci return -EINVAL; 10888c2ecf20Sopenharmony_ci 10898c2ecf20Sopenharmony_ci /* 10908c2ecf20Sopenharmony_ci * Plug the requested number of subblocks before adding it to linux, 10918c2ecf20Sopenharmony_ci * so that onlining will directly online all plugged subblocks. 10928c2ecf20Sopenharmony_ci */ 10938c2ecf20Sopenharmony_ci rc = virtio_mem_mb_plug_sb(vm, mb_id, 0, count); 10948c2ecf20Sopenharmony_ci if (rc) 10958c2ecf20Sopenharmony_ci return rc; 10968c2ecf20Sopenharmony_ci 10978c2ecf20Sopenharmony_ci /* 10988c2ecf20Sopenharmony_ci * Mark the block properly offline before adding it to Linux, 10998c2ecf20Sopenharmony_ci * so the memory notifiers will find the block in the right state. 11008c2ecf20Sopenharmony_ci */ 11018c2ecf20Sopenharmony_ci if (count == vm->nb_sb_per_mb) 11028c2ecf20Sopenharmony_ci virtio_mem_mb_set_state(vm, mb_id, 11038c2ecf20Sopenharmony_ci VIRTIO_MEM_MB_STATE_OFFLINE); 11048c2ecf20Sopenharmony_ci else 11058c2ecf20Sopenharmony_ci virtio_mem_mb_set_state(vm, mb_id, 11068c2ecf20Sopenharmony_ci VIRTIO_MEM_MB_STATE_OFFLINE_PARTIAL); 11078c2ecf20Sopenharmony_ci 11088c2ecf20Sopenharmony_ci /* Add the memory block to linux - if that fails, try to unplug. */ 11098c2ecf20Sopenharmony_ci rc = virtio_mem_mb_add(vm, mb_id); 11108c2ecf20Sopenharmony_ci if (rc) { 11118c2ecf20Sopenharmony_ci enum virtio_mem_mb_state new_state = VIRTIO_MEM_MB_STATE_UNUSED; 11128c2ecf20Sopenharmony_ci 11138c2ecf20Sopenharmony_ci dev_err(&vm->vdev->dev, 11148c2ecf20Sopenharmony_ci "adding memory block %lu failed with %d\n", mb_id, rc); 11158c2ecf20Sopenharmony_ci rc2 = virtio_mem_mb_unplug_sb(vm, mb_id, 0, count); 11168c2ecf20Sopenharmony_ci 11178c2ecf20Sopenharmony_ci /* 11188c2ecf20Sopenharmony_ci * TODO: Linux MM does not properly clean up yet in all cases 11198c2ecf20Sopenharmony_ci * where adding of memory failed - especially on -ENOMEM. 11208c2ecf20Sopenharmony_ci */ 11218c2ecf20Sopenharmony_ci if (rc2) 11228c2ecf20Sopenharmony_ci new_state = VIRTIO_MEM_MB_STATE_PLUGGED; 11238c2ecf20Sopenharmony_ci virtio_mem_mb_set_state(vm, mb_id, new_state); 11248c2ecf20Sopenharmony_ci return rc; 11258c2ecf20Sopenharmony_ci } 11268c2ecf20Sopenharmony_ci 11278c2ecf20Sopenharmony_ci *nb_sb -= count; 11288c2ecf20Sopenharmony_ci return 0; 11298c2ecf20Sopenharmony_ci} 11308c2ecf20Sopenharmony_ci 11318c2ecf20Sopenharmony_ci/* 11328c2ecf20Sopenharmony_ci * Try to plug the desired number of subblocks of a memory block that 11338c2ecf20Sopenharmony_ci * is already added to Linux. 11348c2ecf20Sopenharmony_ci * 11358c2ecf20Sopenharmony_ci * Will modify the state of the memory block. 11368c2ecf20Sopenharmony_ci * 11378c2ecf20Sopenharmony_ci * Note: Can fail after some subblocks were successfully plugged. 11388c2ecf20Sopenharmony_ci */ 11398c2ecf20Sopenharmony_cistatic int virtio_mem_mb_plug_any_sb(struct virtio_mem *vm, unsigned long mb_id, 11408c2ecf20Sopenharmony_ci uint64_t *nb_sb, bool online) 11418c2ecf20Sopenharmony_ci{ 11428c2ecf20Sopenharmony_ci unsigned long pfn, nr_pages; 11438c2ecf20Sopenharmony_ci int sb_id, count; 11448c2ecf20Sopenharmony_ci int rc; 11458c2ecf20Sopenharmony_ci 11468c2ecf20Sopenharmony_ci if (WARN_ON_ONCE(!*nb_sb)) 11478c2ecf20Sopenharmony_ci return -EINVAL; 11488c2ecf20Sopenharmony_ci 11498c2ecf20Sopenharmony_ci while (*nb_sb) { 11508c2ecf20Sopenharmony_ci sb_id = virtio_mem_mb_first_unplugged_sb(vm, mb_id); 11518c2ecf20Sopenharmony_ci if (sb_id >= vm->nb_sb_per_mb) 11528c2ecf20Sopenharmony_ci break; 11538c2ecf20Sopenharmony_ci count = 1; 11548c2ecf20Sopenharmony_ci while (count < *nb_sb && 11558c2ecf20Sopenharmony_ci sb_id + count < vm->nb_sb_per_mb && 11568c2ecf20Sopenharmony_ci !virtio_mem_mb_test_sb_plugged(vm, mb_id, sb_id + count, 11578c2ecf20Sopenharmony_ci 1)) 11588c2ecf20Sopenharmony_ci count++; 11598c2ecf20Sopenharmony_ci 11608c2ecf20Sopenharmony_ci rc = virtio_mem_mb_plug_sb(vm, mb_id, sb_id, count); 11618c2ecf20Sopenharmony_ci if (rc) 11628c2ecf20Sopenharmony_ci return rc; 11638c2ecf20Sopenharmony_ci *nb_sb -= count; 11648c2ecf20Sopenharmony_ci if (!online) 11658c2ecf20Sopenharmony_ci continue; 11668c2ecf20Sopenharmony_ci 11678c2ecf20Sopenharmony_ci /* fake-online the pages if the memory block is online */ 11688c2ecf20Sopenharmony_ci pfn = PFN_DOWN(virtio_mem_mb_id_to_phys(mb_id) + 11698c2ecf20Sopenharmony_ci sb_id * vm->subblock_size); 11708c2ecf20Sopenharmony_ci nr_pages = PFN_DOWN(count * vm->subblock_size); 11718c2ecf20Sopenharmony_ci virtio_mem_fake_online(pfn, nr_pages); 11728c2ecf20Sopenharmony_ci } 11738c2ecf20Sopenharmony_ci 11748c2ecf20Sopenharmony_ci if (virtio_mem_mb_test_sb_plugged(vm, mb_id, 0, vm->nb_sb_per_mb)) { 11758c2ecf20Sopenharmony_ci if (online) 11768c2ecf20Sopenharmony_ci virtio_mem_mb_set_state(vm, mb_id, 11778c2ecf20Sopenharmony_ci VIRTIO_MEM_MB_STATE_ONLINE); 11788c2ecf20Sopenharmony_ci else 11798c2ecf20Sopenharmony_ci virtio_mem_mb_set_state(vm, mb_id, 11808c2ecf20Sopenharmony_ci VIRTIO_MEM_MB_STATE_OFFLINE); 11818c2ecf20Sopenharmony_ci } 11828c2ecf20Sopenharmony_ci 11838c2ecf20Sopenharmony_ci return 0; 11848c2ecf20Sopenharmony_ci} 11858c2ecf20Sopenharmony_ci 11868c2ecf20Sopenharmony_ci/* 11878c2ecf20Sopenharmony_ci * Try to plug the requested amount of memory. 11888c2ecf20Sopenharmony_ci */ 11898c2ecf20Sopenharmony_cistatic int virtio_mem_plug_request(struct virtio_mem *vm, uint64_t diff) 11908c2ecf20Sopenharmony_ci{ 11918c2ecf20Sopenharmony_ci uint64_t nb_sb = diff / vm->subblock_size; 11928c2ecf20Sopenharmony_ci unsigned long mb_id; 11938c2ecf20Sopenharmony_ci int rc; 11948c2ecf20Sopenharmony_ci 11958c2ecf20Sopenharmony_ci if (!nb_sb) 11968c2ecf20Sopenharmony_ci return 0; 11978c2ecf20Sopenharmony_ci 11988c2ecf20Sopenharmony_ci /* Don't race with onlining/offlining */ 11998c2ecf20Sopenharmony_ci mutex_lock(&vm->hotplug_mutex); 12008c2ecf20Sopenharmony_ci 12018c2ecf20Sopenharmony_ci /* Try to plug subblocks of partially plugged online blocks. */ 12028c2ecf20Sopenharmony_ci virtio_mem_for_each_mb_state(vm, mb_id, 12038c2ecf20Sopenharmony_ci VIRTIO_MEM_MB_STATE_ONLINE_PARTIAL) { 12048c2ecf20Sopenharmony_ci rc = virtio_mem_mb_plug_any_sb(vm, mb_id, &nb_sb, true); 12058c2ecf20Sopenharmony_ci if (rc || !nb_sb) 12068c2ecf20Sopenharmony_ci goto out_unlock; 12078c2ecf20Sopenharmony_ci cond_resched(); 12088c2ecf20Sopenharmony_ci } 12098c2ecf20Sopenharmony_ci 12108c2ecf20Sopenharmony_ci /* Try to plug subblocks of partially plugged offline blocks. */ 12118c2ecf20Sopenharmony_ci virtio_mem_for_each_mb_state(vm, mb_id, 12128c2ecf20Sopenharmony_ci VIRTIO_MEM_MB_STATE_OFFLINE_PARTIAL) { 12138c2ecf20Sopenharmony_ci rc = virtio_mem_mb_plug_any_sb(vm, mb_id, &nb_sb, false); 12148c2ecf20Sopenharmony_ci if (rc || !nb_sb) 12158c2ecf20Sopenharmony_ci goto out_unlock; 12168c2ecf20Sopenharmony_ci cond_resched(); 12178c2ecf20Sopenharmony_ci } 12188c2ecf20Sopenharmony_ci 12198c2ecf20Sopenharmony_ci /* 12208c2ecf20Sopenharmony_ci * We won't be working on online/offline memory blocks from this point, 12218c2ecf20Sopenharmony_ci * so we can't race with memory onlining/offlining. Drop the mutex. 12228c2ecf20Sopenharmony_ci */ 12238c2ecf20Sopenharmony_ci mutex_unlock(&vm->hotplug_mutex); 12248c2ecf20Sopenharmony_ci 12258c2ecf20Sopenharmony_ci /* Try to plug and add unused blocks */ 12268c2ecf20Sopenharmony_ci virtio_mem_for_each_mb_state(vm, mb_id, VIRTIO_MEM_MB_STATE_UNUSED) { 12278c2ecf20Sopenharmony_ci if (virtio_mem_too_many_mb_offline(vm)) 12288c2ecf20Sopenharmony_ci return -ENOSPC; 12298c2ecf20Sopenharmony_ci 12308c2ecf20Sopenharmony_ci rc = virtio_mem_mb_plug_and_add(vm, mb_id, &nb_sb); 12318c2ecf20Sopenharmony_ci if (rc || !nb_sb) 12328c2ecf20Sopenharmony_ci return rc; 12338c2ecf20Sopenharmony_ci cond_resched(); 12348c2ecf20Sopenharmony_ci } 12358c2ecf20Sopenharmony_ci 12368c2ecf20Sopenharmony_ci /* Try to prepare, plug and add new blocks */ 12378c2ecf20Sopenharmony_ci while (nb_sb) { 12388c2ecf20Sopenharmony_ci if (virtio_mem_too_many_mb_offline(vm)) 12398c2ecf20Sopenharmony_ci return -ENOSPC; 12408c2ecf20Sopenharmony_ci 12418c2ecf20Sopenharmony_ci rc = virtio_mem_prepare_next_mb(vm, &mb_id); 12428c2ecf20Sopenharmony_ci if (rc) 12438c2ecf20Sopenharmony_ci return rc; 12448c2ecf20Sopenharmony_ci rc = virtio_mem_mb_plug_and_add(vm, mb_id, &nb_sb); 12458c2ecf20Sopenharmony_ci if (rc) 12468c2ecf20Sopenharmony_ci return rc; 12478c2ecf20Sopenharmony_ci cond_resched(); 12488c2ecf20Sopenharmony_ci } 12498c2ecf20Sopenharmony_ci 12508c2ecf20Sopenharmony_ci return 0; 12518c2ecf20Sopenharmony_ciout_unlock: 12528c2ecf20Sopenharmony_ci mutex_unlock(&vm->hotplug_mutex); 12538c2ecf20Sopenharmony_ci return rc; 12548c2ecf20Sopenharmony_ci} 12558c2ecf20Sopenharmony_ci 12568c2ecf20Sopenharmony_ci/* 12578c2ecf20Sopenharmony_ci * Unplug the desired number of plugged subblocks of an offline memory block. 12588c2ecf20Sopenharmony_ci * Will fail if any subblock cannot get unplugged (instead of skipping it). 12598c2ecf20Sopenharmony_ci * 12608c2ecf20Sopenharmony_ci * Will modify the state of the memory block. Might temporarily drop the 12618c2ecf20Sopenharmony_ci * hotplug_mutex. 12628c2ecf20Sopenharmony_ci * 12638c2ecf20Sopenharmony_ci * Note: Can fail after some subblocks were successfully unplugged. 12648c2ecf20Sopenharmony_ci */ 12658c2ecf20Sopenharmony_cistatic int virtio_mem_mb_unplug_any_sb_offline(struct virtio_mem *vm, 12668c2ecf20Sopenharmony_ci unsigned long mb_id, 12678c2ecf20Sopenharmony_ci uint64_t *nb_sb) 12688c2ecf20Sopenharmony_ci{ 12698c2ecf20Sopenharmony_ci int rc; 12708c2ecf20Sopenharmony_ci 12718c2ecf20Sopenharmony_ci rc = virtio_mem_mb_unplug_any_sb(vm, mb_id, nb_sb); 12728c2ecf20Sopenharmony_ci 12738c2ecf20Sopenharmony_ci /* some subblocks might have been unplugged even on failure */ 12748c2ecf20Sopenharmony_ci if (!virtio_mem_mb_test_sb_plugged(vm, mb_id, 0, vm->nb_sb_per_mb)) 12758c2ecf20Sopenharmony_ci virtio_mem_mb_set_state(vm, mb_id, 12768c2ecf20Sopenharmony_ci VIRTIO_MEM_MB_STATE_OFFLINE_PARTIAL); 12778c2ecf20Sopenharmony_ci if (rc) 12788c2ecf20Sopenharmony_ci return rc; 12798c2ecf20Sopenharmony_ci 12808c2ecf20Sopenharmony_ci if (virtio_mem_mb_test_sb_unplugged(vm, mb_id, 0, vm->nb_sb_per_mb)) { 12818c2ecf20Sopenharmony_ci /* 12828c2ecf20Sopenharmony_ci * Remove the block from Linux - this should never fail. 12838c2ecf20Sopenharmony_ci * Hinder the block from getting onlined by marking it 12848c2ecf20Sopenharmony_ci * unplugged. Temporarily drop the mutex, so 12858c2ecf20Sopenharmony_ci * any pending GOING_ONLINE requests can be serviced/rejected. 12868c2ecf20Sopenharmony_ci */ 12878c2ecf20Sopenharmony_ci virtio_mem_mb_set_state(vm, mb_id, 12888c2ecf20Sopenharmony_ci VIRTIO_MEM_MB_STATE_UNUSED); 12898c2ecf20Sopenharmony_ci 12908c2ecf20Sopenharmony_ci mutex_unlock(&vm->hotplug_mutex); 12918c2ecf20Sopenharmony_ci rc = virtio_mem_mb_remove(vm, mb_id); 12928c2ecf20Sopenharmony_ci BUG_ON(rc); 12938c2ecf20Sopenharmony_ci mutex_lock(&vm->hotplug_mutex); 12948c2ecf20Sopenharmony_ci } 12958c2ecf20Sopenharmony_ci return 0; 12968c2ecf20Sopenharmony_ci} 12978c2ecf20Sopenharmony_ci 12988c2ecf20Sopenharmony_ci/* 12998c2ecf20Sopenharmony_ci * Unplug the given plugged subblocks of an online memory block. 13008c2ecf20Sopenharmony_ci * 13018c2ecf20Sopenharmony_ci * Will modify the state of the memory block. 13028c2ecf20Sopenharmony_ci */ 13038c2ecf20Sopenharmony_cistatic int virtio_mem_mb_unplug_sb_online(struct virtio_mem *vm, 13048c2ecf20Sopenharmony_ci unsigned long mb_id, int sb_id, 13058c2ecf20Sopenharmony_ci int count) 13068c2ecf20Sopenharmony_ci{ 13078c2ecf20Sopenharmony_ci const unsigned long nr_pages = PFN_DOWN(vm->subblock_size) * count; 13088c2ecf20Sopenharmony_ci unsigned long start_pfn; 13098c2ecf20Sopenharmony_ci int rc; 13108c2ecf20Sopenharmony_ci 13118c2ecf20Sopenharmony_ci start_pfn = PFN_DOWN(virtio_mem_mb_id_to_phys(mb_id) + 13128c2ecf20Sopenharmony_ci sb_id * vm->subblock_size); 13138c2ecf20Sopenharmony_ci rc = alloc_contig_range(start_pfn, start_pfn + nr_pages, 13148c2ecf20Sopenharmony_ci MIGRATE_MOVABLE, GFP_KERNEL); 13158c2ecf20Sopenharmony_ci if (rc == -ENOMEM) 13168c2ecf20Sopenharmony_ci /* whoops, out of memory */ 13178c2ecf20Sopenharmony_ci return rc; 13188c2ecf20Sopenharmony_ci if (rc) 13198c2ecf20Sopenharmony_ci return -EBUSY; 13208c2ecf20Sopenharmony_ci 13218c2ecf20Sopenharmony_ci /* Mark it as fake-offline before unplugging it */ 13228c2ecf20Sopenharmony_ci virtio_mem_set_fake_offline(start_pfn, nr_pages, true); 13238c2ecf20Sopenharmony_ci adjust_managed_page_count(pfn_to_page(start_pfn), -nr_pages); 13248c2ecf20Sopenharmony_ci 13258c2ecf20Sopenharmony_ci /* Try to unplug the allocated memory */ 13268c2ecf20Sopenharmony_ci rc = virtio_mem_mb_unplug_sb(vm, mb_id, sb_id, count); 13278c2ecf20Sopenharmony_ci if (rc) { 13288c2ecf20Sopenharmony_ci /* Return the memory to the buddy. */ 13298c2ecf20Sopenharmony_ci virtio_mem_fake_online(start_pfn, nr_pages); 13308c2ecf20Sopenharmony_ci return rc; 13318c2ecf20Sopenharmony_ci } 13328c2ecf20Sopenharmony_ci 13338c2ecf20Sopenharmony_ci virtio_mem_mb_set_state(vm, mb_id, 13348c2ecf20Sopenharmony_ci VIRTIO_MEM_MB_STATE_ONLINE_PARTIAL); 13358c2ecf20Sopenharmony_ci return 0; 13368c2ecf20Sopenharmony_ci} 13378c2ecf20Sopenharmony_ci 13388c2ecf20Sopenharmony_ci/* 13398c2ecf20Sopenharmony_ci * Unplug the desired number of plugged subblocks of an online memory block. 13408c2ecf20Sopenharmony_ci * Will skip subblock that are busy. 13418c2ecf20Sopenharmony_ci * 13428c2ecf20Sopenharmony_ci * Will modify the state of the memory block. Might temporarily drop the 13438c2ecf20Sopenharmony_ci * hotplug_mutex. 13448c2ecf20Sopenharmony_ci * 13458c2ecf20Sopenharmony_ci * Note: Can fail after some subblocks were successfully unplugged. Can 13468c2ecf20Sopenharmony_ci * return 0 even if subblocks were busy and could not get unplugged. 13478c2ecf20Sopenharmony_ci */ 13488c2ecf20Sopenharmony_cistatic int virtio_mem_mb_unplug_any_sb_online(struct virtio_mem *vm, 13498c2ecf20Sopenharmony_ci unsigned long mb_id, 13508c2ecf20Sopenharmony_ci uint64_t *nb_sb) 13518c2ecf20Sopenharmony_ci{ 13528c2ecf20Sopenharmony_ci int rc, sb_id; 13538c2ecf20Sopenharmony_ci 13548c2ecf20Sopenharmony_ci /* If possible, try to unplug the complete block in one shot. */ 13558c2ecf20Sopenharmony_ci if (*nb_sb >= vm->nb_sb_per_mb && 13568c2ecf20Sopenharmony_ci virtio_mem_mb_test_sb_plugged(vm, mb_id, 0, vm->nb_sb_per_mb)) { 13578c2ecf20Sopenharmony_ci rc = virtio_mem_mb_unplug_sb_online(vm, mb_id, 0, 13588c2ecf20Sopenharmony_ci vm->nb_sb_per_mb); 13598c2ecf20Sopenharmony_ci if (!rc) { 13608c2ecf20Sopenharmony_ci *nb_sb -= vm->nb_sb_per_mb; 13618c2ecf20Sopenharmony_ci goto unplugged; 13628c2ecf20Sopenharmony_ci } else if (rc != -EBUSY) 13638c2ecf20Sopenharmony_ci return rc; 13648c2ecf20Sopenharmony_ci } 13658c2ecf20Sopenharmony_ci 13668c2ecf20Sopenharmony_ci /* Fallback to single subblocks. */ 13678c2ecf20Sopenharmony_ci for (sb_id = vm->nb_sb_per_mb - 1; sb_id >= 0 && *nb_sb; sb_id--) { 13688c2ecf20Sopenharmony_ci /* Find the next candidate subblock */ 13698c2ecf20Sopenharmony_ci while (sb_id >= 0 && 13708c2ecf20Sopenharmony_ci !virtio_mem_mb_test_sb_plugged(vm, mb_id, sb_id, 1)) 13718c2ecf20Sopenharmony_ci sb_id--; 13728c2ecf20Sopenharmony_ci if (sb_id < 0) 13738c2ecf20Sopenharmony_ci break; 13748c2ecf20Sopenharmony_ci 13758c2ecf20Sopenharmony_ci rc = virtio_mem_mb_unplug_sb_online(vm, mb_id, sb_id, 1); 13768c2ecf20Sopenharmony_ci if (rc == -EBUSY) 13778c2ecf20Sopenharmony_ci continue; 13788c2ecf20Sopenharmony_ci else if (rc) 13798c2ecf20Sopenharmony_ci return rc; 13808c2ecf20Sopenharmony_ci *nb_sb -= 1; 13818c2ecf20Sopenharmony_ci } 13828c2ecf20Sopenharmony_ci 13838c2ecf20Sopenharmony_ciunplugged: 13848c2ecf20Sopenharmony_ci /* 13858c2ecf20Sopenharmony_ci * Once all subblocks of a memory block were unplugged, offline and 13868c2ecf20Sopenharmony_ci * remove it. This will usually not fail, as no memory is in use 13878c2ecf20Sopenharmony_ci * anymore - however some other notifiers might NACK the request. 13888c2ecf20Sopenharmony_ci */ 13898c2ecf20Sopenharmony_ci if (virtio_mem_mb_test_sb_unplugged(vm, mb_id, 0, vm->nb_sb_per_mb)) { 13908c2ecf20Sopenharmony_ci mutex_unlock(&vm->hotplug_mutex); 13918c2ecf20Sopenharmony_ci rc = virtio_mem_mb_offline_and_remove(vm, mb_id); 13928c2ecf20Sopenharmony_ci mutex_lock(&vm->hotplug_mutex); 13938c2ecf20Sopenharmony_ci if (!rc) 13948c2ecf20Sopenharmony_ci virtio_mem_mb_set_state(vm, mb_id, 13958c2ecf20Sopenharmony_ci VIRTIO_MEM_MB_STATE_UNUSED); 13968c2ecf20Sopenharmony_ci } 13978c2ecf20Sopenharmony_ci 13988c2ecf20Sopenharmony_ci return 0; 13998c2ecf20Sopenharmony_ci} 14008c2ecf20Sopenharmony_ci 14018c2ecf20Sopenharmony_ci/* 14028c2ecf20Sopenharmony_ci * Try to unplug the requested amount of memory. 14038c2ecf20Sopenharmony_ci */ 14048c2ecf20Sopenharmony_cistatic int virtio_mem_unplug_request(struct virtio_mem *vm, uint64_t diff) 14058c2ecf20Sopenharmony_ci{ 14068c2ecf20Sopenharmony_ci uint64_t nb_sb = diff / vm->subblock_size; 14078c2ecf20Sopenharmony_ci unsigned long mb_id; 14088c2ecf20Sopenharmony_ci int rc; 14098c2ecf20Sopenharmony_ci 14108c2ecf20Sopenharmony_ci if (!nb_sb) 14118c2ecf20Sopenharmony_ci return 0; 14128c2ecf20Sopenharmony_ci 14138c2ecf20Sopenharmony_ci /* 14148c2ecf20Sopenharmony_ci * We'll drop the mutex a couple of times when it is safe to do so. 14158c2ecf20Sopenharmony_ci * This might result in some blocks switching the state (online/offline) 14168c2ecf20Sopenharmony_ci * and we could miss them in this run - we will retry again later. 14178c2ecf20Sopenharmony_ci */ 14188c2ecf20Sopenharmony_ci mutex_lock(&vm->hotplug_mutex); 14198c2ecf20Sopenharmony_ci 14208c2ecf20Sopenharmony_ci /* Try to unplug subblocks of partially plugged offline blocks. */ 14218c2ecf20Sopenharmony_ci virtio_mem_for_each_mb_state_rev(vm, mb_id, 14228c2ecf20Sopenharmony_ci VIRTIO_MEM_MB_STATE_OFFLINE_PARTIAL) { 14238c2ecf20Sopenharmony_ci rc = virtio_mem_mb_unplug_any_sb_offline(vm, mb_id, 14248c2ecf20Sopenharmony_ci &nb_sb); 14258c2ecf20Sopenharmony_ci if (rc || !nb_sb) 14268c2ecf20Sopenharmony_ci goto out_unlock; 14278c2ecf20Sopenharmony_ci cond_resched(); 14288c2ecf20Sopenharmony_ci } 14298c2ecf20Sopenharmony_ci 14308c2ecf20Sopenharmony_ci /* Try to unplug subblocks of plugged offline blocks. */ 14318c2ecf20Sopenharmony_ci virtio_mem_for_each_mb_state_rev(vm, mb_id, 14328c2ecf20Sopenharmony_ci VIRTIO_MEM_MB_STATE_OFFLINE) { 14338c2ecf20Sopenharmony_ci rc = virtio_mem_mb_unplug_any_sb_offline(vm, mb_id, 14348c2ecf20Sopenharmony_ci &nb_sb); 14358c2ecf20Sopenharmony_ci if (rc || !nb_sb) 14368c2ecf20Sopenharmony_ci goto out_unlock; 14378c2ecf20Sopenharmony_ci cond_resched(); 14388c2ecf20Sopenharmony_ci } 14398c2ecf20Sopenharmony_ci 14408c2ecf20Sopenharmony_ci if (!unplug_online) { 14418c2ecf20Sopenharmony_ci mutex_unlock(&vm->hotplug_mutex); 14428c2ecf20Sopenharmony_ci return 0; 14438c2ecf20Sopenharmony_ci } 14448c2ecf20Sopenharmony_ci 14458c2ecf20Sopenharmony_ci /* Try to unplug subblocks of partially plugged online blocks. */ 14468c2ecf20Sopenharmony_ci virtio_mem_for_each_mb_state_rev(vm, mb_id, 14478c2ecf20Sopenharmony_ci VIRTIO_MEM_MB_STATE_ONLINE_PARTIAL) { 14488c2ecf20Sopenharmony_ci rc = virtio_mem_mb_unplug_any_sb_online(vm, mb_id, 14498c2ecf20Sopenharmony_ci &nb_sb); 14508c2ecf20Sopenharmony_ci if (rc || !nb_sb) 14518c2ecf20Sopenharmony_ci goto out_unlock; 14528c2ecf20Sopenharmony_ci mutex_unlock(&vm->hotplug_mutex); 14538c2ecf20Sopenharmony_ci cond_resched(); 14548c2ecf20Sopenharmony_ci mutex_lock(&vm->hotplug_mutex); 14558c2ecf20Sopenharmony_ci } 14568c2ecf20Sopenharmony_ci 14578c2ecf20Sopenharmony_ci /* Try to unplug subblocks of plugged online blocks. */ 14588c2ecf20Sopenharmony_ci virtio_mem_for_each_mb_state_rev(vm, mb_id, 14598c2ecf20Sopenharmony_ci VIRTIO_MEM_MB_STATE_ONLINE) { 14608c2ecf20Sopenharmony_ci rc = virtio_mem_mb_unplug_any_sb_online(vm, mb_id, 14618c2ecf20Sopenharmony_ci &nb_sb); 14628c2ecf20Sopenharmony_ci if (rc || !nb_sb) 14638c2ecf20Sopenharmony_ci goto out_unlock; 14648c2ecf20Sopenharmony_ci mutex_unlock(&vm->hotplug_mutex); 14658c2ecf20Sopenharmony_ci cond_resched(); 14668c2ecf20Sopenharmony_ci mutex_lock(&vm->hotplug_mutex); 14678c2ecf20Sopenharmony_ci } 14688c2ecf20Sopenharmony_ci 14698c2ecf20Sopenharmony_ci mutex_unlock(&vm->hotplug_mutex); 14708c2ecf20Sopenharmony_ci return nb_sb ? -EBUSY : 0; 14718c2ecf20Sopenharmony_ciout_unlock: 14728c2ecf20Sopenharmony_ci mutex_unlock(&vm->hotplug_mutex); 14738c2ecf20Sopenharmony_ci return rc; 14748c2ecf20Sopenharmony_ci} 14758c2ecf20Sopenharmony_ci 14768c2ecf20Sopenharmony_ci/* 14778c2ecf20Sopenharmony_ci * Try to unplug all blocks that couldn't be unplugged before, for example, 14788c2ecf20Sopenharmony_ci * because the hypervisor was busy. 14798c2ecf20Sopenharmony_ci */ 14808c2ecf20Sopenharmony_cistatic int virtio_mem_unplug_pending_mb(struct virtio_mem *vm) 14818c2ecf20Sopenharmony_ci{ 14828c2ecf20Sopenharmony_ci unsigned long mb_id; 14838c2ecf20Sopenharmony_ci int rc; 14848c2ecf20Sopenharmony_ci 14858c2ecf20Sopenharmony_ci virtio_mem_for_each_mb_state(vm, mb_id, VIRTIO_MEM_MB_STATE_PLUGGED) { 14868c2ecf20Sopenharmony_ci rc = virtio_mem_mb_unplug(vm, mb_id); 14878c2ecf20Sopenharmony_ci if (rc) 14888c2ecf20Sopenharmony_ci return rc; 14898c2ecf20Sopenharmony_ci virtio_mem_mb_set_state(vm, mb_id, VIRTIO_MEM_MB_STATE_UNUSED); 14908c2ecf20Sopenharmony_ci } 14918c2ecf20Sopenharmony_ci 14928c2ecf20Sopenharmony_ci return 0; 14938c2ecf20Sopenharmony_ci} 14948c2ecf20Sopenharmony_ci 14958c2ecf20Sopenharmony_ci/* 14968c2ecf20Sopenharmony_ci * Update all parts of the config that could have changed. 14978c2ecf20Sopenharmony_ci */ 14988c2ecf20Sopenharmony_cistatic void virtio_mem_refresh_config(struct virtio_mem *vm) 14998c2ecf20Sopenharmony_ci{ 15008c2ecf20Sopenharmony_ci const uint64_t phys_limit = 1UL << MAX_PHYSMEM_BITS; 15018c2ecf20Sopenharmony_ci uint64_t new_plugged_size, usable_region_size, end_addr; 15028c2ecf20Sopenharmony_ci 15038c2ecf20Sopenharmony_ci /* the plugged_size is just a reflection of what _we_ did previously */ 15048c2ecf20Sopenharmony_ci virtio_cread_le(vm->vdev, struct virtio_mem_config, plugged_size, 15058c2ecf20Sopenharmony_ci &new_plugged_size); 15068c2ecf20Sopenharmony_ci if (WARN_ON_ONCE(new_plugged_size != vm->plugged_size)) 15078c2ecf20Sopenharmony_ci vm->plugged_size = new_plugged_size; 15088c2ecf20Sopenharmony_ci 15098c2ecf20Sopenharmony_ci /* calculate the last usable memory block id */ 15108c2ecf20Sopenharmony_ci virtio_cread_le(vm->vdev, struct virtio_mem_config, 15118c2ecf20Sopenharmony_ci usable_region_size, &usable_region_size); 15128c2ecf20Sopenharmony_ci end_addr = vm->addr + usable_region_size; 15138c2ecf20Sopenharmony_ci end_addr = min(end_addr, phys_limit); 15148c2ecf20Sopenharmony_ci vm->last_usable_mb_id = virtio_mem_phys_to_mb_id(end_addr) - 1; 15158c2ecf20Sopenharmony_ci 15168c2ecf20Sopenharmony_ci /* see if there is a request to change the size */ 15178c2ecf20Sopenharmony_ci virtio_cread_le(vm->vdev, struct virtio_mem_config, requested_size, 15188c2ecf20Sopenharmony_ci &vm->requested_size); 15198c2ecf20Sopenharmony_ci 15208c2ecf20Sopenharmony_ci dev_info(&vm->vdev->dev, "plugged size: 0x%llx", vm->plugged_size); 15218c2ecf20Sopenharmony_ci dev_info(&vm->vdev->dev, "requested size: 0x%llx", vm->requested_size); 15228c2ecf20Sopenharmony_ci} 15238c2ecf20Sopenharmony_ci 15248c2ecf20Sopenharmony_ci/* 15258c2ecf20Sopenharmony_ci * Workqueue function for handling plug/unplug requests and config updates. 15268c2ecf20Sopenharmony_ci */ 15278c2ecf20Sopenharmony_cistatic void virtio_mem_run_wq(struct work_struct *work) 15288c2ecf20Sopenharmony_ci{ 15298c2ecf20Sopenharmony_ci struct virtio_mem *vm = container_of(work, struct virtio_mem, wq); 15308c2ecf20Sopenharmony_ci uint64_t diff; 15318c2ecf20Sopenharmony_ci int rc; 15328c2ecf20Sopenharmony_ci 15338c2ecf20Sopenharmony_ci hrtimer_cancel(&vm->retry_timer); 15348c2ecf20Sopenharmony_ci 15358c2ecf20Sopenharmony_ci if (vm->broken) 15368c2ecf20Sopenharmony_ci return; 15378c2ecf20Sopenharmony_ci 15388c2ecf20Sopenharmony_ciretry: 15398c2ecf20Sopenharmony_ci rc = 0; 15408c2ecf20Sopenharmony_ci 15418c2ecf20Sopenharmony_ci /* Make sure we start with a clean state if there are leftovers. */ 15428c2ecf20Sopenharmony_ci if (unlikely(vm->unplug_all_required)) 15438c2ecf20Sopenharmony_ci rc = virtio_mem_send_unplug_all_request(vm); 15448c2ecf20Sopenharmony_ci 15458c2ecf20Sopenharmony_ci if (atomic_read(&vm->config_changed)) { 15468c2ecf20Sopenharmony_ci atomic_set(&vm->config_changed, 0); 15478c2ecf20Sopenharmony_ci virtio_mem_refresh_config(vm); 15488c2ecf20Sopenharmony_ci } 15498c2ecf20Sopenharmony_ci 15508c2ecf20Sopenharmony_ci /* Unplug any leftovers from previous runs */ 15518c2ecf20Sopenharmony_ci if (!rc) 15528c2ecf20Sopenharmony_ci rc = virtio_mem_unplug_pending_mb(vm); 15538c2ecf20Sopenharmony_ci 15548c2ecf20Sopenharmony_ci if (!rc && vm->requested_size != vm->plugged_size) { 15558c2ecf20Sopenharmony_ci if (vm->requested_size > vm->plugged_size) { 15568c2ecf20Sopenharmony_ci diff = vm->requested_size - vm->plugged_size; 15578c2ecf20Sopenharmony_ci rc = virtio_mem_plug_request(vm, diff); 15588c2ecf20Sopenharmony_ci } else { 15598c2ecf20Sopenharmony_ci diff = vm->plugged_size - vm->requested_size; 15608c2ecf20Sopenharmony_ci rc = virtio_mem_unplug_request(vm, diff); 15618c2ecf20Sopenharmony_ci } 15628c2ecf20Sopenharmony_ci } 15638c2ecf20Sopenharmony_ci 15648c2ecf20Sopenharmony_ci switch (rc) { 15658c2ecf20Sopenharmony_ci case 0: 15668c2ecf20Sopenharmony_ci vm->retry_timer_ms = VIRTIO_MEM_RETRY_TIMER_MIN_MS; 15678c2ecf20Sopenharmony_ci break; 15688c2ecf20Sopenharmony_ci case -ENOSPC: 15698c2ecf20Sopenharmony_ci /* 15708c2ecf20Sopenharmony_ci * We cannot add any more memory (alignment, physical limit) 15718c2ecf20Sopenharmony_ci * or we have too many offline memory blocks. 15728c2ecf20Sopenharmony_ci */ 15738c2ecf20Sopenharmony_ci break; 15748c2ecf20Sopenharmony_ci case -ETXTBSY: 15758c2ecf20Sopenharmony_ci /* 15768c2ecf20Sopenharmony_ci * The hypervisor cannot process our request right now 15778c2ecf20Sopenharmony_ci * (e.g., out of memory, migrating); 15788c2ecf20Sopenharmony_ci */ 15798c2ecf20Sopenharmony_ci case -EBUSY: 15808c2ecf20Sopenharmony_ci /* 15818c2ecf20Sopenharmony_ci * We cannot free up any memory to unplug it (all plugged memory 15828c2ecf20Sopenharmony_ci * is busy). 15838c2ecf20Sopenharmony_ci */ 15848c2ecf20Sopenharmony_ci case -ENOMEM: 15858c2ecf20Sopenharmony_ci /* Out of memory, try again later. */ 15868c2ecf20Sopenharmony_ci hrtimer_start(&vm->retry_timer, ms_to_ktime(vm->retry_timer_ms), 15878c2ecf20Sopenharmony_ci HRTIMER_MODE_REL); 15888c2ecf20Sopenharmony_ci break; 15898c2ecf20Sopenharmony_ci case -EAGAIN: 15908c2ecf20Sopenharmony_ci /* Retry immediately (e.g., the config changed). */ 15918c2ecf20Sopenharmony_ci goto retry; 15928c2ecf20Sopenharmony_ci default: 15938c2ecf20Sopenharmony_ci /* Unknown error, mark as broken */ 15948c2ecf20Sopenharmony_ci dev_err(&vm->vdev->dev, 15958c2ecf20Sopenharmony_ci "unknown error, marking device broken: %d\n", rc); 15968c2ecf20Sopenharmony_ci vm->broken = true; 15978c2ecf20Sopenharmony_ci } 15988c2ecf20Sopenharmony_ci} 15998c2ecf20Sopenharmony_ci 16008c2ecf20Sopenharmony_cistatic enum hrtimer_restart virtio_mem_timer_expired(struct hrtimer *timer) 16018c2ecf20Sopenharmony_ci{ 16028c2ecf20Sopenharmony_ci struct virtio_mem *vm = container_of(timer, struct virtio_mem, 16038c2ecf20Sopenharmony_ci retry_timer); 16048c2ecf20Sopenharmony_ci 16058c2ecf20Sopenharmony_ci virtio_mem_retry(vm); 16068c2ecf20Sopenharmony_ci vm->retry_timer_ms = min_t(unsigned int, vm->retry_timer_ms * 2, 16078c2ecf20Sopenharmony_ci VIRTIO_MEM_RETRY_TIMER_MAX_MS); 16088c2ecf20Sopenharmony_ci return HRTIMER_NORESTART; 16098c2ecf20Sopenharmony_ci} 16108c2ecf20Sopenharmony_ci 16118c2ecf20Sopenharmony_cistatic void virtio_mem_handle_response(struct virtqueue *vq) 16128c2ecf20Sopenharmony_ci{ 16138c2ecf20Sopenharmony_ci struct virtio_mem *vm = vq->vdev->priv; 16148c2ecf20Sopenharmony_ci 16158c2ecf20Sopenharmony_ci wake_up(&vm->host_resp); 16168c2ecf20Sopenharmony_ci} 16178c2ecf20Sopenharmony_ci 16188c2ecf20Sopenharmony_cistatic int virtio_mem_init_vq(struct virtio_mem *vm) 16198c2ecf20Sopenharmony_ci{ 16208c2ecf20Sopenharmony_ci struct virtqueue *vq; 16218c2ecf20Sopenharmony_ci 16228c2ecf20Sopenharmony_ci vq = virtio_find_single_vq(vm->vdev, virtio_mem_handle_response, 16238c2ecf20Sopenharmony_ci "guest-request"); 16248c2ecf20Sopenharmony_ci if (IS_ERR(vq)) 16258c2ecf20Sopenharmony_ci return PTR_ERR(vq); 16268c2ecf20Sopenharmony_ci vm->vq = vq; 16278c2ecf20Sopenharmony_ci 16288c2ecf20Sopenharmony_ci return 0; 16298c2ecf20Sopenharmony_ci} 16308c2ecf20Sopenharmony_ci 16318c2ecf20Sopenharmony_cistatic int virtio_mem_init(struct virtio_mem *vm) 16328c2ecf20Sopenharmony_ci{ 16338c2ecf20Sopenharmony_ci const uint64_t phys_limit = 1UL << MAX_PHYSMEM_BITS; 16348c2ecf20Sopenharmony_ci uint16_t node_id; 16358c2ecf20Sopenharmony_ci 16368c2ecf20Sopenharmony_ci if (!vm->vdev->config->get) { 16378c2ecf20Sopenharmony_ci dev_err(&vm->vdev->dev, "config access disabled\n"); 16388c2ecf20Sopenharmony_ci return -EINVAL; 16398c2ecf20Sopenharmony_ci } 16408c2ecf20Sopenharmony_ci 16418c2ecf20Sopenharmony_ci /* 16428c2ecf20Sopenharmony_ci * We don't want to (un)plug or reuse any memory when in kdump. The 16438c2ecf20Sopenharmony_ci * memory is still accessible (but not mapped). 16448c2ecf20Sopenharmony_ci */ 16458c2ecf20Sopenharmony_ci if (is_kdump_kernel()) { 16468c2ecf20Sopenharmony_ci dev_warn(&vm->vdev->dev, "disabled in kdump kernel\n"); 16478c2ecf20Sopenharmony_ci return -EBUSY; 16488c2ecf20Sopenharmony_ci } 16498c2ecf20Sopenharmony_ci 16508c2ecf20Sopenharmony_ci /* Fetch all properties that can't change. */ 16518c2ecf20Sopenharmony_ci virtio_cread_le(vm->vdev, struct virtio_mem_config, plugged_size, 16528c2ecf20Sopenharmony_ci &vm->plugged_size); 16538c2ecf20Sopenharmony_ci virtio_cread_le(vm->vdev, struct virtio_mem_config, block_size, 16548c2ecf20Sopenharmony_ci &vm->device_block_size); 16558c2ecf20Sopenharmony_ci virtio_cread_le(vm->vdev, struct virtio_mem_config, node_id, 16568c2ecf20Sopenharmony_ci &node_id); 16578c2ecf20Sopenharmony_ci vm->nid = virtio_mem_translate_node_id(vm, node_id); 16588c2ecf20Sopenharmony_ci virtio_cread_le(vm->vdev, struct virtio_mem_config, addr, &vm->addr); 16598c2ecf20Sopenharmony_ci virtio_cread_le(vm->vdev, struct virtio_mem_config, region_size, 16608c2ecf20Sopenharmony_ci &vm->region_size); 16618c2ecf20Sopenharmony_ci 16628c2ecf20Sopenharmony_ci /* 16638c2ecf20Sopenharmony_ci * We always hotplug memory in memory block granularity. This way, 16648c2ecf20Sopenharmony_ci * we have to wait for exactly one memory block to online. 16658c2ecf20Sopenharmony_ci */ 16668c2ecf20Sopenharmony_ci if (vm->device_block_size > memory_block_size_bytes()) { 16678c2ecf20Sopenharmony_ci dev_err(&vm->vdev->dev, 16688c2ecf20Sopenharmony_ci "The block size is not supported (too big).\n"); 16698c2ecf20Sopenharmony_ci return -EINVAL; 16708c2ecf20Sopenharmony_ci } 16718c2ecf20Sopenharmony_ci 16728c2ecf20Sopenharmony_ci /* bad device setup - warn only */ 16738c2ecf20Sopenharmony_ci if (!IS_ALIGNED(vm->addr, memory_block_size_bytes())) 16748c2ecf20Sopenharmony_ci dev_warn(&vm->vdev->dev, 16758c2ecf20Sopenharmony_ci "The alignment of the physical start address can make some memory unusable.\n"); 16768c2ecf20Sopenharmony_ci if (!IS_ALIGNED(vm->addr + vm->region_size, memory_block_size_bytes())) 16778c2ecf20Sopenharmony_ci dev_warn(&vm->vdev->dev, 16788c2ecf20Sopenharmony_ci "The alignment of the physical end address can make some memory unusable.\n"); 16798c2ecf20Sopenharmony_ci if (vm->addr + vm->region_size > phys_limit) 16808c2ecf20Sopenharmony_ci dev_warn(&vm->vdev->dev, 16818c2ecf20Sopenharmony_ci "Some memory is not addressable. This can make some memory unusable.\n"); 16828c2ecf20Sopenharmony_ci 16838c2ecf20Sopenharmony_ci /* 16848c2ecf20Sopenharmony_ci * Calculate the subblock size: 16858c2ecf20Sopenharmony_ci * - At least MAX_ORDER - 1 / pageblock_order. 16868c2ecf20Sopenharmony_ci * - At least the device block size. 16878c2ecf20Sopenharmony_ci * In the worst case, a single subblock per memory block. 16888c2ecf20Sopenharmony_ci */ 16898c2ecf20Sopenharmony_ci vm->subblock_size = PAGE_SIZE * 1ul << max_t(uint32_t, MAX_ORDER - 1, 16908c2ecf20Sopenharmony_ci pageblock_order); 16918c2ecf20Sopenharmony_ci vm->subblock_size = max_t(uint64_t, vm->device_block_size, 16928c2ecf20Sopenharmony_ci vm->subblock_size); 16938c2ecf20Sopenharmony_ci vm->nb_sb_per_mb = memory_block_size_bytes() / vm->subblock_size; 16948c2ecf20Sopenharmony_ci 16958c2ecf20Sopenharmony_ci /* Round up to the next full memory block */ 16968c2ecf20Sopenharmony_ci vm->first_mb_id = virtio_mem_phys_to_mb_id(vm->addr - 1 + 16978c2ecf20Sopenharmony_ci memory_block_size_bytes()); 16988c2ecf20Sopenharmony_ci vm->next_mb_id = vm->first_mb_id; 16998c2ecf20Sopenharmony_ci vm->last_mb_id = virtio_mem_phys_to_mb_id(vm->addr + 17008c2ecf20Sopenharmony_ci vm->region_size) - 1; 17018c2ecf20Sopenharmony_ci 17028c2ecf20Sopenharmony_ci dev_info(&vm->vdev->dev, "start address: 0x%llx", vm->addr); 17038c2ecf20Sopenharmony_ci dev_info(&vm->vdev->dev, "region size: 0x%llx", vm->region_size); 17048c2ecf20Sopenharmony_ci dev_info(&vm->vdev->dev, "device block size: 0x%llx", 17058c2ecf20Sopenharmony_ci (unsigned long long)vm->device_block_size); 17068c2ecf20Sopenharmony_ci dev_info(&vm->vdev->dev, "memory block size: 0x%lx", 17078c2ecf20Sopenharmony_ci memory_block_size_bytes()); 17088c2ecf20Sopenharmony_ci dev_info(&vm->vdev->dev, "subblock size: 0x%llx", 17098c2ecf20Sopenharmony_ci (unsigned long long)vm->subblock_size); 17108c2ecf20Sopenharmony_ci if (vm->nid != NUMA_NO_NODE) 17118c2ecf20Sopenharmony_ci dev_info(&vm->vdev->dev, "nid: %d", vm->nid); 17128c2ecf20Sopenharmony_ci 17138c2ecf20Sopenharmony_ci return 0; 17148c2ecf20Sopenharmony_ci} 17158c2ecf20Sopenharmony_ci 17168c2ecf20Sopenharmony_cistatic int virtio_mem_create_resource(struct virtio_mem *vm) 17178c2ecf20Sopenharmony_ci{ 17188c2ecf20Sopenharmony_ci /* 17198c2ecf20Sopenharmony_ci * When force-unloading the driver and removing the device, we 17208c2ecf20Sopenharmony_ci * could have a garbage pointer. Duplicate the string. 17218c2ecf20Sopenharmony_ci */ 17228c2ecf20Sopenharmony_ci const char *name = kstrdup(dev_name(&vm->vdev->dev), GFP_KERNEL); 17238c2ecf20Sopenharmony_ci 17248c2ecf20Sopenharmony_ci if (!name) 17258c2ecf20Sopenharmony_ci return -ENOMEM; 17268c2ecf20Sopenharmony_ci 17278c2ecf20Sopenharmony_ci vm->parent_resource = __request_mem_region(vm->addr, vm->region_size, 17288c2ecf20Sopenharmony_ci name, IORESOURCE_SYSTEM_RAM); 17298c2ecf20Sopenharmony_ci if (!vm->parent_resource) { 17308c2ecf20Sopenharmony_ci kfree(name); 17318c2ecf20Sopenharmony_ci dev_warn(&vm->vdev->dev, "could not reserve device region\n"); 17328c2ecf20Sopenharmony_ci dev_info(&vm->vdev->dev, 17338c2ecf20Sopenharmony_ci "reloading the driver is not supported\n"); 17348c2ecf20Sopenharmony_ci return -EBUSY; 17358c2ecf20Sopenharmony_ci } 17368c2ecf20Sopenharmony_ci 17378c2ecf20Sopenharmony_ci /* The memory is not actually busy - make add_memory() work. */ 17388c2ecf20Sopenharmony_ci vm->parent_resource->flags &= ~IORESOURCE_BUSY; 17398c2ecf20Sopenharmony_ci return 0; 17408c2ecf20Sopenharmony_ci} 17418c2ecf20Sopenharmony_ci 17428c2ecf20Sopenharmony_cistatic void virtio_mem_delete_resource(struct virtio_mem *vm) 17438c2ecf20Sopenharmony_ci{ 17448c2ecf20Sopenharmony_ci const char *name; 17458c2ecf20Sopenharmony_ci 17468c2ecf20Sopenharmony_ci if (!vm->parent_resource) 17478c2ecf20Sopenharmony_ci return; 17488c2ecf20Sopenharmony_ci 17498c2ecf20Sopenharmony_ci name = vm->parent_resource->name; 17508c2ecf20Sopenharmony_ci release_resource(vm->parent_resource); 17518c2ecf20Sopenharmony_ci kfree(vm->parent_resource); 17528c2ecf20Sopenharmony_ci kfree(name); 17538c2ecf20Sopenharmony_ci vm->parent_resource = NULL; 17548c2ecf20Sopenharmony_ci} 17558c2ecf20Sopenharmony_ci 17568c2ecf20Sopenharmony_cistatic int virtio_mem_probe(struct virtio_device *vdev) 17578c2ecf20Sopenharmony_ci{ 17588c2ecf20Sopenharmony_ci struct virtio_mem *vm; 17598c2ecf20Sopenharmony_ci int rc; 17608c2ecf20Sopenharmony_ci 17618c2ecf20Sopenharmony_ci BUILD_BUG_ON(sizeof(struct virtio_mem_req) != 24); 17628c2ecf20Sopenharmony_ci BUILD_BUG_ON(sizeof(struct virtio_mem_resp) != 10); 17638c2ecf20Sopenharmony_ci 17648c2ecf20Sopenharmony_ci vdev->priv = vm = kzalloc(sizeof(*vm), GFP_KERNEL); 17658c2ecf20Sopenharmony_ci if (!vm) 17668c2ecf20Sopenharmony_ci return -ENOMEM; 17678c2ecf20Sopenharmony_ci 17688c2ecf20Sopenharmony_ci init_waitqueue_head(&vm->host_resp); 17698c2ecf20Sopenharmony_ci vm->vdev = vdev; 17708c2ecf20Sopenharmony_ci INIT_WORK(&vm->wq, virtio_mem_run_wq); 17718c2ecf20Sopenharmony_ci mutex_init(&vm->hotplug_mutex); 17728c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&vm->next); 17738c2ecf20Sopenharmony_ci spin_lock_init(&vm->removal_lock); 17748c2ecf20Sopenharmony_ci hrtimer_init(&vm->retry_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); 17758c2ecf20Sopenharmony_ci vm->retry_timer.function = virtio_mem_timer_expired; 17768c2ecf20Sopenharmony_ci vm->retry_timer_ms = VIRTIO_MEM_RETRY_TIMER_MIN_MS; 17778c2ecf20Sopenharmony_ci 17788c2ecf20Sopenharmony_ci /* register the virtqueue */ 17798c2ecf20Sopenharmony_ci rc = virtio_mem_init_vq(vm); 17808c2ecf20Sopenharmony_ci if (rc) 17818c2ecf20Sopenharmony_ci goto out_free_vm; 17828c2ecf20Sopenharmony_ci 17838c2ecf20Sopenharmony_ci /* initialize the device by querying the config */ 17848c2ecf20Sopenharmony_ci rc = virtio_mem_init(vm); 17858c2ecf20Sopenharmony_ci if (rc) 17868c2ecf20Sopenharmony_ci goto out_del_vq; 17878c2ecf20Sopenharmony_ci 17888c2ecf20Sopenharmony_ci /* create the parent resource for all memory */ 17898c2ecf20Sopenharmony_ci rc = virtio_mem_create_resource(vm); 17908c2ecf20Sopenharmony_ci if (rc) 17918c2ecf20Sopenharmony_ci goto out_del_vq; 17928c2ecf20Sopenharmony_ci 17938c2ecf20Sopenharmony_ci /* 17948c2ecf20Sopenharmony_ci * If we still have memory plugged, we have to unplug all memory first. 17958c2ecf20Sopenharmony_ci * Registering our parent resource makes sure that this memory isn't 17968c2ecf20Sopenharmony_ci * actually in use (e.g., trying to reload the driver). 17978c2ecf20Sopenharmony_ci */ 17988c2ecf20Sopenharmony_ci if (vm->plugged_size) { 17998c2ecf20Sopenharmony_ci vm->unplug_all_required = 1; 18008c2ecf20Sopenharmony_ci dev_info(&vm->vdev->dev, "unplugging all memory is required\n"); 18018c2ecf20Sopenharmony_ci } 18028c2ecf20Sopenharmony_ci 18038c2ecf20Sopenharmony_ci /* register callbacks */ 18048c2ecf20Sopenharmony_ci vm->memory_notifier.notifier_call = virtio_mem_memory_notifier_cb; 18058c2ecf20Sopenharmony_ci rc = register_memory_notifier(&vm->memory_notifier); 18068c2ecf20Sopenharmony_ci if (rc) 18078c2ecf20Sopenharmony_ci goto out_del_resource; 18088c2ecf20Sopenharmony_ci rc = register_virtio_mem_device(vm); 18098c2ecf20Sopenharmony_ci if (rc) 18108c2ecf20Sopenharmony_ci goto out_unreg_mem; 18118c2ecf20Sopenharmony_ci 18128c2ecf20Sopenharmony_ci virtio_device_ready(vdev); 18138c2ecf20Sopenharmony_ci 18148c2ecf20Sopenharmony_ci /* trigger a config update to start processing the requested_size */ 18158c2ecf20Sopenharmony_ci atomic_set(&vm->config_changed, 1); 18168c2ecf20Sopenharmony_ci queue_work(system_freezable_wq, &vm->wq); 18178c2ecf20Sopenharmony_ci 18188c2ecf20Sopenharmony_ci return 0; 18198c2ecf20Sopenharmony_ciout_unreg_mem: 18208c2ecf20Sopenharmony_ci unregister_memory_notifier(&vm->memory_notifier); 18218c2ecf20Sopenharmony_ciout_del_resource: 18228c2ecf20Sopenharmony_ci virtio_mem_delete_resource(vm); 18238c2ecf20Sopenharmony_ciout_del_vq: 18248c2ecf20Sopenharmony_ci vdev->config->del_vqs(vdev); 18258c2ecf20Sopenharmony_ciout_free_vm: 18268c2ecf20Sopenharmony_ci kfree(vm); 18278c2ecf20Sopenharmony_ci vdev->priv = NULL; 18288c2ecf20Sopenharmony_ci 18298c2ecf20Sopenharmony_ci return rc; 18308c2ecf20Sopenharmony_ci} 18318c2ecf20Sopenharmony_ci 18328c2ecf20Sopenharmony_cistatic void virtio_mem_remove(struct virtio_device *vdev) 18338c2ecf20Sopenharmony_ci{ 18348c2ecf20Sopenharmony_ci struct virtio_mem *vm = vdev->priv; 18358c2ecf20Sopenharmony_ci unsigned long mb_id; 18368c2ecf20Sopenharmony_ci int rc; 18378c2ecf20Sopenharmony_ci 18388c2ecf20Sopenharmony_ci /* 18398c2ecf20Sopenharmony_ci * Make sure the workqueue won't be triggered anymore and no memory 18408c2ecf20Sopenharmony_ci * blocks can be onlined/offlined until we're finished here. 18418c2ecf20Sopenharmony_ci */ 18428c2ecf20Sopenharmony_ci mutex_lock(&vm->hotplug_mutex); 18438c2ecf20Sopenharmony_ci spin_lock_irq(&vm->removal_lock); 18448c2ecf20Sopenharmony_ci vm->removing = true; 18458c2ecf20Sopenharmony_ci spin_unlock_irq(&vm->removal_lock); 18468c2ecf20Sopenharmony_ci mutex_unlock(&vm->hotplug_mutex); 18478c2ecf20Sopenharmony_ci 18488c2ecf20Sopenharmony_ci /* wait until the workqueue stopped */ 18498c2ecf20Sopenharmony_ci cancel_work_sync(&vm->wq); 18508c2ecf20Sopenharmony_ci hrtimer_cancel(&vm->retry_timer); 18518c2ecf20Sopenharmony_ci 18528c2ecf20Sopenharmony_ci /* 18538c2ecf20Sopenharmony_ci * After we unregistered our callbacks, user space can online partially 18548c2ecf20Sopenharmony_ci * plugged offline blocks. Make sure to remove them. 18558c2ecf20Sopenharmony_ci */ 18568c2ecf20Sopenharmony_ci virtio_mem_for_each_mb_state(vm, mb_id, 18578c2ecf20Sopenharmony_ci VIRTIO_MEM_MB_STATE_OFFLINE_PARTIAL) { 18588c2ecf20Sopenharmony_ci rc = virtio_mem_mb_remove(vm, mb_id); 18598c2ecf20Sopenharmony_ci BUG_ON(rc); 18608c2ecf20Sopenharmony_ci virtio_mem_mb_set_state(vm, mb_id, VIRTIO_MEM_MB_STATE_UNUSED); 18618c2ecf20Sopenharmony_ci } 18628c2ecf20Sopenharmony_ci /* 18638c2ecf20Sopenharmony_ci * After we unregistered our callbacks, user space can no longer 18648c2ecf20Sopenharmony_ci * offline partially plugged online memory blocks. No need to worry 18658c2ecf20Sopenharmony_ci * about them. 18668c2ecf20Sopenharmony_ci */ 18678c2ecf20Sopenharmony_ci 18688c2ecf20Sopenharmony_ci /* unregister callbacks */ 18698c2ecf20Sopenharmony_ci unregister_virtio_mem_device(vm); 18708c2ecf20Sopenharmony_ci unregister_memory_notifier(&vm->memory_notifier); 18718c2ecf20Sopenharmony_ci 18728c2ecf20Sopenharmony_ci /* 18738c2ecf20Sopenharmony_ci * There is no way we could reliably remove all memory we have added to 18748c2ecf20Sopenharmony_ci * the system. And there is no way to stop the driver/device from going 18758c2ecf20Sopenharmony_ci * away. Warn at least. 18768c2ecf20Sopenharmony_ci */ 18778c2ecf20Sopenharmony_ci if (vm->nb_mb_state[VIRTIO_MEM_MB_STATE_OFFLINE] || 18788c2ecf20Sopenharmony_ci vm->nb_mb_state[VIRTIO_MEM_MB_STATE_OFFLINE_PARTIAL] || 18798c2ecf20Sopenharmony_ci vm->nb_mb_state[VIRTIO_MEM_MB_STATE_ONLINE] || 18808c2ecf20Sopenharmony_ci vm->nb_mb_state[VIRTIO_MEM_MB_STATE_ONLINE_PARTIAL]) { 18818c2ecf20Sopenharmony_ci dev_warn(&vdev->dev, "device still has system memory added\n"); 18828c2ecf20Sopenharmony_ci } else { 18838c2ecf20Sopenharmony_ci virtio_mem_delete_resource(vm); 18848c2ecf20Sopenharmony_ci kfree_const(vm->resource_name); 18858c2ecf20Sopenharmony_ci } 18868c2ecf20Sopenharmony_ci 18878c2ecf20Sopenharmony_ci /* remove all tracking data - no locking needed */ 18888c2ecf20Sopenharmony_ci vfree(vm->mb_state); 18898c2ecf20Sopenharmony_ci vfree(vm->sb_bitmap); 18908c2ecf20Sopenharmony_ci 18918c2ecf20Sopenharmony_ci /* reset the device and cleanup the queues */ 18928c2ecf20Sopenharmony_ci vdev->config->reset(vdev); 18938c2ecf20Sopenharmony_ci vdev->config->del_vqs(vdev); 18948c2ecf20Sopenharmony_ci 18958c2ecf20Sopenharmony_ci kfree(vm); 18968c2ecf20Sopenharmony_ci vdev->priv = NULL; 18978c2ecf20Sopenharmony_ci} 18988c2ecf20Sopenharmony_ci 18998c2ecf20Sopenharmony_cistatic void virtio_mem_config_changed(struct virtio_device *vdev) 19008c2ecf20Sopenharmony_ci{ 19018c2ecf20Sopenharmony_ci struct virtio_mem *vm = vdev->priv; 19028c2ecf20Sopenharmony_ci 19038c2ecf20Sopenharmony_ci atomic_set(&vm->config_changed, 1); 19048c2ecf20Sopenharmony_ci virtio_mem_retry(vm); 19058c2ecf20Sopenharmony_ci} 19068c2ecf20Sopenharmony_ci 19078c2ecf20Sopenharmony_ci#ifdef CONFIG_PM_SLEEP 19088c2ecf20Sopenharmony_cistatic int virtio_mem_freeze(struct virtio_device *vdev) 19098c2ecf20Sopenharmony_ci{ 19108c2ecf20Sopenharmony_ci /* 19118c2ecf20Sopenharmony_ci * When restarting the VM, all memory is usually unplugged. Don't 19128c2ecf20Sopenharmony_ci * allow to suspend/hibernate. 19138c2ecf20Sopenharmony_ci */ 19148c2ecf20Sopenharmony_ci dev_err(&vdev->dev, "save/restore not supported.\n"); 19158c2ecf20Sopenharmony_ci return -EPERM; 19168c2ecf20Sopenharmony_ci} 19178c2ecf20Sopenharmony_ci 19188c2ecf20Sopenharmony_cistatic int virtio_mem_restore(struct virtio_device *vdev) 19198c2ecf20Sopenharmony_ci{ 19208c2ecf20Sopenharmony_ci return -EPERM; 19218c2ecf20Sopenharmony_ci} 19228c2ecf20Sopenharmony_ci#endif 19238c2ecf20Sopenharmony_ci 19248c2ecf20Sopenharmony_cistatic unsigned int virtio_mem_features[] = { 19258c2ecf20Sopenharmony_ci#if defined(CONFIG_NUMA) && defined(CONFIG_ACPI_NUMA) 19268c2ecf20Sopenharmony_ci VIRTIO_MEM_F_ACPI_PXM, 19278c2ecf20Sopenharmony_ci#endif 19288c2ecf20Sopenharmony_ci}; 19298c2ecf20Sopenharmony_ci 19308c2ecf20Sopenharmony_cistatic const struct virtio_device_id virtio_mem_id_table[] = { 19318c2ecf20Sopenharmony_ci { VIRTIO_ID_MEM, VIRTIO_DEV_ANY_ID }, 19328c2ecf20Sopenharmony_ci { 0 }, 19338c2ecf20Sopenharmony_ci}; 19348c2ecf20Sopenharmony_ci 19358c2ecf20Sopenharmony_cistatic struct virtio_driver virtio_mem_driver = { 19368c2ecf20Sopenharmony_ci .feature_table = virtio_mem_features, 19378c2ecf20Sopenharmony_ci .feature_table_size = ARRAY_SIZE(virtio_mem_features), 19388c2ecf20Sopenharmony_ci .driver.name = KBUILD_MODNAME, 19398c2ecf20Sopenharmony_ci .driver.owner = THIS_MODULE, 19408c2ecf20Sopenharmony_ci .id_table = virtio_mem_id_table, 19418c2ecf20Sopenharmony_ci .probe = virtio_mem_probe, 19428c2ecf20Sopenharmony_ci .remove = virtio_mem_remove, 19438c2ecf20Sopenharmony_ci .config_changed = virtio_mem_config_changed, 19448c2ecf20Sopenharmony_ci#ifdef CONFIG_PM_SLEEP 19458c2ecf20Sopenharmony_ci .freeze = virtio_mem_freeze, 19468c2ecf20Sopenharmony_ci .restore = virtio_mem_restore, 19478c2ecf20Sopenharmony_ci#endif 19488c2ecf20Sopenharmony_ci}; 19498c2ecf20Sopenharmony_ci 19508c2ecf20Sopenharmony_cimodule_virtio_driver(virtio_mem_driver); 19518c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(virtio, virtio_mem_id_table); 19528c2ecf20Sopenharmony_ciMODULE_AUTHOR("David Hildenbrand <david@redhat.com>"); 19538c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Virtio-mem driver"); 19548c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 1955