blob: ecd989007fff0895a32ac796f022ecf2b6564e0f [file] [log] [blame]
// Copyright (C) 2006, Amit Singh <http://osxbook.com>
// Released under GPLv2.
// Borrows code from the Linux kernel Infineon TPM driver.
#include "OSXBookTPM.h"
// #define DEBUG 1
extern "C" {
#include "InfineonTPM.h"
#include <libkern/OSTypes.h>
#include <sys/systm.h>
#include <sys/uio.h>
#include <sys/ioctl.h>
#include <sys/conf.h>
#include <miscfs/devfs/devfs.h>
}
int tpm_open(dev_t dev, int flags, int devtype, struct proc *p);
int tpm_close(dev_t dev, int flags, int devtype, struct proc *p);
int tpm_read(dev_t dev, struct uio *uio, int ioflag);
int tpm_write(dev_t dev, struct uio *uio, int ioflag);
int tpm_ioctl(dev_t dev, u_long cmd, caddr_t data, int fflag, struct proc *p);
#define nullselect (d_select_t *)&nulldev
#define nullstop (d_stop_t *)&nulldev
#define nullreset (d_reset_t *)&nulldev
#define TPM_NAME "tpm"
#define TPM_MINOR 0
#define TPM_ALLOW_MULTIPLE_OPENS 1 // for testing
static void *tpm_dev = NULL;
static int tpm_dev_index = -1;
static int is_open = 0;
static UInt8 tpmData[TPM_BUFSIZE] = { 0 };
static int number_of_wtx = 0;
static com_osxbook_driver_InfineonTPM *instance = NULL;
static struct cdevsw tpm_dev_cdevsw = {
tpm_open, // d_open
tpm_close, // d_close
tpm_read, // d_read
tpm_write, // d_write
eno_ioctl, // d_ioctl
nullstop, // d_stop
nullreset, // d_stop
0, // d_ttys
nullselect, // d_select
eno_mmap, // d_mmap
eno_strat, // d_strategy
eno_getc, // d_getc
eno_putc, // d_putc
D_TTY, // d_type
};
int
tpm_open(dev_t dev, int flags, int devtype, struct proc *p)
{
if (!instance) {
IOLog("no instance in open?\n");
return EIO;
}
if (TPM_ALLOW_MULTIPLE_OPENS) {
return KERN_SUCCESS;
}
instance->lockTPM();
if (is_open) {
instance->unlockTPM();
return EBUSY;
}
is_open = 1;
instance->unlockTPM();
return KERN_SUCCESS;
}
int
tpm_close(dev_t dev, int flags, int devtype, struct proc *p)
{
if (!instance) {
IOLog("no instance in close?\n");
return EIO;
}
instance->lockTPM();
is_open = 0;
instance->unlockTPM();
return KERN_SUCCESS;
}
int
tpm_ioctl(dev_t dev, u_long cmd, caddr_t data, int fflag, struct proc *p)
{
return ENODEV;
}
int
tpm_read(dev_t dev, struct uio *uio, int ioflag)
{
int ret, bytesToRead;
if (!instance) {
return EIO;
}
bytesToRead = instance->dataPending;
instance->dataPending = 0;
if (bytesToRead <= 0) {
return 0;
}
if (bytesToRead > uio_resid(uio)) {
bytesToRead = uio_resid(uio);
}
instance->lockBuffer();
ret = uiomove((char *)tpmData, bytesToRead, uio);
instance->unlockBuffer();
return ret;
}
int
tpm_write(dev_t dev, struct uio *uio, int ioflag)
{
ssize_t len;
int ret = 0, bytesToWrite = 0, maxWaitCounter = 0;
if (!instance) {
return EIO;
}
// This should be replaced by bsd_timeout() or something similar.
while (instance->dataPending != 0) {
IOSleep(TPM_TIMEOUT);
maxWaitCounter++;
if (maxWaitCounter > 120) {
instance->lockBuffer();
instance->dataPending = 0;
memset(tpmData, 0, TPM_BUFSIZE);
instance->unlockBuffer();
}
}
instance->lockBuffer();
bytesToWrite = min(uio_resid(uio), TPM_BUFSIZE);
ret = uiomove((char *)tpmData, bytesToWrite, uio);
if (ret != 0) {
instance->unlockBuffer();
return ret;
}
ret = instance->tpmTransmit(tpmData, TPM_BUFSIZE, &len);
if (ret == 0) {
instance->dataPending = len;
}
instance->unlockBuffer();
return ret;
}
// Our superclass
#define super IOService
OSDefineMetaClassAndStructors(com_osxbook_driver_InfineonTPM, IOService)
// Our makeshift inb/outb implementation.
UInt8
com_osxbook_driver_InfineonTPM::inb(UInt32 port)
{
if (port >= TPM_INF_BASE) {
return ad->ioRead8(port - TPM_INF_BASE, mmio_base);
} else {
return ad->ioRead8(port - TPM_INF_ADDR, mmio_addr);
}
}
void
com_osxbook_driver_InfineonTPM::outb(UInt8 value, UInt32 port)
{
if (port >= TPM_INF_BASE) {
ad->ioWrite8(port - TPM_INF_BASE, value, mmio_base);
} else {
ad->ioWrite8(port - TPM_INF_ADDR, value, mmio_addr);
}
}
UInt8
com_osxbook_driver_InfineonTPM::tpmStatus(void)
{
return inb(TPM_INF_BASE + STAT);
}
void
com_osxbook_driver_InfineonTPM::tpmCancel(void)
{
// nothing
}
bool
com_osxbook_driver_InfineonTPM::init(OSDictionary *dict)
{
super::init(dict);
tpm_dev_index = cdevsw_add(-1, &tpm_dev_cdevsw);
if (tpm_dev_index == -1) {
IOLog("cdevsw_add failed\n");
return KERN_FAILURE;
}
tpm_dev = devfs_make_node(makedev(tpm_dev_index, TPM_MINOR),
DEVFS_CHAR,
UID_ROOT,
GID_WHEEL,
0600,
TPM_NAME);
if (tpm_dev == NULL) {
IOLog("devfs_make_node failed\n");
(void)cdevsw_remove(tpm_dev_index, &tpm_dev_cdevsw);
return KERN_FAILURE;
}
return true;
}
void
com_osxbook_driver_InfineonTPM::free(void)
{
super::free();
int ret = -1;
if (tpm_dev != NULL) {
devfs_remove(tpm_dev);
tpm_dev = NULL;
}
if (tpm_dev_index != -1) {
ret = cdevsw_remove(tpm_dev_index, &tpm_dev_cdevsw);
}
if (ret != tpm_dev_index) {
return;
}
tpm_dev_index = -1;
return;
}
IOService *
com_osxbook_driver_InfineonTPM::probe(IOService *provider, SInt32 *score)
{
IOService *result = super::probe(provider, score);
return result;
}
void
com_osxbook_driver_InfineonTPM::printRegisters(void)
{
int status;
status = inb(STAT);
IOLog("Status register\n");
if (status & 1<<STAT_RDA)
IOLog("RDA *\n");
if (status & 1<<STAT_IRQA)
IOLog("IRQA *\n");
if (status & 1<<STAT_TOK)
IOLog("TOK *\n");
if (status & 1<<STAT_FOK)
IOLog("FOK *\n");
if (status & 1<<STAT_LPA)
IOLog("LPA *\n");
if (status & 1<<STAT_XFE)
IOLog("XFE *\n");
status = inb(CMD);
IOLog("Command register\n");
if (status & 1<<CMD_IRQC)
IOLog("IRQC *\n");
if (status & 1<<CMD_RES)
IOLog("RES *\n");
if (status & 1<<CMD_LP)
IOLog("LP *\n");
if (status & 1<<CMD_DIS)
IOLog("DIS *\n");
}
// One could argue that this method commits sort of a layering violation,
// but it's nice to have everything ready-to-go for testing when the driver
// is loaded.
void
com_osxbook_driver_InfineonTPM::tpmStartup(void)
{
int junk_ret;
ssize_t junk_out;
UInt8 tpmStartup_data[] = {
0, 193, // TPM_TAG_RQU_COMMAND
0, 0, 0, 12,
0, 0, 0, 153, // TPM_ORD_Startup
0, 1
};
UInt8 tpm_continueselftest_data[] = {
0, 193, // TPM_TAG_RQU_COMMAND
0, 0, 0, 10,
0, 0, 0, 83 // TPM_ORD_ContinueSelfTest
};
junk_ret = tpmTransmit(tpmStartup_data, sizeof(tpmStartup_data), &junk_out);
instance->dataPending = 0;
junk_ret = tpmTransmit(tpm_continueselftest_data,
sizeof(tpm_continueselftest_data), &junk_out);
instance->dataPending = 0;
}
bool
com_osxbook_driver_InfineonTPM::start(IOService *provider)
{
bool result = super::start(provider);
tpmLockGroup = NULL;
bufferMutex = NULL;
tpmMutex = NULL;
dataPending = 0;
instance = this;
ad = (IOACPIPlatformDevice *)provider;
// ad->getDeviceStatus() if you are curious
if (ad->getDeviceMemoryCount() < 2) {
IOLog("fatal: unexpected memory count\n");
return KERN_FAILURE;
}
mmio_addr = ad->mapDeviceMemoryWithIndex(0, 0);
mmio_base = ad->mapDeviceMemoryWithIndex(1, 0);
TPM_INF_ADDR = mmio_addr->getPhysicalAddress();
TPM_INF_ADDR_LEN = mmio_addr->getLength();
TPM_INF_DATA = (TPM_INF_ADDR + 1);
TPM_INF_BASE = mmio_base->getPhysicalAddress();
TPM_INF_PORT_LEN = mmio_base->getLength();
setProperty("addr", TPM_INF_ADDR, 32);
setProperty("addr-len", TPM_INF_ADDR_LEN, 32);
setProperty("data", TPM_INF_DATA, 32);
setProperty("base", TPM_INF_BASE, 32);
setProperty("port-len", TPM_INF_PORT_LEN, TPM_INF_PORT_LEN);
UInt8 *bytep;
UInt16 version;
UInt16 vendorid;
UInt16 productid;
bytep = (UInt8 *)&vendorid;
outb(ENABLE_REGISTER_PAIR, TPM_INF_ADDR);
outb(IDVENL, TPM_INF_ADDR);
bytep++;
*bytep = inb(TPM_INF_DATA);
bytep--;
outb(IDVENH, TPM_INF_ADDR);
*bytep = inb(TPM_INF_DATA);
bytep = (UInt8 *)&productid;
outb(IDPDL, TPM_INF_ADDR);
bytep++;
*bytep = inb(TPM_INF_DATA);
bytep--;
outb(IDPDH, TPM_INF_ADDR);
*bytep = inb(TPM_INF_DATA);
bytep = (UInt8 *)&version;
outb(CHIP_ID1, TPM_INF_ADDR);
bytep++;
*bytep = inb(TPM_INF_DATA);
bytep--;
outb(CHIP_ID2, TPM_INF_ADDR);
*bytep = inb(TPM_INF_DATA);
UInt32 what;
/* ((productid[0] << 8) | productid[1]) */
bytep = (UInt8 *)&productid;
what = *bytep << 8;
bytep++;
what |= *bytep;
switch (what) {
case 6:
setProperty("model", "SLD 9630 TT 1.1");
break;
case 11:
setProperty("model", "SLB 9635 TT 1.2");
break;
default:
setProperty("model", "Unknown");
break;
}
/* ((vendorid[0] << 8 | vendorid[1]) */
bytep = (UInt8 *)&vendorid;
what = *bytep << 8;
bytep++;
what |= *bytep;
if (what == (TPM_INFINEON_DEV_VEN_VALUE)) {
UInt8 ioh, iol;
/* configure TPM with IO-ports */
outb(IOLIMH, TPM_INF_ADDR);
outb(((TPM_INF_BASE >> 8) & 0xff), TPM_INF_DATA);
outb(IOLIML, TPM_INF_ADDR);
outb((TPM_INF_BASE & 0xff), TPM_INF_DATA);
/* control if IO-ports are set correctly */
outb(IOLIMH, TPM_INF_ADDR);
ioh = inb(TPM_INF_DATA);
outb(IOLIML, TPM_INF_ADDR);
iol = inb(TPM_INF_DATA);
if ((ioh << 8 | iol) != TPM_INF_BASE) {
IOLog("I/O ports inconsistent\n");
return KERN_FAILURE;
}
outb(TPM_DAR, TPM_INF_ADDR);
outb(0x01, TPM_INF_DATA);
outb(DISABLE_REGISTER_PAIR, TPM_INF_ADDR);
/* disable RESET, LP and IRQC */
outb(RESET_LP_IRQC_DISABLE, TPM_INF_BASE + CMD);
char tmpbuf[8];
sprintf(tmpbuf, "0x%04x", OSSwapBigToHostInt16(version));
setProperty("version", tmpbuf);
sprintf(tmpbuf, "0x%04x", OSSwapBigToHostInt16(vendorid));
setProperty("vendor-id", tmpbuf);
sprintf(tmpbuf, "0x%04x", OSSwapBigToHostInt16(productid));
setProperty("product-id", tmpbuf);
}
// emptyFifo(1);
tpmLockGroup = lck_grp_alloc_init("com.osxbook.driver.OSXBookTPM",
LCK_GRP_ATTR_NULL);
bufferMutex = lck_mtx_alloc_init(tpmLockGroup, LCK_ATTR_NULL);
tpmMutex = lck_mtx_alloc_init(tpmLockGroup, LCK_ATTR_NULL);
tpmStartup();
IOService *service = waitForService(serviceMatching("IOPMrootDomain"));
pmRootDomain = OSDynamicCast(IOPMrootDomain, service);
if (pmRootDomain != 0) {
pmRootDomain->registerInterestedDriver(this);
}
setProperty("info-url", "http://osxbook.com");
return result;
}
int
com_osxbook_driver_InfineonTPM::emptyFifo(int clear_wrfifo)
{
int i, status, check = 0;
if (clear_wrfifo) {
for (i = 0; i < 4096; i++) {
status = inb(TPM_INF_BASE + WRFIFO);
if (status == 0xff) {
if (check == 5) {
break;
} else {
check++;
}
}
}
}
i = 0;
do {
status = inb(TPM_INF_BASE + RDFIFO);
status = inb(TPM_INF_BASE + STAT);
i++;
if (i == TPM_MAX_TRIES) {
return EIO;
}
} while ((status & (1 << STAT_RDA)) != 0);
return 0;
}
int
com_osxbook_driver_InfineonTPM::doWait(int wait_for_bit)
{
int i, status;
for (i = 0; i < TPM_MAX_TRIES; i++) {
status = inb(TPM_INF_BASE + STAT);
/* check the status-register if wait_for_bit is set */
if (status & 1 << wait_for_bit) {
break;
}
IOSleep(TPM_MSLEEP_TIME);
}
if (i == TPM_MAX_TRIES) { /* timeout occurs */
if (wait_for_bit == STAT_XFE) {
IOLog("Timeout in wait(STAT_XFE)\n");
}
if (wait_for_bit == STAT_RDA) {
IOLog("Timeout in wait(STAT_RDA)\n");
}
return EIO;
}
return 0;
}
void
com_osxbook_driver_InfineonTPM::doWaitAndSend(UInt8 sendByte)
{
doWait(STAT_XFE);
outb(sendByte, TPM_INF_BASE + WRFIFO);
}
void
com_osxbook_driver_InfineonTPM::doWtx(void)
{
number_of_wtx++;
IOLog("Granting WTX (%02d / %02d)\n", number_of_wtx, TPM_MAX_WTX_PACKAGES);
doWaitAndSend(TPM_VL_VER);
doWaitAndSend(TPM_CTRL_WTX);
doWaitAndSend(0x00);
doWaitAndSend(0x00);
IOSleep(TPM_WTX_MSLEEP_TIME);
}
void
com_osxbook_driver_InfineonTPM::doWtxAbort(void)
{
IOLog("Aborting WTX\n");
doWaitAndSend(TPM_VL_VER);
doWaitAndSend(TPM_CTRL_WTX_ABORT);
doWaitAndSend(0x00);
doWaitAndSend(0x00);
number_of_wtx = 0;
IOSleep(TPM_WTX_MSLEEP_TIME);
}
int
com_osxbook_driver_InfineonTPM::doRecv(UInt8 *buf, size_t count, ssize_t *out)
{
int i;
int ret;
UInt32 size = 0;
number_of_wtx = 0;
*out = 0;
recv_begin:
/* start receiving header */
for (i = 0; i < 4; i++) {
ret = doWait(STAT_RDA);
if (ret) {
return EIO;
}
buf[i] = inb(TPM_INF_BASE + RDFIFO);
}
if (buf[0] != TPM_VL_VER) {
IOLog("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++) {
doWait(STAT_RDA);
buf[i] = inb(TPM_INF_BASE + RDFIFO);
}
if ((size == 0x6D00) && (buf[1] == 0x80)) {
IOLog("Error handling on vendor layer!\n");
return EIO;
}
for (i = 0; i < size; i++) {
buf[i] = buf[i + 6];
}
size = size - 6;
*out = size;
return 0;
}
if (buf[1] == TPM_CTRL_WTX) {
IOLog("WTX-package received\n");
if (number_of_wtx < TPM_MAX_WTX_PACKAGES) {
doWtx();
goto recv_begin;
} else {
doWtxAbort();
goto recv_begin;
}
}
if (buf[1] == TPM_CTRL_WTX_ABORT_ACK) {
IOLog("WTX-abort acknowledged\n");
*out = size;
return 0;
}
if (buf[1] == TPM_CTRL_ERROR) {
IOLog("ERROR-package received:\n");
if (buf[4] == TPM_INF_NAK) {
IOLog("Negative acknowledgement - retransmit command!\n");
}
return EIO;
}
return EIO;
}
int
com_osxbook_driver_InfineonTPM::tpmTransmit(UInt8 *buf,
size_t bufsiz,
ssize_t *out)
{
int ret;
ssize_t rc;
UInt32 count;
UInt32 ordinal;
*out = 0;
count = OSSwapBigToHostConstInt32(*((UInt32 *)(buf + 2)));
ordinal = OSSwapBigToHostConstInt32(*((UInt32 *)(buf + 6)));
#ifdef DEBUG
IOLog("transmit: count=%d ordinal=%x\n", count, ordinal);
#endif
if (count == 0) {
return ENODATA;
}
if (count > bufsiz) {
IOLog("invalid count value %x %x \n", count, bufsiz);
return E2BIG;
}
lockTPM();
ret = doSend((UInt8 *) buf, (size_t)count, &rc);
if (ret != 0) {
goto out;
}
// For the Infineon chip, we don't need the irq/jiffies/while loop
// that Linux's tpm_transmit does.
ret = doRecv((UInt8 *) buf, bufsiz, &rc);
out:
unlockTPM();
*out = rc;
#if DEBUG
IOLog("transmit: returning %d (rc = %d)\n", ret, rc);
#endif
return ret;
}
int
com_osxbook_driver_InfineonTPM::doSend(UInt8 *buf, size_t count, ssize_t *out)
{
int i, ret;
UInt8 count_high, count_low, count_4, count_3, count_2, count_1;
*out = 0;
/* Disabling Reset, LP and IRQC */
outb(RESET_LP_IRQC_DISABLE, TPM_INF_BASE + CMD);
ret = emptyFifo(1);
if (ret) {
IOLog("Timeout while clearing FIFO\n");
return EIO;
}
ret = doWait(STAT_XFE);
if (ret) {
IOLog("doWait returned error\n");
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 */
doWaitAndSend(TPM_VL_VER);
doWaitAndSend(TPM_CTRL_DATA);
doWaitAndSend(count_high);
doWaitAndSend(count_low);
/* Sending Data Header */
doWaitAndSend(TPM_VL_VER);
doWaitAndSend(TPM_VL_CHANNEL_TPM);
doWaitAndSend(count_4);
doWaitAndSend(count_3);
doWaitAndSend(count_2);
doWaitAndSend(count_1);
/* Sending Data */
for (i = 0; i < count; i++) {
doWaitAndSend(buf[i]);
}
*out = count;
return 0;
}
void
com_osxbook_driver_InfineonTPM::stop(IOService *provider)
{
if (mmio_addr) {
mmio_addr->release();
}
if (mmio_base) {
mmio_base->release();
}
if (bufferMutex) {
lck_mtx_free(bufferMutex, tpmLockGroup);
bufferMutex = NULL;
}
if (tpmLockGroup) {
lck_grp_free(tpmLockGroup);
tpmLockGroup = NULL;
}
if (pmRootDomain != 0) {
pmRootDomain->deRegisterInterestedDriver(this);
}
super::stop(provider);
}
bool
com_osxbook_driver_InfineonTPM::attach(IOService *provider)
{
bool result = super::attach(provider);
return result;
}
void
com_osxbook_driver_InfineonTPM::detach(IOService *provider)
{
super::detach(provider);
}
IOReturn
com_osxbook_driver_InfineonTPM::powerStateDidChangeTo(IOPMPowerFlags theFlags,
unsigned long,
IOService *)
{
if (theFlags & IOPMPowerOn) {
tpmStartup();
} else {
// Well, a non-ON state really. Nothing to do for now.
}
return IOPMAckImplied;
}