Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 | /* GPL HEADER START
*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 only,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License version 2 for more details (a copy is included
* in the LICENSE file that accompanied this code).
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; If not, see http://www.gnu.org/licenses
*
* Please visit http://www.xyratex.com/contact if you need additional
* information or have any questions.
*
* GPL HEADER END
*/
/*
* Copyright 2012 Xyratex Technology Limited
*
* Copyright (c) 2012, Intel Corporation.
*/
#include <crypto/hash.h>
#include <linux/scatterlist.h>
#include "../../../include/linux/libcfs/libcfs.h"
#include "../../../include/linux/libcfs/libcfs_crypto.h"
#include "linux-crypto.h"
/**
* Array of hash algorithm speed in MByte per second
*/
static int cfs_crypto_hash_speeds[CFS_HASH_ALG_MAX];
/**
* Initialize the state descriptor for the specified hash algorithm.
*
* An internal routine to allocate the hash-specific state in \a hdesc for
* use with cfs_crypto_hash_digest() to compute the hash of a single message,
* though possibly in multiple chunks. The descriptor internal state should
* be freed with cfs_crypto_hash_final().
*
* \param[in] hash_alg hash algorithm id (CFS_HASH_ALG_*)
* \param[out] type pointer to the hash description in hash_types[]
* array
* \param[in,out] hdesc hash state descriptor to be initialized
* \param[in] key initial hash value/state, NULL to use default
* value
* \param[in] key_len length of \a key
*
* \retval 0 on success
* \retval negative errno on failure
*/
static int cfs_crypto_hash_alloc(enum cfs_crypto_hash_alg hash_alg,
const struct cfs_crypto_hash_type **type,
struct ahash_request **req,
unsigned char *key,
unsigned int key_len)
{
struct crypto_ahash *tfm;
int err = 0;
*type = cfs_crypto_hash_type(hash_alg);
if (!*type) {
CWARN("Unsupported hash algorithm id = %d, max id is %d\n",
hash_alg, CFS_HASH_ALG_MAX);
return -EINVAL;
}
tfm = crypto_alloc_ahash((*type)->cht_name, 0, CRYPTO_ALG_ASYNC);
if (IS_ERR(tfm)) {
CDEBUG(D_INFO, "Failed to alloc crypto hash %s\n",
(*type)->cht_name);
return PTR_ERR(tfm);
}
*req = ahash_request_alloc(tfm, GFP_KERNEL);
if (!*req) {
CDEBUG(D_INFO, "Failed to alloc ahash_request for %s\n",
(*type)->cht_name);
crypto_free_ahash(tfm);
return -ENOMEM;
}
ahash_request_set_callback(*req, 0, NULL, NULL);
if (key)
err = crypto_ahash_setkey(tfm, key, key_len);
else if ((*type)->cht_key != 0)
err = crypto_ahash_setkey(tfm,
(unsigned char *)&((*type)->cht_key),
(*type)->cht_size);
if (err != 0) {
ahash_request_free(*req);
crypto_free_ahash(tfm);
return err;
}
CDEBUG(D_INFO, "Using crypto hash: %s (%s) speed %d MB/s\n",
crypto_ahash_alg_name(tfm), crypto_ahash_driver_name(tfm),
cfs_crypto_hash_speeds[hash_alg]);
err = crypto_ahash_init(*req);
if (err) {
ahash_request_free(*req);
crypto_free_ahash(tfm);
}
return err;
}
/**
* Calculate hash digest for the passed buffer.
*
* This should be used when computing the hash on a single contiguous buffer.
* It combines the hash initialization, computation, and cleanup.
*
* \param[in] hash_alg id of hash algorithm (CFS_HASH_ALG_*)
* \param[in] buf data buffer on which to compute hash
* \param[in] buf_len length of \a buf in bytes
* \param[in] key initial value/state for algorithm,
* if \a key = NULL use default initial value
* \param[in] key_len length of \a key in bytes
* \param[out] hash pointer to computed hash value,
* if \a hash = NULL then \a hash_len is to digest
* size in bytes, retval -ENOSPC
* \param[in,out] hash_len size of \a hash buffer
*
* \retval -EINVAL \a buf, \a buf_len, \a hash_len,
* \a hash_alg invalid
* \retval -ENOENT \a hash_alg is unsupported
* \retval -ENOSPC \a hash is NULL, or \a hash_len less than
* digest size
* \retval 0 for success
* \retval negative errno for other errors from lower
* layers.
*/
int cfs_crypto_hash_digest(enum cfs_crypto_hash_alg hash_alg,
const void *buf, unsigned int buf_len,
unsigned char *key, unsigned int key_len,
unsigned char *hash, unsigned int *hash_len)
{
struct scatterlist sl;
struct ahash_request *req;
int err;
const struct cfs_crypto_hash_type *type;
if (!buf || buf_len == 0 || !hash_len)
return -EINVAL;
err = cfs_crypto_hash_alloc(hash_alg, &type, &req, key, key_len);
if (err != 0)
return err;
if (!hash || *hash_len < type->cht_size) {
*hash_len = type->cht_size;
crypto_free_ahash(crypto_ahash_reqtfm(req));
ahash_request_free(req);
return -ENOSPC;
}
sg_init_one(&sl, buf, buf_len);
ahash_request_set_crypt(req, &sl, hash, sl.length);
err = crypto_ahash_digest(req);
crypto_free_ahash(crypto_ahash_reqtfm(req));
ahash_request_free(req);
return err;
}
EXPORT_SYMBOL(cfs_crypto_hash_digest);
/**
* Allocate and initialize desriptor for hash algorithm.
*
* This should be used to initialize a hash descriptor for multiple calls
* to a single hash function when computing the hash across multiple
* separate buffers or pages using cfs_crypto_hash_update{,_page}().
*
* The hash descriptor should be freed with cfs_crypto_hash_final().
*
* \param[in] hash_alg algorithm id (CFS_HASH_ALG_*)
* \param[in] key initial value/state for algorithm, if \a key = NULL
* use default initial value
* \param[in] key_len length of \a key in bytes
*
* \retval pointer to descriptor of hash instance
* \retval ERR_PTR(errno) in case of error
*/
struct cfs_crypto_hash_desc *
cfs_crypto_hash_init(enum cfs_crypto_hash_alg hash_alg,
unsigned char *key, unsigned int key_len)
{
struct ahash_request *req;
int err;
const struct cfs_crypto_hash_type *type;
err = cfs_crypto_hash_alloc(hash_alg, &type, &req, key, key_len);
if (err)
return ERR_PTR(err);
return (struct cfs_crypto_hash_desc *)req;
}
EXPORT_SYMBOL(cfs_crypto_hash_init);
/**
* Update hash digest computed on data within the given \a page
*
* \param[in] hdesc hash state descriptor
* \param[in] page data page on which to compute the hash
* \param[in] offset offset within \a page at which to start hash
* \param[in] len length of data on which to compute hash
*
* \retval 0 for success
* \retval negative errno on failure
*/
int cfs_crypto_hash_update_page(struct cfs_crypto_hash_desc *hdesc,
struct page *page, unsigned int offset,
unsigned int len)
{
struct ahash_request *req = (void *)hdesc;
struct scatterlist sl;
sg_init_table(&sl, 1);
sg_set_page(&sl, page, len, offset & ~PAGE_MASK);
ahash_request_set_crypt(req, &sl, NULL, sl.length);
return crypto_ahash_update(req);
}
EXPORT_SYMBOL(cfs_crypto_hash_update_page);
/**
* Update hash digest computed on the specified data
*
* \param[in] hdesc hash state descriptor
* \param[in] buf data buffer on which to compute the hash
* \param[in] buf_len length of \buf on which to compute hash
*
* \retval 0 for success
* \retval negative errno on failure
*/
int cfs_crypto_hash_update(struct cfs_crypto_hash_desc *hdesc,
const void *buf, unsigned int buf_len)
{
struct ahash_request *req = (void *)hdesc;
struct scatterlist sl;
sg_init_one(&sl, buf, buf_len);
ahash_request_set_crypt(req, &sl, NULL, sl.length);
return crypto_ahash_update(req);
}
EXPORT_SYMBOL(cfs_crypto_hash_update);
/**
* Finish hash calculation, copy hash digest to buffer, clean up hash descriptor
*
* \param[in] hdesc hash descriptor
* \param[out] hash pointer to hash buffer to store hash digest
* \param[in,out] hash_len pointer to hash buffer size, if \a hdesc = NULL
* only free \a hdesc instead of computing the hash
*
* \retval 0 for success
* \retval -EOVERFLOW if hash_len is too small for the hash digest
* \retval negative errno for other errors from lower layers
*/
int cfs_crypto_hash_final(struct cfs_crypto_hash_desc *hdesc,
unsigned char *hash, unsigned int *hash_len)
{
int err;
struct ahash_request *req = (void *)hdesc;
int size = crypto_ahash_digestsize(crypto_ahash_reqtfm(req));
if (!hash || !hash_len) {
err = 0;
goto free_ahash;
}
if (*hash_len < size) {
err = -EOVERFLOW;
goto free_ahash;
}
ahash_request_set_crypt(req, NULL, hash, 0);
err = crypto_ahash_final(req);
if (!err)
*hash_len = size;
free_ahash:
crypto_free_ahash(crypto_ahash_reqtfm(req));
ahash_request_free(req);
return err;
}
EXPORT_SYMBOL(cfs_crypto_hash_final);
/**
* Compute the speed of specified hash function
*
* Run a speed test on the given hash algorithm on buffer of the given size.
* The speed is stored internally in the cfs_crypto_hash_speeds[] array, and
* is available through the cfs_crypto_hash_speed() function.
*
* \param[in] hash_alg hash algorithm id (CFS_HASH_ALG_*)
* \param[in] buf data buffer on which to compute the hash
* \param[in] buf_len length of \buf on which to compute hash
*/
static void cfs_crypto_performance_test(enum cfs_crypto_hash_alg hash_alg)
{
int buf_len = max(PAGE_SIZE, 1048576UL);
void *buf;
unsigned long start, end;
int bcount, err = 0;
struct page *page;
unsigned char hash[CFS_CRYPTO_HASH_DIGESTSIZE_MAX];
unsigned int hash_len = sizeof(hash);
page = alloc_page(GFP_KERNEL);
if (!page) {
err = -ENOMEM;
goto out_err;
}
buf = kmap(page);
memset(buf, 0xAD, PAGE_SIZE);
kunmap(page);
for (start = jiffies, end = start + msecs_to_jiffies(MSEC_PER_SEC),
bcount = 0; time_before(jiffies, end); bcount++) {
struct cfs_crypto_hash_desc *hdesc;
int i;
hdesc = cfs_crypto_hash_init(hash_alg, NULL, 0);
if (IS_ERR(hdesc)) {
err = PTR_ERR(hdesc);
break;
}
for (i = 0; i < buf_len / PAGE_SIZE; i++) {
err = cfs_crypto_hash_update_page(hdesc, page, 0,
PAGE_SIZE);
if (err)
break;
}
err = cfs_crypto_hash_final(hdesc, hash, &hash_len);
if (err)
break;
}
end = jiffies;
__free_page(page);
out_err:
if (err) {
cfs_crypto_hash_speeds[hash_alg] = err;
CDEBUG(D_INFO, "Crypto hash algorithm %s test error: rc = %d\n",
cfs_crypto_hash_name(hash_alg), err);
} else {
unsigned long tmp;
tmp = ((bcount * buf_len / jiffies_to_msecs(end - start)) *
1000) / (1024 * 1024);
cfs_crypto_hash_speeds[hash_alg] = (int)tmp;
CDEBUG(D_CONFIG, "Crypto hash algorithm %s speed = %d MB/s\n",
cfs_crypto_hash_name(hash_alg),
cfs_crypto_hash_speeds[hash_alg]);
}
}
/**
* hash speed in Mbytes per second for valid hash algorithm
*
* Return the performance of the specified \a hash_alg that was previously
* computed using cfs_crypto_performance_test().
*
* \param[in] hash_alg hash algorithm id (CFS_HASH_ALG_*)
*
* \retval positive speed of the hash function in MB/s
* \retval -ENOENT if \a hash_alg is unsupported
* \retval negative errno if \a hash_alg speed is unavailable
*/
int cfs_crypto_hash_speed(enum cfs_crypto_hash_alg hash_alg)
{
if (hash_alg < CFS_HASH_ALG_MAX)
return cfs_crypto_hash_speeds[hash_alg];
return -ENOENT;
}
EXPORT_SYMBOL(cfs_crypto_hash_speed);
/**
* Run the performance test for all hash algorithms.
*
* Run the cfs_crypto_performance_test() benchmark for all of the available
* hash functions using a 1MB buffer size. This is a reasonable buffer size
* for Lustre RPCs, even if the actual RPC size is larger or smaller.
*
* Since the setup cost and computation speed of various hash algorithms is
* a function of the buffer size (and possibly internal contention of offload
* engines), this speed only represents an estimate of the actual speed under
* actual usage, but is reasonable for comparing available algorithms.
*
* The actual speeds are available via cfs_crypto_hash_speed() for later
* comparison.
*
* \retval 0 on success
* \retval -ENOMEM if no memory is available for test buffer
*/
static int cfs_crypto_test_hashes(void)
{
enum cfs_crypto_hash_alg hash_alg;
for (hash_alg = 0; hash_alg < CFS_HASH_ALG_MAX; hash_alg++)
cfs_crypto_performance_test(hash_alg);
return 0;
}
static int adler32;
/**
* Register available hash functions
*
* \retval 0
*/
int cfs_crypto_register(void)
{
request_module("crc32c");
adler32 = cfs_crypto_adler32_register();
/* check all algorithms and do performance test */
cfs_crypto_test_hashes();
return 0;
}
/**
* Unregister previously registered hash functions
*/
void cfs_crypto_unregister(void)
{
if (adler32 == 0)
cfs_crypto_adler32_unregister();
}
|