Skip to content

Commit f5b9a34

Browse files
shroffniigaw
authored andcommitted
tree: add support for discovering nvme paths using sysfs multipath link
With the upcoming Linux kernel v6.15, NVMe native multipath now provides a simplified mechanism for discovering all paths to a shared namespace through sysfs. A new "multipath" directory is created under each NVMe head namespace device in "/sys/block/<head>/multipath/". This directory contains symlinks to all namespace path devices that access the same shared namespace. For example, consider a shared namespace accessible via two paths under nvme-subsys1: nvme-subsys1 - NQN=nqn.1994-11.com.samsung:nvme:PM1735a:2.5-inch:S6RTNE0R900057 hostnqn=nqn.2014-08.org.nvmexpress:uuid:41528538-e8ad-4eaf-84a7-9c552917d988 \ +- ns 1 \ +- nvme0 pcie 052e:78:00.0 live optimized +- nvme1 pcie 058e:78:00.0 live optimized The head device `/dev/nvme1n1` will now have the following structure: /sys/block/nvme1n1/multipath/ ├── nvme1c0n1 -> ../../../../../pci052e:78/052e:78:00.0/nvme/nvme0/nvme1c0n1 └── nvme1c1n1 -> ../../../../../pci058e:78/058e:78:00.0/nvme/nvme1/nvme1c1n1 This clearly shows that namespace 1 is accessible through both nvme1c0n1 and nvme1c1n1. This new sysfs structure significantly simplifies multipath discovery and management, making it easier for tools and scripts to enumerate and manage NVMe multipath configurations. So leverage this functionality to update the path links for a shared NVMe namespace, simplifying path discovery and management. This change adds a new struct nvme_ns_head to represent the head of a shared namespace. It contains a list head linking together struct nvme_path objects, where each path corresponds to a shared namespace instance. Additionally, struct nvme_ns has been updated to reference its associated nvme_ns_head, enabling straightforward traversal of all paths to a shared NVMe namespace. Signed-off-by: Nilay Shroff <[email protected]> Signed-off-by: Daniel Wagner <[email protected]>
1 parent bf923b3 commit f5b9a34

5 files changed

Lines changed: 143 additions & 52 deletions

File tree

src/nvme/filters.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,9 @@ int nvme_scan_ctrl_namespaces(nvme_ctrl_t c, struct dirent ***ns)
105105
return scandir(nvme_ctrl_get_sysfs_dir(c), ns,
106106
nvme_namespace_filter, alphasort);
107107
}
108+
109+
int nvme_scan_ns_head_paths(nvme_ns_head_t head, struct dirent ***paths)
110+
{
111+
return scandir(nvme_ns_head_get_sysfs_dir(head), paths,
112+
nvme_paths_filter, alphasort);
113+
}

src/nvme/filters.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,13 @@ int nvme_scan_ctrl_namespace_paths(nvme_ctrl_t c, struct dirent ***paths);
9494
*/
9595
int nvme_scan_ctrl_namespaces(nvme_ctrl_t c, struct dirent ***ns);
9696

97+
/**
98+
* nvme_scan_ns_head_paths() - Scan for namespace paths
99+
* @head: Namespace head node to scan
100+
* @paths : Pointer to array of dirents
101+
*
102+
* Return: number of entries in @ents
103+
*/
104+
int nvme_scan_ns_head_paths(nvme_ns_head_t head, struct dirent ***paths);
105+
97106
#endif /* _LIBNVME_FILTERS_H */

src/nvme/private.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,19 @@ struct nvme_path {
3636
int grpid;
3737
};
3838

39+
struct nvme_ns_head {
40+
struct list_head paths;
41+
struct nvme_ns *n;
42+
43+
char *sysfs_dir;
44+
};
45+
3946
struct nvme_ns {
4047
struct list_node entry;
41-
struct list_head paths;
4248

4349
struct nvme_subsystem *s;
4450
struct nvme_ctrl *c;
51+
struct nvme_ns_head *head;
4552

4653
int fd;
4754
__u32 nsid;

src/nvme/tree.c

Lines changed: 111 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -569,12 +569,12 @@ nvme_ns_t nvme_subsystem_next_ns(nvme_subsystem_t s, nvme_ns_t n)
569569

570570
nvme_path_t nvme_namespace_first_path(nvme_ns_t ns)
571571
{
572-
return list_top(&ns->paths, struct nvme_path, nentry);
572+
return list_top(&ns->head->paths, struct nvme_path, nentry);
573573
}
574574

575575
nvme_path_t nvme_namespace_next_path(nvme_ns_t ns, nvme_path_t p)
576576
{
577-
return p ? list_next(&ns->paths, p, nentry) : NULL;
577+
return p ? list_next(&ns->head->paths, p, nentry) : NULL;
578578
}
579579

580580
static void __nvme_free_ns(struct nvme_ns *n)
@@ -584,6 +584,8 @@ static void __nvme_free_ns(struct nvme_ns *n)
584584
free(n->generic_name);
585585
free(n->name);
586586
free(n->sysfs_dir);
587+
free(n->head->sysfs_dir);
588+
free(n->head);
587589
free(n);
588590
}
589591

@@ -921,25 +923,6 @@ void nvme_free_path(struct nvme_path *p)
921923
free(p);
922924
}
923925

924-
static void nvme_subsystem_set_path_ns(nvme_subsystem_t s, nvme_path_t p)
925-
{
926-
char n_name[32] = { };
927-
int i, c, nsid, ret;
928-
nvme_ns_t n;
929-
930-
ret = sscanf(nvme_path_get_name(p), "nvme%dc%dn%d", &i, &c, &nsid);
931-
if (ret != 3)
932-
return;
933-
934-
sprintf(n_name, "nvme%dn%d", i, nsid);
935-
nvme_subsystem_for_each_ns(s, n) {
936-
if (!strcmp(n_name, nvme_ns_get_name(n))) {
937-
list_add_tail(&n->paths, &p->nentry);
938-
p->n = n;
939-
}
940-
}
941-
}
942-
943926
static int nvme_ctrl_scan_path(nvme_root_t r, struct nvme_ctrl *c, char *name)
944927
{
945928
struct nvme_path *p;
@@ -978,7 +961,6 @@ static int nvme_ctrl_scan_path(nvme_root_t r, struct nvme_ctrl *c, char *name)
978961
}
979962

980963
list_node_init(&p->nentry);
981-
nvme_subsystem_set_path_ns(c->s, p);
982964
list_node_init(&p->entry);
983965
list_add_tail(&c->paths, &p->entry);
984966
return 0;
@@ -2255,8 +2237,8 @@ nvme_ctrl_t nvme_scan_ctrl(nvme_root_t r, const char *name)
22552237
return NULL;
22562238

22572239
path = NULL;
2258-
nvme_ctrl_scan_namespaces(r, c);
22592240
nvme_ctrl_scan_paths(r, c);
2241+
nvme_ctrl_scan_namespaces(r, c);
22602242
return c;
22612243
}
22622244

@@ -2328,6 +2310,11 @@ const char *nvme_ns_get_sysfs_dir(nvme_ns_t n)
23282310
return n->sysfs_dir;
23292311
}
23302312

2313+
const char *nvme_ns_head_get_sysfs_dir(nvme_ns_head_t head)
2314+
{
2315+
return head->sysfs_dir;
2316+
}
2317+
23312318
const char *nvme_ns_get_name(nvme_ns_t n)
23322319
{
23332320
return n->name;
@@ -2754,14 +2741,44 @@ static void nvme_ns_set_generic_name(struct nvme_ns *n, const char *name)
27542741

27552742
static nvme_ns_t nvme_ns_open(const char *sys_path, const char *name)
27562743
{
2744+
int ret;
27572745
struct nvme_ns *n;
2746+
struct nvme_ns_head *head;
2747+
struct stat arg;
2748+
_cleanup_free_ char *path = NULL;
27582749

27592750
n = calloc(1, sizeof(*n));
27602751
if (!n) {
27612752
errno = ENOMEM;
27622753
return NULL;
27632754
}
27642755

2756+
head = calloc(1, sizeof(*head));
2757+
if (!head) {
2758+
errno = ENOMEM;
2759+
free(n);
2760+
return NULL;
2761+
}
2762+
2763+
head->n = n;
2764+
list_head_init(&head->paths);
2765+
ret = asprintf(&path, "%s/%s", sys_path, "multipath");
2766+
if (ret < 0) {
2767+
errno = ENOMEM;
2768+
goto free_ns_head;
2769+
}
2770+
/*
2771+
* The sysfs-dir "multipath" is available only when nvme multipath
2772+
* is configured and we're running kernel version >= 6.14.
2773+
*/
2774+
ret = stat(path, &arg);
2775+
if (ret == 0) {
2776+
head->sysfs_dir = path;
2777+
path = NULL;
2778+
} else
2779+
head->sysfs_dir = NULL;
2780+
2781+
n->head = head;
27652782
n->fd = -1;
27662783
n->name = strdup(name);
27672784

@@ -2770,15 +2787,17 @@ static nvme_ns_t nvme_ns_open(const char *sys_path, const char *name)
27702787
if (nvme_ns_init(sys_path, n) != 0)
27712788
goto free_ns;
27722789

2773-
list_head_init(&n->paths);
27742790
list_node_init(&n->entry);
27752791

27762792
nvme_ns_release_fd(n); /* Do not leak fds */
2793+
27772794
return n;
27782795

27792796
free_ns:
27802797
free(n->generic_name);
27812798
free(n->name);
2799+
free_ns_head:
2800+
free(head);
27822801
free(n);
27832802
return NULL;
27842803
}
@@ -2841,6 +2860,71 @@ nvme_ns_t nvme_scan_namespace(const char *name)
28412860
return __nvme_scan_namespace(nvme_ns_sysfs_dir(), name);
28422861
}
28432862

2863+
2864+
static void nvme_ns_head_scan_path(nvme_subsystem_t s, nvme_ns_t n, char *name)
2865+
{
2866+
nvme_ctrl_t c;
2867+
nvme_path_t p;
2868+
2869+
nvme_subsystem_for_each_ctrl(s, c) {
2870+
nvme_ctrl_for_each_path(c, p) {
2871+
if (!strcmp(nvme_path_get_name(p), name)) {
2872+
list_add_tail(&n->head->paths, &p->nentry);
2873+
p->n = n;
2874+
return;
2875+
}
2876+
}
2877+
}
2878+
}
2879+
2880+
static void nvme_subsystem_set_ns_path(nvme_subsystem_t s, nvme_ns_t n)
2881+
{
2882+
struct nvme_ns_head *head = n->head;
2883+
2884+
if (nvme_ns_head_get_sysfs_dir(head)) {
2885+
struct dirents paths = {};
2886+
int i;
2887+
2888+
/*
2889+
* When multipath is configured on kernel version >= 6.15,
2890+
* we use multipath sysfs link to get each path of a namespace.
2891+
*/
2892+
paths.num = nvme_scan_ns_head_paths(head, &paths.ents);
2893+
2894+
for (i = 0; i < paths.num; i++)
2895+
nvme_ns_head_scan_path(s, n, paths.ents[i]->d_name);
2896+
} else {
2897+
nvme_ctrl_t c;
2898+
nvme_path_t p;
2899+
int ns_ctrl, ns_nsid, ret;
2900+
2901+
/*
2902+
* If multipath is not configured or we're running on kernel
2903+
* version < 6.15, fallback to the old way.
2904+
*/
2905+
ret = sscanf(nvme_ns_get_name(n), "nvme%dn%d",
2906+
&ns_ctrl, &ns_nsid);
2907+
if (ret != 2)
2908+
return;
2909+
2910+
nvme_subsystem_for_each_ctrl(s, c) {
2911+
nvme_ctrl_for_each_path(c, p) {
2912+
int p_subsys, p_ctrl, p_nsid;
2913+
2914+
ret = sscanf(nvme_path_get_name(p),
2915+
"nvme%dc%dn%d",
2916+
&p_subsys, &p_ctrl, &p_nsid);
2917+
if (ret != 3)
2918+
continue;
2919+
if (ns_ctrl == p_subsys && ns_nsid == p_nsid) {
2920+
list_add_tail(&head->paths, &p->nentry);
2921+
p->n = n;
2922+
}
2923+
}
2924+
}
2925+
}
2926+
}
2927+
28442928
static int nvme_ctrl_scan_namespace(nvme_root_t r, struct nvme_ctrl *c,
28452929
char *name)
28462930
{
@@ -2866,33 +2950,9 @@ static int nvme_ctrl_scan_namespace(nvme_root_t r, struct nvme_ctrl *c,
28662950
n->s = c->s;
28672951
n->c = c;
28682952
list_add_tail(&c->namespaces, &n->entry);
2869-
return 0;
2870-
}
2871-
2872-
static void nvme_subsystem_set_ns_path(nvme_subsystem_t s, nvme_ns_t n)
2873-
{
2874-
nvme_ctrl_t c;
2875-
nvme_path_t p;
2876-
int ns_ctrl, ns_nsid, ret;
2877-
2878-
ret = sscanf(nvme_ns_get_name(n), "nvme%dn%d", &ns_ctrl, &ns_nsid);
2879-
if (ret != 2)
2880-
return;
2953+
nvme_subsystem_set_ns_path(c->s, n);
28812954

2882-
nvme_subsystem_for_each_ctrl(s, c) {
2883-
nvme_ctrl_for_each_path(c, p) {
2884-
int p_subsys, p_ctrl, p_nsid;
2885-
2886-
ret = sscanf(nvme_path_get_name(p), "nvme%dc%dn%d",
2887-
&p_subsys, &p_ctrl, &p_nsid);
2888-
if (ret != 3)
2889-
continue;
2890-
if (ns_ctrl == p_subsys && ns_nsid == p_nsid) {
2891-
list_add_tail(&n->paths, &p->nentry);
2892-
p->n = n;
2893-
}
2894-
}
2895-
}
2955+
return 0;
28962956
}
28972957

28982958
static int nvme_subsystem_scan_namespace(nvme_root_t r, nvme_subsystem_t s,
@@ -2922,7 +2982,7 @@ static int nvme_subsystem_scan_namespace(nvme_root_t r, nvme_subsystem_t s,
29222982
list_del_init(&p->nentry);
29232983
p->n = NULL;
29242984
}
2925-
list_head_init(&_n->paths);
2985+
list_head_init(&_n->head->paths);
29262986
__nvme_free_ns(_n);
29272987
}
29282988
n->s = s;

src/nvme/tree.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
*/
2828

2929
typedef struct nvme_ns *nvme_ns_t;
30+
typedef struct nvme_ns_head *nvme_ns_head_t;
3031
typedef struct nvme_path *nvme_path_t;
3132
typedef struct nvme_ctrl *nvme_ctrl_t;
3233
typedef struct nvme_subsystem *nvme_subsystem_t;
@@ -1091,6 +1092,14 @@ void nvme_ctrl_set_dhchap_host_key(nvme_ctrl_t c, const char *key);
10911092
*/
10921093
const char *nvme_ctrl_get_dhchap_key(nvme_ctrl_t c);
10931094

1095+
/**
1096+
* nvme_ns_head_get_sysfs_dir() - sysfs dir of namespave head
1097+
* @head: namespace head instance
1098+
*
1099+
* Returns: sysfs directory name of @head
1100+
*/
1101+
const char *nvme_ns_head_get_sysfs_dir(nvme_ns_head_t head);
1102+
10941103
/**
10951104
* nvme_ctrl_set_dhchap_key() - Set controller key
10961105
* @c: Controller for which the key should be set

0 commit comments

Comments
 (0)