qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: David Woodhouse <dwmw2@infradead.org>
To: "Daniel P. Berrangé" <berrange@redhat.com>,
	"Dinah B" <dinahbaum123@gmail.com>
Cc: "Alex Bennée" <alex.bennee@linaro.org>, qemu-devel@nongnu.org
Subject: Re: Adopting abandoned patch?
Date: Tue, 28 Feb 2023 15:12:42 +0000	[thread overview]
Message-ID: <998ee91a9e08b7e7a1797e2736a9aaa1f9e5a4a4.camel@infradead.org> (raw)
In-Reply-To: <Y/24XKwyFcn56LV5@redhat.com>

[-- Attachment #1: Type: text/plain, Size: 10157 bytes --]

On Tue, 2023-02-28 at 08:16 +0000, Daniel P. Berrangé wrote:
> On Mon, Feb 27, 2023 at 04:34:12PM -0500, Dinah B wrote:
> > It looks like the author didn't include a "Signed off" in their patch draft
> > and it doesn't look like Debian qemu-kvm maintainers ever merged it.
> > Does this change the patch adoption process?
> > 
> > On Mon, Feb 27, 2023 at 4:23 PM Dinah B <dinahbaum123@gmail.com> wrote:
> > 
> > > Thanks, here's the original patch:
> > > https://bugs.debian.org/cgi-bin/bugreport.cgi?att=2;bug=621529;filename=multiboot2.patch;msg=15
> 
> That is unfortunate. That patch is very large and complex, so I don't
> think we'd be willing to take it without a Signed-off-by from the
> original author.
> 
> It is quite old, but try emailing the original author, you might
> get lucky and find their email addr still works

We added multiboot2 support to kexec-tools which you are welcome to
take code from.

I see there's an option ROM in the patch you referenced; I'm not quite
sure how that gets used, but here's one you can have that we use for
loading the Xen shim (in a KVM-pretending-to-be-Xen-HVM guest) to
launch Xen PV guests.

We do the multiboot processing in the VMM, load the executable and
modules into guest RAM, then just install an oprom which will launch it
when the BIOS invokes INT 18h.

Feel free to use this in QEMU under GPLv2 if it's useful. I'll be happy
to add a signed-off-by if you work it into individual patches, but for
now,
Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>

/******************************************************************************
 *
 * PV Shim as an expansion ROM.
 *
 * We already have an ELF and Multiboot loader in Brimstone, which is
 * precisely what Xen needs. We can't *just* go directly into the Xen
 * multiboot entry point though, since it does expect *some* form of
 * BIOS to be present.
 *
 * So we perform the Elf/Multiboot load, placing Xen where it wants to
 * be at 0x200000, and then allow xenloader/ROMBIOS to initialise as
 * normal. But we install an 'expansion ROM' at 0xE0000 which will
 * register itself as a bootable target (BEV), so ROMBIOS will invoke
 * it instead of trying to boot from the disk once everything's set up.
 *
 * When invoked, the BEV entry point just switches to 32-bit mode,
 * loads the two registers with the Multiboot magic and pointer to the
 * data structure that Brimstone set up earlier, and jumps to Xen's
 * entry point.
 */

#define OPROM_SIG 0xaa55;
struct oprom_hdr {
	uint16_t sig;
	uint8_t romlen; /* in 512-byte blocks */
	uint8_t entry[21];
	uint16_t pci;
	uint16_t expansion;
};
static_assert(sizeof(struct oprom_hdr) == 28);

#define PNP_SIG 0x506e5024 /* '$PnP' */
struct pnp_hdr {
	uint32_t sig;
	uint8_t rev;
	uint8_t len; /* in 16-byte counts */
	uint16_t next_header;
	uint8_t reserved1;
	uint8_t checksum;
	uint32_t device_id;
	uint16_t mfr_name;
	uint16_t product_name;
	uint8_t device_type_code[3];
	uint8_t device_indicators;
	uint16_t bcv;
	uint16_t bev;
	uint16_t reserved2;
	uint16_t resource_vector;
};
static_assert(sizeof(struct pnp_hdr) == 32);

union pvshim_rom {
	uint8_t bytes[512];
	struct {
		struct oprom_hdr oprom;
		struct pnp_hdr pnp;
		uint32_t mbi_gpa;
		uint16_t idt[3];
		uint16_t gdt_len;
		uint32_t gdt_addr;
		uint64_t gdt[3];
		uint8_t code16[64];
		uint8_t code32[64];
	};
};
static_assert(!(sizeof(union pvshim_rom) % 512));

/* The UMB location at which we install our expansion ROM */
#define PVSHIM_ROM_ADDR 0xe0000

/* The fixed entry point of PV Shim itself */
#define PVSHIM_ENTRY_ADDR 0x200000

/* Segments used by the GDT that we manually create */
#define CODE32_SEG 0x08
#define DATA32_SEG 0x10

/* Assembler trampoline. There are a whole 16 instructions here;
 * it would have been theoretically possible to build them from
 * a .S file or inline asm, and jump through various hoops to
 * get the relocations and internal/external references to work.
 * Or we could just emit them as bytes; life's too short. */

/* Emit a word or dword as consecutive bytes in little-endian order */
#define le16word(x) static_cast<uint8_t>((x) & 0xff), static_cast<uint8_t>((x) >> 8)
#define le32dword(x) le16word(x), le16word((x)>>16)

/* Helper macro to emit the offset of a field in the pvshim_rom
 * structure, as two bytes */
#define le16ofs(field) le16word(offsetof(union pvshim_rom, field))

/* ROMBIOS / CMOS boot sequence configuration */
#define PORT_CMOS_INDEX		0x70
#define PORT_CMOS_DATA		0x71

#define CMOS_BOOT_DEVICE_1_2	0x3d
#define CMOS_BOOT_DEVICE_BEV	0x04

/* Initial setup entry point of the PV Shim option ROM.
 * Just set the CMOS boot order to boot from the BEV. */
static uint8_t pvshim_init[] =
{
	0xb0, CMOS_BOOT_DEVICE_1_2,			// mov $CMOS_BOOT_DEVICE_1_2, %al
	0xe6, PORT_CMOS_INDEX,				// out %al, $PORT_CMOS_INDEX
	0xb0, CMOS_BOOT_DEVICE_BEV,			// mov $CMOS_BOOT_DEVICE_BEV, %al
	0xe6, PORT_CMOS_DATA,				// out %al, $PORT_CMOS_DATA
	0xcb						// lret
};

/* This is the 16-bit BEV entry point of the PV Shim "option ROM",
 * invoked when ROMBIOS is done with its setup, and it's time to
 * start PV Shim. */
static uint8_t pvshim_trampoline16[] =
{
	/* The Multiboot handoff expects the multiboot_info struct in %ebx.
	 * Do that now from 16-bit mode so that we don't have to mess with
	 * %rip-relative addressing to load it. */
	0x2e, 0x66, 0x8b, 0x1e, le16ofs(mbi_gpa),	// mov %cs:mbi_gpa, %ebx

	/* Clear interrupts, load the IDT and GDT that were set up for us. */
	0xfa,						// cli
	0x2e, 0x0f, 0x01, 0x1e, le16ofs(idt),		// lidtw %cs:idt
	0x2e, 0x0f, 0x01, 0x16, le16ofs(gdt_len),	// lgdtw %cs:gdt_len

	/* Set CR0.PE to enter protected mode. */
	0x31, 0xc0,					// xor %ax,%ax
	0x40,						// inc %ax
	0x0f, 0x01, 0xf0,				// lmsw %ax

	/* Jump to the 32-bit mode trampoline. */
	0x66, 0xea, le16ofs(code32), le16word(PVSHIM_ROM_ADDR >> 16),
	le16word(CODE32_SEG)				// ljmpl $CODE32_SEG,code32
};

static uint8_t pvshim_trampoline32[] =
{
	/* Load the 32-bit data segment into the segment registers */
	0xb8, le32dword(DATA32_SEG),			// mov $DATA32_SEG, %eax
	0x8e, 0xd8,					// mov %eax, %ds
	0x8e, 0xc0,					// mov %eax, %es
	0x8e, 0xe0,					// mov %eax, %fs
	0x8e, 0xe8,					// mov %eax, %gs
	0x8e, 0xd0,					// mov %eax, %ss

	/* The Multiboot handoff requires the magic value in %eax */
	0xb8, le32dword(MULTIBOOT_BOOTLOADER_MAGIC),	// mov $MULTIBOOT_BOOTLOADER_MAGIC, %eax

	/* Go go go! */
	0xea, le32dword(PVSHIM_ENTRY_ADDR), le16word(CODE32_SEG)
							// ljmpl $CODE32_SEG,$PVSHIM_ENTRY_ADDR
};

static const char *pvshim_cmdline =
	"loglvl=all guest_loglvl=none console=com1 console_timestamps=datems xsave=0 xpti=false";

static void pvshim_load()
{
	service_vm::bz_image_metadata bz_image_metadata;
	std::string pvshim_image_name = "pvshim";
	unique_fd pvshim_fd = service_image_fetch(pvshim_image_name, bz_image_metadata);
	die_on(pvshim_fd < 0, "Failed to open PV Shim image");

	size_t mmap_size = PAGE_ROUND_UP(bz_image_metadata.offset + bz_image_metadata.size);
	void *image = mmap(NULL, mmap_size, PROT_READ, MAP_SHARED, pvshim_fd, 0);
	die_on(image == MAP_FAILED, "failed to mmap PV Shim image");

	unsigned char *bytes = static_cast<unsigned char *>(image) + bz_image_metadata.offset;
	size_t bytes_size = bz_image_metadata.size;

	struct gzip_tail gz_tail;
	unsigned char *gz_bytes = parse_gzip_header(bytes, bytes_size, &gz_tail);
	if (gz_bytes) {
		size_t gz_size = bytes_size - (gz_bytes - bytes);

		/* Impose a sane size limit on the decompressed PV Shim image */
		die_on(gz_tail.size > 4 * MiB, "PV Shim decompressed image size too large (%d bytes)",
		       gz_tail.size);

		void *elf_bytes = malloc(gz_tail.size);
		die_on(!elf_bytes, "Failed to allocate %d bytes for PV Shim decompression",
		       gz_tail.size);

		decompress_gzip_stream(&gz_tail, elf_bytes, gz_bytes, gz_size);
		log_info("Decompressed %d bytes of PV Shim image", gz_tail.size);

		load_multiboot(elf_bytes, gz_tail.size, pvshim_cmdline);
		free(elf_bytes);
	} else {
		/* Not compressed; load it directly */
		load_multiboot(bytes, bytes_size, pvshim_cmdline);
	}
	die_on(munmap(image, mmap_size) != 0, "Failed to munmap PV Shim image data %p", image);

	/* Install an expansion ROM which will invoke it once ROMBIOS has done the setup */
	union pvshim_rom rom = { };

	rom.oprom.sig = OPROM_SIG;
	rom.oprom.romlen = sizeof(rom) / 512;
	rom.oprom.expansion = offsetof(union pvshim_rom, pnp);

	rom.pnp.sig = PNP_SIG;
	rom.pnp.rev = 1;
	rom.pnp.len = sizeof(rom.pnp) / 16;
	rom.pnp.bev = offsetof(union pvshim_rom, code16);

	/* Grab the Multiboot info struct from where load_multiboot() put it */
	rom.mbi_gpa = static_cast<uint32_t>(thread_pvcpu->kvm.regs.rbx);
	die_on(thread_pvcpu->kvm.regs.rip != PVSHIM_ENTRY_ADDR,
	       "PV Shim entry point 0x%x is not expected 0x%x",
	       static_cast<uint32_t>(thread_pvcpu->kvm.regs.rip),
	       PVSHIM_ENTRY_ADDR);

	rom.gdt_len = sizeof(rom.gdt) - 1;
	rom.gdt_addr = PVSHIM_ROM_ADDR + offsetof(union pvshim_rom, gdt);
	rom.gdt[1] = 0x00cf9a000000ffff; /* CODE32_SEG (0x0008) */
	rom.gdt[2] = 0x00cf92000000ffff; /* DATA32_SEG (0x0010) */

	/* Copy assembler code into place */
	static_assert(sizeof(rom.oprom.entry) >= sizeof(pvshim_init));
	memcpy(rom.oprom.entry, pvshim_init, sizeof(pvshim_init));

	static_assert(sizeof(rom.code16) >= sizeof(pvshim_trampoline16));
	memcpy(rom.code16, pvshim_trampoline16, sizeof(pvshim_trampoline16));

	static_assert(sizeof(rom.code32) >= sizeof(pvshim_trampoline32));
	memcpy(rom.code32, pvshim_trampoline32, sizeof(pvshim_trampoline32));

	/* Calculate the OpROM checksum */
	uint8_t sum = 0;
	for (unsigned i = 0; i < sizeof(rom) - 1; i++)
		sum = static_cast<uint8_t>(sum + rom.bytes[i]);
	rom.bytes[sizeof(rom) - 1] = static_cast<uint8_t>(-sum);

	/* Drop into guest memory where ROMBIOS will find it as an OpROM */
	memory_write_region(PVSHIM_ROM_ADDR, &rom, sizeof(rom));
}


[-- Attachment #2: smime.p7s --]
[-- Type: application/pkcs7-signature, Size: 5965 bytes --]

      reply	other threads:[~2023-02-28 15:13 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-02-27  3:29 Adopting abandoned patch? Dinah B
2023-02-27  9:57 ` Alex Bennée
2023-02-27 21:23   ` Dinah B
2023-02-27 21:34     ` Dinah B
2023-02-28  8:16       ` Daniel P. Berrangé
2023-02-28 15:12         ` David Woodhouse [this message]

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=998ee91a9e08b7e7a1797e2736a9aaa1f9e5a4a4.camel@infradead.org \
    --to=dwmw2@infradead.org \
    --cc=alex.bennee@linaro.org \
    --cc=berrange@redhat.com \
    --cc=dinahbaum123@gmail.com \
    --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 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).