]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/staging/pohmelfs/crypto.c
Merge branch 'omap-pool'
[linux-2.6-omap-h63xx.git] / drivers / staging / pohmelfs / crypto.c
diff --git a/drivers/staging/pohmelfs/crypto.c b/drivers/staging/pohmelfs/crypto.c
new file mode 100644 (file)
index 0000000..31d765d
--- /dev/null
@@ -0,0 +1,880 @@
+/*
+ * 2007+ Copyright (c) Evgeniy Polyakov <zbr@ioremap.net>
+ * All rights reserved.
+ *
+ * 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.
+ *
+ * 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 for more details.
+ */
+
+#include <linux/crypto.h>
+#include <linux/highmem.h>
+#include <linux/kthread.h>
+#include <linux/pagemap.h>
+#include <linux/slab.h>
+
+#include "netfs.h"
+
+static struct crypto_hash *pohmelfs_init_hash(struct pohmelfs_sb *psb)
+{
+       int err;
+       struct crypto_hash *hash;
+
+       hash = crypto_alloc_hash(psb->hash_string, 0, CRYPTO_ALG_ASYNC);
+       if (IS_ERR(hash)) {
+               err = PTR_ERR(hash);
+               dprintk("%s: idx: %u: failed to allocate hash '%s', err: %d.\n",
+                               __func__, psb->idx, psb->hash_string, err);
+               goto err_out_exit;
+       }
+
+       psb->crypto_attached_size = crypto_hash_digestsize(hash);
+
+       if (!psb->hash_keysize)
+               return hash;
+
+       err = crypto_hash_setkey(hash, psb->hash_key, psb->hash_keysize);
+       if (err) {
+               dprintk("%s: idx: %u: failed to set key for hash '%s', err: %d.\n",
+                               __func__, psb->idx, psb->hash_string, err);
+               goto err_out_free;
+       }
+
+       return hash;
+
+err_out_free:
+       crypto_free_hash(hash);
+err_out_exit:
+       return ERR_PTR(err);
+}
+
+static struct crypto_ablkcipher *pohmelfs_init_cipher(struct pohmelfs_sb *psb)
+{
+       int err = -EINVAL;
+       struct crypto_ablkcipher *cipher;
+
+       if (!psb->cipher_keysize)
+               goto err_out_exit;
+
+       cipher = crypto_alloc_ablkcipher(psb->cipher_string, 0, 0);
+       if (IS_ERR(cipher)) {
+               err = PTR_ERR(cipher);
+               dprintk("%s: idx: %u: failed to allocate cipher '%s', err: %d.\n",
+                               __func__, psb->idx, psb->cipher_string, err);
+               goto err_out_exit;
+       }
+
+       crypto_ablkcipher_clear_flags(cipher, ~0);
+
+       err = crypto_ablkcipher_setkey(cipher, psb->cipher_key, psb->cipher_keysize);
+       if (err) {
+               dprintk("%s: idx: %u: failed to set key for cipher '%s', err: %d.\n",
+                               __func__, psb->idx, psb->cipher_string, err);
+               goto err_out_free;
+       }
+
+       return cipher;
+
+err_out_free:
+       crypto_free_ablkcipher(cipher);
+err_out_exit:
+       return ERR_PTR(err);
+}
+
+int pohmelfs_crypto_engine_init(struct pohmelfs_crypto_engine *e, struct pohmelfs_sb *psb)
+{
+       int err;
+
+       e->page_num = 0;
+
+       e->size = PAGE_SIZE;
+       e->data = kmalloc(e->size, GFP_KERNEL);
+       if (!e->data) {
+               err = -ENOMEM;
+               goto err_out_exit;
+       }
+
+       if (psb->hash_string) {
+               e->hash = pohmelfs_init_hash(psb);
+               if (IS_ERR(e->hash)) {
+                       err = PTR_ERR(e->hash);
+                       e->hash = NULL;
+                       goto err_out_free;
+               }
+       }
+
+       if (psb->cipher_string) {
+               e->cipher = pohmelfs_init_cipher(psb);
+               if (IS_ERR(e->cipher)) {
+                       err = PTR_ERR(e->cipher);
+                       e->cipher = NULL;
+                       goto err_out_free_hash;
+               }
+       }
+
+       return 0;
+
+err_out_free_hash:
+       crypto_free_hash(e->hash);
+err_out_free:
+       kfree(e->data);
+err_out_exit:
+       return err;
+}
+
+void pohmelfs_crypto_engine_exit(struct pohmelfs_crypto_engine *e)
+{
+       if (e->hash)
+               crypto_free_hash(e->hash);
+       if (e->cipher)
+               crypto_free_ablkcipher(e->cipher);
+       kfree(e->data);
+}
+
+static void pohmelfs_crypto_complete(struct crypto_async_request *req, int err)
+{
+       struct pohmelfs_crypto_completion *c = req->data;
+
+       if (err == -EINPROGRESS)
+               return;
+
+       dprintk("%s: req: %p, err: %d.\n", __func__, req, err);
+       c->error = err;
+       complete(&c->complete);
+}
+
+static int pohmelfs_crypto_process(struct ablkcipher_request *req,
+               struct scatterlist *sg_dst, struct scatterlist *sg_src,
+               void *iv, int enc, unsigned long timeout)
+{
+       struct pohmelfs_crypto_completion complete;
+       int err;
+
+       init_completion(&complete.complete);
+       complete.error = -EINPROGRESS;
+
+       ablkcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG,
+                                       pohmelfs_crypto_complete, &complete);
+
+       ablkcipher_request_set_crypt(req, sg_src, sg_dst, sg_src->length, iv);
+
+       if (enc)
+               err = crypto_ablkcipher_encrypt(req);
+       else
+               err = crypto_ablkcipher_decrypt(req);
+
+       switch (err) {
+               case -EINPROGRESS:
+               case -EBUSY:
+                       err = wait_for_completion_interruptible_timeout(&complete.complete,
+                                       timeout);
+                       if (!err)
+                               err = -ETIMEDOUT;
+                       else
+                               err = complete.error;
+                       break;
+               default:
+                       break;
+       }
+
+       return err;
+}
+
+int pohmelfs_crypto_process_input_data(struct pohmelfs_crypto_engine *e, u64 cmd_iv,
+               void *data, struct page *page, unsigned int size)
+{
+       int err;
+       struct scatterlist sg;
+
+       if (!e->cipher && !e->hash)
+               return 0;
+
+       dprintk("%s: eng: %p, iv: %llx, data: %p, page: %p/%lu, size: %u.\n",
+               __func__, e, cmd_iv, data, page, (page)?page->index:0, size);
+
+       if (data) {
+               sg_init_one(&sg, data, size);
+       } else {
+               sg_init_table(&sg, 1);
+               sg_set_page(&sg, page, size, 0);
+       }
+
+       if (e->cipher) {
+               struct ablkcipher_request *req = e->data + crypto_hash_digestsize(e->hash);
+               u8 iv[32];
+
+               memset(iv, 0, sizeof(iv));
+               memcpy(iv, &cmd_iv, sizeof(cmd_iv));
+
+               ablkcipher_request_set_tfm(req, e->cipher);
+
+               err = pohmelfs_crypto_process(req, &sg, &sg, iv, 0, e->timeout);
+               if (err)
+                       goto err_out_exit;
+       }
+
+       if (e->hash) {
+               struct hash_desc desc;
+               void *dst = e->data + e->size/2;
+
+               desc.tfm = e->hash;
+               desc.flags = 0;
+
+               err = crypto_hash_init(&desc);
+               if (err)
+                       goto err_out_exit;
+
+               err = crypto_hash_update(&desc, &sg, size);
+               if (err)
+                       goto err_out_exit;
+
+               err = crypto_hash_final(&desc, dst);
+               if (err)
+                       goto err_out_exit;
+
+               err = !!memcmp(dst, e->data, crypto_hash_digestsize(e->hash));
+
+               if (err) {
+#ifdef CONFIG_POHMELFS_DEBUG
+                       unsigned int i;
+                       unsigned char *recv = e->data, *calc = dst;
+
+                       dprintk("%s: eng: %p, hash: %p, cipher: %p: iv : %llx, hash mismatch (recv/calc): ",
+                                       __func__, e, e->hash, e->cipher, cmd_iv);
+                       for (i=0; i<crypto_hash_digestsize(e->hash); ++i) {
+#if 0
+                               dprintka("%02x ", recv[i]);
+                               if (recv[i] != calc[i]) {
+                                       dprintka("| calc byte: %02x.\n", calc[i]);
+                                       break;
+                               }
+#else
+                               dprintka("%02x/%02x ", recv[i], calc[i]);
+#endif
+                       }
+                       dprintk("\n");
+#endif
+                       goto err_out_exit;
+               } else {
+                       dprintk("%s: eng: %p, hash: %p, cipher: %p: hashes matched.\n",
+                                       __func__, e, e->hash, e->cipher);
+               }
+       }
+
+       dprintk("%s: eng: %p, size: %u, hash: %p, cipher: %p: completed.\n",
+                       __func__, e, e->size, e->hash, e->cipher);
+
+       return 0;
+
+err_out_exit:
+       dprintk("%s: eng: %p, hash: %p, cipher: %p: err: %d.\n",
+                       __func__, e, e->hash, e->cipher, err);
+       return err;
+}
+
+static int pohmelfs_trans_iter(struct netfs_trans *t, struct pohmelfs_crypto_engine *e,
+               int (* iterator) (struct pohmelfs_crypto_engine *e,
+                                 struct scatterlist *dst,
+                                 struct scatterlist *src))
+{
+       void *data = t->iovec.iov_base + sizeof(struct netfs_cmd) + t->psb->crypto_attached_size;
+       unsigned int size = t->iovec.iov_len - sizeof(struct netfs_cmd) - t->psb->crypto_attached_size;
+       struct netfs_cmd *cmd = data;
+       unsigned int sz, pages = t->attached_pages, i, csize, cmd_cmd, dpage_idx;
+       struct scatterlist sg_src, sg_dst;
+       int err;
+
+       while (size) {
+               cmd = data;
+               cmd_cmd = __be16_to_cpu(cmd->cmd);
+               csize = __be32_to_cpu(cmd->size);
+               cmd->iv = __cpu_to_be64(e->iv);
+
+               if (cmd_cmd == NETFS_READ_PAGES || cmd_cmd == NETFS_READ_PAGE)
+                       csize = __be16_to_cpu(cmd->ext);
+
+               sz = csize + __be16_to_cpu(cmd->cpad) + sizeof(struct netfs_cmd);
+
+               dprintk("%s: size: %u, sz: %u, cmd_size: %u, cmd_cpad: %u.\n",
+                               __func__, size, sz, __be32_to_cpu(cmd->size), __be16_to_cpu(cmd->cpad));
+
+               data += sz;
+               size -= sz;
+
+               sg_init_one(&sg_src, cmd->data, sz - sizeof(struct netfs_cmd));
+               sg_init_one(&sg_dst, cmd->data, sz - sizeof(struct netfs_cmd));
+
+               err = iterator(e, &sg_dst, &sg_src);
+               if (err)
+                       return err;
+       }
+
+       if (!pages)
+               return 0;
+
+       dpage_idx = 0;
+       for (i=0; i<t->page_num; ++i) {
+               struct page *page = t->pages[i];
+               struct page *dpage = e->pages[dpage_idx];
+
+               if (!page)
+                       continue;
+
+               sg_init_table(&sg_src, 1);
+               sg_init_table(&sg_dst, 1);
+               sg_set_page(&sg_src, page, page_private(page), 0);
+               sg_set_page(&sg_dst, dpage, page_private(page), 0);
+
+               err = iterator(e, &sg_dst, &sg_src);
+               if (err)
+                       return err;
+
+               pages--;
+               if (!pages)
+                       break;
+               dpage_idx++;
+       }
+
+       return 0;
+}
+
+static int pohmelfs_encrypt_iterator(struct pohmelfs_crypto_engine *e,
+               struct scatterlist *sg_dst, struct scatterlist *sg_src)
+{
+       struct ablkcipher_request *req = e->data;
+       u8 iv[32];
+
+       memset(iv, 0, sizeof(iv));
+
+       memcpy(iv, &e->iv, sizeof(e->iv));
+
+       return pohmelfs_crypto_process(req, sg_dst, sg_src, iv, 1, e->timeout);
+}
+
+static int pohmelfs_encrypt(struct pohmelfs_crypto_thread *tc)
+{
+       struct netfs_trans *t = tc->trans;
+       struct pohmelfs_crypto_engine *e = &tc->eng;
+       struct ablkcipher_request *req = e->data;
+
+       memset(req, 0, sizeof(struct ablkcipher_request));
+       ablkcipher_request_set_tfm(req, e->cipher);
+
+       e->iv = pohmelfs_gen_iv(t);
+
+       return pohmelfs_trans_iter(t, e, pohmelfs_encrypt_iterator);
+}
+
+static int pohmelfs_hash_iterator(struct pohmelfs_crypto_engine *e,
+               struct scatterlist *sg_dst, struct scatterlist *sg_src)
+{
+       return crypto_hash_update(e->data, sg_src, sg_src->length);
+}
+
+static int pohmelfs_hash(struct pohmelfs_crypto_thread *tc)
+{
+       struct pohmelfs_crypto_engine *e = &tc->eng;
+       struct hash_desc *desc = e->data;
+       unsigned char *dst = tc->trans->iovec.iov_base + sizeof(struct netfs_cmd);
+       int err;
+
+       desc->tfm = e->hash;
+       desc->flags = 0;
+
+       err = crypto_hash_init(desc);
+       if (err)
+               return err;
+
+       err = pohmelfs_trans_iter(tc->trans, e, pohmelfs_hash_iterator);
+       if (err)
+               return err;
+
+       err = crypto_hash_final(desc, dst);
+       if (err)
+               return err;
+
+       {
+               unsigned int i;
+               dprintk("%s: ", __func__);
+               for (i=0; i<tc->psb->crypto_attached_size; ++i)
+                       dprintka("%02x ", dst[i]);
+               dprintka("\n");
+       }
+
+       return 0;
+}
+
+static void pohmelfs_crypto_pages_free(struct pohmelfs_crypto_engine *e)
+{
+       unsigned int i;
+
+       for (i=0; i<e->page_num; ++i)
+               __free_page(e->pages[i]);
+       kfree(e->pages);
+}
+
+static int pohmelfs_crypto_pages_alloc(struct pohmelfs_crypto_engine *e, struct pohmelfs_sb *psb)
+{
+       unsigned int i;
+
+       e->pages = kmalloc(psb->trans_max_pages * sizeof(struct page *), GFP_KERNEL);
+       if (!e->pages)
+               return -ENOMEM;
+
+       for (i=0; i<psb->trans_max_pages; ++i) {
+               e->pages[i] = alloc_page(GFP_KERNEL);
+               if (!e->pages[i])
+                       break;
+       }
+
+       e->page_num = i;
+       if (!e->page_num)
+               goto err_out_free;
+
+       return 0;
+
+err_out_free:
+       kfree(e->pages);
+       return -ENOMEM;
+}
+
+static void pohmelfs_sys_crypto_exit_one(struct pohmelfs_crypto_thread *t)
+{
+       struct pohmelfs_sb *psb = t->psb;
+
+       if (t->thread)
+               kthread_stop(t->thread);
+
+       mutex_lock(&psb->crypto_thread_lock);
+       list_del(&t->thread_entry);
+       psb->crypto_thread_num--;
+       mutex_unlock(&psb->crypto_thread_lock);
+
+       pohmelfs_crypto_engine_exit(&t->eng);
+       pohmelfs_crypto_pages_free(&t->eng);
+       kfree(t);
+}
+
+static int pohmelfs_crypto_finish(struct netfs_trans *t, struct pohmelfs_sb *psb, int err)
+{
+       struct netfs_cmd *cmd = t->iovec.iov_base;
+       netfs_convert_cmd(cmd);
+
+       if (likely(!err))
+               err = netfs_trans_finish_send(t, psb);
+
+       t->result = err;
+       netfs_trans_put(t);
+
+       return err;
+}
+
+void pohmelfs_crypto_thread_make_ready(struct pohmelfs_crypto_thread *th)
+{
+       struct pohmelfs_sb *psb = th->psb;
+
+       th->page = NULL;
+       th->trans = NULL;
+
+       mutex_lock(&psb->crypto_thread_lock);
+       list_move_tail(&th->thread_entry, &psb->crypto_ready_list);
+       mutex_unlock(&psb->crypto_thread_lock);
+       wake_up(&psb->wait);
+}
+
+static int pohmelfs_crypto_thread_trans(struct pohmelfs_crypto_thread *t)
+{
+       struct netfs_trans *trans;
+       int err = 0;
+
+       trans = t->trans;
+       trans->eng = NULL;
+
+       if (t->eng.hash) {
+               err = pohmelfs_hash(t);
+               if (err)
+                       goto out_complete;
+       }
+
+       if (t->eng.cipher) {
+               err = pohmelfs_encrypt(t);
+               if (err)
+                       goto out_complete;
+               trans->eng = &t->eng;
+       }
+
+out_complete:
+       t->page = NULL;
+       t->trans = NULL;
+
+       if (!trans->eng)
+               pohmelfs_crypto_thread_make_ready(t);
+
+       pohmelfs_crypto_finish(trans, t->psb, err);
+       return err;
+}
+
+static int pohmelfs_crypto_thread_page(struct pohmelfs_crypto_thread *t)
+{
+       struct pohmelfs_crypto_engine *e = &t->eng;
+       struct page *page = t->page;
+       int err;
+
+       WARN_ON(!PageChecked(page));
+
+       err = pohmelfs_crypto_process_input_data(e, e->iv, NULL, page, t->size);
+       if (!err)
+               SetPageUptodate(page);
+       else
+               SetPageError(page);
+       unlock_page(page);
+       page_cache_release(page);
+
+       pohmelfs_crypto_thread_make_ready(t);
+
+       return err;
+}
+
+static int pohmelfs_crypto_thread_func(void *data)
+{
+       struct pohmelfs_crypto_thread *t = data;
+
+       while (!kthread_should_stop()) {
+               wait_event_interruptible(t->wait, kthread_should_stop() ||
+                               t->trans || t->page);
+
+               if (kthread_should_stop())
+                       break;
+
+               if (!t->trans && !t->page)
+                       continue;
+
+               dprintk("%s: thread: %p, trans: %p, page: %p.\n",
+                               __func__, t, t->trans, t->page);
+
+               if (t->trans)
+                       pohmelfs_crypto_thread_trans(t);
+               else if (t->page)
+                       pohmelfs_crypto_thread_page(t);
+       }
+
+       return 0;
+}
+
+static void pohmelfs_crypto_flush(struct pohmelfs_sb *psb, struct list_head *head)
+{
+       while (!list_empty(head)) {
+               struct pohmelfs_crypto_thread *t = NULL;
+
+               mutex_lock(&psb->crypto_thread_lock);
+               if (!list_empty(head)) {
+                       t = list_first_entry(head, struct pohmelfs_crypto_thread, thread_entry);
+                       list_del_init(&t->thread_entry);
+               }
+               mutex_unlock(&psb->crypto_thread_lock);
+
+               if (t)
+                       pohmelfs_sys_crypto_exit_one(t);
+       }
+}
+
+static void pohmelfs_sys_crypto_exit(struct pohmelfs_sb *psb)
+{
+       while (!list_empty(&psb->crypto_active_list) || !list_empty(&psb->crypto_ready_list)) {
+               dprintk("%s: crypto_thread_num: %u.\n", __func__, psb->crypto_thread_num);
+               pohmelfs_crypto_flush(psb, &psb->crypto_active_list);
+               pohmelfs_crypto_flush(psb, &psb->crypto_ready_list);
+       }
+}
+
+static int pohmelfs_sys_crypto_init(struct pohmelfs_sb *psb)
+{
+       unsigned int i;
+       struct pohmelfs_crypto_thread *t;
+       struct pohmelfs_config *c;
+       struct netfs_state *st;
+       int err;
+
+       list_for_each_entry(c, &psb->state_list, config_entry) {
+               st = &c->state;
+
+               err = pohmelfs_crypto_engine_init(&st->eng, psb);
+               if (err)
+                       goto err_out_exit;
+
+               dprintk("%s: st: %p, eng: %p, hash: %p, cipher: %p.\n",
+                               __func__, st, &st->eng, &st->eng.hash, &st->eng.cipher);
+       }
+
+       for (i=0; i<psb->crypto_thread_num; ++i) {
+               err = -ENOMEM;
+               t = kzalloc(sizeof(struct pohmelfs_crypto_thread), GFP_KERNEL);
+               if (!t)
+                       goto err_out_free_state_engines;
+
+               init_waitqueue_head(&t->wait);
+
+               t->psb = psb;
+               t->trans = NULL;
+               t->eng.thread = t;
+
+               err = pohmelfs_crypto_engine_init(&t->eng, psb);
+               if (err)
+                       goto err_out_free_state_engines;
+
+               err = pohmelfs_crypto_pages_alloc(&t->eng, psb);
+               if (err)
+                       goto err_out_free;
+
+               t->thread = kthread_run(pohmelfs_crypto_thread_func, t,
+                               "pohmelfs-crypto-%d-%d", psb->idx, i);
+               if (IS_ERR(t->thread)) {
+                       err = PTR_ERR(t->thread);
+                       t->thread = NULL;
+                       goto err_out_free;
+               }
+
+               if (t->eng.cipher)
+                       psb->crypto_align_size = crypto_ablkcipher_blocksize(t->eng.cipher);
+
+               mutex_lock(&psb->crypto_thread_lock);
+               list_add_tail(&t->thread_entry, &psb->crypto_ready_list);
+               mutex_unlock(&psb->crypto_thread_lock);
+       }
+
+       psb->crypto_thread_num = i;
+       return 0;
+
+err_out_free:
+       pohmelfs_sys_crypto_exit_one(t);
+err_out_free_state_engines:
+       list_for_each_entry(c, &psb->state_list, config_entry) {
+               st = &c->state;
+               pohmelfs_crypto_engine_exit(&st->eng);
+       }
+err_out_exit:
+       pohmelfs_sys_crypto_exit(psb);
+       return err;
+}
+
+void pohmelfs_crypto_exit(struct pohmelfs_sb *psb)
+{
+       pohmelfs_sys_crypto_exit(psb);
+
+       kfree(psb->hash_string);
+       kfree(psb->cipher_string);
+}
+
+static int pohmelfs_crypt_init_complete(struct page **pages, unsigned int page_num,
+               void *private, int err)
+{
+       struct pohmelfs_sb *psb = private;
+
+       psb->flags = -err;
+       dprintk("%s: err: %d.\n", __func__, err);
+
+       wake_up(&psb->wait);
+
+       return err;
+}
+
+static int pohmelfs_crypto_init_handshake(struct pohmelfs_sb *psb)
+{
+       struct netfs_trans *t;
+       struct netfs_crypto_capabilities *cap;
+       struct netfs_cmd *cmd;
+       char *str;
+       int err = -ENOMEM, size;
+
+       size = sizeof(struct netfs_crypto_capabilities) +
+               psb->cipher_strlen + psb->hash_strlen + 2; /* 0 bytes */
+
+       t = netfs_trans_alloc(psb, size, 0, 0);
+       if (!t)
+               goto err_out_exit;
+
+       t->complete = pohmelfs_crypt_init_complete;
+       t->private = psb;
+
+       cmd = netfs_trans_current(t);
+       cap = (struct netfs_crypto_capabilities *)(cmd + 1);
+       str = (char *)(cap + 1);
+
+       cmd->cmd = NETFS_CAPABILITIES;
+       cmd->id = POHMELFS_CRYPTO_CAPABILITIES;
+       cmd->size = size;
+       cmd->start = 0;
+       cmd->ext = 0;
+       cmd->csize = 0;
+
+       netfs_convert_cmd(cmd);
+       netfs_trans_update(cmd, t, size);
+
+       cap->hash_strlen = psb->hash_strlen;
+       if (cap->hash_strlen) {
+               sprintf(str, "%s", psb->hash_string);
+               str += cap->hash_strlen;
+       }
+
+       cap->cipher_strlen = psb->cipher_strlen;
+       cap->cipher_keysize = psb->cipher_keysize;
+       if (cap->cipher_strlen)
+               sprintf(str, "%s", psb->cipher_string);
+
+       netfs_convert_crypto_capabilities(cap);
+
+       psb->flags = ~0;
+       err = netfs_trans_finish(t, psb);
+       if (err)
+               goto err_out_exit;
+
+       err = wait_event_interruptible_timeout(psb->wait, (psb->flags != ~0),
+                       psb->wait_on_page_timeout);
+       if (!err)
+               err = -ETIMEDOUT;
+       else
+               err = -psb->flags;
+
+       if (!err)
+               psb->perform_crypto = 1;
+       psb->flags = 0;
+
+       /*
+        * At this point NETFS_CAPABILITIES response command
+        * should setup superblock in a way, which is acceptible
+        * for both client and server, so if server refuses connection,
+        * it will send error in transaction response.
+        */
+
+       if (err)
+               goto err_out_exit;
+
+       return 0;
+
+err_out_exit:
+       return err;
+}
+
+int pohmelfs_crypto_init(struct pohmelfs_sb *psb)
+{
+       int err;
+
+       if (!psb->cipher_string && !psb->hash_string)
+               return 0;
+
+       err = pohmelfs_crypto_init_handshake(psb);
+       if (err)
+               return err;
+
+       err = pohmelfs_sys_crypto_init(psb);
+       if (err)
+               return err;
+
+       return 0;
+}
+
+static int pohmelfs_crypto_thread_get(struct pohmelfs_sb *psb,
+               int (* action)(struct pohmelfs_crypto_thread *t, void *data), void *data)
+{
+       struct pohmelfs_crypto_thread *t = NULL;
+       int err;
+
+       while (!t) {
+               err = wait_event_interruptible_timeout(psb->wait,
+                               !list_empty(&psb->crypto_ready_list),
+                               psb->wait_on_page_timeout);
+
+               t = NULL;
+               err = 0;
+               mutex_lock(&psb->crypto_thread_lock);
+               if (!list_empty(&psb->crypto_ready_list)) {
+                       t = list_entry(psb->crypto_ready_list.prev,
+                                       struct pohmelfs_crypto_thread,
+                                       thread_entry);
+
+                       list_move_tail(&t->thread_entry,
+                                       &psb->crypto_active_list);
+
+                       action(t, data);
+                       wake_up(&t->wait);
+
+               }
+               mutex_unlock(&psb->crypto_thread_lock);
+       }
+
+       return err;
+}
+
+static int pohmelfs_trans_crypt_action(struct pohmelfs_crypto_thread *t, void *data)
+{
+       struct netfs_trans *trans = data;
+
+       netfs_trans_get(trans);
+       t->trans = trans;
+
+       dprintk("%s: t: %p, gen: %u, thread: %p.\n", __func__, trans, trans->gen, t);
+       return 0;
+}
+
+int pohmelfs_trans_crypt(struct netfs_trans *trans, struct pohmelfs_sb *psb)
+{
+       if ((!psb->hash_string && !psb->cipher_string) || !psb->perform_crypto) {
+               netfs_trans_get(trans);
+               return pohmelfs_crypto_finish(trans, psb, 0);
+       }
+
+       return pohmelfs_crypto_thread_get(psb, pohmelfs_trans_crypt_action, trans);
+}
+
+struct pohmelfs_crypto_input_action_data
+{
+       struct page                     *page;
+       struct pohmelfs_crypto_engine   *e;
+       u64                             iv;
+       unsigned int                    size;
+};
+
+static int pohmelfs_crypt_input_page_action(struct pohmelfs_crypto_thread *t, void *data)
+{
+       struct pohmelfs_crypto_input_action_data *act = data;
+
+       memcpy(t->eng.data, act->e->data, t->psb->crypto_attached_size);
+
+       t->size = act->size;
+       t->eng.iv = act->iv;
+
+       t->page = act->page;
+       return 0;
+}
+
+int pohmelfs_crypto_process_input_page(struct pohmelfs_crypto_engine *e,
+               struct page *page, unsigned int size, u64 iv)
+{
+       struct inode *inode = page->mapping->host;
+       struct pohmelfs_crypto_input_action_data act;
+       int err = -ENOENT;
+
+       act.page = page;
+       act.e = e;
+       act.size = size;
+       act.iv = iv;
+
+       err = pohmelfs_crypto_thread_get(POHMELFS_SB(inode->i_sb),
+                       pohmelfs_crypt_input_page_action, &act);
+       if (err)
+               goto err_out_exit;
+
+       return 0;
+
+err_out_exit:
+       SetPageUptodate(page);
+       page_cache_release(page);
+
+       return err;
+}