All of lore.kernel.org
 help / color / mirror / Atom feed
From: Akio Takebe <takebe_akio@jp.fujitsu.com>
To: Keir Fraser <keir.fraser@eu.citrix.com>,
	xen-devel <xen-devel@lists.xensource.com>
Cc: Akio Takebe <takebe_akio@jp.fujitsu.com>
Subject: Re: [Patch][2/2][BIOS] Support BCV table
Date: Fri, 27 Mar 2009 19:31:22 +0900	[thread overview]
Message-ID: <8DC9AEC72C5177takebe_akio@jp.fujitsu.com> (raw)
In-Reply-To: <8CC9AEC60A1E3Ctakebe_akio@jp.fujitsu.com>

[-- Attachment #1: Mail message body --]
[-- Type: text/plain, Size: 2551 bytes --]

>>On 27/03/2009 00:48, "Akio Takebe" <takebe_akio@jp.fujitsu.com> wrote:
>>
>>>> You want qemu mucking about with the hvm_info_table? I don't think so.
>>>> You'll have to consider an approach which doesn't touch qemu - you have 
>>>> some
>>>> time anyway since this is not going in for 3.4.
>>> I didn't want to modify qemu, but virtual slot is decided in qemu.
>>> Most of my patch modify hw/pass-through.c
>>> Because hw/pass-though.c is used by only xen,
>>> I though it was accectable to modify it.
>>> So I modified qemu involuntarily. I'm sorry.
>>> If we don't modify qemu, we need to see xenstore and so on
>>> from hvmloader. Do you have any idea?
>>
>>I may be missing some of the motivation and higher-level design, which you
>>may have to describe. I'm not really sure what the whole patchset was
>>actually for and why we'd want it.
>>
>I have two problems.
>1. We cannot load many optionROM
>  In the case of a native PnP BIOS, it load a optionROM and
>  try to initialize their device, then it can free unnecessary ROM 
>  memory. So a native BIOS can load many optionROM.
>  But in the case of xen, optionROMs are loaded in hvmloader.
>  So we cannot free unnecessary memory.
>  Current hvmloader try to load all of optionROM, But if shadow memory
>  doesn't have enough space, it stop loading optionROM.
>  So I wanted to load some necessary optionROM for booting.
>  As the side effect, the patch make booting faster if you don't want to
>  boot from pass-through device.
>  I think it is not important problem.
>  We can configure bootable devices with early number of vslot.
>  
>2. We cannot retry the next drive of HDD type.
>  rombios try to boot from only 0x80 drive.
>  So rombios cannot retry to boot with other drives.
>  I want to boot from other drive.
>  It is useful when the first drive(0x80) broke.
>  Also if acceptable, I want to implement interactive boot key
>  for pass-through device.
>
>>But, for example, why not specify the vendor:dev identifier via
>>hvm_info_table, rather than specifying the vslot?
>Oh, I didn't have the idea. I'll try it.
>
I don't try the idea of vendor:dev id, but I made a patch(bcv.v2.patch)
adding the feature of retrying to boot with the next drives.
What do you think about this patch?
This patch doesn't add any new syntax, just add the retry feature.

Also I made another patch(support_interactive_boot_for_bcv.patch).
It allows user to select a bootable pass-through device with F12.
support_interactive_boot_for_bcv.patch depends on bcv.v2.patch.

Best Regards,

Akio Takebe

[-- Attachment #2: bcv.v2.patch --]
[-- Type: application/octet-stream, Size: 9943 bytes --]

diff -r 0477f9061c8a tools/firmware/rombios/rombios.c
--- a/tools/firmware/rombios/rombios.c	Fri Mar 20 17:42:46 2009 +0000
+++ b/tools/firmware/rombios/rombios.c	Fri Mar 27 13:56:30 2009 +0900
@@ -187,18 +187,27 @@
 #define EBDA_SIZE          1              // In KiB
 #define BASE_MEM_IN_K   (640 - EBDA_SIZE)
 
-/* 256 bytes at 0x9ff00 -- 0x9ffff is used for the IPL boot table. */
+/* 176 bytes at 0x9ff00 -- 0x9ffaf is used for the IPL boot table. */
 #define IPL_TABLE_OFFSET     0x0300  /* offset from EBDA */
 #define IPL_TABLE_ENTRIES    8
 #define IPL_COUNT_OFFSET     0x0380  /* u16: number of valid table entries */
 #define IPL_SEQUENCE_OFFSET  0x0382  /* u16: next boot device */
 #define IPL_BOOTFIRST_OFFSET 0x0384  /* u16: user selected device */
-#define IPL_SIZE             0xff
+#define IPL_SIZE             0xaf
 #define IPL_TYPE_FLOPPY      0x01
 #define IPL_TYPE_HARDDISK    0x02
 #define IPL_TYPE_CDROM       0x03
 #define IPL_TYPE_BEV         0x80
 
+#define IPL_TYPE_BCV         IPL_TYPE_HARDDISK
+
+/* 80 bytes at 0x9ffb0 -- 0x9ffff is used for the BCV boot table. */
+#define BCV_TABLE_OFFSET     0x03b0  /* offset from EBDA */
+#define BCV_TABLE_ENTRIES    4
+#define BCV_COUNT_OFFSET     0x03f0  /* u16: number of valid table entries */
+#define BCV_SEQUENCE_OFFSET  0x03f2  /* u16: next boot device */
+#define BCV_BOOTFIRST_OFFSET 0x03f4  /* u16: user selected device */
+#define BCV_SIZE             0x4f
 
   // Sanity Checks
 #if BX_USE_ATADRV && BX_CPU<3
@@ -2064,14 +2073,65 @@
 
 static char drivetypes[][10]={"", "Floppy","Hard Disk","CD-Rom", "Network"};
 
+/* initialize BCV talbe. BCV and IPL table are the same structure. */
 static void
-init_boot_vectors()
+init_boot_connection_vector()
 {
   ipl_entry_t e;
   Bit16u count = 0;
   Bit16u ss = get_SS();
   Bit16u ebda_seg = read_word(0x0040, 0x000E);
 
+  /* Clear out the BCV table. */
+  memsetb(ebda_seg, BCV_TABLE_OFFSET, 0, BCV_SIZE);
+
+  /* User selected device not set */
+  write_word(ebda_seg, BCV_BOOTFIRST_OFFSET, 0xFFFF);
+
+  /* First HDD */
+//  e.type = IPL_TYPE_HARDDISK; e.flags = 0; e.vector = 0; e.description = 0; e.reserved = 0;
+//  memcpyb(ebda_seg, BCV_TABLE_OFFSET + count * sizeof (e), ss, &e, sizeof (e));
+//  count++;
+
+  /* Remember how many devices we have */
+  write_word(ebda_seg, BCV_COUNT_OFFSET, count);
+  /* Not tried booting anything yet */
+  write_word(ebda_seg, BCV_SEQUENCE_OFFSET, 0xFFFF);
+}
+
+static Bit16u
+get_bcv_count()
+{
+  Bit16u count;
+  Bit16u ss = get_SS();
+  Bit16u ebda_seg = read_word(0x0040, 0x000E);
+  count = read_word(ebda_seg, BCV_COUNT_OFFSET);
+  return count;
+}
+
+static Bit8u
+get_bcv_entry(i, e)
+Bit16u i; ipl_entry_t *e;
+{
+  Bit16u count;
+  Bit16u ss = get_SS();
+  Bit16u ebda_seg = read_word(0x0040, 0x000E);
+  /* Get the count of boot devices, and refuse to overrun the array */
+  count = read_word(ebda_seg, BCV_COUNT_OFFSET);
+  if (i > count) return 0;
+  /* OK to read this device */
+  memcpyb(ss, e, ebda_seg, BCV_TABLE_OFFSET + i * sizeof (*e), sizeof (*e));
+  return 1;
+}
+
+static void
+init_boot_vectors()
+{
+  ipl_entry_t e;
+  Bit16u count = 0;
+  Bit16u ss = get_SS();
+  Bit16u ebda_seg = read_word(0x0040, 0x000E);
+
   /* Clear out the IPL table. */
   memsetb(ebda_seg, IPL_TABLE_OFFSET, 0, IPL_SIZE);
 
@@ -2106,13 +2166,19 @@
 Bit16u i; ipl_entry_t *e;
 {
   Bit16u count;
+  Bit16u j = i;
   Bit16u ss = get_SS();
   Bit16u ebda_seg = read_word(0x0040, 0x000E);
+
   /* Get the count of boot devices, and refuse to overrun the array */
   count = read_word(ebda_seg, IPL_COUNT_OFFSET);
-  if (i >= count) return 0;
+  if (j >= count) return 0;
+
+  /* Translate from CMOS runes to an IPL table offset by subtracting 1 */
+  j--;
+
   /* OK to read this device */
-  memcpyb(ss, e, ebda_seg, IPL_TABLE_OFFSET + i * sizeof (*e), sizeof (*e));
+  memcpyb(ss, e, ebda_seg, IPL_TABLE_OFFSET + j * sizeof (*e), sizeof (*e));
   return 1;
 }
 
@@ -2204,7 +2270,7 @@
   type = e->type;
   /* NIC appears as type 0x80 */
   if (type == IPL_TYPE_BEV) type = 0x4;
-  if (type == 0 || type > 0x4) BX_PANIC("Bad drive type\n");
+  if (type == 0 || type > 0x4) BX_PANIC("Bad drive type(%d)\n", type);
   printf("Booting from %s", drivetypes[type]);
   /* print product string if BEV */
   if (type == 4 && e->description != 0) {
@@ -2217,6 +2283,22 @@
   printf("...\n");
 }
 
+void
+print_bcv_device(bcv, bootdrv)
+  ipl_entry_t *bcv;
+  Bit8u  bootdrv;
+{
+  char description[33];
+  Bit16u ss = get_SS();
+
+  if (bcv->description != 0)
+  {
+    memcpyb(ss, &description, (Bit16u)(bcv->description >> 16), (Bit16u)(bcv->description & 0xffff), 32);
+    description[32] = 0;
+    printf(" [%S]", ss, description);
+  }
+  printf(" bootdrv[0x%x]\n", bootdrv);
+}
 //--------------------------------------------------------------------------
 // print_boot_failure
 //   displays the reason why boot failed
@@ -8207,8 +8289,12 @@
   Bit16u bootip;
   Bit16u status;
   Bit16u bootfirst;
+  Bit16u bcv_count;
+  Bit16u retry_count;
+  Bit16u hdcount, ata_hdcount;
 
   ipl_entry_t e;
+  ipl_entry_t bcv;
 
   // if BX_ELTORITO_BOOT is not defined, old behavior
   //   check bit 5 in CMOS reg 0x2d.  load either 0x00 or 0x80 into DL
@@ -8245,8 +8331,6 @@
     write_word(ebda_seg, IPL_SEQUENCE_OFFSET, 0xFFFF);
   } else if (bootdev == 0) BX_PANIC("No bootable device.\n");
 
-  /* Translate from CMOS runes to an IPL table offset by subtracting 1 */
-  bootdev -= 1;
 #else
   if (seq_nr ==2) BX_PANIC("No more boot devices.");
   if (!!(inb_cmos(0x2d) & 0x20) ^ (seq_nr == 1))
@@ -8255,7 +8339,6 @@
   else
     bootdev = 0x01;
 #endif
-
   /* Read the boot device from the IPL table */
   if (get_boot_vector(bootdev, &e) == 0) {
     BX_INFO("Invalid boot device (0x%x)\n", bootdev);
@@ -8269,8 +8352,32 @@
   switch(e.type) {
   case IPL_TYPE_FLOPPY: /* FDD */
   case IPL_TYPE_HARDDISK: /* HDD */
-
+    ata_hdcount = read_byte(ebda_seg, &EbdaData->ata.hdcount);
+    hdcount     = read_byte(0x40,0x75);
+    bcv_count   = hdcount - ata_hdcount;
+    if ( bcv_count != get_bcv_count() || bcv_count < 0 )
+        BX_PANIC("Invaild hdcount(%d) ata_hdcount=%d bcv_count=%d\n",
+                  hdcount, ata_hdcount, get_bcv_count());
+
+    BX_INFO("hdcount(%d) ata_hdcount=%d bcv_count=%d\n",
+            hdcount, ata_hdcount, get_bcv_count());
+    if ( hdcount == 0 && e.type == IPL_TYPE_HARDDISK) {
+      print_boot_failure(e.type, 1);
+      return;
+    }
+    retry_count = hdcount;
     bootdrv = (e.type == IPL_TYPE_HARDDISK) ? 0x80 : 0x00;
+retry_type_hdd:
+    if ( retry_count == 0 ) {
+      return; /* boot fail with all bcv devices */
+    } else {
+      bootdrv = bootdrv + hdcount - retry_count;
+      if ( ata_hdcount == 0 || bootdrv > 0x80 ) {
+        get_bcv_entry(bcv_count - retry_count, &bcv);
+        print_bcv_device(&bcv, bootdrv);
+      }
+    }
+
     bootseg = 0x07c0;
     status = 0;
 
@@ -8306,7 +8413,8 @@
 
     if (status != 0) {
       print_boot_failure(e.type, 1);
-      return;
+      retry_count--;
+      goto retry_type_hdd;
     }
 
     /* Always check the signature on a HDD boot sector; on FDD, only do
@@ -8314,7 +8422,8 @@
     if ((e.type != IPL_TYPE_FLOPPY) || !((inb_cmos(0x38) & 0x01))) {
       if (read_word(bootseg,0x1fe) != 0xaa55) {
         print_boot_failure(e.type, 0);
-        return;
+        retry_count--;
+        goto retry_type_hdd;
       }
     }
 
@@ -8326,6 +8435,7 @@
     /* Canonicalize bootseg:bootip */
     bootip = (bootseg & 0x0fff) << 4;
     bootseg &= 0xf000;
+    printf("boot from [%x:%x]\n", bootseg, bootip);
   break;
 
 #if BX_ELTORITO_BOOT
@@ -10585,7 +10695,31 @@
   cli           ;; In case expansion ROM BIOS turns IF on
   add  sp, #2   ;; Pop offset value
   pop  cx       ;; Pop seg value (restore CX)
-  jmp   no_bev
+
+  ;; Found BCV device. Recode it in BCV table.
+  mov  bx, 0x001a   ;; 0x1A is the offset into ROM header that contains...
+  mov  di, 0x10[bx]            ;; Pointer to the product name string or zero if none
+
+  xor  bx, bx
+  mov  ds, bx
+  mov  bx, word ptr [0x40E]    ;; EBDA segment
+  mov  ds, bx                  ;; Go to the segment where the BCV table lives
+  mov  bx, BCV_COUNT_OFFSET    ;; Read the number of entries so far
+  cmp  bx, #BCV_TABLE_ENTRIES
+  je   no_bev                  ;; Get out if the table is full
+  shl  bx, #0x4                ;; Turn count into offset (entries are 16 bytes)
+  mov  BCV_TABLE_OFFSET+0[bx], #IPL_TYPE_BCV ;; This entry is a BCV device
+  mov  BCV_TABLE_OFFSET+6[bx], cx            ;; Build a far pointer from the segment...
+  mov  BCV_TABLE_OFFSET+4[bx], ax            ;; and the offset
+  cmp  di, #0x0000
+  je   no_prod_str1
+  mov  BCV_TABLE_OFFSET+0xA[bx], cx          ;; segment to descritption
+  mov  BCV_TABLE_OFFSET+0x8[bx], di          ;; pointer to descritption
+no_prod_str1:
+  shr  bx, #0x4                ;; Turn the offset back into a count
+  inc  bx                      ;; We have one more entry now
+  mov  BCV_COUNT_OFFSET, bx    ;; Remember that.
+  jmp  no_bev
 
 no_bcv:
   mov  ax, 0x1a[bx] ;; 0x1A is also the offset into the expansion header of...
@@ -10606,10 +10740,10 @@
   mov  IPL_TABLE_OFFSET+6[bx], cx            ;; Build a far pointer from the segment...
   mov  IPL_TABLE_OFFSET+4[bx], ax            ;; and the offset
   cmp  di, #0x0000
-  je   no_prod_str
+  je   no_prod_str2
   mov  0xA[bx], cx             ;; Build a far pointer from the segment...
   mov  8[bx], di               ;; and the offset
-no_prod_str:
+no_prod_str2:
   shr  bx, #0x4                ;; Turn the offset back into a count
   inc  bx                      ;; We have one more entry now
   mov  IPL_COUNT_OFFSET, bx    ;; Remember that.
@@ -11041,6 +11175,7 @@
 #endif
 
   call _init_boot_vectors
+  call _init_boot_connection_vector
 
   mov  cx, #(OPTIONROM_PHYSICAL_ADDRESS >> 4)  ;; init option roms
   mov  ax, #(OPTIONROM_PHYSICAL_END >> 4)

[-- Attachment #3: support_interactive_boot_for_bcv.patch --]
[-- Type: application/octet-stream, Size: 4559 bytes --]

diff -r 910b54a7202d tools/firmware/rombios/rombios.c
--- a/tools/firmware/rombios/rombios.c	Fri Mar 27 13:58:04 2009 +0900
+++ b/tools/firmware/rombios/rombios.c	Fri Mar 27 17:55:37 2009 +0900
@@ -942,6 +942,7 @@ static void           interactive_bootke
 static void           interactive_bootkey();
 static void           print_bios_banner();
 static void           print_boot_device();
+static void           print_bcv_device();
 static void           print_boot_failure();
 static void           print_cdromboot_failure();
 
@@ -2187,10 +2188,15 @@ interactive_bootkey()
 interactive_bootkey()
 {
   ipl_entry_t e;
+  ipl_entry_t bcv;
   Bit16u count;
   char description[33];
   Bit8u scan_code;
   Bit8u i;
+  Bit16u bootdrv;
+  Bit16u bcv_count;
+  Bit16u retry_count;
+  Bit16u hdcount, ata_hdcount;
   Bit16u ss = get_SS();
   Bit16u valid_choice = 0;
   Bit16u ebda_seg = read_word(0x0040, 0x000E);
@@ -2216,8 +2222,8 @@ interactive_bootkey()
       switch(e.type)
       {
         case IPL_TYPE_FLOPPY:
+        case IPL_TYPE_CDROM:
         case IPL_TYPE_HARDDISK:
-        case IPL_TYPE_CDROM:
           printf("%s\n", drivetypes[e.type]);
           break;
         case IPL_TYPE_BEV:
@@ -2233,6 +2239,18 @@ interactive_bootkey()
       }
     }
 
+    ata_hdcount = read_byte(ebda_seg, &EbdaData->ata.hdcount);
+    hdcount     = read_byte(0x40,0x75);
+    bcv_count   = hdcount - ata_hdcount;
+    for (i = 0; i < bcv_count; i++){
+      bootdrv = 0x80 + i + ata_hdcount;
+      get_bcv_entry(i, &bcv);
+      printf("%d. ", i+count+1);
+      printf("Attached PCI device:");
+      print_bcv_device(&bcv, bootdrv);
+      
+    }
+
     count++;
     while (!valid_choice) {
       scan_code = get_keystroke();
@@ -2240,12 +2258,19 @@ interactive_bootkey()
       {
         valid_choice = 1;
       }
-      else if (scan_code <= count)
+      else if (scan_code <= count + bcv_count)
       {
         valid_choice = 1;
         scan_code -= 1;
-        /* Set user selected device */
-        write_word(ebda_seg, IPL_BOOTFIRST_OFFSET, scan_code);
+        if (scan_code < count)
+        {
+          /* Set user selected device */
+          write_word(ebda_seg, IPL_BOOTFIRST_OFFSET, scan_code);
+        } else {
+          /* Set user selected device */
+          write_word(ebda_seg, IPL_BOOTFIRST_OFFSET, scan_code);
+          write_word(ebda_seg, BCV_BOOTFIRST_OFFSET, scan_code - count);
+        }
       }
     }
 
@@ -8288,7 +8313,9 @@ Bit16u seq_nr;
   Bit16u bootseg;
   Bit16u bootip;
   Bit16u status;
-  Bit16u bootfirst;
+  Bit16u ipl_bootfirst;
+  Bit16u ipl_count;
+  Bit16u bcv_bootfirst;
   Bit16u bcv_count;
   Bit16u retry_count;
   Bit16u hdcount, ata_hdcount;
@@ -8322,11 +8349,18 @@ Bit16u seq_nr;
   bootdev &= 0xf;
 
   /* Read user selected device */
-  bootfirst = read_word(ebda_seg, IPL_BOOTFIRST_OFFSET);
-  if (bootfirst != 0xFFFF) {
-    bootdev = bootfirst;
+  ipl_bootfirst = read_word(ebda_seg, IPL_BOOTFIRST_OFFSET);
+  bcv_bootfirst = read_word(ebda_seg, BCV_BOOTFIRST_OFFSET);
+  if (ipl_bootfirst != 0xFFFF) {
+    ipl_count = read_word(ebda_seg, IPL_COUNT_OFFSET);
+    if ( ipl_bootfirst > ipl_count ) {
+      bootdev = 2; /* It's a attached PCI. convert to HDD type*/
+    } else {
+      bootdev = ipl_bootfirst;
+    }
     /* User selected device not set */
     write_word(ebda_seg, IPL_BOOTFIRST_OFFSET, 0xFFFF);
+    write_word(ebda_seg, BCV_BOOTFIRST_OFFSET, 0xFFFF);
     /* Reset boot sequence */
     write_word(ebda_seg, IPL_SEQUENCE_OFFSET, 0xFFFF);
   } else if (bootdev == 0) BX_PANIC("No bootable device.\n");
@@ -8367,15 +8401,27 @@ Bit16u seq_nr;
     }
     retry_count = hdcount;
     bootdrv = (e.type == IPL_TYPE_HARDDISK) ? 0x80 : 0x00;
+
 retry_type_hdd:
-    if ( retry_count == 0 ) {
+    if ( retry_count == 0 ) 
       return; /* boot fail with all bcv devices */
-    } else {
+
+    if (ipl_bootfirst != 0xFFFF ) {
+      retry_count = 1;
+      if (bcv_bootfirst != 0xFFFF ) {
+        bootdrv = 0x80 + bcv_bootfirst + ata_hdcount;
+      } else {
+        if ( ata_hdcount == 0) {
+          print_boot_failure(e.type, 1);
+          return;
+        }
+      }
+    } else
       bootdrv = bootdrv + hdcount - retry_count;
-      if ( ata_hdcount == 0 || bootdrv > 0x80 ) {
-        get_bcv_entry(bcv_count - retry_count, &bcv);
-        print_bcv_device(&bcv, bootdrv);
-      }
+
+    if ( ata_hdcount == 0 || bootdrv > 0x80 ) {
+      get_bcv_entry(bcv_count - retry_count, &bcv);
+      print_bcv_device(&bcv, bootdrv);
     }
 
     bootseg = 0x07c0;

[-- Attachment #4: Type: text/plain, Size: 138 bytes --]

_______________________________________________
Xen-devel mailing list
Xen-devel@lists.xensource.com
http://lists.xensource.com/xen-devel

  reply	other threads:[~2009-03-27 10:31 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2009-03-26 13:40 [Patch][0/2][BIOS] Support BCV table Akio Takebe
2009-03-26 13:42 ` Akio Takebe
2009-03-26 13:46   ` [Patch][1/2][BIOS] " Akio Takebe
2009-03-26 13:43 ` [Patch][2/2][BIOS] " Akio Takebe
2009-03-26 14:36   ` Keir Fraser
2009-03-27  0:48     ` Akio Takebe
2009-03-27  4:44       ` Akio Takebe
2009-03-27  9:02         ` Keir Fraser
2009-03-27  9:00       ` Keir Fraser
2009-03-27 10:23         ` Akio Takebe
2009-03-27 10:31           ` Akio Takebe [this message]
2009-03-27 13:49             ` Keir Fraser
2009-03-27 14:22               ` Akio Takebe
2009-03-30  6:50             ` Akio Takebe

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=8DC9AEC72C5177takebe_akio@jp.fujitsu.com \
    --to=takebe_akio@jp.fujitsu.com \
    --cc=keir.fraser@eu.citrix.com \
    --cc=xen-devel@lists.xensource.com \
    /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.