(almost working) patch to make opencrypto use mutex/condvar

Thor Lancelot Simon <[email protected]>
Newsgroups gmane.os.netbsd.devel.kernel,gmane.os.netbsd.devel.crypto
Message-ID <[email protected]>
On Fri, Feb 01, 2008 at 01:34:39PM -0500, Thor Lancelot Simon wrote:
>
> The enclosed doesn't work for me (disappointing, since I wrote it!)
> with the cryptosoft backend, but one of my coworkers swears it works
> fine for him with a hardware driver.  Comments?  This is the first
> code I've tried to convert to our modern synchronization primitives.

It's much better now, but somehow if I run several consumers at once,
e.g.:
	sysctl -w kern.cryptodevallowsoft=0
	openssl speed -engine cryptodev -multi 64 -evp des-ede3-cbc

Most of them end up sleeping on crydev.  I'm not sure how that's
possible, since it didn't happen with the old tsleep/wakeup_one()
code and the condvar code does the analogous operation in the same
place each time.

But it's suitable for playing with.  And if anyone sees where that
cv_signal() is going missing... I owe you one.

Attached, and at http://www.panix.com/~tls/ocf-mtx2.diff

Thor
ocf-mtx2.diff (text/plain, 16.1 KB)
Index: crypto.c
===================================================================
RCS file: /cvsroot/src/sys/opencrypto/crypto.c,v
retrieving revision 1.22
diff -u -r1.22 crypto.c
--- crypto.c	1 Feb 2008 04:52:35 -0000	1.22
+++ crypto.c	1 Feb 2008 21:40:55 -0000
@@ -43,8 +43,10 @@
 #include <opencrypto/cryptodev.h>
 #include <opencrypto/xform.h>			/* XXX for M_XDATA */
 
-  #define splcrypto splnet
-  /* below is kludges to check whats still missing */
+kcondvar_t cryptoret_cv;
+kmutex_t crypto_mtx;
+
+/* below are kludges for residual code wrtitten to FreeBSD interfaces */
   #define SWI_CRYPTO 17
   #define register_swi(lvl, fn)  \
   softint_establish(SOFTINT_NET, (void (*)(void*))fn, NULL)
@@ -89,7 +91,7 @@
  */
 struct pool cryptop_pool;
 struct pool cryptodesc_pool;
-int crypto_pool_initialized = 0;
+struct pool cryptkop_pool;
 
 int	crypto_usercrypto = 1;		/* userland may open /dev/crypto */
 int	crypto_userasymcrypto = 1;	/* userland may do asym crypto reqs */
@@ -178,6 +180,14 @@
 {
 	int error;
 
+	mutex_init(&crypto_mtx, MUTEX_DEFAULT, IPL_NET);
+	cv_init(&cryptoret_cv, "crypto_wait");
+	pool_init(&cryptop_pool, sizeof(struct cryptop), 0, 0,  
+		  0, "cryptop", NULL, IPL_NET); 
+	pool_init(&cryptodesc_pool, sizeof(struct cryptodesc), 0, 0,
+		  0, "cryptodesc", NULL, IPL_NET);
+	pool_init(&cryptkop_pool, sizeof(struct cryptkop), 0, 0,
+		  0, "cryptkop", NULL, IPL_NET);
 
 	crypto_drivers = malloc(CRYPTO_DRIVERS_INITIAL *
 	    sizeof(struct cryptocap), M_CRYPTO_DATA, M_NOWAIT | M_ZERO);
@@ -225,9 +235,8 @@
 	struct cryptoini *cr;
 	u_int32_t hid, lid;
 	int err = EINVAL;
-	int s;
 
-	s = splcrypto();
+	mutex_spin_enter(&crypto_mtx);
 
 	if (crypto_drivers == NULL)
 		goto done;
@@ -288,7 +297,7 @@
 		}
 	}
 done:
-	splx(s);
+	mutex_spin_exit(&crypto_mtx);
 	return err;
 }
 
@@ -301,9 +310,8 @@
 {
 	u_int32_t hid;
 	int err = 0;
-	int s;
 
-	s = splcrypto();
+	mutex_spin_enter(&crypto_mtx);
 
 	if (crypto_drivers == NULL) {
 		err = EINVAL;
@@ -337,7 +345,7 @@
 		bzero(&crypto_drivers[hid], sizeof(struct cryptocap));
 
 done:
-	splx(s);
+	mutex_spin_exit(&crypto_mtx);
 	return err;
 }
 
@@ -349,11 +357,11 @@
 crypto_get_driverid(u_int32_t flags)
 {
 	struct cryptocap *newdrv;
-	int i, s;
+	int i;
 
-	crypto_init();
+	crypto_init();		/* XXX oh, this is foul! */
 
-	s = splcrypto();
+	mutex_spin_enter(&crypto_mtx);
 	for (i = 0; i < crypto_drivers_num; i++)
 		if (crypto_drivers[i].cc_process == NULL &&
 		    (crypto_drivers[i].cc_flags & CRYPTOCAP_F_CLEANUP) == 0 &&
@@ -364,7 +372,7 @@
 	if (i == crypto_drivers_num) {
 		/* Be careful about wrap-around. */
 		if (2 * crypto_drivers_num <= crypto_drivers_num) {
-			splx(s);
+			mutex_spin_exit(&crypto_mtx);
 			printf("crypto: driver count wraparound!\n");
 			return -1;
 		}
@@ -372,7 +380,7 @@
 		newdrv = malloc(2 * crypto_drivers_num *
 		    sizeof(struct cryptocap), M_CRYPTO_DATA, M_NOWAIT|M_ZERO);
 		if (newdrv == NULL) {
-			splx(s);
+			mutex_spin_exit(&crypto_mtx);
 			printf("crypto: no space to expand driver table!\n");
 			return -1;
 		}
@@ -393,7 +401,7 @@
 	if (bootverbose)
 		printf("crypto: assign driver %u, flags %u\n", i, flags);
 
-	splx(s);
+	mutex_spin_exit(&crypto_mtx);
 
 	return i;
 }
@@ -415,11 +423,10 @@
     int (*kprocess)(void*, struct cryptkop *, int),
     void *karg)
 {
-	int s;
 	struct cryptocap *cap;
 	int err;
 
-	s = splcrypto();
+	mutex_spin_enter(&crypto_mtx);
 
 	cap = crypto_checkdriver(driverid);
 	if (cap != NULL &&
@@ -446,7 +453,7 @@
 	} else
 		err = EINVAL;
 
-	splx(s);
+	mutex_spin_exit(&crypto_mtx);
 	return err;
 }
 
@@ -463,9 +470,9 @@
     void *arg)
 {
 	struct cryptocap *cap;
-	int s, err;
+	int err;
 
-	s = splcrypto();
+	mutex_spin_enter(&crypto_mtx);
 
 	cap = crypto_checkdriver(driverid);
 	/* NB: algorithms are in the range [1..max] */
@@ -498,7 +505,7 @@
 	} else
 		err = EINVAL;
 
-	splx(s);
+	mutex_spin_exit(&crypto_mtx);
 	return err;
 }
 
@@ -511,11 +518,11 @@
 int
 crypto_unregister(u_int32_t driverid, int alg)
 {
-	int i, err, s;
+	int i, err;
 	u_int32_t ses;
 	struct cryptocap *cap;
 
-	s = splcrypto();
+	mutex_spin_enter(&crypto_mtx);
 
 	cap = crypto_checkdriver(driverid);
 	if (cap != NULL &&
@@ -544,7 +551,7 @@
 	} else
 		err = EINVAL;
 
-	splx(s);
+	mutex_spin_exit(&crypto_mtx);
 	return err;
 }
 
@@ -554,14 +561,19 @@
  * around so that subsequent calls using those sessions will
  * correctly detect the driver has been unregistered and reroute
  * requests.
+ *
+ * XXX careful.  Don't change this to call crypto_unregister() for each
+ * XXX registered algorithm unless you drop the mutex across the calls;
+ * XXX you can't take it recursively.
  */
 int
 crypto_unregister_all(u_int32_t driverid)
 {
-	int i, err, s = splcrypto();
+	int i, err;
 	u_int32_t ses;
 	struct cryptocap *cap;
 
+	mutex_spin_enter(&crypto_mtx);
 	cap = crypto_checkdriver(driverid);
 	if (cap != NULL) {
 		for (i = CRYPTO_ALGORITHM_MIN; i <= CRYPTO_ALGORITHM_MAX; i++) {
@@ -581,7 +593,7 @@
 	} else
 		err = EINVAL;
 
-	splx(s);
+	mutex_spin_exit(&crypto_mtx);
 	return err;
 }
 
@@ -593,9 +605,9 @@
 crypto_unblock(u_int32_t driverid, int what)
 {
 	struct cryptocap *cap;
-	int needwakeup, err, s;
+	int needwakeup, err;
 
-	s = splcrypto();
+	mutex_spin_enter(&crypto_mtx);
 	cap = crypto_checkdriver(driverid);
 	if (cap != NULL) {
 		needwakeup = 0;
@@ -608,12 +620,13 @@
 			cap->cc_kqblocked = 0;
 		}
 		if (needwakeup) {
+			mutex_spin_exit(&crypto_mtx);
 			setsoftcrypto(softintr_cookie);
 		}
 		err = 0;
 	} else
 		err = EINVAL;
-	splx(s);
+	mutex_spin_exit(&crypto_mtx);
 
 	return err;
 }
@@ -626,9 +639,9 @@
 crypto_dispatch(struct cryptop *crp)
 {
 	u_int32_t hid = SESID2HID(crp->crp_sid);
-	int s, result;
+	int result;
 
-	s = splcrypto();
+	mutex_spin_enter(&crypto_mtx);
 
 	cryptostats.cs_ops++;
 
@@ -645,6 +658,7 @@
 		 */
 		cap = crypto_checkdriver(hid);
 		if (cap && !cap->cc_qblocked) {
+			mutex_spin_exit(&crypto_mtx);
 			result = crypto_invoke(crp, 0);
 			if (result == ERESTART) {
 				/*
@@ -652,10 +666,12 @@
 				 * driver ``blocked'' for cryptop's and put
 				 * the op on the queue.
 				 */
+				mutex_spin_enter(&crypto_mtx);
 				crypto_drivers[hid].cc_qblocked = 1;
 				TAILQ_INSERT_HEAD(&crp_q, crp, crp_next);
 				cryptostats.cs_blocks++;
 			}
+			goto out_released;
 		} else {
 			/*
 			 * The driver is blocked, just queue the op until
@@ -674,13 +690,17 @@
 		 */
 		TAILQ_INSERT_TAIL(&crp_q, crp, crp_next);
 		if (wasempty) {
+			mutex_spin_exit(&crypto_mtx);
 			setsoftcrypto(softintr_cookie);
+			result = 0;
+			goto out_released;
 		}
 
 		result = 0;
 	}
-	splx(s);
 
+	mutex_spin_exit(&crypto_mtx);
+out_released:
 	return result;
 }
 
@@ -692,13 +712,14 @@
 crypto_kdispatch(struct cryptkop *krp)
 {
 	struct cryptocap *cap;
-	int s, result;
+	int result;
 
-	s = splcrypto();
+	mutex_spin_enter(&crypto_mtx);
 	cryptostats.cs_kops++;
 
 	cap = crypto_checkdriver(krp->krp_hid);
 	if (cap && !cap->cc_kqblocked) {
+		mutex_spin_exit(&crypto_mtx);
 		result = crypto_kinvoke(krp, 0);
 		if (result == ERESTART) {
 			/*
@@ -706,6 +727,7 @@
 			 * driver ``blocked'' for cryptop's and put
 			 * the op on the queue.
 			 */
+			mutex_spin_enter(&crypto_mtx);
 			crypto_drivers[krp->krp_hid].cc_kqblocked = 1;
 			TAILQ_INSERT_HEAD(&crp_kq, krp, krp_next);
 			cryptostats.cs_kblocks++;
@@ -718,7 +740,7 @@
 		TAILQ_INSERT_TAIL(&crp_kq, krp, krp_next);
 		result = 0;
 	}
-	splx(s);
+	mutex_spin_exit(&crypto_mtx);
 
 	return result;
 }
@@ -736,7 +758,7 @@
 	if (krp == NULL)
 		return EINVAL;
 	if (krp->krp_callback == NULL) {
-		free(krp, M_XDATA);		/* XXX allocated in cryptodev */
+		pool_put(&cryptkop_pool, krp);
 		return EINVAL;
 	}
 
@@ -857,20 +879,15 @@
 crypto_freereq(struct cryptop *crp)
 {
 	struct cryptodesc *crd;
-	int s;
 
 	if (crp == NULL)
 		return;
 
-	s = splcrypto();
-
 	while ((crd = crp->crp_desc) != NULL) {
 		crp->crp_desc = crd->crd_next;
 		pool_put(&cryptodesc_pool, crd);
 	}
-
 	pool_put(&cryptop_pool, crp);
-	splx(s);
 }
 
 /*
@@ -881,29 +898,17 @@
 {
 	struct cryptodesc *crd;
 	struct cryptop *crp;
-	int s;
-
-	s = splcrypto();
-
-	if (crypto_pool_initialized == 0) {
-		pool_init(&cryptop_pool, sizeof(struct cryptop), 0, 0,
-		    0, "cryptop", NULL, IPL_NET);
-		pool_init(&cryptodesc_pool, sizeof(struct cryptodesc), 0, 0,
-		    0, "cryptodesc", NULL, IPL_NET);
-		crypto_pool_initialized = 1;
-	}
 
 	crp = pool_get(&cryptop_pool, 0);
 	if (crp == NULL) {
-		splx(s);
 		return NULL;
 	}
 	bzero(crp, sizeof(struct cryptop));
+	cv_init(&crp->crp_cv, "crydev");
 
 	while (num--) {
 		crd = pool_get(&cryptodesc_pool, 0);
 		if (crd == NULL) {
-			splx(s);
 			crypto_freereq(crp);
 			return NULL;
 		}
@@ -913,7 +918,6 @@
 		crp->crp_desc = crd;
 	}
 
-	splx(s);
 	return crp;
 }
 
@@ -934,7 +938,7 @@
 	 * has done its tsleep().
 	 */
 	{
-		int s, wasempty;
+		int wasempty;
 		/*
 		 * Normal case; queue the callback for the thread.
 		 *
@@ -943,12 +947,12 @@
 		 * back to mark operations completed.  Thus we need
 		 * to mask both while manipulating the return queue.
 		 */
-		s = splcrypto();
+		mutex_spin_enter(&crypto_mtx);
 		wasempty = TAILQ_EMPTY(&crp_ret_q);
 		TAILQ_INSERT_TAIL(&crp_ret_q, crp, crp_next);
 		if (wasempty)
-			wakeup_one(&crp_ret_q);
-		splx(s);
+			cv_signal(&cryptoret_cv);
+		mutex_spin_exit(&crypto_mtx);
 	}
 }
 
@@ -958,7 +962,7 @@
 void
 crypto_kdone(struct cryptkop *krp)
 {
-	int s, wasempty;
+	int wasempty;
 
 	if (krp->krp_status != 0)
 		cryptostats.cs_kerrs++;
@@ -968,21 +972,20 @@
 	 * back to mark operations completed.  Thus we need
 	 * to mask both while manipulating the return queue.
 	 */
-	s = splcrypto();
+	mutex_spin_enter(&crypto_mtx);
 	wasempty = TAILQ_EMPTY(&crp_ret_kq);
 	TAILQ_INSERT_TAIL(&crp_ret_kq, krp, krp_next);
 	if (wasempty)
-		wakeup_one(&crp_ret_q);
-	splx(s);
+		cv_signal(&cryptoret_cv);
+	mutex_spin_exit(&crypto_mtx);
 }
 
 int
 crypto_getfeat(int *featp)
 {
 	int hid, kalg, feat = 0;
-	int s;
 
-	s = splcrypto();
+	mutex_spin_enter(&crypto_mtx);
 
 	if (crypto_userasymcrypto == 0)
 		goto out;
@@ -1000,7 +1003,7 @@
 				feat |=  1 << kalg;
 	}
 out:
-	splx(s);
+	mutex_spin_exit(&crypto_mtx);
 	*featp = feat;
 	return (0);
 }
@@ -1014,11 +1017,11 @@
 	struct cryptop *crp, *submit;
 	struct cryptkop *krp;
 	struct cryptocap *cap;
-	int result, hint, s;
+	int result, hint;
 
 	printf("crypto softint\n");
 	cryptostats.cs_intrs++;
-	s = splcrypto();
+	mutex_spin_enter(&crypto_mtx);
 	do {
 		/*
 		 * Find the first element in the queue that can be
@@ -1059,7 +1062,11 @@
 		}
 		if (submit != NULL) {
 			TAILQ_REMOVE(&crp_q, submit, crp_next);
+			mutex_spin_exit(&crypto_mtx);
 			result = crypto_invoke(submit, hint);
+			/* we must take here as the TAILQ op or kinvoke
+			   may need this mutex below.  sigh. */
+			mutex_spin_enter(&crypto_mtx);	
 			if (result == ERESTART) {
 				/*
 				 * The driver ran out of resources, mark the
@@ -1089,7 +1096,10 @@
 		}
 		if (krp != NULL) {
 			TAILQ_REMOVE(&crp_kq, krp, krp_next);
+			mutex_spin_exit(&crypto_mtx);
 			result = crypto_kinvoke(krp, 0);
+			/* the next iteration will want the mutex. :-/ */
+			mutex_spin_enter(&crypto_mtx);
 			if (result == ERESTART) {
 				/*
 				 * The driver ran out of resources, mark the
@@ -1107,7 +1117,7 @@
 			}
 		}
 	} while (submit != NULL || krp != NULL);
-	splx(s);
+	mutex_spin_exit(&crypto_mtx);
 }
 
 /*
@@ -1118,9 +1128,15 @@
 {
 	struct cryptop *crp;
 	struct cryptkop *krp;
-	int s;
 
-	s = splcrypto();
+	/*
+	 * We take the mutex here the first time through.  On subsequent
+	 * entries to the infinite loop below, we will still have the
+	 * mutex from the prior iteration.
+	 */
+
+	mutex_spin_enter(&crypto_mtx);
+
 	for (;;) {
 		crp = TAILQ_FIRST(&crp_ret_q);
 		if (crp != NULL)
@@ -1130,7 +1146,7 @@
 			TAILQ_REMOVE(&crp_ret_kq, krp, krp_next);
 
 		if (crp != NULL || krp != NULL) {
-			splx(s);		/* lower ipl for callbacks */
+			mutex_spin_exit(&crypto_mtx);	/* drop for callbacks */
 			if (crp != NULL) {
 #ifdef CRYPTO_TIMING
 				if (crypto_timing) {
@@ -1149,14 +1165,10 @@
 			}
 			if (krp != NULL)
 				krp->krp_callback(krp);
-			s  = splcrypto();
+			mutex_spin_enter(&crypto_mtx);
 		} else {
-			(void) tsleep(&crp_ret_q, PLOCK, "crypto_wait", 0);
+			cv_wait(&cryptoret_cv, &crypto_mtx);
 			cryptostats.cs_rets++;
 		}
 	}
 }
-
-
-
-
Index: cryptodev.c
===================================================================
RCS file: /cvsroot/src/sys/opencrypto/cryptodev.c,v
retrieving revision 1.31
diff -u -r1.31 cryptodev.c
--- cryptodev.c	1 Feb 2008 04:52:35 -0000	1.31
+++ cryptodev.c	1 Feb 2008 21:40:57 -0000
@@ -55,7 +55,6 @@
 #include <opencrypto/cryptodev.h>
 #include <opencrypto/xform.h>
 
-  #define splcrypto splnet
 #ifdef CRYPTO_DEBUG
 #define DPRINTF(a) uprintf a
 #else
@@ -368,7 +367,7 @@
 {
 	struct cryptop *crp = NULL;
 	struct cryptodesc *crde = NULL, *crda = NULL;
-	int error, s;
+	int error;
 
 	if (cop->len > 256*1024-4)
 		return (E2BIG);
@@ -474,11 +473,21 @@
 		crp->crp_mac=cse->tmp_mac;
 	}
 
-	s = splcrypto();	/* NB: only needed with CRYPTO_F_CBIMM */
+	/*
+	 * XXX there was a comment here which said that we went to
+	 * XXX splcrypto() but needed to only if CRYPTO_F_CBIMM,
+	 * XXX disabled on NetBSD since 1.6O due to a race condition.
+	 * XXX But crypto_dispatch went to splcrypto() itself!  (And
+	 * XXX now takes the crypto_mtx mutex itself).  We do, however,
+	 * XXX need to hold the mutex across the call to cv_wait().
+	 * XXX     (should we arrange for crypto_dispatch to return to
+	 * XXX      us with it held?  it seems quite ugly to do so.)
+	 */
 	error = crypto_dispatch(crp);
+	mutex_spin_enter(&crypto_mtx);
 	if (error == 0 && (crp->crp_flags & CRYPTO_F_DONE) == 0)
-		error = tsleep(crp, PSOCK, "crydev", 0);
-	splx(s);
+		cv_wait(&crp->crp_cv, &crypto_mtx);	/* XXX cv_wait_sig? */
+	mutex_spin_exit(&crypto_mtx);
 	if (error) {
 		goto bail;
 	}
@@ -519,7 +528,9 @@
 	cse->error = crp->crp_etype;
 	if (crp->crp_etype == EAGAIN)
 		return crypto_dispatch(crp);
-	wakeup_one(crp);
+	mutex_spin_enter(&crypto_mtx);
+	cv_signal(&crp->crp_cv);
+	mutex_spin_exit(&crypto_mtx);
 	return (0);
 }
 
@@ -528,7 +539,9 @@
 {
 	struct cryptkop *krp = (struct cryptkop *) op;
 
-	wakeup_one(krp);
+	mutex_spin_enter(&crypto_mtx);
+	cv_signal(&krp->krp_cv);
+	mutex_spin_exit(&crypto_mtx);
 	return (0);
 }
 
@@ -594,10 +607,11 @@
 		return (EINVAL);
 	}
 
-	krp = (struct cryptkop *)malloc(sizeof *krp, M_XDATA, M_WAITOK);
+	krp = pool_get(&cryptkop_pool, PR_WAITOK);
 	if (!krp)
 		return (ENOMEM);
 	bzero(krp, sizeof *krp);
+	cv_init(&krp->krp_cv, "crykdev");
 	krp->krp_op = kop->crk_op;
 	krp->krp_status = kop->crk_status;
 	krp->krp_iparams = kop->crk_iparams;
@@ -621,10 +635,13 @@
 
 	error = crypto_kdispatch(krp);
 	if (error == 0)
-		error = tsleep(krp, PSOCK, "crydev", 0);
-	if (error)
+		mutex_spin_enter(&crypto_mtx);
+		cv_wait(&krp->krp_cv, &crypto_mtx);	/* XXX cv_wait_sig? */
+		mutex_spin_exit(&crypto_mtx);
+#if 0
+	if (error)			/* see XXX above -- wait intr? */
 		goto fail;
-
+#endif
 	if (krp->krp_status != 0) {
 		error = krp->krp_status;
 		goto fail;
@@ -646,7 +663,7 @@
 			if (krp->krp_param[i].crp_p)
 				FREE(krp->krp_param[i].crp_p, M_XDATA);
 		}
-		free(krp, M_XDATA);
+		pool_put(&cryptkop_pool, krp);
 	}
 	return (error);
 }
Index: cryptodev.h
===================================================================
RCS file: /cvsroot/src/sys/opencrypto/cryptodev.h,v
retrieving revision 1.10
diff -u -r1.10 cryptodev.h
--- cryptodev.h	1 Feb 2008 04:52:35 -0000	1.10
+++ cryptodev.h	1 Feb 2008 21:40:57 -0000
@@ -288,6 +288,7 @@
 
 	void *		crp_mac;
 	struct timespec	crp_tstamp;	/* performance time stamp */
+	kcondvar_t	crp_cv;
 };
 
 #define CRYPTO_BUF_CONTIG	0x0
@@ -312,6 +313,7 @@
 	u_int32_t	krp_hid;
 	struct crparam	krp_param[CRK_MAXPARAM];	/* kvm */
 	int		(*krp_callback)(struct cryptkop *);
+	kcondvar_t	krp_cv;
 };
 
 /* Crypto capabilities structure */
@@ -391,6 +393,17 @@
 extern	int crypto_userasymcrypto;	/* userland may do asym crypto reqs */
 extern	int crypto_devallowsoft;	/* only use hardware crypto */
 
+/*
+ * Asymmetric operations are allocated in cryptodev.c but can be
+ * freed in crypto.c.
+ */
+extern	struct pool	cryptkop_pool;
+
+/*
+ * Mutual exclusion and its unwelcome friends.
+ */
+
+extern	kmutex_t	crypto_mtx;
 
 /*
  * initialize the crypto framework subsystem (not the pseudo-device).
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.