From e55c72cda8fb4bc0d41042533571f2561b204268 Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Tue, 14 Jul 2026 23:36:42 -0700 Subject: [PATCH] feat(patches): 9pfs xen multi-attach for 6.18.x and 6.19+ Add guest-kernel patches that let one Xen 9pfs frontend back several mounts, each attaching with its own aname, and advertise the capability as edera_multi_attach_v1 in /sys/fs/9p/features. This lets zones mount each volume as an independent 9p mount so df resolves per volume instead of every volume reporting the single shared device. The 9p mount API became fs_context-based in 6.19, so there are two variants: 9pfs-xen-multi-attach.patch (lower: 6.19) and an old-mount-API backport 9pfs-xen-multi-attach-6.18.patch (series: 6.18). --- config.yaml | 9 + patches/9pfs-xen-multi-attach-6.18.patch | 368 ++++++++++++++++++++++ patches/9pfs-xen-multi-attach.patch | 372 +++++++++++++++++++++++ 3 files changed, 749 insertions(+) create mode 100644 patches/9pfs-xen-multi-attach-6.18.patch create mode 100644 patches/9pfs-xen-multi-attach.patch diff --git a/config.yaml b/config.yaml index 8e9c085..aa15263 100644 --- a/config.yaml +++ b/config.yaml @@ -89,6 +89,15 @@ patches: - patch: 0001-9p-xen-mark-9p-transport-device-as-closing-when-remo.patch lower: '6.1' upper: '6.12.74' +# Share one Xen 9pfs frontend across many mounts (each with its own aname) and +# advertise it as edera_multi_attach_v1 in /sys/fs/9p/features, so zones can +# mount each volume as an independent 9p mount and df resolves per volume. +# The 9p mount API became fs_context-based in 6.19, so 6.18.x needs the +# separate old-API backport below. +- patch: 9pfs-xen-multi-attach.patch + lower: '6.19' +- patch: 9pfs-xen-multi-attach-6.18.patch + series: '6.18' - patch: 0002-x86-amd_node-fix-integer-divide-by-zero-during-init.patch lower: '6.17' - patch: 0003-x86-amd_node-fix-null-pointer-dereference-if-amd_smn.patch diff --git a/patches/9pfs-xen-multi-attach-6.18.patch b/patches/9pfs-xen-multi-attach-6.18.patch new file mode 100644 index 0000000..94e1569 --- /dev/null +++ b/patches/9pfs-xen-multi-attach-6.18.patch @@ -0,0 +1,368 @@ +From a5a16b585cb5925fdd6c2f13db2c0b8896a29697 Mon Sep 17 00:00:00 2001 +From: Alex Zenla +Date: Tue, 14 Jul 2026 23:27:39 -0700 +Subject: [PATCH] 9p/xen: share one frontend across mounts and advertise it + (6.18) + +Backport of the multi-attach change to the pre-fs_context 9p mount API +used by 6.18.x. Let the 9p core share one refcounted p9_client across all +mounts of an endpoint (opt-in via p9_trans_module.share_client, set for +xen), so several mounts share a frontend, each attaching with its own +aname to get an independent tree and superblock. + +The endpoint is selected by a tag= mount option (parsed from the options +string in the old API) so the mount source can differ per mount (distinct +/proc/mounts device names), and the capability is advertised to userspace +as edera_multi_attach_v1 in /sys/fs/9p/features. + +Signed-off-by: Alex Zenla +--- + fs/9p/v9fs.c | 20 ++++++ + include/net/9p/client.h | 10 +++ + include/net/9p/transport.h | 8 +++ + net/9p/client.c | 125 +++++++++++++++++++++++++++++++++++++ + net/9p/trans_xen.c | 40 +++++++++++- + 5 files changed, 200 insertions(+), 3 deletions(-) + +diff --git a/fs/9p/v9fs.c b/fs/9p/v9fs.c +index bde3ffb0e319..daaca8117f53 100644 +--- a/fs/9p/v9fs.c ++++ b/fs/9p/v9fs.c +@@ -578,10 +578,30 @@ static ssize_t caches_show(struct kobject *kobj, + static struct kobj_attribute v9fs_attr_cache = __ATTR_RO(caches); + #endif /* CONFIG_9P_FSCACHE */ + ++/* ++ * Capability tokens for userspace to probe, one per line. ++ * ++ * "edera_multi_attach_v1": a transport that sets p9_trans_module.share_client ++ * (e.g. Xen 9pfs) can back several mounts of a single endpoint, each attaching ++ * with its own aname. Userspace can test for this token before mounting subtrees ++ * of one frontend as independent superblocks (distinct st_dev) instead of ++ * bind-mounting from a single mount. The token is vendor-namespaced and ++ * versioned so it never aliases an unrelated upstream feature name, and a future ++ * behavior change can advertise "_v2" instead. ++ */ ++static ssize_t features_show(struct kobject *kobj, ++ struct kobj_attribute *attr, char *buf) ++{ ++ return sysfs_emit(buf, "edera_multi_attach_v1\n"); ++} ++ ++static struct kobj_attribute v9fs_attr_features = __ATTR_RO(features); ++ + static struct attribute *v9fs_attrs[] = { + #ifdef CONFIG_9P_FSCACHE + &v9fs_attr_cache.attr, + #endif ++ &v9fs_attr_features.attr, + NULL, + }; + +diff --git a/include/net/9p/client.h b/include/net/9p/client.h +index 4f785098c67a..763cad1359a0 100644 +--- a/include/net/9p/client.h ++++ b/include/net/9p/client.h +@@ -123,6 +123,16 @@ struct p9_client { + struct idr fids; + struct idr reqs; + ++ /* Client sharing, for transports with p9_trans_module.share_client set: ++ * all mounts of one endpoint reference a single client. @refcount counts ++ * those mounts; @shared_list links the client into the shared-client ++ * registry, keyed by @shared_key (a copy of the endpoint tag/source). ++ * @shared_key is NULL for ordinary, unshared clients. ++ */ ++ refcount_t refcount; ++ struct list_head shared_list; ++ char *shared_key; ++ + char name[__NEW_UTS_LEN + 1]; + }; + +diff --git a/include/net/9p/transport.h b/include/net/9p/transport.h +index 766ec07c9599..0bc000853b58 100644 +--- a/include/net/9p/transport.h ++++ b/include/net/9p/transport.h +@@ -24,6 +24,13 @@ + * we're less flexible when choosing the response message + * size in this case + * @def: set if this transport should be considered the default ++ * @share_client: set if one transport endpoint (e.g. a Xen 9pfs frontend, ++ * keyed by its tag) can back more than one mount. Such an ++ * endpoint cannot multiplex several p9_clients, so when this is ++ * set the 9p core hands every mount of the same endpoint a ++ * single, refcounted p9_client instead of one per mount. Each ++ * mount still issues its own Tattach (with its own aname), so it ++ * gets an independent tree and superblock over the shared client. + * @create: member function to create a new connection on this transport + * @close: member function to discard a connection on this transport + * @request: member function to issue a request to the transport +@@ -44,6 +51,7 @@ struct p9_trans_module { + int maxsize; /* max message size of transport */ + bool pooled_rbuffers; + int def; /* this transport should be default */ ++ bool share_client; /* one endpoint may back many mounts */ + struct module *owner; + int (*create)(struct p9_client *client, + const char *devname, char *args); +diff --git a/net/9p/client.c b/net/9p/client.c +index 9c9d249dabae..84b1b0fb8e8a 100644 +--- a/net/9p/client.c ++++ b/net/9p/client.c +@@ -132,6 +132,59 @@ static int get_protocol_version(char *s) + * Return 0 upon success, -ERRNO upon failure + */ + ++/* ++ * Shared-client registry. ++ * ++ * Some transports (currently only Xen 9pfs) expose a single endpoint that ++ * cannot multiplex more than one p9_client: a second mount of the same ++ * endpoint would clobber the first's client pointer. For such transports ++ * (p9_trans_module.share_client) all mounts of one endpoint share a single ++ * refcounted p9_client and tell themselves apart by attaching (Tattach) with ++ * their own aname, which gives each an independent tree and superblock. ++ * ++ * Clients are keyed by endpoint id: the explicit "tag=" mount option when the ++ * mount supplied one, otherwise the mount device name. ++ */ ++static LIST_HEAD(p9_shared_clients); ++static DEFINE_MUTEX(p9_shared_clients_lock); ++ ++/* Value of the "tag=" mount option in @options, copied into @buf, or NULL when ++ * absent. Matched only at an option boundary. ++ */ ++static const char *p9_options_tag(const char *options, char *buf, size_t buflen) ++{ ++ const char *p = options; ++ ++ while (p && *p) { ++ if (!strncmp(p, "tag=", 4)) { ++ size_t n = strcspn(p + 4, ","); ++ ++ if (n == 0 || n >= buflen) ++ return NULL; ++ memcpy(buf, p + 4, n); ++ buf[n] = '\0'; ++ return buf; ++ } ++ p = strchr(p, ','); ++ if (p) ++ p++; ++ } ++ return NULL; ++} ++ ++/* Caller must hold p9_shared_clients_lock. */ ++static struct p9_client *p9_client_find_shared(struct p9_trans_module *trans, ++ const char *key) ++{ ++ struct p9_client *clnt; ++ ++ list_for_each_entry(clnt, &p9_shared_clients, shared_list) { ++ if (clnt->trans_mod == trans && !strcmp(clnt->shared_key, key)) ++ return clnt; ++ } ++ return NULL; ++} ++ + static int parse_opts(char *opts, struct p9_client *clnt) + { + char *options, *tmp_options; +@@ -981,6 +1034,7 @@ struct p9_client *p9_client_create(const char *dev_name, char *options) + struct p9_client *clnt; + char *client_id; + char *cache_name; ++ bool shared_locked = false; + + clnt = kmalloc(sizeof(*clnt), GFP_KERNEL); + if (!clnt) +@@ -996,6 +1050,9 @@ struct p9_client *p9_client_create(const char *dev_name, char *options) + spin_lock_init(&clnt->lock); + idr_init(&clnt->fids); + idr_init(&clnt->reqs); ++ refcount_set(&clnt->refcount, 1); ++ INIT_LIST_HEAD(&clnt->shared_list); ++ clnt->shared_key = NULL; + + err = parse_opts(options, clnt); + if (err < 0) +@@ -1014,6 +1071,47 @@ struct p9_client *p9_client_create(const char *dev_name, char *options) + p9_debug(P9_DEBUG_MUX, "clnt %p trans %p msize %d protocol %d\n", + clnt, clnt->trans_mod, clnt->msize, clnt->proto_version); + ++ if (clnt->trans_mod->share_client) { ++ char tagbuf[64]; ++ const char *key = p9_options_tag(options, tagbuf, sizeof(tagbuf)); ++ struct p9_client *shared; ++ ++ if (!key) ++ key = dev_name; ++ if (!key) { ++ err = -EINVAL; ++ goto put_trans; ++ } ++ mutex_lock(&p9_shared_clients_lock); ++ shared = p9_client_find_shared(clnt->trans_mod, key); ++ if (shared) { ++ /* Endpoint already has a client; reuse it. Discard the ++ * client we speculatively allocated above. ++ */ ++ refcount_inc(&shared->refcount); ++ mutex_unlock(&p9_shared_clients_lock); ++ v9fs_put_trans(clnt->trans_mod); ++ idr_destroy(&clnt->reqs); ++ idr_destroy(&clnt->fids); ++ kfree(clnt); ++ return shared; ++ } ++ /* First mount of this endpoint. Hold the registry lock across ++ * setup (trans->create + version negotiation) so a concurrent ++ * mount of the same endpoint waits and then finds a fully ++ * initialised client, rather than racing trans->create which ++ * binds the endpoint to a single client. share_client ++ * transports have a local backend, so the stall is bounded. ++ */ ++ clnt->shared_key = kstrdup(key, GFP_KERNEL); ++ if (!clnt->shared_key) { ++ mutex_unlock(&p9_shared_clients_lock); ++ err = -ENOMEM; ++ goto put_trans; ++ } ++ shared_locked = true; ++ } ++ + err = clnt->trans_mod->create(clnt, dev_name, options); + if (err) + goto put_trans; +@@ -1054,6 +1152,10 @@ struct p9_client *p9_client_create(const char *dev_name, char *options) + NULL); + + kfree(cache_name); ++ if (shared_locked) { ++ list_add(&clnt->shared_list, &p9_shared_clients); ++ mutex_unlock(&p9_shared_clients_lock); ++ } + return clnt; + + close_trans: +@@ -1061,6 +1163,10 @@ struct p9_client *p9_client_create(const char *dev_name, char *options) + put_trans: + v9fs_put_trans(clnt->trans_mod); + free_client: ++ if (shared_locked) { ++ kfree(clnt->shared_key); ++ mutex_unlock(&p9_shared_clients_lock); ++ } + kfree(clnt); + return ERR_PTR(err); + } +@@ -1073,6 +1179,18 @@ void p9_client_destroy(struct p9_client *clnt) + + p9_debug(P9_DEBUG_MUX, "clnt %p\n", clnt); + ++ /* Shared client: only the last mount tears it down. */ ++ if (clnt->shared_key) { ++ mutex_lock(&p9_shared_clients_lock); ++ if (!refcount_dec_and_test(&clnt->refcount)) { ++ mutex_unlock(&p9_shared_clients_lock); ++ return; ++ } ++ list_del(&clnt->shared_list); ++ mutex_unlock(&p9_shared_clients_lock); ++ kfree(clnt->shared_key); ++ } ++ + if (clnt->trans_mod) + clnt->trans_mod->close(clnt); + +@@ -1093,6 +1211,11 @@ EXPORT_SYMBOL(p9_client_destroy); + void p9_client_disconnect(struct p9_client *clnt) + { + p9_debug(P9_DEBUG_9P, "clnt %p\n", clnt); ++ /* On a shared client, only the last mount may tear the link down; ++ * disconnecting while a sibling mount is still live would break it. ++ */ ++ if (clnt->shared_key && refcount_read(&clnt->refcount) > 1) ++ return; + clnt->status = Disconnected; + } + EXPORT_SYMBOL(p9_client_disconnect); +@@ -1100,6 +1223,8 @@ EXPORT_SYMBOL(p9_client_disconnect); + void p9_client_begin_disconnect(struct p9_client *clnt) + { + p9_debug(P9_DEBUG_9P, "clnt %p\n", clnt); ++ if (clnt->shared_key && refcount_read(&clnt->refcount) > 1) ++ return; + clnt->status = BeginDisconnect; + } + EXPORT_SYMBOL(p9_client_begin_disconnect); +diff --git a/net/9p/trans_xen.c b/net/9p/trans_xen.c +index 068d57515dd5..9960a7e8246e 100644 +--- a/net/9p/trans_xen.c ++++ b/net/9p/trans_xen.c +@@ -66,16 +66,49 @@ static int p9_xen_cancel(struct p9_client *client, struct p9_req_t *req) + return 1; + } + ++/* Value of the "tag=" mount option in @args, copied into @buf, or NULL when ++ * absent. Matched only at an option boundary. The device is selected by tag, ++ * so several mounts of one frontend can carry distinct source strings. ++ */ ++static const char *p9_xen_opt_tag(const char *args, char *buf, size_t buflen) ++{ ++ const char *p = args; ++ ++ while (p && *p) { ++ if (!strncmp(p, "tag=", 4)) { ++ size_t n = strcspn(p + 4, ","); ++ ++ if (n == 0 || n >= buflen) ++ return NULL; ++ memcpy(buf, p + 4, n); ++ buf[n] = '\0'; ++ return buf; ++ } ++ p = strchr(p, ','); ++ if (p) ++ p++; ++ } ++ return NULL; ++} ++ + static int p9_xen_create(struct p9_client *client, const char *addr, char *args) + { + struct xen_9pfs_front_priv *priv; +- +- if (addr == NULL) ++ char tagbuf[64]; ++ const char *tag; ++ ++ /* Prefer an explicit tag= option so the mount source can differ per ++ * mount (distinct /proc/mounts device names); fall back to the source. ++ */ ++ tag = p9_xen_opt_tag(args, tagbuf, sizeof(tagbuf)); ++ if (!tag) ++ tag = addr; ++ if (tag == NULL) + return -EINVAL; + + read_lock(&xen_9pfs_lock); + list_for_each_entry(priv, &xen_9pfs_devs, list) { +- if (!strcmp(priv->tag, addr)) { ++ if (!strcmp(priv->tag, tag)) { + priv->client = client; + read_unlock(&xen_9pfs_lock); + return 0; +@@ -258,6 +291,7 @@ static struct p9_trans_module p9_xen_trans = { + .maxsize = 1 << (XEN_9PFS_RING_ORDER + XEN_PAGE_SHIFT - 2), + .pooled_rbuffers = false, + .def = 1, ++ .share_client = true, + .create = p9_xen_create, + .close = p9_xen_close, + .request = p9_xen_request, +-- +2.55.0 + diff --git a/patches/9pfs-xen-multi-attach.patch b/patches/9pfs-xen-multi-attach.patch new file mode 100644 index 0000000..0b6ea59 --- /dev/null +++ b/patches/9pfs-xen-multi-attach.patch @@ -0,0 +1,372 @@ +From 4e7625e42cff86b597e335dd8a58c8ce25b70c98 Mon Sep 17 00:00:00 2001 +From: Alex Zenla +Date: Tue, 14 Jul 2026 22:43:35 -0700 +Subject: [PATCH] 9p/xen: share one frontend across mounts and advertise it + +The Xen 9pfs transport binds a single p9_client to each frontend +endpoint, so a second mount of the same tag clobbers the first. Let the +9p core share one refcounted p9_client across all mounts of an endpoint +(opt-in via p9_trans_module.share_client, set for xen), so several mounts +share a frontend, each attaching with its own aname to get an independent +tree and superblock. + +Add a tag= mount option so the endpoint id is decoupled from the mount +source string (distinct /proc/mounts device names per mount), and +advertise the capability to userspace as edera_multi_attach_v1 in +/sys/fs/9p/features so it can probe support before relying on it. + +Signed-off-by: Alex Zenla +--- + fs/9p/v9fs.c | 32 ++++++++++- + fs/9p/vfs_super.c | 1 + + include/net/9p/client.h | 16 ++++++ + include/net/9p/transport.h | 9 ++++ + net/9p/client.c | 107 +++++++++++++++++++++++++++++++++++++ + net/9p/trans_xen.c | 9 +++- + 6 files changed, 172 insertions(+), 2 deletions(-) + +diff --git a/fs/9p/v9fs.c b/fs/9p/v9fs.c +index 057487efaaeb..e99993ec229e 100644 +--- a/fs/9p/v9fs.c ++++ b/fs/9p/v9fs.c +@@ -50,7 +50,7 @@ enum { + Opt_locktimeout, + + /* Client options */ +- Opt_msize, Opt_trans, Opt_legacy, Opt_version, ++ Opt_msize, Opt_trans, Opt_legacy, Opt_version, Opt_tag, + + /* fd transport options */ + /* Options that take integer arguments */ +@@ -99,6 +99,7 @@ const struct fs_parameter_spec v9fs_param_spec[] = { + fsparam_flag ("noextend", Opt_legacy), + fsparam_string ("trans", Opt_trans), + fsparam_enum ("version", Opt_version, p9_versions), ++ fsparam_string ("tag", Opt_tag), + + /* fd transport options */ + fsparam_u32 ("rfdno", Opt_rfdno), +@@ -267,6 +268,15 @@ int v9fs_parse_param(struct fs_context *fc, struct fs_parameter *param) + session_opts->aname = param->string; + param->string = NULL; + break; ++ case Opt_tag: ++ /* Explicit transport endpoint id (Xen 9pfs tag), decoupled from ++ * the mount source so one endpoint can back several mounts that ++ * each show a distinct source in /proc/mounts. ++ */ ++ kfree(ctx->tag); ++ ctx->tag = param->string; ++ param->string = NULL; ++ break; + case Opt_nodevmap: + session_opts->nodev = 1; + break; +@@ -612,10 +622,30 @@ static ssize_t caches_show(struct kobject *kobj, + static struct kobj_attribute v9fs_attr_cache = __ATTR_RO(caches); + #endif /* CONFIG_9P_FSCACHE */ + ++/* ++ * Capability tokens for userspace to probe, one per line. ++ * ++ * "edera_multi_attach_v1": a transport that sets p9_trans_module.share_client ++ * (e.g. Xen 9pfs) can back several mounts of a single endpoint, each attaching ++ * with its own aname. Userspace can test for this token before mounting subtrees ++ * of one frontend as independent superblocks (distinct st_dev) instead of ++ * bind-mounting from a single mount. The token is vendor-namespaced and ++ * versioned so it never aliases an unrelated upstream feature name, and a future ++ * behavior change can advertise "_v2" instead. ++ */ ++static ssize_t features_show(struct kobject *kobj, ++ struct kobj_attribute *attr, char *buf) ++{ ++ return sysfs_emit(buf, "edera_multi_attach_v1\n"); ++} ++ ++static struct kobj_attribute v9fs_attr_features = __ATTR_RO(features); ++ + static struct attribute *v9fs_attrs[] = { + #ifdef CONFIG_9P_FSCACHE + &v9fs_attr_cache.attr, + #endif ++ &v9fs_attr_features.attr, + NULL, + }; + +diff --git a/fs/9p/vfs_super.c b/fs/9p/vfs_super.c +index 315336de6f02..ba67eb19147a 100644 +--- a/fs/9p/vfs_super.c ++++ b/fs/9p/vfs_super.c +@@ -293,6 +293,7 @@ static void v9fs_free_fc(struct fs_context *fc) + #ifdef CONFIG_9P_FSCACHE + kfree(ctx->session_opts.cachetag); + #endif ++ kfree(ctx->tag); + if (ctx->client_opts.trans_mod) + v9fs_put_trans(ctx->client_opts.trans_mod); + kfree(ctx); +diff --git a/include/net/9p/client.h b/include/net/9p/client.h +index 838a94218b59..76d44f85de11 100644 +--- a/include/net/9p/client.h ++++ b/include/net/9p/client.h +@@ -129,6 +129,16 @@ struct p9_client { + struct idr fids; + struct idr reqs; + ++ /* Client sharing, for transports with p9_trans_module.share_client set: ++ * all mounts of one endpoint reference a single client. @refcount counts ++ * those mounts; @shared_list links the client into the shared-client ++ * registry, keyed by @shared_key (a copy of the endpoint tag/source). ++ * @shared_key is NULL for ordinary, unshared clients. ++ */ ++ refcount_t refcount; ++ struct list_head shared_list; ++ char *shared_key; ++ + char name[__NEW_UTS_LEN + 1]; + }; + +@@ -220,6 +230,12 @@ struct v9fs_context { + struct p9_fd_opts fd_opts; + struct p9_rdma_opts rdma_opts; + struct p9_session_opts session_opts; ++ /* Optional explicit transport endpoint id (the Xen 9pfs "tag"). When ++ * set it selects the device and keys client sharing, decoupling the ++ * endpoint from the mount source string so each mount of one endpoint ++ * can present a distinct source in /proc/mounts. ++ */ ++ char *tag; + }; + + /** +diff --git a/include/net/9p/transport.h b/include/net/9p/transport.h +index a912bbaa862f..3a390962c563 100644 +--- a/include/net/9p/transport.h ++++ b/include/net/9p/transport.h +@@ -34,6 +34,14 @@ + * @supports_vmalloc: set if this transport can work with vmalloc'd buffers + * (non-physically contiguous memory). Transports requiring + * DMA should leave this as false. ++ * @share_client: set if a single transport endpoint (e.g. one Xen 9pfs ++ * frontend/backend pair, identified by its tag) can back more ++ * than one mount. Such an endpoint cannot multiplex several ++ * p9_clients, so when this is set the 9p core hands every mount ++ * of the same endpoint a single, refcounted p9_client instead of ++ * creating one per mount. Each mount still issues its own Tattach ++ * (with its own aname), so it gets an independent tree and ++ * superblock over the shared client. + * @create: member function to create a new connection on this transport + * @close: member function to discard a connection on this transport + * @request: member function to issue a request to the transport +@@ -55,6 +63,7 @@ struct p9_trans_module { + bool pooled_rbuffers; + bool def; /* this transport should be default */ + bool supports_vmalloc; /* can work with vmalloc'd buffers */ ++ bool share_client; /* one endpoint may back many mounts */ + struct module *owner; + int (*create)(struct p9_client *client, + struct fs_context *fc); +diff --git a/net/9p/client.c b/net/9p/client.c +index f60d1d041adb..4273ef97c12b 100644 +--- a/net/9p/client.c ++++ b/net/9p/client.c +@@ -81,6 +81,44 @@ static int safe_errno(int err) + return err; + } + ++/* ++ * Shared-client registry. ++ * ++ * Some transports (currently only Xen 9pfs) expose a single endpoint that ++ * cannot multiplex more than one p9_client: a second mount of the same ++ * endpoint would clobber the first's client pointer. For such transports ++ * (p9_trans_module.share_client) all mounts of one endpoint share a single ++ * refcounted p9_client and tell themselves apart by attaching (Tattach) with ++ * their own aname, which gives each an independent tree and superblock. ++ * ++ * Clients are keyed by endpoint id: the explicit transport tag when the mount ++ * supplied one (tag=), otherwise the mount source string. ++ */ ++static LIST_HEAD(p9_shared_clients); ++static DEFINE_MUTEX(p9_shared_clients_lock); ++ ++static const char *p9_client_share_key(struct fs_context *fc) ++{ ++ struct v9fs_context *ctx = fc->fs_private; ++ ++ if (ctx && ctx->tag) ++ return ctx->tag; ++ return fc->source; ++} ++ ++/* Caller must hold p9_shared_clients_lock. */ ++static struct p9_client *p9_client_find_shared(struct p9_trans_module *trans, ++ const char *key) ++{ ++ struct p9_client *clnt; ++ ++ list_for_each_entry(clnt, &p9_shared_clients, shared_list) { ++ if (clnt->trans_mod == trans && !strcmp(clnt->shared_key, key)) ++ return clnt; ++ } ++ return NULL; ++} ++ + static int apply_client_options(struct p9_client *clnt, struct fs_context *fc) + { + struct v9fs_context *ctx = fc->fs_private; +@@ -858,6 +896,7 @@ struct p9_client *p9_client_create(struct fs_context *fc) + struct p9_client *clnt; + char *client_id; + char *cache_name; ++ bool shared_locked = false; + + clnt = kmalloc(sizeof(*clnt), GFP_KERNEL); + if (!clnt) +@@ -873,6 +912,9 @@ struct p9_client *p9_client_create(struct fs_context *fc) + spin_lock_init(&clnt->lock); + idr_init(&clnt->fids); + idr_init(&clnt->reqs); ++ refcount_set(&clnt->refcount, 1); ++ INIT_LIST_HEAD(&clnt->shared_list); ++ clnt->shared_key = NULL; + + err = apply_client_options(clnt, fc); + if (err) +@@ -891,6 +933,44 @@ struct p9_client *p9_client_create(struct fs_context *fc) + p9_debug(P9_DEBUG_MUX, "clnt %p trans %p msize %d protocol %d\n", + clnt, clnt->trans_mod, clnt->msize, clnt->proto_version); + ++ if (clnt->trans_mod->share_client) { ++ const char *key = p9_client_share_key(fc); ++ struct p9_client *shared; ++ ++ if (!key) { ++ err = -EINVAL; ++ goto put_trans; ++ } ++ mutex_lock(&p9_shared_clients_lock); ++ shared = p9_client_find_shared(clnt->trans_mod, key); ++ if (shared) { ++ /* Endpoint already has a client; reuse it. Discard the ++ * client we speculatively allocated above. ++ */ ++ refcount_inc(&shared->refcount); ++ mutex_unlock(&p9_shared_clients_lock); ++ v9fs_put_trans(clnt->trans_mod); ++ idr_destroy(&clnt->reqs); ++ idr_destroy(&clnt->fids); ++ kfree(clnt); ++ return shared; ++ } ++ /* First mount of this endpoint. Hold the registry lock across ++ * setup (trans->create + version negotiation) so a concurrent ++ * mount of the same endpoint waits and then finds a fully ++ * initialised client, rather than racing trans->create which ++ * binds the endpoint to a single client. share_client ++ * transports have a local backend, so the stall is bounded. ++ */ ++ clnt->shared_key = kstrdup(key, GFP_KERNEL); ++ if (!clnt->shared_key) { ++ mutex_unlock(&p9_shared_clients_lock); ++ err = -ENOMEM; ++ goto put_trans; ++ } ++ shared_locked = true; ++ } ++ + err = clnt->trans_mod->create(clnt, fc); + if (err) + goto put_trans; +@@ -931,6 +1011,10 @@ struct p9_client *p9_client_create(struct fs_context *fc) + NULL); + + kfree(cache_name); ++ if (shared_locked) { ++ list_add(&clnt->shared_list, &p9_shared_clients); ++ mutex_unlock(&p9_shared_clients_lock); ++ } + return clnt; + + close_trans: +@@ -938,6 +1022,10 @@ struct p9_client *p9_client_create(struct fs_context *fc) + put_trans: + v9fs_put_trans(clnt->trans_mod); + free_client: ++ if (shared_locked) { ++ kfree(clnt->shared_key); ++ mutex_unlock(&p9_shared_clients_lock); ++ } + kfree(clnt); + return ERR_PTR(err); + } +@@ -950,6 +1038,18 @@ void p9_client_destroy(struct p9_client *clnt) + + p9_debug(P9_DEBUG_MUX, "clnt %p\n", clnt); + ++ /* Shared client: only the last mount tears it down. */ ++ if (clnt->shared_key) { ++ mutex_lock(&p9_shared_clients_lock); ++ if (!refcount_dec_and_test(&clnt->refcount)) { ++ mutex_unlock(&p9_shared_clients_lock); ++ return; ++ } ++ list_del(&clnt->shared_list); ++ mutex_unlock(&p9_shared_clients_lock); ++ kfree(clnt->shared_key); ++ } ++ + if (clnt->trans_mod) + clnt->trans_mod->close(clnt); + +@@ -970,6 +1070,11 @@ EXPORT_SYMBOL(p9_client_destroy); + void p9_client_disconnect(struct p9_client *clnt) + { + p9_debug(P9_DEBUG_9P, "clnt %p\n", clnt); ++ /* On a shared client, only the last mount may tear the link down; ++ * disconnecting while a sibling mount is still live would break it. ++ */ ++ if (clnt->shared_key && refcount_read(&clnt->refcount) > 1) ++ return; + clnt->status = Disconnected; + } + EXPORT_SYMBOL(p9_client_disconnect); +@@ -977,6 +1082,8 @@ EXPORT_SYMBOL(p9_client_disconnect); + void p9_client_begin_disconnect(struct p9_client *clnt) + { + p9_debug(P9_DEBUG_9P, "clnt %p\n", clnt); ++ if (clnt->shared_key && refcount_read(&clnt->refcount) > 1) ++ return; + clnt->status = BeginDisconnect; + } + EXPORT_SYMBOL(p9_client_begin_disconnect); +diff --git a/net/9p/trans_xen.c b/net/9p/trans_xen.c +index 12f752a92332..edc58c16432d 100644 +--- a/net/9p/trans_xen.c ++++ b/net/9p/trans_xen.c +@@ -69,7 +69,13 @@ static int p9_xen_cancel(struct p9_client *client, struct p9_req_t *req) + + static int p9_xen_create(struct p9_client *client, struct fs_context *fc) + { +- const char *addr = fc->source; ++ struct v9fs_context *ctx = fc->fs_private; ++ /* Select the device by its tag. An explicit tag= lets several mounts ++ * of one frontend carry distinct source strings (distinct ++ * /proc/mounts device names); without it the source string is the tag, ++ * as before. ++ */ ++ const char *addr = (ctx && ctx->tag) ? ctx->tag : fc->source; + struct xen_9pfs_front_priv *priv; + + if (addr == NULL) +@@ -261,6 +267,7 @@ static struct p9_trans_module p9_xen_trans = { + .pooled_rbuffers = false, + .def = true, + .supports_vmalloc = false, ++ .share_client = true, + .create = p9_xen_create, + .close = p9_xen_close, + .request = p9_xen_request, +-- +2.55.0 +