Initial commit; kernel source import

This commit is contained in:
Nathan
2025-04-06 23:50:55 -05:00
commit 25c6d769f4
45093 changed files with 18199410 additions and 0 deletions

94
drivers/char/tpm/Kconfig Normal file
View File

@@ -0,0 +1,94 @@
#
# TPM device configuration
#
menuconfig TCG_TPM
tristate "TPM Hardware Support"
depends on HAS_IOMEM
select SECURITYFS
---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.
Notes:
1) For more TPM drivers enable CONFIG_PNP, CONFIG_ACPI
and CONFIG_PNPACPI.
2) Without ACPI enabled, the BIOS event log won't be accessible,
which is required to validate the PCR 0-7 values.
if TCG_TPM
config TCG_TIS
tristate "TPM Interface Specification 1.2 Interface"
depends on X86
---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_TIS_I2C_INFINEON
tristate "TPM Interface Specification 1.2 Interface (I2C - Infineon)"
depends on I2C
---help---
If you have a TPM security chip that is compliant with the
TCG TIS 1.2 TPM specification and Infineon's I2C Protocol Stack
Specification 0.20 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_i2c_infineon.
config TCG_NSC
tristate "National Semiconductor TPM Interface"
depends on X86
---help---
If you have a TPM security chip from National Semiconductor
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 PPC64 || HAS_IOPORT
---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 PNP
---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.trust.rub.de/projects/linux-device-driver-infineon-tpm/
config TCG_IBMVTPM
tristate "IBM VTPM Interface"
depends on PPC_PSERIES
---help---
If you have IBM virtual TPM (VTPM) support 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_ibmvtpm.
config TCG_ST33_I2C
tristate "STMicroelectronics ST33 I2C TPM"
depends on I2C
depends on GPIOLIB
---help---
If you have a TPM security chip from STMicroelectronics working with
an I2C bus 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_stm_st33_i2c.
endif # TCG_TPM

20
drivers/char/tpm/Makefile Normal file
View File

@@ -0,0 +1,20 @@
#
# Makefile for the kernel tpm device drivers.
#
obj-$(CONFIG_TCG_TPM) += tpm.o
ifdef CONFIG_ACPI
obj-$(CONFIG_TCG_TPM) += tpm_bios.o
tpm_bios-objs += tpm_eventlog.o tpm_acpi.o tpm_ppi.o
else
ifdef CONFIG_TCG_IBMVTPM
obj-$(CONFIG_TCG_TPM) += tpm_bios.o
tpm_bios-objs += tpm_eventlog.o tpm_of.o
endif
endif
obj-$(CONFIG_TCG_TIS) += tpm_tis.o
obj-$(CONFIG_TCG_TIS_I2C_INFINEON) += tpm_i2c_infineon.o
obj-$(CONFIG_TCG_NSC) += tpm_nsc.o
obj-$(CONFIG_TCG_ATMEL) += tpm_atmel.o
obj-$(CONFIG_TCG_INFINEON) += tpm_infineon.o
obj-$(CONFIG_TCG_IBMVTPM) += tpm_ibmvtpm.o
obj-$(CONFIG_TCG_ST33_I2C) += tpm_i2c_stm_st33.o

1662
drivers/char/tpm/tpm.c Normal file

File diff suppressed because it is too large Load Diff

363
drivers/char/tpm/tpm.h Normal file
View File

@@ -0,0 +1,363 @@
/*
* 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/delay.h>
#include <linux/fs.h>
#include <linux/mutex.h>
#include <linux/sched.h>
#include <linux/miscdevice.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/tpm.h>
enum tpm_const {
TPM_MINOR = 224, /* officially assigned */
TPM_BUFSIZE = 4096,
TPM_NUM_DEVICES = 256,
TPM_RETRY = 50, /* 5 seconds */
};
enum tpm_timeout {
TPM_TIMEOUT = 5, /* msecs */
TPM_TIMEOUT_RETRY = 100 /* msecs */
};
/* TPM addresses */
enum tpm_addr {
TPM_SUPERIO_ADDR = 0x2E,
TPM_ADDR = 0x4E,
};
#define TPM_WARN_RETRY 0x800
#define TPM_WARN_DOING_SELFTEST 0x802
#define TPM_ERR_DEACTIVATED 0x6
#define TPM_ERR_DISABLED 0x7
#define TPM_ERR_INVALID_POSTINIT 38
#define TPM_HEADER_SIZE 10
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 *);
extern ssize_t tpm_show_durations(struct device *,
struct device_attribute *attr, char *);
extern ssize_t tpm_show_timeouts(struct device *,
struct device_attribute *attr, char *);
struct tpm_chip;
struct tpm_vendor_specific {
const u8 req_complete_mask;
const u8 req_complete_val;
bool (*req_canceled)(struct tpm_chip *chip, u8 status);
void __iomem *iobase; /* ioremapped address */
unsigned long base; /* TPM base address */
int irq;
int probed_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 *);
void (*release) (struct device *);
void (*update_timeouts)(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 */
bool timeout_adjusted;
unsigned long duration[3]; /* jiffies */
bool duration_adjusted;
void *priv;
wait_queue_head_t read_queue;
wait_queue_head_t int_queue;
u16 manufacturer_id;
};
#define TPM_VPRIV(c) (c)->vendor.priv
#define TPM_VID_INTEL 0x8086
#define TPM_VID_WINBOND 0x1050
#define TPM_VID_STM 0x104A
struct tpm_chip {
struct device *dev; /* Device stuff */
int dev_num; /* /dev/tpm# */
unsigned long is_open; /* 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 mutex buffer_mutex;
int needs_resume;
struct mutex resume_mutex;
struct timer_list user_read_timer; /* user needs to claim result */
struct work_struct work;
struct mutex tpm_mutex; /* tpm is processing */
struct tpm_vendor_specific vendor;
struct dentry **bios_dir;
struct list_head list;
void (*release) (struct device *);
struct notifier_block shutdown_nb;
};
#define to_tpm_chip(n) container_of(n, struct tpm_chip, vendor)
static inline void tpm_chip_put(struct tpm_chip *chip)
{
module_put(chip->dev->driver->owner);
}
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);
}
struct tpm_input_header {
__be16 tag;
__be32 length;
__be32 ordinal;
} __packed;
struct tpm_output_header {
__be16 tag;
__be32 length;
__be32 return_code;
} __packed;
struct stclear_flags_t {
__be16 tag;
u8 deactivated;
u8 disableForceClear;
u8 physicalPresence;
u8 physicalPresenceLock;
u8 bGlobalLock;
} __packed;
struct tpm_version_t {
u8 Major;
u8 Minor;
u8 revMajor;
u8 revMinor;
} __packed;
struct tpm_version_1_2_t {
__be16 tag;
u8 Major;
u8 Minor;
u8 revMajor;
u8 revMinor;
} __packed;
struct timeout_t {
__be32 a;
__be32 b;
__be32 c;
__be32 d;
} __packed;
struct duration_t {
__be32 tpm_short;
__be32 tpm_medium;
__be32 tpm_long;
} __packed;
struct permanent_flags_t {
__be16 tag;
u8 disable;
u8 ownership;
u8 deactivated;
u8 readPubek;
u8 disableOwnerClear;
u8 allowMaintenance;
u8 physicalPresenceLifetimeLock;
u8 physicalPresenceHWEnable;
u8 physicalPresenceCMDEnable;
u8 CEKPUsed;
u8 TPMpost;
u8 TPMpostLock;
u8 FIPS;
u8 operator;
u8 enableRevokeEK;
u8 nvLocked;
u8 readSRKPub;
u8 tpmEstablished;
u8 maintenanceDone;
u8 disableFullDALogicInfo;
} __packed;
typedef union {
struct permanent_flags_t perm_flags;
struct stclear_flags_t stclear_flags;
bool owned;
__be32 num_pcrs;
struct tpm_version_t tpm_version;
struct tpm_version_1_2_t tpm_version_1_2;
__be32 manufacturer_id;
struct timeout_t timeout;
struct duration_t duration;
} cap_t;
struct tpm_getcap_params_in {
__be32 cap;
__be32 subcap_size;
__be32 subcap;
} __packed;
struct tpm_getcap_params_out {
__be32 cap_size;
cap_t cap;
} __packed;
struct tpm_readpubek_params_out {
u8 algorithm[4];
u8 encscheme[2];
u8 sigscheme[2];
__be32 paramsize;
u8 parameters[12]; /*assuming RSA*/
__be32 keysize;
u8 modulus[256];
u8 checksum[20];
} __packed;
typedef union {
struct tpm_input_header in;
struct tpm_output_header out;
} tpm_cmd_header;
#define TPM_DIGEST_SIZE 20
struct tpm_pcrread_out {
u8 pcr_result[TPM_DIGEST_SIZE];
} __packed;
struct tpm_pcrread_in {
__be32 pcr_idx;
} __packed;
struct tpm_pcrextend_in {
__be32 pcr_idx;
u8 hash[TPM_DIGEST_SIZE];
} __packed;
/* 128 bytes is an arbitrary cap. This could be as large as TPM_BUFSIZE - 18
* bytes, but 128 is still a relatively large number of random bytes and
* anything much bigger causes users of struct tpm_cmd_t to start getting
* compiler warnings about stack frame size. */
#define TPM_MAX_RNG_DATA 128
struct tpm_getrandom_out {
__be32 rng_data_len;
u8 rng_data[TPM_MAX_RNG_DATA];
} __packed;
struct tpm_getrandom_in {
__be32 num_bytes;
} __packed;
struct tpm_startup_in {
__be16 startup_type;
} __packed;
typedef union {
struct tpm_getcap_params_out getcap_out;
struct tpm_readpubek_params_out readpubek_out;
u8 readpubek_out_buffer[sizeof(struct tpm_readpubek_params_out)];
struct tpm_getcap_params_in getcap_in;
struct tpm_pcrread_in pcrread_in;
struct tpm_pcrread_out pcrread_out;
struct tpm_pcrextend_in pcrextend_in;
struct tpm_getrandom_in getrandom_in;
struct tpm_getrandom_out getrandom_out;
struct tpm_startup_in startup_in;
} tpm_cmd_params;
struct tpm_cmd_t {
tpm_cmd_header header;
tpm_cmd_params params;
} __packed;
ssize_t tpm_getcap(struct device *, __be32, cap_t *, const char *);
extern int tpm_get_timeouts(struct tpm_chip *);
extern void tpm_gen_interrupt(struct tpm_chip *);
extern int tpm_do_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 void tpm_dev_vendor_release(struct tpm_chip *);
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 *);
extern int tpm_pm_resume(struct device *);
extern int wait_for_tpm_stat(struct tpm_chip *, u8, unsigned long,
wait_queue_head_t *, bool);
#ifdef CONFIG_ACPI
extern int tpm_add_ppi(struct kobject *);
extern void tpm_remove_ppi(struct kobject *);
#else
static inline int tpm_add_ppi(struct kobject *parent)
{
return 0;
}
static inline void tpm_remove_ppi(struct kobject *parent)
{
}
#endif

109
drivers/char/tpm/tpm_acpi.c Normal file
View File

@@ -0,0 +1,109 @@
/*
* 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>
*
* Maintained by: <tpmdd-devel@lists.sourceforge.net>
*
* 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 <linux/slab.h>
#include <acpi/acpi.h>
#include "tpm.h"
#include "tpm_eventlog.h"
struct acpi_tcpa {
struct acpi_table_header hdr;
u16 platform_class;
union {
struct client_hdr {
u32 log_max_len __packed;
u64 log_start_addr __packed;
} client;
struct server_hdr {
u16 reserved;
u64 log_max_len __packed;
u64 log_start_addr __packed;
} server;
};
};
/* read binary bios log */
int read_log(struct tpm_bios_log *log)
{
struct acpi_tcpa *buff;
acpi_status status;
void __iomem *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);
if (!virt) {
kfree(log->bios_event_log);
printk("%s: ERROR - Unable to map memory\n", __func__);
return -EIO;
}
memcpy_fromio(log->bios_event_log, virt, len);
acpi_os_unmap_memory(virt, len);
return 0;
}

View File

@@ -0,0 +1,249 @@
/*
* 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 bool tpm_atml_req_canceled(struct tpm_chip *chip, u8 status)
{
return (status == ATML_STATUS_READY);
}
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 = tpm_atml_req_canceled,
.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 SIMPLE_DEV_PM_OPS(tpm_atml_pm, tpm_pm_suspend, tpm_pm_resume);
static struct platform_driver atml_drv = {
.driver = {
.name = "tpm_atmel",
.owner = THIS_MODULE,
.pm = &tpm_atml_pm,
},
};
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 = platform_driver_register(&atml_drv);
if (rc)
return rc;
if ((iobase = atmel_get_base_addr(&base, &region_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:
platform_driver_unregister(&atml_drv);
return rc;
}
static void __exit cleanup_atmel(void)
{
platform_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");

View File

@@ -0,0 +1,131 @@
/*
* 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
#include <asm/prom.h>
#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 (!of_device_is_compatible(dn, "AT97SC3201")) {
of_node_put(dn);
return NULL;
}
reg = of_get_property(dn, "reg", &reglen);
naddrc = of_n_addr_cells(dn);
nsizec = of_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

View File

@@ -0,0 +1,419 @@
/*
* Copyright (C) 2005, 2012 IBM Corporation
*
* Authors:
* Kent Yoder <key@linux.vnet.ibm.com>
* Seiji Munetoh <munetoh@jp.ibm.com>
* Stefan Berger <stefanb@us.ibm.com>
* Reiner Sailer <sailer@watson.ibm.com>
* Kylene Hall <kjhall@us.ibm.com>
*
* Maintained by: <tpmdd-devel@lists.sourceforge.net>
*
* Access to the eventlog created by a system's firmware / BIOS
*
* 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 <linux/slab.h>
#include "tpm.h"
#include "tpm_eventlog.h"
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"
};
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 = "";
/* 41 so there is room for 40 data and 1 nul */
char data[41] = "";
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 const 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 const 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,
};
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)))
goto out_free;
/* now register seq file */
err = seq_open(file, &tpm_ascii_b_measurments_seqops);
if (!err) {
seq = file->private_data;
seq->private = log;
} else {
goto out_free;
}
out:
return err;
out_free:
kfree(log->bios_event_log);
kfree(log);
goto out;
}
static 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)))
goto out_free;
/* now register seq file */
err = seq_open(file, &tpm_binary_b_measurments_seqops);
if (!err) {
seq = file->private_data;
seq->private = log;
} else {
goto out_free;
}
out:
return err;
out_free:
kfree(log->bios_event_log);
kfree(log);
goto out;
}
static 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");

View File

@@ -0,0 +1,86 @@
#ifndef __TPM_EVENTLOG_H__
#define __TPM_EVENTLOG_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 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,
};
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,
};
int read_log(struct tpm_bios_log *log);
#if defined(CONFIG_TCG_IBMVTPM) || defined(CONFIG_TCG_IBMVTPM_MODULE) || \
defined(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
#endif

View File

@@ -0,0 +1,838 @@
/*
* Copyright (C) 2012,2013 Infineon Technologies
*
* Authors:
* Peter Huewe <peter.huewe@infineon.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 and the
* Infineon I2C Protocol Stack Specification v0.20.
*
* It is based on the original tpm_tis device driver from Leendert van
* Dorn and Kyleen Hall.
*
* 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/i2c.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/wait.h>
#include <linux/workqueue.h>
#include "tpm.h"
/* max. buffer size supported by our TPM */
#define TPM_BUFSIZE 1260
/* max. number of iterations after I2C NAK */
#define MAX_COUNT 3
#define SLEEP_DURATION_LOW 55
#define SLEEP_DURATION_HI 65
/* max. number of iterations after I2C NAK for 'long' commands
* we need this especially for sending TPM_READY, since the cleanup after the
* transtion to the ready state may take some time, but it is unpredictable
* how long it will take.
*/
#define MAX_COUNT_LONG 50
#define SLEEP_DURATION_LONG_LOW 200
#define SLEEP_DURATION_LONG_HI 220
/* After sending TPM_READY to 'reset' the TPM we have to sleep even longer */
#define SLEEP_DURATION_RESET_LOW 2400
#define SLEEP_DURATION_RESET_HI 2600
/* we want to use usleep_range instead of msleep for the 5ms TPM_TIMEOUT */
#define TPM_TIMEOUT_US_LOW (TPM_TIMEOUT * 1000)
#define TPM_TIMEOUT_US_HI (TPM_TIMEOUT_US_LOW + 2000)
/* expected value for DIDVID register */
#define TPM_TIS_I2C_DID_VID_9635 0xd1150b00L
#define TPM_TIS_I2C_DID_VID_9645 0x001a15d1L
enum i2c_chip_type {
SLB9635,
SLB9645,
UNKNOWN,
};
/* Structure to store I2C TPM specific stuff */
struct tpm_inf_dev {
struct i2c_client *client;
u8 buf[TPM_BUFSIZE + sizeof(u8)]; /* max. buffer size + addr */
struct tpm_chip *chip;
enum i2c_chip_type chip_type;
bool powered_while_suspended;
struct work_struct init_work;
};
static struct tpm_inf_dev tpm_dev;
static struct i2c_driver tpm_tis_i2c_driver;
/*
* iic_tpm_read() - read from TPM register
* @addr: register address to read from
* @buffer: provided by caller
* @len: number of bytes to read
*
* Read len bytes from TPM register and put them into
* buffer (little-endian format, i.e. first byte is put into buffer[0]).
*
* NOTE: TPM is big-endian for multi-byte values. Multi-byte
* values have to be swapped.
*
* NOTE: We can't unfortunately use the combined read/write functions
* provided by the i2c core as the TPM currently does not support the
* repeated start condition and due to it's special requirements.
* The i2c_smbus* functions do not work for this chip.
*
* Return -EIO on error, 0 on success.
*/
static int iic_tpm_read(u8 addr, u8 *buffer, size_t len)
{
struct i2c_msg msg1 = {
.addr = tpm_dev.client->addr,
.len = 1,
.buf = &addr
};
struct i2c_msg msg2 = {
.addr = tpm_dev.client->addr,
.flags = I2C_M_RD,
.len = len,
.buf = buffer
};
struct i2c_msg msgs[] = {msg1, msg2};
int rc = 0;
int count;
if (work_pending(&tpm_dev.init_work))
flush_work(&tpm_dev.init_work);
/* Lock the adapter for the duration of the whole sequence. */
if (!tpm_dev.client->adapter->algo->master_xfer)
return -EOPNOTSUPP;
i2c_lock_adapter(tpm_dev.client->adapter);
if (tpm_dev.chip_type == SLB9645) {
/* use a combined read for newer chips
* unfortunately the smbus functions are not suitable due to
* the 32 byte limit of the smbus.
* retries should usually not be needed, but are kept just to
* be on the safe side.
*/
for (count = 0; count < MAX_COUNT; count++) {
rc = __i2c_transfer(tpm_dev.client->adapter, msgs, 2);
if (rc > 0)
break; /* break here to skip sleep */
usleep_range(SLEEP_DURATION_LOW, SLEEP_DURATION_HI);
}
} else {
/* slb9635 protocol should work in all cases */
for (count = 0; count < MAX_COUNT; count++) {
rc = __i2c_transfer(tpm_dev.client->adapter, &msg1, 1);
if (rc > 0)
break; /* break here to skip sleep */
usleep_range(SLEEP_DURATION_LOW, SLEEP_DURATION_HI);
}
if (rc <= 0)
goto out;
/* After the TPM has successfully received the register address
* it needs some time, thus we're sleeping here again, before
* retrieving the data
*/
for (count = 0; count < MAX_COUNT; count++) {
usleep_range(SLEEP_DURATION_LOW, SLEEP_DURATION_HI);
rc = __i2c_transfer(tpm_dev.client->adapter, &msg2, 1);
if (rc > 0)
break;
}
}
out:
i2c_unlock_adapter(tpm_dev.client->adapter);
/* take care of 'guard time' */
usleep_range(SLEEP_DURATION_LOW, SLEEP_DURATION_HI);
/* __i2c_transfer returns the number of successfully transferred
* messages.
* So rc should be greater than 0 here otherwise we have an error.
*/
if (rc <= 0)
return -EIO;
return 0;
}
static int iic_tpm_write_generic(u8 addr, u8 *buffer, size_t len,
unsigned int sleep_low,
unsigned int sleep_hi, u8 max_count)
{
int rc = -EIO;
int count;
struct i2c_msg msg1 = {
.addr = tpm_dev.client->addr,
.len = len + 1,
.buf = tpm_dev.buf
};
if (work_pending(&tpm_dev.init_work))
flush_work(&tpm_dev.init_work);
if (len > TPM_BUFSIZE)
return -EINVAL;
if (!tpm_dev.client->adapter->algo->master_xfer)
return -EOPNOTSUPP;
i2c_lock_adapter(tpm_dev.client->adapter);
/* prepend the 'register address' to the buffer */
tpm_dev.buf[0] = addr;
memcpy(&(tpm_dev.buf[1]), buffer, len);
/*
* NOTE: We have to use these special mechanisms here and unfortunately
* cannot rely on the standard behavior of i2c_transfer.
* Even for newer chips the smbus functions are not
* suitable due to the 32 byte limit of the smbus.
*/
for (count = 0; count < max_count; count++) {
rc = __i2c_transfer(tpm_dev.client->adapter, &msg1, 1);
if (rc > 0)
break;
usleep_range(sleep_low, sleep_hi);
}
i2c_unlock_adapter(tpm_dev.client->adapter);
/* take care of 'guard time' */
usleep_range(SLEEP_DURATION_LOW, SLEEP_DURATION_HI);
/* __i2c_transfer returns the number of successfully transferred
* messages.
* So rc should be greater than 0 here otherwise we have an error.
*/
if (rc <= 0)
return -EIO;
return 0;
}
/*
* iic_tpm_write() - write to TPM register
* @addr: register address to write to
* @buffer: containing data to be written
* @len: number of bytes to write
*
* Write len bytes from provided buffer to TPM register (little
* endian format, i.e. buffer[0] is written as first byte).
*
* NOTE: TPM is big-endian for multi-byte values. Multi-byte
* values have to be swapped.
*
* NOTE: use this function instead of the iic_tpm_write_generic function.
*
* Return -EIO on error, 0 on success
*/
static int iic_tpm_write(u8 addr, u8 *buffer, size_t len)
{
return iic_tpm_write_generic(addr, buffer, len, SLEEP_DURATION_LOW,
SLEEP_DURATION_HI, MAX_COUNT);
}
/*
* This function is needed especially for the cleanup situation after
* sending TPM_READY
* */
static int iic_tpm_write_long(u8 addr, u8 *buffer, size_t len)
{
return iic_tpm_write_generic(addr, buffer, len, SLEEP_DURATION_LONG_LOW,
SLEEP_DURATION_LONG_HI, MAX_COUNT_LONG);
}
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_defaults {
TIS_SHORT_TIMEOUT = 750, /* ms */
TIS_LONG_TIMEOUT = 2000, /* 2 sec */
};
#define TPM_ACCESS(l) (0x0000 | ((l) << 4))
#define TPM_STS(l) (0x0001 | ((l) << 4))
#define TPM_DATA_FIFO(l) (0x0005 | ((l) << 4))
#define TPM_DID_VID(l) (0x0006 | ((l) << 4))
static int check_locality(struct tpm_chip *chip, int loc)
{
u8 buf;
int rc;
rc = iic_tpm_read(TPM_ACCESS(loc), &buf, 1);
if (rc < 0)
return rc;
if ((buf & (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) ==
(TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) {
chip->vendor.locality = loc;
return loc;
}
return -EIO;
}
/* implementation similar to tpm_tis */
static void release_locality(struct tpm_chip *chip, int loc, int force)
{
u8 buf;
if (iic_tpm_read(TPM_ACCESS(loc), &buf, 1) < 0)
return;
if (force || (buf & (TPM_ACCESS_REQUEST_PENDING | TPM_ACCESS_VALID)) ==
(TPM_ACCESS_REQUEST_PENDING | TPM_ACCESS_VALID)) {
buf = TPM_ACCESS_ACTIVE_LOCALITY;
iic_tpm_write(TPM_ACCESS(loc), &buf, 1);
}
}
static int request_locality(struct tpm_chip *chip, int loc)
{
unsigned long stop;
u8 buf = TPM_ACCESS_REQUEST_USE;
if (check_locality(chip, loc) >= 0)
return loc;
iic_tpm_write(TPM_ACCESS(loc), &buf, 1);
/* wait for burstcount */
stop = jiffies + chip->vendor.timeout_a;
do {
if (check_locality(chip, loc) >= 0)
return loc;
usleep_range(TPM_TIMEOUT_US_LOW, TPM_TIMEOUT_US_HI);
} while (time_before(jiffies, stop));
return -ETIME;
}
static u8 tpm_tis_i2c_status(struct tpm_chip *chip)
{
/* NOTE: since I2C read may fail, return 0 in this case --> time-out */
u8 buf = 0xFF;
u8 i = 0;
do {
if (iic_tpm_read(TPM_STS(chip->vendor.locality), &buf, 1) < 0)
return 0;
i++;
/* if locallity is set STS should not be 0xFF */
} while ((buf == 0xFF) && i < 10);
return buf;
}
static void tpm_tis_i2c_ready(struct tpm_chip *chip)
{
/* this causes the current command to be aborted */
u8 buf = TPM_STS_COMMAND_READY;
iic_tpm_write_long(TPM_STS(chip->vendor.locality), &buf, 1);
}
static ssize_t get_burstcount(struct tpm_chip *chip)
{
unsigned long stop;
ssize_t burstcnt;
u8 buf[3];
/* wait for burstcount */
/* which timeout value, spec has 2 answers (c & d) */
stop = jiffies + chip->vendor.timeout_d;
do {
/* Note: STS is little endian */
if (iic_tpm_read(TPM_STS(chip->vendor.locality)+1, buf, 3) < 0)
burstcnt = 0;
else
burstcnt = (buf[2] << 16) + (buf[1] << 8) + buf[0];
if (burstcnt)
return burstcnt;
usleep_range(TPM_TIMEOUT_US_LOW, TPM_TIMEOUT_US_HI);
} while (time_before(jiffies, stop));
return -EBUSY;
}
static int wait_for_stat(struct tpm_chip *chip, u8 mask, unsigned long timeout,
int *status)
{
unsigned long stop;
/* check current status */
*status = tpm_tis_i2c_status(chip);
if ((*status != 0xFF) && (*status & mask) == mask)
return 0;
stop = jiffies + timeout;
do {
/* since we just checked the status, give the TPM some time */
usleep_range(TPM_TIMEOUT_US_LOW, TPM_TIMEOUT_US_HI);
*status = tpm_tis_i2c_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)
{
size_t size = 0;
ssize_t burstcnt;
u8 retries = 0;
int rc;
while (size < count) {
burstcnt = get_burstcount(chip);
/* burstcnt < 0 = TPM is busy */
if (burstcnt < 0)
return burstcnt;
/* limit received data to max. left */
if (burstcnt > (count - size))
burstcnt = count - size;
rc = iic_tpm_read(TPM_DATA_FIFO(chip->vendor.locality),
&(buf[size]), burstcnt);
if (rc == 0)
size += burstcnt;
else if (rc < 0)
retries++;
/* avoid endless loop in case of broken HW */
if (retries > MAX_COUNT_LONG)
return -EIO;
}
return size;
}
static int tpm_tis_i2c_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 */
size = recv_data(chip, buf, TPM_HEADER_SIZE);
if (size < TPM_HEADER_SIZE) {
dev_err(chip->dev, "Unable to read header\n");
goto out;
}
expected = be32_to_cpu(*(__be32 *)(buf + 2));
if ((size_t) expected > count) {
size = -EIO;
goto out;
}
size += recv_data(chip, &buf[TPM_HEADER_SIZE],
expected - TPM_HEADER_SIZE);
if (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, &status);
if (status & TPM_STS_DATA_AVAIL) { /* retry? */
dev_err(chip->dev, "Error left over data\n");
size = -EIO;
goto out;
}
out:
tpm_tis_i2c_ready(chip);
/* The TPM needs some time to clean up here,
* so we sleep rather than keeping the bus busy
*/
usleep_range(SLEEP_DURATION_RESET_LOW, SLEEP_DURATION_RESET_HI);
release_locality(chip, chip->vendor.locality, 0);
return size;
}
static int tpm_tis_i2c_send(struct tpm_chip *chip, u8 *buf, size_t len)
{
int rc, status;
ssize_t burstcnt;
size_t count = 0;
u8 retries = 0;
u8 sts = TPM_STS_GO;
if (len > TPM_BUFSIZE)
return -E2BIG; /* command is too long for our tpm, sorry */
if (request_locality(chip, 0) < 0)
return -EBUSY;
status = tpm_tis_i2c_status(chip);
if ((status & TPM_STS_COMMAND_READY) == 0) {
tpm_tis_i2c_ready(chip);
if (wait_for_stat
(chip, TPM_STS_COMMAND_READY,
chip->vendor.timeout_b, &status) < 0) {
rc = -ETIME;
goto out_err;
}
}
while (count < len - 1) {
burstcnt = get_burstcount(chip);
/* burstcnt < 0 = TPM is busy */
if (burstcnt < 0)
return burstcnt;
if (burstcnt > (len - 1 - count))
burstcnt = len - 1 - count;
rc = iic_tpm_write(TPM_DATA_FIFO(chip->vendor.locality),
&(buf[count]), burstcnt);
if (rc == 0)
count += burstcnt;
else if (rc < 0)
retries++;
/* avoid endless loop in case of broken HW */
if (retries > MAX_COUNT_LONG) {
rc = -EIO;
goto out_err;
}
wait_for_stat(chip, TPM_STS_VALID,
chip->vendor.timeout_c, &status);
if ((status & TPM_STS_DATA_EXPECT) == 0) {
rc = -EIO;
goto out_err;
}
}
/* write last byte */
iic_tpm_write(TPM_DATA_FIFO(chip->vendor.locality), &(buf[count]), 1);
wait_for_stat(chip, TPM_STS_VALID, chip->vendor.timeout_c, &status);
if ((status & TPM_STS_DATA_EXPECT) != 0) {
rc = -EIO;
goto out_err;
}
/* go and do it */
iic_tpm_write(TPM_STS(chip->vendor.locality), &sts, 1);
return len;
out_err:
tpm_tis_i2c_ready(chip);
/* The TPM needs some time to clean up here,
* so we sleep rather than keeping the bus busy
*/
usleep_range(SLEEP_DURATION_RESET_LOW, SLEEP_DURATION_RESET_HI);
release_locality(chip, chip->vendor.locality, 0);
return rc;
}
static bool tpm_tis_i2c_req_canceled(struct tpm_chip *chip, u8 status)
{
return (status == TPM_STS_COMMAND_READY);
}
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 DEVICE_ATTR(durations, S_IRUGO, tpm_show_durations, NULL);
static DEVICE_ATTR(timeouts, S_IRUGO, tpm_show_timeouts, NULL);
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,
&dev_attr_durations.attr,
&dev_attr_timeouts.attr,
NULL,
};
static struct attribute_group tis_attr_grp = {
.attrs = tis_attrs
};
static struct tpm_vendor_specific tpm_tis_i2c = {
.status = tpm_tis_i2c_status,
.recv = tpm_tis_i2c_recv,
.send = tpm_tis_i2c_send,
.cancel = tpm_tis_i2c_ready,
.req_complete_mask = TPM_STS_DATA_AVAIL | TPM_STS_VALID,
.req_complete_val = TPM_STS_DATA_AVAIL | TPM_STS_VALID,
.req_canceled = tpm_tis_i2c_req_canceled,
.attr_group = &tis_attr_grp,
.miscdev.fops = &tis_ops,
};
static void tpm_tis_i2c_selftest(struct work_struct *work)
{
tpm_do_selftest(tpm_dev.chip);
}
static int tpm_tis_i2c_init(struct device *dev)
{
u32 vendor;
int rc = 0;
struct tpm_chip *chip;
chip = tpm_register_hardware(dev, &tpm_tis_i2c);
if (!chip) {
dev_err(dev, "could not register hardware\n");
rc = -ENODEV;
goto out_err;
}
/* Disable interrupts */
chip->vendor.irq = 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);
if (request_locality(chip, 0) != 0) {
dev_err(dev, "could not request locality\n");
rc = -ENODEV;
goto out_vendor;
}
/* read four bytes from DID_VID register */
if (iic_tpm_read(TPM_DID_VID(0), (u8 *)&vendor, 4) < 0) {
dev_err(dev, "could not read vendor id\n");
rc = -EIO;
goto out_release;
}
if (vendor == TPM_TIS_I2C_DID_VID_9645) {
tpm_dev.chip_type = SLB9645;
} else if (vendor == TPM_TIS_I2C_DID_VID_9635) {
tpm_dev.chip_type = SLB9635;
} else {
dev_err(dev, "vendor id did not match! ID was %08x\n", vendor);
rc = -ENODEV;
goto out_release;
}
dev_info(dev, "1.2 TPM (device-id 0x%X)\n", vendor >> 16);
INIT_LIST_HEAD(&chip->vendor.list);
tpm_dev.chip = chip;
if (tpm_get_timeouts(chip)) {
dev_err(dev, "Could not get TPM timeouts and durations\n");
rc = -ENODEV;
goto out_release;
}
INIT_WORK(&tpm_dev.init_work, tpm_tis_i2c_selftest);
schedule_work(&tpm_dev.init_work);
if (dev->of_node &&
of_get_property(dev->of_node, "powered-while-suspended", NULL)) {
tpm_dev.powered_while_suspended = true;
}
return 0;
out_release:
release_locality(chip, chip->vendor.locality, 1);
out_vendor:
/* close file handles */
tpm_dev_vendor_release(chip);
/* remove hardware */
tpm_remove_hardware(chip->dev);
/* reset these pointers, otherwise we oops */
chip->dev->release = NULL;
chip->release = NULL;
tpm_dev.client = NULL;
dev_set_drvdata(chip->dev, chip);
out_err:
return rc;
}
static const struct i2c_device_id tpm_tis_i2c_table[] = {
{"tpm_i2c_infineon", 0},
{"slb9635tt", 0},
{"slb9645tt", 1},
{},
};
MODULE_DEVICE_TABLE(i2c, tpm_tis_i2c_table);
#ifdef CONFIG_OF
static const struct of_device_id tpm_tis_i2c_of_match[] = {
{
.name = "tpm_i2c_infineon",
.type = "tpm",
.compatible = "infineon,tpm_i2c_infineon",
.data = (void *)0
},
{
.name = "slb9635tt",
.type = "tpm",
.compatible = "infineon,slb9635tt",
.data = (void *)0
},
{
.name = "slb9645tt",
.type = "tpm",
.compatible = "infineon,slb9645tt",
.data = (void *)1
},
{},
};
MODULE_DEVICE_TABLE(of, tpm_tis_i2c_of_match);
#endif
static int tpm_tis_i2c_suspend(struct device *dev)
{
if (tpm_dev.powered_while_suspended)
return 0;
return tpm_pm_suspend(dev);
}
static int tpm_tis_i2c_resume(struct device *dev)
{
if (tpm_dev.powered_while_suspended)
return 0;
return tpm_pm_resume(dev);
}
static SIMPLE_DEV_PM_OPS(tpm_tis_i2c_ops, tpm_tis_i2c_suspend,
tpm_tis_i2c_resume);
static int tpm_tis_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
int rc;
struct device *dev = &(client->dev);
if (tpm_dev.client != NULL) {
dev_err(dev, "This driver only supports one client at a time\n");
return -EBUSY; /* We only support one client */
}
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
dev_err(dev, "no algorithms associated to the i2c bus\n");
return -ENODEV;
}
client->driver = &tpm_tis_i2c_driver;
tpm_dev.client = client;
rc = tpm_tis_i2c_init(&client->dev);
if (rc != 0) {
client->driver = NULL;
tpm_dev.client = NULL;
rc = -ENODEV;
}
return rc;
}
static int tpm_tis_i2c_remove(struct i2c_client *client)
{
struct tpm_chip *chip = tpm_dev.chip;
release_locality(chip, chip->vendor.locality, 1);
cancel_work_sync(&tpm_dev.init_work);
/* close file handles */
tpm_dev_vendor_release(chip);
/* remove hardware */
tpm_remove_hardware(chip->dev);
/* reset these pointers, otherwise we oops */
chip->dev->release = NULL;
chip->release = NULL;
tpm_dev.client = NULL;
dev_set_drvdata(chip->dev, chip);
return 0;
}
static struct i2c_driver tpm_tis_i2c_driver = {
.id_table = tpm_tis_i2c_table,
.probe = tpm_tis_i2c_probe,
.remove = tpm_tis_i2c_remove,
.driver = {
.name = "tpm_i2c_infineon",
.owner = THIS_MODULE,
.pm = &tpm_tis_i2c_ops,
.of_match_table = of_match_ptr(tpm_tis_i2c_of_match),
},
};
module_i2c_driver(tpm_tis_i2c_driver);
MODULE_AUTHOR("Peter Huewe <peter.huewe@infineon.com>");
MODULE_DESCRIPTION("TPM TIS I2C Infineon Driver");
MODULE_VERSION("2.2.0");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,885 @@
/*
* STMicroelectronics TPM I2C Linux driver for TPM ST33ZP24
* Copyright (C) 2009, 2010 STMicroelectronics
*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* STMicroelectronics version 1.2.0, Copyright (C) 2010
* STMicroelectronics comes with ABSOLUTELY NO WARRANTY.
* This is free software, and you are welcome to redistribute it
* under certain conditions.
*
* @Author: Christophe RICARD tpmsupport@st.com
*
* @File: tpm_stm_st33_i2c.c
*
* @Synopsis:
* 09/15/2010: First shot driver tpm_tis driver for
lpc is used as model.
*/
#include <linux/pci.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/wait.h>
#include <linux/string.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/sysfs.h>
#include <linux/gpio.h>
#include <linux/sched.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/slab.h>
#include "tpm.h"
#include "tpm_i2c_stm_st33.h"
enum stm33zp24_access {
TPM_ACCESS_VALID = 0x80,
TPM_ACCESS_ACTIVE_LOCALITY = 0x20,
TPM_ACCESS_REQUEST_PENDING = 0x04,
TPM_ACCESS_REQUEST_USE = 0x02,
};
enum stm33zp24_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 stm33zp24_int_flags {
TPM_GLOBAL_INT_ENABLE = 0x80,
TPM_INTF_CMD_READY_INT = 0x080,
TPM_INTF_FIFO_AVALAIBLE_INT = 0x040,
TPM_INTF_WAKE_UP_READY_INT = 0x020,
TPM_INTF_LOCALITY_CHANGE_INT = 0x004,
TPM_INTF_STS_VALID_INT = 0x002,
TPM_INTF_DATA_AVAIL_INT = 0x001,
};
enum tis_defaults {
TIS_SHORT_TIMEOUT = 750,
TIS_LONG_TIMEOUT = 2000,
};
/*
* write8_reg
* Send byte to the TIS register according to the ST33ZP24 I2C protocol.
* @param: tpm_register, the tpm tis register where the data should be written
* @param: tpm_data, the tpm_data to write inside the tpm_register
* @param: tpm_size, The length of the data
* @return: Returns negative errno, or else the number of bytes written.
*/
static int write8_reg(struct i2c_client *client, u8 tpm_register,
u8 *tpm_data, u16 tpm_size)
{
struct st33zp24_platform_data *pin_infos;
pin_infos = client->dev.platform_data;
pin_infos->tpm_i2c_buffer[0][0] = tpm_register;
memcpy(&pin_infos->tpm_i2c_buffer[0][1], tpm_data, tpm_size);
return i2c_master_send(client, pin_infos->tpm_i2c_buffer[0],
tpm_size + 1);
} /* write8_reg() */
/*
* read8_reg
* Recv byte from the TIS register according to the ST33ZP24 I2C protocol.
* @param: tpm_register, the tpm tis register where the data should be read
* @param: tpm_data, the TPM response
* @param: tpm_size, tpm TPM response size to read.
* @return: number of byte read successfully: should be one if success.
*/
static int read8_reg(struct i2c_client *client, u8 tpm_register,
u8 *tpm_data, int tpm_size)
{
u8 status = 0;
u8 data;
data = TPM_DUMMY_BYTE;
status = write8_reg(client, tpm_register, &data, 1);
if (status == 2)
status = i2c_master_recv(client, tpm_data, tpm_size);
return status;
} /* read8_reg() */
/*
* I2C_WRITE_DATA
* Send byte to the TIS register according to the ST33ZP24 I2C protocol.
* @param: client, the chip description
* @param: tpm_register, the tpm tis register where the data should be written
* @param: tpm_data, the tpm_data to write inside the tpm_register
* @param: tpm_size, The length of the data
* @return: number of byte written successfully: should be one if success.
*/
#define I2C_WRITE_DATA(client, tpm_register, tpm_data, tpm_size) \
(write8_reg(client, tpm_register | \
TPM_WRITE_DIRECTION, tpm_data, tpm_size))
/*
* I2C_READ_DATA
* Recv byte from the TIS register according to the ST33ZP24 I2C protocol.
* @param: tpm, the chip description
* @param: tpm_register, the tpm tis register where the data should be read
* @param: tpm_data, the TPM response
* @param: tpm_size, tpm TPM response size to read.
* @return: number of byte read successfully: should be one if success.
*/
#define I2C_READ_DATA(client, tpm_register, tpm_data, tpm_size) \
(read8_reg(client, tpm_register, tpm_data, tpm_size))
/*
* clear_interruption
* clear the TPM interrupt register.
* @param: tpm, the chip description
*/
static void clear_interruption(struct i2c_client *client)
{
u8 interrupt;
I2C_READ_DATA(client, TPM_INT_STATUS, &interrupt, 1);
I2C_WRITE_DATA(client, TPM_INT_STATUS, &interrupt, 1);
I2C_READ_DATA(client, TPM_INT_STATUS, &interrupt, 1);
} /* clear_interruption() */
/*
* _wait_for_interrupt_serirq_timeout
* @param: tpm, the chip description
* @param: timeout, the timeout of the interrupt
* @return: the status of the interruption.
*/
static long _wait_for_interrupt_serirq_timeout(struct tpm_chip *chip,
unsigned long timeout)
{
long status;
struct i2c_client *client;
struct st33zp24_platform_data *pin_infos;
client = (struct i2c_client *)TPM_VPRIV(chip);
pin_infos = client->dev.platform_data;
status = wait_for_completion_interruptible_timeout(
&pin_infos->irq_detection,
timeout);
if (status > 0)
enable_irq(gpio_to_irq(pin_infos->io_serirq));
gpio_direction_input(pin_infos->io_serirq);
return status;
} /* wait_for_interrupt_serirq_timeout() */
static int wait_for_serirq_timeout(struct tpm_chip *chip, bool condition,
unsigned long timeout)
{
int status = 2;
struct i2c_client *client;
client = (struct i2c_client *)TPM_VPRIV(chip);
status = _wait_for_interrupt_serirq_timeout(chip, timeout);
if (!status) {
status = -EBUSY;
} else {
clear_interruption(client);
if (condition)
status = 1;
}
return status;
}
/*
* tpm_stm_i2c_cancel, cancel is not implemented.
* @param: chip, the tpm_chip description as specified in driver/char/tpm/tpm.h
*/
static void tpm_stm_i2c_cancel(struct tpm_chip *chip)
{
struct i2c_client *client;
u8 data;
client = (struct i2c_client *)TPM_VPRIV(chip);
data = TPM_STS_COMMAND_READY;
I2C_WRITE_DATA(client, TPM_STS, &data, 1);
if (chip->vendor.irq)
wait_for_serirq_timeout(chip, 1, chip->vendor.timeout_a);
} /* tpm_stm_i2c_cancel() */
/*
* tpm_stm_spi_status return the TPM_STS register
* @param: chip, the tpm chip description
* @return: the TPM_STS register value.
*/
static u8 tpm_stm_i2c_status(struct tpm_chip *chip)
{
struct i2c_client *client;
u8 data;
client = (struct i2c_client *)TPM_VPRIV(chip);
I2C_READ_DATA(client, TPM_STS, &data, 1);
return data;
} /* tpm_stm_i2c_status() */
/*
* check_locality if the locality is active
* @param: chip, the tpm chip description
* @return: the active locality or -EACCESS.
*/
static int check_locality(struct tpm_chip *chip)
{
struct i2c_client *client;
u8 data;
u8 status;
client = (struct i2c_client *)TPM_VPRIV(chip);
status = I2C_READ_DATA(client, TPM_ACCESS, &data, 1);
if (status && (data &
(TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) ==
(TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID))
return chip->vendor.locality;
return -EACCES;
} /* check_locality() */
/*
* request_locality request the TPM locality
* @param: chip, the chip description
* @return: the active locality or EACCESS.
*/
static int request_locality(struct tpm_chip *chip)
{
unsigned long stop;
long rc;
struct i2c_client *client;
u8 data;
client = (struct i2c_client *)TPM_VPRIV(chip);
if (check_locality(chip) == chip->vendor.locality)
return chip->vendor.locality;
data = TPM_ACCESS_REQUEST_USE;
rc = I2C_WRITE_DATA(client, TPM_ACCESS, &data, 1);
if (rc < 0)
goto end;
if (chip->vendor.irq) {
rc = wait_for_serirq_timeout(chip, (check_locality
(chip) >= 0),
chip->vendor.timeout_a);
if (rc > 0)
return chip->vendor.locality;
} else {
stop = jiffies + chip->vendor.timeout_a;
do {
if (check_locality(chip) >= 0)
return chip->vendor.locality;
msleep(TPM_TIMEOUT);
} while (time_before(jiffies, stop));
}
rc = -EACCES;
end:
return rc;
} /* request_locality() */
/*
* release_locality release the active locality
* @param: chip, the tpm chip description.
*/
static void release_locality(struct tpm_chip *chip)
{
struct i2c_client *client;
u8 data;
client = (struct i2c_client *)TPM_VPRIV(chip);
data = TPM_ACCESS_ACTIVE_LOCALITY;
I2C_WRITE_DATA(client, TPM_ACCESS, &data, 1);
}
/*
* get_burstcount return the burstcount address 0x19 0x1A
* @param: chip, the chip description
* return: the burstcount.
*/
static int get_burstcount(struct tpm_chip *chip)
{
unsigned long stop;
int burstcnt, status;
u8 tpm_reg, temp;
struct i2c_client *client = (struct i2c_client *)TPM_VPRIV(chip);
stop = jiffies + chip->vendor.timeout_d;
do {
tpm_reg = TPM_STS + 1;
status = I2C_READ_DATA(client, tpm_reg, &temp, 1);
if (status < 0)
goto end;
tpm_reg = tpm_reg + 1;
burstcnt = temp;
status = I2C_READ_DATA(client, tpm_reg, &temp, 1);
if (status < 0)
goto end;
burstcnt |= temp << 8;
if (burstcnt)
return burstcnt;
msleep(TPM_TIMEOUT);
} while (time_before(jiffies, stop));
end:
return -EBUSY;
} /* get_burstcount() */
/*
* wait_for_stat wait for a TPM_STS value
* @param: chip, the tpm chip description
* @param: mask, the value mask to wait
* @param: timeout, the timeout
* @param: queue, the wait queue.
* @return: the tpm status, 0 if success, -ETIME if timeout is reached.
*/
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;
if (chip->vendor.irq) {
rc = wait_for_serirq_timeout(chip, ((tpm_stm_i2c_status
(chip) & mask) ==
mask), timeout);
if (rc > 0)
return 0;
} else {
stop = jiffies + timeout;
do {
msleep(TPM_TIMEOUT);
status = tpm_stm_i2c_status(chip);
if ((status & mask) == mask)
return 0;
} while (time_before(jiffies, stop));
}
return -ETIME;
} /* wait_for_stat() */
/*
* recv_data receive data
* @param: chip, the tpm chip description
* @param: buf, the buffer where the data are received
* @param: count, the number of data to receive
* @return: the number of bytes read from TPM FIFO.
*/
static int recv_data(struct tpm_chip *chip, u8 *buf, size_t count)
{
int size = 0, burstcnt, len;
struct i2c_client *client;
client = (struct i2c_client *)TPM_VPRIV(chip);
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);
len = min_t(int, burstcnt, count - size);
I2C_READ_DATA(client, TPM_DATA_FIFO, buf + size, len);
size += len;
}
return size;
}
/*
* tpm_ioserirq_handler the serirq irq handler
* @param: irq, the tpm chip description
* @param: dev_id, the description of the chip
* @return: the status of the handler.
*/
static irqreturn_t tpm_ioserirq_handler(int irq, void *dev_id)
{
struct tpm_chip *chip = dev_id;
struct i2c_client *client;
struct st33zp24_platform_data *pin_infos;
disable_irq_nosync(irq);
client = (struct i2c_client *)TPM_VPRIV(chip);
pin_infos = client->dev.platform_data;
complete(&pin_infos->irq_detection);
return IRQ_HANDLED;
} /* tpm_ioserirq_handler() */
/*
* tpm_stm_i2c_send send TPM commands through the I2C bus.
*
* @param: chip, the tpm_chip description as specified in driver/char/tpm/tpm.h
* @param: buf, the buffer to send.
* @param: count, the number of bytes to send.
* @return: In case of success the number of bytes sent.
* In other case, a < 0 value describing the issue.
*/
static int tpm_stm_i2c_send(struct tpm_chip *chip, unsigned char *buf,
size_t len)
{
u32 status, burstcnt = 0, i, size;
int ret;
u8 data;
struct i2c_client *client;
if (chip == NULL)
return -EBUSY;
if (len < TPM_HEADER_SIZE)
return -EBUSY;
client = (struct i2c_client *)TPM_VPRIV(chip);
client->flags = 0;
ret = request_locality(chip);
if (ret < 0)
return ret;
status = tpm_stm_i2c_status(chip);
if ((status & TPM_STS_COMMAND_READY) == 0) {
tpm_stm_i2c_cancel(chip);
if (wait_for_stat
(chip, TPM_STS_COMMAND_READY, chip->vendor.timeout_b,
&chip->vendor.int_queue) < 0) {
ret = -ETIME;
goto out_err;
}
}
for (i = 0; i < len - 1;) {
burstcnt = get_burstcount(chip);
size = min_t(int, len - i - 1, burstcnt);
ret = I2C_WRITE_DATA(client, TPM_DATA_FIFO, buf, size);
if (ret < 0)
goto out_err;
i += size;
}
status = tpm_stm_i2c_status(chip);
if ((status & TPM_STS_DATA_EXPECT) == 0) {
ret = -EIO;
goto out_err;
}
ret = I2C_WRITE_DATA(client, TPM_DATA_FIFO, buf + len - 1, 1);
if (ret < 0)
goto out_err;
status = tpm_stm_i2c_status(chip);
if ((status & TPM_STS_DATA_EXPECT) != 0) {
ret = -EIO;
goto out_err;
}
data = TPM_STS_GO;
I2C_WRITE_DATA(client, TPM_STS, &data, 1);
return len;
out_err:
tpm_stm_i2c_cancel(chip);
release_locality(chip);
return ret;
}
/*
* tpm_stm_i2c_recv received TPM response through the I2C bus.
* @param: chip, the tpm_chip description as specified in driver/char/tpm/tpm.h.
* @param: buf, the buffer to store datas.
* @param: count, the number of bytes to send.
* @return: In case of success the number of bytes received.
* In other case, a < 0 value describing the issue.
*/
static int tpm_stm_i2c_recv(struct tpm_chip *chip, unsigned char *buf,
size_t count)
{
int size = 0;
int expected;
if (chip == NULL)
return -EBUSY;
if (count < TPM_HEADER_SIZE) {
size = -EIO;
goto out;
}
size = recv_data(chip, buf, TPM_HEADER_SIZE);
if (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;
}
size += recv_data(chip, &buf[TPM_HEADER_SIZE],
expected - TPM_HEADER_SIZE);
if (size < expected) {
dev_err(chip->dev, "Unable to read remainder of result\n");
size = -ETIME;
goto out;
}
out:
chip->vendor.cancel(chip);
release_locality(chip);
return size;
}
static bool tpm_st33_i2c_req_canceled(struct tpm_chip *chip, u8 status)
{
return (status == TPM_STS_COMMAND_READY);
}
static const struct file_operations tpm_st33_i2c_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = tpm_read,
.write = tpm_write,
.open = tpm_open,
.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 *stm_tpm_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 stm_tpm_attr_grp = {
.attrs = stm_tpm_attrs
};
static struct tpm_vendor_specific st_i2c_tpm = {
.send = tpm_stm_i2c_send,
.recv = tpm_stm_i2c_recv,
.cancel = tpm_stm_i2c_cancel,
.status = tpm_stm_i2c_status,
.req_complete_mask = TPM_STS_DATA_AVAIL | TPM_STS_VALID,
.req_complete_val = TPM_STS_DATA_AVAIL | TPM_STS_VALID,
.req_canceled = tpm_st33_i2c_req_canceled,
.attr_group = &stm_tpm_attr_grp,
.miscdev = {.fops = &tpm_st33_i2c_fops,},
};
static int interrupts;
module_param(interrupts, int, 0444);
MODULE_PARM_DESC(interrupts, "Enable interrupts");
static int power_mgt = 1;
module_param(power_mgt, int, 0444);
MODULE_PARM_DESC(power_mgt, "Power Management");
/*
* tpm_st33_i2c_probe initialize the TPM device
* @param: client, the i2c_client drescription (TPM I2C description).
* @param: id, the i2c_device_id struct.
* @return: 0 in case of success.
* -1 in other case.
*/
static int
tpm_st33_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int err;
u8 intmask;
struct tpm_chip *chip;
struct st33zp24_platform_data *platform_data;
if (client == NULL) {
pr_info("%s: i2c client is NULL. Device not accessible.\n",
__func__);
err = -ENODEV;
goto end;
}
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
dev_info(&client->dev, "client not i2c capable\n");
err = -ENODEV;
goto end;
}
chip = tpm_register_hardware(&client->dev, &st_i2c_tpm);
if (!chip) {
dev_info(&client->dev, "fail chip\n");
err = -ENODEV;
goto end;
}
platform_data = client->dev.platform_data;
if (!platform_data) {
dev_info(&client->dev, "chip not available\n");
err = -ENODEV;
goto _tpm_clean_answer;
}
platform_data->tpm_i2c_buffer[0] =
kmalloc(TPM_BUFSIZE * sizeof(u8), GFP_KERNEL);
if (platform_data->tpm_i2c_buffer[0] == NULL) {
err = -ENOMEM;
goto _tpm_clean_answer;
}
platform_data->tpm_i2c_buffer[1] =
kmalloc(TPM_BUFSIZE * sizeof(u8), GFP_KERNEL);
if (platform_data->tpm_i2c_buffer[1] == NULL) {
err = -ENOMEM;
goto _tpm_clean_response1;
}
TPM_VPRIV(chip) = client;
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);
chip->vendor.locality = LOCALITY0;
if (power_mgt) {
err = gpio_request(platform_data->io_lpcpd, "TPM IO_LPCPD");
if (err)
goto _gpio_init1;
gpio_set_value(platform_data->io_lpcpd, 1);
}
if (interrupts) {
init_completion(&platform_data->irq_detection);
if (request_locality(chip) != LOCALITY0) {
err = -ENODEV;
goto _tpm_clean_response2;
}
err = gpio_request(platform_data->io_serirq, "TPM IO_SERIRQ");
if (err)
goto _gpio_init2;
clear_interruption(client);
err = request_irq(gpio_to_irq(platform_data->io_serirq),
&tpm_ioserirq_handler,
IRQF_TRIGGER_HIGH,
"TPM SERIRQ management", chip);
if (err < 0) {
dev_err(chip->dev , "TPM SERIRQ signals %d not available\n",
gpio_to_irq(platform_data->io_serirq));
goto _irq_set;
}
err = I2C_READ_DATA(client, TPM_INT_ENABLE, &intmask, 1);
if (err < 0)
goto _irq_set;
intmask |= TPM_INTF_CMD_READY_INT
| TPM_INTF_FIFO_AVALAIBLE_INT
| TPM_INTF_WAKE_UP_READY_INT
| TPM_INTF_LOCALITY_CHANGE_INT
| TPM_INTF_STS_VALID_INT
| TPM_INTF_DATA_AVAIL_INT;
err = I2C_WRITE_DATA(client, TPM_INT_ENABLE, &intmask, 1);
if (err < 0)
goto _irq_set;
intmask = TPM_GLOBAL_INT_ENABLE;
err = I2C_WRITE_DATA(client, (TPM_INT_ENABLE + 3), &intmask, 1);
if (err < 0)
goto _irq_set;
err = I2C_READ_DATA(client, TPM_INT_STATUS, &intmask, 1);
if (err < 0)
goto _irq_set;
chip->vendor.irq = interrupts;
tpm_gen_interrupt(chip);
}
tpm_get_timeouts(chip);
i2c_set_clientdata(client, chip);
dev_info(chip->dev, "TPM I2C Initialized\n");
return 0;
_irq_set:
free_irq(gpio_to_irq(platform_data->io_serirq), (void *)chip);
_gpio_init2:
if (interrupts)
gpio_free(platform_data->io_serirq);
_gpio_init1:
if (power_mgt)
gpio_free(platform_data->io_lpcpd);
_tpm_clean_response2:
kzfree(platform_data->tpm_i2c_buffer[1]);
platform_data->tpm_i2c_buffer[1] = NULL;
_tpm_clean_response1:
kzfree(platform_data->tpm_i2c_buffer[0]);
platform_data->tpm_i2c_buffer[0] = NULL;
_tpm_clean_answer:
tpm_remove_hardware(chip->dev);
end:
pr_info("TPM I2C initialisation fail\n");
return err;
}
/*
* tpm_st33_i2c_remove remove the TPM device
* @param: client, the i2c_client drescription (TPM I2C description).
clear_bit(0, &chip->is_open);
* @return: 0 in case of success.
*/
static int tpm_st33_i2c_remove(struct i2c_client *client)
{
struct tpm_chip *chip = (struct tpm_chip *)i2c_get_clientdata(client);
struct st33zp24_platform_data *pin_infos =
((struct i2c_client *)TPM_VPRIV(chip))->dev.platform_data;
if (pin_infos != NULL) {
free_irq(pin_infos->io_serirq, chip);
gpio_free(pin_infos->io_serirq);
gpio_free(pin_infos->io_lpcpd);
tpm_remove_hardware(chip->dev);
if (pin_infos->tpm_i2c_buffer[1] != NULL) {
kzfree(pin_infos->tpm_i2c_buffer[1]);
pin_infos->tpm_i2c_buffer[1] = NULL;
}
if (pin_infos->tpm_i2c_buffer[0] != NULL) {
kzfree(pin_infos->tpm_i2c_buffer[0]);
pin_infos->tpm_i2c_buffer[0] = NULL;
}
}
return 0;
}
#ifdef CONFIG_PM_SLEEP
/*
* tpm_st33_i2c_pm_suspend suspend the TPM device
* Added: Work around when suspend and no tpm application is running, suspend
* may fail because chip->data_buffer is not set (only set in tpm_open in Linux
* TPM core)
* @param: client, the i2c_client drescription (TPM I2C description).
* @param: mesg, the power management message.
* @return: 0 in case of success.
*/
static int tpm_st33_i2c_pm_suspend(struct device *dev)
{
struct tpm_chip *chip = dev_get_drvdata(dev);
struct st33zp24_platform_data *pin_infos = dev->platform_data;
int ret = 0;
if (power_mgt) {
gpio_set_value(pin_infos->io_lpcpd, 0);
} else {
if (chip->data_buffer == NULL)
chip->data_buffer = pin_infos->tpm_i2c_buffer[0];
ret = tpm_pm_suspend(dev);
}
return ret;
} /* tpm_st33_i2c_suspend() */
/*
* tpm_st33_i2c_pm_resume resume the TPM device
* @param: client, the i2c_client drescription (TPM I2C description).
* @return: 0 in case of success.
*/
static int tpm_st33_i2c_pm_resume(struct device *dev)
{
struct tpm_chip *chip = dev_get_drvdata(dev);
struct st33zp24_platform_data *pin_infos = dev->platform_data;
int ret = 0;
if (power_mgt) {
gpio_set_value(pin_infos->io_lpcpd, 1);
ret = wait_for_serirq_timeout(chip,
(chip->vendor.status(chip) &
TPM_STS_VALID) == TPM_STS_VALID,
chip->vendor.timeout_b);
} else {
if (chip->data_buffer == NULL)
chip->data_buffer = pin_infos->tpm_i2c_buffer[0];
ret = tpm_pm_resume(dev);
if (!ret)
tpm_do_selftest(chip);
}
return ret;
} /* tpm_st33_i2c_pm_resume() */
#endif
static const struct i2c_device_id tpm_st33_i2c_id[] = {
{TPM_ST33_I2C, 0},
{}
};
MODULE_DEVICE_TABLE(i2c, tpm_st33_i2c_id);
static SIMPLE_DEV_PM_OPS(tpm_st33_i2c_ops, tpm_st33_i2c_pm_suspend,
tpm_st33_i2c_pm_resume);
static struct i2c_driver tpm_st33_i2c_driver = {
.driver = {
.owner = THIS_MODULE,
.name = TPM_ST33_I2C,
.pm = &tpm_st33_i2c_ops,
},
.probe = tpm_st33_i2c_probe,
.remove = tpm_st33_i2c_remove,
.id_table = tpm_st33_i2c_id
};
module_i2c_driver(tpm_st33_i2c_driver);
MODULE_AUTHOR("Christophe Ricard (tpmsupport@st.com)");
MODULE_DESCRIPTION("STM TPM I2C ST33 Driver");
MODULE_VERSION("1.2.0");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,61 @@
/*
* STMicroelectronics TPM I2C Linux driver for TPM ST33ZP24
* Copyright (C) 2009, 2010 STMicroelectronics
*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* STMicroelectronics version 1.2.0, Copyright (C) 2010
* STMicroelectronics comes with ABSOLUTELY NO WARRANTY.
* This is free software, and you are welcome to redistribute it
* under certain conditions.
*
* @Author: Christophe RICARD tpmsupport@st.com
*
* @File: stm_st33_tpm_i2c.h
*
* @Date: 09/15/2010
*/
#ifndef __STM_ST33_TPM_I2C_MAIN_H__
#define __STM_ST33_TPM_I2C_MAIN_H__
#define TPM_ACCESS (0x0)
#define TPM_STS (0x18)
#define TPM_HASH_END (0x20)
#define TPM_DATA_FIFO (0x24)
#define TPM_HASH_DATA (0x24)
#define TPM_HASH_START (0x28)
#define TPM_INTF_CAPABILITY (0x14)
#define TPM_INT_STATUS (0x10)
#define TPM_INT_ENABLE (0x08)
#define TPM_DUMMY_BYTE 0xAA
#define TPM_WRITE_DIRECTION 0x80
#define TPM_HEADER_SIZE 10
#define TPM_BUFSIZE 2048
#define LOCALITY0 0
#define TPM_ST33_I2C "st33zp24_i2c"
struct st33zp24_platform_data {
int io_serirq;
int io_lpcpd;
struct i2c_client *client;
u8 *tpm_i2c_buffer[2]; /* 0 Request 1 Response */
struct completion irq_detection;
struct mutex lock;
};
#endif /* __STM_ST33_TPM_I2C_MAIN_H__ */

View File

@@ -0,0 +1,729 @@
/*
* Copyright (C) 2012 IBM Corporation
*
* Author: Ashley Lai <adlai@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/dma-mapping.h>
#include <linux/dmapool.h>
#include <linux/slab.h>
#include <asm/vio.h>
#include <asm/irq.h>
#include <linux/types.h>
#include <linux/list.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <asm/prom.h>
#include "tpm.h"
#include "tpm_ibmvtpm.h"
static const char tpm_ibmvtpm_driver_name[] = "tpm_ibmvtpm";
static struct vio_device_id tpm_ibmvtpm_device_table[] = {
{ "IBM,vtpm", "IBM,vtpm"},
{ "", "" }
};
MODULE_DEVICE_TABLE(vio, tpm_ibmvtpm_device_table);
/**
* ibmvtpm_send_crq - Send a CRQ request
* @vdev: vio device struct
* @w1: first word
* @w2: second word
*
* Return value:
* 0 -Sucess
* Non-zero - Failure
*/
static int ibmvtpm_send_crq(struct vio_dev *vdev, u64 w1, u64 w2)
{
return plpar_hcall_norets(H_SEND_CRQ, vdev->unit_address, w1, w2);
}
/**
* ibmvtpm_get_data - Retrieve ibm vtpm data
* @dev: device struct
*
* Return value:
* vtpm device struct
*/
static struct ibmvtpm_dev *ibmvtpm_get_data(const struct device *dev)
{
struct tpm_chip *chip = dev_get_drvdata(dev);
if (chip)
return (struct ibmvtpm_dev *)TPM_VPRIV(chip);
return NULL;
}
/**
* tpm_ibmvtpm_recv - Receive data after send
* @chip: tpm chip struct
* @buf: buffer to read
* count: size of buffer
*
* Return value:
* Number of bytes read
*/
static int tpm_ibmvtpm_recv(struct tpm_chip *chip, u8 *buf, size_t count)
{
struct ibmvtpm_dev *ibmvtpm;
u16 len;
int sig;
ibmvtpm = (struct ibmvtpm_dev *)TPM_VPRIV(chip);
if (!ibmvtpm->rtce_buf) {
dev_err(ibmvtpm->dev, "ibmvtpm device is not ready\n");
return 0;
}
sig = wait_event_interruptible(ibmvtpm->wq, ibmvtpm->res_len != 0);
if (sig)
return -EINTR;
len = ibmvtpm->res_len;
if (count < len) {
dev_err(ibmvtpm->dev,
"Invalid size in recv: count=%ld, crq_size=%d\n",
count, len);
return -EIO;
}
spin_lock(&ibmvtpm->rtce_lock);
memcpy((void *)buf, (void *)ibmvtpm->rtce_buf, len);
memset(ibmvtpm->rtce_buf, 0, len);
ibmvtpm->res_len = 0;
spin_unlock(&ibmvtpm->rtce_lock);
return len;
}
/**
* tpm_ibmvtpm_send - Send tpm request
* @chip: tpm chip struct
* @buf: buffer contains data to send
* count: size of buffer
*
* Return value:
* Number of bytes sent
*/
static int tpm_ibmvtpm_send(struct tpm_chip *chip, u8 *buf, size_t count)
{
struct ibmvtpm_dev *ibmvtpm;
struct ibmvtpm_crq crq;
u64 *word = (u64 *) &crq;
int rc;
ibmvtpm = (struct ibmvtpm_dev *)TPM_VPRIV(chip);
if (!ibmvtpm->rtce_buf) {
dev_err(ibmvtpm->dev, "ibmvtpm device is not ready\n");
return 0;
}
if (count > ibmvtpm->rtce_size) {
dev_err(ibmvtpm->dev,
"Invalid size in send: count=%ld, rtce_size=%d\n",
count, ibmvtpm->rtce_size);
return -EIO;
}
spin_lock(&ibmvtpm->rtce_lock);
memcpy((void *)ibmvtpm->rtce_buf, (void *)buf, count);
crq.valid = (u8)IBMVTPM_VALID_CMD;
crq.msg = (u8)VTPM_TPM_COMMAND;
crq.len = (u16)count;
crq.data = ibmvtpm->rtce_dma_handle;
rc = ibmvtpm_send_crq(ibmvtpm->vdev, word[0], word[1]);
if (rc != H_SUCCESS) {
dev_err(ibmvtpm->dev, "tpm_ibmvtpm_send failed rc=%d\n", rc);
rc = 0;
} else
rc = count;
spin_unlock(&ibmvtpm->rtce_lock);
return rc;
}
static void tpm_ibmvtpm_cancel(struct tpm_chip *chip)
{
return;
}
static u8 tpm_ibmvtpm_status(struct tpm_chip *chip)
{
return 0;
}
/**
* ibmvtpm_crq_get_rtce_size - Send a CRQ request to get rtce size
* @ibmvtpm: vtpm device struct
*
* Return value:
* 0 - Success
* Non-zero - Failure
*/
static int ibmvtpm_crq_get_rtce_size(struct ibmvtpm_dev *ibmvtpm)
{
struct ibmvtpm_crq crq;
u64 *buf = (u64 *) &crq;
int rc;
crq.valid = (u8)IBMVTPM_VALID_CMD;
crq.msg = (u8)VTPM_GET_RTCE_BUFFER_SIZE;
rc = ibmvtpm_send_crq(ibmvtpm->vdev, buf[0], buf[1]);
if (rc != H_SUCCESS)
dev_err(ibmvtpm->dev,
"ibmvtpm_crq_get_rtce_size failed rc=%d\n", rc);
return rc;
}
/**
* ibmvtpm_crq_get_version - Send a CRQ request to get vtpm version
* - Note that this is vtpm version and not tpm version
* @ibmvtpm: vtpm device struct
*
* Return value:
* 0 - Success
* Non-zero - Failure
*/
static int ibmvtpm_crq_get_version(struct ibmvtpm_dev *ibmvtpm)
{
struct ibmvtpm_crq crq;
u64 *buf = (u64 *) &crq;
int rc;
crq.valid = (u8)IBMVTPM_VALID_CMD;
crq.msg = (u8)VTPM_GET_VERSION;
rc = ibmvtpm_send_crq(ibmvtpm->vdev, buf[0], buf[1]);
if (rc != H_SUCCESS)
dev_err(ibmvtpm->dev,
"ibmvtpm_crq_get_version failed rc=%d\n", rc);
return rc;
}
/**
* ibmvtpm_crq_send_init_complete - Send a CRQ initialize complete message
* @ibmvtpm: vtpm device struct
*
* Return value:
* 0 - Success
* Non-zero - Failure
*/
static int ibmvtpm_crq_send_init_complete(struct ibmvtpm_dev *ibmvtpm)
{
int rc;
rc = ibmvtpm_send_crq(ibmvtpm->vdev, INIT_CRQ_COMP_CMD, 0);
if (rc != H_SUCCESS)
dev_err(ibmvtpm->dev,
"ibmvtpm_crq_send_init_complete failed rc=%d\n", rc);
return rc;
}
/**
* ibmvtpm_crq_send_init - Send a CRQ initialize message
* @ibmvtpm: vtpm device struct
*
* Return value:
* 0 - Success
* Non-zero - Failure
*/
static int ibmvtpm_crq_send_init(struct ibmvtpm_dev *ibmvtpm)
{
int rc;
rc = ibmvtpm_send_crq(ibmvtpm->vdev, INIT_CRQ_CMD, 0);
if (rc != H_SUCCESS)
dev_err(ibmvtpm->dev,
"ibmvtpm_crq_send_init failed rc=%d\n", rc);
return rc;
}
/**
* tpm_ibmvtpm_remove - ibm vtpm remove entry point
* @vdev: vio device struct
*
* Return value:
* 0
*/
static int tpm_ibmvtpm_remove(struct vio_dev *vdev)
{
struct ibmvtpm_dev *ibmvtpm = ibmvtpm_get_data(&vdev->dev);
int rc = 0;
free_irq(vdev->irq, ibmvtpm);
do {
if (rc)
msleep(100);
rc = plpar_hcall_norets(H_FREE_CRQ, vdev->unit_address);
} while (rc == H_BUSY || H_IS_LONG_BUSY(rc));
dma_unmap_single(ibmvtpm->dev, ibmvtpm->crq_dma_handle,
CRQ_RES_BUF_SIZE, DMA_BIDIRECTIONAL);
free_page((unsigned long)ibmvtpm->crq_queue.crq_addr);
if (ibmvtpm->rtce_buf) {
dma_unmap_single(ibmvtpm->dev, ibmvtpm->rtce_dma_handle,
ibmvtpm->rtce_size, DMA_BIDIRECTIONAL);
kfree(ibmvtpm->rtce_buf);
}
tpm_remove_hardware(ibmvtpm->dev);
kfree(ibmvtpm);
return 0;
}
/**
* tpm_ibmvtpm_get_desired_dma - Get DMA size needed by this driver
* @vdev: vio device struct
*
* Return value:
* Number of bytes the driver needs to DMA map
*/
static unsigned long tpm_ibmvtpm_get_desired_dma(struct vio_dev *vdev)
{
struct ibmvtpm_dev *ibmvtpm = ibmvtpm_get_data(&vdev->dev);
return CRQ_RES_BUF_SIZE + ibmvtpm->rtce_size;
}
/**
* tpm_ibmvtpm_suspend - Suspend
* @dev: device struct
*
* Return value:
* 0
*/
static int tpm_ibmvtpm_suspend(struct device *dev)
{
struct ibmvtpm_dev *ibmvtpm = ibmvtpm_get_data(dev);
struct ibmvtpm_crq crq;
u64 *buf = (u64 *) &crq;
int rc = 0;
crq.valid = (u8)IBMVTPM_VALID_CMD;
crq.msg = (u8)VTPM_PREPARE_TO_SUSPEND;
rc = ibmvtpm_send_crq(ibmvtpm->vdev, buf[0], buf[1]);
if (rc != H_SUCCESS)
dev_err(ibmvtpm->dev,
"tpm_ibmvtpm_suspend failed rc=%d\n", rc);
return rc;
}
/**
* ibmvtpm_reset_crq - Reset CRQ
* @ibmvtpm: ibm vtpm struct
*
* Return value:
* 0 - Success
* Non-zero - Failure
*/
static int ibmvtpm_reset_crq(struct ibmvtpm_dev *ibmvtpm)
{
int rc = 0;
do {
if (rc)
msleep(100);
rc = plpar_hcall_norets(H_FREE_CRQ,
ibmvtpm->vdev->unit_address);
} while (rc == H_BUSY || H_IS_LONG_BUSY(rc));
memset(ibmvtpm->crq_queue.crq_addr, 0, CRQ_RES_BUF_SIZE);
ibmvtpm->crq_queue.index = 0;
return plpar_hcall_norets(H_REG_CRQ, ibmvtpm->vdev->unit_address,
ibmvtpm->crq_dma_handle, CRQ_RES_BUF_SIZE);
}
/**
* tpm_ibmvtpm_resume - Resume from suspend
* @dev: device struct
*
* Return value:
* 0
*/
static int tpm_ibmvtpm_resume(struct device *dev)
{
struct ibmvtpm_dev *ibmvtpm = ibmvtpm_get_data(dev);
int rc = 0;
do {
if (rc)
msleep(100);
rc = plpar_hcall_norets(H_ENABLE_CRQ,
ibmvtpm->vdev->unit_address);
} while (rc == H_IN_PROGRESS || rc == H_BUSY || H_IS_LONG_BUSY(rc));
if (rc) {
dev_err(dev, "Error enabling ibmvtpm rc=%d\n", rc);
return rc;
}
rc = vio_enable_interrupts(ibmvtpm->vdev);
if (rc) {
dev_err(dev, "Error vio_enable_interrupts rc=%d\n", rc);
return rc;
}
rc = ibmvtpm_crq_send_init(ibmvtpm);
if (rc)
dev_err(dev, "Error send_init rc=%d\n", rc);
return rc;
}
static bool tpm_ibmvtpm_req_canceled(struct tpm_chip *chip, u8 status)
{
return (status == 0);
}
static const struct file_operations ibmvtpm_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 DEVICE_ATTR(durations, S_IRUGO, tpm_show_durations, NULL);
static DEVICE_ATTR(timeouts, S_IRUGO, tpm_show_timeouts, NULL);
static struct attribute *ibmvtpm_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,
&dev_attr_durations.attr,
&dev_attr_timeouts.attr, NULL,
};
static struct attribute_group ibmvtpm_attr_grp = { .attrs = ibmvtpm_attrs };
static const struct tpm_vendor_specific tpm_ibmvtpm = {
.recv = tpm_ibmvtpm_recv,
.send = tpm_ibmvtpm_send,
.cancel = tpm_ibmvtpm_cancel,
.status = tpm_ibmvtpm_status,
.req_complete_mask = 0,
.req_complete_val = 0,
.req_canceled = tpm_ibmvtpm_req_canceled,
.attr_group = &ibmvtpm_attr_grp,
.miscdev = { .fops = &ibmvtpm_ops, },
};
static const struct dev_pm_ops tpm_ibmvtpm_pm_ops = {
.suspend = tpm_ibmvtpm_suspend,
.resume = tpm_ibmvtpm_resume,
};
/**
* ibmvtpm_crq_get_next - Get next responded crq
* @ibmvtpm vtpm device struct
*
* Return value:
* vtpm crq pointer
*/
static struct ibmvtpm_crq *ibmvtpm_crq_get_next(struct ibmvtpm_dev *ibmvtpm)
{
struct ibmvtpm_crq_queue *crq_q = &ibmvtpm->crq_queue;
struct ibmvtpm_crq *crq = &crq_q->crq_addr[crq_q->index];
if (crq->valid & VTPM_MSG_RES) {
if (++crq_q->index == crq_q->num_entry)
crq_q->index = 0;
smp_rmb();
} else
crq = NULL;
return crq;
}
/**
* ibmvtpm_crq_process - Process responded crq
* @crq crq to be processed
* @ibmvtpm vtpm device struct
*
* Return value:
* Nothing
*/
static void ibmvtpm_crq_process(struct ibmvtpm_crq *crq,
struct ibmvtpm_dev *ibmvtpm)
{
int rc = 0;
switch (crq->valid) {
case VALID_INIT_CRQ:
switch (crq->msg) {
case INIT_CRQ_RES:
dev_info(ibmvtpm->dev, "CRQ initialized\n");
rc = ibmvtpm_crq_send_init_complete(ibmvtpm);
if (rc)
dev_err(ibmvtpm->dev, "Unable to send CRQ init complete rc=%d\n", rc);
return;
case INIT_CRQ_COMP_RES:
dev_info(ibmvtpm->dev,
"CRQ initialization completed\n");
return;
default:
dev_err(ibmvtpm->dev, "Unknown crq message type: %d\n", crq->msg);
return;
}
return;
case IBMVTPM_VALID_CMD:
switch (crq->msg) {
case VTPM_GET_RTCE_BUFFER_SIZE_RES:
if (crq->len <= 0) {
dev_err(ibmvtpm->dev, "Invalid rtce size\n");
return;
}
ibmvtpm->rtce_size = crq->len;
ibmvtpm->rtce_buf = kmalloc(ibmvtpm->rtce_size,
GFP_KERNEL);
if (!ibmvtpm->rtce_buf) {
dev_err(ibmvtpm->dev, "Failed to allocate memory for rtce buffer\n");
return;
}
ibmvtpm->rtce_dma_handle = dma_map_single(ibmvtpm->dev,
ibmvtpm->rtce_buf, ibmvtpm->rtce_size,
DMA_BIDIRECTIONAL);
if (dma_mapping_error(ibmvtpm->dev,
ibmvtpm->rtce_dma_handle)) {
kfree(ibmvtpm->rtce_buf);
ibmvtpm->rtce_buf = NULL;
dev_err(ibmvtpm->dev, "Failed to dma map rtce buffer\n");
}
return;
case VTPM_GET_VERSION_RES:
ibmvtpm->vtpm_version = crq->data;
return;
case VTPM_TPM_COMMAND_RES:
/* len of the data in rtce buffer */
ibmvtpm->res_len = crq->len;
wake_up_interruptible(&ibmvtpm->wq);
return;
default:
return;
}
}
return;
}
/**
* ibmvtpm_interrupt - Interrupt handler
* @irq: irq number to handle
* @vtpm_instance: vtpm that received interrupt
*
* Returns:
* IRQ_HANDLED
**/
static irqreturn_t ibmvtpm_interrupt(int irq, void *vtpm_instance)
{
struct ibmvtpm_dev *ibmvtpm = (struct ibmvtpm_dev *) vtpm_instance;
struct ibmvtpm_crq *crq;
/* while loop is needed for initial setup (get version and
* get rtce_size). There should be only one tpm request at any
* given time.
*/
while ((crq = ibmvtpm_crq_get_next(ibmvtpm)) != NULL) {
ibmvtpm_crq_process(crq, ibmvtpm);
crq->valid = 0;
smp_wmb();
}
return IRQ_HANDLED;
}
/**
* tpm_ibmvtpm_probe - ibm vtpm initialize entry point
* @vio_dev: vio device struct
* @id: vio device id struct
*
* Return value:
* 0 - Success
* Non-zero - Failure
*/
static int tpm_ibmvtpm_probe(struct vio_dev *vio_dev,
const struct vio_device_id *id)
{
struct ibmvtpm_dev *ibmvtpm;
struct device *dev = &vio_dev->dev;
struct ibmvtpm_crq_queue *crq_q;
struct tpm_chip *chip;
int rc = -ENOMEM, rc1;
chip = tpm_register_hardware(dev, &tpm_ibmvtpm);
if (!chip) {
dev_err(dev, "tpm_register_hardware failed\n");
return -ENODEV;
}
ibmvtpm = kzalloc(sizeof(struct ibmvtpm_dev), GFP_KERNEL);
if (!ibmvtpm) {
dev_err(dev, "kzalloc for ibmvtpm failed\n");
goto cleanup;
}
crq_q = &ibmvtpm->crq_queue;
crq_q->crq_addr = (struct ibmvtpm_crq *)get_zeroed_page(GFP_KERNEL);
if (!crq_q->crq_addr) {
dev_err(dev, "Unable to allocate memory for crq_addr\n");
goto cleanup;
}
crq_q->num_entry = CRQ_RES_BUF_SIZE / sizeof(*crq_q->crq_addr);
ibmvtpm->crq_dma_handle = dma_map_single(dev, crq_q->crq_addr,
CRQ_RES_BUF_SIZE,
DMA_BIDIRECTIONAL);
if (dma_mapping_error(dev, ibmvtpm->crq_dma_handle)) {
dev_err(dev, "dma mapping failed\n");
goto cleanup;
}
rc = plpar_hcall_norets(H_REG_CRQ, vio_dev->unit_address,
ibmvtpm->crq_dma_handle, CRQ_RES_BUF_SIZE);
if (rc == H_RESOURCE)
rc = ibmvtpm_reset_crq(ibmvtpm);
if (rc) {
dev_err(dev, "Unable to register CRQ rc=%d\n", rc);
goto reg_crq_cleanup;
}
rc = request_irq(vio_dev->irq, ibmvtpm_interrupt, 0,
tpm_ibmvtpm_driver_name, ibmvtpm);
if (rc) {
dev_err(dev, "Error %d register irq 0x%x\n", rc, vio_dev->irq);
goto init_irq_cleanup;
}
rc = vio_enable_interrupts(vio_dev);
if (rc) {
dev_err(dev, "Error %d enabling interrupts\n", rc);
goto init_irq_cleanup;
}
init_waitqueue_head(&ibmvtpm->wq);
crq_q->index = 0;
ibmvtpm->dev = dev;
ibmvtpm->vdev = vio_dev;
TPM_VPRIV(chip) = (void *)ibmvtpm;
spin_lock_init(&ibmvtpm->rtce_lock);
rc = ibmvtpm_crq_send_init(ibmvtpm);
if (rc)
goto init_irq_cleanup;
rc = ibmvtpm_crq_get_version(ibmvtpm);
if (rc)
goto init_irq_cleanup;
rc = ibmvtpm_crq_get_rtce_size(ibmvtpm);
if (rc)
goto init_irq_cleanup;
return rc;
init_irq_cleanup:
do {
rc1 = plpar_hcall_norets(H_FREE_CRQ, vio_dev->unit_address);
} while (rc1 == H_BUSY || H_IS_LONG_BUSY(rc1));
reg_crq_cleanup:
dma_unmap_single(dev, ibmvtpm->crq_dma_handle, CRQ_RES_BUF_SIZE,
DMA_BIDIRECTIONAL);
cleanup:
if (ibmvtpm) {
if (crq_q->crq_addr)
free_page((unsigned long)crq_q->crq_addr);
kfree(ibmvtpm);
}
tpm_remove_hardware(dev);
return rc;
}
static struct vio_driver ibmvtpm_driver = {
.id_table = tpm_ibmvtpm_device_table,
.probe = tpm_ibmvtpm_probe,
.remove = tpm_ibmvtpm_remove,
.get_desired_dma = tpm_ibmvtpm_get_desired_dma,
.name = tpm_ibmvtpm_driver_name,
.pm = &tpm_ibmvtpm_pm_ops,
};
/**
* ibmvtpm_module_init - Initialize ibm vtpm module
*
* Return value:
* 0 -Success
* Non-zero - Failure
*/
static int __init ibmvtpm_module_init(void)
{
return vio_register_driver(&ibmvtpm_driver);
}
/**
* ibmvtpm_module_exit - Teardown ibm vtpm module
*
* Return value:
* Nothing
*/
static void __exit ibmvtpm_module_exit(void)
{
vio_unregister_driver(&ibmvtpm_driver);
}
module_init(ibmvtpm_module_init);
module_exit(ibmvtpm_module_exit);
MODULE_AUTHOR("adlai@us.ibm.com");
MODULE_DESCRIPTION("IBM vTPM Driver");
MODULE_VERSION("1.0");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,76 @@
/*
* Copyright (C) 2012 IBM Corporation
*
* Author: Ashley Lai <adlai@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.
*
*/
#ifndef __TPM_IBMVTPM_H__
#define __TPM_IBMVTPM_H__
/* vTPM Message Format 1 */
struct ibmvtpm_crq {
u8 valid;
u8 msg;
u16 len;
u32 data;
u64 reserved;
} __attribute__((packed, aligned(8)));
struct ibmvtpm_crq_queue {
struct ibmvtpm_crq *crq_addr;
u32 index;
u32 num_entry;
};
struct ibmvtpm_dev {
struct device *dev;
struct vio_dev *vdev;
struct ibmvtpm_crq_queue crq_queue;
dma_addr_t crq_dma_handle;
u32 rtce_size;
void __iomem *rtce_buf;
dma_addr_t rtce_dma_handle;
spinlock_t rtce_lock;
wait_queue_head_t wq;
u16 res_len;
u32 vtpm_version;
};
#define CRQ_RES_BUF_SIZE PAGE_SIZE
/* Initialize CRQ */
#define INIT_CRQ_CMD 0xC001000000000000LL /* Init cmd */
#define INIT_CRQ_COMP_CMD 0xC002000000000000LL /* Init complete cmd */
#define INIT_CRQ_RES 0x01 /* Init respond */
#define INIT_CRQ_COMP_RES 0x02 /* Init complete respond */
#define VALID_INIT_CRQ 0xC0 /* Valid command for init crq */
/* vTPM CRQ response is the message type | 0x80 */
#define VTPM_MSG_RES 0x80
#define IBMVTPM_VALID_CMD 0x80
/* vTPM CRQ message types */
#define VTPM_GET_VERSION 0x01
#define VTPM_GET_VERSION_RES (0x01 | VTPM_MSG_RES)
#define VTPM_TPM_COMMAND 0x02
#define VTPM_TPM_COMMAND_RES (0x02 | VTPM_MSG_RES)
#define VTPM_GET_RTCE_BUFFER_SIZE 0x03
#define VTPM_GET_RTCE_BUFFER_SIZE_RES (0x03 | VTPM_MSG_RES)
#define VTPM_PREPARE_TO_SUSPEND 0x04
#define VTPM_PREPARE_TO_SUSPEND_RES (0x04 | VTPM_MSG_RES)
#endif

View File

@@ -0,0 +1,677 @@
/*
* 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 <tpmdd@selhorst.net>
* Sirrix AG - security technologies <tpmdd@sirrix.com> and
* Applied Data Security Group, Ruhr-University Bochum, Germany
* Project-Homepage: http://www.trust.rub.de/projects/linux-device-driver-infineon-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
#define TPM_INF_IO_PORT 0x0
#define TPM_INF_IO_MEM 0x1
#define TPM_INF_ADDR 0x0
#define TPM_INF_DATA 0x1
struct tpm_inf_dev {
int iotype;
void __iomem *mem_base; /* MMIO ioremap'd addr */
unsigned long map_base; /* phys MMIO base */
unsigned long map_size; /* MMIO region size */
unsigned int index_off; /* index register offset */
unsigned int data_regs; /* Data registers */
unsigned int data_size;
unsigned int config_port; /* IO Port config index reg */
unsigned int config_size;
};
static struct tpm_inf_dev tpm_dev;
static inline void tpm_data_out(unsigned char data, unsigned char offset)
{
if (tpm_dev.iotype == TPM_INF_IO_PORT)
outb(data, tpm_dev.data_regs + offset);
else
writeb(data, tpm_dev.mem_base + tpm_dev.data_regs + offset);
}
static inline unsigned char tpm_data_in(unsigned char offset)
{
if (tpm_dev.iotype == TPM_INF_IO_PORT)
return inb(tpm_dev.data_regs + offset);
else
return readb(tpm_dev.mem_base + tpm_dev.data_regs + offset);
}
static inline void tpm_config_out(unsigned char data, unsigned char offset)
{
if (tpm_dev.iotype == TPM_INF_IO_PORT)
outb(data, tpm_dev.config_port + offset);
else
writeb(data, tpm_dev.mem_base + tpm_dev.index_off + offset);
}
static inline unsigned char tpm_config_in(unsigned char offset)
{
if (tpm_dev.iotype == TPM_INF_IO_PORT)
return inb(tpm_dev.config_port + offset);
else
return readb(tpm_dev.mem_base + tpm_dev.index_off + offset);
}
/* 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 = tpm_data_in(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 = tpm_data_in(RDFIFO);
status = tpm_data_in(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 = tpm_data_in(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);
tpm_data_out(sendbyte, 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] = tpm_data_in(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] = tpm_data_in(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 */
tpm_data_out(RESET_LP_IRQC_DISABLE, 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 tpm_data_in(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_inf_pnp_tbl[] = {
/* Infineon TPMs */
{"IFX0101", 0},
{"IFX0102", 0},
{"", 0}
};
MODULE_DEVICE_TABLE(pnp, tpm_inf_pnp_tbl);
static int 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_dev.iotype = TPM_INF_IO_PORT;
tpm_dev.config_port = pnp_port_start(dev, 0);
tpm_dev.config_size = pnp_port_len(dev, 0);
tpm_dev.data_regs = pnp_port_start(dev, 1);
tpm_dev.data_size = pnp_port_len(dev, 1);
if ((tpm_dev.data_size < 4) || (tpm_dev.config_size < 2)) {
rc = -EINVAL;
goto err_last;
}
dev_info(&dev->dev, "Found %s with ID %s\n",
dev->name, dev_id->id);
if (!((tpm_dev.data_regs >> 8) & 0xff)) {
rc = -EINVAL;
goto err_last;
}
/* publish my base address and request region */
if (request_region(tpm_dev.data_regs, tpm_dev.data_size,
"tpm_infineon0") == NULL) {
rc = -EINVAL;
goto err_last;
}
if (request_region(tpm_dev.config_port, tpm_dev.config_size,
"tpm_infineon0") == NULL) {
release_region(tpm_dev.data_regs, tpm_dev.data_size);
rc = -EINVAL;
goto err_last;
}
} else if (pnp_mem_valid(dev, 0) &&
!(pnp_mem_flags(dev, 0) & IORESOURCE_DISABLED)) {
tpm_dev.iotype = TPM_INF_IO_MEM;
tpm_dev.map_base = pnp_mem_start(dev, 0);
tpm_dev.map_size = pnp_mem_len(dev, 0);
dev_info(&dev->dev, "Found %s with ID %s\n",
dev->name, dev_id->id);
/* publish my base address and request region */
if (request_mem_region(tpm_dev.map_base, tpm_dev.map_size,
"tpm_infineon0") == NULL) {
rc = -EINVAL;
goto err_last;
}
tpm_dev.mem_base = ioremap(tpm_dev.map_base, tpm_dev.map_size);
if (tpm_dev.mem_base == NULL) {
release_mem_region(tpm_dev.map_base, tpm_dev.map_size);
rc = -EINVAL;
goto err_last;
}
/*
* The only known MMIO based Infineon TPM system provides
* a single large mem region with the device config
* registers at the default TPM_ADDR. The data registers
* seem like they could be placed anywhere within the MMIO
* region, but lets just put them at zero offset.
*/
tpm_dev.index_off = TPM_ADDR;
tpm_dev.data_regs = 0x0;
} else {
rc = -EINVAL;
goto err_last;
}
/* query chip for its vendor, its version number a.s.o. */
tpm_config_out(ENABLE_REGISTER_PAIR, TPM_INF_ADDR);
tpm_config_out(IDVENL, TPM_INF_ADDR);
vendorid[1] = tpm_config_in(TPM_INF_DATA);
tpm_config_out(IDVENH, TPM_INF_ADDR);
vendorid[0] = tpm_config_in(TPM_INF_DATA);
tpm_config_out(IDPDL, TPM_INF_ADDR);
productid[1] = tpm_config_in(TPM_INF_DATA);
tpm_config_out(IDPDH, TPM_INF_ADDR);
productid[0] = tpm_config_in(TPM_INF_DATA);
tpm_config_out(CHIP_ID1, TPM_INF_ADDR);
version[1] = tpm_config_in(TPM_INF_DATA);
tpm_config_out(CHIP_ID2, TPM_INF_ADDR);
version[0] = tpm_config_in(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 */
tpm_config_out(IOLIMH, TPM_INF_ADDR);
tpm_config_out((tpm_dev.data_regs >> 8) & 0xff, TPM_INF_DATA);
tpm_config_out(IOLIML, TPM_INF_ADDR);
tpm_config_out((tpm_dev.data_regs & 0xff), TPM_INF_DATA);
/* control if IO-ports are set correctly */
tpm_config_out(IOLIMH, TPM_INF_ADDR);
ioh = tpm_config_in(TPM_INF_DATA);
tpm_config_out(IOLIML, TPM_INF_ADDR);
iol = tpm_config_in(TPM_INF_DATA);
if ((ioh << 8 | iol) != tpm_dev.data_regs) {
dev_err(&dev->dev,
"Could not set IO-data registers to 0x%x\n",
tpm_dev.data_regs);
rc = -EIO;
goto err_release_region;
}
/* activate register */
tpm_config_out(TPM_DAR, TPM_INF_ADDR);
tpm_config_out(0x01, TPM_INF_DATA);
tpm_config_out(DISABLE_REGISTER_PAIR, TPM_INF_ADDR);
/* disable RESET, LP and IRQC */
tpm_data_out(RESET_LP_IRQC_DISABLE, CMD);
/* Finally, we're done, print some infos */
dev_info(&dev->dev, "TPM found: "
"config base 0x%lx, "
"data base 0x%lx, "
"chip version 0x%02x%02x, "
"vendor id 0x%x%x (Infineon), "
"product id 0x%02x%02x"
"%s\n",
tpm_dev.iotype == TPM_INF_IO_PORT ?
tpm_dev.config_port :
tpm_dev.map_base + tpm_dev.index_off,
tpm_dev.iotype == TPM_INF_IO_PORT ?
tpm_dev.data_regs :
tpm_dev.map_base + tpm_dev.data_regs,
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;
return 0;
} else {
rc = -ENODEV;
goto err_release_region;
}
err_release_region:
if (tpm_dev.iotype == TPM_INF_IO_PORT) {
release_region(tpm_dev.data_regs, tpm_dev.data_size);
release_region(tpm_dev.config_port, tpm_dev.config_size);
} else {
iounmap(tpm_dev.mem_base);
release_mem_region(tpm_dev.map_base, tpm_dev.map_size);
}
err_last:
return rc;
}
static void tpm_inf_pnp_remove(struct pnp_dev *dev)
{
struct tpm_chip *chip = pnp_get_drvdata(dev);
if (chip) {
if (tpm_dev.iotype == TPM_INF_IO_PORT) {
release_region(tpm_dev.data_regs, tpm_dev.data_size);
release_region(tpm_dev.config_port,
tpm_dev.config_size);
} else {
iounmap(tpm_dev.mem_base);
release_mem_region(tpm_dev.map_base, tpm_dev.map_size);
}
tpm_dev_vendor_release(chip);
tpm_remove_hardware(chip->dev);
}
}
static int tpm_inf_pnp_suspend(struct pnp_dev *dev, pm_message_t pm_state)
{
struct tpm_chip *chip = pnp_get_drvdata(dev);
int rc;
if (chip) {
u8 savestate[] = {
0, 193, /* TPM_TAG_RQU_COMMAND */
0, 0, 0, 10, /* blob length (in bytes) */
0, 0, 0, 152 /* TPM_ORD_SaveState */
};
dev_info(&dev->dev, "saving TPM state\n");
rc = tpm_inf_send(chip, savestate, sizeof(savestate));
if (rc < 0) {
dev_err(&dev->dev, "error while saving TPM state\n");
return rc;
}
}
return 0;
}
static int tpm_inf_pnp_resume(struct pnp_dev *dev)
{
/* Re-configure TPM after suspending */
tpm_config_out(ENABLE_REGISTER_PAIR, TPM_INF_ADDR);
tpm_config_out(IOLIMH, TPM_INF_ADDR);
tpm_config_out((tpm_dev.data_regs >> 8) & 0xff, TPM_INF_DATA);
tpm_config_out(IOLIML, TPM_INF_ADDR);
tpm_config_out((tpm_dev.data_regs & 0xff), TPM_INF_DATA);
/* activate register */
tpm_config_out(TPM_DAR, TPM_INF_ADDR);
tpm_config_out(0x01, TPM_INF_DATA);
tpm_config_out(DISABLE_REGISTER_PAIR, TPM_INF_ADDR);
/* disable RESET, LP and IRQC */
tpm_data_out(RESET_LP_IRQC_DISABLE, CMD);
return tpm_pm_resume(&dev->dev);
}
static struct pnp_driver tpm_inf_pnp_driver = {
.name = "tpm_inf_pnp",
.id_table = tpm_inf_pnp_tbl,
.probe = tpm_inf_pnp_probe,
.suspend = tpm_inf_pnp_suspend,
.resume = tpm_inf_pnp_resume,
.remove = tpm_inf_pnp_remove
};
static int __init init_inf(void)
{
return pnp_register_driver(&tpm_inf_pnp_driver);
}
static void __exit cleanup_inf(void)
{
pnp_unregister_driver(&tpm_inf_pnp_driver);
}
module_init(init_inf);
module_exit(cleanup_inf);
MODULE_AUTHOR("Marcel Selhorst <tpmdd@sirrix.com>");
MODULE_DESCRIPTION("Driver for Infineon TPM SLD 9630 TT 1.1 / SLB 9635 TT 1.2");
MODULE_VERSION("1.9.2");
MODULE_LICENSE("GPL");

406
drivers/char/tpm/tpm_nsc.c Normal file
View File

@@ -0,0 +1,406 @@
/*
* 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 <linux/slab.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 bool tpm_nsc_req_canceled(struct tpm_chip *chip, u8 status)
{
return (status == NSC_STATUS_RDY);
}
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 = tpm_nsc_req_canceled,
.attr_group = &nsc_attr_grp,
.miscdev = { .fops = &nsc_ops, },
};
static struct platform_device *pdev = NULL;
static void 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 SIMPLE_DEV_PM_OPS(tpm_nsc_pm, tpm_pm_suspend, tpm_pm_resume);
static struct platform_driver nsc_drv = {
.driver = {
.name = "tpm_nsc",
.owner = THIS_MODULE,
.pm = &tpm_nsc_pm,
},
};
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 = platform_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 = platform_device_alloc("tpm_nscl0", -1);
if (!pdev) {
rc = -ENOMEM;
goto err_unreg_drv;
}
pdev->num_resources = 0;
pdev->dev.driver = &nsc_drv.driver;
pdev->dev.release = tpm_nsc_remove;
if ((rc = platform_device_add(pdev)) < 0)
goto err_put_dev;
if (request_region(base, 2, "tpm_nsc0") == NULL ) {
rc = -EBUSY;
goto err_del_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_del_dev:
platform_device_del(pdev);
err_put_dev:
platform_device_put(pdev);
err_unreg_drv:
platform_driver_unregister(&nsc_drv);
return rc;
}
static void __exit cleanup_nsc(void)
{
if (pdev) {
tpm_nsc_remove(&pdev->dev);
platform_device_unregister(pdev);
}
platform_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");

73
drivers/char/tpm/tpm_of.c Normal file
View File

@@ -0,0 +1,73 @@
/*
* Copyright 2012 IBM Corporation
*
* Author: Ashley Lai <adlai@us.ibm.com>
*
* Maintained by: <tpmdd-devel@lists.sourceforge.net>
*
* Read the event log created by the firmware on PPC64
*
* 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/slab.h>
#include <linux/of.h>
#include "tpm.h"
#include "tpm_eventlog.h"
int read_log(struct tpm_bios_log *log)
{
struct device_node *np;
const u32 *sizep;
const __be64 *basep;
if (log->bios_event_log != NULL) {
pr_err("%s: ERROR - Eventlog already initialized\n", __func__);
return -EFAULT;
}
np = of_find_node_by_name(NULL, "ibm,vtpm");
if (!np) {
pr_err("%s: ERROR - IBMVTPM not supported\n", __func__);
return -ENODEV;
}
sizep = of_get_property(np, "linux,sml-size", NULL);
if (sizep == NULL) {
pr_err("%s: ERROR - SML size not found\n", __func__);
goto cleanup_eio;
}
if (*sizep == 0) {
pr_err("%s: ERROR - event log area empty\n", __func__);
goto cleanup_eio;
}
basep = of_get_property(np, "linux,sml-base", NULL);
if (basep == NULL) {
pr_err(KERN_ERR "%s: ERROR - SML not found\n", __func__);
goto cleanup_eio;
}
of_node_put(np);
log->bios_event_log = kmalloc(*sizep, GFP_KERNEL);
if (!log->bios_event_log) {
pr_err("%s: ERROR - Not enough memory for BIOS measurements\n",
__func__);
return -ENOMEM;
}
log->bios_event_log_end = log->bios_event_log + *sizep;
memcpy(log->bios_event_log, __va(be64_to_cpup(basep)), *sizep);
return 0;
cleanup_eio:
of_node_put(np);
return -EIO;
}

463
drivers/char/tpm/tpm_ppi.c Normal file
View File

@@ -0,0 +1,463 @@
#include <linux/acpi.h>
#include <acpi/acpi_drivers.h>
#include "tpm.h"
static const u8 tpm_ppi_uuid[] = {
0xA6, 0xFA, 0xDD, 0x3D,
0x1B, 0x36,
0xB4, 0x4E,
0xA4, 0x24,
0x8D, 0x10, 0x08, 0x9D, 0x16, 0x53
};
static char *tpm_device_name = "TPM";
#define TPM_PPI_REVISION_ID 1
#define TPM_PPI_FN_VERSION 1
#define TPM_PPI_FN_SUBREQ 2
#define TPM_PPI_FN_GETREQ 3
#define TPM_PPI_FN_GETACT 4
#define TPM_PPI_FN_GETRSP 5
#define TPM_PPI_FN_SUBREQ2 7
#define TPM_PPI_FN_GETOPR 8
#define PPI_TPM_REQ_MAX 22
#define PPI_VS_REQ_START 128
#define PPI_VS_REQ_END 255
#define PPI_VERSION_LEN 3
static acpi_status ppi_callback(acpi_handle handle, u32 level, void *context,
void **return_value)
{
acpi_status status;
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
status = acpi_get_name(handle, ACPI_FULL_PATHNAME, &buffer);
if (strstr(buffer.pointer, context) != NULL) {
*return_value = handle;
kfree(buffer.pointer);
return AE_CTRL_TERMINATE;
}
return AE_OK;
}
static inline void ppi_assign_params(union acpi_object params[4],
u64 function_num)
{
params[0].type = ACPI_TYPE_BUFFER;
params[0].buffer.length = sizeof(tpm_ppi_uuid);
params[0].buffer.pointer = (char *)tpm_ppi_uuid;
params[1].type = ACPI_TYPE_INTEGER;
params[1].integer.value = TPM_PPI_REVISION_ID;
params[2].type = ACPI_TYPE_INTEGER;
params[2].integer.value = function_num;
params[3].type = ACPI_TYPE_PACKAGE;
params[3].package.count = 0;
params[3].package.elements = NULL;
}
static ssize_t tpm_show_ppi_version(struct device *dev,
struct device_attribute *attr, char *buf)
{
acpi_handle handle;
acpi_status status;
struct acpi_object_list input;
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object params[4];
union acpi_object *obj;
input.count = 4;
ppi_assign_params(params, TPM_PPI_FN_VERSION);
input.pointer = params;
status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
ACPI_UINT32_MAX, ppi_callback, NULL,
tpm_device_name, &handle);
if (ACPI_FAILURE(status))
return -ENXIO;
status = acpi_evaluate_object_typed(handle, "_DSM", &input, &output,
ACPI_TYPE_STRING);
if (ACPI_FAILURE(status))
return -ENOMEM;
obj = (union acpi_object *)output.pointer;
status = scnprintf(buf, PAGE_SIZE, "%s\n", obj->string.pointer);
kfree(output.pointer);
return status;
}
static ssize_t tpm_show_ppi_request(struct device *dev,
struct device_attribute *attr, char *buf)
{
acpi_handle handle;
acpi_status status;
struct acpi_object_list input;
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object params[4];
union acpi_object *ret_obj;
input.count = 4;
ppi_assign_params(params, TPM_PPI_FN_GETREQ);
input.pointer = params;
status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
ACPI_UINT32_MAX, ppi_callback, NULL,
tpm_device_name, &handle);
if (ACPI_FAILURE(status))
return -ENXIO;
status = acpi_evaluate_object_typed(handle, "_DSM", &input, &output,
ACPI_TYPE_PACKAGE);
if (ACPI_FAILURE(status))
return -ENOMEM;
/*
* output.pointer should be of package type, including two integers.
* The first is function return code, 0 means success and 1 means
* error. The second is pending TPM operation requested by the OS, 0
* means none and >0 means operation value.
*/
ret_obj = ((union acpi_object *)output.pointer)->package.elements;
if (ret_obj->type == ACPI_TYPE_INTEGER) {
if (ret_obj->integer.value) {
status = -EFAULT;
goto cleanup;
}
ret_obj++;
if (ret_obj->type == ACPI_TYPE_INTEGER)
status = scnprintf(buf, PAGE_SIZE, "%llu\n",
ret_obj->integer.value);
else
status = -EINVAL;
} else {
status = -EINVAL;
}
cleanup:
kfree(output.pointer);
return status;
}
static ssize_t tpm_store_ppi_request(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
char version[PPI_VERSION_LEN + 1];
acpi_handle handle;
acpi_status status;
struct acpi_object_list input;
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object params[4];
union acpi_object obj;
u32 req;
u64 ret;
input.count = 4;
ppi_assign_params(params, TPM_PPI_FN_VERSION);
input.pointer = params;
status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
ACPI_UINT32_MAX, ppi_callback, NULL,
tpm_device_name, &handle);
if (ACPI_FAILURE(status))
return -ENXIO;
status = acpi_evaluate_object_typed(handle, "_DSM", &input, &output,
ACPI_TYPE_STRING);
if (ACPI_FAILURE(status))
return -ENOMEM;
strlcpy(version,
((union acpi_object *)output.pointer)->string.pointer,
PPI_VERSION_LEN + 1);
kfree(output.pointer);
output.length = ACPI_ALLOCATE_BUFFER;
output.pointer = NULL;
/*
* the function to submit TPM operation request to pre-os environment
* is updated with function index from SUBREQ to SUBREQ2 since PPI
* version 1.1
*/
if (strcmp(version, "1.1") == -1)
params[2].integer.value = TPM_PPI_FN_SUBREQ;
else
params[2].integer.value = TPM_PPI_FN_SUBREQ2;
/*
* PPI spec defines params[3].type as ACPI_TYPE_PACKAGE. Some BIOS
* accept buffer/string/integer type, but some BIOS accept buffer/
* string/package type. For PPI version 1.0 and 1.1, use buffer type
* for compatibility, and use package type since 1.2 according to spec.
*/
if (strcmp(version, "1.2") == -1) {
params[3].type = ACPI_TYPE_BUFFER;
params[3].buffer.length = sizeof(req);
sscanf(buf, "%d", &req);
params[3].buffer.pointer = (char *)&req;
} else {
params[3].package.count = 1;
obj.type = ACPI_TYPE_INTEGER;
sscanf(buf, "%llu", &obj.integer.value);
params[3].package.elements = &obj;
}
status = acpi_evaluate_object_typed(handle, "_DSM", &input, &output,
ACPI_TYPE_INTEGER);
if (ACPI_FAILURE(status))
return -ENOMEM;
ret = ((union acpi_object *)output.pointer)->integer.value;
if (ret == 0)
status = (acpi_status)count;
else if (ret == 1)
status = -EPERM;
else
status = -EFAULT;
kfree(output.pointer);
return status;
}
static ssize_t tpm_show_ppi_transition_action(struct device *dev,
struct device_attribute *attr,
char *buf)
{
char version[PPI_VERSION_LEN + 1];
acpi_handle handle;
acpi_status status;
struct acpi_object_list input;
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object params[4];
u32 ret;
char *info[] = {
"None",
"Shutdown",
"Reboot",
"OS Vendor-specific",
"Error",
};
input.count = 4;
ppi_assign_params(params, TPM_PPI_FN_VERSION);
input.pointer = params;
status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
ACPI_UINT32_MAX, ppi_callback, NULL,
tpm_device_name, &handle);
if (ACPI_FAILURE(status))
return -ENXIO;
status = acpi_evaluate_object_typed(handle, "_DSM", &input, &output,
ACPI_TYPE_STRING);
if (ACPI_FAILURE(status))
return -ENOMEM;
strlcpy(version,
((union acpi_object *)output.pointer)->string.pointer,
PPI_VERSION_LEN + 1);
/*
* PPI spec defines params[3].type as empty package, but some platforms
* (e.g. Capella with PPI 1.0) need integer/string/buffer type, so for
* compatibility, define params[3].type as buffer, if PPI version < 1.2
*/
if (strcmp(version, "1.2") == -1) {
params[3].type = ACPI_TYPE_BUFFER;
params[3].buffer.length = 0;
params[3].buffer.pointer = NULL;
}
params[2].integer.value = TPM_PPI_FN_GETACT;
kfree(output.pointer);
output.length = ACPI_ALLOCATE_BUFFER;
output.pointer = NULL;
status = acpi_evaluate_object_typed(handle, "_DSM", &input, &output,
ACPI_TYPE_INTEGER);
if (ACPI_FAILURE(status))
return -ENOMEM;
ret = ((union acpi_object *)output.pointer)->integer.value;
if (ret < ARRAY_SIZE(info) - 1)
status = scnprintf(buf, PAGE_SIZE, "%d: %s\n", ret, info[ret]);
else
status = scnprintf(buf, PAGE_SIZE, "%d: %s\n", ret,
info[ARRAY_SIZE(info)-1]);
kfree(output.pointer);
return status;
}
static ssize_t tpm_show_ppi_response(struct device *dev,
struct device_attribute *attr,
char *buf)
{
acpi_handle handle;
acpi_status status;
struct acpi_object_list input;
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object params[4];
union acpi_object *ret_obj;
u64 req;
input.count = 4;
ppi_assign_params(params, TPM_PPI_FN_GETRSP);
input.pointer = params;
status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
ACPI_UINT32_MAX, ppi_callback, NULL,
tpm_device_name, &handle);
if (ACPI_FAILURE(status))
return -ENXIO;
status = acpi_evaluate_object_typed(handle, "_DSM", &input, &output,
ACPI_TYPE_PACKAGE);
if (ACPI_FAILURE(status))
return -ENOMEM;
/*
* parameter output.pointer should be of package type, including
* 3 integers. The first means function return code, the second means
* most recent TPM operation request, and the last means response to
* the most recent TPM operation request. Only if the first is 0, and
* the second integer is not 0, the response makes sense.
*/
ret_obj = ((union acpi_object *)output.pointer)->package.elements;
if (ret_obj->type != ACPI_TYPE_INTEGER) {
status = -EINVAL;
goto cleanup;
}
if (ret_obj->integer.value) {
status = -EFAULT;
goto cleanup;
}
ret_obj++;
if (ret_obj->type != ACPI_TYPE_INTEGER) {
status = -EINVAL;
goto cleanup;
}
if (ret_obj->integer.value) {
req = ret_obj->integer.value;
ret_obj++;
if (ret_obj->type != ACPI_TYPE_INTEGER) {
status = -EINVAL;
goto cleanup;
}
if (ret_obj->integer.value == 0)
status = scnprintf(buf, PAGE_SIZE, "%llu %s\n", req,
"0: Success");
else if (ret_obj->integer.value == 0xFFFFFFF0)
status = scnprintf(buf, PAGE_SIZE, "%llu %s\n", req,
"0xFFFFFFF0: User Abort");
else if (ret_obj->integer.value == 0xFFFFFFF1)
status = scnprintf(buf, PAGE_SIZE, "%llu %s\n", req,
"0xFFFFFFF1: BIOS Failure");
else if (ret_obj->integer.value >= 1 &&
ret_obj->integer.value <= 0x00000FFF)
status = scnprintf(buf, PAGE_SIZE, "%llu %llu: %s\n",
req, ret_obj->integer.value,
"Corresponding TPM error");
else
status = scnprintf(buf, PAGE_SIZE, "%llu %llu: %s\n",
req, ret_obj->integer.value,
"Error");
} else {
status = scnprintf(buf, PAGE_SIZE, "%llu: %s\n",
ret_obj->integer.value, "No Recent Request");
}
cleanup:
kfree(output.pointer);
return status;
}
static ssize_t show_ppi_operations(char *buf, u32 start, u32 end)
{
char *str = buf;
char version[PPI_VERSION_LEN + 1];
acpi_handle handle;
acpi_status status;
struct acpi_object_list input;
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object params[4];
union acpi_object obj;
int i;
u32 ret;
char *info[] = {
"Not implemented",
"BIOS only",
"Blocked for OS by BIOS",
"User required",
"User not required",
};
input.count = 4;
ppi_assign_params(params, TPM_PPI_FN_VERSION);
input.pointer = params;
status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
ACPI_UINT32_MAX, ppi_callback, NULL,
tpm_device_name, &handle);
if (ACPI_FAILURE(status))
return -ENXIO;
status = acpi_evaluate_object_typed(handle, "_DSM", &input, &output,
ACPI_TYPE_STRING);
if (ACPI_FAILURE(status))
return -ENOMEM;
strlcpy(version,
((union acpi_object *)output.pointer)->string.pointer,
PPI_VERSION_LEN + 1);
kfree(output.pointer);
output.length = ACPI_ALLOCATE_BUFFER;
output.pointer = NULL;
if (strcmp(version, "1.2") == -1)
return -EPERM;
params[2].integer.value = TPM_PPI_FN_GETOPR;
params[3].package.count = 1;
obj.type = ACPI_TYPE_INTEGER;
params[3].package.elements = &obj;
for (i = start; i <= end; i++) {
obj.integer.value = i;
status = acpi_evaluate_object_typed(handle, "_DSM",
&input, &output, ACPI_TYPE_INTEGER);
if (ACPI_FAILURE(status))
return -ENOMEM;
ret = ((union acpi_object *)output.pointer)->integer.value;
if (ret > 0 && ret < ARRAY_SIZE(info))
str += scnprintf(str, PAGE_SIZE, "%d %d: %s\n",
i, ret, info[ret]);
kfree(output.pointer);
output.length = ACPI_ALLOCATE_BUFFER;
output.pointer = NULL;
}
return str - buf;
}
static ssize_t tpm_show_ppi_tcg_operations(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return show_ppi_operations(buf, 0, PPI_TPM_REQ_MAX);
}
static ssize_t tpm_show_ppi_vs_operations(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return show_ppi_operations(buf, PPI_VS_REQ_START, PPI_VS_REQ_END);
}
static DEVICE_ATTR(version, S_IRUGO, tpm_show_ppi_version, NULL);
static DEVICE_ATTR(request, S_IRUGO | S_IWUSR | S_IWGRP,
tpm_show_ppi_request, tpm_store_ppi_request);
static DEVICE_ATTR(transition_action, S_IRUGO,
tpm_show_ppi_transition_action, NULL);
static DEVICE_ATTR(response, S_IRUGO, tpm_show_ppi_response, NULL);
static DEVICE_ATTR(tcg_operations, S_IRUGO, tpm_show_ppi_tcg_operations, NULL);
static DEVICE_ATTR(vs_operations, S_IRUGO, tpm_show_ppi_vs_operations, NULL);
static struct attribute *ppi_attrs[] = {
&dev_attr_version.attr,
&dev_attr_request.attr,
&dev_attr_transition_action.attr,
&dev_attr_response.attr,
&dev_attr_tcg_operations.attr,
&dev_attr_vs_operations.attr, NULL,
};
static struct attribute_group ppi_attr_grp = {
.name = "ppi",
.attrs = ppi_attrs
};
int tpm_add_ppi(struct kobject *parent)
{
return sysfs_create_group(parent, &ppi_attr_grp);
}
EXPORT_SYMBOL_GPL(tpm_add_ppi);
void tpm_remove_ppi(struct kobject *parent)
{
sysfs_remove_group(parent, &ppi_attr_grp);
}
EXPORT_SYMBOL_GPL(tpm_remove_ppi);
MODULE_LICENSE("GPL");

998
drivers/char/tpm/tpm_tis.c Normal file
View File

@@ -0,0 +1,998 @@
/*
* Copyright (C) 2005, 2006 IBM Corporation
*
* Authors:
* Leendert van Doorn <leendert@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 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/slab.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <linux/acpi.h>
#include <linux/freezer.h>
#include "tpm.h"
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_MUTEX(tis_lock);
#if defined(CONFIG_PNP) && defined(CONFIG_ACPI)
static int is_itpm(struct pnp_dev *dev)
{
struct acpi_device *acpi = pnp_acpi_device(dev);
struct acpi_hardware_id *id;
if (!acpi)
return 0;
list_for_each_entry(id, &acpi->pnp.ids, list) {
if (!strcmp("INTC0102", id->id))
return 1;
}
return 0;
}
#else
static inline int is_itpm(struct pnp_dev *dev)
{
return 0;
}
#endif
/* Before we attempt to access the TPM we must see that the valid bit is set.
* The specification says that this bit is 0 at reset and remains 0 until the
* 'TPM has gone through its self test and initialization and has established
* correct values in the other bits.' */
static int wait_startup(struct tpm_chip *chip, int l)
{
unsigned long stop = jiffies + chip->vendor.timeout_a;
do {
if (ioread8(chip->vendor.iobase + TPM_ACCESS(l)) &
TPM_ACCESS_VALID)
return 0;
msleep(TPM_TIMEOUT);
} while (time_before(jiffies, stop));
return -1;
}
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, timeout;
long rc;
if (check_locality(chip, l) >= 0)
return l;
iowrite8(TPM_ACCESS_REQUEST_USE,
chip->vendor.iobase + TPM_ACCESS(l));
stop = jiffies + chip->vendor.timeout_a;
if (chip->vendor.irq) {
again:
timeout = stop - jiffies;
if ((long)timeout <= 0)
return -1;
rc = wait_event_interruptible_timeout(chip->vendor.int_queue,
(check_locality
(chip, l) >= 0),
timeout);
if (rc > 0)
return l;
if (rc == -ERESTARTSYS && freezing(current)) {
clear_thread_flag(TIF_SIGPENDING);
goto again;
}
} else {
/* wait for burstcount */
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 recv_data(struct tpm_chip *chip, u8 *buf, size_t count)
{
int size = 0, burstcnt;
while (size < count &&
wait_for_tpm_stat(chip,
TPM_STS_DATA_AVAIL | TPM_STS_VALID,
chip->vendor.timeout_c,
&chip->vendor.read_queue, true)
== 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_tpm_stat(chip, TPM_STS_VALID, chip->vendor.timeout_c,
&chip->vendor.int_queue, false);
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;
}
static bool itpm;
module_param(itpm, bool, 0444);
MODULE_PARM_DESC(itpm, "Force iTPM workarounds (found on some Lenovo laptops)");
/*
* 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_data(struct tpm_chip *chip, u8 *buf, size_t len)
{
int rc, status, burstcnt;
size_t count = 0;
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_tpm_stat
(chip, TPM_STS_COMMAND_READY, chip->vendor.timeout_b,
&chip->vendor.int_queue, false) < 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_tpm_stat(chip, TPM_STS_VALID, chip->vendor.timeout_c,
&chip->vendor.int_queue, false);
status = tpm_tis_status(chip);
if (!itpm && (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_tpm_stat(chip, TPM_STS_VALID, chip->vendor.timeout_c,
&chip->vendor.int_queue, false);
status = tpm_tis_status(chip);
if ((status & TPM_STS_DATA_EXPECT) != 0) {
rc = -EIO;
goto out_err;
}
return 0;
out_err:
tpm_tis_ready(chip);
release_locality(chip, chip->vendor.locality, 0);
return rc;
}
/*
* 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;
u32 ordinal;
rc = tpm_tis_send_data(chip, buf, len);
if (rc < 0)
return rc;
/* 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_tpm_stat
(chip, TPM_STS_DATA_AVAIL | TPM_STS_VALID,
tpm_calc_ordinal_duration(chip, ordinal),
&chip->vendor.read_queue, false) < 0) {
rc = -ETIME;
goto out_err;
}
}
return len;
out_err:
tpm_tis_ready(chip);
release_locality(chip, chip->vendor.locality, 0);
return rc;
}
struct tis_vendor_timeout_override {
u32 did_vid;
unsigned long timeout_us[4];
};
static struct tis_vendor_timeout_override vendor_timeout_overrides[] = {
{ 0x32041114, { 50000, 50000, 50000, 50000 } }, /* Atmel 3204 */
{ 0x53544d20, { 4000000, 4000000, 4000000, 4000000 } }, /* STMicro */
};
static void report_updated_timeout(struct tpm_chip *chip, const char *id,
unsigned long old, unsigned long new)
{
dev_info(chip->dev, " timeout_%s %d -> %d\n", id,
jiffies_to_usecs(old), jiffies_to_usecs(new));
}
static void tpm_tis_update_timeouts(struct tpm_chip *chip)
{
int i;
u32 did_vid;
did_vid = ioread32(chip->vendor.iobase + TPM_DID_VID(0));
for (i = 0; i < ARRAY_SIZE(vendor_timeout_overrides); i++) {
struct tis_vendor_timeout_override *tod;
unsigned long new;
if (vendor_timeout_overrides[i].did_vid != did_vid)
continue;
tod = &vendor_timeout_overrides[i];
dev_info(chip->dev, "Adjusting reported timeouts:\n");
new = usecs_to_jiffies(tod->timeout_us[0]);
if (new != 0) {
report_updated_timeout(chip, "a",
chip->vendor.timeout_a, new);
chip->vendor.timeout_a = new;
}
new = usecs_to_jiffies(tod->timeout_us[1]);
if (new != 0) {
report_updated_timeout(chip, "b",
chip->vendor.timeout_b, new);
chip->vendor.timeout_b = new;
}
new = usecs_to_jiffies(tod->timeout_us[2]);
if (new != 0) {
report_updated_timeout(chip, "c",
chip->vendor.timeout_c, new);
chip->vendor.timeout_c = new;
}
new = usecs_to_jiffies(tod->timeout_us[3]);
if (new != 0) {
report_updated_timeout(chip, "d",
chip->vendor.timeout_d, new);
chip->vendor.timeout_d = new;
}
chip->vendor.timeout_adjusted = true;
break;
}
}
/*
* Early probing for iTPM with STS_DATA_EXPECT flaw.
* Try sending command without itpm flag set and if that
* fails, repeat with itpm flag set.
*/
static int probe_itpm(struct tpm_chip *chip)
{
int rc = 0;
u8 cmd_getticks[] = {
0x00, 0xc1, 0x00, 0x00, 0x00, 0x0a,
0x00, 0x00, 0x00, 0xf1
};
size_t len = sizeof(cmd_getticks);
bool rem_itpm = itpm;
u16 vendor = ioread16(chip->vendor.iobase + TPM_DID_VID(0));
/* probe only iTPMS */
if (vendor != TPM_VID_INTEL)
return 0;
itpm = false;
rc = tpm_tis_send_data(chip, cmd_getticks, len);
if (rc == 0)
goto out;
tpm_tis_ready(chip);
release_locality(chip, chip->vendor.locality, 0);
itpm = true;
rc = tpm_tis_send_data(chip, cmd_getticks, len);
if (rc == 0) {
dev_info(chip->dev, "Detected an iTPM.\n");
rc = 1;
} else
rc = -EFAULT;
out:
itpm = rem_itpm;
tpm_tis_ready(chip);
release_locality(chip, chip->vendor.locality, 0);
return rc;
}
static bool tpm_tis_req_canceled(struct tpm_chip *chip, u8 status)
{
switch (chip->vendor.manufacturer_id) {
case TPM_VID_WINBOND:
return ((status == TPM_STS_VALID) ||
(status == (TPM_STS_VALID | TPM_STS_COMMAND_READY)));
case TPM_VID_STM:
return (status == (TPM_STS_VALID | TPM_STS_COMMAND_READY));
default:
return (status == TPM_STS_COMMAND_READY);
}
}
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 DEVICE_ATTR(durations, S_IRUGO, tpm_show_durations, NULL);
static DEVICE_ATTR(timeouts, S_IRUGO, tpm_show_timeouts, NULL);
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,
&dev_attr_durations.attr,
&dev_attr_timeouts.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,
.update_timeouts = tpm_tis_update_timeouts,
.req_complete_mask = TPM_STS_DATA_AVAIL | TPM_STS_VALID,
.req_complete_val = TPM_STS_DATA_AVAIL | TPM_STS_VALID,
.req_canceled = tpm_tis_req_canceled,
.attr_group = &tis_attr_grp,
.miscdev = {
.fops = &tis_ops,},
};
static irqreturn_t tis_int_probe(int irq, void *dev_id)
{
struct tpm_chip *chip = dev_id;
u32 interrupt;
interrupt = ioread32(chip->vendor.iobase +
TPM_INT_STATUS(chip->vendor.locality));
if (interrupt == 0)
return IRQ_NONE;
chip->vendor.probed_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 dummy, void *dev_id)
{
struct tpm_chip *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 bool interrupts = true;
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, unsigned int irq)
{
u32 vendor, intfcaps, intmask;
int rc, i, irq_s, irq_e, probe;
struct tpm_chip *chip;
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;
}
/* 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);
if (wait_startup(chip, 0) != 0) {
rc = -ENODEV;
goto out_err;
}
if (request_locality(chip, 0) != 0) {
rc = -ENODEV;
goto out_err;
}
vendor = ioread32(chip->vendor.iobase + TPM_DID_VID(0));
chip->vendor.manufacturer_id = vendor;
dev_info(dev,
"1.2 TPM (device-id 0x%X, rev-id %d) [gentle shutdown]\n",
vendor >> 16, ioread8(chip->vendor.iobase + TPM_RID(0)));
if (!itpm) {
probe = probe_itpm(chip);
if (probe < 0) {
rc = -ENODEV;
goto out_err;
}
itpm = !!probe;
}
if (itpm)
dev_info(dev, "Intel iTPM workaround enabled\n");
/* 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");
/* get the timeouts before testing for irqs */
if (tpm_get_timeouts(chip)) {
dev_err(dev, "Could not get TPM timeouts and durations\n");
rc = -ENODEV;
goto out_err;
}
if (tpm_do_selftest(chip))
dev_err(dev, "TPM self test failed - ignoring\n");
/* 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 = irq;
if (interrupts && !chip->vendor.irq) {
irq_s =
ioread8(chip->vendor.iobase +
TPM_INT_VECTOR(chip->vendor.locality));
if (irq_s) {
irq_e = irq_s;
} else {
irq_s = 3;
irq_e = 15;
}
for (i = irq_s; i <= irq_e && 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));
chip->vendor.probed_irq = 0;
/* Generate Interrupts */
tpm_gen_interrupt(chip);
chip->vendor.irq = chip->vendor.probed_irq;
/* free_irq will call into tis_int_probe;
clear all irqs we haven't seen while doing
tpm_gen_interrupt */
iowrite32(ioread32
(chip->vendor.iobase +
TPM_INT_STATUS(chip->vendor.locality)),
chip->vendor.iobase +
TPM_INT_STATUS(chip->vendor.locality));
/* 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);
mutex_lock(&tis_lock);
list_add(&chip->vendor.list, &tis_chips);
mutex_unlock(&tis_lock);
return 0;
out_err:
if (chip->vendor.iobase)
iounmap(chip->vendor.iobase);
tpm_remove_hardware(chip->dev);
return rc;
}
#if defined(CONFIG_PNP) || defined(CONFIG_PM_SLEEP)
static void tpm_tis_reenable_interrupts(struct tpm_chip *chip)
{
u32 intmask;
/* reenable interrupts that device may have lost or
BIOS/firmware may have disabled */
iowrite8(chip->vendor.irq, chip->vendor.iobase +
TPM_INT_VECTOR(chip->vendor.locality));
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 | TPM_GLOBAL_INT_ENABLE;
iowrite32(intmask,
chip->vendor.iobase + TPM_INT_ENABLE(chip->vendor.locality));
}
#endif
#ifdef CONFIG_PNP
static int tpm_tis_pnp_init(struct pnp_dev *pnp_dev,
const struct pnp_device_id *pnp_id)
{
resource_size_t start, len;
unsigned int irq = 0;
start = pnp_mem_start(pnp_dev, 0);
len = pnp_mem_len(pnp_dev, 0);
if (pnp_irq_valid(pnp_dev, 0))
irq = pnp_irq(pnp_dev, 0);
else
interrupts = false;
if (is_itpm(pnp_dev))
itpm = true;
return tpm_tis_init(&pnp_dev->dev, start, len, irq);
}
static int tpm_tis_pnp_suspend(struct pnp_dev *dev, pm_message_t msg)
{
return tpm_pm_suspend(&dev->dev);
}
static int tpm_tis_pnp_resume(struct pnp_dev *dev)
{
struct tpm_chip *chip = pnp_get_drvdata(dev);
int ret;
if (chip->vendor.irq)
tpm_tis_reenable_interrupts(chip);
ret = tpm_pm_resume(&dev->dev);
if (!ret)
tpm_do_selftest(chip);
return ret;
}
static struct pnp_device_id tpm_pnp_tbl[] = {
{"PNP0C31", 0}, /* TPM */
{"ATM1200", 0}, /* Atmel */
{"IFX0102", 0}, /* Infineon */
{"BCM0101", 0}, /* Broadcom */
{"BCM0102", 0}, /* Broadcom */
{"NSC1200", 0}, /* National */
{"ICO0102", 0}, /* Intel */
/* Add new here */
{"", 0}, /* User Specified */
{"", 0} /* Terminator */
};
MODULE_DEVICE_TABLE(pnp, tpm_pnp_tbl);
static void tpm_tis_pnp_remove(struct pnp_dev *dev)
{
struct tpm_chip *chip = pnp_get_drvdata(dev);
tpm_dev_vendor_release(chip);
kfree(chip);
}
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,
.remove = tpm_tis_pnp_remove,
};
#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");
#endif
#ifdef CONFIG_PM_SLEEP
static int tpm_tis_resume(struct device *dev)
{
struct tpm_chip *chip = dev_get_drvdata(dev);
if (chip->vendor.irq)
tpm_tis_reenable_interrupts(chip);
return tpm_pm_resume(dev);
}
#endif
static SIMPLE_DEV_PM_OPS(tpm_tis_pm, tpm_pm_suspend, tpm_tis_resume);
static struct platform_driver tis_drv = {
.driver = {
.name = "tpm_tis",
.owner = THIS_MODULE,
.pm = &tpm_tis_pm,
},
};
static struct platform_device *pdev;
static bool 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;
#ifdef CONFIG_PNP
if (!force)
return pnp_register_driver(&tis_pnp_driver);
#endif
rc = platform_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, TIS_MEM_BASE, TIS_MEM_LEN, 0)) != 0) {
platform_device_unregister(pdev);
platform_driver_unregister(&tis_drv);
}
return rc;
}
static void __exit cleanup_tis(void)
{
struct tpm_vendor_specific *i, *j;
struct tpm_chip *chip;
mutex_lock(&tis_lock);
list_for_each_entry_safe(i, j, &tis_chips, list) {
chip = to_tpm_chip(i);
tpm_remove_hardware(chip->dev);
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);
}
mutex_unlock(&tis_lock);
#ifdef CONFIG_PNP
if (!force) {
pnp_unregister_driver(&tis_pnp_driver);
return;
}
#endif
platform_device_unregister(pdev);
platform_driver_unregister(&tis_drv);
}
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");