# HG changeset patch
# User kaf24@xxxxxxxxxxxxxxxxxxxx
# Node ID f6507937cb7c95fbcf662b1c23e742ce9c076753
# Parent dc3c59367403959b0ae135966c1acfe9678a8f0d
Fix x86/64 version of Mini-OS. It encompasses the following:
a) 64-bit switch_to scheduler macro (by Aravindh Puthiyaparambil)
b) implements 64-bit hypervisor_callback
c) fixes thread creation issues (thread_starter used to perform
initialisation)
Signed-off-by: Grzegorz Milos <gm281@xxxxxxxxx>
diff -r dc3c59367403 -r f6507937cb7c extras/mini-os/console/console.c
--- a/extras/mini-os/console/console.c Mon May 01 17:44:51 2006 +0100
+++ b/extras/mini-os/console/console.c Tue May 02 09:12:39 2006 +0100
@@ -128,7 +128,7 @@ void printk(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
- print(0, fmt, args);
+ print(1, fmt, args);
va_end(args);
}
diff -r dc3c59367403 -r f6507937cb7c extras/mini-os/events.c
--- a/extras/mini-os/events.c Mon May 01 17:44:51 2006 +0100
+++ b/extras/mini-os/events.c Tue May 02 09:12:39 2006 +0100
@@ -106,6 +106,17 @@ void unbind_virq( u32 port )
unbind_evtchn(port);
}
+#if defined(__x86_64__)
+/* Allocate 4 pages for the irqstack */
+#define STACK_PAGES 4
+char irqstack[1024 * 4 * STACK_PAGES];
+
+static struct pda
+{
+ int irqcount; /* offset 0 (used in x86_64.S) */
+ char *irqstackptr; /* 8 */
+} cpu0_pda;
+#endif
/*
* Initially all events are without a handler and disabled
@@ -113,7 +124,12 @@ void init_events(void)
void init_events(void)
{
int i;
-
+#if defined(__x86_64__)
+ asm volatile("movl %0,%%fs ; movl %0,%%gs" :: "r" (0));
+ wrmsrl(0xc0000101, &cpu0_pda); /* 0xc0000101 is MSR_GS_BASE */
+ cpu0_pda.irqcount = -1;
+ cpu0_pda.irqstackptr = irqstack + 1024 * 4 * STACK_PAGES;
+#endif
/* inintialise event handler */
for ( i = 0; i < NR_EVS; i++ )
{
diff -r dc3c59367403 -r f6507937cb7c extras/mini-os/hypervisor.c
--- a/extras/mini-os/hypervisor.c Mon May 01 17:44:51 2006 +0100
+++ b/extras/mini-os/hypervisor.c Tue May 02 09:12:39 2006 +0100
@@ -41,8 +41,8 @@ void do_hypervisor_callback(struct pt_re
shared_info_t *s = HYPERVISOR_shared_info;
vcpu_info_t *vcpu_info = &s->vcpu_info[cpu];
+
vcpu_info->evtchn_upcall_pending = 0;
-
/* NB. No need for a barrier here -- XCHG is a barrier on x86. */
l1 = xchg(&vcpu_info->evtchn_pending_sel, 0);
while ( l1 != 0 )
diff -r dc3c59367403 -r f6507937cb7c extras/mini-os/include/mm.h
--- a/extras/mini-os/include/mm.h Mon May 01 17:44:51 2006 +0100
+++ b/extras/mini-os/include/mm.h Tue May 02 09:12:39 2006 +0100
@@ -148,7 +148,7 @@ static __inline__ unsigned long machine_
}
#if defined(__x86_64__)
-#define VIRT_START 0xFFFFFFFF00000000UL
+#define VIRT_START 0xFFFFFFFF80000000UL
#elif defined(__i386__)
#define VIRT_START 0xC0000000UL
#endif
diff -r dc3c59367403 -r f6507937cb7c extras/mini-os/include/os.h
--- a/extras/mini-os/include/os.h Mon May 01 17:44:51 2006 +0100
+++ b/extras/mini-os/include/os.h Tue May 02 09:12:39 2006 +0100
@@ -434,6 +434,13 @@ static __inline__ unsigned long __ffs(un
(val) = ((unsigned long)__a) | (((unsigned long)__d)<<32); \
} while(0)
+#define wrmsr(msr,val1,val2) \
+ __asm__ __volatile__("wrmsr" \
+ : /* no outputs */ \
+ : "c" (msr), "a" (val1), "d" (val2))
+
+#define wrmsrl(msr,val) wrmsr(msr,(u32)((u64)(val)),((u64)(val))>>32)
+
#else /* ifdef __x86_64__ */
#error "Unsupported architecture"
diff -r dc3c59367403 -r f6507937cb7c extras/mini-os/include/sched.h
--- a/extras/mini-os/include/sched.h Mon May 01 17:44:51 2006 +0100
+++ b/extras/mini-os/include/sched.h Tue May 02 09:12:39 2006 +0100
@@ -7,8 +7,8 @@ struct thread
{
char *name;
char *stack;
- unsigned long eps;
- unsigned long eip;
+ unsigned long sp; /* Stack pointer */
+ unsigned long ip; /* Instruction pointer */
struct list_head thread_list;
u32 flags;
};
@@ -25,7 +25,9 @@ static inline struct thread* get_current
struct thread **current;
#ifdef __i386__
__asm__("andl %%esp,%0; ":"=r" (current) : "r" (~8191UL));
-#endif
+#else
+ __asm__("andq %%rsp,%0; ":"=r" (current) : "r" (~8191UL));
+#endif
return *current;
}
diff -r dc3c59367403 -r f6507937cb7c extras/mini-os/kernel.c
--- a/extras/mini-os/kernel.c Mon May 01 17:44:51 2006 +0100
+++ b/extras/mini-os/kernel.c Tue May 02 09:12:39 2006 +0100
@@ -35,6 +35,8 @@
#include <lib.h>
#include <sched.h>
#include <xenbus.h>
+#include <xen/features.h>
+#include <xen/version.h>
/*
* Shared page for communicating with the hypervisor.
@@ -84,6 +86,26 @@ static void init_xs(void *ign)
test_xenbus();
}
+
+
+u8 xen_features[XENFEAT_NR_SUBMAPS * 32];
+
+void setup_xen_features(void)
+{
+ xen_feature_info_t fi;
+ int i, j;
+
+ for (i = 0; i < XENFEAT_NR_SUBMAPS; i++)
+ {
+ fi.submap_idx = i;
+ if (HYPERVISOR_xen_version(XENVER_get_features, &fi) < 0)
+ break;
+
+ for (j=0; j<32; j++)
+ xen_features[i*32+j] = !!(fi.submap & 1<<j);
+ }
+}
+
/*
* INITIAL C ENTRY POINT.
@@ -127,7 +149,9 @@ void start_kernel(start_info_t *si)
printk(" flags: 0x%x\n", (unsigned int)si->flags);
printk(" cmd_line: %s\n",
si->cmd_line ? (const char *)si->cmd_line : "NULL");
+ printk(" stack: %p-%p\n", stack, stack + 8192);
+ setup_xen_features();
/* Init memory management. */
init_mm();
@@ -146,7 +170,7 @@ void start_kernel(start_info_t *si)
/* Init XenBus from a separate thread */
create_thread("init_xs", init_xs, NULL);
-
+
/* Everything initialised, start idle thread */
run_idle_thread();
}
diff -r dc3c59367403 -r f6507937cb7c extras/mini-os/minios-x86_64.lds
--- a/extras/mini-os/minios-x86_64.lds Mon May 01 17:44:51 2006 +0100
+++ b/extras/mini-os/minios-x86_64.lds Tue May 02 09:12:39 2006 +0100
@@ -3,7 +3,7 @@ ENTRY(_start)
ENTRY(_start)
SECTIONS
{
- . = 0xFFFFFFFF00000000;
+ . = 0xFFFFFFFF80000000;
_text = .; /* Text and read-only data */
.text : {
*(.text)
diff -r dc3c59367403 -r f6507937cb7c extras/mini-os/sched.c
--- a/extras/mini-os/sched.c Mon May 01 17:44:51 2006 +0100
+++ b/extras/mini-os/sched.c Tue May 02 09:12:39 2006 +0100
@@ -69,17 +69,27 @@ void idle_thread_fn(void *unused);
void dump_stack(struct thread *thread)
{
- unsigned long *bottom = (unsigned long *)thread->stack + 2048;
- unsigned long *pointer = (unsigned long *)thread->eps;
+ unsigned long *bottom = (unsigned long *)(thread->stack + 2*4*1024);
+ unsigned long *pointer = (unsigned long *)thread->sp;
int count;
+ if(thread == current)
+ {
+#ifdef __i386__
+ asm("movl %%esp,%0"
+ : "=r"(pointer));
+#else
+ asm("movq %%rsp,%0"
+ : "=r"(pointer));
+#endif
+ }
printk("The stack for \"%s\"\n", thread->name);
- for(count = 0; count < 15 && pointer < bottom; count ++)
+ for(count = 0; count < 25 && pointer < bottom; count ++)
{
printk("[0x%lx] 0x%lx\n", pointer, *pointer);
pointer++;
}
- if(pointer < bottom) printk("Not the whole stack printed\n");
+ if(pointer < bottom) printk(" ... continues.\n");
}
#ifdef __i386__
@@ -95,13 +105,29 @@ void dump_stack(struct thread *thread)
"1:\t" \
"popl %%ebp\n\t" \
"popfl" \
- :"=m" (prev->eps),"=m" (prev->eip), \
+ :"=m" (prev->sp),"=m" (prev->ip), \
"=S" (esi),"=D" (edi) \
- :"m" (next->eps),"m" (next->eip), \
+ :"m" (next->sp),"m" (next->ip), \
"2" (prev), "d" (next)); \
} while (0)
#elif __x86_64__
-/* FIXME */
+#define switch_threads(prev, next) do { \
+ unsigned long rsi,rdi; \
+ __asm__ __volatile__("pushfq\n\t" \
+ "pushq %%rbp\n\t" \
+ "movq %%rsp,%0\n\t" /* save RSP */ \
+ "movq %4,%%rsp\n\t" /* restore RSP */ \
+ "movq $1f,%1\n\t" /* save RIP */ \
+ "pushq %5\n\t" /* restore RIP */ \
+ "ret\n\t" \
+ "1:\t" \
+ "popq %%rbp\n\t" \
+ "popfq" \
+ :"=m" (prev->sp),"=m" (prev->ip), \
+ "=S" (rsi),"=D" (rdi) \
+ :"m" (next->sp),"m" (next->ip), \
+ "2" (prev), "d" (next)); \
+} while (0)
#endif
void inline print_runqueue(void)
@@ -151,17 +177,19 @@ void schedule(void)
local_irq_restore(flags);
/* Interrupting the switch is equivalent to having the next thread
inturrupted at the return instruction. And therefore at safe point. */
-/* The thread switching only works for i386 at the moment */
-#ifdef __i386__
if(prev != next) switch_threads(prev, next);
-#endif
-}
-
-
-
-void exit_thread(struct thread *thread)
+}
+
+
+/* Gets run when a new thread is scheduled the first time ever,
+ defined in x86_[32/64].S */
+extern void thread_starter(void);
+
+
+void exit_thread(void)
{
unsigned long flags;
+ struct thread *thread = current;
printk("Thread \"%s\" exited.\n", thread->name);
local_irq_save(flags);
/* Remove from the thread list */
@@ -174,6 +202,12 @@ void exit_thread(struct thread *thread)
schedule();
}
+/* Pushes the specified value onto the stack of the specified thread */
+static void stack_push(struct thread *thread, unsigned long value)
+{
+ thread->sp -= sizeof(unsigned long);
+ *((unsigned long *)thread->sp) = value;
+}
struct thread* create_thread(char *name, void (*function)(void *), void *data)
{
@@ -187,23 +221,17 @@ struct thread* create_thread(char *name,
printk("Thread \"%s\": pointer: 0x%lx, stack: 0x%lx\n", name, thread,
thread->stack);
- thread->eps = (unsigned long)thread->stack + 4096 * 2 - 4;
+ thread->sp = (unsigned long)thread->stack + 4096 * 2;
/* Save pointer to the thread on the stack, used by current macro */
*((unsigned long *)thread->stack) = (unsigned long)thread;
- *((unsigned long *)thread->eps) = (unsigned long)thread;
- thread->eps -= 4;
- *((unsigned long *)thread->eps) = (unsigned long)data;
-
- /* No return address */
- thread->eps -= 4;
- *((unsigned long *)thread->eps) = (unsigned long)exit_thread;
-
- thread->eip = (unsigned long)function;
+
+ stack_push(thread, (unsigned long) function);
+ stack_push(thread, (unsigned long) data);
+ thread->ip = (unsigned long) thread_starter;
/* Not runable, not exited */
thread->flags = 0;
set_runnable(thread);
-
local_irq_save(flags);
if(idle_thread != NULL) {
list_add_tail(&thread->thread_list, &idle_thread->thread_list);
@@ -213,7 +241,6 @@ struct thread* create_thread(char *name,
BUG();
}
local_irq_restore(flags);
-
return thread;
}
@@ -240,11 +267,19 @@ void run_idle_thread(void)
void run_idle_thread(void)
{
/* Switch stacks and run the thread */
+#if defined(__i386__)
__asm__ __volatile__("mov %0,%%esp\n\t"
"push %1\n\t"
"ret"
- :"=m" (idle_thread->eps)
- :"m" (idle_thread->eip));
+ :"=m" (idle_thread->sp)
+ :"m" (idle_thread->ip));
+#elif defined(__x86_64__)
+ __asm__ __volatile__("mov %0,%%rsp\n\t"
+ "push %1\n\t"
+ "ret"
+ :"=m" (idle_thread->sp)
+ :"m" (idle_thread->ip));
+#endif
}
diff -r dc3c59367403 -r f6507937cb7c extras/mini-os/x86_32.S
--- a/extras/mini-os/x86_32.S Mon May 01 17:44:51 2006 +0100
+++ b/extras/mini-os/x86_32.S Tue May 02 09:12:39 2006 +0100
@@ -286,3 +286,11 @@ ENTRY(spurious_interrupt_bug)
pushl $0
pushl $do_spurious_interrupt_bug
jmp do_exception
+
+ENTRY(thread_starter)
+ popl %eax
+ popl %ebx
+ pushl %eax
+ call *%ebx
+ call exit_thread
+
diff -r dc3c59367403 -r f6507937cb7c extras/mini-os/x86_64.S
--- a/extras/mini-os/x86_64.S Mon May 01 17:44:51 2006 +0100
+++ b/extras/mini-os/x86_64.S Tue May 02 09:12:39 2006 +0100
@@ -1,4 +1,5 @@
#include <os.h>
+#include <xen/features.h>
.section __xen_guest
.ascii "GUEST_OS=Mini-OS"
@@ -65,10 +66,253 @@ hypercall_page:
hypercall_page:
.org 0x3000
+
+/* Offsets into shared_info_t. */
+#define evtchn_upcall_pending /* 0 */
+#define evtchn_upcall_mask 1
+
+NMI_MASK = 0x80000000
+
+#define RDI 112
+#define ORIG_RAX 120 /* + error_code */
+#define EFLAGS 144
+
+#define REST_SKIP 6*8
+.macro SAVE_REST
+ subq $REST_SKIP,%rsp
+# CFI_ADJUST_CFA_OFFSET REST_SKIP
+ movq %rbx,5*8(%rsp)
+# CFI_REL_OFFSET rbx,5*8
+ movq %rbp,4*8(%rsp)
+# CFI_REL_OFFSET rbp,4*8
+ movq %r12,3*8(%rsp)
+# CFI_REL_OFFSET r12,3*8
+ movq %r13,2*8(%rsp)
+# CFI_REL_OFFSET r13,2*8
+ movq %r14,1*8(%rsp)
+# CFI_REL_OFFSET r14,1*8
+ movq %r15,(%rsp)
+# CFI_REL_OFFSET r15,0*8
+.endm
+
+
+.macro RESTORE_REST
+ movq (%rsp),%r15
+# CFI_RESTORE r15
+ movq 1*8(%rsp),%r14
+# CFI_RESTORE r14
+ movq 2*8(%rsp),%r13
+# CFI_RESTORE r13
+ movq 3*8(%rsp),%r12
+# CFI_RESTORE r12
+ movq 4*8(%rsp),%rbp
+# CFI_RESTORE rbp
+ movq 5*8(%rsp),%rbx
+# CFI_RESTORE rbx
+ addq $REST_SKIP,%rsp
+# CFI_ADJUST_CFA_OFFSET -(REST_SKIP)
+.endm
+
+
+#define ARG_SKIP 9*8
+.macro RESTORE_ARGS
skiprax=0,addskip=0,skiprcx=0,skipr11=0,skipr8910=0,skiprdx=0
+ .if \skipr11
+ .else
+ movq (%rsp),%r11
+# CFI_RESTORE r11
+ .endif
+ .if \skipr8910
+ .else
+ movq 1*8(%rsp),%r10
+# CFI_RESTORE r10
+ movq 2*8(%rsp),%r9
+# CFI_RESTORE r9
+ movq 3*8(%rsp),%r8
+# CFI_RESTORE r8
+ .endif
+ .if \skiprax
+ .else
+ movq 4*8(%rsp),%rax
+# CFI_RESTORE rax
+ .endif
+ .if \skiprcx
+ .else
+ movq 5*8(%rsp),%rcx
+# CFI_RESTORE rcx
+ .endif
+ .if \skiprdx
+ .else
+ movq 6*8(%rsp),%rdx
+# CFI_RESTORE rdx
+ .endif
+ movq 7*8(%rsp),%rsi
+# CFI_RESTORE rsi
+ movq 8*8(%rsp),%rdi
+# CFI_RESTORE rdi
+ .if ARG_SKIP+\addskip > 0
+ addq $ARG_SKIP+\addskip,%rsp
+# CFI_ADJUST_CFA_OFFSET -(ARG_SKIP+\addskip)
+ .endif
+.endm
+
+
+.macro HYPERVISOR_IRET flag
+# testb $3,1*8(%rsp) /* Don't need to do that in Mini-os, as */
+# jnz 2f /* there is no userspace? */
+ testl $NMI_MASK,2*8(%rsp)
+ jnz 2f
+
+ testb $1,(xen_features+XENFEAT_supervisor_mode_kernel)
+ jnz 1f
+
+ /* Direct iret to kernel space. Correct CS and SS. */
+ orb $3,1*8(%rsp)
+ orb $3,4*8(%rsp)
+1: iretq
+
+2: /* Slow iret via hypervisor. */
+ andl $~NMI_MASK, 16(%rsp)
+ pushq $\flag
+ jmp hypercall_page + (__HYPERVISOR_iret * 32)
+.endm
+
+/*
+ * Exception entry point. This expects an error code/orig_rax on the stack
+ * and the exception handler in %rax.
+ */
+ENTRY(error_entry)
+# _frame RDI
+ /* rdi slot contains rax, oldrax contains error code */
+ cld
+ subq $14*8,%rsp
+# CFI_ADJUST_CFA_OFFSET (14*8)
+ movq %rsi,13*8(%rsp)
+# CFI_REL_OFFSET rsi,RSI
+ movq 14*8(%rsp),%rsi /* load rax from rdi slot */
+ movq %rdx,12*8(%rsp)
+# CFI_REL_OFFSET rdx,RDX
+ movq %rcx,11*8(%rsp)
+# CFI_REL_OFFSET rcx,RCX
+ movq %rsi,10*8(%rsp) /* store rax */
+# CFI_REL_OFFSET rax,RAX
+ movq %r8, 9*8(%rsp)
+# CFI_REL_OFFSET r8,R8
+ movq %r9, 8*8(%rsp)
+# CFI_REL_OFFSET r9,R9
+ movq %r10,7*8(%rsp)
+# CFI_REL_OFFSET r10,R10
+ movq %r11,6*8(%rsp)
+# CFI_REL_OFFSET r11,R11
+ movq %rbx,5*8(%rsp)
+# CFI_REL_OFFSET rbx,RBX
+ movq %rbp,4*8(%rsp)
+# CFI_REL_OFFSET rbp,RBP
+ movq %r12,3*8(%rsp)
+# CFI_REL_OFFSET r12,R12
+ movq %r13,2*8(%rsp)
+# CFI_REL_OFFSET r13,R13
+ movq %r14,1*8(%rsp)
+# CFI_REL_OFFSET r14,R14
+ movq %r15,(%rsp)
+# CFI_REL_OFFSET r15,R15
+#if 0
+ cmpl $__KERNEL_CS,CS(%rsp)
+ je error_kernelspace
+#endif
+error_call_handler:
+ movq %rdi, RDI(%rsp)
+ movq %rsp,%rdi
+ movq ORIG_RAX(%rsp),%rsi # get error code
+ movq $-1,ORIG_RAX(%rsp)
+ call *%rax
+
+.macro zeroentry sym
+# INTR_FRAME
+ movq (%rsp),%rcx
+ movq 8(%rsp),%r11
+ addq $0x10,%rsp /* skip rcx and r11 */
+ pushq $0 /* push error code/oldrax */
+# CFI_ADJUST_CFA_OFFSET 8
+ pushq %rax /* push real oldrax to the rdi slot */
+# CFI_ADJUST_CFA_OFFSET 8
+ leaq \sym(%rip),%rax
+ jmp error_entry
+# CFI_ENDPROC
+.endm
+
+
+
+#define XEN_GET_VCPU_INFO(reg) movq HYPERVISOR_shared_info,reg
+#define XEN_PUT_VCPU_INFO(reg)
+#define XEN_PUT_VCPU_INFO_fixup
+#define XEN_LOCKED_BLOCK_EVENTS(reg) movb $1,evtchn_upcall_mask(reg)
+#define XEN_LOCKED_UNBLOCK_EVENTS(reg) movb $0,evtchn_upcall_mask(reg)
+#define XEN_TEST_PENDING(reg) testb $0xFF,evtchn_upcall_pending(reg)
+
+#define XEN_BLOCK_EVENTS(reg) XEN_GET_VCPU_INFO(reg) ; \
+ XEN_LOCKED_BLOCK_EVENTS(reg) ; \
+ XEN_PUT_VCPU_INFO(reg)
+
+#define XEN_UNBLOCK_EVENTS(reg) XEN_GET_VCPU_INFO(reg)
; \
+ XEN_LOCKED_UNBLOCK_EVENTS(reg)
; \
+ XEN_PUT_VCPU_INFO(reg)
+
+
+
ENTRY(hypervisor_callback)
- popq %rcx
- popq %r11
- iretq
+ zeroentry hypervisor_callback2
+
+ENTRY(hypervisor_callback2)
+ movq %rdi, %rsp
+11: movq %gs:8,%rax
+ incl %gs:0
+ cmovzq %rax,%rsp
+ pushq %rdi
+ call do_hypervisor_callback
+ popq %rsp
+ decl %gs:0
+ jmp error_exit
+
+# ALIGN
+restore_all_enable_events:
+ XEN_UNBLOCK_EVENTS(%rsi) # %rsi is already set up...
+
+scrit: /**** START OF CRITICAL REGION ****/
+ XEN_TEST_PENDING(%rsi)
+ jnz 14f # process more events if necessary...
+ XEN_PUT_VCPU_INFO(%rsi)
+ RESTORE_ARGS 0,8,0
+ HYPERVISOR_IRET 0
+
+14: XEN_LOCKED_BLOCK_EVENTS(%rsi)
+ XEN_PUT_VCPU_INFO(%rsi)
+ SAVE_REST
+ movq %rsp,%rdi # set the argument again
+ jmp 11b
+ecrit: /**** END OF CRITICAL REGION ****/
+
+
+retint_kernel:
+retint_restore_args:
+ movl EFLAGS-REST_SKIP(%rsp), %eax
+ shr $9, %eax # EAX[0] == IRET_EFLAGS.IF
+ XEN_GET_VCPU_INFO(%rsi)
+ andb evtchn_upcall_mask(%rsi),%al
+ andb $1,%al # EAX[0] == IRET_EFLAGS.IF & event_mask
+ jnz restore_all_enable_events # != 0 => enable event delivery
+ XEN_PUT_VCPU_INFO(%rsi)
+
+ RESTORE_ARGS 0,8,0
+ HYPERVISOR_IRET 0
+
+
+error_exit:
+ RESTORE_REST
+/* cli */
+ XEN_BLOCK_EVENTS(%rsi)
+ jmp retint_kernel
+
+
ENTRY(failsafe_callback)
popq %rcx
@@ -228,3 +472,12 @@ ENTRY(exception_table)
.quad do_alignment_check
.quad do_machine_check
.quad do_simd_coprocessor_error
+
+
+ENTRY(thread_starter)
+ popq %rdi
+ popq %rbx
+ call *%rbx
+ call exit_thread
+
+
_______________________________________________
Xen-changelog mailing list
Xen-changelog@xxxxxxxxxxxxxxxxxxx
http://lists.xensource.com/xen-changelog
|