diff options
author | José Pekkarinen <jose.pekkarinen@nokia.com> | 2016-05-18 13:18:31 +0300 |
---|---|---|
committer | José Pekkarinen <jose.pekkarinen@nokia.com> | 2016-05-18 13:42:15 +0300 |
commit | 437fd90c0250dee670290f9b714253671a990160 (patch) | |
tree | b871786c360704244a07411c69fb58da9ead4a06 /qemu/roms/ipxe/src/interface | |
parent | 5bbd6fe9b8bab2a93e548c5a53b032d1939eec05 (diff) |
These changes are the raw update to qemu-2.6.
Collission happened in the following patches:
migration: do cleanup operation after completion(738df5b9)
Bug fix.(1750c932f86)
kvmclock: add a new function to update env->tsc.(b52baab2)
The code provided by the patches was already in the upstreamed
version.
Change-Id: I3cc11841a6a76ae20887b2e245710199e1ea7f9a
Signed-off-by: José Pekkarinen <jose.pekkarinen@nokia.com>
Diffstat (limited to 'qemu/roms/ipxe/src/interface')
28 files changed, 1722 insertions, 97 deletions
diff --git a/qemu/roms/ipxe/src/interface/bofm/bofm.c b/qemu/roms/ipxe/src/interface/bofm/bofm.c index b0e92b27c..545088dc6 100644 --- a/qemu/roms/ipxe/src/interface/bofm/bofm.c +++ b/qemu/roms/ipxe/src/interface/bofm/bofm.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <string.h> diff --git a/qemu/roms/ipxe/src/interface/efi/efi_autoboot.c b/qemu/roms/ipxe/src/interface/efi/efi_autoboot.c index ab0f36541..a9e807e23 100644 --- a/qemu/roms/ipxe/src/interface/efi/efi_autoboot.c +++ b/qemu/roms/ipxe/src/interface/efi/efi_autoboot.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <ipxe/efi/efi.h> #include <ipxe/efi/efi_autoboot.h> diff --git a/qemu/roms/ipxe/src/interface/efi/efi_bofm.c b/qemu/roms/ipxe/src/interface/efi/efi_bofm.c index bdb705196..ea0e15f7f 100644 --- a/qemu/roms/ipxe/src/interface/efi/efi_bofm.c +++ b/qemu/roms/ipxe/src/interface/efi/efi_bofm.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <errno.h> #include <ipxe/bofm.h> diff --git a/qemu/roms/ipxe/src/interface/efi/efi_debug.c b/qemu/roms/ipxe/src/interface/efi/efi_debug.c index d23960140..473803951 100644 --- a/qemu/roms/ipxe/src/interface/efi/efi_debug.c +++ b/qemu/roms/ipxe/src/interface/efi/efi_debug.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** * @file @@ -326,7 +330,7 @@ const char * efi_devpath_text ( EFI_DEVICE_PATH_PROTOCOL *path ) { max_len = ( ( sizeof ( text ) - 1 /* NUL */ ) / 2 /* "xx" */ ); if ( len > max_len ) len = max_len; - base16_encode ( start, len, text ); + base16_encode ( start, len, text, sizeof ( text ) ); return text; } diff --git a/qemu/roms/ipxe/src/interface/efi/efi_file.c b/qemu/roms/ipxe/src/interface/efi/efi_file.c index 2ef3c5734..3715b70bf 100644 --- a/qemu/roms/ipxe/src/interface/efi/efi_file.c +++ b/qemu/roms/ipxe/src/interface/efi/efi_file.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** * @file diff --git a/qemu/roms/ipxe/src/interface/efi/efi_guid.c b/qemu/roms/ipxe/src/interface/efi/efi_guid.c index 52ba58ae4..ab1c91e9f 100644 --- a/qemu/roms/ipxe/src/interface/efi/efi_guid.c +++ b/qemu/roms/ipxe/src/interface/efi/efi_guid.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <ipxe/efi/efi.h> #include <ipxe/efi/Protocol/Arp.h> diff --git a/qemu/roms/ipxe/src/interface/efi/efi_hii.c b/qemu/roms/ipxe/src/interface/efi/efi_hii.c index 834060b54..0ea970e67 100644 --- a/qemu/roms/ipxe/src/interface/efi/efi_hii.c +++ b/qemu/roms/ipxe/src/interface/efi/efi_hii.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdlib.h> #include <stddef.h> diff --git a/qemu/roms/ipxe/src/interface/efi/efi_pci.c b/qemu/roms/ipxe/src/interface/efi/efi_pci.c index 86c781d82..97ea72bb9 100644 --- a/qemu/roms/ipxe/src/interface/efi/efi_pci.c +++ b/qemu/roms/ipxe/src/interface/efi/efi_pci.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdlib.h> #include <errno.h> diff --git a/qemu/roms/ipxe/src/interface/efi/efi_reboot.c b/qemu/roms/ipxe/src/interface/efi/efi_reboot.c index 96638c48e..35919221e 100644 --- a/qemu/roms/ipxe/src/interface/efi/efi_reboot.c +++ b/qemu/roms/ipxe/src/interface/efi/efi_reboot.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** * @file diff --git a/qemu/roms/ipxe/src/interface/efi/efi_snp.c b/qemu/roms/ipxe/src/interface/efi/efi_snp.c index 67fba342e..3dfcc5e16 100644 --- a/qemu/roms/ipxe/src/interface/efi/efi_snp.c +++ b/qemu/roms/ipxe/src/interface/efi/efi_snp.c @@ -32,8 +32,10 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include <ipxe/efi/efi_driver.h> #include <ipxe/efi/efi_strings.h> #include <ipxe/efi/efi_utils.h> +#include <ipxe/efi/efi_watchdog.h> #include <ipxe/efi/efi_snp.h> #include <usr/autoboot.h> +#include <config/general.h> /** List of SNP devices */ static LIST_HEAD ( efi_snp_devices ); @@ -41,6 +43,39 @@ static LIST_HEAD ( efi_snp_devices ); /** Network devices are currently claimed for use by iPXE */ static int efi_snp_claimed; +/* Downgrade user experience if configured to do so + * + * The default UEFI user experience for network boot is somewhat + * excremental: only TFTP is available as a download protocol, and if + * anything goes wrong the user will be shown just a dot on an + * otherwise blank screen. (Some programmer was clearly determined to + * win a bet that they could outshine Apple at producing uninformative + * error messages.) + * + * For comparison, the default iPXE user experience provides the + * option to use protocols designed more recently than 1980 (such as + * HTTP and iSCSI), and if anything goes wrong the the user will be + * shown one of over 1200 different error messages, complete with a + * link to a wiki page describing that specific error. + * + * We default to upgrading the user experience to match that available + * in a "legacy" BIOS environment, by installing our own instance of + * EFI_LOAD_FILE_PROTOCOL. + * + * Note that unfortunately we can't sensibly provide the choice of + * both options to the user in the same build, because the UEFI boot + * menu ignores the multitude of ways in which a network device handle + * can be described and opaquely labels both menu entries as just "EFI + * Network". + */ +#ifdef EFI_DOWNGRADE_UX +static EFI_GUID dummy_load_file_protocol_guid = { + 0x6f6c7323, 0x2077, 0x7523, + { 0x6e, 0x68, 0x65, 0x6c, 0x70, 0x66, 0x75, 0x6c } +}; +#define efi_load_file_protocol_guid dummy_load_file_protocol_guid +#endif + /** * Set EFI SNP mode state * @@ -98,28 +133,43 @@ static void efi_snp_set_mode ( struct efi_snp_device *snpdev ) { } /** + * Flush transmit ring and receive queue + * + * @v snpdev SNP device + */ +static void efi_snp_flush ( struct efi_snp_device *snpdev ) { + struct io_buffer *iobuf; + struct io_buffer *tmp; + + /* Reset transmit completion ring */ + snpdev->tx_prod = 0; + snpdev->tx_cons = 0; + + /* Discard any queued receive buffers */ + list_for_each_entry_safe ( iobuf, tmp, &snpdev->rx, list ) { + list_del ( &iobuf->list ); + free_iob ( iobuf ); + } +} + +/** * Poll net device and count received packets * * @v snpdev SNP device */ static void efi_snp_poll ( struct efi_snp_device *snpdev ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; struct io_buffer *iobuf; - unsigned int before = 0; - unsigned int after = 0; - unsigned int arrived; - /* We have to report packet arrivals, and this is the easiest - * way to fake it. - */ - list_for_each_entry ( iobuf, &snpdev->netdev->rx_queue, list ) - before++; + /* Poll network device */ netdev_poll ( snpdev->netdev ); - list_for_each_entry ( iobuf, &snpdev->netdev->rx_queue, list ) - after++; - arrived = ( after - before ); - snpdev->rx_count_interrupts += arrived; - snpdev->rx_count_events += arrived; + /* Retrieve any received packets */ + while ( ( iobuf = netdev_rx_dequeue ( snpdev->netdev ) ) ) { + list_add_tail ( &iobuf->list, &snpdev->rx ); + snpdev->interrupts |= EFI_SIMPLE_NETWORK_RECEIVE_INTERRUPT; + bs->SignalEvent ( &snpdev->snp.WaitForPacket ); + } } /** @@ -221,6 +271,7 @@ efi_snp_reset ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, BOOLEAN ext_verify ) { netdev_close ( snpdev->netdev ); efi_snp_set_state ( snpdev ); + efi_snp_flush ( snpdev ); if ( ( rc = netdev_open ( snpdev->netdev ) ) != 0 ) { DBGC ( snpdev, "SNPDEV %p could not reopen %s: %s\n", @@ -251,6 +302,7 @@ efi_snp_shutdown ( EFI_SIMPLE_NETWORK_PROTOCOL *snp ) { netdev_close ( snpdev->netdev ); efi_snp_set_state ( snpdev ); + efi_snp_flush ( snpdev ); return 0; } @@ -446,20 +498,22 @@ efi_snp_nvdata ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, BOOLEAN read, * * @v snp SNP interface * @v interrupts Interrupt status, or NULL - * @v txbufs Recycled transmit buffer address, or NULL + * @v txbuf Recycled transmit buffer address, or NULL * @ret efirc EFI status code */ static EFI_STATUS EFIAPI efi_snp_get_status ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, - UINT32 *interrupts, VOID **txbufs ) { + UINT32 *interrupts, VOID **txbuf ) { struct efi_snp_device *snpdev = container_of ( snp, struct efi_snp_device, snp ); DBGC2 ( snpdev, "SNPDEV %p GET_STATUS", snpdev ); /* Fail if net device is currently claimed for use by iPXE */ - if ( efi_snp_claimed ) + if ( efi_snp_claimed ) { + DBGC2 ( snpdev, "\n" ); return EFI_NOT_READY; + } /* Poll the network device */ efi_snp_poll ( snpdev ); @@ -468,47 +522,19 @@ efi_snp_get_status ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, * to detect TX completions. */ if ( interrupts ) { - *interrupts = 0; - /* Report TX completions once queue is empty; this - * avoids having to add hooks in the net device layer. - */ - if ( snpdev->tx_count_interrupts && - list_empty ( &snpdev->netdev->tx_queue ) ) { - *interrupts |= EFI_SIMPLE_NETWORK_TRANSMIT_INTERRUPT; - snpdev->tx_count_interrupts--; - } - /* Report RX */ - if ( snpdev->rx_count_interrupts ) { - *interrupts |= EFI_SIMPLE_NETWORK_RECEIVE_INTERRUPT; - snpdev->rx_count_interrupts--; - } + *interrupts = snpdev->interrupts; DBGC2 ( snpdev, " INTS:%02x", *interrupts ); + snpdev->interrupts = 0; } - /* TX completions. It would be possible to design a more - * idiotic scheme for this, but it would be a challenge. - * According to the UEFI header file, txbufs will be filled in - * with a list of "recycled transmit buffers" (i.e. completed - * TX buffers). Observant readers may care to note that - * *txbufs is a void pointer. Precisely how a list of - * completed transmit buffers is meant to be represented as an - * array of voids is left as an exercise for the reader. - * - * The only users of this interface (MnpDxe/MnpIo.c and - * PxeBcDxe/Bc.c within the EFI dev kit) both just poll until - * seeing a non-NULL result return in txbufs. This is valid - * provided that they do not ever attempt to transmit more - * than one packet concurrently (and that TX never times out). - */ - if ( txbufs ) { - if ( snpdev->tx_count_txbufs && - list_empty ( &snpdev->netdev->tx_queue ) ) { - *txbufs = "Which idiot designed this API?"; - snpdev->tx_count_txbufs--; + /* TX completions */ + if ( txbuf ) { + if ( snpdev->tx_prod != snpdev->tx_cons ) { + *txbuf = snpdev->tx[snpdev->tx_cons++ % EFI_SNP_NUM_TX]; } else { - *txbufs = NULL; + *txbuf = NULL; } - DBGC2 ( snpdev, " TX:%s", ( *txbufs ? "some" : "none" ) ); + DBGC2 ( snpdev, " TX:%p", *txbuf ); } DBGC2 ( snpdev, "\n" ); @@ -537,6 +563,7 @@ efi_snp_transmit ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, struct ll_protocol *ll_protocol = snpdev->netdev->ll_protocol; struct io_buffer *iobuf; size_t payload_len; + unsigned int tx_fill; int rc; DBGC2 ( snpdev, "SNPDEV %p TRANSMIT %p+%lx", snpdev, data, @@ -624,12 +651,27 @@ efi_snp_transmit ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, goto err_tx; } - /* Record transmission as outstanding */ - snpdev->tx_count_interrupts++; - snpdev->tx_count_txbufs++; + /* Record in transmit completion ring. If we run out of + * space, report the failure even though we have already + * transmitted the packet. + * + * This allows us to report completions only for packets for + * which we had reported successfully initiating transmission, + * while continuing to support clients that never poll for + * transmit completions. + */ + tx_fill = ( snpdev->tx_prod - snpdev->tx_cons ); + if ( tx_fill >= EFI_SNP_NUM_TX ) { + DBGC ( snpdev, "SNPDEV %p TX completion ring full\n", snpdev ); + rc = -ENOBUFS; + goto err_ring_full; + } + snpdev->tx[ snpdev->tx_prod++ % EFI_SNP_NUM_TX ] = data; + snpdev->interrupts |= EFI_SIMPLE_NETWORK_TRANSMIT_INTERRUPT; return 0; + err_ring_full: err_tx: err_ll_push: free_iob ( iobuf ); @@ -676,12 +718,13 @@ efi_snp_receive ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, efi_snp_poll ( snpdev ); /* Dequeue a packet, if one is available */ - iobuf = netdev_rx_dequeue ( snpdev->netdev ); + iobuf = list_first_entry ( &snpdev->rx, struct io_buffer, list ); if ( ! iobuf ) { DBGC2 ( snpdev, "\n" ); rc = -EAGAIN; goto out_no_packet; } + list_del ( &iobuf->list ); DBGC2 ( snpdev, "+%zx\n", iob_len ( iobuf ) ); /* Return packet to caller */ @@ -721,9 +764,8 @@ efi_snp_receive ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, * @v event Event * @v context Event context */ -static VOID EFIAPI efi_snp_wait_for_packet ( EFI_EVENT event, +static VOID EFIAPI efi_snp_wait_for_packet ( EFI_EVENT event __unused, VOID *context ) { - EFI_BOOT_SERVICES *bs = efi_systab->BootServices; struct efi_snp_device *snpdev = context; DBGCP ( snpdev, "SNPDEV %p WAIT_FOR_PACKET\n", snpdev ); @@ -738,14 +780,6 @@ static VOID EFIAPI efi_snp_wait_for_packet ( EFI_EVENT event, /* Poll the network device */ efi_snp_poll ( snpdev ); - - /* Fire event if packets have been received */ - if ( snpdev->rx_count_events != 0 ) { - DBGC2 ( snpdev, "SNPDEV %p firing WaitForPacket event\n", - snpdev ); - bs->SignalEvent ( event ); - snpdev->rx_count_events--; - } } /** SNP interface */ @@ -837,6 +871,7 @@ efi_snp_load_file ( EFI_LOAD_FILE_PROTOCOL *load_file, struct efi_snp_device *snpdev = container_of ( load_file, struct efi_snp_device, load_file ); struct net_device *netdev = snpdev->netdev; + int rc; /* Fail unless this is a boot attempt */ if ( ! booting ) { @@ -848,14 +883,17 @@ efi_snp_load_file ( EFI_LOAD_FILE_PROTOCOL *load_file, /* Claim network devices for use by iPXE */ efi_snp_claim(); + /* Start watchdog holdoff timer */ + efi_watchdog_start(); + /* Boot from network device */ - ipxe ( netdev ); + if ( ( rc = ipxe ( netdev ) ) != 0 ) + goto err_ipxe; - /* Release network devices for use via SNP */ + err_ipxe: + efi_watchdog_stop(); efi_snp_release(); - - /* Assume boot process was aborted */ - return EFI_ABORTED; + return EFIRC ( rc ); } /** Load file protocol */ @@ -922,6 +960,7 @@ static int efi_snp_probe ( struct net_device *netdev ) { } snpdev->netdev = netdev_get ( netdev ); snpdev->efidev = efidev; + INIT_LIST_HEAD ( &snpdev->rx ); /* Sanity check */ if ( netdev->ll_protocol->ll_addr_len > sizeof ( EFI_MAC_ADDRESS ) ) { diff --git a/qemu/roms/ipxe/src/interface/efi/efi_snp_hii.c b/qemu/roms/ipxe/src/interface/efi/efi_snp_hii.c index c49c76a32..720402bdb 100644 --- a/qemu/roms/ipxe/src/interface/efi/efi_snp_hii.c +++ b/qemu/roms/ipxe/src/interface/efi/efi_snp_hii.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** * @file @@ -59,6 +63,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include <ipxe/efi/efi_hii.h> #include <ipxe/efi/efi_snp.h> #include <ipxe/efi/efi_strings.h> +#include <config/branding.h> /** EFI platform setup formset GUID */ static EFI_GUID efi_hii_platform_setup_formset_guid @@ -136,7 +141,7 @@ static void efi_snp_hii_questions ( struct efi_snp_device *snpdev, previous = setting; name_id = efi_ifr_string ( ifr, "%s", setting->name ); prompt_id = efi_ifr_string ( ifr, "%s", setting->description ); - help_id = efi_ifr_string ( ifr, "http://ipxe.org/cfg/%s", + help_id = efi_ifr_string ( ifr, PRODUCT_SETTING_URI, setting->name ); question_id = setting->tag; efi_ifr_string_op ( ifr, prompt_id, help_id, diff --git a/qemu/roms/ipxe/src/interface/efi/efi_strings.c b/qemu/roms/ipxe/src/interface/efi/efi_strings.c index 751589b46..aa3afc64f 100644 --- a/qemu/roms/ipxe/src/interface/efi/efi_strings.c +++ b/qemu/roms/ipxe/src/interface/efi/efi_strings.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stddef.h> #include <stdarg.h> diff --git a/qemu/roms/ipxe/src/interface/efi/efi_time.c b/qemu/roms/ipxe/src/interface/efi/efi_time.c new file mode 100644 index 000000000..983a0ef5c --- /dev/null +++ b/qemu/roms/ipxe/src/interface/efi/efi_time.c @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <string.h> +#include <errno.h> +#include <time.h> +#include <ipxe/time.h> +#include <ipxe/efi/efi.h> + +/** @file + * + * EFI time source + * + */ + +/** + * Get current time in seconds + * + * @ret time Time, in seconds + */ +static time_t efi_get_time ( void ) { + EFI_RUNTIME_SERVICES *rs = efi_systab->RuntimeServices; + EFI_TIME time; + struct tm tm; + EFI_STATUS efirc; + int rc; + + /* Get current time and date */ + if ( ( efirc = rs->GetTime ( &time, NULL ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( rs, "EFITIME could not get system time: %s\n", + strerror ( rc ) ); + /* Nothing meaningful we can return */ + return 0; + } + + /* Construct broken-down time */ + memset ( &tm, 0, sizeof ( tm ) ); + tm.tm_sec = time.Second; + tm.tm_min = time.Minute; + tm.tm_hour = time.Hour; + tm.tm_mday = time.Day; + tm.tm_mon = ( time.Month - 1 ); + tm.tm_year = ( time.Year - 1900 ); + DBGC ( rs, "EFITIME is %04d-%02d-%02d %02d:%02d:%02d\n", + ( tm.tm_year + 1900 ), ( tm.tm_mon + 1 ), + tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec ); + + /* Convert to seconds since the Epoch */ + return mktime ( &tm ); +} + +PROVIDE_TIME ( efi, time_now, efi_get_time ); diff --git a/qemu/roms/ipxe/src/interface/efi/efi_timer.c b/qemu/roms/ipxe/src/interface/efi/efi_timer.c index 7a1ff7869..81620c92c 100644 --- a/qemu/roms/ipxe/src/interface/efi/efi_timer.c +++ b/qemu/roms/ipxe/src/interface/efi/efi_timer.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <string.h> #include <errno.h> diff --git a/qemu/roms/ipxe/src/interface/efi/efi_uaccess.c b/qemu/roms/ipxe/src/interface/efi/efi_uaccess.c index 8b429b9ee..e058be66b 100644 --- a/qemu/roms/ipxe/src/interface/efi/efi_uaccess.c +++ b/qemu/roms/ipxe/src/interface/efi/efi_uaccess.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <ipxe/uaccess.h> #include <ipxe/efi/efi.h> diff --git a/qemu/roms/ipxe/src/interface/efi/efi_umalloc.c b/qemu/roms/ipxe/src/interface/efi/efi_umalloc.c index 356efaa6f..e3f1dacc2 100644 --- a/qemu/roms/ipxe/src/interface/efi/efi_umalloc.c +++ b/qemu/roms/ipxe/src/interface/efi/efi_umalloc.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <string.h> #include <errno.h> diff --git a/qemu/roms/ipxe/src/interface/efi/efi_watchdog.c b/qemu/roms/ipxe/src/interface/efi/efi_watchdog.c new file mode 100644 index 000000000..7061f81d8 --- /dev/null +++ b/qemu/roms/ipxe/src/interface/efi/efi_watchdog.c @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** + * @file + * + * EFI watchdog holdoff timer + * + */ + +#include <errno.h> +#include <string.h> +#include <ipxe/retry.h> +#include <ipxe/timer.h> +#include <ipxe/efi/efi.h> +#include <ipxe/efi/efi_watchdog.h> + +/** Watchdog holdoff interval (in seconds) */ +#define WATCHDOG_HOLDOFF_SECS 10 + +/** Watchdog timeout (in seconds) */ +#define WATCHDOG_TIMEOUT_SECS ( 5 * 60 ) + +/** Watchdog code (to be logged on watchdog timeout) */ +#define WATCHDOG_CODE 0x6950584544454144ULL + +/** Watchdog data (to be logged on watchdog timeout) */ +#define WATCHDOG_DATA L"iPXE"; + +/** + * Hold off watchdog timer + * + * @v retry Retry timer + * @v over Failure indicator + */ +static void efi_watchdog_expired ( struct retry_timer *timer, + int over __unused ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + static CHAR16 data[] = WATCHDOG_DATA; + EFI_STATUS efirc; + int rc; + + DBGC2 ( timer, "EFI holding off watchdog timer\n" ); + + /* Restart this holdoff timer */ + start_timer_fixed ( timer, ( WATCHDOG_HOLDOFF_SECS * TICKS_PER_SEC ) ); + + /* Reset watchdog timer */ + if ( ( efirc = bs->SetWatchdogTimer ( WATCHDOG_TIMEOUT_SECS, + WATCHDOG_CODE, sizeof ( data ), + data ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( timer, "EFI could not set watchdog timer: %s\n", + strerror ( rc ) ); + return; + } +} + +/** Watchdog holdoff timer */ +struct retry_timer efi_watchdog = TIMER_INIT ( efi_watchdog_expired ); diff --git a/qemu/roms/ipxe/src/interface/efi/efi_wrap.c b/qemu/roms/ipxe/src/interface/efi/efi_wrap.c index ff46b76ed..2ea184e97 100644 --- a/qemu/roms/ipxe/src/interface/efi/efi_wrap.c +++ b/qemu/roms/ipxe/src/interface/efi/efi_wrap.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** * @file diff --git a/qemu/roms/ipxe/src/interface/hyperv/vmbus.c b/qemu/roms/ipxe/src/interface/hyperv/vmbus.c new file mode 100644 index 000000000..795929eae --- /dev/null +++ b/qemu/roms/ipxe/src/interface/hyperv/vmbus.c @@ -0,0 +1,1333 @@ +/* + * Copyright (C) 2014 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** @file + * + * Hyper-V virtual machine bus + * + */ + +#include <stdint.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <byteswap.h> +#include <ipxe/nap.h> +#include <ipxe/malloc.h> +#include <ipxe/iobuf.h> +#include <ipxe/hyperv.h> +#include <ipxe/vmbus.h> + +/** VMBus initial GPADL ID + * + * This is an opaque value with no meaning. The Linux kernel uses + * 0xe1e10. + */ +#define VMBUS_GPADL_MAGIC 0x18ae0000 + +/** + * Post message + * + * @v hv Hyper-V hypervisor + * @v header Message header + * @v len Length of message (including header) + * @ret rc Return status code + */ +static int vmbus_post_message ( struct hv_hypervisor *hv, + const struct vmbus_message_header *header, + size_t len ) { + struct vmbus *vmbus = hv->vmbus; + int rc; + + /* Post message */ + if ( ( rc = hv_post_message ( hv, VMBUS_MESSAGE_ID, VMBUS_MESSAGE_TYPE, + header, len ) ) != 0 ) { + DBGC ( vmbus, "VMBUS %p could not post message: %s\n", + vmbus, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Post empty message + * + * @v hv Hyper-V hypervisor + * @v type Message type + * @ret rc Return status code + */ +static int vmbus_post_empty_message ( struct hv_hypervisor *hv, + unsigned int type ) { + struct vmbus_message_header header = { .type = cpu_to_le32 ( type ) }; + + return vmbus_post_message ( hv, &header, sizeof ( header ) ); +} + +/** + * Wait for received message + * + * @v hv Hyper-V hypervisor + * @ret rc Return status code + */ +static int vmbus_wait_for_message ( struct hv_hypervisor *hv ) { + struct vmbus *vmbus = hv->vmbus; + int rc; + + /* Wait for message */ + if ( ( rc = hv_wait_for_message ( hv, VMBUS_MESSAGE_SINT ) ) != 0 ) { + DBGC ( vmbus, "VMBUS %p failed waiting for message: %s\n", + vmbus, strerror ( rc ) ); + return rc; + } + + /* Sanity check */ + if ( hv->message->received.type != cpu_to_le32 ( VMBUS_MESSAGE_TYPE ) ){ + DBGC ( vmbus, "VMBUS %p invalid message type %d\n", + vmbus, le32_to_cpu ( hv->message->received.type ) ); + return -EINVAL; + } + + return 0; +} + +/** + * Initiate contact + * + * @v hv Hyper-V hypervisor + * @v raw VMBus protocol (raw) version + * @ret rc Return status code + */ +static int vmbus_initiate_contact ( struct hv_hypervisor *hv, + unsigned int raw ) { + struct vmbus *vmbus = hv->vmbus; + const struct vmbus_version_response *version = &vmbus->message->version; + struct vmbus_initiate_contact initiate; + int rc; + + /* Construct message */ + memset ( &initiate, 0, sizeof ( initiate ) ); + initiate.header.type = cpu_to_le32 ( VMBUS_INITIATE_CONTACT ); + initiate.version.raw = cpu_to_le32 ( raw ); + initiate.intr = virt_to_phys ( vmbus->intr ); + initiate.monitor_in = virt_to_phys ( vmbus->monitor_in ); + initiate.monitor_out = virt_to_phys ( vmbus->monitor_out ); + + /* Post message */ + if ( ( rc = vmbus_post_message ( hv, &initiate.header, + sizeof ( initiate ) ) ) != 0 ) + return rc; + + /* Wait for response */ + if ( ( rc = vmbus_wait_for_message ( hv ) ) != 0 ) + return rc; + + /* Check response */ + if ( version->header.type != cpu_to_le32 ( VMBUS_VERSION_RESPONSE ) ) { + DBGC ( vmbus, "VMBUS %p unexpected version response type %d\n", + vmbus, le32_to_cpu ( version->header.type ) ); + return -EPROTO; + } + if ( ! version->supported ) { + DBGC ( vmbus, "VMBUS %p requested version not supported\n", + vmbus ); + return -ENOTSUP; + } + if ( version->version.raw != cpu_to_le32 ( raw ) ) { + DBGC ( vmbus, "VMBUS %p unexpected version %d.%d\n", + vmbus, le16_to_cpu ( version->version.major ), + le16_to_cpu ( version->version.minor ) ); + return -EPROTO; + } + + DBGC ( vmbus, "VMBUS %p initiated contact using version %d.%d\n", + vmbus, le16_to_cpu ( version->version.major ), + le16_to_cpu ( version->version.minor ) ); + return 0; +} + +/** + * Terminate contact + * + * @v hv Hyper-V hypervisor + * @ret rc Return status code + */ +static int vmbus_unload ( struct hv_hypervisor *hv ) { + struct vmbus *vmbus = hv->vmbus; + const struct vmbus_message_header *header = &vmbus->message->header; + int rc; + + /* Post message */ + if ( ( rc = vmbus_post_empty_message ( hv, VMBUS_UNLOAD ) ) != 0 ) + return rc; + + /* Wait for response */ + if ( ( rc = vmbus_wait_for_message ( hv ) ) != 0 ) + return rc; + + /* Check response */ + if ( header->type != cpu_to_le32 ( VMBUS_UNLOAD_RESPONSE ) ) { + DBGC ( vmbus, "VMBUS %p unexpected unload response type %d\n", + vmbus, le32_to_cpu ( header->type ) ); + return -EPROTO; + } + + return 0; +} + +/** + * Negotiate protocol version + * + * @v hv Hyper-V hypervisor + * @ret rc Return status code + */ +static int vmbus_negotiate_version ( struct hv_hypervisor *hv ) { + int rc; + + /* We require the ability to disconnect from and reconnect to + * VMBus; if we don't have this then there is no (viable) way + * for a loaded operating system to continue to use any VMBus + * devices. (There is also a small but non-zero risk that the + * host will continue to write to our interrupt and monitor + * pages, since the VMBUS_UNLOAD message in earlier versions + * is essentially a no-op.) + * + * This requires us to ensure that the host supports protocol + * version 3.0 (VMBUS_VERSION_WIN8_1). However, we can't + * actually _use_ protocol version 3.0, since doing so causes + * an iSCSI-booted Windows Server 2012 R2 VM to crash due to a + * NULL pointer dereference in vmbus.sys. + * + * To work around this problem, we first ensure that we can + * connect using protocol v3.0, then disconnect and reconnect + * using the oldest known protocol. + */ + + /* Initiate contact to check for required protocol support */ + if ( ( rc = vmbus_initiate_contact ( hv, VMBUS_VERSION_WIN8_1 ) ) != 0 ) + return rc; + + /* Terminate contact */ + if ( ( rc = vmbus_unload ( hv ) ) != 0 ) + return rc; + + /* Reinitiate contact using the oldest known protocol version */ + if ( ( rc = vmbus_initiate_contact ( hv, VMBUS_VERSION_WS2008 ) ) != 0 ) + return rc; + + return 0; +} + +/** + * Establish GPA descriptor list + * + * @v vmdev VMBus device + * @v data Data buffer + * @v len Length of data buffer + * @ret gpadl GPADL ID, or negative error + */ +int vmbus_establish_gpadl ( struct vmbus_device *vmdev, userptr_t data, + size_t len ) { + struct hv_hypervisor *hv = vmdev->hv; + struct vmbus *vmbus = hv->vmbus; + physaddr_t addr = user_to_phys ( data, 0 ); + unsigned int pfn_count = hv_pfn_count ( addr, len ); + struct { + struct vmbus_gpadl_header gpadlhdr; + struct vmbus_gpa_range range; + uint64_t pfn[pfn_count]; + } __attribute__ (( packed )) gpadlhdr; + const struct vmbus_gpadl_created *created = &vmbus->message->created; + static unsigned int gpadl = VMBUS_GPADL_MAGIC; + unsigned int i; + int rc; + + /* Allocate GPADL ID */ + gpadl++; + + /* Construct message */ + memset ( &gpadlhdr, 0, sizeof ( gpadlhdr ) ); + gpadlhdr.gpadlhdr.header.type = cpu_to_le32 ( VMBUS_GPADL_HEADER ); + gpadlhdr.gpadlhdr.channel = cpu_to_le32 ( vmdev->channel ); + gpadlhdr.gpadlhdr.gpadl = cpu_to_le32 ( gpadl ); + gpadlhdr.gpadlhdr.range_len = + cpu_to_le16 ( ( sizeof ( gpadlhdr.range ) + + sizeof ( gpadlhdr.pfn ) ) ); + gpadlhdr.gpadlhdr.range_count = cpu_to_le16 ( 1 ); + gpadlhdr.range.len = cpu_to_le32 ( len ); + gpadlhdr.range.offset = cpu_to_le32 ( addr & ( PAGE_SIZE - 1 ) ); + for ( i = 0 ; i < pfn_count ; i++ ) + gpadlhdr.pfn[i] = ( ( addr / PAGE_SIZE ) + i ); + + /* Post message */ + if ( ( rc = vmbus_post_message ( hv, &gpadlhdr.gpadlhdr.header, + sizeof ( gpadlhdr ) ) ) != 0 ) + return rc; + + /* Wait for response */ + if ( ( rc = vmbus_wait_for_message ( hv ) ) != 0 ) + return rc; + + /* Check response */ + if ( created->header.type != cpu_to_le32 ( VMBUS_GPADL_CREATED ) ) { + DBGC ( vmdev, "VMBUS %s unexpected GPADL response type %d\n", + vmdev->dev.name, le32_to_cpu ( created->header.type ) ); + return -EPROTO; + } + if ( created->channel != cpu_to_le32 ( vmdev->channel ) ) { + DBGC ( vmdev, "VMBUS %s unexpected GPADL channel %d\n", + vmdev->dev.name, le32_to_cpu ( created->channel ) ); + return -EPROTO; + } + if ( created->gpadl != cpu_to_le32 ( gpadl ) ) { + DBGC ( vmdev, "VMBUS %s unexpected GPADL ID %#08x\n", + vmdev->dev.name, le32_to_cpu ( created->gpadl ) ); + return -EPROTO; + } + if ( created->status != 0 ) { + DBGC ( vmdev, "VMBUS %s GPADL creation failed: %#08x\n", + vmdev->dev.name, le32_to_cpu ( created->status ) ); + return -EPROTO; + } + + DBGC ( vmdev, "VMBUS %s GPADL %#08x is [%08lx,%08lx)\n", + vmdev->dev.name, gpadl, addr, ( addr + len ) ); + return gpadl; +} + +/** + * Tear down GPA descriptor list + * + * @v vmdev VMBus device + * @v gpadl GPADL ID + * @ret rc Return status code + */ +int vmbus_gpadl_teardown ( struct vmbus_device *vmdev, unsigned int gpadl ) { + struct hv_hypervisor *hv = vmdev->hv; + struct vmbus *vmbus = hv->vmbus; + struct vmbus_gpadl_teardown teardown; + const struct vmbus_gpadl_torndown *torndown = &vmbus->message->torndown; + int rc; + + /* Construct message */ + memset ( &teardown, 0, sizeof ( teardown ) ); + teardown.header.type = cpu_to_le32 ( VMBUS_GPADL_TEARDOWN ); + teardown.channel = cpu_to_le32 ( vmdev->channel ); + teardown.gpadl = cpu_to_le32 ( gpadl ); + + /* Post message */ + if ( ( rc = vmbus_post_message ( hv, &teardown.header, + sizeof ( teardown ) ) ) != 0 ) + return rc; + + /* Wait for response */ + if ( ( rc = vmbus_wait_for_message ( hv ) ) != 0 ) + return rc; + + /* Check response */ + if ( torndown->header.type != cpu_to_le32 ( VMBUS_GPADL_TORNDOWN ) ) { + DBGC ( vmdev, "VMBUS %s unexpected GPADL response type %d\n", + vmdev->dev.name, le32_to_cpu ( torndown->header.type ) ); + return -EPROTO; + } + if ( torndown->gpadl != cpu_to_le32 ( gpadl ) ) { + DBGC ( vmdev, "VMBUS %s unexpected GPADL ID %#08x\n", + vmdev->dev.name, le32_to_cpu ( torndown->gpadl ) ); + return -EPROTO; + } + + return 0; +} + +/** + * Open VMBus channel + * + * @v vmdev VMBus device + * @v op Channel operations + * @v out_len Outbound ring buffer length + * @v in_len Inbound ring buffer length + * @v mtu Maximum expected data packet length (including headers) + * @ret rc Return status code + * + * Both outbound and inbound ring buffer lengths must be a power of + * two and a multiple of PAGE_SIZE. The requirement to be a power of + * two is a policy decision taken to simplify the ring buffer indexing + * logic. + */ +int vmbus_open ( struct vmbus_device *vmdev, + struct vmbus_channel_operations *op, + size_t out_len, size_t in_len, size_t mtu ) { + struct hv_hypervisor *hv = vmdev->hv; + struct vmbus *vmbus = hv->vmbus; + struct vmbus_open_channel open; + const struct vmbus_open_channel_result *opened = + &vmbus->message->opened; + size_t len; + void *ring; + void *packet; + int gpadl; + uint32_t open_id; + int rc; + + /* Sanity checks */ + assert ( ( out_len % PAGE_SIZE ) == 0 ); + assert ( ( out_len & ( out_len - 1 ) ) == 0 ); + assert ( ( in_len % PAGE_SIZE ) == 0 ); + assert ( ( in_len & ( in_len - 1 ) ) == 0 ); + assert ( mtu >= ( sizeof ( struct vmbus_packet_header ) + + sizeof ( struct vmbus_packet_footer ) ) ); + + /* Allocate packet buffer */ + packet = malloc ( mtu ); + if ( ! packet ) { + rc = -ENOMEM; + goto err_alloc_packet; + } + + /* Allocate ring buffer */ + len = ( sizeof ( *vmdev->out ) + out_len + + sizeof ( *vmdev->in ) + in_len ); + assert ( ( len % PAGE_SIZE ) == 0 ); + ring = malloc_dma ( len, PAGE_SIZE ); + if ( ! ring ) { + rc = -ENOMEM; + goto err_alloc_ring; + } + memset ( ring, 0, len ); + + /* Establish GPADL for ring buffer */ + gpadl = vmbus_establish_gpadl ( vmdev, virt_to_user ( ring ), len ); + if ( gpadl < 0 ) { + rc = gpadl; + goto err_establish; + } + + /* Construct message */ + memset ( &open, 0, sizeof ( open ) ); + open.header.type = cpu_to_le32 ( VMBUS_OPEN_CHANNEL ); + open.channel = cpu_to_le32 ( vmdev->channel ); + open_id = random(); + open.id = open_id; /* Opaque random value: endianness irrelevant */ + open.gpadl = cpu_to_le32 ( gpadl ); + open.out_pages = ( ( sizeof ( *vmdev->out ) / PAGE_SIZE ) + + ( out_len / PAGE_SIZE ) ); + + /* Post message */ + if ( ( rc = vmbus_post_message ( hv, &open.header, + sizeof ( open ) ) ) != 0 ) + return rc; + + /* Wait for response */ + if ( ( rc = vmbus_wait_for_message ( hv ) ) != 0 ) + return rc; + + /* Check response */ + if ( opened->header.type != cpu_to_le32 ( VMBUS_OPEN_CHANNEL_RESULT ) ){ + DBGC ( vmdev, "VMBUS %s unexpected open response type %d\n", + vmdev->dev.name, le32_to_cpu ( opened->header.type ) ); + return -EPROTO; + } + if ( opened->channel != cpu_to_le32 ( vmdev->channel ) ) { + DBGC ( vmdev, "VMBUS %s unexpected opened channel %#08x\n", + vmdev->dev.name, le32_to_cpu ( opened->channel ) ); + return -EPROTO; + } + if ( opened->id != open_id /* Non-endian */ ) { + DBGC ( vmdev, "VMBUS %s unexpected open ID %#08x\n", + vmdev->dev.name, le32_to_cpu ( opened->id ) ); + return -EPROTO; + } + if ( opened->status != 0 ) { + DBGC ( vmdev, "VMBUS %s open failed: %#08x\n", + vmdev->dev.name, le32_to_cpu ( opened->status ) ); + return -EPROTO; + } + + /* Store channel parameters */ + vmdev->out_len = out_len; + vmdev->in_len = in_len; + vmdev->out = ring; + vmdev->in = ( ring + sizeof ( *vmdev->out ) + out_len ); + vmdev->gpadl = gpadl; + vmdev->op = op; + vmdev->mtu = mtu; + vmdev->packet = packet; + + DBGC ( vmdev, "VMBUS %s channel GPADL %#08x ring " + "[%#08lx,%#08lx,%#08lx)\n", vmdev->dev.name, vmdev->gpadl, + virt_to_phys ( vmdev->out ), virt_to_phys ( vmdev->in ), + ( virt_to_phys ( vmdev->out ) + len ) ); + return 0; + + vmbus_gpadl_teardown ( vmdev, vmdev->gpadl ); + err_establish: + free_dma ( ring, len ); + err_alloc_ring: + free ( packet ); + err_alloc_packet: + return rc; +} + +/** + * Close VMBus channel + * + * @v vmdev VMBus device + */ +void vmbus_close ( struct vmbus_device *vmdev ) { + struct hv_hypervisor *hv = vmdev->hv; + struct vmbus_close_channel close; + size_t len; + int rc; + + /* Construct message */ + memset ( &close, 0, sizeof ( close ) ); + close.header.type = cpu_to_le32 ( VMBUS_CLOSE_CHANNEL ); + close.channel = cpu_to_le32 ( vmdev->channel ); + + /* Post message */ + if ( ( rc = vmbus_post_message ( hv, &close.header, + sizeof ( close ) ) ) != 0 ) { + DBGC ( vmdev, "VMBUS %s failed to close: %s\n", + vmdev->dev.name, strerror ( rc ) ); + /* Continue to attempt to tear down GPADL, so that our + * memory is no longer accessible by the remote VM. + */ + } + + /* Tear down GPADL */ + if ( ( rc = vmbus_gpadl_teardown ( vmdev, + vmdev->gpadl ) ) != 0 ) { + DBGC ( vmdev, "VMBUS %s failed to tear down channel GPADL: " + "%s\n", vmdev->dev.name, strerror ( rc ) ); + /* We can't prevent the remote VM from continuing to + * access this memory, so leak it. + */ + return; + } + + /* Free ring buffer */ + len = ( sizeof ( *vmdev->out ) + vmdev->out_len + + sizeof ( *vmdev->in ) + vmdev->in_len ); + free_dma ( vmdev->out, len ); + vmdev->out = NULL; + vmdev->in = NULL; + + /* Free packet buffer */ + free ( vmdev->packet ); + vmdev->packet = NULL; + + DBGC ( vmdev, "VMBUS %s closed\n", vmdev->dev.name ); +} + +/** + * Signal channel via monitor page + * + * @v vmdev VMBus device + */ +static void vmbus_signal_monitor ( struct vmbus_device *vmdev ) { + struct hv_hypervisor *hv = vmdev->hv; + struct vmbus *vmbus = hv->vmbus; + struct hv_monitor_trigger *trigger; + unsigned int group; + unsigned int bit; + + /* Set bit in monitor trigger group */ + group = ( vmdev->monitor / ( 8 * sizeof ( trigger->pending ) )); + bit = ( vmdev->monitor % ( 8 * sizeof ( trigger->pending ) ) ); + trigger = &vmbus->monitor_out->trigger[group]; + hv_set_bit ( trigger, bit ); +} + +/** + * Signal channel via hypervisor event + * + * @v vmdev VMBus device + */ +static void vmbus_signal_event ( struct vmbus_device *vmdev ) { + struct hv_hypervisor *hv = vmdev->hv; + int rc; + + /* Signal hypervisor event */ + if ( ( rc = hv_signal_event ( hv, VMBUS_EVENT_ID, 0 ) ) != 0 ) { + DBGC ( vmdev, "VMBUS %s could not signal event: %s\n", + vmdev->dev.name, strerror ( rc ) ); + return; + } +} + +/** + * Fill outbound ring buffer + * + * @v vmdev VMBus device + * @v prod Producer index + * @v data Data + * @v len Length + * @ret prod New producer index + * + * The caller must ensure that there is sufficient space in the ring + * buffer. + */ +static size_t vmbus_produce ( struct vmbus_device *vmdev, size_t prod, + const void *data, size_t len ) { + size_t first; + size_t second; + + /* Determine fragment lengths */ + first = ( vmdev->out_len - prod ); + if ( first > len ) + first = len; + second = ( len - first ); + + /* Copy fragment(s) */ + memcpy ( &vmdev->out->data[prod], data, first ); + if ( second ) + memcpy ( &vmdev->out->data[0], ( data + first ), second ); + + return ( ( prod + len ) & ( vmdev->out_len - 1 ) ); +} + +/** + * Consume inbound ring buffer + * + * @v vmdev VMBus device + * @v cons Consumer index + * @v data Data buffer, or NULL + * @v len Length to consume + * @ret cons New consumer index + */ +static size_t vmbus_consume ( struct vmbus_device *vmdev, size_t cons, + void *data, size_t len ) { + size_t first; + size_t second; + + /* Determine fragment lengths */ + first = ( vmdev->in_len - cons ); + if ( first > len ) + first = len; + second = ( len - first ); + + /* Copy fragment(s) */ + memcpy ( data, &vmdev->in->data[cons], first ); + if ( second ) + memcpy ( ( data + first ), &vmdev->in->data[0], second ); + + return ( ( cons + len ) & ( vmdev->in_len - 1 ) ); +} + +/** + * Send packet via ring buffer + * + * @v vmdev VMBus device + * @v header Packet header + * @v data Data + * @v len Length of data + * @ret rc Return status code + * + * Send a packet via the outbound ring buffer. All fields in the + * packet header must be filled in, with the exception of the total + * packet length. + */ +static int vmbus_send ( struct vmbus_device *vmdev, + struct vmbus_packet_header *header, + const void *data, size_t len ) { + struct hv_hypervisor *hv = vmdev->hv; + struct vmbus *vmbus = hv->vmbus; + static uint8_t padding[ 8 - 1 ]; + struct vmbus_packet_footer footer; + size_t header_len; + size_t pad_len; + size_t footer_len; + size_t ring_len; + size_t cons; + size_t prod; + size_t old_prod; + size_t fill; + + /* Sanity check */ + assert ( vmdev->out != NULL ); + + /* Calculate lengths */ + header_len = ( le16_to_cpu ( header->hdr_qlen ) * 8 ); + pad_len = ( ( -len ) & ( 8 - 1 ) ); + footer_len = sizeof ( footer ); + ring_len = ( header_len + len + pad_len + footer_len ); + + /* Check that we have enough room in the outbound ring buffer */ + cons = le32_to_cpu ( vmdev->out->cons ); + prod = le32_to_cpu ( vmdev->out->prod ); + old_prod = prod; + fill = ( ( prod - cons ) & ( vmdev->out_len - 1 ) ); + if ( ( fill + ring_len ) >= vmdev->out_len ) { + DBGC ( vmdev, "VMBUS %s ring buffer full\n", vmdev->dev.name ); + return -ENOBUFS; + } + + /* Complete header */ + header->qlen = cpu_to_le16 ( ( ring_len - footer_len ) / 8 ); + + /* Construct footer */ + footer.reserved = 0; + footer.prod = vmdev->out->prod; + + /* Copy packet to buffer */ + DBGC2 ( vmdev, "VMBUS %s sending:\n", vmdev->dev.name ); + DBGC2_HDA ( vmdev, prod, header, header_len ); + prod = vmbus_produce ( vmdev, prod, header, header_len ); + DBGC2_HDA ( vmdev, prod, data, len ); + prod = vmbus_produce ( vmdev, prod, data, len ); + prod = vmbus_produce ( vmdev, prod, padding, pad_len ); + DBGC2_HDA ( vmdev, prod, &footer, sizeof ( footer ) ); + prod = vmbus_produce ( vmdev, prod, &footer, sizeof ( footer ) ); + assert ( ( ( prod - old_prod ) & ( vmdev->out_len - 1 ) ) == ring_len ); + + /* Update producer index */ + wmb(); + vmdev->out->prod = cpu_to_le32 ( prod ); + + /* Return if we do not need to signal the host. This follows + * the logic of hv_need_to_signal() in the Linux driver. + */ + mb(); + if ( vmdev->out->intr_mask ) + return 0; + rmb(); + cons = le32_to_cpu ( vmdev->out->cons ); + if ( cons != old_prod ) + return 0; + + /* Set channel bit in interrupt page */ + hv_set_bit ( vmbus->intr->out, vmdev->channel ); + + /* Signal the host */ + vmdev->signal ( vmdev ); + + return 0; +} + +/** + * Send control packet via ring buffer + * + * @v vmdev VMBus device + * @v xid Transaction ID (or zero to not request completion) + * @v data Data + * @v len Length of data + * @ret rc Return status code + * + * Send data using a VMBUS_DATA_INBAND packet. + */ +int vmbus_send_control ( struct vmbus_device *vmdev, uint64_t xid, + const void *data, size_t len ) { + struct vmbus_packet_header *header = vmdev->packet; + + /* Construct header in packet buffer */ + assert ( header != NULL ); + header->type = cpu_to_le16 ( VMBUS_DATA_INBAND ); + header->hdr_qlen = cpu_to_le16 ( sizeof ( *header ) / 8 ); + header->flags = ( xid ? + cpu_to_le16 ( VMBUS_COMPLETION_REQUESTED ) : 0 ); + header->xid = xid; /* Non-endian */ + + return vmbus_send ( vmdev, header, data, len ); +} + +/** + * Send data packet via ring buffer + * + * @v vmdev VMBus device + * @v xid Transaction ID + * @v data Data + * @v len Length of data + * @v iobuf I/O buffer + * @ret rc Return status code + * + * Send data using a VMBUS_DATA_GPA_DIRECT packet. The caller is + * responsible for ensuring that the I/O buffer remains untouched + * until the corresponding completion has been received. + */ +int vmbus_send_data ( struct vmbus_device *vmdev, uint64_t xid, + const void *data, size_t len, struct io_buffer *iobuf ) { + physaddr_t addr = virt_to_phys ( iobuf->data ); + unsigned int pfn_count = hv_pfn_count ( addr, iob_len ( iobuf ) ); + struct { + struct vmbus_gpa_direct_header gpa; + struct vmbus_gpa_range range; + uint64_t pfn[pfn_count]; + } __attribute__ (( packed )) *header = vmdev->packet; + unsigned int i; + + /* Sanity check */ + assert ( header != NULL ); + assert ( sizeof ( *header ) <= vmdev->mtu ); + + /* Construct header in packet buffer */ + header->gpa.header.type = cpu_to_le16 ( VMBUS_DATA_GPA_DIRECT ); + header->gpa.header.hdr_qlen = cpu_to_le16 ( sizeof ( *header ) / 8 ); + header->gpa.header.flags = cpu_to_le16 ( VMBUS_COMPLETION_REQUESTED ); + header->gpa.header.xid = xid; /* Non-endian */ + header->gpa.range_count = 1; + header->range.len = cpu_to_le32 ( iob_len ( iobuf ) ); + header->range.offset = cpu_to_le32 ( addr & ( PAGE_SIZE - 1 ) ); + for ( i = 0 ; i < pfn_count ; i++ ) + header->pfn[i] = ( ( addr / PAGE_SIZE ) + i ); + + return vmbus_send ( vmdev, &header->gpa.header, data, len ); +} + +/** + * Send completion packet via ring buffer + * + * @v vmdev VMBus device + * @v xid Transaction ID + * @v data Data + * @v len Length of data + * @ret rc Return status code + * + * Send data using a VMBUS_COMPLETION packet. + */ +int vmbus_send_completion ( struct vmbus_device *vmdev, uint64_t xid, + const void *data, size_t len ) { + struct vmbus_packet_header *header = vmdev->packet; + + /* Construct header in packet buffer */ + assert ( header != NULL ); + header->type = cpu_to_le16 ( VMBUS_COMPLETION ); + header->hdr_qlen = cpu_to_le16 ( sizeof ( *header ) / 8 ); + header->flags = 0; + header->xid = xid; /* Non-endian */ + + return vmbus_send ( vmdev, header, data, len ); +} + +/** + * Send cancellation packet via ring buffer + * + * @v vmdev VMBus device + * @v xid Transaction ID + * @ret rc Return status code + * + * Send data using a VMBUS_CANCELLATION packet. + */ +int vmbus_send_cancellation ( struct vmbus_device *vmdev, uint64_t xid ) { + struct vmbus_packet_header *header = vmdev->packet; + + /* Construct header in packet buffer */ + assert ( header != NULL ); + header->type = cpu_to_le16 ( VMBUS_CANCELLATION ); + header->hdr_qlen = cpu_to_le16 ( sizeof ( *header ) / 8 ); + header->flags = 0; + header->xid = xid; /* Non-endian */ + + return vmbus_send ( vmdev, header, NULL, 0 ); +} + +/** + * Get transfer page set from pageset ID + * + * @v vmdev VMBus device + * @v pageset Page set ID (in protocol byte order) + * @ret pages Page set, or NULL if not found + */ +static struct vmbus_xfer_pages * vmbus_xfer_pages ( struct vmbus_device *vmdev, + uint16_t pageset ) { + struct vmbus_xfer_pages *pages; + + /* Locate page set */ + list_for_each_entry ( pages, &vmdev->pages, list ) { + if ( pages->pageset == pageset ) + return pages; + } + + DBGC ( vmdev, "VMBUS %s unrecognised page set ID %#04x\n", + vmdev->dev.name, le16_to_cpu ( pageset ) ); + return NULL; +} + +/** + * Construct I/O buffer list from transfer pages + * + * @v vmdev VMBus device + * @v header Transfer page header + * @v list I/O buffer list to populate + * @ret rc Return status code + */ +static int vmbus_xfer_page_iobufs ( struct vmbus_device *vmdev, + struct vmbus_packet_header *header, + struct list_head *list ) { + struct vmbus_xfer_page_header *page_header = + container_of ( header, struct vmbus_xfer_page_header, header ); + struct vmbus_xfer_pages *pages; + struct io_buffer *iobuf; + struct io_buffer *tmp; + size_t len; + size_t offset; + unsigned int range_count; + unsigned int i; + int rc; + + /* Sanity check */ + assert ( header->type == cpu_to_le16 ( VMBUS_DATA_XFER_PAGES ) ); + + /* Locate page set */ + pages = vmbus_xfer_pages ( vmdev, page_header->pageset ); + if ( ! pages ) { + rc = -ENOENT; + goto err_pages; + } + + /* Allocate and populate I/O buffers */ + range_count = le32_to_cpu ( page_header->range_count ); + for ( i = 0 ; i < range_count ; i++ ) { + + /* Parse header */ + len = le32_to_cpu ( page_header->range[i].len ); + offset = le32_to_cpu ( page_header->range[i].offset ); + + /* Allocate I/O buffer */ + iobuf = alloc_iob ( len ); + if ( ! iobuf ) { + DBGC ( vmdev, "VMBUS %s could not allocate %zd-byte " + "I/O buffer\n", vmdev->dev.name, len ); + rc = -ENOMEM; + goto err_alloc; + } + + /* Add I/O buffer to list */ + list_add ( &iobuf->list, list ); + + /* Populate I/O buffer */ + if ( ( rc = pages->op->copy ( pages, iob_put ( iobuf, len ), + offset, len ) ) != 0 ) { + DBGC ( vmdev, "VMBUS %s could not populate I/O buffer " + "range [%zd,%zd): %s\n", + vmdev->dev.name, offset, len, strerror ( rc ) ); + goto err_copy; + } + } + + return 0; + + err_copy: + err_alloc: + list_for_each_entry_safe ( iobuf, tmp, list, list ) { + list_del ( &iobuf->list ); + free_iob ( iobuf ); + } + err_pages: + return rc; +} + +/** + * Poll ring buffer + * + * @v vmdev VMBus device + * @ret rc Return status code + */ +int vmbus_poll ( struct vmbus_device *vmdev ) { + struct vmbus_packet_header *header = vmdev->packet; + struct list_head list; + void *data; + size_t header_len; + size_t len; + size_t footer_len; + size_t ring_len; + size_t cons; + size_t old_cons; + uint64_t xid; + int rc; + + /* Sanity checks */ + assert ( vmdev->packet != NULL ); + assert ( vmdev->in != NULL ); + + /* Return immediately if buffer is empty */ + if ( ! vmbus_has_data ( vmdev ) ) + return 0; + cons = le32_to_cpu ( vmdev->in->cons ); + old_cons = cons; + + /* Consume (start of) header */ + cons = vmbus_consume ( vmdev, cons, header, sizeof ( *header ) ); + + /* Parse and sanity check header */ + header_len = ( le16_to_cpu ( header->hdr_qlen ) * 8 ); + if ( header_len < sizeof ( *header ) ) { + DBGC ( vmdev, "VMBUS %s received underlength header (%zd " + "bytes)\n", vmdev->dev.name, header_len ); + return -EINVAL; + } + len = ( ( le16_to_cpu ( header->qlen ) * 8 ) - header_len ); + footer_len = sizeof ( struct vmbus_packet_footer ); + ring_len = ( header_len + len + footer_len ); + if ( ring_len > vmdev->mtu ) { + DBGC ( vmdev, "VMBUS %s received overlength packet (%zd " + "bytes)\n", vmdev->dev.name, ring_len ); + return -ERANGE; + } + xid = le64_to_cpu ( header->xid ); + + /* Consume remainder of packet */ + cons = vmbus_consume ( vmdev, cons, + ( ( ( void * ) header ) + sizeof ( *header ) ), + ( ring_len - sizeof ( *header ) ) ); + DBGC2 ( vmdev, "VMBUS %s received:\n", vmdev->dev.name ); + DBGC2_HDA ( vmdev, old_cons, header, ring_len ); + assert ( ( ( cons - old_cons ) & ( vmdev->in_len - 1 ) ) == ring_len ); + + /* Allocate I/O buffers, if applicable */ + INIT_LIST_HEAD ( &list ); + if ( header->type == cpu_to_le16 ( VMBUS_DATA_XFER_PAGES ) ) { + if ( ( rc = vmbus_xfer_page_iobufs ( vmdev, header, + &list ) ) != 0 ) + return rc; + } + + /* Update producer index */ + rmb(); + vmdev->in->cons = cpu_to_le32 ( cons ); + + /* Handle packet */ + data = ( ( ( void * ) header ) + header_len ); + switch ( header->type ) { + + case cpu_to_le16 ( VMBUS_DATA_INBAND ) : + if ( ( rc = vmdev->op->recv_control ( vmdev, xid, data, + len ) ) != 0 ) { + DBGC ( vmdev, "VMBUS %s could not handle control " + "packet: %s\n", + vmdev->dev.name, strerror ( rc ) ); + return rc; + } + break; + + case cpu_to_le16 ( VMBUS_DATA_XFER_PAGES ) : + if ( ( rc = vmdev->op->recv_data ( vmdev, xid, data, len, + &list ) ) != 0 ) { + DBGC ( vmdev, "VMBUS %s could not handle data packet: " + "%s\n", vmdev->dev.name, strerror ( rc ) ); + return rc; + } + break; + + case cpu_to_le16 ( VMBUS_COMPLETION ) : + if ( ( rc = vmdev->op->recv_completion ( vmdev, xid, data, + len ) ) != 0 ) { + DBGC ( vmdev, "VMBUS %s could not handle completion: " + "%s\n", vmdev->dev.name, strerror ( rc ) ); + return rc; + } + break; + + case cpu_to_le16 ( VMBUS_CANCELLATION ) : + if ( ( rc = vmdev->op->recv_cancellation ( vmdev, xid ) ) != 0){ + DBGC ( vmdev, "VMBUS %s could not handle cancellation: " + "%s\n", vmdev->dev.name, strerror ( rc ) ); + return rc; + } + break; + + default: + DBGC ( vmdev, "VMBUS %s unknown packet type %d\n", + vmdev->dev.name, le16_to_cpu ( header->type ) ); + return -ENOTSUP; + } + + return 0; +} + +/** + * Dump channel status (for debugging) + * + * @v vmdev VMBus device + */ +void vmbus_dump_channel ( struct vmbus_device *vmdev ) { + size_t out_prod = le32_to_cpu ( vmdev->out->prod ); + size_t out_cons = le32_to_cpu ( vmdev->out->cons ); + size_t in_prod = le32_to_cpu ( vmdev->in->prod ); + size_t in_cons = le32_to_cpu ( vmdev->in->cons ); + size_t in_len; + size_t first; + size_t second; + + /* Dump ring status */ + DBGC ( vmdev, "VMBUS %s out %03zx:%03zx%s in %03zx:%03zx%s\n", + vmdev->dev.name, out_prod, out_cons, + ( vmdev->out->intr_mask ? "(m)" : "" ), in_prod, in_cons, + ( vmdev->in->intr_mask ? "(m)" : "" ) ); + + /* Dump inbound ring contents, if any */ + if ( in_prod != in_cons ) { + in_len = ( ( in_prod - in_cons ) & + ( vmdev->in_len - 1 ) ); + first = ( vmdev->in_len - in_cons ); + if ( first > in_len ) + first = in_len; + second = ( in_len - first ); + DBGC_HDA ( vmdev, in_cons, &vmdev->in->data[in_cons], first ); + DBGC_HDA ( vmdev, 0, &vmdev->in->data[0], second ); + } +} + +/** + * Find driver for VMBus device + * + * @v vmdev VMBus device + * @ret driver Driver, or NULL + */ +static struct vmbus_driver * vmbus_find_driver ( const union uuid *type ) { + struct vmbus_driver *vmdrv; + + for_each_table_entry ( vmdrv, VMBUS_DRIVERS ) { + if ( memcmp ( &vmdrv->type, type, sizeof ( *type ) ) == 0 ) + return vmdrv; + } + return NULL; +} + +/** + * Probe channels + * + * @v hv Hyper-V hypervisor + * @v parent Parent device + * @ret rc Return status code + */ +static int vmbus_probe_channels ( struct hv_hypervisor *hv, + struct device *parent ) { + struct vmbus *vmbus = hv->vmbus; + const struct vmbus_message_header *header = &vmbus->message->header; + const struct vmbus_offer_channel *offer = &vmbus->message->offer; + const union uuid *type; + struct vmbus_driver *driver; + struct vmbus_device *vmdev; + struct vmbus_device *tmp; + unsigned int channel; + int rc; + + /* Post message */ + if ( ( rc = vmbus_post_empty_message ( hv, VMBUS_REQUEST_OFFERS ) ) !=0) + goto err_post_message; + + /* Collect responses */ + while ( 1 ) { + + /* Wait for response */ + if ( ( rc = vmbus_wait_for_message ( hv ) ) != 0 ) + goto err_wait_for_message; + + /* Handle response */ + if ( header->type == cpu_to_le32 ( VMBUS_OFFER_CHANNEL ) ) { + + /* Parse offer */ + type = &offer->type; + channel = le32_to_cpu ( offer->channel ); + DBGC2 ( vmbus, "VMBUS %p offer %d type %s", + vmbus, channel, uuid_ntoa ( type ) ); + if ( offer->monitored ) + DBGC2 ( vmbus, " monitor %d", offer->monitor ); + DBGC2 ( vmbus, "\n" ); + + /* Look for a driver */ + driver = vmbus_find_driver ( type ); + if ( ! driver ) { + DBGC2 ( vmbus, "VMBUS %p has no driver for " + "type %s\n", vmbus, uuid_ntoa ( type )); + /* Not a fatal error */ + continue; + } + + /* Allocate and initialise device */ + vmdev = zalloc ( sizeof ( *vmdev ) ); + if ( ! vmdev ) { + rc = -ENOMEM; + goto err_alloc_vmdev; + } + snprintf ( vmdev->dev.name, sizeof ( vmdev->dev.name ), + "vmbus:%02x", channel ); + vmdev->dev.desc.bus_type = BUS_TYPE_HV; + INIT_LIST_HEAD ( &vmdev->dev.children ); + list_add_tail ( &vmdev->dev.siblings, + &parent->children ); + vmdev->dev.parent = parent; + vmdev->hv = hv; + vmdev->channel = channel; + vmdev->monitor = offer->monitor; + vmdev->signal = ( offer->monitored ? + vmbus_signal_monitor : + vmbus_signal_event ); + INIT_LIST_HEAD ( &vmdev->pages ); + vmdev->driver = driver; + vmdev->dev.driver_name = driver->name; + DBGC ( vmdev, "VMBUS %s has driver \"%s\"\n", + vmdev->dev.name, vmdev->driver->name ); + + } else if ( header->type == + cpu_to_le32 ( VMBUS_ALL_OFFERS_DELIVERED ) ) { + + break; + + } else { + DBGC ( vmbus, "VMBUS %p unexpected offer response type " + "%d\n", vmbus, le32_to_cpu ( header->type ) ); + rc = -EPROTO; + goto err_unexpected_offer; + } + } + + /* Probe all devices. We do this only after completing + * enumeration since devices will need to send and receive + * VMBus messages. + */ + list_for_each_entry ( vmdev, &parent->children, dev.siblings ) { + if ( ( rc = vmdev->driver->probe ( vmdev ) ) != 0 ) { + DBGC ( vmdev, "VMBUS %s could not probe: %s\n", + vmdev->dev.name, strerror ( rc ) ); + goto err_probe; + } + } + + return 0; + + err_probe: + /* Remove driver from each device that was already probed */ + list_for_each_entry_continue_reverse ( vmdev, &parent->children, + dev.siblings ) { + vmdev->driver->remove ( vmdev ); + } + err_unexpected_offer: + err_alloc_vmdev: + err_wait_for_message: + /* Free any devices allocated (but potentially not yet probed) */ + list_for_each_entry_safe ( vmdev, tmp, &parent->children, + dev.siblings ) { + list_del ( &vmdev->dev.siblings ); + free ( vmdev ); + } + err_post_message: + return rc; +} + +/** + * Remove channels + * + * @v hv Hyper-V hypervisor + * @v parent Parent device + */ +static void vmbus_remove_channels ( struct hv_hypervisor *hv __unused, + struct device *parent ) { + struct vmbus_device *vmdev; + struct vmbus_device *tmp; + + /* Remove devices */ + list_for_each_entry_safe ( vmdev, tmp, &parent->children, + dev.siblings ) { + vmdev->driver->remove ( vmdev ); + assert ( list_empty ( &vmdev->dev.children ) ); + assert ( vmdev->out == NULL ); + assert ( vmdev->in == NULL ); + assert ( vmdev->packet == NULL ); + assert ( list_empty ( &vmdev->pages ) ); + list_del ( &vmdev->dev.siblings ); + free ( vmdev ); + } +} + +/** + * Probe Hyper-V virtual machine bus + * + * @v hv Hyper-V hypervisor + * @v parent Parent device + * @ret rc Return status code + */ +int vmbus_probe ( struct hv_hypervisor *hv, struct device *parent ) { + struct vmbus *vmbus; + int rc; + + /* Allocate and initialise structure */ + vmbus = zalloc ( sizeof ( *vmbus ) ); + if ( ! vmbus ) { + rc = -ENOMEM; + goto err_alloc; + } + hv->vmbus = vmbus; + + /* Initialise message buffer pointer + * + * We use a pointer to the fixed-size Hyper-V received message + * buffer. This allows us to access fields within received + * messages without first checking the message size: any + * fields beyond the end of the message will read as zero. + */ + vmbus->message = ( ( void * ) hv->message->received.data ); + assert ( sizeof ( *vmbus->message ) <= + sizeof ( hv->message->received.data ) ); + + /* Allocate interrupt and monitor pages */ + if ( ( rc = hv_alloc_pages ( hv, &vmbus->intr, &vmbus->monitor_in, + &vmbus->monitor_out, NULL ) ) != 0 ) + goto err_alloc_pages; + + /* Enable message interrupt */ + hv_enable_sint ( hv, VMBUS_MESSAGE_SINT ); + + /* Negotiate protocol version */ + if ( ( rc = vmbus_negotiate_version ( hv ) ) != 0 ) + goto err_negotiate_version; + + /* Enumerate channels */ + if ( ( rc = vmbus_probe_channels ( hv, parent ) ) != 0 ) + goto err_probe_channels; + + return 0; + + vmbus_remove_channels ( hv, parent ); + err_probe_channels: + vmbus_unload ( hv ); + err_negotiate_version: + hv_disable_sint ( hv, VMBUS_MESSAGE_SINT ); + hv_free_pages ( hv, vmbus->intr, vmbus->monitor_in, vmbus->monitor_out, + NULL ); + err_alloc_pages: + free ( vmbus ); + err_alloc: + return rc; +} + +/** + * Remove Hyper-V virtual machine bus + * + * @v hv Hyper-V hypervisor + * @v parent Parent device + */ +void vmbus_remove ( struct hv_hypervisor *hv, struct device *parent ) { + struct vmbus *vmbus = hv->vmbus; + + vmbus_remove_channels ( hv, parent ); + vmbus_unload ( hv ); + hv_disable_sint ( hv, VMBUS_MESSAGE_SINT ); + hv_free_pages ( hv, vmbus->intr, vmbus->monitor_in, vmbus->monitor_out, + NULL ); + free ( vmbus ); +} diff --git a/qemu/roms/ipxe/src/interface/linux/linux_entropy.c b/qemu/roms/ipxe/src/interface/linux/linux_entropy.c index 4671a48da..0f8e45d36 100644 --- a/qemu/roms/ipxe/src/interface/linux/linux_entropy.c +++ b/qemu/roms/ipxe/src/interface/linux/linux_entropy.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** @file * diff --git a/qemu/roms/ipxe/src/interface/linux/linux_pci.c b/qemu/roms/ipxe/src/interface/linux/linux_pci.c index cbd825c18..0c140cb89 100644 --- a/qemu/roms/ipxe/src/interface/linux/linux_pci.c +++ b/qemu/roms/ipxe/src/interface/linux/linux_pci.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdio.h> #include <errno.h> diff --git a/qemu/roms/ipxe/src/interface/linux/linux_time.c b/qemu/roms/ipxe/src/interface/linux/linux_time.c index e3cbafec6..9e99fe9cd 100644 --- a/qemu/roms/ipxe/src/interface/linux/linux_time.c +++ b/qemu/roms/ipxe/src/interface/linux/linux_time.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** @file * diff --git a/qemu/roms/ipxe/src/interface/linux/linux_uaccess.c b/qemu/roms/ipxe/src/interface/linux/linux_uaccess.c index 5ab0b6b65..ea2d8057c 100644 --- a/qemu/roms/ipxe/src/interface/linux/linux_uaccess.c +++ b/qemu/roms/ipxe/src/interface/linux/linux_uaccess.c @@ -27,7 +27,6 @@ FILE_LICENCE(GPL2_OR_LATER); * */ -PROVIDE_UACCESS_INLINE(linux, phys_to_user); PROVIDE_UACCESS_INLINE(linux, user_to_phys); PROVIDE_UACCESS_INLINE(linux, virt_to_user); PROVIDE_UACCESS_INLINE(linux, user_to_virt); diff --git a/qemu/roms/ipxe/src/interface/smbios/smbios.c b/qemu/roms/ipxe/src/interface/smbios/smbios.c index 856943428..1dcf819c2 100644 --- a/qemu/roms/ipxe/src/interface/smbios/smbios.c +++ b/qemu/roms/ipxe/src/interface/smbios/smbios.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <string.h> diff --git a/qemu/roms/ipxe/src/interface/smbios/smbios_settings.c b/qemu/roms/ipxe/src/interface/smbios/smbios_settings.c index 83e4320e9..5eadfa081 100644 --- a/qemu/roms/ipxe/src/interface/smbios/smbios_settings.c +++ b/qemu/roms/ipxe/src/interface/smbios/smbios_settings.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <string.h> diff --git a/qemu/roms/ipxe/src/interface/xen/xenbus.c b/qemu/roms/ipxe/src/interface/xen/xenbus.c index ffc8aba3e..c328af443 100644 --- a/qemu/roms/ipxe/src/interface/xen/xenbus.c +++ b/qemu/roms/ipxe/src/interface/xen/xenbus.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdio.h> #include <errno.h> diff --git a/qemu/roms/ipxe/src/interface/xen/xengrant.c b/qemu/roms/ipxe/src/interface/xen/xengrant.c index be12b23dc..269cd5836 100644 --- a/qemu/roms/ipxe/src/interface/xen/xengrant.c +++ b/qemu/roms/ipxe/src/interface/xen/xengrant.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <strings.h> diff --git a/qemu/roms/ipxe/src/interface/xen/xenstore.c b/qemu/roms/ipxe/src/interface/xen/xenstore.c index b96982927..23424a926 100644 --- a/qemu/roms/ipxe/src/interface/xen/xenstore.c +++ b/qemu/roms/ipxe/src/interface/xen/xenstore.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <stdarg.h> @@ -238,6 +242,10 @@ static int xenstore_response ( struct xen_hypervisor *xen, uint32_t req_id, char *string; int rc; + /* Wait for response to become available */ + while ( ! xenevent_pending ( xen, xen->store.port ) ) + cpu_nap(); + /* Receive message header */ xenstore_recv ( xen, &msg, sizeof ( msg ) ); *len = msg.len; |