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