diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/misc/vmw_balloon.c | 384 |
1 files changed, 281 insertions, 103 deletions
diff --git a/drivers/misc/vmw_balloon.c b/drivers/misc/vmw_balloon.c index 3c80a21e0f91..0a2bdaf5773b 100644 --- a/drivers/misc/vmw_balloon.c +++ b/drivers/misc/vmw_balloon.c @@ -25,6 +25,8 @@ #include <linux/workqueue.h> #include <linux/debugfs.h> #include <linux/seq_file.h> +#include <linux/rwsem.h> +#include <linux/slab.h> #include <linux/vmw_vmci_defs.h> #include <linux/vmw_vmci_api.h> #include <asm/hypervisor.h> @@ -78,46 +80,94 @@ enum vmwballoon_capabilities { | VMW_BALLOON_SIGNALLED_WAKEUP_CMD) #define VMW_BALLOON_2M_ORDER (PMD_SHIFT - PAGE_SHIFT) -#define VMW_BALLOON_NUM_PAGE_SIZES (2) -/* - * Backdoor commands availability: +enum vmballoon_page_size_type { + VMW_BALLOON_4K_PAGE, + VMW_BALLOON_2M_PAGE, + VMW_BALLOON_LAST_SIZE = VMW_BALLOON_2M_PAGE +}; + +#define VMW_BALLOON_NUM_PAGE_SIZES (VMW_BALLOON_LAST_SIZE + 1) + +enum vmballoon_op_stat_type { + VMW_BALLOON_OP_STAT, + VMW_BALLOON_OP_FAIL_STAT +}; + +#define VMW_BALLOON_OP_STAT_TYPES (VMW_BALLOON_OP_FAIL_STAT + 1) + +/** + * enum vmballoon_cmd_type - backdoor commands. + * + * Availability of the commands is as followed: + * + * %VMW_BALLOON_CMD_START, %VMW_BALLOON_CMD_GET_TARGET and + * %VMW_BALLOON_CMD_GUEST_ID are always available. + * + * If the host reports %VMW_BALLOON_BASIC_CMDS are supported then + * %VMW_BALLOON_CMD_LOCK and %VMW_BALLOON_CMD_UNLOCK commands are available. * - * START, GET_TARGET and GUEST_ID are always available, + * If the host reports %VMW_BALLOON_BATCHED_CMDS are supported then + * %VMW_BALLOON_CMD_BATCHED_LOCK and VMW_BALLOON_CMD_BATCHED_UNLOCK commands + * are available. * - * VMW_BALLOON_BASIC_CMDS: - * LOCK and UNLOCK commands, - * VMW_BALLOON_BATCHED_CMDS: - * BATCHED_LOCK and BATCHED_UNLOCK commands. - * VMW BALLOON_BATCHED_2M_CMDS: - * BATCHED_2M_LOCK and BATCHED_2M_UNLOCK commands, - * VMW VMW_BALLOON_SIGNALLED_WAKEUP_CMD: - * VMW_BALLOON_CMD_VMCI_DOORBELL_SET command. + * If the host reports %VMW_BALLOON_BATCHED_2M_CMDS are supported then + * %VMW_BALLOON_CMD_BATCHED_2M_LOCK and %VMW_BALLOON_CMD_BATCHED_2M_UNLOCK + * are supported. + * + * If the host reports VMW_BALLOON_SIGNALLED_WAKEUP_CMD is supported then + * VMW_BALLOON_CMD_VMCI_DOORBELL_SET command is supported. + * + * @VMW_BALLOON_CMD_START: Communicating supported version with the hypervisor. + * @VMW_BALLOON_CMD_GET_TARGET: Gets the balloon target size. + * @VMW_BALLOON_CMD_LOCK: Informs the hypervisor about a ballooned page. + * @VMW_BALLOON_CMD_UNLOCK: Informs the hypervisor about a page that is about + * to be deflated from the balloon. + * @VMW_BALLOON_CMD_GUEST_ID: Informs the hypervisor about the type of OS that + * runs in the VM. + * @VMW_BALLOON_CMD_BATCHED_LOCK: Inform the hypervisor about a batch of + * ballooned pages (up to 512). + * @VMW_BALLOON_CMD_BATCHED_UNLOCK: Inform the hypervisor about a batch of + * pages that are about to be deflated from the + * balloon (up to 512). + * @VMW_BALLOON_CMD_BATCHED_2M_LOCK: Similar to @VMW_BALLOON_CMD_BATCHED_LOCK + * for 2MB pages. + * @VMW_BALLOON_CMD_BATCHED_2M_UNLOCK: Similar to + * @VMW_BALLOON_CMD_BATCHED_UNLOCK for 2MB + * pages. + * @VMW_BALLOON_CMD_VMCI_DOORBELL_SET: A command to set doorbell notification + * that would be invoked when the balloon + * size changes. + * @VMW_BALLOON_CMD_LAST: Value of the last command. */ -#define VMW_BALLOON_CMD_START 0 -#define VMW_BALLOON_CMD_GET_TARGET 1 -#define VMW_BALLOON_CMD_LOCK 2 -#define VMW_BALLOON_CMD_UNLOCK 3 -#define VMW_BALLOON_CMD_GUEST_ID 4 -#define VMW_BALLOON_CMD_BATCHED_LOCK 6 -#define VMW_BALLOON_CMD_BATCHED_UNLOCK 7 -#define VMW_BALLOON_CMD_BATCHED_2M_LOCK 8 -#define VMW_BALLOON_CMD_BATCHED_2M_UNLOCK 9 -#define VMW_BALLOON_CMD_VMCI_DOORBELL_SET 10 - -#define VMW_BALLOON_CMD_NUM 11 - -/* error codes */ -#define VMW_BALLOON_SUCCESS 0 -#define VMW_BALLOON_FAILURE -1 -#define VMW_BALLOON_ERROR_CMD_INVALID 1 -#define VMW_BALLOON_ERROR_PPN_INVALID 2 -#define VMW_BALLOON_ERROR_PPN_LOCKED 3 -#define VMW_BALLOON_ERROR_PPN_UNLOCKED 4 -#define VMW_BALLOON_ERROR_PPN_PINNED 5 -#define VMW_BALLOON_ERROR_PPN_NOTNEEDED 6 -#define VMW_BALLOON_ERROR_RESET 7 -#define VMW_BALLOON_ERROR_BUSY 8 +enum vmballoon_cmd_type { + VMW_BALLOON_CMD_START, + VMW_BALLOON_CMD_GET_TARGET, + VMW_BALLOON_CMD_LOCK, + VMW_BALLOON_CMD_UNLOCK, + VMW_BALLOON_CMD_GUEST_ID, + /* No command 5 */ + VMW_BALLOON_CMD_BATCHED_LOCK = 6, + VMW_BALLOON_CMD_BATCHED_UNLOCK, + VMW_BALLOON_CMD_BATCHED_2M_LOCK, + VMW_BALLOON_CMD_BATCHED_2M_UNLOCK, + VMW_BALLOON_CMD_VMCI_DOORBELL_SET, + VMW_BALLOON_CMD_LAST = VMW_BALLOON_CMD_VMCI_DOORBELL_SET, +}; + +#define VMW_BALLOON_CMD_NUM (VMW_BALLOON_CMD_LAST + 1) + +enum vmballoon_error_codes { + VMW_BALLOON_SUCCESS, + VMW_BALLOON_ERROR_CMD_INVALID, + VMW_BALLOON_ERROR_PPN_INVALID, + VMW_BALLOON_ERROR_PPN_LOCKED, + VMW_BALLOON_ERROR_PPN_UNLOCKED, + VMW_BALLOON_ERROR_PPN_PINNED, + VMW_BALLOON_ERROR_PPN_NOTNEEDED, + VMW_BALLOON_ERROR_RESET, + VMW_BALLOON_ERROR_BUSY +}; #define VMW_BALLOON_SUCCESS_WITH_CAPABILITIES (0x03000000) @@ -143,29 +193,28 @@ static const char * const vmballoon_cmd_names[] = { [VMW_BALLOON_CMD_VMCI_DOORBELL_SET] = "doorbellSet" }; -#ifdef CONFIG_DEBUG_FS -struct vmballoon_stats { - unsigned int timer; - unsigned int doorbell; - - /* allocation statistics */ - unsigned int alloc[VMW_BALLOON_NUM_PAGE_SIZES]; - unsigned int alloc_fail[VMW_BALLOON_NUM_PAGE_SIZES]; - unsigned int refused_alloc[VMW_BALLOON_NUM_PAGE_SIZES]; - unsigned int refused_free[VMW_BALLOON_NUM_PAGE_SIZES]; - unsigned int free[VMW_BALLOON_NUM_PAGE_SIZES]; - - /* Monitor operations. */ - unsigned long ops[VMW_BALLOON_CMD_NUM]; - unsigned long ops_fail[VMW_BALLOON_CMD_NUM]; +enum vmballoon_stat_page { + VMW_BALLOON_PAGE_STAT_ALLOC, + VMW_BALLOON_PAGE_STAT_ALLOC_FAIL, + VMW_BALLOON_PAGE_STAT_REFUSED_ALLOC, + VMW_BALLOON_PAGE_STAT_REFUSED_FREE, + VMW_BALLOON_PAGE_STAT_FREE, + VMW_BALLOON_PAGE_STAT_LAST = VMW_BALLOON_PAGE_STAT_FREE }; -#define STATS_INC(stat) (stat)++ -#else -#define STATS_INC(stat) -#endif +#define VMW_BALLOON_PAGE_STAT_NUM (VMW_BALLOON_PAGE_STAT_LAST + 1) + +enum vmballoon_stat_general { + VMW_BALLOON_STAT_TIMER, + VMW_BALLOON_STAT_DOORBELL, + VMW_BALLOON_STAT_LAST = VMW_BALLOON_STAT_DOORBELL +}; + +#define VMW_BALLOON_STAT_NUM (VMW_BALLOON_STAT_LAST + 1) + static DEFINE_STATIC_KEY_TRUE(vmw_balloon_batching); +static DEFINE_STATIC_KEY_FALSE(balloon_stat_enabled); struct vmballoon_page_size { /* list of reserved physical pages */ @@ -215,10 +264,10 @@ struct vmballoon { unsigned int batch_max_pages; struct page *page; -#ifdef CONFIG_DEBUG_FS /* statistics */ - struct vmballoon_stats stats; + struct vmballoon_stats *stats; +#ifdef CONFIG_DEBUG_FS /* debugfs file exporting statistics */ struct dentry *dbg_entry; #endif @@ -226,17 +275,70 @@ struct vmballoon { struct delayed_work dwork; struct vmci_handle vmci_doorbell; + + /** + * @conf_sem: semaphore to protect the configuration and the statistics. + */ + struct rw_semaphore conf_sem; }; static struct vmballoon balloon; +struct vmballoon_stats { + /* timer / doorbell operations */ + atomic64_t general_stat[VMW_BALLOON_STAT_NUM]; + + /* allocation statistics for huge and small pages */ + atomic64_t + page_stat[VMW_BALLOON_PAGE_STAT_NUM][VMW_BALLOON_NUM_PAGE_SIZES]; + + /* Monitor operations: total operations, and failures */ + atomic64_t ops[VMW_BALLOON_CMD_NUM][VMW_BALLOON_OP_STAT_TYPES]; +}; + +static inline bool is_vmballoon_stats_on(void) +{ + return IS_ENABLED(CONFIG_DEBUG_FS) && + static_branch_unlikely(&balloon_stat_enabled); +} + +static inline void vmballoon_stats_op_inc(struct vmballoon *b, unsigned int op, + enum vmballoon_op_stat_type type) +{ + if (is_vmballoon_stats_on()) + atomic64_inc(&b->stats->ops[op][type]); +} + +static inline void vmballoon_stats_gen_inc(struct vmballoon *b, + enum vmballoon_stat_general stat) +{ + if (is_vmballoon_stats_on()) + atomic64_inc(&b->stats->general_stat[stat]); +} + +static inline void vmballoon_stats_gen_add(struct vmballoon *b, + enum vmballoon_stat_general stat, + unsigned int val) +{ + if (is_vmballoon_stats_on()) + atomic64_add(val, &b->stats->general_stat[stat]); +} + +static inline void vmballoon_stats_page_inc(struct vmballoon *b, + enum vmballoon_stat_page stat, + bool is_2m_page) +{ + if (is_vmballoon_stats_on()) + atomic64_inc(&b->stats->page_stat[stat][is_2m_page]); +} + static inline unsigned long __vmballoon_cmd(struct vmballoon *b, unsigned long cmd, unsigned long arg1, unsigned long arg2, unsigned long *result) { unsigned long status, dummy1, dummy2, dummy3, local_result; - STATS_INC(b->stats.ops[cmd]); + vmballoon_stats_op_inc(b, cmd, VMW_BALLOON_OP_STAT); asm volatile ("inl %%dx" : "=a"(status), @@ -263,7 +365,7 @@ __vmballoon_cmd(struct vmballoon *b, unsigned long cmd, unsigned long arg1, if (status != VMW_BALLOON_SUCCESS && status != VMW_BALLOON_SUCCESS_WITH_CAPABILITIES) { - STATS_INC(b->stats.ops_fail[cmd]); + vmballoon_stats_op_inc(b, cmd, VMW_BALLOON_OP_FAIL_STAT); pr_debug("%s: %s [0x%lx,0x%lx) failed, returned %ld\n", __func__, vmballoon_cmd_names[cmd], arg1, arg2, status); @@ -413,7 +515,8 @@ static void vmballoon_pop(struct vmballoon *b) list_for_each_entry_safe(page, next, &page_size->pages, lru) { list_del(&page->lru); vmballoon_free_page(page, is_2m_pages); - STATS_INC(b->stats.free[is_2m_pages]); + vmballoon_stats_page_inc(b, VMW_BALLOON_PAGE_STAT_FREE, + is_2m_pages); b->size -= size_per_page; cond_resched(); } @@ -534,7 +637,8 @@ static int vmballoon_lock(struct vmballoon *b, unsigned int num_pages, } /* Error occurred */ - STATS_INC(b->stats.refused_alloc[is_2m_pages]); + vmballoon_stats_page_inc(b, VMW_BALLOON_PAGE_STAT_REFUSED_ALLOC, + is_2m_pages); /* * Place page on the list of non-balloonable pages @@ -587,7 +691,8 @@ static int vmballoon_unlock(struct vmballoon *b, unsigned int num_pages, } else { /* deallocate page */ vmballoon_free_page(p, is_2m_pages); - STATS_INC(b->stats.free[is_2m_pages]); + vmballoon_stats_page_inc(b, VMW_BALLOON_PAGE_STAT_FREE, + is_2m_pages); /* update balloon size */ b->size -= size_per_page; @@ -611,7 +716,8 @@ static void vmballoon_release_refused_pages(struct vmballoon *b, list_for_each_entry_safe(page, next, &page_size->refused_pages, lru) { list_del(&page->lru); vmballoon_free_page(page, is_2m_pages); - STATS_INC(b->stats.refused_free[is_2m_pages]); + vmballoon_stats_page_inc(b, VMW_BALLOON_PAGE_STAT_REFUSED_FREE, + is_2m_pages); } page_size->n_refused_pages = 0; @@ -693,10 +799,14 @@ static void vmballoon_inflate(struct vmballoon *b) vmballoon_change(b)) { struct page *page; - STATS_INC(b->stats.alloc[is_2m_pages]); + vmballoon_stats_page_inc(b, VMW_BALLOON_PAGE_STAT_ALLOC, + is_2m_pages); + page = vmballoon_alloc_page(is_2m_pages); if (!page) { - STATS_INC(b->stats.alloc_fail[is_2m_pages]); + vmballoon_stats_page_inc(b, + VMW_BALLOON_PAGE_STAT_ALLOC_FAIL, is_2m_pages); + if (is_2m_pages) { vmballoon_lock(b, num_pages, true); @@ -845,7 +955,7 @@ static void vmballoon_doorbell(void *client_data) { struct vmballoon *b = client_data; - STATS_INC(b->stats.doorbell); + vmballoon_stats_gen_inc(b, VMW_BALLOON_STAT_DOORBELL); mod_delayed_work(system_freezable_wq, &b->dwork, 0); } @@ -903,6 +1013,8 @@ static void vmballoon_reset(struct vmballoon *b) { int error; + down_write(&b->conf_sem); + vmballoon_vmci_cleanup(b); /* free all pages, skipping monitor unlock */ @@ -934,6 +1046,8 @@ static void vmballoon_reset(struct vmballoon *b) if (!vmballoon_send_guest_id(b)) pr_err("failed to send guest ID to the host\n"); + + up_write(&b->conf_sem); } /** @@ -950,11 +1064,18 @@ static void vmballoon_work(struct work_struct *work) struct vmballoon *b = container_of(dwork, struct vmballoon, dwork); int64_t change = 0; - STATS_INC(b->stats.timer); - if (b->reset_required) vmballoon_reset(b); + down_read(&b->conf_sem); + + /* + * Update the stats while holding the semaphore to ensure that + * @stats_enabled is consistent with whether the stats are actually + * enabled + */ + vmballoon_stats_gen_inc(b, VMW_BALLOON_STAT_TIMER); + if (!vmballoon_send_get_target(b)) change = vmballoon_change(b); @@ -968,12 +1089,15 @@ static void vmballoon_work(struct work_struct *work) vmballoon_deflate(b); } + up_read(&b->conf_sem); + /* * We are using a freezable workqueue so that balloon operations are * stopped while the system transitions to/from sleep/hibernation. */ queue_delayed_work(system_freezable_wq, dwork, round_jiffies_relative(HZ)); + } /* @@ -981,55 +1105,105 @@ static void vmballoon_work(struct work_struct *work) */ #ifdef CONFIG_DEBUG_FS +static const char * const vmballoon_stat_page_names[] = { + [VMW_BALLOON_PAGE_STAT_ALLOC] = "alloc", + [VMW_BALLOON_PAGE_STAT_ALLOC_FAIL] = "allocFail", + [VMW_BALLOON_PAGE_STAT_REFUSED_ALLOC] = "errAlloc", + [VMW_BALLOON_PAGE_STAT_REFUSED_FREE] = "errFree", + [VMW_BALLOON_PAGE_STAT_FREE] = "free" +}; + +static const char * const vmballoon_stat_names[] = { + [VMW_BALLOON_STAT_TIMER] = "timer", + [VMW_BALLOON_STAT_DOORBELL] = "doorbell" +}; + +static const char * const vmballoon_page_size_names[] = { + [VMW_BALLOON_4K_PAGE] = "4k", + [VMW_BALLOON_2M_PAGE] = "2M" +}; + +static int vmballoon_enable_stats(struct vmballoon *b) +{ + int r = 0; + + down_write(&b->conf_sem); + + /* did we somehow race with another reader which enabled stats? */ + if (b->stats) + goto out; + + b->stats = kzalloc(sizeof(*b->stats), GFP_KERNEL); + + if (!b->stats) { + /* allocation failed */ + r = -ENOMEM; + goto out; + } + static_key_enable(&balloon_stat_enabled.key); +out: + up_write(&b->conf_sem); + return r; +} + +/** + * vmballoon_debug_show - shows statistics of balloon operations. + * @f: pointer to the &struct seq_file. + * @offset: ignored. + * + * Provides the statistics that can be accessed in vmmemctl in the debugfs. + * To avoid the overhead - mainly that of memory - of collecting the statistics, + * we only collect statistics after the first time the counters are read. + * + * Return: zero on success or an error code. + */ static int vmballoon_debug_show(struct seq_file *f, void *offset) { struct vmballoon *b = f->private; - struct vmballoon_stats *stats = &b->stats; - int i; + int i, j; + + /* enables stats if they are disabled */ + if (!b->stats) { + int r = vmballoon_enable_stats(b); + + if (r) + return r; + } /* format capabilities info */ - seq_printf(f, - "balloon capabilities: %#4x\n" - "used capabilities: %#4lx\n" - "is resetting: %c\n", - VMW_BALLOON_CAPABILITIES, b->capabilities, - b->reset_required ? 'y' : 'n'); + seq_printf(f, "%-22s: %#4x\n", "balloon capabilities", + VMW_BALLOON_CAPABILITIES); + seq_printf(f, "%-22s: %#4lx\n", "used capabilities", + b->capabilities); + seq_printf(f, "%-22s: %16s\n", "is resetting", + b->reset_required ? "y" : "n"); /* format size info */ - seq_printf(f, - "target: %8d pages\n" - "current: %8d pages\n", - b->target, b->size); + seq_printf(f, "%-22s: %16u\n", "target", b->target); + seq_printf(f, "%-22s: %16u\n", "current", b->size); for (i = 0; i < VMW_BALLOON_CMD_NUM; i++) { if (vmballoon_cmd_names[i] == NULL) continue; - seq_printf(f, "%-22s: %16lu (%lu failed)\n", - vmballoon_cmd_names[i], stats->ops[i], - stats->ops_fail[i]); + seq_printf(f, "%-22s: %16llu (%llu failed)\n", + vmballoon_cmd_names[i], + atomic64_read(&b->stats->ops[i][VMW_BALLOON_OP_STAT]), + atomic64_read(&b->stats->ops[i][VMW_BALLOON_OP_FAIL_STAT])); } - seq_printf(f, - "\n" - "timer: %8u\n" - "doorbell: %8u\n" - "prim2mAlloc: %8u (%4u failed)\n" - "prim4kAlloc: %8u (%4u failed)\n" - "prim2mFree: %8u\n" - "primFree: %8u\n" - "err2mAlloc: %8u\n" - "errAlloc: %8u\n" - "err2mFree: %8u\n" - "errFree: %8u\n", - stats->timer, - stats->doorbell, - stats->alloc[true], stats->alloc_fail[true], - stats->alloc[false], stats->alloc_fail[false], - stats->free[true], - stats->free[false], - stats->refused_alloc[true], stats->refused_alloc[false], - stats->refused_free[true], stats->refused_free[false]); + for (i = 0; i < VMW_BALLOON_STAT_NUM; i++) + seq_printf(f, "%-22s: %16llu\n", + vmballoon_stat_names[i], + atomic64_read(&b->stats->general_stat[i])); + + for (i = 0; i < VMW_BALLOON_PAGE_STAT_NUM; i++) { + for (j = 0; j < VMW_BALLOON_NUM_PAGE_SIZES; j++) + seq_printf(f, "%-18s(%s): %16llu\n", + vmballoon_stat_page_names[i], + vmballoon_page_size_names[j], + atomic64_read(&b->stats->page_stat[i][j])); + } return 0; } @@ -1064,7 +1238,10 @@ static int __init vmballoon_debugfs_init(struct vmballoon *b) static void __exit vmballoon_debugfs_exit(struct vmballoon *b) { + static_key_disable(&balloon_stat_enabled.key); debugfs_remove(b->dbg_entry); + kfree(b->stats); + b->stats = NULL; } #else @@ -1103,6 +1280,7 @@ static int __init vmballoon_init(void) if (error) return error; + init_rwsem(&balloon.conf_sem); balloon.vmci_doorbell = VMCI_INVALID_HANDLE; balloon.batch_page = NULL; balloon.page = NULL; |