blob: 0215d82350ab0760b234ee4b3a22d22d6547af06 [file] [log] [blame]
/*
* Key blob driver based on CAAM hardware
*
* Copyright (C) 2015 Freescale Semiconductor, Inc.
*/
#include <linux/of_irq.h>
#include <linux/of_address.h>
#include "compat.h"
#include "regs.h"
#include "jr.h"
#include "desc.h"
#include "intern.h"
#include "sm.h"
#include "caam_keyblob.h"
#define INITIAL_DESCSZ 16 /* size of tmp buffer for descriptor const. */
/**
* struct kb_device - the metadata of the caam key blob device node
* @dev: the actual misc device
*/
struct kb_device {
struct miscdevice misc_dev;
struct device *jr_dev;
};
/*
* Pseudo-synchronous ring access functions for carrying out key
* encapsulation and decapsulation
*/
struct sm_key_job_result {
int error;
struct completion completion;
};
static struct kb_device *kb_dev;
static struct kb_device *kb_device_create(void);
static int kb_device_destroy(struct kb_device *kb_dev);
static int kb_open(struct inode *inode, struct file *file);
static int kb_release(struct inode *inode, struct file *file);
static void sm_key_job_done(struct device *dev, u32 *desc,
u32 err, void *context);
static int gen_mem_encap(struct device *jr_dev, void __user *secretbuf,
int keylen, void __user *kmodbuf, void __user *outbuf);
static int gen_mem_decap(struct device *jr_dev, void __user *keyblobbuf,
int bloblen, void __user *kmodbuf, void __user *outbuf);
static long kb_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
static int caam_keyblob_probe(struct platform_device *pdev);
static int caam_keyblob_remove(struct platform_device *pdev);
static int kb_open(struct inode *inode, struct file *file)
{
struct miscdevice *miscdev = file->private_data;
struct kb_device *dev = container_of(miscdev, struct kb_device, misc_dev);
struct device *jr_dev;
if (!dev->jr_dev) {
jr_dev = caam_jr_alloc();
if (IS_ERR(jr_dev)) {
pr_err("Job Ring Device allocation for transform failed\n");
return -ENOMEM;
}
pr_info("Allocate a job ring device\n");
dev->jr_dev = jr_dev;
}
else {
pr_err("Already created a job ring device");
return -EPERM;
}
return 0;
}
static int kb_release(struct inode *inode, struct file *file)
{
struct miscdevice *miscdev = file->private_data;
struct kb_device *dev = container_of(miscdev, struct kb_device, misc_dev);
if (dev && dev->jr_dev) {
caam_jr_free(dev->jr_dev);
pr_info("Free a job ring device\n");
dev->jr_dev = NULL;
}
return 0;
}
static void sm_key_job_done(struct device *dev, u32 *desc,
u32 err, void *context)
{
struct sm_key_job_result *res = context;
res->error = err; /* save off the error for postprocessing */
complete(&res->completion); /* mark us complete */
}
/*
* Construct a blob encapsulation job descriptor
*
* This function dynamically constructs a blob encapsulation job descriptor
* from the following arguments:
*
* - desc pointer to a pointer to the descriptor generated by this
* function. Caller will be responsible to kfree() this
* descriptor after execution.
* - keymod Physical pointer to a key modifier, which must reside in a
* contiguous piece of memory. Modifier will be assumed to be
* 8 bytes long for a blob of type SM_SECMEM, or 16 bytes long
* for a blob of type SM_GENMEM (see blobtype argument).
* - secretbuf Physical pointer to a secret, normally a black or red key,
* possibly residing within an accessible secure memory page,
* of the secret to be encapsulated to an output blob.
* - outbuf Physical pointer to the destination buffer to receive the
* encapsulated output. This buffer will need to be 48 bytes
* larger than the input because of the added encapsulation data.
* The generated descriptor will account for the increase in size,
* but the caller must also account for this increase in the
* buffer allocator.
* - secretsz Size of input secret, in bytes. This is limited to 65536
* less the size of blob overhead, since the length embeds into
* DECO pointer in/out instructions.
* - keycolor Determines if the source data is covered (black key) or
* plaintext (red key). RED_KEY or BLACK_KEY are defined in
* for this purpose.
* - blobtype Determine if encapsulated blob should be a secure memory
* blob (SM_SECMEM), with partition data embedded with key
* material, or a general memory blob (SM_GENMEM).
* - auth If BLACK_KEY source is covered via AES-CCM, specify
* KEY_COVER_CCM, else uses AES-ECB (KEY_COVER_ECB).
*
* Upon completion, desc points to a buffer containing a CAAM job
* descriptor which encapsulates data into an externally-storable blob
* suitable for use across power cycles.
*
* This is an example of a black key encapsulation job into a general memory
* blob. Notice the 16-byte key modifier in the LOAD instruction. Also note
* the output 48 bytes longer than the input:
*
* [00] B0800008 jobhdr: stidx=0 len=8
* [01] 14400010 ld: ccb2-key len=16 offs=0
* [02] 08144891 ptr->@0x08144891
* [03] F800003A seqoutptr: len=58
* [04] 01000000 out_ptr->@0x01000000
* [05] F000000A seqinptr: len=10
* [06] 09745090 in_ptr->@0x09745090
* [07] 870D0004 operation: encap blob reg=memory, black, format=normal
*
* This is an example of a red key encapsulation job for storing a red key
* into a secure memory blob. Note the 8 byte modifier on the 12 byte offset
* in the LOAD instruction; this accounts for blob permission storage:
*
* [00] B0800008 jobhdr: stidx=0 len=8
* [01] 14400C08 ld: ccb2-key len=8 offs=12
* [02] 087D0784 ptr->@0x087d0784
* [03] F8000050 seqoutptr: len=80
* [04] 09251BB2 out_ptr->@0x09251bb2
* [05] F0000020 seqinptr: len=32
* [06] 40000F31 in_ptr->@0x40000f31
* [07] 870D0008 operation: encap blob reg=memory, red, sec_mem,
* format=normal
*
* Note: this function only generates 32-bit pointers at present, and should
* be refactored using a scheme that allows both 32 and 64 bit addressing
*/
static int blob_encap_jobdesc(u32 **desc, dma_addr_t keymod,
void *secretbuf, dma_addr_t outbuf,
u16 secretsz, u8 keycolor, u8 blobtype, u8 auth)
{
u32 *tdesc, tmpdesc[INITIAL_DESCSZ];
u16 dsize, idx;
memset(tmpdesc, 0, INITIAL_DESCSZ * sizeof(u32));
idx = 1;
/*
* Key modifier works differently for secure/general memory blobs
* This accounts for the permission/protection data encapsulated
* within the blob if a secure memory blob is requested
*/
if (blobtype == SM_SECMEM)
tmpdesc[idx++] = CMD_LOAD | LDST_CLASS_2_CCB |
LDST_SRCDST_BYTE_KEY |
((12 << LDST_OFFSET_SHIFT) & LDST_OFFSET_MASK)
| (8 & LDST_LEN_MASK);
else /* is general memory blob */
tmpdesc[idx++] = CMD_LOAD | LDST_CLASS_2_CCB |
LDST_SRCDST_BYTE_KEY | (16 & LDST_LEN_MASK);
tmpdesc[idx++] = (u32)keymod;
/*
* Encapsulation output must include space for blob key encryption
* key and MAC tag
*/
tmpdesc[idx++] = CMD_SEQ_OUT_PTR | (secretsz + BLOB_OVERHEAD);
tmpdesc[idx++] = (u32)outbuf;
/* Input data, should be somewhere in secure memory */
tmpdesc[idx++] = CMD_SEQ_IN_PTR | secretsz;
tmpdesc[idx++] = (u32)secretbuf;
/* Set blob encap, then color */
tmpdesc[idx] = CMD_OPERATION | OP_TYPE_ENCAP_PROTOCOL | OP_PCLID_BLOB;
if (blobtype == SM_SECMEM)
tmpdesc[idx] |= OP_PCL_BLOB_PTXT_SECMEM;
if (auth == KEY_COVER_CCM)
tmpdesc[idx] |= OP_PCL_BLOB_EKT;
if (keycolor == BLACK_KEY)
tmpdesc[idx] |= OP_PCL_BLOB_BLACK;
idx++;
tmpdesc[0] = CMD_DESC_HDR | HDR_ONE | (idx & HDR_DESCLEN_MASK);
dsize = idx * sizeof(u32);
tdesc = kmalloc(dsize, GFP_KERNEL | GFP_DMA);
if (tdesc == NULL)
return 0;
memcpy(tdesc, tmpdesc, dsize);
*desc = tdesc;
return dsize;
}
/*
* Construct a blob decapsulation job descriptor
*
* This function dynamically constructs a blob decapsulation job descriptor
* from the following arguments:
*
* - desc pointer to a pointer to the descriptor generated by this
* function. Caller will be responsible to kfree() this
* descriptor after execution.
* - keymod Physical pointer to a key modifier, which must reside in a
* contiguous piece of memory. Modifier will be assumed to be
* 8 bytes long for a blob of type SM_SECMEM, or 16 bytes long
* for a blob of type SM_GENMEM (see blobtype argument).
* - blobbuf Physical pointer (into external memory) of the blob to
* be decapsulated. Blob must reside in a contiguous memory
* segment.
* - outbuf Physical pointer of the decapsulated output, possibly into
* a location within a secure memory page. Must be contiguous.
* - secretsz Size of encapsulated secret in bytes (not the size of the
* input blob).
* - keycolor Determines if decapsulated content is encrypted (BLACK_KEY)
* or left as plaintext (RED_KEY).
* - blobtype Determine if encapsulated blob should be a secure memory
* blob (SM_SECMEM), with partition data embedded with key
* material, or a general memory blob (SM_GENMEM).
* - auth If decapsulation path is specified by BLACK_KEY, then if
* AES-CCM is requested for key covering use KEY_COVER_CCM, else
* use AES-ECB (KEY_COVER_ECB).
*
* Upon completion, desc points to a buffer containing a CAAM job descriptor
* that decapsulates a key blob from external memory into a black (encrypted)
* key or red (plaintext) content.
*
* This is an example of a black key decapsulation job from a general memory
* blob. Notice the 16-byte key modifier in the LOAD instruction.
*
* [00] B0800008 jobhdr: stidx=0 len=8
* [01] 14400010 ld: ccb2-key len=16 offs=0
* [02] 08A63B7F ptr->@0x08a63b7f
* [03] F8000010 seqoutptr: len=16
* [04] 01000000 out_ptr->@0x01000000
* [05] F000003A seqinptr: len=58
* [06] 01000010 in_ptr->@0x01000010
* [07] 860D0004 operation: decap blob reg=memory, black, format=normal
*
* This is an example of a red key decapsulation job for restoring a red key
* from a secure memory blob. Note the 8 byte modifier on the 12 byte offset
* in the LOAD instruction:
*
* [00] B0800008 jobhdr: stidx=0 len=8
* [01] 14400C08 ld: ccb2-key len=8 offs=12
* [02] 01000000 ptr->@0x01000000
* [03] F8000020 seqoutptr: len=32
* [04] 400000E6 out_ptr->@0x400000e6
* [05] F0000050 seqinptr: len=80
* [06] 08F0C0EA in_ptr->@0x08f0c0ea
* [07] 860D0008 operation: decap blob reg=memory, red, sec_mem,
* format=normal
*
* Note: this function only generates 32-bit pointers at present, and should
* be refactored using a scheme that allows both 32 and 64 bit addressing
*/
static int blob_decap_jobdesc(u32 **desc, dma_addr_t keymod, dma_addr_t blobbuf,
u8 *outbuf, u16 secretsz, u8 keycolor,
u8 blobtype, u8 auth)
{
u32 *tdesc, tmpdesc[INITIAL_DESCSZ];
u16 dsize, idx;
memset(tmpdesc, 0, INITIAL_DESCSZ * sizeof(u32));
idx = 1;
/* Load key modifier */
if (blobtype == SM_SECMEM)
tmpdesc[idx++] = CMD_LOAD | LDST_CLASS_2_CCB |
LDST_SRCDST_BYTE_KEY |
((12 << LDST_OFFSET_SHIFT) & LDST_OFFSET_MASK)
| (8 & LDST_LEN_MASK);
else /* is general memory blob */
tmpdesc[idx++] = CMD_LOAD | LDST_CLASS_2_CCB |
LDST_SRCDST_BYTE_KEY | (16 & LDST_LEN_MASK);
tmpdesc[idx++] = (u32)keymod;
/* Compensate BKEK + MAC tag over size of encapsulated secret */
tmpdesc[idx++] = CMD_SEQ_IN_PTR | (secretsz + BLOB_OVERHEAD);
tmpdesc[idx++] = (u32)blobbuf;
tmpdesc[idx++] = CMD_SEQ_OUT_PTR | secretsz;
tmpdesc[idx++] = (u32)outbuf;
/* Decapsulate from secure memory partition to black blob */
tmpdesc[idx] = CMD_OPERATION | OP_TYPE_DECAP_PROTOCOL | OP_PCLID_BLOB;
if (blobtype == SM_SECMEM)
tmpdesc[idx] |= OP_PCL_BLOB_PTXT_SECMEM;
if (auth == KEY_COVER_CCM)
tmpdesc[idx] |= OP_PCL_BLOB_EKT;
if (keycolor == BLACK_KEY)
tmpdesc[idx] |= OP_PCL_BLOB_BLACK;
idx++;
tmpdesc[0] = CMD_DESC_HDR | HDR_ONE | (idx & HDR_DESCLEN_MASK);
dsize = idx * sizeof(u32);
tdesc = kmalloc(dsize, GFP_KERNEL | GFP_DMA);
if (tdesc == NULL)
return 0;
memcpy(tdesc, tmpdesc, dsize);
*desc = tdesc;
return dsize;
}
static int gen_mem_encap(struct device *jr_dev, void __user *secretbuf,
int keylen, void __user *kmodbuf, void __user *outbuf)
{
int retval = 0;
u32 dsize;
u32 __iomem *encapdesc = NULL;
dma_addr_t secret_dma = 0, keymod_dma = 0, outbuf_dma = 0;
u8 __iomem *lsecret = NULL, *lkeymod = NULL, *loutbuf = NULL;
struct sm_key_job_result testres;
/* Build/map/flush the scret */
lsecret = kmalloc(keylen, GFP_KERNEL | GFP_DMA);
if (!lsecret) {
dev_err(jr_dev, "%s: can't alloc for key\n", __func__);
retval = -ENOMEM;
goto out;
}
if (copy_from_user(lsecret, secretbuf, keylen)) {
dev_err(jr_dev, "%s: can't Copy for key\n", __func__);
retval = -EFAULT;
goto out;
}
secret_dma = dma_map_single(jr_dev, lsecret, keylen,
DMA_TO_DEVICE);
/* Build/map/flush the key modifier */
lkeymod = kmalloc(GENMEM_KEYMOD_LEN, GFP_KERNEL | GFP_DMA);
if (!lkeymod) {
dev_err(jr_dev, "%s: can't alloc for keymod\n", __func__);
retval = -ENOMEM;
goto out;
}
if (copy_from_user(lkeymod, kmodbuf, GENMEM_KEYMOD_LEN)) {
dev_err(jr_dev, "%s: can't Copy for keymod\n", __func__);
retval = -EFAULT;
goto out;
}
keymod_dma = dma_map_single(jr_dev, lkeymod, GENMEM_KEYMOD_LEN,
DMA_TO_DEVICE);
loutbuf = kmalloc(keylen + BLOB_OVERHEAD, GFP_KERNEL | GFP_DMA);
if (!lkeymod) {
dev_err(jr_dev, "%s: can't alloc for output\n", __func__);
retval = -ENOMEM;
goto out;
}
outbuf_dma = dma_map_single(jr_dev, loutbuf, keylen + BLOB_OVERHEAD,
DMA_FROM_DEVICE);
dsize = blob_encap_jobdesc(&encapdesc, keymod_dma, (void *)secret_dma, outbuf_dma,
keylen, RED_KEY, SM_GENMEM, KEY_COVER_ECB);
if (!dsize) {
dev_err(jr_dev, "can't alloc an encapsulation descriptor\n");
retval = -ENOMEM;
goto out;
}
init_completion(&testres.completion);
retval = caam_jr_enqueue(jr_dev, encapdesc, sm_key_job_done,
&testres);
if (!retval) {
wait_for_completion_interruptible(&testres.completion);
dev_info(jr_dev, "job ring return %d\n", testres.error);
if (!testres.error) {
dma_sync_single_for_cpu(jr_dev, outbuf_dma, keylen + BLOB_OVERHEAD,
DMA_FROM_DEVICE);
if (copy_to_user(outbuf, loutbuf, keylen + BLOB_OVERHEAD)) {
retval = -EFAULT;
dev_err(jr_dev, "can't copy for output\n");
goto out;
}
}
retval = testres.error;
}
out:
if (outbuf_dma)
dma_unmap_single(jr_dev, outbuf_dma, keylen + BLOB_OVERHEAD,
DMA_FROM_DEVICE);
if (keymod_dma)
dma_unmap_single(jr_dev, keymod_dma, GENMEM_KEYMOD_LEN, DMA_TO_DEVICE);
if (secret_dma)
dma_unmap_single(jr_dev, secret_dma, keylen, DMA_TO_DEVICE);
kfree(encapdesc);
kfree(lkeymod);
kfree(lsecret);
kfree(loutbuf);
return retval;
}
static int gen_mem_decap(struct device *jr_dev, void __user *keyblobbuf,
int bloblen, void __user *kmodbuf, void __user *outbuf)
{
int retval = 0;
int keylen = bloblen - BLOB_OVERHEAD;
u32 dsize;
dma_addr_t keyblob_dma = 0, keymod_dma = 0, outbuf_dma = 0;
u8 __iomem *lkeyblob = NULL, *lkeymod = NULL, *loutbuf = NULL;
struct sm_key_job_result testres;
u32 __iomem *decapdesc = NULL;
/* Build/map/flush the scret */
lkeyblob = kmalloc(bloblen, GFP_KERNEL | GFP_DMA);
if (!lkeyblob) {
dev_err(jr_dev, "%s: can't alloc for keylob\n", __func__);
retval = -ENOMEM;
goto out;
}
if (copy_from_user(lkeyblob, keyblobbuf, bloblen)) {
dev_err(jr_dev, "%s: can't Copy for keyblob\n", __func__);
retval = -EFAULT;
goto out;
}
keyblob_dma = dma_map_single(jr_dev, lkeyblob, bloblen,
DMA_TO_DEVICE);
/* Build/map/flush the key modifier */
lkeymod = kmalloc(GENMEM_KEYMOD_LEN, GFP_KERNEL | GFP_DMA);
if (!lkeymod) {
dev_err(jr_dev, "%s: can't alloc for keymod\n", __func__);
retval = -ENOMEM;
goto out;
}
if (copy_from_user(lkeymod, kmodbuf, GENMEM_KEYMOD_LEN)) {
dev_err(jr_dev, "%s: can't Copy for keymod\n", __func__);
retval = -EFAULT;
goto out;
}
keymod_dma = dma_map_single(jr_dev, lkeymod, GENMEM_KEYMOD_LEN,
DMA_TO_DEVICE);
loutbuf = kmalloc(keylen, GFP_KERNEL | GFP_DMA);
if (!loutbuf) {
dev_err(jr_dev, "%s: can't alloc for outbuf\n", __func__);
retval = -ENOMEM;
goto out;
}
outbuf_dma = dma_map_single(jr_dev, loutbuf, keylen,
DMA_FROM_DEVICE);
/* Build the encapsulation job descriptor */
dsize = blob_decap_jobdesc(&decapdesc, keymod_dma, keyblob_dma, (u8 *)outbuf_dma,
keylen, RED_KEY, SM_GENMEM, KEY_COVER_ECB);
if (!dsize) {
dev_err(jr_dev, "can't alloc a decapsulation descriptor\n");
retval = -ENOMEM;
goto out;
}
init_completion(&testres.completion);
retval = caam_jr_enqueue(jr_dev, decapdesc, sm_key_job_done,
&testres);
if (!retval) {
wait_for_completion_interruptible(&testres.completion);
dev_info(jr_dev, "job ring return %d\n", testres.error);
if (!testres.error) {
dma_sync_single_for_cpu(jr_dev, outbuf_dma, keylen,
DMA_FROM_DEVICE);
if (copy_to_user(outbuf, loutbuf, keylen)) {
retval = -EFAULT;
goto out;
}
}
retval = testres.error;
}
out:
if (outbuf_dma)
dma_unmap_single(jr_dev, outbuf_dma, keylen,
DMA_FROM_DEVICE);
if (keymod_dma)
dma_unmap_single(jr_dev, keymod_dma, GENMEM_KEYMOD_LEN,
DMA_TO_DEVICE);
if (keyblob_dma)
dma_unmap_single(jr_dev, keyblob_dma, bloblen,
DMA_TO_DEVICE);
kfree(decapdesc);
kfree(lkeymod);
kfree(lkeyblob);
kfree(loutbuf);
return retval;
}
static long kb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int retval = 0;
struct caam_kb_data kb_data;
struct miscdevice *miscdev = file->private_data;
struct kb_device *dev = container_of(miscdev, struct kb_device, misc_dev);
if (copy_from_user(&kb_data, (void *)arg, sizeof(kb_data))) {
retval = -EFAULT;
goto err;
}
if (!kb_data.rawkey || !kb_data.keyblob ||
(kb_data.rawkey_len + BLOB_OVERHEAD != kb_data.keyblob_len) ||
(kb_data.keymod_len != GENMEM_KEYMOD_LEN)) {
retval = -EINVAL;
goto err;
}
printk(KERN_INFO"%s:rawkey_len %d, keyblob_len %d\n",
__func__, kb_data.rawkey_len, kb_data.keyblob_len);
switch (cmd) {
case CAAM_KB_ENCRYPT:
{
retval = gen_mem_encap(dev->jr_dev, kb_data.rawkey, kb_data.rawkey_len,
kb_data.keymod, kb_data.keyblob);
break;
}
case CAAM_KB_DECRYPT:
{
retval = gen_mem_decap(dev->jr_dev, kb_data.keyblob, kb_data.keyblob_len,
kb_data.keymod, kb_data.rawkey);
break;
}
default:
return -ENOTTY;
}
err:
return retval;
}
static const struct file_operations kb_fops = {
.owner = THIS_MODULE,
.open = kb_open,
.release = kb_release,
.unlocked_ioctl = kb_ioctl,
};
static struct kb_device *kb_device_create(void)
{
struct kb_device *idev;
int ret;
idev = kzalloc(sizeof(struct kb_device), GFP_KERNEL);
if (!idev)
return ERR_PTR(-ENOMEM);
idev->misc_dev.minor = MISC_DYNAMIC_MINOR;
idev->misc_dev.name = "caam_kb";
idev->misc_dev.fops = &kb_fops;
idev->misc_dev.parent = NULL;
ret = misc_register(&idev->misc_dev);
if (ret) {
pr_err("kb: failed to register misc device.\n");
return ERR_PTR(ret);
}
return idev;
}
static int kb_device_destroy(struct kb_device *kb_dev)
{
if ((kb_dev) && (kb_dev->jr_dev)) {
caam_jr_free(kb_dev->jr_dev);
kb_dev->jr_dev = NULL;
}
if (kb_dev)
misc_deregister(&kb_dev->misc_dev);
return 0;
}
/*
* Probe key blob device
*/
static int caam_keyblob_probe(struct platform_device *pdev)
{
int err;
dev_dbg(&pdev->dev, "%s enter\n", __func__);
kb_dev = kb_device_create();
if (IS_ERR_OR_NULL(kb_dev)) {
err = PTR_ERR(kb_dev);
goto err;
}
dev_info(&pdev->dev, "caam keyblob initialized\n");
return 0;
err:
return err;
}
/*
* Remove key blob device
*/
static int caam_keyblob_remove(struct platform_device *pdev)
{
kb_device_destroy(kb_dev);
return 0;
}
static struct of_device_id caam_keyblob_match[] = {
{
.compatible = "fsl,sec-v4.0-keyblob",
},
{
.compatible = "fsl,sec4.0-keyblob",
},
{},
};
MODULE_DEVICE_TABLE(of, caam_keyblob_match);
static struct platform_driver caam_keyblob_driver = {
.driver = {
.name = "caam_keyblob",
.owner = THIS_MODULE,
.of_match_table = caam_keyblob_match,
},
.probe = caam_keyblob_probe,
.remove = caam_keyblob_remove,
};
static int __init keyblob_driver_init(void)
{
return platform_driver_register(&caam_keyblob_driver);
}
static void __exit keyblob_driver_exit(void)
{
platform_driver_unregister(&caam_keyblob_driver);
}
module_init(keyblob_driver_init);
module_exit(keyblob_driver_exit);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("FSL CAAM Secure Memory / Keystore");
MODULE_AUTHOR("Freescale Semiconductor - NMSG/MAD");