diff --git a/NEWS b/NEWS
index f62ef1f47ea8..d91002a76fff 100644
--- a/NEWS
+++ b/NEWS
@@ -4,6 +4,9 @@ Post-v2.13.0
    - OpenFlow:
      * The OpenFlow ofp_desc/serial_num may now be configured by setting the
        value of other-config:dp-sn in the Bridge table.
+   - AF_XDP:
+     * New option 'use-intr' for netdev-afxdp to save CPU cycles by enabling
+       interrupt mode. Disabled by default.
 
 
 v2.13.0 - 14 Feb 2020
diff --git a/lib/netdev-afxdp.c b/lib/netdev-afxdp.c
index 482400d8d135..7b60acde1b02 100644
--- a/lib/netdev-afxdp.c
+++ b/lib/netdev-afxdp.c
@@ -604,6 +604,7 @@ netdev_afxdp_set_config(struct netdev *netdev, const struct smap *args,
     enum afxdp_mode xdp_mode;
     bool need_wakeup;
     int new_n_rxq;
+    bool use_intr;
 
     ovs_mutex_lock(&dev->mutex);
     new_n_rxq = MAX(smap_get_int(args, "n_rxq", NR_QUEUE), 1);
@@ -637,12 +638,16 @@ netdev_afxdp_set_config(struct netdev *netdev, const struct smap *args,
     }
 #endif
 
+    use_intr = smap_get_bool(args, "use-intr", false);
+
     if (dev->requested_n_rxq != new_n_rxq
         || dev->requested_xdp_mode != xdp_mode
-        || dev->requested_need_wakeup != need_wakeup) {
+        || dev->requested_need_wakeup != need_wakeup
+        || dev->requested_use_intr != use_intr) {
         dev->requested_n_rxq = new_n_rxq;
         dev->requested_xdp_mode = xdp_mode;
         dev->requested_need_wakeup = need_wakeup;
+        dev->requested_use_intr = use_intr;
         netdev_request_reconfigure(netdev);
     }
     ovs_mutex_unlock(&dev->mutex);
@@ -661,6 +666,8 @@ netdev_afxdp_get_config(const struct netdev *netdev, struct smap *args)
                     xdp_modes[dev->xdp_mode_in_use].name);
     smap_add_format(args, "use-need-wakeup", "%s",
                     dev->use_need_wakeup ? "true" : "false");
+    smap_add_format(args, "use-intr", "%s",
+                    dev->use_intr ? "true" : "false");
     ovs_mutex_unlock(&dev->mutex);
     return 0;
 }
@@ -696,6 +703,7 @@ netdev_afxdp_reconfigure(struct netdev *netdev)
     if (netdev->n_rxq == dev->requested_n_rxq
         && dev->xdp_mode == dev->requested_xdp_mode
         && dev->use_need_wakeup == dev->requested_need_wakeup
+        && dev->use_intr == dev->requested_use_intr
         && dev->xsks) {
         goto out;
     }
@@ -713,6 +721,7 @@ netdev_afxdp_reconfigure(struct netdev *netdev)
         VLOG_ERR("setrlimit(RLIMIT_MEMLOCK) failed: %s", ovs_strerror(errno));
     }
     dev->use_need_wakeup = dev->requested_need_wakeup;
+    dev->use_intr = dev->requested_use_intr;
 
     err = xsk_configure_all(netdev);
     if (err) {
@@ -815,6 +824,32 @@ prepare_fill_queue(struct xsk_socket_info *xsk_info)
     xsk_info->available_rx += BATCH_SIZE;
 }
 
+static int
+enable_intr_mode(int fd, struct netdev *netdev)
+{
+    struct pollfd fds[1];
+    int ret;
+
+    memset(fds, 0, sizeof fds);
+    fds[0].fd = fd;
+    fds[0].events = POLLIN;
+
+    ret = poll(fds, 1, 1);
+    if (OVS_UNLIKELY(ret == 0)) {
+        /* Timeout. */
+        ovsrcu_quiesce_start();
+        return EAGAIN;
+    }
+    if (OVS_UNLIKELY(ret < 0)) {
+        VLOG_WARN_RL(&rl, "%s: error polling rx fd: %s.",
+                     netdev_get_name(netdev),
+                     ovs_strerror(errno));
+        return EAGAIN;
+    }
+
+    return 0;
+}
+
 int
 netdev_afxdp_rxq_recv(struct netdev_rxq *rxq_, struct dp_packet_batch *batch,
                       int *qfill)
@@ -827,6 +862,7 @@ netdev_afxdp_rxq_recv(struct netdev_rxq *rxq_, struct dp_packet_batch *batch,
     uint32_t idx_rx = 0;
     int qid = rxq_->queue_id;
     unsigned int rcvd, i;
+    int ret;
 
     xsk_info = dev->xsks[qid];
     if (!xsk_info || !xsk_info->xsk) {
@@ -838,6 +874,13 @@ netdev_afxdp_rxq_recv(struct netdev_rxq *rxq_, struct dp_packet_batch *batch,
     umem = xsk_info->umem;
     rx->fd = xsk_socket__fd(xsk_info->xsk);
 
+    if (OVS_UNLIKELY(dev->use_intr)) {
+        ret = enable_intr_mode(rx->fd, netdev);
+        if (ret > 0) {
+            return ret;
+        }
+    }
+
     rcvd = xsk_ring_cons__peek(&xsk_info->rx, BATCH_SIZE, &idx_rx);
     if (!rcvd) {
         xsk_rx_wakeup_if_needed(umem, netdev, rx->fd);
diff --git a/lib/netdev-linux-private.h b/lib/netdev-linux-private.h
index c7c515f70700..a89eb36b5e3c 100644
--- a/lib/netdev-linux-private.h
+++ b/lib/netdev-linux-private.h
@@ -116,6 +116,8 @@ struct netdev_linux {
 
     bool use_need_wakeup;
     bool requested_need_wakeup;
+    bool use_intr;
+    bool requested_use_intr;
 
     struct netdev_afxdp_tx_lock *tx_locks;  /* Array of locks for TX queues. */
 #endif
diff --git a/vswitchd/vswitch.xml b/vswitchd/vswitch.xml
index 4a74ed3ef24e..046018179578 100644
--- a/vswitchd/vswitch.xml
+++ b/vswitchd/vswitch.xml
@@ -3186,6 +3186,18 @@ ovs-vsctl add-port br0 p0 -- set Interface p0 type=patch options:peer=p1 \
         </p>
       </column>
 
+      <column name="options" key="use-intr"
+              type='{"type": "boolean"}'>
+        <p>
+          Specifies whether to use interrupt mode feature in afxdp netdev.
+          If enabled, the pmd thread calls poll() syscall on the RX path.
+          If there is no packet at rx queue, the pmd is blocked at poll()
+          until it is timeout or new packets arrive. This saves CPU cycles
+          when packet arrival rate is low.
+          Defaults to false.
+        </p>
+      </column>
+
       <column name="options" key="vhost-server-path"
               type='{"type": "string"}'>
         <p>
