Commit | Line | Data |
---|---|---|
27084efe LD |
1 | /* |
2 | * Copyright (C) 2005, 2006 IBM Corporation | |
399235dc | 3 | * Copyright (C) 2014, 2015 Intel Corporation |
27084efe LD |
4 | * |
5 | * Authors: | |
6 | * Leendert van Doorn <leendert@watson.ibm.com> | |
7 | * Kylene Hall <kjhall@us.ibm.com> | |
8 | * | |
8e81cc13 KY |
9 | * Maintained by: <tpmdd-devel@lists.sourceforge.net> |
10 | * | |
27084efe LD |
11 | * Device driver for TCG/TCPA TPM (trusted platform module). |
12 | * Specifications at www.trustedcomputinggroup.org | |
13 | * | |
14 | * This device driver implements the TPM interface as defined in | |
15 | * the TCG TPM Interface Spec version 1.2, revision 1.0. | |
16 | * | |
17 | * This program is free software; you can redistribute it and/or | |
18 | * modify it under the terms of the GNU General Public License as | |
19 | * published by the Free Software Foundation, version 2 of the | |
20 | * License. | |
21 | */ | |
57135568 KJH |
22 | #include <linux/init.h> |
23 | #include <linux/module.h> | |
24 | #include <linux/moduleparam.h> | |
27084efe | 25 | #include <linux/pnp.h> |
5a0e3ad6 | 26 | #include <linux/slab.h> |
27084efe LD |
27 | #include <linux/interrupt.h> |
28 | #include <linux/wait.h> | |
3f0d3d01 | 29 | #include <linux/acpi.h> |
20b87bbf | 30 | #include <linux/freezer.h> |
27084efe | 31 | #include "tpm.h" |
57dacc2b | 32 | #include "tpm_tis_core.h" |
27084efe | 33 | |
399235dc | 34 | struct tpm_info { |
51dd43df | 35 | struct resource res; |
ef7b81dc JG |
36 | /* irq > 0 means: use irq $irq; |
37 | * irq = 0 means: autoprobe for an irq; | |
38 | * irq = -1 means: no irq support | |
39 | */ | |
40 | int irq; | |
399235dc JS |
41 | }; |
42 | ||
57dacc2b CR |
43 | struct tpm_tis_tcg_phy { |
44 | struct tpm_tis_data priv; | |
4eea703c | 45 | void __iomem *iobase; |
448e9c55 SD |
46 | }; |
47 | ||
57dacc2b CR |
48 | static inline struct tpm_tis_tcg_phy *to_tpm_tis_tcg_phy(struct tpm_tis_data *data) |
49 | { | |
50 | return container_of(data, struct tpm_tis_tcg_phy, priv); | |
51 | } | |
52 | ||
41a5e1cf CR |
53 | static bool interrupts = true; |
54 | module_param(interrupts, bool, 0444); | |
55 | MODULE_PARM_DESC(interrupts, "Enable interrupts"); | |
56 | ||
57 | static bool itpm; | |
58 | module_param(itpm, bool, 0444); | |
59 | MODULE_PARM_DESC(itpm, "Force iTPM workarounds (found on some Lenovo laptops)"); | |
60 | ||
61 | static bool force; | |
62 | #ifdef CONFIG_X86 | |
63 | module_param(force, bool, 0444); | |
64 | MODULE_PARM_DESC(force, "Force device probe rather than using ACPI entry"); | |
65 | #endif | |
66 | ||
1560ffe6 | 67 | #if defined(CONFIG_PNP) && defined(CONFIG_ACPI) |
399235dc | 68 | static int has_hid(struct acpi_device *dev, const char *hid) |
3f0d3d01 | 69 | { |
3f0d3d01 MG |
70 | struct acpi_hardware_id *id; |
71 | ||
399235dc JS |
72 | list_for_each_entry(id, &dev->pnp.ids, list) |
73 | if (!strcmp(hid, id->id)) | |
3f0d3d01 | 74 | return 1; |
3f0d3d01 MG |
75 | |
76 | return 0; | |
77 | } | |
399235dc JS |
78 | |
79 | static inline int is_itpm(struct acpi_device *dev) | |
80 | { | |
81 | return has_hid(dev, "INTC0102"); | |
82 | } | |
1560ffe6 | 83 | #else |
399235dc | 84 | static inline int is_itpm(struct acpi_device *dev) |
1560ffe6 RD |
85 | { |
86 | return 0; | |
87 | } | |
3f0d3d01 MG |
88 | #endif |
89 | ||
1107d065 CR |
90 | static int tpm_tcg_read_bytes(struct tpm_tis_data *data, u32 addr, u16 len, |
91 | u8 *result) | |
92 | { | |
93 | struct tpm_tis_tcg_phy *phy = to_tpm_tis_tcg_phy(data); | |
94 | ||
95 | while (len--) | |
96 | *result++ = ioread8(phy->iobase + addr); | |
97 | return 0; | |
98 | } | |
99 | ||
100 | static int tpm_tcg_write_bytes(struct tpm_tis_data *data, u32 addr, u16 len, | |
101 | u8 *value) | |
102 | { | |
103 | struct tpm_tis_tcg_phy *phy = to_tpm_tis_tcg_phy(data); | |
104 | ||
105 | while (len--) | |
106 | iowrite8(*value++, phy->iobase + addr); | |
107 | return 0; | |
108 | } | |
109 | ||
110 | static int tpm_tcg_read16(struct tpm_tis_data *data, u32 addr, u16 *result) | |
111 | { | |
112 | struct tpm_tis_tcg_phy *phy = to_tpm_tis_tcg_phy(data); | |
113 | ||
114 | *result = ioread16(phy->iobase + addr); | |
115 | return 0; | |
116 | } | |
117 | ||
118 | static int tpm_tcg_read32(struct tpm_tis_data *data, u32 addr, u32 *result) | |
119 | { | |
120 | struct tpm_tis_tcg_phy *phy = to_tpm_tis_tcg_phy(data); | |
121 | ||
122 | *result = ioread32(phy->iobase + addr); | |
123 | return 0; | |
124 | } | |
125 | ||
126 | static int tpm_tcg_write32(struct tpm_tis_data *data, u32 addr, u32 value) | |
127 | { | |
128 | struct tpm_tis_tcg_phy *phy = to_tpm_tis_tcg_phy(data); | |
129 | ||
130 | iowrite32(value, phy->iobase + addr); | |
131 | return 0; | |
132 | } | |
133 | ||
134 | static const struct tpm_tis_phy_ops tpm_tcg = { | |
135 | .read_bytes = tpm_tcg_read_bytes, | |
136 | .write_bytes = tpm_tcg_write_bytes, | |
137 | .read16 = tpm_tcg_read16, | |
138 | .read32 = tpm_tcg_read32, | |
139 | .write32 = tpm_tcg_write32, | |
140 | }; | |
141 | ||
399235dc JS |
142 | static int tpm_tis_init(struct device *dev, struct tpm_info *tpm_info, |
143 | acpi_handle acpi_dev_handle) | |
27084efe | 144 | { |
57dacc2b | 145 | struct tpm_tis_tcg_phy *phy; |
41a5e1cf | 146 | int irq = -1; |
27084efe | 147 | |
57dacc2b CR |
148 | phy = devm_kzalloc(dev, sizeof(struct tpm_tis_tcg_phy), GFP_KERNEL); |
149 | if (phy == NULL) | |
448e9c55 | 150 | return -ENOMEM; |
afb5abc2 | 151 | |
57dacc2b CR |
152 | phy->iobase = devm_ioremap_resource(dev, &tpm_info->res); |
153 | if (IS_ERR(phy->iobase)) | |
154 | return PTR_ERR(phy->iobase); | |
27084efe | 155 | |
41a5e1cf CR |
156 | if (interrupts) |
157 | irq = tpm_info->irq; | |
9519de3f | 158 | |
3507d612 | 159 | if (itpm) |
41a5e1cf | 160 | phy->priv.flags |= TPM_TIS_ITPM_POSSIBLE; |
25112048 | 161 | |
41a5e1cf CR |
162 | return tpm_tis_core_init(dev, &phy->priv, irq, &tpm_tcg, |
163 | acpi_dev_handle); | |
27084efe | 164 | } |
96854310 | 165 | |
a2fa3fb0 SK |
166 | static SIMPLE_DEV_PM_OPS(tpm_tis_pm, tpm_pm_suspend, tpm_tis_resume); |
167 | ||
afc6d369 | 168 | static int tpm_tis_pnp_init(struct pnp_dev *pnp_dev, |
ef7b81dc | 169 | const struct pnp_device_id *pnp_id) |
9e323d3e | 170 | { |
ef7b81dc | 171 | struct tpm_info tpm_info = {}; |
0dc55365 | 172 | acpi_handle acpi_dev_handle = NULL; |
51dd43df | 173 | struct resource *res; |
7917ff9a | 174 | |
51dd43df JG |
175 | res = pnp_get_resource(pnp_dev, IORESOURCE_MEM, 0); |
176 | if (!res) | |
177 | return -ENODEV; | |
178 | tpm_info.res = *res; | |
9e323d3e | 179 | |
7917ff9a | 180 | if (pnp_irq_valid(pnp_dev, 0)) |
399235dc | 181 | tpm_info.irq = pnp_irq(pnp_dev, 0); |
7917ff9a | 182 | else |
ef7b81dc | 183 | tpm_info.irq = -1; |
7917ff9a | 184 | |
399235dc JS |
185 | if (pnp_acpi_device(pnp_dev)) { |
186 | if (is_itpm(pnp_acpi_device(pnp_dev))) | |
187 | itpm = true; | |
188 | ||
00194826 | 189 | acpi_dev_handle = ACPI_HANDLE(&pnp_dev->dev); |
399235dc | 190 | } |
0dc55365 | 191 | |
399235dc | 192 | return tpm_tis_init(&pnp_dev->dev, &tpm_info, acpi_dev_handle); |
9e323d3e KJH |
193 | } |
194 | ||
0bbed20e | 195 | static struct pnp_device_id tpm_pnp_tbl[] = { |
27084efe | 196 | {"PNP0C31", 0}, /* TPM */ |
93e1b7d4 KJH |
197 | {"ATM1200", 0}, /* Atmel */ |
198 | {"IFX0102", 0}, /* Infineon */ | |
199 | {"BCM0101", 0}, /* Broadcom */ | |
061991ec | 200 | {"BCM0102", 0}, /* Broadcom */ |
93e1b7d4 | 201 | {"NSC1200", 0}, /* National */ |
fb0e7e11 | 202 | {"ICO0102", 0}, /* Intel */ |
93e1b7d4 KJH |
203 | /* Add new here */ |
204 | {"", 0}, /* User Specified */ | |
205 | {"", 0} /* Terminator */ | |
27084efe | 206 | }; |
31bde71c | 207 | MODULE_DEVICE_TABLE(pnp, tpm_pnp_tbl); |
27084efe | 208 | |
39af33fc | 209 | static void tpm_tis_pnp_remove(struct pnp_dev *dev) |
253115b7 RA |
210 | { |
211 | struct tpm_chip *chip = pnp_get_drvdata(dev); | |
399235dc | 212 | |
afb5abc2 JS |
213 | tpm_chip_unregister(chip); |
214 | tpm_tis_remove(chip); | |
253115b7 RA |
215 | } |
216 | ||
27084efe LD |
217 | static struct pnp_driver tis_pnp_driver = { |
218 | .name = "tpm_tis", | |
219 | .id_table = tpm_pnp_tbl, | |
220 | .probe = tpm_tis_pnp_init, | |
253115b7 | 221 | .remove = tpm_tis_pnp_remove, |
a2fa3fb0 SK |
222 | .driver = { |
223 | .pm = &tpm_tis_pm, | |
224 | }, | |
27084efe LD |
225 | }; |
226 | ||
93e1b7d4 KJH |
227 | #define TIS_HID_USR_IDX sizeof(tpm_pnp_tbl)/sizeof(struct pnp_device_id) -2 |
228 | module_param_string(hid, tpm_pnp_tbl[TIS_HID_USR_IDX].id, | |
229 | sizeof(tpm_pnp_tbl[TIS_HID_USR_IDX].id), 0444); | |
230 | MODULE_PARM_DESC(hid, "Set additional specific HID for this driver to probe"); | |
7a192ec3 | 231 | |
399235dc JS |
232 | #ifdef CONFIG_ACPI |
233 | static int tpm_check_resource(struct acpi_resource *ares, void *data) | |
234 | { | |
235 | struct tpm_info *tpm_info = (struct tpm_info *) data; | |
236 | struct resource res; | |
237 | ||
51dd43df | 238 | if (acpi_dev_resource_interrupt(ares, 0, &res)) |
399235dc | 239 | tpm_info->irq = res.start; |
30f9c8c9 | 240 | else if (acpi_dev_resource_memory(ares, &res)) { |
51dd43df | 241 | tpm_info->res = res; |
30f9c8c9 JS |
242 | tpm_info->res.name = NULL; |
243 | } | |
399235dc JS |
244 | |
245 | return 1; | |
246 | } | |
247 | ||
248 | static int tpm_tis_acpi_init(struct acpi_device *acpi_dev) | |
249 | { | |
4d627e67 JG |
250 | struct acpi_table_tpm2 *tbl; |
251 | acpi_status st; | |
399235dc | 252 | struct list_head resources; |
4d627e67 | 253 | struct tpm_info tpm_info = {}; |
399235dc JS |
254 | int ret; |
255 | ||
4d627e67 JG |
256 | st = acpi_get_table(ACPI_SIG_TPM2, 1, |
257 | (struct acpi_table_header **) &tbl); | |
258 | if (ACPI_FAILURE(st) || tbl->header.length < sizeof(*tbl)) { | |
259 | dev_err(&acpi_dev->dev, | |
260 | FW_BUG "failed to get TPM2 ACPI table\n"); | |
261 | return -EINVAL; | |
262 | } | |
263 | ||
264 | if (tbl->start_method != ACPI_TPM2_MEMORY_MAPPED) | |
399235dc JS |
265 | return -ENODEV; |
266 | ||
267 | INIT_LIST_HEAD(&resources); | |
ef7b81dc | 268 | tpm_info.irq = -1; |
399235dc JS |
269 | ret = acpi_dev_get_resources(acpi_dev, &resources, tpm_check_resource, |
270 | &tpm_info); | |
271 | if (ret < 0) | |
272 | return ret; | |
273 | ||
274 | acpi_dev_free_resource_list(&resources); | |
275 | ||
51dd43df | 276 | if (resource_type(&tpm_info.res) != IORESOURCE_MEM) { |
4d627e67 JG |
277 | dev_err(&acpi_dev->dev, |
278 | FW_BUG "TPM2 ACPI table does not define a memory resource\n"); | |
279 | return -EINVAL; | |
280 | } | |
281 | ||
399235dc JS |
282 | if (is_itpm(acpi_dev)) |
283 | itpm = true; | |
284 | ||
285 | return tpm_tis_init(&acpi_dev->dev, &tpm_info, acpi_dev->handle); | |
286 | } | |
287 | ||
288 | static int tpm_tis_acpi_remove(struct acpi_device *dev) | |
289 | { | |
290 | struct tpm_chip *chip = dev_get_drvdata(&dev->dev); | |
291 | ||
292 | tpm_chip_unregister(chip); | |
293 | tpm_tis_remove(chip); | |
294 | ||
295 | return 0; | |
296 | } | |
297 | ||
298 | static struct acpi_device_id tpm_acpi_tbl[] = { | |
299 | {"MSFT0101", 0}, /* TPM 2.0 */ | |
300 | /* Add new here */ | |
301 | {"", 0}, /* User Specified */ | |
302 | {"", 0} /* Terminator */ | |
303 | }; | |
304 | MODULE_DEVICE_TABLE(acpi, tpm_acpi_tbl); | |
305 | ||
306 | static struct acpi_driver tis_acpi_driver = { | |
307 | .name = "tpm_tis", | |
308 | .ids = tpm_acpi_tbl, | |
309 | .ops = { | |
310 | .add = tpm_tis_acpi_init, | |
311 | .remove = tpm_tis_acpi_remove, | |
312 | }, | |
313 | .drv = { | |
314 | .pm = &tpm_tis_pm, | |
315 | }, | |
316 | }; | |
317 | #endif | |
318 | ||
00194826 JG |
319 | static struct platform_device *force_pdev; |
320 | ||
321 | static int tpm_tis_plat_probe(struct platform_device *pdev) | |
322 | { | |
323 | struct tpm_info tpm_info = {}; | |
324 | struct resource *res; | |
325 | ||
326 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
327 | if (res == NULL) { | |
328 | dev_err(&pdev->dev, "no memory resource defined\n"); | |
329 | return -ENODEV; | |
330 | } | |
331 | tpm_info.res = *res; | |
332 | ||
333 | res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); | |
334 | if (res) { | |
335 | tpm_info.irq = res->start; | |
336 | } else { | |
337 | if (pdev == force_pdev) | |
338 | tpm_info.irq = -1; | |
339 | else | |
340 | /* When forcing auto probe the IRQ */ | |
341 | tpm_info.irq = 0; | |
342 | } | |
343 | ||
344 | return tpm_tis_init(&pdev->dev, &tpm_info, NULL); | |
345 | } | |
346 | ||
347 | static int tpm_tis_plat_remove(struct platform_device *pdev) | |
348 | { | |
349 | struct tpm_chip *chip = dev_get_drvdata(&pdev->dev); | |
350 | ||
351 | tpm_chip_unregister(chip); | |
352 | tpm_tis_remove(chip); | |
353 | ||
354 | return 0; | |
355 | } | |
356 | ||
7a192ec3 | 357 | static struct platform_driver tis_drv = { |
00194826 JG |
358 | .probe = tpm_tis_plat_probe, |
359 | .remove = tpm_tis_plat_remove, | |
7a192ec3 | 360 | .driver = { |
afb5abc2 | 361 | .name = "tpm_tis", |
b633f050 | 362 | .pm = &tpm_tis_pm, |
7a192ec3 | 363 | }, |
9e323d3e KJH |
364 | }; |
365 | ||
00194826 JG |
366 | static int tpm_tis_force_device(void) |
367 | { | |
368 | struct platform_device *pdev; | |
369 | static const struct resource x86_resources[] = { | |
370 | { | |
371 | .start = 0xFED40000, | |
372 | .end = 0xFED40000 + TIS_MEM_LEN - 1, | |
373 | .flags = IORESOURCE_MEM, | |
374 | }, | |
375 | }; | |
376 | ||
377 | if (!force) | |
378 | return 0; | |
379 | ||
380 | /* The driver core will match the name tpm_tis of the device to | |
381 | * the tpm_tis platform driver and complete the setup via | |
382 | * tpm_tis_plat_probe | |
383 | */ | |
384 | pdev = platform_device_register_simple("tpm_tis", -1, x86_resources, | |
385 | ARRAY_SIZE(x86_resources)); | |
386 | if (IS_ERR(pdev)) | |
387 | return PTR_ERR(pdev); | |
388 | force_pdev = pdev; | |
389 | ||
390 | return 0; | |
391 | } | |
392 | ||
27084efe LD |
393 | static int __init init_tis(void) |
394 | { | |
9e323d3e | 395 | int rc; |
00194826 JG |
396 | |
397 | rc = tpm_tis_force_device(); | |
398 | if (rc) | |
399 | goto err_force; | |
400 | ||
401 | rc = platform_driver_register(&tis_drv); | |
402 | if (rc) | |
403 | goto err_platform; | |
404 | ||
399235dc | 405 | #ifdef CONFIG_ACPI |
00194826 JG |
406 | rc = acpi_bus_register_driver(&tis_acpi_driver); |
407 | if (rc) | |
408 | goto err_acpi; | |
399235dc | 409 | #endif |
9e323d3e | 410 | |
00194826 JG |
411 | if (IS_ENABLED(CONFIG_PNP)) { |
412 | rc = pnp_register_driver(&tis_pnp_driver); | |
413 | if (rc) | |
414 | goto err_pnp; | |
9e323d3e | 415 | } |
00194826 | 416 | |
4fba3c3b | 417 | return 0; |
00194826 JG |
418 | |
419 | err_pnp: | |
420 | #ifdef CONFIG_ACPI | |
421 | acpi_bus_unregister_driver(&tis_acpi_driver); | |
422 | err_acpi: | |
423 | #endif | |
424 | platform_device_unregister(force_pdev); | |
425 | err_platform: | |
426 | if (force_pdev) | |
427 | platform_device_unregister(force_pdev); | |
428 | err_force: | |
7f2ab000 | 429 | return rc; |
27084efe LD |
430 | } |
431 | ||
432 | static void __exit cleanup_tis(void) | |
433 | { | |
00194826 | 434 | pnp_unregister_driver(&tis_pnp_driver); |
399235dc | 435 | #ifdef CONFIG_ACPI |
00194826 | 436 | acpi_bus_unregister_driver(&tis_acpi_driver); |
7f2ab000 | 437 | #endif |
7f2ab000 | 438 | platform_driver_unregister(&tis_drv); |
00194826 JG |
439 | |
440 | if (force_pdev) | |
441 | platform_device_unregister(force_pdev); | |
27084efe LD |
442 | } |
443 | ||
444 | module_init(init_tis); | |
445 | module_exit(cleanup_tis); | |
446 | MODULE_AUTHOR("Leendert van Doorn (leendert@watson.ibm.com)"); | |
447 | MODULE_DESCRIPTION("TPM Driver"); | |
448 | MODULE_VERSION("2.0"); | |
449 | MODULE_LICENSE("GPL"); |