[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[PATCH 2/3] net: Implement virtio-net Driver for Qemu in GRUB
From: |
Andrew Hamilton |
Subject: |
[PATCH 2/3] net: Implement virtio-net Driver for Qemu in GRUB |
Date: |
Thu, 28 Nov 2024 12:41:41 -0600 |
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 <adhamilt@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