Hi, Any update on this? Thank you, Kyungmin Park On Sat, Jun 12, 2010 at 4:49 AM, Maurus Cuelenaere <mcuelenaere@xxxxxxxxx> wrote: > This patch adds support for the Samsung crypto engine driver available in the > S3C64XX and S5PC100 SoCs. Currently this supports AES and (T)DES with ECB and > CBC block ciphers (also CTR for AES). Support for (HMAC)-SHA1 acceleration is > also available in this engine, but isn't used in the driver yet. > > Support for DMA has been added in the code but is currently disabled due to > issues with data transfers. > > Signed-off-by: Maurus Cuelenaere <mcuelenaere@xxxxxxxxx> > --- > drivers/crypto/Kconfig | 11 + > drivers/crypto/Makefile | 1 + > drivers/crypto/s3c-sss.c | 1320 ++++++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 1332 insertions(+), 0 deletions(-) > create mode 100644 drivers/crypto/s3c-sss.c > > diff --git a/drivers/crypto/Kconfig b/drivers/crypto/Kconfig > index b08403d..597a151 100644 > --- a/drivers/crypto/Kconfig > +++ b/drivers/crypto/Kconfig > @@ -222,4 +222,15 @@ config CRYPTO_DEV_PPC4XX > help > This option allows you to have support for AMCC crypto acceleration. > > +config CRYPTO_DEV_SSS > + tristate "Samsung Security Sub-Systems" > + depends on SAMSUNG_DEV_SSS > + select CRYPTO_ALGAPI > + select CRYPTO_BLKCIPHER > + select CRYPTO_DES > + select CRYPTO_HASH > + help > + This driver utilizes the cryptographic engine in Samsung S3C64XX > + and S5PC100 SoCs. > + > endif # CRYPTO_HW > diff --git a/drivers/crypto/Makefile b/drivers/crypto/Makefile > index 6ffcb3f..ef14b4d 100644 > --- a/drivers/crypto/Makefile > +++ b/drivers/crypto/Makefile > @@ -6,3 +6,4 @@ obj-$(CONFIG_CRYPTO_DEV_MV_CESA) += mv_cesa.o > obj-$(CONFIG_CRYPTO_DEV_TALITOS) += talitos.o > obj-$(CONFIG_CRYPTO_DEV_IXP4XX) += ixp4xx_crypto.o > obj-$(CONFIG_CRYPTO_DEV_PPC4XX) += amcc/ > +obj-$(CONFIG_CRYPTO_DEV_SSS) += s3c-sss.o > diff --git a/drivers/crypto/s3c-sss.c b/drivers/crypto/s3c-sss.c > new file mode 100644 > index 0000000..9fd5288 > --- /dev/null > +++ b/drivers/crypto/s3c-sss.c > @@ -0,0 +1,1320 @@ > +/* > + * linux/drivers/crypto/s3c-sss.c > + * > + * Copyright (C) 2010 Maurus Cuelenaere > + * > + * Support for S3C64XX Security Sub-Systems > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 as > + * published by the Free Software Foundation. > + * > + */ > + > +/*#define DEBUG*/ > + > +#include <linux/clk.h> > +#include <linux/crypto.h> > +#include <linux/dma-mapping.h> > +#include <linux/interrupt.h> > +#include <linux/io.h> > +#include <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/platform_device.h> > +#include <linux/spinlock.h> > +#include <linux/timer.h> > + > +#include <crypto/internal/hash.h> > +#include <crypto/algapi.h> > +#include <crypto/aes.h> > +#include <crypto/ctr.h> > +#include <crypto/des.h> > +#include <crypto/sha.h> > +#include <crypto/scatterwalk.h> > + > +#include <mach/dma.h> > + > +#include <plat/regs-sss.h> > + > +#define SSS_CRA_PRIORITY 300 > +#define SSS_MAX_KEY_SIZE AES_MAX_KEY_SIZE > +#define SSS_FIFO_SIZE 0x40U > +#define SSS_TIMEOUT (3*HZ) > + > +/** > + * struct s3c_sss - driver state. > + * @dev: pointer to the device struct > + * @clock: clock associated with peripheral > + * @irq: irq associated with peripheral > + * @regs: pointer to mapped registers > + * @regs_phys: pointer to physical address of registers > + * @regs_res: pointer to struct resource representing registers > + * @cur_req: pointer to pending request (NULL indicates no current request) > + * @dma_client: struct used for passing to DMA core > + * @lock: lock used for synchronizing queue accesses > + * @tasklet: tasklet doing the main work > + * @timer: timer used for timing out faulty requests > + * @queue: queue containing requests > + */ > +struct s3c_sss { > + struct device *dev; > + > + struct clk *clock; > + int irq; > + void __iomem *regs; > + void __iomem *regs_phys; > + struct resource *regs_res; > + > + struct ablkcipher_request *cur_req; > + struct s3c2410_dma_client dma_client; > + spinlock_t lock; > + struct tasklet_struct tasklet; > + struct timer_list timer; > + struct crypto_queue queue; > +}; > + > +/** > + * struct sss_context - cipher/hash key state > + * @key: storage for the key > + * @key_len: length of the key > + * @dev: pointer to struct containing the driver state > + */ > +struct sss_context { > + u8 key[SSS_MAX_KEY_SIZE]; > + unsigned int key_len; > + > + struct s3c_sss *dev; > +}; > + > +/** > + * struct sss_fifo_channel - FIFO handling state > + * > + * @cur_sg: scatterlist used in current transfer > + * @offset: offset within current scatterlist > + * @dir: FIFO direction > + * @sg: pointer to scatter-gather lists > + * @sg_count: amount of scatter-gather lists > + * @req_size: size of current request > + * @bytes_done: amount of data transferred > + * @dev: pointer to struct containing the driver state > + */ > +struct sss_fifo_channel { > + /* DMA */ > + struct scatterlist *cur_sg; > + int offset; > + int sg_count; > + > + /* generic */ > + enum { > + FIFO_RX, > + FIFO_TX > + } dir; > + struct scatterlist *sg; > + size_t req_size; > + size_t bytes_done; > + struct s3c_sss *dev; > +}; > + > +/** > + * struct sss_req_context - driver-specific data associated with a request > + * @algorithm: algorithm used in request > + * @blk_cipher: block cipher used in request > + * @direction: whether to encrypt or decrypt > + * @rx: RX FIFO channel, see struct sss_fifo_channel > + * @tx: TX FIFO channel, see struct sss_fifo_channel > + * @setup_done: whether hardware has been set up > + * @err: indicates any occured error during request > + */ > +struct sss_req_context { > + enum sss_algorithm { > + ALGO_AES, > + ALGO_DES, > + ALGO_TDES, > + ALGO_SHA1, > + ALGO_HMAC_SHA1, > + } algorithm; > + enum sss_block_cipher { > + CIPH_ECB, > + CIPH_CBC, > + CIPH_CTR, > + } blk_cipher; > + enum sss_direction { > + ENCRYPT, > + DECRYPT, > + } direction; > + > + struct sss_fifo_channel rx; > + struct sss_fifo_channel tx; > + bool setup_done; > + int err; > +}; > + > +/** > + * struct sss_crypto_wrapper - simple wrapper for easy access to the driver data > + * @alg: wrapped crypto algorithm > + * @dev: pointer to the driver state > + */ > +struct sss_crypto_wrapper { > + struct crypto_alg alg; > + struct s3c_sss *dev; > +}; > + > +/*** Helper functions ***/ > + > +#define fifo_to_req_ctx(fifo, dir) container_of((fifo), \ > + struct sss_req_context, dir) > + > +static inline struct sss_req_context *sss_to_req_ctx(struct s3c_sss *sss) > +{ > + struct ablkcipher_request *req = sss->cur_req; > + return req ? ablkcipher_request_ctx(req) : NULL; > +} > + > +static inline unsigned int fifo_to_dma_channel(struct sss_fifo_channel *chan) > +{ > + return chan->dir == FIFO_TX ? DMACH_SECURITY_TX : DMACH_SECURITY_RX; > +} > + > +static inline bool sss_dma_enabled(void) > +{ > + /* DMA is disabled till someone figures out why it's not transmitting > + all data to the crypto engine. */ > + return false; > +} > + > +static int count_sgs(struct scatterlist *sg) > +{ > + int i; > + > + for (i = 0; sg; i++) > + sg = sg_next(sg); > + > + return i; > +} > + > +static inline void orrl(u32 val, void __iomem *reg) > +{ > + writel(readl(reg) | val, reg); > +} > + > +#ifdef DEBUG > +static void sss_dump_regs(struct s3c_sss *sss) > +{ > + dev_dbg(sss->dev, "DnI_CFG: %x\n", readl(sss->regs + DnI_CFG)); > + > + dev_dbg(sss->dev, "FRx_Ctrl: %x\n", readl(sss->regs + FRx_Ctrl)); > + dev_dbg(sss->dev, "FRx_MLen: %x\n", readl(sss->regs + FRx_MLen)); > + dev_dbg(sss->dev, "FRx_BlkSz: %x\n", readl(sss->regs + FRx_BlkSz)); > + dev_dbg(sss->dev, "FRx_Addr: %x\n", readl(sss->regs + FRx_Addr)); > + dev_dbg(sss->dev, "FRx_MLenCnt: %x\n", readl(sss->regs + FRx_MLenCnt)); > + > + dev_dbg(sss->dev, "FTx_Ctrl: %x\n", readl(sss->regs + FTx_Ctrl)); > + dev_dbg(sss->dev, "FTx_MLen: %x\n", readl(sss->regs + FTx_MLen)); > + dev_dbg(sss->dev, "FTx_BlkSz: %x\n", readl(sss->regs + FTx_BlkSz)); > + dev_dbg(sss->dev, "FTx_Addr: %x\n", readl(sss->regs + FTx_Addr)); > + dev_dbg(sss->dev, "FTx_MLenCnt: %x\n", readl(sss->regs + FTx_MLenCnt)); > + > + dev_dbg(sss->dev, "AES_CTRL: %x\n", readl(sss->regs + AES_CTRL)); > + dev_dbg(sss->dev, "TDES_CTRL: %x\n", readl(sss->regs + TDES_CTRL)); > + dev_dbg(sss->dev, "HASH_CTRL: %x\n", readl(sss->regs + HASH_CTRL)); > + dev_dbg(sss->dev, "HASH_STATUS: %x\n", readl(sss->regs + HASH_STATUS)); > +} > +#else > +#define sss_dump_regs(...) > +#endif > + > +#ifdef DEBUG > +static void sss_dump_channel(struct sss_fifo_channel *chan) > +{ > + struct s3c_sss *sss = chan->dev; > + bool tx = (chan->dir == FIFO_TX); > + u32 val; > + > + val = readl(sss->regs + (tx ? FTx_Ctrl : FRx_Ctrl)); > + > + dev_dbg(sss->dev, "FIFO_%cX: %c%c%c%c%c\n", tx ? 'T' : 'R', > + val & FXx_Ctrl_Full ? 'F' : ' ', > + val & FXx_Ctrl_Empty ? 'E' : ' ', > + val & FXx_Ctrl_Done ? 'D' : ' ', > + val & FXx_Ctrl_Running ? 'R' : ' ', > + val & FXx_Ctrl_Start ? 'S' : ' '); > + > + if (sss_dma_enabled()) { > + dev_dbg(sss->dev, " cur_sg: %p\n", chan->cur_sg); > + dev_dbg(sss->dev, " offset: %d\n", chan->offset); > + dev_dbg(sss->dev, " sg_count: %d\n", chan->sg_count); > + } > + > + dev_dbg(sss->dev, " req_size: %d\n", chan->req_size); > + dev_dbg(sss->dev, " bytes_done: %d\n", chan->bytes_done); > + dev_dbg(sss->dev, " mlencnt: %d\n", > + readl(sss->regs + (tx ? FTx_MLenCnt : FRx_MLenCnt)) * 4); > + dev_dbg(sss->dev, " wd2write: %d\n", (val >> tx ? 8 : 16) & 0xFF); > + dev_dbg(sss->dev, " wd2read: %d\n", (val >> tx ? 16 : 8) & 0xFF); > +} > +#else > +#define sss_dump_channel(...) > +#endif > + > +static void sss_reset_fifo(struct s3c_sss *sss, int reg) > +{ > + int timeout = 1000; > + u32 val; > + > + writel(FXx_Ctrl_Reset, sss->regs + reg); > + > + while (timeout-- > 0) { > + val = readl(sss->regs + reg); > + if (!(val & FXx_Ctrl_Reset)) > + break; > + } > + > + if (timeout <= 0) > + dev_warn(sss->dev, "Failed to reset FIFO_%cX!\n", > + reg == FRx_Ctrl ? 'R' : 'T'); > +} > + > +static void sss_reset_hw(struct s3c_sss *sss) > +{ > + u32 val = 0; > + > + if (sss_dma_enabled()) > + val |= (DnI_CFG_RxDmaEnb | DnI_CFG_TxDmaEnb | > + DnI_CFG_RxTrgLevel(16) | DnI_CFG_TxTrgLevel(16)); > + > + writel(val, sss->regs + DnI_CFG); > + > + /* Reset FIFOs */ > + sss_reset_fifo(sss, FRx_Ctrl); > + sss_reset_fifo(sss, FTx_Ctrl); > + > + /* Ensure all subsystems are disabled */ > + writel(0, sss->regs + AES_CTRL); > + writel(0, sss->regs + TDES_CTRL); > + writel(0, sss->regs + HASH_CTRL); > +} > + > +#ifdef DEBUG > +static void check_priv_mismatch(struct s3c_sss *sss, const char* name, int reg) > +{ > + u32 val = readl(sss->regs + reg); > + > + if (val & RdPrivMismatch) > + dev_warn(sss->dev, "%s read privilege mismatch! (0x%x)\n", name, > + val); > + > + if (val & WrPrivMismatch) > + dev_warn(sss->dev, "%s write privilege mismatch! (0x%x)\n", > + name, val); > +} > +#else > +#define check_priv_mismatch(...) > +#endif > + > +static irqreturn_t sss_irq(int irq, void *priv) > +{ > + struct s3c_sss *sss = priv; > + u32 cfg = readl(sss->regs + DnI_CFG); > + > + check_priv_mismatch(sss, "CONFIG", DnI_CFG); > + > + if (cfg & DnI_CFG_FRx_Intr_Status) { > + dev_dbg(sss->dev, "%s: FIFO_RX IRQ\n", __func__); > + check_priv_mismatch(sss, "FIFO RX", FRx_Ctrl); > + > + if (sss->cur_req && !sss_dma_enabled()) { > + struct sss_fifo_channel *ch = &sss_to_req_ctx(sss)->rx; > + if (ch->req_size) { > + dev_dbg(sss->dev, "Increasing consumption with " > + "%d bytes\n", ch->req_size); > + ch->bytes_done += ch->req_size; > + ch->req_size = 0; > + tasklet_schedule(&sss->tasklet); > + } > + } > + } > + > + if (cfg & DnI_CFG_FTx_Intr_Status) { > + dev_dbg(sss->dev, "%s: FIFO_TX IRQ\n", __func__); > + check_priv_mismatch(sss, "FIFO TX", FTx_Ctrl); > + > + if (sss->cur_req && !sss_dma_enabled()) > + tasklet_schedule(&sss->tasklet); > + } > + > + if (cfg & DnI_CFG_SHA_Intr_Status) > + dev_dbg(sss->dev, "%s: HASH IRQ\n", __func__); > + > + if (cfg & DnI_CFG_DES_Intr_Status) { > + dev_dbg(sss->dev, "%s: TDES IRQ\n", __func__); > + check_priv_mismatch(sss, "TDES", TDES_CTRL); > + } > + > + if (cfg & DnI_CFG_AES_Intr_Status) { > + dev_dbg(sss->dev, "%s: AES IRQ\n", __func__); > + check_priv_mismatch(sss, "AES", AES_CTRL); > + } > + > + return IRQ_HANDLED; > +} > + > +static void sss_dma_cb(struct s3c2410_dma_chan *chan, void *pw, int size, > + enum s3c2410_dma_buffresult res) > +{ > + struct sss_fifo_channel *fifo_chan = pw; > + struct s3c_sss *sss = fifo_chan->dev; > + struct sss_req_context *req_ctx; > + > + dev_dbg(sss->dev, "%s: FIFO_%cX\n", __func__, > + fifo_chan->dir == FIFO_RX ? 'R' : 'T'); > + > + if (fifo_chan->dir == FIFO_RX) > + req_ctx = fifo_to_req_ctx(fifo_chan, rx); > + else > + req_ctx = fifo_to_req_ctx(fifo_chan, tx); > + > + switch (res) { > + case S3C2410_RES_OK: > + fifo_chan->bytes_done += fifo_chan->req_size; > + fifo_chan->offset += fifo_chan->req_size; > + fifo_chan->req_size = 0; > + break; > + case S3C2410_RES_ERR: > + case S3C2410_RES_ABORT: > + default: > + dev_err(sss->dev, "Error occured during DMA transfer!\n"); > + if (!req_ctx->err) > + req_ctx->err = -EIO; > + } > + > + tasklet_schedule(&sss->tasklet); > +} > + > +static int sss_setup_dma(struct s3c_sss *sss, unsigned int channel) > +{ > + enum s3c2410_dmasrc source; > + unsigned long reg; > + int ret; > + > + ret = s3c2410_dma_request(channel, &sss->dma_client, sss); > + if (ret < 0) > + return ret; > + > + if (channel == DMACH_SECURITY_RX) { > + reg = (unsigned long)(sss->regs_phys + SDMA_FRx_Buf); > + source = S3C2410_DMASRC_MEM; > + } else { /* DMACH_SECURITY_TX */ > + reg = (unsigned long)(sss->regs_phys + SDMA_FTx_Buf); > + source = S3C2410_DMASRC_HW; > + } > + > + s3c2410_dma_config(channel, 4); > + s3c2410_dma_devconfig(channel, source, reg); > + s3c2410_dma_set_buffdone_fn(channel, sss_dma_cb); > + > + return 0; > +} > + > +static void sss_setup_hw(struct s3c_sss *sss) > +{ > + struct ablkcipher_request *req = sss->cur_req; > + struct sss_context *ctx = crypto_tfm_ctx(req->base.tfm); > + struct sss_req_context *req_ctx = ablkcipher_request_ctx(req); > + u32 val, cfg, fifo_rx, fifo_tx; > + > + dev_dbg(sss->dev, "%s: setting up hw\n", __func__); > + > + sss_reset_hw(sss); > + > + cfg = readl(sss->regs + DnI_CFG); > + cfg |= (DnI_CFG_FTx_Intr_En | DnI_CFG_FRx_Intr_En); > + > + fifo_rx = (FXx_Ctrl_Host_Rd_En | FXx_Ctrl_Host_Wr_En | > + FRx_Ctrl_Sync_Tx); > + fifo_tx = (FXx_Ctrl_Host_Rd_En | FXx_Ctrl_Host_Wr_En); > + > + switch (req_ctx->algorithm) { > + case ALGO_AES: > + cfg |= DnI_CFG_AES_Intr_En; > + fifo_rx |= FXx_Ctrl_Module_AES; > + fifo_tx |= FXx_Ctrl_Module_AES; > + > + switch (req_ctx->blk_cipher) { > + case CIPH_ECB: > + val = AES_CTRL_OpMode_ECB; > + break; > + case CIPH_CBC: > + val = AES_CTRL_OpMode_CBC; > + memcpy(sss->regs + AES_IV, req->info, 16); > + break; > + case CIPH_CTR: > + val = AES_CTRL_OpMode_CTR; > + memcpy(sss->regs + AES_CTR, req->info, 16); /* ??? */ > + break; > + } > + > + if (req_ctx->direction == DECRYPT && > + req_ctx->blk_cipher != CIPH_CTR) > + val |= AES_CTRL_OpDirection_Dec; > + > + switch (ctx->key_len) { > + case AES_KEYSIZE_128: > + val |= AES_CTRL_KeyMode_128bits; > + break; > + case AES_KEYSIZE_192: > + val |= AES_CTRL_KeyMode_192bits; > + break; > + case AES_KEYSIZE_256: > + val |= AES_CTRL_KeyMode_256bits; > + break; > + } > + memcpy(sss->regs + AES_KEY, ctx->key, ctx->key_len); > + > + writel(val, sss->regs + AES_CTRL); > + > + writel(AES_BLOCK_SIZE / 4, sss->regs + FRx_BlkSz); > + writel(AES_BLOCK_SIZE / 4, sss->regs + FTx_BlkSz); > + writel(sss->regs_phys + AES_DIN, sss->regs + FRx_Addr); > + writel(sss->regs_phys + AES_DOUT, sss->regs + FTx_Addr); > + > + break; > + case ALGO_DES: > + case ALGO_TDES: > + cfg |= DnI_CFG_DES_Intr_En; > + fifo_rx |= FXx_Ctrl_Module_DES; > + fifo_tx |= FXx_Ctrl_Module_DES; > + > + switch (req_ctx->blk_cipher) { > + case CIPH_ECB: > + val = TDES_CTRL_Mode_ECB; > + break; > + case CIPH_CBC: > + val = TDES_CTRL_Mode_CBC; > + memcpy(sss->regs + TDES_IV, req->info, 16); > + break; > + case CIPH_CTR: > + /* NOP */ > + break; > + } > + > + if (req_ctx->direction == DECRYPT) > + val |= TDES_CTRL_OpDirection_Dec; > + > + if (req_ctx->algorithm == ALGO_TDES) > + val |= TDES_CTRL_Mode_Tdes; > + > + val |= TDES_CTRL_IntMode; > + > + memcpy(sss->regs + TDES_KEY, ctx->key, ctx->key_len); > + > + writel(val, sss->regs + TDES_CTRL); > + > + writel(DES_BLOCK_SIZE / 4, sss->regs + FRx_BlkSz); > + writel(DES_BLOCK_SIZE / 4, sss->regs + FTx_BlkSz); > + writel(sss->regs_phys + TDES_INPUT, sss->regs + FRx_Addr); > + writel(sss->regs_phys + TDES_OUTPUT, sss->regs + FTx_Addr); > + > + break; > + case ALGO_SHA1: > + case ALGO_HMAC_SHA1: > + cfg |= DnI_CFG_SHA_Intr_En; > + fifo_rx |= FXx_Ctrl_Module_SHA; > + fifo_tx |= FXx_Ctrl_Module_SHA; > + > + /*TODO*/ > + > + break; > + } > + > + writel(cfg, sss->regs + DnI_CFG); > + writel(fifo_rx, sss->regs + FRx_Ctrl); > + writel(fifo_tx, sss->regs + FTx_Ctrl); > +} > + > +static void sss_setup_hw_mlen(struct sss_fifo_channel *chan, size_t len) > +{ > + struct s3c_sss *sss = chan->dev; > + > + if (chan->dir == FIFO_RX) { > + writel(len / 4, sss->regs + FRx_MLen); > + orrl(FXx_Ctrl_Start, sss->regs + FRx_Ctrl); > + } else { > + writel(len / 4, sss->regs + FTx_MLen); > + orrl(FXx_Ctrl_Start, sss->regs + FTx_Ctrl); > + } > +} > + > +static int sss_setup_dma_channel(struct sss_fifo_channel *chan) > +{ > + struct s3c_sss *sss = chan->dev; > + unsigned int channel = fifo_to_dma_channel(chan); > + int ret; > + > + if (chan->offset >= sg_dma_len(chan->cur_sg)) { > + chan->cur_sg = sg_next(chan->cur_sg); > + chan->offset = 0; > + } > + > + chan->req_size = min(sg_dma_len(chan->cur_sg) - chan->offset, > + SSS_FIFO_SIZE); > + > + sss_setup_hw_mlen(chan, chan->req_size); > + > + dev_dbg(sss->dev, "Enqueue'ing for FIFO_%cX: %x (%d)\n", > + channel == DMACH_SECURITY_TX ? 'T' : 'R', > + sg_dma_address(chan->cur_sg) + chan->offset, chan->req_size); > + > + ret = s3c2410_dma_enqueue(channel, chan, > + sg_dma_address(chan->cur_sg) + chan->offset, > + chan->req_size); > + if (ret) > + return ret; > + > + return s3c2410_dma_ctrl(fifo_to_dma_channel(chan), S3C2410_DMAOP_START); > +} > + > +static void sss_setup_fifo(struct sss_fifo_channel *chan) > +{ > + struct s3c_sss *sss = chan->dev; > + struct ablkcipher_request *req = sss->cur_req; > + enum dma_data_direction dir; > + unsigned int sg_flags; > + > + if (chan->dir == FIFO_RX) { > + sg_flags = SG_MITER_FROM_SG; > + chan->sg = req->src; > + dir = DMA_TO_DEVICE; > + } else { > + sg_flags = SG_MITER_TO_SG; > + chan->sg = req->dst; > + dir = DMA_FROM_DEVICE; > + } > + > + if (sss_dma_enabled()) { > + int sg_count = count_sgs(chan->sg); > + chan->sg_count = dma_map_sg(sss->dev, chan->sg, sg_count, dir); > + chan->cur_sg = chan->sg; > + > + s3c2410_dma_ctrl(fifo_to_dma_channel(chan), > + S3C2410_DMAOP_FLUSH); > + } > +} > + > +static int sss_handle_fifo(struct sss_fifo_channel *chan) > +{ > + struct s3c_sss *sss = chan->dev; > + struct ablkcipher_request *req = sss->cur_req; > + void __iomem *fifo; > + > + if (chan->req_size) > + /* FIFO is still transferring data */ > + return -EINPROGRESS; > + > + if (sss_dma_enabled()) > + return sss_setup_dma_channel(chan); > + > + /* PIO */ > + if (chan->dir == FIFO_RX) > + fifo = sss->regs + FRx_Buf; > + else > + fifo = sss->regs + FTx_Buf; > + > + chan->req_size = min(req->nbytes - chan->bytes_done, SSS_FIFO_SIZE); > + > + sss_setup_hw_mlen(chan, chan->req_size); > + > + dev_dbg(sss->dev, "Transferring %d bytes to FIFO_%s\n", chan->req_size, > + chan->dir == FIFO_TX ? "TX" : "RX"); > + > + scatterwalk_map_and_copy(fifo, chan->sg, chan->bytes_done, > + chan->req_size, (chan->dir == FIFO_TX)); > + > + if (chan->dir == FIFO_TX) { > + chan->bytes_done += chan->req_size; > + chan->req_size = 0; > + } > + > + return 0; > +} > + > +static void sss_cleanup_fifo(struct sss_fifo_channel *chan) > +{ > + struct s3c_sss *sss = chan->dev; > + enum dma_data_direction dir; > + > + if (sss_dma_enabled()) { > + if (chan->dir == FIFO_RX) > + dir = DMA_TO_DEVICE; > + else > + dir = DMA_FROM_DEVICE; > + > + dma_unmap_sg(sss->dev, chan->sg, chan->sg_count, dir); > + > + s3c2410_dma_ctrl(fifo_to_dma_channel(chan), S3C2410_DMAOP_STOP); > + } > +} > + > +static void sss_timer_callback(unsigned long priv) > +{ > + struct s3c_sss *sss = (struct s3c_sss *)priv; > + struct sss_req_context *req_ctx = sss_to_req_ctx(sss); > + > + dev_err(sss->dev, "Request timed out!\n"); > + req_ctx->err = -ETIMEDOUT; > + > + tasklet_schedule(&sss->tasklet); > +} > + > +static void sss_tasklet_callback(unsigned long priv) > +{ > + struct s3c_sss *sss = (struct s3c_sss *)priv; > + struct sss_req_context *req_ctx; > + struct ablkcipher_request *req; > + unsigned long flags; > + > + if (!sss->cur_req) { > + spin_lock_irqsave(&sss->lock, flags); > + sss->cur_req = ablkcipher_dequeue_request(&sss->queue); > + spin_unlock_irqrestore(&sss->lock, flags); > + > + if (!sss->cur_req) { > + dev_warn(sss->dev, "Tasklet was called without any " > + "pending request!\n"); > + return; > + } > + } > + > + /*TODO: backlog*/ > + > + req = sss->cur_req; > + req_ctx = ablkcipher_request_ctx(req); > + > + dev_dbg(sss->dev, "Current request: %p (%d)\n", req, req->nbytes); > + > + if (!req_ctx->setup_done) { > + clk_enable(sss->clock); > + > + sss_setup_hw(sss); > + sss_setup_fifo(&req_ctx->rx); > + sss_setup_fifo(&req_ctx->tx); > + > + req_ctx->setup_done = true; > + } > + > + /* Ensure timeout handler is killed */ > + if (timer_pending(&sss->timer)) > + del_timer(&sss->timer); > + > + if (!req_ctx->err && (req_ctx->rx.bytes_done < req->nbytes || > + req_ctx->tx.bytes_done < req->nbytes)) { > + int ret; > + > + if (req_ctx->tx.bytes_done < req_ctx->rx.bytes_done) > + /* Keep TX in sync with RX */ > + ret = sss_handle_fifo(&req_ctx->tx); > + else > + /* Transmit some more data to RX */ > + ret = sss_handle_fifo(&req_ctx->rx); > + > + sss_dump_channel(&req_ctx->tx); > + sss_dump_channel(&req_ctx->rx); > + > + if (ret && ret != -EINPROGRESS) { > + req_ctx->err = ret; > + goto cleanup; > + } > + > + mod_timer(&sss->timer, jiffies + SSS_TIMEOUT); > + return; > + } > + > +cleanup: > + sss_cleanup_fifo(&req_ctx->rx); > + sss_cleanup_fifo(&req_ctx->tx); > + > + clk_disable(sss->clock); > + > + /* Inform client of completion */ > + req->base.complete(&req->base, req_ctx->err); > + > + spin_lock_irqsave(&sss->lock, flags); > + sss->cur_req = NULL; > + /* Check whether there's still work to do */ > + if (sss->queue.qlen || crypto_get_backlog(&sss->queue)) > + tasklet_schedule(&sss->tasklet); > + spin_unlock_irqrestore(&sss->lock, flags); > +} > + > +/*** SW handling ***/ > + > +static int sss_crypto_generic(struct ablkcipher_request *req, > + enum sss_algorithm alg, > + enum sss_block_cipher blk_ciph, > + enum sss_direction dir) > +{ > + struct sss_context *ctx = crypto_tfm_ctx(req->base.tfm); > + struct sss_req_context *req_ctx = ablkcipher_request_ctx(req); > + struct s3c_sss *sss = ctx->dev; > + unsigned long flags; > + int ret; > + > + /* Fill the request */ > + *req_ctx = (struct sss_req_context){ > + .algorithm = alg, > + .blk_cipher = blk_ciph, > + .direction = dir, > + .rx = { > + .dev = sss, > + .dir = FIFO_RX, > + }, > + .tx = { > + .dev = sss, > + .dir = FIFO_TX, > + }, > + }; > + > + /* Enqueue the request */ > + spin_lock_irqsave(&sss->lock, flags); > + ret = ablkcipher_enqueue_request(&sss->queue, req); > + if (ret == -EINPROGRESS && !sss->cur_req) > + tasklet_schedule(&sss->tasklet); > + spin_unlock_irqrestore(&sss->lock, flags); > + > + if (ret != -EINPROGRESS) > + dev_err(sss->dev, "Couldn't enqueue request!\n"); > + > + return ret; > +} > + > +static int sss_aes_setkey(struct crypto_ablkcipher *cipher, const u8 *key, > + unsigned int len) > +{ > + struct crypto_tfm *tfm = crypto_ablkcipher_tfm(cipher); > + struct sss_context *ctx = crypto_tfm_ctx(tfm); > + > + switch (len) { > + case AES_KEYSIZE_128: > + case AES_KEYSIZE_192: > + case AES_KEYSIZE_256: > + break; > + default: > + crypto_ablkcipher_set_flags(cipher, CRYPTO_TFM_RES_BAD_KEY_LEN); > + return -EINVAL; > + } > + > + memcpy(ctx->key, key, len); > + ctx->key_len = len; > + > + return 0; > +} > + > +static int sss_des_setkey(struct crypto_ablkcipher *cipher, const u8 *key, > + unsigned int len) > +{ > + struct crypto_tfm *tfm = crypto_ablkcipher_tfm(cipher); > + struct sss_context *ctx = crypto_tfm_ctx(tfm); > + > + if (len > SSS_MAX_KEY_SIZE) { > + crypto_ablkcipher_set_flags(cipher, CRYPTO_TFM_RES_BAD_KEY_LEN); > + return -EINVAL; > + } > + > + /* RFC2451: weak key checks SHOULD be performed */ > + if (len == DES_KEY_SIZE) { > + u32 tmp[DES_EXPKEY_WORDS]; > + int ret = des_ekey(tmp, key); > + > + if (unlikely(ret == 0) && > + (crypto_tfm_get_flags(tfm) & CRYPTO_TFM_REQ_WEAK_KEY)) { > + crypto_tfm_set_flags(tfm, CRYPTO_TFM_RES_WEAK_KEY); > + return -EINVAL; > + } > + } > + > + memcpy(ctx->key, key, len); > + ctx->key_len = len; > + > + return 0; > +} > + > +static int sss_cra_init(struct crypto_tfm *tfm) > +{ > + struct sss_crypto_wrapper *wrapper = container_of(tfm->__crt_alg, > + struct sss_crypto_wrapper, alg); > + struct s3c_sss *sss = wrapper->dev; > + struct sss_context *ctx = crypto_tfm_ctx(tfm); > + > + ctx->dev = sss; > + tfm->crt_ablkcipher.reqsize = sizeof(struct sss_req_context); > + > + return 0; > +} > + > +static int sss_aes_cbc_encrypt(struct ablkcipher_request *req) > +{ > + return sss_crypto_generic(req, ALGO_AES, CIPH_CBC, ENCRYPT); > +} > + > +static int sss_aes_cbc_decrypt(struct ablkcipher_request *req) > +{ > + return sss_crypto_generic(req, ALGO_AES, CIPH_CBC, DECRYPT); > +} > + > +static int sss_aes_ecb_encrypt(struct ablkcipher_request *req) > +{ > + return sss_crypto_generic(req, ALGO_AES, CIPH_ECB, ENCRYPT); > +} > + > +static int sss_aes_ecb_decrypt(struct ablkcipher_request *req) > +{ > + return sss_crypto_generic(req, ALGO_AES, CIPH_ECB, DECRYPT); > +} > + > +static int sss_aes_ctr_encrypt(struct ablkcipher_request *req) > +{ > + return sss_crypto_generic(req, ALGO_AES, CIPH_CTR, ENCRYPT); > +} > + > +static int sss_aes_ctr_decrypt(struct ablkcipher_request *req) > +{ > + return sss_crypto_generic(req, ALGO_AES, CIPH_CTR, DECRYPT); > +} > + > +static int sss_des_ecb_encrypt(struct ablkcipher_request *req) > +{ > + return sss_crypto_generic(req, ALGO_DES, CIPH_ECB, ENCRYPT); > +} > + > +static int sss_des_ecb_decrypt(struct ablkcipher_request *req) > +{ > + return sss_crypto_generic(req, ALGO_DES, CIPH_ECB, DECRYPT); > +} > + > +static int sss_des_cbc_encrypt(struct ablkcipher_request *req) > +{ > + return sss_crypto_generic(req, ALGO_DES, CIPH_CBC, ENCRYPT); > +} > + > +static int sss_des_cbc_decrypt(struct ablkcipher_request *req) > +{ > + return sss_crypto_generic(req, ALGO_DES, CIPH_CBC, DECRYPT); > +} > + > +static int sss_tdes_ecb_encrypt(struct ablkcipher_request *req) > +{ > + return sss_crypto_generic(req, ALGO_TDES, CIPH_ECB, ENCRYPT); > +} > + > +static int sss_tdes_ecb_decrypt(struct ablkcipher_request *req) > +{ > + return sss_crypto_generic(req, ALGO_TDES, CIPH_ECB, DECRYPT); > +} > + > +static int sss_tdes_cbc_encrypt(struct ablkcipher_request *req) > +{ > + return sss_crypto_generic(req, ALGO_TDES, CIPH_CBC, ENCRYPT); > +} > + > +static int sss_tdes_cbc_decrypt(struct ablkcipher_request *req) > +{ > + return sss_crypto_generic(req, ALGO_TDES, CIPH_CBC, DECRYPT); > +} > + > +static struct sss_algo_template { > + char *alg_name; > + char *blk_ciph_name; > + int blk_size; > + struct ablkcipher_alg ablkcipher; > + > + struct sss_crypto_wrapper *alg; > +} sss_crypto_algos[] = { > + /* AES ECB/CBC/CTR */ > + { > + .alg_name = "aes", > + .blk_ciph_name = "ecb", > + .blk_size = AES_BLOCK_SIZE, > + .ablkcipher = { > + .min_keysize = AES_MIN_KEY_SIZE, > + .max_keysize = AES_MAX_KEY_SIZE, > + .setkey = sss_aes_setkey, > + .encrypt = sss_aes_ecb_encrypt, > + .decrypt = sss_aes_ecb_decrypt, > + }, > + }, > + { > + .alg_name = "aes", > + .blk_ciph_name = "cbc", > + .blk_size = AES_BLOCK_SIZE, > + .ablkcipher = { > + .ivsize = AES_BLOCK_SIZE, > + .min_keysize = AES_MIN_KEY_SIZE, > + .max_keysize = AES_MAX_KEY_SIZE, > + .setkey = sss_aes_setkey, > + .encrypt = sss_aes_cbc_encrypt, > + .decrypt = sss_aes_cbc_decrypt, > + }, > + }, > + { > + .alg_name = "aes", > + .blk_ciph_name = "ctr", > + .blk_size = AES_BLOCK_SIZE, > + .ablkcipher = { > + .ivsize = CTR_RFC3686_IV_SIZE, > + .min_keysize = AES_MIN_KEY_SIZE, > + .max_keysize = AES_MAX_KEY_SIZE, > + .setkey = sss_aes_setkey, > + .encrypt = sss_aes_ctr_encrypt, > + .decrypt = sss_aes_ctr_decrypt, > + }, > + }, > + /* DES CBC/ECB */ > + { > + .alg_name = "des", > + .blk_ciph_name = "cbc", > + .blk_size = DES_BLOCK_SIZE, > + .ablkcipher = { > + .ivsize = DES_BLOCK_SIZE, > + .min_keysize = DES_KEY_SIZE, > + .max_keysize = DES_KEY_SIZE, > + .setkey = sss_des_setkey, > + .encrypt = sss_des_cbc_encrypt, > + .decrypt = sss_des_cbc_decrypt, > + }, > + }, > + { > + .alg_name = "des", > + .blk_ciph_name = "ecb", > + .blk_size = DES_BLOCK_SIZE, > + .ablkcipher = { > + .min_keysize = DES_KEY_SIZE, > + .max_keysize = DES_KEY_SIZE, > + .setkey = sss_des_setkey, > + .encrypt = sss_des_ecb_encrypt, > + .decrypt = sss_des_ecb_decrypt, > + }, > + }, > + /* TDES CBC/ECB */ > + { > + .alg_name = "des3_ede", > + .blk_ciph_name = "cbc", > + .blk_size = DES3_EDE_BLOCK_SIZE, > + .ablkcipher = { > + .ivsize = DES3_EDE_BLOCK_SIZE, > + .min_keysize = DES3_EDE_KEY_SIZE, > + .max_keysize = DES3_EDE_KEY_SIZE, > + .setkey = sss_des_setkey, > + .encrypt = sss_tdes_cbc_encrypt, > + .decrypt = sss_tdes_cbc_decrypt, > + }, > + }, > + { > + .alg_name = "des3_ede", > + .blk_ciph_name = "ecb", > + .blk_size = DES3_EDE_BLOCK_SIZE, > + .ablkcipher = { > + .min_keysize = DES3_EDE_KEY_SIZE, > + .max_keysize = DES3_EDE_KEY_SIZE, > + .setkey = sss_des_setkey, > + .encrypt = sss_tdes_ecb_encrypt, > + .decrypt = sss_tdes_ecb_decrypt, > + }, > + }, > +}; > + > +static int sss_init_template(struct platform_device *pdev, > + struct sss_algo_template *templ) > +{ > + struct s3c_sss *sss = platform_get_drvdata(pdev); > + struct sss_crypto_wrapper *alg; > + > + alg = kzalloc(sizeof(struct sss_crypto_wrapper), GFP_KERNEL); > + if (!alg) > + return -ENOMEM; > + > + alg->dev = sss; > + alg->alg = (struct crypto_alg){ > + .cra_ablkcipher = templ->ablkcipher, > + .cra_blocksize = templ->blk_size, > + .cra_ctxsize = sizeof(struct sss_context), > + .cra_flags = CRYPTO_ALG_TYPE_ABLKCIPHER | > + CRYPTO_ALG_ASYNC, > + .cra_init = sss_cra_init, > + .cra_module = THIS_MODULE, > + .cra_priority = SSS_CRA_PRIORITY, > + .cra_type = &crypto_ablkcipher_type, > + }; > + > + snprintf(alg->alg.cra_name, CRYPTO_MAX_ALG_NAME, "%s(%s)", > + templ->blk_ciph_name, templ->alg_name); > + snprintf(alg->alg.cra_driver_name, CRYPTO_MAX_ALG_NAME, "%s-%s-%s", > + pdev->name, templ->alg_name, templ->blk_ciph_name); > + > + /* Save pointer for removal */ > + templ->alg = alg; > + > + dev_info(sss->dev, "crypto acceleration for %s", alg->alg.cra_name); > + > + return 0; > +} > + > +static struct ahash_alg sss_hash_algos[] = { > +#if 0 > + { > + .init = sss_sha_init, > + .update = sss_sha_update, > + .final = sss_sha1_final, > + .digest = sss_sha1_digest, > + .halg = { > + .digestsize = SHA1_DIGEST_SIZE, > + .statesize = sizeof(struct sha1_state), > + .base = { > + .cra_name = "sha1", > + .cra_driver_name = "s3c-sss-sha1", > + .cra_priority = SSS_CRA_PRIORITY, > + .cra_flags = CRYPTO_ALG_TYPE_AHASH | > + CRYPTO_ALG_ASYNC, > + .cra_blocksize = SHA1_BLOCK_SIZE, > + .cra_ctxsize = sizeof(struct sss_context), > + .cra_type = &crypto_ahash_type, > + .cra_module = THIS_MODULE, > + .cra_init = sss_cra_init, > + } > + }, > + }, > + { > + .init = sss_sha_init, > + .update = sss_sha_update, > + .final = sss_sha1_final, > + .digest = sss_sha1_digest, > + .setkey = sss_sha1_setkey, > + .halg = { > + .digestsize = SHA1_DIGEST_SIZE, > + .statesize = sizeof(struct sha1_state), > + .base = { > + .cra_name = "hmac(sha1)", > + .cra_driver_name = "s3c-sss-hmac-sha1", > + .cra_priority = SSS_CRA_PRIORITY, > + .cra_flags = CRYPTO_ALG_TYPE_AHASH | > + CRYPTO_ALG_ASYNC, > + .cra_blocksize = SHA1_BLOCK_SIZE, > + .cra_ctxsize = sizeof(struct sss_context), > + .cra_type = &crypto_ahash_type, > + .cra_module = THIS_MODULE, > + .cra_init = sss_cra_init, > + } > + }, > + }, > +#endif > +}; > + > +static void sss_unregister_algos(void) > +{ > + struct sss_crypto_wrapper *alg; > + int i; > + > + for (i = 0; i < ARRAY_SIZE(sss_crypto_algos); i++) { > + alg = sss_crypto_algos[i].alg; > + > + if (alg) { > + crypto_unregister_alg(&alg->alg); > + kfree(alg); > + > + sss_crypto_algos[i].alg = NULL; > + } > + } > + > + /* Unregistering algorithms that weren't registered in the first place > + doesn't do any harm, so just do it for all. */ > + for (i = 0; i < ARRAY_SIZE(sss_hash_algos); i++) > + crypto_unregister_ahash(&sss_hash_algos[i]); > +} > + > +static int sss_register_algos(struct platform_device *pdev) > +{ > + int i, ret; > + > + for (i = 0; i < ARRAY_SIZE(sss_crypto_algos); i++) { > + ret = sss_init_template(pdev, &sss_crypto_algos[i]); > + if (ret) > + goto exit; > + > + ret = crypto_register_alg(&sss_crypto_algos[i].alg->alg); > + if (ret) > + goto exit; > + } > + > + for (i = 0; i < ARRAY_SIZE(sss_hash_algos); i++) { > + ret = crypto_register_ahash(&sss_hash_algos[i]); > + if (ret) > + goto exit; > + } > + > + return 0; > + > +exit: > + sss_unregister_algos(); > + return ret; > +} > + > +static int __devinit sss_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct resource *res; > + struct s3c_sss *sss; > + int ret; > + > + sss = kzalloc(sizeof(struct s3c_sss), GFP_KERNEL); > + if (!sss) { > + dev_err(dev, "cannot allocate memory\n"); > + return -ENOMEM; > + } > + > + spin_lock_init(&sss->lock); > + crypto_init_queue(&sss->queue, 50); > + tasklet_init(&sss->tasklet, sss_tasklet_callback, (unsigned long) sss); > + setup_timer(&sss->timer, sss_timer_callback, (unsigned long) sss); > + > + sss->dev = dev; > + sss->dma_client.name = (char *) pdev->name; > + platform_set_drvdata(pdev, sss); > + > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + if (!res) { > + dev_err(dev, "cannot find register resource\n"); > + ret = -EINVAL; > + goto exit_dev; > + } > + > + sss->regs_res = request_mem_region(res->start, resource_size(res), > + dev_name(dev)); > + if (!sss->regs_res) { > + dev_err(dev, "cannot request register resource\n"); > + ret = -ENOENT; > + goto exit_dev; > + } > + > + sss->regs_phys = (void __iomem *) res->start; > + > + sss->regs = ioremap(res->start, resource_size(res)); > + if (!sss->regs) { > + dev_err(dev, "cannot map registers\n"); > + ret = -ENXIO; > + goto exit_resource; > + } > + > + ret = platform_get_irq(pdev, 0); > + if (ret < 0 || ret == NO_IRQ) { > + dev_err(dev, "cannot find IRQ\n"); > + goto exit_regs_remap; > + } > + > + sss->irq = ret; > + > + ret = request_irq(sss->irq, sss_irq, 0, dev_name(dev), sss); > + if (ret < 0) { > + dev_err(dev, "cannot claim IRQ\n"); > + goto exit_regs_remap; > + } > + > + sss->clock = clk_get(dev, "secur"); > + if (!sss->clock) { > + dev_err(dev, "cannot find clock\n"); > + ret = -ENXIO; > + goto exit_irq; > + } > + > + WARN_ON(clk_set_rate(sss->clock, 66*1000000)); /*REMOVEME*/ > + > + if (sss_dma_enabled()) { > + ret = sss_setup_dma(sss, DMACH_SECURITY_RX); > + if (ret < 0) { > + dev_err(dev, "cannot setup SECURITY_RX DMA channel\n"); > + goto exit_clock; > + } > + > + ret = sss_setup_dma(sss, DMACH_SECURITY_TX); > + if (ret < 0) { > + dev_err(dev, "cannot setup SECURITY_TX DMA channel\n"); > + goto exit_dma_rx; > + } > + } > + > + ret = sss_register_algos(pdev); > + if (ret) { > + dev_err(dev, "cannot register algos\n"); > + goto exit_dma_tx; > + } > + > + return 0; > + > +exit_dma_tx: > + if (sss_dma_enabled()) > + s3c2410_dma_free(DMACH_SECURITY_TX, &sss->dma_client); > +exit_dma_rx: > + if (sss_dma_enabled()) > + s3c2410_dma_free(DMACH_SECURITY_RX, &sss->dma_client); > +exit_clock: > + clk_put(sss->clock); > +exit_irq: > + free_irq(sss->irq, sss); > +exit_regs_remap: > + iounmap(sss->regs); > +exit_resource: > + release_resource(sss->regs_res); > + kfree(sss->regs_res); > +exit_dev: > + tasklet_kill(&sss->tasklet); > + kfree(sss); > + > + return ret; > +} > + > +static int __devexit sss_remove(struct platform_device *pdev) > +{ > + struct s3c_sss *sss = platform_get_drvdata(pdev); > + > + if (timer_pending(&sss->timer)) > + del_timer(&sss->timer); > + > + if (sss_dma_enabled()) { > + s3c2410_dma_free(DMACH_SECURITY_TX, &sss->dma_client); > + s3c2410_dma_free(DMACH_SECURITY_RX, &sss->dma_client); > + } > + > + sss_unregister_algos(); > + clk_put(sss->clock); > + free_irq(sss->irq, sss); > + iounmap(sss->regs); > + release_resource(sss->regs_res); > + kfree(sss->regs_res); > + tasklet_kill(&sss->tasklet); > + kfree(sss); > + > + return 0; > +} > + > +static struct platform_driver sss_crypto = { > + .driver = { > + .name = "s3c-sss", > + .owner = THIS_MODULE, > + }, > + .probe = sss_probe, > + .remove = __devexit_p(sss_remove), > +}; > + > +static int __init sss_crypto_init(void) > +{ > + return platform_driver_register(&sss_crypto); > +} > +module_init(sss_crypto_init); > + > +static void __exit sss_crypto_exit(void) > +{ > + platform_driver_unregister(&sss_crypto); > +} > +module_exit(sss_crypto_exit); > + > +MODULE_AUTHOR("Maurus Cuelenaere <mcuelenaere@xxxxxxxxx>"); > +MODULE_DESCRIPTION("Support for Samsung's Security Sub-Systems"); > +MODULE_LICENSE("GPL"); > +MODULE_ALIAS("platform:sss_crypto"); > -- > 1.7.1 > > -- > To unsubscribe from this list: send the line "unsubscribe linux-samsung-soc" in > the body of a message to majordomo@xxxxxxxxxxxxxxx > More majordomo info at http://vger.kernel.org/majordomo-info.html > -- To unsubscribe from this list: send the line "unsubscribe linux-crypto" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html