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 384a72cda5SIcenowy Zheng struct sunxi_sid_cfg { 391a963642SIcenowy Zheng u32 value_offset; 404a72cda5SIcenowy Zheng u32 size; 411a963642SIcenowy Zheng bool need_register_readout; 424a72cda5SIcenowy Zheng }; 434a72cda5SIcenowy Zheng 443d0b16a6SMaxime Ripard struct sunxi_sid { 453d0b16a6SMaxime Ripard void __iomem *base; 461a963642SIcenowy Zheng u32 value_offset; 473d0b16a6SMaxime Ripard }; 483d0b16a6SMaxime Ripard 493d0b16a6SMaxime Ripard /* We read the entire key, due to a 32 bit read alignment requirement. Since we 503d0b16a6SMaxime Ripard * want to return the requested byte, this results in somewhat slower code and 513d0b16a6SMaxime Ripard * uses 4 times more reads as needed but keeps code simpler. Since the SID is 523d0b16a6SMaxime Ripard * only very rarely probed, this is not really an issue. 533d0b16a6SMaxime Ripard */ 543d0b16a6SMaxime Ripard static u8 sunxi_sid_read_byte(const struct sunxi_sid *sid, 553d0b16a6SMaxime Ripard const unsigned int offset) 563d0b16a6SMaxime Ripard { 573d0b16a6SMaxime Ripard u32 sid_key; 583d0b16a6SMaxime Ripard 593d0b16a6SMaxime Ripard sid_key = ioread32be(sid->base + round_down(offset, 4)); 603d0b16a6SMaxime Ripard sid_key >>= (offset % 4) * 8; 613d0b16a6SMaxime Ripard 623d0b16a6SMaxime Ripard return sid_key; /* Only return the last byte */ 633d0b16a6SMaxime Ripard } 643d0b16a6SMaxime Ripard 659c7b16ebSSrinivas Kandagatla static int sunxi_sid_read(void *context, unsigned int offset, 669c7b16ebSSrinivas Kandagatla void *val, size_t bytes) 673d0b16a6SMaxime Ripard { 683d0b16a6SMaxime Ripard struct sunxi_sid *sid = context; 693d0b16a6SMaxime Ripard u8 *buf = val; 703d0b16a6SMaxime Ripard 711a963642SIcenowy Zheng /* Offset the read operation to the real position of SID */ 721a963642SIcenowy Zheng offset += sid->value_offset; 731a963642SIcenowy Zheng 749c7b16ebSSrinivas Kandagatla while (bytes--) 759c7b16ebSSrinivas Kandagatla *buf++ = sunxi_sid_read_byte(sid, offset++); 763d0b16a6SMaxime Ripard 773d0b16a6SMaxime Ripard return 0; 783d0b16a6SMaxime Ripard } 793d0b16a6SMaxime Ripard 801a963642SIcenowy Zheng static int sun8i_sid_register_readout(const struct sunxi_sid *sid, 810ab09d65SIcenowy Zheng const unsigned int offset, 820ab09d65SIcenowy Zheng u32 *out) 831a963642SIcenowy Zheng { 841a963642SIcenowy Zheng u32 reg_val; 851a963642SIcenowy Zheng int ret; 861a963642SIcenowy Zheng 871a963642SIcenowy Zheng /* Set word, lock access, and set read command */ 880ab09d65SIcenowy Zheng reg_val = (offset & SUN8I_SID_OFFSET_MASK) 891a963642SIcenowy Zheng << SUN8I_SID_OFFSET_SHIFT; 901a963642SIcenowy Zheng reg_val |= SUN8I_SID_OP_LOCK | SUN8I_SID_READ; 911a963642SIcenowy Zheng writel(reg_val, sid->base + SUN8I_SID_PRCTL); 921a963642SIcenowy Zheng 931a963642SIcenowy Zheng ret = readl_poll_timeout(sid->base + SUN8I_SID_PRCTL, reg_val, 941a963642SIcenowy Zheng !(reg_val & SUN8I_SID_READ), 100, 250000); 951a963642SIcenowy Zheng if (ret) 961a963642SIcenowy Zheng return ret; 971a963642SIcenowy Zheng 980ab09d65SIcenowy Zheng if (out) 990ab09d65SIcenowy Zheng *out = readl(sid->base + SUN8I_SID_RDKEY); 1000ab09d65SIcenowy Zheng 1011a963642SIcenowy Zheng writel(0, sid->base + SUN8I_SID_PRCTL); 1020ab09d65SIcenowy Zheng 1030ab09d65SIcenowy Zheng return 0; 1040ab09d65SIcenowy Zheng } 1050ab09d65SIcenowy Zheng 1060ab09d65SIcenowy Zheng /* 1070ab09d65SIcenowy Zheng * On Allwinner H3, the value on the 0x200 offset of the SID controller seems 1080ab09d65SIcenowy Zheng * to be not reliable at all. 1090ab09d65SIcenowy Zheng * Read by the registers instead. 1100ab09d65SIcenowy Zheng */ 1110ab09d65SIcenowy Zheng static int sun8i_sid_read_by_reg(void *context, unsigned int offset, 1120ab09d65SIcenowy Zheng void *val, size_t bytes) 1130ab09d65SIcenowy Zheng { 1140ab09d65SIcenowy Zheng struct sunxi_sid *sid = context; 115de2a3eaeSChen-Yu Tsai u32 word; 1160ab09d65SIcenowy Zheng int ret; 1170ab09d65SIcenowy Zheng 118de2a3eaeSChen-Yu Tsai /* .stride = 4 so offset is guaranteed to be aligned */ 119de2a3eaeSChen-Yu Tsai while (bytes >= 4) { 120de2a3eaeSChen-Yu Tsai ret = sun8i_sid_register_readout(sid, offset, val); 1210ab09d65SIcenowy Zheng if (ret) 1220ab09d65SIcenowy Zheng return ret; 123de2a3eaeSChen-Yu Tsai 124de2a3eaeSChen-Yu Tsai val += 4; 125de2a3eaeSChen-Yu Tsai offset += 4; 126de2a3eaeSChen-Yu Tsai bytes -= 4; 1270ab09d65SIcenowy Zheng } 1280ab09d65SIcenowy Zheng 129de2a3eaeSChen-Yu Tsai if (!bytes) 130de2a3eaeSChen-Yu Tsai return 0; 131de2a3eaeSChen-Yu Tsai 132de2a3eaeSChen-Yu Tsai /* Handle any trailing bytes */ 133de2a3eaeSChen-Yu Tsai ret = sun8i_sid_register_readout(sid, offset, &word); 134de2a3eaeSChen-Yu Tsai if (ret) 135de2a3eaeSChen-Yu Tsai return ret; 136de2a3eaeSChen-Yu Tsai 137de2a3eaeSChen-Yu Tsai memcpy(val, &word, bytes); 138de2a3eaeSChen-Yu Tsai 1391a963642SIcenowy Zheng return 0; 1401a963642SIcenowy Zheng } 1411a963642SIcenowy Zheng 1423d0b16a6SMaxime Ripard static int sunxi_sid_probe(struct platform_device *pdev) 1433d0b16a6SMaxime Ripard { 1443d0b16a6SMaxime Ripard struct device *dev = &pdev->dev; 1453d0b16a6SMaxime Ripard struct resource *res; 146*7fa5ad23SChen-Yu Tsai struct nvmem_config *nvmem_cfg; 1473d0b16a6SMaxime Ripard struct nvmem_device *nvmem; 1483d0b16a6SMaxime Ripard struct sunxi_sid *sid; 1499c4adfb5SChen-Yu Tsai int size; 1503d0b16a6SMaxime Ripard char *randomness; 1514a72cda5SIcenowy Zheng const struct sunxi_sid_cfg *cfg; 1523d0b16a6SMaxime Ripard 1533d0b16a6SMaxime Ripard sid = devm_kzalloc(dev, sizeof(*sid), GFP_KERNEL); 1543d0b16a6SMaxime Ripard if (!sid) 1553d0b16a6SMaxime Ripard return -ENOMEM; 1563d0b16a6SMaxime Ripard 1574a72cda5SIcenowy Zheng cfg = of_device_get_match_data(dev); 1584a72cda5SIcenowy Zheng if (!cfg) 1594a72cda5SIcenowy Zheng return -EINVAL; 1601a963642SIcenowy Zheng sid->value_offset = cfg->value_offset; 1614a72cda5SIcenowy Zheng 1623d0b16a6SMaxime Ripard res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 1633d0b16a6SMaxime Ripard sid->base = devm_ioremap_resource(dev, res); 1643d0b16a6SMaxime Ripard if (IS_ERR(sid->base)) 1653d0b16a6SMaxime Ripard return PTR_ERR(sid->base); 1663d0b16a6SMaxime Ripard 1674a72cda5SIcenowy Zheng size = cfg->size; 1684a72cda5SIcenowy Zheng 169*7fa5ad23SChen-Yu Tsai nvmem_cfg = devm_kzalloc(dev, sizeof(*nvmem_cfg), GFP_KERNEL); 170*7fa5ad23SChen-Yu Tsai if (!nvmem_cfg) 171*7fa5ad23SChen-Yu Tsai return -ENOMEM; 172*7fa5ad23SChen-Yu Tsai 173*7fa5ad23SChen-Yu Tsai nvmem_cfg->dev = dev; 174*7fa5ad23SChen-Yu Tsai nvmem_cfg->name = "sunxi-sid"; 175*7fa5ad23SChen-Yu Tsai nvmem_cfg->read_only = true; 176*7fa5ad23SChen-Yu Tsai nvmem_cfg->size = cfg->size; 177*7fa5ad23SChen-Yu Tsai nvmem_cfg->word_size = 1; 178*7fa5ad23SChen-Yu Tsai nvmem_cfg->stride = 4; 179*7fa5ad23SChen-Yu Tsai nvmem_cfg->priv = sid; 1800ab09d65SIcenowy Zheng if (cfg->need_register_readout) 181*7fa5ad23SChen-Yu Tsai nvmem_cfg->reg_read = sun8i_sid_read_by_reg; 1820ab09d65SIcenowy Zheng else 183*7fa5ad23SChen-Yu Tsai nvmem_cfg->reg_read = sunxi_sid_read; 184*7fa5ad23SChen-Yu Tsai 185*7fa5ad23SChen-Yu Tsai nvmem = devm_nvmem_register(dev, nvmem_cfg); 1863d0b16a6SMaxime Ripard if (IS_ERR(nvmem)) 1873d0b16a6SMaxime Ripard return PTR_ERR(nvmem); 1883d0b16a6SMaxime Ripard 1896396bb22SKees Cook randomness = kzalloc(size, GFP_KERNEL); 1906eed8dd9SBartosz Golaszewski if (!randomness) 1916eed8dd9SBartosz Golaszewski return -ENOMEM; 192fb727077SMaxime Ripard 193*7fa5ad23SChen-Yu Tsai nvmem_cfg->reg_read(sid, 0, randomness, size); 1943d0b16a6SMaxime Ripard add_device_randomness(randomness, size); 1953d0b16a6SMaxime Ripard kfree(randomness); 1963d0b16a6SMaxime Ripard 1973d0b16a6SMaxime Ripard platform_set_drvdata(pdev, nvmem); 1983d0b16a6SMaxime Ripard 1993d0b16a6SMaxime Ripard return 0; 2003d0b16a6SMaxime Ripard } 2013d0b16a6SMaxime Ripard 2024a72cda5SIcenowy Zheng static const struct sunxi_sid_cfg sun4i_a10_cfg = { 2034a72cda5SIcenowy Zheng .size = 0x10, 2044a72cda5SIcenowy Zheng }; 2054a72cda5SIcenowy Zheng 2064a72cda5SIcenowy Zheng static const struct sunxi_sid_cfg sun7i_a20_cfg = { 2074a72cda5SIcenowy Zheng .size = 0x200, 2084a72cda5SIcenowy Zheng }; 2094a72cda5SIcenowy Zheng 2101a963642SIcenowy Zheng static const struct sunxi_sid_cfg sun8i_h3_cfg = { 2111a963642SIcenowy Zheng .value_offset = 0x200, 2121a963642SIcenowy Zheng .size = 0x100, 2131a963642SIcenowy Zheng .need_register_readout = true, 2141a963642SIcenowy Zheng }; 2151a963642SIcenowy Zheng 216b7fe57b8SIcenowy Zheng static const struct sunxi_sid_cfg sun50i_a64_cfg = { 217b7fe57b8SIcenowy Zheng .value_offset = 0x200, 218b7fe57b8SIcenowy Zheng .size = 0x100, 219b7fe57b8SIcenowy Zheng }; 220b7fe57b8SIcenowy Zheng 2213d0b16a6SMaxime Ripard static const struct of_device_id sunxi_sid_of_match[] = { 2224a72cda5SIcenowy Zheng { .compatible = "allwinner,sun4i-a10-sid", .data = &sun4i_a10_cfg }, 2234a72cda5SIcenowy Zheng { .compatible = "allwinner,sun7i-a20-sid", .data = &sun7i_a20_cfg }, 2241a963642SIcenowy Zheng { .compatible = "allwinner,sun8i-h3-sid", .data = &sun8i_h3_cfg }, 225b7fe57b8SIcenowy Zheng { .compatible = "allwinner,sun50i-a64-sid", .data = &sun50i_a64_cfg }, 2263d0b16a6SMaxime Ripard {/* sentinel */}, 2273d0b16a6SMaxime Ripard }; 2283d0b16a6SMaxime Ripard MODULE_DEVICE_TABLE(of, sunxi_sid_of_match); 2293d0b16a6SMaxime Ripard 2303d0b16a6SMaxime Ripard static struct platform_driver sunxi_sid_driver = { 2313d0b16a6SMaxime Ripard .probe = sunxi_sid_probe, 2323d0b16a6SMaxime Ripard .driver = { 2333d0b16a6SMaxime Ripard .name = "eeprom-sunxi-sid", 2343d0b16a6SMaxime Ripard .of_match_table = sunxi_sid_of_match, 2353d0b16a6SMaxime Ripard }, 2363d0b16a6SMaxime Ripard }; 2373d0b16a6SMaxime Ripard module_platform_driver(sunxi_sid_driver); 2383d0b16a6SMaxime Ripard 2393d0b16a6SMaxime Ripard MODULE_AUTHOR("Oliver Schinagl <oliver@schinagl.nl>"); 2403d0b16a6SMaxime Ripard MODULE_DESCRIPTION("Allwinner sunxi security id driver"); 2413d0b16a6SMaxime Ripard MODULE_LICENSE("GPL"); 242