return err;
 }
 
+static int sparx5_tc_flower_action_check(struct vcap_control *vctrl,
+                                        struct flow_cls_offload *fco,
+                                        struct vcap_admin *admin)
+{
+       struct flow_rule *rule = flow_cls_offload_flow_rule(fco);
+       struct flow_action_entry *actent, *last_actent = NULL;
+       struct flow_action *act = &rule->action;
+       u64 action_mask = 0;
+       int idx;
+
+       if (!flow_action_has_entries(act)) {
+               NL_SET_ERR_MSG_MOD(fco->common.extack, "No actions");
+               return -EINVAL;
+       }
+
+       if (!flow_action_basic_hw_stats_check(act, fco->common.extack))
+               return -EOPNOTSUPP;
+
+       flow_action_for_each(idx, actent, act) {
+               if (action_mask & BIT(actent->id)) {
+                       NL_SET_ERR_MSG_MOD(fco->common.extack,
+                                          "More actions of the same type");
+                       return -EINVAL;
+               }
+               action_mask |= BIT(actent->id);
+               last_actent = actent; /* Save last action for later check */
+       }
+
+       /* Check that last action is a goto */
+       if (last_actent->id != FLOW_ACTION_GOTO) {
+               NL_SET_ERR_MSG_MOD(fco->common.extack,
+                                  "Last action must be 'goto'");
+               return -EINVAL;
+       }
+
+       /* Check if the goto chain is in the next lookup */
+       if (!vcap_is_next_lookup(vctrl, fco->common.chain_index,
+                                last_actent->chain_index)) {
+               NL_SET_ERR_MSG_MOD(fco->common.extack,
+                                  "Invalid goto chain");
+               return -EINVAL;
+       }
+
+       /* Catch unsupported combinations of actions */
+       if (action_mask & BIT(FLOW_ACTION_TRAP) &&
+           action_mask & BIT(FLOW_ACTION_ACCEPT)) {
+               NL_SET_ERR_MSG_MOD(fco->common.extack,
+                                  "Cannot combine pass and trap action");
+               return -EOPNOTSUPP;
+       }
+
+       return 0;
+}
+
 static int sparx5_tc_flower_replace(struct net_device *ndev,
                                    struct flow_cls_offload *fco,
                                    struct vcap_admin *admin)
        struct vcap_rule *vrule;
        int err, idx;
 
-       frule = flow_cls_offload_flow_rule(fco);
-       if (!flow_action_has_entries(&frule->action)) {
-               NL_SET_ERR_MSG_MOD(fco->common.extack, "No actions");
-               return -EINVAL;
-       }
+       vctrl = port->sparx5->vcap_ctrl;
 
-       if (!flow_action_basic_hw_stats_check(&frule->action, fco->common.extack))
-               return -EOPNOTSUPP;
+       err = sparx5_tc_flower_action_check(vctrl, fco, admin);
+       if (err)
+               return err;
 
-       vctrl = port->sparx5->vcap_ctrl;
        vrule = vcap_alloc_rule(vctrl, ndev, fco->common.chain_index, VCAP_USER_TC,
                                fco->common.prio, 0);
        if (IS_ERR(vrule))
 
        vrule->cookie = fco->cookie;
        sparx5_tc_use_dissectors(fco, admin, vrule);
+       frule = flow_cls_offload_flow_rule(fco);
        flow_action_for_each(idx, act, &frule->action) {
                switch (act->id) {
                case FLOW_ACTION_TRAP:
                        if (err)
                                goto out;
                        break;
+               case FLOW_ACTION_GOTO:
+                       /* Links between VCAPs will be added later */
+                       break;
                default:
                        NL_SET_ERR_MSG_MOD(fco->common.extack,
                                           "Unsupported TC action");
 
 }
 EXPORT_SYMBOL_GPL(vcap_find_admin);
 
+/* Is the next chain id in the following lookup, possible in another VCAP */
+bool vcap_is_next_lookup(struct vcap_control *vctrl, int cur_cid, int next_cid)
+{
+       struct vcap_admin *admin, *next_admin;
+       int lookup, next_lookup;
+
+       /* The offset must be at least one lookup */
+       if (next_cid < cur_cid + VCAP_CID_LOOKUP_SIZE)
+               return false;
+
+       if (vcap_api_check(vctrl))
+               return false;
+
+       admin = vcap_find_admin(vctrl, cur_cid);
+       if (!admin)
+               return false;
+
+       /* If no VCAP contains the next chain, the next chain must be beyond
+        * the last chain in the current VCAP
+        */
+       next_admin = vcap_find_admin(vctrl, next_cid);
+       if (!next_admin)
+               return next_cid > admin->last_cid;
+
+       lookup = vcap_chain_id_to_lookup(admin, cur_cid);
+       next_lookup = vcap_chain_id_to_lookup(next_admin, next_cid);
+
+       /* Next lookup must be the following lookup */
+       if (admin == next_admin || admin->vtype == next_admin->vtype)
+               return next_lookup == lookup + 1;
+
+       /* Must be the first lookup in the next VCAP instance */
+       return next_lookup == 0;
+}
+EXPORT_SYMBOL_GPL(vcap_is_next_lookup);
+
 /* Check if there is room for a new rule */
 static int vcap_rule_space(struct vcap_admin *admin, int size)
 {