/* * Xen hypercall batching. * * Xen allows multiple hypercalls to be issued at once, using the * multicall interface. This allows the cost of trapping into the * hypervisor to be amortized over several calls. * * This file implements a simple interface for multicalls. There's a * per-cpu buffer of outstanding multicalls. When you want to queue a * multicall for issuing, you can allocate a multicall slot for the * call and its arguments, along with storage for space which is * pointed to by the arguments (for passing pointers to structures, * etc). When the multicall is actually issued, all the space for the * commands and allocated memory is freed for reuse. * * Multicalls are flushed whenever any of the buffers get full, or * when explicitly requested. There's no way to get per-multicall * return results back. It will BUG if any of the multicalls fail. * * Jeremy Fitzhardinge , XenSource Inc, 2007 */ #include #include #include #include #include "multicalls.h" #include "debugfs.h" #define MC_BATCH 32 #define MC_DEBUG 1 #define MC_ARGS (MC_BATCH * 16) struct mc_buffer { struct multicall_entry entries[MC_BATCH]; #if MC_DEBUG struct multicall_entry debug[MC_BATCH]; void *caller[MC_BATCH]; #endif unsigned char args[MC_ARGS]; struct callback { void (*fn)(void *); void *data; } callbacks[MC_BATCH]; unsigned mcidx, argidx, cbidx; }; static DEFINE_PER_CPU(struct mc_buffer, mc_buffer); DEFINE_PER_CPU(unsigned long, xen_mc_irq_flags); /* flush reasons 0- slots, 1- args, 2- callbacks */ enum flush_reasons { FL_SLOTS, FL_ARGS, FL_CALLBACKS, FL_N_REASONS }; #ifdef CONFIG_XEN_DEBUG_FS #define NHYPERCALLS 40 /* not really */ static struct { unsigned histo[MC_BATCH+1]; unsigned issued; unsigned arg_total; unsigned hypercalls; unsigned histo_hypercalls[NHYPERCALLS]; unsigned flush[FL_N_REASONS]; } mc_stats; static u8 zero_stats; static inline void check_zero(void) { if (unlikely(zero_stats)) { memset(&mc_stats, 0, sizeof(mc_stats)); zero_stats = 0; } } static void mc_add_stats(const struct mc_buffer *mc) { int i; check_zero(); mc_stats.issued++; mc_stats.hypercalls += mc->mcidx; mc_stats.arg_total += mc->argidx; mc_stats.histo[mc->mcidx]++; for(i = 0; i < mc->mcidx; i++) { unsigned op = mc->entries[i].op; if (op < NHYPERCALLS) mc_stats.histo_hypercalls[op]++; } } static void mc_stats_flush(enum flush_reasons idx) { check_zero(); mc_stats.flush[idx]++; } #else /* !CONFIG_XEN_DEBUG_FS */ static inline void mc_add_stats(const struct mc_buffer *mc) { } static inline void mc_stats_flush(enum flush_reasons idx) { } #endif /* CONFIG_XEN_DEBUG_FS */ void xen_mc_flush(void) { struct mc_buffer *b = &__get_cpu_var(mc_buffer); int ret = 0; unsigned long flags; int i; BUG_ON(preemptible()); /* Disable interrupts in case someone comes in and queues something in the middle */ local_irq_save(flags); mc_add_stats(b); if (b->mcidx) { #if MC_DEBUG memcpy(b->debug, b->entries, b->mcidx * sizeof(struct multicall_entry)); #endif if (HYPERVISOR_multicall(b->entries, b->mcidx) != 0) BUG(); for (i = 0; i < b->mcidx; i++) if (b->entries[i].result < 0) ret++; #if MC_DEBUG if (ret) { printk(KERN_ERR "%d multicall(s) failed: cpu %d\n", ret, smp_processor_id()); dump_stack(); for (i = 0; i < b->mcidx; i++) { printk(KERN_DEBUG " call %2d/%d: op=%lu arg=[%lx] result=%ld\t%pF\n", i+1, b->mcidx, b->debug[i].op, b->debug[i].args[0], b->entries[i].result, b->caller[i]); } } #endif b->mcidx = 0; b->argidx = 0; } else BUG_ON(b->argidx != 0); for (i = 0; i < b->cbidx; i++) { struct callback *cb = &b->callbacks[i]; (*cb->fn)(cb->data); } b->cbidx = 0; local_irq_restore(flags); WARN_ON(ret); } struct multicall_space __xen_mc_entry(size_t args) { struct mc_buffer *b = &__get_cpu_var(mc_buffer); struct multicall_space ret; unsigned argidx = roundup(b->argidx, sizeof(u64)); BUG_ON(preemptible()); BUG_ON(b->argidx > MC_ARGS); if (b->mcidx == MC_BATCH || (argidx + args) > MC_ARGS) { mc_stats_flush(b->mcidx == MC_BATCH ? FL_SLOTS : FL_ARGS); xen_mc_flush(); argidx = roundup(b->argidx, sizeof(u64)); } ret.mc = &b->entries[b->mcidx]; #ifdef MC_DEBUG b->caller[b->mcidx] = __builtin_return_address(0); #endif b->mcidx++; ret.args = &b->args[argidx]; b->argidx = argidx + args; BUG_ON(b->argidx > MC_ARGS); return ret; } struct multicall_space xen_mc_extend_args(unsigned long op, size_t size) { struct mc_buffer *b = &__get_cpu_var(mc_buffer); struct multicall_space ret = { NULL, NULL }; BUG_ON(preemptible()); BUG_ON(b->argidx > MC_ARGS); if (b->mcidx == 0) return ret; if (b->entries[b->mcidx - 1].op != op) return ret; if ((b->argidx + size) > MC_ARGS) return ret; ret.mc = &b->entries[b->mcidx - 1]; ret.args = &b->args[b->argidx]; b->argidx += size; BUG_ON(b->argidx > MC_ARGS); return ret; } void xen_mc_callback(void (*fn)(void *), void *data) { struct mc_buffer *b = &__get_cpu_var(mc_buffer); struct callback *cb; if (b->cbidx == MC_BATCH) { mc_stats_flush(FL_CALLBACKS); xen_mc_flush(); } cb = &b->callbacks[b->cbidx++]; cb->fn = fn; cb->data = data; } #ifdef CONFIG_XEN_DEBUG_FS static struct dentry *d_mc_debug; static int __init xen_mc_debugfs(void) { struct dentry *d_xen = xen_init_debugfs(); if (d_xen == NULL) return -ENOMEM; d_mc_debug = debugfs_create_dir("multicalls", d_xen); debugfs_create_u8("zero_stats", 0644, d_mc_debug, &zero_stats); debugfs_create_u32("batches", 0444, d_mc_debug, &mc_stats.issued); debugfs_create_u32("hypercalls", 0444, d_mc_debug, &mc_stats.hypercalls); debugfs_create_u32("arg_total", 0444, d_mc_debug, &mc_stats.arg_total); xen_debugfs_create_u32_array("batch_histo", 0444, d_mc_debug, mc_stats.histo, MC_BATCH); xen_debugfs_create_u32_array("hypercall_histo", 0444, d_mc_debug, mc_stats.histo_hypercalls, NHYPERCALLS); xen_debugfs_create_u32_array("flush_reasons", 0444, d_mc_debug, mc_stats.flush, FL_N_REASONS); return 0; } fs_initcall(xen_mc_debugfs); #endif /* CONFIG_XEN_DEBUG_FS */