diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/char/tpm |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/char/tpm')
-rw-r--r-- | drivers/char/tpm/Kconfig | 39 | ||||
-rw-r--r-- | drivers/char/tpm/Makefile | 7 | ||||
-rw-r--r-- | drivers/char/tpm/tpm.c | 697 | ||||
-rw-r--r-- | drivers/char/tpm/tpm.h | 93 | ||||
-rw-r--r-- | drivers/char/tpm/tpm_atmel.c | 216 | ||||
-rw-r--r-- | drivers/char/tpm/tpm_nsc.c | 373 |
6 files changed, 1425 insertions, 0 deletions
diff --git a/drivers/char/tpm/Kconfig b/drivers/char/tpm/Kconfig new file mode 100644 index 000000000000..7a969778915a --- /dev/null +++ b/drivers/char/tpm/Kconfig | |||
@@ -0,0 +1,39 @@ | |||
1 | # | ||
2 | # TPM device configuration | ||
3 | # | ||
4 | |||
5 | menu "TPM devices" | ||
6 | |||
7 | config TCG_TPM | ||
8 | tristate "TPM Hardware Support" | ||
9 | depends on EXPERIMENTAL && PCI | ||
10 | ---help--- | ||
11 | If you have a TPM security chip in your system, which | ||
12 | implements the Trusted Computing Group's specification, | ||
13 | say Yes and it will be accessible from within Linux. For | ||
14 | more information see <http://www.trustedcomputinggroup.org>. | ||
15 | An implementation of the Trusted Software Stack (TSS), the | ||
16 | userspace enablement piece of the specification, can be | ||
17 | obtained at: <http://sourceforge.net/projects/trousers>. To | ||
18 | compile this driver as a module, choose M here; the module | ||
19 | will be called tpm. If unsure, say N. | ||
20 | |||
21 | config TCG_NSC | ||
22 | tristate "National Semiconductor TPM Interface" | ||
23 | depends on TCG_TPM | ||
24 | ---help--- | ||
25 | If you have a TPM security chip from National Semicondutor | ||
26 | say Yes and it will be accessible from within Linux. To | ||
27 | compile this driver as a module, choose M here; the module | ||
28 | will be called tpm_nsc. | ||
29 | |||
30 | config TCG_ATMEL | ||
31 | tristate "Atmel TPM Interface" | ||
32 | depends on TCG_TPM | ||
33 | ---help--- | ||
34 | If you have a TPM security chip from Atmel say Yes and it | ||
35 | will be accessible from within Linux. To compile this driver | ||
36 | as a module, choose M here; the module will be called tpm_atmel. | ||
37 | |||
38 | endmenu | ||
39 | |||
diff --git a/drivers/char/tpm/Makefile b/drivers/char/tpm/Makefile new file mode 100644 index 000000000000..736d3df266f5 --- /dev/null +++ b/drivers/char/tpm/Makefile | |||
@@ -0,0 +1,7 @@ | |||
1 | # | ||
2 | # Makefile for the kernel tpm device drivers. | ||
3 | # | ||
4 | obj-$(CONFIG_TCG_TPM) += tpm.o | ||
5 | obj-$(CONFIG_TCG_NSC) += tpm_nsc.o | ||
6 | obj-$(CONFIG_TCG_ATMEL) += tpm_atmel.o | ||
7 | |||
diff --git a/drivers/char/tpm/tpm.c b/drivers/char/tpm/tpm.c new file mode 100644 index 000000000000..8318268169d6 --- /dev/null +++ b/drivers/char/tpm/tpm.c | |||
@@ -0,0 +1,697 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2004 IBM Corporation | ||
3 | * | ||
4 | * Authors: | ||
5 | * Leendert van Doorn <leendert@watson.ibm.com> | ||
6 | * Dave Safford <safford@watson.ibm.com> | ||
7 | * Reiner Sailer <sailer@watson.ibm.com> | ||
8 | * Kylene Hall <kjhall@us.ibm.com> | ||
9 | * | ||
10 | * Maintained by: <tpmdd_devel@lists.sourceforge.net> | ||
11 | * | ||
12 | * Device driver for TCG/TCPA TPM (trusted platform module). | ||
13 | * Specifications at www.trustedcomputinggroup.org | ||
14 | * | ||
15 | * This program is free software; you can redistribute it and/or | ||
16 | * modify it under the terms of the GNU General Public License as | ||
17 | * published by the Free Software Foundation, version 2 of the | ||
18 | * License. | ||
19 | * | ||
20 | * Note, the TPM chip is not interrupt driven (only polling) | ||
21 | * and can have very long timeouts (minutes!). Hence the unusual | ||
22 | * calls to schedule_timeout. | ||
23 | * | ||
24 | */ | ||
25 | |||
26 | #include <linux/sched.h> | ||
27 | #include <linux/poll.h> | ||
28 | #include <linux/spinlock.h> | ||
29 | #include "tpm.h" | ||
30 | |||
31 | #define TPM_MINOR 224 /* officially assigned */ | ||
32 | |||
33 | #define TPM_BUFSIZE 2048 | ||
34 | |||
35 | /* PCI configuration addresses */ | ||
36 | #define PCI_GEN_PMCON_1 0xA0 | ||
37 | #define PCI_GEN1_DEC 0xE4 | ||
38 | #define PCI_LPC_EN 0xE6 | ||
39 | #define PCI_GEN2_DEC 0xEC | ||
40 | |||
41 | static LIST_HEAD(tpm_chip_list); | ||
42 | static DEFINE_SPINLOCK(driver_lock); | ||
43 | static int dev_mask[32]; | ||
44 | |||
45 | static void user_reader_timeout(unsigned long ptr) | ||
46 | { | ||
47 | struct tpm_chip *chip = (struct tpm_chip *) ptr; | ||
48 | |||
49 | down(&chip->buffer_mutex); | ||
50 | atomic_set(&chip->data_pending, 0); | ||
51 | memset(chip->data_buffer, 0, TPM_BUFSIZE); | ||
52 | up(&chip->buffer_mutex); | ||
53 | } | ||
54 | |||
55 | void tpm_time_expired(unsigned long ptr) | ||
56 | { | ||
57 | int *exp = (int *) ptr; | ||
58 | *exp = 1; | ||
59 | } | ||
60 | |||
61 | EXPORT_SYMBOL_GPL(tpm_time_expired); | ||
62 | |||
63 | /* | ||
64 | * Initialize the LPC bus and enable the TPM ports | ||
65 | */ | ||
66 | int tpm_lpc_bus_init(struct pci_dev *pci_dev, u16 base) | ||
67 | { | ||
68 | u32 lpcenable, tmp; | ||
69 | int is_lpcm = 0; | ||
70 | |||
71 | switch (pci_dev->vendor) { | ||
72 | case PCI_VENDOR_ID_INTEL: | ||
73 | switch (pci_dev->device) { | ||
74 | case PCI_DEVICE_ID_INTEL_82801CA_12: | ||
75 | case PCI_DEVICE_ID_INTEL_82801DB_12: | ||
76 | is_lpcm = 1; | ||
77 | break; | ||
78 | } | ||
79 | /* init ICH (enable LPC) */ | ||
80 | pci_read_config_dword(pci_dev, PCI_GEN1_DEC, &lpcenable); | ||
81 | lpcenable |= 0x20000000; | ||
82 | pci_write_config_dword(pci_dev, PCI_GEN1_DEC, lpcenable); | ||
83 | |||
84 | if (is_lpcm) { | ||
85 | pci_read_config_dword(pci_dev, PCI_GEN1_DEC, | ||
86 | &lpcenable); | ||
87 | if ((lpcenable & 0x20000000) == 0) { | ||
88 | dev_err(&pci_dev->dev, | ||
89 | "cannot enable LPC\n"); | ||
90 | return -ENODEV; | ||
91 | } | ||
92 | } | ||
93 | |||
94 | /* initialize TPM registers */ | ||
95 | pci_read_config_dword(pci_dev, PCI_GEN2_DEC, &tmp); | ||
96 | |||
97 | if (!is_lpcm) | ||
98 | tmp = (tmp & 0xFFFF0000) | (base & 0xFFF0); | ||
99 | else | ||
100 | tmp = | ||
101 | (tmp & 0xFFFF0000) | (base & 0xFFF0) | | ||
102 | 0x00000001; | ||
103 | |||
104 | pci_write_config_dword(pci_dev, PCI_GEN2_DEC, tmp); | ||
105 | |||
106 | if (is_lpcm) { | ||
107 | pci_read_config_dword(pci_dev, PCI_GEN_PMCON_1, | ||
108 | &tmp); | ||
109 | tmp |= 0x00000004; /* enable CLKRUN */ | ||
110 | pci_write_config_dword(pci_dev, PCI_GEN_PMCON_1, | ||
111 | tmp); | ||
112 | } | ||
113 | tpm_write_index(0x0D, 0x55); /* unlock 4F */ | ||
114 | tpm_write_index(0x0A, 0x00); /* int disable */ | ||
115 | tpm_write_index(0x08, base); /* base addr lo */ | ||
116 | tpm_write_index(0x09, (base & 0xFF00) >> 8); /* base addr hi */ | ||
117 | tpm_write_index(0x0D, 0xAA); /* lock 4F */ | ||
118 | break; | ||
119 | case PCI_VENDOR_ID_AMD: | ||
120 | /* nothing yet */ | ||
121 | break; | ||
122 | } | ||
123 | |||
124 | return 0; | ||
125 | } | ||
126 | |||
127 | EXPORT_SYMBOL_GPL(tpm_lpc_bus_init); | ||
128 | |||
129 | /* | ||
130 | * Internal kernel interface to transmit TPM commands | ||
131 | */ | ||
132 | static ssize_t tpm_transmit(struct tpm_chip *chip, const char *buf, | ||
133 | size_t bufsiz) | ||
134 | { | ||
135 | ssize_t len; | ||
136 | u32 count; | ||
137 | __be32 *native_size; | ||
138 | |||
139 | native_size = (__force __be32 *) (buf + 2); | ||
140 | count = be32_to_cpu(*native_size); | ||
141 | |||
142 | if (count == 0) | ||
143 | return -ENODATA; | ||
144 | if (count > bufsiz) { | ||
145 | dev_err(&chip->pci_dev->dev, | ||
146 | "invalid count value %x %x \n", count, bufsiz); | ||
147 | return -E2BIG; | ||
148 | } | ||
149 | |||
150 | down(&chip->tpm_mutex); | ||
151 | |||
152 | if ((len = chip->vendor->send(chip, (u8 *) buf, count)) < 0) { | ||
153 | dev_err(&chip->pci_dev->dev, | ||
154 | "tpm_transmit: tpm_send: error %d\n", len); | ||
155 | return len; | ||
156 | } | ||
157 | |||
158 | down(&chip->timer_manipulation_mutex); | ||
159 | chip->time_expired = 0; | ||
160 | init_timer(&chip->device_timer); | ||
161 | chip->device_timer.function = tpm_time_expired; | ||
162 | chip->device_timer.expires = jiffies + 2 * 60 * HZ; | ||
163 | chip->device_timer.data = (unsigned long) &chip->time_expired; | ||
164 | add_timer(&chip->device_timer); | ||
165 | up(&chip->timer_manipulation_mutex); | ||
166 | |||
167 | do { | ||
168 | u8 status = inb(chip->vendor->base + 1); | ||
169 | if ((status & chip->vendor->req_complete_mask) == | ||
170 | chip->vendor->req_complete_val) { | ||
171 | down(&chip->timer_manipulation_mutex); | ||
172 | del_singleshot_timer_sync(&chip->device_timer); | ||
173 | up(&chip->timer_manipulation_mutex); | ||
174 | goto out_recv; | ||
175 | } | ||
176 | set_current_state(TASK_UNINTERRUPTIBLE); | ||
177 | schedule_timeout(TPM_TIMEOUT); | ||
178 | rmb(); | ||
179 | } while (!chip->time_expired); | ||
180 | |||
181 | |||
182 | chip->vendor->cancel(chip); | ||
183 | dev_err(&chip->pci_dev->dev, "Time expired\n"); | ||
184 | up(&chip->tpm_mutex); | ||
185 | return -EIO; | ||
186 | |||
187 | out_recv: | ||
188 | len = chip->vendor->recv(chip, (u8 *) buf, bufsiz); | ||
189 | if (len < 0) | ||
190 | dev_err(&chip->pci_dev->dev, | ||
191 | "tpm_transmit: tpm_recv: error %d\n", len); | ||
192 | up(&chip->tpm_mutex); | ||
193 | return len; | ||
194 | } | ||
195 | |||
196 | #define TPM_DIGEST_SIZE 20 | ||
197 | #define CAP_PCR_RESULT_SIZE 18 | ||
198 | static u8 cap_pcr[] = { | ||
199 | 0, 193, /* TPM_TAG_RQU_COMMAND */ | ||
200 | 0, 0, 0, 22, /* length */ | ||
201 | 0, 0, 0, 101, /* TPM_ORD_GetCapability */ | ||
202 | 0, 0, 0, 5, | ||
203 | 0, 0, 0, 4, | ||
204 | 0, 0, 1, 1 | ||
205 | }; | ||
206 | |||
207 | #define READ_PCR_RESULT_SIZE 30 | ||
208 | static u8 pcrread[] = { | ||
209 | 0, 193, /* TPM_TAG_RQU_COMMAND */ | ||
210 | 0, 0, 0, 14, /* length */ | ||
211 | 0, 0, 0, 21, /* TPM_ORD_PcrRead */ | ||
212 | 0, 0, 0, 0 /* PCR index */ | ||
213 | }; | ||
214 | |||
215 | static ssize_t show_pcrs(struct device *dev, char *buf) | ||
216 | { | ||
217 | u8 data[READ_PCR_RESULT_SIZE]; | ||
218 | ssize_t len; | ||
219 | int i, j, index, num_pcrs; | ||
220 | char *str = buf; | ||
221 | |||
222 | struct tpm_chip *chip = | ||
223 | pci_get_drvdata(container_of(dev, struct pci_dev, dev)); | ||
224 | if (chip == NULL) | ||
225 | return -ENODEV; | ||
226 | |||
227 | memcpy(data, cap_pcr, sizeof(cap_pcr)); | ||
228 | if ((len = tpm_transmit(chip, data, sizeof(data))) | ||
229 | < CAP_PCR_RESULT_SIZE) | ||
230 | return len; | ||
231 | |||
232 | num_pcrs = be32_to_cpu(*((__force __be32 *) (data + 14))); | ||
233 | |||
234 | for (i = 0; i < num_pcrs; i++) { | ||
235 | memcpy(data, pcrread, sizeof(pcrread)); | ||
236 | index = cpu_to_be32(i); | ||
237 | memcpy(data + 10, &index, 4); | ||
238 | if ((len = tpm_transmit(chip, data, sizeof(data))) | ||
239 | < READ_PCR_RESULT_SIZE) | ||
240 | return len; | ||
241 | str += sprintf(str, "PCR-%02d: ", i); | ||
242 | for (j = 0; j < TPM_DIGEST_SIZE; j++) | ||
243 | str += sprintf(str, "%02X ", *(data + 10 + j)); | ||
244 | str += sprintf(str, "\n"); | ||
245 | } | ||
246 | return str - buf; | ||
247 | } | ||
248 | |||
249 | static DEVICE_ATTR(pcrs, S_IRUGO, show_pcrs, NULL); | ||
250 | |||
251 | #define READ_PUBEK_RESULT_SIZE 314 | ||
252 | static u8 readpubek[] = { | ||
253 | 0, 193, /* TPM_TAG_RQU_COMMAND */ | ||
254 | 0, 0, 0, 30, /* length */ | ||
255 | 0, 0, 0, 124, /* TPM_ORD_ReadPubek */ | ||
256 | }; | ||
257 | |||
258 | static ssize_t show_pubek(struct device *dev, char *buf) | ||
259 | { | ||
260 | u8 data[READ_PUBEK_RESULT_SIZE]; | ||
261 | ssize_t len; | ||
262 | __be32 *native_val; | ||
263 | int i; | ||
264 | char *str = buf; | ||
265 | |||
266 | struct tpm_chip *chip = | ||
267 | pci_get_drvdata(container_of(dev, struct pci_dev, dev)); | ||
268 | if (chip == NULL) | ||
269 | return -ENODEV; | ||
270 | |||
271 | memcpy(data, readpubek, sizeof(readpubek)); | ||
272 | memset(data + sizeof(readpubek), 0, 20); /* zero nonce */ | ||
273 | |||
274 | if ((len = tpm_transmit(chip, data, sizeof(data))) < | ||
275 | READ_PUBEK_RESULT_SIZE) | ||
276 | return len; | ||
277 | |||
278 | /* | ||
279 | ignore header 10 bytes | ||
280 | algorithm 32 bits (1 == RSA ) | ||
281 | encscheme 16 bits | ||
282 | sigscheme 16 bits | ||
283 | parameters (RSA 12->bytes: keybit, #primes, expbit) | ||
284 | keylenbytes 32 bits | ||
285 | 256 byte modulus | ||
286 | ignore checksum 20 bytes | ||
287 | */ | ||
288 | |||
289 | native_val = (__force __be32 *) (data + 34); | ||
290 | |||
291 | str += | ||
292 | sprintf(str, | ||
293 | "Algorithm: %02X %02X %02X %02X\nEncscheme: %02X %02X\n" | ||
294 | "Sigscheme: %02X %02X\nParameters: %02X %02X %02X %02X" | ||
295 | " %02X %02X %02X %02X %02X %02X %02X %02X\n" | ||
296 | "Modulus length: %d\nModulus: \n", | ||
297 | data[10], data[11], data[12], data[13], data[14], | ||
298 | data[15], data[16], data[17], data[22], data[23], | ||
299 | data[24], data[25], data[26], data[27], data[28], | ||
300 | data[29], data[30], data[31], data[32], data[33], | ||
301 | be32_to_cpu(*native_val) | ||
302 | ); | ||
303 | |||
304 | for (i = 0; i < 256; i++) { | ||
305 | str += sprintf(str, "%02X ", data[i + 39]); | ||
306 | if ((i + 1) % 16 == 0) | ||
307 | str += sprintf(str, "\n"); | ||
308 | } | ||
309 | return str - buf; | ||
310 | } | ||
311 | |||
312 | static DEVICE_ATTR(pubek, S_IRUGO, show_pubek, NULL); | ||
313 | |||
314 | #define CAP_VER_RESULT_SIZE 18 | ||
315 | static u8 cap_version[] = { | ||
316 | 0, 193, /* TPM_TAG_RQU_COMMAND */ | ||
317 | 0, 0, 0, 18, /* length */ | ||
318 | 0, 0, 0, 101, /* TPM_ORD_GetCapability */ | ||
319 | 0, 0, 0, 6, | ||
320 | 0, 0, 0, 0 | ||
321 | }; | ||
322 | |||
323 | #define CAP_MANUFACTURER_RESULT_SIZE 18 | ||
324 | static u8 cap_manufacturer[] = { | ||
325 | 0, 193, /* TPM_TAG_RQU_COMMAND */ | ||
326 | 0, 0, 0, 22, /* length */ | ||
327 | 0, 0, 0, 101, /* TPM_ORD_GetCapability */ | ||
328 | 0, 0, 0, 5, | ||
329 | 0, 0, 0, 4, | ||
330 | 0, 0, 1, 3 | ||
331 | }; | ||
332 | |||
333 | static ssize_t show_caps(struct device *dev, char *buf) | ||
334 | { | ||
335 | u8 data[READ_PUBEK_RESULT_SIZE]; | ||
336 | ssize_t len; | ||
337 | char *str = buf; | ||
338 | |||
339 | struct tpm_chip *chip = | ||
340 | pci_get_drvdata(container_of(dev, struct pci_dev, dev)); | ||
341 | if (chip == NULL) | ||
342 | return -ENODEV; | ||
343 | |||
344 | memcpy(data, cap_manufacturer, sizeof(cap_manufacturer)); | ||
345 | |||
346 | if ((len = tpm_transmit(chip, data, sizeof(data))) < | ||
347 | CAP_MANUFACTURER_RESULT_SIZE) | ||
348 | return len; | ||
349 | |||
350 | str += sprintf(str, "Manufacturer: 0x%x\n", | ||
351 | be32_to_cpu(*(data + 14))); | ||
352 | |||
353 | memcpy(data, cap_version, sizeof(cap_version)); | ||
354 | |||
355 | if ((len = tpm_transmit(chip, data, sizeof(data))) < | ||
356 | CAP_VER_RESULT_SIZE) | ||
357 | return len; | ||
358 | |||
359 | str += | ||
360 | sprintf(str, "TCG version: %d.%d\nFirmware version: %d.%d\n", | ||
361 | (int) data[14], (int) data[15], (int) data[16], | ||
362 | (int) data[17]); | ||
363 | |||
364 | return str - buf; | ||
365 | } | ||
366 | |||
367 | static DEVICE_ATTR(caps, S_IRUGO, show_caps, NULL); | ||
368 | |||
369 | /* | ||
370 | * Device file system interface to the TPM | ||
371 | */ | ||
372 | int tpm_open(struct inode *inode, struct file *file) | ||
373 | { | ||
374 | int rc = 0, minor = iminor(inode); | ||
375 | struct tpm_chip *chip = NULL, *pos; | ||
376 | |||
377 | spin_lock(&driver_lock); | ||
378 | |||
379 | list_for_each_entry(pos, &tpm_chip_list, list) { | ||
380 | if (pos->vendor->miscdev.minor == minor) { | ||
381 | chip = pos; | ||
382 | break; | ||
383 | } | ||
384 | } | ||
385 | |||
386 | if (chip == NULL) { | ||
387 | rc = -ENODEV; | ||
388 | goto err_out; | ||
389 | } | ||
390 | |||
391 | if (chip->num_opens) { | ||
392 | dev_dbg(&chip->pci_dev->dev, | ||
393 | "Another process owns this TPM\n"); | ||
394 | rc = -EBUSY; | ||
395 | goto err_out; | ||
396 | } | ||
397 | |||
398 | chip->num_opens++; | ||
399 | pci_dev_get(chip->pci_dev); | ||
400 | |||
401 | spin_unlock(&driver_lock); | ||
402 | |||
403 | chip->data_buffer = kmalloc(TPM_BUFSIZE * sizeof(u8), GFP_KERNEL); | ||
404 | if (chip->data_buffer == NULL) { | ||
405 | chip->num_opens--; | ||
406 | pci_dev_put(chip->pci_dev); | ||
407 | return -ENOMEM; | ||
408 | } | ||
409 | |||
410 | atomic_set(&chip->data_pending, 0); | ||
411 | |||
412 | file->private_data = chip; | ||
413 | return 0; | ||
414 | |||
415 | err_out: | ||
416 | spin_unlock(&driver_lock); | ||
417 | return rc; | ||
418 | } | ||
419 | |||
420 | EXPORT_SYMBOL_GPL(tpm_open); | ||
421 | |||
422 | int tpm_release(struct inode *inode, struct file *file) | ||
423 | { | ||
424 | struct tpm_chip *chip = file->private_data; | ||
425 | |||
426 | file->private_data = NULL; | ||
427 | |||
428 | spin_lock(&driver_lock); | ||
429 | chip->num_opens--; | ||
430 | spin_unlock(&driver_lock); | ||
431 | |||
432 | down(&chip->timer_manipulation_mutex); | ||
433 | if (timer_pending(&chip->user_read_timer)) | ||
434 | del_singleshot_timer_sync(&chip->user_read_timer); | ||
435 | else if (timer_pending(&chip->device_timer)) | ||
436 | del_singleshot_timer_sync(&chip->device_timer); | ||
437 | up(&chip->timer_manipulation_mutex); | ||
438 | |||
439 | kfree(chip->data_buffer); | ||
440 | atomic_set(&chip->data_pending, 0); | ||
441 | |||
442 | pci_dev_put(chip->pci_dev); | ||
443 | return 0; | ||
444 | } | ||
445 | |||
446 | EXPORT_SYMBOL_GPL(tpm_release); | ||
447 | |||
448 | ssize_t tpm_write(struct file * file, const char __user * buf, | ||
449 | size_t size, loff_t * off) | ||
450 | { | ||
451 | struct tpm_chip *chip = file->private_data; | ||
452 | int in_size = size, out_size; | ||
453 | |||
454 | /* cannot perform a write until the read has cleared | ||
455 | either via tpm_read or a user_read_timer timeout */ | ||
456 | while (atomic_read(&chip->data_pending) != 0) { | ||
457 | set_current_state(TASK_UNINTERRUPTIBLE); | ||
458 | schedule_timeout(TPM_TIMEOUT); | ||
459 | } | ||
460 | |||
461 | down(&chip->buffer_mutex); | ||
462 | |||
463 | if (in_size > TPM_BUFSIZE) | ||
464 | in_size = TPM_BUFSIZE; | ||
465 | |||
466 | if (copy_from_user | ||
467 | (chip->data_buffer, (void __user *) buf, in_size)) { | ||
468 | up(&chip->buffer_mutex); | ||
469 | return -EFAULT; | ||
470 | } | ||
471 | |||
472 | /* atomic tpm command send and result receive */ | ||
473 | out_size = tpm_transmit(chip, chip->data_buffer, TPM_BUFSIZE); | ||
474 | |||
475 | atomic_set(&chip->data_pending, out_size); | ||
476 | up(&chip->buffer_mutex); | ||
477 | |||
478 | /* Set a timeout by which the reader must come claim the result */ | ||
479 | down(&chip->timer_manipulation_mutex); | ||
480 | init_timer(&chip->user_read_timer); | ||
481 | chip->user_read_timer.function = user_reader_timeout; | ||
482 | chip->user_read_timer.data = (unsigned long) chip; | ||
483 | chip->user_read_timer.expires = jiffies + (60 * HZ); | ||
484 | add_timer(&chip->user_read_timer); | ||
485 | up(&chip->timer_manipulation_mutex); | ||
486 | |||
487 | return in_size; | ||
488 | } | ||
489 | |||
490 | EXPORT_SYMBOL_GPL(tpm_write); | ||
491 | |||
492 | ssize_t tpm_read(struct file * file, char __user * buf, | ||
493 | size_t size, loff_t * off) | ||
494 | { | ||
495 | struct tpm_chip *chip = file->private_data; | ||
496 | int ret_size = -ENODATA; | ||
497 | |||
498 | if (atomic_read(&chip->data_pending) != 0) { /* Result available */ | ||
499 | down(&chip->timer_manipulation_mutex); | ||
500 | del_singleshot_timer_sync(&chip->user_read_timer); | ||
501 | up(&chip->timer_manipulation_mutex); | ||
502 | |||
503 | down(&chip->buffer_mutex); | ||
504 | |||
505 | ret_size = atomic_read(&chip->data_pending); | ||
506 | atomic_set(&chip->data_pending, 0); | ||
507 | |||
508 | if (ret_size == 0) /* timeout just occurred */ | ||
509 | ret_size = -ETIME; | ||
510 | else if (ret_size > 0) { /* relay data */ | ||
511 | if (size < ret_size) | ||
512 | ret_size = size; | ||
513 | |||
514 | if (copy_to_user((void __user *) buf, | ||
515 | chip->data_buffer, ret_size)) { | ||
516 | ret_size = -EFAULT; | ||
517 | } | ||
518 | } | ||
519 | up(&chip->buffer_mutex); | ||
520 | } | ||
521 | |||
522 | return ret_size; | ||
523 | } | ||
524 | |||
525 | EXPORT_SYMBOL_GPL(tpm_read); | ||
526 | |||
527 | void __devexit tpm_remove(struct pci_dev *pci_dev) | ||
528 | { | ||
529 | struct tpm_chip *chip = pci_get_drvdata(pci_dev); | ||
530 | |||
531 | if (chip == NULL) { | ||
532 | dev_err(&pci_dev->dev, "No device data found\n"); | ||
533 | return; | ||
534 | } | ||
535 | |||
536 | spin_lock(&driver_lock); | ||
537 | |||
538 | list_del(&chip->list); | ||
539 | |||
540 | spin_unlock(&driver_lock); | ||
541 | |||
542 | pci_set_drvdata(pci_dev, NULL); | ||
543 | misc_deregister(&chip->vendor->miscdev); | ||
544 | |||
545 | device_remove_file(&pci_dev->dev, &dev_attr_pubek); | ||
546 | device_remove_file(&pci_dev->dev, &dev_attr_pcrs); | ||
547 | device_remove_file(&pci_dev->dev, &dev_attr_caps); | ||
548 | |||
549 | pci_disable_device(pci_dev); | ||
550 | |||
551 | dev_mask[chip->dev_num / 32] &= !(1 << (chip->dev_num % 32)); | ||
552 | |||
553 | kfree(chip); | ||
554 | |||
555 | pci_dev_put(pci_dev); | ||
556 | } | ||
557 | |||
558 | EXPORT_SYMBOL_GPL(tpm_remove); | ||
559 | |||
560 | static u8 savestate[] = { | ||
561 | 0, 193, /* TPM_TAG_RQU_COMMAND */ | ||
562 | 0, 0, 0, 10, /* blob length (in bytes) */ | ||
563 | 0, 0, 0, 152 /* TPM_ORD_SaveState */ | ||
564 | }; | ||
565 | |||
566 | /* | ||
567 | * We are about to suspend. Save the TPM state | ||
568 | * so that it can be restored. | ||
569 | */ | ||
570 | int tpm_pm_suspend(struct pci_dev *pci_dev, u32 pm_state) | ||
571 | { | ||
572 | struct tpm_chip *chip = pci_get_drvdata(pci_dev); | ||
573 | if (chip == NULL) | ||
574 | return -ENODEV; | ||
575 | |||
576 | tpm_transmit(chip, savestate, sizeof(savestate)); | ||
577 | return 0; | ||
578 | } | ||
579 | |||
580 | EXPORT_SYMBOL_GPL(tpm_pm_suspend); | ||
581 | |||
582 | /* | ||
583 | * Resume from a power safe. The BIOS already restored | ||
584 | * the TPM state. | ||
585 | */ | ||
586 | int tpm_pm_resume(struct pci_dev *pci_dev) | ||
587 | { | ||
588 | struct tpm_chip *chip = pci_get_drvdata(pci_dev); | ||
589 | |||
590 | if (chip == NULL) | ||
591 | return -ENODEV; | ||
592 | |||
593 | spin_lock(&driver_lock); | ||
594 | tpm_lpc_bus_init(pci_dev, chip->vendor->base); | ||
595 | spin_unlock(&driver_lock); | ||
596 | |||
597 | return 0; | ||
598 | } | ||
599 | |||
600 | EXPORT_SYMBOL_GPL(tpm_pm_resume); | ||
601 | |||
602 | /* | ||
603 | * Called from tpm_<specific>.c probe function only for devices | ||
604 | * the driver has determined it should claim. Prior to calling | ||
605 | * this function the specific probe function has called pci_enable_device | ||
606 | * upon errant exit from this function specific probe function should call | ||
607 | * pci_disable_device | ||
608 | */ | ||
609 | int tpm_register_hardware(struct pci_dev *pci_dev, | ||
610 | struct tpm_vendor_specific *entry) | ||
611 | { | ||
612 | char devname[7]; | ||
613 | struct tpm_chip *chip; | ||
614 | int i, j; | ||
615 | |||
616 | /* Driver specific per-device data */ | ||
617 | chip = kmalloc(sizeof(*chip), GFP_KERNEL); | ||
618 | if (chip == NULL) | ||
619 | return -ENOMEM; | ||
620 | |||
621 | memset(chip, 0, sizeof(struct tpm_chip)); | ||
622 | |||
623 | init_MUTEX(&chip->buffer_mutex); | ||
624 | init_MUTEX(&chip->tpm_mutex); | ||
625 | init_MUTEX(&chip->timer_manipulation_mutex); | ||
626 | INIT_LIST_HEAD(&chip->list); | ||
627 | |||
628 | chip->vendor = entry; | ||
629 | |||
630 | chip->dev_num = -1; | ||
631 | |||
632 | for (i = 0; i < 32; i++) | ||
633 | for (j = 0; j < 8; j++) | ||
634 | if ((dev_mask[i] & (1 << j)) == 0) { | ||
635 | chip->dev_num = i * 32 + j; | ||
636 | dev_mask[i] |= 1 << j; | ||
637 | goto dev_num_search_complete; | ||
638 | } | ||
639 | |||
640 | dev_num_search_complete: | ||
641 | if (chip->dev_num < 0) { | ||
642 | dev_err(&pci_dev->dev, | ||
643 | "No available tpm device numbers\n"); | ||
644 | kfree(chip); | ||
645 | return -ENODEV; | ||
646 | } else if (chip->dev_num == 0) | ||
647 | chip->vendor->miscdev.minor = TPM_MINOR; | ||
648 | else | ||
649 | chip->vendor->miscdev.minor = MISC_DYNAMIC_MINOR; | ||
650 | |||
651 | snprintf(devname, sizeof(devname), "%s%d", "tpm", chip->dev_num); | ||
652 | chip->vendor->miscdev.name = devname; | ||
653 | |||
654 | chip->vendor->miscdev.dev = &(pci_dev->dev); | ||
655 | chip->pci_dev = pci_dev_get(pci_dev); | ||
656 | |||
657 | if (misc_register(&chip->vendor->miscdev)) { | ||
658 | dev_err(&chip->pci_dev->dev, | ||
659 | "unable to misc_register %s, minor %d\n", | ||
660 | chip->vendor->miscdev.name, | ||
661 | chip->vendor->miscdev.minor); | ||
662 | pci_dev_put(pci_dev); | ||
663 | kfree(chip); | ||
664 | dev_mask[i] &= !(1 << j); | ||
665 | return -ENODEV; | ||
666 | } | ||
667 | |||
668 | pci_set_drvdata(pci_dev, chip); | ||
669 | |||
670 | list_add(&chip->list, &tpm_chip_list); | ||
671 | |||
672 | device_create_file(&pci_dev->dev, &dev_attr_pubek); | ||
673 | device_create_file(&pci_dev->dev, &dev_attr_pcrs); | ||
674 | device_create_file(&pci_dev->dev, &dev_attr_caps); | ||
675 | |||
676 | return 0; | ||
677 | } | ||
678 | |||
679 | EXPORT_SYMBOL_GPL(tpm_register_hardware); | ||
680 | |||
681 | static int __init init_tpm(void) | ||
682 | { | ||
683 | return 0; | ||
684 | } | ||
685 | |||
686 | static void __exit cleanup_tpm(void) | ||
687 | { | ||
688 | |||
689 | } | ||
690 | |||
691 | module_init(init_tpm); | ||
692 | module_exit(cleanup_tpm); | ||
693 | |||
694 | MODULE_AUTHOR("Leendert van Doorn (leendert@watson.ibm.com)"); | ||
695 | MODULE_DESCRIPTION("TPM Driver"); | ||
696 | MODULE_VERSION("2.0"); | ||
697 | MODULE_LICENSE("GPL"); | ||
diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h new file mode 100644 index 000000000000..575cf5aed41a --- /dev/null +++ b/drivers/char/tpm/tpm.h | |||
@@ -0,0 +1,93 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2004 IBM Corporation | ||
3 | * | ||
4 | * Authors: | ||
5 | * Leendert van Doorn <leendert@watson.ibm.com> | ||
6 | * Dave Safford <safford@watson.ibm.com> | ||
7 | * Reiner Sailer <sailer@watson.ibm.com> | ||
8 | * Kylene Hall <kjhall@us.ibm.com> | ||
9 | * | ||
10 | * Maintained by: <tpmdd_devel@lists.sourceforge.net> | ||
11 | * | ||
12 | * Device driver for TCG/TCPA TPM (trusted platform module). | ||
13 | * Specifications at www.trustedcomputinggroup.org | ||
14 | * | ||
15 | * This program is free software; you can redistribute it and/or | ||
16 | * modify it under the terms of the GNU General Public License as | ||
17 | * published by the Free Software Foundation, version 2 of the | ||
18 | * License. | ||
19 | * | ||
20 | */ | ||
21 | #include <linux/module.h> | ||
22 | #include <linux/version.h> | ||
23 | #include <linux/pci.h> | ||
24 | #include <linux/delay.h> | ||
25 | #include <linux/fs.h> | ||
26 | #include <linux/miscdevice.h> | ||
27 | |||
28 | #define TPM_TIMEOUT msecs_to_jiffies(5) | ||
29 | |||
30 | /* TPM addresses */ | ||
31 | #define TPM_ADDR 0x4E | ||
32 | #define TPM_DATA 0x4F | ||
33 | |||
34 | struct tpm_chip; | ||
35 | |||
36 | struct tpm_vendor_specific { | ||
37 | u8 req_complete_mask; | ||
38 | u8 req_complete_val; | ||
39 | u16 base; /* TPM base address */ | ||
40 | |||
41 | int (*recv) (struct tpm_chip *, u8 *, size_t); | ||
42 | int (*send) (struct tpm_chip *, u8 *, size_t); | ||
43 | void (*cancel) (struct tpm_chip *); | ||
44 | struct miscdevice miscdev; | ||
45 | }; | ||
46 | |||
47 | struct tpm_chip { | ||
48 | struct pci_dev *pci_dev; /* PCI device stuff */ | ||
49 | |||
50 | int dev_num; /* /dev/tpm# */ | ||
51 | int num_opens; /* only one allowed */ | ||
52 | int time_expired; | ||
53 | |||
54 | /* Data passed to and from the tpm via the read/write calls */ | ||
55 | u8 *data_buffer; | ||
56 | atomic_t data_pending; | ||
57 | struct semaphore buffer_mutex; | ||
58 | |||
59 | struct timer_list user_read_timer; /* user needs to claim result */ | ||
60 | struct semaphore tpm_mutex; /* tpm is processing */ | ||
61 | struct timer_list device_timer; /* tpm is processing */ | ||
62 | struct semaphore timer_manipulation_mutex; | ||
63 | |||
64 | struct tpm_vendor_specific *vendor; | ||
65 | |||
66 | struct list_head list; | ||
67 | }; | ||
68 | |||
69 | static inline int tpm_read_index(int index) | ||
70 | { | ||
71 | outb(index, TPM_ADDR); | ||
72 | return inb(TPM_DATA) & 0xFF; | ||
73 | } | ||
74 | |||
75 | static inline void tpm_write_index(int index, int value) | ||
76 | { | ||
77 | outb(index, TPM_ADDR); | ||
78 | outb(value & 0xFF, TPM_DATA); | ||
79 | } | ||
80 | |||
81 | extern void tpm_time_expired(unsigned long); | ||
82 | extern int tpm_lpc_bus_init(struct pci_dev *, u16); | ||
83 | |||
84 | extern int tpm_register_hardware(struct pci_dev *, | ||
85 | struct tpm_vendor_specific *); | ||
86 | extern int tpm_open(struct inode *, struct file *); | ||
87 | extern int tpm_release(struct inode *, struct file *); | ||
88 | extern ssize_t tpm_write(struct file *, const char __user *, size_t, | ||
89 | loff_t *); | ||
90 | extern ssize_t tpm_read(struct file *, char __user *, size_t, loff_t *); | ||
91 | extern void __devexit tpm_remove(struct pci_dev *); | ||
92 | extern int tpm_pm_suspend(struct pci_dev *, u32); | ||
93 | extern int tpm_pm_resume(struct pci_dev *); | ||
diff --git a/drivers/char/tpm/tpm_atmel.c b/drivers/char/tpm/tpm_atmel.c new file mode 100644 index 000000000000..f9333e729b62 --- /dev/null +++ b/drivers/char/tpm/tpm_atmel.c | |||
@@ -0,0 +1,216 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2004 IBM Corporation | ||
3 | * | ||
4 | * Authors: | ||
5 | * Leendert van Doorn <leendert@watson.ibm.com> | ||
6 | * Dave Safford <safford@watson.ibm.com> | ||
7 | * Reiner Sailer <sailer@watson.ibm.com> | ||
8 | * Kylene Hall <kjhall@us.ibm.com> | ||
9 | * | ||
10 | * Maintained by: <tpmdd_devel@lists.sourceforge.net> | ||
11 | * | ||
12 | * Device driver for TCG/TCPA TPM (trusted platform module). | ||
13 | * Specifications at www.trustedcomputinggroup.org | ||
14 | * | ||
15 | * This program is free software; you can redistribute it and/or | ||
16 | * modify it under the terms of the GNU General Public License as | ||
17 | * published by the Free Software Foundation, version 2 of the | ||
18 | * License. | ||
19 | * | ||
20 | */ | ||
21 | |||
22 | #include "tpm.h" | ||
23 | |||
24 | /* Atmel definitions */ | ||
25 | #define TPM_ATML_BASE 0x400 | ||
26 | |||
27 | /* write status bits */ | ||
28 | #define ATML_STATUS_ABORT 0x01 | ||
29 | #define ATML_STATUS_LASTBYTE 0x04 | ||
30 | |||
31 | /* read status bits */ | ||
32 | #define ATML_STATUS_BUSY 0x01 | ||
33 | #define ATML_STATUS_DATA_AVAIL 0x02 | ||
34 | #define ATML_STATUS_REWRITE 0x04 | ||
35 | |||
36 | |||
37 | static int tpm_atml_recv(struct tpm_chip *chip, u8 * buf, size_t count) | ||
38 | { | ||
39 | u8 status, *hdr = buf; | ||
40 | u32 size; | ||
41 | int i; | ||
42 | __be32 *native_size; | ||
43 | |||
44 | /* start reading header */ | ||
45 | if (count < 6) | ||
46 | return -EIO; | ||
47 | |||
48 | for (i = 0; i < 6; i++) { | ||
49 | status = inb(chip->vendor->base + 1); | ||
50 | if ((status & ATML_STATUS_DATA_AVAIL) == 0) { | ||
51 | dev_err(&chip->pci_dev->dev, | ||
52 | "error reading header\n"); | ||
53 | return -EIO; | ||
54 | } | ||
55 | *buf++ = inb(chip->vendor->base); | ||
56 | } | ||
57 | |||
58 | /* size of the data received */ | ||
59 | native_size = (__force __be32 *) (hdr + 2); | ||
60 | size = be32_to_cpu(*native_size); | ||
61 | |||
62 | if (count < size) { | ||
63 | dev_err(&chip->pci_dev->dev, | ||
64 | "Recv size(%d) less than available space\n", size); | ||
65 | for (; i < size; i++) { /* clear the waiting data anyway */ | ||
66 | status = inb(chip->vendor->base + 1); | ||
67 | if ((status & ATML_STATUS_DATA_AVAIL) == 0) { | ||
68 | dev_err(&chip->pci_dev->dev, | ||
69 | "error reading data\n"); | ||
70 | return -EIO; | ||
71 | } | ||
72 | } | ||
73 | return -EIO; | ||
74 | } | ||
75 | |||
76 | /* read all the data available */ | ||
77 | for (; i < size; i++) { | ||
78 | status = inb(chip->vendor->base + 1); | ||
79 | if ((status & ATML_STATUS_DATA_AVAIL) == 0) { | ||
80 | dev_err(&chip->pci_dev->dev, | ||
81 | "error reading data\n"); | ||
82 | return -EIO; | ||
83 | } | ||
84 | *buf++ = inb(chip->vendor->base); | ||
85 | } | ||
86 | |||
87 | /* make sure data available is gone */ | ||
88 | status = inb(chip->vendor->base + 1); | ||
89 | if (status & ATML_STATUS_DATA_AVAIL) { | ||
90 | dev_err(&chip->pci_dev->dev, "data available is stuck\n"); | ||
91 | return -EIO; | ||
92 | } | ||
93 | |||
94 | return size; | ||
95 | } | ||
96 | |||
97 | static int tpm_atml_send(struct tpm_chip *chip, u8 * buf, size_t count) | ||
98 | { | ||
99 | int i; | ||
100 | |||
101 | dev_dbg(&chip->pci_dev->dev, "tpm_atml_send: "); | ||
102 | for (i = 0; i < count; i++) { | ||
103 | dev_dbg(&chip->pci_dev->dev, "0x%x(%d) ", buf[i], buf[i]); | ||
104 | outb(buf[i], chip->vendor->base); | ||
105 | } | ||
106 | |||
107 | return count; | ||
108 | } | ||
109 | |||
110 | static void tpm_atml_cancel(struct tpm_chip *chip) | ||
111 | { | ||
112 | outb(ATML_STATUS_ABORT, chip->vendor->base + 1); | ||
113 | } | ||
114 | |||
115 | static struct file_operations atmel_ops = { | ||
116 | .owner = THIS_MODULE, | ||
117 | .llseek = no_llseek, | ||
118 | .open = tpm_open, | ||
119 | .read = tpm_read, | ||
120 | .write = tpm_write, | ||
121 | .release = tpm_release, | ||
122 | }; | ||
123 | |||
124 | static struct tpm_vendor_specific tpm_atmel = { | ||
125 | .recv = tpm_atml_recv, | ||
126 | .send = tpm_atml_send, | ||
127 | .cancel = tpm_atml_cancel, | ||
128 | .req_complete_mask = ATML_STATUS_BUSY | ATML_STATUS_DATA_AVAIL, | ||
129 | .req_complete_val = ATML_STATUS_DATA_AVAIL, | ||
130 | .base = TPM_ATML_BASE, | ||
131 | .miscdev = { .fops = &atmel_ops, }, | ||
132 | }; | ||
133 | |||
134 | static int __devinit tpm_atml_init(struct pci_dev *pci_dev, | ||
135 | const struct pci_device_id *pci_id) | ||
136 | { | ||
137 | u8 version[4]; | ||
138 | int rc = 0; | ||
139 | |||
140 | if (pci_enable_device(pci_dev)) | ||
141 | return -EIO; | ||
142 | |||
143 | if (tpm_lpc_bus_init(pci_dev, TPM_ATML_BASE)) { | ||
144 | rc = -ENODEV; | ||
145 | goto out_err; | ||
146 | } | ||
147 | |||
148 | /* verify that it is an Atmel part */ | ||
149 | if (tpm_read_index(4) != 'A' || tpm_read_index(5) != 'T' | ||
150 | || tpm_read_index(6) != 'M' || tpm_read_index(7) != 'L') { | ||
151 | rc = -ENODEV; | ||
152 | goto out_err; | ||
153 | } | ||
154 | |||
155 | /* query chip for its version number */ | ||
156 | if ((version[0] = tpm_read_index(0x00)) != 0xFF) { | ||
157 | version[1] = tpm_read_index(0x01); | ||
158 | version[2] = tpm_read_index(0x02); | ||
159 | version[3] = tpm_read_index(0x03); | ||
160 | } else { | ||
161 | dev_info(&pci_dev->dev, "version query failed\n"); | ||
162 | rc = -ENODEV; | ||
163 | goto out_err; | ||
164 | } | ||
165 | |||
166 | if ((rc = tpm_register_hardware(pci_dev, &tpm_atmel)) < 0) | ||
167 | goto out_err; | ||
168 | |||
169 | dev_info(&pci_dev->dev, | ||
170 | "Atmel TPM version %d.%d.%d.%d\n", version[0], version[1], | ||
171 | version[2], version[3]); | ||
172 | |||
173 | return 0; | ||
174 | out_err: | ||
175 | pci_disable_device(pci_dev); | ||
176 | return rc; | ||
177 | } | ||
178 | |||
179 | static struct pci_device_id tpm_pci_tbl[] __devinitdata = { | ||
180 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_0)}, | ||
181 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_12)}, | ||
182 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0)}, | ||
183 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_12)}, | ||
184 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801EB_0)}, | ||
185 | {PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_8111_LPC)}, | ||
186 | {0,} | ||
187 | }; | ||
188 | |||
189 | MODULE_DEVICE_TABLE(pci, tpm_pci_tbl); | ||
190 | |||
191 | static struct pci_driver atmel_pci_driver = { | ||
192 | .name = "tpm_atmel", | ||
193 | .id_table = tpm_pci_tbl, | ||
194 | .probe = tpm_atml_init, | ||
195 | .remove = __devexit_p(tpm_remove), | ||
196 | .suspend = tpm_pm_suspend, | ||
197 | .resume = tpm_pm_resume, | ||
198 | }; | ||
199 | |||
200 | static int __init init_atmel(void) | ||
201 | { | ||
202 | return pci_register_driver(&atmel_pci_driver); | ||
203 | } | ||
204 | |||
205 | static void __exit cleanup_atmel(void) | ||
206 | { | ||
207 | pci_unregister_driver(&atmel_pci_driver); | ||
208 | } | ||
209 | |||
210 | module_init(init_atmel); | ||
211 | module_exit(cleanup_atmel); | ||
212 | |||
213 | MODULE_AUTHOR("Leendert van Doorn (leendert@watson.ibm.com)"); | ||
214 | MODULE_DESCRIPTION("TPM Driver"); | ||
215 | MODULE_VERSION("2.0"); | ||
216 | MODULE_LICENSE("GPL"); | ||
diff --git a/drivers/char/tpm/tpm_nsc.c b/drivers/char/tpm/tpm_nsc.c new file mode 100644 index 000000000000..9cce833a0923 --- /dev/null +++ b/drivers/char/tpm/tpm_nsc.c | |||
@@ -0,0 +1,373 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2004 IBM Corporation | ||
3 | * | ||
4 | * Authors: | ||
5 | * Leendert van Doorn <leendert@watson.ibm.com> | ||
6 | * Dave Safford <safford@watson.ibm.com> | ||
7 | * Reiner Sailer <sailer@watson.ibm.com> | ||
8 | * Kylene Hall <kjhall@us.ibm.com> | ||
9 | * | ||
10 | * Maintained by: <tpmdd_devel@lists.sourceforge.net> | ||
11 | * | ||
12 | * Device driver for TCG/TCPA TPM (trusted platform module). | ||
13 | * Specifications at www.trustedcomputinggroup.org | ||
14 | * | ||
15 | * This program is free software; you can redistribute it and/or | ||
16 | * modify it under the terms of the GNU General Public License as | ||
17 | * published by the Free Software Foundation, version 2 of the | ||
18 | * License. | ||
19 | * | ||
20 | */ | ||
21 | |||
22 | #include "tpm.h" | ||
23 | |||
24 | /* National definitions */ | ||
25 | #define TPM_NSC_BASE 0x360 | ||
26 | #define TPM_NSC_IRQ 0x07 | ||
27 | |||
28 | #define NSC_LDN_INDEX 0x07 | ||
29 | #define NSC_SID_INDEX 0x20 | ||
30 | #define NSC_LDC_INDEX 0x30 | ||
31 | #define NSC_DIO_INDEX 0x60 | ||
32 | #define NSC_CIO_INDEX 0x62 | ||
33 | #define NSC_IRQ_INDEX 0x70 | ||
34 | #define NSC_ITS_INDEX 0x71 | ||
35 | |||
36 | #define NSC_STATUS 0x01 | ||
37 | #define NSC_COMMAND 0x01 | ||
38 | #define NSC_DATA 0x00 | ||
39 | |||
40 | /* status bits */ | ||
41 | #define NSC_STATUS_OBF 0x01 /* output buffer full */ | ||
42 | #define NSC_STATUS_IBF 0x02 /* input buffer full */ | ||
43 | #define NSC_STATUS_F0 0x04 /* F0 */ | ||
44 | #define NSC_STATUS_A2 0x08 /* A2 */ | ||
45 | #define NSC_STATUS_RDY 0x10 /* ready to receive command */ | ||
46 | #define NSC_STATUS_IBR 0x20 /* ready to receive data */ | ||
47 | |||
48 | /* command bits */ | ||
49 | #define NSC_COMMAND_NORMAL 0x01 /* normal mode */ | ||
50 | #define NSC_COMMAND_EOC 0x03 | ||
51 | #define NSC_COMMAND_CANCEL 0x22 | ||
52 | |||
53 | /* | ||
54 | * Wait for a certain status to appear | ||
55 | */ | ||
56 | static int wait_for_stat(struct tpm_chip *chip, u8 mask, u8 val, u8 * data) | ||
57 | { | ||
58 | int expired = 0; | ||
59 | struct timer_list status_timer = | ||
60 | TIMER_INITIALIZER(tpm_time_expired, jiffies + 10 * HZ, | ||
61 | (unsigned long) &expired); | ||
62 | |||
63 | /* status immediately available check */ | ||
64 | *data = inb(chip->vendor->base + NSC_STATUS); | ||
65 | if ((*data & mask) == val) | ||
66 | return 0; | ||
67 | |||
68 | /* wait for status */ | ||
69 | add_timer(&status_timer); | ||
70 | do { | ||
71 | set_current_state(TASK_UNINTERRUPTIBLE); | ||
72 | schedule_timeout(TPM_TIMEOUT); | ||
73 | *data = inb(chip->vendor->base + 1); | ||
74 | if ((*data & mask) == val) { | ||
75 | del_singleshot_timer_sync(&status_timer); | ||
76 | return 0; | ||
77 | } | ||
78 | } | ||
79 | while (!expired); | ||
80 | |||
81 | return -EBUSY; | ||
82 | } | ||
83 | |||
84 | static int nsc_wait_for_ready(struct tpm_chip *chip) | ||
85 | { | ||
86 | int status; | ||
87 | int expired = 0; | ||
88 | struct timer_list status_timer = | ||
89 | TIMER_INITIALIZER(tpm_time_expired, jiffies + 100, | ||
90 | (unsigned long) &expired); | ||
91 | |||
92 | /* status immediately available check */ | ||
93 | status = inb(chip->vendor->base + NSC_STATUS); | ||
94 | if (status & NSC_STATUS_OBF) | ||
95 | status = inb(chip->vendor->base + NSC_DATA); | ||
96 | if (status & NSC_STATUS_RDY) | ||
97 | return 0; | ||
98 | |||
99 | /* wait for status */ | ||
100 | add_timer(&status_timer); | ||
101 | do { | ||
102 | set_current_state(TASK_UNINTERRUPTIBLE); | ||
103 | schedule_timeout(TPM_TIMEOUT); | ||
104 | status = inb(chip->vendor->base + NSC_STATUS); | ||
105 | if (status & NSC_STATUS_OBF) | ||
106 | status = inb(chip->vendor->base + NSC_DATA); | ||
107 | if (status & NSC_STATUS_RDY) { | ||
108 | del_singleshot_timer_sync(&status_timer); | ||
109 | return 0; | ||
110 | } | ||
111 | } | ||
112 | while (!expired); | ||
113 | |||
114 | dev_info(&chip->pci_dev->dev, "wait for ready failed\n"); | ||
115 | return -EBUSY; | ||
116 | } | ||
117 | |||
118 | |||
119 | static int tpm_nsc_recv(struct tpm_chip *chip, u8 * buf, size_t count) | ||
120 | { | ||
121 | u8 *buffer = buf; | ||
122 | u8 data, *p; | ||
123 | u32 size; | ||
124 | __be32 *native_size; | ||
125 | |||
126 | if (count < 6) | ||
127 | return -EIO; | ||
128 | |||
129 | if (wait_for_stat(chip, NSC_STATUS_F0, NSC_STATUS_F0, &data) < 0) { | ||
130 | dev_err(&chip->pci_dev->dev, "F0 timeout\n"); | ||
131 | return -EIO; | ||
132 | } | ||
133 | if ((data = | ||
134 | inb(chip->vendor->base + NSC_DATA)) != NSC_COMMAND_NORMAL) { | ||
135 | dev_err(&chip->pci_dev->dev, "not in normal mode (0x%x)\n", | ||
136 | data); | ||
137 | return -EIO; | ||
138 | } | ||
139 | |||
140 | /* read the whole packet */ | ||
141 | for (p = buffer; p < &buffer[count]; p++) { | ||
142 | if (wait_for_stat | ||
143 | (chip, NSC_STATUS_OBF, NSC_STATUS_OBF, &data) < 0) { | ||
144 | dev_err(&chip->pci_dev->dev, | ||
145 | "OBF timeout (while reading data)\n"); | ||
146 | return -EIO; | ||
147 | } | ||
148 | if (data & NSC_STATUS_F0) | ||
149 | break; | ||
150 | *p = inb(chip->vendor->base + NSC_DATA); | ||
151 | } | ||
152 | |||
153 | if ((data & NSC_STATUS_F0) == 0) { | ||
154 | dev_err(&chip->pci_dev->dev, "F0 not set\n"); | ||
155 | return -EIO; | ||
156 | } | ||
157 | if ((data = inb(chip->vendor->base + NSC_DATA)) != NSC_COMMAND_EOC) { | ||
158 | dev_err(&chip->pci_dev->dev, | ||
159 | "expected end of command(0x%x)\n", data); | ||
160 | return -EIO; | ||
161 | } | ||
162 | |||
163 | native_size = (__force __be32 *) (buf + 2); | ||
164 | size = be32_to_cpu(*native_size); | ||
165 | |||
166 | if (count < size) | ||
167 | return -EIO; | ||
168 | |||
169 | return size; | ||
170 | } | ||
171 | |||
172 | static int tpm_nsc_send(struct tpm_chip *chip, u8 * buf, size_t count) | ||
173 | { | ||
174 | u8 data; | ||
175 | int i; | ||
176 | |||
177 | /* | ||
178 | * If we hit the chip with back to back commands it locks up | ||
179 | * and never set IBF. Hitting it with this "hammer" seems to | ||
180 | * fix it. Not sure why this is needed, we followed the flow | ||
181 | * chart in the manual to the letter. | ||
182 | */ | ||
183 | outb(NSC_COMMAND_CANCEL, chip->vendor->base + NSC_COMMAND); | ||
184 | |||
185 | if (nsc_wait_for_ready(chip) != 0) | ||
186 | return -EIO; | ||
187 | |||
188 | if (wait_for_stat(chip, NSC_STATUS_IBF, 0, &data) < 0) { | ||
189 | dev_err(&chip->pci_dev->dev, "IBF timeout\n"); | ||
190 | return -EIO; | ||
191 | } | ||
192 | |||
193 | outb(NSC_COMMAND_NORMAL, chip->vendor->base + NSC_COMMAND); | ||
194 | if (wait_for_stat(chip, NSC_STATUS_IBR, NSC_STATUS_IBR, &data) < 0) { | ||
195 | dev_err(&chip->pci_dev->dev, "IBR timeout\n"); | ||
196 | return -EIO; | ||
197 | } | ||
198 | |||
199 | for (i = 0; i < count; i++) { | ||
200 | if (wait_for_stat(chip, NSC_STATUS_IBF, 0, &data) < 0) { | ||
201 | dev_err(&chip->pci_dev->dev, | ||
202 | "IBF timeout (while writing data)\n"); | ||
203 | return -EIO; | ||
204 | } | ||
205 | outb(buf[i], chip->vendor->base + NSC_DATA); | ||
206 | } | ||
207 | |||
208 | if (wait_for_stat(chip, NSC_STATUS_IBF, 0, &data) < 0) { | ||
209 | dev_err(&chip->pci_dev->dev, "IBF timeout\n"); | ||
210 | return -EIO; | ||
211 | } | ||
212 | outb(NSC_COMMAND_EOC, chip->vendor->base + NSC_COMMAND); | ||
213 | |||
214 | return count; | ||
215 | } | ||
216 | |||
217 | static void tpm_nsc_cancel(struct tpm_chip *chip) | ||
218 | { | ||
219 | outb(NSC_COMMAND_CANCEL, chip->vendor->base + NSC_COMMAND); | ||
220 | } | ||
221 | |||
222 | static struct file_operations nsc_ops = { | ||
223 | .owner = THIS_MODULE, | ||
224 | .llseek = no_llseek, | ||
225 | .open = tpm_open, | ||
226 | .read = tpm_read, | ||
227 | .write = tpm_write, | ||
228 | .release = tpm_release, | ||
229 | }; | ||
230 | |||
231 | static struct tpm_vendor_specific tpm_nsc = { | ||
232 | .recv = tpm_nsc_recv, | ||
233 | .send = tpm_nsc_send, | ||
234 | .cancel = tpm_nsc_cancel, | ||
235 | .req_complete_mask = NSC_STATUS_OBF, | ||
236 | .req_complete_val = NSC_STATUS_OBF, | ||
237 | .base = TPM_NSC_BASE, | ||
238 | .miscdev = { .fops = &nsc_ops, }, | ||
239 | |||
240 | }; | ||
241 | |||
242 | static int __devinit tpm_nsc_init(struct pci_dev *pci_dev, | ||
243 | const struct pci_device_id *pci_id) | ||
244 | { | ||
245 | int rc = 0; | ||
246 | |||
247 | if (pci_enable_device(pci_dev)) | ||
248 | return -EIO; | ||
249 | |||
250 | if (tpm_lpc_bus_init(pci_dev, TPM_NSC_BASE)) { | ||
251 | rc = -ENODEV; | ||
252 | goto out_err; | ||
253 | } | ||
254 | |||
255 | /* verify that it is a National part (SID) */ | ||
256 | if (tpm_read_index(NSC_SID_INDEX) != 0xEF) { | ||
257 | rc = -ENODEV; | ||
258 | goto out_err; | ||
259 | } | ||
260 | |||
261 | dev_dbg(&pci_dev->dev, "NSC TPM detected\n"); | ||
262 | dev_dbg(&pci_dev->dev, | ||
263 | "NSC LDN 0x%x, SID 0x%x, SRID 0x%x\n", | ||
264 | tpm_read_index(0x07), tpm_read_index(0x20), | ||
265 | tpm_read_index(0x27)); | ||
266 | dev_dbg(&pci_dev->dev, | ||
267 | "NSC SIOCF1 0x%x SIOCF5 0x%x SIOCF6 0x%x SIOCF8 0x%x\n", | ||
268 | tpm_read_index(0x21), tpm_read_index(0x25), | ||
269 | tpm_read_index(0x26), tpm_read_index(0x28)); | ||
270 | dev_dbg(&pci_dev->dev, "NSC IO Base0 0x%x\n", | ||
271 | (tpm_read_index(0x60) << 8) | tpm_read_index(0x61)); | ||
272 | dev_dbg(&pci_dev->dev, "NSC IO Base1 0x%x\n", | ||
273 | (tpm_read_index(0x62) << 8) | tpm_read_index(0x63)); | ||
274 | dev_dbg(&pci_dev->dev, "NSC Interrupt number and wakeup 0x%x\n", | ||
275 | tpm_read_index(0x70)); | ||
276 | dev_dbg(&pci_dev->dev, "NSC IRQ type select 0x%x\n", | ||
277 | tpm_read_index(0x71)); | ||
278 | dev_dbg(&pci_dev->dev, | ||
279 | "NSC DMA channel select0 0x%x, select1 0x%x\n", | ||
280 | tpm_read_index(0x74), tpm_read_index(0x75)); | ||
281 | dev_dbg(&pci_dev->dev, | ||
282 | "NSC Config " | ||
283 | "0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n", | ||
284 | tpm_read_index(0xF0), tpm_read_index(0xF1), | ||
285 | tpm_read_index(0xF2), tpm_read_index(0xF3), | ||
286 | tpm_read_index(0xF4), tpm_read_index(0xF5), | ||
287 | tpm_read_index(0xF6), tpm_read_index(0xF7), | ||
288 | tpm_read_index(0xF8), tpm_read_index(0xF9)); | ||
289 | |||
290 | dev_info(&pci_dev->dev, | ||
291 | "NSC PC21100 TPM revision %d\n", | ||
292 | tpm_read_index(0x27) & 0x1F); | ||
293 | |||
294 | if (tpm_read_index(NSC_LDC_INDEX) == 0) | ||
295 | dev_info(&pci_dev->dev, ": NSC TPM not active\n"); | ||
296 | |||
297 | /* select PM channel 1 */ | ||
298 | tpm_write_index(NSC_LDN_INDEX, 0x12); | ||
299 | tpm_read_index(NSC_LDN_INDEX); | ||
300 | |||
301 | /* disable the DPM module */ | ||
302 | tpm_write_index(NSC_LDC_INDEX, 0); | ||
303 | tpm_read_index(NSC_LDC_INDEX); | ||
304 | |||
305 | /* set the data register base addresses */ | ||
306 | tpm_write_index(NSC_DIO_INDEX, TPM_NSC_BASE >> 8); | ||
307 | tpm_write_index(NSC_DIO_INDEX + 1, TPM_NSC_BASE); | ||
308 | tpm_read_index(NSC_DIO_INDEX); | ||
309 | tpm_read_index(NSC_DIO_INDEX + 1); | ||
310 | |||
311 | /* set the command register base addresses */ | ||
312 | tpm_write_index(NSC_CIO_INDEX, (TPM_NSC_BASE + 1) >> 8); | ||
313 | tpm_write_index(NSC_CIO_INDEX + 1, (TPM_NSC_BASE + 1)); | ||
314 | tpm_read_index(NSC_DIO_INDEX); | ||
315 | tpm_read_index(NSC_DIO_INDEX + 1); | ||
316 | |||
317 | /* set the interrupt number to be used for the host interface */ | ||
318 | tpm_write_index(NSC_IRQ_INDEX, TPM_NSC_IRQ); | ||
319 | tpm_write_index(NSC_ITS_INDEX, 0x00); | ||
320 | tpm_read_index(NSC_IRQ_INDEX); | ||
321 | |||
322 | /* enable the DPM module */ | ||
323 | tpm_write_index(NSC_LDC_INDEX, 0x01); | ||
324 | tpm_read_index(NSC_LDC_INDEX); | ||
325 | |||
326 | if ((rc = tpm_register_hardware(pci_dev, &tpm_nsc)) < 0) | ||
327 | goto out_err; | ||
328 | |||
329 | return 0; | ||
330 | |||
331 | out_err: | ||
332 | pci_disable_device(pci_dev); | ||
333 | return rc; | ||
334 | } | ||
335 | |||
336 | static struct pci_device_id tpm_pci_tbl[] __devinitdata = { | ||
337 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_0)}, | ||
338 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_12)}, | ||
339 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0)}, | ||
340 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_12)}, | ||
341 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801EB_0)}, | ||
342 | {PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_8111_LPC)}, | ||
343 | {0,} | ||
344 | }; | ||
345 | |||
346 | MODULE_DEVICE_TABLE(pci, tpm_pci_tbl); | ||
347 | |||
348 | static struct pci_driver nsc_pci_driver = { | ||
349 | .name = "tpm_nsc", | ||
350 | .id_table = tpm_pci_tbl, | ||
351 | .probe = tpm_nsc_init, | ||
352 | .remove = __devexit_p(tpm_remove), | ||
353 | .suspend = tpm_pm_suspend, | ||
354 | .resume = tpm_pm_resume, | ||
355 | }; | ||
356 | |||
357 | static int __init init_nsc(void) | ||
358 | { | ||
359 | return pci_register_driver(&nsc_pci_driver); | ||
360 | } | ||
361 | |||
362 | static void __exit cleanup_nsc(void) | ||
363 | { | ||
364 | pci_unregister_driver(&nsc_pci_driver); | ||
365 | } | ||
366 | |||
367 | module_init(init_nsc); | ||
368 | module_exit(cleanup_nsc); | ||
369 | |||
370 | MODULE_AUTHOR("Leendert van Doorn (leendert@watson.ibm.com)"); | ||
371 | MODULE_DESCRIPTION("TPM Driver"); | ||
372 | MODULE_VERSION("2.0"); | ||
373 | MODULE_LICENSE("GPL"); | ||