* format: \\HOST\SHARE[\OPTIONAL PATH]
         */
        char *leaf_fullpath;
+       bool dfs_conn:1;
 };
 
 static inline bool is_smb1(struct TCP_Server_Info *server)
        struct list_head smb_ses_list;
        struct list_head rlist; /* reconnect list */
        struct list_head tcon_list;
+       struct list_head dlist; /* dfs list */
        struct cifs_tcon *tcon_ipc;
        spinlock_t ses_lock;  /* protect anything here that is not protected */
        struct mutex session_mutex;
        /* BB add field for back pointer to sb struct(s)? */
 #ifdef CONFIG_CIFS_DFS_UPCALL
        struct delayed_work dfs_cache_work;
+       struct list_head dfs_ses_list;
 #endif
        struct delayed_work     query_interfaces; /* query interfaces workqueue job */
        char *origin_fullpath; /* canonical copy of smb3_fs_context::source */
 
 
 int cifs_wait_for_server_reconnect(struct TCP_Server_Info *server, bool retry);
 
-/* Put references of @ses and its children */
 static inline void cifs_put_smb_ses(struct cifs_ses *ses)
 {
-       struct cifs_ses *next;
-
-       do {
-               next = ses->dfs_root_ses;
-               __cifs_put_smb_ses(ses);
-       } while ((ses = next));
+       __cifs_put_smb_ses(ses);
 }
 
 /* Get an active reference of @ses and its children.
 static inline void cifs_smb_ses_inc_refcount(struct cifs_ses *ses)
 {
        lockdep_assert_held(&cifs_tcp_ses_lock);
-
-       for (; ses; ses = ses->dfs_root_ses)
-               ses->ses_count++;
+       ses->ses_count++;
 }
 
 static inline bool dfs_src_pathname_equal(const char *s1, const char *s2)
 
        if (server->nosharesock)
                return 0;
 
+       if (!match_super && (ctx->dfs_conn || server->dfs_conn))
+               return 0;
+
        /* If multidialect negotiation see if existing sessions match one */
        if (strcmp(ctx->vals->version_string, SMB3ANY_VERSION_STRING) == 0) {
                if (server->vals->protocol_id < SMB30_PROT_ID)
 
        if (ctx->nosharesock)
                tcp_ses->nosharesock = true;
+       tcp_ses->dfs_conn = ctx->dfs_conn;
 
        tcp_ses->ops = ctx->ops;
        tcp_ses->vals = ctx->vals;
 }
 
 /* this function must be called with ses_lock and chan_lock held */
-static int match_session(struct cifs_ses *ses, struct smb3_fs_context *ctx)
+static int match_session(struct cifs_ses *ses,
+                        struct smb3_fs_context *ctx,
+                        bool match_super)
 {
        if (ctx->sectype != Unspecified &&
            ctx->sectype != ses->sectype)
                return 0;
 
-       if (ctx->dfs_root_ses != ses->dfs_root_ses)
+       if (!match_super && ctx->dfs_root_ses != ses->dfs_root_ses)
                return 0;
 
        /*
                        continue;
                }
                spin_lock(&ses->chan_lock);
-               if (match_session(ses, ctx)) {
+               if (match_session(ses, ctx, false)) {
                        spin_unlock(&ses->chan_lock);
                        spin_unlock(&ses->ses_lock);
                        ret = ses;
         * need to lock before changing something in the session.
         */
        spin_lock(&cifs_tcp_ses_lock);
-       if (ctx->dfs_root_ses)
-               cifs_smb_ses_inc_refcount(ctx->dfs_root_ses);
        ses->dfs_root_ses = ctx->dfs_root_ses;
        list_add(&ses->smb_ses_list, &server->smb_ses_list);
        spin_unlock(&cifs_tcp_ses_lock);
 {
        unsigned int xid;
        struct cifs_ses *ses;
+       LIST_HEAD(ses_list);
 
        /*
         * IPC tcon share the lifetime of their session and are
 
        list_del_init(&tcon->tcon_list);
        tcon->status = TID_EXITING;
+#ifdef CONFIG_CIFS_DFS_UPCALL
+       list_replace_init(&tcon->dfs_ses_list, &ses_list);
+#endif
        spin_unlock(&tcon->tc_lock);
        spin_unlock(&cifs_tcp_ses_lock);
 
        cifs_fscache_release_super_cookie(tcon);
        tconInfoFree(tcon, netfs_trace_tcon_ref_free);
        cifs_put_smb_ses(ses);
+#ifdef CONFIG_CIFS_DFS_UPCALL
+       dfs_put_root_smb_sessions(&ses_list);
+#endif
 }
 
 /**
        spin_lock(&ses->chan_lock);
        spin_lock(&tcon->tc_lock);
        if (!match_server(tcp_srv, ctx, true) ||
-           !match_session(ses, ctx) ||
+           !match_session(ses, ctx, true) ||
            !match_tcon(tcon, ctx) ||
            !match_prepath(sb, tcon, mnt_data)) {
                rc = 0;
 int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx)
 {
        struct cifs_mount_ctx mnt_ctx = { .cifs_sb = cifs_sb, .fs_ctx = ctx, };
-       bool isdfs;
        int rc;
 
-       rc = dfs_mount_share(&mnt_ctx, &isdfs);
+       rc = dfs_mount_share(&mnt_ctx);
        if (rc)
                goto error;
-       if (!isdfs)
+       if (!ctx->dfs_conn)
                goto out;
 
        /*
 }
 
 static struct cifs_tcon *
-__cifs_construct_tcon(struct cifs_sb_info *cifs_sb, kuid_t fsuid)
+cifs_construct_tcon(struct cifs_sb_info *cifs_sb, kuid_t fsuid)
 {
        int rc;
        struct cifs_tcon *master_tcon = cifs_sb_master_tcon(cifs_sb);
        return tcon;
 }
 
-static struct cifs_tcon *
-cifs_construct_tcon(struct cifs_sb_info *cifs_sb, kuid_t fsuid)
-{
-       struct cifs_tcon *ret;
-
-       cifs_mount_lock();
-       ret = __cifs_construct_tcon(cifs_sb, fsuid);
-       cifs_mount_unlock();
-       return ret;
-}
-
 struct cifs_tcon *
 cifs_sb_master_tcon(struct cifs_sb_info *cifs_sb)
 {
 
  * Get an active reference of @ses so that next call to cifs_put_tcon() won't
  * release it as any new DFS referrals must go through its IPC tcon.
  */
-static void add_root_smb_session(struct cifs_mount_ctx *mnt_ctx)
+static void set_root_smb_session(struct cifs_mount_ctx *mnt_ctx)
 {
        struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
        struct cifs_ses *ses = mnt_ctx->ses;
        return rc;
 }
 
-static int set_ref_paths(struct cifs_mount_ctx *mnt_ctx,
+static int setup_dfs_ref(struct cifs_mount_ctx *mnt_ctx,
                         struct dfs_info3_param *tgt,
                         struct dfs_ref_walk *rw)
 {
        }
        ref_walk_path(rw) = ref_path;
        ref_walk_fpath(rw) = full_path;
+       ref_walk_ses(rw) = ctx->dfs_root_ses;
        return 0;
 }
 
 {
        struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
        struct dfs_info3_param tgt = {};
-       bool is_refsrv;
        int rc = -ENOENT;
 
 again:
        do {
+               ctx->dfs_root_ses = ref_walk_ses(rw);
                if (ref_walk_empty(rw)) {
                        rc = dfs_get_referral(mnt_ctx, ref_walk_path(rw) + 1,
                                              NULL, ref_walk_tl(rw));
                        if (rc)
                                continue;
 
-                       is_refsrv = tgt.server_type == DFS_TYPE_ROOT ||
-                               DFS_INTERLINK(tgt.flags);
                        ref_walk_set_tgt_hint(rw);
-
                        if (tgt.flags & DFSREF_STORAGE_SERVER) {
                                rc = cifs_mount_get_tcon(mnt_ctx);
                                if (!rc)
                                        continue;
                        }
 
-                       if (is_refsrv)
-                               add_root_smb_session(mnt_ctx);
-
+                       set_root_smb_session(mnt_ctx);
                        rc = ref_walk_advance(rw);
                        if (!rc) {
-                               rc = set_ref_paths(mnt_ctx, &tgt, rw);
+                               rc = setup_dfs_ref(mnt_ctx, &tgt, rw);
                                if (!rc) {
                                        rc = -EREMOTE;
                                        goto again;
        return rc;
 }
 
-static int dfs_referral_walk(struct cifs_mount_ctx *mnt_ctx)
+static int dfs_referral_walk(struct cifs_mount_ctx *mnt_ctx,
+                            struct dfs_ref_walk **rw)
 {
-       struct dfs_ref_walk *rw;
        int rc;
 
-       rw = ref_walk_alloc();
-       if (IS_ERR(rw))
-               return PTR_ERR(rw);
+       *rw = ref_walk_alloc();
+       if (IS_ERR(*rw)) {
+               rc = PTR_ERR(*rw);
+               *rw = NULL;
+               return rc;
+       }
 
-       ref_walk_init(rw);
-       rc = set_ref_paths(mnt_ctx, NULL, rw);
+       ref_walk_init(*rw);
+       rc = setup_dfs_ref(mnt_ctx, NULL, *rw);
        if (!rc)
-               rc = __dfs_referral_walk(mnt_ctx, rw);
-       ref_walk_free(rw);
+               rc = __dfs_referral_walk(mnt_ctx, *rw);
        return rc;
 }
 
 {
        struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;
        struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
+       struct dfs_ref_walk *rw = NULL;
        struct cifs_tcon *tcon;
        char *origin_fullpath;
-       bool new_tcon = true;
        int rc;
 
        origin_fullpath = dfs_get_path(cifs_sb, ctx->source);
        if (IS_ERR(origin_fullpath))
                return PTR_ERR(origin_fullpath);
 
-       rc = dfs_referral_walk(mnt_ctx);
+       rc = dfs_referral_walk(mnt_ctx, &rw);
        if (!rc) {
                /*
                 * Prevent superblock from being created with any missing
 
        tcon = mnt_ctx->tcon;
        spin_lock(&tcon->tc_lock);
-       if (!tcon->origin_fullpath) {
-               tcon->origin_fullpath = origin_fullpath;
-               origin_fullpath = NULL;
-       } else {
-               new_tcon = false;
-       }
+       tcon->origin_fullpath = origin_fullpath;
+       origin_fullpath = NULL;
+       ref_walk_set_tcon(rw, tcon);
        spin_unlock(&tcon->tc_lock);
-
-       if (new_tcon) {
-               queue_delayed_work(dfscache_wq, &tcon->dfs_cache_work,
-                                  dfs_cache_get_ttl() * HZ);
-       }
+       queue_delayed_work(dfscache_wq, &tcon->dfs_cache_work,
+                          dfs_cache_get_ttl() * HZ);
 
 out:
        kfree(origin_fullpath);
+       ref_walk_free(rw);
        return rc;
 }
 
        return rc;
 }
 
-int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx, bool *isdfs)
+int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx)
 {
        struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
        bool nodfs = ctx->nodfs;
        if (rc)
                return rc;
 
-       *isdfs = false;
        rc = get_session(mnt_ctx, NULL);
        if (rc)
                return rc;
                return rc;
        }
 
-       *isdfs = true;
-       add_root_smb_session(mnt_ctx);
-       rc = __dfs_mount_share(mnt_ctx);
-       dfs_put_root_smb_sessions(mnt_ctx);
+       if (!ctx->dfs_conn) {
+               ctx->dfs_conn = true;
+               cifs_mount_put_conns(mnt_ctx);
+               rc = get_session(mnt_ctx, NULL);
+       }
+       if (!rc) {
+               set_root_smb_session(mnt_ctx);
+               rc = __dfs_mount_share(mnt_ctx);
+       }
        return rc;
 }
 
 
 struct dfs_ref {
        char *path;
        char *full_path;
+       struct cifs_ses *ses;
        struct dfs_cache_tgt_list tl;
        struct dfs_cache_tgt_iterator *tit;
 };
 #define ref_walk_path(w)       (ref_walk_cur(w)->path)
 #define ref_walk_fpath(w)      (ref_walk_cur(w)->full_path)
 #define ref_walk_tl(w)         (&ref_walk_cur(w)->tl)
+#define ref_walk_ses(w)        (ref_walk_cur(w)->ses)
 
 static inline struct dfs_ref_walk *ref_walk_alloc(void)
 {
        kfree(ref->path);
        kfree(ref->full_path);
        dfs_cache_free_tgts(&ref->tl);
+       if (ref->ses)
+               cifs_put_smb_ses(ref->ses);
        memset(ref, 0, sizeof(*ref));
 }
 
 static inline void ref_walk_free(struct dfs_ref_walk *rw)
 {
-       struct dfs_ref *ref = ref_walk_start(rw);
+       struct dfs_ref *ref;
 
-       for (; ref <= ref_walk_end(rw); ref++)
+       if (!rw)
+               return;
+
+       for (ref = ref_walk_start(rw); ref <= ref_walk_end(rw); ref++)
                __ref_walk_free(ref);
        kfree(rw);
 }
                                       ref_walk_tit(rw));
 }
 
+static inline void ref_walk_set_tcon(struct dfs_ref_walk *rw,
+                                    struct cifs_tcon *tcon)
+{
+       struct dfs_ref *ref = ref_walk_start(rw);
+
+       for (; ref <= ref_walk_cur(rw); ref++) {
+               if (WARN_ON_ONCE(!ref->ses))
+                       continue;
+               list_add(&ref->ses->dlist, &tcon->dfs_ses_list);
+               ref->ses = NULL;
+       }
+}
+
 int dfs_parse_target_referral(const char *full_path, const struct dfs_info3_param *ref,
                              struct smb3_fs_context *ctx);
-int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx, bool *isdfs);
+int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx);
 
 static inline char *dfs_get_path(struct cifs_sb_info *cifs_sb, const char *path)
 {
  * references of all DFS root sessions that were used across the mount process
  * in dfs_mount_share().
  */
-static inline void dfs_put_root_smb_sessions(struct cifs_mount_ctx *mnt_ctx)
+static inline void dfs_put_root_smb_sessions(struct list_head *head)
 {
-       const struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
-       struct cifs_ses *ses = ctx->dfs_root_ses;
-       struct cifs_ses *cur;
-
-       if (!ses)
-               return;
+       struct cifs_ses *ses, *n;
 
-       for (cur = ses; cur; cur = cur->dfs_root_ses) {
-               if (cur->dfs_root_ses)
-                       cifs_put_smb_ses(cur->dfs_root_ses);
+       list_for_each_entry_safe(ses, n, head, dlist) {
+               list_del_init(&ses->dlist);
+               cifs_put_smb_ses(ses);
        }
-       cifs_put_smb_ses(ses);
 }
 
 #endif /* _CIFS_DFS_H */
 
        struct cifs_ses *ses;
 
        tcon = container_of(work, struct cifs_tcon, dfs_cache_work.work);
-       ses = tcon->ses->dfs_root_ses;
 
-       for (; ses; ses = ses->dfs_root_ses)
+       list_for_each_entry(ses, &tcon->dfs_ses_list, dlist)
                refresh_ses_referral(ses);
        refresh_tcon_referral(tcon, false);
 
 
        struct cifs_ses *dfs_root_ses;
        bool dfs_automount:1; /* set for dfs automount only */
        enum cifs_reparse_type reparse_type;
+       bool dfs_conn:1; /* set for dfs mounts */
 };
 
 extern const struct fs_parameter_spec smb3_fs_parameters[];
 
        mutex_init(&ret_buf->fscache_lock);
 #endif
        trace_smb3_tcon_ref(ret_buf->debug_id, ret_buf->tc_count, trace);
+#ifdef CONFIG_CIFS_DFS_UPCALL
+       INIT_LIST_HEAD(&ret_buf->dfs_ses_list);
+#endif
 
        return ret_buf;
 }
 
                ctx->source = NULL;
                goto out;
        }
-       ctx->dfs_automount = is_dfs_mount(mntpt);
+       ctx->dfs_automount = ctx->dfs_conn = is_dfs_mount(mntpt);
        cifs_dbg(FYI, "%s: ctx: source=%s UNC=%s prepath=%s dfs_automount=%d\n",
                 __func__, ctx->source, ctx->UNC, ctx->prepath, ctx->dfs_automount);