mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
420 lines
10 KiB
420 lines
10 KiB
// SPDX-License-Identifier: GPL-2.0-or-later |
|
/* Paravirtualization interfaces |
|
Copyright (C) 2006 Rusty Russell IBM Corporation |
|
|
|
|
|
2007 - x86_64 support added by Glauber de Oliveira Costa, Red Hat Inc |
|
*/ |
|
|
|
#include <linux/errno.h> |
|
#include <linux/init.h> |
|
#include <linux/export.h> |
|
#include <linux/efi.h> |
|
#include <linux/bcd.h> |
|
#include <linux/highmem.h> |
|
#include <linux/kprobes.h> |
|
#include <linux/pgtable.h> |
|
|
|
#include <asm/bug.h> |
|
#include <asm/paravirt.h> |
|
#include <asm/debugreg.h> |
|
#include <asm/desc.h> |
|
#include <asm/setup.h> |
|
#include <asm/time.h> |
|
#include <asm/pgalloc.h> |
|
#include <asm/irq.h> |
|
#include <asm/delay.h> |
|
#include <asm/fixmap.h> |
|
#include <asm/apic.h> |
|
#include <asm/tlbflush.h> |
|
#include <asm/timer.h> |
|
#include <asm/special_insns.h> |
|
#include <asm/tlb.h> |
|
#include <asm/io_bitmap.h> |
|
|
|
/* |
|
* nop stub, which must not clobber anything *including the stack* to |
|
* avoid confusing the entry prologues. |
|
*/ |
|
extern void _paravirt_nop(void); |
|
asm (".pushsection .entry.text, \"ax\"\n" |
|
".global _paravirt_nop\n" |
|
"_paravirt_nop:\n\t" |
|
"ret\n\t" |
|
".size _paravirt_nop, . - _paravirt_nop\n\t" |
|
".type _paravirt_nop, @function\n\t" |
|
".popsection"); |
|
|
|
void __init default_banner(void) |
|
{ |
|
printk(KERN_INFO "Booting paravirtualized kernel on %s\n", |
|
pv_info.name); |
|
} |
|
|
|
/* Undefined instruction for dealing with missing ops pointers. */ |
|
static const unsigned char ud2a[] = { 0x0f, 0x0b }; |
|
|
|
struct branch { |
|
unsigned char opcode; |
|
u32 delta; |
|
} __attribute__((packed)); |
|
|
|
static unsigned paravirt_patch_call(void *insn_buff, const void *target, |
|
unsigned long addr, unsigned len) |
|
{ |
|
const int call_len = 5; |
|
struct branch *b = insn_buff; |
|
unsigned long delta = (unsigned long)target - (addr+call_len); |
|
|
|
if (len < call_len) { |
|
pr_warn("paravirt: Failed to patch indirect CALL at %ps\n", (void *)addr); |
|
/* Kernel might not be viable if patching fails, bail out: */ |
|
BUG_ON(1); |
|
} |
|
|
|
b->opcode = 0xe8; /* call */ |
|
b->delta = delta; |
|
BUILD_BUG_ON(sizeof(*b) != call_len); |
|
|
|
return call_len; |
|
} |
|
|
|
#ifdef CONFIG_PARAVIRT_XXL |
|
/* identity function, which can be inlined */ |
|
u64 notrace _paravirt_ident_64(u64 x) |
|
{ |
|
return x; |
|
} |
|
|
|
static unsigned paravirt_patch_jmp(void *insn_buff, const void *target, |
|
unsigned long addr, unsigned len) |
|
{ |
|
struct branch *b = insn_buff; |
|
unsigned long delta = (unsigned long)target - (addr+5); |
|
|
|
if (len < 5) { |
|
#ifdef CONFIG_RETPOLINE |
|
WARN_ONCE(1, "Failing to patch indirect JMP in %ps\n", (void *)addr); |
|
#endif |
|
return len; /* call too long for patch site */ |
|
} |
|
|
|
b->opcode = 0xe9; /* jmp */ |
|
b->delta = delta; |
|
|
|
return 5; |
|
} |
|
#endif |
|
|
|
DEFINE_STATIC_KEY_TRUE(virt_spin_lock_key); |
|
|
|
void __init native_pv_lock_init(void) |
|
{ |
|
if (!boot_cpu_has(X86_FEATURE_HYPERVISOR)) |
|
static_branch_disable(&virt_spin_lock_key); |
|
} |
|
|
|
unsigned paravirt_patch_default(u8 type, void *insn_buff, |
|
unsigned long addr, unsigned len) |
|
{ |
|
/* |
|
* Neat trick to map patch type back to the call within the |
|
* corresponding structure. |
|
*/ |
|
void *opfunc = *((void **)&pv_ops + type); |
|
unsigned ret; |
|
|
|
if (opfunc == NULL) |
|
/* If there's no function, patch it with a ud2a (BUG) */ |
|
ret = paravirt_patch_insns(insn_buff, len, ud2a, ud2a+sizeof(ud2a)); |
|
else if (opfunc == _paravirt_nop) |
|
ret = 0; |
|
|
|
#ifdef CONFIG_PARAVIRT_XXL |
|
/* identity functions just return their single argument */ |
|
else if (opfunc == _paravirt_ident_64) |
|
ret = paravirt_patch_ident_64(insn_buff, len); |
|
|
|
else if (type == PARAVIRT_PATCH(cpu.iret)) |
|
/* If operation requires a jmp, then jmp */ |
|
ret = paravirt_patch_jmp(insn_buff, opfunc, addr, len); |
|
#endif |
|
else |
|
/* Otherwise call the function. */ |
|
ret = paravirt_patch_call(insn_buff, opfunc, addr, len); |
|
|
|
return ret; |
|
} |
|
|
|
unsigned paravirt_patch_insns(void *insn_buff, unsigned len, |
|
const char *start, const char *end) |
|
{ |
|
unsigned insn_len = end - start; |
|
|
|
/* Alternative instruction is too large for the patch site and we cannot continue: */ |
|
BUG_ON(insn_len > len || start == NULL); |
|
|
|
memcpy(insn_buff, start, insn_len); |
|
|
|
return insn_len; |
|
} |
|
|
|
struct static_key paravirt_steal_enabled; |
|
struct static_key paravirt_steal_rq_enabled; |
|
|
|
static u64 native_steal_clock(int cpu) |
|
{ |
|
return 0; |
|
} |
|
|
|
/* These are in entry.S */ |
|
extern void native_iret(void); |
|
|
|
static struct resource reserve_ioports = { |
|
.start = 0, |
|
.end = IO_SPACE_LIMIT, |
|
.name = "paravirt-ioport", |
|
.flags = IORESOURCE_IO | IORESOURCE_BUSY, |
|
}; |
|
|
|
/* |
|
* Reserve the whole legacy IO space to prevent any legacy drivers |
|
* from wasting time probing for their hardware. This is a fairly |
|
* brute-force approach to disabling all non-virtual drivers. |
|
* |
|
* Note that this must be called very early to have any effect. |
|
*/ |
|
int paravirt_disable_iospace(void) |
|
{ |
|
return request_resource(&ioport_resource, &reserve_ioports); |
|
} |
|
|
|
static DEFINE_PER_CPU(enum paravirt_lazy_mode, paravirt_lazy_mode) = PARAVIRT_LAZY_NONE; |
|
|
|
static inline void enter_lazy(enum paravirt_lazy_mode mode) |
|
{ |
|
BUG_ON(this_cpu_read(paravirt_lazy_mode) != PARAVIRT_LAZY_NONE); |
|
|
|
this_cpu_write(paravirt_lazy_mode, mode); |
|
} |
|
|
|
static void leave_lazy(enum paravirt_lazy_mode mode) |
|
{ |
|
BUG_ON(this_cpu_read(paravirt_lazy_mode) != mode); |
|
|
|
this_cpu_write(paravirt_lazy_mode, PARAVIRT_LAZY_NONE); |
|
} |
|
|
|
void paravirt_enter_lazy_mmu(void) |
|
{ |
|
enter_lazy(PARAVIRT_LAZY_MMU); |
|
} |
|
|
|
void paravirt_leave_lazy_mmu(void) |
|
{ |
|
leave_lazy(PARAVIRT_LAZY_MMU); |
|
} |
|
|
|
void paravirt_flush_lazy_mmu(void) |
|
{ |
|
preempt_disable(); |
|
|
|
if (paravirt_get_lazy_mode() == PARAVIRT_LAZY_MMU) { |
|
arch_leave_lazy_mmu_mode(); |
|
arch_enter_lazy_mmu_mode(); |
|
} |
|
|
|
preempt_enable(); |
|
} |
|
|
|
#ifdef CONFIG_PARAVIRT_XXL |
|
void paravirt_start_context_switch(struct task_struct *prev) |
|
{ |
|
BUG_ON(preemptible()); |
|
|
|
if (this_cpu_read(paravirt_lazy_mode) == PARAVIRT_LAZY_MMU) { |
|
arch_leave_lazy_mmu_mode(); |
|
set_ti_thread_flag(task_thread_info(prev), TIF_LAZY_MMU_UPDATES); |
|
} |
|
enter_lazy(PARAVIRT_LAZY_CPU); |
|
} |
|
|
|
void paravirt_end_context_switch(struct task_struct *next) |
|
{ |
|
BUG_ON(preemptible()); |
|
|
|
leave_lazy(PARAVIRT_LAZY_CPU); |
|
|
|
if (test_and_clear_ti_thread_flag(task_thread_info(next), TIF_LAZY_MMU_UPDATES)) |
|
arch_enter_lazy_mmu_mode(); |
|
} |
|
#endif |
|
|
|
enum paravirt_lazy_mode paravirt_get_lazy_mode(void) |
|
{ |
|
if (in_interrupt()) |
|
return PARAVIRT_LAZY_NONE; |
|
|
|
return this_cpu_read(paravirt_lazy_mode); |
|
} |
|
|
|
struct pv_info pv_info = { |
|
.name = "bare hardware", |
|
#ifdef CONFIG_PARAVIRT_XXL |
|
.extra_user_64bit_cs = __USER_CS, |
|
#endif |
|
}; |
|
|
|
/* 64-bit pagetable entries */ |
|
#define PTE_IDENT __PV_IS_CALLEE_SAVE(_paravirt_ident_64) |
|
|
|
struct paravirt_patch_template pv_ops = { |
|
/* Init ops. */ |
|
.init.patch = native_patch, |
|
|
|
/* Time ops. */ |
|
.time.sched_clock = native_sched_clock, |
|
.time.steal_clock = native_steal_clock, |
|
|
|
/* Cpu ops. */ |
|
.cpu.io_delay = native_io_delay, |
|
|
|
#ifdef CONFIG_PARAVIRT_XXL |
|
.cpu.cpuid = native_cpuid, |
|
.cpu.get_debugreg = native_get_debugreg, |
|
.cpu.set_debugreg = native_set_debugreg, |
|
.cpu.read_cr0 = native_read_cr0, |
|
.cpu.write_cr0 = native_write_cr0, |
|
.cpu.write_cr4 = native_write_cr4, |
|
.cpu.wbinvd = native_wbinvd, |
|
.cpu.read_msr = native_read_msr, |
|
.cpu.write_msr = native_write_msr, |
|
.cpu.read_msr_safe = native_read_msr_safe, |
|
.cpu.write_msr_safe = native_write_msr_safe, |
|
.cpu.read_pmc = native_read_pmc, |
|
.cpu.load_tr_desc = native_load_tr_desc, |
|
.cpu.set_ldt = native_set_ldt, |
|
.cpu.load_gdt = native_load_gdt, |
|
.cpu.load_idt = native_load_idt, |
|
.cpu.store_tr = native_store_tr, |
|
.cpu.load_tls = native_load_tls, |
|
.cpu.load_gs_index = native_load_gs_index, |
|
.cpu.write_ldt_entry = native_write_ldt_entry, |
|
.cpu.write_gdt_entry = native_write_gdt_entry, |
|
.cpu.write_idt_entry = native_write_idt_entry, |
|
|
|
.cpu.alloc_ldt = paravirt_nop, |
|
.cpu.free_ldt = paravirt_nop, |
|
|
|
.cpu.load_sp0 = native_load_sp0, |
|
|
|
.cpu.iret = native_iret, |
|
|
|
#ifdef CONFIG_X86_IOPL_IOPERM |
|
.cpu.invalidate_io_bitmap = native_tss_invalidate_io_bitmap, |
|
.cpu.update_io_bitmap = native_tss_update_io_bitmap, |
|
#endif |
|
|
|
.cpu.start_context_switch = paravirt_nop, |
|
.cpu.end_context_switch = paravirt_nop, |
|
|
|
/* Irq ops. */ |
|
.irq.save_fl = __PV_IS_CALLEE_SAVE(native_save_fl), |
|
.irq.irq_disable = __PV_IS_CALLEE_SAVE(native_irq_disable), |
|
.irq.irq_enable = __PV_IS_CALLEE_SAVE(native_irq_enable), |
|
.irq.safe_halt = native_safe_halt, |
|
.irq.halt = native_halt, |
|
#endif /* CONFIG_PARAVIRT_XXL */ |
|
|
|
/* Mmu ops. */ |
|
.mmu.flush_tlb_user = native_flush_tlb_local, |
|
.mmu.flush_tlb_kernel = native_flush_tlb_global, |
|
.mmu.flush_tlb_one_user = native_flush_tlb_one_user, |
|
.mmu.flush_tlb_others = native_flush_tlb_others, |
|
.mmu.tlb_remove_table = |
|
(void (*)(struct mmu_gather *, void *))tlb_remove_page, |
|
|
|
.mmu.exit_mmap = paravirt_nop, |
|
|
|
#ifdef CONFIG_PARAVIRT_XXL |
|
.mmu.read_cr2 = __PV_IS_CALLEE_SAVE(native_read_cr2), |
|
.mmu.write_cr2 = native_write_cr2, |
|
.mmu.read_cr3 = __native_read_cr3, |
|
.mmu.write_cr3 = native_write_cr3, |
|
|
|
.mmu.pgd_alloc = __paravirt_pgd_alloc, |
|
.mmu.pgd_free = paravirt_nop, |
|
|
|
.mmu.alloc_pte = paravirt_nop, |
|
.mmu.alloc_pmd = paravirt_nop, |
|
.mmu.alloc_pud = paravirt_nop, |
|
.mmu.alloc_p4d = paravirt_nop, |
|
.mmu.release_pte = paravirt_nop, |
|
.mmu.release_pmd = paravirt_nop, |
|
.mmu.release_pud = paravirt_nop, |
|
.mmu.release_p4d = paravirt_nop, |
|
|
|
.mmu.set_pte = native_set_pte, |
|
.mmu.set_pmd = native_set_pmd, |
|
|
|
.mmu.ptep_modify_prot_start = __ptep_modify_prot_start, |
|
.mmu.ptep_modify_prot_commit = __ptep_modify_prot_commit, |
|
|
|
.mmu.set_pud = native_set_pud, |
|
|
|
.mmu.pmd_val = PTE_IDENT, |
|
.mmu.make_pmd = PTE_IDENT, |
|
|
|
.mmu.pud_val = PTE_IDENT, |
|
.mmu.make_pud = PTE_IDENT, |
|
|
|
.mmu.set_p4d = native_set_p4d, |
|
|
|
#if CONFIG_PGTABLE_LEVELS >= 5 |
|
.mmu.p4d_val = PTE_IDENT, |
|
.mmu.make_p4d = PTE_IDENT, |
|
|
|
.mmu.set_pgd = native_set_pgd, |
|
#endif /* CONFIG_PGTABLE_LEVELS >= 5 */ |
|
|
|
.mmu.pte_val = PTE_IDENT, |
|
.mmu.pgd_val = PTE_IDENT, |
|
|
|
.mmu.make_pte = PTE_IDENT, |
|
.mmu.make_pgd = PTE_IDENT, |
|
|
|
.mmu.dup_mmap = paravirt_nop, |
|
.mmu.activate_mm = paravirt_nop, |
|
|
|
.mmu.lazy_mode = { |
|
.enter = paravirt_nop, |
|
.leave = paravirt_nop, |
|
.flush = paravirt_nop, |
|
}, |
|
|
|
.mmu.set_fixmap = native_set_fixmap, |
|
#endif /* CONFIG_PARAVIRT_XXL */ |
|
|
|
#if defined(CONFIG_PARAVIRT_SPINLOCKS) |
|
/* Lock ops. */ |
|
#ifdef CONFIG_SMP |
|
.lock.queued_spin_lock_slowpath = native_queued_spin_lock_slowpath, |
|
.lock.queued_spin_unlock = |
|
PV_CALLEE_SAVE(__native_queued_spin_unlock), |
|
.lock.wait = paravirt_nop, |
|
.lock.kick = paravirt_nop, |
|
.lock.vcpu_is_preempted = |
|
PV_CALLEE_SAVE(__native_vcpu_is_preempted), |
|
#endif /* SMP */ |
|
#endif |
|
}; |
|
|
|
#ifdef CONFIG_PARAVIRT_XXL |
|
/* At this point, native_get/set_debugreg has real function entries */ |
|
NOKPROBE_SYMBOL(native_get_debugreg); |
|
NOKPROBE_SYMBOL(native_set_debugreg); |
|
NOKPROBE_SYMBOL(native_load_idt); |
|
#endif |
|
|
|
EXPORT_SYMBOL(pv_ops); |
|
EXPORT_SYMBOL_GPL(pv_info);
|
|
|