345 lines
8.3 KiB
C
Executable File
345 lines
8.3 KiB
C
Executable File
/*
|
|
* Copyright (C) 2018 Samsung Electronics Co., Ltd.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Functions related to blk-crypt(for inline encryption) handling
|
|
*
|
|
* NOTE :
|
|
* blk-crypt implementation not depends on a inline encryption hardware.
|
|
*
|
|
* TODO :
|
|
* We need to agonize what lock we use between spin_lock_irq and spin_lock
|
|
* for algorithm_manager.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/blkdev.h>
|
|
#include <crypto/skcipher.h>
|
|
|
|
#define BLK_CRYPT_ALG_NAMELEN_MAX (15)
|
|
#define BLK_CRYPT_VERSION "1.0.0"
|
|
#define BLK_CRYPT_MODE_PRIVATE (127)
|
|
|
|
struct blk_crypt_algorithm {
|
|
struct list_head list;
|
|
atomic_t ref;
|
|
unsigned int mode;
|
|
struct block_device *bdev;
|
|
char name[BLK_CRYPT_ALG_NAMELEN_MAX+1];
|
|
const struct blk_crypt_algorithm_cbs *hw_cbs;
|
|
};
|
|
|
|
struct blk_crypt_context {
|
|
struct blk_crypt_algorithm *bc_alg;
|
|
void *bc_private;
|
|
};
|
|
|
|
struct blk_crypt_algorithm_manager {
|
|
struct list_head list;
|
|
spinlock_t lock;
|
|
atomic_t ref;
|
|
struct blk_crypt_algorithm *last_acc;
|
|
};
|
|
|
|
/* Initialize algorithm manager */
|
|
static struct blk_crypt_algorithm_manager alg_manager = {
|
|
.list = LIST_HEAD_INIT(alg_manager.list),
|
|
.lock = __SPIN_LOCK_UNLOCKED(alg_manager.lock),
|
|
.ref = ATOMIC_INIT(0),
|
|
.last_acc = NULL,
|
|
};
|
|
|
|
static struct kmem_cache *blk_crypt_cachep = NULL;
|
|
|
|
/* Static functions */
|
|
/**
|
|
* blk_crypt_alloc_context() - allocate blk_crypt_context structure
|
|
* @mode: filesystem encryption mode.
|
|
*
|
|
* Find hardware algorithm matched with specified mode and
|
|
* calling the hw callback function to allocate crypt_info structure.
|
|
*
|
|
* Return: Allocated blk_crypt_context if succeed to find algorithm mode,
|
|
* -ENOKEY otherwise.
|
|
*/
|
|
static blk_crypt_t *blk_crypt_alloc_context(struct block_device *bdev, const char *cipher_str)
|
|
{
|
|
struct blk_crypt_algorithm *alg;
|
|
struct blk_crypt_context *bctx;
|
|
int res = -ENOENT;
|
|
|
|
spin_lock_irq(&alg_manager.lock);
|
|
if (alg_manager.last_acc && !strcmp(alg_manager.last_acc->name, cipher_str)) {
|
|
alg = alg_manager.last_acc;
|
|
atomic_inc(&alg->ref);
|
|
goto unlock;
|
|
}
|
|
|
|
list_for_each_entry(alg, &alg_manager.list, list) {
|
|
if (strcmp(alg->name, cipher_str)) {
|
|
pr_info("blk-crypt: no matched alg(%s) != requested alg(%s)\n",
|
|
alg->name, cipher_str);
|
|
continue;
|
|
}
|
|
|
|
if (alg->bdev && alg->bdev != bdev) {
|
|
pr_info("blk-crypt: no matched alg(%s)->bdev != bdev\n",
|
|
cipher_str);
|
|
continue;
|
|
}
|
|
|
|
alg_manager.last_acc = alg;
|
|
atomic_inc(&alg->ref);
|
|
break;
|
|
}
|
|
unlock:
|
|
spin_unlock_irq(&alg_manager.lock);
|
|
|
|
if (alg == (void *)&alg_manager.list)
|
|
return ERR_PTR(-ENOENT);
|
|
|
|
bctx = kmem_cache_zalloc(blk_crypt_cachep, GFP_NOFS);
|
|
if (!bctx) {
|
|
res = -ENOMEM;
|
|
goto err_free;
|
|
}
|
|
|
|
bctx->bc_alg = alg;
|
|
bctx->bc_private = alg->hw_cbs->alloc();
|
|
if (IS_ERR(bctx->bc_private)) {
|
|
res = PTR_ERR(bctx->bc_private);
|
|
pr_err("blk-crypt: failed to alloc data for alg(%s), err:%d\n",
|
|
alg->name, res);
|
|
goto err_free;
|
|
}
|
|
|
|
return bctx;
|
|
err_free:
|
|
atomic_dec(&alg->ref);
|
|
if (bctx)
|
|
kmem_cache_free(blk_crypt_cachep, bctx);
|
|
return ERR_PTR(res);
|
|
}
|
|
|
|
|
|
static inline blk_crypt_t *__bio_crypt(const struct bio *bio)
|
|
{
|
|
if (bio && (bio->bi_opf & REQ_CRYPT))
|
|
return bio->bi_cryptd;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
bool blk_crypt_encrypted(const struct bio *bio)
|
|
{
|
|
return __bio_crypt(bio) ? true : false;
|
|
}
|
|
|
|
bool blk_crypt_mergeable(const struct bio *a, const struct bio *b)
|
|
{
|
|
#ifdef CONFIG_BLK_DEV_CRYPT
|
|
if (__bio_crypt(a) != __bio_crypt(b))
|
|
return false;
|
|
|
|
#ifdef CONFIG_BLK_DEV_CRYPT_DUN
|
|
if (__bio_crypt(a)) {
|
|
if (!bio_dun(a) ^ !bio_dun(b))
|
|
return false;
|
|
if (bio_dun(a) && bio_end_dun(a) != bio_dun(b))
|
|
return false;
|
|
}
|
|
#endif
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
/* Filesystem APIs */
|
|
blk_crypt_t *blk_crypt_get_context(struct block_device *bdev, const char *cipher_str)
|
|
{
|
|
struct blk_crypt_t *bctx;
|
|
bctx = blk_crypt_alloc_context(bdev, cipher_str);
|
|
if (IS_ERR(bctx)) {
|
|
pr_debug("error allocating diskciher '%s' err: %d",
|
|
cipher_str, PTR_ERR(bctx));
|
|
return bctx;
|
|
}
|
|
|
|
return bctx;
|
|
}
|
|
|
|
unsigned char *blk_crypt_get_key(blk_crypt_t *bc_ctx)
|
|
{
|
|
struct blk_crypt_context *bctx = bc_ctx;
|
|
|
|
if (!bctx)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
return bctx->bc_alg->hw_cbs->get_key(bctx->bc_private);
|
|
}
|
|
|
|
int blk_crypt_set_key(blk_crypt_t *bc_ctx, u8 *raw_key, u32 keysize, void *priv)
|
|
{
|
|
struct blk_crypt_context *bctx = bc_ctx;
|
|
|
|
if (!bctx)
|
|
return -EINVAL;
|
|
|
|
return bctx->bc_alg->hw_cbs->set_key(bctx->bc_private, raw_key, keysize, priv);
|
|
}
|
|
|
|
void *blk_crypt_get_data(blk_crypt_t *bc_ctx)
|
|
{
|
|
struct blk_crypt_context *bctx = bc_ctx;
|
|
return bctx->bc_private;
|
|
}
|
|
|
|
void blk_crypt_put_context(blk_crypt_t *bc_ctx)
|
|
{
|
|
struct blk_crypt_context *bctx = bc_ctx;
|
|
|
|
if (!bctx)
|
|
return;
|
|
|
|
bctx->bc_alg->hw_cbs->free(bctx->bc_private);
|
|
atomic_dec(&(bctx->bc_alg->ref));
|
|
kmem_cache_free(blk_crypt_cachep, bctx);
|
|
}
|
|
|
|
/* H/W algorithm APIs */
|
|
static int blk_crypt_initialize()
|
|
{
|
|
if (likely(blk_crypt_cachep))
|
|
return 0;
|
|
|
|
blk_crypt_cachep = KMEM_CACHE(blk_crypt_context, SLAB_RECLAIM_ACCOUNT);
|
|
if (!blk_crypt_cachep)
|
|
return -ENOMEM;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* blk_crypt_alg_register() - register a hw algorithm in algorithm manager
|
|
* @name: name of algorithm
|
|
* @mode: filesystem encryption mode
|
|
* @hw_cbs: hardware callback functions
|
|
*
|
|
* A mode and name must be unique value so that these values are used to
|
|
* differentiate each algorithm. If the algorithm manager finds duplicated
|
|
* mode or name in algorithm list, it considers that as existing algorithm.
|
|
*
|
|
* Return: hw algorithm pointer on success,
|
|
* -EEXIST if already registered.
|
|
*/
|
|
void *blk_crypt_alg_register(struct block_device *bdev, const char *name,
|
|
const unsigned int mode, const struct blk_crypt_algorithm_cbs *hw_cbs)
|
|
{
|
|
struct blk_crypt_algorithm *hw_alg, *new_alg;
|
|
|
|
if (unlikely(blk_crypt_initialize())) {
|
|
pr_err("blk-crypt: failed to initialize\n");
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
if (!name || strlen(name) > BLK_CRYPT_ALG_NAMELEN_MAX) {
|
|
pr_err("blk-crypt: invalid algorithm name or namelen\n");
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
/* Allocate new dhe_algorithm */
|
|
new_alg = kzalloc(sizeof(struct blk_crypt_algorithm), GFP_KERNEL);
|
|
if (!new_alg)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
/* Initialize new hw_algorithm */
|
|
strncpy(new_alg->name, name, BLK_CRYPT_ALG_NAMELEN_MAX);
|
|
new_alg->bdev = bdev;
|
|
new_alg->mode = mode;
|
|
new_alg->hw_cbs = hw_cbs;
|
|
INIT_LIST_HEAD(&new_alg->list);
|
|
|
|
/* Check the existence of same thing. */
|
|
spin_lock_irq(&alg_manager.lock);
|
|
list_for_each_entry(hw_alg, &alg_manager.list, list) {
|
|
/* an algorithm can be allocated for multiple devices */
|
|
if (hw_alg->bdev && bdev && (hw_alg->bdev != bdev))
|
|
continue;
|
|
if (hw_alg->mode == mode)
|
|
break;
|
|
if (!strcmp(hw_alg->name, name))
|
|
break;
|
|
}
|
|
|
|
/* Already exist, can not register new one */
|
|
if (&alg_manager.list != &hw_alg->list) {
|
|
spin_unlock_irq(&alg_manager.lock);
|
|
pr_err("blk-crypt: algorithm(%s) already exists\n", name);
|
|
kfree(new_alg);
|
|
return ERR_PTR(-EEXIST);
|
|
}
|
|
|
|
/* Add to algorithm manager */
|
|
list_add(&new_alg->list, &alg_manager.list);
|
|
spin_unlock_irq(&alg_manager.lock);
|
|
pr_info("blk_crypt: registed algorithm(%s)\n", name);
|
|
|
|
return new_alg;
|
|
}
|
|
EXPORT_SYMBOL(blk_crypt_alg_register);
|
|
|
|
/**
|
|
* blk_crypt_alg_unregister() - unregister hw algorithm from the algorithm manager
|
|
* @handle: The pointer of hw algorithm that returned by blk_crypt_alg_register().
|
|
*
|
|
* Free and unregister specified handle if there is no ref count.
|
|
*/
|
|
int blk_crypt_alg_unregister(void *handle)
|
|
{
|
|
struct blk_crypt_algorithm *alg;
|
|
|
|
if (!handle)
|
|
return -EINVAL;
|
|
|
|
alg = (struct blk_crypt_algorithm *)handle;
|
|
|
|
/* prevent unregister if an algorithm is being referenced */
|
|
if (atomic_read(&alg->ref))
|
|
return -EBUSY;
|
|
|
|
/* just unlink this algorithm from the manager */
|
|
spin_lock_irq(&alg_manager.lock);
|
|
list_del_init(&alg->list);
|
|
spin_unlock_irq(&alg_manager.lock);
|
|
|
|
kfree(handle);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(blk_crypt_alg_unregister);
|
|
|
|
/* Blk-crypt core functions */
|
|
static int __init blk_crypt_init(void)
|
|
{
|
|
pr_info("%s\n", __func__);
|
|
blk_crypt_initialize();
|
|
return 0;
|
|
}
|
|
|
|
static void __exit blk_crypt_exit(void)
|
|
{
|
|
pr_info("%s\n", __func__);
|
|
if (atomic_read(&alg_manager.ref)) {
|
|
pr_warn("blk-crypt: remailed %d registered algorithm\n",
|
|
atomic_read(&alg_manager.ref));
|
|
}
|
|
|
|
kmem_cache_destroy(blk_crypt_cachep);
|
|
blk_crypt_cachep = NULL;
|
|
}
|
|
|
|
module_init(blk_crypt_init);
|
|
module_exit(blk_crypt_exit);
|