Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0+
0002 /*
0003  * Allwinner sunXi SoCs Security ID support.
0004  *
0005  * Copyright (c) 2013 Oliver Schinagl <oliver@schinagl.nl>
0006  * Copyright (C) 2014 Maxime Ripard <maxime.ripard@free-electrons.com>
0007  */
0008 
0009 #include <linux/device.h>
0010 #include <linux/io.h>
0011 #include <linux/iopoll.h>
0012 #include <linux/module.h>
0013 #include <linux/nvmem-provider.h>
0014 #include <linux/of.h>
0015 #include <linux/of_device.h>
0016 #include <linux/platform_device.h>
0017 #include <linux/slab.h>
0018 #include <linux/random.h>
0019 
0020 /* Registers and special values for doing register-based SID readout on H3 */
0021 #define SUN8I_SID_PRCTL     0x40
0022 #define SUN8I_SID_RDKEY     0x60
0023 
0024 #define SUN8I_SID_OFFSET_MASK   0x1FF
0025 #define SUN8I_SID_OFFSET_SHIFT  16
0026 #define SUN8I_SID_OP_LOCK   (0xAC << 8)
0027 #define SUN8I_SID_READ      BIT(1)
0028 
0029 struct sunxi_sid_cfg {
0030     u32 value_offset;
0031     u32 size;
0032     bool    need_register_readout;
0033 };
0034 
0035 struct sunxi_sid {
0036     void __iomem        *base;
0037     u32         value_offset;
0038 };
0039 
0040 static int sunxi_sid_read(void *context, unsigned int offset,
0041               void *val, size_t bytes)
0042 {
0043     struct sunxi_sid *sid = context;
0044 
0045     memcpy_fromio(val, sid->base + sid->value_offset + offset, bytes);
0046 
0047     return 0;
0048 }
0049 
0050 static int sun8i_sid_register_readout(const struct sunxi_sid *sid,
0051                       const unsigned int offset,
0052                       u32 *out)
0053 {
0054     u32 reg_val;
0055     int ret;
0056 
0057     /* Set word, lock access, and set read command */
0058     reg_val = (offset & SUN8I_SID_OFFSET_MASK)
0059           << SUN8I_SID_OFFSET_SHIFT;
0060     reg_val |= SUN8I_SID_OP_LOCK | SUN8I_SID_READ;
0061     writel(reg_val, sid->base + SUN8I_SID_PRCTL);
0062 
0063     ret = readl_poll_timeout(sid->base + SUN8I_SID_PRCTL, reg_val,
0064                  !(reg_val & SUN8I_SID_READ), 100, 250000);
0065     if (ret)
0066         return ret;
0067 
0068     if (out)
0069         *out = readl(sid->base + SUN8I_SID_RDKEY);
0070 
0071     writel(0, sid->base + SUN8I_SID_PRCTL);
0072 
0073     return 0;
0074 }
0075 
0076 /*
0077  * On Allwinner H3, the value on the 0x200 offset of the SID controller seems
0078  * to be not reliable at all.
0079  * Read by the registers instead.
0080  */
0081 static int sun8i_sid_read_by_reg(void *context, unsigned int offset,
0082                  void *val, size_t bytes)
0083 {
0084     struct sunxi_sid *sid = context;
0085     u32 word;
0086     int ret;
0087 
0088     /* .stride = 4 so offset is guaranteed to be aligned */
0089     while (bytes >= 4) {
0090         ret = sun8i_sid_register_readout(sid, offset, val);
0091         if (ret)
0092             return ret;
0093 
0094         val += 4;
0095         offset += 4;
0096         bytes -= 4;
0097     }
0098 
0099     if (!bytes)
0100         return 0;
0101 
0102     /* Handle any trailing bytes */
0103     ret = sun8i_sid_register_readout(sid, offset, &word);
0104     if (ret)
0105         return ret;
0106 
0107     memcpy(val, &word, bytes);
0108 
0109     return 0;
0110 }
0111 
0112 static int sunxi_sid_probe(struct platform_device *pdev)
0113 {
0114     struct device *dev = &pdev->dev;
0115     struct resource *res;
0116     struct nvmem_config *nvmem_cfg;
0117     struct nvmem_device *nvmem;
0118     struct sunxi_sid *sid;
0119     int size;
0120     char *randomness;
0121     const struct sunxi_sid_cfg *cfg;
0122 
0123     sid = devm_kzalloc(dev, sizeof(*sid), GFP_KERNEL);
0124     if (!sid)
0125         return -ENOMEM;
0126 
0127     cfg = of_device_get_match_data(dev);
0128     if (!cfg)
0129         return -EINVAL;
0130     sid->value_offset = cfg->value_offset;
0131 
0132     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
0133     sid->base = devm_ioremap_resource(dev, res);
0134     if (IS_ERR(sid->base))
0135         return PTR_ERR(sid->base);
0136 
0137     size = cfg->size;
0138 
0139     nvmem_cfg = devm_kzalloc(dev, sizeof(*nvmem_cfg), GFP_KERNEL);
0140     if (!nvmem_cfg)
0141         return -ENOMEM;
0142 
0143     nvmem_cfg->dev = dev;
0144     nvmem_cfg->name = "sunxi-sid";
0145     nvmem_cfg->type = NVMEM_TYPE_OTP;
0146     nvmem_cfg->read_only = true;
0147     nvmem_cfg->size = cfg->size;
0148     nvmem_cfg->word_size = 1;
0149     nvmem_cfg->stride = 4;
0150     nvmem_cfg->priv = sid;
0151     if (cfg->need_register_readout)
0152         nvmem_cfg->reg_read = sun8i_sid_read_by_reg;
0153     else
0154         nvmem_cfg->reg_read = sunxi_sid_read;
0155 
0156     nvmem = devm_nvmem_register(dev, nvmem_cfg);
0157     if (IS_ERR(nvmem))
0158         return PTR_ERR(nvmem);
0159 
0160     randomness = kzalloc(size, GFP_KERNEL);
0161     if (!randomness)
0162         return -ENOMEM;
0163 
0164     nvmem_cfg->reg_read(sid, 0, randomness, size);
0165     add_device_randomness(randomness, size);
0166     kfree(randomness);
0167 
0168     platform_set_drvdata(pdev, nvmem);
0169 
0170     return 0;
0171 }
0172 
0173 static const struct sunxi_sid_cfg sun4i_a10_cfg = {
0174     .size = 0x10,
0175 };
0176 
0177 static const struct sunxi_sid_cfg sun7i_a20_cfg = {
0178     .size = 0x200,
0179 };
0180 
0181 static const struct sunxi_sid_cfg sun8i_h3_cfg = {
0182     .value_offset = 0x200,
0183     .size = 0x100,
0184     .need_register_readout = true,
0185 };
0186 
0187 static const struct sunxi_sid_cfg sun20i_d1_cfg = {
0188     .value_offset = 0x200,
0189     .size = 0x100,
0190 };
0191 
0192 static const struct sunxi_sid_cfg sun50i_a64_cfg = {
0193     .value_offset = 0x200,
0194     .size = 0x100,
0195     .need_register_readout = true,
0196 };
0197 
0198 static const struct sunxi_sid_cfg sun50i_h6_cfg = {
0199     .value_offset = 0x200,
0200     .size = 0x200,
0201 };
0202 
0203 static const struct of_device_id sunxi_sid_of_match[] = {
0204     { .compatible = "allwinner,sun4i-a10-sid", .data = &sun4i_a10_cfg },
0205     { .compatible = "allwinner,sun7i-a20-sid", .data = &sun7i_a20_cfg },
0206     { .compatible = "allwinner,sun8i-a83t-sid", .data = &sun50i_a64_cfg },
0207     { .compatible = "allwinner,sun8i-h3-sid", .data = &sun8i_h3_cfg },
0208     { .compatible = "allwinner,sun20i-d1-sid", .data = &sun20i_d1_cfg },
0209     { .compatible = "allwinner,sun50i-a64-sid", .data = &sun50i_a64_cfg },
0210     { .compatible = "allwinner,sun50i-h5-sid", .data = &sun50i_a64_cfg },
0211     { .compatible = "allwinner,sun50i-h6-sid", .data = &sun50i_h6_cfg },
0212     {/* sentinel */},
0213 };
0214 MODULE_DEVICE_TABLE(of, sunxi_sid_of_match);
0215 
0216 static struct platform_driver sunxi_sid_driver = {
0217     .probe = sunxi_sid_probe,
0218     .driver = {
0219         .name = "eeprom-sunxi-sid",
0220         .of_match_table = sunxi_sid_of_match,
0221     },
0222 };
0223 module_platform_driver(sunxi_sid_driver);
0224 
0225 MODULE_AUTHOR("Oliver Schinagl <oliver@schinagl.nl>");
0226 MODULE_DESCRIPTION("Allwinner sunxi security id driver");
0227 MODULE_LICENSE("GPL");