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_by_reg(void *context, unsigned int offset, 1190ab09d65SIcenowy Zheng void *val, size_t bytes) 1200ab09d65SIcenowy Zheng { 1210ab09d65SIcenowy Zheng struct sunxi_sid *sid = context; 122*de2a3eaeSChen-Yu Tsai u32 word; 1230ab09d65SIcenowy Zheng int ret; 1240ab09d65SIcenowy Zheng 125*de2a3eaeSChen-Yu Tsai /* .stride = 4 so offset is guaranteed to be aligned */ 126*de2a3eaeSChen-Yu Tsai while (bytes >= 4) { 127*de2a3eaeSChen-Yu Tsai ret = sun8i_sid_register_readout(sid, offset, val); 1280ab09d65SIcenowy Zheng if (ret) 1290ab09d65SIcenowy Zheng return ret; 130*de2a3eaeSChen-Yu Tsai 131*de2a3eaeSChen-Yu Tsai val += 4; 132*de2a3eaeSChen-Yu Tsai offset += 4; 133*de2a3eaeSChen-Yu Tsai bytes -= 4; 1340ab09d65SIcenowy Zheng } 1350ab09d65SIcenowy Zheng 136*de2a3eaeSChen-Yu Tsai if (!bytes) 137*de2a3eaeSChen-Yu Tsai return 0; 138*de2a3eaeSChen-Yu Tsai 139*de2a3eaeSChen-Yu Tsai /* Handle any trailing bytes */ 140*de2a3eaeSChen-Yu Tsai ret = sun8i_sid_register_readout(sid, offset, &word); 141*de2a3eaeSChen-Yu Tsai if (ret) 142*de2a3eaeSChen-Yu Tsai return ret; 143*de2a3eaeSChen-Yu Tsai 144*de2a3eaeSChen-Yu Tsai memcpy(val, &word, bytes); 145*de2a3eaeSChen-Yu Tsai 1461a963642SIcenowy Zheng return 0; 1471a963642SIcenowy Zheng } 1481a963642SIcenowy Zheng 1493d0b16a6SMaxime Ripard static int sunxi_sid_probe(struct platform_device *pdev) 1503d0b16a6SMaxime Ripard { 1513d0b16a6SMaxime Ripard struct device *dev = &pdev->dev; 1523d0b16a6SMaxime Ripard struct resource *res; 1533d0b16a6SMaxime Ripard struct nvmem_device *nvmem; 1543d0b16a6SMaxime Ripard struct sunxi_sid *sid; 1559c4adfb5SChen-Yu Tsai int size; 1563d0b16a6SMaxime Ripard char *randomness; 1574a72cda5SIcenowy Zheng const struct sunxi_sid_cfg *cfg; 1583d0b16a6SMaxime Ripard 1593d0b16a6SMaxime Ripard sid = devm_kzalloc(dev, sizeof(*sid), GFP_KERNEL); 1603d0b16a6SMaxime Ripard if (!sid) 1613d0b16a6SMaxime Ripard return -ENOMEM; 1623d0b16a6SMaxime Ripard 1634a72cda5SIcenowy Zheng cfg = of_device_get_match_data(dev); 1644a72cda5SIcenowy Zheng if (!cfg) 1654a72cda5SIcenowy Zheng return -EINVAL; 1661a963642SIcenowy Zheng sid->value_offset = cfg->value_offset; 1674a72cda5SIcenowy Zheng 1683d0b16a6SMaxime Ripard res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 1693d0b16a6SMaxime Ripard sid->base = devm_ioremap_resource(dev, res); 1703d0b16a6SMaxime Ripard if (IS_ERR(sid->base)) 1713d0b16a6SMaxime Ripard return PTR_ERR(sid->base); 1723d0b16a6SMaxime Ripard 1734a72cda5SIcenowy Zheng size = cfg->size; 1744a72cda5SIcenowy Zheng 1754a72cda5SIcenowy Zheng econfig.size = size; 1763d0b16a6SMaxime Ripard econfig.dev = dev; 1770ab09d65SIcenowy Zheng if (cfg->need_register_readout) 1780ab09d65SIcenowy Zheng econfig.reg_read = sun8i_sid_read_by_reg; 1790ab09d65SIcenowy Zheng else 1809c7b16ebSSrinivas Kandagatla econfig.reg_read = sunxi_sid_read; 1819c7b16ebSSrinivas Kandagatla econfig.priv = sid; 1826eed8dd9SBartosz Golaszewski nvmem = devm_nvmem_register(dev, &econfig); 1833d0b16a6SMaxime Ripard if (IS_ERR(nvmem)) 1843d0b16a6SMaxime Ripard return PTR_ERR(nvmem); 1853d0b16a6SMaxime Ripard 1866396bb22SKees Cook randomness = kzalloc(size, GFP_KERNEL); 1876eed8dd9SBartosz Golaszewski if (!randomness) 1886eed8dd9SBartosz Golaszewski return -ENOMEM; 189fb727077SMaxime Ripard 1909c4adfb5SChen-Yu Tsai econfig.reg_read(sid, 0, randomness, size); 1913d0b16a6SMaxime Ripard 1923d0b16a6SMaxime Ripard add_device_randomness(randomness, size); 1933d0b16a6SMaxime Ripard kfree(randomness); 1943d0b16a6SMaxime Ripard 1953d0b16a6SMaxime Ripard platform_set_drvdata(pdev, nvmem); 1963d0b16a6SMaxime Ripard 1973d0b16a6SMaxime Ripard return 0; 1983d0b16a6SMaxime Ripard } 1993d0b16a6SMaxime Ripard 2004a72cda5SIcenowy Zheng static const struct sunxi_sid_cfg sun4i_a10_cfg = { 2014a72cda5SIcenowy Zheng .size = 0x10, 2024a72cda5SIcenowy Zheng }; 2034a72cda5SIcenowy Zheng 2044a72cda5SIcenowy Zheng static const struct sunxi_sid_cfg sun7i_a20_cfg = { 2054a72cda5SIcenowy Zheng .size = 0x200, 2064a72cda5SIcenowy Zheng }; 2074a72cda5SIcenowy Zheng 2081a963642SIcenowy Zheng static const struct sunxi_sid_cfg sun8i_h3_cfg = { 2091a963642SIcenowy Zheng .value_offset = 0x200, 2101a963642SIcenowy Zheng .size = 0x100, 2111a963642SIcenowy Zheng .need_register_readout = true, 2121a963642SIcenowy Zheng }; 2131a963642SIcenowy Zheng 214b7fe57b8SIcenowy Zheng static const struct sunxi_sid_cfg sun50i_a64_cfg = { 215b7fe57b8SIcenowy Zheng .value_offset = 0x200, 216b7fe57b8SIcenowy Zheng .size = 0x100, 217b7fe57b8SIcenowy Zheng }; 218b7fe57b8SIcenowy Zheng 2193d0b16a6SMaxime Ripard static const struct of_device_id sunxi_sid_of_match[] = { 2204a72cda5SIcenowy Zheng { .compatible = "allwinner,sun4i-a10-sid", .data = &sun4i_a10_cfg }, 2214a72cda5SIcenowy Zheng { .compatible = "allwinner,sun7i-a20-sid", .data = &sun7i_a20_cfg }, 2221a963642SIcenowy Zheng { .compatible = "allwinner,sun8i-h3-sid", .data = &sun8i_h3_cfg }, 223b7fe57b8SIcenowy Zheng { .compatible = "allwinner,sun50i-a64-sid", .data = &sun50i_a64_cfg }, 2243d0b16a6SMaxime Ripard {/* sentinel */}, 2253d0b16a6SMaxime Ripard }; 2263d0b16a6SMaxime Ripard MODULE_DEVICE_TABLE(of, sunxi_sid_of_match); 2273d0b16a6SMaxime Ripard 2283d0b16a6SMaxime Ripard static struct platform_driver sunxi_sid_driver = { 2293d0b16a6SMaxime Ripard .probe = sunxi_sid_probe, 2303d0b16a6SMaxime Ripard .driver = { 2313d0b16a6SMaxime Ripard .name = "eeprom-sunxi-sid", 2323d0b16a6SMaxime Ripard .of_match_table = sunxi_sid_of_match, 2333d0b16a6SMaxime Ripard }, 2343d0b16a6SMaxime Ripard }; 2353d0b16a6SMaxime Ripard module_platform_driver(sunxi_sid_driver); 2363d0b16a6SMaxime Ripard 2373d0b16a6SMaxime Ripard MODULE_AUTHOR("Oliver Schinagl <oliver@schinagl.nl>"); 2383d0b16a6SMaxime Ripard MODULE_DESCRIPTION("Allwinner sunxi security id driver"); 2393d0b16a6SMaxime Ripard MODULE_LICENSE("GPL"); 240