mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-09 01:28:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
125
drivers/char/tpm/Kconfig
Normal file
125
drivers/char/tpm/Kconfig
Normal file
|
@ -0,0 +1,125 @@
|
|||
#
|
||||
# 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_ATMEL
|
||||
tristate "TPM Interface Specification 1.2 Interface (I2C - Atmel)"
|
||||
depends on I2C
|
||||
---help---
|
||||
If you have an Atmel I2C TPM security chip 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_atmel.
|
||||
|
||||
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_i2c_infineon.
|
||||
|
||||
config TCG_TIS_I2C_NUVOTON
|
||||
tristate "TPM Interface Specification 1.2 Interface (I2C - Nuvoton)"
|
||||
depends on I2C
|
||||
---help---
|
||||
If you have a TPM security chip with an I2C interface from
|
||||
Nuvoton Technology Corp. 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_i2c_nuvoton.
|
||||
|
||||
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_MAP
|
||||
---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.
|
||||
|
||||
config TCG_XEN
|
||||
tristate "XEN TPM Interface"
|
||||
depends on TCG_TPM && XEN
|
||||
select XEN_XENBUS_FRONTEND
|
||||
---help---
|
||||
If you want to make TPM support available to a Xen user domain,
|
||||
say Yes and it will be accessible from within Linux. See
|
||||
the manpages for xl, xl.conf, and docs/misc/vtpm.txt in
|
||||
the Xen source repository for more details.
|
||||
To compile this driver as a module, choose M here; the module
|
||||
will be called xen-tpmfront.
|
||||
|
||||
endif # TCG_TPM
|
24
drivers/char/tpm/Makefile
Normal file
24
drivers/char/tpm/Makefile
Normal file
|
@ -0,0 +1,24 @@
|
|||
#
|
||||
# Makefile for the kernel tpm device drivers.
|
||||
#
|
||||
obj-$(CONFIG_TCG_TPM) += tpm.o
|
||||
tpm-y := tpm-interface.o tpm-dev.o tpm-sysfs.o
|
||||
tpm-$(CONFIG_ACPI) += tpm_ppi.o
|
||||
|
||||
ifdef CONFIG_ACPI
|
||||
tpm-y += tpm_eventlog.o tpm_acpi.o
|
||||
else
|
||||
ifdef CONFIG_TCG_IBMVTPM
|
||||
tpm-y += tpm_eventlog.o tpm_of.o
|
||||
endif
|
||||
endif
|
||||
obj-$(CONFIG_TCG_TIS) += tpm_tis.o
|
||||
obj-$(CONFIG_TCG_TIS_I2C_ATMEL) += tpm_i2c_atmel.o
|
||||
obj-$(CONFIG_TCG_TIS_I2C_INFINEON) += tpm_i2c_infineon.o
|
||||
obj-$(CONFIG_TCG_TIS_I2C_NUVOTON) += tpm_i2c_nuvoton.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
|
||||
obj-$(CONFIG_TCG_XEN) += xen-tpmfront.o
|
213
drivers/char/tpm/tpm-dev.c
Normal file
213
drivers/char/tpm/tpm-dev.c
Normal file
|
@ -0,0 +1,213 @@
|
|||
/*
|
||||
* 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>
|
||||
*
|
||||
* Copyright (C) 2013 Obsidian Research Corp
|
||||
* Jason Gunthorpe <jgunthorpe@obsidianresearch.com>
|
||||
*
|
||||
* Device file system interface to the 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/miscdevice.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include "tpm.h"
|
||||
|
||||
struct file_priv {
|
||||
struct tpm_chip *chip;
|
||||
|
||||
/* Data passed to and from the tpm via the read/write calls */
|
||||
atomic_t data_pending;
|
||||
struct mutex buffer_mutex;
|
||||
|
||||
struct timer_list user_read_timer; /* user needs to claim result */
|
||||
struct work_struct work;
|
||||
|
||||
u8 data_buffer[TPM_BUFSIZE];
|
||||
};
|
||||
|
||||
static void user_reader_timeout(unsigned long ptr)
|
||||
{
|
||||
struct file_priv *priv = (struct file_priv *)ptr;
|
||||
|
||||
schedule_work(&priv->work);
|
||||
}
|
||||
|
||||
static void timeout_work(struct work_struct *work)
|
||||
{
|
||||
struct file_priv *priv = container_of(work, struct file_priv, work);
|
||||
|
||||
mutex_lock(&priv->buffer_mutex);
|
||||
atomic_set(&priv->data_pending, 0);
|
||||
memset(priv->data_buffer, 0, sizeof(priv->data_buffer));
|
||||
mutex_unlock(&priv->buffer_mutex);
|
||||
}
|
||||
|
||||
static int tpm_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct miscdevice *misc = file->private_data;
|
||||
struct tpm_chip *chip = container_of(misc, struct tpm_chip,
|
||||
vendor.miscdev);
|
||||
struct file_priv *priv;
|
||||
|
||||
/* It's assured that the chip will be opened just once,
|
||||
* by the check of is_open variable, which is protected
|
||||
* by driver_lock. */
|
||||
if (test_and_set_bit(0, &chip->is_open)) {
|
||||
dev_dbg(chip->dev, "Another process owns this TPM\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
|
||||
if (priv == NULL) {
|
||||
clear_bit(0, &chip->is_open);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
priv->chip = chip;
|
||||
atomic_set(&priv->data_pending, 0);
|
||||
mutex_init(&priv->buffer_mutex);
|
||||
setup_timer(&priv->user_read_timer, user_reader_timeout,
|
||||
(unsigned long)priv);
|
||||
INIT_WORK(&priv->work, timeout_work);
|
||||
|
||||
file->private_data = priv;
|
||||
get_device(chip->dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t tpm_read(struct file *file, char __user *buf,
|
||||
size_t size, loff_t *off)
|
||||
{
|
||||
struct file_priv *priv = file->private_data;
|
||||
ssize_t ret_size;
|
||||
int rc;
|
||||
|
||||
del_singleshot_timer_sync(&priv->user_read_timer);
|
||||
flush_work(&priv->work);
|
||||
ret_size = atomic_read(&priv->data_pending);
|
||||
if (ret_size > 0) { /* relay data */
|
||||
ssize_t orig_ret_size = ret_size;
|
||||
if (size < ret_size)
|
||||
ret_size = size;
|
||||
|
||||
mutex_lock(&priv->buffer_mutex);
|
||||
rc = copy_to_user(buf, priv->data_buffer, ret_size);
|
||||
memset(priv->data_buffer, 0, orig_ret_size);
|
||||
if (rc)
|
||||
ret_size = -EFAULT;
|
||||
|
||||
mutex_unlock(&priv->buffer_mutex);
|
||||
}
|
||||
|
||||
atomic_set(&priv->data_pending, 0);
|
||||
|
||||
return ret_size;
|
||||
}
|
||||
|
||||
static ssize_t tpm_write(struct file *file, const char __user *buf,
|
||||
size_t size, loff_t *off)
|
||||
{
|
||||
struct file_priv *priv = file->private_data;
|
||||
size_t in_size = size;
|
||||
ssize_t out_size;
|
||||
|
||||
/* cannot perform a write until the read has cleared
|
||||
either via tpm_read or a user_read_timer timeout.
|
||||
This also prevents splitted buffered writes from blocking here.
|
||||
*/
|
||||
if (atomic_read(&priv->data_pending) != 0)
|
||||
return -EBUSY;
|
||||
|
||||
if (in_size > TPM_BUFSIZE)
|
||||
return -E2BIG;
|
||||
|
||||
mutex_lock(&priv->buffer_mutex);
|
||||
|
||||
if (copy_from_user
|
||||
(priv->data_buffer, (void __user *) buf, in_size)) {
|
||||
mutex_unlock(&priv->buffer_mutex);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
/* atomic tpm command send and result receive */
|
||||
out_size = tpm_transmit(priv->chip, priv->data_buffer,
|
||||
sizeof(priv->data_buffer));
|
||||
if (out_size < 0) {
|
||||
mutex_unlock(&priv->buffer_mutex);
|
||||
return out_size;
|
||||
}
|
||||
|
||||
atomic_set(&priv->data_pending, out_size);
|
||||
mutex_unlock(&priv->buffer_mutex);
|
||||
|
||||
/* Set a timeout by which the reader must come claim the result */
|
||||
mod_timer(&priv->user_read_timer, jiffies + (60 * HZ));
|
||||
|
||||
return in_size;
|
||||
}
|
||||
|
||||
/*
|
||||
* Called on file close
|
||||
*/
|
||||
static int tpm_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct file_priv *priv = file->private_data;
|
||||
|
||||
del_singleshot_timer_sync(&priv->user_read_timer);
|
||||
flush_work(&priv->work);
|
||||
file->private_data = NULL;
|
||||
atomic_set(&priv->data_pending, 0);
|
||||
clear_bit(0, &priv->chip->is_open);
|
||||
put_device(priv->chip->dev);
|
||||
kfree(priv);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations tpm_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.llseek = no_llseek,
|
||||
.open = tpm_open,
|
||||
.read = tpm_read,
|
||||
.write = tpm_write,
|
||||
.release = tpm_release,
|
||||
};
|
||||
|
||||
int tpm_dev_add_device(struct tpm_chip *chip)
|
||||
{
|
||||
int rc;
|
||||
|
||||
chip->vendor.miscdev.fops = &tpm_fops;
|
||||
if (chip->dev_num == 0)
|
||||
chip->vendor.miscdev.minor = TPM_MINOR;
|
||||
else
|
||||
chip->vendor.miscdev.minor = MISC_DYNAMIC_MINOR;
|
||||
|
||||
chip->vendor.miscdev.name = chip->devname;
|
||||
chip->vendor.miscdev.parent = chip->dev;
|
||||
|
||||
rc = misc_register(&chip->vendor.miscdev);
|
||||
if (rc) {
|
||||
chip->vendor.miscdev.name = NULL;
|
||||
dev_err(chip->dev,
|
||||
"unable to misc_register %s, minor %d err=%d\n",
|
||||
chip->vendor.miscdev.name,
|
||||
chip->vendor.miscdev.minor, rc);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
void tpm_dev_del_device(struct tpm_chip *chip)
|
||||
{
|
||||
if (chip->vendor.miscdev.name)
|
||||
misc_deregister(&chip->vendor.miscdev);
|
||||
}
|
1145
drivers/char/tpm/tpm-interface.c
Normal file
1145
drivers/char/tpm/tpm-interface.c
Normal file
File diff suppressed because it is too large
Load diff
318
drivers/char/tpm/tpm-sysfs.c
Normal file
318
drivers/char/tpm/tpm-sysfs.c
Normal file
|
@ -0,0 +1,318 @@
|
|||
/*
|
||||
* 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>
|
||||
*
|
||||
* Copyright (C) 2013 Obsidian Research Corp
|
||||
* Jason Gunthorpe <jgunthorpe@obsidianresearch.com>
|
||||
*
|
||||
* sysfs filesystem inspection interface to the 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/device.h>
|
||||
#include "tpm.h"
|
||||
|
||||
/* XXX for now this helper is duplicated in tpm-interface.c */
|
||||
static ssize_t transmit_cmd(struct tpm_chip *chip, struct tpm_cmd_t *cmd,
|
||||
int len, const char *desc)
|
||||
{
|
||||
int err;
|
||||
|
||||
len = tpm_transmit(chip, (u8 *) cmd, len);
|
||||
if (len < 0)
|
||||
return len;
|
||||
else if (len < TPM_HEADER_SIZE)
|
||||
return -EFAULT;
|
||||
|
||||
err = be32_to_cpu(cmd->header.out.return_code);
|
||||
if (err != 0 && desc)
|
||||
dev_err(chip->dev, "A TPM error (%d) occurred %s\n", err, desc);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
#define READ_PUBEK_RESULT_SIZE 314
|
||||
#define TPM_ORD_READPUBEK cpu_to_be32(124)
|
||||
static struct tpm_input_header tpm_readpubek_header = {
|
||||
.tag = TPM_TAG_RQU_COMMAND,
|
||||
.length = cpu_to_be32(30),
|
||||
.ordinal = TPM_ORD_READPUBEK
|
||||
};
|
||||
static ssize_t pubek_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
u8 *data;
|
||||
struct tpm_cmd_t tpm_cmd;
|
||||
ssize_t err;
|
||||
int i, rc;
|
||||
char *str = buf;
|
||||
|
||||
struct tpm_chip *chip = dev_get_drvdata(dev);
|
||||
|
||||
tpm_cmd.header.in = tpm_readpubek_header;
|
||||
err = transmit_cmd(chip, &tpm_cmd, READ_PUBEK_RESULT_SIZE,
|
||||
"attempting to read the PUBEK");
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
ignore header 10 bytes
|
||||
algorithm 32 bits (1 == RSA )
|
||||
encscheme 16 bits
|
||||
sigscheme 16 bits
|
||||
parameters (RSA 12->bytes: keybit, #primes, expbit)
|
||||
keylenbytes 32 bits
|
||||
256 byte modulus
|
||||
ignore checksum 20 bytes
|
||||
*/
|
||||
data = tpm_cmd.params.readpubek_out_buffer;
|
||||
str +=
|
||||
sprintf(str,
|
||||
"Algorithm: %02X %02X %02X %02X\n"
|
||||
"Encscheme: %02X %02X\n"
|
||||
"Sigscheme: %02X %02X\n"
|
||||
"Parameters: %02X %02X %02X %02X "
|
||||
"%02X %02X %02X %02X "
|
||||
"%02X %02X %02X %02X\n"
|
||||
"Modulus length: %d\n"
|
||||
"Modulus:\n",
|
||||
data[0], data[1], data[2], data[3],
|
||||
data[4], data[5],
|
||||
data[6], data[7],
|
||||
data[12], data[13], data[14], data[15],
|
||||
data[16], data[17], data[18], data[19],
|
||||
data[20], data[21], data[22], data[23],
|
||||
be32_to_cpu(*((__be32 *) (data + 24))));
|
||||
|
||||
for (i = 0; i < 256; i++) {
|
||||
str += sprintf(str, "%02X ", data[i + 28]);
|
||||
if ((i + 1) % 16 == 0)
|
||||
str += sprintf(str, "\n");
|
||||
}
|
||||
out:
|
||||
rc = str - buf;
|
||||
return rc;
|
||||
}
|
||||
static DEVICE_ATTR_RO(pubek);
|
||||
|
||||
static ssize_t pcrs_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
cap_t cap;
|
||||
u8 digest[TPM_DIGEST_SIZE];
|
||||
ssize_t rc;
|
||||
int i, j, num_pcrs;
|
||||
char *str = buf;
|
||||
struct tpm_chip *chip = dev_get_drvdata(dev);
|
||||
|
||||
rc = tpm_getcap(dev, TPM_CAP_PROP_PCR, &cap,
|
||||
"attempting to determine the number of PCRS");
|
||||
if (rc)
|
||||
return 0;
|
||||
|
||||
num_pcrs = be32_to_cpu(cap.num_pcrs);
|
||||
for (i = 0; i < num_pcrs; i++) {
|
||||
rc = tpm_pcr_read_dev(chip, i, digest);
|
||||
if (rc)
|
||||
break;
|
||||
str += sprintf(str, "PCR-%02d: ", i);
|
||||
for (j = 0; j < TPM_DIGEST_SIZE; j++)
|
||||
str += sprintf(str, "%02X ", digest[j]);
|
||||
str += sprintf(str, "\n");
|
||||
}
|
||||
return str - buf;
|
||||
}
|
||||
static DEVICE_ATTR_RO(pcrs);
|
||||
|
||||
static ssize_t enabled_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
cap_t cap;
|
||||
ssize_t rc;
|
||||
|
||||
rc = tpm_getcap(dev, TPM_CAP_FLAG_PERM, &cap,
|
||||
"attempting to determine the permanent enabled state");
|
||||
if (rc)
|
||||
return 0;
|
||||
|
||||
rc = sprintf(buf, "%d\n", !cap.perm_flags.disable);
|
||||
return rc;
|
||||
}
|
||||
static DEVICE_ATTR_RO(enabled);
|
||||
|
||||
static ssize_t active_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
cap_t cap;
|
||||
ssize_t rc;
|
||||
|
||||
rc = tpm_getcap(dev, TPM_CAP_FLAG_PERM, &cap,
|
||||
"attempting to determine the permanent active state");
|
||||
if (rc)
|
||||
return 0;
|
||||
|
||||
rc = sprintf(buf, "%d\n", !cap.perm_flags.deactivated);
|
||||
return rc;
|
||||
}
|
||||
static DEVICE_ATTR_RO(active);
|
||||
|
||||
static ssize_t owned_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
cap_t cap;
|
||||
ssize_t rc;
|
||||
|
||||
rc = tpm_getcap(dev, TPM_CAP_PROP_OWNER, &cap,
|
||||
"attempting to determine the owner state");
|
||||
if (rc)
|
||||
return 0;
|
||||
|
||||
rc = sprintf(buf, "%d\n", cap.owned);
|
||||
return rc;
|
||||
}
|
||||
static DEVICE_ATTR_RO(owned);
|
||||
|
||||
static ssize_t temp_deactivated_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
cap_t cap;
|
||||
ssize_t rc;
|
||||
|
||||
rc = tpm_getcap(dev, TPM_CAP_FLAG_VOL, &cap,
|
||||
"attempting to determine the temporary state");
|
||||
if (rc)
|
||||
return 0;
|
||||
|
||||
rc = sprintf(buf, "%d\n", cap.stclear_flags.deactivated);
|
||||
return rc;
|
||||
}
|
||||
static DEVICE_ATTR_RO(temp_deactivated);
|
||||
|
||||
static ssize_t caps_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
cap_t cap;
|
||||
ssize_t rc;
|
||||
char *str = buf;
|
||||
|
||||
rc = tpm_getcap(dev, TPM_CAP_PROP_MANUFACTURER, &cap,
|
||||
"attempting to determine the manufacturer");
|
||||
if (rc)
|
||||
return 0;
|
||||
str += sprintf(str, "Manufacturer: 0x%x\n",
|
||||
be32_to_cpu(cap.manufacturer_id));
|
||||
|
||||
/* Try to get a TPM version 1.2 TPM_CAP_VERSION_INFO */
|
||||
rc = tpm_getcap(dev, CAP_VERSION_1_2, &cap,
|
||||
"attempting to determine the 1.2 version");
|
||||
if (!rc) {
|
||||
str += sprintf(str,
|
||||
"TCG version: %d.%d\nFirmware version: %d.%d\n",
|
||||
cap.tpm_version_1_2.Major,
|
||||
cap.tpm_version_1_2.Minor,
|
||||
cap.tpm_version_1_2.revMajor,
|
||||
cap.tpm_version_1_2.revMinor);
|
||||
} else {
|
||||
/* Otherwise just use TPM_STRUCT_VER */
|
||||
rc = tpm_getcap(dev, CAP_VERSION_1_1, &cap,
|
||||
"attempting to determine the 1.1 version");
|
||||
if (rc)
|
||||
return 0;
|
||||
str += sprintf(str,
|
||||
"TCG version: %d.%d\nFirmware version: %d.%d\n",
|
||||
cap.tpm_version.Major,
|
||||
cap.tpm_version.Minor,
|
||||
cap.tpm_version.revMajor,
|
||||
cap.tpm_version.revMinor);
|
||||
}
|
||||
|
||||
return str - buf;
|
||||
}
|
||||
static DEVICE_ATTR_RO(caps);
|
||||
|
||||
static ssize_t cancel_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct tpm_chip *chip = dev_get_drvdata(dev);
|
||||
if (chip == NULL)
|
||||
return 0;
|
||||
|
||||
chip->ops->cancel(chip);
|
||||
return count;
|
||||
}
|
||||
static DEVICE_ATTR_WO(cancel);
|
||||
|
||||
static ssize_t durations_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct tpm_chip *chip = dev_get_drvdata(dev);
|
||||
|
||||
if (chip->vendor.duration[TPM_LONG] == 0)
|
||||
return 0;
|
||||
|
||||
return sprintf(buf, "%d %d %d [%s]\n",
|
||||
jiffies_to_usecs(chip->vendor.duration[TPM_SHORT]),
|
||||
jiffies_to_usecs(chip->vendor.duration[TPM_MEDIUM]),
|
||||
jiffies_to_usecs(chip->vendor.duration[TPM_LONG]),
|
||||
chip->vendor.duration_adjusted
|
||||
? "adjusted" : "original");
|
||||
}
|
||||
static DEVICE_ATTR_RO(durations);
|
||||
|
||||
static ssize_t timeouts_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct tpm_chip *chip = dev_get_drvdata(dev);
|
||||
|
||||
return sprintf(buf, "%d %d %d %d [%s]\n",
|
||||
jiffies_to_usecs(chip->vendor.timeout_a),
|
||||
jiffies_to_usecs(chip->vendor.timeout_b),
|
||||
jiffies_to_usecs(chip->vendor.timeout_c),
|
||||
jiffies_to_usecs(chip->vendor.timeout_d),
|
||||
chip->vendor.timeout_adjusted
|
||||
? "adjusted" : "original");
|
||||
}
|
||||
static DEVICE_ATTR_RO(timeouts);
|
||||
|
||||
static struct attribute *tpm_dev_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 const struct attribute_group tpm_dev_group = {
|
||||
.attrs = tpm_dev_attrs,
|
||||
};
|
||||
|
||||
int tpm_sysfs_add_device(struct tpm_chip *chip)
|
||||
{
|
||||
int err;
|
||||
err = sysfs_create_group(&chip->dev->kobj,
|
||||
&tpm_dev_group);
|
||||
|
||||
if (err)
|
||||
dev_err(chip->dev,
|
||||
"failed to create sysfs attributes, %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
void tpm_sysfs_del_device(struct tpm_chip *chip)
|
||||
{
|
||||
sysfs_remove_group(&chip->dev->kobj, &tpm_dev_group);
|
||||
}
|
352
drivers/char/tpm/tpm.h
Normal file
352
drivers/char/tpm/tpm.h
Normal file
|
@ -0,0 +1,352 @@
|
|||
/*
|
||||
* 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,
|
||||
};
|
||||
|
||||
/* Indexes the duration array */
|
||||
enum tpm_duration {
|
||||
TPM_SHORT = 0,
|
||||
TPM_MEDIUM = 1,
|
||||
TPM_LONG = 2,
|
||||
TPM_UNDEFINED,
|
||||
};
|
||||
|
||||
#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
|
||||
struct tpm_chip;
|
||||
|
||||
struct tpm_vendor_specific {
|
||||
void __iomem *iobase; /* ioremapped address */
|
||||
unsigned long base; /* TPM base address */
|
||||
|
||||
int irq;
|
||||
int probed_irq;
|
||||
|
||||
int region_size;
|
||||
int have_region;
|
||||
|
||||
struct miscdevice miscdev;
|
||||
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 */
|
||||
const struct tpm_class_ops *ops;
|
||||
|
||||
int dev_num; /* /dev/tpm# */
|
||||
char devname[7];
|
||||
unsigned long is_open; /* only one allowed */
|
||||
int time_expired;
|
||||
|
||||
struct mutex tpm_mutex; /* tpm is processing */
|
||||
|
||||
struct tpm_vendor_specific vendor;
|
||||
|
||||
struct dentry **bios_dir;
|
||||
|
||||
struct list_head list;
|
||||
void (*release) (struct device *);
|
||||
};
|
||||
|
||||
#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;
|
||||
|
||||
#define TPM_TAG_RQU_COMMAND cpu_to_be16(193)
|
||||
|
||||
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;
|
||||
|
||||
enum tpm_capabilities {
|
||||
TPM_CAP_FLAG = cpu_to_be32(4),
|
||||
TPM_CAP_PROP = cpu_to_be32(5),
|
||||
CAP_VERSION_1_1 = cpu_to_be32(0x06),
|
||||
CAP_VERSION_1_2 = cpu_to_be32(0x1A)
|
||||
};
|
||||
|
||||
enum tpm_sub_capabilities {
|
||||
TPM_CAP_PROP_PCR = cpu_to_be32(0x101),
|
||||
TPM_CAP_PROP_MANUFACTURER = cpu_to_be32(0x103),
|
||||
TPM_CAP_FLAG_PERM = cpu_to_be32(0x108),
|
||||
TPM_CAP_FLAG_VOL = cpu_to_be32(0x109),
|
||||
TPM_CAP_PROP_OWNER = cpu_to_be32(0x111),
|
||||
TPM_CAP_PROP_TIS_TIMEOUT = cpu_to_be32(0x115),
|
||||
TPM_CAP_PROP_TIS_DURATION = cpu_to_be32(0x120),
|
||||
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
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 *);
|
||||
|
||||
ssize_t tpm_transmit(struct tpm_chip *chip, const char *buf,
|
||||
size_t bufsiz);
|
||||
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_class_ops *ops);
|
||||
extern void tpm_dev_vendor_release(struct tpm_chip *);
|
||||
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);
|
||||
|
||||
int tpm_dev_add_device(struct tpm_chip *chip);
|
||||
void tpm_dev_del_device(struct tpm_chip *chip);
|
||||
int tpm_sysfs_add_device(struct tpm_chip *chip);
|
||||
void tpm_sysfs_del_device(struct tpm_chip *chip);
|
||||
|
||||
int tpm_pcr_read_dev(struct tpm_chip *chip, int pcr_idx, u8 *res_buf);
|
||||
|
||||
#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
109
drivers/char/tpm/tpm_acpi.c
Normal 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 <linux/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_iomem(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_iomem(virt, len);
|
||||
return 0;
|
||||
}
|
223
drivers/char/tpm/tpm_atmel.c
Normal file
223
drivers/char/tpm/tpm_atmel.c
Normal file
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
* 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 tpm_class_ops 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,
|
||||
};
|
||||
|
||||
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, ®ion_size)) == NULL) {
|
||||
rc = -ENODEV;
|
||||
goto err_unreg_drv;
|
||||
}
|
||||
|
||||
have_region =
|
||||
(atmel_request_region
|
||||
(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");
|
131
drivers/char/tpm/tpm_atmel.h
Normal file
131
drivers/char/tpm/tpm_atmel.h
Normal 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", ®len);
|
||||
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
|
414
drivers/char/tpm/tpm_eventlog.c
Normal file
414
drivers/char/tpm/tpm_eventlog.c
Normal file
|
@ -0,0 +1,414 @@
|
|||
/*
|
||||
* 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;
|
||||
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 */
|
||||
seq_printf(m, "%20phN", event->pcr_value);
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
void tpm_bios_log_teardown(struct dentry **lst)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 3; i++)
|
||||
securityfs_remove(lst[i]);
|
||||
}
|
86
drivers/char/tpm/tpm_eventlog.h
Normal file
86
drivers/char/tpm/tpm_eventlog.h
Normal 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
|
248
drivers/char/tpm/tpm_i2c_atmel.c
Normal file
248
drivers/char/tpm/tpm_i2c_atmel.c
Normal file
|
@ -0,0 +1,248 @@
|
|||
/*
|
||||
* ATMEL I2C TPM AT97SC3204T
|
||||
*
|
||||
* Copyright (C) 2012 V Lab Technologies
|
||||
* Teddy Reed <teddy@prosauce.org>
|
||||
* Copyright (C) 2013, Obsidian Research Corp.
|
||||
* Jason Gunthorpe <jgunthorpe@obsidianresearch.com>
|
||||
* Device driver for ATMEL I2C TPMs.
|
||||
*
|
||||
* Teddy Reed determined the basic I2C command flow, unlike other I2C TPM
|
||||
* devices the raw TCG formatted TPM command data is written via I2C and then
|
||||
* raw TCG formatted TPM command data is returned via I2C.
|
||||
*
|
||||
* TGC status/locality/etc functions seen in the LPC implementation do not
|
||||
* seem to be present.
|
||||
*
|
||||
* 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, see http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/i2c.h>
|
||||
#include "tpm.h"
|
||||
|
||||
#define I2C_DRIVER_NAME "tpm_i2c_atmel"
|
||||
|
||||
#define TPM_I2C_SHORT_TIMEOUT 750 /* ms */
|
||||
#define TPM_I2C_LONG_TIMEOUT 2000 /* 2 sec */
|
||||
|
||||
#define ATMEL_STS_OK 1
|
||||
|
||||
struct priv_data {
|
||||
size_t len;
|
||||
/* This is the amount we read on the first try. 25 was chosen to fit a
|
||||
* fair number of read responses in the buffer so a 2nd retry can be
|
||||
* avoided in small message cases. */
|
||||
u8 buffer[sizeof(struct tpm_output_header) + 25];
|
||||
};
|
||||
|
||||
static int i2c_atmel_send(struct tpm_chip *chip, u8 *buf, size_t len)
|
||||
{
|
||||
struct priv_data *priv = chip->vendor.priv;
|
||||
struct i2c_client *client = to_i2c_client(chip->dev);
|
||||
s32 status;
|
||||
|
||||
priv->len = 0;
|
||||
|
||||
if (len <= 2)
|
||||
return -EIO;
|
||||
|
||||
status = i2c_master_send(client, buf, len);
|
||||
|
||||
dev_dbg(chip->dev,
|
||||
"%s(buf=%*ph len=%0zx) -> sts=%d\n", __func__,
|
||||
(int)min_t(size_t, 64, len), buf, len, status);
|
||||
return status;
|
||||
}
|
||||
|
||||
static int i2c_atmel_recv(struct tpm_chip *chip, u8 *buf, size_t count)
|
||||
{
|
||||
struct priv_data *priv = chip->vendor.priv;
|
||||
struct i2c_client *client = to_i2c_client(chip->dev);
|
||||
struct tpm_output_header *hdr =
|
||||
(struct tpm_output_header *)priv->buffer;
|
||||
u32 expected_len;
|
||||
int rc;
|
||||
|
||||
if (priv->len == 0)
|
||||
return -EIO;
|
||||
|
||||
/* Get the message size from the message header, if we didn't get the
|
||||
* whole message in read_status then we need to re-read the
|
||||
* message. */
|
||||
expected_len = be32_to_cpu(hdr->length);
|
||||
if (expected_len > count)
|
||||
return -ENOMEM;
|
||||
|
||||
if (priv->len >= expected_len) {
|
||||
dev_dbg(chip->dev,
|
||||
"%s early(buf=%*ph count=%0zx) -> ret=%d\n", __func__,
|
||||
(int)min_t(size_t, 64, expected_len), buf, count,
|
||||
expected_len);
|
||||
memcpy(buf, priv->buffer, expected_len);
|
||||
return expected_len;
|
||||
}
|
||||
|
||||
rc = i2c_master_recv(client, buf, expected_len);
|
||||
dev_dbg(chip->dev,
|
||||
"%s reread(buf=%*ph count=%0zx) -> ret=%d\n", __func__,
|
||||
(int)min_t(size_t, 64, expected_len), buf, count,
|
||||
expected_len);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void i2c_atmel_cancel(struct tpm_chip *chip)
|
||||
{
|
||||
dev_err(chip->dev, "TPM operation cancellation was requested, but is not supported");
|
||||
}
|
||||
|
||||
static u8 i2c_atmel_read_status(struct tpm_chip *chip)
|
||||
{
|
||||
struct priv_data *priv = chip->vendor.priv;
|
||||
struct i2c_client *client = to_i2c_client(chip->dev);
|
||||
int rc;
|
||||
|
||||
/* The TPM fails the I2C read until it is ready, so we do the entire
|
||||
* transfer here and buffer it locally. This way the common code can
|
||||
* properly handle the timeouts. */
|
||||
priv->len = 0;
|
||||
memset(priv->buffer, 0, sizeof(priv->buffer));
|
||||
|
||||
|
||||
/* Once the TPM has completed the command the command remains readable
|
||||
* until another command is issued. */
|
||||
rc = i2c_master_recv(client, priv->buffer, sizeof(priv->buffer));
|
||||
dev_dbg(chip->dev,
|
||||
"%s: sts=%d", __func__, rc);
|
||||
if (rc <= 0)
|
||||
return 0;
|
||||
|
||||
priv->len = rc;
|
||||
|
||||
return ATMEL_STS_OK;
|
||||
}
|
||||
|
||||
static bool i2c_atmel_req_canceled(struct tpm_chip *chip, u8 status)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
static const struct tpm_class_ops i2c_atmel = {
|
||||
.status = i2c_atmel_read_status,
|
||||
.recv = i2c_atmel_recv,
|
||||
.send = i2c_atmel_send,
|
||||
.cancel = i2c_atmel_cancel,
|
||||
.req_complete_mask = ATMEL_STS_OK,
|
||||
.req_complete_val = ATMEL_STS_OK,
|
||||
.req_canceled = i2c_atmel_req_canceled,
|
||||
};
|
||||
|
||||
static int i2c_atmel_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
int rc;
|
||||
struct tpm_chip *chip;
|
||||
struct device *dev = &client->dev;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
|
||||
return -ENODEV;
|
||||
|
||||
chip = tpm_register_hardware(dev, &i2c_atmel);
|
||||
if (!chip) {
|
||||
dev_err(dev, "%s() error in tpm_register_hardware\n", __func__);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
chip->vendor.priv = devm_kzalloc(dev, sizeof(struct priv_data),
|
||||
GFP_KERNEL);
|
||||
if (!chip->vendor.priv) {
|
||||
rc = -ENOMEM;
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
/* Default timeouts */
|
||||
chip->vendor.timeout_a = msecs_to_jiffies(TPM_I2C_SHORT_TIMEOUT);
|
||||
chip->vendor.timeout_b = msecs_to_jiffies(TPM_I2C_LONG_TIMEOUT);
|
||||
chip->vendor.timeout_c = msecs_to_jiffies(TPM_I2C_SHORT_TIMEOUT);
|
||||
chip->vendor.timeout_d = msecs_to_jiffies(TPM_I2C_SHORT_TIMEOUT);
|
||||
chip->vendor.irq = 0;
|
||||
|
||||
/* There is no known way to probe for this device, and all version
|
||||
* information seems to be read via TPM commands. Thus we rely on the
|
||||
* TPM startup process in the common code to detect the device. */
|
||||
if (tpm_get_timeouts(chip)) {
|
||||
rc = -ENODEV;
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
if (tpm_do_selftest(chip)) {
|
||||
rc = -ENODEV;
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
out_err:
|
||||
tpm_dev_vendor_release(chip);
|
||||
tpm_remove_hardware(chip->dev);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int i2c_atmel_remove(struct i2c_client *client)
|
||||
{
|
||||
struct device *dev = &(client->dev);
|
||||
struct tpm_chip *chip = dev_get_drvdata(dev);
|
||||
|
||||
if (chip)
|
||||
tpm_dev_vendor_release(chip);
|
||||
tpm_remove_hardware(dev);
|
||||
kfree(chip);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id i2c_atmel_id[] = {
|
||||
{I2C_DRIVER_NAME, 0},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, i2c_atmel_id);
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id i2c_atmel_of_match[] = {
|
||||
{.compatible = "atmel,at97sc3204t"},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, i2c_atmel_of_match);
|
||||
#endif
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(i2c_atmel_pm_ops, tpm_pm_suspend, tpm_pm_resume);
|
||||
|
||||
static struct i2c_driver i2c_atmel_driver = {
|
||||
.id_table = i2c_atmel_id,
|
||||
.probe = i2c_atmel_probe,
|
||||
.remove = i2c_atmel_remove,
|
||||
.driver = {
|
||||
.name = I2C_DRIVER_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &i2c_atmel_pm_ops,
|
||||
.of_match_table = of_match_ptr(i2c_atmel_of_match),
|
||||
},
|
||||
};
|
||||
|
||||
module_i2c_driver(i2c_atmel_driver);
|
||||
|
||||
MODULE_AUTHOR("Jason Gunthorpe <jgunthorpe@obsidianresearch.com>");
|
||||
MODULE_DESCRIPTION("Atmel TPM I2C Driver");
|
||||
MODULE_LICENSE("GPL");
|
747
drivers/char/tpm/tpm_i2c_infineon.c
Normal file
747
drivers/char/tpm/tpm_i2c_infineon.c
Normal file
|
@ -0,0 +1,747 @@
|
|||
/*
|
||||
* 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/i2c.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/wait.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;
|
||||
};
|
||||
|
||||
static struct tpm_inf_dev tpm_dev;
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/* 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 (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 tpm_class_ops 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,
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
tpm_get_timeouts(chip);
|
||||
tpm_do_selftest(chip);
|
||||
|
||||
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;
|
||||
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 SIMPLE_DEV_PM_OPS(tpm_tis_i2c_ops, tpm_pm_suspend, tpm_pm_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;
|
||||
}
|
||||
|
||||
tpm_dev.client = client;
|
||||
rc = tpm_tis_i2c_init(&client->dev);
|
||||
if (rc != 0) {
|
||||
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);
|
||||
|
||||
/* 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;
|
||||
|
||||
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");
|
674
drivers/char/tpm/tpm_i2c_nuvoton.c
Normal file
674
drivers/char/tpm/tpm_i2c_nuvoton.c
Normal file
|
@ -0,0 +1,674 @@
|
|||
/******************************************************************************
|
||||
* Nuvoton TPM I2C Device Driver Interface for WPCT301/NPCT501,
|
||||
* based on the TCG TPM Interface Spec version 1.2.
|
||||
* Specifications at www.trustedcomputinggroup.org
|
||||
*
|
||||
* Copyright (C) 2011, Nuvoton Technology Corporation.
|
||||
* Dan Morav <dan.morav@nuvoton.com>
|
||||
* Copyright (C) 2013, Obsidian Research Corp.
|
||||
* Jason Gunthorpe <jgunthorpe@obsidianresearch.com>
|
||||
*
|
||||
* 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, see http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Nuvoton contact information: APC.Support@nuvoton.com
|
||||
*****************************************************************************/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/i2c.h>
|
||||
#include "tpm.h"
|
||||
|
||||
/* I2C interface offsets */
|
||||
#define TPM_STS 0x00
|
||||
#define TPM_BURST_COUNT 0x01
|
||||
#define TPM_DATA_FIFO_W 0x20
|
||||
#define TPM_DATA_FIFO_R 0x40
|
||||
#define TPM_VID_DID_RID 0x60
|
||||
/* TPM command header size */
|
||||
#define TPM_HEADER_SIZE 10
|
||||
#define TPM_RETRY 5
|
||||
/*
|
||||
* I2C bus device maximum buffer size w/o counting I2C address or command
|
||||
* i.e. max size required for I2C write is 34 = addr, command, 32 bytes data
|
||||
*/
|
||||
#define TPM_I2C_MAX_BUF_SIZE 32
|
||||
#define TPM_I2C_RETRY_COUNT 32
|
||||
#define TPM_I2C_BUS_DELAY 1 /* msec */
|
||||
#define TPM_I2C_RETRY_DELAY_SHORT 2 /* msec */
|
||||
#define TPM_I2C_RETRY_DELAY_LONG 10 /* msec */
|
||||
|
||||
#define I2C_DRIVER_NAME "tpm_i2c_nuvoton"
|
||||
|
||||
struct priv_data {
|
||||
unsigned int intrs;
|
||||
};
|
||||
|
||||
static s32 i2c_nuvoton_read_buf(struct i2c_client *client, u8 offset, u8 size,
|
||||
u8 *data)
|
||||
{
|
||||
s32 status;
|
||||
|
||||
status = i2c_smbus_read_i2c_block_data(client, offset, size, data);
|
||||
dev_dbg(&client->dev,
|
||||
"%s(offset=%u size=%u data=%*ph) -> sts=%d\n", __func__,
|
||||
offset, size, (int)size, data, status);
|
||||
return status;
|
||||
}
|
||||
|
||||
static s32 i2c_nuvoton_write_buf(struct i2c_client *client, u8 offset, u8 size,
|
||||
u8 *data)
|
||||
{
|
||||
s32 status;
|
||||
|
||||
status = i2c_smbus_write_i2c_block_data(client, offset, size, data);
|
||||
dev_dbg(&client->dev,
|
||||
"%s(offset=%u size=%u data=%*ph) -> sts=%d\n", __func__,
|
||||
offset, size, (int)size, data, status);
|
||||
return status;
|
||||
}
|
||||
|
||||
#define TPM_STS_VALID 0x80
|
||||
#define TPM_STS_COMMAND_READY 0x40
|
||||
#define TPM_STS_GO 0x20
|
||||
#define TPM_STS_DATA_AVAIL 0x10
|
||||
#define TPM_STS_EXPECT 0x08
|
||||
#define TPM_STS_RESPONSE_RETRY 0x02
|
||||
#define TPM_STS_ERR_VAL 0x07 /* bit2...bit0 reads always 0 */
|
||||
|
||||
#define TPM_I2C_SHORT_TIMEOUT 750 /* ms */
|
||||
#define TPM_I2C_LONG_TIMEOUT 2000 /* 2 sec */
|
||||
|
||||
/* read TPM_STS register */
|
||||
static u8 i2c_nuvoton_read_status(struct tpm_chip *chip)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(chip->dev);
|
||||
s32 status;
|
||||
u8 data;
|
||||
|
||||
status = i2c_nuvoton_read_buf(client, TPM_STS, 1, &data);
|
||||
if (status <= 0) {
|
||||
dev_err(chip->dev, "%s() error return %d\n", __func__,
|
||||
status);
|
||||
data = TPM_STS_ERR_VAL;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/* write byte to TPM_STS register */
|
||||
static s32 i2c_nuvoton_write_status(struct i2c_client *client, u8 data)
|
||||
{
|
||||
s32 status;
|
||||
int i;
|
||||
|
||||
/* this causes the current command to be aborted */
|
||||
for (i = 0, status = -1; i < TPM_I2C_RETRY_COUNT && status < 0; i++) {
|
||||
status = i2c_nuvoton_write_buf(client, TPM_STS, 1, &data);
|
||||
msleep(TPM_I2C_BUS_DELAY);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
/* write commandReady to TPM_STS register */
|
||||
static void i2c_nuvoton_ready(struct tpm_chip *chip)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(chip->dev);
|
||||
s32 status;
|
||||
|
||||
/* this causes the current command to be aborted */
|
||||
status = i2c_nuvoton_write_status(client, TPM_STS_COMMAND_READY);
|
||||
if (status < 0)
|
||||
dev_err(chip->dev,
|
||||
"%s() fail to write TPM_STS.commandReady\n", __func__);
|
||||
}
|
||||
|
||||
/* read burstCount field from TPM_STS register
|
||||
* return -1 on fail to read */
|
||||
static int i2c_nuvoton_get_burstcount(struct i2c_client *client,
|
||||
struct tpm_chip *chip)
|
||||
{
|
||||
unsigned long stop = jiffies + chip->vendor.timeout_d;
|
||||
s32 status;
|
||||
int burst_count = -1;
|
||||
u8 data;
|
||||
|
||||
/* wait for burstcount to be non-zero */
|
||||
do {
|
||||
/* in I2C burstCount is 1 byte */
|
||||
status = i2c_nuvoton_read_buf(client, TPM_BURST_COUNT, 1,
|
||||
&data);
|
||||
if (status > 0 && data > 0) {
|
||||
burst_count = min_t(u8, TPM_I2C_MAX_BUF_SIZE, data);
|
||||
break;
|
||||
}
|
||||
msleep(TPM_I2C_BUS_DELAY);
|
||||
} while (time_before(jiffies, stop));
|
||||
|
||||
return burst_count;
|
||||
}
|
||||
|
||||
/*
|
||||
* WPCT301/NPCT501 SINT# supports only dataAvail
|
||||
* any call to this function which is not waiting for dataAvail will
|
||||
* set queue to NULL to avoid waiting for interrupt
|
||||
*/
|
||||
static bool i2c_nuvoton_check_status(struct tpm_chip *chip, u8 mask, u8 value)
|
||||
{
|
||||
u8 status = i2c_nuvoton_read_status(chip);
|
||||
return (status != TPM_STS_ERR_VAL) && ((status & mask) == value);
|
||||
}
|
||||
|
||||
static int i2c_nuvoton_wait_for_stat(struct tpm_chip *chip, u8 mask, u8 value,
|
||||
u32 timeout, wait_queue_head_t *queue)
|
||||
{
|
||||
if (chip->vendor.irq && queue) {
|
||||
s32 rc;
|
||||
struct priv_data *priv = chip->vendor.priv;
|
||||
unsigned int cur_intrs = priv->intrs;
|
||||
|
||||
enable_irq(chip->vendor.irq);
|
||||
rc = wait_event_interruptible_timeout(*queue,
|
||||
cur_intrs != priv->intrs,
|
||||
timeout);
|
||||
if (rc > 0)
|
||||
return 0;
|
||||
/* At this point we know that the SINT pin is asserted, so we
|
||||
* do not need to do i2c_nuvoton_check_status */
|
||||
} else {
|
||||
unsigned long ten_msec, stop;
|
||||
bool status_valid;
|
||||
|
||||
/* check current status */
|
||||
status_valid = i2c_nuvoton_check_status(chip, mask, value);
|
||||
if (status_valid)
|
||||
return 0;
|
||||
|
||||
/* use polling to wait for the event */
|
||||
ten_msec = jiffies + msecs_to_jiffies(TPM_I2C_RETRY_DELAY_LONG);
|
||||
stop = jiffies + timeout;
|
||||
do {
|
||||
if (time_before(jiffies, ten_msec))
|
||||
msleep(TPM_I2C_RETRY_DELAY_SHORT);
|
||||
else
|
||||
msleep(TPM_I2C_RETRY_DELAY_LONG);
|
||||
status_valid = i2c_nuvoton_check_status(chip, mask,
|
||||
value);
|
||||
if (status_valid)
|
||||
return 0;
|
||||
} while (time_before(jiffies, stop));
|
||||
}
|
||||
dev_err(chip->dev, "%s(%02x, %02x) -> timeout\n", __func__, mask,
|
||||
value);
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
/* wait for dataAvail field to be set in the TPM_STS register */
|
||||
static int i2c_nuvoton_wait_for_data_avail(struct tpm_chip *chip, u32 timeout,
|
||||
wait_queue_head_t *queue)
|
||||
{
|
||||
return i2c_nuvoton_wait_for_stat(chip,
|
||||
TPM_STS_DATA_AVAIL | TPM_STS_VALID,
|
||||
TPM_STS_DATA_AVAIL | TPM_STS_VALID,
|
||||
timeout, queue);
|
||||
}
|
||||
|
||||
/* Read @count bytes into @buf from TPM_RD_FIFO register */
|
||||
static int i2c_nuvoton_recv_data(struct i2c_client *client,
|
||||
struct tpm_chip *chip, u8 *buf, size_t count)
|
||||
{
|
||||
s32 rc;
|
||||
int burst_count, bytes2read, size = 0;
|
||||
|
||||
while (size < count &&
|
||||
i2c_nuvoton_wait_for_data_avail(chip,
|
||||
chip->vendor.timeout_c,
|
||||
&chip->vendor.read_queue) == 0) {
|
||||
burst_count = i2c_nuvoton_get_burstcount(client, chip);
|
||||
if (burst_count < 0) {
|
||||
dev_err(chip->dev,
|
||||
"%s() fail to read burstCount=%d\n", __func__,
|
||||
burst_count);
|
||||
return -EIO;
|
||||
}
|
||||
bytes2read = min_t(size_t, burst_count, count - size);
|
||||
rc = i2c_nuvoton_read_buf(client, TPM_DATA_FIFO_R,
|
||||
bytes2read, &buf[size]);
|
||||
if (rc < 0) {
|
||||
dev_err(chip->dev,
|
||||
"%s() fail on i2c_nuvoton_read_buf()=%d\n",
|
||||
__func__, rc);
|
||||
return -EIO;
|
||||
}
|
||||
dev_dbg(chip->dev, "%s(%d):", __func__, bytes2read);
|
||||
size += bytes2read;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
/* Read TPM command results */
|
||||
static int i2c_nuvoton_recv(struct tpm_chip *chip, u8 *buf, size_t count)
|
||||
{
|
||||
struct device *dev = chip->dev;
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
s32 rc;
|
||||
int expected, status, burst_count, retries, size = 0;
|
||||
|
||||
if (count < TPM_HEADER_SIZE) {
|
||||
i2c_nuvoton_ready(chip); /* return to idle */
|
||||
dev_err(dev, "%s() count < header size\n", __func__);
|
||||
return -EIO;
|
||||
}
|
||||
for (retries = 0; retries < TPM_RETRY; retries++) {
|
||||
if (retries > 0) {
|
||||
/* if this is not the first trial, set responseRetry */
|
||||
i2c_nuvoton_write_status(client,
|
||||
TPM_STS_RESPONSE_RETRY);
|
||||
}
|
||||
/*
|
||||
* read first available (> 10 bytes), including:
|
||||
* tag, paramsize, and result
|
||||
*/
|
||||
status = i2c_nuvoton_wait_for_data_avail(
|
||||
chip, chip->vendor.timeout_c, &chip->vendor.read_queue);
|
||||
if (status != 0) {
|
||||
dev_err(dev, "%s() timeout on dataAvail\n", __func__);
|
||||
size = -ETIMEDOUT;
|
||||
continue;
|
||||
}
|
||||
burst_count = i2c_nuvoton_get_burstcount(client, chip);
|
||||
if (burst_count < 0) {
|
||||
dev_err(dev, "%s() fail to get burstCount\n", __func__);
|
||||
size = -EIO;
|
||||
continue;
|
||||
}
|
||||
size = i2c_nuvoton_recv_data(client, chip, buf,
|
||||
burst_count);
|
||||
if (size < TPM_HEADER_SIZE) {
|
||||
dev_err(dev, "%s() fail to read header\n", __func__);
|
||||
size = -EIO;
|
||||
continue;
|
||||
}
|
||||
/*
|
||||
* convert number of expected bytes field from big endian 32 bit
|
||||
* to machine native
|
||||
*/
|
||||
expected = be32_to_cpu(*(__be32 *) (buf + 2));
|
||||
if (expected > count) {
|
||||
dev_err(dev, "%s() expected > count\n", __func__);
|
||||
size = -EIO;
|
||||
continue;
|
||||
}
|
||||
rc = i2c_nuvoton_recv_data(client, chip, &buf[size],
|
||||
expected - size);
|
||||
size += rc;
|
||||
if (rc < 0 || size < expected) {
|
||||
dev_err(dev, "%s() fail to read remainder of result\n",
|
||||
__func__);
|
||||
size = -EIO;
|
||||
continue;
|
||||
}
|
||||
if (i2c_nuvoton_wait_for_stat(
|
||||
chip, TPM_STS_VALID | TPM_STS_DATA_AVAIL,
|
||||
TPM_STS_VALID, chip->vendor.timeout_c,
|
||||
NULL)) {
|
||||
dev_err(dev, "%s() error left over data\n", __func__);
|
||||
size = -ETIMEDOUT;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
i2c_nuvoton_ready(chip);
|
||||
dev_dbg(chip->dev, "%s() -> %d\n", __func__, size);
|
||||
return size;
|
||||
}
|
||||
|
||||
/*
|
||||
* Send TPM command.
|
||||
*
|
||||
* 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 i2c_nuvoton_send(struct tpm_chip *chip, u8 *buf, size_t len)
|
||||
{
|
||||
struct device *dev = chip->dev;
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
u32 ordinal;
|
||||
size_t count = 0;
|
||||
int burst_count, bytes2write, retries, rc = -EIO;
|
||||
|
||||
for (retries = 0; retries < TPM_RETRY; retries++) {
|
||||
i2c_nuvoton_ready(chip);
|
||||
if (i2c_nuvoton_wait_for_stat(chip, TPM_STS_COMMAND_READY,
|
||||
TPM_STS_COMMAND_READY,
|
||||
chip->vendor.timeout_b, NULL)) {
|
||||
dev_err(dev, "%s() timeout on commandReady\n",
|
||||
__func__);
|
||||
rc = -EIO;
|
||||
continue;
|
||||
}
|
||||
rc = 0;
|
||||
while (count < len - 1) {
|
||||
burst_count = i2c_nuvoton_get_burstcount(client,
|
||||
chip);
|
||||
if (burst_count < 0) {
|
||||
dev_err(dev, "%s() fail get burstCount\n",
|
||||
__func__);
|
||||
rc = -EIO;
|
||||
break;
|
||||
}
|
||||
bytes2write = min_t(size_t, burst_count,
|
||||
len - 1 - count);
|
||||
rc = i2c_nuvoton_write_buf(client, TPM_DATA_FIFO_W,
|
||||
bytes2write, &buf[count]);
|
||||
if (rc < 0) {
|
||||
dev_err(dev, "%s() fail i2cWriteBuf\n",
|
||||
__func__);
|
||||
break;
|
||||
}
|
||||
dev_dbg(dev, "%s(%d):", __func__, bytes2write);
|
||||
count += bytes2write;
|
||||
rc = i2c_nuvoton_wait_for_stat(chip,
|
||||
TPM_STS_VALID |
|
||||
TPM_STS_EXPECT,
|
||||
TPM_STS_VALID |
|
||||
TPM_STS_EXPECT,
|
||||
chip->vendor.timeout_c,
|
||||
NULL);
|
||||
if (rc < 0) {
|
||||
dev_err(dev, "%s() timeout on Expect\n",
|
||||
__func__);
|
||||
rc = -ETIMEDOUT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (rc < 0)
|
||||
continue;
|
||||
|
||||
/* write last byte */
|
||||
rc = i2c_nuvoton_write_buf(client, TPM_DATA_FIFO_W, 1,
|
||||
&buf[count]);
|
||||
if (rc < 0) {
|
||||
dev_err(dev, "%s() fail to write last byte\n",
|
||||
__func__);
|
||||
rc = -EIO;
|
||||
continue;
|
||||
}
|
||||
dev_dbg(dev, "%s(last): %02x", __func__, buf[count]);
|
||||
rc = i2c_nuvoton_wait_for_stat(chip,
|
||||
TPM_STS_VALID | TPM_STS_EXPECT,
|
||||
TPM_STS_VALID,
|
||||
chip->vendor.timeout_c, NULL);
|
||||
if (rc) {
|
||||
dev_err(dev, "%s() timeout on Expect to clear\n",
|
||||
__func__);
|
||||
rc = -ETIMEDOUT;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (rc < 0) {
|
||||
/* retries == TPM_RETRY */
|
||||
i2c_nuvoton_ready(chip);
|
||||
return rc;
|
||||
}
|
||||
/* execute the TPM command */
|
||||
rc = i2c_nuvoton_write_status(client, TPM_STS_GO);
|
||||
if (rc < 0) {
|
||||
dev_err(dev, "%s() fail to write Go\n", __func__);
|
||||
i2c_nuvoton_ready(chip);
|
||||
return rc;
|
||||
}
|
||||
ordinal = be32_to_cpu(*((__be32 *) (buf + 6)));
|
||||
rc = i2c_nuvoton_wait_for_data_avail(chip,
|
||||
tpm_calc_ordinal_duration(chip,
|
||||
ordinal),
|
||||
&chip->vendor.read_queue);
|
||||
if (rc) {
|
||||
dev_err(dev, "%s() timeout command duration\n", __func__);
|
||||
i2c_nuvoton_ready(chip);
|
||||
return rc;
|
||||
}
|
||||
|
||||
dev_dbg(dev, "%s() -> %zd\n", __func__, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
static bool i2c_nuvoton_req_canceled(struct tpm_chip *chip, u8 status)
|
||||
{
|
||||
return (status == TPM_STS_COMMAND_READY);
|
||||
}
|
||||
|
||||
static const struct tpm_class_ops tpm_i2c = {
|
||||
.status = i2c_nuvoton_read_status,
|
||||
.recv = i2c_nuvoton_recv,
|
||||
.send = i2c_nuvoton_send,
|
||||
.cancel = i2c_nuvoton_ready,
|
||||
.req_complete_mask = TPM_STS_DATA_AVAIL | TPM_STS_VALID,
|
||||
.req_complete_val = TPM_STS_DATA_AVAIL | TPM_STS_VALID,
|
||||
.req_canceled = i2c_nuvoton_req_canceled,
|
||||
};
|
||||
|
||||
/* The only purpose for the handler is to signal to any waiting threads that
|
||||
* the interrupt is currently being asserted. The driver does not do any
|
||||
* processing triggered by interrupts, and the chip provides no way to mask at
|
||||
* the source (plus that would be slow over I2C). Run the IRQ as a one-shot,
|
||||
* this means it cannot be shared. */
|
||||
static irqreturn_t i2c_nuvoton_int_handler(int dummy, void *dev_id)
|
||||
{
|
||||
struct tpm_chip *chip = dev_id;
|
||||
struct priv_data *priv = chip->vendor.priv;
|
||||
|
||||
priv->intrs++;
|
||||
wake_up(&chip->vendor.read_queue);
|
||||
disable_irq_nosync(chip->vendor.irq);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int get_vid(struct i2c_client *client, u32 *res)
|
||||
{
|
||||
static const u8 vid_did_rid_value[] = { 0x50, 0x10, 0xfe };
|
||||
u32 temp;
|
||||
s32 rc;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
|
||||
return -ENODEV;
|
||||
rc = i2c_nuvoton_read_buf(client, TPM_VID_DID_RID, 4, (u8 *)&temp);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
/* check WPCT301 values - ignore RID */
|
||||
if (memcmp(&temp, vid_did_rid_value, sizeof(vid_did_rid_value))) {
|
||||
/*
|
||||
* f/w rev 2.81 has an issue where the VID_DID_RID is not
|
||||
* reporting the right value. so give it another chance at
|
||||
* offset 0x20 (FIFO_W).
|
||||
*/
|
||||
rc = i2c_nuvoton_read_buf(client, TPM_DATA_FIFO_W, 4,
|
||||
(u8 *) (&temp));
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
/* check WPCT301 values - ignore RID */
|
||||
if (memcmp(&temp, vid_did_rid_value,
|
||||
sizeof(vid_did_rid_value)))
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
*res = temp;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int i2c_nuvoton_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
int rc;
|
||||
struct tpm_chip *chip;
|
||||
struct device *dev = &client->dev;
|
||||
u32 vid = 0;
|
||||
|
||||
rc = get_vid(client, &vid);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
dev_info(dev, "VID: %04X DID: %02X RID: %02X\n", (u16) vid,
|
||||
(u8) (vid >> 16), (u8) (vid >> 24));
|
||||
|
||||
chip = tpm_register_hardware(dev, &tpm_i2c);
|
||||
if (!chip) {
|
||||
dev_err(dev, "%s() error in tpm_register_hardware\n", __func__);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
chip->vendor.priv = devm_kzalloc(dev, sizeof(struct priv_data),
|
||||
GFP_KERNEL);
|
||||
if (!chip->vendor.priv) {
|
||||
rc = -ENOMEM;
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
init_waitqueue_head(&chip->vendor.read_queue);
|
||||
init_waitqueue_head(&chip->vendor.int_queue);
|
||||
|
||||
/* Default timeouts */
|
||||
chip->vendor.timeout_a = msecs_to_jiffies(TPM_I2C_SHORT_TIMEOUT);
|
||||
chip->vendor.timeout_b = msecs_to_jiffies(TPM_I2C_LONG_TIMEOUT);
|
||||
chip->vendor.timeout_c = msecs_to_jiffies(TPM_I2C_SHORT_TIMEOUT);
|
||||
chip->vendor.timeout_d = msecs_to_jiffies(TPM_I2C_SHORT_TIMEOUT);
|
||||
|
||||
/*
|
||||
* I2C intfcaps (interrupt capabilitieis) in the chip are hard coded to:
|
||||
* TPM_INTF_INT_LEVEL_LOW | TPM_INTF_DATA_AVAIL_INT
|
||||
* The IRQ should be set in the i2c_board_info (which is done
|
||||
* automatically in of_i2c_register_devices, for device tree users */
|
||||
chip->vendor.irq = client->irq;
|
||||
|
||||
if (chip->vendor.irq) {
|
||||
dev_dbg(dev, "%s() chip-vendor.irq\n", __func__);
|
||||
rc = devm_request_irq(dev, chip->vendor.irq,
|
||||
i2c_nuvoton_int_handler,
|
||||
IRQF_TRIGGER_LOW,
|
||||
chip->vendor.miscdev.name,
|
||||
chip);
|
||||
if (rc) {
|
||||
dev_err(dev, "%s() Unable to request irq: %d for use\n",
|
||||
__func__, chip->vendor.irq);
|
||||
chip->vendor.irq = 0;
|
||||
} else {
|
||||
/* Clear any pending interrupt */
|
||||
i2c_nuvoton_ready(chip);
|
||||
/* - wait for TPM_STS==0xA0 (stsValid, commandReady) */
|
||||
rc = i2c_nuvoton_wait_for_stat(chip,
|
||||
TPM_STS_COMMAND_READY,
|
||||
TPM_STS_COMMAND_READY,
|
||||
chip->vendor.timeout_b,
|
||||
NULL);
|
||||
if (rc == 0) {
|
||||
/*
|
||||
* TIS is in ready state
|
||||
* write dummy byte to enter reception state
|
||||
* TPM_DATA_FIFO_W <- rc (0)
|
||||
*/
|
||||
rc = i2c_nuvoton_write_buf(client,
|
||||
TPM_DATA_FIFO_W,
|
||||
1, (u8 *) (&rc));
|
||||
if (rc < 0)
|
||||
goto out_err;
|
||||
/* TPM_STS <- 0x40 (commandReady) */
|
||||
i2c_nuvoton_ready(chip);
|
||||
} else {
|
||||
/*
|
||||
* timeout_b reached - command was
|
||||
* aborted. TIS should now be in idle state -
|
||||
* only TPM_STS_VALID should be set
|
||||
*/
|
||||
if (i2c_nuvoton_read_status(chip) !=
|
||||
TPM_STS_VALID) {
|
||||
rc = -EIO;
|
||||
goto out_err;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tpm_get_timeouts(chip)) {
|
||||
rc = -ENODEV;
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
if (tpm_do_selftest(chip)) {
|
||||
rc = -ENODEV;
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
out_err:
|
||||
tpm_dev_vendor_release(chip);
|
||||
tpm_remove_hardware(chip->dev);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int i2c_nuvoton_remove(struct i2c_client *client)
|
||||
{
|
||||
struct device *dev = &(client->dev);
|
||||
struct tpm_chip *chip = dev_get_drvdata(dev);
|
||||
|
||||
if (chip)
|
||||
tpm_dev_vendor_release(chip);
|
||||
tpm_remove_hardware(dev);
|
||||
kfree(chip);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static const struct i2c_device_id i2c_nuvoton_id[] = {
|
||||
{I2C_DRIVER_NAME, 0},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, i2c_nuvoton_id);
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id i2c_nuvoton_of_match[] = {
|
||||
{.compatible = "nuvoton,npct501"},
|
||||
{.compatible = "winbond,wpct301"},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, i2c_nuvoton_of_match);
|
||||
#endif
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(i2c_nuvoton_pm_ops, tpm_pm_suspend, tpm_pm_resume);
|
||||
|
||||
static struct i2c_driver i2c_nuvoton_driver = {
|
||||
.id_table = i2c_nuvoton_id,
|
||||
.probe = i2c_nuvoton_probe,
|
||||
.remove = i2c_nuvoton_remove,
|
||||
.driver = {
|
||||
.name = I2C_DRIVER_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &i2c_nuvoton_pm_ops,
|
||||
.of_match_table = of_match_ptr(i2c_nuvoton_of_match),
|
||||
},
|
||||
};
|
||||
|
||||
module_i2c_driver(i2c_nuvoton_driver);
|
||||
|
||||
MODULE_AUTHOR("Dan Morav (dan.morav@nuvoton.com)");
|
||||
MODULE_DESCRIPTION("Nuvoton TPM I2C Driver");
|
||||
MODULE_LICENSE("GPL");
|
847
drivers/char/tpm/tpm_i2c_stm_st33.c
Normal file
847
drivers/char/tpm/tpm_i2c_stm_st33.c
Normal file
|
@ -0,0 +1,847 @@
|
|||
/*
|
||||
* 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/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, ret;
|
||||
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);
|
||||
if (burstcnt < 0)
|
||||
return burstcnt;
|
||||
len = min_t(int, burstcnt, count - size);
|
||||
ret = I2C_READ_DATA(client, TPM_DATA_FIFO, buf + size, len);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
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, i, size;
|
||||
int burstcnt = 0;
|
||||
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);
|
||||
if (burstcnt < 0)
|
||||
return burstcnt;
|
||||
size = min_t(int, len - i - 1, burstcnt);
|
||||
ret = I2C_WRITE_DATA(client, TPM_DATA_FIFO, buf + i, 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->ops->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 tpm_class_ops 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,
|
||||
};
|
||||
|
||||
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);
|
||||
tpm_do_selftest(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
|
||||
* @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 st33zp24_platform_data *pin_infos = dev->platform_data;
|
||||
int ret = 0;
|
||||
|
||||
if (power_mgt) {
|
||||
gpio_set_value(pin_infos->io_lpcpd, 0);
|
||||
} else {
|
||||
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->ops->status(chip) &
|
||||
TPM_STS_VALID) == TPM_STS_VALID,
|
||||
chip->vendor.timeout_b);
|
||||
} else {
|
||||
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");
|
61
drivers/char/tpm/tpm_i2c_stm_st33.h
Normal file
61
drivers/char/tpm/tpm_i2c_stm_st33.h
Normal 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__ */
|
702
drivers/char/tpm/tpm_ibmvtpm.c
Normal file
702
drivers/char/tpm/tpm_ibmvtpm.c
Normal file
|
@ -0,0 +1,702 @@
|
|||
/*
|
||||
* 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=%zd, 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;
|
||||
__be64 *word = (__be64 *)&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=%zd, 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 = cpu_to_be16(count);
|
||||
crq.data = cpu_to_be32(ibmvtpm->rtce_dma_handle);
|
||||
|
||||
rc = ibmvtpm_send_crq(ibmvtpm->vdev, be64_to_cpu(word[0]),
|
||||
be64_to_cpu(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, cpu_to_be64(buf[0]),
|
||||
cpu_to_be64(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, cpu_to_be64(buf[0]),
|
||||
cpu_to_be64(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);
|
||||
|
||||
/* ibmvtpm initializes at probe time, so the data we are
|
||||
* asking for may not be set yet. Estimate that 4K required
|
||||
* for TCE-mapped buffer in addition to CRQ.
|
||||
*/
|
||||
if (!ibmvtpm)
|
||||
return CRQ_RES_BUF_SIZE + PAGE_SIZE;
|
||||
|
||||
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, cpu_to_be64(buf[0]),
|
||||
cpu_to_be64(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 tpm_class_ops 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,
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
case IBMVTPM_VALID_CMD:
|
||||
switch (crq->msg) {
|
||||
case VTPM_GET_RTCE_BUFFER_SIZE_RES:
|
||||
if (be16_to_cpu(crq->len) <= 0) {
|
||||
dev_err(ibmvtpm->dev, "Invalid rtce size\n");
|
||||
return;
|
||||
}
|
||||
ibmvtpm->rtce_size = be16_to_cpu(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 = be32_to_cpu(crq->data);
|
||||
return;
|
||||
case VTPM_TPM_COMMAND_RES:
|
||||
/* len of the data in rtce buffer */
|
||||
ibmvtpm->res_len = be16_to_cpu(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");
|
76
drivers/char/tpm/tpm_ibmvtpm.h
Normal file
76
drivers/char/tpm/tpm_ibmvtpm.h
Normal 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;
|
||||
__be16 len;
|
||||
__be32 data;
|
||||
__be64 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
|
651
drivers/char/tpm/tpm_infineon.c
Normal file
651
drivers/char/tpm/tpm_infineon.c
Normal file
|
@ -0,0 +1,651 @@
|
|||
/*
|
||||
* 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 const struct tpm_class_ops 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,
|
||||
};
|
||||
|
||||
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");
|
380
drivers/char/tpm/tpm_nsc.c
Normal file
380
drivers/char/tpm/tpm_nsc.c
Normal file
|
@ -0,0 +1,380 @@
|
|||
/*
|
||||
* 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 tpm_class_ops 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,
|
||||
};
|
||||
|
||||
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
73
drivers/char/tpm/tpm_of.c
Normal 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;
|
||||
}
|
338
drivers/char/tpm/tpm_ppi.c
Normal file
338
drivers/char/tpm/tpm_ppi.c
Normal file
|
@ -0,0 +1,338 @@
|
|||
#include <linux/acpi.h>
|
||||
#include "tpm.h"
|
||||
|
||||
#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 const u8 tpm_ppi_uuid[] = {
|
||||
0xA6, 0xFA, 0xDD, 0x3D,
|
||||
0x1B, 0x36,
|
||||
0xB4, 0x4E,
|
||||
0xA4, 0x24,
|
||||
0x8D, 0x10, 0x08, 0x9D, 0x16, 0x53
|
||||
};
|
||||
|
||||
static char tpm_ppi_version[PPI_VERSION_LEN + 1];
|
||||
static acpi_handle tpm_ppi_handle;
|
||||
|
||||
static acpi_status ppi_callback(acpi_handle handle, u32 level, void *context,
|
||||
void **return_value)
|
||||
{
|
||||
union acpi_object *obj;
|
||||
|
||||
if (!acpi_check_dsm(handle, tpm_ppi_uuid, TPM_PPI_REVISION_ID,
|
||||
1 << TPM_PPI_FN_VERSION))
|
||||
return AE_OK;
|
||||
|
||||
/* Cache version string */
|
||||
obj = acpi_evaluate_dsm_typed(handle, tpm_ppi_uuid,
|
||||
TPM_PPI_REVISION_ID, TPM_PPI_FN_VERSION,
|
||||
NULL, ACPI_TYPE_STRING);
|
||||
if (obj) {
|
||||
strlcpy(tpm_ppi_version, obj->string.pointer,
|
||||
PPI_VERSION_LEN + 1);
|
||||
ACPI_FREE(obj);
|
||||
}
|
||||
|
||||
*return_value = handle;
|
||||
|
||||
return AE_CTRL_TERMINATE;
|
||||
}
|
||||
|
||||
static inline union acpi_object *
|
||||
tpm_eval_dsm(int func, acpi_object_type type, union acpi_object *argv4)
|
||||
{
|
||||
BUG_ON(!tpm_ppi_handle);
|
||||
return acpi_evaluate_dsm_typed(tpm_ppi_handle, tpm_ppi_uuid,
|
||||
TPM_PPI_REVISION_ID, func, argv4, type);
|
||||
}
|
||||
|
||||
static ssize_t tpm_show_ppi_version(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
return scnprintf(buf, PAGE_SIZE, "%s\n", tpm_ppi_version);
|
||||
}
|
||||
|
||||
static ssize_t tpm_show_ppi_request(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
ssize_t size = -EINVAL;
|
||||
union acpi_object *obj;
|
||||
|
||||
obj = tpm_eval_dsm(TPM_PPI_FN_GETREQ, ACPI_TYPE_PACKAGE, NULL);
|
||||
if (!obj)
|
||||
return -ENXIO;
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
if (obj->package.count == 2 &&
|
||||
obj->package.elements[0].type == ACPI_TYPE_INTEGER &&
|
||||
obj->package.elements[1].type == ACPI_TYPE_INTEGER) {
|
||||
if (obj->package.elements[0].integer.value)
|
||||
size = -EFAULT;
|
||||
else
|
||||
size = scnprintf(buf, PAGE_SIZE, "%llu\n",
|
||||
obj->package.elements[1].integer.value);
|
||||
}
|
||||
|
||||
ACPI_FREE(obj);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static ssize_t tpm_store_ppi_request(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
u32 req;
|
||||
u64 ret;
|
||||
int func = TPM_PPI_FN_SUBREQ;
|
||||
union acpi_object *obj, tmp;
|
||||
union acpi_object argv4 = ACPI_INIT_DSM_ARGV4(1, &tmp);
|
||||
|
||||
/*
|
||||
* 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 (acpi_check_dsm(tpm_ppi_handle, tpm_ppi_uuid, TPM_PPI_REVISION_ID,
|
||||
1 << TPM_PPI_FN_SUBREQ2))
|
||||
func = 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(tpm_ppi_version, "1.2") < 0) {
|
||||
if (sscanf(buf, "%d", &req) != 1)
|
||||
return -EINVAL;
|
||||
argv4.type = ACPI_TYPE_BUFFER;
|
||||
argv4.buffer.length = sizeof(req);
|
||||
argv4.buffer.pointer = (u8 *)&req;
|
||||
} else {
|
||||
tmp.type = ACPI_TYPE_INTEGER;
|
||||
if (sscanf(buf, "%llu", &tmp.integer.value) != 1)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
obj = tpm_eval_dsm(func, ACPI_TYPE_INTEGER, &argv4);
|
||||
if (!obj) {
|
||||
return -ENXIO;
|
||||
} else {
|
||||
ret = obj->integer.value;
|
||||
ACPI_FREE(obj);
|
||||
}
|
||||
|
||||
if (ret == 0)
|
||||
return (acpi_status)count;
|
||||
|
||||
return (ret == 1) ? -EPERM : -EFAULT;
|
||||
}
|
||||
|
||||
static ssize_t tpm_show_ppi_transition_action(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
u32 ret;
|
||||
acpi_status status;
|
||||
union acpi_object *obj = NULL;
|
||||
union acpi_object tmp = {
|
||||
.buffer.type = ACPI_TYPE_BUFFER,
|
||||
.buffer.length = 0,
|
||||
.buffer.pointer = NULL
|
||||
};
|
||||
|
||||
static char *info[] = {
|
||||
"None",
|
||||
"Shutdown",
|
||||
"Reboot",
|
||||
"OS Vendor-specific",
|
||||
"Error",
|
||||
};
|
||||
|
||||
/*
|
||||
* 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(tpm_ppi_version, "1.2") < 0)
|
||||
obj = &tmp;
|
||||
obj = tpm_eval_dsm(TPM_PPI_FN_GETACT, ACPI_TYPE_INTEGER, obj);
|
||||
if (!obj) {
|
||||
return -ENXIO;
|
||||
} else {
|
||||
ret = obj->integer.value;
|
||||
ACPI_FREE(obj);
|
||||
}
|
||||
|
||||
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]);
|
||||
return status;
|
||||
}
|
||||
|
||||
static ssize_t tpm_show_ppi_response(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
acpi_status status = -EINVAL;
|
||||
union acpi_object *obj, *ret_obj;
|
||||
u64 req, res;
|
||||
|
||||
obj = tpm_eval_dsm(TPM_PPI_FN_GETRSP, ACPI_TYPE_PACKAGE, NULL);
|
||||
if (!obj)
|
||||
return -ENXIO;
|
||||
|
||||
/*
|
||||
* 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 = obj->package.elements;
|
||||
if (obj->package.count < 3 ||
|
||||
ret_obj[0].type != ACPI_TYPE_INTEGER ||
|
||||
ret_obj[1].type != ACPI_TYPE_INTEGER ||
|
||||
ret_obj[2].type != ACPI_TYPE_INTEGER)
|
||||
goto cleanup;
|
||||
|
||||
if (ret_obj[0].integer.value) {
|
||||
status = -EFAULT;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
req = ret_obj[1].integer.value;
|
||||
res = ret_obj[2].integer.value;
|
||||
if (req) {
|
||||
if (res == 0)
|
||||
status = scnprintf(buf, PAGE_SIZE, "%llu %s\n", req,
|
||||
"0: Success");
|
||||
else if (res == 0xFFFFFFF0)
|
||||
status = scnprintf(buf, PAGE_SIZE, "%llu %s\n", req,
|
||||
"0xFFFFFFF0: User Abort");
|
||||
else if (res == 0xFFFFFFF1)
|
||||
status = scnprintf(buf, PAGE_SIZE, "%llu %s\n", req,
|
||||
"0xFFFFFFF1: BIOS Failure");
|
||||
else if (res >= 1 && res <= 0x00000FFF)
|
||||
status = scnprintf(buf, PAGE_SIZE, "%llu %llu: %s\n",
|
||||
req, res, "Corresponding TPM error");
|
||||
else
|
||||
status = scnprintf(buf, PAGE_SIZE, "%llu %llu: %s\n",
|
||||
req, res, "Error");
|
||||
} else {
|
||||
status = scnprintf(buf, PAGE_SIZE, "%llu: %s\n",
|
||||
req, "No Recent Request");
|
||||
}
|
||||
|
||||
cleanup:
|
||||
ACPI_FREE(obj);
|
||||
return status;
|
||||
}
|
||||
|
||||
static ssize_t show_ppi_operations(char *buf, u32 start, u32 end)
|
||||
{
|
||||
int i;
|
||||
u32 ret;
|
||||
char *str = buf;
|
||||
union acpi_object *obj, tmp;
|
||||
union acpi_object argv = ACPI_INIT_DSM_ARGV4(1, &tmp);
|
||||
|
||||
static char *info[] = {
|
||||
"Not implemented",
|
||||
"BIOS only",
|
||||
"Blocked for OS by BIOS",
|
||||
"User required",
|
||||
"User not required",
|
||||
};
|
||||
|
||||
if (!acpi_check_dsm(tpm_ppi_handle, tpm_ppi_uuid, TPM_PPI_REVISION_ID,
|
||||
1 << TPM_PPI_FN_GETOPR))
|
||||
return -EPERM;
|
||||
|
||||
tmp.integer.type = ACPI_TYPE_INTEGER;
|
||||
for (i = start; i <= end; i++) {
|
||||
tmp.integer.value = i;
|
||||
obj = tpm_eval_dsm(TPM_PPI_FN_GETOPR, ACPI_TYPE_INTEGER, &argv);
|
||||
if (!obj) {
|
||||
return -ENOMEM;
|
||||
} else {
|
||||
ret = obj->integer.value;
|
||||
ACPI_FREE(obj);
|
||||
}
|
||||
|
||||
if (ret > 0 && ret < ARRAY_SIZE(info))
|
||||
str += scnprintf(str, PAGE_SIZE, "%d %d: %s\n",
|
||||
i, ret, info[ret]);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
/* Cache TPM ACPI handle and version string */
|
||||
acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, ACPI_UINT32_MAX,
|
||||
ppi_callback, NULL, NULL, &tpm_ppi_handle);
|
||||
return tpm_ppi_handle ? sysfs_create_group(parent, &ppi_attr_grp) : 0;
|
||||
}
|
||||
|
||||
void tpm_remove_ppi(struct kobject *parent)
|
||||
{
|
||||
if (tpm_ppi_handle)
|
||||
sysfs_remove_group(parent, &ppi_attr_grp);
|
||||
}
|
961
drivers/char/tpm/tpm_tis.c
Normal file
961
drivers/char/tpm/tpm_tis.c
Normal file
|
@ -0,0 +1,961 @@
|
|||
/*
|
||||
* 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))
|
||||
|
||||
struct priv_data {
|
||||
bool irq_tested;
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static void disable_interrupts(struct tpm_chip *chip)
|
||||
{
|
||||
u32 intmask;
|
||||
|
||||
intmask =
|
||||
ioread32(chip->vendor.iobase +
|
||||
TPM_INT_ENABLE(chip->vendor.locality));
|
||||
intmask &= ~TPM_GLOBAL_INT_ENABLE;
|
||||
iowrite32(intmask,
|
||||
chip->vendor.iobase +
|
||||
TPM_INT_ENABLE(chip->vendor.locality));
|
||||
free_irq(chip->vendor.irq, chip);
|
||||
chip->vendor.irq = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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_main(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;
|
||||
}
|
||||
|
||||
static int tpm_tis_send(struct tpm_chip *chip, u8 *buf, size_t len)
|
||||
{
|
||||
int rc, irq;
|
||||
struct priv_data *priv = chip->vendor.priv;
|
||||
|
||||
if (!chip->vendor.irq || priv->irq_tested)
|
||||
return tpm_tis_send_main(chip, buf, len);
|
||||
|
||||
/* Verify receipt of the expected IRQ */
|
||||
irq = chip->vendor.irq;
|
||||
chip->vendor.irq = 0;
|
||||
rc = tpm_tis_send_main(chip, buf, len);
|
||||
chip->vendor.irq = irq;
|
||||
if (!priv->irq_tested)
|
||||
msleep(1);
|
||||
if (!priv->irq_tested) {
|
||||
disable_interrupts(chip);
|
||||
dev_err(chip->dev,
|
||||
FW_BUG "TPM interrupt not working, polling instead\n");
|
||||
}
|
||||
priv->irq_tested = true;
|
||||
return rc;
|
||||
}
|
||||
|
||||
struct tis_vendor_timeout_override {
|
||||
u32 did_vid;
|
||||
unsigned long timeout_us[4];
|
||||
};
|
||||
|
||||
static const struct tis_vendor_timeout_override vendor_timeout_overrides[] = {
|
||||
/* Atmel 3204 */
|
||||
{ 0x32041114, { (TIS_SHORT_TIMEOUT*1000), (TIS_LONG_TIMEOUT*1000),
|
||||
(TIS_SHORT_TIMEOUT*1000), (TIS_SHORT_TIMEOUT*1000) } },
|
||||
};
|
||||
|
||||
static bool tpm_tis_update_timeouts(struct tpm_chip *chip,
|
||||
unsigned long *timeout_cap)
|
||||
{
|
||||
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++) {
|
||||
if (vendor_timeout_overrides[i].did_vid != did_vid)
|
||||
continue;
|
||||
memcpy(timeout_cap, vendor_timeout_overrides[i].timeout_us,
|
||||
sizeof(vendor_timeout_overrides[i].timeout_us));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 tpm_class_ops 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,
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
((struct priv_data *)chip->vendor.priv)->irq_tested = true;
|
||||
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;
|
||||
struct priv_data *priv;
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(struct priv_data), GFP_KERNEL);
|
||||
if (priv == NULL)
|
||||
return -ENOMEM;
|
||||
if (!(chip = tpm_register_hardware(dev, &tpm_tis)))
|
||||
return -ENODEV;
|
||||
chip->vendor.priv = priv;
|
||||
|
||||
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)\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");
|
||||
|
||||
/* 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));
|
||||
}
|
||||
}
|
||||
|
||||
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\n");
|
||||
rc = -ENODEV;
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
#ifdef 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));
|
||||
}
|
||||
|
||||
static int tpm_tis_resume(struct device *dev)
|
||||
{
|
||||
struct tpm_chip *chip = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
if (chip->vendor.irq)
|
||||
tpm_tis_reenable_interrupts(chip);
|
||||
|
||||
ret = tpm_pm_resume(dev);
|
||||
if (!ret)
|
||||
tpm_do_selftest(chip);
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(tpm_tis_pm, tpm_pm_suspend, tpm_tis_resume);
|
||||
|
||||
#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 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,
|
||||
.remove = tpm_tis_pnp_remove,
|
||||
.driver = {
|
||||
.pm = &tpm_tis_pm,
|
||||
},
|
||||
};
|
||||
|
||||
#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
|
||||
|
||||
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;
|
||||
pdev = platform_device_register_simple("tpm_tis", -1, NULL, 0);
|
||||
if (IS_ERR(pdev)) {
|
||||
rc = PTR_ERR(pdev);
|
||||
goto err_dev;
|
||||
}
|
||||
rc = tpm_tis_init(&pdev->dev, TIS_MEM_BASE, TIS_MEM_LEN, 0);
|
||||
if (rc)
|
||||
goto err_init;
|
||||
return 0;
|
||||
err_init:
|
||||
platform_device_unregister(pdev);
|
||||
err_dev:
|
||||
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");
|
398
drivers/char/tpm/xen-tpmfront.c
Normal file
398
drivers/char/tpm/xen-tpmfront.c
Normal file
|
@ -0,0 +1,398 @@
|
|||
/*
|
||||
* Implementation of the Xen vTPM device frontend
|
||||
*
|
||||
* Author: Daniel De Graaf <dgdegra@tycho.nsa.gov>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*/
|
||||
#include <linux/errno.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <xen/xen.h>
|
||||
#include <xen/events.h>
|
||||
#include <xen/interface/io/tpmif.h>
|
||||
#include <xen/grant_table.h>
|
||||
#include <xen/xenbus.h>
|
||||
#include <xen/page.h>
|
||||
#include "tpm.h"
|
||||
#include <xen/platform_pci.h>
|
||||
|
||||
struct tpm_private {
|
||||
struct tpm_chip *chip;
|
||||
struct xenbus_device *dev;
|
||||
|
||||
struct vtpm_shared_page *shr;
|
||||
|
||||
unsigned int evtchn;
|
||||
int ring_ref;
|
||||
domid_t backend_id;
|
||||
};
|
||||
|
||||
enum status_bits {
|
||||
VTPM_STATUS_RUNNING = 0x1,
|
||||
VTPM_STATUS_IDLE = 0x2,
|
||||
VTPM_STATUS_RESULT = 0x4,
|
||||
VTPM_STATUS_CANCELED = 0x8,
|
||||
};
|
||||
|
||||
static u8 vtpm_status(struct tpm_chip *chip)
|
||||
{
|
||||
struct tpm_private *priv = TPM_VPRIV(chip);
|
||||
switch (priv->shr->state) {
|
||||
case VTPM_STATE_IDLE:
|
||||
return VTPM_STATUS_IDLE | VTPM_STATUS_CANCELED;
|
||||
case VTPM_STATE_FINISH:
|
||||
return VTPM_STATUS_IDLE | VTPM_STATUS_RESULT;
|
||||
case VTPM_STATE_SUBMIT:
|
||||
case VTPM_STATE_CANCEL: /* cancel requested, not yet canceled */
|
||||
return VTPM_STATUS_RUNNING;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static bool vtpm_req_canceled(struct tpm_chip *chip, u8 status)
|
||||
{
|
||||
return status & VTPM_STATUS_CANCELED;
|
||||
}
|
||||
|
||||
static void vtpm_cancel(struct tpm_chip *chip)
|
||||
{
|
||||
struct tpm_private *priv = TPM_VPRIV(chip);
|
||||
priv->shr->state = VTPM_STATE_CANCEL;
|
||||
wmb();
|
||||
notify_remote_via_evtchn(priv->evtchn);
|
||||
}
|
||||
|
||||
static unsigned int shr_data_offset(struct vtpm_shared_page *shr)
|
||||
{
|
||||
return sizeof(*shr) + sizeof(u32) * shr->nr_extra_pages;
|
||||
}
|
||||
|
||||
static int vtpm_send(struct tpm_chip *chip, u8 *buf, size_t count)
|
||||
{
|
||||
struct tpm_private *priv = TPM_VPRIV(chip);
|
||||
struct vtpm_shared_page *shr = priv->shr;
|
||||
unsigned int offset = shr_data_offset(shr);
|
||||
|
||||
u32 ordinal;
|
||||
unsigned long duration;
|
||||
|
||||
if (offset > PAGE_SIZE)
|
||||
return -EINVAL;
|
||||
|
||||
if (offset + count > PAGE_SIZE)
|
||||
return -EINVAL;
|
||||
|
||||
/* Wait for completion of any existing command or cancellation */
|
||||
if (wait_for_tpm_stat(chip, VTPM_STATUS_IDLE, chip->vendor.timeout_c,
|
||||
&chip->vendor.read_queue, true) < 0) {
|
||||
vtpm_cancel(chip);
|
||||
return -ETIME;
|
||||
}
|
||||
|
||||
memcpy(offset + (u8 *)shr, buf, count);
|
||||
shr->length = count;
|
||||
barrier();
|
||||
shr->state = VTPM_STATE_SUBMIT;
|
||||
wmb();
|
||||
notify_remote_via_evtchn(priv->evtchn);
|
||||
|
||||
ordinal = be32_to_cpu(((struct tpm_input_header*)buf)->ordinal);
|
||||
duration = tpm_calc_ordinal_duration(chip, ordinal);
|
||||
|
||||
if (wait_for_tpm_stat(chip, VTPM_STATUS_IDLE, duration,
|
||||
&chip->vendor.read_queue, true) < 0) {
|
||||
/* got a signal or timeout, try to cancel */
|
||||
vtpm_cancel(chip);
|
||||
return -ETIME;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static int vtpm_recv(struct tpm_chip *chip, u8 *buf, size_t count)
|
||||
{
|
||||
struct tpm_private *priv = TPM_VPRIV(chip);
|
||||
struct vtpm_shared_page *shr = priv->shr;
|
||||
unsigned int offset = shr_data_offset(shr);
|
||||
size_t length = shr->length;
|
||||
|
||||
if (shr->state == VTPM_STATE_IDLE)
|
||||
return -ECANCELED;
|
||||
|
||||
/* In theory the wait at the end of _send makes this one unnecessary */
|
||||
if (wait_for_tpm_stat(chip, VTPM_STATUS_RESULT, chip->vendor.timeout_c,
|
||||
&chip->vendor.read_queue, true) < 0) {
|
||||
vtpm_cancel(chip);
|
||||
return -ETIME;
|
||||
}
|
||||
|
||||
if (offset > PAGE_SIZE)
|
||||
return -EIO;
|
||||
|
||||
if (offset + length > PAGE_SIZE)
|
||||
length = PAGE_SIZE - offset;
|
||||
|
||||
if (length > count)
|
||||
length = count;
|
||||
|
||||
memcpy(buf, offset + (u8 *)shr, length);
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
static const struct tpm_class_ops tpm_vtpm = {
|
||||
.status = vtpm_status,
|
||||
.recv = vtpm_recv,
|
||||
.send = vtpm_send,
|
||||
.cancel = vtpm_cancel,
|
||||
.req_complete_mask = VTPM_STATUS_IDLE | VTPM_STATUS_RESULT,
|
||||
.req_complete_val = VTPM_STATUS_IDLE | VTPM_STATUS_RESULT,
|
||||
.req_canceled = vtpm_req_canceled,
|
||||
};
|
||||
|
||||
static irqreturn_t tpmif_interrupt(int dummy, void *dev_id)
|
||||
{
|
||||
struct tpm_private *priv = dev_id;
|
||||
|
||||
switch (priv->shr->state) {
|
||||
case VTPM_STATE_IDLE:
|
||||
case VTPM_STATE_FINISH:
|
||||
wake_up_interruptible(&priv->chip->vendor.read_queue);
|
||||
break;
|
||||
case VTPM_STATE_SUBMIT:
|
||||
case VTPM_STATE_CANCEL:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int setup_chip(struct device *dev, struct tpm_private *priv)
|
||||
{
|
||||
struct tpm_chip *chip;
|
||||
|
||||
chip = tpm_register_hardware(dev, &tpm_vtpm);
|
||||
if (!chip)
|
||||
return -ENODEV;
|
||||
|
||||
init_waitqueue_head(&chip->vendor.read_queue);
|
||||
|
||||
priv->chip = chip;
|
||||
TPM_VPRIV(chip) = priv;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* caller must clean up in case of errors */
|
||||
static int setup_ring(struct xenbus_device *dev, struct tpm_private *priv)
|
||||
{
|
||||
struct xenbus_transaction xbt;
|
||||
const char *message = NULL;
|
||||
int rv;
|
||||
|
||||
priv->shr = (void *)__get_free_page(GFP_KERNEL|__GFP_ZERO);
|
||||
if (!priv->shr) {
|
||||
xenbus_dev_fatal(dev, -ENOMEM, "allocating shared ring");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
rv = xenbus_grant_ring(dev, virt_to_mfn(priv->shr));
|
||||
if (rv < 0)
|
||||
return rv;
|
||||
|
||||
priv->ring_ref = rv;
|
||||
|
||||
rv = xenbus_alloc_evtchn(dev, &priv->evtchn);
|
||||
if (rv)
|
||||
return rv;
|
||||
|
||||
rv = bind_evtchn_to_irqhandler(priv->evtchn, tpmif_interrupt, 0,
|
||||
"tpmif", priv);
|
||||
if (rv <= 0) {
|
||||
xenbus_dev_fatal(dev, rv, "allocating TPM irq");
|
||||
return rv;
|
||||
}
|
||||
priv->chip->vendor.irq = rv;
|
||||
|
||||
again:
|
||||
rv = xenbus_transaction_start(&xbt);
|
||||
if (rv) {
|
||||
xenbus_dev_fatal(dev, rv, "starting transaction");
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = xenbus_printf(xbt, dev->nodename,
|
||||
"ring-ref", "%u", priv->ring_ref);
|
||||
if (rv) {
|
||||
message = "writing ring-ref";
|
||||
goto abort_transaction;
|
||||
}
|
||||
|
||||
rv = xenbus_printf(xbt, dev->nodename, "event-channel", "%u",
|
||||
priv->evtchn);
|
||||
if (rv) {
|
||||
message = "writing event-channel";
|
||||
goto abort_transaction;
|
||||
}
|
||||
|
||||
rv = xenbus_printf(xbt, dev->nodename, "feature-protocol-v2", "1");
|
||||
if (rv) {
|
||||
message = "writing feature-protocol-v2";
|
||||
goto abort_transaction;
|
||||
}
|
||||
|
||||
rv = xenbus_transaction_end(xbt, 0);
|
||||
if (rv == -EAGAIN)
|
||||
goto again;
|
||||
if (rv) {
|
||||
xenbus_dev_fatal(dev, rv, "completing transaction");
|
||||
return rv;
|
||||
}
|
||||
|
||||
xenbus_switch_state(dev, XenbusStateInitialised);
|
||||
|
||||
return 0;
|
||||
|
||||
abort_transaction:
|
||||
xenbus_transaction_end(xbt, 1);
|
||||
if (message)
|
||||
xenbus_dev_error(dev, rv, "%s", message);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static void ring_free(struct tpm_private *priv)
|
||||
{
|
||||
if (!priv)
|
||||
return;
|
||||
|
||||
if (priv->ring_ref)
|
||||
gnttab_end_foreign_access(priv->ring_ref, 0,
|
||||
(unsigned long)priv->shr);
|
||||
else
|
||||
free_page((unsigned long)priv->shr);
|
||||
|
||||
if (priv->chip && priv->chip->vendor.irq)
|
||||
unbind_from_irqhandler(priv->chip->vendor.irq, priv);
|
||||
|
||||
kfree(priv);
|
||||
}
|
||||
|
||||
static int tpmfront_probe(struct xenbus_device *dev,
|
||||
const struct xenbus_device_id *id)
|
||||
{
|
||||
struct tpm_private *priv;
|
||||
int rv;
|
||||
|
||||
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv) {
|
||||
xenbus_dev_fatal(dev, -ENOMEM, "allocating priv structure");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
rv = setup_chip(&dev->dev, priv);
|
||||
if (rv) {
|
||||
kfree(priv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = setup_ring(dev, priv);
|
||||
if (rv) {
|
||||
tpm_remove_hardware(&dev->dev);
|
||||
ring_free(priv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
tpm_get_timeouts(priv->chip);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static int tpmfront_remove(struct xenbus_device *dev)
|
||||
{
|
||||
struct tpm_chip *chip = dev_get_drvdata(&dev->dev);
|
||||
struct tpm_private *priv = TPM_VPRIV(chip);
|
||||
tpm_remove_hardware(&dev->dev);
|
||||
ring_free(priv);
|
||||
TPM_VPRIV(chip) = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tpmfront_resume(struct xenbus_device *dev)
|
||||
{
|
||||
/* A suspend/resume/migrate will interrupt a vTPM anyway */
|
||||
tpmfront_remove(dev);
|
||||
return tpmfront_probe(dev, NULL);
|
||||
}
|
||||
|
||||
static void backend_changed(struct xenbus_device *dev,
|
||||
enum xenbus_state backend_state)
|
||||
{
|
||||
int val;
|
||||
|
||||
switch (backend_state) {
|
||||
case XenbusStateInitialised:
|
||||
case XenbusStateConnected:
|
||||
if (dev->state == XenbusStateConnected)
|
||||
break;
|
||||
|
||||
if (xenbus_scanf(XBT_NIL, dev->otherend,
|
||||
"feature-protocol-v2", "%d", &val) < 0)
|
||||
val = 0;
|
||||
if (!val) {
|
||||
xenbus_dev_fatal(dev, -EINVAL,
|
||||
"vTPM protocol 2 required");
|
||||
return;
|
||||
}
|
||||
xenbus_switch_state(dev, XenbusStateConnected);
|
||||
break;
|
||||
|
||||
case XenbusStateClosing:
|
||||
case XenbusStateClosed:
|
||||
device_unregister(&dev->dev);
|
||||
xenbus_frontend_closed(dev);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct xenbus_device_id tpmfront_ids[] = {
|
||||
{ "vtpm" },
|
||||
{ "" }
|
||||
};
|
||||
MODULE_ALIAS("xen:vtpm");
|
||||
|
||||
static struct xenbus_driver tpmfront_driver = {
|
||||
.ids = tpmfront_ids,
|
||||
.probe = tpmfront_probe,
|
||||
.remove = tpmfront_remove,
|
||||
.resume = tpmfront_resume,
|
||||
.otherend_changed = backend_changed,
|
||||
};
|
||||
|
||||
static int __init xen_tpmfront_init(void)
|
||||
{
|
||||
if (!xen_domain())
|
||||
return -ENODEV;
|
||||
|
||||
if (!xen_has_pv_devices())
|
||||
return -ENODEV;
|
||||
|
||||
return xenbus_register_frontend(&tpmfront_driver);
|
||||
}
|
||||
module_init(xen_tpmfront_init);
|
||||
|
||||
static void __exit xen_tpmfront_exit(void)
|
||||
{
|
||||
xenbus_unregister_driver(&tpmfront_driver);
|
||||
}
|
||||
module_exit(xen_tpmfront_exit);
|
||||
|
||||
MODULE_AUTHOR("Daniel De Graaf <dgdegra@tycho.nsa.gov>");
|
||||
MODULE_DESCRIPTION("Xen vTPM Driver");
|
||||
MODULE_LICENSE("GPL");
|
Loading…
Add table
Add a link
Reference in a new issue