From: Rusty Russell <rusty@rustcorp.com.au>
To: Andrew Morton <akpm@osdl.org>
Cc: Andi Kleen <ak@muc.de>, Chris Wright <chrisw@sous-sol.org>,
virtualization <virtualization@lists.osdl.org>,
Linux Kernel Mailing List <linux-kernel@vger.kernel.org>
Subject: [PATCH 4/4] x86 paravirt_ops: binary patching infrastructure
Date: Mon, 07 Aug 2006 14:48:33 +1000 [thread overview]
Message-ID: <1154926114.21647.38.camel@localhost.localdomain> (raw)
In-Reply-To: <1154926048.21647.35.camel@localhost.localdomain>
It turns out that the most called ops, by several orders of magnitude,
are the interrupt manipulation ops. These are obvious candidates for
patching, so mark them up and create infrastructure for it.
The method used is that the ops structure has a patch function, which
is called for each place which needs to be patched: this returns a
number of instructions (the rest are NOP-padded).
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Signed-off-by: Jeremy Fitzhardinge <jeremy@xensource.com>
===================================================================
--- a/arch/i386/kernel/alternative.c
+++ b/arch/i386/kernel/alternative.c
@@ -3,6 +3,7 @@
#include <linux/list.h>
#include <asm/alternative.h>
#include <asm/sections.h>
+#include <asm/paravirt.h>
static int no_replacement = 0;
static int smp_alt_once = 0;
@@ -342,6 +343,38 @@ void alternatives_smp_switch(int smp)
#endif
+#ifdef CONFIG_PARAVIRT
+void apply_paravirt(struct paravirt_patch *start, struct paravirt_patch *end)
+{
+ unsigned char **noptable = find_nop_table();
+ struct paravirt_patch *p;
+ int diff, i, k;
+
+ local_irq_disable();
+ for (p = start; p < end; p++) {
+ unsigned int used;
+ used = paravirt_ops.patch(p->instrtype, p->instr, p->len);
+ /* Pad the rest with nops */
+ diff = p->len - used;
+ for (i = used; diff > 0; diff -= k, i += k) {
+ k = diff;
+ if (k > ASM_NOP_MAX)
+ k = ASM_NOP_MAX;
+ memcpy(p->instr + i, noptable[k], k);
+ }
+ }
+ sync_core();
+ local_irq_enable();
+}
+extern struct paravirt_patch __start_parainstructions[],
+ __stop_parainstructions[];
+#else
+void apply_paravirt(struct paravirt_patch *start, struct paravirt_patch *end)
+{
+}
+extern char __start_parainstructions[], __stop_parainstructions[];
+#endif /* CONFIG_PARAVIRT */
+
void __init alternative_instructions(void)
{
if (no_replacement) {
@@ -386,4 +419,6 @@ void __init alternative_instructions(voi
alternatives_smp_switch(0);
}
#endif
-}
+
+ apply_paravirt(__start_parainstructions, __stop_parainstructions);
+}
===================================================================
--- a/arch/i386/kernel/module.c
+++ b/arch/i386/kernel/module.c
@@ -108,7 +108,8 @@ int module_finalize(const Elf_Ehdr *hdr,
const Elf_Shdr *sechdrs,
struct module *me)
{
- const Elf_Shdr *s, *text = NULL, *alt = NULL, *locks = NULL;
+ const Elf_Shdr *s, *text = NULL, *alt = NULL, *locks = NULL,
+ *para = NULL;
char *secstrings = (void *)hdr + sechdrs[hdr->e_shstrndx].sh_offset;
for (s = sechdrs; s < sechdrs + hdr->e_shnum; s++) {
@@ -118,6 +119,8 @@ int module_finalize(const Elf_Ehdr *hdr,
alt = s;
if (!strcmp(".smp_locks", secstrings + s->sh_name))
locks= s;
+ if (!strcmp(".parainstructions", secstrings + s->sh_name))
+ para = s;
}
if (alt) {
@@ -132,6 +135,10 @@ int module_finalize(const Elf_Ehdr *hdr,
lseg, lseg + locks->sh_size,
tseg, tseg + text->sh_size);
}
+ if (para) {
+ void *aseg = (void *)alt->sh_addr;
+ apply_paravirt(aseg, aseg + alt->sh_size);
+ }
return 0;
}
===================================================================
--- a/arch/i386/kernel/paravirt.c
+++ b/arch/i386/kernel/paravirt.c
@@ -335,8 +335,53 @@ extern fastcall void nopara_iret(void);
extern fastcall void nopara_iret(void);
extern fastcall void nopara_irq_enable_sysexit(void);
+#define DEF_PARAINST(name, code) \
+ extern const char para_##name; \
+ extern const char para_##name##_end; \
+ asm("para_" #name ": " code ";" \
+ "para_"#name"_end:")
+
+DEF_PARAINST(irq_disable, "cli");
+DEF_PARAINST(irq_enable, "sti");
+DEF_PARAINST(restore_flags, "push %eax; popf");
+DEF_PARAINST(save_flags, "pushf; pop %eax");
+DEF_PARAINST(save_flags_irq_disable, "pushf; pop %eax; cli");
+
+/* Simple instruction patching code. */
+static const struct native_insns
+{
+ const char *start, *end;
+} native_insns[] = {
+ [PARAVIRT_IRQ_DISABLE] = { ¶_irq_disable, ¶_irq_disable_end },
+ [PARAVIRT_IRQ_ENABLE] = { ¶_irq_enable, ¶_irq_enable_end },
+ [PARAVIRT_RESTORE_FLAGS] = { ¶_restore_flags, ¶_restore_flags_end },
+ [PARAVIRT_SAVE_FLAGS] = { ¶_save_flags, ¶_save_flags_end },
+ [PARAVIRT_SAVE_FLAGS_IRQ_DISABLE] = { ¶_save_flags_irq_disable,
+ ¶_save_flags_irq_disable_end },
+};
+
+static unsigned nopara_patch(unsigned int type, void *firstinsn, unsigned len)
+{
+ unsigned int insn_len;
+
+ /* Don't touch it if we don't have a replacement */
+ if (type >= ARRAY_SIZE(native_insns) || !native_insns[type].start)
+ return len;
+
+ insn_len = native_insns[type].end - native_insns[type].start;
+
+ /* Similarly if we can't fit replacement. */
+ if (len < insn_len)
+ return len;
+
+ memcpy(firstinsn, native_insns[type].start, insn_len);
+ return insn_len;
+}
+
+
struct paravirt_ops paravirt_ops = {
.kernel_rpl = 0,
+ .patch = nopara_patch,
.cpuid = nopara_cpuid,
.get_debugreg = nopara_get_debugreg,
.set_debugreg = nopara_set_debugreg,
===================================================================
--- a/arch/i386/kernel/vmlinux.lds.S
+++ b/arch/i386/kernel/vmlinux.lds.S
@@ -150,6 +150,12 @@ SECTIONS
.altinstr_replacement : AT(ADDR(.altinstr_replacement) - LOAD_OFFSET) {
*(.altinstr_replacement)
}
+ . = ALIGN(4);
+ __start_parainstructions = .;
+ .parainstructions : AT(ADDR(.parainstructions) - LOAD_OFFSET) {
+ *(.parainstructions)
+ }
+ __stop_parainstructions = .;
/* .exit.text is discard at runtime, not link time, to deal with references
from .altinstructions and .eh_frame */
.exit.text : AT(ADDR(.exit.text) - LOAD_OFFSET) { *(.exit.text) }
===================================================================
--- a/include/asm-i386/alternative.h
+++ b/include/asm-i386/alternative.h
@@ -138,4 +138,7 @@ static inline void alternatives_smp_swit
#define LOCK_PREFIX ""
#endif
+struct paravirt_patch;
+void apply_paravirt(struct paravirt_patch *start, struct paravirt_patch *end);
+
#endif /* _I386_ALTERNATIVE_H */
===================================================================
--- a/include/asm-i386/paravirt.h
+++ b/include/asm-i386/paravirt.h
@@ -3,10 +3,18 @@
/* Various instructions on x86 need to be replaced for
* para-virtualization: those hooks are defined here. */
#include <linux/linkage.h>
+#include <linux/stringify.h>
#ifndef CONFIG_PARAVIRT
#include <asm/no_paravirt.h>
#else
+
+/* These are the most common ops, so we want to be able to patch callers. */
+#define PARAVIRT_IRQ_DISABLE 0
+#define PARAVIRT_IRQ_ENABLE 1
+#define PARAVIRT_RESTORE_FLAGS 2
+#define PARAVIRT_SAVE_FLAGS 3
+#define PARAVIRT_SAVE_FLAGS_IRQ_DISABLE 4
#ifndef __ASSEMBLY__
struct thread_struct;
@@ -14,6 +22,8 @@ struct paravirt_ops
struct paravirt_ops
{
unsigned int kernel_rpl;
+
+ unsigned (*patch)(unsigned int type, void *firstinsn, unsigned len);
/* All the function pointers here are declared as "fastcall"
so that we get a specific register-based calling
@@ -113,31 +123,6 @@ static inline void sync_core(void)
#define read_cr4() paravirt_ops.read_cr4()
#define read_cr4_safe(x) paravirt_ops.read_cr4_safe()
#define write_cr4(x) paravirt_ops.write_cr4(x)
-
-static inline unsigned long __raw_local_save_flags(void)
-{
- return paravirt_ops.save_fl();
-}
-
-static inline void raw_local_irq_restore(unsigned long flags)
-{
- return paravirt_ops.restore_fl(flags);
-}
-
-static inline void raw_local_irq_disable(void)
-{
- paravirt_ops.irq_disable();
-}
-
-static inline void raw_local_irq_enable(void)
-{
- paravirt_ops.irq_enable();
-}
-
-static inline unsigned long __raw_local_irq_save(void)
-{
- return paravirt_ops.save_fl_irq_disable();
-}
static inline void raw_safe_halt(void)
{
@@ -217,13 +202,89 @@ static inline void halt(void)
#define write_idt_entry(dt, entry, a, b) (paravirt_ops.write_idt_entry((dt), (entry), ((u64)a) << 32 | b))
#define set_iopl_mask(mask) (paravirt_ops.set_iopl_mask(mask))
-#define CLI_STRING "pushl %eax; pushl %ecx; pushl %edx; call *paravirt_ops+PARAVIRT_irq_disable; popl %edx; popl %ecx; popl %eax"
-#define STI_STRING "pushl %eax; pushl %ecx; pushl %edx; call *paravirt_ops+PARAVIRT_irq_enable; popl %edx; popl %ecx; popl %eax"
+/* These all sit in the .parainstructions section to tell us what to patch. */
+struct paravirt_patch {
+ u8 *instr; /* original instructions */
+ u8 instrtype; /* type of this instruction */
+ u8 len; /* length of original instruction */
+ u16 pad;
+};
+
+#define paravirt_alt(insn_string, typenum) \
+ "771:\n\t" insn_string "\n" "772:\n" \
+ ".pushsection .parainstructions,\"a\"\n"\
+ " .long 771b\n" \
+ " .byte " __stringify(typenum) "\n" \
+ " .byte 772b-771b\n" \
+ " .byte 0,0\n" \
+ ".popsection"
+
+static inline unsigned long __raw_local_save_flags(void)
+{
+ unsigned long f;
+
+ __asm__ __volatile__(paravirt_alt("call *%1",
+ PARAVIRT_SAVE_FLAGS)
+ : "=a"(f): "m"(paravirt_ops.save_fl)
+ : "memory", "ecx", "edx");
+ return f;
+}
+
+static inline void raw_local_irq_restore(unsigned long f)
+{
+ __asm__ __volatile__(paravirt_alt("call *%0",
+ PARAVIRT_RESTORE_FLAGS)
+ : : "m" (paravirt_ops.restore_fl), "a"(f)
+ : "memory", "ecx", "edx");
+}
+
+static inline unsigned long __raw_local_irq_save(void)
+{
+ unsigned long f;
+
+ __asm__ __volatile__(paravirt_alt("call *%1",
+ PARAVIRT_SAVE_FLAGS_IRQ_DISABLE)
+ : "=a"(f): "m"(paravirt_ops.save_fl_irq_disable)
+ : "memory", "ecx", "edx");
+ return f;
+}
+
+static inline void raw_local_irq_disable(void)
+{
+ __asm__ __volatile__(paravirt_alt("call *%0",
+ PARAVIRT_IRQ_DISABLE)
+ : : "m" (paravirt_ops.irq_disable)
+ : "memory", "eax", "ecx", "edx");
+}
+
+static inline void raw_local_irq_enable(void)
+{
+ __asm__ __volatile__(paravirt_alt("call *%0",
+ PARAVIRT_IRQ_ENABLE)
+ : : "m" (paravirt_ops.irq_enable)
+ : "memory", "eax", "ecx", "edx");
+}
+
+/* XXX TODO?: work out some way to mark eax, ecx & edx as clobbered rather than having explicit push/pops */
+#define CLI_STRING paravirt_alt("pushl %eax; pushl %ecx; pushl %edx; call *paravirt_ops+PARAVIRT_irq_disable; popl %edx; popl %ecx; popl %eax", PARAVIRT_IRQ_DISABLE)
+#define STI_STRING paravirt_alt("pushl %eax; pushl %ecx; pushl %edx; call *paravirt_ops+PARAVIRT_irq_enable; popl %edx; popl %ecx; popl %eax", PARAVIRT_IRQ_ENABLE)
+
#else /* __ASSEMBLY__ */
+#define PARA_PATCH(ptype, ops) \
+771:; \
+ ops; \
+772:; \
+ .pushsection .parainstructions,"a"; \
+ .long 771b; \
+ .byte ptype; \
+ .byte 772b-771b; \
+ .byte 0,0; \
+ .popsection
+
#define INTERRUPT_RETURN jmp *paravirt_ops+PARAVIRT_iret
-#define DISABLE_INTERRUPTS call *paravirt_ops+PARAVIRT_irq_disable
-#define ENABLE_INTERRUPTS pushl %eax; pushl %ecx; pushl %edx; call *paravirt_ops+PARAVIRT_irq_enable; popl %edx; popl %ecx; popl %eax
+#define DISABLE_INTERRUPTS PARA_PATCH(PARAVIRT_IRQ_DISABLE,call *paravirt_ops+PARAVIRT_irq_disable)
+#define ENABLE_INTERRUPTS PARA_PATCH(PARAVIRT_IRQ_ENABLE,pushl %eax; pushl %ecx; pushl %edx; call *paravirt_ops+PARAVIRT_irq_enable; popl %edx; popl %ecx; popl %eax)
#define ENABLE_INTERRUPTS_SYSEXIT jmp *paravirt_ops+PARAVIRT_irq_enable_sysexit
#define GET_CR0_INTO_EAX call *paravirt_ops+PARAVIRT_read_cr0
#endif /* __ASSEMBLY__ */
--
Help! Save Australia from the worst of the DMCA: http://linux.org.au/law
next prev parent reply other threads:[~2006-08-07 4:48 UTC|newest]
Thread overview: 31+ messages / expand[flat|nested] mbox.gz Atom feed top
2006-08-07 4:43 [PATCH 1/4] x86 paravirt_ops: create no_paravirt.h for native ops Rusty Russell
2006-08-07 4:45 ` [PATCH 2/4] x86 paravirt_ops: paravirt_desc.h for native descriptor ops Rusty Russell
2006-08-07 4:47 ` [PATCH 3/4] x86 paravirt_ops: implementation of paravirt_ops Rusty Russell
2006-08-07 4:48 ` Rusty Russell [this message]
2006-08-07 5:14 ` [PATCH 4/4] x86 paravirt_ops: binary patching infrastructure Rusty Russell
2006-08-07 5:38 ` Andi Kleen
2006-08-07 5:56 ` Jeremy Fitzhardinge
2006-08-07 5:39 ` [PATCH 3/4] x86 paravirt_ops: implementation of paravirt_ops Andi Kleen
2006-08-07 5:56 ` Jeremy Fitzhardinge
2006-08-07 6:13 ` Rusty Russell
2006-08-07 6:20 ` Andi Kleen
2006-08-07 7:27 ` Rusty Russell
2006-08-07 5:40 ` [PATCH 2/4] x86 paravirt_ops: paravirt_desc.h for native descriptor ops Andi Kleen
2006-08-07 7:50 ` Rusty Russell
2006-08-07 8:53 ` Andi Kleen
2006-08-07 17:19 ` Dave Jones
2006-08-07 5:22 ` [PATCH 1/4] x86 paravirt_ops: create no_paravirt.h for native ops Jeremy Fitzhardinge
2006-08-07 5:30 ` Andi Kleen
2006-08-07 5:43 ` Jeremy Fitzhardinge
2006-08-07 6:02 ` Andi Kleen
2006-08-07 6:23 ` Jeremy Fitzhardinge
2006-08-07 6:03 ` Rusty Russell
2006-08-07 6:16 ` Andi Kleen
2006-08-07 6:04 ` Rusty Russell
2006-08-07 6:17 ` Andi Kleen
2006-08-07 6:27 ` Muli Ben-Yehuda
2006-08-07 7:34 ` Jan Engelhardt
2006-08-07 8:40 ` Muli Ben-Yehuda
2006-08-07 17:54 ` Jan Engelhardt
2006-08-07 20:51 ` Zachary Amsden
2006-08-08 1:59 ` Andi Kleen
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1154926114.21647.38.camel@localhost.localdomain \
--to=rusty@rustcorp.com.au \
--cc=ak@muc.de \
--cc=akpm@osdl.org \
--cc=chrisw@sous-sol.org \
--cc=linux-kernel@vger.kernel.org \
--cc=virtualization@lists.osdl.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).