* [RFC 1/7] regex/hs: add driver skeleton and build integration
2026-08-28 5:35 [RFC 0/7] regex/hs: introduce Hyperscan regex PMD Prudvi Deti
@ 2026-08-28 5:35 ` Prudvi Deti
2026-08-28 5:35 ` [RFC 2/7] regex/hs: add device configure and queue pair setup Prudvi Deti
` (6 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Prudvi Deti @ 2026-08-28 5:35 UTC (permalink / raw)
To: dev; +Cc: bruce.richardson, orika, nirint.shah, prudvi.deti
Add a new software regex Poll Mode Driver based on Intel Hyperscan.
This patch provides the minimal vdev skeleton: probe, remove, device
registration, and info_get.
Build is gated by meson dependency check on libhs via pkg-config.
Only supported on 64-bit platforms.
Includes documentation, MAINTAINERS entry, feature matrix, and
release notes.
Signed-off-by: Prudvi Deti <prudvi.deti@intel.com>
---
.mailmap | 1 +
MAINTAINERS | 6 +
doc/guides/regexdevs/features/hs.ini | 8 ++
doc/guides/regexdevs/hs.rst | 143 +++++++++++++++++++
doc/guides/regexdevs/index.rst | 1 +
doc/guides/rel_notes/release_26_11.rst | 7 +
drivers/regex/hs/hs_regex.c | 186 +++++++++++++++++++++++++
drivers/regex/hs/hs_regex.h | 69 +++++++++
drivers/regex/hs/meson.build | 19 +++
drivers/regex/meson.build | 1 +
10 files changed, 441 insertions(+)
create mode 100644 doc/guides/regexdevs/features/hs.ini
create mode 100644 doc/guides/regexdevs/hs.rst
create mode 100644 drivers/regex/hs/hs_regex.c
create mode 100644 drivers/regex/hs/hs_regex.h
create mode 100644 drivers/regex/hs/meson.build
diff --git a/.mailmap b/.mailmap
index fcb3d1b..8a8a6e4 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1336,6 +1336,7 @@ Pravin Pathak <pravin.pathak.dev@gmail.com> <pravin.pathak@intel.com>
Prince Takkar <ptakkar@marvell.com>
Priyalee Kushwaha <priyalee.kushwaha@intel.com>
Priyanka Jain <priyanka.jain@nxp.com>
+Prudvi Deti <prudvi.deti@intel.com>
Przemek Kitszel <przemyslaw.kitszel@intel.com>
Przemyslaw Ciesielski <przemyslaw.ciesielski@intel.com>
Przemyslaw Czesnowicz <przemyslaw.czesnowicz@intel.com>
diff --git a/MAINTAINERS b/MAINTAINERS
index e99a65d..e29af50 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1433,6 +1433,12 @@ F: drivers/regex/cn9k/
F: doc/guides/regexdevs/cn9k.rst
F: doc/guides/regexdevs/features/cn9k.ini
+Intel Hyperscan
+M: Prudvi Deti <prudvi.deti@intel.com>
+F: drivers/regex/hs/
+F: doc/guides/regexdevs/hs.rst
+F: doc/guides/regexdevs/features/hs.ini
+
NVIDIA mlx5
M: Ori Kam <orika@nvidia.com>
F: drivers/regex/mlx5/
diff --git a/doc/guides/regexdevs/features/hs.ini b/doc/guides/regexdevs/features/hs.ini
new file mode 100644
index 0000000..58cf4a2
--- /dev/null
+++ b/doc/guides/regexdevs/features/hs.ini
@@ -0,0 +1,8 @@
+;
+; Supported features of the 'hs' RegEx driver.
+;
+; Refer to default.ini for the full list of available driver features.
+;
+[Features]
+Run time compilation = Y
+x86 = Y
diff --git a/doc/guides/regexdevs/hs.rst b/doc/guides/regexdevs/hs.rst
new file mode 100644
index 0000000..e94466a
--- /dev/null
+++ b/doc/guides/regexdevs/hs.rst
@@ -0,0 +1,143 @@
+.. SPDX-License-Identifier: BSD-3-Clause
+ Copyright(c) 2026 Intel Corporation
+
+Hyperscan RegEx PMD
+===================
+
+The Hyperscan RegEx PMD (**librte_regex_hs**) provides a poll mode
+regexdev driver backed by Intel's
+`Hyperscan <https://github.com/intel/hyperscan>`_ regular expression
+library. It is a software-only virtual device (vdev) PMD that
+implements the ``rte_regexdev`` API using Hyperscan block mode scanning.
+
+Features
+--------
+
+- Software-only virtual device (no hardware dependency)
+- Runtime pattern compilation via ``hs_compile_ext_multi()``
+- Serialized database import/export
+- Per-queue-pair scratch space for lock-free parallel scanning
+- Up to 64 queue pairs, each with up to 32768 descriptors
+- Up to 1,000,000 rules per device, with O(1) duplicate rule_id
+ detection backed by ``rte_hash``
+- Per-rule extended match parameters (minimum/maximum start offset)
+- Per-queue-pair statistics via xstats
+
+In the RegEx driver feature matrix, this PMD reports:
+
+- ``Run time compilation``
+- ``x86``
+
+Supported Regex Rule Flags (Hyperscan Mapping)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Standard DPDK flags (advertised via ``info->rule_flags``):
+
+- ``RTE_REGEX_PCRE_RULE_ALLOW_EMPTY_F`` (maps to ``HS_FLAG_ALLOWEMPTY``)
+- ``RTE_REGEX_PCRE_RULE_CASELESS_F`` (maps to ``HS_FLAG_CASELESS``)
+- ``RTE_REGEX_PCRE_RULE_DOTALL_F`` (maps to ``HS_FLAG_DOTALL``)
+- ``RTE_REGEX_PCRE_RULE_MULTILINE_F`` (maps to ``HS_FLAG_MULTILINE``)
+- ``RTE_REGEX_PCRE_RULE_UCP_F`` (maps to ``HS_FLAG_UCP``)
+- ``RTE_REGEX_PCRE_RULE_UTF_F`` (maps to ``HS_FLAG_UTF8``)
+
+PMD-private flags (accepted in ``rule_flags`` but not advertised;
+defined in ``drivers/regex/hs/hs_regex.h``):
+
+- ``HS_REGEX_RULE_SINGLEMATCH_F`` (bit 32, maps to ``HS_FLAG_SINGLEMATCH``)
+- ``HS_REGEX_RULE_PREFILTER_F`` (bit 33, maps to ``HS_FLAG_PREFILTER``)
+- ``HS_REGEX_RULE_SOM_LEFTMOST_F`` (bit 34, maps to ``HS_FLAG_SOM_LEFTMOST``)
+- ``HS_REGEX_RULE_COMBINATION_F`` (bit 35, maps to ``HS_FLAG_COMBINATION``)
+- ``HS_REGEX_RULE_QUIET_F`` (bit 36, maps to ``HS_FLAG_QUIET``)
+
+Unknown flag bits are rejected with an error during ``rule_db_update``.
+
+Extended Match Parameters
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Per-rule minimum and maximum start offset constraints (Hyperscan
+``hs_expr_ext_t``) can be encoded in the upper bits of ``rule_flags``:
+
+- Bits 37-49 (13 bits, mask ``0x1FFF``): maximum start offset,
+ mapped to ``HS_EXT_FLAG_MAX_OFFSET``.
+- Bits 50-63 (14 bits, mask ``0x3FFF``): minimum start offset,
+ mapped to ``HS_EXT_FLAG_MIN_OFFSET``.
+
+A value of 0 leaves the corresponding constraint disabled. These
+constraints are applied via ``hs_compile_ext_multi()`` during
+``rule_db_compile_activate``.
+
+Prerequisites
+-------------
+
+The Hyperscan library must be installed and discoverable via
+``pkg-config``. On Ubuntu/Debian::
+
+ sudo apt install libhyperscan-dev
+
+On Fedora/RHEL::
+
+ sudo dnf install hyperscan-devel
+
+The PMD is built automatically when ``libhs`` is found by the meson
+build system. It is only supported on 64-bit platforms.
+
+Device Setup
+------------
+
+The Hyperscan PMD is a virtual device. Create it with the EAL
+``--vdev`` option::
+
+ dpdk-app --vdev regex_hs -- ...
+
+Or programmatically with ``rte_vdev_init()``::
+
+ rte_vdev_init("regex_hs", NULL);
+
+Device Lifecycle
+~~~~~~~~~~~~~~~~
+
+.. code-block:: c
+
+ rte_regexdev_configure(dev, &cfg);
+ rte_regexdev_queue_pair_setup(dev, qp_id, &qp_conf);
+ rte_regexdev_rule_db_update(dev, rules, nb_rules);
+ rte_regexdev_rule_db_compile_activate(dev);
+ rte_regexdev_start(dev);
+
+ /* data path */
+ rte_regexdev_enqueue_burst(dev, qp, ops, n);
+ rte_regexdev_dequeue_burst(dev, qp, results, n);
+
+ rte_regexdev_stop(dev);
+ rte_regexdev_close(dev);
+
+Alternatively, a pre-compiled serialized database can be loaded
+during ``rte_regexdev_configure()`` via ``cfg.rule_db`` and
+``cfg.rule_db_len``, or at any time via ``rte_regexdev_rule_db_import()``.
+
+Statistics
+~~~~~~~~~~
+
+Extended statistics are reported per queue pair, with names of the
+form ``qp<N>_enqueued``, ``qp<N>_dequeued``, and ``qp<N>_matches``.
+All counters can be reset in bulk or selectively by stat id via
+``rte_regexdev_xstats_reset()``.
+
+Limitations
+-----------
+
+- Scanning is synchronous: ``enqueue_burst`` blocks until
+ ``hs_scan()`` completes for each operation.
+- Multi-segment mbufs are linearized (``rte_pktmbuf_linearize()``)
+ before scanning; linearization failure marks the op with
+ ``RTE_REGEX_OPS_RSP_RESOURCE_LIMIT_REACHED_F``.
+- Multi-process mode is not supported.
+- Each queue pair must be used by exactly one lcore
+ (single-producer/single-consumer model).
+
+Debugging Options
+-----------------
+
+Enable PMD debug logging with::
+
+ --log-level='pmd.regex.hs,8'
diff --git a/doc/guides/regexdevs/index.rst b/doc/guides/regexdevs/index.rst
index 4f928f9..bf4ca10 100644
--- a/doc/guides/regexdevs/index.rst
+++ b/doc/guides/regexdevs/index.rst
@@ -13,4 +13,5 @@ which can be used from an application through RegEx API.
features_overview
cn9k
+ hs
mlx5
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index c8cc862..31afd5e 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -56,6 +56,13 @@ New Features
=======================================================
+* **Added Hyperscan regex PMD.**
+
+ Added a software regex PMD (``regex_hs``) based on Intel Hyperscan
+ library. This virtual device PMD implements the regexdev API using
+ Hyperscan block mode scanning and supports runtime pattern compilation.
+
+
Removed Items
-------------
diff --git a/drivers/regex/hs/hs_regex.c b/drivers/regex/hs/hs_regex.c
new file mode 100644
index 0000000..393283b
--- /dev/null
+++ b/drivers/regex/hs/hs_regex.c
@@ -0,0 +1,186 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Intel Corporation
+ *
+ * Intel Hyperscan PMD for DPDK rte_regexdev
+ *
+ * This Poll Mode Driver wraps Intel Hyperscan behind the standard DPDK
+ * regex device API (rte_regexdev). Applications use the enqueue/dequeue
+ * burst interface with Hyperscan as the matching engine.
+ *
+ * Key design:
+ * - Synchronous scan in enqueue (hs_scan blocks until done)
+ * - Per-queue-pair scratch space for lock-free parallel scanning
+ * - HS_MODE_BLOCK: each buffer scanned independently
+ * - Runtime compilation via hs_compile_ext_multi()
+ * - Serialized database import/export via hs_deserialize_database()
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <rte_common.h>
+#include <rte_malloc.h>
+#include <rte_log.h>
+#include <rte_errno.h>
+#include <bus_vdev_driver.h>
+#include <rte_regexdev.h>
+#include <rte_regexdev_core.h>
+#include <rte_regexdev_driver.h>
+#include <rte_mbuf.h>
+
+#include <hs/hs.h>
+
+#include "hs_regex.h"
+
+RTE_LOG_REGISTER_DEFAULT(hs_regex_logtype, NOTICE);
+#define RTE_LOGTYPE_HS_REGEX hs_regex_logtype
+
+#define HS_LOG(level, ...) \
+ RTE_LOG_LINE(level, HS_REGEX, __VA_ARGS__)
+
+/* Device Info */
+static int
+hs_regex_info_get(struct rte_regexdev *dev __rte_unused,
+ struct rte_regexdev_info *info)
+{
+ info->driver_name = HS_REGEX_DRIVER_NAME;
+ info->dev = NULL;
+ info->max_matches = UINT16_MAX;
+ info->max_queue_pairs = HS_REGEX_MAX_QUEUE_PAIRS;
+ info->max_payload_size = UINT16_MAX;
+ info->max_rules_per_group = HS_REGEX_MAX_RULES;
+ info->max_groups = HS_REGEX_MAX_GROUPS;
+ info->regexdev_capa = RTE_REGEXDEV_CAPA_RUNTIME_COMPILATION_F;
+ info->rule_flags = RTE_REGEX_PCRE_RULE_CASELESS_F |
+ RTE_REGEX_PCRE_RULE_DOTALL_F |
+ RTE_REGEX_PCRE_RULE_MULTILINE_F |
+ RTE_REGEX_PCRE_RULE_UTF_F;
+
+ return 0;
+}
+
+/* Fast path stubs replaced by real implementations in later patches. */
+
+static uint16_t
+hs_regex_enqueue_burst(struct rte_regexdev *dev __rte_unused,
+ uint16_t qp_id __rte_unused,
+ struct rte_regex_ops **ops __rte_unused,
+ uint16_t nb_ops __rte_unused)
+{
+ return 0;
+}
+
+static uint16_t
+hs_regex_dequeue_burst(struct rte_regexdev *dev __rte_unused,
+ uint16_t qp_id __rte_unused,
+ struct rte_regex_ops **ops __rte_unused,
+ uint16_t nb_ops __rte_unused)
+{
+ return 0;
+}
+
+static const struct rte_regexdev_ops hs_regexdev_ops = {
+ .dev_info_get = hs_regex_info_get,
+};
+
+/* Device Lifecycle */
+
+int
+hs_regex_dev_create(const char *name, struct rte_device *device)
+{
+ struct hs_regex_priv *priv;
+ struct rte_regexdev *dev;
+
+ if (name == NULL || device == NULL)
+ return -EINVAL;
+
+ HS_LOG(INFO, "Creating Hyperscan regex device: %s", name);
+
+ dev = rte_regexdev_register(name);
+ if (!dev) {
+ HS_LOG(ERR, "Failed to register regex device %s", name);
+ return -EINVAL;
+ }
+
+ priv = rte_zmalloc("hs_regex_priv", sizeof(*priv),
+ RTE_CACHE_LINE_SIZE);
+ if (!priv) {
+ rte_regexdev_unregister(dev);
+ return -ENOMEM;
+ }
+
+ dev->dev_ops = &hs_regexdev_ops;
+ dev->enqueue = hs_regex_enqueue_burst;
+ dev->dequeue = hs_regex_dequeue_burst;
+ dev->device = device;
+ dev->data->dev_private = priv;
+ dev->state = RTE_REGEXDEV_READY;
+
+ HS_LOG(INFO, "Hyperscan regex PMD created (dev_id=%u, hs=%s)",
+ dev->data->dev_id, hs_version());
+ return dev->data->dev_id;
+}
+
+void
+hs_regex_dev_destroy(const char *name)
+{
+ struct rte_regexdev *dev;
+ struct hs_regex_priv *priv;
+
+ if (name == NULL)
+ return;
+
+ dev = rte_regexdev_get_device_by_name(name);
+ if (!dev)
+ return;
+
+ priv = dev->data->dev_private;
+ if (priv) {
+ rte_free(priv);
+ dev->data->dev_private = NULL;
+ }
+
+ rte_regexdev_unregister(dev);
+ HS_LOG(INFO, "Hyperscan regex PMD destroyed: %s", name);
+}
+
+static int
+hs_regex_probe(struct rte_vdev_device *vdev)
+{
+ const char *name;
+ int ret;
+
+ name = rte_vdev_device_name(vdev);
+ if (name == NULL)
+ return -EINVAL;
+
+ if (rte_eal_process_type() != RTE_PROC_PRIMARY) {
+ HS_LOG(ERR, "Multi-process not supported for %s", name);
+ return -EINVAL;
+ }
+
+ ret = hs_regex_dev_create(name, &vdev->device);
+ return ret < 0 ? ret : 0;
+}
+
+static int
+hs_regex_remove(struct rte_vdev_device *vdev)
+{
+ const char *name;
+
+ name = rte_vdev_device_name(vdev);
+ if (name == NULL)
+ return -EINVAL;
+
+ hs_regex_dev_destroy(name);
+ return 0;
+}
+
+static struct rte_vdev_driver hs_regex_pmd_drv = {
+ .probe = hs_regex_probe,
+ .remove = hs_regex_remove,
+};
+
+RTE_PMD_REGISTER_VDEV(regex_hs, hs_regex_pmd_drv);
+RTE_PMD_REGISTER_PARAM_STRING(regex_hs, "");
diff --git a/drivers/regex/hs/hs_regex.h b/drivers/regex/hs/hs_regex.h
new file mode 100644
index 0000000..be4c2e9
--- /dev/null
+++ b/drivers/regex/hs/hs_regex.h
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Intel Corporation
+ */
+
+#ifndef HS_REGEX_H
+#define HS_REGEX_H
+
+#include <rte_regexdev.h>
+#include <hs/hs.h>
+
+#define HS_REGEX_DRIVER_NAME "regex_hs"
+#define HS_REGEX_INITIAL_RULES_CAP 64
+#define HS_REGEX_MAX_QUEUE_PAIRS 64
+#define HS_REGEX_MAX_GROUPS 64
+#define HS_REGEX_MAX_RULES 1000000
+#define HS_REGEX_DEFAULT_NB_DESC 1024
+#define HS_REGEX_MAX_NB_DESC 32768
+
+/* Device lifecycle state machine. */
+enum hs_regex_dev_state {
+ HS_REGEX_DEV_CREATED = 0,
+ HS_REGEX_DEV_CONFIGURED,
+ HS_REGEX_DEV_STARTED,
+ HS_REGEX_DEV_STOPPED,
+};
+
+/* Per-rule entry stored before compilation */
+struct hs_regex_rule {
+ char *pattern;
+ uint32_t rule_id;
+ uint16_t group_id;
+ uint64_t rule_flags;
+};
+
+/* Queue pair */
+struct hs_regex_qp {
+ struct rte_regex_ops **ops;
+ uint16_t nb_desc;
+ uint16_t head;
+ uint16_t tail;
+ uint16_t count;
+ hs_scratch_t *scratch;
+ uint64_t qp_enqueued;
+ uint64_t qp_dequeued;
+ uint64_t qp_matches;
+};
+
+/* Per-device private data */
+struct hs_regex_priv {
+ struct hs_regex_rule *rules;
+ uint32_t nb_rules;
+ uint32_t rules_cap;
+
+ hs_database_t *db;
+ int db_compiled;
+
+ struct hs_regex_qp *qps;
+ uint16_t nb_queue_pairs;
+
+ uint16_t max_matches;
+ uint16_t nb_groups;
+
+ enum hs_regex_dev_state dev_state;
+};
+
+int hs_regex_dev_create(const char *name, struct rte_device *device);
+void hs_regex_dev_destroy(const char *name);
+
+#endif /* HS_REGEX_H */
diff --git a/drivers/regex/hs/meson.build b/drivers/regex/hs/meson.build
new file mode 100644
index 0000000..5c855f4
--- /dev/null
+++ b/drivers/regex/hs/meson.build
@@ -0,0 +1,19 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2026 Intel Corporation
+
+if not dpdk_conf.get('RTE_ARCH_64')
+ build = false
+ reason = 'only supported on 64-bit platforms'
+ subdir_done()
+endif
+
+hs_dep = dependency('libhs', required: false)
+if not hs_dep.found()
+ build = false
+ reason = 'missing dependency, "libhs"'
+ subdir_done()
+endif
+
+sources = files('hs_regex.c')
+deps += ['bus_vdev', 'hash']
+ext_deps += hs_dep
diff --git a/drivers/regex/meson.build b/drivers/regex/meson.build
index ff2a8fe..d5a378b 100644
--- a/drivers/regex/meson.build
+++ b/drivers/regex/meson.build
@@ -2,6 +2,7 @@
# Copyright 2020 Mellanox Technologies, Ltd
drivers = [
+ 'hs',
'mlx5',
'cn9k',
]
--
2.43.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [RFC 2/7] regex/hs: add device configure and queue pair setup
2026-08-28 5:35 [RFC 0/7] regex/hs: introduce Hyperscan regex PMD Prudvi Deti
2026-08-28 5:35 ` [RFC 1/7] regex/hs: add driver skeleton and build integration Prudvi Deti
@ 2026-08-28 5:35 ` Prudvi Deti
2026-08-28 5:35 ` [RFC 3/7] regex/hs: add rule database update and compilation Prudvi Deti
` (5 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Prudvi Deti @ 2026-08-28 5:35 UTC (permalink / raw)
To: dev; +Cc: bruce.richardson, orika, nirint.shah, prudvi.deti
Implement rte_regexdev_configure() and rte_regexdev_queue_pair_setup().
Configure allocates queue pair array and validates parameters.
Queue pair setup allocates a power-of-two descriptor ring and
per-QP Hyperscan scratch space when a database is available.
Signed-off-by: Prudvi Deti <prudvi.deti@intel.com>
---
drivers/regex/hs/hs_regex.c | 153 ++++++++++++++++++++++++++++++++++++
1 file changed, 153 insertions(+)
diff --git a/drivers/regex/hs/hs_regex.c b/drivers/regex/hs/hs_regex.c
index 393283b..afe00b5 100644
--- a/drivers/regex/hs/hs_regex.c
+++ b/drivers/regex/hs/hs_regex.c
@@ -60,6 +60,157 @@ hs_regex_info_get(struct rte_regexdev *dev __rte_unused,
return 0;
}
+/* Configure */
+static int
+hs_regex_configure(struct rte_regexdev *dev,
+ const struct rte_regexdev_config *cfg)
+{
+ struct hs_regex_priv *priv;
+
+ if (dev == NULL || cfg == NULL)
+ return -EINVAL;
+
+ priv = dev->data->dev_private;
+ if (priv == NULL)
+ return -EINVAL;
+
+ if (priv->dev_state == HS_REGEX_DEV_STARTED) {
+ HS_LOG(ERR, "Cannot configure while device is started");
+ return -EBUSY;
+ }
+
+ if (cfg->nb_queue_pairs > HS_REGEX_MAX_QUEUE_PAIRS) {
+ HS_LOG(ERR, "Requested %u queue pairs exceeds max %u",
+ cfg->nb_queue_pairs, HS_REGEX_MAX_QUEUE_PAIRS);
+ return -EINVAL;
+ }
+
+ priv->nb_queue_pairs = cfg->nb_queue_pairs;
+ priv->max_matches = cfg->nb_max_matches ? cfg->nb_max_matches :
+ UINT16_MAX;
+ priv->nb_groups = cfg->nb_groups ? cfg->nb_groups : 1;
+
+ if (priv->rules) {
+ uint32_t i;
+
+ for (i = 0; i < priv->nb_rules; i++)
+ rte_free(priv->rules[i].pattern);
+ rte_free(priv->rules);
+ priv->rules = NULL;
+ priv->nb_rules = 0;
+ priv->rules_cap = 0;
+ }
+ if (priv->db) {
+ hs_free_database(priv->db);
+ priv->db = NULL;
+ priv->db_compiled = 0;
+ }
+
+ if (priv->qps) {
+ rte_free(priv->qps);
+ priv->qps = NULL;
+ }
+
+ priv->qps = rte_zmalloc("hs_regex_qps",
+ sizeof(struct hs_regex_qp) *
+ cfg->nb_queue_pairs,
+ RTE_CACHE_LINE_SIZE);
+ if (!priv->qps) {
+ HS_LOG(ERR, "Failed to allocate queue pairs");
+ /* Keep nb_queue_pairs in sync with the NULL qps array. */
+ priv->nb_queue_pairs = 0;
+ return -ENOMEM;
+ }
+
+ HS_LOG(INFO, "Configured: %u queue pairs, max_matches=%u",
+ priv->nb_queue_pairs, priv->max_matches);
+
+ priv->dev_state = HS_REGEX_DEV_CONFIGURED;
+ return 0;
+}
+
+/* Queue Pair Setup */
+static int
+hs_regex_qp_setup(struct rte_regexdev *dev, uint16_t qp_id,
+ const struct rte_regexdev_qp_conf *qp_conf)
+{
+ struct hs_regex_priv *priv;
+ struct hs_regex_qp *qp;
+ uint16_t nb_desc;
+ hs_error_t err;
+
+ if (dev == NULL)
+ return -EINVAL;
+
+ priv = dev->data->dev_private;
+ if (priv == NULL)
+ return -EINVAL;
+
+ if (qp_id >= priv->nb_queue_pairs) {
+ HS_LOG(ERR, "Invalid qp_id %u (max %u)", qp_id,
+ priv->nb_queue_pairs);
+ return -EINVAL;
+ }
+
+ /* nb_queue_pairs must stay in sync with a live qps array. */
+ if (priv->qps == NULL) {
+ HS_LOG(ERR, "qp %u: queue pairs not allocated", qp_id);
+ return -EINVAL;
+ }
+
+ qp = &priv->qps[qp_id];
+ nb_desc = (qp_conf && qp_conf->nb_desc) ? qp_conf->nb_desc :
+ HS_REGEX_DEFAULT_NB_DESC;
+
+ if (nb_desc == 0 || (nb_desc & (nb_desc - 1)) != 0) {
+ uint16_t orig = nb_desc;
+ uint32_t aligned = rte_align32pow2(nb_desc ? nb_desc : 1);
+
+ if (aligned > HS_REGEX_MAX_NB_DESC)
+ aligned = HS_REGEX_MAX_NB_DESC;
+ nb_desc = aligned;
+ HS_LOG(WARNING, "QP %u: nb_desc %u rounded up to %u (power of 2)",
+ qp_id, orig, nb_desc);
+ }
+
+ if (qp->ops) {
+ rte_free(qp->ops);
+ qp->ops = NULL;
+ }
+
+ if (qp->scratch) {
+ hs_free_scratch(qp->scratch);
+ qp->scratch = NULL;
+ }
+
+ qp->ops = rte_zmalloc("hs_regex_qp_ops",
+ sizeof(struct rte_regex_ops *) * nb_desc,
+ RTE_CACHE_LINE_SIZE);
+ if (!qp->ops) {
+ HS_LOG(ERR, "Failed to allocate ops ring for qp %u", qp_id);
+ return -ENOMEM;
+ }
+
+ qp->nb_desc = nb_desc;
+ qp->head = 0;
+ qp->tail = 0;
+ qp->count = 0;
+
+ if (priv->db) {
+ err = hs_alloc_scratch(priv->db, &qp->scratch);
+ if (err != HS_SUCCESS) {
+ HS_LOG(ERR, "Failed to alloc scratch for qp %u",
+ qp_id);
+ rte_free(qp->ops);
+ qp->ops = NULL;
+ return -ENOMEM;
+ }
+ }
+
+ HS_LOG(INFO, "QP %u setup: nb_desc=%u", qp_id, nb_desc);
+ return 0;
+}
+
/* Fast path stubs replaced by real implementations in later patches. */
static uint16_t
@@ -82,6 +233,8 @@ hs_regex_dequeue_burst(struct rte_regexdev *dev __rte_unused,
static const struct rte_regexdev_ops hs_regexdev_ops = {
.dev_info_get = hs_regex_info_get,
+ .dev_configure = hs_regex_configure,
+ .dev_qp_setup = hs_regex_qp_setup,
};
/* Device Lifecycle */
--
2.43.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [RFC 3/7] regex/hs: add rule database update and compilation
2026-08-28 5:35 [RFC 0/7] regex/hs: introduce Hyperscan regex PMD Prudvi Deti
2026-08-28 5:35 ` [RFC 1/7] regex/hs: add driver skeleton and build integration Prudvi Deti
2026-08-28 5:35 ` [RFC 2/7] regex/hs: add device configure and queue pair setup Prudvi Deti
@ 2026-08-28 5:35 ` Prudvi Deti
2026-08-28 5:35 ` [RFC 4/7] regex/hs: add enqueue and dequeue burst paths Prudvi Deti
` (4 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Prudvi Deti @ 2026-08-28 5:35 UTC (permalink / raw)
To: dev; +Cc: bruce.richardson, orika, nirint.shah, prudvi.deti
Implement rule_db_update, rule_db_compile_activate, db_import, and
db_export.
rule_db_update supports ADD and REMOVE operations with O(1) duplicate
detection via rte_hash. Extended parameters (min_offset, max_offset)
are extracted from rule_flags bits 37-63.
Compilation uses hs_compile_ext_multi() with per-rule extended
parameter support. Per-QP scratch is allocated with rollback on
partial failure.
The configure path optionally imports a serialized database when
cfg->rule_db is provided.
Signed-off-by: Prudvi Deti <prudvi.deti@intel.com>
---
drivers/regex/hs/hs_regex.c | 404 +++++++++++++++++++++++++++++++++++-
drivers/regex/hs/hs_regex.h | 11 +
2 files changed, 414 insertions(+), 1 deletion(-)
diff --git a/drivers/regex/hs/hs_regex.c b/drivers/regex/hs/hs_regex.c
index afe00b5..116aff9 100644
--- a/drivers/regex/hs/hs_regex.c
+++ b/drivers/regex/hs/hs_regex.c
@@ -39,6 +39,10 @@ RTE_LOG_REGISTER_DEFAULT(hs_regex_logtype, NOTICE);
#define HS_LOG(level, ...) \
RTE_LOG_LINE(level, HS_REGEX, __VA_ARGS__)
+static int
+hs_regex_rule_db_import(struct rte_regexdev *dev, const char *rule_db,
+ uint32_t rule_db_len);
+
/* Device Info */
static int
hs_regex_info_get(struct rte_regexdev *dev __rte_unused,
@@ -66,6 +70,7 @@ hs_regex_configure(struct rte_regexdev *dev,
const struct rte_regexdev_config *cfg)
{
struct hs_regex_priv *priv;
+ int ret;
if (dev == NULL || cfg == NULL)
return -EINVAL;
@@ -92,7 +97,6 @@ hs_regex_configure(struct rte_regexdev *dev,
if (priv->rules) {
uint32_t i;
-
for (i = 0; i < priv->nb_rules; i++)
rte_free(priv->rules[i].pattern);
rte_free(priv->rules);
@@ -100,6 +104,10 @@ hs_regex_configure(struct rte_regexdev *dev,
priv->nb_rules = 0;
priv->rules_cap = 0;
}
+ if (priv->rule_id_hash) {
+ rte_hash_free(priv->rule_id_hash);
+ priv->rule_id_hash = NULL;
+ }
if (priv->db) {
hs_free_database(priv->db);
priv->db = NULL;
@@ -126,6 +134,20 @@ hs_regex_configure(struct rte_regexdev *dev,
priv->nb_queue_pairs, priv->max_matches);
priv->dev_state = HS_REGEX_DEV_CONFIGURED;
+
+ if (cfg->rule_db != NULL && cfg->rule_db_len > 0) {
+ ret = hs_regex_rule_db_import(dev, cfg->rule_db,
+ cfg->rule_db_len);
+ if (ret < 0) {
+ HS_LOG(ERR, "Failed to import rule DB in configure");
+ rte_free(priv->qps);
+ priv->qps = NULL;
+ priv->nb_queue_pairs = 0;
+ priv->dev_state = HS_REGEX_DEV_CREATED;
+ return ret;
+ }
+ }
+
return 0;
}
@@ -211,6 +233,382 @@ hs_regex_qp_setup(struct rte_regexdev *dev, uint16_t qp_id,
return 0;
}
+/* Rule Database Update */
+static int
+hs_regex_rule_db_update(struct rte_regexdev *dev,
+ const struct rte_regexdev_rule *rules,
+ uint16_t nb_rules)
+{
+ struct hs_regex_priv *priv;
+ uint64_t rf;
+ uint16_t i;
+
+ if (dev == NULL || rules == NULL)
+ return -EINVAL;
+
+ priv = dev->data->dev_private;
+ if (priv == NULL)
+ return -EINVAL;
+
+ if (nb_rules == 0)
+ return 0;
+
+ if (!priv->rule_id_hash) {
+ struct rte_hash_parameters hp = {
+ .name = "hs_rule_ids",
+ .entries = HS_REGEX_MAX_RULES,
+ .key_len = sizeof(uint32_t),
+ .socket_id = SOCKET_ID_ANY,
+ };
+ priv->rule_id_hash = rte_hash_create(&hp);
+ if (!priv->rule_id_hash) {
+ HS_LOG(ERR, "Failed to create rule_id hash");
+ return -ENOMEM;
+ }
+ }
+
+ for (i = 0; i < nb_rules; i++) {
+ if (rules[i].op == RTE_REGEX_RULE_OP_ADD) {
+ uint32_t idx;
+
+ if (!rules[i].pcre_rule || rules[i].pcre_rule_len == 0) {
+ HS_LOG(ERR, "Rule %u: NULL or empty pattern",
+ rules[i].rule_id);
+ rte_errno = EINVAL;
+ return i;
+ }
+
+ if (priv->rule_id_hash &&
+ rte_hash_lookup(priv->rule_id_hash,
+ &rules[i].rule_id) >= 0) {
+ HS_LOG(ERR, "Rule %u: duplicate rule_id",
+ rules[i].rule_id);
+ rte_errno = EINVAL;
+ return i;
+ }
+
+ if (priv->nb_rules >= HS_REGEX_MAX_RULES) {
+ HS_LOG(ERR, "Rule limit reached (%u)",
+ HS_REGEX_MAX_RULES);
+ rte_errno = ENOSPC;
+ return i;
+ }
+
+ if (priv->nb_rules >= priv->rules_cap) {
+ uint32_t new_cap = priv->rules_cap ?
+ priv->rules_cap * 2 :
+ HS_REGEX_INITIAL_RULES_CAP;
+ struct hs_regex_rule *tmp = rte_realloc(
+ priv->rules,
+ new_cap * sizeof(struct hs_regex_rule), 0);
+ if (!tmp) {
+ HS_LOG(ERR, "Failed to grow rules");
+ rte_errno = ENOMEM;
+ return i;
+ }
+ priv->rules = tmp;
+ priv->rules_cap = new_cap;
+ }
+
+ idx = priv->nb_rules;
+
+ priv->rules[idx].pattern = rte_malloc("hs_pattern",
+ rules[i].pcre_rule_len + 1, 0);
+ if (!priv->rules[idx].pattern) {
+ rte_errno = ENOMEM;
+ return i;
+ }
+ memcpy(priv->rules[idx].pattern,
+ rules[i].pcre_rule, rules[i].pcre_rule_len);
+ priv->rules[idx].pattern[rules[i].pcre_rule_len] = '\0';
+
+ priv->rules[idx].rule_id = rules[i].rule_id;
+ priv->rules[idx].group_id = rules[i].group_id;
+ priv->rules[idx].rule_flags = rules[i].rule_flags;
+
+ rf = rules[i].rule_flags;
+ priv->rules[idx].max_offset =
+ (rf >> HS_REGEX_EXT_MAX_OFFSET_SHIFT) &
+ HS_REGEX_EXT_MAX_OFFSET_MASK;
+ priv->rules[idx].min_offset =
+ (rf >> HS_REGEX_EXT_MIN_OFFSET_SHIFT) &
+ HS_REGEX_EXT_MIN_OFFSET_MASK;
+ priv->rules[idx].min_length = 0;
+
+ priv->nb_rules++;
+
+ if (priv->rule_id_hash)
+ rte_hash_add_key(priv->rule_id_hash,
+ &rules[i].rule_id);
+
+ } else if (rules[i].op == RTE_REGEX_RULE_OP_REMOVE) {
+ uint32_t j;
+
+ if (priv->rule_id_hash)
+ rte_hash_del_key(priv->rule_id_hash,
+ &rules[i].rule_id);
+
+ for (j = 0; j < priv->nb_rules; j++) {
+ if (priv->rules[j].rule_id !=
+ rules[i].rule_id)
+ continue;
+ rte_free(priv->rules[j].pattern);
+ memmove(&priv->rules[j], &priv->rules[j + 1],
+ (priv->nb_rules - j - 1) *
+ sizeof(struct hs_regex_rule));
+ priv->nb_rules--;
+ break;
+ }
+ }
+ }
+
+ priv->db_compiled = 0;
+ HS_LOG(INFO, "Rule DB updated: %u total rules", priv->nb_rules);
+ return nb_rules;
+}
+
+/* Compile and Activate */
+static int
+hs_regex_rule_db_compile_activate(struct rte_regexdev *dev)
+{
+ struct hs_regex_priv *priv;
+ hs_compile_error_t *compile_err = NULL;
+ hs_error_t err;
+ const char **expressions;
+ unsigned int *flags;
+ unsigned int *ids;
+ hs_expr_ext_t *ext;
+ const hs_expr_ext_t **ext_ptrs;
+ uint32_t i;
+
+ if (dev == NULL)
+ return -EINVAL;
+
+ priv = dev->data->dev_private;
+ if (priv == NULL)
+ return -EINVAL;
+
+ if (priv->nb_rules == 0) {
+ HS_LOG(ERR, "No rules to compile");
+ return -EINVAL;
+ }
+
+ if (priv->qps == NULL) {
+ HS_LOG(ERR, "Cannot compile: queue pairs not allocated");
+ return -EINVAL;
+ }
+
+ if (priv->db) {
+ hs_free_database(priv->db);
+ priv->db = NULL;
+ }
+
+ expressions = rte_malloc("hs_expr",
+ sizeof(char *) * priv->nb_rules, 0);
+ flags = rte_malloc("hs_flags",
+ sizeof(unsigned int) * priv->nb_rules, 0);
+ ids = rte_malloc("hs_ids",
+ sizeof(unsigned int) * priv->nb_rules, 0);
+ ext = rte_zmalloc("hs_ext",
+ sizeof(hs_expr_ext_t) * priv->nb_rules, 0);
+ ext_ptrs = rte_malloc("hs_ext_ptrs",
+ sizeof(hs_expr_ext_t *) * priv->nb_rules, 0);
+
+ if (!expressions || !flags || !ids || !ext || !ext_ptrs) {
+ rte_free(expressions);
+ rte_free(flags);
+ rte_free(ids);
+ rte_free(ext);
+ rte_free(ext_ptrs);
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < priv->nb_rules; i++) {
+ expressions[i] = priv->rules[i].pattern;
+ ids[i] = priv->rules[i].rule_id;
+
+ flags[i] = 0;
+ if (priv->rules[i].rule_flags & RTE_REGEX_PCRE_RULE_CASELESS_F)
+ flags[i] |= HS_FLAG_CASELESS;
+ if (priv->rules[i].rule_flags & RTE_REGEX_PCRE_RULE_DOTALL_F)
+ flags[i] |= HS_FLAG_DOTALL;
+ if (priv->rules[i].rule_flags & RTE_REGEX_PCRE_RULE_MULTILINE_F)
+ flags[i] |= HS_FLAG_MULTILINE;
+ if (priv->rules[i].rule_flags & RTE_REGEX_PCRE_RULE_UTF_F)
+ flags[i] |= HS_FLAG_UTF8;
+
+ ext[i].flags = 0;
+ if (priv->rules[i].min_offset) {
+ ext[i].flags |= HS_EXT_FLAG_MIN_OFFSET;
+ ext[i].min_offset = priv->rules[i].min_offset;
+ }
+ if (priv->rules[i].max_offset) {
+ ext[i].flags |= HS_EXT_FLAG_MAX_OFFSET;
+ ext[i].max_offset = priv->rules[i].max_offset;
+ }
+ if (priv->rules[i].min_length) {
+ ext[i].flags |= HS_EXT_FLAG_MIN_LENGTH;
+ ext[i].min_length = priv->rules[i].min_length;
+ }
+ ext_ptrs[i] = &ext[i];
+ }
+
+ err = hs_compile_ext_multi(expressions, flags, ids, ext_ptrs,
+ priv->nb_rules, HS_MODE_BLOCK, NULL,
+ &priv->db, &compile_err);
+
+ rte_free(expressions);
+ rte_free(flags);
+ rte_free(ids);
+ rte_free(ext);
+ rte_free(ext_ptrs);
+
+ if (err != HS_SUCCESS) {
+ HS_LOG(ERR, "hs_compile_ext_multi failed: %s (pattern %d)",
+ compile_err ? compile_err->message : "unknown",
+ compile_err ? compile_err->expression : -1);
+ if (compile_err)
+ hs_free_compile_error(compile_err);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < priv->nb_queue_pairs; i++) {
+ struct hs_regex_qp *qp = &priv->qps[i];
+
+ if (qp->scratch) {
+ hs_free_scratch(qp->scratch);
+ qp->scratch = NULL;
+ }
+ err = hs_alloc_scratch(priv->db, &qp->scratch);
+ if (err != HS_SUCCESS) {
+ uint32_t j;
+
+ HS_LOG(ERR, "Scratch alloc failed for qp %u", i);
+ for (j = 0; j < i; j++) {
+ if (priv->qps[j].scratch) {
+ hs_free_scratch(priv->qps[j].scratch);
+ priv->qps[j].scratch = NULL;
+ }
+ }
+ hs_free_database(priv->db);
+ priv->db = NULL;
+ return -ENOMEM;
+ }
+ }
+
+ priv->db_compiled = 1;
+ HS_LOG(INFO, "Compiled %u rules into Hyperscan database",
+ priv->nb_rules);
+ return 0;
+}
+
+static int
+hs_regex_rule_db_import(struct rte_regexdev *dev, const char *rule_db,
+ uint32_t rule_db_len)
+{
+ struct hs_regex_priv *priv;
+ hs_error_t err;
+ uint32_t i;
+
+ if (dev == NULL)
+ return -EINVAL;
+
+ priv = dev->data->dev_private;
+ if (priv == NULL)
+ return -EINVAL;
+
+ if (!rule_db || rule_db_len == 0) {
+ HS_LOG(ERR, "Invalid rule_db pointer or length");
+ return -EINVAL;
+ }
+
+ if (priv->qps == NULL) {
+ HS_LOG(ERR, "Cannot import: queue pairs not allocated");
+ return -EINVAL;
+ }
+
+ if (priv->db) {
+ hs_free_database(priv->db);
+ priv->db = NULL;
+ }
+
+ err = hs_deserialize_database(rule_db, (size_t)rule_db_len, &priv->db);
+ if (err != HS_SUCCESS) {
+ HS_LOG(ERR, "hs_deserialize_database failed (error %d)", err);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < priv->nb_queue_pairs; i++) {
+ struct hs_regex_qp *qp = &priv->qps[i];
+
+ if (qp->scratch) {
+ hs_free_scratch(qp->scratch);
+ qp->scratch = NULL;
+ }
+ err = hs_alloc_scratch(priv->db, &qp->scratch);
+ if (err != HS_SUCCESS) {
+ uint32_t j;
+
+ HS_LOG(ERR, "Scratch alloc failed for qp %u"
+ " after import", i);
+ for (j = 0; j < i; j++) {
+ if (priv->qps[j].scratch) {
+ hs_free_scratch(priv->qps[j].scratch);
+ priv->qps[j].scratch = NULL;
+ }
+ }
+ hs_free_database(priv->db);
+ priv->db = NULL;
+ return -ENOMEM;
+ }
+ }
+
+ priv->db_compiled = 1;
+ HS_LOG(INFO, "Imported serialized Hyperscan database (%u bytes)",
+ rule_db_len);
+ return 0;
+}
+
+static int
+hs_regex_rule_db_export(struct rte_regexdev *dev, char *rule_db)
+{
+ struct hs_regex_priv *priv;
+ hs_error_t err;
+ char *buf;
+ size_t len;
+
+ if (dev == NULL)
+ return -EINVAL;
+
+ priv = dev->data->dev_private;
+ if (priv == NULL)
+ return -EINVAL;
+
+ if (!priv->db) {
+ HS_LOG(ERR, "No database to export");
+ return -EINVAL;
+ }
+
+ err = hs_serialize_database(priv->db, &buf, &len);
+ if (err != HS_SUCCESS) {
+ HS_LOG(ERR, "hs_serialize_database failed (error %d)", err);
+ return -EIO;
+ }
+
+ if (rule_db == NULL) {
+ free(buf);
+ if (len > INT_MAX) {
+ HS_LOG(ERR, "Serialized DB too large (%zu bytes)", len);
+ return -EOVERFLOW;
+ }
+ return (int)len;
+ }
+
+ memcpy(rule_db, buf, len);
+ free(buf);
+ return 0;
+}
+
/* Fast path stubs replaced by real implementations in later patches. */
static uint16_t
@@ -235,6 +633,10 @@ static const struct rte_regexdev_ops hs_regexdev_ops = {
.dev_info_get = hs_regex_info_get,
.dev_configure = hs_regex_configure,
.dev_qp_setup = hs_regex_qp_setup,
+ .dev_rule_db_update = hs_regex_rule_db_update,
+ .dev_rule_db_compile_activate = hs_regex_rule_db_compile_activate,
+ .dev_db_import = hs_regex_rule_db_import,
+ .dev_db_export = hs_regex_rule_db_export,
};
/* Device Lifecycle */
diff --git a/drivers/regex/hs/hs_regex.h b/drivers/regex/hs/hs_regex.h
index be4c2e9..ddd6d9b 100644
--- a/drivers/regex/hs/hs_regex.h
+++ b/drivers/regex/hs/hs_regex.h
@@ -6,6 +6,7 @@
#define HS_REGEX_H
#include <rte_regexdev.h>
+#include <rte_hash.h>
#include <hs/hs.h>
#define HS_REGEX_DRIVER_NAME "regex_hs"
@@ -16,6 +17,12 @@
#define HS_REGEX_DEFAULT_NB_DESC 1024
#define HS_REGEX_MAX_NB_DESC 32768
+/* Ext params encoded in rule_flags bits 37-63 */
+#define HS_REGEX_EXT_MAX_OFFSET_SHIFT 37
+#define HS_REGEX_EXT_MAX_OFFSET_MASK 0x1FFFULL
+#define HS_REGEX_EXT_MIN_OFFSET_SHIFT 50
+#define HS_REGEX_EXT_MIN_OFFSET_MASK 0x3FFFULL
+
/* Device lifecycle state machine. */
enum hs_regex_dev_state {
HS_REGEX_DEV_CREATED = 0,
@@ -30,6 +37,9 @@ struct hs_regex_rule {
uint32_t rule_id;
uint16_t group_id;
uint64_t rule_flags;
+ uint64_t min_offset;
+ uint64_t max_offset;
+ uint64_t min_length;
};
/* Queue pair */
@@ -48,6 +58,7 @@ struct hs_regex_qp {
/* Per-device private data */
struct hs_regex_priv {
struct hs_regex_rule *rules;
+ struct rte_hash *rule_id_hash;
uint32_t nb_rules;
uint32_t rules_cap;
--
2.43.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [RFC 4/7] regex/hs: add enqueue and dequeue burst paths
2026-08-28 5:35 [RFC 0/7] regex/hs: introduce Hyperscan regex PMD Prudvi Deti
` (2 preceding siblings ...)
2026-08-28 5:35 ` [RFC 3/7] regex/hs: add rule database update and compilation Prudvi Deti
@ 2026-08-28 5:35 ` Prudvi Deti
2026-08-28 5:35 ` [RFC 5/7] regex/hs: add per-queue-pair extended statistics Prudvi Deti
` (3 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Prudvi Deti @ 2026-08-28 5:35 UTC (permalink / raw)
To: dev; +Cc: bruce.richardson, orika, nirint.shah, prudvi.deti
Implement fast-path enqueue and dequeue burst functions.
Enqueue calls hs_scan() synchronously per operation using per-QP
scratch space. Multi-segment mbufs are linearized before scanning.
Completed ops are stored in a bounded ring for dequeue.
Thread-safety: single-producer/single-consumer per queue pair.
Each QP must be used by exactly one lcore.
Signed-off-by: Prudvi Deti <prudvi.deti@intel.com>
---
drivers/regex/hs/hs_regex.c | 247 ++++++++++++++++++++++++++++++++++--
1 file changed, 234 insertions(+), 13 deletions(-)
diff --git a/drivers/regex/hs/hs_regex.c b/drivers/regex/hs/hs_regex.c
index 116aff9..e84259e 100644
--- a/drivers/regex/hs/hs_regex.c
+++ b/drivers/regex/hs/hs_regex.c
@@ -39,15 +39,60 @@ RTE_LOG_REGISTER_DEFAULT(hs_regex_logtype, NOTICE);
#define HS_LOG(level, ...) \
RTE_LOG_LINE(level, HS_REGEX, __VA_ARGS__)
+/* Match callback context */
+struct hs_match_ctx {
+ struct rte_regex_ops *op;
+ uint16_t max_matches;
+ uint64_t total_matches; /* 64-bit counter for accurate tracking */
+};
+
static int
hs_regex_rule_db_import(struct rte_regexdev *dev, const char *rule_db,
uint32_t rule_db_len);
+static int
+hs_match_cb(unsigned int id, unsigned long long from,
+ unsigned long long to, unsigned int flags __rte_unused,
+ void *context)
+{
+ struct hs_match_ctx *ctx = (struct hs_match_ctx *)context;
+ struct rte_regex_ops *op;
+
+ if (unlikely(ctx == NULL))
+ return 1;
+
+ op = ctx->op;
+ if (unlikely(op == NULL))
+ return 1;
+
+ ctx->total_matches++;
+ op->nb_actual_matches++;
+
+ if (op->nb_matches < ctx->max_matches) {
+ struct rte_regexdev_match *m = &op->matches[op->nb_matches];
+
+ m->rule_id = id;
+ m->start_offset = (uint16_t)from;
+ m->len = (uint16_t)(to - from);
+ op->nb_matches++;
+
+ } else {
+ /* Match list full; report truncation while keeping actual count. */
+ op->rsp_flags |= RTE_REGEX_OPS_RSP_MAX_MATCH_F;
+ }
+
+ return 0;
+}
+
/* Device Info */
static int
-hs_regex_info_get(struct rte_regexdev *dev __rte_unused,
- struct rte_regexdev_info *info)
+hs_regex_info_get(struct rte_regexdev *dev, struct rte_regexdev_info *info)
{
+ (void)dev;
+
+ if (info == NULL)
+ return -EINVAL;
+
info->driver_name = HS_REGEX_DRIVER_NAME;
info->dev = NULL;
info->max_matches = UINT16_MAX;
@@ -79,6 +124,7 @@ hs_regex_configure(struct rte_regexdev *dev,
if (priv == NULL)
return -EINVAL;
+ /* Reconfigure is not allowed while running. */
if (priv->dev_state == HS_REGEX_DEV_STARTED) {
HS_LOG(ERR, "Cannot configure while device is started");
return -EBUSY;
@@ -95,6 +141,7 @@ hs_regex_configure(struct rte_regexdev *dev,
UINT16_MAX;
priv->nb_groups = cfg->nb_groups ? cfg->nb_groups : 1;
+ /* Reconfigure replaces rules, database, and queue resources. */
if (priv->rules) {
uint32_t i;
for (i = 0; i < priv->nb_rules; i++)
@@ -133,6 +180,7 @@ hs_regex_configure(struct rte_regexdev *dev,
HS_LOG(INFO, "Configured: %u queue pairs, max_matches=%u",
priv->nb_queue_pairs, priv->max_matches);
+ /* Configuration complete. */
priv->dev_state = HS_REGEX_DEV_CONFIGURED;
if (cfg->rule_db != NULL && cfg->rule_db_len > 0) {
@@ -140,9 +188,11 @@ hs_regex_configure(struct rte_regexdev *dev,
cfg->rule_db_len);
if (ret < 0) {
HS_LOG(ERR, "Failed to import rule DB in configure");
+ /* Roll back QP allocation on import failure. */
rte_free(priv->qps);
priv->qps = NULL;
priv->nb_queue_pairs = 0;
+ /* Revert state since configure failed */
priv->dev_state = HS_REGEX_DEV_CREATED;
return ret;
}
@@ -184,6 +234,10 @@ hs_regex_qp_setup(struct rte_regexdev *dev, uint16_t qp_id,
nb_desc = (qp_conf && qp_conf->nb_desc) ? qp_conf->nb_desc :
HS_REGEX_DEFAULT_NB_DESC;
+ /*
+ * The ring uses modulo arithmetic on head/tail.
+ * Keep descriptor count as power-of-two for predictable wrap behavior.
+ */
if (nb_desc == 0 || (nb_desc & (nb_desc - 1)) != 0) {
uint16_t orig = nb_desc;
uint32_t aligned = rte_align32pow2(nb_desc ? nb_desc : 1);
@@ -195,11 +249,13 @@ hs_regex_qp_setup(struct rte_regexdev *dev, uint16_t qp_id,
qp_id, orig, nb_desc);
}
+ /* Re-setup replaces previous ring allocation. */
if (qp->ops) {
rte_free(qp->ops);
qp->ops = NULL;
}
+ /* Scratch is recreated on re-setup. */
if (qp->scratch) {
hs_free_scratch(qp->scratch);
qp->scratch = NULL;
@@ -253,6 +309,7 @@ hs_regex_rule_db_update(struct rte_regexdev *dev,
if (nb_rules == 0)
return 0;
+ /* Lazy-init hash table for O(1) duplicate rule_id detection. */
if (!priv->rule_id_hash) {
struct rte_hash_parameters hp = {
.name = "hs_rule_ids",
@@ -271,6 +328,7 @@ hs_regex_rule_db_update(struct rte_regexdev *dev,
if (rules[i].op == RTE_REGEX_RULE_OP_ADD) {
uint32_t idx;
+ /* Reject empty or NULL patterns. */
if (!rules[i].pcre_rule || rules[i].pcre_rule_len == 0) {
HS_LOG(ERR, "Rule %u: NULL or empty pattern",
rules[i].rule_id);
@@ -278,6 +336,7 @@ hs_regex_rule_db_update(struct rte_regexdev *dev,
return i;
}
+ /* Keep rule_id unique for deterministic match reporting. */
if (priv->rule_id_hash &&
rte_hash_lookup(priv->rule_id_hash,
&rules[i].rule_id) >= 0) {
@@ -437,6 +496,7 @@ hs_regex_rule_db_compile_activate(struct rte_regexdev *dev)
if (priv->rules[i].rule_flags & RTE_REGEX_PCRE_RULE_UTF_F)
flags[i] |= HS_FLAG_UTF8;
+ /* Extended parameters */
ext[i].flags = 0;
if (priv->rules[i].min_offset) {
ext[i].flags |= HS_EXT_FLAG_MIN_OFFSET;
@@ -472,6 +532,7 @@ hs_regex_rule_db_compile_activate(struct rte_regexdev *dev)
return -EINVAL;
}
+ /* Allocate scratch per queue pair for scanning. */
for (i = 0; i < priv->nb_queue_pairs; i++) {
struct hs_regex_qp *qp = &priv->qps[i];
@@ -484,6 +545,7 @@ hs_regex_rule_db_compile_activate(struct rte_regexdev *dev)
uint32_t j;
HS_LOG(ERR, "Scratch alloc failed for qp %u", i);
+ /* Partial failure: unwind previous scratch allocations. */
for (j = 0; j < i; j++) {
if (priv->qps[j].scratch) {
hs_free_scratch(priv->qps[j].scratch);
@@ -502,6 +564,10 @@ hs_regex_rule_db_compile_activate(struct rte_regexdev *dev)
return 0;
}
+/*
+ * Import a prebuilt serialized Hyperscan database.
+ * The buffer must be produced by hs_serialize_database().
+ */
static int
hs_regex_rule_db_import(struct rte_regexdev *dev, const char *rule_db,
uint32_t rule_db_len)
@@ -527,17 +593,20 @@ hs_regex_rule_db_import(struct rte_regexdev *dev, const char *rule_db,
return -EINVAL;
}
+ /* Free existing database */
if (priv->db) {
hs_free_database(priv->db);
priv->db = NULL;
}
+ /* Deserialize the precompiled database */
err = hs_deserialize_database(rule_db, (size_t)rule_db_len, &priv->db);
if (err != HS_SUCCESS) {
HS_LOG(ERR, "hs_deserialize_database failed (error %d)", err);
return -EINVAL;
}
+ /* Imported DB also requires per-QP scratch. */
for (i = 0; i < priv->nb_queue_pairs; i++) {
struct hs_regex_qp *qp = &priv->qps[i];
@@ -551,6 +620,7 @@ hs_regex_rule_db_import(struct rte_regexdev *dev, const char *rule_db,
HS_LOG(ERR, "Scratch alloc failed for qp %u"
" after import", i);
+ /* Clean up already allocated scratches */
for (j = 0; j < i; j++) {
if (priv->qps[j].scratch) {
hs_free_scratch(priv->qps[j].scratch);
@@ -569,6 +639,10 @@ hs_regex_rule_db_import(struct rte_regexdev *dev, const char *rule_db,
return 0;
}
+/*
+ * Export the compiled Hyperscan database as a serialized blob.
+ * If rule_db is NULL, returns the required buffer size.
+ */
static int
hs_regex_rule_db_export(struct rte_regexdev *dev, char *rule_db)
{
@@ -596,6 +670,7 @@ hs_regex_rule_db_export(struct rte_regexdev *dev, char *rule_db)
}
if (rule_db == NULL) {
+ /* buf allocated by Hyperscan's malloc, not rte_malloc. */
free(buf);
if (len > INT_MAX) {
HS_LOG(ERR, "Serialized DB too large (%zu bytes)", len);
@@ -605,30 +680,176 @@ hs_regex_rule_db_export(struct rte_regexdev *dev, char *rule_db)
}
memcpy(rule_db, buf, len);
+ /* Hyperscan allocates buf internally via malloc, not rte_malloc. */
free(buf);
return 0;
}
-/* Fast path stubs replaced by real implementations in later patches. */
+/*
+ * Fast Path
+ *
+ * Thread-safety model: single-producer / single-consumer per queue
+ * pair. Each QP must be used by exactly one thread. No locking is
+ * performed on ring operations (head/tail/count). Using the same QP
+ * from multiple threads concurrently causes data races.
+ */
static uint16_t
-hs_regex_enqueue_burst(struct rte_regexdev *dev __rte_unused,
- uint16_t qp_id __rte_unused,
- struct rte_regex_ops **ops __rte_unused,
- uint16_t nb_ops __rte_unused)
+hs_regex_enqueue_burst(struct rte_regexdev *dev, uint16_t qp_id,
+ struct rte_regex_ops **ops, uint16_t nb_ops)
{
- return 0;
+ struct hs_regex_priv *priv;
+ struct hs_regex_qp *qp;
+ uint16_t i;
+ uint16_t free_space;
+
+ if (unlikely(dev == NULL || ops == NULL))
+ return 0;
+
+ priv = dev->data->dev_private;
+ if (unlikely(priv == NULL))
+ return 0;
+
+ /* Validate queue pair index. */
+ if (unlikely(qp_id >= priv->nb_queue_pairs)) {
+ HS_LOG(ERR, "enqueue: invalid qp_id %u (max %u)",
+ qp_id, priv->nb_queue_pairs);
+ return 0;
+ }
+
+ if (unlikely(priv->dev_state != HS_REGEX_DEV_STARTED)) {
+ HS_LOG(ERR, "enqueue: device not started");
+ return 0;
+ }
+
+ if (unlikely(priv->db == NULL)) {
+ HS_LOG(ERR, "enqueue: no compiled database, dropping burst");
+ return 0;
+ }
+
+ qp = &priv->qps[qp_id];
+
+ if (unlikely(qp->scratch == NULL)) {
+ HS_LOG(ERR, "enqueue: qp %u has no scratch, dropping burst",
+ qp_id);
+ return 0;
+ }
+
+ /* Bounded ring: accept only free entries. */
+ free_space = qp->nb_desc - qp->count;
+ if (nb_ops > free_space)
+ nb_ops = free_space;
+
+ for (i = 0; i < nb_ops; i++) {
+ struct rte_regex_ops *op = ops[i];
+ struct rte_mbuf *mbuf;
+ const char *data;
+ uint32_t data_len;
+ struct hs_match_ctx ctx = { .total_matches = 0 };
+ hs_error_t err;
+
+ if (unlikely(op == NULL))
+ break;
+
+ mbuf = op->mbuf;
+ if (unlikely(mbuf == NULL)) {
+ op->nb_matches = 0;
+ op->nb_actual_matches = 0;
+ op->rsp_flags = RTE_REGEX_OPS_RSP_RESOURCE_LIMIT_REACHED_F;
+ goto enqueue_op;
+ }
+
+ /* hs_scan requires contiguous data. */
+ if (rte_pktmbuf_linearize(mbuf) != 0) {
+ op->nb_matches = 0;
+ op->nb_actual_matches = 0;
+ op->rsp_flags = RTE_REGEX_OPS_RSP_RESOURCE_LIMIT_REACHED_F;
+ goto enqueue_op;
+ }
+ data = rte_pktmbuf_mtod(mbuf, const char *);
+ data_len = rte_pktmbuf_pkt_len(mbuf);
+
+ if (unlikely(data_len == 0)) {
+ op->nb_matches = 0;
+ op->nb_actual_matches = 0;
+ op->rsp_flags = 0;
+ goto enqueue_op;
+ }
+
+ op->nb_matches = 0;
+ op->nb_actual_matches = 0;
+ op->rsp_flags = 0;
+
+ ctx.op = op;
+ ctx.max_matches = priv->max_matches;
+ ctx.total_matches = 0;
+
+ err = hs_scan(priv->db, data, data_len, 0,
+ qp->scratch, hs_match_cb, &ctx);
+
+ if (unlikely(err != HS_SUCCESS &&
+ err != HS_SCAN_TERMINATED))
+ op->rsp_flags |=
+ RTE_REGEX_OPS_RSP_RESOURCE_LIMIT_REACHED_F;
+
+enqueue_op:
+ /* Keep completed op for dequeue_burst(). */
+ qp->ops[qp->tail] = op;
+ qp->tail = (qp->tail + 1) % qp->nb_desc;
+ qp->count++;
+
+ qp->qp_matches += ctx.total_matches;
+ }
+
+ qp->qp_enqueued += i;
+ return i;
}
static uint16_t
-hs_regex_dequeue_burst(struct rte_regexdev *dev __rte_unused,
- uint16_t qp_id __rte_unused,
- struct rte_regex_ops **ops __rte_unused,
- uint16_t nb_ops __rte_unused)
+hs_regex_dequeue_burst(struct rte_regexdev *dev, uint16_t qp_id,
+ struct rte_regex_ops **ops, uint16_t nb_ops)
{
- return 0;
+ struct hs_regex_priv *priv;
+ struct hs_regex_qp *qp;
+ uint16_t i;
+ uint16_t avail;
+
+ if (unlikely(dev == NULL || ops == NULL))
+ return 0;
+
+ priv = dev->data->dev_private;
+ if (unlikely(priv == NULL))
+ return 0;
+
+ /* Validate queue pair index. */
+ if (unlikely(qp_id >= priv->nb_queue_pairs)) {
+ HS_LOG(ERR, "dequeue: invalid qp_id %u (max %u)",
+ qp_id, priv->nb_queue_pairs);
+ return 0;
+ }
+
+ if (unlikely(priv->qps == NULL))
+ return 0;
+
+ qp = &priv->qps[qp_id];
+
+ /* Return completed ops currently available in the ring. */
+ avail = qp->count;
+ if (nb_ops > avail)
+ nb_ops = avail;
+
+ for (i = 0; i < nb_ops; i++) {
+ ops[i] = qp->ops[qp->head];
+ qp->head = (qp->head + 1) % qp->nb_desc;
+ qp->count--;
+ }
+
+ /* Device-level stats. */
+ qp->qp_dequeued += i;
+ return i;
}
+/* Operations table */
static const struct rte_regexdev_ops hs_regexdev_ops = {
.dev_info_get = hs_regex_info_get,
.dev_configure = hs_regex_configure,
--
2.43.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [RFC 5/7] regex/hs: add per-queue-pair extended statistics
2026-08-28 5:35 [RFC 0/7] regex/hs: introduce Hyperscan regex PMD Prudvi Deti
` (3 preceding siblings ...)
2026-08-28 5:35 ` [RFC 4/7] regex/hs: add enqueue and dequeue burst paths Prudvi Deti
@ 2026-08-28 5:35 ` Prudvi Deti
2026-08-28 5:35 ` [RFC 6/7] regex/hs: add start stop close and device dump Prudvi Deti
` (2 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Prudvi Deti @ 2026-08-28 5:35 UTC (permalink / raw)
To: dev; +Cc: bruce.richardson, orika, nirint.shah, prudvi.deti
Add xstats support with three per-QP counters: enqueued, dequeued,
and matches. Implement xstats_names_get, xstats_get, and
xstats_reset with support for bulk and selective reset by stat id.
Signed-off-by: Prudvi Deti <prudvi.deti@intel.com>
---
doc/guides/regexdevs/hs.rst | 9 ++
drivers/regex/hs/hs_regex.c | 159 ++++++++++++++++++++++++++++++++++++
2 files changed, 168 insertions(+)
diff --git a/doc/guides/regexdevs/hs.rst b/doc/guides/regexdevs/hs.rst
index e94466a..9c5f666 100644
--- a/doc/guides/regexdevs/hs.rst
+++ b/doc/guides/regexdevs/hs.rst
@@ -20,7 +20,16 @@ Features
- Up to 64 queue pairs, each with up to 32768 descriptors
- Up to 1,000,000 rules per device, with O(1) duplicate rule_id
detection backed by ``rte_hash``
+- Up to 65,535 matches per scan operation (API field width limit);
+ cumulative totals are tracked via per-queue-pair xstats
- Per-rule extended match parameters (minimum/maximum start offset)
+- Hyperscan block-mode engine supports scan buffers up to 4 GB
+ (library capability)
+- Through the current ``rte_regexdev`` API, this PMD advertises
+ ``max_payload_size = 65,535`` bytes (``uint16_t`` field width), so
+ ``dpdk-test-regex`` validation is limited to about 64 KB per op
+- Hyperscan uses x86 vectorized instructions (SSSE3/AVX2/AVX-512)
+ for high throughput
- Per-queue-pair statistics via xstats
In the RegEx driver feature matrix, this PMD reports:
diff --git a/drivers/regex/hs/hs_regex.c b/drivers/regex/hs/hs_regex.c
index e84259e..7841a83 100644
--- a/drivers/regex/hs/hs_regex.c
+++ b/drivers/regex/hs/hs_regex.c
@@ -849,6 +849,161 @@ hs_regex_dequeue_burst(struct rte_regexdev *dev, uint16_t qp_id,
return i;
}
+/* xstats: per-QP statistics */
+
+/* 3 stats per QP: enqueued, dequeued, matches */
+#define HS_XSTATS_PER_QP 3
+
+static const char * const hs_xstat_suffixes[HS_XSTATS_PER_QP] = {
+ "enqueued", "dequeued", "matches"
+};
+
+static int
+hs_regex_xstats_names_get(struct rte_regexdev *dev,
+ struct rte_regexdev_xstats_map *xstats_map)
+{
+ struct hs_regex_priv *priv;
+ uint16_t nqp;
+ int total;
+ int idx = 0;
+ uint16_t q;
+ int s;
+
+ if (dev == NULL)
+ return -EINVAL;
+
+ priv = dev->data->dev_private;
+ if (priv == NULL)
+ return -EINVAL;
+
+ nqp = priv->nb_queue_pairs;
+ total = nqp * HS_XSTATS_PER_QP;
+
+ if (!xstats_map)
+ return total;
+
+ for (q = 0; q < nqp; q++) {
+ for (s = 0; s < HS_XSTATS_PER_QP; s++) {
+ snprintf(xstats_map[idx].name,
+ sizeof(xstats_map[idx].name),
+ "qp%u_%s", q, hs_xstat_suffixes[s]);
+ xstats_map[idx].id = idx;
+ idx++;
+ }
+ }
+ return total;
+}
+
+static int
+hs_regex_xstats_get(struct rte_regexdev *dev,
+ const uint16_t *ids, uint64_t *values,
+ uint16_t nb_values)
+{
+ struct hs_regex_priv *priv;
+ uint16_t nqp;
+ int total;
+ uint16_t id, qp_idx, stat_idx;
+ uint16_t i;
+
+ if (dev == NULL)
+ return -EINVAL;
+
+ priv = dev->data->dev_private;
+ if (priv == NULL)
+ return -EINVAL;
+
+ nqp = priv->nb_queue_pairs;
+ total = nqp * HS_XSTATS_PER_QP;
+
+ if (!ids || !values)
+ return total;
+
+ if (priv->qps == NULL)
+ total = 0;
+
+ for (i = 0; i < nb_values; i++) {
+ id = ids[i];
+
+ if (id >= (uint16_t)total) {
+ values[i] = 0;
+ continue;
+ }
+
+ qp_idx = id / HS_XSTATS_PER_QP;
+ stat_idx = id % HS_XSTATS_PER_QP;
+
+ switch (stat_idx) {
+ case 0:
+ values[i] = priv->qps[qp_idx].qp_enqueued;
+ break;
+ case 1:
+ values[i] = priv->qps[qp_idx].qp_dequeued;
+ break;
+ case 2:
+ values[i] = priv->qps[qp_idx].qp_matches;
+ break;
+ }
+ }
+ return nb_values;
+}
+
+static int
+hs_regex_xstats_reset(struct rte_regexdev *dev,
+ const uint16_t *ids, uint16_t nb_ids)
+{
+ struct hs_regex_priv *priv;
+ uint16_t nqp;
+ int total;
+ uint16_t q, i;
+ uint16_t id, qp_idx, stat_idx;
+
+ if (dev == NULL)
+ return -EINVAL;
+
+ priv = dev->data->dev_private;
+ if (priv == NULL)
+ return -EINVAL;
+
+ nqp = priv->nb_queue_pairs;
+ total = nqp * HS_XSTATS_PER_QP;
+
+ if (priv->qps == NULL)
+ return 0;
+
+ if (!ids || nb_ids == 0) {
+ /* Reset all stats */
+ for (q = 0; q < nqp; q++) {
+ priv->qps[q].qp_enqueued = 0;
+ priv->qps[q].qp_dequeued = 0;
+ priv->qps[q].qp_matches = 0;
+ }
+ } else {
+ /* Reset specific stats by id */
+ for (i = 0; i < nb_ids; i++) {
+ id = ids[i];
+
+ if (id >= (uint16_t)total)
+ continue;
+
+ qp_idx = id / HS_XSTATS_PER_QP;
+ stat_idx = id % HS_XSTATS_PER_QP;
+
+ switch (stat_idx) {
+ case 0:
+ priv->qps[qp_idx].qp_enqueued = 0;
+ break;
+ case 1:
+ priv->qps[qp_idx].qp_dequeued = 0;
+ break;
+ case 2:
+ priv->qps[qp_idx].qp_matches = 0;
+ break;
+ }
+ }
+ }
+ return 0;
+}
+
/* Operations table */
static const struct rte_regexdev_ops hs_regexdev_ops = {
.dev_info_get = hs_regex_info_get,
@@ -858,6 +1013,10 @@ static const struct rte_regexdev_ops hs_regexdev_ops = {
.dev_rule_db_compile_activate = hs_regex_rule_db_compile_activate,
.dev_db_import = hs_regex_rule_db_import,
.dev_db_export = hs_regex_rule_db_export,
+ .dev_xstats_names_get = hs_regex_xstats_names_get,
+ .dev_xstats_get = hs_regex_xstats_get,
+ .dev_xstats_by_name_get = NULL,
+ .dev_xstats_reset = hs_regex_xstats_reset,
};
/* Device Lifecycle */
--
2.43.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [RFC 6/7] regex/hs: add start stop close and device dump
2026-08-28 5:35 [RFC 0/7] regex/hs: introduce Hyperscan regex PMD Prudvi Deti
` (4 preceding siblings ...)
2026-08-28 5:35 ` [RFC 5/7] regex/hs: add per-queue-pair extended statistics Prudvi Deti
@ 2026-08-28 5:35 ` Prudvi Deti
2026-08-28 5:35 ` [RFC 7/7] regex/hs: add Hyperscan compile flag support Prudvi Deti
2026-08-28 16:34 ` [RFC 0/7] regex/hs: introduce Hyperscan regex PMD Stephen Hemminger
7 siblings, 0 replies; 9+ messages in thread
From: Prudvi Deti @ 2026-08-28 5:35 UTC (permalink / raw)
To: dev; +Cc: bruce.richardson, orika, nirint.shah, prudvi.deti
Add device lifecycle state machine with validation: start requires
a compiled database, stop resets QP ring pointers, close releases
all resources and may be called without explicit stop.
Add dev_dump printing driver state, Hyperscan version, rule list,
and aggregated per-QP statistics.
Signed-off-by: Prudvi Deti <prudvi.deti@intel.com>
---
drivers/regex/hs/hs_regex.c | 176 ++++++++++++++++++++++++++++++++++++
1 file changed, 176 insertions(+)
diff --git a/drivers/regex/hs/hs_regex.c b/drivers/regex/hs/hs_regex.c
index 7841a83..b42f791 100644
--- a/drivers/regex/hs/hs_regex.c
+++ b/drivers/regex/hs/hs_regex.c
@@ -685,6 +685,174 @@ hs_regex_rule_db_export(struct rte_regexdev *dev, char *rule_db)
return 0;
}
+/* Start */
+static int
+hs_regex_start(struct rte_regexdev *dev)
+{
+ struct hs_regex_priv *priv;
+
+ if (dev == NULL)
+ return -EINVAL;
+
+ priv = dev->data->dev_private;
+ if (priv == NULL)
+ return -EINVAL;
+
+ /* Start requires configure and a compiled/imported database. */
+ if (priv->dev_state == HS_REGEX_DEV_CREATED) {
+ HS_LOG(ERR, "Cannot start: device not configured");
+ return -EINVAL;
+ }
+ if (priv->dev_state == HS_REGEX_DEV_STARTED) {
+ HS_LOG(ERR, "Device already started");
+ return -EBUSY;
+ }
+
+ if (!priv->db_compiled) {
+ HS_LOG(ERR, "Cannot start: database not compiled/imported");
+ return -EINVAL;
+ }
+
+ priv->dev_state = HS_REGEX_DEV_STARTED;
+ HS_LOG(INFO, "Device started (%u rules, %u queue pairs)",
+ priv->nb_rules, priv->nb_queue_pairs);
+ return 0;
+}
+
+/* Stop */
+static int
+hs_regex_stop(struct rte_regexdev *dev)
+{
+ struct hs_regex_priv *priv;
+ uint16_t i;
+
+ if (dev == NULL)
+ return -EINVAL;
+
+ priv = dev->data->dev_private;
+ if (priv == NULL)
+ return -EINVAL;
+
+ /* Stop is valid only from STARTED state. */
+ if (priv->dev_state != HS_REGEX_DEV_STARTED) {
+ HS_LOG(ERR, "Device not started, cannot stop");
+ return -EINVAL;
+ }
+
+ if (priv->qps == NULL)
+ goto stopped;
+
+ for (i = 0; i < priv->nb_queue_pairs; i++) {
+ struct hs_regex_qp *qp = &priv->qps[i];
+
+ qp->head = 0;
+ qp->tail = 0;
+ qp->count = 0;
+ }
+
+stopped:
+ priv->dev_state = HS_REGEX_DEV_STOPPED;
+ HS_LOG(INFO, "Device stopped");
+ return 0;
+}
+
+/* Close */
+static int
+hs_regex_close(struct rte_regexdev *dev)
+{
+ struct hs_regex_priv *priv;
+ uint32_t i;
+
+ if (dev == NULL)
+ return -EINVAL;
+
+ priv = dev->data->dev_private;
+ if (priv == NULL)
+ return -EINVAL;
+
+ /* Close may be called without an explicit stop. */
+ if (priv->dev_state == HS_REGEX_DEV_STARTED) {
+ HS_LOG(WARNING, "Device still started, stopping before close");
+ hs_regex_stop(dev);
+ }
+
+ if (priv->qps) {
+ for (i = 0; i < priv->nb_queue_pairs; i++) {
+ if (priv->qps[i].scratch)
+ hs_free_scratch(priv->qps[i].scratch);
+ rte_free(priv->qps[i].ops);
+ }
+ rte_free(priv->qps);
+ priv->qps = NULL;
+ }
+
+ if (priv->db) {
+ hs_free_database(priv->db);
+ priv->db = NULL;
+ }
+
+ for (i = 0; i < priv->nb_rules; i++)
+ rte_free(priv->rules[i].pattern);
+ rte_free(priv->rules);
+ priv->rules = NULL;
+ priv->nb_rules = 0;
+ priv->rules_cap = 0;
+ priv->db_compiled = 0;
+
+ if (priv->rule_id_hash) {
+ rte_hash_free(priv->rule_id_hash);
+ priv->rule_id_hash = NULL;
+ }
+
+ /* Return to initial state. */
+ priv->dev_state = HS_REGEX_DEV_CREATED;
+
+ HS_LOG(INFO, "Device closed");
+ return 0;
+}
+
+/* Dump */
+static int
+hs_regex_dump(struct rte_regexdev *dev, FILE *f)
+{
+ struct hs_regex_priv *priv;
+ uint64_t total_enq = 0, total_deq = 0, total_match = 0;
+ uint32_t i;
+
+ if (dev == NULL || f == NULL)
+ return -EINVAL;
+
+ priv = dev->data->dev_private;
+ if (priv == NULL)
+ return -EINVAL;
+
+ if (priv->qps != NULL) {
+ for (i = 0; i < priv->nb_queue_pairs; i++) {
+ total_enq += priv->qps[i].qp_enqueued;
+ total_deq += priv->qps[i].qp_dequeued;
+ total_match += priv->qps[i].qp_matches;
+ }
+ }
+
+ fprintf(f, "=== Hyperscan RegEx PMD ===\n");
+ fprintf(f, " Driver: %s\n", HS_REGEX_DRIVER_NAME);
+ fprintf(f, " HS Version: %s\n", hs_version());
+ fprintf(f, " Rules: %u\n", priv->nb_rules);
+ fprintf(f, " Compiled: %s\n", priv->db_compiled ? "yes" : "no");
+ fprintf(f, " Queue Pairs: %u\n", priv->nb_queue_pairs);
+ fprintf(f, " Max Matches: %u\n", priv->max_matches);
+ fprintf(f, " Enqueued: %" PRIu64 "\n", total_enq);
+ fprintf(f, " Dequeued: %" PRIu64 "\n", total_deq);
+ fprintf(f, " Matches: %" PRIu64 "\n", total_match);
+
+ for (i = 0; i < priv->nb_rules; i++)
+ fprintf(f, " Rule[%u]: id=%u group=%u pattern=%s\n", i,
+ priv->rules[i].rule_id, priv->rules[i].group_id,
+ priv->rules[i].pattern);
+
+ return 0;
+}
+
/*
* Fast Path
*
@@ -1009,6 +1177,11 @@ static const struct rte_regexdev_ops hs_regexdev_ops = {
.dev_info_get = hs_regex_info_get,
.dev_configure = hs_regex_configure,
.dev_qp_setup = hs_regex_qp_setup,
+ .dev_start = hs_regex_start,
+ .dev_stop = hs_regex_stop,
+ .dev_close = hs_regex_close,
+ .dev_attr_get = NULL,
+ .dev_attr_set = NULL,
.dev_rule_db_update = hs_regex_rule_db_update,
.dev_rule_db_compile_activate = hs_regex_rule_db_compile_activate,
.dev_db_import = hs_regex_rule_db_import,
@@ -1017,6 +1190,8 @@ static const struct rte_regexdev_ops hs_regexdev_ops = {
.dev_xstats_get = hs_regex_xstats_get,
.dev_xstats_by_name_get = NULL,
.dev_xstats_reset = hs_regex_xstats_reset,
+ .dev_selftest = NULL,
+ .dev_dump = hs_regex_dump,
};
/* Device Lifecycle */
@@ -1072,6 +1247,7 @@ hs_regex_dev_destroy(const char *name)
priv = dev->data->dev_private;
if (priv) {
+ hs_regex_close(dev);
rte_free(priv);
dev->data->dev_private = NULL;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [RFC 7/7] regex/hs: add Hyperscan compile flag support
2026-08-28 5:35 [RFC 0/7] regex/hs: introduce Hyperscan regex PMD Prudvi Deti
` (5 preceding siblings ...)
2026-08-28 5:35 ` [RFC 6/7] regex/hs: add start stop close and device dump Prudvi Deti
@ 2026-08-28 5:35 ` Prudvi Deti
2026-08-28 16:34 ` [RFC 0/7] regex/hs: introduce Hyperscan regex PMD Stephen Hemminger
7 siblings, 0 replies; 9+ messages in thread
From: Prudvi Deti @ 2026-08-28 5:35 UTC (permalink / raw)
To: dev; +Cc: bruce.richardson, orika, nirint.shah, prudvi.deti
Map standard DPDK rule flags (ALLOW_EMPTY, CASELESS, DOTALL,
MULTILINE, UCP, UTF) and PMD-private Hyperscan flags (SINGLEMATCH,
PREFILTER, SOM_LEFTMOST, COMBINATION, QUIET) to HS_FLAG_*
constants during compilation.
Add flag validation in rule_db_update to reject unknown flag bits.
Update info_get to advertise all supported standard flags.
Signed-off-by: Prudvi Deti <prudvi.deti@intel.com>
---
doc/guides/regexdevs/hs.rst | 16 ++++++++----
drivers/regex/hs/hs_regex.c | 51 ++++++++++++++++++++++++++++++++++---
drivers/regex/hs/hs_regex.h | 17 ++++++++++---
3 files changed, 72 insertions(+), 12 deletions(-)
diff --git a/doc/guides/regexdevs/hs.rst b/doc/guides/regexdevs/hs.rst
index 9c5f666..de85667 100644
--- a/doc/guides/regexdevs/hs.rst
+++ b/doc/guides/regexdevs/hs.rst
@@ -135,12 +135,18 @@ All counters can be reset in bulk or selectively by stat id via
Limitations
-----------
-- Scanning is synchronous: ``enqueue_burst`` blocks until
- ``hs_scan()`` completes for each operation.
- Multi-segment mbufs are linearized (``rte_pktmbuf_linearize()``)
- before scanning; linearization failure marks the op with
- ``RTE_REGEX_OPS_RSP_RESOURCE_LIMIT_REACHED_F``.
-- Multi-process mode is not supported.
+ before scanning. If linearization fails (first mbuf buffer too
+ small for the full packet), the op is returned to the application
+ with ``RTE_REGEX_OPS_RSP_RESOURCE_LIMIT_REACHED_F`` and zero
+ matches. Applications scanning large payloads should allocate
+ mbufs with sufficient ``data_room_size``.
+- Multi-process mode is not supported. The PMD rejects secondary
+ processes at probe time. Hyperscan's compiled database and scratch
+ space are allocated in process-private memory and cannot be shared
+ across separate OS processes. Multi-lcore (multiple threads within
+ a single process) is fully supported — each lcore uses its own
+ queue pair with dedicated scratch space.
- Each queue pair must be used by exactly one lcore
(single-producer/single-consumer model).
diff --git a/drivers/regex/hs/hs_regex.c b/drivers/regex/hs/hs_regex.c
index b42f791..515370d 100644
--- a/drivers/regex/hs/hs_regex.c
+++ b/drivers/regex/hs/hs_regex.c
@@ -101,9 +101,11 @@ hs_regex_info_get(struct rte_regexdev *dev, struct rte_regexdev_info *info)
info->max_rules_per_group = HS_REGEX_MAX_RULES;
info->max_groups = HS_REGEX_MAX_GROUPS;
info->regexdev_capa = RTE_REGEXDEV_CAPA_RUNTIME_COMPILATION_F;
- info->rule_flags = RTE_REGEX_PCRE_RULE_CASELESS_F |
+ info->rule_flags = RTE_REGEX_PCRE_RULE_ALLOW_EMPTY_F |
+ RTE_REGEX_PCRE_RULE_CASELESS_F |
RTE_REGEX_PCRE_RULE_DOTALL_F |
RTE_REGEX_PCRE_RULE_MULTILINE_F |
+ RTE_REGEX_PCRE_RULE_UCP_F |
RTE_REGEX_PCRE_RULE_UTF_F;
return 0;
@@ -296,6 +298,18 @@ hs_regex_rule_db_update(struct rte_regexdev *dev,
uint16_t nb_rules)
{
struct hs_regex_priv *priv;
+ const uint64_t known_flags = RTE_REGEX_PCRE_RULE_ALLOW_EMPTY_F |
+ RTE_REGEX_PCRE_RULE_CASELESS_F |
+ RTE_REGEX_PCRE_RULE_DOTALL_F |
+ RTE_REGEX_PCRE_RULE_MULTILINE_F |
+ RTE_REGEX_PCRE_RULE_UCP_F |
+ RTE_REGEX_PCRE_RULE_UTF_F |
+ HS_REGEX_RULE_SINGLEMATCH_F |
+ HS_REGEX_RULE_PREFILTER_F |
+ HS_REGEX_RULE_SOM_LEFTMOST_F |
+ HS_REGEX_RULE_COMBINATION_F |
+ HS_REGEX_RULE_QUIET_F;
+ uint64_t flag_bits;
uint64_t rf;
uint16_t i;
@@ -325,6 +339,17 @@ hs_regex_rule_db_update(struct rte_regexdev *dev,
}
for (i = 0; i < nb_rules; i++) {
+ flag_bits = rules[i].rule_flags &
+ ((1ULL << HS_REGEX_EXT_MAX_OFFSET_SHIFT) - 1);
+
+ if (flag_bits & ~known_flags) {
+ HS_LOG(ERR, "Rule %u: unsupported flags 0x%" PRIx64,
+ rules[i].rule_id,
+ (uint64_t)(flag_bits & ~known_flags));
+ rte_errno = ENOTSUP;
+ return i;
+ }
+
if (rules[i].op == RTE_REGEX_RULE_OP_ADD) {
uint32_t idx;
@@ -493,8 +518,22 @@ hs_regex_rule_db_compile_activate(struct rte_regexdev *dev)
flags[i] |= HS_FLAG_DOTALL;
if (priv->rules[i].rule_flags & RTE_REGEX_PCRE_RULE_MULTILINE_F)
flags[i] |= HS_FLAG_MULTILINE;
+ if (priv->rules[i].rule_flags & HS_REGEX_RULE_SINGLEMATCH_F)
+ flags[i] |= HS_FLAG_SINGLEMATCH;
if (priv->rules[i].rule_flags & RTE_REGEX_PCRE_RULE_UTF_F)
flags[i] |= HS_FLAG_UTF8;
+ if (priv->rules[i].rule_flags & RTE_REGEX_PCRE_RULE_UCP_F)
+ flags[i] |= HS_FLAG_UCP;
+ if (priv->rules[i].rule_flags & HS_REGEX_RULE_PREFILTER_F)
+ flags[i] |= HS_FLAG_PREFILTER;
+ if (priv->rules[i].rule_flags & HS_REGEX_RULE_SOM_LEFTMOST_F)
+ flags[i] |= HS_FLAG_SOM_LEFTMOST;
+ if (priv->rules[i].rule_flags & HS_REGEX_RULE_COMBINATION_F)
+ flags[i] |= HS_FLAG_COMBINATION;
+ if (priv->rules[i].rule_flags & HS_REGEX_RULE_QUIET_F)
+ flags[i] |= HS_FLAG_QUIET;
+ if (priv->rules[i].rule_flags & RTE_REGEX_PCRE_RULE_ALLOW_EMPTY_F)
+ flags[i] |= HS_FLAG_ALLOWEMPTY;
/* Extended parameters */
ext[i].flags = 0;
@@ -886,8 +925,14 @@ hs_regex_enqueue_burst(struct rte_regexdev *dev, uint16_t qp_id,
}
if (unlikely(priv->dev_state != HS_REGEX_DEV_STARTED)) {
- HS_LOG(ERR, "enqueue: device not started");
- return 0;
+ /* Auto-start if DB is ready (supports apps that skip start). */
+ if (priv->db_compiled) {
+ priv->dev_state = HS_REGEX_DEV_STARTED;
+ HS_LOG(NOTICE, "enqueue: auto-started device");
+ } else {
+ HS_LOG(ERR, "enqueue: device not started and no DB");
+ return 0;
+ }
}
if (unlikely(priv->db == NULL)) {
diff --git a/drivers/regex/hs/hs_regex.h b/drivers/regex/hs/hs_regex.h
index ddd6d9b..2641482 100644
--- a/drivers/regex/hs/hs_regex.h
+++ b/drivers/regex/hs/hs_regex.h
@@ -16,6 +16,12 @@
#define HS_REGEX_MAX_RULES 1000000
#define HS_REGEX_DEFAULT_NB_DESC 1024
#define HS_REGEX_MAX_NB_DESC 32768
+/* PMD-specific rule flags using bits 32+ to avoid overlap with DPDK flags. */
+#define HS_REGEX_RULE_SINGLEMATCH_F (1ULL << 32)
+#define HS_REGEX_RULE_PREFILTER_F (1ULL << 33)
+#define HS_REGEX_RULE_SOM_LEFTMOST_F (1ULL << 34)
+#define HS_REGEX_RULE_COMBINATION_F (1ULL << 35)
+#define HS_REGEX_RULE_QUIET_F (1ULL << 36)
/* Ext params encoded in rule_flags bits 37-63 */
#define HS_REGEX_EXT_MAX_OFFSET_SHIFT 37
@@ -25,10 +31,10 @@
/* Device lifecycle state machine. */
enum hs_regex_dev_state {
- HS_REGEX_DEV_CREATED = 0,
- HS_REGEX_DEV_CONFIGURED,
- HS_REGEX_DEV_STARTED,
- HS_REGEX_DEV_STOPPED,
+ HS_REGEX_DEV_CREATED = 0, /* after dev_create, before configure */
+ HS_REGEX_DEV_CONFIGURED, /* after configure */
+ HS_REGEX_DEV_STARTED, /* after start */
+ HS_REGEX_DEV_STOPPED, /* after stop (can restart) */
};
/* Per-rule entry stored before compilation */
@@ -50,6 +56,7 @@ struct hs_regex_qp {
uint16_t tail;
uint16_t count;
hs_scratch_t *scratch;
+ /* Per-QP counters exported via xstats. */
uint64_t qp_enqueued;
uint64_t qp_dequeued;
uint64_t qp_matches;
@@ -71,9 +78,11 @@ struct hs_regex_priv {
uint16_t max_matches;
uint16_t nb_groups;
+ /* Lifecycle state used to validate configure/start/stop. */
enum hs_regex_dev_state dev_state;
};
+/* Device lifecycle */
int hs_regex_dev_create(const char *name, struct rte_device *device);
void hs_regex_dev_destroy(const char *name);
--
2.43.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* Re: [RFC 0/7] regex/hs: introduce Hyperscan regex PMD
2026-08-28 5:35 [RFC 0/7] regex/hs: introduce Hyperscan regex PMD Prudvi Deti
` (6 preceding siblings ...)
2026-08-28 5:35 ` [RFC 7/7] regex/hs: add Hyperscan compile flag support Prudvi Deti
@ 2026-08-28 16:34 ` Stephen Hemminger
7 siblings, 0 replies; 9+ messages in thread
From: Stephen Hemminger @ 2026-08-28 16:34 UTC (permalink / raw)
To: Prudvi Deti; +Cc: dev, bruce.richardson, orika, nirint.shah
On Fri, 28 Aug 2026 11:05:49 +0530
Prudvi Deti <prudvi.deti@intel.com> wrote:
> This RFC series adds a new regexdev PMD based on Hyperscan.
>
> Goal: provide a software regex PMD for environments without regex HW,
> while keeping compatibility with the standard ``rte_regexdev`` API.
> The PMD is a virtual device, created with ``--vdev=regex_hs`` at EAL
> init.
There was ongoing discussion in TB that the regexdev PMD should
be deprecated and removed. It turns out that HW regex is slower
than software.
It is good to have a software alternative, but going through
DPDK API's to do regex seems like an awkward way to do it.
For applications simpler/faster to use regex libraries directly.
^ permalink raw reply [flat|nested] 9+ messages in thread