* [RFC v2 1/7] regex/hs: add driver skeleton and build integration
2026-09-10 8:00 ` [RFC v2 " Prudvi Deti
@ 2026-09-10 8:00 ` Prudvi Deti
2026-09-10 8:00 ` [RFC v2 2/7] regex/hs: add device configure and queue pair setup Prudvi Deti
` (6 subsequent siblings)
7 siblings, 0 replies; 25+ messages in thread
From: Prudvi Deti @ 2026-09-10 8:00 UTC (permalink / raw)
To: dev
Cc: Bruce Richardson, Stephen Hemminger, Ori Kam, Aman Deep Singh,
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 | 187 +++++++++++++++++++++++++
doc/guides/regexdevs/index.rst | 1 +
doc/guides/rel_notes/release_26_11.rst | 19 +++
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, 497 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..dc22514
--- /dev/null
+++ b/doc/guides/regexdevs/hs.rst
@@ -0,0 +1,187 @@
+.. 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``
+- Although Hyperscan supports 1,000,000 patterns, the current DPDK public API
+ limits match-result rule IDs to 20 bits. Rule IDs above ``0xFFFFF`` cannot
+ be represented without truncation.
+- 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 and Fedora/RHEL, install Intel
+Hyperscan 5.4.2 from source:
+
+#. Install the build dependencies:
+
+ On Ubuntu/Debian:
+
+ .. code-block:: console
+
+ sudo apt install build-essential cmake curl libboost-dev pkg-config python3 ragel
+
+ On Fedora/RHEL:
+
+ .. code-block:: console
+
+ sudo dnf install boost-devel cmake curl gcc gcc-c++ make pkgconf-pkg-config python3 ragel
+
+#. Download the Intel Hyperscan 5.4.2 source archive:
+
+ .. code-block:: console
+
+ curl -LO https://github.com/intel/hyperscan/archive/refs/tags/v5.4.2.tar.gz
+
+#. Extract the archive:
+
+ .. code-block:: console
+
+ tar -xzf v5.4.2.tar.gz
+
+#. Build and install Hyperscan:
+
+ .. code-block:: console
+
+ cd hyperscan-5.4.2
+ cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON
+ cmake --build build --parallel
+ sudo cmake --install build
+ sudo ldconfig
+
+ Verify that version 5.4.2 is discoverable:
+
+ .. code-block:: console
+
+ PKG_CONFIG_PATH=/usr/local/lib64/pkgconfig pkg-config --modversion libhs
+
+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
+-----------
+
+- ``RTE_REGEX_OPS_REQ_MATCH_HIGH_PRIORITY_F`` is not compatible with this
+ Hyperscan PMD and is not supported. DPDK requires ordering matches by rule
+ ID, start offset, and match length, while Hyperscan without SOM reports only
+ the match end offset.
+- 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..617d6ea 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -56,6 +56,25 @@ New Features
=======================================================
+* **Added Hyperscan regex PMD.**
+
+ Added a new software regex PMD (``regex_hs``) based on Intel
+ Hyperscan library. The PMD is a virtual device created with
+ ``--vdev=regex_hs`` and implements the ``rte_regexdev`` API using
+ Hyperscan block mode scanning. Key capabilities include:
+
+ - Runtime pattern compilation via ``hs_compile_ext_multi()`` and
+ serialized database import/export
+ - Up to 64 queue pairs with per-queue-pair Hyperscan scratch space
+ for lock-free parallel scanning across multiple lcores
+ - Per-queue-pair extended statistics (enqueued, dequeued, matches)
+ - Support for standard DPDK PCRE rule flags and PMD-private
+ Hyperscan compile flags (singlematch, prefilter, SOM-leftmost,
+ combination, quiet)
+ - Per-rule extended match parameters (minimum/maximum start offset)
+ - Device lifecycle with start, stop, close, and device dump
+
+
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] 25+ messages in thread* [RFC v2 2/7] regex/hs: add device configure and queue pair setup
2026-09-10 8:00 ` [RFC v2 " Prudvi Deti
2026-09-10 8:00 ` [RFC v2 1/7] regex/hs: add driver skeleton and build integration Prudvi Deti
@ 2026-09-10 8:00 ` Prudvi Deti
2026-09-10 8:00 ` [RFC v2 3/7] regex/hs: add rule database update and compilation Prudvi Deti
` (5 subsequent siblings)
7 siblings, 0 replies; 25+ messages in thread
From: Prudvi Deti @ 2026-09-10 8:00 UTC (permalink / raw)
To: dev
Cc: Bruce Richardson, Stephen Hemminger, Ori Kam, Aman Deep Singh,
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 | 182 ++++++++++++++++++++++++++++++++++++
1 file changed, 182 insertions(+)
diff --git a/drivers/regex/hs/hs_regex.c b/drivers/regex/hs/hs_regex.c
index 393283b..888ff62 100644
--- a/drivers/regex/hs/hs_regex.c
+++ b/drivers/regex/hs/hs_regex.c
@@ -60,6 +60,186 @@ 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->dev_cfg_flags != 0) {
+ HS_LOG(ERR, "Unsupported device configuration flags 0x%x",
+ cfg->dev_cfg_flags);
+ return -EINVAL;
+ }
+
+ 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;
+ }
+
+ /* Reconfigure replaces rules, database, and queue resources. */
+ 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) {
+ uint16_t i;
+
+ 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;
+ }
+
+ 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;
+
+ 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;
+
+ /* nb_queue_pairs is only meaningful once qps is allocated. */
+ if (priv->qps == NULL) {
+ HS_LOG(ERR, "qp %u: queue pairs not allocated", qp_id);
+ 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;
+ }
+ if (qp_conf && qp_conf->qp_conf_flags != 0) {
+ HS_LOG(ERR, "QP %u: unsupported configuration flags 0x%x",
+ qp_id, qp_conf->qp_conf_flags);
+ 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) {
+ HS_LOG(WARNING,
+ "QP %u: nb_desc %u exceeds max %u, capping "
+ "(next power of 2 would be %u)",
+ qp_id, orig, HS_REGEX_MAX_NB_DESC, aligned);
+ aligned = HS_REGEX_MAX_NB_DESC;
+ } else {
+ HS_LOG(WARNING, "QP %u: nb_desc %u rounded up to %u (power of 2)",
+ qp_id, orig, aligned);
+ }
+ nb_desc = aligned;
+ }
+
+ if (qp->ops) {
+ rte_free(qp->ops);
+ qp->ops = NULL;
+ }
+
+ /*
+ * Scratch is recreated on re-setup here if a database already
+ * exists; otherwise compile_activate()/import() populate it
+ * for every queue pair once a database becomes available.
+ */
+ 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 +262,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] 25+ messages in thread* [RFC v2 3/7] regex/hs: add rule database update and compilation
2026-09-10 8:00 ` [RFC v2 " Prudvi Deti
2026-09-10 8:00 ` [RFC v2 1/7] regex/hs: add driver skeleton and build integration Prudvi Deti
2026-09-10 8:00 ` [RFC v2 2/7] regex/hs: add device configure and queue pair setup Prudvi Deti
@ 2026-09-10 8:00 ` Prudvi Deti
2026-09-10 8:00 ` [RFC v2 4/7] regex/hs: add enqueue and dequeue burst paths Prudvi Deti
` (4 subsequent siblings)
7 siblings, 0 replies; 25+ messages in thread
From: Prudvi Deti @ 2026-09-10 8:00 UTC (permalink / raw)
To: dev
Cc: Bruce Richardson, Stephen Hemminger, Ori Kam, Aman Deep Singh,
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>
---
doc/guides/regexdevs/hs.rst | 18 ++
drivers/regex/hs/hs_regex.c | 476 +++++++++++++++++++++++++++++++++++-
drivers/regex/hs/hs_regex.h | 15 ++
3 files changed, 508 insertions(+), 1 deletion(-)
diff --git a/doc/guides/regexdevs/hs.rst b/doc/guides/regexdevs/hs.rst
index dc22514..99a0e82 100644
--- a/doc/guides/regexdevs/hs.rst
+++ b/doc/guides/regexdevs/hs.rst
@@ -155,6 +155,24 @@ 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()``.
+Serialized databases are not portable across CPU platforms or
+Hyperscan library versions: importing a database built for a
+different CPU type or a different Hyperscan version fails with
+``HS_DB_PLATFORM_ERROR`` or ``HS_DB_VERSION_ERROR`` respectively. Only
+import databases exported (via ``rule_db_export()``) from a matching
+CPU platform and Hyperscan version.
+
+``rule_db_export()`` treats its output as an opaque byte buffer with
+no alignment requirement: Hyperscan's serialized format is copied via
+``memcpy()`` and is not accessed through any aligned type.
+
+``rule_db_update()`` processes rules in order and commits each one
+(add or remove) as it succeeds. On failure, it returns the index of
+the first failed rule; rules before that index are already
+committed. Applications must not resubmit the original full array on
+partial failure — only correct the failed rule and resubmit it along
+with any remaining rules from the returned index onward.
+
Statistics
~~~~~~~~~~
diff --git a/drivers/regex/hs/hs_regex.c b/drivers/regex/hs/hs_regex.c
index 888ff62..9462d49 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;
@@ -93,7 +98,6 @@ hs_regex_configure(struct rte_regexdev *dev,
/* Reconfigure replaces rules, database, and queue resources. */
if (priv->rules) {
uint32_t i;
-
for (i = 0; i < priv->nb_rules; i++)
rte_free(priv->rules[i].pattern);
rte_free(priv->rules);
@@ -101,6 +105,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;
@@ -139,6 +147,22 @@ 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->max_matches = 0;
+ priv->nb_groups = 0;
+ priv->dev_state = HS_REGEX_DEV_CREATED;
+ return ret;
+ }
+ }
+
return 0;
}
@@ -240,6 +264,452 @@ hs_regex_qp_setup(struct rte_regexdev *dev, uint16_t qp_id,
return 0;
}
+/*
+ * Rule Database Update
+ * On failure, returns the index of the first failed rule; rules
+ * before that index are already committed (not rolled back).
+ */
+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;
+ 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;
+ uint64_t flag_bits;
+ 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) {
+ char hash_name[RTE_HASH_NAMESIZE];
+ struct rte_hash_parameters hp = {
+ .entries = HS_REGEX_MAX_RULES,
+ .key_len = sizeof(uint32_t),
+ .socket_id = SOCKET_ID_ANY,
+ };
+
+ snprintf(hash_name, sizeof(hash_name), "hs_rule_ids_%u",
+ dev->data->dev_id);
+ hp.name = hash_name;
+ 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 &&
+ rules[i].op != RTE_REGEX_RULE_OP_REMOVE) {
+ HS_LOG(ERR, "Rule %u: unsupported operation %u",
+ rules[i].rule_id, rules[i].op);
+ rte_errno = EINVAL;
+ return 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) {
+ int hash_ret;
+ uint32_t idx;
+
+ if (rules[i].rule_id > 0xFFFFF) {
+ HS_LOG(WARNING,
+ "Rule ID %u exceeds 20-bit match result width; "
+ "reported ID will be truncated",
+ rules[i].rule_id);
+ }
+
+ /* 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);
+ 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;
+
+ hash_ret = rte_hash_add_key(priv->rule_id_hash,
+ &rules[i].rule_id);
+ if (hash_ret < 0) {
+ HS_LOG(ERR, "Rule %u: failed to add rule_id to hash: %d",
+ rules[i].rule_id, hash_ret);
+ rte_free(priv->rules[idx].pattern);
+ memset(&priv->rules[idx], 0,
+ sizeof(priv->rules[idx]));
+ rte_errno = -hash_ret;
+ return i;
+ }
+
+ priv->nb_rules++;
+
+ } else if (rules[i].op == RTE_REGEX_RULE_OP_REMOVE) {
+ int hash_ret;
+ uint32_t j;
+
+ for (j = 0; j < priv->nb_rules; j++) {
+ if (priv->rules[j].rule_id == rules[i].rule_id)
+ break;
+ }
+ if (j == priv->nb_rules) {
+ HS_LOG(ERR, "Rule %u: rule_id not found",
+ rules[i].rule_id);
+ rte_errno = ENOENT;
+ return i;
+ }
+
+ hash_ret = rte_hash_del_key(priv->rule_id_hash,
+ &rules[i].rule_id);
+ if (hash_ret < 0) {
+ HS_LOG(ERR, "Rule %u: failed to remove rule_id from hash: %d",
+ rules[i].rule_id, hash_ret);
+ rte_errno = -hash_ret;
+ return i;
+ }
+
+ 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--;
+ }
+ }
+
+ 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;
+ priv->db_compiled = 0;
+ }
+
+ 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 (rule_db_len > HS_REGEX_MAX_RULE_DB_LEN) {
+ HS_LOG(ERR, "rule_db_len %u exceeds max %u",
+ rule_db_len, HS_REGEX_MAX_RULE_DB_LEN);
+ 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;
+ }
+ priv->db_compiled = 0;
+
+ 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
@@ -264,6 +734,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..e48ac86 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"
@@ -15,6 +16,16 @@
#define HS_REGEX_MAX_RULES 1000000
#define HS_REGEX_DEFAULT_NB_DESC 1024
#define HS_REGEX_MAX_NB_DESC 32768
+/* Sanity cap on imported serialized database size (defense in depth;
+ * Hyperscan allocates memory proportional to this size).
+ */
+#define HS_REGEX_MAX_RULE_DB_LEN (512U * 1024 * 1024)
+
+/* 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 {
@@ -30,6 +41,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 +62,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] 25+ messages in thread* [RFC v2 4/7] regex/hs: add enqueue and dequeue burst paths
2026-09-10 8:00 ` [RFC v2 " Prudvi Deti
` (2 preceding siblings ...)
2026-09-10 8:00 ` [RFC v2 3/7] regex/hs: add rule database update and compilation Prudvi Deti
@ 2026-09-10 8:00 ` Prudvi Deti
2026-09-10 8:00 ` [RFC v2 5/7] regex/hs: add per-queue-pair extended statistics Prudvi Deti
` (3 subsequent siblings)
7 siblings, 0 replies; 25+ messages in thread
From: Prudvi Deti @ 2026-09-10 8:00 UTC (permalink / raw)
To: dev
Cc: Bruce Richardson, Stephen Hemminger, Ori Kam, Aman Deep Singh,
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 | 250 ++++++++++++++++++++++++++++++++++--
1 file changed, 236 insertions(+), 14 deletions(-)
diff --git a/drivers/regex/hs/hs_regex.c b/drivers/regex/hs/hs_regex.c
index 9462d49..f17bae5 100644
--- a/drivers/regex/hs/hs_regex.c
+++ b/drivers/regex/hs/hs_regex.c
@@ -39,17 +39,64 @@ 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;
+ uint8_t stop_on_match;
+ 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++;
+ if (op->nb_actual_matches < UINT16_MAX)
+ op->nb_actual_matches++;
+ else
+ op->rsp_flags |= RTE_REGEX_OPS_RSP_MAX_MATCH_F;
+
+ 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 ctx->stop_on_match ? 1 : 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)
{
+ if (info == NULL)
+ return -EINVAL;
+
info->driver_name = HS_REGEX_DRIVER_NAME;
- info->dev = NULL;
+ info->dev = dev->device;
info->max_matches = UINT16_MAX;
info->max_queue_pairs = HS_REGEX_MAX_QUEUE_PAIRS;
info->max_payload_size = UINT16_MAX;
@@ -79,6 +126,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;
@@ -146,6 +194,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) {
@@ -153,11 +202,13 @@ 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;
priv->max_matches = 0;
priv->nb_groups = 0;
+ /* Revert state since configure failed */
priv->dev_state = HS_REGEX_DEV_CREATED;
return ret;
}
@@ -204,6 +255,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);
@@ -221,6 +276,7 @@ hs_regex_qp_setup(struct rte_regexdev *dev, uint16_t qp_id,
nb_desc = aligned;
}
+ /* Re-setup replaces previous ring allocation. */
if (qp->ops) {
rte_free(qp->ops);
qp->ops = NULL;
@@ -295,6 +351,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) {
char hash_name[RTE_HASH_NAMESIZE];
struct rte_hash_parameters hp = {
@@ -352,6 +409,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) {
@@ -531,6 +589,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;
@@ -566,6 +625,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];
@@ -578,6 +638,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);
@@ -596,6 +657,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)
@@ -627,18 +692,21 @@ 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;
}
priv->db_compiled = 0;
+ /* 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];
@@ -652,6 +720,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);
@@ -670,6 +739,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)
{
@@ -697,6 +770,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);
@@ -706,30 +780,178 @@ 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.stop_on_match = !!(op->req_flags &
+ RTE_REGEX_OPS_REQ_STOP_ON_MATCH_F);
+ 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 - 1);
+ 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 - 1);
+ 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] 25+ messages in thread* [RFC v2 5/7] regex/hs: add per-queue-pair extended statistics
2026-09-10 8:00 ` [RFC v2 " Prudvi Deti
` (3 preceding siblings ...)
2026-09-10 8:00 ` [RFC v2 4/7] regex/hs: add enqueue and dequeue burst paths Prudvi Deti
@ 2026-09-10 8:00 ` Prudvi Deti
2026-09-10 8:00 ` [RFC v2 6/7] regex/hs: add start stop close and device dump Prudvi Deti
` (2 subsequent siblings)
7 siblings, 0 replies; 25+ messages in thread
From: Prudvi Deti @ 2026-09-10 8:00 UTC (permalink / raw)
To: dev
Cc: Bruce Richardson, Stephen Hemminger, Ori Kam, Aman Deep Singh,
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 99a0e82..c99cf11 100644
--- a/doc/guides/regexdevs/hs.rst
+++ b/doc/guides/regexdevs/hs.rst
@@ -23,7 +23,16 @@ Features
- Although Hyperscan supports 1,000,000 patterns, the current DPDK public API
limits match-result rule IDs to 20 bits. Rule IDs above ``0xFFFFF`` cannot
be represented without truncation.
+- 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 f17bae5..580a837 100644
--- a/drivers/regex/hs/hs_regex.c
+++ b/drivers/regex/hs/hs_regex.c
@@ -951,6 +951,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,
@@ -960,6 +1115,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] 25+ messages in thread* [RFC v2 6/7] regex/hs: add start stop close and device dump
2026-09-10 8:00 ` [RFC v2 " Prudvi Deti
` (4 preceding siblings ...)
2026-09-10 8:00 ` [RFC v2 5/7] regex/hs: add per-queue-pair extended statistics Prudvi Deti
@ 2026-09-10 8:00 ` Prudvi Deti
2026-09-10 8:00 ` [RFC v2 7/7] regex/hs: add Hyperscan compile flag support Prudvi Deti
2026-09-16 12:26 ` [RFC v3 0/7] regex/hs: introduce Hyperscan regex PMD Prudvi Deti
7 siblings, 0 replies; 25+ messages in thread
From: Prudvi Deti @ 2026-09-10 8:00 UTC (permalink / raw)
To: dev
Cc: Bruce Richardson, Stephen Hemminger, Ori Kam, Aman Deep Singh,
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>
---
doc/guides/regexdevs/hs.rst | 5 +
drivers/regex/hs/hs_regex.c | 193 ++++++++++++++++++++++++++++++++++++
drivers/regex/hs/hs_regex.h | 2 +
3 files changed, 200 insertions(+)
diff --git a/doc/guides/regexdevs/hs.rst b/doc/guides/regexdevs/hs.rst
index c99cf11..b638ecc 100644
--- a/doc/guides/regexdevs/hs.rst
+++ b/doc/guides/regexdevs/hs.rst
@@ -205,6 +205,11 @@ Limitations
- Multi-process mode is not supported.
- Each queue pair must be used by exactly one lcore
(single-producer/single-consumer model).
+- Control-plane calls (``configure``, ``queue_pair_setup``,
+ ``start``, ``stop``, ``close``) must not be called concurrently
+ with ``enqueue_burst``/``dequeue_burst`` on any queue pair, or with
+ each other. The application must quiesce the datapath before
+ invoking any control-plane function.
Debugging Options
-----------------
diff --git a/drivers/regex/hs/hs_regex.c b/drivers/regex/hs/hs_regex.c
index 580a837..395cf84 100644
--- a/drivers/regex/hs/hs_regex.c
+++ b/drivers/regex/hs/hs_regex.c
@@ -785,6 +785,191 @@ 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];
+
+ if (qp->count > 0)
+ HS_LOG(WARNING,
+ "qp %u: stopping with %u ops still pending "
+ "(not returned to application)",
+ i, qp->count);
+ 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);
+
+ if (priv->qps != NULL) {
+ for (i = 0; i < priv->nb_queue_pairs; i++)
+ fprintf(f, " QP[%u]: enqueued=%" PRIu64
+ " dequeued=%" PRIu64 " matches=%" PRIu64 "\n",
+ i, priv->qps[i].qp_enqueued,
+ priv->qps[i].qp_dequeued,
+ priv->qps[i].qp_matches);
+ }
+
+ for (i = 0; i < priv->nb_rules && i < HS_REGEX_DUMP_MAX_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);
+ if (priv->nb_rules > HS_REGEX_DUMP_MAX_RULES)
+ fprintf(f, " ... and %u more rules omitted\n",
+ priv->nb_rules - HS_REGEX_DUMP_MAX_RULES);
+
+ return 0;
+}
+
/*
* Fast Path
*
@@ -1111,6 +1296,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,
@@ -1119,6 +1309,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 */
@@ -1174,6 +1366,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;
}
diff --git a/drivers/regex/hs/hs_regex.h b/drivers/regex/hs/hs_regex.h
index e48ac86..07e737b 100644
--- a/drivers/regex/hs/hs_regex.h
+++ b/drivers/regex/hs/hs_regex.h
@@ -16,6 +16,8 @@
#define HS_REGEX_MAX_RULES 1000000
#define HS_REGEX_DEFAULT_NB_DESC 1024
#define HS_REGEX_MAX_NB_DESC 32768
+/* Cap on per-rule lines printed by dev_dump(); large rule counts are summarized. */
+#define HS_REGEX_DUMP_MAX_RULES 32
/* Sanity cap on imported serialized database size (defense in depth;
* Hyperscan allocates memory proportional to this size).
*/
--
2.43.0
^ permalink raw reply related [flat|nested] 25+ messages in thread* [RFC v2 7/7] regex/hs: add Hyperscan compile flag support
2026-09-10 8:00 ` [RFC v2 " Prudvi Deti
` (5 preceding siblings ...)
2026-09-10 8:00 ` [RFC v2 6/7] regex/hs: add start stop close and device dump Prudvi Deti
@ 2026-09-10 8:00 ` Prudvi Deti
2026-09-16 12:26 ` [RFC v3 0/7] regex/hs: introduce Hyperscan regex PMD Prudvi Deti
7 siblings, 0 replies; 25+ messages in thread
From: Prudvi Deti @ 2026-09-10 8:00 UTC (permalink / raw)
To: dev
Cc: Bruce Richardson, Stephen Hemminger, Ori Kam, Aman Deep Singh,
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 | 26 ++++++++++++++++++++-----
drivers/regex/hs/hs_regex.c | 38 +++++++++++++++++++++++++++++++++----
drivers/regex/hs/hs_regex.h | 23 ++++++++++++++++++----
3 files changed, 74 insertions(+), 13 deletions(-)
diff --git a/doc/guides/regexdevs/hs.rst b/doc/guides/regexdevs/hs.rst
index b638ecc..b76296a 100644
--- a/doc/guides/regexdevs/hs.rst
+++ b/doc/guides/regexdevs/hs.rst
@@ -182,6 +182,16 @@ committed. Applications must not resubmit the original full array on
partial failure — only correct the failed rule and resubmit it along
with any remaining rules from the returned index onward.
+Calling ``rte_regexdev_start()`` is optional with this PMD:
+``enqueue_burst()`` auto-starts the device if a database has already
+been compiled or imported, so applications (such as
+``dpdk-test-regex``) that go straight from ``configure()``/
+``queue_pair_setup()`` to ``enqueue_burst()`` without an explicit
+``start()`` call still work correctly. Applications that intend to
+call ``start()`` explicitly should still do so before the first
+``enqueue_burst()`` call, since once the device auto-starts, a
+subsequent explicit ``start()`` call will fail with ``-EBUSY``.
+
Statistics
~~~~~~~~~~
@@ -197,12 +207,18 @@ Limitations
Hyperscan PMD and is not supported. DPDK requires ordering matches by rule
ID, start offset, and match length, while Hyperscan without SOM reports only
the match end offset.
-- 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).
- Control-plane calls (``configure``, ``queue_pair_setup``,
diff --git a/drivers/regex/hs/hs_regex.c b/drivers/regex/hs/hs_regex.c
index 395cf84..05607c5 100644
--- a/drivers/regex/hs/hs_regex.c
+++ b/drivers/regex/hs/hs_regex.c
@@ -103,9 +103,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;
@@ -336,7 +338,12 @@ hs_regex_rule_db_update(struct rte_regexdev *dev,
RTE_REGEX_PCRE_RULE_DOTALL_F |
RTE_REGEX_PCRE_RULE_MULTILINE_F |
RTE_REGEX_PCRE_RULE_UCP_F |
- RTE_REGEX_PCRE_RULE_UTF_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;
@@ -586,8 +593,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;
@@ -1003,8 +1024,17 @@ 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, e.g. dpdk-test-regex; see hs.rst).
+ */
+ 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 07e737b..211d876 100644
--- a/drivers/regex/hs/hs_regex.h
+++ b/drivers/regex/hs/hs_regex.h
@@ -22,6 +22,18 @@
* Hyperscan allocates memory proportional to this size).
*/
#define HS_REGEX_MAX_RULE_DB_LEN (512U * 1024 * 1024)
+/* PMD-specific rule flags using bits 32+ to avoid overlap with DPDK flags. */
+
+/** Report at most one match per pattern per scan (maps to HS_FLAG_SINGLEMATCH). */
+#define HS_REGEX_RULE_SINGLEMATCH_F (1ULL << 32)
+/** Treat pattern as a prefilter approximation (maps to HS_FLAG_PREFILTER). */
+#define HS_REGEX_RULE_PREFILTER_F (1ULL << 33)
+/** Report leftmost start of match offset (maps to HS_FLAG_SOM_LEFTMOST). */
+#define HS_REGEX_RULE_SOM_LEFTMOST_F (1ULL << 34)
+/** Enable logical combination expressions (maps to HS_FLAG_COMBINATION). */
+#define HS_REGEX_RULE_COMBINATION_F (1ULL << 35)
+/** Ignore match reporting for this pattern (maps to HS_FLAG_QUIET). */
+#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
@@ -31,10 +43,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 */
@@ -56,6 +68,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;
@@ -77,9 +90,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] 25+ messages in thread* [RFC v3 0/7] regex/hs: introduce Hyperscan regex PMD
2026-09-10 8:00 ` [RFC v2 " Prudvi Deti
` (6 preceding siblings ...)
2026-09-10 8:00 ` [RFC v2 7/7] regex/hs: add Hyperscan compile flag support Prudvi Deti
@ 2026-09-16 12:26 ` Prudvi Deti
2026-09-16 12:26 ` [RFC v3 1/7] regex/hs: add driver skeleton and build integration Prudvi Deti
` (6 more replies)
7 siblings, 7 replies; 25+ messages in thread
From: Prudvi Deti @ 2026-09-16 12:26 UTC (permalink / raw)
To: dev; +Cc: Bruce Richardson, Stephen Hemminger, Ori Kam, Aman Singh,
Nirint Shah
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.
The series introduces:
- a new vdev driver under ``drivers/regex/hs``
- device and queue pair configuration, start/stop/close lifecycle,
and device dump support
- rule database update, compile+activate, import/export of serialized
Hyperscan databases
- enqueue/dequeue burst paths with per-queue-pair scratch usage and
per-queue-pair extended statistics (enqueued/dequeued/matches)
- PMD-private rule flag mappings for common Hyperscan compile options
(singlematch, prefilter, SOM-leftmost, combination, quiet)
Behavior notes relevant for review:
- per-op match counters are bounded by API field width
(``nb_matches``/``nb_actual_matches`` are ``uint16_t``)
- payload size exposed through regexdev is ``max_payload_size = 65535``
bytes, while Hyperscan block mode itself supports larger scan buffers
- queue pairs are intended for single-thread ownership (SPSC model)
with dedicated scratch per queue pair
Documentation, feature matrix integration, meson integration, and
maintainers metadata are included.
Prudvi Deti (7):
regex/hs: add driver skeleton and build integration
regex/hs: add device configure and queue pair setup
regex/hs: add rule database update and compilation
regex/hs: add enqueue and dequeue burst paths
regex/hs: add per-queue-pair extended statistics
regex/hs: add start stop close and device dump
regex/hs: add Hyperscan compile flag support
---
v3: Addressed AI review of the driver skeleton patch:
* Fixed a resource ordering issue in hs_regex_dev_create(): the device
private data is now allocated before rte_regexdev_register(), so a
registration failure cannot leave a registered device with no cleanup
path, and a private-data allocation failure no longer needs to
unregister the device.
v2: Addressed AI reviews with the following changes:
* Fixed a queue-pair resource leak on reconfigure (stale scratch and
operation ring were not freed before reallocation).
* Rejected unsupported device and queue-pair configuration flags
instead of silently ignoring them.
* Fixed a rule-id hash name collision between multiple device
instances by including the device id in the hash name.
* Validated rule operations and fixed rule-id hash/array consistency
on add and remove failures.
* Cleared the compiled-database state on a failed rule database
import so a partially failed import cannot be reported as started.
* Implemented stop-on-match request handling and saturated the
per-op actual-match count instead of wrapping past 65535.
* Fixed device info to report the backing vdev device instead of NULL.
* Moved the pending-operation warning so it fires on both explicit
and implicit stop.
* Documented the 20-bit match-result rule-id limit and the
unsupported high-priority match request mode.
* Capped dev_dump() rule listing with a summary line for large rule
counts instead of printing every rule unconditionally.
* Documented that rule_db_export()'s output buffer has no alignment
requirement.
* Documented building Hyperscan 5.4.2 from source as a prerequisite.
.mailmap | 1 +
MAINTAINERS | 6 +
doc/guides/regexdevs/features/hs.ini | 8 +
doc/guides/regexdevs/hs.rst | 235 ++++
doc/guides/regexdevs/index.rst | 1 +
doc/guides/rel_notes/release_26_11.rst | 19 +
drivers/regex/hs/hs_regex.c | 1447 ++++++++++++++++++++++++
drivers/regex/hs/hs_regex.h | 101 ++
drivers/regex/hs/meson.build | 19 +
drivers/regex/meson.build | 1 +
10 files changed, 1838 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
--
2.43.0
^ permalink raw reply [flat|nested] 25+ messages in thread* [RFC v3 1/7] regex/hs: add driver skeleton and build integration
2026-09-16 12:26 ` [RFC v3 0/7] regex/hs: introduce Hyperscan regex PMD Prudvi Deti
@ 2026-09-16 12:26 ` Prudvi Deti
2026-09-16 12:26 ` [RFC v3 2/7] regex/hs: add device configure and queue pair setup Prudvi Deti
` (5 subsequent siblings)
6 siblings, 0 replies; 25+ messages in thread
From: Prudvi Deti @ 2026-09-16 12:26 UTC (permalink / raw)
To: dev; +Cc: Bruce Richardson, Stephen Hemminger, Ori Kam, Aman Singh,
Nirint Shah
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 | 187 +++++++++++++++++++++++++
doc/guides/regexdevs/index.rst | 1 +
doc/guides/rel_notes/release_26_11.rst | 19 +++
drivers/regex/hs/hs_regex.c | 187 +++++++++++++++++++++++++
drivers/regex/hs/hs_regex.h | 69 +++++++++
drivers/regex/hs/meson.build | 19 +++
drivers/regex/meson.build | 1 +
10 files changed, 498 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..dc22514
--- /dev/null
+++ b/doc/guides/regexdevs/hs.rst
@@ -0,0 +1,187 @@
+.. 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``
+- Although Hyperscan supports 1,000,000 patterns, the current DPDK public API
+ limits match-result rule IDs to 20 bits. Rule IDs above ``0xFFFFF`` cannot
+ be represented without truncation.
+- 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 and Fedora/RHEL, install Intel
+Hyperscan 5.4.2 from source:
+
+#. Install the build dependencies:
+
+ On Ubuntu/Debian:
+
+ .. code-block:: console
+
+ sudo apt install build-essential cmake curl libboost-dev pkg-config python3 ragel
+
+ On Fedora/RHEL:
+
+ .. code-block:: console
+
+ sudo dnf install boost-devel cmake curl gcc gcc-c++ make pkgconf-pkg-config python3 ragel
+
+#. Download the Intel Hyperscan 5.4.2 source archive:
+
+ .. code-block:: console
+
+ curl -LO https://github.com/intel/hyperscan/archive/refs/tags/v5.4.2.tar.gz
+
+#. Extract the archive:
+
+ .. code-block:: console
+
+ tar -xzf v5.4.2.tar.gz
+
+#. Build and install Hyperscan:
+
+ .. code-block:: console
+
+ cd hyperscan-5.4.2
+ cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON
+ cmake --build build --parallel
+ sudo cmake --install build
+ sudo ldconfig
+
+ Verify that version 5.4.2 is discoverable:
+
+ .. code-block:: console
+
+ PKG_CONFIG_PATH=/usr/local/lib64/pkgconfig pkg-config --modversion libhs
+
+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
+-----------
+
+- ``RTE_REGEX_OPS_REQ_MATCH_HIGH_PRIORITY_F`` is not compatible with this
+ Hyperscan PMD and is not supported. DPDK requires ordering matches by rule
+ ID, start offset, and match length, while Hyperscan without SOM reports only
+ the match end offset.
+- 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..617d6ea 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -56,6 +56,25 @@ New Features
=======================================================
+* **Added Hyperscan regex PMD.**
+
+ Added a new software regex PMD (``regex_hs``) based on Intel
+ Hyperscan library. The PMD is a virtual device created with
+ ``--vdev=regex_hs`` and implements the ``rte_regexdev`` API using
+ Hyperscan block mode scanning. Key capabilities include:
+
+ - Runtime pattern compilation via ``hs_compile_ext_multi()`` and
+ serialized database import/export
+ - Up to 64 queue pairs with per-queue-pair Hyperscan scratch space
+ for lock-free parallel scanning across multiple lcores
+ - Per-queue-pair extended statistics (enqueued, dequeued, matches)
+ - Support for standard DPDK PCRE rule flags and PMD-private
+ Hyperscan compile flags (singlematch, prefilter, SOM-leftmost,
+ combination, quiet)
+ - Per-rule extended match parameters (minimum/maximum start offset)
+ - Device lifecycle with start, stop, close, and device dump
+
+
Removed Items
-------------
diff --git a/drivers/regex/hs/hs_regex.c b/drivers/regex/hs/hs_regex.c
new file mode 100644
index 0000000..081e4b3
--- /dev/null
+++ b/drivers/regex/hs/hs_regex.c
@@ -0,0 +1,187 @@
+/* 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);
+
+ priv = rte_zmalloc("hs_regex_priv", sizeof(*priv),
+ RTE_CACHE_LINE_SIZE);
+ if (!priv) {
+ HS_LOG(ERR, "Failed to allocate private data");
+ return -ENOMEM;
+ }
+
+ dev = rte_regexdev_register(name);
+ if (!dev) {
+ HS_LOG(ERR, "Failed to register regex device %s", name);
+ rte_free(priv);
+ return -EINVAL;
+ }
+
+ 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] 25+ messages in thread* [RFC v3 2/7] regex/hs: add device configure and queue pair setup
2026-09-16 12:26 ` [RFC v3 0/7] regex/hs: introduce Hyperscan regex PMD Prudvi Deti
2026-09-16 12:26 ` [RFC v3 1/7] regex/hs: add driver skeleton and build integration Prudvi Deti
@ 2026-09-16 12:26 ` Prudvi Deti
2026-09-16 12:27 ` [RFC v3 3/7] regex/hs: add rule database update and compilation Prudvi Deti
` (4 subsequent siblings)
6 siblings, 0 replies; 25+ messages in thread
From: Prudvi Deti @ 2026-09-16 12:26 UTC (permalink / raw)
To: dev; +Cc: Bruce Richardson, Stephen Hemminger, Ori Kam, Aman Singh,
Nirint Shah
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 | 182 ++++++++++++++++++++++++++++++++++++
1 file changed, 182 insertions(+)
diff --git a/drivers/regex/hs/hs_regex.c b/drivers/regex/hs/hs_regex.c
index 081e4b3..2e0a215 100644
--- a/drivers/regex/hs/hs_regex.c
+++ b/drivers/regex/hs/hs_regex.c
@@ -60,6 +60,186 @@ 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->dev_cfg_flags != 0) {
+ HS_LOG(ERR, "Unsupported device configuration flags 0x%x",
+ cfg->dev_cfg_flags);
+ return -EINVAL;
+ }
+
+ 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;
+ }
+
+ /* Reconfigure replaces rules, database, and queue resources. */
+ 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) {
+ uint16_t i;
+
+ 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;
+ }
+
+ 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;
+
+ 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;
+
+ /* nb_queue_pairs is only meaningful once qps is allocated. */
+ if (priv->qps == NULL) {
+ HS_LOG(ERR, "qp %u: queue pairs not allocated", qp_id);
+ 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;
+ }
+ if (qp_conf && qp_conf->qp_conf_flags != 0) {
+ HS_LOG(ERR, "QP %u: unsupported configuration flags 0x%x",
+ qp_id, qp_conf->qp_conf_flags);
+ 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) {
+ HS_LOG(WARNING,
+ "QP %u: nb_desc %u exceeds max %u, capping "
+ "(next power of 2 would be %u)",
+ qp_id, orig, HS_REGEX_MAX_NB_DESC, aligned);
+ aligned = HS_REGEX_MAX_NB_DESC;
+ } else {
+ HS_LOG(WARNING, "QP %u: nb_desc %u rounded up to %u (power of 2)",
+ qp_id, orig, aligned);
+ }
+ nb_desc = aligned;
+ }
+
+ if (qp->ops) {
+ rte_free(qp->ops);
+ qp->ops = NULL;
+ }
+
+ /*
+ * Scratch is recreated on re-setup here if a database already
+ * exists; otherwise compile_activate()/import() populate it
+ * for every queue pair once a database becomes available.
+ */
+ 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 +262,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] 25+ messages in thread* [RFC v3 3/7] regex/hs: add rule database update and compilation
2026-09-16 12:26 ` [RFC v3 0/7] regex/hs: introduce Hyperscan regex PMD Prudvi Deti
2026-09-16 12:26 ` [RFC v3 1/7] regex/hs: add driver skeleton and build integration Prudvi Deti
2026-09-16 12:26 ` [RFC v3 2/7] regex/hs: add device configure and queue pair setup Prudvi Deti
@ 2026-09-16 12:27 ` Prudvi Deti
2026-09-16 12:27 ` [RFC v3 4/7] regex/hs: add enqueue and dequeue burst paths Prudvi Deti
` (3 subsequent siblings)
6 siblings, 0 replies; 25+ messages in thread
From: Prudvi Deti @ 2026-09-16 12:27 UTC (permalink / raw)
To: dev; +Cc: Bruce Richardson, Stephen Hemminger, Ori Kam, Aman Singh,
Nirint Shah
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>
---
doc/guides/regexdevs/hs.rst | 18 ++
drivers/regex/hs/hs_regex.c | 476 +++++++++++++++++++++++++++++++++++-
drivers/regex/hs/hs_regex.h | 15 ++
3 files changed, 508 insertions(+), 1 deletion(-)
diff --git a/doc/guides/regexdevs/hs.rst b/doc/guides/regexdevs/hs.rst
index dc22514..99a0e82 100644
--- a/doc/guides/regexdevs/hs.rst
+++ b/doc/guides/regexdevs/hs.rst
@@ -155,6 +155,24 @@ 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()``.
+Serialized databases are not portable across CPU platforms or
+Hyperscan library versions: importing a database built for a
+different CPU type or a different Hyperscan version fails with
+``HS_DB_PLATFORM_ERROR`` or ``HS_DB_VERSION_ERROR`` respectively. Only
+import databases exported (via ``rule_db_export()``) from a matching
+CPU platform and Hyperscan version.
+
+``rule_db_export()`` treats its output as an opaque byte buffer with
+no alignment requirement: Hyperscan's serialized format is copied via
+``memcpy()`` and is not accessed through any aligned type.
+
+``rule_db_update()`` processes rules in order and commits each one
+(add or remove) as it succeeds. On failure, it returns the index of
+the first failed rule; rules before that index are already
+committed. Applications must not resubmit the original full array on
+partial failure — only correct the failed rule and resubmit it along
+with any remaining rules from the returned index onward.
+
Statistics
~~~~~~~~~~
diff --git a/drivers/regex/hs/hs_regex.c b/drivers/regex/hs/hs_regex.c
index 2e0a215..db25e50 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;
@@ -93,7 +98,6 @@ hs_regex_configure(struct rte_regexdev *dev,
/* Reconfigure replaces rules, database, and queue resources. */
if (priv->rules) {
uint32_t i;
-
for (i = 0; i < priv->nb_rules; i++)
rte_free(priv->rules[i].pattern);
rte_free(priv->rules);
@@ -101,6 +105,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;
@@ -139,6 +147,22 @@ 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->max_matches = 0;
+ priv->nb_groups = 0;
+ priv->dev_state = HS_REGEX_DEV_CREATED;
+ return ret;
+ }
+ }
+
return 0;
}
@@ -240,6 +264,452 @@ hs_regex_qp_setup(struct rte_regexdev *dev, uint16_t qp_id,
return 0;
}
+/*
+ * Rule Database Update
+ * On failure, returns the index of the first failed rule; rules
+ * before that index are already committed (not rolled back).
+ */
+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;
+ 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;
+ uint64_t flag_bits;
+ 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) {
+ char hash_name[RTE_HASH_NAMESIZE];
+ struct rte_hash_parameters hp = {
+ .entries = HS_REGEX_MAX_RULES,
+ .key_len = sizeof(uint32_t),
+ .socket_id = SOCKET_ID_ANY,
+ };
+
+ snprintf(hash_name, sizeof(hash_name), "hs_rule_ids_%u",
+ dev->data->dev_id);
+ hp.name = hash_name;
+ 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 &&
+ rules[i].op != RTE_REGEX_RULE_OP_REMOVE) {
+ HS_LOG(ERR, "Rule %u: unsupported operation %u",
+ rules[i].rule_id, rules[i].op);
+ rte_errno = EINVAL;
+ return 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) {
+ int hash_ret;
+ uint32_t idx;
+
+ if (rules[i].rule_id > 0xFFFFF) {
+ HS_LOG(WARNING,
+ "Rule ID %u exceeds 20-bit match result width; "
+ "reported ID will be truncated",
+ rules[i].rule_id);
+ }
+
+ /* 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);
+ 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;
+
+ hash_ret = rte_hash_add_key(priv->rule_id_hash,
+ &rules[i].rule_id);
+ if (hash_ret < 0) {
+ HS_LOG(ERR, "Rule %u: failed to add rule_id to hash: %d",
+ rules[i].rule_id, hash_ret);
+ rte_free(priv->rules[idx].pattern);
+ memset(&priv->rules[idx], 0,
+ sizeof(priv->rules[idx]));
+ rte_errno = -hash_ret;
+ return i;
+ }
+
+ priv->nb_rules++;
+
+ } else if (rules[i].op == RTE_REGEX_RULE_OP_REMOVE) {
+ int hash_ret;
+ uint32_t j;
+
+ for (j = 0; j < priv->nb_rules; j++) {
+ if (priv->rules[j].rule_id == rules[i].rule_id)
+ break;
+ }
+ if (j == priv->nb_rules) {
+ HS_LOG(ERR, "Rule %u: rule_id not found",
+ rules[i].rule_id);
+ rte_errno = ENOENT;
+ return i;
+ }
+
+ hash_ret = rte_hash_del_key(priv->rule_id_hash,
+ &rules[i].rule_id);
+ if (hash_ret < 0) {
+ HS_LOG(ERR, "Rule %u: failed to remove rule_id from hash: %d",
+ rules[i].rule_id, hash_ret);
+ rte_errno = -hash_ret;
+ return i;
+ }
+
+ 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--;
+ }
+ }
+
+ 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;
+ priv->db_compiled = 0;
+ }
+
+ 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 (rule_db_len > HS_REGEX_MAX_RULE_DB_LEN) {
+ HS_LOG(ERR, "rule_db_len %u exceeds max %u",
+ rule_db_len, HS_REGEX_MAX_RULE_DB_LEN);
+ 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;
+ }
+ priv->db_compiled = 0;
+
+ 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
@@ -264,6 +734,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..e48ac86 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"
@@ -15,6 +16,16 @@
#define HS_REGEX_MAX_RULES 1000000
#define HS_REGEX_DEFAULT_NB_DESC 1024
#define HS_REGEX_MAX_NB_DESC 32768
+/* Sanity cap on imported serialized database size (defense in depth;
+ * Hyperscan allocates memory proportional to this size).
+ */
+#define HS_REGEX_MAX_RULE_DB_LEN (512U * 1024 * 1024)
+
+/* 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 {
@@ -30,6 +41,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 +62,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] 25+ messages in thread* [RFC v3 4/7] regex/hs: add enqueue and dequeue burst paths
2026-09-16 12:26 ` [RFC v3 0/7] regex/hs: introduce Hyperscan regex PMD Prudvi Deti
` (2 preceding siblings ...)
2026-09-16 12:27 ` [RFC v3 3/7] regex/hs: add rule database update and compilation Prudvi Deti
@ 2026-09-16 12:27 ` Prudvi Deti
2026-09-16 12:27 ` [RFC v3 5/7] regex/hs: add per-queue-pair extended statistics Prudvi Deti
` (2 subsequent siblings)
6 siblings, 0 replies; 25+ messages in thread
From: Prudvi Deti @ 2026-09-16 12:27 UTC (permalink / raw)
To: dev; +Cc: Bruce Richardson, Stephen Hemminger, Ori Kam, Aman Singh,
Nirint Shah
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 | 250 ++++++++++++++++++++++++++++++++++--
1 file changed, 236 insertions(+), 14 deletions(-)
diff --git a/drivers/regex/hs/hs_regex.c b/drivers/regex/hs/hs_regex.c
index db25e50..657b605 100644
--- a/drivers/regex/hs/hs_regex.c
+++ b/drivers/regex/hs/hs_regex.c
@@ -39,17 +39,64 @@ 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;
+ uint8_t stop_on_match;
+ 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++;
+ if (op->nb_actual_matches < UINT16_MAX)
+ op->nb_actual_matches++;
+ else
+ op->rsp_flags |= RTE_REGEX_OPS_RSP_MAX_MATCH_F;
+
+ 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 ctx->stop_on_match ? 1 : 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)
{
+ if (info == NULL)
+ return -EINVAL;
+
info->driver_name = HS_REGEX_DRIVER_NAME;
- info->dev = NULL;
+ info->dev = dev->device;
info->max_matches = UINT16_MAX;
info->max_queue_pairs = HS_REGEX_MAX_QUEUE_PAIRS;
info->max_payload_size = UINT16_MAX;
@@ -79,6 +126,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;
@@ -146,6 +194,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) {
@@ -153,11 +202,13 @@ 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;
priv->max_matches = 0;
priv->nb_groups = 0;
+ /* Revert state since configure failed */
priv->dev_state = HS_REGEX_DEV_CREATED;
return ret;
}
@@ -204,6 +255,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);
@@ -221,6 +276,7 @@ hs_regex_qp_setup(struct rte_regexdev *dev, uint16_t qp_id,
nb_desc = aligned;
}
+ /* Re-setup replaces previous ring allocation. */
if (qp->ops) {
rte_free(qp->ops);
qp->ops = NULL;
@@ -295,6 +351,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) {
char hash_name[RTE_HASH_NAMESIZE];
struct rte_hash_parameters hp = {
@@ -352,6 +409,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) {
@@ -531,6 +589,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;
@@ -566,6 +625,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];
@@ -578,6 +638,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);
@@ -596,6 +657,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)
@@ -627,18 +692,21 @@ 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;
}
priv->db_compiled = 0;
+ /* 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];
@@ -652,6 +720,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);
@@ -670,6 +739,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)
{
@@ -697,6 +770,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);
@@ -706,30 +780,178 @@ 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.stop_on_match = !!(op->req_flags &
+ RTE_REGEX_OPS_REQ_STOP_ON_MATCH_F);
+ 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 - 1);
+ 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 - 1);
+ 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] 25+ messages in thread* [RFC v3 5/7] regex/hs: add per-queue-pair extended statistics
2026-09-16 12:26 ` [RFC v3 0/7] regex/hs: introduce Hyperscan regex PMD Prudvi Deti
` (3 preceding siblings ...)
2026-09-16 12:27 ` [RFC v3 4/7] regex/hs: add enqueue and dequeue burst paths Prudvi Deti
@ 2026-09-16 12:27 ` Prudvi Deti
2026-09-16 12:27 ` [RFC v3 6/7] regex/hs: add start stop close and device dump Prudvi Deti
2026-09-16 12:27 ` [RFC v3 7/7] regex/hs: add Hyperscan compile flag support Prudvi Deti
6 siblings, 0 replies; 25+ messages in thread
From: Prudvi Deti @ 2026-09-16 12:27 UTC (permalink / raw)
To: dev; +Cc: Bruce Richardson, Stephen Hemminger, Ori Kam, Aman Singh,
Nirint Shah
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 99a0e82..c99cf11 100644
--- a/doc/guides/regexdevs/hs.rst
+++ b/doc/guides/regexdevs/hs.rst
@@ -23,7 +23,16 @@ Features
- Although Hyperscan supports 1,000,000 patterns, the current DPDK public API
limits match-result rule IDs to 20 bits. Rule IDs above ``0xFFFFF`` cannot
be represented without truncation.
+- 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 657b605..2a86d8b 100644
--- a/drivers/regex/hs/hs_regex.c
+++ b/drivers/regex/hs/hs_regex.c
@@ -951,6 +951,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,
@@ -960,6 +1115,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] 25+ messages in thread* [RFC v3 6/7] regex/hs: add start stop close and device dump
2026-09-16 12:26 ` [RFC v3 0/7] regex/hs: introduce Hyperscan regex PMD Prudvi Deti
` (4 preceding siblings ...)
2026-09-16 12:27 ` [RFC v3 5/7] regex/hs: add per-queue-pair extended statistics Prudvi Deti
@ 2026-09-16 12:27 ` Prudvi Deti
2026-09-16 12:27 ` [RFC v3 7/7] regex/hs: add Hyperscan compile flag support Prudvi Deti
6 siblings, 0 replies; 25+ messages in thread
From: Prudvi Deti @ 2026-09-16 12:27 UTC (permalink / raw)
To: dev; +Cc: Bruce Richardson, Stephen Hemminger, Ori Kam, Aman Singh,
Nirint Shah
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>
---
doc/guides/regexdevs/hs.rst | 5 +
drivers/regex/hs/hs_regex.c | 193 ++++++++++++++++++++++++++++++++++++
drivers/regex/hs/hs_regex.h | 2 +
3 files changed, 200 insertions(+)
diff --git a/doc/guides/regexdevs/hs.rst b/doc/guides/regexdevs/hs.rst
index c99cf11..b638ecc 100644
--- a/doc/guides/regexdevs/hs.rst
+++ b/doc/guides/regexdevs/hs.rst
@@ -205,6 +205,11 @@ Limitations
- Multi-process mode is not supported.
- Each queue pair must be used by exactly one lcore
(single-producer/single-consumer model).
+- Control-plane calls (``configure``, ``queue_pair_setup``,
+ ``start``, ``stop``, ``close``) must not be called concurrently
+ with ``enqueue_burst``/``dequeue_burst`` on any queue pair, or with
+ each other. The application must quiesce the datapath before
+ invoking any control-plane function.
Debugging Options
-----------------
diff --git a/drivers/regex/hs/hs_regex.c b/drivers/regex/hs/hs_regex.c
index 2a86d8b..d1cea38 100644
--- a/drivers/regex/hs/hs_regex.c
+++ b/drivers/regex/hs/hs_regex.c
@@ -785,6 +785,191 @@ 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];
+
+ if (qp->count > 0)
+ HS_LOG(WARNING,
+ "qp %u: stopping with %u ops still pending "
+ "(not returned to application)",
+ i, qp->count);
+ 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);
+
+ if (priv->qps != NULL) {
+ for (i = 0; i < priv->nb_queue_pairs; i++)
+ fprintf(f, " QP[%u]: enqueued=%" PRIu64
+ " dequeued=%" PRIu64 " matches=%" PRIu64 "\n",
+ i, priv->qps[i].qp_enqueued,
+ priv->qps[i].qp_dequeued,
+ priv->qps[i].qp_matches);
+ }
+
+ for (i = 0; i < priv->nb_rules && i < HS_REGEX_DUMP_MAX_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);
+ if (priv->nb_rules > HS_REGEX_DUMP_MAX_RULES)
+ fprintf(f, " ... and %u more rules omitted\n",
+ priv->nb_rules - HS_REGEX_DUMP_MAX_RULES);
+
+ return 0;
+}
+
/*
* Fast Path
*
@@ -1111,6 +1296,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,
@@ -1119,6 +1309,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 */
@@ -1175,6 +1367,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;
}
diff --git a/drivers/regex/hs/hs_regex.h b/drivers/regex/hs/hs_regex.h
index e48ac86..07e737b 100644
--- a/drivers/regex/hs/hs_regex.h
+++ b/drivers/regex/hs/hs_regex.h
@@ -16,6 +16,8 @@
#define HS_REGEX_MAX_RULES 1000000
#define HS_REGEX_DEFAULT_NB_DESC 1024
#define HS_REGEX_MAX_NB_DESC 32768
+/* Cap on per-rule lines printed by dev_dump(); large rule counts are summarized. */
+#define HS_REGEX_DUMP_MAX_RULES 32
/* Sanity cap on imported serialized database size (defense in depth;
* Hyperscan allocates memory proportional to this size).
*/
--
2.43.0
^ permalink raw reply related [flat|nested] 25+ messages in thread* [RFC v3 7/7] regex/hs: add Hyperscan compile flag support
2026-09-16 12:26 ` [RFC v3 0/7] regex/hs: introduce Hyperscan regex PMD Prudvi Deti
` (5 preceding siblings ...)
2026-09-16 12:27 ` [RFC v3 6/7] regex/hs: add start stop close and device dump Prudvi Deti
@ 2026-09-16 12:27 ` Prudvi Deti
6 siblings, 0 replies; 25+ messages in thread
From: Prudvi Deti @ 2026-09-16 12:27 UTC (permalink / raw)
To: dev; +Cc: Bruce Richardson, Stephen Hemminger, Ori Kam, Aman Singh,
Nirint Shah
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 | 26 ++++++++++++++++++++-----
drivers/regex/hs/hs_regex.c | 38 +++++++++++++++++++++++++++++++++----
drivers/regex/hs/hs_regex.h | 23 ++++++++++++++++++----
3 files changed, 74 insertions(+), 13 deletions(-)
diff --git a/doc/guides/regexdevs/hs.rst b/doc/guides/regexdevs/hs.rst
index b638ecc..b76296a 100644
--- a/doc/guides/regexdevs/hs.rst
+++ b/doc/guides/regexdevs/hs.rst
@@ -182,6 +182,16 @@ committed. Applications must not resubmit the original full array on
partial failure — only correct the failed rule and resubmit it along
with any remaining rules from the returned index onward.
+Calling ``rte_regexdev_start()`` is optional with this PMD:
+``enqueue_burst()`` auto-starts the device if a database has already
+been compiled or imported, so applications (such as
+``dpdk-test-regex``) that go straight from ``configure()``/
+``queue_pair_setup()`` to ``enqueue_burst()`` without an explicit
+``start()`` call still work correctly. Applications that intend to
+call ``start()`` explicitly should still do so before the first
+``enqueue_burst()`` call, since once the device auto-starts, a
+subsequent explicit ``start()`` call will fail with ``-EBUSY``.
+
Statistics
~~~~~~~~~~
@@ -197,12 +207,18 @@ Limitations
Hyperscan PMD and is not supported. DPDK requires ordering matches by rule
ID, start offset, and match length, while Hyperscan without SOM reports only
the match end offset.
-- 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).
- Control-plane calls (``configure``, ``queue_pair_setup``,
diff --git a/drivers/regex/hs/hs_regex.c b/drivers/regex/hs/hs_regex.c
index d1cea38..801f857 100644
--- a/drivers/regex/hs/hs_regex.c
+++ b/drivers/regex/hs/hs_regex.c
@@ -103,9 +103,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;
@@ -336,7 +338,12 @@ hs_regex_rule_db_update(struct rte_regexdev *dev,
RTE_REGEX_PCRE_RULE_DOTALL_F |
RTE_REGEX_PCRE_RULE_MULTILINE_F |
RTE_REGEX_PCRE_RULE_UCP_F |
- RTE_REGEX_PCRE_RULE_UTF_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;
@@ -586,8 +593,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;
@@ -1003,8 +1024,17 @@ 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, e.g. dpdk-test-regex; see hs.rst).
+ */
+ 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 07e737b..211d876 100644
--- a/drivers/regex/hs/hs_regex.h
+++ b/drivers/regex/hs/hs_regex.h
@@ -22,6 +22,18 @@
* Hyperscan allocates memory proportional to this size).
*/
#define HS_REGEX_MAX_RULE_DB_LEN (512U * 1024 * 1024)
+/* PMD-specific rule flags using bits 32+ to avoid overlap with DPDK flags. */
+
+/** Report at most one match per pattern per scan (maps to HS_FLAG_SINGLEMATCH). */
+#define HS_REGEX_RULE_SINGLEMATCH_F (1ULL << 32)
+/** Treat pattern as a prefilter approximation (maps to HS_FLAG_PREFILTER). */
+#define HS_REGEX_RULE_PREFILTER_F (1ULL << 33)
+/** Report leftmost start of match offset (maps to HS_FLAG_SOM_LEFTMOST). */
+#define HS_REGEX_RULE_SOM_LEFTMOST_F (1ULL << 34)
+/** Enable logical combination expressions (maps to HS_FLAG_COMBINATION). */
+#define HS_REGEX_RULE_COMBINATION_F (1ULL << 35)
+/** Ignore match reporting for this pattern (maps to HS_FLAG_QUIET). */
+#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
@@ -31,10 +43,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 */
@@ -56,6 +68,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;
@@ -77,9 +90,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] 25+ messages in thread