[CRYPTO] Allow multiple implementations of the same algorithm

This is the first step on the road towards asynchronous support in
the Crypto API.  It adds support for having multiple crypto_alg objects
for the same algorithm registered in the system.

For example, each device driver would register a crypto_alg object
for each algorithm that it supports.  While at the same time the
user may load software implementations of those same algorithms.

Users of the Crypto API may then select a specific implementation
by name, or choose any implementation for a given algorithm with
the highest priority.

The priority field is a 32-bit signed integer.  In future it will be
possible to modify it from user-space.

This also provides a solution to the problem of selecting amongst
various AES implementations, that is, aes vs. aes-i586 vs. aes-padlock.

Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
diff --git a/crypto/api.c b/crypto/api.c
index 40ae42e..2715afd 100644
--- a/crypto/api.c
+++ b/crypto/api.c
@@ -3,6 +3,7 @@
  *
  * Copyright (c) 2002 James Morris <jmorris@intercode.com.au>
  * Copyright (c) 2002 David S. Miller (davem@redhat.com)
+ * Copyright (c) 2005 Herbert Xu <herbert@gondor.apana.org.au>
  *
  * Portions derived from Cryptoapi, by Alexander Kjeldaas <astor@fast.no>
  * and Nettle, by Niels Möller.
@@ -18,9 +19,11 @@
 #include <linux/init.h>
 #include <linux/crypto.h>
 #include <linux/errno.h>
+#include <linux/kernel.h>
 #include <linux/kmod.h>
 #include <linux/rwsem.h>
 #include <linux/slab.h>
+#include <linux/string.h>
 #include "internal.h"
 
 LIST_HEAD(crypto_alg_list);
@@ -39,6 +42,7 @@
 static struct crypto_alg *crypto_alg_lookup(const char *name)
 {
 	struct crypto_alg *q, *alg = NULL;
+	int best = -1;
 
 	if (!name)
 		return NULL;
@@ -46,11 +50,23 @@
 	down_read(&crypto_alg_sem);
 	
 	list_for_each_entry(q, &crypto_alg_list, cra_list) {
-		if (!(strcmp(q->cra_name, name))) {
-			if (crypto_alg_get(q))
-				alg = q;
+		int exact, fuzzy;
+
+		exact = !strcmp(q->cra_driver_name, name);
+		fuzzy = !strcmp(q->cra_name, name);
+		if (!exact && !(fuzzy && q->cra_priority > best))
+			continue;
+
+		if (unlikely(!crypto_alg_get(q)))
+			continue;
+
+		best = q->cra_priority;
+		if (alg)
+			crypto_alg_put(alg);
+		alg = q;
+
+		if (exact)
 			break;
-		}
 	}
 	
 	up_read(&crypto_alg_sem);
@@ -207,9 +223,26 @@
 	kfree(tfm);
 }
 
+static inline int crypto_set_driver_name(struct crypto_alg *alg)
+{
+	static const char suffix[] = "-generic";
+	char *driver_name = (char *)alg->cra_driver_name;
+	int len;
+
+	if (*driver_name)
+		return 0;
+
+	len = strlcpy(driver_name, alg->cra_name, CRYPTO_MAX_ALG_NAME);
+	if (len + sizeof(suffix) > CRYPTO_MAX_ALG_NAME)
+		return -ENAMETOOLONG;
+
+	memcpy(driver_name + len, suffix, sizeof(suffix));
+	return 0;
+}
+
 int crypto_register_alg(struct crypto_alg *alg)
 {
-	int ret = 0;
+	int ret;
 	struct crypto_alg *q;
 
 	if (alg->cra_alignmask & (alg->cra_alignmask + 1))
@@ -220,11 +253,18 @@
 
 	if (alg->cra_blocksize > PAGE_SIZE)
 		return -EINVAL;
+
+	if (alg->cra_priority < 0)
+		return -EINVAL;
 	
+	ret = crypto_set_driver_name(alg);
+	if (unlikely(ret))
+		return ret;
+
 	down_write(&crypto_alg_sem);
 	
 	list_for_each_entry(q, &crypto_alg_list, cra_list) {
-		if (!(strcmp(q->cra_name, alg->cra_name))) {
+		if (!strcmp(q->cra_driver_name, alg->cra_driver_name)) {
 			ret = -EEXIST;
 			goto out;
 		}