Warning: That file was not part of the compilation database. It may have many parsing errors.
1 | /* |
---|---|
2 | * Allwinner sunXi SoCs Security ID support. |
3 | * |
4 | * Copyright (c) 2013 Oliver Schinagl <oliver@schinagl.nl> |
5 | * Copyright (C) 2014 Maxime Ripard <maxime.ripard@free-electrons.com> |
6 | * |
7 | * This program is free software; you can redistribute it and/or modify |
8 | * it under the terms of the GNU General Public License as published by |
9 | * the Free Software Foundation; either version 2 of the License, or |
10 | * (at your option) any later version. |
11 | * |
12 | * This program is distributed in the hope that it will be useful, |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | * GNU General Public License for more details. |
16 | */ |
17 | |
18 | #include <linux/device.h> |
19 | #include <linux/io.h> |
20 | #include <linux/iopoll.h> |
21 | #include <linux/module.h> |
22 | #include <linux/nvmem-provider.h> |
23 | #include <linux/of.h> |
24 | #include <linux/of_device.h> |
25 | #include <linux/platform_device.h> |
26 | #include <linux/slab.h> |
27 | #include <linux/random.h> |
28 | |
29 | /* Registers and special values for doing register-based SID readout on H3 */ |
30 | #define SUN8I_SID_PRCTL 0x40 |
31 | #define SUN8I_SID_RDKEY 0x60 |
32 | |
33 | #define SUN8I_SID_OFFSET_MASK 0x1FF |
34 | #define SUN8I_SID_OFFSET_SHIFT 16 |
35 | #define SUN8I_SID_OP_LOCK (0xAC << 8) |
36 | #define SUN8I_SID_READ BIT(1) |
37 | |
38 | static struct nvmem_config econfig = { |
39 | .name = "sunxi-sid", |
40 | .read_only = true, |
41 | .stride = 4, |
42 | .word_size = 1, |
43 | }; |
44 | |
45 | struct sunxi_sid_cfg { |
46 | u32 value_offset; |
47 | u32 size; |
48 | bool need_register_readout; |
49 | }; |
50 | |
51 | struct sunxi_sid { |
52 | void __iomem *base; |
53 | u32 value_offset; |
54 | }; |
55 | |
56 | /* We read the entire key, due to a 32 bit read alignment requirement. Since we |
57 | * want to return the requested byte, this results in somewhat slower code and |
58 | * uses 4 times more reads as needed but keeps code simpler. Since the SID is |
59 | * only very rarely probed, this is not really an issue. |
60 | */ |
61 | static u8 sunxi_sid_read_byte(const struct sunxi_sid *sid, |
62 | const unsigned int offset) |
63 | { |
64 | u32 sid_key; |
65 | |
66 | sid_key = ioread32be(sid->base + round_down(offset, 4)); |
67 | sid_key >>= (offset % 4) * 8; |
68 | |
69 | return sid_key; /* Only return the last byte */ |
70 | } |
71 | |
72 | static int sunxi_sid_read(void *context, unsigned int offset, |
73 | void *val, size_t bytes) |
74 | { |
75 | struct sunxi_sid *sid = context; |
76 | u8 *buf = val; |
77 | |
78 | /* Offset the read operation to the real position of SID */ |
79 | offset += sid->value_offset; |
80 | |
81 | while (bytes--) |
82 | *buf++ = sunxi_sid_read_byte(sid, offset++); |
83 | |
84 | return 0; |
85 | } |
86 | |
87 | static int sun8i_sid_register_readout(const struct sunxi_sid *sid, |
88 | const unsigned int offset, |
89 | u32 *out) |
90 | { |
91 | u32 reg_val; |
92 | int ret; |
93 | |
94 | /* Set word, lock access, and set read command */ |
95 | reg_val = (offset & SUN8I_SID_OFFSET_MASK) |
96 | << SUN8I_SID_OFFSET_SHIFT; |
97 | reg_val |= SUN8I_SID_OP_LOCK | SUN8I_SID_READ; |
98 | writel(reg_val, sid->base + SUN8I_SID_PRCTL); |
99 | |
100 | ret = readl_poll_timeout(sid->base + SUN8I_SID_PRCTL, reg_val, |
101 | !(reg_val & SUN8I_SID_READ), 100, 250000); |
102 | if (ret) |
103 | return ret; |
104 | |
105 | if (out) |
106 | *out = readl(sid->base + SUN8I_SID_RDKEY); |
107 | |
108 | writel(0, sid->base + SUN8I_SID_PRCTL); |
109 | |
110 | return 0; |
111 | } |
112 | |
113 | /* |
114 | * On Allwinner H3, the value on the 0x200 offset of the SID controller seems |
115 | * to be not reliable at all. |
116 | * Read by the registers instead. |
117 | */ |
118 | static int sun8i_sid_read_byte_by_reg(const struct sunxi_sid *sid, |
119 | const unsigned int offset, |
120 | u8 *out) |
121 | { |
122 | u32 word; |
123 | int ret; |
124 | |
125 | ret = sun8i_sid_register_readout(sid, offset & ~0x03, &word); |
126 | |
127 | if (ret) |
128 | return ret; |
129 | |
130 | *out = (word >> ((offset & 0x3) * 8)) & 0xff; |
131 | |
132 | return 0; |
133 | } |
134 | |
135 | static int sun8i_sid_read_by_reg(void *context, unsigned int offset, |
136 | void *val, size_t bytes) |
137 | { |
138 | struct sunxi_sid *sid = context; |
139 | u8 *buf = val; |
140 | int ret; |
141 | |
142 | while (bytes--) { |
143 | ret = sun8i_sid_read_byte_by_reg(sid, offset++, buf++); |
144 | if (ret) |
145 | return ret; |
146 | } |
147 | |
148 | return 0; |
149 | } |
150 | |
151 | static int sunxi_sid_probe(struct platform_device *pdev) |
152 | { |
153 | struct device *dev = &pdev->dev; |
154 | struct resource *res; |
155 | struct nvmem_device *nvmem; |
156 | struct sunxi_sid *sid; |
157 | int i, size; |
158 | char *randomness; |
159 | const struct sunxi_sid_cfg *cfg; |
160 | |
161 | sid = devm_kzalloc(dev, sizeof(*sid), GFP_KERNEL); |
162 | if (!sid) |
163 | return -ENOMEM; |
164 | |
165 | cfg = of_device_get_match_data(dev); |
166 | if (!cfg) |
167 | return -EINVAL; |
168 | sid->value_offset = cfg->value_offset; |
169 | |
170 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
171 | sid->base = devm_ioremap_resource(dev, res); |
172 | if (IS_ERR(sid->base)) |
173 | return PTR_ERR(sid->base); |
174 | |
175 | size = cfg->size; |
176 | |
177 | econfig.size = size; |
178 | econfig.dev = dev; |
179 | if (cfg->need_register_readout) |
180 | econfig.reg_read = sun8i_sid_read_by_reg; |
181 | else |
182 | econfig.reg_read = sunxi_sid_read; |
183 | econfig.priv = sid; |
184 | nvmem = devm_nvmem_register(dev, &econfig); |
185 | if (IS_ERR(nvmem)) |
186 | return PTR_ERR(nvmem); |
187 | |
188 | randomness = kzalloc(size, GFP_KERNEL); |
189 | if (!randomness) |
190 | return -ENOMEM; |
191 | |
192 | for (i = 0; i < size; i++) |
193 | econfig.reg_read(sid, i, &randomness[i], 1); |
194 | |
195 | add_device_randomness(randomness, size); |
196 | kfree(randomness); |
197 | |
198 | platform_set_drvdata(pdev, nvmem); |
199 | |
200 | return 0; |
201 | } |
202 | |
203 | static const struct sunxi_sid_cfg sun4i_a10_cfg = { |
204 | .size = 0x10, |
205 | }; |
206 | |
207 | static const struct sunxi_sid_cfg sun7i_a20_cfg = { |
208 | .size = 0x200, |
209 | }; |
210 | |
211 | static const struct sunxi_sid_cfg sun8i_h3_cfg = { |
212 | .value_offset = 0x200, |
213 | .size = 0x100, |
214 | .need_register_readout = true, |
215 | }; |
216 | |
217 | static const struct sunxi_sid_cfg sun50i_a64_cfg = { |
218 | .value_offset = 0x200, |
219 | .size = 0x100, |
220 | }; |
221 | |
222 | static const struct of_device_id sunxi_sid_of_match[] = { |
223 | { .compatible = "allwinner,sun4i-a10-sid", .data = &sun4i_a10_cfg }, |
224 | { .compatible = "allwinner,sun7i-a20-sid", .data = &sun7i_a20_cfg }, |
225 | { .compatible = "allwinner,sun8i-h3-sid", .data = &sun8i_h3_cfg }, |
226 | { .compatible = "allwinner,sun50i-a64-sid", .data = &sun50i_a64_cfg }, |
227 | {/* sentinel */}, |
228 | }; |
229 | MODULE_DEVICE_TABLE(of, sunxi_sid_of_match); |
230 | |
231 | static struct platform_driver sunxi_sid_driver = { |
232 | .probe = sunxi_sid_probe, |
233 | .driver = { |
234 | .name = "eeprom-sunxi-sid", |
235 | .of_match_table = sunxi_sid_of_match, |
236 | }, |
237 | }; |
238 | module_platform_driver(sunxi_sid_driver); |
239 | |
240 | MODULE_AUTHOR("Oliver Schinagl <oliver@schinagl.nl>"); |
241 | MODULE_DESCRIPTION("Allwinner sunxi security id driver"); |
242 | MODULE_LICENSE("GPL"); |
243 |
Warning: That file was not part of the compilation database. It may have many parsing errors.