Creation of Cybook 2416 (actually Gen4) repository
This commit is contained in:
62
drivers/char/tpm/Kconfig
Normal file
62
drivers/char/tpm/Kconfig
Normal file
@@ -0,0 +1,62 @@
|
||||
#
|
||||
# TPM device configuration
|
||||
#
|
||||
|
||||
menu "TPM devices"
|
||||
|
||||
config TCG_TPM
|
||||
tristate "TPM Hardware Support"
|
||||
depends on EXPERIMENTAL
|
||||
---help---
|
||||
If you have a TPM security chip in your system, which
|
||||
implements the Trusted Computing Group's specification,
|
||||
say Yes and it will be accessible from within Linux. For
|
||||
more information see <http://www.trustedcomputinggroup.org>.
|
||||
An implementation of the Trusted Software Stack (TSS), the
|
||||
userspace enablement piece of the specification, can be
|
||||
obtained at: <http://sourceforge.net/projects/trousers>. To
|
||||
compile this driver as a module, choose M here; the module
|
||||
will be called tpm. If unsure, say N.
|
||||
Note: For more TPM drivers enable CONFIG_PNP, CONFIG_ACPI
|
||||
and CONFIG_PNPACPI.
|
||||
|
||||
config TCG_TIS
|
||||
tristate "TPM Interface Specification 1.2 Interface"
|
||||
depends on TCG_TPM && PNPACPI
|
||||
---help---
|
||||
If you have a TPM security chip that is compliant with the
|
||||
TCG TIS 1.2 TPM specification say Yes and it will be accessible
|
||||
from within Linux. To compile this driver as a module, choose
|
||||
M here; the module will be called tpm_tis.
|
||||
|
||||
config TCG_NSC
|
||||
tristate "National Semiconductor TPM Interface"
|
||||
depends on TCG_TPM && PNPACPI
|
||||
---help---
|
||||
If you have a TPM security chip from National Semicondutor
|
||||
say Yes and it will be accessible from within Linux. To
|
||||
compile this driver as a module, choose M here; the module
|
||||
will be called tpm_nsc.
|
||||
|
||||
config TCG_ATMEL
|
||||
tristate "Atmel TPM Interface"
|
||||
depends on TCG_TPM
|
||||
---help---
|
||||
If you have a TPM security chip from Atmel say Yes and it
|
||||
will be accessible from within Linux. To compile this driver
|
||||
as a module, choose M here; the module will be called tpm_atmel.
|
||||
|
||||
config TCG_INFINEON
|
||||
tristate "Infineon Technologies TPM Interface"
|
||||
depends on TCG_TPM && PNPACPI
|
||||
---help---
|
||||
If you have a TPM security chip from Infineon Technologies
|
||||
(either SLD 9630 TT 1.1 or SLB 9635 TT 1.2) say Yes and it
|
||||
will be accessible from within Linux.
|
||||
To compile this driver as a module, choose M here; the module
|
||||
will be called tpm_infineon.
|
||||
Further information on this driver and the supported hardware
|
||||
can be found at http://www.prosec.rub.de/tpm
|
||||
|
||||
endmenu
|
||||
|
||||
11
drivers/char/tpm/Makefile
Normal file
11
drivers/char/tpm/Makefile
Normal file
@@ -0,0 +1,11 @@
|
||||
#
|
||||
# Makefile for the kernel tpm device drivers.
|
||||
#
|
||||
obj-$(CONFIG_TCG_TPM) += tpm.o
|
||||
ifdef CONFIG_ACPI
|
||||
obj-$(CONFIG_TCG_TPM) += tpm_bios.o
|
||||
endif
|
||||
obj-$(CONFIG_TCG_TIS) += tpm_tis.o
|
||||
obj-$(CONFIG_TCG_NSC) += tpm_nsc.o
|
||||
obj-$(CONFIG_TCG_ATMEL) += tpm_atmel.o
|
||||
obj-$(CONFIG_TCG_INFINEON) += tpm_infineon.o
|
||||
1173
drivers/char/tpm/tpm.c
Normal file
1173
drivers/char/tpm/tpm.c
Normal file
File diff suppressed because it is too large
Load Diff
151
drivers/char/tpm/tpm.h
Normal file
151
drivers/char/tpm/tpm.h
Normal file
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright (C) 2004 IBM Corporation
|
||||
*
|
||||
* Authors:
|
||||
* Leendert van Doorn <leendert@watson.ibm.com>
|
||||
* Dave Safford <safford@watson.ibm.com>
|
||||
* Reiner Sailer <sailer@watson.ibm.com>
|
||||
* Kylene Hall <kjhall@us.ibm.com>
|
||||
*
|
||||
* Maintained by: <tpmdd_devel@lists.sourceforge.net>
|
||||
*
|
||||
* Device driver for TCG/TCPA TPM (trusted platform module).
|
||||
* Specifications at www.trustedcomputinggroup.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/io.h>
|
||||
|
||||
enum tpm_timeout {
|
||||
TPM_TIMEOUT = 5, /* msecs */
|
||||
};
|
||||
|
||||
/* TPM addresses */
|
||||
enum tpm_addr {
|
||||
TPM_SUPERIO_ADDR = 0x2E,
|
||||
TPM_ADDR = 0x4E,
|
||||
};
|
||||
|
||||
extern ssize_t tpm_show_pubek(struct device *, struct device_attribute *attr,
|
||||
char *);
|
||||
extern ssize_t tpm_show_pcrs(struct device *, struct device_attribute *attr,
|
||||
char *);
|
||||
extern ssize_t tpm_show_caps(struct device *, struct device_attribute *attr,
|
||||
char *);
|
||||
extern ssize_t tpm_show_caps_1_2(struct device *, struct device_attribute *attr,
|
||||
char *);
|
||||
extern ssize_t tpm_store_cancel(struct device *, struct device_attribute *attr,
|
||||
const char *, size_t);
|
||||
extern ssize_t tpm_show_enabled(struct device *, struct device_attribute *attr,
|
||||
char *);
|
||||
extern ssize_t tpm_show_active(struct device *, struct device_attribute *attr,
|
||||
char *);
|
||||
extern ssize_t tpm_show_owned(struct device *, struct device_attribute *attr,
|
||||
char *);
|
||||
extern ssize_t tpm_show_temp_deactivated(struct device *,
|
||||
struct device_attribute *attr, char *);
|
||||
|
||||
struct tpm_chip;
|
||||
|
||||
struct tpm_vendor_specific {
|
||||
const u8 req_complete_mask;
|
||||
const u8 req_complete_val;
|
||||
const u8 req_canceled;
|
||||
void __iomem *iobase; /* ioremapped address */
|
||||
unsigned long base; /* TPM base address */
|
||||
|
||||
int irq;
|
||||
|
||||
int region_size;
|
||||
int have_region;
|
||||
|
||||
int (*recv) (struct tpm_chip *, u8 *, size_t);
|
||||
int (*send) (struct tpm_chip *, u8 *, size_t);
|
||||
void (*cancel) (struct tpm_chip *);
|
||||
u8 (*status) (struct tpm_chip *);
|
||||
struct miscdevice miscdev;
|
||||
struct attribute_group *attr_group;
|
||||
struct list_head list;
|
||||
int locality;
|
||||
unsigned long timeout_a, timeout_b, timeout_c, timeout_d; /* jiffies */
|
||||
unsigned long duration[3]; /* jiffies */
|
||||
|
||||
wait_queue_head_t read_queue;
|
||||
wait_queue_head_t int_queue;
|
||||
};
|
||||
|
||||
struct tpm_chip {
|
||||
struct device *dev; /* Device stuff */
|
||||
|
||||
int dev_num; /* /dev/tpm# */
|
||||
int num_opens; /* only one allowed */
|
||||
int time_expired;
|
||||
|
||||
/* Data passed to and from the tpm via the read/write calls */
|
||||
u8 *data_buffer;
|
||||
atomic_t data_pending;
|
||||
struct semaphore buffer_mutex;
|
||||
|
||||
struct timer_list user_read_timer; /* user needs to claim result */
|
||||
struct work_struct work;
|
||||
struct semaphore tpm_mutex; /* tpm is processing */
|
||||
|
||||
struct tpm_vendor_specific vendor;
|
||||
|
||||
struct dentry **bios_dir;
|
||||
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
#define to_tpm_chip(n) container_of(n, struct tpm_chip, vendor)
|
||||
|
||||
static inline int tpm_read_index(int base, int index)
|
||||
{
|
||||
outb(index, base);
|
||||
return inb(base+1) & 0xFF;
|
||||
}
|
||||
|
||||
static inline void tpm_write_index(int base, int index, int value)
|
||||
{
|
||||
outb(index, base);
|
||||
outb(value & 0xFF, base+1);
|
||||
}
|
||||
|
||||
extern void tpm_get_timeouts(struct tpm_chip *);
|
||||
extern void tpm_gen_interrupt(struct tpm_chip *);
|
||||
extern void tpm_continue_selftest(struct tpm_chip *);
|
||||
extern unsigned long tpm_calc_ordinal_duration(struct tpm_chip *, u32);
|
||||
extern struct tpm_chip* tpm_register_hardware(struct device *,
|
||||
const struct tpm_vendor_specific *);
|
||||
extern int tpm_open(struct inode *, struct file *);
|
||||
extern int tpm_release(struct inode *, struct file *);
|
||||
extern ssize_t tpm_write(struct file *, const char __user *, size_t,
|
||||
loff_t *);
|
||||
extern ssize_t tpm_read(struct file *, char __user *, size_t, loff_t *);
|
||||
extern void tpm_remove_hardware(struct device *);
|
||||
extern int tpm_pm_suspend(struct device *, pm_message_t);
|
||||
extern int tpm_pm_resume(struct device *);
|
||||
|
||||
#ifdef CONFIG_ACPI
|
||||
extern struct dentry ** tpm_bios_log_setup(char *);
|
||||
extern void tpm_bios_log_teardown(struct dentry **);
|
||||
#else
|
||||
static inline struct dentry ** tpm_bios_log_setup(char *name)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
static inline void tpm_bios_log_teardown(struct dentry **dir)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
242
drivers/char/tpm/tpm_atmel.c
Normal file
242
drivers/char/tpm/tpm_atmel.c
Normal file
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* Copyright (C) 2004 IBM Corporation
|
||||
*
|
||||
* Authors:
|
||||
* Leendert van Doorn <leendert@watson.ibm.com>
|
||||
* Dave Safford <safford@watson.ibm.com>
|
||||
* Reiner Sailer <sailer@watson.ibm.com>
|
||||
* Kylene Hall <kjhall@us.ibm.com>
|
||||
*
|
||||
* Maintained by: <tpmdd_devel@lists.sourceforge.net>
|
||||
*
|
||||
* Device driver for TCG/TCPA TPM (trusted platform module).
|
||||
* Specifications at www.trustedcomputinggroup.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "tpm.h"
|
||||
#include "tpm_atmel.h"
|
||||
|
||||
/* write status bits */
|
||||
enum tpm_atmel_write_status {
|
||||
ATML_STATUS_ABORT = 0x01,
|
||||
ATML_STATUS_LASTBYTE = 0x04
|
||||
};
|
||||
/* read status bits */
|
||||
enum tpm_atmel_read_status {
|
||||
ATML_STATUS_BUSY = 0x01,
|
||||
ATML_STATUS_DATA_AVAIL = 0x02,
|
||||
ATML_STATUS_REWRITE = 0x04,
|
||||
ATML_STATUS_READY = 0x08
|
||||
};
|
||||
|
||||
static int tpm_atml_recv(struct tpm_chip *chip, u8 *buf, size_t count)
|
||||
{
|
||||
u8 status, *hdr = buf;
|
||||
u32 size;
|
||||
int i;
|
||||
__be32 *native_size;
|
||||
|
||||
/* start reading header */
|
||||
if (count < 6)
|
||||
return -EIO;
|
||||
|
||||
for (i = 0; i < 6; i++) {
|
||||
status = ioread8(chip->vendor.iobase + 1);
|
||||
if ((status & ATML_STATUS_DATA_AVAIL) == 0) {
|
||||
dev_err(chip->dev, "error reading header\n");
|
||||
return -EIO;
|
||||
}
|
||||
*buf++ = ioread8(chip->vendor.iobase);
|
||||
}
|
||||
|
||||
/* size of the data received */
|
||||
native_size = (__force __be32 *) (hdr + 2);
|
||||
size = be32_to_cpu(*native_size);
|
||||
|
||||
if (count < size) {
|
||||
dev_err(chip->dev,
|
||||
"Recv size(%d) less than available space\n", size);
|
||||
for (; i < size; i++) { /* clear the waiting data anyway */
|
||||
status = ioread8(chip->vendor.iobase + 1);
|
||||
if ((status & ATML_STATUS_DATA_AVAIL) == 0) {
|
||||
dev_err(chip->dev, "error reading data\n");
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* read all the data available */
|
||||
for (; i < size; i++) {
|
||||
status = ioread8(chip->vendor.iobase + 1);
|
||||
if ((status & ATML_STATUS_DATA_AVAIL) == 0) {
|
||||
dev_err(chip->dev, "error reading data\n");
|
||||
return -EIO;
|
||||
}
|
||||
*buf++ = ioread8(chip->vendor.iobase);
|
||||
}
|
||||
|
||||
/* make sure data available is gone */
|
||||
status = ioread8(chip->vendor.iobase + 1);
|
||||
|
||||
if (status & ATML_STATUS_DATA_AVAIL) {
|
||||
dev_err(chip->dev, "data available is stuck\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static int tpm_atml_send(struct tpm_chip *chip, u8 *buf, size_t count)
|
||||
{
|
||||
int i;
|
||||
|
||||
dev_dbg(chip->dev, "tpm_atml_send:\n");
|
||||
for (i = 0; i < count; i++) {
|
||||
dev_dbg(chip->dev, "%d 0x%x(%d)\n", i, buf[i], buf[i]);
|
||||
iowrite8(buf[i], chip->vendor.iobase);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static void tpm_atml_cancel(struct tpm_chip *chip)
|
||||
{
|
||||
iowrite8(ATML_STATUS_ABORT, chip->vendor.iobase + 1);
|
||||
}
|
||||
|
||||
static u8 tpm_atml_status(struct tpm_chip *chip)
|
||||
{
|
||||
return ioread8(chip->vendor.iobase + 1);
|
||||
}
|
||||
|
||||
static const struct file_operations atmel_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.llseek = no_llseek,
|
||||
.open = tpm_open,
|
||||
.read = tpm_read,
|
||||
.write = tpm_write,
|
||||
.release = tpm_release,
|
||||
};
|
||||
|
||||
static DEVICE_ATTR(pubek, S_IRUGO, tpm_show_pubek, NULL);
|
||||
static DEVICE_ATTR(pcrs, S_IRUGO, tpm_show_pcrs, NULL);
|
||||
static DEVICE_ATTR(caps, S_IRUGO, tpm_show_caps, NULL);
|
||||
static DEVICE_ATTR(cancel, S_IWUSR |S_IWGRP, NULL, tpm_store_cancel);
|
||||
|
||||
static struct attribute* atmel_attrs[] = {
|
||||
&dev_attr_pubek.attr,
|
||||
&dev_attr_pcrs.attr,
|
||||
&dev_attr_caps.attr,
|
||||
&dev_attr_cancel.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct attribute_group atmel_attr_grp = { .attrs = atmel_attrs };
|
||||
|
||||
static const struct tpm_vendor_specific tpm_atmel = {
|
||||
.recv = tpm_atml_recv,
|
||||
.send = tpm_atml_send,
|
||||
.cancel = tpm_atml_cancel,
|
||||
.status = tpm_atml_status,
|
||||
.req_complete_mask = ATML_STATUS_BUSY | ATML_STATUS_DATA_AVAIL,
|
||||
.req_complete_val = ATML_STATUS_DATA_AVAIL,
|
||||
.req_canceled = ATML_STATUS_READY,
|
||||
.attr_group = &atmel_attr_grp,
|
||||
.miscdev = { .fops = &atmel_ops, },
|
||||
};
|
||||
|
||||
static struct platform_device *pdev;
|
||||
|
||||
static void atml_plat_remove(void)
|
||||
{
|
||||
struct tpm_chip *chip = dev_get_drvdata(&pdev->dev);
|
||||
|
||||
if (chip) {
|
||||
if (chip->vendor.have_region)
|
||||
atmel_release_region(chip->vendor.base,
|
||||
chip->vendor.region_size);
|
||||
atmel_put_base_addr(chip->vendor.iobase);
|
||||
tpm_remove_hardware(chip->dev);
|
||||
platform_device_unregister(pdev);
|
||||
}
|
||||
}
|
||||
|
||||
static struct device_driver atml_drv = {
|
||||
.name = "tpm_atmel",
|
||||
.bus = &platform_bus_type,
|
||||
.owner = THIS_MODULE,
|
||||
.suspend = tpm_pm_suspend,
|
||||
.resume = tpm_pm_resume,
|
||||
};
|
||||
|
||||
static int __init init_atmel(void)
|
||||
{
|
||||
int rc = 0;
|
||||
void __iomem *iobase = NULL;
|
||||
int have_region, region_size;
|
||||
unsigned long base;
|
||||
struct tpm_chip *chip;
|
||||
|
||||
rc = driver_register(&atml_drv);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
if ((iobase = atmel_get_base_addr(&base, ®ion_size)) == NULL) {
|
||||
rc = -ENODEV;
|
||||
goto err_unreg_drv;
|
||||
}
|
||||
|
||||
have_region =
|
||||
(atmel_request_region
|
||||
(tpm_atmel.base, region_size, "tpm_atmel0") == NULL) ? 0 : 1;
|
||||
|
||||
pdev = platform_device_register_simple("tpm_atmel", -1, NULL, 0);
|
||||
if (IS_ERR(pdev)) {
|
||||
rc = PTR_ERR(pdev);
|
||||
goto err_rel_reg;
|
||||
}
|
||||
|
||||
if (!(chip = tpm_register_hardware(&pdev->dev, &tpm_atmel))) {
|
||||
rc = -ENODEV;
|
||||
goto err_unreg_dev;
|
||||
}
|
||||
|
||||
chip->vendor.iobase = iobase;
|
||||
chip->vendor.base = base;
|
||||
chip->vendor.have_region = have_region;
|
||||
chip->vendor.region_size = region_size;
|
||||
|
||||
return 0;
|
||||
|
||||
err_unreg_dev:
|
||||
platform_device_unregister(pdev);
|
||||
err_rel_reg:
|
||||
atmel_put_base_addr(iobase);
|
||||
if (have_region)
|
||||
atmel_release_region(base,
|
||||
region_size);
|
||||
err_unreg_drv:
|
||||
driver_unregister(&atml_drv);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void __exit cleanup_atmel(void)
|
||||
{
|
||||
driver_unregister(&atml_drv);
|
||||
atml_plat_remove();
|
||||
}
|
||||
|
||||
module_init(init_atmel);
|
||||
module_exit(cleanup_atmel);
|
||||
|
||||
MODULE_AUTHOR("Leendert van Doorn (leendert@watson.ibm.com)");
|
||||
MODULE_DESCRIPTION("TPM Driver");
|
||||
MODULE_VERSION("2.0");
|
||||
MODULE_LICENSE("GPL");
|
||||
128
drivers/char/tpm/tpm_atmel.h
Normal file
128
drivers/char/tpm/tpm_atmel.h
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright (C) 2005 IBM Corporation
|
||||
*
|
||||
* Authors:
|
||||
* Kylene Hall <kjhall@us.ibm.com>
|
||||
*
|
||||
* Maintained by: <tpmdd_devel@lists.sourceforge.net>
|
||||
*
|
||||
* Device driver for TCG/TCPA TPM (trusted platform module).
|
||||
* Specifications at www.trustedcomputinggroup.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*
|
||||
* These difference are required on power because the device must be
|
||||
* discovered through the device tree and iomap must be used to get
|
||||
* around the need for holes in the io_page_mask. This does not happen
|
||||
* automatically because the tpm is not a normal pci device and lives
|
||||
* under the root node.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_PPC64
|
||||
#define atmel_getb(chip, offset) readb(chip->vendor->iobase + offset);
|
||||
#define atmel_putb(val, chip, offset) writeb(val, chip->vendor->iobase + offset)
|
||||
#define atmel_request_region request_mem_region
|
||||
#define atmel_release_region release_mem_region
|
||||
|
||||
static inline void atmel_put_base_addr(void __iomem *iobase)
|
||||
{
|
||||
iounmap(iobase);
|
||||
}
|
||||
|
||||
static void __iomem * atmel_get_base_addr(unsigned long *base, int *region_size)
|
||||
{
|
||||
struct device_node *dn;
|
||||
unsigned long address, size;
|
||||
const unsigned int *reg;
|
||||
int reglen;
|
||||
int naddrc;
|
||||
int nsizec;
|
||||
|
||||
dn = of_find_node_by_name(NULL, "tpm");
|
||||
|
||||
if (!dn)
|
||||
return NULL;
|
||||
|
||||
if (!device_is_compatible(dn, "AT97SC3201")) {
|
||||
of_node_put(dn);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
reg = get_property(dn, "reg", ®len);
|
||||
naddrc = prom_n_addr_cells(dn);
|
||||
nsizec = prom_n_size_cells(dn);
|
||||
|
||||
of_node_put(dn);
|
||||
|
||||
|
||||
if (naddrc == 2)
|
||||
address = ((unsigned long) reg[0] << 32) | reg[1];
|
||||
else
|
||||
address = reg[0];
|
||||
|
||||
if (nsizec == 2)
|
||||
size =
|
||||
((unsigned long) reg[naddrc] << 32) | reg[naddrc + 1];
|
||||
else
|
||||
size = reg[naddrc];
|
||||
|
||||
*base = address;
|
||||
*region_size = size;
|
||||
return ioremap(*base, *region_size);
|
||||
}
|
||||
#else
|
||||
#define atmel_getb(chip, offset) inb(chip->vendor->base + offset)
|
||||
#define atmel_putb(val, chip, offset) outb(val, chip->vendor->base + offset)
|
||||
#define atmel_request_region request_region
|
||||
#define atmel_release_region release_region
|
||||
/* Atmel definitions */
|
||||
enum tpm_atmel_addr {
|
||||
TPM_ATMEL_BASE_ADDR_LO = 0x08,
|
||||
TPM_ATMEL_BASE_ADDR_HI = 0x09
|
||||
};
|
||||
|
||||
/* Verify this is a 1.1 Atmel TPM */
|
||||
static int atmel_verify_tpm11(void)
|
||||
{
|
||||
|
||||
/* verify that it is an Atmel part */
|
||||
if (tpm_read_index(TPM_ADDR, 4) != 'A' ||
|
||||
tpm_read_index(TPM_ADDR, 5) != 'T' ||
|
||||
tpm_read_index(TPM_ADDR, 6) != 'M' ||
|
||||
tpm_read_index(TPM_ADDR, 7) != 'L')
|
||||
return 1;
|
||||
|
||||
/* query chip for its version number */
|
||||
if (tpm_read_index(TPM_ADDR, 0x00) != 1 ||
|
||||
tpm_read_index(TPM_ADDR, 0x01) != 1)
|
||||
return 1;
|
||||
|
||||
/* This is an atmel supported part */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void atmel_put_base_addr(void __iomem *iobase)
|
||||
{
|
||||
}
|
||||
|
||||
/* Determine where to talk to device */
|
||||
static void __iomem * atmel_get_base_addr(unsigned long *base, int *region_size)
|
||||
{
|
||||
int lo, hi;
|
||||
|
||||
if (atmel_verify_tpm11() != 0)
|
||||
return NULL;
|
||||
|
||||
lo = tpm_read_index(TPM_ADDR, TPM_ATMEL_BASE_ADDR_LO);
|
||||
hi = tpm_read_index(TPM_ADDR, TPM_ATMEL_BASE_ADDR_HI);
|
||||
|
||||
*base = (hi << 8) | lo;
|
||||
*region_size = 2;
|
||||
|
||||
return ioport_map(*base, *region_size);
|
||||
}
|
||||
#endif
|
||||
544
drivers/char/tpm/tpm_bios.c
Normal file
544
drivers/char/tpm/tpm_bios.c
Normal file
@@ -0,0 +1,544 @@
|
||||
/*
|
||||
* Copyright (C) 2005 IBM Corporation
|
||||
*
|
||||
* Authors:
|
||||
* Seiji Munetoh <munetoh@jp.ibm.com>
|
||||
* Stefan Berger <stefanb@us.ibm.com>
|
||||
* Reiner Sailer <sailer@watson.ibm.com>
|
||||
* Kylene Hall <kjhall@us.ibm.com>
|
||||
*
|
||||
* Access to the eventlog extended by the TCG BIOS of PC platform
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version
|
||||
* 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/security.h>
|
||||
#include <linux/module.h>
|
||||
#include <acpi/acpi.h>
|
||||
#include <acpi/actypes.h>
|
||||
#include <acpi/actbl.h>
|
||||
#include "tpm.h"
|
||||
|
||||
#define TCG_EVENT_NAME_LEN_MAX 255
|
||||
#define MAX_TEXT_EVENT 1000 /* Max event string length */
|
||||
#define ACPI_TCPA_SIG "TCPA" /* 0x41504354 /'TCPA' */
|
||||
|
||||
enum bios_platform_class {
|
||||
BIOS_CLIENT = 0x00,
|
||||
BIOS_SERVER = 0x01,
|
||||
};
|
||||
|
||||
struct tpm_bios_log {
|
||||
void *bios_event_log;
|
||||
void *bios_event_log_end;
|
||||
};
|
||||
|
||||
struct acpi_tcpa {
|
||||
struct acpi_table_header hdr;
|
||||
u16 platform_class;
|
||||
union {
|
||||
struct client_hdr {
|
||||
u32 log_max_len __attribute__ ((packed));
|
||||
u64 log_start_addr __attribute__ ((packed));
|
||||
} client;
|
||||
struct server_hdr {
|
||||
u16 reserved;
|
||||
u64 log_max_len __attribute__ ((packed));
|
||||
u64 log_start_addr __attribute__ ((packed));
|
||||
} server;
|
||||
};
|
||||
};
|
||||
|
||||
struct tcpa_event {
|
||||
u32 pcr_index;
|
||||
u32 event_type;
|
||||
u8 pcr_value[20]; /* SHA1 */
|
||||
u32 event_size;
|
||||
u8 event_data[0];
|
||||
};
|
||||
|
||||
enum tcpa_event_types {
|
||||
PREBOOT = 0,
|
||||
POST_CODE,
|
||||
UNUSED,
|
||||
NO_ACTION,
|
||||
SEPARATOR,
|
||||
ACTION,
|
||||
EVENT_TAG,
|
||||
SCRTM_CONTENTS,
|
||||
SCRTM_VERSION,
|
||||
CPU_MICROCODE,
|
||||
PLATFORM_CONFIG_FLAGS,
|
||||
TABLE_OF_DEVICES,
|
||||
COMPACT_HASH,
|
||||
IPL,
|
||||
IPL_PARTITION_DATA,
|
||||
NONHOST_CODE,
|
||||
NONHOST_CONFIG,
|
||||
NONHOST_INFO,
|
||||
};
|
||||
|
||||
static const char* tcpa_event_type_strings[] = {
|
||||
"PREBOOT",
|
||||
"POST CODE",
|
||||
"",
|
||||
"NO ACTION",
|
||||
"SEPARATOR",
|
||||
"ACTION",
|
||||
"EVENT TAG",
|
||||
"S-CRTM Contents",
|
||||
"S-CRTM Version",
|
||||
"CPU Microcode",
|
||||
"Platform Config Flags",
|
||||
"Table of Devices",
|
||||
"Compact Hash",
|
||||
"IPL",
|
||||
"IPL Partition Data",
|
||||
"Non-Host Code",
|
||||
"Non-Host Config",
|
||||
"Non-Host Info"
|
||||
};
|
||||
|
||||
struct tcpa_pc_event {
|
||||
u32 event_id;
|
||||
u32 event_size;
|
||||
u8 event_data[0];
|
||||
};
|
||||
|
||||
enum tcpa_pc_event_ids {
|
||||
SMBIOS = 1,
|
||||
BIS_CERT,
|
||||
POST_BIOS_ROM,
|
||||
ESCD,
|
||||
CMOS,
|
||||
NVRAM,
|
||||
OPTION_ROM_EXEC,
|
||||
OPTION_ROM_CONFIG,
|
||||
OPTION_ROM_MICROCODE = 10,
|
||||
S_CRTM_VERSION,
|
||||
S_CRTM_CONTENTS,
|
||||
POST_CONTENTS,
|
||||
HOST_TABLE_OF_DEVICES,
|
||||
};
|
||||
|
||||
static const char* tcpa_pc_event_id_strings[] = {
|
||||
"",
|
||||
"SMBIOS",
|
||||
"BIS Certificate",
|
||||
"POST BIOS ",
|
||||
"ESCD ",
|
||||
"CMOS",
|
||||
"NVRAM",
|
||||
"Option ROM",
|
||||
"Option ROM config",
|
||||
"",
|
||||
"Option ROM microcode ",
|
||||
"S-CRTM Version",
|
||||
"S-CRTM Contents ",
|
||||
"POST Contents ",
|
||||
"Table of Devices",
|
||||
};
|
||||
|
||||
/* returns pointer to start of pos. entry of tcg log */
|
||||
static void *tpm_bios_measurements_start(struct seq_file *m, loff_t *pos)
|
||||
{
|
||||
loff_t i;
|
||||
struct tpm_bios_log *log = m->private;
|
||||
void *addr = log->bios_event_log;
|
||||
void *limit = log->bios_event_log_end;
|
||||
struct tcpa_event *event;
|
||||
|
||||
/* read over *pos measurements */
|
||||
for (i = 0; i < *pos; i++) {
|
||||
event = addr;
|
||||
|
||||
if ((addr + sizeof(struct tcpa_event)) < limit) {
|
||||
if (event->event_type == 0 && event->event_size == 0)
|
||||
return NULL;
|
||||
addr += sizeof(struct tcpa_event) + event->event_size;
|
||||
}
|
||||
}
|
||||
|
||||
/* now check if current entry is valid */
|
||||
if ((addr + sizeof(struct tcpa_event)) >= limit)
|
||||
return NULL;
|
||||
|
||||
event = addr;
|
||||
|
||||
if ((event->event_type == 0 && event->event_size == 0) ||
|
||||
((addr + sizeof(struct tcpa_event) + event->event_size) >= limit))
|
||||
return NULL;
|
||||
|
||||
return addr;
|
||||
}
|
||||
|
||||
static void *tpm_bios_measurements_next(struct seq_file *m, void *v,
|
||||
loff_t *pos)
|
||||
{
|
||||
struct tcpa_event *event = v;
|
||||
struct tpm_bios_log *log = m->private;
|
||||
void *limit = log->bios_event_log_end;
|
||||
|
||||
v += sizeof(struct tcpa_event) + event->event_size;
|
||||
|
||||
/* now check if current entry is valid */
|
||||
if ((v + sizeof(struct tcpa_event)) >= limit)
|
||||
return NULL;
|
||||
|
||||
event = v;
|
||||
|
||||
if (event->event_type == 0 && event->event_size == 0)
|
||||
return NULL;
|
||||
|
||||
if ((event->event_type == 0 && event->event_size == 0) ||
|
||||
((v + sizeof(struct tcpa_event) + event->event_size) >= limit))
|
||||
return NULL;
|
||||
|
||||
(*pos)++;
|
||||
return v;
|
||||
}
|
||||
|
||||
static void tpm_bios_measurements_stop(struct seq_file *m, void *v)
|
||||
{
|
||||
}
|
||||
|
||||
static int get_event_name(char *dest, struct tcpa_event *event,
|
||||
unsigned char * event_entry)
|
||||
{
|
||||
const char *name = "";
|
||||
char data[40] = "";
|
||||
int i, n_len = 0, d_len = 0;
|
||||
struct tcpa_pc_event *pc_event;
|
||||
|
||||
switch(event->event_type) {
|
||||
case PREBOOT:
|
||||
case POST_CODE:
|
||||
case UNUSED:
|
||||
case NO_ACTION:
|
||||
case SCRTM_CONTENTS:
|
||||
case SCRTM_VERSION:
|
||||
case CPU_MICROCODE:
|
||||
case PLATFORM_CONFIG_FLAGS:
|
||||
case TABLE_OF_DEVICES:
|
||||
case COMPACT_HASH:
|
||||
case IPL:
|
||||
case IPL_PARTITION_DATA:
|
||||
case NONHOST_CODE:
|
||||
case NONHOST_CONFIG:
|
||||
case NONHOST_INFO:
|
||||
name = tcpa_event_type_strings[event->event_type];
|
||||
n_len = strlen(name);
|
||||
break;
|
||||
case SEPARATOR:
|
||||
case ACTION:
|
||||
if (MAX_TEXT_EVENT > event->event_size) {
|
||||
name = event_entry;
|
||||
n_len = event->event_size;
|
||||
}
|
||||
break;
|
||||
case EVENT_TAG:
|
||||
pc_event = (struct tcpa_pc_event *)event_entry;
|
||||
|
||||
/* ToDo Row data -> Base64 */
|
||||
|
||||
switch (pc_event->event_id) {
|
||||
case SMBIOS:
|
||||
case BIS_CERT:
|
||||
case CMOS:
|
||||
case NVRAM:
|
||||
case OPTION_ROM_EXEC:
|
||||
case OPTION_ROM_CONFIG:
|
||||
case S_CRTM_VERSION:
|
||||
name = tcpa_pc_event_id_strings[pc_event->event_id];
|
||||
n_len = strlen(name);
|
||||
break;
|
||||
/* hash data */
|
||||
case POST_BIOS_ROM:
|
||||
case ESCD:
|
||||
case OPTION_ROM_MICROCODE:
|
||||
case S_CRTM_CONTENTS:
|
||||
case POST_CONTENTS:
|
||||
name = tcpa_pc_event_id_strings[pc_event->event_id];
|
||||
n_len = strlen(name);
|
||||
for (i = 0; i < 20; i++)
|
||||
d_len += sprintf(&data[2*i], "%02x",
|
||||
pc_event->event_data[i]);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return snprintf(dest, MAX_TEXT_EVENT, "[%.*s%.*s]",
|
||||
n_len, name, d_len, data);
|
||||
|
||||
}
|
||||
|
||||
static int tpm_binary_bios_measurements_show(struct seq_file *m, void *v)
|
||||
{
|
||||
struct tcpa_event *event = v;
|
||||
char *data = v;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < sizeof(struct tcpa_event) + event->event_size; i++)
|
||||
seq_putc(m, data[i]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tpm_bios_measurements_release(struct inode *inode,
|
||||
struct file *file)
|
||||
{
|
||||
struct seq_file *seq = file->private_data;
|
||||
struct tpm_bios_log *log = seq->private;
|
||||
|
||||
if (log) {
|
||||
kfree(log->bios_event_log);
|
||||
kfree(log);
|
||||
}
|
||||
|
||||
return seq_release(inode, file);
|
||||
}
|
||||
|
||||
static int tpm_ascii_bios_measurements_show(struct seq_file *m, void *v)
|
||||
{
|
||||
int len = 0;
|
||||
int i;
|
||||
char *eventname;
|
||||
struct tcpa_event *event = v;
|
||||
unsigned char *event_entry =
|
||||
(unsigned char *) (v + sizeof(struct tcpa_event));
|
||||
|
||||
eventname = kmalloc(MAX_TEXT_EVENT, GFP_KERNEL);
|
||||
if (!eventname) {
|
||||
printk(KERN_ERR "%s: ERROR - No Memory for event name\n ",
|
||||
__func__);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
seq_printf(m, "%2d ", event->pcr_index);
|
||||
|
||||
/* 2nd: SHA1 */
|
||||
for (i = 0; i < 20; i++)
|
||||
seq_printf(m, "%02x", event->pcr_value[i]);
|
||||
|
||||
/* 3rd: event type identifier */
|
||||
seq_printf(m, " %02x", event->event_type);
|
||||
|
||||
len += get_event_name(eventname, event, event_entry);
|
||||
|
||||
/* 4th: eventname <= max + \'0' delimiter */
|
||||
seq_printf(m, " %s\n", eventname);
|
||||
|
||||
kfree(eventname);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct seq_operations tpm_ascii_b_measurments_seqops = {
|
||||
.start = tpm_bios_measurements_start,
|
||||
.next = tpm_bios_measurements_next,
|
||||
.stop = tpm_bios_measurements_stop,
|
||||
.show = tpm_ascii_bios_measurements_show,
|
||||
};
|
||||
|
||||
static struct seq_operations tpm_binary_b_measurments_seqops = {
|
||||
.start = tpm_bios_measurements_start,
|
||||
.next = tpm_bios_measurements_next,
|
||||
.stop = tpm_bios_measurements_stop,
|
||||
.show = tpm_binary_bios_measurements_show,
|
||||
};
|
||||
|
||||
/* read binary bios log */
|
||||
static int read_log(struct tpm_bios_log *log)
|
||||
{
|
||||
struct acpi_tcpa *buff;
|
||||
acpi_status status;
|
||||
struct acpi_table_header *virt;
|
||||
u64 len, start;
|
||||
|
||||
if (log->bios_event_log != NULL) {
|
||||
printk(KERN_ERR
|
||||
"%s: ERROR - Eventlog already initialized\n",
|
||||
__func__);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
/* Find TCPA entry in RSDT (ACPI_LOGICAL_ADDRESSING) */
|
||||
status = acpi_get_table(ACPI_SIG_TCPA, 1,
|
||||
(struct acpi_table_header **)&buff);
|
||||
|
||||
if (ACPI_FAILURE(status)) {
|
||||
printk(KERN_ERR "%s: ERROR - Could not get TCPA table\n",
|
||||
__func__);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
switch(buff->platform_class) {
|
||||
case BIOS_SERVER:
|
||||
len = buff->server.log_max_len;
|
||||
start = buff->server.log_start_addr;
|
||||
break;
|
||||
case BIOS_CLIENT:
|
||||
default:
|
||||
len = buff->client.log_max_len;
|
||||
start = buff->client.log_start_addr;
|
||||
break;
|
||||
}
|
||||
if (!len) {
|
||||
printk(KERN_ERR "%s: ERROR - TCPA log area empty\n", __func__);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* malloc EventLog space */
|
||||
log->bios_event_log = kmalloc(len, GFP_KERNEL);
|
||||
if (!log->bios_event_log) {
|
||||
printk("%s: ERROR - Not enough Memory for BIOS measurements\n",
|
||||
__func__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
log->bios_event_log_end = log->bios_event_log + len;
|
||||
|
||||
virt = acpi_os_map_memory(start, len);
|
||||
|
||||
memcpy(log->bios_event_log, virt, len);
|
||||
|
||||
acpi_os_unmap_memory(virt, len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tpm_ascii_bios_measurements_open(struct inode *inode,
|
||||
struct file *file)
|
||||
{
|
||||
int err;
|
||||
struct tpm_bios_log *log;
|
||||
struct seq_file *seq;
|
||||
|
||||
log = kzalloc(sizeof(struct tpm_bios_log), GFP_KERNEL);
|
||||
if (!log)
|
||||
return -ENOMEM;
|
||||
|
||||
if ((err = read_log(log)))
|
||||
return err;
|
||||
|
||||
/* now register seq file */
|
||||
err = seq_open(file, &tpm_ascii_b_measurments_seqops);
|
||||
if (!err) {
|
||||
seq = file->private_data;
|
||||
seq->private = log;
|
||||
} else {
|
||||
kfree(log->bios_event_log);
|
||||
kfree(log);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
const struct file_operations tpm_ascii_bios_measurements_ops = {
|
||||
.open = tpm_ascii_bios_measurements_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = tpm_bios_measurements_release,
|
||||
};
|
||||
|
||||
static int tpm_binary_bios_measurements_open(struct inode *inode,
|
||||
struct file *file)
|
||||
{
|
||||
int err;
|
||||
struct tpm_bios_log *log;
|
||||
struct seq_file *seq;
|
||||
|
||||
log = kzalloc(sizeof(struct tpm_bios_log), GFP_KERNEL);
|
||||
if (!log)
|
||||
return -ENOMEM;
|
||||
|
||||
if ((err = read_log(log)))
|
||||
return err;
|
||||
|
||||
/* now register seq file */
|
||||
err = seq_open(file, &tpm_binary_b_measurments_seqops);
|
||||
if (!err) {
|
||||
seq = file->private_data;
|
||||
seq->private = log;
|
||||
} else {
|
||||
kfree(log->bios_event_log);
|
||||
kfree(log);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
const struct file_operations tpm_binary_bios_measurements_ops = {
|
||||
.open = tpm_binary_bios_measurements_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = tpm_bios_measurements_release,
|
||||
};
|
||||
|
||||
static int is_bad(void *p)
|
||||
{
|
||||
if (!p)
|
||||
return 1;
|
||||
if (IS_ERR(p) && (PTR_ERR(p) != -ENODEV))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct dentry **tpm_bios_log_setup(char *name)
|
||||
{
|
||||
struct dentry **ret = NULL, *tpm_dir, *bin_file, *ascii_file;
|
||||
|
||||
tpm_dir = securityfs_create_dir(name, NULL);
|
||||
if (is_bad(tpm_dir))
|
||||
goto out;
|
||||
|
||||
bin_file =
|
||||
securityfs_create_file("binary_bios_measurements",
|
||||
S_IRUSR | S_IRGRP, tpm_dir, NULL,
|
||||
&tpm_binary_bios_measurements_ops);
|
||||
if (is_bad(bin_file))
|
||||
goto out_tpm;
|
||||
|
||||
ascii_file =
|
||||
securityfs_create_file("ascii_bios_measurements",
|
||||
S_IRUSR | S_IRGRP, tpm_dir, NULL,
|
||||
&tpm_ascii_bios_measurements_ops);
|
||||
if (is_bad(ascii_file))
|
||||
goto out_bin;
|
||||
|
||||
ret = kmalloc(3 * sizeof(struct dentry *), GFP_KERNEL);
|
||||
if (!ret)
|
||||
goto out_ascii;
|
||||
|
||||
ret[0] = ascii_file;
|
||||
ret[1] = bin_file;
|
||||
ret[2] = tpm_dir;
|
||||
|
||||
return ret;
|
||||
|
||||
out_ascii:
|
||||
securityfs_remove(ascii_file);
|
||||
out_bin:
|
||||
securityfs_remove(bin_file);
|
||||
out_tpm:
|
||||
securityfs_remove(tpm_dir);
|
||||
out:
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tpm_bios_log_setup);
|
||||
|
||||
void tpm_bios_log_teardown(struct dentry **lst)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 3; i++)
|
||||
securityfs_remove(lst[i]);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tpm_bios_log_teardown);
|
||||
MODULE_LICENSE("GPL");
|
||||
543
drivers/char/tpm/tpm_infineon.c
Normal file
543
drivers/char/tpm/tpm_infineon.c
Normal file
@@ -0,0 +1,543 @@
|
||||
/*
|
||||
* Description:
|
||||
* Device Driver for the Infineon Technologies
|
||||
* SLD 9630 TT 1.1 and SLB 9635 TT 1.2 Trusted Platform Module
|
||||
* Specifications at www.trustedcomputinggroup.org
|
||||
*
|
||||
* Copyright (C) 2005, Marcel Selhorst <selhorst@crypto.rub.de>
|
||||
* Sirrix AG - security technologies, http://www.sirrix.com and
|
||||
* Applied Data Security Group, Ruhr-University Bochum, Germany
|
||||
* Project-Homepage: http://www.prosec.rub.de/tpm
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/pnp.h>
|
||||
#include "tpm.h"
|
||||
|
||||
/* Infineon specific definitions */
|
||||
/* maximum number of WTX-packages */
|
||||
#define TPM_MAX_WTX_PACKAGES 50
|
||||
/* msleep-Time for WTX-packages */
|
||||
#define TPM_WTX_MSLEEP_TIME 20
|
||||
/* msleep-Time --> Interval to check status register */
|
||||
#define TPM_MSLEEP_TIME 3
|
||||
/* gives number of max. msleep()-calls before throwing timeout */
|
||||
#define TPM_MAX_TRIES 5000
|
||||
#define TPM_INFINEON_DEV_VEN_VALUE 0x15D1
|
||||
|
||||
/* These values will be filled after PnP-call */
|
||||
static int TPM_INF_DATA;
|
||||
static int TPM_INF_ADDR;
|
||||
static int TPM_INF_BASE;
|
||||
static int TPM_INF_ADDR_LEN;
|
||||
static int TPM_INF_PORT_LEN;
|
||||
|
||||
/* TPM header definitions */
|
||||
enum infineon_tpm_header {
|
||||
TPM_VL_VER = 0x01,
|
||||
TPM_VL_CHANNEL_CONTROL = 0x07,
|
||||
TPM_VL_CHANNEL_PERSONALISATION = 0x0A,
|
||||
TPM_VL_CHANNEL_TPM = 0x0B,
|
||||
TPM_VL_CONTROL = 0x00,
|
||||
TPM_INF_NAK = 0x15,
|
||||
TPM_CTRL_WTX = 0x10,
|
||||
TPM_CTRL_WTX_ABORT = 0x18,
|
||||
TPM_CTRL_WTX_ABORT_ACK = 0x18,
|
||||
TPM_CTRL_ERROR = 0x20,
|
||||
TPM_CTRL_CHAININGACK = 0x40,
|
||||
TPM_CTRL_CHAINING = 0x80,
|
||||
TPM_CTRL_DATA = 0x04,
|
||||
TPM_CTRL_DATA_CHA = 0x84,
|
||||
TPM_CTRL_DATA_CHA_ACK = 0xC4
|
||||
};
|
||||
|
||||
enum infineon_tpm_register {
|
||||
WRFIFO = 0x00,
|
||||
RDFIFO = 0x01,
|
||||
STAT = 0x02,
|
||||
CMD = 0x03
|
||||
};
|
||||
|
||||
enum infineon_tpm_command_bits {
|
||||
CMD_DIS = 0x00,
|
||||
CMD_LP = 0x01,
|
||||
CMD_RES = 0x02,
|
||||
CMD_IRQC = 0x06
|
||||
};
|
||||
|
||||
enum infineon_tpm_status_bits {
|
||||
STAT_XFE = 0x00,
|
||||
STAT_LPA = 0x01,
|
||||
STAT_FOK = 0x02,
|
||||
STAT_TOK = 0x03,
|
||||
STAT_IRQA = 0x06,
|
||||
STAT_RDA = 0x07
|
||||
};
|
||||
|
||||
/* some outgoing values */
|
||||
enum infineon_tpm_values {
|
||||
CHIP_ID1 = 0x20,
|
||||
CHIP_ID2 = 0x21,
|
||||
TPM_DAR = 0x30,
|
||||
RESET_LP_IRQC_DISABLE = 0x41,
|
||||
ENABLE_REGISTER_PAIR = 0x55,
|
||||
IOLIMH = 0x60,
|
||||
IOLIML = 0x61,
|
||||
DISABLE_REGISTER_PAIR = 0xAA,
|
||||
IDVENL = 0xF1,
|
||||
IDVENH = 0xF2,
|
||||
IDPDL = 0xF3,
|
||||
IDPDH = 0xF4
|
||||
};
|
||||
|
||||
static int number_of_wtx;
|
||||
|
||||
static int empty_fifo(struct tpm_chip *chip, int clear_wrfifo)
|
||||
{
|
||||
int status;
|
||||
int check = 0;
|
||||
int i;
|
||||
|
||||
if (clear_wrfifo) {
|
||||
for (i = 0; i < 4096; i++) {
|
||||
status = inb(chip->vendor.base + WRFIFO);
|
||||
if (status == 0xff) {
|
||||
if (check == 5)
|
||||
break;
|
||||
else
|
||||
check++;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Note: The values which are currently in the FIFO of the TPM
|
||||
are thrown away since there is no usage for them. Usually,
|
||||
this has nothing to say, since the TPM will give its answer
|
||||
immediately or will be aborted anyway, so the data here is
|
||||
usually garbage and useless.
|
||||
We have to clean this, because the next communication with
|
||||
the TPM would be rubbish, if there is still some old data
|
||||
in the Read FIFO.
|
||||
*/
|
||||
i = 0;
|
||||
do {
|
||||
status = inb(chip->vendor.base + RDFIFO);
|
||||
status = inb(chip->vendor.base + STAT);
|
||||
i++;
|
||||
if (i == TPM_MAX_TRIES)
|
||||
return -EIO;
|
||||
} while ((status & (1 << STAT_RDA)) != 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wait(struct tpm_chip *chip, int wait_for_bit)
|
||||
{
|
||||
int status;
|
||||
int i;
|
||||
for (i = 0; i < TPM_MAX_TRIES; i++) {
|
||||
status = inb(chip->vendor.base + STAT);
|
||||
/* check the status-register if wait_for_bit is set */
|
||||
if (status & 1 << wait_for_bit)
|
||||
break;
|
||||
msleep(TPM_MSLEEP_TIME);
|
||||
}
|
||||
if (i == TPM_MAX_TRIES) { /* timeout occurs */
|
||||
if (wait_for_bit == STAT_XFE)
|
||||
dev_err(chip->dev, "Timeout in wait(STAT_XFE)\n");
|
||||
if (wait_for_bit == STAT_RDA)
|
||||
dev_err(chip->dev, "Timeout in wait(STAT_RDA)\n");
|
||||
return -EIO;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
static void wait_and_send(struct tpm_chip *chip, u8 sendbyte)
|
||||
{
|
||||
wait(chip, STAT_XFE);
|
||||
outb(sendbyte, chip->vendor.base + WRFIFO);
|
||||
}
|
||||
|
||||
/* Note: WTX means Waiting-Time-Extension. Whenever the TPM needs more
|
||||
calculation time, it sends a WTX-package, which has to be acknowledged
|
||||
or aborted. This usually occurs if you are hammering the TPM with key
|
||||
creation. Set the maximum number of WTX-packages in the definitions
|
||||
above, if the number is reached, the waiting-time will be denied
|
||||
and the TPM command has to be resend.
|
||||
*/
|
||||
|
||||
static void tpm_wtx(struct tpm_chip *chip)
|
||||
{
|
||||
number_of_wtx++;
|
||||
dev_info(chip->dev, "Granting WTX (%02d / %02d)\n",
|
||||
number_of_wtx, TPM_MAX_WTX_PACKAGES);
|
||||
wait_and_send(chip, TPM_VL_VER);
|
||||
wait_and_send(chip, TPM_CTRL_WTX);
|
||||
wait_and_send(chip, 0x00);
|
||||
wait_and_send(chip, 0x00);
|
||||
msleep(TPM_WTX_MSLEEP_TIME);
|
||||
}
|
||||
|
||||
static void tpm_wtx_abort(struct tpm_chip *chip)
|
||||
{
|
||||
dev_info(chip->dev, "Aborting WTX\n");
|
||||
wait_and_send(chip, TPM_VL_VER);
|
||||
wait_and_send(chip, TPM_CTRL_WTX_ABORT);
|
||||
wait_and_send(chip, 0x00);
|
||||
wait_and_send(chip, 0x00);
|
||||
number_of_wtx = 0;
|
||||
msleep(TPM_WTX_MSLEEP_TIME);
|
||||
}
|
||||
|
||||
static int tpm_inf_recv(struct tpm_chip *chip, u8 * buf, size_t count)
|
||||
{
|
||||
int i;
|
||||
int ret;
|
||||
u32 size = 0;
|
||||
number_of_wtx = 0;
|
||||
|
||||
recv_begin:
|
||||
/* start receiving header */
|
||||
for (i = 0; i < 4; i++) {
|
||||
ret = wait(chip, STAT_RDA);
|
||||
if (ret)
|
||||
return -EIO;
|
||||
buf[i] = inb(chip->vendor.base + RDFIFO);
|
||||
}
|
||||
|
||||
if (buf[0] != TPM_VL_VER) {
|
||||
dev_err(chip->dev,
|
||||
"Wrong transport protocol implementation!\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (buf[1] == TPM_CTRL_DATA) {
|
||||
/* size of the data received */
|
||||
size = ((buf[2] << 8) | buf[3]);
|
||||
|
||||
for (i = 0; i < size; i++) {
|
||||
wait(chip, STAT_RDA);
|
||||
buf[i] = inb(chip->vendor.base + RDFIFO);
|
||||
}
|
||||
|
||||
if ((size == 0x6D00) && (buf[1] == 0x80)) {
|
||||
dev_err(chip->dev, "Error handling on vendor layer!\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
for (i = 0; i < size; i++)
|
||||
buf[i] = buf[i + 6];
|
||||
|
||||
size = size - 6;
|
||||
return size;
|
||||
}
|
||||
|
||||
if (buf[1] == TPM_CTRL_WTX) {
|
||||
dev_info(chip->dev, "WTX-package received\n");
|
||||
if (number_of_wtx < TPM_MAX_WTX_PACKAGES) {
|
||||
tpm_wtx(chip);
|
||||
goto recv_begin;
|
||||
} else {
|
||||
tpm_wtx_abort(chip);
|
||||
goto recv_begin;
|
||||
}
|
||||
}
|
||||
|
||||
if (buf[1] == TPM_CTRL_WTX_ABORT_ACK) {
|
||||
dev_info(chip->dev, "WTX-abort acknowledged\n");
|
||||
return size;
|
||||
}
|
||||
|
||||
if (buf[1] == TPM_CTRL_ERROR) {
|
||||
dev_err(chip->dev, "ERROR-package received:\n");
|
||||
if (buf[4] == TPM_INF_NAK)
|
||||
dev_err(chip->dev,
|
||||
"-> Negative acknowledgement"
|
||||
" - retransmit command!\n");
|
||||
return -EIO;
|
||||
}
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static int tpm_inf_send(struct tpm_chip *chip, u8 * buf, size_t count)
|
||||
{
|
||||
int i;
|
||||
int ret;
|
||||
u8 count_high, count_low, count_4, count_3, count_2, count_1;
|
||||
|
||||
/* Disabling Reset, LP and IRQC */
|
||||
outb(RESET_LP_IRQC_DISABLE, chip->vendor.base + CMD);
|
||||
|
||||
ret = empty_fifo(chip, 1);
|
||||
if (ret) {
|
||||
dev_err(chip->dev, "Timeout while clearing FIFO\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
ret = wait(chip, STAT_XFE);
|
||||
if (ret)
|
||||
return -EIO;
|
||||
|
||||
count_4 = (count & 0xff000000) >> 24;
|
||||
count_3 = (count & 0x00ff0000) >> 16;
|
||||
count_2 = (count & 0x0000ff00) >> 8;
|
||||
count_1 = (count & 0x000000ff);
|
||||
count_high = ((count + 6) & 0xffffff00) >> 8;
|
||||
count_low = ((count + 6) & 0x000000ff);
|
||||
|
||||
/* Sending Header */
|
||||
wait_and_send(chip, TPM_VL_VER);
|
||||
wait_and_send(chip, TPM_CTRL_DATA);
|
||||
wait_and_send(chip, count_high);
|
||||
wait_and_send(chip, count_low);
|
||||
|
||||
/* Sending Data Header */
|
||||
wait_and_send(chip, TPM_VL_VER);
|
||||
wait_and_send(chip, TPM_VL_CHANNEL_TPM);
|
||||
wait_and_send(chip, count_4);
|
||||
wait_and_send(chip, count_3);
|
||||
wait_and_send(chip, count_2);
|
||||
wait_and_send(chip, count_1);
|
||||
|
||||
/* Sending Data */
|
||||
for (i = 0; i < count; i++) {
|
||||
wait_and_send(chip, buf[i]);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static void tpm_inf_cancel(struct tpm_chip *chip)
|
||||
{
|
||||
/*
|
||||
Since we are using the legacy mode to communicate
|
||||
with the TPM, we have no cancel functions, but have
|
||||
a workaround for interrupting the TPM through WTX.
|
||||
*/
|
||||
}
|
||||
|
||||
static u8 tpm_inf_status(struct tpm_chip *chip)
|
||||
{
|
||||
return inb(chip->vendor.base + STAT);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(pubek, S_IRUGO, tpm_show_pubek, NULL);
|
||||
static DEVICE_ATTR(pcrs, S_IRUGO, tpm_show_pcrs, NULL);
|
||||
static DEVICE_ATTR(caps, S_IRUGO, tpm_show_caps, NULL);
|
||||
static DEVICE_ATTR(cancel, S_IWUSR | S_IWGRP, NULL, tpm_store_cancel);
|
||||
|
||||
static struct attribute *inf_attrs[] = {
|
||||
&dev_attr_pubek.attr,
|
||||
&dev_attr_pcrs.attr,
|
||||
&dev_attr_caps.attr,
|
||||
&dev_attr_cancel.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct attribute_group inf_attr_grp = {.attrs = inf_attrs };
|
||||
|
||||
static const struct file_operations inf_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.llseek = no_llseek,
|
||||
.open = tpm_open,
|
||||
.read = tpm_read,
|
||||
.write = tpm_write,
|
||||
.release = tpm_release,
|
||||
};
|
||||
|
||||
static const struct tpm_vendor_specific tpm_inf = {
|
||||
.recv = tpm_inf_recv,
|
||||
.send = tpm_inf_send,
|
||||
.cancel = tpm_inf_cancel,
|
||||
.status = tpm_inf_status,
|
||||
.req_complete_mask = 0,
|
||||
.req_complete_val = 0,
|
||||
.attr_group = &inf_attr_grp,
|
||||
.miscdev = {.fops = &inf_ops,},
|
||||
};
|
||||
|
||||
static const struct pnp_device_id tpm_pnp_tbl[] = {
|
||||
/* Infineon TPMs */
|
||||
{"IFX0101", 0},
|
||||
{"IFX0102", 0},
|
||||
{"", 0}
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(pnp, tpm_pnp_tbl);
|
||||
|
||||
static int __devinit tpm_inf_pnp_probe(struct pnp_dev *dev,
|
||||
const struct pnp_device_id *dev_id)
|
||||
{
|
||||
int rc = 0;
|
||||
u8 iol, ioh;
|
||||
int vendorid[2];
|
||||
int version[2];
|
||||
int productid[2];
|
||||
char chipname[20];
|
||||
struct tpm_chip *chip;
|
||||
|
||||
/* read IO-ports through PnP */
|
||||
if (pnp_port_valid(dev, 0) && pnp_port_valid(dev, 1) &&
|
||||
!(pnp_port_flags(dev, 0) & IORESOURCE_DISABLED)) {
|
||||
TPM_INF_ADDR = pnp_port_start(dev, 0);
|
||||
TPM_INF_ADDR_LEN = pnp_port_len(dev, 0);
|
||||
TPM_INF_DATA = (TPM_INF_ADDR + 1);
|
||||
TPM_INF_BASE = pnp_port_start(dev, 1);
|
||||
TPM_INF_PORT_LEN = pnp_port_len(dev, 1);
|
||||
if ((TPM_INF_PORT_LEN < 4) || (TPM_INF_ADDR_LEN < 2)) {
|
||||
rc = -EINVAL;
|
||||
goto err_last;
|
||||
}
|
||||
dev_info(&dev->dev, "Found %s with ID %s\n",
|
||||
dev->name, dev_id->id);
|
||||
if (!((TPM_INF_BASE >> 8) & 0xff)) {
|
||||
rc = -EINVAL;
|
||||
goto err_last;
|
||||
}
|
||||
/* publish my base address and request region */
|
||||
if (request_region
|
||||
(TPM_INF_BASE, TPM_INF_PORT_LEN, "tpm_infineon0") == NULL) {
|
||||
rc = -EINVAL;
|
||||
goto err_last;
|
||||
}
|
||||
if (request_region
|
||||
(TPM_INF_ADDR, TPM_INF_ADDR_LEN, "tpm_infineon0") == NULL) {
|
||||
rc = -EINVAL;
|
||||
goto err_last;
|
||||
}
|
||||
} else {
|
||||
rc = -EINVAL;
|
||||
goto err_last;
|
||||
}
|
||||
|
||||
/* query chip for its vendor, its version number a.s.o. */
|
||||
outb(ENABLE_REGISTER_PAIR, TPM_INF_ADDR);
|
||||
outb(IDVENL, TPM_INF_ADDR);
|
||||
vendorid[1] = inb(TPM_INF_DATA);
|
||||
outb(IDVENH, TPM_INF_ADDR);
|
||||
vendorid[0] = inb(TPM_INF_DATA);
|
||||
outb(IDPDL, TPM_INF_ADDR);
|
||||
productid[1] = inb(TPM_INF_DATA);
|
||||
outb(IDPDH, TPM_INF_ADDR);
|
||||
productid[0] = inb(TPM_INF_DATA);
|
||||
outb(CHIP_ID1, TPM_INF_ADDR);
|
||||
version[1] = inb(TPM_INF_DATA);
|
||||
outb(CHIP_ID2, TPM_INF_ADDR);
|
||||
version[0] = inb(TPM_INF_DATA);
|
||||
|
||||
switch ((productid[0] << 8) | productid[1]) {
|
||||
case 6:
|
||||
snprintf(chipname, sizeof(chipname), " (SLD 9630 TT 1.1)");
|
||||
break;
|
||||
case 11:
|
||||
snprintf(chipname, sizeof(chipname), " (SLB 9635 TT 1.2)");
|
||||
break;
|
||||
default:
|
||||
snprintf(chipname, sizeof(chipname), " (unknown chip)");
|
||||
break;
|
||||
}
|
||||
|
||||
if ((vendorid[0] << 8 | vendorid[1]) == (TPM_INFINEON_DEV_VEN_VALUE)) {
|
||||
|
||||
/* configure TPM with IO-ports */
|
||||
outb(IOLIMH, TPM_INF_ADDR);
|
||||
outb(((TPM_INF_BASE >> 8) & 0xff), TPM_INF_DATA);
|
||||
outb(IOLIML, TPM_INF_ADDR);
|
||||
outb((TPM_INF_BASE & 0xff), TPM_INF_DATA);
|
||||
|
||||
/* control if IO-ports are set correctly */
|
||||
outb(IOLIMH, TPM_INF_ADDR);
|
||||
ioh = inb(TPM_INF_DATA);
|
||||
outb(IOLIML, TPM_INF_ADDR);
|
||||
iol = inb(TPM_INF_DATA);
|
||||
|
||||
if ((ioh << 8 | iol) != TPM_INF_BASE) {
|
||||
dev_err(&dev->dev,
|
||||
"Could not set IO-ports to 0x%x\n",
|
||||
TPM_INF_BASE);
|
||||
rc = -EIO;
|
||||
goto err_release_region;
|
||||
}
|
||||
|
||||
/* activate register */
|
||||
outb(TPM_DAR, TPM_INF_ADDR);
|
||||
outb(0x01, TPM_INF_DATA);
|
||||
outb(DISABLE_REGISTER_PAIR, TPM_INF_ADDR);
|
||||
|
||||
/* disable RESET, LP and IRQC */
|
||||
outb(RESET_LP_IRQC_DISABLE, TPM_INF_BASE + CMD);
|
||||
|
||||
/* Finally, we're done, print some infos */
|
||||
dev_info(&dev->dev, "TPM found: "
|
||||
"config base 0x%x, "
|
||||
"io base 0x%x, "
|
||||
"chip version 0x%02x%02x, "
|
||||
"vendor id 0x%x%x (Infineon), "
|
||||
"product id 0x%02x%02x"
|
||||
"%s\n",
|
||||
TPM_INF_ADDR,
|
||||
TPM_INF_BASE,
|
||||
version[0], version[1],
|
||||
vendorid[0], vendorid[1],
|
||||
productid[0], productid[1], chipname);
|
||||
|
||||
if (!(chip = tpm_register_hardware(&dev->dev, &tpm_inf))) {
|
||||
goto err_release_region;
|
||||
}
|
||||
chip->vendor.base = TPM_INF_BASE;
|
||||
return 0;
|
||||
} else {
|
||||
rc = -ENODEV;
|
||||
goto err_release_region;
|
||||
}
|
||||
|
||||
err_release_region:
|
||||
release_region(TPM_INF_BASE, TPM_INF_PORT_LEN);
|
||||
release_region(TPM_INF_ADDR, TPM_INF_ADDR_LEN);
|
||||
|
||||
err_last:
|
||||
return rc;
|
||||
}
|
||||
|
||||
static __devexit void tpm_inf_pnp_remove(struct pnp_dev *dev)
|
||||
{
|
||||
struct tpm_chip *chip = pnp_get_drvdata(dev);
|
||||
|
||||
if (chip) {
|
||||
release_region(TPM_INF_BASE, TPM_INF_PORT_LEN);
|
||||
release_region(TPM_INF_ADDR, TPM_INF_ADDR_LEN);
|
||||
tpm_remove_hardware(chip->dev);
|
||||
}
|
||||
}
|
||||
|
||||
static struct pnp_driver tpm_inf_pnp = {
|
||||
.name = "tpm_inf_pnp",
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.suspend = tpm_pm_suspend,
|
||||
.resume = tpm_pm_resume,
|
||||
},
|
||||
.id_table = tpm_pnp_tbl,
|
||||
.probe = tpm_inf_pnp_probe,
|
||||
.remove = __devexit_p(tpm_inf_pnp_remove),
|
||||
};
|
||||
|
||||
static int __init init_inf(void)
|
||||
{
|
||||
return pnp_register_driver(&tpm_inf_pnp);
|
||||
}
|
||||
|
||||
static void __exit cleanup_inf(void)
|
||||
{
|
||||
pnp_unregister_driver(&tpm_inf_pnp);
|
||||
}
|
||||
|
||||
module_init(init_inf);
|
||||
module_exit(cleanup_inf);
|
||||
|
||||
MODULE_AUTHOR("Marcel Selhorst <selhorst@crypto.rub.de>");
|
||||
MODULE_DESCRIPTION("Driver for Infineon TPM SLD 9630 TT 1.1 / SLB 9635 TT 1.2");
|
||||
MODULE_VERSION("1.8");
|
||||
MODULE_LICENSE("GPL");
|
||||
402
drivers/char/tpm/tpm_nsc.c
Normal file
402
drivers/char/tpm/tpm_nsc.c
Normal file
@@ -0,0 +1,402 @@
|
||||
/*
|
||||
* Copyright (C) 2004 IBM Corporation
|
||||
*
|
||||
* Authors:
|
||||
* Leendert van Doorn <leendert@watson.ibm.com>
|
||||
* Dave Safford <safford@watson.ibm.com>
|
||||
* Reiner Sailer <sailer@watson.ibm.com>
|
||||
* Kylene Hall <kjhall@us.ibm.com>
|
||||
*
|
||||
* Maintained by: <tpmdd_devel@lists.sourceforge.net>
|
||||
*
|
||||
* Device driver for TCG/TCPA TPM (trusted platform module).
|
||||
* Specifications at www.trustedcomputinggroup.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/platform_device.h>
|
||||
#include "tpm.h"
|
||||
|
||||
/* National definitions */
|
||||
enum tpm_nsc_addr{
|
||||
TPM_NSC_IRQ = 0x07,
|
||||
TPM_NSC_BASE0_HI = 0x60,
|
||||
TPM_NSC_BASE0_LO = 0x61,
|
||||
TPM_NSC_BASE1_HI = 0x62,
|
||||
TPM_NSC_BASE1_LO = 0x63
|
||||
};
|
||||
|
||||
enum tpm_nsc_index {
|
||||
NSC_LDN_INDEX = 0x07,
|
||||
NSC_SID_INDEX = 0x20,
|
||||
NSC_LDC_INDEX = 0x30,
|
||||
NSC_DIO_INDEX = 0x60,
|
||||
NSC_CIO_INDEX = 0x62,
|
||||
NSC_IRQ_INDEX = 0x70,
|
||||
NSC_ITS_INDEX = 0x71
|
||||
};
|
||||
|
||||
enum tpm_nsc_status_loc {
|
||||
NSC_STATUS = 0x01,
|
||||
NSC_COMMAND = 0x01,
|
||||
NSC_DATA = 0x00
|
||||
};
|
||||
|
||||
/* status bits */
|
||||
enum tpm_nsc_status {
|
||||
NSC_STATUS_OBF = 0x01, /* output buffer full */
|
||||
NSC_STATUS_IBF = 0x02, /* input buffer full */
|
||||
NSC_STATUS_F0 = 0x04, /* F0 */
|
||||
NSC_STATUS_A2 = 0x08, /* A2 */
|
||||
NSC_STATUS_RDY = 0x10, /* ready to receive command */
|
||||
NSC_STATUS_IBR = 0x20 /* ready to receive data */
|
||||
};
|
||||
|
||||
/* command bits */
|
||||
enum tpm_nsc_cmd_mode {
|
||||
NSC_COMMAND_NORMAL = 0x01, /* normal mode */
|
||||
NSC_COMMAND_EOC = 0x03,
|
||||
NSC_COMMAND_CANCEL = 0x22
|
||||
};
|
||||
/*
|
||||
* Wait for a certain status to appear
|
||||
*/
|
||||
static int wait_for_stat(struct tpm_chip *chip, u8 mask, u8 val, u8 * data)
|
||||
{
|
||||
unsigned long stop;
|
||||
|
||||
/* status immediately available check */
|
||||
*data = inb(chip->vendor.base + NSC_STATUS);
|
||||
if ((*data & mask) == val)
|
||||
return 0;
|
||||
|
||||
/* wait for status */
|
||||
stop = jiffies + 10 * HZ;
|
||||
do {
|
||||
msleep(TPM_TIMEOUT);
|
||||
*data = inb(chip->vendor.base + 1);
|
||||
if ((*data & mask) == val)
|
||||
return 0;
|
||||
}
|
||||
while (time_before(jiffies, stop));
|
||||
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
static int nsc_wait_for_ready(struct tpm_chip *chip)
|
||||
{
|
||||
int status;
|
||||
unsigned long stop;
|
||||
|
||||
/* status immediately available check */
|
||||
status = inb(chip->vendor.base + NSC_STATUS);
|
||||
if (status & NSC_STATUS_OBF)
|
||||
status = inb(chip->vendor.base + NSC_DATA);
|
||||
if (status & NSC_STATUS_RDY)
|
||||
return 0;
|
||||
|
||||
/* wait for status */
|
||||
stop = jiffies + 100;
|
||||
do {
|
||||
msleep(TPM_TIMEOUT);
|
||||
status = inb(chip->vendor.base + NSC_STATUS);
|
||||
if (status & NSC_STATUS_OBF)
|
||||
status = inb(chip->vendor.base + NSC_DATA);
|
||||
if (status & NSC_STATUS_RDY)
|
||||
return 0;
|
||||
}
|
||||
while (time_before(jiffies, stop));
|
||||
|
||||
dev_info(chip->dev, "wait for ready failed\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
|
||||
static int tpm_nsc_recv(struct tpm_chip *chip, u8 * buf, size_t count)
|
||||
{
|
||||
u8 *buffer = buf;
|
||||
u8 data, *p;
|
||||
u32 size;
|
||||
__be32 *native_size;
|
||||
|
||||
if (count < 6)
|
||||
return -EIO;
|
||||
|
||||
if (wait_for_stat(chip, NSC_STATUS_F0, NSC_STATUS_F0, &data) < 0) {
|
||||
dev_err(chip->dev, "F0 timeout\n");
|
||||
return -EIO;
|
||||
}
|
||||
if ((data =
|
||||
inb(chip->vendor.base + NSC_DATA)) != NSC_COMMAND_NORMAL) {
|
||||
dev_err(chip->dev, "not in normal mode (0x%x)\n",
|
||||
data);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* read the whole packet */
|
||||
for (p = buffer; p < &buffer[count]; p++) {
|
||||
if (wait_for_stat
|
||||
(chip, NSC_STATUS_OBF, NSC_STATUS_OBF, &data) < 0) {
|
||||
dev_err(chip->dev,
|
||||
"OBF timeout (while reading data)\n");
|
||||
return -EIO;
|
||||
}
|
||||
if (data & NSC_STATUS_F0)
|
||||
break;
|
||||
*p = inb(chip->vendor.base + NSC_DATA);
|
||||
}
|
||||
|
||||
if ((data & NSC_STATUS_F0) == 0 &&
|
||||
(wait_for_stat(chip, NSC_STATUS_F0, NSC_STATUS_F0, &data) < 0)) {
|
||||
dev_err(chip->dev, "F0 not set\n");
|
||||
return -EIO;
|
||||
}
|
||||
if ((data = inb(chip->vendor.base + NSC_DATA)) != NSC_COMMAND_EOC) {
|
||||
dev_err(chip->dev,
|
||||
"expected end of command(0x%x)\n", data);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
native_size = (__force __be32 *) (buf + 2);
|
||||
size = be32_to_cpu(*native_size);
|
||||
|
||||
if (count < size)
|
||||
return -EIO;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static int tpm_nsc_send(struct tpm_chip *chip, u8 * buf, size_t count)
|
||||
{
|
||||
u8 data;
|
||||
int i;
|
||||
|
||||
/*
|
||||
* If we hit the chip with back to back commands it locks up
|
||||
* and never set IBF. Hitting it with this "hammer" seems to
|
||||
* fix it. Not sure why this is needed, we followed the flow
|
||||
* chart in the manual to the letter.
|
||||
*/
|
||||
outb(NSC_COMMAND_CANCEL, chip->vendor.base + NSC_COMMAND);
|
||||
|
||||
if (nsc_wait_for_ready(chip) != 0)
|
||||
return -EIO;
|
||||
|
||||
if (wait_for_stat(chip, NSC_STATUS_IBF, 0, &data) < 0) {
|
||||
dev_err(chip->dev, "IBF timeout\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
outb(NSC_COMMAND_NORMAL, chip->vendor.base + NSC_COMMAND);
|
||||
if (wait_for_stat(chip, NSC_STATUS_IBR, NSC_STATUS_IBR, &data) < 0) {
|
||||
dev_err(chip->dev, "IBR timeout\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
if (wait_for_stat(chip, NSC_STATUS_IBF, 0, &data) < 0) {
|
||||
dev_err(chip->dev,
|
||||
"IBF timeout (while writing data)\n");
|
||||
return -EIO;
|
||||
}
|
||||
outb(buf[i], chip->vendor.base + NSC_DATA);
|
||||
}
|
||||
|
||||
if (wait_for_stat(chip, NSC_STATUS_IBF, 0, &data) < 0) {
|
||||
dev_err(chip->dev, "IBF timeout\n");
|
||||
return -EIO;
|
||||
}
|
||||
outb(NSC_COMMAND_EOC, chip->vendor.base + NSC_COMMAND);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static void tpm_nsc_cancel(struct tpm_chip *chip)
|
||||
{
|
||||
outb(NSC_COMMAND_CANCEL, chip->vendor.base + NSC_COMMAND);
|
||||
}
|
||||
|
||||
static u8 tpm_nsc_status(struct tpm_chip *chip)
|
||||
{
|
||||
return inb(chip->vendor.base + NSC_STATUS);
|
||||
}
|
||||
|
||||
static const struct file_operations nsc_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.llseek = no_llseek,
|
||||
.open = tpm_open,
|
||||
.read = tpm_read,
|
||||
.write = tpm_write,
|
||||
.release = tpm_release,
|
||||
};
|
||||
|
||||
static DEVICE_ATTR(pubek, S_IRUGO, tpm_show_pubek, NULL);
|
||||
static DEVICE_ATTR(pcrs, S_IRUGO, tpm_show_pcrs, NULL);
|
||||
static DEVICE_ATTR(caps, S_IRUGO, tpm_show_caps, NULL);
|
||||
static DEVICE_ATTR(cancel, S_IWUSR|S_IWGRP, NULL, tpm_store_cancel);
|
||||
|
||||
static struct attribute * nsc_attrs[] = {
|
||||
&dev_attr_pubek.attr,
|
||||
&dev_attr_pcrs.attr,
|
||||
&dev_attr_caps.attr,
|
||||
&dev_attr_cancel.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct attribute_group nsc_attr_grp = { .attrs = nsc_attrs };
|
||||
|
||||
static const struct tpm_vendor_specific tpm_nsc = {
|
||||
.recv = tpm_nsc_recv,
|
||||
.send = tpm_nsc_send,
|
||||
.cancel = tpm_nsc_cancel,
|
||||
.status = tpm_nsc_status,
|
||||
.req_complete_mask = NSC_STATUS_OBF,
|
||||
.req_complete_val = NSC_STATUS_OBF,
|
||||
.req_canceled = NSC_STATUS_RDY,
|
||||
.attr_group = &nsc_attr_grp,
|
||||
.miscdev = { .fops = &nsc_ops, },
|
||||
};
|
||||
|
||||
static struct platform_device *pdev = NULL;
|
||||
|
||||
static void __devexit tpm_nsc_remove(struct device *dev)
|
||||
{
|
||||
struct tpm_chip *chip = dev_get_drvdata(dev);
|
||||
if ( chip ) {
|
||||
release_region(chip->vendor.base, 2);
|
||||
tpm_remove_hardware(chip->dev);
|
||||
}
|
||||
}
|
||||
|
||||
static struct device_driver nsc_drv = {
|
||||
.name = "tpm_nsc",
|
||||
.bus = &platform_bus_type,
|
||||
.owner = THIS_MODULE,
|
||||
.suspend = tpm_pm_suspend,
|
||||
.resume = tpm_pm_resume,
|
||||
};
|
||||
|
||||
static int __init init_nsc(void)
|
||||
{
|
||||
int rc = 0;
|
||||
int lo, hi, err;
|
||||
int nscAddrBase = TPM_ADDR;
|
||||
struct tpm_chip *chip;
|
||||
unsigned long base;
|
||||
|
||||
/* verify that it is a National part (SID) */
|
||||
if (tpm_read_index(TPM_ADDR, NSC_SID_INDEX) != 0xEF) {
|
||||
nscAddrBase = (tpm_read_index(TPM_SUPERIO_ADDR, 0x2C)<<8)|
|
||||
(tpm_read_index(TPM_SUPERIO_ADDR, 0x2B)&0xFE);
|
||||
if (tpm_read_index(nscAddrBase, NSC_SID_INDEX) != 0xF6)
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
err = driver_register(&nsc_drv);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
hi = tpm_read_index(nscAddrBase, TPM_NSC_BASE0_HI);
|
||||
lo = tpm_read_index(nscAddrBase, TPM_NSC_BASE0_LO);
|
||||
base = (hi<<8) | lo;
|
||||
|
||||
/* enable the DPM module */
|
||||
tpm_write_index(nscAddrBase, NSC_LDC_INDEX, 0x01);
|
||||
|
||||
pdev = kzalloc(sizeof(struct platform_device), GFP_KERNEL);
|
||||
if (!pdev) {
|
||||
rc = -ENOMEM;
|
||||
goto err_unreg_drv;
|
||||
}
|
||||
|
||||
pdev->name = "tpm_nscl0";
|
||||
pdev->id = -1;
|
||||
pdev->num_resources = 0;
|
||||
pdev->dev.release = tpm_nsc_remove;
|
||||
pdev->dev.driver = &nsc_drv;
|
||||
|
||||
if ((rc = platform_device_register(pdev)) < 0)
|
||||
goto err_free_dev;
|
||||
|
||||
if (request_region(base, 2, "tpm_nsc0") == NULL ) {
|
||||
rc = -EBUSY;
|
||||
goto err_unreg_dev;
|
||||
}
|
||||
|
||||
if (!(chip = tpm_register_hardware(&pdev->dev, &tpm_nsc))) {
|
||||
rc = -ENODEV;
|
||||
goto err_rel_reg;
|
||||
}
|
||||
|
||||
dev_dbg(&pdev->dev, "NSC TPM detected\n");
|
||||
dev_dbg(&pdev->dev,
|
||||
"NSC LDN 0x%x, SID 0x%x, SRID 0x%x\n",
|
||||
tpm_read_index(nscAddrBase,0x07), tpm_read_index(nscAddrBase,0x20),
|
||||
tpm_read_index(nscAddrBase,0x27));
|
||||
dev_dbg(&pdev->dev,
|
||||
"NSC SIOCF1 0x%x SIOCF5 0x%x SIOCF6 0x%x SIOCF8 0x%x\n",
|
||||
tpm_read_index(nscAddrBase,0x21), tpm_read_index(nscAddrBase,0x25),
|
||||
tpm_read_index(nscAddrBase,0x26), tpm_read_index(nscAddrBase,0x28));
|
||||
dev_dbg(&pdev->dev, "NSC IO Base0 0x%x\n",
|
||||
(tpm_read_index(nscAddrBase,0x60) << 8) | tpm_read_index(nscAddrBase,0x61));
|
||||
dev_dbg(&pdev->dev, "NSC IO Base1 0x%x\n",
|
||||
(tpm_read_index(nscAddrBase,0x62) << 8) | tpm_read_index(nscAddrBase,0x63));
|
||||
dev_dbg(&pdev->dev, "NSC Interrupt number and wakeup 0x%x\n",
|
||||
tpm_read_index(nscAddrBase,0x70));
|
||||
dev_dbg(&pdev->dev, "NSC IRQ type select 0x%x\n",
|
||||
tpm_read_index(nscAddrBase,0x71));
|
||||
dev_dbg(&pdev->dev,
|
||||
"NSC DMA channel select0 0x%x, select1 0x%x\n",
|
||||
tpm_read_index(nscAddrBase,0x74), tpm_read_index(nscAddrBase,0x75));
|
||||
dev_dbg(&pdev->dev,
|
||||
"NSC Config "
|
||||
"0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n",
|
||||
tpm_read_index(nscAddrBase,0xF0), tpm_read_index(nscAddrBase,0xF1),
|
||||
tpm_read_index(nscAddrBase,0xF2), tpm_read_index(nscAddrBase,0xF3),
|
||||
tpm_read_index(nscAddrBase,0xF4), tpm_read_index(nscAddrBase,0xF5),
|
||||
tpm_read_index(nscAddrBase,0xF6), tpm_read_index(nscAddrBase,0xF7),
|
||||
tpm_read_index(nscAddrBase,0xF8), tpm_read_index(nscAddrBase,0xF9));
|
||||
|
||||
dev_info(&pdev->dev,
|
||||
"NSC TPM revision %d\n",
|
||||
tpm_read_index(nscAddrBase, 0x27) & 0x1F);
|
||||
|
||||
chip->vendor.base = base;
|
||||
|
||||
return 0;
|
||||
|
||||
err_rel_reg:
|
||||
release_region(base, 2);
|
||||
err_unreg_dev:
|
||||
platform_device_unregister(pdev);
|
||||
err_free_dev:
|
||||
kfree(pdev);
|
||||
err_unreg_drv:
|
||||
driver_unregister(&nsc_drv);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void __exit cleanup_nsc(void)
|
||||
{
|
||||
if (pdev) {
|
||||
tpm_nsc_remove(&pdev->dev);
|
||||
platform_device_unregister(pdev);
|
||||
kfree(pdev);
|
||||
pdev = NULL;
|
||||
}
|
||||
|
||||
driver_unregister(&nsc_drv);
|
||||
}
|
||||
|
||||
module_init(init_nsc);
|
||||
module_exit(cleanup_nsc);
|
||||
|
||||
MODULE_AUTHOR("Leendert van Doorn (leendert@watson.ibm.com)");
|
||||
MODULE_DESCRIPTION("TPM Driver");
|
||||
MODULE_VERSION("2.0");
|
||||
MODULE_LICENSE("GPL");
|
||||
704
drivers/char/tpm/tpm_tis.c
Normal file
704
drivers/char/tpm/tpm_tis.c
Normal file
@@ -0,0 +1,704 @@
|
||||
/*
|
||||
* Copyright (C) 2005, 2006 IBM Corporation
|
||||
*
|
||||
* Authors:
|
||||
* Leendert van Doorn <leendert@watson.ibm.com>
|
||||
* Kylene Hall <kjhall@us.ibm.com>
|
||||
*
|
||||
* Device driver for TCG/TCPA TPM (trusted platform module).
|
||||
* Specifications at www.trustedcomputinggroup.org
|
||||
*
|
||||
* This device driver implements the TPM interface as defined in
|
||||
* the TCG TPM Interface Spec version 1.2, revision 1.0.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*/
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/pnp.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/wait.h>
|
||||
#include "tpm.h"
|
||||
|
||||
#define TPM_HEADER_SIZE 10
|
||||
|
||||
enum tis_access {
|
||||
TPM_ACCESS_VALID = 0x80,
|
||||
TPM_ACCESS_ACTIVE_LOCALITY = 0x20,
|
||||
TPM_ACCESS_REQUEST_PENDING = 0x04,
|
||||
TPM_ACCESS_REQUEST_USE = 0x02,
|
||||
};
|
||||
|
||||
enum tis_status {
|
||||
TPM_STS_VALID = 0x80,
|
||||
TPM_STS_COMMAND_READY = 0x40,
|
||||
TPM_STS_GO = 0x20,
|
||||
TPM_STS_DATA_AVAIL = 0x10,
|
||||
TPM_STS_DATA_EXPECT = 0x08,
|
||||
};
|
||||
|
||||
enum tis_int_flags {
|
||||
TPM_GLOBAL_INT_ENABLE = 0x80000000,
|
||||
TPM_INTF_BURST_COUNT_STATIC = 0x100,
|
||||
TPM_INTF_CMD_READY_INT = 0x080,
|
||||
TPM_INTF_INT_EDGE_FALLING = 0x040,
|
||||
TPM_INTF_INT_EDGE_RISING = 0x020,
|
||||
TPM_INTF_INT_LEVEL_LOW = 0x010,
|
||||
TPM_INTF_INT_LEVEL_HIGH = 0x008,
|
||||
TPM_INTF_LOCALITY_CHANGE_INT = 0x004,
|
||||
TPM_INTF_STS_VALID_INT = 0x002,
|
||||
TPM_INTF_DATA_AVAIL_INT = 0x001,
|
||||
};
|
||||
|
||||
enum tis_defaults {
|
||||
TIS_MEM_BASE = 0xFED40000,
|
||||
TIS_MEM_LEN = 0x5000,
|
||||
TIS_SHORT_TIMEOUT = 750, /* ms */
|
||||
TIS_LONG_TIMEOUT = 2000, /* 2 sec */
|
||||
};
|
||||
|
||||
#define TPM_ACCESS(l) (0x0000 | ((l) << 12))
|
||||
#define TPM_INT_ENABLE(l) (0x0008 | ((l) << 12))
|
||||
#define TPM_INT_VECTOR(l) (0x000C | ((l) << 12))
|
||||
#define TPM_INT_STATUS(l) (0x0010 | ((l) << 12))
|
||||
#define TPM_INTF_CAPS(l) (0x0014 | ((l) << 12))
|
||||
#define TPM_STS(l) (0x0018 | ((l) << 12))
|
||||
#define TPM_DATA_FIFO(l) (0x0024 | ((l) << 12))
|
||||
|
||||
#define TPM_DID_VID(l) (0x0F00 | ((l) << 12))
|
||||
#define TPM_RID(l) (0x0F04 | ((l) << 12))
|
||||
|
||||
static LIST_HEAD(tis_chips);
|
||||
static DEFINE_SPINLOCK(tis_lock);
|
||||
|
||||
static int check_locality(struct tpm_chip *chip, int l)
|
||||
{
|
||||
if ((ioread8(chip->vendor.iobase + TPM_ACCESS(l)) &
|
||||
(TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) ==
|
||||
(TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID))
|
||||
return chip->vendor.locality = l;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void release_locality(struct tpm_chip *chip, int l, int force)
|
||||
{
|
||||
if (force || (ioread8(chip->vendor.iobase + TPM_ACCESS(l)) &
|
||||
(TPM_ACCESS_REQUEST_PENDING | TPM_ACCESS_VALID)) ==
|
||||
(TPM_ACCESS_REQUEST_PENDING | TPM_ACCESS_VALID))
|
||||
iowrite8(TPM_ACCESS_ACTIVE_LOCALITY,
|
||||
chip->vendor.iobase + TPM_ACCESS(l));
|
||||
}
|
||||
|
||||
static int request_locality(struct tpm_chip *chip, int l)
|
||||
{
|
||||
unsigned long stop;
|
||||
long rc;
|
||||
|
||||
if (check_locality(chip, l) >= 0)
|
||||
return l;
|
||||
|
||||
iowrite8(TPM_ACCESS_REQUEST_USE,
|
||||
chip->vendor.iobase + TPM_ACCESS(l));
|
||||
|
||||
if (chip->vendor.irq) {
|
||||
rc = wait_event_interruptible_timeout(chip->vendor.int_queue,
|
||||
(check_locality
|
||||
(chip, l) >= 0),
|
||||
chip->vendor.timeout_a);
|
||||
if (rc > 0)
|
||||
return l;
|
||||
|
||||
} else {
|
||||
/* wait for burstcount */
|
||||
stop = jiffies + chip->vendor.timeout_a;
|
||||
do {
|
||||
if (check_locality(chip, l) >= 0)
|
||||
return l;
|
||||
msleep(TPM_TIMEOUT);
|
||||
}
|
||||
while (time_before(jiffies, stop));
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static u8 tpm_tis_status(struct tpm_chip *chip)
|
||||
{
|
||||
return ioread8(chip->vendor.iobase +
|
||||
TPM_STS(chip->vendor.locality));
|
||||
}
|
||||
|
||||
static void tpm_tis_ready(struct tpm_chip *chip)
|
||||
{
|
||||
/* this causes the current command to be aborted */
|
||||
iowrite8(TPM_STS_COMMAND_READY,
|
||||
chip->vendor.iobase + TPM_STS(chip->vendor.locality));
|
||||
}
|
||||
|
||||
static int get_burstcount(struct tpm_chip *chip)
|
||||
{
|
||||
unsigned long stop;
|
||||
int burstcnt;
|
||||
|
||||
/* wait for burstcount */
|
||||
/* which timeout value, spec has 2 answers (c & d) */
|
||||
stop = jiffies + chip->vendor.timeout_d;
|
||||
do {
|
||||
burstcnt = ioread8(chip->vendor.iobase +
|
||||
TPM_STS(chip->vendor.locality) + 1);
|
||||
burstcnt += ioread8(chip->vendor.iobase +
|
||||
TPM_STS(chip->vendor.locality) +
|
||||
2) << 8;
|
||||
if (burstcnt)
|
||||
return burstcnt;
|
||||
msleep(TPM_TIMEOUT);
|
||||
} while (time_before(jiffies, stop));
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
static int wait_for_stat(struct tpm_chip *chip, u8 mask, unsigned long timeout,
|
||||
wait_queue_head_t *queue)
|
||||
{
|
||||
unsigned long stop;
|
||||
long rc;
|
||||
u8 status;
|
||||
|
||||
/* check current status */
|
||||
status = tpm_tis_status(chip);
|
||||
if ((status & mask) == mask)
|
||||
return 0;
|
||||
|
||||
if (chip->vendor.irq) {
|
||||
rc = wait_event_interruptible_timeout(*queue,
|
||||
((tpm_tis_status
|
||||
(chip) & mask) ==
|
||||
mask), timeout);
|
||||
if (rc > 0)
|
||||
return 0;
|
||||
} else {
|
||||
stop = jiffies + timeout;
|
||||
do {
|
||||
msleep(TPM_TIMEOUT);
|
||||
status = tpm_tis_status(chip);
|
||||
if ((status & mask) == mask)
|
||||
return 0;
|
||||
} while (time_before(jiffies, stop));
|
||||
}
|
||||
return -ETIME;
|
||||
}
|
||||
|
||||
static int recv_data(struct tpm_chip *chip, u8 *buf, size_t count)
|
||||
{
|
||||
int size = 0, burstcnt;
|
||||
while (size < count &&
|
||||
wait_for_stat(chip,
|
||||
TPM_STS_DATA_AVAIL | TPM_STS_VALID,
|
||||
chip->vendor.timeout_c,
|
||||
&chip->vendor.read_queue)
|
||||
== 0) {
|
||||
burstcnt = get_burstcount(chip);
|
||||
for (; burstcnt > 0 && size < count; burstcnt--)
|
||||
buf[size++] = ioread8(chip->vendor.iobase +
|
||||
TPM_DATA_FIFO(chip->vendor.
|
||||
locality));
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
static int tpm_tis_recv(struct tpm_chip *chip, u8 *buf, size_t count)
|
||||
{
|
||||
int size = 0;
|
||||
int expected, status;
|
||||
|
||||
if (count < TPM_HEADER_SIZE) {
|
||||
size = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* read first 10 bytes, including tag, paramsize, and result */
|
||||
if ((size =
|
||||
recv_data(chip, buf, TPM_HEADER_SIZE)) < TPM_HEADER_SIZE) {
|
||||
dev_err(chip->dev, "Unable to read header\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
expected = be32_to_cpu(*(__be32 *) (buf + 2));
|
||||
if (expected > count) {
|
||||
size = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if ((size +=
|
||||
recv_data(chip, &buf[TPM_HEADER_SIZE],
|
||||
expected - TPM_HEADER_SIZE)) < expected) {
|
||||
dev_err(chip->dev, "Unable to read remainder of result\n");
|
||||
size = -ETIME;
|
||||
goto out;
|
||||
}
|
||||
|
||||
wait_for_stat(chip, TPM_STS_VALID, chip->vendor.timeout_c,
|
||||
&chip->vendor.int_queue);
|
||||
status = tpm_tis_status(chip);
|
||||
if (status & TPM_STS_DATA_AVAIL) { /* retry? */
|
||||
dev_err(chip->dev, "Error left over data\n");
|
||||
size = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
out:
|
||||
tpm_tis_ready(chip);
|
||||
release_locality(chip, chip->vendor.locality, 0);
|
||||
return size;
|
||||
}
|
||||
|
||||
/*
|
||||
* If interrupts are used (signaled by an irq set in the vendor structure)
|
||||
* tpm.c can skip polling for the data to be available as the interrupt is
|
||||
* waited for here
|
||||
*/
|
||||
static int tpm_tis_send(struct tpm_chip *chip, u8 *buf, size_t len)
|
||||
{
|
||||
int rc, status, burstcnt;
|
||||
size_t count = 0;
|
||||
u32 ordinal;
|
||||
|
||||
if (request_locality(chip, 0) < 0)
|
||||
return -EBUSY;
|
||||
|
||||
status = tpm_tis_status(chip);
|
||||
if ((status & TPM_STS_COMMAND_READY) == 0) {
|
||||
tpm_tis_ready(chip);
|
||||
if (wait_for_stat
|
||||
(chip, TPM_STS_COMMAND_READY, chip->vendor.timeout_b,
|
||||
&chip->vendor.int_queue) < 0) {
|
||||
rc = -ETIME;
|
||||
goto out_err;
|
||||
}
|
||||
}
|
||||
|
||||
while (count < len - 1) {
|
||||
burstcnt = get_burstcount(chip);
|
||||
for (; burstcnt > 0 && count < len - 1; burstcnt--) {
|
||||
iowrite8(buf[count], chip->vendor.iobase +
|
||||
TPM_DATA_FIFO(chip->vendor.locality));
|
||||
count++;
|
||||
}
|
||||
|
||||
wait_for_stat(chip, TPM_STS_VALID, chip->vendor.timeout_c,
|
||||
&chip->vendor.int_queue);
|
||||
status = tpm_tis_status(chip);
|
||||
if ((status & TPM_STS_DATA_EXPECT) == 0) {
|
||||
rc = -EIO;
|
||||
goto out_err;
|
||||
}
|
||||
}
|
||||
|
||||
/* write last byte */
|
||||
iowrite8(buf[count],
|
||||
chip->vendor.iobase +
|
||||
TPM_DATA_FIFO(chip->vendor.locality));
|
||||
wait_for_stat(chip, TPM_STS_VALID, chip->vendor.timeout_c,
|
||||
&chip->vendor.int_queue);
|
||||
status = tpm_tis_status(chip);
|
||||
if ((status & TPM_STS_DATA_EXPECT) != 0) {
|
||||
rc = -EIO;
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
/* go and do it */
|
||||
iowrite8(TPM_STS_GO,
|
||||
chip->vendor.iobase + TPM_STS(chip->vendor.locality));
|
||||
|
||||
if (chip->vendor.irq) {
|
||||
ordinal = be32_to_cpu(*((__be32 *) (buf + 6)));
|
||||
if (wait_for_stat
|
||||
(chip, TPM_STS_DATA_AVAIL | TPM_STS_VALID,
|
||||
tpm_calc_ordinal_duration(chip, ordinal),
|
||||
&chip->vendor.read_queue) < 0) {
|
||||
rc = -ETIME;
|
||||
goto out_err;
|
||||
}
|
||||
}
|
||||
return len;
|
||||
out_err:
|
||||
tpm_tis_ready(chip);
|
||||
release_locality(chip, chip->vendor.locality, 0);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static const struct file_operations tis_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.llseek = no_llseek,
|
||||
.open = tpm_open,
|
||||
.read = tpm_read,
|
||||
.write = tpm_write,
|
||||
.release = tpm_release,
|
||||
};
|
||||
|
||||
static DEVICE_ATTR(pubek, S_IRUGO, tpm_show_pubek, NULL);
|
||||
static DEVICE_ATTR(pcrs, S_IRUGO, tpm_show_pcrs, NULL);
|
||||
static DEVICE_ATTR(enabled, S_IRUGO, tpm_show_enabled, NULL);
|
||||
static DEVICE_ATTR(active, S_IRUGO, tpm_show_active, NULL);
|
||||
static DEVICE_ATTR(owned, S_IRUGO, tpm_show_owned, NULL);
|
||||
static DEVICE_ATTR(temp_deactivated, S_IRUGO, tpm_show_temp_deactivated,
|
||||
NULL);
|
||||
static DEVICE_ATTR(caps, S_IRUGO, tpm_show_caps_1_2, NULL);
|
||||
static DEVICE_ATTR(cancel, S_IWUSR | S_IWGRP, NULL, tpm_store_cancel);
|
||||
|
||||
static struct attribute *tis_attrs[] = {
|
||||
&dev_attr_pubek.attr,
|
||||
&dev_attr_pcrs.attr,
|
||||
&dev_attr_enabled.attr,
|
||||
&dev_attr_active.attr,
|
||||
&dev_attr_owned.attr,
|
||||
&dev_attr_temp_deactivated.attr,
|
||||
&dev_attr_caps.attr,
|
||||
&dev_attr_cancel.attr, NULL,
|
||||
};
|
||||
|
||||
static struct attribute_group tis_attr_grp = {
|
||||
.attrs = tis_attrs
|
||||
};
|
||||
|
||||
static struct tpm_vendor_specific tpm_tis = {
|
||||
.status = tpm_tis_status,
|
||||
.recv = tpm_tis_recv,
|
||||
.send = tpm_tis_send,
|
||||
.cancel = tpm_tis_ready,
|
||||
.req_complete_mask = TPM_STS_DATA_AVAIL | TPM_STS_VALID,
|
||||
.req_complete_val = TPM_STS_DATA_AVAIL | TPM_STS_VALID,
|
||||
.req_canceled = TPM_STS_COMMAND_READY,
|
||||
.attr_group = &tis_attr_grp,
|
||||
.miscdev = {
|
||||
.fops = &tis_ops,},
|
||||
};
|
||||
|
||||
static irqreturn_t tis_int_probe(int irq, void *dev_id)
|
||||
{
|
||||
struct tpm_chip *chip = (struct tpm_chip *) dev_id;
|
||||
u32 interrupt;
|
||||
|
||||
interrupt = ioread32(chip->vendor.iobase +
|
||||
TPM_INT_STATUS(chip->vendor.locality));
|
||||
|
||||
if (interrupt == 0)
|
||||
return IRQ_NONE;
|
||||
|
||||
chip->vendor.irq = irq;
|
||||
|
||||
/* Clear interrupts handled with TPM_EOI */
|
||||
iowrite32(interrupt,
|
||||
chip->vendor.iobase +
|
||||
TPM_INT_STATUS(chip->vendor.locality));
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t tis_int_handler(int irq, void *dev_id)
|
||||
{
|
||||
struct tpm_chip *chip = (struct tpm_chip *) dev_id;
|
||||
u32 interrupt;
|
||||
int i;
|
||||
|
||||
interrupt = ioread32(chip->vendor.iobase +
|
||||
TPM_INT_STATUS(chip->vendor.locality));
|
||||
|
||||
if (interrupt == 0)
|
||||
return IRQ_NONE;
|
||||
|
||||
if (interrupt & TPM_INTF_DATA_AVAIL_INT)
|
||||
wake_up_interruptible(&chip->vendor.read_queue);
|
||||
if (interrupt & TPM_INTF_LOCALITY_CHANGE_INT)
|
||||
for (i = 0; i < 5; i++)
|
||||
if (check_locality(chip, i) >= 0)
|
||||
break;
|
||||
if (interrupt &
|
||||
(TPM_INTF_LOCALITY_CHANGE_INT | TPM_INTF_STS_VALID_INT |
|
||||
TPM_INTF_CMD_READY_INT))
|
||||
wake_up_interruptible(&chip->vendor.int_queue);
|
||||
|
||||
/* Clear interrupts handled with TPM_EOI */
|
||||
iowrite32(interrupt,
|
||||
chip->vendor.iobase +
|
||||
TPM_INT_STATUS(chip->vendor.locality));
|
||||
ioread32(chip->vendor.iobase + TPM_INT_STATUS(chip->vendor.locality));
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int interrupts = 1;
|
||||
module_param(interrupts, bool, 0444);
|
||||
MODULE_PARM_DESC(interrupts, "Enable interrupts");
|
||||
|
||||
static int tpm_tis_init(struct device *dev, resource_size_t start,
|
||||
resource_size_t len)
|
||||
{
|
||||
u32 vendor, intfcaps, intmask;
|
||||
int rc, i;
|
||||
struct tpm_chip *chip;
|
||||
|
||||
if (!start)
|
||||
start = TIS_MEM_BASE;
|
||||
if (!len)
|
||||
len = TIS_MEM_LEN;
|
||||
|
||||
if (!(chip = tpm_register_hardware(dev, &tpm_tis)))
|
||||
return -ENODEV;
|
||||
|
||||
chip->vendor.iobase = ioremap(start, len);
|
||||
if (!chip->vendor.iobase) {
|
||||
rc = -EIO;
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
vendor = ioread32(chip->vendor.iobase + TPM_DID_VID(0));
|
||||
|
||||
/* Default timeouts */
|
||||
chip->vendor.timeout_a = msecs_to_jiffies(TIS_SHORT_TIMEOUT);
|
||||
chip->vendor.timeout_b = msecs_to_jiffies(TIS_LONG_TIMEOUT);
|
||||
chip->vendor.timeout_c = msecs_to_jiffies(TIS_SHORT_TIMEOUT);
|
||||
chip->vendor.timeout_d = msecs_to_jiffies(TIS_SHORT_TIMEOUT);
|
||||
|
||||
dev_info(dev,
|
||||
"1.2 TPM (device-id 0x%X, rev-id %d)\n",
|
||||
vendor >> 16, ioread8(chip->vendor.iobase + TPM_RID(0)));
|
||||
|
||||
/* Figure out the capabilities */
|
||||
intfcaps =
|
||||
ioread32(chip->vendor.iobase +
|
||||
TPM_INTF_CAPS(chip->vendor.locality));
|
||||
dev_dbg(dev, "TPM interface capabilities (0x%x):\n",
|
||||
intfcaps);
|
||||
if (intfcaps & TPM_INTF_BURST_COUNT_STATIC)
|
||||
dev_dbg(dev, "\tBurst Count Static\n");
|
||||
if (intfcaps & TPM_INTF_CMD_READY_INT)
|
||||
dev_dbg(dev, "\tCommand Ready Int Support\n");
|
||||
if (intfcaps & TPM_INTF_INT_EDGE_FALLING)
|
||||
dev_dbg(dev, "\tInterrupt Edge Falling\n");
|
||||
if (intfcaps & TPM_INTF_INT_EDGE_RISING)
|
||||
dev_dbg(dev, "\tInterrupt Edge Rising\n");
|
||||
if (intfcaps & TPM_INTF_INT_LEVEL_LOW)
|
||||
dev_dbg(dev, "\tInterrupt Level Low\n");
|
||||
if (intfcaps & TPM_INTF_INT_LEVEL_HIGH)
|
||||
dev_dbg(dev, "\tInterrupt Level High\n");
|
||||
if (intfcaps & TPM_INTF_LOCALITY_CHANGE_INT)
|
||||
dev_dbg(dev, "\tLocality Change Int Support\n");
|
||||
if (intfcaps & TPM_INTF_STS_VALID_INT)
|
||||
dev_dbg(dev, "\tSts Valid Int Support\n");
|
||||
if (intfcaps & TPM_INTF_DATA_AVAIL_INT)
|
||||
dev_dbg(dev, "\tData Avail Int Support\n");
|
||||
|
||||
if (request_locality(chip, 0) != 0) {
|
||||
rc = -ENODEV;
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
/* INTERRUPT Setup */
|
||||
init_waitqueue_head(&chip->vendor.read_queue);
|
||||
init_waitqueue_head(&chip->vendor.int_queue);
|
||||
|
||||
intmask =
|
||||
ioread32(chip->vendor.iobase +
|
||||
TPM_INT_ENABLE(chip->vendor.locality));
|
||||
|
||||
intmask |= TPM_INTF_CMD_READY_INT
|
||||
| TPM_INTF_LOCALITY_CHANGE_INT | TPM_INTF_DATA_AVAIL_INT
|
||||
| TPM_INTF_STS_VALID_INT;
|
||||
|
||||
iowrite32(intmask,
|
||||
chip->vendor.iobase +
|
||||
TPM_INT_ENABLE(chip->vendor.locality));
|
||||
if (interrupts) {
|
||||
chip->vendor.irq =
|
||||
ioread8(chip->vendor.iobase +
|
||||
TPM_INT_VECTOR(chip->vendor.locality));
|
||||
|
||||
for (i = 3; i < 16 && chip->vendor.irq == 0; i++) {
|
||||
iowrite8(i, chip->vendor.iobase +
|
||||
TPM_INT_VECTOR(chip->vendor.locality));
|
||||
if (request_irq
|
||||
(i, tis_int_probe, IRQF_SHARED,
|
||||
chip->vendor.miscdev.name, chip) != 0) {
|
||||
dev_info(chip->dev,
|
||||
"Unable to request irq: %d for probe\n",
|
||||
i);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Clear all existing */
|
||||
iowrite32(ioread32
|
||||
(chip->vendor.iobase +
|
||||
TPM_INT_STATUS(chip->vendor.locality)),
|
||||
chip->vendor.iobase +
|
||||
TPM_INT_STATUS(chip->vendor.locality));
|
||||
|
||||
/* Turn on */
|
||||
iowrite32(intmask | TPM_GLOBAL_INT_ENABLE,
|
||||
chip->vendor.iobase +
|
||||
TPM_INT_ENABLE(chip->vendor.locality));
|
||||
|
||||
/* Generate Interrupts */
|
||||
tpm_gen_interrupt(chip);
|
||||
|
||||
/* Turn off */
|
||||
iowrite32(intmask,
|
||||
chip->vendor.iobase +
|
||||
TPM_INT_ENABLE(chip->vendor.locality));
|
||||
free_irq(i, chip);
|
||||
}
|
||||
}
|
||||
if (chip->vendor.irq) {
|
||||
iowrite8(chip->vendor.irq,
|
||||
chip->vendor.iobase +
|
||||
TPM_INT_VECTOR(chip->vendor.locality));
|
||||
if (request_irq
|
||||
(chip->vendor.irq, tis_int_handler, IRQF_SHARED,
|
||||
chip->vendor.miscdev.name, chip) != 0) {
|
||||
dev_info(chip->dev,
|
||||
"Unable to request irq: %d for use\n",
|
||||
chip->vendor.irq);
|
||||
chip->vendor.irq = 0;
|
||||
} else {
|
||||
/* Clear all existing */
|
||||
iowrite32(ioread32
|
||||
(chip->vendor.iobase +
|
||||
TPM_INT_STATUS(chip->vendor.locality)),
|
||||
chip->vendor.iobase +
|
||||
TPM_INT_STATUS(chip->vendor.locality));
|
||||
|
||||
/* Turn on */
|
||||
iowrite32(intmask | TPM_GLOBAL_INT_ENABLE,
|
||||
chip->vendor.iobase +
|
||||
TPM_INT_ENABLE(chip->vendor.locality));
|
||||
}
|
||||
}
|
||||
|
||||
INIT_LIST_HEAD(&chip->vendor.list);
|
||||
spin_lock(&tis_lock);
|
||||
list_add(&chip->vendor.list, &tis_chips);
|
||||
spin_unlock(&tis_lock);
|
||||
|
||||
tpm_get_timeouts(chip);
|
||||
tpm_continue_selftest(chip);
|
||||
|
||||
return 0;
|
||||
out_err:
|
||||
if (chip->vendor.iobase)
|
||||
iounmap(chip->vendor.iobase);
|
||||
tpm_remove_hardware(chip->dev);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int __devinit tpm_tis_pnp_init(struct pnp_dev *pnp_dev,
|
||||
const struct pnp_device_id *pnp_id)
|
||||
{
|
||||
resource_size_t start, len;
|
||||
start = pnp_mem_start(pnp_dev, 0);
|
||||
len = pnp_mem_len(pnp_dev, 0);
|
||||
|
||||
return tpm_tis_init(&pnp_dev->dev, start, len);
|
||||
}
|
||||
|
||||
static int tpm_tis_pnp_suspend(struct pnp_dev *dev, pm_message_t msg)
|
||||
{
|
||||
return tpm_pm_suspend(&dev->dev, msg);
|
||||
}
|
||||
|
||||
static int tpm_tis_pnp_resume(struct pnp_dev *dev)
|
||||
{
|
||||
return tpm_pm_resume(&dev->dev);
|
||||
}
|
||||
|
||||
static struct pnp_device_id tpm_pnp_tbl[] __devinitdata = {
|
||||
{"PNP0C31", 0}, /* TPM */
|
||||
{"ATM1200", 0}, /* Atmel */
|
||||
{"IFX0102", 0}, /* Infineon */
|
||||
{"BCM0101", 0}, /* Broadcom */
|
||||
{"NSC1200", 0}, /* National */
|
||||
/* Add new here */
|
||||
{"", 0}, /* User Specified */
|
||||
{"", 0} /* Terminator */
|
||||
};
|
||||
|
||||
static struct pnp_driver tis_pnp_driver = {
|
||||
.name = "tpm_tis",
|
||||
.id_table = tpm_pnp_tbl,
|
||||
.probe = tpm_tis_pnp_init,
|
||||
.suspend = tpm_tis_pnp_suspend,
|
||||
.resume = tpm_tis_pnp_resume,
|
||||
};
|
||||
|
||||
#define TIS_HID_USR_IDX sizeof(tpm_pnp_tbl)/sizeof(struct pnp_device_id) -2
|
||||
module_param_string(hid, tpm_pnp_tbl[TIS_HID_USR_IDX].id,
|
||||
sizeof(tpm_pnp_tbl[TIS_HID_USR_IDX].id), 0444);
|
||||
MODULE_PARM_DESC(hid, "Set additional specific HID for this driver to probe");
|
||||
|
||||
static struct device_driver tis_drv = {
|
||||
.name = "tpm_tis",
|
||||
.bus = &platform_bus_type,
|
||||
.owner = THIS_MODULE,
|
||||
.suspend = tpm_pm_suspend,
|
||||
.resume = tpm_pm_resume,
|
||||
};
|
||||
|
||||
static struct platform_device *pdev;
|
||||
|
||||
static int force;
|
||||
module_param(force, bool, 0444);
|
||||
MODULE_PARM_DESC(force, "Force device probe rather than using ACPI entry");
|
||||
static int __init init_tis(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (force) {
|
||||
rc = driver_register(&tis_drv);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
if (IS_ERR(pdev=platform_device_register_simple("tpm_tis", -1, NULL, 0)))
|
||||
return PTR_ERR(pdev);
|
||||
if((rc=tpm_tis_init(&pdev->dev, 0, 0)) != 0) {
|
||||
platform_device_unregister(pdev);
|
||||
driver_unregister(&tis_drv);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
return pnp_register_driver(&tis_pnp_driver);
|
||||
}
|
||||
|
||||
static void __exit cleanup_tis(void)
|
||||
{
|
||||
struct tpm_vendor_specific *i, *j;
|
||||
struct tpm_chip *chip;
|
||||
spin_lock(&tis_lock);
|
||||
list_for_each_entry_safe(i, j, &tis_chips, list) {
|
||||
chip = to_tpm_chip(i);
|
||||
iowrite32(~TPM_GLOBAL_INT_ENABLE &
|
||||
ioread32(chip->vendor.iobase +
|
||||
TPM_INT_ENABLE(chip->vendor.
|
||||
locality)),
|
||||
chip->vendor.iobase +
|
||||
TPM_INT_ENABLE(chip->vendor.locality));
|
||||
release_locality(chip, chip->vendor.locality, 1);
|
||||
if (chip->vendor.irq)
|
||||
free_irq(chip->vendor.irq, chip);
|
||||
iounmap(i->iobase);
|
||||
list_del(&i->list);
|
||||
tpm_remove_hardware(chip->dev);
|
||||
}
|
||||
spin_unlock(&tis_lock);
|
||||
if (force) {
|
||||
platform_device_unregister(pdev);
|
||||
driver_unregister(&tis_drv);
|
||||
} else
|
||||
pnp_unregister_driver(&tis_pnp_driver);
|
||||
}
|
||||
|
||||
module_init(init_tis);
|
||||
module_exit(cleanup_tis);
|
||||
MODULE_AUTHOR("Leendert van Doorn (leendert@watson.ibm.com)");
|
||||
MODULE_DESCRIPTION("TPM Driver");
|
||||
MODULE_VERSION("2.0");
|
||||
MODULE_LICENSE("GPL");
|
||||
Reference in New Issue
Block a user