+/* Must be RCU postponed. */
+void cls_match_free_cb(struct cls_match *);
+
+static inline void
+cls_match_set_remove_version(struct cls_match *rule, cls_version_t version)
+{
+ atomic_store_relaxed(&rule->remove_version, version);
+}
+
+static inline bool
+cls_match_visible_in_version(const struct cls_match *rule,
+ cls_version_t version)
+{
+ cls_version_t remove_version;
+
+ /* C11 does not want to access an atomic via a const object pointer. */
+ atomic_read_relaxed(&CONST_CAST(struct cls_match *, rule)->remove_version,
+ &remove_version);
+
+ return rule->add_version <= version && version < remove_version;
+}
+
+static inline bool
+cls_match_is_eventually_invisible(const struct cls_match *rule)
+{
+ cls_version_t remove_version;
+
+ /* C11 does not want to access an atomic via a const object pointer. */
+ atomic_read_relaxed(&CONST_CAST(struct cls_match *, rule)->remove_version,
+ &remove_version);
+
+ return remove_version <= CLS_MAX_VERSION;
+}
+
+\f
+/* cls_match 'next' */
+
+static inline const struct cls_match *
+cls_match_next(const struct cls_match *rule)
+{
+ return ovsrcu_get(struct cls_match *, &rule->next);
+}
+
+static inline struct cls_match *
+cls_match_next_protected(const struct cls_match *rule)
+{
+ return ovsrcu_get_protected(struct cls_match *, &rule->next);
+}
+
+/* Puts 'rule' in the position between 'prev' and 'next'. If 'prev' == NULL,
+ * then the 'rule' is the new list head, and if 'next' == NULL, the rule is the
+ * new list tail.
+ * If there are any nodes between 'prev' and 'next', they are dropped from the
+ * list. */
+static inline void
+cls_match_insert(struct cls_match *prev, struct cls_match *next,
+ struct cls_match *rule)
+{
+ ovsrcu_set_hidden(&rule->next, next);
+
+ if (prev) {
+ ovsrcu_set(&prev->next, rule);
+ }
+}
+
+/* Puts 'new_rule' in the position of 'old_rule', which is the next node after
+ * 'prev'. If 'prev' == NULL, then the 'new_rule' is the new list head.
+ *
+ * The replaced cls_match still links to the later rules, and may still be
+ * referenced by other threads until all other threads quiesce. The replaced
+ * rule may not be re-inserted, re-initialized, or deleted until after all
+ * other threads have quiesced (use ovsrcu_postpone). */
+static inline void
+cls_match_replace(struct cls_match *prev,
+ struct cls_match *old_rule, struct cls_match *new_rule)
+{
+ cls_match_insert(prev, cls_match_next_protected(old_rule), new_rule);
+}
+
+/* Removes 'rule' following 'prev' from the list. If 'prev' is NULL, then the
+ * 'rule' is a list head, and the caller is responsible for maintaining its
+ * list head pointer (if any).
+ *
+ * Afterward, the removed rule is not linked to any more, but still links to
+ * the following rules, and may still be referenced by other threads until all
+ * other threads quiesce. The removed rule may not be re-inserted,
+ * re-initialized, or deleted until after all other threads have quiesced (use
+ * ovsrcu_postpone).
+ */
+static inline void
+cls_match_remove(struct cls_match *prev, struct cls_match *rule)
+{
+ if (prev) {
+ ovsrcu_set(&prev->next, cls_match_next_protected(rule));
+ }
+}
+
+#define CLS_MATCH_FOR_EACH(ITER, HEAD) \
+ for ((ITER) = (HEAD); (ITER); (ITER) = cls_match_next(ITER))
+
+#define CLS_MATCH_FOR_EACH_AFTER_HEAD(ITER, HEAD) \
+ CLS_MATCH_FOR_EACH(ITER, cls_match_next(HEAD))
+
+/* Iterate cls_matches keeping the previous pointer for modifications. */
+#define FOR_EACH_RULE_IN_LIST_PROTECTED(ITER, PREV, HEAD) \
+ for ((PREV) = NULL, (ITER) = (HEAD); \
+ (ITER); \
+ (PREV) = (ITER), (ITER) = cls_match_next_protected(ITER))
+
+\f