13d0b16a6SMaxime Ripard /* 23d0b16a6SMaxime Ripard * Allwinner sunXi SoCs Security ID support. 33d0b16a6SMaxime Ripard * 43d0b16a6SMaxime Ripard * Copyright (c) 2013 Oliver Schinagl <oliver@schinagl.nl> 53d0b16a6SMaxime Ripard * Copyright (C) 2014 Maxime Ripard <maxime.ripard@free-electrons.com> 63d0b16a6SMaxime Ripard * 73d0b16a6SMaxime Ripard * This program is free software; you can redistribute it and/or modify 83d0b16a6SMaxime Ripard * it under the terms of the GNU General Public License as published by 93d0b16a6SMaxime Ripard * the Free Software Foundation; either version 2 of the License, or 103d0b16a6SMaxime Ripard * (at your option) any later version. 113d0b16a6SMaxime Ripard * 123d0b16a6SMaxime Ripard * This program is distributed in the hope that it will be useful, 133d0b16a6SMaxime Ripard * but WITHOUT ANY WARRANTY; without even the implied warranty of 143d0b16a6SMaxime Ripard * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 153d0b16a6SMaxime Ripard * GNU General Public License for more details. 163d0b16a6SMaxime Ripard */ 173d0b16a6SMaxime Ripard 183d0b16a6SMaxime Ripard #include <linux/device.h> 193d0b16a6SMaxime Ripard #include <linux/io.h> 201a963642SIcenowy Zheng #include <linux/iopoll.h> 213d0b16a6SMaxime Ripard #include <linux/module.h> 223d0b16a6SMaxime Ripard #include <linux/nvmem-provider.h> 233d0b16a6SMaxime Ripard #include <linux/of.h> 244a72cda5SIcenowy Zheng #include <linux/of_device.h> 253d0b16a6SMaxime Ripard #include <linux/platform_device.h> 263d0b16a6SMaxime Ripard #include <linux/slab.h> 273d0b16a6SMaxime Ripard #include <linux/random.h> 283d0b16a6SMaxime Ripard 291a963642SIcenowy Zheng /* Registers and special values for doing register-based SID readout on H3 */ 301a963642SIcenowy Zheng #define SUN8I_SID_PRCTL 0x40 311a963642SIcenowy Zheng #define SUN8I_SID_RDKEY 0x60 321a963642SIcenowy Zheng 331a963642SIcenowy Zheng #define SUN8I_SID_OFFSET_MASK 0x1FF 341a963642SIcenowy Zheng #define SUN8I_SID_OFFSET_SHIFT 16 351a963642SIcenowy Zheng #define SUN8I_SID_OP_LOCK (0xAC << 8) 361a963642SIcenowy Zheng #define SUN8I_SID_READ BIT(1) 371a963642SIcenowy Zheng 383d0b16a6SMaxime Ripard static struct nvmem_config econfig = { 393d0b16a6SMaxime Ripard .name = "sunxi-sid", 403d0b16a6SMaxime Ripard .read_only = true, 419c7b16ebSSrinivas Kandagatla .stride = 4, 429c7b16ebSSrinivas Kandagatla .word_size = 1, 433d0b16a6SMaxime Ripard }; 443d0b16a6SMaxime Ripard 454a72cda5SIcenowy Zheng struct sunxi_sid_cfg { 461a963642SIcenowy Zheng u32 value_offset; 474a72cda5SIcenowy Zheng u32 size; 481a963642SIcenowy Zheng bool need_register_readout; 494a72cda5SIcenowy Zheng }; 504a72cda5SIcenowy Zheng 513d0b16a6SMaxime Ripard struct sunxi_sid { 523d0b16a6SMaxime Ripard void __iomem *base; 531a963642SIcenowy Zheng u32 value_offset; 543d0b16a6SMaxime Ripard }; 553d0b16a6SMaxime Ripard 563d0b16a6SMaxime Ripard /* We read the entire key, due to a 32 bit read alignment requirement. Since we 573d0b16a6SMaxime Ripard * want to return the requested byte, this results in somewhat slower code and 583d0b16a6SMaxime Ripard * uses 4 times more reads as needed but keeps code simpler. Since the SID is 593d0b16a6SMaxime Ripard * only very rarely probed, this is not really an issue. 603d0b16a6SMaxime Ripard */ 613d0b16a6SMaxime Ripard static u8 sunxi_sid_read_byte(const struct sunxi_sid *sid, 623d0b16a6SMaxime Ripard const unsigned int offset) 633d0b16a6SMaxime Ripard { 643d0b16a6SMaxime Ripard u32 sid_key; 653d0b16a6SMaxime Ripard 663d0b16a6SMaxime Ripard sid_key = ioread32be(sid->base + round_down(offset, 4)); 673d0b16a6SMaxime Ripard sid_key >>= (offset % 4) * 8; 683d0b16a6SMaxime Ripard 693d0b16a6SMaxime Ripard return sid_key; /* Only return the last byte */ 703d0b16a6SMaxime Ripard } 713d0b16a6SMaxime Ripard 729c7b16ebSSrinivas Kandagatla static int sunxi_sid_read(void *context, unsigned int offset, 739c7b16ebSSrinivas Kandagatla void *val, size_t bytes) 743d0b16a6SMaxime Ripard { 753d0b16a6SMaxime Ripard struct sunxi_sid *sid = context; 763d0b16a6SMaxime Ripard u8 *buf = val; 773d0b16a6SMaxime Ripard 781a963642SIcenowy Zheng /* Offset the read operation to the real position of SID */ 791a963642SIcenowy Zheng offset += sid->value_offset; 801a963642SIcenowy Zheng 819c7b16ebSSrinivas Kandagatla while (bytes--) 829c7b16ebSSrinivas Kandagatla *buf++ = sunxi_sid_read_byte(sid, offset++); 833d0b16a6SMaxime Ripard 843d0b16a6SMaxime Ripard return 0; 853d0b16a6SMaxime Ripard } 863d0b16a6SMaxime Ripard 871a963642SIcenowy Zheng static int sun8i_sid_register_readout(const struct sunxi_sid *sid, 880ab09d65SIcenowy Zheng const unsigned int offset, 890ab09d65SIcenowy Zheng u32 *out) 901a963642SIcenowy Zheng { 911a963642SIcenowy Zheng u32 reg_val; 921a963642SIcenowy Zheng int ret; 931a963642SIcenowy Zheng 941a963642SIcenowy Zheng /* Set word, lock access, and set read command */ 950ab09d65SIcenowy Zheng reg_val = (offset & SUN8I_SID_OFFSET_MASK) 961a963642SIcenowy Zheng << SUN8I_SID_OFFSET_SHIFT; 971a963642SIcenowy Zheng reg_val |= SUN8I_SID_OP_LOCK | SUN8I_SID_READ; 981a963642SIcenowy Zheng writel(reg_val, sid->base + SUN8I_SID_PRCTL); 991a963642SIcenowy Zheng 1001a963642SIcenowy Zheng ret = readl_poll_timeout(sid->base + SUN8I_SID_PRCTL, reg_val, 1011a963642SIcenowy Zheng !(reg_val & SUN8I_SID_READ), 100, 250000); 1021a963642SIcenowy Zheng if (ret) 1031a963642SIcenowy Zheng return ret; 1041a963642SIcenowy Zheng 1050ab09d65SIcenowy Zheng if (out) 1060ab09d65SIcenowy Zheng *out = readl(sid->base + SUN8I_SID_RDKEY); 1070ab09d65SIcenowy Zheng 1081a963642SIcenowy Zheng writel(0, sid->base + SUN8I_SID_PRCTL); 1090ab09d65SIcenowy Zheng 1100ab09d65SIcenowy Zheng return 0; 1110ab09d65SIcenowy Zheng } 1120ab09d65SIcenowy Zheng 1130ab09d65SIcenowy Zheng /* 1140ab09d65SIcenowy Zheng * On Allwinner H3, the value on the 0x200 offset of the SID controller seems 1150ab09d65SIcenowy Zheng * to be not reliable at all. 1160ab09d65SIcenowy Zheng * Read by the registers instead. 1170ab09d65SIcenowy Zheng */ 1180ab09d65SIcenowy Zheng static int sun8i_sid_read_byte_by_reg(const struct sunxi_sid *sid, 1190ab09d65SIcenowy Zheng const unsigned int offset, 1200ab09d65SIcenowy Zheng u8 *out) 1210ab09d65SIcenowy Zheng { 1220ab09d65SIcenowy Zheng u32 word; 1230ab09d65SIcenowy Zheng int ret; 1240ab09d65SIcenowy Zheng 1250ab09d65SIcenowy Zheng ret = sun8i_sid_register_readout(sid, offset & ~0x03, &word); 1260ab09d65SIcenowy Zheng 1270ab09d65SIcenowy Zheng if (ret) 1280ab09d65SIcenowy Zheng return ret; 1290ab09d65SIcenowy Zheng 1300ab09d65SIcenowy Zheng *out = (word >> ((offset & 0x3) * 8)) & 0xff; 1310ab09d65SIcenowy Zheng 1320ab09d65SIcenowy Zheng return 0; 1330ab09d65SIcenowy Zheng } 1340ab09d65SIcenowy Zheng 1350ab09d65SIcenowy Zheng static int sun8i_sid_read_by_reg(void *context, unsigned int offset, 1360ab09d65SIcenowy Zheng void *val, size_t bytes) 1370ab09d65SIcenowy Zheng { 1380ab09d65SIcenowy Zheng struct sunxi_sid *sid = context; 1390ab09d65SIcenowy Zheng u8 *buf = val; 1400ab09d65SIcenowy Zheng int ret; 1410ab09d65SIcenowy Zheng 1420ab09d65SIcenowy Zheng while (bytes--) { 1430ab09d65SIcenowy Zheng ret = sun8i_sid_read_byte_by_reg(sid, offset++, buf++); 1440ab09d65SIcenowy Zheng if (ret) 1450ab09d65SIcenowy Zheng return ret; 1460ab09d65SIcenowy Zheng } 1470ab09d65SIcenowy Zheng 1481a963642SIcenowy Zheng return 0; 1491a963642SIcenowy Zheng } 1501a963642SIcenowy Zheng 1513d0b16a6SMaxime Ripard static int sunxi_sid_probe(struct platform_device *pdev) 1523d0b16a6SMaxime Ripard { 1533d0b16a6SMaxime Ripard struct device *dev = &pdev->dev; 1543d0b16a6SMaxime Ripard struct resource *res; 1553d0b16a6SMaxime Ripard struct nvmem_device *nvmem; 1563d0b16a6SMaxime Ripard struct sunxi_sid *sid; 157fb727077SMaxime Ripard int ret, i, size; 1583d0b16a6SMaxime Ripard char *randomness; 1594a72cda5SIcenowy Zheng const struct sunxi_sid_cfg *cfg; 1603d0b16a6SMaxime Ripard 1613d0b16a6SMaxime Ripard sid = devm_kzalloc(dev, sizeof(*sid), GFP_KERNEL); 1623d0b16a6SMaxime Ripard if (!sid) 1633d0b16a6SMaxime Ripard return -ENOMEM; 1643d0b16a6SMaxime Ripard 1654a72cda5SIcenowy Zheng cfg = of_device_get_match_data(dev); 1664a72cda5SIcenowy Zheng if (!cfg) 1674a72cda5SIcenowy Zheng return -EINVAL; 1681a963642SIcenowy Zheng sid->value_offset = cfg->value_offset; 1694a72cda5SIcenowy Zheng 1703d0b16a6SMaxime Ripard res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 1713d0b16a6SMaxime Ripard sid->base = devm_ioremap_resource(dev, res); 1723d0b16a6SMaxime Ripard if (IS_ERR(sid->base)) 1733d0b16a6SMaxime Ripard return PTR_ERR(sid->base); 1743d0b16a6SMaxime Ripard 1754a72cda5SIcenowy Zheng size = cfg->size; 1764a72cda5SIcenowy Zheng 1774a72cda5SIcenowy Zheng econfig.size = size; 1783d0b16a6SMaxime Ripard econfig.dev = dev; 1790ab09d65SIcenowy Zheng if (cfg->need_register_readout) 1800ab09d65SIcenowy Zheng econfig.reg_read = sun8i_sid_read_by_reg; 1810ab09d65SIcenowy Zheng else 1829c7b16ebSSrinivas Kandagatla econfig.reg_read = sunxi_sid_read; 1839c7b16ebSSrinivas Kandagatla econfig.priv = sid; 1843d0b16a6SMaxime Ripard nvmem = nvmem_register(&econfig); 1853d0b16a6SMaxime Ripard if (IS_ERR(nvmem)) 1863d0b16a6SMaxime Ripard return PTR_ERR(nvmem); 1873d0b16a6SMaxime Ripard 188*6396bb22SKees Cook randomness = kzalloc(size, GFP_KERNEL); 189fb727077SMaxime Ripard if (!randomness) { 190fb727077SMaxime Ripard ret = -EINVAL; 191fb727077SMaxime Ripard goto err_unreg_nvmem; 192fb727077SMaxime Ripard } 193fb727077SMaxime Ripard 1943d0b16a6SMaxime Ripard for (i = 0; i < size; i++) 1950ab09d65SIcenowy Zheng econfig.reg_read(sid, i, &randomness[i], 1); 1963d0b16a6SMaxime Ripard 1973d0b16a6SMaxime Ripard add_device_randomness(randomness, size); 1983d0b16a6SMaxime Ripard kfree(randomness); 1993d0b16a6SMaxime Ripard 2003d0b16a6SMaxime Ripard platform_set_drvdata(pdev, nvmem); 2013d0b16a6SMaxime Ripard 2023d0b16a6SMaxime Ripard return 0; 203fb727077SMaxime Ripard 204fb727077SMaxime Ripard err_unreg_nvmem: 205fb727077SMaxime Ripard nvmem_unregister(nvmem); 206fb727077SMaxime Ripard return ret; 2073d0b16a6SMaxime Ripard } 2083d0b16a6SMaxime Ripard 2093d0b16a6SMaxime Ripard static int sunxi_sid_remove(struct platform_device *pdev) 2103d0b16a6SMaxime Ripard { 2113d0b16a6SMaxime Ripard struct nvmem_device *nvmem = platform_get_drvdata(pdev); 2123d0b16a6SMaxime Ripard 2133d0b16a6SMaxime Ripard return nvmem_unregister(nvmem); 2143d0b16a6SMaxime Ripard } 2153d0b16a6SMaxime Ripard 2164a72cda5SIcenowy Zheng static const struct sunxi_sid_cfg sun4i_a10_cfg = { 2174a72cda5SIcenowy Zheng .size = 0x10, 2184a72cda5SIcenowy Zheng }; 2194a72cda5SIcenowy Zheng 2204a72cda5SIcenowy Zheng static const struct sunxi_sid_cfg sun7i_a20_cfg = { 2214a72cda5SIcenowy Zheng .size = 0x200, 2224a72cda5SIcenowy Zheng }; 2234a72cda5SIcenowy Zheng 2241a963642SIcenowy Zheng static const struct sunxi_sid_cfg sun8i_h3_cfg = { 2251a963642SIcenowy Zheng .value_offset = 0x200, 2261a963642SIcenowy Zheng .size = 0x100, 2271a963642SIcenowy Zheng .need_register_readout = true, 2281a963642SIcenowy Zheng }; 2291a963642SIcenowy Zheng 230b7fe57b8SIcenowy Zheng static const struct sunxi_sid_cfg sun50i_a64_cfg = { 231b7fe57b8SIcenowy Zheng .value_offset = 0x200, 232b7fe57b8SIcenowy Zheng .size = 0x100, 233b7fe57b8SIcenowy Zheng }; 234b7fe57b8SIcenowy Zheng 2353d0b16a6SMaxime Ripard static const struct of_device_id sunxi_sid_of_match[] = { 2364a72cda5SIcenowy Zheng { .compatible = "allwinner,sun4i-a10-sid", .data = &sun4i_a10_cfg }, 2374a72cda5SIcenowy Zheng { .compatible = "allwinner,sun7i-a20-sid", .data = &sun7i_a20_cfg }, 2381a963642SIcenowy Zheng { .compatible = "allwinner,sun8i-h3-sid", .data = &sun8i_h3_cfg }, 239b7fe57b8SIcenowy Zheng { .compatible = "allwinner,sun50i-a64-sid", .data = &sun50i_a64_cfg }, 2403d0b16a6SMaxime Ripard {/* sentinel */}, 2413d0b16a6SMaxime Ripard }; 2423d0b16a6SMaxime Ripard MODULE_DEVICE_TABLE(of, sunxi_sid_of_match); 2433d0b16a6SMaxime Ripard 2443d0b16a6SMaxime Ripard static struct platform_driver sunxi_sid_driver = { 2453d0b16a6SMaxime Ripard .probe = sunxi_sid_probe, 2463d0b16a6SMaxime Ripard .remove = sunxi_sid_remove, 2473d0b16a6SMaxime Ripard .driver = { 2483d0b16a6SMaxime Ripard .name = "eeprom-sunxi-sid", 2493d0b16a6SMaxime Ripard .of_match_table = sunxi_sid_of_match, 2503d0b16a6SMaxime Ripard }, 2513d0b16a6SMaxime Ripard }; 2523d0b16a6SMaxime Ripard module_platform_driver(sunxi_sid_driver); 2533d0b16a6SMaxime Ripard 2543d0b16a6SMaxime Ripard MODULE_AUTHOR("Oliver Schinagl <oliver@schinagl.nl>"); 2553d0b16a6SMaxime Ripard MODULE_DESCRIPTION("Allwinner sunxi security id driver"); 2563d0b16a6SMaxime Ripard MODULE_LICENSE("GPL"); 257