Provide virtio-net driver for network interface support in a virtual environment such as Qemu. This was written to comply with virtio version 1.x. This uses a virtio-pci interface.
Signed-off-by: Andrew Hamilton <adham...@gmail.com> --- grub-core/Makefile.core.def | 12 + grub-core/net/drivers/virtio/virtionet.c | 1000 ++++++++++++++++++++++ 2 files changed, 1012 insertions(+) create mode 100644 grub-core/net/drivers/virtio/virtionet.c diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def index 063ef5dd7..89b7bdd03 100644 --- a/grub-core/Makefile.core.def +++ b/grub-core/Makefile.core.def @@ -1625,6 +1625,18 @@ module = { enable = i386_pc; }; +module = { + name = virtionet; + common = net/drivers/virtio/virtionet.c; + enable = i386_coreboot; + enable = i386_qemu; + enable = i386_multiboot; + enable = i386_ieee1275; + enable = i386_pc; + enable = i386_efi; + enable = x86_64_efi; +}; + module = { name = gettext; common = gettext/gettext.c; diff --git a/grub-core/net/drivers/virtio/virtionet.c b/grub-core/net/drivers/virtio/virtionet.c new file mode 100644 index 000000000..191795eb3 --- /dev/null +++ b/grub-core/net/drivers/virtio/virtionet.c @@ -0,0 +1,1000 @@ +/* + * virtionet.c - Provide virtio-net driver for network interface + * support in a virtual environment such as Qemu. + * This was written to comply with virtio version + * 1.x. This uses a virtio-pci interface. + * + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2024 Free Software Foundation, Inc. + * + * GRUB 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 3 of the License, or + * (at your option) any later version. + * + * GRUB 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 GRUB. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <grub/dl.h> +#include <grub/lib/crc.h> +#include <grub/loader.h> +#include <grub/misc.h> +#include <grub/mm.h> +#include <grub/net.h> +#include <grub/pci.h> +#include <grub/time.h> +#include <grub/types.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +/* Driver Conformance Targets (per virtio spec 1.2): + * 1. Clause 1 (virtio Driver) + * 2. Clause 2 (virtio PCI Driver) + * 3. Clause 5 (virtio Network Driver) + * 4. Clause 46 (virtio Legacy) - Legacy Devices Not Supported + * + * Limitations: + * 1. Currently assumes byte order between Qemu (device) and + * GRUB (driver) over PCI is the same - may not work + * for big-endian devices or CPUs. + * 2. Has been tested on i386-pc and x86_64-efi GRUB environments + * running on an x86_64 processor. Other platforms / CPUs may + * need additional testing. + * 3. If additional "virtio" device support is added to GRUB in + * the future it may make sense to split some of the functions + * in this code out (such as for virtio queues, virtio PCI). + * 4. Certainly isn't the fastest implementation of a virtio-net + * driver, but lays a groundwork for incremental improvement + * if needed. Some more complex (but faster) virtio-net features + * are not used for simplicity. + * 5. This driver will support at most one virtio-net device. + */ + +#define GRUB_ALIGN_4096 __attribute__ ((aligned (4096))) +#define GRUB_VIOPCI_MAX_BARS (6) +#define GRUB_VIO_BUFF_CNT (2048) +#define GRUB_VIONET_RX_Q_SEL (0) +#define GRUB_VIONET_TX_Q_SEL (1) + +struct virtio_pci_cap_struct_t +{ + grub_uint8_t cap_vndr; + grub_uint8_t cap_next; + grub_uint8_t cap_len; + grub_uint8_t cfg_type; + grub_uint8_t bar; + grub_uint8_t id; + grub_uint8_t padding[2]; + grub_uint32_t offset; + grub_uint32_t length; +} GRUB_PACKED; +typedef struct virtio_pci_cap_struct_t virtio_pci_cap_t; + +#define GRUB_VIRTIO_PCI_CAP_COMMON_CFG 1 +#define GRUB_VIRTIO_PCI_CAP_NOTIFY_CFG 2 +#define GRUB_VIRTIO_PCI_CAP_DEVICE_CFG 4 + +struct virtio_pci_common_cfg_struct_t +{ + grub_uint32_t device_feature_select_rw; + grub_uint32_t device_feature_ro; + grub_uint32_t driver_feature_select_rw; + grub_uint32_t driver_feature_rw; + grub_uint16_t config_msix_vector_rw; + grub_uint16_t num_queues_ro; + grub_uint8_t device_status_rw; + grub_uint8_t config_generation_ro; + grub_uint16_t queue_select_rw; + grub_uint16_t queue_size_rw; + grub_uint16_t queue_msix_vector_rw; + grub_uint16_t queue_enable_rw; + grub_uint16_t queue_notify_off_ro; + grub_uint32_t queue_desc_lo_rw; + grub_uint32_t queue_desc_hi_rw; + grub_uint32_t queue_driver_lo_rw; + grub_uint32_t queue_driver_hi_rw; + grub_uint32_t queue_device_lo_rw; + grub_uint32_t queue_device_hi_rw; + grub_uint16_t queue_notify_data_ro; + grub_uint16_t queue_reset_rw; +} GRUB_PACKED; +typedef struct virtio_pci_common_cfg_struct_t virtio_pci_common_cfg_t; + +struct grub_virtio_net_config_struct_t +{ + grub_uint8_t mac[6]; + grub_uint16_t status; + grub_uint16_t max_virtqueue_pairs; + grub_uint16_t mtu; + grub_uint32_t speed; + grub_uint8_t duplex; + grub_uint8_t rss_max_key_size; + grub_uint16_t rss_max_indirection_table_length; + grub_uint32_t supported_hash_types; +} GRUB_PACKED; + +typedef struct grub_virtio_net_config_struct_t grub_virtio_net_config_t; + +/* This marks a buffer as device write-only (otherwise device read-only). */ +#define GRUB_VIRTQ_DESC_F_WRITE 2 + +#define GRUB_VIO_Q_SZ (4) + +struct virtq_desc +{ + grub_uint64_t addr; + grub_uint32_t len; + grub_uint16_t flags; + grub_uint16_t next; +} GRUB_PACKED; + +#define GRUB_VIRTQ_AVAIL_F_NO_INTERRUPT 1 +struct virtq_avail +{ + grub_uint16_t flags; + grub_uint16_t idx; + grub_uint16_t ring[GRUB_VIO_Q_SZ]; + grub_uint16_t used_event; /* Only if VIRTIO_F_EVENT_IDX */ +} GRUB_PACKED GRUB_ALIGN_4096; + +struct virtq_used_elem +{ + grub_uint32_t id; + grub_uint32_t len; +} GRUB_PACKED; + +struct virtq_used +{ + grub_uint16_t flags; + grub_uint16_t idx; + struct virtq_used_elem ring[GRUB_VIO_Q_SZ]; + grub_uint16_t avail_event; /* Only if VIRTIO_F_EVENT_IDX */ +} GRUB_PACKED GRUB_ALIGN_4096; + +struct virtq +{ + struct virtq_desc desc[GRUB_VIO_Q_SZ]; + struct virtq_avail avail; + struct virtq_used used; +}; + +static volatile grub_uint8_t rx_buff[GRUB_VIO_Q_SZ] + [GRUB_VIO_BUFF_CNT] GRUB_ALIGN_4096; +static volatile grub_uint8_t tx_buff[GRUB_VIO_Q_SZ] + [GRUB_VIO_BUFF_CNT] GRUB_ALIGN_4096; + +static struct virtq virtq_rx; +static struct virtq virtq_tx; + +struct grub_virtionetcard_data +{ + volatile virtio_pci_common_cfg_t *pci_common; + volatile grub_uint16_t *virtio_notify; + volatile grub_virtio_net_config_t *virtio_net_cfg; + struct virtq *v_rx; + grub_uint16_t v_rx_last_seen_used; + grub_uint16_t v_tx_last_seen_used; + grub_uint32_t rx_notify_idx; + grub_uint32_t tx_notify_idx; + struct virtq *v_tx; + int card_found; +}; + +struct grub_virtio_net_hdr_struct_t +{ + grub_uint8_t flags; + grub_uint8_t gso_type; + grub_uint16_t hdr_len; + grub_uint16_t gso_size; + grub_uint16_t csum_start; + grub_uint16_t csum_offset; + grub_uint16_t num_buffers; +} GRUB_PACKED; + +typedef struct grub_virtio_net_hdr_struct_t grub_virtio_net_hdr_t; + +#define GRUB_VIRTIO_NET_DEVSTAT_ACKNOWLEDGE (1) +#define GRUB_VIRTIO_NET_DEVSTAT_DRIVER (2) +#define GRUB_VIRTIO_NET_DEVSTAT_FAILED (128) +#define GRUB_VIRTIO_NET_DEVSTAT_FEATURES_OK (8) +#define GRUB_VIRTIO_NET_DEVSTAT_DRIVER_OK (4) + +/* Device has specific MAC address - bit 5 */ +#define GRUB_VIRTIO_NET_F_MAC_L (1 << 5) +/* Modern driver - v1.0 and newer - bit 32 */ +#define GRUB_VIRTIO_F_VERSION_1_U (1) + +/* Vendor 1AF4 with device ID 1000 is a transitional network device. */ +#define GRUB_VIRTIO_NET_TRANS_PCIID 0x10001AF4 + +/* Vendor 1AF4 with device ID 1041 is a regular network device. */ +#define GRUB_VIRTIO_NET_PCIID 0x10411AF4 + +/* Perform a memory barrier to ensure transactions are complete. */ +/* This is required by the specification in certain situations. */ +#if defined(__x86_64__) +#define MB() asm volatile ("mfence; sfence;" ::: "memory"); +#elif defined(__i386__) +#define MB() asm volatile ("lock; addl $0,0(%%esp)" ::: "memory") +#elif defined(__aarch64__) || defined(__arm__) +#define MB() asm volatile ("dsb sy" ::: "memory") +#else +/* No-op so code compiles. */ +#define MB() do { } while (0); +#endif + +/* Reset the virio-net device */ +static int +grub_virtio_net_dev_reset (volatile virtio_pci_common_cfg_t *const net_dev) +{ + if (net_dev == NULL) + { + return GRUB_ERR_BAD_ARGUMENT; + } + + /* Tell the virtio net device to reset. */ + net_dev->device_status_rw = 0; + + /* The virtio spec says to check that the device is done with + * reset by checking that device status is zero. In testing + * this appeared to happen "instantly" but allow up to one + * second for the device to finish just in case. */ + int count = 0; + while (net_dev->device_status_rw != 0 && count < 20) + { + count++; + grub_millisleep (50); + } + + return GRUB_ERR_NONE; +} + +/* Initialize the virtio queues. */ +static void +grub_vio_q_setup (volatile virtio_pci_common_cfg_t *const net_dev, + const grub_uint16_t queue_sel, + volatile grub_uint8_t buff[GRUB_VIO_Q_SZ][GRUB_VIO_BUFF_CNT], + struct virtq *virtq, + struct virtq **v_q) +{ + + /* Initialize virtual queues for the device. */ + for (int i = 0; i < GRUB_VIO_Q_SZ; i++) + { + virtq->desc[i].addr = (grub_addr_t)&buff[i]; + virtq->desc[i].len = GRUB_VIO_BUFF_CNT; + virtq->desc[i].flags = GRUB_VIRTQ_DESC_F_WRITE; + + virtq->avail.ring[i] = i; + } + + virtq->avail.flags = GRUB_VIRTQ_AVAIL_F_NO_INTERRUPT; + virtq->avail.idx = 0; + + virtq->used.flags = 0; + virtq->used.idx = 0; + + *v_q = virtq; + + MB (); + + /* Setup the driver virtual queue */ + net_dev->queue_select_rw = queue_sel; + net_dev->queue_size_rw = GRUB_VIO_Q_SZ; + net_dev->queue_desc_lo_rw + = (grub_uint32_t)((grub_addr_t)&virtq->desc[0] & 0xFFFFFFFFu); + net_dev->queue_driver_lo_rw + = (grub_uint32_t)((grub_addr_t)&virtq->avail & 0xFFFFFFFFu); + net_dev->queue_device_lo_rw + = (grub_uint32_t)((grub_addr_t)&virtq->used & 0xFFFFFFFFu); +#if GRUB_CPU_SIZEOF_VOID_P == 8 + net_dev->queue_desc_hi_rw + = (grub_uint32_t)(((grub_addr_t)&virtq->desc[0] >> 32u) & 0xFFFFFFFFu); + net_dev->queue_driver_hi_rw + = (grub_uint32_t)(((grub_addr_t)&virtq->avail >> 32u) & 0xFFFFFFFFu); + net_dev->queue_device_hi_rw + = (grub_uint32_t)(((grub_addr_t)&virtq->used >> 32u) & 0xFFFFFFFFu); +#else /* GRUB_CPU_SIZEOF_VOID_P != 8 */ + net_dev->queue_desc_hi_rw = 0; + net_dev->queue_driver_hi_rw = 0; + net_dev->queue_device_hi_rw = 0; +#endif /* GRUB_CPU_SIZEOF_VOID_P check */ + net_dev->queue_enable_rw = 1; + + MB (); + + return; +} + +/* + * Initialize the virtio-net device + * + * For required steps, reference virtio spec 1.2 section 3.1.1 + * "Driver Requirements: Device Initialization" + */ +static grub_err_t +grub_virtio_net_dev_init (volatile virtio_pci_common_cfg_t *const net_dev, + volatile grub_uint16_t *const virtio_notify, + const grub_uint32_t notify_len, + const grub_uint32_t notify_multiplier, + grub_uint32_t *rx_notify_idx, + grub_uint32_t *tx_notify_idx, struct virtq **v_rx, + struct virtq **v_tx) +{ + grub_err_t ret_code = GRUB_ERR_NONE; + if (net_dev == NULL || virtio_notify == NULL || v_rx == NULL || v_tx == NULL) + { + grub_dprintf ("vnet", "param is null error"); + return GRUB_ERR_BAD_ARGUMENT; + } + + if ((ret_code = grub_virtio_net_dev_reset (net_dev)) != GRUB_ERR_NONE) + { + grub_dprintf ("vnet", "grub_virtio_net_dev_reset error"); + return ret_code; + } + + /* Driver has noticed the device. */ + net_dev->device_status_rw |= GRUB_VIRTIO_NET_DEVSTAT_ACKNOWLEDGE; + + MB (); + + /* Driver knows how to drive the device. */ + net_dev->device_status_rw |= GRUB_VIRTIO_NET_DEVSTAT_DRIVER; + + MB (); + + net_dev->device_feature_select_rw = 0; + + /* Read feature bits and write the subset of features we support*/ + grub_uint32_t dev_feat_0_31 = net_dev->device_feature_ro; + net_dev->device_feature_select_rw = 1; /* Select dev feature 32 to 63 */ + + grub_uint32_t dev_feat_32_63 = net_dev->device_feature_ro; + grub_dprintf ("vnet", "dev features: %x %x\n", dev_feat_0_31, + dev_feat_32_63); + + /* Check device feature vs. what we support in the driver. */ + if ((dev_feat_32_63 & GRUB_VIRTIO_F_VERSION_1_U) + != GRUB_VIRTIO_F_VERSION_1_U) + { + grub_dprintf ("vnet", "error: legacy device not supported\n"); + net_dev->device_status_rw = GRUB_VIRTIO_NET_DEVSTAT_FAILED; + MB (); + return GRUB_ERR_BAD_DEVICE; + } + else if ((dev_feat_0_31 & GRUB_VIRTIO_NET_F_MAC_L) + != GRUB_VIRTIO_NET_F_MAC_L) + { + grub_dprintf ("vnet", "error: device does not support MAC reporting\n"); + net_dev->device_status_rw = GRUB_VIRTIO_NET_DEVSTAT_FAILED; + MB (); + return GRUB_ERR_BAD_DEVICE; + } + + /* Set the negotiated features to the device. */ + net_dev->driver_feature_select_rw = 0; + grub_uint32_t driver_features = GRUB_VIRTIO_NET_F_MAC_L; + net_dev->driver_feature_rw = (dev_feat_0_31 & driver_features); + + net_dev->driver_feature_select_rw = 1; + driver_features = GRUB_VIRTIO_F_VERSION_1_U; + net_dev->driver_feature_rw = (dev_feat_32_63 & driver_features); + + /* Driver accepts features. */ + net_dev->device_status_rw |= GRUB_VIRTIO_NET_DEVSTAT_FEATURES_OK; + + MB (); + + if ((net_dev->device_status_rw & GRUB_VIRTIO_NET_DEVSTAT_FEATURES_OK) + != GRUB_VIRTIO_NET_DEVSTAT_FEATURES_OK) + { + grub_dprintf ("vnet", "error: device did not accept features\n"); + return GRUB_ERR_BAD_DEVICE; + } + + /* Initialize RX virtual queues for the device. */ + grub_vio_q_setup (net_dev, GRUB_VIONET_RX_Q_SEL, rx_buff, &virtq_rx, v_rx); + + /* Initialize TX virtual queues for the device. */ + grub_vio_q_setup (net_dev, GRUB_VIONET_TX_Q_SEL, tx_buff, &virtq_tx, v_tx); + + /* Everything is setup in the driver now. */ + net_dev->device_status_rw |= GRUB_VIRTIO_NET_DEVSTAT_DRIVER_OK; + + /* It seems required to start the receive available index at 1. */ + virtq_rx.avail.idx = 1; + + MB (); + + net_dev->queue_select_rw = GRUB_VIONET_TX_Q_SEL; + /* Device is suppossed to present a notification cap length + * that is at least as large as notify_offs plus 2 bytes. */ + grub_uint32_t notify_offs = net_dev->queue_notify_off_ro * notify_multiplier; + if ((notify_offs + 2) >= notify_len) + { + grub_dprintf ("vnet", "error: bad tx notify offset\n"); + return GRUB_ERR_BAD_DEVICE; + } + *tx_notify_idx = (notify_offs) / sizeof (virtio_notify[0]); + + net_dev->queue_select_rw = GRUB_VIONET_RX_Q_SEL; + notify_offs = net_dev->queue_notify_off_ro * notify_multiplier; + if ((notify_offs + 2) >= notify_len) + { + grub_dprintf ("vnet", "error: bad rx notify offset\n"); + return GRUB_ERR_BAD_DEVICE; + } + *rx_notify_idx = (notify_offs) / sizeof (virtio_notify[0]); + + virtio_notify[*rx_notify_idx] = 0; + virtio_notify[*tx_notify_idx] = 1; + MB (); + + return GRUB_ERR_NONE; +} + +static struct grub_preboot *fini_hnd; + +/* + * Check if the given PCI(e) device is a virtio-net PCI device. If so, get + * the processor side BAR addresses. The find the device PCI capability + * registers, and then find the virtio PCI extended capability registers to + * find the required info for initializing and using a virtio-net device. + * If that succeeded, then map the extended capability info to RAM so they + * can easily be accessed. Pass this needed information on to the virtio-net + * init function. + */ +static int +grub_virtionet_pci_iter (grub_pci_device_t dev, grub_pci_id_t pciid, + void *data) +{ + grub_uint32_t val; + grub_pci_address_t addr; + int i; + grub_uint32_t idx; + struct grub_virtionetcard_data *virtdata + = (struct grub_virtionetcard_data *)data; + virtio_pci_cap_t virtio_pci_cap; + virtio_pci_cap_t virtio_cfg_cap; + virtio_pci_cap_t virtio_notify_cap; + virtio_pci_cap_t virtio_net_dev_cap; + /* Ensure our buffer is big enough with possible padding. */ + grub_uint32_t virtio_pci_buff[(sizeof(virtio_pci_cap_t) / + sizeof(grub_uint32_t)) + 1]; + int ttl; + grub_addr_t bar_addr[GRUB_VIOPCI_MAX_BARS]; + + if (virtdata == NULL) + { + grub_error (GRUB_ERR_BUG, "virtdata is NULL."); + grub_dprintf ("vnet", "Error: virtdata is NULL\n"); + return 0; + } + + grub_memset (bar_addr, 0, sizeof (bar_addr)); + grub_memset (&virtio_pci_cap, 0, sizeof (virtio_pci_cap)); + + /* If the device was already found, skip this one - we only + * support one for now. */ + if (virtdata->card_found) + { + return 0; + } + + /* If this is not a virtio-net device, just return. Note + * that we support both "transitional" and regular IDs. */ + if ((pciid != GRUB_VIRTIO_NET_TRANS_PCIID) + && (pciid != GRUB_VIRTIO_NET_PCIID)) + { + virtdata->card_found = 0; + return 0; + } + + virtdata->card_found = 1; + + grub_dprintf ("vnet", "Found device OK %x\n", pciid); + + /* Look through the max. 6 PCI config regs for the BAR + * addresses. */ + int reg = GRUB_PCI_REG_ADDRESSES; + i = 0; + while (reg < GRUB_PCI_REG_CIS_POINTER) + { + grub_addr_t bar_tmp; + addr = grub_pci_make_address (dev, reg); + reg += sizeof (grub_uint32_t); + bar_tmp = grub_pci_read (addr); + + if ((bar_tmp & GRUB_PCI_ADDR_SPACE_MASK) == GRUB_PCI_ADDR_SPACE_MEMORY) + { +#if GRUB_CPU_SIZEOF_VOID_P == 8 + if ((bar_tmp & GRUB_PCI_ADDR_MEM_TYPE_MASK) + == GRUB_PCI_ADDR_MEM_TYPE_64) + { + /* If the BAR memory is 64-bit then it will occupy + * two BAR slots in the PCI config registers. */ + addr = grub_pci_make_address (dev, reg); + reg += sizeof (grub_uint32_t); + bar_tmp |= ((grub_uint64_t)grub_pci_read (addr)) << 32; + } +#endif /* GRUB_CPU_SIZEOF_VOID_P == 8 */ + bar_addr[i] = (bar_tmp & GRUB_PCI_ADDR_MEM_MASK); + } + i++; + } + + /* Find the virtio_pci_cap capability (0x09). */ + grub_uint8_t pos = grub_pci_find_capability (dev, 0x09); + if (pos <= GRUB_PCI_REG_CAP_POINTER) + { + grub_error (GRUB_ERR_BAD_DEVICE, "cap pointer invalid."); + grub_dprintf ("vnet", "error: cap pointer invalid %x\n", pos); + virtdata->card_found = 0; + return 0; + } + + /* Ensure we don't get stuck in an infinte loop if the + * PCI linked list doesn't resolve as expected. */ + ttl = 48; + bool got_cfg = false; + bool got_notify = false; + bool got_net_dev_cfg = false; + bool got_all = false; + grub_uint8_t notify_offset = 0; + /* Look through the virtio-pci device extended capabilities to find the + * three structures required by this driver to get the BAR where the data + * is located and offset into the BAR.*/ + do + { + for (idx = 0; idx < (sizeof (virtio_pci_cap) / sizeof (grub_uint32_t)); + idx++) + { + addr = grub_pci_make_address (dev, + pos + (idx * sizeof (grub_uint32_t))); + val = grub_pci_read (addr); + virtio_pci_buff[idx] = val; + } + grub_memcpy (&virtio_pci_cap, virtio_pci_buff, sizeof (virtio_pci_cap)); + + if (virtio_pci_cap.cfg_type == GRUB_VIRTIO_PCI_CAP_COMMON_CFG) + { + got_cfg = true; + grub_memcpy (&virtio_cfg_cap, &virtio_pci_cap, + sizeof (virtio_cfg_cap)); + } + else if (virtio_pci_cap.cfg_type == GRUB_VIRTIO_PCI_CAP_NOTIFY_CFG) + { + notify_offset = pos + sizeof (virtio_pci_cap); + got_notify = true; + grub_memcpy (&virtio_notify_cap, &virtio_pci_cap, + sizeof (virtio_notify_cap)); + } + else if (virtio_pci_cap.cfg_type == GRUB_VIRTIO_PCI_CAP_DEVICE_CFG) + { + got_net_dev_cfg = true; + grub_memcpy (&virtio_net_dev_cap, &virtio_pci_cap, + sizeof (virtio_net_dev_cap)); + } + got_all = got_cfg && got_notify && got_net_dev_cfg; + + pos = virtio_pci_cap.cap_next; + ttl--; + } + while (((!got_all)) && (pos > GRUB_PCI_REG_CAP_POINTER) && (ttl > 0)); + + if (!got_all) + { + grub_error (GRUB_ERR_BAD_DEVICE, + "virtio cfg, notify, or netdev cap not found."); + virtdata->card_found = 0; + return 0; + } + + if ((virtio_cfg_cap.bar >= GRUB_VIOPCI_MAX_BARS) || + (virtio_notify_cap.bar >= GRUB_VIOPCI_MAX_BARS) || + (virtio_net_dev_cap.bar >= GRUB_VIOPCI_MAX_BARS) || + (bar_addr[virtio_cfg_cap.bar] == 0) || + (bar_addr[virtio_notify_cap.bar] == 0) || + (bar_addr[virtio_net_dev_cap.bar] == 0)) + { + grub_error (GRUB_ERR_BAD_DEVICE, "virtio pci bar invalid."); + virtdata->card_found = 0; + return 0; + } + + /* Enable MEM address spaces and set Bus Master */ + grub_pci_address_t rcaddr + = grub_pci_make_address (dev, GRUB_PCI_REG_COMMAND); + grub_pci_write_word (rcaddr, grub_pci_read_word (rcaddr) + | GRUB_PCI_COMMAND_MEM_ENABLED + | GRUB_PCI_COMMAND_BUS_MASTER); + + virtdata->pci_common = grub_pci_device_map_range ( + dev, bar_addr[virtio_cfg_cap.bar] + virtio_cfg_cap.offset, + sizeof (virtio_pci_common_cfg_t)); + + if (virtdata->pci_common == NULL) + { + grub_error (GRUB_ERR_BAD_DEVICE, "virtio_pci_common_cfg is NULL."); + virtdata->card_found = 0; + return 0; + } + + virtdata->virtio_notify = grub_pci_device_map_range ( + dev, bar_addr[virtio_notify_cap.bar] + virtio_notify_cap.offset, + virtio_notify_cap.length); + + if (virtdata->virtio_notify == NULL) + { + grub_error (GRUB_ERR_BAD_DEVICE, "virtio_notify is NULL."); + virtdata->card_found = 0; + return 0; + } + + virtdata->virtio_net_cfg = grub_pci_device_map_range ( + dev, bar_addr[virtio_net_dev_cap.bar] + virtio_net_dev_cap.offset, + virtio_net_dev_cap.length); + + if (virtdata->virtio_net_cfg == NULL) + { + grub_error (GRUB_ERR_BAD_DEVICE, "virtio_net_cfg is NULL."); + virtdata->card_found = 0; + return 0; + } + + rcaddr = grub_pci_make_address (dev, notify_offset); + + grub_uint32_t notify_off_multiplier = grub_pci_read (rcaddr); + if (notify_off_multiplier >= virtio_notify_cap.length) + { + grub_error (GRUB_ERR_BAD_DEVICE, + "notify_off_multiplier too large %u >= %u.", + notify_off_multiplier, virtio_notify_cap.length); + virtdata->card_found = 0; + return 0; + } + + grub_err_t err = + grub_virtio_net_dev_init ( + virtdata->pci_common, virtdata->virtio_notify, + virtio_notify_cap.length, notify_off_multiplier, + &virtdata->rx_notify_idx, &virtdata->tx_notify_idx, &virtdata->v_rx, + &virtdata->v_tx); + if (err != GRUB_ERR_NONE) + { + grub_error (err, "grub_virtio_net_dev_init error."); + return 0; + } + + return 0; +} + +/* Iterate through PCI(e) devices looking for virtio-net PCI devices */ +static void +grub_virtionet_pci_scan (struct grub_virtionetcard_data *virtdata) +{ + grub_pci_iterate (grub_virtionet_pci_iter, virtdata); + return; +} + +static grub_err_t +grub_virtionet_restore_hw (void) +{ + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_virtionet_open (struct grub_net_card *dev __attribute__ ((unused))) +{ + return GRUB_ERR_NONE; +} + +static void +grub_virtionet_close (struct grub_net_card *dev __attribute__ ((unused))) +{ + return; +} + +/* Write (transmit) data out the virtio-net device. */ +static int +grub_virtionet_write (struct grub_virtionetcard_data *card, + grub_uint8_t *txbuf, grub_size_t txbufsize, + grub_ssize_t *actual) +{ + grub_uint32_t tx_len; + + if (card == NULL || card->v_tx == NULL || txbuf == NULL || actual == NULL) + { + grub_error (GRUB_ERR_BUG, "grub_virtionet_write NULL parameter."); + return -1; + } + + tx_len = txbufsize; + + grub_uint16_t avail_idx = card->v_tx->avail.idx % GRUB_VIO_Q_SZ; + grub_addr_t tx_q_addr = (grub_addr_t)(card->v_tx->desc[avail_idx].addr); + + grub_memset ((grub_uint8_t *)tx_q_addr, 0, sizeof (grub_virtio_net_hdr_t)); + grub_memcpy ((grub_uint8_t *)(tx_q_addr + sizeof (grub_virtio_net_hdr_t)), + txbuf, tx_len); + *actual = tx_len; + card->v_tx_last_seen_used = card->v_tx->used.idx; + card->v_tx->desc[avail_idx].flags = 0; + card->v_tx->desc[avail_idx].len = tx_len + sizeof (grub_virtio_net_hdr_t); + card->v_tx->avail.flags = GRUB_VIRTQ_AVAIL_F_NO_INTERRUPT; + MB (); + /* Indicate we processed / freed the used buffer and made it available. + * to the device again so we can get a new packet. + */ + grub_uint16_t new_avail_idx = card->v_tx->avail.idx + 1; + card->v_tx->avail.idx = new_avail_idx; + MB (); + card->virtio_notify[card->tx_notify_idx] = new_avail_idx; + MB (); + + return 0; +} + +/* Transmit data out the virtio-net device. */ +static grub_err_t +grub_virtionet_send (struct grub_net_card *dev, struct grub_net_buff *pack) +{ + grub_ssize_t actual; + int status; + struct grub_virtionetcard_data *data = dev->data; + grub_size_t len; + + len = (pack->tail - pack->data); + if (len > dev->mtu) + len = dev->mtu; + + grub_memcpy (dev->txbuf, pack->data, len); + status = grub_virtionet_write (data, dev->txbuf, len, &actual); + + if (status) + return grub_error (GRUB_ERR_IO, N_ ("couldn't send network packet")); + return GRUB_ERR_NONE; +} + +/* Read (receive) data in from the virtio-net device. */ +static int +grub_virtionet_read (struct grub_virtionetcard_data *card, void *rcvbuf, + grub_size_t rcvbufsize, grub_ssize_t *actual) +{ + grub_uint32_t rx_len; + + if (card == NULL || card->v_rx == NULL || rcvbuf == NULL || actual == NULL) + { + grub_error (GRUB_ERR_BUG, "grub_virtionet_read NULL parameter."); + return -1; + } + + /* Store temporary (RAM local) versions of these values to avoid any + * potential for them to change unexpected when checking multiple + * times. + */ + grub_uint16_t shdw_last_used = card->v_rx_last_seen_used; + grub_uint16_t shadow_used = card->v_rx->used.idx; + + /* The device will indicate it "used" a receive buffer (or buffers) + * by incrementing the used index by the number of buffers used. + * + * For us, this means new data was received, so process it. + */ + if (shdw_last_used != shadow_used) + { + grub_uint16_t shdw_last_u_mod = shdw_last_used % GRUB_VIO_Q_SZ; + /* Ensure we only copy at most receive buff size. */ + rx_len + = grub_min (rcvbufsize, card->v_rx->used.ring[shdw_last_u_mod].len); + + grub_addr_t rx_q_addr + = (grub_addr_t)(card->v_rx->desc[shdw_last_u_mod].addr); + grub_memcpy (rcvbuf, (grub_uint8_t *)rx_q_addr, rx_len); + *actual = rx_len; + + card->v_rx->desc[shdw_last_u_mod].flags = GRUB_VIRTQ_DESC_F_WRITE; + card->v_rx->avail.flags = GRUB_VIRTQ_AVAIL_F_NO_INTERRUPT; + card->v_rx_last_seen_used++; + MB (); + /* Indicate we processed / freed the used buffer and made it available. + * to the device again so we can get a new packet. + */ + grub_uint16_t avail_idx = card->v_rx->avail.idx + 1; + card->v_rx->avail.idx = avail_idx; + MB (); + card->virtio_notify[card->rx_notify_idx] = avail_idx; + MB (); + } + else + { + /* No new data. */ + *actual = 0; + } + + return 0; +} + +/* Receive data from the virtio-net device. */ +static struct grub_net_buff * +grub_virtionet_recv (struct grub_net_card *dev) +{ + grub_ssize_t actual = 0; + int rc = 0; + struct grub_virtionetcard_data *data = dev->data; + struct grub_net_buff *nb; + grub_ssize_t nb_size = 0; + grub_uint8_t *data_ptr = NULL; + + rc = grub_virtionet_read (data, dev->rcvbuf, dev->rcvbufsize, &actual); + + if (actual <= 0 || rc < 0) + return NULL; + + /* Although we don't use the virtio net hdr, check message size for + * sanity... there should be some actual data too. */ + if (actual <= (grub_ssize_t)sizeof (grub_virtio_net_hdr_t)) + { + grub_dprintf ("vnet", "error: rx too small min: %zi, got: %zi\n", + sizeof (grub_virtio_net_hdr_t), actual); + return NULL; + } + + /* Calculate amount of actual received data (without virtio header) */ + /* and then round up to the nearest 4 to ensure 4-byte alignment. */ + nb_size = (actual - sizeof (grub_virtio_net_hdr_t)); + nb = grub_netbuff_alloc (nb_size + 2); + if (!nb) + { + grub_error (GRUB_ERR_BUG, "grub_virtionet_read nb alloc failure."); + return NULL; + } + + /* Reserve 2 bytes so that 2 + 14/18 bytes of ethernet header is divisible + by 4. So that IP header is aligned on 4 bytes. */ + grub_netbuff_reserve (nb, 2); + + /* Copy the data after the virtio net hdr. */ + data_ptr = (grub_uint8_t *)dev->rcvbuf; + grub_memcpy (nb->data, &(data_ptr[sizeof (grub_virtio_net_hdr_t)]), nb_size); + + if (grub_netbuff_put (nb, nb_size)) + { + grub_error (GRUB_ERR_BUG, "grub_virtionet_read nb put failure."); + grub_netbuff_free (nb); + return NULL; + } + + return nb; +} + +struct grub_net_card_driver grub_virtionet_card_driver = + { + .open = grub_virtionet_open, + .close = grub_virtionet_close, + .send = grub_virtionet_send, + .recv = grub_virtionet_recv + }; + +struct grub_net_card grub_vionet_card = + { + .driver = &grub_virtionet_card_driver, + .name = "virtionet" + }; + +/* Close / cleanup / reset the virtio-net device. */ +static grub_err_t +grub_virtionet_fini_hw (int noreturn __attribute__ ((unused))) +{ + grub_err_t ret_code = GRUB_ERR_NONE; + virtio_pci_common_cfg_t *pci_common + = (virtio_pci_common_cfg_t *)grub_vionet_card.data; + + if (pci_common == NULL) + { + grub_dprintf ("vnet", "grub_virtionet_fini_hw: pci_common is NULL\n"); + return GRUB_ERR_NONE; + } + + if ((ret_code = grub_virtio_net_dev_reset (pci_common)) != GRUB_ERR_NONE) + { + grub_dprintf ("vnet", "grub_virtionet_fini_hw: dev reset error\n"); + return ret_code; + } + return GRUB_ERR_NONE; +} + +/* Module initialization for virtio-net */ +GRUB_MOD_INIT (virtionet) +{ + struct grub_virtionetcard_data *virtdata; + + virtdata = grub_zalloc (sizeof (struct grub_virtionetcard_data)); + if (!virtdata) + { + grub_dprintf ("vnet", "virtdata alloc failure.\n"); + grub_print_error (); + return; + } + + grub_virtionet_pci_scan (virtdata); + if (virtdata->card_found == 0) + { + grub_dprintf ("vnet", "card not found.\n"); + grub_print_error (); + return; + } + + grub_vionet_card.mtu = 1500; + /* Get device MAC into the driver and set type. */ + grub_net_link_level_address_t lla; + grub_memset (&lla, 0, sizeof (lla)); + grub_memcpy (&lla.mac, (grub_uint8_t *)virtdata->virtio_net_cfg->mac, 6); + lla.type = GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET; + grub_vionet_card.default_address = lla; + + grub_vionet_card.txbufsize = ALIGN_UP (grub_vionet_card.mtu, 64) + 256; + grub_vionet_card.rcvbufsize = ALIGN_UP (grub_vionet_card.mtu, 64) + 256; + + grub_vionet_card.txbuf = grub_malloc (grub_vionet_card.txbufsize); + if (!grub_vionet_card.txbuf) + { + grub_free (virtdata); + grub_print_error (); + return; + } + + grub_vionet_card.rcvbuf = grub_malloc (grub_vionet_card.rcvbufsize); + if (!grub_vionet_card.rcvbuf) + { + grub_free (virtdata); + grub_free (grub_vionet_card.txbuf); + grub_print_error (); + return; + } + grub_vionet_card.data = virtdata; + grub_vionet_card.flags = 0; + grub_vionet_card.idle_poll_delay_ms = 10; + grub_net_card_register (&grub_vionet_card); + + /* Make sure the virtio device is reset so it stops accessing memory */ + /* before booting the loader target. */ + fini_hnd = grub_loader_register_preboot_hook ( + grub_virtionet_fini_hw, grub_virtionet_restore_hw, + GRUB_LOADER_PREBOOT_HOOK_PRIO_DISK); +} + +/* Module cleanup function for virtio-net */ +GRUB_MOD_FINI (virtionet) +{ + /* Stop the device from access our memory. */ + grub_virtionet_fini_hw (0); + + /* Cleanup things. */ + grub_loader_unregister_preboot_hook (fini_hnd); + grub_net_card_unregister (&grub_vionet_card); + if (grub_vionet_card.rcvbuf != NULL) + { + grub_free (grub_vionet_card.rcvbuf); + grub_vionet_card.rcvbuf = NULL; + } + if (grub_vionet_card.txbuf != NULL) + { + grub_free (grub_vionet_card.txbuf); + grub_vionet_card.txbuf = NULL; + } + if (grub_vionet_card.data != NULL) + { + grub_free (grub_vionet_card.data); + grub_vionet_card.data = NULL; + } +} + -- 2.39.5 _______________________________________________ Grub-devel mailing list Grub-devel@gnu.org https://lists.gnu.org/mailman/listinfo/grub-devel