diff --git a/NEWS b/NEWS
index c41c6adf6..53adbed82 100644
--- a/NEWS
+++ b/NEWS
@@ -30,6 +30,10 @@ Post-v3.1.0
        ovs-vsctl set port vhost-user0 qos=@newqos -- \
           --id=@newqos create qos type=egress-policer \
           other-config:kpkts_rate=123 other-config:kpkts_burst=123
+     * Added support for ingress packet-per-second policing.
+       Examples:
+       $ ovs-vsctl set interface dpdk0 ingress_policing_kpkts_rate=123
+       $ ovs-vsctl set interface dpdk0 ingress_policing_kpkts_burst=123
 
 
 v3.1.0 - 16 Feb 2023
diff --git a/lib/netdev-dpdk.c b/lib/netdev-dpdk.c
index bcd13c116..a717edc5e 100644
--- a/lib/netdev-dpdk.c
+++ b/lib/netdev-dpdk.c
@@ -413,6 +413,8 @@ struct ingress_policer {
     struct rte_meter_srtcm_params app_srtcm_params;
     struct rte_meter_srtcm in_policer;
     struct rte_meter_srtcm_profile in_prof;
+    struct token_bucket tb;
+    enum policer_type type;
     rte_spinlock_t policer_lock;
 };
 
@@ -496,6 +498,9 @@ struct netdev_dpdk {
         uint32_t policer_rate;
         uint32_t policer_burst;
 
+        uint32_t policer_kpkts_rate;
+        uint32_t policer_kpkts_burst;
+
         /* Array of vhost rxq states, see vring_state_changed. */
         bool *vhost_rxq_enabled;
     );
@@ -1315,6 +1320,8 @@ common_construct(struct netdev *netdev, dpdk_port_t port_no,
     ovsrcu_init(&dev->ingress_policer, NULL);
     dev->policer_rate = 0;
     dev->policer_burst = 0;
+    dev->policer_kpkts_rate = 0;
+    dev->policer_kpkts_burst = 0;
 
     netdev->n_rxq = 0;
     netdev->n_txq = 0;
@@ -2367,9 +2374,16 @@ ingress_policer_run(struct ingress_policer *policer, struct rte_mbuf **pkts,
     int cnt = 0;
 
     rte_spinlock_lock(&policer->policer_lock);
-    cnt = srtcm_policer_run_single_packet(&policer->in_policer,
-                                          &policer->in_prof,
-                                          pkts, pkt_cnt, should_steal);
+    if (policer->type & POLICER_BPS) {
+        cnt = srtcm_policer_run_single_packet(&policer->in_policer,
+                                              &policer->in_prof,
+                                              pkts, pkt_cnt, should_steal);
+    }
+
+    if (policer->type & POLICER_PKTPS) {
+        cnt = pkts_policer_run_single_packet(&policer->tb, pkts, pkt_cnt,
+                                             should_steal);
+    }
     rte_spinlock_unlock(&policer->policer_lock);
 
     return cnt;
@@ -3531,7 +3545,8 @@ netdev_dpdk_get_features(const struct netdev *netdev,
 }
 
 static struct ingress_policer *
-netdev_dpdk_policer_construct(uint32_t rate, uint32_t burst)
+netdev_dpdk_policer_construct(uint32_t rate, uint32_t burst,
+                              uint32_t kpkts_rate, uint32_t kpkts_burst)
 {
     struct ingress_policer *policer = NULL;
     uint64_t rate_bytes;
@@ -3539,6 +3554,9 @@ netdev_dpdk_policer_construct(uint32_t rate, uint32_t burst)
     int err = 0;
 
     policer = xmalloc(sizeof *policer);
+    if (!policer) {
+        return NULL;
+    }
     rte_spinlock_init(&policer->policer_lock);
 
     /* rte_meter requires bytes so convert kbits rate and burst to bytes. */
@@ -3556,8 +3574,29 @@ netdev_dpdk_policer_construct(uint32_t rate, uint32_t burst)
     }
     if (err) {
         VLOG_ERR("Could not create rte meter for ingress policer");
+    } else {
+        policer->type |= POLICER_BPS;
+    }
+
+    if (kpkts_rate > MAX_KPKTS_PARAMETER || kpkts_burst > MAX_KPKTS_PARAMETER
+        || kpkts_rate == 0 || kpkts_burst == 0) {
+        /* Paramters between (1 ~ MAX_KPKTS_PARAMETER). */
+        /* Zero means not to police the flow. */
+        VLOG_ERR("Could not create tocken bucket for ingress policer");
+    } else {
+        /*
+        * By default token is added per millisecond.
+        * Rate in kilo-packets/second, bucket 1000 packets.
+        * msec * kilo-packets/sec = 1 packets.
+        */
+        token_bucket_init(&policer->tb, kpkts_rate, kpkts_burst * 1000);
+        policer->type |= POLICER_PKTPS;
+    }
+
+    if (!policer->type) {
+        /* Neither kbps nor kpkts policer is configured. */
         free(policer);
-        return NULL;
+        policer = NULL;
     }
 
     return policer;
@@ -3566,8 +3605,8 @@ netdev_dpdk_policer_construct(uint32_t rate, uint32_t burst)
 static int
 netdev_dpdk_set_policing(struct netdev* netdev, uint32_t policer_rate,
                          uint32_t policer_burst,
-                         uint32_t policer_kpkts_rate OVS_UNUSED,
-                         uint32_t policer_kpkts_burst OVS_UNUSED)
+                         uint32_t policer_kpkts_rate,
+                         uint32_t policer_kpkts_burst)
 {
     struct netdev_dpdk *dev = netdev_dpdk_cast(netdev);
     struct ingress_policer *policer;
@@ -3580,13 +3619,23 @@ netdev_dpdk_set_policing(struct netdev* netdev, uint32_t policer_rate,
                      : !policer_burst ? 8000
                      : policer_burst);
 
+    /*
+     * Force to 0 if no rate specified,
+     * default to rate value if burst is 0,
+     * else stick with user-specified value.
+     */
+    policer_kpkts_burst = (!policer_kpkts_rate ? 0
+                           : !policer_kpkts_burst ? policer_kpkts_rate
+                           : policer_kpkts_burst);
     ovs_mutex_lock(&dev->mutex);
 
     policer = ovsrcu_get_protected(struct ingress_policer *,
-                                    &dev->ingress_policer);
+                                   &dev->ingress_policer);
 
     if (dev->policer_rate == policer_rate &&
-        dev->policer_burst == policer_burst) {
+        dev->policer_burst == policer_burst &&
+        dev->policer_kpkts_rate == policer_kpkts_rate &&
+        dev->policer_kpkts_burst == policer_kpkts_burst) {
         /* Assume that settings haven't changed since we last set them. */
         ovs_mutex_unlock(&dev->mutex);
         return 0;
@@ -3597,14 +3646,19 @@ netdev_dpdk_set_policing(struct netdev* netdev, uint32_t policer_rate,
         ovsrcu_postpone(free, policer);
     }
 
-    if (policer_rate != 0) {
-        policer = netdev_dpdk_policer_construct(policer_rate, policer_burst);
-    } else {
+    if (!policer_rate && !policer_kpkts_rate) {
+        /* both policers are zero. */
         policer = NULL;
+    } else {
+        policer = netdev_dpdk_policer_construct(policer_rate, policer_burst,
+                                                policer_kpkts_rate,
+                                                policer_kpkts_burst);
     }
     ovsrcu_set(&dev->ingress_policer, policer);
     dev->policer_rate = policer_rate;
     dev->policer_burst = policer_burst;
+    dev->policer_kpkts_rate = policer_kpkts_rate;
+    dev->policer_kpkts_burst = policer_kpkts_burst;
     ovs_mutex_unlock(&dev->mutex);
 
     return 0;
diff --git a/tests/system-dpdk.at b/tests/system-dpdk.at
index 75a6ab7f5..492bc2f55 100644
--- a/tests/system-dpdk.at
+++ b/tests/system-dpdk.at
@@ -297,7 +297,9 @@ AT_CHECK([grep -E 'ingress_policing_rate: 0' stdout], [], [stdout])
 
 dnl Clean up
 AT_CHECK([ovs-vsctl del-port br10 phy0], [], [stdout], [stderr])
-OVS_VSWITCHD_STOP("[SYSTEM_DPDK_ALLOWED_LOGS]")
+OVS_VSWITCHD_STOP("m4_join([], [SYSTEM_DPDK_ALLOWED_LOGS], [
+\@Could not create tocken bucket for ingress policer@d
+])")
 AT_CLEANUP
 dnl --------------------------------------------------------------------------
 
@@ -339,6 +341,7 @@ AT_CHECK([grep "VHOST_CONFIG: ($OVS_RUNDIR/dpdkvhostclient0) reconnecting..." ov
 dnl Clean up
 AT_CHECK([ovs-vsctl del-port br10 dpdkvhostuserclient0], [], [stdout], [stderr])
 OVS_VSWITCHD_STOP("m4_join([], [SYSTEM_DPDK_ALLOWED_LOGS], [
+\@Could not create tocken bucket for ingress policer@d
 \@VHOST_CONFIG: ($OVS_RUNDIR/dpdkvhostclient0) failed to connect: No such file or directory@d
 ])")
 AT_CLEANUP
@@ -420,12 +423,223 @@ AT_CHECK([grep "VHOST_CONFIG: ($OVS_RUNDIR/dpdkvhostclient0) reconnecting..." ov
 dnl Clean up
 AT_CHECK([ovs-vsctl del-port br10 dpdkvhostuserclient0], [], [stdout], [stderr])
 OVS_VSWITCHD_STOP("m4_join([], [SYSTEM_DPDK_ALLOWED_LOGS], [
+\@Could not create tocken bucket for ingress policer@d
+\@VHOST_CONFIG: ($OVS_RUNDIR/dpdkvhostclient0) failed to connect: No such file or directory@d
+])")
+AT_CLEANUP
+dnl --------------------------------------------------------------------------
+
+
+dnl --------------------------------------------------------------------------
+dnl Ingress policing (kpkts) create delete vport port
+AT_SETUP([OVS-DPDK - Ingress policing (kpkts) create delete vport port])
+AT_KEYWORDS([dpdk])
+
+OVS_DPDK_PRE_CHECK()
+OVS_DPDK_START()
+
+dnl Add userspace bridge and attach it to OVS and add ingress policer
+AT_CHECK([ovs-vsctl add-br br10 -- set bridge br10 datapath_type=netdev])
+AT_CHECK([ovs-vsctl add-port br10 dpdkvhostuserclient0 -- set Interface dpdkvhostuserclient0 type=dpdkvhostuserclient options:vhost-server-path=$OVS_RUNDIR/dpdkvhostclient0], [], [stdout], [stderr])
+AT_CHECK([ovs-vsctl set interface dpdkvhostuserclient0 ingress_policing_kpkts_rate=10000 ingress_policing_kpkts_burst=10000])
+AT_CHECK([ovs-vsctl show], [], [stdout])
+sleep 2
+
+dnl Remove ingress policer
+AT_CHECK([ovs-vsctl set interface dpdkvhostuserclient0 ingress_policing_kpkts_rate=0 ingress_policing_kpkts_burst=0])
+
+dnl Fail if ingress policer could not be created
+AT_FAIL_IF([grep "Could not create tocken bucket for ingress policer" ovs-vswitchd.log], [], [stdout])
+
+dnl Check ingress policer was removed correctly
+AT_CHECK([ovs-vsctl list interface dpdkvhostuserclient0], [], [stdout])
+AT_CHECK([grep -E 'ingress_policing_kpkts_burst: 0' stdout], [], [stdout])
+
+AT_CHECK([ovs-vsctl list interface dpdkvhostuserclient0], [], [stdout])
+AT_CHECK([grep -E 'ingress_policing_kpkts_rate: 0' stdout], [], [stdout])
+
+dnl Parse log file
+AT_CHECK([grep "VHOST_CONFIG: ($OVS_RUNDIR/dpdkvhostclient0) vhost-user client: socket created" ovs-vswitchd.log], [], [stdout])
+AT_CHECK([grep "vHost User device 'dpdkvhostuserclient0' created in 'client' mode, using client socket" ovs-vswitchd.log], [], [stdout])
+AT_CHECK([grep "VHOST_CONFIG: ($OVS_RUNDIR/dpdkvhostclient0) reconnecting..." ovs-vswitchd.log], [], [stdout])
+
+dnl Clean up
+AT_CHECK([ovs-vsctl del-port br10 dpdkvhostuserclient0], [], [stdout], [stderr])
+OVS_VSWITCHD_STOP("m4_join([], [SYSTEM_DPDK_ALLOWED_LOGS], [
+\@Could not create rte meter for ingress policer@d
+\@VHOST_CONFIG: ($OVS_RUNDIR/dpdkvhostclient0) failed to connect: No such file or directory@d
+])")
+AT_CLEANUP
+dnl --------------------------------------------------------------------------
+
+
+
+dnl --------------------------------------------------------------------------
+dnl Ingress policing (kpkts) no policing rate
+AT_SETUP([OVS-DPDK - Ingress policing (kpkts) no policing rate])
+AT_KEYWORDS([dpdk])
+
+OVS_DPDK_PRE_CHECK()
+OVS_DPDK_START()
+
+dnl Add userspace bridge and attach it to OVS and add ingress policer
+AT_CHECK([ovs-vsctl add-br br10 -- set bridge br10 datapath_type=netdev])
+AT_CHECK([ovs-vsctl add-port br10 dpdkvhostuserclient0 -- set Interface dpdkvhostuserclient0 type=dpdkvhostuserclient options:vhost-server-path=$OVS_RUNDIR/dpdkvhostclient0], [], [stdout], [stderr])
+AT_CHECK([ovs-vsctl set interface dpdkvhostuserclient0 ingress_policing_kpkts_burst=1000])
+AT_CHECK([ovs-vsctl show], [], [stdout])
+sleep 2
+
+dnl check ingress policer not be created
+AT_FAIL_IF([grep "Could not create tocken bucket for ingress policer" ovs-vswitchd.log], [], [stdout])
+
+dnl Check ingress policer was created correctly
+AT_CHECK([ovs-vsctl list interface dpdkvhostuserclient0], [], [stdout])
+AT_CHECK([grep -E 'ingress_policing_kpkts_burst: 1000' stdout], [], [stdout])
+
+AT_CHECK([ovs-vsctl list interface dpdkvhostuserclient0], [], [stdout])
+AT_CHECK([grep -E 'ingress_policing_kpkts_rate: 0' stdout], [], [stdout])
+
+
+dnl Parse log file
+AT_CHECK([grep "VHOST_CONFIG: ($OVS_RUNDIR/dpdkvhostclient0) vhost-user client: socket created" ovs-vswitchd.log], [], [stdout])
+AT_CHECK([grep "vHost User device 'dpdkvhostuserclient0' created in 'client' mode, using client socket" ovs-vswitchd.log], [], [stdout])
+AT_CHECK([grep "VHOST_CONFIG: ($OVS_RUNDIR/dpdkvhostclient0) reconnecting..." ovs-vswitchd.log], [], [stdout])
+
+dnl Clean up
+AT_CHECK([ovs-vsctl del-port br10 dpdkvhostuserclient0], [], [stdout], [stderr])
+OVS_VSWITCHD_STOP("m4_join([], [SYSTEM_DPDK_ALLOWED_LOGS], [
+\@VHOST_CONFIG: ($OVS_RUNDIR/dpdkvhostclient0) failed to connect: No such file or directory@d
+])")
+AT_CLEANUP
+dnl --------------------------------------------------------------------------
+
+
+
+dnl --------------------------------------------------------------------------
+dnl Ingress policing (kpkts) no policing burst
+AT_SETUP([OVS-DPDK - Ingress policing (kpkts) no policing burst])
+AT_KEYWORDS([dpdk])
+
+OVS_DPDK_PRE_CHECK()
+OVS_DPDK_START()
+
+dnl Add userspace bridge and attach it to OVS and add ingress policer
+AT_CHECK([ovs-vsctl add-br br10 -- set bridge br10 datapath_type=netdev])
+AT_CHECK([ovs-vsctl add-port br10 dpdkvhostuserclient0 -- set Interface dpdkvhostuserclient0 type=dpdkvhostuserclient options:vhost-server-path=$OVS_RUNDIR/dpdkvhostclient0], [], [stdout], [stderr])
+AT_CHECK([ovs-vsctl set interface dpdkvhostuserclient0 ingress_policing_kpkts_rate=10000])
+AT_CHECK([ovs-vsctl show], [], [stdout])
+sleep 2
+
+dnl check ingress policer not be created
+AT_FAIL_IF([grep "Could not create tocken bucket for ingress policer" ovs-vswitchd.log], [], [stdout])
+
+dnl Check ingress policer was created correctly
+AT_CHECK([ovs-vsctl list interface dpdkvhostuserclient0], [], [stdout])
+AT_CHECK([grep -E 'ingress_policing_kpkts_burst: 0' stdout], [], [stdout])
+
+AT_CHECK([ovs-vsctl list interface dpdkvhostuserclient0], [], [stdout])
+AT_CHECK([grep -E 'ingress_policing_kpkts_rate: 10000' stdout], [], [stdout])
+
+dnl Parse log file
+AT_CHECK([grep "VHOST_CONFIG: ($OVS_RUNDIR/dpdkvhostclient0) vhost-user client: socket created" ovs-vswitchd.log], [], [stdout])
+AT_CHECK([grep "vHost User device 'dpdkvhostuserclient0' created in 'client' mode, using client socket" ovs-vswitchd.log], [], [stdout])
+AT_CHECK([grep "VHOST_CONFIG: ($OVS_RUNDIR/dpdkvhostclient0) reconnecting..." ovs-vswitchd.log], [], [stdout])
+
+dnl Clean up
+AT_CHECK([ovs-vsctl del-port br10 dpdkvhostuserclient0], [], [stdout], [stderr])
+OVS_VSWITCHD_STOP("m4_join([], [SYSTEM_DPDK_ALLOWED_LOGS], [
+\@Could not create rte meter for ingress policer@d
+\@VHOST_CONFIG: ($OVS_RUNDIR/dpdkvhostclient0) failed to connect: No such file or directory@d
+])")
+AT_CLEANUP
+dnl --------------------------------------------------------------------------
+
+
+
+dnl --------------------------------------------------------------------------
+dnl Ingress policing (kpkts) max policing rate
+AT_SETUP([OVS-DPDK - Ingress policing (kpkts) max policing rate])
+AT_KEYWORDS([dpdk])
+
+OVS_DPDK_PRE_CHECK()
+OVS_DPDK_START()
+
+dnl Add userspace bridge and attach it to OVS and add ingress policer
+AT_CHECK([ovs-vsctl add-br br10 -- set bridge br10 datapath_type=netdev])
+AT_CHECK([ovs-vsctl add-port br10 dpdkvhostuserclient0 -- set Interface dpdkvhostuserclient0 type=dpdkvhostuserclient options:vhost-server-path=$OVS_RUNDIR/dpdkvhostclient0], [], [stdout], [stderr])
+AT_CHECK([ovs-vsctl set interface dpdkvhostuserclient0 ingress_policing_kpkts_rate=42949671])
+AT_CHECK([ovs-vsctl show], [], [stdout])
+sleep 2
+
+dnl check ingress policer not be created
+AT_CHECK([grep "Could not create tocken bucket for ingress policer" ovs-vswitchd.log], [], [stdout])
+
+dnl Check ingress policer was created correctly
+AT_CHECK([ovs-vsctl list interface dpdkvhostuserclient0], [], [stdout])
+AT_CHECK([grep -E 'ingress_policing_kpkts_burst: 0' stdout], [], [stdout])
+
+AT_CHECK([ovs-vsctl list interface dpdkvhostuserclient0], [], [stdout])
+AT_CHECK([grep -E 'ingress_policing_kpkts_rate: 42949671' stdout], [], [stdout])
+
+dnl Parse log file
+AT_CHECK([grep "VHOST_CONFIG: ($OVS_RUNDIR/dpdkvhostclient0) vhost-user client: socket created" ovs-vswitchd.log], [], [stdout])
+AT_CHECK([grep "vHost User device 'dpdkvhostuserclient0' created in 'client' mode, using client socket" ovs-vswitchd.log], [], [stdout])
+AT_CHECK([grep "VHOST_CONFIG: ($OVS_RUNDIR/dpdkvhostclient0) reconnecting..." ovs-vswitchd.log], [], [stdout])
+
+dnl Clean up
+AT_CHECK([ovs-vsctl del-port br10 dpdkvhostuserclient0], [], [stdout], [stderr])
+OVS_VSWITCHD_STOP("m4_join([], [SYSTEM_DPDK_ALLOWED_LOGS], [
+\@Could not create tocken bucket for ingress policer@d
+\@Could not create rte meter for ingress policer@d
 \@VHOST_CONFIG: ($OVS_RUNDIR/dpdkvhostclient0) failed to connect: No such file or directory@d
 ])")
 AT_CLEANUP
 dnl --------------------------------------------------------------------------
 
 
+
+dnl --------------------------------------------------------------------------
+dnl Ingress policing (kpkts) max policing burst
+AT_SETUP([OVS-DPDK - Ingress policing (kpkts) max policing burst])
+AT_KEYWORDS([dpdk])
+
+OVS_DPDK_PRE_CHECK()
+OVS_DPDK_START()
+
+dnl Add userspace bridge and attach it to OVS and add ingress policer
+AT_CHECK([ovs-vsctl add-br br10 -- set bridge br10 datapath_type=netdev])
+AT_CHECK([ovs-vsctl add-port br10 dpdkvhostuserclient0 -- set Interface dpdkvhostuserclient0 type=dpdkvhostuserclient options:vhost-server-path=$OVS_RUNDIR/dpdkvhostclient0], [], [stdout], [stderr])
+AT_CHECK([ovs-vsctl set interface dpdkvhostuserclient0 ingress_policing_kpkts_burst=42949671])
+AT_CHECK([ovs-vsctl show], [], [stdout])
+sleep 2
+
+dnl check ingress policer not be created
+AT_FAIL_IF([grep "Could not create tocken bucket for ingress policer" ovs-vswitchd.log], [], [stdout])
+
+dnl Check ingress policer was created correctly
+AT_CHECK([ovs-vsctl list interface dpdkvhostuserclient0], [], [stdout])
+AT_CHECK([grep -E 'ingress_policing_kpkts_burst: 42949671' stdout], [], [stdout])
+
+AT_CHECK([ovs-vsctl list interface dpdkvhostuserclient0], [], [stdout])
+AT_CHECK([grep -E 'ingress_policing_kpkts_rate: 0' stdout], [], [stdout])
+
+dnl Parse log file
+AT_CHECK([grep "VHOST_CONFIG: ($OVS_RUNDIR/dpdkvhostclient0) vhost-user client: socket created" ovs-vswitchd.log], [], [stdout])
+AT_CHECK([grep "vHost User device 'dpdkvhostuserclient0' created in 'client' mode, using client socket" ovs-vswitchd.log], [], [stdout])
+AT_CHECK([grep "VHOST_CONFIG: ($OVS_RUNDIR/dpdkvhostclient0) reconnecting..." ovs-vswitchd.log], [], [stdout])
+
+dnl Clean up
+AT_CHECK([ovs-vsctl del-port br10 dpdkvhostuserclient0], [], [stdout], [stderr])
+OVS_VSWITCHD_STOP("m4_join([], [SYSTEM_DPDK_ALLOWED_LOGS], [
+\@Could not create tocken bucket for ingress policer@d
+\@Could not create rte meter for ingress policer@d
+\@VHOST_CONFIG: ($OVS_RUNDIR/dpdkvhostclient0) failed to connect: No such file or directory@d
+])")
+AT_CLEANUP
+dnl --------------------------------------------------------------------------
+
+
+
 dnl --------------------------------------------------------------------------
 dnl QoS create delete phy port
 AT_SETUP([OVS-DPDK - QoS create delete phy port])
