summaryrefslogtreecommitdiff
path: root/drivers/char/hw_random
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2018-04-04 17:11:08 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2018-04-04 17:11:08 -0700
commit9eb31227cbccd3a37da0f42604f1ab5fc556bc53 (patch)
tree9aa467e620e002bf01cecdd98e3908e0cc3e7221 /drivers/char/hw_random
parent527cd20771888443b5d8707debe98f62c7a1f596 (diff)
parentf444ec106407d600f17fa1a4bd14f84577401dec (diff)
Merge branch 'linus' of git://git.kernel.org/pub/scm/linux/kernel/git/herbert/crypto-2.6
Pull crypto updates from Herbert Xu: "API: - add AEAD support to crypto engine - allow batch registration in simd Algorithms: - add CFB mode - add speck block cipher - add sm4 block cipher - new test case for crct10dif - improve scheduling latency on ARM - scatter/gather support to gcm in aesni - convert x86 crypto algorithms to skcihper Drivers: - hmac(sha224/sha256) support in inside-secure - aes gcm/ccm support in stm32 - stm32mp1 support in stm32 - ccree driver from staging tree - gcm support over QI in caam - add ks-sa hwrng driver" * 'linus' of git://git.kernel.org/pub/scm/linux/kernel/git/herbert/crypto-2.6: (212 commits) crypto: ccree - remove unused enums crypto: ahash - Fix early termination in hash walk crypto: brcm - explicitly cast cipher to hash type crypto: talitos - don't leak pointers to authenc keys crypto: qat - don't leak pointers to authenc keys crypto: picoxcell - don't leak pointers to authenc keys crypto: ixp4xx - don't leak pointers to authenc keys crypto: chelsio - don't leak pointers to authenc keys crypto: caam/qi - don't leak pointers to authenc keys crypto: caam - don't leak pointers to authenc keys crypto: lrw - Free rctx->ext with kzfree crypto: talitos - fix IPsec cipher in length crypto: Deduplicate le32_to_cpu_array() and cpu_to_le32_array() crypto: doc - clarify hash callbacks state machine crypto: api - Keep failed instances alive crypto: api - Make crypto_alg_lookup static crypto: api - Remove unused crypto_type lookup function crypto: chelsio - Remove declaration of static function from header crypto: inside-secure - hmac(sha224) support crypto: inside-secure - hmac(sha256) support ..
Diffstat (limited to 'drivers/char/hw_random')
-rw-r--r--drivers/char/hw_random/Kconfig7
-rw-r--r--drivers/char/hw_random/Makefile1
-rw-r--r--drivers/char/hw_random/bcm2835-rng.c2
-rw-r--r--drivers/char/hw_random/cavium-rng-vf.c2
-rw-r--r--drivers/char/hw_random/cavium-rng.c2
-rw-r--r--drivers/char/hw_random/imx-rngc.c2
-rw-r--r--drivers/char/hw_random/ks-sa-rng.c257
-rw-r--r--drivers/char/hw_random/mxc-rnga.c23
-rw-r--r--drivers/char/hw_random/omap-rng.c22
-rw-r--r--drivers/char/hw_random/stm32-rng.c44
10 files changed, 330 insertions, 32 deletions
diff --git a/drivers/char/hw_random/Kconfig b/drivers/char/hw_random/Kconfig
index 4d0f571c15f9..d53541e96bee 100644
--- a/drivers/char/hw_random/Kconfig
+++ b/drivers/char/hw_random/Kconfig
@@ -452,3 +452,10 @@ config UML_RANDOM
(check your distro, or download from
http://sourceforge.net/projects/gkernel/). rngd periodically reads
/dev/hwrng and injects the entropy into /dev/random.
+
+config HW_RANDOM_KEYSTONE
+ depends on ARCH_KEYSTONE
+ default HW_RANDOM
+ tristate "TI Keystone NETCP SA Hardware random number generator"
+ help
+ This option enables Keystone's hardware random generator.
diff --git a/drivers/char/hw_random/Makefile b/drivers/char/hw_random/Makefile
index b780370bd4eb..533e913c93d1 100644
--- a/drivers/char/hw_random/Makefile
+++ b/drivers/char/hw_random/Makefile
@@ -38,3 +38,4 @@ obj-$(CONFIG_HW_RANDOM_MESON) += meson-rng.o
obj-$(CONFIG_HW_RANDOM_CAVIUM) += cavium-rng.o cavium-rng-vf.o
obj-$(CONFIG_HW_RANDOM_MTK) += mtk-rng.o
obj-$(CONFIG_HW_RANDOM_S390) += s390-trng.o
+obj-$(CONFIG_HW_RANDOM_KEYSTONE) += ks-sa-rng.o
diff --git a/drivers/char/hw_random/bcm2835-rng.c b/drivers/char/hw_random/bcm2835-rng.c
index 7a84cec30c3a..6767d965c36c 100644
--- a/drivers/char/hw_random/bcm2835-rng.c
+++ b/drivers/char/hw_random/bcm2835-rng.c
@@ -163,6 +163,8 @@ static int bcm2835_rng_probe(struct platform_device *pdev)
/* Clock is optional on most platforms */
priv->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(priv->clk) && PTR_ERR(priv->clk) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
priv->rng.name = pdev->name;
priv->rng.init = bcm2835_rng_init;
diff --git a/drivers/char/hw_random/cavium-rng-vf.c b/drivers/char/hw_random/cavium-rng-vf.c
index dd1007aecb10..2d1352b67168 100644
--- a/drivers/char/hw_random/cavium-rng-vf.c
+++ b/drivers/char/hw_random/cavium-rng-vf.c
@@ -77,7 +77,7 @@ static int cavium_rng_probe_vf(struct pci_dev *pdev,
}
/* Remove the VF */
-void cavium_rng_remove_vf(struct pci_dev *pdev)
+static void cavium_rng_remove_vf(struct pci_dev *pdev)
{
struct cavium_rng *rng;
diff --git a/drivers/char/hw_random/cavium-rng.c b/drivers/char/hw_random/cavium-rng.c
index a944e0a47f42..63d6e68c24d2 100644
--- a/drivers/char/hw_random/cavium-rng.c
+++ b/drivers/char/hw_random/cavium-rng.c
@@ -62,7 +62,7 @@ static int cavium_rng_probe(struct pci_dev *pdev,
}
/* Disable VF and RNG Hardware */
-void cavium_rng_remove(struct pci_dev *pdev)
+static void cavium_rng_remove(struct pci_dev *pdev)
{
struct cavium_rng_pf *rng;
diff --git a/drivers/char/hw_random/imx-rngc.c b/drivers/char/hw_random/imx-rngc.c
index eca87249bcff..250123bc4905 100644
--- a/drivers/char/hw_random/imx-rngc.c
+++ b/drivers/char/hw_random/imx-rngc.c
@@ -300,7 +300,7 @@ static int __maybe_unused imx_rngc_resume(struct device *dev)
return 0;
}
-SIMPLE_DEV_PM_OPS(imx_rngc_pm_ops, imx_rngc_suspend, imx_rngc_resume);
+static SIMPLE_DEV_PM_OPS(imx_rngc_pm_ops, imx_rngc_suspend, imx_rngc_resume);
static const struct of_device_id imx_rngc_dt_ids[] = {
{ .compatible = "fsl,imx25-rngb", .data = NULL, },
diff --git a/drivers/char/hw_random/ks-sa-rng.c b/drivers/char/hw_random/ks-sa-rng.c
new file mode 100644
index 000000000000..62c6696c1dbd
--- /dev/null
+++ b/drivers/char/hw_random/ks-sa-rng.c
@@ -0,0 +1,257 @@
+/*
+ * Random Number Generator driver for the Keystone SOC
+ *
+ * Copyright (C) 2016 Texas Instruments Incorporated - http://www.ti.com
+ *
+ * Authors: Sandeep Nair
+ * Vitaly Andrianov
+ *
+ * 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.
+ *
+ * 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/hw_random.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/pm_runtime.h>
+#include <linux/err.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/delay.h>
+
+#define SA_CMD_STATUS_OFS 0x8
+
+/* TRNG enable control in SA System module*/
+#define SA_CMD_STATUS_REG_TRNG_ENABLE BIT(3)
+
+/* TRNG start control in TRNG module */
+#define TRNG_CNTL_REG_TRNG_ENABLE BIT(10)
+
+/* Data ready indicator in STATUS register */
+#define TRNG_STATUS_REG_READY BIT(0)
+
+/* Data ready clear control in INTACK register */
+#define TRNG_INTACK_REG_READY BIT(0)
+
+/*
+ * Number of samples taken to gather entropy during startup.
+ * If value is 0, the number of samples is 2^24 else
+ * equals value times 2^8.
+ */
+#define TRNG_DEF_STARTUP_CYCLES 0
+#define TRNG_CNTL_REG_STARTUP_CYCLES_SHIFT 16
+
+/*
+ * Minimum number of samples taken to regenerate entropy
+ * If value is 0, the number of samples is 2^24 else
+ * equals value times 2^6.
+ */
+#define TRNG_DEF_MIN_REFILL_CYCLES 1
+#define TRNG_CFG_REG_MIN_REFILL_CYCLES_SHIFT 0
+
+/*
+ * Maximum number of samples taken to regenerate entropy
+ * If value is 0, the number of samples is 2^24 else
+ * equals value times 2^8.
+ */
+#define TRNG_DEF_MAX_REFILL_CYCLES 0
+#define TRNG_CFG_REG_MAX_REFILL_CYCLES_SHIFT 16
+
+/* Number of CLK input cycles between samples */
+#define TRNG_DEF_CLK_DIV_CYCLES 0
+#define TRNG_CFG_REG_SAMPLE_DIV_SHIFT 8
+
+/* Maximum retries to get rng data */
+#define SA_MAX_RNG_DATA_RETRIES 5
+/* Delay between retries (in usecs) */
+#define SA_RNG_DATA_RETRY_DELAY 5
+
+struct trng_regs {
+ u32 output_l;
+ u32 output_h;
+ u32 status;
+ u32 intmask;
+ u32 intack;
+ u32 control;
+ u32 config;
+};
+
+struct ks_sa_rng {
+ struct device *dev;
+ struct hwrng rng;
+ struct clk *clk;
+ struct regmap *regmap_cfg;
+ struct trng_regs *reg_rng;
+};
+
+static int ks_sa_rng_init(struct hwrng *rng)
+{
+ u32 value;
+ struct device *dev = (struct device *)rng->priv;
+ struct ks_sa_rng *ks_sa_rng = dev_get_drvdata(dev);
+
+ /* Enable RNG module */
+ regmap_write_bits(ks_sa_rng->regmap_cfg, SA_CMD_STATUS_OFS,
+ SA_CMD_STATUS_REG_TRNG_ENABLE,
+ SA_CMD_STATUS_REG_TRNG_ENABLE);
+
+ /* Configure RNG module */
+ writel(0, &ks_sa_rng->reg_rng->control);
+ value = TRNG_DEF_STARTUP_CYCLES << TRNG_CNTL_REG_STARTUP_CYCLES_SHIFT;
+ writel(value, &ks_sa_rng->reg_rng->control);
+
+ value = (TRNG_DEF_MIN_REFILL_CYCLES <<
+ TRNG_CFG_REG_MIN_REFILL_CYCLES_SHIFT) |
+ (TRNG_DEF_MAX_REFILL_CYCLES <<
+ TRNG_CFG_REG_MAX_REFILL_CYCLES_SHIFT) |
+ (TRNG_DEF_CLK_DIV_CYCLES <<
+ TRNG_CFG_REG_SAMPLE_DIV_SHIFT);
+
+ writel(value, &ks_sa_rng->reg_rng->config);
+
+ /* Disable all interrupts from TRNG */
+ writel(0, &ks_sa_rng->reg_rng->intmask);
+
+ /* Enable RNG */
+ value = readl(&ks_sa_rng->reg_rng->control);
+ value |= TRNG_CNTL_REG_TRNG_ENABLE;
+ writel(value, &ks_sa_rng->reg_rng->control);
+
+ return 0;
+}
+
+static void ks_sa_rng_cleanup(struct hwrng *rng)
+{
+ struct device *dev = (struct device *)rng->priv;
+ struct ks_sa_rng *ks_sa_rng = dev_get_drvdata(dev);
+
+ /* Disable RNG */
+ writel(0, &ks_sa_rng->reg_rng->control);
+ regmap_write_bits(ks_sa_rng->regmap_cfg, SA_CMD_STATUS_OFS,
+ SA_CMD_STATUS_REG_TRNG_ENABLE, 0);
+}
+
+static int ks_sa_rng_data_read(struct hwrng *rng, u32 *data)
+{
+ struct device *dev = (struct device *)rng->priv;
+ struct ks_sa_rng *ks_sa_rng = dev_get_drvdata(dev);
+
+ /* Read random data */
+ data[0] = readl(&ks_sa_rng->reg_rng->output_l);
+ data[1] = readl(&ks_sa_rng->reg_rng->output_h);
+
+ writel(TRNG_INTACK_REG_READY, &ks_sa_rng->reg_rng->intack);
+
+ return sizeof(u32) * 2;
+}
+
+static int ks_sa_rng_data_present(struct hwrng *rng, int wait)
+{
+ struct device *dev = (struct device *)rng->priv;
+ struct ks_sa_rng *ks_sa_rng = dev_get_drvdata(dev);
+
+ u32 ready;
+ int j;
+
+ for (j = 0; j < SA_MAX_RNG_DATA_RETRIES; j++) {
+ ready = readl(&ks_sa_rng->reg_rng->status);
+ ready &= TRNG_STATUS_REG_READY;
+
+ if (ready || !wait)
+ break;
+
+ udelay(SA_RNG_DATA_RETRY_DELAY);
+ }
+
+ return ready;
+}
+
+static int ks_sa_rng_probe(struct platform_device *pdev)
+{
+ struct ks_sa_rng *ks_sa_rng;
+ struct device *dev = &pdev->dev;
+ int ret;
+ struct resource *mem;
+
+ ks_sa_rng = devm_kzalloc(dev, sizeof(*ks_sa_rng), GFP_KERNEL);
+ if (!ks_sa_rng)
+ return -ENOMEM;
+
+ ks_sa_rng->dev = dev;
+ ks_sa_rng->rng = (struct hwrng) {
+ .name = "ks_sa_hwrng",
+ .init = ks_sa_rng_init,
+ .data_read = ks_sa_rng_data_read,
+ .data_present = ks_sa_rng_data_present,
+ .cleanup = ks_sa_rng_cleanup,
+ };
+ ks_sa_rng->rng.priv = (unsigned long)dev;
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ ks_sa_rng->reg_rng = devm_ioremap_resource(dev, mem);
+ if (IS_ERR(ks_sa_rng->reg_rng))
+ return PTR_ERR(ks_sa_rng->reg_rng);
+
+ ks_sa_rng->regmap_cfg =
+ syscon_regmap_lookup_by_phandle(dev->of_node,
+ "ti,syscon-sa-cfg");
+
+ if (IS_ERR(ks_sa_rng->regmap_cfg)) {
+ dev_err(dev, "syscon_node_to_regmap failed\n");
+ return -EINVAL;
+ }
+
+ pm_runtime_enable(dev);
+ ret = pm_runtime_get_sync(dev);
+ if (ret < 0) {
+ dev_err(dev, "Failed to enable SA power-domain\n");
+ pm_runtime_disable(dev);
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, ks_sa_rng);
+
+ return devm_hwrng_register(&pdev->dev, &ks_sa_rng->rng);
+}
+
+static int ks_sa_rng_remove(struct platform_device *pdev)
+{
+ pm_runtime_put_sync(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static const struct of_device_id ks_sa_rng_dt_match[] = {
+ {
+ .compatible = "ti,keystone-rng",
+ },
+ { },
+};
+MODULE_DEVICE_TABLE(of, ks_sa_rng_dt_match);
+
+static struct platform_driver ks_sa_rng_driver = {
+ .driver = {
+ .name = "ks-sa-rng",
+ .of_match_table = ks_sa_rng_dt_match,
+ },
+ .probe = ks_sa_rng_probe,
+ .remove = ks_sa_rng_remove,
+};
+
+module_platform_driver(ks_sa_rng_driver);
+
+MODULE_DESCRIPTION("Keystone NETCP SA H/W Random Number Generator driver");
+MODULE_AUTHOR("Vitaly Andrianov <vitalya@ti.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/hw_random/mxc-rnga.c b/drivers/char/hw_random/mxc-rnga.c
index 467362262651..f83bee513d91 100644
--- a/drivers/char/hw_random/mxc-rnga.c
+++ b/drivers/char/hw_random/mxc-rnga.c
@@ -16,16 +16,13 @@
* This driver is based on other RNG drivers.
*/
-#include <linux/module.h>
-#include <linux/init.h>
-#include <linux/kernel.h>
#include <linux/clk.h>
-#include <linux/err.h>
-#include <linux/ioport.h>
-#include <linux/platform_device.h>
-#include <linux/hw_random.h>
#include <linux/delay.h>
+#include <linux/hw_random.h>
#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
/* RNGA Registers */
#define RNGA_CONTROL 0x00
@@ -197,10 +194,18 @@ static int __exit mxc_rnga_remove(struct platform_device *pdev)
return 0;
}
+static const struct of_device_id mxc_rnga_of_match[] = {
+ { .compatible = "fsl,imx21-rnga", },
+ { .compatible = "fsl,imx31-rnga", },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, mxc_rnga_of_match);
+
static struct platform_driver mxc_rnga_driver = {
.driver = {
- .name = "mxc_rnga",
- },
+ .name = "mxc_rnga",
+ .of_match_table = mxc_rnga_of_match,
+ },
.remove = __exit_p(mxc_rnga_remove),
};
diff --git a/drivers/char/hw_random/omap-rng.c b/drivers/char/hw_random/omap-rng.c
index 74d11ae6abe9..b65ff6962899 100644
--- a/drivers/char/hw_random/omap-rng.c
+++ b/drivers/char/hw_random/omap-rng.c
@@ -150,6 +150,7 @@ struct omap_rng_dev {
const struct omap_rng_pdata *pdata;
struct hwrng rng;
struct clk *clk;
+ struct clk *clk_reg;
};
static inline u32 omap_rng_read(struct omap_rng_dev *priv, u16 reg)
@@ -480,6 +481,19 @@ static int omap_rng_probe(struct platform_device *pdev)
}
}
+ priv->clk_reg = devm_clk_get(&pdev->dev, "reg");
+ if (IS_ERR(priv->clk_reg) && PTR_ERR(priv->clk_reg) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+ if (!IS_ERR(priv->clk_reg)) {
+ ret = clk_prepare_enable(priv->clk_reg);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "Unable to enable the register clk: %d\n",
+ ret);
+ goto err_register;
+ }
+ }
+
ret = (dev->of_node) ? of_get_omap_rng_device_details(priv, pdev) :
get_omap_rng_device_details(priv);
if (ret)
@@ -499,8 +513,8 @@ err_register:
pm_runtime_put_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev);
- if (!IS_ERR(priv->clk))
- clk_disable_unprepare(priv->clk);
+ clk_disable_unprepare(priv->clk_reg);
+ clk_disable_unprepare(priv->clk);
err_ioremap:
dev_err(dev, "initialization failed.\n");
return ret;
@@ -517,8 +531,8 @@ static int omap_rng_remove(struct platform_device *pdev)
pm_runtime_put_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev);
- if (!IS_ERR(priv->clk))
- clk_disable_unprepare(priv->clk);
+ clk_disable_unprepare(priv->clk);
+ clk_disable_unprepare(priv->clk_reg);
return 0;
}
diff --git a/drivers/char/hw_random/stm32-rng.c b/drivers/char/hw_random/stm32-rng.c
index 63d84e6f1891..0d2328da3b76 100644
--- a/drivers/char/hw_random/stm32-rng.c
+++ b/drivers/char/hw_random/stm32-rng.c
@@ -16,15 +16,18 @@
#include <linux/delay.h>
#include <linux/hw_random.h>
#include <linux/io.h>
+#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/pm_runtime.h>
+#include <linux/reset.h>
#include <linux/slab.h>
#define RNG_CR 0x00
#define RNG_CR_RNGEN BIT(2)
+#define RNG_CR_CED BIT(5)
#define RNG_SR 0x04
#define RNG_SR_SEIS BIT(6)
@@ -33,19 +36,12 @@
#define RNG_DR 0x08
-/*
- * It takes 40 cycles @ 48MHz to generate each random number (e.g. <1us).
- * At the time of writing STM32 parts max out at ~200MHz meaning a timeout
- * of 500 leaves us a very comfortable margin for error. The loop to which
- * the timeout applies takes at least 4 instructions per iteration so the
- * timeout is enough to take us up to multi-GHz parts!
- */
-#define RNG_TIMEOUT 500
-
struct stm32_rng_private {
struct hwrng rng;
void __iomem *base;
struct clk *clk;
+ struct reset_control *rst;
+ bool ced;
};
static int stm32_rng_read(struct hwrng *rng, void *data, size_t max, bool wait)
@@ -59,13 +55,16 @@ static int stm32_rng_read(struct hwrng *rng, void *data, size_t max, bool wait)
while (max > sizeof(u32)) {
sr = readl_relaxed(priv->base + RNG_SR);
+ /* Manage timeout which is based on timer and take */
+ /* care of initial delay time when enabling rng */
if (!sr && wait) {
- unsigned int timeout = RNG_TIMEOUT;
-
- do {
- cpu_relax();
- sr = readl_relaxed(priv->base + RNG_SR);
- } while (!sr && --timeout);
+ retval = readl_relaxed_poll_timeout_atomic(priv->base
+ + RNG_SR,
+ sr, sr,
+ 10, 50000);
+ if (retval)
+ dev_err((struct device *)priv->rng.priv,
+ "%s: timeout %x!\n", __func__, sr);
}
/* If error detected or data not ready... */
@@ -99,7 +98,11 @@ static int stm32_rng_init(struct hwrng *rng)
if (err)
return err;
- writel_relaxed(RNG_CR_RNGEN, priv->base + RNG_CR);
+ if (priv->ced)
+ writel_relaxed(RNG_CR_RNGEN, priv->base + RNG_CR);
+ else
+ writel_relaxed(RNG_CR_RNGEN | RNG_CR_CED,
+ priv->base + RNG_CR);
/* clear error indicators */
writel_relaxed(0, priv->base + RNG_SR);
@@ -140,6 +143,15 @@ static int stm32_rng_probe(struct platform_device *ofdev)
if (IS_ERR(priv->clk))
return PTR_ERR(priv->clk);
+ priv->rst = devm_reset_control_get(&ofdev->dev, NULL);
+ if (!IS_ERR(priv->rst)) {
+ reset_control_assert(priv->rst);
+ udelay(2);
+ reset_control_deassert(priv->rst);
+ }
+
+ priv->ced = of_property_read_bool(np, "clock-error-detect");
+
dev_set_drvdata(dev, priv);
priv->rng.name = dev_driver_string(dev),