From: Zhao Qunqin zhaoqunqin@loongson.cn
LoongArch inclusion category: feature bugzilla: https://gitee.com/openeuler/kernel/issues/IAZ3GN
--------------------------------
Signed-off-by: Zhao Qunqin zhaoqunqin@loongson.cn --- arch/loongarch/include/asm/se.h | 146 ++++++++ drivers/char/Kconfig | 18 + drivers/char/Makefile | 2 + drivers/char/loongson_se.c | 598 ++++++++++++++++++++++++++++++++ drivers/char/lsse_sdf_cdev.c | 381 ++++++++++++++++++++ 5 files changed, 1145 insertions(+) create mode 100644 arch/loongarch/include/asm/se.h create mode 100644 drivers/char/loongson_se.c create mode 100644 drivers/char/lsse_sdf_cdev.c
diff --git a/arch/loongarch/include/asm/se.h b/arch/loongarch/include/asm/se.h new file mode 100644 index 000000000000..a6b968d2d545 --- /dev/null +++ b/arch/loongarch/include/asm/se.h @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2012 IBM Corporation + * + * Copyright 2023 Loongson Technology, Inc. + * Yinggang Gu guyinggang@loongson.cn + * + * Device driver for Loongson SE module. + * + * 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, version 2 of the + * License. + * + */ +#ifndef __LOONGSON_SE_H__ +#define __LOONGSON_SE_H__ + +#define SE_MAILBOX_S 0x0 +#define SE_MAILBOX_L 0x20 +#define SE_S2LINT_STAT 0x88 +#define SE_S2LINT_EN 0x8c +#define SE_S2LINT_SET 0x90 +#define SE_S2LINT_CL 0x94 +#define SE_L2SINT_STAT 0x98 +#define SE_L2SINT_EN 0x9c +#define SE_L2SINT_SET 0xa0 +#define SE_L2SINT_CL 0xa4 + +/* INT bit definition */ +#define SE_INT_SETUP BIT(0) +#define SE_INT_SM2 BIT(0) +#define SE_INT_SM3 BIT(0) +#define SE_INT_SM4 BIT(0) +#define SE_INT_RNG BIT(0) +#define SE_INT_TPM BIT(5) +#define SE_INT_ALL 0xffffffff + +#define SE_CMD_START 0x0 +#define SE_CMD_STOP 0x1 +#define SE_CMD_GETVER 0x2 +#define SE_CMD_SETBUF 0x3 +#define SE_CMD_SETMSG 0x4 + +#define SE_CMD_RNG 0x100 + +#define SE_CMD_SM2_SIGN 0x200 +#define SE_CMD_SM2_VSIGN 0x201 + +#define SE_CMD_SM3_DIGEST 0x300 +#define SE_CMD_SM3_UPDATE 0x301 +#define SE_CMD_SM3_FINISH 0x302 + +#define SE_CMD_SM4_ECB_ENCRY 0x400 +#define SE_CMD_SM4_ECB_DECRY 0x401 +#define SE_CMD_SM4_CBC_ENCRY 0x402 +#define SE_CMD_SM4_CBC_DECRY 0x403 +#define SE_CMD_SM4_CTR 0x404 + +#define SE_CMD_TPM 0x500 +#define SE_CMD_ZUC_INIT_READ 0x600 +#define SE_CMD_ZUC_READ 0x601 + +#define SE_CMD_SDF 0x700 + +#define SE_CH_MAX 32 + +#define SE_CH_RNG 1 +#define SE_CH_SM2 2 +#define SE_CH_SM3 3 +#define SE_CH_SM4 4 +#define SE_CH_TPM 5 +#define SE_CH_ZUC 6 +#define SE_CH_SDF 7 + +struct se_msg { + u32 cmd; + u32 data_off; + u32 data_len; + u32 info[5]; +}; + +struct se_cmd { + u32 cmd; + u32 info[7]; +}; + +struct se_res { + u32 cmd; + u32 cmd_ret; + u32 info[6]; +}; + +struct se_mailbox_data { + u32 int_bit; + union { + u32 mailbox[8]; + struct se_cmd gcmd; + struct se_res res; + } u; +}; + +struct lsse_ch { + u32 id; + u32 int_bit; + struct loongson_se *se; + void *priv; + spinlock_t ch_lock; + void *smsg; + void *rmsg; + int msg_size; + void *data_buffer; + dma_addr_t data_addr; + int data_size; + + void (*complete)(struct lsse_ch *se_ch); +}; + +struct loongson_se { + struct device *dev; + void __iomem *base; + u32 version; + u32 ch_status; + spinlock_t cmd_lock; + spinlock_t dev_lock; + + /* Interaction memory */ + void *mem_base; + dma_addr_t mem_addr; + unsigned long *mem_map; + int mem_map_size; + void *smsg; + void *rmsg; + + /* Synchronous CMD */ + struct completion cmd_completion; + + /* Virtual Channel */ + struct lsse_ch chs[SE_CH_MAX]; +}; + +struct lsse_ch *se_init_ch(int id, int data_size, int msg_size, void *priv, + void (*complete)(struct lsse_ch *se_ch)); +void se_deinit_ch(struct lsse_ch *ch); +int se_send_ch_requeset(struct lsse_ch *ch); + +#endif diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index 625af75833fc..8a5e272bdc78 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -391,6 +391,24 @@ config UV_MMTIMER The uv_mmtimer device allows direct userspace access to the UV system timer.
+config LOONGSON_SE + tristate "LOONGSON SECURITY MODULE Interface" + depends on LOONGARCH + default m + help + If you have LOONGSON security module (SE) support say Yes and it + will be accessible from within Linux. To compile this driver + as a module, choose M here; the module will be called loongson-se. + +config LOONGSON_SE_SDF + tristate "LOONGSON SECURITY MODULE SDF Interface" + depends on LOONGARCH && LOONGSON_SE + default m + help + If you want to use LOONGSON security module (SE) as SDF say Yes + and it will be accessible from within Linux. To compile this driver + as a module, choose M here; + source "drivers/char/tpm/Kconfig"
config TELCLOCK diff --git a/drivers/char/Makefile b/drivers/char/Makefile index c5f532e412f1..109af71c5416 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -32,6 +32,8 @@ obj-$(CONFIG_SCx200_GPIO) += scx200_gpio.o obj-$(CONFIG_PC8736x_GPIO) += pc8736x_gpio.o obj-$(CONFIG_NSC_GPIO) += nsc_gpio.o obj-$(CONFIG_TELCLOCK) += tlclk.o +obj-$(CONFIG_LOONGSON_SE) += loongson_se.o +obj-$(CONFIG_LOONGSON_SE_SDF) += lsse_sdf_cdev.o
obj-$(CONFIG_MWAVE) += mwave/ obj-y += agp/ diff --git a/drivers/char/loongson_se.c b/drivers/char/loongson_se.c new file mode 100644 index 000000000000..5c010b2e8d8c --- /dev/null +++ b/drivers/char/loongson_se.c @@ -0,0 +1,598 @@ +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/cdev.h> +#include <linux/device.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/miscdevice.h> +#include <linux/iopoll.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <asm/se.h> + +static int se_mem_size = 0x800000; +module_param(se_mem_size, int, S_IRUGO); +MODULE_PARM_DESC(se_mem_size, "LOONGSON SE shared memory size"); + +static int se_mem_page = PAGE_SIZE; +module_param(se_mem_page, int, S_IRUGO); +MODULE_PARM_DESC(se_mem_page, "LOONGSON SE shared memory page size"); + +static struct loongson_se se_dev; + +static int lsse_open(struct inode *inode, struct file *filp) +{ + return 0; +} + +static ssize_t lsse_write(struct file *filp, const char __user *buf, + size_t cnt, loff_t *offt) +{ + return 0; +} + +static struct file_operations lsse_fops = { + .owner = THIS_MODULE, + .open = lsse_open, + .write = lsse_write, +}; + +static struct miscdevice lsse_miscdev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "loongson-se", + .fops = &lsse_fops, +}; + +static inline u32 se_readl(u64 addr) +{ + return readl(se_dev.base + addr); +} + +static inline void se_writel(u32 val, u64 addr) +{ + writel(val, se_dev.base + addr); +} + +static inline bool se_ch_status(struct loongson_se *se, u32 int_bit) +{ + return !!(se->ch_status & int_bit) == 1; +} + +static void se_enable_int(struct loongson_se *se, u32 int_bit) +{ + unsigned long flag; + u32 tmp; + + if (!int_bit) + return; + + spin_lock_irqsave(&se->dev_lock, flag); + + tmp = se_readl(SE_S2LINT_EN); + tmp |= int_bit; + se_writel(tmp, SE_S2LINT_EN); + + spin_unlock_irqrestore(&se->dev_lock, flag); +} + +static void se_disable_int(struct loongson_se *se, u32 int_bit) +{ + unsigned long flag; + u32 tmp; + + if (!int_bit) + return; + + spin_lock_irqsave(&se->dev_lock, flag); + + tmp = se_readl(SE_S2LINT_EN); + tmp &= ~(int_bit); + se_writel(tmp, SE_S2LINT_EN); + + spin_unlock_irqrestore(&se->dev_lock, flag); +} + +static int se_send_requeset(struct loongson_se *se, + struct se_mailbox_data *req) +{ + unsigned long flag; + u32 status; + int err = 0; + int i; + + if (!se || !req) + return -EINVAL; + + if (se_readl(SE_L2SINT_STAT) || + !(se_readl(SE_L2SINT_EN) & req->int_bit)) + return -EBUSY; + + spin_lock_irqsave(&se->cmd_lock, flag); + + for (i = 0; i < ARRAY_SIZE(req->u.mailbox); i++) + se_writel(req->u.mailbox[i], SE_MAILBOX_S + i * 4); + + se_writel(req->int_bit, SE_L2SINT_SET); + + err = readl_relaxed_poll_timeout_atomic(se->base + SE_L2SINT_STAT, status, + !(status & req->int_bit), 10, 10000); + + spin_unlock_irqrestore(&se->cmd_lock, flag); + + return err; +} + +static int se_get_response(struct loongson_se *se, + struct se_mailbox_data *res) +{ + unsigned long flag; + int i; + + if (!se || !res) + return -EINVAL; + + if ((se_readl(SE_S2LINT_STAT) & res->int_bit) == 0) + return -EBUSY; + + spin_lock_irqsave(&se->cmd_lock, flag); + + for (i = 0; i < ARRAY_SIZE(res->u.mailbox); i++) + res->u.mailbox[i] = se_readl(SE_MAILBOX_L + i * 4); + + se_writel(res->int_bit, SE_S2LINT_CL); + + spin_unlock_irqrestore(&se->cmd_lock, flag); + + return 0; +} + +static int loongson_se_get_res(struct loongson_se *se, u32 int_bit, u32 cmd, + struct se_mailbox_data *res) +{ + int err = 0; + + res->int_bit = int_bit; + + if (se_get_response(se, res)) { + dev_err(se->dev, "Int 0x%x get response fail.\n", int_bit); + return -EFAULT; + } + + /* Check response */ + if (res->u.res.cmd == cmd) + err = 0; + else { + dev_err(se->dev, "Response cmd is 0x%x, not expect cmd 0x%x.\n", + res->u.res.cmd, cmd); + err = -EFAULT; + } + + return err; +} + +static int se_send_genl_cmd(struct loongson_se *se, struct se_mailbox_data *req, + struct se_mailbox_data *res, int retry) +{ + int err = 0, cnt = 0; + +try_again: + if (cnt++ >= retry) { + err = -ETIMEDOUT; + goto out; + } + + dev_dbg(se->dev, "%d time send cmd 0x%x\n", cnt, req->u.gcmd.cmd); + + err = se_send_requeset(se, req); + if (err) + goto try_again; + + if (!wait_for_completion_timeout(&se->cmd_completion, + msecs_to_jiffies(0x1000))) { + se_enable_int(se, req->int_bit); + goto try_again; + } + + err = loongson_se_get_res(se, req->int_bit, req->u.gcmd.cmd, res); + if (err || res->u.res.cmd_ret) { + se_enable_int(se, req->int_bit); + goto try_again; + } + +out: + se_enable_int(se, req->int_bit); + + return err; +} + +static int loongson_se_set_msg(struct lsse_ch *ch) +{ + struct loongson_se *se = ch->se; + struct se_mailbox_data req = {0}; + struct se_mailbox_data res = {0}; + int err; + + req.int_bit = SE_INT_SETUP; + req.u.gcmd.cmd = SE_CMD_SETMSG; + /* MSG off */ + req.u.gcmd.info[0] = ch->id; + req.u.gcmd.info[1] = ch->smsg - se->mem_base; + req.u.gcmd.info[2] = ch->msg_size; + + dev_dbg(se->dev, "Set Channel %d msg off 0x%x, msg size %d\n", ch->id, + req.u.gcmd.info[1], req.u.gcmd.info[2]); + + err = se_send_genl_cmd(se, &req, &res, 5); + if (res.u.res.cmd_ret) + return res.u.res.cmd_ret; + + return err; +} + +static irqreturn_t loongson_se_irq(int irq, void *dev_id) +{ + struct loongson_se *se = (struct loongson_se *)dev_id; + struct lsse_ch *ch; + u32 int_status; + + int_status = se_readl(SE_S2LINT_STAT); + + dev_dbg(se->dev, "%s int status is 0x%x\n", __func__, int_status); + + se_disable_int(se, int_status); + + if (int_status & SE_INT_SETUP) { + complete(&se->cmd_completion); + int_status &= ~SE_INT_SETUP; + } + + while (int_status) { + int id = __ffs(int_status); + ch = &se->chs[id]; + if (ch->complete) + ch->complete(ch); + int_status &= ~BIT(id); + se_writel(BIT(id), SE_S2LINT_CL); + } + + return IRQ_HANDLED; +} + +static int se_init_hw(struct loongson_se *se) +{ + struct se_mailbox_data req = {0}; + struct se_mailbox_data res = {0}; + struct device *dev = se->dev; + int err, retry = 5; + u64 size; + + size = se_mem_size; + + if (size & (size - 1)) { + size = roundup_pow_of_two(size); + se_mem_size = size; + } + + se_enable_int(se, SE_INT_SETUP); + + /* Start engine */ + memset(&req, 0, sizeof(struct se_mailbox_data)); + memset(&res, 0, sizeof(struct se_mailbox_data)); + req.int_bit = SE_INT_SETUP; + req.u.gcmd.cmd = SE_CMD_START; + err = se_send_genl_cmd(se, &req, &res, retry); + if (err) + return err; + + /* Get Version */ + memset(&req, 0, sizeof(struct se_mailbox_data)); + memset(&res, 0, sizeof(struct se_mailbox_data)); + req.int_bit = SE_INT_SETUP; + req.u.gcmd.cmd = SE_CMD_GETVER; + err = se_send_genl_cmd(se, &req, &res, retry); + if (err) + return err; + + se->version = res.u.res.info[0]; + + /* Setup data buffer */ + se->mem_base = dmam_alloc_coherent(dev, size, + &se->mem_addr, GFP_KERNEL); + if (!se->mem_base) + return -ENOMEM; + + memset(se->mem_base, 0, size); + + memset(&req, 0, sizeof(struct se_mailbox_data)); + memset(&res, 0, sizeof(struct se_mailbox_data)); + req.int_bit = SE_INT_SETUP; + req.u.gcmd.cmd = SE_CMD_SETBUF; + /* MMAP */ + req.u.gcmd.info[0] = (se->mem_addr & 0xffffffff) | 0x80; + req.u.gcmd.info[1] = se->mem_addr >> 32; + /* MASK */ + req.u.gcmd.info[2] = ~(size - 1); + req.u.gcmd.info[3] = 0xffffffff; + + pr_debug("Set win mmap 0x%llx, mask 0x%llx\n", + ((u64)req.u.gcmd.info[1] << 32) | req.u.gcmd.info[0], + ((u64)req.u.gcmd.info[3] << 32) | req.u.gcmd.info[2]); + + err = se_send_genl_cmd(se, &req, &res, retry); + if (err) + return err; + + se->mem_map_size = size / se_mem_page; + se->mem_map = bitmap_zalloc(se->mem_map_size, GFP_KERNEL); + if (!se->mem_map) + return -ENOMEM; + + dev_info(se->dev, "SE module setup down, shared memory size is 0x%x bytes, " + "memory page size is 0x%x bytes\n", + se_mem_size, se_mem_page); + + return err; +} + +static void loongson_se_disable_hw(struct loongson_se *se) +{ + struct se_mailbox_data req = {0}; + struct se_mailbox_data res = {0}; + int retry = 5; + + /* Stop engine */ + req.int_bit = SE_INT_SETUP; + req.u.gcmd.cmd = SE_CMD_STOP; + se_send_genl_cmd(se, &req, &res, retry); + + se_disable_int(se, SE_INT_ALL); + kfree(se->mem_map); +} + +int se_send_ch_requeset(struct lsse_ch *ch) +{ + struct loongson_se *se; + u32 status, int_bit; + int err = 0; + + if (!ch) + return -EINVAL; + + se = ch->se; + int_bit = ch->int_bit; + + if ((se_readl(SE_L2SINT_STAT) & int_bit) || + !(se_readl(SE_L2SINT_EN) & int_bit)) + return -EBUSY; + + se_enable_int(se, int_bit); + se_writel(int_bit, SE_L2SINT_SET); + + err = readl_relaxed_poll_timeout_atomic(se->base + SE_L2SINT_STAT, status, + !(status & int_bit), 10, 10000); + + return err; +} +EXPORT_SYMBOL_GPL(se_send_ch_requeset); + +struct lsse_ch *se_init_ch(int id, int data_size, int msg_size, void *priv, + void (*complete)(struct lsse_ch *se_ch)) +{ + struct loongson_se *se = &se_dev; + struct lsse_ch *ch; + unsigned long flag; + int data_first, data_nr; + int msg_first, msg_nr; + + if (!se) { + pr_err("SE has bot been initialized\n"); + return NULL; + } + + if (id == 0 || id > SE_CH_MAX) { + dev_err(se->dev, "Channel number %d is invalid\n", id); + return NULL; + } + + if (se_ch_status(se, BIT(id))) { + dev_err(se->dev, "Channel number %d has been initialized\n", id); + return NULL; + } + + spin_lock_irqsave(&se->dev_lock, flag); + + ch = &se_dev.chs[id]; + ch->se = se; + ch->id = id; + ch->int_bit = BIT(id); + se->ch_status |= BIT(id); + + data_nr = round_up(data_size, se_mem_page) / se_mem_page; + data_first = bitmap_find_next_zero_area(se->mem_map, se->mem_map_size, + 0, data_nr, 0); + if (data_first >= se->mem_map_size) { + dev_err(se->dev, "Insufficient memory space\n"); + spin_unlock_irqrestore(&se->dev_lock, flag); + return NULL; + } + + bitmap_set(se->mem_map, data_first, data_nr); + ch->data_buffer = se->mem_base + data_first * se_mem_page; + ch->data_addr = se->mem_addr + data_first * se_mem_page; + ch->data_size = data_size; + + msg_nr = round_up(msg_size, se_mem_page) / se_mem_page; + msg_first = bitmap_find_next_zero_area(se->mem_map, se->mem_map_size, + 0, msg_nr, 0); + if (msg_first >= se->mem_map_size) { + dev_err(se->dev, "Insufficient memory space\n"); + bitmap_clear(se->mem_map, data_first, data_nr); + spin_unlock_irqrestore(&se->dev_lock, flag); + return NULL; + } + + bitmap_set(se->mem_map, msg_first, msg_nr); + ch->smsg = se->mem_base + msg_first * se_mem_page; + ch->rmsg = ch->smsg + msg_size / 2; + ch->msg_size = msg_size; + + ch->complete = complete; + ch->priv = priv; + + spin_lock_init(&ch->ch_lock); + + spin_unlock_irqrestore(&se->dev_lock, flag); + + if (loongson_se_set_msg(ch)) { + dev_err(se->dev, "Channel %d setup message address failed\n", id); + return NULL; + } + + se_enable_int(se, ch->int_bit); + + return ch; +} +EXPORT_SYMBOL_GPL(se_init_ch); + +void se_deinit_ch(struct lsse_ch *ch) +{ + struct loongson_se *se = &se_dev; + unsigned long flag; + int first, nr; + int id = ch->id; + + if (!se) { + pr_err("SE has bot been initialized\n"); + return; + } + + if (id == 0 || id > SE_CH_MAX) { + dev_err(se->dev, "Channel number %d is invalid\n", id); + return; + } + + if (!se_ch_status(se, BIT(id))) { + dev_err(se->dev, "Channel number %d has not been initialized\n", id); + return; + } + + spin_lock_irqsave(&se->dev_lock, flag); + + se->ch_status &= ~BIT(ch->id); + + first = (ch->data_buffer - se->mem_base) / se_mem_page; + nr = round_up(ch->data_size, se_mem_page) / se_mem_page; + bitmap_clear(se->mem_map, first, nr); + + first = (ch->smsg - se->mem_base) / se_mem_page; + nr = round_up(ch->msg_size, se_mem_page) / se_mem_page; + bitmap_clear(se->mem_map, first, nr); + + spin_unlock_irqrestore(&se->dev_lock, flag); + + se_disable_int(se, ch->int_bit); +} +EXPORT_SYMBOL_GPL(se_deinit_ch); + +static struct platform_device lsse_sdf_pdev = { + .name = "loongson-sdf", + .id = -1, +}; + +static const struct of_device_id loongson_se_of_match[] = { + { .compatible = "loongson,ls3c6000se", }, + {} +}; +MODULE_DEVICE_TABLE(of, loongson_se_of_match); + +static int loongson_se_probe(struct platform_device *pdev) +{ + struct loongson_se *se = &se_dev; + struct resource *res; + struct device *dev = &pdev->dev; + int nr_irq, err, i; + int irq[8]; + + nr_irq = platform_irq_count(pdev); + if (nr_irq < 0) + return -ENODEV; + + for (i = 0; i < nr_irq; i++) { + irq[i] = platform_get_irq(pdev, i); + if (irq[i] < 0) + return -ENODEV; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + se->base = devm_ioremap_resource(dev, res); + if (IS_ERR(se->base)) + return PTR_ERR(se->base); + + se->dev = &pdev->dev; + platform_set_drvdata(pdev, se); + dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); + init_completion(&se->cmd_completion); + spin_lock_init(&se->cmd_lock); + spin_lock_init(&se->dev_lock); + + for (i = 0; i < nr_irq; i++) { + err = devm_request_irq(dev, irq[i], loongson_se_irq, 0, + "loongson-se", se); + if (err) + goto out; + } + + err = se_init_hw(se); + if (err) + goto disable_hw; + + err = misc_register(&lsse_miscdev); + if (err) + goto disable_hw; + + err = platform_device_register(&lsse_sdf_pdev); + if (err) + pr_err("register sdf device failed\n"); + + return 0; + +disable_hw: + loongson_se_disable_hw(se); +out: + for (; i>=0; i--) + devm_free_irq(dev, irq[i], se); + return err; +} + +static int loongson_se_remove(struct platform_device *pdev) +{ + struct loongson_se *se = platform_get_drvdata(pdev); + + misc_deregister(&lsse_miscdev); + loongson_se_disable_hw(se); + platform_device_unregister(&lsse_sdf_pdev); + + return 0; +} + +static struct platform_driver loongson_se_driver = { + .probe = loongson_se_probe, + .remove = loongson_se_remove, + .driver = { + .name = "loongson-se", + .of_match_table = loongson_se_of_match, + }, +}; + +module_platform_driver(loongson_se_driver); + +MODULE_AUTHOR("Yinggang Gu"); +MODULE_DESCRIPTION("Loongson SE driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/lsse_sdf_cdev.c b/drivers/char/lsse_sdf_cdev.c new file mode 100644 index 000000000000..edadb56f2273 --- /dev/null +++ b/drivers/char/lsse_sdf_cdev.c @@ -0,0 +1,381 @@ +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/cdev.h> +#include <linux/module.h> +#include <linux/wait.h> +#include <linux/of.h> +#include <linux/iopoll.h> +#include <linux/platform_device.h> +#include <linux/uaccess.h> +#include <asm/se.h> +#include <linux/list.h> + +#define SE_SDF_BUFSIZE (PAGE_SIZE * 2) +#define SDF_OPENSESSION 0x204 +#define SDF_CLOSESESSION 0x205 + +struct lsse_sdf_dev { + struct lsse_ch *se_ch; + struct mutex data_lock; + bool processing_cmd; + + /* Synchronous CMD */ + wait_queue_head_t wq; +}; + +struct se_sdf_msg { + u32 cmd; + u32 data_off; + u32 data_len; + u32 info[5]; +}; + +struct sdf_command_header { + int command; + union { + int param_cnt; + int ret; + } u; + int param_len[14]; +}; + +struct sdf_kernel_command { + struct sdf_command_header header; + void *handle; +}; + +#define KERNEL_COMMAND_SIZE (sizeof(struct sdf_kernel_command)) + +struct sdf_handle { + struct list_head handle_list; + void *handle; +}; + +struct sdf_file_pvt_data { + struct lsse_sdf_dev *se; + struct list_head handle_list; + struct sdf_kernel_command skc; + struct sdf_handle *ph; +}; + +static struct lsse_sdf_dev *se_sdf_dev; + +static void lsse_sdf_complete(struct lsse_ch *ch) +{ + struct lsse_sdf_dev *se = (struct lsse_sdf_dev *)ch->priv; + + se->processing_cmd = false; + wake_up(&se->wq); +} + +static int se_send_sdf_cmd(struct lsse_sdf_dev *se, int len, int retry) +{ + struct se_sdf_msg *smsg = (struct se_sdf_msg *)se->se_ch->smsg; + unsigned long flag; + int err; + + spin_lock_irqsave(&se->se_ch->ch_lock, flag); + + smsg->cmd = SE_CMD_SDF; + /* One time one cmd */ + smsg->data_off = se->se_ch->data_buffer - se->se_ch->se->mem_base; + smsg->data_len = len; + +try_again: + if (!retry--) + goto out; + + pr_debug("Send sdf cmd, last retry %d times\n", retry); + + err = se_send_ch_requeset(se->se_ch); + if (err) { + udelay(5); + goto try_again; + } + +out: + spin_unlock_irqrestore(&se->se_ch->ch_lock, flag); + + return err; +} + +static int lsse_sdf_recv(struct sdf_file_pvt_data *pvt, char *buf, + size_t size, int user, int *se_ret) +{ + int len, time, ret = 0; + struct se_sdf_msg *rmsg; + struct sdf_kernel_command *skc; + struct sdf_handle *ph; + struct lsse_sdf_dev *se = pvt->se; + + if (!se->se_ch->rmsg) { + pr_err("se device is not ready\n"); + return -EBUSY; + } + + time = wait_event_timeout(se->wq, !se->processing_cmd, HZ*30); + if (!time) + return -ETIME; + + rmsg = (struct se_sdf_msg *)se->se_ch->rmsg; + if (rmsg->cmd != SE_CMD_SDF) { + pr_err("se get wrong response\n"); + return -EIO; + } + len = rmsg->data_len; + + if ((!user && len > KERNEL_COMMAND_SIZE) || len > SE_SDF_BUFSIZE + || (size && len > size)) + return -E2BIG; + + if (user) { + ret = copy_to_user((char __user *)buf, + se->se_ch->data_buffer + rmsg->data_off, len); + if (!se_ret) + return ret; + + skc = (struct sdf_kernel_command *) + (se->se_ch->data_buffer + rmsg->data_off); + *se_ret = skc->header.u.ret; + if (skc->header.command == SDF_OPENSESSION && !*se_ret) { + ph = kmalloc(sizeof(*ph), GFP_KERNEL); + if (!ph) + return -ENOMEM; + ph->handle = skc->handle; + list_add(&ph->handle_list, &pvt->handle_list); + } + } + else + memcpy(buf, se->se_ch->data_buffer + rmsg->data_off, len); + + return ret; +} + +static struct sdf_handle *find_sdf_handle(void *handle, + struct sdf_file_pvt_data *pvt) +{ + struct sdf_handle *ph; + + list_for_each_entry(ph, &pvt->handle_list, handle_list) { + if (ph->handle == handle) + return ph; + } + + return NULL; +} + +static int lsse_sdf_send(struct sdf_file_pvt_data *pvt, const char *buf, + size_t count, int user) +{ + int ret, se_ret; + struct sdf_handle *ph = NULL; + struct sdf_kernel_command *skc; + struct lsse_sdf_dev *se = pvt->se; + + if (!se->se_ch->smsg) { + pr_err("se device is not ready\n"); + return 0; + } + + if (count > se->se_ch->data_size) { + pr_err("Invalid size in send: count=%zd, size=%d\n", + count, se->se_ch->data_size); + return -EIO; + } + + if (user) { + ret = mutex_lock_interruptible(&se->data_lock); + if (ret) + goto out; + } else + mutex_lock(&se->data_lock); + + if (user) { + ret = copy_from_user(se->se_ch->data_buffer, buf, count); + if (ret) { + ret = -EFAULT; + goto out_unlock; + } + skc = (struct sdf_kernel_command *)se->se_ch->data_buffer; + if (skc->header.command == SDF_CLOSESESSION) { + ph = find_sdf_handle(skc->handle, pvt); + } + } + else + memcpy(se->se_ch->data_buffer, buf, count); + + se->processing_cmd = true; + ret = se_send_sdf_cmd(se, count, 5); + if (ret) { + pr_err("se_send_sdf_cmd failed\n"); + goto out_unlock; + } + + ret = lsse_sdf_recv(pvt, (char *)buf, 0, user, &se_ret); + if (ret) { + pr_err("recv failed ret: %x\n", ret); + goto out_unlock; + } + if (ph && !se_ret) { + list_del(&ph->handle_list); + kfree(ph); + } +out_unlock: + mutex_unlock(&se->data_lock); +out: + return ret; +} + +static ssize_t lsse_sdf_write(struct file *filp, const char __user *buf, + size_t cnt, loff_t *offt) +{ + struct sdf_file_pvt_data *pvt = filp->private_data; + + if (cnt > SE_SDF_BUFSIZE) + return -E2BIG; + + if (lsse_sdf_send(pvt, buf, cnt, 1)) + return -EFAULT; + + return cnt; +} + +static ssize_t lsse_sdf_read(struct file *filp, char __user *buf, + size_t size, loff_t *off) +{ + return lsse_sdf_recv(filp->private_data, buf, size, 1, NULL); +} + +static int close_one_handle(struct sdf_file_pvt_data *pvt, struct sdf_handle *ph) +{ + struct sdf_kernel_command *skc = &pvt->skc; + + skc->header.command = 0x205; + skc->header.u.param_cnt = 1; + skc->header.param_len[0] = 8; + skc->handle = ph->handle; + /* close one session */ + lsse_sdf_send(pvt, (char *)&pvt->skc, KERNEL_COMMAND_SIZE, 0); + if (skc->header.u.ret) { + pr_err("Auto Close Session failed, session handle: %llx, ret: %d\n", + (u64)ph->handle, skc->header.u.ret); + return skc->header.u.ret; + } + kfree(ph); + + return 0; +} + +static int close_all_handle(struct sdf_file_pvt_data *pvt) +{ + int ret = 0; + struct sdf_handle *ph, *tmp; + + list_for_each_entry_safe(ph, tmp, &pvt->handle_list, handle_list) { + list_del(&ph->handle_list); + ret = close_one_handle(pvt, ph); + if (ret) + return ret; + } + + return 0; +} + +static int lsse_sdf_release(struct inode *inode, struct file *filp) +{ + int ret; + struct sdf_file_pvt_data *pvt = filp->private_data; + + ret = close_all_handle(pvt); + filp->private_data = NULL; + kfree(pvt); + + if (ret) + ret = -EFAULT; + return ret; +} + +static int lsse_sdf_open(struct inode *inode, struct file *filp) +{ + struct sdf_file_pvt_data *pvt = kmalloc(sizeof(*pvt), GFP_KERNEL); + + if (!pvt) + return -ENOMEM; + + INIT_LIST_HEAD(&pvt->handle_list); + pvt->se = se_sdf_dev; + filp->private_data = pvt; + + return 0; +} + +static struct file_operations lsse_sdf_fops = { + .owner = THIS_MODULE, + .open = lsse_sdf_open, + .write = lsse_sdf_write, + .read = lsse_sdf_read, + .release = lsse_sdf_release, +}; + +static struct miscdevice lsse_sdf_miscdev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "lsse_sdf", + .fops = &lsse_sdf_fops, +}; + +static int lsse_sdf_probe(struct platform_device *pdev) +{ + int msg_size; + int ret; + + se_sdf_dev = kzalloc(sizeof(*se_sdf_dev), GFP_KERNEL); + if (IS_ERR_OR_NULL(se_sdf_dev)) + return PTR_ERR(se_sdf_dev); + + mutex_init(&se_sdf_dev->data_lock); + init_waitqueue_head(&se_sdf_dev->wq); + se_sdf_dev->processing_cmd = false; + + msg_size = 2 * sizeof(struct se_sdf_msg); + se_sdf_dev->se_ch = se_init_ch(SE_CH_SDF, SE_SDF_BUFSIZE, msg_size, + se_sdf_dev, lsse_sdf_complete); + + ret = misc_register(&lsse_sdf_miscdev); + if (ret < 0) { + pr_err("register sdf dev failed!\n"); + goto out; + } + + return 0; + +out: + kfree(se_sdf_dev); + + return ret; +} + +static int lsse_sdf_remove(struct platform_device *pdev) +{ + misc_deregister(&lsse_sdf_miscdev); + se_deinit_ch(se_sdf_dev->se_ch); + kfree(se_sdf_dev); + + return 0; +} + +static struct platform_driver loongson_sdf_driver = { + .probe = lsse_sdf_probe, + .remove = lsse_sdf_remove, + .driver = { + .name = "loongson-sdf", + }, +}; +module_platform_driver(loongson_sdf_driver); + +MODULE_ALIAS("platform:loongson-sdf"); +MODULE_AUTHOR("Yinggang Gu"); +MODULE_DESCRIPTION("Loongson SE sdf driver"); +MODULE_LICENSE("GPL");