|
|
da32ae |
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
|
da32ae |
From: Muneendra Kumar <muneendra.kumar@broadcom.com>
|
|
|
da32ae |
Date: Wed, 9 Feb 2022 19:28:10 -0800
|
|
|
da32ae |
Subject: [PATCH] multipathd: handle fpin events
|
|
|
da32ae |
|
|
|
da32ae |
This patch incorporates the functionality to handle
|
|
|
da32ae |
FPIN ELS events present as part of FCTransport daemon
|
|
|
da32ae |
(available in EPEL8) into the multipathd. This helps us to
|
|
|
da32ae |
reduce the response time to react and take the necessary actions
|
|
|
da32ae |
on receiving the FPIN events.
|
|
|
da32ae |
|
|
|
da32ae |
This patch currently support FPIN-Li Events.
|
|
|
da32ae |
|
|
|
da32ae |
It adds a new thread to listen for ELS frames from driver and on
|
|
|
da32ae |
receiving the frame payload, push the payload to a list and notify the
|
|
|
da32ae |
fpin_els_li_consumer thread to process it.Once consumer thread is
|
|
|
da32ae |
notified, it returns to listen for more ELS frames from driver.
|
|
|
da32ae |
|
|
|
da32ae |
The consumer thread process the ELS frames and moves the devices paths
|
|
|
da32ae |
which are affected due to link integrity to marginal path groups.
|
|
|
da32ae |
This also sets the associated portstate to marginal.
|
|
|
da32ae |
The paths which are set to marginal path group will be unset
|
|
|
da32ae |
on receiving the RSCN events
|
|
|
da32ae |
|
|
|
da32ae |
[ MW: minor fixup for 32bit compilation ]
|
|
|
da32ae |
|
|
|
da32ae |
Signed-off-by: Muneendra Kumar <muneendra.kumar@broadcom.com>
|
|
|
da32ae |
Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>
|
|
|
da32ae |
Signed-off-by: Martin Wilck <mwilck@suse.com>
|
|
|
da32ae |
Reviewed-by: Martin Wilck <mwilck@suse.com>
|
|
|
da32ae |
---
|
|
|
da32ae |
Makefile.inc | 13 +
|
|
|
da32ae |
libmultipath/Makefile | 5 +
|
|
|
da32ae |
libmultipath/dict.c | 56 +++-
|
|
|
da32ae |
libmultipath/libmultipath.version | 5 +
|
|
|
da32ae |
libmultipath/propsel.c | 47 ++-
|
|
|
da32ae |
libmultipath/structs.h | 7 +
|
|
|
da32ae |
multipath/multipath.conf.5 | 19 +-
|
|
|
da32ae |
multipathd/Makefile | 10 +
|
|
|
da32ae |
multipathd/fpin.h | 20 ++
|
|
|
da32ae |
multipathd/fpin_handlers.c | 540 ++++++++++++++++++++++++++++++
|
|
|
da32ae |
multipathd/main.c | 43 ++-
|
|
|
da32ae |
11 files changed, 749 insertions(+), 16 deletions(-)
|
|
|
da32ae |
create mode 100644 multipathd/fpin.h
|
|
|
da32ae |
create mode 100644 multipathd/fpin_handlers.c
|
|
|
da32ae |
|
|
|
da32ae |
diff --git a/Makefile.inc b/Makefile.inc
|
|
|
da32ae |
index 5ac660de..688c4599 100644
|
|
|
da32ae |
--- a/Makefile.inc
|
|
|
da32ae |
+++ b/Makefile.inc
|
|
|
da32ae |
@@ -149,6 +149,19 @@ check_file = $(shell \
|
|
|
da32ae |
echo "$$found" \
|
|
|
da32ae |
)
|
|
|
da32ae |
|
|
|
da32ae |
+# Check whether a file contains a variable with name $1 in header file $2
|
|
|
da32ae |
+check_var = $(shell \
|
|
|
da32ae |
+ if grep -Eq "(^|[[:blank:]])$1([[:blank:]]|=|$$)" "$2"; then \
|
|
|
da32ae |
+ found=1; \
|
|
|
da32ae |
+ status="yes"; \
|
|
|
da32ae |
+ else \
|
|
|
da32ae |
+ found=0; \
|
|
|
da32ae |
+ status="no"; \
|
|
|
da32ae |
+ fi; \
|
|
|
da32ae |
+ echo 1>&2 "Checking for .. $1 in $2 ... $$status"; \
|
|
|
da32ae |
+ echo "$$found" \
|
|
|
da32ae |
+ )
|
|
|
da32ae |
+
|
|
|
da32ae |
%.o: %.c
|
|
|
da32ae |
@echo building $@ because of $?
|
|
|
da32ae |
$(CC) $(CFLAGS) -c -o $@ $<
|
|
|
da32ae |
diff --git a/libmultipath/Makefile b/libmultipath/Makefile
|
|
|
da32ae |
index 7f3921c5..8a960419 100644
|
|
|
da32ae |
--- a/libmultipath/Makefile
|
|
|
da32ae |
+++ b/libmultipath/Makefile
|
|
|
da32ae |
@@ -45,6 +45,11 @@ ifneq ($(call check_func,dm_hold_control_dev,/usr/include/libdevmapper.h),0)
|
|
|
da32ae |
CFLAGS += -DLIBDM_API_HOLD_CONTROL
|
|
|
da32ae |
endif
|
|
|
da32ae |
|
|
|
da32ae |
+ifneq ($(call check_var,ELS_DTAG_LNK_INTEGRITY,/usr/include/scsi/fc/fc_els.h),0)
|
|
|
da32ae |
+ CFLAGS += -DFPIN_EVENT_HANDLER
|
|
|
da32ae |
+endif
|
|
|
da32ae |
+
|
|
|
da32ae |
+
|
|
|
da32ae |
OBJS = memory.o parser.o vector.o devmapper.o callout.o \
|
|
|
da32ae |
hwtable.o blacklist.o util.o dmparser.o config.o \
|
|
|
da32ae |
structs.o discovery.o propsel.o dict.o \
|
|
|
da32ae |
diff --git a/libmultipath/dict.c b/libmultipath/dict.c
|
|
|
da32ae |
index 1b75be47..eb5a8083 100644
|
|
|
da32ae |
--- a/libmultipath/dict.c
|
|
|
da32ae |
+++ b/libmultipath/dict.c
|
|
|
da32ae |
@@ -513,6 +513,59 @@ snprint_def_find_multipaths(struct config *conf, struct strbuf *buff,
|
|
|
da32ae |
find_multipaths_optvals[conf->find_multipaths]);
|
|
|
da32ae |
}
|
|
|
da32ae |
|
|
|
da32ae |
+static const char * const marginal_pathgroups_optvals[] = {
|
|
|
da32ae |
+ [MARGINAL_PATHGROUP_OFF] = "off",
|
|
|
da32ae |
+ [MARGINAL_PATHGROUP_ON] = "on",
|
|
|
da32ae |
+#ifdef FPIN_EVENT_HANDLER
|
|
|
da32ae |
+ [MARGINAL_PATHGROUP_FPIN] = "fpin",
|
|
|
da32ae |
+#endif
|
|
|
da32ae |
+};
|
|
|
da32ae |
+
|
|
|
da32ae |
+static int
|
|
|
da32ae |
+def_marginal_pathgroups_handler(struct config *conf, vector strvec,
|
|
|
da32ae |
+ const char *file, int line_nr)
|
|
|
da32ae |
+{
|
|
|
da32ae |
+ char *buff;
|
|
|
da32ae |
+ unsigned int i;
|
|
|
da32ae |
+
|
|
|
da32ae |
+ buff = set_value(strvec);
|
|
|
da32ae |
+ if (!buff)
|
|
|
da32ae |
+ return 1;
|
|
|
da32ae |
+ for (i = MARGINAL_PATHGROUP_OFF;
|
|
|
da32ae |
+ i < ARRAY_SIZE(marginal_pathgroups_optvals); i++) {
|
|
|
da32ae |
+ if (marginal_pathgroups_optvals[i] != NULL &&
|
|
|
da32ae |
+ !strcmp(buff, marginal_pathgroups_optvals[i])) {
|
|
|
da32ae |
+ conf->marginal_pathgroups = i;
|
|
|
da32ae |
+ break;
|
|
|
da32ae |
+ }
|
|
|
da32ae |
+ }
|
|
|
da32ae |
+
|
|
|
da32ae |
+ if (i >= ARRAY_SIZE(marginal_pathgroups_optvals)) {
|
|
|
da32ae |
+ if (strcmp(buff, "no") == 0 || strcmp(buff, "0") == 0)
|
|
|
da32ae |
+ conf->marginal_pathgroups = MARGINAL_PATHGROUP_OFF;
|
|
|
da32ae |
+ else if (strcmp(buff, "yes") == 0 || strcmp(buff, "1") == 0)
|
|
|
da32ae |
+ conf->marginal_pathgroups = MARGINAL_PATHGROUP_ON;
|
|
|
da32ae |
+ /* This can only be true if FPIN_EVENT_HANDLER isn't defined,
|
|
|
da32ae |
+ * otherwise this check will have already happened above */
|
|
|
da32ae |
+ else if (strcmp(buff, "fpin") == 0)
|
|
|
da32ae |
+ condlog(1, "%s line %d, support for \"fpin\" is not compiled in for marginal_pathgroups", file, line_nr);
|
|
|
da32ae |
+ else
|
|
|
da32ae |
+ condlog(1, "%s line %d, invalid value for marginal_pathgroups: \"%s\"",
|
|
|
da32ae |
+ file, line_nr, buff);
|
|
|
da32ae |
+ }
|
|
|
da32ae |
+ free(buff);
|
|
|
da32ae |
+ return 0;
|
|
|
da32ae |
+}
|
|
|
da32ae |
+
|
|
|
da32ae |
+static int
|
|
|
da32ae |
+snprint_def_marginal_pathgroups(struct config *conf, struct strbuf *buff,
|
|
|
da32ae |
+ const void *data)
|
|
|
da32ae |
+{
|
|
|
da32ae |
+ return append_strbuf_quoted(buff,
|
|
|
da32ae |
+ marginal_pathgroups_optvals[conf->marginal_pathgroups]);
|
|
|
da32ae |
+}
|
|
|
da32ae |
+
|
|
|
da32ae |
+
|
|
|
da32ae |
declare_def_handler(selector, set_str)
|
|
|
da32ae |
declare_def_snprint_defstr(selector, print_str, DEFAULT_SELECTOR)
|
|
|
da32ae |
declare_hw_handler(selector, set_str)
|
|
|
da32ae |
@@ -1527,9 +1580,6 @@ declare_ovr_snprint(all_tg_pt, print_yes_no_undef)
|
|
|
da32ae |
declare_hw_handler(all_tg_pt, set_yes_no_undef)
|
|
|
da32ae |
declare_hw_snprint(all_tg_pt, print_yes_no_undef)
|
|
|
da32ae |
|
|
|
da32ae |
-declare_def_handler(marginal_pathgroups, set_yes_no)
|
|
|
da32ae |
-declare_def_snprint(marginal_pathgroups, print_yes_no)
|
|
|
da32ae |
-
|
|
|
da32ae |
declare_def_handler(recheck_wwid, set_yes_no_undef)
|
|
|
da32ae |
declare_def_snprint_defint(recheck_wwid, print_yes_no_undef, DEFAULT_RECHECK_WWID)
|
|
|
da32ae |
declare_ovr_handler(recheck_wwid, set_yes_no_undef)
|
|
|
da32ae |
diff --git a/libmultipath/libmultipath.version b/libmultipath/libmultipath.version
|
|
|
da32ae |
index 0d89e9e1..1d018eab 100644
|
|
|
da32ae |
--- a/libmultipath/libmultipath.version
|
|
|
da32ae |
+++ b/libmultipath/libmultipath.version
|
|
|
da32ae |
@@ -297,3 +297,8 @@ LIBMULTIPATH_9.1.1 {
|
|
|
da32ae |
global:
|
|
|
da32ae |
trigger_path_udev_change;
|
|
|
da32ae |
} LIBMULTIPATH_9.1.0;
|
|
|
da32ae |
+
|
|
|
da32ae |
+LIBMULTIPATH_9.1.2 {
|
|
|
da32ae |
+global:
|
|
|
da32ae |
+ cleanup_mutex;
|
|
|
da32ae |
+} LIBMULTIPATH_9.1.1;
|
|
|
da32ae |
diff --git a/libmultipath/propsel.c b/libmultipath/propsel.c
|
|
|
da32ae |
index b2876670..677ab9e1 100644
|
|
|
da32ae |
--- a/libmultipath/propsel.c
|
|
|
da32ae |
+++ b/libmultipath/propsel.c
|
|
|
da32ae |
@@ -84,6 +84,8 @@ static const char cmdline_origin[] =
|
|
|
da32ae |
"(setting: multipath command line [-p] flag)";
|
|
|
da32ae |
static const char autodetect_origin[] =
|
|
|
da32ae |
"(setting: storage device autodetected)";
|
|
|
da32ae |
+static const char fpin_marginal_path_origin[] =
|
|
|
da32ae |
+ "(setting: overridden by marginal_path_fpin)";
|
|
|
da32ae |
static const char marginal_path_origin[] =
|
|
|
da32ae |
"(setting: implied by marginal_path check)";
|
|
|
da32ae |
static const char delay_watch_origin[] =
|
|
|
da32ae |
@@ -1036,9 +1038,12 @@ int select_san_path_err_threshold(struct config *conf, struct multipath *mp)
|
|
|
da32ae |
const char *origin;
|
|
|
da32ae |
STRBUF_ON_STACK(buff);
|
|
|
da32ae |
|
|
|
da32ae |
- if (marginal_path_check_enabled(mp)) {
|
|
|
da32ae |
+ if (marginal_path_check_enabled(mp) || (conf->marginal_pathgroups == MARGINAL_PATHGROUP_FPIN)) {
|
|
|
da32ae |
mp->san_path_err_threshold = NU_NO;
|
|
|
da32ae |
- origin = marginal_path_origin;
|
|
|
da32ae |
+ if (conf->marginal_pathgroups == MARGINAL_PATHGROUP_FPIN)
|
|
|
da32ae |
+ origin = fpin_marginal_path_origin;
|
|
|
da32ae |
+ else
|
|
|
da32ae |
+ origin = marginal_path_origin;
|
|
|
da32ae |
goto out;
|
|
|
da32ae |
}
|
|
|
da32ae |
mp_set_mpe(san_path_err_threshold);
|
|
|
da32ae |
@@ -1059,9 +1064,12 @@ int select_san_path_err_forget_rate(struct config *conf, struct multipath *mp)
|
|
|
da32ae |
const char *origin;
|
|
|
da32ae |
STRBUF_ON_STACK(buff);
|
|
|
da32ae |
|
|
|
da32ae |
- if (marginal_path_check_enabled(mp)) {
|
|
|
da32ae |
+ if (marginal_path_check_enabled(mp) || (conf->marginal_pathgroups == MARGINAL_PATHGROUP_FPIN)) {
|
|
|
da32ae |
mp->san_path_err_forget_rate = NU_NO;
|
|
|
da32ae |
- origin = marginal_path_origin;
|
|
|
da32ae |
+ if (conf->marginal_pathgroups == MARGINAL_PATHGROUP_FPIN)
|
|
|
da32ae |
+ origin = fpin_marginal_path_origin;
|
|
|
da32ae |
+ else
|
|
|
da32ae |
+ origin = marginal_path_origin;
|
|
|
da32ae |
goto out;
|
|
|
da32ae |
}
|
|
|
da32ae |
mp_set_mpe(san_path_err_forget_rate);
|
|
|
da32ae |
@@ -1083,9 +1091,12 @@ int select_san_path_err_recovery_time(struct config *conf, struct multipath *mp)
|
|
|
da32ae |
const char *origin;
|
|
|
da32ae |
STRBUF_ON_STACK(buff);
|
|
|
da32ae |
|
|
|
da32ae |
- if (marginal_path_check_enabled(mp)) {
|
|
|
da32ae |
+ if (marginal_path_check_enabled(mp) || (conf->marginal_pathgroups == MARGINAL_PATHGROUP_FPIN)) {
|
|
|
da32ae |
mp->san_path_err_recovery_time = NU_NO;
|
|
|
da32ae |
- origin = marginal_path_origin;
|
|
|
da32ae |
+ if (conf->marginal_pathgroups == MARGINAL_PATHGROUP_FPIN)
|
|
|
da32ae |
+ origin = fpin_marginal_path_origin;
|
|
|
da32ae |
+ else
|
|
|
da32ae |
+ origin = marginal_path_origin;
|
|
|
da32ae |
goto out;
|
|
|
da32ae |
}
|
|
|
da32ae |
mp_set_mpe(san_path_err_recovery_time);
|
|
|
da32ae |
@@ -1107,6 +1118,12 @@ int select_marginal_path_err_sample_time(struct config *conf, struct multipath *
|
|
|
da32ae |
const char *origin;
|
|
|
da32ae |
STRBUF_ON_STACK(buff);
|
|
|
da32ae |
|
|
|
da32ae |
+ if (conf->marginal_pathgroups == MARGINAL_PATHGROUP_FPIN) {
|
|
|
da32ae |
+ mp->marginal_path_err_sample_time = NU_NO;
|
|
|
da32ae |
+ origin = fpin_marginal_path_origin;
|
|
|
da32ae |
+ goto out;
|
|
|
da32ae |
+ }
|
|
|
da32ae |
+
|
|
|
da32ae |
mp_set_mpe(marginal_path_err_sample_time);
|
|
|
da32ae |
mp_set_ovr(marginal_path_err_sample_time);
|
|
|
da32ae |
mp_set_hwe(marginal_path_err_sample_time);
|
|
|
da32ae |
@@ -1130,6 +1147,12 @@ int select_marginal_path_err_rate_threshold(struct config *conf, struct multipat
|
|
|
da32ae |
const char *origin;
|
|
|
da32ae |
STRBUF_ON_STACK(buff);
|
|
|
da32ae |
|
|
|
da32ae |
+ if (conf->marginal_pathgroups == MARGINAL_PATHGROUP_FPIN) {
|
|
|
da32ae |
+ mp->marginal_path_err_rate_threshold = NU_NO;
|
|
|
da32ae |
+ origin = fpin_marginal_path_origin;
|
|
|
da32ae |
+ goto out;
|
|
|
da32ae |
+ }
|
|
|
da32ae |
+
|
|
|
da32ae |
mp_set_mpe(marginal_path_err_rate_threshold);
|
|
|
da32ae |
mp_set_ovr(marginal_path_err_rate_threshold);
|
|
|
da32ae |
mp_set_hwe(marginal_path_err_rate_threshold);
|
|
|
da32ae |
@@ -1147,6 +1170,12 @@ int select_marginal_path_err_recheck_gap_time(struct config *conf, struct multip
|
|
|
da32ae |
const char *origin;
|
|
|
da32ae |
STRBUF_ON_STACK(buff);
|
|
|
da32ae |
|
|
|
da32ae |
+ if (conf->marginal_pathgroups == MARGINAL_PATHGROUP_FPIN) {
|
|
|
da32ae |
+ mp->marginal_path_err_recheck_gap_time = NU_NO;
|
|
|
da32ae |
+ origin = fpin_marginal_path_origin;
|
|
|
da32ae |
+ goto out;
|
|
|
da32ae |
+ }
|
|
|
da32ae |
+
|
|
|
da32ae |
mp_set_mpe(marginal_path_err_recheck_gap_time);
|
|
|
da32ae |
mp_set_ovr(marginal_path_err_recheck_gap_time);
|
|
|
da32ae |
mp_set_hwe(marginal_path_err_recheck_gap_time);
|
|
|
da32ae |
@@ -1165,6 +1194,12 @@ int select_marginal_path_double_failed_time(struct config *conf, struct multipat
|
|
|
da32ae |
const char *origin;
|
|
|
da32ae |
STRBUF_ON_STACK(buff);
|
|
|
da32ae |
|
|
|
da32ae |
+ if (conf->marginal_pathgroups == MARGINAL_PATHGROUP_FPIN) {
|
|
|
da32ae |
+ mp->marginal_path_double_failed_time = NU_NO;
|
|
|
da32ae |
+ origin = fpin_marginal_path_origin;
|
|
|
da32ae |
+ goto out;
|
|
|
da32ae |
+ }
|
|
|
da32ae |
+
|
|
|
da32ae |
mp_set_mpe(marginal_path_double_failed_time);
|
|
|
da32ae |
mp_set_ovr(marginal_path_double_failed_time);
|
|
|
da32ae |
mp_set_hwe(marginal_path_double_failed_time);
|
|
|
da32ae |
diff --git a/libmultipath/structs.h b/libmultipath/structs.h
|
|
|
da32ae |
index 399540e7..1188363e 100644
|
|
|
da32ae |
--- a/libmultipath/structs.h
|
|
|
da32ae |
+++ b/libmultipath/structs.h
|
|
|
da32ae |
@@ -110,6 +110,12 @@ enum find_multipaths_states {
|
|
|
da32ae |
__FIND_MULTIPATHS_LAST,
|
|
|
da32ae |
};
|
|
|
da32ae |
|
|
|
da32ae |
+enum marginal_pathgroups_mode {
|
|
|
da32ae |
+ MARGINAL_PATHGROUP_OFF = YN_NO,
|
|
|
da32ae |
+ MARGINAL_PATHGROUP_ON = YN_YES,
|
|
|
da32ae |
+ MARGINAL_PATHGROUP_FPIN,
|
|
|
da32ae |
+};
|
|
|
da32ae |
+
|
|
|
da32ae |
enum flush_states {
|
|
|
da32ae |
FLUSH_UNDEF = YNU_UNDEF,
|
|
|
da32ae |
FLUSH_DISABLED = YNU_NO,
|
|
|
da32ae |
@@ -409,6 +415,7 @@ struct multipath {
|
|
|
da32ae |
unsigned char prflag;
|
|
|
da32ae |
int all_tg_pt;
|
|
|
da32ae |
struct gen_multipath generic_mp;
|
|
|
da32ae |
+ bool fpin_must_reload;
|
|
|
da32ae |
};
|
|
|
da32ae |
|
|
|
da32ae |
static inline int marginal_path_check_enabled(const struct multipath *mpp)
|
|
|
da32ae |
diff --git a/multipath/multipath.conf.5 b/multipath/multipath.conf.5
|
|
|
da32ae |
index 7f85f766..5ed2cd3c 100644
|
|
|
da32ae |
--- a/multipath/multipath.conf.5
|
|
|
da32ae |
+++ b/multipath/multipath.conf.5
|
|
|
da32ae |
@@ -1088,20 +1088,26 @@ The default is: \fBno\fR
|
|
|
da32ae |
.
|
|
|
da32ae |
.TP
|
|
|
da32ae |
.B marginal_pathgroups
|
|
|
da32ae |
-If set to \fIno\fR, the \fIdelay_*_checks\fR, \fImarginal_path_*\fR, and
|
|
|
da32ae |
+If set to \fIoff\fR, the \fIdelay_*_checks\fR, \fImarginal_path_*\fR, and
|
|
|
da32ae |
\fIsan_path_err_*\fR options will keep marginal, or \(dqshaky\(dq, paths from
|
|
|
da32ae |
being reinstated until they have been monitored for some time. This can cause
|
|
|
da32ae |
situations where all non-marginal paths are down, and no paths are usable
|
|
|
da32ae |
until multipathd detects this and reinstates a marginal path. If the multipath
|
|
|
da32ae |
device is not configured to queue IO in this case, it can cause IO errors to
|
|
|
da32ae |
occur, even though there are marginal paths available. However, if this
|
|
|
da32ae |
-option is set to \fIyes\fR, when one of the marginal path detecting methods
|
|
|
da32ae |
+option is set to \fIon\fR, when one of the marginal path detecting methods
|
|
|
da32ae |
determines that a path is marginal, it will be reinstated and placed in a
|
|
|
da32ae |
seperate pathgroup that will only be used after all the non-marginal pathgroups
|
|
|
da32ae |
have been tried first. This prevents the possibility of IO errors occuring
|
|
|
da32ae |
while marginal paths are still usable. After the path has been monitored
|
|
|
da32ae |
for the configured time, and is declared healthy, it will be returned to its
|
|
|
da32ae |
-normal pathgroup. See "Shaky paths detection" below for more information.
|
|
|
da32ae |
+normal pathgroup.
|
|
|
da32ae |
+However if this option is set to \fIfpin\fR multipathd will receive fpin
|
|
|
da32ae |
+notifications, set path states to "marginal" accordingly, and regroup paths
|
|
|
da32ae |
+as described for "marginal_pathgroups yes". This option can't be used in combination
|
|
|
da32ae |
+with other options for "Shaky path detection" (see below).If it is set to fpin,
|
|
|
da32ae |
+marginal_path_xyz and san_path_err_xyz parameters are implicitly set to 0.
|
|
|
da32ae |
+See "Shaky paths detection" below for more information.
|
|
|
da32ae |
.RS
|
|
|
da32ae |
.TP
|
|
|
da32ae |
The default is: \fBno\fR
|
|
|
da32ae |
@@ -1842,6 +1848,13 @@ increase and the threshold is never reached. Ticks are the time between
|
|
|
da32ae |
path checks by multipathd, which is variable and controlled by the
|
|
|
da32ae |
\fIpolling_interval\fR and \fImax_polling_interval\fR parameters.
|
|
|
da32ae |
.
|
|
|
da32ae |
+.TP
|
|
|
da32ae |
+.B \(dqFPIN \(dq failure tracking
|
|
|
da32ae |
+Fibre channel fabrics can notify hosts about fabric-level issues such
|
|
|
da32ae |
+as integrity failures or congestion with so-called Fabric Performance
|
|
|
da32ae |
+Impact Notifications (FPINs).On receiving the fpin notifications through ELS
|
|
|
da32ae |
+multipathd will move the affected path and port states to marginal.
|
|
|
da32ae |
+.
|
|
|
da32ae |
.RS 8
|
|
|
da32ae |
.LP
|
|
|
da32ae |
This method is \fBdeprecated\fR in favor of the \(dqmarginal_path\(dq failure
|
|
|
da32ae |
diff --git a/multipathd/Makefile b/multipathd/Makefile
|
|
|
da32ae |
index 393b6cbb..cd6f7e6d 100644
|
|
|
da32ae |
--- a/multipathd/Makefile
|
|
|
da32ae |
+++ b/multipathd/Makefile
|
|
|
da32ae |
@@ -4,6 +4,10 @@ ifneq ($(call check_func,dm_task_get_errno,/usr/include/libdevmapper.h),0)
|
|
|
da32ae |
CFLAGS += -DLIBDM_API_GET_ERRNO
|
|
|
da32ae |
endif
|
|
|
da32ae |
|
|
|
da32ae |
+ifneq ($(call check_var,ELS_DTAG_LNK_INTEGRITY,/usr/include/scsi/fc/fc_els.h),0)
|
|
|
da32ae |
+ CFLAGS += -DFPIN_EVENT_HANDLER
|
|
|
da32ae |
+ FPIN_SUPPORT = 1
|
|
|
da32ae |
+endif
|
|
|
da32ae |
#
|
|
|
da32ae |
# debugging stuff
|
|
|
da32ae |
#
|
|
|
da32ae |
@@ -34,6 +38,12 @@ endif
|
|
|
da32ae |
OBJS = main.o pidfile.o uxlsnr.o uxclnt.o cli.o cli_handlers.o waiter.o \
|
|
|
da32ae |
dmevents.o init_unwinder.o
|
|
|
da32ae |
|
|
|
da32ae |
+ifeq ($(FPIN_SUPPORT),1)
|
|
|
da32ae |
+OBJS += fpin_handlers.o
|
|
|
da32ae |
+endif
|
|
|
da32ae |
+
|
|
|
da32ae |
+
|
|
|
da32ae |
+
|
|
|
da32ae |
EXEC = multipathd
|
|
|
da32ae |
|
|
|
da32ae |
all : $(EXEC)
|
|
|
da32ae |
diff --git a/multipathd/fpin.h b/multipathd/fpin.h
|
|
|
da32ae |
new file mode 100644
|
|
|
da32ae |
index 00000000..bfcc1ce2
|
|
|
da32ae |
--- /dev/null
|
|
|
da32ae |
+++ b/multipathd/fpin.h
|
|
|
da32ae |
@@ -0,0 +1,20 @@
|
|
|
da32ae |
+#ifndef __FPIN_H__
|
|
|
da32ae |
+#define __FPIN_H__
|
|
|
da32ae |
+
|
|
|
da32ae |
+#ifdef FPIN_EVENT_HANDLER
|
|
|
da32ae |
+void *fpin_fabric_notification_receiver(void *unused);
|
|
|
da32ae |
+void *fpin_els_li_consumer(void *data);
|
|
|
da32ae |
+void fpin_clean_marginal_dev_list(__attribute__((unused)) void *arg);
|
|
|
da32ae |
+#else
|
|
|
da32ae |
+static void *fpin_fabric_notification_receiver(__attribute__((unused))void *unused)
|
|
|
da32ae |
+{
|
|
|
da32ae |
+ return NULL;
|
|
|
da32ae |
+}
|
|
|
da32ae |
+static void *fpin_els_li_consumer(__attribute__((unused))void *data)
|
|
|
da32ae |
+{
|
|
|
da32ae |
+ return NULL;
|
|
|
da32ae |
+}
|
|
|
da32ae |
+/* fpin_clean_marginal_dev_list() is never called */
|
|
|
da32ae |
+#endif
|
|
|
da32ae |
+
|
|
|
da32ae |
+#endif
|
|
|
da32ae |
diff --git a/multipathd/fpin_handlers.c b/multipathd/fpin_handlers.c
|
|
|
da32ae |
new file mode 100644
|
|
|
da32ae |
index 00000000..aaf5655d
|
|
|
da32ae |
--- /dev/null
|
|
|
da32ae |
+++ b/multipathd/fpin_handlers.c
|
|
|
da32ae |
@@ -0,0 +1,540 @@
|
|
|
da32ae |
+#include <errno.h>
|
|
|
da32ae |
+#include <unistd.h>
|
|
|
da32ae |
+#include <sys/types.h>
|
|
|
da32ae |
+#include <sys/socket.h>
|
|
|
da32ae |
+#include <libudev.h>
|
|
|
da32ae |
+#include <scsi/scsi_netlink_fc.h>
|
|
|
da32ae |
+#include <scsi/fc/fc_els.h>
|
|
|
da32ae |
+
|
|
|
da32ae |
+#include "parser.h"
|
|
|
da32ae |
+#include "vector.h"
|
|
|
da32ae |
+#include "structs.h"
|
|
|
da32ae |
+#include "structs_vec.h"
|
|
|
da32ae |
+#include "main.h"
|
|
|
da32ae |
+#include "debug.h"
|
|
|
da32ae |
+#include "util.h"
|
|
|
da32ae |
+#include "sysfs.h"
|
|
|
da32ae |
+
|
|
|
da32ae |
+#include "fpin.h"
|
|
|
da32ae |
+#include "devmapper.h"
|
|
|
da32ae |
+
|
|
|
da32ae |
+static pthread_cond_t fpin_li_cond = PTHREAD_COND_INITIALIZER;
|
|
|
da32ae |
+static pthread_mutex_t fpin_li_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
|
da32ae |
+static pthread_mutex_t fpin_li_marginal_dev_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
|
da32ae |
+
|
|
|
da32ae |
+static LIST_HEAD(els_marginal_list_head);
|
|
|
da32ae |
+static LIST_HEAD(fpin_li_marginal_dev_list_head);
|
|
|
da32ae |
+
|
|
|
da32ae |
+
|
|
|
da32ae |
+#define DEF_RX_BUF_SIZE 4096
|
|
|
da32ae |
+#define DEV_NAME_LEN 128
|
|
|
da32ae |
+#define FCH_EVT_LINKUP 0x2
|
|
|
da32ae |
+#define FCH_EVT_LINK_FPIN 0x501
|
|
|
da32ae |
+#define FCH_EVT_RSCN 0x5
|
|
|
da32ae |
+
|
|
|
da32ae |
+#define list_first_entry(ptr, type, member) \
|
|
|
da32ae |
+ list_entry((ptr)->next, type, member)
|
|
|
da32ae |
+
|
|
|
da32ae |
+/* max ELS frame Size */
|
|
|
da32ae |
+#define FC_PAYLOAD_MAXLEN 2048
|
|
|
da32ae |
+
|
|
|
da32ae |
+struct els_marginal_list {
|
|
|
da32ae |
+ uint32_t event_code;
|
|
|
da32ae |
+ uint16_t host_num;
|
|
|
da32ae |
+ uint16_t length;
|
|
|
da32ae |
+ char payload[FC_PAYLOAD_MAXLEN];
|
|
|
da32ae |
+ struct list_head node;
|
|
|
da32ae |
+};
|
|
|
da32ae |
+/* Structure to store the marginal devices info */
|
|
|
da32ae |
+struct marginal_dev_list {
|
|
|
da32ae |
+ char dev_t[BLK_DEV_SIZE];
|
|
|
da32ae |
+ uint32_t host_num;
|
|
|
da32ae |
+ struct list_head node;
|
|
|
da32ae |
+};
|
|
|
da32ae |
+
|
|
|
da32ae |
+static void _udev_device_unref(void *p)
|
|
|
da32ae |
+{
|
|
|
da32ae |
+ udev_device_unref(p);
|
|
|
da32ae |
+}
|
|
|
da32ae |
+
|
|
|
da32ae |
+
|
|
|
da32ae |
+/*set/unset the path state to marginal*/
|
|
|
da32ae |
+static int fpin_set_pathstate(struct path *pp, bool set)
|
|
|
da32ae |
+{
|
|
|
da32ae |
+ const char *action = set ? "set" : "unset";
|
|
|
da32ae |
+
|
|
|
da32ae |
+ if (!pp || !pp->mpp || !pp->mpp->alias)
|
|
|
da32ae |
+ return -1;
|
|
|
da32ae |
+
|
|
|
da32ae |
+ condlog(3, "\n%s: %s marginal path %s (fpin)",
|
|
|
da32ae |
+ action, pp->mpp->alias, pp->dev_t);
|
|
|
da32ae |
+ pp->marginal = set;
|
|
|
da32ae |
+ pp->mpp->fpin_must_reload = true;
|
|
|
da32ae |
+ return 0;
|
|
|
da32ae |
+}
|
|
|
da32ae |
+
|
|
|
da32ae |
+/* This will unset marginal state of a device*/
|
|
|
da32ae |
+static void fpin_path_unsetmarginal(char *devname, struct vectors *vecs)
|
|
|
da32ae |
+{
|
|
|
da32ae |
+ struct path *pp;
|
|
|
da32ae |
+
|
|
|
da32ae |
+ pp = find_path_by_dev(vecs->pathvec, devname);
|
|
|
da32ae |
+ if (!pp)
|
|
|
da32ae |
+ pp = find_path_by_devt(vecs->pathvec, devname);
|
|
|
da32ae |
+
|
|
|
da32ae |
+ fpin_set_pathstate(pp, false);
|
|
|
da32ae |
+}
|
|
|
da32ae |
+
|
|
|
da32ae |
+/*This will set the marginal state of a device*/
|
|
|
da32ae |
+static int fpin_path_setmarginal(struct path *pp)
|
|
|
da32ae |
+{
|
|
|
da32ae |
+ return fpin_set_pathstate(pp, true);
|
|
|
da32ae |
+}
|
|
|
da32ae |
+
|
|
|
da32ae |
+/* Unsets all the devices in the list from marginal state */
|
|
|
da32ae |
+static void
|
|
|
da32ae |
+fpin_unset_marginal_dev(uint32_t host_num, struct vectors *vecs)
|
|
|
da32ae |
+{
|
|
|
da32ae |
+ struct marginal_dev_list *tmp_marg = NULL;
|
|
|
da32ae |
+ struct marginal_dev_list *marg = NULL;
|
|
|
da32ae |
+ struct multipath *mpp;
|
|
|
da32ae |
+ int ret = 0;
|
|
|
da32ae |
+ int i;
|
|
|
da32ae |
+
|
|
|
da32ae |
+ pthread_cleanup_push(cleanup_lock, &vecs->lock);
|
|
|
da32ae |
+ lock(&vecs->lock);
|
|
|
da32ae |
+ pthread_testcancel();
|
|
|
da32ae |
+
|
|
|
da32ae |
+ pthread_mutex_lock(&fpin_li_marginal_dev_mutex);
|
|
|
da32ae |
+ pthread_cleanup_push(cleanup_mutex, &fpin_li_marginal_dev_mutex);
|
|
|
da32ae |
+ pthread_testcancel();
|
|
|
da32ae |
+ if (list_empty(&fpin_li_marginal_dev_list_head)) {
|
|
|
da32ae |
+ condlog(4, "Marginal List is empty\n");
|
|
|
da32ae |
+ goto empty;
|
|
|
da32ae |
+ }
|
|
|
da32ae |
+ list_for_each_entry_safe(marg, tmp_marg, &fpin_li_marginal_dev_list_head, node) {
|
|
|
da32ae |
+ if (marg->host_num != host_num)
|
|
|
da32ae |
+ continue;
|
|
|
da32ae |
+ condlog(4, " unsetting marginal dev: is %s %d\n",
|
|
|
da32ae |
+ tmp_marg->dev_t, tmp_marg->host_num);
|
|
|
da32ae |
+ fpin_path_unsetmarginal(marg->dev_t, vecs);
|
|
|
da32ae |
+ list_del(&marg->node);
|
|
|
da32ae |
+ free(marg);
|
|
|
da32ae |
+ }
|
|
|
da32ae |
+empty:
|
|
|
da32ae |
+ pthread_cleanup_pop(1);
|
|
|
da32ae |
+ /* walk backwards because reload_and_sync_map() can remove mpp */
|
|
|
da32ae |
+ vector_foreach_slot_backwards(vecs->mpvec, mpp, i) {
|
|
|
da32ae |
+ if (mpp->fpin_must_reload) {
|
|
|
da32ae |
+ ret = reload_and_sync_map(mpp, vecs, 0);
|
|
|
da32ae |
+ if (ret == 2)
|
|
|
da32ae |
+ condlog(2, "map removed during reload");
|
|
|
da32ae |
+ else
|
|
|
da32ae |
+ mpp->fpin_must_reload = false;
|
|
|
da32ae |
+ }
|
|
|
da32ae |
+ }
|
|
|
da32ae |
+ pthread_cleanup_pop(1);
|
|
|
da32ae |
+}
|
|
|
da32ae |
+
|
|
|
da32ae |
+/*
|
|
|
da32ae |
+ * On Receiving the frame from HBA driver, insert the frame into link
|
|
|
da32ae |
+ * integrity frame list which will be picked up later by consumer thread for
|
|
|
da32ae |
+ * processing.
|
|
|
da32ae |
+ */
|
|
|
da32ae |
+static int
|
|
|
da32ae |
+fpin_els_add_li_frame(struct fc_nl_event *fc_event)
|
|
|
da32ae |
+{
|
|
|
da32ae |
+ struct els_marginal_list *els_mrg = NULL;
|
|
|
da32ae |
+ int ret = 0;
|
|
|
da32ae |
+
|
|
|
da32ae |
+ if (fc_event->event_datalen > FC_PAYLOAD_MAXLEN)
|
|
|
da32ae |
+ return -EINVAL;
|
|
|
da32ae |
+
|
|
|
da32ae |
+ pthread_mutex_lock(&fpin_li_mutex);
|
|
|
da32ae |
+ pthread_cleanup_push(cleanup_mutex, &fpin_li_mutex);
|
|
|
da32ae |
+ pthread_testcancel();
|
|
|
da32ae |
+ els_mrg = calloc(1, sizeof(struct els_marginal_list));
|
|
|
da32ae |
+ if (els_mrg != NULL) {
|
|
|
da32ae |
+ els_mrg->host_num = fc_event->host_no;
|
|
|
da32ae |
+ els_mrg->event_code = fc_event->event_code;
|
|
|
da32ae |
+ els_mrg->length = fc_event->event_datalen;
|
|
|
da32ae |
+ memcpy(els_mrg->payload, &(fc_event->event_data), fc_event->event_datalen);
|
|
|
da32ae |
+ list_add_tail(&els_mrg->node, &els_marginal_list_head);
|
|
|
da32ae |
+ pthread_cond_signal(&fpin_li_cond);
|
|
|
da32ae |
+ } else
|
|
|
da32ae |
+ ret = -ENOMEM;
|
|
|
da32ae |
+ pthread_cleanup_pop(1);
|
|
|
da32ae |
+ return ret;
|
|
|
da32ae |
+
|
|
|
da32ae |
+}
|
|
|
da32ae |
+
|
|
|
da32ae |
+/*Sets the rport port_state to marginal*/
|
|
|
da32ae |
+static void fpin_set_rport_marginal(struct udev_device *rport_dev)
|
|
|
da32ae |
+{
|
|
|
da32ae |
+ sysfs_attr_set_value(rport_dev, "port_state",
|
|
|
da32ae |
+ "Marginal", strlen("Marginal"));
|
|
|
da32ae |
+}
|
|
|
da32ae |
+
|
|
|
da32ae |
+/*Add the marginal devices info into the list*/
|
|
|
da32ae |
+static void
|
|
|
da32ae |
+fpin_add_marginal_dev_info(uint32_t host_num, char *devname)
|
|
|
da32ae |
+{
|
|
|
da32ae |
+ struct marginal_dev_list *newdev = NULL;
|
|
|
da32ae |
+
|
|
|
da32ae |
+ newdev = calloc(1, sizeof(struct marginal_dev_list));
|
|
|
da32ae |
+ if (newdev != NULL) {
|
|
|
da32ae |
+ newdev->host_num = host_num;
|
|
|
da32ae |
+ strlcpy(newdev->dev_t, devname, BLK_DEV_SIZE);
|
|
|
da32ae |
+ condlog(4, "\n%s hostno %d devname %s\n", __func__,
|
|
|
da32ae |
+ host_num, newdev->dev_t);
|
|
|
da32ae |
+ pthread_mutex_lock(&fpin_li_marginal_dev_mutex);
|
|
|
da32ae |
+ list_add_tail(&(newdev->node),
|
|
|
da32ae |
+ &fpin_li_marginal_dev_list_head);
|
|
|
da32ae |
+ pthread_mutex_unlock(&fpin_li_marginal_dev_mutex);
|
|
|
da32ae |
+ }
|
|
|
da32ae |
+}
|
|
|
da32ae |
+
|
|
|
da32ae |
+/*
|
|
|
da32ae |
+ * This function goes through the vecs->pathvec, and for
|
|
|
da32ae |
+ * each path, check that the host number,
|
|
|
da32ae |
+ * the target WWPN associated with the path matches
|
|
|
da32ae |
+ * with the els wwpn and sets the path and port state to
|
|
|
da32ae |
+ * Marginal
|
|
|
da32ae |
+ */
|
|
|
da32ae |
+static int fpin_chk_wwn_setpath_marginal(uint16_t host_num, struct vectors *vecs,
|
|
|
da32ae |
+ uint64_t els_wwpn)
|
|
|
da32ae |
+{
|
|
|
da32ae |
+ struct path *pp;
|
|
|
da32ae |
+ struct multipath *mpp;
|
|
|
da32ae |
+ int i, k;
|
|
|
da32ae |
+ char rport_id[42];
|
|
|
da32ae |
+ const char *value = NULL;
|
|
|
da32ae |
+ struct udev_device *rport_dev = NULL;
|
|
|
da32ae |
+ uint64_t wwpn;
|
|
|
da32ae |
+ int ret = 0;
|
|
|
da32ae |
+
|
|
|
da32ae |
+ pthread_cleanup_push(cleanup_lock, &vecs->lock);
|
|
|
da32ae |
+ lock(&vecs->lock);
|
|
|
da32ae |
+ pthread_testcancel();
|
|
|
da32ae |
+
|
|
|
da32ae |
+ vector_foreach_slot(vecs->pathvec, pp, k) {
|
|
|
da32ae |
+ /* Checks the host number and also for the SCSI FCP */
|
|
|
da32ae |
+ if (pp->sg_id.proto_id != SCSI_PROTOCOL_FCP || host_num != pp->sg_id.host_no)
|
|
|
da32ae |
+ continue;
|
|
|
da32ae |
+ sprintf(rport_id, "rport-%d:%d-%d",
|
|
|
da32ae |
+ pp->sg_id.host_no, pp->sg_id.channel, pp->sg_id.transport_id);
|
|
|
da32ae |
+ rport_dev = udev_device_new_from_subsystem_sysname(udev,
|
|
|
da32ae |
+ "fc_remote_ports", rport_id);
|
|
|
da32ae |
+ if (!rport_dev) {
|
|
|
da32ae |
+ condlog(2, "%s: No fc_remote_port device for '%s'", pp->dev,
|
|
|
da32ae |
+ rport_id);
|
|
|
da32ae |
+ continue;
|
|
|
da32ae |
+ }
|
|
|
da32ae |
+ pthread_cleanup_push(_udev_device_unref, rport_dev);
|
|
|
da32ae |
+ value = udev_device_get_sysattr_value(rport_dev, "port_name");
|
|
|
da32ae |
+ if (!value)
|
|
|
da32ae |
+ goto unref;
|
|
|
da32ae |
+
|
|
|
da32ae |
+ if (value)
|
|
|
da32ae |
+ wwpn = strtol(value, NULL, 16);
|
|
|
da32ae |
+ /*
|
|
|
da32ae |
+ * If the port wwpn matches sets the path and port state
|
|
|
da32ae |
+ * to marginal
|
|
|
da32ae |
+ */
|
|
|
da32ae |
+ if (wwpn == els_wwpn) {
|
|
|
da32ae |
+ ret = fpin_path_setmarginal(pp);
|
|
|
da32ae |
+ if (ret < 0)
|
|
|
da32ae |
+ goto unref;
|
|
|
da32ae |
+ fpin_set_rport_marginal(rport_dev);
|
|
|
da32ae |
+ fpin_add_marginal_dev_info(host_num, pp->dev);
|
|
|
da32ae |
+ }
|
|
|
da32ae |
+unref:
|
|
|
da32ae |
+ pthread_cleanup_pop(1);
|
|
|
da32ae |
+ }
|
|
|
da32ae |
+ /* walk backwards because reload_and_sync_map() can remove mpp */
|
|
|
da32ae |
+ vector_foreach_slot_backwards(vecs->mpvec, mpp, i) {
|
|
|
da32ae |
+ if (mpp->fpin_must_reload) {
|
|
|
da32ae |
+ ret = reload_and_sync_map(mpp, vecs, 0);
|
|
|
da32ae |
+ if (ret == 2)
|
|
|
da32ae |
+ condlog(2, "map removed during reload");
|
|
|
da32ae |
+ else
|
|
|
da32ae |
+ mpp->fpin_must_reload = false;
|
|
|
da32ae |
+ }
|
|
|
da32ae |
+ }
|
|
|
da32ae |
+ pthread_cleanup_pop(1);
|
|
|
da32ae |
+ return ret;
|
|
|
da32ae |
+}
|
|
|
da32ae |
+
|
|
|
da32ae |
+/*
|
|
|
da32ae |
+ * This function loops around all the impacted wwns received as part of els
|
|
|
da32ae |
+ * frame and sets the associated path and port states to marginal.
|
|
|
da32ae |
+ */
|
|
|
da32ae |
+static int
|
|
|
da32ae |
+fpin_parse_li_els_setpath_marginal(uint16_t host_num, struct fc_tlv_desc *tlv,
|
|
|
da32ae |
+ struct vectors *vecs)
|
|
|
da32ae |
+{
|
|
|
da32ae |
+ uint32_t wwn_count = 0, iter = 0;
|
|
|
da32ae |
+ uint64_t wwpn;
|
|
|
da32ae |
+ struct fc_fn_li_desc *li_desc = (struct fc_fn_li_desc *)tlv;
|
|
|
da32ae |
+ int count = 0;
|
|
|
da32ae |
+ int ret = 0;
|
|
|
da32ae |
+
|
|
|
da32ae |
+ /* Update the wwn to list */
|
|
|
da32ae |
+ wwn_count = be32_to_cpu(li_desc->pname_count);
|
|
|
da32ae |
+ condlog(4, "Got wwn count as %d\n", wwn_count);
|
|
|
da32ae |
+
|
|
|
da32ae |
+ for (iter = 0; iter < wwn_count; iter++) {
|
|
|
da32ae |
+ wwpn = be64_to_cpu(li_desc->pname_list[iter]);
|
|
|
da32ae |
+ ret = fpin_chk_wwn_setpath_marginal(host_num, vecs, wwpn);
|
|
|
da32ae |
+ if (ret < 0)
|
|
|
da32ae |
+ condlog(2, "failed to set the path marginal associated with wwpn: 0x%" PRIx64 "\n", wwpn);
|
|
|
da32ae |
+
|
|
|
da32ae |
+ count++;
|
|
|
da32ae |
+ }
|
|
|
da32ae |
+ return count;
|
|
|
da32ae |
+}
|
|
|
da32ae |
+
|
|
|
da32ae |
+/*
|
|
|
da32ae |
+ * This function process the ELS frame received from HBA driver,
|
|
|
da32ae |
+ * and sets the path associated with the port wwn to marginal
|
|
|
da32ae |
+ * and also set the port state to marginal.
|
|
|
da32ae |
+ */
|
|
|
da32ae |
+static int
|
|
|
da32ae |
+fpin_process_els_frame(uint16_t host_num, char *fc_payload, struct vectors *vecs)
|
|
|
da32ae |
+{
|
|
|
da32ae |
+
|
|
|
da32ae |
+ int count = -1;
|
|
|
da32ae |
+ struct fc_els_fpin *fpin = (struct fc_els_fpin *)fc_payload;
|
|
|
da32ae |
+ struct fc_tlv_desc *tlv;
|
|
|
da32ae |
+
|
|
|
da32ae |
+ tlv = (struct fc_tlv_desc *)&fpin->fpin_desc[0];
|
|
|
da32ae |
+
|
|
|
da32ae |
+ /*
|
|
|
da32ae |
+ * Parse the els frame and set the affected paths and port
|
|
|
da32ae |
+ * state to marginal
|
|
|
da32ae |
+ */
|
|
|
da32ae |
+ count = fpin_parse_li_els_setpath_marginal(host_num, tlv, vecs);
|
|
|
da32ae |
+ if (count <= 0)
|
|
|
da32ae |
+ condlog(4, "Could not find any WWNs, ret = %d\n",
|
|
|
da32ae |
+ count);
|
|
|
da32ae |
+ return count;
|
|
|
da32ae |
+}
|
|
|
da32ae |
+
|
|
|
da32ae |
+/*
|
|
|
da32ae |
+ * This function process the FPIN ELS frame received from HBA driver,
|
|
|
da32ae |
+ * and push the frame to appropriate frame list. Currently we have only FPIN
|
|
|
da32ae |
+ * LI frame list.
|
|
|
da32ae |
+ */
|
|
|
da32ae |
+static int
|
|
|
da32ae |
+fpin_handle_els_frame(struct fc_nl_event *fc_event)
|
|
|
da32ae |
+{
|
|
|
da32ae |
+ int ret = -1;
|
|
|
da32ae |
+ uint32_t els_cmd;
|
|
|
da32ae |
+ struct fc_els_fpin *fpin = (struct fc_els_fpin *)&fc_event->event_data;
|
|
|
da32ae |
+ struct fc_tlv_desc *tlv;
|
|
|
da32ae |
+ uint32_t dtag;
|
|
|
da32ae |
+
|
|
|
da32ae |
+ els_cmd = (uint32_t)fc_event->event_data;
|
|
|
da32ae |
+ tlv = (struct fc_tlv_desc *)&fpin->fpin_desc[0];
|
|
|
da32ae |
+ dtag = be32_to_cpu(tlv->desc_tag);
|
|
|
da32ae |
+ condlog(4, "Got CMD in add as 0x%x fpin_cmd 0x%x dtag 0x%x\n",
|
|
|
da32ae |
+ els_cmd, fpin->fpin_cmd, dtag);
|
|
|
da32ae |
+
|
|
|
da32ae |
+ if ((fc_event->event_code == FCH_EVT_LINK_FPIN) ||
|
|
|
da32ae |
+ (fc_event->event_code == FCH_EVT_LINKUP) ||
|
|
|
da32ae |
+ (fc_event->event_code == FCH_EVT_RSCN)) {
|
|
|
da32ae |
+
|
|
|
da32ae |
+ if (els_cmd == ELS_FPIN) {
|
|
|
da32ae |
+ /*
|
|
|
da32ae |
+ * Check the type of fpin by checking the tag info
|
|
|
da32ae |
+ * At present we are supporting only LI events
|
|
|
da32ae |
+ */
|
|
|
da32ae |
+ if (dtag == ELS_DTAG_LNK_INTEGRITY) {
|
|
|
da32ae |
+ /*Push the Payload to FPIN frame queue. */
|
|
|
da32ae |
+ ret = fpin_els_add_li_frame(fc_event);
|
|
|
da32ae |
+ if (ret != 0)
|
|
|
da32ae |
+ condlog(0, "Failed to process LI frame with error %d\n",
|
|
|
da32ae |
+ ret);
|
|
|
da32ae |
+ } else {
|
|
|
da32ae |
+ condlog(4, "Unsupported FPIN received 0x%x\n", dtag);
|
|
|
da32ae |
+ return ret;
|
|
|
da32ae |
+ }
|
|
|
da32ae |
+ } else {
|
|
|
da32ae |
+ /*Push the Payload to FPIN frame queue. */
|
|
|
da32ae |
+ ret = fpin_els_add_li_frame(fc_event);
|
|
|
da32ae |
+ if (ret != 0)
|
|
|
da32ae |
+ condlog(0, "Failed to process Linkup/RSCN event with error %d evnt %d\n",
|
|
|
da32ae |
+ ret, fc_event->event_code);
|
|
|
da32ae |
+ }
|
|
|
da32ae |
+ } else
|
|
|
da32ae |
+ condlog(4, "Invalid command received: 0x%x\n", els_cmd);
|
|
|
da32ae |
+ return ret;
|
|
|
da32ae |
+}
|
|
|
da32ae |
+
|
|
|
da32ae |
+/*cleans the global marginal dev list*/
|
|
|
da32ae |
+void fpin_clean_marginal_dev_list(__attribute__((unused)) void *arg)
|
|
|
da32ae |
+{
|
|
|
da32ae |
+ struct marginal_dev_list *tmp_marg = NULL;
|
|
|
da32ae |
+
|
|
|
da32ae |
+ pthread_mutex_lock(&fpin_li_marginal_dev_mutex);
|
|
|
da32ae |
+ while (!list_empty(&fpin_li_marginal_dev_list_head)) {
|
|
|
da32ae |
+ tmp_marg = list_first_entry(&fpin_li_marginal_dev_list_head,
|
|
|
da32ae |
+ struct marginal_dev_list, node);
|
|
|
da32ae |
+ list_del(&tmp_marg->node);
|
|
|
da32ae |
+ free(tmp_marg);
|
|
|
da32ae |
+ }
|
|
|
da32ae |
+ pthread_mutex_unlock(&fpin_li_marginal_dev_mutex);
|
|
|
da32ae |
+}
|
|
|
da32ae |
+
|
|
|
da32ae |
+/* Cleans the global els marginal list */
|
|
|
da32ae |
+static void fpin_clean_els_marginal_list(void *arg)
|
|
|
da32ae |
+{
|
|
|
da32ae |
+ struct list_head *head = (struct list_head *)arg;
|
|
|
da32ae |
+ struct els_marginal_list *els_marg;
|
|
|
da32ae |
+
|
|
|
da32ae |
+ while (!list_empty(head)) {
|
|
|
da32ae |
+ els_marg = list_first_entry(head, struct els_marginal_list,
|
|
|
da32ae |
+ node);
|
|
|
da32ae |
+ list_del(&els_marg->node);
|
|
|
da32ae |
+ free(els_marg);
|
|
|
da32ae |
+ }
|
|
|
da32ae |
+}
|
|
|
da32ae |
+
|
|
|
da32ae |
+static void rcu_unregister(__attribute__((unused)) void *param)
|
|
|
da32ae |
+{
|
|
|
da32ae |
+ rcu_unregister_thread();
|
|
|
da32ae |
+}
|
|
|
da32ae |
+/*
|
|
|
da32ae |
+ * This is the FPIN ELS consumer thread. The thread sleeps on pthread cond
|
|
|
da32ae |
+ * variable unless notified by fpin_fabric_notification_receiver thread.
|
|
|
da32ae |
+ * This thread is only to process FPIN-LI ELS frames. A new thread and frame
|
|
|
da32ae |
+ * list will be added if any more ELS frames types are to be supported.
|
|
|
da32ae |
+ */
|
|
|
da32ae |
+void *fpin_els_li_consumer(void *data)
|
|
|
da32ae |
+{
|
|
|
da32ae |
+ struct list_head marginal_list_head;
|
|
|
da32ae |
+ int ret = 0;
|
|
|
da32ae |
+ uint16_t host_num;
|
|
|
da32ae |
+ struct els_marginal_list *els_marg;
|
|
|
da32ae |
+ uint32_t event_code;
|
|
|
da32ae |
+ struct vectors *vecs = (struct vectors *)data;
|
|
|
da32ae |
+
|
|
|
da32ae |
+ pthread_cleanup_push(rcu_unregister, NULL);
|
|
|
da32ae |
+ rcu_register_thread();
|
|
|
da32ae |
+ pthread_cleanup_push(fpin_clean_marginal_dev_list, NULL);
|
|
|
da32ae |
+ INIT_LIST_HEAD(&marginal_list_head);
|
|
|
da32ae |
+ pthread_cleanup_push(fpin_clean_els_marginal_list,
|
|
|
da32ae |
+ (void *)&marginal_list_head);
|
|
|
da32ae |
+ for ( ; ; ) {
|
|
|
da32ae |
+ pthread_mutex_lock(&fpin_li_mutex);
|
|
|
da32ae |
+ pthread_cleanup_push(cleanup_mutex, &fpin_li_mutex);
|
|
|
da32ae |
+ pthread_testcancel();
|
|
|
da32ae |
+ while (list_empty(&els_marginal_list_head))
|
|
|
da32ae |
+ pthread_cond_wait(&fpin_li_cond, &fpin_li_mutex);
|
|
|
da32ae |
+
|
|
|
da32ae |
+ if (!list_empty(&els_marginal_list_head)) {
|
|
|
da32ae |
+ condlog(4, "Invoke List splice tail\n");
|
|
|
da32ae |
+ list_splice_tail_init(&els_marginal_list_head, &marginal_list_head);
|
|
|
da32ae |
+ }
|
|
|
da32ae |
+ pthread_cleanup_pop(1);
|
|
|
da32ae |
+
|
|
|
da32ae |
+ while (!list_empty(&marginal_list_head)) {
|
|
|
da32ae |
+ els_marg = list_first_entry(&marginal_list_head,
|
|
|
da32ae |
+ struct els_marginal_list, node);
|
|
|
da32ae |
+ host_num = els_marg->host_num;
|
|
|
da32ae |
+ event_code = els_marg->event_code;
|
|
|
da32ae |
+ /* Now finally process FPIN LI ELS Frame */
|
|
|
da32ae |
+ condlog(4, "Got a new Payload buffer, processing it\n");
|
|
|
da32ae |
+ if ((event_code == FCH_EVT_LINKUP) || (event_code == FCH_EVT_RSCN))
|
|
|
da32ae |
+ fpin_unset_marginal_dev(host_num, vecs);
|
|
|
da32ae |
+ else {
|
|
|
da32ae |
+ ret = fpin_process_els_frame(host_num, els_marg->payload, vecs);
|
|
|
da32ae |
+ if (ret <= 0)
|
|
|
da32ae |
+ condlog(0, "ELS frame processing failed with ret %d\n", ret);
|
|
|
da32ae |
+ }
|
|
|
da32ae |
+ list_del(&els_marg->node);
|
|
|
da32ae |
+ free(els_marg);
|
|
|
da32ae |
+
|
|
|
da32ae |
+ }
|
|
|
da32ae |
+ }
|
|
|
da32ae |
+
|
|
|
da32ae |
+ pthread_cleanup_pop(1);
|
|
|
da32ae |
+ pthread_cleanup_pop(1);
|
|
|
da32ae |
+ pthread_cleanup_pop(1);
|
|
|
da32ae |
+ return NULL;
|
|
|
da32ae |
+}
|
|
|
da32ae |
+
|
|
|
da32ae |
+static void receiver_cleanup_list(__attribute__((unused)) void *arg)
|
|
|
da32ae |
+{
|
|
|
da32ae |
+ pthread_mutex_lock(&fpin_li_mutex);
|
|
|
da32ae |
+ fpin_clean_els_marginal_list(&els_marginal_list_head);
|
|
|
da32ae |
+ pthread_mutex_unlock(&fpin_li_mutex);
|
|
|
da32ae |
+}
|
|
|
da32ae |
+
|
|
|
da32ae |
+/*
|
|
|
da32ae |
+ * Listen for ELS frames from driver. on receiving the frame payload,
|
|
|
da32ae |
+ * push the payload to a list, and notify the fpin_els_li_consumer thread to
|
|
|
da32ae |
+ * process it. Once consumer thread is notified, return to listen for more ELS
|
|
|
da32ae |
+ * frames from driver.
|
|
|
da32ae |
+ */
|
|
|
da32ae |
+void *fpin_fabric_notification_receiver(__attribute__((unused))void *unused)
|
|
|
da32ae |
+{
|
|
|
da32ae |
+ int ret;
|
|
|
da32ae |
+ long fd;
|
|
|
da32ae |
+ uint32_t els_cmd;
|
|
|
da32ae |
+ struct fc_nl_event *fc_event = NULL;
|
|
|
da32ae |
+ struct sockaddr_nl fc_local;
|
|
|
da32ae |
+ unsigned char buf[DEF_RX_BUF_SIZE] __attribute__((aligned(sizeof(uint64_t))));
|
|
|
da32ae |
+ size_t plen = 0;
|
|
|
da32ae |
+
|
|
|
da32ae |
+ pthread_cleanup_push(rcu_unregister, NULL);
|
|
|
da32ae |
+ rcu_register_thread();
|
|
|
da32ae |
+
|
|
|
da32ae |
+ pthread_cleanup_push(receiver_cleanup_list, NULL);
|
|
|
da32ae |
+ fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_SCSITRANSPORT);
|
|
|
da32ae |
+ if (fd < 0) {
|
|
|
da32ae |
+ condlog(0, "fc socket error %ld", fd);
|
|
|
da32ae |
+ return NULL;
|
|
|
da32ae |
+ }
|
|
|
da32ae |
+
|
|
|
da32ae |
+ pthread_cleanup_push(close_fd, (void *)fd);
|
|
|
da32ae |
+ memset(&fc_local, 0, sizeof(fc_local));
|
|
|
da32ae |
+ fc_local.nl_family = AF_NETLINK;
|
|
|
da32ae |
+ fc_local.nl_groups = ~0;
|
|
|
da32ae |
+ fc_local.nl_pid = getpid();
|
|
|
da32ae |
+ ret = bind(fd, (struct sockaddr *)&fc_local, sizeof(fc_local));
|
|
|
da32ae |
+ if (ret == -1) {
|
|
|
da32ae |
+ condlog(0, "fc socket bind error %d\n", ret);
|
|
|
da32ae |
+ goto out;
|
|
|
da32ae |
+ }
|
|
|
da32ae |
+ for ( ; ; ) {
|
|
|
da32ae |
+ condlog(4, "Waiting for ELS...\n");
|
|
|
da32ae |
+ ret = read(fd, buf, DEF_RX_BUF_SIZE);
|
|
|
da32ae |
+ if (ret < 0) {
|
|
|
da32ae |
+ condlog(0, "failed to read the els frame (%d)", ret);
|
|
|
da32ae |
+ continue;
|
|
|
da32ae |
+ }
|
|
|
da32ae |
+ condlog(4, "Got a new request %d\n", ret);
|
|
|
da32ae |
+ if (!NLMSG_OK((struct nlmsghdr *)buf, (unsigned int)ret)) {
|
|
|
da32ae |
+ condlog(0, "bad els frame read (%d)", ret);
|
|
|
da32ae |
+ continue;
|
|
|
da32ae |
+ }
|
|
|
da32ae |
+ /* Push the frame to appropriate frame list */
|
|
|
da32ae |
+ plen = NLMSG_PAYLOAD((struct nlmsghdr *)buf, 0);
|
|
|
da32ae |
+ fc_event = (struct fc_nl_event *)NLMSG_DATA(buf);
|
|
|
da32ae |
+ if (plen < sizeof(*fc_event)) {
|
|
|
da32ae |
+ condlog(0, "too short (%d) to be an FC event", ret);
|
|
|
da32ae |
+ continue;
|
|
|
da32ae |
+ }
|
|
|
da32ae |
+ els_cmd = (uint32_t)fc_event->event_data;
|
|
|
da32ae |
+ condlog(4, "Got host no as %d, event 0x%x, len %d evntnum %d evntcode %d\n",
|
|
|
da32ae |
+ fc_event->host_no, els_cmd, fc_event->event_datalen,
|
|
|
da32ae |
+ fc_event->event_num, fc_event->event_code);
|
|
|
da32ae |
+ fpin_handle_els_frame(fc_event);
|
|
|
da32ae |
+ }
|
|
|
da32ae |
+out:
|
|
|
da32ae |
+ pthread_cleanup_pop(1);
|
|
|
da32ae |
+ pthread_cleanup_pop(1);
|
|
|
da32ae |
+ pthread_cleanup_pop(1);
|
|
|
da32ae |
+ return NULL;
|
|
|
da32ae |
+}
|
|
|
da32ae |
diff --git a/multipathd/main.c b/multipathd/main.c
|
|
|
da32ae |
index 5def5301..53be9b95 100644
|
|
|
da32ae |
--- a/multipathd/main.c
|
|
|
da32ae |
+++ b/multipathd/main.c
|
|
|
da32ae |
@@ -16,6 +16,7 @@
|
|
|
da32ae |
#include <linux/oom.h>
|
|
|
da32ae |
#include <libudev.h>
|
|
|
da32ae |
#include <urcu.h>
|
|
|
da32ae |
+#include "fpin.h"
|
|
|
da32ae |
#ifdef USE_SYSTEMD
|
|
|
da32ae |
#include <systemd/sd-daemon.h>
|
|
|
da32ae |
#endif
|
|
|
da32ae |
@@ -130,9 +131,11 @@ static volatile enum daemon_status running_state = DAEMON_INIT;
|
|
|
da32ae |
pid_t daemon_pid;
|
|
|
da32ae |
static pthread_mutex_t config_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
|
da32ae |
static pthread_cond_t config_cond;
|
|
|
da32ae |
-static pthread_t check_thr, uevent_thr, uxlsnr_thr, uevq_thr, dmevent_thr;
|
|
|
da32ae |
+static pthread_t check_thr, uevent_thr, uxlsnr_thr, uevq_thr, dmevent_thr,
|
|
|
da32ae |
+ fpin_thr, fpin_consumer_thr;
|
|
|
da32ae |
static bool check_thr_started, uevent_thr_started, uxlsnr_thr_started,
|
|
|
da32ae |
- uevq_thr_started, dmevent_thr_started;
|
|
|
da32ae |
+ uevq_thr_started, dmevent_thr_started, fpin_thr_started,
|
|
|
da32ae |
+ fpin_consumer_thr_started;
|
|
|
da32ae |
static int pid_fd = -1;
|
|
|
da32ae |
|
|
|
da32ae |
static inline enum daemon_status get_running_state(void)
|
|
|
da32ae |
@@ -2819,7 +2822,9 @@ reconfigure (struct vectors * vecs)
|
|
|
da32ae |
conf->sequence_nr = old->sequence_nr + 1;
|
|
|
da32ae |
rcu_assign_pointer(multipath_conf, conf);
|
|
|
da32ae |
call_rcu(&old->rcu, rcu_free_config);
|
|
|
da32ae |
-
|
|
|
da32ae |
+#ifdef FPIN_EVENT_HANDLER
|
|
|
da32ae |
+ fpin_clean_marginal_dev_list(NULL);
|
|
|
da32ae |
+#endif
|
|
|
da32ae |
configure(vecs);
|
|
|
da32ae |
|
|
|
da32ae |
|
|
|
da32ae |
@@ -3060,6 +3065,11 @@ static void cleanup_threads(void)
|
|
|
da32ae |
pthread_cancel(uevq_thr);
|
|
|
da32ae |
if (dmevent_thr_started)
|
|
|
da32ae |
pthread_cancel(dmevent_thr);
|
|
|
da32ae |
+ if (fpin_thr_started)
|
|
|
da32ae |
+ pthread_cancel(fpin_thr);
|
|
|
da32ae |
+ if (fpin_consumer_thr_started)
|
|
|
da32ae |
+ pthread_cancel(fpin_consumer_thr);
|
|
|
da32ae |
+
|
|
|
da32ae |
|
|
|
da32ae |
if (check_thr_started)
|
|
|
da32ae |
pthread_join(check_thr, NULL);
|
|
|
da32ae |
@@ -3071,6 +3081,11 @@ static void cleanup_threads(void)
|
|
|
da32ae |
pthread_join(uevq_thr, NULL);
|
|
|
da32ae |
if (dmevent_thr_started)
|
|
|
da32ae |
pthread_join(dmevent_thr, NULL);
|
|
|
da32ae |
+ if (fpin_thr_started)
|
|
|
da32ae |
+ pthread_join(fpin_thr, NULL);
|
|
|
da32ae |
+ if (fpin_consumer_thr_started)
|
|
|
da32ae |
+ pthread_join(fpin_consumer_thr, NULL);
|
|
|
da32ae |
+
|
|
|
da32ae |
|
|
|
da32ae |
/*
|
|
|
da32ae |
* As all threads are joined now, and we're in DAEMON_SHUTDOWN
|
|
|
da32ae |
@@ -3168,6 +3183,7 @@ child (__attribute__((unused)) void *param)
|
|
|
da32ae |
char *envp;
|
|
|
da32ae |
enum daemon_status state;
|
|
|
da32ae |
int exit_code = 1;
|
|
|
da32ae |
+ int fpin_marginal_paths = 0;
|
|
|
da32ae |
|
|
|
da32ae |
init_unwinder();
|
|
|
da32ae |
mlockall(MCL_CURRENT | MCL_FUTURE);
|
|
|
da32ae |
@@ -3246,7 +3262,10 @@ child (__attribute__((unused)) void *param)
|
|
|
da32ae |
|
|
|
da32ae |
setscheduler();
|
|
|
da32ae |
set_oom_adj();
|
|
|
da32ae |
-
|
|
|
da32ae |
+#ifdef FPIN_EVENT_HANDLER
|
|
|
da32ae |
+ if (conf->marginal_pathgroups == MARGINAL_PATHGROUP_FPIN)
|
|
|
da32ae |
+ fpin_marginal_paths = 1;
|
|
|
da32ae |
+#endif
|
|
|
da32ae |
/*
|
|
|
da32ae |
* Startup done, invalidate configuration
|
|
|
da32ae |
*/
|
|
|
da32ae |
@@ -3314,6 +3333,22 @@ child (__attribute__((unused)) void *param)
|
|
|
da32ae |
goto failed;
|
|
|
da32ae |
} else
|
|
|
da32ae |
uevq_thr_started = true;
|
|
|
da32ae |
+
|
|
|
da32ae |
+ if (fpin_marginal_paths) {
|
|
|
da32ae |
+ if ((rc = pthread_create(&fpin_thr, &misc_attr,
|
|
|
da32ae |
+ fpin_fabric_notification_receiver, NULL))) {
|
|
|
da32ae |
+ condlog(0, "failed to create the fpin receiver thread: %d", rc);
|
|
|
da32ae |
+ goto failed;
|
|
|
da32ae |
+ } else
|
|
|
da32ae |
+ fpin_thr_started = true;
|
|
|
da32ae |
+
|
|
|
da32ae |
+ if ((rc = pthread_create(&fpin_consumer_thr,
|
|
|
da32ae |
+ &misc_attr, fpin_els_li_consumer, vecs))) {
|
|
|
da32ae |
+ condlog(0, "failed to create the fpin consumer thread thread: %d", rc);
|
|
|
da32ae |
+ goto failed;
|
|
|
da32ae |
+ } else
|
|
|
da32ae |
+ fpin_consumer_thr_started = true;
|
|
|
da32ae |
+ }
|
|
|
da32ae |
pthread_attr_destroy(&misc_attr);
|
|
|
da32ae |
|
|
|
da32ae |
while (1) {
|