From: Rene Rebe <rene@exactcode.de>
To: qemu-devel@nongnu.org, Alexander Graf <agraf@suse.de>
Subject: [Qemu-devel] [PATCH] Add multi-boot kernel loading support
Date: Mon, 02 Feb 2009 19:08:00 +0100 [thread overview]
Message-ID: <49873680.2040603@exactcode.de> (raw)
Hi all,
Alexander Graf implemented multi-boot kernel loading during
his work to run Darwin inside Qemu/KVM. As the boot loader
expects to load the kernel in an EFI environment a custom
booter is used to load the kernel using a legacy BIOS.
This is a port of the patch to the new extload / INT 19
machinery (including minor cleanups).
Signed-off-by: René Rebe <rene@exactcode.de>
Index: elf_ops.h
===================================================================
--- elf_ops.h (revision 6501)
+++ elf_ops.h (working copy)
@@ -195,6 +195,10 @@
}
if (ELF_MACHINE != ehdr.e_machine)
+#if (ELF_MACHINE == EM_X86_64) && !defined(CONFIG_USER_ONLY)
+ /* x86_64 systems can run i386 code as well */
+ if(ehdr.e_machine != EM_386)
+#endif
goto fail;
if (pentry)
Index: hw/pc.c
===================================================================
--- hw/pc.c (revision 6501)
+++ hw/pc.c (working copy)
@@ -533,6 +533,438 @@
return size;
}
+/* Generate an initial boot sector which sets state and jump to
+ a specified vector */
+static void generate_bootsect_multiboot(uint8_t *option_rom,
+ uint32_t mh_entry_addr, uint32_t bootinfo)
+{
+ uint8_t rom[512], *p, *reloc, *pgdt, *pmmaploop;
+ uint8_t sum;
+ uint32_t ip;
+ int i;
+ int mmaploop;
+
+ memset(rom, 0, sizeof(rom));
+
+ p = rom;
+ /* Make sure we have an option rom signature */
+ *p++ = 0x55;
+ *p++ = 0xaa;
+
+ /* ROM size in sectors*/
+ *p++ = 1;
+
+ /* Hook int19 */
+
+ *p++ = 0x50; /* push ax */
+ *p++ = 0x1e; /* push ds */
+ *p++ = 0x31; *p++ = 0xc0; /* xor ax, ax */
+ *p++ = 0x8e; *p++ = 0xd8; /* mov ax, ds */
+
+ *p++ = 0xc7; *p++ = 0x06; /* movvw _start,0x64 */
+ *p++ = 0x64; *p++ = 0x00;
+ reloc = p;
+ *p++ = 0x00; *p++ = 0x00;
+
+ *p++ = 0x8c; *p++ = 0x0e; /* mov cs,0x66 */
+ *p++ = 0x66; *p++ = 0x00;
+
+ *p++ = 0x1f; /* pop ds */
+ *p++ = 0x58; /* pop ax */
+ *p++ = 0xcb; /* lret */
+
+ /* Actual code */
+ *reloc = (p - rom);
+
+ *p++ = 0xfa; /* CLI */
+ *p++ = 0xfc; /* CLD */
+
+ /* 660f011528000000 lgdt [0x28] */
+ *p++ = 0x66; /* 32-bit operand size */
+ *p++ = 0x67; /* 32-bit addr size */
+ *p++ = 0x0f; /* LGDT [0x128] */
+ *p++ = 0x01;
+ *p++ = 0x15;
+ pgdt=p; /* we calculate the gdt position later */
+ p+=4;
+
+ /* Initialize multiboot mmap structs using the 0x15(e820) */
+ *p++ = 0x31; /* XOR BX,BX */
+ *p++ = 0xdb;
+
+ *p++ = 0x66; /* 32-bit operand size */
+ *p++ = 0x67; /* 32-bit addr size */
+ *p++ = 0xbf; /* MOV EDI,0x9004 */
+ *p++ = 0x04;
+ *p++ = 0x90;
+ *p++ = 0x00;
+ *p++ = 0x00;
+
+ pmmaploop = p;
+ *p++ = 0x66; /* 32-bit operand size */
+ *p++ = 0x67; /* 32-bit addr size */
+ *p++ = 0xb8; /* MOV EAX,0x20 */
+ *p++ = 0x20;
+ *p++ = 0x00;
+ *p++ = 0x00;
+ *p++ = 0x00;
+
+ *p++ = 0x66; /* 32-bit operand size */
+ *p++ = 0x67; /* 32-bit addr size */
+ *p++ = 0x89; /* MOV -4(EDI),EAX */
+ *p++ = 0x47;
+ *p++ = 0xfc;
+
+ *p++ = 0x66; /* 32-bit operand size */
+ *p++ = 0x67; /* 32-bit addr size */
+ *p++ = 0xb8; /* MOV EAX,0x0000e820 */
+ *p++ = 0x20;
+ *p++ = 0xe8;
+ *p++ = 0x00;
+ *p++ = 0x00;
+
+ *p++ = 0x66; /* 32-bit operand size */
+ *p++ = 0x67; /* 32-bit addr size */
+ *p++ = 0xba; /* MOV EDX,0x534d4150 */
+ *p++ = 0x50;
+ *p++ = 0x41;
+ *p++ = 0x4d;
+ *p++ = 0x53;
+
+ *p++ = 0x66; /* 32-bit operand size */
+ *p++ = 0x67; /* 32-bit addr size */
+ *p++ = 0xb9; /* MOV ECX,0x20 */
+ *p++ = 0x20;
+ *p++ = 0x00;
+ *p++ = 0x00;
+ *p++ = 0x00;
+
+ *p++ = 0xcd; /* INT 0x15 */
+ *p++ = 0x15;
+
+ *p++ = 0x66; /* 32-bit operand size */
+ *p++ = 0x67; /* 32-bit addr size */
+ *p++ = 0xb8; /* MOV EAX, 0x24 */
+ *p++ = 0x24;
+ *p++ = 0x00;
+ *p++ = 0x00;
+ *p++ = 0x00;
+
+ *p++ = 0xf7; /* MUL AX, BX */
+ *p++ = 0xe3;
+
+ *p++ = 0x66; /* 32-bit operand size */
+ *p++ = 0x67; /* 32-bit addr size */
+ *p++ = 0x21; /* AND EBX, EBX */
+ *p++ = 0xdb;
+
+ /* don't store if bx = 0 */
+ *p++ = 0x66; /* 32-bit operand size */
+ *p++ = 0x67; /* 32-bit addr size */
+ *p++ = 0x0f; /* JZ next instruction */
+ *p++ = 0x84;
+ *p++ = 0x07;
+ *p++ = 0x00;
+ *p++ = 0x00;
+ *p++ = 0x00;
+
+ /* store the amount of blocks in the bootinfo struct */
+ *p++ = 0x66; /* 32-bit operand size */
+ *p++ = 0x67; /* 32-bit addr size */
+ *p++ = 0xa3; /* MOV [bootinfo+0x2c], EAX */
+ *p++ = (bootinfo+0x2c);
+ *p++ = (bootinfo+0x2c) >> 8;
+ *p++ = (bootinfo+0x2c) >> 16;
+ *p++ = (bootinfo+0x2c) >> 24;
+
+ *p++ = 0x66; /* 32-bit operand size */
+ *p++ = 0x67; /* 32-bit addr size */
+ *p++ = 0x05; /* ADD EAX, 0x9004 */
+ *p++ = 0x04;
+ *p++ = 0x90;
+ *p++ = 0x00;
+ *p++ = 0x00;
+
+ *p++ = 0x89; /* MOV DI, AX */
+ *p++ = 0xc7;
+
+ *p++ = 0x66; /* 32-bit operand size */
+ *p++ = 0x67; /* 32-bit addr size */
+ *p++ = 0x21; /* AND EBX, EBX */
+ *p++ = 0xdb;
+
+ /* process next entry */
+ *p++ = 0x66; /* 32-bit operand size */
+ *p++ = 0x67; /* 32-bit addr size */
+ *p++ = 0x0f; /* JNZ mmaploop */
+ *p++ = 0x85;
+ mmaploop = (int)((long)pmmaploop) - ((long)p) - 4;
+ *p++ = mmaploop;
+ *p++ = mmaploop >> 8;
+ *p++ = mmaploop >> 16;
+ *p++ = mmaploop >> 24;
+
+ /* get us to protected mode now */
+
+ *p++ = 0x66;
+ *p++ = 0xb8; /* MOV EAX,0x01 */
+ *p++ = 0x01;
+ *p++ = 0x00;
+ *p++ = 0x00;
+ *p++ = 0x00;
+
+ *p++ = 0x0f; /* MOV CR0,EAX */
+ *p++ = 0x22;
+ *p++ = 0xc0;
+
+ /* the JMP sets CS for us and gets us to 32-bit */
+ ip = 0x000d0000 + (p - rom) + 8; /* set i to the IP after the JMP */
+ *p++ = 0x66; /* 32-bit operand size */
+ *p++ = 0xea; /* JMP */
+ *p++ = ip; /* IP */
+ *p++ = ip >> 8;
+ *p++ = ip >> 16;
+ *p++ = ip >> 24;
+ *p++ = 0x08;
+ *p++ = 0x00;
+
+ /* initialize all other segments */
+ *p++ = 0xb8; /* MOV EAX,0x10 */
+ *p++ = 0x10;
+ *p++ = 0x00;
+ *p++ = 0x00;
+ *p++ = 0x00;
+ for (i = 0; i < 6; i++) {
+ if (i == 1) /* Skip CS */
+ continue;
+
+ *p++ = 0x8e; /* MOV <seg>,EAX */
+ *p++ = 0xc0 + (i << 3);
+ }
+
+ /* EBX contains a pointer to the bootinfo struct */
+ *p++ = 0xbb; /* MOV EBX,imm32 */
+ *p++ = bootinfo;
+ *p++ = bootinfo >> 8;
+ *p++ = bootinfo >> 16;
+ *p++ = bootinfo >> 24;
+
+ /* EAX has to contain the following magic */
+ *p++ = 0xb8; /* MOV EAX,0x2badb002 */
+ *p++ = 0x02;
+ *p++ = 0xb0;
+ *p++ = 0xad;
+ *p++ = 0x2b;
+
+ /* Jump off to the kernel */
+ *p++ = 0xea; /* JMP */
+ *p++ = mh_entry_addr; /* IP */
+ *p++ = mh_entry_addr >> 8;
+ *p++ = mh_entry_addr >> 16;
+ *p++ = mh_entry_addr >> 24;
+ *p++ = 0x08;
+ *p++ = 0x00;
+
+ { /* GDT loading */
+ uint32_t gdt_base = 0x000d0000 + (p - rom); /* 0x00007c00 is the first IP */
+ uint32_t gdtr = gdt_base + 0x28;
+ uint8_t gdt[] = { /* GDT base: 0x00000100 */
+ /* 0x00 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* 0x08: code segment (base=0, limit=0xfffff, type=32bit code exec/read, DPL=0, 4k) */
+ 0xff, 0xff, 0x00, 0x00, 0x00, 0x9a, 0xcf, 0x00,
+ /* 0x10: data segment (base=0, limit=0xfffff, type=32bit data read/write, DPL=0, 4k) */
+ 0xff, 0xff, 0x00, 0x00, 0x00, 0x92, 0xcf, 0x00,
+ /* 0x18: code segment (base=0, limit=0x0ffff, type=16bit code exec/read/conf, DPL=0, 1b) */
+ 0xff, 0xff, 0x00, 0x00, 0x00, 0x9e, 0x00, 0x00,
+ /* 0x20: data segment (base=0, limit=0x0ffff, type=16bit data read/write, DPL=0, 1b) */
+ 0xff, 0xff, 0x00, 0x00, 0x00, 0x92, 0x00, 0x00,
+ /* 0x28: gdtdesc */
+ 0x27, 0x00, gdt_base, gdt_base >> 8, gdt_base >> 16, gdt_base >> 24
+ };
+
+ memcpy(p, gdt, sizeof(gdt));
+ p+=sizeof(gdt);
+ *pgdt++ = gdtr;
+ *pgdt++ = gdtr >> 8;
+ *pgdt++ = gdtr >> 16;
+ *pgdt++ = gdtr >> 24;
+ }
+
+ fprintf(stderr, "qemu: multiboot loader code is %d bytes long.\n", (int)(p-rom));
+
+ /* sign rom */
+ sum = 0;
+ for (i = 0; i < (sizeof(rom) - 1); i++)
+ sum += rom[i];
+ rom[sizeof(rom) - 1] = -sum;
+
+ memcpy(option_rom, rom, sizeof(rom));
+}
+
+static int load_multiboot(uint8_t *option_rom,
+ FILE *f,
+ const char *kernel_filename,
+ const char *initrd_filename,
+ const char *kernel_cmdline,
+ uint8_t *header)
+{
+ int i, is_multiboot = 0;
+ uint32_t flags = 0;
+ uint32_t mh_entry_addr;
+ uint32_t mh_load_addr;
+ uint32_t mb_kernel_size;
+ uint32_t mb_bootinfo = 0x90000;
+ uint32_t tmp_size;
+
+ /* Ok, let's see if it is a multiboot image */
+ for(i = 0; i < 8144; i += 4) { /* the header is 12x32bit long, so */
+ /* the latest entry may be 8192 - 48 */
+ if(ldl_p(header+i) == 0x1BADB002) {
+ uint32_t checksum = ldl_p(header+i+8);
+ flags = ldl_p(header+i+4);
+ checksum += flags;
+ checksum += (uint32_t)0x1BADB002;
+ if(!checksum) {
+ is_multiboot = 1;
+ break;
+ }
+ }
+ }
+
+ if(!is_multiboot) return 0; /* no multiboot */
+ fprintf(stderr, "qemu: I believe we found a multiboot image!\n");
+
+ if(flags & 0x00000004) { /* MULTIBOOT_HEADER_HAS_VBE */
+ fprintf(stderr, "qemu: multiboot knows VBE. we don't.\n");
+ }
+ if(!(flags & 0x00010000)) { /* MULTIBOOT_HEADER_HAS_ADDR */
+ uint64_t elf_entry;
+ int kernel_size;
+ fclose(f);
+ kernel_size = load_elf(kernel_filename, 0, &elf_entry, NULL, NULL);
+ if(kernel_size < 0) {
+ fprintf(stderr, "Error while loading elf kernel\n");
+ exit(1);
+ }
+ mh_load_addr = mh_entry_addr = elf_entry;
+ mb_kernel_size = kernel_size;
+
+ fprintf(stderr, "qemu: loading multiboot-elf kernel (%#x bytes) with entry %#zx\n",
+ mb_kernel_size, (size_t)mh_entry_addr);
+ } else {
+ /* Valid if mh_flags sets MULTIBOOT_HEADER_HAS_ADDR. */
+ uint32_t mh_header_addr = ldl_p(header+i+12);
+ mh_load_addr = ldl_p(header+i+16);
+ uint32_t mh_load_end_addr = ldl_p(header+i+20);
+ uint32_t mh_bss_end_addr = ldl_p(header+i+24);
+ uint8_t *mb_kernel_addr = phys_ram_base + (mh_load_addr);
+ uint32_t mb_kernel_text_offset = i - (mh_header_addr - mh_load_addr);
+
+ mh_entry_addr = ldl_p(header+i+28);
+ mb_kernel_size = get_file_size(f) - mb_kernel_text_offset;
+
+ /* Valid if mh_flags sets MULTIBOOT_HEADER_HAS_VBE.
+ uint32_t mh_mode_type = ldl_p(header+i+32);
+ uint32_t mh_width = ldl_p(header+i+36);
+ uint32_t mh_height = ldl_p(header+i+40);
+ uint32_t mh_depth = ldl_p(header+i+44); */
+
+ fprintf(stderr, "multiboot: mh_header_addr = %#x\n", mh_header_addr);
+ fprintf(stderr, "multiboot: mh_load_addr = %#x\n", mh_load_addr);
+ fprintf(stderr, "multiboot: mh_load_end_addr = %#x\n", mh_load_end_addr);
+ fprintf(stderr, "multiboot: mh_bss_end_addr = %#x\n", mh_bss_end_addr);
+
+ fseek(f, mb_kernel_text_offset, SEEK_SET);
+
+ fprintf(stderr, "qemu: loading multiboot kernel (%#x bytes) at %#zx\n",
+ mb_kernel_size, mb_kernel_addr - phys_ram_base);
+
+ if ((tmp_size=fread(mb_kernel_addr, 1, mb_kernel_size, f)) != mb_kernel_size) {
+ fprintf(stderr, "qemu: read error on multiboot kernel '%s' (%#x != %#x)\n", kernel_filename, tmp_size, mb_kernel_size);
+ exit(1);
+ }
+ fclose(f);
+ }
+
+
+ /* load modules */
+
+ stl_p(phys_ram_base + mb_bootinfo + 20, 0x0); /* mods_count */
+ if(initrd_filename) {
+ uint32_t mb_mod_info = mb_bootinfo + 0x100;
+ uint32_t mb_mod_cmdline = mb_bootinfo+ 0x300;
+ uint32_t mb_mod_start = mh_load_addr;
+ uint32_t mb_mod_length = mb_kernel_size;
+ char *next_initrd;
+ char *next_space;
+ int mb_mod_count = 0;
+
+ do {
+ next_initrd = strchr(initrd_filename, ',');
+ if(next_initrd)
+ *next_initrd = '\0';
+ /* if a space comes after the module filename, treat everything after that as parameters */
+ strcpy(phys_ram_base + mb_mod_cmdline, initrd_filename);
+ stl_p(phys_ram_base + mb_mod_info + 8, mb_mod_cmdline); /* string */
+ mb_mod_cmdline += strlen(initrd_filename) + 1;
+ if(next_space = strchr(initrd_filename, ' '))
+ *next_space = '\0';
+printf("multiboot loading module: %s\n", initrd_filename);
+ f = fopen(initrd_filename, "rb");
+ if(f) {
+ mb_mod_start = (mb_mod_start + mb_mod_length + (TARGET_PAGE_SIZE - 1))
+ & (TARGET_PAGE_MASK);
+ mb_mod_length = get_file_size(f);
+
+ if ((tmp_size=fread((phys_ram_base + mb_mod_start), 1, mb_mod_length, f))
+ != mb_mod_length) {
+ fprintf(stderr, "qemu: read error on multiboot module '%s' (%#x != %#x)\n",
+ initrd_filename, tmp_size, mb_mod_length);
+ exit(1);
+ }
+
+ mb_mod_count++;
+ stl_p(phys_ram_base + mb_mod_info + 0, mb_mod_start);
+ stl_p(phys_ram_base + mb_mod_info + 4, mb_mod_start + mb_mod_length);
+printf("mod_start: %#x\nmod_end: %#x\n", mb_mod_start, mb_mod_start + mb_mod_length);
+ stl_p(phys_ram_base + mb_mod_info + 12, 0x0); /* reserved */
+ }
+ initrd_filename = next_initrd+1;
+ mb_mod_info += 16;
+ } while(next_initrd);
+ stl_p(phys_ram_base + mb_bootinfo + 20, mb_mod_count); /* mods_count */
+ stl_p(phys_ram_base + mb_bootinfo + 24, mb_bootinfo + 0x100); /* mods_addr */
+ }
+
+ /* Commandline support */
+ stl_p(phys_ram_base + mb_bootinfo + 16, mb_bootinfo + 0x200);
+ strcpy((char*)(phys_ram_base + mb_bootinfo + 0x200), kernel_cmdline);
+
+ /* the kernel is where we want it to be now */
+
+#define MULTIBOOT_FLAGS_MEMORY (1 << 0)
+#define MULTIBOOT_FLAGS_BOOT_DEVICE (1 << 1)
+#define MULTIBOOT_FLAGS_CMDLINE (1 << 2)
+#define MULTIBOOT_FLAGS_MODULES (1 << 3)
+#define MULTIBOOT_FLAGS_MMAP (1 << 6)
+ stl_p(phys_ram_base + mb_bootinfo, MULTIBOOT_FLAGS_MEMORY
+ | MULTIBOOT_FLAGS_BOOT_DEVICE
+ | MULTIBOOT_FLAGS_CMDLINE
+ | MULTIBOOT_FLAGS_MODULES
+ | MULTIBOOT_FLAGS_MMAP);
+ stl_p(phys_ram_base + mb_bootinfo + 4, 640 * 1024); /* mem_lower */
+ stl_p(phys_ram_base + mb_bootinfo + 8, ram_size); /* mem_upper */
+ stl_p(phys_ram_base + mb_bootinfo + 12, 0x8001ffff); /* XXX: use the -boot switch? */
+ stl_p(phys_ram_base + mb_bootinfo + 48, 0x9000); /* mmap_addr */
+
+ fprintf(stderr, "multiboot: mh_entry_addr = %#x\n", mh_entry_addr);
+
+ generate_bootsect_multiboot(option_rom, mh_entry_addr, mb_bootinfo);
+
+ return 1; /* yes, we are multiboot */
+}
+
static void load_linux(uint8_t *option_rom,
const char *kernel_filename,
const char *initrd_filename,
@@ -544,7 +976,7 @@
uint16_t real_seg;
int setup_size, kernel_size, initrd_size, cmdline_size;
uint32_t initrd_max;
- uint8_t header[1024];
+ uint8_t header[8192];
target_phys_addr_t real_addr, prot_addr, cmdline_addr, initrd_addr;
FILE *f, *fi;
@@ -554,7 +986,7 @@
/* load the kernel header */
f = fopen(kernel_filename, "rb");
if (!f || !(kernel_size = get_file_size(f)) ||
- fread(header, 1, 1024, f) != 1024) {
+ fread(header, 1, 8192, f) != 8192) {
fprintf(stderr, "qemu: could not load kernel '%s'\n",
kernel_filename);
exit(1);
@@ -566,8 +998,14 @@
#endif
if (ldl_p(header+0x202) == 0x53726448)
protocol = lduw_p(header+0x206);
- else
+ else {
+ /* This looks like a multiboot kernel. If it is, let's stop
+ treating it like Linux. */
+ if (load_multiboot(option_rom, f, kernel_filename, initrd_filename,
+ kernel_cmdline, header))
+ return;
protocol = 0;
+ }
if (protocol < 0x200 || !(header[0x211] & 0x01)) {
/* Low kernel */
@@ -660,7 +1098,7 @@
}
/* store the finalized header and load the rest of the kernel */
- cpu_physical_memory_write(real_addr, header, 1024);
+ cpu_physical_memory_write(real_addr, header, 8192);
setup_size = header[0x1f1];
if (setup_size == 0)
@@ -669,7 +1107,7 @@
setup_size = (setup_size+1)*512;
kernel_size -= setup_size; /* Size of protected-mode code */
- if (!fread_targphys_ok(real_addr+1024, setup_size-1024, f) ||
+ if (!fread_targphys_ok(real_addr+8192, setup_size-8192, f) ||
!fread_targphys_ok(prot_addr, kernel_size, f)) {
fprintf(stderr, "qemu: read error on kernel '%s'\n",
kernel_filename);
--
René Rebe - ExactCODE GmbH - Europe, Germany, Berlin
Geschäftsführer: Susanne Klaus, René Rebe
Sitz: Berlin, Amtsgericht Charlottenburg HRB 105 123 B
USt-IdNr.: DE251602478
http://exactcode.de | http://t2-project.org | http://rene.rebe.name
next reply other threads:[~2009-02-02 18:08 UTC|newest]
Thread overview: 19+ messages / expand[flat|nested] mbox.gz Atom feed top
2009-02-02 18:08 Rene Rebe [this message]
2009-02-02 18:15 ` [Qemu-devel] [PATCH] Add multi-boot kernel loading support Nathan Froyd
2009-02-02 18:17 ` Alexander Graf
2009-02-02 18:25 ` Nathan Froyd
2009-02-02 22:42 ` Anthony Liguori
2009-02-03 8:15 ` Rene Rebe
2009-02-03 8:23 ` Alexander Graf
2009-02-03 8:27 ` Rene Rebe
2009-02-03 8:25 ` Alexander Graf
2009-02-03 8:29 ` Rene Rebe
2009-02-03 10:53 ` Jamie Lokier
2009-02-03 11:12 ` Alexander Graf
2009-02-03 12:58 ` Kevin Wolf
2009-02-03 13:57 ` Rene Rebe
2009-02-03 14:31 ` Kevin Wolf
2009-02-04 10:36 ` Rene Rebe
2009-02-04 13:05 ` Kevin Wolf
2009-02-03 13:01 ` Paul Brook
2009-02-03 14:00 ` Rene Rebe
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=49873680.2040603@exactcode.de \
--to=rene@exactcode.de \
--cc=agraf@suse.de \
--cc=qemu-devel@nongnu.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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.