On 2026/06/11 11:30, Josh Hilke wrote:
Add a test for the IGB transmit rate limiting functionality. The test
verifies that TRL is enabled by setting the appropriate registers and
that data is transmitted at the appropriate rate based on chosen
target rate.

Signed-off-by: Josh Hilke <[email protected]>
---
  tests/qtest/igb-test.c | 161 +++++++++++++++++++++++++++++++++++++++++
  1 file changed, 161 insertions(+)

diff --git a/tests/qtest/igb-test.c b/tests/qtest/igb-test.c
index 3d397ea..9c93abf 100644
--- a/tests/qtest/igb-test.c
+++ b/tests/qtest/igb-test.c
@@ -34,9 +34,11 @@
  #include "qemu/iov.h"
  #include "qemu/module.h"
  #include "qemu/bitops.h"
+#include "qemu/timer.h"
  #include "libqos/libqos-malloc.h"
  #include "libqos/e1000e.h"
  #include "hw/net/igb_regs.h"
+#include "hw/pci/pci_regs.h"
#ifndef _WIN32 @@ -45,6 +47,71 @@ static const struct eth_header packet = {
      .h_source = E1000E_ADDRESS,
  };
+/*
+ * Calculate TRL rate factor for a given target rate.
+ *
+ * Rate Factor = 1 Gbps / Target Rate.
+ *
+ * The hardware TRLRC register stores the rate factor as a fixed-point number
+ * with 14 fractional bits. Scale 1Gbps by 2^14 before dividing to preserve
+ * precision in integer arithmetic.
+ */
+static uint32_t igb_trl_get_rate_factor(uint64_t rate_bytes_s)
+{
+    return (E1000_LINK_RATE_1GBPS << 14) / rate_bytes_s;
+}
+
+static void igb_trl_enable(QE1000E *d, uint64_t rate_bytes_s)
+{
+    /* Select Queue 0 */
+    e1000e_macreg_write(d, E1000_TRLDQSEL, 0);
+    /* Enable TRL with target rate */
+    e1000e_macreg_write(d, E1000_TRLRC,
+                    E1000_TRLRC_RS_ENA | 
igb_trl_get_rate_factor(rate_bytes_s));

Indents are somewhat off in this file.

Regards,
Akihiko Odaki

+}
+
+static void igb_trl_disable(QE1000E *d)
+{
+    /* Select Queue 0 */
+    e1000e_macreg_write(d, E1000_TRLDQSEL, 0);
+    /* Disable TRL */
+    e1000e_macreg_write(d, E1000_TRLRC, 0);
+}
+
+static void igb_msix_clear_pending(QPCIDevice *dev, uint16_t entry)
+{
+    uint64_t vector_offset =
+        dev->msix_table_off + (entry * PCI_MSIX_ENTRY_SIZE);
+    uint32_t val = qpci_io_readl(dev, dev->msix_table_bar,
+                                vector_offset + PCI_MSIX_ENTRY_VECTOR_CTRL);
+
+    /* Unmask (clears pending bit in QEMU) */
+    qpci_io_writel(dev, dev->msix_table_bar,
+                    vector_offset + PCI_MSIX_ENTRY_VECTOR_CTRL,
+                    val & ~PCI_MSIX_ENTRY_CTRL_MASKBIT);
+
+    /* Mask again */
+    qpci_io_writel(dev, dev->msix_table_bar,
+                    vector_offset + PCI_MSIX_ENTRY_VECTOR_CTRL,
+                    val | PCI_MSIX_ENTRY_CTRL_MASKBIT);
+}
+
+static void igb_read_tx_tail(QE1000E *d, void *descr)
+{
+    QE1000E_PCI *d_pci = container_of(d, QE1000E_PCI, e1000e);
+    uint32_t last_entry;
+    uint32_t tail;
+    uint32_t len;
+
+    tail = e1000e_macreg_read(d, E1000_TDT_A(0));
+    len = e1000e_macreg_read(d, E1000_TDLEN_A(0)) / E1000_RING_DESC_LEN;
+    last_entry = (tail + len - 1) % len;
+
+    qtest_memread(d_pci->pci_dev.bus->qts,
+                    d->tx_ring + last_entry * E1000_RING_DESC_LEN, descr,
+                    E1000_RING_DESC_LEN);
+}
+
  static void igb_send_verify(QE1000E *d, int *test_sockets, QGuestAllocator 
*alloc)
  {
      union e1000_adv_tx_desc descr;
@@ -70,6 +137,14 @@ static void igb_send_verify(QE1000E *d, int *test_sockets, 
QGuestAllocator *allo
      /* Wait for TX WB interrupt */
      e1000e_wait_isr(d, E1000E_TX0_MSG_ID);
+ /*
+     * Read the descriptor back from guest memory again, now that WB has
+     * occurred. This is required because if the packet was delayed by the
+     * trasmit rate limiter, e1000e_tx_ring_push read it back before QEMU
+     * actually processed it.
+     */
+    igb_read_tx_tail(d, &descr);
+
      /* Check DD bit */
      g_assert_cmphex(le32_to_cpu(descr.wb.status) & E1000_TXD_STAT_DD, ==,
                      E1000_TXD_STAT_DD);
@@ -133,6 +208,29 @@ static void igb_receive_verify(QE1000E *d, int 
*test_sockets, QGuestAllocator *a
      guest_free(alloc, data);
  }
+/* Returns the amount of microseconds it takes to transmit a 64-byte packet.*/
+static uint64_t igb_send_and_measure(QE1000E *d, int *test_sockets,
+                                     QGuestAllocator *alloc)
+{
+    QE1000E_PCI *d_pci = container_of(d, QE1000E_PCI, e1000e);
+    QPCIDevice *pdev = &d_pci->pci_dev;
+    QTestState *qts = global_qtest;
+    uint64_t start_time, end_time;
+
+    /* Clear any stale pending interrupt for this vector before we start */
+    igb_msix_clear_pending(pdev, E1000E_TX0_MSG_ID);
+
+    /* Clear EICR to allow new interrupts to be raised in MSI-X mode */
+    e1000e_macreg_write(d, E1000_EICR, 0xFFFFFFFF);
+
+    /* Send a test packet and measure the time it takes. */
+    start_time = qtest_clock_step(qts, 1);
+    igb_send_verify(d, test_sockets, alloc);
+    end_time = qtest_clock_step(qts, 1);
+
+    return end_time - start_time;
+}
+
  static void test_e1000e_init(void *obj, void *data, QGuestAllocator * alloc)
  {
      /* init does nothing */
@@ -191,6 +289,67 @@ static void test_igb_multiple_transfers(void *obj, void 
*data,
} +static void test_igb_transmit_rate_limiter(void *obj, void *data,
+                                           QGuestAllocator *alloc)
+{
+    QOSGraphObject *e_object = obj;
+    QE1000E_PCI *e1000e = obj;
+    QPCIDevice *dev = e_object->get_driver(e_object, "pci-device");
+    QE1000E *d = &e1000e->e1000e;
+    const uint64_t expected_baseline_elapsed = 10 * SCALE_US;
+    const uint64_t expected_max_elapsed = 530 * SCALE_US;
+    const uint64_t expected_min_elapsed = 500 * SCALE_US;
+    uint64_t elapsed;
+
+    if (qpci_check_buggy_msi(dev)) {
+        return;
+    }
+
+    /* Send a test packet to verify there's no rate limit. */
+    elapsed = igb_send_and_measure(d, data, alloc);
+
+    /*
+     * QEMU doesn't emulate the hardware line rate of the device, so the packet
+     * is processed instantly (requiring 0 clock steps in wait_isr, which steps
+     * by 10us). Give a 10us margin for error.
+     */
+    g_assert_cmpint(elapsed, <, expected_baseline_elapsed);
+
+    /*
+     * Enable TRL and set the target rate to:
+     * (E1000_LINK_RATE_1GBPS / 1000) = 125 KB/s.
+     */
+    igb_trl_enable(d, E1000_LINK_RATE_1GBPS / 1000);
+
+    /*
+     * Send Packet 1 (no delay)
+     * The first packet triggers rate limiting for subsequent transmissions.
+     */
+    elapsed = igb_send_and_measure(d, data, alloc);
+    g_assert_cmpint(elapsed, <, expected_baseline_elapsed);
+
+    /* Send Packet 2 (should be delayed) */
+    elapsed = igb_send_and_measure(d, data, alloc);
+    /*
+     * Expected delay: 64 byte packet / 125 KB/s = 512 us.
+     * Since QEMU steps the clock by 10 us on each wait_isr iteration,
+     * the delay is rounded up to the next 10 us step, which is 520 us.
+     */
+    g_assert_cmpint(elapsed, >=, expected_min_elapsed);
+    g_assert_cmpint(elapsed, <=, expected_max_elapsed);
+
+    /* Send Packet 3 (should also be delayed) */
+    elapsed = igb_send_and_measure(d, data, alloc);
+    g_assert_cmpint(elapsed, >=, expected_min_elapsed);
+    g_assert_cmpint(elapsed, <=, expected_max_elapsed);
+
+    igb_trl_disable(d);
+
+    /* Send Packet 4 (no delay since TRL is disabled) */
+    elapsed = igb_send_and_measure(d, data, alloc);
+    g_assert_cmpint(elapsed, <, expected_baseline_elapsed);
+}
+
  static void data_test_clear(void *sockets)
  {
      int *test_sockets = sockets;
@@ -247,6 +406,8 @@ static void register_igb_test(void)
      qos_add_test("rx", "igb", test_igb_rx, &opts);
      qos_add_test("multiple_transfers", "igb",
                   test_igb_multiple_transfers, &opts);
+    qos_add_test("transmit_rate_limiter", "igb",
+                 test_igb_transmit_rate_limiter, &opts);
  #endif
opts.before = data_test_init_no_socket;


Reply via email to